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.cpp | 298 ++++++++++++++++++++++++++++-------------- src/widgets/widgets/qmenu.h | 1 - src/widgets/widgets/qmenu_p.h | 251 ++++++++++++++++++++++++++++++++++- 3 files changed, 444 insertions(+), 106 deletions(-) (limited to 'src/widgets/widgets') diff --git a/src/widgets/widgets/qmenu.cpp b/src/widgets/widgets/qmenu.cpp index 846199ebe2..e25c145d26 100644 --- a/src/widgets/widgets/qmenu.cpp +++ b/src/widgets/widgets/qmenu.cpp @@ -78,6 +78,22 @@ QT_BEGIN_NAMESPACE QMenu *QMenuPrivate::mouseDown = 0; +QPointer QMenuPrivate::previousMouseMenu(Q_NULLPTR); +static void handleEnterLeaveEvents(QPointer *previous_ptr, QMenu *next) +{ + QWidget *previous = previous_ptr->data(); + if (previous != next) { + if (previous) { + QEvent leaveEvent(QEvent::Leave); + QApplication::sendEvent(previous, &leaveEvent); + } + if (next) { + QEvent enterEvent(QEvent::Enter); + QApplication::sendEvent(next, &enterEvent); + } + } + *previous_ptr = next; +} /* QMenu code */ // internal class used for the torn off popup @@ -156,6 +172,9 @@ void QMenuPrivate::init() } setPlatformMenu(QGuiApplicationPrivate::platformTheme()->createPlatformMenu()); + sloppyState.initialize(q); + delayState.initialize(q); + mousePopupDelay = q->style()->styleHint(QStyle::SH_Menu_SubMenuPopupDelay, 0, q); } void QMenuPrivate::setPlatformMenu(QPlatformMenu *menu) @@ -278,7 +297,6 @@ void QMenuPrivate::updateActionRects(const QRect &screen) const maxIconWidth = 0; hasCheckableItems = false; ncols = 1; - sloppyAction = 0; for (int i = 0; i < actions.count(); ++i) { QAction *action = actions.at(i); @@ -490,21 +508,30 @@ void QMenuPrivate::hideMenu(QMenu *menu) aboutToHide = false; blocker.unblock(); #endif // QT_NO_EFFECTS + if (activeMenu == menu) + activeMenu = 0; + menu->d_func()->causedPopup.action = 0; + menu->d_func()->causedPopup.widget = 0; menu->close(); + if (previousMouseMenu.data() == menu) + handleEnterLeaveEvents(&previousMouseMenu, Q_NULLPTR); } void QMenuPrivate::popupAction(QAction *action, int delay, bool activateFirst) { Q_Q(QMenu); - if (action && action->isEnabled()) { - if (!delay) - q->internalDelayedPopup(); - else if (!menuDelayTimer.isActive() && (!action->menu() || !action->menu()->isVisible())) - menuDelayTimer.start(delay, q); - if (activateFirst && action->menu()) - action->menu()->d_func()->setFirstActionActive(); + if (action) { + if (action->isEnabled()) { + if (!delay) + q->internalDelayedPopup(); + else if (action->menu() && !action->menu()->isVisible()) + delayState.start(delay, action); + else if (!action->menu()) + delayState.stop(); + if (activateFirst && action->menu()) + action->menu()->d_func()->setFirstActionActive(); + } } else if (QMenu *menu = activeMenu) { //hide the current item - activeMenu = 0; hideMenu(menu); } } @@ -555,33 +582,32 @@ void QMenuPrivate::setCurrentAction(QAction *action, int popup, SelectionReason { Q_Q(QMenu); tearoffHighlighted = 0; + + if (action + && (action->isSeparator() + || (!action->isEnabled() && !q->style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, q)))) + action = Q_NULLPTR; + // Reselect the currently active action in case mouse moved over other menu items when // moving from sub menu action to sub menu (QTBUG-20094). - if (reason != SelectedFromKeyboard && action == currentAction && !(action && action->menu() && action->menu() != activeMenu)) { + if (reason != SelectedFromKeyboard) { if (QMenu *menu = qobject_cast(causedPopup.widget)) { if (causedPopup.action && menu->d_func()->activeMenu == q) menu->d_func()->setCurrentAction(causedPopup.action, 0, reason, false); } - return; } if (currentAction) q->update(actionRect(currentAction)); - sloppyAction = 0; - if (!sloppyRegion.isEmpty()) - sloppyRegion = QRegion(); QMenu *hideActiveMenu = activeMenu; -#ifndef QT_NO_STATUSTIP QAction *previousAction = currentAction; -#endif currentAction = action; if (action) { if (!action->isSeparator()) { activateAction(action, QAction::Hover); if (popup != -1) { - hideActiveMenu = 0; //will be done "later" // if the menu is visible then activate the required action, // otherwise we just mark the action as currentAction // and activate it when the menu will be popuped. @@ -604,24 +630,122 @@ void QMenuPrivate::setCurrentAction(QAction *action, int popup, SelectionReason } } } - } else { //action is a separator - if (popup != -1) - hideActiveMenu = 0; //will be done "later" } #ifndef QT_NO_STATUSTIP } else if (previousAction) { previousAction->d_func()->showStatusText(topCausedWidget(), QString()); #endif } - if (hideActiveMenu) { - activeMenu = 0; + if (hideActiveMenu && previousAction != currentAction) { + if (popup == -1) { #ifndef QT_NO_EFFECTS - // kill any running effect - qFadeEffect(0); - qScrollEffect(0); + // kill any running effect + qFadeEffect(0); + qScrollEffect(0); #endif - hideMenu(hideActiveMenu); + hideMenu(hideActiveMenu); + } else if (!currentAction || !currentAction->menu()) { + sloppyState.startTimerIfNotRunning(); + } + } +} + +void QMenuSloppyState::reset() +{ + m_enabled = false; + m_first_mouse = true; + m_init_guard = false; + m_uni_dir_discarded_count = 0; + m_time.stop(); + m_reset_action = Q_NULLPTR; + m_origin_action = Q_NULLPTR; + m_action_rect = QRect(); + m_previous_point = QPointF(); + if (m_sub_menu) { + QMenuPrivate::get(m_sub_menu)->sloppyState.m_parent = Q_NULLPTR; + m_sub_menu = Q_NULLPTR; + } +} +void QMenuSloppyState::enter() +{ + QMenuPrivate *menuPriv = QMenuPrivate::get(m_menu); + + if (m_discard_state_when_entering_parent && m_sub_menu == menuPriv->activeMenu) { + menuPriv->hideMenu(m_sub_menu); + reset(); + } + if (m_parent) + m_parent->childEnter(); +} + +void QMenuSloppyState::childLeave() +{ + if (m_enabled && !QMenuPrivate::get(m_menu)->hasReceievedEnter) { + startTimer(); + if (m_parent) + m_parent->childLeave(); + } +} + +void QMenuSloppyState::setSubMenuPopup(const QRect &actionRect, QAction *resetAction, QMenu *subMenu) +{ + m_enabled = true; + m_init_guard = true; + m_time.stop(); + m_action_rect = actionRect; + m_sub_menu = subMenu; + QMenuPrivate::get(subMenu)->sloppyState.m_parent = this; + m_reset_action = resetAction; + m_origin_action = resetAction; +} + +bool QMenuSloppyState::hasParentActiveDelayTimer() const +{ + return m_parent && m_parent->m_menu && QMenuPrivate::get(m_parent->m_menu)->delayState.timer.isActive(); +} + +class ResetOnDestroy +{ +public: + ResetOnDestroy(QMenuSloppyState *sloppyState, bool *guard) + : toReset(sloppyState) + , guard(guard) + { + *guard = false; } + + ~ResetOnDestroy() + { + if (!*guard) + toReset->reset(); + } + + QMenuSloppyState *toReset; + bool *guard; +}; + +void QMenuSloppyState::timeout() +{ + QMenuPrivate *menu_priv = QMenuPrivate::get(m_menu); + if (menu_priv->currentAction == m_reset_action + && menu_priv->hasReceievedEnter + && (menu_priv->currentAction + && menu_priv->currentAction->menu() == menu_priv->activeMenu)) { + return; + } + + ResetOnDestroy resetState(this, &m_init_guard); + + if (hasParentActiveDelayTimer() || !m_menu || !m_menu->isVisible()) + return; + + if (m_sub_menu) + menu_priv->hideMenu(m_sub_menu); + + if (menu_priv->hasReceievedEnter) + menu_priv->setCurrentAction(m_reset_action,0); + else + menu_priv->setCurrentAction(Q_NULLPTR, 0); } //return the top causedPopup.widget that is not a QMenu @@ -972,8 +1096,10 @@ bool QMenuPrivate::mouseEventTaken(QMouseEvent *e) tearoffHighlighted = 0; } - if (q->frameGeometry().contains(e->globalPos())) //otherwise if the event is in our rect we want it.. + if (q->frameGeometry().contains(e->globalPos())) { //otherwise if the event is in our rect we want it.. + handleEnterLeaveEvents(&previousMouseMenu, q); return false; + } for(QWidget *caused = causedPopup.widget; caused;) { bool passOnEvent = false; @@ -989,16 +1115,17 @@ bool QMenuPrivate::mouseEventTaken(QMouseEvent *e) next_widget = m->d_func()->causedPopup.widget; } if (passOnEvent) { + handleEnterLeaveEvents(&previousMouseMenu,qobject_cast(caused)); if(e->type() != QEvent::MouseButtonRelease || mouseDown == caused) { QMouseEvent new_e(e->type(), cpos, caused->mapTo(caused->topLevelWidget(), cpos), e->screenPos(), e->button(), e->buttons(), e->modifiers()); QApplication::sendEvent(caused, &new_e); return true; + } } - } - if (!next_widget) - break; caused = next_widget; + if (!caused) + handleEnterLeaveEvents(&previousMouseMenu, Q_NULLPTR); } return false; } @@ -2243,6 +2370,8 @@ void QMenu::hideEvent(QHideEvent *) #endif d->mouseDown = 0; d->hasHadMouse = false; + if (d->activeMenu) + d->hideMenu(d->activeMenu); d->causedPopup.widget = 0; d->causedPopup.action = 0; if (d->scroll) @@ -2409,7 +2538,7 @@ void QMenu::mouseReleaseEvent(QMouseEvent *e) #endif d->activateAction(action, QAction::Trigger); } - } else if (d->hasMouseMoved(e->globalPos())) { + } else if ((!action || action->isEnabled()) && d->hasMouseMoved(e->globalPos())) { d->hideUpToMenuBar(); } } @@ -2474,8 +2603,8 @@ QMenu::event(QEvent *e) } } break; case QEvent::ContextMenu: - if(d->menuDelayTimer.isActive()) { - d->menuDelayTimer.stop(); + if (d->delayState.timer.isActive()) { + d->delayState.stop(); internalDelayedPopup(); } break; @@ -2492,6 +2621,7 @@ QMenu::event(QEvent *e) case QEvent::Show: d->mouseDown = 0; d->updateActionRects(); + d->sloppyState.reset(); if (d->currentAction) d->popupAction(d->currentAction, 0, false); break; @@ -2899,34 +3029,34 @@ void QMenu::mouseMoveEvent(QMouseEvent *e) Q_D(QMenu); if (!isVisible() || d->aboutToHide || d->mouseEventTaken(e)) return; + d->motions++; - if (d->motions == 0) // ignore first mouse move event (see enterEvent()) + if (d->motions == 0) return; + d->hasHadMouse = d->hasHadMouse || rect().contains(e->pos()); QAction *action = d->actionAt(e->pos()); - if (!action || action->isSeparator()) { + if ((!action || action->isSeparator()) && !d->sloppyState.enabled()) { if (d->hasHadMouse - && d->sloppyDelayTimer == 0 // Keep things as they are while we're moving to the submenu - && (!d->currentAction || (action && action->isSeparator()) - || !(d->currentAction->menu() && d->currentAction->menu()->isVisible()))) - d->setCurrentAction(0); + || (!d->currentAction || !d->currentAction->menu() || !d->currentAction->menu()->isVisible())) { + d->setCurrentAction(action); + } return; - } else if(e->buttons()) { - d->mouseDown = this; } - if (d->sloppyRegion.contains(e->pos())) { - // If the timer is already running then don't start a new one unless the action is the same - if (d->sloppyAction != action && d->sloppyDelayTimer != 0) { - killTimer(d->sloppyDelayTimer); - d->sloppyDelayTimer = 0; - } - if (d->sloppyDelayTimer == 0) { - d->sloppyAction = action; - d->sloppyDelayTimer = startTimer(style()->styleHint(QStyle::SH_Menu_SubMenuPopupDelay, 0, this) * 6); - } - } else if (action != d->currentAction) { - d->setCurrentAction(action, style()->styleHint(QStyle::SH_Menu_SubMenuPopupDelay, 0, this)); + + if (e->buttons()) + d->mouseDown = this; + + if (d->activeMenu) + d->activeMenu->d_func()->setCurrentAction(0); + + QMenuSloppyState::MouseEventResult sloppyEventResult = d->sloppyState.processMouseEvent(e->localPos(), action, d->currentAction); + if (sloppyEventResult == QMenuSloppyState::EventShouldBePropogated) { + d->setCurrentAction(action, d->mousePopupDelay); + } else if (sloppyEventResult == QMenuSloppyState::EventDiscardsSloppyState) { + d->sloppyState.reset(); + d->hideMenu(d->activeMenu); } } @@ -2935,7 +3065,11 @@ void QMenu::mouseMoveEvent(QMouseEvent *e) */ void QMenu::enterEvent(QEvent *) { - d_func()->motions = -1; // force us to ignore the generate mouse move in mouseMoveEvent() + Q_D(QMenu); + d->hasReceievedEnter = true; + d->sloppyState.enter(); + d->sloppyState.startTimer(); + d->motions = -1; // force us to ignore the generate mouse move in mouseMoveEvent() } /*! @@ -2944,9 +3078,8 @@ void QMenu::enterEvent(QEvent *) void QMenu::leaveEvent(QEvent *) { Q_D(QMenu); - d->sloppyAction = 0; - if (!d->sloppyRegion.isEmpty()) - d->sloppyRegion = QRegion(); + d->hasReceievedEnter = false; + d->sloppyState.leave(); if (!d->activeMenu && d->currentAction) setActiveAction(0); } @@ -2962,13 +3095,14 @@ QMenu::timerEvent(QTimerEvent *e) d->scrollMenu((QMenuPrivate::QMenuScroller::ScrollDirection)d->scroll->scrollDirection); if (d->scroll->scrollFlags == QMenuPrivate::QMenuScroller::ScrollNone) d->scroll->scrollTimer.stop(); - } else if(d->menuDelayTimer.timerId() == e->timerId()) { - d->menuDelayTimer.stop(); + } else if (d->delayState.timer.timerId() == e->timerId()) { + if (d->currentAction && !d->currentAction->menu()) + return; + d->delayState.stop(); + d->sloppyState.stopTimer(); internalDelayedPopup(); - } else if (d->sloppyDelayTimer == e->timerId()) { - killTimer(d->sloppyDelayTimer); - d->sloppyDelayTimer = 0; - internalSetSloppyAction(); + } else if (d->sloppyState.isTimerId(e->timerId())) { + d->sloppyState.timeout(); } else if(d->searchBufferTimer.timerId() == e->timerId()) { d->searchBuffer.clear(); } @@ -3077,26 +3211,16 @@ void QMenu::actionEvent(QActionEvent *e) } } -/*! - \internal -*/ -void QMenu::internalSetSloppyAction() -{ - if (d_func()->sloppyAction) - d_func()->setCurrentAction(d_func()->sloppyAction, 0); -} - /*! \internal */ void QMenu::internalDelayedPopup() { Q_D(QMenu); - //hide the current item if (QMenu *menu = d->activeMenu) { - d->activeMenu = 0; - d->hideMenu(menu); + if (d->activeMenu->menuAction() != d->currentAction) + d->hideMenu(menu); } if (!d->currentAction || !d->currentAction->isEnabled() || !d->currentAction->menu() || @@ -3110,34 +3234,12 @@ void QMenu::internalDelayedPopup() int subMenuOffset = style()->pixelMetric(QStyle::PM_SubMenuOverlap, 0, this); const QRect actionRect(d->actionRect(d->currentAction)); - const QSize menuSize(d->activeMenu->sizeHint()); const QPoint rightPos(mapToGlobal(QPoint(actionRect.right() + subMenuOffset + 1, actionRect.top()))); QPoint pos(rightPos); - //calc sloppy focus buffer - if (style()->styleHint(QStyle::SH_Menu_SloppySubMenus, 0, this)) { - QPoint cur = QCursor::pos(); - if (actionRect.contains(mapFromGlobal(cur))) { - QPoint pts[4]; - pts[0] = QPoint(cur.x(), cur.y() - 2); - pts[3] = QPoint(cur.x(), cur.y() + 2); - if (pos.x() >= cur.x()) { - pts[1] = QPoint(geometry().right(), pos.y()); - pts[2] = QPoint(geometry().right(), pos.y() + menuSize.height()); - } else { - pts[1] = QPoint(pos.x() + menuSize.width(), pos.y()); - pts[2] = QPoint(pos.x() + menuSize.width(), pos.y() + menuSize.height()); - } - QPolygon points(4); - for(int i = 0; i < 4; i++) - points.setPoint(i, mapFromGlobal(pts[i])); - d->sloppyRegion = QRegion(points); - } - } - - //do the popup d->activeMenu->popup(pos); + d->sloppyState.setSubMenuPopup(actionRect, d->currentAction, d->activeMenu); } /*! diff --git a/src/widgets/widgets/qmenu.h b/src/widgets/widgets/qmenu.h index fef7903278..481d3b335d 100644 --- a/src/widgets/widgets/qmenu.h +++ b/src/widgets/widgets/qmenu.h @@ -188,7 +188,6 @@ protected: #endif private Q_SLOTS: - void internalSetSloppyAction(); void internalDelayedPopup(); private: 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