File indexing completed on 2023-09-24 04:08:50
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org> 0004 SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-only 0007 */ 0008 0009 #include "kfileplacesmodel.h" 0010 #include "kfileplacesitem_p.h" 0011 0012 #ifdef _WIN32_WCE 0013 #include "WinBase.h" 0014 #include "Windows.h" 0015 #endif 0016 0017 #include <KCoreDirLister> 0018 #include <KLazyLocalizedString> 0019 #include <KListOpenFilesJob> 0020 #include <KLocalizedString> 0021 #include <kfileitem.h> 0022 #include <kio/statjob.h> 0023 #include <kprotocolinfo.h> 0024 0025 #include <KBookmarkManager> 0026 #include <KConfig> 0027 #include <KConfigGroup> 0028 #include <KUrlMimeData> 0029 0030 #include <solid/devicenotifier.h> 0031 #include <solid/opticaldisc.h> 0032 #include <solid/opticaldrive.h> 0033 #include <solid/portablemediaplayer.h> 0034 #include <solid/predicate.h> 0035 #include <solid/storageaccess.h> 0036 #include <solid/storagedrive.h> 0037 #include <solid/storagevolume.h> 0038 0039 #include <QAction> 0040 #include <QCoreApplication> 0041 #include <QDebug> 0042 #include <QDir> 0043 #include <QFile> 0044 #include <QMimeData> 0045 #include <QMimeDatabase> 0046 #include <QStandardPaths> 0047 #include <QTimer> 0048 0049 namespace 0050 { 0051 QString stateNameForGroupType(KFilePlacesModel::GroupType type) 0052 { 0053 switch (type) { 0054 case KFilePlacesModel::PlacesType: 0055 return QStringLiteral("GroupState-Places-IsHidden"); 0056 case KFilePlacesModel::RemoteType: 0057 return QStringLiteral("GroupState-Remote-IsHidden"); 0058 case KFilePlacesModel::RecentlySavedType: 0059 return QStringLiteral("GroupState-RecentlySaved-IsHidden"); 0060 case KFilePlacesModel::SearchForType: 0061 return QStringLiteral("GroupState-SearchFor-IsHidden"); 0062 case KFilePlacesModel::DevicesType: 0063 return QStringLiteral("GroupState-Devices-IsHidden"); 0064 case KFilePlacesModel::RemovableDevicesType: 0065 return QStringLiteral("GroupState-RemovableDevices-IsHidden"); 0066 case KFilePlacesModel::TagsType: 0067 return QStringLiteral("GroupState-Tags-IsHidden"); 0068 default: 0069 Q_UNREACHABLE(); 0070 } 0071 } 0072 0073 static bool isFileIndexingEnabled() 0074 { 0075 KConfig config(QStringLiteral("baloofilerc")); 0076 KConfigGroup basicSettings = config.group("Basic Settings"); 0077 return basicSettings.readEntry("Indexing-Enabled", true); 0078 } 0079 0080 static QString timelineDateString(int year, int month, int day = 0) 0081 { 0082 const QString dateFormat = QStringLiteral("%1-%2"); 0083 0084 QString date = dateFormat.arg(year).arg(month, 2, 10, QLatin1Char('0')); 0085 if (day > 0) { 0086 date += QStringLiteral("-%1").arg(day, 2, 10, QLatin1Char('0')); 0087 } 0088 return date; 0089 } 0090 0091 static QUrl createTimelineUrl(const QUrl &url) 0092 { 0093 // based on dolphin urls 0094 const QString timelinePrefix = QLatin1String("timeline:") + QLatin1Char('/'); 0095 QUrl timelineUrl; 0096 0097 const QString path = url.toDisplayString(QUrl::PreferLocalFile); 0098 if (path.endsWith(QLatin1String("/yesterday"))) { 0099 const QDate date = QDate::currentDate().addDays(-1); 0100 const int year = date.year(); 0101 const int month = date.month(); 0102 const int day = date.day(); 0103 0104 timelineUrl = QUrl(timelinePrefix + timelineDateString(year, month) + QLatin1Char('/') + timelineDateString(year, month, day)); 0105 } else if (path.endsWith(QLatin1String("/thismonth"))) { 0106 const QDate date = QDate::currentDate(); 0107 timelineUrl = QUrl(timelinePrefix + timelineDateString(date.year(), date.month())); 0108 } else if (path.endsWith(QLatin1String("/lastmonth"))) { 0109 const QDate date = QDate::currentDate().addMonths(-1); 0110 timelineUrl = QUrl(timelinePrefix + timelineDateString(date.year(), date.month())); 0111 } else { 0112 Q_ASSERT(path.endsWith(QLatin1String("/today"))); 0113 timelineUrl = url; 0114 } 0115 0116 return timelineUrl; 0117 } 0118 0119 static QUrl createSearchUrl(const QUrl &url) 0120 { 0121 QUrl searchUrl = url; 0122 0123 const QString path = url.toDisplayString(QUrl::PreferLocalFile); 0124 0125 const QStringList validSearchPaths = {QStringLiteral("/documents"), QStringLiteral("/images"), QStringLiteral("/audio"), QStringLiteral("/videos")}; 0126 0127 for (const QString &validPath : validSearchPaths) { 0128 if (path.endsWith(validPath)) { 0129 searchUrl.setScheme(QStringLiteral("baloosearch")); 0130 return searchUrl; 0131 } 0132 } 0133 0134 qWarning() << "Invalid search url:" << url; 0135 0136 return searchUrl; 0137 } 0138 } 0139 0140 class KFilePlacesModelPrivate 0141 { 0142 public: 0143 explicit KFilePlacesModelPrivate(KFilePlacesModel *qq) 0144 : q(qq) 0145 , bookmarkManager(nullptr) 0146 , fileIndexingEnabled(isFileIndexingEnabled()) 0147 , tags() 0148 , tagsLister(new KCoreDirLister(q)) 0149 { 0150 if (KProtocolInfo::isKnownProtocol(QStringLiteral("tags"))) { 0151 QObject::connect(tagsLister, &KCoreDirLister::itemsAdded, q, [this](const QUrl &, const KFileItemList &items) { 0152 if (tags.isEmpty()) { 0153 QList<QUrl> existingBookmarks; 0154 0155 KBookmarkGroup root = bookmarkManager->root(); 0156 KBookmark bookmark = root.first(); 0157 0158 while (!bookmark.isNull()) { 0159 existingBookmarks.append(bookmark.url()); 0160 bookmark = root.next(bookmark); 0161 } 0162 0163 if (!existingBookmarks.contains(QUrl(tagsUrlBase))) { 0164 KBookmark alltags = KFilePlacesItem::createSystemBookmark(bookmarkManager, 0165 kli18nc("KFile System Bookmarks", "All tags").untranslatedText(), 0166 QUrl(tagsUrlBase), 0167 QStringLiteral("tag")); 0168 } 0169 } 0170 0171 for (const KFileItem &item : items) { 0172 const QString name = item.name(); 0173 0174 if (!tags.contains(name)) { 0175 tags.append(name); 0176 } 0177 } 0178 reloadBookmarks(); 0179 }); 0180 0181 QObject::connect(tagsLister, &KCoreDirLister::itemsDeleted, q, [this](const KFileItemList &items) { 0182 for (const KFileItem &item : items) { 0183 tags.removeAll(item.name()); 0184 } 0185 reloadBookmarks(); 0186 }); 0187 0188 tagsLister->openUrl(QUrl(tagsUrlBase), KCoreDirLister::OpenUrlFlag::Reload); 0189 } 0190 } 0191 0192 KFilePlacesModel *const q; 0193 0194 QList<KFilePlacesItem *> items; 0195 QVector<QString> availableDevices; 0196 QMap<QObject *, QPersistentModelIndex> setupInProgress; 0197 QMap<QObject *, QPersistentModelIndex> teardownInProgress; 0198 QStringList supportedSchemes; 0199 0200 Solid::Predicate predicate; 0201 KBookmarkManager *bookmarkManager; 0202 0203 const bool fileIndexingEnabled; 0204 0205 QString alternativeApplicationName; 0206 0207 void reloadAndSignal(); 0208 QList<KFilePlacesItem *> loadBookmarkList(); 0209 int findNearestPosition(int source, int target); 0210 0211 QVector<QString> tags; 0212 const QString tagsUrlBase = QStringLiteral("tags:/"); 0213 KCoreDirLister *tagsLister; 0214 0215 void initDeviceList(); 0216 void deviceAdded(const QString &udi); 0217 void deviceRemoved(const QString &udi); 0218 void itemChanged(const QString &udi, const QVector<int> &roles); 0219 void reloadBookmarks(); 0220 void storageSetupDone(Solid::ErrorType error, const QVariant &errorData, Solid::StorageAccess *sender); 0221 void storageTeardownDone(const QString &filePath, Solid::ErrorType error, const QVariant &errorData, QObject *sender); 0222 0223 private: 0224 bool isBalooUrl(const QUrl &url) const; 0225 }; 0226 0227 KBookmark KFilePlacesModel::bookmarkForUrl(const QUrl &searchUrl) const 0228 { 0229 KBookmarkGroup root = d->bookmarkManager->root(); 0230 KBookmark current = root.first(); 0231 while (!current.isNull()) { 0232 if (current.url() == searchUrl) { 0233 return current; 0234 } 0235 current = root.next(current); 0236 } 0237 return KBookmark(); 0238 } 0239 0240 static inline QString versionKey() 0241 { 0242 return QStringLiteral("kde_places_version"); 0243 } 0244 0245 KFilePlacesModel::KFilePlacesModel(const QString &alternativeApplicationName, QObject *parent) 0246 : QAbstractItemModel(parent) 0247 , d(new KFilePlacesModelPrivate(this)) 0248 { 0249 const QString file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/user-places.xbel"); 0250 d->bookmarkManager = KBookmarkManager::managerForExternalFile(file); 0251 d->alternativeApplicationName = alternativeApplicationName; 0252 0253 // Let's put some places in there if it's empty. 0254 KBookmarkGroup root = d->bookmarkManager->root(); 0255 0256 const auto setDefaultMetadataItemForGroup = [&root](KFilePlacesModel::GroupType type) { 0257 root.setMetaDataItem(stateNameForGroupType(type), QStringLiteral("false")); 0258 }; 0259 0260 // Increase this version number and use the following logic to handle the update process for existing installations. 0261 static const int s_currentVersion = 4; 0262 0263 const bool newFile = root.first().isNull() || !QFile::exists(file); 0264 const int fileVersion = root.metaDataItem(versionKey()).toInt(); 0265 0266 if (newFile || fileVersion < s_currentVersion) { 0267 root.setMetaDataItem(versionKey(), QString::number(s_currentVersion)); 0268 0269 const QList<QUrl> seenUrls = root.groupUrlList(); 0270 0271 /* clang-format off */ 0272 auto createSystemBookmark = 0273 [this, &seenUrls](const char *untranslatedLabel, 0274 const QUrl &url, 0275 const QString &iconName, 0276 const KBookmark &after) { 0277 if (!seenUrls.contains(url)) { 0278 return KFilePlacesItem::createSystemBookmark(d->bookmarkManager, untranslatedLabel, url, iconName, after); 0279 } 0280 return KBookmark(); 0281 }; 0282 /* clang-format on */ 0283 0284 if (fileVersion < 2) { 0285 // NOTE: The context for these kli18nc calls has to be "KFile System Bookmarks". 0286 // The real i18nc call is made later, with this context, so the two must match. 0287 createSystemBookmark(kli18nc("KFile System Bookmarks", "Home").untranslatedText(), 0288 QUrl::fromLocalFile(QDir::homePath()), 0289 QStringLiteral("user-home"), 0290 KBookmark()); 0291 0292 // Some distros may not create various standard XDG folders by default 0293 // so check for their existence before adding bookmarks for them 0294 const QString desktopFolder = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); 0295 if (QDir(desktopFolder).exists()) { 0296 createSystemBookmark(kli18nc("KFile System Bookmarks", "Desktop").untranslatedText(), 0297 QUrl::fromLocalFile(desktopFolder), 0298 QStringLiteral("user-desktop"), 0299 KBookmark()); 0300 } 0301 const QString documentsFolder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); 0302 if (QDir(documentsFolder).exists()) { 0303 createSystemBookmark(kli18nc("KFile System Bookmarks", "Documents").untranslatedText(), 0304 QUrl::fromLocalFile(documentsFolder), 0305 QStringLiteral("folder-documents"), 0306 KBookmark()); 0307 } 0308 const QString downloadFolder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); 0309 if (QDir(downloadFolder).exists()) { 0310 createSystemBookmark(kli18nc("KFile System Bookmarks", "Downloads").untranslatedText(), 0311 QUrl::fromLocalFile(downloadFolder), 0312 QStringLiteral("folder-downloads"), 0313 KBookmark()); 0314 } 0315 createSystemBookmark(kli18nc("KFile System Bookmarks", "Network").untranslatedText(), 0316 QUrl(QStringLiteral("remote:/")), 0317 QStringLiteral("folder-network"), 0318 KBookmark()); 0319 0320 createSystemBookmark(kli18nc("KFile System Bookmarks", "Trash").untranslatedText(), 0321 QUrl(QStringLiteral("trash:/")), 0322 QStringLiteral("user-trash"), 0323 KBookmark()); 0324 } 0325 0326 if (!newFile && fileVersion < 3) { 0327 KBookmarkGroup rootGroup = d->bookmarkManager->root(); 0328 KBookmark bItem = rootGroup.first(); 0329 while (!bItem.isNull()) { 0330 KBookmark nextbItem = rootGroup.next(bItem); 0331 const bool isSystemItem = bItem.metaDataItem(QStringLiteral("isSystemItem")) == QLatin1String("true"); 0332 if (isSystemItem) { 0333 const QString text = bItem.fullText(); 0334 // Because of b8a4c2223453932202397d812a0c6b30c6186c70 we need to find the system bookmark named Audio Files 0335 // and rename it to Audio, otherwise users are getting untranslated strings 0336 if (text == QLatin1String("Audio Files")) { 0337 bItem.setFullText(QStringLiteral("Audio")); 0338 } else if (text == QLatin1String("Today")) { 0339 // Because of 19feef732085b444515da3f6c66f3352bbcb1824 we need to find the system bookmark named Today 0340 // and rename it to Modified Today, otherwise users are getting untranslated strings 0341 bItem.setFullText(QStringLiteral("Modified Today")); 0342 } else if (text == QLatin1String("Yesterday")) { 0343 // Because of 19feef732085b444515da3f6c66f3352bbcb1824 we need to find the system bookmark named Yesterday 0344 // and rename it to Modified Yesterday, otherwise users are getting untranslated strings 0345 bItem.setFullText(QStringLiteral("Modified Yesterday")); 0346 } else if (text == QLatin1String("This Month")) { 0347 // Because of 7e1d2fb84546506c91684dd222c2485f0783848f we need to find the system bookmark named This Month 0348 // and remove it, otherwise users are getting untranslated strings 0349 rootGroup.deleteBookmark(bItem); 0350 } else if (text == QLatin1String("Last Month")) { 0351 // Because of 7e1d2fb84546506c91684dd222c2485f0783848f we need to find the system bookmark named Last Month 0352 // and remove it, otherwise users are getting untranslated strings 0353 rootGroup.deleteBookmark(bItem); 0354 } 0355 } 0356 0357 bItem = nextbItem; 0358 } 0359 } 0360 if (fileVersion < 4) { 0361 auto findSystemBookmark = [this](const QString &untranslatedText) { 0362 KBookmarkGroup root = d->bookmarkManager->root(); 0363 KBookmark bItem = root.first(); 0364 while (!bItem.isNull()) { 0365 const bool isSystemItem = bItem.metaDataItem(QStringLiteral("isSystemItem")) == QLatin1String("true"); 0366 if (isSystemItem && bItem.fullText() == untranslatedText) { 0367 return bItem; 0368 } 0369 bItem = root.next(bItem); 0370 } 0371 return KBookmark(); 0372 }; 0373 // This variable is used to insert the new bookmarks at the correct place starting after the "Downloads" 0374 // bookmark. When the user already has some of the bookmarks set up manually, the createSystemBookmark() 0375 // function returns an empty KBookmark so the following entries will be added at the end of the bookmark 0376 // section to not mess with the users setup. 0377 KBookmark after = findSystemBookmark(QLatin1String("Downloads")); 0378 0379 const QString musicFolder = QStandardPaths::writableLocation(QStandardPaths::MusicLocation); 0380 if (QDir(musicFolder).exists()) { 0381 after = createSystemBookmark(kli18nc("KFile System Bookmarks", "Music").untranslatedText(), 0382 QUrl::fromLocalFile(musicFolder), 0383 QStringLiteral("folder-music"), 0384 after); 0385 } 0386 const QString pictureFolder = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); 0387 if (QDir(pictureFolder).exists()) { 0388 after = createSystemBookmark(kli18nc("KFile System Bookmarks", "Pictures").untranslatedText(), 0389 QUrl::fromLocalFile(pictureFolder), 0390 QStringLiteral("folder-pictures"), 0391 after); 0392 } 0393 // Choosing the name "Videos" instead of "Movies", since that is how the folder 0394 // is called normally on Linux: https://cgit.freedesktop.org/xdg/xdg-user-dirs/tree/user-dirs.defaults 0395 const QString videoFolder = QStandardPaths::writableLocation(QStandardPaths::MoviesLocation); 0396 if (QDir(videoFolder).exists()) { 0397 after = createSystemBookmark(kli18nc("KFile System Bookmarks", "Videos").untranslatedText(), 0398 QUrl::fromLocalFile(videoFolder), 0399 QStringLiteral("folder-videos"), 0400 after); 0401 } 0402 } 0403 0404 if (newFile) { 0405 setDefaultMetadataItemForGroup(PlacesType); 0406 setDefaultMetadataItemForGroup(RemoteType); 0407 setDefaultMetadataItemForGroup(DevicesType); 0408 setDefaultMetadataItemForGroup(RemovableDevicesType); 0409 setDefaultMetadataItemForGroup(TagsType); 0410 } 0411 0412 // Force bookmarks to be saved. If on open/save dialog and the bookmarks are not saved, QFile::exists 0413 // will always return false, which opening/closing all the time the open/save dialog would cause the 0414 // bookmarks to be added once each time, having lots of times each bookmark. (ereslibre) 0415 d->bookmarkManager->saveAs(file); 0416 } 0417 0418 // Add a Recently Used entry if available (it comes from kio-extras) 0419 if (qEnvironmentVariableIsSet("KDE_FULL_SESSION") && KProtocolInfo::isKnownProtocol(QStringLiteral("recentlyused")) 0420 && root.metaDataItem(QStringLiteral("withRecentlyUsed")) != QLatin1String("true")) { 0421 root.setMetaDataItem(QStringLiteral("withRecentlyUsed"), QStringLiteral("true")); 0422 0423 KBookmark recentFilesBookmark = KFilePlacesItem::createSystemBookmark(d->bookmarkManager, 0424 kli18nc("KFile System Bookmarks", "Recent Files").untranslatedText(), 0425 QUrl(QStringLiteral("recentlyused:/files")), 0426 QStringLiteral("document-open-recent")); 0427 0428 KBookmark recentDirectoriesBookmark = KFilePlacesItem::createSystemBookmark(d->bookmarkManager, 0429 kli18nc("KFile System Bookmarks", "Recent Locations").untranslatedText(), 0430 QUrl(QStringLiteral("recentlyused:/locations")), 0431 QStringLiteral("folder-open-recent")); 0432 0433 setDefaultMetadataItemForGroup(RecentlySavedType); 0434 0435 // Move The recently used bookmarks below the trash, making it the first element in the Recent group 0436 KBookmark trashBookmark = bookmarkForUrl(QUrl(QStringLiteral("trash:/"))); 0437 if (!trashBookmark.isNull()) { 0438 root.moveBookmark(recentFilesBookmark, trashBookmark); 0439 root.moveBookmark(recentDirectoriesBookmark, recentFilesBookmark); 0440 } 0441 0442 d->bookmarkManager->save(); 0443 } 0444 0445 // if baloo is enabled, add new urls even if the bookmark file is not empty 0446 if (d->fileIndexingEnabled && root.metaDataItem(QStringLiteral("withBaloo")) != QLatin1String("true")) { 0447 root.setMetaDataItem(QStringLiteral("withBaloo"), QStringLiteral("true")); 0448 0449 // don't add by default "Modified Today" and "Modified Yesterday" when recentlyused:/ is present 0450 if (root.metaDataItem(QStringLiteral("withRecentlyUsed")) != QLatin1String("true")) { 0451 KFilePlacesItem::createSystemBookmark(d->bookmarkManager, 0452 kli18nc("KFile System Bookmarks", "Modified Today").untranslatedText(), 0453 QUrl(QStringLiteral("timeline:/today")), 0454 QStringLiteral("go-jump-today")); 0455 KFilePlacesItem::createSystemBookmark(d->bookmarkManager, 0456 kli18nc("KFile System Bookmarks", "Modified Yesterday").untranslatedText(), 0457 QUrl(QStringLiteral("timeline:/yesterday")), 0458 QStringLiteral("view-calendar-day")); 0459 } 0460 0461 setDefaultMetadataItemForGroup(SearchForType); 0462 setDefaultMetadataItemForGroup(RecentlySavedType); 0463 0464 d->bookmarkManager->save(); 0465 } 0466 0467 QString predicate( 0468 QString::fromLatin1("[[[[ StorageVolume.ignored == false AND [ StorageVolume.usage == 'FileSystem' OR StorageVolume.usage == 'Encrypted' ]]" 0469 " OR " 0470 "[ IS StorageAccess AND StorageDrive.driveType == 'Floppy' ]]" 0471 " OR " 0472 "OpticalDisc.availableContent & 'Audio' ]" 0473 " OR " 0474 "StorageAccess.ignored == false ]")); 0475 0476 if (KProtocolInfo::isKnownProtocol(QStringLiteral("mtp"))) { 0477 predicate = QLatin1Char('[') + predicate + QLatin1String(" OR PortableMediaPlayer.supportedProtocols == 'mtp']"); 0478 } 0479 if (KProtocolInfo::isKnownProtocol(QStringLiteral("afc"))) { 0480 predicate = QLatin1Char('[') + predicate + QLatin1String(" OR PortableMediaPlayer.supportedProtocols == 'afc']"); 0481 } 0482 0483 d->predicate = Solid::Predicate::fromString(predicate); 0484 0485 Q_ASSERT(d->predicate.isValid()); 0486 0487 connect(d->bookmarkManager, &KBookmarkManager::changed, this, [this]() { 0488 d->reloadBookmarks(); 0489 }); 0490 connect(d->bookmarkManager, &KBookmarkManager::bookmarksChanged, this, [this]() { 0491 d->reloadBookmarks(); 0492 }); 0493 0494 d->reloadBookmarks(); 0495 QTimer::singleShot(0, this, [this]() { 0496 d->initDeviceList(); 0497 }); 0498 } 0499 0500 KFilePlacesModel::KFilePlacesModel(QObject *parent) 0501 : KFilePlacesModel({}, parent) 0502 { 0503 } 0504 0505 KFilePlacesModel::~KFilePlacesModel() = default; 0506 0507 QUrl KFilePlacesModel::url(const QModelIndex &index) const 0508 { 0509 return data(index, UrlRole).toUrl(); 0510 } 0511 0512 bool KFilePlacesModel::setupNeeded(const QModelIndex &index) const 0513 { 0514 return data(index, SetupNeededRole).toBool(); 0515 } 0516 0517 QIcon KFilePlacesModel::icon(const QModelIndex &index) const 0518 { 0519 return data(index, Qt::DecorationRole).value<QIcon>(); 0520 } 0521 0522 QString KFilePlacesModel::text(const QModelIndex &index) const 0523 { 0524 return data(index, Qt::DisplayRole).toString(); 0525 } 0526 0527 bool KFilePlacesModel::isHidden(const QModelIndex &index) const 0528 { 0529 // Note: we do not want to show an index if its parent is hidden 0530 return data(index, HiddenRole).toBool() || isGroupHidden(index); 0531 } 0532 0533 bool KFilePlacesModel::isGroupHidden(const GroupType type) const 0534 { 0535 const QString hidden = d->bookmarkManager->root().metaDataItem(stateNameForGroupType(type)); 0536 return hidden == QLatin1String("true"); 0537 } 0538 0539 bool KFilePlacesModel::isGroupHidden(const QModelIndex &index) const 0540 { 0541 if (!index.isValid()) { 0542 return false; 0543 } 0544 0545 KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer()); 0546 return isGroupHidden(item->groupType()); 0547 } 0548 0549 bool KFilePlacesModel::isDevice(const QModelIndex &index) const 0550 { 0551 if (!index.isValid()) { 0552 return false; 0553 } 0554 0555 KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer()); 0556 0557 return item->isDevice(); 0558 } 0559 0560 bool KFilePlacesModel::isTeardownAllowed(const QModelIndex &index) const 0561 { 0562 if (!index.isValid()) { 0563 return false; 0564 } 0565 0566 KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer()); 0567 return item->isTeardownAllowed(); 0568 } 0569 0570 bool KFilePlacesModel::isEjectAllowed(const QModelIndex &index) const 0571 { 0572 if (!index.isValid()) { 0573 return false; 0574 } 0575 0576 KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer()); 0577 return item->isEjectAllowed(); 0578 } 0579 0580 bool KFilePlacesModel::isTeardownOverlayRecommended(const QModelIndex &index) const 0581 { 0582 if (!index.isValid()) { 0583 return false; 0584 } 0585 0586 KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer()); 0587 return item->isTeardownOverlayRecommended(); 0588 } 0589 0590 KFilePlacesModel::DeviceAccessibility KFilePlacesModel::deviceAccessibility(const QModelIndex &index) const 0591 { 0592 if (!index.isValid()) { 0593 return KFilePlacesModel::Accessible; 0594 } 0595 0596 KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer()); 0597 return item->deviceAccessibility(); 0598 } 0599 0600 Solid::Device KFilePlacesModel::deviceForIndex(const QModelIndex &index) const 0601 { 0602 if (!index.isValid()) { 0603 return Solid::Device(); 0604 } 0605 0606 KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer()); 0607 0608 if (item->isDevice()) { 0609 return item->device(); 0610 } else { 0611 return Solid::Device(); 0612 } 0613 } 0614 0615 KBookmark KFilePlacesModel::bookmarkForIndex(const QModelIndex &index) const 0616 { 0617 if (!index.isValid()) { 0618 return KBookmark(); 0619 } 0620 0621 KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer()); 0622 return item->bookmark(); 0623 } 0624 0625 KFilePlacesModel::GroupType KFilePlacesModel::groupType(const QModelIndex &index) const 0626 { 0627 if (!index.isValid()) { 0628 return UnknownType; 0629 } 0630 0631 KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer()); 0632 return item->groupType(); 0633 } 0634 0635 QModelIndexList KFilePlacesModel::groupIndexes(const KFilePlacesModel::GroupType type) const 0636 { 0637 if (type == UnknownType) { 0638 return QModelIndexList(); 0639 } 0640 0641 QModelIndexList indexes; 0642 const int rows = rowCount(); 0643 for (int row = 0; row < rows; ++row) { 0644 const QModelIndex current = index(row, 0); 0645 if (groupType(current) == type) { 0646 indexes << current; 0647 } 0648 } 0649 0650 return indexes; 0651 } 0652 0653 QVariant KFilePlacesModel::data(const QModelIndex &index, int role) const 0654 { 0655 if (!index.isValid()) { 0656 return QVariant(); 0657 } 0658 0659 KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer()); 0660 if (role == KFilePlacesModel::GroupHiddenRole) { 0661 return isGroupHidden(item->groupType()); 0662 } else { 0663 return item->data(role); 0664 } 0665 } 0666 0667 QModelIndex KFilePlacesModel::index(int row, int column, const QModelIndex &parent) const 0668 { 0669 if (row < 0 || column != 0 || row >= d->items.size()) { 0670 return QModelIndex(); 0671 } 0672 0673 if (parent.isValid()) { 0674 return QModelIndex(); 0675 } 0676 0677 return createIndex(row, column, d->items.at(row)); 0678 } 0679 0680 QModelIndex KFilePlacesModel::parent(const QModelIndex &child) const 0681 { 0682 Q_UNUSED(child); 0683 return QModelIndex(); 0684 } 0685 0686 QHash<int, QByteArray> KFilePlacesModel::roleNames() const 0687 { 0688 auto super = QAbstractItemModel::roleNames(); 0689 0690 super[UrlRole] = "url"; 0691 super[HiddenRole] = "isHidden"; 0692 super[SetupNeededRole] = "isSetupNeeded"; 0693 super[FixedDeviceRole] = "isFixedDevice"; 0694 super[CapacityBarRecommendedRole] = "isCapacityBarRecommended"; 0695 super[GroupRole] = "group"; 0696 super[IconNameRole] = "iconName"; 0697 super[GroupHiddenRole] = "isGroupHidden"; 0698 super[TeardownAllowedRole] = "isTeardownAllowed"; 0699 super[EjectAllowedRole] = "isEjectAllowed"; 0700 super[TeardownOverlayRecommendedRole] = "isTeardownOverlayRecommended"; 0701 super[DeviceAccessibilityRole] = "deviceAccessibility"; 0702 0703 return super; 0704 } 0705 0706 int KFilePlacesModel::rowCount(const QModelIndex &parent) const 0707 { 0708 if (parent.isValid()) { 0709 return 0; 0710 } else { 0711 return d->items.size(); 0712 } 0713 } 0714 0715 int KFilePlacesModel::columnCount(const QModelIndex &parent) const 0716 { 0717 Q_UNUSED(parent) 0718 // We only know 1 piece of information for a particular entry 0719 return 1; 0720 } 0721 0722 QModelIndex KFilePlacesModel::closestItem(const QUrl &url) const 0723 { 0724 int foundRow = -1; 0725 int maxLength = 0; 0726 0727 // Search the item which is equal to the URL or at least is a parent URL. 0728 // If there are more than one possible item URL candidates, choose the item 0729 // which covers the bigger range of the URL. 0730 for (int row = 0; row < d->items.size(); ++row) { 0731 KFilePlacesItem *item = d->items[row]; 0732 0733 if (item->isHidden() || isGroupHidden(item->groupType())) { 0734 continue; 0735 } 0736 0737 const QUrl itemUrl = convertedUrl(item->data(UrlRole).toUrl()); 0738 0739 if (itemUrl.matches(url, QUrl::StripTrailingSlash) 0740 || (itemUrl.isParentOf(url) && itemUrl.query() == url.query() && itemUrl.fragment() == url.fragment())) { 0741 const int length = itemUrl.toString().length(); 0742 if (length > maxLength) { 0743 foundRow = row; 0744 maxLength = length; 0745 } 0746 } 0747 } 0748 0749 if (foundRow == -1) { 0750 return QModelIndex(); 0751 } else { 0752 return createIndex(foundRow, 0, d->items[foundRow]); 0753 } 0754 } 0755 0756 void KFilePlacesModelPrivate::initDeviceList() 0757 { 0758 Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance(); 0759 0760 QObject::connect(notifier, &Solid::DeviceNotifier::deviceAdded, q, [this](const QString &device) { 0761 deviceAdded(device); 0762 }); 0763 QObject::connect(notifier, &Solid::DeviceNotifier::deviceRemoved, q, [this](const QString &device) { 0764 deviceRemoved(device); 0765 }); 0766 0767 const QList<Solid::Device> &deviceList = Solid::Device::listFromQuery(predicate); 0768 0769 availableDevices.reserve(deviceList.size()); 0770 for (const Solid::Device &device : deviceList) { 0771 availableDevices << device.udi(); 0772 } 0773 0774 reloadBookmarks(); 0775 } 0776 0777 void KFilePlacesModelPrivate::deviceAdded(const QString &udi) 0778 { 0779 Solid::Device d(udi); 0780 0781 if (predicate.matches(d)) { 0782 availableDevices << udi; 0783 reloadBookmarks(); 0784 } 0785 } 0786 0787 void KFilePlacesModelPrivate::deviceRemoved(const QString &udi) 0788 { 0789 auto it = std::find(availableDevices.begin(), availableDevices.end(), udi); 0790 if (it != availableDevices.end()) { 0791 availableDevices.erase(it); 0792 reloadBookmarks(); 0793 } 0794 } 0795 0796 void KFilePlacesModelPrivate::itemChanged(const QString &id, const QVector<int> &roles) 0797 { 0798 for (int row = 0; row < items.size(); ++row) { 0799 if (items.at(row)->id() == id) { 0800 QModelIndex index = q->index(row, 0); 0801 Q_EMIT q->dataChanged(index, index, roles); 0802 } 0803 } 0804 } 0805 0806 void KFilePlacesModelPrivate::reloadBookmarks() 0807 { 0808 QList<KFilePlacesItem *> currentItems = loadBookmarkList(); 0809 0810 QList<KFilePlacesItem *>::Iterator it_i = items.begin(); 0811 QList<KFilePlacesItem *>::Iterator it_c = currentItems.begin(); 0812 0813 QList<KFilePlacesItem *>::Iterator end_i = items.end(); 0814 QList<KFilePlacesItem *>::Iterator end_c = currentItems.end(); 0815 0816 while (it_i != end_i || it_c != end_c) { 0817 if (it_i == end_i && it_c != end_c) { 0818 int row = items.count(); 0819 0820 q->beginInsertRows(QModelIndex(), row, row); 0821 it_i = items.insert(it_i, *it_c); 0822 ++it_i; 0823 it_c = currentItems.erase(it_c); 0824 0825 end_i = items.end(); 0826 end_c = currentItems.end(); 0827 q->endInsertRows(); 0828 0829 } else if (it_i != end_i && it_c == end_c) { 0830 int row = items.indexOf(*it_i); 0831 0832 q->beginRemoveRows(QModelIndex(), row, row); 0833 delete *it_i; 0834 it_i = items.erase(it_i); 0835 0836 end_i = items.end(); 0837 end_c = currentItems.end(); 0838 q->endRemoveRows(); 0839 0840 } else if ((*it_i)->id() == (*it_c)->id()) { 0841 bool shouldEmit = !((*it_i)->bookmark() == (*it_c)->bookmark()); 0842 (*it_i)->setBookmark((*it_c)->bookmark()); 0843 if (shouldEmit) { 0844 int row = items.indexOf(*it_i); 0845 QModelIndex idx = q->index(row, 0); 0846 Q_EMIT q->dataChanged(idx, idx); 0847 } 0848 ++it_i; 0849 ++it_c; 0850 } else { 0851 int row = items.indexOf(*it_i); 0852 0853 if (it_i + 1 != end_i && (*(it_i + 1))->id() == (*it_c)->id()) { // if the next one matches, it's a remove 0854 q->beginRemoveRows(QModelIndex(), row, row); 0855 delete *it_i; 0856 it_i = items.erase(it_i); 0857 0858 end_i = items.end(); 0859 end_c = currentItems.end(); 0860 q->endRemoveRows(); 0861 } else { 0862 q->beginInsertRows(QModelIndex(), row, row); 0863 it_i = items.insert(it_i, *it_c); 0864 ++it_i; 0865 it_c = currentItems.erase(it_c); 0866 0867 end_i = items.end(); 0868 end_c = currentItems.end(); 0869 q->endInsertRows(); 0870 } 0871 } 0872 } 0873 0874 qDeleteAll(currentItems); 0875 currentItems.clear(); 0876 0877 Q_EMIT q->reloaded(); 0878 } 0879 0880 bool KFilePlacesModelPrivate::isBalooUrl(const QUrl &url) const 0881 { 0882 const QString scheme = url.scheme(); 0883 return ((scheme == QLatin1String("timeline")) || (scheme == QLatin1String("search"))); 0884 } 0885 0886 QList<KFilePlacesItem *> KFilePlacesModelPrivate::loadBookmarkList() 0887 { 0888 QList<KFilePlacesItem *> items; 0889 0890 KBookmarkGroup root = bookmarkManager->root(); 0891 KBookmark bookmark = root.first(); 0892 QVector<QString> devices = availableDevices; 0893 QVector<QString> tagsList = tags; 0894 0895 while (!bookmark.isNull()) { 0896 const QString udi = bookmark.metaDataItem(QStringLiteral("UDI")); 0897 const QUrl url = bookmark.url(); 0898 const QString tag = bookmark.metaDataItem(QStringLiteral("tag")); 0899 if (!udi.isEmpty() || url.isValid()) { 0900 QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp")); 0901 0902 // If it's not a tag it's a device 0903 if (tag.isEmpty()) { 0904 auto it = std::find(devices.begin(), devices.end(), udi); 0905 bool deviceAvailable = (it != devices.end()); 0906 if (deviceAvailable) { 0907 devices.erase(it); 0908 } 0909 0910 bool allowedHere = 0911 appName.isEmpty() || ((appName == QCoreApplication::instance()->applicationName()) || (appName == alternativeApplicationName)); 0912 bool isSupportedUrl = isBalooUrl(url) ? fileIndexingEnabled : true; 0913 bool isSupportedScheme = supportedSchemes.isEmpty() || supportedSchemes.contains(url.scheme()); 0914 0915 KFilePlacesItem *item = nullptr; 0916 if (deviceAvailable) { 0917 item = new KFilePlacesItem(bookmarkManager, bookmark.address(), udi, q); 0918 if (!item->hasSupportedScheme(supportedSchemes)) { 0919 delete item; 0920 item = nullptr; 0921 } 0922 } else if (isSupportedScheme && isSupportedUrl && udi.isEmpty() && allowedHere) { 0923 // TODO: Update bookmark internal element 0924 item = new KFilePlacesItem(bookmarkManager, bookmark.address(), QString(), q); 0925 } 0926 0927 if (item) { 0928 QObject::connect(item, &KFilePlacesItem::itemChanged, q, [this](const QString &id, const QVector<int> &roles) { 0929 itemChanged(id, roles); 0930 }); 0931 0932 items << item; 0933 } 0934 } else { 0935 auto it = std::find(tagsList.begin(), tagsList.end(), tag); 0936 if (it != tagsList.end()) { 0937 tagsList.removeAll(tag); 0938 KFilePlacesItem *item = new KFilePlacesItem(bookmarkManager, bookmark.address(), QString(), q); 0939 items << item; 0940 QObject::connect(item, &KFilePlacesItem::itemChanged, q, [this](const QString &id, const QVector<int> &roles) { 0941 itemChanged(id, roles); 0942 }); 0943 } 0944 } 0945 } 0946 0947 bookmark = root.next(bookmark); 0948 } 0949 0950 // Add bookmarks for the remaining devices, they were previously unknown 0951 for (const QString &udi : std::as_const(devices)) { 0952 bookmark = KFilePlacesItem::createDeviceBookmark(bookmarkManager, udi); 0953 if (!bookmark.isNull()) { 0954 KFilePlacesItem *item = new KFilePlacesItem(bookmarkManager, bookmark.address(), udi, q); 0955 QObject::connect(item, &KFilePlacesItem::itemChanged, q, [this](const QString &id, const QVector<int> &roles) { 0956 itemChanged(id, roles); 0957 }); 0958 // TODO: Update bookmark internal element 0959 items << item; 0960 } 0961 } 0962 0963 for (const QString &tag : tagsList) { 0964 bookmark = KFilePlacesItem::createTagBookmark(bookmarkManager, tag); 0965 if (!bookmark.isNull()) { 0966 KFilePlacesItem *item = new KFilePlacesItem(bookmarkManager, bookmark.address(), tag, q); 0967 QObject::connect(item, &KFilePlacesItem::itemChanged, q, [this](const QString &id, const QVector<int> &roles) { 0968 itemChanged(id, roles); 0969 }); 0970 items << item; 0971 } 0972 } 0973 0974 // return a sorted list based on groups 0975 std::stable_sort(items.begin(), items.end(), [](KFilePlacesItem *itemA, KFilePlacesItem *itemB) { 0976 return (itemA->groupType() < itemB->groupType()); 0977 }); 0978 0979 return items; 0980 } 0981 0982 int KFilePlacesModelPrivate::findNearestPosition(int source, int target) 0983 { 0984 const KFilePlacesItem *item = items.at(source); 0985 const KFilePlacesModel::GroupType groupType = item->groupType(); 0986 int newTarget = qMin(target, items.count() - 1); 0987 0988 // moving inside the same group is ok 0989 if ((items.at(newTarget)->groupType() == groupType)) { 0990 return target; 0991 } 0992 0993 if (target > source) { // moving down, move it to the end of the group 0994 int groupFooter = source; 0995 while (items.at(groupFooter)->groupType() == groupType) { 0996 groupFooter++; 0997 // end of the list move it there 0998 if (groupFooter == items.count()) { 0999 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()) { 1032 res |= Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled; 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 { 1049 QStringList types; 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; 1059 QByteArray itemData; 1060 1061 QDataStream stream(&itemData, QIODevice::WriteOnly); 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)); 1106 QDataStream stream(&itemData, QIODevice::ReadOnly); 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 1136 const QList<QUrl> urls = KUrlMimeData::urlsFromMimeData(data); 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 1177 QUrl KFilePlacesModel::convertedUrl(const QUrl &url) 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(); 1303 Q_EMIT dataChanged(index, index, {KFilePlacesModel::HiddenRole}); 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 1373 int KFilePlacesModel::hiddenCount() const 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 1387 QAction *KFilePlacesModel::teardownActionForIndex(const QModelIndex &index) const 1388 { 1389 Solid::Device device = deviceForIndex(index); 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 1449 QAction *KFilePlacesModel::ejectActionForIndex(const QModelIndex &index) const 1450 { 1451 Solid::Device device = deviceForIndex(index); 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 1462 void KFilePlacesModel::requestTeardown(const QModelIndex &index) 1463 { 1464 Solid::Device device = deviceForIndex(index); 1465 Solid::StorageAccess *access = device.as<Solid::StorageAccess>(); 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 1479 void KFilePlacesModel::requestEject(const QModelIndex &index) 1480 { 1481 Solid::Device device = deviceForIndex(index); 1482 1483 Solid::OpticalDrive *drive = device.parent().as<Solid::OpticalDrive>(); 1484 1485 if (drive != nullptr) { 1486 d->teardownInProgress[drive] = index; 1487 1488 QString filePath; 1489 Solid::StorageAccess *access = device.as<Solid::StorageAccess>(); 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 { 1500 QString label = data(index, Qt::DisplayRole).toString().replace(QLatin1Char('&'), QLatin1String("&&")); 1501 QString message = i18n("The device '%1' is not a disk and cannot be ejected.", label); 1502 Q_EMIT errorMessage(message); 1503 } 1504 } 1505 1506 void KFilePlacesModel::requestSetup(const QModelIndex &index) 1507 { 1508 Solid::Device device = deviceForIndex(index); 1509 1510 if (device.is<Solid::StorageAccess>() && !d->setupInProgress.contains(device.as<Solid::StorageAccess>()) 1511 && !device.as<Solid::StorageAccess>()->isAccessible()) { 1512 Solid::StorageAccess *access = device.as<Solid::StorageAccess>(); 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 1583 void KFilePlacesModel::setSupportedSchemes(const QStringList &schemes) 1584 { 1585 d->supportedSchemes = schemes; 1586 d->reloadBookmarks(); 1587 Q_EMIT supportedSchemesChanged(); 1588 } 1589 1590 QStringList KFilePlacesModel::supportedSchemes() const 1591 { 1592 return d->supportedSchemes; 1593 } 1594 1595 #include "moc_kfileplacesmodel.cpp"