From 70ba5f7c30ba5c4ed2b6a4bf44e61f9859691401 Mon Sep 17 00:00:00 2001 From: vspader Date: Sun, 18 Feb 2007 17:35:28 +0000 Subject: [PATCH] Added type-select for the file drawer. Hazahs are in order! --- AppController.m | 6 + Cog.xcodeproj/project.pbxproj | 8 + Custom/KFTypeSelectTableView.h | 121 ++ Custom/KFTypeSelectTableView.m | 1272 +++++++++++++++++++ English.lproj/MainMenu.nib/info.nib | 7 +- English.lproj/MainMenu.nib/keyedobjects.nib | Bin 68089 -> 68157 bytes FileDrawer/FileIconCell.m | 1 - FileDrawer/FileOutlineView.m | 40 +- FileDrawer/FileTreeController.m | 44 +- 9 files changed, 1472 insertions(+), 27 deletions(-) create mode 100644 Custom/KFTypeSelectTableView.h create mode 100644 Custom/KFTypeSelectTableView.m diff --git a/AppController.m b/AppController.m index 34630a4a8..5c83aaf27 100644 --- a/AppController.m +++ b/AppController.m @@ -1,4 +1,5 @@ #import "AppController.h" +#import "KFTypeSelectTableView.h"" @implementation AppController @@ -8,6 +9,11 @@ if (self) { [self initDefaults]; + + /* Use KFTypeSelectTableView as our NSTableView base class to allow type-select searching of all + * table and outline views. + */ + [[KFTypeSelectTableView class] poseAsClass:[NSTableView class]]; } return self; diff --git a/Cog.xcodeproj/project.pbxproj b/Cog.xcodeproj/project.pbxproj index 1f64099c7..d96de0252 100644 --- a/Cog.xcodeproj/project.pbxproj +++ b/Cog.xcodeproj/project.pbxproj @@ -31,6 +31,8 @@ 8E4E7C1B0AA1ED4500D11405 /* file_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E4E7C190AA1ED4500D11405 /* file_gray.png */; }; 8E53E8610A44C11B007E5BCE /* ID3Tag.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8E53E8600A44C11B007E5BCE /* ID3Tag.framework */; }; 8E53E8690A44C121007E5BCE /* ID3Tag.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8E53E8600A44C11B007E5BCE /* ID3Tag.framework */; }; + 8E57824B0B88B7AC00C97376 /* KFTypeSelectTableView.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8E5782490B88B7AC00C97376 /* KFTypeSelectTableView.h */; }; + 8E57824C0B88B7AC00C97376 /* KFTypeSelectTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E57824A0B88B7AC00C97376 /* KFTypeSelectTableView.m */; }; 8E6889240AAA403C00AD3950 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8E6889230AAA403C00AD3950 /* Carbon.framework */; }; 8E6A8E2C0A0D8A68002ABE9C /* CoreAudioFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E6A8E280A0D8A68002ABE9C /* CoreAudioFile.m */; }; 8E6A8E380A0D8AD8002ABE9C /* CoreAudioUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E6A8E360A0D8AD8002ABE9C /* CoreAudioUtils.m */; }; @@ -192,6 +194,7 @@ 171678BF0AC8C39E00C28CF3 /* SmartFolderNode.h in CopyFiles */, 8E76ED760B877C0700494D51 /* AMRemovableColumnsTableView.h in CopyFiles */, 8E76ED780B877C0700494D51 /* AMRemovableTableColumn.h in CopyFiles */, + 8E57824B0B88B7AC00C97376 /* KFTypeSelectTableView.h in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -227,6 +230,8 @@ 8E4E7C180AA1ED4500D11405 /* file_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = file_blue.png; sourceTree = ""; }; 8E4E7C190AA1ED4500D11405 /* file_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = file_gray.png; sourceTree = ""; }; 8E53E8600A44C11B007E5BCE /* ID3Tag.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ID3Tag.framework; path = Libraries/ID3Tag/build/Release/ID3Tag.framework; sourceTree = ""; }; + 8E5782490B88B7AC00C97376 /* KFTypeSelectTableView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = KFTypeSelectTableView.h; sourceTree = ""; }; + 8E57824A0B88B7AC00C97376 /* KFTypeSelectTableView.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.objc; fileEncoding = 30; path = KFTypeSelectTableView.m; sourceTree = ""; }; 8E643DF20A2B585600844A28 /* GameFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GameFile.h; sourceTree = ""; }; 8E643DF30A2B585600844A28 /* GameFile.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = GameFile.mm; sourceTree = ""; }; 8E6889230AAA403C00AD3950 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = ""; }; @@ -532,6 +537,8 @@ 8E75751A09F31D5A0080F1EE /* Custom */ = { isa = PBXGroup; children = ( + 8E5782490B88B7AC00C97376 /* KFTypeSelectTableView.h */, + 8E57824A0B88B7AC00C97376 /* KFTypeSelectTableView.m */, 8E76ED720B877C0700494D51 /* AMRemovableColumnsTableView.h */, 8E76ED730B877C0700494D51 /* AMRemovableColumnsTableView.m */, 8E76ED740B877C0700494D51 /* AMRemovableTableColumn.h */, @@ -902,6 +909,7 @@ 171678C00AC8C39E00C28CF3 /* SmartFolderNode.m in Sources */, 8E76ED770B877C0700494D51 /* AMRemovableColumnsTableView.m in Sources */, 8E76ED790B877C0700494D51 /* AMRemovableTableColumn.m in Sources */, + 8E57824C0B88B7AC00C97376 /* KFTypeSelectTableView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Custom/KFTypeSelectTableView.h b/Custom/KFTypeSelectTableView.h new file mode 100644 index 000000000..56166bf0f --- /dev/null +++ b/Custom/KFTypeSelectTableView.h @@ -0,0 +1,121 @@ +// +// KFTypeSelectTableView.h +// KFTypeSelectTableView v1.0.4 +// +// Keyboard navigation enabled table view. Suitable for +// class posing as well as normal use. +// +// All delegate methods are optional, except you need to implement typeSelectTableView:stringValueForTableColumn:row: +// if you're using bindings to supply the table view with data. +// +// ------------------------------------------------------------------------ +// Copyright (c) 2005, Ken Ferry All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// (1) Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// (2) Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// (3) Neither Ken Ferry's name nor the names of other contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ------------------------------------------------------------------------ +// + +#import + +#pragma mark constants + +typedef enum KFTypeSelectMatchAlgorithm { + KFSubstringMatchAlgorithm = 0, + KFPrefixMatchAlgorithm = 1 +} KFTypeSelectMatchAlgorithm; + +@interface KFTypeSelectTableView : NSTableView + +#pragma mark action methods + +// these beep if the operation cannot be performed +- (void)findNext:(id)sender; +- (void)findPrevious:(id)sender; + +#pragma mark accessors +// KVO-compliant +- (NSString *)pattern; + +// a tableview with no match algorithm set uses defaultMatchAlgorithm +// defaultMatchAlgorithm defaults to KFPrefixMatchAlgorithm ++ (KFTypeSelectMatchAlgorithm)defaultMatchAlgorithm; ++ (void)setDefaultMatchAlgorithm:(KFTypeSelectMatchAlgorithm)algorithm; + +- (KFTypeSelectMatchAlgorithm)matchAlgorithm; +- (void)setMatchAlgorithm:(KFTypeSelectMatchAlgorithm)algorithm; + +// defaults to NO +- (BOOL)searchWraps; +- (void)setSearchWraps:(BOOL)flag; + +// supply a set of identifiers to limit columns searched for match. +// Only columns with identifiers in the provided set are searched. +// nil identifiers means search all columns. defaults to nil. +- (NSSet *)searchColumnIdentifiers; +- (void)setSearchColumnIdentifiers:(NSSet *)identifiers; + +@end + +@interface NSObject (KFTypeSelectTableViewDelegate) + +#pragma mark configuration methods + +// Implement this method if the table uses bindings for data. +// Use something like +// return [[[arrayController arrangedObjects] objectAtIndex:row] valueForKey:[column identifier]]; +// Could also use it to supply string representations for non-string data, or to search only part of visible text. +- (NSString *)typeSelectTableView:(id)tableView stringValueForTableColumn:(NSTableColumn *)column row:(int)row; + +// defaults to YES +- (BOOL)typeSelectTableViewSearchTopToBottom:(id)tableView; + + // defaults to first or last row, depending on direction of search +- (int)typeSelectTableViewInitialSearchRow:(id)tableView; + +// A hook for cases (like mail plugin) where there's no good place to configure the table. +// Will be called before type-select is used with any particular delegate. +- (void)configureTypeSelectTableView:(id)tableView; + +#pragma mark reporting methods +// pattern of @"" indicates no search, anything else means a search is in progress +// userInfo dictionary has @"oldPattern" key +// this notification is sent +// when a search begins or is modified +// when a search is cancelled +// x seconds after a search either succeeds or fails, where x is a timeout period +- (void)typeSelectTableViewPatternDidChange:(NSNotification *)aNotification; +- (void)typeSelectTableView:(id)tableView didFindMatch:(NSString *)match range:(NSRange)matchedRange forPattern:(NSString *)pattern; +- (void)typeSelectTableView:(id)tableView didFailToFindMatchForPattern:(NSString *)pattern; // fallback is a beep if delegate does not implement + +@end + +#pragma mark notifications +// delegate automatically receives this notification. See delegate method above. +extern NSString *KFTypeSelectTableViewPattenDidChangeNotification; diff --git a/Custom/KFTypeSelectTableView.m b/Custom/KFTypeSelectTableView.m new file mode 100644 index 000000000..227334695 --- /dev/null +++ b/Custom/KFTypeSelectTableView.m @@ -0,0 +1,1272 @@ +// +// KFTypeSelectTableView.m +// KFTypeSelectTableView v1.0.4 +// +// ------------------------------------------------------------------------ +// Copyright (c) 2005, Ken Ferry All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// (1) Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// (2) Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// (3) Neither Ken Ferry's name nor the names of other contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ------------------------------------------------------------------------ + + +#import +#import +#include +#include + +static uint64_t SecondsToMachAbsolute(double seconds); + +NSString *KFTypeSelectTableViewPatternDidChangeNotification = @"KFTypeSelectTableViewPatternDidChange"; + +/* NOTE - because of behavior detailed at cocoadev.com/index.pl?PosingWithCategoriesSuperGotcha, + * it's important that private methods be _implemented_ in the main implementation of the class, + * not in a category. It's okay for the declarations to be in a category, as below. + * (Summary of link: messages to super act like messages to self in categories on a posing class + * in system 10.3) + */ +@interface KFTypeSelectTableView (Private) + +// responding to events +static BOOL KFKeyEventIsBeginFindEvent(NSEvent *keyEvent); +static BOOL KFKeyEventIsExtendFindEvent(NSEvent *keyEvent); +static BOOL KFKeyEventIsFindNextEvent(NSEvent *keyEvent); +static BOOL KFKeyEventIsFindPreviousEvent(NSEvent *keyEvent); +static BOOL KFKeyEventIsDeleteEvent(NSEvent *keyEvent); +static BOOL KFKeyEventIsCancelEvent(NSEvent *keyEvent); + +// finding strings +- (void)kfFindPattern:(NSString *)pattern + initialRow:(int)initialRow + topToBottom:(BOOL)topToBottom + allowExtension:(BOOL)allowPatternExtension; +- (BOOL)kfWorkUnitGetMatch:(NSString **)match + range:(NSRange *)matchRange + lastSearchedRow:(int *)lastSearchedRow + forPattern:(NSString *)pattern + matchOptions:(unsigned)patternMatchOptions + initialRow:(int)initialRow + boundaryRow:(int)boundaryRow + rowIncrement:(int)rowIncrement + searchColumns:(NSArray *)searchColumns + timeout:(uint64_t)timeout; +- (BOOL)kfShouldAcceptMatch:(NSString *)match + range:(NSRange)matchedRange + inRow:(int)row; +- (BOOL)kfCanPerformTypeSelect; +- (BOOL)kfSelectionShouldChange; +- (BOOL)kfCanGetTableData; +- (NSString *)kfStringValueForTableColumn:(NSTableColumn *)column row:(int)row; +- (NSArray *)kfSearchColumns; +- (BOOL)kfSearchTopToBottom; +- (int)kfInitialRowForNewSearch; + +// taking action +- (void)kfPatternDidChange:(id)sender; +- (void)kfDidFindMatch:(NSString *)match + range:(NSRange)matchedRange + inRow:(int)row; +- (void)kfDidFailToFindMatchSearchingToRow:(int)row; +- (void)kfResetSearch; +- (void)kfConfigureDelegateIfNeeded; + +// utility +- (BOOL)kfRowIsVisible:(int)row; +- (void)kfScrollRectToCenter:(NSRect)aRect vertical:(BOOL)scrollVertical horizontal:(BOOL)scrollHorizontal; + +// simulated ivars infrastructure +- (NSMutableDictionary *)kfSimulatedIvars; +- (id)kfIdentifier; +- (void)kfSetUpSimulatedIvars; +- (void)kfTearDownSimulatedIvars; + +// accessors +- (int)kfSavedRowForExtensionSearch; +- (void)setKfSavedRowForExtensionSearch:(int)row; +- (NSString *)kfLastSuccessfullyMatchedPattern; +- (void)setKfLastSuccessfullyMatchedPattern:(NSString *)string; +- (BOOL)kfCanExtendFind; +- (void)setKfCanExtendFind:(BOOL)flag; +- (id)kfLastConfiguredDelegate; +- (void)setKfLastConfiguredDelegate:(id)anObject; +- (NSInvocation *)kfTimeoutInvocation; +- (void)setKfTimeoutInvocation:(NSInvocation *)anInvocation; +- (void)setPattern:(NSString *)pattern; +@end + + +@implementation KFTypeSelectTableView + +#pragma mark - +#pragma mark SETUP/TEARDOWN +#pragma mark - + + +// Note: don't use init. Won't receive it for preexisting objects when posing. + +- (void)dealloc +{ + NSInvocation *timeoutInvocation = [self kfTimeoutInvocation]; + [[timeoutInvocation class] cancelPreviousPerformRequestsWithTarget:[self kfTimeoutInvocation] + selector:@selector(invoke) + object:nil]; + [self kfTearDownSimulatedIvars]; + [super dealloc]; +} + +#pragma mark - +#pragma mark BODY +#pragma mark - + +#pragma mark responding to events + +- (void)keyDown:(NSEvent *)keyEvent +{ + // Will we drop this event to super? + BOOL eatEvent = NO; + + if ([self kfCanPerformTypeSelect] && ([[self window] firstResponder] == self)) + { + BOOL canExtendFind = [self kfCanExtendFind]; + + if (canExtendFind && KFKeyEventIsExtendFindEvent(keyEvent)) + { + NSText *fieldEditor = [[self window] fieldEditor:YES forObject:self]; + [fieldEditor interpretKeyEvents:[NSArray arrayWithObject:keyEvent]]; + + [self kfFindPattern:[fieldEditor string] + initialRow:[self kfSavedRowForExtensionSearch] + topToBottom:[self kfSearchTopToBottom] + allowExtension:YES]; + eatEvent = YES; + } + else if (KFKeyEventIsBeginFindEvent(keyEvent)) + { + NSText *fieldEditor = [[self window] fieldEditor:YES forObject:self]; + [fieldEditor setString:@""]; + [fieldEditor interpretKeyEvents:[NSArray arrayWithObject:keyEvent]]; + + NSString *newPattern = [fieldEditor string]; + + if (![newPattern isEqualToString:@""]) + { + [self kfFindPattern:[fieldEditor string] + initialRow:[self kfInitialRowForNewSearch] + topToBottom:[self kfSearchTopToBottom] + allowExtension:YES]; + } + + eatEvent = YES; + } + else if (canExtendFind && KFKeyEventIsDeleteEvent(keyEvent)) + { + // User might expect us to knock a character off the pattern - that'd be dangerous. + // If the user mistimed he could trigger a table view delete action. + // Best to squelch the behavior by not doing anything useful. + + eatEvent = YES; + } + else if (KFKeyEventIsFindNextEvent(keyEvent)) + { + [self findNext:self]; + eatEvent = YES; + } + else if (KFKeyEventIsFindPreviousEvent(keyEvent)) + { + [self findPrevious:self]; + eatEvent = YES; + } + else if (KFKeyEventIsCancelEvent(keyEvent)) + { + // this is superfluous in 10.2 and 10.3, but may be useful on systems prior to + // 10.2. I haven't had a chance to find out. + [self cancelOperation:self]; + eatEvent = YES; + } + } + + if (!eatEvent) + { + // FIXME - hack + // I can't find a decent way to clear a hanging dead-key (i.e. option-e) in kfResetSearch. + // Sending an event following a dead key event through interpretKeyEvents is the + // only thing I've found to do it, so we make sure that any keyEvent that we don't understand + // goes through the field editor's interpretKeyEvents. It won't cause any damage because the field + // editor has no delegate and will be cleared before it's used again anyway. + // Without this workaround, entering "option-e, f" will stick this table in a state where all + // key-events start with character "«", which means type-select won't work. The state is only + // exited when a different control starts processing text. + // + // This workaround kills the above problem, but is suboptimal in that a dead-key never times out (besides just + // being nasty). + NSText *fieldEditor = [[self window] fieldEditor:YES forObject:self]; + [fieldEditor interpretKeyEvents:[NSArray arrayWithObject:keyEvent]]; + // end hack + + [self setKfCanExtendFind:NO]; + [super keyDown:keyEvent]; + } +} + +- (void)cancelOperation:(id)sender +{ + [self kfResetSearch]; +} + +// 10.2 private version of cancelOperation +// I'm not sure how far back this will work. +- (void)_cancelKey:(id)sender +{ + [self cancelOperation:sender]; +} + + +// an outline view catches control-down and control-up +// and uses them to select next and previous rows (same as plain up and down) +// We can catch the events by implementing moveDown and moveUp. + +- (void)moveDown:(id)sender +{ + NSEvent *currentEvent = [NSApp currentEvent]; + if ([currentEvent type] == NSKeyDown && KFKeyEventIsFindNextEvent(currentEvent)) + { + [self findNext:self]; + } +} + +- (void)moveUp:(id)sender +{ + NSEvent *currentEvent = [NSApp currentEvent]; + if ([currentEvent type] == NSKeyDown && KFKeyEventIsFindPreviousEvent(currentEvent)) + { + [self findPrevious:self]; + } +} + + +- (BOOL)resignFirstResponder +{ + BOOL shouldResign = [super resignFirstResponder]; + if (shouldResign) + { + [self setKfCanExtendFind:NO]; + } + + return shouldResign; +} + +static unsigned int modifierFlagsICareAboutMask = NSCommandKeyMask | NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSFunctionKeyMask; + +// yes if every character in the event is alphanumeric and no command, control or function modifiers +static BOOL KFKeyEventIsBeginFindEvent(NSEvent *keyEvent) +{ + unsigned int modifiers = [keyEvent modifierFlags] & modifierFlagsICareAboutMask; + NSString *characters = [keyEvent characters]; + int numCharacters = [characters length]; + + if ((modifiers & (NSCommandKeyMask | NSControlKeyMask | NSFunctionKeyMask)) != 0) + { + return NO; + } + + NSMutableCharacterSet *beginFindCharacterSet = [[[NSCharacterSet alphanumericCharacterSet] mutableCopy] autorelease]; + [beginFindCharacterSet formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]]; + + unichar character; + int i; + for (i = 0; i < numCharacters; i++) + { + character = [characters characterAtIndex:i]; + if (![beginFindCharacterSet characterIsMember:character]) + { + return NO; + } + } + + return YES; +} + +// yes if every character in the event is alphanumeric, punctuation or a space, and no command, control or function modifiers +static BOOL KFKeyEventIsExtendFindEvent(NSEvent *keyEvent) +{ + unsigned int modifiers = [keyEvent modifierFlags] & modifierFlagsICareAboutMask; + NSString *characters = [keyEvent characters]; + int numCharacters = [characters length]; + + if ((modifiers & (NSCommandKeyMask | NSControlKeyMask | NSFunctionKeyMask)) != 0) + { + return NO; + } + + NSMutableCharacterSet *extendFindCharacterSet = [[[NSCharacterSet alphanumericCharacterSet] mutableCopy] autorelease]; + [extendFindCharacterSet formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]]; + [extendFindCharacterSet addCharactersInString:@" "]; + + unichar character; + int i; + for (i = 0; i < numCharacters; i++) + { + character = [characters characterAtIndex:i]; + if (![extendFindCharacterSet characterIsMember:character]) + { + return NO; + } + } + + return YES; +} + +static BOOL KFKeyEventIsFindNextEvent(NSEvent *keyEvent) +{ + unsigned int modifiers = [keyEvent modifierFlags] & modifierFlagsICareAboutMask; + NSString *characters = [keyEvent characters]; + int numCharacters = [characters length]; + + if (numCharacters == 1 && [characters characterAtIndex:0] == NSDownArrowFunctionKey && modifiers == (NSControlKeyMask | NSFunctionKeyMask)) + { + return YES; + } + + + return NO; +} + +static BOOL KFKeyEventIsFindPreviousEvent(NSEvent *keyEvent) +{ + unsigned int modifiers = [keyEvent modifierFlags] & modifierFlagsICareAboutMask; + NSString *characters = [keyEvent characters]; + int numCharacters = [characters length]; + + if (numCharacters == 1 && [characters characterAtIndex:0] == NSUpArrowFunctionKey && modifiers == (NSControlKeyMask | NSFunctionKeyMask)) + { + return YES; + } + + return NO; +} + +static BOOL KFKeyEventIsDeleteEvent(NSEvent *keyEvent) +{ + unsigned int modifiers = [keyEvent modifierFlags] & modifierFlagsICareAboutMask; + NSString *characters = [keyEvent characters]; + int numCharacters = [characters length]; + + if (numCharacters == 1 && [characters characterAtIndex:0] == NSDeleteCharacter && modifiers == 0) + { + return YES; + } + if (numCharacters == 1 && [characters characterAtIndex:0] == NSBackspaceCharacter && modifiers == 0) + { + return YES; + } + + return NO; +} + +static BOOL KFKeyEventIsCancelEvent(NSEvent *keyEvent) +{ + unsigned int modifiers = [keyEvent modifierFlags] & modifierFlagsICareAboutMask; + NSString *characters = [keyEvent characters]; + int numCharacters = [characters length]; + + const unichar EscapeKeyCharacter = 0x1b; + + if ((modifiers == NSCommandKeyMask) && [characters isEqualToString:@"."]) + { + return YES; + } + if (numCharacters == 1 && [characters characterAtIndex:0] == EscapeKeyCharacter && modifiers == 0) + { + return YES; + } + + return NO; +} + + +#pragma mark finding patterns + +- (void)findNext:(id)sender +{ + NSString *lastPattern = [self kfLastSuccessfullyMatchedPattern]; + + if (lastPattern == nil || ![self kfCanPerformTypeSelect]) + { + NSBeep(); + } + else + { + [self kfFindPattern:lastPattern + initialRow:[self selectedRow] + 1 + topToBottom:YES + allowExtension:NO]; + } +} + +- (void)findPrevious:(id)sender +{ + NSString *lastPattern = [self kfLastSuccessfullyMatchedPattern]; + + if (lastPattern == nil || ![self kfCanPerformTypeSelect]) + { + NSBeep(); + } + else + { + [self kfFindPattern:lastPattern + initialRow:[self selectedRow] - 1 + topToBottom:NO + allowExtension:NO]; + } +} + + +- (void)kfFindPattern:(NSString *)pattern + initialRow:(int)initialRow + topToBottom:(BOOL)topToBottom + allowExtension:(BOOL)allowPatternExtension +{ + NSArray *searchColumns = [self kfSearchColumns]; + + BOOL shouldWrap = [self searchWraps]; + unsigned patternMatchOptions; + NSDate *distantPast = [NSDate distantPast]; + NSMutableArray *suspendedEvents = [NSMutableArray array]; + const uint64_t eventCheckFrequency = SecondsToMachAbsolute(.01); + NSString *match = nil; + NSRange matchRange = {0,0}; + + // we'll translate topToBottom into these parameters + // so that we can use a single loop for both directions + int rowIncrement, boundaryRow; + + if (topToBottom) + { + rowIncrement = 1; + boundaryRow = [self numberOfRows]; + initialRow = (initialRow < boundaryRow) ? initialRow : boundaryRow; + initialRow = (initialRow > 0) ? initialRow : 0; + if (initialRow == 0) + shouldWrap = NO; + } + else + { + rowIncrement = -1; + boundaryRow = -1; + initialRow = (initialRow > boundaryRow) ? initialRow : boundaryRow; + initialRow = (initialRow < [self numberOfRows] - 1) ? initialRow : [self numberOfRows] - 1; + if (initialRow == [self numberOfRows] - 1) + shouldWrap = NO; + } + + // keep ivars in sync + [self setPattern:pattern]; + [self setKfCanExtendFind:allowPatternExtension]; + + // set up pattern match options + if ([self matchAlgorithm] == KFPrefixMatchAlgorithm) + { + patternMatchOptions = NSCaseInsensitiveSearch | NSAnchoredSearch; + } + else // substring match + { + patternMatchOptions = NSCaseInsensitiveSearch; + } + + BOOL finished = NO; + int row = initialRow; + while (!finished) + { + // Mail generates 3MB in autoreleased objects in a search through 15000 rows + // there's a noticable pause when they're deallocated. We'll avoid it by using + // our own autorelease pool. + // (Update - implementing typeSelectTableView:stringValueForTableColumn:row: in the mail + // plugin dropped the number of allocations) + // + // Note: checking for new input no more often than 100 times a second drops time spent + // in -[NSApplication nextEventMatchingMask::::] from 30-60% of total function time to .2-.5% + // at a cost of < 1% for timing functions. + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + finished = [self kfWorkUnitGetMatch:&match + range:&matchRange + lastSearchedRow:&row + forPattern:pattern + matchOptions:patternMatchOptions + initialRow:row + boundaryRow:boundaryRow + rowIncrement:rowIncrement + searchColumns:searchColumns + timeout:eventCheckFrequency]; + [match retain]; + [pool release]; + [match autorelease]; + + if (!finished) + row += rowIncrement; + + if (finished && match == nil && shouldWrap) + { + if (topToBottom) + row = 0; + else + row = [self numberOfRows] - 1; + + boundaryRow = initialRow; + shouldWrap = NO; + finished = NO; + } + + if (!finished) + { + NSEvent *keyEvent; + while ((keyEvent = [NSApp nextEventMatchingMask:NSKeyDownMask + untilDate:distantPast // means grab events that have already occurred + inMode:NSEventTrackingRunLoopMode + dequeue:YES]) != nil) + { + if (KFKeyEventIsCancelEvent(keyEvent)) + { + [self kfResetSearch]; + // we intentionally dump the suspended events in this case + return; + } + else if (allowPatternExtension && KFKeyEventIsExtendFindEvent(keyEvent)) + { + NSText *fieldEditor = [[self window] fieldEditor:YES forObject:self]; + [fieldEditor interpretKeyEvents:[NSArray arrayWithObject:keyEvent]]; + pattern = [fieldEditor string]; + [self setPattern:pattern]; + } + else if (KFKeyEventIsDeleteEvent(keyEvent)) + { + // eat the event, do nothing. + // User might expect us to knock a character off the pattern - that'd be dangerous. + // If the user mistimed he could trigger a table view delete action. + // Best to squelch the behavior by not doing anything useful. + } + else + { + [suspendedEvents addObject:keyEvent]; + } + } + } + } + + if (match != nil) + { + [self kfDidFindMatch:match + range:matchRange + inRow:row]; + } + else + { + [self kfDidFailToFindMatchSearchingToRow:row]; + } + + int numSuspendedEvents, i; + numSuspendedEvents = [suspendedEvents count]; + for (i = numSuspendedEvents-1; i >= 0; i--) + { + [NSApp postEvent:[suspendedEvents objectAtIndex:i] atStart:YES]; + } +} + +- (BOOL)kfWorkUnitGetMatch:(NSString **)match + range:(NSRange *)matchRange + lastSearchedRow:(int *)lastSearchedRow + forPattern:(NSString *)pattern + matchOptions:(unsigned)patternMatchOptions + initialRow:(int)initialRow + boundaryRow:(int)boundaryRow + rowIncrement:(int)rowIncrement + searchColumns:(NSArray *)searchColumns + timeout:(uint64_t)timeout // times are mach absolute times +{ + int row, col; + int numCols = [searchColumns count]; + NSString *candidateMatch; + NSRange rangeOfPattern; + + uint64_t stopTime = mach_absolute_time() + timeout; + for (row = initialRow; row != boundaryRow; row += rowIncrement) + { + for (col = 0; col < numCols; col++) + { + candidateMatch = [self kfStringValueForTableColumn:[searchColumns objectAtIndex:col] row:row]; + + rangeOfPattern = [candidateMatch rangeOfString:pattern options:patternMatchOptions]; + if ( (rangeOfPattern.location != NSNotFound) + && [self kfShouldAcceptMatch:candidateMatch range:rangeOfPattern inRow:row]) + { + *match = candidateMatch; + *matchRange = rangeOfPattern; + *lastSearchedRow = row; + return YES; + } + } + + // think of this as part of the loop condition, but we want to make sure that + // the loop completes at least one iteration + if (mach_absolute_time() > stopTime) + { + row += rowIncrement; + break; + } + } + + *match = nil; + *matchRange = NSMakeRange(NSNotFound, 0); + *lastSearchedRow = row - rowIncrement; + return row == boundaryRow; +} + +- (BOOL)kfShouldAcceptMatch:(NSString *)match + range:(NSRange)matchedRange + inRow:(int)row +{ + id delegate = [self delegate]; + + if ( [self isKindOfClass:[NSOutlineView class]] + && [delegate respondsToSelector:@selector(outlineView:shouldSelectItem:)]) + { + return [delegate outlineView:(NSOutlineView *)self shouldSelectItem:[(NSOutlineView *)self itemAtRow:row]]; + } + else if ([delegate respondsToSelector:@selector(tableView:shouldSelectRow:)]) + { + return [delegate tableView:self shouldSelectRow:row]; + } + else + { + return YES; + } +} + +- (NSTimeInterval)kfPatternTimeoutInterval +{ + // from Dan Wood's 'Table Techniques Taught Tastefully', as pointed out by someone + // on cocoadev.com + + // Timeout is two times the key repeat rate "InitialKeyRepeat" user default. + // (converted from sixtieths of a second to seconds), but no more than two seconds. + // This behavior is determined based on Inside Macintosh documentation on the List Manager. + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + int keyThreshTicks = [defaults integerForKey:@"InitialKeyRepeat"]; // undocumented key. Still valid in 10.3. + if (0 == keyThreshTicks) // missing value in defaults? Means user has never changed the default. + { + keyThreshTicks = 35; // apparent default value. translates to 1.17 sec timeout. + } + + return MIN(2.0/60.0*keyThreshTicks, 2.0); +} + +- (BOOL)kfCanPerformTypeSelect +{ + return [self kfCanGetTableData] && [self kfSelectionShouldChange]; +} + +- (BOOL)kfSelectionShouldChange +{ + id delegate = [self delegate]; + + if ( [self isKindOfClass:[NSOutlineView class]] + && [delegate respondsToSelector:@selector(selectionShouldChangeInOutlineView:)]) + { + return [delegate selectionShouldChangeInOutlineView:(NSOutlineView *)self]; + } + else if ([delegate respondsToSelector:@selector(selectionShouldChangeInTableView:)]) + { + return [delegate selectionShouldChangeInTableView:self]; + } + else + { + return YES; + } +} + +- (BOOL)kfCanGetTableData +{ + // First case: datasource implements NSTableViewDataSource protocol. Usually not true when + // table view uses bindings or is actually an outline view. + // Second case: self is an outline view and datasource implements NSOutlineViewDataSource protocol. This could arise when using class posing. + // Third case: our delegate supplies the info we need. + return ([[self dataSource] respondsToSelector:@selector(tableView:objectValueForTableColumn:row:)] || + ([self isKindOfClass:[NSOutlineView class]] && [[self dataSource] respondsToSelector:@selector(outlineView:objectValueForTableColumn:byItem:)]) || + [[self delegate] respondsToSelector:@selector(typeSelectTableView:stringValueForTableColumn:row:)]); +} + +- (NSString *)kfStringValueForTableColumn:(NSTableColumn *)column row:(int)row +{ + // There are three ways we can get this information: (1) our delegate supplies it, (2) our datasource + // supplies it like an NSTableViewDataSource, (3) our datasource supplies it like an NSOutlineViewDataSource + + // could optimize by factoring into three separate methods and precomputing which one to call (from keyDown:). + // current sharking indicates this wouldn't help much. + + id delegate = [self delegate]; + NSString *stringValue = nil; + + if ([delegate respondsToSelector:@selector(typeSelectTableView:stringValueForTableColumn:row:)]) + { + stringValue = [delegate typeSelectTableView:self stringValueForTableColumn:column row:row]; + } + else + { + id objectValue = nil; + id dataSource = [self dataSource]; + + // why do we check our own class? outline view is not bindings enabled, so datasource could be + // acting as a datasource for an outline while being a binding data source for us + if ([self isKindOfClass:[NSOutlineView class]] + && [dataSource respondsToSelector:@selector(outlineView:objectValueForTableColumn:byItem:)]) + { + objectValue = [dataSource outlineView:(NSOutlineView *)self + objectValueForTableColumn:column + byItem:[(NSOutlineView *)self itemAtRow:row]]; + } + else if ([dataSource respondsToSelector:@selector(tableView:objectValueForTableColumn:row:)]) + { + objectValue = [dataSource tableView:self objectValueForTableColumn:column row:row]; + } + + NSCell *dataCell = [column dataCellForRow:row]; + [dataCell setObjectValue:objectValue]; + + // sometimes the delegate changes the cell value in tableView:willDisplayCell:forTableColumn:row: + if ([self isKindOfClass:[NSOutlineView class]] + && [delegate respondsToSelector:@selector(outlineView:willDisplayCell:forTableColumn:item:)]) + { + [delegate outlineView:(NSOutlineView *)self + willDisplayCell:dataCell + forTableColumn:column + item:[(NSOutlineView *)self itemAtRow:row]]; + } + else if ([delegate respondsToSelector:@selector(tableView:willDisplayCell:forTableColumn:row:)]) + { + [delegate tableView:self + willDisplayCell:dataCell + forTableColumn:column + row:row]; + } + + stringValue = [dataCell stringValue]; + } + + if (stringValue == nil) + { + stringValue = @""; + } + + return stringValue; +} + +- (NSArray *)kfSearchColumns +{ + NSArray *searchColumns; + NSSet *searchColumnIdentifiers = [self searchColumnIdentifiers]; + + if (searchColumnIdentifiers != nil) + { + NSMutableArray *partialSearchColumns; + NSArray *candidateColumns = [self tableColumns]; + NSTableColumn *column; + int numCols, col; + + partialSearchColumns = [NSMutableArray array]; + + numCols = [candidateColumns count]; + for (col = 0; col < numCols; col++) + { + column = [candidateColumns objectAtIndex:col]; + if ([searchColumnIdentifiers containsObject:[column identifier]]) + { + [partialSearchColumns addObject:column]; + } + } + + searchColumns = partialSearchColumns; + } + else + { + searchColumns = [self tableColumns]; + } + + return searchColumns; +} + + +- (BOOL)kfSearchTopToBottom +{ + BOOL topToBottom = YES; + + id delegate = [self delegate]; + if ([delegate respondsToSelector:@selector(typeSelectTableViewSearchTopToBottom:)]) + { + topToBottom = [delegate typeSelectTableViewSearchTopToBottom:self]; + } + + return topToBottom; +} + +- (int)kfInitialRowForNewSearch +{ + int row; + + id delegate = [self delegate]; + if ([delegate respondsToSelector:@selector(typeSelectTableViewInitialSearchRow:)]) + { + row = [delegate typeSelectTableViewInitialSearchRow:self]; + } + else + { + if ([self kfSearchTopToBottom]) + { + row = 0; + } + else + { + row = [self numberOfRows] - 1; + } + } + + return row; +} + +#pragma mark taking action + +-(void)kfPatternDidChange:(id)sender +{ + NSInvocation *timeoutInvocation = [self kfTimeoutInvocation]; + [[timeoutInvocation class] cancelPreviousPerformRequestsWithTarget:[self kfTimeoutInvocation] + selector:@selector(invoke) + object:nil]; + + id delegate = [self delegate]; + if ([delegate respondsToSelector:@selector(typeSelectTableViewPatternDidChange:)]) + [delegate typeSelectTableViewPatternDidChange:sender]; +} + +- (void)kfDidFindMatch:(NSString *)match + range:(NSRange)matchedRange + inRow:(int)row +{ + NSString *pattern = [self pattern]; + + // update ivars + [self setKfLastSuccessfullyMatchedPattern:pattern]; + if ([self kfCanExtendFind]) + { + [self setKfSavedRowForExtensionSearch:row]; + } + + // select row + [self selectRow:row byExtendingSelection:NO]; + if (![self kfRowIsVisible:row]) + { + // this is what NSTextView does when it finds patterns, and it's what Mail does + // when moving through message table with up and down arrows + [self kfScrollRectToCenter:[self rectOfRow:row] vertical:YES horizontal:NO]; + } + + // start pattern timeout timer (see kfTimeoutInvocation for details) + [[self kfTimeoutInvocation] performSelector:@selector(invoke) + withObject:nil + afterDelay:[self kfPatternTimeoutInterval] + inModes:[NSArray arrayWithObjects:NSDefaultRunLoopMode, NSModalPanelRunLoopMode, nil]]; + + + // inform the delegate + id delegate = [self delegate]; + if ([delegate respondsToSelector:@selector(typeSelectTableView:didFindMatch:range:forPattern:)]) + { + [delegate typeSelectTableView:self didFindMatch:match range:matchedRange forPattern:pattern]; + } +} + +- (void)kfDidFailToFindMatchSearchingToRow:(int)row +{ + if ([self kfCanExtendFind]) + { + [self setKfSavedRowForExtensionSearch:row]; + } + + // start pattern timeout timer (see kfTimeoutInvocation for details) + [[self kfTimeoutInvocation] performSelector:@selector(invoke) + withObject:nil + afterDelay:[self kfPatternTimeoutInterval] + inModes:[NSArray arrayWithObjects:NSDefaultRunLoopMode, NSModalPanelRunLoopMode, nil]]; + + + id delegate = [self delegate]; + if ([delegate respondsToSelector:@selector(typeSelectTableView:didFailToFindMatchForPattern:)]) + { + [delegate typeSelectTableView:self didFailToFindMatchForPattern:[self pattern]]; + } + else + { + NSBeep(); + } +} + +- (void)kfResetSearch +{ + // note - doesn't clear hanging dead key. See keyDown for discussion and workaround. + [self setPattern:@""]; + [self setKfCanExtendFind:NO]; +} + +// note: don't use setDelegate: to call this. NSOutlineView doesn't call through to +// -[NSTableView setDelegate:], so it messes us up when posing. +- (void)kfConfigureDelegateIfNeeded +{ + id delegate = [self delegate]; + if (delegate != [self kfLastConfiguredDelegate]) + { + // order is important here + // We don't want to go into a recursion if the delegate tries to access a configurable value from + // configureTypeSelectTableView. The delegate is interested in the pre-configuration values anyway. + [self setKfLastConfiguredDelegate:delegate]; + if ([delegate respondsToSelector:@selector(configureTypeSelectTableView:)]) + [delegate configureTypeSelectTableView:self]; + } +} +#pragma mark utility + +- (BOOL)kfRowIsVisible:(int)row +{ + NSScrollView *enclosingScrollView = [self enclosingScrollView]; + if (enclosingScrollView == nil) + { + return NO; + } + else + { + NSRect visibleRect = [enclosingScrollView documentVisibleRect]; + NSRect rowRect = [self rectOfRow:row]; + + // only care about whether we're onscreen vertically + return ( (NSMaxY(visibleRect) >= NSMaxY(rowRect)) + && (NSMinY(visibleRect) <= NSMinY(rowRect))); + } +} + +- (void)kfScrollRectToCenter:(NSRect)aRect vertical:(BOOL)scrollVertical horizontal:(BOOL)scrollHorizontal +{ + NSScrollView *scrollView = [self enclosingScrollView]; + + if (scrollView != nil) + { + NSRect newVisibleRect = [scrollView documentVisibleRect]; + + if (scrollVertical) + newVisibleRect.origin.y += NSMidY(aRect) - NSMidY([scrollView documentVisibleRect]); + if (scrollHorizontal) + newVisibleRect.origin.x += NSMidX(aRect) - NSMidX([scrollView documentVisibleRect]); + + newVisibleRect = NSIntersectionRect(newVisibleRect,[self bounds]); + + [self scrollRectToVisible:newVisibleRect]; + } +} + +#pragma mark - +#pragma mark ACCESSORS +#pragma mark - + +#pragma mark simulated ivars setup + +static NSMutableDictionary *idToSimulatedIvarsMap = nil; + +- (NSMutableDictionary *)kfSimulatedIvars +{ + NSMutableDictionary *simulatedIvars = [idToSimulatedIvarsMap objectForKey:[self kfIdentifier]]; + + if (simulatedIvars == nil) + { + [self kfSetUpSimulatedIvars]; + simulatedIvars = [idToSimulatedIvarsMap objectForKey:[self kfIdentifier]]; + } + + return simulatedIvars; +} + +// can avoid memory allocation if we use CFDictionary or NSMapTable and work with self directly +- (id)kfIdentifier +{ + return [NSValue valueWithPointer:self]; +} + +- (void)kfSetUpSimulatedIvars +{ + // prime idToSimulatedIvarsMap + if (idToSimulatedIvarsMap == nil) + { + idToSimulatedIvarsMap = [[NSMutableDictionary alloc] init]; + } + + // if the simulatedIvars dict doesn't exist yet, create it + NSMutableDictionary *simulatedIvars = [idToSimulatedIvarsMap objectForKey:[self kfIdentifier]]; + if (!simulatedIvars) + { + simulatedIvars = [NSMutableDictionary dictionary]; + [idToSimulatedIvarsMap setObject:simulatedIvars forKey:[self kfIdentifier]]; + } +} + +- (void)kfTearDownSimulatedIvars +{ + [idToSimulatedIvarsMap removeObjectForKey:[self kfIdentifier]]; + + if ([idToSimulatedIvarsMap count] == 0) + { + [idToSimulatedIvarsMap release]; + idToSimulatedIvarsMap = nil; + } +} + +#pragma mark private accessors + +- (int)kfSavedRowForExtensionSearch +{ + int row; + NSNumber *rowNumber = [[self kfSimulatedIvars] objectForKey:@"initialRowForExtensionSearch"]; + + // default value + if (rowNumber == nil) + row = NSNotFound; + else + { + row = [rowNumber intValue]; + } + + return row; +} + +- (void)setKfSavedRowForExtensionSearch:(int)row +{ + [[self kfSimulatedIvars] setObject:[NSNumber numberWithInt:row] + forKey:@"initialRowForExtensionSearch"]; +} + +- (NSString *)kfLastSuccessfullyMatchedPattern +{ + NSString *string = [[self kfSimulatedIvars] objectForKey:@"lastPattern"]; + + // defaults to nil + + return string; +} + +- (void)setKfLastSuccessfullyMatchedPattern:(NSString *)string +{ + if (string == nil) + [[self kfSimulatedIvars] removeObjectForKey:@"lastPattern"]; + else + [[self kfSimulatedIvars] setObject:[[string copy] autorelease] forKey:@"lastPattern"]; +} + +-(BOOL)kfCanExtendFind +{ + NSNumber *canExtendFindNumber = [[self kfSimulatedIvars] objectForKey:@"canExtendFind"]; + + // default value + if (canExtendFindNumber == nil) + return NO; + else + return [canExtendFindNumber boolValue]; +} + +-(void)setKfCanExtendFind:(BOOL)flag +{ + [[self kfSimulatedIvars] setObject:[NSNumber numberWithBool:flag] + forKey:@"canExtendFind"]; +} + +// keep track of the last delegate for which we tried to run configureTypeSelectTableView +- (id)kfLastConfiguredDelegate +{ + return [[[self kfSimulatedIvars] objectForKey:@"lastConfiguredDelegate"] nonretainedObjectValue]; +} + +- (void)setKfLastConfiguredDelegate:(id)anObject +{ + if (anObject == nil) + [[self kfSimulatedIvars] removeObjectForKey:@"lastConfiguredDelegate"]; + else + [[self kfSimulatedIvars] setObject:[NSValue valueWithNonretainedObject:anObject] forKey:@"lastConfiguredDelegate"]; +} + +// +// the timeoutNotification encapsulates the message that we send to self when the timeout +// (for clearing the input buffer) expires. Invoke it with a delayed message send. +// +// Why do we use an invocation instead of doing the delayed message send directly? +// -[NSObject performSelector:afterDelay:] retains the receiver until after the message send is +// performed. That can extend the life of the tableView past the life of the delegate, which is +// bad mojo. Yielded a crash in Adium. By buffering with an invocation that doesn't retain its +// target, we can avoid the problem. Any pending delayed messages are cancelled when the table +// table is dealloc'd. +// +- (NSInvocation *)kfTimeoutInvocation +{ + NSInvocation *invocation = [[self kfSimulatedIvars] objectForKey:@"timeoutInvocation"]; + + // defaults to a message to kfResetSearch + if (invocation == nil) + { + SEL selector = @selector(kfResetSearch); + invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:selector]]; + [invocation setTarget:self]; + [invocation setSelector:selector]; + + [self setKfTimeoutInvocation:invocation]; + } + + return invocation; +} + +- (void)setKfTimeoutInvocation:(NSInvocation *)anInvocation +{ + if (anInvocation == nil) + [[self kfSimulatedIvars] removeObjectForKey:@"timeoutInvocation"]; + else + [[self kfSimulatedIvars] setObject:anInvocation forKey:@"timeoutInvocation"]; +} + + +-(void)setPattern:(NSString *)pattern +{ + NSString *oldPattern = [self pattern]; + + if (pattern == nil) + pattern = @""; + + [[self kfSimulatedIvars] setObject:[[pattern copy] autorelease] + forKey:@"pattern"]; + + NSNotification *patternChangedNotification = [NSNotification notificationWithName:KFTypeSelectTableViewPatternDidChangeNotification + object:self + userInfo:[NSDictionary dictionaryWithObject:oldPattern forKey:@"oldPattern"]]; + [self kfPatternDidChange:patternChangedNotification]; + [[NSNotificationCenter defaultCenter] postNotification:patternChangedNotification]; +} + +#pragma mark public accessors + +-(NSString *)pattern +{ + NSString *pattern = [[self kfSimulatedIvars] objectForKey:@"pattern"]; + + if (pattern == nil) + pattern = @""; + + return [[pattern retain] autorelease]; +} + +static KFTypeSelectMatchAlgorithm defaultMatchAlgorith = KFPrefixMatchAlgorithm; ++ (KFTypeSelectMatchAlgorithm)defaultMatchAlgorithm +{ + return defaultMatchAlgorith; +} + ++ (void)setDefaultMatchAlgorithm:(KFTypeSelectMatchAlgorithm)algorithm +{ + defaultMatchAlgorith = algorithm; +} + + +-(KFTypeSelectMatchAlgorithm)matchAlgorithm +{ + [self kfConfigureDelegateIfNeeded]; + + NSNumber *algorithmNumber = [[self kfSimulatedIvars] objectForKey:@"matchAlgorithm"]; + + if (algorithmNumber == nil) + return defaultMatchAlgorith; + else + return [algorithmNumber intValue]; +} + +-(void)setMatchAlgorithm:(KFTypeSelectMatchAlgorithm)algorithm +{ + [[self kfSimulatedIvars] setObject:[NSNumber numberWithInt:algorithm] forKey:@"matchAlgorithm"]; +} + +- (BOOL)searchWraps +{ + [self kfConfigureDelegateIfNeeded]; + + NSNumber *searchWraphsNum = [[self kfSimulatedIvars] objectForKey:@"searchWraps"]; + + // default value + if (searchWraphsNum == nil) + return NO; + else + return [searchWraphsNum boolValue]; +} + +-(void)setSearchWraps:(BOOL)flag +{ + [[self kfSimulatedIvars] setObject:[NSNumber numberWithBool:flag] + forKey:@"searchWraps"]; +} + + +- (NSSet *)searchColumnIdentifiers +{ + [self kfConfigureDelegateIfNeeded]; + + return [[self kfSimulatedIvars] objectForKey:@"searchColumnIdentifiers"]; +} + +- (void)setSearchColumnIdentifiers:(NSSet *)identifiers +{ + if (identifiers == nil) + [[self kfSimulatedIvars] removeObjectForKey:@"searchColumnIdentifiers"]; + else + [[self kfSimulatedIvars] setObject:identifiers forKey:@"searchColumnIdentifiers"]; +} + +@end + +#pragma mark - +#pragma mark HELPER +#pragma mark - + +// need a time function, don't want it to be sensitive to time zone switches or +// clock syncs, would rather not require linking Carbon. We'll go with mach_absolute_time. +// Written with reference to . +static uint64_t SecondsToMachAbsolute(double seconds) +{ + double nanoseconds_d = seconds * 1000000000; + Nanoseconds nanoseconds_n = UInt64ToUnsignedWide((uint64_t) nanoseconds_d); + return UnsignedWideToUInt64(NanosecondsToAbsolute(nanoseconds_n)); +} + + diff --git a/English.lproj/MainMenu.nib/info.nib b/English.lproj/MainMenu.nib/info.nib index aff30ece2..273ae3837 100644 --- a/English.lproj/MainMenu.nib/info.nib +++ b/English.lproj/MainMenu.nib/info.nib @@ -3,13 +3,13 @@ IBDocumentLocation - 66 -65 639 388 0 0 1024 746 + 61 -9 639 388 0 0 1024 746 IBEditorPositions 1063 0 228 136 49 0 0 1024 746 1156 - 233 341 241 366 0 0 1024 746 + 391 336 241 366 0 0 1024 746 29 85 683 383 44 0 0 1024 746 463 @@ -33,9 +33,10 @@ IBOpenObjects 513 + 463 + 1156 29 21 - 463 IBSystem Version 8L127 diff --git a/English.lproj/MainMenu.nib/keyedobjects.nib b/English.lproj/MainMenu.nib/keyedobjects.nib index 724aa37a29dd187fa6e78d9835a656bffc25ea5f..9af7814b73024225ae7eecd413717d29e8b8dabf 100644 GIT binary patch delta 29509 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 U{NFWd^#9mx!T;m`|Fr1;0Gy{Q00000 delta 29711 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 I-y2r{5B?-5K>z>% diff --git a/FileDrawer/FileIconCell.m b/FileDrawer/FileIconCell.m index 320836c9d..d88370c50 100644 --- a/FileDrawer/FileIconCell.m +++ b/FileDrawer/FileIconCell.m @@ -22,5 +22,4 @@ } } - @end diff --git a/FileDrawer/FileOutlineView.m b/FileDrawer/FileOutlineView.m index 290300f8a..ab062dbbf 100644 --- a/FileDrawer/FileOutlineView.m +++ b/FileDrawer/FileOutlineView.m @@ -9,12 +9,15 @@ #import "FileOutlineView.h" #import "FileIconCell.h" +@interface FileOutlineView (KFTypeSelectTableViewSupport) +- (void)findPrevious:(id)sender; +- (void)findNext:(id)sender; +@end + @implementation FileOutlineView - (void) awakeFromNib { - NSLog(@"FILE OUTLINE VIEW"); - NSEnumerator *e = [[self tableColumns] objectEnumerator]; id c; while ((c = [e nextObject])) @@ -27,5 +30,38 @@ [c setDataCell: dataCell]; } } + + +//Navigate outline view with the keyboard, send select actions to delegate +- (void)keyDown:(NSEvent *)theEvent +{ + if (!([theEvent modifierFlags] & NSCommandKeyMask)) { + NSString *charString = [theEvent charactersIgnoringModifiers]; + unichar pressedChar = 0; + + //Get the pressed character + if ([charString length] == 1) pressedChar = [charString characterAtIndex:0]; + + if ((pressedChar == '\031') && // backtab + ([self respondsToSelector:@selector(findPrevious:)])) { + /* KFTypeSelectTableView supports findPrevious; backtab is added to AIOutlineView as a find previous action + * if KFTypeSelectTableView is being used via posing */ + [self findPrevious:self]; + + } else if ((pressedChar == '\t') && + ([self respondsToSelector:@selector(findNext:)])) { + /* KFTypeSelectTableView supports findNext; tab is added to AIOutlineView as a find next action + * if KFTypeSelectTableView is being used via posing */ + [self findNext:self]; + + } else { + [super keyDown:theEvent]; + } + } else { + [super keyDown:theEvent]; + } +} + + @end diff --git a/FileDrawer/FileTreeController.m b/FileDrawer/FileTreeController.m index ef34f7ae4..4966133a9 100644 --- a/FileDrawer/FileTreeController.m +++ b/FileDrawer/FileTreeController.m @@ -9,6 +9,7 @@ #import "FileTreeController.h" #import "DirectoryNode.h" #import "ImageTextCell.h" +#import "KFTypeSelectTableView.h" @implementation FileTreeController @@ -116,27 +117,6 @@ return watcher; } -// Required Protocol Bullshit (RPB) -- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item -{ - return nil; -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item -{ - return NO; -} - -- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item -{ - return 0; -} -- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item -{ - return nil; -} -//End of RPB - - (BOOL)outlineView:(NSOutlineView *)olv writeItems:(NSArray*)items toPasteboard:(NSPasteboard*)pboard { //Get selected paths NSLog(@"Items: %@", items); @@ -164,5 +144,27 @@ return YES; } +//For type-select + +- (void)configureTypeSelectTableView:(KFTypeSelectTableView *)tableView +{ + [tableView setSearchWraps:YES]; +} + +- (int)typeSelectTableViewInitialSearchRow:(id)tableView +{ + return [tableView selectedRow]; +} + +// Return the string value used for type selection +- (NSString *)typeSelectTableView:(KFTypeSelectTableView *)tableView stringValueForTableColumn:(NSTableColumn *)col row:(int)row +{ + id item = [tableView itemAtRow:row]; + + //Reaching down into NSTreeController...yikes + return [[[item observedObject] path] lastPathComponent]; +} + +//End type-select @end