From 0ed68f3f58c63bd1496cb268bd83881da180051f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Lind?= Date: Wed, 21 May 2014 13:57:09 +0200 Subject: Refactor the sloppy submenu logic And enable the style to control the behavior of the sloppy submenus [ChangeLog][QtWidgets][QMenu] QMenu now pick up how "sloppy" submenus behave from the style Task-number: QTBUG-20094 Change-Id: Ib1a9770d9b63028033cc360ae236471449d00267 Reviewed-by: Friedemann Kleint Reviewed-by: Gabriel de Dietrich --- src/widgets/widgets/qmenu_p.h | 251 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 244 insertions(+), 7 deletions(-) (limited to 'src/widgets/widgets/qmenu_p.h') diff --git a/src/widgets/widgets/qmenu_p.h b/src/widgets/widgets/qmenu_p.h index 71bf33e1ce..2709354f3f 100644 --- a/src/widgets/widgets/qmenu_p.h +++ b/src/widgets/widgets/qmenu_p.h @@ -79,20 +79,230 @@ struct QWceMenuAction { }; #endif +template +class QSetValueOnDestroy +{ +public: + QSetValueOnDestroy(T &toSet, T value) + : toSet(toSet) + , value(value) + { } + + ~QSetValueOnDestroy() { toSet = value; } +private: + T &toSet; + T value; +}; + +class QMenuSloppyState +{ + Q_DISABLE_COPY(QMenuSloppyState) +public: + QMenuSloppyState() + : m_menu(Q_NULLPTR) + , m_enabled(false) + , m_uni_directional(false) + , m_select_other_actions(false) + , m_first_mouse(true) + , m_init_guard(false) + , m_uni_dir_discarded_count(0) + , m_uni_dir_fail_at_count(0) + , m_timeout(0) + , m_reset_action(Q_NULLPTR) + , m_origin_action(Q_NULLPTR) + , m_parent(Q_NULLPTR) + { } + + ~QMenuSloppyState() { reset(); } + + void initialize(QMenu *menu) + { + m_menu = menu; + m_uni_directional = menu->style()->styleHint(QStyle::SH_Menu_SubMenuUniDirection, 0, menu); + m_uni_dir_fail_at_count = menu->style()->styleHint(QStyle::SH_Menu_SubMenuUniDirectionFailCount, 0, menu); + m_select_other_actions = menu->style()->styleHint(QStyle::SH_Menu_SubMenuSloppySelectOtherActions, 0 , menu); + m_timeout = menu->style()->styleHint(QStyle::SH_Menu_SubMenuSloppyCloseTimeout); + m_discard_state_when_entering_parent = menu->style()->styleHint(QStyle::SH_Menu_SubMenuResetWhenReenteringParent); + m_dont_start_time_on_leave = menu->style()->styleHint(QStyle::SH_Menu_SubMenuDontStartSloppyOnLeave); + reset(); + } + + void reset(); + bool enabled() const { return m_enabled; } + + void setResetAction(QAction *action) { m_reset_action = action; } + + enum MouseEventResult { + EventIsProcessed, + EventShouldBePropogated, + EventDiscardsSloppyState + }; + + void startTimer() + { + if (m_enabled) + m_time.start(m_timeout, m_menu); + } + + void startTimerIfNotRunning() + { + if (!m_time.isActive()) + startTimer(); + } + + void stopTimer() + { + m_time.stop(); + } + + void enter(); + + void childEnter() + { + stopTimer(); + if (m_parent) + m_parent->childEnter(); + } + + void leave() + { + if (m_dont_start_time_on_leave) + return; + if (m_parent) + m_parent->childLeave(); + startTimer(); + } + void childLeave(); + + static float slope(const QPointF &p1, const QPointF &p2) + { + const QPointF slope = p2 - p1; + if (slope.x()== 0) + return 9999; + return slope.y()/slope.x(); + } + + bool checkSlope(qreal oldS, qreal newS, bool wantSteeper) + { + if (wantSteeper) + return oldS <= newS; + return newS <= oldS; + } + + MouseEventResult processMouseEvent(const QPointF &mousePos, QAction *resetAction, QAction *currentAction) + { + if (m_parent) + m_parent->stopTimer(); + + if (!m_enabled) + return EventShouldBePropogated; + + if (!m_time.isActive()) + startTimer(); + + if (!m_sub_menu) { + reset(); + return EventShouldBePropogated; + } + + QSetValueOnDestroy setFirstMouse(m_first_mouse, false); + QSetValueOnDestroy setPreviousPoint(m_previous_point, mousePos); + + if (resetAction && resetAction->isSeparator()) + m_reset_action = Q_NULLPTR; + else { + m_reset_action = resetAction; + } + + if (m_action_rect.contains(mousePos)) { + startTimer(); + return currentAction == m_menu->menuAction() ? EventIsProcessed : EventShouldBePropogated; + } + + if (m_uni_directional && !m_first_mouse && resetAction != m_origin_action) { + bool left_to_right = m_menu->layoutDirection() == Qt::LeftToRight; + QRect sub_menu_rect = m_sub_menu->geometry(); + QPoint sub_menu_top = + left_to_right? sub_menu_rect.topLeft() : sub_menu_rect.topRight(); + QPoint sub_menu_bottom = + left_to_right? sub_menu_rect.bottomLeft() : sub_menu_rect.bottomRight(); + qreal prev_slope_top = slope(m_previous_point, sub_menu_top); + qreal prev_slope_bottom = slope(m_previous_point, sub_menu_bottom); + + qreal current_slope_top = slope(mousePos, sub_menu_top); + qreal current_slope_bottom = slope(mousePos, sub_menu_bottom); + + bool slopeTop = checkSlope(prev_slope_top, current_slope_top, sub_menu_top.y() < mousePos.y()); + bool slopeBottom = checkSlope(prev_slope_bottom, current_slope_bottom, sub_menu_bottom.y() > mousePos.y()); + bool rightDirection = false; + int mouseDir = m_previous_point.y() - mousePos.y(); + if (mouseDir >= 0) { + rightDirection = rightDirection || slopeTop; + } + if (mouseDir <= 0) { + rightDirection = rightDirection || slopeBottom; + } + + if (m_uni_dir_discarded_count >= m_uni_dir_fail_at_count && !rightDirection) { + m_uni_dir_discarded_count = 0; + return EventDiscardsSloppyState; + } + + if (!rightDirection) + m_uni_dir_discarded_count++; + else + m_uni_dir_discarded_count = 0; + + } + + return m_select_other_actions ? EventShouldBePropogated : EventIsProcessed; + } + + void setSubMenuPopup(const QRect &actionRect, QAction *resetAction, QMenu *subMenu); + bool hasParentActiveDelayTimer() const; + void timeout(); + int timeForTimeout() const { return m_timeout; } + + bool isTimerId(int timerId) const { return m_time.timerId() == timerId; } + QMenu *subMenu() const { return m_sub_menu; } + +private: + QMenu *m_menu; + bool m_enabled; + bool m_uni_directional; + bool m_select_other_actions; + bool m_first_mouse; + bool m_init_guard; + bool m_discard_state_when_entering_parent; + bool m_dont_start_time_on_leave; + short m_uni_dir_discarded_count; + short m_uni_dir_fail_at_count; + short m_timeout; + QBasicTimer m_time; + QAction *m_reset_action; + QAction *m_origin_action; + QRectF m_action_rect; + QPointF m_previous_point; + QPointer m_sub_menu; + QMenuSloppyState *m_parent; +}; + class QMenuPrivate : public QWidgetPrivate { Q_DECLARE_PUBLIC(QMenu) public: QMenuPrivate() : itemsDirty(0), maxIconWidth(0), tabWidth(0), ncols(0), collapsibleSeparators(true), toolTipsVisible(false), - activationRecursionGuard(false), hasHadMouse(0), aboutToHide(0), motions(0), + activationRecursionGuard(false), delayedPopupGuard(false), + hasReceievedEnter(false), + hasHadMouse(0), aboutToHide(0), motions(0), currentAction(0), #ifdef QT_KEYPAD_NAVIGATION selectAction(0), cancelAction(0), #endif scroll(0), eventLoop(0), tearoff(0), tornoff(0), tearoffHighlighted(0), - hasCheckableItems(0), sloppyDelayTimer(0), sloppyAction(0), doChildEffects(false), platformMenu(0) + hasCheckableItems(0), doChildEffects(false), platformMenu(0) #if defined(Q_OS_WINCE) && !defined(QT_NO_MENUBAR) ,wce_menu(0) @@ -135,6 +345,8 @@ public: int getLastVisibleAction() const; bool activationRecursionGuard; + bool delayedPopupGuard; + bool hasReceievedEnter; //selection static QMenu *mouseDown; @@ -142,12 +354,39 @@ public: uint hasHadMouse : 1; uint aboutToHide : 1; int motions; + int mousePopupDelay; QAction *currentAction; #ifdef QT_KEYPAD_NAVIGATION QAction *selectAction; QAction *cancelAction; #endif - QBasicTimer menuDelayTimer; + struct DelayState { + DelayState() + : parent(0) + , action(0) + { } + void initialize(QMenu *parent) + { + this->parent = parent; + } + + void start(int timeout, QAction *toStartAction) + { + if (timer.isActive() && toStartAction == action) + return; + action = toStartAction; + timer.start(timeout,parent); + } + void stop() + { + action = 0; + timer.stop(); + } + + QMenu *parent; + QBasicTimer timer; + QAction *action; + } delayState; enum SelectionReason { SelectedFromKeyboard, SelectedFromElsewhere @@ -206,10 +445,7 @@ public: mutable bool hasCheckableItems; - //sloppy selection - int sloppyDelayTimer; - mutable QAction *sloppyAction; - QRegion sloppyRegion; + QMenuSloppyState sloppyState; //default action QPointer defaultAction; @@ -265,6 +501,7 @@ public: QAction* wceCommands(uint command); #endif QPointer noReplayFor; + static QPointer previousMouseMenu; }; #endif // QT_NO_MENU -- cgit v1.2.3