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>
bb10
Dongmei Wang 2016-11-17 14:00:30 -08:00 committed by Gabriel de Dietrich
parent eea585ad0b
commit ae6ef2e3ec
2 changed files with 81 additions and 9 deletions

View File

@ -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();
}

View File

@ -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"