1// -*- c++ -*-
2/*
3 This file is part of the KDE libraries
4 SPDX-FileCopyrightText: 1997, 1998 Richard Moore <rich@kde.org>
5 SPDX-FileCopyrightText: 1998 Stephan Kulow <coolo@kde.org>
6 SPDX-FileCopyrightText: 1998 Daniel Grana <grana@ie.iwi.unibe.ch>
7 SPDX-FileCopyrightText: 1999, 2000, 2001, 2002, 2003 Carsten Pfeiffer <pfeiffer@kde.org>
8 SPDX-FileCopyrightText: 2003 Clarence Dang <dang@kde.org>
9 SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
10 SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org>
11
12 SPDX-License-Identifier: LGPL-2.0-or-later
13*/
14
15#include "kfilewidget.h"
16
17#include "../utils_p.h"
18#include "kfilebookmarkhandler_p.h"
19#include "kfileplacesmodel.h"
20#include "kfileplacesview.h"
21#include "kfilepreviewgenerator.h"
22#include "kfilewidgetdocktitlebar_p.h"
23#include "kurlcombobox.h"
24#include "kurlnavigator.h"
25#include "kurlnavigatorbuttonbase_p.h"
26
27#include <config-kiofilewidgets.h>
28
29#include <defaults-kfile.h>
30#include <kdiroperator.h>
31#include <kfilefiltercombo.h>
32#include <kfileitemdelegate.h>
33#include <kio/job.h>
34#include <kio/jobuidelegate.h>
35#include <kio/statjob.h>
36#include <kprotocolmanager.h>
37#include <krecentdirs.h>
38#include <krecentdocument.h>
39#include <kurlauthorized.h>
40#include <kurlcompletion.h>
41
42#include <KActionMenu>
43#include <KConfigGroup>
44#include <KDirLister>
45#include <KFileItem>
46#include <KFilePlacesModel>
47#include <KIconLoader>
48#include <KJobWidgets>
49#include <KLocalizedString>
50#include <KMessageBox>
51#include <KMessageWidget>
52#include <KSharedConfig>
53#include <KShell>
54#include <KStandardActions>
55#include <KToggleAction>
56
57#include <QAbstractProxyModel>
58#include <QApplication>
59#include <QCheckBox>
60#include <QDebug>
61#include <QDockWidget>
62#include <QFormLayout>
63#include <QHelpEvent>
64#include <QIcon>
65#include <QLabel>
66#include <QLayout>
67#include <QLineEdit>
68#include <QLoggingCategory>
69#include <QMenu>
70#include <QMimeDatabase>
71#include <QPushButton>
72#include <QScreen>
73#include <QSplitter>
74#include <QStandardPaths>
75#include <QTimer>
76#include <QToolBar>
77
78#include <algorithm>
79#include <array>
80#include <qnamespace.h>
81
82Q_DECLARE_LOGGING_CATEGORY(KIO_KFILEWIDGETS_FW)
83Q_LOGGING_CATEGORY(KIO_KFILEWIDGETS_FW, "kf.kio.kfilewidgets.kfilewidget", QtInfoMsg)
84
85class KFileWidgetPrivate
86{
87public:
88 explicit KFileWidgetPrivate(KFileWidget *qq)
89 : q(qq)
90 {
91 }
92
93 ~KFileWidgetPrivate()
94 {
95 delete m_bookmarkHandler; // Should be deleted before m_ops!
96 // Must be deleted before m_ops, otherwise the unit test crashes due to the
97 // connection to the QDockWidget::visibilityChanged signal, which may get
98 // emitted after this object is destroyed
99 delete m_placesDock;
100 delete m_ops;
101 }
102
103 QSize screenSize() const
104 {
105 return q->parentWidget() ? q->parentWidget()->screen()->availableGeometry().size() //
106 : QGuiApplication::primaryScreen()->availableGeometry().size();
107 }
108
109 void initDirOpWidgets();
110 void initToolbar();
111 void initZoomWidget();
112 void initLocationWidget();
113 void initFilterWidget();
114 void initQuickFilterWidget();
115 void updateLocationWhatsThis();
116 void updateAutoSelectExtension();
117 void initPlacesPanel();
118 void setPlacesViewSplitterSizes();
119 void initGUI();
120 void readViewConfig();
121 void writeViewConfig();
122 void setNonExtSelection();
123 void setLocationText(const QUrl &);
124 void setLocationText(const QList<QUrl> &);
125 void appendExtension(QUrl &url);
126 void updateLocationEditExtension(const QString &);
127 QString findMatchingFilter(const QString &filter, const QString &filename) const;
128 void updateFilter();
129 void updateFilterText();
130 /*
131 * Parses the string "line" for files. If line doesn't contain any ", the
132 * whole line will be interpreted as one file. If the number of " is odd,
133 * an empty list will be returned. Otherwise, all items enclosed in " "
134 * will be returned as correct urls.
135 */
136 QList<QUrl> tokenize(const QString &line) const;
137 /*
138 * Reads the recent used files and inserts them into the location combobox
139 */
140 void readRecentFiles();
141 /*
142 * Saves the entries from the location combobox.
143 */
144 void saveRecentFiles();
145 /*
146 * called when an item is highlighted/selected in multiselection mode.
147 * handles setting the m_locationEdit.
148 */
149 void multiSelectionChanged();
150
151 /*
152 * Returns the absolute version of the URL specified in m_locationEdit.
153 */
154 QUrl getCompleteUrl(const QString &) const;
155
156 /*
157 * Asks for overwrite confirmation using a KMessageBox and returns
158 * true if the user accepts.
159 *
160 */
161 bool toOverwrite(const QUrl &);
162
163 // private slots
164 void slotLocationChanged(const QString &);
165 void urlEntered(const QUrl &);
166 void enterUrl(const QUrl &);
167 void enterUrl(const QString &);
168 void locationAccepted(const QString &);
169 void slotFileFilterChanged();
170 void slotQuickFilterChanged();
171 void fileHighlighted(const KFileItem &, bool);
172 void fileSelected(const KFileItem &);
173 void slotLoadingFinished();
174 void togglePlacesPanel(bool show, QObject *sender = nullptr);
175 void setQuickFilterVisible(bool);
176 void slotAutoSelectExtClicked();
177 void placesViewSplitterMoved(int, int);
178 void activateUrlNavigator();
179
180 enum ZoomState {
181 ZoomOut,
182 ZoomIn,
183 };
184 void changeIconsSize(ZoomState zoom);
185 void slotDirOpIconSizeChanged(int size);
186 void slotIconSizeSliderMoved(int);
187 void slotIconSizeChanged(int);
188 void slotViewDoubleClicked(const QModelIndex &);
189 void slotViewKeyEnterReturnPressed();
190
191 void addToRecentDocuments();
192
193 QString locationEditCurrentText() const;
194 void updateNameFilter(const KFileFilter &);
195
196 /*
197 * KIO::NetAccess::mostLocalUrl local replacement.
198 * This method won't show any progress dialogs for stating, since
199 * they are very annoying when stating.
200 */
201 QUrl mostLocalUrl(const QUrl &url);
202
203 void setInlinePreviewShown(bool show);
204
205 KFileWidget *const q;
206
207 // the last selected url
208 QUrl m_url;
209
210 // now following all kind of widgets, that I need to rebuild
211 // the geometry management
212 QBoxLayout *m_boxLayout = nullptr;
213 QFormLayout *m_lafBox = nullptr;
214
215 QLabel *m_locationLabel = nullptr;
216 QWidget *m_opsWidget = nullptr;
217 QVBoxLayout *m_opsWidgetLayout = nullptr;
218
219 QLabel *m_filterLabel = nullptr;
220 KUrlNavigator *m_urlNavigator = nullptr;
221 KMessageWidget *m_messageWidget = nullptr;
222 QPushButton *m_okButton = nullptr;
223 QPushButton *m_cancelButton = nullptr;
224 QDockWidget *m_placesDock = nullptr;
225 KFilePlacesView *m_placesView = nullptr;
226 QSplitter *m_placesViewSplitter = nullptr;
227 // caches the places view width. This value will be updated when the splitter
228 // is moved. This allows us to properly set a value when the dialog itself
229 // is resized
230 int m_placesViewWidth = -1;
231
232 QWidget *m_labeledCustomWidget = nullptr;
233 QWidget *m_bottomCustomWidget = nullptr;
234
235 // Automatically Select Extension stuff
236 QCheckBox *m_autoSelectExtCheckBox = nullptr;
237 QString m_extension; // current extension for this filter
238
239 QList<QUrl> m_urlList; // the list of selected urls
240
241 KFileWidget::OperationMode m_operationMode = KFileWidget::Opening;
242
243 // The file class used for KRecentDirs
244 QString m_fileClass;
245
246 KFileBookmarkHandler *m_bookmarkHandler = nullptr;
247
248 KActionMenu *m_bookmarkButton = nullptr;
249
250 QToolBar *m_toolbar = nullptr;
251 KUrlComboBox *m_locationEdit = nullptr;
252 KDirOperator *m_ops = nullptr;
253 KFileFilterCombo *m_filterWidget = nullptr;
254 QTimer m_filterDelayTimer;
255
256 QWidget *m_quickFilter = nullptr;
257 QLineEdit *m_quickFilterEdit = nullptr;
258 QToolButton *m_quickFilterLock = nullptr;
259 QToolButton *m_quickFilterClose = nullptr;
260
261 KFilePlacesModel *m_model = nullptr;
262
263 // whether or not the _user_ has checked the above box
264 bool m_autoSelectExtChecked = false;
265
266 // indicates if the location edit should be kept or cleared when changing
267 // directories
268 bool m_keepLocation = false;
269
270 // the KDirOperators view is set in KFileWidget::show(), so to avoid
271 // setting it again and again, we have this nice little boolean :)
272 bool m_hasView = false;
273
274 bool m_hasDefaultFilter = false; // necessary for the m_operationMode
275 bool m_inAccept = false; // true between beginning and end of accept()
276 bool m_confirmOverwrite = false;
277 bool m_differentHierarchyLevelItemsEntered = false;
278
279 const std::array<short, 8> m_stdIconSizes = {
280 KIconLoader::SizeSmall,
281 KIconLoader::SizeSmallMedium,
282 KIconLoader::SizeMedium,
283 KIconLoader::SizeLarge,
284 KIconLoader::SizeHuge,
285 KIconLoader::SizeEnormous,
286 256,
287 512,
288 };
289
290 QSlider *m_iconSizeSlider = nullptr;
291 QAction *m_zoomOutAction = nullptr;
292 QAction *m_zoomInAction = nullptr;
293
294 // The group which stores app-specific settings. These settings are recent
295 // files and urls. Visual settings (view mode, sorting criteria...) are not
296 // app-specific and are stored in kdeglobals
297 KConfigGroup m_configGroup;
298 KConfigGroup m_stateConfigGroup;
299
300 KToggleAction *m_togglePlacesPanelAction = nullptr;
301 KToggleAction *m_toggleQuickFilterAction = nullptr;
302};
303
304Q_GLOBAL_STATIC(QUrl, lastDirectory) // to set the start path
305
306// returns true if the string contains "<a>:/" sequence, where <a> is at least 2 alpha chars
307static bool containsProtocolSection(const QString &string)
308{
309 int len = string.length();
310 static const char prot[] = ":/";
311 for (int i = 0; i < len;) {
312 i = string.indexOf(s: QLatin1String(prot), from: i);
313 if (i == -1) {
314 return false;
315 }
316 int j = i - 1;
317 for (; j >= 0; j--) {
318 const QChar &ch(string[j]);
319 if (ch.toLatin1() == 0 || !ch.isLetter()) {
320 break;
321 }
322 if (ch.isSpace() && (i - j - 1) >= 2) {
323 return true;
324 }
325 }
326 if (j < 0 && i >= 2) {
327 return true; // at least two letters before ":/"
328 }
329 i += 3; // skip : and / and one char
330 }
331 return false;
332}
333
334// this string-to-url conversion function handles relative paths, full paths and URLs
335// without the http-prepending that QUrl::fromUserInput does.
336static QUrl urlFromString(const QString &str)
337{
338 if (Utils::isAbsoluteLocalPath(path: str)) {
339 return QUrl::fromLocalFile(localfile: str);
340 }
341 QUrl url(str);
342 if (url.isRelative()) {
343 url.clear();
344 url.setPath(path: str);
345 }
346 return url;
347}
348
349KFileWidget::KFileWidget(const QUrl &_startDir, QWidget *parent)
350 : QWidget(parent)
351 , d(new KFileWidgetPrivate(this))
352{
353 QUrl startDir(_startDir);
354 // qDebug() << "startDir" << startDir;
355 QString filename;
356
357 d->m_okButton = new QPushButton(this);
358 KGuiItem::assign(button: d->m_okButton, item: KStandardGuiItem::ok());
359 d->m_okButton->setDefault(true);
360 d->m_cancelButton = new QPushButton(this);
361 KGuiItem::assign(button: d->m_cancelButton, item: KStandardGuiItem::cancel());
362 // The dialog shows them
363 d->m_okButton->hide();
364 d->m_cancelButton->hide();
365
366 d->initDirOpWidgets();
367
368 // Resolve this now so that a 'kfiledialog:' URL, if specified,
369 // does not get inserted into the urlNavigator history.
370 d->m_url = getStartUrl(startDir, recentDirClass&: d->m_fileClass, fileName&: filename);
371 startDir = d->m_url;
372
373 const auto operatorActions = d->m_ops->allActions();
374 for (QAction *action : operatorActions) {
375 addAction(action);
376 }
377
378 QAction *goToNavigatorAction = new QAction(this);
379
380 connect(sender: goToNavigatorAction, signal: &QAction::triggered, context: this, slot: [this]() {
381 d->activateUrlNavigator();
382 });
383
384 goToNavigatorAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L));
385
386 addAction(action: goToNavigatorAction);
387
388 KUrlComboBox *pathCombo = d->m_urlNavigator->editor();
389 KUrlCompletion *pathCompletionObj = new KUrlCompletion(KUrlCompletion::DirCompletion);
390 pathCombo->setCompletionObject(compObj: pathCompletionObj);
391 pathCombo->setAutoDeleteCompletionObject(true);
392
393 connect(sender: d->m_urlNavigator, signal: &KUrlNavigator::urlChanged, context: this, slot: [this](const QUrl &url) {
394 d->enterUrl(url);
395 });
396 connect(sender: d->m_urlNavigator, signal: &KUrlNavigator::returnPressed, context: d->m_ops, slot: qOverload<>(&QWidget::setFocus));
397
398 // Location, "Name:", line-edit and label
399 d->initLocationWidget();
400
401 // "Filter:" line-edit and label
402 d->initFilterWidget();
403
404 d->initQuickFilterWidget();
405 // the Automatically Select Extension checkbox
406 // (the text, visibility etc. is set in updateAutoSelectExtension(), which is called by readConfig())
407 d->m_autoSelectExtCheckBox = new QCheckBox(this);
408 connect(sender: d->m_autoSelectExtCheckBox, signal: &QCheckBox::clicked, context: this, slot: [this]() {
409 d->slotAutoSelectExtClicked();
410 });
411
412 d->initGUI(); // activate GM
413
414 // read our configuration
415 KSharedConfig::Ptr config = KSharedConfig::openConfig();
416 config->reparseConfiguration(); // grab newly added dirs by other processes (#403524)
417 d->m_configGroup = KConfigGroup(config, ConfigGroup);
418
419 d->m_stateConfigGroup = KSharedConfig::openStateConfig()->group(group: ConfigGroup);
420
421 // migrate existing recent files/urls from main config to state config
422 if (d->m_configGroup.hasKey(key: RecentURLs)) {
423 d->m_stateConfigGroup.writeEntry(key: RecentURLs, value: d->m_configGroup.readEntry(key: RecentURLs));
424 d->m_configGroup.revertToDefault(key: RecentURLs);
425 }
426
427 if (d->m_configGroup.hasKey(key: RecentFiles)) {
428 d->m_stateConfigGroup.writeEntry(key: RecentFiles, value: d->m_configGroup.readEntry(key: RecentFiles));
429 d->m_configGroup.revertToDefault(key: RecentFiles);
430 }
431
432 d->readViewConfig();
433 d->readRecentFiles();
434
435 d->m_ops->action(action: KDirOperator::ShowPreview)->setChecked(d->m_ops->isInlinePreviewShown());
436 d->slotDirOpIconSizeChanged(size: d->m_ops->iconSize());
437
438 // getStartUrl() above will have resolved the startDir parameter into
439 // a directory and file name in the two cases: (a) where it is a
440 // special "kfiledialog:" URL, or (b) where it is a plain file name
441 // only without directory or protocol. For any other startDir
442 // specified, it is not possible to resolve whether there is a file name
443 // present just by looking at the URL; the only way to be sure is
444 // to stat it.
445 bool statRes = false;
446 if (filename.isEmpty()) {
447 KIO::StatJob *statJob = KIO::stat(url: startDir, flags: KIO::HideProgressInfo);
448 KJobWidgets::setWindow(job: statJob, widget: this);
449 statRes = statJob->exec();
450 // qDebug() << "stat of" << startDir << "-> statRes" << statRes << "isDir" << statJob->statResult().isDir();
451 if (!statRes || !statJob->statResult().isDir()) {
452 filename = startDir.fileName();
453 startDir = startDir.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash);
454 // qDebug() << "statJob -> startDir" << startDir << "filename" << filename;
455 }
456 }
457
458 d->m_ops->setUrl(url: startDir, clearforward: true);
459 d->m_urlNavigator->setLocationUrl(startDir);
460 if (d->m_placesView) {
461 d->m_placesView->setUrl(startDir);
462 }
463
464 // We have a file name either explicitly specified, or have checked that
465 // we could stat it and it is not a directory. Set it.
466 if (!filename.isEmpty()) {
467 QLineEdit *lineEdit = d->m_locationEdit->lineEdit();
468 // qDebug() << "selecting filename" << filename;
469 if (statRes) {
470 d->setLocationText(QUrl(filename));
471 } else {
472 lineEdit->setText(filename);
473 // Preserve this filename when clicking on the view (cf fileHighlighted)
474 lineEdit->setModified(true);
475 }
476 lineEdit->selectAll();
477 }
478
479 d->m_locationEdit->setFocus();
480
481 const QAction *showHiddenAction = d->m_ops->action(action: KDirOperator::ShowHiddenFiles);
482 Q_ASSERT(showHiddenAction);
483 d->m_urlNavigator->setShowHiddenFolders(showHiddenAction->isChecked());
484 connect(sender: showHiddenAction, signal: &QAction::toggled, context: this, slot: [this](bool checked) {
485 d->m_urlNavigator->setShowHiddenFolders(checked);
486 });
487
488 const QAction *hiddenFilesLastAction = d->m_ops->action(action: KDirOperator::SortHiddenFilesLast);
489 Q_ASSERT(hiddenFilesLastAction);
490 d->m_urlNavigator->setSortHiddenFoldersLast(hiddenFilesLastAction->isChecked());
491 connect(sender: hiddenFilesLastAction, signal: &QAction::toggled, context: this, slot: [this](bool checked) {
492 d->m_urlNavigator->setSortHiddenFoldersLast(checked);
493 });
494}
495
496KFileWidget::~KFileWidget()
497{
498 KSharedConfig::Ptr config = KSharedConfig::openConfig();
499 config->sync();
500 d->m_ops->removeEventFilter(obj: this);
501 d->m_locationEdit->removeEventFilter(obj: this);
502}
503
504void KFileWidget::setLocationLabel(const QString &text)
505{
506 d->m_locationLabel->setText(text);
507}
508
509void KFileWidget::setFilters(const QList<KFileFilter> &filters, const KFileFilter &activeFilter)
510{
511 d->m_ops->clearFilter();
512 d->m_filterWidget->setFilters(filters, defaultFilter: activeFilter);
513 d->m_ops->updateDir();
514 d->m_hasDefaultFilter = false;
515 d->m_filterWidget->setEditable(true);
516 d->updateFilterText();
517
518 d->updateAutoSelectExtension();
519}
520
521KFileFilter KFileWidget::currentFilter() const
522{
523 return d->m_filterWidget->currentFilter();
524}
525
526void KFileWidget::clearFilter()
527{
528 d->m_filterWidget->setFilters(filters: {}, defaultFilter: KFileFilter());
529 d->m_ops->clearFilter();
530 d->m_hasDefaultFilter = false;
531 d->m_filterWidget->setEditable(true);
532
533 d->updateAutoSelectExtension();
534}
535
536void KFileWidget::setPreviewWidget(KPreviewWidgetBase *w)
537{
538 d->m_ops->setPreviewWidget(w);
539 d->m_ops->clearHistory();
540 d->m_hasView = true;
541}
542
543QUrl KFileWidgetPrivate::getCompleteUrl(const QString &_url) const
544{
545 // qDebug() << "got url " << _url;
546
547 const QString url = KShell::tildeExpand(path: _url);
548 QUrl u;
549
550 if (Utils::isAbsoluteLocalPath(path: url)) {
551 u = QUrl::fromLocalFile(localfile: url);
552 } else {
553 QUrl relativeUrlTest(m_ops->url());
554 relativeUrlTest.setPath(path: Utils::concatPaths(path1: relativeUrlTest.path(), path2: url));
555 if (!m_ops->dirLister()->findByUrl(url: relativeUrlTest).isNull() || !KProtocolInfo::isKnownProtocol(url: relativeUrlTest)) {
556 u = relativeUrlTest;
557 } else {
558 // Try to preserve URLs if they have a scheme (for example,
559 // "https://example.com/foo.txt") and otherwise resolve relative
560 // paths to absolute ones (e.g. "foo.txt" -> "file:///tmp/foo.txt").
561 u = QUrl(url);
562 if (u.isRelative()) {
563 u = relativeUrlTest;
564 }
565 }
566 }
567
568 return u;
569}
570
571QSize KFileWidget::sizeHint() const
572{
573 int fontSize = fontMetrics().height();
574 const QSize goodSize(48 * fontSize, 30 * fontSize);
575 const QSize scrnSize = d->screenSize();
576 const QSize minSize(scrnSize / 2);
577 const QSize maxSize(scrnSize * qreal(0.9));
578 return (goodSize.expandedTo(otherSize: minSize).boundedTo(otherSize: maxSize));
579}
580
581static QString relativePathOrUrl(const QUrl &baseUrl, const QUrl &url);
582
583/*
584 * Escape the given Url so that is fit for use in the selected list of file. This
585 * mainly handles double quote (") characters. These are used to separate entries
586 * in the list, however, if `"` appears in the filename (or path), this will be
587 * escaped as `\"`. Later, the tokenizer is able to understand the difference
588 * and do the right thing
589 */
590static QString escapeDoubleQuotes(QString &&path);
591
592// Called by KFileDialog
593void KFileWidget::slotOk()
594{
595 // qDebug() << "slotOk\n";
596
597 const QString locationEditCurrentText(KShell::tildeExpand(path: d->locationEditCurrentText()));
598
599 QList<QUrl> locationEditCurrentTextList(d->tokenize(line: locationEditCurrentText));
600 KFile::Modes mode = d->m_ops->mode();
601
602 // Make sure that one of the modes was provided
603 if (!((mode & KFile::File) || (mode & KFile::Directory) || (mode & KFile::Files))) {
604 mode |= KFile::File;
605 // qDebug() << "No mode() provided";
606 }
607
608 const bool directoryMode = (mode & KFile::Directory);
609 const bool onlyDirectoryMode = directoryMode && !(mode & KFile::File) && !(mode & KFile::Files);
610
611 // Clear the list as we are going to refill it
612 d->m_urlList.clear();
613
614 // In directory mode, treat an empty selection as selecting the current dir.
615 // In file mode, there's nothing to do.
616 if (locationEditCurrentTextList.isEmpty() && !onlyDirectoryMode) {
617 return;
618 }
619
620 // if we are on file mode, and the list of provided files/folder is greater than one, inform
621 // the user about it
622 if (locationEditCurrentTextList.count() > 1) {
623 if (mode & KFile::File) {
624 KMessageBox::error(parent: this, i18n("You can only select one file"), i18n("More than one file provided"));
625 return;
626 }
627
628 /*
629 * Logic of the next part of code (ends at "end multi relative urls").
630 *
631 * We allow for instance to be at "/" and insert '"home/foo/bar.txt" "boot/grub/menu.lst"'.
632 * Why we need to support this ? Because we provide tree views, which aren't plain.
633 *
634 * Now, how does this logic work. It will get the first element on the list (with no filename),
635 * following the previous example say "/home/foo" and set it as the top most url.
636 *
637 * After this, it will iterate over the rest of items and check if this URL (topmost url)
638 * contains the url being iterated.
639 *
640 * As you might have guessed it will do "/home/foo" against "/boot/grub" (again stripping
641 * filename), and a false will be returned. Then we upUrl the top most url, resulting in
642 * "/home" against "/boot/grub", what will again return false, so we upUrl again. Now we
643 * have "/" against "/boot/grub", what returns true for us, so we can say that the closest
644 * common ancestor of both is "/".
645 *
646 * This example has been written for 2 urls, but this works for any number of urls.
647 */
648 if (!d->m_differentHierarchyLevelItemsEntered) { // avoid infinite recursion. running this
649 int start = 0;
650 QUrl topMostUrl;
651 KIO::StatJob *statJob = nullptr;
652 bool res = false;
653
654 // we need to check for a valid first url, so in theory we only iterate one time over
655 // this loop. However it can happen that the user did
656 // "home/foo/nonexistantfile" "boot/grub/menu.lst", so we look for a good first
657 // candidate.
658 while (!res && start < locationEditCurrentTextList.count()) {
659 topMostUrl = locationEditCurrentTextList.at(i: start);
660 statJob = KIO::stat(url: topMostUrl, flags: KIO::HideProgressInfo);
661 KJobWidgets::setWindow(job: statJob, widget: this);
662 res = statJob->exec();
663 start++;
664 }
665
666 Q_ASSERT(statJob);
667
668 // if this is not a dir, strip the filename. after this we have an existent and valid
669 // dir (we stated correctly the file).
670 if (!statJob->statResult().isDir()) {
671 topMostUrl = topMostUrl.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash);
672 }
673
674 // now the funny part. for the rest of filenames, go and look for the closest ancestor
675 // of all them.
676 for (int i = start; i < locationEditCurrentTextList.count(); ++i) {
677 QUrl currUrl = locationEditCurrentTextList.at(i);
678 KIO::StatJob *statJob = KIO::stat(url: currUrl, flags: KIO::HideProgressInfo);
679 KJobWidgets::setWindow(job: statJob, widget: this);
680 int res = statJob->exec();
681 if (res) {
682 // again, we don't care about filenames
683 if (!statJob->statResult().isDir()) {
684 currUrl = currUrl.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash);
685 }
686
687 // iterate while this item is contained on the top most url
688 while (!topMostUrl.matches(url: currUrl, options: QUrl::StripTrailingSlash) && !topMostUrl.isParentOf(url: currUrl)) {
689 topMostUrl = KIO::upUrl(url: topMostUrl);
690 }
691 }
692 }
693
694 // now recalculate all paths for them being relative in base of the top most url
695 QStringList stringList;
696 stringList.reserve(asize: locationEditCurrentTextList.count());
697 for (int i = 0; i < locationEditCurrentTextList.count(); ++i) {
698 Q_ASSERT(topMostUrl.isParentOf(locationEditCurrentTextList[i]));
699 QString relativePath = relativePathOrUrl(baseUrl: topMostUrl, url: locationEditCurrentTextList[i]);
700 stringList << escapeDoubleQuotes(path: std::move(relativePath));
701 }
702
703 d->m_ops->setUrl(url: topMostUrl, clearforward: true);
704 const bool signalsBlocked = d->m_locationEdit->lineEdit()->blockSignals(b: true);
705 d->m_locationEdit->lineEdit()->setText(QStringLiteral("\"%1\"").arg(a: stringList.join(QStringLiteral("\" \""))));
706 d->m_locationEdit->lineEdit()->blockSignals(b: signalsBlocked);
707
708 d->m_differentHierarchyLevelItemsEntered = true;
709 slotOk();
710 return;
711 }
712 /*
713 * end multi relative urls
714 */
715 } else if (!locationEditCurrentTextList.isEmpty()) {
716 // if we are on file or files mode, and we have an absolute url written by
717 // the user:
718 // * convert it to relative and call slotOk again if the protocol supports listing.
719 // * use the full url if the protocol doesn't support listing
720 // This is because when using a protocol that supports listing we want to show the directory
721 // the user just opened/saved from the next time they open the dialog, it makes sense usability wise.
722 // If the protocol doesn't support listing (i.e. http:// ) the user would end up with the dialog
723 // showing an "empty directory" which is bad usability wise.
724 if (!locationEditCurrentText.isEmpty() && !onlyDirectoryMode
725 && (Utils::isAbsoluteLocalPath(path: locationEditCurrentText) || containsProtocolSection(string: locationEditCurrentText))) {
726 QUrl url = urlFromString(str: locationEditCurrentText);
727 if (KProtocolManager::supportsListing(url)) {
728 QString fileName;
729 if (d->m_operationMode == Opening) {
730 KIO::StatJob *statJob = KIO::stat(url, flags: KIO::HideProgressInfo);
731 KJobWidgets::setWindow(job: statJob, widget: this);
732 int res = statJob->exec();
733 if (res) {
734 if (!statJob->statResult().isDir()) {
735 fileName = url.fileName();
736 url = url.adjusted(options: QUrl::RemoveFilename); // keeps trailing slash
737 } else {
738 Utils::appendSlashToPath(url);
739 }
740 }
741 } else {
742 const QUrl directory = url.adjusted(options: QUrl::RemoveFilename);
743 // Check if the folder exists
744 KIO::StatJob *statJob = KIO::stat(url: directory, flags: KIO::HideProgressInfo);
745 KJobWidgets::setWindow(job: statJob, widget: this);
746 int res = statJob->exec();
747 if (res) {
748 if (statJob->statResult().isDir()) {
749 url = url.adjusted(options: QUrl::StripTrailingSlash);
750 fileName = url.fileName();
751 url = url.adjusted(options: QUrl::RemoveFilename);
752 }
753 }
754 }
755 d->m_ops->setUrl(url, clearforward: true);
756 const bool signalsBlocked = d->m_locationEdit->lineEdit()->blockSignals(b: true);
757 d->m_locationEdit->lineEdit()->setText(fileName);
758 d->m_locationEdit->lineEdit()->blockSignals(b: signalsBlocked);
759 slotOk();
760 return;
761 } else {
762 locationEditCurrentTextList = {url};
763 }
764 }
765 }
766
767 // restore it
768 d->m_differentHierarchyLevelItemsEntered = false;
769
770 // locationEditCurrentTextList contains absolute paths
771 // this is the general loop for the File and Files mode. Obviously we know
772 // that the File mode will iterate only one time here
773 QList<QUrl>::ConstIterator it = locationEditCurrentTextList.constBegin();
774 while (it != locationEditCurrentTextList.constEnd()) {
775 QUrl url(*it);
776
777 if (d->m_operationMode == Saving && !directoryMode) {
778 d->appendExtension(url);
779 }
780
781 d->m_url = url;
782 KIO::StatJob *statJob = KIO::stat(url, flags: KIO::HideProgressInfo);
783 KJobWidgets::setWindow(job: statJob, widget: this);
784 int res = statJob->exec();
785
786 if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("open"), baseUrl: QUrl(), destUrl: url)) {
787 QString msg = KIO::buildErrorString(errorCode: KIO::ERR_ACCESS_DENIED, errorText: d->m_url.toDisplayString());
788 KMessageBox::error(parent: this, text: msg);
789 return;
790 }
791
792 // if we are on local mode, make sure we haven't got a remote base url
793 if ((mode & KFile::LocalOnly) && !d->mostLocalUrl(url: d->m_url).isLocalFile()) {
794 KMessageBox::error(parent: this, i18n("You can only select local files"), i18n("Remote files not accepted"));
795 return;
796 }
797
798 const auto &supportedSchemes = d->m_model->supportedSchemes();
799 if (!supportedSchemes.isEmpty() && !supportedSchemes.contains(str: d->m_url.scheme())) {
800 KMessageBox::error(parent: this,
801 i18np("The selected URL uses an unsupported scheme. "
802 "Please use the following scheme: %2",
803 "The selected URL uses an unsupported scheme. "
804 "Please use one of the following schemes: %2",
805 supportedSchemes.size(),
806 supportedSchemes.join(QLatin1String(", "))),
807 i18n("Unsupported URL scheme"));
808 return;
809 }
810
811 // if user has typed folder name manually, open it
812 if (res && !directoryMode && statJob->statResult().isDir()) {
813 d->m_ops->setUrl(url, clearforward: true);
814 const bool signalsBlocked = d->m_locationEdit->lineEdit()->blockSignals(b: true);
815 d->m_locationEdit->lineEdit()->setText(QString());
816 d->m_locationEdit->lineEdit()->blockSignals(b: signalsBlocked);
817 return;
818 } else if (res && onlyDirectoryMode && !statJob->statResult().isDir()) {
819 // if we are given a file when on directory only mode, reject it
820 return;
821 } else if (!(mode & KFile::ExistingOnly) || res) {
822 // if we don't care about ExistingOnly flag, add the file even if
823 // it doesn't exist. If we care about it, don't add it to the list
824 if (!onlyDirectoryMode || (res && statJob->statResult().isDir())) {
825 d->m_urlList << url;
826 }
827 } else {
828 KMessageBox::error(parent: this, i18n("The file \"%1\" could not be found", url.toDisplayString(QUrl::PreferLocalFile)), i18n("Cannot open file"));
829 return; // do not emit accepted() if we had ExistingOnly flag and stat failed
830 }
831
832 if ((d->m_operationMode == Saving) && d->m_confirmOverwrite && !d->toOverwrite(url)) {
833 return;
834 }
835
836 ++it;
837 }
838
839 // if we have reached this point and we didn't return before, that is because
840 // we want this dialog to be accepted
841 Q_EMIT accepted();
842}
843
844void KFileWidget::accept()
845{
846 d->m_inAccept = true;
847
848 *lastDirectory() = d->m_ops->url();
849 if (!d->m_fileClass.isEmpty()) {
850 KRecentDirs::add(fileClass: d->m_fileClass, directory: d->m_ops->url().toString());
851 }
852
853 // clear the topmost item, we insert it as full path later on as item 1
854 d->m_locationEdit->setItemText(index: 0, text: QString());
855
856 const QList<QUrl> list = selectedUrls();
857 int atmost = d->m_locationEdit->maxItems(); // don't add more items than necessary
858 for (const auto &url : list) {
859 if (atmost-- == 0) {
860 break;
861 }
862
863 // we strip the last slash (-1) because KUrlComboBox does that as well
864 // when operating in file-mode. If we wouldn't , dupe-finding wouldn't
865 // work.
866 const QString file = url.toDisplayString(options: QUrl::StripTrailingSlash | QUrl::PreferLocalFile);
867
868 // remove dupes
869 for (int i = 1; i < d->m_locationEdit->count(); ++i) {
870 if (d->m_locationEdit->itemText(index: i) == file) {
871 d->m_locationEdit->removeItem(index: i--);
872 break;
873 }
874 }
875 // FIXME I don't think this works correctly when the KUrlComboBox has some default urls.
876 // KUrlComboBox should provide a function to add an url and rotate the existing ones, keeping
877 // track of maxItems, and we shouldn't be able to insert items as we please.
878 d->m_locationEdit->insertItem(aindex: 1, atext: file);
879 }
880
881 d->writeViewConfig();
882 d->saveRecentFiles();
883
884 d->addToRecentDocuments();
885
886 if (!(mode() & KFile::Files)) { // single selection
887 Q_EMIT fileSelected(d->m_url);
888 }
889
890 d->m_ops->close();
891}
892
893void KFileWidgetPrivate::fileHighlighted(const KFileItem &i, bool isKeyNavigation)
894{
895 if ((m_locationEdit->hasFocus() && !m_locationEdit->currentText().isEmpty())) { // don't disturb
896 return;
897 }
898
899 if (!i.isNull() && i.isDir() && !(m_ops->mode() & KFile::Directory)) {
900 return;
901 }
902
903 const bool modified = m_locationEdit->lineEdit()->isModified();
904
905 if (!(m_ops->mode() & KFile::Files)) {
906 if (i.isNull()) {
907 if (!modified) {
908 setLocationText(QUrl());
909 }
910 return;
911 }
912
913 m_url = i.url();
914
915 if (!m_locationEdit->hasFocus()) { // don't disturb while editing
916 setLocationText(m_url);
917 }
918
919 Q_EMIT q->fileHighlighted(m_url);
920 } else {
921 multiSelectionChanged();
922 Q_EMIT q->selectionChanged();
923 }
924
925 m_locationEdit->lineEdit()->setModified(false);
926
927 // When saving, and when double-click mode is being used, highlight the
928 // filename after a file is single-clicked so the user has a chance to quickly
929 // rename it if desired
930 // Note that double-clicking will override this and overwrite regardless of
931 // single/double click mouse setting (see slotViewDoubleClicked() )
932 if (!isKeyNavigation && m_operationMode == KFileWidget::Saving) {
933 m_locationEdit->setFocus();
934 }
935}
936
937void KFileWidgetPrivate::fileSelected(const KFileItem &i)
938{
939 if (!i.isNull() && i.isDir()) {
940 return;
941 }
942
943 if (!(m_ops->mode() & KFile::Files)) {
944 if (i.isNull()) {
945 setLocationText(QUrl());
946 return;
947 }
948 setLocationText(i.targetUrl());
949 } else {
950 multiSelectionChanged();
951 Q_EMIT q->selectionChanged();
952 }
953
954 // Same as above in fileHighlighted(), but for single-click mode
955 if (m_operationMode == KFileWidget::Saving) {
956 m_locationEdit->setFocus();
957 } else {
958 q->slotOk();
959 }
960}
961
962// I know it's slow to always iterate thru the whole filelist
963// (d->m_ops->selectedItems()), but what can we do?
964void KFileWidgetPrivate::multiSelectionChanged()
965{
966 if (m_locationEdit->hasFocus() && !m_locationEdit->currentText().isEmpty()) { // don't disturb
967 return;
968 }
969
970 const KFileItemList list = m_ops->selectedItems();
971
972 if (list.isEmpty()) {
973 setLocationText(QUrl());
974 return;
975 }
976
977 // Allow single folder selection, so user can click "Open" to open it
978 if (list.length() == 1 && list.first().isDir()) {
979 setLocationText(list.first().targetUrl());
980 return;
981 }
982 // Remove any selected folders from the locations
983 QList<QUrl> urlList;
984 for (const auto &item : list) {
985 if (!item.isDir()) {
986 urlList.append(t: item.targetUrl());
987 }
988 }
989 setLocationText(urlList);
990}
991
992void KFileWidgetPrivate::setLocationText(const QUrl &url)
993{
994 // fileHighlighed and fileSelected will be called one after the other:
995 // avoid to set two times in a row the location text with the same name
996 // as this would insert spurious entries in the undo stack
997 if ((url.isEmpty() && m_locationEdit->lineEdit()->text().isEmpty()) || m_locationEdit->lineEdit()->text() == escapeDoubleQuotes(path: url.fileName())) {
998 return;
999 }
1000 // Block m_locationEdit signals as setCurrentItem() will cause textChanged() to get
1001 // emitted, so slotLocationChanged() will be called. Make sure we don't clear the
1002 // KDirOperator's view-selection in there
1003 const QSignalBlocker blocker(m_locationEdit);
1004
1005 if (!url.isEmpty()) {
1006 if (!url.isRelative()) {
1007 const QUrl directory = url.adjusted(options: QUrl::RemoveFilename);
1008 if (!directory.path().isEmpty()) {
1009 q->setUrl(url: directory, clearforward: false);
1010 } else {
1011 q->setUrl(url, clearforward: false);
1012 }
1013 }
1014 m_locationEdit->lineEdit()->selectAll();
1015 m_locationEdit->lineEdit()->insert(escapeDoubleQuotes(path: url.fileName()));
1016 } else if (!m_locationEdit->lineEdit()->text().isEmpty()) {
1017 m_locationEdit->clearEditText();
1018 }
1019
1020 if (m_operationMode == KFileWidget::Saving) {
1021 setNonExtSelection();
1022 }
1023}
1024
1025static QString relativePathOrUrl(const QUrl &baseUrl, const QUrl &url)
1026{
1027 if (baseUrl.isParentOf(url)) {
1028 const QString basePath(QDir::cleanPath(path: baseUrl.path()));
1029 QString relPath(QDir::cleanPath(path: url.path()));
1030 relPath.remove(i: 0, len: basePath.length());
1031 if (relPath.startsWith(c: QLatin1Char('/'))) {
1032 relPath.remove(i: 0, len: 1);
1033 }
1034 return relPath;
1035 } else {
1036 return url.toDisplayString();
1037 }
1038}
1039
1040static QString escapeDoubleQuotes(QString &&path)
1041{
1042 // First escape the escape character that we are using
1043 path.replace(QStringLiteral("\\"), QStringLiteral("\\\\"));
1044 // Second, escape the quotes
1045 path.replace(QStringLiteral("\""), QStringLiteral("\\\""));
1046 return path;
1047}
1048
1049void KFileWidgetPrivate::initDirOpWidgets()
1050{
1051 m_opsWidget = new QWidget(q);
1052 m_opsWidgetLayout = new QVBoxLayout(m_opsWidget);
1053 m_opsWidgetLayout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
1054 m_opsWidgetLayout->setSpacing(0);
1055
1056 m_model = new KFilePlacesModel(q);
1057
1058 // Don't pass "startDir" (KFileWidget constructor 1st arg) to the
1059 // KUrlNavigator at this stage: it may also contain a file name which
1060 // should not get inserted in that form into the old-style navigation
1061 // bar history. Wait until the KIO::stat has been done later.
1062 //
1063 // The stat cannot be done before this point, bug 172678.
1064 m_urlNavigator = new KUrlNavigator(m_model, QUrl(), m_opsWidget); // d->m_toolbar);
1065 m_urlNavigator->setPlacesSelectorVisible(false);
1066
1067 // Add the urlNavigator inside a widget to give it proper padding
1068 const auto navWidget = new QWidget(m_opsWidget);
1069 const auto navLayout = new QHBoxLayout(navWidget);
1070 navLayout->addWidget(m_urlNavigator);
1071 navLayout->setContentsMargins(left: q->style()->pixelMetric(metric: QStyle::PM_LayoutLeftMargin),
1072 top: 0,
1073 right: q->style()->pixelMetric(metric: QStyle::PM_LayoutRightMargin),
1074 bottom: q->style()->pixelMetric(metric: QStyle::PM_LayoutBottomMargin));
1075
1076 m_messageWidget = new KMessageWidget(q);
1077 m_messageWidget->setMessageType(KMessageWidget::Error);
1078 m_messageWidget->setWordWrap(true);
1079 m_messageWidget->hide();
1080
1081 auto topSeparator = new QFrame(q);
1082 topSeparator->setFrameStyle(QFrame::HLine);
1083
1084 m_ops = new KDirOperator(QUrl(), m_opsWidget);
1085 m_ops->installEventFilter(filterObj: q);
1086 m_ops->setObjectName(QStringLiteral("KFileWidget::ops"));
1087 m_ops->setIsSaving(m_operationMode == KFileWidget::Saving);
1088 m_ops->setNewFileMenuSelectDirWhenAlreadyExist(true);
1089 m_ops->showOpenWithActions(enable: true);
1090 m_ops->setSizePolicy(hor: QSizePolicy::Expanding, ver: QSizePolicy::Expanding);
1091
1092 auto bottomSparator = new QFrame(q);
1093 bottomSparator->setFrameStyle(QFrame::HLine);
1094
1095 q->connect(sender: m_ops, signal: &KDirOperator::urlEntered, context: q, slot: [this](const QUrl &url) {
1096 urlEntered(url);
1097 });
1098 q->connect(sender: m_ops, signal: &KDirOperator::fileHighlighted, context: q, slot: [this](const KFileItem &item) {
1099 fileHighlighted(i: item, isKeyNavigation: m_ops->usingKeyNavigation());
1100 });
1101 q->connect(sender: m_ops, signal: &KDirOperator::fileSelected, context: q, slot: [this](const KFileItem &item) {
1102 fileSelected(i: item);
1103 });
1104 q->connect(sender: m_ops, signal: &KDirOperator::finishedLoading, context: q, slot: [this]() {
1105 slotLoadingFinished();
1106 });
1107 q->connect(sender: m_ops, signal: &KDirOperator::keyEnterReturnPressed, context: q, slot: [this]() {
1108 slotViewKeyEnterReturnPressed();
1109 });
1110 q->connect(sender: m_ops, signal: &KDirOperator::renamingFinished, context: q, slot: [this](const QList<QUrl> &urls) {
1111 // Update file names in location text field after renaming selected files
1112 q->setSelectedUrls(urls);
1113 });
1114
1115 q->connect(sender: m_ops, signal: &KDirOperator::viewChanged, context: q, slot: [](QAbstractItemView *newView) {
1116 newView->setProperty(name: "_breeze_borders_sides", value: QVariant::fromValue(value: QFlags{Qt::TopEdge | Qt::BottomEdge}));
1117 });
1118
1119 m_ops->dirLister()->setAutoErrorHandlingEnabled(false);
1120 q->connect(sender: m_ops->dirLister(), signal: &KDirLister::jobError, context: q, slot: [this](KIO::Job *job) {
1121 m_messageWidget->setText(job->errorString());
1122 m_messageWidget->animatedShow();
1123 });
1124
1125 m_ops->setupMenu(KDirOperator::SortActions | KDirOperator::FileActions | KDirOperator::ViewActions);
1126
1127 initToolbar();
1128
1129 m_opsWidgetLayout->addWidget(m_toolbar);
1130 m_opsWidgetLayout->addWidget(navWidget);
1131 m_opsWidgetLayout->addWidget(m_messageWidget);
1132 m_opsWidgetLayout->addWidget(topSeparator);
1133 m_opsWidgetLayout->addWidget(m_ops);
1134 m_opsWidgetLayout->addWidget(bottomSparator);
1135}
1136
1137void KFileWidgetPrivate::initZoomWidget()
1138{
1139 m_iconSizeSlider = new QSlider(q);
1140 m_iconSizeSlider->setSizePolicy(hor: QSizePolicy::Maximum, ver: QSizePolicy::Fixed);
1141 m_iconSizeSlider->setMinimumWidth(40);
1142 m_iconSizeSlider->setOrientation(Qt::Horizontal);
1143 m_iconSizeSlider->setMinimum(0);
1144 m_iconSizeSlider->setMaximum(m_stdIconSizes.size() - 1);
1145 m_iconSizeSlider->setSingleStep(1);
1146 m_iconSizeSlider->setPageStep(1);
1147 m_iconSizeSlider->setTickPosition(QSlider::TicksBelow);
1148
1149 q->connect(sender: m_iconSizeSlider, signal: &QAbstractSlider::valueChanged, context: q, slot: [this](int step) {
1150 slotIconSizeChanged(m_stdIconSizes[step]);
1151 });
1152
1153 q->connect(sender: m_iconSizeSlider, signal: &QAbstractSlider::sliderMoved, context: q, slot: [this](int step) {
1154 slotIconSizeSliderMoved(m_stdIconSizes[step]);
1155 });
1156
1157 q->connect(sender: m_ops, signal: &KDirOperator::currentIconSizeChanged, context: q, slot: [this](int iconSize) {
1158 slotDirOpIconSizeChanged(size: iconSize);
1159 });
1160
1161 m_zoomOutAction = KStandardActions::create(
1162 id: KStandardActions::ZoomOut,
1163 recvr: q,
1164 slot: [this]() {
1165 changeIconsSize(zoom: ZoomOut);
1166 },
1167 parent: q);
1168
1169 q->addAction(action: m_zoomOutAction);
1170
1171 m_zoomInAction = KStandardActions::create(
1172 id: KStandardActions::ZoomIn,
1173 recvr: q,
1174 slot: [this]() {
1175 changeIconsSize(zoom: ZoomIn);
1176 },
1177 parent: q);
1178
1179 q->addAction(action: m_zoomInAction);
1180}
1181
1182void KFileWidgetPrivate::initToolbar()
1183{
1184 m_toolbar = new QToolBar(m_opsWidget);
1185 m_toolbar->setObjectName(QStringLiteral("KFileWidget::toolbar"));
1186 m_toolbar->setMovable(false);
1187
1188 // add nav items to the toolbar
1189 //
1190 // NOTE: The order of the button icons here differs from that
1191 // found in the file manager and web browser, but has been discussed
1192 // and agreed upon on the kde-core-devel mailing list:
1193 //
1194 // http://lists.kde.org/?l=kde-core-devel&m=116888382514090&w=2
1195
1196 m_ops->action(action: KDirOperator::Up)
1197 ->setWhatsThis(i18n("<qt>Click this button to enter the parent folder.<br /><br />"
1198 "For instance, if the current location is file:/home/konqi clicking this "
1199 "button will take you to file:/home.</qt>"));
1200
1201 m_ops->action(action: KDirOperator::Back)->setWhatsThis(i18n("Click this button to move backwards one step in the browsing history."));
1202 m_ops->action(action: KDirOperator::Forward)->setWhatsThis(i18n("Click this button to move forward one step in the browsing history."));
1203
1204 m_ops->action(action: KDirOperator::Reload)->setWhatsThis(i18n("Click this button to reload the contents of the current location."));
1205 m_ops->action(action: KDirOperator::NewFolder)->setShortcuts(KStandardShortcut::createFolder());
1206 m_ops->action(action: KDirOperator::NewFolder)->setWhatsThis(i18n("Click this button to create a new folder."));
1207
1208 m_togglePlacesPanelAction = new KToggleAction(i18n("Show Places Panel"), q);
1209 q->addAction(action: m_togglePlacesPanelAction);
1210 m_togglePlacesPanelAction->setShortcut(QKeySequence(Qt::Key_F9));
1211 q->connect(sender: m_togglePlacesPanelAction, signal: &QAction::toggled, context: q, slot: [this](bool show) {
1212 togglePlacesPanel(show);
1213 });
1214
1215 m_toggleQuickFilterAction = new KToggleAction(i18n("Show Quick Filter"), q);
1216 q->addAction(action: m_toggleQuickFilterAction);
1217 m_toggleQuickFilterAction->setShortcuts(QList{QKeySequence(Qt::CTRL | Qt::Key_I), QKeySequence(Qt::Key_Backslash)});
1218 q->connect(sender: m_toggleQuickFilterAction, signal: &QAction::toggled, context: q, slot: [this](bool show) {
1219 setQuickFilterVisible(show);
1220 });
1221
1222 // Build the settings menu
1223 KActionMenu *menu = new KActionMenu(QIcon::fromTheme(QStringLiteral("configure")), i18n("Options"), q);
1224 q->addAction(action: menu);
1225 menu->setWhatsThis(
1226 i18n("<qt>This is the preferences menu for the file dialog. "
1227 "Various options can be accessed from this menu including: <ul>"
1228 "<li>how files are sorted in the list</li>"
1229 "<li>types of view, including icon and list</li>"
1230 "<li>showing of hidden files</li>"
1231 "<li>the Places panel</li>"
1232 "<li>file previews</li>"
1233 "<li>separating folders from files</li></ul></qt>"));
1234
1235 menu->addAction(action: m_ops->action(action: KDirOperator::AllowExpansionInDetailsView));
1236 menu->addSeparator();
1237 menu->addAction(action: m_ops->action(action: KDirOperator::ShowHiddenFiles));
1238 menu->addAction(action: m_togglePlacesPanelAction);
1239 menu->addAction(action: m_toggleQuickFilterAction);
1240 menu->addAction(action: m_ops->action(action: KDirOperator::ShowPreviewPanel));
1241
1242 menu->setPopupMode(QToolButton::InstantPopup);
1243 q->connect(sender: menu->menu(), signal: &QMenu::aboutToShow, context: m_ops, slot: &KDirOperator::updateSelectionDependentActions);
1244
1245 m_bookmarkButton = new KActionMenu(QIcon::fromTheme(QStringLiteral("bookmarks")), i18n("Bookmarks"), q);
1246 m_bookmarkButton->setPopupMode(QToolButton::InstantPopup);
1247 q->addAction(action: m_bookmarkButton);
1248 m_bookmarkButton->setWhatsThis(
1249 i18n("<qt>This button allows you to bookmark specific locations. "
1250 "Click on this button to open the bookmark menu where you may add, "
1251 "edit or select a bookmark.<br /><br />"
1252 "These bookmarks are specific to the file dialog, but otherwise operate "
1253 "like bookmarks elsewhere in KDE.</qt>"));
1254
1255 m_bookmarkHandler = new KFileBookmarkHandler(q);
1256 q->connect(sender: m_bookmarkHandler, signal: &KFileBookmarkHandler::openUrl, context: q, slot: [this](const QString &path) {
1257 enterUrl(path);
1258 });
1259 m_bookmarkButton->setMenu(m_bookmarkHandler->menu());
1260
1261 QWidget *midSpacer = new QWidget(q);
1262 midSpacer->setSizePolicy(hor: QSizePolicy::Expanding, ver: QSizePolicy::Expanding);
1263
1264 m_toolbar->addAction(action: m_ops->action(action: KDirOperator::Back));
1265 m_toolbar->addAction(action: m_ops->action(action: KDirOperator::Forward));
1266 m_toolbar->addAction(action: m_ops->action(action: KDirOperator::Up));
1267 m_toolbar->addAction(action: m_ops->action(action: KDirOperator::Reload));
1268 m_toolbar->addSeparator();
1269 m_toolbar->addAction(action: m_ops->action(action: KDirOperator::ViewIconsView));
1270 m_toolbar->addAction(action: m_ops->action(action: KDirOperator::ViewCompactView));
1271 m_toolbar->addAction(action: m_ops->action(action: KDirOperator::ViewDetailsView));
1272 m_toolbar->addSeparator();
1273 m_toolbar->addAction(action: m_ops->action(action: KDirOperator::ShowPreview));
1274 m_toolbar->addAction(action: m_ops->action(action: KDirOperator::SortMenu));
1275 m_toolbar->addAction(action: m_bookmarkButton);
1276
1277 m_toolbar->addWidget(widget: midSpacer);
1278
1279 initZoomWidget();
1280 m_toolbar->addAction(action: m_zoomOutAction);
1281 m_toolbar->addWidget(widget: m_iconSizeSlider);
1282 m_toolbar->addAction(action: m_zoomInAction);
1283 m_toolbar->addSeparator();
1284
1285 m_toolbar->addAction(action: m_ops->action(action: KDirOperator::NewFolder));
1286 m_toolbar->addAction(action: menu);
1287
1288 m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
1289 m_toolbar->setMovable(false);
1290}
1291
1292void KFileWidgetPrivate::initLocationWidget()
1293{
1294 m_locationLabel = new QLabel(i18n("&Name:"), q);
1295 m_locationEdit = new KUrlComboBox(KUrlComboBox::Files, true, q);
1296 m_locationEdit->installEventFilter(filterObj: q);
1297 // Properly let the dialog be resized (to smaller). Otherwise we could have
1298 // huge dialogs that can't be resized to smaller (it would be as big as the longest
1299 // item in this combo box). (ereslibre)
1300 m_locationEdit->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow);
1301 q->connect(sender: m_locationEdit, signal: &KUrlComboBox::editTextChanged, context: q, slot: [this](const QString &text) {
1302 slotLocationChanged(text);
1303 });
1304
1305 // Only way to have the undo button before the clear button
1306 m_locationEdit->lineEdit()->setClearButtonEnabled(false);
1307
1308 QAction *clearAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), {}, m_locationEdit->lineEdit());
1309 m_locationEdit->lineEdit()->addAction(action: clearAction, position: QLineEdit::TrailingPosition);
1310 clearAction->setVisible(false);
1311 q->connect(sender: clearAction, signal: &QAction::triggered, context: m_locationEdit->lineEdit(), slot: &QLineEdit::clear);
1312 q->connect(sender: m_locationEdit->lineEdit(), signal: &QLineEdit::textEdited, context: q, slot: [this, clearAction]() {
1313 clearAction->setVisible(m_locationEdit->lineEdit()->text().length() > 0);
1314 });
1315 q->connect(sender: m_locationEdit->lineEdit(), signal: &QLineEdit::textChanged, context: q, slot: [this](const QString &text) {
1316 m_okButton->setEnabled(!text.isEmpty());
1317 });
1318
1319 QAction *undoAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-undo")), i18nc("@info:tooltip", "Undo filename change"), m_locationEdit->lineEdit());
1320 m_locationEdit->lineEdit()->addAction(action: undoAction, position: QLineEdit::TrailingPosition);
1321 undoAction->setVisible(false);
1322 q->connect(sender: undoAction, signal: &QAction::triggered, context: m_locationEdit->lineEdit(), slot: &QLineEdit::undo);
1323 q->connect(sender: m_locationEdit->lineEdit(), signal: &QLineEdit::textEdited, context: q, slot: [this, undoAction]() {
1324 undoAction->setVisible(m_locationEdit->lineEdit()->isUndoAvailable());
1325 });
1326
1327 updateLocationWhatsThis();
1328 m_locationLabel->setBuddy(m_locationEdit);
1329
1330 KUrlCompletion *fileCompletionObj = new KUrlCompletion(KUrlCompletion::FileCompletion);
1331 m_locationEdit->setCompletionObject(compObj: fileCompletionObj);
1332 m_locationEdit->setAutoDeleteCompletionObject(true);
1333
1334 q->connect(sender: m_locationEdit, signal: &KUrlComboBox::returnPressed, context: q, slot: [this](const QString &text) {
1335 locationAccepted(text);
1336 });
1337}
1338
1339void KFileWidgetPrivate::initFilterWidget()
1340{
1341 m_filterLabel = new QLabel(q);
1342 m_filterWidget = new KFileFilterCombo(q);
1343 m_filterWidget->setSizePolicy(hor: QSizePolicy::Expanding, ver: QSizePolicy::Fixed);
1344 updateFilterText();
1345 // Properly let the dialog be resized (to smaller). Otherwise we could have
1346 // huge dialogs that can't be resized to smaller (it would be as big as the longest
1347 // item in this combo box). (ereslibre)
1348 m_filterWidget->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow);
1349 m_filterLabel->setBuddy(m_filterWidget);
1350 q->connect(sender: m_filterWidget, signal: &KFileFilterCombo::filterChanged, context: q, slot: [this]() {
1351 slotFileFilterChanged();
1352 });
1353
1354 m_filterDelayTimer.setSingleShot(true);
1355 m_filterDelayTimer.setInterval(300);
1356 q->connect(sender: m_filterWidget, signal: &QComboBox::editTextChanged, context: &m_filterDelayTimer, slot: qOverload<>(&QTimer::start));
1357 q->connect(sender: &m_filterDelayTimer, signal: &QTimer::timeout, context: q, slot: [this]() {
1358 slotFileFilterChanged();
1359 });
1360}
1361
1362void KFileWidgetPrivate::initQuickFilterWidget()
1363{
1364 m_quickFilter = new QWidget(q);
1365 // Lock is used for keeping filter open when changing folders
1366 m_quickFilterLock = new QToolButton(m_quickFilter);
1367 m_quickFilterLock->setAutoRaise(true);
1368 m_quickFilterLock->setCheckable(true);
1369 m_quickFilterLock->setIcon(QIcon::fromTheme(QStringLiteral("object-unlocked")));
1370 m_quickFilterLock->setToolTip(i18nc("@info:tooltip", "Keep Filter When Changing Folders"));
1371
1372 m_quickFilterEdit = new QLineEdit(m_quickFilter);
1373 m_quickFilterEdit->setClearButtonEnabled(true);
1374 m_quickFilterEdit->setPlaceholderText(i18n("Filter by name…"));
1375 QObject::connect(sender: m_quickFilterEdit, signal: &QLineEdit::textChanged, context: q, slot: [this]() {
1376 slotQuickFilterChanged();
1377 });
1378
1379 m_quickFilterClose = new QToolButton(m_quickFilter);
1380 m_quickFilterClose->setAutoRaise(true);
1381 m_quickFilterClose->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
1382 m_quickFilterClose->setToolTip(i18nc("@info:tooltip", "Hide Filter Bar"));
1383 QObject::connect(sender: m_quickFilterClose, signal: &QToolButton::clicked, context: q, slot: [this]() {
1384 setQuickFilterVisible(false);
1385 });
1386
1387 QHBoxLayout *hLayout = new QHBoxLayout(m_quickFilter);
1388 hLayout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
1389 hLayout->addWidget(m_quickFilterLock);
1390 hLayout->addWidget(m_quickFilterEdit);
1391 hLayout->addWidget(m_quickFilterClose);
1392
1393 m_quickFilter->setSizePolicy(hor: QSizePolicy::Expanding, ver: QSizePolicy::Fixed);
1394 m_quickFilter->hide();
1395}
1396
1397void KFileWidgetPrivate::setLocationText(const QList<QUrl> &urlList)
1398{
1399 // Block m_locationEdit signals as setCurrentItem() will cause textChanged() to get
1400 // emitted, so slotLocationChanged() will be called. Make sure we don't clear the
1401 // KDirOperator's view-selection in there
1402 const QSignalBlocker blocker(m_locationEdit);
1403
1404 const QUrl baseUrl = m_ops->url();
1405
1406 if (urlList.count() > 1) {
1407 QString urls;
1408 for (const QUrl &url : urlList) {
1409 urls += QStringLiteral("\"%1\" ").arg(a: escapeDoubleQuotes(path: relativePathOrUrl(baseUrl, url)));
1410 }
1411 urls.chop(n: 1);
1412 // Never use setEditText, because it forgets the undo history
1413 m_locationEdit->lineEdit()->selectAll();
1414 m_locationEdit->lineEdit()->insert(urls);
1415 } else if (urlList.count() == 1) {
1416 const auto url = urlList[0];
1417 m_locationEdit->lineEdit()->selectAll();
1418 m_locationEdit->lineEdit()->insert(escapeDoubleQuotes(path: relativePathOrUrl(baseUrl, url)));
1419 } else if (!m_locationEdit->lineEdit()->text().isEmpty()) {
1420 m_locationEdit->clearEditText();
1421 }
1422
1423 if (m_operationMode == KFileWidget::Saving) {
1424 setNonExtSelection();
1425 }
1426}
1427
1428void KFileWidgetPrivate::updateLocationWhatsThis()
1429{
1430 const QString autocompletionWhatsThisText = i18n(
1431 "<qt>While typing in the text area, you may be presented "
1432 "with possible matches. "
1433 "This feature can be controlled by clicking with the right mouse button "
1434 "and selecting a preferred mode from the <b>Text Completion</b> menu.</qt>");
1435
1436 QString whatsThisText;
1437 if (m_operationMode == KFileWidget::Saving) {
1438 whatsThisText = QLatin1String("<qt>") + i18n("This is the name to save the file as.") + autocompletionWhatsThisText;
1439 } else if (m_ops->mode() & KFile::Files) {
1440 whatsThisText = QLatin1String("<qt>")
1441 + i18n("This is the list of files to open. More than "
1442 "one file can be specified by listing several "
1443 "files, separated by spaces.")
1444 + autocompletionWhatsThisText;
1445 } else {
1446 whatsThisText = QLatin1String("<qt>") + i18n("This is the name of the file to open.") + autocompletionWhatsThisText;
1447 }
1448
1449 m_locationLabel->setWhatsThis(whatsThisText);
1450 m_locationEdit->setWhatsThis(whatsThisText);
1451}
1452
1453void KFileWidgetPrivate::initPlacesPanel()
1454{
1455 if (m_placesDock) {
1456 return;
1457 }
1458
1459 m_placesDock = new QDockWidget(i18nc("@title:window", "Places"), q);
1460 m_placesDock->setFeatures(QDockWidget::NoDockWidgetFeatures);
1461 m_placesDock->setTitleBarWidget(new KDEPrivate::KFileWidgetDockTitleBar(m_placesDock));
1462
1463 m_placesView = new KFilePlacesView(m_placesDock);
1464 m_placesView->setModel(m_model);
1465 m_placesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1466
1467 m_placesView->setObjectName(QStringLiteral("url bar"));
1468 QObject::connect(sender: m_placesView, signal: &KFilePlacesView::urlChanged, context: q, slot: [this](const QUrl &url) {
1469 enterUrl(url);
1470 });
1471
1472 QObject::connect(sender: qobject_cast<KFilePlacesModel *>(object: m_placesView->model()), signal: &KFilePlacesModel::errorMessage, context: q, slot: [this](const QString &errorMessage) {
1473 m_messageWidget->setText(errorMessage);
1474 m_messageWidget->animatedShow();
1475 });
1476
1477 // need to set the current url of the urlbar manually (not via urlEntered()
1478 // here, because the initial url of KDirOperator might be the same as the
1479 // one that will be set later (and then urlEntered() won't be emitted).
1480 // TODO: KDE5 ### REMOVE THIS when KDirOperator's initial URL (in the c'tor) is gone.
1481 m_placesView->setUrl(m_url);
1482
1483 m_placesDock->setWidget(m_placesView);
1484 m_placesViewSplitter->insertWidget(index: 0, widget: m_placesDock);
1485
1486 // initialize the size of the splitter
1487 m_placesViewWidth = m_configGroup.readEntry(key: SpeedbarWidth, aDefault: m_placesView->sizeHint().width());
1488
1489 // Needed for when the dialog is shown with the places panel initially hidden
1490 setPlacesViewSplitterSizes();
1491
1492 QObject::connect(sender: m_placesDock, signal: &QDockWidget::visibilityChanged, context: q, slot: [this](bool visible) {
1493 togglePlacesPanel(show: visible, sender: m_placesDock);
1494 });
1495}
1496
1497void KFileWidgetPrivate::setPlacesViewSplitterSizes()
1498{
1499 if (m_placesViewWidth > 0) {
1500 QList<int> sizes = m_placesViewSplitter->sizes();
1501 sizes[0] = m_placesViewWidth;
1502 sizes[1] = q->width() - m_placesViewWidth - m_placesViewSplitter->handleWidth();
1503 m_placesViewSplitter->setSizes(sizes);
1504 }
1505}
1506
1507void KFileWidgetPrivate::initGUI()
1508{
1509 delete m_boxLayout; // deletes all sub layouts
1510
1511 m_boxLayout = new QVBoxLayout(q);
1512 m_boxLayout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0); // no additional margin to the already existing
1513
1514 m_placesViewSplitter = new QSplitter(q);
1515 m_placesViewSplitter->setSizePolicy(hor: QSizePolicy::Expanding, ver: QSizePolicy::Expanding);
1516 m_placesViewSplitter->setChildrenCollapsible(false);
1517 m_boxLayout->addWidget(m_placesViewSplitter);
1518
1519 QObject::connect(sender: m_placesViewSplitter, signal: &QSplitter::splitterMoved, context: q, slot: [this](int pos, int index) {
1520 placesViewSplitterMoved(pos, index);
1521 });
1522 m_placesViewSplitter->insertWidget(index: 0, widget: m_opsWidget);
1523
1524 m_lafBox = new QFormLayout();
1525 m_lafBox->setSpacing(q->style()->pixelMetric(metric: QStyle::PM_LayoutVerticalSpacing));
1526 m_lafBox->setContentsMargins(left: q->style()->pixelMetric(metric: QStyle::PM_LayoutLeftMargin),
1527 top: q->style()->pixelMetric(metric: QStyle::PM_LayoutTopMargin),
1528 right: q->style()->pixelMetric(metric: QStyle::PM_LayoutRightMargin),
1529 bottom: 0);
1530
1531 m_lafBox->addRow(widget: m_quickFilter);
1532 m_lafBox->addRow(label: m_locationLabel, field: m_locationEdit);
1533 m_lafBox->addRow(label: m_filterLabel, field: m_filterWidget);
1534 // Add the "Automatically Select Extension" checkbox
1535 m_lafBox->addWidget(w: m_autoSelectExtCheckBox);
1536
1537 m_opsWidgetLayout->addLayout(layout: m_lafBox);
1538
1539 auto hbox = new QHBoxLayout();
1540 hbox->setSpacing(q->style()->pixelMetric(metric: QStyle::PM_LayoutHorizontalSpacing));
1541 hbox->setContentsMargins(left: q->style()->pixelMetric(metric: QStyle::PM_LayoutTopMargin),
1542 top: q->style()->pixelMetric(metric: QStyle::PM_LayoutLeftMargin),
1543 right: q->style()->pixelMetric(metric: QStyle::PM_LayoutRightMargin),
1544 bottom: q->style()->pixelMetric(metric: QStyle::PM_LayoutBottomMargin));
1545
1546 hbox->addStretch(stretch: 2);
1547 hbox->addWidget(m_okButton);
1548 hbox->addWidget(m_cancelButton);
1549
1550 m_opsWidgetLayout->addLayout(layout: hbox);
1551
1552 auto updateTabOrder = [this]() {
1553 // First the url navigator and its internal tab order
1554 q->setTabOrder(m_urlNavigator, m_ops);
1555 // Add the other elements in the ui that aren't int he toolbar
1556 q->setTabOrder(m_ops, m_autoSelectExtCheckBox);
1557 q->setTabOrder(m_autoSelectExtCheckBox, m_quickFilterLock);
1558 q->setTabOrder(m_quickFilterLock, m_quickFilterEdit);
1559 q->setTabOrder(m_quickFilterEdit, m_quickFilterClose);
1560 q->setTabOrder(m_quickFilterClose, m_locationEdit);
1561 q->setTabOrder(m_locationEdit, m_filterWidget);
1562 q->setTabOrder(m_filterWidget, m_okButton);
1563 q->setTabOrder(m_okButton, m_cancelButton);
1564 q->setTabOrder(m_cancelButton, m_placesView);
1565
1566 // Now add every widget in the toolbar
1567 const auto toolbarChildren = m_toolbar->children();
1568 QList<QWidget *> toolbarButtons;
1569 for (QObject *obj : std::as_const(t: toolbarChildren)) {
1570 if (auto *button = qobject_cast<QToolButton *>(object: obj)) {
1571 // Make toolbar buttons focusable only via tab
1572 button->setFocusPolicy(Qt::TabFocus);
1573 toolbarButtons << button;
1574 } else if (auto *slider = qobject_cast<QSlider *>(object: obj)) {
1575 toolbarButtons << slider;
1576 }
1577 }
1578
1579 q->setTabOrder(m_placesView, toolbarButtons.first());
1580
1581 auto it = toolbarButtons.constBegin();
1582 auto nextIt = ++toolbarButtons.constBegin();
1583 while (nextIt != toolbarButtons.constEnd()) {
1584 q->setTabOrder(*it, *nextIt);
1585 it++;
1586 nextIt++;
1587 }
1588 // Do not manually close the loop: it would break the chain
1589 };
1590 q->connect(sender: m_urlNavigator, signal: &KUrlNavigator::layoutChanged, context: q, slot&: updateTabOrder);
1591 updateTabOrder();
1592}
1593
1594void KFileWidgetPrivate::slotFileFilterChanged()
1595{
1596 m_filterDelayTimer.stop();
1597
1598 KFileFilter filter = m_filterWidget->currentFilter();
1599
1600 m_ops->clearFilter();
1601
1602 if (!filter.mimePatterns().isEmpty()) {
1603 QStringList types = filter.mimePatterns();
1604 types.prepend(QStringLiteral("inode/directory"));
1605 m_ops->setMimeFilter(types);
1606 }
1607
1608 updateNameFilter(filter);
1609
1610 updateAutoSelectExtension();
1611
1612 m_ops->updateDir();
1613
1614 Q_EMIT q->filterChanged(filter);
1615}
1616
1617void KFileWidgetPrivate::slotQuickFilterChanged()
1618{
1619 m_filterDelayTimer.stop();
1620
1621 KFileFilter filter(QStringLiteral("quickFilter"), QStringList{m_quickFilterEdit->text()}, m_filterWidget->currentFilter().mimePatterns());
1622 m_ops->clearFilter();
1623 m_ops->setMimeFilter(filter.mimePatterns());
1624
1625 updateNameFilter(filter);
1626
1627 m_ops->updateDir();
1628
1629 Q_EMIT q->filterChanged(filter);
1630}
1631
1632void KFileWidgetPrivate::updateNameFilter(const KFileFilter &filter)
1633{
1634 const auto filePatterns = filter.filePatterns();
1635 const bool hasRegExSyntax = std::any_of(first: filePatterns.constBegin(), last: filePatterns.constEnd(), pred: [](const QString &filter) {
1636 // Keep the filter.contains checks in sync with Dolphin: dolphin/src/kitemviews/private/kfileitemmodelfilter.cpp setPattern
1637 return filter.contains(c: QLatin1Char('*')) || filter.contains(c: QLatin1Char('?')) || filter.contains(c: QLatin1Char('['));
1638 });
1639
1640 if (hasRegExSyntax) {
1641 m_ops->setNameFilter(filter.filePatterns().join(sep: QLatin1Char(' ')));
1642 } else {
1643 m_ops->setNameFilter(QLatin1Char('*') + filePatterns.join(sep: QLatin1Char('*')) + QLatin1Char('*'));
1644 }
1645}
1646
1647void KFileWidget::setUrl(const QUrl &url, bool clearforward)
1648{
1649 if (url.isLocalFile() && QDir::isRelativePath(path: url.path())) {
1650 d->m_ops->setUrl(url: QUrl::fromLocalFile(localfile: QDir::currentPath() + u'/' + url.path()), clearforward);
1651 } else {
1652 d->m_ops->setUrl(url, clearforward);
1653 }
1654}
1655
1656// Protected
1657void KFileWidgetPrivate::urlEntered(const QUrl &url)
1658{
1659 // qDebug();
1660
1661 KUrlComboBox *pathCombo = m_urlNavigator->editor();
1662 if (pathCombo->count() != 0) { // little hack
1663 pathCombo->setUrl(url);
1664 }
1665
1666 bool blocked = m_locationEdit->blockSignals(b: true);
1667 if (m_keepLocation) {
1668 const QUrl currentUrl = urlFromString(str: locationEditCurrentText());
1669 // iconNameForUrl will get the icon or fallback to a generic one
1670 m_locationEdit->setItemIcon(index: 0, icon: QIcon::fromTheme(name: KIO::iconNameForUrl(url: currentUrl)));
1671 // Preserve the text when clicking on the view (cf fileHighlighted)
1672 m_locationEdit->lineEdit()->setModified(true);
1673 }
1674
1675 m_locationEdit->blockSignals(b: blocked);
1676
1677 m_urlNavigator->setLocationUrl(url);
1678
1679 // is triggered in ctor before completion object is set
1680 KUrlCompletion *completion = dynamic_cast<KUrlCompletion *>(m_locationEdit->completionObject());
1681 if (completion) {
1682 completion->setDir(url);
1683 }
1684
1685 if (m_placesView) {
1686 m_placesView->setUrl(url);
1687 }
1688
1689 m_messageWidget->hide();
1690}
1691
1692void KFileWidgetPrivate::locationAccepted(const QString &url)
1693{
1694 Q_UNUSED(url);
1695 // qDebug();
1696 q->slotOk();
1697}
1698
1699void KFileWidgetPrivate::enterUrl(const QUrl &url)
1700{
1701 // qDebug();
1702
1703 // append '/' if needed: url combo does not add it
1704 // tokenize() expects it because it uses QUrl::adjusted(QUrl::RemoveFilename)
1705 QUrl u(url);
1706 Utils::appendSlashToPath(url&: u);
1707 q->setUrl(url: u);
1708
1709 // We need to check window()->focusWidget() instead of m_locationEdit->hasFocus
1710 // because when the window is showing up m_locationEdit
1711 // may still not have focus but it'll be the one that will have focus when the window
1712 // gets it and we don't want to steal its focus either
1713 if (q->window()->focusWidget() != m_locationEdit) {
1714 m_ops->setFocus();
1715 }
1716
1717 // Clear the quick filter if its not locked
1718 if (!m_quickFilterLock->isChecked()) {
1719 setQuickFilterVisible(false);
1720 }
1721}
1722
1723void KFileWidgetPrivate::enterUrl(const QString &url)
1724{
1725 // qDebug();
1726
1727 enterUrl(url: urlFromString(str: KUrlCompletion::replacedPath(text: url, replaceHome: true, replaceEnv: true)));
1728}
1729
1730bool KFileWidgetPrivate::toOverwrite(const QUrl &url)
1731{
1732 // qDebug();
1733
1734 KIO::StatJob *statJob = KIO::stat(url, flags: KIO::HideProgressInfo);
1735 KJobWidgets::setWindow(job: statJob, widget: q);
1736 bool res = statJob->exec();
1737
1738 if (res) {
1739 int ret = KMessageBox::warningContinueCancel(parent: q,
1740 i18n("The file \"%1\" already exists. Do you wish to overwrite it?", url.fileName()),
1741 i18n("Overwrite File?"),
1742 buttonContinue: KStandardGuiItem::overwrite(),
1743 buttonCancel: KStandardGuiItem::cancel(),
1744 dontAskAgainName: QString(),
1745 options: KMessageBox::Notify | KMessageBox::Dangerous);
1746
1747 if (ret != KMessageBox::Continue) {
1748 m_locationEdit->setFocus();
1749 setNonExtSelection();
1750
1751 return false;
1752 }
1753 return true;
1754 }
1755
1756 return true;
1757}
1758
1759void KFileWidget::setSelectedUrl(const QUrl &url)
1760{
1761 // Honor protocols that do not support directory listing
1762 if (!url.isRelative() && !KProtocolManager::supportsListing(url)) {
1763 return;
1764 }
1765 d->setLocationText(url);
1766}
1767
1768void KFileWidget::setSelectedUrls(const QList<QUrl> &urls)
1769{
1770 if (urls.isEmpty()) {
1771 return;
1772 }
1773
1774 // Honor protocols that do not support directory listing
1775 if (!urls[0].isRelative() && !KProtocolManager::supportsListing(url: urls[0])) {
1776 return;
1777 }
1778 d->setLocationText(urls);
1779}
1780
1781void KFileWidgetPrivate::slotLoadingFinished()
1782{
1783 const QString currentText = m_locationEdit->currentText();
1784 if (currentText.isEmpty()) {
1785 return;
1786 }
1787
1788 m_ops->blockSignals(b: true);
1789 QUrl u(m_ops->url());
1790 if (currentText.startsWith(c: QLatin1Char('/'))) {
1791 u.setPath(path: currentText);
1792 } else {
1793 u.setPath(path: Utils::concatPaths(path1: m_ops->url().path(), path2: currentText));
1794 }
1795 m_ops->setCurrentItem(u);
1796 m_ops->blockSignals(b: false);
1797}
1798
1799void KFileWidgetPrivate::slotLocationChanged(const QString &text)
1800{
1801 // qDebug();
1802
1803 m_locationEdit->lineEdit()->setModified(true);
1804
1805 if (text.isEmpty() && m_ops->view()) {
1806 m_ops->view()->clearSelection();
1807 }
1808
1809 if (!m_locationEdit->lineEdit()->text().isEmpty()) {
1810 const QList<QUrl> urlList(tokenize(line: text));
1811 m_ops->setCurrentItems(urlList);
1812 }
1813
1814 updateFilter();
1815}
1816
1817QUrl KFileWidget::selectedUrl() const
1818{
1819 // qDebug();
1820
1821 if (d->m_inAccept) {
1822 return d->m_url;
1823 } else {
1824 return QUrl();
1825 }
1826}
1827
1828QList<QUrl> KFileWidget::selectedUrls() const
1829{
1830 // qDebug();
1831
1832 QList<QUrl> list;
1833 if (d->m_inAccept) {
1834 if (d->m_ops->mode() & KFile::Files) {
1835 list = d->m_urlList;
1836 } else {
1837 list.append(t: d->m_url);
1838 }
1839 }
1840 return list;
1841}
1842
1843QList<QUrl> KFileWidgetPrivate::tokenize(const QString &line) const
1844{
1845 qCDebug(KIO_KFILEWIDGETS_FW) << "Tokenizing:" << line;
1846
1847 QList<QUrl> urls;
1848 QUrl baseUrl(m_ops->url().adjusted(options: QUrl::RemoveFilename));
1849 Utils::appendSlashToPath(url&: baseUrl);
1850
1851 // A helper that creates, validates and appends a new url based
1852 // on the given filename.
1853 auto addUrl = [baseUrl, &urls](const QString &partial_name) {
1854 if (partial_name.trimmed().isEmpty()) {
1855 return;
1856 }
1857
1858 // url could be absolute
1859 QUrl partial_url(partial_name);
1860 if (!partial_url.isValid()
1861 || partial_url.isRelative()
1862 // the text might look like a url scheme but not be a real one
1863 || (!partial_url.scheme().isEmpty() && (!partial_name.contains(QStringLiteral("://")) || !KProtocolInfo::isKnownProtocol(protocol: partial_url.scheme())))) {
1864 // We have to use setPath here, so that something like "test#file"
1865 // isn't interpreted to have path "test" and fragment "file".
1866 partial_url.clear();
1867 partial_url.setPath(path: partial_name);
1868 }
1869
1870 // This returns QUrl(partial_name) for absolute URLs.
1871 // Otherwise, returns the concatenated url.
1872 if (partial_url.isRelative() || baseUrl.isParentOf(url: partial_url)) {
1873 partial_url = baseUrl.resolved(relative: partial_url);
1874 }
1875
1876 if (partial_url.isValid()) {
1877 urls.append(t: partial_url);
1878 } else {
1879 // This can happen in the first quote! (ex: ' "something here"')
1880 qCDebug(KIO_KFILEWIDGETS_FW) << "Discarding Invalid" << partial_url;
1881 }
1882 };
1883
1884 // An iterative approach here where we toggle the "escape" flag
1885 // if we hit `\`. If we hit `"` and the escape flag is false,
1886 // we split
1887 QString partial_name;
1888 bool escape = false;
1889 for (int i = 0; i < line.length(); i++) {
1890 const QChar ch = line[i];
1891
1892 // Handle any character previously escaped
1893 if (escape) {
1894 partial_name += ch;
1895 escape = false;
1896 continue;
1897 }
1898
1899 // Handle escape start
1900 if (ch.toLatin1() == '\\') {
1901 escape = true;
1902 continue;
1903 }
1904
1905 // Handle UNESCAPED quote (") since the above ifs are
1906 // dealing with the escaped ones
1907 // Ignore this in single-file mode
1908 if (ch.toLatin1() == '"' && q->mode() != KFile::Mode::File) {
1909 addUrl(partial_name);
1910 partial_name.clear();
1911 continue;
1912 }
1913
1914 // Any other character just append
1915 partial_name += ch;
1916 }
1917
1918 // Handle the last item which is buffered in partial_name. This is
1919 // required for single-file selection dialogs since the name will not
1920 // be wrapped in quotes
1921 if (!partial_name.isEmpty()) {
1922 addUrl(partial_name);
1923 partial_name.clear();
1924 }
1925
1926 return urls;
1927}
1928
1929QString KFileWidget::selectedFile() const
1930{
1931 // qDebug();
1932
1933 if (d->m_inAccept) {
1934 const QUrl url = d->mostLocalUrl(url: d->m_url);
1935 if (url.isLocalFile()) {
1936 return url.toLocalFile();
1937 } else {
1938 KMessageBox::error(parent: const_cast<KFileWidget *>(this), i18n("You can only select local files."), i18n("Remote Files Not Accepted"));
1939 }
1940 }
1941 return QString();
1942}
1943
1944QStringList KFileWidget::selectedFiles() const
1945{
1946 // qDebug();
1947
1948 QStringList list;
1949
1950 if (d->m_inAccept) {
1951 if (d->m_ops->mode() & KFile::Files) {
1952 const QList<QUrl> urls = d->m_urlList;
1953 for (const auto &u : urls) {
1954 const QUrl url = d->mostLocalUrl(url: u);
1955 if (url.isLocalFile()) {
1956 list.append(t: url.toLocalFile());
1957 }
1958 }
1959 }
1960
1961 else { // single-selection mode
1962 if (d->m_url.isLocalFile()) {
1963 list.append(t: d->m_url.toLocalFile());
1964 }
1965 }
1966 }
1967
1968 return list;
1969}
1970
1971QUrl KFileWidget::baseUrl() const
1972{
1973 return d->m_ops->url();
1974}
1975
1976void KFileWidget::resizeEvent(QResizeEvent *event)
1977{
1978 QWidget::resizeEvent(event);
1979
1980 if (d->m_placesDock) {
1981 // we don't want our places dock actually changing size when we resize
1982 // and qt doesn't make it easy to enforce such a thing with QSplitter
1983 d->setPlacesViewSplitterSizes();
1984 }
1985}
1986
1987void KFileWidget::showEvent(QShowEvent *event)
1988{
1989 if (!d->m_hasView) { // delayed view-creation
1990 Q_ASSERT(d);
1991 Q_ASSERT(d->m_ops);
1992 d->m_ops->setViewMode(KFile::Default);
1993 d->m_hasView = true;
1994
1995 connect(sender: d->m_ops->view(), signal: &QAbstractItemView::doubleClicked, context: this, slot: [this](const QModelIndex &index) {
1996 d->slotViewDoubleClicked(index);
1997 });
1998 }
1999 d->m_ops->clearHistory();
2000
2001 QWidget::showEvent(event);
2002}
2003
2004bool KFileWidget::eventFilter(QObject *watched, QEvent *event)
2005{
2006 const bool res = QWidget::eventFilter(watched, event);
2007
2008 QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>(event);
2009 if (!keyEvent) {
2010 return res;
2011 }
2012
2013 const auto type = event->type();
2014 const auto key = keyEvent->key();
2015
2016 if (watched == d->m_ops && type == QEvent::KeyPress && (key == Qt::Key_Return || key == Qt::Key_Enter)) {
2017 // ignore return events from the KDirOperator
2018 // they are not needed, activated is used to handle this case
2019 event->accept();
2020 return true;
2021 }
2022
2023 return res;
2024}
2025
2026void KFileWidget::setMode(KFile::Modes m)
2027{
2028 // qDebug();
2029
2030 d->m_ops->setMode(m);
2031 if (d->m_ops->dirOnlyMode()) {
2032 d->m_filterWidget->setDefaultFilter(KFileFilter(i18n("All Folders"), {QStringLiteral("*")}, {}));
2033 } else {
2034 d->m_filterWidget->setDefaultFilter(KFileFilter(i18n("All Files"), {QStringLiteral("*")}, {}));
2035 }
2036
2037 d->updateAutoSelectExtension();
2038}
2039
2040KFile::Modes KFileWidget::mode() const
2041{
2042 return d->m_ops->mode();
2043}
2044
2045void KFileWidgetPrivate::readViewConfig()
2046{
2047 m_ops->setViewConfig(m_configGroup);
2048 m_ops->readConfig(configGroup: m_configGroup);
2049 KUrlComboBox *combo = m_urlNavigator->editor();
2050
2051 KCompletion::CompletionMode cm =
2052 (KCompletion::CompletionMode)m_configGroup.readEntry(key: PathComboCompletionMode, aDefault: static_cast<int>(KCompletion::CompletionPopup));
2053 if (cm != KCompletion::CompletionPopup) {
2054 combo->setCompletionMode(cm);
2055 }
2056
2057 cm = (KCompletion::CompletionMode)m_configGroup.readEntry(key: LocationComboCompletionMode, aDefault: static_cast<int>(KCompletion::CompletionPopup));
2058 if (cm != KCompletion::CompletionPopup) {
2059 m_locationEdit->setCompletionMode(cm);
2060 }
2061
2062 // Show or don't show the places panel
2063 togglePlacesPanel(show: m_configGroup.readEntry(key: ShowSpeedbar, aDefault: true));
2064
2065 // does the user want Automatically Select Extension?
2066 m_autoSelectExtChecked = m_configGroup.readEntry(key: AutoSelectExtChecked, aDefault: DefaultAutoSelectExtChecked);
2067 updateAutoSelectExtension();
2068
2069 // should the URL navigator use the breadcrumb navigation?
2070 m_urlNavigator->setUrlEditable(!m_configGroup.readEntry(key: BreadcrumbNavigation, aDefault: true));
2071
2072 // should the URL navigator show the full path?
2073 m_urlNavigator->setShowFullPath(m_configGroup.readEntry(key: ShowFullPath, aDefault: false));
2074
2075 int w1 = q->minimumSize().width();
2076 int w2 = m_toolbar->sizeHint().width();
2077 if (w1 < w2) {
2078 q->setMinimumWidth(w2);
2079 }
2080}
2081
2082void KFileWidgetPrivate::writeViewConfig()
2083{
2084 // these settings are global settings; ALL instances of the file dialog
2085 // should reflect them.
2086 // There is no way to tell KFileOperator::writeConfig() to write to
2087 // kdeglobals so we write settings to a temporary config group then copy
2088 // them all to kdeglobals
2089 KConfig tmp(QString(), KConfig::SimpleConfig);
2090 KConfigGroup tmpGroup(&tmp, ConfigGroup);
2091
2092 KUrlComboBox *pathCombo = m_urlNavigator->editor();
2093 // saveDialogSize( tmpGroup, KConfigGroup::Persistent | KConfigGroup::Global );
2094 tmpGroup.writeEntry(key: PathComboCompletionMode, value: static_cast<int>(pathCombo->completionMode()));
2095 tmpGroup.writeEntry(key: LocationComboCompletionMode, value: static_cast<int>(m_locationEdit->completionMode()));
2096
2097 const bool showPlacesPanel = m_placesDock && !m_placesDock->isHidden();
2098 tmpGroup.writeEntry(key: ShowSpeedbar, value: showPlacesPanel);
2099 if (m_placesViewWidth > 0) {
2100 tmpGroup.writeEntry(key: SpeedbarWidth, value: m_placesViewWidth);
2101 }
2102
2103 tmpGroup.writeEntry(key: AutoSelectExtChecked, value: m_autoSelectExtChecked);
2104 tmpGroup.writeEntry(key: BreadcrumbNavigation, value: !m_urlNavigator->isUrlEditable());
2105 tmpGroup.writeEntry(key: ShowFullPath, value: m_urlNavigator->showFullPath());
2106
2107 m_ops->writeConfig(configGroup&: tmpGroup);
2108
2109 // Copy saved settings to kdeglobals
2110 tmpGroup.copyTo(other: &m_configGroup, pFlags: KConfigGroup::Persistent | KConfigGroup::Global);
2111
2112 // clean up no longer used values
2113 m_configGroup.revertToDefault(key: "Show Bookmarks", pFlag: KConfigGroup::Global);
2114}
2115
2116void KFileWidgetPrivate::readRecentFiles()
2117{
2118 // qDebug();
2119
2120 const bool oldState = m_locationEdit->blockSignals(b: true);
2121 m_locationEdit->setMaxItems(m_configGroup.readEntry(key: RecentFilesNumber, aDefault: DefaultRecentURLsNumber));
2122 m_locationEdit->setUrls(urls: m_stateConfigGroup.readPathEntry(pKey: RecentFiles, aDefault: QStringList()), remove: KUrlComboBox::RemoveBottom);
2123 m_locationEdit->setCurrentIndex(-1);
2124 m_locationEdit->blockSignals(b: oldState);
2125
2126 KUrlComboBox *combo = m_urlNavigator->editor();
2127 combo->setUrls(urls: m_stateConfigGroup.readPathEntry(pKey: RecentURLs, aDefault: QStringList()), remove: KUrlComboBox::RemoveTop);
2128 combo->setMaxItems(m_configGroup.readEntry(key: RecentURLsNumber, aDefault: DefaultRecentURLsNumber));
2129 combo->setUrl(m_ops->url());
2130 // since we delayed this moment, initialize the directory of the completion object to
2131 // our current directory (that was very probably set on the constructor)
2132 KUrlCompletion *completion = dynamic_cast<KUrlCompletion *>(m_locationEdit->completionObject());
2133 if (completion) {
2134 completion->setDir(m_ops->url());
2135 }
2136}
2137
2138void KFileWidgetPrivate::saveRecentFiles()
2139{
2140 // qDebug();
2141 m_stateConfigGroup.writePathEntry(pKey: RecentFiles, value: m_locationEdit->urls());
2142
2143 KUrlComboBox *pathCombo = m_urlNavigator->editor();
2144 m_stateConfigGroup.writePathEntry(pKey: RecentURLs, value: pathCombo->urls());
2145}
2146
2147QPushButton *KFileWidget::okButton() const
2148{
2149 return d->m_okButton;
2150}
2151
2152QPushButton *KFileWidget::cancelButton() const
2153{
2154 return d->m_cancelButton;
2155}
2156
2157// Called by KFileDialog
2158void KFileWidget::slotCancel()
2159{
2160 d->writeViewConfig();
2161 d->m_ops->close();
2162}
2163
2164void KFileWidget::setKeepLocation(bool keep)
2165{
2166 d->m_keepLocation = keep;
2167}
2168
2169bool KFileWidget::keepsLocation() const
2170{
2171 return d->m_keepLocation;
2172}
2173
2174void KFileWidget::setOperationMode(OperationMode mode)
2175{
2176 // qDebug();
2177
2178 d->m_operationMode = mode;
2179 d->m_keepLocation = (mode == Saving);
2180 d->m_filterWidget->setEditable(!d->m_hasDefaultFilter || mode != Saving);
2181 if (mode == Opening) {
2182 // don't use KStandardGuiItem::open() here which has trailing ellipsis!
2183 d->m_okButton->setText(i18n("&Open"));
2184 d->m_okButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
2185 // hide the new folder actions...usability team says they shouldn't be in open file dialog
2186 d->m_ops->action(action: KDirOperator::NewFolder)->setEnabled(false);
2187 d->m_toolbar->removeAction(action: d->m_ops->action(action: KDirOperator::NewFolder));
2188 } else if (mode == Saving) {
2189 KGuiItem::assign(button: d->m_okButton, item: KStandardGuiItem::save());
2190 d->setNonExtSelection();
2191 } else {
2192 KGuiItem::assign(button: d->m_okButton, item: KStandardGuiItem::ok());
2193 }
2194 d->updateLocationWhatsThis();
2195 d->updateAutoSelectExtension();
2196
2197 if (d->m_ops) {
2198 d->m_ops->setIsSaving(mode == Saving);
2199 }
2200 d->updateFilterText();
2201}
2202
2203KFileWidget::OperationMode KFileWidget::operationMode() const
2204{
2205 return d->m_operationMode;
2206}
2207
2208void KFileWidgetPrivate::slotAutoSelectExtClicked()
2209{
2210 // qDebug() << "slotAutoSelectExtClicked(): "
2211 // << m_autoSelectExtCheckBox->isChecked() << endl;
2212
2213 // whether the _user_ wants it on/off
2214 m_autoSelectExtChecked = m_autoSelectExtCheckBox->isChecked();
2215
2216 // update the current filename's extension
2217 updateLocationEditExtension(m_extension /* extension hasn't changed */);
2218}
2219
2220void KFileWidgetPrivate::placesViewSplitterMoved(int pos, int index)
2221{
2222 // qDebug();
2223
2224 // we need to record the size of the splitter when the splitter changes size
2225 // so we can keep the places box the right size!
2226 if (m_placesDock && index == 1) {
2227 m_placesViewWidth = pos;
2228 // qDebug() << "setting m_lafBox minwidth to" << m_placesViewWidth;
2229 }
2230}
2231
2232void KFileWidgetPrivate::activateUrlNavigator()
2233{
2234 // qDebug();
2235
2236 QLineEdit *lineEdit = m_urlNavigator->editor()->lineEdit();
2237
2238 // If the text field currently has focus and everything is selected,
2239 // pressing the keyboard shortcut returns the whole thing to breadcrumb mode
2240 if (m_urlNavigator->isUrlEditable() && lineEdit->hasFocus() && lineEdit->selectedText() == lineEdit->text()) {
2241 m_urlNavigator->setUrlEditable(false);
2242 } else {
2243 m_urlNavigator->setUrlEditable(true);
2244 m_urlNavigator->setFocus();
2245 lineEdit->selectAll();
2246 }
2247}
2248
2249void KFileWidgetPrivate::slotDirOpIconSizeChanged(int size)
2250{
2251 auto beginIt = m_stdIconSizes.cbegin();
2252 auto endIt = m_stdIconSizes.cend();
2253 auto it = std::lower_bound(first: beginIt, last: endIt, val: size);
2254 const int sliderStep = it != endIt ? it - beginIt : 0;
2255 m_iconSizeSlider->setValue(sliderStep);
2256 m_zoomOutAction->setDisabled(it == beginIt);
2257 m_zoomInAction->setDisabled(it == (endIt - 1));
2258}
2259
2260void KFileWidgetPrivate::changeIconsSize(ZoomState zoom)
2261{
2262 int step = m_iconSizeSlider->value();
2263
2264 if (zoom == ZoomOut) {
2265 if (step == 0) {
2266 return;
2267 }
2268 --step;
2269 } else { // ZoomIn
2270 if (step == static_cast<int>(m_stdIconSizes.size() - 1)) {
2271 return;
2272 }
2273 ++step;
2274 }
2275
2276 m_iconSizeSlider->setValue(step);
2277 slotIconSizeSliderMoved(m_stdIconSizes[step]);
2278}
2279
2280void KFileWidgetPrivate::slotIconSizeChanged(int _value)
2281{
2282 m_ops->setIconSize(_value);
2283 m_iconSizeSlider->setToolTip(i18n("Icon size: %1 pixels", _value));
2284}
2285
2286void KFileWidgetPrivate::slotIconSizeSliderMoved(int size)
2287{
2288 // Force this to be called in case this slot is called first on the
2289 // slider move.
2290 slotIconSizeChanged(value: size);
2291
2292 QPoint global(m_iconSizeSlider->rect().topLeft());
2293 global.ry() += m_iconSizeSlider->height() / 2;
2294 QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), m_iconSizeSlider->mapToGlobal(global));
2295 QApplication::sendEvent(receiver: m_iconSizeSlider, event: &toolTipEvent);
2296}
2297
2298void KFileWidgetPrivate::slotViewDoubleClicked(const QModelIndex &index)
2299{
2300 // double clicking to save should only work on files
2301 if (m_operationMode == KFileWidget::Saving && index.isValid() && m_ops->selectedItems().constFirst().isFile()) {
2302 q->slotOk();
2303 }
2304}
2305
2306void KFileWidgetPrivate::slotViewKeyEnterReturnPressed()
2307{
2308 // an enter/return event occurred in the view
2309 // when we are saving one file and there is no selection in the view (otherwise we get an activated event)
2310 if (m_operationMode == KFileWidget::Saving && (m_ops->mode() & KFile::File) && m_ops->selectedItems().isEmpty()) {
2311 q->slotOk();
2312 }
2313}
2314
2315static QString getExtensionFromPatternList(const QStringList &patternList)
2316{
2317 // qDebug();
2318
2319 QString ret;
2320 // qDebug() << "\tgetExtension " << patternList;
2321
2322 QStringList::ConstIterator patternListEnd = patternList.end();
2323 for (QStringList::ConstIterator it = patternList.begin(); it != patternListEnd; ++it) {
2324 // qDebug() << "\t\ttry: \'" << (*it) << "\'";
2325
2326 // is this pattern like "*.BMP" rather than useless things like:
2327 //
2328 // README
2329 // *.
2330 // *.*
2331 // *.JP*G
2332 // *.JP?
2333 // *.[Jj][Pp][Gg]
2334 if ((*it).startsWith(s: QLatin1String("*.")) && (*it).length() > 2 && (*it).indexOf(ch: QLatin1Char('*'), from: 2) < 0 && (*it).indexOf(ch: QLatin1Char('?'), from: 2) < 0
2335 && (*it).indexOf(ch: QLatin1Char('['), from: 2) < 0 && (*it).indexOf(ch: QLatin1Char(']'), from: 2) < 0) {
2336 ret = (*it).mid(position: 1);
2337 break;
2338 }
2339 }
2340
2341 return ret;
2342}
2343
2344static QString stripUndisplayable(const QString &string)
2345{
2346 QString ret = string;
2347
2348 ret.remove(c: QLatin1Char(':'));
2349 ret = KLocalizedString::removeAcceleratorMarker(label: ret);
2350
2351 return ret;
2352}
2353
2354// QString KFileWidget::currentFilterExtension()
2355//{
2356// return d->m_extension;
2357//}
2358
2359void KFileWidgetPrivate::updateAutoSelectExtension()
2360{
2361 if (!m_autoSelectExtCheckBox) {
2362 return;
2363 }
2364
2365 QMimeDatabase db;
2366 //
2367 // Figure out an extension for the Automatically Select Extension thing
2368 // (some Windows users apparently don't know what to do when confronted
2369 // with a text file called "COPYING" but do know what to do with
2370 // COPYING.txt ...)
2371 //
2372
2373 // qDebug() << "Figure out an extension: ";
2374 QString lastExtension = m_extension;
2375 m_extension.clear();
2376
2377 // Automatically Select Extension is only valid if the user is _saving_ a _file_
2378 if ((m_operationMode == KFileWidget::Saving) && (m_ops->mode() & KFile::File)) {
2379 //
2380 // Get an extension from the filter
2381 //
2382
2383 KFileFilter fileFilter = m_filterWidget->currentFilter();
2384 if (!fileFilter.isEmpty()) {
2385 // if the currently selected filename already has an extension which
2386 // is also included in the currently allowed extensions, keep it
2387 // otherwise use the default extension
2388 QString currentExtension = db.suffixForFileName(fileName: locationEditCurrentText());
2389 if (currentExtension.isEmpty()) {
2390 currentExtension = locationEditCurrentText().section(asep: QLatin1Char('.'), astart: -1, aend: -1);
2391 }
2392 // qDebug() << "filter:" << filter << "locationEdit:" << locationEditCurrentText() << "currentExtension:" << currentExtension;
2393
2394 QString defaultExtension;
2395 QStringList extensionList;
2396
2397 // e.g. "*.cpp"
2398 if (!fileFilter.filePatterns().isEmpty()) {
2399 extensionList = fileFilter.filePatterns();
2400 defaultExtension = getExtensionFromPatternList(patternList: extensionList);
2401 }
2402 // e.g. "text/html"
2403 else if (!fileFilter.mimePatterns().isEmpty()) {
2404 QMimeType mime = db.mimeTypeForName(nameOrAlias: fileFilter.mimePatterns().first());
2405 if (mime.isValid()) {
2406 extensionList = mime.globPatterns();
2407 defaultExtension = mime.preferredSuffix();
2408 if (!defaultExtension.isEmpty()) {
2409 defaultExtension.prepend(c: QLatin1Char('.'));
2410 }
2411 }
2412 }
2413
2414 if ((!currentExtension.isEmpty() && extensionList.contains(t: QLatin1String("*.") + currentExtension))) {
2415 m_extension = QLatin1Char('.') + currentExtension;
2416 } else {
2417 m_extension = defaultExtension;
2418 }
2419
2420 // qDebug() << "List:" << extensionList << "auto-selected extension:" << m_extension;
2421 }
2422
2423 //
2424 // GUI: checkbox
2425 //
2426
2427 QString whatsThisExtension;
2428 if (!m_extension.isEmpty()) {
2429 // remember: sync any changes to the string with below
2430 m_autoSelectExtCheckBox->setText(i18n("Automatically select filename e&xtension (%1)", m_extension));
2431 whatsThisExtension = i18n("the extension <b>%1</b>", m_extension);
2432
2433 m_autoSelectExtCheckBox->setEnabled(true);
2434 m_autoSelectExtCheckBox->setChecked(m_autoSelectExtChecked);
2435 } else {
2436 // remember: sync any changes to the string with above
2437 m_autoSelectExtCheckBox->setText(i18n("Automatically select filename e&xtension"));
2438 whatsThisExtension = i18n("a suitable extension");
2439
2440 m_autoSelectExtCheckBox->setChecked(false);
2441 m_autoSelectExtCheckBox->setEnabled(false);
2442 }
2443
2444 const QString locationLabelText = stripUndisplayable(string: m_locationLabel->text());
2445 m_autoSelectExtCheckBox->setWhatsThis(QLatin1String("<qt>")
2446 + i18n("This option enables some convenient features for "
2447 "saving files with extensions:<br />"
2448 "<ol>"
2449 "<li>Any extension specified in the <b>%1</b> text "
2450 "area will be updated if you change the file type "
2451 "to save in.<br />"
2452 "<br /></li>"
2453 "<li>If no extension is specified in the <b>%2</b> "
2454 "text area when you click "
2455 "<b>Save</b>, %3 will be added to the end of the "
2456 "filename (if the filename does not already exist). "
2457 "This extension is based on the file type that you "
2458 "have chosen to save in.<br />"
2459 "<br />"
2460 "If you do not want KDE to supply an extension for the "
2461 "filename, you can either turn this option off or you "
2462 "can suppress it by adding a period (.) to the end of "
2463 "the filename (the period will be automatically "
2464 "removed)."
2465 "</li>"
2466 "</ol>"
2467 "If unsure, keep this option enabled as it makes your "
2468 "files more manageable.",
2469 locationLabelText,
2470 locationLabelText,
2471 whatsThisExtension)
2472 + QLatin1String("</qt>"));
2473
2474 m_autoSelectExtCheckBox->show();
2475
2476 // update the current filename's extension
2477 updateLocationEditExtension(lastExtension);
2478 }
2479 // Automatically Select Extension not valid
2480 else {
2481 m_autoSelectExtCheckBox->setChecked(false);
2482 m_autoSelectExtCheckBox->hide();
2483 }
2484}
2485
2486// Updates the extension of the filename specified in d->m_locationEdit if the
2487// Automatically Select Extension feature is enabled.
2488// (this prevents you from accidentally saving "file.kwd" as RTF, for example)
2489void KFileWidgetPrivate::updateLocationEditExtension(const QString &lastExtension)
2490{
2491 if (!m_autoSelectExtCheckBox->isChecked() || m_extension.isEmpty()) {
2492 return;
2493 }
2494
2495 const QString urlStr = locationEditCurrentText();
2496 if (urlStr.isEmpty()) {
2497 return;
2498 }
2499
2500 const int fileNameOffset = urlStr.lastIndexOf(c: QLatin1Char('/')) + 1;
2501 QStringView fileName = QStringView(urlStr).mid(pos: fileNameOffset);
2502
2503 const int dot = fileName.lastIndexOf(c: QLatin1Char('.'));
2504 const int len = fileName.length();
2505 if (dot > 0 && // has an extension already and it's not a hidden file
2506 // like ".hidden" (but we do accept ".hidden.ext")
2507 dot != len - 1 // and not deliberately suppressing extension
2508 ) {
2509 const QUrl url = getCompleteUrl(url: urlStr);
2510 // qDebug() << "updateLocationEditExtension (" << url << ")";
2511 // exists?
2512 KIO::StatJob *statJob = KIO::stat(url, flags: KIO::HideProgressInfo);
2513 KJobWidgets::setWindow(job: statJob, widget: q);
2514 bool result = statJob->exec();
2515 if (result) {
2516 // qDebug() << "\tfile exists";
2517
2518 if (statJob->statResult().isDir()) {
2519 // qDebug() << "\tisDir - won't alter extension";
2520 return;
2521 }
2522
2523 // --- fall through ---
2524 }
2525
2526 //
2527 // try to get rid of the current extension
2528 //
2529
2530 // catch "double extensions" like ".tar.gz"
2531 if (!lastExtension.isEmpty() && fileName.endsWith(s: lastExtension)) {
2532 fileName.chop(n: lastExtension.length());
2533 } else if (!m_extension.isEmpty() && fileName.endsWith(s: m_extension)) {
2534 fileName.chop(n: m_extension.length());
2535 } else { // can only handle "single extensions"
2536 fileName.truncate(n: dot);
2537 }
2538
2539 // add extension
2540 const QString newText = QStringView(urlStr).left(n: fileNameOffset) + fileName + m_extension;
2541 if (newText != locationEditCurrentText()) {
2542 const int idx = m_locationEdit->currentIndex();
2543 if (idx == -1) {
2544 m_locationEdit->lineEdit()->selectAll();
2545 m_locationEdit->lineEdit()->insert(newText);
2546 } else {
2547 m_locationEdit->setItemText(index: idx, text: newText);
2548 }
2549 m_locationEdit->lineEdit()->setModified(true);
2550 }
2551 }
2552}
2553
2554QString KFileWidgetPrivate::findMatchingFilter(const QString &filter, const QString &filename) const
2555{
2556 // e.g.: '*.foo *.bar|Foo type' -> '*.foo', '*.bar'
2557 const QStringList patterns = filter.left(n: filter.indexOf(ch: QLatin1Char('|'))).split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts);
2558
2559 QRegularExpression rx;
2560 for (const QString &p : patterns) {
2561 rx.setPattern(QRegularExpression::wildcardToRegularExpression(str: p));
2562 if (rx.match(subject: filename).hasMatch()) {
2563 return p;
2564 }
2565 }
2566 return QString();
2567}
2568
2569// Updates the filter if the extension of the filename specified in d->m_locationEdit is changed
2570// (this prevents you from accidentally saving "file.kwd" as RTF, for example)
2571void KFileWidgetPrivate::updateFilter()
2572{
2573 if ((m_operationMode == KFileWidget::Saving) && (m_ops->mode() & KFile::File)) {
2574 QString urlStr = locationEditCurrentText();
2575 if (urlStr.isEmpty()) {
2576 return;
2577 }
2578
2579 QMimeDatabase db;
2580 QMimeType urlMimeType = db.mimeTypeForFile(fileName: urlStr, mode: QMimeDatabase::MatchExtension);
2581
2582 bool matchesCurrentFilter = [this, urlMimeType, urlStr] {
2583 const KFileFilter filter = m_filterWidget->currentFilter();
2584 if (filter.mimePatterns().contains(str: urlMimeType.name())) {
2585 return true;
2586 }
2587
2588 QString filename = urlStr.mid(position: urlStr.lastIndexOf(c: QLatin1Char('/')) + 1); // only filename
2589
2590 const auto filePatterns = filter.filePatterns();
2591 const bool hasMatch = std::any_of(first: filePatterns.cbegin(), last: filePatterns.cend(), pred: [filename](const QString &pattern) {
2592 QRegularExpression rx(QRegularExpression::wildcardToRegularExpression(str: pattern));
2593
2594 return rx.match(subject: filename).hasMatch();
2595 });
2596 return hasMatch;
2597 }();
2598
2599 if (matchesCurrentFilter) {
2600 return;
2601 }
2602
2603 const auto filters = m_filterWidget->filters();
2604
2605 auto filterIt = std::find_if(first: filters.cbegin(), last: filters.cend(), pred: [urlStr, urlMimeType](const KFileFilter &filter) {
2606 if (filter.mimePatterns().contains(str: urlMimeType.name())) {
2607 return true;
2608 }
2609
2610 QString filename = urlStr.mid(position: urlStr.lastIndexOf(c: QLatin1Char('/')) + 1); // only filename
2611 // accept any match to honor the user's selection; see later code handling the "*" match
2612
2613 const auto filePatterns = filter.filePatterns();
2614 const bool hasMatch = std::any_of(first: filePatterns.cbegin(), last: filePatterns.cend(), pred: [filename](const QString &pattern) {
2615 // never match the catch-all filter
2616 if (pattern == QLatin1String("*")) {
2617 return false;
2618 }
2619
2620 QRegularExpression rx(QRegularExpression::wildcardToRegularExpression(str: pattern));
2621
2622 return rx.match(subject: filename).hasMatch();
2623 });
2624
2625 return hasMatch;
2626 });
2627
2628 if (filterIt != filters.cend()) {
2629 m_filterWidget->setCurrentFilter(*filterIt);
2630 }
2631 }
2632}
2633
2634// applies only to a file that doesn't already exist
2635void KFileWidgetPrivate::appendExtension(QUrl &url)
2636{
2637 // qDebug();
2638
2639 if (!m_autoSelectExtCheckBox->isChecked() || m_extension.isEmpty()) {
2640 return;
2641 }
2642
2643 QString fileName = url.fileName();
2644 if (fileName.isEmpty()) {
2645 return;
2646 }
2647
2648 // qDebug() << "appendExtension(" << url << ")";
2649
2650 const int len = fileName.length();
2651 const int dot = fileName.lastIndexOf(c: QLatin1Char('.'));
2652
2653 const bool suppressExtension = (dot == len - 1);
2654 const bool unspecifiedExtension = !fileName.endsWith(s: m_extension);
2655
2656 // don't KIO::Stat if unnecessary
2657 if (!(suppressExtension || unspecifiedExtension)) {
2658 return;
2659 }
2660
2661 // exists?
2662 KIO::StatJob *statJob = KIO::stat(url, flags: KIO::HideProgressInfo);
2663 KJobWidgets::setWindow(job: statJob, widget: q);
2664 bool res = statJob->exec();
2665 if (res) {
2666 // qDebug() << "\tfile exists - won't append extension";
2667 return;
2668 }
2669
2670 // suppress automatically append extension?
2671 if (suppressExtension) {
2672 //
2673 // Strip trailing dot
2674 // This allows lazy people to have m_autoSelectExtCheckBox->isChecked
2675 // but don't want a file extension to be appended
2676 // e.g. "README." will make a file called "README"
2677 //
2678 // If you really want a name like "README.", then type "README.."
2679 // and the trailing dot will be removed (or just stop being lazy and
2680 // turn off this feature so that you can type "README.")
2681 //
2682 // qDebug() << "\tstrip trailing dot";
2683 QString path = url.path();
2684 path.chop(n: 1);
2685 url.setPath(path);
2686 }
2687 // evilmatically append extension :) if the user hasn't specified one
2688 else if (unspecifiedExtension) {
2689 // qDebug() << "\tappending extension \'" << m_extension << "\'...";
2690 url = url.adjusted(options: QUrl::RemoveFilename); // keeps trailing slash
2691 url.setPath(path: url.path() + fileName + m_extension);
2692 // qDebug() << "\tsaving as \'" << url << "\'";
2693 }
2694}
2695
2696// adds the selected files/urls to 'recent documents'
2697void KFileWidgetPrivate::addToRecentDocuments()
2698{
2699 int m = m_ops->mode();
2700 int atmost = KRecentDocument::maximumItems();
2701 // don't add more than we need. KRecentDocument::add() is pretty slow
2702
2703 if (m & KFile::LocalOnly) {
2704 const QStringList files = q->selectedFiles();
2705 QStringList::ConstIterator it = files.begin();
2706 for (; it != files.end() && atmost > 0; ++it) {
2707 KRecentDocument::add(url: QUrl::fromLocalFile(localfile: *it));
2708 atmost--;
2709 }
2710 }
2711
2712 else { // urls
2713 const QList<QUrl> urls = q->selectedUrls();
2714 QList<QUrl>::ConstIterator it = urls.begin();
2715 for (; it != urls.end() && atmost > 0; ++it) {
2716 if ((*it).isValid()) {
2717 KRecentDocument::add(url: *it);
2718 atmost--;
2719 }
2720 }
2721 }
2722}
2723
2724KUrlComboBox *KFileWidget::locationEdit() const
2725{
2726 return d->m_locationEdit;
2727}
2728
2729KFileFilterCombo *KFileWidget::filterWidget() const
2730{
2731 return d->m_filterWidget;
2732}
2733
2734void KFileWidgetPrivate::togglePlacesPanel(bool show, QObject *sender)
2735{
2736 if (show) {
2737 initPlacesPanel();
2738 m_placesDock->show();
2739
2740 // check to see if they have a home item defined, if not show the home button
2741 QUrl homeURL;
2742 homeURL.setPath(path: QDir::homePath());
2743 KFilePlacesModel *model = static_cast<KFilePlacesModel *>(m_placesView->model());
2744 for (int rowIndex = 0; rowIndex < model->rowCount(); rowIndex++) {
2745 QModelIndex index = model->index(row: rowIndex, column: 0);
2746 QUrl url = model->url(index);
2747
2748 if (homeURL.matches(url, options: QUrl::StripTrailingSlash)) {
2749 m_toolbar->removeAction(action: m_ops->action(action: KDirOperator::Home));
2750 break;
2751 }
2752 }
2753 } else {
2754 if (sender == m_placesDock && m_placesDock && m_placesDock->isVisibleTo(q)) {
2755 // we didn't *really* go away! the dialog was simply hidden or
2756 // we changed virtual desktops or ...
2757 return;
2758 }
2759
2760 if (m_placesDock) {
2761 m_placesDock->hide();
2762 }
2763
2764 QAction *homeAction = m_ops->action(action: KDirOperator::Home);
2765 QAction *reloadAction = m_ops->action(action: KDirOperator::Reload);
2766 if (!m_toolbar->actions().contains(t: homeAction)) {
2767 m_toolbar->insertAction(before: reloadAction, action: homeAction);
2768 }
2769 }
2770
2771 m_togglePlacesPanelAction->setChecked(show);
2772
2773 // if we don't show the places panel, at least show the places menu
2774 m_urlNavigator->setPlacesSelectorVisible(!show);
2775}
2776
2777void KFileWidgetPrivate::setQuickFilterVisible(bool show)
2778{
2779 if (m_quickFilter->isVisible() == show) {
2780 return;
2781 }
2782 m_quickFilter->setVisible(show);
2783 m_filterWidget->setEnabled(!show);
2784 if (show) {
2785 m_quickFilterEdit->setFocus();
2786 } else {
2787 m_quickFilterEdit->clear();
2788 }
2789 m_quickFilterLock->setChecked(false);
2790 m_ops->dirLister()->setQuickFilterMode(show);
2791 m_toggleQuickFilterAction->setChecked(show);
2792}
2793
2794// static, overloaded
2795QUrl KFileWidget::getStartUrl(const QUrl &startDir, QString &recentDirClass)
2796{
2797 QString fileName; // result discarded
2798 return getStartUrl(startDir, recentDirClass, fileName);
2799}
2800
2801// static, overloaded
2802QUrl KFileWidget::getStartUrl(const QUrl &startDir, QString &recentDirClass, QString &fileName)
2803{
2804 recentDirClass.clear();
2805 fileName.clear();
2806 QUrl ret;
2807
2808 bool useDefaultStartDir = startDir.isEmpty();
2809 if (!useDefaultStartDir) {
2810 if (startDir.scheme() == QLatin1String("kfiledialog")) {
2811 // The startDir URL with this protocol may be in the format:
2812 // directory() fileName()
2813 // 1. kfiledialog:///keyword "/" keyword
2814 // 2. kfiledialog:///keyword?global "/" keyword
2815 // 3. kfiledialog:///keyword/ "/" keyword
2816 // 4. kfiledialog:///keyword/?global "/" keyword
2817 // 5. kfiledialog:///keyword/filename /keyword filename
2818 // 6. kfiledialog:///keyword/filename?global /keyword filename
2819
2820 QString keyword;
2821 QString urlDir = startDir.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash).path();
2822 QString urlFile = startDir.fileName();
2823 if (urlDir == QLatin1String("/")) { // '1'..'4' above
2824 keyword = urlFile;
2825 fileName.clear();
2826 } else { // '5' or '6' above
2827 keyword = urlDir.mid(position: 1);
2828 fileName = urlFile;
2829 }
2830
2831 const QLatin1String query(":%1");
2832 recentDirClass = query.arg(args&: keyword);
2833
2834 ret = QUrl::fromLocalFile(localfile: KRecentDirs::dir(fileClass: recentDirClass));
2835 } else { // not special "kfiledialog" URL
2836 ret = startDir;
2837 if (startDir.isLocalFile() && QDir::isRelativePath(path: startDir.path())) {
2838 ret = QUrl::fromLocalFile(localfile: QDir::currentPath() + u'/' + startDir.path());
2839 }
2840
2841 // "foo.png" only gives us a file name, the default start dir will be used.
2842 // "file:foo.png" (from KHTML/webkit, due to fromPath()) means the same
2843 // (and is the reason why we don't just use QUrl::isRelative()).
2844
2845 // In all other cases (startDir contains a directory path, or has no
2846 // fileName for us anyway, such as smb://), startDir is indeed a dir url.
2847 if (!ret.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash).path().isEmpty() || ret.fileName().isEmpty()) {
2848 // can use start directory
2849 // If we won't be able to list it (e.g. http), then use default
2850 if (!KProtocolManager::supportsListing(url: ret)) {
2851 useDefaultStartDir = true;
2852 fileName = startDir.fileName();
2853 }
2854 } else { // file name only
2855 fileName = startDir.fileName();
2856 useDefaultStartDir = true;
2857 }
2858 }
2859 }
2860
2861 if (useDefaultStartDir) {
2862 if (lastDirectory()->isEmpty()) {
2863 *lastDirectory() = QUrl::fromLocalFile(localfile: QStandardPaths::writableLocation(type: QStandardPaths::DocumentsLocation));
2864 const QUrl home(QUrl::fromLocalFile(localfile: QDir::homePath()));
2865 // if there is no docpath set (== home dir), we prefer the current
2866 // directory over it. We also prefer the homedir when our CWD is
2867 // different from our homedirectory or when the document dir
2868 // does not exist
2869 if (lastDirectory()->adjusted(options: QUrl::StripTrailingSlash) == home.adjusted(options: QUrl::StripTrailingSlash) //
2870 || QDir::currentPath() != QDir::homePath() //
2871 || !QDir(lastDirectory()->toLocalFile()).exists()) {
2872 *lastDirectory() = QUrl::fromLocalFile(localfile: QDir::currentPath());
2873 }
2874 }
2875 ret = *lastDirectory();
2876 }
2877
2878 // qDebug() << "for" << startDir << "->" << ret << "recentDirClass" << recentDirClass << "fileName" << fileName;
2879 return ret;
2880}
2881
2882void KFileWidget::setStartDir(const QUrl &directory)
2883{
2884 if (directory.isValid()) {
2885 *lastDirectory() = directory;
2886 }
2887}
2888
2889void KFileWidgetPrivate::setNonExtSelection()
2890{
2891 // Enhanced rename: Don't highlight the file extension.
2892 QString filename = locationEditCurrentText();
2893 QMimeDatabase db;
2894 QString extension = db.suffixForFileName(fileName: filename);
2895
2896 if (!extension.isEmpty()) {
2897 m_locationEdit->lineEdit()->setSelection(0, filename.length() - extension.length() - 1);
2898 } else {
2899 int lastDot = filename.lastIndexOf(c: QLatin1Char('.'));
2900 if (lastDot > 0) {
2901 m_locationEdit->lineEdit()->setSelection(0, lastDot);
2902 } else {
2903 m_locationEdit->lineEdit()->selectAll();
2904 }
2905 }
2906}
2907
2908// Sets the filter text to "File type" if the dialog is saving and a MIME type
2909// filter has been set; otherwise, the text is "Filter:"
2910void KFileWidgetPrivate::updateFilterText()
2911{
2912 QString label = i18n("&File type:");
2913 QString whatsThisText;
2914
2915 if (m_operationMode == KFileWidget::Saving && !m_filterWidget->currentFilter().mimePatterns().isEmpty()) {
2916 whatsThisText = i18n("<qt>This is the file type selector. It is used to select the format that the file will be saved as.</qt>");
2917 } else {
2918 whatsThisText = i18n("<qt>This is the file type selector. It is used to select the format of the files shown.</qt>");
2919 }
2920
2921 if (m_filterLabel) {
2922 m_filterLabel->setText(label);
2923 m_filterLabel->setWhatsThis(whatsThisText);
2924 }
2925 if (m_filterWidget) {
2926 m_filterWidget->setWhatsThis(whatsThisText);
2927 }
2928}
2929
2930void KFileWidget::setCustomWidget(QWidget *widget)
2931{
2932 delete d->m_bottomCustomWidget;
2933 d->m_bottomCustomWidget = widget;
2934
2935 // add it to the dialog, below the filter list box.
2936
2937 // Change the parent so that this widget is a child of the main widget
2938 d->m_bottomCustomWidget->setParent(this);
2939
2940 d->m_opsWidgetLayout->addWidget(d->m_bottomCustomWidget);
2941
2942 // FIXME: This should adjust the tab orders so that the custom widget
2943 // comes after the Cancel button. The code appears to do this, but the result
2944 // somehow screws up the tab order of the file path combo box. Not a major
2945 // problem, but ideally the tab order with a custom widget should be
2946 // the same as the order without one.
2947 setTabOrder(d->m_cancelButton, d->m_bottomCustomWidget);
2948 setTabOrder(d->m_bottomCustomWidget, d->m_urlNavigator);
2949}
2950
2951void KFileWidget::setCustomWidget(const QString &text, QWidget *widget)
2952{
2953 delete d->m_labeledCustomWidget;
2954 d->m_labeledCustomWidget = widget;
2955
2956 QLabel *label = new QLabel(text, this);
2957 label->setAlignment(Qt::AlignRight);
2958 d->m_lafBox->addRow(label, field: widget);
2959}
2960
2961KDirOperator *KFileWidget::dirOperator()
2962{
2963 return d->m_ops;
2964}
2965
2966#if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(6, 3)
2967void KFileWidget::readConfig(KConfigGroup &group)
2968{
2969 d->m_configGroup = group;
2970 d->readViewConfig();
2971 d->readRecentFiles();
2972}
2973#endif
2974
2975QString KFileWidgetPrivate::locationEditCurrentText() const
2976{
2977 return QDir::fromNativeSeparators(pathName: m_locationEdit->currentText());
2978}
2979
2980QUrl KFileWidgetPrivate::mostLocalUrl(const QUrl &url)
2981{
2982 if (url.isLocalFile()) {
2983 return url;
2984 }
2985
2986 KIO::StatJob *statJob = KIO::stat(url, flags: KIO::HideProgressInfo);
2987 KJobWidgets::setWindow(job: statJob, widget: q);
2988 bool res = statJob->exec();
2989
2990 if (!res) {
2991 return url;
2992 }
2993
2994 const QString path = statJob->statResult().stringValue(field: KIO::UDSEntry::UDS_LOCAL_PATH);
2995 if (!path.isEmpty()) {
2996 QUrl newUrl;
2997 newUrl.setPath(path);
2998 return newUrl;
2999 }
3000
3001 return url;
3002}
3003
3004void KFileWidgetPrivate::setInlinePreviewShown(bool show)
3005{
3006 m_ops->setInlinePreviewShown(show);
3007}
3008
3009void KFileWidget::setConfirmOverwrite(bool enable)
3010{
3011 d->m_confirmOverwrite = enable;
3012}
3013
3014void KFileWidget::setInlinePreviewShown(bool show)
3015{
3016 d->setInlinePreviewShown(show);
3017}
3018
3019QSize KFileWidget::dialogSizeHint() const
3020{
3021 int fontSize = fontMetrics().height();
3022 QSize goodSize(48 * fontSize, 30 * fontSize);
3023 const QSize scrnSize = d->screenSize();
3024 QSize minSize(scrnSize / 2);
3025 QSize maxSize(scrnSize * qreal(0.9));
3026 return (goodSize.expandedTo(otherSize: minSize).boundedTo(otherSize: maxSize));
3027}
3028
3029void KFileWidget::setViewMode(KFile::FileView mode)
3030{
3031 d->m_ops->setViewMode(mode);
3032 d->m_hasView = true;
3033}
3034
3035void KFileWidget::setSupportedSchemes(const QStringList &schemes)
3036{
3037 d->m_model->setSupportedSchemes(schemes);
3038 d->m_ops->setSupportedSchemes(schemes);
3039 d->m_urlNavigator->setSupportedSchemes(schemes);
3040}
3041
3042QStringList KFileWidget::supportedSchemes() const
3043{
3044 return d->m_model->supportedSchemes();
3045}
3046
3047#include "moc_kfilewidget.cpp"
3048

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