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 "ui/wm/core/compound_event_filter.h"
6
7#include "base/containers/hash_tables.h"
8#include "base/logging.h"
9#include "ui/aura/client/cursor_client.h"
10#include "ui/aura/env.h"
11#include "ui/aura/window.h"
12#include "ui/aura/window_delegate.h"
13#include "ui/aura/window_event_dispatcher.h"
14#include "ui/aura/window_tracker.h"
15#include "ui/base/hit_test.h"
16#include "ui/events/event.h"
17#include "ui/wm/public/activation_client.h"
18#include "ui/wm/public/drag_drop_client.h"
19
20#if defined(OS_CHROMEOS) && defined(USE_X11)
21#include "ui/events/x/touch_factory_x11.h"
22#endif
23
24namespace wm {
25
26namespace {
27
28// Returns true if the cursor should be hidden on touch events.
29// TODO(tdanderson|rsadam): Move this function into CursorClient.
30bool ShouldHideCursorOnTouch(const ui::TouchEvent& event) {
31#if defined(OS_WIN)
32  return true;
33#elif defined(OS_CHROMEOS)
34#if defined(USE_X11)
35  int device_id = event.source_device_id();
36  if (device_id >= 0 &&
37      !ui::TouchFactory::GetInstance()->IsMultiTouchDevice(device_id)) {
38    // If the touch event is coming from a mouse-device (i.e. not a real
39    // touch-device), then do not hide the cursor.
40    return false;
41  }
42#endif  // defined(USE_X11)
43  return true;
44#else
45  // Linux Aura does not hide the cursor on touch by default.
46  // TODO(tdanderson): Change this if having consistency across
47  // all platforms which use Aura is desired.
48  return false;
49#endif
50}
51
52}  // namespace
53
54////////////////////////////////////////////////////////////////////////////////
55// CompoundEventFilter, public:
56
57CompoundEventFilter::CompoundEventFilter() {
58}
59
60CompoundEventFilter::~CompoundEventFilter() {
61  // Additional filters are not owned by CompoundEventFilter and they
62  // should all be removed when running here. |handlers_| has
63  // check_empty == true and will DCHECK failure if it is not empty.
64}
65
66// static
67gfx::NativeCursor CompoundEventFilter::CursorForWindowComponent(
68    int window_component) {
69  switch (window_component) {
70    case HTBOTTOM:
71      return ui::kCursorSouthResize;
72    case HTBOTTOMLEFT:
73      return ui::kCursorSouthWestResize;
74    case HTBOTTOMRIGHT:
75      return ui::kCursorSouthEastResize;
76    case HTLEFT:
77      return ui::kCursorWestResize;
78    case HTRIGHT:
79      return ui::kCursorEastResize;
80    case HTTOP:
81      return ui::kCursorNorthResize;
82    case HTTOPLEFT:
83      return ui::kCursorNorthWestResize;
84    case HTTOPRIGHT:
85      return ui::kCursorNorthEastResize;
86    default:
87      return ui::kCursorNull;
88  }
89}
90
91void CompoundEventFilter::AddHandler(ui::EventHandler* handler) {
92  handlers_.AddObserver(handler);
93}
94
95void CompoundEventFilter::RemoveHandler(ui::EventHandler* handler) {
96  handlers_.RemoveObserver(handler);
97}
98
99////////////////////////////////////////////////////////////////////////////////
100// CompoundEventFilter, private:
101
102void CompoundEventFilter::UpdateCursor(aura::Window* target,
103                                       ui::MouseEvent* event) {
104  // If drag and drop is in progress, let the drag drop client set the cursor
105  // instead of setting the cursor here.
106  aura::Window* root_window = target->GetRootWindow();
107  aura::client::DragDropClient* drag_drop_client =
108      aura::client::GetDragDropClient(root_window);
109  if (drag_drop_client && drag_drop_client->IsDragDropInProgress())
110    return;
111
112  aura::client::CursorClient* cursor_client =
113      aura::client::GetCursorClient(root_window);
114  if (cursor_client) {
115    gfx::NativeCursor cursor = target->GetCursor(event->location());
116    if ((event->flags() & ui::EF_IS_NON_CLIENT)) {
117      if (target->delegate()) {
118        int window_component =
119            target->delegate()->GetNonClientComponent(event->location());
120        cursor = CursorForWindowComponent(window_component);
121      } else {
122        // Allow the OS to handle non client cursors if we don't have a
123        // a delegate to handle the non client hittest.
124        return;
125      }
126    }
127    cursor_client->SetCursor(cursor);
128  }
129}
130
131void CompoundEventFilter::FilterKeyEvent(ui::KeyEvent* event) {
132  if (handlers_.might_have_observers()) {
133    ObserverListBase<ui::EventHandler>::Iterator it(handlers_);
134    ui::EventHandler* handler;
135    while (!event->stopped_propagation() && (handler = it.GetNext()) != NULL)
136      handler->OnKeyEvent(event);
137  }
138}
139
140void CompoundEventFilter::FilterMouseEvent(ui::MouseEvent* event) {
141  if (handlers_.might_have_observers()) {
142    ObserverListBase<ui::EventHandler>::Iterator it(handlers_);
143    ui::EventHandler* handler;
144    while (!event->stopped_propagation() && (handler = it.GetNext()) != NULL)
145      handler->OnMouseEvent(event);
146  }
147}
148
149void CompoundEventFilter::FilterTouchEvent(ui::TouchEvent* event) {
150  if (handlers_.might_have_observers()) {
151    ObserverListBase<ui::EventHandler>::Iterator it(handlers_);
152    ui::EventHandler* handler;
153    while (!event->stopped_propagation() && (handler = it.GetNext()) != NULL)
154      handler->OnTouchEvent(event);
155  }
156}
157
158void CompoundEventFilter::SetCursorVisibilityOnEvent(aura::Window* target,
159                                                     ui::Event* event,
160                                                     bool show) {
161  if (event->flags() & ui::EF_IS_SYNTHESIZED)
162    return;
163
164  aura::client::CursorClient* client =
165      aura::client::GetCursorClient(target->GetRootWindow());
166  if (!client)
167    return;
168
169  if (show)
170    client->ShowCursor();
171  else
172    client->HideCursor();
173}
174
175void CompoundEventFilter::SetMouseEventsEnableStateOnEvent(aura::Window* target,
176                                                           ui::Event* event,
177                                                           bool enable) {
178  if (event->flags() & ui::EF_IS_SYNTHESIZED)
179    return;
180  aura::client::CursorClient* client =
181      aura::client::GetCursorClient(target->GetRootWindow());
182  if (!client)
183    return;
184
185  if (enable)
186    client->EnableMouseEvents();
187  else
188    client->DisableMouseEvents();
189}
190
191////////////////////////////////////////////////////////////////////////////////
192// CompoundEventFilter, ui::EventHandler implementation:
193
194void CompoundEventFilter::OnKeyEvent(ui::KeyEvent* event) {
195  aura::Window* target = static_cast<aura::Window*>(event->target());
196  aura::client::CursorClient* client =
197      aura::client::GetCursorClient(target->GetRootWindow());
198  if (client && client->ShouldHideCursorOnKeyEvent(*event))
199    SetCursorVisibilityOnEvent(target, event, false);
200
201  FilterKeyEvent(event);
202}
203
204void CompoundEventFilter::OnMouseEvent(ui::MouseEvent* event) {
205  aura::Window* window = static_cast<aura::Window*>(event->target());
206  aura::WindowTracker window_tracker;
207  window_tracker.Add(window);
208
209  // We must always update the cursor, otherwise the cursor can get stuck if an
210  // event filter registered with us consumes the event.
211  // It should also update the cursor for clicking and wheels for ChromeOS boot.
212  // When ChromeOS is booted, it hides the mouse cursor but immediate mouse
213  // operation will show the cursor.
214  // We also update the cursor for mouse enter in case a mouse cursor is sent to
215  // outside of the root window and moved back for some reasons (e.g. running on
216  // on Desktop for testing, or a bug in pointer barrier).
217  if (!(event->flags() & ui::EF_FROM_TOUCH) &&
218       (event->type() == ui::ET_MOUSE_ENTERED ||
219        event->type() == ui::ET_MOUSE_MOVED ||
220        event->type() == ui::ET_MOUSE_PRESSED ||
221        event->type() == ui::ET_MOUSEWHEEL)) {
222    SetMouseEventsEnableStateOnEvent(window, event, true);
223    SetCursorVisibilityOnEvent(window, event, true);
224    UpdateCursor(window, event);
225  }
226
227  FilterMouseEvent(event);
228}
229
230void CompoundEventFilter::OnScrollEvent(ui::ScrollEvent* event) {
231}
232
233void CompoundEventFilter::OnTouchEvent(ui::TouchEvent* event) {
234  FilterTouchEvent(event);
235  if (!event->handled() && event->type() == ui::ET_TOUCH_PRESSED &&
236      ShouldHideCursorOnTouch(*event) &&
237      !aura::Env::GetInstance()->IsMouseButtonDown()) {
238    SetMouseEventsEnableStateOnEvent(
239        static_cast<aura::Window*>(event->target()), event, false);
240  }
241}
242
243void CompoundEventFilter::OnGestureEvent(ui::GestureEvent* event) {
244  if (handlers_.might_have_observers()) {
245    ObserverListBase<ui::EventHandler>::Iterator it(handlers_);
246    ui::EventHandler* handler;
247    while (!event->stopped_propagation() && (handler = it.GetNext()) != NULL)
248      handler->OnGestureEvent(event);
249  }
250
251#if defined(OS_WIN)
252  // A Win8 edge swipe event is a special event that does not have location
253  // information associated with it, and is not preceeded by an ET_TOUCH_PRESSED
254  // event.  So we treat it specially here.
255  if (!event->handled() && event->type() == ui::ET_GESTURE_WIN8_EDGE_SWIPE &&
256      !aura::Env::GetInstance()->IsMouseButtonDown()) {
257    SetMouseEventsEnableStateOnEvent(
258        static_cast<aura::Window*>(event->target()), event, false);
259  }
260#endif
261}
262
263}  // namespace wm
264