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