]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/panels/places/placesitemmodel.cpp
Build TerminalPanel also on Windows
[dolphin.git] / src / panels / places / placesitemmodel.cpp
index d6569ef2d3f4a6dac960f7c4cf11f1c1c2f9e267..077c9044c24930e3c2b37224a2569dc089065175 100644 (file)
  ***************************************************************************/
 
 #include "placesitemmodel.h"
+#include "placesitemsignalhandler.h"
 
-#ifdef HAVE_NEPOMUK
-    #include <Nepomuk/ResourceManager>
-    #include <Nepomuk/Query/ComparisonTerm>
-    #include <Nepomuk/Query/LiteralTerm>
-    #include <Nepomuk/Query/Query>
-    #include <Nepomuk/Query/ResourceTypeTerm>
-    #include <Nepomuk/Vocabulary/NFO>
-    #include <Nepomuk/Vocabulary/NIE>
-#endif
+#include "dolphin_generalsettings.h"
 
 #include <KBookmark>
-#include <KBookmarkGroup>
 #include <KBookmarkManager>
-#include <KComponentData>
-#include <KDebug>
-#include <KIcon>
-#include <kitemviews/kstandarditem.h>
-#include <KLocale>
-#include <KStandardDirs>
-#include <KUser>
+#include "dolphindebug.h"
+#include <QIcon>
+#include <KProtocolInfo>
+#include <KLocalizedString>
+#include <QStandardPaths>
+#include <KAboutData>
+#include "placesitem.h"
+#include <QAction>
 #include <QDate>
+#include <QMimeData>
+#include <QTimer>
+#include <KUrlMimeData>
+#include <KFilePlacesModel>
+
+#include <Solid/Device>
+#include <Solid/DeviceNotifier>
+#include <Solid/OpticalDisc>
+#include <Solid/OpticalDrive>
+#include <Solid/StorageAccess>
+#include <Solid/StorageDrive>
+
+#include <views/dolphinview.h>
+#include <views/viewproperties.h>
+
+namespace {
+    // A suffix to the application-name of the stored bookmarks is
+    // added, which is only read by PlacesItemModel.
+    const QString AppNameSuffix = QStringLiteral("-places-panel");
+    static QList<QUrl> balooURLs = {
+        QUrl(QStringLiteral("timeline:/today")),
+        QUrl(QStringLiteral("timeline:/yesterday")),
+        QUrl(QStringLiteral("timeline:/thismonth")),
+        QUrl(QStringLiteral("timeline:/lastmonth")),
+        QUrl(QStringLiteral("search:/documents")),
+        QUrl(QStringLiteral("search:/images")),
+        QUrl(QStringLiteral("search:/audio")),
+        QUrl(QStringLiteral("search:/videos"))
+    };
+}
 
 PlacesItemModel::PlacesItemModel(QObject* parent) :
     KStandardItemModel(parent),
-    m_nepomukRunning(false),
     m_hiddenItemsShown(false),
