]> cloud.milkyroute.net Git - dolphin.git/blob - src/panels/places/placesitemmodel.cpp
Show home folder if needed after unmounting mounted disk
[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 } else {
477 // No error; it must have been unmounted successfully
478 emit storageTearDownSuccessful();
479 }
480 disconnect(m_deviceToTearDown, &Solid::StorageAccess::teardownDone,
481 this, &PlacesItemModel::slotStorageTearDownDone);
482 m_deviceToTearDown = nullptr;
483 }
484
485 void PlacesItemModel::slotStorageSetupDone(Solid::ErrorType error,
486 const QVariant& errorData,
487 const QString& udi)
488 {
489 Q_UNUSED(udi)
490
491 const int index = m_storageSetupInProgress.take(sender());
492 const PlacesItem* item = placesItem(index);
493 if (!item) {
494 return;
495 }
496
497 if (error != Solid::NoError) {
498 if (errorData.isValid()) {
499 emit errorMessage(i18nc("@info", "An error occurred while accessing '%1', the system responded: %2",
500 item->text(),
501 errorData.toString()));
502 } else {
503 emit errorMessage(i18nc("@info", "An error occurred while accessing '%1'",
504 item->text()));
505 }
506 emit storageSetupDone(index, false);
507 } else {
508 emit storageSetupDone(index, true);
509 }
510 }
511
512 void PlacesItemModel::onSourceModelRowsInserted(const QModelIndex &parent, int first, int last)
513 {
514 for (int i = first; i <= last; i++) {
515 const QModelIndex index = m_sourceModel->index(i, 0, parent);
516 addItemFromSourceModel(index);
517 }
518 }
519
520 void PlacesItemModel::onSourceModelRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
521 {
522 for(int r = first; r <= last; r++) {
523 const QModelIndex index = m_sourceModel->index(r, 0, parent);
524 int oldIndex = mapFromSource(index);
525 if (oldIndex != -1) {
526 removeItem(oldIndex);
527 }
528 }
529 }
530
531 void PlacesItemModel::onSourceModelRowsAboutToBeMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
532 {
533 Q_UNUSED(destination)
534 Q_UNUSED(row)
535
536 for(int r = start; r <= end; r++) {
537 const QModelIndex sourceIndex = m_sourceModel->index(r, 0, parent);
538 // remove moved item
539 removeItem(mapFromSource(sourceIndex));
540 }
541 }
542
543 void PlacesItemModel::onSourceModelRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
544 {
545 Q_UNUSED(destination)
546 Q_UNUSED(parent)
547
548 const int blockSize = (end - start) + 1;
549
550 for (int r = start; r <= end; r++) {
551 // insert the moved item in the new position
552 const int targetRow = row + (start - r) - (r < row ? blockSize : 0);
553 const QModelIndex targetIndex = m_sourceModel->index(targetRow, 0, destination);
554
555 addItemFromSourceModel(targetIndex);
556 }
557 }
558
559 void PlacesItemModel::onSourceModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
560 {
561 Q_UNUSED(roles)
562
563 for (int r = topLeft.row(); r <= bottomRight.row(); r++) {
564 const QModelIndex sourceIndex = m_sourceModel->index(r, 0);
565 const KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex);
566 PlacesItem *placeItem = itemFromBookmark(bookmark);
567
568 if (placeItem && (!m_hiddenItemsShown && m_sourceModel->isHidden(sourceIndex))) {
569 //hide item if it became invisible
570 removeItem(index(placeItem));
571 return;
572 }
573
574 if (!placeItem && (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex))) {
575 //show item if it became visible
576 addItemFromSourceModel(sourceIndex);
577 return;
578 }
579
580 if (placeItem && !m_sourceModel->isDevice(sourceIndex)) {
581 // must update the bookmark object
582 placeItem->setBookmark(bookmark);
583 }
584 }
585 }
586
587 void PlacesItemModel::onSourceModelGroupHiddenChanged(KFilePlacesModel::GroupType group, bool hidden)
588 {
589 const auto groupIndexes = m_sourceModel->groupIndexes(group);
590 for (const QModelIndex &sourceIndex : groupIndexes) {
591 PlacesItem *item = placesItem(mapFromSource(sourceIndex));
592 if (item) {
593 item->setGroupHidden(hidden);
594 }
595 }
596 }
597
598 void PlacesItemModel::cleanupBookmarks()
599 {
600 // KIO model now provides support for baloo urls, and because of that we
601 // need to remove old URLs that were visible only in Dolphin to avoid duplication
602
603 static const QVector<QUrl> balooURLs = {
604 QUrl(QStringLiteral("timeline:/today")),
605 QUrl(QStringLiteral("timeline:/yesterday")),
606 QUrl(QStringLiteral("timeline:/thismonth")),
607 QUrl(QStringLiteral("timeline:/lastmonth")),
608 QUrl(QStringLiteral("search:/documents")),
609 QUrl(QStringLiteral("search:/images")),
610 QUrl(QStringLiteral("search:/audio")),
611 QUrl(QStringLiteral("search:/videos"))
612 };
613
614 int row = 0;
615 do {
616 const QModelIndex sourceIndex = m_sourceModel->index(row, 0);
617 const KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex);
618 const QUrl url = bookmark.url();
619 const QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp"));
620
621 if ((appName == KAboutData::applicationData().componentName() ||
622 appName == KAboutData::applicationData().componentName() + DolphinPlacesModelSingleton::applicationNameSuffix()) && balooURLs.contains(url)) {
623 qCDebug(DolphinDebug) << "Removing old baloo url:" << url;
624 m_sourceModel->removePlace(sourceIndex);
625 } else {
626 row++;
627 }
628 } while (row < m_sourceModel->rowCount());
629 }
630
631 void PlacesItemModel::loadBookmarks()
632 {
633 for(int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) {
634 const QModelIndex sourceIndex = m_sourceModel->index(r, 0);
635 if (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex)) {
636 addItemFromSourceModel(sourceIndex);
637 }
638 }
639 }
640
641 void PlacesItemModel::clear() {
642 KStandardItemModel::clear();
643 }
644
645 void PlacesItemModel::proceedWithTearDown()
646 {
647 Q_ASSERT(m_deviceToTearDown);
648
649 connect(m_deviceToTearDown, &Solid::StorageAccess::teardownDone,
650 this, &PlacesItemModel::slotStorageTearDownDone);
651 m_deviceToTearDown->teardown();
652 }
653
654 void PlacesItemModel::deleteItem(int index)
655 {
656 QModelIndex sourceIndex = mapToSource(index);
657 Q_ASSERT(sourceIndex.isValid());
658 m_sourceModel->removePlace(sourceIndex);
659 }
660
661 void PlacesItemModel::refresh()
662 {
663 m_sourceModel->refresh();
664 }
665
666 void PlacesItemModel::hideItem(int index)
667 {
668 PlacesItem* shownItem = placesItem(index);
669 if (!shownItem) {
670 return;
671 }
672
673 shownItem->setHidden(true);
674 }
675
676 QString PlacesItemModel::internalMimeType() const
677 {
678 return "application/x-dolphinplacesmodel-" +
679 QString::number((qptrdiff)this);
680 }
681
682 int PlacesItemModel::groupedDropIndex(int index, const PlacesItem* item) const
683 {
684 Q_ASSERT(item);
685
686 int dropIndex = index;
687 const QString group = item->group();
688
689 const int itemCount = count();
690 if (index < 0) {
691 dropIndex = itemCount;
692 }
693
694 // Search nearest previous item with the same group
695 int previousIndex = -1;
696 for (int i = dropIndex - 1; i >= 0; --i) {
697 if (placesItem(i)->group() == group) {
698 previousIndex = i;
699 break;
700 }
701 }
702
703 // Search nearest next item with the same group
704 int nextIndex = -1;
705 for (int i = dropIndex; i < count(); ++i) {
706 if (placesItem(i)->group() == group) {
707 nextIndex = i;
708 break;
709 }
710 }
711
712 // Adjust the drop-index to be inserted to the
713 // nearest item with the same group.
714 if (previousIndex >= 0 && nextIndex >= 0) {
715 dropIndex = (dropIndex - previousIndex < nextIndex - dropIndex) ?
716 previousIndex + 1 : nextIndex;
717 } else if (previousIndex >= 0) {
718 dropIndex = previousIndex + 1;
719 } else if (nextIndex >= 0) {
720 dropIndex = nextIndex;
721 }
722
723 return dropIndex;
724 }
725
726 bool PlacesItemModel::equalBookmarkIdentifiers(const KBookmark& b1, const KBookmark& b2)
727 {
728 const QString udi1 = b1.metaDataItem(QStringLiteral("UDI"));
729 const QString udi2 = b2.metaDataItem(QStringLiteral("UDI"));
730 if (!udi1.isEmpty() && !udi2.isEmpty()) {
731 return udi1 == udi2;
732 } else {
733 return b1.metaDataItem(QStringLiteral("ID")) == b2.metaDataItem(QStringLiteral("ID"));
734 }
735 }
736
737 int PlacesItemModel::mapFromSource(const QModelIndex &index) const
738 {
739 if (!index.isValid()) {
740 return -1;
741 }
742
743 return m_indexMap.indexOf(index);
744 }
745
746 bool PlacesItemModel::isDir(int index) const
747 {
748 Q_UNUSED(index)
749 return true;
750 }
751
752 KFilePlacesModel::GroupType PlacesItemModel::groupType(int row) const
753 {
754 return m_sourceModel->groupType(mapToSource(row));
755 }
756
757 bool PlacesItemModel::isGroupHidden(KFilePlacesModel::GroupType type) const
758 {
759 return m_sourceModel->isGroupHidden(type);
760 }
761
762 void PlacesItemModel::setGroupHidden(KFilePlacesModel::GroupType type, bool hidden)
763 {
764 return m_sourceModel->setGroupHidden(type, hidden);
765 }
766
767 QModelIndex PlacesItemModel::mapToSource(int row) const
768 {
769 return m_indexMap.value(row);
770 }
771
772 PlacesItem *PlacesItemModel::itemFromBookmark(const KBookmark &bookmark) const
773 {
774 const QString id = bookmarkId(bookmark);
775 for (int i = 0, iMax = count(); i < iMax; i++) {
776 PlacesItem *item = placesItem(i);
777 const KBookmark itemBookmark = item->bookmark();
778 if (bookmarkId(itemBookmark) == id) {
779 return item;
780 }
781 }
782 return nullptr;
783 }
784