[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / omnibox / omnibox_popup_view_mac.mm
blobaccddf91f16334fc7484aa03e98f2c5649ace682
1 // Copyright (c) 2012 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 #include "chrome/browser/ui/cocoa/omnibox/omnibox_popup_view_mac.h"
7 #include <cmath>
9 #include "base/stl_util.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "chrome/browser/autocomplete/autocomplete_match.h"
12 #include "chrome/browser/search/search.h"
13 #include "chrome/browser/ui/cocoa/browser_window_controller.h"
14 #import "chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h"
15 #import "chrome/browser/ui/cocoa/omnibox/omnibox_popup_separator_view.h"
16 #include "chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.h"
17 #include "chrome/browser/ui/omnibox/omnibox_edit_model.h"
18 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
19 #include "chrome/common/autocomplete_match_type.h"
20 #include "grit/theme_resources.h"
21 #include "skia/ext/skia_utils_mac.h"
22 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h"
23 #import "ui/base/cocoa/cocoa_base_utils.h"
24 #import "ui/base/cocoa/flipped_view.h"
25 #include "ui/base/cocoa/window_size_constants.h"
26 #include "ui/base/resource/resource_bundle.h"
27 #include "ui/gfx/rect.h"
28 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
29 #include "ui/gfx/text_elider.h"
31 namespace {
33 // How much to adjust the cell sizing up from the default determined
34 // by the font.
35 const CGFloat kCellHeightAdjust = 6.0;
37 // Padding between matrix and the top and bottom of the popup window.
38 const CGFloat kPopupPaddingVertical = 5.0;
40 // Animation duration when animating the popup window smaller.
41 const NSTimeInterval kShrinkAnimationDuration = 0.1;
43 // Background colors for different states of the popup elements.
44 NSColor* BackgroundColor() {
45   return [NSColor controlBackgroundColor];
48 }  // namespace
50 OmniboxPopupViewMac::OmniboxPopupViewMac(OmniboxView* omnibox_view,
51                                          OmniboxEditModel* edit_model,
52                                          NSTextField* field)
53     : omnibox_view_(omnibox_view),
54       model_(new OmniboxPopupModel(this, edit_model)),
55       field_(field),
56       popup_(nil),
57       target_popup_frame_(NSZeroRect) {
58   DCHECK(omnibox_view);
59   DCHECK(edit_model);
62 OmniboxPopupViewMac::~OmniboxPopupViewMac() {
63   // Destroy the popup model before this object is destroyed, because
64   // it can call back to us in the destructor.
65   model_.reset();
67   // Break references to |this| because the popup may not be
68   // deallocated immediately.
69   [matrix_ setDelegate:NULL];
72 bool OmniboxPopupViewMac::IsOpen() const {
73   return popup_ != nil;
76 void OmniboxPopupViewMac::UpdatePopupAppearance() {
77   DCHECK([NSThread isMainThread]);
78   const AutocompleteResult& result = GetResult();
79   const size_t start_match = result.ShouldHideTopMatch() ? 1 : 0;
80   const size_t rows = result.size() - start_match;
81   if (rows == 0) {
82     [[popup_ parentWindow] removeChildWindow:popup_];
83     [popup_ orderOut:nil];
85     // Break references to |this| because the popup may not be
86     // deallocated immediately.
87     [matrix_ setDelegate:nil];
88     matrix_.reset();
90     popup_.reset(nil);
92     target_popup_frame_ = NSZeroRect;
94     return;
95   }
97   CreatePopupIfNeeded();
99   // Calculate the width of the matrix based on backing out the popup's border
100   // from the width of the field.
101   const CGFloat matrix_width = NSWidth([field_ bounds]);
102   DCHECK_GT(matrix_width, 0.0);
104   // Load the results into the popup's matrix.
105   DCHECK_GT(rows, 0U);
106   [matrix_ renewRows:rows columns:1];
107   CGFloat max_match_contents_width = 0.0f;
108   CGFloat contents_offset = -1.0f;
109   for (size_t ii = 0; ii < rows; ++ii) {
110     OmniboxPopupCell* cell = [matrix_ cellAtRow:ii column:0];
111     const AutocompleteMatch& match = GetResult().match_at(ii + start_match);
112     [cell setImage:ImageForMatch(match)];
113     [cell setMatch:match];
114     if (match.type == AutocompleteMatchType::SEARCH_SUGGEST_INFINITE) {
115       max_match_contents_width = std::max(max_match_contents_width,
116                                            [cell getMatchContentsWidth]);
117       if (contents_offset < 0.0f) {
118         contents_offset = [OmniboxPopupCell computeContentsOffset:match];
119       }
120       [cell setContentsOffset:contents_offset];
121     }
122   }
124   for (size_t ii = 0; ii < rows; ++ii) {
125     OmniboxPopupCell* cell = [matrix_ cellAtRow:ii column:0];
126     [cell setMaxMatchContentsWidth:max_match_contents_width];
127   }
129   // Set the cell size to fit a line of text in the cell's font.  All
130   // cells should use the same font and each should layout in one
131   // line, so they should all be about the same height.
132   const NSSize cell_size = [[matrix_ cellAtRow:0 column:0] cellSize];
133   DCHECK_GT(cell_size.height, 0.0);
134   const CGFloat cell_height = cell_size.height + kCellHeightAdjust;
135   [matrix_ setCellSize:NSMakeSize(matrix_width, cell_height)];
137   // Update the selection before placing (and displaying) the window.
138   PaintUpdatesNow();
140   // Calculate the matrix size manually rather than using -sizeToCells
141   // because actually resizing the matrix messed up the popup size
142   // animation.
143   DCHECK_EQ([matrix_ intercellSpacing].height, 0.0);
144   PositionPopup(rows * cell_height);
147 gfx::Rect OmniboxPopupViewMac::GetTargetBounds() {
148   // Flip the coordinate system before returning.
149   NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
150   NSRect monitor_frame = [screen frame];
151   gfx::Rect bounds(NSRectToCGRect(target_popup_frame_));
152   bounds.set_y(monitor_frame.size.height - bounds.y() - bounds.height());
153   return bounds;
156 // This is only called by model in SetSelectedLine() after updating
157 // everything.  Popup should already be visible.
158 void OmniboxPopupViewMac::PaintUpdatesNow() {
159   size_t start_match = model_->result().ShouldHideTopMatch() ? 1 : 0;
160   if (start_match > model_->selected_line()) {
161     [matrix_ deselectAllCells];
162   } else {
163     [matrix_ selectCellAtRow:model_->selected_line() - start_match column:0];
164   }
168 void OmniboxPopupViewMac::OnMatrixRowSelected(OmniboxPopupMatrix* matrix,
169                                               size_t row) {
170   size_t start_match = model_->result().ShouldHideTopMatch() ? 1 : 0;
171   model_->SetSelectedLine(row + start_match, false, false);
174 void OmniboxPopupViewMac::OnMatrixRowClicked(OmniboxPopupMatrix* matrix,
175                                              size_t row) {
176   OpenURLForRow(row,
177                 ui::WindowOpenDispositionFromNSEvent([NSApp currentEvent]));
180 void OmniboxPopupViewMac::OnMatrixRowMiddleClicked(OmniboxPopupMatrix* matrix,
181                                                    size_t row) {
182   OpenURLForRow(row, NEW_BACKGROUND_TAB);
185 const AutocompleteResult& OmniboxPopupViewMac::GetResult() const {
186   return model_->result();
189 void OmniboxPopupViewMac::CreatePopupIfNeeded() {
190   if (!popup_) {
191     popup_.reset(
192         [[NSWindow alloc] initWithContentRect:ui::kWindowSizeDeterminedLater
193                                     styleMask:NSBorderlessWindowMask
194                                       backing:NSBackingStoreBuffered
195                                         defer:YES]);
196     [popup_ setBackgroundColor:[NSColor clearColor]];
197     [popup_ setOpaque:NO];
199     // Use a flipped view to pin the matrix top the top left. This is needed
200     // for animated resize.
201     base::scoped_nsobject<FlippedView> contentView(
202         [[FlippedView alloc] initWithFrame:NSZeroRect]);
203     [popup_ setContentView:contentView];
205     // View to draw a background beneath the matrix.
206     background_view_.reset([[NSBox alloc] initWithFrame:NSZeroRect]);
207     [background_view_ setBoxType:NSBoxCustom];
208     [background_view_ setBorderType:NSNoBorder];
209     [background_view_ setFillColor:BackgroundColor()];
210     [background_view_ setContentViewMargins:NSZeroSize];
211     [contentView addSubview:background_view_];
213     matrix_.reset([[OmniboxPopupMatrix alloc] initWithDelegate:this]);
214     [background_view_ addSubview:matrix_];
216     top_separator_view_.reset(
217         [[OmniboxPopupTopSeparatorView alloc] initWithFrame:NSZeroRect]);
218     [contentView addSubview:top_separator_view_];
220     bottom_separator_view_.reset(
221         [[OmniboxPopupBottomSeparatorView alloc] initWithFrame:NSZeroRect]);
222     [contentView addSubview:bottom_separator_view_];
224     // TODO(dtseng): Ignore until we provide NSAccessibility support.
225     [popup_ accessibilitySetOverrideValue:NSAccessibilityUnknownRole
226                              forAttribute:NSAccessibilityRoleAttribute];
227   }
230 void OmniboxPopupViewMac::PositionPopup(const CGFloat matrixHeight) {
231   BrowserWindowController* controller =
232       [BrowserWindowController browserWindowControllerForView:field_];
233   NSRect anchor_rect_base = [controller omniboxPopupAnchorRect];
235   // Calculate the popup's position on the screen.
236   NSRect popup_frame = anchor_rect_base;
237   // Size to fit the matrix and shift down by the size.
238   popup_frame.size.height = matrixHeight + kPopupPaddingVertical * 2.0;
239   popup_frame.size.height += [OmniboxPopupTopSeparatorView preferredHeight];
240   popup_frame.size.height += [OmniboxPopupBottomSeparatorView preferredHeight];
241   popup_frame.origin.y -= NSHeight(popup_frame);
242   // Shift to screen coordinates.
243   popup_frame.origin =
244       [[controller window] convertBaseToScreen:popup_frame.origin];
246   // Do nothing if the popup is already animating to the given |frame|.
247   if (NSEqualRects(popup_frame, target_popup_frame_))
248     return;
250   // Top separator.
251   NSRect top_separator_frame = NSZeroRect;
252   top_separator_frame.size.width = NSWidth(popup_frame);
253   top_separator_frame.size.height =
254       [OmniboxPopupTopSeparatorView preferredHeight];
255   [top_separator_view_ setFrame:top_separator_frame];
257   // Bottom separator.
258   NSRect bottom_separator_frame = NSZeroRect;
259   bottom_separator_frame.size.width = NSWidth(popup_frame);
260   bottom_separator_frame.size.height =
261       [OmniboxPopupBottomSeparatorView preferredHeight];
262   bottom_separator_frame.origin.y =
263       NSHeight(popup_frame) - NSHeight(bottom_separator_frame);
264   [bottom_separator_view_ setFrame:bottom_separator_frame];
266   // Background view.
267   NSRect background_rect = NSZeroRect;
268   background_rect.size.width = NSWidth(popup_frame);
269   background_rect.size.height = NSHeight(popup_frame) -
270       NSHeight(top_separator_frame) - NSHeight(bottom_separator_frame);
271   background_rect.origin.y = NSMaxY(top_separator_frame);
272   [background_view_ setFrame:background_rect];
274   // Matrix.
275   NSPoint field_origin_base =
276       [field_ convertPoint:[field_ bounds].origin toView:nil];
277   NSRect matrix_frame = NSZeroRect;
278   matrix_frame.origin.x = field_origin_base.x - NSMinX(anchor_rect_base);
279   matrix_frame.origin.y = kPopupPaddingVertical;
280   matrix_frame.size.width = [matrix_ cellSize].width;
281   matrix_frame.size.height = matrixHeight;
282   [matrix_ setFrame:matrix_frame];
284   NSRect current_poup_frame = [popup_ frame];
285   target_popup_frame_ = popup_frame;
287   // Animate the frame change if the only change is that the height got smaller.
288   // Otherwise, resize immediately.
289   bool animate = (NSHeight(popup_frame) < NSHeight(current_poup_frame) &&
290                   NSWidth(popup_frame) == NSWidth(current_poup_frame));
292   base::scoped_nsobject<NSDictionary> savedAnimations;
293   if (!animate) {
294     // In an ideal world, running a zero-length animation would cancel any
295     // running animations and set the new frame value immediately.  In practice,
296     // zero-length animations are ignored entirely.  Work around this AppKit bug
297     // by explicitly setting an NSNull animation for the "frame" key and then
298     // running the animation with a non-zero(!!) duration.  This somehow
299     // convinces AppKit to do the right thing.  Save off the current animations
300     // dictionary so it can be restored later.
301     savedAnimations.reset([[popup_ animations] copy]);
302     [popup_ setAnimations:@{@"frame" : [NSNull null]}];
303   }
305   [NSAnimationContext beginGrouping];
306   // Don't use the GTM additon for the "Steve" slowdown because this can happen
307   // async from user actions and the effects could be a surprise.
308   [[NSAnimationContext currentContext] setDuration:kShrinkAnimationDuration];
309   [[popup_ animator] setFrame:popup_frame display:YES];
310   [NSAnimationContext endGrouping];
312   if (!animate) {
313     // Restore the original animations dictionary.  This does not reinstate any
314     // previously running animations.
315     [popup_ setAnimations:savedAnimations];
316   }
318   if (![popup_ isVisible])
319     [[field_ window] addChildWindow:popup_ ordered:NSWindowAbove];
322 NSImage* OmniboxPopupViewMac::ImageForMatch(const AutocompleteMatch& match) {
323   gfx::Image image = model_->GetIconIfExtensionMatch(match);
324   if (!image.IsEmpty())
325     return image.AsNSImage();
327   const int resource_id = match.starred ?
328       IDR_OMNIBOX_STAR : AutocompleteMatch::TypeToIcon(match.type);
329   return OmniboxViewMac::ImageForResource(resource_id);
332 void OmniboxPopupViewMac::OpenURLForRow(size_t row,
333                                         WindowOpenDisposition disposition) {
334   size_t start_match = model_->result().ShouldHideTopMatch() ? 1 : 0;
335   row += start_match;
336   DCHECK_LT(row, GetResult().size());
337   omnibox_view_->OpenMatch(GetResult().match_at(row), disposition, GURL(),
338                            base::string16(), row);