]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemmodel.h
Reverted resortAllItems() in favor of a group comparator for lessThan. Minor bug...
[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 enum RemoveItemsBehavior { KeepItemData, DeleteItemData, DeleteItemDataIfUnfiltered };
378
379 void insertItems(QList<ItemData *> &items);
380 void removeItems(const KItemRangeList &itemRanges, RemoveItemsBehavior behavior);
381
382 /**
383 * Helper method for insertItems() and removeItems(): Creates
384 * a list of ItemData elements based on the given items.
385 * Note that the ItemData instances are created dynamically and
386 * must be deleted by the caller.
387 */
388 QList<ItemData *> createItemDataList(const QUrl &parentUrl, const KFileItemList &items) const;
389
390 /**
391 * Prepares the items for sorting. Normally, the hash 'values' in ItemData is filled
392 * lazily to save time and memory, but for some sort roles, it is expected that the
393 * sort role data is stored in 'values'.
394 */
395 void prepareItemsForSorting(QList<ItemData *> &itemDataList);
396
397 static int expandedParentsCount(const ItemData *data);
398
399 void removeExpandedItems();
400
401 /**
402 * This function is called by setData() and slotRefreshItems(). It emits
403 * the itemsChanged() signal, checks if the sort order is still correct,
404 * and starts m_resortAllItemsTimer if that is not the case.
405 */
406 void emitItemsChangedAndTriggerResorting(const KItemRangeList &itemRanges, const QSet<QByteArray> &changedRoles);
407
408 /**
409 * Resets all values from m_requestRole to false.
410 */
411 void resetRoles();
412
413 /**
414 * @return Role-type for the given role.
415 * Runtime complexity is O(1).
416 */
417 RoleType typeForRole(const QByteArray &role) const;
418
419 /**
420 * @return Role-byte-array for the given role-type.
421 * Runtime complexity is O(1).
422 */
423 QByteArray roleForType(RoleType roleType) const;
424
425 QHash<QByteArray, QVariant> retrieveData(const KFileItem &item, const ItemData *parent) const;
426
427 /**
428 * @return True if role values benefit from natural or case insensitive sorting.
429 */
430 static bool isRoleValueNatural(const RoleType roleType);
431
432 /**
433 * @return True if \a a has a KFileItem whose text is 'less than' the one
434 * of \a b according to QString::operator<(const QString&).
435 */
436 static bool nameLessThan(const ItemData *a, const ItemData *b);
437
438 /**
439 * @return True if the item-data \a a should be ordered before the item-data
440 * \b. The item-data may have different parent-items.
441 */
442 bool lessThan(const ItemData *a, const ItemData *b, const QCollator &collator) const;
443
444 /**
445 * Sorts the items between \a begin and \a end using the comparison
446 * function lessThan().
447 */
448 void sort(const QList<ItemData *>::iterator &begin, const QList<ItemData *>::iterator &end) const;
449
450 /**
451 * Helper method for lessThan() and expandedParentsCountCompare(): Compares
452 * the passed item-data using m_sortRole as criteria. Both items must
453 * have the same parent item, otherwise the comparison will be wrong.
454 */
455 int sortRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const;
456
457 /**
458 * Helper method for lessThan() and expandedParentsCountCompare(): Compares
459 * the passed item-data using m_groupRole as criteria. Both items must
460 * have the same parent item, otherwise the comparison will be wrong.
461 */
462 int groupRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const;
463
464 int stringCompare(const QString &a, const QString &b, const QCollator &collator) const;
465
466 QVariant getNameRoleGroup(const ItemData *itemData, bool asString = true) const;
467 QVariant getSizeRoleGroup(const ItemData *itemData, bool asString = true) const;
468 QVariant getTimeRoleGroup(const std::function<QDateTime(const ItemData *)> &fileTimeCb, const ItemData *itemData, bool asString = true) const;
469 QVariant getPermissionRoleGroup(const ItemData *itemData, bool asString = true) const;
470 QVariant getRatingRoleGroup(const ItemData *itemData, bool asString = true) const;
471 QString getGenericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const;
472
473 QList<QPair<int, QVariant>> nameRoleGroups() const;
474 QList<QPair<int, QVariant>> sizeRoleGroups() const;
475 QList<QPair<int, QVariant>> timeRoleGroups(const std::function<QDateTime(const ItemData *)> &fileTimeCb) const;
476 QList<QPair<int, QVariant>> permissionRoleGroups() const;
477 QList<QPair<int, QVariant>> ratingRoleGroups() const;
478 QList<QPair<int, QVariant>> genericStringRoleGroups(const QByteArray &typeForRole) const;
479
480 /**
481 * Helper method for all xxxRoleGroups() methods to check whether the
482 * item with the given index is a child-item. A child-item is defined
483 * as item having an expansion-level > 0. All xxxRoleGroups() methods
484 * should skip the grouping if the item is a child-item (although
485 * KItemListView would be capable to show sub-groups in groups this
486 * results in visual clutter for most usecases).
487 */
488 bool isChildItem(int index) const;
489
490 void scheduleResortAllItems();
491
492 /**
493 * Is invoked by KFileItemModelRolesUpdater and results in emitting the
494 * sortProgress signal with a percent-value of the progress.
495 */
496 void emitSortProgress(int resolvedCount);
497
498 /**
499 * Applies the filters set through @ref setNameFilter and @ref setMimeTypeFilters.
500 */
501 void applyFilters();
502
503 /**
504 * Removes filtered items whose expanded parents have been deleted
505 * or collapsed via setExpanded(parentIndex, false).
506 */
507 void removeFilteredChildren(const KItemRangeList &parents);
508
509 /**
510 * Loads the selected choice of sorting method from Dolphin General Settings
511 */
512 void loadSortingSettings();
513
514 /**
515 * Maps the QByteArray-roles to RoleTypes and provides translation- and
516 * group-contexts.
517 */
518 struct RoleInfoMap {
519 const char *const role;
520 const RoleType roleType;
521 const KLazyLocalizedString roleTranslation;
522 const KLazyLocalizedString groupTranslation;
523 const KLazyLocalizedString tooltipTranslation;
524 const bool requiresBaloo;
525 const bool requiresIndexer;
526 };
527
528 /**
529 * @return Map of user visible roles that are accessible by KFileItemModel::rolesInformation().
530 */
531 static const RoleInfoMap *rolesInfoMap(int &count);
532
533 /**
534 * Determines the MIME-types of all items that can be done within
535 * the given timeout.
536 */
537 static void determineMimeTypes(const KFileItemList &items, int timeout);
538
539 /**
540 * @return Returns a copy of \a value that is implicitly shared
541 * with other users to save memory.
542 */
543 static QByteArray sharedValue(const QByteArray &value);
544
545 /**
546 * Checks if the model's internal data structures are consistent.
547 */
548 bool isConsistent() const;
549
550 /**
551 * Filters out the expanded folders that don't pass the filter themselves and don't have any filter-passing children.
552 * Will update the removedItemRanges arguments to include the parents that have been filtered.
553 * @returns the number of parents that have been filtered.
554 * @param removedItemRanges The ranges of items being deleted/filtered, will get updated
555 * @param parentsToEnsureVisible Parents that must be visible no matter what due to being ancestors of newly visible items
556 */
557 int filterChildlessParents(KItemRangeList &removedItemRanges, const QSet<ItemData *> &parentsToEnsureVisible = QSet<ItemData *>());
558
559 private:
560 KDirLister *m_dirLister = nullptr;
561
562 QCollator m_collator;
563 bool m_naturalSorting;
564 bool m_sortDirsFirst;
565 bool m_sortHiddenLast;
566
567 RoleType m_sortRole;
568 RoleType m_groupRole;
569 int m_sortingProgressPercent; // Value of directorySortingProgress() signal
570 QSet<QByteArray> m_roles;
571
572 QList<ItemData *> m_itemData;
573
574 // m_items is a cache for the method index(const QUrl&). If it contains N
575 // entries, it is guaranteed that these correspond to the first N items in
576 // the model, i.e., that (for every i between 0 and N - 1)
577 // m_items.value(fileItem(i).url()) == i
578 mutable QHash<QUrl, int> m_items;
579
580 KFileItemModelFilter m_filter;
581 QHash<KFileItem, ItemData *> m_filteredItems; // Items that got hidden by KFileItemModel::setNameFilter()
582
583 bool m_requestRole[RolesCount];
584
585 QTimer *m_maximumUpdateIntervalTimer;
586 QTimer *m_resortAllItemsTimer;
587 QList<ItemData *> m_pendingItemsToInsert;
588
589 // Cache for KFileItemModel::groups()
590 mutable QList<QPair<int, QVariant>> m_groups;
591
592 // Stores the URLs (key: target url, value: url) of the expanded directories.
593 QHash<QUrl, QUrl> m_expandedDirs;
594
595 // URLs that must be expanded. The expanding is initially triggered in setExpanded()
596 // and done step after step in slotCompleted().
597 QSet<QUrl> m_urlsToExpand;
598
599 friend class KFileItemModelRolesUpdater; // Accesses emitSortProgress() method
600 friend class KFileItemModelTest; // For unit testing
601 friend class KFileItemModelBenchmark; // For unit testing
602 friend class KFileItemListViewTest; // For unit testing
603 friend class DolphinPart; // Accesses m_dirLister
604 };
605
606 inline bool KFileItemModel::isRoleValueNatural(RoleType roleType)
607 {
608 return (roleType == TypeRole || roleType == ExtensionRole || roleType == TagsRole || roleType == CommentRole || roleType == TitleRole
609 || roleType == ArtistRole || roleType == GenreRole || roleType == AlbumRole || roleType == PathRole || roleType == DestinationRole
610 || roleType == OriginUrlRole || roleType == OwnerRole || roleType == GroupRole);
611 }
612
613 inline bool KFileItemModel::nameLessThan(const ItemData *a, const ItemData *b)
614 {
615 return a->item.text() < b->item.text();
616 }
617
618 inline bool KFileItemModel::isChildItem(int index) const
619 {
620 if (m_itemData.at(index)->parent) {
621 return true;
622 } else {
623 return false;
624 }
625 }
626
627 #endif