| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 The Qt Company Ltd. |
| ** Contact: http://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Labs Platform module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL3$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see http://www.qt.io/terms-conditions. For further |
| ** information use the contact form at http://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or later as published by the Free |
| ** Software Foundation and appearing in the file LICENSE.GPL included in |
| ** the packaging of this file. Please review the following information to |
| ** ensure the GNU General Public License version 2.0 requirements will be |
| ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qquickplatformfiledialog_p.h" |
| |
| #include <QtCore/qvector.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| /*! |
| \qmltype FileDialog |
| \inherits Dialog |
| //! \instantiates QQuickPlatformFileDialog |
| \inqmlmodule Qt.labs.platform |
| \since 5.8 |
| \brief A native file dialog. |
| |
| The FileDialog type provides a QML API for native platform file dialogs. |
| |
| \image qtlabsplatform-filedialog-gtk.png |
| |
| To show a file dialog, construct an instance of FileDialog, set the |
| desired properties, and call \l {Dialog::}{open()}. The \l currentFile |
| or \l currentFiles properties can be used to determine the currently |
| selected file(s) in the dialog. The \l file and \l files properties |
| are updated only after the final selection has been made by accepting |
| the dialog. |
| |
| \code |
| MenuItem { |
| text: "Open..." |
| onTriggered: fileDialog.open() |
| } |
| |
| FileDialog { |
| id: fileDialog |
| currentFile: document.source |
| folder: StandardPaths.writableLocation(StandardPaths.DocumentsLocation) |
| } |
| |
| MyDocument { |
| id: document |
| source: fileDialog.file |
| } |
| \endcode |
| |
| \section2 Availability |
| |
| A native platform file dialog is currently available on the following platforms: |
| |
| \list |
| \li iOS |
| \li Linux (when running with the GTK+ platform theme) |
| \li macOS |
| \li Windows |
| \li WinRT |
| \endlist |
| |
| \input includes/widgets.qdocinc 1 |
| |
| \labs |
| |
| \sa FolderDialog, StandardPaths |
| */ |
| |
| QQuickPlatformFileDialog::QQuickPlatformFileDialog(QObject *parent) |
| : QQuickPlatformDialog(QPlatformTheme::FileDialog, parent), |
| m_fileMode(OpenFile), |
| m_options(QFileDialogOptions::create()), |
| m_selectedNameFilter(nullptr) |
| { |
| m_options->setFileMode(QFileDialogOptions::ExistingFile); |
| m_options->setAcceptMode(QFileDialogOptions::AcceptOpen); |
| } |
| |
| /*! |
| \qmlproperty enumeration Qt.labs.platform::FileDialog::fileMode |
| |
| This property holds the mode of the dialog. |
| |
| Available values: |
| \value FileDialog.OpenFile The dialog is used to select an existing file (default). |
| \value FileDialog.OpenFiles The dialog is used to select multiple existing files. |
| \value FileDialog.SaveFile The dialog is used to select any file. The file does not have to exist. |
| */ |
| QQuickPlatformFileDialog::FileMode QQuickPlatformFileDialog::fileMode() const |
| { |
| return m_fileMode; |
| } |
| |
| void QQuickPlatformFileDialog::setFileMode(FileMode mode) |
| { |
| if (mode == m_fileMode) |
| return; |
| |
| switch (mode) { |
| case OpenFile: |
| m_options->setFileMode(QFileDialogOptions::ExistingFile); |
| m_options->setAcceptMode(QFileDialogOptions::AcceptOpen); |
| break; |
| case OpenFiles: |
| m_options->setFileMode(QFileDialogOptions::ExistingFiles); |
| m_options->setAcceptMode(QFileDialogOptions::AcceptOpen); |
| break; |
| case SaveFile: |
| m_options->setFileMode(QFileDialogOptions::AnyFile); |
| m_options->setAcceptMode(QFileDialogOptions::AcceptSave); |
| break; |
| default: |
| break; |
| } |
| |
| m_fileMode = mode; |
| emit fileModeChanged(); |
| } |
| |
| /*! |
| \qmlproperty url Qt.labs.platform::FileDialog::file |
| |
| This property holds the final accepted file. |
| |
| Unlike the \l currentFile property, the \c file property is not updated |
| while the user is selecting files in the dialog, but only after the final |
| selection has been made. That is, when the user has clicked \uicontrol OK |
| to accept a file. Alternatively, the \l {Dialog::}{accepted()} signal |
| can be handled to get the final selection. |
| |
| \sa currentFile, {Dialog::}{accepted()} |
| */ |
| QUrl QQuickPlatformFileDialog::file() const |
| { |
| return addDefaultSuffix(m_files.value(0)); |
| } |
| |
| void QQuickPlatformFileDialog::setFile(const QUrl &file) |
| { |
| setFiles(QList<QUrl>() << file); |
| } |
| |
| /*! |
| \qmlproperty list<url> Qt.labs.platform::FileDialog::files |
| |
| This property holds the final accepted files. |
| |
| Unlike the \l currentFiles property, the \c files property is not updated |
| while the user is selecting files in the dialog, but only after the final |
| selection has been made. That is, when the user has clicked \uicontrol OK |
| to accept files. Alternatively, the \l {Dialog::}{accepted()} signal |
| can be handled to get the final selection. |
| |
| \sa currentFiles, {Dialog::}{accepted()} |
| */ |
| QList<QUrl> QQuickPlatformFileDialog::files() const |
| { |
| return addDefaultSuffixes(m_files); |
| } |
| |
| void QQuickPlatformFileDialog::setFiles(const QList<QUrl> &files) |
| { |
| if (m_files == files) |
| return; |
| |
| bool firstChanged = m_files.value(0) != files.value(0); |
| m_files = files; |
| if (firstChanged) |
| emit fileChanged(); |
| emit filesChanged(); |
| } |
| |
| /*! |
| \qmlproperty url Qt.labs.platform::FileDialog::currentFile |
| |
| This property holds the currently selected file in the dialog. |
| |
| Unlike the \l file property, the \c currentFile property is updated |
| while the user is selecting files in the dialog, even before the final |
| selection has been made. |
| |
| \sa file, currentFiles |
| */ |
| QUrl QQuickPlatformFileDialog::currentFile() const |
| { |
| return currentFiles().value(0); |
| } |
| |
| void QQuickPlatformFileDialog::setCurrentFile(const QUrl &file) |
| { |
| setCurrentFiles(QList<QUrl>() << file); |
| } |
| |
| /*! |
| \qmlproperty list<url> Qt.labs.platform::FileDialog::currentFiles |
| |
| This property holds the currently selected files in the dialog. |
| |
| Unlike the \l files property, the \c currentFiles property is updated |
| while the user is selecting files in the dialog, even before the final |
| selection has been made. |
| |
| \sa files, currentFile |
| */ |
| QList<QUrl> QQuickPlatformFileDialog::currentFiles() const |
| { |
| if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(handle())) |
| return fileDialog->selectedFiles(); |
| return m_options->initiallySelectedFiles(); |
| } |
| |
| void QQuickPlatformFileDialog::setCurrentFiles(const QList<QUrl> &files) |
| { |
| if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(handle())) { |
| for (const QUrl &file : files) |
| fileDialog->selectFile(file); |
| } |
| m_options->setInitiallySelectedFiles(files); |
| } |
| |
| /*! |
| \qmlproperty url Qt.labs.platform::FileDialog::folder |
| |
| This property holds the folder where files are selected. |
| For selecting a folder, use FolderDialog instead. |
| |
| \sa FolderDialog |
| */ |
| QUrl QQuickPlatformFileDialog::folder() const |
| { |
| if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(handle())) |
| return fileDialog->directory(); |
| return m_options->initialDirectory(); |
| } |
| |
| void QQuickPlatformFileDialog::setFolder(const QUrl &folder) |
| { |
| if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(handle())) |
| fileDialog->setDirectory(folder); |
| m_options->setInitialDirectory(folder); |
| } |
| |
| /*! |
| \qmlproperty flags Qt.labs.platform::FileDialog::options |
| |
| This property holds the various options that affect the look and feel of the dialog. |
| |
| By default, all options are disabled. |
| |
| Options should be set before showing the dialog. Setting them while the dialog is |
| visible is not guaranteed to have an immediate effect on the dialog (depending on |
| the option and on the platform). |
| |
| Available options: |
| \value FileDialog.DontResolveSymlinks Don't resolve symlinks in the file dialog. By default symlinks are resolved. |
| \value FileDialog.DontConfirmOverwrite Don't ask for confirmation if an existing file is selected. By default confirmation is requested. |
| \value FileDialog.ReadOnly Indicates that the dialog doesn't allow creating directories. |
| \value FileDialog.HideNameFilterDetails Indicates if the file name filter details are hidden or not. |
| */ |
| QFileDialogOptions::FileDialogOptions QQuickPlatformFileDialog::options() const |
| { |
| return m_options->options(); |
| } |
| |
| void QQuickPlatformFileDialog::setOptions(QFileDialogOptions::FileDialogOptions options) |
| { |
| if (options == m_options->options()) |
| return; |
| |
| m_options->setOptions(options); |
| emit optionsChanged(); |
| } |
| |
| void QQuickPlatformFileDialog::resetOptions() |
| { |
| setOptions({}); |
| } |
| |
| /*! |
| \qmlproperty list<string> Qt.labs.platform::FileDialog::nameFilters |
| |
| This property holds the filters that restrict the types of files that |
| can be selected. |
| |
| \code |
| FileDialog { |
| nameFilters: ["Text files (*.txt)", "HTML files (*.html *.htm)"] |
| } |
| \endcode |
| |
| \note \b{*.*} is not a portable filter, because the historical assumption |
| that the file extension determines the file type is not consistent on every |
| operating system. It is possible to have a file with no dot in its name (for |
| example, \c Makefile). In a native Windows file dialog, \b{*.*} will match |
| such files, while in other types of file dialogs it may not. So it is better |
| to use \b{*} if you mean to select any file. |
| |
| \sa selectedNameFilter |
| */ |
| QStringList QQuickPlatformFileDialog::nameFilters() const |
| { |
| return m_options->nameFilters(); |
| } |
| |
| void QQuickPlatformFileDialog::setNameFilters(const QStringList &filters) |
| { |
| if (filters == m_options->nameFilters()) |
| return; |
| |
| m_options->setNameFilters(filters); |
| if (m_selectedNameFilter) { |
| int index = m_selectedNameFilter->index(); |
| if (index < 0 || index >= filters.count()) |
| index = 0; |
| m_selectedNameFilter->update(filters.value(index)); |
| } |
| emit nameFiltersChanged(); |
| } |
| |
| void QQuickPlatformFileDialog::resetNameFilters() |
| { |
| setNameFilters(QStringList()); |
| } |
| |
| /*! |
| \qmlpropertygroup Qt.labs.platform::FileDialog::selectedNameFilter |
| \qmlproperty int Qt.labs.platform::FileDialog::selectedNameFilter.index |
| \qmlproperty string Qt.labs.platform::FileDialog::selectedNameFilter.name |
| \qmlproperty list<string> Qt.labs.platform::FileDialog::selectedNameFilter.extensions |
| |
| These properties hold the currently selected name filter. |
| |
| \table |
| \header |
| \li Name |
| \li Description |
| \row |
| \li \b index : int |
| \li This property determines which \l {nameFilters}{name filter} is selected. |
| The specified filter is selected when the dialog is opened. The value is |
| updated when the user selects another filter. |
| \row |
| \li [read-only] \b name : string |
| \li This property holds the name of the selected filter. In the |
| example below, the name of the first filter is \c {"Text files"} |
| and the second is \c {"HTML files"}. |
| \row |
| \li [read-only] \b extensions : list<string> |
| \li This property holds the list of extensions of the selected filter. |
| In the example below, the list of extensions of the first filter is |
| \c {["txt"]} and the second is \c {["html", "htm"]}. |
| \endtable |
| |
| \code |
| FileDialog { |
| id: fileDialog |
| selectedNameFilter.index: 1 |
| nameFilters: ["Text files (*.txt)", "HTML files (*.html *.htm)"] |
| } |
| |
| MyDocument { |
| id: document |
| fileType: fileDialog.selectedNameFilter.extensions[0] |
| } |
| \endcode |
| |
| \sa nameFilters |
| */ |
| QQuickPlatformFileNameFilter *QQuickPlatformFileDialog::selectedNameFilter() const |
| { |
| if (!m_selectedNameFilter) { |
| QQuickPlatformFileDialog *that = const_cast<QQuickPlatformFileDialog *>(this); |
| m_selectedNameFilter = new QQuickPlatformFileNameFilter(that); |
| m_selectedNameFilter->setOptions(m_options); |
| } |
| return m_selectedNameFilter; |
| } |
| |
| /*! |
| \qmlproperty string Qt.labs.platform::FileDialog::defaultSuffix |
| |
| This property holds a suffix that is added to selected files that have |
| no suffix specified. The suffix is typically used to indicate the file |
| type (e.g. "txt" indicates a text file). |
| |
| If the first character is a dot ('.'), it is removed. |
| */ |
| QString QQuickPlatformFileDialog::defaultSuffix() const |
| { |
| return m_options->defaultSuffix(); |
| } |
| |
| void QQuickPlatformFileDialog::setDefaultSuffix(const QString &suffix) |
| { |
| if (suffix == m_options->defaultSuffix()) |
| return; |
| |
| m_options->setDefaultSuffix(suffix); |
| emit defaultSuffixChanged(); |
| } |
| |
| void QQuickPlatformFileDialog::resetDefaultSuffix() |
| { |
| setDefaultSuffix(QString()); |
| } |
| |
| /*! |
| \qmlproperty string Qt.labs.platform::FileDialog::acceptLabel |
| |
| This property holds the label text shown on the button that accepts the dialog. |
| |
| When set to an empty string, the default label of the underlying platform is used. |
| The default label is typically \uicontrol Open or \uicontrol Save depending on which |
| \l fileMode the dialog is used in. |
| |
| The default value is an empty string. |
| |
| \sa rejectLabel |
| */ |
| QString QQuickPlatformFileDialog::acceptLabel() const |
| { |
| return m_options->labelText(QFileDialogOptions::Accept); |
| } |
| |
| void QQuickPlatformFileDialog::setAcceptLabel(const QString &label) |
| { |
| if (label == m_options->labelText(QFileDialogOptions::Accept)) |
| return; |
| |
| m_options->setLabelText(QFileDialogOptions::Accept, label); |
| emit acceptLabelChanged(); |
| } |
| |
| void QQuickPlatformFileDialog::resetAcceptLabel() |
| { |
| setAcceptLabel(QString()); |
| } |
| |
| /*! |
| \qmlproperty string Qt.labs.platform::FileDialog::rejectLabel |
| |
| This property holds the label text shown on the button that rejects the dialog. |
| |
| When set to an empty string, the default label of the underlying platform is used. |
| The default label is typically \uicontrol Cancel. |
| |
| The default value is an empty string. |
| |
| \sa acceptLabel |
| */ |
| QString QQuickPlatformFileDialog::rejectLabel() const |
| { |
| return m_options->labelText(QFileDialogOptions::Reject); |
| } |
| |
| void QQuickPlatformFileDialog::setRejectLabel(const QString &label) |
| { |
| if (label == m_options->labelText(QFileDialogOptions::Reject)) |
| return; |
| |
| m_options->setLabelText(QFileDialogOptions::Reject, label); |
| emit rejectLabelChanged(); |
| } |
| |
| void QQuickPlatformFileDialog::resetRejectLabel() |
| { |
| setRejectLabel(QString()); |
| } |
| |
| bool QQuickPlatformFileDialog::useNativeDialog() const |
| { |
| return QQuickPlatformDialog::useNativeDialog() |
| && !m_options->testOption(QFileDialogOptions::DontUseNativeDialog); |
| } |
| |
| void QQuickPlatformFileDialog::onCreate(QPlatformDialogHelper *dialog) |
| { |
| if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(dialog)) { |
| // TODO: emit currentFileChanged only when the first entry in currentFiles changes |
| connect(fileDialog, &QPlatformFileDialogHelper::currentChanged, this, &QQuickPlatformFileDialog::currentFileChanged); |
| connect(fileDialog, &QPlatformFileDialogHelper::currentChanged, this, &QQuickPlatformFileDialog::currentFilesChanged); |
| connect(fileDialog, &QPlatformFileDialogHelper::directoryEntered, this, &QQuickPlatformFileDialog::folderChanged); |
| fileDialog->setOptions(m_options); |
| } |
| } |
| |
| void QQuickPlatformFileDialog::onShow(QPlatformDialogHelper *dialog) |
| { |
| m_options->setWindowTitle(title()); |
| if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(dialog)) { |
| fileDialog->setOptions(m_options); // setOptions only assigns a member and isn't virtual |
| if (m_firstShow && m_options->initialDirectory().isValid()) |
| fileDialog->setDirectory(m_options->initialDirectory()); |
| if (m_selectedNameFilter) { |
| const int index = m_selectedNameFilter->index(); |
| const QString filter = m_options->nameFilters().value(index); |
| m_options->setInitiallySelectedNameFilter(filter); |
| fileDialog->selectNameFilter(filter); |
| connect(fileDialog, &QPlatformFileDialogHelper::filterSelected, m_selectedNameFilter, &QQuickPlatformFileNameFilter::update); |
| } |
| } |
| if (m_firstShow) |
| m_firstShow = false; |
| } |
| |
| void QQuickPlatformFileDialog::onHide(QPlatformDialogHelper *dialog) |
| { |
| if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(dialog)) { |
| if (m_selectedNameFilter) |
| disconnect(fileDialog, &QPlatformFileDialogHelper::filterSelected, m_selectedNameFilter, &QQuickPlatformFileNameFilter::update); |
| } |
| } |
| |
| void QQuickPlatformFileDialog::accept() |
| { |
| if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(handle())) |
| setFiles(fileDialog->selectedFiles()); |
| QQuickPlatformDialog::accept(); |
| } |
| |
| QUrl QQuickPlatformFileDialog::addDefaultSuffix(const QUrl &file) const |
| { |
| QUrl url = file; |
| const QString path = url.path(); |
| const QString suffix = m_options->defaultSuffix(); |
| if (!suffix.isEmpty() && !path.endsWith(QLatin1Char('/')) && path.lastIndexOf(QLatin1Char('.')) == -1) |
| url.setPath(path + QLatin1Char('.') + suffix); |
| return url; |
| } |
| |
| QList<QUrl> QQuickPlatformFileDialog::addDefaultSuffixes(const QList<QUrl> &files) const |
| { |
| QList<QUrl> urls; |
| urls.reserve(files.size()); |
| for (const QUrl &file : files) |
| urls += addDefaultSuffix(file); |
| return urls; |
| } |
| |
| QQuickPlatformFileNameFilter::QQuickPlatformFileNameFilter(QObject *parent) |
| : QObject(parent), m_index(-1) |
| { |
| } |
| |
| int QQuickPlatformFileNameFilter::index() const |
| { |
| return m_index; |
| } |
| |
| void QQuickPlatformFileNameFilter::setIndex(int index) |
| { |
| if (m_index == index) |
| return; |
| |
| m_index = index; |
| emit indexChanged(index); |
| } |
| |
| QString QQuickPlatformFileNameFilter::name() const |
| { |
| return m_name; |
| } |
| |
| QStringList QQuickPlatformFileNameFilter::extensions() const |
| { |
| return m_extensions; |
| } |
| |
| QSharedPointer<QFileDialogOptions> QQuickPlatformFileNameFilter::options() const |
| { |
| return m_options; |
| } |
| |
| void QQuickPlatformFileNameFilter::setOptions(const QSharedPointer<QFileDialogOptions> &options) |
| { |
| m_options = options; |
| } |
| |
| static QString extractName(const QString &filter) |
| { |
| return filter.left(filter.indexOf(QLatin1Char('(')) - 1); |
| } |
| |
| static QString extractExtension(const QString &filter) |
| { |
| return filter.mid(filter.indexOf(QLatin1Char('.')) + 1); |
| } |
| |
| static QStringList extractExtensions(const QString &filter) |
| { |
| QStringList extensions; |
| const int from = filter.indexOf(QLatin1Char('(')); |
| const int to = filter.lastIndexOf(QLatin1Char(')')) - 1; |
| if (from >= 0 && from < to) { |
| const QStringRef ref = filter.midRef(from + 1, to - from); |
| const QVector<QStringRef> exts = ref.split(QLatin1Char(' '), Qt::SkipEmptyParts); |
| for (const QStringRef &ref : exts) |
| extensions += extractExtension(ref.toString()); |
| } |
| |
| return extensions; |
| } |
| |
| void QQuickPlatformFileNameFilter::update(const QString &filter) |
| { |
| const QStringList filters = nameFilters(); |
| |
| const int oldIndex = m_index; |
| const QString oldName = m_name; |
| const QStringList oldExtensions = m_extensions; |
| |
| m_index = filters.indexOf(filter); |
| m_name = extractName(filter); |
| m_extensions = extractExtensions(filter); |
| |
| if (oldIndex != m_index) |
| emit indexChanged(m_index); |
| if (oldName != m_name) |
| emit nameChanged(m_name); |
| if (oldExtensions != m_extensions) |
| emit extensionsChanged(m_extensions); |
| } |
| |
| QStringList QQuickPlatformFileNameFilter::nameFilters() const |
| { |
| return m_options ? m_options->nameFilters() : QStringList(); |
| } |
| |
| QString QQuickPlatformFileNameFilter::nameFilter(int index) const |
| { |
| return m_options ? m_options->nameFilters().value(index) : QString(); |
| } |
| |
| QT_END_NAMESPACE |