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