| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #import "ui/app_list/cocoa/apps_search_box_controller.h" | |
| 6 | |
| 7 #include "base/mac/foundation_util.h" | |
| 8 #include "base/mac/mac_util.h" | |
| 9 #include "base/macros.h" | |
| 10 #include "base/strings/sys_string_conversions.h" | |
| 11 #include "ui/app_list/app_list_menu.h" | |
| 12 #include "ui/app_list/app_list_model.h" | |
| 13 #include "ui/app_list/resources/grit/app_list_resources.h" | |
| 14 #include "ui/app_list/search_box_model.h" | |
| 15 #include "ui/app_list/search_box_model_observer.h" | |
| 16 #include "ui/base/cocoa/cocoa_base_utils.h" | |
| 17 #import "ui/base/cocoa/controls/hover_image_menu_button.h" | |
| 18 #import "ui/base/cocoa/controls/hover_image_menu_button_cell.h" | |
| 19 #import "ui/base/cocoa/menu_controller.h" | |
| 20 #include "ui/base/resource/resource_bundle.h" | |
| 21 #include "ui/gfx/image/image_skia_util_mac.h" | |
| 22 | |
| 23 namespace { | |
| 24 | |
| 25 // Padding either side of the search icon and menu button. | |
| 26 const CGFloat kPadding = 14; | |
| 27 | |
| 28 // Size of the search icon. | |
| 29 const CGFloat kSearchIconDimension = 32; | |
| 30 | |
| 31 // Size of the menu button on the right. | |
| 32 const CGFloat kMenuButtonDimension = 29; | |
| 33 | |
| 34 // Menu offset relative to the bottom-right corner of the menu button. | |
| 35 const CGFloat kMenuYOffsetFromButton = -4; | |
| 36 const CGFloat kMenuXOffsetFromButton = -7; | |
| 37 | |
| 38 } | |
| 39 | |
| 40 @interface AppsSearchBoxController () | |
| 41 | |
| 42 - (NSImageView*)searchImageView; | |
| 43 - (void)addSubviews; | |
| 44 | |
| 45 @end | |
| 46 | |
| 47 namespace app_list { | |
| 48 | |
| 49 class SearchBoxModelObserverBridge : public SearchBoxModelObserver { | |
| 50 public: | |
| 51 SearchBoxModelObserverBridge(AppsSearchBoxController* parent); | |
| 52 ~SearchBoxModelObserverBridge() override; | |
| 53 | |
| 54 void SetSearchText(const base::string16& text); | |
| 55 | |
| 56 void IconChanged() override; | |
| 57 void SpeechRecognitionButtonPropChanged() override; | |
| 58 void HintTextChanged() override; | |
| 59 void SelectionModelChanged() override; | |
| 60 void TextChanged() override; | |
| 61 | |
| 62 private: | |
| 63 SearchBoxModel* GetModel(); | |
| 64 | |
| 65 AppsSearchBoxController* parent_; // Weak. Owns us. | |
| 66 | |
| 67 DISALLOW_COPY_AND_ASSIGN(SearchBoxModelObserverBridge); | |
| 68 }; | |
| 69 | |
| 70 SearchBoxModelObserverBridge::SearchBoxModelObserverBridge( | |
| 71 AppsSearchBoxController* parent) | |
| 72 : parent_(parent) { | |
| 73 IconChanged(); | |
| 74 HintTextChanged(); | |
| 75 GetModel()->AddObserver(this); | |
| 76 } | |
| 77 | |
| 78 SearchBoxModelObserverBridge::~SearchBoxModelObserverBridge() { | |
| 79 GetModel()->RemoveObserver(this); | |
| 80 } | |
| 81 | |
| 82 SearchBoxModel* SearchBoxModelObserverBridge::GetModel() { | |
| 83 SearchBoxModel* searchBoxModel = [[parent_ delegate] searchBoxModel]; | |
| 84 DCHECK(searchBoxModel); | |
| 85 return searchBoxModel; | |
| 86 } | |
| 87 | |
| 88 void SearchBoxModelObserverBridge::SetSearchText(const base::string16& text) { | |
| 89 SearchBoxModel* model = GetModel(); | |
| 90 model->RemoveObserver(this); | |
| 91 model->SetText(text); | |
| 92 // TODO(tapted): See if this should call SetSelectionModel here. | |
| 93 model->AddObserver(this); | |
| 94 } | |
| 95 | |
| 96 void SearchBoxModelObserverBridge::IconChanged() { | |
| 97 [[parent_ searchImageView] setImage:gfx::NSImageFromImageSkiaWithColorSpace( | |
| 98 GetModel()->icon(), base::mac::GetSRGBColorSpace())]; | |
| 99 } | |
| 100 | |
| 101 void SearchBoxModelObserverBridge::SpeechRecognitionButtonPropChanged() { | |
| 102 // TODO(mukai): implement. | |
| 103 NOTIMPLEMENTED(); | |
| 104 } | |
| 105 | |
| 106 void SearchBoxModelObserverBridge::HintTextChanged() { | |
| 107 [[[parent_ searchTextField] cell] setPlaceholderString: | |
| 108 base::SysUTF16ToNSString(GetModel()->hint_text())]; | |
| 109 } | |
| 110 | |
| 111 void SearchBoxModelObserverBridge::SelectionModelChanged() { | |
| 112 // TODO(tapted): See if anything needs to be done here for RTL. | |
| 113 } | |
| 114 | |
| 115 void SearchBoxModelObserverBridge::TextChanged() { | |
| 116 // Currently the model text is only changed when we are not observing it, or | |
| 117 // it is changed in tests to establish a particular state. | |
| 118 [[parent_ searchTextField] | |
| 119 setStringValue:base::SysUTF16ToNSString(GetModel()->text())]; | |
| 120 [[parent_ delegate] modelTextDidChange]; | |
| 121 } | |
| 122 | |
| 123 } // namespace app_list | |
| 124 | |
| 125 @interface SearchTextField : NSTextField { | |
| 126 @private | |
| 127 NSRect textFrameInset_; | |
| 128 } | |
| 129 | |
| 130 @property(readonly, nonatomic) NSRect textFrameInset; | |
| 131 | |
| 132 - (void)setMarginsWithLeftMargin:(CGFloat)leftMargin | |
| 133 rightMargin:(CGFloat)rightMargin; | |
| 134 | |
| 135 @end | |
| 136 | |
| 137 @interface AppListMenuController : MenuController { | |
| 138 @private | |
| 139 AppsSearchBoxController* searchBoxController_; // Weak. Owns us. | |
| 140 } | |
| 141 | |
| 142 - (id)initWithSearchBoxController:(AppsSearchBoxController*)parent; | |
| 143 | |
| 144 @end | |
| 145 | |
| 146 @implementation AppsSearchBoxController | |
| 147 | |
| 148 @synthesize delegate = delegate_; | |
| 149 | |
| 150 - (id)initWithFrame:(NSRect)frame { | |
| 151 if ((self = [super init])) { | |
| 152 base::scoped_nsobject<NSView> containerView( | |
| 153 [[NSView alloc] initWithFrame:frame]); | |
| 154 [self setView:containerView]; | |
| 155 [self addSubviews]; | |
| 156 } | |
| 157 return self; | |
| 158 } | |
| 159 | |
| 160 - (void)clearSearch { | |
| 161 [searchTextField_ setStringValue:@""]; | |
| 162 // -controlTextDidChange:'s parameter is marked nonnull in the 10.11 SDK, | |
| 163 // so pass a dummy object even though we know that this class's implementation | |
| 164 // never looks at the parameter. | |
| 165 [self controlTextDidChange:[NSNotification notificationWithName:@"" | |
| 166 object:self]]; | |
| 167 } | |
| 168 | |
| 169 - (void)rebuildMenu { | |
| 170 if (![delegate_ appListDelegate]) | |
| 171 return; | |
| 172 | |
| 173 menuController_.reset(); | |
| 174 appListMenu_.reset( | |
| 175 new app_list::AppListMenu([delegate_ appListDelegate])); | |
| 176 menuController_.reset([[AppListMenuController alloc] | |
| 177 initWithSearchBoxController:self]); | |
| 178 [menuButton_ setMenu:[menuController_ menu]]; // Menu will populate here. | |
| 179 } | |
| 180 | |
| 181 - (void)setDelegate:(id<AppsSearchBoxDelegate>)delegate { | |
| 182 [[menuButton_ menu] removeAllItems]; | |
| 183 menuController_.reset(); | |
| 184 appListMenu_.reset(); | |
| 185 bridge_.reset(); // Ensure observers are cleared before updating |delegate_|. | |
| 186 delegate_ = delegate; | |
| 187 if (!delegate_) | |
| 188 return; | |
| 189 | |
| 190 bridge_.reset(new app_list::SearchBoxModelObserverBridge(self)); | |
| 191 [self rebuildMenu]; | |
| 192 } | |
| 193 | |
| 194 - (NSTextField*)searchTextField { | |
| 195 return searchTextField_; | |
| 196 } | |
| 197 | |
| 198 - (NSPopUpButton*)menuControl { | |
| 199 return menuButton_; | |
| 200 } | |
| 201 | |
| 202 - (app_list::AppListMenu*)appListMenu { | |
| 203 return appListMenu_.get(); | |
| 204 } | |
| 205 | |
| 206 - (NSImageView*)searchImageView { | |
| 207 return searchImageView_; | |
| 208 } | |
| 209 | |
| 210 - (void)addSubviews { | |
| 211 NSRect viewBounds = [[self view] bounds]; | |
| 212 searchImageView_.reset([[NSImageView alloc] initWithFrame:NSMakeRect( | |
| 213 kPadding, 0, kSearchIconDimension, NSHeight(viewBounds))]); | |
| 214 | |
| 215 searchTextField_.reset([[SearchTextField alloc] initWithFrame:viewBounds]); | |
| 216 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
| 217 [searchTextField_ setDelegate:self]; | |
| 218 [searchTextField_ setFont:rb.GetFont( | |
| 219 ui::ResourceBundle::MediumFont).GetNativeFont()]; | |
| 220 [searchTextField_ | |
| 221 setMarginsWithLeftMargin:NSMaxX([searchImageView_ frame]) + kPadding | |
| 222 rightMargin:kMenuButtonDimension + 2 * kPadding]; | |
| 223 | |
| 224 // Add the drop-down menu, with a custom button. | |
| 225 NSRect buttonFrame = NSMakeRect( | |
| 226 NSWidth(viewBounds) - kMenuButtonDimension - kPadding, | |
| 227 floor(NSMidY(viewBounds) - kMenuButtonDimension / 2), | |
| 228 kMenuButtonDimension, | |
| 229 kMenuButtonDimension); | |
| 230 menuButton_.reset([[HoverImageMenuButton alloc] initWithFrame:buttonFrame | |
| 231 pullsDown:YES]); | |
| 232 [[menuButton_ hoverImageMenuButtonCell] setDefaultImage: | |
| 233 rb.GetNativeImageNamed(IDR_APP_LIST_TOOLS_NORMAL).AsNSImage()]; | |
| 234 [[menuButton_ hoverImageMenuButtonCell] setAlternateImage: | |
| 235 rb.GetNativeImageNamed(IDR_APP_LIST_TOOLS_PRESSED).AsNSImage()]; | |
| 236 [[menuButton_ hoverImageMenuButtonCell] setHoverImage: | |
| 237 rb.GetNativeImageNamed(IDR_APP_LIST_TOOLS_HOVER).AsNSImage()]; | |
| 238 | |
| 239 [[self view] addSubview:searchImageView_]; | |
| 240 [[self view] addSubview:searchTextField_]; | |
| 241 [[self view] addSubview:menuButton_]; | |
| 242 } | |
| 243 | |
| 244 - (BOOL)control:(NSControl*)control | |
| 245 textView:(NSTextView*)textView | |
| 246 doCommandBySelector:(SEL)command { | |
| 247 // Forward the message first, to handle grid or search results navigation. | |
| 248 BOOL handled = [delegate_ control:control | |
| 249 textView:textView | |
| 250 doCommandBySelector:command]; | |
| 251 if (handled) | |
| 252 return YES; | |
| 253 | |
| 254 // If the delegate did not handle the escape key, it means the window was not | |
| 255 // dismissed because there were search results. Clear them. | |
| 256 if (command == @selector(complete:)) { | |
| 257 [self clearSearch]; | |
| 258 return YES; | |
| 259 } | |
| 260 | |
| 261 return NO; | |
| 262 } | |
| 263 | |
| 264 - (void)controlTextDidChange:(NSNotification*)notification { | |
| 265 if (bridge_) { | |
| 266 bridge_->SetSearchText( | |
| 267 base::SysNSStringToUTF16([searchTextField_ stringValue])); | |
| 268 } | |
| 269 | |
| 270 [delegate_ modelTextDidChange]; | |
| 271 } | |
| 272 | |
| 273 @end | |
| 274 | |
| 275 @interface SearchTextFieldCell : NSTextFieldCell; | |
| 276 | |
| 277 - (NSRect)textFrameForFrameInternal:(NSRect)cellFrame; | |
| 278 | |
| 279 @end | |
| 280 | |
| 281 @implementation SearchTextField | |
| 282 | |
| 283 @synthesize textFrameInset = textFrameInset_; | |
| 284 | |
| 285 + (Class)cellClass { | |
| 286 return [SearchTextFieldCell class]; | |
| 287 } | |
| 288 | |
| 289 - (id)initWithFrame:(NSRect)theFrame { | |
| 290 if ((self = [super initWithFrame:theFrame])) { | |
| 291 [self setFocusRingType:NSFocusRingTypeNone]; | |
| 292 [self setDrawsBackground:NO]; | |
| 293 [self setBordered:NO]; | |
| 294 } | |
| 295 return self; | |
| 296 } | |
| 297 | |
| 298 - (void)setMarginsWithLeftMargin:(CGFloat)leftMargin | |
| 299 rightMargin:(CGFloat)rightMargin { | |
| 300 // Find the preferred height for the current text properties, and center. | |
| 301 NSRect viewBounds = [self bounds]; | |
| 302 [self sizeToFit]; | |
| 303 NSRect textBounds = [self bounds]; | |
| 304 textFrameInset_.origin.x = leftMargin; | |
| 305 textFrameInset_.origin.y = floor(NSMidY(viewBounds) - NSMidY(textBounds)); | |
| 306 textFrameInset_.size.width = leftMargin + rightMargin; | |
| 307 textFrameInset_.size.height = NSHeight(viewBounds) - NSHeight(textBounds); | |
| 308 [self setFrame:viewBounds]; | |
| 309 } | |
| 310 | |
| 311 @end | |
| 312 | |
| 313 @implementation SearchTextFieldCell | |
| 314 | |
| 315 - (NSRect)textFrameForFrameInternal:(NSRect)cellFrame { | |
| 316 SearchTextField* searchTextField = | |
| 317 base::mac::ObjCCastStrict<SearchTextField>([self controlView]); | |
| 318 NSRect insetRect = [searchTextField textFrameInset]; | |
| 319 cellFrame.origin.x += insetRect.origin.x; | |
| 320 cellFrame.origin.y += insetRect.origin.y; | |
| 321 cellFrame.size.width -= insetRect.size.width; | |
| 322 cellFrame.size.height -= insetRect.size.height; | |
| 323 return cellFrame; | |
| 324 } | |
| 325 | |
| 326 - (NSRect)textFrameForFrame:(NSRect)cellFrame { | |
| 327 return [self textFrameForFrameInternal:cellFrame]; | |
| 328 } | |
| 329 | |
| 330 - (NSRect)textCursorFrameForFrame:(NSRect)cellFrame { | |
| 331 return [self textFrameForFrameInternal:cellFrame]; | |
| 332 } | |
| 333 | |
| 334 - (void)resetCursorRect:(NSRect)cellFrame | |
| 335 inView:(NSView*)controlView { | |
| 336 [super resetCursorRect:[self textCursorFrameForFrame:cellFrame] | |
| 337 inView:controlView]; | |
| 338 } | |
| 339 | |
| 340 - (NSRect)drawingRectForBounds:(NSRect)theRect { | |
| 341 return [super drawingRectForBounds:[self textFrameForFrame:theRect]]; | |
| 342 } | |
| 343 | |
| 344 - (void)editWithFrame:(NSRect)cellFrame | |
| 345 inView:(NSView*)controlView | |
| 346 editor:(NSText*)editor | |
| 347 delegate:(id)delegate | |
| 348 event:(NSEvent*)event { | |
| 349 [super editWithFrame:[self textFrameForFrame:cellFrame] | |
| 350 inView:controlView | |
| 351 editor:editor | |
| 352 delegate:delegate | |
| 353 event:event]; | |
| 354 } | |
| 355 | |
| 356 - (void)selectWithFrame:(NSRect)cellFrame | |
| 357 inView:(NSView*)controlView | |
| 358 editor:(NSText*)editor | |
| 359 delegate:(id)delegate | |
| 360 start:(NSInteger)start | |
| 361 length:(NSInteger)length { | |
| 362 [super selectWithFrame:[self textFrameForFrame:cellFrame] | |
| 363 inView:controlView | |
| 364 editor:editor | |
| 365 delegate:delegate | |
| 366 start:start | |
| 367 length:length]; | |
| 368 } | |
| 369 | |
| 370 @end | |
| 371 | |
| 372 @implementation AppListMenuController | |
| 373 | |
| 374 - (id)initWithSearchBoxController:(AppsSearchBoxController*)parent { | |
| 375 // Need to initialze super with a NULL model, otherwise it will immediately | |
| 376 // try to populate, which can't be done until setting the parent. | |
| 377 if ((self = [super initWithModel:NULL | |
| 378 useWithPopUpButtonCell:YES])) { | |
| 379 searchBoxController_ = parent; | |
| 380 [super setModel:[parent appListMenu]->menu_model()]; | |
| 381 } | |
| 382 return self; | |
| 383 } | |
| 384 | |
| 385 - (NSRect)confinementRectForMenu:(NSMenu*)menu | |
| 386 onScreen:(NSScreen*)screen { | |
| 387 NSPopUpButton* menuButton = [searchBoxController_ menuControl]; | |
| 388 // Ensure the menu comes up below the menu button by trimming the window frame | |
| 389 // to a point anchored below the bottom right of the button. | |
| 390 NSRect anchorRect = [menuButton convertRect:[menuButton bounds] | |
| 391 toView:nil]; | |
| 392 NSPoint anchorPoint = ui::ConvertPointFromWindowToScreen( | |
| 393 [menuButton window], | |
| 394 NSMakePoint(NSMaxX(anchorRect) + kMenuXOffsetFromButton, | |
| 395 NSMinY(anchorRect) - kMenuYOffsetFromButton)); | |
| 396 | |
| 397 NSRect confinementRect = [[menuButton window] frame]; | |
| 398 confinementRect.size = NSMakeSize(anchorPoint.x - NSMinX(confinementRect), | |
| 399 anchorPoint.y - NSMinY(confinementRect)); | |
| 400 return confinementRect; | |
| 401 } | |
| 402 | |
| 403 @end | |
| OLD | NEW |