#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),
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());
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));
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) {
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;
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;
}
}
}
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()
// 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;
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 {
// 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);
}
}
}
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";
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);
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",
}
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);
}
}
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()
{
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;
}
}