File indexing completed on 2025-01-26 05:06:21
0001 /* 0002 SPDX-FileCopyrightText: 2006 David Faure <faure@kde.org> 0003 SPDX-FileCopyrightText: 2008 Fredrik Höglund <fredrik@kde.org> 0004 SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org> 0005 SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org> 0006 SPDX-FileCopyrightText: 2014 Eike Hein <hein@kde.org> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 #include "foldermodel.h" 0012 #include "itemviewadapter.h" 0013 #include "positioner.h" 0014 #include "removeaction.h" 0015 #include "screenmapper.h" 0016 0017 #include <QApplication> 0018 #include <QClipboard> 0019 #include <QCollator> 0020 #include <QDrag> 0021 #include <QImage> 0022 #include <QItemSelectionModel> 0023 #include <QLoggingCategory> 0024 #include <QMenu> 0025 #include <QMimeData> 0026 #include <QMimeDatabase> 0027 #include <QPainter> 0028 #include <QPixmap> 0029 #include <QQuickItem> 0030 #include <QQuickWindow> 0031 #include <QScreen> 0032 #include <QTimer> 0033 #include <qplatformdefs.h> 0034 0035 #include <KAuthorized> 0036 #include <KConfigGroup> 0037 #include <KCoreDirLister> 0038 #include <KDesktopFile> 0039 #include <KDirModel> 0040 #include <KDirWatch> 0041 #include <KFileCopyToMenu> 0042 #include <KFileItemActions> 0043 #include <KFileItemListProperties> 0044 #include <KIO/CopyJob> 0045 #include <KIO/DeleteJob> 0046 #include <KIO/DeleteOrTrashJob> 0047 #include <KIO/DropJob> 0048 #include <KIO/EmptyTrashJob> 0049 #include <KIO/FileUndoManager> 0050 #include <KIO/JobUiDelegate> 0051 #include <KIO/JobUiDelegateFactory> 0052 #include <KIO/OpenUrlJob> 0053 #include <KIO/Paste> 0054 #include <KIO/PasteJob> 0055 #include <KIO/PreviewJob> 0056 #include <KIO/RestoreJob> 0057 #include <KIO/StatJob> 0058 #include <KLocalizedString> 0059 #include <KNotificationJobUiDelegate> 0060 #include <KPropertiesDialog> 0061 #include <KProtocolInfo> 0062 #include <KSharedConfig> 0063 #include <KShell> 0064 #include <KStringHandler> 0065 0066 #include <Plasma/Applet> 0067 #include <Plasma/Containment> 0068 #include <Plasma/Corona> 0069 #include <PlasmaActivities/Consumer> 0070 0071 #include <chrono> 0072 #include <sys/stat.h> 0073 #include <sys/types.h> 0074 #include <unistd.h> 0075 0076 using namespace std::chrono_literals; 0077 0078 Q_LOGGING_CATEGORY(FOLDERMODEL, "plasma.containments.desktop.folder.foldermodel") 0079 0080 class DragTrackerSingleton 0081 { 0082 public: 0083 DragTracker self; 0084 }; 0085 0086 Q_GLOBAL_STATIC(DragTrackerSingleton, privateDragTrackerSelf) 0087 0088 DragTracker::DragTracker(QObject *parent) 0089 : QObject(parent) 0090 { 0091 } 0092 0093 DragTracker::~DragTracker() 0094 { 0095 } 0096 0097 bool DragTracker::isDragInProgress() const 0098 { 0099 return m_dragInProgress; 0100 } 0101 0102 void DragTracker::setDragInProgress(FolderModel *dragOwner, bool dragInProgress) 0103 { 0104 if (dragInProgress == m_dragInProgress) { 0105 return; 0106 } 0107 0108 m_dragInProgress = dragInProgress; 0109 if (dragInProgress) { 0110 m_dragOwner = dragOwner; 0111 } else { 0112 m_dragOwner.clear(); 0113 } 0114 0115 Q_EMIT dragInProgressChanged(m_dragInProgress); 0116 } 0117 0118 FolderModel *DragTracker::dragOwner() 0119 { 0120 return m_dragOwner; 0121 } 0122 0123 DragTracker *DragTracker::self() 0124 { 0125 return &privateDragTrackerSelf()->self; 0126 } 0127 0128 DirLister::DirLister(QObject *parent) 0129 : KDirLister(parent) 0130 { 0131 connect(this, &KCoreDirLister::jobError, this, &DirLister::handleJobError); 0132 } 0133 0134 DirLister::~DirLister() 0135 { 0136 } 0137 0138 void DirLister::handleJobError(KIO::Job *job) 0139 { 0140 if (!autoErrorHandlingEnabled()) { 0141 Q_EMIT error(job->errorString()); 0142 return; 0143 } 0144 } 0145 0146 FolderModel::FolderModel(QObject *parent) 0147 : QSortFilterProxyModel(parent) 0148 , m_dirWatch(nullptr) 0149 , m_urlChangedWhileDragging(false) 0150 , m_dropTargetPositionsCleanup(new QTimer(this)) 0151 , m_previewGenerator(nullptr) 0152 , m_viewAdapter(nullptr) 0153 , m_actionCollection(this) 0154 , m_newMenu(nullptr) 0155 , m_fileItemActions(nullptr) 0156 , m_usedByContainment(false) 0157 , m_locked(true) 0158 , m_sortMode(0) 0159 , m_sortDesc(false) 0160 , m_sortDirsFirst(true) 0161 , m_parseDesktopFiles(false) 0162 , m_previews(false) 0163 , m_filterMode(NoFilter) 0164 , m_filterPatternMatchAll(true) 0165 , m_screenUsed(false) 0166 , m_screenMapper(ScreenMapper::instance()) 0167 , m_complete(false) 0168 , m_currentActivity(KActivities::Consumer().currentActivity()) 0169 , m_showHiddenFiles(false) 0170 { 0171 connect(DragTracker::self(), &DragTracker::dragInProgressChanged, this, &FolderModel::draggingChanged); 0172 connect(DragTracker::self(), &DragTracker::dragInProgressChanged, this, &FolderModel::dragInProgressAnywhereChanged); 0173 // needed to pass the job around with qml 0174 qmlRegisterAnonymousType<KIO::DropJob>("org.kde.private.desktopcontainment.folder", 0); 0175 DirLister *dirLister = new DirLister(this); 0176 dirLister->setDelayedMimeTypes(true); 0177 dirLister->setAutoErrorHandlingEnabled(false); 0178 connect(dirLister, &DirLister::error, this, &FolderModel::dirListFailed); 0179 connect(dirLister, &KCoreDirLister::itemsDeleted, this, &FolderModel::evictFromIsDirCache); 0180 0181 connect(dirLister, &KCoreDirLister::started, this, std::bind(&FolderModel::setStatus, this, Status::Listing)); 0182 0183 void (KCoreDirLister::*myCompletedSignal)() = &KCoreDirLister::completed; 0184 QObject::connect(dirLister, myCompletedSignal, this, [this] { 0185 setStatus(Status::Ready); 0186 Q_EMIT listingCompleted(); 0187 }); 0188 0189 void (KCoreDirLister::*myCanceledSignal)() = &KCoreDirLister::canceled; 0190 QObject::connect(dirLister, myCanceledSignal, this, [this] { 0191 setStatus(Status::Canceled); 0192 Q_EMIT listingCanceled(); 0193 }); 0194 0195 m_dirModel = new KDirModel(this); 0196 m_dirModel->setDirLister(dirLister); 0197 m_dirModel->setDropsAllowed(KDirModel::DropOnDirectory | KDirModel::DropOnLocalExecutable); 0198 m_dirModel->dirLister()->setAutoUpdate(true); 0199 0200 // If we have dropped items queued for moving, go unsorted now. 0201 connect(this, &QAbstractItemModel::rowsAboutToBeInserted, this, [this]() { 0202 if (!m_dropTargetPositions.isEmpty()) { 0203 setSortMode(-1); 0204 } 0205 }); 0206 0207 // Position dropped items at the desired target position. 0208 connect(this, &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex &parent, int first, int last) { 0209 for (int i = first; i <= last; ++i) { 0210 const auto idx = index(i, 0, parent); 0211 const auto url = itemForIndex(idx).url(); 0212 auto it = m_dropTargetPositions.find(url.fileName()); 0213 if (it != m_dropTargetPositions.end()) { 0214 const auto pos = it.value(); 0215 m_dropTargetPositions.erase(it); 0216 Q_EMIT move(pos.x(), pos.y(), {url}); 0217 } 0218 } 0219 }); 0220 0221 /* 0222 * Dropped files may not actually show up as new files, e.g. when we overwrite 0223 * an existing file. Or files that fail to be listed by the dirLister, or... 0224 * To ensure we don't grow the map indefinitely, clean it up periodically. 0225 * The cleanup timer is (re)started whenever we modify the map. We use a quite 0226 * high interval of 10s. This should ensure, that we don't accidentally wipe 0227 * the mapping when we actually still want to use it. Since the time between 0228 * adding an entry in the map and it showing up in the model should be 0229 * small, this should rarely, if ever happen. 0230 */ 0231 m_dropTargetPositionsCleanup->setInterval(10s); 0232 m_dropTargetPositionsCleanup->setSingleShot(true); 0233 connect(m_dropTargetPositionsCleanup, &QTimer::timeout, this, [this]() { 0234 if (!m_dropTargetPositions.isEmpty()) { 0235 qCDebug(FOLDERMODEL) << "clearing drop target positions after timeout:" << m_dropTargetPositions; 0236 m_dropTargetPositions.clear(); 0237 } 0238 }); 0239 0240 m_selectionModel = new QItemSelectionModel(this, this); 0241 connect(m_selectionModel, &QItemSelectionModel::selectionChanged, this, &FolderModel::changeSelection); 0242 connect(m_selectionModel, &QItemSelectionModel::selectionChanged, this, &FolderModel::selectionChanged); 0243 0244 setSourceModel(m_dirModel); 0245 0246 setSortLocaleAware(true); 0247 setFilterCaseSensitivity(Qt::CaseInsensitive); 0248 setDynamicSortFilter(true); 0249 0250 sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder); 0251 0252 createActions(); 0253 } 0254 0255 FolderModel::~FolderModel() 0256 { 0257 if (m_usedByContainment) { 0258 // disconnect so we don't handle signals from the screen mapper when 0259 // removeScreen is called 0260 m_screenMapper->disconnect(this); 0261 m_screenMapper->removeScreen(m_screen, m_currentActivity, resolvedUrl()); 0262 } 0263 } 0264 0265 QHash<int, QByteArray> FolderModel::roleNames() const 0266 { 0267 return staticRoleNames(); 0268 } 0269 0270 QHash<int, QByteArray> FolderModel::staticRoleNames() 0271 { 0272 QHash<int, QByteArray> roleNames; 0273 roleNames[Qt::DisplayRole] = "display"; 0274 roleNames[Qt::DecorationRole] = "decoration"; 0275 roleNames[BlankRole] = "blank"; 0276 roleNames[SelectedRole] = "selected"; 0277 roleNames[IsDirRole] = "isDir"; 0278 roleNames[IsLinkRole] = "isLink"; 0279 roleNames[IsHiddenRole] = "isHidden"; 0280 roleNames[UrlRole] = "url"; 0281 roleNames[LinkDestinationUrl] = "linkDestinationUrl"; 0282 roleNames[SizeRole] = "size"; 0283 roleNames[TypeRole] = "type"; 0284 roleNames[FileNameWrappedRole] = "displayWrapped"; 0285 0286 return roleNames; 0287 } 0288 0289 QPoint FolderModel::localMenuPosition() const 0290 { 0291 QScreen *screen = nullptr; 0292 for (auto *s : qApp->screens()) { 0293 if (s->geometry().contains(m_menuPosition)) { 0294 screen = s; 0295 break; 0296 } 0297 } 0298 if (screen) { 0299 return m_menuPosition - screen->geometry().topLeft(); 0300 } 0301 return m_menuPosition; 0302 } 0303 0304 void FolderModel::classBegin() 0305 { 0306 } 0307 0308 void FolderModel::componentComplete() 0309 { 0310 m_complete = true; 0311 invalidate(); 0312 } 0313 0314 void FolderModel::invalidateIfComplete() 0315 { 0316 if (!m_complete) { 0317 return; 0318 } 0319 0320 invalidate(); 0321 } 0322 0323 void FolderModel::invalidateFilterIfComplete() 0324 { 0325 if (!m_complete) { 0326 return; 0327 } 0328 0329 invalidateFilter(); 0330 } 0331 0332 void FolderModel::newFileMenuItemCreated(const QUrl &url) 0333 { 0334 if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { 0335 m_screenMapper->addMapping(url, m_screen, m_currentActivity, ScreenMapper::DelayedSignal); 0336 m_dropTargetPositions.insert(url.fileName(), localMenuPosition()); 0337 m_menuPosition = {}; 0338 m_dropTargetPositionsCleanup->start(); 0339 } 0340 } 0341 0342 QString FolderModel::url() const 0343 { 0344 return m_url; 0345 } 0346 0347 void FolderModel::setUrl(const QString &url) 0348 { 0349 const QUrl &resolvedNewUrl = resolve(url); 0350 0351 if (url == m_url) { 0352 m_dirModel->dirLister()->updateDirectory(resolvedNewUrl); 0353 return; 0354 } 0355 0356 const auto oldUrl = resolvedUrl(); 0357 0358 beginResetModel(); 0359 m_url = url; 0360 m_isDirCache.clear(); 0361 m_dirModel->dirLister()->openUrl(resolvedNewUrl); 0362 clearDragImages(); 0363 m_dragIndexes.clear(); 0364 endResetModel(); 0365 0366 Q_EMIT urlChanged(); 0367 Q_EMIT resolvedUrlChanged(); 0368 0369 m_errorString.clear(); 0370 Q_EMIT errorStringChanged(); 0371 0372 if (m_dirWatch) { 0373 delete m_dirWatch; 0374 m_dirWatch = nullptr; 0375 } 0376 0377 if (resolvedNewUrl.isValid()) { 0378 m_dirWatch = new KDirWatch(this); 0379 connect(m_dirWatch, &KDirWatch::created, this, &FolderModel::iconNameChanged); 0380 connect(m_dirWatch, &KDirWatch::dirty, this, &FolderModel::iconNameChanged); 0381 m_dirWatch->addFile(resolvedNewUrl.toLocalFile() + QStringLiteral("/.directory")); 0382 } 0383 0384 if (dragging()) { 0385 m_urlChangedWhileDragging = true; 0386 } 0387 0388 Q_EMIT iconNameChanged(); 0389 0390 if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { 0391 m_screenMapper->removeScreen(m_screen, m_currentActivity, oldUrl); 0392 m_screenMapper->addScreen(m_screen, m_currentActivity, resolvedUrl()); 0393 } 0394 } 0395 0396 QUrl FolderModel::resolvedUrl() const 0397 { 0398 return m_dirModel->dirLister()->url(); 0399 } 0400 0401 QUrl FolderModel::resolve(const QString &url) 0402 { 0403 QUrl resolvedUrl; 0404 0405 if (url.startsWith(QLatin1Char('~'))) { 0406 resolvedUrl = QUrl::fromLocalFile(KShell::tildeExpand(url)); 0407 } else { 0408 resolvedUrl = QUrl::fromUserInput(url); 0409 } 0410 0411 return resolvedUrl; 0412 } 0413 0414 QString FolderModel::iconName() const 0415 { 0416 const KFileItem rootItem(m_dirModel->dirLister()->url()); 0417 0418 if (!rootItem.isFinalIconKnown()) { 0419 rootItem.determineMimeType(); 0420 } 0421 0422 return rootItem.iconName(); 0423 } 0424 0425 FolderModel::Status FolderModel::status() const 0426 { 0427 return m_status; 0428 } 0429 0430 void FolderModel::setStatus(Status status) 0431 { 0432 if (m_status != status) { 0433 m_status = status; 0434 Q_EMIT statusChanged(); 0435 } 0436 } 0437 0438 QString FolderModel::errorString() const 0439 { 0440 return m_errorString; 0441 } 0442 0443 bool FolderModel::dragging() const 0444 { 0445 return DragTracker::self()->isDragInProgress() && DragTracker::self()->dragOwner() == this; 0446 } 0447 0448 bool FolderModel::isDragInProgressAnywhere() const 0449 { 0450 return DragTracker::self()->isDragInProgress(); 0451 } 0452 0453 bool FolderModel::usedByContainment() const 0454 { 0455 return m_usedByContainment; 0456 } 0457 0458 void FolderModel::setUsedByContainment(bool used) 0459 { 0460 if (m_usedByContainment != used) { 0461 m_usedByContainment = used; 0462 0463 QAction *action = m_actionCollection.action(QStringLiteral("refresh")); 0464 0465 if (action) { 0466 action->setText(m_usedByContainment ? i18n("&Refresh Desktop") : i18n("&Refresh View")); 0467 action->setIcon(m_usedByContainment ? QIcon::fromTheme(QStringLiteral("user-desktop")) : QIcon::fromTheme(QStringLiteral("view-refresh"))); 0468 } 0469 0470 m_screenMapper->disconnect(this); 0471 connect(m_screenMapper, &ScreenMapper::screensChanged, this, &FolderModel::invalidateFilterIfComplete); 0472 connect(m_screenMapper, &ScreenMapper::screenMappingChanged, this, &FolderModel::invalidateFilterIfComplete); 0473 0474 Q_EMIT usedByContainmentChanged(); 0475 } 0476 } 0477 0478 bool FolderModel::locked() const 0479 { 0480 return m_locked; 0481 } 0482 0483 void FolderModel::setLocked(bool locked) 0484 { 0485 if (m_locked != locked) { 0486 m_locked = locked; 0487 0488 Q_EMIT lockedChanged(); 0489 } 0490 } 0491 0492 void FolderModel::dirListFailed(const QString &error) 0493 { 0494 m_errorString = error; 0495 Q_EMIT errorStringChanged(); 0496 } 0497 0498 int FolderModel::sortMode() const 0499 { 0500 return m_sortMode; 0501 } 0502 0503 void FolderModel::setSortMode(int mode) 0504 { 0505 if (m_sortMode != mode) { 0506 m_sortMode = mode; 0507 0508 if (mode == -1 /* Unsorted */) { 0509 setDynamicSortFilter(false); 0510 } else { 0511 invalidateIfComplete(); 0512 sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder); 0513 setDynamicSortFilter(true); 0514 } 0515 0516 Q_EMIT sortModeChanged(); 0517 } 0518 } 0519 0520 bool FolderModel::sortDesc() const 0521 { 0522 return m_sortDesc; 0523 } 0524 0525 void FolderModel::setSortDesc(bool desc) 0526 { 0527 if (m_sortDesc != desc) { 0528 m_sortDesc = desc; 0529 0530 if (m_sortMode != -1 /* Unsorted */) { 0531 invalidateIfComplete(); 0532 sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder); 0533 } 0534 0535 Q_EMIT sortDescChanged(); 0536 } 0537 } 0538 0539 bool FolderModel::sortDirsFirst() const 0540 { 0541 return m_sortDirsFirst; 0542 } 0543 0544 void FolderModel::setSortDirsFirst(bool enable) 0545 { 0546 if (m_sortDirsFirst != enable) { 0547 m_sortDirsFirst = enable; 0548 0549 if (m_sortMode != -1 /* Unsorted */) { 0550 invalidateIfComplete(); 0551 sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder); 0552 } 0553 0554 Q_EMIT sortDirsFirstChanged(); 0555 } 0556 } 0557 0558 bool FolderModel::parseDesktopFiles() const 0559 { 0560 return m_parseDesktopFiles; 0561 } 0562 0563 void FolderModel::setParseDesktopFiles(bool enable) 0564 { 0565 if (m_parseDesktopFiles != enable) { 0566 m_parseDesktopFiles = enable; 0567 Q_EMIT parseDesktopFilesChanged(); 0568 } 0569 } 0570 0571 QObject *FolderModel::viewAdapter() const 0572 { 0573 return m_viewAdapter; 0574 } 0575 0576 void FolderModel::setViewAdapter(QObject *adapter) 0577 { 0578 if (m_viewAdapter != adapter) { 0579 KAbstractViewAdapter *abstractViewAdapter = dynamic_cast<KAbstractViewAdapter *>(adapter); 0580 0581 m_viewAdapter = abstractViewAdapter; 0582 0583 if (m_viewAdapter && !m_previewGenerator) { 0584 m_previewGenerator = new KFilePreviewGenerator(abstractViewAdapter, this); 0585 m_previewGenerator->setPreviewShown(m_previews); 0586 m_previewGenerator->setEnabledPlugins(m_effectivePreviewPlugins); 0587 } 0588 0589 Q_EMIT viewAdapterChanged(); 0590 } 0591 } 0592 0593 bool FolderModel::previews() const 0594 { 0595 return m_previews; 0596 } 0597 0598 void FolderModel::setPreviews(bool previews) 0599 { 0600 if (m_previews != previews) { 0601 m_previews = previews; 0602 0603 if (m_previewGenerator) { 0604 m_previewGenerator->setPreviewShown(m_previews); 0605 } 0606 0607 Q_EMIT previewsChanged(); 0608 } 0609 } 0610 0611 QStringList FolderModel::previewPlugins() const 0612 { 0613 return m_previewPlugins; 0614 } 0615 0616 void FolderModel::setPreviewPlugins(const QStringList &previewPlugins) 0617 { 0618 QStringList effectivePlugins = previewPlugins; 0619 if (effectivePlugins.isEmpty()) { 0620 effectivePlugins = KIO::PreviewJob::defaultPlugins(); 0621 } 0622 0623 if (m_effectivePreviewPlugins != effectivePlugins) { 0624 m_effectivePreviewPlugins = effectivePlugins; 0625 0626 if (m_previewGenerator) { 0627 m_previewGenerator->setPreviewShown(false); 0628 m_previewGenerator->setEnabledPlugins(m_effectivePreviewPlugins); 0629 m_previewGenerator->setPreviewShown(true); 0630 } 0631 } 0632 0633 if (m_previewPlugins != previewPlugins) { 0634 m_previewPlugins = previewPlugins; 0635 Q_EMIT previewPluginsChanged(); 0636 } 0637 } 0638 0639 int FolderModel::filterMode() const 0640 { 0641 return m_filterMode; 0642 } 0643 0644 void FolderModel::setFilterMode(int filterMode) 0645 { 0646 if (m_filterMode != (FilterMode)filterMode) { 0647 m_filterMode = (FilterMode)filterMode; 0648 0649 invalidateFilterIfComplete(); 0650 0651 Q_EMIT filterModeChanged(); 0652 } 0653 } 0654 0655 QString FolderModel::filterPattern() const 0656 { 0657 return m_filterPattern; 0658 } 0659 0660 void FolderModel::setFilterPattern(const QString &pattern) 0661 { 0662 if (m_filterPattern == pattern) { 0663 return; 0664 } 0665 0666 m_filterPattern = pattern; 0667 m_filterPatternMatchAll = (pattern == QLatin1String("*")); 0668 0669 const QStringList patterns = pattern.split(QLatin1Char(' ')); 0670 m_regExps.clear(); 0671 m_regExps.reserve(patterns.count()); 0672 0673 for (const QString &pattern : patterns) { 0674 auto rx = QRegularExpression::fromWildcard(pattern); 0675 m_regExps.append(rx); 0676 } 0677 0678 invalidateFilterIfComplete(); 0679 0680 Q_EMIT filterPatternChanged(); 0681 } 0682 0683 QStringList FolderModel::filterMimeTypes() const 0684 { 0685 return m_mimeSet.values(); 0686 } 0687 0688 void FolderModel::setFilterMimeTypes(const QStringList &mimeList) 0689 { 0690 const QSet<QString> set(mimeList.constBegin(), mimeList.constEnd()); 0691 0692 if (m_mimeSet != set) { 0693 m_mimeSet = set; 0694 0695 invalidateFilterIfComplete(); 0696 0697 Q_EMIT filterMimeTypesChanged(); 0698 } 0699 } 0700 0701 void FolderModel::setScreen(int screen) 0702 { 0703 m_screenUsed = (screen != -1); 0704 0705 if (!m_screenUsed || m_screen == screen) 0706 return; 0707 0708 m_screen = screen; 0709 if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { 0710 m_screenMapper->addScreen(screen, m_currentActivity, resolvedUrl()); 0711 } 0712 Q_EMIT screenChanged(); 0713 } 0714 0715 KFileItem FolderModel::rootItem() const 0716 { 0717 return m_dirModel->dirLister()->rootItem(); 0718 } 0719 0720 void FolderModel::up() 0721 { 0722 const QUrl &up = KIO::upUrl(resolvedUrl()); 0723 0724 if (up.isValid()) { 0725 setUrl(up.toString()); 0726 } 0727 } 0728 0729 void FolderModel::cd(int row) 0730 { 0731 if (row < 0) { 0732 return; 0733 } 0734 0735 const QModelIndex idx = index(row, 0); 0736 bool isDir = data(idx, IsDirRole).toBool(); 0737 0738 if (isDir) { 0739 const KFileItem item = itemForIndex(idx); 0740 if (m_parseDesktopFiles && item.isDesktopFile()) { 0741 const KDesktopFile file(item.targetUrl().path()); 0742 if (file.hasLinkType()) { 0743 setUrl(file.readUrl()); 0744 } 0745 } else { 0746 setUrl(item.targetUrl().toString()); 0747 } 0748 } 0749 } 0750 0751 void FolderModel::run(int row) 0752 { 0753 if (row < 0) { 0754 return; 0755 } 0756 0757 KFileItem item = itemForIndex(index(row, 0)); 0758 0759 QUrl url(item.targetUrl()); 0760 0761 // FIXME TODO: This can go once we depend on a KIO w/ fe1f50caaf2. 0762 if (url.scheme().isEmpty()) { 0763 url.setScheme(QStringLiteral("file")); 0764 } 0765 0766 auto job = new KIO::OpenUrlJob(url); 0767 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr)); 0768 // On desktop:/ we want to be able to run .desktop files right away, 0769 // otherwise ask for security reasons. We also don't use the targetUrl() 0770 // from above since we don't want the resolved /home/foo/Desktop URL. 0771 job->setShowOpenOrExecuteDialog(item.url().scheme() != QLatin1String("desktop") || item.url().adjusted(QUrl::RemoveFilename).path() != QLatin1String("/") 0772 || !item.isDesktopFile()); 0773 job->setRunExecutables(true); 0774 job->start(); 0775 } 0776 0777 void FolderModel::runSelected() 0778 { 0779 const QModelIndexList indexes = m_selectionModel->selectedIndexes(); 0780 0781 if (indexes.isEmpty()) { 0782 return; 0783 } 0784 0785 if (indexes.count() == 1) { 0786 run(indexes.first().row()); 0787 return; 0788 } 0789 0790 KFileItemActions fileItemActions(this); 0791 KFileItemList items; 0792 0793 for (const QModelIndex &index : indexes) { 0794 // Skip over directories. 0795 if (!index.data(IsDirRole).toBool()) { 0796 items << itemForIndex(index); 0797 } 0798 } 0799 0800 fileItemActions.runPreferredApplications(items); 0801 } 0802 0803 void FolderModel::rename(int row, const QString &name) 0804 { 0805 if (row < 0) { 0806 return; 0807 } 0808 0809 QModelIndex idx = index(row, 0); 0810 m_dirModel->setData(mapToSource(idx), name, Qt::EditRole); 0811 } 0812 0813 int FolderModel::fileExtensionBoundary(int row) 0814 { 0815 const QModelIndex idx = index(row, 0); 0816 const QString &name = data(idx, Qt::DisplayRole).toString(); 0817 0818 int boundary = name.length(); 0819 0820 if (data(idx, IsDirRole).toBool()) { 0821 return boundary; 0822 } 0823 0824 QMimeDatabase db; 0825 const QString &ext = db.suffixForFileName(name); 0826 0827 if (ext.isEmpty()) { 0828 boundary = name.lastIndexOf(QLatin1Char('.')); 0829 0830 if (boundary < 1) { 0831 boundary = name.length(); 0832 } 0833 } else { 0834 boundary -= ext.length() + 1; 0835 } 0836 0837 return boundary; 0838 } 0839 0840 bool FolderModel::hasSelection() const 0841 { 0842 return m_selectionModel->hasSelection(); 0843 } 0844 0845 bool FolderModel::isSelected(int row) 0846 { 0847 if (row < 0) { 0848 return false; 0849 } 0850 0851 return m_selectionModel->isSelected(index(row, 0)); 0852 } 0853 0854 void FolderModel::setSelected(int row) 0855 { 0856 if (row < 0) { 0857 return; 0858 } 0859 0860 m_selectionModel->select(index(row, 0), QItemSelectionModel::Select); 0861 } 0862 0863 void FolderModel::toggleSelected(int row) 0864 { 0865 if (row < 0) { 0866 return; 0867 } 0868 0869 m_selectionModel->select(index(row, 0), QItemSelectionModel::Toggle); 0870 } 0871 0872 void FolderModel::setRangeSelected(int anchor, int to) 0873 { 0874 if (anchor < 0 || to < 0) { 0875 return; 0876 } 0877 0878 QItemSelection selection(index(anchor, 0), index(to, 0)); 0879 m_selectionModel->select(selection, QItemSelectionModel::ClearAndSelect); 0880 } 0881 0882 void FolderModel::updateSelection(const QVariantList &rows, bool toggle) 0883 { 0884 QItemSelection newSelection; 0885 0886 int iRow = -1; 0887 0888 for (const QVariant &row : rows) { 0889 iRow = row.toInt(); 0890 0891 if (iRow < 0) { 0892 return; 0893 } 0894 0895 const QModelIndex &idx = index(iRow, 0); 0896 newSelection.select(idx, idx); 0897 } 0898 0899 if (toggle) { 0900 QItemSelection pinnedSelection = m_pinnedSelection; 0901 pinnedSelection.merge(newSelection, QItemSelectionModel::Toggle); 0902 m_selectionModel->select(pinnedSelection, QItemSelectionModel::ClearAndSelect); 0903 } else { 0904 m_selectionModel->select(newSelection, QItemSelectionModel::ClearAndSelect); 0905 } 0906 } 0907 0908 void FolderModel::clearSelection() 0909 { 0910 if (m_selectionModel->hasSelection()) { 0911 m_selectionModel->clear(); 0912 } 0913 } 0914 0915 void FolderModel::pinSelection() 0916 { 0917 m_pinnedSelection = m_selectionModel->selection(); 0918 } 0919 0920 void FolderModel::unpinSelection() 0921 { 0922 m_pinnedSelection = QItemSelection(); 0923 } 0924 0925 void FolderModel::addItemDragImage(int row, int x, int y, int width, int height, const QVariant &image) 0926 { 0927 if (row < 0) { 0928 return; 0929 } 0930 0931 delete m_dragImages.take(row); 0932 0933 DragImage *dragImage = new DragImage(); 0934 dragImage->row = row; 0935 dragImage->rect = QRect(x, y, width, height); 0936 dragImage->image = image.value<QImage>(); 0937 dragImage->blank = false; 0938 0939 m_dragImages.insert(row, dragImage); 0940 } 0941 0942 void FolderModel::clearDragImages() 0943 { 0944 qDeleteAll(m_dragImages); 0945 m_dragImages.clear(); 0946 } 0947 0948 void FolderModel::setDragHotSpotScrollOffset(int x, int y) 0949 { 0950 m_dragHotSpotScrollOffset.setX(x); 0951 m_dragHotSpotScrollOffset.setY(y); 0952 } 0953 0954 QPoint FolderModel::dragCursorOffset(int row) 0955 { 0956 DragImage *image = m_dragImages.value(row); 0957 if (!image) { 0958 return QPoint(0, 0); 0959 } 0960 0961 return image->cursorOffset; 0962 } 0963 0964 void FolderModel::addDragImage(QDrag *drag, int x, int y) 0965 { 0966 if (!drag || m_dragImages.isEmpty()) { 0967 return; 0968 } 0969 0970 qreal dpr = 1.0; 0971 0972 QRegion region; 0973 0974 for (DragImage *image : std::as_const(m_dragImages)) { 0975 image->blank = isBlank(image->row); 0976 image->rect.translate(-m_dragHotSpotScrollOffset.x(), -m_dragHotSpotScrollOffset.y()); 0977 if (!image->blank && !image->image.isNull()) { 0978 region = region.united(image->rect); 0979 dpr = std::max(dpr, image->image.devicePixelRatioF()); 0980 } 0981 } 0982 0983 QRect rect = region.boundingRect(); 0984 QPoint offset = rect.topLeft(); 0985 rect.translate(-offset.x(), -offset.y()); 0986 0987 QImage dragImage(rect.size() * dpr, QImage::Format_RGBA8888); 0988 dragImage.setDevicePixelRatio(dpr); 0989 dragImage.fill(Qt::transparent); 0990 0991 QPainter painter(&dragImage); 0992 0993 QPoint pos; 0994 0995 for (DragImage *image : std::as_const(m_dragImages)) { 0996 if (!image->blank && !image->image.isNull()) { 0997 pos = image->rect.translated(-offset.x(), -offset.y()).topLeft(); 0998 image->cursorOffset.setX(pos.x() - (x - offset.x())); 0999 image->cursorOffset.setY(pos.y() - (y - offset.y())); 1000 1001 painter.drawImage(pos, image->image); 1002 } 1003 1004 // FIXME HACK: Operate on copy. 1005 image->rect.translate(m_dragHotSpotScrollOffset.x(), m_dragHotSpotScrollOffset.y()); 1006 } 1007 1008 drag->setPixmap(QPixmap::fromImage(dragImage)); 1009 drag->setHotSpot(QPoint(x - offset.x(), y - offset.y())); 1010 } 1011 1012 void FolderModel::dragSelected(int x, int y) 1013 { 1014 if (dragging()) { 1015 return; 1016 } 1017 1018 DragTracker::self()->setDragInProgress(this, true); 1019 m_urlChangedWhileDragging = false; 1020 1021 // Avoid starting a drag synchronously in a mouse handler or interferes with 1022 // child event filtering in parent items (and thus e.g. press-and-hold hand- 1023 // ling in a containment). 1024 QMetaObject::invokeMethod(this, "dragSelectedInternal", Qt::QueuedConnection, Q_ARG(int, x), Q_ARG(int, y)); 1025 } 1026 1027 void FolderModel::dragSelectedInternal(int x, int y) 1028 { 1029 if (!m_viewAdapter || !m_selectionModel->hasSelection()) { 1030 DragTracker::self()->setDragInProgress(nullptr, false); 1031 return; 1032 } 1033 1034 ItemViewAdapter *adapter = qobject_cast<ItemViewAdapter *>(m_viewAdapter); 1035 QQuickItem *item = qobject_cast<QQuickItem *>(adapter->adapterView()); 1036 1037 QDrag *drag = new QDrag(item); 1038 1039 addDragImage(drag, x, y); 1040 1041 m_dragIndexes = m_selectionModel->selectedIndexes(); 1042 1043 std::sort(m_dragIndexes.begin(), m_dragIndexes.end()); 1044 1045 // TODO: Optimize to Q_EMIT contiguous groups. 1046 Q_EMIT dataChanged(m_dragIndexes.constFirst(), m_dragIndexes.constLast(), {BlankRole}); 1047 1048 QModelIndexList sourceDragIndexes; 1049 sourceDragIndexes.reserve(m_dragIndexes.count()); 1050 for (const QModelIndex &index : std::as_const(m_dragIndexes)) { 1051 sourceDragIndexes.append(mapToSource(index)); 1052 } 1053 1054 drag->setMimeData(m_dirModel->mimeData(sourceDragIndexes)); 1055 1056 // Due to spring-loading (aka auto-expand), the URL might change 1057 // while the drag is in-flight - in that case we don't want to 1058 // unnecessarily Q_EMIT dataChanged() for (possibly invalid) indices 1059 // after it ends. 1060 const QUrl currentUrl(m_dirModel->dirLister()->url()); 1061 1062 item->grabMouse(); 1063 drag->exec(supportedDragActions()); 1064 1065 item->ungrabMouse(); 1066 1067 DragTracker::self()->setDragInProgress(nullptr, false); 1068 m_urlChangedWhileDragging = false; 1069 1070 if (m_dirModel->dirLister()->url() == currentUrl) { 1071 const QModelIndex first(m_dragIndexes.first()); 1072 const QModelIndex last(m_dragIndexes.last()); 1073 m_dragIndexes.clear(); 1074 // TODO: Optimize to Q_EMIT contiguous groups. 1075 Q_EMIT dataChanged(first, last, {BlankRole}); 1076 } 1077 } 1078 1079 static bool isDropBetweenSharedViews(const QList<QUrl> &urls, const QUrl &folderUrl) 1080 { 1081 if (urls.empty()) { 1082 return false; 1083 } 1084 1085 for (const auto &url : urls) { 1086 if (folderUrl.adjusted(QUrl::StripTrailingSlash) != url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash)) { 1087 return false; 1088 } 1089 } 1090 return true; 1091 } 1092 1093 void FolderModel::drop(QQuickItem *target, QObject *dropEvent, int row, bool showMenuManually) 1094 { 1095 QMimeData *mimeData = qobject_cast<QMimeData *>(dropEvent->property("mimeData").value<QObject *>()); 1096 1097 if (!mimeData) { 1098 return; 1099 } 1100 1101 QModelIndex idx; 1102 KFileItem item; 1103 1104 if (row > -1 && row < rowCount()) { 1105 idx = index(row, 0); 1106 item = itemForIndex(idx); 1107 } 1108 1109 QUrl dropTargetUrl; 1110 1111 // So we get to run mostLocalUrl() over the current URL. 1112 if (item.isNull()) { 1113 item = rootItem(); 1114 } 1115 1116 if (item.isNull()) { 1117 dropTargetUrl = m_dirModel->dirLister()->url(); 1118 } else if (m_parseDesktopFiles && item.isDesktopFile()) { 1119 const KDesktopFile file(item.targetUrl().path()); 1120 1121 if (file.hasLinkType()) { 1122 dropTargetUrl = QUrl(file.readUrl()); 1123 } else { 1124 dropTargetUrl = item.mostLocalUrl(); 1125 } 1126 } else { 1127 dropTargetUrl = item.mostLocalUrl(); 1128 } 1129 1130 auto dropTargetFolderUrl = dropTargetUrl; 1131 if (dropTargetFolderUrl.fileName() == QLatin1Char('.')) { 1132 // the target URL for desktop:/ is e.g. 'file://home/user/Desktop/.' 1133 dropTargetFolderUrl = dropTargetFolderUrl.adjusted(QUrl::RemoveFilename); 1134 } 1135 1136 // use dropTargetUrl to resolve desktop:/ to the actual file location which is also used by the mime data 1137 /* QMimeData operates on local URLs, but the dir lister and thus screen mapper and positioner may 1138 * use a fancy scheme like desktop:/ instead. Ensure we always use the latter to properly map URLs, 1139 * i.e. go from file:///home/user/Desktop/file to desktop:/file 1140 */ 1141 auto mappableUrl = [this, dropTargetFolderUrl](const QUrl &url) -> QUrl { 1142 if (dropTargetFolderUrl != m_dirModel->dirLister()->url()) { 1143 QString mappedUrl = url.toString(); 1144 const auto local = dropTargetFolderUrl.toString(); 1145 const auto internal = m_dirModel->dirLister()->url().toString(); 1146 if (mappedUrl.startsWith(local)) { 1147 mappedUrl.replace(0, local.size(), internal); 1148 } 1149 return ScreenMapper::stringToUrl(mappedUrl); 1150 } 1151 return url; 1152 }; 1153 1154 const int x = dropEvent->property("x").toInt(); 1155 const int y = dropEvent->property("y").toInt(); 1156 const QPoint dropPos = {x, y}; 1157 1158 if (dragging() && row == -1 && !m_urlChangedWhileDragging) { 1159 if (m_locked || mimeData->urls().isEmpty()) { 1160 return; 1161 } 1162 1163 setSortMode(-1); 1164 1165 for (const auto &url : mimeData->urls()) { 1166 m_dropTargetPositions.insert(url.fileName(), dropPos); 1167 m_screenMapper->addMapping(mappableUrl(url), m_screen, m_currentActivity, ScreenMapper::DelayedSignal); 1168 m_screenMapper->removeItemFromDisabledScreen(mappableUrl(url)); 1169 } 1170 Q_EMIT move(x, y, mimeData->urls()); 1171 1172 return; 1173 } 1174 1175 if (idx.isValid() && !(flags(idx) & Qt::ItemIsDropEnabled)) { 1176 return; 1177 } 1178 1179 // Catch drops from a Task Manager and convert to usable URL. 1180 if (!mimeData->hasUrls() && mimeData->hasFormat(QStringLiteral("text/x-orgkdeplasmataskmanager_taskurl"))) { 1181 QList<QUrl> urls = {QUrl(QString::fromUtf8(mimeData->data(QStringLiteral("text/x-orgkdeplasmataskmanager_taskurl"))))}; 1182 mimeData->setUrls(urls); 1183 } 1184 1185 if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { 1186 if (isDropBetweenSharedViews(mimeData->urls(), dropTargetFolderUrl)) { 1187 setSortMode(-1); 1188 const QList<QUrl> urls = mimeData->urls(); 1189 for (const auto &url : urls) { 1190 m_dropTargetPositions.insert(url.fileName(), dropPos); 1191 m_screenMapper->addMapping(mappableUrl(url), m_screen, m_currentActivity, ScreenMapper::DelayedSignal); 1192 m_screenMapper->removeItemFromDisabledScreen(mappableUrl(url)); 1193 } 1194 m_dropTargetPositionsCleanup->start(); 1195 return; 1196 } 1197 } 1198 1199 Qt::DropAction proposedAction((Qt::DropAction)dropEvent->property("proposedAction").toInt()); 1200 Qt::DropActions possibleActions(dropEvent->property("possibleActions").toInt()); 1201 Qt::MouseButtons buttons(dropEvent->property("buttons").toInt()); 1202 Qt::KeyboardModifiers modifiers(dropEvent->property("modifiers").toInt()); 1203 1204 auto pos = target->mapToScene(dropPos).toPoint(); 1205 pos = target->window()->mapToGlobal(pos); 1206 QDropEvent ev(pos, possibleActions, mimeData, buttons, modifiers); 1207 ev.setDropAction(proposedAction); 1208 1209 KIO::DropJobFlag flag = showMenuManually ? KIO::ShowMenuManually : KIO::DropJobDefaultFlags; 1210 KIO::DropJob *dropJob = KIO::drop(&ev, dropTargetUrl, flag); 1211 dropJob->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled)); 1212 1213 // No menu will be shown so skip copying QMimeData 1214 if (mimeData->urls().empty()) { 1215 return; 1216 } 1217 1218 // The QMimeData we extract from the DropArea's drop event is deleted as soon as this method 1219 // ends but we need to keep a copy for when popupMenuAboutToShow fires. 1220 QMimeData *mimeCopy = new QMimeData(); 1221 const QStringList formats = mimeData->formats(); 1222 for (const QString &format : formats) { 1223 mimeCopy->setData(format, mimeData->data(format)); 1224 } 1225 1226 connect(dropJob, &KIO::DropJob::popupMenuAboutToShow, this, [this, mimeCopy, x, y, dropJob](const KFileItemListProperties &) { 1227 Q_EMIT popupMenuAboutToShow(dropJob, mimeCopy, x, y); 1228 mimeCopy->deleteLater(); 1229 }); 1230 1231 /* 1232 * Position files that come from a drag'n'drop event at the drop event 1233 * target position. To do so, we first listen to copy job to figure out 1234 * the target URL. Then we store the position of this drop event in the 1235 * hash and eventually trigger a move request when we get notified about 1236 * the new file event from the source model. 1237 */ 1238 connect(dropJob, &KIO::DropJob::copyJobStarted, this, [this, dropPos, dropTargetUrl](KIO::CopyJob *copyJob) { 1239 auto map = [this, dropPos, dropTargetUrl](const QUrl &targetUrl) { 1240 // KIO::CopyJob::copyingDone is emitted recursively, ignore everything not directly in the target folder 1241 if ((dropTargetUrl.path() + QStringLiteral("/") + targetUrl.fileName()) != targetUrl.path()) { 1242 return; 1243 } 1244 m_dropTargetPositions.insert(targetUrl.fileName(), dropPos); 1245 m_dropTargetPositionsCleanup->start(); 1246 1247 if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { 1248 // assign a screen for the item before the copy is actually done, so 1249 // filterAcceptsRow doesn't assign the default screen to it 1250 QUrl url = resolvedUrl(); 1251 // if the folderview's folder is a standard path, just use the targetUrl for mapping 1252 if (targetUrl.toString().startsWith(url.toString())) { 1253 m_screenMapper->addMapping(targetUrl, m_screen, m_currentActivity, ScreenMapper::DelayedSignal); 1254 } else if (targetUrl.toString().startsWith(dropTargetUrl.toString())) { 1255 // if the folderview's folder is a special path, like desktop:// , we need to convert 1256 // the targetUrl file:// path to a desktop:/ path for mapping 1257 auto destPath = dropTargetUrl.path(); 1258 auto filePath = targetUrl.path(); 1259 if (filePath.startsWith(destPath)) { 1260 url.setPath(filePath.remove(0, destPath.length())); 1261 m_screenMapper->addMapping(url, m_screen, m_currentActivity, ScreenMapper::DelayedSignal); 1262 } 1263 } 1264 } 1265 }; 1266 // remember drop target position for target URL and forget about the source URL 1267 connect(copyJob, &KIO::CopyJob::copyingDone, this, [map](KIO::Job *, const QUrl &, const QUrl &targetUrl, const QDateTime &, bool, bool) { 1268 map(targetUrl); 1269 }); 1270 connect(copyJob, &KIO::CopyJob::copyingLinkDone, this, [map](KIO::Job *, const QUrl &, const QString &, const QUrl &targetUrl) { 1271 map(targetUrl); 1272 }); 1273 }); 1274 } 1275 1276 void FolderModel::dropCwd(QObject *dropEvent) 1277 { 1278 QMimeData *mimeData = qobject_cast<QMimeData *>(dropEvent->property("mimeData").value<QObject *>()); 1279 1280 if (!mimeData) { 1281 return; 1282 } 1283 1284 Qt::DropAction proposedAction((Qt::DropAction)dropEvent->property("proposedAction").toInt()); 1285 Qt::DropActions possibleActions(dropEvent->property("possibleActions").toInt()); 1286 Qt::MouseButtons buttons(dropEvent->property("buttons").toInt()); 1287 Qt::KeyboardModifiers modifiers(dropEvent->property("modifiers").toInt()); 1288 1289 QDropEvent ev(QPoint(), possibleActions, mimeData, buttons, modifiers); 1290 ev.setDropAction(proposedAction); 1291 1292 KIO::DropJob *dropJob = KIO::drop(&ev, m_dirModel->dirLister()->url().adjusted(QUrl::PreferLocalFile)); 1293 dropJob->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled)); 1294 } 1295 1296 void FolderModel::changeSelection(const QItemSelection &selected, const QItemSelection &deselected) 1297 { 1298 QModelIndexList indices = selected.indexes(); 1299 indices.append(deselected.indexes()); 1300 1301 const QList<int> roles{SelectedRole}; 1302 1303 for (const QModelIndex &index : std::as_const(indices)) { 1304 Q_EMIT dataChanged(index, index, roles); 1305 } 1306 1307 if (!m_selectionModel->hasSelection()) { 1308 clearDragImages(); 1309 } else { 1310 const QModelIndexList deselectedIndices = deselected.indexes(); 1311 for (const QModelIndex &index : deselectedIndices) { 1312 delete m_dragImages.take(index.row()); 1313 } 1314 } 1315 1316 updateActions(); 1317 } 1318 1319 bool FolderModel::isBlank(int row) const 1320 { 1321 if (row < 0) { 1322 return true; 1323 } 1324 1325 return data(index(row, 0), BlankRole).toBool(); 1326 } 1327 1328 QVariant FolderModel::data(const QModelIndex &index, int role) const 1329 { 1330 if (!index.isValid()) { 1331 return QVariant(); 1332 } 1333 1334 if (role == BlankRole) { 1335 return m_dragIndexes.contains(index); 1336 } else if (role == SelectedRole) { 1337 return m_selectionModel->isSelected(index); 1338 } else if (role == IsDirRole) { 1339 return isDir(mapToSource(index), m_dirModel); 1340 } else if (role == IsLinkRole) { 1341 const KFileItem item = itemForIndex(index); 1342 return item.isLink(); 1343 } else if (role == IsHiddenRole) { 1344 const KFileItem item = itemForIndex(index); 1345 return item.isHidden(); 1346 } else if (role == UrlRole) { 1347 return itemForIndex(index).url(); 1348 } else if (role == LinkDestinationUrl) { 1349 const KFileItem item = itemForIndex(index); 1350 1351 if (m_parseDesktopFiles && item.isDesktopFile()) { 1352 const KDesktopFile file(item.targetUrl().path()); 1353 1354 if (file.hasLinkType()) { 1355 return file.readUrl(); 1356 } 1357 } 1358 1359 return item.targetUrl(); 1360 } else if (role == SizeRole) { 1361 bool isDir = data(index, IsDirRole).toBool(); 1362 1363 if (!isDir) { 1364 return m_dirModel->data(mapToSource(QSortFilterProxyModel::index(index.row(), 1)), Qt::DisplayRole); 1365 } 1366 } else if (role == TypeRole) { 1367 return m_dirModel->data(mapToSource(QSortFilterProxyModel::index(index.row(), 6)), Qt::DisplayRole); 1368 } else if (role == FileNameRole) { 1369 return itemForIndex(index).url().fileName(); 1370 } else if (role == FileNameWrappedRole) { 1371 return KStringHandler::preProcessWrap(itemForIndex(index).text()); 1372 } 1373 1374 return QSortFilterProxyModel::data(index, role); 1375 } 1376 1377 int FolderModel::indexForUrl(const QUrl &url) const 1378 { 1379 return mapFromSource(m_dirModel->indexForUrl(url)).row(); 1380 } 1381 1382 KFileItem FolderModel::itemForIndex(const QModelIndex &index) const 1383 { 1384 return m_dirModel->itemForIndex(mapToSource(index)); 1385 } 1386 1387 bool FolderModel::isDir(const QModelIndex &index, const KDirModel *dirModel) const 1388 { 1389 KFileItem item = dirModel->itemForIndex(index); 1390 if (item.isDir()) { 1391 return true; 1392 } 1393 1394 auto it = m_isDirCache.constFind(item.url()); 1395 if (it != m_isDirCache.constEnd()) { 1396 return *it; 1397 } 1398 1399 if (m_parseDesktopFiles && item.isDesktopFile()) { 1400 // Check if the desktop file is a link to a directory 1401 KDesktopFile file(item.targetUrl().path()); 1402 1403 if (!file.hasLinkType()) { 1404 return false; 1405 } 1406 1407 const QUrl url(file.readUrl()); 1408 1409 // Check if we already have a running StatJob for this URL. 1410 if (m_isDirJobs.contains(item.url())) { 1411 return false; 1412 } 1413 1414 // Assume the root folder of a protocol is always a folder. 1415 // This avoids spinning up e.g. trash KIO slave just to check whether trash:/ is a folder. 1416 if (url.path() == QLatin1String("/")) { 1417 m_isDirCache.insert(item.url(), true); 1418 return true; 1419 } 1420 1421 if (KProtocolInfo::protocolClass(url.scheme()) != QLatin1String(":local")) { 1422 return false; 1423 } 1424 1425 KIO::StatJob *job = KIO::stat(url, KIO::HideProgressInfo); 1426 job->setProperty("org.kde.plasma.folder_url", item.url()); 1427 job->setSide(KIO::StatJob::SourceSide); 1428 job->setDetails(KIO::StatNoDetails); 1429 connect(job, &KJob::result, this, &FolderModel::statResult); 1430 m_isDirJobs.insert(item.url(), job); 1431 } 1432 1433 return false; 1434 } 1435 1436 void FolderModel::statResult(KJob *job) 1437 { 1438 KIO::StatJob *statJob = static_cast<KIO::StatJob *>(job); 1439 1440 const QUrl &url = statJob->property("org.kde.plasma.folder_url").toUrl(); 1441 const QModelIndex &idx = index(indexForUrl(url), 0); 1442 1443 if (idx.isValid() && statJob->error() == KJob::NoError) { 1444 m_isDirCache[url] = statJob->statResult().isDir(); 1445 1446 Q_EMIT dataChanged(idx, idx, QList<int>() << IsDirRole); 1447 } 1448 1449 m_isDirJobs.remove(url); 1450 } 1451 1452 void FolderModel::evictFromIsDirCache(const KFileItemList &items) 1453 { 1454 for (const KFileItem &item : items) { 1455 m_screenMapper->removeFromMap(item.url(), m_currentActivity); 1456 m_isDirCache.remove(item.url()); 1457 } 1458 } 1459 1460 bool FolderModel::lessThan(const QModelIndex &left, const QModelIndex &right) const 1461 { 1462 const KDirModel *dirModel = static_cast<KDirModel *>(sourceModel()); 1463 1464 if (m_sortDirsFirst || left.column() == KDirModel::Size) { 1465 bool leftIsDir = isDir(left, dirModel); 1466 bool rightIsDir = isDir(right, dirModel); 1467 1468 if (leftIsDir && !rightIsDir) { 1469 return (sortOrder() == Qt::AscendingOrder); 1470 } 1471 1472 if (!leftIsDir && rightIsDir) { 1473 return (sortOrder() == Qt::DescendingOrder); 1474 } 1475 } 1476 1477 const KFileItem leftItem = dirModel->data(left, KDirModel::FileItemRole).value<KFileItem>(); 1478 const KFileItem rightItem = dirModel->data(right, KDirModel::FileItemRole).value<KFileItem>(); 1479 const int column = left.column(); 1480 int result = 0; 1481 1482 switch (column) { 1483 case KDirModel::Size: { 1484 if (isDir(left, dirModel) && isDir(right, dirModel)) { 1485 const int leftChildCount = dirModel->data(left, KDirModel::ChildCountRole).toInt(); 1486 const int rightChildCount = dirModel->data(right, KDirModel::ChildCountRole).toInt(); 1487 if (leftChildCount < rightChildCount) 1488 result = -1; 1489 else if (leftChildCount > rightChildCount) 1490 result = +1; 1491 } else { 1492 const KIO::filesize_t leftSize = leftItem.size(); 1493 const KIO::filesize_t rightSize = rightItem.size(); 1494 if (leftSize < rightSize) 1495 result = -1; 1496 else if (leftSize > rightSize) 1497 result = +1; 1498 } 1499 1500 break; 1501 } 1502 case KDirModel::ModifiedTime: { 1503 const long long leftTime = leftItem.entry().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1); 1504 const long long rightTime = rightItem.entry().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1); 1505 if (leftTime < rightTime) 1506 result = -1; 1507 else if (leftTime > rightTime) 1508 result = +1; 1509 1510 break; 1511 } 1512 case KDirModel::Type: 1513 result = QString::compare(dirModel->data(left, Qt::DisplayRole).toString(), dirModel->data(right, Qt::DisplayRole).toString()); 1514 break; 1515 1516 default: 1517 break; 1518 } 1519 1520 if (result != 0) 1521 return result < 0; 1522 1523 QCollator collator; 1524 1525 result = collator.compare(leftItem.text(), rightItem.text()); 1526 1527 if (result != 0) 1528 return result < 0; 1529 1530 result = collator.compare(leftItem.name(), rightItem.name()); 1531 1532 if (result != 0) 1533 return result < 0; 1534 1535 return QString::compare(leftItem.url().url(), rightItem.url().url(), Qt::CaseSensitive); 1536 } 1537 1538 Qt::DropActions FolderModel::supportedDragActions() const 1539 { 1540 return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; 1541 } 1542 1543 Qt::DropActions FolderModel::supportedDropActions() const 1544 { 1545 return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; 1546 } 1547 1548 inline bool FolderModel::matchMimeType(const KFileItem &item) const 1549 { 1550 if (m_mimeSet.isEmpty()) { 1551 return false; 1552 } 1553 1554 if (m_mimeSet.contains(QLatin1String("all/all")) || m_mimeSet.contains(QLatin1String("all/allfiles"))) { 1555 return true; 1556 } 1557 1558 const QString mimeType = item.determineMimeType().name(); 1559 return m_mimeSet.contains(mimeType); 1560 } 1561 1562 inline bool FolderModel::matchPattern(const KFileItem &item) const 1563 { 1564 if (m_filterPatternMatchAll) { 1565 return true; 1566 } 1567 1568 const QString name = item.name(); 1569 QListIterator<QRegularExpression> i(m_regExps); 1570 while (i.hasNext()) { 1571 if (i.next().match(name).hasMatch()) { 1572 return true; 1573 } 1574 } 1575 1576 return false; 1577 } 1578 1579 bool FolderModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const 1580 { 1581 const KDirModel *dirModel = static_cast<KDirModel *>(sourceModel()); 1582 const KFileItem item = dirModel->itemForIndex(dirModel->index(sourceRow, KDirModel::Name, sourceParent)); 1583 1584 if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { 1585 const QUrl url = item.url(); 1586 const int screen = m_screenMapper->screenForItem(url, m_currentActivity); 1587 // don't do anything if the folderview is not associated with a screen 1588 if (m_screenUsed && screen == -1) { 1589 // The item is not associated with a screen, probably because this is the first 1590 // time we see it or the folderview was previously used as a regular applet. 1591 // Associated with this folderview if the view is on the first available screen 1592 if (m_screen == m_screenMapper->firstAvailableScreen(resolvedUrl(), m_currentActivity)) { 1593 m_screenMapper->addMapping(url, m_screen, m_currentActivity, ScreenMapper::DelayedSignal); 1594 } else { 1595 return false; 1596 } 1597 } else if (m_screen != screen) { 1598 // the item belongs to a different screen, filter it out 1599 return false; 1600 } 1601 } 1602 1603 if (m_filterMode == NoFilter) { 1604 return true; 1605 } 1606 1607 if (m_filterMode == FilterShowMatches) { 1608 return (matchPattern(item) && matchMimeType(item)); 1609 } else { 1610 return !(matchPattern(item) && matchMimeType(item)); 1611 } 1612 } 1613 1614 void FolderModel::createActions() 1615 { 1616 KIO::FileUndoManager *manager = KIO::FileUndoManager::self(); 1617 1618 QAction *cut = KStandardAction::cut(this, &FolderModel::cut, this); 1619 QAction *copy = KStandardAction::copy(this, &FolderModel::copy, this); 1620 1621 QAction *undo = KStandardAction::undo(manager, &KIO::FileUndoManager::undo, this); 1622 undo->setEnabled(manager->isUndoAvailable()); 1623 undo->setShortcutContext(Qt::WidgetShortcut); 1624 connect(manager, SIGNAL(undoAvailable(bool)), undo, SLOT(setEnabled(bool))); 1625 connect(manager, &KIO::FileUndoManager::undoTextChanged, this, &FolderModel::undoTextChanged); 1626 1627 QAction *paste = KStandardAction::paste(this, &FolderModel::paste, this); 1628 QAction *pasteTo = KStandardAction::paste(this, &FolderModel::pasteTo, this); 1629 1630 QAction *refresh = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("&Refresh View"), this); 1631 refresh->setShortcut(QKeySequence(QKeySequence::Refresh)); 1632 connect(refresh, &QAction::triggered, this, &FolderModel::refresh); 1633 1634 QAction *rename = KStandardAction::renameFile(this, &FolderModel::requestRename, this); 1635 QAction *trash = KStandardAction::moveToTrash(this, &FolderModel::moveSelectedToTrash, this); 1636 QAction *del = KStandardAction::deleteFile(this, &FolderModel::deleteSelected, this); 1637 RemoveAction *remove = new RemoveAction(&m_actionCollection, this); 1638 1639 QAction *emptyTrash = new QAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18n("&Empty Trash"), this); 1640 connect(emptyTrash, &QAction::triggered, this, &FolderModel::emptyTrashBin); 1641 1642 QAction *restoreFromTrash = new QAction(i18nc("Restore from trash", "Restore"), this); 1643 connect(restoreFromTrash, &QAction::triggered, this, &FolderModel::restoreSelectedFromTrash); 1644 1645 QAction *actOpen = new QAction(QIcon::fromTheme(QStringLiteral("window-new")), i18n("&Open"), this); 1646 connect(actOpen, &QAction::triggered, this, &FolderModel::runSelected); 1647 1648 m_actionCollection.addAction(QStringLiteral("open"), actOpen); 1649 m_actionCollection.addAction(QStringLiteral("cut"), cut); 1650 m_actionCollection.addAction(QStringLiteral("undo"), undo); 1651 m_actionCollection.addAction(QStringLiteral("copy"), copy); 1652 m_actionCollection.addAction(QStringLiteral("paste"), paste); 1653 m_actionCollection.addAction(QStringLiteral("pasteto"), pasteTo); 1654 m_actionCollection.addAction(QStringLiteral("refresh"), refresh); 1655 m_actionCollection.addAction(QStringLiteral("rename"), rename); 1656 m_actionCollection.addAction(QStringLiteral("remove"), remove); 1657 m_actionCollection.addAction(QStringLiteral("trash"), trash); 1658 m_actionCollection.addAction(QStringLiteral("del"), del); 1659 m_actionCollection.addAction(QStringLiteral("restoreFromTrash"), restoreFromTrash); 1660 m_actionCollection.addAction(QStringLiteral("emptyTrash"), emptyTrash); 1661 1662 // The RemoveAction needs to be updated after adding all actions to the actionCollection 1663 remove->update(); 1664 1665 m_newMenu = new KNewFileMenu(this); 1666 m_newMenu->setModal(false); 1667 connect(m_newMenu, &KNewFileMenu::directoryCreated, this, &FolderModel::newFileMenuItemCreated); 1668 connect(m_newMenu, &KNewFileMenu::fileCreated, this, &FolderModel::newFileMenuItemCreated); 1669 1670 m_actionCollection.addAction(QStringLiteral("newMenu"), m_newMenu); 1671 1672 m_copyToMenu = new KFileCopyToMenu(nullptr); 1673 } 1674 1675 QAction *FolderModel::action(const QString &name) const 1676 { 1677 return m_actionCollection.action(name); 1678 } 1679 1680 QObject *FolderModel::newMenu() const 1681 { 1682 return m_newMenu->menu(); 1683 } 1684 1685 void FolderModel::updateActions() 1686 { 1687 const QModelIndexList indexes = m_selectionModel->selectedIndexes(); 1688 1689 KFileItemList items; 1690 QList<QUrl> urls; 1691 bool hasRemoteFiles = false; 1692 bool isTrashLink = false; 1693 const bool isTrash = (resolvedUrl().scheme() == QLatin1String("trash")); 1694 1695 if (indexes.isEmpty()) { 1696 items << rootItem(); 1697 } else { 1698 items.reserve(indexes.count()); 1699 urls.reserve(indexes.count()); 1700 for (const QModelIndex &index : indexes) { 1701 KFileItem item = itemForIndex(index); 1702 if (!item.isNull()) { 1703 hasRemoteFiles |= item.localPath().isEmpty(); 1704 items.append(item); 1705 urls.append(item.url()); 1706 } 1707 } 1708 } 1709 1710 KFileItemListProperties itemProperties(items); 1711 // Check if we're showing the menu for the trash link 1712 if (items.count() == 1 && items.at(0).isDesktopFile()) { 1713 KDesktopFile file(items.at(0).localPath()); 1714 if (file.hasLinkType() && file.readUrl() == QLatin1String("trash:/")) { 1715 isTrashLink = true; 1716 } 1717 } 1718 1719 if (m_newMenu) { 1720 m_newMenu->checkUpToDate(); 1721 m_newMenu->setWorkingDirectory(m_dirModel->dirLister()->url()); 1722 // we need to set here as well, when the menu is shown via AppletInterface::eventFilter 1723 m_menuPosition = QCursor::pos(); 1724 1725 if (QAction *newMenuAction = m_actionCollection.action(QStringLiteral("newMenu"))) { 1726 newMenuAction->setEnabled(itemProperties.supportsWriting()); 1727 newMenuAction->setVisible(!isTrash); 1728 } 1729 } 1730 1731 if (QAction *emptyTrash = m_actionCollection.action(QStringLiteral("emptyTrash"))) { 1732 if (isTrash || isTrashLink) { 1733 emptyTrash->setVisible(true); 1734 emptyTrash->setEnabled(!isTrashEmpty()); 1735 } else { 1736 emptyTrash->setVisible(false); 1737 } 1738 } 1739 1740 if (QAction *restoreFromTrash = m_actionCollection.action(QStringLiteral("restoreFromTrash"))) { 1741 restoreFromTrash->setVisible(isTrash); 1742 } 1743 1744 if (QAction *remove = m_actionCollection.action(QStringLiteral("remove"))) { 1745 remove->setVisible(!hasRemoteFiles && itemProperties.supportsMoving() && itemProperties.supportsDeleting()); 1746 } 1747 1748 if (QAction *cut = m_actionCollection.action(QStringLiteral("cut"))) { 1749 cut->setEnabled(itemProperties.supportsDeleting()); 1750 cut->setVisible(!isTrash); 1751 } 1752 1753 if (QAction *paste = m_actionCollection.action(QStringLiteral("paste"))) { 1754 bool enable = false; 1755 1756 const QString pasteText = KIO::pasteActionText(QApplication::clipboard()->mimeData(), &enable, rootItem()); 1757 1758 if (enable) { 1759 paste->setText(pasteText); 1760 paste->setEnabled(true); 1761 } else { 1762 paste->setText(i18n("&Paste")); 1763 paste->setEnabled(false); 1764 } 1765 1766 if (QAction *pasteTo = m_actionCollection.action(QStringLiteral("pasteto"))) { 1767 pasteTo->setVisible(itemProperties.isDirectory() && itemProperties.supportsWriting()); 1768 pasteTo->setEnabled(paste->isEnabled()); 1769 pasteTo->setText(paste->text()); 1770 } 1771 } 1772 1773 if (QAction *rename = m_actionCollection.action(QStringLiteral("rename"))) { 1774 rename->setEnabled(itemProperties.supportsMoving()); 1775 rename->setVisible(!isTrash); 1776 } 1777 } 1778 1779 void FolderModel::openContextMenu(QQuickItem *visualParent, Qt::KeyboardModifiers modifiers) 1780 { 1781 Q_UNUSED(modifiers) 1782 1783 if (m_usedByContainment && !KAuthorized::authorize(QStringLiteral("action/kdesktop_rmb"))) { 1784 return; 1785 } 1786 1787 updateActions(); 1788 1789 const QModelIndexList indexes = m_selectionModel->selectedIndexes(); 1790 1791 QMenu *menu = new QMenu(); 1792 if (!m_fileItemActions) { 1793 m_fileItemActions = new KFileItemActions(this); 1794 } 1795 1796 if (indexes.isEmpty()) { 1797 menu->addAction(m_actionCollection.action(QStringLiteral("newMenu"))); 1798 menu->addSeparator(); 1799 menu->addAction(m_actionCollection.action(QStringLiteral("paste"))); 1800 menu->addAction(m_actionCollection.action(QStringLiteral("undo"))); 1801 menu->addAction(m_actionCollection.action(QStringLiteral("refresh"))); 1802 menu->addAction(m_actionCollection.action(QStringLiteral("emptyTrash"))); 1803 menu->addSeparator(); 1804 1805 KFileItemListProperties itemProperties(KFileItemList() << rootItem()); 1806 m_fileItemActions->setItemListProperties(itemProperties); 1807 1808 m_fileItemActions->insertOpenWithActionsTo(nullptr, menu, QStringList()); 1809 } else { 1810 KFileItemList items; 1811 QList<QUrl> urls; 1812 1813 items.reserve(indexes.count()); 1814 urls.reserve(indexes.count()); 1815 for (const QModelIndex &index : indexes) { 1816 KFileItem item = itemForIndex(index); 1817 if (!item.isNull()) { 1818 items.append(item); 1819 urls.append(item.url()); 1820 } 1821 } 1822 1823 KFileItemListProperties itemProperties(items); 1824 1825 // Start adding the actions: 1826 // "Open" and "Open with" actions 1827 m_fileItemActions->setItemListProperties(itemProperties); 1828 m_fileItemActions->insertOpenWithActionsTo(nullptr, menu, QStringList()); 1829 menu->addSeparator(); 1830 menu->addAction(m_actionCollection.action(QStringLiteral("cut"))); 1831 menu->addAction(m_actionCollection.action(QStringLiteral("copy"))); 1832 if (urls.length() == 1) { 1833 if (items.first().isDir()) { 1834 menu->addAction(m_actionCollection.action(QStringLiteral("pasteto"))); 1835 } 1836 } else { 1837 menu->addAction(m_actionCollection.action(QStringLiteral("paste"))); 1838 } 1839 1840 menu->addAction(m_actionCollection.action(QStringLiteral("rename"))); 1841 menu->addSeparator(); 1842 menu->addAction(m_actionCollection.action(QStringLiteral("restoreFromTrash"))); 1843 1844 if (isDeleteCommandShown()) { 1845 QAction *trashAction = m_actionCollection.action(QStringLiteral("trash")); 1846 QAction *deleteAction = m_actionCollection.action(QStringLiteral("del")); 1847 menu->addAction(trashAction); 1848 menu->addAction(deleteAction); 1849 } else { 1850 if (RemoveAction *removeAction = qobject_cast<RemoveAction *>(m_actionCollection.action(QStringLiteral("remove")))) { 1851 removeAction->update(); 1852 menu->addAction(removeAction); 1853 1854 // Used to monitor Shift modifier usage while the menu is open, to 1855 // swap the Trash and Delete actions. 1856 menu->installEventFilter(removeAction); 1857 QCoreApplication::instance()->installEventFilter(removeAction); 1858 } 1859 } 1860 1861 menu->addAction(m_actionCollection.action(QStringLiteral("emptyTrash"))); 1862 1863 menu->addSeparator(); 1864 1865 m_fileItemActions->addActionsTo(menu); 1866 1867 // Copy To, Move To 1868 KSharedConfig::Ptr dolphin = KSharedConfig::openConfig(QStringLiteral("dolphinrc")); 1869 if (KConfigGroup(dolphin, QStringLiteral("General")).readEntry("ShowCopyMoveMenu", false)) { 1870 m_copyToMenu->setUrls(urls); 1871 m_copyToMenu->setReadOnly(!itemProperties.supportsMoving()); 1872 m_copyToMenu->addActionsTo(menu); 1873 menu->addSeparator(); 1874 } 1875 1876 // Properties 1877 if (KPropertiesDialog::canDisplay(items)) { 1878 menu->addSeparator(); 1879 QAction *act = new QAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("&Properties"), menu); 1880 act->setShortcuts({Qt::ALT | Qt::Key_Return, Qt::ALT | Qt::Key_Enter}); 1881 QObject::connect(act, &QAction::triggered, this, &FolderModel::openPropertiesDialog); 1882 menu->addAction(act); 1883 } 1884 } 1885 1886 menu->setAttribute(Qt::WA_TranslucentBackground); 1887 menu->winId(); // force surface creation before ensurePolish call in menu::Popup which happens before show 1888 1889 if (visualParent && menu->windowHandle()) { 1890 menu->windowHandle()->setTransientParent(visualParent->window()); 1891 } 1892 1893 menu->popup(m_menuPosition); 1894 connect(menu, &QMenu::aboutToHide, [this, menu]() { 1895 menu->deleteLater(); 1896 1897 // Remove the event filter for swapping delete and trash action from the QCoreApplication as it is no longer needed 1898 if (RemoveAction *removeAction = qobject_cast<RemoveAction *>(m_actionCollection.action(QStringLiteral("remove")))) 1899 QCoreApplication::instance()->removeEventFilter(removeAction); 1900 }); 1901 } 1902 1903 void FolderModel::openPropertiesDialog() 1904 { 1905 const QModelIndexList indexes = m_selectionModel->selectedIndexes(); 1906 1907 if (indexes.isEmpty()) { 1908 return; 1909 } 1910 1911 KFileItemList items; 1912 items.reserve(indexes.count()); 1913 for (const QModelIndex &index : indexes) { 1914 KFileItem item = itemForIndex(index); 1915 if (!item.isNull()) { 1916 items.append(item); 1917 } 1918 } 1919 1920 if (!KPropertiesDialog::canDisplay(items)) { 1921 return; 1922 } 1923 1924 KPropertiesDialog::showDialog(items, nullptr, false /*non modal*/); 1925 } 1926 1927 void FolderModel::linkHere(const QUrl &sourceUrl) 1928 { 1929 KIO::CopyJob *job = KIO::link(sourceUrl, m_dirModel->dirLister()->url(), KIO::HideProgressInfo); 1930 KIO::FileUndoManager::self()->recordCopyJob(job); 1931 } 1932 1933 QList<QUrl> FolderModel::selectedUrls() const 1934 { 1935 const auto indexes = m_selectionModel->selectedIndexes(); 1936 1937 QList<QUrl> urls; 1938 urls.reserve(indexes.count()); 1939 1940 for (const QModelIndex &index : indexes) { 1941 urls.append(itemForIndex(index).url()); 1942 } 1943 1944 return urls; 1945 } 1946 1947 void FolderModel::copy() 1948 { 1949 if (!m_selectionModel->hasSelection()) { 1950 return; 1951 } 1952 1953 if (QAction *action = m_actionCollection.action(QStringLiteral("copy"))) { 1954 if (!action->isEnabled()) { 1955 return; 1956 } 1957 } 1958 1959 QMimeData *mimeData = QSortFilterProxyModel::mimeData(m_selectionModel->selectedIndexes()); 1960 QApplication::clipboard()->setMimeData(mimeData); 1961 } 1962 1963 void FolderModel::cut() 1964 { 1965 if (!m_selectionModel->hasSelection()) { 1966 return; 1967 } 1968 1969 if (QAction *action = m_actionCollection.action(QStringLiteral("cut"))) { 1970 if (!action->isEnabled()) { 1971 return; 1972 } 1973 } 1974 1975 QMimeData *mimeData = QSortFilterProxyModel::mimeData(m_selectionModel->selectedIndexes()); 1976 KIO::setClipboardDataCut(mimeData, true); 1977 QApplication::clipboard()->setMimeData(mimeData); 1978 } 1979 1980 void FolderModel::paste() 1981 { 1982 if (QAction *action = m_actionCollection.action(QStringLiteral("paste"))) { 1983 if (!action->isEnabled()) { 1984 return; 1985 } 1986 } 1987 1988 KIO::PasteJob *job = KIO::paste(QApplication::clipboard()->mimeData(), m_dirModel->dirLister()->url()); 1989 auto delegate = new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled); 1990 job->setUiDelegate(delegate); 1991 } 1992 1993 void FolderModel::pasteTo() 1994 { 1995 const QList<QUrl> urls = selectedUrls(); 1996 Q_ASSERT(urls.count() == 1); 1997 KIO::paste(QApplication::clipboard()->mimeData(), urls.first()); 1998 } 1999 2000 void FolderModel::refresh() 2001 { 2002 m_errorString.clear(); 2003 Q_EMIT errorStringChanged(); 2004 2005 m_dirModel->dirLister()->updateDirectory(m_dirModel->dirLister()->url()); 2006 } 2007 2008 Plasma::Applet *FolderModel::applet() const 2009 { 2010 return m_applet; 2011 } 2012 2013 void FolderModel::setApplet(Plasma::Applet *applet) 2014 { 2015 if (m_applet != applet) { 2016 Q_ASSERT(!m_applet); 2017 2018 m_applet = applet; 2019 2020 if (applet) { 2021 Plasma::Containment *containment = applet->containment(); 2022 2023 if (containment) { 2024 Plasma::Corona *corona = containment->corona(); 2025 2026 if (corona) { 2027 connect(corona, &Plasma::Corona::screenRemoved, this, [this](int screenId) { 2028 if (m_screen == screenId) { 2029 m_screenMapper->removeScreen(screenId, m_currentActivity, resolvedUrl()); 2030 } 2031 }); 2032 connect(corona, &Plasma::Corona::screenAdded, this, [this](int screenId) { 2033 if (m_screen == screenId) { 2034 m_screenMapper->addScreen(screenId, m_currentActivity, resolvedUrl()); 2035 } 2036 }); 2037 m_screenMapper->setCorona(corona); 2038 } 2039 setScreen(containment->screen()); 2040 connect(containment, &Plasma::Containment::screenChanged, this, &FolderModel::setScreen); 2041 } 2042 } 2043 2044 Q_EMIT appletChanged(); 2045 } 2046 } 2047 2048 bool FolderModel::showHiddenFiles() const 2049 { 2050 return m_showHiddenFiles; 2051 } 2052 2053 void FolderModel::setShowHiddenFiles(bool enable) 2054 { 2055 if (m_showHiddenFiles != enable) { 2056 m_showHiddenFiles = enable; 2057 m_dirModel->dirLister()->setShowHiddenFiles(enable); 2058 m_dirModel->dirLister()->emitChanges(); 2059 2060 Q_EMIT showHiddenFilesChanged(); 2061 } 2062 } 2063 2064 void FolderModel::moveSelectedToTrash() 2065 { 2066 if (!m_selectionModel->hasSelection()) { 2067 return; 2068 } 2069 2070 if (!isDeleteCommandShown()) { 2071 if (RemoveAction *action = qobject_cast<RemoveAction *>(m_actionCollection.action(QStringLiteral("remove")))) { 2072 if (action->proxyAction() != m_actionCollection.action(QStringLiteral("trash"))) { 2073 return; 2074 } 2075 } 2076 } 2077 2078 if (QAction *action = m_actionCollection.action(QStringLiteral("trash"))) { 2079 if (!action->isEnabled()) { 2080 return; 2081 } 2082 } 2083 2084 using Iface = KIO::AskUserActionInterface; 2085 auto *job = new KIO::DeleteOrTrashJob(selectedUrls(), Iface::Trash, Iface::DefaultConfirmation, this); 2086 job->start(); 2087 } 2088 2089 void FolderModel::deleteSelected() 2090 { 2091 if (!m_selectionModel->hasSelection()) { 2092 return; 2093 } 2094 2095 if (QAction *action = m_actionCollection.action(QStringLiteral("del"))) { 2096 if (!action->isEnabled()) { 2097 return; 2098 } 2099 } 2100 2101 using Iface = KIO::AskUserActionInterface; 2102 auto *job = new KIO::DeleteOrTrashJob(selectedUrls(), Iface::Delete, Iface::DefaultConfirmation, this); 2103 job->start(); 2104 } 2105 2106 void FolderModel::undo() 2107 { 2108 if (QAction *action = m_actionCollection.action(QStringLiteral("undo"))) { 2109 // trigger() doesn't check enabled and would crash if invoked nonetheless. 2110 if (action->isEnabled()) { 2111 action->trigger(); 2112 } 2113 } 2114 } 2115 2116 void FolderModel::emptyTrashBin() 2117 { 2118 using Iface = KIO::AskUserActionInterface; 2119 auto *job = new KIO::DeleteOrTrashJob({}, Iface::EmptyTrash, Iface::DefaultConfirmation, this); 2120 job->start(); 2121 } 2122 2123 void FolderModel::restoreSelectedFromTrash() 2124 { 2125 if (!m_selectionModel->hasSelection()) { 2126 return; 2127 } 2128 2129 const auto &urls = selectedUrls(); 2130 2131 KIO::RestoreJob *job = KIO::restoreFromTrash(urls); 2132 job->uiDelegate()->setAutoErrorHandlingEnabled(true); 2133 } 2134 2135 bool FolderModel::isTrashEmpty() 2136 { 2137 KConfig trashConfig(QStringLiteral("trashrc"), KConfig::SimpleConfig); 2138 return trashConfig.group(QStringLiteral("Status")).readEntry("Empty", true); 2139 } 2140 2141 void FolderModel::undoTextChanged(const QString &text) 2142 { 2143 if (QAction *action = m_actionCollection.action(QStringLiteral("undo"))) { 2144 action->setText(text); 2145 } 2146 } 2147 2148 void FolderModel::createFolder() 2149 { 2150 m_newMenu->setWorkingDirectory(m_dirModel->dirLister()->url()); 2151 m_newMenu->createDirectory(); 2152 } 2153 2154 bool FolderModel::isDeleteCommandShown() 2155 { 2156 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KDE")); 2157 return cg.readEntry("ShowDeleteCommand", false); 2158 }