X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/afca8efa2601d9566c8d34d7b67dfb5abc729956..b4e80645e8e39ef7fcc1545136bad06ab3dd5f3e:/src/kitemviews/kfileitemmodel.h diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index 0fecbcf3f..13554d8c7 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -1,77 +1,119 @@ -/*************************************************************************** - * Copyright (C) 2011 by Peter Penz * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * - ***************************************************************************/ +/* + * SPDX-FileCopyrightText: 2011 Peter Penz + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef KFILEITEMMODEL_H #define KFILEITEMMODEL_H -#include -#include -#include -#include +#include "dolphin_export.h" +#include "kitemviews/kitemmodelbase.h" +#include "kitemviews/private/kfileitemmodelfilter.h" + +#include +#include +#include #include +#include +#include + +#include class KDirLister; + class QTimer; +namespace KIO +{ +class Job; +} + /** * @brief KItemModelBase implementation for KFileItems. * - * KFileItemModel is connected with one KDirLister. Each time the KDirLister - * emits new items, removes items or changes items the model gets synchronized. - * - * KFileItemModel supports sorting and grouping of items. Additional roles that - * are not part of KFileItem can be added with KFileItemModel::setData(). + * Allows to load items of a directory. Sorting and grouping of + * items are supported. Roles that are not part of KFileItem can + * be added with KFileItemModel::setData(). * - * Also the recursive expansion of sub-directories is supported by + * Recursive expansion of sub-directories is supported by * KFileItemModel::setExpanded(). */ -class LIBDOLPHINPRIVATE_EXPORT KFileItemModel : public KItemModelBase +class DOLPHIN_EXPORT KFileItemModel : public KItemModelBase { Q_OBJECT public: - explicit KFileItemModel(KDirLister* dirLister, QObject* parent = 0); - virtual ~KFileItemModel(); + explicit KFileItemModel(QObject *parent = nullptr); + ~KFileItemModel() override; + + /** + * Loads the directory specified by \a url. The signals + * directoryLoadingStarted(), directoryLoadingProgress() and directoryLoadingCompleted() + * indicate the current state of the loading process. The items + * of the directory are added after the loading has been completed. + */ + void loadDirectory(const QUrl &url); + + /** + * Throws away all currently loaded items and refreshes the directory + * by reloading all items again. + */ + void refreshDirectory(const QUrl &url); - virtual int count() const; - virtual QHash data(int index) const; - virtual bool setData(int index, const QHash &values); + /** + * @return Parent directory of the items that are shown. In case + * if a directory tree is shown, KFileItemModel::dir() returns + * the root-parent of all items. + * @see rootItem() + */ + QUrl directory() const override; /** - * @reimp + * Cancels the loading of a directory which has been started by either + * loadDirectory() or refreshDirectory(). */ - virtual int indexForKeyboardSearch(const QString& text, int startFromIndex = 0) const; + void cancelDirectoryLoading(); + + int count() const override; + QHash data(int index) const override; + bool setData(int index, const QHash &values) override; + /** - * @return True - * @reimp + * Sets a separate sorting with directories first (true) or a mixed + * sorting of files and directories (false). */ - virtual bool supportsGrouping() const; + void setSortDirectoriesFirst(bool dirsFirst); + bool sortDirectoriesFirst() const; /** - * @return True - * @reimp + * Sets a separate sorting with hidden files and folders last (true) or not (false). */ - virtual bool supportsSorting() const; + void setSortHiddenLast(bool hiddenLast); + bool sortHiddenLast() const; - /** @reimp */ - virtual QMimeData* createMimeData(const QSet& indexes) const; + void setShowHiddenFiles(bool show); + bool showHiddenFiles() const; + + /** + * If set to true, only directories are shown as items of the model. Files + * are ignored. + */ + void setShowDirectoriesOnly(bool enabled); + bool showDirectoriesOnly() const; + + QMimeData *createMimeData(const KItemSet &indexes) const override; + + int indexForKeyboardSearch(const QString &text, int startFromIndex = 0) const override; + + bool supportsDropping(int index) const override; + + bool canEnterOnHover(int index) const override; + + QString roleDescription(const QByteArray &role) const override; + + QList> groups() const override; /** * @return The file-item for the index \a index. If the index is in a valid @@ -80,120 +122,545 @@ public: */ KFileItem fileItem(int index) const; + /** + * @return The file-item for the url \a url. If no file-item with the given + * URL is found KFileItem::isNull() will be true for the returned + * file-item. The runtime complexity of this call is O(1). + */ + KFileItem fileItem(const QUrl &url) const; + /** * @return The index for the file-item \a item. -1 is returned if no file-item - * is found or if the file-item is null. The runtime + * is found or if the file-item is null. The amortized runtime * complexity of this call is O(1). */ - int index(const KFileItem& item) const; + int index(const KFileItem &item) const; + + /** + * @return The index for the URL \a url. -1 is returned if no file-item + * is found. The amortized runtime complexity of this call is O(1). + */ + int index(const QUrl &url) const; + + /** + * @return Root item of all items representing the item + * for KFileItemModel::dir(). + */ + KFileItem rootItem() const; /** * Clears all items of the model. */ void clear(); - // TODO: "name" + "isDir" is default in ctor - void setRoles(const QSet& roles); + /** + * Sets the roles that should be shown for each item. + */ + void setRoles(const QSet &roles); QSet roles() const; - bool setExpanded(int index, bool expanded); - bool isExpanded(int index) const; - bool isExpandable(int index) const; + bool setExpanded(int index, bool expanded) override; + bool isExpanded(int index) const override; + bool isExpandable(int index) const override; + int expandedParentsCount(int index) const override; + + QSet expandedDirectories() const; + + /** + * Marks the URLs in \a urls as sub-directories which were expanded previously. + * After calling loadDirectory() or refreshDirectory() the marked sub-directories + * will be expanded step-by-step. + */ + void restoreExpandedDirectories(const QSet &urls); + + /** + * Expands all parent-directories of the item \a url. + */ + void expandParentDirectories(const QUrl &url); + + void setNameFilter(const QString &nameFilter); + QString nameFilter() const; + + void setMimeTypeFilters(const QStringList &filters); + QStringList mimeTypeFilters() const; + + void setExcludeMimeTypeFilter(const QStringList &filters); + QStringList excludeMimeTypeFilter() const; + + struct RoleInfo { + QByteArray role; + QString translation; + QString group; + QString tooltip; + bool requiresBaloo; + bool requiresIndexer; + }; + + /** + * @return Provides static information for a role that is supported + * by KFileItemModel. Some roles can only be determined if + * Baloo is enabled and/or the Baloo indexing is enabled. + */ + static RoleInfo roleInformation(const QByteArray &role); + + /** + * @return Provides static information for all available roles that + * are supported by KFileItemModel. Some roles can only be + * determined if Baloo is enabled and/or the Baloo + * indexing is enabled. + */ + static QList rolesInformation(); + + /** + * @return Provides static information for all available grouping + * behaviors supported by KFileItemModel but not directly + * mapped to roles of KFileItemModel. + */ + static QList extraGroupingInformation(); + + /** set to true to hide application/x-trash files */ + void setShowTrashMime(bool show); + +Q_SIGNALS: + /** + * Is emitted if the loading of a directory has been started. It is + * assured that a signal directoryLoadingCompleted() will be send after + * the loading has been finished. For tracking the loading progress + * the signal directoryLoadingProgress() gets emitted in between. + */ + void directoryLoadingStarted(); + + /** + * Is emitted after the loading of a directory has been completed or new + * items have been inserted to an already loaded directory. Usually + * one or more itemsInserted() signals are emitted before loadingCompleted() + * (the only exception is loading an empty directory, where only a + * loadingCompleted() signal gets emitted). + */ + void directoryLoadingCompleted(); + + /** + * Is emitted when the model is being refreshed (F5 key press) + */ + void directoryRefreshing(); + + /** + * Is emitted after the loading of a directory has been canceled. + */ + void directoryLoadingCanceled(); + + /** + * Informs about the progress in percent when loading a directory. It is assured + * that the signal directoryLoadingStarted() has been emitted before. + */ + void directoryLoadingProgress(int percent); + + /** + * Is emitted if the sort-role gets resolved asynchronously and provides + * the progress-information of the sorting in percent. It is assured + * that the last sortProgress-signal contains 100 as value. + */ + void directorySortingProgress(int percent); + + /** + * Is emitted if an information message (e.g. "Connecting to host...") + * should be shown. + */ + void infoMessage(const QString &message); + + /** + * Is emitted if an error message (e.g. "Unknown location") + * should be shown. + */ + void errorMessage(const QString &message, const int kioErrorCode); + + /** + * Is emitted if a redirection from the current URL \a oldUrl + * to the new URL \a newUrl has been done. + */ + void directoryRedirection(const QUrl &oldUrl, const QUrl &newUrl); + + /** + * Is emitted when the URL passed by KFileItemModel::setUrl() represents a file. + * In this case no signal errorMessage() will be emitted. + */ + void urlIsFileError(const QUrl &url); + + /** + * It is emitted for files when they change and + * for dirs when files are added or removed. + */ + void fileItemsChanged(const KFileItemList &changedFileItems); + + /** + * It is emitted when the parent directory was removed. + */ + void currentDirectoryRemoved(); protected: - virtual void onGroupRoleChanged(const QByteArray& current, const QByteArray& previous); - virtual void onSortRoleChanged(const QByteArray& current, const QByteArray& previous); + void onGroupedSortingChanged(bool current) override; + void onSortRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool resortItems = true) override; + void onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) override; + void onGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool resortItems = true) override; + void onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) override; + +private Q_SLOTS: + /** + * Resorts all items dependent on the set sortRole(), sortOrder(), + * groupRole(), groupOrder() and foldersFirst() settings. + */ + void resortAllItems(); -private slots: void slotCompleted(); void slotCanceled(); - void slotNewItems(const KFileItemList& items); - void slotItemsDeleted(const KFileItemList& items); + void slotItemsAdded(const QUrl &directoryUrl, const KFileItemList &items); + void slotItemsDeleted(const KFileItemList &items); + void slotRefreshItems(const QList> &items); void slotClear(); - void slotClear(const KUrl& url); + void slotSortingChoiceChanged(); + void slotListerError(KIO::Job *job); - void dispatchPendingItems(); + void dispatchPendingItemsToInsert(); private: - void insertItems(const KFileItemList& items); - void removeItems(const KFileItemList& items); - - void removeExpandedItems(); - - enum Role { + enum RoleType { + // User visible roles: NoRole, NameRole, SizeRole, - DateRole, + ModificationTimeRole, + CreationTimeRole, + AccessTimeRole, PermissionsRole, OwnerRole, GroupRole, TypeRole, + ExtensionRole, DestinationRole, PathRole, + DeletionTimeRole, + // User visible roles available with Baloo: + CommentRole, + TagsRole, + RatingRole, + DimensionsRole, + WidthRole, + HeightRole, + ImageDateTimeRole, + OrientationRole, + PublisherRole, + PageCountRole, + WordCountRole, + TitleRole, + AuthorRole, + LineCountRole, + ArtistRole, + GenreRole, + AlbumRole, + DurationRole, + TrackRole, + ReleaseYearRole, + BitrateRole, + OriginUrlRole, + AspectRatioRole, + FrameRateRole, + // Non-visible roles: IsDirRole, + IsLinkRole, + IsHiddenRole, IsExpandedRole, - ExpansionLevelRole, - RolesCount // Mandatory last entry + IsExpandableRole, + ExpandedParentsCountRole, + // Mandatory last entry: + RolesCount + }; + + struct ItemData { + KFileItem item; + QHash values; + ItemData *parent; + }; + + struct ItemGroupInfo { + int comparable; + QString text; + + bool operator==(const ItemGroupInfo &other) const; + bool operator!=(const ItemGroupInfo &other) const; + bool operator<(const ItemGroupInfo &other) const; }; + enum RemoveItemsBehavior { KeepItemData, DeleteItemData, DeleteItemDataIfUnfiltered }; + + void insertItems(QList &items); + void removeItems(const KItemRangeList &itemRanges, RemoveItemsBehavior behavior); + + /** + * Helper method for insertItems() and removeItems(): Creates + * a list of ItemData elements based on the given items. + * Note that the ItemData instances are created dynamically and + * must be deleted by the caller. + */ + QList createItemDataList(const QUrl &parentUrl, const KFileItemList &items) const; + + /** + * Helper method for prepareItemsForSorting(). + * For a set role, fills 'values' of ItemData non-lazily. + */ + void prepareItemsWithRole(QList &itemDataList, RoleType roleType); + + /** + * Prepares the items for sorting. Normally, the hash 'values' in ItemData is filled + * lazily to save time and memory, but for some sort roles, it is expected that the + * sort role data is stored in 'values'. + */ + void prepareItemsForSorting(QList &itemDataList); + + static int expandedParentsCount(const ItemData *data); + + void removeExpandedItems(); + + /** + * This function is called by setData() and slotRefreshItems(). It emits + * the itemsChanged() signal, checks if the sort order is still correct, + * and starts m_resortAllItemsTimer if that is not the case. + */ + void emitItemsChangedAndTriggerResorting(const KItemRangeList &itemRanges, const QSet &changedRoles); + + /** + * Resets all values from m_requestRole to false. + */ void resetRoles(); - Role roleIndex(const QByteArray& role) const; + /** + * @return Role-type for the given role. + * Runtime complexity is O(1). + */ + RoleType typeForRole(const QByteArray &role) const; + + /** + * @return Role-byte-array for the given role-type. + * Runtime complexity is O(1). + */ + QByteArray roleForType(RoleType roleType) const; + + QHash retrieveData(const KFileItem &item, const ItemData *parent) const; + + /** + * @return True if role values benefit from natural or case insensitive sorting. + */ + static bool isRoleValueNatural(const RoleType roleType); + + /** + * @return True if \a a has a KFileItem whose text is 'less than' the one + * of \a b according to QString::operator<(const QString&). + */ + static bool nameLessThan(const ItemData *a, const ItemData *b); + + /** + * @return True if the item-data \a a should be ordered before the item-data + * \b. The item-data may have different parent-items. + */ + bool lessThan(const ItemData *a, const ItemData *b, const QCollator &collator) const; + + /** + * Sorts the items between \a begin and \a end using the comparison + * function lessThan(). + */ + void sort(const QList::iterator &begin, const QList::iterator &end) const; + + /** + * Helper method for lessThan() and expandedParentsCountCompare(): Compares + * the passed item-data using m_sortRole as criteria. Both items must + * have the same parent item, otherwise the comparison will be wrong. + */ + int sortRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const; + + /** + * Helper method for lessThan() and expandedParentsCountCompare(): Compares + * the passed item-data using m_groupRole as criteria. Both items must + * have the same parent item, otherwise the comparison will be wrong. + */ + int groupRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const; + + int stringCompare(const QString &a, const QString &b, const QCollator &collator) const; + + ItemGroupInfo nameRoleGroup(const ItemData *itemData, bool withString = true) const; + ItemGroupInfo sizeRoleGroup(const ItemData *itemData, bool withString = true) const; + ItemGroupInfo timeRoleGroup(const std::function &fileTimeCb, const ItemData *itemData, bool withString = true) const; + ItemGroupInfo permissionRoleGroup(const ItemData *itemData, bool withString = true) const; + ItemGroupInfo ratingRoleGroup(const ItemData *itemData, bool withString = true) const; + ItemGroupInfo typeRoleGroup(const ItemData *itemData) const; + ItemGroupInfo genericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const; + + QList> nameRoleGroups() const; + QList> sizeRoleGroups() const; + QList> timeRoleGroups(const std::function &fileTimeCb) const; + QList> permissionRoleGroups() const; + QList> ratingRoleGroups() const; + QList> typeRoleGroups() const; + QList> genericStringRoleGroups(const QByteArray &typeForRole) const; + + /** + * Helper method for all xxxRoleGroups() methods to check whether the + * item with the given index is a child-item. A child-item is defined + * as item having an expansion-level > 0. All xxxRoleGroups() methods + * should skip the grouping if the item is a child-item (although + * KItemListView would be capable to show sub-groups in groups this + * results in visual clutter for most usecases). + */ + bool isChildItem(int index) const; + + void scheduleResortAllItems(); + + /** + * Is invoked by KFileItemModelRolesUpdater and results in emitting the + * sortProgress signal with a percent-value of the progress. + */ + void emitSortProgress(int resolvedCount); + + /** + * Applies the filters set through @ref setNameFilter and @ref setMimeTypeFilters. + */ + void applyFilters(); - QHash retrieveData(const KFileItem& item) const; + /** + * Removes filtered items whose expanded parents have been deleted + * or collapsed via setExpanded(parentIndex, false). + */ + void removeFilteredChildren(const KItemRangeList &parents); - bool lessThan(const KFileItem& a, const KFileItem& b) const; - void sort(const KFileItemList::iterator& start, const KFileItemList::iterator& end); - int stringCompare(const QString& a, const QString& b) const; + /** + * Loads the selected choice of sorting method from Dolphin General Settings + */ + void loadSortingSettings(); /** - * Compares the expansion level of both items. The "expansion level" is defined - * by the number of parent directories. However simply comparing just the numbers - * is not sufficient, it is also important to check the hierarchy for having - * a correct order like shown in a tree. + * Maps the QByteArray-roles to RoleTypes and provides translation- and + * group-contexts. */ - int expansionLevelsCompare(const KFileItem& a, const KFileItem& b) const; + struct RoleInfoMap { + const char *const role; + const RoleType roleType; + const KLazyLocalizedString roleTranslation; + const KLazyLocalizedString groupTranslation; + const KLazyLocalizedString tooltipTranslation; + const bool requiresBaloo; + const bool requiresIndexer; + }; /** - * Helper method for expansionLevelCompare(). + * @return Map of user visible roles that are accessible by KFileItemModel::rolesInformation(). */ - QString subPath(const KFileItem& item, - const QString& itemPath, - int start, - bool* isDir) const; + static const RoleInfoMap *rolesInfoMap(int &count); - bool useMaximumUpdateInterval() const; + /** + * Determines the MIME-types of all items that can be done within + * the given timeout. + */ + static void determineMimeTypes(const KFileItemList &items, int timeout); + + /** + * @return Returns a copy of \a value that is implicitly shared + * with other users to save memory. + */ + static QByteArray sharedValue(const QByteArray &value); + + /** + * Checks if the model's internal data structures are consistent. + */ + bool isConsistent() const; + + /** + * Filters out the expanded folders that don't pass the filter themselves and don't have any filter-passing children. + * Will update the removedItemRanges arguments to include the parents that have been filtered. + * @returns the number of parents that have been filtered. + * @param removedItemRanges The ranges of items being deleted/filtered, will get updated + * @param parentsToEnsureVisible Parents that must be visible no matter what due to being ancestors of newly visible items + */ + int filterChildlessParents(KItemRangeList &removedItemRanges, const QSet &parentsToEnsureVisible = QSet()); private: - QWeakPointer m_dirLister; + KDirLister *m_dirLister = nullptr; + QCollator m_collator; bool m_naturalSorting; - bool m_sortFoldersFirst; + bool m_sortDirsFirst; + bool m_sortHiddenLast; + + RoleType m_sortRole; + RoleType m_groupRole; + QByteArray m_sortExtraInfo; + QByteArray m_groupExtraInfo; + + int m_sortingProgressPercent; // Value of directorySortingProgress() signal + QSet m_roles; + + QList m_itemData; - Role m_groupRole; - Role m_sortRole; - Qt::CaseSensitivity m_caseSensitivity; + // m_items is a cache for the method index(const QUrl&). If it contains N + // entries, it is guaranteed that these correspond to the first N items in + // the model, i.e., that (for every i between 0 and N - 1) + // m_items.value(fileItem(i).url()) == i + mutable QHash m_items; - KFileItemList m_sortedItems; // Allows O(1) access for KFileItemModel::fileItem(int index) - QHash m_items; // Allows O(1) access for KFileItemModel::index(const KFileItem& item) - QList > m_data; + KFileItemModelFilter m_filter; + QHash m_filteredItems; // Items that got hidden by KFileItemModel::setNameFilter() bool m_requestRole[RolesCount]; - QTimer* m_minimumUpdateIntervalTimer; - QTimer* m_maximumUpdateIntervalTimer; - KFileItemList m_pendingItemsToInsert; - KFileItemList m_pendingItemsToDelete; + QTimer *m_maximumUpdateIntervalTimer; + QTimer *m_resortAllItemsTimer; + QList m_pendingItemsToInsert; - // Stores the smallest expansion level of the root-URL. Is required to calculate - // the "expansionLevel" role in an efficient way. A value < 0 indicates that - // it has not been initialized yet. - mutable int m_rootExpansionLevel; + // Cache for KFileItemModel::groups() + mutable QList> m_groups; + // Stores the URLs (key: target url, value: url) of the expanded directories. + QHash m_expandedDirs; + + // URLs that must be expanded. The expanding is initially triggered in setExpanded() + // and done step after step in slotCompleted(). + QSet m_urlsToExpand; + + friend class KFileItemModelRolesUpdater; // Accesses emitSortProgress() method friend class KFileItemModelTest; // For unit testing + friend class KFileItemModelBenchmark; // For unit testing + friend class KFileItemListViewTest; // For unit testing + friend class DolphinPart; // Accesses m_dirLister }; -#endif +inline bool KFileItemModel::isRoleValueNatural(RoleType roleType) +{ + return (roleType == TypeRole || roleType == ExtensionRole || roleType == TagsRole || roleType == CommentRole || roleType == TitleRole + || roleType == ArtistRole || roleType == GenreRole || roleType == AlbumRole || roleType == PathRole || roleType == DestinationRole + || roleType == OriginUrlRole || roleType == OwnerRole || roleType == GroupRole); +} +inline bool KFileItemModel::nameLessThan(const ItemData *a, const ItemData *b) +{ + // Split extension, taking into account it can be empty + constexpr QString::SectionFlags flags = QString::SectionSkipEmpty | QString::SectionIncludeLeadingSep; + return a->item.text().section('.', 0, 0, flags) < b->item.text().section('.', 0, 0, flags); +} +inline bool KFileItemModel::isChildItem(int index) const +{ + if (m_itemData.at(index)->parent) { + return true; + } else { + return false; + } +} + +inline bool KFileItemModel::ItemGroupInfo::operator==(const ItemGroupInfo &other) const +{ + return comparable == other.comparable && text == other.text; +} + +inline bool KFileItemModel::ItemGroupInfo::operator!=(const ItemGroupInfo &other) const +{ + return comparable != other.comparable || text != other.text; +} + +#endif