filechooser.cpp 11.4 KB
Newer Older
Jan Grulich's avatar
Jan Grulich committed
1
/*
2
 * Copyright © 2016-2018 Red Hat, Inc
Jan Grulich's avatar
Jan Grulich committed
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *       Jan Grulich <jgrulich@redhat.com>
 */

#include "filechooser.h"
22
#include "utils.h"
Jan Grulich's avatar
Jan Grulich committed
23

24
#include <QDialogButtonBox>
25
#include <QDBusMetaType>
26
#include <QDBusArgument>
Jan Grulich's avatar
Jan Grulich committed
27
#include <QLoggingCategory>
28
29
30
31
#include <QPushButton>
#include <QVBoxLayout>
#include <QUrl>

Jan Grulich's avatar
Jan Grulich committed
32
#include <KLocalizedString>
33
#include <KFileWidget>
Jan Grulich's avatar
Jan Grulich committed
34

Jan Grulich's avatar
Jan Grulich committed
35
Q_LOGGING_CATEGORY(XdgDesktopPortalKdeFileChooser, "xdp-kde-file-chooser")
Jan Grulich's avatar
Jan Grulich committed
36

37
// Keep in sync with qflatpakfiledialog from flatpak-platform-plugin
38
39
40
41
Q_DECLARE_METATYPE(FileChooserPortal::Filter)
Q_DECLARE_METATYPE(FileChooserPortal::Filters)
Q_DECLARE_METATYPE(FileChooserPortal::FilterList)
Q_DECLARE_METATYPE(FileChooserPortal::FilterListList)
42

43
QDBusArgument &operator << (QDBusArgument &arg, const FileChooserPortal::Filter &filter)
44
45
46
47
48
49
50
{
    arg.beginStructure();
    arg << filter.type << filter.filterString;
    arg.endStructure();
    return arg;
}

51
const QDBusArgument &operator >> (const QDBusArgument &arg, FileChooserPortal::Filter &filter)
52
53
54
55
56
57
58
59
60
61
62
63
{
    uint type;
    QString filterString;
    arg.beginStructure();
    arg >> type >> filterString;
    filter.type = type;
    filter.filterString = filterString;
    arg.endStructure();

    return arg;
}

64
QDBusArgument &operator << (QDBusArgument &arg, const FileChooserPortal::FilterList &filterList)
65
66
67
68
69
70
71
{
    arg.beginStructure();
    arg << filterList.userVisibleName << filterList.filters;
    arg.endStructure();
    return arg;
}

72
const QDBusArgument &operator >> (const QDBusArgument &arg, FileChooserPortal::FilterList &filterList)
73
74
{
    QString userVisibleName;
75
    FileChooserPortal::Filters filters;
76
77
78
79
80
81
82
83
    arg.beginStructure();
    arg >> userVisibleName >> filters;
    filterList.userVisibleName = userVisibleName;
    filterList.filters = filters;
    arg.endStructure();

    return arg;
}
Jan Grulich's avatar
Jan Grulich committed
84

85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
FileDialog::FileDialog(QDialog *parent, Qt::WindowFlags flags)
    : QDialog(parent, flags)
    , m_fileWidget(new KFileWidget(QUrl(), this))
{
    setLayout(new QVBoxLayout);
    layout()->addWidget(m_fileWidget);

    m_buttons = new QDialogButtonBox(this);
    m_buttons->addButton(m_fileWidget->okButton(), QDialogButtonBox::AcceptRole);
    m_buttons->addButton(m_fileWidget->cancelButton(), QDialogButtonBox::RejectRole);
    connect(m_buttons, SIGNAL(rejected()), m_fileWidget, SLOT(slotCancel()));
    connect(m_fileWidget->okButton(), SIGNAL(clicked(bool)), m_fileWidget, SLOT(slotOk()));
    connect(m_fileWidget, SIGNAL(accepted()), m_fileWidget, SLOT(accept()));
    connect(m_fileWidget, SIGNAL(accepted()), SLOT(accept()));
    connect(m_fileWidget->cancelButton(), SIGNAL(clicked(bool)), SLOT(reject()));
    layout()->addWidget(m_buttons);
}

FileDialog::~FileDialog()
{
}

107
108
FileChooserPortal::FileChooserPortal(QObject *parent)
    : QDBusAbstractAdaptor(parent)
Jan Grulich's avatar
Jan Grulich committed
109
{
110
111
112
113
    qDBusRegisterMetaType<Filter>();
    qDBusRegisterMetaType<Filters>();
    qDBusRegisterMetaType<FilterList>();
    qDBusRegisterMetaType<FilterListList>();
Jan Grulich's avatar
Jan Grulich committed
114
115
}

116
FileChooserPortal::~FileChooserPortal()
Jan Grulich's avatar
Jan Grulich committed
117
118
119
{
}

