KoFileDialog.cpp 13.9 KB
Newer Older
Yue Liu's avatar
Yue Liu committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* This file is part of the KDE project
   Copyright (C) 2013 - 2014 Yue Liu <yue.liu@mail.com>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
*/

#include "KoFileDialog.h"
#include <QDebug>
#include <QFileDialog>
#include <QApplication>
24
#include <QImageReader>
25
#include <QClipboard>
Yue Liu's avatar
Yue Liu committed
26
27

#include <kconfiggroup.h>
28
#include <ksharedconfig.h>
29
#include <klocalizedstring.h>
30

31
#include <KisMimeDatabase.h>
32
#include <KoJsonTrader.h>
Yue Liu's avatar
Yue Liu committed
33

34
class Q_DECL_HIDDEN KoFileDialog::Private
Yue Liu's avatar
Yue Liu committed
35
36
37
{
public:
    Private(QWidget *parent_,
38
            KoFileDialog::DialogType dialogType_,
Yue Liu's avatar
Yue Liu committed
39
40
            const QString caption_,
            const QString defaultDir_,
Halla Rempt's avatar
Halla Rempt committed
41
            const QString dialogName_)
Yue Liu's avatar
Yue Liu committed
42
        : parent(parent_)
43
        , type(dialogType_)
Halla Rempt's avatar
Halla Rempt committed
44
        , dialogName(dialogName_)
Yue Liu's avatar
Yue Liu committed
45
        , caption(caption_)
46
        , defaultDirectory(defaultDir_)
Yue Liu's avatar
Yue Liu committed
47
48
        , filterList(QStringList())
        , defaultFilter(QString())
49
        , swapExtensionOrder(false)
Yue Liu's avatar
Yue Liu committed
50
51
52
    {
    }

53
54
55
56
    ~Private()
    {
    }

Yue Liu's avatar
Yue Liu committed
57
    QWidget *parent;
58
    KoFileDialog::DialogType type;
Halla Rempt's avatar
Halla Rempt committed
59
    QString dialogName;
Yue Liu's avatar
Yue Liu committed
60
    QString caption;
61
    QString defaultDirectory;
62
    QString proposedFileName;
63
    QUrl defaultUri;
Yue Liu's avatar
Yue Liu committed
64
65
    QStringList filterList;
    QString defaultFilter;
66
    QScopedPointer<QFileDialog> fileDialog;
67
    QString mimeType;
68
    bool swapExtensionOrder;
Yue Liu's avatar
Yue Liu committed
69
70
71
};

KoFileDialog::KoFileDialog(QWidget *parent,
72
                           KoFileDialog::DialogType type,
73
                           const QString &dialogName)
Halla Rempt's avatar
Halla Rempt committed
74
    : d(new Private(parent, type, "", getUsedDir(dialogName), dialogName))
Yue Liu's avatar
Yue Liu committed
75
76
77
78
79
80
81
82
{
}

KoFileDialog::~KoFileDialog()
{
    delete d;
}

83
84
85
86
87
void KoFileDialog::setCaption(const QString &caption)
{
    d->caption = caption;
}

88
void KoFileDialog::setDefaultDir(const QString &defaultDir, bool force)
89
{
90
91
92
    if (!defaultDir.isEmpty()) {
        if (d->defaultDirectory.isEmpty() || force) {
            QFileInfo f(defaultDir);
93
94
95
96
97
98
            if (f.isDir()) {
                d->defaultDirectory = defaultDir;
            }
            else {
                d->defaultDirectory = f.absolutePath();
            }
99
100
101
102
        }
        if (!QFileInfo(defaultDir).isDir()) {
            d->proposedFileName = QFileInfo(defaultDir).fileName();
        }
103
    }
104
105
}

106
107
108
109
110
void KoFileDialog::setDirectoryUrl(const QUrl &defaultUri)
{
    d->defaultUri = defaultUri;
}

111
112
113
114
void KoFileDialog::setImageFilters()
{
    QStringList imageFilters;
    // add filters for all formats supported by QImage
115
    Q_FOREACH (const QByteArray &format, QImageReader::supportedImageFormats()) {
Halla Rempt's avatar
Halla Rempt committed
116
        imageFilters << QLatin1String("image/") + format;
117
118
    }
    setMimeTypeFilters(imageFilters);
119
120
}

