1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QtTest>
30#include <QtGui>
31#include <QtWidgets>
32#include <QtDebug>
33#include <QPair>
34#include <QList>
35#include <QPointer>
36
37#include <QtTest/private/qtesthelpers_p.h>
38
39#include "../../../../shared/filesystem.h"
40
41#include <memory>
42
43Q_DECLARE_METATYPE(QCompleter::CompletionMode)
44
45using namespace QTestPrivate;
46
47class CsvCompleter : public QCompleter
48{
49 Q_OBJECT
50public:
51 using QCompleter::QCompleter;
52
53 QString pathFromIndex(const QModelIndex& sourceIndex) const override;
54
55 void setCsvCompletion(bool set) { csv = set; }
56
57protected:
58 QStringList splitPath(const QString &path) const override
59 {
60 return csv ? path.split(sep: QLatin1Char(',')) : QCompleter::splitPath(path);
61 }
62
63private:
64 bool csv = true;
65};
66
67QString CsvCompleter::pathFromIndex(const QModelIndex &sourceIndex) const
68{
69 if (!csv)
70 return QCompleter::pathFromIndex(index: sourceIndex);
71
72 if (!sourceIndex.isValid())
73 return QString();
74
75 QModelIndex idx = sourceIndex;
76 QStringList list;
77 do {
78 QString t = model()->data(index: idx, role: completionRole()).toString();
79 list.prepend(t);
80 QModelIndex parent = idx.parent();
81 idx = parent.sibling(arow: parent.row(), acolumn: sourceIndex.column());
82 } while (idx.isValid());
83
84 return list.count() == 1 ? list.constFirst() : list.join(sep: QLatin1Char(','));
85}
86
87class tst_QCompleter : public QObject
88{
89 Q_OBJECT
90public:
91 tst_QCompleter();
92 ~tst_QCompleter();
93
94private slots:
95 void getSetCheck();
96
97 void multipleWidgets();
98 void focusIn();
99
100 void csMatchingOnCsSortedModel_data();
101 void csMatchingOnCsSortedModel();
102 void ciMatchingOnCiSortedModel_data();
103 void ciMatchingOnCiSortedModel();
104
105 void ciMatchingOnCsSortedModel_data();
106 void ciMatchingOnCsSortedModel();
107 void csMatchingOnCiSortedModel_data();
108 void csMatchingOnCiSortedModel();
109
110#if QT_CONFIG(dirmodel) && QT_DEPRECATED_SINCE(5, 15)
111 void directoryModel_data();
112 void directoryModel();
113#endif
114 void fileSystemModel_data();
115 void fileSystemModel();
116 void fileDialog_data();
117 void fileDialog();
118
119 void changingModel_data();
120 void changingModel();
121
122 void sortedEngineRowCount_data();
123 void sortedEngineRowCount();
124 void unsortedEngineRowCount_data();
125 void unsortedEngineRowCount();
126
127 void currentRow();
128 void sortedEngineMapFromSource();
129 void unsortedEngineMapFromSource();
130
131 void historySearch();
132
133 void modelDeletion();
134 void setters();
135
136 void dynamicSortOrder();
137 void disabledItems();
138
139 // task-specific tests below me
140 void task178797_activatedOnReturn();
141 void task189564_omitNonSelectableItems();
142 void task246056_setCompletionPrefix();
143 void task250064_lostFocus();
144
145 void task253125_lineEditCompletion_data();
146 void task253125_lineEditCompletion();
147 void task247560_keyboardNavigation();
148 void QTBUG_14292_filesystem();
149 void QTBUG_52028_tabAutoCompletes();
150 void QTBUG_51889_activatedSentTwice();
151
152private:
153 void filter(bool assync = false);
154 void testRowCount();
155 enum ModelType {
156 CASE_SENSITIVELY_SORTED_MODEL,
157 CASE_INSENSITIVELY_SORTED_MODEL,
158 DIRECTORY_MODEL,
159 HISTORY_MODEL,
160 FILESYSTEM_MODEL
161 };
162 void setSourceModel(ModelType);
163
164 CsvCompleter *completer = nullptr;
165 QTreeWidget *treeWidget;
166 const int completionColumn = 0;
167 const int columnCount = 3;
168};
169
170tst_QCompleter::tst_QCompleter() : treeWidget(new QTreeWidget)
171{
172 treeWidget->move(ax: 100, ay: 100);
173 treeWidget->setColumnCount(columnCount);
174}
175
176tst_QCompleter::~tst_QCompleter()
177{
178 delete treeWidget;
179 delete completer;
180}
181
182void tst_QCompleter::setSourceModel(ModelType type)
183{
184 QTreeWidgetItem *parent, *child;
185 treeWidget->clear();
186 switch(type) {
187 case CASE_SENSITIVELY_SORTED_MODEL:
188 // Creates a tree model with top level items P0, P1, .., p0, p1,..
189 // Each of these items parents have children (for P0 - c0P0, c1P0,...)
190 for (int i = 0; i < 2; i++) {
191 for (int j = 0; j < 5; j++) {
192 parent = new QTreeWidgetItem(treeWidget);
193 const QString text = QLatin1Char(i == 0 ? 'P' : 'p') + QString::number(j);
194 parent->setText(column: completionColumn, atext: text);
195 for (int k = 0; k < 5; k++) {
196 child = new QTreeWidgetItem(parent);
197 QString t = QLatin1Char('c') + QString::number(k) + text;
198 child->setText(column: completionColumn, atext: t);
199 }
200 }
201 }
202 completer->setModel(treeWidget->model());
203 completer->setCompletionColumn(completionColumn);
204 break;
205 case CASE_INSENSITIVELY_SORTED_MODEL:
206 case HISTORY_MODEL:
207 // Creates a tree model with top level items P0, p0, P1, p1,...
208 // Each of these items have children c0p0, c1p0,..
209 for (int i = 0; i < 5; i++) {
210 for (int j = 0; j < 2; j++) {
211 parent = new QTreeWidgetItem(treeWidget);
212 const QString text = QLatin1Char(j == 0 ? 'P' : 'p') + QString::number(i);
213 parent->setText(column: completionColumn, atext: text);
214 for (int k = 0; k < 5; k++) {
215 child = new QTreeWidgetItem(parent);
216 QString t = QLatin1Char('c') + QString::number(k) + text;
217 child->setText(column: completionColumn, atext: t);
218 }
219 }
220 }
221 completer->setModel(treeWidget->model());
222 completer->setCompletionColumn(completionColumn);
223 if (type == CASE_INSENSITIVELY_SORTED_MODEL)
224 break;
225 parent = new QTreeWidgetItem(treeWidget);
226 parent->setText(column: completionColumn, atext: QLatin1String("p3,c3p3"));
227 parent = new QTreeWidgetItem(treeWidget);
228 parent->setText(column: completionColumn, atext: QLatin1String("p2,c4p2"));
229 break;
230 case DIRECTORY_MODEL:
231#if QT_CONFIG(dirmodel) && QT_DEPRECATED_SINCE(5, 15)
232QT_WARNING_PUSH
233QT_WARNING_DISABLE_DEPRECATED
234 completer->setCsvCompletion(false);
235 completer->setModel(new QDirModel(completer));
236 completer->setCompletionColumn(0);
237QT_WARNING_POP
238#endif // QT_CONFIG(dirmodel) && QT_DEPRECATED_SINCE(5, 15)
239 break;
240 case FILESYSTEM_MODEL:
241 completer->setCsvCompletion(false);
242 {
243 auto m = new QFileSystemModel(completer);
244 m->setRootPath("/");
245 completer->setModel(m);
246 }
247 completer->setCompletionColumn(0);
248 break;
249 default:
250 qDebug() << "Invalid type";
251 break;
252 }
253}
254
255void tst_QCompleter::filter(bool assync)
256{
257 QFETCH(QString, filterText);
258 QFETCH(const QString, step);
259 QFETCH(QString, completion);
260 QFETCH(QString, completionText);
261
262 if (filterText.compare(s: "FILTERING_OFF", cs: Qt::CaseInsensitive) == 0) {
263 completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
264 return;
265 }
266
267 int result = -1;
268 const int attempts = assync ? 10 : 1;
269 for (int times = 0; times < attempts; ++times) {
270 completer->setCompletionPrefix(filterText);
271
272 for (QChar s : step) {
273 int row = completer->currentRow();
274 switch (s.toUpper().toLatin1()) {
275 case 'P':
276 --row;
277 break;
278 case 'N':
279 ++row;
280 break;
281 case 'L':
282 row = completer->completionCount() - 1;
283 break;
284 case 'F':
285 row = 0;
286 break;
287 default:
288 QFAIL(qPrintable(QString(
289 "Problem with 'step' value in test data: %1 (only P, N, L and F are allowed)."
290 ).arg(s)));
291 }
292 completer->setCurrentRow(row);
293 }
294
295 result = QString::compare(s1: completer->currentCompletion(), s2: completionText,
296 cs: completer->caseSensitivity());
297 if (result == 0)
298 break;
299 if (assync)
300 QTest::qWait(ms: 50 * times);
301 }
302
303 QCOMPARE(result, 0);
304}
305
306// Testing get/set functions
307void tst_QCompleter::getSetCheck()
308{
309 QStandardItemModel standardItemModel(3,3);
310 QCompleter completer(&standardItemModel);
311
312 // QString QCompleter::completionPrefix()
313 // void QCompleter::setCompletionPrefix(QString)
314 completer.setCompletionPrefix(QString("te"));
315 QCOMPARE(completer.completionPrefix(), QString("te"));
316 completer.setCompletionPrefix(QString());
317 QCOMPARE(completer.completionPrefix(), QString());
318
319 // ModelSorting QCompleter::modelSorting()
320 // void QCompleter::setModelSorting(ModelSorting)
321 completer.setModelSorting(QCompleter::CaseSensitivelySortedModel);
322 QCOMPARE(completer.modelSorting(), QCompleter::CaseSensitivelySortedModel);
323 completer.setModelSorting(QCompleter::CaseInsensitivelySortedModel);
324 QCOMPARE(completer.modelSorting(), QCompleter::CaseInsensitivelySortedModel);
325 completer.setModelSorting(QCompleter::UnsortedModel);
326 QCOMPARE(completer.modelSorting(), QCompleter::UnsortedModel);
327
328 // CompletionMode QCompleter::completionMode()
329 // void QCompleter::setCompletionMode(CompletionMode)
330 QCOMPARE(completer.completionMode(), QCompleter::PopupCompletion); // default value
331 completer.setCompletionMode(QCompleter::UnfilteredPopupCompletion);
332 QCOMPARE(completer.completionMode(), QCompleter::UnfilteredPopupCompletion);
333 completer.setCompletionMode(QCompleter::InlineCompletion);
334 QCOMPARE(completer.completionMode(), QCompleter::InlineCompletion);
335
336 // int QCompleter::completionColumn()
337 // void QCompleter::setCompletionColumn(int)
338 completer.setCompletionColumn(2);
339 QCOMPARE(completer.completionColumn(), 2);
340 completer.setCompletionColumn(1);
341 QCOMPARE(completer.completionColumn(), 1);
342
343 // int QCompleter::completionRole()
344 // void QCompleter::setCompletionRole(int)
345 QCOMPARE(completer.completionRole(), static_cast<int>(Qt::EditRole)); // default value
346 completer.setCompletionRole(Qt::DisplayRole);
347 QCOMPARE(completer.completionRole(), static_cast<int>(Qt::DisplayRole));
348
349 // int QCompleter::maxVisibleItems()
350 // void QCompleter::setMaxVisibleItems(int)
351 QCOMPARE(completer.maxVisibleItems(), 7); // default value
352 completer.setMaxVisibleItems(10);
353 QCOMPARE(completer.maxVisibleItems(), 10);
354 QTest::ignoreMessage(type: QtWarningMsg, message: "QCompleter::setMaxVisibleItems: "
355 "Invalid max visible items (-2147483648) must be >= 0");
356 completer.setMaxVisibleItems(INT_MIN);
357 QCOMPARE(completer.maxVisibleItems(), 10); // Cannot be set to something negative => old value
358
359 // Qt::CaseSensitivity QCompleter::caseSensitivity()
360 // void QCompleter::setCaseSensitivity(Qt::CaseSensitivity)
361 QCOMPARE(completer.caseSensitivity(), Qt::CaseSensitive); // default value
362 completer.setCaseSensitivity(Qt::CaseInsensitive);
363 QCOMPARE(completer.caseSensitivity(), Qt::CaseInsensitive);
364
365 // bool QCompleter::wrapAround()
366 // void QCompleter::setWrapAround(bool)
367 QCOMPARE(completer.wrapAround(), true); // default value
368 completer.setWrapAround(false);
369 QCOMPARE(completer.wrapAround(), false);
370
371#if QT_CONFIG(filesystemmodel)
372 // QTBUG-54642, changing from QFileSystemModel to another model should restore role.
373 completer.setCompletionRole(Qt::EditRole);
374 QCOMPARE(completer.completionRole(), static_cast<int>(Qt::EditRole)); // default value
375 QFileSystemModel fileSystemModel;
376 completer.setModel(&fileSystemModel);
377 QCOMPARE(completer.completionRole(), static_cast<int>(QFileSystemModel::FileNameRole));
378 completer.setModel(&standardItemModel);
379 QCOMPARE(completer.completionRole(), static_cast<int>(Qt::EditRole));
380 completer.setCompletionRole(Qt::ToolTipRole);
381 QStandardItemModel standardItemModel2(2, 2); // Do not clobber a custom role when changing models
382 completer.setModel(&standardItemModel2);
383 QCOMPARE(completer.completionRole(), static_cast<int>(Qt::ToolTipRole));
384#endif // QT_CONFIG(filesystemmodel)
385}
386
387void tst_QCompleter::csMatchingOnCsSortedModel_data()
388{
389 delete completer;
390 completer = new CsvCompleter;
391 completer->setModelSorting(QCompleter::CaseSensitivelySortedModel);
392 completer->setCaseSensitivity(Qt::CaseSensitive);
393 setSourceModel(CASE_SENSITIVELY_SORTED_MODEL);
394
395 QTest::addColumn<QString>(name: "filterText");
396 QTest::addColumn<QString>(name: "step");
397 QTest::addColumn<QString>(name: "completion");
398 QTest::addColumn<QString>(name: "completionText");
399
400#define ROWNAME(name) ((QByteArray(name) + ' ' + QByteArray::number(i)).constData())
401
402 for (int i = 0; i < 2; i++) {
403 if (i == 1)
404 QTest::newRow(dataTag: "FILTERING_OFF") << "FILTERING_OFF" << "" << "" << "";
405
406 // Plain text filter
407 QTest::newRow(ROWNAME("()")) << "" << "" << "P0" << "P0";
408 QTest::newRow(ROWNAME("()F")) << "" << "F" << "P0" << "P0";
409 QTest::newRow(ROWNAME("()L")) << "" << "L" << "p4" << "p4";
410 QTest::newRow(ROWNAME("()N")) << "" << "N" << "P1" << "P1";
411 QTest::newRow(ROWNAME("(P)")) << "P" << "" << "P0" << "P0";
412 QTest::newRow(ROWNAME("(P)F")) << "P" << "" << "P0" << "P0";
413 QTest::newRow(ROWNAME("(P)L")) << "P" << "L" << "P4" << "P4";
414 QTest::newRow(ROWNAME("(p)")) << "p" << "" << "p0" << "p0";
415 QTest::newRow(ROWNAME("(p)N")) << "p" << "N" << "p1" << "p1";
416 QTest::newRow(ROWNAME("(p)NN")) << "p" << "NN" << "p2" << "p2";
417 QTest::newRow(ROWNAME("(p)NNN")) << "p" << "NNN" << "p3" << "p3";
418 QTest::newRow(ROWNAME("(p)NNNN")) << "p" << "NNNN" << "p4" << "p4";
419 QTest::newRow(ROWNAME("(p1)")) << "p1" << "" << "p1" << "p1";
420 QTest::newRow(ROWNAME("(p11)")) << "p11" << "" << "" << "";
421
422 // Tree filter
423 QTest::newRow(ROWNAME("(P0,)")) << "P0," << "" << "c0P0" << "P0,c0P0";
424 QTest::newRow(ROWNAME("(P0,c)")) << "P0,c" << "" << "c0P0" << "P0,c0P0";
425 QTest::newRow(ROWNAME("(P0,c1)")) << "P0,c1" << "" << "c1P0" << "P0,c1P0";
426 QTest::newRow(ROWNAME("(P0,c3P0)")) << "P0,c3P0" << "" << "c3P0" << "P0,c3P0";
427 QTest::newRow(ROWNAME("(P3,c)F")) << "P3,c" << "F" << "c0P3" << "P3,c0P3";
428 QTest::newRow(ROWNAME("(P3,c)L")) << "P3,c" << "L" << "c4P3" << "P3,c4P3";
429 QTest::newRow(ROWNAME("(P3,c)N")) << "P3,c" << "N" << "c1P3" << "P3,c1P3";
430 QTest::newRow(ROWNAME("(P3,c)NN")) << "P3,c" << "NN" << "c2P3" << "P3,c2P3";
431 QTest::newRow(ROWNAME("(P3,,c)")) << "P3,,c" << "" << "" << "";
432 QTest::newRow(ROWNAME("(P3,c0P3,)")) << "P3,c0P3," << "" << "" << "";
433 QTest::newRow(ROWNAME("(P,)")) << "P," << "" << "" << "";
434 }
435#undef ROWNAME
436}
437
438void tst_QCompleter::csMatchingOnCsSortedModel()
439{
440 filter();
441}
442
443void tst_QCompleter::ciMatchingOnCiSortedModel_data()
444{
445 delete completer;
446 completer = new CsvCompleter;
447 completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
448 completer->setCaseSensitivity(Qt::CaseInsensitive);
449 setSourceModel(CASE_INSENSITIVELY_SORTED_MODEL);
450
451 QTest::addColumn<QString>(name: "filterText");
452 QTest::addColumn<QString>(name: "step");
453 QTest::addColumn<QString>(name: "completion");
454 QTest::addColumn<QString>(name: "completionText");
455
456 for (int i = 0; i < 2; i++) {
457 if (i == 1)
458 QTest::newRow(dataTag: "FILTERING_OFF") << "FILTERING_OFF" << "" << "" << "";
459
460 // Plain text filter
461 QTest::newRow(dataTag: "()") << "" << "" << "P0" << "P0";
462 QTest::newRow(dataTag: "()F") << "" << "F" << "P0" << "P0";
463 QTest::newRow(dataTag: "()L") << "" << "L" << "p4" << "p4";
464 QTest::newRow(dataTag: "()N") << "" << "N" << "p0" << "p0";
465 QTest::newRow(dataTag: "(P)") << "P" << "" << "P0" << "P0";
466 QTest::newRow(dataTag: "(P)F") << "P" << "" << "P0" << "P0";
467 QTest::newRow(dataTag: "(P)L") << "P" << "L" << "p4" << "p4";
468 QTest::newRow(dataTag: "(p)") << "p" << "" << "P0" << "P0";
469 QTest::newRow(dataTag: "(p)N") << "p" << "N" << "p0" << "p0";
470 QTest::newRow(dataTag: "(p)NN") << "p" << "NN" << "P1" << "P1";
471 QTest::newRow(dataTag: "(p)NNN") << "p" << "NNN" << "p1" << "p1";
472 QTest::newRow(dataTag: "(p1)") << "p1" << "" << "P1" << "P1";
473 QTest::newRow(dataTag: "(p1)N") << "p1" << "N" << "p1" << "p1";
474 QTest::newRow(dataTag: "(p11)") << "p11" << "" << "" << "";
475
476 //// Tree filter
477 QTest::newRow(dataTag: "(p0,)") << "p0," << "" << "c0P0" << "P0,c0P0";
478 QTest::newRow(dataTag: "(p0,c)") << "p0,c" << "" << "c0P0" << "P0,c0P0";
479 QTest::newRow(dataTag: "(p0,c1)") << "p0,c1" << "" << "c1P0" << "P0,c1P0";
480 QTest::newRow(dataTag: "(p0,c3P0)") << "p0,c3P0" << "" << "c3P0" << "P0,c3P0";
481 QTest::newRow(dataTag: "(p3,c)F") << "p3,c" << "F" << "c0P3" << "P3,c0P3";
482 QTest::newRow(dataTag: "(p3,c)L") << "p3,c" << "L" << "c4P3" << "P3,c4P3";
483 QTest::newRow(dataTag: "(p3,c)N") << "p3,c" << "N" << "c1P3" << "P3,c1P3";
484 QTest::newRow(dataTag: "(p3,c)NN") << "p3,c" << "NN" << "c2P3" << "P3,c2P3";
485 QTest::newRow(dataTag: "(p3,,c)") << "p3,,c" << "" << "" << "";
486 QTest::newRow(dataTag: "(p3,c0P3,)") << "p3,c0P3," << "" << "" << "";
487 QTest::newRow(dataTag: "(p,)") << "p," << "" << "" << "";
488 }
489}
490
491void tst_QCompleter::ciMatchingOnCiSortedModel()
492{
493 filter();
494}
495
496void tst_QCompleter::ciMatchingOnCsSortedModel_data()
497{
498 delete completer;
499 completer = new CsvCompleter;
500 completer->setModelSorting(QCompleter::CaseSensitivelySortedModel);
501 setSourceModel(CASE_SENSITIVELY_SORTED_MODEL);
502 completer->setCaseSensitivity(Qt::CaseInsensitive);
503
504 QTest::addColumn<QString>(name: "filterText");
505 QTest::addColumn<QString>(name: "step");
506 QTest::addColumn<QString>(name: "completion");
507 QTest::addColumn<QString>(name: "completionText");
508
509 for (int i = 0; i < 2; i++) {
510 if (i == 1)
511 QTest::newRow(dataTag: "FILTERING_OFF") << "FILTERING_OFF" << "" << "" << "";
512
513 // Plain text filter
514 QTest::newRow(dataTag: "()") << "" << "" << "P0" << "P0";
515 QTest::newRow(dataTag: "()F") << "" << "F" << "P0" << "P0";
516 QTest::newRow(dataTag: "()L") << "" << "L" << "p4" << "p4";
517 QTest::newRow(dataTag: "(P)") << "P" << "" << "P0" << "P0";
518 QTest::newRow(dataTag: "(P)F") << "P" << "" << "P0" << "P0";
519 QTest::newRow(dataTag: "(P)L") << "P" << "L" << "p4" << "p4";
520 QTest::newRow(dataTag: "(p)") << "p" << "" << "P0" << "P0";
521 QTest::newRow(dataTag: "(p)N") << "p" << "N" << "P1" << "P1";
522 QTest::newRow(dataTag: "(p)NN") << "p" << "NN" << "P2" << "P2";
523 QTest::newRow(dataTag: "(p)NNN") << "p" << "NNN" << "P3" << "P3";
524 QTest::newRow(dataTag: "(p1)") << "p1" << "" << "P1" << "P1";
525 QTest::newRow(dataTag: "(p1)N") << "p1" << "N" << "p1" << "p1";
526 QTest::newRow(dataTag: "(p11)") << "p11" << "" << "" << "";
527
528 // Tree filter
529 QTest::newRow(dataTag: "(p0,)") << "p0," << "" << "c0P0" << "P0,c0P0";
530 QTest::newRow(dataTag: "(p0,c)") << "p0,c" << "" << "c0P0" << "P0,c0P0";
531 QTest::newRow(dataTag: "(p0,c1)") << "p0,c1" << "" << "c1P0" << "P0,c1P0";
532 QTest::newRow(dataTag: "(p0,c3P0)") << "p0,c3P0" << "" << "c3P0" << "P0,c3P0";
533 QTest::newRow(dataTag: "(p3,c)F") << "p3,c" << "F" << "c0P3" << "P3,c0P3";
534 QTest::newRow(dataTag: "(p3,c)L") << "p3,c" << "L" << "c4P3" << "P3,c4P3";
535 QTest::newRow(dataTag: "(p3,c)N") << "p3,c" << "N" << "c1P3" << "P3,c1P3";
536 QTest::newRow(dataTag: "(p3,c)NN") << "p3,c" << "NN" << "c2P3" << "P3,c2P3";
537 QTest::newRow(dataTag: "(p3,,c)") << "p3,,c" << "" << "" << "";
538 QTest::newRow(dataTag: "(p3,c0P3,)") << "p3,c0P3," << "" << "" << "";
539 QTest::newRow(dataTag: "(p,)") << "p," << "" << "" << "";
540 }
541}
542
543void tst_QCompleter::ciMatchingOnCsSortedModel()
544{
545 filter();
546}
547
548void tst_QCompleter::csMatchingOnCiSortedModel_data()
549{
550 delete completer;
551 completer = new CsvCompleter;
552 completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
553 setSourceModel(CASE_INSENSITIVELY_SORTED_MODEL);
554 completer->setCaseSensitivity(Qt::CaseSensitive);
555
556 QTest::addColumn<QString>(name: "filterText");
557 QTest::addColumn<QString>(name: "step");
558 QTest::addColumn<QString>(name: "completion");
559 QTest::addColumn<QString>(name: "completionText");
560
561 for (int i = 0; i < 2; i++) {
562 if (i == 1)
563 QTest::newRow(dataTag: "FILTERING_OFF") << "FILTERING_OFF" << "" << "" << "";
564
565 // Plain text filter
566 QTest::newRow(dataTag: "()") << "" << "" << "P0" << "P0";
567 QTest::newRow(dataTag: "()F") << "" << "F" << "P0" << "P0";
568 QTest::newRow(dataTag: "()L") << "" << "L" << "p4" << "p4";
569 QTest::newRow(dataTag: "()N") << "" << "N" << "p0" << "p0";
570 QTest::newRow(dataTag: "(P)") << "P" << "" << "P0" << "P0";
571 QTest::newRow(dataTag: "(P)F") << "P" << "" << "P0" << "P0";
572 QTest::newRow(dataTag: "(P)L") << "P" << "L" << "P4" << "P4";
573 QTest::newRow(dataTag: "(p)") << "p" << "" << "p0" << "p0";
574 QTest::newRow(dataTag: "(p)N") << "p" << "N" << "p1" << "p1";
575 QTest::newRow(dataTag: "(p)NN") << "p" << "NN" << "p2" << "p2";
576 QTest::newRow(dataTag: "(p)NNN") << "p" << "NNN" << "p3" << "p3";
577 QTest::newRow(dataTag: "(p1)") << "p1" << "" << "p1" << "p1";
578 QTest::newRow(dataTag: "(p11)") << "p11" << "" << "" << "";
579
580 //// Tree filter
581 QTest::newRow(dataTag: "(p0,)") << "p0," << "" << "c0p0" << "p0,c0p0";
582 QTest::newRow(dataTag: "(p0,c)") << "p0,c" << "" << "c0p0" << "p0,c0p0";
583 QTest::newRow(dataTag: "(p0,c1)") << "p0,c1" << "" << "c1p0" << "p0,c1p0";
584 QTest::newRow(dataTag: "(p0,c3P0)") << "p0,c3p0" << "" << "c3p0" << "p0,c3p0";
585 QTest::newRow(dataTag: "(p3,c)F") << "p3,c" << "F" << "c0p3" << "p3,c0p3";
586 QTest::newRow(dataTag: "(p3,c)L") << "p3,c" << "L" << "c4p3" << "p3,c4p3";
587 QTest::newRow(dataTag: "(p3,c)N") << "p3,c" << "N" << "c1p3" << "p3,c1p3";
588 QTest::newRow(dataTag: "(p3,c)NN") << "p3,c" << "NN" << "c2p3" << "p3,c2p3";
589 QTest::newRow(dataTag: "(p3,,c)") << "p3,,c" << "" << "" << "";
590 QTest::newRow(dataTag: "(p3,c0P3,)") << "p3,c0P3," << "" << "" << "";
591 QTest::newRow(dataTag: "(p,)") << "p," << "" << "" << "";
592
593 QTest::newRow(dataTag: "FILTERING_OFF") << "FILTERING_OFF" << "" << "" << "";
594 }
595}
596
597void tst_QCompleter::csMatchingOnCiSortedModel()
598{
599 filter();
600}
601
602#if QT_CONFIG(dirmodel) && QT_DEPRECATED_SINCE(5, 15)
603void tst_QCompleter::directoryModel_data()
604{
605 delete completer;
606
607 completer = new CsvCompleter;
608 completer->setModelSorting(QCompleter::CaseSensitivelySortedModel);
609 setSourceModel(DIRECTORY_MODEL);
610 completer->setCaseSensitivity(Qt::CaseInsensitive);
611
612 QTest::addColumn<QString>(name: "filterText");
613 QTest::addColumn<QString>(name: "step");
614 QTest::addColumn<QString>(name: "completion");
615 QTest::addColumn<QString>(name: "completionText");
616
617 // NOTE: Add tests carefully, ensurely the paths exist on all systems
618 // Output is the sourceText; currentCompletionText()
619
620 for (int i = 0; i < 2; i++) {
621 if (i == 1)
622 QTest::newRow(dataTag: "FILTERING_OFF") << "FILTERING_OFF" << "" << "" << "";
623
624#if defined(Q_OS_WIN)
625 QTest::newRow("()") << "C" << "" << "C:" << "C:";
626 QTest::newRow("()") << "C:\\Program" << "" << "Program Files" << "C:\\Program Files";
627#elif defined (Q_OS_MAC)
628 QTest::newRow("()") << "" << "" << "/" << "/";
629 QTest::newRow("(/a)") << "/a" << "" << "Applications" << "/Applications";
630 QTest::newRow("(/u)") << "/u" << "" << "Users" << "/Users";
631#elif defined(Q_OS_ANDROID)
632 QTest::newRow("()") << "" << "" << "/" << "/";
633 QTest::newRow("(/et)") << "/et" << "" << "etc" << "/etc";
634#else
635 QTest::newRow(dataTag: "()") << "" << "" << "/" << "/";
636#if !defined(Q_OS_AIX) && !defined(Q_OS_HPUX) && !defined(Q_OS_QNX)
637 QTest::newRow(dataTag: "(/h)") << "/h" << "" << "home" << "/home";
638#endif
639 QTest::newRow(dataTag: "(/et)") << "/et" << "" << "etc" << "/etc";
640 QTest::newRow(dataTag: "(/etc/passw)") << "/etc/passw" << "" << "passwd" << "/etc/passwd";
641#endif
642 }
643}
644
645void tst_QCompleter::directoryModel()
646{
647#ifdef Q_OS_WINRT
648 QSKIP("WinRT cannot access directories outside of the application's sandbox");
649#endif
650 filter();
651}
652#endif // QT_CONFIG(dirmodel) && QT_DEPRECATED_SINCE(5, 15)
653
654void tst_QCompleter::fileSystemModel_data()
655{
656 delete completer;
657 completer = new CsvCompleter;
658 completer->setModelSorting(QCompleter::CaseSensitivelySortedModel);
659 setSourceModel(FILESYSTEM_MODEL);
660 completer->setCaseSensitivity(Qt::CaseInsensitive);
661
662 QTest::addColumn<QString>(name: "filterText");
663 QTest::addColumn<QString>(name: "step");
664 QTest::addColumn<QString>(name: "completion");
665 QTest::addColumn<QString>(name: "completionText");
666
667 // NOTE: Add tests carefully, ensurely the paths exist on all systems
668 // Output is the sourceText; currentCompletionText()
669
670 for (int i = 0; i < 2; i++) {
671 if (i == 1)
672 QTest::newRow(dataTag: "FILTERING_OFF") << "FILTERING_OFF" << "" << "" << "";
673
674#if defined(Q_OS_WIN)
675 QTest::newRow("()") << "C" << "" << "C:" << "C:";
676 QTest::newRow("()") << "C:\\Program" << "" << "Program Files" << "C:\\Program Files";
677#elif defined (Q_OS_MAC)
678 QTest::newRow("()") << "" << "" << "/" << "/";
679 QTest::newRow("(/a)") << "/a" << "" << "Applications" << "/Applications";
680// QTest::newRow("(/d)") << "/d" << "" << "Developer" << "/Developer";
681#elif defined(Q_OS_ANDROID)
682 QTest::newRow("()") << "" << "" << "/" << "/";
683 QTest::newRow("(/et)") << "/et" << "" << "etc" << "/etc";
684#else
685 QTest::newRow(dataTag: "()") << "" << "" << "/" << "/";
686#if !defined(Q_OS_AIX) && !defined(Q_OS_HPUX) && !defined(Q_OS_QNX)
687 QTest::newRow(dataTag: "(/h)") << "/h" << "" << "home" << "/home";
688#endif
689 QTest::newRow(dataTag: "(/et)") << "/et" << "" << "etc" << "/etc";
690 QTest::newRow(dataTag: "(/etc/passw)") << "/etc/passw" << "" << "passwd" << "/etc/passwd";
691#endif
692 }
693}
694
695void tst_QCompleter::fileSystemModel()
696{
697#ifdef Q_OS_WINRT
698 QSKIP("WinRT cannot access directories outside of the application's sandbox");
699#endif
700 //QFileSystemModel is async.
701 filter(assync: true);
702}
703
704/*!
705 In the file dialog, the completer uses the EditRole.
706 See QTBUG-94799
707*/
708void tst_QCompleter::fileDialog_data()
709{
710 fileSystemModel_data();
711 completer->setCompletionRole(Qt::EditRole);
712}
713
714void tst_QCompleter::fileDialog()
715{
716#ifdef Q_OS_WINRT
717 QSKIP("WinRT cannot access directories outside of the application's sandbox");
718#endif
719 //QFileSystemModel is async.
720 filter(assync: true);
721}
722
723
724void tst_QCompleter::changingModel_data()
725{
726}
727
728void tst_QCompleter::changingModel()
729{
730 for (int i = 0; i < 2; i++) {
731 delete completer;
732 completer = new CsvCompleter;
733 completer->setModelSorting(QCompleter::CaseSensitivelySortedModel);
734 completer->setCaseSensitivity(Qt::CaseSensitive);
735 setSourceModel(CASE_SENSITIVELY_SORTED_MODEL);
736
737 if (i == 1) {
738 completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
739 }
740
741 completer->setCompletionPrefix("p");
742 completer->setCurrentRow(completer->completionCount() - 1);
743 QCOMPARE(completer->currentCompletion(), QString("p4"));
744
745 // Test addition of data
746 QTreeWidgetItem p5item;
747 p5item.setText(column: completionColumn, atext: "p5");
748 treeWidget->addTopLevelItem(item: &p5item);
749 completer->setCompletionPrefix("p5");
750 QCOMPARE(completer->currentCompletion(), QString("p5"));
751
752 // Test removal of data
753 int p5index = treeWidget->indexOfTopLevelItem(item: &p5item);
754 treeWidget->takeTopLevelItem(index: p5index);
755 QCOMPARE(completer->currentCompletion(), QString(""));
756
757 // Test clear
758 treeWidget->clear();
759 QCOMPARE(completer->currentIndex(), QModelIndex());
760 }
761}
762
763void tst_QCompleter::testRowCount()
764{
765 QFETCH(QString, filterText);
766 QFETCH(bool, hasChildren);
767 QFETCH(int, rowCount);
768 QFETCH(int, completionCount);
769
770 if (filterText.compare(s: "FILTERING_OFF", cs: Qt::CaseInsensitive) == 0) {
771 completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
772 return;
773 }
774
775 completer->setCompletionPrefix(filterText);
776 const QAbstractItemModel *completionModel = completer->completionModel();
777 QCOMPARE(completionModel->rowCount(), rowCount);
778 QCOMPARE(completionCount, completionCount);
779 QCOMPARE(completionModel->hasChildren(), hasChildren);
780 QCOMPARE(completionModel->columnCount(), columnCount);
781}
782
783void tst_QCompleter::sortedEngineRowCount_data()
784{
785 delete completer;
786 completer = new CsvCompleter;
787 completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
788 completer->setCaseSensitivity(Qt::CaseInsensitive);
789 setSourceModel(CASE_INSENSITIVELY_SORTED_MODEL);
790
791 QTest::addColumn<QString>(name: "filterText");
792 QTest::addColumn<bool>(name: "hasChildren");
793 QTest::addColumn<int>(name: "rowCount");
794 QTest::addColumn<int>(name: "completionCount");
795
796 QTest::newRow(dataTag: "whatever") << "whatever" << false << 0 << 0;
797 QTest::newRow(dataTag: "p") << "p" << true << 10 << 10;
798 QTest::newRow(dataTag: "p1") << "p1" << true << 2 << 2;
799 QTest::newRow(dataTag: "P1,") << "P1," << true << 5 << 5;
800 QTest::newRow(dataTag: "P1,c") << "P1,c" << true << 5 << 5;
801 QTest::newRow(dataTag: "P1,cc") << "P1,cc" << false << 0 << 0;
802
803 QTest::newRow(dataTag: "FILTERING_OFF") << "FILTERING_OFF" << false << 0 << 0;
804
805 QTest::newRow(dataTag: "whatever(filter off)") << "whatever" << true << 10 << 0;
806 QTest::newRow(dataTag: "p1(filter off)") << "p1" << true << 10 << 2;
807 QTest::newRow(dataTag: "p1,(filter off)") << "p1," << true << 5 << 5;
808 QTest::newRow(dataTag: "p1,c(filter off)") << "p1,c" << true << 5 << 5;
809 QTest::newRow(dataTag: "P1,cc(filter off)") << "P1,cc" << true << 5 << 0;
810}
811
812void tst_QCompleter::sortedEngineRowCount()
813{
814 testRowCount();
815}
816
817void tst_QCompleter::unsortedEngineRowCount_data()
818{
819 delete completer;
820 completer = new CsvCompleter;
821 completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
822 completer->setCaseSensitivity(Qt::CaseSensitive);
823 setSourceModel(CASE_INSENSITIVELY_SORTED_MODEL);
824
825 QTest::addColumn<QString>(name: "filterText");
826 QTest::addColumn<bool>(name: "hasChildren");
827 QTest::addColumn<int>(name: "rowCount");
828 QTest::addColumn<int>(name: "completionCount");
829
830 QTest::newRow(dataTag: "whatever") << "whatever" << false << 0 << 0;
831 QTest::newRow(dataTag: "p") << "p" << true << 5 << 5;
832 QTest::newRow(dataTag: "p1") << "p1" << true << 1 << 1;
833 QTest::newRow(dataTag: "P1,") << "P1," << true << 5 << 5;
834 QTest::newRow(dataTag: "P1,c") << "P1,c" << true << 5 << 5;
835 QTest::newRow(dataTag: "P1,cc") << "P1,cc" << false << 0 << 0;
836
837 QTest::newRow(dataTag: "FILTERING_OFF") << "FILTERING_OFF" << false << 0 << 0;
838
839 QTest::newRow(dataTag: "whatever(filter off)") << "whatever" << true << 10 << 0;
840 QTest::newRow(dataTag: "p1(filter off)") << "p1" << true << 10 << 1;
841 QTest::newRow(dataTag: "p1,(filter off)") << "p1," << true << 5 << 5;
842 QTest::newRow(dataTag: "p1,c(filter off)") << "p1,c" << true << 5 << 5;
843 QTest::newRow(dataTag: "P1,cc(filter off)") << "P1,cc" << true << 5 << 0;
844}
845
846void tst_QCompleter::unsortedEngineRowCount()
847{
848 testRowCount();
849}
850
851void tst_QCompleter::currentRow()
852{
853 delete completer;
854 completer = new CsvCompleter;
855 completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
856 completer->setCaseSensitivity(Qt::CaseInsensitive);
857 setSourceModel(CASE_INSENSITIVELY_SORTED_MODEL);
858
859 // blank text
860 completer->setCompletionPrefix("");
861 QCOMPARE(completer->currentRow(), 0);
862 QVERIFY(completer->setCurrentRow(4));
863 QCOMPARE(completer->currentRow(), 4);
864 QVERIFY(!completer->setCurrentRow(13));
865 QVERIFY(completer->setCurrentRow(4));
866
867 // some text
868 completer->setCompletionPrefix("p1");
869 QCOMPARE(completer->currentRow(), 0);
870 QVERIFY(completer->setCurrentRow(1));
871 QCOMPARE(completer->currentRow(), 1);
872 QVERIFY(!completer->setCurrentRow(2));
873 QCOMPARE(completer->currentRow(), 1);
874
875 // invalid text
876 completer->setCompletionPrefix("well");
877 QCOMPARE(completer->currentRow(), -1);
878}
879
880void tst_QCompleter::sortedEngineMapFromSource()
881{
882 delete completer;
883 completer = new CsvCompleter;
884 completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
885 completer->setCaseSensitivity(Qt::CaseInsensitive);
886 setSourceModel(CASE_INSENSITIVELY_SORTED_MODEL);
887
888 QModelIndex si1, si2, pi;
889 QAbstractItemModel *sourceModel = completer->model();
890 auto completionModel = qobject_cast<const QAbstractProxyModel *>(object: completer->completionModel());
891
892 // Fitering ON
893 // empty
894 si1 = sourceModel->index(row: 4, column: completionColumn); // "P2"
895 si2 = sourceModel->index(row: 2, column: 0, parent: si1); // "P2,c0P2"
896 pi = completionModel->mapFromSource(sourceIndex: si1);
897 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("P2"));
898 pi = completionModel->mapFromSource(sourceIndex: si2);
899 QCOMPARE(pi.isValid(), false);
900
901 // some text
902 completer->setCompletionPrefix("p");
903 pi = completionModel->mapFromSource(sourceIndex: si1);
904 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("P2"));
905 pi = completionModel->mapFromSource(sourceIndex: si2);
906 QCOMPARE(pi.isValid(), false);
907
908 // more text
909 completer->setCompletionPrefix("p2");
910 pi = completionModel->mapFromSource(sourceIndex: si1);
911 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("P2"));
912 pi = completionModel->mapFromSource(sourceIndex: si2);
913 QCOMPARE(pi.isValid(), false);
914
915 // invalid text
916 completer->setCompletionPrefix("whatever");
917 pi = completionModel->mapFromSource(sourceIndex: si1);
918 QVERIFY(!pi.isValid());
919
920 // Fitering OFF
921 completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
922 // empty
923 si1 = sourceModel->index(row: 4, column: completionColumn); // "P2"
924 si2 = sourceModel->index(row: 2, column: 0, parent: si1); // "P2,c0P2"
925 pi = completionModel->mapFromSource(sourceIndex: si1);
926 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("P2"));
927 pi = completionModel->mapFromSource(sourceIndex: si2);
928 QCOMPARE(pi.isValid(), false);
929
930 // some text
931 completer->setCompletionPrefix("p");
932 pi = completionModel->mapFromSource(sourceIndex: si1);
933 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("P2"));
934 pi = completionModel->mapFromSource(sourceIndex: si2);
935 QCOMPARE(pi.isValid(), false);
936
937 // more text
938 completer->setCompletionPrefix("p2");
939 pi = completionModel->mapFromSource(sourceIndex: si1);
940 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("P2"));
941 pi = completionModel->mapFromSource(sourceIndex: si2);
942 QCOMPARE(pi.isValid(), false);
943
944 // invalid text
945 completer->setCompletionPrefix("whatever");
946 pi = completionModel->mapFromSource(sourceIndex: si1);
947 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("P2"));
948}
949
950void tst_QCompleter::unsortedEngineMapFromSource()
951{
952 delete completer;
953 completer = new CsvCompleter;
954 completer->setCaseSensitivity(Qt::CaseInsensitive);
955 setSourceModel(HISTORY_MODEL); // case insensitively sorted model
956 completer->setModelSorting(QCompleter::UnsortedModel);
957
958 QModelIndex si, si2, si3, pi;
959 QAbstractItemModel *sourceModel = completer->model();
960 auto completionModel = qobject_cast<const QAbstractProxyModel *>(object: completer->completionModel());
961
962 si = sourceModel->index(row: 6, column: completionColumn); // "P3"
963 QCOMPARE(si.data().toString(), QLatin1String("P3"));
964 si2 = sourceModel->index(row: 3, column: completionColumn, parent: sourceModel->index(row: 0, column: completionColumn)); // "P0,c3P0"
965 QCOMPARE(si2.data().toString(), QLatin1String("c3P0"));
966 si3 = sourceModel->index(row: 10, column: completionColumn); // "p3,c3p3" (history)
967 QCOMPARE(si3.data().toString(), QLatin1String("p3,c3p3"));
968
969 // FILTERING ON
970 // empty
971 pi = completionModel->mapFromSource(sourceIndex: si);
972 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("P3"));
973 pi = completionModel->mapFromSource(sourceIndex: si2);
974 QCOMPARE(pi.isValid(), false);
975 pi = completionModel->mapFromSource(sourceIndex: si3);
976 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("p3,c3p3"));
977
978 // some text
979 completer->setCompletionPrefix("P");
980 pi = completionModel->mapFromSource(sourceIndex: si);
981 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("P3"));
982 pi = completionModel->mapFromSource(sourceIndex: si2);
983 QCOMPARE(pi.isValid(), false);
984 pi = completionModel->mapFromSource(sourceIndex: si3);
985 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("p3,c3p3"));
986
987 // invalid text
988 completer->setCompletionPrefix("whatever");
989 pi = completionModel->mapFromSource(sourceIndex: si);
990 QVERIFY(!pi.isValid());
991 pi = completionModel->mapFromSource(sourceIndex: si2);
992 QVERIFY(!pi.isValid());
993
994 // tree matching
995 completer->setCompletionPrefix("P0,c");
996 pi = completionModel->mapFromSource(sourceIndex: si);
997 QVERIFY(!pi.isValid());
998 pi = completionModel->mapFromSource(sourceIndex: si2);
999 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("c3P0"));
1000 pi = completionModel->mapFromSource(sourceIndex: si3);
1001 QCOMPARE(pi.isValid(), false);
1002
1003 // more tree matching
1004 completer->setCompletionPrefix("p3,");
1005 pi = completionModel->mapFromSource(sourceIndex: si2);
1006 QVERIFY(!pi.isValid());
1007 pi = completionModel->mapFromSource(sourceIndex: si3);
1008 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("p3,c3p3"));
1009
1010 // FILTERING OFF
1011 // empty
1012 completer->setCompletionPrefix("");
1013 completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
1014 pi = completionModel->mapFromSource(sourceIndex: si);
1015 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("P3"));
1016
1017 // some text
1018 completer->setCompletionPrefix("P");
1019 pi = completionModel->mapFromSource(sourceIndex: si);
1020 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("P3"));
1021
1022 // more text
1023 completer->setCompletionPrefix("P3");
1024 pi = completionModel->mapFromSource(sourceIndex: si);
1025 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("P3"));
1026
1027 // invalid text
1028 completer->setCompletionPrefix("whatever");
1029 pi = completionModel->mapFromSource(sourceIndex: si);
1030 QCOMPARE(completionModel->data(pi).toString(), QLatin1String("P3"));
1031}
1032
1033void tst_QCompleter::historySearch()
1034{
1035 delete completer;
1036 completer = new CsvCompleter;
1037 completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
1038 completer->setCaseSensitivity(Qt::CaseSensitive);
1039 setSourceModel(HISTORY_MODEL);
1040
1041 auto completionModel = qobject_cast<const QAbstractProxyModel *>(object: completer->completionModel());
1042
1043 // "p3,c3p3" and "p2,c4p2" are added in the tree root
1044
1045 // FILTERING ON
1046 // empty
1047 completer->setCurrentRow(10);
1048 QCOMPARE(completer->currentCompletion(), QLatin1String("p3,c3p3"));
1049
1050 // more text
1051 completer->setCompletionPrefix("p2");
1052 completer->setCurrentRow(1);
1053 QCOMPARE(completer->currentCompletion(), QLatin1String("p2,c4p2"));
1054
1055 // comma separated text
1056 completer->setCompletionPrefix("p2,c4");
1057 completer->setCurrentRow(1);
1058 QCOMPARE(completionModel->rowCount(), 2);
1059 QCOMPARE(completer->currentCompletion(), QLatin1String("p2,c4p2"));
1060
1061 // invalid text
1062 completer->setCompletionPrefix("whatever");
1063 QCOMPARE(completer->currentCompletion(), QString());
1064
1065 // FILTERING OFF
1066 completer->setCompletionPrefix("");
1067 completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
1068 completer->setCurrentRow(10);
1069 QCOMPARE(completer->currentCompletion(), QLatin1String("p3,c3p3"));
1070
1071 // more text
1072 completer->setCompletionPrefix("p2");
1073 completer->setCurrentRow(1);
1074 QCOMPARE(completer->currentCompletion(), QLatin1String("p2,c4p2"));
1075
1076 // comma separated text
1077 completer->setCompletionPrefix("p2,c4");
1078 QCOMPARE(completionModel->rowCount(), 5);
1079
1080 // invalid text
1081 completer->setCompletionPrefix("whatever");
1082 QCOMPARE(completer->currentCompletion(), QString());
1083}
1084
1085void tst_QCompleter::setters()
1086{
1087 delete completer;
1088 completer = new CsvCompleter;
1089 QVERIFY(completer->popup() != nullptr);
1090 QPointer<QStandardItemModel> itemModel(new QStandardItemModel(1, 0, completer));
1091 QAbstractItemModel *oldModel = completer->model();
1092 completer->setModel(itemModel.data());
1093 QVERIFY(completer->popup()->model() != oldModel);
1094 QCOMPARE(completer->popup()->model(), completer->completionModel());
1095 completer->setPopup(new QListView);
1096 QCOMPARE(completer->popup()->model(), completer->completionModel());
1097 completer->setModel(new QStringListModel(completer));
1098 QVERIFY(itemModel.isNull()); // must have been deleted
1099
1100 completer->setModel(nullptr);
1101 completer->setWidget(nullptr);
1102}
1103
1104void tst_QCompleter::modelDeletion()
1105{
1106 delete completer;
1107 completer = new CsvCompleter;
1108 const QStringList list = {"item1", "item2", "item3"};
1109 std::unique_ptr<QStringListModel> listModel(new QStringListModel(list));
1110 completer->setCompletionPrefix("i");
1111 completer->setModel(listModel.get());
1112 QCOMPARE(completer->completionCount(), 3);
1113 std::unique_ptr<QListView> view(new QListView);
1114 view->setWindowTitle(QLatin1String(QTest::currentTestFunction()));
1115 view->setModel(completer->completionModel());
1116 listModel.reset();
1117 view->move(ax: 200, ay: 200);
1118 view->show();
1119 QCoreApplication::processEvents();
1120 view.reset();
1121 QCOMPARE(completer->completionCount(), 0);
1122 QCOMPARE(completer->currentRow(), -1);
1123}
1124
1125void tst_QCompleter::multipleWidgets()
1126{
1127 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1128 QSKIP("Wayland: This fails. Figure out why.");
1129
1130 QStringList list;
1131 list << "item1" << "item2" << "item2";
1132 QCompleter completer(list);
1133 completer.setCompletionMode(QCompleter::InlineCompletion);
1134
1135 QWidget window;
1136 window.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
1137 window.move(ax: 200, ay: 200);
1138 window.show();
1139 QApplication::setActiveWindow(&window);
1140 QVERIFY(QTest::qWaitForWindowActive(&window));
1141
1142 QFocusEvent focusIn(QEvent::FocusIn);
1143 QFocusEvent focusOut(QEvent::FocusOut);
1144
1145 auto comboBox = new QComboBox(&window);
1146 comboBox->setEditable(true);
1147 comboBox->setCompleter(&completer);
1148 comboBox->setFocus();
1149 comboBox->show();
1150 window.activateWindow();
1151 QApplication::setActiveWindow(&window);
1152 QVERIFY(QTest::qWaitForWindowActive(&window));
1153 QCOMPARE(QApplication::focusWidget(), comboBox);
1154 comboBox->lineEdit()->setText("it");
1155 QCOMPARE(comboBox->currentText(), QString("it")); // should not complete with setText
1156 QTest::keyPress(widget: comboBox, key: 'e');
1157 QCOMPARE(comboBox->currentText(), QString("item1"));
1158 comboBox->clearEditText();
1159 QCOMPARE(comboBox->currentText(), QString("")); // combo box text must not change!
1160
1161 auto lineEdit = new QLineEdit(&window);
1162 lineEdit->setCompleter(&completer);
1163 lineEdit->show();
1164 lineEdit->setFocus();
1165 QTRY_COMPARE(QApplication::focusWidget(), lineEdit);
1166 lineEdit->setText("it");
1167 QCOMPARE(lineEdit->text(), QString("it")); // should not completer with setText
1168 QCOMPARE(comboBox->currentText(), QString("")); // combo box text must not change!
1169 QTest::keyPress(widget: lineEdit, key: 'e');
1170 QCOMPARE(lineEdit->text(), QString("item1"));
1171 QCOMPARE(comboBox->currentText(), QString("")); // combo box text must not change!
1172}
1173
1174void tst_QCompleter::focusIn()
1175{
1176 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1177 QSKIP("Wayland: This fails. Figure out why.");
1178
1179 QCompleter completer({"item1", "item2", "item2"});
1180
1181 QWidget window;
1182 window.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
1183 window.move(ax: 200, ay: 200);
1184 window.show();
1185 window.activateWindow();
1186 QApplication::setActiveWindow(&window);
1187 QVERIFY(QTest::qWaitForWindowActive(&window));
1188
1189 auto comboBox = new QComboBox(&window);
1190 comboBox->setEditable(true);
1191 comboBox->setCompleter(&completer);
1192 comboBox->show();
1193 comboBox->lineEdit()->setText("it");
1194
1195 auto lineEdit = new QLineEdit(&window);
1196 lineEdit->setCompleter(&completer);
1197 lineEdit->setText("it");
1198 lineEdit->show();
1199
1200 auto lineEdit2 = new QLineEdit(&window); // has no completer!
1201 lineEdit2->show();
1202
1203 comboBox->setFocus();
1204 QTRY_COMPARE(completer.widget(), comboBox);
1205 lineEdit->setFocus();
1206 QTRY_COMPARE(completer.widget(), lineEdit);
1207 comboBox->setFocus();
1208 QTRY_COMPARE(completer.widget(), comboBox);
1209 lineEdit2->setFocus();
1210 QTRY_COMPARE(completer.widget(), comboBox);
1211}
1212
1213void tst_QCompleter::dynamicSortOrder()
1214{
1215 QStandardItemModel model;
1216 QCompleter completer(&model);
1217 completer.setModelSorting(QCompleter::CaseSensitivelySortedModel);
1218 QStandardItem *root = model.invisibleRootItem();
1219 for (int i = 0; i < 20; i++) {
1220 root->appendRow(aitem: new QStandardItem(QString::number(i)));
1221 }
1222 root->appendRow(aitem: new QStandardItem("13"));
1223 root->sortChildren(column: 0, order: Qt::AscendingOrder);
1224 completer.setCompletionPrefix("1");
1225 QCOMPARE(completer.completionCount(), 12);
1226 completer.setCompletionPrefix("13");
1227 QCOMPARE(completer.completionCount(), 2);
1228
1229 root->sortChildren(column: 0, order: Qt::DescendingOrder);
1230 completer.setCompletionPrefix("13");
1231 QCOMPARE(completer.completionCount(), 2);
1232 completer.setCompletionPrefix("1");
1233 QCOMPARE(completer.completionCount(), 12);
1234}
1235
1236void tst_QCompleter::disabledItems()
1237{
1238 QLineEdit lineEdit;
1239 lineEdit.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
1240 auto model = new QStandardItemModel(&lineEdit);
1241 QStandardItem *suggestions = new QStandardItem("suggestions");
1242 suggestions->setEnabled(false);
1243 model->appendRow(aitem: suggestions);
1244 model->appendRow(aitem: new QStandardItem("suggestions Enabled"));
1245 auto completer = new QCompleter(model, &lineEdit);
1246 QSignalSpy spy(completer, QOverload<const QString &>::of(ptr: &QCompleter::activated));
1247 lineEdit.setCompleter(completer);
1248 lineEdit.move(ax: 200, ay: 200);
1249 lineEdit.show();
1250 QVERIFY(QTest::qWaitForWindowExposed(&lineEdit));
1251
1252 QTest::keyPress(widget: &lineEdit, key: Qt::Key_S);
1253 QTest::keyPress(widget: &lineEdit, key: Qt::Key_U);
1254 QAbstractItemView *view = lineEdit.completer()->popup();
1255 QVERIFY(view->isVisible());
1256 QTest::mouseClick(widget: view->viewport(), button: Qt::LeftButton, stateKey: {}, pos: view->visualRect(index: view->model()->index(row: 0, column: 0)).center());
1257 QCOMPARE(spy.count(), 0);
1258 QVERIFY(view->isVisible());
1259 QTest::mouseClick(widget: view->viewport(), button: Qt::LeftButton, stateKey: {}, pos: view->visualRect(index: view->model()->index(row: 1, column: 0)).center());
1260 QCOMPARE(spy.count(), 1);
1261 QVERIFY(!view->isVisible());
1262}
1263
1264void tst_QCompleter::task178797_activatedOnReturn()
1265{
1266 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1267 QSKIP("Wayland: This fails. Figure out why.");
1268
1269 QLineEdit ledit;
1270 setFrameless(&ledit);
1271 auto completer = new QCompleter({"foobar1", "foobar2"}, &ledit);
1272 ledit.setCompleter(completer);
1273 QSignalSpy spy(completer, QOverload<const QString &>::of(ptr: &QCompleter::activated));
1274 QCOMPARE(spy.count(), 0);
1275 ledit.move(ax: 200, ay: 200);
1276 ledit.show();
1277 QApplication::setActiveWindow(&ledit);
1278 QVERIFY(QTest::qWaitForWindowActive(&ledit));
1279 QTest::keyClick(widget: &ledit, key: Qt::Key_F);
1280 QCoreApplication::processEvents();
1281 QTRY_VERIFY(QApplication::activePopupWidget());
1282 QTest::keyClick(widget: QApplication::activePopupWidget(), key: Qt::Key_Down);
1283 QCoreApplication::processEvents();
1284 QTest::keyClick(widget: QApplication::activePopupWidget(), key: Qt::Key_Return);
1285 QCoreApplication::processEvents();
1286 QCOMPARE(spy.count(), 1);
1287}
1288
1289class task189564_StringListModel : public QStringListModel
1290{
1291 const QString omitString;
1292 Qt::ItemFlags flags(const QModelIndex &index) const override
1293 {
1294 Qt::ItemFlags flags = Qt::ItemIsEnabled;
1295 if (data(index, role: Qt::DisplayRole).toString() != omitString)
1296 flags |= Qt::ItemIsSelectable;
1297 return flags;
1298 }
1299public:
1300 explicit task189564_StringListModel(const QString &omitString, QObject *parent = nullptr)
1301 : QStringListModel(parent)
1302 , omitString(omitString)
1303 {
1304 }
1305};
1306
1307void tst_QCompleter::task189564_omitNonSelectableItems()
1308{
1309 const QString prefix("a");
1310 const int n = 5;
1311
1312 QStringList strings;
1313 for (int i = 0; i < n; ++i)
1314 strings << prefix + QString::number(i);
1315 const QString omitString(strings.at(i: n / 2));
1316 task189564_StringListModel model(omitString);
1317 model.setStringList(strings);
1318 QCompleter completer_(&model);
1319 completer_.setCompletionPrefix(prefix);
1320
1321 QAbstractItemModel *completionModel = completer_.completionModel();
1322 QModelIndexList matches1 =
1323 completionModel->match(start: completionModel->index(row: 0, column: 0), role: Qt::DisplayRole, value: prefix, hits: -1);
1324 QCOMPARE(matches1.size(), n - 1);
1325 QModelIndexList matches2 =
1326 completionModel->match(start: completionModel->index(row: 0, column: 0), role: Qt::DisplayRole, value: omitString);
1327 QVERIFY(matches2.isEmpty());
1328}
1329
1330class task246056_ComboBox : public QComboBox
1331{
1332 Q_OBJECT
1333public:
1334 task246056_ComboBox()
1335 {
1336 setEditable(true);
1337 setInsertPolicy(NoInsert);
1338 if (completer()) {
1339 completer()->setCompletionMode(QCompleter::PopupCompletion);
1340 completer()->setCompletionRole(Qt::DisplayRole);
1341 connect(sender: lineEdit(), signal: &QLineEdit::editingFinished, receiver: this, slot: &task246056_ComboBox::setCompletionPrefix);
1342 }
1343 }
1344private slots:
1345 void setCompletionPrefix() { completer()->setCompletionPrefix(lineEdit()->text()); }
1346};
1347
1348void tst_QCompleter::task246056_setCompletionPrefix()
1349{
1350 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1351 QSKIP("Wayland: This fails. Figure out why.");
1352
1353 task246056_ComboBox comboBox;
1354 setFrameless(&comboBox);
1355 QVERIFY(comboBox.completer());
1356 comboBox.addItem(atext: "");
1357 comboBox.addItem(atext: "a1");
1358 comboBox.addItem(atext: "a2");
1359 comboBox.move(ax: 200, ay: 200);
1360 comboBox.show();
1361 QApplication::setActiveWindow(&comboBox);
1362 QVERIFY(QTest::qWaitForWindowActive(&comboBox));
1363 QSignalSpy spy(comboBox.completer(), QOverload<const QModelIndex &>::of(ptr: &QCompleter::activated));
1364 QTest::keyPress(widget: &comboBox, key: 'a');
1365 QTest::keyPress(widget: comboBox.completer()->popup(), key: Qt::Key_Down);
1366 QTest::keyPress(widget: comboBox.completer()->popup(), key: Qt::Key_Down);
1367 QTest::keyPress(widget: comboBox.completer()->popup(), key: Qt::Key_Enter); // don't crash!
1368 QCOMPARE(spy.count(), 1);
1369 const auto index = spy.at(i: 0).constFirst().toModelIndex();
1370 QVERIFY(!index.isValid());
1371}
1372
1373class task250064_TextEdit : public QTextEdit
1374{
1375public:
1376 QCompleter *completer;
1377
1378 task250064_TextEdit()
1379 {
1380 completer = new QCompleter(this);
1381 completer->setWidget(this);
1382 }
1383
1384 void keyPressEvent (QKeyEvent *e) override
1385 {
1386 completer->popup();
1387 QTextEdit::keyPressEvent(e);
1388 }
1389};
1390
1391class task250064_Widget : public QWidget
1392{
1393 Q_OBJECT
1394public:
1395 task250064_Widget() : m_textEdit(new task250064_TextEdit)
1396 {
1397 auto tabWidget = new QTabWidget;
1398 tabWidget->setFocusPolicy(Qt::ClickFocus);
1399 tabWidget->addTab(widget: m_textEdit, "untitled");
1400
1401 auto layout = new QVBoxLayout(this);
1402 layout->addWidget(tabWidget);
1403
1404 m_textEdit->setPlainText("bla bla bla");
1405 m_textEdit->setFocus();
1406 }
1407
1408 void setCompletionModel()
1409 {
1410 m_textEdit->completer->setModel(nullptr);
1411 }
1412
1413 QTextEdit *textEdit() const { return m_textEdit; }
1414
1415private:
1416 task250064_TextEdit *m_textEdit;
1417};
1418
1419void tst_QCompleter::task250064_lostFocus()
1420{
1421 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1422 QSKIP("Wayland: This fails. Figure out why.");
1423
1424 task250064_Widget widget;
1425 widget.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
1426 widget.show();
1427 QApplication::setActiveWindow(&widget);
1428 QVERIFY(QTest::qWaitForWindowActive(&widget));
1429 QTest::keyPress(widget: widget.textEdit(), key: 'a');
1430 Qt::FocusPolicy origPolicy = widget.textEdit()->focusPolicy();
1431 QVERIFY(origPolicy != Qt::NoFocus);
1432 widget.setCompletionModel();
1433 QCOMPARE(widget.textEdit()->focusPolicy(), origPolicy);
1434}
1435
1436void tst_QCompleter::task253125_lineEditCompletion_data()
1437{
1438 QTest::addColumn<QStringList>(name: "list");
1439 QTest::addColumn<QCompleter::CompletionMode>(name: "completionMode");
1440
1441 QStringList list = {"alpha", "beta", "gamma", "delta", "epsilon", "zeta",
1442 "eta", "theta", "iota", "kappa", "lambda", "mu",
1443 "nu", "xi", "omicron", "pi", "rho", "sigma",
1444 "tau", "upsilon", "phi", "chi", "psi", "omega"};
1445
1446 QTest::newRow(dataTag: "Inline") << list << QCompleter::InlineCompletion;
1447 QTest::newRow(dataTag: "Filtered") << list << QCompleter::PopupCompletion;
1448 QTest::newRow(dataTag: "Unfiltered") << list << QCompleter::UnfilteredPopupCompletion;
1449}
1450
1451void tst_QCompleter::task253125_lineEditCompletion()
1452{
1453 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1454 QSKIP("Wayland: This fails. Figure out why.");
1455
1456 QFETCH(QStringList, list);
1457 QFETCH(QCompleter::CompletionMode, completionMode);
1458
1459 std::unique_ptr<QStringListModel> model(new QStringListModel(list));
1460
1461 std::unique_ptr<QCompleter> completer(new QCompleter(list));
1462 completer->setModel(model.get());
1463 completer->setCompletionMode(completionMode);
1464
1465 QLineEdit edit;
1466 edit.setWindowTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1String("::")
1467 + QLatin1String(QTest::currentDataTag()));
1468 edit.setCompleter(completer.get());
1469 edit.move(ax: 200, ay: 200);
1470 edit.show();
1471 edit.setFocus();
1472 QApplication::setActiveWindow(&edit);
1473 QVERIFY(QTest::qWaitForWindowActive(&edit));
1474
1475 QTest::keyClick(widget: &edit, key: 'i');
1476 QCOMPARE(edit.completer()->currentCompletion(), QString("iota"));
1477 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Down);
1478 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Enter);
1479
1480 QCOMPARE(edit.text(), QString("iota"));
1481
1482 edit.clear();
1483 completer->setCompletionMode(QCompleter::PopupCompletion);
1484 completer->setFilterMode(Qt::MatchContains);
1485
1486 QTest::keyClick(widget: &edit, key: 't');
1487 QCOMPARE(edit.completer()->currentCompletion(), QString("beta"));
1488 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Down);
1489 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Enter);
1490
1491 QCOMPARE(edit.text(), QString("beta"));
1492
1493 edit.clear();
1494
1495 QTest::keyClick(widget: &edit, key: 'p');
1496 QTest::keyClick(widget: &edit, key: 'p');
1497 QCOMPARE(edit.completer()->currentCompletion(), QString("kappa"));
1498 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Down);
1499 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Enter);
1500
1501 QCOMPARE(edit.text(), QString("kappa"));
1502
1503 edit.clear();
1504 completer->setFilterMode(Qt::MatchStartsWith);
1505
1506 QTest::keyClick(widget: &edit, key: 't');
1507 QCOMPARE(edit.completer()->currentCompletion(), QString("theta"));
1508 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Down);
1509 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Enter);
1510
1511 QCOMPARE(edit.text(), QString("theta"));
1512
1513 edit.clear();
1514
1515 QTest::keyClick(widget: &edit, key: 'p');
1516 QTest::keyClick(widget: &edit, key: 'p');
1517 QCOMPARE(edit.completer()->currentCompletion(), QString());
1518 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Down);
1519 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Enter);
1520
1521 QCOMPARE(edit.text(), QString("pp"));
1522
1523 edit.clear();
1524
1525 QTest::keyClick(widget: &edit, key: 'u');
1526 QTest::keyClick(widget: &edit, key: 'p');
1527 QTest::keyClick(widget: &edit, key: 's');
1528 QCOMPARE(edit.completer()->currentCompletion(), QString("upsilon"));
1529 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Down);
1530 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Enter);
1531
1532 QCOMPARE(edit.text(), QString("upsilon"));
1533
1534 edit.clear();
1535 completer->setFilterMode(Qt::MatchEndsWith);
1536
1537 QTest::keyClick(widget: &edit, key: 'm');
1538 QTest::keyClick(widget: &edit, key: 'm');
1539 QTest::keyClick(widget: &edit, key: 'a');
1540 QCOMPARE(edit.completer()->currentCompletion(), QString("gamma"));
1541 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Down);
1542 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Enter);
1543
1544 QCOMPARE(edit.text(), QString("gamma"));
1545
1546 edit.clear();
1547
1548 QTest::keyClick(widget: &edit, key: 'g');
1549 QTest::keyClick(widget: &edit, key: 'm');
1550 QTest::keyClick(widget: &edit, key: 'a');
1551 QCOMPARE(edit.completer()->currentCompletion(), QString("sigma"));
1552 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Down);
1553 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Enter);
1554
1555 QCOMPARE(edit.text(), QString("sigma"));
1556
1557 edit.clear();
1558
1559 QTest::keyClick(widget: &edit, key: 'm');
1560 QTest::keyClick(widget: &edit, key: 'm');
1561 QCOMPARE(edit.completer()->currentCompletion(), QString());
1562 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Down);
1563 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Enter);
1564
1565 QCOMPARE(edit.text(), QString("mm"));
1566
1567 edit.clear();
1568 completer->setFilterMode(Qt::MatchStartsWith);
1569
1570 QTest::keyClick(widget: &edit, key: 'z');
1571 QTest::keyClick(widget: &edit, key: 'e');
1572 QCOMPARE(edit.completer()->currentCompletion(), QString("zeta"));
1573 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Down);
1574 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Enter);
1575
1576 QCOMPARE(edit.text(), QString("zeta"));
1577
1578 edit.clear();
1579 completer->setFilterMode(Qt::MatchEndsWith);
1580
1581 QTest::keyClick(widget: &edit, key: 'e');
1582 QTest::keyClick(widget: &edit, key: 'g');
1583 QTest::keyClick(widget: &edit, key: 'a');
1584 QCOMPARE(edit.completer()->currentCompletion(), QString("omega"));
1585 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Down);
1586 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Enter);
1587
1588 QCOMPARE(edit.text(), QString("omega"));
1589
1590 edit.clear();
1591 completer->setFilterMode(Qt::MatchContains);
1592
1593 QTest::keyClick(widget: &edit, key: 'c');
1594 QTest::keyClick(widget: &edit, key: 'r');
1595 QCOMPARE(edit.completer()->currentCompletion(), QString("omicron"));
1596 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Down);
1597 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Enter);
1598
1599 QCOMPARE(edit.text(), QString("omicron"));
1600
1601 edit.clear();
1602
1603 QTest::keyClick(widget: &edit, key: 'z');
1604 QTest::keyClick(widget: &edit, key: 'z');
1605 QCOMPARE(edit.completer()->currentCompletion(), QString());
1606 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Down);
1607 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Enter);
1608
1609 QCOMPARE(edit.text(), QString("zz"));
1610}
1611
1612void tst_QCompleter::task247560_keyboardNavigation()
1613{
1614 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1615 QSKIP("Wayland: This fails. Figure out why.");
1616
1617 QStandardItemModel model;
1618
1619 for (int i = 0; i < 5; i++) {
1620 const QString prefix = QLatin1String("row ") + QString::number(i) + QLatin1String(" column ");
1621 for (int j = 0; j < 5; j++)
1622 model.setItem(row: i, column: j, item: new QStandardItem(prefix + QString::number(j)));
1623 }
1624
1625
1626 QCompleter completer(&model);
1627 completer.setCompletionColumn(1);
1628
1629 QLineEdit edit;
1630 edit.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
1631 edit.setCompleter(&completer);
1632 edit.move(ax: 200, ay: 200);
1633 edit.show();
1634 edit.setFocus();
1635 QApplication::setActiveWindow(&edit);
1636 QVERIFY(QTest::qWaitForWindowActive(&edit));
1637
1638 QTest::keyClick(widget: &edit, key: 'r');
1639 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Down);
1640 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Down);
1641 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Enter);
1642
1643 QCOMPARE(edit.text(), QString("row 1 column 1"));
1644
1645 edit.clear();
1646
1647 QTest::keyClick(widget: &edit, key: 'r');
1648 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Up);
1649 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Up);
1650 QTest::keyClick(widget: edit.completer()->popup(), key: Qt::Key_Enter);
1651
1652 QCOMPARE(edit.text(), QString("row 3 column 1"));
1653}
1654
1655// Helpers for QTBUG_14292_filesystem: Recursion helper for below recurseTreeModel
1656// Function to recurse over a tree model applying a function
1657// taking index and depth, returning true to terminate recursion.
1658template <class Function>
1659bool recurseTreeModel(const QAbstractItemModel &m, const QModelIndex &idx, Function f, int depth = 0)
1660{
1661 if (idx.isValid() && f(idx, depth))
1662 return true;
1663 const int rowCount = m.rowCount(parent: idx);
1664 for (int row = 0; row < rowCount; ++row)
1665 if (recurseTreeModel(m, m.index(row, column: 0, parent: idx), f, depth + 1))
1666 return true;
1667 return false;
1668}
1669
1670// Function applicable to the above recurseTreeModel() to search for a data item.
1671class SearchFunction
1672{
1673public:
1674 SearchFunction(const QString &needle, int role = Qt::DisplayRole) :
1675 m_needle(needle), m_role(role) {}
1676
1677 bool operator()(const QModelIndex &idx, int /* depth */) const
1678 { return idx.data(arole: m_role).toString() == m_needle; }
1679
1680private:
1681 const QString m_needle;
1682 const int m_role;
1683};
1684
1685// Function applicable to the above recurseTreeModel() for debug output
1686// of a model.
1687class DebugFunction
1688{
1689public:
1690 DebugFunction(QDebug d) : m_d(d) {}
1691
1692 bool operator()(const QModelIndex &idx, int depth)
1693 {
1694 for (int i = 0; i < 4 * depth; ++i)
1695 m_d << ' ';
1696 m_d << idx.data(arole: QFileSystemModel::FileNameRole).toString()
1697 << '\n';
1698 return false;
1699 }
1700private:
1701 QDebug m_d;
1702};
1703
1704QDebug operator<<(QDebug d, const QAbstractItemModel &m)
1705{
1706 QDebug dns = d.nospace();
1707 dns << '\n';
1708 recurseTreeModel(m, idx: QModelIndex(), f: DebugFunction(dns));
1709 return d;
1710}
1711
1712static const char testDir1[] = "hello";
1713static const char testDir2[] = "holla";
1714
1715// Helper for QTBUG_14292_filesystem, checking whether both
1716// test directories are seen by the file system model for usage
1717// with QTRY_VERIFY.
1718
1719static inline bool testFileSystemReady(const QAbstractItemModel &model)
1720{
1721 return recurseTreeModel(m: model, idx: QModelIndex(), f: SearchFunction(QLatin1String(testDir1), QFileSystemModel::FileNameRole))
1722 && recurseTreeModel(m: model, idx: QModelIndex(), f: SearchFunction(QLatin1String(testDir2), QFileSystemModel::FileNameRole));
1723}
1724
1725void tst_QCompleter::QTBUG_14292_filesystem()
1726{
1727 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1728 QSKIP("Wayland: This fails. Figure out why.");
1729
1730 // This test tests whether the creation of subdirectories
1731 // does not cause completers based on file system models
1732 // to pop up the completion list due to file changed signals.
1733 FileSystem fs;
1734 QFileSystemModel model;
1735 model.setRootPath(fs.path());
1736
1737 QVERIFY(fs.createDirectory(QLatin1String(testDir1)));
1738 QVERIFY(fs.createDirectory(QLatin1String(testDir2)));
1739
1740 QLineEdit edit;
1741 edit.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
1742 QCompleter comp;
1743 comp.setModel(&model);
1744 edit.setCompleter(&comp);
1745
1746 edit.move(ax: 200, ay: 200);
1747 edit.show();
1748 QApplication::setActiveWindow(&edit);
1749 QVERIFY(QTest::qWaitForWindowActive(&edit));
1750 QCOMPARE(QApplication::activeWindow(), &edit);
1751 edit.setFocus();
1752 QTRY_VERIFY(edit.hasFocus());
1753
1754 // Wait for all file system model slots/timers to trigger
1755 // until the model sees the subdirectories.
1756#ifdef Q_OS_WINRT
1757 QEXPECT_FAIL("", "Fails on WinRT - QTBUG-68297", Abort);
1758#endif
1759 QTRY_VERIFY(testFileSystemReady(model));
1760 // But this should not cause the combo to pop up.
1761 QVERIFY(!comp.popup()->isVisible());
1762 edit.setText(fs.path());
1763 QTest::keyClick(widget: &edit, key: '/');
1764 QTRY_VERIFY(comp.popup()->isVisible());
1765 QCOMPARE(comp.popup()->model()->rowCount(), 2);
1766 QApplication::processEvents();
1767 QTest::keyClick(widget: &edit, key: 'h');
1768 QCOMPARE(comp.popup()->model()->rowCount(), 2);
1769 QTest::keyClick(widget: &edit, key: 'e');
1770 QCOMPARE(comp.popup()->model()->rowCount(), 1);
1771 QTest::keyClick(widget: &edit, key: 'r');
1772 QTRY_VERIFY(!comp.popup()->isVisible());
1773 QVERIFY(fs.createDirectory(QStringLiteral("hero")));
1774 QTRY_VERIFY(comp.popup()->isVisible());
1775 QCOMPARE(comp.popup()->model()->rowCount(), 1);
1776 QTest::keyClick(widget: comp.popup(), key: Qt::Key_Escape);
1777 QTRY_VERIFY(!comp.popup()->isVisible());
1778 QVERIFY(fs.createDirectory(QStringLiteral("nothingThere")));
1779 //there is no reason creating a file should open a popup, it did in Qt 4.7.0
1780 QTest::qWait(ms: 60);
1781 QVERIFY(!comp.popup()->isVisible());
1782
1783 QTest::keyClick(widget: &edit, key: Qt::Key_Backspace);
1784 QTRY_VERIFY(comp.popup()->isVisible());
1785 QCOMPARE(comp.popup()->model()->rowCount(), 2);
1786 QTest::keyClick(widget: &edit, key: 'm');
1787 QTRY_VERIFY(!comp.popup()->isVisible());
1788
1789 QWidget w;
1790 w.move(ax: 400, ay: 200);
1791 w.show();
1792 QApplication::setActiveWindow(&w);
1793 QVERIFY(QTest::qWaitForWindowActive(&w));
1794 QVERIFY(!edit.hasFocus() && !comp.popup()->hasFocus());
1795
1796 QVERIFY(fs.createDirectory(QStringLiteral("hemo")));
1797 //there is no reason creating a file should open a popup, it did in Qt 4.7.0
1798 QTest::qWait(ms: 60);
1799 QVERIFY(!comp.popup()->isVisible());
1800}
1801
1802void tst_QCompleter::QTBUG_52028_tabAutoCompletes()
1803{
1804 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1805 QSKIP("Wayland: This fails. Figure out why.");
1806
1807 QWidget w;
1808 w.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
1809 w.setLayout(new QVBoxLayout);
1810
1811 QComboBox cbox;
1812 cbox.setEditable(true);
1813 cbox.setInsertPolicy(QComboBox::NoInsert);
1814 cbox.addItems(texts: {"foobar1", "foobar2", "hux"});
1815
1816 cbox.completer()->setCaseSensitivity(Qt::CaseInsensitive);
1817 cbox.completer()->setCompletionMode(QCompleter::PopupCompletion);
1818
1819 w.layout()->addWidget(w: &cbox);
1820
1821 // Adding a line edit is a good reason for tab to do something unrelated
1822 auto le = new QLineEdit;
1823 w.layout()->addWidget(w: le);
1824
1825 const auto pos = w.screen()->availableGeometry().topLeft() + QPoint(200,200);
1826 w.move(pos);
1827 w.show();
1828 QApplication::setActiveWindow(&w);
1829 QVERIFY(QTest::qWaitForWindowActive(&w));
1830
1831 QSignalSpy activatedSpy(&cbox, QOverload<int>::of(ptr: &QComboBox::activated));
1832
1833 // Tab key will complete but not activate
1834 cbox.lineEdit()->clear();
1835 QTest::keyClick(widget: &cbox, key: Qt::Key_H);
1836 QVERIFY(cbox.completer()->popup());
1837 QTRY_VERIFY(cbox.completer()->popup()->isVisible());
1838 QTest::keyClick(widget: cbox.completer()->popup(), key: Qt::Key_Tab);
1839 QCOMPARE(cbox.completer()->currentCompletion(), QLatin1String("hux"));
1840 QCOMPARE(activatedSpy.count(), 0);
1841 QEXPECT_FAIL("", "QTBUG-52028 will not be fixed today.", Abort);
1842 QCOMPARE(cbox.currentText(), QLatin1String("hux"));
1843 QCOMPARE(activatedSpy.count(), 0);
1844 QVERIFY(!le->hasFocus());
1845}
1846
1847void tst_QCompleter::QTBUG_51889_activatedSentTwice()
1848{
1849 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1850 QSKIP("Wayland: This fails. Figure out why.");
1851
1852 QWidget w;
1853 w.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
1854 w.setLayout(new QVBoxLayout);
1855
1856 QComboBox cbox;
1857 setFrameless(&cbox);
1858 cbox.setEditable(true);
1859 cbox.setInsertPolicy(QComboBox::NoInsert);
1860 cbox.addItems(texts: {"foobar1", "foobar2", "bar", "hux"});
1861
1862 cbox.completer()->setCaseSensitivity(Qt::CaseInsensitive);
1863 cbox.completer()->setCompletionMode(QCompleter::PopupCompletion);
1864
1865 w.layout()->addWidget(w: &cbox);
1866
1867 w.layout()->addWidget(w: new QLineEdit);
1868
1869 const auto pos = w.screen()->availableGeometry().topLeft() + QPoint(200,200);
1870 w.move(pos);
1871 w.show();
1872 QApplication::setActiveWindow(&w);
1873 QVERIFY(QTest::qWaitForWindowActive(&w));
1874
1875 QSignalSpy activatedSpy(&cbox, QOverload<int>::of(ptr: &QComboBox::activated));
1876
1877 // Navigate + enter activates only once (first item)
1878 cbox.lineEdit()->clear();
1879 QTest::keyClick(widget: &cbox, key: Qt::Key_F);
1880 QVERIFY(cbox.completer()->popup());
1881 QTRY_VERIFY(cbox.completer()->popup()->isVisible());
1882 QTest::keyClick(widget: cbox.completer()->popup(), key: Qt::Key_Down);
1883 QTest::keyClick(widget: cbox.completer()->popup(), key: Qt::Key_Return);
1884 QTRY_COMPARE(activatedSpy.count(), 1);
1885
1886 // Navigate + enter activates only once (non-first item)
1887 cbox.lineEdit()->clear();
1888 activatedSpy.clear();
1889 QTest::keyClick(widget: &cbox, key: Qt::Key_H);
1890 QVERIFY(cbox.completer()->popup());
1891 QTRY_VERIFY(cbox.completer()->popup()->isVisible());
1892 QTest::keyClick(widget: cbox.completer()->popup(), key: Qt::Key_Down);
1893 QTest::keyClick(widget: cbox.completer()->popup(), key: Qt::Key_Return);
1894 QTRY_COMPARE(activatedSpy.count(), 1);
1895
1896 // Full text + enter activates only once
1897 cbox.lineEdit()->clear();
1898 activatedSpy.clear();
1899 QTest::keyClicks(widget: &cbox, sequence: "foobar1");
1900 QVERIFY(cbox.completer()->popup());
1901 QTRY_VERIFY(cbox.completer()->popup()->isVisible());
1902 QTest::keyClick(widget: &cbox, key: Qt::Key_Return);
1903 QTRY_COMPARE(activatedSpy.count(), 1);
1904}
1905
1906QTEST_MAIN(tst_QCompleter)
1907#include "tst_qcompleter.moc"
1908

source code of qtbase/tests/auto/widgets/util/qcompleter/tst_qcompleter.cpp