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 const bool modified = m_locationEdit->lineEdit()->isModified();
904
905 if (!(m_ops->mode() & KFile::Files)) {
906 if (i.isNull()) {
907 if (!modified) {
908 setLocationText(QUrl());
909 }
910 return;
911 }
912
913 m_url = i.url();
914
915 if (!m_locationEdit->hasFocus()) { // don't disturb while editing
916 setLocationText(m_url);
917 }
918
919 Q_EMIT q->fileHighlighted(m_url);
920 } else {
921 multiSelectionChanged();
922 Q_EMIT q->selectionChanged();
923 }
924
925 m_locationEdit->lineEdit()->setModified(false);
926
927 // When saving, and when double-click mode is being used, highlight the
928 // filename after a file is single-clicked so the user has a chance to quickly
929 // rename it if desired
930 // Note that double-clicking will override this and overwrite regardless of
931 // single/double click mouse setting (see slotViewDoubleClicked() )
932 if (!isKeyNavigation && m_operationMode == KFileWidget::Saving) {
933 m_locationEdit->setFocus();
934 }
935}
936
937void KFileWidgetPrivate::fileSelected(const KFileItem &i)
938{
939 if (!i.isNull() && i.isDir()) {
940 return;
941 }
942
943 if (!(m_ops->mode() & KFile::Files)) {
944 if (i.isNull()) {
945 setLocationText(QUrl());
946 return;
947 }
948 setLocationText(i.targetUrl());
949 } else {
950 multiSelectionChanged();
951 Q_EMIT q->selectionChanged();
952 }
953
954 // Same as above in fileHighlighted(), but for single-click mode
955 if (m_operationMode == KFileWidget::Saving) {
956 m_locationEdit->setFocus();
957 } else {
958 q->slotOk();
959 }
960}
961
962// I know it's slow to always iterate thru the whole filelist
963// (d->m_ops->selectedItems()), but what can we do?
964void KFileWidgetPrivate::multiSelectionChanged()
965{
966 if (m_locationEdit->hasFocus() && !m_locationEdit->currentText().isEmpty()) { // don't disturb
967 return;
968 }
969
970 const KFileItemList list = m_ops->selectedItems();
971
972 if (list.isEmpty()) {
973 setLocationText(QUrl());
974 return;
975 }
976
977 // Allow single folder selection, so user can click "Open" to open it
978 if (list.length() == 1 && list.first().isDir()) {
979 setLocationText(list.first().targetUrl());
980 return;
981 }
982 // Remove any selected folders from the locations
983 QList<QUrl> urlList;
984 for (const auto &item : list) {
985 if (!item.isDir()) {
986 urlList.append(item.targetUrl());
987 }
988 }
989 setLocationText(urlList);
990}
991
992void KFileWidgetPrivate::setLocationText(const QUrl &url)
993{
994 // fileHighlighed and fileSelected will be called one after the other:
995 // avoid to set two times in a row the location text with the same name
996 // as this would insert spurious entries in the undo stack
997 if ((url.isEmpty() && m_locationEdit->lineEdit()->text().isEmpty()) || m_locationEdit->lineEdit()->text() == escapeDoubleQuotes(url.fileName())) {
998 return;
999 }
1000 // Block m_locationEdit signals as setCurrentItem() will cause textChanged() to get
1001 // emitted, so slotLocationChanged() will be called. Make sure we don't clear the
1002 // KDirOperator's view-selection in there
1003 const QSignalBlocker blocker(m_locationEdit);
1004
1005 if (!url.isEmpty()) {
1006 if (!url.isRelative()) {
1007 const QUrl directory = url.adjusted(QUrl::RemoveFilename);
1008 if (!directory.path().isEmpty()) {
1009 q->setUrl(directory, false);
1010 } else {
1011 q->setUrl(url, false);
1012 }
1013 }
1014 m_locationEdit->lineEdit()->selectAll();
1015 m_locationEdit->lineEdit()->insert(escapeDoubleQuotes(url.fileName()));
1016 } else if (!m_locationEdit->lineEdit()->text().isEmpty()) {
1017 m_locationEdit->clearEditText();
1018 }
1019
1020 if (m_operationMode == KFileWidget::Saving) {
1021 setNonExtSelection();
1022 }
1023}
1024
1025static QString relativePathOrUrl(const QUrl &baseUrl, const QUrl &url)
1026{
1027 if (baseUrl.isParentOf(url)) {
1028 const QString basePath(QDir::cleanPath(baseUrl.path()));
1029 QString relPath(QDir::cleanPath(url.path()));
1030 relPath.remove(0, basePath.length());
1031 if (relPath.startsWith(QLatin1Char('/'))) {
1032 relPath.remove(0, 1);
1033 }
1034 return relPath;
1035 } else {
1036 return url.toDisplayString();
1037 }
1038}
1039
1040static QString escapeDoubleQuotes(QString &&path)
1041{
1042 // First escape the escape character that we are using
1043 path.replace(QStringLiteral("\\"), QStringLiteral("\\\\"));
1044 // Second, escape the quotes
1045 path.replace(QStringLiteral("\""), QStringLiteral("\\\""));
1046 return path;
1047}
1048
1049void KFileWidgetPrivate::initDirOpWidgets()
1050{
1051 m_opsWidget = new QWidget(q);
1052 m_opsWidgetLayout = new QVBoxLayout(m_opsWidget);
1053 m_opsWidgetLayout->setContentsMargins(0, 0, 0, 0);
1054 m_opsWidgetLayout->setSpacing(0);
1055
1056 m_model = new KFilePlacesModel(q);
1057
1058 // Don't pass "startDir" (KFileWidget constructor 1st arg) to the
1059 // KUrlNavigator at this stage: it may also contain a file name which
1060 // should not get inserted in that form into the old-style navigation
1061 // bar history. Wait until the KIO::stat has been done later.
1062 //
1063 // The stat cannot be done before this point, bug 172678.
1064 m_urlNavigator = new KUrlNavigator(m_model, QUrl(), m_opsWidget); // d->m_toolbar);
1065 m_urlNavigator->setPlacesSelectorVisible(false);
1066
1067 // Add the urlNavigator inside a widget to give it proper padding
1068 const auto navWidget = new QWidget(m_opsWidget);
1069 const auto navLayout = new QHBoxLayout(navWidget);
1070 navLayout->addWidget(m_urlNavigator);
1071 navLayout->setContentsMargins(q->style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
1072 0,
1073 q->style()->pixelMetric(QStyle::PM_LayoutRightMargin),
1074 q->style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
1075
1076 m_messageWidget = new KMessageWidget(q);
1077 m_messageWidget->setMessageType(KMessageWidget::Error);
1078 m_messageWidget->setWordWrap(true);
1079 m_messageWidget->hide();
1080
1081 auto topSeparator = new QFrame(q);
1082 topSeparator->setFrameStyle(QFrame::HLine);
1083
1084 m_ops = new KDirOperator(QUrl(), m_opsWidget);
1085 m_ops->installEventFilter(q);
1086 m_ops->setObjectName(QStringLiteral("KFileWidget::ops"));
1087 m_ops->setIsSaving(m_operationMode == KFileWidget::Saving);
1088 m_ops->setNewFileMenuSelectDirWhenAlreadyExist(true);
1089 m_ops->showOpenWithActions(true);
1090 m_ops->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1091
1092 auto bottomSparator = new QFrame(q);
1093 bottomSparator->setFrameStyle(QFrame::HLine);
1094
1095 q->connect(m_ops, &KDirOperator::urlEntered, q, [this](const QUrl &url) {
1096 urlEntered(url);
1097 });
1098 q->connect(m_ops, &KDirOperator::fileHighlighted, q, [this](const KFileItem &item, bool isKeyNavigation) {
1099 fileHighlighted(item, isKeyNavigation);
1100 });
1101 q->connect(m_ops, &KDirOperator::fileSelected, q, [this](const KFileItem &item) {
1102 fileSelected(item);
1103 });
1104 q->connect(m_ops, &KDirOperator::finishedLoading, q, [this]() {
1105 slotLoadingFinished();
1106 });
1107 q->connect(m_ops, &KDirOperator::keyEnterReturnPressed, q, [this]() {
1108 slotViewKeyEnterReturnPressed();
1109 });
1110 q->connect(m_ops, &KDirOperator::renamingFinished, q, [this](const QList<QUrl> &urls) {
1111 // Update file names in location text field after renaming selected files
1112 q->setSelectedUrls(urls);
1113 });
1114
1115 q->connect(m_ops, &KDirOperator::viewChanged, q, [](QAbstractItemView *newView) {
1116 newView->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::TopEdge | Qt::BottomEdge}));
1117 });
1118
1119 m_ops->dirLister()->setAutoErrorHandlingEnabled(false);
1120 q->connect(m_ops->dirLister(), &KDirLister::jobError, q, [this](KIO::Job *job) {
1121 m_messageWidget->setText(job->errorString());
1122 m_messageWidget->animatedShow();
1123 });
1124
1125 m_ops->setupMenu(KDirOperator::SortActions | KDirOperator::FileActions | KDirOperator::ViewActions);
1126
1127 initToolbar();
1128
1129 m_opsWidgetLayout->addWidget(m_toolbar);
1130 m_opsWidgetLayout->addWidget(navWidget);
1131 m_opsWidgetLayout->addWidget(m_messageWidget);
1132 m_opsWidgetLayout->addWidget(topSeparator);
1133 m_opsWidgetLayout->addWidget(m_ops);
1134 m_opsWidgetLayout->addWidget(bottomSparator);
1135}
1136
1137void KFileWidgetPrivate::initZoomWidget()
1138{
1139 m_iconSizeSlider = new QSlider(q);
1140 m_iconSizeSlider->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
1141 m_iconSizeSlider->setMinimumWidth(40);
1142 m_iconSizeSlider->setOrientation(Qt::Horizontal);
1143 m_iconSizeSlider->setMinimum(0);
1144 m_iconSizeSlider->setMaximum(m_stdIconSizes.size() - 1);
1145 m_iconSizeSlider->setSingleStep(1);
1146 m_iconSizeSlider->setPageStep(1);
1147 m_iconSizeSlider->setTickPosition(QSlider::TicksBelow);
1148
1149 q->connect(m_iconSizeSlider, &QAbstractSlider::valueChanged, q, [this](int step) {
1150 slotIconSizeChanged(m_stdIconSizes[step]);
1151 });
1152
1153 q->connect(m_iconSizeSlider, &QAbstractSlider::sliderMoved, q, [this](int step) {
1154 slotIconSizeSliderMoved(m_stdIconSizes[step]);
1155 });
1156
1157 q->connect(m_ops, &KDirOperator::currentIconSizeChanged, q, [this](int iconSize) {
1158 slotDirOpIconSizeChanged(iconSize);
1159 });
1160
1161 m_zoomOutAction = KStandardActions::create(
1163 q,
1164 [this]() {
1165 changeIconsSize(ZoomOut);
1166 },
1167 q);
1168
1169 q->addAction(m_zoomOutAction);
1170
1171 m_zoomInAction = KStandardActions::create(
1173 q,
1174 [this]() {
1175 changeIconsSize(ZoomIn);
1176 },
1177 q);
1178
1179 q->addAction(m_zoomInAction);
1180}
1181
1182void KFileWidgetPrivate::initToolbar()
1183{
1184 m_toolbar = new QToolBar(m_opsWidget);
1185 m_toolbar->setObjectName(QStringLiteral("KFileWidget::toolbar"));
1186 m_toolbar->setMovable(false);
1187
1188 // add nav items to the toolbar
1189 //
1190 // NOTE: The order of the button icons here differs from that
1191 // found in the file manager and web browser, but has been discussed
1192 // and agreed upon on the kde-core-devel mailing list:
1193 //
1194 // http://lists.kde.org/?l=kde-core-devel&m=116888382514090&w=2
1195
1196 m_ops->action(KDirOperator::Up)
1197 ->setWhatsThis(i18n("<qt>Click this button to enter the parent folder.<br /><br />"
1198 "For instance, if the current location is file:/home/konqi clicking this "
1199 "button will take you to file:/home.</qt>"));
1200
1201 m_ops->action(KDirOperator::Back)->setWhatsThis(i18n("Click this button to move backwards one step in the browsing history."));
1202 m_ops->action(KDirOperator::Forward)->setWhatsThis(i18n("Click this button to move forward one step in the browsing history."));
1203
1204 m_ops->action(KDirOperator::Reload)->setWhatsThis(i18n("Click this button to reload the contents of the current location."));
1205 m_ops->action(KDirOperator::NewFolder)->setShortcuts(KStandardShortcut::createFolder());
1206 m_ops->action(KDirOperator::NewFolder)->setWhatsThis(i18n("Click this button to create a new folder."));
1207
1208 m_togglePlacesPanelAction = new KToggleAction(i18n("Show Places Panel"), q);
1209 q->addAction(m_togglePlacesPanelAction);
1210 m_togglePlacesPanelAction->setShortcut(QKeySequence(Qt::Key_F9));
1211 q->connect(m_togglePlacesPanelAction, &QAction::toggled, q, [this](bool show) {
1212 togglePlacesPanel(show);
1213 });
1214
1215 m_toggleBookmarksAction = new KToggleAction(i18n("Show Bookmarks Button"), q);
1216 q->addAction(m_toggleBookmarksAction);
1217 q->connect(m_toggleBookmarksAction, &QAction::toggled, q, [this](bool show) {
1218 toggleBookmarks(show);
1219 });
1220
1221 m_toggleQuickFilterAction = new KToggleAction(i18n("Show Quick Filter"), q);
1222 q->addAction(m_toggleQuickFilterAction);
1223 m_toggleQuickFilterAction->setShortcuts(QList{QKeySequence(Qt::CTRL | Qt::Key_I), QKeySequence(Qt::Key_Backslash)});
1224 q->connect(m_toggleQuickFilterAction, &QAction::toggled, q, [this](bool show) {
1225 setQuickFilterVisible(show);
1226 });
1227
1228 // Build the settings menu
1229 KActionMenu *menu = new KActionMenu(QIcon::fromTheme(QStringLiteral("configure")), i18n("Options"), q);
1230 q->addAction(menu);
1231 menu->setWhatsThis(
1232 i18n("<qt>This is the preferences menu for the file dialog. "
1233 "Various options can be accessed from this menu including: <ul>"
1234 "<li>how files are sorted in the list</li>"
1235 "<li>types of view, including icon and list</li>"
1236 "<li>showing of hidden files</li>"
1237 "<li>the Places panel</li>"
1238 "<li>file previews</li>"
1239 "<li>separating folders from files</li></ul></qt>"));
1240
1241 menu->addAction(m_ops->action(KDirOperator::AllowExpansionInDetailsView));
1242 menu->addSeparator();
1243 menu->addAction(m_ops->action(KDirOperator::ShowHiddenFiles));
1244 menu->addAction(m_togglePlacesPanelAction);
1245 menu->addAction(m_toggleQuickFilterAction);
1246 menu->addAction(m_toggleBookmarksAction);
1247 menu->addAction(m_ops->action(KDirOperator::ShowPreviewPanel));
1248
1251
1252 m_bookmarkButton = new KActionMenu(QIcon::fromTheme(QStringLiteral("bookmarks")), i18n("Bookmarks"), q);
1253 m_bookmarkButton->setPopupMode(QToolButton::InstantPopup);
1254 q->addAction(m_bookmarkButton);
1255 m_bookmarkButton->setWhatsThis(
1256 i18n("<qt>This button allows you to bookmark specific locations. "
1257 "Click on this button to open the bookmark menu where you may add, "
1258 "edit or select a bookmark.<br /><br />"
1259 "These bookmarks are specific to the file dialog, but otherwise operate "
1260 "like bookmarks elsewhere in KDE.</qt>"));
1261
1262 QWidget *midSpacer = new QWidget(q);
1264
1265 m_toolbar->addAction(m_ops->action(KDirOperator::Back));
1266 m_toolbar->addAction(m_ops->action(KDirOperator::Forward));
1267 m_toolbar->addAction(m_ops->action(KDirOperator::Up));
1268 m_toolbar->addAction(m_ops->action(KDirOperator::Reload));
1269 m_toolbar->addSeparator();
1270 m_toolbar->addAction(m_ops->action(KDirOperator::ViewIconsView));
1271 m_toolbar->addAction(m_ops->action(KDirOperator::ViewCompactView));
1272 m_toolbar->addAction(m_ops->action(KDirOperator::ViewDetailsView));
1273 m_toolbar->addSeparator();
1274 m_toolbar->addAction(m_ops->action(KDirOperator::ShowPreview));
1275 m_toolbar->addAction(m_ops->action(KDirOperator::SortMenu));
1276 m_toolbar->addAction(m_bookmarkButton);
1277
1278 m_toolbar->addWidget(midSpacer);
1279
1280 initZoomWidget();
1281 m_toolbar->addAction(m_zoomOutAction);
1282 m_toolbar->addWidget(m_iconSizeSlider);
1283 m_toolbar->addAction(m_zoomInAction);
1284 m_toolbar->addSeparator();
1285
1286 m_toolbar->addAction(m_ops->action(KDirOperator::NewFolder));
1287 m_toolbar->addAction(menu);
1288
1289 m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
1290 m_toolbar->setMovable(false);
1291}
1292
1293void KFileWidgetPrivate::initLocationWidget()
1294{
1295 m_locationLabel = new QLabel(i18n("&Name:"), q);
1296 m_locationEdit = new KUrlComboBox(KUrlComboBox::Files, true, q);
1297 m_locationEdit->installEventFilter(q);
1298 // Properly let the dialog be resized (to smaller). Otherwise we could have
1299 // huge dialogs that can't be resized to smaller (it would be as big as the longest
1300 // item in this combo box). (ereslibre)
1301 m_locationEdit->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow);
1302 q->connect(m_locationEdit, &KUrlComboBox::editTextChanged, q, [this](const QString &text) {
1303 slotLocationChanged(text);
1304 });
1305
1306 // Only way to have the undo button before the clear button
1307 m_locationEdit->lineEdit()->setClearButtonEnabled(false);
1308
1309 QAction *clearAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), {}, m_locationEdit->lineEdit());
1310 m_locationEdit->lineEdit()->addAction(clearAction, QLineEdit::TrailingPosition);
1311 clearAction->setVisible(false);
1312 q->connect(clearAction, &QAction::triggered, m_locationEdit->lineEdit(), &QLineEdit::clear);
1313 q->connect(m_locationEdit->lineEdit(), &QLineEdit::textEdited, q, [this, clearAction]() {
1314 clearAction->setVisible(m_locationEdit->lineEdit()->text().length() > 0);
1315 });
1316 q->connect(m_locationEdit->lineEdit(), &QLineEdit::textChanged, q, [this](const QString &text) {
1317 m_okButton->setEnabled(!text.isEmpty());
1318 });
1319
1320 QAction *undoAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-undo")), i18nc("@info:tooltip", "Undo filename change"), m_locationEdit->lineEdit());
1321 m_locationEdit->lineEdit()->addAction(undoAction, QLineEdit::TrailingPosition);
1322 undoAction->setVisible(false);
1323 q->connect(undoAction, &QAction::triggered, m_locationEdit->lineEdit(), &QLineEdit::undo);
1324 q->connect(m_locationEdit->lineEdit(), &QLineEdit::textEdited, q, [this, undoAction]() {
1325 undoAction->setVisible(m_locationEdit->lineEdit()->isUndoAvailable());
1326 });
1327
1328 updateLocationWhatsThis();
1329 m_locationLabel->setBuddy(m_locationEdit);
1330
1331 KUrlCompletion *fileCompletionObj = new KUrlCompletion(KUrlCompletion::FileCompletion);
1332 m_locationEdit->setCompletionObject(fileCompletionObj);
1333 m_locationEdit->setAutoDeleteCompletionObject(true);
1334
1335 q->connect(m_locationEdit, &KUrlComboBox::returnPressed, q, [this](const QString &text) {
1336 locationAccepted(text);
1337 });
1338}
1339
1340void KFileWidgetPrivate::initFilterWidget()
1341{
1342 m_filterLabel = new QLabel(q);
1343 m_filterWidget = new KFileFilterCombo(q);
1344 m_filterWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
1345 updateFilterText();
1346 // Properly let the dialog be resized (to smaller). Otherwise we could have
1347 // huge dialogs that can't be resized to smaller (it would be as big as the longest
1348 // item in this combo box). (ereslibre)
1349 m_filterWidget->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow);
1350 m_filterLabel->setBuddy(m_filterWidget);
1351 q->connect(m_filterWidget, &KFileFilterCombo::filterChanged, q, [this]() {
1352 slotMimeFilterChanged();
1353 });
1354
1355 m_filterDelayTimer.setSingleShot(true);
1356 m_filterDelayTimer.setInterval(300);
1357 q->connect(m_filterWidget, &QComboBox::editTextChanged, &m_filterDelayTimer, qOverload<>(&QTimer::start));
1358 q->connect(&m_filterDelayTimer, &QTimer::timeout, q, [this]() {
1359 slotMimeFilterChanged();
1360 });
1361}
1362
1363void KFileWidgetPrivate::initQuickFilterWidget()
1364{
1365 m_quickFilter = new QWidget(q);
1366 // Lock is used for keeping filter open when changing folders
1367 m_quickFilterLock = new QToolButton(m_quickFilter);
1368 m_quickFilterLock->setAutoRaise(true);
1369 m_quickFilterLock->setCheckable(true);
1370 m_quickFilterLock->setIcon(QIcon::fromTheme(QStringLiteral("object-unlocked")));
1371 m_quickFilterLock->setToolTip(i18nc("@info:tooltip", "Keep Filter When Changing Folders"));
1372
1373 m_quickFilterEdit = new QLineEdit(m_quickFilter);
1374 m_quickFilterEdit->setClearButtonEnabled(true);
1375 m_quickFilterEdit->setPlaceholderText(i18n("Filter by name…"));
1376 QObject::connect(m_quickFilterEdit, &QLineEdit::textChanged, q, [this]() {
1377 slotQuickFilterChanged();
1378 });
1379
1380 m_quickFilterClose = new QToolButton(m_quickFilter);
1381 m_quickFilterClose->setAutoRaise(true);
1382 m_quickFilterClose->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
1383 m_quickFilterClose->setToolTip(i18nc("@info:tooltip", "Hide Filter Bar"));
1384 QObject::connect(m_quickFilterClose, &QToolButton::clicked, q, [this]() {
1385 setQuickFilterVisible(false);
1386 });
1387
1388 QHBoxLayout *hLayout = new QHBoxLayout(m_quickFilter);
1389 hLayout->setContentsMargins(0, 0, 0, 0);
1390 hLayout->addWidget(m_quickFilterLock);
1391 hLayout->addWidget(m_quickFilterEdit);
1392 hLayout->addWidget(m_quickFilterClose);
1393
1394 m_quickFilter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
1395 m_quickFilter->hide();
1396}
1397
1398void KFileWidgetPrivate::setLocationText(const QList<QUrl> &urlList)
1399{
1400 // Block m_locationEdit signals as setCurrentItem() will cause textChanged() to get
1401 // emitted, so slotLocationChanged() will be called. Make sure we don't clear the
1402 // KDirOperator's view-selection in there
1403 const QSignalBlocker blocker(m_locationEdit);
1404
1405 const QUrl baseUrl = m_ops->url();
1406
1407 if (urlList.count() > 1) {
1408 QString urls;
1409 for (const QUrl &url : urlList) {
1410 urls += QStringLiteral("\"%1\" ").arg(escapeDoubleQuotes(relativePathOrUrl(baseUrl, url)));
1411 }
1412 urls.chop(1);
1413 // Never use setEditText, because it forgets the undo history
1414 m_locationEdit->lineEdit()->selectAll();
1415 m_locationEdit->lineEdit()->insert(urls);
1416 } else if (urlList.count() == 1) {
1417 const auto url = urlList[0];
1418 m_locationEdit->lineEdit()->selectAll();
1419 m_locationEdit->lineEdit()->insert(escapeDoubleQuotes(relativePathOrUrl(baseUrl, url)));
1420 } else if (!m_locationEdit->lineEdit()->text().isEmpty()) {
1421 m_locationEdit->clearEditText();
1422 }
1423
1424 if (m_operationMode == KFileWidget::Saving) {
1425 setNonExtSelection();
1426 }
1427}
1428
1429void KFileWidgetPrivate::updateLocationWhatsThis()
1430{
1431 const QString autocompletionWhatsThisText = i18n(
1432 "<qt>While typing in the text area, you may be presented "
1433 "with possible matches. "
1434 "This feature can be controlled by clicking with the right mouse button "
1435 "and selecting a preferred mode from the <b>Text Completion</b> menu.</qt>");
1436
1437 QString whatsThisText;
1438 if (m_operationMode == KFileWidget::Saving) {
1439 whatsThisText = QLatin1String("<qt>") + i18n("This is the name to save the file as.") + autocompletionWhatsThisText;
1440 } else if (m_ops->mode() & KFile::Files) {
1441 whatsThisText = QLatin1String("<qt>")
1442 + i18n("This is the list of files to open. More than "
1443 "one file can be specified by listing several "
1444 "files, separated by spaces.")
1445 + autocompletionWhatsThisText;
1446 } else {
1447 whatsThisText = QLatin1String("<qt>") + i18n("This is the name of the file to open.") + autocompletionWhatsThisText;
1448 }
1449
1450 m_locationLabel->setWhatsThis(whatsThisText);
1451 m_locationEdit->setWhatsThis(whatsThisText);
1452}
1453
1454void KFileWidgetPrivate::initPlacesPanel()
1455{
1456 if (m_placesDock) {
1457 return;
1458 }
1459
1460 m_placesDock = new QDockWidget(i18nc("@title:window", "Places"), q);
1461 m_placesDock->setFeatures(QDockWidget::NoDockWidgetFeatures);
1462 m_placesDock->setTitleBarWidget(new KDEPrivate::KFileWidgetDockTitleBar(m_placesDock));
1463
1464 m_placesView = new KFilePlacesView(m_placesDock);
1465 m_placesView->setModel(m_model);
1466 m_placesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1467
1468 m_placesView->setObjectName(QStringLiteral("url bar"));
1469 QObject::connect(m_placesView, &KFilePlacesView::urlChanged, q, [this](const QUrl &url) {
1470 enterUrl(url);
1471 });
1472
1473 QObject::connect(qobject_cast<KFilePlacesModel *>(m_placesView->model()), &KFilePlacesModel::errorMessage, q, [this](const QString &errorMessage) {
1474 m_messageWidget->setText(errorMessage);
1475 m_messageWidget->animatedShow();
1476 });
1477
1478 // need to set the current url of the urlbar manually (not via urlEntered()
1479 // here, because the initial url of KDirOperator might be the same as the
1480 // one that will be set later (and then urlEntered() won't be emitted).
1481 // TODO: KDE5 ### REMOVE THIS when KDirOperator's initial URL (in the c'tor) is gone.
1482 m_placesView->setUrl(m_url);
1483
1484 m_placesDock->setWidget(m_placesView);
1485 m_placesViewSplitter->insertWidget(0, m_placesDock);
1486
1487 // initialize the size of the splitter
1488 m_placesViewWidth = m_configGroup.readEntry(SpeedbarWidth, m_placesView->sizeHint().width());
1489
1490 // Needed for when the dialog is shown with the places panel initially hidden
1491 setPlacesViewSplitterSizes();
1492
1493 QObject::connect(m_placesDock, &QDockWidget::visibilityChanged, q, [this](bool visible) {
1494 togglePlacesPanel(visible, m_placesDock);
1495 });
1496}
1497
1498void KFileWidgetPrivate::setPlacesViewSplitterSizes()
1499{
1500 if (m_placesViewWidth > 0) {
1501 QList<int> sizes = m_placesViewSplitter->sizes();
1502 sizes[0] = m_placesViewWidth;
1503 sizes[1] = q->width() - m_placesViewWidth - m_placesViewSplitter->handleWidth();
1504 m_placesViewSplitter->setSizes(sizes);
1505 }
1506}
1507
1508void KFileWidgetPrivate::initGUI()
1509{
1510 delete m_boxLayout; // deletes all sub layouts
1511
1512 m_boxLayout = new QVBoxLayout(q);
1513 m_boxLayout->setContentsMargins(0, 0, 0, 0); // no additional margin to the already existing
1514
1515 m_placesViewSplitter = new QSplitter(q);
1516 m_placesViewSplitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1517 m_placesViewSplitter->setChildrenCollapsible(false);
1518 m_boxLayout->addWidget(m_placesViewSplitter);
1519
1520 QObject::connect(m_placesViewSplitter, &QSplitter::splitterMoved, q, [this](int pos, int index) {
1521 placesViewSplitterMoved(pos, index);
1522 });
1523 m_placesViewSplitter->insertWidget(0, m_opsWidget);
1524
1525 m_lafBox = new QFormLayout();
1526 m_lafBox->setSpacing(q->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
1527 m_lafBox->setContentsMargins(q->style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
1528 q->style()->pixelMetric(QStyle::PM_LayoutTopMargin),
1529 q->style()->pixelMetric(QStyle::PM_LayoutRightMargin),
1530 0);
1531
1532 m_lafBox->addRow(m_quickFilter);
1533 m_lafBox->addRow(m_locationLabel, m_locationEdit);
1534 m_lafBox->addRow(m_filterLabel, m_filterWidget);
1535 // Add the "Automatically Select Extension" checkbox
1536 m_lafBox->addWidget(m_autoSelectExtCheckBox);
1537
1538 m_opsWidgetLayout->addLayout(m_lafBox);
1539
1540 auto hbox = new QHBoxLayout();
1541 hbox->setSpacing(q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
1542 hbox->setContentsMargins(q->style()->pixelMetric(QStyle::PM_LayoutTopMargin),
1543 q->style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
1544 q->style()->pixelMetric(QStyle::PM_LayoutRightMargin),
1545 q->style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
1546
1547 hbox->addStretch(2);
1548 hbox->addWidget(m_okButton);
1549 hbox->addWidget(m_cancelButton);
1550
1551 m_opsWidgetLayout->addLayout(hbox);
1552
1553 auto updateTabOrder = [this]() {
1554 // First the url navigator and its internal tab order
1555 q->setTabOrder(m_urlNavigator, m_ops);
1556 // Add the other elements in the ui that aren't int he toolbar
1557 q->setTabOrder(m_ops, m_autoSelectExtCheckBox);
1558 q->setTabOrder(m_autoSelectExtCheckBox, m_quickFilterLock);
1559 q->setTabOrder(m_quickFilterLock, m_quickFilterEdit);
1560 q->setTabOrder(m_quickFilterEdit, m_quickFilterClose);
1561 q->setTabOrder(m_quickFilterClose, m_locationEdit);
1562 q->setTabOrder(m_locationEdit, m_filterWidget);
1563 q->setTabOrder(m_filterWidget, m_okButton);
1564 q->setTabOrder(m_okButton, m_cancelButton);
1565 q->setTabOrder(m_cancelButton, m_placesView);
1566
1567 // Now add every widget in the toolbar
1568 const auto toolbarChildren = m_toolbar->children();
1569 QList<QWidget *> toolbarButtons;
1570 for (QObject *obj : std::as_const(toolbarChildren)) {
1571 if (auto *button = qobject_cast<QToolButton *>(obj)) {
1572 // Make toolbar buttons focusable only via tab
1573 button->setFocusPolicy(Qt::TabFocus);
1574 toolbarButtons << button;
1575 } else if (auto *slider = qobject_cast<QSlider *>(obj)) {
1576 toolbarButtons << slider;
1577 }
1578 }
1579
1580 q->setTabOrder(m_placesView, toolbarButtons.first());
1581
1582 auto it = toolbarButtons.constBegin();
1583 auto nextIt = ++toolbarButtons.constBegin();
1584 while (nextIt != toolbarButtons.constEnd()) {
1585 q->setTabOrder(*it, *nextIt);
1586 it++;
1587 nextIt++;
1588 }
1589 // Do not manually close the loop: it would break the chain
1590 };
1591 q->connect(m_urlNavigator, &KUrlNavigator::layoutChanged, q, updateTabOrder);
1592 updateTabOrder();
1593}
1594
1595void KFileWidgetPrivate::slotMimeFilterChanged()
1596{
1597 m_filterDelayTimer.stop();
1598
1599 KFileFilter filter = m_filterWidget->currentFilter();
1600
1601 m_ops->clearFilter();
1602
1603 if (!filter.mimePatterns().isEmpty()) {
1604 QStringList types = filter.mimePatterns();
1605 types.prepend(QStringLiteral("inode/directory"));
1606 m_ops->setMimeFilter(types);
1607 }
1608
1609 updateNameFilter(filter);
1610
1611 updateAutoSelectExtension();
1612
1613 m_ops->updateDir();
1614
1615 Q_EMIT q->filterChanged(filter);
1616}
1617
1618void KFileWidgetPrivate::slotQuickFilterChanged()
1619{
1620 m_filterDelayTimer.stop();
1621
1622 KFileFilter filter(QStringLiteral("quickFilter"), QStringList{m_quickFilterEdit->text()}, m_filterWidget->currentFilter().mimePatterns());
1623 m_ops->clearFilter();
1624 m_ops->setMimeFilter(filter.mimePatterns());
1625
1626 updateNameFilter(filter);
1627
1628 m_ops->updateDir();
1629
1630 Q_EMIT q->filterChanged(filter);
1631}
1632
1633void KFileWidgetPrivate::updateNameFilter(const KFileFilter &filter)
1634{
1635 const auto filePatterns = filter.filePatterns();
1636 const bool hasRegExSyntax = std::any_of(filePatterns.constBegin(), filePatterns.constEnd(), [](const QString &filter) {
1637 // Keep the filter.contains checks in sync with Dolphin: dolphin/src/kitemviews/private/kfileitemmodelfilter.cpp setPattern
1638 return filter.contains(QLatin1Char('*')) || filter.contains(QLatin1Char('?')) || filter.contains(QLatin1Char('['));
1639 });
1640
1641 if (hasRegExSyntax) {
1642 m_ops->setNameFilter(filter.filePatterns().join(QLatin1Char(' ')));
1643 } else {
1644 m_ops->setNameFilter(QLatin1Char('*') + filePatterns.join(QLatin1Char('*')) + QLatin1Char('*'));
1645 }
1646}
1647
1648void KFileWidget::setUrl(const QUrl &url, bool clearforward)
1649{
1650 // qDebug();
1651
1652 d->m_ops->setUrl(url, clearforward);
1653}
1654
1655// Protected
1656void KFileWidgetPrivate::urlEntered(const QUrl &url)
1657{
1658 // qDebug();
1659
1660 KUrlComboBox *pathCombo = m_urlNavigator->editor();
1661 if (pathCombo->count() != 0) { // little hack
1662 pathCombo->setUrl(url);
1663 }
1664
1665 bool blocked = m_locationEdit->blockSignals(true);
1666 if (m_keepLocation) {
1667 const QUrl currentUrl = urlFromString(locationEditCurrentText());
1668 // iconNameForUrl will get the icon or fallback to a generic one
1669 m_locationEdit->setItemIcon(0, QIcon::fromTheme(KIO::iconNameForUrl(currentUrl)));
1670 // Preserve the text when clicking on the view (cf fileHighlighted)
1671 m_locationEdit->lineEdit()->setModified(true);
1672 }
1673
1674 m_locationEdit->blockSignals(blocked);
1675
1676 m_urlNavigator->setLocationUrl(url);
1677
1678 // is triggered in ctor before completion object is set
1679 KUrlCompletion *completion = dynamic_cast<KUrlCompletion *>(m_locationEdit->completionObject());
1680 if (completion) {
1681 completion->setDir(url);
1682 }
1683
1684 if (m_placesView) {
1685 m_placesView->setUrl(url);
1686 }
1687
1688 m_messageWidget->hide();
1689}
1690
1691void KFileWidgetPrivate::locationAccepted(const QString &url)
1692{
1693 Q_UNUSED(url);
1694 // qDebug();
1695 q->slotOk();
1696}
1697
1698void KFileWidgetPrivate::enterUrl(const QUrl &url)
1699{
1700 // qDebug();
1701
1702 // append '/' if needed: url combo does not add it
1703 // tokenize() expects it because it uses QUrl::adjusted(QUrl::RemoveFilename)
1704 QUrl u(url);
1705 Utils::appendSlashToPath(u);
1706 q->setUrl(u);
1707
1708 // We need to check window()->focusWidget() instead of m_locationEdit->hasFocus
1709 // because when the window is showing up m_locationEdit
1710 // may still not have focus but it'll be the one that will have focus when the window
1711 // gets it and we don't want to steal its focus either
1712 if (q->window()->focusWidget() != m_locationEdit) {
1713 m_ops->setFocus();
1714 }
1715
1716 // Clear the quick filter if its not locked
1717 if (!m_quickFilterLock->isChecked()) {
1718 setQuickFilterVisible(false);
1719 }
1720}
1721
1722void KFileWidgetPrivate::enterUrl(const QString &url)
1723{
1724 // qDebug();
1725
1726 enterUrl(urlFromString(KUrlCompletion::replacedPath(url, true, true)));
1727}
1728
1729bool KFileWidgetPrivate::toOverwrite(const QUrl &url)
1730{
1731 // qDebug();
1732
1733 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
1734 KJobWidgets::setWindow(statJob, q);
1735 bool res = statJob->exec();
1736
1737 if (res) {
1739 i18n("The file \"%1\" already exists. Do you wish to overwrite it?", url.fileName()),
1740 i18n("Overwrite File?"),
1743 QString(),
1745
1746 if (ret != KMessageBox::Continue) {
1747 m_locationEdit->setFocus();
1748 setNonExtSelection();
1749
1750 return false;
1751 }
1752 return true;
1753 }
1754
1755 return true;
1756}
1757
1759{
1760 // Honor protocols that do not support directory listing
1761 if (!url.isRelative() && !KProtocolManager::supportsListing(url)) {
1762 return;
1763 }
1764 d->setLocationText(url);
1765}
1766
1768{
1769 if (urls.isEmpty()) {
1770 return;
1771 }
1772
1773 // Honor protocols that do not support directory listing
1774 if (!urls[0].isRelative() && !KProtocolManager::supportsListing(urls[0])) {
1775 return;
1776 }
1777 d->setLocationText(urls);
1778}
1779
1780void KFileWidgetPrivate::slotLoadingFinished()
1781{
1782 const QString currentText = m_locationEdit->currentText();
1783 if (currentText.isEmpty()) {
1784 return;
1785 }
1786
1787 m_ops->blockSignals(true);
1788 QUrl u(m_ops->url());
1789 if (currentText.startsWith(QLatin1Char('/'))) {
1790 u.setPath(currentText);
1791 } else {
1792 u.setPath(Utils::concatPaths(m_ops->url().path(), currentText));
1793 }
1794 m_ops->setCurrentItem(u);
1795 m_ops->blockSignals(false);
1796}
1797
1798void KFileWidgetPrivate::slotLocationChanged(const QString &text)
1799{
1800 // qDebug();
1801
1802 m_locationEdit->lineEdit()->setModified(true);
1803
1804 if (text.isEmpty() && m_ops->view()) {
1805 m_ops->view()->clearSelection();
1806 }
1807
1808 if (!m_locationEdit->lineEdit()->text().isEmpty()) {
1809 const QList<QUrl> urlList(tokenize(text));
1810 m_ops->setCurrentItems(urlList);
1811 }
1812
1813 updateFilter();
1814}
1815
1817{
1818 // qDebug();
1819
1820 if (d->m_inAccept) {
1821 return d->m_url;
1822 } else {
1823 return QUrl();
1824 }
1825}
1826
1828{
1829 // qDebug();
1830
1831 QList<QUrl> list;
1832 if (d->m_inAccept) {
1833 if (d->m_ops->mode() & KFile::Files) {
1834 list = d->m_urlList;
1835 } else {
1836 list.append(d->m_url);
1837 }
1838 }
1839 return list;
1840}
1841
1842QList<QUrl> KFileWidgetPrivate::tokenize(const QString &line) const
1843{
1844 qCDebug(KIO_KFILEWIDGETS_FW) << "Tokenizing:" << line;
1845
1846 QList<QUrl> urls;
1847 QUrl baseUrl(m_ops->url().adjusted(QUrl::RemoveFilename));
1848 Utils::appendSlashToPath(baseUrl);
1849
1850 // A helper that creates, validates and appends a new url based
1851 // on the given filename.
1852 auto addUrl = [baseUrl, &urls](const QString &partial_name) {
1853 if (partial_name.trimmed().isEmpty()) {
1854 return;
1855 }
1856
1857 // url could be absolute
1858 QUrl partial_url(partial_name);
1859 if (!partial_url.isValid()
1860 || partial_url.isRelative()
1861 // the text might look like a url scheme but not be a real one
1862 || (!partial_url.scheme().isEmpty() && (!partial_name.contains(QStringLiteral("://")) || !KProtocolInfo::isKnownProtocol(partial_url.scheme())))) {
1863 // We have to use setPath here, so that something like "test#file"
1864 // isn't interpreted to have path "test" and fragment "file".
1865 partial_url.clear();
1866 partial_url.setPath(partial_name);
1867 }
1868
1869 // This returns QUrl(partial_name) for absolute URLs.
1870 // Otherwise, returns the concatenated url.
1871 if (partial_url.isRelative() || baseUrl.isParentOf(partial_url)) {
1872 partial_url = baseUrl.resolved(partial_url);
1873 }
1874
1875 if (partial_url.isValid()) {
1876 urls.append(partial_url);
1877 } else {
1878 // This can happen in the first quote! (ex: ' "something here"')
1879 qCDebug(KIO_KFILEWIDGETS_FW) << "Discarding Invalid" << partial_url;
1880 }
1881 };
1882
1883 // An iterative approach here where we toggle the "escape" flag
1884 // if we hit `\`. If we hit `"` and the escape flag is false,
1885 // we split
1886 QString partial_name;
1887 bool escape = false;
1888 for (int i = 0; i < line.length(); i++) {
1889 const QChar ch = line[i];
1890
1891 // Handle any character previously escaped
1892 if (escape) {
1893 partial_name += ch;
1894 escape = false;
1895 continue;
1896 }
1897
1898 // Handle escape start
1899 if (ch.toLatin1() == '\\') {
1900 escape = true;
1901 continue;
1902 }
1903
1904 // Handle UNESCAPED quote (") since the above ifs are
1905 // dealing with the escaped ones
1906 // Ignore this in single-file mode
1907 if (ch.toLatin1() == '"' && q->mode() != KFile::Mode::File) {
1908 addUrl(partial_name);
1909 partial_name.clear();
1910 continue;
1911 }
1912
1913 // Any other character just append
1914 partial_name += ch;
1915 }
1916
1917 // Handle the last item which is buffered in partial_name. This is
1918 // required for single-file selection dialogs since the name will not
1919 // be wrapped in quotes
1920 if (!partial_name.isEmpty()) {
1921 addUrl(partial_name);
1922 partial_name.clear();
1923 }
1924
1925 return urls;
1926}
1927
1929{
1930 // qDebug();
1931
1932 if (d->m_inAccept) {
1933 const QUrl url = d->mostLocalUrl(d->m_url);
1934 if (url.isLocalFile()) {
1935 return url.toLocalFile();
1936 } else {
1937 KMessageBox::error(const_cast<KFileWidget *>(this), i18n("You can only select local files."), i18n("Remote Files Not Accepted"));
1938 }
1939 }
1940 return QString();
1941}
1942
1944{
1945 // qDebug();
1946
1947 QStringList list;
1948
1949 if (d->m_inAccept) {
1950 if (d->m_ops->mode() & KFile::Files) {
1951 const QList<QUrl> urls = d->m_urlList;
1952 for (const auto &u : urls) {
1953 const QUrl url = d->mostLocalUrl(u);
1954 if (url.isLocalFile()) {
1955 list.append(url.toLocalFile());
1956 }
1957 }
1958 }
1959
1960 else { // single-selection mode
1961 if (d->m_url.isLocalFile()) {
1962 list.append(d->m_url.toLocalFile());
1963 }
1964 }
1965 }
1966
1967 return list;
1968}
1969
1971{
1972 return d->m_ops->url();
1973}
1974
1975void KFileWidget::resizeEvent(QResizeEvent *event)
1976{
1978
1979 if (d->m_placesDock) {
1980 // we don't want our places dock actually changing size when we resize
1981 // and qt doesn't make it easy to enforce such a thing with QSplitter
1982 d->setPlacesViewSplitterSizes();
1983 }
1984}
1985
1986void KFileWidget::showEvent(QShowEvent *event)
1987{
1988 if (!d->m_hasView) { // delayed view-creation
1989 Q_ASSERT(d);
1990 Q_ASSERT(d->m_ops);
1991 d->m_ops->setViewMode(KFile::Default);
1992 d->m_hasView = true;
1993
1994 connect(d->m_ops->view(), &QAbstractItemView::doubleClicked, this, [this](const QModelIndex &index) {
1995 d->slotViewDoubleClicked(index);
1996 });
1997 }
1998 d->m_ops->clearHistory();
1999
2001}
2002
2003bool KFileWidget::eventFilter(QObject *watched, QEvent *event)
2004{
2005 const bool res = QWidget::eventFilter(watched, event);
2006
2007 QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>(event);
2008 if (!keyEvent) {
2009 return res;
2010 }
2011
2012 const auto type = event->type();
2013 const auto key = keyEvent->key();
2014
2015 if (watched == d->m_ops && type == QEvent::KeyPress && (key == Qt::Key_Return || key == Qt::Key_Enter)) {
2016 // ignore return events from the KDirOperator
2017 // they are not needed, activated is used to handle this case
2018 event->accept();
2019 return true;
2020 }
2021
2022 return res;
2023}
2024
2026{
2027 // qDebug();
2028
2029 d->m_ops->setMode(m);
2030 if (d->m_ops->dirOnlyMode()) {
2031 d->m_filterWidget->setDefaultFilter(KFileFilter(i18n("All Folders"), {QStringLiteral("*")}, {}));
2032 } else {
2033 d->m_filterWidget->setDefaultFilter(KFileFilter(i18n("All Files"), {QStringLiteral("*")}, {}));
2034 }
2035
2036 d->updateAutoSelectExtension();
2037}
2038
2040{
2041 return d->m_ops->mode();
2042}
2043
2044void KFileWidgetPrivate::readViewConfig()
2045{
2046 m_ops->setViewConfig(m_configGroup);
2047 m_ops->readConfig(m_configGroup);
2048 KUrlComboBox *combo = m_urlNavigator->editor();
2049
2051 (KCompletion::CompletionMode)m_configGroup.readEntry(PathComboCompletionMode, static_cast<int>(KCompletion::CompletionPopup));
2052 if (cm != KCompletion::CompletionPopup) {
2053 combo->setCompletionMode(cm);
2054 }
2055
2056 cm = (KCompletion::CompletionMode)m_configGroup.readEntry(LocationComboCompletionMode, static_cast<int>(KCompletion::CompletionPopup));
2057 if (cm != KCompletion::CompletionPopup) {
2058 m_locationEdit->setCompletionMode(cm);
2059 }
2060
2061 // Show or don't show the places panel
2062 togglePlacesPanel(m_configGroup.readEntry(ShowSpeedbar, true));
2063
2064 // show or don't show the bookmarks
2065 toggleBookmarks(m_configGroup.readEntry(ShowBookmarks, false));
2066
2067 // does the user want Automatically Select Extension?
2068 m_autoSelectExtChecked = m_configGroup.readEntry(AutoSelectExtChecked, DefaultAutoSelectExtChecked);
2069 updateAutoSelectExtension();
2070
2071 // should the URL navigator use the breadcrumb navigation?
2072 m_urlNavigator->setUrlEditable(!m_configGroup.readEntry(BreadcrumbNavigation, true));
2073
2074 // should the URL navigator show the full path?
2075 m_urlNavigator->setShowFullPath(m_configGroup.readEntry(ShowFullPath, false));
2076
2077 int w1 = q->minimumSize().width();
2078 int w2 = m_toolbar->sizeHint().width();
2079 if (w1 < w2) {
2080 q->setMinimumWidth(w2);
2081 }
2082}
2083
2084void KFileWidgetPrivate::writeViewConfig()
2085{
2086 // these settings are global settings; ALL instances of the file dialog
2087 // should reflect them.
2088 // There is no way to tell KFileOperator::writeConfig() to write to
2089 // kdeglobals so we write settings to a temporary config group then copy
2090 // them all to kdeglobals
2091 KConfig tmp(QString(), KConfig::SimpleConfig);
2092 KConfigGroup tmpGroup(&tmp, ConfigGroup);
2093
2094 KUrlComboBox *pathCombo = m_urlNavigator->editor();
2095 // saveDialogSize( tmpGroup, KConfigGroup::Persistent | KConfigGroup::Global );
2096 tmpGroup.writeEntry(PathComboCompletionMode, static_cast<int>(pathCombo->completionMode()));
2097 tmpGroup.writeEntry(LocationComboCompletionMode, static_cast<int>(m_locationEdit->completionMode()));
2098
2099 const bool showPlacesPanel = m_placesDock && !m_placesDock->isHidden();
2100 tmpGroup.writeEntry(ShowSpeedbar, showPlacesPanel);
2101 if (m_placesViewWidth > 0) {
2102 tmpGroup.writeEntry(SpeedbarWidth, m_placesViewWidth);
2103 }
2104
2105 tmpGroup.writeEntry(ShowBookmarks, m_bookmarkHandler != nullptr);
2106 tmpGroup.writeEntry(AutoSelectExtChecked, m_autoSelectExtChecked);
2107 tmpGroup.writeEntry(BreadcrumbNavigation, !m_urlNavigator->isUrlEditable());
2108 tmpGroup.writeEntry(ShowFullPath, m_urlNavigator->showFullPath());
2109
2110 m_ops->writeConfig(tmpGroup);
2111
2112 // Copy saved settings to kdeglobals
2113 tmpGroup.copyTo(&m_configGroup, KConfigGroup::Persistent | KConfigGroup::Global);
2114}
2115
2116void KFileWidgetPrivate::readRecentFiles()
2117{
2118 // qDebug();
2119
2120 const bool oldState = m_locationEdit->blockSignals(true);
2121 m_locationEdit->setMaxItems(m_configGroup.readEntry(RecentFilesNumber, DefaultRecentURLsNumber));
2122 m_locationEdit->setUrls(m_stateConfigGroup.readPathEntry(RecentFiles, QStringList()), KUrlComboBox::RemoveBottom);
2123 m_locationEdit->setCurrentIndex(-1);
2124 m_locationEdit->blockSignals(oldState);
2125
2126 KUrlComboBox *combo = m_urlNavigator->editor();
2127 combo->setUrls(m_stateConfigGroup.readPathEntry(RecentURLs, QStringList()), KUrlComboBox::RemoveTop);
2128 combo->setMaxItems(m_configGroup.readEntry(RecentURLsNumber, DefaultRecentURLsNumber));
2129 combo->setUrl(m_ops->url());
2130 // since we delayed this moment, initialize the directory of the completion object to
2131 // our current directory (that was very probably set on the constructor)
2132 KUrlCompletion *completion = dynamic_cast<KUrlCompletion *>(m_locationEdit->completionObject());
2133 if (completion) {
2134 completion->setDir(m_ops->url());
2135 }
2136}
2137
2138void KFileWidgetPrivate::saveRecentFiles()
2139{
2140 // qDebug();
2141 m_stateConfigGroup.writePathEntry(RecentFiles, m_locationEdit->urls());
2142
2143 KUrlComboBox *pathCombo = m_urlNavigator->editor();
2144 m_stateConfigGroup.writePathEntry(RecentURLs, pathCombo->urls());
2145}
2146
2148{
2149 return d->m_okButton;
2150}
2151
2153{
2154 return d->m_cancelButton;
2155}
2156
2157// Called by KFileDialog
2158void KFileWidget::slotCancel()
2159{
2160 d->writeViewConfig();
2161 d->m_ops->close();
2162}
2163
2165{
2166 d->m_keepLocation = keep;
2167}
2168
2170{
2171 return d->m_keepLocation;
2172}
2173
2175{
2176 // qDebug();
2177
2178 d->m_operationMode = mode;
2179 d->m_keepLocation = (mode == Saving);
2180 d->m_filterWidget->setEditable(!d->m_hasDefaultFilter || mode != Saving);
2181 if (mode == Opening) {
2182 // don't use KStandardGuiItem::open() here which has trailing ellipsis!
2183 d->m_okButton->setText(i18n("&Open"));
2184 d->m_okButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
2185 // hide the new folder actions...usability team says they shouldn't be in open file dialog
2186 d->m_ops->action(KDirOperator::NewFolder)->setEnabled(false);
2187 d->m_toolbar->removeAction(d->m_ops->action(KDirOperator::NewFolder));
2188 } else if (mode == Saving) {
2189 KGuiItem::assign(d->m_okButton, KStandardGuiItem::save());
2190 d->setNonExtSelection();
2191 } else {
2192 KGuiItem::assign(d->m_okButton, KStandardGuiItem::ok());
2193 }
2194 d->updateLocationWhatsThis();
2195 d->updateAutoSelectExtension();
2196
2197 if (d->m_ops) {
2198 d->m_ops->setIsSaving(mode == Saving);
2199 }
2200 d->updateFilterText();
2201}
2202
2204{
2205 return d->m_operationMode;
2206}
2207
2208void KFileWidgetPrivate::slotAutoSelectExtClicked()
2209{
2210 // qDebug() << "slotAutoSelectExtClicked(): "
2211 // << m_autoSelectExtCheckBox->isChecked() << endl;
2212
2213 // whether the _user_ wants it on/off
2214 m_autoSelectExtChecked = m_autoSelectExtCheckBox->isChecked();
2215
2216 // update the current filename's extension
2217 updateLocationEditExtension(m_extension /* extension hasn't changed */);
2218}
2219
2220void KFileWidgetPrivate::placesViewSplitterMoved(int pos, int index)
2221{
2222 // qDebug();
2223
2224 // we need to record the size of the splitter when the splitter changes size
2225 // so we can keep the places box the right size!
2226 if (m_placesDock && index == 1) {
2227 m_placesViewWidth = pos;
2228 // qDebug() << "setting m_lafBox minwidth to" << m_placesViewWidth;
2229 }
2230}
2231
2232void KFileWidgetPrivate::activateUrlNavigator()
2233{
2234 // qDebug();
2235
2236 QLineEdit *lineEdit = m_urlNavigator->editor()->lineEdit();
2237
2238 // If the text field currently has focus and everything is selected,
2239 // pressing the keyboard shortcut returns the whole thing to breadcrumb mode
2240 if (m_urlNavigator->isUrlEditable() && lineEdit->hasFocus() && lineEdit->selectedText() == lineEdit->text()) {
2241 m_urlNavigator->setUrlEditable(false);
2242 } else {
2243 m_urlNavigator->setUrlEditable(true);
2244 m_urlNavigator->setFocus();
2245 lineEdit->selectAll();
2246 }
2247}
2248
2249void KFileWidgetPrivate::slotDirOpIconSizeChanged(int size)
2250{
2251 auto beginIt = m_stdIconSizes.cbegin();
2252 auto endIt = m_stdIconSizes.cend();
2253 auto it = std::lower_bound(beginIt, endIt, size);
2254 const int sliderStep = it != endIt ? it - beginIt : 0;
2255 m_iconSizeSlider->setValue(sliderStep);
2256 m_zoomOutAction->setDisabled(it == beginIt);
2257 m_zoomInAction->setDisabled(it == (endIt - 1));
2258}
2259
2260void KFileWidgetPrivate::changeIconsSize(ZoomState zoom)
2261{
2262 int step = m_iconSizeSlider->value();
2263
2264 if (zoom == ZoomOut) {
2265 if (step == 0) {
2266 return;
2267 }
2268 --step;
2269 } else { // ZoomIn
2270 if (step == static_cast<int>(m_stdIconSizes.size() - 1)) {
2271 return;
2272 }
2273 ++step;
2274 }
2275
2276 m_iconSizeSlider->setValue(step);
2277 slotIconSizeSliderMoved(m_stdIconSizes[step]);
2278}
2279
2280void KFileWidgetPrivate::slotIconSizeChanged(int _value)
2281{
2282 m_ops->setIconSize(_value);
2283 m_iconSizeSlider->setToolTip(i18n("Icon size: %1 pixels", _value));
2284}
2285
2286void KFileWidgetPrivate::slotIconSizeSliderMoved(int size)
2287{
2288 // Force this to be called in case this slot is called first on the
2289 // slider move.
2290 slotIconSizeChanged(size);
2291
2292 QPoint global(m_iconSizeSlider->rect().topLeft());
2293 global.ry() += m_iconSizeSlider->height() / 2;
2294 QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), m_iconSizeSlider->mapToGlobal(global));
2295 QApplication::sendEvent(m_iconSizeSlider, &toolTipEvent);
2296}
2297
2298void KFileWidgetPrivate::slotViewDoubleClicked(const QModelIndex &index)
2299{
2300 // double clicking to save should only work on files
2301 if (m_operationMode == KFileWidget::Saving && index.isValid() && m_ops->selectedItems().constFirst().isFile()) {
2302 q->slotOk();
2303 }
2304}
2305
2306void KFileWidgetPrivate::slotViewKeyEnterReturnPressed()
2307{
2308 // an enter/return event occurred in the view
2309 // when we are saving one file and there is no selection in the view (otherwise we get an activated event)
2310 if (m_operationMode == KFileWidget::Saving && (m_ops->mode() & KFile::File) && m_ops->selectedItems().isEmpty()) {
2311 q->slotOk();
2312 }
2313}
2314
2315static QString getExtensionFromPatternList(const QStringList &patternList)
2316{
2317 // qDebug();
2318
2319 QString ret;
2320 // qDebug() << "\tgetExtension " << patternList;
2321
2322 QStringList::ConstIterator patternListEnd = patternList.end();
2323 for (QStringList::ConstIterator it = patternList.begin(); it != patternListEnd; ++it) {
2324 // qDebug() << "\t\ttry: \'" << (*it) << "\'";
2325
2326 // is this pattern like "*.BMP" rather than useless things like:
2327 //
2328 // README
2329 // *.
2330 // *.*
2331 // *.JP*G
2332 // *.JP?
2333 // *.[Jj][Pp][Gg]
2334 if ((*it).startsWith(QLatin1String("*.")) && (*it).length() > 2 && (*it).indexOf(QLatin1Char('*'), 2) < 0 && (*it).indexOf(QLatin1Char('?'), 2) < 0
2335 && (*it).indexOf(QLatin1Char('['), 2) < 0 && (*it).indexOf(QLatin1Char(']'), 2) < 0) {
2336 ret = (*it).mid(1);
2337 break;
2338 }
2339 }
2340
2341 return ret;
2342}
2343
2344static QString stripUndisplayable(const QString &string)
2345{
2346 QString ret = string;
2347
2348 ret.remove(QLatin1Char(':'));
2350
2351 return ret;
2352}
2353
2354// QString KFileWidget::currentFilterExtension()
2355//{
2356// return d->m_extension;
2357//}
2358
2359void KFileWidgetPrivate::updateAutoSelectExtension()
2360{
2361 if (!m_autoSelectExtCheckBox) {
2362 return;
2363 }
2364
2365 QMimeDatabase db;
2366 //
2367 // Figure out an extension for the Automatically Select Extension thing
2368 // (some Windows users apparently don't know what to do when confronted
2369 // with a text file called "COPYING" but do know what to do with
2370 // COPYING.txt ...)
2371 //
2372
2373 // qDebug() << "Figure out an extension: ";
2374 QString lastExtension = m_extension;
2375 m_extension.clear();
2376
2377 // Automatically Select Extension is only valid if the user is _saving_ a _file_
2378 if ((m_operationMode == KFileWidget::Saving) && (m_ops->mode() & KFile::File)) {
2379 //
2380 // Get an extension from the filter
2381 //
2382
2383 KFileFilter fileFilter = m_filterWidget->currentFilter();
2384 if (!fileFilter.isEmpty()) {
2385 // if the currently selected filename already has an extension which
2386 // is also included in the currently allowed extensions, keep it
2387 // otherwise use the default extension
2388 QString currentExtension = db.suffixForFileName(locationEditCurrentText());
2389 if (currentExtension.isEmpty()) {
2390 currentExtension = locationEditCurrentText().section(QLatin1Char('.'), -1, -1);
2391 }
2392 // qDebug() << "filter:" << filter << "locationEdit:" << locationEditCurrentText() << "currentExtension:" << currentExtension;
2393
2394 QString defaultExtension;
2395 QStringList extensionList;
2396
2397 // e.g. "*.cpp"
2398 if (!fileFilter.filePatterns().isEmpty()) {
2399 extensionList = fileFilter.filePatterns();
2400 defaultExtension = getExtensionFromPatternList(extensionList);
2401 }
2402 // e.g. "text/html"
2403 else if (!fileFilter.mimePatterns().isEmpty()) {
2404 QMimeType mime = db.mimeTypeForName(fileFilter.mimePatterns().first());
2405 if (mime.isValid()) {
2406 extensionList = mime.globPatterns();
2407 defaultExtension = mime.preferredSuffix();
2408 if (!defaultExtension.isEmpty()) {
2409 defaultExtension.prepend(QLatin1Char('.'));
2410 }
2411 }
2412 }
2413
2414 if ((!currentExtension.isEmpty() && extensionList.contains(QLatin1String("*.") + currentExtension))) {
2415 m_extension = QLatin1Char('.') + currentExtension;
2416 } else {
2417 m_extension = defaultExtension;
2418 }
2419
2420 // qDebug() << "List:" << extensionList << "auto-selected extension:" << m_extension;
2421 }
2422
2423 //
2424 // GUI: checkbox
2425 //
2426
2427 QString whatsThisExtension;
2428 if (!m_extension.isEmpty()) {
2429 // remember: sync any changes to the string with below
2430 m_autoSelectExtCheckBox->setText(i18n("Automatically select filename e&xtension (%1)", m_extension));
2431 whatsThisExtension = i18n("the extension <b>%1</b>", m_extension);
2432
2433 m_autoSelectExtCheckBox->setEnabled(true);
2434 m_autoSelectExtCheckBox->setChecked(m_autoSelectExtChecked);
2435 } else {
2436 // remember: sync any changes to the string with above
2437 m_autoSelectExtCheckBox->setText(i18n("Automatically select filename e&xtension"));
2438 whatsThisExtension = i18n("a suitable extension");
2439
2440 m_autoSelectExtCheckBox->setChecked(false);
2441 m_autoSelectExtCheckBox->setEnabled(false);
2442 }
2443
2444 const QString locationLabelText = stripUndisplayable(m_locationLabel->text());
2445 m_autoSelectExtCheckBox->setWhatsThis(QLatin1String("<qt>")
2446 + i18n("This option enables some convenient features for "
2447 "saving files with extensions:<br />"
2448 "<ol>"
2449 "<li>Any extension specified in the <b>%1</b> text "
2450 "area will be updated if you change the file type "
2451 "to save in.<br />"
2452 "<br /></li>"
2453 "<li>If no extension is specified in the <b>%2</b> "
2454 "text area when you click "
2455 "<b>Save</b>, %3 will be added to the end of the "
2456 "filename (if the filename does not already exist). "
2457 "This extension is based on the file type that you "
2458 "have chosen to save in.<br />"
2459 "<br />"
2460 "If you do not want KDE to supply an extension for the "
2461 "filename, you can either turn this option off or you "
2462 "can suppress it by adding a period (.) to the end of "
2463 "the filename (the period will be automatically "
2464 "removed)."
2465 "</li>"
2466 "</ol>"
2467 "If unsure, keep this option enabled as it makes your "
2468 "files more manageable.",
2469 locationLabelText,
2470 locationLabelText,
2471 whatsThisExtension)
2472 + QLatin1String("</qt>"));
2473
2474 m_autoSelectExtCheckBox->show();
2475
2476 // update the current filename's extension
2477 updateLocationEditExtension(lastExtension);
2478 }
2479 // Automatically Select Extension not valid
2480 else {
2481 m_autoSelectExtCheckBox->setChecked(false);
2482 m_autoSelectExtCheckBox->hide();
2483 }
2484}
2485
2486// Updates the extension of the filename specified in d->m_locationEdit if the
2487// Automatically Select Extension feature is enabled.
2488// (this prevents you from accidentally saving "file.kwd" as RTF, for example)
2489void KFileWidgetPrivate::updateLocationEditExtension(const QString &lastExtension)
2490{
2491 if (!m_autoSelectExtCheckBox->isChecked() || m_extension.isEmpty()) {
2492 return;
2493 }
2494
2495 const QString urlStr = locationEditCurrentText();
2496 if (urlStr.isEmpty()) {
2497 return;
2498 }
2499
2500 const int fileNameOffset = urlStr.lastIndexOf(QLatin1Char('/')) + 1;
2501 QStringView fileName = QStringView(urlStr).mid(fileNameOffset);
2502
2503 const int dot = fileName.lastIndexOf(QLatin1Char('.'));
2504 const int len = fileName.length();
2505 if (dot > 0 && // has an extension already and it's not a hidden file
2506 // like ".hidden" (but we do accept ".hidden.ext")
2507 dot != len - 1 // and not deliberately suppressing extension
2508 ) {
2509 const QUrl url = getCompleteUrl(urlStr);
2510 // qDebug() << "updateLocationEditExtension (" << url << ")";
2511 // exists?
2512 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
2513 KJobWidgets::setWindow(statJob, q);
2514 bool result = statJob->exec();
2515 if (result) {
2516 // qDebug() << "\tfile exists";
2517
2518 if (statJob->statResult().isDir()) {
2519 // qDebug() << "\tisDir - won't alter extension";
2520 return;
2521 }
2522
2523 // --- fall through ---
2524 }
2525
2526 //
2527 // try to get rid of the current extension
2528 //
2529
2530 // catch "double extensions" like ".tar.gz"
2531 if (!lastExtension.isEmpty() && fileName.endsWith(lastExtension)) {
2532 fileName.chop(lastExtension.length());
2533 } else if (!m_extension.isEmpty() && fileName.endsWith(m_extension)) {
2534 fileName.chop(m_extension.length());
2535 } else { // can only handle "single extensions"
2536 fileName.truncate(dot);
2537 }
2538
2539 // add extension
2540 const QString newText = QStringView(urlStr).left(fileNameOffset) + fileName + m_extension;
2541 if (newText != locationEditCurrentText()) {
2542 const int idx = m_locationEdit->currentIndex();
2543 if (idx == -1) {
2544 m_locationEdit->lineEdit()->selectAll();
2545 m_locationEdit->lineEdit()->insert(newText);
2546 } else {
2547 m_locationEdit->setItemText(idx, newText);
2548 }
2549 m_locationEdit->lineEdit()->setModified(true);
2550 }
2551 }
2552}
2553
2554QString KFileWidgetPrivate::findMatchingFilter(const QString &filter, const QString &filename) const
2555{
2556 // e.g.: '*.foo *.bar|Foo type' -> '*.foo', '*.bar'
2557 const QStringList patterns = filter.left(filter.indexOf(QLatin1Char('|'))).split(QLatin1Char(' '), Qt::SkipEmptyParts);
2558
2559 QRegularExpression rx;
2560 for (const QString &p : patterns) {
2562 if (rx.match(filename).hasMatch()) {
2563 return p;
2564 }
2565 }
2566 return QString();
2567}
2568
2569// Updates the filter if the extension of the filename specified in d->m_locationEdit is changed
2570// (this prevents you from accidentally saving "file.kwd" as RTF, for example)
2571void KFileWidgetPrivate::updateFilter()
2572{
2573 if ((m_operationMode == KFileWidget::Saving) && (m_ops->mode() & KFile::File)) {
2574 QString urlStr = locationEditCurrentText();
2575 if (urlStr.isEmpty()) {
2576 return;
2577 }
2578
2579 QMimeDatabase db;
2580 QMimeType urlMimeType = db.mimeTypeForFile(urlStr, QMimeDatabase::MatchExtension);
2581
2582 bool matchesCurrentFilter = [this, urlMimeType, urlStr] {
2583 const KFileFilter filter = m_filterWidget->currentFilter();
2584 if (filter.mimePatterns().contains(urlMimeType.name())) {
2585 return true;
2586 }
2587
2588 QString filename = urlStr.mid(urlStr.lastIndexOf(QLatin1Char('/')) + 1); // only filename
2589
2590 const auto filePatterns = filter.filePatterns();
2591 const bool hasMatch = std::any_of(filePatterns.cbegin(), filePatterns.cend(), [filename](const QString &pattern) {
2592 QRegularExpression rx(QRegularExpression::wildcardToRegularExpression(pattern));
2593
2594 return rx.match(filename).hasMatch();
2595 });
2596 return hasMatch;
2597 }();
2598
2599 if (matchesCurrentFilter) {
2600 return;
2601 }
2602
2603 const auto filters = m_filterWidget->filters();
2604
2605 auto filterIt = std::find_if(filters.cbegin(), filters.cend(), [urlStr, urlMimeType](const KFileFilter &filter) {
2606 if (filter.mimePatterns().contains(urlMimeType.name())) {
2607 return true;
2608 }
2609
2610 QString filename = urlStr.mid(urlStr.lastIndexOf(QLatin1Char('/')) + 1); // only filename
2611 // accept any match to honor the user's selection; see later code handling the "*" match
2612
2613 const auto filePatterns = filter.filePatterns();
2614 const bool hasMatch = std::any_of(filePatterns.cbegin(), filePatterns.cend(), [filename](const QString &pattern) {
2615 // never match the catch-all filter
2616 if (pattern == QLatin1String("*")) {
2617 return false;
2618 }
2619
2620 QRegularExpression rx(QRegularExpression::wildcardToRegularExpression(pattern));
2621
2622 return rx.match(filename).hasMatch();
2623 });
2624
2625 return hasMatch;
2626 });
2627
2628 if (filterIt != filters.cend()) {
2629 m_filterWidget->setCurrentFilter(*filterIt);
2630 }
2631 }
2632}
2633
2634// applies only to a file that doesn't already exist
2635void KFileWidgetPrivate::appendExtension(QUrl &url)
2636{
2637 // qDebug();
2638
2639 if (!m_autoSelectExtCheckBox->isChecked() || m_extension.isEmpty()) {
2640 return;
2641 }
2642
2643 QString fileName = url.fileName();
2644 if (fileName.isEmpty()) {
2645 return;
2646 }
2647
2648 // qDebug() << "appendExtension(" << url << ")";
2649
2650 const int len = fileName.length();
2651 const int dot = fileName.lastIndexOf(QLatin1Char('.'));
2652
2653 const bool suppressExtension = (dot == len - 1);
2654 const bool unspecifiedExtension = !fileName.endsWith(m_extension);
2655
2656 // don't KIO::Stat if unnecessary
2657 if (!(suppressExtension || unspecifiedExtension)) {
2658 return;
2659 }
2660
2661 // exists?
2662 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
2663 KJobWidgets::setWindow(statJob, q);
2664 bool res = statJob->exec();
2665 if (res) {
2666 // qDebug() << "\tfile exists - won't append extension";
2667 return;
2668 }
2669
2670 // suppress automatically append extension?
2671 if (suppressExtension) {
2672 //
2673 // Strip trailing dot
2674 // This allows lazy people to have m_autoSelectExtCheckBox->isChecked
2675 // but don't want a file extension to be appended
2676 // e.g. "README." will make a file called "README"
2677 //
2678 // If you really want a name like "README.", then type "README.."
2679 // and the trailing dot will be removed (or just stop being lazy and
2680 // turn off this feature so that you can type "README.")
2681 //
2682 // qDebug() << "\tstrip trailing dot";
2683 QString path = url.path();
2684 path.chop(1);
2685 url.setPath(path);
2686 }
2687 // evilmatically append extension :) if the user hasn't specified one
2688 else if (unspecifiedExtension) {
2689 // qDebug() << "\tappending extension \'" << m_extension << "\'...";
2690 url = url.adjusted(QUrl::RemoveFilename); // keeps trailing slash
2691 url.setPath(url.path() + fileName + m_extension);
2692 // qDebug() << "\tsaving as \'" << url << "\'";
2693 }
2694}
2695
2696// adds the selected files/urls to 'recent documents'
2697void KFileWidgetPrivate::addToRecentDocuments()
2698{
2699 int m = m_ops->mode();
2700 int atmost = KRecentDocument::maximumItems();
2701 // don't add more than we need. KRecentDocument::add() is pretty slow
2702
2703 if (m & KFile::LocalOnly) {
2704 const QStringList files = q->selectedFiles();
2705 QStringList::ConstIterator it = files.begin();
2706 for (; it != files.end() && atmost > 0; ++it) {
2708 atmost--;
2709 }
2710 }
2711
2712 else { // urls
2713 const QList<QUrl> urls = q->selectedUrls();
2715 for (; it != urls.end() && atmost > 0; ++it) {
2716 if ((*it).isValid()) {
2718 atmost--;
2719 }
2720 }
2721 }
2722}
2723
2725{
2726 return d->m_locationEdit;
2727}
2728
2730{
2731 return d->m_filterWidget;
2732}
2733
2734void KFileWidgetPrivate::togglePlacesPanel(bool show, QObject *sender)
2735{
2736 if (show) {
2737 initPlacesPanel();
2738 m_placesDock->show();
2739
2740 // check to see if they have a home item defined, if not show the home button
2741 QUrl homeURL;
2742 homeURL.setPath(QDir::homePath());
2743 KFilePlacesModel *model = static_cast<KFilePlacesModel *>(m_placesView->model());
2744 for (int rowIndex = 0; rowIndex < model->rowCount(); rowIndex++) {
2745 QModelIndex index = model->index(rowIndex, 0);
2746 QUrl url = model->url(index);
2747
2748 if (homeURL.matches(url, QUrl::StripTrailingSlash)) {
2749 m_toolbar->removeAction(m_ops->action(KDirOperator::Home));
2750 break;
2751 }
2752 }
2753 } else {
2754 if (sender == m_placesDock && m_placesDock && m_placesDock->isVisibleTo(q)) {
2755 // we didn't *really* go away! the dialog was simply hidden or
2756 // we changed virtual desktops or ...
2757 return;
2758 }
2759
2760 if (m_placesDock) {
2761 m_placesDock->hide();
2762 }
2763
2764 QAction *homeAction = m_ops->action(KDirOperator::Home);
2765 QAction *reloadAction = m_ops->action(KDirOperator::Reload);
2766 if (!m_toolbar->actions().contains(homeAction)) {
2767 m_toolbar->insertAction(reloadAction, homeAction);
2768 }
2769 }
2770
2771 m_togglePlacesPanelAction->setChecked(show);
2772
2773 // if we don't show the places panel, at least show the places menu
2774 m_urlNavigator->setPlacesSelectorVisible(!show);
2775}
2776
2777void KFileWidgetPrivate::toggleBookmarks(bool show)
2778{
2779 if (show) {
2780 if (m_bookmarkHandler) {
2781 return;
2782 }
2783 m_bookmarkHandler = new KFileBookmarkHandler(q);
2784 q->connect(m_bookmarkHandler, &KFileBookmarkHandler::openUrl, q, [this](const QString &path) {
2785 enterUrl(path);
2786 });
2787 m_bookmarkButton->setMenu(m_bookmarkHandler->menu());
2788 } else if (m_bookmarkHandler) {
2789 m_bookmarkButton->setMenu(nullptr);
2790 delete m_bookmarkHandler;
2791 m_bookmarkHandler = nullptr;
2792 }
2793
2794 if (m_bookmarkButton) {
2795 m_bookmarkButton->setVisible(show);
2796 }
2797
2798 m_toggleBookmarksAction->setChecked(show);
2799}
2800
2801void KFileWidgetPrivate::setQuickFilterVisible(bool show)
2802{
2803 if (m_quickFilter->isVisible() == show) {
2804 return;
2805 }
2806 m_quickFilter->setVisible(show);
2807 m_filterWidget->setEnabled(!show);
2808 if (show) {
2809 m_quickFilterEdit->setFocus();
2810 } else {
2811 m_quickFilterEdit->clear();
2812 }
2813 m_quickFilterLock->setChecked(false);
2814 m_ops->dirLister()->setQuickFilterMode(show);
2815 m_toggleQuickFilterAction->setChecked(show);
2816}
2817
2818// static, overloaded
2819QUrl KFileWidget::getStartUrl(const QUrl &startDir, QString &recentDirClass)
2820{
2821 QString fileName; // result discarded
2822 return getStartUrl(startDir, recentDirClass, fileName);
2823}
2824
2825// static, overloaded
2826QUrl KFileWidget::getStartUrl(const QUrl &startDir, QString &recentDirClass, QString &fileName)
2827{
2828 recentDirClass.clear();
2829 fileName.clear();
2830 QUrl ret;
2831
2832 bool useDefaultStartDir = startDir.isEmpty();
2833 if (!useDefaultStartDir) {
2834 if (startDir.scheme() == QLatin1String("kfiledialog")) {
2835 // The startDir URL with this protocol may be in the format:
2836 // directory() fileName()
2837 // 1. kfiledialog:///keyword "/" keyword
2838 // 2. kfiledialog:///keyword?global "/" keyword
2839 // 3. kfiledialog:///keyword/ "/" keyword
2840 // 4. kfiledialog:///keyword/?global "/" keyword
2841 // 5. kfiledialog:///keyword/filename /keyword filename
2842 // 6. kfiledialog:///keyword/filename?global /keyword filename
2843
2844 QString keyword;
2846 QString urlFile = startDir.fileName();
2847 if (urlDir == QLatin1String("/")) { // '1'..'4' above
2848 keyword = urlFile;
2849 fileName.clear();
2850 } else { // '5' or '6' above
2851 keyword = urlDir.mid(1);
2852 fileName = urlFile;
2853 }
2854
2855 const QLatin1String query(":%1");
2856 recentDirClass = query.arg(keyword);
2857
2858 ret = QUrl::fromLocalFile(KRecentDirs::dir(recentDirClass));
2859 } else { // not special "kfiledialog" URL
2860 // "foo.png" only gives us a file name, the default start dir will be used.
2861 // "file:foo.png" (from KHTML/webkit, due to fromPath()) means the same
2862 // (and is the reason why we don't just use QUrl::isRelative()).
2863
2864 // In all other cases (startDir contains a directory path, or has no
2865 // fileName for us anyway, such as smb://), startDir is indeed a dir url.
2866
2867 if (!startDir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path().isEmpty() || startDir.fileName().isEmpty()) {
2868 // can use start directory
2869 ret = startDir; // will be checked by stat later
2870 // If we won't be able to list it (e.g. http), then use default
2872 useDefaultStartDir = true;
2873 fileName = startDir.fileName();
2874 }
2875 } else { // file name only
2876 fileName = startDir.fileName();
2877 useDefaultStartDir = true;
2878 }
2879 }
2880 }
2881
2882 if (useDefaultStartDir) {
2883 if (lastDirectory()->isEmpty()) {
2886 // if there is no docpath set (== home dir), we prefer the current
2887 // directory over it. We also prefer the homedir when our CWD is
2888 // different from our homedirectory or when the document dir
2889 // does not exist
2890 if (lastDirectory()->adjusted(QUrl::StripTrailingSlash) == home.adjusted(QUrl::StripTrailingSlash) //
2892 || !QDir(lastDirectory()->toLocalFile()).exists()) {
2893 *lastDirectory() = QUrl::fromLocalFile(QDir::currentPath());
2894 }
2895 }
2896 ret = *lastDirectory();
2897 }
2898
2899 // qDebug() << "for" << startDir << "->" << ret << "recentDirClass" << recentDirClass << "fileName" << fileName;
2900 return ret;
2901}
2902
2903void KFileWidget::setStartDir(const QUrl &directory)
2904{
2905 if (directory.isValid()) {
2906 *lastDirectory() = directory;
2907 }
2908}
2909
2910void KFileWidgetPrivate::setNonExtSelection()
2911{
2912 // Enhanced rename: Don't highlight the file extension.
2913 QString filename = locationEditCurrentText();
2914 QMimeDatabase db;
2915 QString extension = db.suffixForFileName(filename);
2916
2917 if (!extension.isEmpty()) {
2918 m_locationEdit->lineEdit()->setSelection(0, filename.length() - extension.length() - 1);
2919 } else {
2920 int lastDot = filename.lastIndexOf(QLatin1Char('.'));
2921 if (lastDot > 0) {
2922 m_locationEdit->lineEdit()->setSelection(0, lastDot);
2923 } else {
2924 m_locationEdit->lineEdit()->selectAll();
2925 }
2926 }
2927}
2928
2929// Sets the filter text to "File type" if the dialog is saving and a MIME type
2930// filter has been set; otherwise, the text is "Filter:"
2931void KFileWidgetPrivate::updateFilterText()
2932{
2933 QString label = i18n("&File type:");
2934 QString whatsThisText;
2935
2936 if (m_operationMode == KFileWidget::Saving && !m_filterWidget->currentFilter().mimePatterns().isEmpty()) {
2937 whatsThisText = i18n("<qt>This is the file type selector. It is used to select the format that the file will be saved as.</qt>");
2938 } else {
2939 whatsThisText = i18n("<qt>This is the file type selector. It is used to select the format of the files shown.</qt>");
2940 }
2941
2942 if (m_filterLabel) {
2943 m_filterLabel->setText(label);
2944 m_filterLabel->setWhatsThis(whatsThisText);
2945 }
2946 if (m_filterWidget) {
2947 m_filterWidget->setWhatsThis(whatsThisText);
2948 }
2949}
2950
2952{
2953 delete d->m_bottomCustomWidget;
2954 d->m_bottomCustomWidget = widget;
2955
2956 // add it to the dialog, below the filter list box.
2957
2958 // Change the parent so that this widget is a child of the main widget
2959 d->m_bottomCustomWidget->setParent(this);
2960
2961 d->m_opsWidgetLayout->addWidget(d->m_bottomCustomWidget);
2962
2963 // FIXME: This should adjust the tab orders so that the custom widget
2964 // comes after the Cancel button. The code appears to do this, but the result
2965 // somehow screws up the tab order of the file path combo box. Not a major
2966 // problem, but ideally the tab order with a custom widget should be
2967 // the same as the order without one.
2968 setTabOrder(d->m_cancelButton, d->m_bottomCustomWidget);
2969 setTabOrder(d->m_bottomCustomWidget, d->m_urlNavigator);
2970}
2971
2973{
2974 delete d->m_labeledCustomWidget;
2975 d->m_labeledCustomWidget = widget;
2976
2977 QLabel *label = new QLabel(text, this);
2978 label->setAlignment(Qt::AlignRight);
2979 d->m_lafBox->addRow(label, widget);
2980}
2981
2983{
2984 return d->m_ops;
2985}
2986
2987#if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(6, 3)
2989{
2990 d->m_configGroup = group;
2991 d->readViewConfig();
2992 d->readRecentFiles();
2993}
2994#endif
2995
2996QString KFileWidgetPrivate::locationEditCurrentText() const
2997{
2998 return QDir::fromNativeSeparators(m_locationEdit->currentText());
2999}
3000
3001QUrl KFileWidgetPrivate::mostLocalUrl(const QUrl &url)
3002{
3003 if (url.isLocalFile()) {
3004 return url;
3005 }
3006
3007 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
3008 KJobWidgets::setWindow(statJob, q);
3009 bool res = statJob->exec();
3010
3011 if (!res) {
3012 return url;
3013 }
3014
3015 const QString path = statJob->statResult().stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
3016 if (!path.isEmpty()) {
3017 QUrl newUrl;
3018 newUrl.setPath(path);
3019 return newUrl;
3020 }
3021
3022 return url;
3023}
3024
3025void KFileWidgetPrivate::setInlinePreviewShown(bool show)
3026{
3027 m_ops->setInlinePreviewShown(show);
3028}
3029
3031{
3032 d->m_confirmOverwrite = enable;
3033}
3034
3036{
3037 d->setInlinePreviewShown(show);
3038}
3039
3041{
3042 int fontSize = fontMetrics().height();
3043 QSize goodSize(48 * fontSize, 30 * fontSize);
3044 const QSize scrnSize = d->screenSize();
3045 QSize minSize(scrnSize / 2);
3046 QSize maxSize(scrnSize * qreal(0.9));
3047 return (goodSize.expandedTo(minSize).boundedTo(maxSize));
3048}
3049
3050void KFileWidget::setViewMode(KFile::FileView mode)
3051{
3052 d->m_ops->setViewMode(mode);
3053 d->m_hasView = true;
3054}
3055
3057{
3058 d->m_model->setSupportedSchemes(schemes);
3059 d->m_ops->setSupportedSchemes(schemes);
3060 d->m_urlNavigator->setSupportedSchemes(schemes);
3061}
3062
3064{
3065 return d->m_model->supportedSchemes();
3066}
3067
3068#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 event(QEvent *e)
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 Apr 11 2025 11:51:43 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.