1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Quick Dialogs module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qquickabstractfiledialog_p.h"
41 #include "qquickitem.h"
42 
43 #include <private/qguiapplication_p.h>
44 #include <QQmlEngine>
45 #include <QQuickWindow>
46 #include <QRegularExpression>
47 #include <QWindow>
48 
49 QT_BEGIN_NAMESPACE
50 
QQuickAbstractFileDialog(QObject * parent)51 QQuickAbstractFileDialog::QQuickAbstractFileDialog(QObject *parent)
52     : QQuickAbstractDialog(parent)
53     , m_dlgHelper(0)
54     , m_options(QFileDialogOptions::create())
55     , m_selectExisting(true)
56     , m_selectMultiple(false)
57     , m_selectFolder(false)
58     , m_sidebarVisible(true)
59 {
60     updateModes();
61     connect(this, SIGNAL(accepted()), this, SIGNAL(selectionAccepted()));
62 }
63 
~QQuickAbstractFileDialog()64 QQuickAbstractFileDialog::~QQuickAbstractFileDialog()
65 {
66 }
67 
setVisible(bool v)68 void QQuickAbstractFileDialog::setVisible(bool v)
69 {
70     if (helper() && v) {
71         m_dlgHelper->setOptions(m_options);
72         m_dlgHelper->setFilter();
73         emit filterSelected();
74     }
75     QQuickAbstractDialog::setVisible(v);
76 }
77 
title() const78 QString QQuickAbstractFileDialog::title() const
79 {
80     return m_options->windowTitle();
81 }
82 
setTitle(const QString & t)83 void QQuickAbstractFileDialog::setTitle(const QString &t)
84 {
85     if (m_options->windowTitle() == t) return;
86     m_options->setWindowTitle(t);
87     emit titleChanged();
88 }
89 
setSelectExisting(bool selectExisting)90 void QQuickAbstractFileDialog::setSelectExisting(bool selectExisting)
91 {
92     if (selectExisting == m_selectExisting) return;
93     m_selectExisting = selectExisting;
94     updateModes();
95 }
96 
setSelectMultiple(bool selectMultiple)97 void QQuickAbstractFileDialog::setSelectMultiple(bool selectMultiple)
98 {
99     if (selectMultiple == m_selectMultiple) return;
100     m_selectMultiple = selectMultiple;
101     updateModes();
102 }
103 
setSelectFolder(bool selectFolder)104 void QQuickAbstractFileDialog::setSelectFolder(bool selectFolder)
105 {
106     if (selectFolder == m_selectFolder) return;
107     m_selectFolder = selectFolder;
108     updateModes();
109 }
110 
folder() const111 QUrl QQuickAbstractFileDialog::folder() const
112 {
113     if (m_dlgHelper && !m_dlgHelper->directory().isEmpty())
114         return m_dlgHelper->directory();
115     return m_options->initialDirectory();
116 }
117 
fixupFolder(const QUrl & f)118 static QUrl fixupFolder(const QUrl &f)
119 {
120     QString lf = f.toLocalFile();
121 #ifndef Q_OS_WIN // Don't mangle Windows network paths
122     while (lf.startsWith("//"))
123         lf.remove(0, 1);
124 #endif
125     if (lf.isEmpty())
126         lf = QDir::currentPath();
127     return QUrl::fromLocalFile(lf);
128 }
129 
setFolder(const QUrl & f)130 void QQuickAbstractFileDialog::setFolder(const QUrl &f)
131 {
132     QUrl u = fixupFolder(f);
133     if (m_dlgHelper)
134         m_dlgHelper->setDirectory(u);
135     m_options->setInitialDirectory(u);
136     emit folderChanged();
137 }
138 
updateFolder(const QUrl & f)139 void QQuickAbstractFileDialog::updateFolder(const QUrl &f)
140 {
141     QUrl u = fixupFolder(f);
142     m_options->setInitialDirectory(u);
143     emit folderChanged();
144 }
145 
setNameFilters(const QStringList & f)146 void QQuickAbstractFileDialog::setNameFilters(const QStringList &f)
147 {
148     m_options->setNameFilters(f);
149     if (f.isEmpty())
150         selectNameFilter(QString());
151     else if (!f.contains(selectedNameFilter(), Qt::CaseInsensitive))
152         selectNameFilter(f.first());
153     emit nameFiltersChanged();
154 }
155 
selectedNameFilter() const156 QString QQuickAbstractFileDialog::selectedNameFilter() const
157 {
158     QString ret;
159     if (m_dlgHelper)
160         ret = m_dlgHelper->selectedNameFilter();
161     if (ret.isEmpty())
162         return m_options->initiallySelectedNameFilter();
163     return ret;
164 }
165 
selectNameFilter(const QString & f)166 void QQuickAbstractFileDialog::selectNameFilter(const QString &f)
167 {
168     // This should work whether the dialog is currently being shown already, or ahead of time.
169     m_options->setInitiallySelectedNameFilter(f);
170     if (m_dlgHelper)
171         m_dlgHelper->selectNameFilter(f);
172     emit filterSelected();
173 }
174 
setSelectedNameFilterIndex(int idx)175 void QQuickAbstractFileDialog::setSelectedNameFilterIndex(int idx)
176 {
177     selectNameFilter(nameFilters().at(idx));
178 }
179 
setSidebarVisible(bool s)180 void QQuickAbstractFileDialog::setSidebarVisible(bool s)
181 {
182     if (s == m_sidebarVisible) return;
183     m_sidebarVisible = s;
184     emit sidebarVisibleChanged();
185 }
186 
selectedNameFilterExtensions() const187 QStringList QQuickAbstractFileDialog::selectedNameFilterExtensions() const
188 {
189     QString filterRaw = selectedNameFilter();
190     QStringList ret;
191 #if QT_CONFIG(regularexpression)
192     if (filterRaw.isEmpty()) {
193         ret << "*";
194         return ret;
195     }
196     QRegularExpression re("(\\*\\.?\\w*)");
197     QRegularExpressionMatchIterator i = re.globalMatch(filterRaw);
198     while (i.hasNext())
199         ret << i.next().captured(1);
200 #endif // QT_CONFIG(regularexpression)
201     if (ret.isEmpty())
202         ret << filterRaw;
203     return ret;
204 }
205 
selectedNameFilterIndex() const206 int QQuickAbstractFileDialog::selectedNameFilterIndex() const
207 {
208     return nameFilters().indexOf(selectedNameFilter());
209 }
210 
fileUrl() const211 QUrl QQuickAbstractFileDialog::fileUrl() const
212 {
213     QList<QUrl> urls = fileUrls();
214     return (urls.count() == 1) ? urls[0] : QUrl();
215 }
216 
updateModes()217 void QQuickAbstractFileDialog::updateModes()
218 {
219     // The 4 possible modes are AnyFile, ExistingFile, Directory, ExistingFiles
220     // Assume AnyFile until we find a reason to the contrary
221     QFileDialogOptions::FileMode mode = QFileDialogOptions::AnyFile;
222 
223     if (m_selectFolder) {
224         mode = QFileDialogOptions::Directory;
225         m_options->setOption(QFileDialogOptions::ShowDirsOnly);
226         m_selectMultiple = false;
227         m_selectExisting = true;
228         setNameFilters(QStringList());
229     } else if (m_selectExisting) {
230         mode = m_selectMultiple ?
231             QFileDialogOptions::ExistingFiles : QFileDialogOptions::ExistingFile;
232         m_options->setOption(QFileDialogOptions::ShowDirsOnly, false);
233     } else if (m_selectMultiple) {
234         m_selectExisting = true;
235     }
236     if (!m_selectExisting)
237         m_selectMultiple = false;
238     m_options->setFileMode(mode);
239     m_options->setAcceptMode(m_selectExisting ?
240                            QFileDialogOptions::AcceptOpen : QFileDialogOptions::AcceptSave);
241     emit fileModeChanged();
242 }
243 
addShortcut(const QString & name,const QString & visibleName,const QString & path)244 void QQuickAbstractFileDialog::addShortcut(const QString &name, const QString &visibleName, const QString &path)
245 {
246     QQmlEngine *engine = qmlEngine(this);
247     QUrl url = QUrl::fromLocalFile(path);
248 
249     // Since the app can have bindings to the shortcut, we always add it
250     // to the public API, even if the directory doesn't (yet) exist.
251     m_shortcuts.setProperty(name, url.toString());
252 
253     // ...but we are more strict about showing it as a clickable link inside the dialog
254     if (visibleName.isEmpty() || !QDir(path).exists())
255         return;
256 
257     QJSValue o = engine->newObject();
258     o.setProperty("name", visibleName);
259     // TODO maybe some day QJSValue could directly store a QUrl
260     o.setProperty("url", url.toString());
261 
262     int length = m_shortcutDetails.property(QLatin1String("length")).toInt();
263     m_shortcutDetails.setProperty(length, o);
264 }
265 
addShortcutFromStandardLocation(const QString & name,QStandardPaths::StandardLocation loc,bool local)266 void QQuickAbstractFileDialog::addShortcutFromStandardLocation(const QString &name, QStandardPaths::StandardLocation loc, bool local)
267 {
268     if (m_selectExisting) {
269         QStringList readPaths = QStandardPaths::standardLocations(loc);
270         QString path = readPaths.isEmpty() ? QString() : local ? readPaths.first() : readPaths.last();
271         addShortcut(name, QStandardPaths::displayName(loc), path);
272     } else {
273         QString path = QStandardPaths::writableLocation(loc);
274         addShortcut(name, QStandardPaths::displayName(loc), path);
275     }
276 }
277 
populateShortcuts()278 void QQuickAbstractFileDialog::populateShortcuts()
279 {
280     QJSEngine *engine = qmlEngine(this);
281     m_shortcutDetails = engine->newArray();
282     m_shortcuts = engine->newObject();
283 
284     addShortcutFromStandardLocation(QLatin1String("desktop"), QStandardPaths::DesktopLocation);
285     addShortcutFromStandardLocation(QLatin1String("documents"), QStandardPaths::DocumentsLocation);
286     addShortcutFromStandardLocation(QLatin1String("music"), QStandardPaths::MusicLocation);
287     addShortcutFromStandardLocation(QLatin1String("movies"), QStandardPaths::MoviesLocation);
288     addShortcutFromStandardLocation(QLatin1String("home"), QStandardPaths::HomeLocation);
289 
290 #ifndef Q_OS_IOS
291     addShortcutFromStandardLocation(QLatin1String("pictures"), QStandardPaths::PicturesLocation);
292 #else
293     // On iOS we point pictures to the system picture folder when loading
294     addShortcutFromStandardLocation(QLatin1String("pictures"), QStandardPaths::PicturesLocation, !m_selectExisting);
295 #endif
296 
297 #ifndef Q_OS_IOS
298     // on iOS, this returns only "/", which is never a useful path to read or write anything
299     const QFileInfoList drives = QDir::drives();
300     for (const QFileInfo &fi : drives)
301         addShortcut(fi.absoluteFilePath(), fi.absoluteFilePath(), fi.absoluteFilePath());
302 #endif
303 }
304 
shortcuts()305 QJSValue QQuickAbstractFileDialog::shortcuts()
306 {
307     if (m_shortcuts.isUndefined())
308         populateShortcuts();
309 
310     return m_shortcuts;
311 }
312 
__shortcuts()313 QJSValue QQuickAbstractFileDialog::__shortcuts()
314 {
315     if (m_shortcutDetails.isUndefined())
316         populateShortcuts();
317 
318     return m_shortcutDetails;
319 }
320 
setDefaultSuffix(const QString & suffix)321 void QQuickAbstractFileDialog::setDefaultSuffix(const QString &suffix)
322 {
323     if (suffix == m_options->defaultSuffix())
324         return;
325     m_options->setDefaultSuffix(suffix);
326     emit defaultSuffixChanged();
327 }
328 
329 QT_END_NAMESPACE
330