]> 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 = new PlacesItem(bookmark);
401 updateItem(item, index);
402 insertSortedItem(item);
403
404 if (m_sourceModel->isDevice(index)) {
405 connect(item->signalHandler(), &PlacesItemSignalHandler::tearDownExternallyRequested,
406 this, &PlacesItemModel::storageTearDownExternallyRequested);
407 }
408 }
409
410 void PlacesItemModel::removeItemByIndex(const QModelIndex &sourceIndex)
411 {
412 QString id = bookmarkId(m_sourceModel->bookmarkForIndex(sourceIndex));
413
414 for (int i = 0, iMax = count(); i < iMax; ++i) {
415 if (bookmarkId(placesItem(i)->bookmark()) == id) {
416 removeItem(i);
417 return;
418 }
419 }
420 }
421
422 QString PlacesItemModel::bookmarkId(const KBookmark &bookmark) const
423 {
424 QString id = bookmark.metaDataItem(QStringLiteral("UDI"));
425 if (id.isEmpty()) {
426 id = bookmark.metaDataItem(QStringLiteral("ID"));
427 }
428 return id;
429 }
430
431 void PlacesItemModel::initializeDefaultViewProperties() const
432 {
433 for(int i = 0, iMax = m_sourceModel->rowCount(); i < iMax; i++) {
434 const QModelIndex index = m_sourceModel->index(i, 0);
435 const PlacesItem *item = placesItem(mapFromSource(index));
436 if (!item) {
437 continue;
438 }
439
440 // Create default view-properties for all "Search For" and "Recently Saved" bookmarks
441 // in case the user has not already created custom view-properties for a corresponding
442 // query yet.
443 const bool createDefaultViewProperties = item->isSearchOrTimelineUrl() && !GeneralSettings::self()->globalViewProps();
444 if (createDefaultViewProperties) {
445 const QUrl itemUrl = item->url();
446 ViewProperties props(KFilePlacesModel::convertedUrl(itemUrl));
447 if (!props.exist()) {
448 const QString path = itemUrl.path();
449 if (path == QLatin1String("/documents")) {
450 props.setViewMode(DolphinView::DetailsView);
451 props.setPreviewsShown(false);
452 props.setVisibleRoles({"text", "path"});
453 } else if (path == QLatin1String("/images")) {
454 props.setViewMode(DolphinView::IconsView);
455 props.setPreviewsShown(true);
456 props.setVisibleRoles({"text", "height", "width"});
457 } else if (path == QLatin1String("/audio")) {
458 props.setViewMode(DolphinView::DetailsView);
459 props.setPreviewsShown(false);
460 props.setVisibleRoles({"text", "artist", "album"});
461 } else if (path == QLatin1String("/videos")) {
462 props.setViewMode(DolphinView::IconsView);
463 props.setPreviewsShown(true);
464 props.setVisibleRoles({"text"});
465 } else if (itemUrl.scheme() == QLatin1String("timeline")) {
466 props.setViewMode(DolphinView::DetailsView);
467 props.setVisibleRoles({"text", "modificationtime"});
468 }
469 props.save();
470 }
471 }
472 }
473 }
474
475 void PlacesItemModel::updateItem(PlacesItem *item, const QModelIndex &index)
476 {
477 item->setGroup(index.data(KFilePlacesModel::GroupRole).toString());
478 item->setIcon(index.data(KFilePlacesModel::IconNameRole).toString());
479 item->setGroupHidden(index.data(KFilePlacesModel::GroupHiddenRole).toBool());
480 }
481
482 void PlacesItemModel::slotStorageTearDownDone(Solid::ErrorType error, const QVariant& errorData)
483 {
484 if (error && errorData.isValid()) {
485 emit errorMessage(errorData.toString());
486 }
487 m_deviceToTearDown->disconnect();
488 m_deviceToTearDown = nullptr;
489 }
490
491 void PlacesItemModel::slotStorageSetupDone(Solid::ErrorType error,
492 const QVariant& errorData,
493 const QString& udi)
494 {
495 Q_UNUSED(udi);
496
497 const int index = m_storageSetupInProgress.take(sender());
498 const PlacesItem* item = placesItem(index);
499 if (!item) {
500 return;
501 }
502
503 if (error != Solid::NoError) {
504 if (errorData.isValid()) {
505 emit errorMessage(i18nc("@info", "An error occurred while accessing '%1', the system responded: %2",
506 item->text(),
507 errorData.toString()));
508 } else {
509 emit errorMessage(i18nc("@info", "An error occurred while accessing '%1'",
510 item->text()));
511 }
512 emit storageSetupDone(index, false);
513 } else {
514 emit storageSetupDone(index, true);
515 }
516 }
517
518 void PlacesItemModel::onSourceModelRowsInserted(const QModelIndex &parent, int first, int last)
519 {
520 for (int i = first; i <= last; i++) {
521 const QModelIndex index = m_sourceModel->index(i, 0, parent);
522 addItemFromSourceModel(index);
523 }
524 }
525
526 void PlacesItemModel::onSourceModelRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
527 {
528 for(int r = first; r <= last; r++) {
529 const QModelIndex index = m_sourceModel->index(r, 0, parent);
530 int oldIndex = mapFromSource(index);
531 if (oldIndex != -1) {
532 removeItem(oldIndex);
533 }
534 }
535 }
536
537 void PlacesItemModel::onSourceModelRowsAboutToBeMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
538 {
539 Q_UNUSED(destination);
540 Q_UNUSED(row);
541
542 for(int r = start; r <= end; r++) {
543 const QModelIndex sourceIndex = m_sourceModel->index(r, 0, parent);
544 // remove moved item
545 removeItem(mapFromSource(sourceIndex));
546 }
547 }
548
549 void PlacesItemModel::onSourceModelRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
550 {
551 Q_UNUSED(destination);
552 Q_UNUSED(parent);
553
554 const int blockSize = (end - start) + 1;
555
556 for (int r = start; r <= end; r++) {
557 // insert the moved item in the new position
558 const int targetRow = row + (start - r) - (r < row ? blockSize : 0);
559 const QModelIndex targetIndex = m_sourceModel->index(targetRow, 0, destination);
560
561 addItemFromSourceModel(targetIndex);
562 }
563 }
564
565 void PlacesItemModel::onSourceModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
566 {
567 Q_UNUSED(roles);
568
569 for (int r = topLeft.row(); r <= bottomRight.row(); r++) {
570 const QModelIndex sourceIndex = m_sourceModel->index(r, 0);
571 const KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex);
572 PlacesItem *placeItem = itemFromBookmark(bookmark);
573
574 if (placeItem && (!m_hiddenItemsShown && m_sourceModel->isHidden(sourceIndex))) {
575 //hide item if it became invisible
576 removeItem(index(placeItem));
577 return;
578 }
579
580 if (!placeItem && (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex))) {
581 //show item if it became visible
582 addItemFromSourceModel(sourceIndex);
583 return;
584 }
585
586 if (placeItem && !m_sourceModel->isDevice(sourceIndex)) {
587 placeItem->setText(bookmark.text());
588 placeItem->setIcon(sourceIndex.data(KFilePlacesModel::IconNameRole).toString());
589 placeItem->setUrl(m_sourceModel->url(sourceIndex));
590 placeItem->bookmark().setMetaDataItem(QStringLiteral("OnlyInApp"),
591 bookmark.metaDataItem(QStringLiteral("OnlyInApp")));
592 // must update the bookmark object
593 placeItem->setBookmark(bookmark);
594 }
595 }
596 }
597
598 void PlacesItemModel::onSourceModelGroupHiddenChanged(KFilePlacesModel::GroupType group, bool hidden)
599 {
600 const auto groupIndexes = m_sourceModel->groupIndexes(group);
601 for (const QModelIndex &sourceIndex : groupIndexes) {
602 PlacesItem *item = placesItem(mapFromSource(sourceIndex));
603 if (item) {
604 item->setGroupHidden(hidden);
605 }
606 }
607 }
608
609 void PlacesItemModel::cleanupBookmarks()
610 {
611 // KIO model now provides support for baloo urls, and because of that we
612 // need to remove old URLs that were visible only in Dolphin to avoid duplication
613 int row = 0;
614 do {
615 const QModelIndex sourceIndex = m_sourceModel->index(row, 0);
616 const KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex);
617 const QUrl url = bookmark.url();
618 const QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp"));
619
620 if ((appName == KAboutData::applicationData().componentName() ||
621 appName == KAboutData::applicationData().componentName() + DolphinPlacesModelSingleton::applicationNameSuffix()) && balooURLs.contains(url)) {
622 qCDebug(DolphinDebug) << "Removing old baloo url:" << url;
623 m_sourceModel->removePlace(sourceIndex);
624 } else {
625 row++;
626 }
627 } while (row < m_sourceModel->rowCount());
628 }
629
630 void PlacesItemModel::loadBookmarks()
631 {
632 for(int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) {
633 const QModelIndex sourceIndex = m_sourceModel->index(r, 0);
634 if (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex)) {
635 addItemFromSourceModel(sourceIndex);
636 }
637 }
638
639 #ifdef PLACESITEMMODEL_DEBUG
640 qCDebug(DolphinDebug) << "Loaded bookmarks";
641 showModelState();
642 #endif
643 }
644
645 void PlacesItemModel::clear() {
646 KStandardItemModel::clear();
647 }
648
649 void PlacesItemModel::proceedWithTearDown()
650 {
651 Q_ASSERT(m_deviceToTearDown);
652
653 connect(m_deviceToTearDown, &Solid::StorageAccess::teardownDone,
654 this, &PlacesItemModel::slotStorageTearDownDone);
655 m_deviceToTearDown->teardown();
656 }
657
658 void PlacesItemModel::deleteItem(int index)
659 {
660 QModelIndex sourceIndex = mapToSource(index);
661 Q_ASSERT(sourceIndex.isValid());
662 m_sourceModel->removePlace(sourceIndex);
663 }
664
665 void PlacesItemModel::refresh()
666 {
667 m_sourceModel->refresh();
668 }
669
670 void PlacesItemModel::hideItem(int index)
671 {
672 PlacesItem* shownItem = placesItem(index);
673 if (!shownItem) {
674 return;
675 }
676
677 shownItem->setHidden(true);
678 }
679
680 QString PlacesItemModel::internalMimeType() const
681 {
682 return "application/x-dolphinplacesmodel-" +
683 QString::number((qptrdiff)this);
684 }
685
686 int PlacesItemModel::groupedDropIndex(int index, const PlacesItem* item) const
687 {
688 Q_ASSERT(item);
689
690 int dropIndex = index;
691 const QString group = item->group();
692
693 const int itemCount = count();
694 if (index < 0) {
695 dropIndex = itemCount;
696 }
697
698 // Search nearest previous item with the same group
699 int previousIndex = -1;
700 for (int i = dropIndex - 1; i >= 0; --i) {
701 if (placesItem(i)->group() == group) {
702 previousIndex = i;
703 break;
704 }
705 }
706
707 // Search nearest next item with the same group
708 int nextIndex = -1;
709 for (int i = dropIndex; i < count(); ++i) {
710 if (placesItem(i)->group() == group) {
711 nextIndex = i;
712 break;
713 }
714 }
715
716 // Adjust the drop-index to be inserted to the
717 // nearest item with the same group.
718 if (previousIndex >= 0 && nextIndex >= 0) {
719 dropIndex = (dropIndex - previousIndex < nextIndex - dropIndex) ?
720 previousIndex + 1 : nextIndex;
721 } else if (previousIndex >= 0) {
722 dropIndex = previousIndex + 1;
723 } else if (nextIndex >= 0) {
724 dropIndex = nextIndex;
725 }
726
727 return dropIndex;
728 }
729
730 bool PlacesItemModel::equalBookmarkIdentifiers(const KBookmark& b1, const KBookmark& b2)
731 {
732 const QString udi1 = b1.metaDataItem(QStringLiteral("UDI"));
733 const QString udi2 = b2.metaDataItem(QStringLiteral("UDI"));
734 if (!udi1.isEmpty() && !udi2.isEmpty()) {
735 return udi1 == udi2;
736 } else {
737 return b1.metaDataItem(QStringLiteral("ID")) == b2.metaDataItem(QStringLiteral("ID"));
738 }
739 }
740
741 int PlacesItemModel::mapFromSource(const QModelIndex &index) const
742 {
743 if (!index.isValid()) {
744 return -1;
745 }
746
747 return m_indexMap.indexOf(index);
748 }
749
750 bool PlacesItemModel::isDir(int index) const
751 {
752 Q_UNUSED(index);
753 return true;
754 }
755
756 KFilePlacesModel::GroupType PlacesItemModel::groupType(int row) const
757 {
758 return m_sourceModel->groupType(mapToSource(row));
759 }
760
761 bool PlacesItemModel::isGroupHidden(KFilePlacesModel::GroupType type) const
762 {
763 return m_sourceModel->isGroupHidden(type);
764 }
765
766 void PlacesItemModel::setGroupHidden(KFilePlacesModel::GroupType type, bool hidden)
767 {
768 return m_sourceModel->setGroupHidden(type, hidden);
769 }
770
771 QModelIndex PlacesItemModel::mapToSource(int row) const
772 {
773 return m_indexMap.value(row);
774 }
775
776 PlacesItem *PlacesItemModel::itemFromBookmark(const KBookmark &bookmark) const
777 {
778 const QString id = bookmarkId(bookmark);
779 for (int i = 0, iMax = count(); i < iMax; i++) {
780 PlacesItem *item = placesItem(i);
781 const KBookmark itemBookmark = item->bookmark();
782 if (bookmarkId(itemBookmark) == id) {
783 return item;
784 }
785 }
786 return nullptr;
787 }
788
789 #ifdef PLACESITEMMODEL_DEBUG
790 void PlacesItemModel::showModelState()
791 {
792 qCDebug(DolphinDebug) << "=================================";
793 qCDebug(DolphinDebug) << "Model:";
794 qCDebug(DolphinDebug) << "hidden-index model-index text";
795 int modelIndex = 0;
796 for (int i = 0; i < m_bookmarkedItems.count(); ++i) {
797 if (m_bookmarkedItems[i]) {
798 qCDebug(DolphinDebug) << i << "(Hidden) " << " " << m_bookmarkedItems[i]->dataValue("text").toString();
799 } else {
800 if (item(modelIndex)) {
801 qCDebug(DolphinDebug) << i << " " << modelIndex << " " << item(modelIndex)->dataValue("text").toString();
802 } else {
803 qCDebug(DolphinDebug) << i << " " << modelIndex << " " << "(not available yet)";
804 }
805 ++modelIndex;
806 }
807 }
808
809 qCDebug(DolphinDebug);
810 qCDebug(DolphinDebug) << "Bookmarks:";
811
812 int bookmarkIndex = 0;
813 KBookmarkGroup root = m_bookmarkManager->root();
814 KBookmark bookmark = root.first();
815 while (!bookmark.isNull()) {
816 const QString udi = bookmark.metaDataItem("UDI");
817 const QString text = udi.isEmpty() ? bookmark.text() : udi;
818 if (bookmark.metaDataItem("IsHidden") == QLatin1String("true")) {
819 qCDebug(DolphinDebug) << bookmarkIndex << "(Hidden)" << text;
820 } else {
821 qCDebug(DolphinDebug) << bookmarkIndex << " " << text;
822 }
823
824 bookmark = root.next(bookmark);
825 ++bookmarkIndex;
826 }
827 }
828 #endif
829