]> cloud.milkyroute.net Git - dolphin.git/blob - src/panels/places/placesitemmodel.cpp
Make sure that old URLs do not appear duplicated in places model
[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 #include "placesitemsignalhandler.h"
26
27 #include "dolphin_generalsettings.h"
28
29 #include <KBookmark>
30 #include <KBookmarkManager>
31 #include "dolphindebug.h"
32 #include <QIcon>
33 #include <KProtocolInfo>
34 #include <KLocalizedString>
35 #include <QStandardPaths>
36 #include <KAboutData>
37 #include "placesitem.h"
38 #include <QAction>
39 #include <QDate>
40 #include <QMimeData>
41 #include <QTimer>
42 #include <KUrlMimeData>
43 #include <KFilePlacesModel>
44
45 #include <Solid/Device>
46 #include <Solid/DeviceNotifier>
47 #include <Solid/OpticalDisc>
48 #include <Solid/OpticalDrive>
49 #include <Solid/StorageAccess>
50 #include <Solid/StorageDrive>
51
52 #include <views/dolphinview.h>
53 #include <views/viewproperties.h>
54
55 namespace {
56 // A suffix to the application-name of the stored bookmarks is
57 // added, which is only read by PlacesItemModel.
58 const QString AppNameSuffix = QStringLiteral("-places-panel");
59 static QList<QUrl> balooURLs = {
60 QUrl(QStringLiteral("timeline:/today")),
61 QUrl(QStringLiteral("timeline:/yesterday")),
62 QUrl(QStringLiteral("timeline:/thismonth")),
63 QUrl(QStringLiteral("timeline:/lastmonth")),
64 QUrl(QStringLiteral("search:/documents")),
65 QUrl(QStringLiteral("search:/images")),
66 QUrl(QStringLiteral("search:/audio")),
67 QUrl(QStringLiteral("search:/videos"))
68 };
69 }
70
71 PlacesItemModel::PlacesItemModel(QObject* parent) :
72 KStandardItemModel(parent),
73 m_hiddenItemsShown(false),
74 m_deviceToTearDown(nullptr),
75 m_storageSetupInProgress(),
76 m_sourceModel(new KFilePlacesModel(KAboutData::applicationData().componentName() + AppNameSuffix, this))
77 {
78 cleanupBookmarks();
79 loadBookmarks();
80 initializeDefaultViewProperties();
81
82 connect(m_sourceModel.data(), &KFilePlacesModel::rowsInserted, this, &PlacesItemModel::onSourceModelRowsInserted);
83 connect(m_sourceModel.data(), &KFilePlacesModel::rowsAboutToBeRemoved, this, &PlacesItemModel::onSourceModelRowsAboutToBeRemoved);
84 connect(m_sourceModel.data(), &KFilePlacesModel::dataChanged, this, &PlacesItemModel::onSourceModelDataChanged);
85 connect(m_sourceModel.data(), &KFilePlacesModel::rowsAboutToBeMoved, this, &PlacesItemModel::onSourceModelRowsAboutToBeMoved);
86 connect(m_sourceModel.data(), &KFilePlacesModel::rowsMoved, this, &PlacesItemModel::onSourceModelRowsMoved);
87 connect(m_sourceModel.data(), &KFilePlacesModel::groupHiddenChanged, this, &PlacesItemModel::onSourceModelGroupHiddenChanged);
88 }
89
90 PlacesItemModel::~PlacesItemModel()
91 {
92 }
93
94 void PlacesItemModel::createPlacesItem(const QString& text,
95 const QUrl& url,
96 const QString& iconName,
97 int after)
98 {
99 m_sourceModel->addPlace(text, url, iconName, {}, mapToSource(after));
100 }
101
102 PlacesItem* PlacesItemModel::placesItem(int index) const
103 {
104 return dynamic_cast<PlacesItem*>(item(index));
105 }
106
107 int PlacesItemModel::hiddenCount() const
108 {
109 return m_sourceModel->hiddenCount();
110 }
111
112 void PlacesItemModel::setHiddenItemsShown(bool show)
113 {
114 if (m_hiddenItemsShown == show) {
115 return;
116 }
117
118 m_hiddenItemsShown = show;
119
120 if (show) {
121 for (int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) {
122 const QModelIndex index = m_sourceModel->index(r, 0);
123 if (!m_sourceModel->isHidden(index)) {
124 continue;
125 }
126 addItemFromSourceModel(index);
127 }
128 } else {
129 for (int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) {
130 const QModelIndex index = m_sourceModel->index(r, 0);
131 if (m_sourceModel->isHidden(index)) {
132 removeItemByIndex(index);
133 }
134 }
135 }
136
137 #ifdef PLACESITEMMODEL_DEBUG
138 qCDebug(DolphinDebug) << "Changed visibility of hidden items";
139 showModelState();
140 #endif
141 }
142
143 bool PlacesItemModel::hiddenItemsShown() const
144 {
145 return m_hiddenItemsShown;
146 }
147
148 int PlacesItemModel::closestItem(const QUrl& url) const
149 {
150 return mapFromSource(m_sourceModel->closestItem(url));
151 }
152
153 // look for the correct position for the item based on source model
154 void PlacesItemModel::insertSortedItem(PlacesItem* item)
155 {
156 if (!item) {
157 return;
158 }
159
160 const KBookmark iBookmark = item->bookmark();
161 const QString iBookmarkId = bookmarkId(iBookmark);
162 QModelIndex sourceIndex;
163 int pos = 0;
164
165 for(int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) {
166 sourceIndex = m_sourceModel->index(r, 0);
167 const KBookmark sourceBookmark = m_sourceModel->bookmarkForIndex(sourceIndex);
168
169 if (bookmarkId(sourceBookmark) == iBookmarkId) {
170 break;
171 }
172
173 if (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex)) {
174 pos++;
175 }
176 }
177
178 m_indexMap.insert(pos, sourceIndex);
179 insertItem(pos, item);
180 }
181
182 void PlacesItemModel::onItemInserted(int index)
183 {
184 KStandardItemModel::onItemInserted(index);
185 #ifdef PLACESITEMMODEL_DEBUG
186 qCDebug(DolphinDebug) << "Inserted item" << index;
187 showModelState();
188 #endif
189 }
190
191 void PlacesItemModel::onItemRemoved(int index, KStandardItem* removedItem)
192 {
193 m_indexMap.removeAt(index);
194
195 KStandardItemModel::onItemRemoved(index, removedItem);
196 #ifdef PLACESITEMMODEL_DEBUG
197 qCDebug(DolphinDebug) << "Removed item" << index;
198 showModelState();
199 #endif
200 }
201
202 void PlacesItemModel::onItemChanged(int index, const QSet<QByteArray>& changedRoles)
203 {
204 const QModelIndex sourceIndex = mapToSource(index);
205 const PlacesItem *changedItem = placesItem(mapFromSource(sourceIndex));
206
207 if (!changedItem || !sourceIndex.isValid()) {
208 qWarning() << "invalid item changed signal";
209 return;
210 }
211 if (changedRoles.contains("isHidden")) {
212 if (m_sourceModel->isHidden(sourceIndex) != changedItem->isHidden()) {
213 m_sourceModel->setPlaceHidden(sourceIndex, changedItem->isHidden());
214 } else {
215 m_sourceModel->refresh();
216 }
217 }
218 KStandardItemModel::onItemChanged(index, changedRoles);
219 }
220
221 QAction* PlacesItemModel::ejectAction(int index) const
222 {
223 const PlacesItem* item = placesItem(index);
224 if (item && item->device().is<Solid::OpticalDisc>()) {
225 return new QAction(QIcon::fromTheme(QStringLiteral("media-eject")), i18nc("@item", "Eject"), nullptr);
226 }
227
228 return nullptr;
229 }
230
231 QAction* PlacesItemModel::teardownAction(int index) const
232 {
233 const PlacesItem* item = placesItem(index);
234 if (!item) {
235 return nullptr;
236 }
237
238 Solid::Device device = item->device();
239 const bool providesTearDown = device.is<Solid::StorageAccess>() &&
240 device.as<Solid::StorageAccess>()->isAccessible();
241 if (!providesTearDown) {
242 return nullptr;
243 }
244
245 Solid::StorageDrive* drive = device.as<Solid::StorageDrive>();
246 if (!drive) {
247 drive = device.parent().as<Solid::StorageDrive>();
248 }
249
250 bool hotPluggable = false;
251 bool removable = false;
252 if (drive) {
253 hotPluggable = drive->isHotpluggable();
254 removable = drive->isRemovable();
255 }
256
257 QString iconName;
258 QString text;
259 if (device.is<Solid::OpticalDisc>()) {
260 text = i18nc("@item", "Release");
261 } else if (removable || hotPluggable) {
262 text = i18nc("@item", "Safely Remove");
263 iconName = QStringLiteral("media-eject");
264 } else {
265 text = i18nc("@item", "Unmount");
266 iconName = QStringLiteral("media-eject");
267 }
268
269 if (iconName.isEmpty()) {
270 return new QAction(text, nullptr);
271 }
272
273 return new QAction(QIcon::fromTheme(iconName), text, nullptr);
274 }
275
276 void PlacesItemModel::requestEject(int index)
277 {
278 const PlacesItem* item = placesItem(index);
279 if (item) {
280 Solid::OpticalDrive* drive = item->device().parent().as<Solid::OpticalDrive>();
281 if (drive) {
282 connect(drive, &Solid::OpticalDrive::ejectDone,
283 this, &PlacesItemModel::slotStorageTearDownDone);
284 drive->eject();
285 } else {
286 const QString label = item->text();
287 const QString message = i18nc("@info", "The device '%1' is not a disk and cannot be ejected.", label);
288 emit errorMessage(message);
289 }
290 }
291 }
292
293 void PlacesItemModel::requestTearDown(int index)
294 {
295 const PlacesItem* item = placesItem(index);
296 if (item) {
297 Solid::StorageAccess *tmp = item->device().as<Solid::StorageAccess>();
298 if (tmp) {
299 m_deviceToTearDown = tmp;
300 // disconnect the Solid::StorageAccess::teardownRequested
301 // to prevent emitting PlacesItemModel::storageTearDownExternallyRequested
302 // after we have emitted PlacesItemModel::storageTearDownRequested
303 disconnect(tmp, &Solid::StorageAccess::teardownRequested,
304 item->signalHandler(), &PlacesItemSignalHandler::onTearDownRequested);
305 emit storageTearDownRequested(tmp->filePath());
306 }
307 }
308 }
309
310 bool PlacesItemModel::storageSetupNeeded(int index) const
311 {
312 const PlacesItem* item = placesItem(index);
313 return item ? item->storageSetupNeeded() : false;
314 }
315
316 void PlacesItemModel::requestStorageSetup(int index)
317 {
318 const PlacesItem* item = placesItem(index);
319 if (!item) {
320 return;
321 }
322
323 Solid::Device device = item->device();
324 const bool setup = device.is<Solid::StorageAccess>()
325 && !m_storageSetupInProgress.contains(device.as<Solid::StorageAccess>())
326 && !device.as<Solid::StorageAccess>()->isAccessible();
327 if (setup) {
328 Solid::StorageAccess* access = device.as<Solid::StorageAccess>();
329
330 m_storageSetupInProgress[access] = index;
331
332 connect(access, &Solid::StorageAccess::setupDone,
333 this, &PlacesItemModel::slotStorageSetupDone);
334
335 access->setup();
336 }
337 }
338
339 QMimeData* PlacesItemModel::createMimeData(const KItemSet& indexes) const
340 {
341 QList<QUrl> urls;
342 QByteArray itemData;
343
344 QDataStream stream(&itemData, QIODevice::WriteOnly);
345
346 for (int index : indexes) {
347 const QUrl itemUrl = placesItem(index)->url();
348 if (itemUrl.isValid()) {
349 urls << itemUrl;
350 }
351 stream << index;
352 }
353
354 QMimeData* mimeData = new QMimeData();
355 if (!urls.isEmpty()) {
356 mimeData->setUrls(urls);
357 } else {
358 // #378954: prevent itemDropEvent() drops if there isn't a source url.
359 mimeData->setData(blacklistItemDropEventMimeType(), QByteArrayLiteral("true"));
360 }
361 mimeData->setData(internalMimeType(), itemData);
362
363 return mimeData;
364 }
365
366 bool PlacesItemModel::supportsDropping(int index) const
367 {
368 return index >= 0 && index < count();
369 }
370
371 void PlacesItemModel::dropMimeDataBefore(int index, const QMimeData* mimeData)
372 {
373 if (mimeData->hasFormat(internalMimeType())) {
374 // The item has been moved inside the view
375 QByteArray itemData = mimeData->data(internalMimeType());
376 QDataStream stream(&itemData, QIODevice::ReadOnly);
377 int oldIndex;
378 stream >> oldIndex;
379
380 m_sourceModel->movePlace(oldIndex, index);
381 } else if (mimeData->hasFormat(QStringLiteral("text/uri-list"))) {
382 // One or more items must be added to the model
383 const QList<QUrl> urls = KUrlMimeData::urlsFromMimeData(mimeData);
384 for (int i = urls.count() - 1; i >= 0; --i) {
385 const QUrl& url = urls[i];
386
387 QString text = url.fileName();
388 if (text.isEmpty()) {
389 text = url.host();
390 }
391
392 if ((url.isLocalFile() && !QFileInfo(url.toLocalFile()).isDir())
393 || url.scheme() == QLatin1String("trash")) {
394 // Only directories outside the trash are allowed
395 continue;
396 }
397
398 createPlacesItem(text, url, KIO::iconNameForUrl(url), qMax(0, index - 1));
399 }
400 }
401 // will save bookmark alteration and fix sort if that is broken by the drag/drop operation
402 refresh();
403 }
404
405 void PlacesItemModel::addItemFromSourceModel(const QModelIndex &index)
406 {
407 if (!m_hiddenItemsShown && m_sourceModel->isHidden(index)) {
408 return;
409 }
410
411 const KBookmark bookmark = m_sourceModel->bookmarkForIndex(index);
412 Q_ASSERT(!bookmark.isNull());
413 PlacesItem *item = new PlacesItem(bookmark);
414 updateItem(item, index);
415 insertSortedItem(item);
416
417 if (m_sourceModel->isDevice(index)) {
418 connect(item->signalHandler(), &PlacesItemSignalHandler::tearDownExternallyRequested,
419 this, &PlacesItemModel::storageTearDownExternallyRequested);
420 }
421 }
422
423 void PlacesItemModel::removeItemByIndex(const QModelIndex &sourceIndex)
424 {
425 QString id = bookmarkId(m_sourceModel->bookmarkForIndex(sourceIndex));
426
427 for (int i = 0, iMax = count(); i < iMax; ++i) {
428 if (bookmarkId(placesItem(i)->bookmark()) == id) {
429 removeItem(i);
430 return;
431 }
432 }
433 }
434
435 QString PlacesItemModel::bookmarkId(const KBookmark &bookmark) const
436 {
437 QString id = bookmark.metaDataItem(QStringLiteral("UDI"));
438 if (id.isEmpty()) {
439 id = bookmark.metaDataItem(QStringLiteral("ID"));
440 }
441 return id;
442 }
443
444 void PlacesItemModel::initializeDefaultViewProperties() const
445 {
446 for(int i = 0, iMax = m_sourceModel->rowCount(); i < iMax; i++) {
447 const QModelIndex index = m_sourceModel->index(i, 0);
448 const PlacesItem *item = placesItem(mapFromSource(index));
449 if (!item) {
450 continue;
451 }
452
453 // Create default view-properties for all "Search For" and "Recently Saved" bookmarks
454 // in case the user has not already created custom view-properties for a corresponding
455 // query yet.
456 const bool createDefaultViewProperties = item->isSearchOrTimelineUrl() && !GeneralSettings::self()->globalViewProps();
457 if (createDefaultViewProperties) {
458 const QUrl itemUrl = item->url();
459 ViewProperties props(KFilePlacesModel::convertedUrl(itemUrl));
460 if (!props.exist()) {
461 const QString path = itemUrl.path();
462 if (path == QLatin1String("/documents")) {
463 props.setViewMode(DolphinView::DetailsView);
464 props.setPreviewsShown(false);
465 props.setVisibleRoles({"text", "path"});
466 } else if (path == QLatin1String("/images")) {
467 props.setViewMode(DolphinView::IconsView);
468 props.setPreviewsShown(true);
469 props.setVisibleRoles({"text", "imageSize"});
470 } else if (path == QLatin1String("/audio")) {
471 props.setViewMode(DolphinView::DetailsView);
472 props.setPreviewsShown(false);
473 props.setVisibleRoles({"text", "artist", "album"});
474 } else if (path == QLatin1String("/videos")) {
475 props.setViewMode(DolphinView::IconsView);
476 props.setPreviewsShown(true);
477 props.setVisibleRoles({"text"});
478 } else if (itemUrl.scheme() == QLatin1String("timeline")) {
479 props.setViewMode(DolphinView::DetailsView);
480 props.setVisibleRoles({"text", "modificationtime"});
481 }
482 props.save();
483 }
484 }
485 }
486 }
487
488 void PlacesItemModel::updateItem(PlacesItem *item, const QModelIndex &index)
489 {
490 item->setGroup(index.data(KFilePlacesModel::GroupRole).toString());
491 item->setIcon(index.data(KFilePlacesModel::IconNameRole).toString());
492 item->setGroupHidden(index.data(KFilePlacesModel::GroupHiddenRole).toBool());
493 }
494
495 void PlacesItemModel::slotStorageTearDownDone(Solid::ErrorType error, const QVariant& errorData)
496 {
497 if (error && errorData.isValid()) {
498 emit errorMessage(errorData.toString());
499 }
500 m_deviceToTearDown->disconnect();
501 m_deviceToTearDown = nullptr;
502 }
503
504 void PlacesItemModel::slotStorageSetupDone(Solid::ErrorType error,
505 const QVariant& errorData,
506 const QString& udi)
507 {
508 Q_UNUSED(udi);
509
510 const int index = m_storageSetupInProgress.take(sender());
511 const PlacesItem* item = placesItem(index);
512 if (!item) {
513 return;
514 }
515
516 if (error != Solid::NoError) {
517 if (errorData.isValid()) {
518 emit errorMessage(i18nc("@info", "An error occurred while accessing '%1', the system responded: %2",
519 item->text(),
520 errorData.toString()));
521 } else {
522 emit errorMessage(i18nc("@info", "An error occurred while accessing '%1'",
523 item->text()));
524 }
525 emit storageSetupDone(index, false);
526 } else {
527 emit storageSetupDone(index, true);
528 }
529 }
530
531 void PlacesItemModel::onSourceModelRowsInserted(const QModelIndex &parent, int first, int last)
532 {
533 for (int i = first; i <= last; i++) {
534 const QModelIndex index = m_sourceModel->index(i, 0, parent);
535 addItemFromSourceModel(index);
536 }
537 }
538
539 void PlacesItemModel::onSourceModelRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
540 {
541 for(int r = first; r <= last; r++) {
542 const QModelIndex index = m_sourceModel->index(r, 0, parent);
543 int oldIndex = mapFromSource(index);
544 if (oldIndex != -1) {
545 removeItem(oldIndex);
546 }
547 }
548 }
549
550 void PlacesItemModel::onSourceModelRowsAboutToBeMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
551 {
552 Q_UNUSED(destination);
553 Q_UNUSED(row);
554
555 for(int r = start; r <= end; r++) {
556 const QModelIndex sourceIndex = m_sourceModel->index(r, 0, parent);
557 // remove moved item
558 removeItem(mapFromSource(sourceIndex));
559 }
560 }
561
562 void PlacesItemModel::onSourceModelRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
563 {
564 Q_UNUSED(destination);
565 Q_UNUSED(parent);
566
567 const int blockSize = (end - start) + 1;
568
569 for (int r = start; r <= end; r++) {
570 // insert the moved item in the new position
571 const int targetRow = row + (start - r) - (r < row ? blockSize : 0);
572 const QModelIndex targetIndex = m_sourceModel->index(targetRow, 0, destination);
573
574 addItemFromSourceModel(targetIndex);
575 }
576 }
577
578 void PlacesItemModel::onSourceModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
579 {
580 Q_UNUSED(roles);
581
582 for (int r = topLeft.row(); r <= bottomRight.row(); r++) {
583 const QModelIndex sourceIndex = m_sourceModel->index(r, 0);
584 const KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex);
585 PlacesItem *placeItem = itemFromBookmark(bookmark);
586
587 if (placeItem && (!m_hiddenItemsShown && m_sourceModel->isHidden(sourceIndex))) {
588 //hide item if it became invisible
589 removeItem(index(placeItem));
590 return;
591 }
592
593 if (!placeItem && (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex))) {
594 //show item if it became visible
595 addItemFromSourceModel(sourceIndex);
596 return;
597 }
598
599 if (placeItem && !m_sourceModel->isDevice(sourceIndex)) {
600 placeItem->setText(bookmark.text());
601 placeItem->setIcon(sourceIndex.data(KFilePlacesModel::IconNameRole).toString());
602 placeItem->setUrl(m_sourceModel->url(sourceIndex));
603 placeItem->bookmark().setMetaDataItem(QStringLiteral("OnlyInApp"),
604 bookmark.metaDataItem(QStringLiteral("OnlyInApp")));
605 }
606 }
607 }
608
609 void PlacesItemModel::onSourceModelGroupHiddenChanged(KFilePlacesModel::GroupType group, bool hidden)
610 {
611 for(const QModelIndex &sourceIndex : m_sourceModel->groupIndexes(group)) {
612 PlacesItem *item = placesItem(mapFromSource(sourceIndex));
613 if (item) {
614 item->setGroupHidden(hidden);
615 }
616 }
617 }
618
619 void PlacesItemModel::cleanupBookmarks()
620 {
621 // KIO model now provides support for baloo urls, and because of that we
622 // need to remove old URLs that were visible only in Dolphin to avoid duplication
623 int row = 0;
624 do {
625 const QModelIndex sourceIndex = m_sourceModel->index(row, 0);
626 const KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex);
627 const QUrl url = bookmark.url();
628 const QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp"));
629
630 if ((appName == KAboutData::applicationData().componentName() ||
631 appName == KAboutData::applicationData().componentName() + AppNameSuffix) && balooURLs.contains(url)) {
632 qCDebug(DolphinDebug) << "Removing old baloo url:" << url;
633 m_sourceModel->removePlace(sourceIndex);
634 } else {
635 row++;
636 }
637 } while (row < m_sourceModel->rowCount());
638 }
639
640 void PlacesItemModel::loadBookmarks()
641 {
642 for(int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) {
643 const QModelIndex sourceIndex = m_sourceModel->index(r, 0);
644 KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex);
645 if (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex)) {
646 addItemFromSourceModel(sourceIndex);
647 }
648 }
649
650 #ifdef PLACESITEMMODEL_DEBUG
651 qCDebug(DolphinDebug) << "Loaded bookmarks";
652 showModelState();
653 #endif
654 }
655
656 void PlacesItemModel::clear() {
657 KStandardItemModel::clear();
658 }
659
660 void PlacesItemModel::proceedWithTearDown()
661 {
662 Q_ASSERT(m_deviceToTearDown);
663
664 connect(m_deviceToTearDown, &Solid::StorageAccess::teardownDone,
665 this, &PlacesItemModel::slotStorageTearDownDone);
666 m_deviceToTearDown->teardown();
667 }
668
669 void PlacesItemModel::deleteItem(int index)
670 {
671 QModelIndex sourceIndex = mapToSource(index);
672 Q_ASSERT(sourceIndex.isValid());
673 m_sourceModel->removePlace(sourceIndex);
674 }
675
676 void PlacesItemModel::refresh()
677 {
678 m_sourceModel->refresh();
679 }
680
681 void PlacesItemModel::hideItem(int index)
682 {
683 PlacesItem* shownItem = placesItem(index);
684 if (!shownItem) {
685 return;
686 }
687
688 shownItem->setHidden(true);
689 }
690
691 QString PlacesItemModel::internalMimeType() const
692 {
693 return "application/x-dolphinplacesmodel-" +
694 QString::number((qptrdiff)this);
695 }
696
697 int PlacesItemModel::groupedDropIndex(int index, const PlacesItem* item) const
698 {
699 Q_ASSERT(item);
700
701 int dropIndex = index;
702 const QString group = item->group();
703
704 const int itemCount = count();
705 if (index < 0) {
706 dropIndex = itemCount;
707 }
708
709 // Search nearest previous item with the same group
710 int previousIndex = -1;
711 for (int i = dropIndex - 1; i >= 0; --i) {
712 if (placesItem(i)->group() == group) {
713 previousIndex = i;
714 break;
715 }
716 }
717
718 // Search nearest next item with the same group
719 int nextIndex = -1;
720 for (int i = dropIndex; i < count(); ++i) {
721 if (placesItem(i)->group() == group) {
722 nextIndex = i;
723 break;
724 }
725 }
726
727 // Adjust the drop-index to be inserted to the
728 // nearest item with the same group.
729 if (previousIndex >= 0 && nextIndex >= 0) {
730 dropIndex = (dropIndex - previousIndex < nextIndex - dropIndex) ?
731 previousIndex + 1 : nextIndex;
732 } else if (previousIndex >= 0) {
733 dropIndex = previousIndex + 1;
734 } else if (nextIndex >= 0) {
735 dropIndex = nextIndex;
736 }
737
738 return dropIndex;
739 }
740
741 bool PlacesItemModel::equalBookmarkIdentifiers(const KBookmark& b1, const KBookmark& b2)
742 {
743 const QString udi1 = b1.metaDataItem(QStringLiteral("UDI"));
744 const QString udi2 = b2.metaDataItem(QStringLiteral("UDI"));
745 if (!udi1.isEmpty() && !udi2.isEmpty()) {
746 return udi1 == udi2;
747 } else {
748 return b1.metaDataItem(QStringLiteral("ID")) == b2.metaDataItem(QStringLiteral("ID"));
749 }
750 }
751
752 int PlacesItemModel::mapFromSource(const QModelIndex &index) const
753 {
754 if (!index.isValid()) {
755 return -1;
756 }
757
758 return m_indexMap.indexOf(index);
759 }
760
761 bool PlacesItemModel::isDir(int index) const
762 {
763 Q_UNUSED(index);
764 return true;
765 }
766
767 KFilePlacesModel::GroupType PlacesItemModel::groupType(int row) const
768 {
769 return m_sourceModel->groupType(mapToSource(row));
770 }
771
772 bool PlacesItemModel::isGroupHidden(KFilePlacesModel::GroupType type) const
773 {
774 return m_sourceModel->isGroupHidden(type);
775 }
776
777 void PlacesItemModel::setGroupHidden(KFilePlacesModel::GroupType type, bool hidden)
778 {
779 return m_sourceModel->setGroupHidden(type, hidden);
780 }
781
782 QModelIndex PlacesItemModel::mapToSource(int row) const
783 {
784 return m_indexMap.value(row);
785 }
786
787 PlacesItem *PlacesItemModel::itemFromBookmark(const KBookmark &bookmark) const
788 {
789 const QString id = bookmarkId(bookmark);
790 for (int i = 0, iMax = count(); i < iMax; i++) {
791 PlacesItem *item = placesItem(i);
792 const KBookmark itemBookmark = item->bookmark();
793 if (bookmarkId(itemBookmark) == id) {
794 return item;
795 }
796 }
797 return nullptr;
798 }
799
800 #ifdef PLACESITEMMODEL_DEBUG
801 void PlacesItemModel::showModelState()
802 {
803 qCDebug(DolphinDebug) << "=================================";
804 qCDebug(DolphinDebug) << "Model:";
805 qCDebug(DolphinDebug) << "hidden-index model-index text";
806 int modelIndex = 0;
807 for (int i = 0; i < m_bookmarkedItems.count(); ++i) {
808 if (m_bookmarkedItems[i]) {
809 qCDebug(DolphinDebug) << i << "(Hidden) " << " " << m_bookmarkedItems[i]->dataValue("text").toString();
810 } else {
811 if (item(modelIndex)) {
812 qCDebug(DolphinDebug) << i << " " << modelIndex << " " << item(modelIndex)->dataValue("text").toString();
813 } else {
814 qCDebug(DolphinDebug) << i << " " << modelIndex << " " << "(not available yet)";
815 }
816 ++modelIndex;
817 }
818 }
819
820 qCDebug(DolphinDebug);
821 qCDebug(DolphinDebug) << "Bookmarks:";
822
823 int bookmarkIndex = 0;
824 KBookmarkGroup root = m_bookmarkManager->root();
825 KBookmark bookmark = root.first();
826 while (!bookmark.isNull()) {
827 const QString udi = bookmark.metaDataItem("UDI");
828 const QString text = udi.isEmpty() ? bookmark.text() : udi;
829 if (bookmark.metaDataItem("IsHidden") == QLatin1String("true")) {
830 qCDebug(DolphinDebug) << bookmarkIndex << "(Hidden)" << text;
831 } else {
832 qCDebug(DolphinDebug) << bookmarkIndex << " " << text;
833 }
834
835 bookmark = root.next(bookmark);
836 ++bookmarkIndex;
837 }
838 }
839 #endif
840