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:
Jørgen Lind 2014-05-21 13:57:09 +02:00
parent fb44d3838a
commit 0ed68f3f58
8 changed files with 519 additions and 106 deletions

View File

@ -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;

View File

@ -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"];

View File

@ -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).

View File

@ -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

View File

@ -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)) {

View File

@ -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);
}
/*!

View File

@ -188,7 +188,6 @@ protected:
#endif
private Q_SLOTS:
void internalSetSloppyAction();
void internalDelayedPopup();
private:

View File

@ -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