1// Copyright (c) 2014 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/link_disambiguation/link_disambiguation_popup.h"
6
7#include "ui/aura/client/screen_position_client.h"
8#include "ui/events/event.h"
9#include "ui/events/event_processor.h"
10#include "ui/events/event_utils.h"
11#include "ui/events/gesture_event_details.h"
12#include "ui/gfx/display.h"
13#include "ui/gfx/image/image.h"
14#include "ui/gfx/image/image_skia.h"
15#include "ui/gfx/screen.h"
16#include "ui/views/bubble/bubble_delegate.h"
17#include "ui/views/controls/image_view.h"
18
19class LinkDisambiguationPopup::ZoomBubbleView
20    : public views::BubbleDelegateView {
21 public:
22  ZoomBubbleView(const gfx::Rect& target_rect,
23                 const gfx::ImageSkia* zoomed_skia_image,
24                 const aura::Window* content,
25                 LinkDisambiguationPopup* popup,
26                 const base::Callback<void(ui::GestureEvent*)>& gesture_cb,
27                 const base::Callback<void(ui::MouseEvent*)>& mouse_cb);
28
29  void Close();
30
31 private:
32  // views::View overrides
33  virtual gfx::Size GetPreferredSize() const OVERRIDE;
34  virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
35  virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
36
37  // WidgetObserver overrides
38  virtual void OnWidgetClosing(views::Widget* widget) OVERRIDE;
39
40  const float scale_;
41  const aura::Window* content_;
42  const base::Callback<void(ui::GestureEvent*)> gesture_cb_;
43  const base::Callback<void(ui::MouseEvent*)> mouse_cb_;
44  LinkDisambiguationPopup* popup_;
45  const gfx::Rect target_rect_;
46
47  DISALLOW_COPY_AND_ASSIGN(ZoomBubbleView);
48};
49
50LinkDisambiguationPopup::ZoomBubbleView::ZoomBubbleView(
51    const gfx::Rect& target_rect,
52    const gfx::ImageSkia* zoomed_skia_image,
53    const aura::Window* content,
54    LinkDisambiguationPopup* popup,
55    const base::Callback<void(ui::GestureEvent*)>& gesture_cb,
56    const base::Callback<void(ui::MouseEvent*)>& mouse_cb)
57    : BubbleDelegateView(NULL, views::BubbleBorder::FLOAT),
58      scale_(static_cast<float>(zoomed_skia_image->width()) /
59          static_cast<float>(target_rect.width())),
60      content_(content),
61      gesture_cb_(gesture_cb),
62      mouse_cb_(mouse_cb),
63      popup_(popup),
64      target_rect_(target_rect) {
65  views::ImageView* image_view = new views::ImageView();
66  image_view->SetBounds(
67      0, 0, zoomed_skia_image->width(), zoomed_skia_image->height());
68  image_view->SetImage(zoomed_skia_image);
69
70  AddChildView(image_view);
71
72  views::BubbleDelegateView::CreateBubble(this);
73}
74
75void LinkDisambiguationPopup::ZoomBubbleView::Close() {
76  if (GetWidget())
77    GetWidget()->Close();
78}
79
80gfx::Size LinkDisambiguationPopup::ZoomBubbleView::GetPreferredSize() const {
81  return target_rect_.size();
82}
83
84void LinkDisambiguationPopup::ZoomBubbleView::OnMouseEvent(
85    ui::MouseEvent* event) {
86  // Transform mouse event back to coordinate system of the web content window
87  // before providing to the callback.
88  gfx::PointF xform_location(
89      (event->location().x() / scale_) + target_rect_.x(),
90      (event->location().y() / scale_) + target_rect_.y());
91  ui::MouseEvent xform_event(event->type(), xform_location, xform_location,
92      event->flags(), event->changed_button_flags());
93  mouse_cb_.Run(&xform_event);
94  event->SetHandled();
95
96  // If user completed a click we can close the window.
97  if (event->type() == ui::EventType::ET_MOUSE_RELEASED)
98    Close();
99}
100
101void LinkDisambiguationPopup::ZoomBubbleView::OnGestureEvent(
102    ui::GestureEvent* event) {
103  // If we receive gesture events that are outside of our bounds we close
104  // ourselves, as perhaps the user has decided on a different part of the page.
105  if (event->location().x() > bounds().width() ||
106      event->location().y() > bounds().height()) {
107    Close();
108    return;
109  }
110
111  // Scale the gesture event back to the size of the original |target_rect_|,
112  // and then offset it to be relative to that |target_rect_| before sending
113  // it back to the callback.
114  gfx::PointF xform_location(
115      (event->location().x() / scale_) + target_rect_.x(),
116      (event->location().y() / scale_) + target_rect_.y());
117  ui::GestureEventDetails xform_details(event->details());
118  xform_details.set_bounding_box(gfx::RectF(
119      (event->details().bounding_box().x() / scale_) + target_rect_.x(),
120      (event->details().bounding_box().y() / scale_) + target_rect_.y(),
121      event->details().bounding_box().width() / scale_,
122      event->details().bounding_box().height() / scale_));
123  ui::GestureEvent xform_event(xform_location.x(),
124                               xform_location.y(),
125                               event->flags(),
126                               event->time_stamp(),
127                               xform_details);
128  gesture_cb_.Run(&xform_event);
129  event->SetHandled();
130
131  // If we completed a tap we close ourselves, as the web content will navigate
132  // if the user hit a link.
133  if (event->type() == ui::EventType::ET_GESTURE_TAP)
134    Close();
135}
136
137void LinkDisambiguationPopup::ZoomBubbleView::OnWidgetClosing(
138    views::Widget* widget) {
139  popup_->InvalidateBubbleView();
140}
141
142LinkDisambiguationPopup::LinkDisambiguationPopup()
143    : content_(NULL),
144      view_(NULL) {
145}
146
147LinkDisambiguationPopup::~LinkDisambiguationPopup() {
148  Close();
149}
150
151void LinkDisambiguationPopup::Show(
152    const SkBitmap& zoomed_bitmap,
153    const gfx::Rect& target_rect,
154    const gfx::NativeView content,
155    const base::Callback<void(ui::GestureEvent*)>& gesture_cb,
156    const base::Callback<void(ui::MouseEvent*)>& mouse_cb) {
157  content_ = content;
158
159  view_ = new ZoomBubbleView(
160      target_rect,
161      gfx::Image::CreateFrom1xBitmap(zoomed_bitmap).ToImageSkia(),
162      content_,
163      this,
164      gesture_cb,
165      mouse_cb);
166
167  // Center the zoomed bubble over the target rectangle, constrained to the
168  // work area in the current display. Since |target_rect| is provided in
169  // |content_| coordinate system, we must convert it into Screen coordinates
170  // for correct window positioning.
171  aura::client::ScreenPositionClient* screen_position_client =
172      aura::client::GetScreenPositionClient(content_->GetRootWindow());
173  gfx::Point target_screen(target_rect.x() + (target_rect.width() / 2),
174      target_rect.y() + (target_rect.height() / 2));
175  if (screen_position_client)
176    screen_position_client->ConvertPointToScreen(content_, &target_screen);
177  gfx::Rect window_bounds(
178      target_screen.x() - (zoomed_bitmap.width() / 2),
179      target_screen.y() - (zoomed_bitmap.height() / 2),
180      zoomed_bitmap.width(),
181      zoomed_bitmap.height());
182  const gfx::Display display =
183      gfx::Screen::GetScreenFor(content)->GetDisplayNearestWindow(content);
184  window_bounds.AdjustToFit(display.work_area());
185  view_->GetWidget()->SetBounds(window_bounds);
186  view_->GetWidget()->Show();
187}
188
189void LinkDisambiguationPopup::Close() {
190  if (view_) {
191    view_->Close();
192    view_ = NULL;
193  }
194}
195
196void LinkDisambiguationPopup::InvalidateBubbleView() {
197  view_ = NULL;
198}
199