MyWindowController.m

/*
     File: MyWindowController.m 
 Abstract: Header file for this sample's main NSWindowController "TestWindow".
  
  Version: 1.3 
  
 Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple 
 Inc. ("Apple") in consideration of your agreement to the following 
 terms, and your use, installation, modification or redistribution of 
 this Apple software constitutes acceptance of these terms.  If you do 
 not agree with these terms, please do not use, install, modify or 
 redistribute this Apple software. 
  
 In consideration of your agreement to abide by the following terms, and 
 subject to these terms, Apple grants you a personal, non-exclusive 
 license, under Apple's copyrights in this original Apple software (the 
 "Apple Software"), to use, reproduce, modify and redistribute the Apple 
 Software, with or without modifications, in source and/or binary forms; 
 provided that if you redistribute the Apple Software in its entirety and 
 without modifications, you must retain this notice and the following 
 text and disclaimers in all such redistributions of the Apple Software. 
 Neither the name, trademarks, service marks or logos of Apple Inc. may 
 be used to endorse or promote products derived from the Apple Software 
 without specific prior written permission from Apple.  Except as 
 expressly stated in this notice, no other rights or licenses, express or 
 implied, are granted by Apple herein, including but not limited to any 
 patent rights that may be infringed by your derivative works or by other 
 works in which the Apple Software may be incorporated. 
  
 The Apple Software is provided by Apple on an "AS IS" basis.  APPLE 
 MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 
 THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 
 FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 
 OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 
  
 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 
 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 
 MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 
 AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 
 STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 
 POSSIBILITY OF SUCH DAMAGE. 
  
 Copyright (C) 2012 Apple Inc. All Rights Reserved. 
  
 */
 
#import "MyWindowController.h"
#import "GetPathsOperation.h"
#import "LoadOperation.h"
 
@interface MyWindowController ()
{
    NSMutableArray *tableRecords;    // the data source for the table
    
    NSOperationQueue *queue;         // queue of NSOperations (1 for parsing file system, 2+ for loading image files)
    NSTimer *timer;                  // update timer for progress indicator
    
    NSMutableString *imagesFoundStr; // indicates number of images found, (NSTextField is bound to this value)
    
    NSInteger scanCount;
}
 
@property (retain) NSTimer *timer;
 
@end
 
 
@implementation MyWindowController
 
@synthesize timer;
 
// -------------------------------------------------------------------------------
//  awakeFromNib
// -------------------------------------------------------------------------------
- (void)awakeFromNib
{   
    // register for the notification when an image file has been loaded by the NSOperation: "LoadOperation"
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(anyThread_handleLoadedImages:)
                                                 name:kLoadImageDidFinish
                                               object:nil];
    
    // make sure double-click on a table row calls "doubleClickAction"
    [myTableView setTarget:self];
    [myTableView setDoubleAction:@selector(doubleClickAction:)];
}
 
// -------------------------------------------------------------------------------
//  init
// -------------------------------------------------------------------------------
- (id)init
{
    self = [super init];
    if (self)
    {
        queue = [[NSOperationQueue alloc] init];
        tableRecords = [[NSMutableArray alloc] init];
    }
    return self;
}
 
// -------------------------------------------------------------------------------
//  setResultsString:string
// -------------------------------------------------------------------------------
- (void)setResultsString:(NSString *)string
{
    [self willChangeValueForKey:@"imagesFoundStr"];
    imagesFoundStr = [NSMutableString stringWithString:string];
    [self didChangeValueForKey:@"imagesFoundStr"];
}
 
// -------------------------------------------------------------------------------
//  updateCountIndicator
//
//  Canned routine for updating the number of items in the table (used in several places).
// -------------------------------------------------------------------------------
- (void)updateCountIndicator
{
    // set the number of images found indicator string
    NSString *resultStr = [NSString stringWithFormat:@"Images found: %ld", [tableRecords count]];
    [self setResultsString: resultStr];
}
 
// -------------------------------------------------------------------------------
//  applicationShouldTerminateAfterLastWindowClosed:sender
//
//  NSApplication delegate method placed here so the sample conveniently quits
//  after we close the window.
// -------------------------------------------------------------------------------
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
    return YES;
}
 
