kdeplatformfiledialoghelper.cpp 16.1 KB
Newer Older
1
/*  This file is part of the KDE libraries
2
3
4
5
6
    SPDX-FileCopyrightText: 2013 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
    SPDX-FileCopyrightText: 2014 Martin Klapetek <mklapetek@kde.org>

    SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
7
8

#include "kdeplatformfiledialoghelper.h"
9
10
11
#include "kdeplatformfiledialogbase_p.h"
#include "kdirselectdialog_p.h"

Alexander Lohnau's avatar
Alexander Lohnau committed
12
13
14
#include <KIO/StatJob>
#include <KJobWidgets>
#include <KProtocolInfo>
15
16
#include <KSharedConfig>
#include <KWindowConfig>
Alexander Lohnau's avatar
Alexander Lohnau committed
17
18
19
#include <kdiroperator.h>
#include <kfilefiltercombo.h>
#include <kfilewidget.h>
20
#include <kio_version.h>
Alexander Lohnau's avatar
Alexander Lohnau committed
21
#include <klocalizedstring.h>
22

23
#include <QDialogButtonBox>
Alexander Lohnau's avatar
Alexander Lohnau committed
24
#include <QMimeDatabase>
25
26
#include <QPushButton>
#include <QTextStream>
Alexander Lohnau's avatar
Alexander Lohnau committed
27
28
#include <QVBoxLayout>
#include <QWindow>
29
30
31
32
33
34
35
namespace
{
/*
 * Map a Qt filter string into a KDE one.
 */
static QString qt2KdeFilter(const QStringList &f)
{
Alexander Lohnau's avatar
Alexander Lohnau committed
36
37
38
    QString filter;
    QTextStream str(&filter, QIODevice::WriteOnly);
    QStringList list(f);
39
    list.replaceInStrings(QStringLiteral("/"), QStringLiteral("\\/"));
40
    QStringList::const_iterator it(list.constBegin()), end(list.constEnd());
Alexander Lohnau's avatar
Alexander Lohnau committed
41
    bool first = true;
42
43

    for (; it != end; ++it) {
Alexander Lohnau's avatar
Alexander Lohnau committed
44
        int ob = it->lastIndexOf(QLatin1Char('(')), cb = it->lastIndexOf(QLatin1Char(')'));
45
46
47
48
49

        if (-1 != cb && ob < cb) {
            if (first) {
                first = false;
            } else {
50
                str << '\n';
51
52
            }
            str << it->mid(ob + 1, (cb - ob) - 1) << '|' << it->mid(0, ob);
53
54
55
56
57
58
59
60
61
        }
    }

    return filter;
}

/*
 * Map a KDE filter string into a Qt one.
 */
62
static QString kde2QtFilter(const QStringList &list, const QString &kde, const QString &filterText)
63
{
64
    QStringList::const_iterator it(list.constBegin()), end(list.constEnd());
Alexander Lohnau's avatar
Alexander Lohnau committed
65
    int pos;
66

67
    for (; it != end; ++it) {
Alexander Lohnau's avatar
Alexander Lohnau committed
68
69
70
        if (-1 != (pos = it->indexOf(kde)) && pos > 0 && (QLatin1Char('(') == (*it)[pos - 1] || QLatin1Char(' ') == (*it)[pos - 1])
            && it->length() >= kde.length() + pos && (QLatin1Char(')') == (*it)[pos + kde.length()] || QLatin1Char(' ') == (*it)[pos + kde.length()])
            && (filterText.isEmpty() || it->startsWith(filterText))) {
71
72
73
74
75
76
77
78
            return *it;
        }
    }
    return QString();
}
}

KDEPlatformFileDialog::KDEPlatformFileDialog()
79
    : KDEPlatformFileDialogBase()
