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