KIO

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

KDE's Doxygen guidelines are available online.