44 CPMenu _searchMenuTemplate;
48 CPArray _recentSearches;
51 BOOL _sendsWholeSearchString;
52 BOOL _sendsSearchStringImmediately;
53 BOOL _canResignFirstResponder;
69 @"image-search-inset" : CGInsetMake(0, 0, 0, 5),
70 @"image-cancel-inset" : CGInsetMake(0, 5, 0, 0),
71 @"search-menu-offset": CGPointMake(10, -4),
72 @"search-right-margin": 2
76 + (Class)_binderClassForBinding:(
CPString)aBinding
79 return [_CPSearchFieldPredicateBinder class];
81 return [
super _binderClassForBinding:aBinding];
84 - (id)initWithFrame:(CGRect)frame
86 if (
self = [super initWithFrame:
frame])
89 _sendsWholeSearchString = NO;
90 _sendsSearchStringImmediately = NO;
91 _recentsAutosaveName = nil;
101 _recentSearches = [CPArray array];
103 [
self setBezeled:YES];
104 [
self setBezelStyle:CPTextFieldRoundedBezel];
105 [
self setBordered:YES];
106 [
self setEditable:YES];
107 [
self setContinuous:YES];
109 var
bounds = [
self bounds],
110 cancelButton = [[
CPButton alloc] initWithFrame:[
self cancelButtonRectForBounds:bounds]],
111 searchButton = [[
CPButton alloc] initWithFrame:[
self searchButtonRectForBounds:bounds]];
113 [
self setCancelButton:cancelButton];
114 [
self resetCancelButton];
116 [
self setSearchButton:searchButton];
117 [
self resetSearchButton];
119 _canResignFirstResponder = YES;
124 #pragma mark Override observers
126 - (void)_removeObservers
131 [
super _removeObservers];
136 - (void)_addObservers
141 [
super _addObservers];
153 if (button != _searchButton)
155 [_searchButton removeFromSuperview];
156 _searchButton = button;
159 [_searchButton setAutoresizingMask:CPViewMaxXMargin];
170 return _searchButton;
177 - (void)resetSearchButton
180 searchButtonImage = (_searchMenuTemplate === nil) ? [
self currentValueForThemeAttribute:
@"image-search"] : [
self currentValueForThemeAttribute:
@"image-find"];
184 [button
setImage:searchButtonImage];
194 if (button != _cancelButton)
196 [_cancelButton removeFromSuperview];
197 _cancelButton = button;
200 [_cancelButton setAutoresizingMask:CPViewMinXMargin];
201 [_cancelButton setTarget:self];
202 [_cancelButton setAction:@selector(cancelOperation:)];
203 [_cancelButton setButtonType:CPMomentaryChangeButton];
204 [
self _updateCancelButtonVisibility];
215 return _cancelButton;
222 - (void)resetCancelButton
227 [button
setImage:[
self valueForThemeAttribute:@"image-cancel"]];
228 [button
setAlternateImage:[
self valueForThemeAttribute:@"image-cancel-pressed"]];
241 - (CGRect)searchTextRectForBounds:(CGRect)rect
244 width = CGRectGetWidth(rect),
250 rightMargin = [
self currentValueForThemeAttribute:@"search-right-margin"];
251 leftOffset = CGRectGetMaxX(searchBounds) + rightMargin;
257 width = CGRectGetMinX(cancelRect) - leftOffset;
260 return CGRectMake(leftOffset, CGRectGetMinY(rect),
width, CGRectGetHeight(rect));
268 - (CGRect)searchButtonRectForBounds:(CGRect)rect
270 var size = [[
self currentValueForThemeAttribute:@"image-search"] size] || CGSizeMakeZero(),
271 inset = [
self currentValueForThemeAttribute:@"image-search-inset"];
273 return CGRectMake(inset.left - inset.right, inset.top - inset.bottom + (CGRectGetHeight(rect) - size.height) / 2, size.width, size.height);
281 - (CGRect)cancelButtonRectForBounds:(CGRect)rect
283 var size = [[
self currentValueForThemeAttribute:@"image-cancel"] size] || CGSizeMakeZero(),
284 inset = [
self currentValueForThemeAttribute:@"image-cancel-inset"];
286 return CGRectMake(CGRectGetWidth(rect) - size.width + inset.left - inset.right, inset.top - inset.bottom + (CGRectGetHeight(rect) - size.width) / 2, size.height, size.height);
296 return _searchMenuTemplate;
304 - (void)setSearchMenuTemplate:(
CPMenu)aMenu
306 _searchMenuTemplate = aMenu;
309 [
self _loadRecentSearchList];
310 [
self _updateSearchMenu];
318 - (BOOL)sendsWholeSearchString
320 return _sendsWholeSearchString;
327 - (void)setSendsWholeSearchString:(BOOL)flag
329 _sendsWholeSearchString = flag;
336 - (BOOL)sendsSearchStringImmediately
338 return _sendsSearchStringImmediately;
345 - (void)setSendsSearchStringImmediately:(BOOL)flag
347 _sendsSearchStringImmediately = flag;
355 - (int)maximumRecents
357 return _maximumRecents;
364 - (void)setMaximumRecents:(
int)max
371 _maximumRecents = max;
378 - (CPArray)recentSearches
380 return _recentSearches;
388 - (void)setRecentSearches:(CPArray)searches
390 var max = MIN([
self maximumRecents], [searches count]);
392 searches = [searches subarrayWithRange:CPMakeRange(0, max)];
393 _recentSearches = searches;
394 [
self _autosaveRecentSearchList];
403 return _recentsAutosaveName;
412 if (_recentsAutosaveName != nil)
413 [
self _deregisterForAutosaveNotification];
415 _recentsAutosaveName =
name;
417 if (_recentsAutosaveName != nil)
418 [
self _registerForAutosaveNotification];
423 - (CGRect)contentRectForBounds:(CGRect)bounds
429 + (double)_keyboardDelayForPartialSearchString:(
CPString)string
431 return (6 - MIN([
string length], 4)) / 10;
441 return [
super isOpaque] && [_cancelButton isOpaque] && [_searchButton isOpaque];
444 - (void)_updateCancelButtonVisibility
446 [_cancelButton setHidden:([[
self stringValue] length] === 0)];
451 if (![
self sendsWholeSearchString])
453 if ([
self sendsSearchStringImmediately])
454 [
self _sendPartialString];
457 [_partialStringTimer invalidate];
458 var timeInterval = [
CPSearchField _keyboardDelayForPartialSearchString:[
self stringValue]];
462 selector:@selector(_sendPartialString)
468 [
self _updateCancelButtonVisibility];
471 - (void)_sendAction:(
id)sender
473 [
self sendAction:[
self action] to:[
self target]];
476 - (BOOL)sendAction:(
SEL)anAction to:(
id)anObject
480 [_partialStringTimer invalidate];
482 [
self _addStringToRecentSearches:[
self stringValue]];
483 [
self _updateCancelButtonVisibility];
486 - (void)_addStringToRecentSearches:(
CPString)string
488 if (
string === nil ||
string ===
@"" || [_recentSearches containsObject:
string])
492 [searches addObject:string];
493 [
self setRecentSearches:searches];
494 [
self _updateSearchMenu];
500 if (CGRectContainsPoint([
self frame], aPoint))
506 - (BOOL)resignFirstResponder
516 if (CGRectContainsPoint([
self searchButtonRectForBounds:[
self bounds]], point))
518 if (_searchMenuTemplate == nil)
520 if ([_searchButton target] && [_searchButton action])
521 [_searchButton mouseDown:anEvent];
523 [
self _sendAction:self];
528 else if (CGRectContainsPoint([
self cancelButtonRectForBounds:[
self bounds]], point))
529 [_cancelButton mouseDown:anEvent];
572 [item setTag:CPSearchFieldClearRecentsMenuItemTag];
573 [item setTarget:self];
574 [template addItem:item];
579 [item setTag:CPSearchFieldRecentsTitleMenuItemTag];
580 [item setEnabled:NO];
581 [template addItem:item];
586 [item setTag:CPSearchFieldRecentsMenuItemTag];
587 [item setTarget:self];
588 [template addItem:item];
593 - (void)_updateSearchMenu
595 if (_searchMenuTemplate === nil)
599 countOfRecents = [_recentSearches count],
600 numberOfItems = [_searchMenuTemplate numberOfItems];
602 [menu setAutoenablesItems:NO];
604 for (var i = 0; i < numberOfItems; i++)
606 var item = [[_searchMenuTemplate itemAtIndex:i] copy];
611 if (countOfRecents === 0)
614 if ([menu numberOfItems] > 0)
615 [
self _addSeparatorToMenu:menu];
620 var itemAction =
@selector(_searchFieldSearch:);
622 for (var recentIndex = 0; recentIndex < countOfRecents; ++recentIndex)
627 [item setTarget:self];
628 [menu addItem:recentItem];
635 if (countOfRecents === 0)
638 if ([menu numberOfItems] > 0)
639 [
self _addSeparatorToMenu:menu];
641 [item setAction:@selector(_searchFieldClearRecents:)];
642 [item setTarget:self];
646 if (countOfRecents !== 0)
649 if ([menu numberOfItems] > 0)
650 [
self _addSeparatorToMenu:menu];
654 [item setEnabled:([item isEnabled] && [item action] != nil && [item target] != nil)];
658 [menu setDelegate:self];
663 - (void)_addSeparatorToMenu:(
CPMenu)aMenu
666 [separator setEnabled:NO];
672 _canResignFirstResponder = NO;
677 _canResignFirstResponder = YES;
684 if (_searchMenu === nil || [_searchMenu numberOfItems] === 0 || ![
self isEnabled] || ([_recentSearches count] === 0))
687 var aFrame = [[
self superview] convertRect:[
self frame] toView:nil],
688 offset = [
self currentValueForThemeAttribute:@"search-menu-offset"],
689 location = CGPointMake(aFrame.origin.x + offset.x, aFrame.origin.y + aFrame.size.height + offset.y);
693 [
self selectAll:nil];
697 - (void)_sendPartialString
699 [
super sendAction:[
self action] to:[
self target]];
700 [_partialStringTimer invalidate];
703 - (void)cancelOperation:(
id)sender
708 [
self _updateCancelButtonVisibility];
711 - (void)_searchFieldSearch:(
id)sender
713 var searchString = [sender title];
716 [
self _addStringToRecentSearches:searchString];
718 [
self setObjectValue:searchString];
719 [
self _sendPartialString];
720 [
self selectAll:nil];
722 [
self _updateCancelButtonVisibility];
725 - (void)_searchFieldClearRecents:(
id)sender
727 [
self setRecentSearches:[CPArray array]];
728 [
self _updateSearchMenu];
729 [
self setStringValue:@""];
730 [
self _updateCancelButtonVisibility];
733 - (void)_registerForAutosaveNotification
738 - (void)_deregisterForAutosaveNotification
743 - (void)_autosaveRecentSearchList
745 if (_recentsAutosaveName != nil)
749 - (void)_updateAutosavedRecents:(
id)notification
751 var name = [notification object];
755 - (void)_loadRecentSearchList
757 var name = [
self recentsAutosaveName];
764 _recentSearches = list;
769 [
super unbind:aBinding];
774 [[[
self class] _binderClassForBinding:aBinding] unbind:CPValueBinding forObject:self];
787 - (void)updateTrackingAreas
789 if (_searchButtonTrackingArea)
792 _searchButtonTrackingArea = nil;
795 if (_cancelButtonTrackingArea)
798 _cancelButtonTrackingArea = nil;
804 options:CPTrackingCursorUpdate | CPTrackingActiveInKeyWindow
814 options:CPTrackingCursorUpdate | CPTrackingActiveInKeyWindow
826 if ([[[anEvent trackingArea] userInfo] objectForKey:
@"isButton"])
846 [_searchButton removeFromSuperview];
847 [_cancelButton removeFromSuperview];
856 [coder encodeBool:_sendsWholeSearchString forKey:CPSendsWholeSearchStringKey];
857 [coder encodeBool:_sendsSearchStringImmediately forKey:CPSendsSearchStringImmediatelyKey];
858 [coder encodeInt:_maximumRecents forKey:CPMaximumRecentsKey];
860 if (_recentsAutosaveName)
861 [coder encodeObject:_recentsAutosaveName forKey:CPRecentsAutosaveNameKey];
863 if (_searchMenuTemplate)
864 [coder encodeObject:_searchMenuTemplate forKey:CPSearchMenuTemplateKey];
869 if (
self = [super initWithCoder:coder])
872 _sendsWholeSearchString = [coder decodeBoolForKey:CPSendsWholeSearchStringKey];
873 _sendsSearchStringImmediately = [coder decodeBoolForKey:CPSendsSearchStringImmediatelyKey];
874 _maximumRecents = [coder decodeIntForKey:CPMaximumRecentsKey];
876 var
template = [coder decodeObjectForKey:CPSearchMenuTemplateKey];
889 @implementation _CPSearchFieldPredicateBinder :
CPBinder
895 - (void)setValue:(
id)aValue forBinding:(
CPString)aBinding
899 var options = [_info objectForKey:CPOptionsKey];
901 _controller = [_info objectForKey:CPObservedObjectKey];
902 _predicateFormat = [options objectForKey:"CPPredicateFormat"];
903 [_source bind:CPValueBinding toObject:self withKeyPath:"searchFieldValue" options:nil];
907 - (void)setSearchFieldValue:(
CPString)aValue
909 var destination = [_info objectForKey:CPObservedObjectKey],
910 keyPath = [_info objectForKey:CPObservedKeyPathKey];
912 var formatString = _predicateFormat.replace(/\$value/g,
"%@");
913 [
self suppressSpecificNotificationFromObject:destination keyPath:keyPath];
916 [_controller setFilterPredicate:[CPPredicate predicateWithFormat:formatString, aValue]];
918 [_controller setFilterPredicate:nil];
920 [
self unsuppressSpecificNotificationFromObject:destination keyPath:keyPath];
924 return [_source stringValue];