// -------------------------------------------------------------------------------
//  mainThread_handleLoadedImages:note
//
//  The method used to modify the table's data source on the main thread.
//  This will cause the table to update itself once the NSArrayController is changed.
//
//  The notification contains an NSDictionary containing the image file's info
//  to add to the table view.
// -------------------------------------------------------------------------------
- (void)mainThread_handleLoadedImages:(NSNotification *)note
{
    // Pending NSNotifications can possibly back up while waiting to be executed,
    // and if the user stops the queue, we may have left-over pending
    // notifications to process.
    //
    // So make sure we have "active" running NSOperations in the queue
    // if we are to continuously add found image files to the table view.
    // Otherwise, we let any remaining notifications drain out.
    //
    NSDictionary *notifData = [note userInfo];
    
    NSNumber *loadScanCountNum = [notifData valueForKey:kScanCountKey];
    NSInteger loadScanCount = [loadScanCountNum integerValue];
    
    if ([myStopButton isEnabled])
    {
        // make sure the current scan matches the scan of our loaded image
        if (scanCount == loadScanCount)
        {
            [tableRecords addObject:notifData];
            [myTableView reloadData];
            
            // set the number of images found indicator string
            [self updateCountIndicator];
        }
    }
}
 
// -------------------------------------------------------------------------------
//  anyThread_handleLoadedImages:note
//
//  This method is called from any possible thread (any NSOperation) used to 
//  update our table view and its data source.
//  
//  The notification contains the NSDictionary containing the image file's info
//  to add to the table view.
// -------------------------------------------------------------------------------
- (void)anyThread_handleLoadedImages:(NSNotification *)note
{
    // update our table view on the main thread
    [self performSelectorOnMainThread:@selector(mainThread_handleLoadedImages:) withObject:note waitUntilDone:NO];
}
 
// -------------------------------------------------------------------------------
//  windowShouldClose:sender
// -------------------------------------------------------------------------------
- (BOOL)windowShouldClose:(id)sender
{
    // are you sure you want to close, (threads running)
    NSInteger numOperationsRunning = [[queue operations] count];
    
    if (numOperationsRunning > 0)
    {
        NSAlert *alert = [NSAlert alertWithMessageText:@"Image files are currently loading."
                                         defaultButton:@"OK"
                                       alternateButton:nil
                                           otherButton:nil
                             informativeTextWithFormat:@"Please click the \"Stop\" button before closing."];
        [alert beginSheetModalForWindow:[self window] modalDelegate:nil didEndSelector:nil contextInfo:nil];
    }
    
    return (numOperationsRunning == 0);
}
 
// -------------------------------------------------------------------------------
//  loadFileURLs:fromURL
// -------------------------------------------------------------------------------
- (void)loadFileURLs:(NSURL *)fromURL
{
    [queue cancelAllOperations];
    
    // start the GetPathsOperation with the root path to start the search
    GetPathsOperation *getPathsOp = [[GetPathsOperation alloc] initWithRootURL:fromURL queue:queue scanCount:scanCount];
    
    [queue addOperation:getPathsOp];    // this will start the "GetPathsOperation"
}
 
 
#pragma mark - Actions
 
// -------------------------------------------------------------------------------
//  doubleClickAction:sender
//
//  Inspect our selected objects (user double-clicked them).
// -------------------------------------------------------------------------------
- (void)doubleClickAction:(id)sender
{
    NSTableView *theTableView = (NSTableView *)sender;
    NSInteger selectedRow = [theTableView selectedRow];
    if (selectedRow != -1)
    {
        NSDictionary *objectDict = [tableRecords objectAtIndex: selectedRow];
        if (objectDict != nil)
        {
            NSString *pathStr = [objectDict valueForKey:kPathKey];
            [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:pathStr]];
        }
    }
}
 
// -------------------------------------------------------------------------------
//  stopAction:sender
// -------------------------------------------------------------------------------
- (IBAction)stopAction:(id)sender
{
    [queue cancelAllOperations];
    
    [myStopButton setEnabled:NO];
    [myStartButton setEnabled:YES];
    
    [myProgressInd setHidden:YES];
    [myProgressInd stopAnimation:self];
    
    [self updateCountIndicator];
}
 
