#include "placesitemmodel.h"
+#include "dolphin_generalsettings.h"
+
#include <KBookmark>
#include <KBookmarkGroup>
#include <KBookmarkManager>
#include <KComponentData>
#include <KDebug>
#include <KIcon>
+#include <kprotocolinfo.h>
#include <KLocale>
#include <KStandardDirs>
#include <KUser>
#include "placesitem.h"
#include <QAction>
#include <QDate>
+#include <QMimeData>
#include <QTimer>
#include <Solid/Device>
#include <Solid/StorageAccess>
#include <Solid/StorageDrive>
+#include <views/dolphinview.h>
+#include <views/viewproperties.h>
+
#ifdef HAVE_NEPOMUK
- #include <Nepomuk/ResourceManager>
+ #include <Nepomuk2/ResourceManager>
+ #include <Nepomuk2/Query/ComparisonTerm>
+ #include <Nepomuk2/Query/LiteralTerm>
+ #include <Nepomuk2/Query/FileQuery>
+ #include <Nepomuk2/Query/ResourceTypeTerm>
+ #include <Nepomuk2/Vocabulary/NFO>
+ #include <Nepomuk2/Vocabulary/NIE>
#endif
+namespace {
+ // As long as KFilePlacesView from kdelibs is available in parallel, the
+ // system-bookmarks for "Recently Accessed" and "Search For" should be
+ // shown only inside the Places Panel. This is necessary as the stored
+ // URLs needs to get translated to a Nepomuk-search-URL on-the-fly to
+ // be independent from changes in the Nepomuk-search-URL-syntax.
+ // Hence a prefix to the application-name of the stored bookmarks is
+ // added, which is only read by PlacesItemModel.
+ const char* AppNamePrefix = "-places-panel";
+}
+
PlacesItemModel::PlacesItemModel(QObject* parent) :
KStandardItemModel(parent),
- m_nepomukRunning(false),
+ m_fileIndexingEnabled(false),
m_hiddenItemsShown(false),
m_availableDevices(),
m_predicate(),
m_bookmarkManager(0),
m_systemBookmarks(),
m_systemBookmarksIndexes(),
- m_hiddenItems(),
+ m_bookmarkedItems(),
m_hiddenItemToRemove(-1),
- m_saveBookmarksTimer(0)
+ m_saveBookmarksTimer(0),
+ m_updateBookmarksTimer(0),
+ m_storageSetupInProgress()
{
#ifdef HAVE_NEPOMUK
- m_nepomukRunning = (Nepomuk::ResourceManager::instance()->initialized());
+ Nepomuk2::ResourceManager* rm = Nepomuk2::ResourceManager::instance();
+ connect(rm, SIGNAL(nepomukSystemStarted()), this, SLOT(slotNepomukStarted()));
+ connect(rm, SIGNAL(nepomukSystemStopped()), this, SLOT(slotNepomukStopped()));
+
+ if (rm->initialized()) {
+ KConfig config("nepomukserverrc");
+ m_fileIndexingEnabled = config.group("Service-nepomukfileindexer").readEntry("autostart", true);
+ }
+
#endif
const QString file = KStandardDirs::locateLocal("data", "kfileplaces/bookmarks.xml");
m_bookmarkManager = KBookmarkManager::managerForFile(file, "kfilePlaces");
initializeAvailableDevices();
loadBookmarks();
+ const int syncBookmarksTimeout = 100;
+
m_saveBookmarksTimer = new QTimer(this);
- m_saveBookmarksTimer->setInterval(100);
+ m_saveBookmarksTimer->setInterval(syncBookmarksTimeout);
m_saveBookmarksTimer->setSingleShot(true);
connect(m_saveBookmarksTimer, SIGNAL(timeout()), this, SLOT(saveBookmarks()));
+
+ m_updateBookmarksTimer = new QTimer(this);
+ m_updateBookmarksTimer->setInterval(syncBookmarksTimeout);
+ m_updateBookmarksTimer->setSingleShot(true);
+ connect(m_updateBookmarksTimer, SIGNAL(timeout()), this, SLOT(updateBookmarks()));
+
+ connect(m_bookmarkManager, SIGNAL(changed(QString,QString)),
+ m_updateBookmarksTimer, SLOT(start()));
+ connect(m_bookmarkManager, SIGNAL(bookmarksChanged(QString)),
+ m_updateBookmarksTimer, SLOT(start()));
}
PlacesItemModel::~PlacesItemModel()
{
saveBookmarks();
- qDeleteAll(m_hiddenItems);
- m_hiddenItems.clear();
+ qDeleteAll(m_bookmarkedItems);
+ m_bookmarkedItems.clear();
+}
+
+PlacesItem* PlacesItemModel::createPlacesItem(const QString& text,
+ const KUrl& url,
+ const QString& iconName)
+{
+ const KBookmark bookmark = PlacesItem::createBookmark(m_bookmarkManager, text, url, iconName);
+ return new PlacesItem(bookmark);
}
PlacesItem* PlacesItemModel::placesItem(int index) const
int PlacesItemModel::hiddenCount() const
{
int modelIndex = 0;
- int itemCount = 0;
- foreach (const PlacesItem* hiddenItem, m_hiddenItems) {
- if (hiddenItem) {
- ++itemCount;
+ int hiddenItemCount = 0;
+ foreach (const PlacesItem* item, m_bookmarkedItems) {
+ if (item) {
+ ++hiddenItemCount;
} else {
if (placesItem(modelIndex)->isHidden()) {
- ++itemCount;
+ ++hiddenItemCount;
}
++modelIndex;
}
}
- return itemCount;
+ return hiddenItemCount;
}
void PlacesItemModel::setHiddenItemsShown(bool show)
m_hiddenItemsShown = show;
if (show) {
- // Move all items that are part of m_hiddenItems to the model.
+ // Move all items that are part of m_bookmarkedItems to the model.
+ QList<PlacesItem*> itemsToInsert;
+ QList<int> insertPos;
int modelIndex = 0;
- for (int hiddenIndex = 0; hiddenIndex < m_hiddenItems.count(); ++hiddenIndex) {
- if (m_hiddenItems[hiddenIndex]) {
- PlacesItem* visibleItem = new PlacesItem(*m_hiddenItems[hiddenIndex]);
- delete m_hiddenItems[hiddenIndex];
- m_hiddenItems.removeAt(hiddenIndex);
- insertItem(modelIndex, visibleItem);
- Q_ASSERT(!m_hiddenItems[hiddenIndex]);
+ for (int i = 0; i < m_bookmarkedItems.count(); ++i) {
+ if (m_bookmarkedItems[i]) {
+ itemsToInsert.append(m_bookmarkedItems[i]);
+ m_bookmarkedItems[i] = 0;
+ insertPos.append(modelIndex);
}
++modelIndex;
}
+
+ // Inserting the items will automatically insert an item
+ // to m_bookmarkedItems in PlacesItemModel::onItemsInserted().
+ // The items are temporary saved in itemsToInsert, so
+ // m_bookmarkedItems can be shrinked now.
+ m_bookmarkedItems.erase(m_bookmarkedItems.begin(),
+ m_bookmarkedItems.begin() + itemsToInsert.count());
+
+ for (int i = 0; i < itemsToInsert.count(); ++i) {
+ insertItem(insertPos[i], itemsToInsert[i]);
+ }
+
+ Q_ASSERT(m_bookmarkedItems.count() == count());
} else {
// Move all items of the model, where the "isHidden" property is true, to
- // m_hiddenItems.
- Q_ASSERT(m_hiddenItems.count() == count());
+ // m_bookmarkedItems.
+ Q_ASSERT(m_bookmarkedItems.count() == count());
for (int i = count() - 1; i >= 0; --i) {
- PlacesItem* visibleItem = placesItem(i);
- if (visibleItem->isHidden()) {
- PlacesItem* hiddenItem = new PlacesItem(*visibleItem);
- removeItem(i);
- m_hiddenItems.insert(i, hiddenItem);
+ if (placesItem(i)->isHidden()) {
+ hideItem(i);
}
}
}
+
#ifdef PLACESITEMMODEL_DEBUG
kDebug() << "Changed visibility of hidden items";
showModelState();
return foundIndex;
}
-QString PlacesItemModel::groupName(const KUrl &url) const
+void PlacesItemModel::appendItemToGroup(PlacesItem* item)
{
- const QString protocol = url.protocol();
+ if (!item) {
+ return;
+ }
- if (protocol.contains(QLatin1String("search"))) {
- return searchForGroupName();
+ int i = 0;
+ while (i < count() && placesItem(i)->group() != item->group()) {
+ ++i;
}
- if (protocol == QLatin1String("timeline")) {
- return recentlyAccessedGroupName();
+ bool inserted = false;
+ while (!inserted && i < count()) {
+ if (placesItem(i)->group() != item->group()) {
+ insertItem(i, item);
+ inserted = true;
+ }
+ ++i;
}
- return placesGroupName();
+ if (!inserted) {
+ appendItem(item);
+ }
}
+
QAction* PlacesItemModel::ejectAction(int index) const
{
const PlacesItem* item = placesItem(index);
this, SLOT(slotStorageTeardownDone(Solid::ErrorType,QVariant)));
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);
}
}
}
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);
}
}
}
+bool PlacesItemModel::storageSetupNeeded(int index) const
+{
+ const PlacesItem* item = placesItem(index);
+ return item ? item->storageSetupNeeded() : false;
+}
+
+void PlacesItemModel::requestStorageSetup(int index)
+{
+ 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, SIGNAL(setupDone(Solid::ErrorType,QVariant,QString)),
+ this, SLOT(slotStorageSetupDone(Solid::ErrorType,QVariant,QString)));
+
+ access->setup();
+ }
+}
+
+QMimeData* PlacesItemModel::createMimeData(const QSet<int>& indexes) const
+{
+ KUrl::List urls;
+ QByteArray itemData;
+
+ QDataStream stream(&itemData, QIODevice::WriteOnly);
+
+ foreach (int index, indexes) {
+ const KUrl itemUrl = placesItem(index)->url();
+ if (itemUrl.isValid()) {
+ urls << itemUrl;
+ }
+ stream << index;
+ }
+
+ QMimeData* mimeData = new QMimeData();
+ if (!urls.isEmpty()) {
+ urls.populateMimeData(mimeData);
+ }
+ mimeData->setData(internalMimeType(), itemData);
+
+ return mimeData;
+}
+
+bool PlacesItemModel::supportsDropping(int index) const
+{
+ return index >= 0 && index < count();
+}
+
+void PlacesItemModel::dropMimeDataBefore(int index, const QMimeData* mimeData)
+{
+ 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;
+ if (oldIndex == index || oldIndex == index - 1) {
+ // No moving has been done
+ return;
+ }
+
+ PlacesItem* oldItem = placesItem(oldIndex);
+ if (!oldItem) {
+ return;
+ }
+
+ PlacesItem* newItem = new PlacesItem(oldItem->bookmark());
+ removeItem(oldIndex);
+
+ if (oldIndex < index) {
+ --index;
+ }
+
+ const int dropIndex = groupedDropIndex(index, newItem);
+ insertItem(dropIndex, newItem);
+ } else if (mimeData->hasFormat("text/uri-list")) {
+ // One or more items must be added to the model
+ const KUrl::List urls = KUrl::List::fromMimeData(mimeData);
+ for (int i = urls.count() - 1; i >= 0; --i) {
+ const KUrl& url = urls[i];
+
+ QString text = url.fileName();
+ if (text.isEmpty()) {
+ text = url.host();
+ }
+
+ PlacesItem* newItem = createPlacesItem(text, url);
+ const int dropIndex = groupedDropIndex(index, newItem);
+ insertItem(dropIndex, newItem);
+ }
+ }
+}
+
+KUrl PlacesItemModel::convertedUrl(const KUrl& url)
+{
+ KUrl newUrl = url;
+ if (url.protocol() == QLatin1String("timeline")) {
+ newUrl = createTimelineUrl(url);
+ } else if (url.protocol() == QLatin1String("search")) {
+ newUrl = createSearchUrl(url);
+ }
+
+ return newUrl;
+}
+
void PlacesItemModel::onItemInserted(int index)
{
+ 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;
- }
+ m_bookmarkedItems.append(0);
+ } else {
- int modelIndex = -1;
- int hiddenIndex = 0;
- while (hiddenIndex < m_hiddenItems.count()) {
- if (!m_hiddenItems[hiddenIndex]) {
- ++modelIndex;
- if (modelIndex + 1 == index) {
- break;
+ int modelIndex = -1;
+ int bookmarkIndex = 0;
+ while (bookmarkIndex < m_bookmarkedItems.count()) {
+ if (!m_bookmarkedItems[bookmarkIndex]) {
+ ++modelIndex;
+ if (modelIndex + 1 == index) {
+ break;
+ }
}
+ ++bookmarkIndex;
}
- ++hiddenIndex;
+ m_bookmarkedItems.insert(bookmarkIndex, 0);
}
- m_hiddenItems.insert(hiddenIndex, 0);
- m_saveBookmarksTimer->start();
+ triggerBookmarksSaving();
#ifdef PLACESITEMMODEL_DEBUG
kDebug() << "Inserted item" << index;
#endif
}
-void PlacesItemModel::onItemRemoved(int index)
+void PlacesItemModel::onItemRemoved(int index, KStandardItem* removedItem)
{
- const int removeIndex = hiddenIndex(index);
- Q_ASSERT(!m_hiddenItems[removeIndex]);
- m_hiddenItems.removeAt(removeIndex);
+ PlacesItem* placesItem = dynamic_cast<PlacesItem*>(removedItem);
+ if (placesItem) {
+ const KBookmark bookmark = placesItem->bookmark();
+ m_bookmarkManager->root().deleteBookmark(bookmark);
+ }
- m_saveBookmarksTimer->start();
+ const int boomarkIndex = bookmarkIndex(index);
+ Q_ASSERT(!m_bookmarkedItems[boomarkIndex]);
+ m_bookmarkedItems.removeAt(boomarkIndex);
+
+ triggerBookmarksSaving();
#ifdef PLACESITEMMODEL_DEBUG
kDebug() << "Removed item" << index;
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 changed 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()) {
+ if (!m_hiddenItemsShown && changedItem->isHidden()) {
m_hiddenItemToRemove = index;
- QTimer::singleShot(0, this, SLOT(removeHiddenItem()));
+ QTimer::singleShot(0, this, SLOT(hideItem()));
}
}
- m_saveBookmarksTimer->start();
+
+ triggerBookmarksSaving();
}
void PlacesItemModel::slotDeviceAdded(const QString& 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));
+
+ if (!m_predicate.matches(device)) {
+ return;
}
+
+ m_availableDevices << udi;
+ const KBookmark bookmark = PlacesItem::createDeviceBookmark(m_bookmarkManager, udi);
+ appendItem(new PlacesItem(bookmark));
}
void PlacesItemModel::slotDeviceRemoved(const QString& udi)
return;
}
- for (int i = 0; i < m_hiddenItems.count(); ++i) {
- PlacesItem* item = m_hiddenItems[i];
+ for (int i = 0; i < m_bookmarkedItems.count(); ++i) {
+ PlacesItem* item = m_bookmarkedItems[i];
if (item && item->udi() == udi) {
- m_hiddenItems.removeAt(i);
+ m_bookmarkedItems.removeAt(i);
delete item;
return;
}
- }
+ }
- for (int i = 0; i < count(); ++i) {
- if (placesItem(i)->udi() == udi) {
- removeItem(i);
- 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)
}
}
-void PlacesItemModel::removeHiddenItem()
+void PlacesItemModel::slotStorageSetupDone(Solid::ErrorType error,
+ const QVariant& errorData,
+ const QString& udi)
{
- 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();
+ Q_UNUSED(udi);
+
+ const int index = m_storageSetupInProgress.take(sender());
+ const PlacesItem* item = placesItem(index);
+ if (!item) {
+ return;
+ }
+
+ if (error) {
+ // TODO: Request message-freeze exception
+ if (errorData.isValid()) {
+ // emit errorMessage(i18nc("@info", "An error occurred while accessing '%1', the system responded: %2",
+ // item->text(),
+ // errorData.toString()));
+ emit errorMessage(QString("An error occurred while accessing '%1', the system responded: %2")
+ .arg(item->text()).arg(errorData.toString()));
+ } else {
+ // emit errorMessage(i18nc("@info", "An error occurred while accessing '%1'",
+ // item->text()));
+ emit errorMessage(QString("An error occurred while accessing '%1'").arg(item->text()));
+ }
+ emit storageSetupDone(index, false);
+ } else {
+ emit storageSetupDone(index, true);
}
+}
+
+void PlacesItemModel::hideItem()
+{
+ hideItem(m_hiddenItemToRemove);
m_hiddenItemToRemove = -1;
}
+void PlacesItemModel::updateBookmarks()
+{
+ // Verify whether new bookmarks have been added or existing
+ // bookmarks have been changed.
+ KBookmarkGroup root = m_bookmarkManager->root();
+ KBookmark newBookmark = root.first();
+ while (!newBookmark.isNull()) {
+ if (acceptBookmark(newBookmark, m_availableDevices)) {
+ bool found = false;
+ int modelIndex = 0;
+ for (int i = 0; i < m_bookmarkedItems.count(); ++i) {
+ PlacesItem* item = m_bookmarkedItems[i];
+ if (!item) {
+ item = placesItem(modelIndex);
+ ++modelIndex;
+ }
+
+ const KBookmark oldBookmark = item->bookmark();
+ if (equalBookmarkIdentifiers(newBookmark, oldBookmark)) {
+ // The bookmark has been found in the model or as
+ // a hidden item. The content of the bookmark might
+ // have been changed, so an update is done.
+ found = true;
+ if (newBookmark.metaDataItem("UDI").isEmpty()) {
+ item->setBookmark(newBookmark);
+ }
+ break;
+ }
+ }
+
+ if (!found) {
+ const QString udi = newBookmark.metaDataItem("UDI");
+
+ /*
+ * See Bug 304878
+ * Only add a new places item, if the item text is not empty
+ * and if the device is available. Fixes the strange behaviour -
+ * add a places item without text in the Places section - when you
+ * remove a device (e.g. a usb stick) without unmounting.
+ */
+ if (udi.isEmpty() || Solid::Device(udi).isValid()) {
+ PlacesItem* item = new PlacesItem(newBookmark);
+ if (item->isHidden() && !m_hiddenItemsShown) {
+ m_bookmarkedItems.append(item);
+ } else {
+ appendItemToGroup(item);
+ }
+ }
+ }
+ }
+
+ newBookmark = root.next(newBookmark);
+ }
+
+ // Remove items that are not part of the bookmark-manager anymore
+ int modelIndex = 0;
+ for (int i = m_bookmarkedItems.count() - 1; i >= 0; --i) {
+ PlacesItem* item = m_bookmarkedItems[i];
+ const bool itemIsPartOfModel = (item == 0);
+ if (itemIsPartOfModel) {
+ item = placesItem(modelIndex);
+ }
+
+ bool hasBeenRemoved = true;
+ const KBookmark oldBookmark = item->bookmark();
+ KBookmark newBookmark = root.first();
+ while (!newBookmark.isNull()) {
+ if (equalBookmarkIdentifiers(newBookmark, oldBookmark)) {
+ hasBeenRemoved = false;
+ break;
+ }
+ newBookmark = root.next(newBookmark);
+ }
+
+ if (hasBeenRemoved) {
+ if (m_bookmarkedItems[i]) {
+ delete m_bookmarkedItems[i];
+ m_bookmarkedItems.removeAt(i);
+ } else {
+ removeItem(modelIndex);
+ --modelIndex;
+ }
+ }
+
+ if (itemIsPartOfModel) {
+ ++modelIndex;
+ }
+ }
+}
void PlacesItemModel::saveBookmarks()
{
- // TODO: Temporary deactivated until 100 % backward compatibility is provided
- // m_bookmarkManager->emitChanged(m_bookmarkManager->root());
+ m_bookmarkManager->emitChanged(m_bookmarkManager->root());
}
void PlacesItemModel::loadBookmarks()
missingSystemBookmarks.insert(data.url);
}
- // 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, so they are collected as separate lists.
+ // The bookmarks might have a mixed order of places, devices and search-groups due
+ // to the compatibility with the KFilePlacesPanel. In Dolphin's places panel the
+ // items should always be collected in one group so the items are collected first
+ // in separate lists before inserting them.
QList<PlacesItem*> placesItems;
+ QList<PlacesItem*> recentlyAccessedItems;
+ QList<PlacesItem*> searchForItems;
QList<PlacesItem*> devicesItems;
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") &&
- url.protocol() != QLatin1String("search")));
-
- if ((udi.isEmpty() && allowedHere) || deviceAvailable) {
+ if (acceptBookmark(bookmark, devices)) {
PlacesItem* item = new PlacesItem(bookmark);
- if (deviceAvailable) {
+ if (item->groupType() == PlacesItem::DevicesType) {
+ devices.remove(item->udi());
devicesItems.append(item);
} else {
- placesItems.append(item);
-
+ const KUrl url = bookmark.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);
+ // Try to retranslate the text of system bookmarks to have translated
+ // items when changing the language. In case if the user has applied a custom
+ // text, the retranslation will fail and the users custom text is still used.
+ // It is important to use "KFile System Bookmarks" as context (see
+ // createSystemBookmarks()).
+ item->setText(i18nc("KFile System Bookmarks", bookmark.text().toUtf8().data()));
item->setSystemItem(true);
- item->setGroup(m_systemBookmarks[index].group);
+ }
+
+ switch (item->groupType()) {
+ case PlacesItem::PlacesType: placesItems.append(item); break;
+ case PlacesItem::RecentlyAccessedType: recentlyAccessedItems.append(item); break;
+ case PlacesItem::SearchForType: searchForItems.append(item); break;
+ case PlacesItem::DevicesType:
+ default: Q_ASSERT(false); break;
}
}
}
bookmark = root.next(bookmark);
}
- 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)) {
- 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 = createSystemPlacesItem(data);
+ switch (item->groupType()) {
+ case PlacesItem::PlacesType: placesItems.append(item); break;
+ case PlacesItem::RecentlyAccessedType: recentlyAccessedItems.append(item); break;
+ case PlacesItem::SearchForType: searchForItems.append(item); break;
+ case PlacesItem::DevicesType:
+ default: Q_ASSERT(false); break;
}
-
- PlacesItem* item = new PlacesItem(bookmark);
- item->setSystemItem(true);
- item->setGroup(data.group);
- appendItem(item);
}
}
}
- // Create items for devices that have not stored as bookmark yet
+ // Create items for devices that have not been stored as bookmark yet
foreach (const QString& udi, devices) {
const KBookmark bookmark = PlacesItem::createDeviceBookmark(m_bookmarkManager, udi);
devicesItems.append(new PlacesItem(bookmark));
}
- addItems(devicesItems);
+ QList<PlacesItem*> items;
+ items.append(placesItems);
+ items.append(recentlyAccessedItems);
+ items.append(searchForItems);
+ items.append(devicesItems);
+
+ foreach (PlacesItem* item, items) {
+ if (!m_hiddenItemsShown && item->isHidden()) {
+ m_bookmarkedItems.append(item);
+ } else {
+ appendItem(item);
+ }
+ }
#ifdef PLACESITEMMODEL_DEBUG
kDebug() << "Loaded bookmarks";
#endif
}
-void PlacesItemModel::addItems(const QList<PlacesItem*>& items)
+bool PlacesItemModel::acceptBookmark(const KBookmark& bookmark,
+ const QSet<QString>& availableDevices) const
{
- foreach (PlacesItem* item, items) {
- if (!m_hiddenItemsShown && item->isHidden()) {
- m_hiddenItems.append(item);
- } else {
- appendItem(item);
+ const QString udi = bookmark.metaDataItem("UDI");
+ const KUrl url = bookmark.url();
+ const QString appName = bookmark.metaDataItem("OnlyInApp");
+ const bool deviceAvailable = availableDevices.contains(udi);
+
+ const bool allowedHere = (appName.isEmpty()
+ || appName == KGlobal::mainComponent().componentName()
+ || appName == KGlobal::mainComponent().componentName() + AppNamePrefix)
+ && (m_fileIndexingEnabled || (url.protocol() != QLatin1String("timeline") &&
+ url.protocol() != QLatin1String("search")));
+
+ return (udi.isEmpty() && allowedHere) || deviceAvailable;
+}
+
+PlacesItem* PlacesItemModel::createSystemPlacesItem(const SystemBookmarkData& data)
+{
+ 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 "Recently Accessed" and "Search For" should be a setting available only
+ // in the Places Panel (see description of AppNamePrefix for more details).
+ const QString appName = KGlobal::mainComponent().componentName() + AppNamePrefix;
+ bookmark.setMetaDataItem("OnlyInApp", appName);
+ }
+
+ PlacesItem* item = new PlacesItem(bookmark);
+ item->setSystemItem(true);
+
+ // Create default view-properties for all "Search For" and "Recently Accessed" bookmarks
+ // in case if the user has not already created custom view-properties for a corresponding
+ // query yet.
+ const bool createDefaultViewProperties = (item->groupType() == PlacesItem::SearchForType ||
+ item->groupType() == PlacesItem::RecentlyAccessedType) &&
+ !GeneralSettings::self()->globalViewProps();
+ if (createDefaultViewProperties) {
+ ViewProperties props(convertedUrl(data.url));
+ if (!props.exist()) {
+ const QString path = data.url.path();
+ if (path == QLatin1String("/documents")) {
+ props.setViewMode(DolphinView::DetailsView);
+ props.setPreviewsShown(false);
+ props.setVisibleRoles(QList<QByteArray>() << "text" << "path");
+ } else if (path == QLatin1String("/images")) {
+ props.setViewMode(DolphinView::IconsView);
+ props.setPreviewsShown(true);
+ props.setVisibleRoles(QList<QByteArray>() << "text" << "imageSize");
+ } else if (path == QLatin1String("/audio")) {
+ props.setViewMode(DolphinView::DetailsView);
+ props.setPreviewsShown(false);
+ props.setVisibleRoles(QList<QByteArray>() << "text" << "artist" << "album");
+ } else if (path == QLatin1String("/videos")) {
+ props.setViewMode(DolphinView::IconsView);
+ props.setPreviewsShown(true);
+ props.setVisibleRoles(QList<QByteArray>() << "text");
+ } else if (data.url.protocol() == "timeline") {
+ props.setViewMode(DolphinView::DetailsView);
+ props.setVisibleRoles(QList<QByteArray>() << "text" << "date");
+ }
}
}
+
+ return item;
}
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
-
+ // Note: The context of the I18N_NOOP2 must be "KFile System Bookmarks". The real
+ // i18nc call is done after reading the bookmark. The reason why the i18nc call is not
+ // done here is because otherwise switching the language would not result in retranslating the
+ // bookmarks.
m_systemBookmarks.append(SystemBookmarkData(KUrl(KUser().homeDir()),
"user-home",
- i18nc("@item", "Home"),
- placesGroup));
+ I18N_NOOP2("KFile System Bookmarks", "Home")));
m_systemBookmarks.append(SystemBookmarkData(KUrl("remote:/"),
"network-workgroup",
- i18nc("@item", "Network"),
- placesGroup));
+ I18N_NOOP2("KFile System Bookmarks", "Network")));
m_systemBookmarks.append(SystemBookmarkData(KUrl("/"),
"folder-red",
- i18nc("@item", "Root"),
- placesGroup));
+ I18N_NOOP2("KFile System Bookmarks", "Root")));
m_systemBookmarks.append(SystemBookmarkData(KUrl("trash:/"),
"user-trash",
- i18nc("@item", "Trash"),
- placesGroup));
+ I18N_NOOP2("KFile System Bookmarks", "Trash")));
- if (m_nepomukRunning) {
+ if (m_fileIndexingEnabled) {
m_systemBookmarks.append(SystemBookmarkData(KUrl("timeline:/today"),
- timeLineIcon,
- i18nc("@item Recently Accessed", "Today"),
- recentlyAccessedGroup));
+ "go-jump-today",
+ I18N_NOOP2("KFile System Bookmarks", "Today")));
m_systemBookmarks.append(SystemBookmarkData(KUrl("timeline:/yesterday"),
- timeLineIcon,
- i18nc("@item Recently Accessed", "Yesterday"),
- recentlyAccessedGroup));
+ "view-calendar-day",
+ I18N_NOOP2("KFile System Bookmarks", "Yesterday")));
m_systemBookmarks.append(SystemBookmarkData(KUrl("timeline:/thismonth"),
- timeLineIcon,
- i18nc("@item Recently Accessed", "This Month"),
- recentlyAccessedGroup));
+ "view-calendar-month",
+ I18N_NOOP2("KFile System Bookmarks", "This Month")));
m_systemBookmarks.append(SystemBookmarkData(KUrl("timeline:/lastmonth"),
- timeLineIcon,
- i18nc("@item Recently Accessed", "Last Month"),
- recentlyAccessedGroup));
+ "view-calendar-month",
+ I18N_NOOP2("KFile System Bookmarks", "Last Month")));
m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/documents"),
"folder-txt",
- i18nc("@item Commonly Accessed", "Documents"),
- searchForGroup));
+ I18N_NOOP2("KFile System Bookmarks", "Documents")));
m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/images"),
"folder-image",
- i18nc("@item Commonly Accessed", "Images"),
- searchForGroup));
+ I18N_NOOP2("KFile System Bookmarks", "Images")));
m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/audio"),
"folder-sound",
- i18nc("@item Commonly Accessed", "Audio"),
- searchForGroup));
+ I18N_NOOP2("KFile System Bookmarks", "Audio Files")));
m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/videos"),
"folder-video",
- i18nc("@item Commonly Accessed", "Videos"),
- searchForGroup));
+ I18N_NOOP2("KFile System Bookmarks", "Videos")));
}
for (int i = 0; i < m_systemBookmarks.count(); ++i) {
}
}
+void PlacesItemModel::clear() {
+ m_bookmarkedItems.clear();
+ KStandardItemModel::clear();
+}
+
+void PlacesItemModel::slotNepomukStarted()
+{
+ KConfig config("nepomukserverrc");
+ m_fileIndexingEnabled = config.group("Service-nepomukfileindexer").readEntry("autostart", true);
+ if (m_fileIndexingEnabled) {
+ m_systemBookmarks.clear();
+ m_systemBookmarksIndexes.clear();
+ createSystemBookmarks();
+
+ clear();
+ loadBookmarks();
+ }
+}
+
+void PlacesItemModel::slotNepomukStopped()
+{
+ if (m_fileIndexingEnabled) {
+ m_fileIndexingEnabled = false;
+
+ m_systemBookmarks.clear();
+ m_systemBookmarksIndexes.clear();
+ createSystemBookmarks();
+
+ clear();
+ loadBookmarks();
+ }
+}
+
+
void PlacesItemModel::initializeAvailableDevices()
{
- m_predicate = Solid::Predicate::fromString(
- "[[[[ StorageVolume.ignored == false AND [ StorageVolume.usage == 'FileSystem' OR StorageVolume.usage == 'Encrypted' ]]"
+ QString predicate("[[[[ StorageVolume.ignored == false AND [ StorageVolume.usage == 'FileSystem' OR StorageVolume.usage == 'Encrypted' ]]"
" OR "
"[ IS StorageAccess AND StorageDrive.driveType == 'Floppy' ]]"
" OR "
"OpticalDisc.availableContent & 'Audio' ]"
" OR "
"StorageAccess.ignored == false ]");
+
+
+ if (KProtocolInfo::isKnownProtocol("mtp")) {
+ predicate.prepend("[");
+ predicate.append(" OR PortableMediaPlayer.supportedProtocols == 'mtp']");
+ }
+
+ m_predicate = Solid::Predicate::fromString(predicate);
Q_ASSERT(m_predicate.isValid());
Solid::DeviceNotifier* notifier = Solid::DeviceNotifier::instance();
connect(notifier, SIGNAL(deviceRemoved(QString)), this, SLOT(slotDeviceRemoved(QString)));
const QList<Solid::Device>& deviceList = Solid::Device::listFromQuery(m_predicate);
- foreach(const Solid::Device& device, deviceList) {
+ foreach (const Solid::Device& device, deviceList) {
m_availableDevices << device.udi();
}
}
-int PlacesItemModel::hiddenIndex(int index) const
+int PlacesItemModel::bookmarkIndex(int index) const
{
- int hiddenIndex = 0;
- int visibleItemIndex = 0;
- while (hiddenIndex < m_hiddenItems.count()) {
- if (!m_hiddenItems[hiddenIndex]) {
- if (visibleItemIndex == index) {
+ int bookmarkIndex = 0;
+ int modelIndex = 0;
+ while (bookmarkIndex < m_bookmarkedItems.count()) {
+ if (!m_bookmarkedItems[bookmarkIndex]) {
+ if (modelIndex == index) {
break;
}
- ++visibleItemIndex;
+ ++modelIndex;
}
- ++hiddenIndex;
+ ++bookmarkIndex;
}
- return hiddenIndex >= m_hiddenItems.count() ? -1 : hiddenIndex;
+ return bookmarkIndex >= m_bookmarkedItems.count() ? -1 : bookmarkIndex;
}
-QString PlacesItemModel::placesGroupName()
+void PlacesItemModel::hideItem(int index)
{
- return i18nc("@item", "Places");
+ PlacesItem* shownItem = placesItem(index);
+ if (!shownItem) {
+ return;
+ }
+
+ shownItem->setHidden(true);
+ if (m_hiddenItemsShown) {
+ // Removing items from the model is not allowed if all hidden
+ // items should be shown.
+ return;
+ }
+
+ const int newIndex = bookmarkIndex(index);
+ if (newIndex >= 0) {
+ const KBookmark hiddenBookmark = shownItem->bookmark();
+ PlacesItem* hiddenItem = new PlacesItem(hiddenBookmark);
+
+ const PlacesItem* previousItem = placesItem(index - 1);
+ KBookmark previousBookmark;
+ if (previousItem) {
+ previousBookmark = previousItem->bookmark();
+ }
+
+ const bool updateBookmark = (m_bookmarkManager->root().indexOf(hiddenBookmark) >= 0);
+ removeItem(index);
+
+ if (updateBookmark) {
+ // removeItem() also removed the bookmark from m_bookmarkManager in
+ // PlacesItemModel::onItemRemoved(). However for hidden items the
+ // bookmark should still be remembered, so readd it again:
+ m_bookmarkManager->root().addBookmark(hiddenBookmark);
+ m_bookmarkManager->root().moveBookmark(hiddenBookmark, previousBookmark);
+ triggerBookmarksSaving();
+ }
+
+ m_bookmarkedItems.insert(newIndex, hiddenItem);
+ }
+}
+
+void PlacesItemModel::triggerBookmarksSaving()
+{
+ if (m_saveBookmarksTimer) {
+ m_saveBookmarksTimer->start();
+ }
+}
+
+QString PlacesItemModel::internalMimeType() const
+{
+ return "application/x-dolphinplacesmodel-" +
+ QString::number((qptrdiff)this);
+}
+
+int PlacesItemModel::groupedDropIndex(int index, const PlacesItem* item) const
+{
+ Q_ASSERT(item);
+
+ int dropIndex = index;
+ const PlacesItem::GroupType type = item->groupType();
+
+ 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)->groupType() == type) {
+ previousIndex = i;
+ break;
+ }
+ }
+
+ // Search nearest next item with the same group
+ int nextIndex = -1;
+ for (int i = dropIndex; i < count(); ++i) {
+ if (placesItem(i)->groupType() == type) {
+ nextIndex = i;
+ break;
+ }
+ }
+
+ // 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::recentlyAccessedGroupName()
+bool PlacesItemModel::equalBookmarkIdentifiers(const KBookmark& b1, const KBookmark& b2)
{
- return i18nc("@item", "Recently Accessed");
+ const QString udi1 = b1.metaDataItem("UDI");
+ const QString udi2 = b2.metaDataItem("UDI");
+ if (!udi1.isEmpty() && !udi2.isEmpty()) {
+ return udi1 == udi2;
+ } else {
+ return b1.metaDataItem("ID") == b2.metaDataItem("ID");
+ }
}
-QString PlacesItemModel::searchForGroupName()
+KUrl PlacesItemModel::createTimelineUrl(const KUrl& url)
{
- return i18nc("@item", "Search For");
+ // 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(QLatin1String("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(QLatin1String("thismonth"))) {
+ const QDate date = QDate::currentDate();
+ timelineUrl = "timeline:/" + timelineDateString(date.year(), date.month());
+ } else if (path.endsWith(QLatin1String("lastmonth"))) {
+ const QDate date = QDate::currentDate().addMonths(-1);
+ timelineUrl = "timeline:/" + timelineDateString(date.year(), date.month());
+ } else {
+ Q_ASSERT(path.endsWith(QLatin1String("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(QLatin1String("documents"))) {
+ searchUrl = searchUrlForTerm(Nepomuk2::Query::ResourceTypeTerm(Nepomuk2::Vocabulary::NFO::Document()));
+ } else if (path.endsWith(QLatin1String("images"))) {
+ searchUrl = searchUrlForTerm(Nepomuk2::Query::ResourceTypeTerm(Nepomuk2::Vocabulary::NFO::Image()));
+ } else if (path.endsWith(QLatin1String("audio"))) {
+ searchUrl = searchUrlForTerm(Nepomuk2::Query::ComparisonTerm(Nepomuk2::Vocabulary::NIE::mimeType(),
+ Nepomuk2::Query::LiteralTerm("audio")));
+ } else if (path.endsWith(QLatin1String("videos"))) {
+ searchUrl = searchUrlForTerm(Nepomuk2::Query::ComparisonTerm(Nepomuk2::Vocabulary::NIE::mimeType(),
+ Nepomuk2::Query::LiteralTerm("video")));
+ } else {
+ Q_ASSERT(false);
+ }
+#else
+ Q_UNUSED(url);
+#endif
+
+ return searchUrl;
+}
+
+#ifdef HAVE_NEPOMUK
+KUrl PlacesItemModel::searchUrlForTerm(const Nepomuk2::Query::Term& term)
+{
+ const Nepomuk2::Query::FileQuery query(term);
+ return query.toSearchUrl();
+}
+#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();
+ kDebug() << "=================================";
+ kDebug() << "Model:";
+ kDebug() << "hidden-index model-index text";
+ int modelIndex = 0;
+ for (int i = 0; i < m_bookmarkedItems.count(); ++i) {
+ if (m_bookmarkedItems[i]) {
+ kDebug() << i << "(Hidden) " << " " << m_bookmarkedItems[i]->dataValue("text").toString();
} else {
- if (item(j)) {
- kDebug() << i << " " << j << " " << item(j)->dataValue("text").toString();
+ if (item(modelIndex)) {
+ kDebug() << i << " " << modelIndex << " " << item(modelIndex)->dataValue("text").toString();
} else {
- kDebug() << i << " " << j << " " << "(not available yet)";
+ kDebug() << i << " " << modelIndex << " " << "(not available yet)";
}
- ++j;
+ ++modelIndex;
+ }
+ }
+
+ kDebug();
+ kDebug() << "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")) {
+ kDebug() << bookmarkIndex << "(Hidden)" << text;
+ } else {
+ kDebug() << bookmarkIndex << " " << text;
}
+
+ bookmark = root.next(bookmark);
+ ++bookmarkIndex;
}
}
#endif