File indexing completed on 2024-03-24 05:00:55
0001 /* This file is part of FSView. 0002 SPDX-FileCopyrightText: 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> 0003 0004 Some file management code taken from the Dolphin file manager: 0005 SPDX-FileCopyrightText: 2006-2009 Peter Penz <peter.penz19@mail.com> 0006 0007 SPDX-License-Identifier: GPL-2.0-only 0008 */ 0009 0010 /* 0011 * The KPart embedding the FSView widget 0012 */ 0013 0014 #include "fsview_part.h" 0015 0016 #include <QClipboard> 0017 #include <QTimer> 0018 #include <QStyle> 0019 0020 #include <kfileitem.h> 0021 #include <kpluginfactory.h> 0022 #include <KPluginMetaData> 0023 0024 #include <kprotocolmanager.h> 0025 #include <kio/copyjob.h> 0026 #include <kio/deletejob.h> 0027 #include <KIO/Paste> 0028 #include <kmessagebox.h> 0029 #include <kactionmenu.h> 0030 #include <kactioncollection.h> 0031 #include <kpropertiesdialog.h> 0032 #include <KMimeTypeEditor> 0033 #include <kio/jobuidelegate.h> 0034 #include <KIO/FileUndoManager> 0035 #include <KJobWidgets> 0036 #include <kconfig.h> 0037 #include <kconfiggroup.h> 0038 #include <ksharedconfig.h> 0039 #include <KLocalizedString> 0040 #include <KIO/ApplicationLauncherJob> 0041 #include <KIO/JobUiDelegateFactory> 0042 0043 #include <QApplication> 0044 #include <QMimeData> 0045 0046 #include "fsviewdebug.h" 0047 0048 K_PLUGIN_CLASS_WITH_JSON(FSViewPart, "fsview_part.json") 0049 0050 // FSJob, for progress 0051 0052 FSJob::FSJob(FSView *v) 0053 : KIO::Job() 0054 { 0055 _view = v; 0056 connect(v, &FSView::progress, this, &FSJob::progressSlot); 0057 } 0058 0059 void FSJob::kill(bool /*quietly*/) 0060 { 0061 _view->stop(); 0062 0063 Job::kill(); 0064 } 0065 0066 void FSJob::progressSlot(int percent, int dirs, const QString &cDir) 0067 { 0068 if (percent < 100) { 0069 emitPercent(percent, 100); 0070 slotInfoMessage(this, i18np("Read 1 folder, in %2", 0071 "Read %1 folders, in %2", 0072 dirs, cDir)); 0073 } else { 0074 slotInfoMessage(this, i18np("1 folder", "%1 folders", dirs)); 0075 } 0076 } 0077 0078 #if QT_VERSION_MAJOR < 6 0079 void FSJob::slotInfoMessage(KJob* job, const QString& plain, const QString& rich) 0080 { 0081 KCompositeJob::slotInfoMessage(job, plain, rich); 0082 } 0083 #endif 0084 0085 // FSViewPart 0086 0087 FSViewPart::FSViewPart(QWidget *parentWidget, 0088 QObject *parent, 0089 const KPluginMetaData& metaData, 0090 const QList<QVariant> & /* args */) 0091 #if QT_VERSION_MAJOR < 6 0092 : KParts::ReadOnlyPart(parent) 0093 { 0094 setMetaData(metaData); 0095 #else 0096 : KParts::ReadOnlyPart(parent, metaData) 0097 { 0098 #endif 0099 _view = new FSView(new Inode(), parentWidget); 0100 _view->setWhatsThis(i18n("<p>This is the FSView plugin, a graphical " 0101 "browsing mode showing filesystem utilization " 0102 "by using a tree map visualization.</p>" 0103 "<p>Note that in this mode, automatic updating " 0104 "when filesystem changes are made " 0105 "is intentionally <b>not</b> done.</p>" 0106 "<p>For details on usage and options available, " 0107 "see the online help under " 0108 "menu 'Help/FSView Manual'.</p>")); 0109 0110 _view->show(); 0111 setWidget(_view); 0112 0113 _ext = new FSViewNavigationExtension(this); 0114 _job = nullptr; 0115 0116 _areaMenu = new KActionMenu(i18n("Stop at Area"), 0117 actionCollection()); 0118 actionCollection()->addAction(QStringLiteral("treemap_areadir"), _areaMenu); 0119 _depthMenu = new KActionMenu(i18n("Stop at Depth"), 0120 actionCollection()); 0121 actionCollection()->addAction(QStringLiteral("treemap_depthdir"), _depthMenu); 0122 _visMenu = new KActionMenu(i18n("Visualization"), 0123 actionCollection()); 0124 actionCollection()->addAction(QStringLiteral("treemap_visdir"), _visMenu); 0125 0126 _colorMenu = new KActionMenu(i18n("Color Mode"), 0127 actionCollection()); 0128 actionCollection()->addAction(QStringLiteral("treemap_colordir"), _colorMenu); 0129 0130 QAction *action; 0131 action = actionCollection()->addAction(QStringLiteral("help_fsview")); 0132 action->setText(i18n("&FSView Manual")); 0133 action->setIcon(QIcon::fromTheme(QStringLiteral("fsview"))); 0134 action->setToolTip(i18n("Show FSView manual")); 0135 action->setWhatsThis(i18n("Opens the help browser with the " 0136 "FSView documentation")); 0137 connect(action, &QAction::triggered, this, &FSViewPart::showHelp); 0138 0139 connect(_visMenu->menu(), &QMenu::aboutToShow,this, &FSViewPart::slotShowVisMenu); 0140 connect(_areaMenu->menu(), &QMenu::aboutToShow, this, &FSViewPart::slotShowAreaMenu); 0141 connect(_depthMenu->menu(), &QMenu::aboutToShow, this, &FSViewPart::slotShowDepthMenu); 0142 connect(_colorMenu->menu(), &QMenu::aboutToShow, this, &FSViewPart::slotShowColorMenu); 0143 0144 // Both of these click signals are connected. Whether a single or 0145 // double click activates an item is checked against the current 0146 // style setting when the click happens. 0147 connect(_view, &FSView::clicked, _ext, &FSViewNavigationExtension::itemSingleClicked); 0148 connect(_view, &FSView::doubleClicked, _ext, &FSViewNavigationExtension::itemDoubleClicked); 0149 0150 connect(_view, &TreeMapWidget::returnPressed, _ext, &FSViewNavigationExtension::selected); 0151 connect(_view, QOverload<>::of(&TreeMapWidget::selectionChanged), this, &FSViewPart::updateActions); 0152 connect(_view, &TreeMapWidget::contextMenuRequested, this, &FSViewPart::contextMenu); 0153 0154 connect(_view, &FSView::started, this, &FSViewPart::startedSlot); 0155 connect(_view, &FSView::completed, this, &FSViewPart::completedSlot); 0156 0157 // Create common file management actions - this is necessary in KDE4 0158 // as these common actions are no longer automatically part of KParts. 0159 // Much of this is taken from Dolphin. 0160 // FIXME: Renaming didn't even seem to work in KDE3! Implement (non-inline) renaming 0161 // functionality. 0162 //QAction* renameAction = m_actionCollection->addAction("rename"); 0163 //rename->setText(i18nc("@action:inmenu Edit", "Rename...")); 0164 //rename->setShortcut(Qt::Key_F2); 0165 0166 QAction *moveToTrashAction = actionCollection()->addAction(QStringLiteral("move_to_trash")); 0167 moveToTrashAction->setText(i18nc("@action:inmenu File", "Move to Trash")); 0168 moveToTrashAction->setIcon(QIcon::fromTheme(QStringLiteral("user-trash"))); 0169 actionCollection()->setDefaultShortcut(moveToTrashAction, QKeySequence(QKeySequence::Delete)); 0170 connect(moveToTrashAction, &QAction::triggered, _ext, &FSViewNavigationExtension::trash); 0171 0172 QAction *deleteAction = actionCollection()->addAction(QStringLiteral("delete")); 0173 deleteAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); 0174 deleteAction->setText(i18nc("@action:inmenu File", "Delete")); 0175 actionCollection()->setDefaultShortcut(deleteAction, QKeySequence(Qt::SHIFT | Qt::Key_Delete)); 0176 connect(deleteAction, &QAction::triggered, _ext, &FSViewNavigationExtension::del); 0177 0178 QAction *editMimeTypeAction = actionCollection()->addAction(QStringLiteral("editMimeType")); 0179 editMimeTypeAction->setText(i18nc("@action:inmenu Edit", "&Edit File Type...")); 0180 connect(editMimeTypeAction, &QAction::triggered, _ext, &FSViewNavigationExtension::editMimeType); 0181 0182 QAction *propertiesAction = actionCollection()->addAction(QStringLiteral("properties")); 0183 propertiesAction->setText(i18nc("@action:inmenu File", "Properties")); 0184 propertiesAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); 0185 propertiesAction->setShortcut(Qt::ALT | Qt::Key_Return); 0186 connect(propertiesAction, &QAction::triggered, this, &FSViewPart::slotProperties); 0187 0188 QTimer::singleShot(1, this, SLOT(showInfo())); 0189 0190 updateActions(); 0191 0192 setXMLFile(QStringLiteral("fsview_part.rc")); 0193 } 0194 0195 FSViewPart::~FSViewPart() 0196 { 0197 qCDebug(FSVIEWLOG); 0198 0199 delete _job; 0200 _view->saveFSOptions(); 0201 } 0202 0203 QString FSViewPart::componentName() const 0204 { 0205 // also the part ui.rc file is in the program folder 0206 // TODO: change the component name to "fsviewpart" by removing this method and 0207 // adapting the folder where the file is placed. 0208 // Needs a way to also move any potential custom user ui.rc files 0209 // from fsview/fsview_part.rc to fsviewpart/fsview_part.rc 0210 return QStringLiteral("fsview"); 0211 } 0212 0213 void FSViewPart::showInfo() 0214 { 0215 QString info; 0216 info = i18n("FSView intentionally does not support automatic updates " 0217 "when changes are made to files or directories, " 0218 "currently visible in FSView, from the outside.\n" 0219 "For details, see the 'Help/FSView Manual'."); 0220 0221 KMessageBox::information(_view, info, QString(), QStringLiteral("ShowFSViewInfo")); 0222 } 0223 0224 void FSViewPart::showHelp() 0225 { 0226 const KService::Ptr helpCenter = KService::serviceByDesktopName(QStringLiteral("org.kde.khelpcenter")); 0227 auto job = new KIO::ApplicationLauncherJob(helpCenter); 0228 job->setUrls({QUrl(QStringLiteral("help:/konqueror/index.html#fsview"))}); 0229 job->start(); 0230 } 0231 0232 void FSViewPart::startedSlot() 0233 { 0234 _job = new FSJob(_view); 0235 _job->setUiDelegate(KIO::createDefaultJobUiDelegate()); 0236 emit started(_job); 0237 } 0238 0239 void FSViewPart::completedSlot(int dirs) 0240 { 0241 if (_job) { 0242 _job->progressSlot(100, dirs, QString()); 0243 delete _job; 0244 _job = nullptr; 0245 } 0246 0247 KConfigGroup cconfig = _view->config()->group("MetricCache"); 0248 _view->saveMetric(&cconfig); 0249 0250 emit completed(); 0251 } 0252 0253 void FSViewPart::slotShowVisMenu() 0254 { 0255 _visMenu->menu()->clear(); 0256 _view->addVisualizationItems(_visMenu->menu(), 1301); 0257 } 0258 0259 void FSViewPart::slotShowAreaMenu() 0260 { 0261 _areaMenu->menu()->clear(); 0262 _view->addAreaStopItems(_areaMenu->menu(), 1001, nullptr); 0263 } 0264 0265 void FSViewPart::slotShowDepthMenu() 0266 { 0267 _depthMenu->menu()->clear(); 0268 _view->addDepthStopItems(_depthMenu->menu(), 1501, nullptr); 0269 } 0270 0271 void FSViewPart::slotShowColorMenu() 0272 { 0273 _colorMenu->menu()->clear(); 0274 _view->addColorItems(_colorMenu->menu(), 1401); 0275 } 0276 0277 bool FSViewPart::openFile() // never called since openUrl is reimplemented 0278 { 0279 qCDebug(FSVIEWLOG) << localFilePath(); 0280 _view->setPath(localFilePath()); 0281 0282 return true; 0283 } 0284 0285 bool FSViewPart::openUrl(const QUrl &url) 0286 { 0287 qCDebug(FSVIEWLOG) << url.path(); 0288 0289 if (!url.isValid()) { 0290 return false; 0291 } 0292 if (!url.isLocalFile()) { 0293 return false; 0294 } 0295 0296 setUrl(url); 0297 emit setWindowCaption(this->url().toDisplayString(QUrl::PreferLocalFile)); 0298 0299 _view->setPath(this->url().path()); 0300 0301 return true; 0302 } 0303 0304 bool FSViewPart::closeUrl() 0305 { 0306 qCDebug(FSVIEWLOG); 0307 0308 _view->stop(); 0309 0310 return true; 0311 } 0312 0313 void FSViewPart::setNonStandardActionEnabled(const char *actionName, bool enabled) 0314 { 0315 QAction *action = actionCollection()->action(actionName); 0316 action->setEnabled(enabled); 0317 } 0318 0319 void FSViewPart::updateActions() 0320 { 0321 int canDel = 0, canCopy = 0, canMove = 0; 0322 0323 const auto selectedItems = _view->selection(); 0324 for (TreeMapItem *item : selectedItems) { 0325 Inode *inode = static_cast<Inode *>(item); 0326 const QUrl u = QUrl::fromLocalFile(inode->path()); 0327 canCopy++; 0328 if (KProtocolManager::supportsDeleting(u)) { 0329 canDel++; 0330 } 0331 if (KProtocolManager::supportsMoving(u)) { 0332 canMove++; 0333 } 0334 } 0335 0336 // Standard KNavigationExtension actions. 0337 emit _ext->enableAction("copy", canCopy > 0); 0338 emit _ext->enableAction("cut", canMove > 0); 0339 // Custom actions. 0340 //setNonStandardActionEnabled("rename", canMove > 0 ); // FIXME 0341 setNonStandardActionEnabled("move_to_trash", (canDel > 0 && canMove > 0)); 0342 setNonStandardActionEnabled("delete", canDel > 0); 0343 setNonStandardActionEnabled("editMimeType", _view->selection().count() == 1); 0344 setNonStandardActionEnabled("properties", _view->selection().count() == 1); 0345 0346 const KFileItemList items = selectedFileItems(); 0347 emit _ext->selectionInfo(items); 0348 0349 if (canCopy > 0) { 0350 stateChanged(QStringLiteral("has_selection")); 0351 } else { 0352 stateChanged(QStringLiteral("has_no_selection")); 0353 } 0354 0355 qCDebug(FSVIEWLOG) << "deletable" << canDel; 0356 } 0357 0358 KFileItemList FSViewPart::selectedFileItems() const 0359 { 0360 const auto selectedItems = _view->selection(); 0361 KFileItemList items; 0362 items.reserve(selectedItems.count()); 0363 for (TreeMapItem *item : selectedItems) { 0364 Inode *inode = static_cast<Inode *>(item); 0365 const QUrl u = QUrl::fromLocalFile(inode->path()); 0366 const QString mimetype = inode->mimeType().name(); 0367 const QFileInfo &info = inode->fileInfo(); 0368 mode_t mode = 0369 info.isFile() ? S_IFREG : 0370 info.isDir() ? S_IFDIR : 0371 info.isSymLink() ? S_IFLNK : (mode_t) - 1; 0372 items.append(KFileItem(u, mimetype, mode)); 0373 } 0374 return items; 0375 } 0376 0377 void FSViewPart::contextMenu(TreeMapItem * /*item*/, const QPoint &p) 0378 { 0379 int canDel = 0, canCopy = 0, canMove = 0; 0380 0381 const auto selectedItems = _view->selection(); 0382 for (TreeMapItem *item : selectedItems) { 0383 Inode *inode = static_cast<Inode *>(item); 0384 const QUrl u = QUrl::fromLocalFile(inode->path()); 0385 0386 canCopy++; 0387 if (KProtocolManager::supportsDeleting(u)) { 0388 canDel++; 0389 } 0390 if (KProtocolManager::supportsMoving(u)) { 0391 canMove++; 0392 } 0393 } 0394 0395 QList<QAction *> editActions; 0396 KParts::NavigationExtension::ActionGroupMap actionGroups; 0397 KParts::NavigationExtension::PopupFlags flags = KParts::NavigationExtension::ShowUrlOperations | 0398 KParts::NavigationExtension::ShowProperties; 0399 0400 bool addTrash = (canMove > 0); 0401 bool addDel = false; 0402 if (canDel == 0) { 0403 flags |= KParts::NavigationExtension::NoDeletion; 0404 } else { 0405 if (!url().isLocalFile()) { 0406 addDel = true; 0407 } else if (QApplication::keyboardModifiers() & Qt::ShiftModifier) { 0408 addTrash = false; 0409 addDel = true; 0410 } else { 0411 KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::IncludeGlobals); 0412 KConfigGroup configGroup(globalConfig, "KDE"); 0413 addDel = configGroup.readEntry("ShowDeleteCommand", false); 0414 } 0415 } 0416 0417 if (addTrash) { 0418 editActions.append(actionCollection()->action(QStringLiteral("move_to_trash"))); 0419 } 0420 if (addDel) { 0421 editActions.append(actionCollection()->action(QStringLiteral("delete"))); 0422 } 0423 0424 // FIXME: rename is currently unavailable. Requires popup renaming. 0425 // if (canMove) 0426 // editActions.append(actionCollection()->action("rename")); 0427 actionGroups.insert(QStringLiteral("editactions"), editActions); 0428 0429 const KFileItemList items = selectedFileItems(); 0430 if (items.count() > 0) { 0431 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0432 emit _ext->popupMenu(_view->mapToGlobal(p), items, 0433 KParts::OpenUrlArguments(), 0434 BrowserArguments(), 0435 flags, 0436 actionGroups); 0437 #else 0438 emit _ext->browserPopupMenuFromFiles(_view->mapToGlobal(p), items, 0439 KParts::OpenUrlArguments(), 0440 BrowserArguments(), 0441 flags, 0442 actionGroups); 0443 #endif 0444 } 0445 } 0446 0447 void FSViewPart::slotProperties() 0448 { 0449 QList<QUrl> urlList; 0450 if (view()) { 0451 urlList = view()->selectedUrls(); 0452 } 0453 0454 if (!urlList.isEmpty()) { 0455 KPropertiesDialog::showDialog(urlList.first(), view()); 0456 } 0457 } 0458 0459 // FSViewNavigationExtension 0460 0461 FSViewNavigationExtension::FSViewNavigationExtension(FSViewPart *viewPart) 0462 : BrowserExtension(viewPart) 0463 { 0464 _view = viewPart->view(); 0465 } 0466 0467 FSViewNavigationExtension::~FSViewNavigationExtension() 0468 {} 0469 0470 void FSViewNavigationExtension::del() 0471 { 0472 const QList<QUrl> urls = _view->selectedUrls(); 0473 KJobUiDelegate* baseUiDelegate = KIO::createDefaultJobUiDelegate(KJobUiDelegate::Flags{}, _view); 0474 KIO::JobUiDelegate* uiDelegate = qobject_cast<KIO::JobUiDelegate*>(baseUiDelegate); 0475 uiDelegate->setWindow(_view); 0476 if (uiDelegate && uiDelegate->askDeleteConfirmation(urls, 0477 KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) { 0478 KIO::Job *job = KIO::del(urls); 0479 KJobWidgets::setWindow(job, _view); 0480 job->uiDelegate()->setAutoErrorHandlingEnabled(true); 0481 connect(job, &KJob::result, this, &FSViewNavigationExtension::refresh); 0482 } 0483 } 0484 0485 void FSViewNavigationExtension::trash() 0486 { 0487 bool deleteNotTrash = ((QGuiApplication::keyboardModifiers() & Qt::ShiftModifier) != 0); 0488 if (deleteNotTrash) { 0489 del(); 0490 } else { 0491 KJobUiDelegate* baseUiDelegate = KIO::createDefaultJobUiDelegate(KJobUiDelegate::Flags{}, _view); 0492 KIO::JobUiDelegate* uiDelegate = qobject_cast<KIO::JobUiDelegate*>(baseUiDelegate); 0493 uiDelegate->setWindow(_view); 0494 const QList<QUrl> urls = _view->selectedUrls(); 0495 if (uiDelegate && uiDelegate->askDeleteConfirmation(urls, 0496 KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) { 0497 KIO::Job *job = KIO::trash(urls); 0498 KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Trash, urls, QUrl("trash:/"), job); 0499 KJobWidgets::setWindow(job, _view); 0500 job->uiDelegate()->setAutoErrorHandlingEnabled(true); 0501 connect(job, &KJob::result, this, &FSViewNavigationExtension::refresh); 0502 } 0503 } 0504 } 0505 0506 void FSViewNavigationExtension::copySelection(bool move) 0507 { 0508 QMimeData *data = new QMimeData; 0509 data->setUrls(_view->selectedUrls()); 0510 KIO::setClipboardDataCut(data, move); 0511 QApplication::clipboard()->setMimeData(data); 0512 } 0513 0514 void FSViewNavigationExtension::editMimeType() 0515 { 0516 Inode *i = (Inode *) _view->selection().first(); 0517 if (i) { 0518 KMimeTypeEditor::editMimeType(i->mimeType().name(), _view); 0519 } 0520 } 0521 0522 // refresh treemap at end of KIO jobs 0523 void FSViewNavigationExtension::refresh() 0524 { 0525 // only need to refresh common ancestor for all selected items 0526 TreeMapItem *commonParent = _view->selection().commonParent(); 0527 if (!commonParent) { 0528 return; 0529 } 0530 0531 /* if commonParent is a file, update parent directory */ 0532 if (!((Inode *)commonParent)->isDir()) { 0533 commonParent = commonParent->parent(); 0534 if (!commonParent) { 0535 return; 0536 } 0537 } 0538 0539 qCDebug(FSVIEWLOG) << "refreshing" 0540 << ((Inode *)commonParent)->path(); 0541 0542 _view->requestUpdate((Inode *)commonParent); 0543 } 0544 0545 void FSViewNavigationExtension::itemSingleClicked(TreeMapItem *i) 0546 { 0547 if (_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) { 0548 selected(i); 0549 } 0550 } 0551 0552 0553 void FSViewNavigationExtension::itemDoubleClicked(TreeMapItem *i) 0554 { 0555 if (!_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) { 0556 selected(i); 0557 } 0558 } 0559 0560 void FSViewNavigationExtension::selected(TreeMapItem *i) 0561 { 0562 if (!i) { 0563 return; 0564 } 0565 0566 QUrl url = QUrl::fromLocalFile(((Inode *)i)->path()); 0567 emit openUrlRequest(url); 0568 } 0569 0570 #include "fsview_part.moc"