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

View File

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