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
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]);
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 {
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);
91 setState:([tableView selectedRow] == rowIndex) ? NSOnState : NSOffState];
92 [popupCell highlight:(hoveredIndex_ == rowIndex)
93 withFrame:[tableView bounds]
97 - (NSInteger)highlightedRow {
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];
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]);
145 [layoutManager defaultLineHeightForFont:OmniboxViewMac::GetLargeFont(
151 - (OmniboxPopupTableController*)controller {
152 return base::mac::ObjCCastStrict<OmniboxPopupTableController>(
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];
184 [super otherMouseDown:theEvent];
187 - (void)otherMouseDragged:(NSEvent*)theEvent {
188 if ([theEvent buttonNumber] == kMiddleButtonNumber) {
189 [self highlightRowUnder:theEvent];
191 [super otherMouseDragged:theEvent];
194 - (void)otherMouseUp:(NSEvent*)theEvent {
195 // Only intercept middle button.
196 if ([theEvent buttonNumber] != kMiddleButtonNumber) {
197 [super otherMouseUp:theEvent];
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) {
208 observer_->OnMatrixRowMiddleClicked(self, highlightedRow);
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];
222 if (![self selectCellForEvent:theEvent]) {
223 [self selectCell:selectedCell];
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];
234 const NSInteger selectedRow = [self selectedRow];
236 // No row could be selected if the model failed to update.
237 if (selectedRow == -1) {
243 observer_->OnMatrixRowClicked(self, selectedRow);
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];
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
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]];
289 - (BOOL)selectCellForEvent:(NSEvent*)theEvent {
290 NSPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil];
292 NSInteger row = [self rowAtPoint:point];
293 [self selectRowIndex:row];
296 observer_->OnMatrixRowSelected(self, row);