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