121
void KoFileDialog::setMimeTypeFilters(const QStringList &mimeTypeList, QString defaultMimeType)
Yue Liu's avatar
Yue Liu committed
122
{
123
    d->filterList = getFilterStringListFromMime(mimeTypeList, true);
124

125
126
127
    QString defaultFilter;

    if (!defaultMimeType.isEmpty()) {
128
        QString suffix = KisMimeDatabase::suffixesForMimeType(defaultMimeType).first();
129
130

        if (!d->proposedFileName.isEmpty()) {
131
            d->proposedFileName = QFileInfo(d->proposedFileName).completeBaseName() + "." + suffix;
132
133
134
        }

        QStringList defaultFilters = getFilterStringListFromMime(QStringList() << defaultMimeType, false);
135
136
137
138
        if (defaultFilters.size() > 0) {
            defaultFilter = defaultFilters.first();
        }
    }
139

Yue Liu's avatar
Yue Liu committed
140
141
142
    d->defaultFilter = defaultFilter;
}

143
144
QString KoFileDialog::selectedNameFilter() const
{
145
    return d->fileDialog->selectedNameFilter();
146
147
148
149
}

QString KoFileDialog::selectedMimeType() const
{
150
    return d->mimeType;
151
152
}

153
154
void KoFileDialog::createFileDialog()
{
155
    d->fileDialog.reset(new QFileDialog(d->parent, d->caption, d->defaultDirectory + "/" + d->proposedFileName));
156
157
158
    if (!d->defaultUri.isEmpty()) {
        d->fileDialog->setDirectoryUrl(d->defaultUri);
    }
159
160
161
    KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");

    bool dontUseNative = true;
162
163
164
#ifdef Q_OS_ANDROID
    dontUseNative = false;
#endif
165
#ifdef Q_OS_UNIX
166
    if (qgetenv("XDG_CURRENT_DESKTOP") == "KDE") {
167
        dontUseNative = false;
168
169
    }
#endif
170
171
172
#ifdef Q_OS_WIN
    dontUseNative = false;
#endif
173

174
175
    bool optionDontUseNative = group.readEntry("DontUseNativeFileDialog", dontUseNative);
    d->fileDialog->setOption(QFileDialog::DontUseNativeDialog, optionDontUseNative);
176
    d->fileDialog->setOption(QFileDialog::DontConfirmOverwrite, false);
177
    d->fileDialog->setOption(QFileDialog::HideNameFilterDetails, dontUseNative ? true : false);
178

179
#ifdef Q_OS_MACOS
180
    QList<QUrl> urls = d->fileDialog->sidebarUrls();
Halla Rempt's avatar
Halla Rempt committed
181
    QUrl volumes = QUrl::fromLocalFile("/Volumes");
182
183
184
185
186
187
188
    if (!urls.contains(volumes)) {
        urls.append(volumes);
    }

    d->fileDialog->setSidebarUrls(urls);
#endif

189
    if (d->type == SaveFile) {
190
191
        d->fileDialog->setAcceptMode(QFileDialog::AcceptSave);
        d->fileDialog->setFileMode(QFileDialog::AnyFile);
192
193

#ifdef Q_OS_ANDROID
194
195
        // HACK: discovered by looking into the code
        d->fileDialog->setWindowTitle(d->proposedFileName.isEmpty() ? "Untitled.kra" : d->proposedFileName);
196
#endif
197
198
199
    }
    else { // open / import

200
        d->fileDialog->setAcceptMode(QFileDialog::AcceptOpen);
201

Halla Rempt's avatar
Fix dox    
Halla Rempt committed
202
        if (d->type == ImportDirectory || d->type == OpenDirectory){
203
204
            d->fileDialog->setFileMode(QFileDialog::Directory);
            d->fileDialog->setOption(QFileDialog::ShowDirsOnly, true);
205
206
207
208
209
        }
        else { // open / import file(s)
            if (d->type == OpenFile
                    || d->type == ImportFile)
            {
210
                d->fileDialog->setFileMode(QFileDialog::ExistingFile);
211
212
            }
            else { // files
213
                d->fileDialog->setFileMode(QFileDialog::ExistingFiles);
Yue Liu's avatar
Yue Liu committed
214
215
216
217
            }
        }
    }

218
#ifndef Q_OS_ANDROID
219
    d->fileDialog->setNameFilters(d->filterList);
220

221
    if (!d->proposedFileName.isEmpty()) {
222
223
        QString mime = KisMimeDatabase::mimeTypeForFile(d->proposedFileName, d->type == KoFileDialog::SaveFile ? false : true);

224
225
226
227
228
229
230
231
232
        QString description = KisMimeDatabase::descriptionForMimeType(mime);
        Q_FOREACH(const QString &filter, d->filterList) {
            if (filter.startsWith(description)) {
                d->fileDialog->selectNameFilter(filter);
                break;
            }
        }
    }
    else if (!d->defaultFilter.isEmpty()) {
233
        d->fileDialog->selectNameFilter(d->defaultFilter);
Yue Liu's avatar
Yue Liu committed
234
    }
235
#endif
Yue Liu's avatar
Yue Liu committed
236

237
    if (d->type == ImportDirectory ||
238
            d->type == ImportFile || d->type == ImportFiles ||
239
            d->type == SaveFile) {
240
241
242
243
244
245
246
247
248

        bool allowModal = true;
// MacOS do not declare native file dialog as modal BUG:413241.
#ifdef Q_OS_MACOS
        allowModal = optionDontUseNative;
#endif
        if (allowModal) {
            d->fileDialog->setWindowModality(Qt::WindowModal);
        }
Yue Liu's avatar
Yue Liu committed
249
250
251
    }
}

