]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/panels/places/placesitemmodel.cpp
Fix several bookmark synchronization issues
[dolphin.git] / src / panels / places / placesitemmodel.cpp
index aa0147e9547c87838a3602eee93cb1820da636aa..681d9d17ef7572c465e44f02e66865481a46310f 100644 (file)
 
 #include "placesitemmodel.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 <KBookmark>
 #include <KBookmarkGroup>
 #include <KBookmarkManager>
 #include <KComponentData>
 #include <KDebug>
+#include <KIcon>
 #include <KLocale>
 #include <KStandardDirs>
 #include <KUser>
 #include "placesitem.h"
+#include <QAction>
 #include <QDate>
+#include <QTimer>
 
 #include <Solid/Device>
 #include <Solid/DeviceNotifier>
+#include <Solid/OpticalDisc>
+#include <Solid/OpticalDrive>
+#include <Solid/StorageAccess>
+#include <Solid/StorageDrive>
+
+#ifdef HAVE_NEPOMUK
+    #include <Nepomuk/ResourceManager>
+#endif
 
 PlacesItemModel::PlacesItemModel(QObject* parent) :
     KStandardItemModel(parent),
@@ -56,7 +57,9 @@ PlacesItemModel::PlacesItemModel(QObject* parent) :
     m_bookmarkManager(0),
     m_systemBookmarks(),
     m_systemBookmarksIndexes(),
-    m_hiddenItems()
+    m_hiddenItems(),
+    m_hiddenItemToRemove(-1),
+    m_saveBookmarksTimer(0)
 {
 #ifdef HAVE_NEPOMUK
     m_nepomukRunning = (Nepomuk::ResourceManager::instance()->initialized());
@@ -67,14 +70,30 @@ PlacesItemModel::PlacesItemModel(QObject* parent) :
     createSystemBookmarks();
     initializeAvailableDevices();
     loadBookmarks();
+
+    m_saveBookmarksTimer = new QTimer(this);
+    m_saveBookmarksTimer->setInterval(100);
+    m_saveBookmarksTimer->setSingleShot(true);
+    connect(m_saveBookmarksTimer, SIGNAL(timeout()), this, SLOT(saveBookmarks()));
 }
 
 PlacesItemModel::~PlacesItemModel()
 {
+    saveBookmarks();
     qDeleteAll(m_hiddenItems);
     m_hiddenItems.clear();
 }
 
+PlacesItem* PlacesItemModel::createPlacesItem(const QString& text,
+                                              const KUrl& url,
+                                              const QString& iconName)
+{
+    const KBookmark bookmark = PlacesItem::createBookmark(m_bookmarkManager, text, url, iconName);
+    PlacesItem* item = new PlacesItem(bookmark);
+    item->setGroup(groupName(url));
+    return item;
+}
+
 PlacesItem* PlacesItemModel::placesItem(int index) const
 {
     return dynamic_cast<PlacesItem*>(item(index));
@@ -98,29 +117,6 @@ int PlacesItemModel::hiddenCount() const
     return itemCount;
 }
 