-    m_availableDevices(),
-    m_bookmarkManager(0),
-    m_systemBookmarks(),
-    m_systemBookmarksIndexes(),
-    m_hiddenItems()
-{
-#ifdef HAVE_NEPOMUK
-    m_nepomukRunning = (Nepomuk::ResourceManager::instance()->initialized());
-#endif
-    const QString file = KStandardDirs::locateLocal("data", "kfileplaces/bookmarks.xml");
-    m_bookmarkManager = KBookmarkManager::managerForFile(file, "kfilePlaces");
-
-    createSystemBookmarks();
+    m_deviceToTearDown(nullptr),
+    m_storageSetupInProgress(),
+    m_sourceModel(new KFilePlacesModel(KAboutData::applicationData().componentName() + AppNameSuffix, this))
+{
+    cleanupBookmarks();
     loadBookmarks();
+    initializeDefaultViewProperties();
+
+    connect(m_sourceModel.data(), &KFilePlacesModel::rowsInserted, this, &PlacesItemModel::onSourceModelRowsInserted);
+    connect(m_sourceModel.data(), &KFilePlacesModel::rowsAboutToBeRemoved, this, &PlacesItemModel::onSourceModelRowsAboutToBeRemoved);
+    connect(m_sourceModel.data(), &KFilePlacesModel::dataChanged, this, &PlacesItemModel::onSourceModelDataChanged);
+    connect(m_sourceModel.data(), &KFilePlacesModel::rowsAboutToBeMoved, this, &PlacesItemModel::onSourceModelRowsAboutToBeMoved);
+    connect(m_sourceModel.data(), &KFilePlacesModel::rowsMoved, this, &PlacesItemModel::onSourceModelRowsMoved);
+    connect(m_sourceModel.data(), &KFilePlacesModel::groupHiddenChanged, this, &PlacesItemModel::onSourceModelGroupHiddenChanged);
 }
 
 PlacesItemModel::~PlacesItemModel()
 {
-    qDeleteAll(m_hiddenItems);
-    m_hiddenItems.clear();
+}
+
+void PlacesItemModel::createPlacesItem(const QString& text,
+                                       const QUrl& url,
+                                       const QString& iconName,
+                                       int after)
+{
+    m_sourceModel->addPlace(text, url, iconName, {}, mapToSource(after));
+}
+
+PlacesItem* PlacesItemModel::placesItem(int index) const
+{
+    return dynamic_cast<PlacesItem*>(item(index));
 }
 
 int PlacesItemModel::hiddenCount() const
 {
-    int itemCount = 0;
-    foreach (const KStandardItem* item, m_hiddenItems) {
-        if (item) {
-            ++itemCount;
+    return m_sourceModel->hiddenCount();
+}
+
+void PlacesItemModel::setHiddenItemsShown(bool show)
+{
+    if (m_hiddenItemsShown == show) {
+        return;
+    }
+
+    m_hiddenItemsShown = show;
+
+    if (show) {
+        for (int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) {
+            const QModelIndex index = m_sourceModel->index(r, 0);
+            if (!m_sourceModel->isHidden(index)) {
+                continue;
+            }
+            addItemFromSourceModel(index);
+        }
+    } else {
+        for (int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) {
+            const QModelIndex index = m_sourceModel->index(r, 0);
+            if (m_sourceModel->isHidden(index)) {
+                removeItemByIndex(index);
+            }
         }
     }
 
-    return itemCount;
+#ifdef PLACESITEMMODEL_DEBUG
+        qCDebug(DolphinDebug) << "Changed visibility of hidden items";
+        showModelState();
+#endif
+}
+
+bool PlacesItemModel::hiddenItemsShown() const
+{
+    return m_hiddenItemsShown;
+}
+
+int PlacesItemModel::closestItem(const QUrl& url) const
+{
+    return mapFromSource(m_sourceModel->closestItem(url));
 }
 
-void PlacesItemModel::setItemHidden(int index, bool hide)
+// look for the correct position for the item based on source model
+void PlacesItemModel::insertSortedItem(PlacesItem* item)
 {
-    if (index >= 0 && index < count()) {
-        KStandardItem* shownItem = this->item(index);
-        shownItem->setDataValue("isHidden", hide);
-        if (!m_hiddenItemsShown && hide) {
-            const int newIndex = hiddenIndex(index);
-            KStandardItem* hiddenItem = new KStandardItem(*shownItem);
-            removeItem(index);
-            m_hiddenItems.insert(newIndex, hiddenItem);
+    if (!item) {
+        return;
+    }
+
+    const KBookmark iBookmark = item->bookmark();
+    const QString iBookmarkId = bookmarkId(iBookmark);
+    QModelIndex sourceIndex;
+    int pos = 0;
+
+    for(int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) {
+        sourceIndex = m_sourceModel->index(r, 0);
+        const KBookmark sourceBookmark = m_sourceModel->bookmarkForIndex(sourceIndex);
+
+        if (bookmarkId(sourceBookmark) == iBookmarkId) {
+            break;
         }
+
+        if (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex)) {
+            pos++;
+        }
+    }
+
+    m_indexMap.insert(pos, sourceIndex);
+    insertItem(pos, item);
+}
+
+void PlacesItemModel::onItemInserted(int index)
+{
+    KStandardItemModel::onItemInserted(index);
 #ifdef PLACESITEMMODEL_DEBUG
-        kDebug() << "Changed hide-state from" << index << "to" << hide;
-        showModelState();
+    qCDebug(DolphinDebug) << "Inserted item" << index;
+    showModelState();
 #endif
+}
+
+void PlacesItemModel::onItemRemoved(int index, KStandardItem* removedItem)
+{
+    m_indexMap.removeAt(index);
+
+    KStandardItemModel::onItemRemoved(index, removedItem);
+#ifdef PLACESITEMMODEL_DEBUG
+    qCDebug(DolphinDebug) << "Removed item" << index;
+    showModelState();
+#endif
+}
+
+void PlacesItemModel::onItemChanged(int index, const QSet<QByteArray>& changedRoles)
+{
+    const QModelIndex sourceIndex = mapToSource(index);
+    const PlacesItem *changedItem = placesItem(mapFromSource(sourceIndex));
+
+    if (!changedItem || !sourceIndex.isValid()) {
+        qWarning() << "invalid item changed signal";
+        return;
+    }
+    if (changedRoles.contains("isHidden")) {
+        if (m_sourceModel->isHidden(sourceIndex) != changedItem->isHidden()) {
+            m_sourceModel->setPlaceHidden(sourceIndex, changedItem->isHidden());
+        } else {
+            m_sourceModel->refresh();
+        }
     }
+    KStandardItemModel::onItemChanged(index, changedRoles);
 }
 
-bool PlacesItemModel::isItemHidden(int index) const
+QAction* PlacesItemModel::ejectAction(int index) const
 {
-    return (index >= 0 && index < count()) ? m_hiddenItems[index] != 0 : false;
+    const PlacesItem* item = placesItem(index);
+    if (item && item->device().is<Solid::OpticalDisc>()) {
+        return new QAction(QIcon::fromTheme(QStringLiteral("media-eject")), i18nc("@item", "Eject"), nullptr);
+    }
+
+    return nullptr;
 }
 
-void PlacesItemModel::setHiddenItemsShown(bool show)
+QAction* PlacesItemModel::teardownAction(int index) const
+{
+    const PlacesItem* item = placesItem(index);
+    if (!item) {
+        return nullptr;
+    }
+
+    Solid::Device device = item->device();
+    const bool providesTearDown = device.is<Solid::StorageAccess>() &&
+                                  device.as<Solid::StorageAccess>()->isAccessible();
+    if (!providesTearDown) {
+        return nullptr;
+    }
+
+    Solid::StorageDrive* drive = device.as<Solid::StorageDrive>();
+    if (!drive) {
+        drive = device.parent().as<Solid::StorageDrive>();
+    }
+
+    bool hotPluggable = false;
+    bool removable = false;
+    if (drive) {
+        hotPluggable = drive->isHotpluggable();
+        removable = drive->isRemovable();
+    }
+
+    QString iconName;
+    QString text;
+    if (device.is<Solid::OpticalDisc>()) {
+        text = i18nc("@item", "Release");
+    } else if (removable || hotPluggable) {
+        text = i18nc("@item", "Safely Remove");
+        iconName = QStringLiteral("media-eject");
+    } else {
+        text = i18nc("@item", "Unmount");
+        iconName = QStringLiteral("media-eject");
+    }
+
+    if (iconName.isEmpty()) {
+        return new QAction(text, nullptr);
+    }
+
+    return new QAction(QIcon::fromTheme(iconName), text, nullptr);
+}
+
+void PlacesItemModel::requestEject(int index)
 {
-    if (m_hiddenItemsShown != show) {
-        m_hiddenItemsShown = show;
+    const PlacesItem* item = placesItem(index);
+    if (item) {
+        Solid::OpticalDrive* drive = item->device().parent().as<Solid::OpticalDrive>();
+        if (drive) {
+            connect(drive, &Solid::OpticalDrive::ejectDone,
+                    this, &PlacesItemModel::slotStorageTearDownDone);
+            drive->eject();
+        } else {
+            const QString label = item->text();
+            const QString message = i18nc("@info", "The device '%1' is not a disk and cannot be ejected.", label);
+            emit errorMessage(message);
+        }
     }
 }
 
-bool PlacesItemModel::hiddenItemsShown() const
+void PlacesItemModel::requestTearDown(int index)
 {
-    return m_hiddenItemsShown;
+    const PlacesItem* item = placesItem(index);
+    if (item) {
+        Solid::StorageAccess *tmp = item->device().as<Solid::StorageAccess>();
+        if (tmp) {
+            m_deviceToTearDown = tmp;
+            // disconnect the Solid::StorageAccess::teardownRequested
+            // to prevent emitting PlacesItemModel::storageTearDownExternallyRequested
+            // after we have emitted PlacesItemModel::storageTearDownRequested
+            disconnect(tmp, &Solid::StorageAccess::teardownRequested,
+                       item->signalHandler(), &PlacesItemSignalHandler::onTearDownRequested);
+            emit storageTearDownRequested(tmp->filePath());
+        }
+    }
+}
+
+bool PlacesItemModel::storageSetupNeeded(int index) const
+{
+    const PlacesItem* item = placesItem(index);
+    return item ? item->storageSetupNeeded() : false;
 }
 
-bool PlacesItemModel::isSystemItem(int index) const
+void PlacesItemModel::requestStorageSetup(int index)
 {
-    if (index >= 0 && index < count()) {
-        const KUrl url = data(index).value("url").value<KUrl>();
-        return m_systemBookmarksIndexes.contains(url);
+    const PlacesItem* item = placesItem(index);
+    if (!item) {
+        return;
+    }
+
+    Solid::Device device = item->device();
+    const bool setup = device.is<Solid::StorageAccess>()
+                       && !m_storageSetupInProgress.contains(device.as<Solid::StorageAccess>())
+                       && !device.as<Solid::StorageAccess>()->isAccessible();
+    if (setup) {
+        Solid::StorageAccess* access = device.as<Solid::StorageAccess>();
+
+        m_storageSetupInProgress[access] = index;
+
+        connect(access, &Solid::StorageAccess::setupDone,
+                this, &PlacesItemModel::slotStorageSetupDone);
+
+        access->setup();
+    }
+}
+
+QMimeData* PlacesItemModel::createMimeData(const KItemSet& indexes) const
+{
+    QList<QUrl> urls;
+    QByteArray itemData;
+
+    QDataStream stream(&itemData, QIODevice::WriteOnly);
+
+    for (int index : indexes) {
+        const QUrl itemUrl = placesItem(index)->url();
+        if (itemUrl.isValid()) {
+            urls << itemUrl;
+        }
+        stream << index;
+    }
+
+    QMimeData* mimeData = new QMimeData();
+    if (!urls.isEmpty()) {
+        mimeData->setUrls(urls);
+    } else {
+        // #378954: prevent itemDropEvent() drops if there isn't a source url.
+        mimeData->setData(blacklistItemDropEventMimeType(), QByteArrayLiteral("true"));
     }
-    return false;
+    mimeData->setData(internalMimeType(), itemData);
+
+    return mimeData;
+}
+
+bool PlacesItemModel::supportsDropping(int index) const
+{
+    return index >= 0 && index < count();
 }
 
-int PlacesItemModel::closestItem(const KUrl& url) const
+void PlacesItemModel::dropMimeDataBefore(int index, const QMimeData* mimeData)
 {
-    int foundIndex = -1;
-    int maxLength = 0;
+    if (mimeData->hasFormat(internalMimeType())) {
+        // The item has been moved inside the view
+        QByteArray itemData = mimeData->data(internalMimeType());
+        QDataStream stream(&itemData, QIODevice::ReadOnly);
+        int oldIndex;
+        stream >> oldIndex;
+
+        m_sourceModel->movePlace(oldIndex, index);
+    } else if (mimeData->hasFormat(QStringLiteral("text/uri-list"))) {
+        // One or more items must be added to the model
+        const QList<QUrl> urls = KUrlMimeData::urlsFromMimeData(mimeData);
+        for (int i = urls.count() - 1; i >= 0; --i) {
+            const QUrl& url = urls[i];
+
+            QString text = url.fileName();
+            if (text.isEmpty()) {
+                text = url.host();
+            }
 
-    for (int i = 0; i < count(); ++i) {
-        const KUrl itemUrl = data(i).value("url").value<KUrl>();
-        if (itemUrl.isParentOf(url)) {
-            const int length = itemUrl.prettyUrl().length();
-            if (length > maxLength) {
-                foundIndex = i;
-                maxLength = length;
+            if ((url.isLocalFile() && !QFileInfo(url.toLocalFile()).isDir())
+                    || url.scheme() == QLatin1String("trash")) {
+                // Only directories outside the trash are allowed
+                continue;
             }
+
+            createPlacesItem(text, url, KIO::iconNameForUrl(url), qMax(0, index - 1));
         }
     }
+    // will save bookmark alteration and fix sort if that is broken by the drag/drop operation
+    refresh();
+}
 
-    return foundIndex;
+void PlacesItemModel::addItemFromSourceModel(const QModelIndex &index)
+{
+    if (!m_hiddenItemsShown && m_sourceModel->isHidden(index)) {
+        return;
+    }
+
+    const KBookmark bookmark = m_sourceModel->bookmarkForIndex(index);
+    Q_ASSERT(!bookmark.isNull());
+    PlacesItem *item = itemFromBookmark(bookmark);
+    if (!item) {
+        item = new PlacesItem(bookmark);
+    }
+    updateItem(item, index);
+    insertSortedItem(item);
+
+    if (m_sourceModel->isDevice(index)) {
+        connect(item->signalHandler(), &PlacesItemSignalHandler::tearDownExternallyRequested,
+                this, &PlacesItemModel::storageTearDownExternallyRequested);
+    }
 }
 
-QString PlacesItemModel::groupName(const KUrl &url) const
+void PlacesItemModel::removeItemByIndex(const QModelIndex &sourceIndex)
 {
-    const QString protocol = url.protocol();
+    QString id = bookmarkId(m_sourceModel->bookmarkForIndex(sourceIndex));
 
-    if (protocol.contains(QLatin1String("search"))) {
-        return searchForGroupName();
+    for (int i = 0, iMax = count(); i < iMax; ++i) {
+        if (bookmarkId(placesItem(i)->bookmark()) == id) {
+            removeItem(i);
+            return;
+        }
     }
+}
 
-    if (protocol == QLatin1String("timeline")) {
-        return recentlyAccessedGroupName();
+QString PlacesItemModel::bookmarkId(const KBookmark &bookmark) const
+{
+    QString id = bookmark.metaDataItem(QStringLiteral("UDI"));
+    if (id.isEmpty()) {
+        id = bookmark.metaDataItem(QStringLiteral("ID"));
     }
+    return id;
+}
+
+void PlacesItemModel::initializeDefaultViewProperties() const
+{
+    for(int i = 0, iMax = m_sourceModel->rowCount(); i < iMax; i++) {
+        const QModelIndex index = m_sourceModel->index(i, 0);
+        const PlacesItem *item = placesItem(mapFromSource(index));
+        if (!item) {
+            continue;
+        }
 
-    return placesGroupName();
+        // Create default view-properties for all "Search For" and "Recently Saved" bookmarks
+        // in case the user has not already created custom view-properties for a corresponding
+        // query yet.
+        const bool createDefaultViewProperties = item->isSearchOrTimelineUrl() && !GeneralSettings::self()->globalViewProps();
+        if (createDefaultViewProperties) {
+            const QUrl itemUrl = item->url();
+            ViewProperties props(KFilePlacesModel::convertedUrl(itemUrl));
+            if (!props.exist()) {
+                const QString path = itemUrl.path();
+                if (path == QLatin1String("/documents")) {
+                    props.setViewMode(DolphinView::DetailsView);
+                    props.setPreviewsShown(false);
+                    props.setVisibleRoles({"text", "path"});
+                } else if (path == QLatin1String("/images")) {
+                    props.setViewMode(DolphinView::IconsView);
+                    props.setPreviewsShown(true);
+                    props.setVisibleRoles({"text", "imageSize"});
+                } else if (path == QLatin1String("/audio")) {
+                    props.setViewMode(DolphinView::DetailsView);
+                    props.setPreviewsShown(false);
+                    props.setVisibleRoles({"text", "artist", "album"});
+                } else if (path == QLatin1String("/videos")) {
+                    props.setViewMode(DolphinView::IconsView);
+                    props.setPreviewsShown(true);
+                    props.setVisibleRoles({"text"});
+                } else if (itemUrl.scheme() == QLatin1String("timeline")) {
+                    props.setViewMode(DolphinView::DetailsView);
+                    props.setVisibleRoles({"text", "modificationtime"});
+                }
+                props.save();
+            }
+        }
+    }
 }
 
-QAction* PlacesItemModel::ejectAction(int index) const
+void PlacesItemModel::updateItem(PlacesItem *item, const QModelIndex &index)
 {
-    Q_UNUSED(index);
-    return 0;
+    item->setGroup(index.data(KFilePlacesModel::GroupRole).toString());
+    item->setIcon(index.data(KFilePlacesModel::IconNameRole).toString());
+    item->setGroupHidden(index.data(KFilePlacesModel::GroupHiddenRole).toBool());
 }
 
-QAction* PlacesItemModel::tearDownAction(int index) const
+void PlacesItemModel::slotStorageTearDownDone(Solid::ErrorType error, const QVariant& errorData)
 {
-    Q_UNUSED(index);
-    return 0;
+    if (error && errorData.isValid()) {
+        emit errorMessage(errorData.toString());
+    }
+    m_deviceToTearDown->disconnect();
+    m_deviceToTearDown = nullptr;
 }
 
-void PlacesItemModel::onItemInserted(int index)
+void PlacesItemModel::slotStorageSetupDone(Solid::ErrorType error,
+                                           const QVariant& errorData,
+                                           const QString& udi)
 {
-    if (index == count() - 1) {
-        m_hiddenItems.append(0);
+    Q_UNUSED(udi);
+
+    const int index = m_storageSetupInProgress.take(sender());
+    const PlacesItem*  item = placesItem(index);
+    if (!item) {
+        return;
+    }
+
+    if (error != Solid::NoError) {
+        if (errorData.isValid()) {
+            emit errorMessage(i18nc("@info", "An error occurred while accessing '%1', the system responded: %2",
+                                    item->text(),
+                                    errorData.toString()));
+        } else {
+            emit errorMessage(i18nc("@info", "An error occurred while accessing '%1'",
+                                    item->text()));
+        }
+        emit storageSetupDone(index, false);
     } else {
-        m_hiddenItems.insert(hiddenIndex(index), 0);
+        emit storageSetupDone(index, true);
     }
 }
 
-void PlacesItemModel::onItemRemoved(int index)
+void PlacesItemModel::onSourceModelRowsInserted(const QModelIndex &parent, int first, int last)
 {
-    const int removeIndex = hiddenIndex(index);
-    Q_ASSERT(!m_hiddenItems[removeIndex]);
-    m_hiddenItems.removeAt(removeIndex);
-#ifdef PLACESITEMMODEL_DEBUG
-    kDebug() << "Removed item" << index;
-    showModelState();
-#endif
+    for (int i = first; i <= last; i++) {
+        const QModelIndex index = m_sourceModel->index(i, 0, parent);
+        addItemFromSourceModel(index);
+    }
 }
 
-void PlacesItemModel::loadBookmarks()
+void PlacesItemModel::onSourceModelRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
 {
-    KBookmarkGroup root = m_bookmarkManager->root();
-    KBookmark bookmark = root.first();
-    QSet<QString> devices = m_availableDevices;
+    for(int r = first; r <= last; r++) {
+        const QModelIndex index = m_sourceModel->index(r, 0, parent);
+        int oldIndex = mapFromSource(index);
+        if (oldIndex != -1) {
+            removeItem(oldIndex);
+        }
+    }
+}
+
+void PlacesItemModel::onSourceModelRowsAboutToBeMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
+{
+    Q_UNUSED(destination);
+    Q_UNUSED(row);
 
-    QSet<KUrl> missingSystemBookmarks;
-    foreach (const SystemBookmarkData& data, m_systemBookmarks) {
-        missingSystemBookmarks.insert(data.url);
+    for(int r = start; r <= end; r++) {
+        const QModelIndex sourceIndex = m_sourceModel->index(r, 0, parent);
+        // remove moved item
+        removeItem(mapFromSource(sourceIndex));
     }
+}
 
-    while (!bookmark.isNull()) {
-        const QString udi = bookmark.metaDataItem("UDI");
-        const KUrl url = bookmark.url();
-        const QString appName = bookmark.metaDataItem("OnlyInApp");
-        const bool deviceAvailable = devices.remove(udi);
-
-        const bool allowedHere = (appName.isEmpty() || appName == KGlobal::mainComponent().componentName())
-                                 && (m_nepomukRunning || url.protocol() != QLatin1String("timeline"));
-
-        if ((udi.isEmpty() && allowedHere) || deviceAvailable) {
-            KStandardItem* item = new KStandardItem();
-            item->setIcon(KIcon(bookmark.icon()));
-            item->setDataValue("address", bookmark.address());
-            item->setDataValue("url", url);
-
-            if (missingSystemBookmarks.contains(url)) {
-                missingSystemBookmarks.remove(url);
-                // Apply the translated text to the system bookmarks, otherwise an outdated
-                // translation might be shown.
-                const int index = m_systemBookmarksIndexes.value(url);
-                item->setText(m_systemBookmarks[index].text);
-
-                // The system bookmarks don't contain "real" queries stored as URLs, so
-                // they must be translated first.
-                item->setDataValue("url", translatedSystemBookmarkUrl(url));
-            } else {
-                item->setText(bookmark.text());
-            }
+void PlacesItemModel::onSourceModelRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
+{
+    Q_UNUSED(destination);
+    Q_UNUSED(parent);
 
-            if (deviceAvailable) {
-                item->setDataValue("udi", udi);
-                item->setGroup(i18nc("@item", "Devices"));
-            } else {
-                item->setGroup(i18nc("@item", "Places"));
-            }
+    const int blockSize = (end - start) + 1;
 
-            if (bookmark.metaDataItem("IsHidden") == QLatin1String("true")) {
-                m_hiddenItems.append(item);
-            } else {
-                appendItem(item);
-            }
+    for (int r = start; r <= end; r++) {
+        // insert the moved item in the new position
+        const int targetRow = row + (start - r) - (r < row ? blockSize : 0);
+        const QModelIndex targetIndex = m_sourceModel->index(targetRow, 0, destination);
+
+        addItemFromSourceModel(targetIndex);
+    }
+}
+
+void PlacesItemModel::onSourceModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
+{
+    Q_UNUSED(roles);
+
+    for (int r = topLeft.row(); r <= bottomRight.row(); r++) {
+        const QModelIndex sourceIndex = m_sourceModel->index(r, 0);
+        const KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex);
+        PlacesItem *placeItem = itemFromBookmark(bookmark);
+
+        if (placeItem && (!m_hiddenItemsShown && m_sourceModel->isHidden(sourceIndex))) {
+            //hide item if it became invisible
+            removeItem(index(placeItem));
+            return;
         }
 
-        bookmark = root.next(bookmark);
+        if (!placeItem && (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex))) {
+            //show item if it became visible
+            addItemFromSourceModel(sourceIndex);
+            return;
+        }
+
+        if (placeItem && !m_sourceModel->isDevice(sourceIndex)) {
+            placeItem->setText(bookmark.text());
+            placeItem->setIcon(sourceIndex.data(KFilePlacesModel::IconNameRole).toString());
+            placeItem->setUrl(m_sourceModel->url(sourceIndex));
+            placeItem->bookmark().setMetaDataItem(QStringLiteral("OnlyInApp"),
+                                                  bookmark.metaDataItem(QStringLiteral("OnlyInApp")));
+            // must update the bookmark object
+            placeItem->setBookmark(bookmark);
+        }
     }
+}
 
-    if (!missingSystemBookmarks.isEmpty()) {
-        foreach (const SystemBookmarkData& data, m_systemBookmarks) {
-            if (missingSystemBookmarks.contains(data.url)) {
-                KStandardItem* item = new KStandardItem();
-                item->setIcon(KIcon(data.icon));
-                item->setText(data.text);
-                item->setDataValue("url", translatedSystemBookmarkUrl(data.url));
-                item->setGroup(data.group);
-                appendItem(item);
-            }
+void PlacesItemModel::onSourceModelGroupHiddenChanged(KFilePlacesModel::GroupType group, bool hidden)
+{
+    for(const QModelIndex &sourceIndex : m_sourceModel->groupIndexes(group)) {
+        PlacesItem *item = placesItem(mapFromSource(sourceIndex));
+        if (item) {
+            item->setGroupHidden(hidden);
         }
     }
+}
 
-#ifdef PLACESITEMMODEL_DEBUG
-    kDebug() << "Loaded bookmarks";
-    showModelState();
-#endif
+void PlacesItemModel::cleanupBookmarks()
+{
+    // KIO model now provides support for baloo urls, and because of that we
+    // need to remove old URLs that were visible only in Dolphin to avoid duplication
+    int row = 0;
+    do {
+        const QModelIndex sourceIndex = m_sourceModel->index(row, 0);
+        const KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex);
+        const QUrl url = bookmark.url();
+        const QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp"));
+
+        if ((appName == KAboutData::applicationData().componentName() ||
+             appName == KAboutData::applicationData().componentName() + AppNameSuffix) && balooURLs.contains(url)) {
+            qCDebug(DolphinDebug) << "Removing old baloo url:" << url;
+            m_sourceModel->removePlace(sourceIndex);
+        } else {
+            row++;
+        }
+    } while (row < m_sourceModel->rowCount());
 }
 
-void PlacesItemModel::createSystemBookmarks()
-{
-    Q_ASSERT(m_systemBookmarks.isEmpty());
-    Q_ASSERT(m_systemBookmarksIndexes.isEmpty());
-
-    const QString placesGroup = placesGroupName();
-    const QString recentlyAccessedGroup = recentlyAccessedGroupName();
-    const QString searchForGroup = searchForGroupName();
-    const QString timeLineIcon = "package_utility_time"; // TODO: Ask the Oxygen team to create
-                                                         // a custom icon for the timeline-protocol
-
-    m_systemBookmarks.append(SystemBookmarkData(KUrl(KUser().homeDir()),
-                                                "user-home",
-                                                i18nc("@item", "Home"),
-                                                placesGroup));
-    m_systemBookmarks.append(SystemBookmarkData(KUrl("remote:/"),
-                                                "network-workgroup",
-                                                i18nc("@item", "Network"),
-                                                placesGroup));
-    m_systemBookmarks.append(SystemBookmarkData(KUrl("/"),
-                                                "folder-red",
-                                                i18nc("@item", "Root"),
-                                                placesGroup));
-    m_systemBookmarks.append(SystemBookmarkData(KUrl("trash:/"),
-                                                "user-trash",
-                                                i18nc("@item", "Trash"),
-                                                placesGroup));
-
-    if (m_nepomukRunning) {
-        m_systemBookmarks.append(SystemBookmarkData(KUrl("timeline:/today"),
-                                                    timeLineIcon,
-                                                    i18nc("@item Recently Accessed", "Today"),
-                                                    recentlyAccessedGroup));
-        m_systemBookmarks.append(SystemBookmarkData(KUrl("timeline:/yesterday"),
-                                                    timeLineIcon,
-                                                    i18nc("@item Recently Accessed", "Yesterday"),
-                                                    recentlyAccessedGroup));
-        m_systemBookmarks.append(SystemBookmarkData(KUrl("timeline:/thismonth"),
-                                                    timeLineIcon,
-                                                    i18nc("@item Recently Accessed", "This Month"),
-                                                    recentlyAccessedGroup));
-        m_systemBookmarks.append(SystemBookmarkData(KUrl("timeline:/lastmonth"),
-                                                    timeLineIcon,
-                                                    i18nc("@item Recently Accessed", "Last Month"),
-                                                    recentlyAccessedGroup));
-        m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/documents"),
-                                                    "folder-txt",
-                                                    i18nc("@item Commonly Accessed", "Documents"),
-                                                    searchForGroup));
-        m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/images"),
-                                                    "folder-image",
-                                                    i18nc("@item Commonly Accessed", "Images"),
-                                                    searchForGroup));
-        m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/audio"),
-                                                    "folder-sound",
-                                                    i18nc("@item Commonly Accessed", "Audio"),
-                                                    searchForGroup));
-        m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/videos"),
-                                                    "folder-video",
-                                                    i18nc("@item Commonly Accessed", "Videos"),
-                                                    searchForGroup));
-    }
-
-    for (int i = 0; i < m_systemBookmarks.count(); ++i) {
-        const KUrl url = translatedSystemBookmarkUrl(m_systemBookmarks[i].url);
-        m_systemBookmarksIndexes.insert(url, i);
-    }
-}
-
-int PlacesItemModel::hiddenIndex(int index) const
-{
-    int hiddenIndex = 0;
-    int visibleItemIndex = 0;
-    while (hiddenIndex < m_hiddenItems.count()) {
-        if (!m_hiddenItems[hiddenIndex]) {
-            if (visibleItemIndex == index) {
-                break;
-            }
-            ++visibleItemIndex;
+void PlacesItemModel::loadBookmarks()
+{
+    for(int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) {
+        const QModelIndex sourceIndex = m_sourceModel->index(r, 0);
+        if (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex)) {
+            addItemFromSourceModel(sourceIndex);
         }
-        ++hiddenIndex;
     }
 
-    return hiddenIndex >= m_hiddenItems.count() ? -1 : hiddenIndex;
+#ifdef PLACESITEMMODEL_DEBUG
+    qCDebug(DolphinDebug) << "Loaded bookmarks";
+    showModelState();
+#endif
+}
+
+void PlacesItemModel::clear() {
+    KStandardItemModel::clear();
 }
 
-QString PlacesItemModel::placesGroupName()
+void PlacesItemModel::proceedWithTearDown()
 {
-    return i18nc("@item", "Places");
+    Q_ASSERT(m_deviceToTearDown);
+
+    connect(m_deviceToTearDown, &Solid::StorageAccess::teardownDone,
+            this, &PlacesItemModel::slotStorageTearDownDone);
+    m_deviceToTearDown->teardown();
 }
 
-QString PlacesItemModel::recentlyAccessedGroupName()
+void PlacesItemModel::deleteItem(int index)
 {
-    return i18nc("@item", "Recently Accessed");
+    QModelIndex sourceIndex = mapToSource(index);
+    Q_ASSERT(sourceIndex.isValid());
+    m_sourceModel->removePlace(sourceIndex);
 }
 
-QString PlacesItemModel::searchForGroupName()
+void PlacesItemModel::refresh()
 {
-    return i18nc("@item", "Search For");
+    m_sourceModel->refresh();
 }
 
-KUrl PlacesItemModel::translatedSystemBookmarkUrl(const KUrl& url)
+void PlacesItemModel::hideItem(int index)
 {
-    KUrl translatedUrl = url;
-    if (url.protocol() == QLatin1String("timeline")) {
-        translatedUrl = createTimelineUrl(url);
-    } else if (url.protocol() == QLatin1String("search")) {
-        translatedUrl = createSearchUrl(url);
+    PlacesItem* shownItem = placesItem(index);
+    if (!shownItem) {
+        return;
     }
 
-    return translatedUrl;
+    shownItem->setHidden(true);
 }
 
-KUrl PlacesItemModel::createTimelineUrl(const KUrl& url)
+QString PlacesItemModel::internalMimeType() const
 {
-    // TODO: Clarify with the Nepomuk-team whether it makes sense
-    // provide default-timeline-URLs like 'yesterday', 'this month'
-    // and 'last month'.
-    KUrl timelineUrl;
+    return "application/x-dolphinplacesmodel-" +
+            QString::number((qptrdiff)this);
+}
 
-    const QString path = url.pathOrUrl();
-    if (path.endsWith("yesterday")) {
-        const QDate date = QDate::currentDate().addDays(-1);
-        const int year = date.year();
-        const int month = date.month();
-        const int day = date.day();
-        timelineUrl = "timeline:/" + timelineDateString(year, month) +
-              '/' + timelineDateString(year, month, day);
-    } else if (path.endsWith("thismonth")) {
-        const QDate date = QDate::currentDate();
-        timelineUrl = "timeline:/" + timelineDateString(date.year(), date.month());
-    } else if (path.endsWith("lastmonth")) {
-        const QDate date = QDate::currentDate().addMonths(-1);
-        timelineUrl = "timeline:/" + timelineDateString(date.year(), date.month());
-    } else {
-        Q_ASSERT(path.endsWith("today"));
-        timelineUrl= url;
+int PlacesItemModel::groupedDropIndex(int index, const PlacesItem* item) const
+{
+    Q_ASSERT(item);
+
+    int dropIndex = index;
+    const QString group = item->group();
+
+    const int itemCount = count();
+    if (index < 0) {
+        dropIndex = itemCount;
+    }
+
+    // Search nearest previous item with the same group
+    int previousIndex = -1;
+    for (int i = dropIndex - 1; i >= 0; --i) {
+        if (placesItem(i)->group() == group) {
+            previousIndex = i;
+            break;
+        }
+    }
+
+    // Search nearest next item with the same group
+    int nextIndex = -1;
+    for (int i = dropIndex; i < count(); ++i) {
+        if (placesItem(i)->group() == group) {
+            nextIndex = i;
+            break;
+        }
     }
 
-    return timelineUrl;
+    // Adjust the drop-index to be inserted to the
+    // nearest item with the same group.
+    if (previousIndex >= 0 && nextIndex >= 0) {
+        dropIndex = (dropIndex - previousIndex < nextIndex - dropIndex) ?
+                    previousIndex + 1 : nextIndex;
+    } else if (previousIndex >= 0) {
+        dropIndex = previousIndex + 1;
+    } else if (nextIndex >= 0) {
+        dropIndex = nextIndex;
+    }
+
+    return dropIndex;
 }
 
-QString PlacesItemModel::timelineDateString(int year, int month, int day)
+bool PlacesItemModel::equalBookmarkIdentifiers(const KBookmark& b1, const KBookmark& b2)
 {
-    QString date = QString::number(year) + '-';
-    if (month < 10) {
-        date += '0';
+    const QString udi1 = b1.metaDataItem(QStringLiteral("UDI"));
+    const QString udi2 = b2.metaDataItem(QStringLiteral("UDI"));
+    if (!udi1.isEmpty() && !udi2.isEmpty()) {
+        return udi1 == udi2;
+    } else {
+        return b1.metaDataItem(QStringLiteral("ID")) == b2.metaDataItem(QStringLiteral("ID"));
     }
-    date += QString::number(month);
+}
 
-    if (day >= 1) {
-        date += '-';
-        if (day < 10) {
-            date += '0';
-        }
-        date += QString::number(day);
+int PlacesItemModel::mapFromSource(const QModelIndex &index) const
+{
+    if (!index.isValid()) {
+        return -1;
     }
 
-    return date;
+    return m_indexMap.indexOf(index);
 }
 
-KUrl PlacesItemModel::createSearchUrl(const KUrl& url)
+bool PlacesItemModel::isDir(int index) const
 {
-    KUrl searchUrl;
+    Q_UNUSED(index);
+    return true;
+}
 
-#ifdef HAVE_NEPOMUK
-    const QString path = url.pathOrUrl();
-    if (path.endsWith("documents")) {
-        searchUrl = searchUrlForTerm(Nepomuk::Query::ResourceTypeTerm(Nepomuk::Vocabulary::NFO::Document()));
-    } else if (path.endsWith("images")) {
-        searchUrl = searchUrlForTerm(Nepomuk::Query::ResourceTypeTerm(Nepomuk::Vocabulary::NFO::Image()));
-    } else if (path.endsWith("audio")) {
-        searchUrl = searchUrlForTerm(Nepomuk::Query::ComparisonTerm(Nepomuk::Vocabulary::NIE::mimeType(),
-                                                                    Nepomuk::Query::LiteralTerm("audio")));
-    } else if (path.endsWith("videos")) {
-        searchUrl = searchUrlForTerm(Nepomuk::Query::ComparisonTerm(Nepomuk::Vocabulary::NIE::mimeType(),
-                                                                    Nepomuk::Query::LiteralTerm("video")));
-    } else {
-        Q_ASSERT(false);
-    }
-#else
-    Q_UNUSED(url);
-#endif
+KFilePlacesModel::GroupType PlacesItemModel::groupType(int row) const
+{
+    return m_sourceModel->groupType(mapToSource(row));
+}
 
-    return searchUrl;
+bool PlacesItemModel::isGroupHidden(KFilePlacesModel::GroupType type) const
+{
+    return m_sourceModel->isGroupHidden(type);
 }
 
-#ifdef HAVE_NEPOMUK
-KUrl PlacesItemModel::searchUrlForTerm(const Nepomuk::Query::Term& term)
+void PlacesItemModel::setGroupHidden(KFilePlacesModel::GroupType type, bool hidden)
 {
-    const Nepomuk::Query::Query query(term);
-    return query.toSearchUrl();
+    return m_sourceModel->setGroupHidden(type, hidden);
+}
+
+QModelIndex PlacesItemModel::mapToSource(int row) const
+{
+    return m_indexMap.value(row);
+}
+
+PlacesItem *PlacesItemModel::itemFromBookmark(const KBookmark &bookmark) const
+{
+    const QString id = bookmarkId(bookmark);
+    for (int i = 0, iMax = count(); i < iMax; i++) {
+        PlacesItem *item = placesItem(i);
+        const KBookmark itemBookmark = item->bookmark();
+        if (bookmarkId(itemBookmark) == id) {
+            return item;
+        }
+    }
+    return nullptr;
 }
-#endif
 
 #ifdef PLACESITEMMODEL_DEBUG
 void PlacesItemModel::showModelState()
 {
-    kDebug() << "hidden-index   model-index   text";
-    int j = 0;
-    for (int i = 0; i < m_hiddenItems.count(); ++i) {
-        if (m_hiddenItems[i]) {
-            kDebug() <<  i << "(Hidden)    " << "             " << m_hiddenItems[i]->dataValue("text").toString();
+    qCDebug(DolphinDebug) << "=================================";
+    qCDebug(DolphinDebug) << "Model:";
+    qCDebug(DolphinDebug) << "hidden-index model-index   text";
+    int modelIndex = 0;
+    for (int i = 0; i < m_bookmarkedItems.count(); ++i) {
+        if (m_bookmarkedItems[i]) {
+            qCDebug(DolphinDebug) <<  i << "(Hidden)    " << "             " << m_bookmarkedItems[i]->dataValue("text").toString();
+        } else {
+            if (item(modelIndex)) {
+                qCDebug(DolphinDebug) <<  i << "          " << modelIndex << "           " << item(modelIndex)->dataValue("text").toString();
+            } else {
+                qCDebug(DolphinDebug) <<  i << "          " << modelIndex << "           " << "(not available yet)";
+            }
+            ++modelIndex;
+        }
+    }
+
+    qCDebug(DolphinDebug);
+    qCDebug(DolphinDebug) << "Bookmarks:";
+
+    int bookmarkIndex = 0;
+    KBookmarkGroup root = m_bookmarkManager->root();
+    KBookmark bookmark = root.first();
+    while (!bookmark.isNull()) {
+        const QString udi = bookmark.metaDataItem("UDI");
+        const QString text = udi.isEmpty() ? bookmark.text() : udi;
+        if (bookmark.metaDataItem("IsHidden") == QLatin1String("true")) {
+            qCDebug(DolphinDebug) << bookmarkIndex << "(Hidden)" << text;
         } else {
-            kDebug() <<  i << "            " << j << "           " << item(j)->dataValue("text").toString();
-            ++j;
+            qCDebug(DolphinDebug) << bookmarkIndex << "        " << text;
         }
+
+        bookmark = root.next(bookmark);
+        ++bookmarkIndex;
     }
 }
 #endif
 
-#include "placesitemmodel.moc"