MyWindowController.m

/*
 Copyright (C) 2015 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 Sample's main NSWindowController
 */
 
#import "MyWindowController.h"
 
@interface MyWindowController ()
 
@property IBOutlet NSWindow *simpleSheet;
@property (weak) IBOutlet NSSearchField *searchField;
 
@property (nonatomic) NSMutableArray *allKeywords;
@property NSMutableArray *builtInKeywords;
 
@property BOOL completePosting;
@property BOOL commandHandling;
 
@end
 
 
#pragma mark -
 
@implementation MyWindowController
 
// -------------------------------------------------------------------------------
//  awakeFromNib
// -------------------------------------------------------------------------------
- (void)awakeFromNib
{
    // add the searchMenu to this control, allowing recent searches to be added.
    //
    // note that we could build this menu inside our nib, but for clarity we're
    // building the menu in code to illustrate the use of tags:
    //      NSSearchFieldRecentsTitleMenuItemTag, NSSearchFieldNoRecentsMenuItemTag, etc.
    //
    if ([self.searchField respondsToSelector:@selector(setRecentSearches:)])
    {
        NSMenu *searchMenu = [[NSMenu alloc] initWithTitle:@"Search Menu"];
        [searchMenu setAutoenablesItems:YES];
        
        // first add our custom menu item (Important note: "action" MUST be valid or the menu item is disabled)
        NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Custom" action:@selector(actionMenuItem:) keyEquivalent:@""];
        [item setTarget: self];
        [searchMenu insertItem:item atIndex:0];
        
        // add our own separator to keep our custom menu separate
        NSMenuItem *separator =  [NSMenuItem separatorItem];
        [searchMenu insertItem:separator atIndex:1];
 
        NSMenuItem *recentsTitleItem = [[NSMenuItem alloc] initWithTitle:@"Recent Searches" action:nil keyEquivalent:@""];
        // tag this menu item so NSSearchField can use it and respond to it appropriately
        [recentsTitleItem setTag:NSSearchFieldRecentsTitleMenuItemTag];
        [searchMenu insertItem:recentsTitleItem atIndex:2];
        
        NSMenuItem *norecentsTitleItem = [[NSMenuItem alloc] initWithTitle:@"No recent searches" action:nil keyEquivalent:@""];
        // tag this menu item so NSSearchField can use it and respond to it appropriately
        [norecentsTitleItem setTag:NSSearchFieldNoRecentsMenuItemTag];
        [searchMenu insertItem:norecentsTitleItem atIndex:3];
        
        NSMenuItem *recentsItem = [[NSMenuItem alloc] initWithTitle:@"Recents" action:nil keyEquivalent:@""];
        // tag this menu item so NSSearchField can use it and respond to it appropriately
        [recentsItem setTag:NSSearchFieldRecentsMenuItemTag];   
        [searchMenu insertItem:recentsItem atIndex:4];
        
        NSMenuItem *separatorItem = (NSMenuItem*)[NSMenuItem separatorItem];
        // tag this menu item so NSSearchField can use it, by hiding/show it appropriately:
        [separatorItem setTag:NSSearchFieldRecentsTitleMenuItemTag];
        [searchMenu insertItem:separatorItem atIndex:5];
        
        NSMenuItem *clearItem = [[NSMenuItem alloc] initWithTitle:@"Clear" action:nil keyEquivalent:@""];
        [clearItem setTag:NSSearchFieldClearRecentsMenuItemTag];    // tag this menu item so NSSearchField can use it
        [searchMenu insertItem:clearItem atIndex:6];
        
        id searchCell = [self.searchField cell];
        [searchCell setMaximumRecents:20];
        [searchCell setSearchMenuTemplate:searchMenu];
    }
    
    // build the list of keyword strings for our type completion dropdown list in NSSearchField
    _builtInKeywords = [[NSMutableArray alloc] initWithObjects:
                    @"Favorite", @"Favorite1", @"Favorite11", @"Favorite3", @"Vacations1", @"Vacations2",
                    @"Hawaii", @"Family", @"Important", @"Important2", @"Personal", nil];
}
 
