File indexing completed on 2024-04-28 08:50:20

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"