diff options
Diffstat (limited to 'tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp')
-rw-r--r-- | tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp | 609 |
1 files changed, 452 insertions, 157 deletions
diff --git a/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp b/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp index 909b0b4aaa..66c1359bd8 100644 --- a/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp +++ b/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> #include <QSignalSpy> @@ -33,6 +8,7 @@ #include "qcombobox.h" #include <private/qcombobox_p.h> #include <private/qguiapplication_p.h> +#include <qpa/qplatformintegration.h> #include <qpa/qplatformtheme.h> #include <qfontcombobox.h> @@ -49,6 +25,7 @@ #include <qtablewidget.h> #include <qscrollbar.h> #include <qboxlayout.h> +#include <qshortcut.h> #include <qstackedwidget.h> #include <qstandarditemmodel.h> @@ -65,11 +42,15 @@ #include <qstandarditemmodel.h> #include <qproxystyle.h> #include <qfont.h> +#include <qstylehints.h> #include "../../../shared/platforminputcontext.h" #include <private/qinputmethod_p.h> #include <QtTest/private/qtesthelpers_p.h> +#include <QtTest/private/qemulationdetector_p.h> + +#include <QtWidgets/private/qapplication_p.h> using namespace QTestPrivate; @@ -130,6 +111,7 @@ private slots: #ifndef QT_NO_STYLE_FUSION void task190351_layout(); void task191329_size(); + void popupPositionAfterStyleChange(); #endif void task166349_setEditableOnReturn(); void task190205_setModelAdjustToContents(); @@ -169,6 +151,11 @@ private slots: void task_QTBUG_52027_mapCompleterIndex(); void checkMenuItemPosWhenStyleSheetIsSet(); void checkEmbeddedLineEditWhenStyleSheetIsSet(); + void propagateStyleChanges(); + void buttonPressKeys(); + void clearModel(); + void cancelClosesPopupNotDialog(); + void closePopupWithCheckableItems(); private: PlatformInputContext m_platformInputContext; @@ -471,11 +458,19 @@ void tst_QComboBox::setEditable() void tst_QComboBox::setPalette() { -#ifdef Q_OS_MAC - if (QApplication::style()->inherits("QMacStyle")) { - QSKIP("This test doesn't make sense for pixmap-based styles"); - } -#endif + // If (a) Palettes are pixmap based and/or (b) contain color groups/roles which the + // resolve mask prevents from being copied, the direct comparison of the inherited + // palette and the parent palette will fail. + // To prevent that, set a simple gray system palette for QComboBox and QLineEdit + const QPalette comboBoxPalette = qApp->palette("QComboBox"); + const QPalette lineEditPalette = qApp->palette("QLineEdit"); + auto guard = qScopeGuard([&]{ + qApp->setPalette(comboBoxPalette, "QComboBox"); + qApp->setPalette(lineEditPalette, "QLineEdit"); + }); + qApp->setPalette(QPalette(Qt::gray), "QComboBox"); + qApp->setPalette(QPalette(Qt::gray), "QLineEdit"); + TestWidget topLevel; topLevel.show(); QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); @@ -483,16 +478,15 @@ void tst_QComboBox::setPalette() QPalette pal = testWidget->palette(); pal.setColor(QPalette::Base, Qt::red); testWidget->setPalette(pal); - testWidget->setEditable(!testWidget->isEditable()); + testWidget->setEditable(true); pal.setColor(QPalette::Base, Qt::blue); testWidget->setPalette(pal); - const QObjectList comboChildren = testWidget->children(); - for (int i = 0; i < comboChildren.size(); ++i) { - QObject *o = comboChildren.at(i); - if (o->isWidgetType()) { - QCOMPARE(((QWidget*)o)->palette(), pal); + const QObjectList &comboChildren = testWidget->children(); + for (auto *child : comboChildren) { + if (auto *widget = qobject_cast<QWidget *>(child)) { + QCOMPARE(widget->palette(), pal); } } @@ -769,7 +763,7 @@ void tst_QComboBox::insertPolicy() testWidget->setInsertPolicy(insertPolicy); testWidget->addItems(initialEntries); testWidget->setEditable(true); - if (initialEntries.count() > 0) + if (initialEntries.size() > 0) testWidget->setCurrentIndex(currentIndex); // clear @@ -781,10 +775,10 @@ void tst_QComboBox::insertPolicy() // First check that there is the right number of entries, or // we may unwittingly pass - QCOMPARE((int)result.count(), testWidget->count()); + QCOMPARE((int)result.size(), testWidget->count()); // No need to compare if there are no strings to compare - if (result.count() > 0) { + if (result.size() > 0) { for (int i=0; i<testWidget->count(); ++i) { QCOMPARE(testWidget->itemText(i), result.at(i)); } @@ -855,7 +849,6 @@ void tst_QComboBox::autoCompletionCaseSensitivity() TestWidget topLevel; topLevel.show(); QComboBox *testWidget = topLevel.comboBox(); - qApp->setActiveWindow(&topLevel); testWidget->setFocus(); QVERIFY(QTest::qWaitForWindowActive(&topLevel)); QCOMPARE(qApp->focusWidget(), (QWidget *)testWidget); @@ -881,18 +874,18 @@ void tst_QComboBox::autoCompletionCaseSensitivity() QTest::keyClick(testWidget->lineEdit(), Qt::Key_A); qApp->processEvents(); QCOMPARE(testWidget->currentText(), QString("aww")); - QCOMPARE(spyReturn.count(), 0); + QCOMPARE(spyReturn.size(), 0); QTest::keyClick(testWidget->lineEdit(), Qt::Key_B); qApp->processEvents(); // autocompletions preserve userkey-case from 4.2 QCOMPARE(testWidget->currentText(), QString("abCDEF")); - QCOMPARE(spyReturn.count(), 0); + QCOMPARE(spyReturn.size(), 0); QTest::keyClick(testWidget->lineEdit(), Qt::Key_Enter); qApp->processEvents(); QCOMPARE(testWidget->currentText(), QString("aBCDEF")); // case restored to item's case - QCOMPARE(spyReturn.count(), 1); + QCOMPARE(spyReturn.size(), 1); testWidget->clearEditText(); QTest::keyClick(testWidget->lineEdit(), 'c'); @@ -1028,7 +1021,7 @@ void tst_QComboBox::currentIndex_data() expectedCurrentIndex = -1; expectedCurrentText = ""; expectedSignalCount = 2; - QTest::newRow("check that isetting the index to -1 works") + QTest::newRow("check that setting the index to -1 works") << initialItems << setCurrentIndex << removeIndex << insertIndex << insertText << expectedCurrentIndex << expectedCurrentText << expectedSignalCount; @@ -1171,66 +1164,71 @@ void tst_QComboBox::currentIndex() TestWidget topLevel; topLevel.show(); QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); - QComboBox *testWidget = topLevel.comboBox(); + QComboBox *comboBox = topLevel.comboBox(); // test both editable/non-editable combobox for (int edit = 0; edit < 2; ++edit) { - testWidget->clear(); - testWidget->setEditable(edit ? true : false); + comboBox->clear(); + comboBox->setEditable(edit ? true : false); if (edit) - QVERIFY(testWidget->lineEdit()); + QVERIFY(comboBox->lineEdit()); // verify it is empty, has no current index and no current text - QCOMPARE(testWidget->count(), 0); - QCOMPARE(testWidget->currentIndex(), -1); - QVERIFY(testWidget->currentText().isEmpty()); + QCOMPARE(comboBox->count(), 0); + QCOMPARE(comboBox->currentIndex(), -1); + QVERIFY(comboBox->currentText().isEmpty()); // spy on currentIndexChanged - QSignalSpy indexChangedInt(testWidget, SIGNAL(currentIndexChanged(int))); + QSignalSpy indexChangedSpy(comboBox, &QComboBox::currentIndexChanged); // stuff items into it - foreach(QString text, initialItems) { - testWidget->addItem(text); - } - QCOMPARE(testWidget->count(), initialItems.count()); + for (const QString &text : initialItems) + comboBox->addItem(text); + + QCOMPARE(comboBox->count(), initialItems.size()); // set current index, remove and/or insert if (setCurrentIndex >= -1) { - testWidget->setCurrentIndex(setCurrentIndex); - QCOMPARE(testWidget->currentIndex(), setCurrentIndex); + comboBox->setCurrentIndex(setCurrentIndex); + QCOMPARE(comboBox->currentIndex(), setCurrentIndex); } if (removeIndex >= 0) - testWidget->removeItem(removeIndex); + comboBox->removeItem(removeIndex); if (insertIndex >= 0) - testWidget->insertItem(insertIndex, insertText); + comboBox->insertItem(insertIndex, insertText); // compare with expected index and text - QCOMPARE(testWidget->currentIndex(), expectedCurrentIndex); - QCOMPARE(testWidget->currentText(), expectedCurrentText); + QCOMPARE(comboBox->currentIndex(), expectedCurrentIndex); + QCOMPARE(comboBox->currentText(), expectedCurrentText); // check that signal count is correct - QCOMPARE(indexChangedInt.count(), expectedSignalCount); + QCOMPARE(indexChangedSpy.size(), expectedSignalCount); // compare with last sent signal values - if (indexChangedInt.count()) - QCOMPARE(indexChangedInt.at(indexChangedInt.count() - 1).at(0).toInt(), - testWidget->currentIndex()); + if (indexChangedSpy.size()) + QCOMPARE(indexChangedSpy.at(indexChangedSpy.size() - 1).at(0).toInt(), + comboBox->currentIndex()); + + // Test a no-op index change + const int index = comboBox->currentIndex(); + comboBox->setCurrentIndex(index); + QCOMPARE(indexChangedSpy.size(), expectedSignalCount); if (edit) { - testWidget->setCurrentIndex(-1); - testWidget->setInsertPolicy(QComboBox::InsertAtBottom); - QTest::keyPress(testWidget, 'a'); - QTest::keyPress(testWidget, 'b'); - QCOMPARE(testWidget->currentText(), QString("ab")); - QCOMPARE(testWidget->currentIndex(), -1); - int numItems = testWidget->count(); - QTest::keyPress(testWidget, Qt::Key_Return); - QCOMPARE(testWidget->count(), numItems + 1); - QCOMPARE(testWidget->currentIndex(), numItems); - testWidget->setCurrentIndex(-1); - QTest::keyPress(testWidget, 'a'); - QTest::keyPress(testWidget, 'b'); - QCOMPARE(testWidget->currentIndex(), -1); + comboBox->setCurrentIndex(-1); + comboBox->setInsertPolicy(QComboBox::InsertAtBottom); + QTest::keyPress(comboBox, 'a'); + QTest::keyPress(comboBox, 'b'); + QCOMPARE(comboBox->currentText(), QString("ab")); + QCOMPARE(comboBox->currentIndex(), -1); + int numItems = comboBox->count(); + QTest::keyPress(comboBox, Qt::Key_Return); + QCOMPARE(comboBox->count(), numItems + 1); + QCOMPARE(comboBox->currentIndex(), numItems); + comboBox->setCurrentIndex(-1); + QTest::keyPress(comboBox, 'a'); + QTest::keyPress(comboBox, 'b'); + QCOMPARE(comboBox->currentIndex(), -1); } } } @@ -1250,8 +1248,8 @@ void tst_QComboBox::insertItems_data() QTest::newRow("prepend") << initialItems << insertedItems << 0 << 0; QTest::newRow("prepend with negative value") << initialItems << insertedItems << -1 << 0; - QTest::newRow("append") << initialItems << insertedItems << initialItems.count() << initialItems.count(); - QTest::newRow("append with too high value") << initialItems << insertedItems << 999 << initialItems.count(); + QTest::newRow("append") << initialItems << insertedItems << initialItems.size() << initialItems.size(); + QTest::newRow("append with too high value") << initialItems << insertedItems << 999 << initialItems.size(); QTest::newRow("insert") << initialItems << insertedItems << 1 << 1; } @@ -1267,12 +1265,12 @@ void tst_QComboBox::insertItems() QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); QComboBox *testWidget = topLevel.comboBox(); testWidget->insertItems(0, initialItems); - QCOMPARE(testWidget->count(), initialItems.count()); + QCOMPARE(testWidget->count(), initialItems.size()); testWidget->insertItems(insertIndex, insertedItems); - QCOMPARE(testWidget->count(), initialItems.count() + insertedItems.count()); - for (int i=0; i<insertedItems.count(); ++i) + QCOMPARE(testWidget->count(), initialItems.size() + insertedItems.size()); + for (int i=0; i<insertedItems.size(); ++i) QCOMPARE(testWidget->itemText(expectedIndex + i), insertedItems.at(i)); } @@ -1288,11 +1286,12 @@ void tst_QComboBox::insertItem_data() initialItems << "foo" << "bar"; for(int e = 0 ; e<2 ; e++) { bool editable = (e==0); - QTest::newRow("Insert less then 0") << initialItems << -1 << "inserted" << 0 << editable; - QTest::newRow("Insert at 0") << initialItems << 0 << "inserted" << 0 << editable; - QTest::newRow("Insert beyond count") << initialItems << 3 << "inserted" << 2 << editable; - QTest::newRow("Insert at count") << initialItems << 2 << "inserted" << 2 << editable; - QTest::newRow("Insert in the middle") << initialItems << 1 << "inserted" << 1 << editable; + const auto txt = editable ? QByteArray("editable: ") : QByteArray("non-editable: "); + QTest::newRow(txt + "Insert less then 0") << initialItems << -1 << "inserted" << 0 << editable; + QTest::newRow(txt + "Insert at 0") << initialItems << 0 << "inserted" << 0 << editable; + QTest::newRow(txt + "Insert beyond count") << initialItems << 3 << "inserted" << 2 << editable; + QTest::newRow(txt + "Insert at count") << initialItems << 2 << "inserted" << 2 << editable; + QTest::newRow(txt + "Insert in the middle") << initialItems << 1 << "inserted" << 1 << editable; } } @@ -1309,14 +1308,14 @@ void tst_QComboBox::insertItem() QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); QComboBox *testWidget = topLevel.comboBox(); testWidget->insertItems(0, initialItems); - QCOMPARE(testWidget->count(), initialItems.count()); + QCOMPARE(testWidget->count(), initialItems.size()); testWidget->setEditable(true); if (editable) testWidget->setEditText("FOO"); testWidget->insertItem(insertIndex, itemLabel); - QCOMPARE(testWidget->count(), initialItems.count() + 1); + QCOMPARE(testWidget->count(), initialItems.size() + 1); QCOMPARE(testWidget->itemText(expectedIndex), itemLabel); if (editable) @@ -1384,21 +1383,21 @@ void tst_QComboBox::textpixmapdata() QFETCH(IconList, icons); QFETCH(VariantList, variant); - QVERIFY(text.count() == icons.count() && text.count() == variant.count()); + QVERIFY(text.size() == icons.size() && text.size() == variant.size()); TestWidget topLevel; topLevel.show(); QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); QComboBox *testWidget = topLevel.comboBox(); - for (int i = 0; i<text.count(); ++i) { + for (int i = 0; i<text.size(); ++i) { testWidget->insertItem(i, text.at(i)); testWidget->setItemIcon(i, icons.at(i)); testWidget->setItemData(i, variant.at(i), Qt::UserRole); } - QCOMPARE(testWidget->count(), text.count()); + QCOMPARE(testWidget->count(), text.size()); - for (int i = 0; i<text.count(); ++i) { + for (int i = 0; i<text.size(); ++i) { QIcon icon = testWidget->itemIcon(i); QCOMPARE(icon.cacheKey(), icons.at(i).cacheKey()); QPixmap original = icons.at(i).pixmap(1024); @@ -1406,7 +1405,7 @@ void tst_QComboBox::textpixmapdata() QCOMPARE(pixmap.toImage(), original.toImage()); } - for (int i = 0; i<text.count(); ++i) { + for (int i = 0; i<text.size(); ++i) { QCOMPARE(testWidget->itemText(i), text.at(i)); // ### we should test icons/pixmap as well, but I need to fix the api mismatch first QCOMPARE(testWidget->itemData(i, Qt::UserRole), variant.at(i)); @@ -1480,14 +1479,12 @@ void tst_QComboBox::setCurrentText() else QCOMPARE(testWidget->currentText(), QString("foo")); -#ifndef QT_NO_PROPERTIES // verify WRITE for currentText property testWidget->setCurrentIndex(0); const QByteArray n("currentText"); QCOMPARE(testWidget->property(n).toString(), QString("foo")); testWidget->setProperty(n, QString("bar")); QCOMPARE(testWidget->property(n).toString(), QString("bar")); -#endif } void tst_QComboBox::currentTextChanged_data() @@ -1509,42 +1506,70 @@ void tst_QComboBox::currentTextChanged() testWidget->addItems(QStringList() << "foo" << "bar"); QCOMPARE(testWidget->count(), 2); - QSignalSpy spy(testWidget, SIGNAL(currentTextChanged(QString))); + QSignalSpy textChangedSpy(testWidget, &QComboBox::currentTextChanged); testWidget->setEditable(editable); // set text in list testWidget->setCurrentIndex(0); QCOMPARE(testWidget->currentIndex(), 0); - spy.clear(); + textChangedSpy.clear(); testWidget->setCurrentText(QString("bar")); - QCOMPARE(spy.count(), 1); - QCOMPARE(qvariant_cast<QString>(spy.at(0).at(0)), QString("bar")); + QCOMPARE(textChangedSpy.size(), 1); + QCOMPARE(qvariant_cast<QString>(textChangedSpy.at(0).at(0)), QString("bar")); // set text not in list testWidget->setCurrentIndex(0); QCOMPARE(testWidget->currentIndex(), 0); - spy.clear(); + textChangedSpy.clear(); testWidget->setCurrentText(QString("qt")); if (editable) { - QCOMPARE(spy.count(), 1); - QCOMPARE(qvariant_cast<QString>(spy.at(0).at(0)), QString("qt")); + QCOMPARE(textChangedSpy.size(), 1); + QCOMPARE(qvariant_cast<QString>(textChangedSpy.at(0).at(0)), QString("qt")); } else { - QCOMPARE(spy.count(), 0); + QCOMPARE(textChangedSpy.size(), 0); } // item changed testWidget->setCurrentIndex(0); QCOMPARE(testWidget->currentIndex(), 0); - spy.clear(); + textChangedSpy.clear(); testWidget->setItemText(0, QString("ape")); - QCOMPARE(spy.count(), 1); - QCOMPARE(qvariant_cast<QString>(spy.at(0).at(0)), QString("ape")); + QCOMPARE(textChangedSpy.size(), 1); + QCOMPARE(qvariant_cast<QString>(textChangedSpy.at(0).at(0)), QString("ape")); + // change it back - spy.clear(); + textChangedSpy.clear(); testWidget->setItemText(0, QString("foo")); - QCOMPARE(spy.count(), 1); - QCOMPARE(qvariant_cast<QString>(spy.at(0).at(0)), QString("foo")); + QCOMPARE(textChangedSpy.size(), 1); + QCOMPARE(qvariant_cast<QString>(textChangedSpy.at(0).at(0)), QString("foo")); + + // currentIndexChanged vs. currentTextChanged + testWidget->clear(); + testWidget->addItems(QStringList() << "first" << "second" << "third" << "fourth" << "fourth"); + testWidget->setCurrentIndex(4); + textChangedSpy.clear(); + QSignalSpy indexChangedSpy(testWidget, &QComboBox::currentIndexChanged); + + // Index change w/o text change + testWidget->removeItem(3); + QCOMPARE(textChangedSpy.count(), 0); + QCOMPARE(indexChangedSpy.count(), 1); + + // Index and text change + testWidget->setCurrentIndex(0); + QCOMPARE(textChangedSpy.count(), 1); + QCOMPARE(indexChangedSpy.count(), 2); + + // remove item above current index + testWidget->removeItem(2); + QCOMPARE(textChangedSpy.count(), 1); + QCOMPARE(indexChangedSpy.count(), 2); + + // Text change w/o index change + testWidget->setItemText(0, "first class"); + QCOMPARE(textChangedSpy.count(), 2); + QCOMPARE(indexChangedSpy.count(), 2); } void tst_QComboBox::editTextChanged() @@ -1568,13 +1593,13 @@ void tst_QComboBox::editTextChanged() QCOMPARE(testWidget->currentIndex(), 0); testWidget->setCurrentIndex(0); QCOMPARE(testWidget->currentIndex(), 0); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); // no signal should be sent when changing to other index because we are not editable QCOMPARE(testWidget->currentIndex(), 0); testWidget->setCurrentIndex(1); QCOMPARE(testWidget->currentIndex(), 1); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); // now set to editable and reset current index testWidget->setEditable(true); @@ -1586,20 +1611,20 @@ void tst_QComboBox::editTextChanged() QCOMPARE(testWidget->currentIndex(), 0); testWidget->setCurrentIndex(0); QCOMPARE(testWidget->currentIndex(), 0); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); // signal should be sent when changing to other index QCOMPARE(testWidget->currentIndex(), 0); testWidget->setCurrentIndex(1); QCOMPARE(testWidget->currentIndex(), 1); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(qvariant_cast<QString>(spy.at(0).at(0)), QString("bar")); // insert some keys and notice they are all signaled spy.clear(); QTest::keyClicks(testWidget, "bingo"); - QCOMPARE(spy.count(), 5); + QCOMPARE(spy.size(), 5); QCOMPARE(qvariant_cast<QString>(spy.at(4).at(0)), QString("barbingo")); } @@ -1652,6 +1677,16 @@ void tst_QComboBox::setModel() QCOMPARE(box.rootModelIndex(), rootModelIndex); box.setModel(box.model()); QCOMPARE(box.rootModelIndex(), rootModelIndex); + + // check that setting the same model as the completer's doesn't crash + QCompleter *completer = new QCompleter(&box); + box.setEditable(true); + box.setCompleter(completer); + auto *listModel = new QStringListModel({ "one", "two" }, completer); + completer->setModel(listModel); + QCOMPARE(listModel->rowCount(), 2); // make sure it wasn't deleted + box.setModel(listModel); + QCOMPARE(listModel->rowCount(), 2); // make sure it wasn't deleted } void tst_QComboBox::setCustomModelAndView() @@ -1756,7 +1791,7 @@ void tst_QComboBox::setMaxCount() // insert 5 items at pos 2. Make sure only two get inserted QSignalSpy spy(box.model(), SIGNAL(rowsInserted(QModelIndex,int,int))); box.insertItems(2, items); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(spy.at(0).at(1).toInt(), 2); QCOMPARE(spy.at(0).at(2).toInt(), 3); @@ -1986,7 +2021,7 @@ void tst_QComboBox::flaggedItems_data() disableFlagList << 1; keyMovementList.clear(); keyMovementList << Qt::Key_T << Qt::Key_Enter; - QTest::newRow(testCase.toLatin1() + "disabled") << itemList << deselectFlagList << disableFlagList << keyMovementList << bool(editable) << 2; + QTest::newRow(testCase.toLatin1() + "disabled with key") << itemList << deselectFlagList << disableFlagList << keyMovementList << bool(editable) << 2; QTest::newRow(testCase.toLatin1() + "broken autocompletion") << itemList << deselectFlagList << disableFlagList << keyMovementList << bool(editable) << 2; } } @@ -1998,8 +2033,8 @@ void tst_QComboBox::flaggedItems() QSKIP("Wayland: This fails. Figure out why."); QFETCH(QStringList, itemList); - QFETCH(IntList, deselectFlagList); - QFETCH(IntList, disableFlagList); + QFETCH(const IntList, deselectFlagList); + QFETCH(const IntList, disableFlagList); QFETCH(KeyList, keyMovementList); QFETCH(bool, editable); QFETCH(int, expectedIndex); @@ -2010,17 +2045,16 @@ void tst_QComboBox::flaggedItems() listWidget.addItems(itemList); comboBox.setEditable(editable); - foreach (int index, deselectFlagList) + for (int index : deselectFlagList) listWidget.item(index)->setFlags(listWidget.item(index)->flags() & ~Qt::ItemIsSelectable); - foreach (int index, disableFlagList) + for (int index : disableFlagList) listWidget.item(index)->setFlags(listWidget.item(index)->flags() & ~Qt::ItemIsEnabled); comboBox.setModel(listWidget.model()); comboBox.setView(&listWidget); comboBox.move(200, 200); comboBox.show(); - QApplication::setActiveWindow(&comboBox); comboBox.activateWindow(); comboBox.setFocus(); QVERIFY(QTest::qWaitForWindowActive(&comboBox)); @@ -2030,7 +2064,7 @@ void tst_QComboBox::flaggedItems() if (editable) comboBox.lineEdit()->selectAll(); - for (int i = 0; i < keyMovementList.count(); ++i) { + for (int i = 0; i < keyMovementList.size(); ++i) { Qt::Key key = keyMovementList[i]; QTest::keyClick(&comboBox, key); } @@ -2096,7 +2130,7 @@ void tst_QComboBox::mouseWheel_data() void tst_QComboBox::mouseWheel() { - QFETCH(IntList, disabledItems); + QFETCH(const IntList, disabledItems); QFETCH(int, startIndex); QFETCH(int, wheelDirection); QFETCH(int, expectedIndex); @@ -2111,7 +2145,7 @@ void tst_QComboBox::mouseWheel() QListWidget listWidget; listWidget.addItems(list); - foreach (int index, disabledItems) + for (int index : disabledItems) listWidget.item(index)->setFlags(listWidget.item(index)->flags() & ~Qt::ItemIsEnabled); box.setModel(listWidget.model()); @@ -2245,13 +2279,13 @@ void tst_QComboBox::separatorItem_data() void tst_QComboBox::separatorItem() { QFETCH(QStringList, items); - QFETCH(IntList, separators); + QFETCH(const IntList, separators); QComboBox box; box.addItems(items); - foreach(int index, separators) + for (int index : separators) box.insertSeparator(index); - QCOMPARE(box.count(), (items.count() + separators.count())); + QCOMPARE(box.count(), (items.size() + separators.size())); for (int i = 0, s = 0; i < box.count(); ++i) { if (i == separators.at(s)) { QCOMPARE(box.itemText(i), QString()); @@ -2370,7 +2404,8 @@ void tst_QComboBox::task191329_size() QFrame *container = tableCombo.findChild<QComboBoxPrivateContainer *>(); QVERIFY(container); QCOMPARE(static_cast<QAbstractItemView *>(table), container->findChild<QAbstractItemView *>()); - foreach (QWidget *button, container->findChildren<QComboBoxPrivateScroller *>()) { + const auto buttons = container->findChildren<QComboBoxPrivateScroller *>(); + for (QWidget *button : buttons) { //the popup should be large enough to contains everithing so the top and left button are hidden QVERIFY(!button->isVisible()); } @@ -2449,7 +2484,6 @@ void tst_QComboBox::task247863_keyBoardSelection() combo.addItem( QLatin1String("111")); combo.addItem( QLatin1String("222")); combo.show(); - QApplication::setActiveWindow(&combo); QTRY_COMPARE(QApplication::activeWindow(), static_cast<QWidget *>(&combo)); QSignalSpy spy(&combo, &QComboBox::activated); @@ -2459,7 +2493,7 @@ void tst_QComboBox::task247863_keyBoardSelection() QTest::keyClick(static_cast<QWidget *>(0), Qt::Key_Down); QTest::keyClick(static_cast<QWidget *>(0), Qt::Key_Enter); QCOMPARE(combo.currentText(), QLatin1String("222")); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); } void tst_QComboBox::task220195_keyBoardSelection2() @@ -2475,7 +2509,6 @@ void tst_QComboBox::task220195_keyBoardSelection2() combo.addItem( QLatin1String("foo2")); combo.addItem( QLatin1String("foo3")); combo.show(); - QApplication::setActiveWindow(&combo); QVERIFY(QTest::qWaitForWindowActive(&combo)); combo.setCurrentIndex(-1); @@ -2734,16 +2767,16 @@ void tst_QComboBox::resetModel() QComboBox cb; StringListModel model({"1", "2"}); QSignalSpy spy(&cb, &QComboBox::currentIndexChanged); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); QCOMPARE(cb.currentIndex(), -1); //no selection cb.setModel(&model); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(cb.currentIndex(), 0); //first item selected model.reset(); - QCOMPARE(spy.count(), 2); + QCOMPARE(spy.size(), 2); QCOMPARE(cb.currentIndex(), 0); //first item selected } @@ -2761,7 +2794,6 @@ void tst_QComboBox::keyBoardNavigationWithMouse() combo.move(200, 200); combo.showNormal(); - QApplication::setActiveWindow(&combo); QVERIFY(QTest::qWaitForWindowActive(&combo)); QCOMPARE(combo.currentText(), QLatin1String("0")); @@ -2817,17 +2849,16 @@ void tst_QComboBox::task_QTBUG_1071_changingFocusEmitsActivated() layout.addWidget(&edit); w.show(); - QApplication::setActiveWindow(&w); QVERIFY(QTest::qWaitForWindowActive(&w)); cb.clearEditText(); cb.setFocus(); QApplication::processEvents(); QTRY_VERIFY(cb.hasFocus()); QTest::keyClick(static_cast<QWidget *>(0), '1'); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); edit.setFocus(); QTRY_VERIFY(edit.hasFocus()); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE(spy.size(), 1); } void tst_QComboBox::maxVisibleItems_data() @@ -3169,31 +3200,55 @@ void tst_QComboBox::task_QTBUG_54191_slotOnEditTextChangedSetsComboBoxToReadOnly QCOMPARE(cb.currentIndex(), 1); } +class ComboBox : public QComboBox { +public: + using QComboBox::QComboBox; + + void keyPressEvent(QKeyEvent *e) override + { + QComboBox::keyPressEvent(e); + accepted = e->isAccepted(); + } + bool accepted = false; +}; + void tst_QComboBox::keyboardSelection() { - QComboBox comboBox; + ComboBox comboBox; const int keyboardInterval = QApplication::keyboardInputInterval(); - QStringList list; - list << "OA" << "OB" << "OC" << "OO" << "OP" << "PP"; + const QStringList list = {"OA", "OB", "OC", "OO", "OP", "PP"}; comboBox.addItems(list); // Clear any remaining keyboard input from previous tests. QTest::qWait(keyboardInterval); QTest::keyClicks(&comboBox, "oo", Qt::NoModifier, 50); QCOMPARE(comboBox.currentText(), list.at(3)); + QCOMPARE(comboBox.accepted, true); QTest::qWait(keyboardInterval); QTest::keyClicks(&comboBox, "op", Qt::NoModifier, 50); QCOMPARE(comboBox.currentText(), list.at(4)); + QCOMPARE(comboBox.accepted, true); QTest::keyClick(&comboBox, Qt::Key_P, Qt::NoModifier, keyboardInterval); QCOMPARE(comboBox.currentText(), list.at(5)); + QCOMPARE(comboBox.accepted, true); QTest::keyClick(&comboBox, Qt::Key_O, Qt::NoModifier, keyboardInterval); QCOMPARE(comboBox.currentText(), list.at(0)); + QCOMPARE(comboBox.accepted, true); QTest::keyClick(&comboBox, Qt::Key_O, Qt::NoModifier, keyboardInterval); QCOMPARE(comboBox.currentText(), list.at(1)); + QCOMPARE(comboBox.accepted, true); + + QTest::keyClick(&comboBox, Qt::Key_Tab, Qt::NoModifier, keyboardInterval); + QCOMPARE(comboBox.currentText(), list.at(1)); + QCOMPARE(comboBox.accepted, false); + + QTest::keyClick(&comboBox, Qt::Key_Tab, Qt::ControlModifier, keyboardInterval); + QCOMPARE(comboBox.currentText(), list.at(1)); + QCOMPARE(comboBox.accepted, false); } void tst_QComboBox::updateDelegateOnEditableChange() @@ -3245,11 +3300,11 @@ void tst_QComboBox::respectChangedOwnershipOfItemView() QTableView *v2 = new QTableView(&box1); box1.setView(v2); // Here we do not expect v1 to be deleted QApplication::processEvents(); - QCOMPARE(spy1.count(), 0); + QCOMPARE(spy1.size(), 0); QSignalSpy spy2(v2, SIGNAL(destroyed())); box1.setView(v1); - QCOMPARE(spy2.count(), 1); + QCOMPARE(spy2.size(), 1); } void tst_QComboBox::task_QTBUG_49831_scrollerNotActivated() @@ -3270,14 +3325,14 @@ void tst_QComboBox::task_QTBUG_49831_scrollerNotActivated() QVERIFY(container); QVERIFY(QTest::qWaitForWindowExposed(container)); - QList<QComboBoxPrivateScroller *> scrollers = container->findChildren<QComboBoxPrivateScroller *>(); + const QList<QComboBoxPrivateScroller *> scrollers = container->findChildren<QComboBoxPrivateScroller *>(); // Not all styles support scrollers. We rely only on those platforms that do to catch any regression. if (!scrollers.isEmpty()) { - Q_FOREACH (QComboBoxPrivateScroller *scroller, scrollers) { + for (QComboBoxPrivateScroller *scroller : scrollers) { if (scroller->isVisible()) { QSignalSpy doScrollSpy(scroller, SIGNAL(doScroll(int))); QTest::mouseMove(scroller, QPoint(5, 5), 500); - QTRY_VERIFY(doScrollSpy.count() > 0); + QTRY_VERIFY(doScrollSpy.size() > 0); } } } @@ -3355,6 +3410,65 @@ void tst_QComboBox::task_QTBUG_56693_itemFontFromModel() box.hidePopup(); } +#ifndef QT_NO_STYLE_FUSION +void tst_QComboBox::popupPositionAfterStyleChange() +{ +#ifdef Q_OS_QNX + QSKIP("Fails on QNX, QTBUG-123798"); +#endif + // Check that the popup opens up centered on top of the current + // index if the style has changed since the last time it was + // opened (QTBUG-113765). + QComboBox box; + QStyleOptionComboBox opt; + const bool usePopup = qApp->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, &box); + if (!usePopup) + QSKIP("This test is only relevant for styles that centers the popup on top of the combo!"); + if (QTestPrivate::isRunningArmOnX86()) + QSKIP("Flaky on QEMU, QTBUG-114760"); + + box.addItems({"first", "middle", "last"}); + box.show(); + QVERIFY(QTest::qWaitForWindowExposed(&box)); + box.showPopup(); + + QFrame *container = box.findChild<QComboBoxPrivateContainer *>(); + QVERIFY(container); + QVERIFY(QTest::qWaitForWindowExposed(container)); + + // Select the last menu item, which will close the popup. This item is then expected + // to be centered on top of the combobox the next time the popup opens. + const QRect lastItemRect = box.view()->visualRect(box.view()->model()->index(2, 0)); + QTest::mouseClick(box.view(), Qt::LeftButton, Qt::NoModifier, lastItemRect.center()); + + // Change style. This can make the popup smaller, which will result in up-and-down + // scroll widgets showing in the menu, directly underneath the mouse before the popup + // ends up hidden. This again will trigger the item view to scroll, which seems to be + // the root cause of QTBUG-113765. + qApp->setStyle(QStringLiteral("Fusion")); + + // Click on the combobox again to reopen it. But since both QComboBox + // (QComboBoxPrivateScroller) is using its own internal timer to do scrolling, we + // need to wait a bit until the scrolling is done before we can reopen it (since + // the scrolling is the sore spot that we want to test). + // But note, we expect, but don't require, the popup to scroll. And for that + // reason, we don't see it as a failure if the scrolling doesn't happen. + (void) QTest::qWaitFor([&box]{ return box.view()->verticalScrollBar()->value() > 0; }, 1000); + + // Verify that the popup is hidden before we click the button + QTRY_VERIFY(!container->isVisible()); + QTest::mouseClick(&box, Qt::LeftButton); + + // Click on item under mouse. But wait a bit, to avoid a double click + QTest::qWait(2 * QGuiApplication::styleHints()->mouseDoubleClickInterval()); + QTest::mouseClick(&box, Qt::LeftButton); + + // Ensure that the item that was centered on top of the combobox, and which + // we therefore clicked, was the same item we clicked on the first time. + QTRY_COMPARE(box.currentText(), QStringLiteral("last")); +} +#endif // QT_NO_STYLE_FUSION + void tst_QComboBox::inputMethodUpdate() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) @@ -3434,10 +3548,9 @@ void tst_QComboBox::task_QTBUG_52027_mapCompleterIndex() cbox.setCompleter(completer); QSignalSpy spy(&cbox, SIGNAL(activated(int))); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); cbox.move(200, 200); cbox.show(); - QApplication::setActiveWindow(&cbox); QVERIFY(QTest::qWaitForWindowActive(&cbox)); QTest::keyClicks(&cbox, "foobar2"); @@ -3459,11 +3572,10 @@ void tst_QComboBox::task_QTBUG_52027_mapCompleterIndex() completer->setModel(model); if (QGuiApplication::platformName() == "offscreen") { - QWARN("Offscreen platform requires explicit activateWindow()"); + qWarning("Offscreen platform requires explicit activateWindow()"); cbox.activateWindow(); } - QApplication::setActiveWindow(&cbox); QVERIFY(QTest::qWaitForWindowActive(&cbox)); QTest::keyClicks(&cbox, "foobar1"); @@ -3531,7 +3643,6 @@ void tst_QComboBox::checkEmbeddedLineEditWhenStyleSheetIsSet() layout->addWidget(comboBox); topLevel.show(); comboBox->setEditable(true); - QApplication::setActiveWindow(&topLevel); QVERIFY(QTest::qWaitForWindowActive(&topLevel)); QImage grab = comboBox->grab().toImage(); @@ -3544,5 +3655,189 @@ void tst_QComboBox::checkEmbeddedLineEditWhenStyleSheetIsSet() qApp->setStyleSheet(oldCss); } +/*! + Tests that the style-based frame style propagates to the internal container + widget of QComboBox when the style changes by verifying that the respective + styleHint is asked for when the style changes. + + See QTBUG-92488 +*/ +void tst_QComboBox::propagateStyleChanges() +{ + class FrameStyle : public QProxyStyle + { + public: + FrameStyle(int frameStyle, QStyle *style = nullptr) + : QProxyStyle(style), frameStyle(frameStyle) + {} + + int styleHint(QStyle::StyleHint hint, const QStyleOption *opt, + const QWidget *widget, QStyleHintReturn *returnData) const override + { + if (hint == QStyle::SH_ComboBox_PopupFrameStyle) { + inquired = true; + return frameStyle; + } + return QProxyStyle::styleHint(hint, opt, widget, returnData); + } + + int frameStyle; + mutable bool inquired = false; + }; + + FrameStyle framelessStyle(QFrame::NoFrame); + FrameStyle frameStyle(QFrame::Plain | QFrame::Sunken); + + QComboBox combo; + // container will be created and take settings from this style + combo.setStyle(&framelessStyle); + QVERIFY(framelessStyle.inquired); + combo.addItem(QLatin1String("Open")); + combo.addItem(QLatin1String("Close")); + // needed because of QComboBox's adjustSizeTimer not doing anything otherwise + combo.setSizeAdjustPolicy(QComboBox::AdjustToContents); + combo.setStyle(&frameStyle); + QVERIFY(frameStyle.inquired); +} + +void tst_QComboBox::buttonPressKeys() +{ + QComboBox comboBox; + comboBox.setEditable(false); + comboBox.addItem(QString::number(1)); + comboBox.addItem(QString::number(2)); + const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme() + ->themeHint(QPlatformTheme::ButtonPressKeys) + .value<QList<Qt::Key>>(); + for (int i = 0; i < buttonPressKeys.size(); ++i) { + QTest::keyClick(&comboBox, buttonPressKeys[i]); + // On some platforms, a window will not be immediately visible, + // but take some event-loop iterations to complete. + // Using QTRY_VERIFY to deal with that. + QTRY_VERIFY(comboBox.view()->isVisible()); + comboBox.hidePopup(); + } +} + +void tst_QComboBox::clearModel() +{ + using namespace Qt::StringLiterals; + QStringListModel model({ "one"_L1, "two"_L1, "three"_L1 }); + + QComboBox combo; + combo.setModel(&model); + combo.setCurrentIndex(1); + + QCOMPARE(combo.currentIndex(), 1); + QCOMPARE(combo.currentText(), model.index(1).data().toString()); + + QSignalSpy indexSpy(&combo, &QComboBox::currentIndexChanged); + QSignalSpy textSpy(&combo, &QComboBox::currentTextChanged); + + QVERIFY(indexSpy.isEmpty()); + QVERIFY(textSpy.isEmpty()); + + model.setStringList({}); + + QCOMPARE(indexSpy.size(), 1); + const int index = indexSpy.takeFirst().at(0).toInt(); + QCOMPARE(index, -1); + + QCOMPARE(textSpy.size(), 1); + const QString text = textSpy.takeFirst().at(0).toString(); + QCOMPARE(text, QString()); + + QCOMPARE(combo.currentIndex(), -1); + QCOMPARE(combo.currentText(), QString()); +} + +void tst_QComboBox::cancelClosesPopupNotDialog() +{ + if (QGuiApplication::platformName() == "offscreen") + QSKIP("The offscreen platform plugin doesn't activate popups."); + + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) + QSKIP("QWindow::requestActivate() is not supported."); + + QDialog dialog; + QComboBox combobox; + combobox.addItems({"A", "B", "C"}); + + std::unique_ptr<QShortcut> shortcut(new QShortcut(QKeySequence::Cancel, &dialog)); + bool shortcutTriggered = false; + connect(shortcut.get(), &QShortcut::activated, [&shortcutTriggered]{ + shortcutTriggered = true; + }); + + QVBoxLayout vbox; + vbox.addWidget(&combobox); + dialog.setLayout(&vbox); + + dialog.show(); + QVERIFY(QTest::qWaitForWindowActive(&dialog)); + + // while the combobox is closed, escape key triggers the shortcut + QTest::keyClick(dialog.window()->windowHandle(), Qt::Key_Escape); + QVERIFY(shortcutTriggered); + shortcutTriggered = false; + + combobox.showPopup(); + QTRY_VERIFY(combobox.view()->isVisible()); + + // an open combobox overrides and accepts the escape key to close + QTest::keyClick(dialog.window()->windowHandle(), Qt::Key_Escape); + QVERIFY(!shortcutTriggered); + shortcutTriggered = false; + QTRY_VERIFY(!combobox.view()->isVisible()); + QVERIFY(dialog.isVisible()); + + // once closed, escape key triggers the shortcut again + QTest::keyClick(dialog.window()->windowHandle(), Qt::Key_Escape); + QVERIFY(shortcutTriggered); + shortcutTriggered = false; + QVERIFY(dialog.isVisible()); + + shortcut.reset(); + + // without shortcut, escape key propagates to the parent + QTest::keyClick(dialog.window()->windowHandle(), Qt::Key_Escape); + QVERIFY(!dialog.isVisible()); +} + +void tst_QComboBox::closePopupWithCheckableItems() +{ + QWidget widget; + + QVBoxLayout *vb = new QVBoxLayout(&widget); + + QLabel *dlgLabel = new QLabel("Click when combo expanded."); + vb->addWidget(dlgLabel); + + QComboBox *combo = new QComboBox(); + vb->addWidget(combo); + + QStandardItemModel model; + const int rowCount = 10; + for (int r = 0; r < rowCount; ++r) { + QString str = "Item: " + QString::number(r); + QStandardItem *item = new QStandardItem(str); + const bool isChecked = (r % 2); + + item->setData(isChecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); + item->setFlags(Qt::ItemIsUserCheckable | (item->flags() & ~(Qt::ItemIsSelectable)) ); + model.appendRow(item); + } + + combo->setModel(&model); + + widget.show(); + QVERIFY(QTest::qWaitForWindowExposed(&widget)); + + QTest::mouseClick(widget.windowHandle(), Qt::LeftButton, {}, combo->geometry().center()); + QVERIFY(QTest::qWaitForWindowExposed(combo->view())); + QTest::mouseClick(widget.windowHandle(), Qt::LeftButton, {}, dlgLabel->geometry().center()); + QTRY_VERIFY(!combo->view()->isVisible()); +} + QTEST_MAIN(tst_QComboBox) #include "tst_qcombobox.moc" |