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 | |
30 | #include <QtTest/QtTest> |
31 | #include <qapplication.h> |
32 | #include <qmainwindow.h> |
33 | #include <qmenubar.h> |
34 | #include <qstyle.h> |
35 | #include <qproxystyle.h> |
36 | #include <qstylefactory.h> |
37 | #include <qaction.h> |
38 | #include <qstyleoption.h> |
39 | #include <QVBoxLayout> |
40 | #include <QLabel> |
41 | #include <QPlainTextEdit> |
42 | #include <qscreen.h> |
43 | |
44 | #include <qobject.h> |
45 | |
46 | QT_FORWARD_DECLARE_CLASS(QMainWindow) |
47 | |
48 | #include <qmenubar.h> |
49 | |
50 | #include <QtTest/private/qtesthelpers_p.h> |
51 | |
52 | using namespace QTestPrivate; |
53 | |
54 | // Helper to calculate the action position in window coordinates |
55 | static inline QPoint widgetToWindowPos(const QWidget *w, const QPoint &pos) |
56 | { |
57 | const QWindow *window = w->window()->windowHandle(); |
58 | Q_ASSERT(window); |
59 | return window->mapFromGlobal(pos: w->mapToGlobal(pos)); |
60 | } |
61 | |
62 | static QPoint (const QMenuBar *mb, QAction *a) |
63 | { |
64 | return widgetToWindowPos(w: mb, pos: mb->actionGeometry(a).center()); |
65 | } |
66 | |
67 | class : public QMenu |
68 | { |
69 | Q_OBJECT |
70 | public slots: |
71 | void () |
72 | { |
73 | //this will change the geometry of the menu |
74 | addAction(text: "action1" ); |
75 | addAction(text: "action2" ); |
76 | } |
77 | }; |
78 | |
79 | struct |
80 | { |
81 | QList<QMenu *> ; |
82 | QList<QAction *> ; |
83 | }; |
84 | |
85 | class : public QObject |
86 | { |
87 | Q_OBJECT |
88 | public: |
89 | tst_QMenuBar(); |
90 | |
91 | private slots: |
92 | void getSetCheck(); |
93 | void cleanup(); |
94 | |
95 | void clear(); |
96 | void removeItemAt(); |
97 | void removeItemAt_data(); |
98 | void removeItem_data(); |
99 | void removeItem(); |
100 | void count(); |
101 | void insertItem_QString_QObject(); |
102 | |
103 | #if !defined(Q_OS_DARWIN) |
104 | void accel(); |
105 | void activatedCount(); |
106 | void activatedCount_data(); |
107 | |
108 | void check_accelKeys(); |
109 | void check_cursorKeys1(); |
110 | void check_cursorKeys2(); |
111 | void check_cursorKeys3(); |
112 | |
113 | void check_escKey(); |
114 | #endif |
115 | void allowActiveAndDisabled(); |
116 | void taskQTBUG56860_focus(); |
117 | void check_endKey(); |
118 | void check_homeKey(); |
119 | |
120 | // void check_mouse1_data(); |
121 | // void check_mouse1(); |
122 | // void check_mouse2_data(); |
123 | // void check_mouse2(); |
124 | |
125 | void check_altPress(); |
126 | void check_altClosePress(); |
127 | #if !defined(Q_OS_DARWIN) |
128 | void check_shortcutPress(); |
129 | void check_menuPosition(); |
130 | void taskQTBUG46812_doNotLeaveMenubarHighlighted(); |
131 | #endif |
132 | void task223138_triggered(); |
133 | void task256322_highlight(); |
134 | void menubarSizeHint(); |
135 | #ifndef Q_OS_MACOS |
136 | void taskQTBUG4965_escapeEaten(); |
137 | #endif |
138 | void taskQTBUG11823_crashwithInvisibleActions(); |
139 | void closeOnSecondClickAndOpenOnThirdClick(); |
140 | void cornerWidgets_data(); |
141 | void cornerWidgets(); |
142 | void taskQTBUG53205_crashReparentNested(); |
143 | #ifdef Q_OS_MACOS |
144 | void taskQTBUG56275_reinsertMenuInParentlessQMenuBar(); |
145 | void QTBUG_57404_existingMenuItemException(); |
146 | #endif |
147 | void QTBUG_25669_menubarActionDoubleTriggered(); |
148 | void taskQTBUG55966_subMenuRemoved(); |
149 | void QTBUG_58344_invalidIcon(); |
150 | void platformMenu(); |
151 | void addActionQt5connect(); |
152 | void QTBUG_65488_hiddenActionTriggered(); |
153 | protected slots: |
154 | void onSimpleActivated( QAction*); |
155 | void onComplexActionTriggered(); |
156 | void slotForTaskQTBUG53205(); |
157 | |
158 | private: |
159 | TestMenu initSimpleMenuBar(QMenuBar *mb, bool forceNonNative = true); |
160 | TestMenu initWindowWithSimpleMenuBar(QMainWindow &w, bool forceNonNative = true); |
161 | QAction *createCharacterAction(QMenu *, char lowerAscii); |
162 | QMenu *addNumberedMenu(QMenuBar *mb, int n); |
163 | TestMenu initComplexMenuBar(QMenuBar *mb); |
164 | TestMenu initWindowWithComplexMenuBar(QMainWindow &w); |
165 | |
166 | QAction* ; |
167 | int ; |
168 | int [int('k')]; |
169 | QMenuBar* ; |
170 | }; |
171 | |
172 | // Testing get/set functions |
173 | void tst_QMenuBar::() |
174 | { |
175 | QMenuBar obj1; |
176 | // QAction * QMenuBar::activeAction() |
177 | // void QMenuBar::setActiveAction(QAction *) |
178 | QAction *var1 = new QAction(0); |
179 | obj1.setActiveAction(var1); |
180 | QCOMPARE(var1, obj1.activeAction()); |
181 | obj1.setActiveAction((QAction *)0); |
182 | QCOMPARE((QAction *)0, obj1.activeAction()); |
183 | delete var1; |
184 | } |
185 | |
186 | #include <qcursor.h> |
187 | |
188 | /*! |
189 | Test plan: |
190 | insertItem (all flavors and combinations) |
191 | removing menu items |
192 | clearing the menu |
193 | |
194 | check the common behaviour + emitted signals for: |
195 | accelerator keys |
196 | navigating tru the menu and then pressing ENTER |
197 | mouse clicks |
198 | mouse drags |
199 | combinations of key + mouse (if possible) |
200 | checked / unckecked state of menu options |
201 | active / inactive state |
202 | |
203 | Can't test these without pixmap comparison... |
204 | show and hide |
205 | icons in a menu |
206 | pixmaps in a menu |
207 | |
208 | */ |
209 | |
210 | tst_QMenuBar::() : m_lastSimpleAcceleratorId(0), m_simpleActivatedCount(0) |
211 | { |
212 | QApplication::setEffectEnabled(Qt::UI_AnimateMenu, enable: false); |
213 | } |
214 | |
215 | void tst_QMenuBar::( QAction* action ) |
216 | { |
217 | m_lastSimpleAcceleratorId = action; |
218 | m_simpleActivatedCount++; |
219 | } |
220 | |
221 | void tst_QMenuBar::() |
222 | { |
223 | QVERIFY(QApplication::topLevelWidgets().isEmpty()); |
224 | } |
225 | |
226 | // Create a simple menu bar and connect its actions to onSimpleActivated(). |
227 | |
228 | TestMenu tst_QMenuBar::(QMenuBar *mb, bool forceNonNative) { |
229 | TestMenu result; |
230 | if (forceNonNative) |
231 | mb->setNativeMenuBar(false); |
232 | connect(sender: mb, SIGNAL(triggered(QAction*)), receiver: this, SLOT(onSimpleActivated(QAction*))); |
233 | QMenu * = mb->addMenu(QStringLiteral("&accel" )); |
234 | QAction *action = menu->addAction(QStringLiteral("menu1" ) ); |
235 | action->setShortcut(QKeySequence(Qt::ALT + Qt::Key_A)); |
236 | action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_A)); |
237 | connect(sender: menu, SIGNAL(triggered(QAction*)), receiver: this, SLOT(onSimpleActivated(QAction*))); |
238 | result.menus << menu; |
239 | result.actions << action; |
240 | |
241 | menu = mb->addMenu(QStringLiteral("accel1" )); |
242 | action = menu->addAction(QStringLiteral("&Open..." ) ); |
243 | action->setShortcut(Qt::Key_O); |
244 | result.actions << action; |
245 | |
246 | action = menu->addAction(QStringLiteral("action" )); |
247 | action->setShortcut(QKeySequence(Qt::ALT + Qt::Key_Z)); |
248 | result.actions << action; |
249 | |
250 | result.menus << menu; |
251 | connect(sender: menu, SIGNAL(triggered(QAction*)), receiver: this, SLOT(onSimpleActivated(QAction*))); |
252 | |
253 | m_lastSimpleAcceleratorId = 0; |
254 | m_simpleActivatedCount = 0; |
255 | |
256 | return result; |
257 | } |
258 | |
259 | inline TestMenu tst_QMenuBar::initWindowWithSimpleMenuBar(QMainWindow &w, bool forceNonNative) |
260 | { |
261 | w.resize(w: 200, h: 200); |
262 | centerOnScreen(w: &w); |
263 | return initSimpleMenuBar(mb: w.menuBar(), forceNonNative); |
264 | } |
265 | |
266 | // add a menu with number n, set number as data. |
267 | QMenu *tst_QMenuBar::(QMenuBar *mb, int n) |
268 | { |
269 | const QString text = QStringLiteral("Menu &" ) + QString::number(n); |
270 | QMenu * = mb->addMenu(title: text); |
271 | menu->setObjectName(text); |
272 | QAction *action = menu->menuAction(); |
273 | action->setObjectName(text + QStringLiteral("Action" )); |
274 | action->setData(QVariant(n)); |
275 | connect(sender: action, SIGNAL(triggered()), receiver: this, SLOT(onComplexActionTriggered())); |
276 | return menu; |
277 | } |
278 | |
279 | // Create an action triggering on Ctrl+character, set number as data. |
280 | QAction *tst_QMenuBar::(QMenu *, char lowerAscii) |
281 | { |
282 | const QString text = QStringLiteral("Item " ) + QChar(QLatin1Char(lowerAscii)).toUpper(); |
283 | QAction *action = menu->addAction(text); |
284 | action->setObjectName(text); |
285 | action->setData(QVariant(int(lowerAscii))); |
286 | action->setShortcut(Qt::CTRL + (lowerAscii - 'a' + Qt::Key_A)); |
287 | connect(sender: action, SIGNAL(triggered()), receiver: this, SLOT(onComplexActionTriggered())); |
288 | return action; |
289 | } |
290 | |
291 | void tst_QMenuBar::() |
292 | { |
293 | if (QAction *a = qobject_cast<QAction *>(object: sender())) |
294 | m_complexTriggerCount[a->data().toInt()]++; |
295 | } |
296 | |
297 | // Create a complex menu bar and connect its actions to onComplexActionTriggered() |
298 | // for their invocations to be counted in m_complexTriggerCount. The index is the |
299 | // menu number (1..n) for the menu bar actions and the ASCII-code of the shortcut |
300 | // character for the actions in the menus. |
301 | TestMenu tst_QMenuBar::(QMenuBar *mb) |
302 | { |
303 | TestMenu result; |
304 | mb->setNativeMenuBar(false); |
305 | QMenu * = addNumberedMenu(mb, n: 1); |
306 | result.menus << menu; |
307 | for (char c = 'a'; c < 'c'; ++c) |
308 | result.actions << createCharacterAction(menu, lowerAscii: c); |
309 | |
310 | menu = addNumberedMenu(mb, n: 2); |
311 | menu->menuAction()->setData(QVariant(2)); |
312 | result.menus << menu; |
313 | for (char c = 'c'; c < 'g'; ++c) |
314 | result.actions << createCharacterAction(menu, lowerAscii: c); |
315 | menu->addSeparator(); |
316 | for (char c = 'g'; c < 'i'; ++c) |
317 | result.actions << createCharacterAction(menu, lowerAscii: c); |
318 | |
319 | QAction *action = mb->addAction(QStringLiteral("M&enu 3" )); |
320 | action->setData(QVariant(3)); |
321 | action->setShortcut(Qt::ALT + Qt::Key_J); |
322 | connect(sender: action, SIGNAL(triggered()), receiver: this, SLOT(onComplexActionTriggered())); |
323 | result.actions << action; |
324 | |
325 | std::fill(m_complexTriggerCount, m_complexTriggerCount + sizeof(m_complexTriggerCount) / sizeof(int), 0); |
326 | |
327 | return result; |
328 | } |
329 | |
330 | inline TestMenu tst_QMenuBar::initWindowWithComplexMenuBar(QMainWindow &w) |
331 | { |
332 | w.resize(w: 400, h: 200); |
333 | centerOnScreen(w: &w); |
334 | return initComplexMenuBar(mb: w.menuBar()); |
335 | } |
336 | |
337 | // On Mac native key events are needed to test menu action activation |
338 | #if !defined(Q_OS_DARWIN) |
339 | void tst_QMenuBar::() |
340 | { |
341 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
342 | QSKIP("Wayland: This fails. Figure out why." ); |
343 | |
344 | // create a popup menu with menu items set the accelerators later... |
345 | QMainWindow w; |
346 | const TestMenu = initWindowWithSimpleMenuBar(w); |
347 | w.show(); |
348 | QApplication::setActiveWindow(&w); |
349 | QVERIFY(QTest::qWaitForWindowActive(&w)); |
350 | // shortcuts won't work unless the window is active |
351 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_A, modifier: Qt::ControlModifier ); |
352 | QTest::qWait(ms: 300); |
353 | |
354 | QCOMPARE( m_lastSimpleAcceleratorId, menu.actions.front() ); |
355 | } |
356 | #endif |
357 | |
358 | // On Mac native key events are needed to test menu action activation |
359 | #if !defined(Q_OS_DARWIN) |
360 | void tst_QMenuBar::() |
361 | { |
362 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
363 | QSKIP("Wayland: This fails. Figure out why." ); |
364 | |
365 | // create a popup menu with menu items set the accelerators later... |
366 | QMainWindow w; |
367 | QFETCH( bool, forceNonNative ); |
368 | initWindowWithSimpleMenuBar(w, forceNonNative); |
369 | w.show(); |
370 | QApplication::setActiveWindow(&w); |
371 | QVERIFY(QTest::qWaitForWindowActive(&w)); |
372 | |
373 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_A, modifier: Qt::ControlModifier ); |
374 | //wait(5000); |
375 | QCOMPARE( m_simpleActivatedCount, 2 ); //1 from the popupmenu and 1 from the menubar |
376 | } |
377 | |
378 | void tst_QMenuBar::() |
379 | { |
380 | QTest::addColumn<bool>(name: "forceNonNative" ); |
381 | QTest::newRow( dataTag: "forcing non-native menubar" ) << true; |
382 | QTest::newRow( dataTag: "not forcing non-native menubar" ) << false; |
383 | } |
384 | #endif |
385 | |
386 | void tst_QMenuBar::() |
387 | { |
388 | QMenuBar ; |
389 | initSimpleMenuBar(mb: &menuBar); |
390 | menuBar.clear(); |
391 | QCOMPARE( menuBar.actions().size(), 0 ); |
392 | |
393 | menuBar.clear(); |
394 | for (int i = 0; i < 10; i++) { |
395 | QMenu * = menuBar.addMenu( QStringLiteral("Menu " ) + QString::number(i)); |
396 | for (int k = 0; k < i; k++) |
397 | menu->addAction( QStringLiteral("Item " ) + QString::number(k)); |
398 | QCOMPARE( menuBar.actions().size(), i + 1 ); |
399 | } |
400 | QCOMPARE( menuBar.actions().size(), 10 ); |
401 | menuBar.clear(); |
402 | QCOMPARE( menuBar.actions().size(), 0 ); |
403 | } |
404 | |
405 | void tst_QMenuBar::() |
406 | { |
407 | QMenuBar ; |
408 | QCOMPARE( menuBar.actions().size(), 0 ); |
409 | |
410 | for (int i = 0; i < 10; i++) { |
411 | menuBar.addAction( QStringLiteral("Menu " ) + QString::number(i)); |
412 | QCOMPARE( menuBar.actions().size(), i + 1 ); |
413 | } |
414 | } |
415 | |
416 | void tst_QMenuBar::() |
417 | { |
418 | QTest::addColumn<int>(name: "removeIndex" ); |
419 | QTest::newRow( dataTag: "first" ) << 0; |
420 | QTest::newRow( dataTag: "middle" ) << 1; |
421 | QTest::newRow( dataTag: "last" ) << 2; |
422 | } |
423 | |
424 | // Basically the same test as removeItemAt, except that we remember and remove id's. |
425 | void tst_QMenuBar::() |
426 | { |
427 | QMenuBar ; |
428 | |
429 | QMenu *pm = new QMenu( "stuff" , &menuBar ); |
430 | pm->setTitle("Menu 1" ); |
431 | pm->addAction( text: QString("Item 10" ) ); |
432 | QAction* action1 = menuBar.addMenu( menu: pm ); |
433 | |
434 | pm = new QMenu( &menuBar ); |
435 | pm->setTitle("Menu 2" ); |
436 | pm->addAction( text: QString("Item 20" ) ); |
437 | pm->addAction( text: QString("Item 21" ) ); |
438 | QAction *action2 = menuBar.addMenu( menu: pm ); |
439 | |
440 | pm = new QMenu( "Menu 3" , &menuBar ); |
441 | pm->addAction( text: QString("Item 30" ) ); |
442 | pm->addAction( text: QString("Item 31" ) ); |
443 | pm->addAction( text: QString("Item 32" ) ); |
444 | QAction *action3 = menuBar.addMenu( menu: pm ); |
445 | |
446 | const QList<QAction *> = menuBar.actions(); |
447 | |
448 | QCOMPARE( action1->text(), QString("Menu 1" ) ); |
449 | QCOMPARE( action2->text(), QString("Menu 2" ) ); |
450 | QCOMPARE( action3->text(), QString("Menu 3" ) ); |
451 | |
452 | QVERIFY( menuBarActions.at(0) == action1 ); |
453 | QVERIFY( menuBarActions.at(1) == action2 ); |
454 | QVERIFY( menuBarActions.at(2) == action3 ); |
455 | |
456 | // Ok, now that we know we have created the menu we expect, lets remove an item... |
457 | QFETCH( int, removeIndex ); |
458 | switch (removeIndex ) |
459 | { |
460 | case 0: { |
461 | menuBar.removeAction(action: action1); |
462 | const QList<QAction *> = menuBar.actions(); |
463 | QCOMPARE( menuBarActions2.at(0)->text(), QString("Menu 2" ) ); |
464 | QCOMPARE( menuBarActions2.at(1)->text(), QString("Menu 3" ) ); |
465 | } |
466 | break; |
467 | case 1: { |
468 | menuBar.removeAction(action: action2); |
469 | const QList<QAction *> = menuBar.actions(); |
470 | QCOMPARE( menuBarActions2.at(0)->text(), QString("Menu 1" ) ); |
471 | QCOMPARE( menuBarActions2.at(1)->text(), QString("Menu 3" ) ); |
472 | } |
473 | break; |
474 | case 2: { |
475 | menuBar.removeAction(action: action3); |
476 | const QList<QAction *> = menuBar.actions(); |
477 | QCOMPARE( menuBarActions2.at(0)->text(), QString("Menu 1" ) ); |
478 | QCOMPARE( menuBarActions2.at(1)->text(), QString("Menu 2" ) ); |
479 | } |
480 | break; |
481 | } |
482 | QList<QAction *> = menuBar.actions(); |
483 | QVERIFY( menuBarActions2.size() == 2 ); |
484 | } |
485 | |
486 | void tst_QMenuBar::() |
487 | { |
488 | QTest::addColumn<int>(name: "removeIndex" ); |
489 | QTest::newRow( dataTag: "first" ) << 0; |
490 | QTest::newRow( dataTag: "middle" ) << 1; |
491 | QTest::newRow( dataTag: "last" ) << 2; |
492 | } |
493 | |
494 | void tst_QMenuBar::() |
495 | { |
496 | QMenuBar ; |
497 | QMenu *pm = new QMenu("Menu 1" , &menuBar); |
498 | pm->addAction( text: QString("Item 10" ) ); |
499 | menuBar.addMenu( menu: pm ); |
500 | |
501 | pm = new QMenu( "Menu 2" , &menuBar); |
502 | pm->addAction( text: QString("Item 20" ) ); |
503 | pm->addAction( text: QString("Item 21" ) ); |
504 | menuBar.addMenu( menu: pm ); |
505 | |
506 | pm = new QMenu( "Menu 3" , &menuBar); |
507 | pm->addAction( text: QString("Item 30" ) ); |
508 | pm->addAction( text: QString("Item 31" ) ); |
509 | pm->addAction( text: QString("Item 32" ) ); |
510 | menuBar.addMenu( menu: pm ); |
511 | |
512 | QList<QAction *> = menuBar.actions(); |
513 | |
514 | QCOMPARE( menuBarActions.at(0)->text(), QString("Menu 1" ) ); |
515 | QCOMPARE( menuBarActions.at(1)->text(), QString("Menu 2" ) ); |
516 | QCOMPARE( menuBarActions.at(2)->text(), QString("Menu 3" ) ); |
517 | |
518 | // Ok, now that we know we have created the menu we expect, lets remove an item... |
519 | QFETCH( int, removeIndex ); |
520 | menuBar.removeAction(action: menuBarActions.at(i: removeIndex)); |
521 | const QList<QAction *> = menuBar.actions(); |
522 | switch (removeIndex ) |
523 | { |
524 | case 0: |
525 | QCOMPARE( menuBarActions2.at(0)->text(), QString("Menu 2" ) ); |
526 | QCOMPARE( menuBarActions2.at(1)->text(), QString("Menu 3" ) ); |
527 | break; |
528 | case 1: |
529 | QCOMPARE( menuBarActions2.at(0)->text(), QString("Menu 1" ) ); |
530 | QCOMPARE( menuBarActions2.at(1)->text(), QString("Menu 3" ) ); |
531 | break; |
532 | case 2: |
533 | QCOMPARE( menuBarActions2.at(0)->text(), QString("Menu 1" ) ); |
534 | QCOMPARE( menuBarActions2.at(1)->text(), QString("Menu 2" ) ); |
535 | break; |
536 | } |
537 | |
538 | QVERIFY( menuBarActions2.size() == 2 ); |
539 | } |
540 | |
541 | /* |
542 | Check the insert functions that create menu items. |
543 | For the moment i only check the strings and pixmaps. The rest are special cases which are |
544 | used less frequently. |
545 | */ |
546 | |
547 | void tst_QMenuBar::() |
548 | { |
549 | QMenuBar ; |
550 | initComplexMenuBar(mb: &menuBar); |
551 | |
552 | const QList<QAction *> actions = menuBar.actions(); |
553 | |
554 | QCOMPARE(actions.at(0)->text(), QString("Menu &1" ) ); |
555 | QCOMPARE(actions.at(1)->text(), QString("Menu &2" ) ); |
556 | QCOMPARE(actions.at(2)->text(), QString("M&enu 3" ) ); |
557 | QVERIFY(actions.size() < 4); // there is no menu 4! |
558 | } |
559 | |
560 | // On Mac native key events are needed to test menu action activation |
561 | #if !defined(Q_OS_DARWIN) |
562 | void tst_QMenuBar::() |
563 | { |
564 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
565 | QSKIP("Wayland: This fails. Figure out why." ); |
566 | |
567 | QMainWindow w; |
568 | initWindowWithComplexMenuBar(w); |
569 | w.show(); |
570 | QApplication::setActiveWindow(&w); |
571 | QVERIFY(QTest::qWaitForWindowActive(&w)); |
572 | |
573 | // start with a bogus key that shouldn't trigger anything |
574 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_I, modifier: Qt::ControlModifier); |
575 | QCOMPARE(m_complexTriggerCount[1], 0); |
576 | QCOMPARE(m_complexTriggerCount[2], 0); |
577 | QCOMPARE(m_complexTriggerCount[3], 0); |
578 | QCOMPARE(m_complexTriggerCount[4], 0); |
579 | QCOMPARE(m_complexTriggerCount[int('a')], 0); |
580 | QCOMPARE(m_complexTriggerCount[int('b')], 0); |
581 | QCOMPARE(m_complexTriggerCount[int('c')], 0); |
582 | QCOMPARE(m_complexTriggerCount[int('d')], 0); |
583 | |
584 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_A, modifier: Qt::ControlModifier); |
585 | QCOMPARE(m_complexTriggerCount[1], 0); |
586 | QCOMPARE(m_complexTriggerCount[2], 0); |
587 | QCOMPARE(m_complexTriggerCount[3], 0); |
588 | QCOMPARE(m_complexTriggerCount[4], 0); |
589 | QCOMPARE(m_complexTriggerCount[int('a')], 1); |
590 | QCOMPARE(m_complexTriggerCount[int('b')], 0); |
591 | QCOMPARE(m_complexTriggerCount[int('c')], 0); |
592 | QCOMPARE(m_complexTriggerCount[int('d')], 0); |
593 | |
594 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_C, modifier: Qt::ControlModifier); |
595 | QCOMPARE(m_complexTriggerCount[1], 0); |
596 | QCOMPARE(m_complexTriggerCount[2], 0); |
597 | QCOMPARE(m_complexTriggerCount[3], 0); |
598 | QCOMPARE(m_complexTriggerCount[4], 0); |
599 | QCOMPARE(m_complexTriggerCount[int('a')], 1); |
600 | QCOMPARE(m_complexTriggerCount[int('b')], 0); |
601 | QCOMPARE(m_complexTriggerCount[int('c')], 1); |
602 | QCOMPARE(m_complexTriggerCount[int('d')], 0); |
603 | |
604 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_B, modifier: Qt::ControlModifier); |
605 | QCOMPARE(m_complexTriggerCount[1], 0); |
606 | QCOMPARE(m_complexTriggerCount[2], 0); |
607 | QCOMPARE(m_complexTriggerCount[3], 0); |
608 | QCOMPARE(m_complexTriggerCount[4], 0); |
609 | QCOMPARE(m_complexTriggerCount[int('a')], 1); |
610 | QCOMPARE(m_complexTriggerCount[int('b')], 1); |
611 | QCOMPARE(m_complexTriggerCount[int('c')], 1); |
612 | QCOMPARE(m_complexTriggerCount[int('d')], 0); |
613 | |
614 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_D, modifier: Qt::ControlModifier); |
615 | QCOMPARE(m_complexTriggerCount[1], 0); |
616 | QCOMPARE(m_complexTriggerCount[2], 0); |
617 | QCOMPARE(m_complexTriggerCount[3], 0); |
618 | QCOMPARE(m_complexTriggerCount[4], 0); |
619 | QCOMPARE(m_complexTriggerCount[int('a')], 1); |
620 | QCOMPARE(m_complexTriggerCount[int('b')], 1); |
621 | QCOMPARE(m_complexTriggerCount[int('c')], 1); |
622 | QCOMPARE(m_complexTriggerCount[int('d')], 1); |
623 | |
624 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_J, modifier: Qt::AltModifier); |
625 | QCOMPARE(m_complexTriggerCount[1], 0); |
626 | QCOMPARE(m_complexTriggerCount[2], 0); |
627 | QCOMPARE(m_complexTriggerCount[3], 1); |
628 | QCOMPARE(m_complexTriggerCount[4], 0); |
629 | QCOMPARE(m_complexTriggerCount[int('a')], 1); |
630 | QCOMPARE(m_complexTriggerCount[int('b')], 1); |
631 | QCOMPARE(m_complexTriggerCount[int('c')], 1); |
632 | QCOMPARE(m_complexTriggerCount[int('d')], 1); |
633 | } |
634 | #endif |
635 | |
636 | // On Mac native key events are needed to test menu action activation |
637 | #if !defined(Q_OS_DARWIN) |
638 | void tst_QMenuBar::() |
639 | { |
640 | if (qgetenv(varName: "XDG_CURRENT_DESKTOP" ) == "Unity" ) |
641 | QSKIP("This test is flaky on Ubuntu/Unity due to regression introduced by QTBUG-39362" ); |
642 | |
643 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
644 | QSKIP("Wayland: This fails. Figure out why." ); |
645 | |
646 | QMainWindow w; |
647 | initWindowWithComplexMenuBar(w); |
648 | w.show(); |
649 | QApplication::setActiveWindow(&w); |
650 | QVERIFY(QTest::qWaitForWindowActive(&w)); |
651 | |
652 | // start with a ALT + 1 that activates the first popupmenu |
653 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_1, modifier: Qt::AltModifier ); |
654 | // the Popupmenu should be visible now |
655 | QCOMPARE(m_complexTriggerCount[3], 0); |
656 | QCOMPARE(m_complexTriggerCount[4], 0); |
657 | QCOMPARE(m_complexTriggerCount[int('a')], 0); |
658 | QCOMPARE(m_complexTriggerCount[int('b')], 0); |
659 | QCOMPARE(m_complexTriggerCount[int('c')], 0); |
660 | QCOMPARE(m_complexTriggerCount[int('d')], 0); |
661 | |
662 | // Simulate a cursor key down click |
663 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Down ); |
664 | // and an Enter key |
665 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Enter ); |
666 | // Let's see if the correct slot is called... |
667 | QCOMPARE(m_complexTriggerCount[3], 0); |
668 | QCOMPARE(m_complexTriggerCount[4], 0); |
669 | QCOMPARE(m_complexTriggerCount[int('a')], 0); // this shouldn't have been called |
670 | QCOMPARE(m_complexTriggerCount[int('b')], 1); // and this should have been called by a signal now |
671 | QCOMPARE(m_complexTriggerCount[int('c')], 0); |
672 | QCOMPARE(m_complexTriggerCount[int('d')], 0); |
673 | } |
674 | #endif |
675 | |
676 | // Qt/Mac does not use the native popups/menubar |
677 | #if !defined(Q_OS_DARWIN) |
678 | void tst_QMenuBar::() |
679 | { |
680 | if (qgetenv(varName: "XDG_CURRENT_DESKTOP" ) == "Unity" ) |
681 | QSKIP("This test is flaky on Ubuntu/Unity due to regression introduced by QTBUG-39362" ); |
682 | |
683 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
684 | QSKIP("Wayland: This fails. Figure out why." ); |
685 | |
686 | QMainWindow w; |
687 | initWindowWithComplexMenuBar(w); |
688 | w.show(); |
689 | QApplication::setActiveWindow(&w); |
690 | QVERIFY(QTest::qWaitForWindowActive(&w)); |
691 | |
692 | // select popupmenu2 |
693 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_2, modifier: Qt::AltModifier ); |
694 | |
695 | // Simulate some cursor keys |
696 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Left ); |
697 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Down ); |
698 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Right ); |
699 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Down ); |
700 | // and an Enter key |
701 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Enter ); |
702 | // Let's see if the correct slot is called... |
703 | QCOMPARE(m_complexTriggerCount[3], 0); |
704 | QCOMPARE(m_complexTriggerCount[4], 0); |
705 | QCOMPARE(m_complexTriggerCount[int('a')], 0); // this shouldn't have been caled |
706 | QCOMPARE(m_complexTriggerCount[int('b')], 0); // and this should have been called by a signal ow |
707 | QCOMPARE(m_complexTriggerCount[int('c')], 0); |
708 | QCOMPARE(m_complexTriggerCount[int('d')], 1); |
709 | } |
710 | #endif |
711 | |
712 | /*! |
713 | If a popupmenu is active you can use Left to move to the menu to the left of it. |
714 | */ |
715 | // Qt/Mac does not use the native popups/menubar |
716 | #if !defined(Q_OS_DARWIN) |
717 | void tst_QMenuBar::() |
718 | { |
719 | if (qgetenv(varName: "XDG_CURRENT_DESKTOP" ) == "Unity" ) |
720 | QSKIP("This test is flaky on Ubuntu/Unity due to regression introduced by QTBUG-39362" ); |
721 | |
722 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
723 | QSKIP("Wayland: This fails. Figure out why." ); |
724 | |
725 | QMainWindow w; |
726 | initWindowWithComplexMenuBar(w); |
727 | w.show(); |
728 | QApplication::setActiveWindow(&w); |
729 | QVERIFY(QTest::qWaitForWindowActive(&w)); |
730 | |
731 | // select Popupmenu 2 |
732 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_2, modifier: Qt::AltModifier ); |
733 | |
734 | // Simulate some keys |
735 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Left ); |
736 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Down ); |
737 | // and press ENTER |
738 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Enter ); |
739 | // Let's see if the correct slot is called... |
740 | QCOMPARE(m_complexTriggerCount[3], 0); |
741 | QCOMPARE(m_complexTriggerCount[4], 0); |
742 | QCOMPARE(m_complexTriggerCount[int('a')], 0); // this shouldn't have been called |
743 | QCOMPARE(m_complexTriggerCount[int('b')], 1); // and this should have been called by a signal now |
744 | QCOMPARE(m_complexTriggerCount[int('c')], 0); |
745 | QCOMPARE(m_complexTriggerCount[int('d')], 0); |
746 | } |
747 | #endif |
748 | |
749 | void tst_QMenuBar::() |
750 | { |
751 | #if defined(Q_OS_DARWIN) |
752 | QSKIP("Native key events are needed to test menu action activation on macOS." ); |
753 | #endif |
754 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
755 | QSKIP("Wayland: This fails. Figure out why." ); |
756 | |
757 | QMainWindow w; |
758 | QMenuBar *mb = w.menuBar(); |
759 | mb->setNativeMenuBar(false); |
760 | |
761 | QMenu *em = mb->addMenu(title: "&Edit" ); |
762 | em->setObjectName("EditMenu" ); |
763 | em->addAction(text: "&Cut" ); |
764 | em->addAction(text: "C&opy" ); |
765 | QPlainTextEdit *e = new QPlainTextEdit; |
766 | e->setObjectName("edit" ); |
767 | |
768 | w.setCentralWidget(e); |
769 | w.show(); |
770 | QApplication::setActiveWindow(&w); |
771 | QVERIFY(QTest::qWaitForWindowActive(&w)); |
772 | |
773 | QTRY_COMPARE(QApplication::focusWidget(), e); |
774 | |
775 | // Open menu |
776 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_E, modifier: Qt::AltModifier ); |
777 | QTRY_COMPARE(QApplication::activePopupWidget(), em); |
778 | // key down to trigger focus |
779 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Down ); |
780 | // and press ENTER to close |
781 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Enter ); |
782 | QTRY_COMPARE(QApplication::activePopupWidget(), nullptr); |
783 | // focus should have returned to the editor by now |
784 | QTRY_COMPARE(QApplication::focusWidget(), e); |
785 | |
786 | // Now do it all over again... |
787 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_E, modifier: Qt::AltModifier ); |
788 | QTRY_COMPARE(QApplication::activePopupWidget(), em); |
789 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Down ); |
790 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Enter ); |
791 | QTRY_COMPARE(QApplication::activePopupWidget(), nullptr); |
792 | QTRY_COMPARE(QApplication::focusWidget(), e); |
793 | |
794 | } |
795 | |
796 | /*! |
797 | If a popupmenu is active you can use home to go quickly to the first item in the menu. |
798 | */ |
799 | void tst_QMenuBar::() |
800 | { |
801 | // I'm temporarily shutting up this testcase. |
802 | // Seems like the behaviour i'm expecting isn't ok. |
803 | QSKIP("This test has been \"temporarily\" disabled at least since 2009 :)" ); |
804 | |
805 | QEXPECT_FAIL( "0" , "Popupmenu should respond to a Home key" , Abort ); |
806 | |
807 | QMainWindow w; |
808 | initWindowWithComplexMenuBar(w); |
809 | w.show(); |
810 | QApplication::setActiveWindow(&w); |
811 | QVERIFY(QTest::qWaitForWindowActive(&w)); |
812 | |
813 | // select Popupmenu 2 |
814 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_2, modifier: Qt::AltModifier ); |
815 | |
816 | // Simulate some keys |
817 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Down ); |
818 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Down ); |
819 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Down ); |
820 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Home ); |
821 | // and press ENTER |
822 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Enter ); |
823 | // Let's see if the correct slot is called... |
824 | // QVERIFY2( m_complexActionTriggerCount[int('c')] == 1, "Popupmenu should respond to a Home key" ); |
825 | QCOMPARE(m_complexTriggerCount[int('c')], 1); |
826 | QCOMPARE(m_complexTriggerCount[3], 0); |
827 | QCOMPARE(m_complexTriggerCount[4], 0); |
828 | QCOMPARE(m_complexTriggerCount[int('a')], 0); |
829 | QCOMPARE(m_complexTriggerCount[int('b')], 0); |
830 | QCOMPARE(m_complexTriggerCount[int('d')], 0); |
831 | QCOMPARE(m_complexTriggerCount[int('e')], 0); |
832 | QCOMPARE(m_complexTriggerCount[int('f')], 0); |
833 | QCOMPARE(m_complexTriggerCount[int('g')], 0); |
834 | QCOMPARE(m_complexTriggerCount[int('h')], 0); |
835 | } |
836 | |
837 | /*! |
838 | If a popupmenu is active you can use end to go quickly to the last item in the menu. |
839 | */ |
840 | void tst_QMenuBar::() |
841 | { |
842 | // I'm temporarily silenting this testcase. |
843 | // Seems like the behaviour i'm expecting isn't ok. |
844 | QSKIP("This test has been \"temporarily\" disabled at least since 2009 :)" ); |
845 | |
846 | QEXPECT_FAIL( "0" , "Popupmenu should respond to an End key" , Abort ); |
847 | |
848 | QMainWindow w; |
849 | initWindowWithComplexMenuBar(w); |
850 | w.show(); |
851 | QApplication::setActiveWindow(&w); |
852 | QVERIFY(QTest::qWaitForWindowActive(&w)); |
853 | |
854 | // select Popupmenu 2 |
855 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_2, modifier: Qt::AltModifier ); |
856 | |
857 | // Simulate some keys |
858 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_End ); |
859 | // and press ENTER |
860 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Enter ); |
861 | // Let's see if the correct slot is called... |
862 | // QVERIFY2( m_complexActionTriggerCount[int('h')] == 1, "Popupmenu should respond to an End key" ); |
863 | QCOMPARE(m_complexTriggerCount[int('h')], 1);//, "Popupmenu should respond to an End key"); |
864 | QCOMPARE(m_complexTriggerCount[3], 0); |
865 | QCOMPARE(m_complexTriggerCount[4], 0); |
866 | QCOMPARE(m_complexTriggerCount[int('a')], 0); |
867 | QCOMPARE(m_complexTriggerCount[int('b')], 0); |
868 | QCOMPARE(m_complexTriggerCount[int('c')], 0); |
869 | QCOMPARE(m_complexTriggerCount[int('d')], 0); |
870 | QCOMPARE(m_complexTriggerCount[int('e')], 0); |
871 | QCOMPARE(m_complexTriggerCount[int('f')], 0); |
872 | QCOMPARE(m_complexTriggerCount[int('g')], 0); |
873 | } |
874 | |
875 | /*! |
876 | If a popupmenu is active you can use esc to hide the menu and then the |
877 | menubar should become active. |
878 | If Down is pressed next the popup is activated again. |
879 | */ |
880 | |
881 | // Qt/Mac does not use the native popups/menubar |
882 | #if !defined(Q_OS_DARWIN) |
883 | void tst_QMenuBar::() |
884 | { |
885 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
886 | QSKIP("Wayland: This fails. Figure out why." ); |
887 | |
888 | QMainWindow w; |
889 | const TestMenu = initWindowWithComplexMenuBar(w); |
890 | w.show(); |
891 | w.setFocus(); |
892 | QApplication::setActiveWindow(&w); |
893 | QVERIFY(QTest::qWaitForWindowActive(&w)); |
894 | |
895 | QVERIFY( !menu.menus.at(0)->isActiveWindow() ); |
896 | QVERIFY( !menu.menus.at(1)->isActiveWindow() ); |
897 | |
898 | // select Popupmenu 2 |
899 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_2, modifier: Qt::AltModifier ); |
900 | QVERIFY( !menu.menus.at(0)->isActiveWindow() ); |
901 | QVERIFY( menu.menus.at(1)->isActiveWindow() ); |
902 | |
903 | // If we press ESC, the popup should disappear |
904 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Escape ); |
905 | QVERIFY( !menu.menus.at(0)->isActiveWindow() ); |
906 | QVERIFY( !menu.menus.at(1)->isActiveWindow() ); |
907 | |
908 | if (!QApplication::style()->inherits(classname: "QWindowsStyle" )) |
909 | return; |
910 | |
911 | if (!QGuiApplication::platformName().compare(other: QLatin1String("minimal" ), cs: Qt::CaseInsensitive) |
912 | || !QGuiApplication::platformName().compare(other: QLatin1String("offscreen" ), cs: Qt::CaseInsensitive)) { |
913 | QWARN("Skipping menu button test on minimal/offscreen platforms" ); |
914 | return; |
915 | } |
916 | |
917 | // If we press Down the popupmenu should be active again |
918 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Down ); |
919 | QVERIFY( !menu.menus.at(0)->isActiveWindow() ); |
920 | QVERIFY( menu.menus.at(1)->isActiveWindow() ); |
921 | |
922 | // and press ENTER |
923 | QTest::keyClick( widget: menu.menus.at(i: 1), key: Qt::Key_Enter ); |
924 | // Let's see if the correct slot is called... |
925 | QVERIFY2(m_complexTriggerCount[int('c')] == 1, "Expected item 2C to be selected" ); |
926 | } |
927 | #endif |
928 | |
929 | |
930 | // void tst_QMenuBar::check_mouse1_data() |
931 | // { |
932 | // QTest::addColumn<QString>("popup_item"); |
933 | // QTest::addColumn<int>("itemA_count"); |
934 | // QTest::addColumn<int>("itemB_count"); |
935 | |
936 | // QTest::newRow( "A" ) << QString( "Item A Ctrl+A" ) << 1 << 0; |
937 | // QTest::newRow( "B" ) << QString( "Item B Ctrl+B" ) << 0 << 1; |
938 | // } |
939 | |
940 | // /*! |
941 | // Check if the correct signals are emitted if we select a popupmenu. |
942 | // */ |
943 | // void tst_QMenuBar::check_mouse1() |
944 | // { |
945 | // if (QSystem::curStyle() == "Motif") |
946 | // QSKIP("This fails in Motif due to a bug in the testing framework"); |
947 | // QFETCH( QString, popup_item ); |
948 | // QFETCH( int, itemA_count ); |
949 | // QFETCH( int, itemB_count ); |
950 | |
951 | // // initComplexMenubar(); |
952 | // QVERIFY( !pm1->isActiveWindow() ); |
953 | // QVERIFY( !pm2->isActiveWindow() ); |
954 | |
955 | // QTest::qWait(1000); |
956 | // QtTestMouse mouse; |
957 | // mouse.mouseEvent( QtTestMouse::MouseClick, mb, "Menu &1", Qt::LeftButton ); |
958 | |
959 | // QVERIFY( pm1->isActiveWindow() ); |
960 | // QVERIFY( !pm2->isActiveWindow() ); |
961 | |
962 | // QTest::qWait(1000); |
963 | // mouse.mouseEvent( QtTestMouse::MouseClick, pm1, popup_item, Qt::LeftButton ); |
964 | |
965 | // QCOMPARE(m_complexActionTriggerCount[3], 0); |
966 | // QCOMPARE(m_complexActionTriggerCount[4], 0); |
967 | // QCOMPARE(m_complexActionTriggerCount['a'], (uint)itemA_count); // this option should have fired |
968 | // QCOMPARE(m_complexActionTriggerCount['b'], (uint)itemB_count); |
969 | // QCOMPARE(m_complexActionTriggerCount['c'], 0); |
970 | // QCOMPARE(m_complexActionTriggerCount['d'], 0); |
971 | // QCOMPARE(m_complexActionTriggerCount['e'], 0); |
972 | // QCOMPARE(m_complexActionTriggerCount['f'], 0); |
973 | // QCOMPARE(m_complexActionTriggerCount['g'], 0); |
974 | // } |
975 | |
976 | // void tst_QMenuBar::check_mouse2_data() |
977 | // { |
978 | // QTest::addColumn<QString>("label"); |
979 | // QTest::addColumn<int>("itemA_count"); |
980 | // QTest::addColumn<int>("itemB_count"); |
981 | // QTest::addColumn<int>("itemC_count"); |
982 | // QTest::addColumn<int>("itemD_count"); |
983 | // QTest::addColumn<int>("itemE_count"); |
984 | // QTest::addColumn<int>("itemF_count"); |
985 | // QTest::addColumn<int>("itemG_count"); |
986 | // QTest::addColumn<int>("itemH_count"); |
987 | // QTest::addColumn<int>("menu3_count"); |
988 | |
989 | // QTest::newRow( "A" ) << QString( "Menu &1/Item A Ctrl+A" ) << 1 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0; |
990 | // QTest::newRow( "B" ) << QString( "Menu &1/Item B Ctrl+B" ) << 0 << 1 << 0 << 0 << 0 << 0 << 0 << 0 << 0; |
991 | // QTest::newRow( "C" ) << QString( "Menu &2/Item C Ctrl+C" ) << 0 << 0 << 1 << 0 << 0 << 0 << 0 << 0 << 0; |
992 | // QTest::newRow( "D" ) << QString( "Menu &2/Item D Ctrl+D" ) << 0 << 0 << 0 << 1 << 0 << 0 << 0 << 0 << 0; |
993 | // QTest::newRow( "E" ) << QString( "Menu &2/Item E Ctrl+E" ) << 0 << 0 << 0 << 0 << 1 << 0 << 0 << 0 << 0; |
994 | // QTest::newRow( "F" ) << QString( "Menu &2/Item F Ctrl+F" ) << 0 << 0 << 0 << 0 << 0 << 1 << 0 << 0 << 0; |
995 | // QTest::newRow( "G" ) << QString( "Menu &2/Item G Ctrl+G" ) << 0 << 0 << 0 << 0 << 0 << 0 << 1 << 0 << 0; |
996 | // QTest::newRow( "H" ) << QString( "Menu &2/Item H Ctrl+H" ) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 1 << 0; |
997 | // QTest::newRow( "menu 3" ) << QString( "M&enu 3" ) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 1; |
998 | // } |
999 | |
1000 | // /*! |
1001 | // Check if the correct signals are emitted if we select a popupmenu. |
1002 | // This time, we use a little bit more magic from the testframework. |
1003 | // */ |
1004 | // void tst_QMenuBar::check_mouse2() |
1005 | // { |
1006 | // if (QSystem::curStyle() == "Motif") |
1007 | // QSKIP("This fails in Motif due to a bug in the testing framework"); |
1008 | // QFETCH( QString, label ); |
1009 | // QFETCH( int, itemA_count ); |
1010 | // QFETCH( int, itemB_count ); |
1011 | // QFETCH( int, itemC_count ); |
1012 | // QFETCH( int, itemD_count ); |
1013 | // QFETCH( int, itemE_count ); |
1014 | // QFETCH( int, itemF_count ); |
1015 | // QFETCH( int, itemG_count ); |
1016 | // QFETCH( int, itemH_count ); |
1017 | // QFETCH( int, menu3_count ); |
1018 | |
1019 | // // initComplexMenubar(); |
1020 | // QtTestMouse mouse; |
1021 | // mouse.click( QtTestMouse::Menu, label, Qt::LeftButton ); |
1022 | |
1023 | // // check if the correct signals have fired |
1024 | // QCOMPARE(m_complexActionTriggerCount[3], (uint)menu3_count); |
1025 | // QCOMPARE(m_complexActionTriggerCount[4], 0); |
1026 | // QCOMPARE(m_complexActionTriggerCount['a'], (uint)itemA_count); |
1027 | // QCOMPARE(m_complexActionTriggerCount['b'], (uint)itemB_count); |
1028 | // QCOMPARE(m_complexActionTriggerCount['c'], (uint)itemC_count); |
1029 | // QCOMPARE(m_complexActionTriggerCount['d'], (uint)itemD_count); |
1030 | // QCOMPARE(m_complexActionTriggerCount['e'], (uint)itemE_count); |
1031 | // QCOMPARE(m_complexActionTriggerCount['f'], (uint)itemF_count); |
1032 | // QCOMPARE(m_complexActionTriggerCount['g'], (uint)itemG_count); |
1033 | // QCOMPARE(m_complexActionTriggerCount['h'], (uint)itemH_count); |
1034 | // } |
1035 | |
1036 | void tst_QMenuBar::allowActiveAndDisabled() |
1037 | { |
1038 | QMenuBar ; |
1039 | menuBar.setNativeMenuBar(false); |
1040 | |
1041 | // Task 241043 : check that second menu is activated if only |
1042 | // disabled menu items are added |
1043 | |
1044 | QMenu ("&File" ); |
1045 | // Task 241043 : check that second menu is activated |
1046 | // if all items are disabled |
1047 | QAction *act = fileMenu.addAction(text: "Disabled" ); |
1048 | act->setEnabled(false); |
1049 | |
1050 | menuBar.addMenu(menu: &fileMenu); |
1051 | QMenu ("Disabled" ); |
1052 | disabledMenu.setEnabled(false); |
1053 | QMenu ("Active" ); |
1054 | menuBar.addMenu(menu: &disabledMenu); |
1055 | menuBar.addMenu(menu: &activeMenu); |
1056 | centerOnScreen(w: &menuBar); |
1057 | menuBar.show(); |
1058 | QVERIFY(QTest::qWaitForWindowExposed(&menuBar)); |
1059 | |
1060 | // Here we verify that AllowActiveAndDisabled correctly skips |
1061 | // the disabled menu entry |
1062 | QTest::keyClick(widget: &menuBar, key: Qt::Key_F, modifier: Qt::AltModifier ); |
1063 | QTest::keyClick(widget: &fileMenu, key: (Qt::Key_Right)); |
1064 | if (qApp->style()->styleHint(stylehint: QStyle::SH_Menu_AllowActiveAndDisabled)) |
1065 | QCOMPARE(menuBar.activeAction()->text(), disabledMenu.title()); |
1066 | else |
1067 | QCOMPARE(menuBar.activeAction()->text(), activeMenu.title()); |
1068 | |
1069 | QTest::keyClick(widget: &menuBar, key: (Qt::Key_Left)); |
1070 | if (qApp->style()->styleHint(stylehint: QStyle::SH_Menu_AllowActiveAndDisabled)) |
1071 | QCOMPARE(menuBar.activeAction()->text(), fileMenu.title()); |
1072 | else |
1073 | QCOMPARE(menuBar.activeAction()->text(), fileMenu.title()); |
1074 | } |
1075 | |
1076 | void tst_QMenuBar::() |
1077 | { |
1078 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1079 | QSKIP("Wayland: This fails. Figure out why." ); |
1080 | |
1081 | if ( !qApp->style()->styleHint(stylehint: QStyle::SH_MenuBar_AltKeyNavigation) ) { |
1082 | QSKIP(QString( "this is not supposed to work in the %1 style. Skipping." ). |
1083 | arg(qApp->style()->objectName()).toLatin1()); |
1084 | } |
1085 | |
1086 | QMainWindow w; |
1087 | initWindowWithSimpleMenuBar(w); |
1088 | w.show(); |
1089 | w.setFocus(); |
1090 | QApplication::setActiveWindow(&w); |
1091 | QVERIFY(QTest::qWaitForWindowActive(&w)); |
1092 | |
1093 | QTest::keyClick( widget: &w, key: Qt::Key_Alt ); |
1094 | QTRY_VERIFY( ::qobject_cast<QMenuBar *>(qApp->focusWidget()) ); |
1095 | } |
1096 | |
1097 | // QTBUG-47377: Pressing 'Alt' after opening a menu by pressing 'Alt+Accelerator' |
1098 | // should close it and QMenuBar::activeAction() should be 0. |
1099 | void tst_QMenuBar::() |
1100 | { |
1101 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1102 | QSKIP("Wayland: This fails. Figure out why." ); |
1103 | |
1104 | const QStyle *style = QApplication::style(); |
1105 | if (!style->styleHint(stylehint: QStyle::SH_MenuBar_AltKeyNavigation) ) { |
1106 | QSKIP(("This test is not supposed to work in the " + style->objectName().toLatin1() |
1107 | + " style. Skipping." ).constData()); |
1108 | } |
1109 | |
1110 | QMainWindow w; |
1111 | w.setWindowTitle(QTest::currentTestFunction()); |
1112 | w.menuBar()->setNativeMenuBar(false); |
1113 | QMenu * = w.menuBar()->addMenu(title: tr(s: "&File" )); |
1114 | menuFile->addAction(text: "Quit" ); |
1115 | QMenu * = w.menuBar()->addMenu(title: tr(s: "&Edit" )); |
1116 | menuEdit->addAction(text: "Copy" ); |
1117 | |
1118 | w.show(); |
1119 | w.move(QGuiApplication::primaryScreen()->availableGeometry().center()); |
1120 | QApplication::setActiveWindow(&w); |
1121 | QVERIFY(QTest::qWaitForWindowActive(&w)); |
1122 | |
1123 | QTest::keyClick(widget: &w, key: Qt::Key_F, modifier: Qt::AltModifier); |
1124 | QTRY_VERIFY(menuFile->isVisible()); |
1125 | QTest::keyClick(widget: menuFile, key: Qt::Key_Alt, modifier: Qt::AltModifier); |
1126 | QTRY_VERIFY(!menuFile->isVisible()); |
1127 | QTRY_VERIFY(!w.menuBar()->activeAction()); |
1128 | } |
1129 | |
1130 | // Qt/Mac does not use the native popups/menubar |
1131 | #if !defined(Q_OS_DARWIN) |
1132 | void tst_QMenuBar::() |
1133 | { |
1134 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1135 | QSKIP("Wayland: This fails. Figure out why." ); |
1136 | |
1137 | QMainWindow w; |
1138 | const TestMenu = initWindowWithComplexMenuBar(w); |
1139 | w.show(); |
1140 | w.setFocus(); |
1141 | QApplication::setActiveWindow(&w); |
1142 | QVERIFY(QTest::qWaitForWindowActive(&w)); |
1143 | |
1144 | QCOMPARE(m_complexTriggerCount[3], 0); |
1145 | QTest::keyClick(widget: &w, key: Qt::Key_E, modifier: Qt::AltModifier); |
1146 | QTest::qWait(ms: 200); |
1147 | QCOMPARE(m_complexTriggerCount[3], 1); |
1148 | QVERIFY(!w.menuBar()->activeAction()); |
1149 | |
1150 | QTest::keyClick(widget: &w, key: Qt::Key_1, modifier: Qt::AltModifier ); |
1151 | QVERIFY(menu.menus.at(0)->isActiveWindow()); |
1152 | QTest::keyClick(widget: &w, key: Qt::Key_2); |
1153 | QVERIFY(menu.menus.at(0)->isActiveWindow()); |
1154 | } |
1155 | #endif |
1156 | |
1157 | class LayoutDirectionSaver |
1158 | { |
1159 | Q_DISABLE_COPY(LayoutDirectionSaver) |
1160 | public: |
1161 | explicit LayoutDirectionSaver(Qt::LayoutDirection direction) |
1162 | : m_oldDirection(qApp->layoutDirection()) |
1163 | { |
1164 | qApp->setLayoutDirection(direction); |
1165 | } |
1166 | |
1167 | ~LayoutDirectionSaver() |
1168 | { |
1169 | qApp->setLayoutDirection(m_oldDirection); |
1170 | } |
1171 | |
1172 | private: |
1173 | const Qt::LayoutDirection m_oldDirection; |
1174 | }; |
1175 | |
1176 | // Qt/Mac does not use the native popups/menubar |
1177 | #if !defined(Q_OS_DARWIN) |
1178 | void tst_QMenuBar::() |
1179 | { |
1180 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1181 | QSKIP("Wayland: This fails. Figure out why." ); |
1182 | |
1183 | QMainWindow w; |
1184 | |
1185 | Menu ; |
1186 | menu.setTitle("&menu" ); |
1187 | QRect availRect = w.screen()->availableGeometry(); |
1188 | QRect screenRect = w.screen()->geometry(); |
1189 | |
1190 | while(menu.sizeHint().height() < (screenRect.height()*2/3)) { |
1191 | menu.addAction(text: "item" ); |
1192 | } |
1193 | |
1194 | w.menuBar()->setNativeMenuBar(false); |
1195 | QAction * = w.menuBar()->addMenu(menu: &menu); |
1196 | centerOnScreen(w: &w); |
1197 | w.show(); |
1198 | qApp->setActiveWindow(&w); |
1199 | QVERIFY(QTest::qWaitForWindowActive(&w)); |
1200 | |
1201 | //the menu should be below the menubar item |
1202 | { |
1203 | w.move(availRect.topLeft()); |
1204 | QRect mbItemRect = w.menuBar()->actionGeometry(menu_action); |
1205 | mbItemRect.moveTo(p: w.menuBar()->mapToGlobal(mbItemRect.topLeft())); |
1206 | QTest::keyClick(widget: &w, key: Qt::Key_M, modifier: Qt::AltModifier ); |
1207 | QVERIFY(menu.isActiveWindow()); |
1208 | QCOMPARE(menu.pos(), QPoint(mbItemRect.x(), mbItemRect.bottom() + 1)); |
1209 | menu.close(); |
1210 | } |
1211 | |
1212 | //the menu should be above the menubar item |
1213 | { |
1214 | w.move(ax: 0,ay: screenRect.bottom() - screenRect.height()/4); //just leave some place for the menubar |
1215 | QRect mbItemRect = w.menuBar()->actionGeometry(menu_action); |
1216 | mbItemRect.moveTo(p: w.menuBar()->mapToGlobal(mbItemRect.topLeft())); |
1217 | QTest::keyClick(widget: &w, key: Qt::Key_M, modifier: Qt::AltModifier ); |
1218 | QVERIFY(menu.isActiveWindow()); |
1219 | #ifdef Q_OS_WINRT |
1220 | QEXPECT_FAIL("" , "QTest::keyClick does not work on WinRT." , Abort); |
1221 | #endif |
1222 | QCOMPARE(menu.pos(), QPoint(mbItemRect.x(), mbItemRect.top() - menu.height())); |
1223 | menu.close(); |
1224 | } |
1225 | |
1226 | //the menu should be on the side of the menubar item and should be "stuck" to the bottom of the screen |
1227 | { |
1228 | w.move(ax: 0,ay: screenRect.y() + screenRect.height()/2); //put it in the middle |
1229 | QRect mbItemRect = w.menuBar()->actionGeometry(menu_action); |
1230 | mbItemRect.moveTo(p: w.menuBar()->mapToGlobal(mbItemRect.topLeft())); |
1231 | QTest::keyClick(widget: &w, key: Qt::Key_M, modifier: Qt::AltModifier ); |
1232 | QVERIFY(menu.isActiveWindow()); |
1233 | QPoint firstPoint = QPoint(mbItemRect.right()+1, screenRect.bottom() - menu.height() + 1); |
1234 | QPoint secondPoint = QPoint(mbItemRect.right()+1, availRect.bottom() - menu.height() + 1); |
1235 | QVERIFY(menu.pos() == firstPoint || menu.pos() == secondPoint); |
1236 | menu.close(); |
1237 | } |
1238 | |
1239 | // QTBUG-2596: in RTL, the menu should be stuck at the right of the action geometry |
1240 | { |
1241 | LayoutDirectionSaver directionSaver(Qt::RightToLeft); |
1242 | menu.clear(); |
1243 | QObject::connect(sender: &menu, SIGNAL(aboutToShow()), receiver: &menu, SLOT(addActions())); |
1244 | QRect mbItemRect = w.menuBar()->actionGeometry(menu_action); |
1245 | mbItemRect.moveTo(p: w.menuBar()->mapToGlobal(mbItemRect.topLeft())); |
1246 | QTest::keyClick(widget: &w, key: Qt::Key_M, modifier: Qt::AltModifier ); |
1247 | QVERIFY(menu.isActiveWindow()); |
1248 | QCOMPARE(menu.geometry().right(), mbItemRect.right()); |
1249 | menu.close(); |
1250 | } |
1251 | |
1252 | // QTBUG-28031: Click at bottom-right corner. |
1253 | { |
1254 | w.move(ax: 400, ay: 200); |
1255 | LayoutDirectionSaver directionSaver(Qt::RightToLeft); |
1256 | QMenuBar *mb = w.menuBar(); |
1257 | const QPoint bottomRight = mb->actionGeometry(menu.menuAction()).bottomRight() - QPoint(1, 1); |
1258 | const QPoint localPos = widgetToWindowPos(w: mb, pos: bottomRight); |
1259 | const QPoint globalPos = w.mapToGlobal(localPos); |
1260 | QTest::mouseClick(window: w.windowHandle(), button: Qt::LeftButton, stateKey: {}, pos: localPos); |
1261 | QTRY_VERIFY(menu.isActiveWindow()); |
1262 | QCOMPARE(menu.geometry().right() - 1, globalPos.x()); |
1263 | menu.close(); |
1264 | } |
1265 | } |
1266 | #endif |
1267 | |
1268 | void tst_QMenuBar::() |
1269 | { |
1270 | //we create a window with submenus and we check that both menubar and menus get the triggered signal |
1271 | QMainWindow win; |
1272 | centerOnScreen(w: &win); |
1273 | QMenu * = win.menuBar()->addMenu(title: "test" ); |
1274 | QAction *top = menu->addAction(text: "toplevelaction" ); |
1275 | QMenu * = menu->addMenu(title: "nested menu" ); |
1276 | QAction *action = submenu->addAction(text: "nested action" ); |
1277 | |
1278 | QSignalSpy (win.menuBar(), SIGNAL(triggered(QAction*))); |
1279 | QSignalSpy (menu, SIGNAL(triggered(QAction*))); |
1280 | QSignalSpy (submenu, SIGNAL(triggered(QAction*))); |
1281 | |
1282 | //let's trigger the first action |
1283 | top->trigger(); |
1284 | |
1285 | QCOMPARE(menubarSpy.count(), 1); |
1286 | QCOMPARE(menuSpy.count(), 1); |
1287 | QCOMPARE(submenuSpy.count(), 0); |
1288 | |
1289 | menubarSpy.clear(); |
1290 | menuSpy.clear(); |
1291 | submenuSpy.clear(); |
1292 | |
1293 | //let's trigger the sub action |
1294 | action->trigger(); |
1295 | QCOMPARE(menubarSpy.count(), 1); |
1296 | QCOMPARE(menuSpy.count(), 1); |
1297 | QCOMPARE(submenuSpy.count(), 1); |
1298 | } |
1299 | |
1300 | void tst_QMenuBar::() |
1301 | { |
1302 | if (!QGuiApplication::platformName().compare(other: QLatin1String("minimal" ), cs: Qt::CaseInsensitive)) |
1303 | QSKIP("Highlighting does not work correctly for minimal platform" ); |
1304 | |
1305 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1306 | QSKIP("Wayland: This fails. Figure out why." ); |
1307 | |
1308 | QMainWindow win; |
1309 | win.menuBar()->setNativeMenuBar(false); //we can't check the geometry of native menubars |
1310 | QMenu ; |
1311 | QAction *file = win.menuBar()->addMenu(menu: &menu); |
1312 | file->setText("file" ); |
1313 | QMenu ; |
1314 | QAction *file2 = win.menuBar()->addMenu(menu: &menu2); |
1315 | file2->setText("file2" ); |
1316 | QAction *nothing = win.menuBar()->addAction(text: "nothing" ); |
1317 | |
1318 | centerOnScreen(w: &win); |
1319 | win.show(); |
1320 | QApplication::setActiveWindow(&win); |
1321 | QVERIFY(QTest::qWaitForWindowActive(&win)); |
1322 | |
1323 | const QPoint filePos = menuBarActionWindowPos(mb: win.menuBar(), a: file); |
1324 | QWindow *window = win.windowHandle(); |
1325 | QTest::mousePress(window, button: Qt::LeftButton, stateKey: {}, pos: filePos); |
1326 | QTest::mouseMove(window, pos: filePos); |
1327 | QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: {}, pos: filePos); |
1328 | QTRY_VERIFY(menu.isVisible()); |
1329 | QVERIFY(!menu2.isVisible()); |
1330 | QCOMPARE(win.menuBar()->activeAction(), file); |
1331 | |
1332 | const QPoint file2Pos = menuBarActionWindowPos(mb: win.menuBar(), a: file2); |
1333 | QTest::mouseMove(window, pos: file2Pos); |
1334 | QTRY_VERIFY(!menu.isVisible()); |
1335 | QTRY_VERIFY(menu2.isVisible()); |
1336 | QCOMPARE(win.menuBar()->activeAction(), file2); |
1337 | |
1338 | QPoint nothingCenter = menuBarActionWindowPos(mb: win.menuBar(), a: nothing); |
1339 | QTest::mouseMove(window, pos: nothingCenter); |
1340 | QTRY_VERIFY(!menu2.isVisible()); |
1341 | QVERIFY(!menu.isVisible()); |
1342 | QTRY_COMPARE(win.menuBar()->activeAction(), nothing); |
1343 | } |
1344 | |
1345 | void tst_QMenuBar::() |
1346 | { |
1347 | struct MyStyle : public QProxyStyle |
1348 | { |
1349 | MyStyle() : QProxyStyle(QStyleFactory::create("windows" )) { } |
1350 | |
1351 | virtual int pixelMetric(PixelMetric metric, const QStyleOption * option = 0, const QWidget * widget = 0 ) const |
1352 | { |
1353 | // I chose strange values (prime numbers to be more sure that the size of the menubar is correct) |
1354 | switch (metric) |
1355 | { |
1356 | case QStyle::PM_MenuBarItemSpacing: |
1357 | return 7; |
1358 | case PM_MenuBarHMargin: |
1359 | return 13; |
1360 | case PM_MenuBarVMargin: |
1361 | return 11; |
1362 | case PM_MenuBarPanelWidth: |
1363 | return 1; |
1364 | default: |
1365 | return QProxyStyle::pixelMetric(metric, option, widget); |
1366 | } |
1367 | } |
1368 | } style; |
1369 | |
1370 | QMenuBar mb; |
1371 | mb.setNativeMenuBar(false); //we can't check the geometry of native menubars |
1372 | |
1373 | mb.setStyle(&style); |
1374 | //this is a list of arbitrary strings so that we check the geometry |
1375 | QStringList list = QStringList() << "trer" << "ezrfgtgvqd" << "sdgzgzerzerzer" << "eerzertz" << "er" ; |
1376 | foreach(QString str, list) |
1377 | mb.addAction(text: str); |
1378 | |
1379 | const int panelWidth = style.pixelMetric(metric: QStyle::PM_MenuBarPanelWidth); |
1380 | const int hmargin = style.pixelMetric(metric: QStyle::PM_MenuBarHMargin); |
1381 | const int vmargin = style.pixelMetric(metric: QStyle::PM_MenuBarVMargin); |
1382 | const int spacing = style.pixelMetric(metric: QStyle::PM_MenuBarItemSpacing); |
1383 | |
1384 | centerOnScreen(w: &mb); |
1385 | mb.show(); |
1386 | QRect result; |
1387 | foreach(QAction *action, mb.actions()) { |
1388 | const QRect actionRect = mb.actionGeometry(action); |
1389 | if (!result.isNull()) //this is the first item |
1390 | QCOMPARE(actionRect.left() - result.right() - 1, spacing); |
1391 | result |= actionRect; |
1392 | QCOMPARE(result.x(), panelWidth + hmargin + spacing); |
1393 | QCOMPARE(result.y(), panelWidth + vmargin); |
1394 | } |
1395 | |
1396 | //this code is copied from QMenuBar |
1397 | //there is no public member that allows to initialize a styleoption instance |
1398 | QStyleOptionMenuItem opt; |
1399 | opt.rect = mb.rect(); |
1400 | opt.menuRect = mb.rect(); |
1401 | opt.state = QStyle::State_None; |
1402 | opt.menuItemType = QStyleOptionMenuItem::Normal; |
1403 | opt.checkType = QStyleOptionMenuItem::NotCheckable; |
1404 | opt.palette = mb.palette(); |
1405 | |
1406 | QSize resSize = QSize(result.x(), result.y()) + result.size() |
1407 | + QSize(panelWidth + hmargin, panelWidth + vmargin); |
1408 | |
1409 | |
1410 | resSize = style.sizeFromContents(type: QStyle::CT_MenuBar, option: &opt, |
1411 | size: resSize.expandedTo(otherSize: QApplication::globalStrut()), |
1412 | widget: &mb); |
1413 | |
1414 | QCOMPARE(resSize, mb.sizeHint()); |
1415 | } |
1416 | |
1417 | // On Mac, do not test the menubar with escape key |
1418 | #ifndef Q_OS_MACOS |
1419 | void tst_QMenuBar::() |
1420 | { |
1421 | QMenuBar ; |
1422 | menubar.setNativeMenuBar(false); |
1423 | QMenu ("menu1" ); |
1424 | QAction *first = menubar.addMenu(menu: &menu); |
1425 | menu.addAction(text: "quit" , receiver: &menubar, SLOT(close()), shortcut: QKeySequence("ESC" )); |
1426 | centerOnScreen(w: &menubar); |
1427 | menubar.show(); |
1428 | QApplication::setActiveWindow(&menubar); |
1429 | QVERIFY(QTest::qWaitForWindowExposed(&menubar)); |
1430 | menubar.setActiveAction(first); |
1431 | QTRY_VERIFY(menu.isVisible()); |
1432 | QCOMPARE(menubar.activeAction(), first); |
1433 | QVERIFY(QTest::qWaitForWindowExposed(&menu)); |
1434 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Escape); |
1435 | QVERIFY(!menu.isVisible()); |
1436 | QTRY_VERIFY(menubar.hasFocus()); |
1437 | QCOMPARE(menubar.activeAction(), first); |
1438 | QTest::qWait(ms: 200); |
1439 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Escape); |
1440 | QVERIFY(!menubar.activeAction()); |
1441 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Escape); //now the action should be triggered |
1442 | QTRY_VERIFY(!menubar.isVisible()); |
1443 | } |
1444 | #endif |
1445 | |
1446 | void tst_QMenuBar::() |
1447 | { |
1448 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1449 | QSKIP("Wayland: This fails. Figure out why." ); |
1450 | |
1451 | QMenuBar ; |
1452 | menubar.setNativeMenuBar(false); //we can't check the geometry of native menubars |
1453 | |
1454 | QAction * m = menubar.addAction( text: "&m" ); |
1455 | QAction * a = menubar.addAction( text: "&a" ); |
1456 | |
1457 | centerOnScreen(w: &menubar); |
1458 | menubar.show(); |
1459 | QApplication::setActiveWindow(&menubar); |
1460 | QVERIFY(QTest::qWaitForWindowActive(&menubar)); |
1461 | menubar.setActiveAction(m); |
1462 | QCOMPARE(menubar.activeAction(), m); |
1463 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Right); |
1464 | QCOMPARE(menubar.activeAction(), a); |
1465 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Right); |
1466 | QCOMPARE(menubar.activeAction(), m); |
1467 | a->setVisible(false); |
1468 | |
1469 | menubar.setActiveAction(m); |
1470 | QCOMPARE(menubar.activeAction(), m); //the active action shouldn't have changed |
1471 | |
1472 | //it used to crash here because the action is invisible |
1473 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_Right); |
1474 | QCOMPARE(menubar.activeAction(), m); //the active action shouldn't have changed |
1475 | } |
1476 | |
1477 | void tst_QMenuBar::closeOnSecondClickAndOpenOnThirdClick() // QTBUG-32807, menu should close on 2nd click. |
1478 | { |
1479 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1480 | QSKIP("Wayland: This fails. Figure out why." ); |
1481 | |
1482 | QMainWindow mainWindow; |
1483 | mainWindow.resize(w: 300, h: 200); |
1484 | centerOnScreen(w: &mainWindow); |
1485 | QMenuBar * = mainWindow.menuBar(); |
1486 | menuBar->setNativeMenuBar(false); |
1487 | QMenu * = menuBar->addMenu(QStringLiteral("OpenCloseOpen" )); |
1488 | fileMenu->addAction(QStringLiteral("Quit" )); |
1489 | mainWindow.show(); |
1490 | QApplication::setActiveWindow(&mainWindow); |
1491 | QVERIFY(QTest::qWaitForWindowActive(&mainWindow)); |
1492 | |
1493 | const QPoint center = menuBarActionWindowPos(mb: mainWindow.menuBar(), a: fileMenu->menuAction()); |
1494 | const QPoint globalPos = mainWindow.mapToGlobal(center); |
1495 | |
1496 | QWindow *window = mainWindow.windowHandle(); |
1497 | QTest::mouseMove(window, pos: center); |
1498 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: {}, pos: center); |
1499 | QTRY_VERIFY(fileMenu->isVisible()); |
1500 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: {}, pos: fileMenu->mapFromGlobal(globalPos)); |
1501 | QTRY_VERIFY(!fileMenu->isVisible()); |
1502 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: {}, pos: center); |
1503 | QTRY_VERIFY(fileMenu->isVisible()); |
1504 | } |
1505 | |
1506 | Q_DECLARE_METATYPE(Qt::Corner) |
1507 | |
1508 | void tst_QMenuBar::() |
1509 | { |
1510 | QTest::addColumn<Qt::Corner>(name: "corner" ); |
1511 | QTest::newRow(dataTag: "left" ) << Qt::TopLeftCorner; |
1512 | QTest::newRow(dataTag: "right" ) << Qt::TopRightCorner; |
1513 | } |
1514 | |
1515 | static QByteArray msgComparison(int v1, const char *op, int v2) |
1516 | { |
1517 | QString result; |
1518 | QDebug(&result) << v1 << op << v2 << "failed" ; |
1519 | return result.toLocal8Bit(); |
1520 | } |
1521 | |
1522 | void tst_QMenuBar::() |
1523 | { |
1524 | enum { cornerWidgetWidth = 100 }; |
1525 | |
1526 | QFETCH(Qt::Corner, corner); |
1527 | |
1528 | #if defined(Q_OS_MACOS) |
1529 | QSKIP("Test interferes with native menu bars on this platform" ); |
1530 | #endif |
1531 | |
1532 | QWidget widget; |
1533 | const QString dataTag = QLatin1String(QTest::currentDataTag()); |
1534 | widget.setWindowTitle(QLatin1String(QTest::currentTestFunction()) + dataTag); |
1535 | QVBoxLayout *layout = new QVBoxLayout(&widget); |
1536 | QMenuBar * = new QMenuBar(&widget); |
1537 | menuBar->setNativeMenuBar(false); |
1538 | layout->addWidget(menuBar); |
1539 | QMenu * = menuBar->addMenu(title: "File" ); |
1540 | fileMenu->addAction(text: "Quit" ); |
1541 | QMenu * =menuBar->addMenu(title: "Edit" ); |
1542 | editMenu->addAction(text: "Copy" ); |
1543 | centerOnScreen(w: &widget); |
1544 | |
1545 | QLabel *cornerLabel = new QLabel(dataTag); |
1546 | cornerLabel->setFixedWidth(cornerWidgetWidth); |
1547 | menuBar->setCornerWidget(w: cornerLabel, corner); |
1548 | QCOMPARE(menuBar->cornerWidget(corner), cornerLabel); |
1549 | widget.show(); |
1550 | QVERIFY(QTest::qWaitForWindowExposed(&widget)); |
1551 | |
1552 | const QRect = menuBar->actionGeometry(fileMenu->menuAction()); |
1553 | const QRect = menuBar->actionGeometry(editMenu->menuAction()); |
1554 | const int = menuBar->width(); |
1555 | switch (corner) { // QTBUG-36010 , verify corner widget geometry is correct |
1556 | case Qt::TopLeftCorner: |
1557 | QVERIFY2(fileMenuGeometry.left() >= cornerWidgetWidth, |
1558 | msgComparison(fileMenuGeometry.left(), ">=" , cornerWidgetWidth)); |
1559 | #ifdef Q_OS_WINRT |
1560 | QEXPECT_FAIL("" , "Broken on WinRT - QTBUG-68297" , Abort); |
1561 | #endif |
1562 | QVERIFY2(menuBarWidth - editMenuGeometry.right() < cornerWidgetWidth, |
1563 | msgComparison(menuBarWidth - editMenuGeometry.right(), "<" , cornerWidgetWidth)); |
1564 | break; |
1565 | case Qt::TopRightCorner: |
1566 | QVERIFY2(fileMenuGeometry.left() < cornerWidgetWidth, |
1567 | msgComparison(fileMenuGeometry.left(), "<" , cornerWidgetWidth)); |
1568 | QVERIFY2(menuBarWidth - editMenuGeometry.right() >= cornerWidgetWidth, |
1569 | msgComparison(menuBarWidth - editMenuGeometry.right(), ">=" , cornerWidgetWidth)); |
1570 | break; |
1571 | default: |
1572 | break; |
1573 | } |
1574 | |
1575 | menuBar->setCornerWidget(w: 0, corner); // Don't crash. |
1576 | QVERIFY(!menuBar->cornerWidget(corner)); |
1577 | delete cornerLabel; |
1578 | } |
1579 | |
1580 | |
1581 | void tst_QMenuBar::() |
1582 | { |
1583 | // This test was largely inspired by the test case submitted for the bug |
1584 | QMainWindow mainWindow; |
1585 | mainWindow.resize(w: 300, h: 200); |
1586 | centerOnScreen(w: &mainWindow); |
1587 | const TestMenu = initWindowWithComplexMenuBar(w&: mainWindow); |
1588 | QApplication::setActiveWindow(&mainWindow); |
1589 | |
1590 | // they can't be windows |
1591 | QWidget hiddenParent(&mainWindow, {}); |
1592 | //this one is going to be moved around |
1593 | QWidget movingParent(&hiddenParent, {}); |
1594 | |
1595 | //set up the container widget |
1596 | QWidget containerWidget(&movingParent, {}); |
1597 | |
1598 | //set the new parent, a window |
1599 | QScopedPointer<QWidget> windowedParent; |
1600 | windowedParent.reset(other: new QWidget(nullptr, Qt::WindowFlags())); |
1601 | windowedParent->setGeometry(ax: 400, ay: 10, aw: 300, ah: 300); |
1602 | |
1603 | windowedParent->show(); |
1604 | QVERIFY(QTest::qWaitForWindowExposed(windowedParent.data())); |
1605 | |
1606 | //set the "container", can't be a window |
1607 | QWidget containedWidget(&containerWidget, {}); |
1608 | |
1609 | taskQTBUG53205MenuBar = new QMenuBar(&containedWidget); |
1610 | |
1611 | connect(sender: testMenus.actions[0], signal: &QAction::triggered, receiver: this, slot: &tst_QMenuBar::slotForTaskQTBUG53205); |
1612 | //now, move things around |
1613 | //from : QMainWindow<-hiddenParent<-movingParent<-containerWidget<-containedWidget<-menuBar |
1614 | //to windowedParent<-movingParent<-containerWidget<-containedWidget<-menuBar |
1615 | movingParent.setParent(parent: windowedParent.data(), f: {}); |
1616 | // this resets the parenting and the menu bar's window |
1617 | taskQTBUG53205MenuBar->setParent(nullptr); |
1618 | taskQTBUG53205MenuBar->setParent(&containedWidget); |
1619 | //from windowedParent<-movingParent<-containerWidget<-containedWidget<-menuBar |
1620 | //to : QMainWindow<-hiddenParent<-movingParent<-containerWidget<-containedWidget<-menuBar |
1621 | movingParent.setParent(parent: &hiddenParent, f: {}); |
1622 | windowedParent.reset(); //make the old window invalid |
1623 | // trigger the aciton, reset the menu bar's window, this used to crash here. |
1624 | testMenus.actions[0]->trigger(); |
1625 | } |
1626 | |
1627 | void tst_QMenuBar::() |
1628 | { |
1629 | QMainWindow win; |
1630 | win.menuBar()->setNativeMenuBar(false); |
1631 | QAction *act1 = win.menuBar()->addAction(text: "A very long named action that make menuBar item wide enough" ); |
1632 | QSignalSpy spy(win.menuBar(), &QMenuBar::triggered); |
1633 | |
1634 | QRect actRect = win.menuBar()->actionGeometry(act1); |
1635 | // resize to action's size to make Action1 hidden |
1636 | win.resize(w: actRect.width() - 10, h: win.size().height()); |
1637 | win.show(); |
1638 | QApplication::setActiveWindow(&win); |
1639 | QVERIFY(QTest::qWaitForWindowExposed(&win)); |
1640 | // click center of the blank area on the menubar where Action1 resided |
1641 | QTest::mouseClick(window: win.windowHandle(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: win.menuBar()->geometry().center()); |
1642 | QCoreApplication::sendPostedEvents(); // make sure all queued events also dispatched |
1643 | QCOMPARE(spy.count(), 0); |
1644 | } |
1645 | |
1646 | // QTBUG-56526 |
1647 | void tst_QMenuBar::() |
1648 | { |
1649 | QMenuBar ; |
1650 | QPlatformMenuBar * = menuBar.platformMenuBar(); |
1651 | if (!platformMenuBar) |
1652 | QSKIP("No platform menubar implementation available on this platform." ); |
1653 | |
1654 | // QMenu must not create a platform menu instance at creation time, because |
1655 | // on Unity the type of the platform menu instance must be different (QGtk3Menu |
1656 | // vs. QDbusPlatformMenu) depending on whether the menu is in the global menubar |
1657 | // or a standalone context menu. |
1658 | QMenu * = new QMenu(&menuBar); |
1659 | QVERIFY(!menu->platformMenu()); |
1660 | |
1661 | menuBar.addMenu(menu); |
1662 | QVERIFY(menu->platformMenu()); |
1663 | } |
1664 | |
1665 | class TestObject : public QObject |
1666 | { |
1667 | Q_OBJECT |
1668 | public: |
1669 | bool flag = false; |
1670 | void setFlag() |
1671 | { |
1672 | flag = true; |
1673 | } |
1674 | }; |
1675 | |
1676 | void tst_QMenuBar::() |
1677 | { |
1678 | bool flag = false; |
1679 | auto functor = [&flag](){ flag = true; }; |
1680 | |
1681 | TestObject obj; |
1682 | |
1683 | QMenuBar ; |
1684 | |
1685 | auto action1 = menuBar.addAction(QStringLiteral("1" ), object: &obj, slot: &TestObject::setFlag); |
1686 | auto action2 = menuBar.addAction(QStringLiteral("2" ), slot: functor); |
1687 | |
1688 | action1->activate(event: QAction::Trigger); |
1689 | action2->activate(event: QAction::Trigger); |
1690 | |
1691 | QVERIFY(obj.flag); |
1692 | QVERIFY(flag); |
1693 | |
1694 | flag = false; |
1695 | |
1696 | auto action3 = menuBar.addAction(QStringLiteral("3" ), object: this, slot: functor); |
1697 | action3->activate(event: QAction::Trigger); |
1698 | QVERIFY(flag); |
1699 | } |
1700 | |
1701 | void tst_QMenuBar::() |
1702 | { |
1703 | QMainWindow win; |
1704 | win.menuBar()->setNativeMenuBar(false); |
1705 | QAction *act1 = win.menuBar()->addAction(text: "Action1" ); |
1706 | QAction *act2 = win.menuBar()->addAction(text: "Action2" ); |
1707 | QSignalSpy spy(win.menuBar(), &QMenuBar::triggered); |
1708 | |
1709 | win.show(); |
1710 | QApplication::setActiveWindow(&win); |
1711 | QVERIFY(QTest::qWaitForWindowExposed(&win)); |
1712 | |
1713 | QPoint posAct1 = menuBarActionWindowPos(mb: win.menuBar(), a: act1); |
1714 | QPoint posAct2 = menuBarActionWindowPos(mb: win.menuBar(), a: act2); |
1715 | |
1716 | QTest::mouseClick(window: win.windowHandle(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: posAct1); |
1717 | QTRY_COMPARE(spy.count(), 1); |
1718 | |
1719 | QTest::mouseClick(window: win.windowHandle(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: posAct2); |
1720 | QTRY_COMPARE(spy.count(), 2); |
1721 | |
1722 | QTest::mouseClick(window: win.windowHandle(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: posAct2); |
1723 | QTRY_COMPARE(spy.count(), 3); |
1724 | } |
1725 | |
1726 | void tst_QMenuBar::() |
1727 | { |
1728 | QWidget *parent = taskQTBUG53205MenuBar->parentWidget(); |
1729 | taskQTBUG53205MenuBar->setParent(nullptr); |
1730 | taskQTBUG53205MenuBar->setParent(parent); |
1731 | } |
1732 | |
1733 | // Qt/Mac does not use the native popups/menubar |
1734 | #if !defined(Q_OS_DARWIN) |
1735 | void tst_QMenuBar::() |
1736 | { |
1737 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1738 | QSKIP("Wayland: This fails. Figure out why." ); |
1739 | |
1740 | QMainWindow mainWindow; |
1741 | QWidget *centralWidget = new QWidget; |
1742 | centralWidget->setFocusPolicy(Qt::StrongFocus); |
1743 | mainWindow.setCentralWidget(centralWidget); |
1744 | initWindowWithSimpleMenuBar(w&: mainWindow); |
1745 | |
1746 | mainWindow.show(); |
1747 | QApplication::setActiveWindow(&mainWindow); |
1748 | QVERIFY(QTest::qWaitForWindowActive(&mainWindow)); |
1749 | |
1750 | QVERIFY(!mainWindow.menuBar()->hasFocus()); |
1751 | QCOMPARE(m_simpleActivatedCount, 0); |
1752 | |
1753 | QTest::keyPress(widget: &mainWindow, key: Qt::Key_Alt, modifier: Qt::AltModifier); |
1754 | QVERIFY(!mainWindow.menuBar()->hasFocus()); |
1755 | QCOMPARE(m_simpleActivatedCount, 0); |
1756 | |
1757 | QTest::keyPress(widget: &mainWindow, key: Qt::Key_Z, modifier: Qt::AltModifier); |
1758 | QVERIFY(!mainWindow.menuBar()->hasFocus()); |
1759 | QCOMPARE(m_simpleActivatedCount, 2); // the action AND the menu will activate |
1760 | |
1761 | QTest::keyRelease(widget: &mainWindow, key: Qt::Key_Alt, modifier: Qt::NoModifier); |
1762 | QVERIFY(!mainWindow.menuBar()->hasFocus()); |
1763 | QCOMPARE(m_simpleActivatedCount, 2); |
1764 | |
1765 | QTest::keyRelease(widget: &mainWindow, key: Qt::Key_Z, modifier: Qt::NoModifier); |
1766 | QVERIFY(!mainWindow.menuBar()->hasFocus()); |
1767 | QCOMPARE(m_simpleActivatedCount, 2); |
1768 | } |
1769 | #endif |
1770 | |
1771 | #ifdef Q_OS_MACOS |
1772 | extern bool tst_qmenubar_taskQTBUG56275(QMenuBar *); |
1773 | |
1774 | void tst_QMenuBar::taskQTBUG56275_reinsertMenuInParentlessQMenuBar() |
1775 | { |
1776 | QMenuBar menubar; |
1777 | |
1778 | QMenu *menu = new QMenu("menu" , &menubar); |
1779 | QMenu* submenu = menu->addMenu("submenu" ); |
1780 | submenu->addAction("action1" ); |
1781 | submenu->addAction("action2" ); |
1782 | menu->addAction("action3" ); |
1783 | menubar.addMenu(menu); |
1784 | |
1785 | QTest::qWait(100); |
1786 | menubar.clear(); |
1787 | menubar.addMenu(menu); |
1788 | QTest::qWait(100); |
1789 | |
1790 | QVERIFY(tst_qmenubar_taskQTBUG56275(&menubar)); |
1791 | } |
1792 | |
1793 | void tst_QMenuBar::QTBUG_57404_existingMenuItemException() |
1794 | { |
1795 | QMainWindow mw1; |
1796 | QMainWindow mw2; |
1797 | mw1.show(); |
1798 | mw2.show(); |
1799 | |
1800 | QMenuBar *mb = new QMenuBar(&mw1); |
1801 | mw1.setMenuBar(mb); |
1802 | mb->show(); |
1803 | QMenu *editMenu = new QMenu(QLatin1String("Edit" ), &mw1); |
1804 | mb->addMenu(editMenu); |
1805 | QAction *copyAction = editMenu->addAction("&Copy" ); |
1806 | copyAction->setShortcut(QKeySequence("Ctrl+C" )); |
1807 | copyAction->setMenuRole(QAction::NoRole); |
1808 | |
1809 | QVERIFY(QTest::qWaitForWindowExposed(&mw2)); |
1810 | QTest::qWait(100); |
1811 | mw2.close(); |
1812 | mw1.activateWindow(); |
1813 | QTest::qWait(100); |
1814 | // No crash, all fine. Ideally, there should be only one warning. |
1815 | } |
1816 | #endif // Q_OS_MACOS |
1817 | |
1818 | void tst_QMenuBar::() |
1819 | { |
1820 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1821 | QSKIP("Wayland: This fails. Figure out why." ); |
1822 | |
1823 | QMainWindow window; |
1824 | QMenuBar * = window.menuBar(); |
1825 | QMenu * = menubar->addMenu(title: "Parent menu" ); |
1826 | |
1827 | QAction *action = parentMenu->addAction(text: "Action in parent menu" ); |
1828 | QMenu * = new QMenu("Submenu" ); |
1829 | action->setMenu(subMenu); |
1830 | delete subMenu; |
1831 | |
1832 | window.show(); |
1833 | QApplication::setActiveWindow(&window); |
1834 | QVERIFY(QTest::qWaitForWindowActive(&window)); |
1835 | QTest::qWait(ms: 500); |
1836 | } |
1837 | |
1838 | void tst_QMenuBar::() |
1839 | { |
1840 | QMenuBar ; |
1841 | QMenu ("menu" ); |
1842 | menu.addAction(icon: QIcon("crash.png" ), text: "crash" ); |
1843 | menuBar.addMenu(menu: &menu); |
1844 | // No crash, all fine. |
1845 | } |
1846 | |
1847 | QTEST_MAIN(tst_QMenuBar) |
1848 | #include "tst_qmenubar.moc" |
1849 | |