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