120
uint FileChooserPortal::OpenFile(const QDBusObjectPath &handle,
Jan Grulich's avatar
Jan Grulich committed
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
                           const QString &app_id,
                           const QString &parent_window,
                           const QString &title,
                           const QVariantMap &options,
                           QVariantMap &results)
{
    Q_UNUSED(app_id);

    qCDebug(XdgDesktopPortalKdeFileChooser) << "OpenFile called with parameters:";
    qCDebug(XdgDesktopPortalKdeFileChooser) << "    handle: " << handle.path();
    qCDebug(XdgDesktopPortalKdeFileChooser) << "    parent_window: " << parent_window;
    qCDebug(XdgDesktopPortalKdeFileChooser) << "    title: " << title;
    qCDebug(XdgDesktopPortalKdeFileChooser) << "    options: " << options;

    bool modalDialog = true;
    bool multipleFiles = false;
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
    QString acceptLabel;
    QStringList nameFilters;
    QStringList mimeTypeFilters;

    /* TODO
     * choices a(ssa(ss)s)
     * List of serialized combo boxes to add to the file chooser.
     *
     * For each element, the first string is an ID that will be returned with the response, te second string is a user-visible label.
     * The a(ss) is the list of choices, each being a is an ID and a user-visible label. The final string is the initial selection,
     * or "", to let the portal decide which choice will be initially selected. None of the strings, except for the initial selection, should be empty.
     *
     * As a special case, passing an empty array for the list of choices indicates a boolean choice that is typically displayed as a check button, using "true" and "false" as the choices.
     * Example: [('encoding', 'Encoding', [('utf8', 'Unicode (UTF-8)'), ('latin15', 'Western')], 'latin15'), ('reencode', 'Reencode', [], 'false')]
     */
Jan Grulich's avatar
Jan Grulich committed
152

153
154
    if (options.contains(QLatin1String("accept_label"))) {
        acceptLabel = options.value(QLatin1String("accept_label")).toString();
Jan Grulich's avatar
Jan Grulich committed
155
156
157
158
159
160
    }

    if (options.contains(QLatin1String("modal"))) {
        modalDialog = options.value(QLatin1String("modal")).toBool();
    }

161
162
163
164
165
166
    if (options.contains(QLatin1String("multiple"))) {
        multipleFiles = options.value(QLatin1String("multiple")).toBool();
    }

    if (options.contains(QLatin1String("filters"))) {
        FilterListList filterListList = qdbus_cast<FilterListList>(options.value(QLatin1String("filters")));
Jan Grulich's avatar
Jan Grulich committed
167
        for (const FilterList &filterList : filterListList) {
168
            QStringList filterStrings;
Jan Grulich's avatar
Jan Grulich committed
169
            for (const Filter &filterStruct : filterList.filters) {
170
171
172
173
174
175
176
177
                if (filterStruct.type == 0) {
                    filterStrings << filterStruct.filterString;
                } else {
                    mimeTypeFilters << filterStruct.filterString;
                }
            }

            if (!filterStrings.isEmpty()) {
178
                nameFilters << QStringLiteral("%1|%2").arg(filterStrings.join(QLatin1Char(' '))).arg(filterList.userVisibleName);
179
180
            }
        }
Jan Grulich's avatar
Jan Grulich committed
181
182
    }

183
    QScopedPointer<FileDialog, QScopedPointerDeleteLater> fileDialog(new FileDialog());
184
    Utils::setParentWindow(fileDialog.data(), parent_window);
Jan Grulich's avatar
Jan Grulich committed
185
186
    fileDialog->setWindowTitle(title);
    fileDialog->setModal(modalDialog);
187
188
    fileDialog->m_fileWidget->setMode(multipleFiles ? KFile::Mode::File | KFile::Mode::ExistingOnly : KFile::Mode::Files | KFile::Mode::ExistingOnly);
    fileDialog->m_fileWidget->okButton()->setText(!acceptLabel.isEmpty() ? acceptLabel : i18n("Open"));
Jan Grulich's avatar
Jan Grulich committed
189

190
    if (!nameFilters.isEmpty()) {
191
        fileDialog->m_fileWidget->setFilter(nameFilters.join(QLatin1Char('\n')));
192
193
194
    }

    if (!mimeTypeFilters.isEmpty()) {
195
        fileDialog->m_fileWidget->setMimeFilter(mimeTypeFilters);
196
197
    }

Jan Grulich's avatar
Jan Grulich committed
198
199
    if (fileDialog->exec() == QDialog::Accepted) {
        QStringList files;
200
        for (const QString &filename : fileDialog->m_fileWidget->selectedFiles()) {
Jan Grulich's avatar
Jan Grulich committed
201
202
203
204
205
206
207
208
209
210
           QUrl url = QUrl::fromLocalFile(filename);
           files << url.toDisplayString();
        }
        results.insert(QLatin1String("uris"), files);
        return 0;
    }

    return 1;
}

