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