]> cloud.milkyroute.net Git - dolphin.git/blob - src/panels/places/placesitemmodel.cpp
Create items for devices that have not been added as bookmarks yet
[dolphin.git] / src / panels / places / placesitemmodel.cpp
1 /***************************************************************************
2 * Copyright (C) 2012 by Peter Penz <peter.penz19@gmail.com> *
3 * *
4 * Based on KFilePlacesModel from kdelibs: *
5 * Copyright (C) 2007 Kevin Ottens <ervin@kde.org> *
6 * Copyright (C) 2007 David Faure <faure@kde.org> *
7 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program; if not, write to the *
20 * Free Software Foundation, Inc., *
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
22 ***************************************************************************/
23
24 #include "placesitemmodel.h"
25
26 #ifdef HAVE_NEPOMUK
27 #include <Nepomuk/ResourceManager>
28 #include <Nepomuk/Query/ComparisonTerm>
29 #include <Nepomuk/Query/LiteralTerm>
30 #include <Nepomuk/Query/Query>
31 #include <Nepomuk/Query/ResourceTypeTerm>
32 #include <Nepomuk/Vocabulary/NFO>
33 #include <Nepomuk/Vocabulary/NIE>
34 #endif
35
36 #include <KBookmark>
37 #include <KBookmarkGroup>
38 #include <KBookmarkManager>
39 #include <KComponentData>
40 #include <KDebug>
41 #include <KIcon>
42 #include <KLocale>
43 #include <KStandardDirs>
44 #include <KUser>
45 #include "placesitem.h"
46 #include <QAction>
47 #include <QDate>
48
49 #include <Solid/Device>
50 #include <Solid/DeviceNotifier>
51
52 PlacesItemModel::PlacesItemModel(QObject* parent) :
53 KStandardItemModel(parent),
54 m_nepomukRunning(false),
55 m_hiddenItemsShown(false),
56 m_availableDevices(),
57 m_predicate(),
58 m_bookmarkManager(0),
59 m_systemBookmarks(),
60 m_systemBookmarksIndexes(),
61 m_hiddenItems()
62 {
63 #ifdef HAVE_NEPOMUK
64 m_nepomukRunning = (Nepomuk::ResourceManager::instance()->initialized());
65 #endif
66 const QString file = KStandardDirs::locateLocal("data", "kfileplaces/bookmarks.xml");
67 m_bookmarkManager = KBookmarkManager::managerForFile(file, "kfilePlaces");
68
69 createSystemBookmarks();
70 initializeAvailableDevices();
71 loadBookmarks();
72 }
73
74 PlacesItemModel::~PlacesItemModel()
75 {
76 qDeleteAll(m_hiddenItems);
77 m_hiddenItems.clear();
78 }
79
80 PlacesItem* PlacesItemModel::placesItem(int index) const
81 {
82 return dynamic_cast<PlacesItem*>(item(index));
83 }
84
85 int PlacesItemModel::hiddenCount() const
86 {
87 int modelIndex = 0;
88 int itemCount = 0;
89 foreach (const PlacesItem* hiddenItem, m_hiddenItems) {
90 if (hiddenItem) {
91 ++itemCount;
92 } else {
93 if (placesItem(modelIndex)->isHidden()) {
94 ++itemCount;
95 }
96 ++modelIndex;
97 }
98 }
99
100 return itemCount;
101 }
102
103 void PlacesItemModel::setItemHidden(int index, bool hide)
104 {
105 if (index >= 0 && index < count()) {
106 PlacesItem* shownItem = placesItem(index);
107 shownItem->setHidden(true);
108 if (!m_hiddenItemsShown && hide) {
109 const int newIndex = hiddenIndex(index);
110 PlacesItem* hiddenItem = new PlacesItem(*shownItem);
111 removeItem(index);
112 m_hiddenItems.insert(newIndex, hiddenItem);
113 }
114 #ifdef PLACESITEMMODEL_DEBUG
115 kDebug() << "Changed hide-state from" << index << "to" << hide;
116 showModelState();
117 #endif
118 }
119 }
120
121 bool PlacesItemModel::isItemHidden(int index) const
122 {
123 return (index >= 0 && index < count()) ? m_hiddenItems[index] != 0 : false;
124 }
125
126 void PlacesItemModel::setHiddenItemsShown(bool show)
127 {
128 if (m_hiddenItemsShown == show) {
129 return;
130 }
131
132 m_hiddenItemsShown = show;
133
134 if (show) {
135 // Move all items that are part of m_hiddenItems to the model.
136 int modelIndex = 0;
137 for (int hiddenIndex = 0; hiddenIndex < m_hiddenItems.count(); ++hiddenIndex) {
138 if (m_hiddenItems[hiddenIndex]) {
139 PlacesItem* visibleItem = new PlacesItem(*m_hiddenItems[hiddenIndex]);
140 delete m_hiddenItems[hiddenIndex];
141 m_hiddenItems.removeAt(hiddenIndex);
142 insertItem(modelIndex, visibleItem);
143 Q_ASSERT(!m_hiddenItems[hiddenIndex]);
144 }
145 ++modelIndex;
146 }
147 } else {
148 // Move all items of the model, where the "isHidden" property is true, to
149 // m_hiddenItems.
150 Q_ASSERT(m_hiddenItems.count() == count());
151 for (int i = count() - 1; i >= 0; --i) {
152 PlacesItem* visibleItem = placesItem(i);
153 if (visibleItem->isHidden()) {
154 PlacesItem* hiddenItem = new PlacesItem(*visibleItem);
155 removeItem(i);
156 m_hiddenItems.insert(i, hiddenItem);
157 }
158 }
159 }
160 #ifdef PLACESITEMMODEL_DEBUG
161 kDebug() << "Changed visibility of hidden items";
162 showModelState();
163 #endif
164 }
165
166 bool PlacesItemModel::hiddenItemsShown() const
167 {
168 return m_hiddenItemsShown;
169 }
170
171 bool PlacesItemModel::isSystemItem(int index) const
172 {
173 if (index >= 0 && index < count()) {
174 const KUrl url = placesItem(index)->url();
175 return m_systemBookmarksIndexes.contains(url);
176 }
177 return false;
178 }
179
180 int PlacesItemModel::closestItem(const KUrl& url) const
181 {
182 int foundIndex = -1;
183 int maxLength = 0;
184
185 for (int i = 0; i < count(); ++i) {
186 const KUrl itemUrl = placesItem(i)->url();
187 if (itemUrl.isParentOf(url)) {
188 const int length = itemUrl.prettyUrl().length();
189 if (length > maxLength) {
190 foundIndex = i;
191 maxLength = length;
192 }
193 }
194 }
195
196 return foundIndex;
197 }
198
199 QString PlacesItemModel::groupName(const KUrl &url) const
200 {
201 const QString protocol = url.protocol();
202
203 if (protocol.contains(QLatin1String("search"))) {
204 return searchForGroupName();
205 }
206
207 if (protocol == QLatin1String("timeline")) {
208 return recentlyAccessedGroupName();
209 }
210
211 return placesGroupName();
212 }
213
214 QAction* PlacesItemModel::ejectAction(int index) const
215 {
216 const PlacesItem* item = placesItem(index);
217 if (item && item->device().is<Solid::OpticalDisc>()) {
218 return new QAction(KIcon("media-eject"), i18nc("@item", "Eject '%1'", item->text()), 0);
219 }
220
221 return 0;
222 }
223
224 QAction* PlacesItemModel::tearDownAction(int index) const
225 {
226 // TODO: This is a dummy-implementation to have at least all
227 // translation-strings as part of the code before the freeze
228 QString iconName;
229 QString text;
230 QString label;
231 switch (index) {
232 case 0:
233 text = i18nc("@item", "Release '%1'", label);
234 break;
235 case 1:
236 text = i18nc("@item", "Safely Remove '%1'", label);
237 iconName = "media-eject";
238 break;
239 case 2:
240 text = i18nc("@item", "Unmount '%1'", label);
241 iconName = "media-eject";
242 break;
243 default:
244 break;
245 }
246
247 //return new QAction(KIcon(iconName), text, 0);
248 return 0;
249 }
250
251 void PlacesItemModel::onItemInserted(int index)
252 {
253 int modelIndex = 0;
254 int hiddenIndex = 0;
255 while (hiddenIndex < m_hiddenItems.count()) {
256 if (!m_hiddenItems[hiddenIndex]) {
257 ++modelIndex;
258 if (modelIndex + 1 == index) {
259 ++hiddenIndex;
260 break;
261 }
262 }
263 ++hiddenIndex;
264 }
265 m_hiddenItems.insert(hiddenIndex, 0);
266
267 #ifdef PLACESITEMMODEL_DEBUG
268 kDebug() << "Inserted item" << index;
269 showModelState();
270 #endif
271 }
272
273 void PlacesItemModel::onItemRemoved(int index)
274 {
275 const int removeIndex = hiddenIndex(index);
276 Q_ASSERT(!m_hiddenItems[removeIndex]);
277 m_hiddenItems.removeAt(removeIndex);
278 #ifdef PLACESITEMMODEL_DEBUG
279 kDebug() << "Removed item" << index;
280 showModelState();
281 #endif
282 }
283
284 void PlacesItemModel::slotDeviceAdded(const QString& udi)
285 {
286 Q_UNUSED(udi);
287 }
288
289 void PlacesItemModel::slotDeviceRemoved(const QString& udi)
290 {
291 Q_UNUSED(udi);
292 }
293
294 void PlacesItemModel::loadBookmarks()
295 {
296 KBookmarkGroup root = m_bookmarkManager->root();
297 KBookmark bookmark = root.first();
298 QSet<QString> devices = m_availableDevices;
299
300 QSet<KUrl> missingSystemBookmarks;
301 foreach (const SystemBookmarkData& data, m_systemBookmarks) {
302 missingSystemBookmarks.insert(data.url);
303 }
304
305 // The bookmarks might have a mixed order of "places" and "devices". In
306 // Dolphin's places panel the devices should always be appended as last
307 // group.
308 QList<PlacesItem*> placesItems;
309 QList<PlacesItem*> devicesItems;
310
311 while (!bookmark.isNull()) {
312 const QString udi = bookmark.metaDataItem("UDI");
313 const KUrl url = bookmark.url();
314 const QString appName = bookmark.metaDataItem("OnlyInApp");
315 const bool deviceAvailable = devices.remove(udi);
316
317 const bool allowedHere = (appName.isEmpty() || appName == KGlobal::mainComponent().componentName())
318 && (m_nepomukRunning || url.protocol() != QLatin1String("timeline"));
319
320 if ((udi.isEmpty() && allowedHere) || deviceAvailable) {
321 PlacesItem* item = new PlacesItem(bookmark);
322 if (deviceAvailable) {
323 devicesItems.append(item);
324 } else {
325 placesItems.append(item);
326
327 if (missingSystemBookmarks.contains(url)) {
328 missingSystemBookmarks.remove(url);
329
330 // Apply the translated text to the system bookmarks, otherwise an outdated
331 // translation might be shown.
332 const int index = m_systemBookmarksIndexes.value(url);
333 item->setText(m_systemBookmarks[index].text);
334
335 // The system bookmarks don't contain "real" queries stored as URLs, so
336 // they must be translated first.
337 item->setUrl(translatedSystemBookmarkUrl(url));
338 }
339 }
340 }
341
342 bookmark = root.next(bookmark);
343 }
344
345 addItems(placesItems);
346
347 if (!missingSystemBookmarks.isEmpty()) {
348 foreach (const SystemBookmarkData& data, m_systemBookmarks) {
349 if (missingSystemBookmarks.contains(data.url)) {
350 PlacesItem* item = new PlacesItem();
351 item->setIcon(data.icon);
352 item->setText(data.text);
353 item->setUrl(translatedSystemBookmarkUrl(data.url));
354 item->setGroup(data.group);
355 appendItem(item);
356 }
357 }
358 }
359
360 // Create items for devices that have not stored as bookmark yet
361 foreach (const QString& udi, devices) {
362 PlacesItem* item = new PlacesItem(udi);
363 devicesItems.append(item);
364 }
365
366 addItems(devicesItems);
367
368 #ifdef PLACESITEMMODEL_DEBUG
369 kDebug() << "Loaded bookmarks";
370 showModelState();
371 #endif
372 }
373
374 void PlacesItemModel::addItems(const QList<PlacesItem*>& items)
375 {
376 foreach (PlacesItem* item, items) {
377 if (item->isHidden()) {
378 m_hiddenItems.append(item);
379 } else {
380 appendItem(item);
381 }
382 }
383 }
384
385 void PlacesItemModel::createSystemBookmarks()
386 {
387 Q_ASSERT(m_systemBookmarks.isEmpty());
388 Q_ASSERT(m_systemBookmarksIndexes.isEmpty());
389
390 const QString placesGroup = placesGroupName();
391 const QString recentlyAccessedGroup = recentlyAccessedGroupName();
392 const QString searchForGroup = searchForGroupName();
393 const QString timeLineIcon = "package_utility_time"; // TODO: Ask the Oxygen team to create
394 // a custom icon for the timeline-protocol
395
396 m_systemBookmarks.append(SystemBookmarkData(KUrl(KUser().homeDir()),
397 "user-home",
398 i18nc("@item", "Home"),
399 placesGroup));
400 m_systemBookmarks.append(SystemBookmarkData(KUrl("remote:/"),
401 "network-workgroup",
402 i18nc("@item", "Network"),
403 placesGroup));
404 m_systemBookmarks.append(SystemBookmarkData(KUrl("/"),
405 "folder-red",
406 i18nc("@item", "Root"),
407 placesGroup));
408 m_systemBookmarks.append(SystemBookmarkData(KUrl("trash:/"),
409 "user-trash",
410 i18nc("@item", "Trash"),
411 placesGroup));
412
413 if (m_nepomukRunning) {
414 m_systemBookmarks.append(SystemBookmarkData(KUrl("timeline:/today"),
415 timeLineIcon,
416 i18nc("@item Recently Accessed", "Today"),
417 recentlyAccessedGroup));
418 m_systemBookmarks.append(SystemBookmarkData(KUrl("timeline:/yesterday"),
419 timeLineIcon,
420 i18nc("@item Recently Accessed", "Yesterday"),
421 recentlyAccessedGroup));
422 m_systemBookmarks.append(SystemBookmarkData(KUrl("timeline:/thismonth"),
423 timeLineIcon,
424 i18nc("@item Recently Accessed", "This Month"),
425 recentlyAccessedGroup));
426 m_systemBookmarks.append(SystemBookmarkData(KUrl("timeline:/lastmonth"),
427 timeLineIcon,
428 i18nc("@item Recently Accessed", "Last Month"),
429 recentlyAccessedGroup));
430 m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/documents"),
431 "folder-txt",
432 i18nc("@item Commonly Accessed", "Documents"),
433 searchForGroup));
434 m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/images"),
435 "folder-image",
436 i18nc("@item Commonly Accessed", "Images"),
437 searchForGroup));
438 m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/audio"),
439 "folder-sound",
440 i18nc("@item Commonly Accessed", "Audio"),
441 searchForGroup));
442 m_systemBookmarks.append(SystemBookmarkData(KUrl("search:/videos"),
443 "folder-video",
444 i18nc("@item Commonly Accessed", "Videos"),
445 searchForGroup));
446 }
447
448 for (int i = 0; i < m_systemBookmarks.count(); ++i) {
449 const KUrl url = translatedSystemBookmarkUrl(m_systemBookmarks[i].url);
450 m_systemBookmarksIndexes.insert(url, i);
451 }
452 }
453
454 void PlacesItemModel::initializeAvailableDevices()
455 {
456 m_predicate = Solid::Predicate::fromString(
457 "[[[[ StorageVolume.ignored == false AND [ StorageVolume.usage == 'FileSystem' OR StorageVolume.usage == 'Encrypted' ]]"
458 " OR "
459 "[ IS StorageAccess AND StorageDrive.driveType == 'Floppy' ]]"
460 " OR "
461 "OpticalDisc.availableContent & 'Audio' ]"
462 " OR "
463 "StorageAccess.ignored == false ]");
464 Q_ASSERT(m_predicate.isValid());
465
466 Solid::DeviceNotifier* notifier = Solid::DeviceNotifier::instance();
467 connect(notifier, SIGNAL(deviceAdded(QString)), this, SLOT(slotDeviceAdded(QString)));
468 connect(notifier, SIGNAL(deviceRemoved(QString)), this, SLOT(slotDeviceRemoved(QString)));
469
470 const QList<Solid::Device>& deviceList = Solid::Device::listFromQuery(m_predicate);
471 foreach(const Solid::Device& device, deviceList) {
472 m_availableDevices << device.udi();
473 }
474 }
475
476 int PlacesItemModel::hiddenIndex(int index) const
477 {
478 int hiddenIndex = 0;
479 int visibleItemIndex = 0;
480 while (hiddenIndex < m_hiddenItems.count()) {
481 if (!m_hiddenItems[hiddenIndex]) {
482 if (visibleItemIndex == index) {
483 break;
484 }
485 ++visibleItemIndex;
486 }
487 ++hiddenIndex;
488 }
489
490 return hiddenIndex >= m_hiddenItems.count() ? -1 : hiddenIndex;
491 }
492
493 QString PlacesItemModel::placesGroupName()
494 {
495 return i18nc("@item", "Places");
496 }
497
498 QString PlacesItemModel::recentlyAccessedGroupName()
499 {
500 return i18nc("@item", "Recently Accessed");
501 }
502
503 QString PlacesItemModel::searchForGroupName()
504 {
505 return i18nc("@item", "Search For");
506 }
507
508 KUrl PlacesItemModel::translatedSystemBookmarkUrl(const KUrl& url)
509 {
510 KUrl translatedUrl = url;
511 if (url.protocol() == QLatin1String("timeline")) {
512 translatedUrl = createTimelineUrl(url);
513 } else if (url.protocol() == QLatin1String("search")) {
514 translatedUrl = createSearchUrl(url);
515 }
516
517 return translatedUrl;
518 }
519
520 KUrl PlacesItemModel::createTimelineUrl(const KUrl& url)
521 {
522 // TODO: Clarify with the Nepomuk-team whether it makes sense
523 // provide default-timeline-URLs like 'yesterday', 'this month'
524 // and 'last month'.
525 KUrl timelineUrl;
526
527 const QString path = url.pathOrUrl();
528 if (path.endsWith("yesterday")) {
529 const QDate date = QDate::currentDate().addDays(-1);
530 const int year = date.year();
531 const int month = date.month();
532 const int day = date.day();
533 timelineUrl = "timeline:/" + timelineDateString(year, month) +
534 '/' + timelineDateString(year, month, day);
535 } else if (path.endsWith("thismonth")) {
536 const QDate date = QDate::currentDate();
537 timelineUrl = "timeline:/" + timelineDateString(date.year(), date.month());
538 } else if (path.endsWith("lastmonth")) {
539 const QDate date = QDate::currentDate().addMonths(-1);
540 timelineUrl = "timeline:/" + timelineDateString(date.year(), date.month());
541 } else {
542 Q_ASSERT(path.endsWith("today"));
543 timelineUrl= url;
544 }
545
546 return timelineUrl;
547 }
548
549 QString PlacesItemModel::timelineDateString(int year, int month, int day)
550 {
551 QString date = QString::number(year) + '-';
552 if (month < 10) {
553 date += '0';
554 }
555 date += QString::number(month);
556
557 if (day >= 1) {
558 date += '-';
559 if (day < 10) {
560 date += '0';
561 }
562 date += QString::number(day);
563 }
564
565 return date;
566 }
567
568 KUrl PlacesItemModel::createSearchUrl(const KUrl& url)
569 {
570 KUrl searchUrl;
571
572 #ifdef HAVE_NEPOMUK
573 const QString path = url.pathOrUrl();
574 if (path.endsWith("documents")) {
575 searchUrl = searchUrlForTerm(Nepomuk::Query::ResourceTypeTerm(Nepomuk::Vocabulary::NFO::Document()));
576 } else if (path.endsWith("images")) {
577 searchUrl = searchUrlForTerm(Nepomuk::Query::ResourceTypeTerm(Nepomuk::Vocabulary::NFO::Image()));
578 } else if (path.endsWith("audio")) {
579 searchUrl = searchUrlForTerm(Nepomuk::Query::ComparisonTerm(Nepomuk::Vocabulary::NIE::mimeType(),
580 Nepomuk::Query::LiteralTerm("audio")));
581 } else if (path.endsWith("videos")) {
582 searchUrl = searchUrlForTerm(Nepomuk::Query::ComparisonTerm(Nepomuk::Vocabulary::NIE::mimeType(),
583 Nepomuk::Query::LiteralTerm("video")));
584 } else {
585 Q_ASSERT(false);
586 }
587 #else
588 Q_UNUSED(url);
589 #endif
590
591 return searchUrl;
592 }
593
594 #ifdef HAVE_NEPOMUK
595 KUrl PlacesItemModel::searchUrlForTerm(const Nepomuk::Query::Term& term)
596 {
597 const Nepomuk::Query::Query query(term);
598 return query.toSearchUrl();
599 }
600 #endif
601
602 #ifdef PLACESITEMMODEL_DEBUG
603 void PlacesItemModel::showModelState()
604 {
605 kDebug() << "hidden-index model-index text";
606 int j = 0;
607 for (int i = 0; i < m_hiddenItems.count(); ++i) {
608 if (m_hiddenItems[i]) {
609 kDebug() << i << "(Hidden) " << " " << m_hiddenItems[i]->dataValue("text").toString();
610 } else {
611 kDebug() << i << " " << j << " " << item(j)->dataValue("text").toString();
612 ++j;
613 }
614 }
615 }
616 #endif
617
618 #include "placesitemmodel.moc"