KIO

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

KDE's Doxygen guidelines are available online.