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

KDE's Doxygen guidelines are available online.