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