]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemmodel.h
Fix sorting issues
[dolphin.git] / src / kitemviews / kfileitemmodel.h
1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
19
20 #ifndef KFILEITEMMODEL_H
21 #define KFILEITEMMODEL_H
22
23 #include <libdolphin_export.h>
24 #include <KFileItemList>
25 #include <KUrl>
26 #include <kitemviews/kfileitemmodelfilter_p.h>
27 #include <kitemviews/kitemmodelbase.h>
28
29 #include <QHash>
30
31 class KDirLister;
32 class QTimer;
33
34 /**
35 * @brief KItemModelBase implementation for KFileItems.
36 *
37 * KFileItemModel is connected with one KDirLister. Each time the KDirLister
38 * emits new items, removes items or changes items the model gets synchronized.
39 *
40 * KFileItemModel supports sorting and grouping of items. Additional roles that
41 * are not part of KFileItem can be added with KFileItemModel::setData().
42 *
43 * Also the recursive expansion of sub-directories is supported by
44 * KFileItemModel::setExpanded().
45 *
46 * TODO: In the longterm instead of passing a KDirLister just an URL should
47 * be passed and a KDirLister used internally. This solves the following issues:
48 * - The user of the API does not need to decide whether he listens to KDirLister
49 * or KFileItemModel.
50 * - It resolves minor conceptual differences between KDirLister and KFileItemModel.
51 * E.g. there is no way for KFileItemModel to check whether a completed() signal
52 * will be emitted after newItems() will be send by KDirLister or not (in the case
53 * of setShowingDotFiles() no completed() signal will get emitted).
54 */
55 class LIBDOLPHINPRIVATE_EXPORT KFileItemModel : public KItemModelBase
56 {
57 Q_OBJECT
58
59 public:
60 explicit KFileItemModel(KDirLister* dirLister, QObject* parent = 0);
61 virtual ~KFileItemModel();
62
63 virtual int count() const;
64 virtual QHash<QByteArray, QVariant> data(int index) const;
65 virtual bool setData(int index, const QHash<QByteArray, QVariant>& values);
66
67 /**
68 * Sets a separate sorting with folders first (true) or a mixed sorting of files and folders (false).
69 */
70 void setSortFoldersFirst(bool foldersFirst);
71 bool sortFoldersFirst() const;
72
73 void setShowHiddenFiles(bool show);
74 bool showHiddenFiles() const;
75
76 /**
77 * If set to true, only folders are shown as items of the model. Files
78 * are ignored.
79 */
80 void setShowFoldersOnly(bool enabled);
81 bool showFoldersOnly() const;
82
83 /** @reimp */
84 virtual QMimeData* createMimeData(const QSet<int>& indexes) const;
85
86 /** @reimp */
87 virtual int indexForKeyboardSearch(const QString& text, int startFromIndex = 0) const;
88
89 /** @reimp */
90 virtual bool supportsDropping(int index) const;
91
92 /** @reimp */
93 virtual QString roleDescription(const QByteArray& role) const;
94
95 /** @reimp */
96 virtual QList<QPair<int, QVariant> > groups() const;
97
98 /**
99 * @return The file-item for the index \a index. If the index is in a valid
100 * range it is assured that the file-item is not null. The runtime
101 * complexity of this call is O(1).
102 */
103 KFileItem fileItem(int index) const;
104
105 /**
106 * @return The file-item for the url \a url. If no file-item with the given
107 * URL is found KFileItem::isNull() will be true for the returned
108 * file-item. The runtime complexity of this call is O(1).
109 */
110 KFileItem fileItem(const KUrl& url) const;
111
112 /**
113 * @return The index for the file-item \a item. -1 is returned if no file-item
114 * is found or if the file-item is null. The runtime
115 * complexity of this call is O(1).
116 */
117 int index(const KFileItem& item) const;
118
119 /**
120 * @return The index for the URL \a url. -1 is returned if no file-item
121 * is found. The runtime complexity of this call is O(1).
122 */
123 int index(const KUrl& url) const;
124
125 /**
126 * @return Root item of all items.
127 */
128 KFileItem rootItem() const;
129
130 /**
131 * Clears all items of the model.
132 */
133 void clear();
134
135 // TODO: "name" + "isDir" is default in ctor
136 void setRoles(const QSet<QByteArray>& roles);
137 QSet<QByteArray> roles() const;
138
139 virtual bool setExpanded(int index, bool expanded);
140 virtual bool isExpanded(int index) const;
141 virtual bool isExpandable(int index) const;
142
143 QSet<KUrl> expandedUrls() const;
144
145 /**
146 * Marks the URLs in \a urls as subfolders which were expanded previously.
147 * They are re-expanded one by one each time the KDirLister's completed() signal is received.
148 * Note that a manual triggering of the KDirLister is required.
149 */
150 void restoreExpandedUrls(const QSet<KUrl>& urls);
151
152 /**
153 * Expands all parent-items of each URL given by \a urls.
154 */
155 void setExpanded(const QSet<KUrl>& urls);
156
157 void setNameFilter(const QString& nameFilter);
158 QString nameFilter() const;
159
160 signals:
161 /**
162 * Is emitted after the loading of a directory has been completed or new
163 * items have been inserted to an already loaded directory. Usually
164 * one or more itemsInserted() signals are emitted before loadingCompleted()
165 * (the only exception is loading an empty directory, where only a
166 * loadingCompleted() signal gets emitted).
167 */
168 void loadingCompleted();
169
170 protected:
171 virtual void onGroupedSortingChanged(bool current);
172 virtual void onSortRoleChanged(const QByteArray& current, const QByteArray& previous);
173 virtual void onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous);
174
175 private slots:
176 /**
177 * Resorts all items dependent on the set sortRole(), sortOrder()
178 * and foldersFirst() settings.
179 */
180 void resortAllItems();
181
182 void slotCompleted();
183 void slotCanceled();
184 void slotNewItems(const KFileItemList& items);
185 void slotItemsDeleted(const KFileItemList& items);
186 void slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items);
187 void slotClear();
188 void slotClear(const KUrl& url);
189 void slotNaturalSortingChanged();
190
191 void dispatchPendingItemsToInsert();
192
193 private:
194 enum Role {
195 NoRole,
196 NameRole,
197 SizeRole,
198 DateRole,
199 PermissionsRole,
200 OwnerRole,
201 GroupRole,
202 TypeRole,
203 DestinationRole,
204 PathRole,
205 CommentRole,
206 TagsRole,
207 RatingRole,
208 IsDirRole,
209 IsExpandedRole,
210 IsExpandableRole,
211 ExpansionLevelRole,
212 RolesCount // Mandatory last entry
213 };
214
215 struct ItemData
216 {
217 KFileItem item;
218 QHash<QByteArray, QVariant> values;
219 ItemData* parent;
220 };
221
222 void insertItems(const KFileItemList& items);
223 void removeItems(const KFileItemList& items);
224
225 /**
226 * Helper method for insertItems() and removeItems(): Creates
227 * a list of ItemData elements based on the given items.
228 * Note that the ItemData instances are created dynamically and
229 * must be deleted by the caller.
230 */
231 QList<ItemData*> createItemDataList(const KFileItemList& items) const;
232
233 void removeExpandedItems();
234
235 /**
236 * Resets all values from m_requestRole to false.
237 */
238 void resetRoles();
239
240 /**
241 * @return Role-index for the given role byte-array.
242 * Runtime complexity is O(1).
243 */
244 Role roleIndex(const QByteArray& role) const;
245
246 /**
247 * @return Role-byte-array for the given role-index.
248 * Runtime complexity is O(1).
249 */
250 QByteArray roleByteArray(Role role) const;
251
252 QHash<QByteArray, QVariant> retrieveData(const KFileItem& item) const;
253
254 /**
255 * @return True if the item-data \a a should be ordered before the item-data
256 * \b. The item-data may have different parent-items.
257 */
258 bool lessThan(const ItemData* a, const ItemData* b) const;
259
260 /**
261 * Helper method for lessThan() and expansionLevelsCompare(): Compares
262 * the passed item-data using m_sortRole as criteria. Both items must
263 * have the same parent item, otherwise the comparison will be wrong.
264 */
265 int sortRoleCompare(const ItemData* a, const ItemData* b) const;
266
267 /**
268 * Sorts the items by using lessThan() as comparison criteria.
269 * The merge sort algorithm is used to assure a worst-case
270 * of O(n * log(n)) and to keep the number of comparisons low.
271 */
272 void sort(QList<ItemData*>::iterator begin, QList<ItemData*>::iterator end);
273
274 /** Helper method for sort(). */
275 void merge(QList<ItemData*>::iterator begin,
276 QList<ItemData*>::iterator pivot,
277 QList<ItemData*>::iterator end);
278
279 /** Helper method for sort(). */
280 QList<ItemData*>::iterator lowerBound(QList<ItemData*>::iterator begin,
281 QList<ItemData*>::iterator end,
282 const ItemData* value);
283
284 /** Helper method for sort(). */
285 QList<ItemData*>::iterator upperBound(QList<ItemData*>::iterator begin,
286 QList<ItemData*>::iterator end,
287 const ItemData* value);
288 /** Helper method for sort(). */
289 void reverse(QList<ItemData*>::iterator begin, QList<ItemData*>::iterator end);
290
291 int stringCompare(const QString& a, const QString& b) const;
292
293 /**
294 * Compares the expansion level of both items. The "expansion level" is defined
295 * by the number of parent directories. However simply comparing just the numbers
296 * is not sufficient, it is also important to check the hierarchy for having
297 * a correct order like shown in a tree.
298 */
299 int expansionLevelsCompare(const ItemData* a, const ItemData* b) const;
300
301 /**
302 * Helper method for expansionLevelCompare().
303 */
304 QString subPath(const KFileItem& item,
305 const QString& itemPath,
306 int start,
307 bool* isDir) const;
308
309 bool useMaximumUpdateInterval() const;
310
311 QList<QPair<int, QVariant> > nameRoleGroups() const;
312 QList<QPair<int, QVariant> > sizeRoleGroups() const;
313 QList<QPair<int, QVariant> > dateRoleGroups() const;
314 QList<QPair<int, QVariant> > permissionRoleGroups() const;
315 QList<QPair<int, QVariant> > ratingRoleGroups() const;
316 QList<QPair<int, QVariant> > genericStringRoleGroups(const QByteArray& role) const;
317
318 /**
319 * Helper method for all xxxRoleGroups() methods to check whether the
320 * item with the given index is a child-item. A child-item is defined
321 * as item having an expansion-level > 0. All xxxRoleGroups() methods
322 * should skip the grouping if the item is a child-item (although
323 * KItemListView would be capable to show sub-groups in groups this
324 * results in visual clutter for most usecases).
325 */
326 bool isChildItem(int index) const;
327
328 /**
329 * @return Recursive list of child items that have \a item as upper most parent.
330 */
331 KFileItemList childItems(const KFileItem& item) const;
332
333 private:
334 QWeakPointer<KDirLister> m_dirLister;
335
336 bool m_naturalSorting;
337 bool m_sortFoldersFirst;
338
339 Role m_sortRole;
340 QSet<QByteArray> m_roles;
341 Qt::CaseSensitivity m_caseSensitivity;
342
343 QList<ItemData*> m_itemData;
344 QHash<KUrl, int> m_items; // Allows O(1) access for KFileItemModel::index(const KFileItem& item)
345
346 KFileItemModelFilter m_filter;
347 QSet<KFileItem> m_filteredItems; // Items that got hidden by KFileItemModel::setNameFilter()
348
349 bool m_requestRole[RolesCount];
350
351 QTimer* m_minimumUpdateIntervalTimer;
352 QTimer* m_maximumUpdateIntervalTimer;
353 QTimer* m_resortAllItemsTimer;
354 KFileItemList m_pendingItemsToInsert;
355 bool m_pendingEmitLoadingCompleted;
356
357 // Cache for KFileItemModel::groups()
358 mutable QList<QPair<int, QVariant> > m_groups;
359
360 // Stores the smallest expansion level of the root-URL. Is required to calculate
361 // the "expansionLevel" role in an efficient way. A value < 0 indicates a
362 // special meaning:
363 enum RootExpansionLevelTypes
364 {
365 // m_rootExpansionLevel is uninitialized and must be determined by checking
366 // the root URL from the KDirLister.
367 UninitializedRootExpansionLevel = -1,
368 // All items should be forced to get an expansion level of 0 even if they
369 // represent child items. This is useful for slaves that provide no parent items
370 // for child items like e.g. the search IO slaves.
371 ForceRootExpansionLevel = -2
372 };
373 mutable int m_rootExpansionLevel;
374
375 // Stores the URLs of the expanded folders.
376 QSet<KUrl> m_expandedUrls;
377
378 // URLs that must be expanded. The expanding is initially triggered in setExpanded()
379 // and done step after step in slotCompleted().
380 QSet<KUrl> m_urlsToExpand;
381
382 friend class KFileItemModelTest; // For unit testing
383 };
384
385 inline bool KFileItemModel::isChildItem(int index) const
386 {
387 return m_requestRole[ExpansionLevelRole] && m_itemData.at(index)->values.value("expansionLevel").toInt() > 0;
388 }
389
390 #endif
391
392