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