Fix menu size in multiscreen setups

During QMenuPrivate::popup() and QMenuBarPrivate::popupAction(),
QMenuPrivate::popupGeometry() is called from updateActionRects() with
screen = 0 several times (from sizeHint() and various event handlers
triggered), which causes it to return the primary screen geometry
always.

To fix this for the non-QGraphicsView case, use the screen of
the menu when it is visible or the screen stored in a newly introduced
popupScreen member variable, which is set from a few places
in QMenuPrivate::popup().

Fixes: QTBUG-118434
Pick-to: 6.7 6.6 6.5
Change-Id: I6b18593d313719d628b0856004197ac59f46c270
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Friedemann Kleint 2023-11-08 22:23:00 +01:00 committed by Volker Hilsheimer
parent 9069b7fb20
commit 8cd7a3d472
3 changed files with 25 additions and 4 deletions

View File

@ -292,6 +292,13 @@ inline bool QMenuPrivate::useFullScreenForPopup() const
QRect QMenuPrivate::popupGeometry(QScreen *screen) const
{
Q_Q(const QMenu);
if (screen == nullptr
#if QT_CONFIG(graphicsview)
&& q->graphicsProxyWidget() == nullptr
#endif
) {
screen = q->isVisible() ? q->screen() : popupScreen.data();
}
if (useFullScreenForPopup())
return screen ? screen->geometry()
: QWidgetPrivate::screenGeometry(q);
@ -2309,6 +2316,9 @@ void QMenu::popup(const QPoint &p, QAction *atAction)
void QMenuPrivate::popup(const QPoint &p, QAction *atAction, PositionFunction positionFunction)
{
Q_Q(QMenu);
popupScreen = QGuiApplication::screenAt(p);
QScopeGuard popupScreenGuard([this](){ popupScreen.clear(); });
if (scroll) { // reset scroll state from last popup
if (scroll->scrollOffset)
itemsDirty = 1; // sizeHint will be incorrect if there is previous scroll
@ -2384,6 +2394,7 @@ void QMenuPrivate::popup(const QPoint &p, QAction *atAction, PositionFunction po
pos = QPushButtonPrivate::get(causedButton)->adjustedMenuPosition();
else
pos = p;
popupScreen = QGuiApplication::screenAt(pos);
const QSize menuSizeHint(q->sizeHint());
QSize size = menuSizeHint;
@ -2522,6 +2533,7 @@ void QMenuPrivate::popup(const QPoint &p, QAction *atAction, PositionFunction po
}
}
}
popupScreen = QGuiApplication::screenAt(pos);
q->setGeometry(QRect(pos, size));
#if QT_CONFIG(effects)
int hGuess = q->isRightToLeft() ? QEffects::LeftScroll : QEffects::RightScroll;

View File

@ -473,6 +473,11 @@ public:
mutable quint8 ncols = 0; // "255cols ought to be enough for anybody."
// Contains the screen of the popup point during popup(QPoint).
// This is to make sure the screen is remembered,
// when the menu contains many items on multiple screens
QPointer<QScreen> popupScreen;
mutable bool itemsDirty : 1;
mutable bool hasCheckableItems : 1;
bool lastContextMenu : 1;

View File

@ -281,17 +281,21 @@ void QMenuBarPrivate::popupAction(QAction *action, bool activateFirst)
if (action->isEnabled() && action->menu()->isEnabled()) {
closePopupMode = 0;
activeMenu = action->menu();
activeMenu->d_func()->causedPopup.widget = q;
activeMenu->d_func()->causedPopup.action = action;
auto *activeMenuPriv = activeMenu->d_func();
activeMenuPriv->causedPopup.widget = q;
activeMenuPriv->causedPopup.action = action;
QRect adjustedActionRect = actionRect(action);
QPoint pos(q->mapToGlobal(QPoint(adjustedActionRect.left(), adjustedActionRect.bottom() + 1)));
QSize popup_size = activeMenu->sizeHint();
//we put the popup menu on the screen containing the bottom-center of the action rect
QScreen *menubarScreen = q->window()->windowHandle()->screen();
QScreen *popupScreen = menubarScreen->virtualSiblingAt(pos + QPoint(adjustedActionRect.width() / 2, 0));
QPointer<QScreen> popupScreen = menubarScreen->virtualSiblingAt(pos + QPoint(adjustedActionRect.width() / 2, 0));
if (!popupScreen)
popupScreen = menubarScreen;
std::swap(popupScreen, activeMenuPriv->popupScreen);
const QSize popup_size = activeMenu->sizeHint();
std::swap(popupScreen, activeMenuPriv->popupScreen);
QRect screenRect = popupScreen->geometry();
pos = QPoint(qMax(pos.x(), screenRect.x()), qMax(pos.y(), screenRect.y()));
const bool fitUp = (pos.y() - popup_size.height() >= screenRect.top());