]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemmodel.h
Merge remote-tracking branch 'fork/work/zakharafoniam/useful-groups'
[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 /**
215 * @return Provides static information for all available grouping
216 * behaviors supported by KFileItemModel but not directly
217 * mapped to roles of KFileItemModel.
218 */
219 static QList<RoleInfo> extraGroupingInformation();
220
221 /** set to true to hide application/x-trash files */
222 void setShowTrashMime(bool show);
223
224 Q_SIGNALS:
225 /**
226 * Is emitted if the loading of a directory has been started. It is
227 * assured that a signal directoryLoadingCompleted() will be send after
228 * the loading has been finished. For tracking the loading progress
229 * the signal directoryLoadingProgress() gets emitted in between.
230 */
231 void directoryLoadingStarted();
232
233 /**
234 * Is emitted after the loading of a directory has been completed or new
235 * items have been inserted to an already loaded directory. Usually
236 * one or more itemsInserted() signals are emitted before loadingCompleted()
237 * (the only exception is loading an empty directory, where only a
238 * loadingCompleted() signal gets emitted).
239 */
240 void directoryLoadingCompleted();
241
242 /**
243 * Is emitted when the model is being refreshed (F5 key press)
244 */
245 void directoryRefreshing();
246
247 /**
248 * Is emitted after the loading of a directory has been canceled.
249 */
250 void directoryLoadingCanceled();
251
252 /**
253 * Informs about the progress in percent when loading a directory. It is assured
254 * that the signal directoryLoadingStarted() has been emitted before.
255 */
256 void directoryLoadingProgress(int percent);
257
258 /**
259 * Is emitted if the sort-role gets resolved asynchronously and provides
260 * the progress-information of the sorting in percent. It is assured
261 * that the last sortProgress-signal contains 100 as value.
262 */
263 void directorySortingProgress(int percent);
264
265 /**
266 * Is emitted if an information message (e.g. "Connecting to host...")
267 * should be shown.
268 */
269 void infoMessage(const QString &message);
270
271 /**
272 * Is emitted if an error message (e.g. "Unknown location")
273 * should be shown.
274 */
275 void errorMessage(const QString &message, const int kioErrorCode);
276
277 /**
278 * Is emitted if a redirection from the current URL \a oldUrl
279 * to the new URL \a newUrl has been done.
280 */
281 void directoryRedirection(const QUrl &oldUrl, const QUrl &newUrl);
282
283 /**
284 * Is emitted when the URL passed by KFileItemModel::setUrl() represents a file.
285 * In this case no signal errorMessage() will be emitted.
286 */
287 void urlIsFileError(const QUrl &url);
288
289 /**
290 * It is emitted for files when they change and
291 * for dirs when files are added or removed.
292 */
293 void fileItemsChanged(const KFileItemList &changedFileItems);
294
295 /**
296 * It is emitted when the parent directory was removed.
297 */
298 void currentDirectoryRemoved();
299
300 protected:
301 void onGroupedSortingChanged(bool current) override;
302 void onSortRoleChanged(const QByteArray &current, const QByteArray &previous, bool resortItems = true) override;
303 void onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) override;
304 void onGroupRoleChanged(const QByteArray &current, const QByteArray &previous, bool resortItems = true) override;
305 void onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) override;
306
307 private Q_SLOTS:
308 /**
309 * Resorts all items dependent on the set sortRole(), sortOrder(),
310 * groupRole(), groupOrder() and foldersFirst() settings.
311 */
312 void resortAllItems();
313
314 void slotCompleted();
315 void slotCanceled();
316 void slotItemsAdded(const QUrl &directoryUrl, const KFileItemList &items);
317 void slotItemsDeleted(const KFileItemList &items);
318 void slotRefreshItems(const QList<QPair<KFileItem, KFileItem>> &items);
319 void slotClear();
320 void slotSortingChoiceChanged();
321 void slotListerError(KIO::Job *job);
322
323 void dispatchPendingItemsToInsert();
324
325 private:
326 enum RoleType {
327 // User visible roles:
328 NoRole,
329 NameRole,
330 SizeRole,
331 ModificationTimeRole,
332 CreationTimeRole,
333 AccessTimeRole,
334 PermissionsRole,
335 OwnerRole,
336 GroupRole,
337 TypeRole,
338 ExtensionRole,
339 DestinationRole,
340 PathRole,
341 DeletionTimeRole,
342 // User visible roles available with Baloo:
343 CommentRole,
344 TagsRole,
345 RatingRole,
346 DimensionsRole,
347 WidthRole,
348 HeightRole,
349 ImageDateTimeRole,
350 OrientationRole,
351 PublisherRole,
352 PageCountRole,
353 WordCountRole,
354 TitleRole,
355 AuthorRole,
356 LineCountRole,
357 ArtistRole,
358 GenreRole,
359 AlbumRole,
360 DurationRole,
361 TrackRole,
362 ReleaseYearRole,
363 BitrateRole,
364 OriginUrlRole,
365 AspectRatioRole,
366 FrameRateRole,
367 // Non-visible roles:
368 IsDirRole,
369 IsLinkRole,
370 IsHiddenRole,
371 IsExpandedRole,
372 IsExpandableRole,
373 ExpandedParentsCountRole,
374 // Mandatory last entry:
375 RolesCount
376 };
377
378 struct ItemData {
379 KFileItem item;
380 QHash<QByteArray, QVariant> values;
381 ItemData *parent;
382 };
383
384 struct ItemGroupInfo {
385 int comparable;
386 QString text;
387
388 bool operator==(const ItemGroupInfo &other) const;
389 bool operator!=(const ItemGroupInfo &other) const;
390 bool operator<(const ItemGroupInfo &other) const;
391 };
392
393 enum RemoveItemsBehavior { KeepItemData, DeleteItemData, DeleteItemDataIfUnfiltered };
394
395 void insertItems(QList<ItemData *> &items);
396 void removeItems(const KItemRangeList &itemRanges, RemoveItemsBehavior behavior);
397
398 /**
399 * Helper method for insertItems() and removeItems(): Creates
400 * a list of ItemData elements based on the given items.
401 * Note that the ItemData instances are created dynamically and
402 * must be deleted by the caller.
403 */
404 QList<ItemData *> createItemDataList(const QUrl &parentUrl, const KFileItemList &items) const;
405
406 /**
407 * Helper method for prepareItemsForSorting().
408 * For a set role, fills 'values' of ItemData non-lazily.
409 */
410 void prepareItemsWithRole(QList<ItemData *> &itemDataList, RoleType roleType);
411
412 /**
413 * Prepares the items for sorting. Normally, the hash 'values' in ItemData is filled
414 * lazily to save time and memory, but for some sort roles, it is expected that the
415 * sort role data is stored in 'values'.
416 */
417 void prepareItemsForSorting(QList<ItemData *> &itemDataList);
418
419 static int expandedParentsCount(const ItemData *data);
420
421 void removeExpandedItems();
422
423 /**
424 * This function is called by setData() and slotRefreshItems(). It emits
425 * the itemsChanged() signal, checks if the sort order is still correct,
426 * and starts m_resortAllItemsTimer if that is not the case.
427 */
428 void emitItemsChangedAndTriggerResorting(const KItemRangeList &itemRanges, const QSet<QByteArray> &changedRoles);
429
430 /**
431 * Resets all values from m_requestRole to false.
432 */
433 void resetRoles();
434
435 /**
436 * @return Role-type for the given role.
437 * Runtime complexity is O(1).
438 */
439 RoleType typeForRole(const QByteArray &role) const;
440
441 /**
442 * @return Role-byte-array for the given role-type.
443 * Runtime complexity is O(1).
444 */
445 QByteArray roleForType(RoleType roleType) const;
446
447 QHash<QByteArray, QVariant> retrieveData(const KFileItem &item, const ItemData *parent) const;
448
449 /**
450 * @return True if role values benefit from natural or case insensitive sorting.
451 */
452 static bool isRoleValueNatural(const RoleType roleType);
453
454 /**
455 * @return True if \a a has a KFileItem whose text is 'less than' the one
456 * of \a b according to QString::operator<(const QString&).
457 */
458 static bool nameLessThan(const ItemData *a, const ItemData *b);
459
460 /**
461 * @return True if the item-data \a a should be ordered before the item-data
462 * \b. The item-data may have different parent-items.
463 */
464 bool lessThan(const ItemData *a, const ItemData *b, const QCollator &collator) const;
465
466 /**
467 * Sorts the items between \a begin and \a end using the comparison
468 * function lessThan().
469 */
470 void sort(const QList<ItemData *>::iterator &begin, const QList<ItemData *>::iterator &end) const;
471
472 /**
473 * Helper method for lessThan() and expandedParentsCountCompare(): Compares
474 * the passed item-data using m_sortRole as criteria. Both items must
475 * have the same parent item, otherwise the comparison will be wrong.
476 */
477 int sortRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const;
478
479 /**
480 * Helper method for lessThan() and expandedParentsCountCompare(): Compares
481 * the passed item-data using m_groupRole as criteria. Both items must
482 * have the same parent item, otherwise the comparison will be wrong.
483 */
484 int groupRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const;
485
486 int stringCompare(const QString &a, const QString &b, const QCollator &collator) const;
487
488 ItemGroupInfo nameRoleGroup(const ItemData *itemData, bool withString = true) const;
489 ItemGroupInfo sizeRoleGroup(const ItemData *itemData, bool withString = true) const;
490 ItemGroupInfo timeRoleGroup(const std::function<QDateTime(const ItemData *)> &fileTimeCb, const ItemData *itemData, bool withString = true) const;
491 ItemGroupInfo permissionRoleGroup(const ItemData *itemData, bool withString = true) const;
492 ItemGroupInfo ratingRoleGroup(const ItemData *itemData, bool withString = true) const;
493 ItemGroupInfo typeRoleGroup(const ItemData *itemData) const;
494 ItemGroupInfo genericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const;
495
496 QList<QPair<int, QVariant>> nameRoleGroups() const;
497 QList<QPair<int, QVariant>> sizeRoleGroups() const;
498 QList<QPair<int, QVariant>> timeRoleGroups(const std::function<QDateTime(const ItemData *)> &fileTimeCb) const;
499 QList<QPair<int, QVariant>> permissionRoleGroups() const;
500 QList<QPair<int, QVariant>> ratingRoleGroups() const;
501 QList<QPair<int, QVariant>> typeRoleGroups() const;
502 QList<QPair<int, QVariant>> genericStringRoleGroups(const QByteArray &typeForRole) const;
503
504 /**
505 * Helper method for all xxxRoleGroups() methods to check whether the
506 * item with the given index is a child-item. A child-item is defined
507 * as item having an expansion-level > 0. All xxxRoleGroups() methods
508 * should skip the grouping if the item is a child-item (although
509 * KItemListView would be capable to show sub-groups in groups this
510 * results in visual clutter for most usecases).
511 */
512 bool isChildItem(int index) const;
513
514 void scheduleResortAllItems();
515
516 /**
517 * Is invoked by KFileItemModelRolesUpdater and results in emitting the
518 * sortProgress signal with a percent-value of the progress.
519 */
520 void emitSortProgress(int resolvedCount);
521
522 /**
523 * Applies the filters set through @ref setNameFilter and @ref setMimeTypeFilters.
524 */
525 void applyFilters();
526
527 /**
528 * Removes filtered items whose expanded parents have been deleted
529 * or collapsed via setExpanded(parentIndex, false).
530 */
531 void removeFilteredChildren(const KItemRangeList &parents);
532
533 /**
534 * Loads the selected choice of sorting method from Dolphin General Settings
535 */
536 void loadSortingSettings();
537
538 /**
539 * Maps the QByteArray-roles to RoleTypes and provides translation- and
540 * group-contexts.
541 */
542 struct RoleInfoMap {
543 const char *const role;
544 const RoleType roleType;
545 const KLazyLocalizedString roleTranslation;
546 const KLazyLocalizedString groupTranslation;
547 const KLazyLocalizedString tooltipTranslation;
548 const bool requiresBaloo;
549 const bool requiresIndexer;
550 };
551
552 /**
553 * @return Map of user visible roles that are accessible by KFileItemModel::rolesInformation().
554 */
555 static const RoleInfoMap *rolesInfoMap(int &count);
556
557 /**
558 * Determines the MIME-types of all items that can be done within
559 * the given timeout.
560 */
561 static void determineMimeTypes(const KFileItemList &items, int timeout);
562
563 /**
564 * @return Returns a copy of \a value that is implicitly shared
565 * with other users to save memory.
566 */
567 static QByteArray sharedValue(const QByteArray &value);
568
569 /**
570 * Checks if the model's internal data structures are consistent.
571 */
572 bool isConsistent() const;
573
574 /**
575 * Filters out the expanded folders that don't pass the filter themselves and don't have any filter-passing children.
576 * Will update the removedItemRanges arguments to include the parents that have been filtered.
577 * @returns the number of parents that have been filtered.
578 * @param removedItemRanges The ranges of items being deleted/filtered, will get updated
579 * @param parentsToEnsureVisible Parents that must be visible no matter what due to being ancestors of newly visible items
580 */
581 int filterChildlessParents(KItemRangeList &removedItemRanges, const QSet<ItemData *> &parentsToEnsureVisible = QSet<ItemData *>());
582
583 private:
584 KDirLister *m_dirLister = nullptr;
585
586 QCollator m_collator;
587 bool m_naturalSorting;
588 bool m_sortDirsFirst;
589 bool m_sortHiddenLast;
590
591 RoleType m_sortRole;
592 RoleType m_groupRole;
593 QByteArray m_sortExtraInfo;
594 QByteArray m_groupExtraInfo;
595
596 int m_sortingProgressPercent; // Value of directorySortingProgress() signal
597 QSet<QByteArray> m_roles;
598
599 QList<ItemData *> m_itemData;
600
601 // m_items is a cache for the method index(const QUrl&). If it contains N
602 // entries, it is guaranteed that these correspond to the first N items in
603 // the model, i.e., that (for every i between 0 and N - 1)
604 // m_items.value(fileItem(i).url()) == i
605 mutable QHash<QUrl, int> m_items;
606
607 KFileItemModelFilter m_filter;
608 QHash<KFileItem, ItemData *> m_filteredItems; // Items that got hidden by KFileItemModel::setNameFilter()
609
610 bool m_requestRole[RolesCount];
611
612 QTimer *m_maximumUpdateIntervalTimer;
613 QTimer *m_resortAllItemsTimer;
614 QList<ItemData *> m_pendingItemsToInsert;
615
616 // Cache for KFileItemModel::groups()
617 mutable QList<QPair<int, QVariant>> m_groups;
618
619 // Stores the URLs (key: target url, value: url) of the expanded directories.
620 QHash<QUrl, QUrl> m_expandedDirs;
621
622 // URLs that must be expanded. The expanding is initially triggered in setExpanded()
623 // and done step after step in slotCompleted().
624 QSet<QUrl> m_urlsToExpand;
625
626 friend class KFileItemModelRolesUpdater; // Accesses emitSortProgress() method
627 friend class KFileItemModelTest; // For unit testing
628 friend class KFileItemModelBenchmark; // For unit testing
629 friend class KFileItemListViewTest; // For unit testing
630 friend class DolphinPart; // Accesses m_dirLister
631 };
632
633 inline bool KFileItemModel::isRoleValueNatural(RoleType roleType)
634 {
635 return (roleType == TypeRole || roleType == ExtensionRole || roleType == TagsRole || roleType == CommentRole || roleType == TitleRole
636 || roleType == ArtistRole || roleType == GenreRole || roleType == AlbumRole || roleType == PathRole || roleType == DestinationRole
637 || roleType == OriginUrlRole || roleType == OwnerRole || roleType == GroupRole);
638 }
639
640 inline bool KFileItemModel::nameLessThan(const ItemData *a, const ItemData *b)
641 {
642 // Split extension, taking into account it can be empty
643 constexpr QString::SectionFlags flags = QString::SectionSkipEmpty | QString::SectionIncludeLeadingSep;
644 return a->item.text().section('.', 0, 0, flags) < b->item.text().section('.', 0, 0, flags);
645 }
646
647 inline bool KFileItemModel::isChildItem(int index) const
648 {
649 if (m_itemData.at(index)->parent) {
650 return true;
651 } else {
652 return false;
653 }
654 }
655
656 inline bool KFileItemModel::ItemGroupInfo::operator==(const ItemGroupInfo &other) const
657 {
658 return comparable == other.comparable && text == other.text;
659 }
660
661 inline bool KFileItemModel::ItemGroupInfo::operator!=(const ItemGroupInfo &other) const
662 {
663 return comparable != other.comparable || text != other.text;
664 }
665
666 #endif