-void PlacesItemModel::setItemHidden(int index, bool hide)
-{
-    if (index >= 0 && index < count()) {
-        PlacesItem* shownItem = placesItem(index);
-        shownItem->setHidden(true);
-        if (!m_hiddenItemsShown && hide) {
-            const int newIndex = hiddenIndex(index);
-            PlacesItem* hiddenItem = new PlacesItem(*shownItem);
-            removeItem(index);
-            m_hiddenItems.insert(newIndex, hiddenItem);
-        }
-#ifdef PLACESITEMMODEL_DEBUG
-        kDebug() << "Changed hide-state from" << index << "to" << hide;
-        showModelState();
-#endif
-    }
-}
-
-bool PlacesItemModel::isItemHidden(int index) const
-{
-    return (index >= 0 && index < count()) ? m_hiddenItems[index] != 0 : false;
-}
-
 void PlacesItemModel::setHiddenItemsShown(bool show)
 {
     if (m_hiddenItemsShown == show) {
@@ -166,15 +162,6 @@ bool PlacesItemModel::hiddenItemsShown() const
     return m_hiddenItemsShown;
 }
 
-bool PlacesItemModel::isSystemItem(int index) const
-{
-    if (index >= 0 && index < count()) {
-        const KUrl url = placesItem(index)->url();
-        return m_systemBookmarksIndexes.contains(url);
-    }
-    return false;
-}
-
 int PlacesItemModel::closestItem(const KUrl& url) const
 {
     int foundIndex = -1;
@@ -211,25 +198,123 @@ QString PlacesItemModel::groupName(const KUrl &url) const
 
 QAction* PlacesItemModel::ejectAction(int index) const
 {
-    Q_UNUSED(index);
+    const PlacesItem* item = placesItem(index);
+    if (item && item->device().is<Solid::OpticalDisc>()) {
+        return new QAction(KIcon("media-eject"), i18nc("@item", "Eject '%1'", item->text()), 0);
+    }
+
     return 0;
 }
 
-QAction* PlacesItemModel::tearDownAction(int index) const
+QAction* PlacesItemModel::teardownAction(int index) const
 {
-    Q_UNUSED(index);
-    return 0;
+    const PlacesItem* item = placesItem(index);
+    if (!item) {
+        return 0;
+    }
+
+    Solid::Device device = item->device();
+    const bool providesTearDown = device.is<Solid::StorageAccess>() &&
+                                  device.as<Solid::StorageAccess>()->isAccessible();
+    if (!providesTearDown) {
+        return 0;
+    }
+
+    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;
+    const QString label = item->text();
+    if (device.is<Solid::OpticalDisc>()) {
+        text = i18nc("@item", "Release '%1'", label);
+    } else if (removable || hotPluggable) {
+        text = i18nc("@item", "Safely Remove '%1'", label);
+        iconName = "media-eject";
+    } else {
+        text = i18nc("@item", "Unmount '%1'", label);
+        iconName = "media-eject";
+    }
+
+    if (iconName.isEmpty()) {
+        return new QAction(text, 0);
+    }
+
+    return new QAction(KIcon(iconName), text, 0);
+}
+
+void PlacesItemModel::requestEject(int index)
+{
+    const PlacesItem* item = placesItem(index);
+    if (item) {
+        Solid::OpticalDrive* drive = item->device().parent().as<Solid::OpticalDrive>();
+        if (drive) {
+            connect(drive, SIGNAL(ejectDone(Solid::ErrorType,QVariant,QString)),
+                    this, SLOT(slotStorageTeardownDone(Solid::ErrorType,QVariant)));
+            drive->eject();
+        } else {
+
+        }
+    }
+}
+
+void PlacesItemModel::requestTeardown(int index)
+{
+    const PlacesItem* item = placesItem(index);
+    if (item) {
+        Solid::StorageAccess* access = item->device().as<Solid::StorageAccess>();
+        if (access) {
+            connect(access, SIGNAL(teardownDone(Solid::ErrorType,QVariant,QString)),
+                    this, SLOT(slotStorageTeardownDone(Solid::ErrorType,QVariant)));
+            access->teardown();
+        } 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);
+        }
+    }
 }
 
 void PlacesItemModel::onItemInserted(int index)
 {
-    int modelIndex = 0;
+    const PlacesItem* insertedItem = placesItem(index);
+    if (insertedItem) {
+        // Take care to apply the PlacesItemModel-order of the inserted item
+        // also to the bookmark-manager.
+        const KBookmark insertedBookmark = insertedItem->bookmark();
+
+        const PlacesItem* previousItem = placesItem(index - 1);
+        KBookmark previousBookmark;
+        if (previousItem) {
+            previousBookmark = previousItem->bookmark();
+        }
+
+        m_bookmarkManager->root().moveBookmark(insertedBookmark, previousBookmark);
+    }
+
+    if (index == count() - 1) {
+        // The item has been appended as last item to the list. In this
+        // case assure that it is also appended after the hidden items and
+        // not before (like done otherwise).
+        m_hiddenItems.append(0);
+        return;
+    }
+
+    int modelIndex = -1;
     int hiddenIndex = 0;
     while (hiddenIndex < m_hiddenItems.count()) {
         if (!m_hiddenItems[hiddenIndex]) {
             ++modelIndex;
             if (modelIndex + 1 == index) {
-                ++hiddenIndex;
                 break;
             }
         }
@@ -237,31 +322,120 @@ void PlacesItemModel::onItemInserted(int index)
     }
     m_hiddenItems.insert(hiddenIndex, 0);
 
+    m_saveBookmarksTimer->start();
+
 #ifdef PLACESITEMMODEL_DEBUG
     kDebug() << "Inserted item" << index;
     showModelState();
 #endif
 }
 
