]> cloud.milkyroute.net Git - dolphin.git/blob - src/panels/places/placesitemmodel.cpp
Merge branch 'Applications/14.12'
[dolphin.git] / src / panels / places / placesitemmodel.cpp
1 /***************************************************************************
2 * Copyright (C) 2012 by Peter Penz <peter.penz19@gmail.com> *
3 * *
4 * Based on KFilePlacesModel from kdelibs: *
5 * Copyright (C) 2007 Kevin Ottens <ervin@kde.org> *
6 * Copyright (C) 2007 David Faure <faure@kde.org> *
7 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program; if not, write to the *
20 * Free Software Foundation, Inc., *
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
22 ***************************************************************************/
23
24 #include "placesitemmodel.h"
25
26 #include "dolphin_generalsettings.h"
27
28 #include <KBookmark>
29 #include <KBookmarkGroup>
30 #include <KBookmarkManager>
31 #include <KComponentData>
32 #include <KDebug>
33 #include <KIcon>
34 #include <kprotocolinfo.h>
35 #include <KLocale>
36 #include <KStandardDirs>
37 #include <KUser>
38 #include "placesitem.h"
39 #include <QAction>
40 #include <QDate>
41 #include <QMimeData>
42 #include <QTimer>
43
44 #include <Solid/Device>
45 #include <Solid/DeviceNotifier>
46 #include <Solid/OpticalDisc>
47 #include <Solid/OpticalDrive>
48 #include <Solid/StorageAccess>
49 #include <Solid/StorageDrive>
50
51 #include <views/dolphinview.h>
52 #include <views/viewproperties.h>
53
54 #ifdef HAVE_BALOO
55 #include <baloo/query.h>
56 #include <baloo/indexerconfig.h>
57 #endif
58
59 namespace {
60 // As long as KFilePlacesView from kdelibs is available in parallel, the
61 // system-bookmarks for "Recently Saved" and "Search For" should be
62 // shown only inside the Places Panel. This is necessary as the stored
63 // URLs needs to get translated to a Baloo-search-URL on-the-fly to
64 // be independent from changes in the Baloo-search-URL-syntax.
65 // Hence a prefix to the application-name of the stored bookmarks is
66 // added, which is only read by PlacesItemModel.
67 const char* AppNamePrefix = "-places-panel";
68 }
69
70 PlacesItemModel::PlacesItemModel(QObject* parent) :
71 KStandardItemModel(parent),
72 m_fileIndexingEnabled(false),
73 m_hiddenItemsShown(false),
74 m_availableDevices(),
75 m_predicate(),
76 m_bookmarkManager(0),
77 m_systemBookmarks(),
78 m_systemBookmarksIndexes(),
79 m_bookmarkedItems(),
80 m_hiddenItemToRemove(-1),
81 m_updateBookmarksTimer(0),
82 m_storageSetupInProgress()
83 {
84 #ifdef HAVE_BALOO
85 Baloo::IndexerConfig config;
86 m_fileIndexingEnabled = config.fileIndexingEnabled();
87 #endif
88 const QString file = KStandardDirs::locateLocal("data", "kfileplaces/bookmarks.xml");
89 m_bookmarkManager = KBookmarkManager::managerForFile(file, "kfilePlaces");
90
91 createSystemBookmarks();
92 initializeAvailableDevices();
93 loadBookmarks();
94
95 const int syncBookmarksTimeout = 100;
96
97 m_updateBookmarksTimer = new QTimer(this);
98 m_updateBookmarksTimer->setInterval(syncBookmarksTimeout);
99 m_updateBookmarksTimer->setSingleShot(true);
100 connect(m_updateBookmarksTimer, SIGNAL(timeout()), this, SLOT(updateBookmarks()));
101
102 connect(m_bookmarkManager, SIGNAL(changed(QString,QString)),
103 m_updateBookmarksTimer, SLOT(start()));
104 connect(m_bookmarkManager, SIGNAL(bookmarksChanged(QString)),
105 m_updateBookmarksTimer, SLOT(start()));
106 }
107
108 PlacesItemModel::~PlacesItemModel()
109 {
110 saveBookmarks();
111 qDeleteAll(m_bookmarkedItems);
112 m_bookmarkedItems.clear();
113 }
114
115 PlacesItem* PlacesItemModel::createPlacesItem(const QString& text,
116 const KUrl& url,
117 const QString& iconName)
118 {
119 const KBookmark bookmark = PlacesItem::createBookmark(m_bookmarkManager, text, url, iconName);
120 return new PlacesItem(bookmark);
121 }
122
123 PlacesItem* PlacesItemModel::placesItem(int index) const
124 {
125 return dynamic_cast<PlacesItem*>(item(index));
126 }
127
128 int PlacesItemModel::hiddenCount() const
129 {
130 int modelIndex = 0;
131 int hiddenItemCount = 0;
132 foreach (const PlacesItem* item, m_bookmarkedItems) {
133 if (item) {
134 ++hiddenItemCount;
135 } else {
136 if (placesItem(modelIndex)->isHidden()) {
137 ++hiddenItemCount;
138 }
139 ++modelIndex;
140 }
141 }
142
143 return hiddenItemCount;
144 }
145
146 void PlacesItemModel::setHiddenItemsShown(bool show)
147 {
148 if (m_hiddenItemsShown == show) {
149 return;
150 }
151
152 m_hiddenItemsShown = show;
153
154 if (show) {
155 // Move all items that are part of m_bookmarkedItems to the model.
156 QList<PlacesItem*> itemsToInsert;
157 QList<int> insertPos;
158 int modelIndex = 0;
159 for (int i = 0; i < m_bookmarkedItems.count(); ++i) {
160 if (m_bookmarkedItems[i]) {
161 itemsToInsert.append(m_bookmarkedItems[i]);
162 m_bookmarkedItems[i] = 0;
163 insertPos.append(modelIndex);
164 }
165 ++modelIndex;
166 }
167
168 // Inserting the items will automatically insert an item
169 // to m_bookmarkedItems in PlacesItemModel::onItemsInserted().
170 // The items are temporary saved in itemsToInsert, so
171 // m_bookmarkedItems can be shrinked now.
172 m_bookmarkedItems.erase(m_bookmarkedItems.begin(),
173 m_bookmarkedItems.begin() + itemsToInsert.count());
174
175 for (int i = 0; i < itemsToInsert.count(); ++i) {
176 insertItem(insertPos[i], itemsToInsert[i]);
177 }
178
179 Q_ASSERT(m_bookmarkedItems.count() == count());
180 } else {
181 // Move all items of the model, where the "isHidden" property is true, to
182 // m_bookmarkedItems.
183 Q_ASSERT(m_bookmarkedItems.count() == count());
184 for (int i = count() - 1; i >= 0; --i) {
185 if (placesItem(i)->isHidden()) {
186 hideItem(i);
187 }
188 }
189 }
190
191 #ifdef PLACESITEMMODEL_DEBUG
192 kDebug() << "Changed visibility of hidden items";
193 showModelState();
194 #endif
195 }
196
197 bool PlacesItemModel::hiddenItemsShown() const
198 {
199 return m_hiddenItemsShown;
200 }
201
202 int PlacesItemModel::closestItem(const KUrl& url) const
203 {
204 int foundIndex = -1;
205 int maxLength = 0;
206
207 for (int i = 0; i < count(); ++i) {
208 const KUrl itemUrl = placesItem(i)->url();
209 if (itemUrl.isParentOf(url)) {
210 const int length = itemUrl.prettyUrl().length();
211 if (length > maxLength) {
212 foundIndex = i;
213 maxLength = length;
214 }
215 }
216 }
217
218 return foundIndex;
219 }
220
221 void PlacesItemModel::appendItemToGroup(PlacesItem* item)
222 {
223 if (!item) {
224 return;
225 }
226
227 int i = 0;
228 while (i < count() && placesItem(i)->group() != item->group()) {
229 ++i;
230 }
231
232 bool inserted = false;
233 while (!inserted && i < count()) {
234 if (placesItem(i)->group() != item->group()) {
235 insertItem(i, item);
236 inserted = true;
237 }
238 ++i;
239 }
240
241 if (!inserted) {
242 appendItem(item);
243 }
244 }
245
246
247 QAction* PlacesItemModel::ejectAction(int index) const
248 {
249 const PlacesItem* item = placesItem(index);
250 if (item && item->device().is<Solid::OpticalDisc>()) {
251 return new QAction(KIcon("media-eject"), i18nc("@item", "Eject '%1'", item->text()), 0);
252 }
253
254 return 0;
255 }
256
257 QAction* PlacesItemModel::teardownAction(int index) const
258 {
259 const PlacesItem* item = placesItem(index);
260 if (!item) {
261 return 0;
262 }
263
264 Solid::Device device = item->device();
265 const bool providesTearDown = device.is<Solid::StorageAccess>() &&
266 device.as<Solid::StorageAccess>()->isAccessible();
267 if (!providesTearDown) {
268 return 0;
269 }
270
271 Solid::StorageDrive* drive = device.as<Solid::StorageDrive>();
272 if (!drive) {
273 drive = device.parent().as<Solid::StorageDrive>();
274 }
275
276 bool hotPluggable = false;
277 bool removable = false;
278 if (drive) {
279 hotPluggable = drive->isHotpluggable();
280 removable = drive->isRemovable();
281 }
282
283 QString iconName;
284 QString text;
285 const QString label = item->text();
286 if (device.is<Solid::OpticalDisc>()) {
287 text = i18nc("@item", "Release '%1'", label);
288 } else if (removable || hotPluggable) {
289 text = i18nc("@item", "Safely Remove '%1'", label);
290 iconName = "media-eject";
291 } else {
292 text = i18nc("@item", "Unmount '%1'", label);
293 iconName = "media-eject";
294 }
295
296 if (iconName.isEmpty()) {
297 return new QAction(text, 0);
298 }
299
300 return new QAction(KIcon(iconName), text, 0);
301 }
302
303 void PlacesItemModel::requestEject(int index)
304 {
305 const PlacesItem* item = placesItem(index);
306 if (item) {
307 Solid::OpticalDrive* drive = item->device().parent().as<Solid::OpticalDrive>();
308 if (drive) {
309 connect(drive, SIGNAL(ejectDone(Solid::ErrorType,QVariant,QString)),
310 this, SLOT(slotStorageTeardownDone(Solid::ErrorType,QVariant)),
311 Qt::UniqueConnection);
312 drive->eject();
313 } else {
314 const QString label = item->text();
315 const QString message = i18nc("@info", "The device '%1' is not a disk and cannot be ejected.", label);
316 emit errorMessage(message);
317 }
318 }
319 }
320
321 void PlacesItemModel::requestTeardown(int index)
322 {
323 const PlacesItem* item = placesItem(index);
324 if (item) {
325 Solid::StorageAccess* access = item->device().as<Solid::StorageAccess>();
326 if (access) {
327 connect(access, SIGNAL(teardownDone(Solid::ErrorType,QVariant,QString)),
328 this, SLOT(slotStorageTeardownDone(Solid::ErrorType,QVariant)),
329 Qt::UniqueConnection);
330 access->teardown();
331 }
332 }
333 }
334
335 bool PlacesItemModel::storageSetupNeeded(int index) const
336 {
337 const PlacesItem* item = placesItem(index);
338 return item ? item->storageSetupNeeded() : false;
339 }
340
341 void PlacesItemModel::requestStorageSetup(int index)
342 {
343 const PlacesItem* item = placesItem(index);
344 if (!item) {
345 return;
346 }
347
348 Solid::Device device = item->device();
349 const bool setup = device.is<Solid::StorageAccess>()
350 && !m_storageSetupInProgress.contains(device.as<Solid::StorageAccess>())
351 && !device.as<Solid::StorageAccess>()->isAccessible();
352 if (setup) {
353 Solid::StorageAccess* access = device.as<Solid::StorageAccess>();
354
355 m_storageSetupInProgress[access] = index;
356
357 connect(access, SIGNAL(setupDone(Solid::ErrorType,QVariant,QString)),
358 this, SLOT(slotStorageSetupDone(Solid::ErrorType,QVariant,QString)),
359 Qt::UniqueConnection);
360
361 access->setup();
362 }
363 }
364
365 QMimeData* PlacesItemModel::createMimeData(const KItemSet& indexes) const
366 {
367 KUrl::List urls;
368 QByteArray itemData;
369
370 QDataStream stream(&itemData, QIODevice::WriteOnly);
371
372 foreach (int index, indexes) {
373 const KUrl itemUrl = placesItem(index)->url();
374 if (itemUrl.isValid()) {
375 urls << itemUrl;
376 }
377 stream << index;
378 }
379
380 QMimeData* mimeData = new QMimeData();
381 if (!urls.isEmpty()) {
382 urls.populateMimeData(mimeData);
383 }
384 mimeData->setData(internalMimeType(), itemData);
385
386 return mimeData;
387 }
388
389 bool PlacesItemModel::supportsDropping(int index) const
390 {
391 return index >= 0 && index < count();
392 }
393
394 void PlacesItemModel::dropMimeDataBefore(int index, const QMimeData* mimeData)
395 {
396 if (mimeData->hasFormat(internalMimeType())) {
397 // The item has been moved inside the view
398 QByteArray itemData = mimeData->data(internalMimeType());
399 QDataStream stream(&itemData, QIODevice::ReadOnly);
400 int oldIndex;
401 stream >> oldIndex;
402 if (oldIndex == index || oldIndex == index - 1) {
403 // No moving has been done
404 return;
405 }
406
407 PlacesItem* oldItem = placesItem(oldIndex);
408 if (!oldItem) {
409 return;
410 }
411
412 PlacesItem* newItem = new PlacesItem(oldItem->bookmark());
413 removeItem(oldIndex);
414
415 if (oldIndex < index) {
416 --index;
417 }
418
419 const int dropIndex = groupedDropIndex(index, newItem);
420 insertItem(dropIndex, newItem);
421 } else if (mimeData->hasFormat("text/uri-list")) {
422 // One or more items must be added to the model
423 const KUrl::List urls = KUrl::List::fromMimeData(mimeData);
424 for (int i = urls.count() - 1; i >= 0; --i) {
425 const KUrl& url = urls[i];
426
427 QString text = url.fileName();
428 if (text.isEmpty()) {
429 text = url.host();
430 }
431
432 if ((url.isLocalFile() && !QFileInfo(url.toLocalFile()).isDir())
433 || url.protocol() == "trash") {
434 // Only directories outside the trash are allowed
435 continue;
436 }
437
438 PlacesItem* newItem = createPlacesItem(text, url);
439 const int dropIndex = groupedDropIndex(index, newItem);
440 insertItem(dropIndex, newItem);
441 }
442 }
443 }
444
445 KUrl PlacesItemModel::convertedUrl(const KUrl& url)
446 {
447 KUrl newUrl = url;
448 if (url.protocol() == QLatin1String("timeline")) {
449 newUrl = createTimelineUrl(url);
450 } else if (url.protocol() == QLatin1String("search")) {
451 newUrl = createSearchUrl(url);
452 }
453
454 return newUrl;
455 }
456
457 void PlacesItemModel::onItemInserted(int index)
458 {
459 const PlacesItem* insertedItem = placesItem(index);
460 if (insertedItem) {
461 // Take care to apply the PlacesItemModel-order of the inserted item
462 // also to the bookmark-manager.
463 const KBookmark insertedBookmark = insertedItem->bookmark();
464
465 const PlacesItem* previousItem = placesItem(index - 1);
466 KBookmark previousBookmark;
467 if (previousItem) {
468 previousBookmark = previousItem->bookmark();
469 }
470
471 m_bookmarkManager->root().moveBookmark(insertedBookmark, previousBookmark);
472 }
473
474 if (index == count() - 1) {
475 // The item has been appended as last item to the list. In this
476 // case assure that it is also appended after the hidden items and
477 // not before (like done otherwise).
478 m_bookmarkedItems.append(0);
479 } else {
480
481 int modelIndex = -1;
482 int bookmarkIndex = 0;
483 while (bookmarkIndex < m_bookmarkedItems.count()) {
484 if (!m_bookmarkedItems[bookmarkIndex]) {
485 ++modelIndex;
486 if (modelIndex + 1 == index) {
487 break;
488 }
489 }
490 ++bookmarkIndex;
491 }
492 m_bookmarkedItems.insert(bookmarkIndex, 0);
493 }
494
495 #ifdef PLACESITEMMODEL_DEBUG
496 kDebug() << "Inserted item" << index;
497 showModelState();
498 #endif
499 }
500
501 void PlacesItemModel::onItemRemoved(int index, KStandardItem* removedItem)
502 {
503 PlacesItem* placesItem = dynamic_cast<PlacesItem*>(removedItem);
504 if (placesItem) {
505 const KBookmark bookmark = placesItem->bookmark();
506 m_bookmarkManager->root().deleteBookmark(bookmark);
507 }
508
509 const int boomarkIndex = bookmarkIndex(index);
510 Q_ASSERT(!m_bookmarkedItems[boomarkIndex]);
511 m_bookmarkedItems.removeAt(boomarkIndex);
512
513 #ifdef PLACESITEMMODEL_DEBUG
514 kDebug() << "Removed item" << index;
515 showModelState();
516 #endif
517 }
518
519 void PlacesItemModel::onItemChanged(int index, const QSet<QByteArray>& changedRoles)
520 {
521 const PlacesItem* changedItem = placesItem(index);
522 if (changedItem) {
523 // Take care to apply the PlacesItemModel-order of the changed item
524 // also to the bookmark-manager.
525 const KBookmark insertedBookmark = changedItem->bookmark();
526
527 const PlacesItem* previousItem = placesItem(index - 1);
528 KBookmark previousBookmark;
529 if (previousItem) {
530 previousBookmark = previousItem->bookmark();
531 }
532
533 m_bookmarkManager->root().moveBookmark(insertedBookmark, previousBookmark);
534 }
535
536 if (changedRoles.contains("isHidden")) {
537 if (!m_hiddenItemsShown && changedItem->isHidden()) {
538 m_hiddenItemToRemove = index;
539 QTimer::singleShot(0, this, SLOT(hideItem()));
540 }
541 }
542 }
543
544 void PlacesItemModel::slotDeviceAdded(const QString& udi)
545 {
546 const Solid::Device device(udi);
547
548 if (!m_predicate.matches(device)) {
549 return;
550 }
551
552 m_availableDevices << udi;
553 const KBookmark bookmark = PlacesItem::createDeviceBookmark(m_bookmarkManager, udi);
554 appendItem(new PlacesItem(bookmark));
555 }
556
557 void PlacesItemModel::slotDeviceRemoved(const QString& udi)
558 {
559 if (!m_availableDevices.contains(udi)) {
560 return;
561 }
562
563 for (int i = 0; i < m_bookmarkedItems.count(); ++i) {
564 PlacesItem* item = m_bookmarkedItems[i];
565 if (item && item->udi() == udi) {
566 m_bookmarkedItems.removeAt(i);
567 delete item;
568 return;
569 }
570 }
571
572 for (int i = 0; i < count(); ++i) {
573 if (placesItem(i)->udi() == udi) {
574 removeItem(i);
575 return;
576 }
577 }
578 }
579
580 void PlacesItemModel::slotStorageTeardownDone(Solid::ErrorType error, const QVariant& errorData)
581 {
582 if (error && errorData.isValid()) {
583 emit errorMessage(errorData.toString());
584 }
585 }
586
587 void PlacesItemModel::slotStorageSetupDone(Solid::ErrorType error,
588 const QVariant& errorData,
589 const QString& udi)
590 {
591 Q_UNUSED(udi);
592
593 Q_ASSERT(!m_storageSetupInProgress.isEmpty());
594 const int index = m_storageSetupInProgress.take(sender());
595 const PlacesItem* item = placesItem(index);
596 if (!item) {
597 return;
598 }
599
600 if (error) {
601 if (errorData.isValid()) {
602 emit errorMessage(i18nc("@info", "An error occurred while accessing '%1', the system responded: %2",
603 item->text(),
604 errorData.toString()));
605 } else {
606 emit errorMessage(i18nc("@info", "An error occurred while accessing '%1'",
607 item->text()));
608 }
609 emit storageSetupDone(index, false);
610 } else {
611 emit storageSetupDone(index, true);
612 }
613 }
614
615 void PlacesItemModel::hideItem()
616 {
617 hideItem(m_hiddenItemToRemove);
618 m_hiddenItemToRemove = -1;
619 }
620
621 void PlacesItemModel::updateBookmarks()
622 {
623 // Verify whether new bookmarks have been added or existing
624 // bookmarks have been changed.
625 KBookmarkGroup root = m_bookmarkManager->root();
626 KBookmark newBookmark = root.first();
627 while (!newBookmark.isNull()) {
628 if (acceptBookmark(newBookmark, m_availableDevices)) {
629 bool found = false;
630 int modelIndex = 0;
631 for (int i = 0; i < m_bookmarkedItems.count(); ++i) {
632 PlacesItem* item = m_bookmarkedItems[i];
633 if (!item) {
634 item = placesItem(modelIndex);
635 ++modelIndex;
636 }
637
638 const KBookmark oldBookmark = item->bookmark();
639 if (equalBookmarkIdentifiers(newBookmark, oldBookmark)) {
640 // The bookmark has been found in the model or as
641 // a hidden item. The content of the bookmark might
642 // have been changed, so an update is done.
643 found = true;
644 if (newBookmark.metaDataItem("UDI").isEmpty()) {
645 item->setBookmark(newBookmark);
646 item->setText(i18nc("KFile System Bookmarks", newBookmark.text().toUtf8().data()));
647 }
648 break;
649 }
650 }
651
652 if (!found) {
653 const QString udi = newBookmark.metaDataItem("UDI");
654
655 /*
656 * See Bug 304878
657 * Only add a new places item, if the item text is not empty
658 * and if the device is available. Fixes the strange behaviour -
659 * add a places item without text in the Places section - when you
660 * remove a device (e.g. a usb stick) without unmounting.
661 */
662 if (udi.isEmpty() || Solid::Device(udi).isValid()) {
663 PlacesItem* item = new PlacesItem(newBookmark);
664 if (item->isHidden() && !m_hiddenItemsShown) {
665 m_bookmarkedItems.append(item);
666 } else {
667 appendItemToGroup(item);
668 }
669 }
670 }
671 }
672
673 newBookmark = root.next(newBookmark);
674 }
675
676 // Remove items that are not part of the bookmark-manager anymore
677 int modelIndex = 0;
678 for (int i = m_bookmarkedItems.count() - 1; i >= 0; --i) {
679 PlacesItem* item = m_bookmarkedItems[i];
680 const bool itemIsPartOfModel = (item == 0);
681 if (itemIsPartOfModel) {
682 item = placesItem(modelIndex);
683 }
684
685 bool hasBeenRemoved = true;
686 const KBookmark oldBookmark = item->bookmark();
687 KBookmark newBookmark = root.first();
688 while (!newBookmark.isNull()) {
689 if (equalBookmarkIdentifiers(newBookmark, oldBookmark)) {
690 hasBeenRemoved = false;
691 break;
692 }
693 newBookmark = root.next(newBookmark);
694 }
695
696 if (hasBeenRemoved) {
697 if (m_bookmarkedItems[i]) {
698 delete m_bookmarkedItems[i];
699 m_bookmarkedItems.removeAt(i);
700 } else {
701 removeItem(modelIndex);
702 --modelIndex;
703 }
704 }
705
706 if (itemIsPartOfModel) {
707 ++modelIndex;
708 }
709 }
710 }
711
712 void PlacesItemModel::saveBookmarks()
713 {
714 m_bookmarkManager->emitChanged(m_bookmarkManager->root());
715 }
716
717 void PlacesItemModel::loadBookmarks()
718 {
719 KBookmarkGroup root = m_bookmarkManager->root();
720 KBookmark bookmark = root.first();
721 QSet<QString> devices = m_availableDevices;
722
723 QSet<KUrl> missingSystemBookmarks;
724 foreach (const SystemBookmarkData& data, m_systemBookmarks) {
725 missingSystemBookmarks.insert(data.url);
726 }
727
728 // The bookmarks might have a mixed order of places, devices and search-groups due
729 // to the compatibility with the KFilePlacesPanel. In Dolphin's places panel the
730 // items should always be collected in one group so the items are collected first
731 // in separate lists before inserting them.
732 QList<PlacesItem*> placesItems;
733 QList<PlacesItem*> recentlySavedItems;
734 QList<PlacesItem*> searchForItems;
735 QList<PlacesItem*> devicesItems;
736
737 while (!bookmark.isNull()) {
738 if (acceptBookmark(bookmark, devices)) {
739 PlacesItem* item = new PlacesItem(bookmark);
740 if (item->groupType() == PlacesItem::DevicesType) {
741 devices.remove(item->udi());
742 devicesItems.append(item);
743 } else {
744 const KUrl url = bookmark.url();
745 if (missingSystemBookmarks.contains(url)) {
746 missingSystemBookmarks.remove(url);
747
748 // Try to retranslate the text of system bookmarks to have translated
749 // items when changing the language. In case if the user has applied a custom
750 // text, the retranslation will fail and the users custom text is still used.
751 // It is important to use "KFile System Bookmarks" as context (see
752 // createSystemBookmarks()).
753 item->setText(i18nc("KFile System Bookmarks", bookmark.text().toUtf8().data()));
754 item->setSystemItem(true);
755 }
756
757 switch (item->groupType()) {
758 case PlacesItem::PlacesType: placesItems.append(item); break;
759 case PlacesItem::RecentlySavedType: recentlySavedItems.append(item); break;
760 case PlacesItem::SearchForType: searchForItems.append(item); break;
761 case PlacesItem::DevicesType:
762 default: Q_ASSERT(false); break;
763 }
764 }
765 }
766
767 bookmark = root.next(bookmark);
768 }
769
770 if (!missingSystemBookmarks.isEmpty()) {
771 // The current bookmarks don't contain all system-bookmarks. Add the missing
772 // bookmarks.
773 foreach (const SystemBookmarkData& data, m_systemBookmarks) {
774 if (missingSystemBookmarks.contains(data.url)) {
775 PlacesItem* item = createSystemPlacesItem(data);
776 switch (item->groupType()) {
777 case PlacesItem::PlacesType: placesItems.append(item); break;
778 case PlacesItem::RecentlySavedType: recentlySavedItems.append(item); break;
779 case PlacesItem::SearchForType: searchForItems.append(item); break;
780 case PlacesItem::DevicesType:
781 default: Q_ASSERT(false); break;
782 }
783 }
784 }
785 }
786
787 // Create items for devices that have not been stored as bookmark yet
788 foreach (const QString& udi, devices) {
789 const KBookmark bookmark = PlacesItem::createDeviceBookmark(m_bookmarkManager, udi);
790 devicesItems.append(new PlacesItem(bookmark));
791 }
792
793 QList<PlacesItem*> items;
794 items.append(placesItems);
795 items.append(recentlySavedItems);
796 items.append(searchForItems);
797 items.append(devicesItems);
798
799 foreach (PlacesItem* item, items) {
800 if (!m_hiddenItemsShown && item->isHidden()) {
801 m_bookmarkedItems.append(item);
802 } else {
803 appendItem(item);
804 }
805 }
806
807 #ifdef PLACESITEMMODEL_DEBUG
808 kDebug() << "Loaded bookmarks";
809 showModelState();
810 #endif
811 }
812
813 bool PlacesItemModel::acceptBookmark(const KBookmark& bookmark,
814 const QSet<QString>& availableDevices) const
815 {
816 const QString udi = bookmark.metaDataItem("UDI");
817 const KUrl url = bookmark.url();
818 const QString appName = bookmark.metaDataItem("OnlyInApp");
819 const bool deviceAvailable = availableDevices.contains(udi);
820
821 const bool allowedHere = (appName.isEmpty()
822 || appName == KGlobal::mainComponent().componentName()
823 || appName == KGlobal::mainComponent().componentName() + AppNamePrefix)
824 && (m_fileIndexingEnabled || (url.protocol() != QLatin1String("timeline") &&
825 url.protocol() != QLatin1String("search")));
826
827 return (udi.isEmpty() && allowedHere) || deviceAvailable;
828 }
829
830 PlacesItem* PlacesItemModel::createSystemPlacesItem(const SystemBookmarkData& data)
831 {
832 KBookmark bookmark = PlacesItem::createBookmark(m_bookmarkManager,
833 data.text,
834 data.url,
835 data.icon);
836
837 const QString protocol = data.url.protocol();
838 if (protocol == QLatin1String("timeline") || protocol == QLatin1String("search")) {
839 // As long as the KFilePlacesView from kdelibs is available, the system-bookmarks
840 // for "Recently Saved" and "Search For" should be a setting available only
841 // in the Places Panel (see description of AppNamePrefix for more details).
842 const QString appName = KGlobal::mainComponent().componentName() + AppNamePrefix;
843 bookmark.setMetaDataItem("OnlyInApp", appName);
844 }
845
846 PlacesItem* item = new PlacesItem(bookmark);
847 item->setSystemItem(true);
848
849 // Create default view-properties for all "Search For" and "Recently Saved" bookmarks
850 // in case if the user has not already created custom view-properties for a corresponding
851 // query yet.
852 const bool createDefaultViewProperties = (item->groupType() == PlacesItem::SearchForType ||
853 item->groupType() == PlacesItem::RecentlySavedType) &&
854 !GeneralSettings::self()->globalViewProps();
855 if (createDefaultViewProperties) {
856 ViewProperties props(convertedUrl(data.url));
857 if (!props.exist()) {
858 const QString path = data.url.path();
859 if (path == QLatin1String("/documents")) {
860 props.setViewMode(DolphinView::DetailsView);
861 props.setPreviewsShown(false);
862 props.setVisibleRoles(QList<QByteArray>() << "text" << "path");
863 } else if (path == QLatin1String("/images")) {
864 props.setViewMode(DolphinView::IconsView);
865 props.setPreviewsShown(true);
866 props.setVisibleRoles(QList<QByteArray>() << "text" << "imageSize");
867 } else if (path == QLatin1String("/audio")) {
868 props.setViewMode(DolphinView::DetailsView);
869 props.setPreviewsShown(false);
870 props.setVisibleRoles(QList<QByteArray>() << "text" << "artist" << "album");
871 } else if (path == QLatin1String("/videos")) {
872 props.setViewMode(DolphinView::IconsView);
873 props.setPreviewsShown(true);
874 props.setVisibleRoles(QList<QByteArray>() << "text");
875 } else if (data.url.protocol() == "timeline") {
876 props.setViewMode(DolphinView::DetailsView);
877 props.setVisibleRoles(QList<QByteArray>() << "text" << "date");
878 }
879 }
880 }
881
882 return item;
883 }
884
885 void PlacesItemModel::createSystemBookmarks()
886 {
887 Q_ASSERT(m_systemBookmarks.isEmpty());
888 Q_ASSERT(m_systemBookmarksIndexes.isEmpty());
889
890 // Note: The context of the I18N_NOOP2 must be "KFile System Bookmarks". The real
891 // i18nc call is done after reading the bookmark. The reason why the i18nc call is not
892 // done here is because otherwise switching the language would not result in retranslating the
893 // bookmarks.
894 m_systemBookmarks.append(SystemBookmarkData(KUrl(KUser().homeDir()),
895 "user-home",
896 I18N_NOOP2("KFile System Bookmarks", "Home")));
897 m_systemBookmarks.append(SystemBookmarkData(KUrl("remote:/"),
898 "network-workgroup",
899 I18N_NOOP2("KFile System Bookmarks", "Network")));
900 m_systemBookmarks.append(SystemBookmarkData(KUrl("/"),
901 "folder-red",
902 I18N_NOOP2("KFile System Bookmarks", "Root")));
903 m_systemBookmarks.append(SystemBookmarkData(KUrl("trash:/"),
904 "user-trash",
905 I18N_NOOP2("KFile System Bookmarks", "Trash")));
906
907 if (m_fileIndexingEnabled) {
908 m_systemBookmarks.append(SystemBookmarkData(KUrl("timeline:/today"),
909 "go-jump-today",
910 I18N_NOOP2("KFile System Bookmarks", "Today")));
911 m_systemBookmarks.append(SystemBookmarkData(KUrl("timeline:/yesterday"),
912 "view-calendar-day",
913 I18N_NOOP2("KFile System Bookmarks", "Yesterday")));
914 m_systemBookmarks.append(SystemBookmarkData(KUrl("timeline:/thismonth"),
915 "view-calendar-month",
916 I18N_NOOP2("KFile System Bookmarks", "This Month")));
917 m_systemBookmarks.append(SystemBookmarkData(KUrl("timeline:/lastmonth"),
918 "view-calendar-month",
919 I18N_NOOP2("KFile System Bookmarks", "Last Month")));
920 m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/documents"),
921 "folder-txt",
922 I18N_NOOP2("KFile System Bookmarks", "Documents")));
923 m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/images"),
924 "folder-image",
925 I18N_NOOP2("KFile System Bookmarks", "Images")));
926 m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/audio"),
927 "folder-sound",
928 I18N_NOOP2("KFile System Bookmarks", "Audio Files")));
929 m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/videos"),
930 "folder-video",
931 I18N_NOOP2("KFile System Bookmarks", "Videos")));
932 }
933
934 for (int i = 0; i < m_systemBookmarks.count(); ++i) {
935 m_systemBookmarksIndexes.insert(m_systemBookmarks[i].url, i);
936 }
937 }
938
939 void PlacesItemModel::clear() {
940 m_bookmarkedItems.clear();
941 KStandardItemModel::clear();
942 }
943
944 void PlacesItemModel::initializeAvailableDevices()
945 {
946 QString predicate("[[[[ StorageVolume.ignored == false AND [ StorageVolume.usage == 'FileSystem' OR StorageVolume.usage == 'Encrypted' ]]"
947 " OR "
948 "[ IS StorageAccess AND StorageDrive.driveType == 'Floppy' ]]"
949 " OR "
950 "OpticalDisc.availableContent & 'Audio' ]"
951 " OR "
952 "StorageAccess.ignored == false ]");
953
954
955 if (KProtocolInfo::isKnownProtocol("mtp")) {
956 predicate.prepend("[");
957 predicate.append(" OR PortableMediaPlayer.supportedProtocols == 'mtp']");
958 }
959
960 m_predicate = Solid::Predicate::fromString(predicate);
961 Q_ASSERT(m_predicate.isValid());
962
963 Solid::DeviceNotifier* notifier = Solid::DeviceNotifier::instance();
964 connect(notifier, SIGNAL(deviceAdded(QString)), this, SLOT(slotDeviceAdded(QString)));
965 connect(notifier, SIGNAL(deviceRemoved(QString)), this, SLOT(slotDeviceRemoved(QString)));
966
967 const QList<Solid::Device>& deviceList = Solid::Device::listFromQuery(m_predicate);
968 foreach (const Solid::Device& device, deviceList) {
969 m_availableDevices << device.udi();
970 }
971 }
972
973 int PlacesItemModel::bookmarkIndex(int index) const
974 {
975 int bookmarkIndex = 0;
976 int modelIndex = 0;
977 while (bookmarkIndex < m_bookmarkedItems.count()) {
978 if (!m_bookmarkedItems[bookmarkIndex]) {
979 if (modelIndex == index) {
980 break;
981 }
982 ++modelIndex;
983 }
984 ++bookmarkIndex;
985 }
986
987 return bookmarkIndex >= m_bookmarkedItems.count() ? -1 : bookmarkIndex;
988 }
989
990 void PlacesItemModel::hideItem(int index)
991 {
992 PlacesItem* shownItem = placesItem(index);
993 if (!shownItem) {
994 return;
995 }
996
997 shownItem->setHidden(true);
998 if (m_hiddenItemsShown) {
999 // Removing items from the model is not allowed if all hidden
1000 // items should be shown.
1001 return;
1002 }
1003
1004 const int newIndex = bookmarkIndex(index);
1005 if (newIndex >= 0) {
1006 const KBookmark hiddenBookmark = shownItem->bookmark();
1007 PlacesItem* hiddenItem = new PlacesItem(hiddenBookmark);
1008
1009 const PlacesItem* previousItem = placesItem(index - 1);
1010 KBookmark previousBookmark;
1011 if (previousItem) {
1012 previousBookmark = previousItem->bookmark();
1013 }
1014
1015 const bool updateBookmark = (m_bookmarkManager->root().indexOf(hiddenBookmark) >= 0);
1016 removeItem(index);
1017
1018 if (updateBookmark) {
1019 // removeItem() also removed the bookmark from m_bookmarkManager in
1020 // PlacesItemModel::onItemRemoved(). However for hidden items the
1021 // bookmark should still be remembered, so readd it again:
1022 m_bookmarkManager->root().addBookmark(hiddenBookmark);
1023 m_bookmarkManager->root().moveBookmark(hiddenBookmark, previousBookmark);
1024 }
1025
1026 m_bookmarkedItems.insert(newIndex, hiddenItem);
1027 }
1028 }
1029
1030 QString PlacesItemModel::internalMimeType() const
1031 {
1032 return "application/x-dolphinplacesmodel-" +
1033 QString::number((qptrdiff)this);
1034 }
1035
1036 int PlacesItemModel::groupedDropIndex(int index, const PlacesItem* item) const
1037 {
1038 Q_ASSERT(item);
1039
1040 int dropIndex = index;
1041 const PlacesItem::GroupType type = item->groupType();
1042
1043 const int itemCount = count();
1044 if (index < 0) {
1045 dropIndex = itemCount;
1046 }
1047
1048 // Search nearest previous item with the same group
1049 int previousIndex = -1;
1050 for (int i = dropIndex - 1; i >= 0; --i) {
1051 if (placesItem(i)->groupType() == type) {
1052 previousIndex = i;
1053 break;
1054 }
1055 }
1056
1057 // Search nearest next item with the same group
1058 int nextIndex = -1;
1059 for (int i = dropIndex; i < count(); ++i) {
1060 if (placesItem(i)->groupType() == type) {
1061 nextIndex = i;
1062 break;
1063 }
1064 }
1065
1066 // Adjust the drop-index to be inserted to the
1067 // nearest item with the same group.
1068 if (previousIndex >= 0 && nextIndex >= 0) {
1069 dropIndex = (dropIndex - previousIndex < nextIndex - dropIndex) ?
1070 previousIndex + 1 : nextIndex;
1071 } else if (previousIndex >= 0) {
1072 dropIndex = previousIndex + 1;
1073 } else if (nextIndex >= 0) {
1074 dropIndex = nextIndex;
1075 }
1076
1077 return dropIndex;
1078 }
1079
1080 bool PlacesItemModel::equalBookmarkIdentifiers(const KBookmark& b1, const KBookmark& b2)
1081 {
1082 const QString udi1 = b1.metaDataItem("UDI");
1083 const QString udi2 = b2.metaDataItem("UDI");
1084 if (!udi1.isEmpty() && !udi2.isEmpty()) {
1085 return udi1 == udi2;
1086 } else {
1087 return b1.metaDataItem("ID") == b2.metaDataItem("ID");
1088 }
1089 }
1090
1091 KUrl PlacesItemModel::createTimelineUrl(const KUrl& url)
1092 {
1093 // TODO: Clarify with the Baloo-team whether it makes sense
1094 // provide default-timeline-URLs like 'yesterday', 'this month'
1095 // and 'last month'.
1096 KUrl timelineUrl;
1097
1098 const QString path = url.pathOrUrl();
1099 if (path.endsWith(QLatin1String("yesterday"))) {
1100 const QDate date = QDate::currentDate().addDays(-1);
1101 const int year = date.year();
1102 const int month = date.month();
1103 const int day = date.day();
1104 timelineUrl = "timeline:/" + timelineDateString(year, month) +
1105 '/' + timelineDateString(year, month, day);
1106 } else if (path.endsWith(QLatin1String("thismonth"))) {
1107 const QDate date = QDate::currentDate();
1108 timelineUrl = "timeline:/" + timelineDateString(date.year(), date.month());
1109 } else if (path.endsWith(QLatin1String("lastmonth"))) {
1110 const QDate date = QDate::currentDate().addMonths(-1);
1111 timelineUrl = "timeline:/" + timelineDateString(date.year(), date.month());
1112 } else {
1113 Q_ASSERT(path.endsWith(QLatin1String("today")));
1114 timelineUrl= url;
1115 }
1116
1117 return timelineUrl;
1118 }
1119
1120 QString PlacesItemModel::timelineDateString(int year, int month, int day)
1121 {
1122 QString date = QString::number(year) + '-';
1123 if (month < 10) {
1124 date += '0';
1125 }
1126 date += QString::number(month);
1127
1128 if (day >= 1) {
1129 date += '-';
1130 if (day < 10) {
1131 date += '0';
1132 }
1133 date += QString::number(day);
1134 }
1135
1136 return date;
1137 }
1138
1139 KUrl PlacesItemModel::createSearchUrl(const KUrl& url)
1140 {
1141 KUrl searchUrl;
1142
1143 #ifdef HAVE_BALOO
1144 const QString path = url.pathOrUrl();
1145 if (path.endsWith(QLatin1String("documents"))) {
1146 searchUrl = searchUrlForType("Document");
1147 } else if (path.endsWith(QLatin1String("images"))) {
1148 searchUrl = searchUrlForType("Image");
1149 } else if (path.endsWith(QLatin1String("audio"))) {
1150 searchUrl = searchUrlForType("Audio");
1151 } else if (path.endsWith(QLatin1String("videos"))) {
1152 searchUrl = searchUrlForType("Video");
1153 } else {
1154 Q_ASSERT(false);
1155 }
1156 #else
1157 Q_UNUSED(url);
1158 #endif
1159
1160 return searchUrl;
1161 }
1162
1163 #ifdef HAVE_BALOO
1164 KUrl PlacesItemModel::searchUrlForType(const QString& type)
1165 {
1166 Baloo::Query query;
1167 query.addType("File");
1168 query.addType(type);
1169
1170 return query.toSearchUrl();
1171 }
1172 #endif
1173
1174 #ifdef PLACESITEMMODEL_DEBUG
1175 void PlacesItemModel::showModelState()
1176 {
1177 kDebug() << "=================================";
1178 kDebug() << "Model:";
1179 kDebug() << "hidden-index model-index text";
1180 int modelIndex = 0;
1181 for (int i = 0; i < m_bookmarkedItems.count(); ++i) {
1182 if (m_bookmarkedItems[i]) {
1183 kDebug() << i << "(Hidden) " << " " << m_bookmarkedItems[i]->dataValue("text").toString();
1184 } else {
1185 if (item(modelIndex)) {
1186 kDebug() << i << " " << modelIndex << " " << item(modelIndex)->dataValue("text").toString();
1187 } else {
1188 kDebug() << i << " " << modelIndex << " " << "(not available yet)";
1189 }
1190 ++modelIndex;
1191 }
1192 }
1193
1194 kDebug();
1195 kDebug() << "Bookmarks:";
1196
1197 int bookmarkIndex = 0;
1198 KBookmarkGroup root = m_bookmarkManager->root();
1199 KBookmark bookmark = root.first();
1200 while (!bookmark.isNull()) {
1201 const QString udi = bookmark.metaDataItem("UDI");
1202 const QString text = udi.isEmpty() ? bookmark.text() : udi;
1203 if (bookmark.metaDataItem("IsHidden") == QLatin1String("true")) {
1204 kDebug() << bookmarkIndex << "(Hidden)" << text;
1205 } else {
1206 kDebug() << bookmarkIndex << " " << text;
1207 }
1208
1209 bookmark = root.next(bookmark);
1210 ++bookmarkIndex;
1211 }
1212 }
1213 #endif
1214
1215 #include "placesitemmodel.moc"