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 "ash/magnifier/partial_magnification_controller.h"
6
7#include "ash/shell.h"
8#include "ash/shell_window_ids.h"
9#include "ui/aura/window.h"
10#include "ui/aura/window_event_dispatcher.h"
11#include "ui/aura/window_property.h"
12#include "ui/aura/window_tree_host.h"
13#include "ui/gfx/screen.h"
14#include "ui/compositor/layer.h"
15#include "ui/views/layout/fill_layout.h"
16#include "ui/views/widget/widget.h"
17#include "ui/views/widget/widget_delegate.h"
18#include "ui/wm/core/compound_event_filter.h"
19
20namespace {
21
22const float kMinPartialMagnifiedScaleThreshold = 1.1f;
23
24// Number of pixels to make the border of the magnified area.
25const int kZoomInset = 16;
26
27// Width of the magnified area.
28const int kMagnifierWidth = 200;
29
30// Height of the magnified area.
31const int kMagnifierHeight = 200;
32
33// Name of the magnifier window.
34const char kPartialMagniferWindowName[] = "PartialMagnifierWindow";
35
36}  // namespace
37
38namespace ash {
39
40PartialMagnificationController::PartialMagnificationController()
41    : is_on_zooming_(false),
42      is_enabled_(false),
43      scale_(kNonPartialMagnifiedScale),
44      zoom_widget_(NULL) {
45  Shell::GetInstance()->AddPreTargetHandler(this);
46}
47
48PartialMagnificationController::~PartialMagnificationController() {
49  CloseMagnifierWindow();
50
51  Shell::GetInstance()->RemovePreTargetHandler(this);
52}
53
54void PartialMagnificationController::SetScale(float scale) {
55  if (!is_enabled_)
56    return;
57
58  scale_ = scale;
59
60  if (IsPartialMagnified()) {
61    CreateMagnifierWindow();
62  } else {
63    CloseMagnifierWindow();
64  }
65}
66
67void PartialMagnificationController::SetEnabled(bool enabled) {
68  if (enabled) {
69    is_enabled_ = enabled;
70    SetScale(kDefaultPartialMagnifiedScale);
71  } else {
72    SetScale(kNonPartialMagnifiedScale);
73    is_enabled_ = enabled;
74  }
75}
76
77////////////////////////////////////////////////////////////////////////////////
78// PartialMagnificationController: ui::EventHandler implementation
79
80void PartialMagnificationController::OnMouseEvent(ui::MouseEvent* event) {
81  if (IsPartialMagnified() && event->type() == ui::ET_MOUSE_MOVED) {
82    aura::Window* target = static_cast<aura::Window*>(event->target());
83    aura::Window* current_root = target->GetRootWindow();
84    // TODO(zork): Handle the case where the event is captured on a different
85    // display, such as when a menu is opened.
86    gfx::Rect root_bounds = current_root->bounds();
87
88    if (root_bounds.Contains(event->root_location())) {
89      SwitchTargetRootWindow(current_root);
90
91      OnMouseMove(event->root_location());
92    }
93  }
94}
95
96////////////////////////////////////////////////////////////////////////////////
97// PartialMagnificationController: aura::WindowObserver implementation
98
99void PartialMagnificationController::OnWindowDestroying(
100    aura::Window* window) {
101  CloseMagnifierWindow();
102
103  aura::Window* new_root_window = GetCurrentRootWindow();
104  if (new_root_window != window)
105    SwitchTargetRootWindow(new_root_window);
106}
107
108void PartialMagnificationController::OnWidgetDestroying(
109    views::Widget* widget) {
110  DCHECK_EQ(widget, zoom_widget_);
111  RemoveZoomWidgetObservers();
112  zoom_widget_ = NULL;
113}
114
115void PartialMagnificationController::OnMouseMove(
116    const gfx::Point& location_in_root) {
117  gfx::Point origin(location_in_root);
118
119  origin.Offset(-kMagnifierWidth / 2, -kMagnifierHeight / 2);
120
121  if (zoom_widget_) {
122    zoom_widget_->SetBounds(gfx::Rect(origin.x(), origin.y(),
123                                      kMagnifierWidth, kMagnifierHeight));
124  }
125}
126
127bool PartialMagnificationController::IsPartialMagnified() const {
128  return scale_ >= kMinPartialMagnifiedScaleThreshold;
129}
130
131void PartialMagnificationController::CreateMagnifierWindow() {
132  if (zoom_widget_)
133    return;
134
135  aura::Window* root_window = GetCurrentRootWindow();
136  if (!root_window)
137    return;
138
139  root_window->AddObserver(this);
140
141  gfx::Point mouse(
142      root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot());
143
144  zoom_widget_ = new views::Widget;
145  views::Widget::InitParams params(
146      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
147  params.activatable = views::Widget::InitParams::ACTIVATABLE_NO;
148  params.accept_events = false;
149  params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
150  params.parent = root_window;
151  zoom_widget_->Init(params);
152  zoom_widget_->SetBounds(gfx::Rect(mouse.x() - kMagnifierWidth / 2,
153                                    mouse.y() - kMagnifierHeight / 2,
154                                    kMagnifierWidth, kMagnifierHeight));
155  zoom_widget_->set_focus_on_creation(false);
156  zoom_widget_->Show();
157
158  aura::Window* window = zoom_widget_->GetNativeView();
159  window->SetName(kPartialMagniferWindowName);
160
161  zoom_widget_->GetNativeView()->layer()->SetBounds(
162      gfx::Rect(0, 0,
163                kMagnifierWidth,
164                kMagnifierHeight));
165  zoom_widget_->GetNativeView()->layer()->SetBackgroundZoom(
166      scale_,
167      kZoomInset);
168
169  zoom_widget_->AddObserver(this);
170}
171
172void PartialMagnificationController::CloseMagnifierWindow() {
173  if (zoom_widget_) {
174    RemoveZoomWidgetObservers();
175    zoom_widget_->Close();
176    zoom_widget_ = NULL;
177  }
178}
179
180void PartialMagnificationController::RemoveZoomWidgetObservers() {
181  DCHECK(zoom_widget_);
182  zoom_widget_->RemoveObserver(this);
183  aura::Window* root_window =
184      zoom_widget_->GetNativeView()->GetRootWindow();
185  DCHECK(root_window);
186  root_window->RemoveObserver(this);
187}
188
189void PartialMagnificationController::SwitchTargetRootWindow(
190    aura::Window* new_root_window) {
191  if (zoom_widget_ &&
192      new_root_window == zoom_widget_->GetNativeView()->GetRootWindow())
193    return;
194
195  CloseMagnifierWindow();
196
197  // Recreate the magnifier window by updating the scale factor.
198  SetScale(GetScale());
199}
200
201aura::Window* PartialMagnificationController::GetCurrentRootWindow() {
202  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
203  for (aura::Window::Windows::const_iterator iter = root_windows.begin();
204       iter != root_windows.end(); ++iter) {
205    aura::Window* root_window = *iter;
206    if (root_window->ContainsPointInRoot(
207            root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot()))
208      return root_window;
209  }
210  return NULL;
211}
212
213}  // namespace ash
214