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 | |
55 | using namespace QTestPrivate; |
56 | |
57 | Q_DECLARE_METATYPE(Qt::Key); |
58 | Q_DECLARE_METATYPE(Qt::KeyboardModifiers); |
59 | |
60 | struct { |
61 | int ; |
62 | int ; |
63 | int ; |
64 | int ; |
65 | |
66 | (const QMenu *) { |
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 | |
74 | class : public QObject |
75 | { |
76 | Q_OBJECT |
77 | |
78 | public: |
79 | tst_QMenu(); |
80 | |
81 | public slots: |
82 | void initTestCase(); |
83 | void cleanupTestCase(); |
84 | void init(); |
85 | private 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 | |
136 | protected slots: |
137 | void onActivated(QAction*); |
138 | void onHighlighted(QAction*); |
139 | void onStatusMessageChanged(const QString &); |
140 | void onStatusTipTimer(); |
141 | void (QAction *a) { delete a; } |
142 | private: |
143 | void createActions(); |
144 | QMenu *[2], *; |
145 | enum { = 10 }; |
146 | QAction *, *, *[num_builtins]; |
147 | QString ; |
148 | bool ; |
149 | }; |
150 | |
151 | // Testing get/set functions |
152 | void tst_QMenu::() |
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 | |
174 | 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 | |
182 | void tst_QMenu::() |
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 | |
193 | void tst_QMenu::() |
194 | { |
195 | for (int i = 0; i < 2; i++) |
196 | menus[i]->clear(); |
197 | for (int i = 0; i < num_builtins; i++) { |
198 | bool = 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 | |
209 | void tst_QMenu::() |
210 | { |
211 | activated = highlighted = 0; |
212 | lastMenu = 0; |
213 | m_onStatusTipTimerExecuted = false; |
214 | } |
215 | |
216 | void tst_QMenu::() |
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 | |
263 | void tst_QMenu::(QAction *action) |
264 | { |
265 | highlighted = action; |
266 | lastMenu = qobject_cast<QMenu*>(object: sender()); |
267 | } |
268 | |
269 | void tst_QMenu::(QAction *action) |
270 | { |
271 | activated = action; |
272 | lastMenu = qobject_cast<QMenu*>(object: sender()); |
273 | } |
274 | |
275 | void tst_QMenu::(const QString &s) |
276 | { |
277 | statustip=s; |
278 | } |
279 | |
280 | void 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 | |
289 | static void testFunction() { } |
290 | |
291 | void tst_QMenu::() |
292 | { |
293 | QMenu ; |
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 | |
317 | void tst_QMenu::() |
318 | { |
319 | QWidget topLevel; |
320 | topLevel.resize(w: 300, h: 200); |
321 | centerOnScreen(w: &topLevel); |
322 | QMenu (&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 | |
357 | void tst_QMenu::() |
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 | |
390 | void tst_QMenu::() |
391 | { |
392 | QFETCH(Qt::Key, key); |
393 | QFETCH(Qt::KeyboardModifiers, modifiers); |
394 | QFETCH(int, expected_action); |
395 | QFETCH(int, ); |
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 |
428 | QT_BEGIN_NAMESPACE |
429 | extern bool qt_tab_all_widgets(); // from qapplication.cpp |
430 | QT_END_NAMESPACE |
431 | #endif |
432 | |
433 | void tst_QMenu::() |
434 | { |
435 | QMenu ; |
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 | |
467 | void tst_QMenu::() |
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 * = new QAction("&File" , &w); |
479 | w.menuBar()->addAction(action: aFileMenu); |
480 | |
481 | QMenu *m = new QMenu(&w); |
482 | QAction * = 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 | |
516 | void tst_QMenu::() |
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 |
555 | void tst_QMenu::() |
556 | { |
557 | QMenu * = 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 | |
573 | void tst_QMenu::() |
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 *=m.addAction(text: "menuitem1" ); |
586 | QAction *=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 | |
616 | static QMenu *() |
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 | |
625 | void tst_QMenu::() |
626 | { |
627 | if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation)) |
628 | QSKIP("Window activation is not supported" ); |
629 | |
630 | QWidget widget; |
631 | QScopedPointer<QMenu> (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 | |
700 | void tst_QMenu::() |
701 | { |
702 | if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation)) |
703 | QSKIP("Window activation is not supported" ); |
704 | |
705 | QWidget widget; |
706 | QMenu * = new QMenu(&widget); |
707 | QVERIFY(!menu->isTearOffEnabled()); //default value |
708 | menu->setTearOffEnabled(true); |
709 | QVERIFY(menu->isTearOffEnabled()); |
710 | QMenu * = 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 = menu->actionGeometry(menu->actions().at(i: 0)); |
728 | const QPoint (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 | |
749 | void tst_QMenu::() |
750 | { |
751 | QMainWindow win; |
752 | win.setLayoutDirection(Qt::RightToLeft); |
753 | win.resize(w: 300, h: 200); |
754 | centerOnScreen(w: &win); |
755 | |
756 | QMenu (&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 | |
778 | void tst_QMenu::() |
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 | |
786 | void tst_QMenu::() |
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 * = 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. |
818 | class : public QMenu |
819 | { |
820 | Q_OBJECT |
821 | public: |
822 | () : 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 | |
830 | protected: |
831 | void (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 (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 | |
854 | private: |
855 | int ; |
856 | int ; |
857 | QMenu *; |
858 | QAction *; |
859 | }; |
860 | |
861 | void tst_QMenu::() |
862 | { |
863 | |
864 | #ifdef Q_OS_WINRT |
865 | QSKIP("Broken on WinRT - QTBUG-68297" ); |
866 | #endif |
867 | SubMenuPositionExecMenu ; |
868 | menu.exec(pos: QGuiApplication::primaryScreen()->availableGeometry().center()); |
869 | } |
870 | |
871 | void tst_QMenu::() |
872 | { |
873 | QMenu ; |
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 | |
879 | class : public QMenu |
880 | { |
881 | Q_OBJECT |
882 | public slots: |
883 | void () |
884 | { |
885 | QMenu::clear(); |
886 | } |
887 | }; |
888 | |
889 | void tst_QMenu::() |
890 | { |
891 | //this test used to crash |
892 | 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 | |
899 | void tst_QMenu::task250673_activeMultiColumnSubMenuPosition() |
900 | { |
901 | class : 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 * = 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 = main.style()->pixelMetric(metric: QStyle::PM_SubMenuOverlap, option: 0, widget: &main); |
937 | QVERIFY((sub.geometry().left() - subMenuOffset + 5) < main.geometry().right()); |
938 | } |
939 | |
940 | void tst_QMenu::() |
941 | { |
942 | QMenu ; |
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 | |
952 | void tst_QMenu::() |
953 | { |
954 | QMenu ; |
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 | |
985 | class : public QMenu |
986 | { |
987 | Q_OBJECT |
988 | public slots: |
989 | void (QPaintEvent *e) |
990 | { |
991 | QMenu::paintEvent(e); |
992 | painted = true; |
993 | } |
994 | |
995 | public: |
996 | bool ; |
997 | }; |
998 | |
999 | // Mouse move related signals for Windows Mobile unavailable |
1000 | void tst_QMenu::() |
1001 | { |
1002 | Menu258920 ; |
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 | |
1022 | void tst_QMenu::() |
1023 | { |
1024 | QMenu ; |
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 | |
1032 | void tst_QMenu::() |
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 | |
1042 | class : public QMenu { |
1043 | Q_OBJECT |
1044 | public: |
1045 | explicit PopulateOnAboutToShowTestMenu(QWidget *parent = 0); |
1046 | |
1047 | public slots: |
1048 | void populateMenu(); |
1049 | }; |
1050 | |
1051 | PopulateOnAboutToShowTestMenu::(QWidget *parent) : QMenu(parent) |
1052 | { |
1053 | connect(sender: this, SIGNAL(aboutToShow()), receiver: this, SLOT(populateMenu())); |
1054 | } |
1055 | |
1056 | void PopulateOnAboutToShowTestMenu::() |
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 | |
1065 | static 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 | |
1072 | void tst_QMenu::() |
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 *= 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 | |
1117 | void tst_QMenu::() |
1118 | { |
1119 | QMenu ("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 | |
1134 | void tst_QMenu::() |
1135 | { |
1136 | QMenu ("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 | |
1153 | static bool isPlatformWayland() |
1154 | { |
1155 | return !QGuiApplication::platformName().compare(other: QLatin1String("wayland" ), cs: Qt::CaseInsensitive); |
1156 | } |
1157 | |
1158 | void tst_QMenu::() |
1159 | { |
1160 | if (isPlatformWayland()) |
1161 | QSKIP("Creating xdg_popups on Wayland requires real input events. Positions would be off." ); |
1162 | |
1163 | QMenu ("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 | |
1177 | void tst_QMenu::() |
1178 | { |
1179 | QMenu ("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 |
1195 | void tst_QMenu::() |
1196 | { |
1197 | if (isPlatformWayland()) |
1198 | QSKIP("Wayland: Creating (grabbing) popups requires real mouse events." ); |
1199 | |
1200 | QMenu ("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 (&sub, &QMenu::aboutToShow); |
1209 | QSignalSpy (&sub, &QMenu::aboutToHide); |
1210 | QVERIFY(QTest::qWaitForWindowExposed(&menu)); |
1211 | QWindow * = 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 | |
1235 | class MyWidget : public QWidget |
1236 | { |
1237 | public: |
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 | |
1267 | void tst_QMenu::() |
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 = geometry.bottomRight() - QPoint(5, 5); |
1278 | |
1279 | QMainWindow topLevel; |
1280 | topLevel.setGeometry(geometry); |
1281 | |
1282 | QMenuBar * = topLevel.menuBar(); |
1283 | menuBar->setNativeMenuBar(false); |
1284 | QMenu * = menuBar->addMenu(title: "Menu1" ); |
1285 | QMenu * = menu->addMenu(title: "Menu2" ); |
1286 | |
1287 | QWidgetAction * = new QWidgetAction(menu); |
1288 | MyWidget *w1 = new MyWidget(menu); |
1289 | menuAction->setDefaultWidget(w1); |
1290 | |
1291 | QWidgetAction * = new QWidgetAction(submenu); |
1292 | MyWidget *w2 = new MyWidget(submenu); |
1293 | submenuAction->setDefaultWidget(w2); |
1294 | |
1295 | QAction * = 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 = 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 | |
1360 | void tst_QMenu::() |
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 = geometry.bottomRight() - QPoint(5, 5); |
1374 | |
1375 | QMainWindow topLevel; |
1376 | topLevel.setGeometry(geometry); |
1377 | |
1378 | auto = topLevel.menuBar(); |
1379 | auto = 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 = menu->addMenu(title: "Submenu" ); |
1385 | auto = 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 = 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 | */ |
1416 | void tst_QMenu::() |
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 ; |
1447 | QMenu ; |
1448 | |
1449 | int = 0; |
1450 | int = 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: ®ularAction); |
1465 | submenu.addAction(action: &widgetAction); |
1466 | |
1467 | menu.addMenu(menu: &submenu); |
1468 | menu.addAction(action: ®ularAction); |
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, ®ularAction); |
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 | |
1492 | class : public QMenu |
1493 | { |
1494 | Q_OBJECT |
1495 | public: |
1496 | () : 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 (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 | |
1512 | public slots: |
1513 | void () |
1514 | { |
1515 | activateAction(index: 1); |
1516 | } |
1517 | |
1518 | void () |
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 | |
1526 | private: |
1527 | QAction *[2]; |
1528 | QDialog [2]; |
1529 | int ; |
1530 | }; |
1531 | |
1532 | void tst_QMenu::() |
1533 | { |
1534 | MyMenu ; |
1535 | |
1536 | QTimer::singleShot(msec: 1000, receiver: &menu, SLOT(activateLastAction())); |
1537 | menu.activateAction(index: 0); |
1538 | } |
1539 | |
1540 | #ifdef Q_OS_MAC |
1541 | void 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 | |
1558 | void tst_qmenu_QTBUG_37933_ampersands(); |
1559 | |
1560 | void tst_QMenu::QTBUG_37933_ampersands() |
1561 | { |
1562 | // external in .mm file |
1563 | tst_qmenu_QTBUG_37933_ampersands(); |
1564 | } |
1565 | #endif |
1566 | |
1567 | void tst_QMenu::() |
1568 | { |
1569 | // menu shouldn't to take on full screen height when menu width is larger than screen width |
1570 | QMenu ; |
1571 | QString longString; |
1572 | longString.fill(c: QLatin1Char('Q'), size: 3000); |
1573 | menu.addAction(text: longString); |
1574 | QSize = 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 | |
1584 | void tst_QMenu::() |
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 ; |
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 | |
1603 | void tst_QMenu::() |
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 ; |
1614 | QMenu ("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 | |
1628 | void tst_QMenu::() |
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 | |
1670 | void tst_QMenu::() |
1671 | { |
1672 | class : 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 |
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 (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 | |
1807 | void tst_QMenu::() |
1808 | { |
1809 | if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation)) |
1810 | QSKIP("Window activation is not supported" ); |
1811 | QWidget widget; |
1812 | QScopedPointer<QMenu> (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 | |
1844 | void tst_QMenu::() |
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 ; |
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 | |
1874 | QTEST_MAIN(tst_QMenu) |
1875 | #include "tst_qmenu.moc" |
1876 | |