From 8529a8cbb0c6404c758b5efc650186d740b0e09f Mon Sep 17 00:00:00 2001 From: Pierre Rossi Date: Fri, 4 Dec 2009 00:04:09 +0100 Subject: Fixes problem with QMenu when it's populated on the aboutToShow When the menu is populated that late, if the menu is to go off-screen, then it is moved and ends up covering its originating button. Includes some code cleanup thanks to Thierry's tips. Reviewed-by: Gabriel Reviewed-by: Thierry --- src/gui/widgets/qmenu.cpp | 35 +++++++++++++---------------------- src/gui/widgets/qmenu_p.h | 1 + src/gui/widgets/qpushbutton.cpp | 33 ++++++++++++++++++++++++--------- src/gui/widgets/qpushbutton_p.h | 4 ++++ tests/auto/qmenu/tst_qmenu.cpp | 36 +++++++++++++++++++++++++++++++++++- 5 files changed, 77 insertions(+), 32 deletions(-) diff --git a/src/gui/widgets/qmenu.cpp b/src/gui/widgets/qmenu.cpp index 2e27226..ec9683d 100644 --- a/src/gui/widgets/qmenu.cpp +++ b/src/gui/widgets/qmenu.cpp @@ -65,6 +65,8 @@ #include "qmenubar_p.h" #include "qwidgetaction.h" #include "qtoolbutton.h" +#include "qpushbutton.h" +#include #include #include #ifdef QT3_SUPPORT @@ -417,12 +419,7 @@ void QMenuPrivate::hideUpToMenuBar() hideMenu(m, fadeMenus); if (!fadeMenus) // Mac doesn't clear the action until after hidden. m->d_func()->setCurrentAction(0); - } else { -#ifndef QT_NO_TOOLBUTTON - if (qobject_cast(caused) == 0) -#endif - qWarning("QMenu: Internal error"); - caused = 0; + } else { caused = 0; } } #if defined(Q_WS_MAC) @@ -1825,8 +1822,15 @@ void QMenu::popup(const QPoint &p, QAction *atAction) ensurePolished(); // Get the right font emit aboutToShow(); + const bool actionListChanged = d->itemsDirty; d->updateActionRects(); - QPoint pos = p; + QPoint pos; + QPushButton *causedButton = qobject_cast(d->causedPopup.widget); + if (actionListChanged && causedButton) + pos = QPushButtonPrivate::get(causedButton)->adjustedMenuPosition(); + else + pos = p; + QSize size = sizeHint(); QRect screen; #ifndef QT_NO_GRAPHICSVIEW @@ -2302,22 +2306,9 @@ void QMenu::mouseReleaseEvent(QMouseEvent *e) if (action->menu()) action->menu()->d_func()->setFirstActionActive(); else { -#if defined(Q_WS_WIN) && !defined(QT_NO_MENUBAR) +#if defined(Q_WS_WIN) //On Windows only context menus can be activated with the right button - bool isContextMenu = true; - const QWidget *cause = d->causedPopup.widget; - while (cause) { - //if the popup was caused by either QMenuBar or a QToolButton, it is not a context menu - if (qobject_cast(cause) || qobject_cast(cause)) { - isContextMenu = false; - break; - } else if (const QMenu *menu = qobject_cast(cause)) { - cause = menu->d_func()->causedPopup.widget; - } else { - break; - } - } - if (e->button() == Qt::LeftButton || isContextMenu) + if (e->button() == Qt::LeftButton || d->topCausedWidget() == 0) #endif d->activateAction(action, QAction::Trigger); } diff --git a/src/gui/widgets/qmenu_p.h b/src/gui/widgets/qmenu_p.h index 5757885..93017f5 100644 --- a/src/gui/widgets/qmenu_p.h +++ b/src/gui/widgets/qmenu_p.h @@ -182,6 +182,7 @@ public: } void init(); + static QMenuPrivate *get(QMenu *m) { return m->d_func(); } int scrollerHeight() const; //item calculations diff --git a/src/gui/widgets/qpushbutton.cpp b/src/gui/widgets/qpushbutton.cpp index eb34336..849bc43 100644 --- a/src/gui/widgets/qpushbutton.cpp +++ b/src/gui/widgets/qpushbutton.cpp @@ -63,6 +63,7 @@ #include "qaccessible.h" #endif +#include "private/qmenu_p.h" #include "private/qpushbutton_p.h" QT_BEGIN_NAMESPACE @@ -575,12 +576,33 @@ void QPushButtonPrivate::_q_popupPressed() return; menu->setNoReplayFor(q); + + QPoint menuPos = adjustedMenuPosition(); + + QPointer guard(q); + QMenuPrivate::get(menu)->causedPopup.widget = guard; + + //Because of a delay in menu effects, we must keep track of the + //menu visibility to avoid flicker on button release + menuOpen = true; + menu->exec(menuPos); + if (guard) { + menuOpen = false; + q->setDown(false); + } +} + +QPoint QPushButtonPrivate::adjustedMenuPosition() +{ + Q_Q(QPushButton); + bool horizontal = true; #if !defined(QT_NO_TOOLBAR) QToolBar *tb = qobject_cast(parent); if (tb && tb->orientation() == Qt::Vertical) horizontal = false; #endif + QWidgetItem item(q); QRect rect = item.geometry(); rect.setRect(rect.x() - q->x(), rect.y() - q->y(), rect.width(), rect.height()); @@ -603,17 +625,10 @@ void QPushButtonPrivate::_q_popupPressed() else x -= menuSize.width(); } - QPointer guard(q); - //Because of a delay in menu effects, we must keep track of the - //menu visibility to avoid flicker on button release - menuOpen = true; - menu->exec(QPoint(x, y)); - if (guard) { - menuOpen = false; - q->setDown(false); - } + return QPoint(x,y); } + #endif // QT_NO_MENU void QPushButtonPrivate::resetLayoutItemMargins() diff --git a/src/gui/widgets/qpushbutton_p.h b/src/gui/widgets/qpushbutton_p.h index f448027..2510e05 100644 --- a/src/gui/widgets/qpushbutton_p.h +++ b/src/gui/widgets/qpushbutton_p.h @@ -68,6 +68,10 @@ public: defaultButton(false), flat(false), menuOpen(false), lastAutoDefault(false) {} inline void init() { resetLayoutItemMargins(); } + static QPushButtonPrivate* get(QPushButton *b) { return b->d_func(); } +#ifndef QT_NO_MENU + QPoint adjustedMenuPosition(); +#endif void resetLayoutItemMargins(); void _q_popupPressed(); QDialog *dialogParent() const; diff --git a/tests/auto/qmenu/tst_qmenu.cpp b/tests/auto/qmenu/tst_qmenu.cpp index 7cdfe46..6079189 100644 --- a/tests/auto/qmenu/tst_qmenu.cpp +++ b/tests/auto/qmenu/tst_qmenu.cpp @@ -82,7 +82,7 @@ private slots: void keyboardNavigation_data(); void keyboardNavigation(); void focus(); - void overrideMenuAction(); + void overrideMenuAction(); void statusTip(); void widgetActionFocus(); void mouseActivation(); @@ -103,12 +103,14 @@ private slots: void task258920_mouseBorder(); void setFixedWidth(); void deleteActionInTriggered(); + void pushButtonPopulateOnAboutToShow(); protected slots: void onActivated(QAction*); void onHighlighted(QAction*); void onStatusMessageChanged(const QString &); void onStatusTipTimer(); void deleteAction(QAction *a) { delete a; } + void populateMenu(); private: void createActions(); QMenu *menus[2], *lastMenu; @@ -258,6 +260,15 @@ void tst_QMenu::onStatusMessageChanged(const QString &s) statustip=s; } +void tst_QMenu::populateMenu(){ + //just adds 3 dummy actions and a separator. + lastMenu->addAction("Foo"); + lastMenu->addAction("Bar"); + lastMenu->addAction("FooBar"); + lastMenu->addSeparator(); + +} + //actual tests void @@ -896,7 +907,30 @@ void tst_QMenu::deleteActionInTriggered() QVERIFY(!a); } +void tst_QMenu::pushButtonPopulateOnAboutToShow() +{ + QPushButton b("Test PushButton"); + b.setWindowFlags(Qt::FramelessWindowHint); + b.setWindowFlags(Qt::X11BypassWindowManagerHint); + lastMenu = new QMenu; + b.setMenu(lastMenu); + const int scrNumber = QApplication::desktop()->screenNumber(&b); + connect(lastMenu, SIGNAL(aboutToShow()), this, SLOT(populateMenu())); + b.show(); + const QRect screen = QApplication::desktop()->screenGeometry(scrNumber); + b.move(10, screen.bottom()-b.height()-5); + QTest::qWaitForWindowShown(&b); + QTimer::singleShot(300,lastMenu, SLOT(hide())); + QTest::mouseClick(&b, Qt::LeftButton, Qt::NoModifier, b.rect().center()); + QVERIFY(!lastMenu->geometry().intersects(b.geometry())); + + b.move(10, screen.bottom()-lastMenu->height()-5); + QTimer::singleShot(300,lastMenu, SLOT(hide())); + QTest::mouseClick(&b, Qt::LeftButton, Qt::NoModifier, b.rect().center()); + QVERIFY(!lastMenu->geometry().intersects(b.geometry())); + +} QTEST_MAIN(tst_QMenu) -- cgit v0.12