1 /* This file is part of FSView.
2 SPDX-FileCopyrightText: 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
3
4 Some file management code taken from the Dolphin file manager:
5 SPDX-FileCopyrightText: 2006-2009 Peter Penz <peter.penz19@mail.com>
6
7 SPDX-License-Identifier: GPL-2.0-only
8 */
9
10 /*
11 * The KPart embedding the FSView widget
12 */
13
14 #include "fsview_part.h"
15
16 #include <QClipboard>
17 #include <QTimer>
18 #include <QStyle>
19
20 #include <kfileitem.h>
21 #include <kpluginfactory.h>
22 #if KPARTS_VERSION >= QT_VERSION_CHECK(5, 77, 0)
23 #include <KPluginMetaData>
24 #else
25 #include <kaboutdata.h>
26 #endif
27
28 #include <kprotocolmanager.h>
29 #include <kio/copyjob.h>
30 #include <kio/deletejob.h>
31 #include <kio/paste.h>
32 #include <kmessagebox.h>
33 #include <kactionmenu.h>
34 #include <kactioncollection.h>
35 #include <kpropertiesdialog.h>
36 #include <KMimeTypeEditor>
37 #include <kio/jobuidelegate.h>
38 #include <KIO/FileUndoManager>
39 #include <KJobWidgets>
40 #include <kconfig.h>
41 #include <kconfiggroup.h>
42 #include <ksharedconfig.h>
43 #include <KLocalizedString>
44 #include <KIO/ApplicationLauncherJob>
45
46 #include <QApplication>
47 #include <QMimeData>
48
49 #include "fsviewdebug.h"
50
51 K_PLUGIN_CLASS_WITH_JSON(FSViewPart, "fsview_part.json")
52
53 // FSJob, for progress
54
FSJob(FSView * v)55 FSJob::FSJob(FSView *v)
56 : KIO::Job()
57 {
58 _view = v;
59 connect(v, &FSView::progress, this, &FSJob::progressSlot);
60 }
61
kill(bool)62 void FSJob::kill(bool /*quietly*/)
63 {
64 _view->stop();
65
66 Job::kill();
67 }
68
progressSlot(int percent,int dirs,const QString & cDir)69 void FSJob::progressSlot(int percent, int dirs, const QString &cDir)
70 {
71 if (percent < 100) {
72 emitPercent(percent, 100);
73 slotInfoMessage(this, i18np("Read 1 folder, in %2",
74 "Read %1 folders, in %2",
75 dirs, cDir), QString());
76 } else {
77 slotInfoMessage(this, i18np("1 folder", "%1 folders", dirs), QString());
78 }
79 }
80
81 // FSViewPart
82
FSViewPart(QWidget * parentWidget,QObject * parent,const KPluginMetaData & metaData,const QList<QVariant> &)83 FSViewPart::FSViewPart(QWidget *parentWidget,
84 QObject *parent,
85 #if KPARTS_VERSION >= QT_VERSION_CHECK(5, 77, 0)
86 const KPluginMetaData& metaData,
87 #endif
88 const QList<QVariant> & /* args */)
89 : KParts::ReadOnlyPart(parent)
90 {
91 #if KPARTS_VERSION >= QT_VERSION_CHECK(5, 77, 0)
92 setMetaData(metaData);
93 #else
94 KAboutData aboutData(QStringLiteral("fsview"), i18n("FSView"), QStringLiteral("0.1"),
95 i18n("Filesystem Viewer"),
96 KAboutLicense::GPL,
97 i18n("(c) 2002, Josef Weidendorfer"));
98 setComponentData(aboutData);
99 #endif
100
101 _view = new FSView(new Inode(), parentWidget);
102 _view->setWhatsThis(i18n("<p>This is the FSView plugin, a graphical "
103 "browsing mode showing filesystem utilization "
104 "by using a tree map visualization.</p>"
105 "<p>Note that in this mode, automatic updating "
106 "when filesystem changes are made "
107 "is intentionally <b>not</b> done.</p>"
108 "<p>For details on usage and options available, "
109 "see the online help under "
110 "menu 'Help/FSView Manual'.</p>"));
111
112 _view->show();
113 setWidget(_view);
114
115 _ext = new FSViewBrowserExtension(this);
116 _job = nullptr;
117
118 _areaMenu = new KActionMenu(i18n("Stop at Area"),
119 actionCollection());
120 actionCollection()->addAction(QStringLiteral("treemap_areadir"), _areaMenu);
121 _depthMenu = new KActionMenu(i18n("Stop at Depth"),
122 actionCollection());
123 actionCollection()->addAction(QStringLiteral("treemap_depthdir"), _depthMenu);
124 _visMenu = new KActionMenu(i18n("Visualization"),
125 actionCollection());
126 actionCollection()->addAction(QStringLiteral("treemap_visdir"), _visMenu);
127
128 _colorMenu = new KActionMenu(i18n("Color Mode"),
129 actionCollection());
130 actionCollection()->addAction(QStringLiteral("treemap_colordir"), _colorMenu);
131
132 QAction *action;
133 action = actionCollection()->addAction(QStringLiteral("help_fsview"));
134 action->setText(i18n("&FSView Manual"));
135 action->setIcon(QIcon::fromTheme(QStringLiteral("fsview")));
136 action->setToolTip(i18n("Show FSView manual"));
137 action->setWhatsThis(i18n("Opens the help browser with the "
138 "FSView documentation"));
139 connect(action, &QAction::triggered, this, &FSViewPart::showHelp);
140
141 connect(_visMenu->menu(), &QMenu::aboutToShow,this, &FSViewPart::slotShowVisMenu);
142 connect(_areaMenu->menu(), &QMenu::aboutToShow, this, &FSViewPart::slotShowAreaMenu);
143 connect(_depthMenu->menu(), &QMenu::aboutToShow, this, &FSViewPart::slotShowDepthMenu);
144 connect(_colorMenu->menu(), &QMenu::aboutToShow, this, &FSViewPart::slotShowColorMenu);
145
146 // Both of these click signals are connected. Whether a single or
147 // double click activates an item is checked against the current
148 // style setting when the click happens.
149 connect(_view, &FSView::clicked, _ext, &FSViewBrowserExtension::itemSingleClicked);
150 connect(_view, &FSView::doubleClicked, _ext, &FSViewBrowserExtension::itemDoubleClicked);
151
152 connect(_view, &TreeMapWidget::returnPressed, _ext, &FSViewBrowserExtension::selected);
153 connect(_view, QOverload<>::of(&TreeMapWidget::selectionChanged), this, &FSViewPart::updateActions);
154 connect(_view, &TreeMapWidget::contextMenuRequested, this, &FSViewPart::contextMenu);
155
156 connect(_view, &FSView::started, this, &FSViewPart::startedSlot);
157 connect(_view, &FSView::completed, this, &FSViewPart::completedSlot);
158
159 // Create common file management actions - this is necessary in KDE4
160 // as these common actions are no longer automatically part of KParts.
161 // Much of this is taken from Dolphin.
162 // FIXME: Renaming didn't even seem to work in KDE3! Implement (non-inline) renaming
163 // functionality.
164 //QAction* renameAction = m_actionCollection->addAction("rename");
165 //rename->setText(i18nc("@action:inmenu Edit", "Rename..."));
166 //rename->setShortcut(Qt::Key_F2);
167
168 QAction *moveToTrashAction = actionCollection()->addAction(QStringLiteral("move_to_trash"));
169 moveToTrashAction->setText(i18nc("@action:inmenu File", "Move to Trash"));
170 moveToTrashAction->setIcon(QIcon::fromTheme(QStringLiteral("user-trash")));
171 actionCollection()->setDefaultShortcut(moveToTrashAction, QKeySequence(QKeySequence::Delete));
172 connect(moveToTrashAction, &QAction::triggered, _ext, &FSViewBrowserExtension::trash);
173
174 QAction *deleteAction = actionCollection()->addAction(QStringLiteral("delete"));
175 deleteAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
176 deleteAction->setText(i18nc("@action:inmenu File", "Delete"));
177 actionCollection()->setDefaultShortcut(deleteAction, QKeySequence(Qt::SHIFT | Qt::Key_Delete));
178 connect(deleteAction, &QAction::triggered, _ext, &FSViewBrowserExtension::del);
179
180 QAction *editMimeTypeAction = actionCollection()->addAction(QStringLiteral("editMimeType"));
181 editMimeTypeAction->setText(i18nc("@action:inmenu Edit", "&Edit File Type..."));
182 connect(editMimeTypeAction, &QAction::triggered, _ext, &FSViewBrowserExtension::editMimeType);
183
184 QAction *propertiesAction = actionCollection()->addAction(QStringLiteral("properties"));
185 propertiesAction->setText(i18nc("@action:inmenu File", "Properties"));
186 propertiesAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
187 propertiesAction->setShortcut(Qt::ALT | Qt::Key_Return);
188 connect(propertiesAction, &QAction::triggered, this, &FSViewPart::slotProperties);
189
190 QTimer::singleShot(1, this, SLOT(showInfo()));
191
192 updateActions();
193
194 setXMLFile(QStringLiteral("fsview_part.rc"));
195 }
196
~FSViewPart()197 FSViewPart::~FSViewPart()
198 {
199 qCDebug(FSVIEWLOG);
200
201 delete _job;
202 _view->saveFSOptions();
203 }
204
componentName() const205 QString FSViewPart::componentName() const
206 {
207 // also the part ui.rc file is in the program folder
208 // TODO: change the component name to "fsviewpart" by removing this method and
209 // adapting the folder where the file is placed.
210 // Needs a way to also move any potential custom user ui.rc files
211 // from fsview/fsview_part.rc to fsviewpart/fsview_part.rc
212 return QStringLiteral("fsview");
213 }
214
showInfo()215 void FSViewPart::showInfo()
216 {
217 QString info;
218 info = i18n("FSView intentionally does not support automatic updates "
219 "when changes are made to files or directories, "
220 "currently visible in FSView, from the outside.\n"
221 "For details, see the 'Help/FSView Manual'.");
222
223 KMessageBox::information(_view, info, QString(), QStringLiteral("ShowFSViewInfo"));
224 }
225
showHelp()226 void FSViewPart::showHelp()
227 {
228 const KService::Ptr helpCenter = KService::serviceByDesktopName(QStringLiteral("org.kde.help"));
229 auto job = new KIO::ApplicationLauncherJob(helpCenter);
230 job->setUrls({QUrl(QStringLiteral("help:/konqueror/index.html#fsview"))});
231 job->start();
232 }
233
startedSlot()234 void FSViewPart::startedSlot()
235 {
236 _job = new FSJob(_view);
237 _job->setUiDelegate(new KIO::JobUiDelegate());
238 emit started(_job);
239 }
240
completedSlot(int dirs)241 void FSViewPart::completedSlot(int dirs)
242 {
243 if (_job) {
244 _job->progressSlot(100, dirs, QString());
245 delete _job;
246 _job = nullptr;
247 }
248
249 KConfigGroup cconfig = _view->config()->group("MetricCache");
250 _view->saveMetric(&cconfig);
251
252 emit completed();
253 }
254
slotShowVisMenu()255 void FSViewPart::slotShowVisMenu()
256 {
257 _visMenu->menu()->clear();
258 _view->addVisualizationItems(_visMenu->menu(), 1301);
259 }
260
slotShowAreaMenu()261 void FSViewPart::slotShowAreaMenu()
262 {
263 _areaMenu->menu()->clear();
264 _view->addAreaStopItems(_areaMenu->menu(), 1001, nullptr);
265 }
266
slotShowDepthMenu()267 void FSViewPart::slotShowDepthMenu()
268 {
269 _depthMenu->menu()->clear();
270 _view->addDepthStopItems(_depthMenu->menu(), 1501, nullptr);
271 }
272
slotShowColorMenu()273 void FSViewPart::slotShowColorMenu()
274 {
275 _colorMenu->menu()->clear();
276 _view->addColorItems(_colorMenu->menu(), 1401);
277 }
278
openFile()279 bool FSViewPart::openFile() // never called since openUrl is reimplemented
280 {
281 qCDebug(FSVIEWLOG) << localFilePath();
282 _view->setPath(localFilePath());
283
284 return true;
285 }
286
openUrl(const QUrl & url)287 bool FSViewPart::openUrl(const QUrl &url)
288 {
289 qCDebug(FSVIEWLOG) << url.path();
290
291 if (!url.isValid()) {
292 return false;
293 }
294 if (!url.isLocalFile()) {
295 return false;
296 }
297
298 setUrl(url);
299 emit setWindowCaption(this->url().toDisplayString(QUrl::PreferLocalFile));
300
301 _view->setPath(this->url().path());
302
303 return true;
304 }
305
closeUrl()306 bool FSViewPart::closeUrl()
307 {
308 qCDebug(FSVIEWLOG);
309
310 _view->stop();
311
312 return true;
313 }
314
setNonStandardActionEnabled(const char * actionName,bool enabled)315 void FSViewPart::setNonStandardActionEnabled(const char *actionName, bool enabled)
316 {
317 QAction *action = actionCollection()->action(actionName);
318 action->setEnabled(enabled);
319 }
320
updateActions()321 void FSViewPart::updateActions()
322 {
323 int canDel = 0, canCopy = 0, canMove = 0;
324
325 const auto selectedItems = _view->selection();
326 for (TreeMapItem *item : selectedItems) {
327 Inode *inode = static_cast<Inode *>(item);
328 const QUrl u = QUrl::fromLocalFile(inode->path());
329 canCopy++;
330 if (KProtocolManager::supportsDeleting(u)) {
331 canDel++;
332 }
333 if (KProtocolManager::supportsMoving(u)) {
334 canMove++;
335 }
336 }
337
338 // Standard KBrowserExtension actions.
339 emit _ext->enableAction("copy", canCopy > 0);
340 emit _ext->enableAction("cut", canMove > 0);
341 // Custom actions.
342 //setNonStandardActionEnabled("rename", canMove > 0 ); // FIXME
343 setNonStandardActionEnabled("move_to_trash", (canDel > 0 && canMove > 0));
344 setNonStandardActionEnabled("delete", canDel > 0);
345 setNonStandardActionEnabled("editMimeType", _view->selection().count() == 1);
346 setNonStandardActionEnabled("properties", _view->selection().count() == 1);
347
348 const KFileItemList items = selectedFileItems();
349 emit _ext->selectionInfo(items);
350
351 if (canCopy > 0) {
352 stateChanged(QStringLiteral("has_selection"));
353 } else {
354 stateChanged(QStringLiteral("has_no_selection"));
355 }
356
357 qCDebug(FSVIEWLOG) << "deletable" << canDel;
358 }
359
selectedFileItems() const360 KFileItemList FSViewPart::selectedFileItems() const
361 {
362 const auto selectedItems = _view->selection();
363 KFileItemList items;
364 items.reserve(selectedItems.count());
365 for (TreeMapItem *item : selectedItems) {
366 Inode *inode = static_cast<Inode *>(item);
367 const QUrl u = QUrl::fromLocalFile(inode->path());
368 const QString mimetype = inode->mimeType().name();
369 const QFileInfo &info = inode->fileInfo();
370 mode_t mode =
371 info.isFile() ? S_IFREG :
372 info.isDir() ? S_IFDIR :
373 info.isSymLink() ? S_IFLNK : (mode_t) - 1;
374 items.append(KFileItem(u, mimetype, mode));
375 }
376 return items;
377 }
378
contextMenu(TreeMapItem *,const QPoint & p)379 void FSViewPart::contextMenu(TreeMapItem * /*item*/, const QPoint &p)
380 {
381 int canDel = 0, canCopy = 0, canMove = 0;
382
383 const auto selectedItems = _view->selection();
384 for (TreeMapItem *item : selectedItems) {
385 Inode *inode = static_cast<Inode *>(item);
386 const QUrl u = QUrl::fromLocalFile(inode->path());
387
388 canCopy++;
389 if (KProtocolManager::supportsDeleting(u)) {
390 canDel++;
391 }
392 if (KProtocolManager::supportsMoving(u)) {
393 canMove++;
394 }
395 }
396
397 QList<QAction *> editActions;
398 KParts::BrowserExtension::ActionGroupMap actionGroups;
399 KParts::BrowserExtension::PopupFlags flags = KParts::BrowserExtension::ShowUrlOperations |
400 KParts::BrowserExtension::ShowProperties;
401
402 bool addTrash = (canMove > 0);
403 bool addDel = false;
404 if (canDel == 0) {
405 flags |= KParts::BrowserExtension::NoDeletion;
406 } else {
407 if (!url().isLocalFile()) {
408 addDel = true;
409 } else if (QApplication::keyboardModifiers() & Qt::ShiftModifier) {
410 addTrash = false;
411 addDel = true;
412 } else {
413 KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::IncludeGlobals);
414 KConfigGroup configGroup(globalConfig, "KDE");
415 addDel = configGroup.readEntry("ShowDeleteCommand", false);
416 }
417 }
418
419 if (addTrash) {
420 editActions.append(actionCollection()->action(QStringLiteral("move_to_trash")));
421 }
422 if (addDel) {
423 editActions.append(actionCollection()->action(QStringLiteral("delete")));
424 }
425
426 // FIXME: rename is currently unavailable. Requires popup renaming.
427 // if (canMove)
428 // editActions.append(actionCollection()->action("rename"));
429 actionGroups.insert(QStringLiteral("editactions"), editActions);
430
431 const KFileItemList items = selectedFileItems();
432 if (items.count() > 0)
433 emit _ext->popupMenu(_view->mapToGlobal(p), items,
434 KParts::OpenUrlArguments(),
435 KParts::BrowserArguments(),
436 flags,
437 actionGroups);
438 }
439
slotProperties()440 void FSViewPart::slotProperties()
441 {
442 QList<QUrl> urlList;
443 if (view()) {
444 urlList = view()->selectedUrls();
445 }
446
447 if (!urlList.isEmpty()) {
448 KPropertiesDialog::showDialog(urlList.first(), view());
449 }
450 }
451
452 // FSViewBrowserExtension
453
FSViewBrowserExtension(FSViewPart * viewPart)454 FSViewBrowserExtension::FSViewBrowserExtension(FSViewPart *viewPart)
455 : KParts::BrowserExtension(viewPart)
456 {
457 _view = viewPart->view();
458 }
459
~FSViewBrowserExtension()460 FSViewBrowserExtension::~FSViewBrowserExtension()
461 {}
462
del()463 void FSViewBrowserExtension::del()
464 {
465 const QList<QUrl> urls = _view->selectedUrls();
466 KIO::JobUiDelegate uiDelegate;
467 uiDelegate.setWindow(_view);
468 if (uiDelegate.askDeleteConfirmation(urls,
469 KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) {
470 KIO::Job *job = KIO::del(urls);
471 KJobWidgets::setWindow(job, _view);
472 job->uiDelegate()->setAutoErrorHandlingEnabled(true);
473 connect(job, &KJob::result, this, &FSViewBrowserExtension::refresh);
474 }
475 }
476
trash()477 void FSViewBrowserExtension::trash()
478 {
479 bool deleteNotTrash = ((QGuiApplication::keyboardModifiers() & Qt::ShiftModifier) != 0);
480 if (deleteNotTrash) {
481 del();
482 } else {
483 KIO::JobUiDelegate uiDelegate;
484 uiDelegate.setWindow(_view);
485 const QList<QUrl> urls = _view->selectedUrls();
486 if (uiDelegate.askDeleteConfirmation(urls,
487 KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) {
488 KIO::Job *job = KIO::trash(urls);
489 KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Trash, urls, QUrl("trash:/"), job);
490 KJobWidgets::setWindow(job, _view);
491 job->uiDelegate()->setAutoErrorHandlingEnabled(true);
492 connect(job, &KJob::result, this, &FSViewBrowserExtension::refresh);
493 }
494 }
495 }
496
copySelection(bool move)497 void FSViewBrowserExtension::copySelection(bool move)
498 {
499 QMimeData *data = new QMimeData;
500 data->setUrls(_view->selectedUrls());
501 KIO::setClipboardDataCut(data, move);
502 QApplication::clipboard()->setMimeData(data);
503 }
504
editMimeType()505 void FSViewBrowserExtension::editMimeType()
506 {
507 Inode *i = (Inode *) _view->selection().first();
508 if (i) {
509 KMimeTypeEditor::editMimeType(i->mimeType().name(), _view);
510 }
511 }
512
513 // refresh treemap at end of KIO jobs
refresh()514 void FSViewBrowserExtension::refresh()
515 {
516 // only need to refresh common ancestor for all selected items
517 TreeMapItem *commonParent = _view->selection().commonParent();
518 if (!commonParent) {
519 return;
520 }
521
522 /* if commonParent is a file, update parent directory */
523 if (!((Inode *)commonParent)->isDir()) {
524 commonParent = commonParent->parent();
525 if (!commonParent) {
526 return;
527 }
528 }
529
530 qCDebug(FSVIEWLOG) << "refreshing"
531 << ((Inode *)commonParent)->path();
532
533 _view->requestUpdate((Inode *)commonParent);
534 }
535
itemSingleClicked(TreeMapItem * i)536 void FSViewBrowserExtension::itemSingleClicked(TreeMapItem *i)
537 {
538 if (_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) {
539 selected(i);
540 }
541 }
542
543
itemDoubleClicked(TreeMapItem * i)544 void FSViewBrowserExtension::itemDoubleClicked(TreeMapItem *i)
545 {
546 if (!_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) {
547 selected(i);
548 }
549 }
550
selected(TreeMapItem * i)551 void FSViewBrowserExtension::selected(TreeMapItem *i)
552 {
553 if (!i) {
554 return;
555 }
556
557 QUrl url = QUrl::fromLocalFile(((Inode *)i)->path());
558 emit openUrlRequest(url);
559 }
560
561 #include "fsview_part.moc"
562