summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDongmei Wang <dongmei.wang@qt.io>2016-11-17 14:00:30 -0800
committerGabriel de Dietrich <gabriel.dedietrich@qt.io>2017-04-14 17:06:12 +0000
commitae6ef2e3ec2174b2253fd04e1d69b949d3130067 (patch)
tree3155d95e4a3eebc93afc72d5597d7a43e9cbd4fc
parenteea585ad0bfb92947ae2d77f3bc6662121cec9a9 (diff)
QMenu: Fix torn-off menu display crash issue
When tearing off either a non-scrollable multi-colume menu or a scrollable menu, displaying the torn-off menu crashes. The root cause is when the torn-off menu is created, the tear-off menu's style, margins and other attributes are not set to it. The patch is to ensure the torn-off menu has the same attributes as the tear-off menu does and set the torn-off menu with a correct menu size. Task-number: QTBUG-24815 Change-Id: Icea45f149ea8792671af4a62e62cad6ee01a1f95 Reviewed-by: Gabriel de Dietrich <gabriel.dedietrich@qt.io>
-rw-r--r--src/widgets/widgets/qmenu.cpp53
-rw-r--r--tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp37
2 files changed, 81 insertions, 9 deletions
diff --git a/src/widgets/widgets/qmenu.cpp b/src/widgets/widgets/qmenu.cpp
index f08f573391..995a08e310 100644
--- a/src/widgets/widgets/qmenu.cpp
+++ b/src/widgets/widgets/qmenu.cpp
@@ -84,18 +84,38 @@ class QTornOffMenu : public QMenu
Q_OBJECT
class QTornOffMenuPrivate : public QMenuPrivate
{
- Q_DECLARE_PUBLIC(QMenu)
+ Q_DECLARE_PUBLIC(QTornOffMenu)
public:
- QTornOffMenuPrivate(QMenu *p) : causedMenu(p) {
+ QTornOffMenuPrivate(QMenu *p) : causedMenu(p), initialized(false) {
tornoff = 1;
causedPopup.widget = 0;
- causedPopup.action = ((QTornOffMenu*)p)->d_func()->causedPopup.action;
- causedStack = ((QTornOffMenu*)p)->d_func()->calcCausedStack();
+ causedPopup.action = p->d_func()->causedPopup.action;
+ causedStack = p->d_func()->calcCausedStack();
+ }
+
+ void setMenuSize(const QSize &menuSize) {
+ Q_Q(QTornOffMenu);
+ QSize size = menuSize;
+ const QPoint p = (!initialized) ? causedMenu->pos() : q->pos();
+ QRect screen = popupGeometry(QApplication::desktop()->screenNumber(p));
+ const int desktopFrame = q->style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, 0, q);
+ const int titleBarHeight = q->style()->pixelMetric(QStyle::PM_TitleBarHeight, 0, q);
+ if (scroll && (size.height() > screen.height() - titleBarHeight || size.width() > screen.width())) {
+ const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, q);
+ const int hmargin = q->style()->pixelMetric(QStyle::PM_MenuHMargin, 0, q);
+ scroll->scrollFlags |= uint(QMenuPrivate::QMenuScroller::ScrollDown);
+ size.setWidth(qMin(actionRects.at(getLastVisibleAction()).right() + fw + hmargin + rightmargin + 1, screen.width()));
+ size.setHeight(screen.height() - desktopFrame * 2 - titleBarHeight);
+ }
+ q->setFixedSize(size);
}
+
QVector<QPointer<QWidget> > calcCausedStack() const Q_DECL_OVERRIDE { return causedStack; }
QPointer<QMenu> causedMenu;
QVector<QPointer<QWidget> > causedStack;
+ bool initialized;
};
+
public:
QTornOffMenu(QMenu *p) : QMenu(*(new QTornOffMenuPrivate(p)))
{
@@ -109,11 +129,20 @@ public:
setAttribute(Qt::WA_X11NetWmWindowTypeMenu, true);
setWindowTitle(p->windowTitle());
setEnabled(p->isEnabled());
+ setStyleSheet(p->styleSheet());
+ if (style() != p->style())
+ setStyle(p->style());
+ int leftMargin, topMargin, rightMargin, bottomMargin;
+ p->getContentsMargins(&leftMargin, &topMargin, &rightMargin, &bottomMargin);
+ setContentsMargins(leftMargin, topMargin, rightMargin, bottomMargin);
+ setLayoutDirection(p->layoutDirection());
//QObject::connect(this, SIGNAL(triggered(QAction*)), this, SLOT(onTrigger(QAction*)));
//QObject::connect(this, SIGNAL(hovered(QAction*)), this, SLOT(onHovered(QAction*)));
QList<QAction*> items = p->actions();
for(int i = 0; i < items.count(); i++)
addAction(items.at(i));
+ d->setMenuSize(sizeHint());
+ d->initialized = true;
}
void syncWithMenu(QMenu *menu, QActionEvent *act)
{
@@ -127,12 +156,17 @@ public:
}
void actionEvent(QActionEvent *e) Q_DECL_OVERRIDE
{
+ Q_D(QTornOffMenu);
QMenu::actionEvent(e);
- setFixedSize(sizeHint());
+ if (d->initialized) {
+ d->setMenuSize(sizeHint());
+ }
}
+
public slots:
void onTrigger(QAction *action) { d_func()->activateAction(action, QAction::Trigger, false); }
void onHovered(QAction *action) { d_func()->activateAction(action, QAction::Hover, false); }
+
private:
Q_DECLARE_PRIVATE(QTornOffMenu)
friend class QMenuPrivate;
@@ -369,9 +403,11 @@ void QMenuPrivate::updateActionRects(const QRect &screen) const
}
max_column_width += tabWidth; //finally add in the tab width
- const int sfcMargin = style->sizeFromContents(QStyle::CT_Menu, &opt, QApplication::globalStrut(), q).width() - QApplication::globalStrut().width();
- const int min_column_width = q->minimumWidth() - (sfcMargin + leftmargin + rightmargin + 2 * (fw + hmargin));
- max_column_width = qMax(min_column_width, max_column_width);
+ if (!tornoff || (tornoff && scroll)) { // exclude non-scrollable tear-off menu since the tear-off menu has a fixed size
+ const int sfcMargin = style->sizeFromContents(QStyle::CT_Menu, &opt, QApplication::globalStrut(), q).width() - QApplication::globalStrut().width();
+ const int min_column_width = q->minimumWidth() - (sfcMargin + leftmargin + rightmargin + 2 * (fw + hmargin));
+ max_column_width = qMax(min_column_width, max_column_width);
+ }
//calculate position
int x = hmargin + fw + leftmargin;
@@ -3512,7 +3548,6 @@ void QMenu::actionEvent(QActionEvent *e)
}
if (isVisible()) {
- d->updateActionRects();
resize(sizeHint());
update();
}
diff --git a/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp b/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp
index 9109b2e8a3..e5e2e157c5 100644
--- a/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp
+++ b/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp
@@ -130,6 +130,8 @@ private slots:
void QTBUG_56917_wideSubmenuScreenNumber();
void menuSize_Scrolling_data();
void menuSize_Scrolling();
+ void tearOffMenuNotDisplayed();
+
protected slots:
void onActivated(QAction*);
void onHighlighted(QAction*);
@@ -1556,5 +1558,40 @@ void tst_QMenu::menuSize_Scrolling()
menu.height() - mm.fw - mm.vmargin - bottomMargin - 1);
}
+void tst_QMenu::tearOffMenuNotDisplayed()
+{
+ QWidget widget;
+ QScopedPointer<QMenu> menu(new QMenu(&widget));
+ menu->setTearOffEnabled(true);
+ QVERIFY(menu->isTearOffEnabled());
+
+ menu->setStyleSheet("QMenu { menu-scrollable: 1 }");
+ for (int i = 0; i < 80; i++)
+ menu->addAction(QString::number(i));
+
+ widget.resize(300, 200);
+ centerOnScreen(&widget);
+ widget.show();
+ widget.activateWindow();
+ QVERIFY(QTest::qWaitForWindowActive(&widget));
+ menu->popup(widget.geometry().topRight() + QPoint(50, 0));
+ QVERIFY(QTest::qWaitForWindowActive(menu.data()));
+ QVERIFY(!menu->isTearOffMenuVisible());
+
+ MenuMetrics mm(menu.data());
+ const int tearOffOffset = mm.fw + mm.vmargin + mm.tearOffHeight / 2;
+
+ QTest::mouseClick(menu.data(), Qt::LeftButton, 0, QPoint(10, tearOffOffset), 10);
+ QTRY_VERIFY(menu->isTearOffMenuVisible());
+ QPointer<QMenu> torn = getTornOffMenu();
+ QVERIFY(torn);
+ QVERIFY(torn->isVisible());
+ QVERIFY(torn->minimumWidth() >=0 && torn->minimumWidth() < QWIDGETSIZE_MAX);
+
+ menu->hideTearOffMenu();
+ QVERIFY(!menu->isTearOffMenuVisible());
+ QVERIFY(!torn->isVisible());
+}
+
QTEST_MAIN(tst_QMenu)
#include "tst_qmenu.moc"