// -------------------------------------------------------------------------------
//  applicationShouldTerminateAfterLastWindowClosed:
//
//  NSApplication delegate method placed here so the sample conveniently quits
//  after we close the window.
// -------------------------------------------------------------------------------
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
    return YES;
}
 
 
#pragma mark - Custom sheet
 
// -------------------------------------------------------------------------------
//  sheetDidEnd:sheet:returnCode:returnCode:contextInfo:
// -------------------------------------------------------------------------------
- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
    [sheet orderOut:self];
}
 
// -------------------------------------------------------------------------------
//  sheetDoneButtonAction:sender
// -------------------------------------------------------------------------------
- (IBAction)sheetDoneButtonAction:(id)sender
{
    [NSApp endSheet:self.simpleSheet];
}
 
// -------------------------------------------------------------------------------
//  actionMenuItem:sender
// -------------------------------------------------------------------------------
- (IBAction)actionMenuItem:(id)sender
{
    [NSApp beginSheet:self.simpleSheet modalForWindow:[self window] modalDelegate:self
                didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:nil];
}
 
 
#pragma mark - Keyword search handling
 
// -------------------------------------------------------------------------------
//  allKeywords
// -------------------------------------------------------------------------------
- (NSArray *)allKeywords
{
    if (_allKeywords == nil)
    {
        _allKeywords = [self.builtInKeywords mutableCopy];
        
        [_allKeywords sortUsingComparator:^(NSString *a, NSString *b) {
            return [a localizedStandardCompare:b];
        }];
    }
    
    return _allKeywords;
}
 
// -------------------------------------------------------------------------------
//  control:textView:completions:forPartialWordRange:indexOfSelectedItem:
//
//  Use this method to override NSFieldEditor's default matches (which is a much bigger
//  list of keywords).  By not implementing this method, you will then get back
//  NSSearchField's default feature.
// -------------------------------------------------------------------------------
- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index
{
    NSMutableArray *matches = [[NSMutableArray alloc] init];
    NSString *partialString  = [textView.string substringWithRange:charRange];
    
    NSUInteger rangeOptions = NSAnchoredSearch | NSCaseInsensitiveSearch;
    
    [self.allKeywords enumerateObjectsUsingBlock:^(NSString *keyword, NSUInteger idx, BOOL *stop) {
        
        NSRange searchRange = NSMakeRange(0, keyword.length);
        NSRange foundRange = [keyword rangeOfString:partialString options:rangeOptions range:searchRange];
        
        BOOL partialStringIsMatchForKeyword = YES;
        if (foundRange.location == NSNotFound)
        {
            partialStringIsMatchForKeyword = NO;
        }
        
        if (partialStringIsMatchForKeyword)
        {
            [matches addObject:keyword];
        }
    }];
    
    [matches sortUsingComparator:^(NSString *a, NSString *b) {
        return [a localizedStandardCompare:b];
    }];
    
    return matches;
}
 
// -------------------------------------------------------------------------------
//  controlTextDidChange:notification:
//
//  The text in NSSearchField has changed, try to attempt type completion.
// -------------------------------------------------------------------------------
- (void)controlTextDidChange:(NSNotification *)notification
{
    NSTextView *textView = notification.userInfo[@"NSFieldEditor"];
    
    // prevent calling "complete" too often
    if (!self.completePosting && !self.commandHandling)
    {
        _completePosting = YES;
        [textView complete:nil];
        _completePosting = NO;
    }
}
    
// -------------------------------------------------------------------------------
//  control:textView:commandSelector
//
//  Handle all commend selectors that we can handle here
// -------------------------------------------------------------------------------
- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector
{
    BOOL didPerformRequestedSelectorOnTextView = NO;
    
    if ([textView respondsToSelector:commandSelector])
    {
        _commandHandling = YES;
        
        NSMethodSignature *textViewSelectorMethodSignature = [textView methodSignatureForSelector:commandSelector];
        
        NSInvocation *textViewInvocationForSelector = [NSInvocation invocationWithMethodSignature:textViewSelectorMethodSignature];
        
        [textViewInvocationForSelector setTarget:textView];
        [textViewInvocationForSelector setSelector:commandSelector];
        [textViewInvocationForSelector invoke];
        _commandHandling = NO;
        
        didPerformRequestedSelectorOnTextView = YES;
    }
    
    return didPerformRequestedSelectorOnTextView;
}
 
@end