00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 @import "CPTextField.j"
00024
00025 #include "Platform/Platform.h"
00026
00027 CPSearchFieldRecentsTitleMenuItemTag = 1000;
00028 CPSearchFieldRecentsMenuItemTag = 1001;
00029 CPSearchFieldClearRecentsMenuItemTag = 1002;
00030 CPSearchFieldNoRecentsMenuItemTag = 1003;
00031
00032 var CPSearchFieldSearchImage = nil,
00033 CPSearchFieldFindImage = nil,
00034 CPSearchFieldCancelImage = nil,
00035 CPSearchFieldCancelPressedImage = nil;
00036
00044 @implementation CPSearchField : CPTextField
00045 {
00046 CPButton _searchButton;
00047 CPButton _cancelButton;
00048 CPMenu _searchMenuTemplate;
00049 CPMenu _searchMenu;
00050
00051 CPString _recentsAutosaveName;
00052 CPArray _recentSearches;
00053
00054 int _maximumRecents;
00055 BOOL _sendsWholeSearchString;
00056 BOOL _sendsSearchStringImmediately;
00057 CPTimer _partialStringTimer;
00058 }
00059
00060 + (void)initialize
00061 {
00062 if (self != [CPSearchField class])
00063 return;
00064
00065 var bundle = [CPBundle bundleForClass:self];
00066 CPSearchFieldSearchImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSearchField/CPSearchFieldSearch.png"]];
00067 CPSearchFieldFindImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSearchField/CPSearchFieldFind.png"]];
00068 CPSearchFieldCancelImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSearchField/CPSearchFieldCancel.png"]];
00069 CPSearchFieldCancelPressedImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSearchField/CPSearchFieldCancelPressed.png"]];
00070 }
00071
00072 - (id)initWithFrame:(CGRect)frame
00073 {
00074 if (self = [super initWithFrame:frame])
00075 {
00076 _recentSearches = [CPArray array];
00077 _maximumRecents = 10;
00078 _sendsWholeSearchString = NO;
00079 _sendsSearchStringImmediately = NO;
00080 _recentsAutosaveName = nil;
00081
00082 [self _initWithFrame:frame];
00083 #if PLATFORM(DOM)
00084 _cancelButton._DOMElement.style.cursor = "default";
00085 _searchButton._DOMElement.style.cursor = "default";
00086 #endif
00087 }
00088
00089 return self;
00090 }
00091
00092 - (void)_initWithFrame:(CGRect)frame
00093 {
00094 [self setBezeled:YES];
00095 [self setBezelStyle:CPTextFieldRoundedBezel];
00096 [self setBordered:YES];
00097 [self setEditable:YES];
00098 [self setDelegate:self];
00099
00100 _cancelButton = [[CPButton alloc] initWithFrame:CPMakeRect(frame.size.width - 27,(frame.size.height-22)/2,22,22)];
00101 [self resetCancelButton];
00102 [_cancelButton setHidden:YES];
00103 [_cancelButton setAutoresizingMask:CPViewMinXMargin];
00104 [self addSubview:_cancelButton];
00105
00106 _searchButton = [[CPButton alloc] initWithFrame:CPMakeRect(5,(frame.size.height-25)/2,25,25)];
00107 [self resetSearchButton];
00108 [self addSubview:_searchButton];
00109 }
00110
00111
00116 - (void)setSearchButton:(CPButton)button
00117 {
00118 _searchButton = button;
00119 }
00120
00125 - (CPButton)searchButton
00126 {
00127 return _searchButton;
00128 }
00129
00134 - (void)resetSearchButton
00135 {
00136 var searchButtonImage,
00137 action,
00138 target,
00139 button = [self searchButton];
00140
00141 if (_searchMenuTemplate === nil)
00142 {
00143 searchButtonImage = CPSearchFieldSearchImage;
00144 action = @selector(_sendAction:);
00145 target = self;
00146 }
00147 else
00148 {
00149 searchButtonImage = CPSearchFieldFindImage;
00150 action = @selector(_showMenu:);
00151 target = self;
00152 }
00153
00154 [button setBordered:NO];
00155 [button setImageScaling:CPScaleToFit];
00156 [button setImage:searchButtonImage];
00157 [button setAutoresizingMask:CPViewMaxXMargin];
00158 [button setTarget:target];
00159 [button setAction:action];
00160 }
00161
00166 - (void)setCancelButton:(CPButton)button
00167 {
00168 _cancelButton = button;
00169 }
00170
00175 - (CPButton)cancelButton
00176 {
00177 return _cancelButton;
00178 }
00179
00184 - (void)resetCancelButton
00185 {
00186 var button = [self cancelButton];
00187 [button setBordered:NO];
00188 [button setImageScaling:CPScaleToFit];
00189 [button setImage:CPSearchFieldCancelImage];
00190 [button setAlternateImage:CPSearchFieldCancelPressedImage];
00191 [button setAutoresizingMask:CPViewMinXMargin];
00192 [button setTarget:self];
00193 [button setAction:@selector(_searchFieldCancel:)];
00194 }
00195
00196
00203 - (CPRect)searchTextRectForBounds:(CPRect)rect
00204 {
00205 var leftOffset = 0, width = rect.size.width;
00206
00207 if (_searchButton)
00208 {
00209 var searchRect = [_searchButton frame];
00210 leftOffset = searchRect.origin.x + searchRect.size.width;
00211 }
00212
00213 if (_cancelButton)
00214 {
00215 var cancelRect = [_cancelButton frame];
00216 width = cancelRect.origin.x - leftOffset;
00217 }
00218
00219 return CPMakeRect(leftOffset,rect.origin.y,width,rect.size.height);
00220 }
00221
00227 - (CPRect)searchButtonRectForBounds:(CPRect)rect
00228 {
00229 return [_searchButton frame];
00230 }
00231
00237 - (CPRect)cancelButtonRectForBounds:(CPRect)rect
00238 {
00239 return [_cancelButton frame];
00240 }
00241
00242
00247 - (CPMenu)searchMenuTemplate
00248 {
00249 return _searchMenuTemplate;
00250 }
00251
00257 - (void)setSearchMenuTemplate:(CPMenu)menu
00258 {
00259 _searchMenuTemplate = menu;
00260
00261 [self resetSearchButton];
00262 [self _loadRecentSearchList];
00263 [self _updateSearchMenu];
00264 }
00265
00266
00271 - (BOOL)sendsWholeSearchString
00272 {
00273 return _sendsWholeSearchString;
00274 }
00275
00280 - (void)setSendsWholeSearchString:(BOOL)flag
00281 {
00282 _sendsWholeSearchString = flag;
00283 }
00284
00289 - (BOOL)sendsSearchStringImmediately
00290 {
00291 return _sendsSearchStringImmediately;
00292 }
00293
00298 - (void)setSendsSearchStringImmediately:(BOOL)flag
00299 {
00300 _sendsSearchStringImmediately = flag;
00301 }
00302
00303
00308 - (int)maximumRecents
00309 {
00310 return _maximumRecents;
00311 }
00312
00317 - (void)setMaximumRecents:(int)max
00318 {
00319 if (max > 254)
00320 max = 254;
00321 else if (max < 0)
00322 max = 10;
00323
00324 _maximumRecents = max;
00325 }
00326
00331 - (CPArray)recentSearches
00332 {
00333 return _recentSearches;
00334 }
00335
00341 - (void)setRecentSearches:(CPArray)searches
00342 {
00343 var max = MIN([self maximumRecents], [searches count]),
00344 searches = [searches subarrayWithRange:CPMakeRange(0, max)];
00345
00346 _recentSearches = searches;
00347 [self _autosaveRecentSearchList];
00348 }
00349
00354 - (CPString)recentsAutosaveName
00355 {
00356 return _recentsAutosaveName;
00357 }
00358
00363 - (void)setRecentsAutosaveName:(CPString)name
00364 {
00365 if (_recentsAutosaveName != nil)
00366 [self _deregisterForAutosaveNotification];
00367
00368 _recentsAutosaveName = name;
00369
00370 if (_recentsAutosaveName != nil)
00371 [self _registerForAutosaveNotification];
00372 }
00373
00374
00375
00376 - (CPRect)contentRectForBounds:(CPRect)bounds
00377 {
00378 var superbounds = [super contentRectForBounds:bounds];
00379 return [self searchTextRectForBounds:superbounds];
00380 }
00381
00382 + (double)_keyboardDelayForPartialSearchString:(CPString)string
00383 {
00384 return (6 - MIN([string length],4))/10;
00385 }
00386
00387 - (CPMenu)menu
00388 {
00389 return _searchMenu;
00390 }
00391
00392 - (BOOL)isOpaque
00393 {
00394 return [super isOpaque] && [_cancelButton isOpaque] && [_searchButton isOpaque];
00395 }
00396
00397 - (void)_updateCancelButtonVisibility
00398 {
00399 [_cancelButton setHidden:([[self stringValue] length] === 0)];
00400 }
00401
00402 - (void)controlTextDidChange:(CPNotification)aNotification
00403 {
00404 if (![self sendsWholeSearchString])
00405 {
00406 if ([self sendsSearchStringImmediately])
00407 [self _sendPartialString];
00408 else
00409 {
00410 [_partialStringTimer invalidate];
00411 var timeInterval = [CPSearchField _keyboardDelayForPartialSearchString:[self stringValue]];
00412
00413 _partialStringTimer = [CPTimer scheduledTimerWithTimeInterval:timeInterval
00414 target:self
00415 selector:@selector(_sendPartialString)
00416 userInfo:nil
00417 repeats:NO];
00418 }
00419 }
00420
00421 [self _updateCancelButtonVisibility];
00422 }
00423
00424 - (void)_sendAction:(id)sender
00425 {
00426 [self sendAction:[self action] to:[self target]];
00427 }
00428
00429 - (void)sendAction:(SEL)anAction to:(id)anObject
00430 {
00431 [super sendAction:anAction to:anObject];
00432
00433 [_partialStringTimer invalidate];
00434
00435 [self _addStringToRecentSearches:[self stringValue]];
00436 [self _updateCancelButtonVisibility];
00437 }
00438
00439 - (void)_addStringToRecentSearches:(CPString)string
00440 {
00441 if (string === nil || string === @"" || [_recentSearches containsObject:string])
00442 return;
00443
00444 var searches = [CPMutableArray arrayWithArray:_recentSearches];
00445 [searches addObject:string];
00446 [self setRecentSearches:searches];
00447 [self _updateSearchMenu];
00448 }
00449
00450 - (BOOL)trackMouse:(CPEvent)event
00451 {
00452 var rect,
00453 point,
00454 location = [event locationInWindow];
00455
00456 point = [self convertPoint:location fromView:nil];
00457
00458 rect = [self searchButtonRectForBounds:[self frame]];
00459 if (CPRectContainsPoint(rect,point))
00460 {
00461 return [[self searchButton] trackMouse:event];
00462 }
00463
00464 rect = [self cancelButtonRectForBounds:[self frame]];
00465 if (CPRectContainsPoint(rect,point))
00466 {
00467 return [[self cancelButton] trackMouse:event];
00468 }
00469
00470 return [super trackMouse:event];
00471 }
00472
00473 - (CPMenu)_defaultSearchMenuTemplate
00474 {
00475 var template, item;
00476
00477 template = [[CPMenu alloc] init];
00478
00479 item = [[CPMenuItem alloc] initWithTitle:@"Recent searches"
00480 action:NULL
00481 keyEquivalent:@""];
00482 [item setTag:CPSearchFieldRecentsTitleMenuItemTag];
00483 [item setEnabled:NO];
00484 [template addItem:item];
00485
00486 item = [[CPMenuItem alloc] initWithTitle:@"Recent search item"
00487 action:@selector(_searchFieldSearch:)
00488 keyEquivalent:@""];
00489 [item setTag:CPSearchFieldRecentsMenuItemTag];
00490 [item setTarget:self];
00491 [template addItem:item];
00492
00493 item = [[CPMenuItem alloc] initWithTitle:@"Clear recent searches"
00494 action:@selector(_searchFieldClearRecents:)
00495 keyEquivalent:@""];
00496 [item setTag:CPSearchFieldClearRecentsMenuItemTag];
00497 [item setTarget:self];
00498 [template addItem:item];
00499
00500 item = [[CPMenuItem alloc] initWithTitle:@"No recent searches"
00501 action:NULL
00502 keyEquivalent:@""];
00503 [item setTag:CPSearchFieldNoRecentsMenuItemTag];
00504 [item setEnabled:NO];
00505 [template addItem:item];
00506
00507 return template;
00508 }
00509
00510 - (void)_updateSearchMenu
00511 {
00512 if (_searchMenuTemplate === nil)
00513 return;
00514
00515 var i, menu = [[CPMenu alloc] init],
00516 countOfRecents = [_recentSearches count],
00517 numberOfItems = [_searchMenuTemplate numberOfItems];
00518
00519 for (i = 0; i < numberOfItems; i++)
00520 {
00521 var item = [_searchMenuTemplate itemAtIndex:i],
00522 tag = [item tag];
00523
00524 if (!(tag === CPSearchFieldRecentsTitleMenuItemTag && countOfRecents === 0) &&
00525 !(tag === CPSearchFieldClearRecentsMenuItemTag && countOfRecents === 0) &&
00526 !(tag === CPSearchFieldNoRecentsMenuItemTag && countOfRecents != 0) &&
00527 !(tag === CPSearchFieldRecentsMenuItemTag))
00528 {
00529 var itemAction, itemTarget;
00530 switch (tag)
00531 {
00532 case CPSearchFieldRecentsTitleMenuItemTag : itemAction = NULL; itemTarget = NULL; break;
00533 case CPSearchFieldClearRecentsMenuItemTag : itemAction = @selector(_searchFieldClearRecents:); itemTarget = self; break;
00534 case CPSearchFieldNoRecentsMenuItemTag : itemAction = NULL; itemTarget = NULL; break;
00535 default: itemAction = [item action]; itemTarget = [item target]; break;
00536 }
00537
00538 if (tag === CPSearchFieldClearRecentsMenuItemTag || tag === CPSearchFieldRecentsTitleMenuItemTag)
00539 {
00540 var separator = [CPMenuItem separatorItem];
00541 [separator setEnabled:NO];
00542 [menu addItem:separator];
00543 }
00544
00545 var templateItem = [[CPMenuItem alloc] initWithTitle:[item title]
00546 action:itemAction
00547 keyEquivalent:[item keyEquivalent]];
00548 [templateItem setTarget:itemTarget];
00549 [templateItem setEnabled:([item isEnabled] && itemAction != NULL)];
00550 [templateItem setTag:tag];
00551 [menu addItem:templateItem];
00552 }
00553 else if (tag === CPSearchFieldRecentsMenuItemTag)
00554 {
00555 var j;
00556 for (j = 0; j < countOfRecents; j++)
00557 {
00558 var rencentItem = [[CPMenuItem alloc] initWithTitle:[_recentSearches objectAtIndex:j]
00559 action:@selector(_searchFieldSearch:)
00560 keyEquivalent:[item keyEquivalent]];
00561 [rencentItem setTarget:self];
00562 [menu addItem:rencentItem];
00563 }
00564 }
00565 }
00566 _searchMenu = menu;
00567 }
00568
00569 - (void)_showMenu:(id)sender
00570 {
00571 if (_searchMenu === nil || [_searchMenu numberOfItems] === 0 || ![self isEnabled])
00572 return;
00573
00574 var aFrame = [[self superview] convertRect:[self frame] toView:nil],
00575 location = CPMakePoint(aFrame.origin.x + 10, aFrame.origin.y + aFrame.size.height - 4);
00576
00577 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];
00578
00579 [CPMenu popUpContextMenu:_searchMenu withEvent:anEvent forView:sender];
00580 }
00581
00582 - (void)_sendPartialString
00583 {
00584 [[self target] performSelector:[self action] withObject:self];
00585 }
00586
00587 - (void)_searchFieldCancel:(id)sender
00588 {
00589 [self setObjectValue:@""];
00590 [self _sendPartialString];
00591 [self _updateCancelButtonVisibility];
00592 }
00593
00594 - (void)_searchFieldSearch:(id)sender
00595 {
00596 var searchString = [sender title];
00597
00598 if ([sender tag] != CPSearchFieldRecentsMenuItemTag)
00599 [self _addStringToRecentSearches:searchString];
00600
00601 [self setObjectValue:searchString];
00602 [self _sendPartialString];
00603
00604 [self _updateCancelButtonVisibility];
00605 }
00606
00607 - (void)_searchFieldClearRecents:(id)sender
00608 {
00609 [self setRecentSearches:[CPArray array]];
00610 [self _updateSearchMenu];
00611 }
00612
00613 - (void)_registerForAutosaveNotification
00614 {
00615 [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_updateAutosavedRecents:) name:@"CPAutosavedRecentsChangedNotification" object:nil];
00616 }
00617
00618 - (void)_deregisterForAutosaveNotification
00619 {
00620 [[CPNotificationCenter defaultCenter] removeObserver:self name:@"CPAutosavedRecentsChangedNotification" object:nil];
00621 }
00622
00623 - (void)_autosaveRecentSearchList
00624 {
00625 if (_recentsAutosaveName != nil)
00626 [[CPNotificationCenter defaultCenter] postNotificationName:@"CPAutosavedRecentsChangedNotification" object:_recentsAutosaveName];
00627 }
00628
00629 - (void)_updateAutosavedRecents:(id)notification
00630 {
00631 var list = [self recentSearches],
00632 name = [notification object],
00633 bundle_name = [[[CPBundle mainBundle] infoDictionary] objectForKey:"CPBundleName"],
00634 cookie_name = [bundle_name lowercaseString] + "." + [notification object],
00635
00636 cookie = [[CPCookie alloc] initWithName:cookie_name],
00637 cookie_value = [list componentsJoinedByString:@","];
00638
00639 [cookie setValue:cookie_value
00640 expires:[[CPDate alloc] initWithTimeIntervalSinceNow:3600*24*365]
00641 domain:(window.location.href.hostname)];
00642 }
00643
00644 - (void)_loadRecentSearchList
00645 {
00646 var list,
00647 name = [self recentsAutosaveName];
00648
00649 if (name === nil)
00650 return;
00651
00652 var bundle_name = [[[CPBundle mainBundle] infoDictionary] objectForKey:"CPBundleName"],
00653 cookie_name = [bundle_name lowercaseString] + "." + name,
00654
00655 cookie = [[CPCookie alloc] initWithName:cookie_name];
00656
00657 if (cookie != nil)
00658 {
00659 var cookie_value = [cookie value];
00660 list = (cookie_value != @"") ? [cookie_value componentsSeparatedByString:@","] : [CPArray array];
00661 _recentSearches = list;
00662 }
00663 }
00664
00665 @end
00666
00667 var CPSearchButtonKey = @"CPSearchButtonKey",
00668 CPCancelButtonKey = @"CPCancelButtonKey",
00669 CPRecentsAutosaveNameKey = @"CPRecentsAutosaveNameKey",
00670 CPSendsWholeSearchStringKey = @"CPSendsWholeSearchStringKey",
00671 CPSendsSearchStringImmediatelyKey = @"CPSendsSearchStringImmediatelyKey",
00672 CPMaximumRecentsKey = @"CPMaximumRecentsKey",
00673 CPSearchMenuTemplateKey = @"CPSearchMenuTemplateKey";
00674
00675 @implementation CPSearchField (CPCoding)
00676
00677 - (void)encodeWithCoder:(CPCoder)coder
00678 {
00679 [super encodeWithCoder:coder];
00680
00681 [coder encodeObject:_searchButton forKey:CPSearchButtonKey];
00682 [coder encodeObject:_cancelButton forKey:CPCancelButtonKey];
00683 [coder encodeBool:_sendsWholeSearchString forKey:CPSendsWholeSearchStringKey];
00684 [coder encodeBool:_sendsSearchStringImmediately forKey:CPSendsSearchStringImmediatelyKey];
00685 [coder encodeInt:_maximumRecents forKey:CPMaximumRecentsKey];
00686
00687 if (_recentsAutosaveName)
00688 [coder encodeObject:_recentsAutosaveName forKey:CPRecentsAutosaveNameKey];
00689 if (_searchMenuTemplate)
00690 [coder encodeObject:_searchMenuTemplate forKey:CPSearchMenuTemplateKey];
00691 }
00692
00693 - (id)initWithCoder:(CPCoder)coder
00694 {
00695 if (self = [super initWithCoder:coder])
00696 {
00697 _searchButton = [coder decodeObjectForKey:CPSearchButtonKey];
00698 _cancelButton = [coder decodeObjectForKey:CPCancelButtonKey];
00699 _recentsAutosaveName = [coder decodeObjectForKey:CPRecentsAutosaveNameKey];
00700 _sendsWholeSearchString = [coder decodeBoolForKey:CPSendsWholeSearchStringKey];
00701 _sendsSearchStringImmediately = [coder decodeBoolForKey:CPSendsSearchStringImmediatelyKey];
00702 _maximumRecents = [coder decodeIntForKey:CPMaximumRecentsKey];
00703
00704 var template = [coder decodeObjectForKey:CPSearchMenuTemplateKey];
00705 if (template)
00706 [self setSearchMenuTemplate:template];
00707
00708 [self setDelegate:self];
00709 }
00710
00711 return self;
00712 }
00713
00714 @end