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

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