252
QString KoFileDialog::filename()
Yue Liu's avatar
Yue Liu committed
253
{
254
    QString url;
255
256
257
    createFileDialog();
    if (d->fileDialog->exec() == QDialog::Accepted) {
        url = d->fileDialog->selectedFiles().first();
258
    }
259

260
    if (!url.isEmpty()) {
261
262
263
264
265
266
        QString suffix = QFileInfo(url).suffix();
        if (KisMimeDatabase::mimeTypeForSuffix(suffix).isEmpty()) {
            suffix = "";
        }

        if (d->type == SaveFile && suffix.isEmpty()) {
267
268
269
270
271
272
273
274
275
276
            QString selectedFilter;
            // index 0 is all supported; if that is chosen, saveDocument will automatically make it .kra
            for (int i = 1; i < d->filterList.size(); ++i) {
                if (d->filterList[i].startsWith(d->fileDialog->selectedNameFilter())) {
                    selectedFilter = d->filterList[i];
                    break;
                }
            }
            int start = selectedFilter.indexOf("*.") + 1;
            int end = selectedFilter.indexOf(" ", start);
277
            int n = end - start;
278
            QString extension = selectedFilter.mid(start, n);
279
280
281
            if (!(extension.contains(".") || url.endsWith("."))) {
                extension = "." + extension;
            }
282
283
// We can only write to the Uri that was returned, we don't have permission to change the Uri.
#ifndef Q_OS_ANDROID
284
            url = url + extension;
285
#endif
286
        }
287

288
        d->mimeType = KisMimeDatabase::mimeTypeForFile(url, d->type == KoFileDialog::SaveFile ? false : true);
289
290
        saveUsedDir(url, d->dialogName);
    }
291
    return url;
292
293
}

294
QStringList KoFileDialog::filenames()
Yue Liu's avatar
Yue Liu committed
295
{
296
    QStringList urls;
297

298
299
300
    createFileDialog();
    if (d->fileDialog->exec() == QDialog::Accepted) {
        urls = d->fileDialog->selectedFiles();
301
    }
302
303
304
    if (urls.size() > 0) {
        saveUsedDir(urls.first(), d->dialogName);
    }
305
    return urls;
306
307
}

308
QStringList KoFileDialog::splitNameFilter(const QString &nameFilter, QStringList *mimeList)
309
{
310
311
    Q_ASSERT(mimeList);

312
313
314
315
316
317
318
319
    QStringList filters;
    QString description;

    if (nameFilter.contains("(")) {
        description = nameFilter.left(nameFilter.indexOf("(") -1).trimmed();
    }

    QStringList entries = nameFilter.mid(nameFilter.indexOf("(") + 1).split(" ",QString::SkipEmptyParts );
320
    entries.sort();
321
    Q_FOREACH (QString entry, entries) {
322
323
324
325

        entry = entry.remove("*");
        entry = entry.remove(")");

326
327
328
329
330
        QString mimeType = KisMimeDatabase::mimeTypeForSuffix(entry);
        if (mimeType != "application/octet-stream") {
            if (!mimeList->contains(mimeType)) {
                mimeList->append(mimeType);
                filters.append(KisMimeDatabase::descriptionForMimeType(mimeType) + " ( *" + entry + " )");
331
            }
332
333
        }
        else {
334
            filters.append(entry.remove(".").toUpper() + " " + description + " ( *." + entry + " )");
335
336
337
338
339
        }
    }
    return filters;
}

340
const QStringList KoFileDialog::getFilterStringListFromMime(const QStringList &_mimeList,
341
                                                            bool withAllSupportedEntry)
