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