Add ICU message format support
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / omnibox / omnibox_popup_matrix.mm
blobfe30f61809c135dd022c3bd6b195942eeef7efda
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 "chrome/browser/ui/cocoa/omnibox/omnibox_popup_matrix.h"
7 #include "base/logging.h"
8 #include "base/mac/foundation_util.h"
9 #import "chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h"
10 #include "chrome/browser/ui/cocoa/omnibox/omnibox_popup_view_mac.h"
11 #include "chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.h"
12 #include "components/omnibox/browser/autocomplete_result.h"
14 namespace {
16 // NSEvent -buttonNumber for middle mouse button.
17 const NSInteger kMiddleButtonNumber = 2;
19 }  // namespace
21 @interface OmniboxPopupMatrix ()
22 - (OmniboxPopupTableController*)controller;
23 - (void)resetTrackingArea;
24 - (void)highlightRowUnder:(NSEvent*)theEvent;
25 - (BOOL)selectCellForEvent:(NSEvent*)theEvent;
26 @end
28 @implementation OmniboxPopupTableController
30 - (instancetype)initWithMatchResults:(const AutocompleteResult&)result
31                            tableView:(OmniboxPopupMatrix*)tableView
32                            popupView:(const OmniboxPopupViewMac&)popupView
33                          answerImage:(NSImage*)answerImage {
34   base::scoped_nsobject<NSMutableArray> array([[NSMutableArray alloc] init]);
35   CGFloat max_match_contents_width = 0.0f;
36   CGFloat contentsOffset = -1.0f;
37   for (const AutocompleteMatch& match : result) {
38     if (match.type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL &&
39         contentsOffset < 0.0f)
40       contentsOffset = [OmniboxPopupCell computeContentsOffset:match];
41     base::scoped_nsobject<OmniboxPopupCellData> cellData(
42         [[OmniboxPopupCellData alloc]
43              initWithMatch:match
44             contentsOffset:contentsOffset
45                      image:popupView.ImageForMatch(match)
46                answerImage:(match.answer ? answerImage : nil)]);
47     [array addObject:cellData];
48     if (match.type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) {
49       max_match_contents_width =
50           std::max(max_match_contents_width, [cellData getMatchContentsWidth]);
51     }
52   }
54   [tableView setMaxMatchContentsWidth:max_match_contents_width];
55   return [self initWithArray:array
56                      hovered:[[tableView controller] highlightedRow]];
59 - (instancetype)initWithArray:(NSArray*)array hovered:(NSInteger)hoveredIndex {
60   if ((self = [super init])) {
61     hoveredIndex_ = hoveredIndex;
62     array_.reset([array copy]);
63   }
64   return self;
67 - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView {
68   return [array_ count];
71 - (id)tableView:(NSTableView*)tableView
72     objectValueForTableColumn:(NSTableColumn*)tableColumn
73                           row:(NSInteger)rowIndex {
74   return [array_ objectAtIndex:rowIndex];
77 - (void)tableView:(NSTableView*)tableView
78     setObjectValue:(id)object
79     forTableColumn:(NSTableColumn*)tableColumn
80                row:(NSInteger)rowIndex {
81   NOTREACHED();
84 - (void)tableView:(NSTableView*)tableView
85     willDisplayCell:(id)cell
86      forTableColumn:(NSTableColumn*)tableColumn
87                 row:(NSInteger)rowIndex {
88   OmniboxPopupCell* popupCell =
89       base::mac::ObjCCastStrict<OmniboxPopupCell>(cell);
90   [popupCell
91       setState:([tableView selectedRow] == rowIndex) ? NSOnState : NSOffState];
92   [popupCell highlight:(hoveredIndex_ == rowIndex)
93              withFrame:[tableView bounds]
94                 inView:tableView];
97 - (NSInteger)highlightedRow {
98   return hoveredIndex_;
101 - (void)setHighlightedRow:(NSInteger)rowIndex {
102   hoveredIndex_ = rowIndex;
105 - (CGFloat)tableView:(NSTableView*)tableView heightOfRow:(NSInteger)row {
106   CGFloat height = kContentLineHeight;
107   if ([[array_ objectAtIndex:row] isAnswer]) {
108     OmniboxPopupMatrix* matrix =
109         base::mac::ObjCCastStrict<OmniboxPopupMatrix>(tableView);
110     height += [matrix answerLineHeight];
111   }
112   return height;
115 @end
117 @implementation OmniboxPopupMatrix
119 @synthesize separator = separator_;
120 @synthesize maxMatchContentsWidth = maxMatchContentsWidth_;
121 @synthesize answerLineHeight = answerLineHeight_;
123 - (instancetype)initWithObserver:(OmniboxPopupMatrixObserver*)observer {
124   if ((self = [super initWithFrame:NSZeroRect])) {
125     observer_ = observer;
127     base::scoped_nsobject<NSTableColumn> column(
128         [[NSTableColumn alloc] initWithIdentifier:@"MainColumn"]);
129     [column setDataCell:[[[OmniboxPopupCell alloc] init] autorelease]];
130     [self addTableColumn:column];
132     // Cells pack with no spacing.
133     [self setIntercellSpacing:NSMakeSize(0.0, 0.0)];
135     [self setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleNone];
136     [self setBackgroundColor:[NSColor controlBackgroundColor]];
137     [self setAllowsEmptySelection:YES];
138     [self deselectAll:self];
140     [self resetTrackingArea];
142     base::scoped_nsobject<NSLayoutManager> layoutManager(
143         [[NSLayoutManager alloc] init]);
144     answerLineHeight_ =
145         [layoutManager defaultLineHeightForFont:OmniboxViewMac::GetLargeFont(
146                                                     gfx::Font::NORMAL)];
147   }
148   return self;
151 - (OmniboxPopupTableController*)controller {
152   return base::mac::ObjCCastStrict<OmniboxPopupTableController>(
153       [self delegate]);
156 - (void)setObserver:(OmniboxPopupMatrixObserver*)observer {
157   observer_ = observer;
160 - (void)updateTrackingAreas {
161   [self resetTrackingArea];
162   [super updateTrackingAreas];
165 // Callbacks from tracking area.
166 - (void)mouseEntered:(NSEvent*)theEvent {
167   [self highlightRowUnder:theEvent];
170 - (void)mouseMoved:(NSEvent*)theEvent {
171   [self highlightRowUnder:theEvent];
174 - (void)mouseExited:(NSEvent*)theEvent {
175   [self highlightRowUnder:theEvent];
178 // The tracking area events aren't forwarded during a drag, so handle
179 // highlighting manually for middle-click and middle-drag.
180 - (void)otherMouseDown:(NSEvent*)theEvent {
181   if ([theEvent buttonNumber] == kMiddleButtonNumber) {
182     [self highlightRowUnder:theEvent];
183   }
184   [super otherMouseDown:theEvent];
187 - (void)otherMouseDragged:(NSEvent*)theEvent {
188   if ([theEvent buttonNumber] == kMiddleButtonNumber) {
189     [self highlightRowUnder:theEvent];
190   }
191   [super otherMouseDragged:theEvent];
194 - (void)otherMouseUp:(NSEvent*)theEvent {
195   // Only intercept middle button.
196   if ([theEvent buttonNumber] != kMiddleButtonNumber) {
197     [super otherMouseUp:theEvent];
198     return;
199   }
201   // -otherMouseDragged: should always have been called at this location, but
202   // make sure the user is getting the right feedback.
203   [self highlightRowUnder:theEvent];
205   const NSInteger highlightedRow = [[self controller] highlightedRow];
206   if (highlightedRow != -1) {
207     DCHECK(observer_);
208     observer_->OnMatrixRowMiddleClicked(self, highlightedRow);
209   }
212 // Track the mouse until released, keeping the cell under the mouse selected.
213 // If the mouse wanders off-view, revert to the originally-selected cell. If
214 // the mouse is released over a cell, call the delegate to open the row's URL.
215 - (void)mouseDown:(NSEvent*)theEvent {
216   NSCell* selectedCell = [self selectedCell];
218   // Clear any existing highlight.
219   [[self controller] setHighlightedRow:-1];
221   do {
222     if (![self selectCellForEvent:theEvent]) {
223       [self selectCell:selectedCell];
224     }
226     const NSUInteger mask = NSLeftMouseUpMask | NSLeftMouseDraggedMask;
227     theEvent = [[self window] nextEventMatchingMask:mask];
228   } while ([theEvent type] == NSLeftMouseDragged);
230   // Do not message the delegate if released outside view.
231   if (![self selectCellForEvent:theEvent]) {
232     [self selectCell:selectedCell];
233   } else {
234     const NSInteger selectedRow = [self selectedRow];
236     // No row could be selected if the model failed to update.
237     if (selectedRow == -1) {
238       NOTREACHED();
239       return;
240     }
242     DCHECK(observer_);
243     observer_->OnMatrixRowClicked(self, selectedRow);
244   }
247 - (void)selectRowIndex:(NSInteger)rowIndex {
248   NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:rowIndex];
249   [self selectRowIndexes:indexSet byExtendingSelection:NO];
252 - (NSInteger)highlightedRow {
253   return [[self controller] highlightedRow];
256 - (void)setController:(OmniboxPopupTableController*)controller {
257   matrixController_.reset([controller retain]);
258   [self setDelegate:controller];
259   [self setDataSource:controller];
260   [self reloadData];
263 - (void)resetTrackingArea {
264   if (trackingArea_.get())
265     [self removeTrackingArea:trackingArea_.get()];
267   trackingArea_.reset([[CrTrackingArea alloc]
268       initWithRect:[self frame]
269            options:NSTrackingMouseEnteredAndExited |
270                    NSTrackingMouseMoved |
271                    NSTrackingActiveInActiveApp |
272                    NSTrackingInVisibleRect
273              owner:self
274           userInfo:nil]);
275   [self addTrackingArea:trackingArea_.get()];
278 - (void)highlightRowUnder:(NSEvent*)theEvent {
279   NSPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil];
280   NSInteger oldRow = [[self controller] highlightedRow];
281   NSInteger newRow = [self rowAtPoint:point];
282   if (oldRow != newRow) {
283     [[self controller] setHighlightedRow:newRow];
284     [self setNeedsDisplayInRect:[self rectOfRow:oldRow]];
285     [self setNeedsDisplayInRect:[self rectOfRow:newRow]];
286   }
289 - (BOOL)selectCellForEvent:(NSEvent*)theEvent {
290   NSPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil];
292   NSInteger row = [self rowAtPoint:point];
293   [self selectRowIndex:row];
294   if (row != -1) {
295     DCHECK(observer_);
296     observer_->OnMatrixRowSelected(self, row);
297     return YES;
298   }
299   return NO;
302 @end