211
uint FileChooserPortal::SaveFile(const QDBusObjectPath &handle,
Jan Grulich's avatar
Jan Grulich committed
212
213
214
215
216
217
218
219
220
221
222
223
224
225
                           const QString &app_id,
                           const QString &parent_window,
                           const QString &title,
                           const QVariantMap &options,
                           QVariantMap &results)
{
    Q_UNUSED(app_id);

    qCDebug(XdgDesktopPortalKdeFileChooser) << "SaveFile called with parameters:";
    qCDebug(XdgDesktopPortalKdeFileChooser) << "    handle: " << handle.path();
    qCDebug(XdgDesktopPortalKdeFileChooser) << "    parent_window: " << parent_window;
    qCDebug(XdgDesktopPortalKdeFileChooser) << "    title: " << title;
    qCDebug(XdgDesktopPortalKdeFileChooser) << "    options: " << options;

226
    bool modalDialog = true;
Jan Grulich's avatar
Jan Grulich committed
227
228
229
230
    QString acceptLabel;
    QString currentName;
    QString currentFolder;
    QString currentFile;
231
232
    QStringList nameFilters;
    QStringList mimeTypeFilters;
Jan Grulich's avatar
Jan Grulich committed
233

234
    // TODO parse options - choices
Jan Grulich's avatar
Jan Grulich committed
235
236
237
238
239
240
241
242
243
244
245
246
247
248

    if (options.contains(QLatin1String("modal"))) {
        modalDialog = options.value(QLatin1String("modal")).toBool();
    }

    if (options.contains(QLatin1String("accept_label"))) {
        acceptLabel = options.value(QLatin1String("accept_label")).toString();
    }

    if (options.contains(QLatin1String("current_name"))) {
        currentName = options.value(QLatin1String("current_name")).toString();
    }

    if (options.contains(QLatin1String("current_folder"))) {
249
        currentFolder = QString::fromUtf8(options.value(QLatin1String("current_folder")).toByteArray());
Jan Grulich's avatar
Jan Grulich committed
250
251
252
    }

    if (options.contains(QLatin1String("current_file"))) {
253
        currentFile = QString::fromUtf8(options.value(QLatin1String("current_file")).toByteArray());
Jan Grulich's avatar
Jan Grulich committed
254
255
    }

256
257
    if (options.contains(QLatin1String("filters"))) {
        FilterListList filterListList = qdbus_cast<FilterListList>(options.value(QLatin1String("filters")));
Jan Grulich's avatar
Jan Grulich committed
258
        for (const FilterList &filterList : filterListList) {
259
            QStringList filterStrings;
Jan Grulich's avatar
Jan Grulich committed
260
            for (const Filter &filterStruct : filterList.filters) {
261
262
263
264
265
266
267
268
                if (filterStruct.type == 0) {
                    filterStrings << filterStruct.filterString;
                } else {
                    mimeTypeFilters << filterStruct.filterString;
                }
            }

            if (!filterStrings.isEmpty()) {
269
                nameFilters << QStringLiteral("%1|%2").arg(filterStrings.join(QLatin1Char(' '))).arg(filterList.userVisibleName);
270
271
272
273
            }
        }
    }

274
    QScopedPointer<FileDialog, QScopedPointerDeleteLater> fileDialog(new FileDialog());
275
    Utils::setParentWindow(fileDialog.data(), parent_window);
Jan Grulich's avatar
Jan Grulich committed
276
277
    fileDialog->setWindowTitle(title);
    fileDialog->setModal(modalDialog);
278
    fileDialog->m_fileWidget->setOperationMode(KFileWidget::Saving);
Jan Grulich's avatar
Jan Grulich committed
279
280

    if (!currentFolder.isEmpty()) {
281
        fileDialog->m_fileWidget->setUrl(QUrl::fromLocalFile(currentFolder));
Jan Grulich's avatar
Jan Grulich committed
282
283
284
    }

    if (!currentFile.isEmpty()) {
285
        fileDialog->m_fileWidget->setSelectedUrl(QUrl::fromLocalFile(currentFile));
Jan Grulich's avatar
Jan Grulich committed
286
287
    }

288
    if (!currentName.isEmpty()) {
289
290
        const QUrl url = fileDialog->m_fileWidget->baseUrl();
        fileDialog->m_fileWidget->setSelectedUrl(QUrl::fromLocalFile(QStringLiteral("%1/%2").arg(url.toDisplayString(QUrl::StripTrailingSlash), currentName)));
291
292
    }

Jan Grulich's avatar
Jan Grulich committed
293
    if (!acceptLabel.isEmpty()) {
294
        fileDialog->m_fileWidget->okButton()->setText(acceptLabel);
Jan Grulich's avatar
Jan Grulich committed
295
296
    }

297
    if (!nameFilters.isEmpty()) {
298
        fileDialog->m_fileWidget->setFilter(nameFilters.join(QLatin1Char('\n')));
299
300
301
    }

    if (!mimeTypeFilters.isEmpty()) {
302
        fileDialog->m_fileWidget->setMimeFilter(mimeTypeFilters);
303
304
    }

Jan Grulich's avatar
Jan Grulich committed
305
306
    if (fileDialog->exec() == QDialog::Accepted) {
        QStringList files;
307
308
        QUrl url = QUrl::fromLocalFile(fileDialog->m_fileWidget->selectedFile());
        files << url.toDisplayString();
Jan Grulich's avatar
Jan Grulich committed
309
310
311
312
313
314
315
        results.insert(QLatin1String("uris"), files);
        return 0;
    }

    return 1;
}