1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QtTest>
30#include <QtTest/private/qtesthelpers_p.h>
31#include <qapplication.h>
32#include <private/qguiapplication_p.h>
33#include <QPushButton>
34#include <QMainWindow>
35#include <QMenuBar>
36#include <QToolBar>
37#include <QToolButton>
38#include <QStatusBar>
39#include <QListWidget>
40#include <QWidgetAction>
41#include <QDesktopWidget>
42#include <QScreen>
43#include <QSpinBox>
44#include <qdialog.h>
45
46#include <qmenu.h>
47#include <qstyle.h>
48#include <QStyleHints>
49#include <QTimer>
50#include <qdebug.h>
51
52#include <qpa/qplatformtheme.h>
53#include <qpa/qplatformintegration.h>
54
55using namespace QTestPrivate;
56
57Q_DECLARE_METATYPE(Qt::Key);
58Q_DECLARE_METATYPE(Qt::KeyboardModifiers);
59
60struct MenuMetrics {
61 int fw;
62 int hmargin;
63 int vmargin;
64 int tearOffHeight;
65
66 MenuMetrics(const QMenu *menu) {
67 fw = menu->style()->pixelMetric(metric: QStyle::PM_MenuPanelWidth, option: nullptr, widget: menu);
68 hmargin = menu->style()->pixelMetric(metric: QStyle::PM_MenuHMargin, option: nullptr, widget: menu);
69 vmargin = menu->style()->pixelMetric(metric: QStyle::PM_MenuVMargin, option: nullptr, widget: menu);
70 tearOffHeight = menu->style()->pixelMetric(metric: QStyle::PM_MenuTearoffHeight, option: nullptr, widget: menu);
71 }
72};
73
74class tst_QMenu : public QObject
75{
76 Q_OBJECT
77
78public:
79 tst_QMenu();
80
81public slots:
82 void initTestCase();
83 void cleanupTestCase();
84 void init();
85private slots:
86 void getSetCheck();
87 void addActionsAndClear();
88 void addActionsConnect();
89
90 void keyboardNavigation_data();
91 void keyboardNavigation();
92 void focus();
93 void overrideMenuAction();
94 void statusTip();
95 void widgetActionFocus();
96 void mouseActivation();
97 void tearOff();
98 void submenuTearOffDontClose();
99 void layoutDirection();
100
101 void task208001_stylesheet();
102 void activeSubMenuPosition();
103 void activeSubMenuPositionExec();
104 void task242454_sizeHint();
105 void task176201_clear();
106 void task250673_activeMultiColumnSubMenuPosition();
107 void task256918_setFont();
108 void menuSizeHint();
109 void task258920_mouseBorder();
110 void setFixedWidth();
111 void deleteActionInTriggered();
112 void pushButtonPopulateOnAboutToShow();
113 void QTBUG7907_submenus_autoselect();
114 void QTBUG7411_submenus_activate();
115 void QTBUG30595_rtl_submenu();
116 void QTBUG20403_nested_popup_on_shortcut_trigger();
117 void QTBUG47515_widgetActionEnterLeave();
118 void QTBUG8122_widgetActionCrashOnClose();
119 void widgetActionTriggerClosesMenu();
120
121 void QTBUG_10735_crashWithDialog();
122#ifdef Q_OS_MAC
123 void QTBUG_37933_ampersands_data();
124 void QTBUG_37933_ampersands();
125#else
126 void click_while_dismissing_submenu();
127#endif
128 void QTBUG_56917_wideMenuSize();
129 void QTBUG_56917_wideMenuScreenNumber();
130 void QTBUG_56917_wideSubmenuScreenNumber();
131 void menuSize_Scrolling_data();
132 void menuSize_Scrolling();
133 void tearOffMenuNotDisplayed();
134 void QTBUG_61039_menu_shortcuts();
135
136protected slots:
137 void onActivated(QAction*);
138 void onHighlighted(QAction*);
139 void onStatusMessageChanged(const QString &);
140 void onStatusTipTimer();
141 void deleteAction(QAction *a) { delete a; }
142private:
143 void createActions();
144 QMenu *menus[2], *lastMenu;
145 enum { num_builtins = 10 };
146 QAction *activated, *highlighted, *builtins[num_builtins];
147 QString statustip;
148 bool m_onStatusTipTimerExecuted;
149};
150
151// Testing get/set functions
152void tst_QMenu::getSetCheck()
153{
154 QMenu obj1;
155 // QAction * QMenu::defaultAction()
156 // void QMenu::setDefaultAction(QAction *)
157 QAction *var1 = new QAction(0);
158 obj1.setDefaultAction(var1);
159 QCOMPARE(var1, obj1.defaultAction());
160 obj1.setDefaultAction((QAction *)0);
161 QCOMPARE((QAction *)0, obj1.defaultAction());
162 delete var1;
163
164 // QAction * QMenu::activeAction()
165 // void QMenu::setActiveAction(QAction *)
166 QAction *var2 = new QAction(0);
167 obj1.setActiveAction(var2);
168 QCOMPARE(var2, obj1.activeAction());
169 obj1.setActiveAction((QAction *)0);
170 QCOMPARE((QAction *)0, obj1.activeAction());
171 delete var2;
172}
173
174tst_QMenu::tst_QMenu()
175 : m_onStatusTipTimerExecuted(false)
176{
177 QApplication::setEffectEnabled(Qt::UI_FadeTooltip, enable: false);
178 QApplication::setEffectEnabled(Qt::UI_AnimateMenu, enable: false);
179 QApplication::setEffectEnabled(Qt::UI_AnimateTooltip, enable: false);
180}
181
182void tst_QMenu::initTestCase()
183{
184 for (int i = 0; i < num_builtins; i++)
185 builtins[i] = 0;
186 for (int i = 0; i < 2; i++) {
187 menus[i] = new QMenu;
188 QObject::connect(sender: menus[i], SIGNAL(triggered(QAction*)), receiver: this, SLOT(onActivated(QAction*)));
189 QObject::connect(sender: menus[i], SIGNAL(hovered(QAction*)), receiver: this, SLOT(onHighlighted(QAction*)));
190 }
191}
192
193void tst_QMenu::cleanupTestCase()
194{
195 for (int i = 0; i < 2; i++)
196 menus[i]->clear();
197 for (int i = 0; i < num_builtins; i++) {
198 bool menuAction = false;
199 for (int j = 0; j < 2; ++j)
200 if (menus[j]->menuAction() == builtins[i])
201 menuAction = true;
202 if (!menuAction)
203 delete builtins[i];
204 }
205 delete menus[0];
206 delete menus[1];
207}
208
209void tst_QMenu::init()
210{
211 activated = highlighted = 0;
212 lastMenu = 0;
213 m_onStatusTipTimerExecuted = false;
214}
215
216void tst_QMenu::createActions()
217{
218 if (!builtins[0])
219 builtins[0] = new QAction("New", 0);
220 menus[0]->addAction(action: builtins[0]);
221
222 if (!builtins[1]) {
223 builtins[1] = new QAction(0);
224 builtins[1]->setSeparator(true);
225 }
226 menus[0]->addAction(action: builtins[1]);
227
228 if (!builtins[2]) {
229 builtins[2] = menus[1]->menuAction();
230 builtins[2]->setText("&Open..");
231 builtins[8] = new QAction("Close", 0);
232 menus[1]->addAction(action: builtins[8]);
233 builtins[9] = new QAction("Quit", 0);
234 menus[1]->addAction(action: builtins[9]);
235 }
236 menus[0]->addAction(action: builtins[2]);
237
238 if (!builtins[3])
239 builtins[3] = new QAction("Open &as..", 0);
240 menus[0]->addAction(action: builtins[3]);
241
242 if (!builtins[4]) {
243 builtins[4] = new QAction("Save", 0);
244 builtins[4]->setEnabled(false);
245 }
246 menus[0]->addAction(action: builtins[4]);
247
248 if (!builtins[5])
249 builtins[5] = new QAction("Sa&ve as..", 0);
250 menus[0]->addAction(action: builtins[5]);
251
252 if (!builtins[6]) {
253 builtins[6] = new QAction(0);
254 builtins[6]->setSeparator(true);
255 }
256 menus[0]->addAction(action: builtins[6]);
257
258 if (!builtins[7])
259 builtins[7] = new QAction("Prin&t", 0);
260 menus[0]->addAction(action: builtins[7]);
261}
262
263void tst_QMenu::onHighlighted(QAction *action)
264{
265 highlighted = action;
266 lastMenu = qobject_cast<QMenu*>(object: sender());
267}
268
269void tst_QMenu::onActivated(QAction *action)
270{
271 activated = action;
272 lastMenu = qobject_cast<QMenu*>(object: sender());
273}
274
275void tst_QMenu::onStatusMessageChanged(const QString &s)
276{
277 statustip=s;
278}
279
280void tst_QMenu::addActionsAndClear()
281{
282 QCOMPARE(menus[0]->actions().count(), 0);
283 createActions();
284 QCOMPARE(menus[0]->actions().count(), 8);
285 menus[0]->clear();
286 QCOMPARE(menus[0]->actions().count(), 0);
287}
288
289static void testFunction() { }
290
291void tst_QMenu::addActionsConnect()
292{
293 QMenu menu;
294 const QString text = QLatin1String("bla");
295 const QIcon icon;
296 menu.addAction(text, receiver: &menu, SLOT(deleteLater()));
297 menu.addAction(text, object: &menu, slot: &QMenu::deleteLater);
298 menu.addAction(text, slot: testFunction);
299 menu.addAction(text, object: &menu, slot: testFunction);
300 menu.addAction(icon, text, receiver: &menu, SLOT(deleteLater()));
301 menu.addAction(actionIcon: icon, text, object: &menu, slot: &QMenu::deleteLater);
302 menu.addAction(actionIcon: icon, text, slot: testFunction);
303 menu.addAction(actionIcon: icon, text, object: &menu, slot: testFunction);
304#ifndef QT_NO_SHORTCUT
305 const QKeySequence keySequence(Qt::CTRL + Qt::Key_C);
306 menu.addAction(text, receiver: &menu, SLOT(deleteLater()), shortcut: keySequence);
307 menu.addAction(text, object: &menu, slot: &QMenu::deleteLater, shortcut: keySequence);
308 menu.addAction(text, slot: testFunction, shortcut: keySequence);
309 menu.addAction(text, object: &menu, slot: testFunction, shortcut: keySequence);
310 menu.addAction(icon, text, receiver: &menu, SLOT(deleteLater()), shortcut: keySequence);
311 menu.addAction(actionIcon: icon, text, object: &menu, slot: &QMenu::deleteLater, shortcut: keySequence);
312 menu.addAction(actionIcon: icon, text, slot: testFunction, shortcut: keySequence);
313 menu.addAction(actionIcon: icon, text, object: &menu, slot: testFunction, shortcut: keySequence);
314#endif // !QT_NO_SHORTCUT
315}
316
317void tst_QMenu::mouseActivation()
318{
319 QWidget topLevel;
320 topLevel.resize(w: 300, h: 200);
321 centerOnScreen(w: &topLevel);
322 QMenu menu(&topLevel);
323 topLevel.show();
324 menu.addAction(text: "Menu Action");
325 menu.move(topLevel.geometry().topRight() + QPoint(50, 0));
326 menu.show();
327 QTest::mouseClick(widget: &menu, button: Qt::LeftButton, stateKey: {}, pos: menu.rect().center(), delay: 300);
328 QVERIFY(!menu.isVisible());
329
330 //context menus can always be accessed with right click except on windows
331 menu.show();
332 QTest::mouseClick(widget: &menu, button: Qt::RightButton, stateKey: {}, pos: menu.rect().center(), delay: 300);
333 QVERIFY(!menu.isVisible());
334
335#ifdef Q_OS_WIN
336 //on windows normal mainwindow menus Can only be accessed with left mouse button
337 QMenuBar menubar;
338 QMenu submenu("Menu");
339 submenu.addAction("action");
340 QAction *action = menubar.addMenu(&submenu);
341 menubar.move(topLevel.geometry().topRight() + QPoint(300, 0));
342 menubar.show();
343
344
345 QTest::mouseClick(&menubar, Qt::LeftButton, 0, menubar.actionGeometry(action).center(), 300);
346 QVERIFY(submenu.isVisible());
347 QTest::mouseClick(&submenu, Qt::LeftButton, 0, QPoint(5, 5), 300);
348 QVERIFY(!submenu.isVisible());
349
350 QTest::mouseClick(&menubar, Qt::LeftButton, 0, menubar.actionGeometry(action).center(), 300);
351 QVERIFY(submenu.isVisible());
352 QTest::mouseClick(&submenu, Qt::RightButton, 0, QPoint(5, 5), 300);
353 QVERIFY(submenu.isVisible());
354#endif
355}
356
357void tst_QMenu::keyboardNavigation_data()
358{
359 QTest::addColumn<Qt::Key>(name: "key");
360 QTest::addColumn<Qt::KeyboardModifiers>(name: "modifiers");
361 QTest::addColumn<int>(name: "expected_action");
362 QTest::addColumn<int>(name: "expected_menu");
363 QTest::addColumn<bool>(name: "init");
364 QTest::addColumn<bool>(name: "expected_activated");
365 QTest::addColumn<bool>(name: "expected_highlighted");
366
367 //test up and down (order is important here)
368 QTest::newRow(dataTag: "data0") << Qt::Key(Qt::Key_Down) << Qt::KeyboardModifiers(Qt::NoModifier) << 0 << 0 << true << false << true;
369 QTest::newRow(dataTag: "data1") << Qt::Key(Qt::Key_Down) << Qt::KeyboardModifiers(Qt::NoModifier) << 2 << 0 << false << false << true; //skips the separator
370 QTest::newRow(dataTag: "data2") << Qt::Key(Qt::Key_Down) << Qt::KeyboardModifiers(Qt::NoModifier) << 3 << 0 << false << false << true;
371
372 if (QApplication::style()->styleHint(stylehint: QStyle::SH_Menu_AllowActiveAndDisabled))
373 QTest::newRow(dataTag: "data3_noMac") << Qt::Key(Qt::Key_Down) << Qt::KeyboardModifiers(Qt::NoModifier) << 4 << 0 << false << false << true;
374 else
375 QTest::newRow(dataTag: "data3_Mac") << Qt::Key(Qt::Key_Down) << Qt::KeyboardModifiers(Qt::NoModifier) << 5 << 0 << false << false << true;
376 QTest::newRow(dataTag: "data4") << Qt::Key(Qt::Key_Up) << Qt::KeyboardModifiers(Qt::NoModifier) << 3 << 0 << false << false << true;
377 QTest::newRow(dataTag: "data5") << Qt::Key(Qt::Key_Up) << Qt::KeyboardModifiers(Qt::NoModifier) << 2 << 0 << false << false << true;
378 QTest::newRow(dataTag: "data6") << Qt::Key(Qt::Key_Right) << Qt::KeyboardModifiers(Qt::NoModifier) << 8 << 1 << false << false << true;
379 QTest::newRow(dataTag: "data7") << Qt::Key(Qt::Key_Down) << Qt::KeyboardModifiers(Qt::NoModifier) << 9 << 1 << false << false << true;
380 QTest::newRow(dataTag: "data8") << Qt::Key(Qt::Key_Escape) << Qt::KeyboardModifiers(Qt::NoModifier) << 2 << 0 << false << false << false;
381 QTest::newRow(dataTag: "data9") << Qt::Key(Qt::Key_Down) << Qt::KeyboardModifiers(Qt::NoModifier) << 3 << 0 << false << false<< true;
382 QTest::newRow(dataTag: "data10") << Qt::Key(Qt::Key_Return) << Qt::KeyboardModifiers(Qt::NoModifier) << 3 << 0 << false << true << false;
383
384 if (QGuiApplication::platformName().compare(other: QLatin1String("xcb"), cs: Qt::CaseInsensitive)) {
385 // Test shortcuts.
386 QTest::newRow(dataTag: "shortcut0") << Qt::Key(Qt::Key_V) << Qt::KeyboardModifiers(Qt::AltModifier) << 5 << 0 << true << true << false;
387 }
388}
389
390void tst_QMenu::keyboardNavigation()
391{
392 QFETCH(Qt::Key, key);
393 QFETCH(Qt::KeyboardModifiers, modifiers);
394 QFETCH(int, expected_action);
395 QFETCH(int, expected_menu);
396 QFETCH(bool, init);
397 QFETCH(bool, expected_activated);
398 QFETCH(bool, expected_highlighted);
399
400 if (init) {
401 lastMenu = menus[0];
402 lastMenu->clear();
403 createActions();
404 lastMenu->popup(pos: QPoint(0, 0));
405 }
406
407 QTest::keyClick(widget: lastMenu, key, modifier: modifiers);
408 if (expected_activated) {
409#ifdef Q_OS_MAC
410 QEXPECT_FAIL("shortcut0", "Shortcut navication fails, see QTBUG-23684", Continue);
411#endif
412 QCOMPARE(activated, builtins[expected_action]);
413#ifndef Q_OS_MAC
414 QEXPECT_FAIL("shortcut0", "QTBUG-22449: QMenu doesn't remove highlight if a menu item is activated by a shortcut", Abort);
415#endif
416 QCOMPARE(menus[expected_menu]->activeAction(), nullptr);
417 } else {
418 QCOMPARE(menus[expected_menu]->activeAction(), builtins[expected_action]);
419 }
420
421 if (expected_highlighted)
422 QCOMPARE(menus[expected_menu]->activeAction(), highlighted);
423 else
424 QCOMPARE(highlighted, nullptr);
425}
426
427#ifdef Q_OS_MAC
428QT_BEGIN_NAMESPACE
429extern bool qt_tab_all_widgets(); // from qapplication.cpp
430QT_END_NAMESPACE
431#endif
432
433void tst_QMenu::focus()
434{
435 QMenu menu;
436 menu.addAction(text: "One");
437 menu.addAction(text: "Two");
438 menu.addAction(text: "Three");
439
440#ifdef Q_OS_MAC
441 if (!qt_tab_all_widgets())
442 QSKIP("Computer is currently set up to NOT tab to all widgets,"
443 " this test assumes you can tab to all widgets");
444#endif
445
446 QWidget window;
447 window.resize(w: 300, h: 200);
448 QPushButton button("Push me", &window);
449 centerOnScreen(w: &window);
450 window.show();
451 qApp->setActiveWindow(&window);
452
453 QVERIFY(button.hasFocus());
454 QCOMPARE(QApplication::focusWidget(), (QWidget *)&button);
455 QCOMPARE(QApplication::activeWindow(), &window);
456 menu.move(window.geometry().topRight() + QPoint(50, 0));
457 menu.show();
458 QVERIFY(button.hasFocus());
459 QCOMPARE(QApplication::focusWidget(), (QWidget *)&button);
460 QCOMPARE(QApplication::activeWindow(), &window);
461 menu.hide();
462 QVERIFY(button.hasFocus());
463 QCOMPARE(QApplication::focusWidget(), (QWidget *)&button);
464 QCOMPARE(QApplication::activeWindow(), &window);
465}
466
467void tst_QMenu::overrideMenuAction()
468{
469 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
470 QSKIP("Window activation is not supported");
471
472 //test the override menu action by first creating an action to which we set its menu
473 QMainWindow w;
474 w.resize(w: 300, h: 200);
475 w.menuBar()->setNativeMenuBar(false);
476 centerOnScreen(w: &w);
477
478 QAction *aFileMenu = new QAction("&File", &w);
479 w.menuBar()->addAction(action: aFileMenu);
480
481 QMenu *m = new QMenu(&w);
482 QAction *menuaction = m->menuAction();
483 connect(asender: m, SIGNAL(triggered(QAction*)), SLOT(onActivated(QAction*)));
484 aFileMenu->setMenu(m); //this sets the override menu action for the QMenu
485 QCOMPARE(m->menuAction(), aFileMenu);
486
487 // On Mac and Windows CE, we need to create native key events to test menu
488 // action activation, so skip this part of the test.
489#if !defined(Q_OS_DARWIN)
490 QAction *aQuit = new QAction("Quit", &w);
491 aQuit->setShortcut(QKeySequence("Ctrl+X"));
492 m->addAction(action: aQuit);
493
494 w.show();
495 QApplication::setActiveWindow(&w);
496 w.setFocus();
497 QVERIFY(QTest::qWaitForWindowActive(&w));
498 QVERIFY(w.hasFocus());
499
500 //test of the action inside the menu
501 QTest::keyClick(widget: &w, key: Qt::Key_X, modifier: Qt::ControlModifier);
502 QTRY_COMPARE(activated, aQuit);
503
504 //test if the menu still pops out
505 QTest::keyClick(widget: &w, key: Qt::Key_F, modifier: Qt::AltModifier);
506 QTRY_VERIFY(m->isVisible());
507#endif
508
509 delete aFileMenu;
510
511 //after the deletion of the override menu action,
512 //the menu should have its default menu action back
513 QCOMPARE(m->menuAction(), menuaction);
514}
515
516void tst_QMenu::statusTip()
517{
518 //check that the statustip of actions inserted into the menu are displayed
519 QMainWindow w;
520 w.resize(w: 300, h: 200);
521 centerOnScreen(w: &w);
522 connect(asender: w.statusBar(), SIGNAL(messageChanged(QString)), SLOT(onStatusMessageChanged(QString)));; //creates the status bar
523 QToolBar tb;
524 QAction a("main action", &tb);
525 a.setStatusTip("main action");
526 QMenu m(&tb);
527 QAction subact("sub action", &m);
528 subact.setStatusTip("sub action");
529 m.addAction(action: &subact);
530 a.setMenu(&m);
531 tb.addAction(action: &a);
532
533 w.addToolBar(toolbar: &tb);
534 w.show();
535 QVERIFY(QTest::qWaitForWindowExposed(&w));
536
537 QRect rect1 = tb.actionGeometry(action: &a);
538 QToolButton *btn = qobject_cast<QToolButton*>(object: tb.childAt(p: rect1.center()));
539
540 QVERIFY(btn != NULL);
541
542 //because showMenu calls QMenu::exec, we need to use a singleshot
543 //to continue the test
544 QTimer timer;
545 timer.setSingleShot(true);
546 connect(sender: &timer, signal: &QTimer::timeout, receiver: this, slot: &tst_QMenu::onStatusTipTimer);
547 timer.setInterval(200);
548 timer.start();
549 btn->showMenu();
550 QVERIFY(m_onStatusTipTimerExecuted);
551 QVERIFY(statustip.isEmpty());
552}
553
554//2nd part of the test
555void tst_QMenu::onStatusTipTimer()
556{
557 QMenu *menu = qobject_cast<QMenu*>(object: QApplication::activePopupWidget());
558 QVERIFY(menu != 0);
559 QVERIFY(menu->isVisible());
560 QTest::keyClick(widget: menu, key: Qt::Key_Down);
561
562 //we store the statustip to press escape in any case
563 //otherwise, if the test fails it blocks (never gets out of QMenu::exec
564 const QString st=statustip;
565
566 menu->close(); //goes out of the menu
567
568 QCOMPARE(st, QString("sub action"));
569 QVERIFY(!menu->isVisible());
570 m_onStatusTipTimerExecuted = true;
571}
572
573void tst_QMenu::widgetActionFocus()
574{
575 //test if the focus is correctly handled with a QWidgetAction
576 QMenu m;
577 QListWidget *l = new QListWidget(&m);
578 for (int i = 1; i<3 ; i++)
579 l->addItem(QStringLiteral("item" ) + QString::number(i));
580 QWidgetAction *wa = new QWidgetAction(&m);
581 wa->setDefaultWidget(l);
582 m.addAction(action: wa);
583 m.setActiveAction(wa);
584 l->setFocus(); //to ensure it has primarily the focus
585 QAction *menuitem1=m.addAction(text: "menuitem1");
586 QAction *menuitem2=m.addAction(text: "menuitem2");
587
588 m.popup(pos: QPoint());
589
590 QVERIFY(m.isVisible());
591 QVERIFY(l->hasFocus());
592 QVERIFY(l->currentItem());
593 QCOMPARE(l->currentItem()->text(), QString("item1"));
594
595 QTest::keyClick(widget: QApplication::focusWidget(), key: Qt::Key_Down);
596 QVERIFY(l->currentItem());
597 QCOMPARE(l->currentItem()->text(), QString("item2"));
598
599 QTest::keyClick(widget: QApplication::focusWidget(), key: Qt::Key_Down);
600 QVERIFY(m.hasFocus());
601 QCOMPARE(m.activeAction(), menuitem1);
602
603 QTest::keyClick(widget: QApplication::focusWidget(), key: Qt::Key_Down);
604 QVERIFY(m.hasFocus());
605 QCOMPARE(m.activeAction(), menuitem2);
606
607 QTest::keyClick(widget: QApplication::focusWidget(), key: Qt::Key_Up);
608 QVERIFY(m.hasFocus());
609 QCOMPARE(m.activeAction(), menuitem1);
610
611 QTest::keyClick(widget: QApplication::focusWidget(), key: Qt::Key_Up);
612 QVERIFY(l->hasFocus());
613 QCOMPARE(m.activeAction(), (QAction *)wa);
614}
615
616static QMenu *getTornOffMenu()
617{
618 for (QWidget *w : QApplication::allWidgets()) {
619 if (w->isVisible() && w->inherits(classname: "QTornOffMenu"))
620 return static_cast<QMenu *>(w);
621 }
622 return nullptr;
623}
624
625void tst_QMenu::tearOff()
626{
627 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
628 QSKIP("Window activation is not supported");
629
630 QWidget widget;
631 QScopedPointer<QMenu> menu(new QMenu(&widget));
632 QVERIFY(!menu->isTearOffEnabled()); //default value
633 menu->setTearOffEnabled(true);
634 menu->setTitle(QLatin1String("Same &Menu"));
635 menu->addAction(text: "aaa");
636 menu->addAction(text: "bbb");
637 QVERIFY(menu->isTearOffEnabled());
638
639 widget.resize(w: 300, h: 200);
640 centerOnScreen(w: &widget);
641 widget.show();
642 widget.activateWindow();
643 QVERIFY(QTest::qWaitForWindowActive(&widget));
644 menu->popup(pos: widget.geometry().topRight() + QPoint(50, 0));
645 QVERIFY(QTest::qWaitForWindowActive(menu.data()));
646 QVERIFY(!menu->isTearOffMenuVisible());
647
648 MenuMetrics mm(menu.data());
649 const int tearOffOffset = mm.fw + mm.vmargin + mm.tearOffHeight / 2;
650
651 QTest::mouseClick(widget: menu.data(), button: Qt::LeftButton, stateKey: {}, pos: QPoint(10, tearOffOffset), delay: 10);
652 QTRY_VERIFY(menu->isTearOffMenuVisible());
653 QPointer<QMenu> torn = getTornOffMenu();
654 QVERIFY(torn);
655 QVERIFY(torn->isVisible());
656
657 // Check menu title
658 const QString cleanTitle = QPlatformTheme::removeMnemonics(original: menu->title()).trimmed();
659 QCOMPARE(torn->windowTitle(), cleanTitle);
660
661 // Change menu title and check again
662 menu->setTitle(QLatin1String("Sample &Menu"));
663 const QString newCleanTitle = QPlatformTheme::removeMnemonics(original: menu->title()).trimmed();
664 QCOMPARE(torn->windowTitle(), newCleanTitle);
665
666 // Clear menu title and check again
667 menu->setTitle(QString());
668 QCOMPARE(torn->windowTitle(), QString());
669
670 menu->hideTearOffMenu();
671 QVERIFY(!menu->isTearOffMenuVisible());
672 QVERIFY(!torn->isVisible());
673
674#ifndef QT_NO_CURSOR
675 // Test under-mouse positioning
676 menu->showTearOffMenu();
677 torn = getTornOffMenu();
678 QVERIFY(torn);
679 QVERIFY(torn->isVisible());
680 QVERIFY(menu->isTearOffMenuVisible());
681 // Some platforms include the window title bar in its geometry.
682 QTRY_COMPARE(torn->windowHandle()->position(), QCursor::pos());
683
684 menu->hideTearOffMenu();
685 QVERIFY(!menu->isTearOffMenuVisible());
686 QVERIFY(!torn->isVisible());
687
688 // Test custom positioning
689 const QPoint &pos = QCursor::pos() / 2 + QPoint(10, 10);
690 menu->showTearOffMenu(pos);
691 torn = getTornOffMenu();
692 QVERIFY(torn);
693 QVERIFY(torn->isVisible());
694 QVERIFY(menu->isTearOffMenuVisible());
695 // Some platforms include the window title bar in its geometry.
696 QTRY_COMPARE(torn->windowHandle()->position(), pos);
697#endif // QT_NO_CURSOR
698}
699
700void tst_QMenu::submenuTearOffDontClose()
701{
702 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
703 QSKIP("Window activation is not supported");
704
705 QWidget widget;
706 QMenu *menu = new QMenu(&widget);
707 QVERIFY(!menu->isTearOffEnabled()); //default value
708 menu->setTearOffEnabled(true);
709 QVERIFY(menu->isTearOffEnabled());
710 QMenu *submenu = new QMenu(&widget);
711 submenu->addAction(text: "aaa");
712 submenu->addAction(text: "bbb");
713 QVERIFY(!submenu->isTearOffEnabled()); //default value
714 submenu->setTearOffEnabled(true);
715 QVERIFY(submenu->isTearOffEnabled());
716 menu->addMenu(menu: submenu);
717
718 widget.resize(w: 300, h: 200);
719 centerOnScreen(w: &widget);
720 widget.show();
721 widget.activateWindow();
722 QVERIFY(QTest::qWaitForWindowActive(&widget));
723 // Show parent menu
724 menu->popup(pos: widget.geometry().topRight() + QPoint(50, 0));
725 QVERIFY(QTest::qWaitForWindowActive(menu));
726 // Then its submenu
727 const QRect submenuRect = menu->actionGeometry(menu->actions().at(i: 0));
728 const QPoint submenuPos(submenuRect.topLeft() + QPoint(3, 3));
729 // Move then click to avoid the submenu moves from causing it to close
730 QTest::mouseMove(widget: menu, pos: submenuPos, delay: 100);
731 QTest::mouseClick(widget: menu, button: Qt::LeftButton, stateKey: {}, pos: submenuPos, delay: 100);
732 QVERIFY(QTest::qWaitFor([&]() { return submenu->window()->windowHandle(); }));
733 QVERIFY(QTest::qWaitForWindowActive(submenu));
734 // Make sure we enter the submenu frame directly on the tear-off area
735 QTest::mouseMove(widget: submenu, pos: QPoint(10, 3), delay: 100);
736 if (submenu->style()->styleHint(stylehint: QStyle::SH_Menu_SubMenuDontStartSloppyOnLeave)) {
737 qWarning(msg: "Sloppy menu timer disabled by the style: %s", qPrintable(QApplication::style()->objectName()));
738 // Submenu must get the enter event
739 QTRY_VERIFY(submenu->underMouse());
740 } else {
741 const int closeTimeout = submenu->style()->styleHint(stylehint: QStyle::SH_Menu_SubMenuSloppyCloseTimeout);
742 QTest::qWait(ms: closeTimeout + 100);
743 // Menu must not disappear and it must get the enter event
744 QVERIFY(submenu->isVisible());
745 QVERIFY(submenu->underMouse());
746 }
747}
748
749void tst_QMenu::layoutDirection()
750{
751 QMainWindow win;
752 win.setLayoutDirection(Qt::RightToLeft);
753 win.resize(w: 300, h: 200);
754 centerOnScreen(w: &win);
755
756 QMenu menu(&win);
757 menu.addAction(text: "foo");
758 menu.move(win.geometry().topRight() + QPoint(50, 0));
759 menu.show();
760 QVERIFY(QTest::qWaitForWindowExposed(&menu));
761 QCOMPARE(menu.layoutDirection(), Qt::RightToLeft);
762 menu.close();
763
764 menu.setParent(0);
765 menu.move(win.geometry().topRight() + QPoint(50, 0));
766 menu.show();
767 QVERIFY(QTest::qWaitForWindowExposed(&menu));
768 QCOMPARE(menu.layoutDirection(), QApplication::layoutDirection());
769 menu.close();
770
771 //now the menubar
772 QAction *action = win.menuBar()->addMenu(menu: &menu);
773 win.menuBar()->setActiveAction(action);
774 QVERIFY(QTest::qWaitForWindowExposed(&menu));
775 QCOMPARE(menu.layoutDirection(), Qt::RightToLeft);
776}
777
778void tst_QMenu::task208001_stylesheet()
779{
780 //test if it crash
781 QMainWindow main;
782 main.setStyleSheet("QMenu [title =\"File\"] { color: red;}");
783 main.menuBar()->addMenu(title: "File");
784}
785
786void tst_QMenu::activeSubMenuPosition()
787{
788 QPushButton lab("subMenuPosition test");
789
790 QMenu *sub = new QMenu("Submenu", &lab);
791 sub->addAction(text: "Sub-Item1");
792 QAction *subAction = sub->addAction(text: "Sub-Item2");
793
794 QMenu *main = new QMenu("Menu-Title", &lab);
795 (void)main->addAction(text: "Item 1");
796 QAction *menuAction = main->addMenu(menu: sub);
797 (void)main->addAction(text: "Item 3");
798 (void)main->addAction(text: "Item 4");
799
800 main->setActiveAction(menuAction);
801 sub->setActiveAction(subAction);
802 main->popup(pos: QPoint(200,200));
803
804 QVERIFY(main->isVisible());
805 QCOMPARE(main->activeAction(), menuAction);
806 QVERIFY(sub->isVisible());
807 QVERIFY(sub->pos() != QPoint(0,0));
808 // well, it's enough to check the pos is not (0,0) but it's more safe
809 // to check that submenu is to the right of the main menu too.
810 QVERIFY(sub->pos().x() > main->pos().x());
811 QCOMPARE(sub->activeAction(), subAction);
812}
813
814// QTBUG-49588, QTBUG-48396: activeSubMenuPositionExec() is the same as
815// activeSubMenuPosition(), but uses QMenu::exec(), which produces a different
816// sequence of events. Verify that the sub menu is positioned to the right of the
817// main menu.
818class SubMenuPositionExecMenu : public QMenu
819{
820 Q_OBJECT
821public:
822 SubMenuPositionExecMenu() : QMenu("Menu-Title"), m_timerId(-1), m_timerTick(0)
823 {
824 addAction(text: "Item 1");
825 m_subMenu = addMenu(title: "Submenu");
826 m_subAction = m_subMenu->addAction(text: "Sub-Item1");
827 setActiveAction(m_subMenu->menuAction());
828 }
829
830protected:
831 void showEvent(QShowEvent *e) override
832 {
833 QVERIFY(m_subMenu->isVisible());
834 QVERIFY2(m_subMenu->x() > x(),
835 (QByteArray::number(m_subMenu->x()) + ' ' + QByteArray::number(x())).constData());
836 m_timerId = startTimer(interval: 50);
837 QMenu::showEvent(event: e);
838 }
839
840 void timerEvent(QTimerEvent *e) override
841 {
842 if (e->timerId() == m_timerId) {
843 switch (m_timerTick++) {
844 case 0:
845 m_subMenu->close();
846 break;
847 case 1:
848 close();
849 break;
850 }
851 }
852 }
853
854private:
855 int m_timerId;
856 int m_timerTick;
857 QMenu *m_subMenu;
858 QAction *m_subAction;
859};
860
861void tst_QMenu::activeSubMenuPositionExec()
862{
863
864#ifdef Q_OS_WINRT
865 QSKIP("Broken on WinRT - QTBUG-68297");
866#endif
867 SubMenuPositionExecMenu menu;
868 menu.exec(pos: QGuiApplication::primaryScreen()->availableGeometry().center());
869}
870
871void tst_QMenu::task242454_sizeHint()
872{
873 QMenu menu;
874 QString s = QLatin1String("foo\nfoo\nfoo\nfoo");
875 menu.addAction(text: s);
876 QVERIFY(menu.sizeHint().width() > menu.fontMetrics().boundingRect(QRect(), Qt::TextSingleLine, s).width());
877}
878
879class Menu : public QMenu
880{
881 Q_OBJECT
882public slots:
883 void clear()
884 {
885 QMenu::clear();
886 }
887};
888
889void tst_QMenu::task176201_clear()
890{
891 //this test used to crash
892 Menu menu;
893 QAction *action = menu.addAction(text: "test");
894 menu.connect(asender: action, SIGNAL(triggered()), SLOT(clear()));
895 menu.popup(pos: QPoint());
896 QTest::mouseClick(widget: &menu, button: Qt::LeftButton, stateKey: {}, pos: menu.rect().center());
897}
898
899void tst_QMenu::task250673_activeMultiColumnSubMenuPosition()
900{
901 class MyMenu : public QMenu
902 {
903 public:
904 int columnCount() const { return QMenu::columnCount(); }
905 };
906
907 QMenu sub;
908
909 if (sub.style()->styleHint(stylehint: QStyle::SH_Menu_Scrollable, opt: 0, widget: &sub)) {
910 //the style prevents the menus from getting columns
911 QSKIP("the style doesn't support multiple columns, it makes the menu scrollable");
912 }
913
914 sub.addAction(text: "Sub-Item1");
915 QAction *subAction = sub.addAction(text: "Sub-Item2");
916
917 MyMenu main;
918 main.addAction(text: "Item 1");
919 QAction *menuAction = main.addMenu(menu: &sub);
920 main.popup(pos: QPoint(200,200));
921
922 uint i = 2;
923 while (main.columnCount() < 2) {
924 main.addAction(text: QLatin1String("Item ") + QString::number(i));
925 ++i;
926 QVERIFY(i<1000);
927 }
928 main.setActiveAction(menuAction);
929 sub.setActiveAction(subAction);
930
931 QVERIFY(main.isVisible());
932 QCOMPARE(main.activeAction(), menuAction);
933 QVERIFY(sub.isVisible());
934 QVERIFY(sub.pos().x() > main.pos().x());
935
936 const int subMenuOffset = main.style()->pixelMetric(metric: QStyle::PM_SubMenuOverlap, option: 0, widget: &main);
937 QVERIFY((sub.geometry().left() - subMenuOffset + 5) < main.geometry().right());
938}
939
940void tst_QMenu::task256918_setFont()
941{
942 QMenu menu;
943 QAction *action = menu.addAction(text: "foo");
944 QFont f;
945 f.setPointSize(30);
946 action->setFont(f);
947 centerOnScreen(w: &menu, size: QSize(120, 40));
948 menu.show(); //ensures that the actiongeometry are calculated
949 QVERIFY(menu.actionGeometry(action).height() > f.pointSize());
950}
951
952void tst_QMenu::menuSizeHint()
953{
954 QMenu menu;
955 //this is a list of arbitrary strings so that we check the geometry
956 for (auto str : {"trer", "ezrfgtgvqd", "sdgzgzerzerzer", "eerzertz", "er"})
957 menu.addAction(text: str);
958
959 const QMargins cm = menu.contentsMargins();
960 const int panelWidth = menu.style()->pixelMetric(metric: QStyle::PM_MenuPanelWidth, option: 0, widget: &menu);
961 const int hmargin = menu.style()->pixelMetric(metric: QStyle::PM_MenuHMargin, option: 0, widget: &menu),
962 vmargin = menu.style()->pixelMetric(metric: QStyle::PM_MenuVMargin, option: 0, widget: &menu);
963
964 int maxWidth =0;
965 QRect result;
966 for (QAction *action : menu.actions()) {
967 maxWidth = qMax(a: maxWidth, b: menu.actionGeometry(action).width());
968 result |= menu.actionGeometry(action);
969 QCOMPARE(result.x(), cm.left() + hmargin + panelWidth);
970 QCOMPARE(result.y(), cm.top() + vmargin + panelWidth);
971 }
972
973 QStyleOption opt(0);
974 opt.rect = menu.rect();
975 opt.state = QStyle::State_None;
976
977 QSize resSize = QSize(result.x(), result.y()) + result.size() + QSize(hmargin + cm.right() + panelWidth, vmargin + cm.top() + panelWidth);
978
979 resSize = menu.style()->sizeFromContents(ct: QStyle::CT_Menu, opt: &opt,
980 contentsSize: resSize.expandedTo(otherSize: QApplication::globalStrut()), w: &menu);
981
982 QCOMPARE(resSize, menu.sizeHint());
983}
984
985class Menu258920 : public QMenu
986{
987 Q_OBJECT
988public slots:
989 void paintEvent(QPaintEvent *e)
990 {
991 QMenu::paintEvent(e);
992 painted = true;
993 }
994
995public:
996 bool painted;
997};
998
999// Mouse move related signals for Windows Mobile unavailable
1000void tst_QMenu::task258920_mouseBorder()
1001{
1002 Menu258920 menu;
1003 // For styles which inherit from QWindowsStyle, styleHint(QStyle::SH_Menu_MouseTracking) is true.
1004 menu.setMouseTracking(true);
1005 QAction *action = menu.addAction(text: "test");
1006
1007 const QPoint center = QGuiApplication::primaryScreen()->availableGeometry().center();
1008 menu.popup(pos: center);
1009 QVERIFY(QTest::qWaitForWindowExposed(&menu));
1010 QRect actionRect = menu.actionGeometry(action);
1011 const QPoint actionCenter = actionRect.center();
1012 QTest::mouseMove(widget: &menu, pos: actionCenter - QPoint(-10, 0));
1013 QTest::mouseMove(widget: &menu, pos: actionCenter);
1014 QTest::mouseMove(widget: &menu, pos: actionCenter + QPoint(10, 0));
1015 QTRY_COMPARE(action, menu.activeAction());
1016 menu.painted = false;
1017 QTest::mouseMove(widget: &menu, pos: QPoint(actionRect.center().x(), actionRect.bottom() + 1));
1018 QTRY_COMPARE(static_cast<QAction*>(0), menu.activeAction());
1019 QTRY_VERIFY(menu.painted);
1020}
1021
1022void tst_QMenu::setFixedWidth()
1023{
1024 QMenu menu;
1025 menu.addAction(text: "action");
1026 menu.setFixedWidth(300);
1027 //the sizehint should reflect the minimumwidth because the action will try to
1028 //get as much space as possible
1029 QCOMPARE(menu.sizeHint().width(), menu.minimumWidth());
1030}
1031
1032void tst_QMenu::deleteActionInTriggered()
1033{
1034 // should not crash
1035 QMenu m;
1036 QObject::connect(sender: &m, SIGNAL(triggered(QAction*)), receiver: this, SLOT(deleteAction(QAction*)));
1037 QPointer<QAction> a = m.addAction(text: "action");
1038 a.data()->trigger();
1039 QVERIFY(!a);
1040}
1041
1042class PopulateOnAboutToShowTestMenu : public QMenu {
1043 Q_OBJECT
1044public:
1045 explicit PopulateOnAboutToShowTestMenu(QWidget *parent = 0);
1046
1047public slots:
1048 void populateMenu();
1049};
1050
1051PopulateOnAboutToShowTestMenu::PopulateOnAboutToShowTestMenu(QWidget *parent) : QMenu(parent)
1052{
1053 connect(sender: this, SIGNAL(aboutToShow()), receiver: this, SLOT(populateMenu()));
1054}
1055
1056void PopulateOnAboutToShowTestMenu::populateMenu()
1057{
1058 // just adds 3 dummy actions and a separator.
1059 addAction(text: "Foo");
1060 addAction(text: "Bar");
1061 addAction(text: "FooBar");
1062 addSeparator();
1063}
1064
1065static inline QByteArray msgGeometryIntersects(const QRect &r1, const QRect &r2)
1066{
1067 QString result;
1068 QDebug(&result) << r1 << "intersects" << r2;
1069 return result.toLocal8Bit();
1070}
1071
1072void tst_QMenu::pushButtonPopulateOnAboutToShow()
1073{
1074#ifdef Q_OS_MACOS
1075 QSKIP("Popup menus may partially overlap the button on macOS, and that's okey");
1076#endif
1077
1078 QPushButton b("Test PushButton");
1079 b.setWindowFlags(Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);
1080
1081 QMenu *buttonMenu= new PopulateOnAboutToShowTestMenu(&b);
1082 b.setMenu(buttonMenu);
1083 const QScreen *scr = QGuiApplication::screenAt(point: b.pos());
1084 b.show();
1085 const QRect screen = scr->geometry();
1086
1087 QRect desiredGeometry = b.geometry();
1088 desiredGeometry.moveTopLeft(p: QPoint(screen.x() + 10, screen.bottom() - b.height() - 5));
1089
1090 b.setGeometry(desiredGeometry);
1091 QVERIFY(QTest::qWaitForWindowExposed(&b));
1092
1093 if (b.geometry() != desiredGeometry) {
1094 // We are trying to put the button very close to the edge of the screen,
1095 // explicitly to test behavior when the popup menu goes off the screen.
1096 // However a modern window manager is quite likely to reject this requested geometry
1097 // (kwin in kde4 does, for example, since the button would probably appear behind
1098 // or partially behind the taskbar).
1099 // Your best bet is to run this test _without_ a WM.
1100 QSKIP("Your window manager won't allow a window against the bottom of the screen");
1101 }
1102
1103 QTimer::singleShot(msec: 300, receiver: buttonMenu, SLOT(hide()));
1104 QTest::mouseClick(widget: &b, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: b.rect().center());
1105#ifdef Q_OS_WINRT
1106 QEXPECT_FAIL("", "WinRT does not support QTest::mouseClick", Abort);
1107#endif
1108 QVERIFY2(!buttonMenu->geometry().intersects(b.geometry()), msgGeometryIntersects(buttonMenu->geometry(), b.geometry()));
1109
1110 // note: we're assuming that, if we previously got the desired geometry, we'll get it here too
1111 b.move(ax: 10, ay: screen.bottom()-buttonMenu->height()-5);
1112 QTimer::singleShot(msec: 300, receiver: buttonMenu, SLOT(hide()));
1113 QTest::mouseClick(widget: &b, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: b.rect().center());
1114 QVERIFY2(!buttonMenu->geometry().intersects(b.geometry()), msgGeometryIntersects(buttonMenu->geometry(), b.geometry()));
1115}
1116
1117void tst_QMenu::QTBUG7907_submenus_autoselect()
1118{
1119 QMenu menu("Test Menu");
1120 QMenu set1("Setting1");
1121 QMenu set2("Setting2");
1122 QMenu subset("Subsetting");
1123 subset.addAction(text: "Values");
1124 set1.addMenu(menu: &subset);
1125 menu.addMenu(menu: &set1);
1126 menu.addMenu(menu: &set2);
1127 centerOnScreen(w: &menu, size: QSize(120, 100));
1128 menu.show();
1129 QVERIFY(QTest::qWaitForWindowExposed(&menu));
1130 QTest::mouseClick(widget: &menu, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(5,5) );
1131 QVERIFY(!subset.isVisible());
1132}
1133
1134void tst_QMenu::QTBUG7411_submenus_activate()
1135{
1136 QMenu menu("Test Menu");
1137 QAction *act = menu.addAction(text: "foo");
1138 QMenu sub1("&sub1");
1139 sub1.addAction(text: "foo");
1140 sub1.setTitle("&sub1");
1141 QAction *act1 = menu.addMenu(menu: &sub1);
1142 centerOnScreen(w: &menu, size: QSize(120, 100));
1143 menu.show();
1144 QVERIFY(QTest::qWaitForWindowExposed(&menu));
1145 menu.setActiveAction(act);
1146 QTest::keyPress(widget: &menu, key: Qt::Key_Down);
1147 QCOMPARE(menu.activeAction(), act1);
1148 QVERIFY(!sub1.isVisible());
1149 QTest::keyPress(widget: &menu, key: Qt::Key_S);
1150 QTRY_VERIFY(sub1.isVisible());
1151}
1152
1153static bool isPlatformWayland()
1154{
1155 return !QGuiApplication::platformName().compare(other: QLatin1String("wayland"), cs: Qt::CaseInsensitive);
1156}
1157
1158void tst_QMenu::QTBUG30595_rtl_submenu()
1159{
1160 if (isPlatformWayland())
1161 QSKIP("Creating xdg_popups on Wayland requires real input events. Positions would be off.");
1162
1163 QMenu menu("Test Menu");
1164 menu.setLayoutDirection(Qt::RightToLeft);
1165 QMenu sub("&sub");
1166 sub.addAction(text: "bar");
1167 sub.setTitle("&sub");
1168 menu.addMenu(menu: &sub);
1169 centerOnScreen(w: &menu, size: QSize(120, 40));
1170 menu.show();
1171 QVERIFY(QTest::qWaitForWindowExposed(&menu));
1172 QTest::mouseClick(widget: &menu, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(5,5) );
1173 QTRY_VERIFY(sub.isVisible());
1174 QVERIFY2(sub.pos().x() < menu.pos().x(), QByteArray::number(sub.pos().x()) + QByteArrayLiteral(" not less than ") + QByteArray::number(menu.pos().x()));
1175}
1176
1177void tst_QMenu::QTBUG20403_nested_popup_on_shortcut_trigger()
1178{
1179 QMenu menu("Test Menu");
1180 QMenu sub1("&sub1");
1181 QMenu subsub1("&subsub1");
1182 subsub1.addAction(text: "foo");
1183 sub1.addMenu(menu: &subsub1);
1184 menu.addMenu(menu: &sub1);
1185 centerOnScreen(w: &menu, size: QSize(120, 100));
1186 menu.show();
1187 QVERIFY(QTest::qWaitForWindowExposed(&menu));
1188 QTest::keyPress(widget: &menu, key: Qt::Key_S);
1189 QTest::qWait(ms: 100); // 20ms delay with previous behavior
1190 QTRY_VERIFY(sub1.isVisible());
1191 QVERIFY(!subsub1.isVisible());
1192}
1193
1194#ifndef Q_OS_MACOS
1195void tst_QMenu::click_while_dismissing_submenu()
1196{
1197 if (isPlatformWayland())
1198 QSKIP("Wayland: Creating (grabbing) popups requires real mouse events.");
1199
1200 QMenu menu("Test Menu");
1201 QAction *action = menu.addAction(text: "action");
1202 QMenu sub("&sub");
1203 sub.addAction(text: "subaction");
1204 menu.addMenu(menu: &sub);
1205 centerOnScreen(w: &menu, size: QSize(120, 100));
1206 menu.show();
1207 QSignalSpy spy(action, &QAction::triggered);
1208 QSignalSpy menuShownSpy(&sub, &QMenu::aboutToShow);
1209 QSignalSpy menuHiddenSpy(&sub, &QMenu::aboutToHide);
1210 QVERIFY(QTest::qWaitForWindowExposed(&menu));
1211 QWindow *menuWindow = menu.windowHandle();
1212 QVERIFY(menuWindow);
1213 //go over the submenu, press, move and release over the top level action
1214 //this opens the submenu, move two times to emulate user interaction (d->motions > 0 in QMenu)
1215 QTest::mouseMove(window: menuWindow, pos: menu.rect().center() + QPoint(0,2));
1216 QTest::mouseMove(window: menuWindow, pos: menu.rect().center() + QPoint(1,3), delay: 60);
1217#ifdef Q_OS_WINRT
1218 QEXPECT_FAIL("", "WinRT does not support QTest::mouseMove", Abort);
1219#endif
1220 QVERIFY(menuShownSpy.wait());
1221 QVERIFY(sub.isVisible());
1222 QVERIFY(QTest::qWaitForWindowExposed(&sub));
1223 //press over the submenu entry
1224 QTest::mousePress(window: menuWindow, button: Qt::LeftButton, stateKey: {}, pos: menu.rect().center() + QPoint(0, 2), delay: 300);
1225 //move over the main action
1226 QTest::mouseMove(window: menuWindow, pos: menu.rect().center() - QPoint(0,2));
1227 QVERIFY(menuHiddenSpy.wait());
1228 //the submenu must have been hidden for the bug to be triggered
1229 QVERIFY(!sub.isVisible());
1230 QTest::mouseRelease(window: menuWindow, button: Qt::LeftButton, stateKey: {}, pos: menu.rect().center() - QPoint(0, 2), delay: 300);
1231 QCOMPARE(spy.count(), 1);
1232}
1233#endif
1234
1235class MyWidget : public QWidget
1236{
1237public:
1238 MyWidget(QWidget *parent) :
1239 QWidget(parent),
1240 move(0), enter(0), leave(0)
1241 {
1242 setMinimumSize(minw: 100, minh: 100);
1243 setMouseTracking(true);
1244 }
1245
1246 bool event(QEvent *e) override
1247 {
1248 switch (e->type()) {
1249 case QEvent::MouseMove:
1250 ++move;
1251 break;
1252 case QEvent::Enter:
1253 ++enter;
1254 break;
1255 case QEvent::Leave:
1256 ++leave;
1257 break;
1258 default:
1259 break;
1260 }
1261 return QWidget::event(event: e);
1262 }
1263
1264 int move, enter, leave;
1265};
1266
1267void tst_QMenu::QTBUG47515_widgetActionEnterLeave()
1268{
1269 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
1270 QSKIP("Window activation is not supported");
1271 if (QGuiApplication::platformName() == QLatin1String("cocoa"))
1272 QSKIP("This test is meaningless on macOS, for additional info see QTBUG-63031");
1273
1274 const QRect availableGeometry = QGuiApplication::primaryScreen()->availableGeometry();
1275 QRect geometry(QPoint(), availableGeometry.size() / 3);
1276 geometry.moveCenter(p: availableGeometry.center());
1277 QPoint pointOutsideMenu = geometry.bottomRight() - QPoint(5, 5);
1278
1279 QMainWindow topLevel;
1280 topLevel.setGeometry(geometry);
1281
1282 QMenuBar *menuBar = topLevel.menuBar();
1283 menuBar->setNativeMenuBar(false);
1284 QMenu *menu = menuBar->addMenu(title: "Menu1");
1285 QMenu *submenu = menu->addMenu(title: "Menu2");
1286
1287 QWidgetAction *menuAction = new QWidgetAction(menu);
1288 MyWidget *w1 = new MyWidget(menu);
1289 menuAction->setDefaultWidget(w1);
1290
1291 QWidgetAction *submenuAction = new QWidgetAction(submenu);
1292 MyWidget *w2 = new MyWidget(submenu);
1293 submenuAction->setDefaultWidget(w2);
1294
1295 QAction *nextMenuAct = menu->addMenu(menu: submenu);
1296
1297 menu->addAction(action: menuAction);
1298 submenu->addAction(action: submenuAction);
1299
1300 topLevel.show();
1301 topLevel.setWindowTitle(QTest::currentTestFunction());
1302 QVERIFY(QTest::qWaitForWindowActive(&topLevel));
1303 QWindow *topLevelWindow = topLevel.windowHandle();
1304 QVERIFY(topLevelWindow);
1305
1306 // Root menu: Click on menu bar to open menu1
1307 {
1308 const QPoint menuActionPos = menuBar->mapTo(&topLevel, menuBar->actionGeometry(menu->menuAction()).center());
1309 QTest::mouseClick(window: topLevelWindow, button: Qt::LeftButton, stateKey: Qt::KeyboardModifiers(), pos: menuActionPos);
1310 QVERIFY(QTest::qWaitForWindowExposed(menu));
1311
1312 w1->enter = 0;
1313 w1->leave = 0;
1314 QPoint w1Center = topLevel.mapFromGlobal(w1->mapToGlobal(w1->rect().center()));
1315 QTest::mouseMove(window: topLevelWindow, pos: w1Center);
1316 QVERIFY(w1->isVisible());
1317 QTRY_COMPARE(w1->leave, 0);
1318#ifdef Q_OS_WINRT
1319 QEXPECT_FAIL("", "WinRT does not support QTest::mouseMove", Abort);
1320#endif
1321 QTRY_COMPARE(w1->enter, 1);
1322
1323 // Check whether leave event is not delivered on mouse move
1324 w1->move = 0;
1325 QTest::mouseMove(window: topLevelWindow, pos: w1Center + QPoint(1, 1));
1326 QTRY_COMPARE(w1->move, 1);
1327 QTRY_COMPARE(w1->leave, 0);
1328 QTRY_COMPARE(w1->enter, 1);
1329
1330 QTest::mouseMove(window: topLevelWindow, pos: topLevel.mapFromGlobal(pointOutsideMenu));
1331 QTRY_COMPARE(w1->leave, 1);
1332 QTRY_COMPARE(w1->enter, 1);
1333 }
1334
1335 // Submenu
1336 {
1337 menu->setActiveAction(nextMenuAct);
1338 QVERIFY(QTest::qWaitForWindowExposed(submenu));
1339
1340 QPoint w2Center = topLevel.mapFromGlobal(w2->mapToGlobal(w2->rect().center()));
1341 QTest::mouseMove(window: topLevelWindow, pos: w2Center);
1342
1343 QVERIFY(w2->isVisible());
1344 QTRY_COMPARE(w2->leave, 0);
1345 QTRY_COMPARE(w2->enter, 1);
1346
1347 // Check whether leave event is not delivered on mouse move
1348 w2->move = 0;
1349 QTest::mouseMove(window: topLevelWindow, pos: w2Center + QPoint(1, 1));
1350 QTRY_COMPARE(w2->move, 1);
1351 QTRY_COMPARE(w2->leave, 0);
1352 QTRY_COMPARE(w2->enter, 1);
1353
1354 QTest::mouseMove(window: topLevelWindow, pos: topLevel.mapFromGlobal(pointOutsideMenu));
1355 QTRY_COMPARE(w2->leave, 1);
1356 QTRY_COMPARE(w2->enter, 1);
1357 }
1358}
1359
1360void tst_QMenu::QTBUG8122_widgetActionCrashOnClose()
1361{
1362 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
1363 QSKIP("Window activation is not supported");
1364 if (QGuiApplication::platformName() == QLatin1String("cocoa"))
1365 QSKIP("See QTBUG-63031");
1366#ifdef Q_OS_WINRT
1367 QSKIP("WinRT does not support QTest::mouseMove");
1368#endif
1369
1370 const QRect availableGeometry = QGuiApplication::primaryScreen()->availableGeometry();
1371 QRect geometry(QPoint(), availableGeometry.size() / 3);
1372 geometry.moveCenter(p: availableGeometry.center());
1373 QPoint pointOutsideMenu = geometry.bottomRight() - QPoint(5, 5);
1374
1375 QMainWindow topLevel;
1376 topLevel.setGeometry(geometry);
1377
1378 auto menuBar = topLevel.menuBar();
1379 auto menu = menuBar->addMenu(title: "Menu");
1380 auto wAct = new QWidgetAction(menu);
1381 auto spinBox1 = new QSpinBox(menu);
1382 wAct->setDefaultWidget(spinBox1);
1383 menu->addAction(action: wAct);
1384 auto subMenu = menu->addMenu(title: "Submenu");
1385 auto nextMenuAct = menu->addMenu(menu: subMenu);
1386 auto wAct2 = new QWidgetAction(menu);
1387 auto spinBox2 = new QSpinBox(menu);
1388 wAct2->setDefaultWidget(spinBox2);
1389 subMenu->addAction(action: wAct2);
1390 QObject::connect(sender: spinBox2, signal: &QSpinBox::editingFinished, receiver: menu, slot: &QMenu::hide);
1391
1392 topLevel.show();
1393 topLevel.setWindowTitle(QTest::currentTestFunction());
1394 QVERIFY(QTest::qWaitForWindowActive(&topLevel));
1395 QWindow *topLevelWindow = topLevel.windowHandle();
1396 QVERIFY(topLevelWindow);
1397
1398 const QPoint menuActionPos = menuBar->mapTo(&topLevel, menuBar->actionGeometry(menu->menuAction()).center());
1399 QTest::mouseClick(window: topLevelWindow, button: Qt::LeftButton, stateKey: Qt::KeyboardModifiers(), pos: menuActionPos);
1400 QVERIFY(QTest::qWaitForWindowExposed(menu));
1401
1402 QPoint w1Center = topLevel.mapFromGlobal(spinBox1->mapToGlobal(spinBox1->rect().center()));
1403 QTest::mouseClick(window: topLevelWindow, button: Qt::LeftButton, stateKey: Qt::KeyboardModifiers(), pos: w1Center);
1404 menu->setActiveAction(nextMenuAct);
1405 QVERIFY(QTest::qWaitForWindowExposed(subMenu));
1406
1407 QPoint w2Center = topLevel.mapFromGlobal(spinBox2->mapToGlobal(spinBox2->rect().center()));
1408 QTest::mouseClick(window: topLevelWindow, button: Qt::LeftButton, stateKey: Qt::KeyboardModifiers(), pos: w2Center);
1409 QTest::mouseMove(window: topLevelWindow, pos: topLevel.mapFromGlobal(pointOutsideMenu));
1410 QTRY_VERIFY(menu->isHidden());
1411}
1412
1413/*!
1414 Test that a QWidgetAction that fires closes the menus that it is in.
1415*/
1416void tst_QMenu::widgetActionTriggerClosesMenu()
1417{
1418 class ButtonAction : public QWidgetAction
1419 {
1420 public:
1421 ButtonAction()
1422 : QWidgetAction(nullptr)
1423 {}
1424
1425 void click()
1426 {
1427 if (pushButton)
1428 pushButton->click();
1429 }
1430
1431 protected:
1432 QWidget *createWidget(QWidget *parent)
1433 {
1434 QPushButton *button = new QPushButton(QLatin1String("Button"), parent);
1435 connect(sender: button, signal: &QPushButton::clicked, receiver: this, slot: &QAction::trigger);
1436
1437 if (!pushButton)
1438 pushButton = button;
1439 return button;
1440 }
1441
1442 private:
1443 QPointer<QPushButton> pushButton;
1444 };
1445
1446 QMenu menu;
1447 QMenu submenu;
1448
1449 int menuTriggeredCount = 0;
1450 int menuAboutToHideCount = 0;
1451 QAction *actionTriggered = nullptr;
1452
1453 connect(sender: &menu, signal: &QMenu::triggered, context: this, slot: [&](QAction *action){
1454 ++menuTriggeredCount;
1455 actionTriggered = action;
1456 });
1457 connect (sender: &menu, signal: &QMenu::aboutToHide, context: this, slot: [&](){
1458 ++menuAboutToHideCount;
1459 });
1460
1461 QAction regularAction(QLatin1String("Action"));
1462 ButtonAction widgetAction;
1463
1464 submenu.addAction(action: &regularAction);
1465 submenu.addAction(action: &widgetAction);
1466
1467 menu.addMenu(menu: &submenu);
1468 menu.addAction(action: &regularAction);
1469 menu.addAction(action: &widgetAction);
1470
1471 menu.popup(pos: QPoint(200,200));
1472 submenu.popup(pos: QPoint(250,250));
1473 if (!QTest::qWaitForWindowExposed(widget: &menu) || !QTest::qWaitForWindowExposed(widget: &submenu))
1474 QSKIP("Failed to show menus, aborting test");
1475
1476 regularAction.trigger();
1477 QVERIFY(menu.isVisible());
1478 QVERIFY(submenu.isVisible());
1479 QCOMPARE(menuTriggeredCount, 1);
1480 QCOMPARE(actionTriggered, &regularAction);
1481 menuTriggeredCount = 0;
1482 actionTriggered = nullptr;
1483
1484 widgetAction.click();
1485 QVERIFY(!menu.isVisible());
1486 QVERIFY(!submenu.isVisible());
1487 QCOMPARE(menuTriggeredCount, 1);
1488 QCOMPARE(menuAboutToHideCount, 1);
1489 QCOMPARE(actionTriggered, &widgetAction);
1490}
1491
1492class MyMenu : public QMenu
1493{
1494 Q_OBJECT
1495public:
1496 MyMenu() : m_currentIndex(0)
1497 {
1498 for (int i = 0; i < 2; ++i)
1499 dialogActions[i] = addAction(text: QLatin1String("dialog ") + QString::number(i), receiver: dialogs + i, SLOT(exec()));
1500 }
1501
1502 void activateAction(int index)
1503 {
1504 m_currentIndex = index;
1505 popup(pos: QPoint());
1506 QVERIFY(QTest::qWaitForWindowExposed(this));
1507 setActiveAction(dialogActions[index]);
1508 QTimer::singleShot(msec: 500, receiver: this, SLOT(checkVisibility()));
1509 QTest::keyClick(widget: this, key: Qt::Key_Enter); //activation
1510 }
1511
1512public slots:
1513 void activateLastAction()
1514 {
1515 activateAction(index: 1);
1516 }
1517
1518 void checkVisibility()
1519 {
1520 QTRY_VERIFY(dialogs[m_currentIndex].isVisible());
1521 if (m_currentIndex == 1) {
1522 QApplication::closeAllWindows(); //this is the end of the test
1523 }
1524 }
1525
1526private:
1527 QAction *dialogActions[2];
1528 QDialog dialogs[2];
1529 int m_currentIndex;
1530};
1531
1532void tst_QMenu::QTBUG_10735_crashWithDialog()
1533{
1534 MyMenu menu;
1535
1536 QTimer::singleShot(msec: 1000, receiver: &menu, SLOT(activateLastAction()));
1537 menu.activateAction(index: 0);
1538}
1539
1540#ifdef Q_OS_MAC
1541void tst_QMenu::QTBUG_37933_ampersands_data()
1542{
1543 QTest::addColumn<QString>("title");
1544 QTest::addColumn<QString>("visibleTitle");
1545 QTest::newRow("simple") << QString("Test") << QString("Test");
1546 QTest::newRow("ampersand") << QString("&Test") << QString("Test");
1547 QTest::newRow("double_ampersand") << QString("&Test && more") << QString("Test & more");
1548 QTest::newRow("ampersand_in_parentheses") << QString("Test(&T) (&&) more") << QString("Test (&) more");
1549 QTest::newRow("ampersand_in_parentheses_after_space") << QString("Test (&T)") << QString("Test");
1550 QTest::newRow("ampersand_in_parentheses_after_spaces") << QString("Test (&T)") << QString("Test");
1551 QTest::newRow("ampersand_in_parentheses_before_space") << QString("Test(&T) ") << QString("Test ");
1552 QTest::newRow("only_ampersand_in_parentheses") << QString("(&T)") << QString("");
1553 QTest::newRow("only_ampersand_in_parentheses_after_space") << QString(" (&T)") << QString("");
1554 QTest::newRow("parentheses_after_space") << QString(" (Dummy)") << QString(" (Dummy)");
1555 QTest::newRow("ampersand_after_space") << QString("About &Qt Project") << QString("About Qt Project");
1556}
1557
1558void tst_qmenu_QTBUG_37933_ampersands();
1559
1560void tst_QMenu::QTBUG_37933_ampersands()
1561{
1562 // external in .mm file
1563 tst_qmenu_QTBUG_37933_ampersands();
1564}
1565#endif
1566
1567void tst_QMenu::QTBUG_56917_wideMenuSize()
1568{
1569 // menu shouldn't to take on full screen height when menu width is larger than screen width
1570 QMenu menu;
1571 QString longString;
1572 longString.fill(c: QLatin1Char('Q'), size: 3000);
1573 menu.addAction(text: longString);
1574 QSize menuSizeHint = menu.sizeHint();
1575 menu.popup(pos: QPoint());
1576 QVERIFY(QTest::qWaitForWindowExposed(&menu));
1577 QVERIFY(menu.isVisible());
1578#ifdef Q_OS_WINRT
1579 QEXPECT_FAIL("", "Broken on WinRT - QTBUG-68297", Abort);
1580#endif
1581 QVERIFY(menu.height() <= menuSizeHint.height());
1582}
1583
1584void tst_QMenu::QTBUG_56917_wideMenuScreenNumber()
1585{
1586 if (QApplication::styleHints()->showIsFullScreen())
1587 QSKIP("The platform defaults to windows being fullscreen.");
1588 // menu must appear on the same screen where show action is triggered
1589 QString longString;
1590 longString.fill(c: QLatin1Char('Q'), size: 3000);
1591
1592 const QList<QScreen *> screens = QGuiApplication::screens();
1593 for (QScreen *screen : screens) {
1594 QMenu menu;
1595 menu.addAction(text: longString);
1596 menu.popup(pos: screen->geometry().center());
1597 QVERIFY(QTest::qWaitForWindowExposed(&menu));
1598 QVERIFY(menu.isVisible());
1599 QCOMPARE(QGuiApplication::screenAt(menu.pos()), screen);
1600 }
1601}
1602
1603void tst_QMenu::QTBUG_56917_wideSubmenuScreenNumber()
1604{
1605 if (QApplication::styleHints()->showIsFullScreen())
1606 QSKIP("The platform defaults to windows being fullscreen.");
1607 // submenu must appear on the same screen where its parent menu is shown
1608 QString longString;
1609 longString.fill(c: QLatin1Char('Q'), size: 3000);
1610
1611 const QList<QScreen *> screens = QGuiApplication::screens();
1612 for (QScreen *screen : screens) {
1613 QMenu menu;
1614 QMenu submenu("Submenu");
1615 submenu.addAction(text: longString);
1616 QAction *action = menu.addMenu(menu: &submenu);
1617 menu.popup(pos: screen->geometry().center());
1618 QVERIFY(QTest::qWaitForWindowExposed(&menu));
1619 QVERIFY(menu.isVisible());
1620 QTest::mouseClick(widget: &menu, button: Qt::LeftButton, stateKey: {}, pos: menu.actionGeometry(action).center());
1621 QTest::qWait(ms: 100);
1622 QVERIFY(QTest::qWaitForWindowExposed(&submenu));
1623 QVERIFY(submenu.isVisible());
1624 QCOMPARE(QGuiApplication::screenAt(submenu.pos()), screen);
1625 }
1626}
1627
1628void tst_QMenu::menuSize_Scrolling_data()
1629{
1630 QTest::addColumn<int>(name: "numItems");
1631 QTest::addColumn<int>(name: "topMargin");
1632 QTest::addColumn<int>(name: "bottomMargin");
1633 QTest::addColumn<int>(name: "leftMargin");
1634 QTest::addColumn<int>(name: "rightMargin");
1635 QTest::addColumn<int>(name: "topPadding");
1636 QTest::addColumn<int>(name: "bottomPadding");
1637 QTest::addColumn<int>(name: "leftPadding");
1638 QTest::addColumn<int>(name: "rightPadding");
1639 QTest::addColumn<int>(name: "border");
1640 QTest::addColumn<bool>(name: "scrollable");
1641 QTest::addColumn<bool>(name: "tearOff");
1642
1643 // test data
1644 // a single column and non-scrollable menu with contents margins + border
1645 QTest::newRow(dataTag: "data0") << 5 << 2 << 2 << 2 << 2 << 0 << 0 << 0 << 0 << 2 << false << false;
1646 // a single column and non-scrollable menu with paddings + border
1647 QTest::newRow(dataTag: "data1") << 5 << 0 << 0 << 0 << 0 << 2 << 2 << 2 << 2 << 2 << false << false;
1648 // a single column and non-scrollable menu with contents margins + paddings + border
1649 QTest::newRow(dataTag: "data2") << 5 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << false << false;
1650 // a single column and non-scrollable menu with contents margins + paddings + border + tear-off
1651 QTest::newRow(dataTag: "data3") << 5 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << false << true;
1652 // a multi-column menu with contents margins + border
1653 QTest::newRow(dataTag: "data4") << 80 << 2 << 2 << 2 << 2 << 0 << 0 << 0 << 0 << 2 << false << false;
1654 // a multi-column menu with paddings + border
1655 QTest::newRow(dataTag: "data5") << 80 << 0 << 0 << 0 << 0 << 2 << 2 << 2 << 2 << 2 << false << false;
1656 // a multi-column menu with contents margins + paddings + border
1657 QTest::newRow(dataTag: "data6") << 80 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << false << false;
1658 // a multi-column menu with contents margins + paddings + border + tear-off
1659 QTest::newRow(dataTag: "data7") << 80 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << false << true;
1660 // a scrollable menu with contents margins + border
1661 QTest::newRow(dataTag: "data8") << 80 << 2 << 2 << 2 << 2 << 0 << 0 << 0 << 0 << 2 << true << false;
1662 // a scrollable menu with paddings + border
1663 QTest::newRow(dataTag: "data9") << 80 << 0 << 0 << 0 << 0 << 2 << 2 << 2 << 2 << 2 << true << false;
1664 // a scrollable menu with contents margins + paddings + border
1665 QTest::newRow(dataTag: "data10") << 80 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << true << false;
1666 // a scrollable menu with contents margins + paddings + border + tear-off
1667 QTest::newRow(dataTag: "data11") << 80 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << true << true;
1668}
1669
1670void tst_QMenu::menuSize_Scrolling()
1671{
1672 class TestMenu : public QMenu
1673 {
1674 public:
1675 struct ContentsMargins
1676 {
1677 ContentsMargins(int l, int t, int r, int b)
1678 : left(l), top(t), right(r), bottom(b) {}
1679 int left;
1680 int top;
1681 int right;
1682 int bottom;
1683 };
1684
1685 struct MenuPaddings
1686 {
1687 MenuPaddings(int l, int t, int r, int b)
1688 : left(l), top(t), right(r), bottom(b) {}
1689 int left;
1690 int top;
1691 int right;
1692 int bottom;
1693 };
1694
1695 TestMenu(int numItems, const ContentsMargins &margins, const MenuPaddings &paddings,
1696 int border, bool scrollable, bool tearOff)
1697 : QMenu("Test Menu"),
1698 m_numItems(numItems),
1699 m_scrollable(scrollable),
1700 m_tearOff(tearOff)
1701 {
1702 init(margins, paddings, border);
1703 }
1704
1705 ~TestMenu() {}
1706
1707 private:
1708 void showEvent(QShowEvent *e) override
1709 {
1710 QVERIFY(actions().length() == m_numItems);
1711
1712 int hmargin = style()->pixelMetric(metric: QStyle::PM_MenuHMargin, option: nullptr, widget: this);
1713 int fw = style()->pixelMetric(metric: QStyle::PM_MenuPanelWidth, option: nullptr, widget: this);
1714 const QMargins cm = contentsMargins();
1715 QRect lastItem = actionGeometry(actions().at(i: actions().length() - 1));
1716 QSize s = size();
1717#ifdef Q_OS_WINRT
1718 QEXPECT_FAIL("", "Broken on WinRT - QTBUG-68297", Abort);
1719#endif
1720 if (!QGuiApplication::platformName().compare(other: QLatin1String("minimal"), cs: Qt::CaseInsensitive)
1721 || !QGuiApplication::platformName().compare(other: QLatin1String("offscreen"), cs: Qt::CaseInsensitive)) {
1722 QWARN("Skipping test on minimal/offscreen platforms - QTBUG-73522");
1723 QMenu::showEvent(event: e);
1724 return;
1725 }
1726
1727 QCOMPARE( s.width(), lastItem.right() + fw + hmargin + cm.right() + 1);
1728 QMenu::showEvent(event: e);
1729 }
1730
1731 void init(const ContentsMargins &margins, const MenuPaddings &paddings, int border)
1732 {
1733 setLayoutDirection(Qt::LeftToRight);
1734
1735 setTearOffEnabled(m_tearOff);
1736 setContentsMargins(left: margins.left, top: margins.top, right: margins.right, bottom: margins.bottom);
1737 QString cssStyle("QMenu {menu-scrollable: ");
1738 cssStyle += (m_scrollable ? QString::number(1) : QString::number(0));
1739 cssStyle += "; border: ";
1740 cssStyle += QString::number(border);
1741 cssStyle += "px solid black; padding: ";
1742 cssStyle += QString::number(paddings.top);
1743 cssStyle += "px ";
1744 cssStyle += QString::number(paddings.right);
1745 cssStyle += "px ";
1746 cssStyle += QString::number(paddings.bottom);
1747 cssStyle += "px ";
1748 cssStyle += QString::number(paddings.left);
1749 cssStyle += "px;}";
1750 setStyleSheet(cssStyle);
1751 for (int i = 1; i <= m_numItems; i++)
1752 addAction(text: "MenuItem " + QString::number(i));
1753 }
1754
1755 private:
1756 int m_numItems;
1757 bool m_scrollable;
1758 bool m_tearOff;
1759 };
1760
1761 QFETCH(int, numItems);
1762 QFETCH(int, topMargin);
1763 QFETCH(int, bottomMargin);
1764 QFETCH(int, leftMargin);
1765 QFETCH(int, rightMargin);
1766 QFETCH(int, topPadding);
1767 QFETCH(int, bottomPadding);
1768 QFETCH(int, leftPadding);
1769 QFETCH(int, rightPadding);
1770 QFETCH(int, border);
1771 QFETCH(bool, scrollable);
1772 QFETCH(bool, tearOff);
1773
1774 qApp->setAttribute(attribute: Qt::AA_DontUseNativeMenuBar);
1775
1776 TestMenu::ContentsMargins margins(leftMargin, topMargin, rightMargin, bottomMargin);
1777 TestMenu::MenuPaddings paddings(leftPadding, topPadding, rightPadding, bottomPadding);
1778 TestMenu menu(numItems, margins, paddings, border, scrollable, tearOff);
1779 menu.popup(pos: QPoint(0,0));
1780 centerOnScreen(w: &menu);
1781 QVERIFY(QTest::qWaitForWindowExposed(&menu));
1782
1783 QList<QAction *> actions = menu.actions();
1784 QCOMPARE(actions.length(), numItems);
1785
1786 MenuMetrics mm(&menu);
1787 QTest::keyClick(widget: &menu, key: Qt::Key_Home);
1788 QTRY_COMPARE(menu.actionGeometry(actions.first()).y(), mm.fw + mm.vmargin + topMargin + (tearOff ? mm.tearOffHeight : 0));
1789 QCOMPARE(menu.actionGeometry(actions.first()).x(), mm.fw + mm.hmargin + leftMargin);
1790
1791 if (!scrollable)
1792 return;
1793
1794 QTest::keyClick(widget: &menu, key: Qt::Key_End);
1795#ifdef Q_OS_WINRT
1796 QEXPECT_FAIL("data8", "Broken on WinRT - QTBUG-68297", Abort);
1797 QEXPECT_FAIL("data9", "Broken on WinRT - QTBUG-68297", Abort);
1798 QEXPECT_FAIL("data10", "Broken on WinRT - QTBUG-68297", Abort);
1799 QEXPECT_FAIL("data11", "Broken on WinRT - QTBUG-68297", Abort);
1800#endif
1801 QTRY_COMPARE(menu.actionGeometry(actions.last()).right(),
1802 menu.width() - mm.fw - mm.hmargin - leftMargin - 1);
1803 QCOMPARE(menu.actionGeometry(actions.last()).bottom(),
1804 menu.height() - mm.fw - mm.vmargin - bottomMargin - 1);
1805}
1806
1807void tst_QMenu::tearOffMenuNotDisplayed()
1808{
1809 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
1810 QSKIP("Window activation is not supported");
1811 QWidget widget;
1812 QScopedPointer<QMenu> menu(new QMenu(&widget));
1813 menu->setTearOffEnabled(true);
1814 QVERIFY(menu->isTearOffEnabled());
1815
1816 menu->setStyleSheet("QMenu { menu-scrollable: 1 }");
1817 for (int i = 0; i < 80; i++)
1818 menu->addAction(text: QString::number(i));
1819
1820 widget.resize(w: 300, h: 200);
1821 centerOnScreen(w: &widget);
1822 widget.show();
1823 widget.activateWindow();
1824 QVERIFY(QTest::qWaitForWindowActive(&widget));
1825 menu->popup(pos: widget.geometry().topRight() + QPoint(50, 0));
1826 QVERIFY(QTest::qWaitForWindowActive(menu.data()));
1827 QVERIFY(!menu->isTearOffMenuVisible());
1828
1829 MenuMetrics mm(menu.data());
1830 const int tearOffOffset = mm.fw + mm.vmargin + mm.tearOffHeight / 2;
1831
1832 QTest::mouseClick(widget: menu.data(), button: Qt::LeftButton, stateKey: {}, pos: QPoint(10, tearOffOffset), delay: 10);
1833 QTRY_VERIFY(menu->isTearOffMenuVisible());
1834 QPointer<QMenu> torn = getTornOffMenu();
1835 QVERIFY(torn);
1836 QVERIFY(torn->isVisible());
1837 QVERIFY(torn->minimumWidth() >=0 && torn->minimumWidth() < QWIDGETSIZE_MAX);
1838
1839 menu->hideTearOffMenu();
1840 QVERIFY(!menu->isTearOffMenuVisible());
1841 QVERIFY(!torn->isVisible());
1842}
1843
1844void tst_QMenu::QTBUG_61039_menu_shortcuts()
1845{
1846 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
1847 QSKIP("Window activation is not supported");
1848
1849 QAction *actionKamen = new QAction("Action Kamen");
1850 actionKamen->setShortcut(QKeySequence(QLatin1String("K")));
1851
1852 QAction *actionJoe = new QAction("Action Joe");
1853 actionJoe->setShortcut(QKeySequence(QLatin1String("Ctrl+J")));
1854
1855 QMenu menu;
1856 menu.addAction(action: actionKamen);
1857 menu.addAction(action: actionJoe);
1858 QVERIFY(!menu.platformMenu());
1859
1860 QWidget widget;
1861 widget.addAction(action: menu.menuAction());
1862 widget.show();
1863 QVERIFY(QTest::qWaitForWindowActive(&widget));
1864
1865 QSignalSpy actionKamenSpy(actionKamen, &QAction::triggered);
1866 QTest::keyClick(widget: &widget, key: Qt::Key_K);
1867 QTRY_COMPARE(actionKamenSpy.count(), 1);
1868
1869 QSignalSpy actionJoeSpy(actionJoe, &QAction::triggered);
1870 QTest::keyClick(widget: &widget, key: Qt::Key_J, modifier: Qt::ControlModifier);
1871 QTRY_COMPARE(actionJoeSpy.count(), 1);
1872}
1873
1874QTEST_MAIN(tst_QMenu)
1875#include "tst_qmenu.moc"
1876

source code of qtbase/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp