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/autocomplete_result.h"
16 // NSEvent -buttonNumber for middle mouse button.
17 const NSInteger kMiddleButtonNumber = 2;
21 @interface OmniboxPopupTableController ()
22 - (instancetype)initWithArray:(NSArray*)array;
25 @implementation OmniboxPopupTableController
27 - (instancetype)initWithMatchResults:(const AutocompleteResult&)result
28 tableView:(OmniboxPopupMatrix*)tableView
29 popupView:(const OmniboxPopupViewMac&)popupView
30 answerImage:(NSImage*)answerImage {
31 base::scoped_nsobject<NSMutableArray> array([[NSMutableArray alloc] init]);
32 CGFloat max_match_contents_width = 0.0f;
33 CGFloat contentsOffset = -1.0f;
34 for (const AutocompleteMatch& match : result) {
35 if (match.type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL &&
36 contentsOffset < 0.0f)
37 contentsOffset = [OmniboxPopupCell computeContentsOffset:match];
38 base::scoped_nsobject<OmniboxPopupCellData> cellData(
39 [[OmniboxPopupCellData alloc]
41 contentsOffset:contentsOffset
42 image:popupView.ImageForMatch(match)
43 answerImage:(match.answer ? answerImage : nil)]);
44 [array addObject:cellData];
45 if (match.type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) {
46 max_match_contents_width =
47 std::max(max_match_contents_width, [cellData getMatchContentsWidth]);
51 [tableView setMaxMatchContentsWidth:max_match_contents_width];
52 return [self initWithArray:array];
55 - (instancetype)initWithArray:(NSArray*)array {
56 if ((self = [super init])) {
58 array_.reset([array copy]);
63 - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView {
64 return [array_ count];
67 - (id)tableView:(NSTableView*)tableView
68 objectValueForTableColumn:(NSTableColumn*)tableColumn
69 row:(NSInteger)rowIndex {
70 return [array_ objectAtIndex:rowIndex];
73 - (void)tableView:(NSTableView*)tableView
74 setObjectValue:(id)object
75 forTableColumn:(NSTableColumn*)tableColumn
76 row:(NSInteger)rowIndex {
80 - (void)tableView:(NSTableView*)tableView
81 willDisplayCell:(id)cell
82 forTableColumn:(NSTableColumn*)tableColumn
83 row:(NSInteger)rowIndex {
84 OmniboxPopupCell* popupCell =
85 base::mac::ObjCCastStrict<OmniboxPopupCell>(cell);
87 setState:([tableView selectedRow] == rowIndex) ? NSOnState : NSOffState];
88 [popupCell highlight:(hoveredIndex_ == rowIndex)
89 withFrame:[tableView bounds]
93 - (NSInteger)highlightedRow {
97 - (void)setHighlightedRow:(NSInteger)rowIndex {
98 hoveredIndex_ = rowIndex;
101 - (CGFloat)tableView:(NSTableView*)tableView heightOfRow:(NSInteger)row {
102 CGFloat height = kContentLineHeight;
103 if ([[array_ objectAtIndex:row] isAnswer]) {
104 OmniboxPopupMatrix* matrix =
105 base::mac::ObjCCastStrict<OmniboxPopupMatrix>(tableView);
106 height += [matrix answerLineHeight];
113 @interface OmniboxPopupMatrix ()
114 - (OmniboxPopupTableController*)controller;
115 - (void)resetTrackingArea;
116 - (void)highlightRowUnder:(NSEvent*)theEvent;
117 - (BOOL)selectCellForEvent:(NSEvent*)theEvent;
120 @implementation OmniboxPopupMatrix
122 @synthesize separator = separator_;
123 @synthesize maxMatchContentsWidth = maxMatchContentsWidth_;
124 @synthesize answerLineHeight = answerLineHeight_;
126 - (instancetype)initWithObserver:(OmniboxPopupMatrixObserver*)observer {
127 if ((self = [super initWithFrame:NSZeroRect])) {
128 observer_ = observer;
130 base::scoped_nsobject<NSTableColumn> column(
131 [[NSTableColumn alloc] initWithIdentifier:@"MainColumn"]);
132 [column setDataCell:[[[OmniboxPopupCell alloc] init] autorelease]];
133 [self addTableColumn:column];
135 // Cells pack with no spacing.
136 [self setIntercellSpacing:NSMakeSize(0.0, 0.0)];
138 [self setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleNone];
139 [self setBackgroundColor:[NSColor controlBackgroundColor]];
140 [self setAllowsEmptySelection:YES];
141 [self deselectAll:self];
143 [self resetTrackingArea];
145 base::scoped_nsobject<NSLayoutManager> layoutManager(
146 [[NSLayoutManager alloc] init]);
148 [layoutManager defaultLineHeightForFont:OmniboxViewMac::GetLargeFont(
154 - (OmniboxPopupTableController*)controller {
155 return base::mac::ObjCCastStrict<OmniboxPopupTableController>(
159 - (void)setObserver:(OmniboxPopupMatrixObserver*)observer {
160 observer_ = observer;
163 - (void)updateTrackingAreas {
164 [self resetTrackingArea];
165 [super updateTrackingAreas];
168 // Callbacks from tracking area.
169 - (void)mouseEntered:(NSEvent*)theEvent {
170 [self highlightRowUnder:theEvent];
173 - (void)mouseMoved:(NSEvent*)theEvent {
174 [self highlightRowUnder:theEvent];
177 - (void)mouseExited:(NSEvent*)theEvent {
178 [[self controller] setHighlightedRow:-1];
181 // The tracking area events aren't forwarded during a drag, so handle
182 // highlighting manually for middle-click and middle-drag.
183 - (void)otherMouseDown:(NSEvent*)theEvent {
184 if ([theEvent buttonNumber] == kMiddleButtonNumber) {
185 [self highlightRowUnder:theEvent];
187 [super otherMouseDown:theEvent];
190 - (void)otherMouseDragged:(NSEvent*)theEvent {
191 if ([theEvent buttonNumber] == kMiddleButtonNumber) {
192 [self highlightRowUnder:theEvent];
194 [super otherMouseDragged:theEvent];
197 - (void)otherMouseUp:(NSEvent*)theEvent {
198 // Only intercept middle button.
199 if ([theEvent buttonNumber] != kMiddleButtonNumber) {
200 [super otherMouseUp:theEvent];
204 // -otherMouseDragged: should always have been called at this location, but
205 // make sure the user is getting the right feedback.
206 [self highlightRowUnder:theEvent];
208 const NSInteger highlightedRow = [[self controller] highlightedRow];
209 if (highlightedRow != -1) {
211 observer_->OnMatrixRowMiddleClicked(self, highlightedRow);
215 // Track the mouse until released, keeping the cell under the mouse selected.
216 // If the mouse wanders off-view, revert to the originally-selected cell. If
217 // the mouse is released over a cell, call the delegate to open the row's URL.
218 - (void)mouseDown:(NSEvent*)theEvent {
219 NSCell* selectedCell = [self selectedCell];
221 // Clear any existing highlight.
222 [[self controller] setHighlightedRow:-1];
225 if (![self selectCellForEvent:theEvent]) {
226 [self selectCell:selectedCell];
229 const NSUInteger mask = NSLeftMouseUpMask | NSLeftMouseDraggedMask;
230 theEvent = [[self window] nextEventMatchingMask:mask];
231 } while ([theEvent type] == NSLeftMouseDragged);
233 // Do not message the delegate if released outside view.
234 if (![self selectCellForEvent:theEvent]) {
235 [self selectCell:selectedCell];
237 const NSInteger selectedRow = [self selectedRow];
239 // No row could be selected if the model failed to update.
240 if (selectedRow == -1) {
246 observer_->OnMatrixRowClicked(self, selectedRow);
250 - (void)selectRowIndex:(NSInteger)rowIndex {
251 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:rowIndex];
252 [self selectRowIndexes:indexSet byExtendingSelection:NO];
255 - (NSInteger)highlightedRow {
256 return [[self controller] highlightedRow];
259 - (void)setController:(OmniboxPopupTableController*)controller {
260 matrixController_.reset([controller retain]);
261 [self setDelegate:controller];
262 [self setDataSource:controller];
266 - (void)resetTrackingArea {
267 if (trackingArea_.get())
268 [self removeTrackingArea:trackingArea_.get()];
270 trackingArea_.reset([[CrTrackingArea alloc]
271 initWithRect:[self frame]
272 options:NSTrackingMouseEnteredAndExited |
273 NSTrackingMouseMoved |
274 NSTrackingActiveInActiveApp |
275 NSTrackingInVisibleRect
278 [self addTrackingArea:trackingArea_.get()];
281 - (void)highlightRowUnder:(NSEvent*)theEvent {
282 NSPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil];
283 NSInteger oldRow = [[self controller] highlightedRow];
284 NSInteger newRow = [self rowAtPoint:point];
285 if (oldRow != newRow) {
286 [[self controller] setHighlightedRow:newRow];
287 [self setNeedsDisplayInRect:[self rectOfRow:oldRow]];
288 [self setNeedsDisplayInRect:[self rectOfRow:newRow]];
292 - (BOOL)selectCellForEvent:(NSEvent*)theEvent {
293 NSPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil];
295 NSInteger row = [self rowAtPoint:point];
296 [self selectRowIndex:row];
299 observer_->OnMatrixRowSelected(self, row);