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