1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 The Qt Company Ltd. |
4 | ** Contact: http://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL3$ |
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 http://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at http://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or later as published by the Free |
28 | ** Software Foundation and appearing in the file LICENSE.GPL included in |
29 | ** the packaging of this file. Please review the following information to |
30 | ** ensure the GNU General Public License version 2.0 requirements will be |
31 | ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
32 | ** |
33 | ** $QT_END_LICENSE$ |
34 | ** |
35 | ****************************************************************************/ |
36 | |
37 | #include <QtTest/qtest.h> |
38 | #include <QtTest/qsignalspy.h> |
39 | #include <QtGui/qcursor.h> |
40 | #include <QtGui/qstylehints.h> |
41 | #include <QtQml/qqmlengine.h> |
42 | #include <QtQml/qqmlcomponent.h> |
43 | #include <QtQml/qqmlcontext.h> |
44 | #include <QtQuick/qquickview.h> |
45 | #include <QtQuick/private/qquickitem_p.h> |
46 | #include "../shared/util.h" |
47 | #include "../shared/visualtestutil.h" |
48 | #include "../shared/qtest_quickcontrols.h" |
49 | |
50 | #include <QtQuickTemplates2/private/qquickaction_p.h> |
51 | #include <QtQuickTemplates2/private/qquickapplicationwindow_p.h> |
52 | #include <QtQuickTemplates2/private/qquickoverlay_p.h> |
53 | #include <QtQuickTemplates2/private/qquickbutton_p.h> |
54 | #include <QtQuickTemplates2/private/qquickmenu_p.h> |
55 | #include <QtQuickTemplates2/private/qquickmenuitem_p.h> |
56 | #include <QtQuickTemplates2/private/qquickmenuseparator_p.h> |
57 | |
58 | using namespace QQuickVisualTestUtil; |
59 | |
60 | class : public QQmlDataTest |
61 | { |
62 | Q_OBJECT |
63 | |
64 | public: |
65 | |
66 | private slots: |
67 | void defaults(); |
68 | void count(); |
69 | void mouse(); |
70 | void pressAndHold(); |
71 | void contextMenuKeyboard(); |
72 | void disabledMenuItemKeyNavigation(); |
73 | void mnemonics(); |
74 | void menuButton(); |
75 | void addItem(); |
76 | void menuSeparator(); |
77 | void repeater(); |
78 | void order(); |
79 | void popup(); |
80 | void actions(); |
81 | void removeTakeItem(); |
82 | void subMenuMouse_data(); |
83 | void subMenuMouse(); |
84 | void subMenuDisabledMouse_data(); |
85 | void subMenuDisabledMouse(); |
86 | void subMenuKeyboard_data(); |
87 | void subMenuKeyboard(); |
88 | void subMenuDisabledKeyboard_data(); |
89 | void subMenuDisabledKeyboard(); |
90 | void subMenuPosition_data(); |
91 | void subMenuPosition(); |
92 | void addRemoveSubMenus(); |
93 | void scrollable_data(); |
94 | void scrollable(); |
95 | void disableWhenTriggered_data(); |
96 | void disableWhenTriggered(); |
97 | void menuItemWidth_data(); |
98 | void menuItemWidth(); |
99 | void menuItemWidthAfterMenuWidthChanged_data(); |
100 | void menuItemWidthAfterMenuWidthChanged(); |
101 | void menuItemWidthAfterImplicitWidthChanged_data(); |
102 | void menuItemWidthAfterImplicitWidthChanged(); |
103 | void menuItemWidthAfterRetranslate(); |
104 | void giveMenuItemFocusOnButtonPress(); |
105 | }; |
106 | |
107 | void tst_QQuickMenu::() |
108 | { |
109 | QQuickApplicationHelper helper(this, QLatin1String("applicationwindow.qml" )); |
110 | QVERIFY2(helper.ready, helper.failureMessage()); |
111 | |
112 | QQuickMenu * = helper.appWindow->property(name: "emptyMenu" ).value<QQuickMenu*>(); |
113 | QCOMPARE(emptyMenu->isVisible(), false); |
114 | QCOMPARE(emptyMenu->currentIndex(), -1); |
115 | QCOMPARE(emptyMenu->contentItem()->property("currentIndex" ), QVariant(-1)); |
116 | QCOMPARE(emptyMenu->count(), 0); |
117 | } |
118 | |
119 | void tst_QQuickMenu::() |
120 | { |
121 | QQuickApplicationHelper helper(this, QLatin1String("applicationwindow.qml" )); |
122 | QVERIFY2(helper.ready, helper.failureMessage()); |
123 | |
124 | QQuickMenu * = helper.window->property(name: "emptyMenu" ).value<QQuickMenu*>(); |
125 | QVERIFY(menu); |
126 | |
127 | QSignalSpy countSpy(menu, &QQuickMenu::countChanged); |
128 | QVERIFY(countSpy.isValid()); |
129 | |
130 | menu->addItem(item: new QQuickItem); |
131 | QCOMPARE(menu->count(), 1); |
132 | QCOMPARE(countSpy.count(), 1); |
133 | |
134 | menu->insertItem(index: 0, item: new QQuickItem); |
135 | QCOMPARE(menu->count(), 2); |
136 | QCOMPARE(countSpy.count(), 2); |
137 | |
138 | menu->removeItem(item: menu->itemAt(index: 1)); |
139 | QCOMPARE(menu->count(), 1); |
140 | QCOMPARE(countSpy.count(), 3); |
141 | |
142 | QScopedPointer<QQuickItem> item(menu->takeItem(index: 0)); |
143 | QVERIFY(item); |
144 | QCOMPARE(menu->count(), 0); |
145 | QCOMPARE(countSpy.count(), 4); |
146 | } |
147 | |
148 | void tst_QQuickMenu::() |
149 | { |
150 | if ((QGuiApplication::platformName() == QLatin1String("offscreen" )) |
151 | || (QGuiApplication::platformName() == QLatin1String("minimal" ))) |
152 | QSKIP("Mouse hovering not functional on offscreen/minimal platforms" ); |
153 | |
154 | QQuickApplicationHelper helper(this, QLatin1String("applicationwindow.qml" )); |
155 | QVERIFY2(helper.ready, helper.failureMessage()); |
156 | |
157 | QQuickApplicationWindow *window = helper.appWindow; |
158 | centerOnScreen(window); |
159 | moveMouseAway(window); |
160 | window->show(); |
161 | QVERIFY(QTest::qWaitForWindowActive(window)); |
162 | |
163 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu*>(); |
164 | menu->open(); |
165 | QVERIFY(menu->isVisible()); |
166 | QVERIFY(window->overlay()->childItems().contains(menu->contentItem()->parentItem())); |
167 | QTRY_VERIFY(menu->isOpened()); |
168 | |
169 | QQuickItem *firstItem = menu->itemAt(index: 0); |
170 | QSignalSpy clickedSpy(firstItem, SIGNAL(clicked())); |
171 | QSignalSpy triggeredSpy(firstItem, SIGNAL(triggered())); |
172 | QSignalSpy visibleSpy(menu, SIGNAL(visibleChanged())); |
173 | |
174 | // Ensure that presses cause the current index to change, |
175 | // so that the highlight acts as a way of illustrating press state. |
176 | QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, |
177 | pos: QPoint(menu->leftPadding() + firstItem->width() / 2, menu->topPadding() + firstItem->height() / 2)); |
178 | QVERIFY(firstItem->hasActiveFocus()); |
179 | QCOMPARE(menu->currentIndex(), 0); |
180 | QCOMPARE(menu->contentItem()->property("currentIndex" ), QVariant(0)); |
181 | QVERIFY(menu->isVisible()); |
182 | |
183 | QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, |
184 | pos: QPoint(menu->leftPadding() + firstItem->width() / 2, menu->topPadding() + firstItem->height() / 2)); |
185 | QCOMPARE(clickedSpy.count(), 1); |
186 | QCOMPARE(triggeredSpy.count(), 1); |
187 | QTRY_COMPARE(visibleSpy.count(), 1); |
188 | QVERIFY(!menu->isVisible()); |
189 | QVERIFY(!window->overlay()->childItems().contains(menu->contentItem())); |
190 | QCOMPARE(menu->currentIndex(), -1); |
191 | QCOMPARE(menu->contentItem()->property("currentIndex" ), QVariant(-1)); |
192 | |
193 | menu->open(); |
194 | QCOMPARE(visibleSpy.count(), 2); |
195 | QVERIFY(menu->isVisible()); |
196 | QVERIFY(window->overlay()->childItems().contains(menu->contentItem()->parentItem())); |
197 | QTRY_VERIFY(menu->isOpened()); |
198 | |
199 | // Ensure that we have enough space to click outside of the menu. |
200 | QVERIFY(window->width() > menu->contentItem()->width()); |
201 | QVERIFY(window->height() > menu->contentItem()->height()); |
202 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, |
203 | pos: QPoint(menu->contentItem()->width() + 1, menu->contentItem()->height() + 1)); |
204 | QTRY_COMPARE(visibleSpy.count(), 3); |
205 | QVERIFY(!menu->isVisible()); |
206 | QVERIFY(!window->overlay()->childItems().contains(menu->contentItem()->parentItem())); |
207 | |
208 | menu->open(); |
209 | QCOMPARE(visibleSpy.count(), 4); |
210 | QVERIFY(menu->isVisible()); |
211 | QVERIFY(window->overlay()->childItems().contains(menu->contentItem()->parentItem())); |
212 | QTRY_VERIFY(menu->isOpened()); |
213 | |
214 | // Hover-highlight through the menu items one by one |
215 | QQuickItem *prevHoverItem = nullptr; |
216 | QQuickItem *listView = menu->contentItem(); |
217 | for (int y = menu->topPadding(); y < listView->height(); ++y) { |
218 | QQuickItem *hoverItem = nullptr; |
219 | QVERIFY(QMetaObject::invokeMethod(listView, "itemAt" , Q_RETURN_ARG(QQuickItem *, hoverItem), Q_ARG(qreal, 0), Q_ARG(qreal, listView->property("contentY" ).toReal() + y))); |
220 | if (!hoverItem || !hoverItem->isVisible() || hoverItem == prevHoverItem) |
221 | continue; |
222 | QTest::mouseMove(window, pos: QPoint( |
223 | menu->leftPadding() + hoverItem->x() + hoverItem->width() / 2, |
224 | menu->topPadding() + hoverItem->y() + hoverItem->height() / 2)); |
225 | QTRY_VERIFY(hoverItem->property("highlighted" ).toBool()); |
226 | if (prevHoverItem) |
227 | QVERIFY(!prevHoverItem->property("highlighted" ).toBool()); |
228 | prevHoverItem = hoverItem; |
229 | } |
230 | |
231 | // Try pressing within the menu and releasing outside of it; it should close. |
232 | // TODO: won't work until QQuickPopup::releasedOutside() actually gets emitted |
233 | // QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(firstItem->width() / 2, firstItem->height() / 2)); |
234 | // QVERIFY(firstItem->hasActiveFocus()); |
235 | // QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(0)); |
236 | // QVERIFY(menu->isVisible()); |
237 | // QCOMPARE(triggeredSpy.count(), 1); |
238 | |
239 | // QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(menu->contentItem()->width() + 1, firstItem->height() / 2)); |
240 | // QCOMPARE(clickedSpy.count(), 1); |
241 | // QCOMPARE(triggeredSpy.count(), 1); |
242 | // QCOMPARE(visibleSpy.count(), 5); |
243 | // QVERIFY(!menu->isVisible()); |
244 | // QVERIFY(!window->overlay()->childItems().contains(menu->contentItem())); |
245 | // QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1)); |
246 | } |
247 | |
248 | void tst_QQuickMenu::pressAndHold() |
249 | { |
250 | QQuickApplicationHelper helper(this, QLatin1String("pressAndHold.qml" )); |
251 | QVERIFY2(helper.ready, helper.failureMessage()); |
252 | |
253 | QQuickWindow *window = helper.window; |
254 | window->show(); |
255 | QVERIFY(QTest::qWaitForWindowActive(window)); |
256 | |
257 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu *>(); |
258 | QVERIFY(menu); |
259 | |
260 | QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(1, 1)); |
261 | QTRY_VERIFY(menu->isVisible()); |
262 | |
263 | QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(1, 1)); |
264 | QVERIFY(menu->isVisible()); |
265 | |
266 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(1, 1)); |
267 | QTRY_VERIFY(!menu->isVisible()); |
268 | } |
269 | |
270 | void tst_QQuickMenu::() |
271 | { |
272 | if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) |
273 | QSKIP("This platform only allows tab focus for text controls" ); |
274 | |
275 | QQuickApplicationHelper helper(this, QLatin1String("applicationwindow.qml" )); |
276 | QVERIFY2(helper.ready, helper.failureMessage()); |
277 | |
278 | QQuickApplicationWindow *window = helper.appWindow; |
279 | centerOnScreen(window); |
280 | moveMouseAway(window); |
281 | window->show(); |
282 | window->requestActivate(); |
283 | QVERIFY(QTest::qWaitForWindowActive(window)); |
284 | QVERIFY(QGuiApplication::focusWindow() == window); |
285 | |
286 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu*>(); |
287 | QCOMPARE(menu->currentIndex(), -1); |
288 | QCOMPARE(menu->contentItem()->property("currentIndex" ), QVariant(-1)); |
289 | |
290 | QQuickMenuItem *firstItem = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 0)); |
291 | QVERIFY(firstItem); |
292 | QSignalSpy visibleSpy(menu, SIGNAL(visibleChanged())); |
293 | |
294 | menu->setFocus(true); |
295 | menu->open(); |
296 | QCOMPARE(visibleSpy.count(), 1); |
297 | QVERIFY(menu->isVisible()); |
298 | QVERIFY(window->overlay()->childItems().contains(menu->contentItem()->parentItem())); |
299 | QTRY_VERIFY(menu->isOpened()); |
300 | QVERIFY(!firstItem->hasActiveFocus()); |
301 | QVERIFY(!firstItem->property("highlighted" ).toBool()); |
302 | QCOMPARE(menu->currentIndex(), -1); |
303 | QCOMPARE(menu->contentItem()->property("currentIndex" ), QVariant(-1)); |
304 | |
305 | QTest::keyClick(window, key: Qt::Key_Tab); |
306 | QVERIFY(firstItem->hasActiveFocus()); |
307 | QVERIFY(firstItem->hasVisualFocus()); |
308 | QVERIFY(firstItem->isHighlighted()); |
309 | QCOMPARE(firstItem->focusReason(), Qt::TabFocusReason); |
310 | QCOMPARE(menu->currentIndex(), 0); |
311 | QCOMPARE(menu->contentItem()->property("currentIndex" ), QVariant(0)); |
312 | |
313 | QQuickMenuItem *secondItem = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 1)); |
314 | QVERIFY(secondItem); |
315 | QTest::keyClick(window, key: Qt::Key_Tab); |
316 | QVERIFY(!firstItem->hasActiveFocus()); |
317 | QVERIFY(!firstItem->hasVisualFocus()); |
318 | QVERIFY(!firstItem->isHighlighted()); |
319 | QVERIFY(secondItem->hasActiveFocus()); |
320 | QVERIFY(secondItem->hasVisualFocus()); |
321 | QVERIFY(secondItem->isHighlighted()); |
322 | QCOMPARE(secondItem->focusReason(), Qt::TabFocusReason); |
323 | QCOMPARE(menu->currentIndex(), 1); |
324 | QCOMPARE(menu->contentItem()->property("currentIndex" ), QVariant(1)); |
325 | |
326 | QSignalSpy secondTriggeredSpy(secondItem, SIGNAL(triggered())); |
327 | QTest::keyClick(window, key: Qt::Key_Space); |
328 | QCOMPARE(secondTriggeredSpy.count(), 1); |
329 | QTRY_COMPARE(visibleSpy.count(), 2); |
330 | QVERIFY(!menu->isVisible()); |
331 | QVERIFY(!window->overlay()->childItems().contains(menu->contentItem())); |
332 | QVERIFY(!firstItem->hasActiveFocus()); |
333 | QVERIFY(!firstItem->hasVisualFocus()); |
334 | QVERIFY(!firstItem->isHighlighted()); |
335 | QVERIFY(!secondItem->hasActiveFocus()); |
336 | QVERIFY(!secondItem->hasVisualFocus()); |
337 | QVERIFY(!secondItem->isHighlighted()); |
338 | QCOMPARE(menu->currentIndex(), -1); |
339 | QCOMPARE(menu->contentItem()->property("currentIndex" ), QVariant(-1)); |
340 | |
341 | // Enter/return should also work. |
342 | // Open the menu. |
343 | menu->open(); |
344 | QCOMPARE(visibleSpy.count(), 3); |
345 | QVERIFY(menu->isVisible()); |
346 | QTRY_VERIFY(menu->isOpened()); |
347 | // Give the first item focus. |
348 | QTest::keyClick(window, key: Qt::Key_Tab); |
349 | QVERIFY(firstItem->hasActiveFocus()); |
350 | QVERIFY(firstItem->hasVisualFocus()); |
351 | QVERIFY(firstItem->isHighlighted()); |
352 | QCOMPARE(firstItem->focusReason(), Qt::TabFocusReason); |
353 | QCOMPARE(menu->currentIndex(), 0); |
354 | QCOMPARE(menu->contentItem()->property("currentIndex" ), QVariant(0)); |
355 | // Press enter. |
356 | QSignalSpy firstTriggeredSpy(firstItem, SIGNAL(triggered())); |
357 | QTest::keyClick(window, key: Qt::Key_Return); |
358 | QCOMPARE(firstTriggeredSpy.count(), 1); |
359 | QTRY_COMPARE(visibleSpy.count(), 4); |
360 | QVERIFY(!menu->isVisible()); |
361 | QVERIFY(!window->overlay()->childItems().contains(menu->contentItem())); |
362 | QVERIFY(!firstItem->hasActiveFocus()); |
363 | QVERIFY(!firstItem->hasVisualFocus()); |
364 | QVERIFY(!firstItem->isHighlighted()); |
365 | QVERIFY(!secondItem->hasActiveFocus()); |
366 | QVERIFY(!secondItem->hasVisualFocus()); |
367 | QVERIFY(!secondItem->isHighlighted()); |
368 | QCOMPARE(menu->currentIndex(), -1); |
369 | QCOMPARE(menu->contentItem()->property("currentIndex" ), QVariant(-1)); |
370 | |
371 | menu->open(); |
372 | QCOMPARE(visibleSpy.count(), 5); |
373 | QVERIFY(menu->isVisible()); |
374 | QVERIFY(window->overlay()->childItems().contains(menu->contentItem()->parentItem())); |
375 | QTRY_VERIFY(menu->isOpened()); |
376 | QVERIFY(!firstItem->hasActiveFocus()); |
377 | QVERIFY(!firstItem->hasVisualFocus()); |
378 | QVERIFY(!firstItem->isHighlighted()); |
379 | QVERIFY(!secondItem->hasActiveFocus()); |
380 | QVERIFY(!secondItem->hasVisualFocus()); |
381 | QVERIFY(!secondItem->isHighlighted()); |
382 | QCOMPARE(menu->currentIndex(), -1); |
383 | QCOMPARE(menu->contentItem()->property("currentIndex" ), QVariant(-1)); |
384 | |
385 | QTest::keyClick(window, key: Qt::Key_Down); |
386 | QVERIFY(firstItem->hasActiveFocus()); |
387 | QVERIFY(firstItem->hasVisualFocus()); |
388 | QVERIFY(firstItem->isHighlighted()); |
389 | QCOMPARE(firstItem->focusReason(), Qt::TabFocusReason); |
390 | |
391 | QTest::keyClick(window, key: Qt::Key_Down); |
392 | QVERIFY(secondItem->hasActiveFocus()); |
393 | QVERIFY(secondItem->hasVisualFocus()); |
394 | QVERIFY(secondItem->isHighlighted()); |
395 | QCOMPARE(secondItem->focusReason(), Qt::TabFocusReason); |
396 | |
397 | QTest::keyClick(window, key: Qt::Key_Down); |
398 | QQuickMenuItem *thirdItem = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 2)); |
399 | QVERIFY(thirdItem); |
400 | QVERIFY(!firstItem->hasActiveFocus()); |
401 | QVERIFY(!firstItem->hasVisualFocus()); |
402 | QVERIFY(!firstItem->isHighlighted()); |
403 | QVERIFY(!secondItem->hasActiveFocus()); |
404 | QVERIFY(!secondItem->hasVisualFocus()); |
405 | QVERIFY(!secondItem->isHighlighted()); |
406 | QVERIFY(thirdItem->hasActiveFocus()); |
407 | QVERIFY(thirdItem->hasVisualFocus()); |
408 | QVERIFY(thirdItem->isHighlighted()); |
409 | QCOMPARE(thirdItem->focusReason(), Qt::TabFocusReason); |
410 | |
411 | // Key navigation shouldn't wrap by default. |
412 | QTest::keyClick(window, key: Qt::Key_Down); |
413 | QVERIFY(!firstItem->hasActiveFocus()); |
414 | QVERIFY(!firstItem->hasVisualFocus()); |
415 | QVERIFY(!firstItem->isHighlighted()); |
416 | QVERIFY(!secondItem->hasActiveFocus()); |
417 | QVERIFY(!secondItem->hasVisualFocus()); |
418 | QVERIFY(!secondItem->isHighlighted()); |
419 | QVERIFY(thirdItem->hasActiveFocus()); |
420 | QVERIFY(thirdItem->hasVisualFocus()); |
421 | QVERIFY(thirdItem->isHighlighted()); |
422 | QCOMPARE(thirdItem->focusReason(), Qt::TabFocusReason); |
423 | |
424 | QTest::keyClick(window, key: Qt::Key_Up); |
425 | QVERIFY(!firstItem->hasActiveFocus()); |
426 | QVERIFY(!firstItem->hasVisualFocus()); |
427 | QVERIFY(!firstItem->isHighlighted()); |
428 | QVERIFY(secondItem->hasActiveFocus()); |
429 | QVERIFY(secondItem->hasVisualFocus()); |
430 | QVERIFY(secondItem->isHighlighted()); |
431 | QCOMPARE(secondItem->focusReason(), Qt::BacktabFocusReason); |
432 | QVERIFY(!thirdItem->hasActiveFocus()); |
433 | QVERIFY(!thirdItem->hasVisualFocus()); |
434 | QVERIFY(!thirdItem->isHighlighted()); |
435 | |
436 | QTest::keyClick(window, key: Qt::Key_Backtab); |
437 | QVERIFY(firstItem->hasActiveFocus()); |
438 | QVERIFY(firstItem->hasVisualFocus()); |
439 | QVERIFY(firstItem->isHighlighted()); |
440 | QCOMPARE(firstItem->focusReason(), Qt::BacktabFocusReason); |
441 | QVERIFY(!secondItem->hasActiveFocus()); |
442 | QVERIFY(!secondItem->hasVisualFocus()); |
443 | QVERIFY(!secondItem->isHighlighted()); |
444 | QVERIFY(!thirdItem->hasActiveFocus()); |
445 | QVERIFY(!thirdItem->hasVisualFocus()); |
446 | QVERIFY(!thirdItem->isHighlighted()); |
447 | |
448 | QTest::keyClick(window, key: Qt::Key_Escape); |
449 | QTRY_COMPARE(visibleSpy.count(), 6); |
450 | QVERIFY(!menu->isVisible()); |
451 | } |
452 | |
453 | // QTBUG-70181 |
454 | void tst_QQuickMenu::() |
455 | { |
456 | if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) |
457 | QSKIP("This platform only allows tab focus for text controls" ); |
458 | |
459 | QQuickApplicationHelper helper(this, QLatin1String("disabledMenuItemKeyNavigation.qml" )); |
460 | QVERIFY2(helper.ready, helper.failureMessage()); |
461 | |
462 | QQuickApplicationWindow *window = helper.appWindow; |
463 | centerOnScreen(window); |
464 | moveMouseAway(window); |
465 | window->show(); |
466 | window->requestActivate(); |
467 | QVERIFY(QTest::qWaitForWindowActive(window)); |
468 | QVERIFY(QGuiApplication::focusWindow() == window); |
469 | |
470 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu*>(); |
471 | QCOMPARE(menu->currentIndex(), -1); |
472 | QCOMPARE(menu->contentItem()->property("currentIndex" ), QVariant(-1)); |
473 | |
474 | QQuickMenuItem *firstItem = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 0)); |
475 | QVERIFY(firstItem); |
476 | |
477 | QQuickMenuItem *secondItem = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 1)); |
478 | QVERIFY(secondItem); |
479 | |
480 | QQuickMenuItem *thirdItem = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 2)); |
481 | QVERIFY(thirdItem); |
482 | |
483 | menu->setFocus(true); |
484 | menu->open(); |
485 | QVERIFY(menu->isVisible()); |
486 | QTRY_VERIFY(menu->isOpened()); |
487 | QVERIFY(!firstItem->hasActiveFocus()); |
488 | QVERIFY(!firstItem->property("highlighted" ).toBool()); |
489 | QCOMPARE(menu->currentIndex(), -1); |
490 | |
491 | QTest::keyClick(window, key: Qt::Key_Tab); |
492 | QVERIFY(firstItem->hasActiveFocus()); |
493 | QVERIFY(firstItem->hasVisualFocus()); |
494 | QVERIFY(firstItem->isHighlighted()); |
495 | QCOMPARE(firstItem->focusReason(), Qt::TabFocusReason); |
496 | QCOMPARE(menu->currentIndex(), 0); |
497 | |
498 | // Shouldn't be possible to give focus to a disabled menu item. |
499 | QTest::keyClick(window, key: Qt::Key_Down); |
500 | QVERIFY(!secondItem->hasActiveFocus()); |
501 | QVERIFY(!secondItem->hasVisualFocus()); |
502 | QVERIFY(!secondItem->isHighlighted()); |
503 | QVERIFY(thirdItem->hasActiveFocus()); |
504 | QVERIFY(thirdItem->hasVisualFocus()); |
505 | QVERIFY(thirdItem->isHighlighted()); |
506 | QCOMPARE(thirdItem->focusReason(), Qt::TabFocusReason); |
507 | |
508 | QTest::keyClick(window, key: Qt::Key_Up); |
509 | QVERIFY(firstItem->hasActiveFocus()); |
510 | QVERIFY(firstItem->hasVisualFocus()); |
511 | QVERIFY(firstItem->isHighlighted()); |
512 | QCOMPARE(firstItem->focusReason(), Qt::BacktabFocusReason); |
513 | |
514 | QTest::keyClick(window, key: Qt::Key_Escape); |
515 | QTRY_VERIFY(!menu->isVisible()); |
516 | } |
517 | |
518 | void tst_QQuickMenu::() |
519 | { |
520 | #ifdef Q_OS_MACOS |
521 | QSKIP("Mnemonics are not used on macOS" ); |
522 | #endif |
523 | |
524 | QQuickApplicationHelper helper(this, QLatin1String("mnemonics.qml" )); |
525 | QVERIFY2(helper.ready, helper.failureMessage()); |
526 | |
527 | QQuickWindow *window = helper.window; |
528 | window->show(); |
529 | window->requestActivate(); |
530 | QVERIFY(QTest::qWaitForWindowActive(window)); |
531 | |
532 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu *>(); |
533 | QQuickAction *action = window->property(name: "action" ).value<QQuickAction *>(); |
534 | QQuickMenuItem * = window->property(name: "menuItem" ).value<QQuickMenuItem *>(); |
535 | QQuickMenu * = window->property(name: "subMenu" ).value<QQuickMenu *>(); |
536 | QQuickMenuItem * = window->property(name: "subMenuItem" ).value<QQuickMenuItem *>(); |
537 | QVERIFY(menu && action && menuItem && subMenu && subMenuItem); |
538 | |
539 | menu->open(); |
540 | QTRY_VERIFY(menu->isOpened()); |
541 | |
542 | QSignalSpy actionSpy(action, &QQuickAction::triggered); |
543 | QVERIFY(actionSpy.isValid()); |
544 | QTest::keyClick(window, key: Qt::Key_A, modifier: Qt::AltModifier); // "&Action" |
545 | QCOMPARE(actionSpy.count(), 1); |
546 | |
547 | menu->open(); |
548 | QTRY_VERIFY(menu->isOpened()); |
549 | |
550 | QSignalSpy (menuItem, &QQuickMenuItem::triggered); |
551 | QVERIFY(menuItemSpy.isValid()); |
552 | QTest::keyClick(window, key: Qt::Key_I, modifier: Qt::AltModifier); // "Menu &Item" |
553 | QCOMPARE(menuItemSpy.count(), 1); |
554 | |
555 | menu->open(); |
556 | QTRY_VERIFY(menu->isOpened()); |
557 | |
558 | QTest::keyClick(window, key: Qt::Key_M, modifier: Qt::AltModifier); // "Sub &Menu" |
559 | QTRY_VERIFY(subMenu->isOpened()); |
560 | |
561 | QSignalSpy (subMenuItem, &QQuickMenuItem::triggered); |
562 | QVERIFY(subMenuItemSpy.isValid()); |
563 | QTest::keyClick(window, key: Qt::Key_S, modifier: Qt::AltModifier); // "&Sub Menu Item" |
564 | QCOMPARE(subMenuItemSpy.count(), 1); |
565 | } |
566 | |
567 | void tst_QQuickMenu::() |
568 | { |
569 | if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) |
570 | QSKIP("This platform only allows tab focus for text controls" ); |
571 | |
572 | QQuickApplicationHelper helper(this, QLatin1String("applicationwindow.qml" )); |
573 | QVERIFY2(helper.ready, helper.failureMessage()); |
574 | |
575 | QQuickApplicationWindow *window = helper.appWindow; |
576 | window->show(); |
577 | window->requestActivate(); |
578 | QVERIFY(QTest::qWaitForWindowActive(window)); |
579 | QVERIFY(QGuiApplication::focusWindow() == window); |
580 | |
581 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu*>(); |
582 | QQuickButton * = window->property(name: "menuButton" ).value<QQuickButton*>(); |
583 | QSignalSpy visibleSpy(menu, SIGNAL(visibleChanged())); |
584 | |
585 | menuButton->setVisible(true); |
586 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, |
587 | pos: menuButton->mapToScene(point: QPointF(menuButton->width() / 2, menuButton->height() / 2)).toPoint()); |
588 | QCOMPARE(visibleSpy.count(), 1); |
589 | QVERIFY(menu->isVisible()); |
590 | QTRY_VERIFY(menu->isOpened()); |
591 | |
592 | QTest::keyClick(window, key: Qt::Key_Tab); |
593 | QQuickItem *firstItem = menu->itemAt(index: 0); |
594 | QVERIFY(firstItem->hasActiveFocus()); |
595 | } |
596 | |
597 | void tst_QQuickMenu::() |
598 | { |
599 | QQuickApplicationHelper helper(this, QLatin1String("addItem.qml" )); |
600 | QVERIFY2(helper.ready, helper.failureMessage()); |
601 | QQuickApplicationWindow *window = helper.appWindow; |
602 | window->show(); |
603 | QVERIFY(QTest::qWaitForWindowActive(window)); |
604 | |
605 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu*>(); |
606 | QVERIFY(menu); |
607 | menu->open(); |
608 | QVERIFY(menu->isVisible()); |
609 | |
610 | QQuickItem * = menu->itemAt(index: 0); |
611 | QVERIFY(menuItem); |
612 | QTRY_VERIFY(!QQuickItemPrivate::get(menuItem)->culled); // QTBUG-53262 |
613 | |
614 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, |
615 | pos: menuItem->mapToScene(point: QPointF(menuItem->width() / 2, menuItem->height() / 2)).toPoint()); |
616 | QTRY_VERIFY(!menu->isVisible()); |
617 | } |
618 | |
619 | void tst_QQuickMenu::() |
620 | { |
621 | QQuickApplicationHelper helper(this, QLatin1String("menuSeparator.qml" )); |
622 | QVERIFY2(helper.ready, helper.failureMessage()); |
623 | QQuickWindow *window = helper.window; |
624 | centerOnScreen(window); |
625 | moveMouseAway(window); |
626 | window->show(); |
627 | QVERIFY(QTest::qWaitForWindowActive(window)); |
628 | |
629 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu*>(); |
630 | QVERIFY(menu); |
631 | menu->open(); |
632 | QVERIFY(menu->isVisible()); |
633 | |
634 | QQuickMenuItem * = qobject_cast<QQuickMenuItem*>(object: menu->itemAt(index: 0)); |
635 | QVERIFY(newMenuItem); |
636 | QCOMPARE(newMenuItem->text(), QStringLiteral("New" )); |
637 | |
638 | QQuickMenuSeparator * = qobject_cast<QQuickMenuSeparator*>(object: menu->itemAt(index: 1)); |
639 | QVERIFY(menuSeparator); |
640 | |
641 | QQuickMenuItem * = qobject_cast<QQuickMenuItem*>(object: menu->itemAt(index: 2)); |
642 | QVERIFY(saveMenuItem); |
643 | QCOMPARE(saveMenuItem->text(), QStringLiteral("Save" )); |
644 | QTRY_VERIFY(!QQuickItemPrivate::get(saveMenuItem)->culled); // QTBUG-53262 |
645 | |
646 | // Clicking on items should still close the menu. |
647 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, |
648 | pos: newMenuItem->mapToScene(point: QPointF(newMenuItem->width() / 2, newMenuItem->height() / 2)).toPoint()); |
649 | QTRY_VERIFY(!menu->isVisible()); |
650 | |
651 | menu->open(); |
652 | QVERIFY(menu->isVisible()); |
653 | |
654 | // Clicking on a separator shouldn't close the menu. |
655 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, |
656 | pos: menuSeparator->mapToScene(point: QPointF(menuSeparator->width() / 2, menuSeparator->height() / 2)).toPoint()); |
657 | QVERIFY(menu->isVisible()); |
658 | |
659 | // Clicking on items should still close the menu. |
660 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, |
661 | pos: saveMenuItem->mapToScene(point: QPointF(saveMenuItem->width() / 2, saveMenuItem->height() / 2)).toPoint()); |
662 | QTRY_VERIFY(!menu->isVisible()); |
663 | |
664 | moveMouseAway(window); |
665 | |
666 | menu->open(); |
667 | QVERIFY(menu->isVisible()); |
668 | QTRY_VERIFY(menu->isOpened()); |
669 | |
670 | // Key navigation skips separators |
671 | QTest::keyClick(window, key: Qt::Key_Down); |
672 | QVERIFY(newMenuItem->hasActiveFocus()); |
673 | QVERIFY(newMenuItem->hasVisualFocus()); |
674 | QCOMPARE(newMenuItem->focusReason(), Qt::TabFocusReason); |
675 | |
676 | QTest::keyClick(window, key: Qt::Key_Down); |
677 | QVERIFY(saveMenuItem->hasActiveFocus()); |
678 | QVERIFY(saveMenuItem->hasVisualFocus()); |
679 | QCOMPARE(saveMenuItem->focusReason(), Qt::TabFocusReason); |
680 | |
681 | QTest::keyClick(window, key: Qt::Key_Down); |
682 | QVERIFY(saveMenuItem->hasActiveFocus()); |
683 | QVERIFY(saveMenuItem->hasVisualFocus()); |
684 | QCOMPARE(saveMenuItem->focusReason(), Qt::TabFocusReason); |
685 | |
686 | QTest::keyClick(window, key: Qt::Key_Up); |
687 | QVERIFY(newMenuItem->hasActiveFocus()); |
688 | QVERIFY(newMenuItem->hasVisualFocus()); |
689 | QCOMPARE(newMenuItem->focusReason(), Qt::BacktabFocusReason); |
690 | |
691 | QTest::keyClick(window, key: Qt::Key_Up); |
692 | QVERIFY(newMenuItem->hasActiveFocus()); |
693 | QVERIFY(newMenuItem->hasVisualFocus()); |
694 | QCOMPARE(newMenuItem->focusReason(), Qt::BacktabFocusReason); |
695 | } |
696 | |
697 | void tst_QQuickMenu::() |
698 | { |
699 | QQuickApplicationHelper helper(this, QLatin1String("repeater.qml" )); |
700 | QVERIFY2(helper.ready, helper.failureMessage()); |
701 | QQuickWindow *window = helper.window; |
702 | window->show(); |
703 | QVERIFY(QTest::qWaitForWindowActive(window)); |
704 | |
705 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu*>(); |
706 | QVERIFY(menu); |
707 | menu->open(); |
708 | QVERIFY(menu->isVisible()); |
709 | |
710 | QObject *repeater = window->property(name: "repeater" ).value<QObject*>(); |
711 | QVERIFY(repeater); |
712 | |
713 | int count = repeater->property(name: "count" ).toInt(); |
714 | QCOMPARE(count, 5); |
715 | |
716 | for (int i = 0; i < count; ++i) { |
717 | QQuickItem *item = menu->itemAt(index: i); |
718 | QVERIFY(item); |
719 | QCOMPARE(item->property("idx" ).toInt(), i); |
720 | |
721 | QQuickItem *repeaterItem = nullptr; |
722 | QVERIFY(QMetaObject::invokeMethod(repeater, "itemAt" , Q_RETURN_ARG(QQuickItem*, repeaterItem), Q_ARG(int, i))); |
723 | QCOMPARE(item, repeaterItem); |
724 | } |
725 | |
726 | repeater->setProperty(name: "model" , value: 3); |
727 | |
728 | count = repeater->property(name: "count" ).toInt(); |
729 | QCOMPARE(count, 3); |
730 | |
731 | for (int i = 0; i < count; ++i) { |
732 | QQuickItem *item = menu->itemAt(index: i); |
733 | QVERIFY(item); |
734 | QCOMPARE(item->property("idx" ).toInt(), i); |
735 | |
736 | QQuickItem *repeaterItem = nullptr; |
737 | QVERIFY(QMetaObject::invokeMethod(repeater, "itemAt" , Q_RETURN_ARG(QQuickItem*, repeaterItem), Q_ARG(int, i))); |
738 | QCOMPARE(item, repeaterItem); |
739 | } |
740 | } |
741 | |
742 | void tst_QQuickMenu::() |
743 | { |
744 | QQuickApplicationHelper helper(this, QLatin1String("order.qml" )); |
745 | QVERIFY2(helper.ready, helper.failureMessage()); |
746 | QQuickWindow *window = helper.window; |
747 | window->show(); |
748 | QVERIFY(QTest::qWaitForWindowActive(window)); |
749 | |
750 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu*>(); |
751 | QVERIFY(menu); |
752 | menu->open(); |
753 | QVERIFY(menu->isVisible()); |
754 | |
755 | const QStringList texts = {"dynamic_0" , "static_1" , "repeated_2" , "repeated_3" , "static_4" , "dynamic_5" , "dynamic_6" }; |
756 | |
757 | for (int i = 0; i < texts.count(); ++i) { |
758 | QQuickItem *item = menu->itemAt(index: i); |
759 | QVERIFY(item); |
760 | QCOMPARE(item->property("text" ).toString(), texts.at(i)); |
761 | } |
762 | } |
763 | |
764 | void tst_QQuickMenu::() |
765 | { |
766 | QQuickApplicationHelper helper(this, QLatin1String("popup.qml" )); |
767 | QVERIFY2(helper.ready, helper.failureMessage()); |
768 | QQuickApplicationWindow *window = helper.appWindow; |
769 | centerOnScreen(window); |
770 | moveMouseAway(window); |
771 | window->show(); |
772 | QVERIFY(QTest::qWaitForWindowActive(window)); |
773 | |
774 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu *>(); |
775 | QVERIFY(menu); |
776 | |
777 | QQuickMenuItem * = window->property(name: "menuItem1" ).value<QQuickMenuItem *>(); |
778 | QVERIFY(menuItem1); |
779 | |
780 | QQuickMenuItem * = window->property(name: "menuItem2" ).value<QQuickMenuItem *>(); |
781 | QVERIFY(menuItem2); |
782 | |
783 | QQuickMenuItem * = window->property(name: "menuItem3" ).value<QQuickMenuItem *>(); |
784 | QVERIFY(menuItem3); |
785 | |
786 | QQuickItem *button = window->property(name: "button" ).value<QQuickItem *>(); |
787 | QVERIFY(button); |
788 | |
789 | #if QT_CONFIG(cursor) |
790 | QPoint oldCursorPos = QCursor::pos(); |
791 | QPoint cursorPos = window->mapToGlobal(pos: QPoint(11, 22)); |
792 | QCursor::setPos(cursorPos); |
793 | QTRY_COMPARE(QCursor::pos(), cursorPos); |
794 | |
795 | QVERIFY(QMetaObject::invokeMethod(window, "popupAtCursor" )); |
796 | QCOMPARE(menu->parentItem(), window->contentItem()); |
797 | QCOMPARE(menu->currentIndex(), -1); |
798 | QCOMPARE(menu->contentItem()->property("currentIndex" ).toInt(), -1); |
799 | const qreal elevenOrLeftMargin = qMax(a: qreal(11), b: menu->leftMargin()); |
800 | const qreal twentyTwoOrTopMargin = qMax(a: qreal(22), b: menu->topMargin()); |
801 | // If the Menu has large margins, it may be moved to stay within them. |
802 | // QTBUG-75503: QTRY_COMPARE doesn't use qFuzzyCompare() in all cases, |
803 | // meaning a lot of these comparisons could trigger a 10 second wait; |
804 | // use QTRY_VERIFY and qFuzzyCompare instead. |
805 | QTRY_VERIFY(qFuzzyCompare(menu->x(), elevenOrLeftMargin)); |
806 | QTRY_VERIFY(qFuzzyCompare(menu->y(), twentyTwoOrTopMargin)); |
807 | menu->close(); |
808 | |
809 | QVERIFY(QMetaObject::invokeMethod(window, "popupAtPos" , Q_ARG(QVariant, QPointF(33, 44)))); |
810 | QCOMPARE(menu->parentItem(), window->contentItem()); |
811 | QCOMPARE(menu->currentIndex(), -1); |
812 | QCOMPARE(menu->contentItem()->property("currentIndex" ).toInt(), -1); |
813 | QTRY_VERIFY(qFuzzyCompare(menu->x(), 33)); |
814 | QTRY_VERIFY(qFuzzyCompare(menu->y(), 44)); |
815 | menu->close(); |
816 | |
817 | QVERIFY(QMetaObject::invokeMethod(window, "popupAtCoord" , Q_ARG(QVariant, 55), Q_ARG(QVariant, 66))); |
818 | QCOMPARE(menu->parentItem(), window->contentItem()); |
819 | QCOMPARE(menu->currentIndex(), -1); |
820 | QCOMPARE(menu->contentItem()->property("currentIndex" ).toInt(), -1); |
821 | QTRY_VERIFY(qFuzzyCompare(menu->x(), 55)); |
822 | QTRY_VERIFY(qFuzzyCompare(menu->y(), 66)); |
823 | menu->close(); |
824 | |
825 | menu->setParentItem(nullptr); |
826 | QVERIFY(QMetaObject::invokeMethod(window, "popupAtParentCursor" , Q_ARG(QVariant, QVariant::fromValue(button)))); |
827 | QCOMPARE(menu->parentItem(), button); |
828 | QCOMPARE(menu->currentIndex(), -1); |
829 | QCOMPARE(menu->contentItem()->property("currentIndex" ).toInt(), -1); |
830 | QTRY_VERIFY(qFuzzyCompare(menu->x(), button->mapFromScene(QPointF(elevenOrLeftMargin, twentyTwoOrTopMargin)).x())); |
831 | QTRY_VERIFY(qFuzzyCompare(menu->y(), button->mapFromScene(QPointF(elevenOrLeftMargin, twentyTwoOrTopMargin)).y())); |
832 | menu->close(); |
833 | |
834 | menu->setParentItem(nullptr); |
835 | QVERIFY(QMetaObject::invokeMethod(window, "popupAtParentPos" , Q_ARG(QVariant, QVariant::fromValue(button)), Q_ARG(QVariant, QPointF(-11, -22)))); |
836 | QCOMPARE(menu->parentItem(), button); |
837 | QCOMPARE(menu->currentIndex(), -1); |
838 | QCOMPARE(menu->contentItem()->property("currentIndex" ).toInt(), -1); |
839 | // Don't need to worry about margins here because we're opening close |
840 | // to the center of the window. |
841 | QTRY_VERIFY(qFuzzyCompare(menu->x(), -11)); |
842 | QTRY_VERIFY(qFuzzyCompare(menu->y(), -22)); |
843 | QCOMPARE(menu->popupItem()->position(), button->mapToScene(QPointF(-11, -22))); |
844 | menu->close(); |
845 | |
846 | menu->setParentItem(nullptr); |
847 | QVERIFY(QMetaObject::invokeMethod(window, "popupAtParentCoord" , Q_ARG(QVariant, QVariant::fromValue(button)), Q_ARG(QVariant, -33), Q_ARG(QVariant, -44))); |
848 | QCOMPARE(menu->parentItem(), button); |
849 | QCOMPARE(menu->currentIndex(), -1); |
850 | QCOMPARE(menu->contentItem()->property("currentIndex" ).toInt(), -1); |
851 | QTRY_VERIFY(qFuzzyCompare(menu->x(), -33)); |
852 | QTRY_VERIFY(qFuzzyCompare(menu->y(), -44)); |
853 | QCOMPARE(menu->popupItem()->position(), button->mapToScene(QPointF(-33, -44))); |
854 | menu->close(); |
855 | |
856 | const qreal twelveOrLeftMargin = qMax(a: qreal(12), b: menu->leftMargin()); |
857 | cursorPos = window->mapToGlobal(pos: QPoint(twelveOrLeftMargin, window->height() / 2)); |
858 | QCursor::setPos(cursorPos); |
859 | QTRY_COMPARE(QCursor::pos(), cursorPos); |
860 | |
861 | const QList<QQuickMenuItem *> = QList<QQuickMenuItem *>() << menuItem1 << menuItem2 << menuItem3; |
862 | for (QQuickMenuItem * : menuItems) { |
863 | menu->resetParentItem(); |
864 | |
865 | QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtCursor" , Q_ARG(QVariant, QVariant::fromValue(menuItem)))); |
866 | QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem)); |
867 | QCOMPARE(menu->contentItem()->property("currentIndex" ).toInt(), menuItems.indexOf(menuItem)); |
868 | QTRY_VERIFY(qFuzzyCompare(menu->x(), twelveOrLeftMargin)); |
869 | QTRY_VERIFY(qFuzzyCompare(menu->y(), window->height() / 2 - menu->topPadding() - menuItem->y())); |
870 | menu->close(); |
871 | |
872 | QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtPos" , Q_ARG(QVariant, QPointF(33, window->height() / 3)), Q_ARG(QVariant, QVariant::fromValue(menuItem)))); |
873 | QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem)); |
874 | QCOMPARE(menu->contentItem()->property("currentIndex" ).toInt(), menuItems.indexOf(menuItem)); |
875 | QTRY_VERIFY(qFuzzyCompare(menu->x(), 33)); |
876 | QTRY_VERIFY(qFuzzyCompare(menu->y(), window->height() / 3 - menu->topPadding() - menuItem->y())); |
877 | menu->close(); |
878 | |
879 | QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtCoord" , Q_ARG(QVariant, 55), Q_ARG(QVariant, window->height() / 3 * 2), Q_ARG(QVariant, QVariant::fromValue(menuItem)))); |
880 | QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem)); |
881 | QCOMPARE(menu->contentItem()->property("currentIndex" ).toInt(), menuItems.indexOf(menuItem)); |
882 | QTRY_VERIFY(qFuzzyCompare(menu->x(), 55)); |
883 | QTRY_COMPARE_WITH_TIMEOUT(menu->y(), window->height() / 3 * 2 - menu->topPadding() - menuItem->y(), 500); |
884 | menu->close(); |
885 | |
886 | menu->setParentItem(nullptr); |
887 | QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtParentCursor" , Q_ARG(QVariant, QVariant::fromValue(button)), Q_ARG(QVariant, QVariant::fromValue(menuItem)))); |
888 | QCOMPARE(menu->parentItem(), button); |
889 | QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem)); |
890 | QCOMPARE(menu->contentItem()->property("currentIndex" ).toInt(), menuItems.indexOf(menuItem)); |
891 | QTRY_VERIFY(qFuzzyCompare(menu->x(), button->mapFromScene(QPoint(twelveOrLeftMargin, window->height() / 2)).x())); |
892 | QTRY_VERIFY(qFuzzyCompare(menu->y(), button->mapFromScene(QPoint(twelveOrLeftMargin, window->height() / 2)).y() - menu->topPadding() - menuItem->y())); |
893 | menu->close(); |
894 | |
895 | menu->setParentItem(nullptr); |
896 | QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtParentPos" , Q_ARG(QVariant, QVariant::fromValue(button)), Q_ARG(QVariant, QPointF(-11, -22)), Q_ARG(QVariant, QVariant::fromValue(menuItem)))); |
897 | QCOMPARE(menu->parentItem(), button); |
898 | QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem)); |
899 | QCOMPARE(menu->contentItem()->property("currentIndex" ).toInt(), menuItems.indexOf(menuItem)); |
900 | QTRY_VERIFY(qFuzzyCompare(menu->x(), -11)); |
901 | QTRY_VERIFY(qFuzzyCompare(menu->y(), -22 - menu->topPadding() - menuItem->y())); |
902 | QCOMPARE(menu->popupItem()->position(), button->mapToScene(QPointF(-11, -22 - menu->topPadding() - menuItem->y()))); |
903 | menu->close(); |
904 | |
905 | menu->setParentItem(nullptr); |
906 | QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtParentCoord" , Q_ARG(QVariant, QVariant::fromValue(button)), Q_ARG(QVariant, -33), Q_ARG(QVariant, -44), Q_ARG(QVariant, QVariant::fromValue(menuItem)))); |
907 | QCOMPARE(menu->parentItem(), button); |
908 | QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem)); |
909 | QCOMPARE(menu->contentItem()->property("currentIndex" ).toInt(), menuItems.indexOf(menuItem)); |
910 | QTRY_VERIFY(qFuzzyCompare(menu->x(), -33)); |
911 | QTRY_VERIFY(qFuzzyCompare(menu->y(), -44 - menu->topPadding() - menuItem->y())); |
912 | QCOMPARE(menu->popupItem()->position(), button->mapToScene(QPointF(-33, -44 - menu->topPadding() - menuItem->y()))); |
913 | menu->close(); |
914 | } |
915 | |
916 | QCursor::setPos(oldCursorPos); |
917 | QTRY_COMPARE(QCursor::pos(), oldCursorPos); |
918 | #endif |
919 | } |
920 | |
921 | void tst_QQuickMenu::() |
922 | { |
923 | QQuickApplicationHelper helper(this, QLatin1String("actions.qml" )); |
924 | QVERIFY2(helper.ready, helper.failureMessage()); |
925 | QQuickWindow *window = helper.window; |
926 | window->show(); |
927 | QVERIFY(QTest::qWaitForWindowActive(window)); |
928 | |
929 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu *>(); |
930 | QVERIFY(menu); |
931 | |
932 | QPointer<QQuickAction> action1 = menu->actionAt(index: 0); |
933 | QVERIFY(!action1.isNull()); |
934 | |
935 | QPointer<QQuickAction> action3 = menu->actionAt(index: 2); |
936 | QVERIFY(!action3.isNull()); |
937 | |
938 | QVERIFY(!menu->actionAt(1)); |
939 | QVERIFY(!menu->actionAt(3)); |
940 | |
941 | QPointer<QQuickMenuItem> = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 0)); |
942 | QVERIFY(!menuItem1.isNull()); |
943 | QCOMPARE(menuItem1->action(), action1.data()); |
944 | QCOMPARE(menuItem1->text(), "action1" ); |
945 | |
946 | QPointer<QQuickMenuItem> = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 1)); |
947 | QVERIFY(!menuItem2.isNull()); |
948 | QVERIFY(!menuItem2->action()); |
949 | QCOMPARE(menuItem2->text(), "menuitem2" ); |
950 | |
951 | QPointer<QQuickMenuItem> = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 2)); |
952 | QVERIFY(!menuItem3.isNull()); |
953 | QCOMPARE(menuItem3->action(), action3.data()); |
954 | QCOMPARE(menuItem3->text(), "action3" ); |
955 | |
956 | QPointer<QQuickMenuItem> = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 3)); |
957 | QVERIFY(!menuItem4.isNull()); |
958 | QVERIFY(!menuItem4->action()); |
959 | QCOMPARE(menuItem4->text(), "menuitem4" ); |
960 | |
961 | // takeAction(int) does not destroy the action, but does destroy the respective item |
962 | QCOMPARE(menu->takeAction(0), action1.data()); |
963 | QVERIFY(!menu->itemAt(3)); |
964 | QCoreApplication::sendPostedEvents(receiver: action1, event_type: QEvent::DeferredDelete); |
965 | QVERIFY(!action1.isNull()); |
966 | QCoreApplication::sendPostedEvents(receiver: menuItem1, event_type: QEvent::DeferredDelete); |
967 | QVERIFY(menuItem1.isNull()); |
968 | |
969 | // takeAction(int) does not destroy an item that doesn't have an action |
970 | QVERIFY(!menuItem2->subMenu()); |
971 | QVERIFY(!menu->takeAction(0)); |
972 | QCoreApplication::sendPostedEvents(receiver: menuItem2, event_type: QEvent::DeferredDelete); |
973 | QVERIFY(!menuItem2.isNull()); |
974 | |
975 | // addAction(Action) re-creates the respective item in the menu |
976 | menu->addAction(action: action1); |
977 | menuItem1 = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 3)); |
978 | QVERIFY(!menuItem1.isNull()); |
979 | QCOMPARE(menuItem1->action(), action1.data()); |
980 | |
981 | // removeAction(Action) destroys both the action and the respective item |
982 | menu->removeAction(action: action1); |
983 | QVERIFY(!menu->itemAt(3)); |
984 | QCoreApplication::sendPostedEvents(receiver: action1, event_type: QEvent::DeferredDelete); |
985 | QVERIFY(action1.isNull()); |
986 | QCoreApplication::sendPostedEvents(receiver: menuItem1, event_type: QEvent::DeferredDelete); |
987 | QVERIFY(menuItem1.isNull()); |
988 | } |
989 | |
990 | void tst_QQuickMenu::() |
991 | { |
992 | QQuickApplicationHelper helper(this, QLatin1String("removeTakeItem.qml" )); |
993 | QVERIFY2(helper.ready, helper.failureMessage()); |
994 | QQuickWindow *window = helper.window; |
995 | window->show(); |
996 | QVERIFY(QTest::qWaitForWindowActive(window)); |
997 | |
998 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu *>(); |
999 | QVERIFY(menu); |
1000 | |
1001 | QPointer<QQuickMenuItem> = window->property(name: "menuItem1" ).value<QQuickMenuItem *>(); |
1002 | QVERIFY(!menuItem1.isNull()); |
1003 | QCOMPARE(menuItem1->menu(), menu); |
1004 | |
1005 | QPointer<QQuickMenuItem> = window->property(name: "menuItem2" ).value<QQuickMenuItem *>(); |
1006 | QVERIFY(!menuItem2.isNull()); |
1007 | QCOMPARE(menuItem2->menu(), menu); |
1008 | |
1009 | QPointer<QQuickMenuItem> = window->property(name: "menuItem3" ).value<QQuickMenuItem *>(); |
1010 | QVERIFY(!menuItem3.isNull()); |
1011 | QCOMPARE(menuItem3->menu(), menu); |
1012 | |
1013 | // takeItem(int) does not destroy |
1014 | QVariant ret; |
1015 | QVERIFY(QMetaObject::invokeMethod(window, "takeSecondItem" , Q_RETURN_ARG(QVariant, ret))); |
1016 | QCOMPARE(ret.value<QQuickMenuItem *>(), menuItem2); |
1017 | QVERIFY(!menuItem2->menu()); |
1018 | QCoreApplication::sendPostedEvents(receiver: menuItem2, event_type: QEvent::DeferredDelete); |
1019 | QVERIFY(!menuItem2.isNull()); |
1020 | |
1021 | // removeItem(Item) destroys |
1022 | QVERIFY(QMetaObject::invokeMethod(window, "removeFirstItem" )); |
1023 | QVERIFY(!menuItem1->menu()); |
1024 | QCoreApplication::sendPostedEvents(receiver: menuItem1, event_type: QEvent::DeferredDelete); |
1025 | QVERIFY(menuItem1.isNull()); |
1026 | |
1027 | // removeItem(null) must not call removeItem(0) |
1028 | QVERIFY(QMetaObject::invokeMethod(window, "removeNullItem" )); |
1029 | QCOMPARE(menuItem3->menu(), menu); |
1030 | QCoreApplication::sendPostedEvents(receiver: menuItem3, event_type: QEvent::DeferredDelete); |
1031 | QVERIFY(!menuItem3.isNull()); |
1032 | |
1033 | // deprecated removeItem(int) does not destroy |
1034 | QVERIFY(QMetaObject::invokeMethod(window, "removeFirstIndex" )); |
1035 | QVERIFY(!menuItem3->menu()); |
1036 | QCoreApplication::sendPostedEvents(receiver: menuItem3, event_type: QEvent::DeferredDelete); |
1037 | QVERIFY(!menuItem3.isNull()); |
1038 | } |
1039 | |
1040 | void tst_QQuickMenu::() |
1041 | { |
1042 | QTest::addColumn<bool>(name: "cascade" ); |
1043 | |
1044 | QTest::newRow(dataTag: "cascading" ) << true; |
1045 | QTest::newRow(dataTag: "non-cascading" ) << false; |
1046 | } |
1047 | |
1048 | void tst_QQuickMenu::() |
1049 | { |
1050 | if ((QGuiApplication::platformName() == QLatin1String("offscreen" )) |
1051 | || (QGuiApplication::platformName() == QLatin1String("minimal" ))) |
1052 | QSKIP("Mouse hovering not functional on offscreen/minimal platforms" ); |
1053 | |
1054 | QFETCH(bool, cascade); |
1055 | |
1056 | QQuickApplicationHelper helper(this, QLatin1String("subMenus.qml" )); |
1057 | QVERIFY2(helper.ready, helper.failureMessage()); |
1058 | QQuickApplicationWindow *window = helper.appWindow; |
1059 | centerOnScreen(window); |
1060 | moveMouseAway(window); |
1061 | window->show(); |
1062 | QVERIFY(QTest::qWaitForWindowActive(window)); |
1063 | |
1064 | QQuickMenu *mainMenu = window->property(name: "mainMenu" ).value<QQuickMenu *>(); |
1065 | QVERIFY(mainMenu); |
1066 | mainMenu->setCascade(cascade); |
1067 | QCOMPARE(mainMenu->cascade(), cascade); |
1068 | |
1069 | QQuickMenu * = window->property(name: "subMenu1" ).value<QQuickMenu *>(); |
1070 | QVERIFY(subMenu1); |
1071 | |
1072 | QQuickMenu * = window->property(name: "subMenu2" ).value<QQuickMenu *>(); |
1073 | QVERIFY(subMenu2); |
1074 | |
1075 | QQuickMenu * = window->property(name: "subSubMenu1" ).value<QQuickMenu *>(); |
1076 | QVERIFY(subSubMenu1); |
1077 | |
1078 | mainMenu->open(); |
1079 | QVERIFY(mainMenu->isVisible()); |
1080 | QTRY_VERIFY(mainMenu->isOpened()); |
1081 | QVERIFY(!subMenu1->isVisible()); |
1082 | QVERIFY(!subMenu2->isVisible()); |
1083 | QVERIFY(!subSubMenu1->isVisible()); |
1084 | |
1085 | // open the sub-menu with mouse click |
1086 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: mainMenu->itemAt(index: 1)); |
1087 | QVERIFY(subMenu1Item); |
1088 | QCOMPARE(subMenu1Item->subMenu(), subMenu1); |
1089 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: subMenu1Item->mapToScene(point: QPoint(1, 1)).toPoint()); |
1090 | QTRY_COMPARE(mainMenu->isVisible(), cascade); |
1091 | QVERIFY(subMenu1->isVisible()); |
1092 | QTRY_VERIFY(subMenu1->isOpened()); |
1093 | QVERIFY(!subMenu2->isVisible()); |
1094 | QVERIFY(!subSubMenu1->isVisible()); |
1095 | |
1096 | // open the cascading sub-sub-menu with mouse hover |
1097 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 2)); |
1098 | QVERIFY(subSubMenu1Item); |
1099 | QCOMPARE(subSubMenu1Item->subMenu(), subSubMenu1); |
1100 | QTest::mouseMove(window, pos: subSubMenu1Item->mapToScene(point: QPoint(1, 1)).toPoint()); |
1101 | QCOMPARE(mainMenu->isVisible(), cascade); |
1102 | QVERIFY(subMenu1->isVisible()); |
1103 | QVERIFY(!subMenu2->isVisible()); |
1104 | QVERIFY(!subSubMenu1->isVisible()); |
1105 | if (cascade) { |
1106 | QTRY_VERIFY(subSubMenu1->isVisible()); |
1107 | QTRY_VERIFY(subSubMenu1->isOpened()); |
1108 | } |
1109 | |
1110 | // close the sub-sub-menu with mouse hover over another parent menu item |
1111 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 0)); |
1112 | QVERIFY(subMenuItem1); |
1113 | QVERIFY(!subMenuItem1->subMenu()); |
1114 | QTest::mouseMove(window, pos: subMenuItem1->mapToScene(point: QPoint(1, 1)).toPoint()); |
1115 | QCOMPARE(mainMenu->isVisible(), cascade); |
1116 | QVERIFY(subMenu1->isVisible()); |
1117 | QVERIFY(!subMenu2->isVisible()); |
1118 | QTRY_VERIFY(!subSubMenu1->isVisible()); |
1119 | |
1120 | // re-open the sub-sub-menu with mouse hover |
1121 | QTest::mouseMove(window, pos: subSubMenu1Item->mapToScene(point: QPoint(1, 1)).toPoint()); |
1122 | QCOMPARE(mainMenu->isVisible(), cascade); |
1123 | QVERIFY(subMenu1->isVisible()); |
1124 | QVERIFY(!subMenu2->isVisible()); |
1125 | if (!cascade) { |
1126 | QVERIFY(!subSubMenu1->isVisible()); |
1127 | } else { |
1128 | QTRY_VERIFY(subSubMenu1->isVisible()); |
1129 | QTRY_VERIFY(subSubMenu1->isOpened()); |
1130 | } |
1131 | |
1132 | // close sub-menu and sub-sub-menu with mouse hover in the main menu |
1133 | QQuickMenuItem *mainMenuItem1 = qobject_cast<QQuickMenuItem *>(object: mainMenu->itemAt(index: 0)); |
1134 | QVERIFY(mainMenuItem1); |
1135 | QTest::mouseMove(window, pos: mainMenuItem1->mapToScene(point: QPoint(1, 1)).toPoint()); |
1136 | QCOMPARE(mainMenu->isVisible(), cascade); |
1137 | QTRY_COMPARE(subMenu1->isVisible(), !cascade); |
1138 | QVERIFY(!subMenu2->isVisible()); |
1139 | QVERIFY(!subSubMenu1->isVisible()); |
1140 | |
1141 | // close all menus by click triggering an item |
1142 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: mainMenuItem1->mapToScene(point: QPoint(1, 1)).toPoint()); |
1143 | QTRY_VERIFY(!mainMenu->isVisible()); |
1144 | QTRY_VERIFY(!subMenu1->isVisible()); |
1145 | QVERIFY(!subMenu2->isVisible()); |
1146 | QVERIFY(!subSubMenu1->isVisible()); |
1147 | } |
1148 | |
1149 | void tst_QQuickMenu::() |
1150 | { |
1151 | subMenuMouse_data(); |
1152 | } |
1153 | |
1154 | // QTBUG-69540 |
1155 | void tst_QQuickMenu::() |
1156 | { |
1157 | if ((QGuiApplication::platformName() == QLatin1String("offscreen" )) |
1158 | || (QGuiApplication::platformName() == QLatin1String("minimal" ))) |
1159 | QSKIP("Mouse hovering not functional on offscreen/minimal platforms" ); |
1160 | |
1161 | QFETCH(bool, cascade); |
1162 | |
1163 | QQuickApplicationHelper helper(this, QLatin1String("subMenuDisabled.qml" )); |
1164 | QVERIFY2(helper.ready, helper.failureMessage()); |
1165 | QQuickApplicationWindow *window = helper.appWindow; |
1166 | centerOnScreen(window); |
1167 | moveMouseAway(window); |
1168 | window->show(); |
1169 | QVERIFY(QTest::qWaitForWindowActive(window)); |
1170 | |
1171 | QQuickMenu *mainMenu = window->property(name: "mainMenu" ).value<QQuickMenu *>(); |
1172 | QVERIFY(mainMenu); |
1173 | mainMenu->setCascade(cascade); |
1174 | QCOMPARE(mainMenu->cascade(), cascade); |
1175 | |
1176 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: mainMenu->itemAt(index: 0)); |
1177 | QVERIFY(menuItem1); |
1178 | |
1179 | QQuickMenu * = window->property(name: "subMenu" ).value<QQuickMenu *>(); |
1180 | QVERIFY(subMenu); |
1181 | |
1182 | mainMenu->open(); |
1183 | QVERIFY(mainMenu->isVisible()); |
1184 | QVERIFY(!menuItem1->isHighlighted()); |
1185 | QVERIFY(!subMenu->isVisible()); |
1186 | |
1187 | // Open the sub-menu with a mouse click. |
1188 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: menuItem1->mapToScene(point: QPoint(1, 1)).toPoint()); |
1189 | // Need to use the TRY variant here when cascade is false, |
1190 | // as e.g. Material style menus have transitions and don't close immediately. |
1191 | QTRY_COMPARE(mainMenu->isVisible(), cascade); |
1192 | QVERIFY(subMenu->isVisible()); |
1193 | QVERIFY(menuItem1->isHighlighted()); |
1194 | // Now the sub-menu is open. The current behavior is that the first menu item |
1195 | // in the new menu is highlighted; make sure that we choose the next item if |
1196 | // the first is disabled. |
1197 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: subMenu->itemAt(index: 0)); |
1198 | QVERIFY(subMenuItem1); |
1199 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: subMenu->itemAt(index: 1)); |
1200 | QVERIFY(subMenuItem2); |
1201 | QVERIFY(!subMenuItem1->isHighlighted()); |
1202 | QVERIFY(subMenuItem2->isHighlighted()); |
1203 | |
1204 | // Close all menus by clicking on the item that isn't disabled. |
1205 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: subMenuItem2->mapToScene(point: QPoint(1, 1)).toPoint()); |
1206 | QTRY_VERIFY(!mainMenu->isVisible()); |
1207 | QTRY_VERIFY(!subMenu->isVisible()); |
1208 | } |
1209 | |
1210 | void tst_QQuickMenu::() |
1211 | { |
1212 | QTest::addColumn<bool>(name: "cascade" ); |
1213 | QTest::addColumn<bool>(name: "mirrored" ); |
1214 | |
1215 | QTest::newRow(dataTag: "cascading" ) << true << false; |
1216 | QTest::newRow(dataTag: "cascading,mirrored" ) << true << true; |
1217 | QTest::newRow(dataTag: "non-cascading" ) << false << false; |
1218 | QTest::newRow(dataTag: "non-cascading,mirrored" ) << false << true; |
1219 | } |
1220 | |
1221 | void tst_QQuickMenu::() |
1222 | { |
1223 | QFETCH(bool, cascade); |
1224 | QFETCH(bool, mirrored); |
1225 | |
1226 | QQuickApplicationHelper helper(this, QLatin1String("subMenus.qml" )); |
1227 | QVERIFY2(helper.ready, helper.failureMessage()); |
1228 | QQuickApplicationWindow *window = helper.appWindow; |
1229 | centerOnScreen(window); |
1230 | moveMouseAway(window); |
1231 | window->show(); |
1232 | QVERIFY(QTest::qWaitForWindowActive(window)); |
1233 | |
1234 | if (mirrored) |
1235 | window->setLocale(QLocale("ar_EG" )); |
1236 | |
1237 | QQuickMenu *mainMenu = window->property(name: "mainMenu" ).value<QQuickMenu *>(); |
1238 | QVERIFY(mainMenu); |
1239 | mainMenu->setCascade(cascade); |
1240 | QCOMPARE(mainMenu->cascade(), cascade); |
1241 | |
1242 | QQuickMenu * = window->property(name: "subMenu1" ).value<QQuickMenu *>(); |
1243 | QVERIFY(subMenu1); |
1244 | |
1245 | QQuickMenu * = window->property(name: "subMenu2" ).value<QQuickMenu *>(); |
1246 | QVERIFY(subMenu2); |
1247 | |
1248 | QQuickMenu * = window->property(name: "subSubMenu1" ).value<QQuickMenu *>(); |
1249 | QVERIFY(subSubMenu1); |
1250 | |
1251 | mainMenu->open(); |
1252 | QVERIFY(mainMenu->isVisible()); |
1253 | QTRY_VERIFY(mainMenu->isOpened()); |
1254 | QVERIFY(!subMenu1->isVisible()); |
1255 | QVERIFY(!subMenu2->isVisible()); |
1256 | QVERIFY(!subSubMenu1->isVisible()); |
1257 | |
1258 | // navigate to the sub-menu item and trigger it to open the sub-menu |
1259 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: mainMenu->itemAt(index: 1)); |
1260 | QVERIFY(subMenu1Item); |
1261 | QVERIFY(!subMenu1Item->isHighlighted()); |
1262 | QCOMPARE(subMenu1Item->subMenu(), subMenu1); |
1263 | QTest::keyClick(window, key: Qt::Key_Down); |
1264 | QTest::keyClick(window, key: Qt::Key_Down); |
1265 | QVERIFY(subMenu1Item->isHighlighted()); |
1266 | QTest::keyClick(window, key: Qt::Key_Space); |
1267 | QTRY_COMPARE(mainMenu->isVisible(), cascade); |
1268 | QVERIFY(subMenu1->isVisible()); |
1269 | QTRY_VERIFY(subMenu1->isOpened()); |
1270 | QVERIFY(!subMenu2->isVisible()); |
1271 | QVERIFY(!subSubMenu1->isVisible()); |
1272 | |
1273 | // navigate to the sub-sub-menu item and open it with the arrow key |
1274 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 2)); |
1275 | QVERIFY(subSubMenu1Item); |
1276 | QVERIFY(!subSubMenu1Item->isHighlighted()); |
1277 | QCOMPARE(subSubMenu1Item->subMenu(), subSubMenu1); |
1278 | QTest::keyClick(window, key: Qt::Key_Down); |
1279 | QTest::keyClick(window, key: Qt::Key_Down); |
1280 | QTest::keyClick(window, key: Qt::Key_Down); |
1281 | QVERIFY(subSubMenu1Item->isHighlighted()); |
1282 | QTRY_COMPARE(mainMenu->isVisible(), cascade); |
1283 | QVERIFY(subMenu1->isVisible()); |
1284 | QVERIFY(!subMenu2->isVisible()); |
1285 | QVERIFY(!subSubMenu1->isVisible()); |
1286 | QTest::keyClick(window, key: mirrored ? Qt::Key_Left : Qt::Key_Right); |
1287 | QCOMPARE(mainMenu->isVisible(), cascade); |
1288 | QTRY_COMPARE(subMenu1->isVisible(), cascade); |
1289 | QVERIFY(!subMenu2->isVisible()); |
1290 | QVERIFY(subSubMenu1->isVisible()); |
1291 | QTRY_VERIFY(subSubMenu1->isOpened()); |
1292 | |
1293 | // navigate within the sub-sub-menu |
1294 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: subSubMenu1->itemAt(index: 0)); |
1295 | QVERIFY(subSubMenuItem1); |
1296 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: subSubMenu1->itemAt(index: 1)); |
1297 | QVERIFY(subSubMenuItem2); |
1298 | QVERIFY(subSubMenuItem1->isHighlighted()); |
1299 | QVERIFY(!subSubMenuItem2->isHighlighted()); |
1300 | QTest::keyClick(window, key: Qt::Key_Down); |
1301 | QVERIFY(!subSubMenuItem1->isHighlighted()); |
1302 | QVERIFY(subSubMenuItem2->isHighlighted()); |
1303 | |
1304 | // navigate to the parent menu with the arrow key |
1305 | QTest::keyClick(window, key: mirrored ? Qt::Key_Right : Qt::Key_Left); |
1306 | QVERIFY(subSubMenu1Item->isHighlighted()); |
1307 | QCOMPARE(mainMenu->isVisible(), cascade); |
1308 | QVERIFY(subMenu1->isVisible()); |
1309 | QVERIFY(!subMenu2->isVisible()); |
1310 | QTRY_VERIFY(!subSubMenu1->isVisible()); |
1311 | |
1312 | // navigate within the sub-menu |
1313 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 0)); |
1314 | QVERIFY(subMenuItem1); |
1315 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 1)); |
1316 | QVERIFY(subMenuItem2); |
1317 | QVERIFY(!subMenuItem1->isHighlighted()); |
1318 | QVERIFY(!subMenuItem2->isHighlighted()); |
1319 | QVERIFY(subSubMenu1Item->isHighlighted()); |
1320 | QTest::keyClick(window, key: Qt::Key_Up); |
1321 | QVERIFY(!subMenuItem1->isHighlighted()); |
1322 | QVERIFY(subMenuItem2->isHighlighted()); |
1323 | QVERIFY(!subSubMenu1Item->isHighlighted()); |
1324 | |
1325 | // close the menus with esc |
1326 | QTest::keyClick(window, key: Qt::Key_Escape); |
1327 | QCOMPARE(mainMenu->isVisible(), cascade); |
1328 | QTRY_VERIFY(!subMenu1->isVisible()); |
1329 | QVERIFY(!subMenu2->isVisible()); |
1330 | QVERIFY(!subSubMenu1->isVisible()); |
1331 | QTest::keyClick(window, key: Qt::Key_Escape); |
1332 | QTRY_VERIFY(!mainMenu->isVisible()); |
1333 | QVERIFY(!subMenu1->isVisible()); |
1334 | QVERIFY(!subMenu2->isVisible()); |
1335 | QVERIFY(!subSubMenu1->isVisible()); |
1336 | } |
1337 | |
1338 | void tst_QQuickMenu::() |
1339 | { |
1340 | subMenuKeyboard_data(); |
1341 | } |
1342 | |
1343 | // QTBUG-69540 |
1344 | void tst_QQuickMenu::() |
1345 | { |
1346 | QFETCH(bool, cascade); |
1347 | QFETCH(bool, mirrored); |
1348 | |
1349 | QQuickApplicationHelper helper(this, QLatin1String("subMenuDisabled.qml" )); |
1350 | QVERIFY2(helper.ready, helper.failureMessage()); |
1351 | QQuickApplicationWindow *window = helper.appWindow; |
1352 | centerOnScreen(window); |
1353 | moveMouseAway(window); |
1354 | window->show(); |
1355 | QVERIFY(QTest::qWaitForWindowActive(window)); |
1356 | |
1357 | if (mirrored) |
1358 | window->setLocale(QLocale("ar_EG" )); |
1359 | |
1360 | QQuickMenu *mainMenu = window->property(name: "mainMenu" ).value<QQuickMenu *>(); |
1361 | QVERIFY(mainMenu); |
1362 | mainMenu->setCascade(cascade); |
1363 | QCOMPARE(mainMenu->cascade(), cascade); |
1364 | |
1365 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: mainMenu->itemAt(index: 0)); |
1366 | QVERIFY(menuItem1); |
1367 | |
1368 | QQuickMenu * = window->property(name: "subMenu" ).value<QQuickMenu *>(); |
1369 | QVERIFY(subMenu); |
1370 | |
1371 | mainMenu->open(); |
1372 | QVERIFY(mainMenu->isVisible()); |
1373 | QTRY_VERIFY(mainMenu->isOpened()); |
1374 | QVERIFY(!menuItem1->isHighlighted()); |
1375 | QVERIFY(!subMenu->isVisible()); |
1376 | |
1377 | // Highlight the top-level menu item. |
1378 | QTest::keyClick(window, key: Qt::Key_Down); |
1379 | QVERIFY(menuItem1->isHighlighted()); |
1380 | |
1381 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: subMenu->itemAt(index: 0)); |
1382 | QVERIFY(subMenuItem1); |
1383 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: subMenu->itemAt(index: 1)); |
1384 | QVERIFY(subMenuItem2); |
1385 | |
1386 | // Open the sub-menu. |
1387 | QTest::keyClick(window, key: mirrored ? Qt::Key_Left : Qt::Key_Right); |
1388 | // The first sub-menu item is disabled, so it should highlight the second one. |
1389 | QVERIFY(!subMenuItem1->isHighlighted()); |
1390 | QVERIFY(subMenuItem2->isHighlighted()); |
1391 | |
1392 | // Close the menus with escape. |
1393 | QTest::keyClick(window, key: Qt::Key_Escape); |
1394 | QTRY_COMPARE(mainMenu->isVisible(), cascade); |
1395 | QTRY_VERIFY(!subMenu->isVisible()); |
1396 | QTest::keyClick(window, key: Qt::Key_Escape); |
1397 | QTRY_VERIFY(!mainMenu->isVisible()); |
1398 | QVERIFY(!subMenu->isVisible()); |
1399 | } |
1400 | |
1401 | /* |
1402 | QCOMPARE() compares doubles with 1-in-1e12 precision, which is too fine for these tests. |
1403 | Casting to floats, compared with 1-in-1e5 precision, gives more robust results. |
1404 | */ |
1405 | #define FLOAT_EQ(u, v) QCOMPARE(float(u), float(v)) |
1406 | |
1407 | void tst_QQuickMenu::() |
1408 | { |
1409 | QTest::addColumn<bool>(name: "cascade" ); |
1410 | QTest::addColumn<bool>(name: "flip" ); |
1411 | QTest::addColumn<bool>(name: "mirrored" ); |
1412 | QTest::addColumn<qreal>(name: "overlap" ); |
1413 | |
1414 | QTest::newRow(dataTag: "cascading" ) << true << false << false << 0.0; |
1415 | QTest::newRow(dataTag: "cascading,flip" ) << true << true << false << 0.0; |
1416 | QTest::newRow(dataTag: "cascading,overlap" ) << true << false << false << 10.0; |
1417 | QTest::newRow(dataTag: "cascading,flip,overlap" ) << true << true << false << 10.0; |
1418 | QTest::newRow(dataTag: "cascading,mirrored" ) << true << false << true << 0.0; |
1419 | QTest::newRow(dataTag: "cascading,mirrored,flip" ) << true << true << true << 0.0; |
1420 | QTest::newRow(dataTag: "cascading,mirrored,overlap" ) << true << false << true << 10.0; |
1421 | QTest::newRow(dataTag: "cascading,mirrored,flip,overlap" ) << true << true << true << 10.0; |
1422 | QTest::newRow(dataTag: "non-cascading" ) << false << false << false << 0.0; |
1423 | } |
1424 | |
1425 | void tst_QQuickMenu::() |
1426 | { |
1427 | QFETCH(bool, cascade); |
1428 | QFETCH(bool, flip); |
1429 | QFETCH(bool, mirrored); |
1430 | QFETCH(qreal, overlap); |
1431 | |
1432 | QQuickApplicationHelper helper(this, QLatin1String("subMenus.qml" )); |
1433 | QVERIFY2(helper.ready, helper.failureMessage()); |
1434 | QQuickApplicationWindow *window = helper.appWindow; |
1435 | |
1436 | // Ensure that the default size of the window fits three menus side by side. |
1437 | QQuickMenu *mainMenu = window->property(name: "mainMenu" ).value<QQuickMenu *>(); |
1438 | QVERIFY(mainMenu); |
1439 | window->setWidth(mainMenu->width() * 3 + mainMenu->leftMargin() + mainMenu->rightMargin()); |
1440 | |
1441 | // the default size of the window fits three menus side by side. |
1442 | // when testing flipping, we resize the window so that the first |
1443 | // sub-menu fits, but the second doesn't |
1444 | if (flip) |
1445 | window->setWidth(window->width() - mainMenu->width()); |
1446 | |
1447 | centerOnScreen(window); |
1448 | moveMouseAway(window); |
1449 | window->show(); |
1450 | QVERIFY(QTest::qWaitForWindowActive(window)); |
1451 | |
1452 | if (mirrored) |
1453 | window->setLocale(QLocale("ar_EG" )); |
1454 | |
1455 | mainMenu->setCascade(cascade); |
1456 | QCOMPARE(mainMenu->cascade(), cascade); |
1457 | mainMenu->setOverlap(overlap); |
1458 | QCOMPARE(mainMenu->overlap(), overlap); |
1459 | |
1460 | QQuickMenu * = window->property(name: "subMenu1" ).value<QQuickMenu *>(); |
1461 | QVERIFY(subMenu1); |
1462 | subMenu1->setCascade(cascade); |
1463 | QCOMPARE(subMenu1->cascade(), cascade); |
1464 | subMenu1->setOverlap(overlap); |
1465 | QCOMPARE(subMenu1->overlap(), overlap); |
1466 | |
1467 | QQuickMenu * = window->property(name: "subMenu2" ).value<QQuickMenu *>(); |
1468 | QVERIFY(subMenu2); |
1469 | subMenu2->setCascade(cascade); |
1470 | QCOMPARE(subMenu2->cascade(), cascade); |
1471 | subMenu2->setOverlap(overlap); |
1472 | QCOMPARE(subMenu2->overlap(), overlap); |
1473 | |
1474 | QQuickMenu * = window->property(name: "subSubMenu1" ).value<QQuickMenu *>(); |
1475 | QVERIFY(subSubMenu1); |
1476 | subSubMenu1->setCascade(cascade); |
1477 | QCOMPARE(subSubMenu1->cascade(), cascade); |
1478 | subSubMenu1->setOverlap(overlap); |
1479 | QCOMPARE(subSubMenu1->overlap(), overlap); |
1480 | |
1481 | // choose the main menu position so that there's room for the |
1482 | // sub-menus to cascade to the left when mirrored |
1483 | if (mirrored) |
1484 | mainMenu->setX(window->width() - mainMenu->width()); |
1485 | |
1486 | mainMenu->open(); |
1487 | QVERIFY(mainMenu->isVisible()); |
1488 | QTRY_VERIFY(mainMenu->isOpened()); |
1489 | QVERIFY(!subMenu1->isVisible()); |
1490 | QVERIFY(!subMenu2->isVisible()); |
1491 | QVERIFY(!subSubMenu1->isVisible()); |
1492 | |
1493 | // open the sub-menu (never flips) |
1494 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: mainMenu->itemAt(index: 1)); |
1495 | QVERIFY(subMenu1Item); |
1496 | QCOMPARE(subMenu1Item->subMenu(), subMenu1); |
1497 | emit subMenu1Item->triggered(); |
1498 | QTRY_COMPARE(mainMenu->isVisible(), cascade); |
1499 | QVERIFY(subMenu1->isVisible()); |
1500 | QTRY_VERIFY(subMenu1->isOpened()); |
1501 | QVERIFY(!subMenu2->isVisible()); |
1502 | QVERIFY(!subSubMenu1->isVisible()); |
1503 | |
1504 | if (cascade) { |
1505 | QCOMPARE(subMenu1->parentItem(), subMenu1Item); |
1506 | // vertically aligned to the parent menu item |
1507 | // We cast to float here because we want to use its larger tolerance for equality (because it has less precision than double). |
1508 | FLOAT_EQ(subMenu1->popupItem()->y(), mainMenu->popupItem()->y() + subMenu1Item->y()); |
1509 | if (mirrored) { |
1510 | // on the left of the parent menu |
1511 | FLOAT_EQ(subMenu1->popupItem()->x(), mainMenu->popupItem()->x() - subMenu1->width() + overlap); |
1512 | } else { |
1513 | // on the right of the parent menu |
1514 | FLOAT_EQ(subMenu1->popupItem()->x(), mainMenu->popupItem()->x() + mainMenu->width() - overlap); |
1515 | } |
1516 | } else { |
1517 | QCOMPARE(subMenu1->parentItem(), mainMenu->parentItem()); |
1518 | // centered over the parent menu |
1519 | FLOAT_EQ(subMenu1->popupItem()->x(), mainMenu->popupItem()->x() + (mainMenu->width() - subMenu1->width()) / 2); |
1520 | FLOAT_EQ(subMenu1->popupItem()->y(), mainMenu->popupItem()->y() + (mainMenu->height() - subMenu1->height()) / 2); |
1521 | } |
1522 | |
1523 | // open the sub-sub-menu (can flip) |
1524 | QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 2)); |
1525 | QVERIFY(subSubMenu1Item); |
1526 | QCOMPARE(subSubMenu1Item->subMenu(), subSubMenu1); |
1527 | emit subSubMenu1Item->triggered(); |
1528 | QTRY_COMPARE(mainMenu->isVisible(), cascade); |
1529 | QTRY_COMPARE(subMenu1->isVisible(), cascade); |
1530 | QVERIFY(!subMenu2->isVisible()); |
1531 | QVERIFY(subSubMenu1->isVisible()); |
1532 | QTRY_VERIFY(subSubMenu1->isOpened()); |
1533 | |
1534 | if (cascade) { |
1535 | QCOMPARE(subSubMenu1->parentItem(), subSubMenu1Item); |
1536 | // vertically aligned to the parent menu item |
1537 | FLOAT_EQ(subSubMenu1->popupItem()->y(), subMenu1->popupItem()->y() + subSubMenu1Item->y()); |
1538 | if (mirrored != flip) { |
1539 | // on the left of the parent menu |
1540 | FLOAT_EQ(subSubMenu1->popupItem()->x(), subMenu1->popupItem()->x() - subSubMenu1->width() + overlap); |
1541 | } else { |
1542 | // on the right of the parent menu |
1543 | FLOAT_EQ(subSubMenu1->popupItem()->x(), subMenu1->popupItem()->x() + subMenu1->width() - overlap); |
1544 | } |
1545 | } else { |
1546 | QCOMPARE(subSubMenu1->parentItem(), subMenu1->parentItem()); |
1547 | // centered over the parent menu |
1548 | FLOAT_EQ(subSubMenu1->popupItem()->x(), subMenu1->popupItem()->x() + (subMenu1->width() - subSubMenu1->width()) / 2); |
1549 | FLOAT_EQ(subSubMenu1->popupItem()->y(), subMenu1->popupItem()->y() + (subMenu1->height() - subSubMenu1->height()) / 2); |
1550 | } |
1551 | } |
1552 | |
1553 | #undef FLOAT_EQ |
1554 | |
1555 | void tst_QQuickMenu::() |
1556 | { |
1557 | QQuickApplicationHelper helper(this, QLatin1String("subMenus.qml" )); |
1558 | QVERIFY2(helper.ready, helper.failureMessage()); |
1559 | QQuickWindow *window = helper.window; |
1560 | window->show(); |
1561 | QVERIFY(QTest::qWaitForWindowActive(window)); |
1562 | |
1563 | QQuickMenu *mainMenu = window->property(name: "mainMenu" ).value<QQuickMenu *>(); |
1564 | QVERIFY(mainMenu); |
1565 | |
1566 | QVERIFY(!mainMenu->menuAt(0)); |
1567 | |
1568 | QPointer<QQuickMenu> = window->property(name: "subMenu1" ).value<QQuickMenu *>(); |
1569 | QVERIFY(!subMenu1.isNull()); |
1570 | QCOMPARE(mainMenu->menuAt(1), subMenu1.data()); |
1571 | |
1572 | QVERIFY(!mainMenu->menuAt(2)); |
1573 | |
1574 | QPointer<QQuickMenu> = window->property(name: "subMenu2" ).value<QQuickMenu *>(); |
1575 | QVERIFY(!subMenu2.isNull()); |
1576 | QCOMPARE(mainMenu->menuAt(3), subMenu2.data()); |
1577 | |
1578 | QVERIFY(!mainMenu->menuAt(4)); |
1579 | |
1580 | QPointer<QQuickMenu> = window->property(name: "subSubMenu1" ).value<QQuickMenu *>(); |
1581 | QVERIFY(!subSubMenu1.isNull()); |
1582 | |
1583 | // takeMenu(int) does not destroy the menu, but does destroy the respective item in the parent menu |
1584 | QPointer<QQuickMenuItem> = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 2)); |
1585 | QVERIFY(subSubMenu1Item); |
1586 | QCOMPARE(subSubMenu1Item->subMenu(), subSubMenu1.data()); |
1587 | QCOMPARE(subMenu1->takeMenu(2), subSubMenu1.data()); |
1588 | QVERIFY(!subMenu1->itemAt(2)); |
1589 | QCoreApplication::sendPostedEvents(receiver: subSubMenu1, event_type: QEvent::DeferredDelete); |
1590 | QVERIFY(!subSubMenu1.isNull()); |
1591 | QCoreApplication::sendPostedEvents(receiver: subSubMenu1Item, event_type: QEvent::DeferredDelete); |
1592 | QVERIFY(subSubMenu1Item.isNull()); |
1593 | |
1594 | // takeMenu(int) does not destroy an item that doesn't present a menu |
1595 | QPointer<QQuickMenuItem> = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 0)); |
1596 | QVERIFY(subMenuItem1); |
1597 | QVERIFY(!subMenuItem1->subMenu()); |
1598 | QVERIFY(!subMenu1->takeMenu(0)); |
1599 | QCoreApplication::sendPostedEvents(receiver: subMenuItem1, event_type: QEvent::DeferredDelete); |
1600 | QVERIFY(!subMenuItem1.isNull()); |
1601 | |
1602 | // addMenu(Menu) re-creates the respective item in the parent menu |
1603 | subMenu1->addMenu(menu: subSubMenu1); |
1604 | subSubMenu1Item = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 2)); |
1605 | QVERIFY(!subSubMenu1Item.isNull()); |
1606 | |
1607 | // removeMenu(Menu) destroys both the menu and the respective item in the parent menu |
1608 | subMenu1->removeMenu(menu: subSubMenu1); |
1609 | QVERIFY(!subMenu1->itemAt(2)); |
1610 | QCoreApplication::sendPostedEvents(receiver: subSubMenu1, event_type: QEvent::DeferredDelete); |
1611 | QVERIFY(subSubMenu1.isNull()); |
1612 | QCoreApplication::sendPostedEvents(receiver: subSubMenu1Item, event_type: QEvent::DeferredDelete); |
1613 | QVERIFY(subSubMenu1Item.isNull()); |
1614 | } |
1615 | |
1616 | void tst_QQuickMenu::() |
1617 | { |
1618 | QTest::addColumn<QString>(name: "qmlFilePath" ); |
1619 | |
1620 | QTest::addRow(format: "Window" ) << QString::fromLatin1(str: "windowScrollable.qml" ); |
1621 | QTest::addRow(format: "ApplicationWindow" ) << QString::fromLatin1(str: "applicationWindowScrollable.qml" ); |
1622 | QTest::addRow(format: "WithPadding" ) << QString::fromLatin1(str: "scrollableWithPadding.qml" ); |
1623 | } |
1624 | |
1625 | void tst_QQuickMenu::() |
1626 | { |
1627 | QFETCH(QString, qmlFilePath); |
1628 | |
1629 | QQuickApplicationHelper helper(this, qmlFilePath); |
1630 | QVERIFY2(helper.ready, helper.failureMessage()); |
1631 | QQuickWindow *window = helper.window; |
1632 | window->show(); |
1633 | QVERIFY(QTest::qWaitForWindowActive(window)); |
1634 | |
1635 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu*>(); |
1636 | menu->open(); |
1637 | QVERIFY(menu->isVisible()); |
1638 | |
1639 | QQuickItem *contentItem = menu->contentItem(); |
1640 | QCOMPARE(contentItem->property("interactive" ).toBool(), true); |
1641 | } |
1642 | |
1643 | void tst_QQuickMenu::() |
1644 | { |
1645 | QTest::addColumn<int>(name: "menuItemIndex" ); |
1646 | QTest::addColumn<int>(name: "subMenuItemIndex" ); |
1647 | |
1648 | QTest::addRow(format: "Action" ) << 0 << -1; |
1649 | QTest::addRow(format: "MenuItem with Action" ) << 1 << -1; |
1650 | QTest::addRow(format: "MenuItem with Action declared outside menu" ) << 2 << -1; |
1651 | QTest::addRow(format: "MenuItem with no Action" ) << 3 << -1; |
1652 | |
1653 | QTest::addRow(format: "Sub-Action" ) << 4 << 0; |
1654 | QTest::addRow(format: "Sub-MenuItem with Action declared inside" ) << 4 << 1; |
1655 | QTest::addRow(format: "Sub-MenuItem with Action declared outside menu" ) << 4 << 2; |
1656 | QTest::addRow(format: "Sub-MenuItem with no Action" ) << 4 << 3; |
1657 | } |
1658 | |
1659 | // Tests that the menu is dismissed when a menu item sets "enabled = false" in onTriggered(). |
1660 | void tst_QQuickMenu::() |
1661 | { |
1662 | if ((QGuiApplication::platformName() == QLatin1String("offscreen" )) |
1663 | || (QGuiApplication::platformName() == QLatin1String("minimal" ))) |
1664 | QSKIP("Mouse hovering not functional on offscreen/minimal platforms" ); |
1665 | |
1666 | QFETCH(int, ); |
1667 | QFETCH(int, ); |
1668 | |
1669 | QQuickApplicationHelper helper(this, QLatin1String("disableWhenTriggered.qml" )); |
1670 | QVERIFY2(helper.ready, helper.failureMessage()); |
1671 | QQuickWindow *window = helper.window; |
1672 | window->show(); |
1673 | QVERIFY(QTest::qWaitForWindowActive(window)); |
1674 | |
1675 | QQuickMenu * = window->findChild<QQuickMenu*>(aName: "Menu" ); |
1676 | QVERIFY(menu); |
1677 | |
1678 | menu->open(); |
1679 | QVERIFY(menu->isVisible()); |
1680 | QTRY_VERIFY(menu->isOpened()); |
1681 | |
1682 | QPointer<QQuickMenuItem> = qobject_cast<QQuickMenuItem*>(object: menu->itemAt(index: menuItemIndex)); |
1683 | QVERIFY(menuItem); |
1684 | |
1685 | if (subMenuItemIndex == -1) { |
1686 | // Click a top-level menu item. |
1687 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, |
1688 | pos: menuItem->mapToScene(point: QPointF(menuItem->width() / 2, menuItem->height() / 2)).toPoint()); |
1689 | QCOMPARE(menuItem->isEnabled(), false); |
1690 | QTRY_VERIFY(!menu->isVisible()); |
1691 | } else { |
1692 | // Click a sub-menu item. |
1693 | QPointer<QQuickMenu> = menuItem->subMenu(); |
1694 | QVERIFY(subMenu); |
1695 | |
1696 | QPointer<QQuickMenuItem> = qobject_cast<QQuickMenuItem*>(object: subMenu->itemAt(index: subMenuItemIndex)); |
1697 | QVERIFY(subMenuItem); |
1698 | |
1699 | // First, open the sub-menu. |
1700 | QTest::mouseMove(window, pos: menuItem->mapToScene(point: QPoint(1, 1)).toPoint()); |
1701 | QTRY_VERIFY(subMenu->isVisible()); |
1702 | QVERIFY(menuItem->isHovered()); |
1703 | QTRY_VERIFY(subMenu->contentItem()->property("contentHeight" ).toReal() > 0.0); |
1704 | |
1705 | // Click the item. |
1706 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, |
1707 | pos: subMenuItem->mapToScene(point: QPointF(subMenuItem->width() / 2, subMenuItem->height() / 2)).toPoint()); |
1708 | QCOMPARE(subMenuItem->isEnabled(), false); |
1709 | QTRY_VERIFY(!menu->isVisible()); |
1710 | } |
1711 | } |
1712 | |
1713 | void tst_QQuickMenu::() |
1714 | { |
1715 | QTest::addColumn<bool>(name: "mirrored" ); |
1716 | |
1717 | QTest::newRow(dataTag: "non-mirrored" ) << false; |
1718 | QTest::newRow(dataTag: "mirrored" ) << true; |
1719 | } |
1720 | |
1721 | void tst_QQuickMenu::() |
1722 | { |
1723 | QFETCH(bool, mirrored); |
1724 | |
1725 | QQuickApplicationHelper helper(this, QLatin1String("menuItemWidths.qml" )); |
1726 | QVERIFY2(helper.ready, helper.failureMessage()); |
1727 | QQuickApplicationWindow *window = helper.appWindow; |
1728 | window->show(); |
1729 | QVERIFY(QTest::qWaitForWindowActive(window)); |
1730 | |
1731 | if (mirrored) |
1732 | window->setLocale(QLocale("ar_EG" )); |
1733 | |
1734 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu *>(); |
1735 | QVERIFY(menu); |
1736 | menu->open(); |
1737 | QTRY_VERIFY(menu->isOpened()); |
1738 | for (int i = 0; i < menu->count(); ++i) |
1739 | QCOMPARE(menu->itemAt(i)->width(), menu->availableWidth()); |
1740 | } |
1741 | |
1742 | void tst_QQuickMenu::() |
1743 | { |
1744 | QTest::addColumn<bool>(name: "mirrored" ); |
1745 | |
1746 | QTest::newRow(dataTag: "non-mirrored" ) << false; |
1747 | QTest::newRow(dataTag: "mirrored" ) << true; |
1748 | } |
1749 | |
1750 | void tst_QQuickMenu::() |
1751 | { |
1752 | QFETCH(bool, mirrored); |
1753 | |
1754 | QQuickApplicationHelper helper(this, QLatin1String("menuItemWidths.qml" )); |
1755 | QVERIFY2(helper.ready, helper.failureMessage()); |
1756 | QQuickApplicationWindow *window = helper.appWindow; |
1757 | window->show(); |
1758 | QVERIFY(QTest::qWaitForWindowActive(window)); |
1759 | |
1760 | if (mirrored) |
1761 | window->setLocale(QLocale("ar_EG" )); |
1762 | |
1763 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu *>(); |
1764 | QVERIFY(menu); |
1765 | menu->open(); |
1766 | QTRY_VERIFY(menu->isOpened()); |
1767 | for (int i = 0; i < menu->count(); ++i) { |
1768 | // Check that the width of menu items is correct before we resize the menu. |
1769 | const QQuickItem *item = menu->itemAt(index: i); |
1770 | QVERIFY2(qFuzzyCompare(item->width(), menu->availableWidth()), |
1771 | qPrintable(QString::fromLatin1("Expected width of %1 to be %2, but it's %3" ) |
1772 | .arg(item->objectName()).arg(menu->availableWidth()).arg(item->width()))); |
1773 | } |
1774 | |
1775 | menu->setWidth(menu->width() + 10); |
1776 | |
1777 | // Check that the width of menu items is correct after we resize the menu. |
1778 | for (int i = 0; i < menu->count(); ++i) { |
1779 | // Check that the width of menu items is correct after we resize the menu. |
1780 | const QQuickItem *item = menu->itemAt(index: i); |
1781 | QVERIFY2(qFuzzyCompare(item->width(), menu->availableWidth()), |
1782 | qPrintable(QString::fromLatin1("Expected width of %1 to be %2, but it's %3" ) |
1783 | .arg(item->objectName()).arg(menu->availableWidth()).arg(item->width()))); |
1784 | } |
1785 | } |
1786 | |
1787 | void tst_QQuickMenu::() |
1788 | { |
1789 | QTest::addColumn<bool>(name: "mirrored" ); |
1790 | |
1791 | QTest::newRow(dataTag: "non-mirrored" ) << false; |
1792 | QTest::newRow(dataTag: "mirrored" ) << true; |
1793 | } |
1794 | |
1795 | void tst_QQuickMenu::() |
1796 | { |
1797 | QFETCH(bool, mirrored); |
1798 | |
1799 | QQuickApplicationHelper helper(this, QLatin1String("menuItemWidths.qml" )); |
1800 | QVERIFY2(helper.ready, helper.failureMessage()); |
1801 | QQuickApplicationWindow *window = helper.appWindow; |
1802 | window->show(); |
1803 | QVERIFY(QTest::qWaitForWindowActive(window)); |
1804 | |
1805 | if (mirrored) |
1806 | window->setLocale(QLocale("ar_EG" )); |
1807 | |
1808 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu *>(); |
1809 | QVERIFY(menu); |
1810 | menu->open(); |
1811 | QTRY_VERIFY(menu->isOpened()); |
1812 | // Check that the width of the menu item is correct before we change its font size. |
1813 | QQuickMenuItem * = qobject_cast<QQuickMenuItem*>(object: menu->itemAt(index: 0)); |
1814 | QCOMPARE(menuItem->width(), menu->availableWidth()); |
1815 | |
1816 | // Add some text to increase the implicitWidth of the MenuItem. |
1817 | const qreal oldImplicitWidth = menuItem->implicitWidth(); |
1818 | for (int i = 0; menuItem->implicitWidth() <= oldImplicitWidth; ++i) { |
1819 | menuItem->setText(menuItem->text() + QLatin1String("---" )); |
1820 | if (i == 100) |
1821 | QFAIL("Shouldn't need 100 iterations to increase MenuItem's implicitWidth; something is wrong here" ); |
1822 | } |
1823 | |
1824 | // Check that the width of the menu item is correct after we change its font size. |
1825 | QCOMPARE(menuItem->width(), menu->availableWidth()); |
1826 | } |
1827 | |
1828 | void tst_QQuickMenu::() |
1829 | { |
1830 | QQuickApplicationHelper helper(this, QLatin1String("menuItemWidths.qml" )); |
1831 | QVERIFY2(helper.ready, helper.failureMessage()); |
1832 | QQuickApplicationWindow *window = helper.appWindow; |
1833 | window->show(); |
1834 | QVERIFY(QTest::qWaitForWindowActive(window)); |
1835 | |
1836 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu *>(); |
1837 | QVERIFY(menu); |
1838 | menu->open(); |
1839 | QTRY_VERIFY(menu->isOpened()); |
1840 | for (int i = 0; i < menu->count(); ++i) { |
1841 | // Check that the width of each menu item is correct before we retranslate. |
1842 | const QQuickItem *item = menu->itemAt(index: i); |
1843 | QVERIFY2(qFuzzyCompare(item->width(), menu->availableWidth()), |
1844 | qPrintable(QString::fromLatin1("Expected width of %1 to be %2, but it's %3" ) |
1845 | .arg(item->objectName()).arg(menu->availableWidth()).arg(item->width()))); |
1846 | } |
1847 | |
1848 | // Call retranslate() and cause all bindings to be re-evaluated. |
1849 | helper.engine.retranslate(); |
1850 | |
1851 | for (int i = 0; i < menu->count(); ++i) { |
1852 | // Check that the width of each menu item is correct after we retranslate. |
1853 | const QQuickItem *item = menu->itemAt(index: i); |
1854 | QVERIFY2(qFuzzyCompare(item->width(), menu->availableWidth()), |
1855 | qPrintable(QString::fromLatin1("Expected width of %1 to be %2, but it's %3" ) |
1856 | .arg(item->objectName()).arg(menu->availableWidth()).arg(item->width()))); |
1857 | } |
1858 | } |
1859 | |
1860 | void tst_QQuickMenu::() |
1861 | { |
1862 | QQuickApplicationHelper helper(this, QLatin1String("giveMenuItemFocusOnButtonPress.qml" )); |
1863 | QVERIFY2(helper.ready, helper.failureMessage()); |
1864 | QQuickApplicationWindow *window = helper.appWindow; |
1865 | window->show(); |
1866 | QVERIFY(QTest::qWaitForWindowActive(window)); |
1867 | |
1868 | // Press enter on the button to open the menu. |
1869 | QQuickButton * = window->property(name: "menuButton" ).value<QQuickButton*>(); |
1870 | QVERIFY(menuButton); |
1871 | menuButton->forceActiveFocus(); |
1872 | QVERIFY(menuButton->hasActiveFocus()); |
1873 | |
1874 | QSignalSpy clickedSpy(window, SIGNAL(menuButtonClicked())); |
1875 | QVERIFY(clickedSpy.isValid()); |
1876 | |
1877 | QTest::keyClick(window, key: Qt::Key_Return); |
1878 | QCOMPARE(clickedSpy.count(), 1); |
1879 | |
1880 | // The menu should still be open. |
1881 | QQuickMenu * = window->property(name: "menu" ).value<QQuickMenu*>(); |
1882 | QVERIFY(menu); |
1883 | QTRY_VERIFY(menu->isOpened()); |
1884 | } |
1885 | |
1886 | QTEST_QUICKCONTROLS_MAIN(tst_QQuickMenu) |
1887 | |
1888 | #include "tst_qquickmenu.moc" |
1889 | |