-void PlacesItemModel::onItemRemoved(int index)
+void PlacesItemModel::onItemRemoved(int index, KStandardItem* removedItem)
 {
+    PlacesItem* placesItem = dynamic_cast<PlacesItem*>(removedItem);
+    if (placesItem) {
+        const KBookmark bookmark = placesItem->bookmark();
+        m_bookmarkManager->root().deleteBookmark(bookmark);
+    }
+
     const int removeIndex = hiddenIndex(index);
     Q_ASSERT(!m_hiddenItems[removeIndex]);
     m_hiddenItems.removeAt(removeIndex);
+
+    m_saveBookmarksTimer->start();
+
 #ifdef PLACESITEMMODEL_DEBUG
     kDebug() << "Removed item" << index;
     showModelState();
 #endif
 }
 
+void PlacesItemModel::onItemChanged(int index, const QSet<QByteArray>& changedRoles)
+{
+    const PlacesItem* changedItem = placesItem(index);
+    if (changedItem) {
+        // Take care to apply the PlacesItemModel-order of the inserted item
+        // also to the bookmark-manager.
+        const KBookmark insertedBookmark = changedItem->bookmark();
+
+        const PlacesItem* previousItem = placesItem(index - 1);
+        KBookmark previousBookmark;
+        if (previousItem) {
+            previousBookmark = previousItem->bookmark();
+        }
+
+        m_bookmarkManager->root().moveBookmark(insertedBookmark, previousBookmark);
+    }
+
+    if (changedRoles.contains("isHidden")) {
+        const PlacesItem* shownItem = placesItem(index);
+        Q_ASSERT(shownItem);
+        if (!m_hiddenItemsShown && shownItem->isHidden()) {
+            m_hiddenItemToRemove = index;
+            QTimer::singleShot(0, this, SLOT(removeHiddenItem()));
+        }
+    }
+    m_saveBookmarksTimer->start();
+}
+
 void PlacesItemModel::slotDeviceAdded(const QString& udi)
 {
-    Q_UNUSED(udi);
+    const Solid::Device device(udi);
+    if (m_predicate.matches(device)) {
+        m_availableDevices << udi;
+        const KBookmark bookmark = PlacesItem::createDeviceBookmark(m_bookmarkManager, udi);
+        appendItem(new PlacesItem(bookmark));
+    }
 }
 
 void PlacesItemModel::slotDeviceRemoved(const QString& udi)
 {
-    Q_UNUSED(udi);
+    if (!m_availableDevices.contains(udi)) {
+        return;
+    }
+
+    for (int i = 0; i < m_hiddenItems.count(); ++i) {
+        PlacesItem* item = m_hiddenItems[i];
+        if (item && item->udi() == udi) {
+            m_hiddenItems.removeAt(i);
+            delete item;
+            return;
+        }
+     }
+
+     for (int i = 0; i < count(); ++i) {
+         if (placesItem(i)->udi() == udi) {
+             removeItem(i);
+             return;
+         }
+     }
+}
+
+void PlacesItemModel::slotStorageTeardownDone(Solid::ErrorType error, const QVariant& errorData)
+{
+    if (error && errorData.isValid()) {
+        emit errorMessage(errorData.toString());
+    }
+}
+
+void PlacesItemModel::removeHiddenItem()
+{
+    const PlacesItem* shownItem = placesItem(m_hiddenItemToRemove);
+    const int newIndex = hiddenIndex(m_hiddenItemToRemove);
+    if (shownItem && newIndex >= 0) {
+        PlacesItem* hiddenItem = new PlacesItem(*shownItem);
+        removeItem(m_hiddenItemToRemove);
+        m_hiddenItems.insert(newIndex, hiddenItem);
+        m_saveBookmarksTimer->start();
+    }
+    m_hiddenItemToRemove = -1;
+}
+
+
+void PlacesItemModel::saveBookmarks()
+{
+    // TODO: Temporary deactivated until 100 % backward compatibility is provided
+    // m_bookmarkManager->emitChanged(m_bookmarkManager->root());
 }
 
 void PlacesItemModel::loadBookmarks()
