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/kitemmodelbase.h> |
27 | #include <kitemviews/private/kfileitemmodelfilter.h> |
28 | |
29 | #include <QHash> |
30 | #include <QSet> |
31 | |
32 | class KFileItemModelDirLister; |
33 | class QTimer; |
34 | |
35 | /** |
36 | * @brief KItemModelBase implementation for KFileItems. |
37 | * |
38 | * Allows to load items of a directory. Sorting and grouping of |
39 | * items are supported. Roles that are not part of KFileItem can |
40 | * be added with KFileItemModel::setData(). |
41 | * |
42 | * Recursive expansion of sub-directories is supported by |
43 | * KFileItemModel::setExpanded(). |
44 | */ |
45 | class LIBDOLPHINPRIVATE_EXPORT KFileItemModel : public KItemModelBase |
46 | { |
47 | Q_OBJECT |
48 | |
49 | public: |
50 | explicit KFileItemModel(QObject* parent = 0); |
51 | virtual ~KFileItemModel(); |
52 | |
53 | /** |
54 | * Loads the directory specified by \a url. The signals |
55 | * directoryLoadingStarted(), directoryLoadingProgress() and directoryLoadingCompleted() |
56 | * indicate the current state of the loading process. The items |
57 | * of the directory are added after the loading has been completed. |
58 | */ |
59 | void loadDirectory(const KUrl& url); |
60 | |
61 | /** |
62 | * Throws away all currently loaded items and refreshes the directory |
63 | * by reloading all items again. |
64 | */ |
65 | void refreshDirectory(const KUrl& url); |
66 | |
67 | /** |
68 | * @return Parent directory of the items that are shown. In case |
69 | * if a directory tree is shown, KFileItemModel::dir() returns |
70 | * the root-parent of all items. |
71 | * @see rootItem() |
72 | */ |
73 | KUrl directory() const; |
74 | |
75 | /** |
76 | * Cancels the loading of a directory which has been started by either |
77 | * loadDirectory() or refreshDirectory(). |
78 | */ |
79 | void cancelDirectoryLoading(); |
80 | |
81 | virtual int count() const; |
82 | virtual QHash<QByteArray, QVariant> data(int index) const; |
83 | virtual bool setData(int index, const QHash<QByteArray, QVariant>& values); |
84 | |
85 | /** |
86 | * Sets a separate sorting with directories first (true) or a mixed |
87 | * sorting of files and directories (false). |
88 | */ |
89 | void setSortDirectoriesFirst(bool dirsFirst); |
90 | bool sortDirectoriesFirst() const; |
91 | |
92 | void setShowHiddenFiles(bool show); |
93 | bool showHiddenFiles() const; |
94 | |
95 | /** |
96 | * If set to true, only directories are shown as items of the model. Files |
97 | * are ignored. |
98 | */ |
99 | void setShowDirectoriesOnly(bool enabled); |
100 | bool showDirectoriesOnly() const; |
101 | |
102 | /** @reimp */ |
103 | virtual QMimeData* createMimeData(const KItemSet& indexes) const; |
104 | |
105 | /** @reimp */ |
106 | virtual int indexForKeyboardSearch(const QString& text, int startFromIndex = 0) const; |
107 | |
108 | /** @reimp */ |
109 | virtual bool supportsDropping(int index) const; |
110 | |
111 | /** @reimp */ |
112 | virtual QString roleDescription(const QByteArray& role) const; |
113 | |
114 | /** @reimp */ |
115 | virtual QList<QPair<int, QVariant> > groups() const; |
116 | |
117 | /** |
118 | * @return The file-item for the index \a index. If the index is in a valid |
119 | * range it is assured that the file-item is not null. The runtime |
120 | * complexity of this call is O(1). |
121 | */ |
122 | KFileItem fileItem(int index) const; |
123 | |
124 | /** |
125 | * @return The file-item for the url \a url. If no file-item with the given |
126 | * URL is found KFileItem::isNull() will be true for the returned |
127 | * file-item. The runtime complexity of this call is O(1). |
128 | */ |
129 | KFileItem fileItem(const KUrl& url) const; |
130 | |
131 | /** |
132 | * @return The index for the file-item \a item. -1 is returned if no file-item |
133 | * is found or if the file-item is null. The amortized runtime |
134 | * complexity of this call is O(1). |
135 | */ |
136 | int index(const KFileItem& item) const; |
137 | |
138 | /** |
139 | * @return The index for the URL \a url. -1 is returned if no file-item |
140 | * is found. The amortized runtime complexity of this call is O(1). |
141 | */ |
142 | int index(const KUrl& url) const; |
143 | |
144 | /** |
145 | * @return Root item of all items representing the item |
146 | * for KFileItemModel::dir(). |
147 | */ |
148 | KFileItem rootItem() const; |
149 | |
150 | /** |
151 | * Clears all items of the model. |
152 | */ |
153 | void clear(); |
154 | |
155 | /** |
156 | * Sets the roles that should be shown for each item. |
157 | */ |
158 | void setRoles(const QSet<QByteArray>& roles); |
159 | QSet<QByteArray> roles() const; |
160 | |
161 | virtual bool setExpanded(int index, bool expanded); |
162 | virtual bool isExpanded(int index) const; |
163 | virtual bool isExpandable(int index) const; |
164 | virtual int expandedParentsCount(int index) const; |
165 | |
166 | QSet<KUrl> expandedDirectories() const; |
167 | |
168 | /** |
169 | * Marks the URLs in \a urls as sub-directories which were expanded previously. |
170 | * After calling loadDirectory() or refreshDirectory() the marked sub-directories |
171 | * will be expanded step-by-step. |
172 | */ |
173 | void restoreExpandedDirectories(const QSet<KUrl>& urls); |
174 | |
175 | /** |
176 | * Expands all parent-directories of the item \a url. |
177 | */ |
178 | void expandParentDirectories(const KUrl& url); |
179 | |
180 | void setNameFilter(const QString& nameFilter); |
181 | QString nameFilter() const; |
182 | |
183 | void setMimeTypeFilters(const QStringList& filters); |
184 | QStringList mimeTypeFilters() const; |
185 | |
186 | struct RoleInfo |
187 | { QByteArray role; |
188 | QString translation; |
189 | QString group; |
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 | 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 KUrl& oldUrl, const KUrl& 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 KUrl& url); |
261 | |
262 | protected: |
263 | virtual void onGroupedSortingChanged(bool current); |
264 | virtual void onSortRoleChanged(const QByteArray& current, const QByteArray& previous); |
265 | virtual void onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous); |
266 | |
267 | private slots: |
268 | /** |
269 | * Resorts all items dependent on the set sortRole(), sortOrder() |
270 | * and foldersFirst() settings. |
271 | */ |
272 | void resortAllItems(); |
273 | |
274 | void slotCompleted(); |
275 | void slotCanceled(); |
276 | void slotItemsAdded(const KUrl& directoryUrl, const KFileItemList& items); |
277 | void slotItemsDeleted(const KFileItemList& items); |
278 | void slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items); |
279 | void slotClear(); |
280 | void slotNaturalSortingChanged(); |
281 | |
282 | void dispatchPendingItemsToInsert(); |
283 | |
284 | private: |
285 | enum RoleType { |
286 | // User visible roles: |
287 | NoRole, NameRole, SizeRole, DateRole, PermissionsRole, OwnerRole, |
288 | GroupRole, TypeRole, DestinationRole, PathRole, |
289 | // User visible roles available with Baloo: |
290 | , TagsRole, RatingRole, ImageSizeRole, OrientationRole, |
291 | WordCountRole, LineCountRole, ArtistRole, AlbumRole, DurationRole, TrackRole, |
292 | CopiedFromRole, |
293 | // Non-visible roles: |
294 | IsDirRole, IsLinkRole, IsExpandedRole, IsExpandableRole, ExpandedParentsCountRole, |
295 | // Mandatory last entry: |
296 | RolesCount |
297 | }; |
298 | |
299 | struct ItemData |
300 | { |
301 | KFileItem item; |
302 | QHash<QByteArray, QVariant> values; |
303 | ItemData* parent; |
304 | }; |
305 | |
306 | enum RemoveItemsBehavior { |
307 | KeepItemData, |
308 | DeleteItemData |
309 | }; |
310 | |
311 | void insertItems(QList<ItemData*>& items); |
312 | void removeItems(const KItemRangeList& itemRanges, RemoveItemsBehavior behavior); |
313 | |
314 | /** |
315 | * Helper method for insertItems() and removeItems(): Creates |
316 | * a list of ItemData elements based on the given items. |
317 | * Note that the ItemData instances are created dynamically and |
318 | * must be deleted by the caller. |
319 | */ |
320 | QList<ItemData*> createItemDataList(const KUrl& parentUrl, const KFileItemList& items) const; |
321 | |
322 | /** |
323 | * Prepares the items for sorting. Normally, the hash 'values' in ItemData is filled |
324 | * lazily to save time and memory, but for some sort roles, it is expected that the |
325 | * sort role data is stored in 'values'. |
326 | */ |
327 | void prepareItemsForSorting(QList<ItemData*>& itemDataList); |
328 | |
329 | static int expandedParentsCount(const ItemData* data); |
330 | |
331 | void removeExpandedItems(); |
332 | |
333 | /** |
334 | * This function is called by setData() and slotRefreshItems(). It emits |
335 | * the itemsChanged() signal, checks if the sort order is still correct, |
336 | * and starts m_resortAllItemsTimer if that is not the case. |
337 | */ |
338 | void emitItemsChangedAndTriggerResorting(const KItemRangeList& itemRanges, const QSet<QByteArray>& changedRoles); |
339 | |
340 | /** |
341 | * Resets all values from m_requestRole to false. |
342 | */ |
343 | void resetRoles(); |
344 | |
345 | /** |
346 | * @return Role-type for the given role. |
347 | * Runtime complexity is O(1). |
348 | */ |
349 | RoleType typeForRole(const QByteArray& role) const; |
350 | |
351 | /** |
352 | * @return Role-byte-array for the given role-type. |
353 | * Runtime complexity is O(1). |
354 | */ |
355 | QByteArray roleForType(RoleType roleType) const; |
356 | |
357 | QHash<QByteArray, QVariant> retrieveData(const KFileItem& item, const ItemData* parent) const; |
358 | |
359 | /** |
360 | * @return True if \a a has a KFileItem whose text is 'less than' the one |
361 | * of \a b according to QString::operator<(const QString&). |
362 | */ |
363 | static bool nameLessThan(const ItemData* a, const ItemData* b); |
364 | |
365 | /** |
366 | * @return True if the item-data \a a should be ordered before the item-data |
367 | * \b. The item-data may have different parent-items. |
368 | */ |
369 | bool lessThan(const ItemData* a, const ItemData* b) const; |
370 | |
371 | /** |
372 | * Sorts the items between \a begin and \a end using the comparison |
373 | * function lessThan(). |
374 | */ |
375 | void sort(QList<ItemData*>::iterator begin, QList<ItemData*>::iterator end) const; |
376 | |
377 | /** |
378 | * Helper method for lessThan() and expandedParentsCountCompare(): Compares |
379 | * the passed item-data using m_sortRole as criteria. Both items must |
380 | * have the same parent item, otherwise the comparison will be wrong. |
381 | */ |
382 | int sortRoleCompare(const ItemData* a, const ItemData* b) const; |
383 | |
384 | int stringCompare(const QString& a, const QString& b) const; |
385 | |
386 | bool useMaximumUpdateInterval() const; |
387 | |
388 | QList<QPair<int, QVariant> > nameRoleGroups() const; |
389 | QList<QPair<int, QVariant> > sizeRoleGroups() const; |
390 | QList<QPair<int, QVariant> > dateRoleGroups() const; |
391 | QList<QPair<int, QVariant> > permissionRoleGroups() const; |
392 | QList<QPair<int, QVariant> > ratingRoleGroups() const; |
393 | QList<QPair<int, QVariant> > genericStringRoleGroups(const QByteArray& typeForRole) const; |
394 | |
395 | /** |
396 | * Helper method for all xxxRoleGroups() methods to check whether the |
397 | * item with the given index is a child-item. A child-item is defined |
398 | * as item having an expansion-level > 0. All xxxRoleGroups() methods |
399 | * should skip the grouping if the item is a child-item (although |
400 | * KItemListView would be capable to show sub-groups in groups this |
401 | * results in visual clutter for most usecases). |
402 | */ |
403 | bool isChildItem(int index) const; |
404 | |
405 | /** |
406 | * Is invoked by KFileItemModelRolesUpdater and results in emitting the |
407 | * sortProgress signal with a percent-value of the progress. |
408 | */ |
409 | void emitSortProgress(int resolvedCount); |
410 | |
411 | /** |
412 | * Applies the filters set through @ref setNameFilter and @ref setMimeTypeFilters. |
413 | */ |
414 | void applyFilters(); |
415 | |
416 | /** |
417 | * Removes filtered items whose expanded parents have been deleted |
418 | * or collapsed via setExpanded(parentIndex, false). |
419 | */ |
420 | void removeFilteredChildren(const KItemRangeList& parents); |
421 | |
422 | /** |
423 | * Maps the QByteArray-roles to RoleTypes and provides translation- and |
424 | * group-contexts. |
425 | */ |
426 | struct RoleInfoMap |
427 | { |
428 | const char* const role; |
429 | const RoleType roleType; |
430 | const char* const roleTranslationContext; |
431 | const char* const roleTranslation; |
432 | const char* const groupTranslationContext; |
433 | const char* const groupTranslation; |
434 | const bool requiresBaloo; |
435 | const bool requiresIndexer; |
436 | }; |
437 | |
438 | /** |
439 | * @return Map of user visible roles that are accessible by KFileItemModel::rolesInformation(). |
440 | */ |
441 | static const RoleInfoMap* rolesInfoMap(int& count); |
442 | |
443 | /** |
444 | * Determines the MIME-types of all items that can be done within |
445 | * the given timeout. |
446 | */ |
447 | static void determineMimeTypes(const KFileItemList& items, int timeout); |
448 | |
449 | /** |
450 | * @return Returns a copy of \a value that is implicitly shared |
451 | * with other users to save memory. |
452 | */ |
453 | static QByteArray sharedValue(const QByteArray& value); |
454 | |
455 | /** |
456 | * Checks if the model's internal data structures are consistent. |
457 | */ |
458 | bool isConsistent() const; |
459 | |
460 | private: |
461 | KFileItemModelDirLister* m_dirLister; |
462 | |
463 | bool m_naturalSorting; |
464 | bool m_sortDirsFirst; |
465 | |
466 | RoleType m_sortRole; |
467 | int m_sortingProgressPercent; // Value of directorySortingProgress() signal |
468 | QSet<QByteArray> m_roles; |
469 | Qt::CaseSensitivity m_caseSensitivity; |
470 | |
471 | QList<ItemData*> m_itemData; |
472 | |
473 | // m_items is a cache for the method index(const KUrl&). If it contains N |
474 | // entries, it is guaranteed that these correspond to the first N items in |
475 | // the model, i.e., that (for every i between 0 and N - 1) |
476 | // m_items.value(fileItem(i).url()) == i |
477 | mutable QHash<KUrl, int> m_items; |
478 | |
479 | KFileItemModelFilter m_filter; |
480 | QHash<KFileItem, ItemData*> m_filteredItems; // Items that got hidden by KFileItemModel::setNameFilter() |
481 | |
482 | bool m_requestRole[RolesCount]; |
483 | |
484 | QTimer* m_maximumUpdateIntervalTimer; |
485 | QTimer* m_resortAllItemsTimer; |
486 | QList<ItemData*> m_pendingItemsToInsert; |
487 | |
488 | // Cache for KFileItemModel::groups() |
489 | mutable QList<QPair<int, QVariant> > m_groups; |
490 | |
491 | // Stores the URLs (key: target url, value: url) of the expanded directories. |
492 | QHash<KUrl, KUrl> m_expandedDirs; |
493 | |
494 | // URLs that must be expanded. The expanding is initially triggered in setExpanded() |
495 | // and done step after step in slotCompleted(). |
496 | QSet<KUrl> m_urlsToExpand; |
497 | |
498 | friend class KFileItemModelLessThan; // Accesses lessThan() method |
499 | friend class KFileItemModelRolesUpdater; // Accesses emitSortProgress() method |
500 | friend class KFileItemModelTest; // For unit testing |
501 | friend class KFileItemModelBenchmark; // For unit testing |
502 | friend class KFileItemListViewTest; // For unit testing |
503 | friend class DolphinPart; // Accesses m_dirLister |
504 | }; |
505 | |
506 | inline bool KFileItemModel::nameLessThan(const ItemData* a, const ItemData* b) |
507 | { |
508 | return a->item.text() < b->item.text(); |
509 | } |
510 | |
511 | |
512 | inline bool KFileItemModel::isChildItem(int index) const |
513 | { |
514 | if (m_itemData.at(index)->parent) { |
515 | return true; |
516 | } else { |
517 | return false; |
518 | } |
519 | } |
520 | |
521 | #endif |
522 | |
523 | |
524 | |