diff --git a/src/servers/app/ClickTarget.h b/src/servers/app/ClickTarget.h new file mode 100644 index 0000000000..ead8ee9f68 --- /dev/null +++ b/src/servers/app/ClickTarget.h @@ -0,0 +1,80 @@ +/* + * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef CLICK_TARGET_H +#define CLICK_TARGET_H + + +#include + + +/*! \brief Identifies a mouse click target in the app server. + + Used to discriminate between different targets in order to filter + multi-clicks. A click on a different target resets the click count. +*/ +struct ClickTarget { +public: + enum Type { + TYPE_INVALID, + TYPE_WINDOW_CONTENTS, + TYPE_WINDOW_DECORATOR + }; + +public: + ClickTarget() + : + fType(TYPE_INVALID), + fWindow(B_NULL_TOKEN), + fWindowElement(0) + { + } + + ClickTarget(Type type, int32 window, int32 windowElement) + : + fType(type), + fWindow(window), + fWindowElement(windowElement) + { + } + + bool IsValid() const + { + return fType != TYPE_INVALID; + } + + Type GetType() const + { + return fType; + } + + int32 WindowToken() const + { + return fWindow; + } + + int32 WindowElement() const + { + return fWindowElement; + } + + bool operator==(const ClickTarget& other) const + { + return fType == other.fType && fWindow == other.fWindow + && fWindowElement == other.fWindowElement; + } + + bool operator!=(const ClickTarget& other) const + { + return !(*this == other); + } + +private: + Type fType; + int32 fWindow; + int32 fWindowElement; +}; + + +#endif // CLICK_TARGET_H diff --git a/src/servers/app/DefaultWindowBehaviour.cpp b/src/servers/app/DefaultWindowBehaviour.cpp index 7ccbc4f649..f9416d505c 100644 --- a/src/servers/app/DefaultWindowBehaviour.cpp +++ b/src/servers/app/DefaultWindowBehaviour.cpp @@ -19,6 +19,7 @@ #include +#include "ClickTarget.h" #include "Desktop.h" #include "DrawingEngine.h" #include "Window.h" @@ -120,8 +121,7 @@ struct DefaultWindowBehaviour::MouseTrackingState : State { fMinimizeCheckOnMouseUp = false; if (message->FindInt32("modifiers") == fBehavior.fLastModifiers && (fWindow->Flags() & B_NOT_MINIMIZABLE) == 0 - && system_time() - fLastMoveTime - < kWindowActivationTimeout) { + && system_time() - fLastMoveTime < kWindowActivationTimeout) { fWindow->ServerWindow()->NotifyMinimize(true); } } @@ -629,10 +629,7 @@ DefaultWindowBehaviour::DefaultWindowBehaviour(Window* window) fWindow(window), fDesktop(window->Desktop()), fState(NULL), - fLastModifiers(0), - fLastMouseButtons(0), - fLastRegion(REGION_NONE), - fResetClickCount(0) + fLastModifiers(0) { } @@ -644,30 +641,12 @@ DefaultWindowBehaviour::~DefaultWindowBehaviour() bool -DefaultWindowBehaviour::MouseDown(BMessage* message, BPoint where) +DefaultWindowBehaviour::MouseDown(BMessage* message, BPoint where, + int32 lastHitRegion, int32& clickCount, int32& _hitRegion) { - // Get the click count and reset it, if the modifiers changed in the - // meantime. Do the same when this is not the button we've seen before. - // TODO: At least the modifier check should be done in a better place - // (e.g. the input server). It should also reset clicks after mouse - // movement (which we don't do here either -- though that's probably - // acceptable). - int32 clickCount = message->FindInt32("clicks"); - int32 modifiers = message->FindInt32("modifiers"); + fLastModifiers = message->FindInt32("modifiers"); int32 buttons = message->FindInt32("buttons"); - if (clickCount <= 1) { - fResetClickCount = 0; - } else if (modifiers != fLastModifiers || buttons != fLastMouseButtons - || clickCount - fResetClickCount < 1) { - fResetClickCount = clickCount - 1; - clickCount = 1; - } else - clickCount -= fResetClickCount; - - fLastModifiers = modifiers; - fLastMouseButtons = buttons; - // if a state is active, let it do the job if (fState != NULL) return fState->MouseDown(message, where); @@ -683,7 +662,7 @@ DefaultWindowBehaviour::MouseDown(BMessage* message, BPoint where) if (decorator != NULL) inBorderRegion = decorator->GetFootprint().Contains(where); - bool windowModifier = _IsWindowModifier(modifiers); + bool windowModifier = _IsWindowModifier(fLastModifiers); if (windowModifier || inBorderRegion) { // click on the window decorator or we have the window modifier keys @@ -712,7 +691,7 @@ DefaultWindowBehaviour::MouseDown(BMessage* message, BPoint where) // tab sliding in any case if either shift key is held down // except sliding up-down by moving mouse left-right would // look strange - if ((modifiers & B_SHIFT_KEY) != 0 + if ((fLastModifiers & B_SHIFT_KEY) != 0 && fWindow->Look() != kLeftTitledWindowLook) { action = ACTION_SLIDE_TAB; break; @@ -762,15 +741,7 @@ DefaultWindowBehaviour::MouseDown(BMessage* message, BPoint where) } } - // The hit region changed since the last the click. Reset the click count. - if (hitRegion != fLastRegion) { - fLastRegion = hitRegion; - clickCount = 1; - - fResetClickCount = message->FindInt32("clicks") - 1; - if (fResetClickCount < 0) - fResetClickCount = 0; - } + _hitRegion = (int32)hitRegion; if (action == ACTION_NONE) { // No action -- if this is a click inside the window's contents, @@ -778,6 +749,10 @@ DefaultWindowBehaviour::MouseDown(BMessage* message, BPoint where) return inBorderRegion; } + // reset the click count, if the hit region differs from the previous one + if (hitRegion != lastHitRegion) + clickCount = 1; + DesktopSettings desktopSettings(fDesktop); if (!desktopSettings.AcceptFirstClick()) { // Ignore clicks on decorator buttons if the diff --git a/src/servers/app/DefaultWindowBehaviour.h b/src/servers/app/DefaultWindowBehaviour.h index fc33983c6d..4be1408746 100644 --- a/src/servers/app/DefaultWindowBehaviour.h +++ b/src/servers/app/DefaultWindowBehaviour.h @@ -29,7 +29,9 @@ public: DefaultWindowBehaviour(Window* window); virtual ~DefaultWindowBehaviour(); - virtual bool MouseDown(BMessage* message, BPoint where); + virtual bool MouseDown(BMessage* message, BPoint where, + int32 lastHitRegion, int32& clickCount, + int32& _hitRegion); virtual void MouseUp(BMessage* message, BPoint where); virtual void MouseMoved(BMessage *message, BPoint where, bool isFake); @@ -38,7 +40,7 @@ public: private: enum Region { - REGION_NONE, + REGION_NONE = 0, REGION_TAB, REGION_BORDER, @@ -90,9 +92,6 @@ protected: Desktop* fDesktop; State* fState; int32 fLastModifiers; - int32 fLastMouseButtons; - Region fLastRegion; - int32 fResetClickCount; }; diff --git a/src/servers/app/Desktop.cpp b/src/servers/app/Desktop.cpp index a9901f0a02..9b1e3f03ed 100644 --- a/src/servers/app/Desktop.cpp +++ b/src/servers/app/Desktop.cpp @@ -36,6 +36,7 @@ #include #include "AppServer.h" +#include "ClickTarget.h" #include "DecorManager.h" #include "DesktopSettingsPrivate.h" #include "DrawingEngine.h" @@ -65,6 +66,20 @@ #endif +static inline float +square_vector_length(float x, float y) +{ + return x * x + y * y; +} + + +static inline float +square_distance(const BPoint& a, const BPoint& b) +{ + return square_vector_length(a.x - b.x, a.y - b.y); +} + + class KeyboardFilter : public EventFilter { public: KeyboardFilter(Desktop* desktop); @@ -83,14 +98,19 @@ class KeyboardFilter : public EventFilter { class MouseFilter : public EventFilter { - public: - MouseFilter(Desktop* desktop); +public: + MouseFilter(Desktop* desktop); - virtual filter_result Filter(BMessage* message, EventTarget** _target, - int32* _viewToken, BMessage* latestMouseMoved); + virtual filter_result Filter(BMessage* message, EventTarget** _target, + int32* _viewToken, BMessage* latestMouseMoved); - private: - Desktop* fDesktop; +private: + Desktop* fDesktop; + int32 fLastClickButtons; + int32 fLastClickModifiers; + int32 fResetClickCount; + BPoint fLastClickPoint; + ClickTarget fLastClickTarget; }; @@ -218,7 +238,12 @@ KeyboardFilter::RemoveTarget(EventTarget* target) MouseFilter::MouseFilter(Desktop* desktop) : - fDesktop(desktop) + fDesktop(desktop), + fLastClickButtons(0), + fLastClickModifiers(0), + fResetClickCount(0), + fLastClickPoint(), + fLastClickTarget() { } @@ -248,9 +273,66 @@ MouseFilter::Filter(BMessage* message, EventTarget** _target, int32* _viewToken, // dispatch event to the window switch (message->what) { case B_MOUSE_DOWN: - window->MouseDown(message, where, &viewToken); + { + int32 windowToken = window->ServerWindow()->ServerToken(); + + // First approximation of click count validation. We reset the + // click count when modifiers or pressed buttons have changed + // or when we've got a different click target, or when the + // previous click location is too far from the new one. We can + // only check the window of the click target here; we'll recheck + // after asking the window. + int32 modifiers = message->FindInt32("modifiers"); + + int32 originalClickCount = message->FindInt32("clicks"); + if (originalClickCount <= 0) + originalClickCount = 1; + + int32 clickCount = originalClickCount; + if (clickCount > 1) { + if (modifiers != fLastClickModifiers + || buttons != fLastClickButtons + || !fLastClickTarget.IsValid() + || fLastClickTarget.WindowToken() != windowToken + || square_distance(where, fLastClickPoint) >= 16 + || clickCount - fResetClickCount < 1) { + clickCount = 1; + } else + clickCount -= fResetClickCount; + } + + // notify the window + ClickTarget clickTarget; + window->MouseDown(message, where, fLastClickTarget, clickCount, + clickTarget); + + // If the click target changed, always reset the click count. + if (clickCount != 1 && clickTarget != fLastClickTarget) + clickCount = 1; + + // update our click count management attributes + fResetClickCount = originalClickCount - clickCount; + fLastClickTarget = clickTarget; + fLastClickButtons = buttons; + fLastClickModifiers = modifiers; + fLastClickPoint = where; + + // get the view token from the click target + if (clickTarget.GetType() == ClickTarget::TYPE_WINDOW_CONTENTS) + viewToken = clickTarget.WindowElement(); + + // update the message's "clicks" field, if necessary + if (clickCount != originalClickCount) { + if (message->HasInt32("clicks")) + message->ReplaceInt32("clicks", clickCount); + else + message->AddInt32("clicks", clickCount); + } + + // notify desktop listeners fDesktop->NotifyMouseDown(window, message, where); break; + } case B_MOUSE_UP: window->MouseUp(message, where, &viewToken); @@ -273,6 +355,13 @@ MouseFilter::Filter(BMessage* message, EventTarget** _target, int32* _viewToken, *_viewToken = viewToken; *_target = &window->EventTarget(); } + } else if (message->what == B_MOUSE_DOWN) { + // the mouse-down didn't hit a window -- reset the click target + fResetClickCount = 0; + fLastClickTarget = ClickTarget(); + fLastClickButtons = message->FindInt32("buttons"); + fLastClickModifiers = message->FindInt32("modifiers"); + fLastClickPoint = where; } if (window == NULL || viewToken == B_NULL_TOKEN) { diff --git a/src/servers/app/Window.cpp b/src/servers/app/Window.cpp index d21f8816c1..40b3639ce1 100644 --- a/src/servers/app/Window.cpp +++ b/src/servers/app/Window.cpp @@ -14,6 +14,7 @@ #include "Window.h" +#include "ClickTarget.h" #include "Decorator.h" #include "DecorManager.h" #include "Desktop.h" @@ -781,14 +782,47 @@ Window::EnableUpdateRequests() // #pragma mark - -void -Window::MouseDown(BMessage* message, BPoint where, int32* _viewToken) -{ - DesktopSettings desktopSettings(fDesktop); +/*! \brief Handles a mouse-down message for the window. - bool eventEaten = fWindowBehaviour->MouseDown(message, where); - if (!eventEaten) { + \param message The message. + \param where The point where the mouse click happened. + \param lastClickTarget The target of the previous click. + \param clickCount The number of subsequent, no longer than double-click + interval separated clicks that have happened so far. This number doesn't + necessarily match the value in the message. It has already been + pre-processed in order to avoid erroneous multi-clicks (e.g. when a + different button has been used or a different window was targeted). This + is an in-out variable. The method can reset the value to 1, if it + doesn't want this event handled as a multi-click. Returning a different + click target will also make the caller reset the click count. + \param _clickTarget Set by the method to a value identifying the clicked + element. If not explicitly set, an invalid click target is assumed. +*/ +void +Window::MouseDown(BMessage* message, BPoint where, + const ClickTarget& lastClickTarget, int32& clickCount, + ClickTarget& _clickTarget) +{ + // If the previous click hit our decorator, get the hit region. + int32 windowToken = fWindow->ServerToken(); + int32 lastHitRegion = 0; + if (lastClickTarget.GetType() == ClickTarget::TYPE_WINDOW_DECORATOR + && lastClickTarget.WindowToken() == windowToken) { + lastHitRegion = lastClickTarget.WindowElement(); + } + + // Let the window behavior process the mouse event. + int32 hitRegion = 0; + bool eventEaten = fWindowBehaviour->MouseDown(message, where, lastHitRegion, + clickCount, hitRegion); + + if (eventEaten) { + // click on the decorator (or equivalent) + _clickTarget = ClickTarget(ClickTarget::TYPE_WINDOW_DECORATOR, + windowToken, (int32)hitRegion); + } else { // click was inside the window contents + int32 viewToken = B_NULL_TOKEN; if (View* view = ViewAt(where)) { if (HasModal()) return; @@ -801,6 +835,7 @@ Window::MouseDown(BMessage* message, BPoint where, int32* _viewToken) // Activate or focus the window in case it doesn't accept first // click, depending on the mouse mode + DesktopSettings desktopSettings(fDesktop); if (desktopSettings.MouseMode() == B_NORMAL_MOUSE && !acceptFirstClick) fDesktop->ActivateWindow(this); @@ -819,9 +854,12 @@ Window::MouseDown(BMessage* message, BPoint where, int32* _viewToken) } // fill out view token for the view under the mouse - *_viewToken = view->Token(); + viewToken = view->Token(); view->MouseDown(message, where); } + + _clickTarget = ClickTarget(ClickTarget::TYPE_WINDOW_CONTENTS, + windowToken, viewToken); } } diff --git a/src/servers/app/Window.h b/src/servers/app/Window.h index 7f55ec0e55..7b62336c04 100644 --- a/src/servers/app/Window.h +++ b/src/servers/app/Window.h @@ -27,6 +27,7 @@ namespace BPrivate { class PortLink; }; +class ClickTarget; class ClientLooper; class Decorator; class Desktop; @@ -144,7 +145,9 @@ public: int32 xOffset, int32 yOffset); void MouseDown(BMessage* message, BPoint where, - int32* _viewToken); + const ClickTarget& lastClickTarget, + int32& clickCount, + ClickTarget& _clickTarget); void MouseUp(BMessage* message, BPoint where, int32* _viewToken); void MouseMoved(BMessage* message, BPoint where, diff --git a/src/servers/app/WindowBehaviour.cpp b/src/servers/app/WindowBehaviour.cpp index f26e0788a2..ccc9a0868c 100644 --- a/src/servers/app/WindowBehaviour.cpp +++ b/src/servers/app/WindowBehaviour.cpp @@ -27,3 +27,29 @@ void WindowBehaviour::ModifiersChanged(int32 modifiers) { } + + +/*! \fn WindowBehaviour::MouseDown() + \brief Handles a mouse-down message for the window. + + Note that values passed and returned for the hit regions are only meaningful + to the WindowBehavior subclass, save for the value 0, which is refers to an + invalid region. + + \param message The message. + \param where The point where the mouse click happened. + \param lastHitRegion The hit region of the previous click. + \param clickCount The number of subsequent, no longer than double-click + interval separated clicks that have happened so far. This number doesn't + necessarily match the value in the message. It has already been + pre-processed in order to avoid erroneous multi-clicks (e.g. when a + different button has been used or a different window was targeted). This + is an in-out variable. The method can reset the value to 1, if it + doesn't want this event handled as a multi-click. Returning a different + click hit region will also make the caller reset the click count. + \param _hitRegion Set by the method to a value identifying the clicked + decorator element. If not explicitly set, an invalid hit region (0) is + assumed. Only needs to be set when returning \c true. + \return \c true, if the event was a WindowBehaviour event and should be + discarded. +*/ diff --git a/src/servers/app/WindowBehaviour.h b/src/servers/app/WindowBehaviour.h index 46503e2b5a..425ef2a5fd 100644 --- a/src/servers/app/WindowBehaviour.h +++ b/src/servers/app/WindowBehaviour.h @@ -11,8 +11,11 @@ #include +#include "Decorator.h" + class BMessage; +class ClickTarget; class WindowBehaviour { @@ -20,8 +23,9 @@ public: WindowBehaviour(); virtual ~WindowBehaviour(); - //! \return true if event was a WindowBehaviour event and should be discard - virtual bool MouseDown(BMessage* message, BPoint where) = 0; + virtual bool MouseDown(BMessage* message, BPoint where, + int32 lastHitRegion, int32& clickCount, + int32& _hitRegion) = 0; virtual void MouseUp(BMessage* message, BPoint where) = 0; virtual void MouseMoved(BMessage *message, BPoint where, bool isFake) = 0;