Yue Liu's avatar
Yue Liu committed
342
{
343
344
    QStringList mimeSeen;

Halla Rempt's avatar
Halla Rempt committed
345
346
347
348
349
350
351
    // 1
    QString allSupported;
    // 2
    QString kritaNative;
    // 3
    QString ora;

Yue Liu's avatar
Yue Liu committed
352
    QStringList ret;
353
354
    QStringList mimeList = _mimeList;
    mimeList.sort();
355
356
    Q_FOREACH(const QString &mimeType, mimeList) {
        if (!mimeSeen.contains(mimeType)) {
Halla Rempt's avatar
Halla Rempt committed
357
            QString description = KisMimeDatabase::descriptionForMimeType(mimeType);
358
            if (description.isEmpty() && !mimeType.isEmpty()) {
359
360
361
362
363
                description = mimeType.split("/")[1];
                if (description.startsWith("x-")) {
                    description = description.remove(0, 2);
                }
            }
Halla Rempt's avatar
Halla Rempt committed
364
365


366
            QString oneFilter;
367
368
369
            QStringList patterns = KisMimeDatabase::suffixesForMimeType(mimeType);
            QStringList globPatterns;
            Q_FOREACH(const QString &pattern, patterns) {
Halla Rempt's avatar
Fix dox    
Halla Rempt committed
370
371
372
373
374
375
376
377
378
                if (pattern.startsWith(".")) {
                    globPatterns << "*" + pattern;
                }
                else if (pattern.startsWith("*.")) {
                    globPatterns << pattern;
                }
                else {
                    globPatterns << "*." + pattern;
                }
379
380
381
            }

            Q_FOREACH(const QString &glob, globPatterns) {
382
                if (d->swapExtensionOrder) {
383
                    oneFilter.prepend(glob + " ");
384
                    if (withAllSupportedEntry) {
Halla Rempt's avatar
Halla Rempt committed
385
                        allSupported.prepend(glob + " ");
386
                    }
387
#ifdef Q_OS_LINUX
388
389
                    if (qgetenv("XDG_CURRENT_DESKTOP") == "GNOME") {
                        oneFilter.prepend(glob.toUpper() + " ");
390
391
392
                        if (withAllSupportedEntry) {
                            allSupported.prepend(glob.toUpper() + " ");
                        }
393
                    }
394
395
#endif

396
397
                }
                else {
398
                    oneFilter.append(glob + " ");
399
                    if (withAllSupportedEntry) {
Halla Rempt's avatar
Halla Rempt committed
400
                        allSupported.append(glob + " ");
401
                    }
402
#ifdef Q_OS_LINUX
403
404
405
406
407
                    if (qgetenv("XDG_CURRENT_DESKTOP") == "GNOME") {
                        oneFilter.append(glob.toUpper() + " ");
                        if (withAllSupportedEntry) {
                            allSupported.append(glob.toUpper() + " ");
                        }
408
409
                    }
#endif
410
                }
Yue Liu's avatar
Yue Liu committed
411
            }
Halla Rempt's avatar
Fix dox    
Halla Rempt committed
412
413
414
415

            Q_ASSERT(!description.isEmpty());

            oneFilter = description + " ( " + oneFilter + ")";
Halla Rempt's avatar
Halla Rempt committed
416
417


Halla Rempt's avatar
Fix dox    
Halla Rempt committed
418
            if (mimeType == "application/x-krita") {
Halla Rempt's avatar
Halla Rempt committed
419
420
421
422
423
424
                kritaNative = oneFilter;
                continue;
            }
            if (mimeType == "image/openraster") {
                ora = oneFilter;
                continue;
Halla Rempt's avatar
Fix dox    
Halla Rempt committed
425
426
427
428
            }
            else {
                ret << oneFilter;
            }
429
            mimeSeen << mimeType;
Yue Liu's avatar
Yue Liu committed
430
431
432
        }
    }

Halla Rempt's avatar
Halla Rempt committed
433
434
435
436
437
438
    ret.sort();
    ret.removeDuplicates();

    if (!ora.isEmpty()) ret.prepend(ora);
    if (!kritaNative.isEmpty())  ret.prepend(kritaNative);
    if (!allSupported.isEmpty()) ret.prepend(i18n("All supported formats") + " ( " + allSupported + (")"));
439

Yue Liu's avatar
Yue Liu committed
440
    return ret;
Halla Rempt's avatar
Fix dox    
Halla Rempt committed
441

Yue Liu's avatar
Yue Liu committed
442
443
}

444
QString KoFileDialog::getUsedDir(const QString &dialogName)
Yue Liu's avatar
Yue Liu committed
445
{
446
    if (dialogName.isEmpty()) return "";
Yue Liu's avatar
Yue Liu committed
447

448
    KConfigGroup group =  KSharedConfig::openConfig()->group("File Dialogs");
449
    QString dir = group.readEntry(dialogName, "");
450
    return dir;
Yue Liu's avatar
Yue Liu committed
451
452
}

453
454
void KoFileDialog::saveUsedDir(const QString &fileName,
                               const QString &dialogName)
Yue Liu's avatar
Yue Liu committed
455
{
456

Yue Liu's avatar
Yue Liu committed
457
458
459
    if (dialogName.isEmpty()) return;

    QFileInfo fileInfo(fileName);
460
    KConfigGroup group =  KSharedConfig::openConfig()->group("File Dialogs");
Yue Liu's avatar
Yue Liu committed
461
462
463
    group.writeEntry(dialogName, fileInfo.absolutePath());

}