blob: 09aaa8a68f42d2fa30ded4d5013ee27fc32ef90d [file] [log] [blame]
// Copyright (c) 2010 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 "views/mouse_watcher.h"
#include "base/compiler_specific.h"
#include "base/message_loop.h"
#include "base/task.h"
#include "views/screen.h"
#include "views/view.h"
#include "views/widget/widget.h"
namespace views {
// Amount of time between when the mouse moves outside the view's zone and when
// the listener is notified.
static const int kNotifyListenerTimeMs = 300;
class MouseWatcher::Observer : public MessageLoopForUI::Observer {
public:
explicit Observer(MouseWatcher* mouse_watcher)
: mouse_watcher_(mouse_watcher),
ALLOW_THIS_IN_INITIALIZER_LIST(notify_listener_factory_(this)) {
MessageLoopForUI::current()->AddObserver(this);
}
~Observer() {
MessageLoopForUI::current()->RemoveObserver(this);
}
// MessageLoop::Observer implementation:
#if defined(OS_WIN)
void WillProcessMessage(const MSG& msg) {
}
void DidProcessMessage(const MSG& msg) {
// We spy on three different Windows messages here to see if the mouse has
// moved out of the bounds of the 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 (msg.message) {
case WM_MOUSEMOVE:
HandleGlobalMouseMoveEvent(false);
break;
case WM_MOUSELEAVE:
case WM_NCMOUSELEAVE:
HandleGlobalMouseMoveEvent(true);
break;
}
}
#else
void WillProcessEvent(GdkEvent* event) {
}
void DidProcessEvent(GdkEvent* event) {
switch (event->type) {
case GDK_MOTION_NOTIFY:
HandleGlobalMouseMoveEvent(false);
break;
case GDK_LEAVE_NOTIFY:
HandleGlobalMouseMoveEvent(true);
break;
default:
break;
}
}
#endif
private:
View* view() const { return mouse_watcher_->host_; }
// Returns whether or not the cursor is currently in the view's "zone" which
// is defined as a slightly larger region than the view.
bool IsCursorInViewZone() {
gfx::Rect bounds = view()->GetLocalBounds();
gfx::Point view_topleft(bounds.origin());
View::ConvertPointToScreen(view(), &view_topleft);
bounds.set_origin(view_topleft);
bounds.SetRect(view_topleft.x() - mouse_watcher_->hot_zone_insets_.left(),
view_topleft.y() - mouse_watcher_->hot_zone_insets_.top(),
bounds.width() + mouse_watcher_->hot_zone_insets_.width(),
bounds.height() + mouse_watcher_->hot_zone_insets_.height());
gfx::Point cursor_point = Screen::GetCursorScreenPoint();
return bounds.Contains(cursor_point.x(), cursor_point.y());
}
// Returns true if the mouse is over the view's window.
bool IsMouseOverWindow() {
Widget* widget = view()->GetWidget();
if (!widget)
return false;
return Screen::GetWindowAtCursorScreenPoint() ==
widget->GetNativeWindow();
}
// Called from the message loop observer when a mouse movement has occurred.
void HandleGlobalMouseMoveEvent(bool check_window) {
bool in_view = IsCursorInViewZone();
if (!in_view || (check_window && !IsMouseOverWindow())) {
// Mouse moved outside the view's zone, start a timer to notify the
// listener.
if (notify_listener_factory_.empty()) {
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
notify_listener_factory_.NewRunnableMethod(
&Observer::NotifyListener),
!in_view ? kNotifyListenerTimeMs :
mouse_watcher_->notify_on_exit_time_ms_);
}
} else {
// Mouse moved quickly out of the view and then into it again, so cancel
// the timer.
notify_listener_factory_.RevokeAll();
}
}
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.
ScopedRunnableMethodFactory<Observer> notify_listener_factory_;
DISALLOW_COPY_AND_ASSIGN(Observer);
};
MouseWatcherListener::~MouseWatcherListener() {
}
MouseWatcher::MouseWatcher(View* host,
MouseWatcherListener* listener,
const gfx::Insets& hot_zone_insets)
: host_(host),
listener_(listener),
hot_zone_insets_(hot_zone_insets),
notify_on_exit_time_ms_(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_->MouseMovedOutOfView();
}
} // namespace views