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