// -------------------------------------------------------------------------------
//  startAction:sender
// -------------------------------------------------------------------------------
- (IBAction)startAction:(id)sender
{   
    NSOpenPanel *openPanel = [[NSOpenPanel alloc] init];
    
    [openPanel setResolvesAliases:YES];
    [openPanel setCanChooseDirectories:YES];
    [openPanel setAllowsMultipleSelection:NO];
    [openPanel setCanChooseFiles:NO];
    [openPanel setPrompt:@"Choose"];
    [openPanel setMessage:@"Choose a directory that has a large number image files:"];
    [openPanel setTitle:@"Choose"];
    
    [openPanel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger result) {
        if (result == NSFileHandlingPanelOKButton)
        {
            scanCount++;
            
            // user has chosen a directory, start finding image files:
            
            [tableRecords removeAllObjects];    // clear the table data
            [myTableView reloadData];
            
            [self updateCountIndicator];
            
            [myStopButton setEnabled:YES];
            [myStartButton setEnabled:NO];
            
            [myProgressInd setHidden:NO];
            [myProgressInd startAnimation:self];
            
            [self loadFileURLs:[openPanel URL]];    // start the file search NSOperation
            
            // schedule our update timer for our UI
            self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                                          target:self
                                                        selector:@selector(updateProgress:)
                                                        userInfo:nil
                                                         repeats:YES];
        }
    }];
}
 
 
#pragma mark - Timer Support
 
// -------------------------------------------------------------------------------
//  updateProgress:t
// -------------------------------------------------------------------------------
-(void)updateProgress:(NSTimer *)t
{
    if ([[queue operations] count] == 0)
    {
        [timer invalidate];
        self.timer = nil;
        
        [myProgressInd stopAnimation:self];
        [myProgressInd setHidden:YES];
        [myStopButton setEnabled:NO];
        [myStartButton setEnabled:YES];
        
        // set the number of images found indicator string
        [self updateCountIndicator];
    }
}
 
 
#pragma mark - Data Source
 
// -------------------------------------------------------------------------------
//  numberOfRowsInTableView:aTableView
// -------------------------------------------------------------------------------
- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
    return [tableRecords count];
}
 
// -------------------------------------------------------------------------------
//  objectValueForTableColumn:aTableColumn:row
// -------------------------------------------------------------------------------
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
    id theValue = nil;
    if (tableRecords.count > 0)
    {
        id theRecord = [tableRecords objectAtIndex:rowIndex];
        theValue = [theRecord objectForKey:[aTableColumn identifier]];
    }
    return theValue;
}
 
// -------------------------------------------------------------------------------
//  sortWithDescriptor:descriptor
// -------------------------------------------------------------------------------
- (void)sortWithDescriptor:(id)descriptor
{
    NSMutableArray *sorted = [[NSMutableArray alloc] initWithCapacity:1];
    [sorted addObjectsFromArray:[tableRecords sortedArrayUsingDescriptors:[NSArray arrayWithObject:descriptor]]];
    [tableRecords removeAllObjects];
    [tableRecords addObjectsFromArray:sorted];
    [myTableView reloadData];
}
 
// -------------------------------------------------------------------------------
//  didClickTableColumn:tableColumn
// -------------------------------------------------------------------------------
- (void)tableView:(NSTableView *)inTableView didClickTableColumn:(NSTableColumn *)tableColumn
{   
    NSArray *allColumns=[inTableView tableColumns];
    NSInteger i;
    for (i=0; i<[inTableView numberOfColumns]; i++) 
    {
        if ([allColumns objectAtIndex:i]!=tableColumn)
        {
            [inTableView setIndicatorImage:nil inTableColumn:[allColumns objectAtIndex:i]];
        }
    }
    [inTableView setHighlightedTableColumn:tableColumn];
    
    if ([inTableView indicatorImageInTableColumn:tableColumn] != [NSImage imageNamed:@"NSAscendingSortIndicator"])
    {
        [inTableView setIndicatorImage:[NSImage imageNamed:@"NSAscendingSortIndicator"] inTableColumn:tableColumn];  
        NSSortDescriptor *sortDesc = [[NSSortDescriptor alloc] initWithKey:[tableColumn identifier] ascending:YES];
        [self sortWithDescriptor:sortDesc];
    }
    else
    {
        [inTableView setIndicatorImage:[NSImage imageNamed:@"NSDescendingSortIndicator"] inTableColumn:tableColumn];
        NSSortDescriptor *sortDesc = [[NSSortDescriptor alloc] initWithKey:[tableColumn identifier] ascending:NO];
        [self sortWithDescriptor:sortDesc];
    }
}
 
@end