KIO

kfileplacesmodel.cpp
1 /*
2  This file is part of the KDE project
3  SPDX-FileCopyrightText: 2007 Kevin Ottens <[email protected]>
4  SPDX-FileCopyrightText: 2007 David Faure <[email protected]>
5 
6  SPDX-License-Identifier: LGPL-2.0-only
7 */
8 
9 #include "kfileplacesmodel.h"
10 #include "kfileplacesitem_p.h"
11 
12 #ifdef _WIN32_WCE
13 #include "WinBase.h"
14 #include "Windows.h"
15 #endif
16 
17 #include <KCoreDirLister>
18 #include <KLazyLocalizedString>
19 #include <KListOpenFilesJob>
20 #include <KLocalizedString>
21 #include <kfileitem.h>
22 #include <kio/statjob.h>
23 #include <kprotocolinfo.h>
24 
25 #include <KBookmarkManager>
26 #include <KConfig>
27 #include <KConfigGroup>
28 #include <KUrlMimeData>
29 
30 #include <solid/devicenotifier.h>
31 #include <solid/opticaldisc.h>
32 #include <solid/opticaldrive.h>
33 #include <solid/portablemediaplayer.h>
34 #include <solid/predicate.h>
35 #include <solid/storageaccess.h>
36 #include <solid/storagedrive.h>
37 #include <solid/storagevolume.h>
38 
39 #include <QAction>
40 #include <QCoreApplication>
41 #include <QDebug>
42 #include <QDir>
43 #include <QFile>
44 #include <QMimeData>
45 #include <QMimeDatabase>
46 #include <QStandardPaths>
47 #include <QTimer>
48 
49 namespace
50 {
51 QString stateNameForGroupType(KFilePlacesModel::GroupType type)
52 {
53  switch (type) {
55  return QStringLiteral("GroupState-Places-IsHidden");
57  return QStringLiteral("GroupState-Remote-IsHidden");
59  return QStringLiteral("GroupState-RecentlySaved-IsHidden");
61  return QStringLiteral("GroupState-SearchFor-IsHidden");
63  return QStringLiteral("GroupState-Devices-IsHidden");
65  return QStringLiteral("GroupState-RemovableDevices-IsHidden");
67  return QStringLiteral("GroupState-Tags-IsHidden");
68  default:
69  Q_UNREACHABLE();
70  }
71 }
72 
73 static bool isFileIndexingEnabled()
74 {
75  KConfig config(QStringLiteral("baloofilerc"));
76  KConfigGroup basicSettings = config.group("Basic Settings");
77  return basicSettings.readEntry("Indexing-Enabled", true);
78 }
79 
80 static QString timelineDateString(int year, int month, int day = 0)
81 {
82  const QString dateFormat = QStringLiteral("%1-%2");
83 
84  QString date = dateFormat.arg(year).arg(month, 2, 10, QLatin1Char('0'));
85  if (day > 0) {
86  date += QStringLiteral("-%1").arg(day, 2, 10, QLatin1Char('0'));
87  }
88  return date;
89 }
90 
91 static QUrl createTimelineUrl(const QUrl &url)
92 {
93  // based on dolphin urls
94  const QString timelinePrefix = QLatin1String("timeline:") + QLatin1Char('/');
95  QUrl timelineUrl;
96 
98  if (path.endsWith(QLatin1String("/yesterday"))) {
99  const QDate date = QDate::currentDate().addDays(-1);
100  const int year = date.year();
101  const int month = date.month();
102  const int day = date.day();
103 
104  timelineUrl = QUrl(timelinePrefix + timelineDateString(year, month) + QLatin1Char('/') + timelineDateString(year, month, day));
105  } else if (path.endsWith(QLatin1String("/thismonth"))) {
106  const QDate date = QDate::currentDate();
107  timelineUrl = QUrl(timelinePrefix + timelineDateString(date.year(), date.month()));
108  } else if (path.endsWith(QLatin1String("/lastmonth"))) {
109  const QDate date = QDate::currentDate().addMonths(-1);
110  timelineUrl = QUrl(timelinePrefix + timelineDateString(date.year(), date.month()));
111  } else {
112  Q_ASSERT(path.endsWith(QLatin1String("/today")));
113  timelineUrl = url;
114  }
115 
116  return timelineUrl;
117 }
118 
119 static QUrl createSearchUrl(const QUrl &url)
120 {
121  QUrl searchUrl = url;
122 
124 
125  const QStringList validSearchPaths = {QStringLiteral("/documents"), QStringLiteral("/images"), QStringLiteral("/audio"), QStringLiteral("/videos")};
126 
127  for (const QString &validPath : validSearchPaths) {
128  if (path.endsWith(validPath)) {
129  searchUrl.setScheme(QStringLiteral("baloosearch"));
130  return searchUrl;
131  }
132  }
133 
134  qWarning() << "Invalid search url:" << url;
135 
136  return searchUrl;
137 }
138 }
139 
140 class KFilePlacesModelPrivate
141 {
142 public:
143  explicit KFilePlacesModelPrivate(KFilePlacesModel *qq)
144  : q(qq)
145  , bookmarkManager(nullptr)
146  , fileIndexingEnabled(isFileIndexingEnabled())
147  , tags()
148  , tagsLister(new KCoreDirLister(q))
149  {
150  if (KProtocolInfo::isKnownProtocol(QStringLiteral("tags"))) {
151  QObject::connect(tagsLister, &KCoreDirLister::itemsAdded, q, [this](const QUrl &, const KFileItemList &items) {
152  if (tags.isEmpty()) {
153  QList<QUrl> existingBookmarks;
154 
155  KBookmarkGroup root = bookmarkManager->root();
156  KBookmark bookmark = root.first();
157 
158  while (!bookmark.isNull()) {
159  existingBookmarks.append(bookmark.url());
160  bookmark = root.next(bookmark);
161  }
162 
163  if (!existingBookmarks.contains(QUrl(tagsUrlBase))) {
164  KBookmark alltags = KFilePlacesItem::createSystemBookmark(bookmarkManager,
165  kli18nc("KFile System Bookmarks", "All tags").untranslatedText(),
166  QUrl(tagsUrlBase),
167  QStringLiteral("tag"));
168  }
169  }
170 
171  for (const KFileItem &item : items) {
172  const QString name = item.name();
173 
174  if (!tags.contains(name)) {
175  tags.append(name);
176  }
177  }
178  reloadBookmarks();
179  });
180 
181  QObject::connect(tagsLister, &KCoreDirLister::itemsDeleted, q, [this](const KFileItemList &items) {
182  for (const KFileItem &item : items) {
183  tags.removeAll(item.name());
184  }
185  reloadBookmarks();
186  });
187 
188  tagsLister->openUrl(QUrl(tagsUrlBase), KCoreDirLister::OpenUrlFlag::Reload);
189  }
190  }
191 
192  KFilePlacesModel *const q;
193 
195  QVector<QString> availableDevices;
197  QMap<QObject *, QPersistentModelIndex> teardownInProgress;
198  QStringList supportedSchemes;
199 
200  Solid::Predicate predicate;
201  KBookmarkManager *bookmarkManager;
202 
203  const bool fileIndexingEnabled;
204 
205  QString alternativeApplicationName;
206 
207  void reloadAndSignal();
208  QList<KFilePlacesItem *> loadBookmarkList();
209  int findNearestPosition(int source, int target);
210 
211  QVector<QString> tags;
212  const QString tagsUrlBase = QStringLiteral("tags:/");
213  KCoreDirLister *tagsLister;
214 
215  void initDeviceList();
216  void deviceAdded(const QString &udi);
217  void deviceRemoved(const QString &udi);
218  void itemChanged(const QString &udi, const QVector<int> &roles);
219  void reloadBookmarks();
220  void storageSetupDone(Solid::ErrorType error, const QVariant &errorData, Solid::StorageAccess *sender);
221  void storageTeardownDone(const QString &filePath, Solid::ErrorType error, const QVariant &errorData, QObject *sender);
222 
223 private:
224  bool isBalooUrl(const QUrl &url) const;
225 };
226 
228 {
229  KBookmarkGroup root = d->bookmarkManager->root();
230  KBookmark current = root.first();
231  while (!current.isNull()) {
232  if (current.url() == searchUrl) {
233  return current;
234  }
235  current = root.next(current);
236  }
237  return KBookmark();
238 }
239 
240 static inline QString versionKey()
241 {
242  return QStringLiteral("kde_places_version");
243 }
244 
245 KFilePlacesModel::KFilePlacesModel(const QString &alternativeApplicationName, QObject *parent)
246  : QAbstractItemModel(parent)
247  , d(new KFilePlacesModelPrivate(this))
248 {
250  d->bookmarkManager = KBookmarkManager::managerForExternalFile(file);
251  d->alternativeApplicationName = alternativeApplicationName;
252 
253  // Let's put some places in there if it's empty.
254  KBookmarkGroup root = d->bookmarkManager->root();
255 
256  const auto setDefaultMetadataItemForGroup = [&root](KFilePlacesModel::GroupType type) {
257  root.setMetaDataItem(stateNameForGroupType(type), QStringLiteral("false"));
258  };
259 
260  // Increase this version number and use the following logic to handle the update process for existing installations.
261  static const int s_currentVersion = 4;
262 
263  const bool newFile = root.first().isNull() || !QFile::exists(file);
264  const int fileVersion = root.metaDataItem(versionKey()).toInt();
265 
266  if (newFile || fileVersion < s_currentVersion) {
267  root.setMetaDataItem(versionKey(), QString::number(s_currentVersion));
268 
269  const QList<QUrl> seenUrls = root.groupUrlList();
270 
271  /* clang-format off */
272  auto createSystemBookmark =
273  [this, &seenUrls](const char *untranslatedLabel,
274  const QUrl &url,
275  const QString &iconName,
276  const KBookmark &after) {
277  if (!seenUrls.contains(url)) {
278  return KFilePlacesItem::createSystemBookmark(d->bookmarkManager, untranslatedLabel, url, iconName, after);
279  }
280  return KBookmark();
281  };
282  /* clang-format on */
283 
284  if (fileVersion < 2) {
285  // NOTE: The context for these kli18nc calls has to be "KFile System Bookmarks".
286  // The real i18nc call is made later, with this context, so the two must match.
287  createSystemBookmark(kli18nc("KFile System Bookmarks", "Home").untranslatedText(),
289  QStringLiteral("user-home"),
290  KBookmark());
291 
292  // Some distros may not create various standard XDG folders by default
293  // so check for their existence before adding bookmarks for them
295  if (QDir(desktopFolder).exists()) {
296  createSystemBookmark(kli18nc("KFile System Bookmarks", "Desktop").untranslatedText(),
297  QUrl::fromLocalFile(desktopFolder),
298  QStringLiteral("user-desktop"),
299  KBookmark());
300  }
302  if (QDir(documentsFolder).exists()) {
303  createSystemBookmark(kli18nc("KFile System Bookmarks", "Documents").untranslatedText(),
304  QUrl::fromLocalFile(documentsFolder),
305  QStringLiteral("folder-documents"),
306  KBookmark());
307  }
309  if (QDir(downloadFolder).exists()) {
310  createSystemBookmark(kli18nc("KFile System Bookmarks", "Downloads").untranslatedText(),
311  QUrl::fromLocalFile(downloadFolder),
312  QStringLiteral("folder-downloads"),
313  KBookmark());
314  }
315  createSystemBookmark(kli18nc("KFile System Bookmarks", "Network").untranslatedText(),
316  QUrl(QStringLiteral("remote:/")),
317  QStringLiteral("folder-network"),
318  KBookmark());
319 
320  createSystemBookmark(kli18nc("KFile System Bookmarks", "Trash").untranslatedText(),
321  QUrl(QStringLiteral("trash:/")),
322  QStringLiteral("user-trash"),
323  KBookmark());
324  }
325 
326  if (!newFile && fileVersion < 3) {
327  KBookmarkGroup rootGroup = d->bookmarkManager->root();
328  KBookmark bItem = rootGroup.first();
329  while (!bItem.isNull()) {
330  KBookmark nextbItem = rootGroup.next(bItem);
331  const bool isSystemItem = bItem.metaDataItem(QStringLiteral("isSystemItem")) == QLatin1String("true");
332  if (isSystemItem) {
333  const QString text = bItem.fullText();
334  // Because of b8a4c2223453932202397d812a0c6b30c6186c70 we need to find the system bookmark named Audio Files
335  // and rename it to Audio, otherwise users are getting untranslated strings
336  if (text == QLatin1String("Audio Files")) {
337  bItem.setFullText(QStringLiteral("Audio"));
338  } else if (text == QLatin1String("Today")) {
339  // Because of 19feef732085b444515da3f6c66f3352bbcb1824 we need to find the system bookmark named Today
340  // and rename it to Modified Today, otherwise users are getting untranslated strings
341  bItem.setFullText(QStringLiteral("Modified Today"));
342  } else if (text == QLatin1String("Yesterday")) {
343  // Because of 19feef732085b444515da3f6c66f3352bbcb1824 we need to find the system bookmark named Yesterday
344  // and rename it to Modified Yesterday, otherwise users are getting untranslated strings
345  bItem.setFullText(QStringLiteral("Modified Yesterday"));
346  } else if (text == QLatin1String("This Month")) {
347  // Because of 7e1d2fb84546506c91684dd222c2485f0783848f we need to find the system bookmark named This Month
348  // and remove it, otherwise users are getting untranslated strings
349  rootGroup.deleteBookmark(bItem);
350  } else if (text == QLatin1String("Last Month")) {
351  // Because of 7e1d2fb84546506c91684dd222c2485f0783848f we need to find the system bookmark named Last Month
352  // and remove it, otherwise users are getting untranslated strings
353  rootGroup.deleteBookmark(bItem);
354  }
355  }
356 
357  bItem = nextbItem;
358  }
359  }
360  if (fileVersion < 4) {
361  auto findSystemBookmark = [this](const QString &untranslatedText) {
362  KBookmarkGroup root = d->bookmarkManager->root();
363  KBookmark bItem = root.first();
364  while (!bItem.isNull()) {
365  const bool isSystemItem = bItem.metaDataItem(QStringLiteral("isSystemItem")) == QLatin1String("true");
366  if (isSystemItem && bItem.fullText() == untranslatedText) {
367  return bItem;
368  }
369  bItem = root.next(bItem);
370  }
371  return KBookmark();
372  };
373  // This variable is used to insert the new bookmarks at the correct place starting after the "Downloads"
374  // bookmark. When the user already has some of the bookmarks set up manually, the createSystemBookmark()
375  // function returns an empty KBookmark so the following entries will be added at the end of the bookmark
376  // section to not mess with the users setup.
377  KBookmark after = findSystemBookmark(QLatin1String("Downloads"));
378 
380  if (QDir(musicFolder).exists()) {
381  after = createSystemBookmark(kli18nc("KFile System Bookmarks", "Music").untranslatedText(),
382  QUrl::fromLocalFile(musicFolder),
383  QStringLiteral("folder-music"),
384  after);
385  }
387  if (QDir(pictureFolder).exists()) {
388  after = createSystemBookmark(kli18nc("KFile System Bookmarks", "Pictures").untranslatedText(),
389  QUrl::fromLocalFile(pictureFolder),
390  QStringLiteral("folder-pictures"),
391  after);
392  }
393  // Choosing the name "Videos" instead of "Movies", since that is how the folder
394  // is called normally on Linux: https://cgit.freedesktop.org/xdg/xdg-user-dirs/tree/user-dirs.defaults
396  if (QDir(videoFolder).exists()) {
397  after = createSystemBookmark(kli18nc("KFile System Bookmarks", "Videos").untranslatedText(),
398  QUrl::fromLocalFile(videoFolder),
399  QStringLiteral("folder-videos"),
400  after);
401  }
402  }
403 
404  if (newFile) {
405  setDefaultMetadataItemForGroup(PlacesType);
406  setDefaultMetadataItemForGroup(RemoteType);
407  setDefaultMetadataItemForGroup(DevicesType);
408  setDefaultMetadataItemForGroup(RemovableDevicesType);
409  setDefaultMetadataItemForGroup(TagsType);
410  }
411 
412  // Force bookmarks to be saved. If on open/save dialog and the bookmarks are not saved, QFile::exists
413  // will always return false, which opening/closing all the time the open/save dialog would cause the
414  // bookmarks to be added once each time, having lots of times each bookmark. (ereslibre)
415  d->bookmarkManager->saveAs(file);
416  }
417 
418  // Add a Recently Used entry if available (it comes from kio-extras)
419  if (qEnvironmentVariableIsSet("KDE_FULL_SESSION") && KProtocolInfo::isKnownProtocol(QStringLiteral("recentlyused"))
420  && root.metaDataItem(QStringLiteral("withRecentlyUsed")) != QLatin1String("true")) {
421  root.setMetaDataItem(QStringLiteral("withRecentlyUsed"), QStringLiteral("true"));
422 
423  KBookmark recentFilesBookmark = KFilePlacesItem::createSystemBookmark(d->bookmarkManager,
424  kli18nc("KFile System Bookmarks", "Recent Files").untranslatedText(),
425  QUrl(QStringLiteral("recentlyused:/files")),
426  QStringLiteral("document-open-recent"));
427 
428  KBookmark recentDirectoriesBookmark = KFilePlacesItem::createSystemBookmark(d->bookmarkManager,
429  kli18nc("KFile System Bookmarks", "Recent Locations").untranslatedText(),
430  QUrl(QStringLiteral("recentlyused:/locations")),
431  QStringLiteral("folder-open-recent"));
432 
433  setDefaultMetadataItemForGroup(RecentlySavedType);
434 
435  // Move The recently used bookmarks below the trash, making it the first element in the Recent group
436  KBookmark trashBookmark = bookmarkForUrl(QUrl(QStringLiteral("trash:/")));
437  if (!trashBookmark.isNull()) {
438  root.moveBookmark(recentFilesBookmark, trashBookmark);
439  root.moveBookmark(recentDirectoriesBookmark, recentFilesBookmark);
440  }
441 
442  d->bookmarkManager->save();
443  }
444 
445  // if baloo is enabled, add new urls even if the bookmark file is not empty
446  if (d->fileIndexingEnabled && root.metaDataItem(QStringLiteral("withBaloo")) != QLatin1String("true")) {
447  root.setMetaDataItem(QStringLiteral("withBaloo"), QStringLiteral("true"));
448 
449  // don't add by default "Modified Today" and "Modified Yesterday" when recentlyused:/ is present
450  if (root.metaDataItem(QStringLiteral("withRecentlyUsed")) != QLatin1String("true")) {
451  KFilePlacesItem::createSystemBookmark(d->bookmarkManager,
452  kli18nc("KFile System Bookmarks", "Modified Today").untranslatedText(),
453  QUrl(QStringLiteral("timeline:/today")),
454  QStringLiteral("go-jump-today"));
455  KFilePlacesItem::createSystemBookmark(d->bookmarkManager,
456  kli18nc("KFile System Bookmarks", "Modified Yesterday").untranslatedText(),
457  QUrl(QStringLiteral("timeline:/yesterday")),
458  QStringLiteral("view-calendar-day"));
459  }
460 
461  setDefaultMetadataItemForGroup(SearchForType);
462  setDefaultMetadataItemForGroup(RecentlySavedType);
463 
464  d->bookmarkManager->save();
465  }
466 
467  QString predicate(
468  QString::fromLatin1("[[[[ StorageVolume.ignored == false AND [ StorageVolume.usage == 'FileSystem' OR StorageVolume.usage == 'Encrypted' ]]"
469  " OR "
470  "[ IS StorageAccess AND StorageDrive.driveType == 'Floppy' ]]"
471  " OR "
472  "OpticalDisc.availableContent & 'Audio' ]"
473  " OR "
474  "StorageAccess.ignored == false ]"));
475 
476  if (KProtocolInfo::isKnownProtocol(QStringLiteral("mtp"))) {
477  predicate = QLatin1Char('[') + predicate + QLatin1String(" OR PortableMediaPlayer.supportedProtocols == 'mtp']");
478  }
479  if (KProtocolInfo::isKnownProtocol(QStringLiteral("afc"))) {
480  predicate = QLatin1Char('[') + predicate + QLatin1String(" OR PortableMediaPlayer.supportedProtocols == 'afc']");
481  }
482 
483  d->predicate = Solid::Predicate::fromString(predicate);
484 
485  Q_ASSERT(d->predicate.isValid());
486 
487  connect(d->bookmarkManager, &KBookmarkManager::changed, this, [this]() {
488  d->reloadBookmarks();
489  });
490  connect(d->bookmarkManager, &KBookmarkManager::bookmarksChanged, this, [this]() {
491  d->reloadBookmarks();
492  });
493 
494  d->reloadBookmarks();
495  QTimer::singleShot(0, this, [this]() {
496  d->initDeviceList();
497  });
498 }
499 
500 KFilePlacesModel::KFilePlacesModel(QObject *parent)
501  : KFilePlacesModel({}, parent)
502 {
503 }
504 
505 KFilePlacesModel::~KFilePlacesModel() = default;
506 
507 QUrl KFilePlacesModel::url(const QModelIndex &index) const
508 {
509  return data(index, UrlRole).toUrl();
510 }
511 
512 bool KFilePlacesModel::setupNeeded(const QModelIndex &index) const
513 {
514  return data(index, SetupNeededRole).toBool();
515 }
516 
517 QIcon KFilePlacesModel::icon(const QModelIndex &index) const
518 {
520 }
521 
522 QString KFilePlacesModel::text(const QModelIndex &index) const
523 {
524  return data(index, Qt::DisplayRole).toString();
525 }
526 
527 bool KFilePlacesModel::isHidden(const QModelIndex &index) const
528 {
529  // Note: we do not want to show an index if its parent is hidden
531 }
532 
533 bool KFilePlacesModel::isGroupHidden(const GroupType type) const
534 {
535  const QString hidden = d->bookmarkManager->root().metaDataItem(stateNameForGroupType(type));
536  return hidden == QLatin1String("true");
537 }
538 
539 bool KFilePlacesModel::isGroupHidden(const QModelIndex &index) const
540 {
541  if (!index.isValid()) {
542  return false;
543  }
544 
545  KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
546  return isGroupHidden(item->groupType());
547 }
548 
549 bool KFilePlacesModel::isDevice(const QModelIndex &index) const
550 {
551  if (!index.isValid()) {
552  return false;
553  }
554 
555  KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
556 
557  return item->isDevice();
558 }
559 
560 bool KFilePlacesModel::isTeardownAllowed(const QModelIndex &index) const
561 {
562  if (!index.isValid()) {
563  return false;
564  }
565 
566  KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
567  return item->isTeardownAllowed();
568 }
569 
570 bool KFilePlacesModel::isEjectAllowed(const QModelIndex &index) const
571 {
572  if (!index.isValid()) {
573  return false;
574  }
575 
576  KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
577  return item->isEjectAllowed();
578 }
579 
581 {
582  if (!index.isValid()) {
583  return false;
584  }
585 
586  KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
587  return item->isTeardownOverlayRecommended();
588 }
589 
590 KFilePlacesModel::DeviceAccessibility KFilePlacesModel::deviceAccessibility(const QModelIndex &index) const
591 {
592  if (!index.isValid()) {
593  return KFilePlacesModel::Accessible;
594  }
595 
596  KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
597  return item->deviceAccessibility();
598 }
599 
601 {
602  if (!index.isValid()) {
603  return Solid::Device();
604  }
605 
606  KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
607 
608  if (item->isDevice()) {
609  return item->device();
610  } else {
611  return Solid::Device();
612  }
613 }
614 
616 {
617  if (!index.isValid()) {
618  return KBookmark();
619  }
620 
621  KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
622  return item->bookmark();
623 }
624 
626 {
627  if (!index.isValid()) {
628  return UnknownType;
629  }
630 
631  KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
632  return item->groupType();
633 }
634 
635 QModelIndexList KFilePlacesModel::groupIndexes(const KFilePlacesModel::GroupType type) const
636 {
637  if (type == UnknownType) {
638  return QModelIndexList();
639  }
640 
641  QModelIndexList indexes;
642  const int rows = rowCount();
643  for (int row = 0; row < rows; ++row) {
644  const QModelIndex current = index(row, 0);
645  if (groupType(current) == type) {
646  indexes << current;
647  }
648  }
649 
650  return indexes;
651 }
652 
653 QVariant KFilePlacesModel::data(const QModelIndex &index, int role) const
654 {
655  if (!index.isValid()) {
656  return QVariant();
657  }
658 
659  KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
660  if (role == KFilePlacesModel::GroupHiddenRole) {
661  return isGroupHidden(item->groupType());
662  } else {
663  return item->data(role);
664  }
665 }
666 
667 QModelIndex KFilePlacesModel::index(int row, int column, const QModelIndex &parent) const
668 {
669  if (row < 0 || column != 0 || row >= d->items.size()) {
670  return QModelIndex();
671  }
672 
673  if (parent.isValid()) {
674  return QModelIndex();
675  }
676 
677  return createIndex(row, column, d->items.at(row));
678 }
679 
681 {
682  Q_UNUSED(child);
683  return QModelIndex();
684 }
685 
687 {
688  auto super = QAbstractItemModel::roleNames();
689 
690  super[UrlRole] = "url";
691  super[HiddenRole] = "isHidden";
692  super[SetupNeededRole] = "isSetupNeeded";
693  super[FixedDeviceRole] = "isFixedDevice";
694  super[CapacityBarRecommendedRole] = "isCapacityBarRecommended";
695  super[GroupRole] = "group";
696  super[IconNameRole] = "iconName";
697  super[GroupHiddenRole] = "isGroupHidden";
698  super[TeardownAllowedRole] = "isTeardownAllowed";
699  super[EjectAllowedRole] = "isEjectAllowed";
700  super[TeardownOverlayRecommendedRole] = "isTeardownOverlayRecommended";
701  super[DeviceAccessibilityRole] = "deviceAccessibility";
702 
703  return super;
704 }
705 
706 int KFilePlacesModel::rowCount(const QModelIndex &parent) const
707 {
708  if (parent.isValid()) {
709  return 0;
710  } else {
711  return d->items.size();
712  }
713 }
714 
715 int KFilePlacesModel::columnCount(const QModelIndex &parent) const
716 {
717  Q_UNUSED(parent)
718  // We only know 1 piece of information for a particular entry
719  return 1;
720 }
721 
723 {
724  int foundRow = -1;
725  int maxLength = 0;
726 
727  // Search the item which is equal to the URL or at least is a parent URL.
728  // If there are more than one possible item URL candidates, choose the item
729  // which covers the bigger range of the URL.
730  for (int row = 0; row < d->items.size(); ++row) {
731  KFilePlacesItem *item = d->items[row];
732 
733  if (item->isHidden() || isGroupHidden(item->groupType())) {
734  continue;
735  }
736 
737  const QUrl itemUrl = convertedUrl(item->data(UrlRole).toUrl());
738 
739  if (itemUrl.matches(url, QUrl::StripTrailingSlash)
740  || (itemUrl.isParentOf(url) && itemUrl.query() == url.query() && itemUrl.fragment() == url.fragment())) {
741  const int length = itemUrl.toString().length();
742  if (length > maxLength) {
743  foundRow = row;
744  maxLength = length;
745  }
746  }
747  }
748 
749  if (foundRow == -1) {
750  return QModelIndex();
751  } else {
752  return createIndex(foundRow, 0, d->items[foundRow]);
753  }
754 }
755 
756 void KFilePlacesModelPrivate::initDeviceList()
757 {
758  Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance();
759 
760  QObject::connect(notifier, &Solid::DeviceNotifier::deviceAdded, q, [this](const QString &device) {
761  deviceAdded(device);
762  });
763  QObject::connect(notifier, &Solid::DeviceNotifier::deviceRemoved, q, [this](const QString &device) {
764  deviceRemoved(device);
765  });
766 
767  const QList<Solid::Device> &deviceList = Solid::Device::listFromQuery(predicate);
768 
769  availableDevices.reserve(deviceList.size());
770  for (const Solid::Device &device : deviceList) {
771  availableDevices << device.udi();
772  }
773 
774  reloadBookmarks();
775 }
776 
777 void KFilePlacesModelPrivate::deviceAdded(const QString &udi)
778 {
779  Solid::Device d(udi);
780 
781  if (predicate.matches(d)) {
782  availableDevices << udi;
783  reloadBookmarks();
784  }
785 }
786 
787 void KFilePlacesModelPrivate::deviceRemoved(const QString &udi)
788 {
789  auto it = std::find(availableDevices.begin(), availableDevices.end(), udi);
790  if (it != availableDevices.end()) {
791  availableDevices.erase(it);
792  reloadBookmarks();
793  }
794 }
795 
796 void KFilePlacesModelPrivate::itemChanged(const QString &id, const QVector<int> &roles)
797 {
798  for (int row = 0; row < items.size(); ++row) {
799  if (items.at(row)->id() == id) {
800  QModelIndex index = q->index(row, 0);
801  Q_EMIT q->dataChanged(index, index, roles);
802  }
803  }
804 }
805 
806 void KFilePlacesModelPrivate::reloadBookmarks()
807 {
808  QList<KFilePlacesItem *> currentItems = loadBookmarkList();
809 
811  QList<KFilePlacesItem *>::Iterator it_c = currentItems.begin();
812 
813  QList<KFilePlacesItem *>::Iterator end_i = items.end();
814  QList<KFilePlacesItem *>::Iterator end_c = currentItems.end();
815 
816  while (it_i != end_i || it_c != end_c) {
817  if (it_i == end_i && it_c != end_c) {
818  int row = items.count();
819 
820  q->beginInsertRows(QModelIndex(), row, row);
821  it_i = items.insert(it_i, *it_c);
822  ++it_i;
823  it_c = currentItems.erase(it_c);
824 
825  end_i = items.end();
826  end_c = currentItems.end();
827  q->endInsertRows();
828 
829  } else if (it_i != end_i && it_c == end_c) {
830  int row = items.indexOf(*it_i);
831 
832  q->beginRemoveRows(QModelIndex(), row, row);
833  delete *it_i;
834  it_i = items.erase(it_i);
835 
836  end_i = items.end();
837  end_c = currentItems.end();
838  q->endRemoveRows();
839 
840  } else if ((*it_i)->id() == (*it_c)->id()) {
841  bool shouldEmit = !((*it_i)->bookmark() == (*it_c)->bookmark());
842  (*it_i)->setBookmark((*it_c)->bookmark());
843  if (shouldEmit) {
844  int row = items.indexOf(*it_i);
845  QModelIndex idx = q->index(row, 0);
846  Q_EMIT q->dataChanged(idx, idx);
847  }
848  ++it_i;
849  ++it_c;
850  } else {
851  int row = items.indexOf(*it_i);
852 
853  if (it_i + 1 != end_i && (*(it_i + 1))->id() == (*it_c)->id()) { // if the next one matches, it's a remove
854  q->beginRemoveRows(QModelIndex(), row, row);
855  delete *it_i;
856  it_i = items.erase(it_i);
857 
858  end_i = items.end();
859  end_c = currentItems.end();
860  q->endRemoveRows();
861  } else {
862  q->beginInsertRows(QModelIndex(), row, row);
863  it_i = items.insert(it_i, *it_c);
864  ++it_i;
865  it_c = currentItems.erase(it_c);
866 
867  end_i = items.end();
868  end_c = currentItems.end();
869  q->endInsertRows();
870  }
871  }
872  }
873 
874  qDeleteAll(currentItems);
875  currentItems.clear();
876 
877  Q_EMIT q->reloaded();
878 }
879 
880 bool KFilePlacesModelPrivate::isBalooUrl(const QUrl &url) const
881 {
882  const QString scheme = url.scheme();
883  return ((scheme == QLatin1String("timeline")) || (scheme == QLatin1String("search")));
884 }
885 
886 QList<KFilePlacesItem *> KFilePlacesModelPrivate::loadBookmarkList()
887 {
889 
890  KBookmarkGroup root = bookmarkManager->root();
891  KBookmark bookmark = root.first();
892  QVector<QString> devices = availableDevices;
893  QVector<QString> tagsList = tags;
894 
895  while (!bookmark.isNull()) {
896  const QString udi = bookmark.metaDataItem(QStringLiteral("UDI"));
897  const QUrl url = bookmark.url();
898  const QString tag = bookmark.metaDataItem(QStringLiteral("tag"));
899  if (!udi.isEmpty() || url.isValid()) {
900  QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp"));
901 
902  // If it's not a tag it's a device
903  if (tag.isEmpty()) {
904  auto it = std::find(devices.begin(), devices.end(), udi);
905  bool deviceAvailable = (it != devices.end());
906  if (deviceAvailable) {
907  devices.erase(it);
908  }
909 
910  bool allowedHere =
911  appName.isEmpty() || ((appName == QCoreApplication::instance()->applicationName()) || (appName == alternativeApplicationName));
912  bool isSupportedUrl = isBalooUrl(url) ? fileIndexingEnabled : true;
913  bool isSupportedScheme = supportedSchemes.isEmpty() || supportedSchemes.contains(url.scheme());
914 
915  KFilePlacesItem *item = nullptr;
916  if (deviceAvailable) {
917  item = new KFilePlacesItem(bookmarkManager, bookmark.address(), udi, q);
918  if (!item->hasSupportedScheme(supportedSchemes)) {
919  delete item;
920  item = nullptr;
921  }
922  } else if (isSupportedScheme && isSupportedUrl && udi.isEmpty() && allowedHere) {
923  // TODO: Update bookmark internal element
924  item = new KFilePlacesItem(bookmarkManager, bookmark.address(), QString(), q);
925  }
926 
927  if (item) {
928  QObject::connect(item, &KFilePlacesItem::itemChanged, q, [this](const QString &id, const QVector<int> &roles) {
929  itemChanged(id, roles);
930  });
931 
932  items << item;
933  }
934  } else {
935  auto it = std::find(tagsList.begin(), tagsList.end(), tag);
936  if (it != tagsList.end()) {
937  tagsList.removeAll(tag);
938  KFilePlacesItem *item = new KFilePlacesItem(bookmarkManager, bookmark.address(), QString(), q);
939  items << item;
940  QObject::connect(item, &KFilePlacesItem::itemChanged, q, [this](const QString &id, const QVector<int> &roles) {
941  itemChanged(id, roles);
942  });
943  }
944  }
945  }
946 
947  bookmark = root.next(bookmark);
948  }
949 
950  // Add bookmarks for the remaining devices, they were previously unknown
951  for (const QString &udi : std::as_const(devices)) {
952  bookmark = KFilePlacesItem::createDeviceBookmark(bookmarkManager, udi);
953  if (!bookmark.isNull()) {
954  KFilePlacesItem *item = new KFilePlacesItem(bookmarkManager, bookmark.address(), udi, q);
955  QObject::connect(item, &KFilePlacesItem::itemChanged, q, [this](const QString &id, const QVector<int> &roles) {
956  itemChanged(id, roles);
957  });
958  // TODO: Update bookmark internal element
959  items << item;
960  }
961  }
962 
963  for (const QString &tag : tagsList) {
964  bookmark = KFilePlacesItem::createTagBookmark(bookmarkManager, tag);
965  if (!bookmark.isNull()) {
966  KFilePlacesItem *item = new KFilePlacesItem(bookmarkManager, bookmark.address(), tag, q);
967  QObject::connect(item, &KFilePlacesItem::itemChanged, q, [this](const QString &id, const QVector<int> &roles) {
968  itemChanged(id, roles);
969  });
970  items << item;
971  }
972  }
973 
974  // return a sorted list based on groups
975  std::stable_sort(items.begin(), items.end(), [](KFilePlacesItem *itemA, KFilePlacesItem *itemB) {
976  return (itemA->groupType() < itemB->groupType());
977  });
978 
979  return items;
980 }
981 
982 int KFilePlacesModelPrivate::findNearestPosition(int source, int target)
983 {
984  const KFilePlacesItem *item = items.at(source);
985  const KFilePlacesModel::GroupType groupType = item->groupType();
986  int newTarget = qMin(target, items.count() - 1);
987 
988  // moving inside the same group is ok
989  if ((items.at(newTarget)->groupType() == groupType)) {
990  return target;
991  }
992 
993  if (target > source) { // moving down, move it to the end of the group
994  int groupFooter = source;
995  while (items.at(groupFooter)->groupType() == groupType) {
996  groupFooter++;
997  // end of the list move it there
998  if (groupFooter == items.count()) {
999  break;
1000  }
1001  }
1002  target = groupFooter;
1003  } else { // moving up, move it to beginning of the group
1004  int groupHead = source;
1005  while (items.at(groupHead)->groupType() == groupType) {
1006  groupHead--;
1007  // beginning of the list move it there
1008  if (groupHead == 0) {
1009  break;
1010  }
1011  }
1012  target = groupHead;
1013  }
1014  return target;
1015 }
1016 
1017 void KFilePlacesModelPrivate::reloadAndSignal()
1018 {
1019  bookmarkManager->emitChanged(bookmarkManager->root()); // ... we'll get relisted anyway
1020 }
1021 
1022 Qt::DropActions KFilePlacesModel::supportedDropActions() const
1023 {
1024  return Qt::ActionMask;
1025 }
1026 
1027 Qt::ItemFlags KFilePlacesModel::flags(const QModelIndex &index) const
1028 {
1029  Qt::ItemFlags res;
1030 
1031  if (index.isValid()) {
1033  }
1034 
1035  if (!index.isValid()) {
1036  res |= Qt::ItemIsDropEnabled;
1037  }
1038 
1039  return res;
1040 }
1041 
1042 static QString _k_internalMimetype(const KFilePlacesModel *const self)
1043 {
1044  return QStringLiteral("application/x-kfileplacesmodel-") + QString::number(reinterpret_cast<qptrdiff>(self));
1045 }
1046 
1047 QStringList KFilePlacesModel::mimeTypes() const
1048 {
1050 
1051  types << _k_internalMimetype(this) << QStringLiteral("text/uri-list");
1052 
1053  return types;
1054 }
1055 
1056 QMimeData *KFilePlacesModel::mimeData(const QModelIndexList &indexes) const
1057 {
1058  QList<QUrl> urls;
1060 
1062 
1063  for (const QModelIndex &index : std::as_const(indexes)) {
1064  QUrl itemUrl = url(index);
1065  if (itemUrl.isValid()) {
1066  urls << itemUrl;
1067  }
1068  stream << index.row();
1069  }
1070 
1071  QMimeData *mimeData = new QMimeData();
1072 
1073  if (!urls.isEmpty()) {
1074  mimeData->setUrls(urls);
1075  }
1076 
1077  mimeData->setData(_k_internalMimetype(this), itemData);
1078 
1079  return mimeData;
1080 }
1081 
1082 bool KFilePlacesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
1083 {
1084  if (action == Qt::IgnoreAction) {
1085  return true;
1086  }
1087 
1088  if (column > 0) {
1089  return false;
1090  }
1091 
1092  if (row == -1 && parent.isValid()) {
1093  return false; // Don't allow to move an item onto another one,
1094  // too easy for the user to mess something up
1095  // If we really really want to allow copying files this way,
1096  // let's do it in the views to get the good old drop menu
1097  }
1098 
1099  if (data->hasFormat(QStringLiteral("application/x-kfileplacesmodel-ignore"))) {
1100  return false;
1101  }
1102 
1103  if (data->hasFormat(_k_internalMimetype(this))) {
1104  // The operation is an internal move
1105  QByteArray itemData = data->data(_k_internalMimetype(this));
1107  int itemRow;
1108 
1109  stream >> itemRow;
1110 
1111  if (!movePlace(itemRow, row)) {
1112  return false;
1113  }
1114 
1115  } else if (data->hasFormat(QStringLiteral("text/uri-list"))) {
1116  // The operation is an add
1117 
1118  QMimeDatabase db;
1119  KBookmark afterBookmark;
1120 
1121  if (row == -1) {
1122  // The dropped item is moved or added to the last position
1123 
1124  KFilePlacesItem *lastItem = d->items.last();
1125  afterBookmark = lastItem->bookmark();
1126 
1127  } else {
1128  // The dropped item is moved or added before position 'row', ie after position 'row-1'
1129 
1130  if (row > 0) {
1131  KFilePlacesItem *afterItem = d->items[row - 1];
1132  afterBookmark = afterItem->bookmark();
1133  }
1134  }
1135 
1137 
1138  KBookmarkGroup group = d->bookmarkManager->root();
1139 
1140  for (const QUrl &url : urls) {
1141  KIO::StatJob *job = KIO::statDetails(url, KIO::StatJob::SourceSide, KIO::StatBasic);
1142 
1143  if (!job->exec()) {
1144  Q_EMIT errorMessage(i18nc("Placeholder is error message", "Could not add to the Places panel: %1", job->errorString()));
1145  continue;
1146  }
1147 
1148  KFileItem item(job->statResult(), url, true /*delayed mime types*/);
1149 
1150  if (!item.isDir()) {
1151  Q_EMIT errorMessage(i18n("Only folders can be added to the Places panel."));
1152  continue;
1153  }
1154 
1155  KBookmark bookmark = KFilePlacesItem::createBookmark(d->bookmarkManager, item.text(), url, KIO::iconNameForUrl(url));
1156 
1157  group.moveBookmark(bookmark, afterBookmark);
1158  afterBookmark = bookmark;
1159  }
1160 
1161  } else {
1162  // Oops, shouldn't happen thanks to mimeTypes()
1163  qWarning() << ": received wrong mimedata, " << data->formats();
1164  return false;
1165  }
1166 
1167  refresh();
1168 
1169  return true;
1170 }
1171 
1172 void KFilePlacesModel::refresh() const
1173 {
1174  d->reloadAndSignal();
1175 }
1176 
1178 {
1179  QUrl newUrl = url;
1180  if (url.scheme() == QLatin1String("timeline")) {
1181  newUrl = createTimelineUrl(url);
1182  } else if (url.scheme() == QLatin1String("search")) {
1183  newUrl = createSearchUrl(url);
1184  }
1185 
1186  return newUrl;
1187 }
1188 
1189 void KFilePlacesModel::addPlace(const QString &text, const QUrl &url, const QString &iconName, const QString &appName)
1190 {
1191  addPlace(text, url, iconName, appName, QModelIndex());
1192 }
1193 
1194 void KFilePlacesModel::addPlace(const QString &text, const QUrl &url, const QString &iconName, const QString &appName, const QModelIndex &after)
1195 {
1196  KBookmark bookmark = KFilePlacesItem::createBookmark(d->bookmarkManager, text, url, iconName);
1197 
1198  if (!appName.isEmpty()) {
1199  bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), appName);
1200  }
1201 
1202  if (after.isValid()) {
1203  KFilePlacesItem *item = static_cast<KFilePlacesItem *>(after.internalPointer());
1204  d->bookmarkManager->root().moveBookmark(bookmark, item->bookmark());
1205  }
1206 
1207  refresh();
1208 }
1209 
1210 void KFilePlacesModel::editPlace(const QModelIndex &index, const QString &text, const QUrl &url, const QString &iconName, const QString &appName)
1211 {
1212  if (!index.isValid()) {
1213  return;
1214  }
1215 
1216  KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
1217 
1218  if (item->isDevice()) {
1219  return;
1220  }
1221 
1222  KBookmark bookmark = item->bookmark();
1223 
1224  if (bookmark.isNull()) {
1225  return;
1226  }
1227 
1228  QVector<int> changedRoles;
1229  bool changed = false;
1230 
1231  if (text != bookmark.fullText()) {
1232  bookmark.setFullText(text);
1233  changed = true;
1234  changedRoles << Qt::DisplayRole;
1235  }
1236 
1237  if (url != bookmark.url()) {
1238  bookmark.setUrl(url);
1239  changed = true;
1240  changedRoles << KFilePlacesModel::UrlRole;
1241  }
1242 
1243  if (iconName != bookmark.icon()) {
1244  bookmark.setIcon(iconName);
1245  changed = true;
1246  changedRoles << Qt::DecorationRole;
1247  }
1248 
1249  const QString onlyInApp = bookmark.metaDataItem(QStringLiteral("OnlyInApp"));
1250  if (appName != onlyInApp) {
1251  bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), appName);
1252  changed = true;
1253  }
1254 
1255  if (changed) {
1256  refresh();
1257  Q_EMIT dataChanged(index, index, changedRoles);
1258  }
1259 }
1260 
1261 void KFilePlacesModel::removePlace(const QModelIndex &index) const
1262 {
1263  if (!index.isValid()) {
1264  return;
1265  }
1266 
1267  KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
1268 
1269  if (item->isDevice()) {
1270  return;
1271  }
1272 
1273  KBookmark bookmark = item->bookmark();
1274 
1275  if (bookmark.isNull()) {
1276  return;
1277  }
1278 
1279  d->bookmarkManager->root().deleteBookmark(bookmark);
1280  refresh();
1281 }
1282 
1283 void KFilePlacesModel::setPlaceHidden(const QModelIndex &index, bool hidden)
1284 {
1285  if (!index.isValid()) {
1286  return;
1287  }
1288 
1289  KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
1290 
1291  if (item->bookmark().isNull() || item->isHidden() == hidden) {
1292  return;
1293  }
1294 
1295  const bool groupHidden = isGroupHidden(item->groupType());
1296  const bool hidingChildOnShownParent = hidden && !groupHidden;
1297  const bool showingChildOnShownParent = !hidden && !groupHidden;
1298 
1299  if (hidingChildOnShownParent || showingChildOnShownParent) {
1300  item->setHidden(hidden);
1301 
1302  d->reloadAndSignal();
1304  }
1305 }
1306 
1307 void KFilePlacesModel::setGroupHidden(const GroupType type, bool hidden)
1308 {
1309  if (isGroupHidden(type) == hidden) {
1310  return;
1311  }
1312 
1313  d->bookmarkManager->root().setMetaDataItem(stateNameForGroupType(type), (hidden ? QStringLiteral("true") : QStringLiteral("false")));
1314  d->reloadAndSignal();
1315  Q_EMIT groupHiddenChanged(type, hidden);
1316 }
1317 
1318 bool KFilePlacesModel::movePlace(int itemRow, int row)
1319 {
1320  KBookmark afterBookmark;
1321 
1322  if ((itemRow < 0) || (itemRow >= d->items.count())) {
1323  return false;
1324  }
1325 
1326  if (row >= d->items.count()) {
1327  row = -1;
1328  }
1329 
1330  if (row == -1) {
1331  // The dropped item is moved or added to the last position
1332 
1333  KFilePlacesItem *lastItem = d->items.last();
1334  afterBookmark = lastItem->bookmark();
1335 
1336  } else {
1337  // The dropped item is moved or added before position 'row', ie after position 'row-1'
1338 
1339  if (row > 0) {
1340  KFilePlacesItem *afterItem = d->items[row - 1];
1341  afterBookmark = afterItem->bookmark();
1342  }
1343  }
1344 
1345  KFilePlacesItem *item = d->items[itemRow];
1346  KBookmark bookmark = item->bookmark();
1347 
1348  int destRow = row == -1 ? d->items.count() : row;
1349 
1350  // avoid move item away from its group
1351  destRow = d->findNearestPosition(itemRow, destRow);
1352 
1353  // The item is not moved when the drop indicator is on either item edge
1354  if (itemRow == destRow || itemRow + 1 == destRow) {
1355  return false;
1356  }
1357 
1358  beginMoveRows(QModelIndex(), itemRow, itemRow, QModelIndex(), destRow);
1359  d->bookmarkManager->root().moveBookmark(bookmark, afterBookmark);
1360  // Move item ourselves so that reloadBookmarks() does not consider
1361  // the move as a remove + insert.
1362  //
1363  // 2nd argument of QList::move() expects the final destination index,
1364  // but 'row' is the value of the destination index before the moved
1365  // item has been removed from its original position. That is why we
1366  // adjust if necessary.
1367  d->items.move(itemRow, itemRow < destRow ? (destRow - 1) : destRow);
1368  endMoveRows();
1369 
1370  return true;
1371 }
1372 
1374 {
1375  int rows = rowCount();
1376  int hidden = 0;
1377 
1378  for (int i = 0; i < rows; ++i) {
1379  if (isHidden(index(i, 0))) {
1380  hidden++;
1381  }
1382  }
1383 
1384  return hidden;
1385 }
1386 
1388 {
1390 
1391  QAction *action = nullptr;
1392 
1393  if (device.is<Solid::StorageAccess>() && device.as<Solid::StorageAccess>()->isAccessible()) {
1394  Solid::StorageDrive *drive = device.as<Solid::StorageDrive>();
1395 
1396  if (drive == nullptr) {
1397  drive = device.parent().as<Solid::StorageDrive>();
1398  }
1399 
1400  const bool teardownInProgress = deviceAccessibility(index) == KFilePlacesModel::TeardownInProgress;
1401 
1402  bool hotpluggable = false;
1403  bool removable = false;
1404 
1405  if (drive != nullptr) {
1406  hotpluggable = drive->isHotpluggable();
1407  removable = drive->isRemovable();
1408  }
1409 
1410  QString iconName;
1411  QString text;
1412 
1413  if (device.is<Solid::OpticalDisc>()) {
1414  if (teardownInProgress) {
1415  text = i18nc("@action:inmenu", "Releasing…");
1416  } else {
1417  text = i18nc("@action:inmenu", "&Release");
1418  }
1419  } else if (removable || hotpluggable) {
1420  if (teardownInProgress) {
1421  text = i18nc("@action:inmenu", "Safely Removing…");
1422  } else {
1423  text = i18nc("@action:inmenu", "&Safely Remove");
1424  }
1425  iconName = QStringLiteral("media-eject");
1426  } else {
1427  if (teardownInProgress) {
1428  text = i18nc("@action:inmenu", "Unmounting…");
1429  } else {
1430  text = i18nc("@action:inmenu", "&Unmount");
1431  }
1432  iconName = QStringLiteral("media-eject");
1433  }
1434 
1435  if (!iconName.isEmpty()) {
1436  action = new QAction(QIcon::fromTheme(iconName), text, nullptr);
1437  } else {
1438  action = new QAction(text, nullptr);
1439  }
1440 
1441  if (teardownInProgress) {
1442  action->setEnabled(false);
1443  }
1444  }
1445 
1446  return action;
1447 }
1448 
1450 {
1452 
1453  if (device.is<Solid::OpticalDisc>()) {
1454  QString text = i18nc("@action:inmenu", "&Eject");
1455 
1456  return new QAction(QIcon::fromTheme(QStringLiteral("media-eject")), text, nullptr);
1457  }
1458 
1459  return nullptr;
1460 }
1461 
1463 {
1466 
1467  if (access != nullptr) {
1468  d->teardownInProgress[access] = index;
1469 
1470  const QString filePath = access->filePath();
1471  connect(access, &Solid::StorageAccess::teardownDone, this, [this, access, filePath](Solid::ErrorType error, QVariant errorData) {
1472  d->storageTeardownDone(filePath, error, errorData, access);
1473  });
1474 
1475  access->teardown();
1476  }
1477 }
1478 
1480 {
1482 
1483  Solid::OpticalDrive *drive = device.parent().as<Solid::OpticalDrive>();
1484 
1485  if (drive != nullptr) {
1486  d->teardownInProgress[drive] = index;
1487 
1488  QString filePath;
1490  if (access) {
1491  filePath = access->filePath();
1492  }
1493 
1494  connect(drive, &Solid::OpticalDrive::ejectDone, this, [this, filePath, drive](Solid::ErrorType error, QVariant errorData) {
1495  d->storageTeardownDone(filePath, error, errorData, drive);
1496  });
1497 
1498  drive->eject();
1499  } else {
1501  QString message = i18n("The device '%1' is not a disk and cannot be ejected.", label);
1503  }
1504 }
1505 
1507 {
1509 
1510  if (device.is<Solid::StorageAccess>() && !d->setupInProgress.contains(device.as<Solid::StorageAccess>())
1511  && !device.as<Solid::StorageAccess>()->isAccessible()) {
1513 
1514  d->setupInProgress[access] = index;
1515 
1516  connect(access, &Solid::StorageAccess::setupDone, this, [this, access](Solid::ErrorType error, QVariant errorData) {
1517  d->storageSetupDone(error, errorData, access);
1518  });
1519 
1520  access->setup();
1521  }
1522 }
1523 
1524 void KFilePlacesModelPrivate::storageSetupDone(Solid::ErrorType error, const QVariant &errorData, Solid::StorageAccess *sender)
1525 {
1526  QPersistentModelIndex index = setupInProgress.take(sender);
1527 
1528  if (!index.isValid()) {
1529  return;
1530  }
1531 
1532  if (!error) {
1533  Q_EMIT q->setupDone(index, true);
1534  } else {
1535  if (errorData.isValid()) {
1536  Q_EMIT q->errorMessage(i18n("An error occurred while accessing '%1', the system responded: %2", q->text(index), errorData.toString()));
1537  } else {
1538  Q_EMIT q->errorMessage(i18n("An error occurred while accessing '%1'", q->text(index)));
1539  }
1540  Q_EMIT q->setupDone(index, false);
1541  }
1542 }
1543 
1544 void KFilePlacesModelPrivate::storageTeardownDone(const QString &filePath, Solid::ErrorType error, const QVariant &errorData, QObject *sender)
1545 {
1546  QPersistentModelIndex index = teardownInProgress.take(sender);
1547  if (!index.isValid()) {
1548  return;
1549  }
1550 
1551  if (error == Solid::ErrorType::DeviceBusy && !filePath.isEmpty()) {
1552  auto *listOpenFilesJob = new KListOpenFilesJob(filePath);
1553  QObject::connect(listOpenFilesJob, &KIO::Job::result, q, [this, index, error, errorData, listOpenFilesJob]() {
1554  const auto blockingProcesses = listOpenFilesJob->processInfoList();
1555 
1556  QStringList blockingApps;
1557  blockingApps.reserve(blockingProcesses.count());
1558  for (const auto &process : blockingProcesses) {
1559  blockingApps << process.name();
1560  }
1561 
1562  Q_EMIT q->teardownDone(index, error, errorData);
1563  if (blockingProcesses.isEmpty()) {
1564  Q_EMIT q->errorMessage(i18n("One or more files on this device are open within an application."));
1565  } else {
1566  blockingApps.removeDuplicates();
1567  Q_EMIT q->errorMessage(xi18np("One or more files on this device are opened in application <application>\"%2\"</application>.",
1568  "One or more files on this device are opened in following applications: <application>%2</application>.",
1569  blockingApps.count(),
1570  blockingApps.join(i18nc("separator in list of apps blocking device unmount", ", "))));
1571  }
1572  });
1573  listOpenFilesJob->start();
1574  return;
1575  }
1576 
1577  Q_EMIT q->teardownDone(index, error, errorData);
1578  if (error != Solid::ErrorType::NoError && error != Solid::ErrorType::UserCanceled) {
1579  Q_EMIT q->errorMessage(errorData.toString());
1580  }
1581 }
1582 
1584 {
1585  d->supportedSchemes = schemes;
1586  d->reloadBookmarks();
1588 }
1589 
1590 QStringList KFilePlacesModel::supportedSchemes() const
1591 {
1592  return d->supportedSchemes;
1593 }
1594 
1595 #include "moc_kfileplacesmodel.cpp"
@ IconNameRole
roleName is "iconName".
void setIcon(const QString &icon)
Q_INVOKABLE int hiddenCount() const
void deviceRemoved(const QString &udi)
QString readEntry(const char *key, const char *aDefault=nullptr) const
QString icon() const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
bool isParentOf(const QUrl &childUrl) const const
@ UnknownType
Unknown GroupType.
int month() const const
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Get the number of rows for a model index.
@ FixedDeviceRole
Whether the place is a fixed device (neither hotpluggable nor removable).
bool isValid() const const
@ GroupHiddenRole
roleName is "isGroupHidden".
bool is() const
DecorationRole
Q_INVOKABLE bool isDevice(const QModelIndex &index) const
KBookmark bookmarkForUrl(const QUrl &searchUrl) const
static KBookmarkManager * managerForExternalFile(const QString &bookmarksFile)
@ CapacityBarRecommendedRole
Whether the place should have its free space displayed in a capacity bar.
QString number(int n, int base)
@ RecentlySavedType
"Recent" section
void deviceAdded(const QString &udi)
QUrl toUrl() const const
void setData(const QString &mimeType, const QByteArray &data)
void * internalPointer() const const
void setUrls(const QList< QUrl > &urls)
Q_INVOKABLE QAction * ejectActionForIndex(const QModelIndex &index) const
Q_EMITQ_EMIT
QVariant data(const QModelIndex &index, int role) const override
Get a visible data based on Qt role for the given index.
void result(KJob *job)
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
Get the children model index for the given row and column.
QString scheme() const const
QVector::iterator begin()
Q_INVOKABLE QIcon icon(const QModelIndex &index) const
Q_INVOKABLE QString text(const QModelIndex &index) const
@ SetupNeededRole
roleName is "isSetupNeeded".
int count(const T &value) const const
T value() const const
@ UrlRole
roleName is "url".
void errorMessage(const QString &message)
message An error message explaining what went wrong.
@ EjectAllowedRole
roleName is "isEjectAllowed".
int year() const const
bool isRemovable() const
void changed(const QString &groupAddress, const QString &caller)
QIcon fromTheme(const QString &name)
QString metaDataItem(const QString &key) const
Q_INVOKABLE void refresh() const
Reload bookmark information.
QString query(QUrl::ComponentFormattingOptions options) const const
void itemsAdded(const QUrl &directoryUrl, const KFileItemList &items)
Signal that new items were found during directory listing.
QString writableLocation(QStandardPaths::StandardLocation type)
KBookmark first() const
PHONON_EXPORT Notifier * notifier()
QString homePath()
void setUrl(const QUrl &url)
KBookmark bookmarkForIndex(const QModelIndex &index) const
void setupDone(Solid::ErrorType error, QVariant errorData, const QString &udi)
Q_INVOKABLE bool isHidden(const QModelIndex &index) const
bool isValid() const const
bool contains(const T &value) const const
void setSupportedSchemes(const QStringList &schemes)
Set the URL schemes that the file widget should allow navigating to.
bool exists() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
Q_INVOKABLE GroupType groupType(const QModelIndex &index) const
bool matches(const QUrl &url, QUrl::FormattingOptions options) const const
QString address() const
void setFullText(const QString &fullText)
@ PlacesType
"Places" section
void setScheme(const QString &scheme)
@ TeardownOverlayRecommendedRole
roleName is "isTeardownOverlayRecommended".
QStringList types(Mode mode=Writing)
static bool isKnownProtocol(const QUrl &url)
Returns whether a protocol is installed that is able to handle url.
void reserve(int alloc)
void deleteBookmark(const KBookmark &bk)
bool isValid() const const
bool beginMoveRows(const QModelIndex &sourceParent, int sourceFirst, int sourceLast, const QModelIndex &destinationParent, int destinationChild)
KIOCORE_EXPORT QString iconNameForUrl(const QUrl &url)
Return the icon name for a URL.
Definition: global.cpp:212
QDate addMonths(int nmonths) const const
int size() const const
virtual QHash< int, QByteArray > roleNames() const const
QModelIndex createIndex(int row, int column, void *ptr) const const
@ TagsType
"Tags" section.
QString toString(QUrl::FormattingOptions options) const const
KBookmark next(const KBookmark &current) const
typedef ItemFlags
QString i18n(const char *text, const TYPE &arg...)
void ejectDone(Solid::ErrorType error, QVariant errorData, const QString &udi)
PreferLocalFile
@ RemovableDevicesType
"Removable Devices" section
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles)
Helper class for the kiojob used to list and update a directory.
int indexOf(const T &value, int from) const const
virtual QMap< int, QVariant > itemData(const QModelIndex &index) const const
bool isEmpty() const const
Q_INVOKABLE void removePlace(const QModelIndex &index) const
Deletes the place with index index from the model.
QModelIndex closestItem(const QUrl &url) const
Returns the closest item for the URL url.
QUrl fromLocalFile(const QString &localFile)
DevIface * as()
Q_INVOKABLE QModelIndexList groupIndexes(const GroupType type) const
QCA_EXPORT QString appName()
int length() const const
QString toDisplayString(QUrl::FormattingOptions options) const const
QCoreApplication * instance()
const T & at(int i) const const
bool isNull() const
int toInt(bool *ok, int base) const const
@ DeviceAccessibilityRole
roleName is "deviceAccessibility".
void groupHiddenChanged(KFilePlacesModel::GroupType group, bool hidden)
Emitted whenever the visibility of the group group changes.
bool isEmpty() const const
@ GroupRole
The name of the group, for example "Remote" or "Devices".
Q_INVOKABLE QUrl url(const QModelIndex &index) const
Q_INVOKABLE void addPlace(const QString &text, const QUrl &url, const QString &iconName=QString(), const QString &appName=QString())
Adds a new place to the model.
Q_INVOKABLE void requestTeardown(const QModelIndex &index)
Unmounts the place at index index by triggering the teardown functionality of its Solid device.
QUrl url() const
Solid::Device deviceForIndex(const QModelIndex &index) const
Q_INVOKABLE bool isTeardownOverlayRecommended(const QModelIndex &index) const
QDate currentDate()
Q_INVOKABLE bool isGroupHidden(const GroupType type) const
static QUrl convertedUrl(const QUrl &url)
Converts the URL, which contains "virtual" URLs for system-items like "timeline:/lastmonth" into a Qu...
QDate addDays(qint64 ndays) const const
static QList< Device > listFromQuery(const Predicate &predicate, const QString &parentUdi=QString())
KSharedConfigPtr config()
GroupType
Describes the available group types used in this model.
bool isValid() const const
@ StatBasic
Filename, access, type, size, linkdest.
Definition: global.h:369
Q_INVOKABLE void editPlace(const QModelIndex &index, const QString &text, const QUrl &url, const QString &iconName=QString(), const QString &appName=QString())
Edits the place with index index.
Q_INVOKABLE void requestEject(const QModelIndex &index)
Ejects the place at index index by triggering the eject functionality of its Solid device.
const UDSEntry & statResult() const
Result of the stat operation.
Definition: statjob.cpp:115
void insert(int i, const T &value)
bool toBool() const const
Q_INVOKABLE bool isTeardownAllowed(const QModelIndex &index) const
QString & replace(int position, int n, QChar after)
Q_INVOKABLE void setPlaceHidden(const QModelIndex &index, bool hidden)
Changes the visibility of the place with index index, but only if the place is not inside an hidden g...
int row() const const
int removeAll(const T &t)
static Predicate fromString(const QString &predicate)
QList< QUrl > groupUrlList() const
QString label(StandardShortcut id)
QVector::iterator end()
typedef DropActions
@ RemoteType
"Remote" section
@ HiddenRole
roleName is "isHidden".
Q_INVOKABLE QAction * teardownActionForIndex(const QModelIndex &index) const
bool moveBookmark(const KBookmark &bookmark, const KBookmark &after)
Device parent() const
Q_INVOKABLE bool isEjectAllowed(const QModelIndex &index) const
QString errorString() const override
Converts an error code and a non-i18n error message into an error message in the current language.
Definition: job_error.cpp:22
QString fragment(QUrl::ComponentFormattingOptions options) const const
Q_INVOKABLE void setGroupHidden(const GroupType type, bool hidden)
Changes the visibility of the group with type type.
QString fullText() const
QList::iterator erase(QList::iterator pos)
void setEnabled(bool)
Q_INVOKABLE bool movePlace(int itemRow, int row)
Move place at itemRow to a position before row.
Q_INVOKABLE void requestSetup(const QModelIndex &index)
Mounts the place at index index by triggering the setup functionality of its Solid device.
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
bool isAccessible() const
Q_INVOKABLE bool setupNeeded(const QModelIndex &index) const
Q_INVOKABLE KFilePlacesModel::DeviceAccessibility deviceAccessibility(const QModelIndex &index) const
QString fromLatin1(const char *str, int size)
QString name(StandardShortcut id)
KIOCORE_EXPORT StatJob * statDetails(const QUrl &url, KIO::StatJob::StatSide side, KIO::StatDetails details=KIO::StatDefaultDetails, JobFlags flags=DefaultFlags)
Find all details for one file or directory.
Definition: statjob.cpp:263
bool isHotpluggable() const
void clear()
QVector::iterator erase(QVector::iterator begin, QVector::iterator end)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QList::iterator begin()
void teardownDone(Solid::ErrorType error, QVariant errorData, const QString &udi)
KCOREADDONS_EXPORT QList< QUrl > urlsFromMimeData(const QMimeData *mimeData, DecodeOptions decodeOptions=PreferKdeUrls, MetaDataMap *metaData=nullptr)
QHash< int, QByteArray > roleNames() const override
Reimplemented from QAbstractItemModel.
void supportedSchemesChanged()
Emitted whenever the list of supported schemes has been changed.
void setMetaDataItem(const QString &key, const QString &value, MetaDataOverwriteMode mode=OverwriteMetaData)
int columnCount(const QModelIndex &parent=QModelIndex()) const override
Get the number of columns for a model index.
@ SearchForType
"Search for" section
QList::iterator end()
void itemsDeleted(const KFileItemList &items)
Signal that items have been deleted.
QObject * parent() const const
void bookmarksChanged(QString groupAddress)
QString message
bool exec()
QString & append(QChar ch)
int access(const QString &path, int mode)
@ TeardownAllowedRole
roleName is "isTeardownAllowed".
@ DevicesType
"Devices" section
QString toString() const const
int day() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sun Feb 5 2023 04:00:51 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.