| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "chrome/browser/ui/cocoa/omnibox/omnibox_popup_matrix.h" |
| |
| #include "base/logging.h" |
| #import "chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h" |
| |
| namespace { |
| |
| // NSEvent -buttonNumber for middle mouse button. |
| const NSInteger kMiddleButtonNumber = 2; |
| |
| } // namespace |
| |
| @interface OmniboxPopupMatrix() |
| - (void)resetTrackingArea; |
| - (void)highlightRowAt:(NSInteger)rowIndex; |
| - (void)highlightRowUnder:(NSEvent*)theEvent; |
| - (BOOL)selectCellForEvent:(NSEvent*)theEvent; |
| @end |
| |
| @implementation OmniboxPopupMatrix |
| |
| - (id)initWithDelegate:(OmniboxPopupMatrixDelegate*)delegate { |
| if ((self = [super initWithFrame:NSZeroRect])) { |
| delegate_ = delegate; |
| [self setCellClass:[OmniboxPopupCell class]]; |
| |
| // Cells pack with no spacing. |
| [self setIntercellSpacing:NSMakeSize(0.0, 0.0)]; |
| |
| [self setDrawsBackground:YES]; |
| [self setBackgroundColor:[NSColor controlBackgroundColor]]; |
| [self renewRows:0 columns:1]; |
| [self setAllowsEmptySelection:YES]; |
| [self setMode:NSRadioModeMatrix]; |
| [self deselectAllCells]; |
| |
| [self resetTrackingArea]; |
| } |
| return self; |
| } |
| |
| - (void)setDelegate:(OmniboxPopupMatrixDelegate*)delegate { |
| delegate_ = delegate; |
| } |
| |
| - (NSInteger)highlightedRow { |
| NSArray* cells = [self cells]; |
| const NSUInteger count = [cells count]; |
| for(NSUInteger i = 0; i < count; ++i) { |
| if ([[cells objectAtIndex:i] isHighlighted]) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| - (void)updateTrackingAreas { |
| [self resetTrackingArea]; |
| [super updateTrackingAreas]; |
| } |
| |
| // Callbacks from tracking area. |
| - (void)mouseEntered:(NSEvent*)theEvent { |
| [self highlightRowUnder:theEvent]; |
| } |
| |
| - (void)mouseMoved:(NSEvent*)theEvent { |
| [self highlightRowUnder:theEvent]; |
| } |
| |
| - (void)mouseExited:(NSEvent*)theEvent { |
| [self highlightRowAt:-1]; |
| } |
| |
| // The tracking area events aren't forwarded during a drag, so handle |
| // highlighting manually for middle-click and middle-drag. |
| - (void)otherMouseDown:(NSEvent*)theEvent { |
| if ([theEvent buttonNumber] == kMiddleButtonNumber) { |
| [self highlightRowUnder:theEvent]; |
| } |
| [super otherMouseDown:theEvent]; |
| } |
| |
| - (void)otherMouseDragged:(NSEvent*)theEvent { |
| if ([theEvent buttonNumber] == kMiddleButtonNumber) { |
| [self highlightRowUnder:theEvent]; |
| } |
| [super otherMouseDragged:theEvent]; |
| } |
| |
| - (void)otherMouseUp:(NSEvent*)theEvent { |
| // Only intercept middle button. |
| if ([theEvent buttonNumber] != kMiddleButtonNumber) { |
| [super otherMouseUp:theEvent]; |
| return; |
| } |
| |
| // -otherMouseDragged: should always have been called at this location, but |
| // make sure the user is getting the right feedback. |
| [self highlightRowUnder:theEvent]; |
| |
| const NSInteger highlightedRow = [self highlightedRow]; |
| if (highlightedRow != -1) { |
| DCHECK(delegate_); |
| delegate_->OnMatrixRowMiddleClicked(self, highlightedRow); |
| } |
| } |
| |
| // Track the mouse until released, keeping the cell under the mouse selected. |
| // If the mouse wanders off-view, revert to the originally-selected cell. If |
| // the mouse is released over a cell, call the delegate to open the row's URL. |
| - (void)mouseDown:(NSEvent*)theEvent { |
| NSCell* selectedCell = [self selectedCell]; |
| |
| // Clear any existing highlight. |
| [self highlightRowAt:-1]; |
| |
| do { |
| if (![self selectCellForEvent:theEvent]) { |
| [self selectCell:selectedCell]; |
| } |
| |
| const NSUInteger mask = NSLeftMouseUpMask | NSLeftMouseDraggedMask; |
| theEvent = [[self window] nextEventMatchingMask:mask]; |
| } while ([theEvent type] == NSLeftMouseDragged); |
| |
| // Do not message the delegate if released outside view. |
| if (![self selectCellForEvent:theEvent]) { |
| [self selectCell:selectedCell]; |
| } else { |
| const NSInteger selectedRow = [self selectedRow]; |
| DCHECK_GE(selectedRow, 0); |
| |
| DCHECK(delegate_); |
| delegate_->OnMatrixRowClicked(self, selectedRow); |
| } |
| } |
| |
| - (void)resetTrackingArea { |
| if (trackingArea_.get()) |
| [self removeTrackingArea:trackingArea_.get()]; |
| |
| trackingArea_.reset([[CrTrackingArea alloc] |
| initWithRect:[self frame] |
| options:NSTrackingMouseEnteredAndExited | |
| NSTrackingMouseMoved | |
| NSTrackingActiveInActiveApp | |
| NSTrackingInVisibleRect |
| owner:self |
| userInfo:nil]); |
| [self addTrackingArea:trackingArea_.get()]; |
| } |
| |
| - (void)highlightRowAt:(NSInteger)rowIndex { |
| // highlightCell will be nil if rowIndex is out of range, so no cell will be |
| // highlighted. |
| NSCell* highlightCell = [self cellAtRow:rowIndex column:0]; |
| |
| for (NSCell* cell in [self cells]) { |
| [cell setHighlighted:(cell == highlightCell)]; |
| } |
| } |
| |
| - (void)highlightRowUnder:(NSEvent*)theEvent { |
| NSPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; |
| NSInteger row, column; |
| if ([self getRow:&row column:&column forPoint:point]) { |
| [self highlightRowAt:row]; |
| } else { |
| [self highlightRowAt:-1]; |
| } |
| } |
| |
| // Select cell under |theEvent|, returning YES if a selection is made. |
| - (BOOL)selectCellForEvent:(NSEvent*)theEvent { |
| NSPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; |
| |
| NSInteger row, column; |
| if ([self getRow:&row column:&column forPoint:point]) { |
| DCHECK_EQ(column, 0); |
| DCHECK(delegate_); |
| delegate_->OnMatrixRowSelected(self, row); |
| return YES; |
| } |
| return NO; |
| } |
| |
| @end |