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/views/mouse_watcher.h"
6
7#include "base/bind.h"
8#include "base/compiler_specific.h"
9#include "base/event_types.h"
10#include "base/memory/weak_ptr.h"
11#include "base/message_loop/message_loop.h"
12#include "ui/base/events/event_constants.h"
13#include "ui/base/events/event_utils.h"
14#include "ui/gfx/screen.h"
15
16namespace views {
17
18// Amount of time between when the mouse moves outside the Host's zone and when
19// the listener is notified.
20const int kNotifyListenerTimeMs = 300;
21
22class MouseWatcher::Observer : public base::MessageLoopForUI::Observer {
23 public:
24  explicit Observer(MouseWatcher* mouse_watcher)
25      : mouse_watcher_(mouse_watcher),
26        notify_listener_factory_(this) {
27    base::MessageLoopForUI::current()->AddObserver(this);
28  }
29
30  virtual ~Observer() {
31    base::MessageLoopForUI::current()->RemoveObserver(this);
32  }
33
34  // MessageLoop::Observer implementation:
35#if defined(OS_WIN)
36  virtual base::EventStatus WillProcessEvent(
37      const base::NativeEvent& event) OVERRIDE {
38    return base::EVENT_CONTINUE;
39  }
40
41  virtual void DidProcessEvent(const base::NativeEvent& event) OVERRIDE {
42    // We spy on three different Windows messages here to see if the mouse has
43    // moved out of the bounds of the current view. The messages are:
44    //
45    // WM_MOUSEMOVE:
46    //   For when the mouse moves from the view into the rest of the browser UI,
47    //   i.e. within the bounds of the same windows HWND.
48    // WM_MOUSELEAVE:
49    //   For when the mouse moves out of the bounds of the view's HWND.
50    // WM_NCMOUSELEAVE:
51    //   For notification when the mouse leaves the _non-client_ area.
52    //
53    switch (event.message) {
54      case WM_MOUSEMOVE:
55        HandleGlobalMouseMoveEvent(MouseWatcherHost::MOUSE_MOVE);
56        break;
57      case WM_MOUSELEAVE:
58      case WM_NCMOUSELEAVE:
59        HandleGlobalMouseMoveEvent(MouseWatcherHost::MOUSE_EXIT);
60        break;
61    }
62  }
63#elif defined(USE_AURA)
64  virtual base::EventStatus WillProcessEvent(
65      const base::NativeEvent& event) OVERRIDE {
66    return base::EVENT_CONTINUE;
67  }
68  virtual void DidProcessEvent(const base::NativeEvent& event) OVERRIDE {
69    switch (ui::EventTypeFromNative(event)) {
70      case ui::ET_MOUSE_MOVED:
71      case ui::ET_MOUSE_DRAGGED:
72        // DRAGGED is a special case of MOVED. See events_win.cc/events_x.cc.
73        HandleGlobalMouseMoveEvent(MouseWatcherHost::MOUSE_MOVE);
74        break;
75      case ui::ET_MOUSE_EXITED:
76        HandleGlobalMouseMoveEvent(MouseWatcherHost::MOUSE_EXIT);
77        break;
78      default:
79        break;
80    }
81  }
82#endif
83
84 private:
85  MouseWatcherHost* host() const { return mouse_watcher_->host_.get(); }
86
87  // Called from the message loop observer when a mouse movement has occurred.
88  void HandleGlobalMouseMoveEvent(MouseWatcherHost::MouseEventType event_type) {
89    bool contained = host()->Contains(
90        // TODO(scottmg): Native is wrong http://crbug.com/133312
91        gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(),
92        event_type);
93    if (!contained) {
94      // Mouse moved outside the host's zone, start a timer to notify the
95      // listener.
96      if (!notify_listener_factory_.HasWeakPtrs()) {
97        base::MessageLoop::current()->PostDelayedTask(
98            FROM_HERE,
99            base::Bind(&Observer::NotifyListener,
100                       notify_listener_factory_.GetWeakPtr()),
101            event_type == MouseWatcherHost::MOUSE_MOVE
102                ? base::TimeDelta::FromMilliseconds(kNotifyListenerTimeMs)
103                : mouse_watcher_->notify_on_exit_time_);
104      }
105    } else {
106      // Mouse moved quickly out of the host and then into it again, so cancel
107      // the timer.
108      notify_listener_factory_.InvalidateWeakPtrs();
109    }
110  }
111
112  void NotifyListener() {
113    mouse_watcher_->NotifyListener();
114    // WARNING: we've been deleted.
115  }
116
117 private:
118  MouseWatcher* mouse_watcher_;
119
120  // A factory that is used to construct a delayed callback to the listener.
121  base::WeakPtrFactory<Observer> notify_listener_factory_;
122
123  DISALLOW_COPY_AND_ASSIGN(Observer);
124};
125
126MouseWatcherListener::~MouseWatcherListener() {
127}
128
129MouseWatcherHost::~MouseWatcherHost() {
130}
131
132MouseWatcher::MouseWatcher(MouseWatcherHost* host,
133                           MouseWatcherListener* listener)
134    : host_(host),
135      listener_(listener),
136      notify_on_exit_time_(base::TimeDelta::FromMilliseconds(
137          kNotifyListenerTimeMs)) {
138}
139
140MouseWatcher::~MouseWatcher() {
141}
142
143void MouseWatcher::Start() {
144  if (!is_observing())
145    observer_.reset(new Observer(this));
146}
147
148void MouseWatcher::Stop() {
149  observer_.reset(NULL);
150}
151
152void MouseWatcher::NotifyListener() {
153  observer_.reset(NULL);
154  listener_->MouseMovedOutOfHost();
155}
156
157}  // namespace views
158