Create c/b/ui/views/layout_constants.*.
[chromium-blink-merge.git] / chrome / browser / ui / views / omnibox / omnibox_popup_contents_view.cc
blob8f708ab0b358d52370f6708851a4204de4cef196
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/views/omnibox/omnibox_popup_contents_view.h"
7 #include <algorithm>
9 #include "chrome/browser/search/search.h"
10 #include "chrome/browser/themes/theme_properties.h"
11 #include "chrome/browser/ui/views/layout_constants.h"
12 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
13 #include "chrome/browser/ui/views/omnibox/omnibox_result_view.h"
14 #include "components/omnibox/browser/omnibox_view.h"
15 #include "grit/theme_resources.h"
16 #include "ui/base/resource/material_design/material_design_controller.h"
17 #include "ui/base/theme_provider.h"
18 #include "ui/compositor/clip_transform_recorder.h"
19 #include "ui/compositor/paint_recorder.h"
20 #include "ui/gfx/canvas.h"
21 #include "ui/gfx/image/image.h"
22 #include "ui/gfx/path.h"
23 #include "ui/resources/grit/ui_resources.h"
24 #include "ui/views/controls/image_view.h"
25 #include "ui/views/resources/grit/views_resources.h"
26 #include "ui/views/view_targeter.h"
27 #include "ui/views/widget/widget.h"
28 #include "ui/views/window/non_client_view.h"
30 class OmniboxPopupContentsView::AutocompletePopupWidget
31 : public views::Widget,
32 public base::SupportsWeakPtr<AutocompletePopupWidget> {
33 public:
34 AutocompletePopupWidget() {}
35 ~AutocompletePopupWidget() override {}
37 private:
38 DISALLOW_COPY_AND_ASSIGN(AutocompletePopupWidget);
41 ////////////////////////////////////////////////////////////////////////////////
42 // OmniboxPopupContentsView, public:
44 OmniboxPopupView* OmniboxPopupContentsView::Create(
45 const gfx::FontList& font_list,
46 OmniboxView* omnibox_view,
47 OmniboxEditModel* edit_model,
48 LocationBarView* location_bar_view) {
49 OmniboxPopupContentsView* view = NULL;
50 view = new OmniboxPopupContentsView(
51 font_list, omnibox_view, edit_model, location_bar_view);
52 view->Init();
53 return view;
56 OmniboxPopupContentsView::OmniboxPopupContentsView(
57 const gfx::FontList& font_list,
58 OmniboxView* omnibox_view,
59 OmniboxEditModel* edit_model,
60 LocationBarView* location_bar_view)
61 : model_(new OmniboxPopupModel(this, edit_model)),
62 omnibox_view_(omnibox_view),
63 location_bar_view_(location_bar_view),
64 font_list_(font_list),
65 ignore_mouse_drag_(false),
66 size_animation_(this),
67 start_margin_(0),
68 end_margin_(0) {
69 // The contents is owned by the LocationBarView.
70 set_owned_by_client();
72 ui::ThemeProvider* theme = location_bar_view_->GetThemeProvider();
73 if (ui::MaterialDesignController::IsModeMaterial()) {
74 top_shadow_ = theme->GetImageSkiaNamed(IDR_OMNIBOX_DROPDOWN_SHADOW_TOP);
75 bottom_shadow_ =
76 theme->GetImageSkiaNamed(IDR_OMNIBOX_DROPDOWN_SHADOW_BOTTOM);
77 } else {
78 bottom_shadow_ = theme->GetImageSkiaNamed(IDR_BUBBLE_B);
81 SetEventTargeter(
82 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
85 void OmniboxPopupContentsView::Init() {
86 // This can't be done in the constructor as at that point we aren't
87 // necessarily our final class yet, and we may have subclasses
88 // overriding CreateResultView.
89 for (size_t i = 0; i < AutocompleteResult::kMaxMatches; ++i) {
90 OmniboxResultView* result_view = CreateResultView(i, font_list_);
91 result_view->SetVisible(false);
92 AddChildViewAt(result_view, static_cast<int>(i));
96 OmniboxPopupContentsView::~OmniboxPopupContentsView() {
97 // We don't need to do anything with |popup_| here. The OS either has already
98 // closed the window, in which case it's been deleted, or it will soon, in
99 // which case there's nothing we need to do.
102 gfx::Rect OmniboxPopupContentsView::GetPopupBounds() const {
103 if (!size_animation_.is_animating())
104 return target_bounds_;
106 gfx::Rect current_frame_bounds = start_bounds_;
107 int total_height_delta = target_bounds_.height() - start_bounds_.height();
108 // Round |current_height_delta| instead of truncating so we won't leave single
109 // white pixels at the bottom of the popup as long when animating very small
110 // height differences.
111 int current_height_delta = static_cast<int>(
112 size_animation_.GetCurrentValue() * total_height_delta - 0.5);
113 current_frame_bounds.set_height(
114 current_frame_bounds.height() + current_height_delta);
115 return current_frame_bounds;
118 void OmniboxPopupContentsView::LayoutChildren() {
119 gfx::Rect contents_rect = GetContentsBounds();
120 contents_rect.Inset(GetLayoutInsets(OMNIBOX_DROPDOWN_TEXT));
121 contents_rect.Inset(0, views::NonClientFrameView::kClientEdgeThickness, 0, 0);
123 // In the non-material dropdown, the colored/clickable regions within the
124 // dropdown are only as wide as the location bar. In the material version,
125 // these are full width, and OmniboxResultView instead insets the icons/text
126 // inside to be aligned with the location bar.
127 if (!ui::MaterialDesignController::IsModeMaterial())
128 contents_rect.Inset(start_margin_, 0, end_margin_, 0);
130 int top = contents_rect.y();
131 for (size_t i = 0; i < AutocompleteResult::kMaxMatches; ++i) {
132 View* v = child_at(i);
133 if (v->visible()) {
134 v->SetBounds(contents_rect.x(), top, contents_rect.width(),
135 v->GetPreferredSize().height());
136 top = v->bounds().bottom();
141 ////////////////////////////////////////////////////////////////////////////////
142 // OmniboxPopupContentsView, OmniboxPopupView overrides:
144 bool OmniboxPopupContentsView::IsOpen() const {
145 return popup_ != NULL;
148 void OmniboxPopupContentsView::InvalidateLine(size_t line) {
149 OmniboxResultView* result = result_view_at(line);
150 result->Invalidate();
152 if (HasMatchAt(line) && GetMatchAtIndex(line).associated_keyword.get()) {
153 result->ShowKeyword(IsSelectedIndex(line) &&
154 model_->selected_line_state() == OmniboxPopupModel::KEYWORD);
158 void OmniboxPopupContentsView::UpdatePopupAppearance() {
159 if (model_->result().empty() || omnibox_view_->IsImeShowingPopup()) {
160 // No matches or the IME is showing a popup window which may overlap
161 // the omnibox popup window. Close any existing popup.
162 if (popup_ != NULL) {
163 size_animation_.Stop();
165 // NOTE: Do NOT use CloseNow() here, as we may be deep in a callstack
166 // triggered by the popup receiving a message (e.g. LBUTTONUP), and
167 // destroying the popup would cause us to read garbage when we unwind back
168 // to that level.
169 popup_->Close(); // This will eventually delete the popup.
170 popup_.reset();
172 return;
175 // Update the match cached by each row, in the process of doing so make sure
176 // we have enough row views.
177 const size_t result_size = model_->result().size();
178 max_match_contents_width_ = 0;
179 for (size_t i = 0; i < result_size; ++i) {
180 OmniboxResultView* view = result_view_at(i);
181 const AutocompleteMatch& match = GetMatchAtIndex(i);
182 view->SetMatch(match);
183 view->SetVisible(true);
184 if (match.answer && !model_->answer_bitmap().isNull()) {
185 view->SetAnswerImage(
186 gfx::ImageSkia::CreateFrom1xBitmap(model_->answer_bitmap()));
188 if (match.type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) {
189 max_match_contents_width_ = std::max(
190 max_match_contents_width_, view->GetMatchContentsWidth());
194 for (size_t i = result_size; i < AutocompleteResult::kMaxMatches; ++i)
195 child_at(i)->SetVisible(false);
197 // In non-material mode, we want the popup to appear as if it's overlaying
198 // the top of the page content, i.e., is flush against the client edge at the
199 // bottom of the toolbar. However, if the bookmarks bar is attached, we want
200 // to draw over it (so as not to push the results below it), but that means
201 // the toolbar won't be drawing a client edge separating itself from the
202 // popup. So we unconditionally overlap the toolbar by the thickness of the
203 // client edge and draw our own edge (see OnPaint()), which fixes the
204 // attached bookmark bar case without breaking the other case.
205 int top_edge_overlap = views::NonClientFrameView::kClientEdgeThickness;
206 if (ui::MaterialDesignController::IsModeMaterial()) {
207 // In material mode, we cover the bookmark bar similarly, but instead of
208 // appearing below the client edge, we want the popup to appear to overlay
209 // the bottom of the toolbar. So instead of drawing a client edge atop the
210 // popup, we shift the popup to completely cover the client edge, and then
211 // draw an additional semitransparent shadow above that. So the total
212 // overlap necessary is the client edge thickness plus the shadow height.
213 top_edge_overlap += top_shadow_->height();
216 gfx::Point top_left_screen_coord;
217 int width;
218 location_bar_view_->GetOmniboxPopupPositioningInfo(
219 &top_left_screen_coord, &width, &start_margin_,
220 &end_margin_, top_edge_overlap);
221 gfx::Rect new_target_bounds(top_left_screen_coord,
222 gfx::Size(width, CalculatePopupHeight()));
224 // If we're animating and our target height changes, reset the animation.
225 // NOTE: If we just reset blindly on _every_ update, then when the user types
226 // rapidly we could get "stuck" trying repeatedly to animate shrinking by the
227 // last few pixels to get to one visible result.
228 if (new_target_bounds.height() != target_bounds_.height())
229 size_animation_.Reset();
230 target_bounds_ = new_target_bounds;
232 if (popup_ == NULL) {
233 views::Widget* popup_parent = location_bar_view_->GetWidget();
235 // If the popup is currently closed, we need to create it.
236 popup_ = (new AutocompletePopupWidget)->AsWeakPtr();
237 // On Windows use TYPE_MENU to ensure that this window uses the software
238 // compositor which avoids the UI thread blocking issue during command
239 // buffer creation. We can revert this change once http://crbug.com/125248
240 // is fixed.
241 #if defined(OS_WIN)
242 views::Widget::InitParams params(views::Widget::InitParams::TYPE_MENU);
243 // The menu style assumes a top most window. We don't want that in this
244 // case.
245 params.keep_on_top = false;
246 #else
247 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
248 #endif
249 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
250 params.parent = popup_parent->GetNativeView();
251 params.bounds = GetPopupBounds();
252 params.context = popup_parent->GetNativeWindow();
253 popup_->Init(params);
254 // Third-party software such as DigitalPersona identity verification can
255 // hook the underlying window creation methods and use SendMessage to
256 // synchronously change focus/activation, resulting in the popup being
257 // destroyed by the time control returns here. Bail out in this case to
258 // avoid a NULL dereference.
259 if (!popup_.get())
260 return;
261 popup_->SetVisibilityAnimationTransition(views::Widget::ANIMATE_NONE);
262 popup_->SetContentsView(this);
263 popup_->StackAbove(omnibox_view_->GetRelativeWindowForPopup());
264 if (!popup_.get()) {
265 // For some IMEs GetRelativeWindowForPopup triggers the omnibox to lose
266 // focus, thereby closing (and destroying) the popup.
267 // TODO(sky): this won't be needed once we close the omnibox on input
268 // window showing.
269 return;
271 popup_->ShowInactive();
272 } else {
273 // Animate the popup shrinking, but don't animate growing larger since that
274 // would make the popup feel less responsive.
275 start_bounds_ = GetWidget()->GetWindowBoundsInScreen();
276 if (target_bounds_.height() < start_bounds_.height())
277 size_animation_.Show();
278 else
279 start_bounds_ = target_bounds_;
280 popup_->SetBounds(GetPopupBounds());
283 Layout();
286 gfx::Rect OmniboxPopupContentsView::GetTargetBounds() {
287 return target_bounds_;
290 void OmniboxPopupContentsView::PaintUpdatesNow() {
291 // TODO(beng): remove this from the interface.
294 void OmniboxPopupContentsView::OnDragCanceled() {
295 ignore_mouse_drag_ = true;
298 ////////////////////////////////////////////////////////////////////////////////
299 // OmniboxPopupContentsView, OmniboxResultViewModel implementation:
301 bool OmniboxPopupContentsView::IsSelectedIndex(size_t index) const {
302 return index == model_->selected_line();
305 bool OmniboxPopupContentsView::IsHoveredIndex(size_t index) const {
306 return index == model_->hovered_line();
309 gfx::Image OmniboxPopupContentsView::GetIconIfExtensionMatch(
310 size_t index) const {
311 if (!HasMatchAt(index))
312 return gfx::Image();
313 return model_->GetIconIfExtensionMatch(GetMatchAtIndex(index));
316 bool OmniboxPopupContentsView::IsStarredMatch(
317 const AutocompleteMatch& match) const {
318 return model_->IsStarredMatch(match);
321 ////////////////////////////////////////////////////////////////////////////////
322 // OmniboxPopupContentsView, AnimationDelegate implementation:
324 void OmniboxPopupContentsView::AnimationProgressed(
325 const gfx::Animation* animation) {
326 // We should only be running the animation when the popup is already visible.
327 DCHECK(popup_ != NULL);
328 popup_->SetBounds(GetPopupBounds());
331 ////////////////////////////////////////////////////////////////////////////////
332 // OmniboxPopupContentsView, views::View overrides:
334 void OmniboxPopupContentsView::Layout() {
335 // Size our children to the available content area.
336 LayoutChildren();
338 // We need to manually schedule a paint here since we are a layered window and
339 // won't implicitly require painting until we ask for one.
340 SchedulePaint();
343 views::View* OmniboxPopupContentsView::GetTooltipHandlerForPoint(
344 const gfx::Point& point) {
345 return NULL;
348 bool OmniboxPopupContentsView::OnMousePressed(
349 const ui::MouseEvent& event) {
350 ignore_mouse_drag_ = false; // See comment on |ignore_mouse_drag_| in header.
351 if (event.IsLeftMouseButton() || event.IsMiddleMouseButton())
352 UpdateLineEvent(event, event.IsLeftMouseButton());
353 return true;
356 bool OmniboxPopupContentsView::OnMouseDragged(
357 const ui::MouseEvent& event) {
358 if (event.IsLeftMouseButton() || event.IsMiddleMouseButton())
359 UpdateLineEvent(event, !ignore_mouse_drag_ && event.IsLeftMouseButton());
360 return true;
363 void OmniboxPopupContentsView::OnMouseReleased(
364 const ui::MouseEvent& event) {
365 if (ignore_mouse_drag_) {
366 OnMouseCaptureLost();
367 return;
370 if (event.IsOnlyMiddleMouseButton() || event.IsOnlyLeftMouseButton()) {
371 OpenSelectedLine(event, event.IsOnlyLeftMouseButton() ? CURRENT_TAB :
372 NEW_BACKGROUND_TAB);
376 void OmniboxPopupContentsView::OnMouseCaptureLost() {
377 ignore_mouse_drag_ = false;
380 void OmniboxPopupContentsView::OnMouseMoved(
381 const ui::MouseEvent& event) {
382 model_->SetHoveredLine(GetIndexForPoint(event.location()));
385 void OmniboxPopupContentsView::OnMouseEntered(
386 const ui::MouseEvent& event) {
387 model_->SetHoveredLine(GetIndexForPoint(event.location()));
390 void OmniboxPopupContentsView::OnMouseExited(
391 const ui::MouseEvent& event) {
392 model_->SetHoveredLine(OmniboxPopupModel::kNoMatch);
395 void OmniboxPopupContentsView::OnGestureEvent(ui::GestureEvent* event) {
396 switch (event->type()) {
397 case ui::ET_GESTURE_TAP_DOWN:
398 case ui::ET_GESTURE_SCROLL_BEGIN:
399 case ui::ET_GESTURE_SCROLL_UPDATE:
400 UpdateLineEvent(*event, true);
401 break;
402 case ui::ET_GESTURE_TAP:
403 case ui::ET_GESTURE_SCROLL_END:
404 OpenSelectedLine(*event, CURRENT_TAB);
405 break;
406 default:
407 return;
409 event->SetHandled();
412 ////////////////////////////////////////////////////////////////////////////////
413 // OmniboxPopupContentsView, protected:
415 int OmniboxPopupContentsView::CalculatePopupHeight() {
416 DCHECK_GE(static_cast<size_t>(child_count()), model_->result().size());
417 int popup_height = 0;
418 for (size_t i = 0; i < model_->result().size(); ++i)
419 popup_height += child_at(i)->GetPreferredSize().height();
421 // Add enough space on the top and bottom so it looks like there is the same
422 // amount of space between the text and the popup border as there is in the
423 // interior between each row of text.
424 return popup_height + views::NonClientFrameView::kClientEdgeThickness +
425 GetLayoutInsets(OMNIBOX_DROPDOWN_TEXT).height() +
426 bottom_shadow_->height() -
427 GetLayoutConstant(OMNIBOX_DROPDOWN_BORDER_INTERIOR);
430 OmniboxResultView* OmniboxPopupContentsView::CreateResultView(
431 int model_index,
432 const gfx::FontList& font_list) {
433 return new OmniboxResultView(this, model_index, location_bar_view_,
434 font_list);
437 ////////////////////////////////////////////////////////////////////////////////
438 // OmniboxPopupContentsView, views::View overrides, private:
440 const char* OmniboxPopupContentsView::GetClassName() const {
441 return "OmniboxPopupContentsView";
444 void OmniboxPopupContentsView::OnPaint(gfx::Canvas* canvas) {
445 // Top border.
446 if (ui::MaterialDesignController::IsModeMaterial()) {
447 canvas->TileImageInt(*top_shadow_, 0, 0, width(), top_shadow_->height());
448 } else {
449 canvas->FillRect(
450 gfx::Rect(0, 0, width(),
451 views::NonClientFrameView::kClientEdgeThickness),
452 ThemeProperties::GetDefaultColor(
453 ThemeProperties::COLOR_TOOLBAR_SEPARATOR));
456 // Bottom border.
457 canvas->TileImageInt(*bottom_shadow_, 0, height() - bottom_shadow_->height(),
458 width(), bottom_shadow_->height());
461 void OmniboxPopupContentsView::PaintChildren(const ui::PaintContext& context) {
462 gfx::Rect contents_bounds = GetContentsBounds();
463 const int interior = GetLayoutConstant(OMNIBOX_DROPDOWN_BORDER_INTERIOR);
464 contents_bounds.Inset(0, views::NonClientFrameView::kClientEdgeThickness, 0,
465 bottom_shadow_->height() - interior);
467 ui::ClipTransformRecorder clip_transform_recorder(context);
468 clip_transform_recorder.ClipRect(contents_bounds);
470 ui::PaintRecorder recorder(context, size());
471 SkColor background_color = result_view_at(0)->GetColor(
472 OmniboxResultView::NORMAL, OmniboxResultView::BACKGROUND);
473 recorder.canvas()->DrawColor(background_color);
475 View::PaintChildren(context);
478 ////////////////////////////////////////////////////////////////////////////////
479 // OmniboxPopupContentsView, private:
481 views::View* OmniboxPopupContentsView::TargetForRect(views::View* root,
482 const gfx::Rect& rect) {
483 CHECK_EQ(root, this);
484 return this;
487 bool OmniboxPopupContentsView::HasMatchAt(size_t index) const {
488 return index < model_->result().size();
491 const AutocompleteMatch& OmniboxPopupContentsView::GetMatchAtIndex(
492 size_t index) const {
493 return model_->result().match_at(index);
496 size_t OmniboxPopupContentsView::GetIndexForPoint(
497 const gfx::Point& point) {
498 if (!HitTestPoint(point))
499 return OmniboxPopupModel::kNoMatch;
501 int nb_match = model_->result().size();
502 DCHECK(nb_match <= child_count());
503 for (int i = 0; i < nb_match; ++i) {
504 views::View* child = child_at(i);
505 gfx::Point point_in_child_coords(point);
506 View::ConvertPointToTarget(this, child, &point_in_child_coords);
507 if (child->visible() && child->HitTestPoint(point_in_child_coords))
508 return i;
510 return OmniboxPopupModel::kNoMatch;
513 void OmniboxPopupContentsView::UpdateLineEvent(
514 const ui::LocatedEvent& event,
515 bool should_set_selected_line) {
516 size_t index = GetIndexForPoint(event.location());
517 model_->SetHoveredLine(index);
518 if (HasMatchAt(index) && should_set_selected_line)
519 model_->SetSelectedLine(index, false, false);
522 void OmniboxPopupContentsView::OpenSelectedLine(
523 const ui::LocatedEvent& event,
524 WindowOpenDisposition disposition) {
525 size_t index = GetIndexForPoint(event.location());
526 if (!HasMatchAt(index))
527 return;
528 omnibox_view_->OpenMatch(model_->result().match_at(index), disposition,
529 GURL(), base::string16(), index);
532 OmniboxResultView* OmniboxPopupContentsView::result_view_at(size_t i) {
533 return static_cast<OmniboxResultView*>(child_at(static_cast<int>(i)));