]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemmodel.h
Implemented the possibility for sorting/grouping behaviors that are not tied to roles...
[dolphin.git] / src / kitemviews / kfileitemmodel.h
1 /*
2 * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #ifndef KFILEITEMMODEL_H
8 #define KFILEITEMMODEL_H
9
10 #include "dolphin_export.h"
11 #include "kitemviews/kitemmodelbase.h"
12 #include "kitemviews/private/kfileitemmodelfilter.h"
13
14 #include <KFileItem>
15 #include <KLazyLocalizedString>
16
17 #include <QCollator>
18 #include <QHash>
19 #include <QSet>
20 #include <QUrl>
21
22 #include <functional>
23
24 class KDirLister;
25
26 class QTimer;
27
28 namespace KIO
29 {
30 class Job;
31 }
32
33 /**
34 * @brief KItemModelBase implementation for KFileItems.
35 *
36 * Allows to load items of a directory. Sorting and grouping of
37 * items are supported. Roles that are not part of KFileItem can
38 * be added with KFileItemModel::setData().
39 *
40 * Recursive expansion of sub-directories is supported by
41 * KFileItemModel::setExpanded().
42 */
43 class DOLPHIN_EXPORT KFileItemModel : public KItemModelBase
44 {
45 Q_OBJECT
46
47 public:
48 explicit KFileItemModel(QObject *parent = nullptr);
49 ~KFileItemModel() override;
50
51 /**
52 * Loads the directory specified by \a url. The signals
53 * directoryLoadingStarted(), directoryLoadingProgress() and directoryLoadingCompleted()
54 * indicate the current state of the loading process. The items
55 * of the directory are added after the loading has been completed.
56 */
57 void loadDirectory(const QUrl &url);
58
59 /**
60 * Throws away all currently loaded items and refreshes the directory
61 * by reloading all items again.
62 */
63 void refreshDirectory(const QUrl &url);
64
65 /**
66 * @return Parent directory of the items that are shown. In case
67 * if a directory tree is shown, KFileItemModel::dir() returns
68 * the root-parent of all items.
69 * @see rootItem()
70 */
71 QUrl directory() const override;
72
73 /**
74 * Cancels the loading of a directory which has been started by either
75 * loadDirectory() or refreshDirectory().
76 */
77 void cancelDirectoryLoading();
78
79 int count() const override;
80 QHash<QByteArray, QVariant> data(int index) const override;
81 bool setData(int index, const QHash<QByteArray, QVariant> &values) override;
82
83 /**
84 * Sets a separate sorting with directories first (true) or a mixed
85 * sorting of files and directories (false).
86 */
87 void setSortDirectoriesFirst(bool dirsFirst);
88 bool sortDirectoriesFirst() const;
89
90 /**
91 * Sets a separate sorting with hidden files and folders last (true) or not (false).
92 */
93 void setSortHiddenLast(bool hiddenLast);
94 bool sortHiddenLast() const;
95
96 void setShowHiddenFiles(bool show);
97 bool showHiddenFiles() const;
98
99 /**
100 * If set to true, only directories are shown as items of the model. Files
101 * are ignored.
102 */
103 void setShowDirectoriesOnly(bool enabled);
104 bool showDirectoriesOnly() const;
105
106 QMimeData *createMimeData(const KItemSet &indexes) const override;
107
108 int indexForKeyboardSearch(const QString &text, int startFromIndex = 0) const override;
109
110 bool supportsDropping(int index) const override;
111
112 bool canEnterOnHover(int index) const override;
113
114 QString roleDescription(const QByteArray &role) const override;
115
116 QList<QPair<int, QVariant>> groups() const override;
117
118 /**
119 * @return The file-item for the index \a index. If the index is in a valid
120 * range it is assured that the file-item is not null. The runtime
121 * complexity of this call is O(1).
122 */
123 KFileItem fileItem(int index) const;
124
125 /**
126 * @return The file-item for the url \a url. If no file-item with the given
127 * URL is found KFileItem::isNull() will be true for the returned
128 * file-item. The runtime complexity of this call is O(1).
129 */
130 KFileItem fileItem(const QUrl &url) const;
131
132 /**
133 * @return The index for the file-item \a item. -1 is returned if no file-item
134 * is found or if the file-item is null. The amortized runtime
135 * complexity of this call is O(1).
136 */
137 int index(const KFileItem &item) const;
138
139 /**
140 * @return The index for the URL \a url. -1 is returned if no file-item
141 * is found. The amortized runtime complexity of this call is O(1).
142 */
143 int index(const QUrl &url) const;
144
145 /**
146 * @return Root item of all items representing the item
147 * for KFileItemModel::dir().
148 */
149 KFileItem rootItem() const;
150
151 /**
152 * Clears all items of the model.
153 */
154 void clear();
155
156 /**
157 * Sets the roles that should be shown for each item.
158 */
159 void setRoles(const QSet<QByteArray> &roles);
160 QSet<QByteArray> roles() const;
161
162 bool setExpanded(int index, bool expanded) override;
163 bool isExpanded(int index) const override;
164 bool isExpandable(int index) const override;
165 int expandedParentsCount(int index) const override;
166
167 QSet<QUrl> expandedDirectories() const;
168
169 /**
170 * Marks the URLs in \a urls as sub-directories which were expanded previously.
171 * After calling loadDirectory() or refreshDirectory() the marked sub-directories
172 * will be expanded step-by-step.
173 */
174 void restoreExpandedDirectories(const QSet<QUrl> &urls);
175
176 /**
177 * Expands all parent-directories of the item \a url.
178 */
179 void expandParentDirectories(const QUrl &url);
180
181 void setNameFilter(const QString &nameFilter);
182 QString nameFilter() const;
183
184 void setMimeTypeFilters(const QStringList &filters);
185 QStringList mimeTypeFilters() const;
186
187 void setExcludeMimeTypeFilter(const QStringList &filters);
188 QStringList excludeMimeTypeFilter() const;
189
190 struct RoleInfo {
191 QByteArray role;
192 QString translation;
193 QString group;
194 QString tooltip;
195 bool requiresBaloo;
196 bool requiresIndexer;
197 };
198
199 /**
200 * @return Provides static information for a role that is supported
201 * by KFileItemModel. Some roles can only be determined if
202 * Baloo is enabled and/or the Baloo indexing is enabled.
203 */
204 static RoleInfo roleInformation(const QByteArray &role);
205
206 /**
207 * @return Provides static information for all available roles that
208 * are supported by KFileItemModel. Some roles can only be
209 * determined if Baloo is enabled and/or the Baloo
210 * indexing is enabled.
211 */
212 static QList<RoleInfo> rolesInformation();
213
214 /** set to true to hide application/x-trash files */
215 void setShowTrashMime(bool show);
216
217 Q_SIGNALS:
218 /**
219 * Is emitted if the loading of a directory has been started. It is
220 * assured that a signal directoryLoadingCompleted() will be send after
221 * the loading has been finished. For tracking the loading progress
222 * the signal directoryLoadingProgress() gets emitted in between.
223 */
224 void directoryLoadingStarted();
225
226 /**
227 * Is emitted after the loading of a directory has been completed or new
228 * items have been inserted to an already loaded directory. Usually
229 * one or more itemsInserted() signals are emitted before loadingCompleted()
230 * (the only exception is loading an empty directory, where only a
231 * loadingCompleted() signal gets emitted).
232 */
233 void directoryLoadingCompleted();
234
235 /**
236 * Is emitted when the model is being refreshed (F5 key press)
237 */
238 void directoryRefreshing();
239
240 /**
241 * Is emitted after the loading of a directory has been canceled.
242 */
243 void directoryLoadingCanceled();
244
245 /**
246 * Informs about the progress in percent when loading a directory. It is assured
247 * that the signal directoryLoadingStarted() has been emitted before.
248 */
249 void directoryLoadingProgress(int percent);
250
251 /**
252 * Is emitted if the sort-role gets resolved asynchronously and provides
253 * the progress-information of the sorting in percent. It is assured
254 * that the last sortProgress-signal contains 100 as value.
255 */
256 void directorySortingProgress(int percent);
257
258 /**
259 * Is emitted if an information message (e.g. "Connecting to host...")
260 * should be shown.
261 */
262 void infoMessage(const QString &message);
263
264 /**
265 * Is emitted if an error message (e.g. "Unknown location")
266 * should be shown.
267 */
268 void errorMessage(const QString &message);
269
270 /**
271 * Is emitted if a redirection from the current URL \a oldUrl
272 * to the new URL \a newUrl has been done.
273 */
274 void directoryRedirection(const QUrl &oldUrl, const QUrl &newUrl);
275
276 /**
277 * Is emitted when the URL passed by KFileItemModel::setUrl() represents a file.
278 * In this case no signal errorMessage() will be emitted.
279 */
280 void urlIsFileError(const QUrl &url);
281
282 /**
283 * It is emitted for files when they change and
284 * for dirs when files are added or removed.
285 */
286 void fileItemsChanged(const KFileItemList &changedFileItems);
287
288 /**
289 * It is emitted when the parent directory was removed.
290 */
291 void currentDirectoryRemoved();
292
293 protected:
294 void onGroupedSortingChanged(bool current) override;
295 void onSortRoleChanged(const QByteArray &current, const QByteArray &previous, bool resortItems = true) override;
296 void onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems = true) override;
297 void onGroupRoleChanged(const QByteArray &current, const QByteArray &previous, bool resortItems = true) override;
298 void onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems = true) override;
299
300 private Q_SLOTS:
301 /**
302 * Resorts all items dependent on the set sortRole(), sortOrder(),
303 * groupRole(), groupOrder() and foldersFirst() settings.
304 */
305 void resortAllItems();
306
307 void slotCompleted();
308 void slotCanceled();
309 void slotItemsAdded(const QUrl &directoryUrl, const KFileItemList &items);
310 void slotItemsDeleted(const KFileItemList &items);
311 void slotRefreshItems(const QList<QPair<KFileItem, KFileItem>> &items);
312 void slotClear();
313 void slotSortingChoiceChanged();
314 void slotListerError(KIO::Job *job);
315
316 void dispatchPendingItemsToInsert();
317
318 private:
319 enum RoleType {
320 // User visible roles:
321 NoRole,
322 NameRole,
323 SizeRole,
324 ModificationTimeRole,
325 CreationTimeRole,
326 AccessTimeRole,
327 PermissionsRole,
328 OwnerRole,
329 GroupRole,
330 TypeRole,
331 ExtensionRole,
332 DestinationRole,
333 PathRole,
334 DeletionTimeRole,
335 // User visible roles available with Baloo:
336 CommentRole,
337 TagsRole,
338 RatingRole,
339 DimensionsRole,
340 WidthRole,
341 HeightRole,
342 ImageDateTimeRole,
343 OrientationRole,
344 PublisherRole,
345 PageCountRole,
346 WordCountRole,
347 TitleRole,
348 AuthorRole,
349 LineCountRole,
350 ArtistRole,
351 GenreRole,
352 AlbumRole,
353 DurationRole,
354 TrackRole,
355 ReleaseYearRole,
356 BitrateRole,
357 OriginUrlRole,
358 AspectRatioRole,
359 FrameRateRole,
360 // Non-visible roles:
361 IsDirRole,
362 IsLinkRole,
363 IsHiddenRole,
364 IsExpandedRole,
365 IsExpandableRole,
366 ExpandedParentsCountRole,
367 // Mandatory last entry:
368 RolesCount
369 };
370
371 struct ItemData {
372 KFileItem item;
373 QHash<QByteArray, QVariant> values;
374 ItemData *parent;
375 };
376
377 struct ItemGroupInfo {
378 int comparable;
379 QString text;
380
381 bool operator==(const ItemGroupInfo &other) const;
382 bool operator!=(const ItemGroupInfo &other) const;
383 bool operator<(const ItemGroupInfo &other) const;
384 };
385
386 enum RemoveItemsBehavior { KeepItemData, DeleteItemData, DeleteItemDataIfUnfiltered };
387
388 void insertItems(QList<ItemData *> &items);
389 void removeItems(const KItemRangeList &itemRanges, RemoveItemsBehavior behavior);
390
391 /**
392 * Helper method for insertItems() and removeItems(): Creates
393 * a list of ItemData elements based on the given items.
394 * Note that the ItemData instances are created dynamically and
395 * must be deleted by the caller.
396 */
397 QList<ItemData *> createItemDataList(const QUrl &parentUrl, const KFileItemList &items) const;
398
399 /**
400 * Prepares the items for sorting. Normally, the hash 'values' in ItemData is filled
401 * lazily to save time and memory, but for some sort roles, it is expected that the
402 * sort role data is stored in 'values'.
403 */
404 void prepareItemsForSorting(QList<ItemData *> &itemDataList);
405
406 static int expandedParentsCount(const ItemData *data);
407
408 void removeExpandedItems();
409
410 /**
411 * This function is called by setData() and slotRefreshItems(). It emits
412 * the itemsChanged() signal, checks if the sort order is still correct,
413 * and starts m_resortAllItemsTimer if that is not the case.
414 */
415 void emitItemsChangedAndTriggerResorting(const KItemRangeList &itemRanges, const QSet<QByteArray> &changedRoles);
416
417 /**
418 * Resets all values from m_requestRole to false.
419 */
420 void resetRoles();
421
422 /**
423 * @return Role-type for the given role.
424 * Runtime complexity is O(1).
425 */
426 RoleType typeForRole(const QByteArray &role) const;
427
428 /**
429 * @return Role-byte-array for the given role-type.
430 * Runtime complexity is O(1).
431 */
432 QByteArray roleForType(RoleType roleType) const;
433
434 QHash<QByteArray, QVariant> retrieveData(const KFileItem &item, const ItemData *parent) const;
435
436 /**
437 * @return True if role values benefit from natural or case insensitive sorting.
438 */
439 static bool isRoleValueNatural(const RoleType roleType);
440
441 /**
442 * @return True if \a a has a KFileItem whose text is 'less than' the one
443 * of \a b according to QString::operator<(const QString&).
444 */
445 static bool nameLessThan(const ItemData *a, const ItemData *b);
446
447 /**
448 * @return True if the item-data \a a should be ordered before the item-data
449 * \b. The item-data may have different parent-items.
450 */
451 bool lessThan(const ItemData *a, const ItemData *b, const QCollator &collator) const;
452
453 /**
454 * Sorts the items between \a begin and \a end using the comparison
455 * function lessThan().
456 */
457 void sort(const QList<ItemData *>::iterator &begin, const QList<ItemData *>::iterator &end) const;
458
459 /**
460 * Helper method for lessThan() and expandedParentsCountCompare(): Compares
461 * the passed item-data using m_sortRole as criteria. Both items must
462 * have the same parent item, otherwise the comparison will be wrong.
463 */
464 int sortRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const;
465
466 /**
467 * Helper method for lessThan() and expandedParentsCountCompare(): Compares
468 * the passed item-data using m_groupRole as criteria. Both items must
469 * have the same parent item, otherwise the comparison will be wrong.
470 */
471 int groupRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const;
472
473 int stringCompare(const QString &a, const QString &b, const QCollator &collator) const;
474
475 ItemGroupInfo nameRoleGroup(const ItemData *itemData, bool withString = true) const;
476 ItemGroupInfo sizeRoleGroup(const ItemData *itemData, bool withString = true) const;
477 ItemGroupInfo timeRoleGroup(const std::function<QDateTime(const ItemData *)> &fileTimeCb, const ItemData *itemData, bool withString = true) const;
478 ItemGroupInfo permissionRoleGroup(const ItemData *itemData, bool withString = true) const;
479 ItemGroupInfo ratingRoleGroup(const ItemData *itemData, bool withString = true) const;
480 QString genericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const;
481
482 QList<QPair<int, QVariant>> nameRoleGroups() const;
483 QList<QPair<int, QVariant>> sizeRoleGroups() const;
484 QList<QPair<int, QVariant>> timeRoleGroups(const std::function<QDateTime(const ItemData *)> &fileTimeCb) const;
485 QList<QPair<int, QVariant>> permissionRoleGroups() const;
486 QList<QPair<int, QVariant>> ratingRoleGroups() const;
487 QList<QPair<int, QVariant>> genericStringRoleGroups(const QByteArray &typeForRole) const;
488
489 /**
490 * Helper method for all xxxRoleGroups() methods to check whether the
491 * item with the given index is a child-item. A child-item is defined
492 * as item having an expansion-level > 0. All xxxRoleGroups() methods
493 * should skip the grouping if the item is a child-item (although
494 * KItemListView would be capable to show sub-groups in groups this
495 * results in visual clutter for most usecases).
496 */
497 bool isChildItem(int index) const;
498
499 void scheduleResortAllItems();
500
501 /**
502 * Is invoked by KFileItemModelRolesUpdater and results in emitting the
503 * sortProgress signal with a percent-value of the progress.
504 */
505 void emitSortProgress(int resolvedCount);
506
507 /**
508 * Applies the filters set through @ref setNameFilter and @ref setMimeTypeFilters.
509 */
510 void applyFilters();
511
512 /**
513 * Removes filtered items whose expanded parents have been deleted
514 * or collapsed via setExpanded(parentIndex, false).
515 */
516 void removeFilteredChildren(const KItemRangeList &parents);
517
518 /**
519 * Loads the selected choice of sorting method from Dolphin General Settings
520 */
521 void loadSortingSettings();
522
523 /**
524 * Maps the QByteArray-roles to RoleTypes and provides translation- and
525 * group-contexts.
526 */
527 struct RoleInfoMap {
528 const char *const role;
529 const RoleType roleType;
530 const KLazyLocalizedString roleTranslation;
531 const KLazyLocalizedString groupTranslation;
532 const KLazyLocalizedString tooltipTranslation;
533 const bool requiresBaloo;
534 const bool requiresIndexer;
535 };
536
537 /**
538 * @return Map of user visible roles that are accessible by KFileItemModel::rolesInformation().
539 */
540 static const RoleInfoMap *rolesInfoMap(int &count);
541
542 /**
543 * Determines the MIME-types of all items that can be done within
544 * the given timeout.
545 */
546 static void determineMimeTypes(const KFileItemList &items, int timeout);
547
548 /**
549 * @return Returns a copy of \a value that is implicitly shared
550 * with other users to save memory.
551 */
552 static QByteArray sharedValue(const QByteArray &value);
553
554 /**
555 * Checks if the model's internal data structures are consistent.
556 */
557 bool isConsistent() const;
558
559 /**
560 * Filters out the expanded folders that don't pass the filter themselves and don't have any filter-passing children.
561 * Will update the removedItemRanges arguments to include the parents that have been filtered.
562 * @returns the number of parents that have been filtered.
563 * @param removedItemRanges The ranges of items being deleted/filtered, will get updated
564 * @param parentsToEnsureVisible Parents that must be visible no matter what due to being ancestors of newly visible items
565 */
566 int filterChildlessParents(KItemRangeList &removedItemRanges, const QSet<ItemData *> &parentsToEnsureVisible = QSet<ItemData *>());
567
568 private:
569 KDirLister *m_dirLister = nullptr;
570
571 QCollator m_collator;
572 bool m_naturalSorting;
573 bool m_sortDirsFirst;
574 bool m_sortHiddenLast;
575
576 RoleType m_sortRole;
577 RoleType m_groupRole;
578 QByteArray m_sortExtraInfo;
579 QByteArray m_groupExtraInfo;
580
581 int m_sortingProgressPercent; // Value of directorySortingProgress() signal
582 QSet<QByteArray> m_roles;
583
584 QList<ItemData *> m_itemData;
585
586 // m_items is a cache for the method index(const QUrl&). If it contains N
587 // entries, it is guaranteed that these correspond to the first N items in
588 // the model, i.e., that (for every i between 0 and N - 1)
589 // m_items.value(fileItem(i).url()) == i
590 mutable QHash<QUrl, int> m_items;
591
592 KFileItemModelFilter m_filter;
593 QHash<KFileItem, ItemData *> m_filteredItems; // Items that got hidden by KFileItemModel::setNameFilter()
594
595 bool m_requestRole[RolesCount];
596
597 QTimer *m_maximumUpdateIntervalTimer;
598 QTimer *m_resortAllItemsTimer;
599 QList<ItemData *> m_pendingItemsToInsert;
600
601 // Cache for KFileItemModel::groups()
602 mutable QList<QPair<int, QVariant>> m_groups;
603
604 // Stores the URLs (key: target url, value: url) of the expanded directories.
605 QHash<QUrl, QUrl> m_expandedDirs;
606
607 // URLs that must be expanded. The expanding is initially triggered in setExpanded()
608 // and done step after step in slotCompleted().
609 QSet<QUrl> m_urlsToExpand;
610
611 friend class KFileItemModelRolesUpdater; // Accesses emitSortProgress() method
612 friend class KFileItemModelTest; // For unit testing
613 friend class KFileItemModelBenchmark; // For unit testing
614 friend class KFileItemListViewTest; // For unit testing
615 friend class DolphinPart; // Accesses m_dirLister
616 };
617
618 inline bool KFileItemModel::isRoleValueNatural(RoleType roleType)
619 {
620 return (roleType == TypeRole || roleType == ExtensionRole || roleType == TagsRole || roleType == CommentRole || roleType == TitleRole
621 || roleType == ArtistRole || roleType == GenreRole || roleType == AlbumRole || roleType == PathRole || roleType == DestinationRole
622 || roleType == OriginUrlRole || roleType == OwnerRole || roleType == GroupRole);
623 }
624
625 inline bool KFileItemModel::nameLessThan(const ItemData *a, const ItemData *b)
626 {
627 return a->item.text() < b->item.text();
628 }
629
630 inline bool KFileItemModel::isChildItem(int index) const
631 {
632 if (m_itemData.at(index)->parent) {
633 return true;
634 } else {
635 return false;
636 }
637 }
638
639 inline bool KFileItemModel::ItemGroupInfo::operator==(const ItemGroupInfo &other) const
640 {
641 return comparable == other.comparable && text == other.text;
642 }
643
644 inline bool KFileItemModel::ItemGroupInfo::operator!=(const ItemGroupInfo &other) const
645 {
646 return comparable != other.comparable || text != other.text;
647 }
648
649 inline bool KFileItemModel::ItemGroupInfo::operator<(const ItemGroupInfo &other) const
650 {
651 if (comparable == other.comparable) {
652 return text < other.text;
653 } else {
654 return comparable < other.comparable;
655 }
656 }
657
658 #endif