80
81
82
    , m_fileWidget(new KFileWidget(QUrl(), this))
{
    setLayout(new QVBoxLayout);
David Edmundson's avatar
David Edmundson committed
83
    connect(m_fileWidget, &KFileWidget::filterChanged, this, &KDEPlatformFileDialogBase::filterSelected);
84
85
86
    layout()->addWidget(m_fileWidget);

    m_buttons = new QDialogButtonBox(this);
87
88
    m_buttons->addButton(m_fileWidget->okButton(), QDialogButtonBox::AcceptRole);
    m_buttons->addButton(m_fileWidget->cancelButton(), QDialogButtonBox::RejectRole);
David Edmundson's avatar
David Edmundson committed
89
    connect(m_buttons, &QDialogButtonBox::rejected, m_fileWidget, &KFileWidget::slotCancel);
90
91
    // Also call the cancel function when the dialog is closed via the escape key
    // or titlebar close button to make sure we always save the view config
Alexander Lohnau's avatar
Alexander Lohnau committed
92
    connect(this, &KDEPlatformFileDialog::rejected, m_fileWidget, &KFileWidget::slotCancel);
David Edmundson's avatar
David Edmundson committed
93
94
95
96
    connect(m_fileWidget->okButton(), &QAbstractButton::clicked, m_fileWidget, &KFileWidget::slotOk);
    connect(m_fileWidget, &KFileWidget::accepted, m_fileWidget, &KFileWidget::accept);
    connect(m_fileWidget, &KFileWidget::accepted, this, &QDialog::accept);
    connect(m_fileWidget->cancelButton(), &QAbstractButton::clicked, this, &QDialog::reject);
97
    connect(m_fileWidget->dirOperator(), &KDirOperator::urlEntered, this, &KDEPlatformFileDialogBase::directoryEntered);
98
99
100
101
102
103
104
105
106
107
    layout()->addWidget(m_buttons);
}

QUrl KDEPlatformFileDialog::directory()
{
    return m_fileWidget->baseUrl();
}

QList<QUrl> KDEPlatformFileDialog::selectedFiles()
{
108
    return m_fileWidget->selectedUrls();
109
110
111
112
}

void KDEPlatformFileDialog::selectFile(const QUrl &filename)
{
113
114
    QUrl dirUrl = filename.adjusted(QUrl::RemoveFilename);
    m_fileWidget->setUrl(dirUrl);
115
    m_fileWidget->setSelectedUrl(filename);
116
117
}

118
119
120
121
void KDEPlatformFileDialog::setViewMode(QFileDialogOptions::ViewMode view)
{
    switch (view) {
    case QFileDialogOptions::ViewMode::Detail:
122
        m_fileWidget->setViewMode(KFile::FileView::Detail);
123
124
        break;
    case QFileDialogOptions::ViewMode::List:
125
        m_fileWidget->setViewMode(KFile::FileView::Simple);
126
127
        break;
    default:
128
        m_fileWidget->setViewMode(KFile::FileView::Default);
129
130
131
132
        break;
    }
}

133
134
135
136
137
138
139
140
141
142
void KDEPlatformFileDialog::setFileMode(QFileDialogOptions::FileMode mode)
{
    switch (mode) {
    case QFileDialogOptions::FileMode::AnyFile:
        m_fileWidget->setMode(KFile::File);
        break;
    case QFileDialogOptions::FileMode::ExistingFile:
        m_fileWidget->setMode(KFile::Mode::File | KFile::Mode::ExistingOnly);
        break;
    case QFileDialogOptions::FileMode::Directory:
143
        m_fileWidget->setMode(KFile::Mode::Directory | KFile::Mode::ExistingOnly);
144
145
146
147
148
149
150
151
152
153
        break;
    case QFileDialogOptions::FileMode::ExistingFiles:
        m_fileWidget->setMode(KFile::Mode::Files | KFile::Mode::ExistingOnly);
        break;
    default:
        m_fileWidget->setMode(KFile::File);
        break;
    }
}

154
155
156
157
158
159
160
161
162
163
164
void KDEPlatformFileDialog::setCustomLabel(QFileDialogOptions::DialogLabel label, const QString &text)
{
    if (label == QFileDialogOptions::Accept) { // OK button
        m_fileWidget->okButton()->setText(text);
    } else if (label == QFileDialogOptions::Reject) { // Cancel button
        m_fileWidget->cancelButton()->setText(text);
    } else if (label == QFileDialogOptions::LookIn) { // Location label
        m_fileWidget->setLocationLabel(text);
    }
}

165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
QString KDEPlatformFileDialog::selectedMimeTypeFilter()
{
    if (m_fileWidget->filterWidget()->isMimeFilter()) {
        const auto mimeTypeFromFilter = QMimeDatabase().mimeTypeForName(m_fileWidget->filterWidget()->currentFilter());
        // If one does not call selectMimeTypeFilter(), KFileFilterCombo::currentFilter() returns invalid mimeTypes,
        // such as "application/json application/zip".
        if (mimeTypeFromFilter.isValid()) {
            return mimeTypeFromFilter.name();
        }
    }

    if (selectedFiles().isEmpty()) {
        return QString();
    }

    // Works for both KFile::File and KFile::Files modes.
    return QMimeDatabase().mimeTypeForUrl(selectedFiles().at(0)).name();
}

