Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / views / omnibox / omnibox_popup_contents_view.cc
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.
4
5 #include "chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h"
6
7 #include <algorithm>
8
9 #include "chrome/browser/search/search.h"
10 #include "chrome/browser/themes/theme_properties.h"
11 #include "chrome/browser/ui/omnibox/omnibox_view.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 "ui/base/theme_provider.h"
15 #include "ui/gfx/canvas.h"
16 #include "ui/gfx/image/image.h"
17 #include "ui/gfx/path.h"
18 #include "ui/resources/grit/ui_resources.h"
19 #include "ui/views/controls/image_view.h"
20 #include "ui/views/view_targeter.h"
21 #include "ui/views/widget/widget.h"
22 #include "ui/views/window/non_client_view.h"
23 #include "ui/wm/core/window_animations.h"
24
25 // This is the number of pixels in the border image interior to the actual
26 // border.
27 const int kBorderInterior = 6;
28
29 class OmniboxPopupContentsView::AutocompletePopupWidget
30     : public views::Widget,
31       public base::SupportsWeakPtr<AutocompletePopupWidget> {
32  public:
33   AutocompletePopupWidget() {}
34   virtual ~AutocompletePopupWidget() {}
35
36  private:
37   DISALLOW_COPY_AND_ASSIGN(AutocompletePopupWidget);
38 };
39
40 ////////////////////////////////////////////////////////////////////////////////
41 // OmniboxPopupContentsView, public:
42
43 OmniboxPopupView* OmniboxPopupContentsView::Create(
44     const gfx::FontList& font_list,
45     OmniboxView* omnibox_view,
46     OmniboxEditModel* edit_model,
47     LocationBarView* location_bar_view) {
48   OmniboxPopupContentsView* view = NULL;
49   view = new OmniboxPopupContentsView(
50       font_list, omnibox_view, edit_model, location_bar_view);
51   view->Init();
52   return view;
53 }
54
55 OmniboxPopupContentsView::OmniboxPopupContentsView(
56     const gfx::FontList& font_list,
57     OmniboxView* omnibox_view,
58     OmniboxEditModel* edit_model,
59     LocationBarView* location_bar_view)
60     : model_(new OmniboxPopupModel(this, edit_model)),
61       omnibox_view_(omnibox_view),
62       location_bar_view_(location_bar_view),
63       font_list_(font_list),
64       ignore_mouse_drag_(false),
65       size_animation_(this),
66       left_margin_(0),
67       right_margin_(0),
68       outside_vertical_padding_(0) {
69   // The contents is owned by the LocationBarView.
70   set_owned_by_client();
71
72   ui::ThemeProvider* theme = location_bar_view_->GetThemeProvider();
73   bottom_shadow_ = theme->GetImageSkiaNamed(IDR_BUBBLE_B);
74
75   SetEventTargeter(
76       scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
77 }
78
79 void OmniboxPopupContentsView::Init() {
80   // This can't be done in the constructor as at that point we aren't
81   // necessarily our final class yet, and we may have subclasses
82   // overriding CreateResultView.
83   for (size_t i = 0; i < AutocompleteResult::kMaxMatches; ++i) {
84     OmniboxResultView* result_view = CreateResultView(i, font_list_);
85     result_view->SetVisible(false);
86     AddChildViewAt(result_view, static_cast<int>(i));
87   }
88 }
89
90 OmniboxPopupContentsView::~OmniboxPopupContentsView() {
91   // We don't need to do anything with |popup_| here.  The OS either has already
92   // closed the window, in which case it's been deleted, or it will soon, in
93   // which case there's nothing we need to do.
94 }
95
96 gfx::Rect OmniboxPopupContentsView::GetPopupBounds() const {
97   if (!size_animation_.is_animating())
98     return target_bounds_;
99
100   gfx::Rect current_frame_bounds = start_bounds_;
101   int total_height_delta = target_bounds_.height() - start_bounds_.height();
102   // Round |current_height_delta| instead of truncating so we won't leave single
103   // white pixels at the bottom of the popup as long when animating very small
104   // height differences.
105   int current_height_delta = static_cast<int>(
106       size_animation_.GetCurrentValue() * total_height_delta - 0.5);
107   current_frame_bounds.set_height(
108       current_frame_bounds.height() + current_height_delta);
109   return current_frame_bounds;
110 }
111
112 void OmniboxPopupContentsView::LayoutChildren() {
113   gfx::Rect contents_rect = GetContentsBounds();
114
115   contents_rect.Inset(left_margin_,
116                       views::NonClientFrameView::kClientEdgeThickness +
117                           outside_vertical_padding_,
118                       right_margin_, outside_vertical_padding_);
119   int top = contents_rect.y();
120   for (size_t i = 0; i < AutocompleteResult::kMaxMatches; ++i) {
121     View* v = child_at(i);
122     if (v->visible()) {
123       v->SetBounds(contents_rect.x(), top, contents_rect.width(),
124                    v->GetPreferredSize().height());
125       top = v->bounds().bottom();
126     }
127   }
128 }
129
130 ////////////////////////////////////////////////////////////////////////////////
131 // OmniboxPopupContentsView, OmniboxPopupView overrides:
132
133 bool OmniboxPopupContentsView::IsOpen() const {
134   return popup_ != NULL;
135 }
136
137 void OmniboxPopupContentsView::InvalidateLine(size_t line) {
138   OmniboxResultView* result = result_view_at(line);
139   result->Invalidate();
140
141   if (HasMatchAt(line) && GetMatchAtIndex(line).associated_keyword.get()) {
142     result->ShowKeyword(IsSelectedIndex(line) &&
143         model_->selected_line_state() == OmniboxPopupModel::KEYWORD);
144   }
145 }
146
147 void OmniboxPopupContentsView::UpdatePopupAppearance() {
148   const size_t hidden_matches = model_->result().ShouldHideTopMatch() ? 1 : 0;
149   if (model_->result().size() <= hidden_matches ||
150       omnibox_view_->IsImeShowingPopup()) {
151     // No matches or the IME is showing a popup window which may overlap
152     // the omnibox popup window.  Close any existing popup.
153     if (popup_ != NULL) {
154       size_animation_.Stop();
155
156       // NOTE: Do NOT use CloseNow() here, as we may be deep in a callstack
157       // triggered by the popup receiving a message (e.g. LBUTTONUP), and
158       // destroying the popup would cause us to read garbage when we unwind back
159       // to that level.
160       popup_->Close();  // This will eventually delete the popup.
161       popup_.reset();
162     }
163     return;
164   }
165
166   // Update the match cached by each row, in the process of doing so make sure
167   // we have enough row views.
168   const size_t result_size = model_->result().size();
169   max_match_contents_width_ = 0;
170   for (size_t i = 0; i < result_size; ++i) {
171     OmniboxResultView* view = result_view_at(i);
172     const AutocompleteMatch& match = GetMatchAtIndex(i);
173     view->SetMatch(match);
174     view->SetVisible(i >= hidden_matches);
175     if (match.type == AutocompleteMatchType::SEARCH_SUGGEST_INFINITE) {
176       max_match_contents_width_ = std::max(
177           max_match_contents_width_, view->GetMatchContentsWidth());
178     }
179   }
180
181   for (size_t i = result_size; i < AutocompleteResult::kMaxMatches; ++i)
182     child_at(i)->SetVisible(false);
183
184   gfx::Point top_left_screen_coord;
185   int width;
186   location_bar_view_->GetOmniboxPopupPositioningInfo(
187       &top_left_screen_coord, &width, &left_margin_, &right_margin_);
188   gfx::Rect new_target_bounds(top_left_screen_coord,
189                               gfx::Size(width, CalculatePopupHeight()));
190
191   // If we're animating and our target height changes, reset the animation.
192   // NOTE: If we just reset blindly on _every_ update, then when the user types
193   // rapidly we could get "stuck" trying repeatedly to animate shrinking by the
194   // last few pixels to get to one visible result.
195   if (new_target_bounds.height() != target_bounds_.height())
196     size_animation_.Reset();
197   target_bounds_ = new_target_bounds;
198
199   if (popup_ == NULL) {
200     views::Widget* popup_parent = location_bar_view_->GetWidget();
201
202     // If the popup is currently closed, we need to create it.
203     popup_ = (new AutocompletePopupWidget)->AsWeakPtr();
204     views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
205     params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
206     params.parent = popup_parent->GetNativeView();
207     params.bounds = GetPopupBounds();
208     params.context = popup_parent->GetNativeWindow();
209     popup_->Init(params);
210     // Third-party software such as DigitalPersona identity verification can
211     // hook the underlying window creation methods and use SendMessage to
212     // synchronously change focus/activation, resulting in the popup being
213     // destroyed by the time control returns here.  Bail out in this case to
214     // avoid a NULL dereference.
215     if (!popup_.get())
216       return;
217     wm::SetWindowVisibilityAnimationTransition(
218         popup_->GetNativeView(), wm::ANIMATE_NONE);
219     popup_->SetContentsView(this);
220     popup_->StackAbove(omnibox_view_->GetRelativeWindowForPopup());
221     if (!popup_.get()) {
222       // For some IMEs GetRelativeWindowForPopup triggers the omnibox to lose
223       // focus, thereby closing (and destroying) the popup.
224       // TODO(sky): this won't be needed once we close the omnibox on input
225       // window showing.
226       return;
227     }
228     popup_->ShowInactive();
229   } else {
230     // Animate the popup shrinking, but don't animate growing larger since that
231     // would make the popup feel less responsive.
232     start_bounds_ = GetWidget()->GetWindowBoundsInScreen();
233     if (target_bounds_.height() < start_bounds_.height())
234       size_animation_.Show();
235     else
236       start_bounds_ = target_bounds_;
237     popup_->SetBounds(GetPopupBounds());
238   }
239
240   Layout();
241 }
242
243 gfx::Rect OmniboxPopupContentsView::GetTargetBounds() {
244   return target_bounds_;
245 }
246
247 void OmniboxPopupContentsView::PaintUpdatesNow() {
248   // TODO(beng): remove this from the interface.
249 }
250
251 void OmniboxPopupContentsView::OnDragCanceled() {
252   ignore_mouse_drag_ = true;
253 }
254
255 ////////////////////////////////////////////////////////////////////////////////
256 // OmniboxPopupContentsView, OmniboxResultViewModel implementation:
257
258 bool OmniboxPopupContentsView::IsSelectedIndex(size_t index) const {
259   return index == model_->selected_line();
260 }
261
262 bool OmniboxPopupContentsView::IsHoveredIndex(size_t index) const {
263   return index == model_->hovered_line();
264 }
265
266 gfx::Image OmniboxPopupContentsView::GetIconIfExtensionMatch(
267     size_t index) const {
268   if (!HasMatchAt(index))
269     return gfx::Image();
270   return model_->GetIconIfExtensionMatch(GetMatchAtIndex(index));
271 }
272
273 bool OmniboxPopupContentsView::IsStarredMatch(
274     const AutocompleteMatch& match) const {
275   return model_->IsStarredMatch(match);
276 }
277
278 ////////////////////////////////////////////////////////////////////////////////
279 // OmniboxPopupContentsView, AnimationDelegate implementation:
280
281 void OmniboxPopupContentsView::AnimationProgressed(
282     const gfx::Animation* animation) {
283   // We should only be running the animation when the popup is already visible.
284   DCHECK(popup_ != NULL);
285   popup_->SetBounds(GetPopupBounds());
286 }
287
288 ////////////////////////////////////////////////////////////////////////////////
289 // OmniboxPopupContentsView, views::View overrides:
290
291 void OmniboxPopupContentsView::Layout() {
292   // Size our children to the available content area.
293   LayoutChildren();
294
295   // We need to manually schedule a paint here since we are a layered window and
296   // won't implicitly require painting until we ask for one.
297   SchedulePaint();
298 }
299
300 views::View* OmniboxPopupContentsView::GetTooltipHandlerForPoint(
301     const gfx::Point& point) {
302   return NULL;
303 }
304
305 bool OmniboxPopupContentsView::OnMousePressed(
306     const ui::MouseEvent& event) {
307   ignore_mouse_drag_ = false;  // See comment on |ignore_mouse_drag_| in header.
308   if (event.IsLeftMouseButton() || event.IsMiddleMouseButton())
309     UpdateLineEvent(event, event.IsLeftMouseButton());
310   return true;
311 }
312
313 bool OmniboxPopupContentsView::OnMouseDragged(
314     const ui::MouseEvent& event) {
315   if (event.IsLeftMouseButton() || event.IsMiddleMouseButton())
316     UpdateLineEvent(event, !ignore_mouse_drag_ && event.IsLeftMouseButton());
317   return true;
318 }
319
320 void OmniboxPopupContentsView::OnMouseReleased(
321     const ui::MouseEvent& event) {
322   if (ignore_mouse_drag_) {
323     OnMouseCaptureLost();
324     return;
325   }
326
327   if (event.IsOnlyMiddleMouseButton() || event.IsOnlyLeftMouseButton()) {
328     OpenSelectedLine(event, event.IsOnlyLeftMouseButton() ? CURRENT_TAB :
329                                                             NEW_BACKGROUND_TAB);
330   }
331 }
332
333 void OmniboxPopupContentsView::OnMouseCaptureLost() {
334   ignore_mouse_drag_ = false;
335 }
336
337 void OmniboxPopupContentsView::OnMouseMoved(
338     const ui::MouseEvent& event) {
339   model_->SetHoveredLine(GetIndexForPoint(event.location()));
340 }
341
342 void OmniboxPopupContentsView::OnMouseEntered(
343     const ui::MouseEvent& event) {
344   model_->SetHoveredLine(GetIndexForPoint(event.location()));
345 }
346
347 void OmniboxPopupContentsView::OnMouseExited(
348     const ui::MouseEvent& event) {
349   model_->SetHoveredLine(OmniboxPopupModel::kNoMatch);
350 }
351
352 void OmniboxPopupContentsView::OnGestureEvent(ui::GestureEvent* event) {
353   switch (event->type()) {
354     case ui::ET_GESTURE_TAP_DOWN:
355     case ui::ET_GESTURE_SCROLL_BEGIN:
356     case ui::ET_GESTURE_SCROLL_UPDATE:
357       UpdateLineEvent(*event, true);
358       break;
359     case ui::ET_GESTURE_TAP:
360     case ui::ET_GESTURE_SCROLL_END:
361       OpenSelectedLine(*event, CURRENT_TAB);
362       break;
363     default:
364       return;
365   }
366   event->SetHandled();
367 }
368
369 ////////////////////////////////////////////////////////////////////////////////
370 // OmniboxPopupContentsView, protected:
371
372 void OmniboxPopupContentsView::PaintResultViews(gfx::Canvas* canvas) {
373   canvas->DrawColor(result_view_at(0)->GetColor(
374       OmniboxResultView::NORMAL, OmniboxResultView::BACKGROUND));
375   View::PaintChildren(canvas, views::CullSet());
376 }
377
378 int OmniboxPopupContentsView::CalculatePopupHeight() {
379   DCHECK_GE(static_cast<size_t>(child_count()), model_->result().size());
380   int popup_height = 0;
381   for (size_t i = model_->result().ShouldHideTopMatch() ? 1 : 0;
382        i < model_->result().size(); ++i)
383     popup_height += child_at(i)->GetPreferredSize().height();
384
385   // Add enough space on the top and bottom so it looks like there is the same
386   // amount of space between the text and the popup border as there is in the
387   // interior between each row of text.
388   //
389   // Discovering the exact amount of leading and padding around the font is
390   // a bit tricky and platform-specific, but this computation seems to work in
391   // practice.
392   OmniboxResultView* result_view = result_view_at(0);
393   outside_vertical_padding_ =
394       (result_view->GetPreferredSize().height() -
395        result_view->GetTextHeight());
396
397   return popup_height +
398          views::NonClientFrameView::kClientEdgeThickness +  // Top border.
399          outside_vertical_padding_ * 2 +                    // Padding.
400          bottom_shadow_->height() - kBorderInterior;        // Bottom border.
401 }
402
403 OmniboxResultView* OmniboxPopupContentsView::CreateResultView(
404     int model_index,
405     const gfx::FontList& font_list) {
406   return new OmniboxResultView(this, model_index, location_bar_view_,
407                                font_list);
408 }
409
410 ////////////////////////////////////////////////////////////////////////////////
411 // OmniboxPopupContentsView, views::View overrides, protected:
412
413 void OmniboxPopupContentsView::OnPaint(gfx::Canvas* canvas) {
414   gfx::Rect contents_bounds = GetContentsBounds();
415   contents_bounds.set_height(
416       contents_bounds.height() - bottom_shadow_->height() + kBorderInterior);
417
418   gfx::Path path;
419   MakeContentsPath(&path, contents_bounds);
420   canvas->Save();
421   canvas->sk_canvas()->clipPath(path,
422                                 SkRegion::kIntersect_Op,
423                                 true /* doAntialias */);
424   PaintResultViews(canvas);
425   canvas->Restore();
426
427   // Top border.
428   canvas->FillRect(
429       gfx::Rect(0, 0, width(), views::NonClientFrameView::kClientEdgeThickness),
430       ThemeProperties::GetDefaultColor(
431           ThemeProperties::COLOR_TOOLBAR_SEPARATOR));
432
433   // Bottom border.
434   canvas->TileImageInt(*bottom_shadow_, 0, height() - bottom_shadow_->height(),
435                        width(), bottom_shadow_->height());
436 }
437
438 void OmniboxPopupContentsView::PaintChildren(gfx::Canvas* canvas,
439                                              const views::CullSet& cull_set) {
440   // We paint our children inside OnPaint().
441 }
442
443 ////////////////////////////////////////////////////////////////////////////////
444 // OmniboxPopupContentsView, private:
445
446 views::View* OmniboxPopupContentsView::TargetForRect(views::View* root,
447                                                      const gfx::Rect& rect) {
448   CHECK_EQ(root, this);
449   return this;
450 }
451
452 bool OmniboxPopupContentsView::HasMatchAt(size_t index) const {
453   return index < model_->result().size();
454 }
455
456 const AutocompleteMatch& OmniboxPopupContentsView::GetMatchAtIndex(
457     size_t index) const {
458   return model_->result().match_at(index);
459 }
460
461 void OmniboxPopupContentsView::MakeContentsPath(
462     gfx::Path* path,
463     const gfx::Rect& bounding_rect) {
464   SkRect rect;
465   rect.set(SkIntToScalar(bounding_rect.x()),
466            SkIntToScalar(bounding_rect.y()),
467            SkIntToScalar(bounding_rect.right()),
468            SkIntToScalar(bounding_rect.bottom()));
469   path->addRect(rect);
470 }
471
472 size_t OmniboxPopupContentsView::GetIndexForPoint(
473     const gfx::Point& point) {
474   if (!HitTestPoint(point))
475     return OmniboxPopupModel::kNoMatch;
476
477   int nb_match = model_->result().size();
478   DCHECK(nb_match <= child_count());
479   for (int i = 0; i < nb_match; ++i) {
480     views::View* child = child_at(i);
481     gfx::Point point_in_child_coords(point);
482     View::ConvertPointToTarget(this, child, &point_in_child_coords);
483     if (child->visible() && child->HitTestPoint(point_in_child_coords))
484       return i;
485   }
486   return OmniboxPopupModel::kNoMatch;
487 }
488
489 void OmniboxPopupContentsView::UpdateLineEvent(
490     const ui::LocatedEvent& event,
491     bool should_set_selected_line) {
492   size_t index = GetIndexForPoint(event.location());
493   model_->SetHoveredLine(index);
494   if (HasMatchAt(index) && should_set_selected_line)
495     model_->SetSelectedLine(index, false, false);
496 }
497
498 void OmniboxPopupContentsView::OpenSelectedLine(
499     const ui::LocatedEvent& event,
500     WindowOpenDisposition disposition) {
501   size_t index = GetIndexForPoint(event.location());
502   if (!HasMatchAt(index))
503     return;
504   omnibox_view_->OpenMatch(model_->result().match_at(index), disposition,
505                            GURL(), base::string16(), index);
506 }
507
508 OmniboxResultView* OmniboxPopupContentsView::result_view_at(size_t i) {
509   return static_cast<OmniboxResultView*>(child_at(static_cast<int>(i)));
510 }