Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5)

Side by Side Diff: ui/app_list/cocoa/apps_search_results_controller.mm

Issue 2131463002: Purge the App Launcher code from Mac (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Zap mac-specific icon assets Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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_results_controller.h"
6
7 #include "base/mac/foundation_util.h"
8 #include "base/mac/mac_util.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "skia/ext/skia_utils_mac.h"
12 #include "ui/app_list/app_list_constants.h"
13 #include "ui/app_list/app_list_model.h"
14 #import "ui/app_list/cocoa/apps_search_results_model_bridge.h"
15 #include "ui/app_list/search_result.h"
16 #import "ui/base/cocoa/flipped_view.h"
17 #include "ui/gfx/image/image_skia_util_mac.h"
18
19 namespace {
20
21 const CGFloat kPreferredRowHeight = 52;
22 const CGFloat kIconDimension = 32;
23 const CGFloat kIconPadding = 14;
24 const CGFloat kIconViewWidth = kIconDimension + 2 * kIconPadding;
25 const CGFloat kTextTrailPadding = kIconPadding;
26
27 // Map background styles to represent selection and hover in the results list.
28 const NSBackgroundStyle kBackgroundNormal = NSBackgroundStyleLight;
29 const NSBackgroundStyle kBackgroundSelected = NSBackgroundStyleDark;
30 const NSBackgroundStyle kBackgroundHovered = NSBackgroundStyleRaised;
31
32 // The mouse hover colour (3% black over kContentsBackgroundColor).
33 const SkColor kHighlightedRowColor = SkColorSetRGB(0xEE, 0xEE, 0xEE);
34 // The keyboard select colour (6% black over kContentsBackgroundColor).
35 const SkColor kSelectedRowColor = SkColorSetRGB(0xE6, 0xE6, 0xE6);
36
37 } // namespace
38
39 @interface AppsSearchResultsController ()
40
41 - (void)loadAndSetViewWithResultsFrameSize:(NSSize)size;
42 - (void)mouseDown:(NSEvent*)theEvent;
43 - (void)tableViewClicked:(id)sender;
44 - (app_list::AppListModel::SearchResults*)searchResults;
45 - (void)activateSelection;
46 - (BOOL)moveSelectionByDelta:(NSInteger)delta;
47 - (NSMenu*)contextMenuForRow:(NSInteger)rowIndex;
48
49 @end
50
51 @interface AppsSearchResultsCell : NSTextFieldCell
52 @end
53
54 // Immutable class representing a search result in the NSTableView.
55 @interface AppsSearchResultRep : NSObject<NSCopying> {
56 @private
57 base::scoped_nsobject<NSAttributedString> attributedStringValue_;
58 base::scoped_nsobject<NSImage> resultIcon_;
59 }
60
61 @property(readonly, nonatomic) NSAttributedString* attributedStringValue;
62 @property(readonly, nonatomic) NSImage* resultIcon;
63
64 - (id)initWithSearchResult:(app_list::SearchResult*)result;
65
66 - (NSMutableAttributedString*)createRenderText:(const base::string16&)content
67 tags:(const app_list::SearchResult::Tags&)tags;
68
69 - (NSAttributedString*)createResultsAttributedStringWithModel
70 :(app_list::SearchResult*)result;
71
72 @end
73
74 // Simple extension to NSTableView that passes mouseDown events to the
75 // delegate so that drag events can be detected, and forwards requests for
76 // context menus.
77 @interface AppsSearchResultsTableView : NSTableView
78
79 - (AppsSearchResultsController*)controller;
80
81 @end
82
83 @implementation AppsSearchResultsController
84
85 @synthesize delegate = delegate_;
86
87 - (id)initWithAppsSearchResultsFrameSize:(NSSize)size {
88 if ((self = [super init])) {
89 hoveredRowIndex_ = -1;
90 [self loadAndSetViewWithResultsFrameSize:size];
91 }
92 return self;
93 }
94
95 - (app_list::AppListModel::SearchResults*)results {
96 DCHECK([delegate_ appListModel]);
97 return [delegate_ appListModel]->results();
98 }
99
100 - (NSTableView*)tableView {
101 return tableView_;
102 }
103
104 - (void)setDelegate:(id<AppsSearchResultsDelegate>)newDelegate {
105 bridge_.reset();
106 delegate_ = newDelegate;
107 app_list::AppListModel* appListModel = [delegate_ appListModel];
108 if (!appListModel || !appListModel->results()) {
109 [tableView_ reloadData];
110 return;
111 }
112
113 bridge_.reset(new app_list::AppsSearchResultsModelBridge(self));
114 [tableView_ reloadData];
115 }
116
117 - (BOOL)handleCommandBySelector:(SEL)command {
118 if (command == @selector(insertNewline:) ||
119 command == @selector(insertLineBreak:)) {
120 [self activateSelection];
121 return YES;
122 }
123
124 if (command == @selector(moveUp:))
125 return [self moveSelectionByDelta:-1];
126
127 if (command == @selector(moveDown:))
128 return [self moveSelectionByDelta:1];
129
130 return NO;
131 }
132
133 - (void)loadAndSetViewWithResultsFrameSize:(NSSize)size {
134 tableView_.reset(
135 [[AppsSearchResultsTableView alloc] initWithFrame:NSZeroRect]);
136 // Refuse first responder so that focus stays with the search text field.
137 [tableView_ setRefusesFirstResponder:YES];
138 [tableView_ setRowHeight:kPreferredRowHeight];
139 [tableView_ setGridStyleMask:NSTableViewSolidHorizontalGridLineMask];
140 [tableView_
141 setGridColor:skia::SkColorToSRGBNSColor(app_list::kResultBorderColor)];
142 [tableView_ setBackgroundColor:[NSColor clearColor]];
143 [tableView_ setAction:@selector(tableViewClicked:)];
144 [tableView_ setDelegate:self];
145 [tableView_ setDataSource:self];
146 [tableView_ setTarget:self];
147
148 // Tracking to highlight an individual row on mouseover.
149 trackingArea_.reset(
150 [[CrTrackingArea alloc] initWithRect:NSZeroRect
151 options:NSTrackingInVisibleRect |
152 NSTrackingMouseEnteredAndExited |
153 NSTrackingMouseMoved |
154 NSTrackingActiveInKeyWindow
155 owner:self
156 userInfo:nil]);
157 [tableView_ addTrackingArea:trackingArea_.get()];
158
159 base::scoped_nsobject<NSTableColumn> resultsColumn(
160 [[NSTableColumn alloc] initWithIdentifier:@""]);
161 base::scoped_nsobject<NSCell> resultsDataCell(
162 [[AppsSearchResultsCell alloc] initTextCell:@""]);
163 [resultsColumn setDataCell:resultsDataCell];
164 [resultsColumn setWidth:size.width];
165 [tableView_ addTableColumn:resultsColumn];
166
167 // An NSTableView is normally put in a NSScrollView, but scrolling is not
168 // used for the app list. Instead, place it in a container with the desired
169 // size; flipped so the table is anchored to the top-left.
170 base::scoped_nsobject<FlippedView> containerView([[FlippedView alloc]
171 initWithFrame:NSMakeRect(0, 0, size.width, size.height)]);
172
173 // The container is then anchored in an un-flipped view, initially hidden,
174 // so that |containerView| slides in from the top when showing results.
175 base::scoped_nsobject<NSView> clipView(
176 [[NSView alloc] initWithFrame:NSMakeRect(0, 0, size.width, 0)]);
177
178 [containerView addSubview:tableView_];
179 [clipView addSubview:containerView];
180 [self setView:clipView];
181 }
182
183 - (void)mouseDown:(NSEvent*)theEvent {
184 lastMouseDownInView_ = [tableView_ convertPoint:[theEvent locationInWindow]
185 fromView:nil];
186 }
187
188 - (void)tableViewClicked:(id)sender {
189 const CGFloat kDragThreshold = 5;
190 // If the user clicked and then dragged elsewhere, ignore the click.
191 NSEvent* event = [[tableView_ window] currentEvent];
192 NSPoint pointInView = [tableView_ convertPoint:[event locationInWindow]
193 fromView:nil];
194 CGFloat deltaX = pointInView.x - lastMouseDownInView_.x;
195 CGFloat deltaY = pointInView.y - lastMouseDownInView_.y;
196 if (deltaX * deltaX + deltaY * deltaY <= kDragThreshold * kDragThreshold)
197 [self activateSelection];
198
199 // Mouse tracking is suppressed by the NSTableView during a drag, so ensure
200 // any hover state is cleaned up.
201 [self mouseMoved:event];
202 }
203
204 - (app_list::AppListModel::SearchResults*)searchResults {
205 app_list::AppListModel* appListModel = [delegate_ appListModel];
206 DCHECK(bridge_);
207 DCHECK(appListModel);
208 DCHECK(appListModel->results());
209 return appListModel->results();
210 }
211
212 - (void)activateSelection {
213 NSInteger selectedRow = [tableView_ selectedRow];
214 if (!bridge_ || selectedRow < 0)
215 return;
216
217 [delegate_ openResult:[self searchResults]->GetItemAt(selectedRow)];
218 }
219
220 - (BOOL)moveSelectionByDelta:(NSInteger)delta {
221 NSInteger rowCount = [tableView_ numberOfRows];
222 if (rowCount <= 0)
223 return NO;
224
225 NSInteger selectedRow = [tableView_ selectedRow];
226 NSInteger targetRow;
227 if (selectedRow == -1) {
228 // No selection. Select first or last, based on direction.
229 targetRow = delta > 0 ? 0 : rowCount - 1;
230 } else {
231 targetRow = (selectedRow + delta) % rowCount;
232 if (targetRow < 0)
233 targetRow += rowCount;
234 }
235
236 [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:targetRow]
237 byExtendingSelection:NO];
238 return YES;
239 }
240
241 - (NSMenu*)contextMenuForRow:(NSInteger)rowIndex {
242 DCHECK(bridge_);
243 if (rowIndex < 0)
244 return nil;
245
246 [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:rowIndex]
247 byExtendingSelection:NO];
248 return bridge_->MenuForItem(rowIndex);
249 }
250
251 - (NSInteger)numberOfRowsInTableView:(NSTableView*)aTableView {
252 return bridge_ ? [self searchResults]->item_count() : 0;
253 }
254
255 - (id)tableView:(NSTableView*)aTableView
256 objectValueForTableColumn:(NSTableColumn*)aTableColumn
257 row:(NSInteger)rowIndex {
258 // When the results were previously cleared, nothing will be selected. For
259 // that case, select the first row when it appears.
260 if (rowIndex == 0 && [tableView_ selectedRow] == -1) {
261 [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:0]
262 byExtendingSelection:NO];
263 }
264
265 base::scoped_nsobject<AppsSearchResultRep> resultRep(
266 [[AppsSearchResultRep alloc]
267 initWithSearchResult:[self searchResults]->GetItemAt(rowIndex)]);
268 return resultRep.autorelease();
269 }
270
271 - (void)tableView:(NSTableView*)tableView
272 willDisplayCell:(id)cell
273 forTableColumn:(NSTableColumn*)tableColumn
274 row:(NSInteger)rowIndex {
275 if (rowIndex == [tableView selectedRow])
276 [cell setBackgroundStyle:kBackgroundSelected];
277 else if (rowIndex == hoveredRowIndex_)
278 [cell setBackgroundStyle:kBackgroundHovered];
279 else
280 [cell setBackgroundStyle:kBackgroundNormal];
281 }
282
283 - (void)mouseExited:(NSEvent*)theEvent {
284 if (hoveredRowIndex_ == -1)
285 return;
286
287 [tableView_ setNeedsDisplayInRect:[tableView_ rectOfRow:hoveredRowIndex_]];
288 hoveredRowIndex_ = -1;
289 }
290
291 - (void)mouseMoved:(NSEvent*)theEvent {
292 NSPoint pointInView = [tableView_ convertPoint:[theEvent locationInWindow]
293 fromView:nil];
294 NSInteger newIndex = [tableView_ rowAtPoint:pointInView];
295 if (newIndex == hoveredRowIndex_)
296 return;
297
298 if (newIndex != -1)
299 [tableView_ setNeedsDisplayInRect:[tableView_ rectOfRow:newIndex]];
300 if (hoveredRowIndex_ != -1)
301 [tableView_ setNeedsDisplayInRect:[tableView_ rectOfRow:hoveredRowIndex_]];
302 hoveredRowIndex_ = newIndex;
303 }
304
305 @end
306
307 @implementation AppsSearchResultRep
308
309 - (NSAttributedString*)attributedStringValue {
310 return attributedStringValue_;
311 }
312
313 - (NSImage*)resultIcon {
314 return resultIcon_;
315 }
316
317 - (id)initWithSearchResult:(app_list::SearchResult*)result {
318 if ((self = [super init])) {
319 attributedStringValue_.reset(
320 [[self createResultsAttributedStringWithModel:result] retain]);
321 if (!result->icon().isNull()) {
322 resultIcon_.reset([gfx::NSImageFromImageSkiaWithColorSpace(
323 result->icon(), base::mac::GetSRGBColorSpace()) retain]);
324 }
325 }
326 return self;
327 }
328
329 - (NSMutableAttributedString*)createRenderText:(const base::string16&)content
330 tags:(const app_list::SearchResult::Tags&)tags {
331 NSFont* boldFont = nil;
332 base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
333 [[NSMutableParagraphStyle alloc] init]);
334 [paragraphStyle setLineBreakMode:NSLineBreakByTruncatingTail];
335 NSDictionary* defaultAttributes = @{
336 NSForegroundColorAttributeName :
337 skia::SkColorToSRGBNSColor(app_list::kResultDefaultTextColor),
338 NSParagraphStyleAttributeName : paragraphStyle
339 };
340
341 base::scoped_nsobject<NSMutableAttributedString> text(
342 [[NSMutableAttributedString alloc]
343 initWithString:base::SysUTF16ToNSString(content)
344 attributes:defaultAttributes]);
345
346 for (app_list::SearchResult::Tags::const_iterator it = tags.begin();
347 it != tags.end(); ++it) {
348 if (it->styles == app_list::SearchResult::Tag::NONE)
349 continue;
350
351 if (it->styles & app_list::SearchResult::Tag::MATCH) {
352 if (!boldFont) {
353 NSFontManager* fontManager = [NSFontManager sharedFontManager];
354 boldFont = [fontManager convertFont:[NSFont controlContentFontOfSize:0]
355 toHaveTrait:NSBoldFontMask];
356 }
357 [text addAttribute:NSFontAttributeName
358 value:boldFont
359 range:it->range.ToNSRange()];
360 }
361
362 if (it->styles & app_list::SearchResult::Tag::DIM) {
363 NSColor* dimmedColor =
364 skia::SkColorToSRGBNSColor(app_list::kResultDimmedTextColor);
365 [text addAttribute:NSForegroundColorAttributeName
366 value:dimmedColor
367 range:it->range.ToNSRange()];
368 } else if (it->styles & app_list::SearchResult::Tag::URL) {
369 NSColor* urlColor =
370 skia::SkColorToSRGBNSColor(app_list::kResultURLTextColor);
371 [text addAttribute:NSForegroundColorAttributeName
372 value:urlColor
373 range:it->range.ToNSRange()];
374 }
375 }
376
377 return text.autorelease();
378 }
379
380 - (NSAttributedString*)createResultsAttributedStringWithModel
381 :(app_list::SearchResult*)result {
382 NSMutableAttributedString* titleText =
383 [self createRenderText:result->title()
384 tags:result->title_tags()];
385 if (!result->details().empty()) {
386 NSMutableAttributedString* detailText =
387 [self createRenderText:result->details()
388 tags:result->details_tags()];
389 base::scoped_nsobject<NSAttributedString> lineBreak(
390 [[NSAttributedString alloc] initWithString:@"\n"]);
391 [titleText appendAttributedString:lineBreak];
392 [titleText appendAttributedString:detailText];
393 }
394 return titleText;
395 }
396
397 - (id)copyWithZone:(NSZone*)zone {
398 return [self retain];
399 }
400
401 @end
402
403 @implementation AppsSearchResultsTableView
404
405 - (AppsSearchResultsController*)controller {
406 return base::mac::ObjCCastStrict<AppsSearchResultsController>(
407 [self delegate]);
408 }
409
410 - (void)mouseDown:(NSEvent*)theEvent {
411 [[self controller] mouseDown:theEvent];
412 [super mouseDown:theEvent];
413 }
414
415 - (NSMenu*)menuForEvent:(NSEvent*)theEvent {
416 NSPoint pointInView = [self convertPoint:[theEvent locationInWindow]
417 fromView:nil];
418 return [[self controller] contextMenuForRow:[self rowAtPoint:pointInView]];
419 }
420
421 @end
422
423 @implementation AppsSearchResultsCell
424
425 - (void)drawWithFrame:(NSRect)cellFrame
426 inView:(NSView*)controlView {
427 if ([self backgroundStyle] != kBackgroundNormal) {
428 if ([self backgroundStyle] == kBackgroundSelected)
429 [skia::SkColorToSRGBNSColor(kSelectedRowColor) set];
430 else
431 [skia::SkColorToSRGBNSColor(kHighlightedRowColor) set];
432
433 // Extend up by one pixel to draw over cell border.
434 NSRect backgroundRect = cellFrame;
435 backgroundRect.origin.y -= 1;
436 backgroundRect.size.height += 1;
437 NSRectFill(backgroundRect);
438 }
439
440 NSAttributedString* titleText = [self attributedStringValue];
441 NSRect titleRect = cellFrame;
442 titleRect.size.width -= kTextTrailPadding + kIconViewWidth;
443 titleRect.origin.x += kIconViewWidth;
444 titleRect.origin.y +=
445 floor(NSHeight(cellFrame) / 2 - [titleText size].height / 2);
446 // Ensure no drawing occurs outside of the cell.
447 titleRect = NSIntersectionRect(titleRect, cellFrame);
448
449 [titleText drawInRect:titleRect];
450
451 NSImage* resultIcon = [[self objectValue] resultIcon];
452 if (!resultIcon)
453 return;
454
455 NSSize iconSize = [resultIcon size];
456 NSRect iconRect = NSMakeRect(
457 floor(NSMinX(cellFrame) + kIconViewWidth / 2 - iconSize.width / 2),
458 floor(NSMinY(cellFrame) + kPreferredRowHeight / 2 - iconSize.height / 2),
459 std::min(iconSize.width, kIconDimension),
460 std::min(iconSize.height, kIconDimension));
461 [resultIcon drawInRect:iconRect
462 fromRect:NSZeroRect
463 operation:NSCompositeSourceOver
464 fraction:1.0
465 respectFlipped:YES
466 hints:nil];
467 }
468
469 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698