184
185
186
187
188
QString KDEPlatformFileDialog::selectedNameFilter()
{
    return m_fileWidget->filterWidget()->currentFilter();
}

189
190
191
192
193
QString KDEPlatformFileDialog::currentFilterText()
{
    return m_fileWidget->filterWidget()->currentText();
}

194
195
196
197
198
void KDEPlatformFileDialog::selectMimeTypeFilter(const QString &filter)
{
    m_fileWidget->filterWidget()->setCurrentFilter(filter);
}

199
200
201
202
203
204
205
void KDEPlatformFileDialog::selectNameFilter(const QString &filter)
{
    m_fileWidget->filterWidget()->setCurrentFilter(filter);
}

void KDEPlatformFileDialog::setDirectory(const QUrl &directory)
{
Alexander Lohnau's avatar
Alexander Lohnau committed
206
    if (!directory.isLocalFile()) {
207
208
209
210
211
        // Short-circuit: Avoid stat if the effective URL hasn't changed
        if (directory == m_fileWidget->baseUrl()) {
            return;
        }

212
213
214
215
216
217
218
        // Qt can not determine if the remote URL points to a file or a
        // directory, that is why options()->initialDirectory() always returns
        // the full URL.
        KIO::StatJob *job = KIO::stat(directory);
        KJobWidgets::setWindow(job, this);
        if (job->exec()) {
            KIO::UDSEntry entry = job->statResult();
219
            if (!entry.isDir()) {
220
221
                // this is probably a file remove the file part
                m_fileWidget->setUrl(directory.adjusted(QUrl::RemoveFilename));
222
                m_fileWidget->setSelectedUrl(directory);
Alexander Lohnau's avatar
Alexander Lohnau committed
223
            } else {
224
225
226
                m_fileWidget->setUrl(directory);
            }
        }
Alexander Lohnau's avatar
Alexander Lohnau committed
227
    } else {
228
229
        m_fileWidget->setUrl(directory);
    }
230
231
}

Alexander Lohnau's avatar
Alexander Lohnau committed
232
bool KDEPlatformFileDialogHelper::isSupportedUrl(const QUrl &url) const
233
234
235
236
{
    return KProtocolInfo::protocols().contains(url.scheme());
}

237
238
239
240
241
242
////////////////////////////////////////////////

KDEPlatformFileDialogHelper::KDEPlatformFileDialogHelper()
    : QPlatformFileDialogHelper()
    , m_dialog(new KDEPlatformFileDialog)
{
David Edmundson's avatar
David Edmundson committed
243
244
245
246
247
248
249
250
251
    connect(m_dialog, &KDEPlatformFileDialogBase::closed, this, &KDEPlatformFileDialogHelper::saveSize);
    connect(m_dialog, &QDialog::finished, this, &KDEPlatformFileDialogHelper::saveSize);
    connect(m_dialog, &KDEPlatformFileDialogBase::currentChanged, this, &QPlatformFileDialogHelper::currentChanged);
    connect(m_dialog, &KDEPlatformFileDialogBase::directoryEntered, this, &QPlatformFileDialogHelper::directoryEntered);
    connect(m_dialog, &KDEPlatformFileDialogBase::fileSelected, this, &QPlatformFileDialogHelper::fileSelected);
    connect(m_dialog, &KDEPlatformFileDialogBase::filesSelected, this, &QPlatformFileDialogHelper::filesSelected);
    connect(m_dialog, &KDEPlatformFileDialogBase::filterSelected, this, &QPlatformFileDialogHelper::filterSelected);
    connect(m_dialog, &QDialog::accepted, this, &QPlatformDialogHelper::accept);
    connect(m_dialog, &QDialog::rejected, this, &QPlatformDialogHelper::reject);
252
253
254
255
}

KDEPlatformFileDialogHelper::~KDEPlatformFileDialogHelper()
{
256
    saveSize();
257
258
259
260
261
    delete m_dialog;
}

