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