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