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