diff --git a/ThirdParty/AMRemovableColumnsTableView/AMRemovableColumnsTableView.h b/ThirdParty/AMRemovableColumnsTableView/AMRemovableColumnsTableView.h deleted file mode 100644 index 2606cbb0d..000000000 --- a/ThirdParty/AMRemovableColumnsTableView/AMRemovableColumnsTableView.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// AMRemovableColumnsTableView.h -// HebX -// -// Created by Andreas on 26.08.05. -// Copyright 2005 Andreas Mayer. All rights reserved. -// - -#import - - -@interface AMRemovableColumnsTableView : NSTableView { - IBOutlet id obligatoryColumnIdentifiers; // NSArray - NSSet *allTableColumns; - NSSet *obligatoryTableColumns; - BOOL am_respondsToControlDidBecomeFirstResponder; -} - -- (NSSet *)allTableColumns; - -- (NSSet *)visibleTableColumns; - -- (NSSet *)hiddenTableColumns; - -// obligatory columns are automatically retrieved from obligatoryColumnIdentifiers if not nil; -// use setter otherwise -- (NSSet *)obligatoryTableColumns; -- (void)setObligatoryTableColumns:(NSSet *)newObligatoryTableColumns; - -- (BOOL)isObligatoryColumn:(NSTableColumn *)column; - -// use these to show and hide columns: - -- (BOOL)hideTableColumn:(NSTableColumn *)column; - -- (BOOL)showTableColumn:(NSTableColumn *)column; - - -@end diff --git a/ThirdParty/AMRemovableColumnsTableView/AMRemovableColumnsTableView.m b/ThirdParty/AMRemovableColumnsTableView/AMRemovableColumnsTableView.m deleted file mode 100644 index 445b6478a..000000000 --- a/ThirdParty/AMRemovableColumnsTableView/AMRemovableColumnsTableView.m +++ /dev/null @@ -1,299 +0,0 @@ -// -// AMRemovableColumnsTableView.m -// HebX -// -// Created by Andreas on 26.08.05. -// Copyright 2005 Andreas Mayer. All rights reserved. -// - -#import "AMRemovableColumnsTableView.h" -#import "AMRemovableTableColumn.h" - - -@interface NSTableView (ApplePrivate) -- (void)_readPersistentTableColumns; -- (void)_writePersistentTableColumns; -@end - - -@interface AMRemovableColumnsTableView (Private) -- (NSString *)columnVisibilitySaveName; -- (void)setAllTableColumns:(NSSet *)newAllTableColumns; -- (void)am_hideTableColumn:(NSTableColumn *)column; -- (void)am_showTableColumn:(NSTableColumn *)column; -- (void)readPersistentTableColumns; -- (void)writePersistentTableColumns; -- (void)am_readPersistentTableColumns; -- (void)am_writePersistentTableColumns; -@end - - -@implementation AMRemovableColumnsTableView - -static BOOL AMRemovableColumnsTableView_readPersistentTableColumnsIsPublic = NO; - -+ (void)initialize -{ - // should the framework support readPersistentTableColumns/writePersistentTableColumns, use the public methods - AMRemovableColumnsTableView_readPersistentTableColumnsIsPublic = [NSTableColumn instancesRespondToSelector:@selector(readPersistentTableColumns)]; -} - -- (void)awakeFromNib -{ - am_respondsToControlDidBecomeFirstResponder = [[self delegate] respondsToSelector:@selector(controlDidBecomeFirstResponder:)]; - - // set main table view for columns - NSEnumerator *enumerator = [[self tableColumns] objectEnumerator]; - AMRemovableTableColumn *column; - while (column = [enumerator nextObject]) { - [column setMainTableView:self]; - } - - // if there's an array of the names of obligatory columns, update the obligatoryTableColumns set - if (obligatoryColumnIdentifiers) { - NSMutableSet *columns = (NSMutableSet *)[NSMutableSet setWithSet:[self allTableColumns]]; - NSEnumerator *enumerator = [columns objectEnumerator]; - NSTableColumn *column; - while (column = [enumerator nextObject]) { - if (![(NSArray *)obligatoryColumnIdentifiers containsObject:[column identifier]]) { - [columns removeObject:column]; - } - } - [self setObligatoryTableColumns:columns]; - } -} - -- (void)dealloc -{ - [obligatoryColumnIdentifiers release]; - [super dealloc]; -} - - -- (NSSet *)allTableColumns -{ - return allTableColumns; -} - -- (void)setAllTableColumns:(NSSet *)newAllTableColumns -{ - if (allTableColumns != newAllTableColumns) { - [newAllTableColumns retain]; - [allTableColumns release]; - allTableColumns = newAllTableColumns; - } -} - -- (NSSet *)visibleTableColumns -{ - NSMutableSet *result = [NSMutableSet set]; - [result addObjectsFromArray:[self tableColumns]]; - return result; -} - -- (NSSet *)hiddenTableColumns -{ - NSMutableSet *result = [NSMutableSet setWithSet:[self allTableColumns]]; - [result minusSet:[self visibleTableColumns]]; - return result; -} - -- (NSSet *)obligatoryTableColumns -{ - return obligatoryTableColumns; -} - -- (void)setObligatoryTableColumns:(NSSet *)newObligatoryTableColumns -{ - if (obligatoryTableColumns != newObligatoryTableColumns) { - [newObligatoryTableColumns retain]; - [obligatoryTableColumns release]; - obligatoryTableColumns = newObligatoryTableColumns; - } -} - -- (BOOL)hideTableColumn:(NSTableColumn *)column -{ - BOOL result = NO; - if (![(AMRemovableTableColumn *)column isHidden] && ![self isObligatoryColumn:column]) { - [(AMRemovableTableColumn *)column setHidden:YES]; - result = YES; - } - return result; -} - -- (BOOL)showTableColumn:(NSTableColumn *)column -{ - BOOL result = NO; - if ([(AMRemovableTableColumn *)column isHidden]) { - [(AMRemovableTableColumn *)column setHidden:NO]; - result = YES; - } - return result; -} - -- (BOOL)isObligatoryColumn:(NSTableColumn *)column -{ - return [(NSArray *)obligatoryColumnIdentifiers containsObject:[column identifier]] ; -} - - -// ============================================================ -#pragma mark - -#pragma mark ━ table view methods ━ -// ============================================================ - -- (NSTableColumn *)tableColumnWithIdentifier:(id)identifier -{ - NSTableColumn *result = nil; - NSEnumerator *enumerator = [[self allTableColumns] objectEnumerator]; - NSTableColumn *column; - while (column = [enumerator nextObject]) { - if ([[column identifier] isEqualToString:identifier]) { - result = column; - break; - } - } - return result; -} - -- (void)setDelegate:(id)anObject -{ - am_respondsToControlDidBecomeFirstResponder = [anObject respondsToSelector:@selector(controlDidBecomeFirstResponder:)]; - [super setDelegate:anObject]; -} - - -// ============================================================ -#pragma mark - -#pragma mark ━ view methods ━ -// ============================================================ - -- (void)viewWillMoveToWindow:(NSWindow *)newWindow -{ - //NSLog(@"AMRemovableColumnsTableView - viewWillMoveToWindow:%@", newWindow); - [self am_readPersistentTableColumns]; - [super viewWillMoveToWindow:newWindow]; -} - - -// ============================================================ -#pragma mark - -#pragma mark ━ responder methods ━ -// ============================================================ - -- (BOOL)becomeFirstResponder -{ - BOOL result = [super becomeFirstResponder]; - if (result && am_respondsToControlDidBecomeFirstResponder) { - [[self delegate] performSelector:@selector(controlDidBecomeFirstResponder:) withObject:self]; - } - return result; -} - - -// ============================================================ -#pragma mark - -#pragma mark ━ private table view methods ━ -// ============================================================ - -- (void)_readPersistentTableColumns -{ - //NSLog(@"_readPersistentTableColumns"); - if (!AMRemovableColumnsTableView_readPersistentTableColumnsIsPublic) { - [self setAllTableColumns:[NSSet setWithArray:[self tableColumns]]]; - // restore visible columns - [self am_readPersistentTableColumns]; - } // else readPersistentTableColumns will be used - [super _readPersistentTableColumns]; -} - -- (void)_writePersistentTableColumns -{ - //NSLog(@"_writePersistentTableColumns"); - [super _writePersistentTableColumns]; - // save visible columns - if (!AMRemovableColumnsTableView_readPersistentTableColumnsIsPublic) { - [self am_writePersistentTableColumns]; - } // else writePersistentTableColumns will be used -} - -// just in case these methods should become public: -- (void)readPersistentTableColumns -{ - //NSLog(@"_readPersistentTableColumns"); - [self setAllTableColumns:[NSSet setWithArray:[self tableColumns]]]; - // restore visible columns - [self am_readPersistentTableColumns]; - [(id)super readPersistentTableColumns]; // cast to avoid compiler warning -} - -- (void)writePersistentTableColumns -{ - //NSLog(@"_writePersistentTableColumns"); - [(id)super writePersistentTableColumns]; // cast to avoid compiler warning - // save visible columns - [self am_writePersistentTableColumns]; -} - - -// ============================================================ -#pragma mark - -#pragma mark ━ private methods ━ -// ============================================================ - -- (NSString *)columnVisibilitySaveName -{ - NSString *autosaveName = [self autosaveName]; - if (!autosaveName) { - NSLog(@"AMRemovableColumnsTableView: autosave name missing for table view: %@", self); - autosaveName = @"no-autosave-name-set"; - } - return [@"AMRemovableColumnsTableView VisibleColumns " stringByAppendingString:autosaveName]; -} - -- (void)am_readPersistentTableColumns -{ - // restore visible columns - NSArray *visibleColumnIdentifiers = [[NSUserDefaults standardUserDefaults] objectForKey:[self columnVisibilitySaveName]]; - if (visibleColumnIdentifiers) { - NSSet *allColumns = [self allTableColumns]; - NSEnumerator *enumerator = [allColumns objectEnumerator]; - NSTableColumn *column; - while (column = [enumerator nextObject]) { - if (![visibleColumnIdentifiers containsObject:[column identifier]]) { - [(AMRemovableTableColumn *)column setMainTableView:self]; - [self removeTableColumn:column]; - } - } - } -} - -- (void)am_writePersistentTableColumns -{ - NSMutableArray *visibleColumnIdentifiers = [NSMutableArray array]; - NSEnumerator *enumerator = [[self visibleTableColumns] objectEnumerator]; - NSTableColumn *column; - while (column = [enumerator nextObject]) { - [visibleColumnIdentifiers addObject:[column identifier]]; - } - [[NSUserDefaults standardUserDefaults] setObject:visibleColumnIdentifiers forKey:[self columnVisibilitySaveName]]; -} - -- (void)am_hideTableColumn:(NSTableColumn *)column -{ - [(AMRemovableTableColumn *)column setMainTableView:self]; - [self removeTableColumn:column]; - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(am_writePersistentTableColumns) object:nil]; - [self performSelector:@selector(am_writePersistentTableColumns) withObject:nil afterDelay:0.0]; -} - -- (void)am_showTableColumn:(NSTableColumn *)column -{ - [self addTableColumn:column]; - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(am_writePersistentTableColumns) object:nil]; - [self performSelector:@selector(am_writePersistentTableColumns) withObject:nil afterDelay:0.0]; -} - - -@end diff --git a/ThirdParty/AMRemovableColumnsTableView/AMRemovableTableColumn.h b/ThirdParty/AMRemovableColumnsTableView/AMRemovableTableColumn.h deleted file mode 100644 index f78ae4e3c..000000000 --- a/ThirdParty/AMRemovableColumnsTableView/AMRemovableTableColumn.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// AMRemovableTableColumn.h -// HebX -// -// Created by Andreas on 28.08.05. -// Copyright 2005 Andreas Mayer. All rights reserved. -// - -#import -#import "AMRemovableColumnsTableView.h" - - -@interface AMRemovableTableColumn : NSTableColumn { - IBOutlet AMRemovableColumnsTableView *mainTableView; -} - -- (AMRemovableColumnsTableView *)mainTableView; -- (void)setMainTableView:(AMRemovableColumnsTableView *)newMainTableView; - -- (BOOL)isHidden; -- (void)setHidden:(BOOL)flag; - - -@end diff --git a/ThirdParty/AMRemovableColumnsTableView/AMRemovableTableColumn.m b/ThirdParty/AMRemovableColumnsTableView/AMRemovableTableColumn.m deleted file mode 100644 index 6a1d38f37..000000000 --- a/ThirdParty/AMRemovableColumnsTableView/AMRemovableTableColumn.m +++ /dev/null @@ -1,63 +0,0 @@ -// -// AMRemovableTableColumn.m -// HebX -// -// Created by Andreas on 28.08.05. -// Copyright 2005 Andreas Mayer. All rights reserved. -// - -#import "AMRemovableTableColumn.h" - -@interface AMRemovableColumnsTableView (Private) -- (void)am_hideTableColumn:(NSTableColumn *)column; -- (void)am_showTableColumn:(NSTableColumn *)column; -@end - - -@implementation AMRemovableTableColumn - -static BOOL AMRemovableTableColumn_frameworkDoesSupportHiddenColumns = NO; - -+ (void)initialize -{ - // should the framework support isHidden/setHidden:, use the build-in methods - AMRemovableTableColumn_frameworkDoesSupportHiddenColumns = [NSTableColumn instancesRespondToSelector:@selector(setHidden:)]; -} - - -- (AMRemovableColumnsTableView *)mainTableView -{ - return mainTableView; -} - -- (void)setMainTableView:(AMRemovableColumnsTableView *)newMainTableView -{ - // do not retain - mainTableView = newMainTableView; -} - - -- (BOOL)isHidden -{ - if (AMRemovableTableColumn_frameworkDoesSupportHiddenColumns) { - return [(id)super isHidden]; // cast to id to avoid compiler warning - } else { - return ([self tableView] != mainTableView); - } -} - -- (void)setHidden:(BOOL)flag -{ - if (AMRemovableTableColumn_frameworkDoesSupportHiddenColumns) { - [(id)super setHidden:flag]; // cast to id to avoid compiler warning - } else { - if (flag) { - [(AMRemovableColumnsTableView *)[self tableView] am_hideTableColumn:self]; - } else { - [mainTableView am_showTableColumn:self]; - } - } -} - - -@end diff --git a/ThirdParty/KFTypeSelectTableView/KFTypeSelectTableView.h b/ThirdParty/KFTypeSelectTableView/KFTypeSelectTableView.h deleted file mode 100644 index 56166bf0f..000000000 --- a/ThirdParty/KFTypeSelectTableView/KFTypeSelectTableView.h +++ /dev/null @@ -1,121 +0,0 @@ -// -// 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/ThirdParty/KFTypeSelectTableView/KFTypeSelectTableView.m b/ThirdParty/KFTypeSelectTableView/KFTypeSelectTableView.m deleted file mode 100644 index f907a4db4..000000000 --- a/ThirdParty/KFTypeSelectTableView/KFTypeSelectTableView.m +++ /dev/null @@ -1,1273 +0,0 @@ -// -// 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; - [super keyDown:keyEvent]; - } - } - - 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)); -} - -