| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/views/mouse_watcher.h" |
| |
| #include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "base/event_types.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/message_loop/message_loop.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/gfx/screen.h" |
| |
| #if defined(USE_AURA) |
| #include "ui/aura/env.h" |
| #include "ui/aura/window.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_handler.h" |
| #endif |
| |
| namespace views { |
| |
| // Amount of time between when the mouse moves outside the Host's zone and when |
| // the listener is notified. |
| const int kNotifyListenerTimeMs = 300; |
| |
| #if defined(OS_WIN) && !defined(USE_AURA) |
| class MouseWatcher::Observer : public base::MessageLoopForUI::Observer { |
| public: |
| explicit Observer(MouseWatcher* mouse_watcher) |
| : mouse_watcher_(mouse_watcher), |
| notify_listener_factory_(this) { |
| base::MessageLoopForUI::current()->AddObserver(this); |
| } |
| |
| virtual ~Observer() { |
| base::MessageLoopForUI::current()->RemoveObserver(this); |
| } |
| |
| // MessageLoop::Observer implementation: |
| virtual base::EventStatus WillProcessEvent( |
| const base::NativeEvent& event) OVERRIDE { |
| return base::EVENT_CONTINUE; |
| } |
| |
| virtual void DidProcessEvent(const base::NativeEvent& event) OVERRIDE { |
| // We spy on three different Windows messages here to see if the mouse has |
| // moved out of the bounds of the current view. The messages are: |
| // |
| // WM_MOUSEMOVE: |
| // For when the mouse moves from the view into the rest of the browser UI, |
| // i.e. within the bounds of the same windows HWND. |
| // WM_MOUSELEAVE: |
| // For when the mouse moves out of the bounds of the view's HWND. |
| // WM_NCMOUSELEAVE: |
| // For notification when the mouse leaves the _non-client_ area. |
| // |
| switch (event.message) { |
| case WM_MOUSEMOVE: |
| HandleGlobalMouseMoveEvent(MouseWatcherHost::MOUSE_MOVE); |
| break; |
| case WM_MOUSELEAVE: |
| case WM_NCMOUSELEAVE: |
| HandleGlobalMouseMoveEvent(MouseWatcherHost::MOUSE_EXIT); |
| break; |
| } |
| } |
| |
| private: |
| MouseWatcherHost* host() const { return mouse_watcher_->host_.get(); } |
| |
| // Called from the message loop observer when a mouse movement has occurred. |
| void HandleGlobalMouseMoveEvent(MouseWatcherHost::MouseEventType event_type) { |
| bool contained = host()->Contains( |
| // TODO(scottmg): Native is wrong http://crbug.com/133312 |
| gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(), |
| event_type); |
| if (!contained) { |
| // Mouse moved outside the host's zone, start a timer to notify the |
| // listener. |
| if (!notify_listener_factory_.HasWeakPtrs()) { |
| base::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&Observer::NotifyListener, |
| notify_listener_factory_.GetWeakPtr()), |
| event_type == MouseWatcherHost::MOUSE_MOVE |
| ? base::TimeDelta::FromMilliseconds(kNotifyListenerTimeMs) |
| : mouse_watcher_->notify_on_exit_time_); |
| } |
| } else { |
| // Mouse moved quickly out of the host and then into it again, so cancel |
| // the timer. |
| notify_listener_factory_.InvalidateWeakPtrs(); |
| } |
| } |
| |
| void NotifyListener() { |
| mouse_watcher_->NotifyListener(); |
| // WARNING: we've been deleted. |
| } |
| |
| private: |
| MouseWatcher* mouse_watcher_; |
| |
| // A factory that is used to construct a delayed callback to the listener. |
| base::WeakPtrFactory<Observer> notify_listener_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Observer); |
| }; |
| #else |
| class MouseWatcher::Observer : public ui::EventHandler { |
| public: |
| explicit Observer(MouseWatcher* mouse_watcher) |
| : mouse_watcher_(mouse_watcher), |
| notify_listener_factory_(this) { |
| aura::Env::GetInstance()->AddPreTargetHandler(this); |
| } |
| |
| virtual ~Observer() { |
| aura::Env::GetInstance()->RemovePreTargetHandler(this); |
| } |
| |
| // ui::EventHandler implementation: |
| virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE { |
| switch (event->type()) { |
| case ui::ET_MOUSE_MOVED: |
| case ui::ET_MOUSE_DRAGGED: |
| HandleMouseEvent(MouseWatcherHost::MOUSE_MOVE); |
| break; |
| case ui::ET_MOUSE_EXITED: |
| HandleMouseEvent(MouseWatcherHost::MOUSE_EXIT); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| private: |
| MouseWatcherHost* host() const { return mouse_watcher_->host_.get(); } |
| |
| // Called when a mouse event we're interested is seen. |
| void HandleMouseEvent(MouseWatcherHost::MouseEventType event_type) { |
| // It's safe to use last_mouse_location() here as this function is invoked |
| // during event dispatching. |
| if (!host()->Contains(aura::Env::GetInstance()->last_mouse_location(), |
| event_type)) { |
| // Mouse moved outside the host's zone, start a timer to notify the |
| // listener. |
| if (!notify_listener_factory_.HasWeakPtrs()) { |
| base::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&Observer::NotifyListener, |
| notify_listener_factory_.GetWeakPtr()), |
| event_type == MouseWatcherHost::MOUSE_MOVE |
| ? base::TimeDelta::FromMilliseconds(kNotifyListenerTimeMs) |
| : mouse_watcher_->notify_on_exit_time_); |
| } |
| } else { |
| // Mouse moved quickly out of the host and then into it again, so cancel |
| // the timer. |
| notify_listener_factory_.InvalidateWeakPtrs(); |
| } |
| } |
| |
| void NotifyListener() { |
| mouse_watcher_->NotifyListener(); |
| // WARNING: we've been deleted. |
| } |
| |
| private: |
| MouseWatcher* mouse_watcher_; |
| |
| // A factory that is used to construct a delayed callback to the listener. |
| base::WeakPtrFactory<Observer> notify_listener_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Observer); |
| }; |
| #endif |
| |
| MouseWatcherListener::~MouseWatcherListener() { |
| } |
| |
| MouseWatcherHost::~MouseWatcherHost() { |
| } |
| |
| MouseWatcher::MouseWatcher(MouseWatcherHost* host, |
| MouseWatcherListener* listener) |
| : host_(host), |
| listener_(listener), |
| notify_on_exit_time_(base::TimeDelta::FromMilliseconds( |
| kNotifyListenerTimeMs)) { |
| } |
| |
| MouseWatcher::~MouseWatcher() { |
| } |
| |
| void MouseWatcher::Start() { |
| if (!is_observing()) |
| observer_.reset(new Observer(this)); |
| } |
| |
| void MouseWatcher::Stop() { |
| observer_.reset(NULL); |
| } |
| |
| void MouseWatcher::NotifyListener() { |
| observer_.reset(NULL); |
| listener_->MouseMovedOutOfHost(); |
| } |
| |
| } // namespace views |