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>
This commit is contained in:
parent
fb44d3838a
commit
0ed68f3f58
@ -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;
|
||||
|
@ -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"];
|
||||
|
@ -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).
|
||||
|
@ -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
|
||||
|
@ -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)) {
|
||||
|
@ -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,26 +630,124 @@ 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
|
||||
QWidget *QMenuPrivate::topCausedWidget() const
|
||||
{
|
||||
@ -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();
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -188,7 +188,6 @@ protected:
|
||||
#endif
|
||||
|
||||
private Q_SLOTS:
|
||||
void internalSetSloppyAction();
|
||||
void internalDelayedPopup();
|
||||
|
||||
private:
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user