void KDEPlatformFileDialogHelper::initializeDialog()
{
262
    m_dialogInitialized = true;
263
    if (options()->testOption(QFileDialogOptions::ShowDirsOnly)) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
264
        m_dialog->deleteLater();
Laurent Montel's avatar
Laurent Montel committed
265
266
267
268
269
270
271
272
273
        KDirSelectDialog *dialog = new KDirSelectDialog(options()->initialDirectory());
        m_dialog = dialog;
        connect(dialog, &QDialog::accepted, this, &QPlatformDialogHelper::accept);
        connect(dialog, &QDialog::rejected, this, &QPlatformDialogHelper::reject);
        if (options()->isLabelExplicitlySet(QFileDialogOptions::Accept)) { // OK button
            dialog->setOkButtonText(options()->labelText(QFileDialogOptions::Accept));
        } else if (options()->isLabelExplicitlySet(QFileDialogOptions::Reject)) { // Cancel button
            dialog->setCancelButtonText(options()->labelText(QFileDialogOptions::Reject));
        } else if (options()->isLabelExplicitlySet(QFileDialogOptions::LookIn)) { // Location label
Alexander Lohnau's avatar
Alexander Lohnau committed
274
            // Not implemented yet.
Laurent Montel's avatar
Laurent Montel committed
275
276
        }

277
278
        if (!options()->windowTitle().isEmpty())
            m_dialog->setWindowTitle(options()->windowTitle());
279
    } else {
280
        // needed for accessing m_fileWidget
Alexander Lohnau's avatar
Alexander Lohnau committed
281
        KDEPlatformFileDialog *dialog = qobject_cast<KDEPlatformFileDialog *>(m_dialog);
282
283
        dialog->m_fileWidget->setOperationMode(options()->acceptMode() == QFileDialogOptions::AcceptOpen ? KFileWidget::Opening : KFileWidget::Saving);
        if (options()->windowTitle().isEmpty()) {
Alexander Lohnau's avatar
Alexander Lohnau committed
284
285
            dialog->setWindowTitle(options()->acceptMode() == QFileDialogOptions::AcceptOpen ? i18nc("@title:window", "Open File")
                                                                                             : i18nc("@title:window", "Save File"));
286
287
288
        } else {
            dialog->setWindowTitle(options()->windowTitle());
        }
289
290
291
        if (!m_directorySet) {
            setDirectory(options()->initialDirectory());
        }
Alexander Lohnau's avatar
Alexander Lohnau committed
292
        // dialog->setViewMode(options()->viewMode()); // don't override our options, fixes remembering the chosen view mode and sizes!
293
        dialog->setFileMode(options()->fileMode());
294

295
296
297
298
299
300
301
302
303
        // custom labels
        if (options()->isLabelExplicitlySet(QFileDialogOptions::Accept)) { // OK button
            dialog->setCustomLabel(QFileDialogOptions::Accept, options()->labelText(QFileDialogOptions::Accept));
        } else if (options()->isLabelExplicitlySet(QFileDialogOptions::Reject)) { // Cancel button
            dialog->setCustomLabel(QFileDialogOptions::Reject, options()->labelText(QFileDialogOptions::Reject));
        } else if (options()->isLabelExplicitlySet(QFileDialogOptions::LookIn)) { // Location label
            dialog->setCustomLabel(QFileDialogOptions::LookIn, options()->labelText(QFileDialogOptions::LookIn));
        }

304
305
306
        const QStringList mimeFilters = options()->mimeTypeFilters();
        const QStringList nameFilters = options()->nameFilters();
        if (!mimeFilters.isEmpty()) {
307
308
309
310
311
312
313
314
            QString defaultMimeFilter;
            if (options()->acceptMode() == QFileDialogOptions::AcceptSave) {
                defaultMimeFilter = options()->initiallySelectedMimeTypeFilter();
                if (defaultMimeFilter.isEmpty()) {
                    defaultMimeFilter = mimeFilters.at(0);
                }
            }
            dialog->m_fileWidget->setMimeFilter(mimeFilters, defaultMimeFilter);
315

Alexander Lohnau's avatar
Alexander Lohnau committed
316
317
            if (mimeFilters.contains(QStringLiteral("inode/directory")))
                dialog->m_fileWidget->setMode(dialog->m_fileWidget->mode() | KFile::Directory);
318
319
        } else if (!nameFilters.isEmpty()) {
            dialog->m_fileWidget->setFilter(qt2KdeFilter(nameFilters));
320
        }
321

322
323
324
325
326
        if (!options()->initiallySelectedMimeTypeFilter().isEmpty()) {
            selectMimeTypeFilter(options()->initiallySelectedMimeTypeFilter());
        } else if (!options()->initiallySelectedNameFilter().isEmpty()) {
            selectNameFilter(options()->initiallySelectedNameFilter());
        }
327
328
329
330

        // overwrite option
        if (options()->testOption(QFileDialogOptions::FileDialogOption::DontConfirmOverwrite)) {
            dialog->m_fileWidget->setConfirmOverwrite(false);
Alexander Lohnau's avatar
Alexander Lohnau committed
331
332
        } else if (options()->acceptMode() == QFileDialogOptions::AcceptSave) {
            dialog->m_fileWidget->setConfirmOverwrite(true);
333
        }
334

Nate Graham's avatar
Nate Graham committed
335
336
        QStringList schemes = options()->supportedSchemes();
        dialog->m_fileWidget->setSupportedSchemes(schemes);
337
    }
