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"
16 // NSEvent -buttonNumber for middle mouse button.
17 const NSInteger kMiddleButtonNumber = 2;
21 @interface OmniboxPopupMatrix ()
22 - (OmniboxPopupTableController*)controller;
23 - (void)resetTrackingArea;
24 - (void)highlightRowUnder:(NSEvent*)theEvent;
25 - (BOOL)selectCellForEvent:(NSEvent*)theEvent;
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]
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]);
54 [tableView setMaxMatchContentsWidth:max_match_contents_width];
55 return [self initWithArray:array];
58 - (instancetype)initWithArray:(NSArray*)array {
59 if ((self = [super init])) {
61 array_.reset([array copy]);
66 - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView {
67 return [array_ count];
70 - (id)tableView:(NSTableView*)tableView
71 objectValueForTableColumn:(NSTableColumn*)tableColumn
72 row:(NSInteger)rowIndex {
73 return [array_ objectAtIndex:rowIndex];
76 - (void)tableView:(NSTableView*)tableView
77 setObjectValue:(id)object
78 forTableColumn:(NSTableColumn*)tableColumn
79 row:(NSInteger)rowIndex {
83 - (void)tableView:(NSTableView*)tableView
84 willDisplayCell:(id)cell
85 forTableColumn:(NSTableColumn*)tableColumn
86 row:(NSInteger)rowIndex {
87 OmniboxPopupCell* popupCell =
88 base::mac::ObjCCastStrict<OmniboxPopupCell>(cell);
90 setState:([tableView selectedRow] == rowIndex) ? NSOnState : NSOffState];
91 [popupCell highlight:(hoveredIndex_ == rowIndex)
92 withFrame:[tableView bounds]
96 - (NSInteger)highlightedRow {
100 - (void)setHighlightedRow:(NSInteger)rowIndex {
101 hoveredIndex_ = rowIndex;
104 - (CGFloat)tableView:(NSTableView*)tableView heightOfRow:(NSInteger)row {
105 CGFloat height = kContentLineHeight;
106 if ([[array_ objectAtIndex:row] isAnswer]) {
107 OmniboxPopupMatrix* matrix =
108 base::mac::ObjCCastStrict<OmniboxPopupMatrix>(tableView);
109 height += [matrix answerLineHeight];
116 @implementation OmniboxPopupMatrix
118 @synthesize separator = separator_;
119 @synthesize maxMatchContentsWidth = maxMatchContentsWidth_;
120 @synthesize answerLineHeight = answerLineHeight_;
122 - (instancetype)initWithObserver:(OmniboxPopupMatrixObserver*)observer {
123 if ((self = [super initWithFrame:NSZeroRect])) {
124 observer_ = observer;
126 base::scoped_nsobject<NSTableColumn> column(
127 [[NSTableColumn alloc] initWithIdentifier:@"MainColumn"]);
128 [column setDataCell:[[[OmniboxPopupCell alloc] init] autorelease]];
129 [self addTableColumn:column];
131 // Cells pack with no spacing.
132 [self setIntercellSpacing:NSMakeSize(0.0, 0.0)];
134 [self setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleNone];
135 [self setBackgroundColor:[NSColor controlBackgroundColor]];
136 [self setAllowsEmptySelection:YES];
137 [self deselectAll:self];
139 [self resetTrackingArea];
141 base::scoped_nsobject<NSLayoutManager> layoutManager(
142 [[NSLayoutManager alloc] init]);
144 [layoutManager defaultLineHeightForFont:OmniboxViewMac::GetLargeFont(
150 - (OmniboxPopupTableController*)controller {
151 return base::mac::ObjCCastStrict<OmniboxPopupTableController>(
155 - (void)setObserver:(OmniboxPopupMatrixObserver*)observer {
156 observer_ = observer;
159 - (void)updateTrackingAreas {
160 [self resetTrackingArea];
161 [super updateTrackingAreas];
164 // Callbacks from tracking area.
165 - (void)mouseMoved:(NSEvent*)theEvent {
166 [self highlightRowUnder:theEvent];
169 - (void)mouseExited:(NSEvent*)theEvent {
170 [self highlightRowUnder:theEvent];
173 // The tracking area events aren't forwarded during a drag, so handle
174 // highlighting manually for middle-click and middle-drag.
175 - (void)otherMouseDown:(NSEvent*)theEvent {
176 if ([theEvent buttonNumber] == kMiddleButtonNumber) {
177 [self highlightRowUnder:theEvent];
179 [super otherMouseDown:theEvent];
182 - (void)otherMouseDragged:(NSEvent*)theEvent {
183 if ([theEvent buttonNumber] == kMiddleButtonNumber) {
184 [self highlightRowUnder:theEvent];
186 [super otherMouseDragged:theEvent];
189 - (void)otherMouseUp:(NSEvent*)theEvent {
190 // Only intercept middle button.
191 if ([theEvent buttonNumber] != kMiddleButtonNumber) {
192 [super otherMouseUp:theEvent];
196 // -otherMouseDragged: should always have been called at this location, but
197 // make sure the user is getting the right feedback.
198 [self highlightRowUnder:theEvent];
200 const NSInteger highlightedRow = [[self controller] highlightedRow];
201 if (highlightedRow != -1) {
203 observer_->OnMatrixRowMiddleClicked(self, highlightedRow);
207 // Track the mouse until released, keeping the cell under the mouse selected.
208 // If the mouse wanders off-view, revert to the originally-selected cell. If
209 // the mouse is released over a cell, call the delegate to open the row's URL.
210 - (void)mouseDown:(NSEvent*)theEvent {
211 NSCell* selectedCell = [self selectedCell];
213 // Clear any existing highlight.
214 [[self controller] setHighlightedRow:-1];
217 if (![self selectCellForEvent:theEvent]) {
218 [self selectCell:selectedCell];
221 const NSUInteger mask = NSLeftMouseUpMask | NSLeftMouseDraggedMask;
222 theEvent = [[self window] nextEventMatchingMask:mask];
223 } while ([theEvent type] == NSLeftMouseDragged);
225 // Do not message the delegate if released outside view.
226 if (![self selectCellForEvent:theEvent]) {
227 [self selectCell:selectedCell];
229 const NSInteger selectedRow = [self selectedRow];
231 // No row could be selected if the model failed to update.
232 if (selectedRow == -1) {
238 observer_->OnMatrixRowClicked(self, selectedRow);
242 - (void)selectRowIndex:(NSInteger)rowIndex {
243 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:rowIndex];
244 [self selectRowIndexes:indexSet byExtendingSelection:NO];
247 - (NSInteger)highlightedRow {
248 return [[self controller] highlightedRow];
251 - (void)setController:(OmniboxPopupTableController*)controller {
252 matrixController_.reset([controller retain]);
253 [self setDelegate:controller];
254 [self setDataSource:controller];
258 - (void)resetTrackingArea {
259 if (trackingArea_.get())
260 [self removeTrackingArea:trackingArea_.get()];
262 trackingArea_.reset([[CrTrackingArea alloc]
263 initWithRect:[self frame]
264 options:NSTrackingMouseEnteredAndExited |
265 NSTrackingMouseMoved |
266 NSTrackingActiveInActiveApp |
267 NSTrackingInVisibleRect
270 [self addTrackingArea:trackingArea_.get()];
273 - (void)highlightRowUnder:(NSEvent*)theEvent {
274 NSPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil];
275 NSInteger oldRow = [[self controller] highlightedRow];
276 NSInteger newRow = [self rowAtPoint:point];
277 if (oldRow != newRow) {
278 [[self controller] setHighlightedRow:newRow];
279 [self setNeedsDisplayInRect:[self rectOfRow:oldRow]];
280 [self setNeedsDisplayInRect:[self rectOfRow:newRow]];
284 - (BOOL)selectCellForEvent:(NSEvent*)theEvent {
285 NSPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil];
287 NSInteger row = [self rowAtPoint:point];
288 [self selectRowIndex:row];
291 observer_->OnMatrixRowSelected(self, row);