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