@@ -277,7 +451,7 @@ void PlacesItemModel::loadBookmarks()
 
     // The bookmarks might have a mixed order of "places" and "devices". In
     // Dolphin's places panel the devices should always be appended as last
-    // group.
+    // group, so they are collected as separate lists.
     QList<PlacesItem*> placesItems;
     QList<PlacesItem*> devicesItems;
 
@@ -288,10 +462,11 @@ void PlacesItemModel::loadBookmarks()
         const bool deviceAvailable = devices.remove(udi);
 
         const bool allowedHere = (appName.isEmpty() || appName == KGlobal::mainComponent().componentName())
-                                 && (m_nepomukRunning || url.protocol() != QLatin1String("timeline"));
+                                 && (m_nepomukRunning || (url.protocol() != QLatin1String("timeline") &&
+                                                          url.protocol() != QLatin1String("search")));
 
         if ((udi.isEmpty() && allowedHere) || deviceAvailable) {
-            PlacesItem* item = new PlacesItem(bookmark, udi);
+            PlacesItem* item = new PlacesItem(bookmark);
             if (deviceAvailable) {
                 devicesItems.append(item);
             } else {
@@ -304,10 +479,8 @@ void PlacesItemModel::loadBookmarks()
                     // 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->setUrl(translatedSystemBookmarkUrl(url));
+                    item->setSystemItem(true);
+                    item->setGroup(m_systemBookmarks[index].group);
                 }
             }
         }
@@ -318,24 +491,37 @@ void PlacesItemModel::loadBookmarks()
     addItems(placesItems);
 
     if (!missingSystemBookmarks.isEmpty()) {
+        // The current bookmarks don't contain all system-bookmarks. Add the missing
+        // bookmarks.
         foreach (const SystemBookmarkData& data, m_systemBookmarks) {
             if (missingSystemBookmarks.contains(data.url)) {
-                PlacesItem* item = new PlacesItem();
-                item->setIcon(data.icon);
-                item->setText(data.text);
-                item->setUrl(translatedSystemBookmarkUrl(data.url));
+                KBookmark bookmark = PlacesItem::createBookmark(m_bookmarkManager,
+                                                                data.text,
+                                                                data.url,
+                                                                data.icon);
+
+                const QString protocol = data.url.protocol();
+                if (protocol == QLatin1String("timeline") || protocol == QLatin1String("search")) {
+                    // As long as the KFilePlacesView from kdelibs is available, the system-bookmarks
+                    // for timeline and search should be a Dolphin-specific setting.
+                    bookmark.setMetaDataItem("OnlyInApp", KGlobal::mainComponent().componentName());
+                }
+
+                PlacesItem* item = new PlacesItem(bookmark);
+                item->setSystemItem(true);
                 item->setGroup(data.group);
                 appendItem(item);
             }
         }
     }
 
-    addItems(devicesItems);
+    // Create items for devices that have not stored as bookmark yet
+    foreach (const QString& udi, devices) {
+        const KBookmark bookmark = PlacesItem::createDeviceBookmark(m_bookmarkManager, udi);
+        devicesItems.append(new PlacesItem(bookmark));
+    }
 
