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