/AppKit/CPSearchField.j

http://github.com/cacaodev/cappuccino · Unknown · 825 lines · 676 code · 149 blank · 0 comment · 0 complexity · b9c97a6b044cb25babb557ca42cda803 MD5 · raw file

  1. /*
  2. * CPSearchField.j
  3. * AppKit
  4. *
  5. * Created by cacaodev.
  6. * Copyright 2009.
  7. *
  8. * This library is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU Lesser General Public
  10. * License as published by the Free Software Foundation; either
  11. * version 2.1 of the License, or (at your option) any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. * Lesser General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Lesser General Public
  19. * License along with this library; if not, write to the Free Software
  20. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  21. */
  22. @import "CPButton.j"
  23. @import "CPMenu.j"
  24. @import "CPMenuItem.j"
  25. @import "CPTextField.j"
  26. @class CPUserDefaults
  27. @global CPApp
  28. CPSearchFieldRecentsTitleMenuItemTag = 1000;
  29. CPSearchFieldRecentsMenuItemTag = 1001;
  30. CPSearchFieldClearRecentsMenuItemTag = 1002;
  31. CPSearchFieldNoRecentsMenuItemTag = 1003;
  32. var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotification";
  33. var RECENT_SEARCH_PREFIX = @" ";
  34. /*!
  35. @ingroup appkit
  36. @class CPSearchField
  37. The CPSearchField class defines the programmatic interface for text fields that are optimized for text-based searches. A CPSearchField object directly inherits from the CPTextField class. The search field implemented by these classes presents a standard user interface for searches, including a search button, a cancel button, and a pop-up icon menu for listing recent search strings and custom search categories.
  38. When the user types and then pauses, the text field's action message is sent to its target. You can query the text field's string value for the current text to search for. Do not rely on the sender of the action to be an CPMenu object because the menu may change. If you need to change the menu, modify the search menu template and call the setSearchMenuTemplate: method to update.
  39. */
  40. @implementation CPSearchField : CPTextField
  41. {
  42. CPButton _searchButton;
  43. CPButton _cancelButton;
  44. CPMenu _searchMenuTemplate;
  45. CPMenu _searchMenu;
  46. CPString _recentsAutosaveName;
  47. CPArray _recentSearches;
  48. int _maximumRecents;
  49. BOOL _sendsWholeSearchString;
  50. BOOL _sendsSearchStringImmediately;
  51. BOOL _canResignFirstResponder;
  52. CPTimer _partialStringTimer;
  53. }
  54. + (CPString)defaultThemeClass
  55. {
  56. return @"searchfield"
  57. }
  58. + (CPDictionary)themeAttributes
  59. {
  60. return @{
  61. @"image-search": [CPNull null],
  62. @"image-find": [CPNull null],
  63. @"image-cancel": [CPNull null],
  64. @"image-cancel-pressed": [CPNull null]
  65. };
  66. }
  67. - (id)initWithFrame:(CGRect)frame
  68. {
  69. if (self = [super initWithFrame:frame])
  70. {
  71. _maximumRecents = 10;
  72. _sendsWholeSearchString = NO;
  73. _sendsSearchStringImmediately = NO;
  74. _recentsAutosaveName = nil;
  75. [self _init];
  76. #if PLATFORM(DOM)
  77. _cancelButton._DOMElement.style.cursor = "default";
  78. _searchButton._DOMElement.style.cursor = "default";
  79. #endif
  80. }
  81. return self;
  82. }
  83. - (void)_init
  84. {
  85. _recentSearches = [CPArray array];
  86. [self setBezeled:YES];
  87. [self setBezelStyle:CPTextFieldRoundedBezel];
  88. [self setBordered:YES];
  89. [self setEditable:YES];
  90. [self setContinuous:YES];
  91. var bounds = [self bounds],
  92. cancelButton = [[CPButton alloc] initWithFrame:[self cancelButtonRectForBounds:bounds]],
  93. searchButton = [[CPButton alloc] initWithFrame:[self searchButtonRectForBounds:bounds]];
  94. [self setCancelButton:cancelButton];
  95. [self resetCancelButton];
  96. [self setSearchButton:searchButton];
  97. [self resetSearchButton];
  98. _canResignFirstResponder = YES;
  99. }
  100. #pragma mark -
  101. #pragma mark Override observers
  102. - (void)_removeObservers
  103. {
  104. if (!_isObserving)
  105. return;
  106. [super _removeObservers];
  107. [[CPNotificationCenter defaultCenter] removeObserver:self name:CPControlTextDidChangeNotification object:self];
  108. }
  109. - (void)_addObservers
  110. {
  111. if (_isObserving)
  112. return;
  113. [super _addObservers];
  114. [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_searchFieldTextDidChange:) name:CPControlTextDidChangeNotification object:self];
  115. }
  116. // Managing Buttons
  117. /*!
  118. Sets the button used to display the search-button image
  119. @param button The search button.
  120. */
  121. - (void)setSearchButton:(CPButton)button
  122. {
  123. if (button != _searchButton)
  124. {
  125. [_searchButton removeFromSuperview];
  126. _searchButton = button;
  127. [_searchButton setFrame:[self searchButtonRectForBounds:[self bounds]]];
  128. [_searchButton setAutoresizingMask:CPViewMaxXMargin];
  129. [self addSubview:_searchButton];
  130. }
  131. }
  132. /*!
  133. Returns the button used to display the search-button image.
  134. @return The search button.
  135. */
  136. - (CPButton)searchButton
  137. {
  138. return _searchButton;
  139. }
  140. /*!
  141. Resets the search button to its default attributes.
  142. This method resets the target, action, regular image, and pressed image. By default, when users click the search button or press the Return key, the action defined for the receiver is sent to its designated target. This method gives you a way to customize the search button for specific situations and then reset the button defaults without having to undo changes individually.
  143. */
  144. - (void)resetSearchButton
  145. {
  146. var button = [self searchButton],
  147. searchButtonImage = (_searchMenuTemplate === nil) ? [self valueForThemeAttribute:@"image-search"] : [self valueForThemeAttribute:@"image-find"];
  148. [button setBordered:NO];
  149. [button setImageScaling:CPImageScaleAxesIndependently];
  150. [button setImage:searchButtonImage];
  151. [button setAutoresizingMask:CPViewMaxXMargin];
  152. }
  153. /*!
  154. Sets the button object used to display the cancel-button image.
  155. @param button The cancel button.
  156. */
  157. - (void)setCancelButton:(CPButton)button
  158. {
  159. if (button != _cancelButton)
  160. {
  161. [_cancelButton removeFromSuperview];
  162. _cancelButton = button;
  163. [_cancelButton setFrame:[self cancelButtonRectForBounds:[self bounds]]];
  164. [_cancelButton setAutoresizingMask:CPViewMinXMargin];
  165. [_cancelButton setTarget:self];
  166. [_cancelButton setAction:@selector(cancelOperation:)];
  167. [_cancelButton setButtonType:CPMomentaryChangeButton];
  168. [self _updateCancelButtonVisibility];
  169. [self addSubview:_cancelButton];
  170. }
  171. }
  172. /*!
  173. Returns the button object used to display the cancel-button image.
  174. @return The cancel button.
  175. */
  176. - (CPButton)cancelButton
  177. {
  178. return _cancelButton;
  179. }
  180. /*!
  181. Resets the cancel button to its default attributes.
  182. This method resets the target, action, regular image, and pressed image. This method gives you a way to customize the cancel button for specific situations and then reset the button defaults without having to undo changes individually.
  183. */
  184. - (void)resetCancelButton
  185. {
  186. var button = [self cancelButton];
  187. [button setBordered:NO];
  188. [button setImageScaling:CPImageScaleAxesIndependently];
  189. [button setImage:[self valueForThemeAttribute:@"image-cancel"]];
  190. [button setAlternateImage:[self valueForThemeAttribute:@"image-cancel-pressed"]];
  191. [button setAutoresizingMask:CPViewMinXMargin];
  192. [button setTarget:self];
  193. [button setAction:@selector(cancelOperation:)];
  194. }
  195. // Custom Layout
  196. /*!
  197. Modifies the bounding rectangle for the search-text field.
  198. @param rect The current bounding rectangle for the search text field.
  199. @return The updated bounding rectangle to use for the search text field. The default value is the value passed into the rect parameter.
  200. Subclasses can override this method to return a new bounding rectangle for the text-field object. You might use this method to provide a custom layout for the search field control.
  201. */
  202. - (CGRect)searchTextRectForBounds:(CGRect)rect
  203. {
  204. var leftOffset = 0,
  205. width = CGRectGetWidth(rect),
  206. bounds = [self bounds];
  207. if (_searchButton)
  208. {
  209. var searchBounds = [self searchButtonRectForBounds:bounds];
  210. leftOffset = CGRectGetMaxX(searchBounds) + 2;
  211. }
  212. if (_cancelButton)
  213. {
  214. var cancelRect = [self cancelButtonRectForBounds:bounds];
  215. width = CGRectGetMinX(cancelRect) - leftOffset;
  216. }
  217. return CGRectMake(leftOffset, CGRectGetMinY(rect), width, CGRectGetHeight(rect));
  218. }
  219. /*!
  220. Modifies the bounding rectangle for the search button.
  221. @param rect The current bounding rectangle for the search button.
  222. Subclasses can override this method to return a new bounding rectangle for the search button. You might use this method to provide a custom layout for the search field control.
  223. */
  224. - (CGRect)searchButtonRectForBounds:(CGRect)rect
  225. {
  226. var size = [[self valueForThemeAttribute:@"image-search"] size] || CGSizeMakeZero();
  227. return CGRectMake(5, (CGRectGetHeight(rect) - size.height) / 2, size.width, size.height);
  228. }
  229. /*!
  230. Modifies the bounding rectangle for the cancel button.
  231. @param rect The updated bounding rectangle to use for the cancel button. The default value is the value passed into the rect parameter.
  232. Subclasses can override this method to return a new bounding rectangle for the cancel button. You might use this method to provide a custom layout for the search field control.
  233. */
  234. - (CGRect)cancelButtonRectForBounds:(CGRect)rect
  235. {
  236. var size = [[self valueForThemeAttribute:@"image-cancel"] size] || CGSizeMakeZero();
  237. return CGRectMake(CGRectGetWidth(rect) - size.width - 5, (CGRectGetHeight(rect) - size.width) / 2, size.height, size.height);
  238. }
  239. // Managing Menu Templates
  240. /*!
  241. Returns the menu template object used to dynamically construct the search pop-up icon menu.
  242. @return The current menu template.
  243. */
  244. - (CPMenu)searchMenuTemplate
  245. {
  246. return _searchMenuTemplate;
  247. }
  248. /*!
  249. Sets the menu template object used to dynamically construct the receiver's pop-up icon menu.
  250. @param menu The menu template to use.
  251. The receiver looks for the tag constants described in ŇMenu tagsÓ to determine how to populate the menu with items related to recent searches. See ŇConfiguring a Search MenuÓ for a sample of how you might set up the search menu template.
  252. */
  253. - (void)setSearchMenuTemplate:(CPMenu)aMenu
  254. {
  255. _searchMenuTemplate = aMenu;
  256. [self resetSearchButton];
  257. [self _loadRecentSearchList];
  258. [self _updateSearchMenu];
  259. }
  260. // Managing Search Modes
  261. /*!
  262. Returns a Boolean value indicating whether the receiver sends the search action message when the user clicks the search button (or presses return) or after each keystroke.
  263. @return \c YES if the action message is sent all at once when the user clicks the search button or presses return; otherwise, NO if the search string is sent after each keystroke. The default value is NO.
  264. */
  265. - (BOOL)sendsWholeSearchString
  266. {
  267. return _sendsWholeSearchString;
  268. }
  269. /*!
  270. Sets whether the receiver sends the search action message when the user clicks the search button (or presses return) or after each keystroke.
  271. @param flag \c YES to send the action message all at once when the user clicks the search button or presses return; otherwise, NO to send the search string after each keystroke.
  272. */
  273. - (void)setSendsWholeSearchString:(BOOL)flag
  274. {
  275. _sendsWholeSearchString = flag;
  276. }
  277. /*!
  278. Returns a Boolean value indicating whether the receiver sends its action immediately upon being notified of changes to the search field text or after a brief pause.
  279. @return \c YES if the text field sends its action immediately upon notification of any changes to the search field; otherwise, NO.
  280. */
  281. - (BOOL)sendsSearchStringImmediately
  282. {
  283. return _sendsSearchStringImmediately;
  284. }
  285. /*!
  286. Sets whether the text field sends its action message to the target immediately upon notification of any changes to the search field text or after a brief pause.
  287. @param flag \c YES to send the text field's action immediately upon notification of any changes to the search field; otherwise, NO if you want the text field to pause briefly before sending its action message. Pausing gives the user the opportunity to type more text into the search field before initiating the search.
  288. */
  289. - (void)setSendsSearchStringImmediately:(BOOL)flag
  290. {
  291. _sendsSearchStringImmediately = flag;
  292. }
  293. // Managing Recent Search Strings
  294. /*!
  295. Returns the maximum number of recent search strings to display in the custom search menu.
  296. @return The maximum number of search strings that can appear in the menu. This value is between 0 and 254.
  297. */
  298. - (int)maximumRecents
  299. {
  300. return _maximumRecents;
  301. }
  302. /*!
  303. Sets the maximum number of search strings that can appear in the search menu.
  304. @param maxRecents The maximum number of search strings that can appear in the menu. This value can be between 0 and 254. Specifying a value less than 0 sets the value to the default, which is 10. Specifying a value greater than 254 sets the maximum to 254.
  305. */
  306. - (void)setMaximumRecents:(int)max
  307. {
  308. if (max > 254)
  309. max = 254;
  310. else if (max < 0)
  311. max = 10;
  312. _maximumRecents = max;
  313. }
  314. /*!
  315. Returns the list of recent search strings for the control.
  316. @return An array of \c CPString objects, each of which contains a search string either displayed in the search menu or from a recent autosave archive. If there have been no recent searches and no prior searches saved under an autosave name, this array may be empty.
  317. */
  318. - (CPArray)recentSearches
  319. {
  320. return _recentSearches;
  321. }
  322. /*!
  323. Sets the list of recent search strings to list in the pop-up icon menu of the receiver.
  324. @param searches An array of CPString objects containing the search strings.
  325. You might use this method to set the recent list of searches from an archived copy.
  326. */
  327. - (void)setRecentSearches:(CPArray)searches
  328. {
  329. var max = MIN([self maximumRecents], [searches count]),
  330. searches = [searches subarrayWithRange:CPMakeRange(0, max)];
  331. _recentSearches = searches;
  332. [self _autosaveRecentSearchList];
  333. }
  334. /*!
  335. Returns the key under which the prior list of recent search strings has been archived.
  336. @return The autosave name, which is used as a key in the standard user defaults to save the recent searches. The default value is nil, which causes searches not to be autosaved.
  337. */
  338. - (CPString)recentsAutosaveName
  339. {
  340. return _recentsAutosaveName;
  341. }
  342. /*!
  343. Sets the autosave name under which the receiver automatically archives the list of recent search strings.
  344. @param name The autosave name, which is used as a key in the standard user defaults to save the recent searches. If you specify nil or an empty string for this parameter, no autosave name is set and searches are not autosaved.
  345. */
  346. - (void)setRecentsAutosaveName:(CPString)name
  347. {
  348. if (_recentsAutosaveName != nil)
  349. [self _deregisterForAutosaveNotification];
  350. _recentsAutosaveName = name;
  351. if (_recentsAutosaveName != nil)
  352. [self _registerForAutosaveNotification];
  353. }
  354. // Private methods and subclassing
  355. - (CGRect)contentRectForBounds:(CGRect)bounds
  356. {
  357. var superbounds = [super contentRectForBounds:bounds];
  358. return [self searchTextRectForBounds:superbounds];
  359. }
  360. + (double)_keyboardDelayForPartialSearchString:(CPString)string
  361. {
  362. return (6 - MIN([string length], 4)) / 10;
  363. }
  364. - (CPMenu)menu
  365. {
  366. return _searchMenu;
  367. }
  368. - (BOOL)isOpaque
  369. {
  370. return [super isOpaque] && [_cancelButton isOpaque] && [_searchButton isOpaque];
  371. }
  372. - (void)_updateCancelButtonVisibility
  373. {
  374. [_cancelButton setHidden:([[self stringValue] length] === 0)];
  375. }
  376. - (void)_searchFieldTextDidChange:(CPNotification)aNotification
  377. {
  378. if (![self sendsWholeSearchString])
  379. {
  380. if ([self sendsSearchStringImmediately])
  381. [self _sendPartialString];
  382. else
  383. {
  384. [_partialStringTimer invalidate];
  385. var timeInterval = [CPSearchField _keyboardDelayForPartialSearchString:[self stringValue]];
  386. _partialStringTimer = [CPTimer scheduledTimerWithTimeInterval:timeInterval
  387. target:self
  388. selector:@selector(_sendPartialString)
  389. userInfo:nil
  390. repeats:NO];
  391. }
  392. }
  393. [self _updateCancelButtonVisibility];
  394. }
  395. - (void)_sendAction:(id)sender
  396. {
  397. [self sendAction:[self action] to:[self target]];
  398. }
  399. - (BOOL)sendAction:(SEL)anAction to:(id)anObject
  400. {
  401. [super sendAction:anAction to:anObject];
  402. [_partialStringTimer invalidate];
  403. [self _addStringToRecentSearches:[self stringValue]];
  404. [self _updateCancelButtonVisibility];
  405. }
  406. - (void)_addStringToRecentSearches:(CPString)string
  407. {
  408. if (string === nil || string === @"" || [_recentSearches containsObject:string])
  409. return;
  410. var searches = [CPMutableArray arrayWithArray:_recentSearches];
  411. [searches addObject:string];
  412. [self setRecentSearches:searches];
  413. [self _updateSearchMenu];
  414. }
  415. - (CPView)hitTest:(CGPoint)aPoint
  416. {
  417. // Make sure a hit anywhere within the search field returns the search field itself
  418. if (CGRectContainsPoint([self frame], aPoint))
  419. return self;
  420. else
  421. return nil;
  422. }
  423. - (BOOL)resignFirstResponder
  424. {
  425. return _canResignFirstResponder && [super resignFirstResponder];
  426. }
  427. - (void)mouseDown:(CPEvent)anEvent
  428. {
  429. var location = [anEvent locationInWindow],
  430. point = [self convertPoint:location fromView:nil];
  431. if (CGRectContainsPoint([self searchButtonRectForBounds:[self bounds]], point))
  432. {
  433. if (_searchMenuTemplate == nil)
  434. {
  435. if ([_searchButton target] && [_searchButton action])
  436. [_searchButton mouseDown:anEvent];
  437. else
  438. [self _sendAction:self];
  439. }
  440. else
  441. [self _showMenu];
  442. }
  443. else if (CGRectContainsPoint([self cancelButtonRectForBounds:[self bounds]], point))
  444. [_cancelButton mouseDown:anEvent];
  445. else
  446. [super mouseDown:anEvent];
  447. }
  448. /*!
  449. Provides the common case items for a recent searches menu. If there are not recent searches,
  450. displays a single disabled item:
  451. No Recent Searches
  452. If there are 1 more recent searches, it displays:
  453. Recent Searches
  454. recent search 1
  455. recent search 2
  456. etc.
  457. ---------------------
  458. Clear Recent Searches
  459. If you wish to add items before or after the template, you can. If you put items
  460. before, a separator will automatically be placed before the default template item.
  461. If you add items after the default template, it is your responsibility to add a separator.
  462. To add a custom item:
  463. item = [[CPMenuItem alloc] initWithTitle:@"google"
  464. action:@selector(google:)
  465. keyEquivalent:@""];
  466. [item setTag:700];
  467. [item setTarget:self];
  468. [template addItem:item];
  469. Be sure that your custom items do not use tags in the range 1000-1003 inclusive.
  470. If you wish to maintain state in custom menu items that you add, you will need to maintain
  471. the item state yourself, then in the action method of the custom items, modify the items
  472. in the search menu template and send [searchField setSearchMenuTemplate:template] to update the menu.
  473. */
  474. - (CPMenu)defaultSearchMenuTemplate
  475. {
  476. var template = [[CPMenu alloc] init],
  477. item;
  478. item = [[CPMenuItem alloc] initWithTitle:@"Recent Searches"
  479. action:nil
  480. keyEquivalent:@""];
  481. [item setTag:CPSearchFieldRecentsTitleMenuItemTag];
  482. [item setEnabled:NO];
  483. [template addItem:item];
  484. item = [[CPMenuItem alloc] initWithTitle:@"Recent search item"
  485. action:@selector(_searchFieldSearch:)
  486. keyEquivalent:@""];
  487. [item setTag:CPSearchFieldRecentsMenuItemTag];
  488. [item setTarget:self];
  489. [template addItem:item];
  490. item = [[CPMenuItem alloc] initWithTitle:@"Clear Recent Searches"
  491. action:@selector(_searchFieldClearRecents:)
  492. keyEquivalent:@""];
  493. [item setTag:CPSearchFieldClearRecentsMenuItemTag];
  494. [item setTarget:self];
  495. [template addItem:item];
  496. item = [[CPMenuItem alloc] initWithTitle:@"No Recent Searches"
  497. action:nil
  498. keyEquivalent:@""];
  499. [item setTag:CPSearchFieldNoRecentsMenuItemTag];
  500. [item setEnabled:NO];
  501. [template addItem:item];
  502. return template;
  503. }
  504. - (void)_updateSearchMenu
  505. {
  506. if (_searchMenuTemplate === nil)
  507. return;
  508. var menu = [[CPMenu alloc] init],
  509. countOfRecents = [_recentSearches count],
  510. numberOfItems = [_searchMenuTemplate numberOfItems];
  511. for (var i = 0; i < numberOfItems; i++)
  512. {
  513. var item = [[_searchMenuTemplate itemAtIndex:i] copy];
  514. switch ([item tag])
  515. {
  516. case CPSearchFieldRecentsTitleMenuItemTag:
  517. if (countOfRecents === 0)
  518. continue;
  519. if ([menu numberOfItems] > 0)
  520. [self _addSeparatorToMenu:menu];
  521. break;
  522. case CPSearchFieldRecentsMenuItemTag:
  523. {
  524. var itemAction = @selector(_searchFieldSearch:);
  525. for (var recentIndex = 0; recentIndex < countOfRecents; ++recentIndex)
  526. {
  527. // RECENT_SEARCH_PREFIX is a hack until CPMenuItem -setIndentationLevel works
  528. var recentItem = [[CPMenuItem alloc] initWithTitle:RECENT_SEARCH_PREFIX + [_recentSearches objectAtIndex:recentIndex]
  529. action:itemAction
  530. keyEquivalent:[item keyEquivalent]];
  531. [item setTarget:self];
  532. [menu addItem:recentItem];
  533. }
  534. continue;
  535. }
  536. case CPSearchFieldClearRecentsMenuItemTag:
  537. if (countOfRecents === 0)
  538. continue;
  539. if ([menu numberOfItems] > 0)
  540. [self _addSeparatorToMenu:menu];
  541. [item setAction:@selector(_searchFieldClearRecents:)];
  542. [item setTarget:self];
  543. break;
  544. case CPSearchFieldNoRecentsMenuItemTag:
  545. if (countOfRecents !== 0)
  546. continue;
  547. if ([menu numberOfItems] > 0)
  548. [self _addSeparatorToMenu:menu];
  549. break;
  550. }
  551. [item setEnabled:([item isEnabled] && [item action] != nil && [item target] != nil)];
  552. [menu addItem:item];
  553. }
  554. [menu setDelegate:self];
  555. _searchMenu = menu;
  556. }
  557. - (void)_addSeparatorToMenu:(CPMenu)aMenu
  558. {
  559. var separator = [CPMenuItem separatorItem];
  560. [separator setEnabled:NO];
  561. [aMenu addItem:separator];
  562. }
  563. - (void)menuWillOpen:(CPMenu)menu
  564. {
  565. _canResignFirstResponder = NO;
  566. }
  567. - (void)menuDidClose:(CPMenu)menu
  568. {
  569. _canResignFirstResponder = YES;
  570. [self becomeFirstResponder];
  571. }
  572. - (void)_showMenu
  573. {
  574. if (_searchMenu === nil || [_searchMenu numberOfItems] === 0 || ![self isEnabled])
  575. return;
  576. var aFrame = [[self superview] convertRect:[self frame] toView:nil],
  577. location = CGPointMake(aFrame.origin.x + 10, aFrame.origin.y + aFrame.size.height - 4);
  578. 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];
  579. [self selectAll:nil];
  580. [CPMenu popUpContextMenu:_searchMenu withEvent:anEvent forView:self];
  581. }
  582. - (void)_sendPartialString
  583. {
  584. [super sendAction:[self action] to:[self target]];
  585. [_partialStringTimer invalidate];
  586. }
  587. - (void)cancelOperation:(id)sender
  588. {
  589. [self setObjectValue:@""];
  590. [self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
  591. [self _updateCancelButtonVisibility];
  592. }
  593. - (void)_searchFieldSearch:(id)sender
  594. {
  595. var searchString = [[sender title] substringFromIndex:[RECENT_SEARCH_PREFIX length]];
  596. if ([sender tag] != CPSearchFieldRecentsMenuItemTag)
  597. [self _addStringToRecentSearches:searchString];
  598. [self setObjectValue:searchString];
  599. [self _sendPartialString];
  600. [self selectAll:nil];
  601. [self _updateCancelButtonVisibility];
  602. }
  603. - (void)_searchFieldClearRecents:(id)sender
  604. {
  605. [self setRecentSearches:[CPArray array]];
  606. [self _updateSearchMenu];
  607. [self setStringValue:@""];
  608. [self _updateCancelButtonVisibility];
  609. }
  610. - (void)_registerForAutosaveNotification
  611. {
  612. [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_updateAutosavedRecents:) name:CPAutosavedRecentsChangedNotification object:_recentsAutosaveName];
  613. }
  614. - (void)_deregisterForAutosaveNotification
  615. {
  616. [[CPNotificationCenter defaultCenter] removeObserver:self name:CPAutosavedRecentsChangedNotification object:_recentsAutosaveName];
  617. }
  618. - (void)_autosaveRecentSearchList
  619. {
  620. if (_recentsAutosaveName != nil)
  621. [[CPNotificationCenter defaultCenter] postNotificationName:CPAutosavedRecentsChangedNotification object:_recentsAutosaveName];
  622. }
  623. - (void)_updateAutosavedRecents:(id)notification
  624. {
  625. var name = [notification object];
  626. [[CPUserDefaults standardUserDefaults] setObject:_recentSearches forKey:name];
  627. }
  628. - (void)_loadRecentSearchList
  629. {
  630. var name = [self recentsAutosaveName];
  631. if (name === nil)
  632. return;
  633. var list = [[CPUserDefaults standardUserDefaults] objectForKey:name];
  634. if (list !== nil)
  635. _recentSearches = list;
  636. }
  637. @end
  638. var CPRecentsAutosaveNameKey = @"CPRecentsAutosaveNameKey",
  639. CPSendsWholeSearchStringKey = @"CPSendsWholeSearchStringKey",
  640. CPSendsSearchStringImmediatelyKey = @"CPSendsSearchStringImmediatelyKey",
  641. CPMaximumRecentsKey = @"CPMaximumRecentsKey",
  642. CPSearchMenuTemplateKey = @"CPSearchMenuTemplateKey";
  643. @implementation CPSearchField (CPCoding)
  644. - (void)encodeWithCoder:(CPCoder)coder
  645. {
  646. [_searchButton removeFromSuperview];
  647. [_cancelButton removeFromSuperview];
  648. [super encodeWithCoder:coder];
  649. if (_searchButton)
  650. [self addSubview:_searchButton];
  651. if (_cancelButton)
  652. [self addSubview:_cancelButton];
  653. [coder encodeBool:_sendsWholeSearchString forKey:CPSendsWholeSearchStringKey];
  654. [coder encodeBool:_sendsSearchStringImmediately forKey:CPSendsSearchStringImmediatelyKey];
  655. [coder encodeInt:_maximumRecents forKey:CPMaximumRecentsKey];
  656. if (_recentsAutosaveName)
  657. [coder encodeObject:_recentsAutosaveName forKey:CPRecentsAutosaveNameKey];
  658. if (_searchMenuTemplate)
  659. [coder encodeObject:_searchMenuTemplate forKey:CPSearchMenuTemplateKey];
  660. }
  661. - (id)initWithCoder:(CPCoder)coder
  662. {
  663. if (self = [super initWithCoder:coder])
  664. {
  665. [self setRecentsAutosaveName:[coder decodeObjectForKey:CPRecentsAutosaveNameKey]];
  666. _sendsWholeSearchString = [coder decodeBoolForKey:CPSendsWholeSearchStringKey];
  667. _sendsSearchStringImmediately = [coder decodeBoolForKey:CPSendsSearchStringImmediatelyKey];
  668. _maximumRecents = [coder decodeIntForKey:CPMaximumRecentsKey];
  669. var template = [coder decodeObjectForKey:CPSearchMenuTemplateKey];
  670. if (template)
  671. [self setSearchMenuTemplate:template];
  672. [self _init];
  673. }
  674. return self;
  675. }
  676. @end