QMenu: Move exec(), popup() to private class

Task-number: QTBUG-78966
Change-Id: I69257dc52706449a1e0babfc29e5f93f63d9291b
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
This commit is contained in:
Friedemann Kleint 2019-12-05 09:02:21 +01:00
parent 2b78e96d23
commit ef14e775de
2 changed files with 98 additions and 84 deletions

View File

@ -2319,76 +2319,82 @@ QSize QMenu::sizeHint() const
void QMenu::popup(const QPoint &p, QAction *atAction) void QMenu::popup(const QPoint &p, QAction *atAction)
{ {
Q_D(QMenu); Q_D(QMenu);
if (d->scroll) { // reset scroll state from last popup d->popup(p, atAction);
if (d->scroll->scrollOffset) }
d->itemsDirty = 1; // sizeHint will be incorrect if there is previous scroll
d->scroll->scrollOffset = 0; void QMenuPrivate::popup(const QPoint &p, QAction *atAction)
d->scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone; {
Q_Q(QMenu);
if (scroll) { // reset scroll state from last popup
if (scroll->scrollOffset)
itemsDirty = 1; // sizeHint will be incorrect if there is previous scroll
scroll->scrollOffset = 0;
scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone;
} }
d->tearoffHighlighted = 0; tearoffHighlighted = 0;
d->motions = 0; motions = 0;
d->doChildEffects = true; doChildEffects = true;
d->updateLayoutDirection(); updateLayoutDirection();
// Ensure that we get correct sizeHints by placing this window on the correct screen. // Ensure that we get correct sizeHints by placing this window on the correct screen.
// However if the QMenu was constructed with a QDesktopScreenWidget as its parent, // However if the QMenu was constructed with a QDesktopScreenWidget as its parent,
// then initialScreenIndex was set, so we should respect that for the lifetime of this menu. // then initialScreenIndex was set, so we should respect that for the lifetime of this menu.
// Use d->popupScreen to remember, because initialScreenIndex will be reset after the first showing. // Use d->popupScreen to remember, because initialScreenIndex will be reset after the first showing.
// However if eventLoop exists, then exec() already did this by calling createWinId(); so leave it alone. (QTBUG-76162) // However if eventLoop exists, then exec() already did this by calling createWinId(); so leave it alone. (QTBUG-76162)
if (!d->eventLoop) { if (!eventLoop) {
const int screenIndex = d->topData()->initialScreenIndex; const int screenIndex = topData()->initialScreenIndex;
if (screenIndex >= 0) if (screenIndex >= 0)
d->popupScreen = screenIndex; popupScreen = screenIndex;
if (auto s = QGuiApplication::screens().value(d->popupScreen)) { if (auto s = QGuiApplication::screens().value(popupScreen)) {
if (d->setScreen(s)) if (setScreen(s))
d->itemsDirty = true; itemsDirty = true;
} else if (d->setScreenForPoint(p)) { } else if (setScreenForPoint(p)) {
d->itemsDirty = true; itemsDirty = true;
} }
} }
const bool contextMenu = d->isContextMenu(); const bool contextMenu = isContextMenu();
if (d->lastContextMenu != contextMenu) { if (lastContextMenu != contextMenu) {
d->itemsDirty = true; itemsDirty = true;
d->lastContextMenu = contextMenu; lastContextMenu = contextMenu;
} }
#if QT_CONFIG(menubar) #if QT_CONFIG(menubar)
// if this menu is part of a chain attached to a QMenuBar, set the // if this menu is part of a chain attached to a QMenuBar, set the
// _NET_WM_WINDOW_TYPE_DROPDOWN_MENU X11 window type // _NET_WM_WINDOW_TYPE_DROPDOWN_MENU X11 window type
setAttribute(Qt::WA_X11NetWmWindowTypeDropDownMenu, qobject_cast<QMenuBar *>(d->topCausedWidget()) != 0); q->setAttribute(Qt::WA_X11NetWmWindowTypeDropDownMenu, qobject_cast<QMenuBar *>(topCausedWidget()) != nullptr);
#endif #endif
ensurePolished(); // Get the right font q->ensurePolished(); // Get the right font
emit aboutToShow(); emit q->aboutToShow();
const bool actionListChanged = d->itemsDirty; const bool actionListChanged = itemsDirty;
QRect screen; QRect screen;
#if QT_CONFIG(graphicsview) #if QT_CONFIG(graphicsview)
bool isEmbedded = !bypassGraphicsProxyWidget(this) && QMenuPrivate::nearestGraphicsProxyWidget(this); bool isEmbedded = !bypassGraphicsProxyWidget(q) && QMenuPrivate::nearestGraphicsProxyWidget(q);
if (isEmbedded) if (isEmbedded)
screen = d->popupGeometry(); screen = popupGeometry();
else else
#endif #endif
screen = d->popupGeometry(QDesktopWidgetPrivate::screenNumber(p)); screen = popupGeometry(QDesktopWidgetPrivate::screenNumber(p));
d->updateActionRects(screen); updateActionRects(screen);
QPoint pos; QPoint pos;
QPushButton *causedButton = qobject_cast<QPushButton*>(d->causedPopup.widget); QPushButton *causedButton = qobject_cast<QPushButton*>(causedPopup.widget);
if (actionListChanged && causedButton) if (actionListChanged && causedButton)
pos = QPushButtonPrivate::get(causedButton)->adjustedMenuPosition(); pos = QPushButtonPrivate::get(causedButton)->adjustedMenuPosition();
else else
pos = p; pos = p;
const QSize menuSizeHint(sizeHint()); const QSize menuSizeHint(q->sizeHint());
QSize size = menuSizeHint; QSize size = menuSizeHint;
const int desktopFrame = style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, nullptr, this); const int desktopFrame = q->style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, nullptr, q);
bool adjustToDesktop = !window()->testAttribute(Qt::WA_DontShowOnScreen); bool adjustToDesktop = !q->window()->testAttribute(Qt::WA_DontShowOnScreen);
// if the screens have very different geometries and the menu is too big, we have to recalculate // if the screens have very different geometries and the menu is too big, we have to recalculate
if ((size.height() > screen.height() || size.width() > screen.width()) || if ((size.height() > screen.height() || size.width() > screen.width()) ||
// Layout is not right, we might be able to save horizontal space // Layout is not right, we might be able to save horizontal space
(d->ncols >1 && size.height() < screen.height())) { (ncols >1 && size.height() < screen.height())) {
size.setWidth(qMin(menuSizeHint.width(), screen.width() - desktopFrame * 2)); size.setWidth(qMin(menuSizeHint.width(), screen.width() - desktopFrame * 2));
size.setHeight(qMin(menuSizeHint.height(), screen.height() - desktopFrame * 2)); size.setHeight(qMin(menuSizeHint.height(), screen.height() - desktopFrame * 2));
adjustToDesktop = true; adjustToDesktop = true;
@ -2397,61 +2403,61 @@ void QMenu::popup(const QPoint &p, QAction *atAction)
#ifdef QT_KEYPAD_NAVIGATION #ifdef QT_KEYPAD_NAVIGATION
if (!atAction && QApplicationPrivate::keypadNavigationEnabled()) { if (!atAction && QApplicationPrivate::keypadNavigationEnabled()) {
// Try to have one item activated // Try to have one item activated
if (d->defaultAction && d->defaultAction->isEnabled()) { if (defaultAction && defaultAction->isEnabled()) {
atAction = d->defaultAction; atAction = defaultAction;
// TODO: This works for first level menus, not yet sub menus // TODO: This works for first level menus, not yet sub menus
} else { } else {
for (QAction *action : qAsConst(d->actions)) for (QAction *action : qAsConst(actions))
if (action->isEnabled()) { if (action->isEnabled()) {
atAction = action; atAction = action;
break; break;
} }
} }
d->currentAction = atAction; currentAction = atAction;
} }
#endif #endif
if (d->ncols > 1) { if (ncols > 1) {
pos.setY(screen.top() + desktopFrame); pos.setY(screen.top() + desktopFrame);
} else if (atAction) { } else if (atAction) {
for (int i = 0, above_height = 0; i < d->actions.count(); i++) { for (int i = 0, above_height = 0; i < actions.count(); i++) {
QAction *action = d->actions.at(i); QAction *action = actions.at(i);
if (action == atAction) { if (action == atAction) {
int newY = pos.y() - above_height; int newY = pos.y() - above_height;
if (d->scroll && newY < desktopFrame) { if (scroll && newY < desktopFrame) {
d->scroll->scrollFlags = d->scroll->scrollFlags scroll->scrollFlags = scroll->scrollFlags
| QMenuPrivate::QMenuScroller::ScrollUp; | QMenuPrivate::QMenuScroller::ScrollUp;
d->scroll->scrollOffset = newY; scroll->scrollOffset = newY;
newY = desktopFrame; newY = desktopFrame;
} }
pos.setY(newY); pos.setY(newY);
if (d->scroll && d->scroll->scrollFlags != QMenuPrivate::QMenuScroller::ScrollNone if (scroll && scroll->scrollFlags != QMenuPrivate::QMenuScroller::ScrollNone
&& !style()->styleHint(QStyle::SH_Menu_FillScreenWithScroll, nullptr, this)) { && !q->style()->styleHint(QStyle::SH_Menu_FillScreenWithScroll, nullptr, q)) {
int below_height = above_height + d->scroll->scrollOffset; int below_height = above_height + scroll->scrollOffset;
for (int i2 = i; i2 < d->actionRects.count(); i2++) for (int i2 = i; i2 < actionRects.count(); i2++)
below_height += d->actionRects.at(i2).height(); below_height += actionRects.at(i2).height();
size.setHeight(below_height); size.setHeight(below_height);
} }
break; break;
} else { } else {
above_height += d->actionRects.at(i).height(); above_height += actionRects.at(i).height();
} }
} }
} }
QPoint mouse = QCursor::pos(); QPoint mouse = QCursor::pos();
d->mousePopupPos = mouse; mousePopupPos = mouse;
const bool snapToMouse = !d->causedPopup.widget && (QRect(p.x() - 3, p.y() - 3, 6, 6).contains(mouse)); const bool snapToMouse = !causedPopup.widget && (QRect(p.x() - 3, p.y() - 3, 6, 6).contains(mouse));
if (adjustToDesktop) { if (adjustToDesktop) {
// handle popup falling "off screen" // handle popup falling "off screen"
if (isRightToLeft()) { if (q->isRightToLeft()) {
if (snapToMouse) // position flowing left from the mouse if (snapToMouse) // position flowing left from the mouse
pos.setX(mouse.x() - size.width()); pos.setX(mouse.x() - size.width());
#if QT_CONFIG(menubar) #if QT_CONFIG(menubar)
// if the menu is in a menubar or is a submenu, it should be right-aligned // if the menu is in a menubar or is a submenu, it should be right-aligned
if (qobject_cast<QMenuBar*>(d->causedPopup.widget) || qobject_cast<QMenu*>(d->causedPopup.widget)) if (qobject_cast<QMenuBar*>(causedPopup.widget) || qobject_cast<QMenu*>(causedPopup.widget))
pos.rx() -= size.width(); pos.rx() -= size.width();
#endif // QT_CONFIG(menubar) #endif // QT_CONFIG(menubar)
@ -2475,8 +2481,8 @@ void QMenu::popup(const QPoint &p, QAction *atAction)
if (pos.y() < screen.top() + desktopFrame) if (pos.y() < screen.top() + desktopFrame)
pos.setY(screen.top() + desktopFrame); pos.setY(screen.top() + desktopFrame);
if (pos.y() + menuSizeHint.height() - 1 > screen.bottom() - desktopFrame) { if (pos.y() + menuSizeHint.height() - 1 > screen.bottom() - desktopFrame) {
if (d->scroll) { if (scroll) {
d->scroll->scrollFlags |= uint(QMenuPrivate::QMenuScroller::ScrollDown); scroll->scrollFlags |= uint(QMenuPrivate::QMenuScroller::ScrollDown);
int y = qMax(screen.y(),pos.y()); int y = qMax(screen.y(),pos.y());
size.setHeight(screen.bottom() - (desktopFrame * 2) - y); size.setHeight(screen.bottom() - (desktopFrame * 2) - y);
} else { } else {
@ -2485,13 +2491,13 @@ void QMenu::popup(const QPoint &p, QAction *atAction)
} }
} }
} }
const int subMenuOffset = style()->pixelMetric(QStyle::PM_SubMenuOverlap, nullptr, this); const int subMenuOffset = q->style()->pixelMetric(QStyle::PM_SubMenuOverlap, nullptr, q);
QMenu *caused = qobject_cast<QMenu*>(d_func()->causedPopup.widget); QMenu *caused = qobject_cast<QMenu*>(causedPopup.widget);
if (caused && caused->geometry().width() + menuSizeHint.width() + subMenuOffset < screen.width()) { if (caused && caused->geometry().width() + menuSizeHint.width() + subMenuOffset < screen.width()) {
QRect parentActionRect(caused->d_func()->actionRect(caused->d_func()->currentAction)); QRect parentActionRect(caused->d_func()->actionRect(caused->d_func()->currentAction));
const QPoint actionTopLeft = caused->mapToGlobal(parentActionRect.topLeft()); const QPoint actionTopLeft = caused->mapToGlobal(parentActionRect.topLeft());
parentActionRect.moveTopLeft(actionTopLeft); parentActionRect.moveTopLeft(actionTopLeft);
if (isRightToLeft()) { if (q->isRightToLeft()) {
if ((pos.x() + menuSizeHint.width() > parentActionRect.left() - subMenuOffset) if ((pos.x() + menuSizeHint.width() > parentActionRect.left() - subMenuOffset)
&& (pos.x() < parentActionRect.right())) && (pos.x() < parentActionRect.right()))
{ {
@ -2513,61 +2519,61 @@ void QMenu::popup(const QPoint &p, QAction *atAction)
} }
} }
} }
setGeometry(QRect(pos, size)); q->setGeometry(QRect(pos, size));
#if QT_CONFIG(effects) #if QT_CONFIG(effects)
int hGuess = isRightToLeft() ? QEffects::LeftScroll : QEffects::RightScroll; int hGuess = q->isRightToLeft() ? QEffects::LeftScroll : QEffects::RightScroll;
int vGuess = QEffects::DownScroll; int vGuess = QEffects::DownScroll;
if (isRightToLeft()) { if (q->isRightToLeft()) {
if ((snapToMouse && (pos.x() + size.width() / 2 > mouse.x())) || if ((snapToMouse && (pos.x() + size.width() / 2 > mouse.x())) ||
(qobject_cast<QMenu*>(d->causedPopup.widget) && pos.x() + size.width() / 2 > d->causedPopup.widget->x())) (qobject_cast<QMenu*>(causedPopup.widget) && pos.x() + size.width() / 2 > causedPopup.widget->x()))
hGuess = QEffects::RightScroll; hGuess = QEffects::RightScroll;
} else { } else {
if ((snapToMouse && (pos.x() + size.width() / 2 < mouse.x())) || if ((snapToMouse && (pos.x() + size.width() / 2 < mouse.x())) ||
(qobject_cast<QMenu*>(d->causedPopup.widget) && pos.x() + size.width() / 2 < d->causedPopup.widget->x())) (qobject_cast<QMenu*>(causedPopup.widget) && pos.x() + size.width() / 2 < causedPopup.widget->x()))
hGuess = QEffects::LeftScroll; hGuess = QEffects::LeftScroll;
} }
#if QT_CONFIG(menubar) #if QT_CONFIG(menubar)
if ((snapToMouse && (pos.y() + size.height() / 2 < mouse.y())) || if ((snapToMouse && (pos.y() + size.height() / 2 < mouse.y())) ||
(qobject_cast<QMenuBar*>(d->causedPopup.widget) && (qobject_cast<QMenuBar*>(causedPopup.widget) &&
pos.y() + size.width() / 2 < d->causedPopup.widget->mapToGlobal(d->causedPopup.widget->pos()).y())) pos.y() + size.width() / 2 < causedPopup.widget->mapToGlobal(causedPopup.widget->pos()).y()))
vGuess = QEffects::UpScroll; vGuess = QEffects::UpScroll;
#endif #endif
if (QApplication::isEffectEnabled(Qt::UI_AnimateMenu)) { if (QApplication::isEffectEnabled(Qt::UI_AnimateMenu)) {
bool doChildEffects = true; bool doChildEffects = true;
#if QT_CONFIG(menubar) #if QT_CONFIG(menubar)
if (QMenuBar *mb = qobject_cast<QMenuBar*>(d->causedPopup.widget)) { if (QMenuBar *mb = qobject_cast<QMenuBar*>(causedPopup.widget)) {
doChildEffects = mb->d_func()->doChildEffects; doChildEffects = mb->d_func()->doChildEffects;
mb->d_func()->doChildEffects = false; mb->d_func()->doChildEffects = false;
} else } else
#endif #endif
if (QMenu *m = qobject_cast<QMenu*>(d->causedPopup.widget)) { if (QMenu *m = qobject_cast<QMenu*>(causedPopup.widget)) {
doChildEffects = m->d_func()->doChildEffects; doChildEffects = m->d_func()->doChildEffects;
m->d_func()->doChildEffects = false; m->d_func()->doChildEffects = false;
} }
if (doChildEffects) { if (doChildEffects) {
if (QApplication::isEffectEnabled(Qt::UI_FadeMenu)) if (QApplication::isEffectEnabled(Qt::UI_FadeMenu))
qFadeEffect(this); qFadeEffect(q);
else if (d->causedPopup.widget) else if (causedPopup.widget)
qScrollEffect(this, qobject_cast<QMenu*>(d->causedPopup.widget) ? hGuess : vGuess); qScrollEffect(q, qobject_cast<QMenu*>(causedPopup.widget) ? hGuess : vGuess);
else else
qScrollEffect(this, hGuess | vGuess); qScrollEffect(q, hGuess | vGuess);
} else { } else {
// kill any running effect // kill any running effect
qFadeEffect(nullptr); qFadeEffect(nullptr);
qScrollEffect(nullptr); qScrollEffect(nullptr);
show(); q->show();
} }
} else } else
#endif #endif
{ {
show(); q->show();
} }
#ifndef QT_NO_ACCESSIBILITY #ifndef QT_NO_ACCESSIBILITY
QAccessibleEvent event(this, QAccessible::PopupMenuStart); QAccessibleEvent event(q, QAccessible::PopupMenuStart);
QAccessible::updateAccessibility(&event); QAccessible::updateAccessibility(&event);
#endif #endif
} }
@ -2633,20 +2639,26 @@ QAction *QMenu::exec()
QAction *QMenu::exec(const QPoint &p, QAction *action) QAction *QMenu::exec(const QPoint &p, QAction *action)
{ {
Q_D(QMenu); Q_D(QMenu);
ensurePolished(); return d->exec(p, action);
createWinId(); }
QEventLoop eventLoop;
d->eventLoop = &eventLoop; QAction *QMenuPrivate::exec(const QPoint &p, QAction *action)
{
Q_Q(QMenu);
q->ensurePolished();
q->createWinId();
QEventLoop evtLoop;
eventLoop = &evtLoop;
popup(p, action); popup(p, action);
QPointer<QObject> guard = this; QPointer<QObject> guard = q;
(void) eventLoop.exec(); (void) evtLoop.exec();
if (guard.isNull()) if (guard.isNull())
return nullptr; return nullptr;
action = d->syncAction; action = syncAction;
d->syncAction = nullptr; syncAction = nullptr;
d->eventLoop = nullptr; eventLoop = nullptr;
return action; return action;
} }

View File

@ -351,6 +351,8 @@ public:
QRect popupGeometry(int screen) const; QRect popupGeometry(int screen) const;
bool useFullScreenForPopup() const; bool useFullScreenForPopup() const;
int getLastVisibleAction() const; int getLastVisibleAction() const;
void popup(const QPoint &p, QAction *atAction);
QAction *exec(const QPoint &p, QAction *action);
//selection //selection
static QMenu *mouseDown; static QMenu *mouseDown;