28 CPSearchFieldRecentsTitleMenuItemTag = 1000;
29 CPSearchFieldRecentsMenuItemTag = 1001;
30 CPSearchFieldClearRecentsMenuItemTag = 1002;
31 CPSearchFieldNoRecentsMenuItemTag = 1003;
33 var CPAutosavedRecentsChangedNotification =
@"CPAutosavedRecentsChangedNotification";
35 var RECENT_SEARCH_PREFIX =
@" ";
48 CPMenu _searchMenuTemplate;
52 CPArray _recentSearches;
55 BOOL _sendsWholeSearchString;
56 BOOL _sendsSearchStringImmediately;
57 BOOL _canResignFirstResponder;
76 - (id)initWithFrame:(CGRect)frame
78 if (
self = [super initWithFrame:
frame])
81 _sendsWholeSearchString = NO;
82 _sendsSearchStringImmediately = NO;
83 _recentsAutosaveName = nil;
87 _cancelButton._DOMElement.style.cursor =
"default";
88 _searchButton._DOMElement.style.cursor =
"default";
97 _recentSearches = [CPArray array];
99 [
self setBezeled:YES];
100 [
self setBezelStyle:CPTextFieldRoundedBezel];
101 [
self setBordered:YES];
102 [
self setEditable:YES];
103 [
self setContinuous:YES];
105 var bounds = [
self bounds],
106 cancelButton = [[
CPButton alloc] initWithFrame:[
self cancelButtonRectForBounds:bounds]],
107 searchButton = [[
CPButton alloc] initWithFrame:[
self searchButtonRectForBounds:bounds]];
109 [
self setCancelButton:cancelButton];
110 [
self resetCancelButton];
112 [
self setSearchButton:searchButton];
113 [
self resetSearchButton];
115 _canResignFirstResponder = YES;
118 - (void)viewWillMoveToSuperview:(
CPView)aView
120 [
super viewWillMoveToSuperview:aView];
135 - (void)setSearchButton:(
CPButton)button
137 if (button != _searchButton)
139 [_searchButton removeFromSuperview];
140 _searchButton = button;
142 [_searchButton setFrame:[
self searchButtonRectForBounds:[
self bounds]]];
143 [_searchButton setAutoresizingMask:CPViewMaxXMargin];
144 [
self addSubview:_searchButton];
154 return _searchButton;
161 - (void)resetSearchButton
163 var button = [
self searchButton],
164 searchButtonImage = (_searchMenuTemplate === nil) ? [
self valueForThemeAttribute:
@"image-search"] : [
self valueForThemeAttribute:
@"image-find"];
168 [button
setImage:searchButtonImage];
169 [button setAutoresizingMask:CPViewMaxXMargin];
176 - (void)setCancelButton:(
CPButton)button
178 if (button != _cancelButton)
180 [_cancelButton removeFromSuperview];
181 _cancelButton = button;
183 [_cancelButton setFrame:[
self cancelButtonRectForBounds:[
self bounds]]];
184 [_cancelButton setAutoresizingMask:CPViewMinXMargin];
185 [_cancelButton setTarget:self];
186 [_cancelButton setAction:@selector(cancelOperation:)];
187 [_cancelButton setButtonType:CPMomentaryChangeButton];
188 [
self _updateCancelButtonVisibility];
189 [
self addSubview:_cancelButton];
199 return _cancelButton;
206 - (void)resetCancelButton
208 var button = [
self cancelButton];
211 [button
setImage:[
self valueForThemeAttribute:@"image-cancel"]];
212 [button
setAlternateImage:[
self valueForThemeAttribute:@"image-cancel-pressed"]];
213 [button setAutoresizingMask:CPViewMinXMargin];
225 - (CGRect)searchTextRectForBounds:(CGRect)rect
228 width = CGRectGetWidth(rect),
229 bounds = [
self bounds];
233 var searchBounds = [
self searchButtonRectForBounds:bounds];
234 leftOffset = CGRectGetMaxX(searchBounds) + 2;
239 var cancelRect = [
self cancelButtonRectForBounds:bounds];
240 width = CGRectGetMinX(cancelRect) - leftOffset;
243 return CGRectMake(leftOffset, CGRectGetMinY(rect),
width, CGRectGetHeight(rect));
251 - (CGRect)searchButtonRectForBounds:(CGRect)rect
253 var size = [[
self valueForThemeAttribute:@"image-search"] size] || CGSizeMakeZero();
255 return CGRectMake(5, (CGRectGetHeight(rect) - size.height) / 2, size.width, size.height);
263 - (CGRect)cancelButtonRectForBounds:(CGRect)rect
265 var size = [[
self valueForThemeAttribute:@"image-cancel"] size] || CGSizeMakeZero();
267 return CGRectMake(CGRectGetWidth(rect) - size.width - 5, (CGRectGetHeight(rect) - size.width) / 2, size.height, size.height);
275 - (
CPMenu)searchMenuTemplate
277 return _searchMenuTemplate;
285 - (void)setSearchMenuTemplate:(
CPMenu)aMenu
287 _searchMenuTemplate = aMenu;
289 [
self resetSearchButton];
290 [
self _loadRecentSearchList];
291 [
self _updateSearchMenu];
299 - (BOOL)sendsWholeSearchString
301 return _sendsWholeSearchString;
308 - (void)setSendsWholeSearchString:(BOOL)flag
310 _sendsWholeSearchString = flag;
317 - (BOOL)sendsSearchStringImmediately
319 return _sendsSearchStringImmediately;
326 - (void)setSendsSearchStringImmediately:(BOOL)flag
328 _sendsSearchStringImmediately = flag;
336 - (int)maximumRecents
338 return _maximumRecents;
345 - (void)setMaximumRecents:(
int)max
352 _maximumRecents = max;
359 - (CPArray)recentSearches
361 return _recentSearches;
369 - (void)setRecentSearches:(CPArray)searches
371 var max = MIN([
self maximumRecents], [searches count]),
372 searches = [searches subarrayWithRange:CPMakeRange(0, max)];
374 _recentSearches = searches;
375 [
self _autosaveRecentSearchList];
384 return _recentsAutosaveName;
391 - (void)setRecentsAutosaveName:(
CPString)name
393 if (_recentsAutosaveName != nil)
394 [
self _deregisterForAutosaveNotification];
396 _recentsAutosaveName =
name;
398 if (_recentsAutosaveName != nil)
399 [
self _registerForAutosaveNotification];
404 - (CGRect)contentRectForBounds:(CGRect)bounds
406 var superbounds = [
super contentRectForBounds:bounds];
407 return [
self searchTextRectForBounds:superbounds];
410 + (double)_keyboardDelayForPartialSearchString:(
CPString)string
412 return (6 - MIN([
string length], 4)) / 10;
422 return [
super isOpaque] && [_cancelButton isOpaque] && [_searchButton isOpaque];
425 - (void)_updateCancelButtonVisibility
427 [_cancelButton setHidden:([[
self stringValue] length] === 0)];
432 if (![
self sendsWholeSearchString])
434 if ([
self sendsSearchStringImmediately])
435 [
self _sendPartialString];
438 [_partialStringTimer invalidate];
439 var timeInterval = [
CPSearchField _keyboardDelayForPartialSearchString:[
self stringValue]];
443 selector:@selector(_sendPartialString)
449 [
self _updateCancelButtonVisibility];
452 - (void)_sendAction:(
id)sender
454 [
self sendAction:[
self action] to:[
self target]];
457 - (BOOL)sendAction:(
SEL)anAction to:(
id)anObject
459 [
super sendAction:anAction to:anObject];
461 [_partialStringTimer invalidate];
463 [
self _addStringToRecentSearches:[
self stringValue]];
464 [
self _updateCancelButtonVisibility];
467 - (void)_addStringToRecentSearches:(
CPString)string
469 if (
string === nil ||
string ===
@"" || [_recentSearches containsObject:
string])
473 [searches addObject:string];
474 [
self setRecentSearches:searches];
475 [
self _updateSearchMenu];
478 - (
CPView)hitTest:(CGPoint)aPoint
481 if (CGRectContainsPoint([
self frame], aPoint))
487 - (BOOL)resignFirstResponder
489 return _canResignFirstResponder && [
super resignFirstResponder];
492 - (void)mouseDown:(
CPEvent)anEvent
494 var location = [anEvent locationInWindow],
495 point = [
self convertPoint:location fromView:nil];
497 if (CGRectContainsPoint([
self searchButtonRectForBounds:[
self bounds]], point))
499 if (_searchMenuTemplate == nil)
501 if ([_searchButton target] && [_searchButton action])
502 [_searchButton mouseDown:anEvent];
504 [
self _sendAction:self];
509 else if (CGRectContainsPoint([
self cancelButtonRectForBounds:[
self bounds]], point))
510 [_cancelButton mouseDown:anEvent];
512 [
super mouseDown:anEvent];
548 - (
CPMenu)defaultSearchMenuTemplate
550 var
template = [[
CPMenu alloc] init],
553 item = [[
CPMenuItem alloc] initWithTitle:@"Recent Searches"
556 [item setTag:CPSearchFieldRecentsTitleMenuItemTag];
557 [item setEnabled:NO];
558 [template addItem:item];
560 item = [[
CPMenuItem alloc] initWithTitle:@"Recent search item"
561 action:@selector(_searchFieldSearch:)
563 [item setTag:CPSearchFieldRecentsMenuItemTag];
564 [item setTarget:self];
565 [template addItem:item];
567 item = [[
CPMenuItem alloc] initWithTitle:@"Clear Recent Searches"
568 action:@selector(_searchFieldClearRecents:)
570 [item setTag:CPSearchFieldClearRecentsMenuItemTag];
571 [item setTarget:self];
572 [template addItem:item];
574 item = [[
CPMenuItem alloc] initWithTitle:@"No Recent Searches"
577 [item setTag:CPSearchFieldNoRecentsMenuItemTag];
578 [item setEnabled:NO];
579 [template addItem:item];
584 - (void)_updateSearchMenu
586 if (_searchMenuTemplate === nil)
590 countOfRecents = [_recentSearches count],
591 numberOfItems = [_searchMenuTemplate numberOfItems];
593 for (var i = 0; i < numberOfItems; i++)
595 var item = [[_searchMenuTemplate itemAtIndex:i] copy];
599 case CPSearchFieldRecentsTitleMenuItemTag:
600 if (countOfRecents === 0)
603 if ([menu numberOfItems] > 0)
604 [
self _addSeparatorToMenu:menu];
607 case CPSearchFieldRecentsMenuItemTag:
609 var itemAction =
@selector(_searchFieldSearch:);
611 for (var recentIndex = 0; recentIndex < countOfRecents; ++recentIndex)
614 var recentItem = [[
CPMenuItem alloc] initWithTitle:RECENT_SEARCH_PREFIX + [_recentSearches objectAtIndex:recentIndex]
616 keyEquivalent:[item keyEquivalent]];
617 [item setTarget:self];
618 [menu addItem:recentItem];
624 case CPSearchFieldClearRecentsMenuItemTag:
625 if (countOfRecents === 0)
628 if ([menu numberOfItems] > 0)
629 [
self _addSeparatorToMenu:menu];
631 [item setAction:@selector(_searchFieldClearRecents:)];
632 [item setTarget:self];
635 case CPSearchFieldNoRecentsMenuItemTag:
636 if (countOfRecents !== 0)
639 if ([menu numberOfItems] > 0)
640 [
self _addSeparatorToMenu:menu];
644 [item setEnabled:([item isEnabled] && [item action] != nil && [item target] != nil)];
648 [menu setDelegate:self];
653 - (void)_addSeparatorToMenu:(
CPMenu)aMenu
656 [separator setEnabled:NO];
660 - (void)menuWillOpen:(
CPMenu)menu
662 _canResignFirstResponder = NO;
665 - (void)menuDidClose:(
CPMenu)menu
667 _canResignFirstResponder = YES;
669 [
self becomeFirstResponder];
674 if (_searchMenu === nil || [_searchMenu numberOfItems] === 0 || ![
self isEnabled])
677 var aFrame = [[
self superview] convertRect:[
self frame] toView:nil],
678 location = CGPointMake(aFrame.origin.x + 10, aFrame.origin.y + aFrame.size.height - 4);
680 var anEvent = [
CPEvent mouseEventWithType:CPRightMouseDown location:location modifierFlags:0 timestamp:[[CPApp currentEvent] timestamp] windowNumber:[[
self window] windowNumber] context:nil eventNumber:1 clickCount:1 pressure:0];
682 [
self selectAll:nil];
686 - (void)_sendPartialString
688 [
super sendAction:[
self action] to:[
self target]];
689 [_partialStringTimer invalidate];
692 - (void)cancelOperation:(
id)sender
694 [
self setObjectValue:@""];
697 [
self _updateCancelButtonVisibility];
700 - (void)_searchFieldSearch:(
id)sender
702 var searchString = [[sender title] substringFromIndex:[RECENT_SEARCH_PREFIX length]];
704 if ([sender
tag] != CPSearchFieldRecentsMenuItemTag)
705 [
self _addStringToRecentSearches:searchString];
707 [
self setObjectValue:searchString];
708 [
self _sendPartialString];
709 [
self selectAll:nil];
711 [
self _updateCancelButtonVisibility];
714 - (void)_searchFieldClearRecents:(
id)sender
716 [
self setRecentSearches:[CPArray array]];
717 [
self _updateSearchMenu];
718 [
self setStringValue:@""];
719 [
self _updateCancelButtonVisibility];
722 - (void)_registerForAutosaveNotification
727 - (void)_deregisterForAutosaveNotification
732 - (void)_autosaveRecentSearchList
734 if (_recentsAutosaveName != nil)
738 - (void)_updateAutosavedRecents:(
id)notification
740 var name = [notification object];
741 [[
CPUserDefaults standardUserDefaults] setObject:_recentSearches forKey:name];
744 - (void)_loadRecentSearchList
746 var name = [
self recentsAutosaveName];
750 var list = [[
CPUserDefaults standardUserDefaults] objectForKey:name];
753 _recentSearches = list;
758 var CPRecentsAutosaveNameKey =
@"CPRecentsAutosaveNameKey",
759 CPSendsWholeSearchStringKey =
@"CPSendsWholeSearchStringKey",
760 CPSendsSearchStringImmediatelyKey =
@"CPSendsSearchStringImmediatelyKey",
761 CPMaximumRecentsKey =
@"CPMaximumRecentsKey",
762 CPSearchMenuTemplateKey =
@"CPSearchMenuTemplateKey";
766 - (void)encodeWithCoder:(
CPCoder)coder
768 [_searchButton removeFromSuperview];
769 [_cancelButton removeFromSuperview];
771 [
super encodeWithCoder:coder];
774 [
self addSubview:_searchButton];
776 [
self addSubview:_cancelButton];
778 [coder encodeBool:_sendsWholeSearchString forKey:CPSendsWholeSearchStringKey];
779 [coder encodeBool:_sendsSearchStringImmediately forKey:CPSendsSearchStringImmediatelyKey];
780 [coder encodeInt:_maximumRecents forKey:CPMaximumRecentsKey];
782 if (_recentsAutosaveName)
783 [coder encodeObject:_recentsAutosaveName forKey:CPRecentsAutosaveNameKey];
785 if (_searchMenuTemplate)
786 [coder encodeObject:_searchMenuTemplate forKey:CPSearchMenuTemplateKey];
789 - (id)initWithCoder:(
CPCoder)coder
791 if (
self = [super initWithCoder:coder])
793 [
self setRecentsAutosaveName:[coder decodeObjectForKey:CPRecentsAutosaveNameKey]];
794 _sendsWholeSearchString = [coder decodeBoolForKey:CPSendsWholeSearchStringKey];
795 _sendsSearchStringImmediately = [coder decodeBoolForKey:CPSendsSearchStringImmediatelyKey];
796 _maximumRecents = [coder decodeIntForKey:CPMaximumRecentsKey];
798 var
template = [coder decodeObjectForKey:CPSearchMenuTemplateKey];
801 [
self setSearchMenuTemplate:template];