]> 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 disconnect(m_deviceToTearDown, &Solid::StorageAccess::teardownDone,
477 this, &PlacesItemModel::slotStorageTearDownDone);
478 m_deviceToTearDown = nullptr;
479 }
480
481 void PlacesItemModel::slotStorageSetupDone(Solid::ErrorType error,
482 const QVariant& errorData,
483 const QString& udi)
484 {
485 Q_UNUSED(udi);
486
487 const int index = m_storageSetupInProgress.take(sender());
488 const PlacesItem* item = placesItem(index);
489 if (!item) {
490 return;
491 }
492
493 if (error != Solid::NoError) {
494 if (errorData.isValid()) {
495 emit errorMessage(i18nc("@info", "An error occurred while accessing '%1', the system responded: %2",
496 item->text(),
497 errorData.toString()));
498 } else {
499 emit errorMessage(i18nc("@info", "An error occurred while accessing '%1'",
500 item->text()));
501 }
502 emit storageSetupDone(index, false);
503 } else {
504 emit storageSetupDone(index, true);
505 }
506 }
507
508 void PlacesItemModel::onSourceModelRowsInserted(const QModelIndex &parent, int first, int last)
509 {
510 for (int i = first; i <= last; i++) {
511 const QModelIndex index = m_sourceModel->index(i, 0, parent);
512 addItemFromSourceModel(index);
513 }
514 }
515
516 void PlacesItemModel::onSourceModelRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
517 {
518 for(int r = first; r <= last; r++) {
519 const QModelIndex index = m_sourceModel->index(r, 0, parent);
520 int oldIndex = mapFromSource(index);
521 if (oldIndex != -1) {
522 removeItem(oldIndex);
523 }
524 }
525 }
526
527 void PlacesItemModel::onSourceModelRowsAboutToBeMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
528 {
529 Q_UNUSED(destination);
530 Q_UNUSED(row);
531
532 for(int r = start; r <= end; r++) {
533 const QModelIndex sourceIndex = m_sourceModel->index(r, 0, parent);
534 // remove moved item
535 removeItem(mapFromSource(sourceIndex));
536 }
537 }
538
539 void PlacesItemModel::onSourceModelRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
540 {
541 Q_UNUSED(destination);
542 Q_UNUSED(parent);
543
544 const int blockSize = (end - start) + 1;
545
546 for (int r = start; r <= end; r++) {
547 // insert the moved item in the new position
548 const int targetRow = row + (start - r) - (r < row ? blockSize : 0);
549 const QModelIndex targetIndex = m_sourceModel->index(targetRow, 0, destination);
550
551 addItemFromSourceModel(targetIndex);
552 }
553 }
554
555 void PlacesItemModel::onSourceModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
556 {
557 Q_UNUSED(roles);
558
559 for (int r = topLeft.row(); r <= bottomRight.row(); r++) {
560 const QModelIndex sourceIndex = m_sourceModel->index(r, 0);
561 const KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex);
562 PlacesItem *placeItem = itemFromBookmark(bookmark);
563
564 if (placeItem && (!m_hiddenItemsShown && m_sourceModel->isHidden(sourceIndex))) {
565 //hide item if it became invisible
566 removeItem(index(placeItem));
567 return;
568 }
569
570 if (!placeItem && (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex))) {
571 //show item if it became visible
572 addItemFromSourceModel(sourceIndex);
573 return;
574 }
575
576 if (placeItem && !m_sourceModel->isDevice(sourceIndex)) {
577 placeItem->setText(bookmark.text());
578 placeItem->setIcon(sourceIndex.data(KFilePlacesModel::IconNameRole).toString());
579 placeItem->setUrl(m_sourceModel->url(sourceIndex));
580 placeItem->bookmark().setMetaDataItem(QStringLiteral("OnlyInApp"),
581 bookmark.metaDataItem(QStringLiteral("OnlyInApp")));
582 // must update the bookmark object
583 placeItem->setBookmark(bookmark);
584 }
585 }
586 }
587
588 void PlacesItemModel::onSourceModelGroupHiddenChanged(KFilePlacesModel::GroupType group, bool hidden)
589 {
590 const auto groupIndexes = m_sourceModel->groupIndexes(group);
591 for (const QModelIndex &sourceIndex : groupIndexes) {
592 PlacesItem *item = placesItem(mapFromSource(sourceIndex));
593 if (item) {
594 item->setGroupHidden(hidden);
595 }
596 }
597 }
598
599 void PlacesItemModel::cleanupBookmarks()
600 {
601 // KIO model now provides support for baloo urls, and because of that we
602 // need to remove old URLs that were visible only in Dolphin to avoid duplication
603 int row = 0;
604 do {
605 const QModelIndex sourceIndex = m_sourceModel->index(row, 0);
606 const KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex);
607 const QUrl url = bookmark.url();
608 const QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp"));
609
610 if ((appName == KAboutData::applicationData().componentName() ||
611 appName == KAboutData::applicationData().componentName() + DolphinPlacesModelSingleton::applicationNameSuffix()) && balooURLs.contains(url)) {
612 qCDebug(DolphinDebug) << "Removing old baloo url:" << url;
613 m_sourceModel->removePlace(sourceIndex);
614 } else {
615 row++;
616 }
617 } while (row < m_sourceModel->rowCount());
618 }
619
620 void PlacesItemModel::loadBookmarks()
621 {
622 for(int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) {
623 const QModelIndex sourceIndex = m_sourceModel->index(r, 0);
624 if (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex)) {
625 addItemFromSourceModel(sourceIndex);
626 }
627 }
628 }
629
630 void PlacesItemModel::clear() {
631 KStandardItemModel::clear();
632 }
633
634 void PlacesItemModel::proceedWithTearDown()
635 {
636 Q_ASSERT(m_deviceToTearDown);
637
638 connect(m_deviceToTearDown, &Solid::StorageAccess::teardownDone,
639 this, &PlacesItemModel::slotStorageTearDownDone);
640 m_deviceToTearDown->teardown();
641 }
642
643 void PlacesItemModel::deleteItem(int index)
644 {
645 QModelIndex sourceIndex = mapToSource(index);
646 Q_ASSERT(sourceIndex.isValid());
647 m_sourceModel->removePlace(sourceIndex);
648 }
649
650 void PlacesItemModel::refresh()
651 {
652 m_sourceModel->refresh();
653 }
654
655 void PlacesItemModel::hideItem(int index)
656 {
657 PlacesItem* shownItem = placesItem(index);
658 if (!shownItem) {
659 return;
660 }
661
662 shownItem->setHidden(true);
663 }
664
665 QString PlacesItemModel::internalMimeType() const
666 {
667 return "application/x-dolphinplacesmodel-" +
668 QString::number((qptrdiff)this);
669 }
670
671 int PlacesItemModel::groupedDropIndex(int index, const PlacesItem* item) const
672 {
673 Q_ASSERT(item);
674
675 int dropIndex = index;
676 const QString group = item->group();
677
678 const int itemCount = count();
679 if (index < 0) {
680 dropIndex = itemCount;
681 }
682
683 // Search nearest previous item with the same group
684 int previousIndex = -1;
685 for (int i = dropIndex - 1; i >= 0; --i) {
686 if (placesItem(i)->group() == group) {
687 previousIndex = i;
688 break;
689 }
690 }
691
692 // Search nearest next item with the same group
693 int nextIndex = -1;
694 for (int i = dropIndex; i < count(); ++i) {
695 if (placesItem(i)->group() == group) {
696 nextIndex = i;
697 break;
698 }
699 }
700
701 // Adjust the drop-index to be inserted to the
702 // nearest item with the same group.
703 if (previousIndex >= 0 && nextIndex >= 0) {
704 dropIndex = (dropIndex - previousIndex < nextIndex - dropIndex) ?
705 previousIndex + 1 : nextIndex;
706 } else if (previousIndex >= 0) {
707 dropIndex = previousIndex + 1;
708 } else if (nextIndex >= 0) {
709 dropIndex = nextIndex;
710 }
711
712 return dropIndex;
713 }
714
715 bool PlacesItemModel::equalBookmarkIdentifiers(const KBookmark& b1, const KBookmark& b2)
716 {
717 const QString udi1 = b1.metaDataItem(QStringLiteral("UDI"));
718 const QString udi2 = b2.metaDataItem(QStringLiteral("UDI"));
719 if (!udi1.isEmpty() && !udi2.isEmpty()) {
720 return udi1 == udi2;
721 } else {
722 return b1.metaDataItem(QStringLiteral("ID")) == b2.metaDataItem(QStringLiteral("ID"));
723 }
724 }
725
726 int PlacesItemModel::mapFromSource(const QModelIndex &index) const
727 {
728 if (!index.isValid()) {
729 return -1;
730 }
731
732 return m_indexMap.indexOf(index);
733 }
734
735 bool PlacesItemModel::isDir(int index) const
736 {
737 Q_UNUSED(index);
738 return true;
739 }
740
741 KFilePlacesModel::GroupType PlacesItemModel::groupType(int row) const
742 {
743 return m_sourceModel->groupType(mapToSource(row));
744 }
745
746 bool PlacesItemModel::isGroupHidden(KFilePlacesModel::GroupType type) const
747 {
748 return m_sourceModel->isGroupHidden(type);
749 }
750
751 void PlacesItemModel::setGroupHidden(KFilePlacesModel::GroupType type, bool hidden)
752 {
753 return m_sourceModel->setGroupHidden(type, hidden);
754 }
755
756 QModelIndex PlacesItemModel::mapToSource(int row) const
757 {
758 return m_indexMap.value(row);
759 }
760
761 PlacesItem *PlacesItemModel::itemFromBookmark(const KBookmark &bookmark) const
762 {
763 const QString id = bookmarkId(bookmark);
764 for (int i = 0, iMax = count(); i < iMax; i++) {
765 PlacesItem *item = placesItem(i);
766 const KBookmark itemBookmark = item->bookmark();
767 if (bookmarkId(itemBookmark) == id) {
768 return item;
769 }
770 }
771 return nullptr;
772 }
773