Plasma-workspace

taskgroupingproxymodel.cpp
1/*
2 SPDX-FileCopyrightText: 2016 Eike Hein <hein@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include "taskgroupingproxymodel.h"
8#include "abstracttasksmodel.h"
9#include "tasktools.h"
10
11#include <QDateTime>
12#include <QSet>
13
14namespace TaskManager
15{
16class Q_DECL_HIDDEN TaskGroupingProxyModel::Private
17{
18public:
19 Private(TaskGroupingProxyModel *q);
20 ~Private();
21
22 AbstractTasksModelIface *abstractTasksSourceModel = nullptr;
23
24 TasksModel::GroupMode groupMode = TasksModel::GroupApplications;
25 bool groupDemandingAttention = false;
26 int windowTasksThreshold = -1;
27
28 QList<QList<int> *> rowMap;
29
30 QSet<QString> blacklistedAppIds;
31 QSet<QString> blacklistedLauncherUrls;
32
33 bool isGroup(int row);
34 bool any(const QModelIndex &parent, int role);
35 bool all(const QModelIndex &parent, int role);
36
37 void sourceRowsAboutToBeInserted(const QModelIndex &parent, int first, int last);
38 void sourceRowsInserted(const QModelIndex &parent, int start, int end);
39 void sourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
40 void sourceRowsRemoved(const QModelIndex &parent, int start, int end);
41 void sourceModelAboutToBeReset();
42 void sourceModelReset();
43 void sourceDataChanged(QModelIndex topLeft, QModelIndex bottomRight, const QList<int> &roles = QList<int>());
44 void adjustMap(int anchor, int delta);
45
46 void rebuildMap();
47 bool shouldGroupTasks();
48 void checkGrouping(bool silent = false);
49 bool isBlacklisted(const QModelIndex &sourceIndex);
50 bool tryToGroup(const QModelIndex &sourceIndex, bool silent = false);
51 void formGroupFor(const QModelIndex &index);
52 void breakGroupFor(const QModelIndex &index, bool silent = false);
53
54private:
55 TaskGroupingProxyModel *const q;
56};
57
58TaskGroupingProxyModel::Private::Private(TaskGroupingProxyModel *q)
59 : q(q)
60{
61}
62
63TaskGroupingProxyModel::Private::~Private()
64{
65 qDeleteAll(rowMap);
66}
67
68bool TaskGroupingProxyModel::Private::isGroup(int row)
69{
70 if (row < 0 || row >= rowMap.count()) {
71 return false;
72 }
73
74 return (rowMap.at(row)->count() > 1);
75}
76
77bool TaskGroupingProxyModel::Private::any(const QModelIndex &parent, int role)
78{
79 bool is = false;
80
81 for (int i = 0; i < q->rowCount(parent); ++i) {
82 if (q->index(i, 0, parent).data(role).toBool()) {
83 return true;
84 }
85 }
86
87 return is;
88}
89
90bool TaskGroupingProxyModel::Private::all(const QModelIndex &parent, int role)
91{
92 bool is = true;
93
94 for (int i = 0; i < q->rowCount(parent); ++i) {
95 if (!q->index(i, 0, parent).data(role).toBool()) {
96 return false;
97 }
98 }
99
100 return is;
101}
102
103void TaskGroupingProxyModel::Private::sourceRowsAboutToBeInserted(const QModelIndex &parent, int first, int last)
104{
105 Q_UNUSED(parent)
106 Q_UNUSED(first)
107 Q_UNUSED(last)
108}
109
110void TaskGroupingProxyModel::Private::sourceRowsInserted(const QModelIndex &parent, int start, int end)
111{
112 // We only support flat source models.
113 if (parent.isValid()) {
114 return;
115 }
116
117 adjustMap(start, (end - start) + 1);
118
119 bool shouldGroup = shouldGroupTasks(); // Can be slightly expensive; cache return value.
120
121 for (int i = start; i <= end; ++i) {
122 if (!shouldGroup || !tryToGroup(q->sourceModel()->index(i, 0))) {
123 q->beginInsertRows(QModelIndex(), rowMap.count(), rowMap.count());
124 rowMap.append(new QList<int>{i});
125 q->endInsertRows();
126 }
127 }
128
129 checkGrouping();
130}
131
132void TaskGroupingProxyModel::Private::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
133{
134 // We only support flat source models.
135 if (parent.isValid()) {
136 return;
137 }
138
139 for (int i = first; i <= last; ++i) {
140 for (int j = 0; j < rowMap.count(); ++j) {
141 const QList<int> *sourceRows = rowMap.at(j);
142 const int mapIndex = sourceRows->indexOf(i);
143
144 if (mapIndex != -1) {
145 // Remove top-level item.
146 if (sourceRows->count() == 1) {
147 q->beginRemoveRows(QModelIndex(), j, j);
148 delete rowMap.takeAt(j);
149 q->endRemoveRows();
150 // Dissolve group.
151 } else if (sourceRows->count() == 2) {
152 const QModelIndex parent = q->index(j, 0);
153 q->beginRemoveRows(parent, 0, 1);
154 rowMap[j]->remove(mapIndex);
155 q->endRemoveRows();
156
157 // We're no longer a group parent.
158 Q_EMIT q->dataChanged(parent, parent);
159 // Remove group member.
160 } else {
161 const QModelIndex parent = q->index(j, 0);
162 q->beginRemoveRows(parent, mapIndex, mapIndex);
163 rowMap[j]->remove(mapIndex);
164 q->endRemoveRows();
165
166 // Various roles of the parent evaluate child data, and the
167 // child list has changed.
168 Q_EMIT q->dataChanged(parent, parent);
169 }
170
171 break;
172 }
173 }
174 }
175}
176
177void TaskGroupingProxyModel::Private::sourceRowsRemoved(const QModelIndex &parent, int start, int end)
178{
179 // We only support flat source models.
180 if (parent.isValid()) {
181 return;
182 }
183
184 adjustMap(start + 1, -((end - start) + 1));
185
186 checkGrouping();
187}
188
189void TaskGroupingProxyModel::Private::sourceModelAboutToBeReset()
190{
191 q->beginResetModel();
192}
193
194void TaskGroupingProxyModel::Private::sourceModelReset()
195{
196 rebuildMap();
197
198 q->endResetModel();
199}
200
201void TaskGroupingProxyModel::Private::sourceDataChanged(QModelIndex topLeft, QModelIndex bottomRight, const QList<int> &roles)
202{
203 for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
204 const QModelIndex &sourceIndex = q->sourceModel()->index(i, 0);
205 QModelIndex proxyIndex = q->mapFromSource(sourceIndex);
206
207 if (!proxyIndex.isValid()) {
208 return;
209 }
210
211 const QModelIndex parent = proxyIndex.parent();
212
213 // If a child item changes, its parent may need an update as well as many of
214 // the data roles evaluate child data. See data().
215 // TODO: Some roles do not need to bubble up as they fall through to the first
216 // child in data(); it _might_ be worth adding constraints here later.
217 if (parent.isValid()) {
218 Q_EMIT q->dataChanged(parent, parent, roles);
219 }
220
221 // When Private::groupDemandingAttention is false, tryToGroup() exempts tasks
222 // which demand attention from being grouped. Therefore if this task is no longer
223 // demanding attention, we need to try grouping it now.
224 if (!parent.isValid() && !groupDemandingAttention && roles.contains(AbstractTasksModel::IsDemandingAttention)
225 && !sourceIndex.data(AbstractTasksModel::IsDemandingAttention).toBool()) {
226 if (shouldGroupTasks() && tryToGroup(sourceIndex)) {
227 q->beginRemoveRows(QModelIndex(), proxyIndex.row(), proxyIndex.row());
228 delete rowMap.takeAt(proxyIndex.row());
229 q->endRemoveRows();
230 } else {
231 Q_EMIT q->dataChanged(proxyIndex, proxyIndex, roles);
232 }
233 } else {
234 Q_EMIT q->dataChanged(proxyIndex, proxyIndex, roles);
235 }
236 }
237}
238
239void TaskGroupingProxyModel::Private::adjustMap(int anchor, int delta)
240{
241 for (int i = 0; i < rowMap.count(); ++i) {
242 QList<int> *sourceRows = rowMap.at(i);
243 for (auto it = sourceRows->begin(); it != sourceRows->end(); ++it) {
244 if ((*it) >= anchor) {
245 *it += delta;
246 }
247 }
248 }
249}
250
251void TaskGroupingProxyModel::Private::rebuildMap()
252{
253 qDeleteAll(rowMap);
254 rowMap.clear();
255
256 const int rows = q->sourceModel()->rowCount();
257
258 rowMap.reserve(rows);
259
260 for (int i = 0; i < rows; ++i) {
261 rowMap.append(new QList<int>{i});
262 }
263
264 checkGrouping(true /* silent */);
265}
266
267bool TaskGroupingProxyModel::Private::shouldGroupTasks()
268{
269 if (groupMode == TasksModel::GroupDisabled) {
270 return false;
271 }
272
273 if (windowTasksThreshold != -1) {
274 // We're going to check the number of window tasks in the source model
275 // against the grouping threshold. In practice that means we're ignoring
276 // launcher and startup tasks. Startup tasks because they're very short-
277 // lived (i.e. forming/breaking groups as they come and go would be very
278 // noisy) and launcher tasks because we expect consumers to budget for
279 // them in the threshold they set.
280 int windowTasksCount = 0;
281
282 for (int i = 0; i < q->sourceModel()->rowCount(); ++i) {
283 const QModelIndex &idx = q->sourceModel()->index(i, 0);
284
285 if (idx.data(AbstractTasksModel::IsWindow).toBool()) {
286 ++windowTasksCount;
287 }
288 }
289
290 return (windowTasksCount > windowTasksThreshold);
291 }
292
293 return true;
294}
295
296void TaskGroupingProxyModel::Private::checkGrouping(bool silent)
297{
298 if (shouldGroupTasks()) {
299 for (int i = (rowMap.count()) - 1; i >= 0; --i) {
300 if (isGroup(i)) {
301 continue;
302 }
303
304 if (tryToGroup(q->sourceModel()->index(rowMap.at(i)->constFirst(), 0), silent)) {
305 q->beginRemoveRows(QModelIndex(), i, i);
306 delete rowMap.takeAt(i); // Safe since we're iterating backwards.
307 q->endRemoveRows();
308 }
309 }
310 } else {
311 for (int i = (rowMap.count()) - 1; i >= 0; --i) {
312 breakGroupFor(q->index(i, 0), silent);
313 }
314 }
315}
316
317bool TaskGroupingProxyModel::Private::isBlacklisted(const QModelIndex &sourceIndex)
318{
319 // Check app id against blacklist.
320 if (blacklistedAppIds.count() && blacklistedAppIds.contains(sourceIndex.data(AbstractTasksModel::AppId).toString())) {
321 return true;
322 }
323
324 // Check launcher URL (sans query items) against blacklist.
325 if (blacklistedLauncherUrls.count()) {
326 const QUrl &launcherUrl = sourceIndex.data(AbstractTasksModel::LauncherUrlWithoutIcon).toUrl();
327 const QString &launcherUrlString = launcherUrl.toString(QUrl::PrettyDecoded | QUrl::RemoveQuery);
328
329 if (blacklistedLauncherUrls.contains(launcherUrlString)) {
330 return true;
331 }
332 }
333
334 return false;
335}
336
337bool TaskGroupingProxyModel::Private::tryToGroup(const QModelIndex &sourceIndex, bool silent)
338{
339 // NOTE: We only group window tasks at this time. If this ever changes, the
340 // implementation of data() will have to be adjusted significantly, as for
341 // many roles it currently falls through to the first child item when dealing
342 // with requests for the parent (e.g. IsWindow).
343 if (!sourceIndex.data(AbstractTasksModel::IsWindow).toBool()) {
344 return false;
345 }
346
347 // If Private::groupDemandingAttention is false and this task is demanding
348 // attention, don't group it at this time. We'll instead try to group it once
349 // it no longer demands attention (see sourceDataChanged()).
350 if (!groupDemandingAttention && sourceIndex.data(AbstractTasksModel::IsDemandingAttention).toBool()) {
351 return false;
352 }
353
354 // Blacklist checks.
355 if (isBlacklisted(sourceIndex)) {
356 return false;
357 }
358
359 // Meat of the matter: Try to add this source row to a sub-list with source rows
360 // associated with the same application.
361 for (int i = 0; i < rowMap.count(); ++i) {
362 const QModelIndex &groupRep = q->sourceModel()->index(rowMap.at(i)->constFirst(), 0);
363
364 // Don't match a row with itself.
365 if (sourceIndex == groupRep) {
366 continue;
367 }
368
369 // Don't group windows with anything other than windows.
370 if (!groupRep.data(AbstractTasksModel::IsWindow).toBool()) {
371 continue;
372 }
373
374 if (appsMatch(sourceIndex, groupRep)) {
375 const QModelIndex parent = q->index(i, 0);
376
377 if (!silent) {
378 const int newIndex = rowMap.at(i)->count();
379
380 if (newIndex == 1) {
381 q->beginInsertRows(parent, 0, 1);
382 } else {
383 q->beginInsertRows(parent, newIndex, newIndex);
384 }
385 }
386
387 rowMap[i]->append(sourceIndex.row());
388
389 if (!silent) {
390 q->endInsertRows();
391
392 Q_EMIT q->dataChanged(parent, parent);
393 }
394
395 return true;
396 }
397 }
398
399 return false;
400}
401
402void TaskGroupingProxyModel::Private::formGroupFor(const QModelIndex &index)
403{
404 // Already in group or a group.
405 if (index.parent().isValid() || isGroup(index.row())) {
406 return;
407 }
408
409 // We need to grab a source index as we may invalidate the index passed
410 // in through grouping.
411 const QModelIndex &sourceTarget = q->mapToSource(index);
412
413 for (int i = (rowMap.count() - 1); i >= 0; --i) {
414 const QModelIndex &sourceIndex = q->sourceModel()->index(rowMap.at(i)->constFirst(), 0);
415
416 if (!appsMatch(sourceTarget, sourceIndex)) {
417 continue;
418 }
419
420 if (tryToGroup(sourceIndex)) {
421 q->beginRemoveRows(QModelIndex(), i, i);
422 delete rowMap.takeAt(i); // Safe since we're iterating backwards.
423 q->endRemoveRows();
424 }
425 }
426}
427
428void TaskGroupingProxyModel::Private::breakGroupFor(const QModelIndex &index, bool silent)
429{
430 const int row = index.row();
431
432 if (!isGroup(row)) {
433 return;
434 }
435
436 // The first child will move up to the top level.
437 QList<int> extraChildren = rowMap.at(row)->mid(1);
438
439 // NOTE: We're going to do remove+insert transactions instead of a
440 // single reparenting move transaction to save on complexity in the
441 // proxies above us.
442 // TODO: This could technically be optimized, though it's very
443 // unlikely to be ever worth it.
444 if (!silent) {
445 q->beginRemoveRows(index, 0, extraChildren.count());
446 }
447
448 rowMap[row]->resize(1);
449
450 if (!silent) {
451 q->endRemoveRows();
452
453 // We're no longer a group parent.
454 Q_EMIT q->dataChanged(index, index);
455
456 q->beginInsertRows(QModelIndex(), rowMap.count(), rowMap.count() + (extraChildren.count() - 1));
457 }
458
459 for (int i = 0; i < extraChildren.count(); ++i) {
460 rowMap.append(new QList<int>{extraChildren.at(i)});
461 }
462
463 if (!silent) {
464 q->endInsertRows();
465 }
466}
467
468TaskGroupingProxyModel::TaskGroupingProxyModel(QObject *parent)
469 : QAbstractProxyModel(parent)
470 , d(new Private(this))
471{
472}
473
474TaskGroupingProxyModel::~TaskGroupingProxyModel()
475{
476}
477
478QModelIndex TaskGroupingProxyModel::index(int row, int column, const QModelIndex &parent) const
479{
480 if (row < 0 || column != 0) {
481 return QModelIndex();
482 }
483
484 if (parent.isValid() && row < d->rowMap.at(parent.row())->count()) {
485 return createIndex(row, column, d->rowMap.at(parent.row()));
486 }
487
488 if (row < d->rowMap.count()) {
489 return createIndex(row, column, nullptr);
490 }
491
492 return QModelIndex();
493}
494
495QModelIndex TaskGroupingProxyModel::parent(const QModelIndex &child) const
496{
497 if (child.internalPointer() == nullptr) {
498 return QModelIndex();
499 } else {
500 const int parentRow = d->rowMap.indexOf(static_cast<QList<int> *>(child.internalPointer()));
501
502 if (parentRow != -1) {
503 return index(parentRow, 0);
504 }
505
506 // If we were asked to find the parent for an internalPointer we can't
507 // locate, we have corrupted data: This should not happen.
508 Q_ASSERT(parentRow != -1);
509 }
510
511 return QModelIndex();
512}
513
514QModelIndex TaskGroupingProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
515{
516 if (!sourceIndex.isValid() || sourceIndex.model() != sourceModel()) {
517 return QModelIndex();
518 }
519
520 for (int i = 0; i < d->rowMap.count(); ++i) {
521 const QList<int> *sourceRows = d->rowMap.at(i);
522 const int childIndex = sourceRows->indexOf(sourceIndex.row());
523 const QModelIndex parent = index(i, 0);
524
525 if (childIndex == 0) {
526 // If the sub-list we found the source row in is larger than 1 (i.e. part
527 // of a group, map to the logical child item instead of the parent item
528 // the source row also stands in for. The parent is therefore unreachable
529 // from mapToSource().
530 if (d->isGroup(i)) {
531 return index(0, 0, parent);
532 // Otherwise map to the top-level item.
533 } else {
534 return parent;
535 }
536 } else if (childIndex != -1) {
537 return index(childIndex, 0, parent);
538 }
539 }
540
541 return QModelIndex();
542}
543
544QModelIndex TaskGroupingProxyModel::mapToSource(const QModelIndex &proxyIndex) const
545{
546 if (!proxyIndex.isValid() || proxyIndex.model() != this || !sourceModel()) {
547 return QModelIndex();
548 }
549
550 const QModelIndex &parent = proxyIndex.parent();
551
552 if (parent.isValid()) {
553 if (parent.row() < 0 || parent.row() >= d->rowMap.count()) {
554 return QModelIndex();
555 }
556
557 return sourceModel()->index(d->rowMap.at(parent.row())->at(proxyIndex.row()), 0);
558 } else {
559 // Group parents items therefore equate to the first child item; the source
560 // row logically appears twice in the proxy.
561 // mapFromSource() is not required to handle this well (consider proxies can
562 // filter out rows, too) and opts to map to the child item, as the group parent
563 // has its Qt::DisplayRole mangled by data(), and it's more useful for trans-
564 // lating dataChanged() from the source model.
565 return sourceModel()->index(d->rowMap.at(proxyIndex.row())->at(0), 0);
566 }
567
568 return QModelIndex();
569}
570
571int TaskGroupingProxyModel::rowCount(const QModelIndex &parent) const
572{
573 if (!sourceModel()) {
574 return 0;
575 }
576
577 if (parent.isValid() && parent.model() == this) {
578 // Don't return row count for top-level item at child row: Group members
579 // never have further children of their own.
580 if (parent.parent().isValid()) {
581 return 0;
582 }
583
584 if (parent.row() < 0 || parent.row() >= d->rowMap.count()) {
585 return 0;
586 }
587
588 const uint rowCount = d->rowMap.at(parent.row())->count();
589
590 // If this sub-list in the map only has one entry, it's a plain item, not
591 // parent to a group.
592 if (rowCount == 1) {
593 return 0;
594 } else {
595 return rowCount;
596 }
597 }
598
599 return d->rowMap.count();
600}
601
602bool TaskGroupingProxyModel::hasChildren(const QModelIndex &parent) const
603{
604 if ((parent.model() && parent.model() != this) || !sourceModel()) {
605 return false;
606 }
607
608 return rowCount(parent);
609}
610
611int TaskGroupingProxyModel::columnCount(const QModelIndex &parent) const
612{
613 Q_UNUSED(parent)
614
615 return 1;
616}
617
618QVariant TaskGroupingProxyModel::data(const QModelIndex &proxyIndex, int role) const
619{
620 if (!proxyIndex.isValid() || proxyIndex.model() != this || !sourceModel()) {
621 return QVariant();
622 }
623
624 const QModelIndex &parent = proxyIndex.parent();
625 const bool isWindowGroup = (!parent.isValid() && d->isGroup(proxyIndex.row()));
626
627 // For group parent items, this will map to the first child task.
628 const QModelIndex &sourceIndex = mapToSource(proxyIndex);
629
630 if (!sourceIndex.isValid()) {
631 return QVariant();
632 }
633
635 return !d->isBlacklisted(sourceIndex);
636 }
637
638 if (isWindowGroup) {
639 // For group parent items, DisplayRole is mapped to AppName of the first child.
640 if (role == Qt::DisplayRole) {
641 const QString &appName = sourceIndex.data(AbstractTasksModel::AppName).toString();
642
643 // Groups are formed by app id or launcher URL; neither requires
644 // AppName to be available. If it's not, fall back to the app id
645 /// rather than an empty string.
646 if (appName.isEmpty()) {
647 return sourceIndex.data(AbstractTasksModel::AppId);
648 }
649
650 return appName;
651 } else if (role == AbstractTasksModel::WinIdList) {
652 QVariantList winIds;
653
654 for (int i = 0; i < rowCount(proxyIndex); ++i) {
655 winIds.append(index(i, 0, proxyIndex).data(AbstractTasksModel::WinIdList).toList());
656 }
657
658 return winIds;
659 } else if (role == AbstractTasksModel::MimeType) {
660 return QStringLiteral("windowsystem/multiple-winids");
661 } else if (role == AbstractTasksModel::MimeData) {
662 // FIXME TODO: Implement.
663 return QVariant();
664 } else if (role == AbstractTasksModel::IsGroupParent) {
665 return true;
666 } else if (role == AbstractTasksModel::ChildCount) {
667 return rowCount(proxyIndex);
668 } else if (role == AbstractTasksModel::IsActive) {
669 return d->any(proxyIndex, AbstractTasksModel::IsActive);
670 } else if (role == AbstractTasksModel::IsClosable) {
671 return d->all(proxyIndex, AbstractTasksModel::IsClosable);
672 } else if (role == AbstractTasksModel::IsMovable) {
673 // Moving groups makes no sense.
674 return false;
675 } else if (role == AbstractTasksModel::IsResizable) {
676 // Resizing groups makes no sense.
677 return false;
678 } else if (role == AbstractTasksModel::IsMaximizable) {
679 return d->all(proxyIndex, AbstractTasksModel::IsMaximizable);
680 } else if (role == AbstractTasksModel::IsMaximized) {
681 return d->all(proxyIndex, AbstractTasksModel::IsMaximized);
682 } else if (role == AbstractTasksModel::IsMinimizable) {
683 return d->all(proxyIndex, AbstractTasksModel::IsMinimizable);
684 } else if (role == AbstractTasksModel::IsMinimized) {
685 return d->all(proxyIndex, AbstractTasksModel::IsMinimized);
686 } else if (role == AbstractTasksModel::IsKeepAbove) {
687 return d->all(proxyIndex, AbstractTasksModel::IsKeepAbove);
688 } else if (role == AbstractTasksModel::IsKeepBelow) {
689 return d->all(proxyIndex, AbstractTasksModel::IsKeepBelow);
690 } else if (role == AbstractTasksModel::IsFullScreenable) {
691 return d->all(proxyIndex, AbstractTasksModel::IsFullScreenable);
692 } else if (role == AbstractTasksModel::IsFullScreen) {
693 return d->all(proxyIndex, AbstractTasksModel::IsFullScreen);
694 } else if (role == AbstractTasksModel::IsShadeable) {
695 return d->all(proxyIndex, AbstractTasksModel::IsShadeable);
696 } else if (role == AbstractTasksModel::IsShaded) {
697 return d->all(proxyIndex, AbstractTasksModel::IsShaded);
698 } else if (role == AbstractTasksModel::CanSetNoBorder) {
699 return d->all(proxyIndex, AbstractTasksModel::CanSetNoBorder);
700 } else if (role == AbstractTasksModel::HasNoBorder) {
701 return d->all(proxyIndex, AbstractTasksModel::HasNoBorder);
703 return d->all(proxyIndex, AbstractTasksModel::IsVirtualDesktopsChangeable);
704 } else if (role == AbstractTasksModel::VirtualDesktops) {
705 QStringList desktops;
706
707 for (int i = 0; i < rowCount(proxyIndex); ++i) {
708 desktops.append(index(i, 0, proxyIndex).data(AbstractTasksModel::VirtualDesktops).toStringList());
709 }
710
711 desktops.removeDuplicates();
712 return desktops;
713 } else if (role == AbstractTasksModel::ScreenGeometry) {
714 // TODO: Nothing needs this for now and it would add complexity to
715 // make it a list; skip it until needed. Once it is, do it similarly
716 // to the AbstractTasksModel::VirtualDesktop case.
717 return QVariant();
718 } else if (role == AbstractTasksModel::Activities) {
719 QStringList activities;
720
721 for (int i = 0; i < rowCount(proxyIndex); ++i) {
722 activities.append(index(i, 0, proxyIndex).data(AbstractTasksModel::Activities).toStringList());
723 }
724
725 activities.removeDuplicates();
726 return activities;
727 } else if (role == AbstractTasksModel::IsDemandingAttention) {
728 return d->any(proxyIndex, AbstractTasksModel::IsDemandingAttention);
729 } else if (role == AbstractTasksModel::SkipTaskbar) {
730 return d->all(proxyIndex, AbstractTasksModel::SkipTaskbar);
731 } else if (role == AbstractTasksModel::LastActivated) {
732 // Find the last activated task in the single group
733 const int groupSize = d->rowMap.at(proxyIndex.row())->size();
734 QDateTime lastActivated = mapToSource(index(0, 0, proxyIndex)).data(AbstractTasksModel::LastActivated).toDateTime();
735
736 for (int i = 1; i < groupSize; i++) {
737 const QDateTime activated = mapToSource(index(i, 0, proxyIndex)).data(AbstractTasksModel::LastActivated).toDateTime();
738
739 if (lastActivated < activated) {
740 lastActivated = activated;
741 }
742 }
743
744 return lastActivated;
745 }
746 }
747
748 return sourceIndex.data(role);
749}
750
751void TaskGroupingProxyModel::setSourceModel(QAbstractItemModel *sourceModel)
752{
753 if (sourceModel == QAbstractProxyModel::sourceModel()) {
754 return;
755 }
756
757 beginResetModel();
758
760 QAbstractProxyModel::sourceModel()->disconnect(this);
761 }
762
764 d->abstractTasksSourceModel = dynamic_cast<AbstractTasksModelIface *>(sourceModel);
765
766 if (sourceModel) {
767 d->rebuildMap();
768
769 using namespace std::placeholders;
770 auto dd = d.get();
771 connect(sourceModel,
773 this,
774 std::bind(&TaskGroupingProxyModel::Private::sourceRowsAboutToBeInserted, dd, _1, _2, _3));
775 connect(sourceModel, &QSortFilterProxyModel::rowsInserted, this, std::bind(&TaskGroupingProxyModel::Private::sourceRowsInserted, dd, _1, _2, _3));
776 connect(sourceModel,
778 this,
779 std::bind(&TaskGroupingProxyModel::Private::sourceRowsAboutToBeRemoved, dd, _1, _2, _3));
780 connect(sourceModel, &QSortFilterProxyModel::rowsRemoved, this, std::bind(&TaskGroupingProxyModel::Private::sourceRowsRemoved, dd, _1, _2, _3));
781 connect(sourceModel, &QSortFilterProxyModel::modelAboutToBeReset, this, std::bind(&TaskGroupingProxyModel::Private::sourceModelAboutToBeReset, dd));
782 connect(sourceModel, &QSortFilterProxyModel::modelReset, this, std::bind(&TaskGroupingProxyModel::Private::sourceModelReset, dd));
783 connect(sourceModel, &QSortFilterProxyModel::dataChanged, this, std::bind(&TaskGroupingProxyModel::Private::sourceDataChanged, dd, _1, _2, _3));
784 } else {
785 qDeleteAll(d->rowMap);
786 d->rowMap.clear();
787 }
788
789 endResetModel();
790}
791
792TasksModel::GroupMode TaskGroupingProxyModel::groupMode() const
793{
794 return d->groupMode;
795}
796
798{
799 if (d->groupMode != mode) {
800 d->groupMode = mode;
801
802 d->checkGrouping();
803
804 Q_EMIT groupModeChanged();
805 }
806}
807
808bool TaskGroupingProxyModel::groupDemandingAttention() const
809{
810 return d->groupDemandingAttention;
811}
812
814{
815 if (d->groupDemandingAttention != group) {
816 d->groupDemandingAttention = group;
817
818 d->checkGrouping();
819
820 Q_EMIT groupDemandingAttentionChanged();
821 }
822}
823
824int TaskGroupingProxyModel::windowTasksThreshold() const
825{
826 return d->windowTasksThreshold;
827}
828
830{
831 if (d->windowTasksThreshold != threshold) {
832 d->windowTasksThreshold = threshold;
833
834 d->checkGrouping();
835
836 Q_EMIT windowTasksThresholdChanged();
837 }
838}
839
840QStringList TaskGroupingProxyModel::blacklistedAppIds() const
841{
842 return d->blacklistedAppIds.values();
843}
844
846{
847 const QSet<QString> &set = QSet<QString>(list.cbegin(), list.cend());
848
849 if (d->blacklistedAppIds != set) {
850 d->blacklistedAppIds = set;
851
852 // checkGrouping() will gather and group up what's newly-allowed under the changed
853 // blacklist.
854 d->checkGrouping();
855
856 // Now break apart what we need to.
857 for (int i = (d->rowMap.count() - 1); i >= 0; --i) {
858 if (d->isGroup(i)) {
859 const QModelIndex &groupRep = index(i, 0);
860
861 if (set.contains(groupRep.data(AbstractTasksModel::AppId).toString())) {
862 d->breakGroupFor(groupRep); // Safe since we're iterating backwards.
863 }
864 }
865 }
866
867 Q_EMIT blacklistedAppIdsChanged();
868 }
869}
870
871QStringList TaskGroupingProxyModel::blacklistedLauncherUrls() const
872{
873 return d->blacklistedLauncherUrls.values();
874}
875
877{
878 const QSet<QString> &set = QSet<QString>(list.cbegin(), list.cend());
879
880 if (d->blacklistedLauncherUrls != set) {
881 d->blacklistedLauncherUrls = set;
882
883 // checkGrouping() will gather and group up what's newly-allowed under the changed
884 // blacklist.
885 d->checkGrouping();
886
887 // Now break apart what we need to.
888 for (int i = (d->rowMap.count() - 1); i >= 0; --i) {
889 if (d->isGroup(i)) {
890 const QModelIndex &groupRep = index(i, 0);
891 const QUrl &launcherUrl = groupRep.data(AbstractTasksModel::LauncherUrlWithoutIcon).toUrl();
892 const QString &launcherUrlString = launcherUrl.toString(QUrl::RemoveQuery);
893
894 if (set.contains(launcherUrlString)) {
895 d->breakGroupFor(groupRep); // Safe since we're iterating backwards.
896 }
897 }
898 }
899
900 Q_EMIT blacklistedLauncherUrlsChanged();
901 }
902}
903
905{
906 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
907 return;
908 }
909
910 if (index.parent().isValid() || !d->isGroup(index.row())) {
911 d->abstractTasksSourceModel->requestActivate(mapToSource(index));
912 }
913}
914
916{
917 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
918 return;
919 }
920
921 d->abstractTasksSourceModel->requestNewInstance(mapToSource(index));
922}
923
925{
926 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
927 return;
928 }
929
930 d->abstractTasksSourceModel->requestOpenUrls(mapToSource(index), urls);
931}
932
934{
935 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
936 return;
937 }
938
939 if (index.parent().isValid() || !d->isGroup(index.row())) {
940 d->abstractTasksSourceModel->requestClose(mapToSource(index));
941 } else {
942 const int row = index.row();
943
944 for (int i = (rowCount(index) - 1); i >= 1; --i) {
945 const QModelIndex &sourceChild = mapToSource(this->index(i, 0, index));
946 d->abstractTasksSourceModel->requestClose(sourceChild);
947 }
948
949 d->abstractTasksSourceModel->requestClose(mapToSource(TaskGroupingProxyModel::index(row, 0)));
950 }
951}
952
954{
955 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
956 return;
957 }
958
959 if (index.parent().isValid() || !d->isGroup(index.row())) {
960 d->abstractTasksSourceModel->requestMove(mapToSource(index));
961 }
962}
963
965{
966 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
967 return;
968 }
969
970 if (index.parent().isValid() || !d->isGroup(index.row())) {
971 d->abstractTasksSourceModel->requestResize(mapToSource(index));
972 }
973}
974
976{
977 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
978 return;
979 }
980
981 if (index.parent().isValid() || !d->isGroup(index.row())) {
982 d->abstractTasksSourceModel->requestToggleMinimized(mapToSource(index));
983 } else {
984 const bool goalState = !index.data(AbstractTasksModel::IsHidden).toBool();
985
986 for (int i = 0; i < rowCount(index); ++i) {
987 const QModelIndex &child = this->index(i, 0, index);
988
989 if (child.data(AbstractTasksModel::IsHidden).toBool() != goalState) {
990 d->abstractTasksSourceModel->requestToggleMinimized(mapToSource(child));
991 }
992 }
993 }
994}
995
997{
998 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
999 return;
1000 }
1001
1002 if (index.parent().isValid() || !d->isGroup(index.row())) {
1003 d->abstractTasksSourceModel->requestToggleMaximized(mapToSource(index));
1004 } else {
1005 const bool goalState = !index.data(AbstractTasksModel::IsMaximized).toBool();
1006
1007 QModelIndexList inStackingOrder;
1008
1009 for (int i = 0; i < rowCount(index); ++i) {
1010 const QModelIndex &child = this->index(i, 0, index);
1011
1012 if (child.data(AbstractTasksModel::IsMaximized).toBool() != goalState) {
1013 inStackingOrder << mapToSource(child);
1014 }
1015 }
1016
1017 std::sort(inStackingOrder.begin(), inStackingOrder.end(), [](const QModelIndex &a, const QModelIndex &b) {
1018 return (a.data(AbstractTasksModel::StackingOrder).toInt() < b.data(AbstractTasksModel::StackingOrder).toInt());
1019 });
1020
1021 for (const QModelIndex &sourceChild : std::as_const(inStackingOrder)) {
1022 d->abstractTasksSourceModel->requestToggleMaximized(sourceChild);
1023 }
1024 }
1025}
1026
1028{
1029 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
1030 return;
1031 }
1032
1033 if (index.parent().isValid() || !d->isGroup(index.row())) {
1034 d->abstractTasksSourceModel->requestToggleKeepAbove(mapToSource(index));
1035 } else {
1036 const bool goalState = !index.data(AbstractTasksModel::IsKeepAbove).toBool();
1037
1038 for (int i = 0; i < rowCount(index); ++i) {
1039 const QModelIndex &child = this->index(i, 0, index);
1040
1041 if (child.data(AbstractTasksModel::IsKeepAbove).toBool() != goalState) {
1042 d->abstractTasksSourceModel->requestToggleKeepAbove(mapToSource(child));
1043 }
1044 }
1045 }
1046}
1047
1049{
1050 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
1051 return;
1052 }
1053
1054 if (index.parent().isValid() || !d->isGroup(index.row())) {
1055 d->abstractTasksSourceModel->requestToggleKeepBelow(mapToSource(index));
1056 } else {
1057 const bool goalState = !index.data(AbstractTasksModel::IsKeepBelow).toBool();
1058
1059 for (int i = 0; i < rowCount(index); ++i) {
1060 const QModelIndex &child = this->index(i, 0, index);
1061
1062 if (child.data(AbstractTasksModel::IsKeepBelow).toBool() != goalState) {
1063 d->abstractTasksSourceModel->requestToggleKeepBelow(mapToSource(child));
1064 }
1065 }
1066 }
1067}
1068
1070{
1071 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
1072 return;
1073 }
1074
1075 if (index.parent().isValid() || !d->isGroup(index.row())) {
1076 d->abstractTasksSourceModel->requestToggleFullScreen(mapToSource(index));
1077 } else {
1078 const bool goalState = !index.data(AbstractTasksModel::IsFullScreen).toBool();
1079
1080 for (int i = 0; i < rowCount(index); ++i) {
1081 const QModelIndex &child = this->index(i, 0, index);
1082
1083 if (child.data(AbstractTasksModel::IsFullScreen).toBool() != goalState) {
1084 d->abstractTasksSourceModel->requestToggleFullScreen(mapToSource(child));
1085 }
1086 }
1087 }
1088}
1089
1091{
1092 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
1093 return;
1094 }
1095
1096 if (index.parent().isValid() || !d->isGroup(index.row())) {
1097 d->abstractTasksSourceModel->requestToggleShaded(mapToSource(index));
1098 } else {
1099 const bool goalState = !index.data(AbstractTasksModel::IsShaded).toBool();
1100
1101 for (int i = 0; i < rowCount(index); ++i) {
1102 const QModelIndex &child = this->index(i, 0, index);
1103
1104 if (child.data(AbstractTasksModel::IsShaded).toBool() != goalState) {
1105 d->abstractTasksSourceModel->requestToggleShaded(mapToSource(child));
1106 }
1107 }
1108 }
1109}
1110
1112{
1113 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
1114 return;
1115 }
1116
1117 if (index.parent().isValid() || !d->isGroup(index.row())) {
1118 d->abstractTasksSourceModel->requestToggleNoBorder(mapToSource(index));
1119 } else {
1120 const bool goalState = !index.data(AbstractTasksModel::HasNoBorder).toBool();
1121
1122 for (int i = 0; i < rowCount(index); ++i) {
1123 const QModelIndex &child = this->index(i, 0, index);
1124
1125 if (child.data(AbstractTasksModel::HasNoBorder).toBool() != goalState) {
1126 d->abstractTasksSourceModel->requestToggleNoBorder(mapToSource(child));
1127 }
1128 }
1129 }
1130}
1131
1132void TaskGroupingProxyModel::requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops)
1133{
1134 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
1135 return;
1136 }
1137
1138 if (index.parent().isValid() || !d->isGroup(index.row())) {
1139 d->abstractTasksSourceModel->requestVirtualDesktops(mapToSource(index), desktops);
1140 } else {
1141 QList<QModelIndex> groupChildren;
1142
1143 const int childCount = rowCount(index);
1144
1145 groupChildren.reserve(childCount);
1146
1147 for (int i = (childCount - 1); i >= 0; --i) {
1148 groupChildren.append(mapToSource(this->index(i, 0, index)));
1149 }
1150
1151 for (const QModelIndex &idx : groupChildren) {
1152 d->abstractTasksSourceModel->requestVirtualDesktops(idx, desktops);
1153 }
1154 }
1155}
1156
1158{
1159 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
1160 return;
1161 }
1162
1163 if (index.parent().isValid() || !d->isGroup(index.row())) {
1164 d->abstractTasksSourceModel->requestNewVirtualDesktop(mapToSource(index));
1165 } else {
1166 QList<QModelIndex> groupChildren;
1167
1168 const int childCount = rowCount(index);
1169
1170 groupChildren.reserve(childCount);
1171
1172 for (int i = (childCount - 1); i >= 0; --i) {
1173 groupChildren.append(mapToSource(this->index(i, 0, index)));
1174 }
1175
1176 for (const QModelIndex &idx : groupChildren) {
1177 d->abstractTasksSourceModel->requestNewVirtualDesktop(idx);
1178 }
1179 }
1180}
1181
1183{
1184 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
1185 return;
1186 }
1187
1188 if (index.parent().isValid() || !d->isGroup(index.row())) {
1189 d->abstractTasksSourceModel->requestActivities(mapToSource(index), activities);
1190 } else {
1191 QList<QModelIndex> groupChildren;
1192
1193 const int childCount = rowCount(index);
1194
1195 groupChildren.reserve(childCount);
1196
1197 for (int i = (childCount - 1); i >= 0; --i) {
1198 groupChildren.append(mapToSource(this->index(i, 0, index)));
1199 }
1200
1201 for (const QModelIndex &idx : groupChildren) {
1202 d->abstractTasksSourceModel->requestActivities(idx, activities);
1203 }
1204 }
1205}
1206
1208{
1209 if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) {
1210 return;
1211 }
1212
1213 if (index.parent().isValid() || !d->isGroup(index.row())) {
1214 d->abstractTasksSourceModel->requestPublishDelegateGeometry(mapToSource(index), geometry, delegate);
1215 } else {
1216 for (int i = 0; i < rowCount(index); ++i) {
1217 d->abstractTasksSourceModel->requestPublishDelegateGeometry(mapToSource(this->index(i, 0, index)), geometry, delegate);
1218 }
1219 }
1220}
1221
1223{
1224 const QString &appId = index.data(AbstractTasksModel::AppId).toString();
1225 const QUrl &launcherUrl = index.data(AbstractTasksModel::LauncherUrlWithoutIcon).toUrl();
1226 const QString &launcherUrlString = launcherUrl.toString(QUrl::RemoveQuery);
1227
1228 if (d->blacklistedAppIds.contains(appId) || d->blacklistedLauncherUrls.contains(launcherUrlString)) {
1229 d->blacklistedAppIds.remove(appId);
1230 d->blacklistedLauncherUrls.remove(launcherUrlString);
1231
1232 if (d->groupMode != TasksModel::GroupDisabled) {
1233 d->formGroupFor(index.parent().isValid() ? index.parent() : index);
1234 }
1235 } else {
1236 d->blacklistedAppIds.insert(appId);
1237 d->blacklistedLauncherUrls.insert(launcherUrlString);
1238
1239 if (d->groupMode != TasksModel::GroupDisabled) {
1240 d->breakGroupFor(index.parent().isValid() ? index.parent() : index);
1241 }
1242 }
1243
1244 // Update IsGroupable data role for all relevant top-level items. We don't need to update
1245 // for group members since they've just been inserted -- it's logically impossible to
1246 // toggle grouping _on_ from a group member.
1247 for (int i = 0; i < d->rowMap.count(); ++i) {
1248 if (!d->isGroup(i)) {
1249 const QModelIndex &idx = TaskGroupingProxyModel::index(i, 0);
1250
1251 if (idx.data(AbstractTasksModel::AppId).toString() == appId
1252 || launcherUrlsMatch(idx.data(AbstractTasksModel::LauncherUrlWithoutIcon).toUrl(), launcherUrl, IgnoreQueryItems)) {
1254 }
1255 }
1256 }
1257
1258 Q_EMIT blacklistedAppIdsChanged();
1259 Q_EMIT blacklistedLauncherUrlsChanged();
1260}
1261
1262}
1263
1264#include "moc_taskgroupingproxymodel.cpp"
@ IsGroupable
Whether this task is being ignored by grouping or not.
@ MimeType
MIME type for this task (window, window group), needed for DND.
@ IsClosable
requestClose (see below) available.
@ Activities
Activities for the task (i.e.
@ IsDemandingAttention
Task is demanding attention.
@ HasNoBorder
Whether the task's window has the no border state set.
@ IsHidden
Task (i.e window) is hidden on screen.
@ IsActive
This is the currently active task.
@ SkipTaskbar
Task should not be shown in a 'task bar' user interface.
@ IsFullScreenable
requestToggleFullScreen (see below) available.
@ CanSetNoBorder
Whether the task can set no border state.
@ IsVirtualDesktopsChangeable
requestVirtualDesktop (see below) available.
@ IsGroupParent
This is a parent item for a group of child tasks.
@ VirtualDesktops
Virtual desktops for the task (i.e.
@ IsShadeable
requestToggleShade (see below) available.
@ IsResizable
requestResize (see below) available.
@ WinIdList
NOTE: On Wayland, these ids are only useful within the same process.
@ LauncherUrlWithoutIcon
Special path to get a launcher URL while skipping fallback icon encoding.
@ IsMinimizable
requestToggleMinimize (see below) available.
@ LastActivated
The timestamp of the last time a task was the active task.
@ IsMovable
requestMove (see below) available.
@ AppId
KService storage id (.desktop name sans extension).
@ ChildCount
The number of tasks in this group.
@ ScreenGeometry
Screen geometry for the task (i.e.
@ IsMaximizable
requestToggleMaximize (see below) available.
A proxy tasks model for grouping tasks, forming a tree.
void requestNewInstance(const QModelIndex &index) override
Request an additional instance of the application backing the task at the given index.
void requestToggleKeepAbove(const QModelIndex &index) override
Request toggling the keep-above state of the task at the given index.
void requestNewVirtualDesktop(const QModelIndex &index) override
Request entering the window at the given index on a new virtual desktop, which is created in response...
void requestMove(const QModelIndex &index) override
Request starting an interactive move for the task at the given index.
void setGroupDemandingAttention(bool group)
Sets whether new tasks which demand attention (AbstractTasksModel::IsDemandingAttention) should be gr...
void requestToggleFullScreen(const QModelIndex &index) override
Request toggling the fullscreen state of the task at the given index.
void setBlacklistedLauncherUrls(const QStringList &list)
Sets the blacklist of launcher URLs (AbstractTasksModel::LauncherUrl) that is consulted before groupi...
void requestToggleShaded(const QModelIndex &index) override
Request toggling the shaded state of the task at the given index.
void setWindowTasksThreshold(int threshold)
Sets the number of source model window tasks (AbstractTasksModel::IsWindow) above which groups will b...
void requestToggleKeepBelow(const QModelIndex &index) override
Request toggling the keep-below state of the task at the given index.
void requestResize(const QModelIndex &index) override
Request starting an interactive resize for the task at the given index.
void requestClose(const QModelIndex &index) override
Request the task at the given index be closed.
void setGroupMode(TasksModel::GroupMode mode)
Sets the group mode, i.e.
void requestToggleMaximized(const QModelIndex &index) override
Request toggling the maximized state of the task at the given index.
void requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate=nullptr) override
Request informing the window manager of new geometry for a visual delegate for the task at the given ...
void requestToggleMinimized(const QModelIndex &index) override
Request toggling the minimized state of the task at the given index.
void requestToggleNoBorder(const QModelIndex &index) override
Request toggling the no border state of the task at given index.
void requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops) override
Request entering the window at the given index on the specified virtual desktops, leaving any other d...
void requestOpenUrls(const QModelIndex &index, const QList< QUrl > &urls) override
Requests to open the given URLs with the application backing the task at the given index.
void requestToggleGrouping(const QModelIndex &index)
Request toggling whether the task at the given index, along with any tasks matching its kind,...
void requestActivate(const QModelIndex &index) override
Request activation of the task at the given index.
void requestActivities(const QModelIndex &index, const QStringList &activities) override
Request moving the task at the given index to the specified activities.
void setBlacklistedAppIds(const QStringList &list)
Sets the blacklist of app ids (AbstractTasksModel::AppId) that is consulted before grouping a task.
QVariant data(const QModelIndex &proxyIndex, int role) const override
@ GroupDisabled
No grouping is done.
Definition tasksmodel.h:102
Q_SCRIPTABLE QString start(QString train="")
Q_SCRIPTABLE Q_NOREPLY void start()
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
void modelAboutToBeReset()
void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
void rowsInserted(const QModelIndex &parent, int first, int last)
void rowsRemoved(const QModelIndex &parent, int first, int last)
virtual void setSourceModel(QAbstractItemModel *sourceModel)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
iterator begin()
const_iterator cbegin() const const
const_iterator cend() const const
void clear()
bool contains(const AT &value) const const
qsizetype count() const const
iterator end()
qsizetype indexOf(const AT &value, qsizetype from) const const
void remove(qsizetype i, qsizetype n)
void reserve(qsizetype size)
void resize(qsizetype size)
T takeAt(qsizetype i)
QVariant data(int role) const const
void * internalPointer() const const
bool isValid() const const
const QAbstractItemModel * model() const const
QModelIndex parent() const const
int row() const const
QObject(QObject *parent)
Q_EMITQ_EMIT
bool contains(const QSet< T > &other) const const
qsizetype count() const const
QChar * data()
bool isEmpty() const const
qsizetype removeDuplicates()
DisplayRole
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
PrettyDecoded
QString toString(FormattingOptions options) const const
bool toBool() const const
QString toString() const const
QUrl toUrl() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Mar 28 2025 11:53:53 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.