338
339
340
341
}

void KDEPlatformFileDialogHelper::exec()
{
342
    restoreSize();
343
    m_dialog->exec();
344
345
346
347
348
349
350
}

void KDEPlatformFileDialogHelper::hide()
{
    m_dialog->hide();
}

351
352
353
354
355
356
357
void KDEPlatformFileDialogHelper::saveSize()
{
    KSharedConfig::Ptr conf = KSharedConfig::openConfig();
    KConfigGroup group = conf->group("FileDialogSize");
    KWindowConfig::saveWindowSize(m_dialog->windowHandle(), group);
}

358
359
360
361
void KDEPlatformFileDialogHelper::restoreSize()
{
    m_dialog->winId(); // ensure there's a window created
    KSharedConfig::Ptr conf = KSharedConfig::openConfig();
362
363
364
365

    // see the note below
    m_dialog->windowHandle()->resize(m_dialog->sizeHint());

366
367
368
369
370
371
372
    KWindowConfig::restoreWindowSize(m_dialog->windowHandle(), conf->group("FileDialogSize"));
    // NOTICE: QWindow::setGeometry() does NOT impact the backing QWidget geometry even if the platform
    // window was created -> QTBUG-40584. We therefore copy the size here.
    // TODO: remove once this was resolved in QWidget QPA
    m_dialog->resize(m_dialog->windowHandle()->size());
}

373
374
375
376
bool KDEPlatformFileDialogHelper::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent)
{
    initializeDialog();
    m_dialog->setWindowFlags(windowFlags);
377
    m_dialog->setWindowModality(windowModality);
378
    restoreSize();
379
    m_dialog->windowHandle()->setTransientParent(parent);
380
    m_dialog->show();
381
382
383
    return true;
}

384
385
386
387
388
389
390
391
392
QVariant KDEPlatformFileDialogHelper::styleHint(StyleHint hint) const
{
    if (hint == DialogIsQtWindow) {
        return true;
    }

    return QPlatformDialogHelper::styleHint(hint);
}

393
394
395
396
397
QList<QUrl> KDEPlatformFileDialogHelper::selectedFiles() const
{
    return m_dialog->selectedFiles();
}

398
399
400
401
402
403
404
405
406
407
QString KDEPlatformFileDialogHelper::selectedMimeTypeFilter() const
{
    return m_dialog->selectedMimeTypeFilter();
}

void KDEPlatformFileDialogHelper::selectMimeTypeFilter(const QString &filter)
{
    m_dialog->selectMimeTypeFilter(filter);
}

408
409
QString KDEPlatformFileDialogHelper::selectedNameFilter() const
{
410
    return kde2QtFilter(options()->nameFilters(), m_dialog->selectedNameFilter(), m_dialog->currentFilterText());
411
412
413
414
415
416
417
}

QUrl KDEPlatformFileDialogHelper::directory() const
{
    return m_dialog->directory();
}

418
void KDEPlatformFileDialogHelper::selectFile(const QUrl &filename)
419
420
{
    m_dialog->selectFile(filename);
421
    m_fileSelected = true;
422
423
}

424
void KDEPlatformFileDialogHelper::setDirectory(const QUrl &directory)
425
{
426
427
    if (!directory.isEmpty()) {
        m_dialog->setDirectory(directory);
428
        m_directorySet = true;
429
    }
430
431
}

432
void KDEPlatformFileDialogHelper::selectNameFilter(const QString &filter)
433
434
435
436
437
438
439
440
441
442
443
444
{
    m_dialog->selectNameFilter(qt2KdeFilter(QStringList(filter)));
}

void KDEPlatformFileDialogHelper::setFilter()
{
}

bool KDEPlatformFileDialogHelper::defaultNameFilterDisables() const
{
    return false;
}