]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemmodel.h
Implemented ItemGroupInfo in place of QVariant in roleRoleGroup functions
[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 /**
117 * @return The file-item for the index \a index. If the index is in a valid
118 * range it is assured that the file-item is not null. The runtime
119 * complexity of this call is O(1).
120 */
121 KFileItem fileItem(int index) const;
122
123 /**
124 * @return The file-item for the url \a url. If no file-item with the given
125 * URL is found KFileItem::isNull() will be true for the returned
126 * file-item. The runtime complexity of this call is O(1).
127 */
128 KFileItem fileItem(const QUrl &url) const;
129
130 /**
131 * @return The index for the file-item \a item. -1 is returned if no file-item
132 * is found or if the file-item is null. The amortized runtime
133 * complexity of this call is O(1).
134 */
135 int index(const KFileItem &item) const;
136
137 /**
138 * @return The index for the URL \a url. -1 is returned if no file-item
139 * is found. The amortized runtime complexity of this call is O(1).
140 */
141 int index(const QUrl &url) const;
142
143 /**
144 * @return Root item of all items representing the item
145 * for KFileItemModel::dir().
146 */
147 KFileItem rootItem() const;
148
149 /**
150 * Clears all items of the model.
151 */
152 void clear();
153
154 /**
155 * Sets the roles that should be shown for each item.
156 */
157 void setRoles(const QSet<QByteArray> &roles);
158 QSet<QByteArray> roles() const;
159
160 bool setExpanded(int index, bool expanded) override;
161 bool isExpanded(int index) const override;
162 bool isExpandable(int index) const override;
163 int expandedParentsCount(int index) const override;
164
165 QSet<QUrl> expandedDirectories() const;
166
167 /**
168 * Marks the URLs in \a urls as sub-directories which were expanded previously.
169 * After calling loadDirectory() or refreshDirectory() the marked sub-directories
170 * will be expanded step-by-step.
171 */
172 void restoreExpandedDirectories(const QSet<QUrl> &urls);
173
174 /**
175 * Expands all parent-directories of the item \a url.
176 */
177 void expandParentDirectories(const QUrl &url);
178
179 void setNameFilter(const QString &nameFilter);
180 QString nameFilter() const;
181
182 void setMimeTypeFilters(const QStringList &filters);
183 QStringList mimeTypeFilters() const;
184
185 void setExcludeMimeTypeFilter(const QStringList &filters);
186 QStringList excludeMimeTypeFilter() const;
187
188 struct RoleInfo {
189 QByteArray role;
190 QString translation;
191 QString group;
192 QString tooltip;
193 bool requiresBaloo;
194 bool requiresIndexer;
195 };
196
197 /**
198 * @return Provides static information for a role that is supported
199 * by KFileItemModel. Some roles can only be determined if
200 * Baloo is enabled and/or the Baloo indexing is enabled.
201 */
202 static RoleInfo roleInformation(const QByteArray &role);
203
204 /**
205 * @return Provides static information for all available roles that
206 * are supported by KFileItemModel. Some roles can only be
207 * determined if Baloo is enabled and/or the Baloo
208 * indexing is enabled.
209 */
210 static QList<RoleInfo> rolesInformation();
211
212 QList<QPair<int, QVariant>> groups() const override;
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 int m_sortingProgressPercent; // Value of directorySortingProgress() signal
579 QSet<QByteArray> m_roles;
580
581 QList<ItemData *> m_itemData;
582
583 // m_items is a cache for the method index(const QUrl&). If it contains N
584 // entries, it is guaranteed that these correspond to the first N items in
585 // the model, i.e., that (for every i between 0 and N - 1)
586 // m_items.value(fileItem(i).url()) == i
587 mutable QHash<QUrl, int> m_items;
588
589 KFileItemModelFilter m_filter;
590 QHash<KFileItem, ItemData *> m_filteredItems; // Items that got hidden by KFileItemModel::setNameFilter()
591
592 bool m_requestRole[RolesCount];
593
594 QTimer *m_maximumUpdateIntervalTimer;
595 QTimer *m_resortAllItemsTimer;
596 QList<ItemData *> m_pendingItemsToInsert;
597
598 // Cache for KFileItemModel::groups()
599 mutable QList<QPair<int, QVariant>> m_groups;
600
601 // Stores the URLs (key: target url, value: url) of the expanded directories.
602 QHash<QUrl, QUrl> m_expandedDirs;
603
604 // URLs that must be expanded. The expanding is initially triggered in setExpanded()
605 // and done step after step in slotCompleted().
606 QSet<QUrl> m_urlsToExpand;
607
608 friend class KFileItemModelRolesUpdater; // Accesses emitSortProgress() method
609 friend class KFileItemModelTest; // For unit testing
610 friend class KFileItemModelBenchmark; // For unit testing
611 friend class KFileItemListViewTest; // For unit testing
612 friend class DolphinPart; // Accesses m_dirLister
613 };
614
615 inline bool KFileItemModel::isRoleValueNatural(RoleType roleType)
616 {
617 return (roleType == TypeRole || roleType == ExtensionRole || roleType == TagsRole || roleType == CommentRole || roleType == TitleRole
618 || roleType == ArtistRole || roleType == GenreRole || roleType == AlbumRole || roleType == PathRole || roleType == DestinationRole
619 || roleType == OriginUrlRole || roleType == OwnerRole || roleType == GroupRole);
620 }
621
622 inline bool KFileItemModel::nameLessThan(const ItemData *a, const ItemData *b)
623 {
624 return a->item.text() < b->item.text();
625 }
626
627 inline bool KFileItemModel::isChildItem(int index) const
628 {
629 if (m_itemData.at(index)->parent) {
630 return true;
631 } else {
632 return false;
633 }
634 }
635
636 inline bool KFileItemModel::ItemGroupInfo::operator==(const ItemGroupInfo &other) const
637 {
638 return comparable == other.comparable && text == other.text;
639 }
640
641 inline bool KFileItemModel::ItemGroupInfo::operator!=(const ItemGroupInfo &other) const
642 {
643 return comparable != other.comparable || text != other.text;
644 }
645
646 inline bool KFileItemModel::ItemGroupInfo::operator<(const ItemGroupInfo &other) const
647 {
648 if (comparable == other.comparable) {
649 return text < other.text;
650 } else {
651 return comparable < other.comparable;
652 }
653 }
654
655 #endif