-    // TODO: add bookmarks for missing devices
-    // foreach (const QString &udi, devices) {
-    //        bookmark = KFilePlacesItem::createDeviceBookmark(bookmarkManager, udi);
-    // ...
+    addItems(devicesItems);
 
 #ifdef PLACESITEMMODEL_DEBUG
     kDebug() << "Loaded bookmarks";
@@ -346,7 +532,7 @@ void PlacesItemModel::loadBookmarks()
 void PlacesItemModel::addItems(const QList<PlacesItem*>& items)
 {
     foreach (PlacesItem* item, items) {
-        if (item->isHidden()) {
+        if (!m_hiddenItemsShown && item->isHidden()) {
             m_hiddenItems.append(item);
         } else {
             appendItem(item);
@@ -409,7 +595,7 @@ void PlacesItemModel::createSystemBookmarks()
                                                     searchForGroup));
         m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/audio"),
                                                     "folder-sound",
-                                                    i18nc("@item Commonly Accessed", "Audio"),
+                                                    i18nc("@item Commonly Accessed", "Audio Files"),
                                                     searchForGroup));
         m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/videos"),
                                                     "folder-video",
@@ -418,8 +604,7 @@ void PlacesItemModel::createSystemBookmarks()
     }
 
     for (int i = 0; i < m_systemBookmarks.count(); ++i) {
-        const KUrl url = translatedSystemBookmarkUrl(m_systemBookmarks[i].url);
-        m_systemBookmarksIndexes.insert(url, i);
+        m_systemBookmarksIndexes.insert(m_systemBookmarks[i].url, i);
     }
 }
 
@@ -477,100 +662,6 @@ QString PlacesItemModel::searchForGroupName()
     return i18nc("@item", "Search For");
 }
 
-KUrl PlacesItemModel::translatedSystemBookmarkUrl(const KUrl& url)
-{
-    KUrl translatedUrl = url;
-    if (url.protocol() == QLatin1String("timeline")) {
-        translatedUrl = createTimelineUrl(url);
-    } else if (url.protocol() == QLatin1String("search")) {
-        translatedUrl = createSearchUrl(url);
-    }
-
-    return translatedUrl;
-}
-
-KUrl PlacesItemModel::createTimelineUrl(const KUrl& url)
-{
-    // TODO: Clarify with the Nepomuk-team whether it makes sense
-    // provide default-timeline-URLs like 'yesterday', 'this month'
-    // and 'last month'.
-    KUrl timelineUrl;
-
-    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;
-    }
-
-    return timelineUrl;
-}
-
-QString PlacesItemModel::timelineDateString(int year, int month, int day)
-{
-    QString date = QString::number(year) + '-';
-    if (month < 10) {
-        date += '0';
-    }
-    date += QString::number(month);
-
-    if (day >= 1) {
-        date += '-';
-        if (day < 10) {
-            date += '0';
-        }
-        date += QString::number(day);
-    }
-
-    return date;
-}
-
-KUrl PlacesItemModel::createSearchUrl(const KUrl& url)
-{
-    KUrl searchUrl;
-
-#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
-
-    return searchUrl;
-}
-
-#ifdef HAVE_NEPOMUK
-KUrl PlacesItemModel::searchUrlForTerm(const Nepomuk::Query::Term& term)
-{
-    const Nepomuk::Query::Query query(term);
-    return query.toSearchUrl();
-}
-#endif
-
 #ifdef PLACESITEMMODEL_DEBUG
 void PlacesItemModel::showModelState()
 {
@@ -580,7 +671,11 @@ void PlacesItemModel::showModelState()
         if (m_hiddenItems[i]) {
             kDebug() <<  i << "(Hidden)    " << "             " << m_hiddenItems[i]->dataValue("text").toString();
         } else {
-            kDebug() <<  i << "            " << j << "           " << item(j)->dataValue("text").toString();
+            if (item(j)) {
+                kDebug() <<  i << "            " << j << "           " << item(j)->dataValue("text").toString();
+            } else {
+                kDebug() <<  i << "            " << j << "           " << "(not available yet)";
+            }
             ++j;
         }
     }