]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemmodel.h
Fixed crashing when initial grouping is by size. Fixed size grouping ignoring directo...
[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 ItemGroupInfo typeRoleGroup(const ItemData *itemData) const;
481 ItemGroupInfo genericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const;
482
483 QList<QPair<int, QVariant>> nameRoleGroups() const;
484 QList<QPair<int, QVariant>> sizeRoleGroups() const;
485 QList<QPair<int, QVariant>> timeRoleGroups(const std::function<QDateTime(const ItemData *)> &fileTimeCb) const;
486 QList<QPair<int, QVariant>> permissionRoleGroups() const;
487 QList<QPair<int, QVariant>> ratingRoleGroups() const;
488 QList<QPair<int, QVariant>> typeRoleGroups() const;
489 QList<QPair<int, QVariant>> genericStringRoleGroups(const QByteArray &typeForRole) const;
490
491 /**
492 * Helper method for all xxxRoleGroups() methods to check whether the
493 * item with the given index is a child-item. A child-item is defined
494 * as item having an expansion-level > 0. All xxxRoleGroups() methods
495 * should skip the grouping if the item is a child-item (although
496 * KItemListView would be capable to show sub-groups in groups this
497 * results in visual clutter for most usecases).
498 */
499 bool isChildItem(int index) const;
500
501 void scheduleResortAllItems();
502
503 /**
504 * Is invoked by KFileItemModelRolesUpdater and results in emitting the
505 * sortProgress signal with a percent-value of the progress.
506 */
507 void emitSortProgress(int resolvedCount);
508
509 /**
510 * Applies the filters set through @ref setNameFilter and @ref setMimeTypeFilters.
511 */
512 void applyFilters();
513
514 /**
515 * Removes filtered items whose expanded parents have been deleted
516 * or collapsed via setExpanded(parentIndex, false).
517 */
518 void removeFilteredChildren(const KItemRangeList &parents);
519
520 /**
521 * Loads the selected choice of sorting method from Dolphin General Settings
522 */
523 void loadSortingSettings();
524
525 /**
526 * Maps the QByteArray-roles to RoleTypes and provides translation- and
527 * group-contexts.
528 */
529 struct RoleInfoMap {
530 const char *const role;
531 const RoleType roleType;
532 const KLazyLocalizedString roleTranslation;
533 const KLazyLocalizedString groupTranslation;
534 const KLazyLocalizedString tooltipTranslation;
535 const bool requiresBaloo;
536 const bool requiresIndexer;
537 };
538
539 /**
540 * @return Map of user visible roles that are accessible by KFileItemModel::rolesInformation().
541 */
542 static const RoleInfoMap *rolesInfoMap(int &count);
543
544 /**
545 * Determines the MIME-types of all items that can be done within
546 * the given timeout.
547 */
548 static void determineMimeTypes(const KFileItemList &items, int timeout);
549
550 /**
551 * @return Returns a copy of \a value that is implicitly shared
552 * with other users to save memory.
553 */
554 static QByteArray sharedValue(const QByteArray &value);
555
556 /**
557 * Checks if the model's internal data structures are consistent.
558 */
559 bool isConsistent() const;
560
561 /**
562 * Filters out the expanded folders that don't pass the filter themselves and don't have any filter-passing children.
563 * Will update the removedItemRanges arguments to include the parents that have been filtered.
564 * @returns the number of parents that have been filtered.
565 * @param removedItemRanges The ranges of items being deleted/filtered, will get updated
566 * @param parentsToEnsureVisible Parents that must be visible no matter what due to being ancestors of newly visible items
567 */
568 int filterChildlessParents(KItemRangeList &removedItemRanges, const QSet<ItemData *> &parentsToEnsureVisible = QSet<ItemData *>());
569
570 private:
571 KDirLister *m_dirLister = nullptr;
572
573 QCollator m_collator;
574 bool m_naturalSorting;
575 bool m_sortDirsFirst;
576 bool m_sortHiddenLast;
577 int m_dirSizeMode;
578
579 RoleType m_sortRole;
580 RoleType m_groupRole;
581 QByteArray m_sortExtraInfo;
582 QByteArray m_groupExtraInfo;
583
584 int m_sortingProgressPercent; // Value of directorySortingProgress() signal
585 QSet<QByteArray> m_roles;
586
587 QList<ItemData *> m_itemData;
588
589 // m_items is a cache for the method index(const QUrl&). If it contains N
590 // entries, it is guaranteed that these correspond to the first N items in
591 // the model, i.e., that (for every i between 0 and N - 1)
592 // m_items.value(fileItem(i).url()) == i
593 mutable QHash<QUrl, int> m_items;
594
595 KFileItemModelFilter m_filter;
596 QHash<KFileItem, ItemData *> m_filteredItems; // Items that got hidden by KFileItemModel::setNameFilter()
597
598 bool m_requestRole[RolesCount];
599
600 QTimer *m_maximumUpdateIntervalTimer;
601 QTimer *m_resortAllItemsTimer;
602 QList<ItemData *> m_pendingItemsToInsert;
603
604 // Cache for KFileItemModel::groups()
605 mutable QList<QPair<int, QVariant>> m_groups;
606
607 // Stores the URLs (key: target url, value: url) of the expanded directories.
608 QHash<QUrl, QUrl> m_expandedDirs;
609
610 // URLs that must be expanded. The expanding is initially triggered in setExpanded()
611 // and done step after step in slotCompleted().
612 QSet<QUrl> m_urlsToExpand;
613
614 friend class KFileItemModelRolesUpdater; // Accesses emitSortProgress() method
615 friend class KFileItemModelTest; // For unit testing
616 friend class KFileItemModelBenchmark; // For unit testing
617 friend class KFileItemListViewTest; // For unit testing
618 friend class DolphinPart; // Accesses m_dirLister
619 };
620
621 inline bool KFileItemModel::isRoleValueNatural(RoleType roleType)
622 {
623 return (roleType == TypeRole || roleType == ExtensionRole || roleType == TagsRole || roleType == CommentRole || roleType == TitleRole
624 || roleType == ArtistRole || roleType == GenreRole || roleType == AlbumRole || roleType == PathRole || roleType == DestinationRole
625 || roleType == OriginUrlRole || roleType == OwnerRole || roleType == GroupRole);
626 }
627
628 inline bool KFileItemModel::nameLessThan(const ItemData *a, const ItemData *b)
629 {
630 return a->item.text() < b->item.text();
631 }
632
633 inline bool KFileItemModel::isChildItem(int index) const
634 {
635 if (m_itemData.at(index)->parent) {
636 return true;
637 } else {
638 return false;
639 }
640 }
641
642 inline bool KFileItemModel::ItemGroupInfo::operator==(const ItemGroupInfo &other) const
643 {
644 return comparable == other.comparable && text == other.text;
645 }
646
647 inline bool KFileItemModel::ItemGroupInfo::operator!=(const ItemGroupInfo &other) const
648 {
649 return comparable != other.comparable || text != other.text;
650 }
651
652 #endif