summaryrefslogtreecommitdiffstats
path: root/src/widgets
diff options
context:
space:
mode:
authorJørgen Lind <jorgen.lind@digia.com>2014-05-21 13:57:09 +0200
committerJørgen Lind <jorgen.lind@digia.com>2014-09-16 12:03:20 +0200
commit0ed68f3f58c63bd1496cb268bd83881da180051f (patch)
tree2a9ced3368fd2904e1bf52333f11ae48c4c70964 /src/widgets
parentfb44d3838a0a35cbc0d23323c614deaac986507f (diff)
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 <Friedemann.Kleint@digia.com> Reviewed-by: Gabriel de Dietrich <gabriel.dedietrich@digia.com>
Diffstat (limited to 'src/widgets')
-rw-r--r--src/widgets/styles/qcommonstyle.cpp19
-rw-r--r--src/widgets/styles/qmacstyle_mac.mm13
-rw-r--r--src/widgets/styles/qstyle.cpp26
-rw-r--r--src/widgets/styles/qstyle.h6
-rw-r--r--src/widgets/styles/qwindowsstyle.cpp11
-rw-r--r--src/widgets/widgets/qmenu.cpp298
-rw-r--r--src/widgets/widgets/qmenu.h1
-rw-r--r--src/widgets/widgets/qmenu_p.h251
8 files changed, 519 insertions, 106 deletions
diff --git a/src/widgets/styles/qcommonstyle.cpp b/src/widgets/styles/qcommonstyle.cpp
index ce6d3d708f..5f343714a4 100644
--- a/src/widgets/styles/qcommonstyle.cpp
+++ b/src/widgets/styles/qcommonstyle.cpp
@@ -4927,6 +4927,25 @@ int QCommonStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget
ret = true;
break;
+ case SH_Menu_SubMenuUniDirection:
+ ret = false;
+ break;
+ case SH_Menu_SubMenuUniDirectionFailCount:
+ ret = 1;
+ break;
+ case SH_Menu_SubMenuSloppySelectOtherActions:
+ ret = true;
+ break;
+ case SH_Menu_SubMenuSloppyCloseTimeout:
+ ret = 1000;
+ break;
+ case SH_Menu_SubMenuResetWhenReenteringParent:
+ ret = false;
+ break;
+ case SH_Menu_SubMenuDontStartSloppyOnLeave:
+ ret = false;
+ break;
+
case SH_ProgressDialog_TextLabelAlignment:
ret = Qt::AlignCenter;
break;
diff --git a/src/widgets/styles/qmacstyle_mac.mm b/src/widgets/styles/qmacstyle_mac.mm
index 242a1c1c9e..8b61ca40d7 100644
--- a/src/widgets/styles/qmacstyle_mac.mm
+++ b/src/widgets/styles/qmacstyle_mac.mm
@@ -2547,6 +2547,19 @@ int QMacStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget *w
case SH_Menu_SubMenuPopupDelay:
ret = 100;
break;
+ case SH_Menu_SubMenuUniDirection:
+ ret = true;
+ break;
+ case SH_Menu_SubMenuSloppySelectOtherActions:
+ ret = false;
+ break;
+ case SH_Menu_SubMenuResetWhenReenteringParent:
+ ret = true;
+ break;
+ case SH_Menu_SubMenuDontStartSloppyOnLeave:
+ ret = true;
+ break;
+
case SH_ScrollBar_LeftClickAbsolutePosition: {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
bool result = [defaults boolForKey:@"AppleScrollerPagingBehavior"];
diff --git a/src/widgets/styles/qstyle.cpp b/src/widgets/styles/qstyle.cpp
index fb5a5c9003..5c292ea85d 100644
--- a/src/widgets/styles/qstyle.cpp
+++ b/src/widgets/styles/qstyle.cpp
@@ -1711,6 +1711,32 @@ void QStyle::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment,
other items of the menu. This is supported on most modern
desktop platforms.
+ \value SH_Menu_SubMenuUniDirection Since Qt 5.5. If the cursor has
+ to move towards the submenu (like it is on OS X), or if the
+ cursor can move in any direction as long as it reaches the
+ submenu before the sloppy timeout.
+
+ \value SH_Menu_SubMenuUniDirectionFailCount Since Qt 5.5. When
+ SH_Menu_SubMenuUniDirection is defined this enum defines the
+ number of failed mouse moves before the sloppy submenu is
+ discarded. This can be used to control the "strictness" of the
+ uni direction algorithm.
+
+ \value SH_Menu_SubMenuSloppySelectOtherActions Since Qt 5.5. Should
+ other action items be selected when the mouse moves towards a
+ sloppy submenu.
+
+ \value SH_Menu_SubMenuSloppyCloseTimeout Since Qt 5.5. The timeout
+ used to close sloppy submenus.
+
+ \value SH_Menu_SubMenuResetWhenReenteringParent Since Qt 5.5. When
+ entering parent from child submenu, should the sloppy state be
+ reset, effectively closing the child and making the current
+ submenu active.
+
+ \value SH_Menu_SubMenuDontStartSloppyOnLeave Since Qt 5.5. Do not
+ start sloppy timers when the mouse leaves a sub-menu.
+
\value SH_ScrollView_FrameOnlyAroundContents Whether scrollviews
draw their frame only around contents (like Motif), or around
contents, scroll bars and corner widgets (like Windows).
diff --git a/src/widgets/styles/qstyle.h b/src/widgets/styles/qstyle.h
index a2de839404..2ea7bbe2ee 100644
--- a/src/widgets/styles/qstyle.h
+++ b/src/widgets/styles/qstyle.h
@@ -708,6 +708,12 @@ public:
SH_ComboBox_UseNativePopup,
SH_LineEdit_PasswordMaskDelay,
SH_TabBar_ChangeCurrentDelay,
+ SH_Menu_SubMenuUniDirection,
+ SH_Menu_SubMenuUniDirectionFailCount,
+ SH_Menu_SubMenuSloppySelectOtherActions,
+ SH_Menu_SubMenuSloppyCloseTimeout,
+ SH_Menu_SubMenuResetWhenReenteringParent,
+ SH_Menu_SubMenuDontStartSloppyOnLeave,
// Add new style hint values here
SH_CustomBase = 0xf0000000
diff --git a/src/widgets/styles/qwindowsstyle.cpp b/src/widgets/styles/qwindowsstyle.cpp
index 2f04a20cd0..b26e329628 100644
--- a/src/widgets/styles/qwindowsstyle.cpp
+++ b/src/widgets/styles/qwindowsstyle.cpp
@@ -594,6 +594,17 @@ int QWindowsStyle::styleHint(StyleHint hint, const QStyleOption *opt, const QWid
break;
}
#endif // Q_OS_WIN && !Q_OS_WINRT
+ case SH_Menu_SubMenuSloppyCloseTimeout:
+ case SH_Menu_SubMenuPopupDelay: {
+#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) && !defined(Q_OS_WINRT)
+ DWORD delay;
+ if (SystemParametersInfo(SPI_GETMENUSHOWDELAY, 0, &delay, 0))
+ ret = delay;
+ else
+#endif // Q_OS_WIN && !Q_OS_WINCE && !Q_OS_WINRT
+ ret = 400;
+ break;
+ }
#ifndef QT_NO_RUBBERBAND
case SH_RubberBand_Mask:
if (const QStyleOptionRubberBand *rbOpt = qstyleoption_cast<const QStyleOptionRubberBand *>(opt)) {
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<QMenu> QMenuPrivate::previousMouseMenu(Q_NULLPTR);
+static void handleEnterLeaveEvents(QPointer<QMenu> *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<QMenu*>(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<QMenu *>(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();
}
@@ -3080,23 +3214,13 @@ 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 <typename T>
+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<bool> setFirstMouse(m_first_mouse, false);
+ QSetValueOnDestroy<QPointF> 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<QMenu> 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<QAction> defaultAction;
@@ -265,6 +501,7 @@ public:
QAction* wceCommands(uint command);
#endif
QPointer<QWidget> noReplayFor;
+ static QPointer<QMenu> previousMouseMenu;
};
#endif // QT_NO_MENU