File indexing completed on 2024-04-28 17:02:58

0001 /*
0002  * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #ifndef KFILEITEMMODEL_H
0008 #define KFILEITEMMODEL_H
0009 
0010 #include "dolphin_export.h"
0011 #include "kitemviews/kitemmodelbase.h"
0012 #include "kitemviews/private/kfileitemmodelfilter.h"
0013 
0014 #include <KFileItem>
0015 #include <KLazyLocalizedString>
0016 
0017 #include <QCollator>
0018 #include <QHash>
0019 #include <QSet>
0020 #include <QUrl>
0021 
0022 #include <functional>
0023 
0024 class KDirLister;
0025 
0026 class QTimer;
0027 
0028 namespace KIO
0029 {
0030 class Job;
0031 }
0032 
0033 /**
0034  * @brief KItemModelBase implementation for KFileItems.
0035  *
0036  * Allows to load items of a directory. Sorting and grouping of
0037  * items are supported. Roles that are not part of KFileItem can
0038  * be added with KFileItemModel::setData().
0039  *
0040  * Recursive expansion of sub-directories is supported by
0041  * KFileItemModel::setExpanded().
0042  */
0043 class DOLPHIN_EXPORT KFileItemModel : public KItemModelBase
0044 {
0045     Q_OBJECT
0046 
0047 public:
0048     explicit KFileItemModel(QObject *parent = nullptr);
0049     ~KFileItemModel() override;
0050 
0051     /**
0052      * Loads the directory specified by \a url. The signals
0053      * directoryLoadingStarted(), directoryLoadingProgress() and directoryLoadingCompleted()
0054      * indicate the current state of the loading process. The items
0055      * of the directory are added after the loading has been completed.
0056      */
0057     void loadDirectory(const QUrl &url);
0058 
0059     /**
0060      * Throws away all currently loaded items and refreshes the directory
0061      * by reloading all items again.
0062      */
0063     void refreshDirectory(const QUrl &url);
0064 
0065     /**
0066      * @return Parent directory of the items that are shown. In case
0067      *         if a directory tree is shown, KFileItemModel::dir() returns
0068      *         the root-parent of all items.
0069      * @see rootItem()
0070      */
0071     QUrl directory() const override;
0072 
0073     /**
0074      * Cancels the loading of a directory which has been started by either
0075      * loadDirectory() or refreshDirectory().
0076      */
0077     void cancelDirectoryLoading();
0078 
0079     int count() const override;
0080     QHash<QByteArray, QVariant> data(int index) const override;
0081     bool setData(int index, const QHash<QByteArray, QVariant> &values) override;
0082 
0083     /**
0084      * Sets a separate sorting with directories first (true) or a mixed
0085      * sorting of files and directories (false).
0086      */
0087     void setSortDirectoriesFirst(bool dirsFirst);
0088     bool sortDirectoriesFirst() const;
0089 
0090     /**
0091      * Sets a separate sorting with hidden files and folders last (true) or not (false).
0092      */
0093     void setSortHiddenLast(bool hiddenLast);
0094     bool sortHiddenLast() const;
0095 
0096     void setShowHiddenFiles(bool show);
0097     bool showHiddenFiles() const;
0098 
0099     /**
0100      * If set to true, only directories are shown as items of the model. Files
0101      * are ignored.
0102      */
0103     void setShowDirectoriesOnly(bool enabled);
0104     bool showDirectoriesOnly() const;
0105 
0106     QMimeData *createMimeData(const KItemSet &indexes) const override;
0107 
0108     int indexForKeyboardSearch(const QString &text, int startFromIndex = 0) const override;
0109 
0110     bool supportsDropping(int index) const override;
0111 
0112     QString roleDescription(const QByteArray &role) const override;
0113 
0114     QList<QPair<int, QVariant>> groups() const override;
0115 
0116     /**
0117      * @return The file-item for the index \a index. If the index is in a valid
0118      *         range it is assured that the file-item is not null. The runtime
0119      *         complexity of this call is O(1).
0120      */
0121     KFileItem fileItem(int index) const;
0122 
0123     /**
0124      * @return The file-item for the url \a url. If no file-item with the given
0125      *         URL is found KFileItem::isNull() will be true for the returned
0126      *         file-item. The runtime complexity of this call is O(1).
0127      */
0128     KFileItem fileItem(const QUrl &url) const;
0129 
0130     /**
0131      * @return The index for the file-item \a item. -1 is returned if no file-item
0132      *         is found or if the file-item is null. The amortized runtime
0133      *         complexity of this call is O(1).
0134      */
0135     int index(const KFileItem &item) const;
0136 
0137     /**
0138      * @return The index for the URL \a url. -1 is returned if no file-item
0139      *         is found. The amortized runtime complexity of this call is O(1).
0140      */
0141     int index(const QUrl &url) const;
0142 
0143     /**
0144      * @return Root item of all items representing the item
0145      *         for KFileItemModel::dir().
0146      */
0147     KFileItem rootItem() const;
0148 
0149     /**
0150      * Clears all items of the model.
0151      */
0152     void clear();
0153 
0154     /**
0155      * Sets the roles that should be shown for each item.
0156      */
0157     void setRoles(const QSet<QByteArray> &roles);
0158     QSet<QByteArray> roles() const;
0159 
0160     bool setExpanded(int index, bool expanded) override;
0161     bool isExpanded(int index) const override;
0162     bool isExpandable(int index) const override;
0163     int expandedParentsCount(int index) const override;
0164 
0165     QSet<QUrl> expandedDirectories() const;
0166 
0167     /**
0168      * Marks the URLs in \a urls as sub-directories which were expanded previously.
0169      * After calling loadDirectory() or refreshDirectory() the marked sub-directories
0170      * will be expanded step-by-step.
0171      */
0172     void restoreExpandedDirectories(const QSet<QUrl> &urls);
0173 
0174     /**
0175      * Expands all parent-directories of the item \a url.
0176      */
0177     void expandParentDirectories(const QUrl &url);
0178 
0179     void setNameFilter(const QString &nameFilter);
0180     QString nameFilter() const;
0181 
0182     void setMimeTypeFilters(const QStringList &filters);
0183     QStringList mimeTypeFilters() const;
0184 
0185     void setExcludeMimeTypeFilter(const QStringList &filters);
0186     QStringList excludeMimeTypeFilter() const;
0187 
0188     struct RoleInfo {
0189         QByteArray role;
0190         QString translation;
0191         QString group;
0192         QString tooltip;
0193         bool requiresBaloo;
0194         bool requiresIndexer;
0195     };
0196 
0197     /**
0198      * @return Provides static information for all available roles that
0199      *         are supported by KFileItemModel. Some roles can only be
0200      *         determined if Baloo is enabled and/or the Baloo
0201      *         indexing is enabled.
0202      */
0203     static QList<RoleInfo> rolesInformation();
0204 
0205     /** set to true to hide application/x-trash files */
0206     void setShowTrashMime(bool show);
0207 
0208 Q_SIGNALS:
0209     /**
0210      * Is emitted if the loading of a directory has been started. It is
0211      * assured that a signal directoryLoadingCompleted() will be send after
0212      * the loading has been finished. For tracking the loading progress
0213      * the signal directoryLoadingProgress() gets emitted in between.
0214      */
0215     void directoryLoadingStarted();
0216 
0217     /**
0218      * Is emitted after the loading of a directory has been completed or new
0219      * items have been inserted to an already loaded directory. Usually
0220      * one or more itemsInserted() signals are emitted before loadingCompleted()
0221      * (the only exception is loading an empty directory, where only a
0222      * loadingCompleted() signal gets emitted).
0223      */
0224     void directoryLoadingCompleted();
0225 
0226     /**
0227      * Is emitted when the model is being refreshed (F5 key press)
0228      */
0229     void directoryRefreshing();
0230 
0231     /**
0232      * Is emitted after the loading of a directory has been canceled.
0233      */
0234     void directoryLoadingCanceled();
0235 
0236     /**
0237      * Informs about the progress in percent when loading a directory. It is assured
0238      * that the signal directoryLoadingStarted() has been emitted before.
0239      */
0240     void directoryLoadingProgress(int percent);
0241 
0242     /**
0243      * Is emitted if the sort-role gets resolved asynchronously and provides
0244      * the progress-information of the sorting in percent. It is assured
0245      * that the last sortProgress-signal contains 100 as value.
0246      */
0247     void directorySortingProgress(int percent);
0248 
0249     /**
0250      * Is emitted if an information message (e.g. "Connecting to host...")
0251      * should be shown.
0252      */
0253     void infoMessage(const QString &message);
0254 
0255     /**
0256      * Is emitted if an error message (e.g. "Unknown location")
0257      * should be shown.
0258      */
0259     void errorMessage(const QString &message);
0260 
0261     /**
0262      * Is emitted if a redirection from the current URL \a oldUrl
0263      * to the new URL \a newUrl has been done.
0264      */
0265     void directoryRedirection(const QUrl &oldUrl, const QUrl &newUrl);
0266 
0267     /**
0268      * Is emitted when the URL passed by KFileItemModel::setUrl() represents a file.
0269      * In this case no signal errorMessage() will be emitted.
0270      */
0271     void urlIsFileError(const QUrl &url);
0272 
0273     /**
0274      * It is emitted for files when they change and
0275      * for dirs when files are added or removed.
0276      */
0277     void fileItemsChanged(const KFileItemList &changedFileItems);
0278 
0279     /**
0280      * It is emitted when the parent directory was removed.
0281      */
0282     void currentDirectoryRemoved();
0283 
0284 protected:
0285     void onGroupedSortingChanged(bool current) override;
0286     void onSortRoleChanged(const QByteArray &current, const QByteArray &previous, bool resortItems = true) override;
0287     void onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) override;
0288 
0289 private Q_SLOTS:
0290     /**
0291      * Resorts all items dependent on the set sortRole(), sortOrder()
0292      * and foldersFirst() settings.
0293      */
0294     void resortAllItems();
0295 
0296     void slotCompleted();
0297     void slotCanceled();
0298     void slotItemsAdded(const QUrl &directoryUrl, const KFileItemList &items);
0299     void slotItemsDeleted(const KFileItemList &items);
0300     void slotRefreshItems(const QList<QPair<KFileItem, KFileItem>> &items);
0301     void slotClear();
0302     void slotSortingChoiceChanged();
0303     void slotListerError(KIO::Job *job);
0304 
0305     void dispatchPendingItemsToInsert();
0306 
0307 private:
0308     enum RoleType {
0309         // User visible roles:
0310         NoRole,
0311         NameRole,
0312         SizeRole,
0313         ModificationTimeRole,
0314         CreationTimeRole,
0315         AccessTimeRole,
0316         PermissionsRole,
0317         OwnerRole,
0318         GroupRole,
0319         TypeRole,
0320         ExtensionRole,
0321         DestinationRole,
0322         PathRole,
0323         DeletionTimeRole,
0324         // User visible roles available with Baloo:
0325         CommentRole,
0326         TagsRole,
0327         RatingRole,
0328         DimensionsRole,
0329         WidthRole,
0330         HeightRole,
0331         ImageDateTimeRole,
0332         OrientationRole,
0333         PublisherRole,
0334         PageCountRole,
0335         WordCountRole,
0336         TitleRole,
0337         AuthorRole,
0338         LineCountRole,
0339         ArtistRole,
0340         GenreRole,
0341         AlbumRole,
0342         DurationRole,
0343         TrackRole,
0344         ReleaseYearRole,
0345         BitrateRole,
0346         OriginUrlRole,
0347         AspectRatioRole,
0348         FrameRateRole,
0349         // Non-visible roles:
0350         IsDirRole,
0351         IsLinkRole,
0352         IsHiddenRole,
0353         IsExpandedRole,
0354         IsExpandableRole,
0355         ExpandedParentsCountRole,
0356         // Mandatory last entry:
0357         RolesCount
0358     };
0359 
0360     struct ItemData {
0361         KFileItem item;
0362         QHash<QByteArray, QVariant> values;
0363         ItemData *parent;
0364     };
0365 
0366     enum RemoveItemsBehavior { KeepItemData, DeleteItemData, DeleteItemDataIfUnfiltered };
0367 
0368     void insertItems(QList<ItemData *> &items);
0369     void removeItems(const KItemRangeList &itemRanges, RemoveItemsBehavior behavior);
0370 
0371     /**
0372      * Helper method for insertItems() and removeItems(): Creates
0373      * a list of ItemData elements based on the given items.
0374      * Note that the ItemData instances are created dynamically and
0375      * must be deleted by the caller.
0376      */
0377     QList<ItemData *> createItemDataList(const QUrl &parentUrl, const KFileItemList &items) const;
0378 
0379     /**
0380      * Prepares the items for sorting. Normally, the hash 'values' in ItemData is filled
0381      * lazily to save time and memory, but for some sort roles, it is expected that the
0382      * sort role data is stored in 'values'.
0383      */
0384     void prepareItemsForSorting(QList<ItemData *> &itemDataList);
0385 
0386     static int expandedParentsCount(const ItemData *data);
0387 
0388     void removeExpandedItems();
0389 
0390     /**
0391      * This function is called by setData() and slotRefreshItems(). It emits
0392      * the itemsChanged() signal, checks if the sort order is still correct,
0393      * and starts m_resortAllItemsTimer if that is not the case.
0394      */
0395     void emitItemsChangedAndTriggerResorting(const KItemRangeList &itemRanges, const QSet<QByteArray> &changedRoles);
0396 
0397     /**
0398      * Resets all values from m_requestRole to false.
0399      */
0400     void resetRoles();
0401 
0402     /**
0403      * @return Role-type for the given role.
0404      *         Runtime complexity is O(1).
0405      */
0406     RoleType typeForRole(const QByteArray &role) const;
0407 
0408     /**
0409      * @return Role-byte-array for the given role-type.
0410      *         Runtime complexity is O(1).
0411      */
0412     QByteArray roleForType(RoleType roleType) const;
0413 
0414     QHash<QByteArray, QVariant> retrieveData(const KFileItem &item, const ItemData *parent) const;
0415 
0416     /**
0417      * @return True if role values benefit from natural or case insensitive sorting.
0418      */
0419     static bool isRoleValueNatural(const RoleType roleType);
0420 
0421     /**
0422      * @return True if \a a has a KFileItem whose text is 'less than' the one
0423      *         of \a b according to QString::operator<(const QString&).
0424      */
0425     static bool nameLessThan(const ItemData *a, const ItemData *b);
0426 
0427     /**
0428      * @return True if the item-data \a a should be ordered before the item-data
0429      *         \b. The item-data may have different parent-items.
0430      */
0431     bool lessThan(const ItemData *a, const ItemData *b, const QCollator &collator) const;
0432 
0433     /**
0434      * Sorts the items between \a begin and \a end using the comparison
0435      * function lessThan().
0436      */
0437     void sort(const QList<ItemData *>::iterator &begin, const QList<ItemData *>::iterator &end) const;
0438 
0439     /**
0440      * Helper method for lessThan() and expandedParentsCountCompare(): Compares
0441      * the passed item-data using m_sortRole as criteria. Both items must
0442      * have the same parent item, otherwise the comparison will be wrong.
0443      */
0444     int sortRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const;
0445 
0446     int stringCompare(const QString &a, const QString &b, const QCollator &collator) const;
0447 
0448     QList<QPair<int, QVariant>> nameRoleGroups() const;
0449     QList<QPair<int, QVariant>> sizeRoleGroups() const;
0450     QList<QPair<int, QVariant>> timeRoleGroups(const std::function<QDateTime(const ItemData *)> &fileTimeCb) const;
0451     QList<QPair<int, QVariant>> permissionRoleGroups() const;
0452     QList<QPair<int, QVariant>> ratingRoleGroups() const;
0453     QList<QPair<int, QVariant>> genericStringRoleGroups(const QByteArray &typeForRole) const;
0454 
0455     /**
0456      * Helper method for all xxxRoleGroups() methods to check whether the
0457      * item with the given index is a child-item. A child-item is defined
0458      * as item having an expansion-level > 0. All xxxRoleGroups() methods
0459      * should skip the grouping if the item is a child-item (although
0460      * KItemListView would be capable to show sub-groups in groups this
0461      * results in visual clutter for most usecases).
0462      */
0463     bool isChildItem(int index) const;
0464 
0465     void scheduleResortAllItems();
0466 
0467     /**
0468      * Is invoked by KFileItemModelRolesUpdater and results in emitting the
0469      * sortProgress signal with a percent-value of the progress.
0470      */
0471     void emitSortProgress(int resolvedCount);
0472 
0473     /**
0474      * Applies the filters set through @ref setNameFilter and @ref setMimeTypeFilters.
0475      */
0476     void applyFilters();
0477 
0478     /**
0479      * Removes filtered items whose expanded parents have been deleted
0480      * or collapsed via setExpanded(parentIndex, false).
0481      */
0482     void removeFilteredChildren(const KItemRangeList &parents);
0483 
0484     /**
0485      * Loads the selected choice of sorting method from Dolphin General Settings
0486      */
0487     void loadSortingSettings();
0488 
0489     /**
0490      * Maps the QByteArray-roles to RoleTypes and provides translation- and
0491      * group-contexts.
0492      */
0493     struct RoleInfoMap {
0494         const char *const role;
0495         const RoleType roleType;
0496         const KLazyLocalizedString roleTranslation;
0497         const KLazyLocalizedString groupTranslation;
0498         const KLazyLocalizedString tooltipTranslation;
0499         const bool requiresBaloo;
0500         const bool requiresIndexer;
0501     };
0502 
0503     /**
0504      * @return Map of user visible roles that are accessible by KFileItemModel::rolesInformation().
0505      */
0506     static const RoleInfoMap *rolesInfoMap(int &count);
0507 
0508     /**
0509      * Determines the MIME-types of all items that can be done within
0510      * the given timeout.
0511      */
0512     static void determineMimeTypes(const KFileItemList &items, int timeout);
0513 
0514     /**
0515      * @return Returns a copy of \a value that is implicitly shared
0516      * with other users to save memory.
0517      */
0518     static QByteArray sharedValue(const QByteArray &value);
0519 
0520     /**
0521      * Checks if the model's internal data structures are consistent.
0522      */
0523     bool isConsistent() const;
0524 
0525     /**
0526      * Filters out the expanded folders that don't pass the filter themselves and don't have any filter-passing children.
0527      * Will update the removedItemRanges arguments to include the parents that have been filtered.
0528      * @returns the number of parents that have been filtered.
0529      * @param removedItemRanges The ranges of items being deleted/filtered, will get updated
0530      * @param parentsToEnsureVisible Parents that must be visible no matter what due to being ancestors of newly visible items
0531      */
0532     int filterChildlessParents(KItemRangeList &removedItemRanges, const QSet<ItemData *> &parentsToEnsureVisible = QSet<ItemData *>());
0533 
0534 private:
0535     KDirLister *m_dirLister = nullptr;
0536 
0537     QCollator m_collator;
0538     bool m_naturalSorting;
0539     bool m_sortDirsFirst;
0540     bool m_sortHiddenLast;
0541 
0542     RoleType m_sortRole;
0543     int m_sortingProgressPercent; // Value of directorySortingProgress() signal
0544     QSet<QByteArray> m_roles;
0545 
0546     QList<ItemData *> m_itemData;
0547 
0548     // m_items is a cache for the method index(const QUrl&). If it contains N
0549     // entries, it is guaranteed that these correspond to the first N items in
0550     // the model, i.e., that (for every i between 0 and N - 1)
0551     // m_items.value(fileItem(i).url()) == i
0552     mutable QHash<QUrl, int> m_items;
0553 
0554     KFileItemModelFilter m_filter;
0555     QHash<KFileItem, ItemData *> m_filteredItems; // Items that got hidden by KFileItemModel::setNameFilter()
0556 
0557     bool m_requestRole[RolesCount];
0558 
0559     QTimer *m_maximumUpdateIntervalTimer;
0560     QTimer *m_resortAllItemsTimer;
0561     QList<ItemData *> m_pendingItemsToInsert;
0562 
0563     // Cache for KFileItemModel::groups()
0564     mutable QList<QPair<int, QVariant>> m_groups;
0565 
0566     // Stores the URLs (key: target url, value: url) of the expanded directories.
0567     QHash<QUrl, QUrl> m_expandedDirs;
0568 
0569     // URLs that must be expanded. The expanding is initially triggered in setExpanded()
0570     // and done step after step in slotCompleted().
0571     QSet<QUrl> m_urlsToExpand;
0572 
0573     friend class KFileItemModelRolesUpdater; // Accesses emitSortProgress() method
0574     friend class KFileItemModelTest; // For unit testing
0575     friend class KFileItemModelBenchmark; // For unit testing
0576     friend class KFileItemListViewTest; // For unit testing
0577     friend class DolphinPart; // Accesses m_dirLister
0578 };
0579 
0580 inline bool KFileItemModel::isRoleValueNatural(RoleType roleType)
0581 {
0582     return (roleType == TypeRole || roleType == ExtensionRole || roleType == TagsRole || roleType == CommentRole || roleType == TitleRole
0583             || roleType == ArtistRole || roleType == GenreRole || roleType == AlbumRole || roleType == PathRole || roleType == DestinationRole
0584             || roleType == OriginUrlRole || roleType == OwnerRole || roleType == GroupRole);
0585 }
0586 
0587 inline bool KFileItemModel::nameLessThan(const ItemData *a, const ItemData *b)
0588 {
0589     return a->item.text() < b->item.text();
0590 }
0591 
0592 inline bool KFileItemModel::isChildItem(int index) const
0593 {
0594     if (m_itemData.at(index)->parent) {
0595         return true;
0596     } else {
0597         return false;
0598     }
0599 }
0600 
0601 #endif