1 | // Copyright (C) 2016 The Qt Company Ltd. |
---|---|
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | /*! |
5 | \class QCompleter |
6 | \brief The QCompleter class provides completions based on an item model. |
7 | \since 4.2 |
8 | |
9 | \inmodule QtWidgets |
10 | |
11 | You can use QCompleter to provide auto completions in any Qt |
12 | widget, such as QLineEdit and QComboBox. |
13 | When the user starts typing a word, QCompleter suggests possible ways of |
14 | completing the word, based on a word list. The word list is |
15 | provided as a QAbstractItemModel. (For simple applications, where |
16 | the word list is static, you can pass a QStringList to |
17 | QCompleter's constructor.) |
18 | |
19 | \section1 Basic Usage |
20 | |
21 | A QCompleter is used typically with a QLineEdit or QComboBox. |
22 | For example, here's how to provide auto completions from a simple |
23 | word list in a QLineEdit: |
24 | |
25 | \snippet code/src_gui_util_qcompleter.cpp 0 |
26 | |
27 | A QFileSystemModel can be used to provide auto completion of file names. |
28 | For example: |
29 | |
30 | \snippet code/src_gui_util_qcompleter.cpp 1 |
31 | |
32 | To set the model on which QCompleter should operate, call |
33 | setModel(). By default, QCompleter will attempt to match the \l |
34 | {completionPrefix}{completion prefix} (i.e., the word that the |
35 | user has started typing) against the Qt::EditRole data stored in |
36 | column 0 in the model case sensitively. This can be changed |
37 | using setCompletionRole(), setCompletionColumn(), and |
38 | setCaseSensitivity(). |
39 | |
40 | If the model is sorted on the column and role that are used for completion, |
41 | you can call setModelSorting() with either |
42 | QCompleter::CaseSensitivelySortedModel or |
43 | QCompleter::CaseInsensitivelySortedModel as the argument. On large models, |
44 | this can lead to significant performance improvements, because QCompleter |
45 | can then use binary search instead of linear search. The binary search only |
46 | works when the filterMode is Qt::MatchStartsWith. |
47 | |
48 | The model can be a \l{QAbstractListModel}{list model}, |
49 | a \l{QAbstractTableModel}{table model}, or a |
50 | \l{QAbstractItemModel}{tree model}. Completion on tree models |
51 | is slightly more involved and is covered in the \l{Handling |
52 | Tree Models} section below. |
53 | |
54 | The completionMode() determines the mode used to provide completions to |
55 | the user. |
56 | |
57 | \section1 Iterating Through Completions |
58 | |
59 | To retrieve a single candidate string, call setCompletionPrefix() |
60 | with the text that needs to be completed and call |
61 | currentCompletion(). You can iterate through the list of |
62 | completions as below: |
63 | |
64 | \snippet code/src_gui_util_qcompleter.cpp 2 |
65 | |
66 | completionCount() returns the total number of completions for the |
67 | current prefix. completionCount() should be avoided when possible, |
68 | since it requires a scan of the entire model. |
69 | |
70 | \section1 The Completion Model |
71 | |
72 | completionModel() return a list model that contains all possible |
73 | completions for the current completion prefix, in the order in which |
74 | they appear in the model. This model can be used to display the current |
75 | completions in a custom view. Calling setCompletionPrefix() automatically |
76 | refreshes the completion model. |
77 | |
78 | \section1 Handling Tree Models |
79 | |
80 | QCompleter can look for completions in tree models, assuming |
81 | that any item (or sub-item or sub-sub-item) can be unambiguously |
82 | represented as a string by specifying the path to the item. The |
83 | completion is then performed one level at a time. |
84 | |
85 | Let's take the example of a user typing in a file system path. |
86 | The model is a (hierarchical) QFileSystemModel. The completion |
87 | occurs for every element in the path. For example, if the current |
88 | text is \c C:\Wind, QCompleter might suggest \c Windows to |
89 | complete the current path element. Similarly, if the current text |
90 | is \c C:\Windows\Sy, QCompleter might suggest \c System. |
91 | |
92 | For this kind of completion to work, QCompleter needs to be able to |
93 | split the path into a list of strings that are matched at each level. |
94 | For \c C:\Windows\Sy, it needs to be split as "C:", "Windows" and "Sy". |
95 | The default implementation of splitPath(), splits the completionPrefix |
96 | using QDir::separator() if the model is a QFileSystemModel. |
97 | |
98 | To provide completions, QCompleter needs to know the path from an index. |
99 | This is provided by pathFromIndex(). The default implementation of |
100 | pathFromIndex(), returns the data for the \l{Qt::EditRole}{edit role} |
101 | for list models and the absolute file path if the mode is a QFileSystemModel. |
102 | |
103 | \sa QAbstractItemModel, QLineEdit, QComboBox, {Completer Example} |
104 | */ |
105 | |
106 | #include "qcompleter_p.h" |
107 | |
108 | #include "QtWidgets/qscrollbar.h" |
109 | #include "QtCore/qdir.h" |
110 | #if QT_CONFIG(stringlistmodel) |
111 | #include "QtCore/qstringlistmodel.h" |
112 | #endif |
113 | #if QT_CONFIG(filesystemmodel) |
114 | #include "QtGui/qfilesystemmodel.h" |
115 | #endif |
116 | #include "QtWidgets/qheaderview.h" |
117 | #if QT_CONFIG(listview) |
118 | #include "QtWidgets/qlistview.h" |
119 | #endif |
120 | #include "QtWidgets/qapplication.h" |
121 | #include "QtGui/qevent.h" |
122 | #include <private/qapplication_p.h> |
123 | #include <private/qwidget_p.h> |
124 | #if QT_CONFIG(lineedit) |
125 | #include "QtWidgets/qlineedit.h" |
126 | #endif |
127 | #include "QtCore/qdir.h" |
128 | |
129 | QT_BEGIN_NAMESPACE |
130 | |
131 | using namespace Qt::StringLiterals; |
132 | |
133 | QCompletionModel::QCompletionModel(QCompleterPrivate *c, QObject *parent) |
134 | : QAbstractProxyModel(*new QCompletionModelPrivate, parent), |
135 | c(c), showAll(false) |
136 | { |
137 | createEngine(); |
138 | } |
139 | |
140 | int QCompletionModel::columnCount(const QModelIndex &) const |
141 | { |
142 | Q_D(const QCompletionModel); |
143 | return d->model->columnCount(); |
144 | } |
145 | |
146 | void QCompletionModel::setSourceModel(QAbstractItemModel *source) |
147 | { |
148 | bool hadModel = (sourceModel() != nullptr); |
149 | |
150 | if (hadModel) |
151 | QObject::disconnect(sender: sourceModel(), signal: nullptr, receiver: this, member: nullptr); |
152 | |
153 | QAbstractProxyModel::setSourceModel(source); |
154 | |
155 | if (source) { |
156 | // TODO: Optimize updates in the source model |
157 | connect(sender: source, SIGNAL(modelReset()), receiver: this, SLOT(invalidate())); |
158 | connect(sender: source, SIGNAL(destroyed()), receiver: this, SLOT(modelDestroyed())); |
159 | connect(sender: source, SIGNAL(layoutChanged()), receiver: this, SLOT(invalidate())); |
160 | connect(sender: source, SIGNAL(rowsInserted(QModelIndex,int,int)), receiver: this, SLOT(rowsInserted())); |
161 | connect(sender: source, SIGNAL(rowsRemoved(QModelIndex,int,int)), receiver: this, SLOT(invalidate())); |
162 | connect(sender: source, SIGNAL(columnsInserted(QModelIndex,int,int)), receiver: this, SLOT(invalidate())); |
163 | connect(sender: source, SIGNAL(columnsRemoved(QModelIndex,int,int)), receiver: this, SLOT(invalidate())); |
164 | connect(sender: source, SIGNAL(dataChanged(QModelIndex,QModelIndex)), receiver: this, SLOT(invalidate())); |
165 | } |
166 | |
167 | invalidate(); |
168 | } |
169 | |
170 | void QCompletionModel::createEngine() |
171 | { |
172 | bool sortedEngine = false; |
173 | if (c->filterMode == Qt::MatchStartsWith) { |
174 | switch (c->sorting) { |
175 | case QCompleter::UnsortedModel: |
176 | sortedEngine = false; |
177 | break; |
178 | case QCompleter::CaseSensitivelySortedModel: |
179 | sortedEngine = c->cs == Qt::CaseSensitive; |
180 | break; |
181 | case QCompleter::CaseInsensitivelySortedModel: |
182 | sortedEngine = c->cs == Qt::CaseInsensitive; |
183 | break; |
184 | } |
185 | } |
186 | |
187 | if (sortedEngine) |
188 | engine.reset(other: new QSortedModelEngine(c)); |
189 | else |
190 | engine.reset(other: new QUnsortedModelEngine(c)); |
191 | } |
192 | |
193 | QModelIndex QCompletionModel::mapToSource(const QModelIndex& index) const |
194 | { |
195 | Q_D(const QCompletionModel); |
196 | if (!index.isValid()) |
197 | return engine->curParent; |
198 | |
199 | int row; |
200 | QModelIndex parent = engine->curParent; |
201 | if (!showAll) { |
202 | if (!engine->matchCount()) |
203 | return QModelIndex(); |
204 | Q_ASSERT(index.row() < engine->matchCount()); |
205 | QIndexMapper& rootIndices = engine->historyMatch.indices; |
206 | if (index.row() < rootIndices.count()) { |
207 | row = rootIndices[index.row()]; |
208 | parent = QModelIndex(); |
209 | } else { |
210 | row = engine->curMatch.indices[index.row() - rootIndices.count()]; |
211 | } |
212 | } else { |
213 | row = index.row(); |
214 | } |
215 | |
216 | return d->model->index(row, column: index.column(), parent); |
217 | } |
218 | |
219 | QModelIndex QCompletionModel::mapFromSource(const QModelIndex& idx) const |
220 | { |
221 | if (!idx.isValid()) |
222 | return QModelIndex(); |
223 | |
224 | int row = -1; |
225 | if (!showAll) { |
226 | if (!engine->matchCount()) |
227 | return QModelIndex(); |
228 | |
229 | QIndexMapper& rootIndices = engine->historyMatch.indices; |
230 | if (idx.parent().isValid()) { |
231 | if (idx.parent() != engine->curParent) |
232 | return QModelIndex(); |
233 | } else { |
234 | row = rootIndices.indexOf(x: idx.row()); |
235 | if (row == -1 && engine->curParent.isValid()) |
236 | return QModelIndex(); // source parent and our parent don't match |
237 | } |
238 | |
239 | if (row == -1) { |
240 | QIndexMapper& indices = engine->curMatch.indices; |
241 | engine->filterOnDemand(idx.row() - indices.last()); |
242 | row = indices.indexOf(x: idx.row()) + rootIndices.count(); |
243 | } |
244 | |
245 | if (row == -1) |
246 | return QModelIndex(); |
247 | } else { |
248 | if (idx.parent() != engine->curParent) |
249 | return QModelIndex(); |
250 | row = idx.row(); |
251 | } |
252 | |
253 | return createIndex(arow: row, acolumn: idx.column()); |
254 | } |
255 | |
256 | bool QCompletionModel::setCurrentRow(int row) |
257 | { |
258 | if (row < 0 || !engine->matchCount()) |
259 | return false; |
260 | |
261 | if (row >= engine->matchCount()) |
262 | engine->filterOnDemand(row + 1 - engine->matchCount()); |
263 | |
264 | if (row >= engine->matchCount()) // invalid row |
265 | return false; |
266 | |
267 | engine->curRow = row; |
268 | return true; |
269 | } |
270 | |
271 | QModelIndex QCompletionModel::currentIndex(bool sourceIndex) const |
272 | { |
273 | if (!engine->matchCount()) |
274 | return QModelIndex(); |
275 | |
276 | int row = engine->curRow; |
277 | if (showAll) |
278 | row = engine->curMatch.indices[engine->curRow]; |
279 | |
280 | QModelIndex idx = createIndex(arow: row, acolumn: c->column); |
281 | if (!sourceIndex) |
282 | return idx; |
283 | return mapToSource(index: idx); |
284 | } |
285 | |
286 | QModelIndex QCompletionModel::index(int row, int column, const QModelIndex& parent) const |
287 | { |
288 | Q_D(const QCompletionModel); |
289 | if (row < 0 || column < 0 || column >= columnCount(parent) || parent.isValid()) |
290 | return QModelIndex(); |
291 | |
292 | if (!showAll) { |
293 | if (!engine->matchCount()) |
294 | return QModelIndex(); |
295 | if (row >= engine->historyMatch.indices.count()) { |
296 | int want = row + 1 - engine->matchCount(); |
297 | if (want > 0) |
298 | engine->filterOnDemand(want); |
299 | if (row >= engine->matchCount()) |
300 | return QModelIndex(); |
301 | } |
302 | } else { |
303 | if (row >= d->model->rowCount(parent: engine->curParent)) |
304 | return QModelIndex(); |
305 | } |
306 | |
307 | return createIndex(arow: row, acolumn: column); |
308 | } |
309 | |
310 | int QCompletionModel::completionCount() const |
311 | { |
312 | if (!engine->matchCount()) |
313 | return 0; |
314 | |
315 | engine->filterOnDemand(INT_MAX); |
316 | return engine->matchCount(); |
317 | } |
318 | |
319 | int QCompletionModel::rowCount(const QModelIndex &parent) const |
320 | { |
321 | Q_D(const QCompletionModel); |
322 | if (parent.isValid()) |
323 | return 0; |
324 | |
325 | if (showAll) { |
326 | // Show all items below current parent, even if we have no valid matches |
327 | if (engine->curParts.size() != 1 && !engine->matchCount() |
328 | && !engine->curParent.isValid()) |
329 | return 0; |
330 | return d->model->rowCount(parent: engine->curParent); |
331 | } |
332 | |
333 | return completionCount(); |
334 | } |
335 | |
336 | void QCompletionModel::setFiltered(bool filtered) |
337 | { |
338 | if (showAll == !filtered) |
339 | return; |
340 | beginResetModel(); |
341 | showAll = !filtered; |
342 | endResetModel(); |
343 | } |
344 | |
345 | bool QCompletionModel::hasChildren(const QModelIndex &parent) const |
346 | { |
347 | Q_D(const QCompletionModel); |
348 | if (parent.isValid()) |
349 | return false; |
350 | |
351 | if (showAll) |
352 | return d->model->hasChildren(parent: mapToSource(index: parent)); |
353 | |
354 | if (!engine->matchCount()) |
355 | return false; |
356 | |
357 | return true; |
358 | } |
359 | |
360 | QVariant QCompletionModel::data(const QModelIndex& index, int role) const |
361 | { |
362 | Q_D(const QCompletionModel); |
363 | return d->model->data(index: mapToSource(index), role); |
364 | } |
365 | |
366 | void QCompletionModel::modelDestroyed() |
367 | { |
368 | QAbstractProxyModel::setSourceModel(nullptr); // switch to static empty model |
369 | invalidate(); |
370 | } |
371 | |
372 | void QCompletionModel::rowsInserted() |
373 | { |
374 | invalidate(); |
375 | emit rowsAdded(); |
376 | } |
377 | |
378 | void QCompletionModel::invalidate() |
379 | { |
380 | engine->cache.clear(); |
381 | filter(parts: engine->curParts); |
382 | } |
383 | |
384 | void QCompletionModel::filter(const QStringList& parts) |
385 | { |
386 | Q_D(QCompletionModel); |
387 | beginResetModel(); |
388 | engine->filter(parts); |
389 | endResetModel(); |
390 | |
391 | if (d->model->canFetchMore(parent: engine->curParent)) |
392 | d->model->fetchMore(parent: engine->curParent); |
393 | } |
394 | |
395 | ////////////////////////////////////////////////////////////////////////////// |
396 | void QCompletionEngine::filter(const QStringList& parts) |
397 | { |
398 | const QAbstractItemModel *model = c->proxy->sourceModel(); |
399 | curParts = parts; |
400 | if (curParts.isEmpty()) |
401 | curParts.append(t: QString()); |
402 | |
403 | curRow = -1; |
404 | curParent = QModelIndex(); |
405 | curMatch = QMatchData(); |
406 | historyMatch = filterHistory(); |
407 | |
408 | if (!model) |
409 | return; |
410 | |
411 | QModelIndex parent; |
412 | for (int i = 0; i < curParts.size() - 1; i++) { |
413 | QString part = curParts.at(i); |
414 | int emi = filter(part, parent, -1).exactMatchIndex; |
415 | if (emi == -1) |
416 | return; |
417 | parent = model->index(row: emi, column: c->column, parent); |
418 | } |
419 | |
420 | // Note that we set the curParent to a valid parent, even if we have no matches |
421 | // When filtering is disabled, we show all the items under this parent |
422 | curParent = parent; |
423 | if (curParts.constLast().isEmpty()) |
424 | curMatch = QMatchData(QIndexMapper(0, model->rowCount(parent: curParent) - 1), -1, false); |
425 | else |
426 | curMatch = filter(curParts.constLast(), curParent, 1); // build at least one |
427 | curRow = curMatch.isValid() ? 0 : -1; |
428 | } |
429 | |
430 | QMatchData QCompletionEngine::filterHistory() |
431 | { |
432 | QAbstractItemModel *source = c->proxy->sourceModel(); |
433 | if (curParts.size() <= 1 || c->proxy->showAll || !source) |
434 | return QMatchData(); |
435 | |
436 | #if QT_CONFIG(filesystemmodel) |
437 | const bool isFsModel = (qobject_cast<QFileSystemModel *>(object: source) != nullptr); |
438 | #else |
439 | const bool isFsModel = false; |
440 | #endif |
441 | Q_UNUSED(isFsModel); |
442 | QList<int> v; |
443 | QIndexMapper im(v); |
444 | QMatchData m(im, -1, true); |
445 | |
446 | for (int i = 0; i < source->rowCount(); i++) { |
447 | QString str = source->index(row: i, column: c->column).data().toString(); |
448 | if (str.startsWith(s: c->prefix, cs: c->cs) |
449 | #if !defined(Q_OS_WIN) |
450 | && (!isFsModel || QDir::toNativeSeparators(pathName: str) != QDir::separator()) |
451 | #endif |
452 | ) |
453 | m.indices.append(x: i); |
454 | } |
455 | return m; |
456 | } |
457 | |
458 | // Returns a match hint from the cache by chopping the search string |
459 | bool QCompletionEngine::matchHint(const QString &part, const QModelIndex &parent, QMatchData *hint) const |
460 | { |
461 | if (part.isEmpty()) |
462 | return false; // early out to avoid cache[parent] lookup costs |
463 | |
464 | const auto cit = cache.find(key: parent); |
465 | if (cit == cache.end()) |
466 | return false; |
467 | |
468 | const CacheItem& map = *cit; |
469 | const auto mapEnd = map.end(); |
470 | |
471 | QString key = c->cs == Qt::CaseInsensitive ? part.toLower() : part; |
472 | |
473 | while (!key.isEmpty()) { |
474 | key.chop(n: 1); |
475 | const auto it = map.find(key); |
476 | if (it != mapEnd) { |
477 | *hint = *it; |
478 | return true; |
479 | } |
480 | } |
481 | |
482 | return false; |
483 | } |
484 | |
485 | bool QCompletionEngine::lookupCache(const QString &part, const QModelIndex &parent, QMatchData *m) const |
486 | { |
487 | if (part.isEmpty()) |
488 | return false; // early out to avoid cache[parent] lookup costs |
489 | |
490 | const auto cit = cache.find(key: parent); |
491 | if (cit == cache.end()) |
492 | return false; |
493 | |
494 | const CacheItem& map = *cit; |
495 | |
496 | const QString key = c->cs == Qt::CaseInsensitive ? part.toLower() : part; |
497 | |
498 | const auto it = map.find(key); |
499 | if (it == map.end()) |
500 | return false; |
501 | |
502 | *m = it.value(); |
503 | return true; |
504 | } |
505 | |
506 | // When the cache size exceeds 1MB, it clears out about 1/2 of the cache. |
507 | void QCompletionEngine::saveInCache(QString part, const QModelIndex& parent, const QMatchData& m) |
508 | { |
509 | if (c->filterMode == Qt::MatchEndsWith) |
510 | return; |
511 | QMatchData old = cache[parent].take(key: part); |
512 | cost = cost + m.indices.cost() - old.indices.cost(); |
513 | if (cost * sizeof(int) > 1024 * 1024) { |
514 | QMap<QModelIndex, CacheItem>::iterator it1 = cache.begin(); |
515 | while (it1 != cache.end()) { |
516 | CacheItem& ci = it1.value(); |
517 | int sz = ci.size()/2; |
518 | QMap<QString, QMatchData>::iterator it2 = ci.begin(); |
519 | int i = 0; |
520 | while (it2 != ci.end() && i < sz) { |
521 | cost -= it2.value().indices.cost(); |
522 | it2 = ci.erase(it: it2); |
523 | i++; |
524 | } |
525 | if (ci.size() == 0) { |
526 | it1 = cache.erase(it: it1); |
527 | } else { |
528 | ++it1; |
529 | } |
530 | } |
531 | } |
532 | |
533 | if (c->cs == Qt::CaseInsensitive) |
534 | part = std::move(part).toLower(); |
535 | cache[parent][part] = m; |
536 | } |
537 | |
538 | /////////////////////////////////////////////////////////////////////////////////// |
539 | QIndexMapper QSortedModelEngine::indexHint(QString part, const QModelIndex& parent, Qt::SortOrder order) |
540 | { |
541 | const QAbstractItemModel *model = c->proxy->sourceModel(); |
542 | |
543 | if (c->cs == Qt::CaseInsensitive) |
544 | part = std::move(part).toLower(); |
545 | |
546 | const CacheItem& map = cache[parent]; |
547 | |
548 | // Try to find a lower and upper bound for the search from previous results |
549 | int to = model->rowCount(parent) - 1; |
550 | int from = 0; |
551 | const CacheItem::const_iterator it = map.lowerBound(key: part); |
552 | |
553 | // look backward for first valid hint |
554 | for (CacheItem::const_iterator it1 = it; it1 != map.constBegin();) { |
555 | --it1; |
556 | const QMatchData& value = it1.value(); |
557 | if (value.isValid()) { |
558 | if (order == Qt::AscendingOrder) { |
559 | from = value.indices.last() + 1; |
560 | } else { |
561 | to = value.indices.first() - 1; |
562 | } |
563 | break; |
564 | } |
565 | } |
566 | |
567 | // look forward for first valid hint |
568 | for(CacheItem::const_iterator it2 = it; it2 != map.constEnd(); ++it2) { |
569 | const QMatchData& value = it2.value(); |
570 | if (value.isValid() && !it2.key().startsWith(s: part)) { |
571 | if (order == Qt::AscendingOrder) { |
572 | to = value.indices.first() - 1; |
573 | } else { |
574 | from = value.indices.first() + 1; |
575 | } |
576 | break; |
577 | } |
578 | } |
579 | |
580 | return QIndexMapper(from, to); |
581 | } |
582 | |
583 | Qt::SortOrder QSortedModelEngine::sortOrder(const QModelIndex &parent) const |
584 | { |
585 | const QAbstractItemModel *model = c->proxy->sourceModel(); |
586 | |
587 | int rowCount = model->rowCount(parent); |
588 | if (rowCount < 2) |
589 | return Qt::AscendingOrder; |
590 | QString first = model->data(index: model->index(row: 0, column: c->column, parent), role: c->role).toString(); |
591 | QString last = model->data(index: model->index(row: rowCount - 1, column: c->column, parent), role: c->role).toString(); |
592 | return QString::compare(s1: first, s2: last, cs: c->cs) <= 0 ? Qt::AscendingOrder : Qt::DescendingOrder; |
593 | } |
594 | |
595 | QMatchData QSortedModelEngine::filter(const QString& part, const QModelIndex& parent, int) |
596 | { |
597 | const QAbstractItemModel *model = c->proxy->sourceModel(); |
598 | |
599 | QMatchData hint; |
600 | if (lookupCache(part, parent, m: &hint)) |
601 | return hint; |
602 | |
603 | QIndexMapper indices; |
604 | Qt::SortOrder order = sortOrder(parent); |
605 | |
606 | if (matchHint(part, parent, hint: &hint)) { |
607 | if (!hint.isValid()) |
608 | return QMatchData(); |
609 | indices = hint.indices; |
610 | } else { |
611 | indices = indexHint(part, parent, order); |
612 | } |
613 | |
614 | // binary search the model within 'indices' for 'part' under 'parent' |
615 | int high = indices.to() + 1; |
616 | int low = indices.from() - 1; |
617 | int probe; |
618 | QModelIndex probeIndex; |
619 | QString probeData; |
620 | |
621 | while (high - low > 1) |
622 | { |
623 | probe = (high + low) / 2; |
624 | probeIndex = model->index(row: probe, column: c->column, parent); |
625 | probeData = model->data(index: probeIndex, role: c->role).toString(); |
626 | const int cmp = QString::compare(s1: probeData, s2: part, cs: c->cs); |
627 | if ((order == Qt::AscendingOrder && cmp >= 0) |
628 | || (order == Qt::DescendingOrder && cmp < 0)) { |
629 | high = probe; |
630 | } else { |
631 | low = probe; |
632 | } |
633 | } |
634 | |
635 | if ((order == Qt::AscendingOrder && low == indices.to()) |
636 | || (order == Qt::DescendingOrder && high == indices.from())) { // not found |
637 | saveInCache(part, parent, m: QMatchData()); |
638 | return QMatchData(); |
639 | } |
640 | |
641 | probeIndex = model->index(row: order == Qt::AscendingOrder ? low+1 : high-1, column: c->column, parent); |
642 | probeData = model->data(index: probeIndex, role: c->role).toString(); |
643 | if (!probeData.startsWith(s: part, cs: c->cs)) { |
644 | saveInCache(part, parent, m: QMatchData()); |
645 | return QMatchData(); |
646 | } |
647 | |
648 | const bool exactMatch = QString::compare(s1: probeData, s2: part, cs: c->cs) == 0; |
649 | int emi = exactMatch ? (order == Qt::AscendingOrder ? low+1 : high-1) : -1; |
650 | |
651 | int from = 0; |
652 | int to = 0; |
653 | if (order == Qt::AscendingOrder) { |
654 | from = low + 1; |
655 | high = indices.to() + 1; |
656 | low = from; |
657 | } else { |
658 | to = high - 1; |
659 | low = indices.from() - 1; |
660 | high = to; |
661 | } |
662 | |
663 | while (high - low > 1) |
664 | { |
665 | probe = (high + low) / 2; |
666 | probeIndex = model->index(row: probe, column: c->column, parent); |
667 | probeData = model->data(index: probeIndex, role: c->role).toString(); |
668 | const bool startsWith = probeData.startsWith(s: part, cs: c->cs); |
669 | if ((order == Qt::AscendingOrder && startsWith) |
670 | || (order == Qt::DescendingOrder && !startsWith)) { |
671 | low = probe; |
672 | } else { |
673 | high = probe; |
674 | } |
675 | } |
676 | |
677 | QMatchData m(order == Qt::AscendingOrder ? QIndexMapper(from, high - 1) : QIndexMapper(low+1, to), emi, false); |
678 | saveInCache(part, parent, m); |
679 | return m; |
680 | } |
681 | |
682 | //////////////////////////////////////////////////////////////////////////////////////// |
683 | int QUnsortedModelEngine::buildIndices(const QString& str, const QModelIndex& parent, int n, |
684 | const QIndexMapper& indices, QMatchData* m) |
685 | { |
686 | Q_ASSERT(m->partial); |
687 | Q_ASSERT(n != -1 || m->exactMatchIndex == -1); |
688 | const QAbstractItemModel *model = c->proxy->sourceModel(); |
689 | int i, count = 0; |
690 | |
691 | for (i = 0; i < indices.count() && count != n; ++i) { |
692 | QModelIndex idx = model->index(row: indices[i], column: c->column, parent); |
693 | |
694 | if (!(model->flags(index: idx) & Qt::ItemIsSelectable)) |
695 | continue; |
696 | |
697 | QString data = model->data(index: idx, role: c->role).toString(); |
698 | |
699 | switch (c->filterMode) { |
700 | case Qt::MatchStartsWith: |
701 | if (!data.startsWith(s: str, cs: c->cs)) |
702 | continue; |
703 | break; |
704 | case Qt::MatchContains: |
705 | if (!data.contains(s: str, cs: c->cs)) |
706 | continue; |
707 | break; |
708 | case Qt::MatchEndsWith: |
709 | if (!data.endsWith(s: str, cs: c->cs)) |
710 | continue; |
711 | break; |
712 | case Qt::MatchExactly: |
713 | case Qt::MatchFixedString: |
714 | case Qt::MatchCaseSensitive: |
715 | case Qt::MatchRegularExpression: |
716 | case Qt::MatchWildcard: |
717 | case Qt::MatchWrap: |
718 | case Qt::MatchRecursive: |
719 | Q_UNREACHABLE(); |
720 | break; |
721 | } |
722 | m->indices.append(x: indices[i]); |
723 | ++count; |
724 | if (m->exactMatchIndex == -1 && QString::compare(s1: data, s2: str, cs: c->cs) == 0) { |
725 | m->exactMatchIndex = indices[i]; |
726 | if (n == -1) |
727 | return indices[i]; |
728 | } |
729 | } |
730 | return indices[i-1]; |
731 | } |
732 | |
733 | void QUnsortedModelEngine::filterOnDemand(int n) |
734 | { |
735 | Q_ASSERT(matchCount()); |
736 | if (!curMatch.partial) |
737 | return; |
738 | Q_ASSERT(n >= -1); |
739 | const QAbstractItemModel *model = c->proxy->sourceModel(); |
740 | int lastRow = model->rowCount(parent: curParent) - 1; |
741 | QIndexMapper im(curMatch.indices.last() + 1, lastRow); |
742 | int lastIndex = buildIndices(str: curParts.constLast(), parent: curParent, n, indices: im, m: &curMatch); |
743 | curMatch.partial = (lastRow != lastIndex); |
744 | saveInCache(part: curParts.constLast(), parent: curParent, m: curMatch); |
745 | } |
746 | |
747 | QMatchData QUnsortedModelEngine::filter(const QString& part, const QModelIndex& parent, int n) |
748 | { |
749 | QMatchData hint; |
750 | |
751 | QList<int> v; |
752 | QIndexMapper im(v); |
753 | QMatchData m(im, -1, true); |
754 | |
755 | const QAbstractItemModel *model = c->proxy->sourceModel(); |
756 | bool foundInCache = lookupCache(part, parent, m: &m); |
757 | |
758 | if (!foundInCache) { |
759 | if (matchHint(part, parent, hint: &hint) && !hint.isValid()) |
760 | return QMatchData(); |
761 | } |
762 | |
763 | if (!foundInCache && !hint.isValid()) { |
764 | const int lastRow = model->rowCount(parent) - 1; |
765 | QIndexMapper all(0, lastRow); |
766 | int lastIndex = buildIndices(str: part, parent, n, indices: all, m: &m); |
767 | m.partial = (lastIndex != lastRow); |
768 | } else { |
769 | if (!foundInCache) { // build from hint as much as we can |
770 | buildIndices(str: part, parent, INT_MAX, indices: hint.indices, m: &m); |
771 | m.partial = hint.partial; |
772 | } |
773 | if (m.partial && ((n == -1 && m.exactMatchIndex == -1) || (m.indices.count() < n))) { |
774 | // need more and have more |
775 | const int lastRow = model->rowCount(parent) - 1; |
776 | QIndexMapper rest(hint.indices.last() + 1, lastRow); |
777 | int want = n == -1 ? -1 : n - m.indices.count(); |
778 | int lastIndex = buildIndices(str: part, parent, n: want, indices: rest, m: &m); |
779 | m.partial = (lastRow != lastIndex); |
780 | } |
781 | } |
782 | |
783 | saveInCache(part, parent, m); |
784 | return m; |
785 | } |
786 | |
787 | /////////////////////////////////////////////////////////////////////////////// |
788 | QCompleterPrivate::QCompleterPrivate() |
789 | : widget(nullptr), |
790 | proxy(nullptr), |
791 | popup(nullptr), |
792 | filterMode(Qt::MatchStartsWith), |
793 | cs(Qt::CaseSensitive), |
794 | role(Qt::EditRole), |
795 | column(0), |
796 | maxVisibleItems(7), |
797 | sorting(QCompleter::UnsortedModel), |
798 | wrap(true), |
799 | eatFocusOut(true), |
800 | hiddenBecauseNoMatch(false) |
801 | { |
802 | } |
803 | |
804 | void QCompleterPrivate::init(QAbstractItemModel *m) |
805 | { |
806 | Q_Q(QCompleter); |
807 | proxy = new QCompletionModel(this, q); |
808 | QObject::connect(sender: proxy, SIGNAL(rowsAdded()), receiver: q, SLOT(_q_autoResizePopup())); |
809 | q->setModel(m); |
810 | #if !QT_CONFIG(listview) |
811 | q->setCompletionMode(QCompleter::InlineCompletion); |
812 | #else |
813 | q->setCompletionMode(QCompleter::PopupCompletion); |
814 | #endif // QT_CONFIG(listview) |
815 | } |
816 | |
817 | void QCompleterPrivate::setCurrentIndex(QModelIndex index, bool select) |
818 | { |
819 | Q_Q(QCompleter); |
820 | if (!q->popup()) |
821 | return; |
822 | if (!select) { |
823 | popup->selectionModel()->setCurrentIndex(index, command: QItemSelectionModel::NoUpdate); |
824 | } else { |
825 | if (!index.isValid()) |
826 | popup->selectionModel()->clear(); |
827 | else |
828 | popup->selectionModel()->setCurrentIndex(index, command: QItemSelectionModel::Select |
829 | | QItemSelectionModel::Rows); |
830 | } |
831 | index = popup->selectionModel()->currentIndex(); |
832 | if (!index.isValid()) |
833 | popup->scrollToTop(); |
834 | else |
835 | popup->scrollTo(index, hint: QAbstractItemView::PositionAtTop); |
836 | } |
837 | |
838 | void QCompleterPrivate::_q_completionSelected(const QItemSelection& selection) |
839 | { |
840 | QModelIndex index; |
841 | if (const auto indexes = selection.indexes(); !indexes.isEmpty()) |
842 | index = indexes.first(); |
843 | |
844 | _q_complete(index, true); |
845 | } |
846 | |
847 | void QCompleterPrivate::_q_complete(QModelIndex index, bool highlighted) |
848 | { |
849 | Q_Q(QCompleter); |
850 | QString completion; |
851 | |
852 | if (!index.isValid() || (!proxy->showAll && (index.row() >= proxy->engine->matchCount()))) { |
853 | completion = prefix; |
854 | index = QModelIndex(); |
855 | } else { |
856 | if (!(index.flags() & Qt::ItemIsEnabled)) |
857 | return; |
858 | QModelIndex si = proxy->mapToSource(index); |
859 | si = si.sibling(arow: si.row(), acolumn: column); // for clicked() |
860 | completion = q->pathFromIndex(index: si); |
861 | #if QT_CONFIG(filesystemmodel) |
862 | // add a trailing separator in inline |
863 | if (mode == QCompleter::InlineCompletion) { |
864 | if (qobject_cast<QFileSystemModel *>(object: proxy->sourceModel()) && QFileInfo(completion).isDir()) |
865 | completion += QDir::separator(); |
866 | } |
867 | #endif |
868 | } |
869 | |
870 | if (highlighted) { |
871 | emit q->highlighted(index); |
872 | emit q->highlighted(text: completion); |
873 | } else { |
874 | emit q->activated(index); |
875 | emit q->activated(text: completion); |
876 | } |
877 | } |
878 | |
879 | void QCompleterPrivate::_q_autoResizePopup() |
880 | { |
881 | if (!popup || !popup->isVisible()) |
882 | return; |
883 | showPopup(popupRect); |
884 | } |
885 | |
886 | void QCompleterPrivate::showPopup(const QRect& rect) |
887 | { |
888 | const QRect screen = widget->screen()->availableGeometry(); |
889 | Qt::LayoutDirection dir = widget->layoutDirection(); |
890 | QPoint pos; |
891 | int rh, w; |
892 | int h = (popup->sizeHintForRow(row: 0) * qMin(a: maxVisibleItems, b: popup->model()->rowCount()) + 3) + 3; |
893 | QScrollBar *hsb = popup->horizontalScrollBar(); |
894 | if (hsb && hsb->isVisible()) |
895 | h += popup->horizontalScrollBar()->sizeHint().height(); |
896 | |
897 | if (rect.isValid()) { |
898 | rh = rect.height(); |
899 | w = rect.width(); |
900 | pos = widget->mapToGlobal(dir == Qt::RightToLeft ? rect.bottomRight() : rect.bottomLeft()); |
901 | } else { |
902 | rh = widget->height(); |
903 | pos = widget->mapToGlobal(QPoint(0, widget->height() - 2)); |
904 | w = widget->width(); |
905 | } |
906 | |
907 | if (w > screen.width()) |
908 | w = screen.width(); |
909 | if ((pos.x() + w) > (screen.x() + screen.width())) |
910 | pos.setX(screen.x() + screen.width() - w); |
911 | if (pos.x() < screen.x()) |
912 | pos.setX(screen.x()); |
913 | |
914 | int top = pos.y() - rh - screen.top() + 2; |
915 | int bottom = screen.bottom() - pos.y(); |
916 | h = qMax(a: h, b: popup->minimumHeight()); |
917 | if (h > bottom) { |
918 | h = qMin(a: qMax(a: top, b: bottom), b: h); |
919 | |
920 | if (top > bottom) |
921 | pos.setY(pos.y() - h - rh + 2); |
922 | } |
923 | |
924 | popup->setGeometry(ax: pos.x(), ay: pos.y(), aw: w, ah: h); |
925 | |
926 | if (!popup->isVisible()) |
927 | popup->show(); |
928 | } |
929 | |
930 | #if QT_CONFIG(filesystemmodel) |
931 | static bool isRoot(const QFileSystemModel *model, const QString &path) |
932 | { |
933 | const auto index = model->index(path); |
934 | return index.isValid() && model->fileInfo(index).isRoot(); |
935 | } |
936 | |
937 | static bool completeOnLoaded(const QFileSystemModel *model, |
938 | const QString &nativePrefix, |
939 | const QString &path, |
940 | Qt::CaseSensitivity caseSensitivity) |
941 | { |
942 | const auto pathSize = path.size(); |
943 | const auto prefixSize = nativePrefix.size(); |
944 | if (prefixSize < pathSize) |
945 | return false; |
946 | const QString prefix = QDir::fromNativeSeparators(pathName: nativePrefix); |
947 | if (prefixSize == pathSize) |
948 | return path.compare(s: prefix, cs: caseSensitivity) == 0 && isRoot(model, path); |
949 | // The user is typing something within that directory and is not in a subdirectory yet. |
950 | const auto separator = u'/'; |
951 | return prefix.startsWith(s: path, cs: caseSensitivity) && prefix.at(i: pathSize) == separator |
952 | && !QStringView{prefix}.right(n: prefixSize - pathSize - 1).contains(c: separator); |
953 | } |
954 | |
955 | void QCompleterPrivate::_q_fileSystemModelDirectoryLoaded(const QString &path) |
956 | { |
957 | Q_Q(QCompleter); |
958 | // Slot called when QFileSystemModel has finished loading. |
959 | // If we hide the popup because there was no match because the model was not loaded yet, |
960 | // we re-start the completion when we get the results (unless triggered by |
961 | // something else, see QTBUG-14292). |
962 | if (hiddenBecauseNoMatch && widget) { |
963 | if (auto model = qobject_cast<const QFileSystemModel *>(object: proxy->sourceModel())) { |
964 | if (completeOnLoaded(model, nativePrefix: prefix, path, caseSensitivity: cs)) |
965 | q->complete(); |
966 | } |
967 | } |
968 | } |
969 | #else // QT_CONFIG(filesystemmodel) |
970 | void QCompleterPrivate::_q_fileSystemModelDirectoryLoaded(const QString &) {} |
971 | #endif |
972 | |
973 | /*! |
974 | Constructs a completer object with the given \a parent. |
975 | */ |
976 | QCompleter::QCompleter(QObject *parent) |
977 | : QObject(*new QCompleterPrivate(), parent) |
978 | { |
979 | Q_D(QCompleter); |
980 | d->init(); |
981 | } |
982 | |
983 | /*! |
984 | Constructs a completer object with the given \a parent that provides completions |
985 | from the specified \a model. |
986 | */ |
987 | QCompleter::QCompleter(QAbstractItemModel *model, QObject *parent) |
988 | : QObject(*new QCompleterPrivate(), parent) |
989 | { |
990 | Q_D(QCompleter); |
991 | d->init(m: model); |
992 | } |
993 | |
994 | #if QT_CONFIG(stringlistmodel) |
995 | /*! |
996 | Constructs a QCompleter object with the given \a parent that uses the specified |
997 | \a list as a source of possible completions. |
998 | */ |
999 | QCompleter::QCompleter(const QStringList& list, QObject *parent) |
1000 | : QObject(*new QCompleterPrivate(), parent) |
1001 | { |
1002 | Q_D(QCompleter); |
1003 | d->init(m: new QStringListModel(list, this)); |
1004 | } |
1005 | #endif // QT_CONFIG(stringlistmodel) |
1006 | |
1007 | /*! |
1008 | Destroys the completer object. |
1009 | */ |
1010 | QCompleter::~QCompleter() |
1011 | { |
1012 | } |
1013 | |
1014 | /*! |
1015 | Sets the widget for which completion are provided for to \a widget. This |
1016 | function is automatically called when a QCompleter is set on a QLineEdit |
1017 | using QLineEdit::setCompleter() or on a QComboBox using |
1018 | QComboBox::setCompleter(). The widget needs to be set explicitly when |
1019 | providing completions for custom widgets. |
1020 | |
1021 | \sa widget(), setModel(), setPopup() |
1022 | */ |
1023 | void QCompleter::setWidget(QWidget *widget) |
1024 | { |
1025 | Q_D(QCompleter); |
1026 | if (widget == d->widget) |
1027 | return; |
1028 | |
1029 | if (d->widget) |
1030 | d->widget->removeEventFilter(obj: this); |
1031 | d->widget = widget; |
1032 | if (d->widget) |
1033 | d->widget->installEventFilter(filterObj: this); |
1034 | |
1035 | if (d->popup) { |
1036 | d->popup->hide(); |
1037 | d->popup->setFocusProxy(d->widget); |
1038 | } |
1039 | } |
1040 | |
1041 | /*! |
1042 | Returns the widget for which the completer object is providing completions. |
1043 | |
1044 | \sa setWidget() |
1045 | */ |
1046 | QWidget *QCompleter::widget() const |
1047 | { |
1048 | Q_D(const QCompleter); |
1049 | return d->widget; |
1050 | } |
1051 | |
1052 | /*! |
1053 | Sets the model which provides completions to \a model. The \a model can |
1054 | be list model or a tree model. If a model has been already previously set |
1055 | and it has the QCompleter as its parent, it is deleted. |
1056 | |
1057 | For convenience, if \a model is a QFileSystemModel, QCompleter switches its |
1058 | caseSensitivity to Qt::CaseInsensitive on Windows and Qt::CaseSensitive |
1059 | on other platforms. |
1060 | |
1061 | \sa completionModel(), modelSorting, {Handling Tree Models} |
1062 | */ |
1063 | void QCompleter::setModel(QAbstractItemModel *model) |
1064 | { |
1065 | Q_D(QCompleter); |
1066 | QAbstractItemModel *oldModel = d->proxy->sourceModel(); |
1067 | if (oldModel == model) |
1068 | return; |
1069 | #if QT_CONFIG(filesystemmodel) |
1070 | if (qobject_cast<const QFileSystemModel *>(object: oldModel)) |
1071 | setCompletionRole(Qt::EditRole); // QTBUG-54642, clear FileNameRole set by QFileSystemModel |
1072 | #endif |
1073 | d->proxy->setSourceModel(model); |
1074 | if (d->popup) |
1075 | setPopup(d->popup); // set the model and make new connections |
1076 | if (oldModel && oldModel->QObject::parent() == this) |
1077 | delete oldModel; |
1078 | #if QT_CONFIG(filesystemmodel) |
1079 | QFileSystemModel *fsModel = qobject_cast<QFileSystemModel *>(object: model); |
1080 | if (fsModel) { |
1081 | #if defined(Q_OS_WIN) |
1082 | setCaseSensitivity(Qt::CaseInsensitive); |
1083 | #else |
1084 | setCaseSensitivity(Qt::CaseSensitive); |
1085 | #endif |
1086 | setCompletionRole(QFileSystemModel::FileNameRole); |
1087 | connect(sender: fsModel, SIGNAL(directoryLoaded(QString)), receiver: this, SLOT(_q_fileSystemModelDirectoryLoaded(QString))); |
1088 | } |
1089 | #endif // QT_CONFIG(filesystemmodel) |
1090 | } |
1091 | |
1092 | /*! |
1093 | Returns the model that provides completion strings. |
1094 | |
1095 | \sa completionModel() |
1096 | */ |
1097 | QAbstractItemModel *QCompleter::model() const |
1098 | { |
1099 | Q_D(const QCompleter); |
1100 | return d->proxy->sourceModel(); |
1101 | } |
1102 | |
1103 | /*! |
1104 | \enum QCompleter::CompletionMode |
1105 | |
1106 | This enum specifies how completions are provided to the user. |
1107 | |
1108 | \value PopupCompletion Current completions are displayed in a popup window. |
1109 | \value InlineCompletion Completions appear inline (as selected text). |
1110 | \value UnfilteredPopupCompletion All possible completions are displayed in a popup window with the most likely suggestion indicated as current. |
1111 | |
1112 | \sa setCompletionMode() |
1113 | */ |
1114 | |
1115 | /*! |
1116 | \property QCompleter::completionMode |
1117 | \brief how the completions are provided to the user |
1118 | |
1119 | The default value is QCompleter::PopupCompletion. |
1120 | */ |
1121 | void QCompleter::setCompletionMode(QCompleter::CompletionMode mode) |
1122 | { |
1123 | Q_D(QCompleter); |
1124 | d->mode = mode; |
1125 | d->proxy->setFiltered(mode != QCompleter::UnfilteredPopupCompletion); |
1126 | |
1127 | if (mode == QCompleter::InlineCompletion) { |
1128 | if (d->widget) |
1129 | d->widget->removeEventFilter(obj: this); |
1130 | if (d->popup) { |
1131 | d->popup->deleteLater(); |
1132 | d->popup = nullptr; |
1133 | } |
1134 | } else { |
1135 | if (d->widget) |
1136 | d->widget->installEventFilter(filterObj: this); |
1137 | } |
1138 | } |
1139 | |
1140 | QCompleter::CompletionMode QCompleter::completionMode() const |
1141 | { |
1142 | Q_D(const QCompleter); |
1143 | return d->mode; |
1144 | } |
1145 | |
1146 | /*! |
1147 | \property QCompleter::filterMode |
1148 | \brief This property controls how filtering is performed. |
1149 | \since 5.2 |
1150 | |
1151 | If filterMode is set to Qt::MatchStartsWith, only those entries that start |
1152 | with the typed characters will be displayed. Qt::MatchContains will display |
1153 | the entries that contain the typed characters, and Qt::MatchEndsWith the |
1154 | ones that end with the typed characters. |
1155 | |
1156 | Setting filterMode to any other Qt::MatchFlag will issue a warning, and no |
1157 | action will be performed. Because of this, the \c Qt::MatchCaseSensitive |
1158 | flag has no effect. Use the \l caseSensitivity property to control case |
1159 | sensitivity. |
1160 | |
1161 | The default mode is Qt::MatchStartsWith. |
1162 | |
1163 | \sa caseSensitivity |
1164 | */ |
1165 | |
1166 | void QCompleter::setFilterMode(Qt::MatchFlags filterMode) |
1167 | { |
1168 | Q_D(QCompleter); |
1169 | |
1170 | if (d->filterMode == filterMode) |
1171 | return; |
1172 | |
1173 | if (Q_UNLIKELY(filterMode != Qt::MatchStartsWith && |
1174 | filterMode != Qt::MatchContains && |
1175 | filterMode != Qt::MatchEndsWith)) { |
1176 | qWarning(msg: "Unhandled QCompleter::filterMode flag is used."); |
1177 | return; |
1178 | } |
1179 | |
1180 | d->filterMode = filterMode; |
1181 | d->proxy->createEngine(); |
1182 | d->proxy->invalidate(); |
1183 | } |
1184 | |
1185 | Qt::MatchFlags QCompleter::filterMode() const |
1186 | { |
1187 | Q_D(const QCompleter); |
1188 | return d->filterMode; |
1189 | } |
1190 | |
1191 | /*! |
1192 | Sets the popup used to display completions to \a popup. QCompleter takes |
1193 | ownership of the view. |
1194 | |
1195 | A QListView is automatically created when the completionMode() is set to |
1196 | QCompleter::PopupCompletion or QCompleter::UnfilteredPopupCompletion. The |
1197 | default popup displays the completionColumn(). |
1198 | |
1199 | Ensure that this function is called before the view settings are modified. |
1200 | This is required since view's properties may require that a model has been |
1201 | set on the view (for example, hiding columns in the view requires a model |
1202 | to be set on the view). |
1203 | |
1204 | \sa popup() |
1205 | */ |
1206 | void QCompleter::setPopup(QAbstractItemView *popup) |
1207 | { |
1208 | Q_ASSERT(popup); |
1209 | Q_D(QCompleter); |
1210 | if (popup == d->popup) |
1211 | return; |
1212 | |
1213 | // Remember existing widget's focus policy, default to NoFocus |
1214 | const Qt::FocusPolicy origPolicy = d->widget ? d->widget->focusPolicy() |
1215 | : Qt::NoFocus; |
1216 | |
1217 | // If popup existed already, disconnect signals and delete object |
1218 | if (d->popup) { |
1219 | QObject::disconnect(sender: d->popup->selectionModel(), signal: nullptr, receiver: this, member: nullptr); |
1220 | QObject::disconnect(sender: d->popup, signal: nullptr, receiver: this, member: nullptr); |
1221 | delete d->popup; |
1222 | } |
1223 | |
1224 | // Assign new object, set model and hide |
1225 | d->popup = popup; |
1226 | if (d->popup->model() != d->proxy) |
1227 | d->popup->setModel(d->proxy); |
1228 | d->popup->hide(); |
1229 | |
1230 | // Mark the widget window as a popup, so that if the last non-popup window is closed by the |
1231 | // user, the application should not be prevented from exiting. It needs to be set explicitly via |
1232 | // setWindowFlag(), because passing the flag via setParent(parent, windowFlags) does not call |
1233 | // QWidgetPrivate::adjustQuitOnCloseAttribute(), and causes an application not to exit if the |
1234 | // popup ends up being the last window. |
1235 | d->popup->setParent(nullptr); |
1236 | d->popup->setWindowFlag(Qt::Popup); |
1237 | d->popup->setFocusPolicy(Qt::NoFocus); |
1238 | if (d->widget) |
1239 | d->widget->setFocusPolicy(origPolicy); |
1240 | |
1241 | d->popup->setFocusProxy(d->widget); |
1242 | d->popup->installEventFilter(filterObj: this); |
1243 | d->popup->setItemDelegate(new QCompleterItemDelegate(d->popup)); |
1244 | #if QT_CONFIG(listview) |
1245 | if (QListView *listView = qobject_cast<QListView *>(object: d->popup)) { |
1246 | listView->setModelColumn(d->column); |
1247 | } |
1248 | #endif |
1249 | |
1250 | QObject::connect(sender: d->popup, SIGNAL(clicked(QModelIndex)), |
1251 | receiver: this, SLOT(_q_complete(QModelIndex))); |
1252 | QObject::connect(sender: this, SIGNAL(activated(QModelIndex)), |
1253 | receiver: d->popup, SLOT(hide())); |
1254 | |
1255 | QObject::connect(sender: d->popup->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), |
1256 | receiver: this, SLOT(_q_completionSelected(QItemSelection))); |
1257 | } |
1258 | |
1259 | /*! |
1260 | Returns the popup used to display completions. |
1261 | |
1262 | \sa setPopup() |
1263 | */ |
1264 | QAbstractItemView *QCompleter::popup() const |
1265 | { |
1266 | Q_D(const QCompleter); |
1267 | #if QT_CONFIG(listview) |
1268 | if (!d->popup && completionMode() != QCompleter::InlineCompletion) { |
1269 | QListView *listView = new QListView; |
1270 | listView->setEditTriggers(QAbstractItemView::NoEditTriggers); |
1271 | listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
1272 | listView->setSelectionBehavior(QAbstractItemView::SelectRows); |
1273 | listView->setSelectionMode(QAbstractItemView::SingleSelection); |
1274 | listView->setModelColumn(d->column); |
1275 | QCompleter *that = const_cast<QCompleter*>(this); |
1276 | that->setPopup(listView); |
1277 | } |
1278 | #endif // QT_CONFIG(listview) |
1279 | return d->popup; |
1280 | } |
1281 | |
1282 | /*! |
1283 | \reimp |
1284 | */ |
1285 | bool QCompleter::event(QEvent *ev) |
1286 | { |
1287 | return QObject::event(event: ev); |
1288 | } |
1289 | |
1290 | /*! |
1291 | \reimp |
1292 | */ |
1293 | bool QCompleter::eventFilter(QObject *o, QEvent *e) |
1294 | { |
1295 | Q_D(QCompleter); |
1296 | |
1297 | if (o == d->widget) { |
1298 | switch (e->type()) { |
1299 | case QEvent::FocusOut: |
1300 | if (d->eatFocusOut) { |
1301 | d->hiddenBecauseNoMatch = false; |
1302 | if (d->popup && d->popup->isVisible()) |
1303 | return true; |
1304 | } |
1305 | break; |
1306 | case QEvent::Hide: |
1307 | if (d->popup) |
1308 | d->popup->hide(); |
1309 | break; |
1310 | default: |
1311 | break; |
1312 | } |
1313 | } |
1314 | |
1315 | if (o != d->popup) |
1316 | return QObject::eventFilter(watched: o, event: e); |
1317 | |
1318 | Q_ASSERT(d->popup); |
1319 | switch (e->type()) { |
1320 | case QEvent::KeyPress: { |
1321 | QKeyEvent *ke = static_cast<QKeyEvent *>(e); |
1322 | |
1323 | QModelIndex curIndex = d->popup->currentIndex(); |
1324 | QModelIndexList selList = d->popup->selectionModel()->selectedIndexes(); |
1325 | |
1326 | const int key = ke->key(); |
1327 | // In UnFilteredPopup mode, select the current item |
1328 | if ((key == Qt::Key_Up || key == Qt::Key_Down) && selList.isEmpty() && curIndex.isValid() |
1329 | && d->mode == QCompleter::UnfilteredPopupCompletion) { |
1330 | d->setCurrentIndex(index: curIndex); |
1331 | return true; |
1332 | } |
1333 | |
1334 | // Handle popup navigation keys. These are hardcoded because up/down might make the |
1335 | // widget do something else (lineedit cursor moves to home/end on mac, for instance) |
1336 | switch (key) { |
1337 | case Qt::Key_End: |
1338 | case Qt::Key_Home: |
1339 | if (ke->modifiers() & Qt::ControlModifier) |
1340 | return false; |
1341 | break; |
1342 | |
1343 | case Qt::Key_Up: |
1344 | if (!curIndex.isValid()) { |
1345 | int rowCount = d->proxy->rowCount(); |
1346 | QModelIndex lastIndex = d->proxy->index(row: rowCount - 1, column: d->column); |
1347 | d->setCurrentIndex(index: lastIndex); |
1348 | return true; |
1349 | } else if (curIndex.row() == 0) { |
1350 | if (d->wrap) |
1351 | d->setCurrentIndex(index: QModelIndex()); |
1352 | return true; |
1353 | } |
1354 | return false; |
1355 | |
1356 | case Qt::Key_Down: |
1357 | if (!curIndex.isValid()) { |
1358 | QModelIndex firstIndex = d->proxy->index(row: 0, column: d->column); |
1359 | d->setCurrentIndex(index: firstIndex); |
1360 | return true; |
1361 | } else if (curIndex.row() == d->proxy->rowCount() - 1) { |
1362 | if (d->wrap) |
1363 | d->setCurrentIndex(index: QModelIndex()); |
1364 | return true; |
1365 | } |
1366 | return false; |
1367 | |
1368 | case Qt::Key_PageUp: |
1369 | case Qt::Key_PageDown: |
1370 | return false; |
1371 | } |
1372 | |
1373 | if (d->widget) { |
1374 | // Send the event to the widget. If the widget accepted the event, do nothing |
1375 | // If the widget did not accept the event, provide a default implementation |
1376 | d->eatFocusOut = false; |
1377 | (static_cast<QObject *>(d->widget))->event(event: ke); |
1378 | d->eatFocusOut = true; |
1379 | } |
1380 | if (!d->widget || e->isAccepted() || !d->popup->isVisible()) { |
1381 | // widget lost focus, hide the popup |
1382 | if (d->widget && (!d->widget->hasFocus() |
1383 | #ifdef QT_KEYPAD_NAVIGATION |
1384 | || (QApplicationPrivate::keypadNavigationEnabled() && !d->widget->hasEditFocus()) |
1385 | #endif |
1386 | )) |
1387 | d->popup->hide(); |
1388 | if (e->isAccepted()) |
1389 | return true; |
1390 | } |
1391 | |
1392 | // default implementation for keys not handled by the widget when popup is open |
1393 | #if QT_CONFIG(shortcut) |
1394 | if (ke->matches(key: QKeySequence::Cancel)) { |
1395 | d->popup->hide(); |
1396 | return true; |
1397 | } |
1398 | #endif |
1399 | switch (key) { |
1400 | #ifdef QT_KEYPAD_NAVIGATION |
1401 | case Qt::Key_Select: |
1402 | if (!QApplicationPrivate::keypadNavigationEnabled()) |
1403 | break; |
1404 | #endif |
1405 | case Qt::Key_Return: |
1406 | case Qt::Key_Enter: |
1407 | case Qt::Key_Tab: |
1408 | d->popup->hide(); |
1409 | if (curIndex.isValid()) |
1410 | d->_q_complete(index: curIndex); |
1411 | break; |
1412 | |
1413 | case Qt::Key_F4: |
1414 | if (ke->modifiers() & Qt::AltModifier) |
1415 | d->popup->hide(); |
1416 | break; |
1417 | |
1418 | case Qt::Key_Backtab: |
1419 | d->popup->hide(); |
1420 | break; |
1421 | |
1422 | default: |
1423 | break; |
1424 | } |
1425 | |
1426 | return true; |
1427 | } |
1428 | |
1429 | #ifdef QT_KEYPAD_NAVIGATION |
1430 | case QEvent::KeyRelease: { |
1431 | if (d->widget && |
1432 | QApplicationPrivate::keypadNavigationEnabled() && ke->key() == Qt::Key_Back) { |
1433 | QKeyEvent *ke = static_cast<QKeyEvent *>(e); |
1434 | // Send the event to the 'widget'. This is what we did for KeyPress, so we need |
1435 | // to do the same for KeyRelease, in case the widget's KeyPress event set |
1436 | // up something (such as a timer) that is relying on also receiving the |
1437 | // key release. I see this as a bug in Qt, and should really set it up for all |
1438 | // the affected keys. However, it is difficult to tell how this will affect |
1439 | // existing code, and I can't test for every combination! |
1440 | d->eatFocusOut = false; |
1441 | static_cast<QObject *>(d->widget)->event(ke); |
1442 | d->eatFocusOut = true; |
1443 | } |
1444 | break; |
1445 | } |
1446 | #endif |
1447 | |
1448 | case QEvent::MouseButtonPress: { |
1449 | #ifdef QT_KEYPAD_NAVIGATION |
1450 | if (d->widget |
1451 | && QApplicationPrivate::keypadNavigationEnabled()) { |
1452 | // if we've clicked in the widget (or its descendant), let it handle the click |
1453 | QWidget *source = qobject_cast<QWidget *>(o); |
1454 | if (source) { |
1455 | QPoint pos = source->mapToGlobal((static_cast<QMouseEvent *>(e))->pos()); |
1456 | QWidget *target = QApplication::widgetAt(pos); |
1457 | if (target && (d->widget->isAncestorOf(target) || |
1458 | target == d->widget)) { |
1459 | d->eatFocusOut = false; |
1460 | static_cast<QObject *>(target)->event(e); |
1461 | d->eatFocusOut = true; |
1462 | return true; |
1463 | } |
1464 | } |
1465 | } |
1466 | #endif |
1467 | if (!d->popup->underMouse()) { |
1468 | if (!QGuiApplicationPrivate::maybeForwardEventToVirtualKeyboard(e)) |
1469 | d->popup->hide(); |
1470 | return true; |
1471 | } |
1472 | } |
1473 | return false; |
1474 | |
1475 | case QEvent::MouseButtonRelease: |
1476 | QGuiApplicationPrivate::maybeForwardEventToVirtualKeyboard(e); |
1477 | return true; |
1478 | case QEvent::InputMethod: |
1479 | case QEvent::ShortcutOverride: |
1480 | if (d->widget) |
1481 | QCoreApplication::sendEvent(receiver: d->widget, event: e); |
1482 | break; |
1483 | |
1484 | default: |
1485 | return false; |
1486 | } |
1487 | return false; |
1488 | } |
1489 | |
1490 | /*! |
1491 | For QCompleter::PopupCompletion and QCompletion::UnfilteredPopupCompletion |
1492 | modes, calling this function displays the popup displaying the current |
1493 | completions. By default, if \a rect is not specified, the popup is displayed |
1494 | on the bottom of the widget(). If \a rect is specified the popup is |
1495 | displayed on the left edge of the rectangle. |
1496 | |
1497 | For QCompleter::InlineCompletion mode, the highlighted() signal is fired |
1498 | with the current completion. |
1499 | */ |
1500 | void QCompleter::complete(const QRect& rect) |
1501 | { |
1502 | Q_D(QCompleter); |
1503 | QModelIndex idx = d->proxy->currentIndex(sourceIndex: false); |
1504 | d->hiddenBecauseNoMatch = false; |
1505 | if (d->mode == QCompleter::InlineCompletion) { |
1506 | if (idx.isValid()) |
1507 | d->_q_complete(index: idx, highlighted: true); |
1508 | return; |
1509 | } |
1510 | |
1511 | Q_ASSERT(d->widget); |
1512 | if ((d->mode == QCompleter::PopupCompletion && !idx.isValid()) |
1513 | || (d->mode == QCompleter::UnfilteredPopupCompletion && d->proxy->rowCount() == 0)) { |
1514 | if (d->popup) |
1515 | d->popup->hide(); // no suggestion, hide |
1516 | d->hiddenBecauseNoMatch = true; |
1517 | return; |
1518 | } |
1519 | |
1520 | popup(); |
1521 | if (d->mode == QCompleter::UnfilteredPopupCompletion) |
1522 | d->setCurrentIndex(index: idx, select: false); |
1523 | |
1524 | d->showPopup(rect); |
1525 | d->popupRect = rect; |
1526 | } |
1527 | |
1528 | /*! |
1529 | Sets the current row to the \a row specified. Returns \c true if successful; |
1530 | otherwise returns \c false. |
1531 | |
1532 | This function may be used along with currentCompletion() to iterate |
1533 | through all the possible completions. |
1534 | |
1535 | \sa currentCompletion(), completionCount() |
1536 | */ |
1537 | bool QCompleter::setCurrentRow(int row) |
1538 | { |
1539 | Q_D(QCompleter); |
1540 | return d->proxy->setCurrentRow(row); |
1541 | } |
1542 | |
1543 | /*! |
1544 | Returns the current row. |
1545 | |
1546 | \sa setCurrentRow() |
1547 | */ |
1548 | int QCompleter::currentRow() const |
1549 | { |
1550 | Q_D(const QCompleter); |
1551 | return d->proxy->currentRow(); |
1552 | } |
1553 | |
1554 | /*! |
1555 | Returns the number of completions for the current prefix. For an unsorted |
1556 | model with a large number of items this can be expensive. Use setCurrentRow() |
1557 | and currentCompletion() to iterate through all the completions. |
1558 | */ |
1559 | int QCompleter::completionCount() const |
1560 | { |
1561 | Q_D(const QCompleter); |
1562 | return d->proxy->completionCount(); |
1563 | } |
1564 | |
1565 | /*! |
1566 | \enum QCompleter::ModelSorting |
1567 | |
1568 | This enum specifies how the items in the model are sorted. |
1569 | |
1570 | \value UnsortedModel The model is unsorted. |
1571 | \value CaseSensitivelySortedModel The model is sorted case sensitively. |
1572 | \value CaseInsensitivelySortedModel The model is sorted case insensitively. |
1573 | |
1574 | \sa setModelSorting() |
1575 | */ |
1576 | |
1577 | /*! |
1578 | \property QCompleter::modelSorting |
1579 | \brief the way the model is sorted |
1580 | |
1581 | By default, no assumptions are made about the order of the items |
1582 | in the model that provides the completions. |
1583 | |
1584 | If the model's data for the completionColumn() and completionRole() is sorted in |
1585 | ascending order, you can set this property to \l CaseSensitivelySortedModel |
1586 | or \l CaseInsensitivelySortedModel. On large models, this can lead to |
1587 | significant performance improvements because the completer object can |
1588 | then use a binary search algorithm instead of linear search algorithm. |
1589 | |
1590 | The sort order (i.e ascending or descending order) of the model is determined |
1591 | dynamically by inspecting the contents of the model. |
1592 | |
1593 | \b{Note:} The performance improvements described above cannot take place |
1594 | when the completer's \l caseSensitivity is different to the case sensitivity |
1595 | used by the model's when sorting. |
1596 | |
1597 | \sa setCaseSensitivity(), QCompleter::ModelSorting |
1598 | */ |
1599 | void QCompleter::setModelSorting(QCompleter::ModelSorting sorting) |
1600 | { |
1601 | Q_D(QCompleter); |
1602 | if (d->sorting == sorting) |
1603 | return; |
1604 | d->sorting = sorting; |
1605 | d->proxy->createEngine(); |
1606 | d->proxy->invalidate(); |
1607 | } |
1608 | |
1609 | QCompleter::ModelSorting QCompleter::modelSorting() const |
1610 | { |
1611 | Q_D(const QCompleter); |
1612 | return d->sorting; |
1613 | } |
1614 | |
1615 | /*! |
1616 | \property QCompleter::completionColumn |
1617 | \brief the column in the model in which completions are searched for. |
1618 | |
1619 | If the popup() is a QListView, it is automatically setup to display |
1620 | this column. |
1621 | |
1622 | By default, the match column is 0. |
1623 | |
1624 | \sa completionRole, caseSensitivity |
1625 | */ |
1626 | void QCompleter::setCompletionColumn(int column) |
1627 | { |
1628 | Q_D(QCompleter); |
1629 | if (d->column == column) |
1630 | return; |
1631 | #if QT_CONFIG(listview) |
1632 | if (QListView *listView = qobject_cast<QListView *>(object: d->popup)) |
1633 | listView->setModelColumn(column); |
1634 | #endif |
1635 | d->column = column; |
1636 | d->proxy->invalidate(); |
1637 | } |
1638 | |
1639 | int QCompleter::completionColumn() const |
1640 | { |
1641 | Q_D(const QCompleter); |
1642 | return d->column; |
1643 | } |
1644 | |
1645 | /*! |
1646 | \property QCompleter::completionRole |
1647 | \brief the item role to be used to query the contents of items for matching. |
1648 | |
1649 | The default role is Qt::EditRole. |
1650 | |
1651 | \sa completionColumn, caseSensitivity |
1652 | */ |
1653 | void QCompleter::setCompletionRole(int role) |
1654 | { |
1655 | Q_D(QCompleter); |
1656 | if (d->role == role) |
1657 | return; |
1658 | d->role = role; |
1659 | d->proxy->invalidate(); |
1660 | } |
1661 | |
1662 | int QCompleter::completionRole() const |
1663 | { |
1664 | Q_D(const QCompleter); |
1665 | return d->role; |
1666 | } |
1667 | |
1668 | /*! |
1669 | \property QCompleter::wrapAround |
1670 | \brief the completions wrap around when navigating through items |
1671 | \since 4.3 |
1672 | |
1673 | The default is true. |
1674 | */ |
1675 | void QCompleter::setWrapAround(bool wrap) |
1676 | { |
1677 | Q_D(QCompleter); |
1678 | if (d->wrap == wrap) |
1679 | return; |
1680 | d->wrap = wrap; |
1681 | } |
1682 | |
1683 | bool QCompleter::wrapAround() const |
1684 | { |
1685 | Q_D(const QCompleter); |
1686 | return d->wrap; |
1687 | } |
1688 | |
1689 | /*! |
1690 | \property QCompleter::maxVisibleItems |
1691 | \brief the maximum allowed size on screen of the completer, measured in items |
1692 | \since 4.6 |
1693 | |
1694 | By default, this property has a value of 7. |
1695 | */ |
1696 | int QCompleter::maxVisibleItems() const |
1697 | { |
1698 | Q_D(const QCompleter); |
1699 | return d->maxVisibleItems; |
1700 | } |
1701 | |
1702 | void QCompleter::setMaxVisibleItems(int maxItems) |
1703 | { |
1704 | Q_D(QCompleter); |
1705 | if (Q_UNLIKELY(maxItems < 0)) { |
1706 | qWarning(msg: "QCompleter::setMaxVisibleItems: " |
1707 | "Invalid max visible items (%d) must be >= 0", maxItems); |
1708 | return; |
1709 | } |
1710 | d->maxVisibleItems = maxItems; |
1711 | } |
1712 | |
1713 | /*! |
1714 | \property QCompleter::caseSensitivity |
1715 | \brief the case sensitivity of the matching |
1716 | |
1717 | The default value is \c Qt::CaseSensitive. |
1718 | |
1719 | \sa completionColumn, completionRole, modelSorting, filterMode |
1720 | */ |
1721 | void QCompleter::setCaseSensitivity(Qt::CaseSensitivity cs) |
1722 | { |
1723 | Q_D(QCompleter); |
1724 | if (d->cs == cs) |
1725 | return; |
1726 | d->cs = cs; |
1727 | d->proxy->createEngine(); |
1728 | d->proxy->invalidate(); |
1729 | } |
1730 | |
1731 | Qt::CaseSensitivity QCompleter::caseSensitivity() const |
1732 | { |
1733 | Q_D(const QCompleter); |
1734 | return d->cs; |
1735 | } |
1736 | |
1737 | /*! |
1738 | \property QCompleter::completionPrefix |
1739 | \brief the completion prefix used to provide completions. |
1740 | |
1741 | The completionModel() is updated to reflect the list of possible |
1742 | matches for \a prefix. |
1743 | */ |
1744 | void QCompleter::setCompletionPrefix(const QString &prefix) |
1745 | { |
1746 | Q_D(QCompleter); |
1747 | d->prefix = prefix; |
1748 | d->proxy->filter(parts: splitPath(path: prefix)); |
1749 | } |
1750 | |
1751 | QString QCompleter::completionPrefix() const |
1752 | { |
1753 | Q_D(const QCompleter); |
1754 | return d->prefix; |
1755 | } |
1756 | |
1757 | /*! |
1758 | Returns the model index of the current completion in the completionModel(). |
1759 | |
1760 | \sa setCurrentRow(), currentCompletion(), model() |
1761 | */ |
1762 | QModelIndex QCompleter::currentIndex() const |
1763 | { |
1764 | Q_D(const QCompleter); |
1765 | return d->proxy->currentIndex(sourceIndex: false); |
1766 | } |
1767 | |
1768 | /*! |
1769 | Returns the current completion string. This includes the \l completionPrefix. |
1770 | When used alongside setCurrentRow(), it can be used to iterate through |
1771 | all the matches. |
1772 | |
1773 | \sa setCurrentRow(), currentIndex() |
1774 | */ |
1775 | QString QCompleter::currentCompletion() const |
1776 | { |
1777 | Q_D(const QCompleter); |
1778 | return pathFromIndex(index: d->proxy->currentIndex(sourceIndex: true)); |
1779 | } |
1780 | |
1781 | /*! |
1782 | Returns the completion model. The completion model is a read-only list model |
1783 | that contains all the possible matches for the current completion prefix. |
1784 | The completion model is auto-updated to reflect the current completions. |
1785 | |
1786 | \note The return value of this function is defined to be an QAbstractItemModel |
1787 | purely for generality. This actual kind of model returned is an instance of an |
1788 | QAbstractProxyModel subclass. |
1789 | |
1790 | \sa completionPrefix, model() |
1791 | */ |
1792 | QAbstractItemModel *QCompleter::completionModel() const |
1793 | { |
1794 | Q_D(const QCompleter); |
1795 | return d->proxy; |
1796 | } |
1797 | |
1798 | /*! |
1799 | Returns the path for the given \a index. The completer object uses this to |
1800 | obtain the completion text from the underlying model. |
1801 | |
1802 | The default implementation returns the \l{Qt::EditRole}{edit role} of the |
1803 | item for list models. It returns the absolute file path if the model is a |
1804 | QFileSystemModel. |
1805 | |
1806 | \sa splitPath() |
1807 | */ |
1808 | |
1809 | QString QCompleter::pathFromIndex(const QModelIndex& index) const |
1810 | { |
1811 | Q_D(const QCompleter); |
1812 | if (!index.isValid()) |
1813 | return QString(); |
1814 | |
1815 | QAbstractItemModel *sourceModel = d->proxy->sourceModel(); |
1816 | if (!sourceModel) |
1817 | return QString(); |
1818 | bool isFsModel = false; |
1819 | #if QT_CONFIG(filesystemmodel) |
1820 | isFsModel = qobject_cast<QFileSystemModel *>(object: d->proxy->sourceModel()) != nullptr; |
1821 | #endif |
1822 | if (!isFsModel) |
1823 | return sourceModel->data(index, role: d->role).toString(); |
1824 | |
1825 | QModelIndex idx = index; |
1826 | QStringList list; |
1827 | do { |
1828 | QString t; |
1829 | #if QT_CONFIG(filesystemmodel) |
1830 | t = sourceModel->data(index: idx, role: QFileSystemModel::FileNameRole).toString(); |
1831 | #endif |
1832 | list.prepend(t); |
1833 | QModelIndex parent = idx.parent(); |
1834 | idx = parent.sibling(arow: parent.row(), acolumn: index.column()); |
1835 | } while (idx.isValid()); |
1836 | |
1837 | #if !defined(Q_OS_WIN) |
1838 | if (list.size() == 1) // only the separator or some other text |
1839 | return list[0]; |
1840 | list[0].clear() ; // the join below will provide the separator |
1841 | #endif |
1842 | |
1843 | return list.join(sep: QDir::separator()); |
1844 | } |
1845 | |
1846 | /*! |
1847 | Splits the given \a path into strings that are used to match at each level |
1848 | in the model(). |
1849 | |
1850 | The default implementation of splitPath() splits a file system path based on |
1851 | QDir::separator() when the sourceModel() is a QFileSystemModel. |
1852 | |
1853 | When used with list models, the first item in the returned list is used for |
1854 | matching. |
1855 | |
1856 | \sa pathFromIndex(), {Handling Tree Models} |
1857 | */ |
1858 | QStringList QCompleter::splitPath(const QString& path) const |
1859 | { |
1860 | bool isFsModel = false; |
1861 | #if QT_CONFIG(filesystemmodel) |
1862 | Q_D(const QCompleter); |
1863 | isFsModel = qobject_cast<QFileSystemModel *>(object: d->proxy->sourceModel()) != nullptr; |
1864 | #endif |
1865 | |
1866 | if (!isFsModel || path.isEmpty()) |
1867 | return QStringList(completionPrefix()); |
1868 | |
1869 | QString pathCopy = QDir::toNativeSeparators(pathName: path); |
1870 | #if defined(Q_OS_WIN) |
1871 | if (pathCopy == "\\"_L1|| pathCopy == "\\\\"_L1) |
1872 | return QStringList(pathCopy); |
1873 | const bool startsWithDoubleSlash = pathCopy.startsWith("\\\\"_L1); |
1874 | if (startsWithDoubleSlash) |
1875 | pathCopy = pathCopy.mid(2); |
1876 | #endif |
1877 | |
1878 | const QChar sep = QDir::separator(); |
1879 | QStringList parts = pathCopy.split(sep); |
1880 | |
1881 | #if defined(Q_OS_WIN) |
1882 | if (startsWithDoubleSlash) |
1883 | parts[0].prepend("\\\\"_L1); |
1884 | #else |
1885 | if (pathCopy[0] == sep) // readd the "/" at the beginning as the split removed it |
1886 | parts[0] = u'/'; |
1887 | #endif |
1888 | |
1889 | return parts; |
1890 | } |
1891 | |
1892 | /*! |
1893 | \fn void QCompleter::activated(const QModelIndex& index) |
1894 | |
1895 | This signal is sent when an item in the popup() is activated by the user. |
1896 | (by clicking or pressing return). The item's \a index in the completionModel() |
1897 | is given. |
1898 | |
1899 | */ |
1900 | |
1901 | /*! |
1902 | \fn void QCompleter::activated(const QString &text) |
1903 | |
1904 | This signal is sent when an item in the popup() is activated by the user (by |
1905 | clicking or pressing return). The item's \a text is given. |
1906 | |
1907 | */ |
1908 | |
1909 | /*! |
1910 | \fn void QCompleter::highlighted(const QModelIndex& index) |
1911 | |
1912 | This signal is sent when an item in the popup() is highlighted by |
1913 | the user. It is also sent if complete() is called with the completionMode() |
1914 | set to QCompleter::InlineCompletion. The item's \a index in the completionModel() |
1915 | is given. |
1916 | */ |
1917 | |
1918 | /*! |
1919 | \fn void QCompleter::highlighted(const QString &text) |
1920 | |
1921 | This signal is sent when an item in the popup() is highlighted by |
1922 | the user. It is also sent if complete() is called with the completionMode() |
1923 | set to QCompleter::InlineCompletion. The item's \a text is given. |
1924 | */ |
1925 | |
1926 | QT_END_NAMESPACE |
1927 | |
1928 | #include "moc_qcompleter.cpp" |
1929 | |
1930 | #include "moc_qcompleter_p.cpp" |
1931 |
Definitions
- QCompletionModel
- columnCount
- setSourceModel
- createEngine
- mapToSource
- mapFromSource
- setCurrentRow
- currentIndex
- index
- completionCount
- rowCount
- setFiltered
- hasChildren
- data
- modelDestroyed
- rowsInserted
- invalidate
- filter
- filter
- filterHistory
- matchHint
- lookupCache
- saveInCache
- indexHint
- sortOrder
- filter
- buildIndices
- filterOnDemand
- filter
- QCompleterPrivate
- init
- setCurrentIndex
- _q_completionSelected
- _q_complete
- _q_autoResizePopup
- showPopup
- isRoot
- completeOnLoaded
- _q_fileSystemModelDirectoryLoaded
- QCompleter
- QCompleter
- QCompleter
- ~QCompleter
- setWidget
- widget
- setModel
- model
- setCompletionMode
- completionMode
- setFilterMode
- filterMode
- setPopup
- popup
- event
- eventFilter
- complete
- setCurrentRow
- currentRow
- completionCount
- setModelSorting
- modelSorting
- setCompletionColumn
- completionColumn
- setCompletionRole
- completionRole
- setWrapAround
- wrapAround
- maxVisibleItems
- setMaxVisibleItems
- setCaseSensitivity
- caseSensitivity
- setCompletionPrefix
- completionPrefix
- currentIndex
- currentCompletion
- completionModel
- pathFromIndex
Start learning QML with our Intro Training
Find out more