1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 1999, 2000 Stephan Kulow <coolo@kde.org>
4 SPDX-FileCopyrightText: 1999, 2000, 2001, 2002, 2003 Carsten Pfeiffer <pfeiffer@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include <config-kiofilewidgets.h>
10#include <defaults-kfile.h> // ConfigGroup, DefaultShowHidden, DefaultDirsFirst, DefaultSortReversed
11
12#include "../utils_p.h"
13
14#include "kdirmodel.h"
15#include "kdiroperator.h"
16#include "kdiroperatordetailview_p.h"
17#include "kdiroperatoriconview_p.h"
18#include "kdirsortfilterproxymodel.h"
19#include "kfileitem.h"
20#include "kfileitemselectionemblem.h"
21#include "kfilemetapreview_p.h"
22#include "knewfilemenu.h"
23#include "kpreviewwidgetbase.h"
24#include "statjob.h"
25#include <KCompletion>
26#include <KConfigGroup>
27#include <KDirLister>
28#include <KFileItemActions>
29#include <KFileItemListProperties>
30#include <KIO/OpenFileManagerWindowJob>
31#include <KIO/RenameFileDialog>
32#include <KIconLoader>
33#include <KJobWidgets>
34#include <KLocalizedString>
35#include <KMessageBox>
36#include <KProtocolManager>
37#include <KSharedConfig>
38#include <KStandardActions>
39#include <KToggleAction>
40#include <KUrlMimeData>
41#include <kfileitemdelegate.h>
42#include <kfilepreviewgenerator.h>
43#include <kio/askuseractioninterface.h>
44#include <kio/copyjob.h>
45#include <kio/deletejob.h>
46#include <kio/deleteortrashjob.h>
47#include <kio/jobuidelegate.h>
48#include <kio/jobuidelegatefactory.h>
49#include <kio/previewjob.h>
50#include <kio/widgetsaskuseractionhandler.h>
51#include <kpropertiesdialog.h>
52
53#include <QActionGroup>
54#include <QApplication>
55#include <QDebug>
56#include <QHeaderView>
57#include <QListView>
58#include <QMenu>
59#include <QMimeDatabase>
60#include <QProgressBar>
61#include <QRegularExpression>
62#include <QScrollBar>
63#include <QScroller>
64#include <QSplitter>
65#include <QStack>
66#include <QTimer>
67#include <QWheelEvent>
68
69#include <memory>
70
71template class QHash<QString, KFileItem>;
72
73// QDir::SortByMask is not only undocumented, it also omits QDir::Type which is another
74// sorting mode.
75static const int QDirSortMask = QDir::SortByMask | QDir::Type;
76
77class KDirOperatorPrivate
78{
79public:
80 explicit KDirOperatorPrivate(KDirOperator *qq)
81 : q(qq)
82 {
83 m_iconSize = static_cast<int>(KIconLoader::SizeSmall);
84 }
85
86 ~KDirOperatorPrivate();
87
88 enum InlinePreviewState {
89 ForcedToFalse = 0,
90 ForcedToTrue,
91 NotForced,
92 };
93
94 // private methods
95 bool checkPreviewInternal() const;
96 bool openUrl(const QUrl &url, KDirLister::OpenUrlFlags flags = KDirLister::NoFlags);
97 int sortColumn() const;
98 Qt::SortOrder sortOrder() const;
99 void updateSorting(QDir::SortFlags sort);
100
101 bool isReadable(const QUrl &url);
102 bool isSchemeSupported(const QString &scheme) const;
103
104 QPoint progressBarPos() const;
105
106 KFile::FileView allViews();
107
108 QMetaObject::Connection m_connection;
109
110 // A pair to store zoom settings for view kinds
111 struct ZoomSettingsForView {
112 QString name;
113 int defaultValue;
114 };
115
116 // private slots
117 void slotDetailedView();
118 void slotSimpleView();
119 void slotTreeView();
120 void slotDetailedTreeView();
121 void slotIconsView();
122 void slotCompactView();
123 void slotDetailsView();
124 void slotToggleHidden(bool);
125 void slotToggleAllowExpansion(bool);
126 void togglePreview(bool);
127 void toggleInlinePreviews(bool);
128 void slotOpenFileManager();
129 void slotSortByName();
130 void slotSortBySize();
131 void slotSortByDate();
132 void slotSortByType();
133 void slotSortReversed(bool doReverse);
134 void slotToggleDirsFirst();
135 void slotToggleIconsView();
136 void slotToggleCompactView();
137 void slotToggleDetailsView();
138 void slotToggleIgnoreCase();
139 void slotStarted();
140 void slotProgress(int);
141 void slotShowProgress();
142 void slotIOFinished();
143 void slotCanceled();
144 void slotRedirected(const QUrl &);
145 void slotProperties();
146 void slotActivated(const QModelIndex &);
147 void slotSelectionChanged();
148 void openContextMenu(const QPoint &);
149 void triggerPreview(const QModelIndex &);
150 void showPreview();
151 void slotSplitterMoved(int, int);
152 void assureVisibleSelection();
153 void synchronizeSortingState(int, Qt::SortOrder);
154 void slotChangeDecorationPosition();
155 void slotExpandToUrl(const QModelIndex &);
156 void slotItemsChanged();
157 void slotDirectoryCreated(const QUrl &);
158 void slotAskUserDeleteResult(bool allowDelete, const QList<QUrl> &urls, KIO::AskUserActionInterface::DeletionType deletionType, QWidget *parent);
159
160 int iconSizeForViewType(QAbstractItemView *itemView) const;
161 void writeIconZoomSettingsIfNeeded();
162 ZoomSettingsForView zoomSettingsForView() const;
163
164 QList<QAction *> insertOpenWithActions();
165
166 // private members
167 KDirOperator *const q;
168 QStack<QUrl *> m_backStack; ///< Contains all URLs you can reach with the back button.
169 QStack<QUrl *> m_forwardStack; ///< Contains all URLs you can reach with the forward button.
170
171 QModelIndex m_lastHoveredIndex;
172
173 KDirLister *m_dirLister = nullptr;
174 QUrl m_currUrl;
175
176 KCompletion m_completion;
177 KCompletion m_dirCompletion;
178 QDir::SortFlags m_sorting;
179 QStyleOptionViewItem::Position m_decorationPosition = QStyleOptionViewItem::Left;
180
181 QSplitter *m_splitter = nullptr;
182
183 QAbstractItemView *m_itemView = nullptr;
184 KDirModel *m_dirModel = nullptr;
185 KDirSortFilterProxyModel *m_proxyModel = nullptr;
186
187 KFileItemList m_pendingMimeTypes;
188
189 // the enum KFile::FileView as an int
190 int m_viewKind;
191 int m_defaultView;
192
193 KFile::Modes m_mode;
194 QProgressBar *m_progressBar = nullptr;
195
196 KPreviewWidgetBase *m_preview = nullptr;
197 QUrl m_previewUrl;
198 int m_previewWidth = 0;
199
200 bool m_completeListDirty = false;
201 bool m_followNewDirectories = true;
202 bool m_followSelectedDirectories = true;
203 bool m_onlyDoubleClickSelectsFiles = !qApp->style()->styleHint(stylehint: QStyle::SH_ItemView_ActivateItemOnSingleClick);
204
205 QUrl m_lastUrl; // used for highlighting a directory on back/cdUp
206
207 QTimer *m_progressDelayTimer = nullptr;
208 KActionMenu *m_actionMenu = nullptr;
209 KActionCollection *m_actionCollection = nullptr;
210 KNewFileMenu *m_newFileMenu = nullptr;
211 KConfigGroup *m_configGroup = nullptr;
212 KFilePreviewGenerator *m_previewGenerator = nullptr;
213 KActionMenu *m_decorationMenu = nullptr;
214 KToggleAction *m_leftAction = nullptr;
215 KFileItemActions *m_itemActions = nullptr;
216
217 int m_dropOptions = 0;
218 int m_iconSize = 0;
219 InlinePreviewState m_inlinePreviewState = NotForced;
220 bool m_dirHighlighting = true;
221 bool m_showPreviews = false;
222 bool m_shouldFetchForItems = false;
223 bool m_isSaving = false;
224 bool m_showOpenWithActions = false;
225 bool m_isTouchEvent = false;
226 bool m_isTouchDrag = false;
227 bool m_keyNavigation = false;
228
229 QList<QUrl> m_itemsToBeSetAsCurrent;
230 QStringList m_supportedSchemes;
231
232 QHash<KDirOperator::Action, QAction *> m_actions;
233};
234
235KDirOperatorPrivate::~KDirOperatorPrivate()
236{
237 if (m_itemView) {
238 // fix libc++ crash: its unique_ptr implementation has already set 'd' to null
239 // and the event filter will get a QEvent::Leave event if we don't remove it.
240 m_itemView->removeEventFilter(obj: q);
241 m_itemView->viewport()->removeEventFilter(obj: q);
242 }
243
244 delete m_itemView;
245 m_itemView = nullptr;
246
247 // TODO:
248 // if (configGroup) {
249 // itemView->writeConfig(configGroup);
250 // }
251
252 qDeleteAll(c: m_backStack);
253 qDeleteAll(c: m_forwardStack);
254
255 // The parent KDirOperator will delete these
256 m_preview = nullptr;
257 m_proxyModel = nullptr;
258 m_dirModel = nullptr;
259 m_progressDelayTimer = nullptr;
260
261 m_dirLister = nullptr; // deleted by KDirModel
262
263 delete m_configGroup;
264 m_configGroup = nullptr;
265}
266
267QPoint KDirOperatorPrivate::progressBarPos() const
268{
269 const int frameWidth = q->style()->pixelMetric(metric: QStyle::PM_DefaultFrameWidth);
270 return QPoint(frameWidth, q->height() - m_progressBar->height() - frameWidth);
271}
272
273KDirOperator::KDirOperator(const QUrl &_url, QWidget *parent)
274 : QWidget(parent)
275 , d(new KDirOperatorPrivate(this))
276{
277 d->m_splitter = new QSplitter(this);
278 d->m_splitter->setChildrenCollapsible(false);
279 connect(sender: d->m_splitter, signal: &QSplitter::splitterMoved, context: this, slot: [this](int pos, int index) {
280 d->slotSplitterMoved(pos, index);
281 });
282
283 d->m_preview = nullptr;
284
285 d->m_mode = KFile::File;
286 d->m_viewKind = KFile::Simple;
287
288 if (_url.isEmpty()) { // no dir specified -> current dir
289 QString strPath = QDir::currentPath();
290 strPath.append(c: QLatin1Char('/'));
291 d->m_currUrl = QUrl::fromLocalFile(localfile: strPath);
292 } else {
293 d->m_currUrl = _url;
294 if (d->m_currUrl.scheme().isEmpty()) {
295 d->m_currUrl.setScheme(QStringLiteral("file"));
296 }
297
298 // make sure we have a trailing slash!
299 Utils::appendSlashToPath(url&: d->m_currUrl);
300 }
301
302 // We set the direction of this widget to LTR, since even on RTL desktops
303 // viewing directory listings in RTL mode makes people's head explode.
304 // Is this the correct place? Maybe it should be in some lower level widgets...?
305 setLayoutDirection(Qt::LeftToRight);
306 setDirLister(new KDirLister());
307
308 connect(sender: &d->m_completion, signal: &KCompletion::match, context: this, slot: &KDirOperator::slotCompletionMatch);
309
310 d->m_progressBar = new QProgressBar(this);
311 d->m_progressBar->setObjectName(QStringLiteral("d->m_progressBar"));
312 d->m_progressBar->setFormat(i18nc("Loading bar percent value", "%p%"));
313 d->m_progressBar->adjustSize();
314 const int frameWidth = style()->pixelMetric(metric: QStyle::PM_DefaultFrameWidth);
315 d->m_progressBar->move(ax: frameWidth, ay: height() - d->m_progressBar->height() - frameWidth);
316
317 d->m_progressDelayTimer = new QTimer(this);
318 d->m_progressDelayTimer->setObjectName(QStringLiteral("d->m_progressBar delay timer"));
319 connect(sender: d->m_progressDelayTimer, signal: &QTimer::timeout, context: this, slot: [this]() {
320 d->slotShowProgress();
321 });
322
323 d->m_completeListDirty = false;
324
325 // action stuff
326 setupActions();
327 setupMenu();
328
329 d->m_sorting = QDir::NoSort; // so updateSorting() doesn't think nothing has changed
330 d->updateSorting(sort: QDir::Name | QDir::DirsFirst);
331
332 setFocusPolicy(Qt::WheelFocus);
333 setAcceptDrops(true);
334}
335
336KDirOperator::~KDirOperator()
337{
338 resetCursor();
339 disconnect(sender: d->m_dirLister, signal: nullptr, receiver: this, member: nullptr);
340}
341
342void KDirOperator::setSorting(QDir::SortFlags spec)
343{
344 d->updateSorting(sort: spec);
345}
346
347QDir::SortFlags KDirOperator::sorting() const
348{
349 return d->m_sorting;
350}
351
352bool KDirOperator::isRoot() const
353{
354#ifdef Q_OS_WIN
355 if (url().isLocalFile()) {
356 const QString path = url().toLocalFile();
357 if (path.length() == 3) {
358 return (path[0].isLetter() && path[1] == QLatin1Char(':') && path[2] == QLatin1Char('/'));
359 }
360 return false;
361 } else
362#endif
363 return url().path() == QLatin1String("/");
364}
365
366KDirLister *KDirOperator::dirLister() const
367{
368 return d->m_dirLister;
369}
370
371void KDirOperator::resetCursor()
372{
373 if (qApp) {
374 QApplication::restoreOverrideCursor();
375 }
376 d->m_progressBar->hide();
377}
378
379void KDirOperator::sortByName()
380{
381 d->updateSorting(sort: (d->m_sorting & ~QDirSortMask) | QDir::Name);
382}
383
384void KDirOperator::sortBySize()
385{
386 d->updateSorting(sort: (d->m_sorting & ~QDirSortMask) | QDir::Size);
387}
388
389void KDirOperator::sortByDate()
390{
391 d->updateSorting(sort: (d->m_sorting & ~QDirSortMask) | QDir::Time);
392}
393
394void KDirOperator::sortByType()
395{
396 d->updateSorting(sort: (d->m_sorting & ~QDirSortMask) | QDir::Type);
397}
398
399void KDirOperator::sortReversed()
400{
401 // toggle it, hence the inversion of current state
402 d->slotSortReversed(doReverse: !(d->m_sorting & QDir::Reversed));
403}
404
405void KDirOperator::toggleDirsFirst()
406{
407 d->slotToggleDirsFirst();
408}
409
410void KDirOperator::toggleIgnoreCase()
411{
412 if (d->m_proxyModel != nullptr) {
413 Qt::CaseSensitivity cs = d->m_proxyModel->sortCaseSensitivity();
414 cs = (cs == Qt::CaseSensitive) ? Qt::CaseInsensitive : Qt::CaseSensitive;
415 d->m_proxyModel->setSortCaseSensitivity(cs);
416 }
417}
418
419void KDirOperator::updateSelectionDependentActions()
420{
421 const bool hasSelection = (d->m_itemView != nullptr) && d->m_itemView->selectionModel()->hasSelection();
422 action(action: KDirOperator::Rename)->setEnabled(hasSelection);
423 action(action: KDirOperator::Trash)->setEnabled(hasSelection);
424 action(action: KDirOperator::Delete)->setEnabled(hasSelection);
425 action(action: KDirOperator::Properties)->setEnabled(hasSelection);
426}
427
428void KDirOperator::setPreviewWidget(KPreviewWidgetBase *w)
429{
430 const bool showPreview = (w != nullptr);
431 if (showPreview) {
432 d->m_viewKind = (d->m_viewKind | KFile::PreviewContents);
433 } else {
434 d->m_viewKind = (d->m_viewKind & ~KFile::PreviewContents);
435 }
436
437 delete d->m_preview;
438 d->m_preview = w;
439
440 if (w) {
441 d->m_splitter->addWidget(widget: w);
442 }
443
444 KToggleAction *previewAction = static_cast<KToggleAction *>(action(action: ShowPreviewPanel));
445 previewAction->setEnabled(showPreview);
446 previewAction->setChecked(showPreview);
447 setViewMode(static_cast<KFile::FileView>(d->m_viewKind));
448}
449
450KFileItemList KDirOperator::selectedItems() const
451{
452 KFileItemList itemList;
453 if (d->m_itemView == nullptr) {
454 return itemList;
455 }
456
457 const QItemSelection selection = d->m_proxyModel->mapSelectionToSource(proxySelection: d->m_itemView->selectionModel()->selection());
458
459 const QModelIndexList indexList = selection.indexes();
460 for (const QModelIndex &index : indexList) {
461 KFileItem item = d->m_dirModel->itemForIndex(index);
462 if (!item.isNull()) {
463 itemList.append(t: item);
464 }
465 }
466
467 return itemList;
468}
469
470bool KDirOperator::isSelected(const KFileItem &item) const
471{
472 if ((item.isNull()) || (d->m_itemView == nullptr)) {
473 return false;
474 }
475
476 const QModelIndex dirIndex = d->m_dirModel->indexForItem(item);
477 const QModelIndex proxyIndex = d->m_proxyModel->mapFromSource(sourceIndex: dirIndex);
478 return d->m_itemView->selectionModel()->isSelected(index: proxyIndex);
479}
480
481int KDirOperator::numDirs() const
482{
483 return (d->m_dirLister == nullptr) ? 0 : d->m_dirLister->directories().count();
484}
485
486int KDirOperator::numFiles() const
487{
488 return (d->m_dirLister == nullptr) ? 0 : d->m_dirLister->items().count() - numDirs();
489}
490
491KCompletion *KDirOperator::completionObject() const
492{
493 return const_cast<KCompletion *>(&d->m_completion);
494}
495
496KCompletion *KDirOperator::dirCompletionObject() const
497{
498 return const_cast<KCompletion *>(&d->m_dirCompletion);
499}
500
501QAction *KDirOperator::action(KDirOperator::Action action) const
502{
503 return d->m_actions[action];
504}
505
506QList<QAction *> KDirOperator::allActions() const
507{
508 return d->m_actions.values();
509}
510
511KFile::FileView KDirOperatorPrivate::allViews()
512{
513 return static_cast<KFile::FileView>(KFile::Simple | KFile::Detail | KFile::Tree | KFile::DetailTree);
514}
515
516void KDirOperatorPrivate::slotDetailedView()
517{
518 // save old zoom settings
519 writeIconZoomSettingsIfNeeded();
520
521 KFile::FileView view = static_cast<KFile::FileView>((m_viewKind & ~allViews()) | KFile::Detail);
522 q->setViewMode(view);
523}
524
525void KDirOperatorPrivate::slotSimpleView()
526{
527 // save old zoom settings
528 writeIconZoomSettingsIfNeeded();
529
530 KFile::FileView view = static_cast<KFile::FileView>((m_viewKind & ~allViews()) | KFile::Simple);
531 q->setViewMode(view);
532}
533
534void KDirOperatorPrivate::slotTreeView()
535{
536 // save old zoom settings
537 writeIconZoomSettingsIfNeeded();
538
539 KFile::FileView view = static_cast<KFile::FileView>((m_viewKind & ~allViews()) | KFile::Tree);
540 q->setViewMode(view);
541}
542
543void KDirOperatorPrivate::slotDetailedTreeView()
544{
545 // save old zoom settings
546 writeIconZoomSettingsIfNeeded();
547
548 KFile::FileView view = static_cast<KFile::FileView>((m_viewKind & ~allViews()) | KFile::DetailTree);
549 q->setViewMode(view);
550}
551
552void KDirOperatorPrivate::slotToggleAllowExpansion(bool allow)
553{
554 KFile::FileView view = KFile::Detail;
555 if (allow) {
556 view = KFile::DetailTree;
557 }
558 q->setViewMode(view);
559}
560
561void KDirOperatorPrivate::slotToggleHidden(bool show)
562{
563 m_dirLister->setShowHiddenFiles(show);
564 q->updateDir();
565 assureVisibleSelection();
566}
567
568void KDirOperatorPrivate::togglePreview(bool on)
569{
570 if (on) {
571 m_viewKind |= KFile::PreviewContents;
572 if (m_preview == nullptr) {
573 m_preview = new KFileMetaPreview(q);
574 q->action(action: KDirOperator::ShowPreviewPanel)->setChecked(true);
575 m_splitter->addWidget(widget: m_preview);
576 }
577
578 m_preview->show();
579
580 auto assureVisFunc = [this]() {
581 assureVisibleSelection();
582 };
583 QMetaObject::invokeMethod(object: q, function&: assureVisFunc, type: Qt::QueuedConnection);
584 if (m_itemView != nullptr) {
585 const QModelIndex index = m_itemView->selectionModel()->currentIndex();
586 if (index.isValid()) {
587 triggerPreview(index);
588 }
589 }
590 } else if (m_preview != nullptr) {
591 m_viewKind = m_viewKind & ~KFile::PreviewContents;
592 m_preview->hide();
593 }
594}
595
596void KDirOperatorPrivate::toggleInlinePreviews(bool show)
597{
598 if (m_showPreviews == show) {
599 return;
600 }
601
602 m_showPreviews = show;
603
604 if (!m_previewGenerator) {
605 return;
606 }
607
608 m_previewGenerator->setPreviewShown(show);
609}
610
611void KDirOperatorPrivate::slotOpenFileManager()
612{
613 const KFileItemList list = q->selectedItems();
614 if (list.isEmpty()) {
615 KIO::highlightInFileManager(urls: {m_currUrl.adjusted(options: QUrl::StripTrailingSlash)});
616 } else {
617 KIO::highlightInFileManager(urls: list.urlList());
618 }
619}
620
621void KDirOperatorPrivate::slotSortByName()
622{
623 q->sortByName();
624}
625
626void KDirOperatorPrivate::slotSortBySize()
627{
628 q->sortBySize();
629}
630
631void KDirOperatorPrivate::slotSortByDate()
632{
633 q->sortByDate();
634}
635
636void KDirOperatorPrivate::slotSortByType()
637{
638 q->sortByType();
639}
640
641void KDirOperatorPrivate::slotSortReversed(bool doReverse)
642{
643 QDir::SortFlags s = m_sorting & ~QDir::Reversed;
644 if (doReverse) {
645 s |= QDir::Reversed;
646 }
647 updateSorting(sort: s);
648}
649
650void KDirOperatorPrivate::slotToggleDirsFirst()
651{
652 QDir::SortFlags s = (m_sorting ^ QDir::DirsFirst);
653 updateSorting(sort: s);
654}
655
656void KDirOperatorPrivate::slotIconsView()
657{
658 // save old zoom settings
659 writeIconZoomSettingsIfNeeded();
660
661 // Put the icons on top
662 q->action(action: KDirOperator::DecorationAtTop)->setChecked(true);
663 m_decorationPosition = QStyleOptionViewItem::Top;
664
665 // Switch to simple view
666 KFile::FileView fileView = static_cast<KFile::FileView>((m_viewKind & ~allViews()) | KFile::Simple);
667 q->setViewMode(fileView);
668}
669
670void KDirOperatorPrivate::slotCompactView()
671{
672 // save old zoom settings
673 writeIconZoomSettingsIfNeeded();
674
675 // Put the icons on the side
676 q->action(action: KDirOperator::DecorationAtLeft)->setChecked(true);
677 m_decorationPosition = QStyleOptionViewItem::Left;
678
679 // Switch to simple view
680 KFile::FileView fileView = static_cast<KFile::FileView>((m_viewKind & ~allViews()) | KFile::Simple);
681 q->setViewMode(fileView);
682}
683
684void KDirOperatorPrivate::slotDetailsView()
685{
686 // save old zoom settings
687 writeIconZoomSettingsIfNeeded();
688
689 KFile::FileView view;
690 if (q->action(action: KDirOperator::AllowExpansionInDetailsView)->isChecked()) {
691 view = static_cast<KFile::FileView>((m_viewKind & ~allViews()) | KFile::DetailTree);
692 } else {
693 view = static_cast<KFile::FileView>((m_viewKind & ~allViews()) | KFile::Detail);
694 }
695 q->setViewMode(view);
696}
697
698void KDirOperatorPrivate::slotToggleIgnoreCase()
699{
700 // TODO: port to Qt4's QAbstractItemView
701 /*if ( !d->fileView )
702 return;
703
704 QDir::SortFlags sorting = d->fileView->sorting();
705 if ( !KFile::isSortCaseInsensitive( sorting ) )
706 d->fileView->setSorting( sorting | QDir::IgnoreCase );
707 else
708 d->fileView->setSorting( sorting & ~QDir::IgnoreCase );
709 d->sorting = d->fileView->sorting();*/
710}
711
712void KDirOperator::mkdir()
713{
714 d->m_newFileMenu->setWorkingDirectory(url());
715 d->m_newFileMenu->createDirectory();
716}
717
718#if KIOWIDGETS_BUILD_DEPRECATED_SINCE(6, 15)
719KIO::DeleteJob *KDirOperator::del(const KFileItemList &items, QWidget *parent, bool ask, bool showProgress)
720{
721 if (items.isEmpty()) {
722 KMessageBox::information(parent, i18n("You did not select a file to delete."), i18n("Nothing to Delete"));
723 return nullptr;
724 }
725
726 if (parent == nullptr) {
727 parent = this;
728 }
729
730 const QList<QUrl> urls = items.urlList();
731
732 bool doIt = !ask;
733 if (ask) {
734 KIO::JobUiDelegate uiDelegate;
735 uiDelegate.setWindow(parent);
736 doIt = uiDelegate.askDeleteConfirmation(urls, deletionType: KIO::JobUiDelegate::Delete, confirmationType: KIO::JobUiDelegate::DefaultConfirmation);
737 }
738
739 if (doIt) {
740 KIO::JobFlags flags = showProgress ? KIO::DefaultFlags : KIO::HideProgressInfo;
741 KIO::DeleteJob *job = KIO::del(src: urls, flags);
742 KJobWidgets::setWindow(job, widget: this);
743 job->uiDelegate()->setAutoErrorHandlingEnabled(true);
744 return job;
745 }
746
747 return nullptr;
748}
749#endif
750
751void KDirOperator::deleteSelected()
752{
753 const QList<QUrl> urls = selectedItems().urlList();
754 if (urls.isEmpty()) {
755 KMessageBox::information(parent: this, i18n("You did not select a file to delete."), i18n("Nothing to Delete"));
756 return;
757 }
758
759 using Iface = KIO::AskUserActionInterface;
760 auto *deleteJob = new KIO::DeleteOrTrashJob(urls, Iface::Delete, Iface::DefaultConfirmation, this);
761 deleteJob->start();
762}
763
764#if KIOWIDGETS_BUILD_DEPRECATED_SINCE(6, 15)
765KIO::CopyJob *KDirOperator::trash(const KFileItemList &items, QWidget *parent, bool ask, bool showProgress)
766{
767 if (items.isEmpty()) {
768 KMessageBox::information(parent, i18n("You did not select a file to trash."), i18n("Nothing to Trash"));
769 return nullptr;
770 }
771
772 const QList<QUrl> urls = items.urlList();
773
774 bool doIt = !ask;
775 if (ask) {
776 KIO::JobUiDelegate uiDelegate;
777 uiDelegate.setWindow(parent);
778 doIt = uiDelegate.askDeleteConfirmation(urls, deletionType: KIO::JobUiDelegate::Trash, confirmationType: KIO::JobUiDelegate::DefaultConfirmation);
779 }
780
781 if (doIt) {
782 KIO::JobFlags flags = showProgress ? KIO::DefaultFlags : KIO::HideProgressInfo;
783 KIO::CopyJob *job = KIO::trash(src: urls, flags);
784 KJobWidgets::setWindow(job, widget: this);
785 job->uiDelegate()->setAutoErrorHandlingEnabled(true);
786 return job;
787 }
788
789 return nullptr;
790}
791#endif
792
793KFilePreviewGenerator *KDirOperator::previewGenerator() const
794{
795 return d->m_previewGenerator;
796}
797
798void KDirOperator::setInlinePreviewShown(bool show)
799{
800 d->m_inlinePreviewState = show ? KDirOperatorPrivate::ForcedToTrue : KDirOperatorPrivate::ForcedToFalse;
801}
802
803bool KDirOperator::isInlinePreviewShown() const
804{
805 return d->m_showPreviews;
806}
807
808int KDirOperator::iconSize() const
809{
810 return d->m_iconSize;
811}
812
813void KDirOperator::setIsSaving(bool isSaving)
814{
815 d->m_isSaving = isSaving;
816}
817
818bool KDirOperator::isSaving() const
819{
820 return d->m_isSaving;
821}
822
823void KDirOperator::renameSelected()
824{
825 if (d->m_itemView == nullptr) {
826 return;
827 }
828
829 const KFileItemList items = selectedItems();
830 if (items.isEmpty()) {
831 return;
832 }
833
834 KIO::RenameFileDialog *dialog = new KIO::RenameFileDialog(items, this);
835 connect(sender: dialog, signal: &KIO::RenameFileDialog::renamingFinished, context: this, slot: [this](const QList<QUrl> &urls) {
836 d->assureVisibleSelection();
837 Q_EMIT renamingFinished(urls);
838 });
839
840 dialog->open();
841}
842
843void KDirOperator::trashSelected()
844{
845 if (d->m_itemView == nullptr) {
846 return;
847 }
848
849 if (QApplication::keyboardModifiers() & Qt::ShiftModifier) {
850 deleteSelected();
851 return;
852 }
853
854 const QList<QUrl> urls = selectedItems().urlList();
855 if (urls.isEmpty()) {
856 KMessageBox::information(parent: this, i18n("You did not select a file to trash."), i18n("Nothing to Trash"));
857 return;
858 }
859
860 using Iface = KIO::AskUserActionInterface;
861 auto *trashJob = new KIO::DeleteOrTrashJob(urls, Iface::Trash, Iface::DefaultConfirmation, this);
862 trashJob->start();
863}
864
865void KDirOperator::setIconSize(int value)
866{
867 if (d->m_iconSize == value) {
868 return;
869 }
870
871 int size = value;
872 // Keep size range in sync with KFileWidgetPrivate::m_stdIconSizes
873 size = std::min(a: 512, b: size);
874 size = std::max<int>(a: KIconLoader::SizeSmall, b: size);
875
876 d->m_iconSize = size;
877
878 if (!d->m_previewGenerator) {
879 return;
880 }
881
882 d->m_itemView->setIconSize(QSize(size, size));
883 d->m_previewGenerator->updateIcons();
884
885 Q_EMIT currentIconSizeChanged(size);
886}
887
888void KDirOperator::close()
889{
890 resetCursor();
891 d->m_pendingMimeTypes.clear();
892 d->m_completion.clear();
893 d->m_dirCompletion.clear();
894 d->m_completeListDirty = true;
895 d->m_dirLister->stop();
896}
897
898void KDirOperator::setUrl(const QUrl &_newurl, bool clearforward)
899{
900 QUrl newurl = _newurl.isValid() ? _newurl.adjusted(options: QUrl::NormalizePathSegments) : QUrl::fromLocalFile(localfile: QDir::homePath());
901 Utils::appendSlashToPath(url&: newurl);
902
903 // already set
904 if (newurl.matches(url: d->m_currUrl, options: QUrl::StripTrailingSlash)) {
905 return;
906 }
907
908 if (!d->isSchemeSupported(scheme: newurl.scheme())) {
909 return;
910 }
911
912 if (!d->isReadable(url: newurl)) {
913 // maybe newurl is a file? check its parent directory
914 newurl = newurl.adjusted(options: QUrl::StripTrailingSlash).adjusted(options: QUrl::RemoveFilename);
915 if (newurl.matches(url: d->m_currUrl, options: QUrl::StripTrailingSlash)) {
916 Q_EMIT urlEntered(newurl); // To remove the filename in pathCombo
917 return; // parent is current dir, nothing to do (fixes #173454, too)
918 }
919 KIO::StatJob *job = KIO::stat(url: newurl);
920 KJobWidgets::setWindow(job, widget: this);
921 bool res = job->exec();
922
923 KIO::UDSEntry entry = job->statResult();
924 KFileItem i(entry, newurl);
925 if ((!res || !d->isReadable(url: newurl)) && i.isDir()) {
926 resetCursor();
927 KMessageBox::error(parent: d->m_itemView,
928 i18n("The specified folder does not exist "
929 "or was not readable."));
930 return;
931 } else if (!i.isDir()) {
932 return;
933 }
934 }
935
936 if (clearforward) {
937 // autodelete should remove this one
938 d->m_backStack.push(t: new QUrl(d->m_currUrl));
939 qDeleteAll(c: d->m_forwardStack);
940 d->m_forwardStack.clear();
941 }
942
943 d->m_currUrl = newurl;
944
945 pathChanged();
946 Q_EMIT urlEntered(newurl);
947
948 // enable/disable actions
949 QAction *forwardAction = action(action: KDirOperator::Forward);
950 forwardAction->setEnabled(!d->m_forwardStack.isEmpty());
951
952 QAction *backAction = action(action: KDirOperator::Back);
953 backAction->setEnabled(!d->m_backStack.isEmpty());
954
955 QAction *upAction = action(action: KDirOperator::Up);
956 upAction->setEnabled(!isRoot());
957
958 d->openUrl(url: newurl);
959}
960
961void KDirOperator::updateDir()
962{
963 QApplication::setOverrideCursor(Qt::WaitCursor);
964 d->m_dirLister->emitChanges();
965 QApplication::restoreOverrideCursor();
966}
967
968void KDirOperator::rereadDir()
969{
970 pathChanged();
971 d->openUrl(url: d->m_currUrl, flags: KDirLister::Reload);
972}
973
974bool KDirOperatorPrivate::isSchemeSupported(const QString &scheme) const
975{
976 return m_supportedSchemes.isEmpty() || m_supportedSchemes.contains(str: scheme);
977}
978
979bool KDirOperatorPrivate::openUrl(const QUrl &url, KDirLister::OpenUrlFlags flags)
980{
981 const bool result = KProtocolManager::supportsListing(url) && isSchemeSupported(scheme: url.scheme()) && m_dirLister->openUrl(dirUrl: url, flags);
982 if (!result) { // in that case, neither completed() nor canceled() will be emitted by KDL
983 slotCanceled();
984 }
985
986 return result;
987}
988
989int KDirOperatorPrivate::sortColumn() const
990{
991 int column = KDirModel::Name;
992 if (KFile::isSortByDate(sort: m_sorting)) {
993 column = KDirModel::ModifiedTime;
994 } else if (KFile::isSortBySize(sort: m_sorting)) {
995 column = KDirModel::Size;
996 } else if (KFile::isSortByType(sort: m_sorting)) {
997 column = KDirModel::Type;
998 } else {
999 Q_ASSERT(KFile::isSortByName(m_sorting));
1000 }
1001
1002 return column;
1003}
1004
1005Qt::SortOrder KDirOperatorPrivate::sortOrder() const
1006{
1007 // Reverse whatever was applied to time sorting, since reversed by default is nicer
1008 if (m_sorting & QDir::Time) {
1009 return (m_sorting & QDir::Reversed) ? Qt::AscendingOrder : Qt::DescendingOrder;
1010 }
1011
1012 return (m_sorting & QDir::Reversed) ? Qt::DescendingOrder : Qt::AscendingOrder;
1013}
1014
1015void KDirOperatorPrivate::updateSorting(QDir::SortFlags sort)
1016{
1017 // qDebug() << "changing sort flags from" << m_sorting << "to" << sort;
1018 if (sort == m_sorting) {
1019 return;
1020 }
1021
1022 m_sorting = sort;
1023 q->updateSortActions();
1024
1025 m_proxyModel->setSortFoldersFirst(m_sorting & QDir::DirsFirst);
1026 m_proxyModel->sort(column: sortColumn(), order: sortOrder());
1027
1028 assureVisibleSelection();
1029}
1030
1031// Protected
1032void KDirOperator::pathChanged()
1033{
1034 if (d->m_itemView == nullptr) {
1035 return;
1036 }
1037
1038 d->m_pendingMimeTypes.clear();
1039 // d->fileView->clear(); TODO
1040 d->m_completion.clear();
1041 d->m_dirCompletion.clear();
1042
1043 // it may be, that we weren't ready at this time
1044 QApplication::restoreOverrideCursor();
1045
1046 // when KIO::Job emits finished, the slot will restore the cursor
1047 QApplication::setOverrideCursor(Qt::WaitCursor);
1048
1049 if (!d->isReadable(url: d->m_currUrl)) {
1050 KMessageBox::error(parent: d->m_itemView,
1051 i18n("The specified folder does not exist "
1052 "or was not readable."));
1053 if (d->m_backStack.isEmpty()) {
1054 home();
1055 } else {
1056 back();
1057 }
1058 }
1059}
1060
1061void KDirOperatorPrivate::slotRedirected(const QUrl &newURL)
1062{
1063 m_currUrl = newURL;
1064 m_pendingMimeTypes.clear();
1065 m_completion.clear();
1066 m_dirCompletion.clear();
1067 m_completeListDirty = true;
1068 Q_EMIT q->urlEntered(newURL);
1069}
1070
1071// Code pinched from kfm then hacked
1072void KDirOperator::back()
1073{
1074 if (d->m_backStack.isEmpty()) {
1075 return;
1076 }
1077
1078 d->m_forwardStack.push(t: new QUrl(d->m_currUrl));
1079
1080 QUrl *s = d->m_backStack.pop();
1081 const QUrl newUrl = *s;
1082 delete s;
1083
1084 if (d->m_dirHighlighting) {
1085 const QUrl _url = newUrl.adjusted(options: QUrl::StripTrailingSlash).adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash);
1086
1087 if (_url == d->m_currUrl.adjusted(options: QUrl::StripTrailingSlash) && !d->m_backStack.isEmpty()) {
1088 // e.g. started in a/b/c, cdUp() twice to "a", then back(), we highlight "c"
1089 d->m_lastUrl = *(d->m_backStack.top());
1090 } else {
1091 d->m_lastUrl = d->m_currUrl;
1092 }
1093 }
1094
1095 setUrl(newurl: newUrl, clearforward: false);
1096}
1097
1098// Code pinched from kfm then hacked
1099void KDirOperator::forward()
1100{
1101 if (d->m_forwardStack.isEmpty()) {
1102 return;
1103 }
1104
1105 d->m_backStack.push(t: new QUrl(d->m_currUrl));
1106
1107 QUrl *s = d->m_forwardStack.pop();
1108 setUrl(newurl: *s, clearforward: false);
1109 delete s;
1110}
1111
1112QUrl KDirOperator::url() const
1113{
1114 return d->m_currUrl;
1115}
1116
1117void KDirOperator::cdUp()
1118{
1119 // Allow /d/c// to go up to /d/ instead of /d/c/
1120 QUrl tmp(d->m_currUrl.adjusted(options: QUrl::NormalizePathSegments));
1121
1122 if (d->m_dirHighlighting) {
1123 d->m_lastUrl = d->m_currUrl;
1124 }
1125
1126 setUrl(newurl: tmp.resolved(relative: QUrl(QStringLiteral(".."))), clearforward: true);
1127}
1128
1129void KDirOperator::home()
1130{
1131 setUrl(newurl: QUrl::fromLocalFile(localfile: QDir::homePath()), clearforward: true);
1132}
1133
1134void KDirOperator::clearFilter()
1135{
1136 d->m_dirLister->setNameFilter(QString());
1137 d->m_dirLister->clearMimeFilter();
1138 checkPreviewSupport();
1139}
1140
1141void KDirOperator::setNameFilter(const QString &filter)
1142{
1143 d->m_dirLister->setNameFilter(filter);
1144 checkPreviewSupport();
1145}
1146
1147QString KDirOperator::nameFilter() const
1148{
1149 return d->m_dirLister->nameFilter();
1150}
1151
1152void KDirOperator::setMimeFilter(const QStringList &mimetypes)
1153{
1154 d->m_dirLister->setMimeFilter(mimetypes);
1155 checkPreviewSupport();
1156}
1157
1158QStringList KDirOperator::mimeFilter() const
1159{
1160 return d->m_dirLister->mimeFilters();
1161}
1162
1163void KDirOperator::setNewFileMenuSupportedMimeTypes(const QStringList &mimeTypes)
1164{
1165 d->m_newFileMenu->setSupportedMimeTypes(mimeTypes);
1166}
1167
1168QStringList KDirOperator::newFileMenuSupportedMimeTypes() const
1169{
1170 return d->m_newFileMenu->supportedMimeTypes();
1171}
1172
1173void KDirOperator::setNewFileMenuSelectDirWhenAlreadyExist(bool selectOnDirExists)
1174{
1175 d->m_newFileMenu->setSelectDirWhenAlreadyExist(selectOnDirExists);
1176}
1177
1178bool KDirOperator::checkPreviewSupport()
1179{
1180 KToggleAction *previewAction = static_cast<KToggleAction *>(action(action: KDirOperator::ShowPreviewPanel));
1181
1182 bool hasPreviewSupport = false;
1183 KConfigGroup cg(KSharedConfig::openConfig(), ConfigGroup);
1184 if (cg.readEntry(key: "Show Default Preview", defaultValue: true)) {
1185 hasPreviewSupport = d->checkPreviewInternal();
1186 }
1187
1188 previewAction->setEnabled(hasPreviewSupport);
1189 return hasPreviewSupport;
1190}
1191
1192void KDirOperator::activatedMenu(const KFileItem &item, const QPoint &pos)
1193{
1194 updateSelectionDependentActions();
1195
1196 d->m_newFileMenu->setWorkingDirectory(item.url());
1197 d->m_newFileMenu->checkUpToDate();
1198
1199 action(action: KDirOperator::New)->setEnabled(item.isDir());
1200
1201 Q_EMIT contextMenuAboutToShow(item, menu: d->m_actionMenu->menu());
1202
1203 // Must be right before we call QMenu::exec(), otherwise we might remove
1204 // other non-related actions along with the open-with ones
1205 const QList<QAction *> openWithActions = d->insertOpenWithActions();
1206
1207 d->m_actionMenu->menu()->exec(pos);
1208
1209 // Remove the open-with actions, otherwise they would accumulate in the menu
1210 for (auto *action : openWithActions) {
1211 d->m_actionMenu->menu()->removeAction(action);
1212 }
1213}
1214
1215QList<QAction *> KDirOperatorPrivate::insertOpenWithActions()
1216{
1217 if (!m_showOpenWithActions) {
1218 return {};
1219 }
1220
1221 const QList<QAction *> beforeOpenWith = m_actionMenu->menu()->actions();
1222
1223 const KFileItemList items = q->selectedItems();
1224 if (!items.isEmpty()) {
1225 m_itemActions->setItemListProperties(KFileItemListProperties(items));
1226 const QList<QAction *> actions = m_actionMenu->menu()->actions();
1227 QAction *before = !actions.isEmpty() ? actions.at(i: 0) : nullptr;
1228 m_itemActions->insertOpenWithActionsTo(before, topMenu: m_actionMenu->menu(), excludedDesktopEntryNames: QStringList());
1229 }
1230
1231 // Get the list of the added open-with actions
1232 QList<QAction *> toRemove = m_actionMenu->menu()->actions();
1233 auto it = std::remove_if(first: toRemove.begin(), last: toRemove.end(), pred: [beforeOpenWith](QAction *a) {
1234 return beforeOpenWith.contains(t: a);
1235 });
1236 toRemove.erase(abegin: it, aend: toRemove.end());
1237
1238 return toRemove;
1239}
1240
1241void KDirOperator::showOpenWithActions(bool enable)
1242{
1243 d->m_showOpenWithActions = enable;
1244}
1245
1246bool KDirOperator::usingKeyNavigation()
1247{
1248 return d->m_keyNavigation;
1249}
1250
1251void KDirOperator::changeEvent(QEvent *event)
1252{
1253 QWidget::changeEvent(event);
1254}
1255
1256bool KDirOperator::eventFilter(QObject *watched, QEvent *event)
1257{
1258 // If we are not hovering any items, check if there is a current index
1259 // set. In that case, we show the preview of that item.
1260 switch (event->type()) {
1261 case QEvent::TouchBegin:
1262 d->m_isTouchEvent = true;
1263 break;
1264 case QEvent::MouseMove: {
1265 if (d->m_isTouchEvent) {
1266 return true;
1267 }
1268
1269 const QModelIndex hoveredIndex = d->m_itemView->indexAt(point: d->m_itemView->viewport()->mapFromGlobal(QCursor::pos()));
1270 if (hoveredIndex.isValid()) {
1271 KFileItemSelectionEmblem(d->m_itemView, hoveredIndex, this).updateSelectionEmblemRectForIndex(iconSize: iconSize());
1272 }
1273
1274 if (d->m_preview && !d->m_preview->isHidden()) {
1275 if (d->m_lastHoveredIndex == hoveredIndex) {
1276 return QWidget::eventFilter(watched, event);
1277 }
1278
1279 d->m_lastHoveredIndex = hoveredIndex;
1280
1281 const QModelIndex currentIndex = d->m_itemView->selectionModel() ? d->m_itemView->selectionModel()->currentIndex() : QModelIndex();
1282
1283 if (!hoveredIndex.isValid() && currentIndex.isValid() && (d->m_lastHoveredIndex != currentIndex)) {
1284 const KFileItem item = d->m_itemView->model()->data(index: currentIndex, role: KDirModel::FileItemRole).value<KFileItem>();
1285 if (!item.isNull()) {
1286 d->m_preview->showPreview(url: item.url());
1287 }
1288 }
1289 }
1290 break;
1291 }
1292 case QEvent::MouseButtonPress:
1293 case QEvent::MouseButtonDblClick: {
1294 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
1295 if (mouseEvent) {
1296 // Navigate and then discard the event so it doesn't select an item on the directory navigated to.
1297 switch (mouseEvent->button()) {
1298 case Qt::BackButton:
1299 back();
1300 return true;
1301 case Qt::ForwardButton:
1302 forward();
1303 return true;
1304 default:
1305 break;
1306 }
1307 }
1308 break;
1309 }
1310 case QEvent::MouseButtonRelease: {
1311 if (d->m_preview != nullptr && !d->m_preview->isHidden()) {
1312 const QModelIndex hoveredIndex = d->m_itemView->indexAt(point: d->m_itemView->viewport()->mapFromGlobal(QCursor::pos()));
1313 const QModelIndex focusedIndex = d->m_itemView->selectionModel() ? d->m_itemView->selectionModel()->currentIndex() : QModelIndex();
1314
1315 if (((!focusedIndex.isValid()) || !d->m_itemView->selectionModel()->isSelected(index: focusedIndex)) && (!hoveredIndex.isValid())) {
1316 d->m_preview->clearPreview();
1317 }
1318 }
1319
1320 d->m_isTouchEvent = false;
1321 if (d->m_isTouchDrag) {
1322 d->m_isTouchDrag = false;
1323 return true;
1324 }
1325
1326 break;
1327 }
1328 case QEvent::HoverLeave: {
1329 if (d->m_preview && !d->m_preview->isHidden()) {
1330 const QModelIndex currentIndex = d->m_itemView->selectionModel() ? d->m_itemView->selectionModel()->currentIndex() : QModelIndex();
1331
1332 if (currentIndex.isValid() && d->m_itemView->selectionModel()->isSelected(index: currentIndex)) {
1333 const KFileItem item = d->m_itemView->model()->data(index: currentIndex, role: KDirModel::FileItemRole).value<KFileItem>();
1334 if (!item.isNull()) {
1335 d->m_preview->showPreview(url: item.url());
1336 }
1337 }
1338 }
1339 break;
1340 }
1341 case QEvent::Wheel: {
1342 QWheelEvent *evt = static_cast<QWheelEvent *>(event);
1343 if (evt->modifiers() & Qt::ControlModifier) {
1344 if (evt->angleDelta().y() > 0) {
1345 setIconSize(d->m_iconSize + 10);
1346 } else {
1347 setIconSize(d->m_iconSize - 10);
1348 }
1349 return true;
1350 }
1351 break;
1352 }
1353 case QEvent::DragEnter: {
1354 // Accepts drops of one file or folder only
1355 QDragEnterEvent *evt = static_cast<QDragEnterEvent *>(event);
1356 const QList<QUrl> urls = KUrlMimeData::urlsFromMimeData(mimeData: evt->mimeData(), decodeOptions: KUrlMimeData::PreferLocalUrls);
1357
1358 // only one file/folder can be dropped at the moment
1359 if (urls.size() != 1) {
1360 evt->ignore();
1361
1362 } else {
1363 // MIME type filtering
1364 bool mimeFilterPass = true;
1365 const QStringList mimeFilters = d->m_dirLister->mimeFilters();
1366
1367 if (mimeFilters.size() > 1) {
1368 mimeFilterPass = false;
1369 const QUrl &url = urls.constFirst();
1370
1371 QMimeDatabase mimeDataBase;
1372 QMimeType fileMimeType = mimeDataBase.mimeTypeForUrl(url);
1373
1374 QRegularExpression regex;
1375 for (const auto &mimeFilter : mimeFilters) {
1376 regex.setPattern(mimeFilter);
1377 if (regex.match(subject: fileMimeType.name()).hasMatch()) { // matches!
1378 mimeFilterPass = true;
1379 break;
1380 }
1381 }
1382 }
1383
1384 event->setAccepted(mimeFilterPass);
1385 }
1386
1387 return true;
1388 }
1389 case QEvent::Drop: {
1390 QDropEvent *evt = static_cast<QDropEvent *>(event);
1391 const QList<QUrl> urls = KUrlMimeData::urlsFromMimeData(mimeData: evt->mimeData(), decodeOptions: KUrlMimeData::PreferLocalUrls);
1392
1393 const QUrl &url = urls.constFirst();
1394
1395 // stat the url to get details
1396 KIO::StatJob *job = KIO::stat(url, flags: KIO::HideProgressInfo);
1397 job->exec();
1398
1399 setFocus();
1400
1401 const KIO::UDSEntry entry = job->statResult();
1402
1403 if (entry.isDir()) {
1404 // if this was a directory
1405 setUrl(newurl: url, clearforward: false);
1406 } else {
1407 // if the current url is not known
1408 if (d->m_dirLister->findByUrl(url).isNull()) {
1409 setUrl(newurl: url.adjusted(options: QUrl::RemoveFilename), clearforward: false);
1410
1411 // Will set the current item once loading has finished
1412 auto urlSetterClosure = [this, url]() {
1413 setCurrentItem(url);
1414 QObject::disconnect(d->m_connection);
1415 };
1416 d->m_connection = connect(sender: this, signal: &KDirOperator::finishedLoading, context: this, slot&: urlSetterClosure);
1417 } else {
1418 setCurrentItem(url);
1419 }
1420 }
1421 evt->accept();
1422 return true;
1423 }
1424 case QEvent::KeyPress: {
1425 QKeyEvent *evt = static_cast<QKeyEvent *>(event);
1426 if (evt->key() == Qt::Key_Return || evt->key() == Qt::Key_Enter) {
1427 // when no elements are selected and Return/Enter is pressed
1428 // emit keyEnterReturnPressed
1429 // let activated event be emitted by subsequent QAbstractItemView::keyPress otherwise
1430 if (!d->m_itemView->currentIndex().isValid()) {
1431 Q_EMIT keyEnterReturnPressed();
1432 evt->accept();
1433 return true;
1434 }
1435 }
1436 // Only use tab key to escape the view navigation
1437 if (evt->key() == Qt::Key_Tab) {
1438 d->m_keyNavigation = false;
1439 d->slotSelectionChanged();
1440 // When saving we need to return here,
1441 // otherwise we skip over the next item with our tab press
1442 // since we focus on that item in slotSelectionChanged
1443 if (d->m_isSaving) {
1444 return true;
1445 }
1446 } else {
1447 d->m_keyNavigation = true;
1448 }
1449 break;
1450 }
1451 case QEvent::Resize: {
1452 /* clang-format off */
1453 if (watched == d->m_itemView->viewport()
1454 && d->m_itemView->horizontalScrollBar()
1455 && d->m_progressBar->parent() == this /* it could have been reparented to a statusbar */) { /* clang-format on */
1456 if (d->m_itemView->horizontalScrollBar()->isVisible()) {
1457 // Show the progress bar above the horizontal scrollbar that may be visible
1458 // in compact view
1459 QPoint progressBarPos = d->progressBarPos();
1460 progressBarPos.ry() -= d->m_itemView->horizontalScrollBar()->height();
1461 d->m_progressBar->move(progressBarPos);
1462 } else {
1463 d->m_progressBar->move(d->progressBarPos());
1464 }
1465 }
1466 break;
1467 }
1468 default:
1469 break;
1470 }
1471
1472 return QWidget::eventFilter(watched, event);
1473}
1474
1475bool KDirOperatorPrivate::checkPreviewInternal() const
1476{
1477 const QStringList supported = KIO::PreviewJob::supportedMimeTypes();
1478 // no preview support for directories?
1479 if (q->dirOnlyMode() && !supported.contains(str: QLatin1String("inode/directory"))) {
1480 return false;
1481 }
1482
1483 const QStringList mimeTypes = m_dirLister->mimeFilters();
1484 const QStringList nameFilters = m_dirLister->nameFilter().split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts);
1485
1486 if (mimeTypes.isEmpty() && nameFilters.isEmpty() && !supported.isEmpty()) {
1487 return true;
1488 } else {
1489 QMimeDatabase db;
1490 QRegularExpression re;
1491
1492 if (!mimeTypes.isEmpty()) {
1493 for (const QString &str : supported) {
1494 // wildcard matching because the "mimetype" can be "image/*"
1495 re.setPattern(QRegularExpression::wildcardToRegularExpression(str));
1496
1497 if (mimeTypes.indexOf(re) != -1) { // matches! -> we want previews
1498 return true;
1499 }
1500 }
1501 }
1502
1503 if (!nameFilters.isEmpty()) {
1504 // find the MIME types of all the filter-patterns
1505 for (const QString &filter : nameFilters) {
1506 if (filter == QLatin1Char('*')) {
1507 return true;
1508 }
1509
1510 const QMimeType mt = db.mimeTypeForFile(fileName: filter, mode: QMimeDatabase::MatchExtension /*fast mode, no file contents exist*/);
1511 if (!mt.isValid()) {
1512 continue;
1513 }
1514 const QString mime = mt.name();
1515
1516 for (const QString &str : supported) {
1517 // the "mimetypes" we get from the PreviewJob can be "image/*"
1518 // so we need to check in wildcard mode
1519 re.setPattern(QRegularExpression::wildcardToRegularExpression(str));
1520 if (re.match(subject: mime).hasMatch()) {
1521 return true;
1522 }
1523 }
1524 }
1525 }
1526 }
1527
1528 return false;
1529}
1530
1531QAbstractItemView *KDirOperator::createView(QWidget *parent, KFile::FileView viewKind)
1532{
1533 QAbstractItemView *itemView = nullptr;
1534 if (KFile::isDetailView(view: viewKind) || KFile::isTreeView(view: viewKind) || KFile::isDetailTreeView(view: viewKind)) {
1535 KDirOperatorDetailView *detailView = new KDirOperatorDetailView(this, parent);
1536 detailView->setViewMode(viewKind);
1537 itemView = detailView;
1538 } else {
1539 itemView = new KDirOperatorIconView(this, parent, decorationPosition());
1540 }
1541
1542 return itemView;
1543}
1544
1545void KDirOperator::setAcceptDrops(bool acceptsDrops)
1546{
1547 QWidget::setAcceptDrops(acceptsDrops);
1548 if (view()) {
1549 view()->setAcceptDrops(acceptsDrops);
1550 if (acceptsDrops) {
1551 view()->installEventFilter(filterObj: this);
1552 } else {
1553 view()->removeEventFilter(obj: this);
1554 }
1555 }
1556}
1557
1558void KDirOperator::setDropOptions(int options)
1559{
1560 d->m_dropOptions = options;
1561 // TODO:
1562 // if (d->fileView)
1563 // d->fileView->setDropOptions(options);
1564}
1565
1566void KDirOperator::setViewMode(KFile::FileView viewKind)
1567{
1568 bool preview = (KFile::isPreviewInfo(view: viewKind) || KFile::isPreviewContents(view: viewKind));
1569
1570 if (viewKind == KFile::Default) {
1571 if (KFile::isDetailView(view: (KFile::FileView)d->m_defaultView)) {
1572 viewKind = KFile::Detail;
1573 } else if (KFile::isTreeView(view: (KFile::FileView)d->m_defaultView)) {
1574 viewKind = KFile::Tree;
1575 } else if (KFile::isDetailTreeView(view: (KFile::FileView)d->m_defaultView)) {
1576 viewKind = KFile::DetailTree;
1577 } else {
1578 viewKind = KFile::Simple;
1579 }
1580
1581 const KFile::FileView defaultViewKind = static_cast<KFile::FileView>(d->m_defaultView);
1582 preview = (KFile::isPreviewInfo(view: defaultViewKind) || KFile::isPreviewContents(view: defaultViewKind)) && action(action: KDirOperator::ShowPreviewPanel)->isEnabled();
1583 }
1584
1585 d->m_viewKind = static_cast<int>(viewKind);
1586 viewKind = static_cast<KFile::FileView>(d->m_viewKind);
1587
1588 QAbstractItemView *newView = createView(parent: this, viewKind);
1589 setViewInternal(newView);
1590
1591 if (acceptDrops()) {
1592 newView->setAcceptDrops(true);
1593 newView->installEventFilter(filterObj: this);
1594 }
1595
1596 d->togglePreview(on: preview);
1597}
1598
1599KFile::FileView KDirOperator::viewMode() const
1600{
1601 return static_cast<KFile::FileView>(d->m_viewKind);
1602}
1603
1604QAbstractItemView *KDirOperator::view() const
1605{
1606 return d->m_itemView;
1607}
1608
1609KFile::Modes KDirOperator::mode() const
1610{
1611 return d->m_mode;
1612}
1613
1614void KDirOperator::setMode(KFile::Modes mode)
1615{
1616 if (d->m_mode == mode) {
1617 return;
1618 }
1619
1620 const bool isDirOnlyChanged = dirOnlyMode(mode: d->m_mode) != dirOnlyMode(mode);
1621
1622 d->m_mode = mode;
1623
1624 d->m_dirLister->setDirOnlyMode(dirOnlyMode());
1625
1626 // When KFile::Directory mode changes, we need to update the dir,
1627 // otherwise initially we would be showing dirs and files even when
1628 // dirOnlyMode() is true
1629 if (isDirOnlyChanged) {
1630 updateDir();
1631 }
1632
1633 // reset the view with the different mode
1634 if (d->m_itemView != nullptr) {
1635 setViewMode(static_cast<KFile::FileView>(d->m_viewKind));
1636 }
1637}
1638
1639void KDirOperator::setViewInternal(QAbstractItemView *view)
1640{
1641 if (view == d->m_itemView) {
1642 return;
1643 }
1644
1645 // TODO: do a real timer and restart it after that
1646 d->m_pendingMimeTypes.clear();
1647 const bool listDir = (d->m_itemView == nullptr);
1648
1649 if (d->m_itemView) {
1650 d->m_itemView->viewport()->removeEventFilter(obj: this);
1651 }
1652 QScroller *scroller = QScroller::scroller(target: view->viewport());
1653 QScrollerProperties scrollerProp;
1654 scrollerProp.setScrollMetric(metric: QScrollerProperties::AcceleratingFlickMaximumTime, value: 0.2); // QTBUG-88249
1655 scroller->setScrollerProperties(scrollerProp);
1656 scroller->grabGesture(target: view->viewport());
1657 connect(sender: scroller, signal: &QScroller::stateChanged, context: this, slot: [this](const QScroller::State newState) {
1658 if (newState == QScroller::Inactive) {
1659 d->m_isTouchEvent = false;
1660 } else if (newState == QScroller::Dragging) {
1661 d->m_isTouchDrag = true;
1662 }
1663 });
1664 view->viewport()->grabGesture(type: Qt::TapGesture);
1665 view->setAttribute(Qt::WA_AcceptTouchEvents);
1666
1667 if (d->m_mode & KFile::Files) {
1668 view->setSelectionMode(QAbstractItemView::ExtendedSelection);
1669 } else {
1670 view->setSelectionMode(QAbstractItemView::SingleSelection);
1671 }
1672
1673 QItemSelectionModel *selectionModel = nullptr;
1674 if ((d->m_itemView != nullptr) && d->m_itemView->selectionModel()->hasSelection()) {
1675 // remember the selection of the current item view and apply this selection
1676 // to the new view later
1677 const QItemSelection selection = d->m_itemView->selectionModel()->selection();
1678 selectionModel = new QItemSelectionModel(d->m_proxyModel, this);
1679 selectionModel->select(selection, command: QItemSelectionModel::Select);
1680 }
1681
1682 setFocusProxy(nullptr);
1683 delete d->m_itemView;
1684 d->m_itemView = view;
1685 d->m_itemView->setModel(d->m_proxyModel);
1686 setFocusProxy(d->m_itemView);
1687
1688 d->m_itemView->viewport()->setObjectName(QStringLiteral("d->itemview_viewport"));
1689 view->viewport()->installEventFilter(filterObj: this);
1690
1691 KFileItemDelegate *delegate = new KFileItemDelegate(d->m_itemView);
1692 d->m_itemView->setItemDelegate(delegate);
1693 d->m_itemView->viewport()->setAttribute(Qt::WA_Hover);
1694 d->m_itemView->setContextMenuPolicy(Qt::CustomContextMenu);
1695 d->m_itemView->setMouseTracking(true);
1696 // d->itemView->setDropOptions(d->dropOptions);
1697
1698 // first push our settings to the view, then listen for changes from the view
1699 QTreeView *treeView = qobject_cast<QTreeView *>(object: d->m_itemView);
1700 if (treeView) {
1701 QHeaderView *headerView = treeView->header();
1702 headerView->setSortIndicator(logicalIndex: d->sortColumn(), order: d->sortOrder());
1703 connect(sender: headerView, signal: &QHeaderView::sortIndicatorChanged, context: this, slot: [this](int logicalIndex, Qt::SortOrder order) {
1704 d->synchronizeSortingState(logicalIndex, order);
1705 });
1706 }
1707
1708 connect(sender: d->m_itemView, signal: &QAbstractItemView::activated, context: this, slot: [this](QModelIndex index) {
1709 d->slotActivated(index);
1710 });
1711
1712 connect(sender: d->m_itemView, signal: &QAbstractItemView::customContextMenuRequested, context: this, slot: [this](QPoint pos) {
1713 d->openContextMenu(pos);
1714 });
1715
1716 connect(sender: d->m_itemView, signal: &QAbstractItemView::entered, context: this, slot: [this](QModelIndex index) {
1717 d->triggerPreview(index);
1718 });
1719
1720 d->m_splitter->insertWidget(index: 0, widget: d->m_itemView);
1721
1722 d->m_splitter->resize(size());
1723 d->m_itemView->show();
1724
1725 if (listDir) {
1726 QApplication::setOverrideCursor(Qt::WaitCursor);
1727 d->openUrl(url: d->m_currUrl);
1728 }
1729
1730 if (selectionModel != nullptr) {
1731 d->m_itemView->setSelectionModel(selectionModel);
1732 auto assureVisFunc = [this]() {
1733 d->assureVisibleSelection();
1734 };
1735 QMetaObject::invokeMethod(object: this, function&: assureVisFunc, type: Qt::QueuedConnection);
1736 }
1737
1738 connect(sender: d->m_itemView->selectionModel(), signal: &QItemSelectionModel::currentChanged, context: this, slot: [this](QModelIndex index) {
1739 d->triggerPreview(index);
1740 });
1741 connect(sender: d->m_itemView->selectionModel(), signal: &QItemSelectionModel::selectionChanged, context: this, slot: [this]() {
1742 d->slotSelectionChanged();
1743 });
1744
1745 // if we cannot cast it to a QListView, disable the "Icon Position" menu. Note that this check
1746 // needs to be done here, and not in createView, since we can be set an external view
1747 d->m_decorationMenu->setEnabled(qobject_cast<QListView *>(object: d->m_itemView));
1748
1749 d->m_shouldFetchForItems = qobject_cast<QTreeView *>(object: view);
1750 if (d->m_shouldFetchForItems) {
1751 connect(sender: d->m_dirModel, signal: &KDirModel::expand, context: this, slot: [this](QModelIndex index) {
1752 d->slotExpandToUrl(index);
1753 });
1754 } else {
1755 d->m_itemsToBeSetAsCurrent.clear();
1756 }
1757
1758 const bool previewForcedToTrue = d->m_inlinePreviewState == KDirOperatorPrivate::ForcedToTrue;
1759 const bool previewShown = d->m_inlinePreviewState == KDirOperatorPrivate::NotForced ? d->m_showPreviews : previewForcedToTrue;
1760 d->m_itemView->setIconSize(previewForcedToTrue ? QSize(KIconLoader::SizeHuge, KIconLoader::SizeHuge) : QSize(d->m_iconSize, d->m_iconSize));
1761 d->m_previewGenerator = new KFilePreviewGenerator(d->m_itemView);
1762 d->m_previewGenerator->setPreviewShown(previewShown);
1763 action(action: KDirOperator::ShowPreview)->setChecked(previewShown);
1764
1765 // ensure we change everything needed
1766 d->slotChangeDecorationPosition();
1767 updateViewActions();
1768
1769 Q_EMIT viewChanged(newView: view);
1770
1771 const int zoom = previewForcedToTrue ? KIconLoader::SizeHuge : d->iconSizeForViewType(itemView: view);
1772
1773 // this will make d->m_iconSize be updated, since setIconSize slot will be called
1774 Q_EMIT currentIconSizeChanged(size: zoom);
1775}
1776
1777void KDirOperator::setDirLister(KDirLister *lister)
1778{
1779 if (lister == d->m_dirLister) { // sanity check
1780 return;
1781 }
1782
1783 delete d->m_dirModel;
1784 d->m_dirModel = nullptr;
1785
1786 delete d->m_proxyModel;
1787 d->m_proxyModel = nullptr;
1788
1789 // delete d->m_dirLister; // deleted by KDirModel already, which took ownership
1790 d->m_dirLister = lister;
1791
1792 d->m_dirModel = new KDirModel(this);
1793 d->m_dirModel->setDirLister(d->m_dirLister);
1794 d->m_dirModel->setDropsAllowed(KDirModel::DropOnDirectory);
1795
1796 d->m_shouldFetchForItems = qobject_cast<QTreeView *>(object: d->m_itemView);
1797 if (d->m_shouldFetchForItems) {
1798 connect(sender: d->m_dirModel, signal: &KDirModel::expand, context: this, slot: [this](QModelIndex index) {
1799 d->slotExpandToUrl(index);
1800 });
1801 } else {
1802 d->m_itemsToBeSetAsCurrent.clear();
1803 }
1804
1805 d->m_proxyModel = new KDirSortFilterProxyModel(this);
1806 d->m_proxyModel->setSourceModel(d->m_dirModel);
1807
1808 d->m_dirLister->setDelayedMimeTypes(true);
1809
1810 QWidget *mainWidget = topLevelWidget();
1811 d->m_dirLister->setMainWindow(mainWidget);
1812 // qDebug() << "mainWidget=" << mainWidget;
1813
1814 connect(sender: d->m_dirLister, signal: &KCoreDirLister::percent, context: this, slot: [this](int percent) {
1815 d->slotProgress(percent);
1816 });
1817 connect(sender: d->m_dirLister, signal: &KCoreDirLister::started, context: this, slot: [this]() {
1818 d->slotStarted();
1819 });
1820 connect(sender: d->m_dirLister, signal: qOverload<>(&KCoreDirLister::completed), context: this, slot: [this]() {
1821 d->slotIOFinished();
1822 });
1823 connect(sender: d->m_dirLister, signal: qOverload<>(&KCoreDirLister::canceled), context: this, slot: [this]() {
1824 d->slotCanceled();
1825 });
1826 connect(sender: d->m_dirLister, signal: &KCoreDirLister::jobError, context: this, slot: [this]() {
1827 d->slotIOFinished();
1828 });
1829 connect(sender: d->m_dirLister, signal: &KCoreDirLister::redirection, context: this, slot: [this](const QUrl &, const QUrl &newUrl) {
1830 d->slotRedirected(newURL: newUrl);
1831 });
1832 connect(sender: d->m_dirLister, signal: &KCoreDirLister::newItems, context: this, slot: [this]() {
1833 d->slotItemsChanged();
1834 });
1835 connect(sender: d->m_dirLister, signal: &KCoreDirLister::itemsDeleted, context: this, slot: [this]() {
1836 d->slotItemsChanged();
1837 });
1838 connect(sender: d->m_dirLister, signal: qOverload<>(&KCoreDirLister::clear), context: this, slot: [this]() {
1839 d->slotItemsChanged();
1840 });
1841}
1842
1843void KDirOperator::selectDir(const KFileItem &item)
1844{
1845 setUrl(newurl: item.targetUrl(), clearforward: true);
1846}
1847
1848void KDirOperator::selectFile(const KFileItem &item)
1849{
1850 QApplication::restoreOverrideCursor();
1851
1852 Q_EMIT fileSelected(item);
1853 d->m_keyNavigation = false;
1854}
1855
1856void KDirOperator::highlightFile(const KFileItem &item)
1857{
1858 if ((d->m_preview != nullptr && !d->m_preview->isHidden()) && !item.isNull()) {
1859 d->m_preview->showPreview(url: item.url());
1860 }
1861
1862 Q_EMIT fileHighlighted(item);
1863 d->m_keyNavigation = false;
1864}
1865
1866void KDirOperator::setCurrentItem(const QUrl &url)
1867{
1868 // qDebug();
1869
1870 KFileItem item = d->m_dirLister->findByUrl(url);
1871 if (d->m_shouldFetchForItems && item.isNull()) {
1872 d->m_itemsToBeSetAsCurrent << url;
1873
1874 if (d->m_viewKind == KFile::DetailTree) {
1875 d->m_dirModel->expandToUrl(url);
1876 }
1877
1878 return;
1879 }
1880
1881 setCurrentItem(item);
1882}
1883
1884void KDirOperator::setCurrentItem(const KFileItem &item)
1885{
1886 // qDebug();
1887
1888 if (!d->m_itemView) {
1889 return;
1890 }
1891
1892 QItemSelectionModel *selModel = d->m_itemView->selectionModel();
1893 if (selModel) {
1894 selModel->clear();
1895 if (!item.isNull()) {
1896 const QModelIndex dirIndex = d->m_dirModel->indexForItem(item);
1897 const QModelIndex proxyIndex = d->m_proxyModel->mapFromSource(sourceIndex: dirIndex);
1898 selModel->setCurrentIndex(index: proxyIndex, command: QItemSelectionModel::Select);
1899 }
1900 }
1901}
1902
1903void KDirOperator::setCurrentItems(const QList<QUrl> &urls)
1904{
1905 // qDebug();
1906
1907 if (!d->m_itemView) {
1908 return;
1909 }
1910
1911 KFileItemList itemList;
1912 for (const QUrl &url : urls) {
1913 KFileItem item = d->m_dirLister->findByUrl(url);
1914 if (d->m_shouldFetchForItems && item.isNull()) {
1915 d->m_itemsToBeSetAsCurrent << url;
1916
1917 if (d->m_viewKind == KFile::DetailTree) {
1918 d->m_dirModel->expandToUrl(url);
1919 }
1920
1921 continue;
1922 }
1923
1924 itemList << item;
1925 }
1926
1927 setCurrentItems(itemList);
1928}
1929
1930void KDirOperator::setCurrentItems(const KFileItemList &items)
1931{
1932 // qDebug();
1933
1934 if (d->m_itemView == nullptr) {
1935 return;
1936 }
1937
1938 QItemSelectionModel *selModel = d->m_itemView->selectionModel();
1939 if (selModel) {
1940 selModel->clear();
1941 QModelIndex proxyIndex;
1942 for (const KFileItem &item : items) {
1943 if (!item.isNull()) {
1944 const QModelIndex dirIndex = d->m_dirModel->indexForItem(item);
1945 proxyIndex = d->m_proxyModel->mapFromSource(sourceIndex: dirIndex);
1946 selModel->select(index: proxyIndex, command: QItemSelectionModel::Select);
1947 }
1948 }
1949 if (proxyIndex.isValid()) {
1950 selModel->setCurrentIndex(index: proxyIndex, command: QItemSelectionModel::NoUpdate);
1951 }
1952 }
1953}
1954
1955QString KDirOperator::makeCompletion(const QString &string)
1956{
1957 if (string.isEmpty()) {
1958 d->m_itemView->selectionModel()->clear();
1959 return QString();
1960 }
1961
1962 prepareCompletionObjects();
1963 return d->m_completion.makeCompletion(string);
1964}
1965
1966QString KDirOperator::makeDirCompletion(const QString &string)
1967{
1968 if (string.isEmpty()) {
1969 d->m_itemView->selectionModel()->clear();
1970 return QString();
1971 }
1972
1973 prepareCompletionObjects();
1974 return d->m_dirCompletion.makeCompletion(string);
1975}
1976
1977void KDirOperator::prepareCompletionObjects()
1978{
1979 if (d->m_itemView == nullptr) {
1980 return;
1981 }
1982
1983 if (d->m_completeListDirty) { // create the list of all possible completions
1984 const KFileItemList itemList = d->m_dirLister->items();
1985 for (const KFileItem &item : itemList) {
1986 d->m_completion.addItem(item: item.name());
1987 if (item.isDir()) {
1988 d->m_dirCompletion.addItem(item: item.name());
1989 }
1990 }
1991 d->m_completeListDirty = false;
1992 }
1993}
1994
1995void KDirOperator::slotCompletionMatch(const QString &match)
1996{
1997 QUrl url(match);
1998 if (url.isRelative()) {
1999 url = d->m_currUrl.resolved(relative: url);
2000 }
2001 setCurrentItem(url);
2002 Q_EMIT completion(match);
2003}
2004
2005void KDirOperator::setupActions()
2006{
2007 d->m_actionMenu = new KActionMenu(i18n("Menu"), this);
2008 d->m_actions[PopupMenu] = d->m_actionMenu;
2009
2010 QAction *upAction = KStandardActions::create(id: KStandardActions::Up, recvr: this, slot: &KDirOperator::cdUp, parent: this);
2011 d->m_actions[Up] = upAction;
2012 upAction->setText(i18n("Parent Folder"));
2013
2014 QAction *backAction = KStandardActions::create(id: KStandardActions::Back, recvr: this, slot: &KDirOperator::back, parent: this);
2015 d->m_actions[Back] = backAction;
2016 auto backShortcuts = backAction->shortcuts();
2017 backShortcuts << Qt::Key_Backspace;
2018 backAction->setShortcuts(backShortcuts);
2019 backAction->setToolTip(i18nc("@info", "Go back"));
2020
2021 QAction *forwardAction = KStandardActions::create(id: KStandardActions::Forward, recvr: this, slot: &KDirOperator::forward, parent: this);
2022 d->m_actions[Forward] = forwardAction;
2023 forwardAction->setToolTip(i18nc("@info", "Go forward"));
2024
2025 QAction *homeAction = KStandardActions::create(id: KStandardActions::Home, recvr: this, slot: &KDirOperator::home, parent: this);
2026 d->m_actions[Home] = homeAction;
2027 homeAction->setText(i18n("Home Folder"));
2028
2029 QAction *reloadAction = KStandardActions::create(id: KStandardActions::Redisplay, recvr: this, slot: &KDirOperator::rereadDir, parent: this);
2030 d->m_actions[Reload] = reloadAction;
2031 reloadAction->setText(i18n("Reload"));
2032 reloadAction->setShortcuts(KStandardShortcut::shortcut(id: KStandardShortcut::Reload));
2033
2034 auto *mkdirAction = new QAction(i18nc("@action", "New Folder…"), this);
2035 d->m_actions[NewFolder] = mkdirAction;
2036 mkdirAction->setIcon(QIcon::fromTheme(QStringLiteral("folder-new")));
2037 connect(sender: mkdirAction, signal: &QAction::triggered, context: this, slot: [this]() {
2038 mkdir();
2039 });
2040
2041 QAction *rename = KStandardActions::renameFile(recvr: this, slot: &KDirOperator::renameSelected, parent: this);
2042 d->m_actions[Rename] = rename;
2043
2044 QAction *trash = new QAction(i18n("Move to Trash"), this);
2045 d->m_actions[Trash] = trash;
2046 trash->setIcon(QIcon::fromTheme(QStringLiteral("user-trash")));
2047 trash->setShortcut(Qt::Key_Delete);
2048 connect(sender: trash, signal: &QAction::triggered, context: this, slot: &KDirOperator::trashSelected);
2049
2050 QAction *action = new QAction(i18n("Delete"), this);
2051 d->m_actions[Delete] = action;
2052 action->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
2053 action->setShortcut(Qt::SHIFT | Qt::Key_Delete);
2054 connect(sender: action, signal: &QAction::triggered, context: this, slot: &KDirOperator::deleteSelected);
2055
2056 // the sort menu actions
2057 KActionMenu *sortMenu = new KActionMenu(i18n("Sorting"), this);
2058 d->m_actions[SortMenu] = sortMenu;
2059 sortMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-sort")));
2060 sortMenu->setPopupMode(QToolButton::InstantPopup);
2061
2062 KToggleAction *byNameAction = new KToggleAction(i18n("Sort by Name"), this);
2063 d->m_actions[SortByName] = byNameAction;
2064 connect(sender: byNameAction, signal: &QAction::triggered, context: this, slot: [this]() {
2065 d->slotSortByName();
2066 });
2067
2068 KToggleAction *bySizeAction = new KToggleAction(i18n("Sort by Size"), this);
2069 d->m_actions[SortBySize] = bySizeAction;
2070 connect(sender: bySizeAction, signal: &QAction::triggered, context: this, slot: [this]() {
2071 d->slotSortBySize();
2072 });
2073
2074 KToggleAction *byDateAction = new KToggleAction(i18n("Sort by Date"), this);
2075 d->m_actions[SortByDate] = byDateAction;
2076 connect(sender: byDateAction, signal: &QAction::triggered, context: this, slot: [this]() {
2077 d->slotSortByDate();
2078 });
2079
2080 KToggleAction *byTypeAction = new KToggleAction(i18n("Sort by Type"), this);
2081 d->m_actions[SortByType] = byTypeAction;
2082 connect(sender: byTypeAction, signal: &QAction::triggered, context: this, slot: [this]() {
2083 d->slotSortByType();
2084 });
2085
2086 QActionGroup *sortOrderGroup = new QActionGroup(this);
2087 sortOrderGroup->setExclusive(true);
2088
2089 KToggleAction *ascendingAction = new KToggleAction(i18n("Ascending"), this);
2090 d->m_actions[SortAscending] = ascendingAction;
2091 ascendingAction->setActionGroup(sortOrderGroup);
2092 connect(sender: ascendingAction, signal: &QAction::triggered, context: this, slot: [this]() {
2093 this->d->slotSortReversed(doReverse: false);
2094 });
2095
2096 KToggleAction *descendingAction = new KToggleAction(i18n("Descending"), this);
2097 d->m_actions[SortDescending] = descendingAction;
2098 descendingAction->setActionGroup(sortOrderGroup);
2099 connect(sender: descendingAction, signal: &QAction::triggered, context: this, slot: [this]() {
2100 this->d->slotSortReversed(doReverse: true);
2101 });
2102
2103 KToggleAction *dirsFirstAction = new KToggleAction(i18n("Folders First"), this);
2104 d->m_actions[SortFoldersFirst] = dirsFirstAction;
2105 connect(sender: dirsFirstAction, signal: &QAction::triggered, context: this, slot: [this]() {
2106 d->slotToggleDirsFirst();
2107 });
2108
2109 KToggleAction *hiddenFilesLastAction = new KToggleAction(i18n("Hidden Files Last"), this);
2110 d->m_actions[SortHiddenFilesLast] = hiddenFilesLastAction;
2111 connect(sender: hiddenFilesLastAction, signal: &QAction::toggled, context: this, slot: [this](bool checked) {
2112 d->m_proxyModel->setSortHiddenFilesLast(checked);
2113 });
2114
2115 // View modes that match those of Dolphin
2116 KToggleAction *iconsViewAction = new KToggleAction(i18n("Icons View"), this);
2117 d->m_actions[ViewIconsView] = iconsViewAction;
2118 iconsViewAction->setIcon(QIcon::fromTheme(QStringLiteral("view-list-icons")));
2119 connect(sender: iconsViewAction, signal: &QAction::triggered, context: this, slot: [this]() {
2120 d->slotIconsView();
2121 });
2122
2123 KToggleAction *compactViewAction = new KToggleAction(i18n("Compact View"), this);
2124 d->m_actions[ViewCompactView] = compactViewAction;
2125 compactViewAction->setIcon(QIcon::fromTheme(QStringLiteral("view-list-details")));
2126 connect(sender: compactViewAction, signal: &QAction::triggered, context: this, slot: [this]() {
2127 d->slotCompactView();
2128 });
2129
2130 KToggleAction *detailsViewAction = new KToggleAction(i18n("Details View"), this);
2131 d->m_actions[ViewDetailsView] = detailsViewAction;
2132 detailsViewAction->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree")));
2133 connect(sender: detailsViewAction, signal: &QAction::triggered, context: this, slot: [this]() {
2134 d->slotDetailsView();
2135 });
2136
2137 QActionGroup *viewModeGroup = new QActionGroup(this);
2138 viewModeGroup->setExclusive(true);
2139 iconsViewAction->setActionGroup(viewModeGroup);
2140 compactViewAction->setActionGroup(viewModeGroup);
2141 detailsViewAction->setActionGroup(viewModeGroup);
2142
2143 QActionGroup *sortGroup = new QActionGroup(this);
2144 byNameAction->setActionGroup(sortGroup);
2145 bySizeAction->setActionGroup(sortGroup);
2146 byDateAction->setActionGroup(sortGroup);
2147 byTypeAction->setActionGroup(sortGroup);
2148
2149 d->m_decorationMenu = new KActionMenu(i18n("Icon Position"), this);
2150 d->m_actions[DecorationMenu] = d->m_decorationMenu;
2151
2152 d->m_leftAction = new KToggleAction(i18n("Next to File Name"), this);
2153 d->m_actions[DecorationAtLeft] = d->m_leftAction;
2154 connect(sender: d->m_leftAction, signal: &QAction::triggered, context: this, slot: [this]() {
2155 d->slotChangeDecorationPosition();
2156 });
2157
2158 KToggleAction *topAction = new KToggleAction(i18n("Above File Name"), this);
2159 d->m_actions[DecorationAtTop] = topAction;
2160 connect(sender: topAction, signal: &QAction::triggered, context: this, slot: [this]() {
2161 d->slotChangeDecorationPosition();
2162 });
2163
2164 d->m_decorationMenu->addAction(action: d->m_leftAction);
2165 d->m_decorationMenu->addAction(action: topAction);
2166
2167 QActionGroup *decorationGroup = new QActionGroup(this);
2168 decorationGroup->setExclusive(true);
2169 d->m_leftAction->setActionGroup(decorationGroup);
2170 topAction->setActionGroup(decorationGroup);
2171
2172 KToggleAction *shortAction = new KToggleAction(i18n("Short View"), this);
2173 d->m_actions[ShortView] = shortAction;
2174 shortAction->setIcon(QIcon::fromTheme(QStringLiteral("view-list-icons")));
2175 connect(sender: shortAction, signal: &QAction::triggered, context: this, slot: [this]() {
2176 d->slotSimpleView();
2177 });
2178
2179 KToggleAction *detailedAction = new KToggleAction(i18n("Detailed View"), this);
2180 d->m_actions[DetailedView] = detailedAction;
2181 detailedAction->setIcon(QIcon::fromTheme(QStringLiteral("view-list-details")));
2182 connect(sender: detailedAction, signal: &QAction::triggered, context: this, slot: [this]() {
2183 d->slotDetailedView();
2184 });
2185
2186 KToggleAction *treeAction = new KToggleAction(i18n("Tree View"), this);
2187 d->m_actions[TreeView] = treeAction;
2188 treeAction->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree")));
2189 connect(sender: treeAction, signal: &QAction::triggered, context: this, slot: [this]() {
2190 d->slotTreeView();
2191 });
2192
2193 KToggleAction *detailedTreeAction = new KToggleAction(i18n("Detailed Tree View"), this);
2194 d->m_actions[DetailedTreeView] = detailedTreeAction;
2195 detailedTreeAction->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree")));
2196 connect(sender: detailedTreeAction, signal: &QAction::triggered, context: this, slot: [this]() {
2197 d->slotDetailedTreeView();
2198 });
2199
2200 QActionGroup *viewGroup = new QActionGroup(this);
2201 shortAction->setActionGroup(viewGroup);
2202 detailedAction->setActionGroup(viewGroup);
2203 treeAction->setActionGroup(viewGroup);
2204 detailedTreeAction->setActionGroup(viewGroup);
2205
2206 KToggleAction *allowExpansionAction = new KToggleAction(i18n("Allow Expansion in Details View"), this);
2207 d->m_actions[AllowExpansionInDetailsView] = allowExpansionAction;
2208 connect(sender: allowExpansionAction, signal: &QAction::toggled, context: this, slot: [this](bool allow) {
2209 d->slotToggleAllowExpansion(allow);
2210 });
2211
2212 KToggleAction *showHiddenAction = new KToggleAction(i18n("Show Hidden Files"), this);
2213 d->m_actions[ShowHiddenFiles] = showHiddenAction;
2214 showHiddenAction->setShortcuts(KStandardShortcut::showHideHiddenFiles());
2215 connect(sender: showHiddenAction, signal: &QAction::toggled, context: this, slot: [this](bool show) {
2216 d->slotToggleHidden(show);
2217 });
2218
2219 KToggleAction *previewAction = new KToggleAction(i18n("Show Preview Panel"), this);
2220 d->m_actions[ShowPreviewPanel] = previewAction;
2221 previewAction->setShortcut(Qt::Key_F11);
2222 connect(sender: previewAction, signal: &QAction::toggled, context: this, slot: [this](bool enable) {
2223 d->togglePreview(on: enable);
2224 });
2225
2226 KToggleAction *inlinePreview = new KToggleAction(QIcon::fromTheme(QStringLiteral("view-preview")), i18n("Show Preview"), this);
2227 d->m_actions[ShowPreview] = inlinePreview;
2228 inlinePreview->setShortcut(Qt::Key_F12);
2229 connect(sender: inlinePreview, signal: &QAction::toggled, context: this, slot: [this](bool enable) {
2230 d->toggleInlinePreviews(show: enable);
2231 });
2232
2233 QAction *fileManager = new QAction(i18n("Open Containing Folder"), this);
2234 d->m_actions[OpenContainingFolder] = fileManager;
2235 fileManager->setIcon(QIcon::fromTheme(QStringLiteral("system-file-manager")));
2236 connect(sender: fileManager, signal: &QAction::triggered, context: this, slot: [this]() {
2237 d->slotOpenFileManager();
2238 });
2239
2240 action = new QAction(i18n("Properties"), this);
2241 d->m_actions[Properties] = action;
2242 action->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
2243 action->setShortcut(Qt::ALT | Qt::Key_Return);
2244 connect(sender: action, signal: &QAction::triggered, context: this, slot: [this]() {
2245 d->slotProperties();
2246 });
2247
2248 // the view menu actions
2249 KActionMenu *viewMenu = new KActionMenu(i18n("&View Mode"), this);
2250 d->m_actions[ViewModeMenu] = viewMenu;
2251 viewMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree")));
2252 viewMenu->addAction(action: shortAction);
2253 viewMenu->addAction(action: detailedAction);
2254 // Comment following lines to hide the extra two modes
2255 viewMenu->addAction(action: treeAction);
2256 viewMenu->addAction(action: detailedTreeAction);
2257 // TODO: QAbstractItemView does not offer an action collection. Provide
2258 // an interface to add a custom action collection.
2259
2260 d->m_itemActions = new KFileItemActions(this);
2261
2262 d->m_newFileMenu = new KNewFileMenu(this);
2263 d->m_actions[KDirOperator::New] = d->m_newFileMenu;
2264 connect(sender: d->m_newFileMenu, signal: &KNewFileMenu::directoryCreated, context: this, slot: [this](const QUrl &url) {
2265 d->slotDirectoryCreated(url);
2266 });
2267 connect(sender: d->m_newFileMenu, signal: &KNewFileMenu::selectExistingDir, context: this, slot: [this](const QUrl &url) {
2268 setCurrentItem(url);
2269 });
2270
2271 const QList<QAction *> list = d->m_actions.values();
2272 for (QAction *action : list) {
2273 addAction(action);
2274 action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
2275 }
2276}
2277
2278void KDirOperator::setupMenu()
2279{
2280 setupMenu(SortActions | ViewActions | FileActions);
2281}
2282
2283void KDirOperator::setupMenu(int whichActions)
2284{
2285 // first fill the submenus (sort and view)
2286 KActionMenu *sortMenu = static_cast<KActionMenu *>(action(action: KDirOperator::SortMenu));
2287 sortMenu->menu()->clear();
2288 sortMenu->addAction(action: action(action: KDirOperator::SortByName));
2289 sortMenu->addAction(action: action(action: KDirOperator::SortBySize));
2290 sortMenu->addAction(action: action(action: KDirOperator::SortByDate));
2291 sortMenu->addAction(action: action(action: KDirOperator::SortByType));
2292 sortMenu->addSeparator();
2293 sortMenu->addAction(action: action(action: KDirOperator::SortAscending));
2294 sortMenu->addAction(action: action(action: KDirOperator::SortDescending));
2295 sortMenu->addSeparator();
2296 sortMenu->addAction(action: action(action: KDirOperator::SortFoldersFirst));
2297 sortMenu->addAction(action: action(action: KDirOperator::SortHiddenFilesLast));
2298
2299 // now plug everything into the popupmenu
2300 d->m_actionMenu->menu()->clear();
2301 if (whichActions & NavActions) {
2302 d->m_actionMenu->addAction(action: action(action: KDirOperator::Up));
2303 d->m_actionMenu->addAction(action: action(action: KDirOperator::Back));
2304 d->m_actionMenu->addAction(action: action(action: KDirOperator::Forward));
2305 d->m_actionMenu->addAction(action: action(action: KDirOperator::Home));
2306 d->m_actionMenu->addSeparator();
2307 }
2308
2309 if (whichActions & FileActions) {
2310 d->m_actionMenu->addAction(action: action(action: KDirOperator::New));
2311
2312 d->m_actionMenu->addAction(action: action(action: KDirOperator::Rename));
2313 action(action: KDirOperator::Rename)->setEnabled(KProtocolManager::supportsMoving(url: d->m_currUrl));
2314
2315 if (d->m_currUrl.isLocalFile() && !(QApplication::keyboardModifiers() & Qt::ShiftModifier)) {
2316 d->m_actionMenu->addAction(action: action(action: KDirOperator::Trash));
2317 }
2318 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KDE"));
2319 const bool del = !d->m_currUrl.isLocalFile() || (QApplication::keyboardModifiers() & Qt::ShiftModifier) || cg.readEntry(key: "ShowDeleteCommand", defaultValue: false);
2320 if (del) {
2321 d->m_actionMenu->addAction(action: action(action: KDirOperator::Delete));
2322 }
2323 d->m_actionMenu->addSeparator();
2324 }
2325
2326 if (whichActions & SortActions) {
2327 d->m_actionMenu->addAction(action: sortMenu);
2328 if (!(whichActions & ViewActions)) {
2329 d->m_actionMenu->addSeparator();
2330 }
2331 }
2332
2333 if (whichActions & ViewActions) {
2334 d->m_actionMenu->addAction(action: action(action: KDirOperator::ViewModeMenu));
2335 d->m_actionMenu->addAction(action: action(action: KDirOperator::Reload));
2336 d->m_actionMenu->addSeparator();
2337 }
2338
2339 if (whichActions & FileActions) {
2340 d->m_actionMenu->addAction(action: action(action: KDirOperator::OpenContainingFolder));
2341 d->m_actionMenu->addAction(action: action(action: KDirOperator::Properties));
2342 }
2343}
2344
2345void KDirOperator::updateSortActions()
2346{
2347 QAction *ascending = action(action: KDirOperator::SortAscending);
2348 QAction *descending = action(action: KDirOperator::SortDescending);
2349
2350 if (KFile::isSortByName(sort: d->m_sorting)) {
2351 action(action: KDirOperator::SortByName)->setChecked(true);
2352 descending->setText(i18nc("Sort descending", "Z-A"));
2353 ascending->setText(i18nc("Sort ascending", "A-Z"));
2354 } else if (KFile::isSortByDate(sort: d->m_sorting)) {
2355 action(action: KDirOperator::SortByDate)->setChecked(true);
2356 descending->setText(i18nc("Sort ascending", "Oldest First"));
2357 ascending->setText(i18nc("Sort descending", "Newest First"));
2358 } else if (KFile::isSortBySize(sort: d->m_sorting)) {
2359 action(action: KDirOperator::SortBySize)->setChecked(true);
2360 descending->setText(i18nc("Sort descending", "Largest First"));
2361 ascending->setText(i18nc("Sort ascending", "Smallest First"));
2362 } else if (KFile::isSortByType(sort: d->m_sorting)) {
2363 action(action: KDirOperator::SortByType)->setChecked(true);
2364 descending->setText(i18nc("Sort descending", "Z-A"));
2365 ascending->setText(i18nc("Sort ascending", "A-Z"));
2366 }
2367 ascending->setChecked(!(d->m_sorting & QDir::Reversed));
2368 descending->setChecked(d->m_sorting & QDir::Reversed);
2369 action(action: KDirOperator::SortFoldersFirst)->setChecked(d->m_sorting & QDir::DirsFirst);
2370}
2371
2372void KDirOperator::updateViewActions()
2373{
2374 KFile::FileView fv = static_cast<KFile::FileView>(d->m_viewKind);
2375
2376 // QAction *separateDirs = d->actionCollection->action("separate dirs");
2377 // separateDirs->setChecked(KFile::isSeparateDirs(fv) &&
2378 // separateDirs->isEnabled());
2379
2380 action(action: KDirOperator::ShortView)->setChecked(KFile::isSimpleView(view: fv));
2381 action(action: KDirOperator::DetailedView)->setChecked(KFile::isDetailView(view: fv));
2382 action(action: KDirOperator::TreeView)->setChecked(KFile::isTreeView(view: fv));
2383 action(action: KDirOperator::DetailedTreeView)->setChecked(KFile::isDetailTreeView(view: fv));
2384
2385 // dolphin style views
2386 action(action: KDirOperator::ViewIconsView)->setChecked(KFile::isSimpleView(view: fv) && d->m_decorationPosition == QStyleOptionViewItem::Top);
2387 action(action: KDirOperator::ViewCompactView)->setChecked(KFile::isSimpleView(view: fv) && d->m_decorationPosition == QStyleOptionViewItem::Left);
2388 action(action: KDirOperator::ViewDetailsView)->setChecked(KFile::isDetailTreeView(view: fv) || KFile::isDetailView(view: fv));
2389}
2390
2391void KDirOperator::readConfig(const KConfigGroup &configGroup)
2392{
2393 d->m_defaultView = 0;
2394 QString viewStyle = configGroup.readEntry(key: "View Style", aDefault: "DetailTree");
2395 if (viewStyle == QLatin1String("Detail")) {
2396 d->m_defaultView |= KFile::Detail;
2397 } else if (viewStyle == QLatin1String("Tree")) {
2398 d->m_defaultView |= KFile::Tree;
2399 } else if (viewStyle == QLatin1String("DetailTree")) {
2400 d->m_defaultView |= KFile::DetailTree;
2401 } else {
2402 d->m_defaultView |= KFile::Simple;
2403 }
2404 // if (configGroup.readEntry(QLatin1String("Separate Directories"),
2405 // DefaultMixDirsAndFiles)) {
2406 // d->defaultView |= KFile::SeparateDirs;
2407 //}
2408 if (configGroup.readEntry(QStringLiteral("Show Preview"), aDefault: false)) {
2409 d->m_defaultView |= KFile::PreviewContents;
2410 }
2411
2412 d->m_previewWidth = configGroup.readEntry(QStringLiteral("Preview Width"), aDefault: 100);
2413
2414 if (configGroup.readEntry(QStringLiteral("Show hidden files"), aDefault: DefaultShowHidden)) {
2415 action(action: KDirOperator::ShowHiddenFiles)->setChecked(true);
2416 d->m_dirLister->setShowHiddenFiles(true);
2417 }
2418
2419 if (configGroup.readEntry(QStringLiteral("Allow Expansion"), aDefault: DefaultShowHidden)) {
2420 action(action: KDirOperator::AllowExpansionInDetailsView)->setChecked(true);
2421 }
2422
2423 const bool hiddenFilesLast = configGroup.readEntry(QStringLiteral("Sort hidden files last"), aDefault: DefaultHiddenFilesLast);
2424 action(action: KDirOperator::SortHiddenFilesLast)->setChecked(hiddenFilesLast);
2425
2426 QDir::SortFlags sorting = QDir::Name;
2427 if (configGroup.readEntry(QStringLiteral("Sort directories first"), aDefault: DefaultDirsFirst)) {
2428 sorting |= QDir::DirsFirst;
2429 }
2430 QString name = QStringLiteral("Name");
2431 QString sortBy = configGroup.readEntry(QStringLiteral("Sort by"), aDefault: name);
2432 if (sortBy == name) {
2433 sorting |= QDir::Name;
2434 } else if (sortBy == QLatin1String("Size")) {
2435 sorting |= QDir::Size;
2436 } else if (sortBy == QLatin1String("Date")) {
2437 sorting |= QDir::Time;
2438 } else if (sortBy == QLatin1String("Type")) {
2439 sorting |= QDir::Type;
2440 }
2441 if (configGroup.readEntry(QStringLiteral("Sort reversed"), aDefault: DefaultSortReversed)) {
2442 sorting |= QDir::Reversed;
2443 }
2444 d->updateSorting(sort: sorting);
2445
2446 if (d->m_inlinePreviewState == KDirOperatorPrivate::NotForced) {
2447 d->m_showPreviews = configGroup.readEntry(QStringLiteral("Show Inline Previews"), aDefault: true);
2448 }
2449 QStyleOptionViewItem::Position pos =
2450 (QStyleOptionViewItem::Position)configGroup.readEntry(QStringLiteral("Decoration position"), aDefault: (int)QStyleOptionViewItem::Top);
2451 setDecorationPosition(pos);
2452}
2453
2454void KDirOperator::writeConfig(KConfigGroup &configGroup)
2455{
2456 QString sortBy = QStringLiteral("Name");
2457 if (KFile::isSortBySize(sort: d->m_sorting)) {
2458 sortBy = QStringLiteral("Size");
2459 } else if (KFile::isSortByDate(sort: d->m_sorting)) {
2460 sortBy = QStringLiteral("Date");
2461 } else if (KFile::isSortByType(sort: d->m_sorting)) {
2462 sortBy = QStringLiteral("Type");
2463 }
2464
2465 configGroup.writeEntry(QStringLiteral("Sort by"), value: sortBy);
2466
2467 configGroup.writeEntry(QStringLiteral("Sort reversed"), value: action(action: KDirOperator::SortDescending)->isChecked());
2468
2469 configGroup.writeEntry(QStringLiteral("Sort directories first"), value: action(action: KDirOperator::SortFoldersFirst)->isChecked());
2470
2471 const bool hiddenFilesLast = action(action: KDirOperator::SortHiddenFilesLast)->isChecked();
2472 configGroup.writeEntry(QStringLiteral("Sort hidden files last"), value: hiddenFilesLast);
2473
2474 // don't save the preview when an application specific preview is in use.
2475 bool appSpecificPreview = false;
2476 if (d->m_preview) {
2477 KFileMetaPreview *tmp = dynamic_cast<KFileMetaPreview *>(d->m_preview);
2478 appSpecificPreview = (tmp == nullptr);
2479 }
2480
2481 if (!appSpecificPreview) {
2482 KToggleAction *previewAction = static_cast<KToggleAction *>(action(action: KDirOperator::ShowPreviewPanel));
2483 if (previewAction->isEnabled()) {
2484 bool hasPreview = previewAction->isChecked();
2485 configGroup.writeEntry(QStringLiteral("Show Preview"), value: hasPreview);
2486
2487 if (hasPreview) {
2488 // remember the width of the preview widget
2489 QList<int> sizes = d->m_splitter->sizes();
2490 Q_ASSERT(sizes.count() == 2);
2491 configGroup.writeEntry(QStringLiteral("Preview Width"), value: sizes[1]);
2492 }
2493 }
2494 }
2495
2496 configGroup.writeEntry(QStringLiteral("Show hidden files"), value: action(action: KDirOperator::ShowHiddenFiles)->isChecked());
2497
2498 configGroup.writeEntry(QStringLiteral("Allow Expansion"), value: action(action: KDirOperator::AllowExpansionInDetailsView)->isChecked());
2499
2500 KFile::FileView fv = static_cast<KFile::FileView>(d->m_viewKind);
2501 QString style;
2502 if (KFile::isDetailView(view: fv)) {
2503 style = QStringLiteral("Detail");
2504 } else if (KFile::isSimpleView(view: fv)) {
2505 style = QStringLiteral("Simple");
2506 } else if (KFile::isTreeView(view: fv)) {
2507 style = QStringLiteral("Tree");
2508 } else if (KFile::isDetailTreeView(view: fv)) {
2509 style = QStringLiteral("DetailTree");
2510 }
2511 configGroup.writeEntry(QStringLiteral("View Style"), value: style);
2512
2513 if (d->m_inlinePreviewState == KDirOperatorPrivate::NotForced) {
2514 configGroup.writeEntry(QStringLiteral("Show Inline Previews"), value: d->m_showPreviews);
2515 d->writeIconZoomSettingsIfNeeded();
2516 }
2517
2518 configGroup.writeEntry(QStringLiteral("Decoration position"), value: (int)d->m_decorationPosition);
2519}
2520
2521void KDirOperatorPrivate::writeIconZoomSettingsIfNeeded()
2522{
2523 // must match behavior of iconSizeForViewType
2524 if (m_configGroup && m_itemView) {
2525 ZoomSettingsForView zoomSettings = zoomSettingsForView();
2526 if ((m_iconSize == zoomSettings.defaultValue) && !m_configGroup->hasDefault(key: zoomSettings.name)) {
2527 m_configGroup->revertToDefault(key: zoomSettings.name);
2528 } else {
2529 m_configGroup->writeEntry(key: zoomSettings.name, value: m_iconSize);
2530 }
2531 }
2532}
2533
2534void KDirOperator::resizeEvent(QResizeEvent *)
2535{
2536 // resize the m_splitter and assure that the width of
2537 // the preview widget is restored
2538 QList<int> sizes = d->m_splitter->sizes();
2539 const bool hasPreview = (sizes.count() == 2);
2540
2541 d->m_splitter->resize(size());
2542 sizes = d->m_splitter->sizes();
2543
2544 const bool restorePreviewWidth = hasPreview && (d->m_previewWidth != sizes[1]);
2545 if (restorePreviewWidth) {
2546 const int availableWidth = sizes[0] + sizes[1];
2547 sizes[0] = availableWidth - d->m_previewWidth;
2548 sizes[1] = d->m_previewWidth;
2549 d->m_splitter->setSizes(sizes);
2550 }
2551 if (hasPreview) {
2552 d->m_previewWidth = sizes[1];
2553 }
2554
2555 if (d->m_progressBar->parent() == this) {
2556 // Might be reparented into a statusbar
2557 const int frameWidth = style()->pixelMetric(metric: QStyle::PM_DefaultFrameWidth);
2558 d->m_progressBar->move(ax: frameWidth, ay: height() - d->m_progressBar->height() - frameWidth);
2559 }
2560}
2561
2562void KDirOperator::setOnlyDoubleClickSelectsFiles(bool enable)
2563{
2564 d->m_onlyDoubleClickSelectsFiles = enable;
2565 // TODO: port to QAbstractItemModel
2566 // if (d->itemView != 0) {
2567 // d->itemView->setOnlyDoubleClickSelectsFiles(enable);
2568 //}
2569}
2570
2571bool KDirOperator::onlyDoubleClickSelectsFiles() const
2572{
2573 return d->m_onlyDoubleClickSelectsFiles;
2574}
2575
2576void KDirOperator::setFollowNewDirectories(bool enable)
2577{
2578 d->m_followNewDirectories = enable;
2579}
2580
2581bool KDirOperator::followNewDirectories() const
2582{
2583 return d->m_followNewDirectories;
2584}
2585
2586void KDirOperator::setFollowSelectedDirectories(bool enable)
2587{
2588 d->m_followSelectedDirectories = enable;
2589}
2590
2591bool KDirOperator::followSelectedDirectories() const
2592{
2593 return d->m_followSelectedDirectories;
2594}
2595
2596void KDirOperatorPrivate::slotStarted()
2597{
2598 m_progressBar->setValue(0);
2599 // delay showing the progressbar for one second
2600 m_progressDelayTimer->setSingleShot(true);
2601 m_progressDelayTimer->start(msec: 1000);
2602}
2603
2604void KDirOperatorPrivate::slotShowProgress()
2605{
2606 m_progressBar->raise();
2607 m_progressBar->show();
2608}
2609
2610void KDirOperatorPrivate::slotProgress(int percent)
2611{
2612 m_progressBar->setValue(percent);
2613}
2614
2615void KDirOperatorPrivate::slotIOFinished()
2616{
2617 m_progressDelayTimer->stop();
2618 slotProgress(percent: 100);
2619 m_progressBar->hide();
2620 Q_EMIT q->finishedLoading();
2621 q->resetCursor();
2622
2623 if (m_preview) {
2624 m_preview->clearPreview();
2625 }
2626
2627 // m_lastUrl can be empty when e.g. kfilewidget is first opened
2628 if (!m_lastUrl.isEmpty() && m_dirHighlighting) {
2629 q->setCurrentItem(m_lastUrl);
2630 }
2631}
2632
2633void KDirOperatorPrivate::slotCanceled()
2634{
2635 Q_EMIT q->finishedLoading();
2636 q->resetCursor();
2637}
2638
2639QProgressBar *KDirOperator::progressBar() const
2640{
2641 return d->m_progressBar;
2642}
2643
2644void KDirOperator::clearHistory()
2645{
2646 qDeleteAll(c: d->m_backStack);
2647 d->m_backStack.clear();
2648 action(action: KDirOperator::Back)->setEnabled(false);
2649
2650 qDeleteAll(c: d->m_forwardStack);
2651 d->m_forwardStack.clear();
2652 action(action: KDirOperator::Forward)->setEnabled(false);
2653}
2654
2655void KDirOperator::setEnableDirHighlighting(bool enable)
2656{
2657 d->m_dirHighlighting = enable;
2658}
2659
2660bool KDirOperator::dirHighlighting() const
2661{
2662 return d->m_dirHighlighting;
2663}
2664
2665bool KDirOperator::dirOnlyMode() const
2666{
2667 return dirOnlyMode(mode: d->m_mode);
2668}
2669
2670bool KDirOperator::dirOnlyMode(uint mode)
2671{
2672 return ((mode & KFile::Directory) && (mode & (KFile::File | KFile::Files)) == 0);
2673}
2674
2675void KDirOperatorPrivate::slotProperties()
2676{
2677 if (m_itemView == nullptr) {
2678 return;
2679 }
2680
2681 const KFileItemList list = q->selectedItems();
2682 if (!list.isEmpty()) {
2683 auto *dialog = new KPropertiesDialog(list, q);
2684 dialog->setAttribute(Qt::WA_DeleteOnClose);
2685 dialog->setModal(true);
2686 dialog->show();
2687 }
2688}
2689
2690void KDirOperatorPrivate::slotActivated(const QModelIndex &index)
2691{
2692 const QModelIndex dirIndex = m_proxyModel->mapToSource(proxyIndex: index);
2693 KFileItem item = m_dirModel->itemForIndex(index: dirIndex);
2694
2695 const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
2696 if (item.isNull() || (modifiers & Qt::ShiftModifier) || (modifiers & Qt::ControlModifier)) {
2697 return;
2698 }
2699
2700 if (item.isDir()) {
2701 // Only allow disabling following selected directories on Tree and
2702 // DetailTree views as selected directories in these views still expand
2703 // when selected. For other views, disabling following selected
2704 // directories would make selecting a directory a noop which is
2705 // unintuitive.
2706 if (m_followSelectedDirectories || (m_viewKind != KFile::Tree && m_viewKind != KFile::DetailTree)) {
2707 q->selectDir(item);
2708 }
2709 } else {
2710 q->selectFile(item);
2711 }
2712}
2713
2714void KDirOperatorPrivate::slotSelectionChanged()
2715{
2716 if (m_itemView == nullptr) {
2717 return;
2718 }
2719
2720 // In the multiselection mode each selection change is indicated by
2721 // emitting a null item. Also when the selection has been cleared, a
2722 // null item must be emitted.
2723 const bool multiSelectionMode = (m_itemView->selectionMode() == QAbstractItemView::ExtendedSelection);
2724 const bool hasSelection = m_itemView->selectionModel()->hasSelection();
2725 if (multiSelectionMode || !hasSelection) {
2726 KFileItem nullItem;
2727 q->highlightFile(item: nullItem);
2728 } else {
2729 const KFileItem selectedItem = q->selectedItems().constFirst();
2730 q->highlightFile(item: selectedItem);
2731 }
2732}
2733
2734void KDirOperatorPrivate::openContextMenu(const QPoint &pos)
2735{
2736 const QModelIndex proxyIndex = m_itemView->indexAt(point: pos);
2737 const QModelIndex dirIndex = m_proxyModel->mapToSource(proxyIndex);
2738 KFileItem item = m_dirModel->itemForIndex(index: dirIndex);
2739
2740 if (item.isNull()) {
2741 return;
2742 }
2743
2744 q->activatedMenu(item, pos: QCursor::pos());
2745}
2746
2747void KDirOperatorPrivate::triggerPreview(const QModelIndex &index)
2748{
2749 if ((m_preview != nullptr && !m_preview->isHidden()) && index.isValid() && (index.column() == KDirModel::Name)) {
2750 const QModelIndex dirIndex = m_proxyModel->mapToSource(proxyIndex: index);
2751 const KFileItem item = m_dirModel->itemForIndex(index: dirIndex);
2752
2753 if (item.isNull()) {
2754 return;
2755 }
2756
2757 if (!item.isDir()) {
2758 m_previewUrl = item.url();
2759 showPreview();
2760 } else {
2761 m_preview->clearPreview();
2762 }
2763 }
2764}
2765
2766void KDirOperatorPrivate::showPreview()
2767{
2768 if (m_preview != nullptr) {
2769 m_preview->showPreview(url: m_previewUrl);
2770 }
2771}
2772
2773void KDirOperatorPrivate::slotSplitterMoved(int, int)
2774{
2775 const QList<int> sizes = m_splitter->sizes();
2776 if (sizes.count() == 2) {
2777 // remember the width of the preview widget (see KDirOperator::resizeEvent())
2778 m_previewWidth = sizes[1];
2779 }
2780}
2781
2782void KDirOperatorPrivate::assureVisibleSelection()
2783{
2784 if (m_itemView == nullptr) {
2785 return;
2786 }
2787
2788 QItemSelectionModel *selModel = m_itemView->selectionModel();
2789 if (selModel->hasSelection()) {
2790 const QModelIndex index = selModel->currentIndex();
2791 m_itemView->scrollTo(index, hint: QAbstractItemView::EnsureVisible);
2792 triggerPreview(index);
2793 }
2794}
2795
2796void KDirOperatorPrivate::synchronizeSortingState(int logicalIndex, Qt::SortOrder order)
2797{
2798 QDir::SortFlags newSort = m_sorting & ~(QDirSortMask | QDir::Reversed);
2799
2800 switch (logicalIndex) {
2801 case KDirModel::Name:
2802 newSort |= QDir::Name;
2803 break;
2804 case KDirModel::Size:
2805 newSort |= QDir::Size;
2806 break;
2807 case KDirModel::ModifiedTime:
2808 newSort |= QDir::Time;
2809 break;
2810 case KDirModel::Type:
2811 newSort |= QDir::Type;
2812 break;
2813 default:
2814 Q_ASSERT(false);
2815 }
2816
2817 if (order == Qt::DescendingOrder) {
2818 newSort |= QDir::Reversed;
2819 }
2820
2821 updateSorting(sort: newSort);
2822
2823 QMetaObject::invokeMethod(
2824 object: q,
2825 function: [this]() {
2826 assureVisibleSelection();
2827 },
2828 type: Qt::QueuedConnection);
2829}
2830
2831void KDirOperatorPrivate::slotChangeDecorationPosition()
2832{
2833 if (!m_itemView) {
2834 return;
2835 }
2836
2837 KDirOperatorIconView *view = qobject_cast<KDirOperatorIconView *>(object: m_itemView);
2838
2839 if (!view) {
2840 return;
2841 }
2842
2843 const bool leftChecked = q->action(action: KDirOperator::DecorationAtLeft)->isChecked();
2844
2845 if (leftChecked) {
2846 view->setDecorationPosition(QStyleOptionViewItem::Left);
2847 } else {
2848 view->setDecorationPosition(QStyleOptionViewItem::Top);
2849 }
2850
2851 m_itemView->update();
2852}
2853
2854void KDirOperatorPrivate::slotExpandToUrl(const QModelIndex &index)
2855{
2856 QTreeView *treeView = qobject_cast<QTreeView *>(object: m_itemView);
2857
2858 if (!treeView) {
2859 return;
2860 }
2861
2862 const KFileItem item = m_dirModel->itemForIndex(index);
2863
2864 if (item.isNull()) {
2865 return;
2866 }
2867
2868 if (!item.isDir()) {
2869 const QModelIndex proxyIndex = m_proxyModel->mapFromSource(sourceIndex: index);
2870
2871 QList<QUrl>::Iterator it = m_itemsToBeSetAsCurrent.begin();
2872 while (it != m_itemsToBeSetAsCurrent.end()) {
2873 const QUrl url = *it;
2874 if (url.matches(url: item.url(), options: QUrl::StripTrailingSlash) || url.isParentOf(url: item.url())) {
2875 const KFileItem _item = m_dirLister->findByUrl(url);
2876 if (!_item.isNull() && _item.isDir()) {
2877 const QModelIndex _index = m_dirModel->indexForItem(_item);
2878 const QModelIndex _proxyIndex = m_proxyModel->mapFromSource(sourceIndex: _index);
2879 treeView->expand(index: _proxyIndex);
2880
2881 // if we have expanded the last parent of this item, select it
2882 if (item.url().adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash) == url.adjusted(options: QUrl::StripTrailingSlash)) {
2883 treeView->selectionModel()->select(index: proxyIndex, command: QItemSelectionModel::Select);
2884 }
2885 }
2886 it = m_itemsToBeSetAsCurrent.erase(pos: it);
2887 } else {
2888 ++it;
2889 }
2890 }
2891 } else if (!m_itemsToBeSetAsCurrent.contains(t: item.url())) {
2892 m_itemsToBeSetAsCurrent << item.url();
2893 }
2894}
2895
2896void KDirOperatorPrivate::slotItemsChanged()
2897{
2898 m_completeListDirty = true;
2899}
2900
2901int KDirOperatorPrivate::iconSizeForViewType(QAbstractItemView *itemView) const
2902{
2903 // must match behavior of writeIconZoomSettingsIfNeeded
2904 if (!itemView || !m_configGroup) {
2905 return 0;
2906 }
2907
2908 ZoomSettingsForView zoomSettings = zoomSettingsForView();
2909 return m_configGroup->readEntry(key: zoomSettings.name, aDefault: zoomSettings.defaultValue);
2910}
2911
2912KDirOperatorPrivate::ZoomSettingsForView KDirOperatorPrivate::zoomSettingsForView() const
2913{
2914 KFile::FileView fv = static_cast<KFile::FileView>(m_viewKind);
2915
2916 if (KFile::isSimpleView(view: fv)) {
2917 if (m_decorationPosition == QStyleOptionViewItem::Top) {
2918 // Simple view decoration above, aka Icons View
2919 return {QStringLiteral("iconViewIconSize"), .defaultValue: static_cast<int>(KIconLoader::SizeHuge)};
2920 } else {
2921 // Simple view decoration left, aka compact view
2922 return {QStringLiteral("listViewIconSize"), .defaultValue: static_cast<int>(KIconLoader::SizeHuge)};
2923 }
2924 }
2925
2926 const int smallIconSize = static_cast<int>(KIconLoader::SizeSmall);
2927 if (KFile::isTreeView(view: fv)) {
2928 return {QStringLiteral("treeViewIconSize"), .defaultValue: smallIconSize};
2929 } else {
2930 // DetailView and DetailTreeView
2931 return {QStringLiteral("detailViewIconSize"), .defaultValue: smallIconSize};
2932 }
2933}
2934
2935void KDirOperator::setViewConfig(KConfigGroup &configGroup)
2936{
2937 delete d->m_configGroup;
2938 d->m_configGroup = new KConfigGroup(configGroup);
2939}
2940
2941KConfigGroup *KDirOperator::viewConfigGroup() const
2942{
2943 return d->m_configGroup;
2944}
2945
2946void KDirOperator::setShowHiddenFiles(bool s)
2947{
2948 action(action: KDirOperator::ShowHiddenFiles)->setChecked(s);
2949}
2950
2951bool KDirOperator::showHiddenFiles() const
2952{
2953 return action(action: KDirOperator::ShowHiddenFiles)->isChecked();
2954}
2955
2956QStyleOptionViewItem::Position KDirOperator::decorationPosition() const
2957{
2958 return d->m_decorationPosition;
2959}
2960
2961void KDirOperator::setDecorationPosition(QStyleOptionViewItem::Position position)
2962{
2963 d->m_decorationPosition = position;
2964 const bool decorationAtLeft = d->m_decorationPosition == QStyleOptionViewItem::Left;
2965 action(action: KDirOperator::DecorationAtLeft)->setChecked(decorationAtLeft);
2966 action(action: KDirOperator::DecorationAtTop)->setChecked(!decorationAtLeft);
2967}
2968
2969bool KDirOperatorPrivate::isReadable(const QUrl &url)
2970{
2971 if (!url.isLocalFile()) {
2972 return true; // what else can we say?
2973 }
2974 const QFileInfo fileInfo(url.toLocalFile());
2975#ifdef Q_OS_WIN
2976 return fileInfo.isReadable() && fileInfo.isDir();
2977#else
2978 return fileInfo.isReadable();
2979#endif
2980}
2981
2982void KDirOperatorPrivate::slotDirectoryCreated(const QUrl &url)
2983{
2984 if (m_followNewDirectories) {
2985 q->setUrl(newurl: url, clearforward: true);
2986 }
2987}
2988
2989void KDirOperator::setSupportedSchemes(const QStringList &schemes)
2990{
2991 d->m_supportedSchemes = schemes;
2992 rereadDir();
2993}
2994
2995QStringList KDirOperator::supportedSchemes() const
2996{
2997 return d->m_supportedSchemes;
2998}
2999
3000#include "moc_kdiroperator.cpp"
3001

source code of kio/src/filewidgets/kdiroperator.cpp