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