diff options
152 files changed, 10947 insertions, 30 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4ad5f89b..9b11240b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,4 +3,5 @@ add_subdirectory(quicktemplates2) add_subdirectory(quickcontrols2) add_subdirectory(quickcontrols2impl) +add_subdirectory(quickdialogs2) add_subdirectory(imports) diff --git a/src/imports/CMakeLists.txt b/src/imports/CMakeLists.txt index 1d9b7814..d056a899 100644 --- a/src/imports/CMakeLists.txt +++ b/src/imports/CMakeLists.txt @@ -2,6 +2,7 @@ add_subdirectory(controls) add_subdirectory(controlsimpl) +add_subdirectory(dialogs) add_subdirectory(platform) add_subdirectory(templates) add_subdirectory(nativestyle) diff --git a/src/imports/dialogs/CMakeLists.txt b/src/imports/dialogs/CMakeLists.txt new file mode 100644 index 00000000..f96fd9ff --- /dev/null +++ b/src/imports/dialogs/CMakeLists.txt @@ -0,0 +1,29 @@ +qt_internal_add_qml_module(qtquickdialogsplugin + URI "QtQuick.Dialogs" + VERSION "${CMAKE_PROJECT_VERSION}" + CLASSNAME QtQuickDialogsPlugin + SKIP_TYPE_REGISTRATION + SOURCES + qtquickdialogsplugin.cpp + DEFINES + QT_NO_CAST_FROM_ASCII + QT_NO_CAST_TO_ASCII + INCLUDE_DIRECTORIES + quick + LIBRARIES + Qt::CorePrivate + Qt::GuiPrivate + Qt::QmlPrivate + Qt::QuickDialogs2Private + Qt::QuickPrivate + Qt::QuickTemplates2Private + PUBLIC_LIBRARIES + Qt::Core + Qt::Gui + Qt::Qml + Qt::Quick +) + +qt_internal_add_docs(qtquickdialogsplugin + doc/qtquickdialogs.qdocconf +) diff --git a/src/imports/dialogs/doc/images/qtquickdialogs-filedialog-gtk.png b/src/imports/dialogs/doc/images/qtquickdialogs-filedialog-gtk.png Binary files differnew file mode 100644 index 00000000..9360d747 --- /dev/null +++ b/src/imports/dialogs/doc/images/qtquickdialogs-filedialog-gtk.png diff --git a/src/imports/dialogs/doc/qtquickdialogs.qdocconf b/src/imports/dialogs/doc/qtquickdialogs.qdocconf new file mode 100644 index 00000000..575c84d0 --- /dev/null +++ b/src/imports/dialogs/doc/qtquickdialogs.qdocconf @@ -0,0 +1,40 @@ +include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) +include($QT_INSTALL_DOCS/config/exampleurl-qtquickcontrols2.qdocconf) + +project = QtQuickDialogs +description = Qt Quick Dialogs Reference Documentation +version = $QT_VERSION + +qhp.projects = QtQuickDialogs + +qhp.QtQuickDialogs.file = qtquickdialogs.qhp +qhp.QtQuickDialogs.namespace = org.qt-project.qtquickdialogs.$QT_VERSION_TAG +qhp.QtQuickDialogs.virtualFolder = qtquickdialogs +qhp.QtQuickDialogs.indexTitle = Qt Quick Dialogs +qhp.QtQuickDialogs.indexRoot = + +qhp.QtQuickDialogs.filterAttributes = qtquickdialogs $QT_VERSION qtrefdoc +qhp.QtQuickDialogs.customFilters.Qt.name = QtQuickDialogs $QT_VERSION +qhp.QtQuickDialogs.customFilters.Qt.filterAttributes = qtquickdialogs $QT_VERSION + +qhp.QtQuickDialogs.subprojects = qmltypes +qhp.QtQuickDialogs.subprojects.qmltypes.title = QML Types +qhp.QtQuickDialogs.subprojects.qmltypes.indexTitle = Qt Quick Dialogs QML Types +qhp.QtQuickDialogs.subprojects.qmltypes.selectors = qmlclass +qhp.QtQuickDialogs.subprojects.qmltypes.sortPages = true + +depends = qtcore qtgui qtdoc qtqml qtquick qtquickcontrols + +# This module has no documented C++ types, clear the module header +moduleheader = + +headerdirs += ../../../quickdialogs2/quickdialogs2 +sourcedirs += ../../../quickdialogs2/quickdialogs2 \ + src + +imagedirs += images + +navigation.landingpage = "Qt Quick Dialogs" +navigation.qmltypespage = "Qt Quick Dialogs QML Types" + +tagfile = qtquickdialogs.tags diff --git a/src/imports/dialogs/doc/src/includes/fallback.qdocinc b/src/imports/dialogs/doc/src/includes/fallback.qdocinc new file mode 100644 index 00000000..f3a4a180 --- /dev/null +++ b/src/imports/dialogs/doc/src/includes/fallback.qdocinc @@ -0,0 +1 @@ +Qt Quick Dialogs uses a Qt Quick implementation as a fallback on platforms that do not have a native implementation available. diff --git a/src/imports/dialogs/qmldir b/src/imports/dialogs/qmldir new file mode 100644 index 00000000..11d5bcdf --- /dev/null +++ b/src/imports/dialogs/qmldir @@ -0,0 +1,3 @@ +module QtQuick.Dialogs +plugin qtquickdialogsplugin +classname QtQuickDialogsPlugin diff --git a/src/imports/dialogs/qtquickdialogsplugin.cpp b/src/imports/dialogs/qtquickdialogsplugin.cpp new file mode 100644 index 00000000..9bc06c25 --- /dev/null +++ b/src/imports/dialogs/qtquickdialogsplugin.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Labs Templates 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 <QtQml/qqmlextensionplugin.h> +#include <QtQml/qqml.h> +#include <QtQuickDialogs2/private/qtquickdialogs2global_p.h> + +QT_BEGIN_NAMESPACE + +class QtQuickDialogsPlugin: public QQmlEngineExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) + +public: + QtQuickDialogsPlugin(QObject *parent = nullptr); +}; + +QtQuickDialogsPlugin::QtQuickDialogsPlugin(QObject *parent) : QQmlEngineExtensionPlugin(parent) +{ + volatile auto registration = &qml_register_types_QtQuick_Dialogs; + Q_UNUSED(registration) +} + +QT_END_NAMESPACE + +#include "qtquickdialogsplugin.moc" diff --git a/src/quickdialogs2/CMakeLists.txt b/src/quickdialogs2/CMakeLists.txt new file mode 100644 index 00000000..88ae9633 --- /dev/null +++ b/src/quickdialogs2/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(quickdialogs2utils) +add_subdirectory(quickdialogs2quickimpl) +add_subdirectory(quickdialogs2) diff --git a/src/quickdialogs2/quickdialogs2/CMakeLists.txt b/src/quickdialogs2/quickdialogs2/CMakeLists.txt new file mode 100644 index 00000000..59f671d7 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2/CMakeLists.txt @@ -0,0 +1,43 @@ +##################################################################### +## QuickDialogs2 Module: +##################################################################### + +qt_internal_add_module(QuickDialogs2 + SOURCES + qquickabstractdialog.cpp + qquickabstractdialog_p.h + qquickfiledialog.cpp + qquickfiledialog_p.h + qtquickdialogs2foreign_p.h + qtquickdialogs2global_p.h + DEFINES + QT_BUILD_QUICKDIALOGS2_LIB + QT_NO_CAST_FROM_ASCII + QT_NO_CAST_TO_ASCII + INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_SOURCE_DIR} + LIBRARIES + Qt::CorePrivate + Qt::GuiPrivate + Qt::QmlPrivate + Qt::QuickPrivate + Qt::QuickDialogs2UtilsPrivate + Qt::QuickDialogs2QuickImplPrivate + PUBLIC_LIBRARIES + Qt::Core + Qt::Gui + Qt::Quick +) + +## Scopes: +##################################################################### + +set_target_properties(QuickDialogs2 PROPERTIES + QT_QML_MODULE_INSTALL_QMLTYPES TRUE + QT_QML_MODULE_VERSION ${CMAKE_PROJECT_VERSION} + QT_QML_MODULE_URI QtQuick.Dialogs + QT_QMLTYPES_FILENAME plugins.qmltypes + QT_QML_MODULE_INSTALL_DIR "${INSTALL_QMLDIR}/QtQuick/Dialogs" +) + +qt6_qml_type_registration(QuickDialogs2) diff --git a/src/quickdialogs2/quickdialogs2/qquickabstractdialog.cpp b/src/quickdialogs2/quickdialogs2/qquickabstractdialog.cpp new file mode 100644 index 00000000..401091f5 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2/qquickabstractdialog.cpp @@ -0,0 +1,454 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 "qquickabstractdialog_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtGui/private/qguiapplication_p.h> +#include <QtQuick/qquickitem.h> +#include <QtQuick/qquickwindow.h> +#include <QtQuickDialogs2QuickImpl/private/qquickdialogimplfactory_p.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcDialogs, "qt.quick.dialogs") + +/*! + \internal + + A dialog that can be backed by different implementations. + + Each dialog has a handle to QPlatformDialogHelper, which is created in create(). + The helper acts as an intermediary between the QML-facing dialog object + and the native/widget/quick implementation: + + +------------+ +------------------------------------+ +-------------------------------------+ + | | | | | | + | FileDialog |----->| Native/Widget/Quick QPlatformFile- |----->| Native OS dialog/QQuickFileDialog/ | + | | | DialogHelper subclass | | QQuickFileDialogImpl | + | | | | | | + +------------+ +------------------------------------+ +-------------------------------------+ +*/ + +/*! + \qmltype Dialog + \inherits QtObject +//! \instantiates QQuickAbstractDialog + \inqmlmodule QtQuick.Dialogs + \since 6.2 + \brief The base class of native dialogs. + + The Dialog type provides common QML API for native platform dialogs. + + To show a native dialog, construct an instance of one of the concrete + Dialog implementations, set the desired properties, and call \l open(). + Dialog emits \l accepted() or \l rejected() when the user is done with + the dialog. +*/ + +/*! + \qmlsignal void QtQuick.Dialogs::Dialog::accepted() + + This signal is emitted when the dialog has been accepted either + interactively or by calling \l accept(). + + \note This signal is \e not emitted when closing the dialog with \l close(). + + \sa rejected() +*/ + +/*! + \qmlsignal void QtQuick.Dialogs::Dialog::rejected() + + This signal is emitted when the dialog has been rejected either + interactively or by calling \l reject(). + + \note This signal is \e not emitted when closing the dialog with \l close(). + + \sa accepted() +*/ + +Q_DECLARE_LOGGING_CATEGORY(lcDialogs) + +QQuickAbstractDialog::QQuickAbstractDialog(QPlatformTheme::DialogType type, QObject *parent) + : QObject(parent), + m_type(type) +{ +} + +QQuickAbstractDialog::~QQuickAbstractDialog() +{ + destroy(); +} + +QPlatformDialogHelper *QQuickAbstractDialog::handle() const +{ + return m_handle.get(); +} + +/*! + \qmldefault + \qmlproperty list<Object> QtQuick.Dialogs::Dialog::data + + This default property holds the list of all objects declared as children of + the dialog. +*/ +QQmlListProperty<QObject> QQuickAbstractDialog::data() +{ + return QQmlListProperty<QObject>(this, &m_data); +} + +/*! + \qmlproperty Window QtQuick.Dialogs::Dialog::parentWindow + + This property holds the parent window of the dialog. + + Unless explicitly set, the window is automatically resolved by iterating + the QML parent objects until a \l Window or an \l Item that has a window + is found. +*/ +QWindow *QQuickAbstractDialog::parentWindow() const +{ + return m_parentWindow; +} + +void QQuickAbstractDialog::setParentWindow(QWindow *window) +{ + qCDebug(lcDialogs) << "set parent window to" << window; + if (m_parentWindow == window) + return; + + m_parentWindow = window; + emit parentWindowChanged(); +} + +/*! + \qmlproperty string QtQuick.Dialogs::Dialog::title + + This property holds the title of the dialog. +*/ +QString QQuickAbstractDialog::title() const +{ + return m_title; +} + +void QQuickAbstractDialog::setTitle(const QString &title) +{ + if (m_title == title) + return; + + m_title = title; + emit titleChanged(); +} + +/*! + \qmlproperty Qt::WindowFlags QtQuick.Dialogs::Dialog::flags + + This property holds the window flags of the dialog. The default value is \c Qt.Dialog. +*/ +Qt::WindowFlags QQuickAbstractDialog::flags() const +{ + return m_flags; +} + +void QQuickAbstractDialog::setFlags(Qt::WindowFlags flags) +{ + if (m_flags == flags) + return; + + m_flags = flags; + emit flagsChanged(); +} + +/*! + \qmlproperty Qt::WindowModality QtQuick.Dialogs::Dialog::modality + + This property holds the modality of the dialog. The default value is \c Qt.WindowModal. + + Available values: + \value Qt.NonModal The dialog is not modal and does not block input to other windows. + \value Qt.WindowModal The dialog is modal to a single window hierarchy and blocks input to its parent window, all grandparent windows, and all siblings of its parent and grandparent windows. + \value Qt.ApplicationModal The dialog is modal to the application and blocks input to all windows. +*/ +Qt::WindowModality QQuickAbstractDialog::modality() const +{ + return m_modality; +} + +void QQuickAbstractDialog::setModality(Qt::WindowModality modality) +{ + if (m_modality == modality) + return; + + m_modality = modality; + emit modalityChanged(); +} + +/*! + \qmlproperty bool QtQuick.Dialogs::Dialog::visible + + This property holds the visibility of the dialog. The default value is \c false. + + \sa open(), close() +*/ +bool QQuickAbstractDialog::isVisible() const +{ + return m_handle && m_visible; +} + +void QQuickAbstractDialog::setVisible(bool visible) +{ + qCDebug(lcDialogs) << "setVisible called with" << visible; + + if (visible) { + // Don't try to open before component completion, as we won't have a window yet, + // and open() sets m_visible to false if it fails. + if (!m_complete) + m_visibleRequested = true; + else + open(); + } else { + close(); + } +} + +/*! + \qmlproperty StandardCode QtQuick.Dialogs::Dialog::result + + This property holds the result code. + + Standard result codes: + \value Dialog.Accepted + \value Dialog.Rejected + + \note MessageDialog sets the result to the value of the clicked standard + button instead of using the standard result codes. +*/ +QQuickAbstractDialog::StandardCode QQuickAbstractDialog::result() const +{ + return m_result; +} + +void QQuickAbstractDialog::setResult(StandardCode result) +{ + if (m_result == result) + return; + + m_result = result; + emit resultChanged(); +} + +/*! + \qmlmethod void QtQuick.Dialogs::Dialog::open() + + Opens the dialog. + + \sa visible, close() +*/ +void QQuickAbstractDialog::open() +{ + qCDebug(lcDialogs) << "open called"; + if (m_visible || !create()) + return; + + onShow(m_handle.get()); + m_visible = m_handle->show(m_flags, m_modality, m_parentWindow); + if (m_visible) + emit visibleChanged(); +} + +/*! + \qmlmethod void QtQuick.Dialogs::Dialog::close() + + Closes the dialog. + + \sa visible, open() +*/ +void QQuickAbstractDialog::close() +{ + if (!m_handle || !m_visible) + return; + + onHide(m_handle.get()); + m_handle->hide(); + m_visible = false; + emit visibleChanged(); +} + +/*! + \qmlmethod void QtQuick.Dialogs::Dialog::accept() + + Closes the dialog and emits the \l accepted() signal. + + \sa reject() +*/ +void QQuickAbstractDialog::accept() +{ + done(Accepted); +} + +/*! + \qmlmethod void QtQuick.Dialogs::Dialog::reject() + + Closes the dialog and emits the \l rejected() signal. + + \sa accept() +*/ +void QQuickAbstractDialog::reject() +{ + done(Rejected); +} + +/*! + \qmlmethod void QtQuick.Dialogs::Dialog::done(StandardCode result) + + Closes the dialog and sets the \a result. + + \sa accept(), reject(), result +*/ +void QQuickAbstractDialog::done(StandardCode result) +{ + close(); + setResult(result); + + if (result == Accepted) + emit accepted(); + else if (result == Rejected) + emit rejected(); +} + +void QQuickAbstractDialog::classBegin() +{ +} + +void QQuickAbstractDialog::componentComplete() +{ + qCDebug(lcDialogs) << "componentComplete"; + m_complete = true; + + if (!m_parentWindow) { + qCDebug(lcDialogs) << "- no parent window; searching for one"; + setParentWindow(findParentWindow()); + } + + if (m_visibleRequested) { + qCDebug(lcDialogs) << "visible was bound to true before component completion; opening dialog"; + open(); + m_visibleRequested = false; + } +} + +static const char *qmlTypeName(const QObject *object) +{ + return object->metaObject()->className() + qstrlen("QQuickPlatform"); +} + +bool QQuickAbstractDialog::create() +{ + qCDebug(lcDialogs) << qmlTypeName(this) << "attempting to create dialog backend of type" + << m_type << "with parent window" << m_parentWindow; + if (m_handle) + return m_handle.get(); + + qCDebug(lcDialogs) << "- attempting to create a native dialog"; + if (useNativeDialog()) + m_handle.reset(QGuiApplicationPrivate::platformTheme()->createPlatformDialogHelper(m_type)); + + if (!m_handle) { + qCDebug(lcDialogs) << "- attempting to create a quick dialog"; + m_handle.reset(QQuickDialogImplFactory::createPlatformDialogHelper(m_type, this)); + } + + qCDebug(lcDialogs) << qmlTypeName(this) << "created ->" << m_handle.get(); + if (m_handle) { + onCreate(m_handle.get()); + connect(m_handle.get(), &QPlatformDialogHelper::accept, this, &QQuickAbstractDialog::accept); + connect(m_handle.get(), &QPlatformDialogHelper::reject, this, &QQuickAbstractDialog::reject); + } + return m_handle.get(); +} + +void QQuickAbstractDialog::destroy() +{ + m_handle.reset(); +} + +bool QQuickAbstractDialog::useNativeDialog() const +{ + if (QCoreApplication::testAttribute(Qt::AA_DontUseNativeDialogs)) { + qCDebug(lcDialogs) << " - Qt::AA_DontUseNativeDialogs was set; not using native dialog"; + return false; + } + + if (!QGuiApplicationPrivate::platformTheme()->usePlatformNativeDialog(m_type)) { + qCDebug(lcDialogs) << " - the platform theme told us a native dialog isn't available; not using native dialog"; + return false; + } + + return true; +} + +void QQuickAbstractDialog::onCreate(QPlatformDialogHelper *dialog) +{ + Q_UNUSED(dialog); +} + +void QQuickAbstractDialog::onShow(QPlatformDialogHelper *dialog) +{ + Q_UNUSED(dialog); +} + +void QQuickAbstractDialog::onHide(QPlatformDialogHelper *dialog) +{ + Q_UNUSED(dialog); +} + +QWindow *QQuickAbstractDialog::findParentWindow() const +{ + QObject *obj = parent(); + while (obj) { + QWindow *window = qobject_cast<QWindow *>(obj); + if (window) + return window; + QQuickItem *item = qobject_cast<QQuickItem *>(obj); + if (item && item->window()) + return item->window(); + obj = obj->parent(); + } + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/quickdialogs2/quickdialogs2/qquickabstractdialog_p.h b/src/quickdialogs2/quickdialogs2/qquickabstractdialog_p.h new file mode 100644 index 00000000..5da2231d --- /dev/null +++ b/src/quickdialogs2/quickdialogs2/qquickabstractdialog_p.h @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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$ +** +****************************************************************************/ + +#ifndef QQUICKABSTRACTDIALOG_P_H +#define QQUICKABSTRACTDIALOG_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <memory> + +#include <QtCore/qobject.h> +#include <QtGui/qpa/qplatformtheme.h> +#include <QtGui/qpa/qplatformdialoghelper.h> +#include <QtQml/qqmlparserstatus.h> +#include <QtQml/qqmllist.h> +#include <QtQml/qqml.h> + +#include "qtquickdialogs2global_p.h" + +QT_BEGIN_NAMESPACE + +class QWindow; +class QPlatformDialogHelper; + +class Q_QUICKDIALOGS2_PRIVATE_EXPORT QQuickAbstractDialog : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(QQmlListProperty<QObject> data READ data FINAL) + Q_PROPERTY(QWindow *parentWindow READ parentWindow WRITE setParentWindow NOTIFY parentWindowChanged FINAL) + Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged FINAL) + Q_PROPERTY(Qt::WindowFlags flags READ flags WRITE setFlags NOTIFY flagsChanged FINAL) + Q_PROPERTY(Qt::WindowModality modality READ modality WRITE setModality NOTIFY modalityChanged FINAL) + Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged FINAL) + Q_PROPERTY(StandardCode result READ result WRITE setResult NOTIFY resultChanged FINAL) + Q_CLASSINFO("DefaultProperty", "data") + Q_MOC_INCLUDE(<QtGui/qwindow.h>) + QML_ANONYMOUS + QML_ADDED_IN_VERSION(6, 2) + +public: + explicit QQuickAbstractDialog(QPlatformTheme::DialogType type, QObject *parent = nullptr); + ~QQuickAbstractDialog(); + + QPlatformDialogHelper *handle() const; + + QQmlListProperty<QObject> data(); + + QWindow *parentWindow() const; + void setParentWindow(QWindow *window); + + QString title() const; + void setTitle(const QString &title); + + Qt::WindowFlags flags() const; + void setFlags(Qt::WindowFlags flags); + + Qt::WindowModality modality() const; + void setModality(Qt::WindowModality modality); + + bool isVisible() const; + void setVisible(bool visible); + + enum StandardCode { Rejected, Accepted }; + Q_ENUM(StandardCode) + + StandardCode result() const; + void setResult(StandardCode result); + +public Q_SLOTS: + void open(); + void close(); + virtual void accept(); + virtual void reject(); + virtual void done(StandardCode result); + +Q_SIGNALS: + void accepted(); + void rejected(); + void parentWindowChanged(); + void titleChanged(); + void flagsChanged(); + void modalityChanged(); + void visibleChanged(); + void resultChanged(); + +protected: + void classBegin() override; + void componentComplete() override; + + bool create(); + void destroy(); + + virtual bool useNativeDialog() const; + virtual void onCreate(QPlatformDialogHelper *dialog); + virtual void onShow(QPlatformDialogHelper *dialog); + virtual void onHide(QPlatformDialogHelper *dialog); + + QWindow *findParentWindow() const; + +private: + bool m_visibleRequested = false; + bool m_visible = false; + bool m_complete = false; + StandardCode m_result = Rejected; + QWindow *m_parentWindow = nullptr; + QString m_title; + Qt::WindowFlags m_flags = Qt::Dialog; + Qt::WindowModality m_modality = Qt::WindowModal; + QPlatformTheme::DialogType m_type; + QList<QObject *> m_data; + std::unique_ptr<QPlatformDialogHelper> m_handle; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickAbstractDialog) + +#endif // QQUICKABSTRACTDIALOG_P_H diff --git a/src/quickdialogs2/quickdialogs2/qquickfiledialog.cpp b/src/quickdialogs2/quickdialogs2/qquickfiledialog.cpp new file mode 100644 index 00000000..e2b34075 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2/qquickfiledialog.cpp @@ -0,0 +1,612 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 "qquickfiledialog_p.h" + +#include <QtCore/qlist.h> +#include <QtCore/qloggingcategory.h> +#include <QtQml/qqmlfile.h> + +#include <QtQuickDialogs2Utils/private/qquickfilenamefilter_p.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcFileDialog, "qt.quick.dialogs.filedialog") + +/*! + \qmltype FileDialog + \inherits Dialog +//! \instantiates QQuickFileDialog + \inqmlmodule QtQuick.Dialogs + \since 6.2 + \brief A file dialog. + + The FileDialog type provides a QML API for file dialogs. + + \image qtquickdialogs-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 selectedFile and \l selectedFiles + 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 + \endlist + + \include includes/fallback.qdocinc + + \sa FolderDialog, StandardPaths +*/ + +Q_DECLARE_LOGGING_CATEGORY(lcDialogs) + +QQuickFileDialog::QQuickFileDialog(QObject *parent) + : QQuickAbstractDialog(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 QtQuick.Dialogs::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. +*/ +QQuickFileDialog::FileMode QQuickFileDialog::fileMode() const +{ + return m_fileMode; +} + +void QQuickFileDialog::setFileMode(FileMode mode) +{ + qCDebug(lcFileDialog) << "setFileMode called with" << 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 QtQuick.Dialogs::FileDialog::selectedFile + \readonly + + This property holds the final accepted file. + + If there are multiple selected files, this property refers to the first + file. + + Unlike the \l currentFile property, the \c selectedFile 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 selectedFiles, currentFile, {Dialog::}{accepted()}, currentFolder +*/ +QUrl QQuickFileDialog::selectedFile() const +{ + return addDefaultSuffix(m_selectedFiles.value(0)); +} + +/*! + \qmlproperty list<url> QtQuick.Dialogs::FileDialog::selectedFiles + + This property holds the final accepted files. + + Unlike the \l currentFiles property, the \c selectedFiles 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()}, currentFolder +*/ +QList<QUrl> QQuickFileDialog::selectedFiles() const +{ + return addDefaultSuffixes(m_selectedFiles); +} + +void QQuickFileDialog::setSelectedFiles(const QList<QUrl> &selectedFiles) +{ + if (m_selectedFiles == selectedFiles) + return; + + bool firstChanged = m_selectedFiles.value(0) != selectedFiles.value(0); + m_selectedFiles = selectedFiles; + if (firstChanged) + emit selectedFileChanged(); + emit selectedFilesChanged(); +} + +/*! + \qmlproperty url QtQuick.Dialogs::FileDialog::currentFile + + This property holds the currently selected file in the dialog. + + Unlike the \l selectedFile 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 selectedFile, currentFiles, currentFolder +*/ +QUrl QQuickFileDialog::currentFile() const +{ + return currentFiles().value(0); +} + +void QQuickFileDialog::setCurrentFile(const QUrl &file) +{ + setCurrentFiles(QList<QUrl>() << file); +} + +/*! + \qmlproperty list<url> QtQuick.Dialogs::FileDialog::currentFiles + + This property holds the currently selected files in the dialog. + + Unlike the \l selectedFiles 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 selectedFiles, currentFile, currentFolder +*/ +QList<QUrl> QQuickFileDialog::currentFiles() const +{ + if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(handle())) + return fileDialog->selectedFiles(); + return m_options->initiallySelectedFiles(); +} + +void QQuickFileDialog::setCurrentFiles(const QList<QUrl> ¤tFiles) +{ + if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(handle())) { + for (const QUrl &file : currentFiles) + fileDialog->selectFile(file); + } + m_options->setInitiallySelectedFiles(currentFiles); +} + +/*! + \qmlproperty url QtQuick.Dialogs::FileDialog::currentFolder + + This property holds the folder where files are selected. It can be set to + control the initial directory that is shown when the dialog is opened. + + For selecting a folder, use FolderDialog instead. + + \sa FolderDialog +*/ +QUrl QQuickFileDialog::currentFolder() const +{ + if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(handle())) + return fileDialog->directory(); + + // If we're not using a native file dialog and the folder is invalid, + // return the current directory. + if (!m_options->initialDirectory().isValid()) + return QUrl::fromLocalFile(QDir::currentPath()); + + return m_options->initialDirectory(); +} + +void QQuickFileDialog::setCurrentFolder(const QUrl ¤tFolder) +{ + qCDebug(lcFileDialog) << "setCurrentFolder called with" << currentFolder; + if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(handle())) + fileDialog->setDirectory(currentFolder); + m_options->setInitialDirectory(currentFolder); +} + +/*! + \qmlproperty flags QtQuick.Dialogs::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 QQuickFileDialog::options() const +{ + return m_options->options(); +} + +void QQuickFileDialog::setOptions(QFileDialogOptions::FileDialogOptions options) +{ + if (options == m_options->options()) + return; + + m_options->setOptions(options); + emit optionsChanged(); +} + +void QQuickFileDialog::resetOptions() +{ + setOptions({}); +} + +/*! + \qmlproperty list<string> QtQuick.Dialogs::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 + + Different platforms may restrict the files that can be selected in + different ways. For example, macOS will disable file entries that do not + match the filters, whereas Windows will hide them. + + \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 QQuickFileDialog::nameFilters() const +{ + return m_options->nameFilters(); +} + +void QQuickFileDialog::setNameFilters(const QStringList &filters) +{ + qCDebug(lcFileDialog).nospace() << "setNameFilters called with " << filters + << " (old filters were: " << m_options->nameFilters() << ")"; + 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 QQuickFileDialog::resetNameFilters() +{ + setNameFilters(QStringList()); +} + +/*! + \qmlproperty int QtQuick.Dialogs::FileDialog::selectedNameFilter.index + \qmlproperty string QtQuick.Dialogs::FileDialog::selectedNameFilter.name + \qmlproperty list<string> QtQuick.Dialogs::FileDialog::selectedNameFilter.extensions + \qmlproperty list<string> QtQuick.Dialogs::FileDialog::selectedNameFilter.globs + + 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"]}. + \row + \li [read-only] \b globs : list<string> + \li This property holds the list of globs of the selected filter. + In the example below, the list of globs of the first filter is + \c {["*.txt"]} and the second is \c {["*.html", "*.htm"]}. + + This property is useful in conjunction with \l {FolderListModel}'s + \l {FolderListModel::}{nameFilters} property, for example. + \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 +*/ +QQuickFileNameFilter *QQuickFileDialog::selectedNameFilter() const +{ + if (!m_selectedNameFilter) { + QQuickFileDialog *that = const_cast<QQuickFileDialog *>(this); + m_selectedNameFilter = new QQuickFileNameFilter(that); + m_selectedNameFilter->setOptions(m_options); + } + return m_selectedNameFilter; +} + +/*! + \qmlproperty string QtQuick.Dialogs::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 QQuickFileDialog::defaultSuffix() const +{ + return m_options->defaultSuffix(); +} + +void QQuickFileDialog::setDefaultSuffix(const QString &suffix) +{ + if (suffix == m_options->defaultSuffix()) + return; + + m_options->setDefaultSuffix(suffix); + emit defaultSuffixChanged(); +} + +void QQuickFileDialog::resetDefaultSuffix() +{ + setDefaultSuffix(QString()); +} + +/*! + \qmlproperty string QtQuick.Dialogs::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 QQuickFileDialog::acceptLabel() const +{ + return m_options->labelText(QFileDialogOptions::Accept); +} + +void QQuickFileDialog::setAcceptLabel(const QString &label) +{ + if (label == m_options->labelText(QFileDialogOptions::Accept)) + return; + + m_options->setLabelText(QFileDialogOptions::Accept, label); + emit acceptLabelChanged(); +} + +void QQuickFileDialog::resetAcceptLabel() +{ + setAcceptLabel(QString()); +} + +/*! + \qmlproperty string QtQuick.Dialogs::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 QQuickFileDialog::rejectLabel() const +{ + return m_options->labelText(QFileDialogOptions::Reject); +} + +void QQuickFileDialog::setRejectLabel(const QString &label) +{ + if (label == m_options->labelText(QFileDialogOptions::Reject)) + return; + + m_options->setLabelText(QFileDialogOptions::Reject, label); + emit rejectLabelChanged(); +} + +void QQuickFileDialog::resetRejectLabel() +{ + setRejectLabel(QString()); +} + +bool QQuickFileDialog::useNativeDialog() const +{ + if (!QQuickAbstractDialog::useNativeDialog()) + return false; + + if (m_options->testOption(QFileDialogOptions::DontUseNativeDialog)) { + qCDebug(lcDialogs) << " - the FileDialog was told not to use a native dialog; not using native dialog"; + return false; + } + + return true; +} + +void QQuickFileDialog::onCreate(QPlatformDialogHelper *dialog) +{ + if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(dialog)) { + connect(fileDialog, &QPlatformFileDialogHelper::currentChanged, this, &QQuickFileDialog::currentFileChanged); + connect(fileDialog, &QPlatformFileDialogHelper::currentChanged, this, &QQuickFileDialog::currentFilesChanged); + connect(fileDialog, &QPlatformFileDialogHelper::directoryEntered, this, &QQuickFileDialog::currentFolderChanged); + fileDialog->setOptions(m_options); + } +} + +void QQuickFileDialog::onShow(QPlatformDialogHelper *dialog) +{ + m_options->setWindowTitle(title()); + if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(dialog)) { + // Ensure that a name filter is always selected. + int index = selectedNameFilter()->index(); + if (index == -1) + index = 0; + const QString filter = m_options->nameFilters().value(index); + m_options->setInitiallySelectedNameFilter(filter); + + fileDialog->setOptions(m_options); // setOptions only assigns a member and isn't virtual + + connect(fileDialog, &QPlatformFileDialogHelper::filterSelected, m_selectedNameFilter, &QQuickFileNameFilter::update); + fileDialog->selectNameFilter(filter); + + const QUrl initialDir = m_options->initialDirectory(); + // If it's not valid, or it's a file and not a directory, we shouldn't set it. + if (m_firstShow && initialDir.isValid() && QDir(QQmlFile::urlToLocalFileOrQrc(initialDir)).exists()) + fileDialog->setDirectory(m_options->initialDirectory()); + } + if (m_firstShow) + m_firstShow = false; +} + +void QQuickFileDialog::onHide(QPlatformDialogHelper *dialog) +{ + if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(dialog)) { + if (m_selectedNameFilter) + disconnect(fileDialog, &QPlatformFileDialogHelper::filterSelected, m_selectedNameFilter, &QQuickFileNameFilter::update); + } +} + +void QQuickFileDialog::accept() +{ + if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(handle())) { + // Take the currently selected files and make them the final set of files. + setSelectedFiles(fileDialog->selectedFiles()); + } + QQuickAbstractDialog::accept(); +} + +QUrl QQuickFileDialog::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> QQuickFileDialog::addDefaultSuffixes(const QList<QUrl> &files) const +{ + QList<QUrl> urls; + urls.reserve(files.size()); + for (const QUrl &file : files) + urls += addDefaultSuffix(file); + return urls; +} + +QT_END_NAMESPACE diff --git a/src/quickdialogs2/quickdialogs2/qquickfiledialog_p.h b/src/quickdialogs2/quickdialogs2/qquickfiledialog_p.h new file mode 100644 index 00000000..a0fbb686 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2/qquickfiledialog_p.h @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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$ +** +****************************************************************************/ + +#ifndef QQUICKFILEDIALOG_P_H +#define QQUICKFILEDIALOG_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qurl.h> +#include <QtQml/qqml.h> + +#include "qquickabstractdialog_p.h" + +QT_BEGIN_NAMESPACE + +class QQuickFileNameFilter; + +class Q_QUICKDIALOGS2_PRIVATE_EXPORT QQuickFileDialog : public QQuickAbstractDialog +{ + Q_OBJECT + Q_PROPERTY(FileMode fileMode READ fileMode WRITE setFileMode NOTIFY fileModeChanged FINAL) + Q_PROPERTY(QUrl selectedFile READ selectedFile NOTIFY selectedFileChanged FINAL) + Q_PROPERTY(QList<QUrl> selectedFiles READ selectedFiles NOTIFY selectedFilesChanged FINAL) + Q_PROPERTY(QUrl currentFile READ currentFile WRITE setCurrentFile NOTIFY currentFileChanged FINAL) + Q_PROPERTY(QList<QUrl> currentFiles READ currentFiles WRITE setCurrentFiles NOTIFY currentFilesChanged FINAL) + Q_PROPERTY(QUrl currentFolder READ currentFolder WRITE setCurrentFolder NOTIFY currentFolderChanged FINAL) + Q_PROPERTY(QFileDialogOptions::FileDialogOptions options READ options WRITE setOptions RESET resetOptions NOTIFY optionsChanged FINAL) + Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters RESET resetNameFilters NOTIFY nameFiltersChanged FINAL) + Q_PROPERTY(QQuickFileNameFilter *selectedNameFilter READ selectedNameFilter CONSTANT) + Q_PROPERTY(QString defaultSuffix READ defaultSuffix WRITE setDefaultSuffix RESET resetDefaultSuffix NOTIFY defaultSuffixChanged FINAL) + Q_PROPERTY(QString acceptLabel READ acceptLabel WRITE setAcceptLabel RESET resetAcceptLabel NOTIFY acceptLabelChanged FINAL) + Q_PROPERTY(QString rejectLabel READ rejectLabel WRITE setRejectLabel RESET resetRejectLabel NOTIFY rejectLabelChanged FINAL) + Q_FLAGS(QFileDialogOptions::FileDialogOptions) + QML_NAMED_ELEMENT(FileDialog) + QML_ADDED_IN_VERSION(6, 2) + Q_MOC_INCLUDE(<QtQuickDialogs2Utils/private/qquickfilenamefilter_p.h>) + +public: + explicit QQuickFileDialog(QObject *parent = nullptr); + + enum FileMode { + OpenFile, + OpenFiles, + SaveFile + }; + Q_ENUM(FileMode) + + FileMode fileMode() const; + void setFileMode(FileMode fileMode); + + QUrl selectedFile() const; + + QList<QUrl> selectedFiles() const; + + QUrl currentFile() const; + void setCurrentFile(const QUrl &file); + + QList<QUrl> currentFiles() const; + void setCurrentFiles(const QList<QUrl> ¤tFiles); + + QUrl currentFolder() const; + void setCurrentFolder(const QUrl ¤tFolder); + + QFileDialogOptions::FileDialogOptions options() const; + void setOptions(QFileDialogOptions::FileDialogOptions options); + void resetOptions(); + + QStringList nameFilters() const; + void setNameFilters(const QStringList &filters); + void resetNameFilters(); + + QQuickFileNameFilter *selectedNameFilter() const; + + QString defaultSuffix() const; + void setDefaultSuffix(const QString &suffix); + void resetDefaultSuffix(); + + QString acceptLabel() const; + void setAcceptLabel(const QString &label); + void resetAcceptLabel(); + + QString rejectLabel() const; + void setRejectLabel(const QString &label); + void resetRejectLabel(); + +Q_SIGNALS: + void fileModeChanged(); + void selectedFileChanged(); + void selectedFilesChanged(); + void currentFileChanged(); + void currentFilesChanged(); + void currentFolderChanged(); + void optionsChanged(); + void nameFiltersChanged(); + void defaultSuffixChanged(); + void acceptLabelChanged(); + void rejectLabelChanged(); + +protected: + bool useNativeDialog() const override; + void onCreate(QPlatformDialogHelper *dialog) override; + void onShow(QPlatformDialogHelper *dialog) override; + void onHide(QPlatformDialogHelper *dialog) override; + void accept() override; + +private: + QUrl addDefaultSuffix(const QUrl &file) const; + QList<QUrl> addDefaultSuffixes(const QList<QUrl> &files) const; + + void setSelectedFiles(const QList<QUrl> &selectedFiles); + + FileMode m_fileMode; + QList<QUrl> m_selectedFiles; + bool m_firstShow = true; + QSharedPointer<QFileDialogOptions> m_options; + mutable QQuickFileNameFilter *m_selectedNameFilter; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickFileDialog) + +#endif // QQUICKFILEDIALOG_P_H diff --git a/src/quickdialogs2/quickdialogs2/qtquickdialogs2foreign_p.h b/src/quickdialogs2/quickdialogs2/qtquickdialogs2foreign_p.h new file mode 100644 index 00000000..fbab7ef7 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2/qtquickdialogs2foreign_p.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 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$ +** +****************************************************************************/ + +#ifndef QTQUICKDIALOGS2FOREIGN_P_H +#define QTQUICKDIALOGS2FOREIGN_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/qpa/qplatformdialoghelper.h> +#include <QtQml/qqml.h> +#include <QtQuickDialogs2Utils/private/qquickfilenamefilter_p.h> + +QT_BEGIN_NAMESPACE + +struct QPlatformDialogHelperForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QPlatformDialogHelper) + QML_ADDED_IN_VERSION(6, 2) +}; + +struct QQuickFileNameFilterForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QQuickFileNameFilter) + QML_ADDED_IN_VERSION(6, 2) +}; + +QT_END_NAMESPACE + +#endif // QTQUICKDIALOGS2FOREIGN_P_H diff --git a/src/quickdialogs2/quickdialogs2/qtquickdialogs2global_p.h b/src/quickdialogs2/quickdialogs2/qtquickdialogs2global_p.h new file mode 100644 index 00000000..87b8cdbf --- /dev/null +++ b/src/quickdialogs2/quickdialogs2/qtquickdialogs2global_p.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 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$ +** +****************************************************************************/ + +#ifndef QTQUICKDIALOGS2GLOBAL_P_H +#define QTQUICKDIALOGS2GLOBAL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qglobal.h> +#include <QtQml/private/qqmlglobal_p.h> + +QT_BEGIN_NAMESPACE + +#ifndef QT_STATIC +# if defined(QT_BUILD_QUICKDIALOGS2_LIB) +# define Q_QUICKDIALOGS2_PRIVATE_EXPORT Q_DECL_EXPORT +# else +# define Q_QUICKDIALOGS2_PRIVATE_EXPORT Q_DECL_IMPORT +# endif +#else +# define Q_QUICKDIALOGS2_PRIVATE_EXPORT +#endif + +QT_END_NAMESPACE + +Q_QUICKDIALOGS2_PRIVATE_EXPORT void qml_register_types_QtQuick_Dialogs(); + +#endif // QTQUICKDIALOGS2GLOBAL_P_H diff --git a/src/quickdialogs2/quickdialogs2quickimpl/CMakeLists.txt b/src/quickdialogs2/quickdialogs2quickimpl/CMakeLists.txt new file mode 100644 index 00000000..f5ad89e8 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/CMakeLists.txt @@ -0,0 +1,134 @@ +##################################################################### +## QuickDialogs2QuickImpl Module: +##################################################################### + +qt_internal_add_module(QuickDialogs2QuickImpl + SOURCES + qquickdialogimplfactory.cpp + qquickdialogimplfactory_p.h + qquickfiledialogdelegate.cpp + qquickfiledialogdelegate_p.h + qquickfiledialogimpl.cpp + qquickfiledialogimpl_p.h + qquickfiledialogimpl_p_p.h + qquickfolderbreadcrumbbar.cpp + qquickfolderbreadcrumbbar_p.h + qquickfolderbreadcrumbbar_p_p.h + qquickplatformfiledialog.cpp + qquickplatformfiledialog_p.h + qtquickdialogs2quickimplforeign_p.h + qtquickdialogs2quickimplglobal_p.h + DEFINES + QT_BUILD_QUICKDIALOGS2QUICKIMPL_LIB + QT_NO_CAST_FROM_ASCII + QT_NO_CAST_TO_ASCII + INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_SOURCE_DIR} + LIBRARIES + Qt::CorePrivate + Qt::GuiPrivate + Qt::QmlPrivate + Qt::QuickPrivate + Qt::QuickTemplates2Private + Qt::QuickDialogs2UtilsPrivate + PUBLIC_LIBRARIES + Qt::Core + Qt::Gui + Qt::Quick +) + +# Resources: +set(qtquickdialogs2quickimpl_resource_files + "images/crumb-separator-icon-round.png" + "images/crumb-separator-icon-round@2x.png" + "images/crumb-separator-icon-round@3x.png" + "images/crumb-separator-icon-round@4x.png" + "images/crumb-separator-icon-square.png" + "images/crumb-separator-icon-square@2x.png" + "images/crumb-separator-icon-square@3x.png" + "images/crumb-separator-icon-square@4x.png" + "images/file-icon-round.png" + "images/file-icon-round@2x.png" + "images/file-icon-round@3x.png" + "images/file-icon-round@4x.png" + "images/file-icon-square.png" + "images/file-icon-square@2x.png" + "images/file-icon-square@3x.png" + "images/file-icon-square@4x.png" + "images/folder-icon-round.png" + "images/folder-icon-round@2x.png" + "images/folder-icon-round@3x.png" + "images/folder-icon-round@4x.png" + "images/folder-icon-square.png" + "images/folder-icon-square@2x.png" + "images/folder-icon-square@3x.png" + "images/folder-icon-square@4x.png" + "images/imagine/filedialogdelegate-background@2x.9.png" + "images/imagine/filedialogdelegate-background@3x.9.png" + "images/imagine/filedialogdelegate-background@4x.9.png" + "images/imagine/filedialogdelegate-background.9.png" + "images/imagine/filedialogdelegate-background-disabled@2x.9.png" + "images/imagine/filedialogdelegate-background-disabled@3x.9.png" + "images/imagine/filedialogdelegate-background-disabled@4x.9.png" + "images/imagine/filedialogdelegate-background-disabled.9.png" + "images/imagine/filedialogdelegate-background-pressed@2x.9.png" + "images/imagine/filedialogdelegate-background-pressed@3x.9.png" + "images/imagine/filedialogdelegate-background-pressed@4x.9.png" + "images/imagine/filedialogdelegate-background-pressed.9.png" + "images/imagine/filedialogdelegate-background-focused@2x.9.png" + "images/imagine/filedialogdelegate-background-focused@3x.9.png" + "images/imagine/filedialogdelegate-background-focused@4x.9.png" + "images/imagine/filedialogdelegate-background-focused.9.png" + "images/imagine/filedialogdelegate-background-highlighted@2x.9.png" + "images/imagine/filedialogdelegate-background-highlighted@3x.9.png" + "images/imagine/filedialogdelegate-background-highlighted@4x.9.png" + "images/imagine/filedialogdelegate-background-highlighted.9.png" + "images/up-icon-round.png" + "images/up-icon-round@2x.png" + "images/up-icon-round@3x.png" + "images/up-icon-round@4x.png" + "images/up-icon-square.png" + "images/up-icon-square@2x.png" + "images/up-icon-square@3x.png" + "images/up-icon-square@4x.png" + "images/up-icon-thick-square.png" + "images/up-icon-thick-square@2x.png" + "images/up-icon-thick-square@3x.png" + "images/up-icon-thick-square@4x.png" + "qml/FileDialog.qml" + "qml/FileDialogDelegate.qml" + "qml/FileDialogDelegateLabel.qml" + "qml/FolderBreadcrumbBar.qml" + "qml/+Fusion/FileDialog.qml" + "qml/+Fusion/FileDialogDelegate.qml" + "qml/+Fusion/FolderBreadcrumbBar.qml" + "qml/+Imagine/FileDialog.qml" + "qml/+Imagine/FileDialogDelegate.qml" + "qml/+Imagine/FolderBreadcrumbBar.qml" + "qml/+Material/FileDialog.qml" + "qml/+Material/FileDialogDelegate.qml" + "qml/+Material/FolderBreadcrumbBar.qml" + "qml/+Universal/FileDialog.qml" + "qml/+Universal/FileDialogDelegate.qml" + "qml/+Universal/FolderBreadcrumbBar.qml" +) + +qt_internal_add_resource(QuickDialogs2QuickImpl "QuickDialogs2QuickImpl" + PREFIX + "/qt-project.org/imports/QtQuick/Dialogs/quickimpl" + FILES + ${qtquickdialogs2quickimpl_resource_files} +) + +## Scopes: +##################################################################### + +set_target_properties(QuickDialogs2QuickImpl PROPERTIES + QT_QML_MODULE_INSTALL_QMLTYPES TRUE + QT_QML_MODULE_VERSION ${CMAKE_PROJECT_VERSION} + QT_QML_MODULE_URI QtQuick.Dialogs.quickimpl + QT_QMLTYPES_FILENAME plugins.qmltypes + QT_QML_MODULE_INSTALL_DIR "${INSTALL_QMLDIR}/QtQuick/Dialogs/quickimpl" +) + +qt6_qml_type_registration(QuickDialogs2QuickImpl) diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-round.png b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-round.png Binary files differnew file mode 100644 index 00000000..aaa5a3e4 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-round.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-round.svg b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-round.svg new file mode 100644 index 00000000..013cedef --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-round.svg @@ -0,0 +1,136 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="8" + height="8" + viewBox="0 0 8 8" + version="1.1" + id="svg2" + inkscape:version="1.1-alpha (b0f32e08fc, 2021-03-07)" + sodipodi:docname="crumb-separator-icon-round.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8"> + <inkscape:path-effect + effect="fillet_chamfer" + id="path-effect3421" + is_visible="true" + lpeversion="1" + satellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1" + unit="px" + method="auto" + mode="F" + radius="1" + chamfer_steps="1" + flexible="false" + use_knot_distance="true" + apply_no_radius="true" + apply_with_radius="true" + only_selected="false" + hide_knots="false" /> + <inkscape:path-effect + effect="fillet_chamfer" + id="path-effect1367" + is_visible="true" + lpeversion="1" + satellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1" + unit="px" + method="auto" + mode="F" + radius="1" + chamfer_steps="1" + flexible="false" + use_knot_distance="true" + apply_no_radius="true" + apply_with_radius="true" + only_selected="false" + hide_knots="false" /> + <inkscape:path-effect + effect="fillet_chamfer" + id="path-effect1365" + is_visible="true" + lpeversion="1" + satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1" + unit="px" + method="auto" + mode="F" + radius="1" + chamfer_steps="1" + flexible="false" + use_knot_distance="true" + apply_no_radius="true" + apply_with_radius="true" + only_selected="false" + hide_knots="false" /> + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1848" + inkscape:window-height="1016" + id="namedview6" + showgrid="true" + inkscape:zoom="16.000001" + inkscape:cx="6.7812496" + inkscape:cy="15.031249" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" + inkscape:pagecheckerboard="0" + height="14px"> + <inkscape:grid + type="xygrid" + id="grid856" /> + </sodipodi:namedview> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="crumb-separator-icon-round" + transform="translate(2.046,3.0914383)"> + <rect + style="fill:none;stroke-width:4;stroke-linejoin:round" + id="rect2017" + width="8" + height="8" + x="-2.046" + y="-3.0914383" /> + </g> + <path + id="rect858" + style="fill:#757575;stroke-width:2.43737;stroke-linejoin:round" + d="M 3.0219754,1.01117 6.416287,3.5317721 a 0.74259596,0.74259596 90 0 1 0,1.1923778 L 3.0219754,7.2447519 A 0.5029757,0.5029757 26.701285 0 1 2.2191312,6.8409408 l 0,-5.4259596 A 0.5029757,0.5029757 153.29872 0 1 3.0219754,1.01117 Z" + sodipodi:nodetypes="cccc" + inkscape:path-effect="#path-effect3421" + inkscape:original-d="m 2.2191312,0.41498117 5,3.71297983 -5,3.7129798 z" /> + <rect + style="fill:none;stroke-width:4;stroke-linejoin:round" + id="rect1449" + width="8" + height="8" + x="0" + y="0" /> +</svg> diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-round@2x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-round@2x.png Binary files differnew file mode 100644 index 00000000..3f66fe17 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-round@2x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-round@3x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-round@3x.png Binary files differnew file mode 100644 index 00000000..24d9f6a6 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-round@3x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-round@4x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-round@4x.png Binary files differnew file mode 100644 index 00000000..1dc83ef0 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-round@4x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-square.png b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-square.png Binary files differnew file mode 100644 index 00000000..1f7ac63e --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-square.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-square.svg b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-square.svg new file mode 100644 index 00000000..96636734 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-square.svg @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="8" + height="8" + viewBox="0 0 8 8" + version="1.1" + id="svg2" + inkscape:version="1.1-alpha (b0f32e08fc, 2021-03-07)" + sodipodi:docname="crumb-separator-icon-square.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8"> + <inkscape:path-effect + effect="fillet_chamfer" + id="path-effect3421" + is_visible="true" + lpeversion="1" + satellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1" + unit="px" + method="auto" + mode="F" + radius="1" + chamfer_steps="1" + flexible="false" + use_knot_distance="true" + apply_no_radius="true" + apply_with_radius="true" + only_selected="false" + hide_knots="false" /> + <inkscape:path-effect + effect="fillet_chamfer" + id="path-effect1367" + is_visible="true" + lpeversion="1" + satellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1" + unit="px" + method="auto" + mode="F" + radius="1" + chamfer_steps="1" + flexible="false" + use_knot_distance="true" + apply_no_radius="true" + apply_with_radius="true" + only_selected="false" + hide_knots="false" /> + <inkscape:path-effect + effect="fillet_chamfer" + id="path-effect1365" + is_visible="true" + lpeversion="1" + satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1" + unit="px" + method="auto" + mode="F" + radius="1" + chamfer_steps="1" + flexible="false" + use_knot_distance="true" + apply_no_radius="true" + apply_with_radius="true" + only_selected="false" + hide_knots="false" /> + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1848" + inkscape:window-height="1016" + id="namedview6" + showgrid="true" + inkscape:zoom="90.509672" + inkscape:cx="4.43599" + inkscape:cy="3.3311357" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" + inkscape:pagecheckerboard="0" + height="14px"> + <inkscape:grid + type="xygrid" + id="grid856" /> + </sodipodi:namedview> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="crumb-separator-icon-square" + transform="translate(2.046,3.0914383)"> + <rect + style="fill:none;stroke-width:4;stroke-linejoin:round" + id="rect2017" + width="8" + height="8" + x="-2.046" + y="-3.0914383" /> + <path + style="fill:#757575;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -0.046,-2.0914383 4,3 -4,3 z" + id="path1065" + sodipodi:nodetypes="cccc" /> + </g> + <rect + style="fill:none;stroke-width:4;stroke-linejoin:round" + id="rect1449" + width="8" + height="8" + x="0" + y="0" /> +</svg> diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-square@2x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-square@2x.png Binary files differnew file mode 100644 index 00000000..524872c1 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-square@2x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-square@3x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-square@3x.png Binary files differnew file mode 100644 index 00000000..33bce847 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-square@3x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-square@4x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-square@4x.png Binary files differnew file mode 100644 index 00000000..185c3eb7 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/crumb-separator-icon-square@4x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-round.png b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-round.png Binary files differnew file mode 100644 index 00000000..c2a4928d --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-round.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-round.svg b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-round.svg new file mode 100644 index 00000000..7ac6a23b --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-round.svg @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="14" + height="18" + viewBox="0 0 14 18" + version="1.1" + id="svg2" + inkscape:version="1.1-alpha (b0f32e08fc, 2021-03-07)" + sodipodi:docname="file-icon-round.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <path + style="fill:none;stroke:#757575;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1.5003822,2.5063148 1.5000273,15.5 A 0.99997269,0.99997269 45.000782 0 0 2.5,16.5 h 9 a 1,1 135 0 0 1,-1 V 6.3440001 A 2.3479106,2.3479106 66.930332 0 0 11.778973,4.6510931 L 9.221027,2.192907 A 2.4805476,2.4805476 21.904486 0 0 7.5000004,1.5009022 l -4.9995913,0.00451 A 1.0009299,1.0009299 134.97494 0 0 1.5003822,2.5063148 Z" + id="path917" + sodipodi:nodetypes="cccccc" + inkscape:path-effect="#path-effect1367" + inkscape:original-d="M 1.5004095,1.5063148 1.5,16.5 h 11 V 5.3440001 L 8.5,1.5 Z" /> + <path + id="path921" + style="fill:none;stroke:#757575;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 12.35,6.5 H 8.5 a 1,1 45 0 1 -1,-1 V 1.55" + inkscape:path-effect="#path-effect1365" + inkscape:original-d="M 12.35,6.5 H 7.5 V 1.55" + sodipodi:nodetypes="ccc" /> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8"> + <inkscape:path-effect + effect="fillet_chamfer" + id="path-effect1367" + is_visible="true" + lpeversion="1" + satellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1" + unit="px" + method="auto" + mode="F" + radius="1" + chamfer_steps="1" + flexible="false" + use_knot_distance="true" + apply_no_radius="true" + apply_with_radius="true" + only_selected="false" + hide_knots="false" /> + <inkscape:path-effect + effect="fillet_chamfer" + id="path-effect1365" + is_visible="true" + lpeversion="1" + satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1" + unit="px" + method="auto" + mode="F" + radius="1" + chamfer_steps="1" + flexible="false" + use_knot_distance="true" + apply_no_radius="true" + apply_with_radius="true" + only_selected="false" + hide_knots="false" /> + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1848" + inkscape:window-height="1016" + id="namedview6" + showgrid="true" + inkscape:zoom="32.000001" + inkscape:cx="7.8906248" + inkscape:cy="9.7343747" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid856" /> + </sodipodi:namedview> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="file-icon-round" + transform="translate(2.046,3.0914383)" /> + <rect + style="fill:none;stroke-width:4;stroke-linejoin:round" + id="rect2270" + width="14" + height="18" + x="0" + y="0" /> +</svg> diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-round@2x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-round@2x.png Binary files differnew file mode 100644 index 00000000..86af70d1 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-round@2x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-round@3x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-round@3x.png Binary files differnew file mode 100644 index 00000000..06fea29e --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-round@3x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-round@4x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-round@4x.png Binary files differnew file mode 100644 index 00000000..09f87873 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-round@4x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-square.png b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-square.png Binary files differnew file mode 100644 index 00000000..9e8f3ddd --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-square.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-square.svg b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-square.svg new file mode 100644 index 00000000..107afa38 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-square.svg @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="14" + height="18" + viewBox="0 0 14 18" + version="1.1" + id="svg2" + inkscape:version="1.1-alpha (b0f32e08fc, 2021-03-07)" + sodipodi:docname="file-icon-square.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <path + style="fill:none;stroke:#757575;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1.5004095,1.5063148 1.5,16.5 h 11 V 5.3440001 L 8.5,1.5 Z" + id="path917" + sodipodi:nodetypes="cccccc" /> + <path + style="fill:none;stroke:#757575;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 8.5,1.55 V 5.5" + id="path919" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#757575;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 12.45,5.5 H 8.5" + id="path921" + sodipodi:nodetypes="cc" /> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1848" + inkscape:window-height="1016" + id="namedview6" + showgrid="true" + inkscape:zoom="22.627417" + inkscape:cx="0.022097087" + inkscape:cy="8.1538251" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid856" /> + </sodipodi:namedview> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="file-icon-square" + transform="translate(2.046,3.0914383)" /> + <rect + style="fill:none;stroke-width:4;stroke-linejoin:round" + id="rect1895" + width="14" + height="18" + x="0" + y="0" /> +</svg> diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-square@2x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-square@2x.png Binary files differnew file mode 100644 index 00000000..8d330677 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-square@2x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-square@3x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-square@3x.png Binary files differnew file mode 100644 index 00000000..2987d2ca --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-square@3x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-square@4x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-square@4x.png Binary files differnew file mode 100644 index 00000000..69d76176 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/file-icon-square@4x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-round.png b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-round.png Binary files differnew file mode 100644 index 00000000..60bc6c72 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-round.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-round.svg b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-round.svg new file mode 100644 index 00000000..ba6b6276 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-round.svg @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="18" + height="14" + viewBox="0 0 18 14" + version="1.1" + id="svg2" + inkscape:version="1.1-alpha (b0f32e08fc, 2021-03-07)" + sodipodi:docname="folder-icon-round.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <path + style="fill:none;stroke:#757575;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1.5003723,2.5063148 1.5000372,11.5 A 0.99996275,0.99996275 45.001067 0 0 2.5,12.5 h 13 a 1,1 135 0 0 1,-1 v -7 a 1,1 45 0 0 -1,-1 h -5 A 2.4142136,2.4142136 22.5 0 1 8.7928932,2.7928932 L 8.2071068,2.2071068 A 2.4106245,2.4106245 22.469847 0 0 6.5000006,1.5010525 l -3.9995917,0.00421 A 1.0010904,1.0010904 134.97091 0 0 1.5003723,2.5063148 Z" + id="path917" + sodipodi:nodetypes="ccccccc" + inkscape:path-effect="#path-effect1132" + inkscape:original-d="M 1.5004095,1.5063148 1.5,12.5 h 15 v -9 h -7 l -2,-2 z" /> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8"> + <inkscape:path-effect + effect="fillet_chamfer" + id="path-effect1132" + is_visible="true" + lpeversion="1" + satellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1" + unit="px" + method="auto" + mode="F" + radius="1" + chamfer_steps="1" + flexible="false" + use_knot_distance="true" + apply_no_radius="true" + apply_with_radius="true" + only_selected="false" + hide_knots="false" /> + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1848" + inkscape:window-height="1016" + id="namedview6" + showgrid="true" + inkscape:zoom="16" + inkscape:cx="-11.28125" + inkscape:cy="7.40625" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid856" /> + </sodipodi:namedview> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="folder-icon-round" + transform="translate(2.046,3.0914383)" /> + <rect + style="fill:none;stroke-width:4;stroke-linejoin:round" + id="rect2548" + width="18" + height="14" + x="0" + y="0" /> +</svg> diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-round@2x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-round@2x.png Binary files differnew file mode 100644 index 00000000..e2873437 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-round@2x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-round@3x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-round@3x.png Binary files differnew file mode 100644 index 00000000..5ee70a95 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-round@3x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-round@4x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-round@4x.png Binary files differnew file mode 100644 index 00000000..47d28a4b --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-round@4x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-square.png b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-square.png Binary files differnew file mode 100644 index 00000000..b84a90ae --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-square.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-square.svg b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-square.svg new file mode 100644 index 00000000..0002b5ac --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-square.svg @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="18" + height="14" + viewBox="0 0 18 14" + version="1.1" + id="svg2" + inkscape:version="1.1-alpha (b0f32e08fc, 2021-03-07)" + sodipodi:docname="folder-icon-square.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <path + style="fill:none;stroke:#757575;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1.5004095,1.5063148 1.5,12.5 h 15 v -9 h -7 l -2,-2 z" + id="path917" + sodipodi:nodetypes="ccccccc" /> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1848" + inkscape:window-height="1016" + id="namedview6" + showgrid="true" + inkscape:zoom="16" + inkscape:cx="-1.59375" + inkscape:cy="12.65625" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid856" /> + </sodipodi:namedview> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="folder-icon-square" + transform="translate(2.046,3.0914383)"> + <rect + style="fill:none;stroke-width:4;stroke-linejoin:round" + id="rect1423" + width="18" + height="14" + x="-2.046" + y="-3.0914383" /> + </g> +</svg> diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-square@2x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-square@2x.png Binary files differnew file mode 100644 index 00000000..9bf320bb --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-square@2x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-square@3x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-square@3x.png Binary files differnew file mode 100644 index 00000000..9f12edb6 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-square@3x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-square@4x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-square@4x.png Binary files differnew file mode 100644 index 00000000..9ec5b389 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/folder-icon-square@4x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-disabled.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-disabled.9.png Binary files differnew file mode 100644 index 00000000..ce48ee74 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-disabled.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-disabled@2x.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-disabled@2x.9.png Binary files differnew file mode 100644 index 00000000..c7abb65c --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-disabled@2x.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-disabled@3x.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-disabled@3x.9.png Binary files differnew file mode 100644 index 00000000..46b84d7d --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-disabled@3x.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-disabled@4x.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-disabled@4x.9.png Binary files differnew file mode 100644 index 00000000..f4dfd338 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-disabled@4x.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-focused.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-focused.9.png Binary files differnew file mode 100644 index 00000000..39fa8664 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-focused.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-focused@2x.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-focused@2x.9.png Binary files differnew file mode 100644 index 00000000..6b61562c --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-focused@2x.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-focused@3x.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-focused@3x.9.png Binary files differnew file mode 100644 index 00000000..e46c0bf1 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-focused@3x.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-focused@4x.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-focused@4x.9.png Binary files differnew file mode 100644 index 00000000..010444e8 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-focused@4x.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-highlighted.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-highlighted.9.png Binary files differnew file mode 100644 index 00000000..6f565e8b --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-highlighted.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-highlighted@2x.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-highlighted@2x.9.png Binary files differnew file mode 100644 index 00000000..9fd0a434 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-highlighted@2x.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-highlighted@3x.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-highlighted@3x.9.png Binary files differnew file mode 100644 index 00000000..62b7435b --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-highlighted@3x.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-highlighted@4x.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-highlighted@4x.9.png Binary files differnew file mode 100644 index 00000000..96444b89 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-highlighted@4x.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-pressed.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-pressed.9.png Binary files differnew file mode 100644 index 00000000..39fa8664 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-pressed.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-pressed@2x.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-pressed@2x.9.png Binary files differnew file mode 100644 index 00000000..6b61562c --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-pressed@2x.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-pressed@3x.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-pressed@3x.9.png Binary files differnew file mode 100644 index 00000000..e46c0bf1 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-pressed@3x.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-pressed@4x.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-pressed@4x.9.png Binary files differnew file mode 100644 index 00000000..010444e8 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background-pressed@4x.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background.9.png Binary files differnew file mode 100644 index 00000000..cef1bafa --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background.svg b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background.svg new file mode 100644 index 00000000..acecfcc9 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background.svg @@ -0,0 +1,358 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="210mm" + height="297mm" + viewBox="0 0 210 297" + version="1.1" + id="svg5" + inkscape:version="1.1-alpha (b0f32e08fc, 2021-03-07)" + sodipodi:docname="filedialogdelegate-background.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <metadata + id="metadata122"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + id="namedview7" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + objecttolerance="10.0" + gridtolerance="10.0" + guidetolerance="10.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:document-units="px" + showgrid="false" + inkscape:zoom="3.2715084" + inkscape:cx="24.912056" + inkscape:cy="54.867657" + inkscape:window-width="1848" + inkscape:window-height="1016" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="g1413" + showguides="true" + inkscape:guide-bbox="true" + inkscape:document-rotation="0"> + <sodipodi:guide + position="6.0854167,296.96676" + orientation="-1,0" + id="guide2310" + inkscape:label="" + inkscape:locked="true" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="3.363578,293.03125" + orientation="0,1" + id="guide2322" + inkscape:label="" + inkscape:locked="true" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="12.170833,297.68429" + orientation="-1,0" + id="guide2760" + inkscape:label="" + inkscape:locked="true" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="18.25625,297.91758" + orientation="-1,0" + id="guide949" + inkscape:label="" + inkscape:locked="true" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="24.341667,299.40187" + orientation="-1,0" + id="guide941" + inkscape:label="" + inkscape:locked="true" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="30.427083,299.08734" + orientation="-1,0" + id="guide1165" + inkscape:label="" + inkscape:locked="false" + inkscape:color="rgb(0,0,255)" /> + </sodipodi:namedview> + <defs + id="defs2" /> + <g + inkscape:label="[9] filedialogdelegate-background-highlighted" + inkscape:groupmode="layer" + id="g1413" + style="display:inline" + sodipodi:insensitive="true"> + <rect + style="fill:none;stroke-width:0.946642;stroke-linejoin:round" + id="rect1401" + width="6.0854168" + height="3.96875" + x="30.427084" + y="-6.9388939e-18" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:7.98967;stroke-linejoin:round" + id="rect1403" + width="0.26458332" + height="3.4395833" + x="30.427084" + y="0.26458332" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:10.1547;stroke-linejoin:round" + id="rect1405" + width="0.26458332" + height="5.5562401" + x="-0.26415125" + y="30.691679" + transform="matrix(3.6708238e-5,-1,1,1.4067378e-5,0,0)" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.21594;stroke-linejoin:round" + id="rect1407" + width="0.26458332" + height="0.26458287" + x="-3.9588995" + y="33.337505" + transform="matrix(1.7480114e-6,-1,0.99999996,2.9541491e-4,0,0)" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.21594;stroke-linejoin:round" + id="rect1409" + width="0.26458332" + height="0.26458287" + x="-2.1059568" + y="36.247913" + transform="matrix(1.7480114e-6,-1,0.99999996,2.9541491e-4,0,0)" /> + <rect + style="fill:#4fc1e9;fill-opacity:1;fill-rule:evenodd;stroke-width:0.263352" + id="rect1411" + width="5.5562501" + height="3.4395833" + x="30.691668" + y="0.26458332" /> + </g> + <g + inkscape:label="[9] filedialogdelegate-background-focused" + inkscape:groupmode="layer" + id="g939" + style="display:inline" + sodipodi:insensitive="true"> + <rect + style="fill:none;stroke-width:0.946642;stroke-linejoin:round" + id="rect927" + width="6.0854168" + height="3.96875" + x="18.25625" + y="-6.9388939e-18" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:7.98967;stroke-linejoin:round" + id="rect929" + width="0.26458332" + height="3.4395833" + x="18.25625" + y="0.26458332" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:10.1547;stroke-linejoin:round" + id="rect931" + width="0.26458332" + height="5.5562401" + x="-0.26432261" + y="18.520845" + transform="matrix(3.6708238e-5,-1,1,1.4067378e-5,0,0)" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.21594;stroke-linejoin:round" + id="rect933" + width="0.26458332" + height="0.26458287" + x="-3.9624968" + y="21.166674" + transform="matrix(1.7480114e-6,-1,0.99999996,2.9541491e-4,0,0)" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.21594;stroke-linejoin:round" + id="rect935" + width="0.26458332" + height="0.26458287" + x="-2.1095541" + y="24.077082" + transform="matrix(1.7480114e-6,-1,0.99999996,2.9541491e-4,0,0)" /> + <rect + style="fill:#e6e9ed;fill-opacity:1;fill-rule:evenodd;stroke-width:0.263352" + id="rect937" + width="5.5562501" + height="3.4395833" + x="18.520834" + y="0.26458332" /> + </g> + <g + inkscape:label="[9] filedialogdelegate-background-pressed" + inkscape:groupmode="layer" + id="g867" + style="display:inline" + sodipodi:insensitive="true"> + <rect + style="fill:none;stroke-width:0.946642;stroke-linejoin:round" + id="rect855" + width="6.0854168" + height="3.96875" + x="12.170834" + y="-6.9388939e-18" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:7.98967;stroke-linejoin:round" + id="rect857" + width="0.26458332" + height="3.4395833" + x="12.170834" + y="0.26458332" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:10.1547;stroke-linejoin:round" + id="rect859" + width="0.26458332" + height="5.5562401" + x="-0.26440823" + y="12.435429" + transform="matrix(3.6708238e-5,-1,1,1.4067378e-5,0,0)" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.21594;stroke-linejoin:round" + id="rect861" + width="0.26458332" + height="0.26458287" + x="-3.9642947" + y="15.081257" + transform="matrix(1.7480114e-6,-1,0.99999996,2.9541491e-4,0,0)" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.21594;stroke-linejoin:round" + id="rect863" + width="0.26458332" + height="0.26458287" + x="-2.111352" + y="17.991665" + transform="matrix(1.7480114e-6,-1,0.99999996,2.9541491e-4,0,0)" /> + <rect + style="fill:#e6e9ed;fill-opacity:1;fill-rule:evenodd;stroke-width:0.263352" + id="rect865" + width="5.5562501" + height="3.4395833" + x="12.435416" + y="0.26458332" /> + </g> + <g + inkscape:label="[9] filedialogdelegate-background-disabled" + inkscape:groupmode="layer" + id="g2435" + style="display:inline" + sodipodi:insensitive="true"> + <rect + style="fill:none;stroke-width:0.946642;stroke-linejoin:round" + id="rect2425" + width="6.0854168" + height="3.96875" + x="6.0854168" + y="-6.9388939e-18" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:7.98967;stroke-linejoin:round" + id="rect2427" + width="0.26458332" + height="3.4395833" + x="6.0854168" + y="0.26458332" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:10.1547;stroke-linejoin:round" + id="rect2429" + width="0.26458332" + height="5.5562401" + x="-0.264494" + y="6.3500099" + transform="matrix(3.6708238e-5,-1,1,1.4067378e-5,0,0)" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.21594;stroke-linejoin:round" + id="rect2431" + width="0.26458332" + height="0.26458287" + x="-3.9660921" + y="8.995841" + transform="matrix(1.7480114e-6,-1,0.99999996,2.9541491e-4,0,0)" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.21594;stroke-linejoin:round" + id="rect2433" + width="0.26458332" + height="0.26458287" + x="-2.1131494" + y="11.906255" + transform="matrix(1.7480114e-6,-1,0.99999996,2.9541491e-4,0,0)" /> + <rect + style="fill:#f5f7fa;fill-opacity:1;fill-rule:evenodd;stroke-width:0.263352" + id="rect124" + width="5.5562501" + height="3.4395833" + x="6.3499999" + y="0.26458332" /> + </g> + <g + inkscape:label="[9] filedialogdelegate-background" + inkscape:groupmode="layer" + id="layer1" + style="display:inline" + sodipodi:insensitive="true"> + <rect + style="fill:none;stroke-width:0.946642;stroke-linejoin:round" + id="rect846" + width="6.0854168" + height="3.96875" + x="0" + y="-6.9388939e-18" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:7.98967;stroke-linejoin:round" + id="rect1054" + width="0.26458332" + height="3.4395833" + x="0" + y="0.26458332" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:10.1547;stroke-linejoin:round" + id="rect1212" + width="0.26458332" + height="5.5562401" + x="-0.26457959" + y="0.26459303" + transform="matrix(3.6708238e-5,-1,1,1.4067378e-5,0,0)" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.21594;stroke-linejoin:round" + id="rect1505" + width="0.26458332" + height="0.26458287" + x="-3.9678898" + y="2.9104238" + transform="matrix(1.7480114e-6,-1,0.99999996,2.9541491e-4,0,0)" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.21594;stroke-linejoin:round" + id="rect1587" + width="0.26458332" + height="0.26458287" + x="-2.1149471" + y="5.8208375" + transform="matrix(1.7480114e-6,-1,0.99999996,2.9541491e-4,0,0)" /> + <rect + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.0578;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect3108" + width="5.5562501" + height="3.4395833" + x="0.26458332" + y="0.26458332" /> + </g> +</svg> diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background@2x.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background@2x.9.png Binary files differnew file mode 100644 index 00000000..5a136a0c --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background@2x.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background@3x.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background@3x.9.png Binary files differnew file mode 100644 index 00000000..f47a366b --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background@3x.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background@4x.9.png b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background@4x.9.png Binary files differnew file mode 100644 index 00000000..9ecb680f --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/imagine/filedialogdelegate-background@4x.9.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-round.png b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-round.png Binary files differnew file mode 100644 index 00000000..a4b7bc03 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-round.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-round.svg b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-round.svg new file mode 100644 index 00000000..fd053957 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-round.svg @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="16" + height="16" + viewBox="0 0 16 16" + version="1.1" + id="svg2" + inkscape:version="1.1-alpha (b0f32e08fc, 2021-03-07)" + sodipodi:docname="up-icon-round.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1848" + inkscape:window-height="1016" + id="namedview6" + showgrid="true" + inkscape:zoom="16" + inkscape:cx="-1.96875" + inkscape:cy="10.65625" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid1497" /> + </sodipodi:namedview> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="up-icon-round" /> + <g + id="g1413" + transform="translate(0.3592822,-0.0034197)"> + <path + style="fill:none;fill-opacity:1;stroke:#757575;stroke-width:2.23721;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 7.6407178,2.1165354 3e-7,11.7737686" + id="path20" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;fill-opacity:1;stroke:#757575;stroke-width:2.23721;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 7.6407178,2.1165354 2.6616436,7.0956099" + id="path950" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;fill-opacity:1;stroke:#757575;stroke-width:2.23721;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 7.6407178,2.1165354 12.619792,7.0956096" + id="path952" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="fill:none;stroke-width:5.33333;stroke-linejoin:round" + id="rect2897" + width="16" + height="16" + x="0" + y="0" /> +</svg> diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-round@2x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-round@2x.png Binary files differnew file mode 100644 index 00000000..25ee970a --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-round@2x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-round@3x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-round@3x.png Binary files differnew file mode 100644 index 00000000..486f2f46 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-round@3x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-round@4x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-round@4x.png Binary files differnew file mode 100644 index 00000000..58540253 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-round@4x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-square.png b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-square.png Binary files differnew file mode 100644 index 00000000..f7d4151a --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-square.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-square.svg b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-square.svg new file mode 100644 index 00000000..93fc45ba --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-square.svg @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="16" + height="16" + viewBox="0 0 16 16" + version="1.1" + id="svg2" + inkscape:version="1.1-alpha (b0f32e08fc, 2021-03-07)" + sodipodi:docname="up-icon-square.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1848" + inkscape:window-height="1016" + id="namedview6" + showgrid="true" + inkscape:zoom="11.313709" + inkscape:cx="-6.3197669" + inkscape:cy="28.416854" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid1497" /> + </sodipodi:namedview> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="up-icon-square" /> + <path + style="fill:#757575;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 8.000315,1 -6,6 L 3,8 8,3 13,8 14,7 Z" + id="path928" + sodipodi:nodetypes="ccccccc" /> + <rect + style="fill:none;stroke-width:5.33333;stroke-linejoin:round" + id="rect2897" + width="16" + height="16" + x="0" + y="0" /> + <rect + style="fill:#757575;fill-opacity:1;stroke:none;stroke-width:5.23075;stroke-linejoin:round" + id="rect4947" + width="1.5" + height="11" + x="7.2501574" + y="3" /> +</svg> diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-square@2x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-square@2x.png Binary files differnew file mode 100644 index 00000000..2e2a3d94 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-square@2x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-square@3x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-square@3x.png Binary files differnew file mode 100644 index 00000000..8965148c --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-square@3x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-square@4x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-square@4x.png Binary files differnew file mode 100644 index 00000000..0a56bbcc --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-square@4x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-thick-square.png b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-thick-square.png Binary files differnew file mode 100644 index 00000000..3f9f8762 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-thick-square.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-thick-square.svg b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-thick-square.svg new file mode 100644 index 00000000..dd20777b --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-thick-square.svg @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="16" + height="16" + viewBox="0 0 16 16" + version="1.1" + id="svg2" + inkscape:version="1.1-alpha (b0f32e08fc, 2021-03-07)" + sodipodi:docname="up-icon-thick-square.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1848" + inkscape:window-height="1016" + id="namedview6" + showgrid="true" + inkscape:zoom="22.627417" + inkscape:cx="2.3201941" + inkscape:cy="10.00998" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid1497" /> + </sodipodi:namedview> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="up-icon-thick-square" /> + <path + style="fill:#757575;fill-opacity:1;stroke:none;stroke-width:1.34164px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 2,8 6,-6 6,6 h -3 v 7 H 5 V 8 Z" + id="path1103" + sodipodi:nodetypes="cccccccc" /> + <rect + style="fill:none;stroke-width:4;stroke-linejoin:round" + id="rect1748" + width="16" + height="16" + x="0" + y="0" /> +</svg> diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-thick-square@2x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-thick-square@2x.png Binary files differnew file mode 100644 index 00000000..b4efe132 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-thick-square@2x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-thick-square@3x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-thick-square@3x.png Binary files differnew file mode 100644 index 00000000..2e8302b1 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-thick-square@3x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-thick-square@4x.png b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-thick-square@4x.png Binary files differnew file mode 100644 index 00000000..5cf05ab3 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/images/up-icon-thick-square@4x.png diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Fusion/FileDialog.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Fusion/FileDialog.qml new file mode 100644 index 00000000..59b15ee2 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Fusion/FileDialog.qml @@ -0,0 +1,196 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 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$ +** +****************************************************************************/ + +import Qt.labs.folderlistmodel +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.impl +import QtQuick.Controls.Fusion +import QtQuick.Controls.Fusion.impl +import QtQuick.Dialogs +import QtQuick.Dialogs.quickimpl +import QtQuick.Layouts +import QtQuick.Templates as T + +import "." as DialogsImpl + +FileDialogImpl { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + contentWidth + leftPadding + rightPadding, + implicitHeaderWidth, + implicitFooterWidth) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + contentHeight + topPadding + bottomPadding + + (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0) + + (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0)) + + padding: 6 + horizontalPadding: 12 + + standardButtons: T.Dialog.Open | T.Dialog.Cancel + + /* + We use attached properties because we want to handle logic in C++, and: + - We can't assume the footer only contains a DialogButtonBox (which would allow us + to connect up to it in QQuickFileDialogImpl); it also needs to hold a ComboBox + and therefore the root footer item will be e.g. a layout item instead. + - We don't want to create our own "FileDialogButtonBox" (in order to be able to handle the logic + in C++) because we'd need to copy (and hence duplicate code in) DialogButtonBox.qml. + */ + FileDialogImpl.buttonBox: buttonBox + FileDialogImpl.nameFiltersComboBox: nameFiltersComboBox + FileDialogImpl.fileDialogListView: fileDialogListView + FileDialogImpl.breadcrumbBar: breadcrumbBar + + background: Rectangle { + implicitWidth: 600 + implicitHeight: 400 + color: control.palette.window + border.color: control.palette.mid + radius: 2 + + Rectangle { + z: -1 + x: 1 + y: 1 + width: parent.width + height: parent.height + color: control.palette.shadow + opacity: 0.2 + radius: 2 + } + } + + header: ColumnLayout { + spacing: 0 + + Label { + objectName: "dialogTitleBarLabel" + text: control.title + horizontalAlignment: Label.AlignHCenter + elide: Label.ElideRight + font.bold: true + padding: 6 + + Layout.fillWidth: true + Layout.leftMargin: 12 + Layout.rightMargin: 12 + Layout.topMargin: control.title.length > 0 ? 0 : 12 + Layout.preferredHeight: control.title.length > 0 ? implicitHeight : 0 + } + + DialogsImpl.FolderBreadcrumbBar { + id: breadcrumbBar + fileDialog: control + + Layout.fillWidth: true + Layout.leftMargin: 12 + Layout.rightMargin: 12 + + KeyNavigation.tab: fileDialogListView + } + } + + contentItem: Frame { + padding: 0 + verticalPadding: 1 + + ListView { + id: fileDialogListView + objectName: "fileDialogListView" + anchors.fill: parent + clip: true + focus: true + boundsBehavior: Flickable.StopAtBounds + + ScrollBar.vertical: ScrollBar {} + + model: FolderListModel { + folder: control.currentFolder + nameFilters: control.selectedNameFilter.globs + showDirsFirst: true + } + delegate: DialogsImpl.FileDialogDelegate { + objectName: "fileDialogDelegate" + index + x: 1 + width: ListView.view.width - 2 + highlighted: ListView.isCurrentItem + fileDialog: control + fileDetailRowWidth: nameFiltersComboBox.width + + KeyNavigation.backtab: breadcrumbBar + KeyNavigation.tab: nameFiltersComboBox + } + } + } + + footer: RowLayout { + id: rowLayout + spacing: 12 + + ComboBox { + // OK to use IDs here, since users shouldn't be overriding this stuff. + id: nameFiltersComboBox + model: control.nameFilters + + Layout.leftMargin: 12 + Layout.fillWidth: true + Layout.bottomMargin: 12 + } + + DialogButtonBox { + id: buttonBox + standardButtons: control.standardButtons + spacing: 6 + horizontalPadding: 0 + verticalPadding: 0 + background: null + + Layout.rightMargin: 12 + Layout.bottomMargin: 12 + } + } + + T.Overlay.modal: Rectangle { + color: Fusion.topShadow + } + + T.Overlay.modeless: Rectangle { + color: Fusion.topShadow + } +} diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Fusion/FileDialogDelegate.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Fusion/FileDialogDelegate.qml new file mode 100644 index 00000000..d4fd46b2 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Fusion/FileDialogDelegate.qml @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 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$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.impl as ControlsImpl +import QtQuick.Controls.Fusion +import QtQuick.Controls.Fusion.impl +import QtQuick.Dialogs.quickimpl as DialogsQuickImpl + +DialogsQuickImpl.FileDialogDelegate { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding, + implicitIndicatorHeight + topPadding + bottomPadding) + + padding: 6 + spacing: 6 + + file: fileUrl + + icon.width: 16 + icon.height: 16 + icon.color: highlighted ? palette.highlightedText : palette.text + icon.source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/" + + (fileIsDir ? "folder" : "file") + "-icon-round.png" + + // We don't use index here, but in C++. Since we're using required + // properties, the index context property will not be injected, so we can't + // use its QQmlContext to access it. + required property int index + required property string fileName + required property url fileUrl + required property int fileSize + required property date fileModified + required property bool fileIsDir + + required property int fileDetailRowWidth + + contentItem: FileDialogDelegateLabel { + delegate: control + fileDetailRowTextColor: control.highlighted ? Fusion.highlightedText(control.palette) : control.palette.text + fileDetailRowWidth: control.fileDetailRowWidth + } + + background: Rectangle { + implicitWidth: 100 + implicitHeight: 20 + color: control.down ? Fusion.buttonColor(control.palette, false, true, true) + : control.highlighted ? Fusion.highlight(control.palette) : control.palette.base + } +} diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Fusion/FolderBreadcrumbBar.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Fusion/FolderBreadcrumbBar.qml new file mode 100644 index 00000000..cafb975b --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Fusion/FolderBreadcrumbBar.qml @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 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$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.impl +import QtQuick.Dialogs.quickimpl as DialogsQuickImpl + +DialogsQuickImpl.FolderBreadcrumbBar { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + (upButton ? upButton.implicitWidth + upButtonSpacing : 0) + + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + upButtonSpacing: 6 + + contentItem: ListView { + id: listView + currentIndex: control.currentIndex + model: control.contentModel + orientation: ListView.Horizontal + snapMode: ListView.SnapToItem + highlightMoveDuration: 0 + interactive: false + clip: true + + Rectangle { + anchors.fill: parent + color: control.palette.light + border.color: control.palette.mid + radius: 2 + z: -1 + } + } + buttonDelegate: Button { + id: buttonDelegateRoot + text: folderName + flat: true + + // The default of 100 is a bit too wide for short directory names. + Binding { + target: buttonDelegateRoot.background + property: "implicitWidth" + value: 24 + } + + required property int index + required property string folderName + } + separatorDelegate: IconImage { + id: iconImage + source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/crumb-separator-icon-round.png" + sourceSize: Qt.size(8, 8) + width: 8 + 6 + height: control.contentItem.height + color: control.palette.dark + y: (control.height - height) / 2 + } + upButton: Button { + x: control.leftPadding + y: control.topPadding + icon.source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/up-icon-round.png" + icon.width: 16 + icon.height: 16 + width: height + height: Math.max(implicitHeight, control.contentItem.height) + focusPolicy: Qt.TabFocus + } + textField: TextField { + text: control.fileDialog.selectedFile + } +} diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Imagine/FileDialog.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Imagine/FileDialog.qml new file mode 100644 index 00000000..3afe1e7e --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Imagine/FileDialog.qml @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 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$ +** +****************************************************************************/ + +import Qt.labs.folderlistmodel +import QtQuick +import QtQuick.Templates as T +import QtQuick.Controls.Imagine +import QtQuick.Controls.Imagine.impl +import QtQuick.Dialogs.quickimpl +import QtQuick.Layouts + +import "." as DialogsImpl + +FileDialogImpl { + id: control + + // Can't set implicitWidth of the NinePatchImage background, so we do it here. + implicitWidth: Math.max(600, + implicitBackgroundWidth + leftInset + rightInset, + contentWidth + leftPadding + rightPadding, + implicitHeaderWidth, + implicitFooterWidth) + implicitHeight: Math.max(400, + implicitBackgroundHeight + topInset + bottomInset, + contentHeight + topPadding + bottomPadding + + (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0) + + (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0)) + + topPadding: background ? background.topPadding : 0 + leftPadding: background ? background.leftPadding : 0 + rightPadding: background ? background.rightPadding : 0 + bottomPadding: background ? background.bottomPadding : 0 + + topInset: background ? -background.topInset || 0 : 0 + leftInset: background ? -background.leftInset || 0 : 0 + rightInset: background ? -background.rightInset || 0 : 0 + bottomInset: background ? -background.bottomInset || 0 : 0 + + standardButtons: T.Dialog.Open | T.Dialog.Cancel + + FileDialogImpl.buttonBox: buttonBox + FileDialogImpl.nameFiltersComboBox: nameFiltersComboBox + FileDialogImpl.fileDialogListView: fileDialogListView + FileDialogImpl.breadcrumbBar: breadcrumbBar + + background: NinePatchImage { + source: Imagine.url + "dialog-background" + NinePatchImageSelector on source { + states: [ + {"modal": control.modal}, + {"dim": control.dim} + ] + } + } + + header: ColumnLayout { + spacing: 12 + + Label { + text: control.title + elide: Label.ElideRight + font.bold: true + + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.topMargin: 12 + Layout.fillWidth: true + Layout.preferredHeight: control.title.length > 0 ? implicitHeight : 0 + + background: NinePatchImage { + width: parent.width + height: parent.height + + source: Imagine.url + "dialog-title" + NinePatchImageSelector on source { + states: [ + {"modal": control.modal}, + {"dim": control.dim} + ] + } + } + } + + DialogsImpl.FolderBreadcrumbBar { + id: breadcrumbBar + fileDialog: control + + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.fillWidth: true + Layout.maximumWidth: parent.width - 28 + } + } + + contentItem: ListView { + id: fileDialogListView + objectName: "fileDialogListView" + clip: true + boundsBehavior: Flickable.StopAtBounds + + ScrollBar.vertical: ScrollBar {} + + model: FolderListModel { + folder: control.currentFolder + nameFilters: control.selectedNameFilter.globs + showDirsFirst: true + } + delegate: DialogsImpl.FileDialogDelegate { + objectName: "fileDialogDelegate" + index + width: ListView.view.width + highlighted: ListView.isCurrentItem + fileDialog: control + fileDetailRowWidth: nameFiltersComboBox.width + } + } + + footer: RowLayout { + id: rowLayout + spacing: 20 + + ComboBox { + id: nameFiltersComboBox + model: control.nameFilters + + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + Layout.fillWidth: true + } + + DialogButtonBox { + id: buttonBox + standardButtons: control.standardButtons + spacing: 12 + + Layout.bottomMargin: 16 + Layout.rightMargin: 16 + } + } + + T.Overlay.modal: NinePatchImage { + source: Imagine.url + "dialog-overlay" + NinePatchImageSelector on source { + states: [ + {"modal": true} + ] + } + } + + T.Overlay.modeless: NinePatchImage { + source: Imagine.url + "dialog-overlay" + NinePatchImageSelector on source { + states: [ + {"modal": false} + ] + } + } +} diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Imagine/FileDialogDelegate.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Imagine/FileDialogDelegate.qml new file mode 100644 index 00000000..1db2ab4e --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Imagine/FileDialogDelegate.qml @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 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$ +** +****************************************************************************/ + +import QtQuick.Controls +import QtQuick.Controls.Imagine +import QtQuick.Controls.Imagine.impl +import QtQuick.Controls.impl as ControlsImpl +import QtQuick.Dialogs.quickimpl as DialogsQuickImpl + +DialogsQuickImpl.FileDialogDelegate { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding, + implicitIndicatorHeight + topPadding + bottomPadding) + + spacing: 12 + + topPadding: background ? background.topPadding : 0 + leftPadding: background ? background.leftPadding : 0 + rightPadding: background ? background.rightPadding : 0 + bottomPadding: background ? background.bottomPadding : 0 + + topInset: background ? -background.topInset || 0 : 0 + leftInset: background ? -background.leftInset || 0 : 0 + rightInset: background ? -background.rightInset || 0 : 0 + bottomInset: background ? -background.bottomInset || 0 : 0 + + file: fileUrl + + icon.width: 16 + icon.height: 16 + icon.color: highlighted ? palette.highlightedText : palette.text + icon.source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/" + + (fileIsDir ? "folder" : "file") + "-icon-round.png" + + required property int index + required property string fileName + required property url fileUrl + required property int fileSize + required property date fileModified + required property bool fileIsDir + + required property int fileDetailRowWidth + + contentItem: FileDialogDelegateLabel { + delegate: control + fileDetailRowTextColor: control.icon.color + fileDetailRowWidth: control.fileDetailRowWidth + } + + background: NinePatchImage { + source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/imagine/filedialogdelegate-background" + NinePatchImageSelector on source { + states: [ + { "disabled": !control.enabled }, + { "pressed": control.down }, + { "focused": control.visualFocus }, + { "highlighted": control.highlighted }, + { "mirrored": control.mirrored }, + { "hovered": control.hovered } + ] + } + } +} diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Imagine/FolderBreadcrumbBar.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Imagine/FolderBreadcrumbBar.qml new file mode 100644 index 00000000..760d41c8 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Imagine/FolderBreadcrumbBar.qml @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 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$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.impl +import QtQuick.Dialogs.quickimpl as DialogsQuickImpl + +DialogsQuickImpl.FolderBreadcrumbBar { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + (upButton ? upButton.implicitWidth + upButtonSpacing : 0) + + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + upButtonSpacing: 20 + padding: 1 + + background: Rectangle {} + contentItem: ListView { + id: listView + currentIndex: control.currentIndex + model: control.contentModel + orientation: ListView.Horizontal + snapMode: ListView.SnapToItem + highlightMoveDuration: 0 + interactive: false + clip: true + } + buttonDelegate: Button { + id: buttonDelegateRoot + text: folderName + flat: true + + required property int index + required property string folderName + } + separatorDelegate: IconImage { + id: iconImage + source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/crumb-separator-icon-round.png" + sourceSize: Qt.size(8, 8) + width: 8 + height: control.contentItem.height + y: (control.height - height) / 2 + } + upButton: ToolButton { + x: control.leftPadding + y: control.topPadding + icon.source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/up-icon-thick-square.png" + icon.width: 16 + icon.height: 16 + focusPolicy: Qt.TabFocus + } + textField: TextField { + text: control.fileDialog.selectedFile + } +} diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Material/FileDialog.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Material/FileDialog.qml new file mode 100644 index 00000000..27599d77 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Material/FileDialog.qml @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 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$ +** +****************************************************************************/ + +import Qt.labs.folderlistmodel +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.impl +import QtQuick.Controls.Material +import QtQuick.Controls.Material.impl +import QtQuick.Dialogs +import QtQuick.Dialogs.quickimpl +import QtQuick.Layouts +import QtQuick.Templates as T + +import "." as DialogsImpl + +FileDialogImpl { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + contentWidth + leftPadding + rightPadding, + implicitFooterWidth) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + contentHeight + topPadding + bottomPadding + + (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0) + + (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0)) + + leftPadding: 24 + rightPadding: 24 + + standardButtons: T.Dialog.Open | T.Dialog.Cancel + + Material.elevation: 24 + + FileDialogImpl.buttonBox: buttonBox + FileDialogImpl.nameFiltersComboBox: nameFiltersComboBox + FileDialogImpl.fileDialogListView: fileDialogListView + FileDialogImpl.breadcrumbBar: breadcrumbBar + + background: Rectangle { + implicitWidth: 600 + implicitHeight: 400 + radius: 2 + color: control.Material.dialogColor + + layer.enabled: control.Material.elevation > 0 + layer.effect: ElevationEffect { + elevation: control.Material.elevation + } + } + + header: ColumnLayout { + spacing: 12 + + Label { + text: control.title + visible: control.title.length > 0 + elide: Label.ElideRight + font.bold: true + font.pixelSize: 16 + + Layout.leftMargin: 24 + Layout.rightMargin: 24 + Layout.topMargin: 24 + Layout.fillWidth: true + } + + DialogsImpl.FolderBreadcrumbBar { + id: breadcrumbBar + fileDialog: control + + Layout.leftMargin: 24 + Layout.rightMargin: 24 + Layout.fillWidth: true + Layout.maximumWidth: parent.width - 48 + } + } + + contentItem: ListView { + id: fileDialogListView + objectName: "fileDialogListView" + clip: true + + ScrollBar.vertical: ScrollBar {} + + model: FolderListModel { + folder: control.currentFolder + nameFilters: control.selectedNameFilter.globs + showDirsFirst: true + } + delegate: DialogsImpl.FileDialogDelegate { + objectName: "fileDialogDelegate" + index + width: ListView.view.width + highlighted: ListView.isCurrentItem + fileDialog: control + fileDetailRowWidth: nameFiltersComboBox.width + } + } + + footer: RowLayout { + id: rowLayout + spacing: 20 + + ComboBox { + id: nameFiltersComboBox + model: control.nameFilters + + Layout.leftMargin: 20 + Layout.fillWidth: true + } + + DialogButtonBox { + id: buttonBox + standardButtons: control.standardButtons + spacing: 12 + horizontalPadding: 0 + verticalPadding: 20 + + Layout.rightMargin: 20 + } + } + + Overlay.modal: Rectangle { + color: Color.transparent(control.palette.shadow, 0.5) + } + + Overlay.modeless: Rectangle { + color: Color.transparent(control.palette.shadow, 0.12) + } +} diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Material/FileDialogDelegate.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Material/FileDialogDelegate.qml new file mode 100644 index 00000000..747165bf --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Material/FileDialogDelegate.qml @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 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$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls.impl +import QtQuick.Controls.Material +import QtQuick.Controls.Material.impl +import QtQuick.Dialogs.quickimpl as DialogsQuickImpl + +DialogsQuickImpl.FileDialogDelegate { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding, + implicitIndicatorHeight + topPadding + bottomPadding) + + padding: 16 + verticalPadding: 8 + spacing: 16 + + icon.width: 16 + icon.height: 16 + icon.color: enabled ? Material.foreground : Material.hintTextColor + icon.source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/" + + (fileIsDir ? "folder" : "file") + "-icon-square.png" + + file: fileUrl + + required property int index + required property string fileName + required property url fileUrl + required property int fileSize + required property date fileModified + required property bool fileIsDir + + required property int fileDetailRowWidth + + contentItem: FileDialogDelegateLabel { + delegate: control + fileDetailRowTextColor: control.Material.hintTextColor + fileDetailRowWidth: control.fileDetailRowWidth + } + + background: Rectangle { + implicitHeight: control.Material.delegateHeight + + color: control.highlighted ? Color.transparent(control.Material.accentColor, 0.08) : "transparent" + + Ripple { + width: parent.width + height: parent.height + + clip: visible + pressed: control.pressed + anchor: control + active: control.down || control.visualFocus || control.hovered + color: control.highlighted ? control.Material.highlightedRippleColor : control.Material.rippleColor + } + } +} diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Material/FolderBreadcrumbBar.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Material/FolderBreadcrumbBar.qml new file mode 100644 index 00000000..a686f39e --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Material/FolderBreadcrumbBar.qml @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 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$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.impl +import QtQuick.Controls.Material +import QtQuick.Dialogs.quickimpl as DialogsQuickImpl + +DialogsQuickImpl.FolderBreadcrumbBar { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + (upButton ? upButton.implicitWidth + upButtonSpacing : 0) + + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + upButtonSpacing: 20 + padding: 1 + + background: Rectangle {} + contentItem: ListView { + id: listView + currentIndex: control.currentIndex + model: control.contentModel + orientation: ListView.Horizontal + snapMode: ListView.SnapToItem + highlightMoveDuration: 0 + interactive: false + clip: true + } + buttonDelegate: Button { + id: buttonDelegateRoot + text: folderName + flat: true + font.capitalization: Font.MixedCase + + // The default of 100 is a bit too wide for short directory names. + Binding { + target: buttonDelegateRoot.background + property: "implicitWidth" + value: control.Material.buttonHeight + } + + required property int index + required property string folderName + } + separatorDelegate: IconImage { + id: iconImage + source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/crumb-separator-icon-square.png" + sourceSize: Qt.size(8, 8) + // The image is 8x8, and add 2 px padding on each side. + width: 8 + 4 + height: control.contentItem.height + color: control.Material.hintTextColor + y: (control.height - height) / 2 + } + upButton: ToolButton { + x: control.leftPadding + y: control.topPadding + icon.source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/up-icon-thick-square.png" + icon.width: 16 + icon.height: 16 + width: height + focusPolicy: Qt.TabFocus + } + textField: TextField { + text: control.fileDialog.selectedFile + } +} diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Universal/FileDialog.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Universal/FileDialog.qml new file mode 100644 index 00000000..7698089e --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Universal/FileDialog.qml @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 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$ +** +****************************************************************************/ + +import Qt.labs.folderlistmodel +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Universal +import QtQuick.Dialogs +import QtQuick.Dialogs.quickimpl +import QtQuick.Layouts +import QtQuick.Templates as T + +import "." as DialogsImpl + +FileDialogImpl { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + contentWidth + leftPadding + rightPadding, + implicitHeaderWidth, + implicitFooterWidth) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + contentHeight + topPadding + bottomPadding + + (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0) + + (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0)) + + padding: 24 + verticalPadding: 18 + + standardButtons: T.Dialog.Open | T.Dialog.Cancel + + FileDialogImpl.buttonBox: buttonBox + FileDialogImpl.nameFiltersComboBox: nameFiltersComboBox + FileDialogImpl.fileDialogListView: fileDialogListView + FileDialogImpl.breadcrumbBar: breadcrumbBar + + background: Rectangle { + implicitWidth: 600 + implicitHeight: 400 + color: control.Universal.chromeMediumLowColor + border.color: control.Universal.chromeHighColor + border.width: 1 // FlyoutBorderThemeThickness + } + + header: ColumnLayout { + spacing: 12 + + Label { + text: control.title + elide: Label.ElideRight + // TODO: QPlatformTheme::TitleBarFont + font.pixelSize: 20 + + Layout.leftMargin: 24 + Layout.rightMargin: 24 + Layout.topMargin: 18 + Layout.fillWidth: true + Layout.preferredHeight: control.title.length > 0 ? implicitHeight : 0 + + background: Rectangle { + x: 1; y: 1 // // FlyoutBorderThemeThickness + color: control.Universal.chromeMediumLowColor + width: parent.width - 2 + height: parent.height - 1 + } + } + + DialogsImpl.FolderBreadcrumbBar { + id: breadcrumbBar + fileDialog: control + + Layout.leftMargin: 24 + Layout.rightMargin: 24 + Layout.fillWidth: true + Layout.maximumWidth: parent.width - 48 + } + } + + contentItem: ListView { + id: fileDialogListView + objectName: "fileDialogListView" + clip: true + boundsBehavior: Flickable.StopAtBounds + + ScrollBar.vertical: ScrollBar {} + + model: FolderListModel { + folder: control.currentFolder + nameFilters: control.selectedNameFilter.globs + showDirsFirst: true + } + delegate: DialogsImpl.FileDialogDelegate { + objectName: "fileDialogDelegate" + index + width: ListView.view.width + highlighted: ListView.isCurrentItem + fileDialog: control + fileDetailRowWidth: nameFiltersComboBox.width + } + } + + footer: RowLayout { + id: rowLayout + spacing: 24 + + ComboBox { + id: nameFiltersComboBox + model: control.nameFilters + + Layout.leftMargin: 24 + Layout.fillWidth: true + Layout.topMargin: 6 + Layout.bottomMargin: 24 + } + + DialogButtonBox { + id: buttonBox + standardButtons: control.standardButtons + spacing: 12 + horizontalPadding: 0 + + Layout.rightMargin: 24 + } + } + + T.Overlay.modal: Rectangle { + color: control.Universal.baseLowColor + } + + T.Overlay.modeless: Rectangle { + color: control.Universal.baseLowColor + } +} diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Universal/FileDialogDelegate.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Universal/FileDialogDelegate.qml new file mode 100644 index 00000000..6f8bd441 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Universal/FileDialogDelegate.qml @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 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$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls.impl +import QtQuick.Controls.Universal +import QtQuick.Dialogs.quickimpl as DialogsQuickImpl + +DialogsQuickImpl.FileDialogDelegate { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding, + implicitIndicatorHeight + topPadding + bottomPadding) + + spacing: 12 + + padding: 12 + topPadding: padding - 1 + bottomPadding: padding + 1 + + icon.width: 20 + icon.height: 20 + icon.color: Color.transparent(Universal.foreground, enabled ? 1.0 : 0.2) + icon.source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/" + + (fileIsDir ? "folder" : "file") + "-icon-square.png" + + file: fileUrl + + required property int index + required property string fileName + required property url fileUrl + required property int fileSize + required property date fileModified + required property bool fileIsDir + + required property int fileDetailRowWidth + + contentItem: FileDialogDelegateLabel { + delegate: control + fileDetailRowTextColor: control.icon.color + fileDetailRowWidth: control.fileDetailRowWidth + } + + background: Rectangle { + visible: control.down || control.highlighted || control.visualFocus || control.hovered + color: control.down ? control.Universal.listMediumColor : + control.hovered ? control.Universal.listLowColor : control.Universal.altMediumLowColor + + Rectangle { + width: parent.width + height: parent.height + visible: control.visualFocus || control.highlighted + color: control.Universal.accent + opacity: control.Universal.theme === Universal.Light ? 0.4 : 0.6 + } + } +} diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Universal/FolderBreadcrumbBar.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Universal/FolderBreadcrumbBar.qml new file mode 100644 index 00000000..1ea32f61 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Universal/FolderBreadcrumbBar.qml @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 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$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.impl +import QtQuick.Controls.Universal +import QtQuick.Dialogs.quickimpl as DialogsQuickImpl + +DialogsQuickImpl.FolderBreadcrumbBar { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + (upButton ? upButton.implicitWidth + upButtonSpacing : 0) + + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + upButtonSpacing: 20 + padding: 1 + + background: Rectangle {} + contentItem: ListView { + id: listView + currentIndex: control.currentIndex + model: control.contentModel + orientation: ListView.Horizontal + snapMode: ListView.SnapToItem + highlightMoveDuration: 0 + interactive: false + clip: true + } + buttonDelegate: ToolButton { + id: buttonDelegateRoot + text: folderName + + // The default is a bit too wide for short directory names. + Binding { + target: buttonDelegateRoot.background + property: "implicitWidth" + value: 48 + } + + required property int index + required property string folderName + } + separatorDelegate: IconImage { + id: iconImage + source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/crumb-separator-icon-square.png" + sourceSize: Qt.size(8, 8) + // The image is 8x8, and add 2 px padding on each side. + width: 8 + 4 + height: control.contentItem.height + color: Color.transparent(control.Universal.foreground, enabled ? 1.0 : 0.2) + y: (control.height - height) / 2 + } + upButton: ToolButton { + x: control.leftPadding + y: control.topPadding + icon.source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/up-icon-square.png" + icon.width: 16 + icon.height: 16 + width: height + focusPolicy: Qt.TabFocus + } + textField: TextField { + text: control.fileDialog.selectedFile + } +} diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/FileDialog.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/FileDialog.qml new file mode 100644 index 00000000..946a55fa --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/FileDialog.qml @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 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$ +** +****************************************************************************/ + +import Qt.labs.folderlistmodel +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.impl +import QtQuick.Dialogs +import QtQuick.Dialogs.quickimpl +import QtQuick.Layouts +import QtQuick.Templates as T + +import "." as DialogsImpl + +FileDialogImpl { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + contentWidth + leftPadding + rightPadding, + implicitHeaderWidth, + implicitFooterWidth) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + contentHeight + topPadding + bottomPadding + + (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0) + + (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0)) + + leftPadding: 20 + rightPadding: 20 + // Ensure that the background's border is visible. + leftInset: -1 + rightInset: -1 + topInset: -1 + bottomInset: -1 + + standardButtons: T.Dialog.Open | T.Dialog.Cancel + + /* + We use attached properties because we want to handle logic in C++, and: + - We can't assume the footer only contains a DialogButtonBox (which would allow us + to connect up to it in QQuickFileDialogImpl); it also needs to hold a ComboBox + and therefore the root footer item will be e.g. a layout item instead. + - We don't want to create our own "FileDialogButtonBox" (in order to be able to handle the logic + in C++) because we'd need to copy (and hence duplicate code in) DialogButtonBox.qml. + */ + FileDialogImpl.buttonBox: buttonBox + FileDialogImpl.nameFiltersComboBox: nameFiltersComboBox + FileDialogImpl.fileDialogListView: fileDialogListView + FileDialogImpl.breadcrumbBar: breadcrumbBar + + background: Rectangle { + implicitWidth: 600 + implicitHeight: 400 + color: control.palette.window + border.color: control.palette.dark + } + + header: Pane { + palette.window: control.palette.light + padding: 20 + + contentItem: Column { + spacing: 12 + + Label { + objectName: "dialogTitleBarLabel" + width: parent.width + text: control.title + visible: control.title.length > 0 + horizontalAlignment: Label.AlignHCenter + elide: Label.ElideRight + font.bold: true + } + + DialogsImpl.FolderBreadcrumbBar { + id: breadcrumbBar + width: parent.width + fileDialog: control + + KeyNavigation.tab: fileDialogListView + } + } + } + + contentItem: ListView { + id: fileDialogListView + objectName: "fileDialogListView" + clip: true + focus: true + boundsBehavior: Flickable.StopAtBounds + + ScrollBar.vertical: ScrollBar {} + + model: FolderListModel { + folder: control.currentFolder + nameFilters: control.selectedNameFilter.globs + showDirsFirst: true + } + delegate: DialogsImpl.FileDialogDelegate { + objectName: "fileDialogDelegate" + index + width: ListView.view.width + highlighted: ListView.isCurrentItem + fileDialog: control + fileDetailRowWidth: nameFiltersComboBox.width + + KeyNavigation.backtab: breadcrumbBar + KeyNavigation.tab: nameFiltersComboBox + } + } + + footer: Rectangle { + color: control.palette.light + implicitWidth: rowLayout.implicitWidth + implicitHeight: rowLayout.implicitHeight + + RowLayout { + id: rowLayout + width: parent.width + height: parent.height + spacing: 20 + + ComboBox { + // OK to use IDs here, since users shouldn't be overriding this stuff. + id: nameFiltersComboBox + model: control.nameFilters + + Layout.leftMargin: 20 + Layout.fillWidth: true + } + + DialogButtonBox { + id: buttonBox + standardButtons: control.standardButtons + palette.window: control.palette.light + spacing: 12 + horizontalPadding: 0 + verticalPadding: 20 + + Layout.rightMargin: 20 + } + } + } + + Overlay.modal: Rectangle { + color: Color.transparent(control.palette.shadow, 0.5) + } + + Overlay.modeless: Rectangle { + color: Color.transparent(control.palette.shadow, 0.12) + } +} diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/FileDialogDelegate.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/FileDialogDelegate.qml new file mode 100644 index 00000000..5692d132 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/FileDialogDelegate.qml @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 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$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.impl as ControlsImpl +import QtQuick.Dialogs.quickimpl as DialogsQuickImpl + +DialogsQuickImpl.FileDialogDelegate { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding, + implicitIndicatorHeight + topPadding + bottomPadding) + + padding: 12 + spacing: 8 + topPadding: 0 + bottomPadding: 0 + + file: fileUrl + + icon.width: 16 + icon.height: 16 + icon.color: highlighted ? palette.highlightedText : palette.text + icon.source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/" + + (fileIsDir ? "folder" : "file") + "-icon-round.png" + + // We don't use index here, but in C++. Since we're using required + // properties, the index context property will not be injected, so we can't + // use its QQmlContext to access it. + required property int index + required property string fileName + required property url fileUrl + required property int fileSize + required property date fileModified + required property bool fileIsDir + + required property int fileDetailRowWidth + + contentItem: FileDialogDelegateLabel { + delegate: control + fileDetailRowTextColor: control.icon.color + fileDetailRowWidth: control.fileDetailRowWidth + } + + background: Rectangle { + implicitWidth: 100 + implicitHeight: 40 + visible: control.down || control.highlighted || control.visualFocus + color: Color.blend(control.down ? control.palette.midlight : control.palette.light, + control.palette.highlight, control.highlighted ? 0.15 : 0.0) + } +} diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/FileDialogDelegateLabel.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/FileDialogDelegateLabel.qml new file mode 100644 index 00000000..56b031d1 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/FileDialogDelegateLabel.qml @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 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$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.impl +import QtQuick.Dialogs.quickimpl as DialogsQuickImpl + +/* + Most of the elements in here are the same between styles, so we + have a reusable component for it and provide some properties to enable style-specific tweaks. +*/ +Item { + id: root + implicitWidth: column.implicitWidth + implicitHeight: column.implicitHeight + + required property DialogsQuickImpl.FileDialogDelegate delegate + required property int fileDetailRowWidth + + property color fileDetailRowTextColor + + Column { + id: column + y: (parent.height - height) / 2 + + Row { + spacing: root.delegate.spacing + + IconImage { + id: iconImage + source: root.delegate.icon.source + sourceSize: Qt.size(root.delegate.icon.width, root.delegate.icon.height) + width: root.delegate.icon.width + height: root.delegate.icon.height + color: root.delegate.icon.color + y: (parent.height - height) / 2 + } + Label { + text: root.delegate.fileName + color: root.delegate.icon.color + y: (parent.height - height) / 2 + } + } + + Item { + id: fileDetailRow + x: iconImage.width + root.delegate.spacing + width: fileDetailRowWidth - x - root.delegate.leftPadding + implicitHeight: childrenRect.height + + Label { + text: locale.formattedDataSize(root.delegate.fileSize) + font.pixelSize: root.delegate.font.pixelSize * 0.75 + color: root.fileDetailRowTextColor + } + Label { + text: Qt.formatDateTime(root.delegate.fileModified) + font.pixelSize: root.delegate.font.pixelSize * 0.75 + color: root.fileDetailRowTextColor + x: parent.width - width + } + } + } +} diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/FolderBreadcrumbBar.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/FolderBreadcrumbBar.qml new file mode 100644 index 00000000..25e11e71 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/FolderBreadcrumbBar.qml @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 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$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.impl +import QtQuick.Dialogs.quickimpl as DialogsQuickImpl + +DialogsQuickImpl.FolderBreadcrumbBar { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + (upButton ? upButton.implicitWidth + upButtonSpacing : 0) + + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + upButtonSpacing: 20 + padding: 1 + + background: Rectangle { + border.color: control.palette.button + } + contentItem: ListView { + id: listView + currentIndex: control.currentIndex + model: control.contentModel + orientation: ListView.Horizontal + snapMode: ListView.SnapToItem + highlightMoveDuration: 0 + interactive: false + clip: true + } + buttonDelegate: Button { + id: buttonDelegateRoot + text: folderName + flat: true + + // The default of 100 is a bit too wide for short directory names. + Binding { + target: buttonDelegateRoot.background + property: "implicitWidth" + value: 40 + } + + required property int index + required property string folderName + } + separatorDelegate: IconImage { + id: iconImage + source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/crumb-separator-icon-round.png" + sourceSize: Qt.size(8, 8) + width: 8 + height: control.contentItem.height + color: control.palette.button + y: (control.height - height) / 2 + } + upButton: ToolButton { + x: control.leftPadding + y: control.topPadding + icon.source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/up-icon-round.png" + icon.width: 16 + icon.height: 16 + width: height + focusPolicy: Qt.TabFocus + } + textField: TextField { + text: control.fileDialog.selectedFile + } +} diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qquickdialogimplfactory.cpp b/src/quickdialogs2/quickdialogs2quickimpl/qquickdialogimplfactory.cpp new file mode 100644 index 00000000..f56466e0 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qquickdialogimplfactory.cpp @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 "qquickdialogimplfactory_p.h" + +#include <QtCore/qloggingcategory.h> + +#include "qquickplatformfiledialog_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \internal + + Creates concrete QML-based dialogs. +*/ + +Q_LOGGING_CATEGORY(lcQuickDialogImplFactory, "qt.quick.dialogs.quickdialogimplfactory") + +QPlatformDialogHelper *QQuickDialogImplFactory::createPlatformDialogHelper( + QPlatformTheme::DialogType type, QObject *parent) +{ + switch (type) { + case QPlatformTheme::FileDialog: { + auto dialog = new QQuickPlatformFileDialog(parent); + // If the QML file failed to load, we need to handle it gracefully. + if (!dialog->isValid()) { + delete dialog; + return nullptr; + } + + return dialog; + } default: + break; + } + + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qquickdialogimplfactory_p.h b/src/quickdialogs2/quickdialogs2quickimpl/qquickdialogimplfactory_p.h new file mode 100644 index 00000000..a4ca1cca --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qquickdialogimplfactory_p.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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$ +** +****************************************************************************/ + +#ifndef QQUICKQMLDIALOGFACTORY_P_H +#define QQUICKQMLDIALOGFACTORY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qobject.h> +#include <QtGui/qpa/qplatformtheme.h> + +#include "qtquickdialogs2quickimplglobal_p.h" + +QT_BEGIN_NAMESPACE + +class Q_QUICKDIALOGS2QUICKIMPL_PRIVATE_EXPORT QQuickDialogImplFactory +{ +public: + static QPlatformDialogHelper *createPlatformDialogHelper(QPlatformTheme::DialogType type, + QObject *parent); +}; + +QT_END_NAMESPACE + +#endif // QQUICKQMLDIALOGFACTORY_P_H diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogdelegate.cpp b/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogdelegate.cpp new file mode 100644 index 00000000..8964772c --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogdelegate.cpp @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 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 "qquickfiledialogdelegate_p.h" + +#include <QtCore/qfileinfo.h> +#include <QtGui/qpa/qplatformtheme.h> +#include <QtQml/QQmlFile> +#include <QtQml/qqmlexpression.h> +#include <QtQuick/private/qquicklistview_p.h> +#include <QtQuickTemplates2/private/qquickitemdelegate_p_p.h> + +#include "qquickfiledialogimpl_p.h" + +QT_BEGIN_NAMESPACE + +class QQuickFileDialogDelegatePrivate : public QQuickItemDelegatePrivate +{ + Q_DECLARE_PUBLIC(QQuickFileDialogDelegate) + +public: + void highlightFile(); + void chooseFile(); + + bool acceptKeyClick(Qt::Key key) const override; + + QQuickFileDialogImpl *fileDialog = nullptr; + QUrl file; +}; + +void QQuickFileDialogDelegatePrivate::highlightFile() +{ + Q_Q(QQuickFileDialogDelegate); + QQuickListViewAttached *attached = static_cast<QQuickListViewAttached*>( + qmlAttachedPropertiesObject<QQuickListView>(q)); + if (!attached) + return; + + QQmlContext *delegateContext = qmlContext(q); + if (!delegateContext) + return; + + bool converted = false; + const int index = q->property("index").toInt(&converted); + if (converted) { + attached->view()->setCurrentIndex(index); + fileDialog->setCurrentFile(file); + } +} + +void QQuickFileDialogDelegatePrivate::chooseFile() +{ + const QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(file)); + if (fileInfo.isDir()) { + // If it's a directory, navigate to it. + fileDialog->setCurrentFolder(file); + } else { + // Otherwise it's a file, so select it and close the dialog. + fileDialog->setSelectedFile(file); + fileDialog->accept(); + } +} + +bool QQuickFileDialogDelegatePrivate::acceptKeyClick(Qt::Key key) const +{ + return key == Qt::Key_Return || key == Qt::Key_Enter; +} + +QQuickFileDialogDelegate::QQuickFileDialogDelegate(QQuickItem *parent) + : QQuickItemDelegate(*(new QQuickFileDialogDelegatePrivate), parent) +{ + Q_D(QQuickFileDialogDelegate); + // Clicking and tabbing should result in it getting focus, + // as e.g. Ubuntu and Windows both allow tabbing through file dialogs. + setFocusPolicy(Qt::StrongFocus); + setCheckable(true); + QObjectPrivate::connect(this, &QQuickFileDialogDelegate::clicked, + d, &QQuickFileDialogDelegatePrivate::highlightFile); + QObjectPrivate::connect(this, &QQuickFileDialogDelegate::doubleClicked, + d, &QQuickFileDialogDelegatePrivate::chooseFile); +} + +QQuickFileDialogImpl *QQuickFileDialogDelegate::fileDialog() const +{ + Q_D(const QQuickFileDialogDelegate); + return d->fileDialog; +} + +void QQuickFileDialogDelegate::setFileDialog(QQuickFileDialogImpl *fileDialog) +{ + Q_D(QQuickFileDialogDelegate); + if (fileDialog == d->fileDialog) + return; + + d->fileDialog = fileDialog; + emit fileDialogChanged(); +} + +QUrl QQuickFileDialogDelegate::file() const +{ + Q_D(const QQuickFileDialogDelegate); + return d->file; +} + +void QQuickFileDialogDelegate::setFile(const QUrl &file) +{ + Q_D(QQuickFileDialogDelegate); + if (file == d->file) + return; + + d->file = file; + emit fileChanged(); +} + +void QQuickFileDialogDelegate::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QQuickFileDialogDelegate); + // We need to respond to being triggered by enter being pressed, + // but we can't use event->isAccepted() to check, because events are pre-accepted. + auto connection = QObjectPrivate::connect(this, &QQuickFileDialogDelegate::clicked, + d, &QQuickFileDialogDelegatePrivate::chooseFile); + + QQuickItemDelegate::keyReleaseEvent(event); + + disconnect(connection); +} + +QT_END_NAMESPACE + +#include "moc_qquickfiledialogdelegate_p.cpp" diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogdelegate_p.h b/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogdelegate_p.h new file mode 100644 index 00000000..e1f6c169 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogdelegate_p.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 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$ +** +****************************************************************************/ + +#ifndef QQUICKFILEDIALOGDELEGATE_P_H +#define QQUICKFILEDIALOGDELEGATE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickitemdelegate_p.h> + +#include "qtquickdialogs2quickimplglobal_p.h" + +QT_BEGIN_NAMESPACE + +class QQuickFileDialogImpl; +class QQuickFileDialogDelegatePrivate; + +class Q_QUICKDIALOGS2QUICKIMPL_PRIVATE_EXPORT QQuickFileDialogDelegate : public QQuickItemDelegate +{ + Q_OBJECT + Q_PROPERTY(QQuickFileDialogImpl *fileDialog READ fileDialog WRITE setFileDialog NOTIFY fileDialogChanged) + Q_PROPERTY(QUrl file READ file WRITE setFile NOTIFY fileChanged) + QML_NAMED_ELEMENT(FileDialogDelegate) + QML_ADDED_IN_VERSION(6, 2) + +public: + explicit QQuickFileDialogDelegate(QQuickItem *parent = nullptr); + + QQuickFileDialogImpl *fileDialog() const; + void setFileDialog(QQuickFileDialogImpl *fileDialog); + + QUrl file() const; + void setFile(const QUrl &file); + +Q_SIGNALS: + void fileDialogChanged(); + void fileChanged(); + +protected: + void keyReleaseEvent(QKeyEvent *event) override; + +private: + Q_DISABLE_COPY(QQuickFileDialogDelegate) + Q_DECLARE_PRIVATE(QQuickFileDialogDelegate) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickFileDialogDelegate) + +#endif // QQUICKFILEDIALOGDELEGATE_P_H diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl.cpp b/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl.cpp new file mode 100644 index 00000000..e50305f6 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl.cpp @@ -0,0 +1,564 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 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 "qquickfiledialogimpl_p.h" +#include "qquickfiledialogimpl_p_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtQml/qqmlinfo.h> +#include <QtQml/qqmlfile.h> +#include <QtQuickDialogs2Utils/private/qquickfilenamefilter_p.h> +#include <QtQuickTemplates2/private/qquickdialogbuttonbox_p_p.h> +#include <QtQuickTemplates2/private/qquickpopupitem_p_p.h> +#include "qquickfiledialogdelegate_p.h" +#include "qquickfolderbreadcrumbbar_p.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcCurrentFolder, "qt.quick.dialogs.quickfiledialogimpl.currentFolder") +Q_LOGGING_CATEGORY(lcOptions, "qt.quick.dialogs.quickfiledialogimpl.options") +Q_LOGGING_CATEGORY(lcNameFilters, "qt.quick.dialogs.quickfiledialogimpl.namefilters") +Q_LOGGING_CATEGORY(lcAttachedNameFilters, "qt.quick.dialogs.quickfiledialogimplattached.namefilters") + +QQuickFileDialogImplPrivate::QQuickFileDialogImplPrivate() +{ +} + +void QQuickFileDialogImplPrivate::setNameFilters(const QStringList &filters) +{ + Q_Q(QQuickFileDialogImpl); + if (filters == nameFilters) + return; + + nameFilters = filters; + emit q->nameFiltersChanged(); +} + +void QQuickFileDialogImplPrivate::updateEnabled() +{ + Q_Q(QQuickFileDialogImpl); + QQuickFileDialogImplAttached *attached = attachedOrWarn(); + if (!attached) + return; + + auto openButton = attached->buttonBox()->standardButton(QPlatformDialogHelper::Open); + if (!openButton) { + qmlWarning(q).nospace() << "Can't update Open button's enabled state because it wasn't found"; + return; + } + + openButton->setEnabled(!currentFile.isEmpty() && attached->breadcrumbBar() + && !attached->breadcrumbBar()->textField()->isVisible()); +} + +/*! + \internal + + Ensures that a file is always selected after a change in \c folder. + + \a oldFolderPath is the previous value of \c folder. +*/ +void QQuickFileDialogImplPrivate::updateCurrentFile(const QString &oldFolderPath) +{ + Q_Q(QQuickFileDialogImpl); + QQuickFileDialogImplAttached *attached = attachedOrWarn(); + if (!attached || !attached->fileDialogListView()) + return; + + QString newCurrentFilePath; + int newCurrentFileIndex = 0; + const QString newFolderPath = QQmlFile::urlToLocalFileOrQrc(currentFolder); + if (!oldFolderPath.isEmpty() && !newFolderPath.isEmpty()) { + // If the user went up a directory (or several), we should set + // currentFile to be the directory that we were in (or + // its closest ancestor that is a child of the new directory). + // E.g. if oldFolderPath is /foo/bar/baz/abc/xyz, and newFolderPath is /foo/bar, + // then we want to set currentFile to be /foo/bar/baz. + const int indexOfFolder = oldFolderPath.indexOf(newFolderPath); + if (indexOfFolder != -1) { + // [folder] + // [ oldFolderPath ] + // /foo/bar/baz/abc/xyz + // [rel...Paths] + QStringList relativePaths = oldFolderPath.mid(indexOfFolder + newFolderPath.size()).split(QLatin1Char('/'), Qt::SkipEmptyParts); + newCurrentFilePath = newFolderPath + QLatin1Char('/') + relativePaths.first(); + + // Now find the index of that directory so that we can set the ListView's currentIndex to it. + const QDir newFolderDir(newFolderPath); + // Just to be safe... + if (!newFolderDir.exists()) { + qmlWarning(q) << "Directory" << newCurrentFilePath << "doesn't exist; can't get a file entry list for it"; + return; + } + + const QFileInfoList dirs = newFolderDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::DirsFirst); + const QFileInfo newCurrentFileInfo(newCurrentFilePath); + // The directory can contain files, but since we put dirs first, that should never affect the indices. + newCurrentFileIndex = dirs.indexOf(newCurrentFileInfo); + } + } + + if (newCurrentFilePath.isEmpty()) { + // When entering into a directory that isn't a parent of the old one, the first + // file delegate should be selected. + // TODO: is there a cheaper way to do this? QDirIterator doesn't support sorting, + // so we can't use that. QQuickFolderListModel uses threads to fetch its data, + // so should be considered asynchronous. We might be able to use it, but it would + // complicate the code even more... + QDir newFolderDir(newFolderPath); + if (newFolderDir.exists()) { + const QFileInfoList files = newFolderDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, QDir::DirsFirst); + if (!files.isEmpty()) + newCurrentFilePath = files.first().absoluteFilePath(); + } + } + + if (!newCurrentFilePath.isEmpty()) { + q->setCurrentFile(QUrl::fromLocalFile(newCurrentFilePath)); + attached->fileDialogListView()->setCurrentIndex(newCurrentFileIndex); + if (QQuickItem *currentItem = attached->fileDialogListView()->currentItem()) + currentItem->forceActiveFocus(); + } +} + +void QQuickFileDialogImplPrivate::handleAccept() +{ + // Let handleClick take care of calling accept(). +} + +void QQuickFileDialogImplPrivate::handleClick(QQuickAbstractButton *button) +{ + Q_Q(QQuickFileDialogImpl); + if (buttonRole(button) == QPlatformDialogHelper::AcceptRole && currentFile.isValid()) { + // The "Open" button was clicked, so we need to set the file to the current file, if any. + const QFileInfo fileInfo(currentFile.toLocalFile()); + if (fileInfo.isDir()) { + // If it's a directory, navigate to it. + q->setCurrentFolder(currentFile); + // Don't call accept(), because selecting a folder != accepting the dialog. + } else { + // Otherwise it's a file, so select it and close the dialog. + q->setSelectedFile(currentFile); + q->accept(); + QQuickDialogPrivate::handleClick(button); + emit q->fileSelected(currentFile); + } + } +} + +QQuickFileDialogImpl::QQuickFileDialogImpl(QObject *parent) + : QQuickDialog(*(new QQuickFileDialogImplPrivate), parent) +{ +} + +QQuickFileDialogImplAttached *QQuickFileDialogImpl::qmlAttachedProperties(QObject *object) +{ + return new QQuickFileDialogImplAttached(object); +} + +QUrl QQuickFileDialogImpl::currentFolder() const +{ + Q_D(const QQuickFileDialogImpl); + return d->currentFolder; +} + +void QQuickFileDialogImpl::setCurrentFolder(const QUrl ¤tFolder) +{ + qCDebug(lcCurrentFolder) << "setCurrentFolder called with" << currentFolder; + Q_D(QQuickFileDialogImpl); + if (currentFolder == d->currentFolder) + return; + + const QString oldFolderPath = QQmlFile::urlToLocalFileOrQrc(d->currentFolder); + + d->currentFolder = currentFolder; + // Since the directory changed, the old file can no longer be selected. + setCurrentFile(QUrl()); + d->updateCurrentFile(oldFolderPath); + emit currentFolderChanged(d->currentFolder); +} + +QUrl QQuickFileDialogImpl::selectedFile() const +{ + Q_D(const QQuickFileDialogImpl); + return d->selectedFile; +} + +void QQuickFileDialogImpl::setSelectedFile(const QUrl &selectedFile) +{ + Q_D(QQuickFileDialogImpl); + if (selectedFile == d->selectedFile) + return; + + d->selectedFile = selectedFile; + emit selectedFileChanged(); +} + +QUrl QQuickFileDialogImpl::currentFile() const +{ + Q_D(const QQuickFileDialogImpl); + return d->currentFile; +} + +void QQuickFileDialogImpl::setCurrentFile(const QUrl ¤tFile) +{ + Q_D(QQuickFileDialogImpl); + if (currentFile == d->currentFile) + return; + + d->currentFile = currentFile; + d->updateEnabled(); + emit currentFileChanged(d->currentFile); +} + +QSharedPointer<QFileDialogOptions> QQuickFileDialogImpl::options() const +{ + Q_D(const QQuickFileDialogImpl); + return d->options; +} + +void QQuickFileDialogImpl::setOptions(const QSharedPointer<QFileDialogOptions> &options) +{ + qCDebug(lcOptions).nospace() << "setOptions called with:" + << " acceptMode=" << options->acceptMode() + << " fileMode=" << options->fileMode() + << " initialDirectory=" << options->initialDirectory() + << " nameFilters=" << options->nameFilters() + << " initiallySelectedNameFilter=" << options->initiallySelectedNameFilter(); + + Q_D(QQuickFileDialogImpl); + d->options = options; + + if (d->options) { + d->selectedNameFilter->setOptions(options); + d->setNameFilters(options->nameFilters()); + } +} + +/*! + \internal + + The list of user-facing strings describing the available file filters. +*/ +QStringList QQuickFileDialogImpl::nameFilters() const +{ + Q_D(const QQuickFileDialogImpl); + return d->options ? d->options->nameFilters() : QStringList(); +} + +void QQuickFileDialogImpl::resetNameFilters() +{ + Q_D(QQuickFileDialogImpl); + d->setNameFilters(QStringList()); +} + +QQuickFileNameFilter *QQuickFileDialogImpl::selectedNameFilter() const +{ + Q_D(const QQuickFileDialogImpl); + if (!d->selectedNameFilter) { + QQuickFileDialogImpl *that = const_cast<QQuickFileDialogImpl *>(this); + d->selectedNameFilter = new QQuickFileNameFilter(that); + if (d->options) + d->selectedNameFilter->setOptions(d->options); + } + return d->selectedNameFilter; +} + +/*! + \internal + + These allow QQuickPlatformFileDialog::show() to set custom labels on the + dialog buttons without having to know about/go through QQuickFileDialogImplAttached + and QQuickDialogButtonBox. +*/ +void QQuickFileDialogImpl::setAcceptLabel(const QString &label) +{ + Q_D(QQuickFileDialogImpl); + d->acceptLabel = label; + QQuickFileDialogImplAttached *attached = d->attachedOrWarn(); + if (!attached) + return; + + auto acceptButton = attached->buttonBox()->standardButton(QPlatformDialogHelper::Open); + if (!acceptButton) { + qmlWarning(this).nospace() << "Can't set accept label to " << label + << "; failed to find Open button in DialogButtonBox of " << this; + return; + } + + acceptButton->setText(!label.isEmpty() + ? label : QQuickDialogButtonBoxPrivate::buttonText(QPlatformDialogHelper::Open)); +} + +void QQuickFileDialogImpl::setRejectLabel(const QString &label) +{ + Q_D(QQuickFileDialogImpl); + d->rejectLabel = label; + QQuickFileDialogImplAttached *attached = d->attachedOrWarn(); + if (!attached) + return; + + auto rejectButton = attached->buttonBox()->standardButton(QPlatformDialogHelper::Cancel); + if (!rejectButton) { + qmlWarning(this).nospace() << "Can't set reject label to " << label + << "; failed to find Open button in DialogButtonBox of " << this; + return; + } + + rejectButton->setText(!label.isEmpty() + ? label : QQuickDialogButtonBoxPrivate::buttonText(QPlatformDialogHelper::Cancel)); +} + +void QQuickFileDialogImpl::selectNameFilter(const QString &filter) +{ + qCDebug(lcNameFilters) << "selectNameFilter called with" << filter; + Q_D(QQuickFileDialogImpl); + d->selectedNameFilter->update(filter); + emit filterSelected(filter); +} + +void QQuickFileDialogImpl::componentComplete() +{ + Q_D(QQuickFileDialogImpl); + QQuickDialog::componentComplete(); + + // Find the right-most button and set its key navigation so that + // tab moves focus to the breadcrumb bar's up button. I tried + // doing this via KeyNavigation on the DialogButtonBox in QML, + // but it didn't work (probably because it's not the right item). + QQuickFileDialogImplAttached *attached = d->attachedOrWarn(); + if (!attached) + return; + + const int buttonCount = attached->buttonBox()->count(); + if (buttonCount == 0) + return; + + QQuickAbstractButton *rightMostButton = qobject_cast<QQuickAbstractButton *>( + attached->buttonBox()->itemAt(buttonCount - 1)); + if (!rightMostButton) { + qmlWarning(this) << "Can't find right-most button in DialogButtonBox"; + return; + } + + auto keyNavigationAttached = QQuickKeyNavigationAttached::qmlAttachedProperties(rightMostButton); + if (!keyNavigationAttached) { + qmlWarning(this) << "Can't create attached KeyNavigation object on" << QDebug::toString(rightMostButton); + return; + } + + keyNavigationAttached->setTab(attached->breadcrumbBar()->upButton()); +} + +void QQuickFileDialogImpl::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) +{ + Q_D(QQuickFileDialogImpl); + QQuickDialog::itemChange(change, data); + + if (change != QQuickItem::ItemVisibleHasChanged || !isComponentComplete() || !data.boolValue) + return; + + QQuickFileDialogImplAttached *attached = d->attachedOrWarn(); + if (!attached) + return; + + attached->fileDialogListView()->forceActiveFocus(); + d->updateEnabled(); +} + +QQuickFileDialogImplAttached *QQuickFileDialogImplPrivate::attachedOrWarn() +{ + Q_Q(QQuickFileDialogImpl); + QQuickFileDialogImplAttached *attached = static_cast<QQuickFileDialogImplAttached*>( + qmlAttachedPropertiesObject<QQuickFileDialogImpl>(q)); + if (!attached) + qmlWarning(q) << "Expected FileDialogImpl attached object to be present on" << this; + return attached; +} + +void QQuickFileDialogImplAttachedPrivate::nameFiltersComboBoxItemActivated(int index) +{ + qCDebug(lcAttachedNameFilters) << "nameFiltersComboBoxItemActivated called with" << index; + auto fileDialogImpl = qobject_cast<QQuickFileDialogImpl*>(parent); + if (!fileDialogImpl) + return; + + fileDialogImpl->selectNameFilter(nameFiltersComboBox->textAt(index)); +} + +void QQuickFileDialogImplAttachedPrivate::fileDialogListViewCurrentIndexChanged() +{ + auto fileDialogImpl = qobject_cast<QQuickFileDialogImpl*>(parent); + if (!fileDialogImpl) + return; + + auto fileDialogDelegate = qobject_cast<QQuickFileDialogDelegate*>(fileDialogListView->currentItem()); + if (!fileDialogDelegate) + return; + + fileDialogImpl->setCurrentFile(fileDialogDelegate->file()); +} + +QQuickFileDialogImplAttached::QQuickFileDialogImplAttached(QObject *parent) + : QObject(*(new QQuickFileDialogImplAttachedPrivate), parent) +{ + if (!qobject_cast<QQuickFileDialogImpl*>(parent)) { + qmlWarning(this) << "FileDialogImpl attached properties should only be " + << "accessed through the root FileDialogImpl instance"; + } +} + +QQuickDialogButtonBox *QQuickFileDialogImplAttached::buttonBox() const +{ + Q_D(const QQuickFileDialogImplAttached); + return d->buttonBox; +} + +void QQuickFileDialogImplAttached::setButtonBox(QQuickDialogButtonBox *buttonBox) +{ + Q_D(QQuickFileDialogImplAttached); + if (buttonBox == d->buttonBox) + return; + + if (d->buttonBox) { + QQuickFileDialogImpl *fileDialogImpl = qobject_cast<QQuickFileDialogImpl*>(parent()); + if (fileDialogImpl) { + auto dialogPrivate = QQuickDialogPrivate::get(fileDialogImpl); + QObjectPrivate::disconnect(d->buttonBox, &QQuickDialogButtonBox::accepted, + dialogPrivate, &QQuickDialogPrivate::handleAccept); + QObjectPrivate::disconnect(d->buttonBox, &QQuickDialogButtonBox::rejected, + dialogPrivate, &QQuickDialogPrivate::handleReject); + QObjectPrivate::disconnect(d->buttonBox, &QQuickDialogButtonBox::clicked, + dialogPrivate, &QQuickDialogPrivate::handleClick); + } + } + + d->buttonBox = buttonBox; + + if (buttonBox) { + QQuickFileDialogImpl *fileDialogImpl = qobject_cast<QQuickFileDialogImpl*>(parent()); + if (fileDialogImpl) { + auto dialogPrivate = QQuickDialogPrivate::get(fileDialogImpl); + QObjectPrivate::connect(d->buttonBox, &QQuickDialogButtonBox::accepted, + dialogPrivate, &QQuickDialogPrivate::handleAccept); + QObjectPrivate::connect(d->buttonBox, &QQuickDialogButtonBox::rejected, + dialogPrivate, &QQuickDialogPrivate::handleReject); + QObjectPrivate::connect(d->buttonBox, &QQuickDialogButtonBox::clicked, + dialogPrivate, &QQuickDialogPrivate::handleClick); + } + } + + emit buttonBoxChanged(); +} + +QQuickComboBox *QQuickFileDialogImplAttached::nameFiltersComboBox() const +{ + Q_D(const QQuickFileDialogImplAttached); + return d->nameFiltersComboBox; +} + +void QQuickFileDialogImplAttached::setNameFiltersComboBox(QQuickComboBox *nameFiltersComboBox) +{ + Q_D(QQuickFileDialogImplAttached); + if (nameFiltersComboBox == d->nameFiltersComboBox) + return; + + d->nameFiltersComboBox = nameFiltersComboBox; + + QObjectPrivate::connect(d->nameFiltersComboBox, &QQuickComboBox::activated, + d, &QQuickFileDialogImplAttachedPrivate::nameFiltersComboBoxItemActivated); + + emit nameFiltersComboBoxChanged(); +} + +QString QQuickFileDialogImplAttached::selectedNameFilter() const +{ + Q_D(const QQuickFileDialogImplAttached); + return d->nameFiltersComboBox ? d->nameFiltersComboBox->currentText() : QString(); +} + +void QQuickFileDialogImplAttached::selectNameFilter(const QString &filter) +{ + Q_D(QQuickFileDialogImplAttached); + qCDebug(lcAttachedNameFilters) << "selectNameFilter called with" << filter; + if (!d->nameFiltersComboBox) + return; + + const int indexInComboBox = d->nameFiltersComboBox->find(filter); + if (indexInComboBox == -1) + return; + + qCDebug(lcAttachedNameFilters) << "setting ComboBox's currentIndex to" << indexInComboBox; + d->nameFiltersComboBox->setCurrentIndex(indexInComboBox); +} + +QQuickListView *QQuickFileDialogImplAttached::fileDialogListView() const +{ + Q_D(const QQuickFileDialogImplAttached); + return d->fileDialogListView; +} + +void QQuickFileDialogImplAttached::setFileDialogListView(QQuickListView *fileDialogListView) +{ + Q_D(QQuickFileDialogImplAttached); + if (fileDialogListView == d->fileDialogListView) + return; + + d->fileDialogListView = fileDialogListView; + + QObjectPrivate::connect(d->fileDialogListView, &QQuickListView::currentIndexChanged, + d, &QQuickFileDialogImplAttachedPrivate::fileDialogListViewCurrentIndexChanged); + + emit fileDialogListViewChanged(); +} + +QQuickFolderBreadcrumbBar *QQuickFileDialogImplAttached::breadcrumbBar() const +{ + Q_D(const QQuickFileDialogImplAttached); + return d->breadcrumbBar; +} + +void QQuickFileDialogImplAttached::setBreadcrumbBar(QQuickFolderBreadcrumbBar *breadcrumbBar) +{ + Q_D(QQuickFileDialogImplAttached); + if (breadcrumbBar == d->breadcrumbBar) + return; + + d->breadcrumbBar = breadcrumbBar; + emit breadcrumbBarChanged(); +} + +QT_END_NAMESPACE diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl_p.h b/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl_p.h new file mode 100644 index 00000000..754d2c30 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl_p.h @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 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$ +** +****************************************************************************/ + +#ifndef QQUICKFILEDIALOGIMPL_P_H +#define QQUICKFILEDIALOGIMPL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick/private/qquicklistview_p.h> +#include <QtQuickTemplates2/private/qquickdialog_p.h> + +#include "qtquickdialogs2quickimplglobal_p.h" + +QT_BEGIN_NAMESPACE + +class QQuickComboBox; +class QQuickDialogButtonBox; + +class QQuickFileDialogImplAttached; +class QQuickFileDialogImplAttachedPrivate; +class QQuickFileDialogImplPrivate; +class QQuickFileNameFilter; +class QQuickFolderBreadcrumbBar; + +class Q_QUICKDIALOGS2QUICKIMPL_PRIVATE_EXPORT QQuickFileDialogImpl : public QQuickDialog +{ + Q_OBJECT + Q_PROPERTY(QUrl currentFolder READ currentFolder WRITE setCurrentFolder NOTIFY currentFolderChanged FINAL) + Q_PROPERTY(QUrl selectedFile READ selectedFile WRITE setSelectedFile NOTIFY selectedFileChanged FINAL) + Q_PROPERTY(QUrl currentFile READ currentFile WRITE setCurrentFile NOTIFY currentFileChanged FINAL) + Q_PROPERTY(QStringList nameFilters READ nameFilters NOTIFY nameFiltersChanged FINAL) + Q_PROPERTY(QQuickFileNameFilter *selectedNameFilter READ selectedNameFilter CONSTANT) + QML_NAMED_ELEMENT(FileDialogImpl) + QML_ATTACHED(QQuickFileDialogImplAttached) + QML_ADDED_IN_VERSION(6, 2) + Q_MOC_INCLUDE(<QtQuickDialogs2Utils/private/qquickfilenamefilter_p.h>) + Q_MOC_INCLUDE(<QtQuickDialogs2QuickImpl/private/qquickfolderbreadcrumbbar_p.h>) + +public: + explicit QQuickFileDialogImpl(QObject *parent = nullptr); + + static QQuickFileDialogImplAttached *qmlAttachedProperties(QObject *object); + + QUrl currentFolder() const; + void setCurrentFolder(const QUrl ¤tFolder); + + QUrl selectedFile() const; + void setSelectedFile(const QUrl &file); + + QUrl currentFile() const; + void setCurrentFile(const QUrl ¤tFile); + + QSharedPointer<QFileDialogOptions> options() const; + void setOptions(const QSharedPointer<QFileDialogOptions> &options); + + QStringList nameFilters() const; + void resetNameFilters(); + + QQuickFileNameFilter *selectedNameFilter() const; + + void setAcceptLabel(const QString &label); + void setRejectLabel(const QString &label); + +public Q_SLOTS: + void selectNameFilter(const QString &filter); + +Q_SIGNALS: + void currentFolderChanged(const QUrl &folderUrl); + void selectedFileChanged(); + void currentFileChanged(const QUrl ¤tFileUrl); + void nameFiltersChanged(); + void fileSelected(const QUrl &fileUrl); + void filterSelected(const QString &filter); + +private: + void componentComplete() override; + void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) override; + + Q_DISABLE_COPY(QQuickFileDialogImpl) + Q_DECLARE_PRIVATE(QQuickFileDialogImpl) +}; + +class Q_QUICKDIALOGS2QUICKIMPL_PRIVATE_EXPORT QQuickFileDialogImplAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickDialogButtonBox *buttonBox READ buttonBox WRITE setButtonBox NOTIFY buttonBoxChanged FINAL) + Q_PROPERTY(QQuickComboBox *nameFiltersComboBox READ nameFiltersComboBox WRITE setNameFiltersComboBox NOTIFY nameFiltersComboBoxChanged) + Q_PROPERTY(QQuickListView *fileDialogListView READ fileDialogListView WRITE setFileDialogListView NOTIFY fileDialogListViewChanged) + Q_PROPERTY(QQuickFolderBreadcrumbBar *breadcrumbBar READ breadcrumbBar WRITE setBreadcrumbBar NOTIFY breadcrumbBarChanged) + Q_MOC_INCLUDE(<QtQuickTemplates2/private/qquickdialogbuttonbox_p.h>) + Q_MOC_INCLUDE(<QtQuickTemplates2/private/qquickcombobox_p.h>) + +public: + explicit QQuickFileDialogImplAttached(QObject *parent = nullptr); + + QQuickDialogButtonBox *buttonBox() const; + void setButtonBox(QQuickDialogButtonBox *buttonBox); + + QQuickComboBox *nameFiltersComboBox() const; + void setNameFiltersComboBox(QQuickComboBox *nameFiltersComboBox); + + QString selectedNameFilter() const; + void selectNameFilter(const QString &filter); + + QQuickListView *fileDialogListView() const; + void setFileDialogListView(QQuickListView *fileDialogListView); + + QQuickFolderBreadcrumbBar *breadcrumbBar() const; + void setBreadcrumbBar(QQuickFolderBreadcrumbBar *breadcrumbBar); + +Q_SIGNALS: + void buttonBoxChanged(); + void nameFiltersComboBoxChanged(); + void fileDialogListViewChanged(); + void breadcrumbBarChanged(); + +private: + Q_DISABLE_COPY(QQuickFileDialogImplAttached) + Q_DECLARE_PRIVATE(QQuickFileDialogImplAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickFileDialogImpl) + +#endif // QQUICKFILEDIALOGIMPL_P_H diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl_p_p.h b/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl_p_p.h new file mode 100644 index 00000000..4acce71d --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl_p_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 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$ +** +****************************************************************************/ + +#ifndef QQUICKFILEDIALOG_P_P_H +#define QQUICKFILEDIALOG_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcombobox_p.h> +#include <QtQuickTemplates2/private/qquickdialog_p_p.h> +#include <QtQuickTemplates2/private/qquickdialogbuttonbox_p.h> + +#include "qquickfiledialogimpl_p.h" + +QT_BEGIN_NAMESPACE + +class QQuickFileNameFilter; + +class QQuickFileDialogImplPrivate : public QQuickDialogPrivate +{ + Q_DECLARE_PUBLIC(QQuickFileDialogImpl) + +public: + QQuickFileDialogImplPrivate(); + + static QQuickFileDialogImplPrivate *get(QQuickFileDialogImpl *dialog) + { + return dialog->d_func(); + } + + QQuickFileDialogImplAttached *attachedOrWarn(); + + void setNameFilters(const QStringList &filters); + + void updateEnabled(); + void updateCurrentFile(const QString &oldFolderPath); + + void handleAccept() override; + void handleClick(QQuickAbstractButton *button) override; + + QSharedPointer<QFileDialogOptions> options; + QUrl currentFolder; + QUrl selectedFile; + QUrl currentFile; + QStringList nameFilters; + mutable QQuickFileNameFilter *selectedNameFilter = nullptr; + QString acceptLabel; + QString rejectLabel; +}; + +class QQuickFileDialogImplAttachedPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickFileDialogImplAttached) + + void nameFiltersComboBoxItemActivated(int index); + void fileDialogListViewCurrentIndexChanged(); + +public: + QPointer<QQuickDialogButtonBox> buttonBox; + QPointer<QQuickComboBox> nameFiltersComboBox; + QPointer<QQuickListView> fileDialogListView; + QPointer<QQuickFolderBreadcrumbBar> breadcrumbBar; +}; + +QT_END_NAMESPACE + +#endif // QQUICKFILEDIALOG_P_P_H diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qquickfolderbreadcrumbbar.cpp b/src/quickdialogs2/quickdialogs2quickimpl/qquickfolderbreadcrumbbar.cpp new file mode 100644 index 00000000..65417a67 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qquickfolderbreadcrumbbar.cpp @@ -0,0 +1,784 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 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 "qquickfolderbreadcrumbbar_p.h" +#include "qquickfolderbreadcrumbbar_p_p.h" + +#include <QtCore/qdir.h> +#include <QtCore/qloggingcategory.h> +#if QT_CONFIG(shortcut) +#include <QtGui/private/qshortcutmap_p.h> +#endif +#include <QtGui/private/qguiapplication_p.h> +#include <QtQml/QQmlFile> +#include <QtQuick/private/qquicktextinput_p.h> +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> +#include <QtQuickTemplates2/private/qquickpopupitem_p_p.h> +#include <QtQuickTemplates2/private/qquickshortcutcontext_p_p.h> + +#include "qquickfiledialogimpl_p.h" +#include "qquickfiledialogimpl_p_p.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcFolderBreadcrumbBar, "qt.quick.dialogs.folderbreadcrumbbar") +Q_LOGGING_CATEGORY(lcContentSize, "qt.quick.dialogs.folderbreadcrumbbar.contentsize") +Q_LOGGING_CATEGORY(lcDelegates, "qt.quick.dialogs.folderbreadcrumbbar.delegates") +Q_LOGGING_CATEGORY(lcShortcuts, "qt.quick.dialogs.folderbreadcrumbbar.shortcuts") +Q_LOGGING_CATEGORY(lcTextInput, "qt.quick.dialogs.folderbreadcrumbbar.textinput") +Q_LOGGING_CATEGORY(lcCurrentItem, "qt.quick.dialogs.folderbreadcrumbbar.currentitem") + +QQuickItem *QQuickFolderBreadcrumbBarPrivate::createDelegateItem(QQmlComponent *component, const QVariantMap &initialProperties) +{ + Q_Q(QQuickFolderBreadcrumbBar); + // If we don't use the correct context, it won't be possible to refer to + // the control's id from within the delegates. + QQmlContext *creationContext = component->creationContext(); + // The component might not have been created in QML, in which case + // the creation context will be null and we have to create it ourselves. + if (!creationContext) + creationContext = qmlContext(q); + QQmlContext *context = new QQmlContext(creationContext, q); + context->setContextObject(q); + QQuickItem *item = qobject_cast<QQuickItem*>(component->createWithInitialProperties(initialProperties, context)); + if (item) + QQml_setParent_noEvent(item, q); + qCDebug(lcDelegates) << "- created delegate item" << item << "with initialProperties" << initialProperties; + return item; +} + +QString QQuickFolderBreadcrumbBarPrivate::folderBaseName(const QString &folderPath) +{ + if (folderPath == QLatin1String("/")) { + // Unix root. + return folderPath; + } else if (folderPath.endsWith(QLatin1String(":/"))) { + // Windows drive. + return folderPath.mid(0, folderPath.size() - 1); + } + const QString baseName = folderPath.mid(folderPath.lastIndexOf(QLatin1Char('/')) + 1); + return baseName; +} + +/*! + \internal + + Returns \c { "/foo", "/foo/bar", "/foo/bar/baz" } if \a folder is \c "/foo/bar/baz". +*/ +QStringList QQuickFolderBreadcrumbBarPrivate::crumbPathsForFolder(const QUrl &folder) +{ + const QString folderPath = QDir::fromNativeSeparators(QQmlFile::urlToLocalFileOrQrc(folder)); + QDir dir(folderPath); + // In order to collect the paths for each breadcrumb, we need to work backwards, so we prepend. + QStringList paths; + do { + paths.prepend(dir.absolutePath()); + } while (dir.cdUp()); + return paths; +} + +void QQuickFolderBreadcrumbBarPrivate::repopulate() +{ + Q_Q(QQuickFolderBreadcrumbBar); + qCDebug(lcDelegates) << "attemping to repopulate breadcrumb bar using folder..."; + + if (repopulating) + return; + + if (!buttonDelegate || !separatorDelegate || !q->contentItem()) { + qCWarning(lcDelegates) << "Both delegates and contentItem must be set before repopulating"; + return; + } + + QBoolBlocker repopulateGuard(repopulating); + + auto failureCleanup = [=](){ + folderPaths.clear(); + while (q->count() > 0) + q->removeItem(q->itemAt(0)); + }; + + qCDebug(lcDelegates) << "- getting paths for directory" << fileDialog->currentFolder(); + folderPaths = crumbPathsForFolder(fileDialog->currentFolder()); + + while (q->count() > 0) + q->removeItem(q->itemAt(0)); + + for (int i = 0; i < folderPaths.size(); ++i) { + const QString &folderPath = folderPaths.at(i); + + QVariantMap initialProperties = { + { QStringLiteral("index"), QVariant::fromValue(i) }, + { QStringLiteral("folderName"), QVariant::fromValue(folderBaseName(folderPath)) } + }; + QQuickItem *buttonItem = createDelegateItem(buttonDelegate, initialProperties); + if (!buttonItem) { + qCWarning(lcDelegates) << "Failed creating breadcrumb buttonDelegate item:\n" << buttonDelegate->errorString(); + failureCleanup(); + break; + } + if (QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton*>(buttonItem)) { + QObjectPrivate::connect(button, &QQuickAbstractButton::clicked, + this, &QQuickFolderBreadcrumbBarPrivate::crumbClicked); + } + insertItem(q->count(), buttonItem); + + // Don't add a separator for the last button. + if (i < folderPaths.size() - 1) { + initialProperties = {}; + QQuickItem *separatorItem = createDelegateItem(separatorDelegate, initialProperties); + if (!separatorItem) { + qCWarning(lcDelegates) << "Failed creating breadcrumb separatorDelegate item:\n" << buttonDelegate->errorString(); + failureCleanup(); + break; + } + insertItem(q->count(), separatorItem); + } + } + + const int finalCount = q->count(); + // We would do - 2, since separators are included in the count, + // but as we don't add a separator for the last button, we only need to subtract 1. + const int newCurrentIndex = finalCount > 2 ? finalCount - 1 : -1; + qCDebug(lcDelegates) << "- setting currentIndex to" << newCurrentIndex; + q->setCurrentIndex(newCurrentIndex); + + updateImplicitContentSize(); + + qCDebug(lcDelegates) << "... bar now contains" << q->count() + << "buttons and separators in total, for the following paths:" << folderPaths; +} + +void QQuickFolderBreadcrumbBarPrivate::crumbClicked() +{ + Q_Q(QQuickFolderBreadcrumbBar); + qCDebug(lcCurrentItem) << "updateCurrentIndex called by sender" << q->sender(); + QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton*>(q->sender()); + if (button) { + const int buttonIndex = contentModel->indexOf(button, nullptr); + q->setCurrentIndex(buttonIndex); + const QUrl folderUrl = QUrl::fromLocalFile(folderPaths.at(buttonIndex / 2)); + // TODO: don't repopulate the whole model when clicking on crumbs + qCDebug(lcCurrentItem) << "setting file dialog's folder to" << folderUrl; + fileDialog->setCurrentFolder(folderUrl); + } +} + +void QQuickFolderBreadcrumbBarPrivate::folderChanged() +{ + if (componentComplete) + repopulate(); +} + +static inline QString upButtonName() +{ + return QStringLiteral("upButton"); +} + +void QQuickFolderBreadcrumbBarPrivate::cancelUpButton() +{ + Q_Q(QQuickFolderBreadcrumbBar); + quickCancelDeferred(q, upButtonName()); +} + +void QQuickFolderBreadcrumbBarPrivate::executeUpButton(bool complete) +{ + Q_Q(QQuickFolderBreadcrumbBar); + if (upButton.wasExecuted()) + return; + + if (!upButton || complete) + quickBeginDeferred(q, upButtonName(), upButton); + if (complete) + quickCompleteDeferred(q, upButtonName(), upButton); +} + +void QQuickFolderBreadcrumbBarPrivate::goUp() +{ + QDir dir(QQmlFile::urlToLocalFileOrQrc(fileDialog->currentFolder())); + dir.cdUp(); + fileDialog->setCurrentFolder(QUrl::fromLocalFile(dir.absolutePath())); +} + +static inline QString textFieldName() +{ + return QStringLiteral("textField"); +} + +void QQuickFolderBreadcrumbBarPrivate::cancelTextField() +{ + Q_Q(QQuickFolderBreadcrumbBar); + quickCancelDeferred(q, textFieldName()); +} + +void QQuickFolderBreadcrumbBarPrivate::executeTextField(bool complete) +{ + Q_Q(QQuickFolderBreadcrumbBar); + if (textField.wasExecuted()) + return; + + if (!textField || complete) + quickBeginDeferred(q, textFieldName(), textField); + if (complete) + quickCompleteDeferred(q, textFieldName(), textField); +} + +void QQuickFolderBreadcrumbBarPrivate::toggleTextFieldVisibility() +{ + const QUrl url = !fileDialog->selectedFile().isEmpty() ? fileDialog->selectedFile() : fileDialog->currentFolder(); + textField->setText(QQmlFile::urlToLocalFileOrQrc(url)); + textField->setVisible(!textField->isVisible()); + + qCDebug(lcTextInput) << "text field visibility changed to" << textField->isVisible(); + + if (textField->isVisible()) { + // The text field is now visible, so give it focus, + // select the text, and let it handle escape/back. + textField->forceActiveFocus(Qt::ShortcutFocusReason); + textField->selectAll(); + } + + // We connect to the TextField's visibleChanged signal, so textFieldVisibleChanged() + // will get called automatically and we don't need to call it here. + + contentItem->setVisible(!textField->isVisible()); + + // When the TextField is visible, certain items in the dialog need to be disabled. + auto fileDialogPrivate = QQuickFileDialogImplPrivate::get(fileDialog); + fileDialogPrivate->updateEnabled(); +} + +void QQuickFolderBreadcrumbBarPrivate::textFieldAccepted() +{ + const QUrl fileUrl = QUrl::fromLocalFile(textField->text()); + const bool mustExist = fileDialog->options()->acceptMode() != QFileDialogOptions::AcceptSave; + const bool enteredPathIsValidUrl = fileUrl.isValid(); + bool enteredPathExists = false; + bool enteredPathIsDir = false; + if (enteredPathIsValidUrl) { + const QFileInfo fileInfo(textField->text()); + enteredPathExists = fileInfo.exists(); + if (enteredPathExists) + enteredPathIsDir = fileInfo.isDir(); + } + + qCDebug(lcTextInput).nospace() << "text field accepted -" + << " text=" << textField->text() + << " fileUrl=" << fileUrl + << " mustExist=" << mustExist + << " enteredPathIsValidUrl=" << enteredPathIsValidUrl + << " enteredPathExists=" << enteredPathExists + << " enteredPathIsDir=" << enteredPathIsDir; + + if (enteredPathIsDir && (enteredPathExists || !mustExist)) { + qCDebug(lcTextInput) << "path entered is a folder; setting folder"; + fileDialog->setCurrentFolder(fileUrl); + } else if (!enteredPathIsDir && (enteredPathExists || !mustExist)) { + qCDebug(lcTextInput) << "path entered is a file; setting file and calling accept()"; + // It's important that we set the currentFile here, as that's what + // QQuickPlatformFileDialog::selectedFiles() needs to return, and + // QQuickFileDialog::accept() sets its file property based on + // selectedFiles(). + fileDialog->setCurrentFile(fileUrl); + fileDialog->setSelectedFile(fileUrl); + fileDialog->accept(); + } else { + qCDebug(lcTextInput) << "path entered is not valid; not setting file/folder"; + } + + toggleTextFieldVisibility(); +} + +void QQuickFolderBreadcrumbBarPrivate::textFieldVisibleChanged() +{ + qCDebug(lcShortcuts) << "text field was either hidden or shown"; + + if (textField && textField->isVisible()) + handleTextFieldShown(); + else + handleTextFieldHidden(); +} + +void QQuickFolderBreadcrumbBarPrivate::textFieldActiveFocusChanged() +{ + // When the text field loses focus, it should be hidden. + if (!textField->hasActiveFocus() && textField->isVisible()) + toggleTextFieldVisibility(); +} + +/* + When the text field is visible: + + - Ctrl+L should do nothing (matches e.g. Ubuntu and Windows) + - Escape/back should hide it +*/ +void QQuickFolderBreadcrumbBarPrivate::handleTextFieldShown() +{ +#if QT_CONFIG(shortcut) + Q_Q(QQuickFolderBreadcrumbBar); + if (editPathToggleShortcutId == 0) + return; + + QGuiApplicationPrivate *appPrivate = QGuiApplicationPrivate::instance(); + qCDebug(lcShortcuts) << "text field was shown; grabbing/ungrabbing relevant shortcuts..."; + + // Disable the back/escape shortcuts for QQuickPopup so that the TextField can get them. + auto popupItem = qobject_cast<QQuickPopupItem*>(fileDialog->popupItem()); + popupItem->ungrabShortcut(); + + appPrivate->shortcutMap.removeShortcut(editPathToggleShortcutId, q); + editPathToggleShortcutId = 0; + + editPathBackShortcutId = appPrivate->shortcutMap.addShortcut( + q, Qt::Key_Back, Qt::WindowShortcut, QQuickShortcutContext::matcher); + editPathEscapeShortcutId = appPrivate->shortcutMap.addShortcut( + q, Qt::Key_Escape, Qt::WindowShortcut, QQuickShortcutContext::matcher); + + qCDebug(lcShortcuts).nospace() << "... shortcut IDs:" + << " editPathToggleShortcutId=" << editPathToggleShortcutId + << " editPathBackShortcutId=" << editPathBackShortcutId + << " editPathEscapeShortcutId=" << editPathEscapeShortcutId; +#endif +} + +/* + When the text field is not visible: + + - Ctrl+L should make it visible + - Escape/back should close the dialog +*/ +void QQuickFolderBreadcrumbBarPrivate::handleTextFieldHidden() +{ +#if QT_CONFIG(shortcut) + Q_Q(QQuickFolderBreadcrumbBar); + + QGuiApplicationPrivate *appPrivate = QGuiApplicationPrivate::instance(); + qCDebug(lcShortcuts) << "text field was hidden; grabbing/ungrabbing relevant shortcuts..."; + + if (editPathToggleShortcutId == 0) { + editPathToggleShortcutId = appPrivate->shortcutMap.addShortcut( + q, Qt::CTRL | Qt::Key_L, Qt::WindowShortcut, QQuickShortcutContext::matcher); + } + + // When the bar is first completed, this function is called, since the text field starts off hidden. + // If removeShortcut is called with a zero id, all shortcuts for the given object will be removed, + // and we don't want that. + if (editPathBackShortcutId != 0) { + appPrivate->shortcutMap.removeShortcut(editPathBackShortcutId, q); + editPathBackShortcutId = 0; + } + if (editPathEscapeShortcutId != 0) { + appPrivate->shortcutMap.removeShortcut(editPathEscapeShortcutId, q); + editPathEscapeShortcutId = 0; + } + + // Re-enable the back/escape shortcuts for QQuickPopup now that TextField no longer needs them. + auto popupItem = qobject_cast<QQuickPopupItem*>(fileDialog->popupItem()); + if (popupItem) + popupItem->grabShortcut(); + + qCDebug(lcShortcuts).nospace() << "... shortcut IDs: " + << " editPathToggleShortcutId=" << editPathToggleShortcutId + << " editPathBackShortcutId=" << editPathBackShortcutId + << " editPathEscapeShortcutId=" << editPathEscapeShortcutId; +#endif +} + +void QQuickFolderBreadcrumbBarPrivate::ungrabEditPathShortcuts() +{ +#if QT_CONFIG(shortcut) + Q_Q(QQuickFolderBreadcrumbBar); + QGuiApplicationPrivate *appPrivate = QGuiApplicationPrivate::instance(); + qCDebug(lcShortcuts) << "ungrabbing all edit path shortcuts"; + + if (editPathToggleShortcutId != 0) { + appPrivate->shortcutMap.removeShortcut(editPathToggleShortcutId, q); + editPathToggleShortcutId = 0; + } + if (editPathBackShortcutId != 0) { + appPrivate->shortcutMap.removeShortcut(editPathBackShortcutId, q); + editPathBackShortcutId = 0; + } + if (editPathEscapeShortcutId != 0) { + appPrivate->shortcutMap.removeShortcut(editPathEscapeShortcutId, q); + editPathEscapeShortcutId = 0; + } +#endif +} + +qreal QQuickFolderBreadcrumbBarPrivate::getContentWidth() const +{ + Q_Q(const QQuickFolderBreadcrumbBar); + const int count = contentModel->count(); + qreal totalWidth = qMax(0, count - 1) * spacing; + for (int i = 0; i < count; ++i) { + QQuickItem *item = q->itemAt(i); + if (item) { + QQuickItemPrivate *p = QQuickItemPrivate::get(item); + if (!p->widthValid) + totalWidth += item->implicitWidth(); + else + totalWidth += item->width(); + } + } + qCDebug(lcContentSize) << "content width:" << totalWidth; + return totalWidth; +} + +qreal QQuickFolderBreadcrumbBarPrivate::getContentHeight() const +{ + Q_Q(const QQuickFolderBreadcrumbBar); + const int count = contentModel->count(); + qreal maxHeight = 0; + for (int i = 0; i < count; ++i) { + QQuickItem *item = q->itemAt(i); + if (item) + maxHeight = qMax(maxHeight, item->implicitHeight()); + } + qCDebug(lcContentSize) << "content height:" << maxHeight; + return maxHeight; +} + +void QQuickFolderBreadcrumbBarPrivate::resizeContent() +{ + Q_Q(QQuickFolderBreadcrumbBar); + if (contentItem) { + const int upButtonSpace = q->upButton() ? q->upButton()->width() + upButtonSpacing : 0; + contentItem->setPosition(QPointF(q->leftPadding() + upButtonSpace, q->topPadding())); + contentItem->setSize(QSizeF(q->availableWidth() - upButtonSpace, q->availableHeight())); + + if (textField) { + textField->setPosition(contentItem->position()); + textField->setSize(contentItem->size()); + } + } +} + +void QQuickFolderBreadcrumbBarPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) +{ + QQuickContainerPrivate::itemGeometryChanged(item, change, diff); + if (change.sizeChange()) + updateImplicitContentSize(); +} + +void QQuickFolderBreadcrumbBarPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + QQuickContainerPrivate::itemImplicitWidthChanged(item); + if (item != contentItem) + updateImplicitContentWidth(); +} + +void QQuickFolderBreadcrumbBarPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + QQuickContainerPrivate::itemImplicitHeightChanged(item); + if (item != contentItem) + updateImplicitContentHeight(); +} + +/*! + \internal + + Private class for breadcrumb navigation of a directory. + + Given a FileDialog, FolderBreadCrumbbar creates breadcrumb buttons and + separators from the specified delegate components. +*/ + +QQuickFolderBreadcrumbBar::QQuickFolderBreadcrumbBar(QQuickItem *parent) + : QQuickContainer(*(new QQuickFolderBreadcrumbBarPrivate), parent) +{ + Q_D(QQuickFolderBreadcrumbBar); + d->changeTypes |= QQuickItemPrivate::Geometry | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight; +} + +QQuickFileDialogImpl *QQuickFolderBreadcrumbBar::fileDialog() const +{ + Q_D(const QQuickFolderBreadcrumbBar); + return d->fileDialog; +} + +void QQuickFolderBreadcrumbBar::setFileDialog(QQuickFileDialogImpl *fileDialog) +{ + Q_D(QQuickFolderBreadcrumbBar); + if (fileDialog == d->fileDialog) + return; + + if (d->fileDialog) + QObjectPrivate::disconnect(d->fileDialog, &QQuickFileDialogImpl::currentFolderChanged, + d, &QQuickFolderBreadcrumbBarPrivate::folderChanged); + + d->fileDialog = fileDialog; + + if (d->fileDialog) + QObjectPrivate::connect(d->fileDialog, &QQuickFileDialogImpl::currentFolderChanged, + d, &QQuickFolderBreadcrumbBarPrivate::folderChanged); + + emit fileDialogChanged(); +} + +QQmlComponent *QQuickFolderBreadcrumbBar::buttonDelegate() +{ + Q_D(QQuickFolderBreadcrumbBar); + return d->buttonDelegate; +} + +void QQuickFolderBreadcrumbBar::setButtonDelegate(QQmlComponent *delegate) +{ + Q_D(QQuickFolderBreadcrumbBar); + qCDebug(lcFolderBreadcrumbBar) << "setButtonDelegate called with" << delegate; + if (d->componentComplete) { + // Simplify the code by disallowing this. + qCWarning(lcFolderBreadcrumbBar) << "BreadcrumbBar does not support setting delegates after component completion"; + return; + } + + if (delegate == d->buttonDelegate) + return; + + d->buttonDelegate = delegate; + emit buttonDelegateChanged(); +} + +QQmlComponent *QQuickFolderBreadcrumbBar::separatorDelegate() +{ + Q_D(QQuickFolderBreadcrumbBar); + return d->separatorDelegate; +} + +void QQuickFolderBreadcrumbBar::setSeparatorDelegate(QQmlComponent *delegate) +{ + Q_D(QQuickFolderBreadcrumbBar); + qCDebug(lcFolderBreadcrumbBar) << "setSeparatorDelegate called with" << delegate; + if (d->componentComplete) { + qCWarning(lcFolderBreadcrumbBar) << "BreadcrumbBar does not support setting delegates after component completion"; + return; + } + + if (delegate == d->separatorDelegate) + return; + + d->separatorDelegate = delegate; + emit separatorDelegateChanged(); +} + +QQuickAbstractButton *QQuickFolderBreadcrumbBar::upButton() +{ + Q_D(QQuickFolderBreadcrumbBar); + if (!d->upButton) + d->executeUpButton(); + return d->upButton; +} + +void QQuickFolderBreadcrumbBar::setUpButton(QQuickAbstractButton *upButton) +{ + Q_D(QQuickFolderBreadcrumbBar); + if (upButton == d->upButton) + return; + + if (!d->upButton.isExecuting()) + d->cancelUpButton(); + + if (d->upButton) { + QObjectPrivate::disconnect(d->upButton.data(), &QQuickAbstractButton::clicked, + d, &QQuickFolderBreadcrumbBarPrivate::goUp); + } + + QQuickControlPrivate::hideOldItem(d->upButton); + d->upButton = upButton; + if (d->upButton) { + if (!d->upButton->parentItem()) + d->upButton->setParentItem(this); + + QObjectPrivate::connect(d->upButton.data(), &QQuickAbstractButton::clicked, + d, &QQuickFolderBreadcrumbBarPrivate::goUp); + } + if (!d->upButton.isExecuting()) + emit upButtonChanged(); +} + +int QQuickFolderBreadcrumbBar::upButtonSpacing() const +{ + Q_D(const QQuickFolderBreadcrumbBar); + return d->upButtonSpacing; +} + +void QQuickFolderBreadcrumbBar::setUpButtonSpacing(int upButtonSpacing) +{ + Q_D(QQuickFolderBreadcrumbBar); + if (upButtonSpacing == d->upButtonSpacing) + return; + + d->upButtonSpacing = upButtonSpacing; + emit upButtonSpacingChanged(); +} + +QQuickTextField *QQuickFolderBreadcrumbBar::textField() +{ + Q_D(QQuickFolderBreadcrumbBar); + return d->textField; +} + +void QQuickFolderBreadcrumbBar::setTextField(QQuickTextField *textField) +{ + Q_D(QQuickFolderBreadcrumbBar); + if (textField == d->textField) + return; + + if (!d->textField.isExecuting()) + d->cancelUpButton(); + + if (d->textField) + d->handleTextFieldHidden(); + + if (d->textField) { + QObjectPrivate::disconnect(d->textField.data(), &QQuickTextInput::visibleChanged, + d, &QQuickFolderBreadcrumbBarPrivate::textFieldVisibleChanged); + QObjectPrivate::disconnect(d->textField.data(), &QQuickTextInput::activeFocusChanged, + d, &QQuickFolderBreadcrumbBarPrivate::textFieldActiveFocusChanged); + QObjectPrivate::disconnect(d->textField.data(), &QQuickTextInput::accepted, + d, &QQuickFolderBreadcrumbBarPrivate::textFieldAccepted); + } + + QQuickControlPrivate::hideOldItem(d->textField); + d->textField = textField; + if (d->textField) { + if (!d->textField->parentItem()) + d->textField->setParentItem(this); + + d->textField->setVisible(false); + + QObjectPrivate::connect(d->textField.data(), &QQuickTextInput::visibleChanged, + d, &QQuickFolderBreadcrumbBarPrivate::textFieldVisibleChanged); + QObjectPrivate::connect(d->textField.data(), &QQuickTextInput::activeFocusChanged, + d, &QQuickFolderBreadcrumbBarPrivate::textFieldActiveFocusChanged); + QObjectPrivate::connect(d->textField.data(), &QQuickTextInput::accepted, + d, &QQuickFolderBreadcrumbBarPrivate::textFieldAccepted); + } + if (!d->textField.isExecuting()) + emit textFieldChanged(); +} + +bool QQuickFolderBreadcrumbBar::event(QEvent *event) +{ +#if QT_CONFIG(shortcut) + Q_D(QQuickFolderBreadcrumbBar); + if (event->type() == QEvent::Shortcut) { + QShortcutEvent *shortcutEvent = static_cast<QShortcutEvent *>(event); + if (shortcutEvent->shortcutId() == d->editPathToggleShortcutId + || shortcutEvent->shortcutId() == d->editPathBackShortcutId + || shortcutEvent->shortcutId() == d->editPathEscapeShortcutId) { + d->toggleTextFieldVisibility(); + return true; + } else if (shortcutEvent->shortcutId() == d->goUpShortcutId) { + d->goUp(); + } + } +#endif + return QQuickItem::event(event); +} + +void QQuickFolderBreadcrumbBar::componentComplete() +{ + Q_D(QQuickFolderBreadcrumbBar); + qCDebug(lcFolderBreadcrumbBar) << "componentComplete"; + QQuickContainer::componentComplete(); + d->repopulate(); + + if (d->textField) { + // Force it to be updated as setTextField() is too early to do it. + d->textFieldVisibleChanged(); + } +} + +void QQuickFolderBreadcrumbBar::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) +{ + Q_D(QQuickFolderBreadcrumbBar); + QQuickContainer::itemChange(change, data); + + if (change == QQuickItem::ItemVisibleHasChanged && isComponentComplete()) { + if (data.boolValue && d->fileDialog->isVisible()) { + // It's visible. + d->handleTextFieldHidden(); + + d->goUpShortcutId = QGuiApplicationPrivate::instance()->shortcutMap.addShortcut( + this, QKeySequence(Qt::ALT | Qt::Key_Up), Qt::WindowShortcut, QQuickShortcutContext::matcher); + } else { + // It's hidden. + // Hide the text field so that when the dialog gets opened again, it's not still visible. + if (d->textField) + d->textField->setVisible(false); + + // Make the ListView visible again. + if (d->contentItem) + d->contentItem->setVisible(true); + + // We also need to ungrab all shortcuts when we're not visible. + d->ungrabEditPathShortcuts(); + + if (d->goUpShortcutId != 0) { + QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(d->goUpShortcutId, this); + d->goUpShortcutId = 0; + } + } + } +} + +bool QQuickFolderBreadcrumbBar::isContent(QQuickItem *item) const +{ + if (!qmlContext(item)) + return false; + + if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) + return false; + + return true; +} + +QFont QQuickFolderBreadcrumbBar::defaultFont() const +{ + // TODO + return QQuickTheme::font(QQuickTheme::TabBar); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickFolderBreadcrumbBar::accessibleRole() const +{ + // TODO + return QAccessible::PageTabList; +} +#endif + +QT_END_NAMESPACE diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qquickfolderbreadcrumbbar_p.h b/src/quickdialogs2/quickdialogs2quickimpl/qquickfolderbreadcrumbbar_p.h new file mode 100644 index 00000000..c04aa5a1 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qquickfolderbreadcrumbbar_p.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 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$ +** +****************************************************************************/ + +#ifndef QQUICKFOLDERBREADCRUMBBAR_P_H +#define QQUICKFOLDERBREADCRUMBBAR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQml/qqmlcomponent.h> +#include <QtQuickTemplates2/private/qquickcontainer_p.h> +#include <QtQuickTemplates2/private/qquicktextfield_p.h> + +#include "qquickfiledialogimpl_p.h" + +QT_BEGIN_NAMESPACE + +class QQuickFolderBreadcrumbBarPrivate; + +class Q_QUICKDIALOGS2QUICKIMPL_PRIVATE_EXPORT QQuickFolderBreadcrumbBar : public QQuickContainer +{ + Q_OBJECT + Q_PROPERTY(QQuickFileDialogImpl *fileDialog READ fileDialog WRITE setFileDialog NOTIFY fileDialogChanged) + Q_PROPERTY(QQmlComponent *buttonDelegate READ buttonDelegate WRITE setButtonDelegate NOTIFY buttonDelegateChanged) + Q_PROPERTY(QQmlComponent *separatorDelegate READ separatorDelegate WRITE setSeparatorDelegate NOTIFY separatorDelegateChanged) + Q_PROPERTY(QQuickAbstractButton *upButton READ upButton WRITE setUpButton NOTIFY upButtonChanged) + Q_PROPERTY(QQuickTextField *textField READ textField WRITE setTextField NOTIFY textFieldChanged) + Q_PROPERTY(int upButtonSpacing READ upButtonSpacing WRITE setUpButtonSpacing NOTIFY upButtonSpacingChanged) + QML_NAMED_ELEMENT(FolderBreadcrumbBar) + QML_ADDED_IN_VERSION(6, 2) + +public: + explicit QQuickFolderBreadcrumbBar(QQuickItem *parent = nullptr); + + QQuickFileDialogImpl *fileDialog() const; + void setFileDialog(QQuickFileDialogImpl *fileDialog); + + QQmlComponent *buttonDelegate(); + void setButtonDelegate(QQmlComponent *delegate); + + QQmlComponent *separatorDelegate(); + void setSeparatorDelegate(QQmlComponent *delegate); + + QQuickAbstractButton *upButton(); + void setUpButton(QQuickAbstractButton *upButton); + + int upButtonSpacing() const; + void setUpButtonSpacing(int upButtonSpacing); + + QQuickTextField *textField(); + void setTextField(QQuickTextField *textField); + +Q_SIGNALS: + void fileDialogChanged(); + void buttonDelegateChanged(); + void separatorDelegateChanged(); + void upButtonChanged(); + void upButtonSpacingChanged(); + void textFieldChanged(); + +protected: + bool event(QEvent *event) override; + + void componentComplete() override; + + void itemChange(ItemChange change, const ItemChangeData &data) override; + + bool isContent(QQuickItem *item) const override; + + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickFolderBreadcrumbBar) + Q_DECLARE_PRIVATE(QQuickFolderBreadcrumbBar) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickFolderBreadcrumbBar) + +#endif // QQUICKFOLDERBREADCRUMBBAR_P_H diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qquickfolderbreadcrumbbar_p_p.h b/src/quickdialogs2/quickdialogs2quickimpl/qquickfolderbreadcrumbbar_p_p.h new file mode 100644 index 00000000..e4afc251 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qquickfolderbreadcrumbbar_p_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 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$ +** +****************************************************************************/ + +#ifndef QQUICKFOLDERBREADCRUMBBAR_P_P_H +#define QQUICKFOLDERBREADCRUMBBAR_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontainer_p_p.h> +#include <QtQuickTemplates2/private/qquickdeferredexecute_p_p.h> + +#include "qquickfiledialogimpl_p.h" + +QT_BEGIN_NAMESPACE + +class QQuickAbstractButton; +class QQuickTextField; + +class Q_QUICKDIALOGS2QUICKIMPL_PRIVATE_EXPORT QQuickFolderBreadcrumbBarPrivate : public QQuickContainerPrivate +{ + Q_DECLARE_PUBLIC(QQuickFolderBreadcrumbBar) + +public: + QQuickItem *createDelegateItem(QQmlComponent *component, const QVariantMap &initialProperties); + static QString folderBaseName(const QString &folderPath); + static QStringList crumbPathsForFolder(const QUrl &folder); + void repopulate(); + void crumbClicked(); + void folderChanged(); + + void cancelUpButton(); + void executeUpButton(bool complete = false); + void goUp(); + + void cancelTextField(); + void executeTextField(bool complete = false); + void toggleTextFieldVisibility(); + void textFieldAccepted(); + + void textFieldVisibleChanged(); + void textFieldActiveFocusChanged(); + void handleTextFieldShown(); + void handleTextFieldHidden(); + void ungrabEditPathShortcuts(); + + qreal getContentWidth() const override; + qreal getContentHeight() const override; + void resizeContent() override; + + void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override; + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + +private: + QQuickFileDialogImpl *fileDialog = nullptr; + QList<QString> folderPaths; + QQmlComponent *buttonDelegate = nullptr; + QQmlComponent *separatorDelegate = nullptr; + QQuickDeferredPointer<QQuickAbstractButton> upButton; + QQuickDeferredPointer<QQuickTextField> textField; + int editPathToggleShortcutId = 0; + int editPathBackShortcutId = 0; + int editPathEscapeShortcutId = 0; + int goUpShortcutId = 0; + int upButtonSpacing = 0; + bool repopulating = false; +}; + +QT_END_NAMESPACE + +#endif // QQUICKFOLDERBREADCRUMBBAR_P_P_H diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qquickplatformfiledialog.cpp b/src/quickdialogs2/quickdialogs2quickimpl/qquickplatformfiledialog.cpp new file mode 100644 index 00000000..530149a0 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qquickplatformfiledialog.cpp @@ -0,0 +1,226 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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/qloggingcategory.h> +#include <QtGui/qwindow.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlinfo.h> +#include <QtQuick/qquickwindow.h> +#include <QtQuickDialogs2Utils/private/qquickfilenamefilter_p.h> +#include <QtQuickTemplates2/private/qquickdialog_p.h> +#include <QtQuickTemplates2/private/qquickpopup_p_p.h> +#include <QtQuickTemplates2/private/qquickpopupanchors_p.h> + +#include "qquickfiledialogimpl_p.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcQuickPlatformFileDialog, "qt.quick.dialogs.quickplatformfiledialog") + +/*! + \class QQuickPlatformFileDialog + \internal + + An interface that QQuickFileDialog can use to access the non-native Qt Quick FileDialog. + + Both this and the native implementations are created in QQuickAbstractDialog::create(). +*/ +QQuickPlatformFileDialog::QQuickPlatformFileDialog(QObject *parent) +{ + qCDebug(lcQuickPlatformFileDialog) << "creating non-native Qt Quick FileDialog with parent" << parent; + + // Set a parent so that we get deleted if we can't be shown for whatever reason. + // Our eventual parent should be the window, though. + setParent(parent); + + auto qmlContext = ::qmlContext(parent); + if (!qmlContext) { + qmlWarning(parent) << "No QQmlContext for QQuickPlatformFileDialog; can't create non-native FileDialog implementation"; + return; + } + + const auto dialogQmlUrl = QUrl(QStringLiteral("qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/qml/FileDialog.qml")); + QQmlComponent fileDialogComponent(qmlContext->engine(), dialogQmlUrl, parent); + if (!fileDialogComponent.isReady()) { + qmlWarning(parent) << "Failed to load non-native FileDialog implementation:\n" << fileDialogComponent.errorString(); + return; + } + m_dialog = qobject_cast<QQuickFileDialogImpl*>(fileDialogComponent.create()); + if (!m_dialog) { + qmlWarning(parent) << "Failed to create an instance of the non-native FileDialog:\n" << fileDialogComponent.errorString(); + return; + } + // Give it a parent until it's parented to the window in show(). + m_dialog->setParent(this); + + connect(m_dialog, &QQuickDialog::accepted, this, &QPlatformDialogHelper::accept); + connect(m_dialog, &QQuickDialog::rejected, this, &QPlatformDialogHelper::reject); + + connect(m_dialog, &QQuickFileDialogImpl::fileSelected, this, &QQuickPlatformFileDialog::fileSelected); + // TODO: add support for multiple file selection (QTBUG-92585) +// connect(m_dialog, &QQuickFileDialogImpl::filesSelected, [this](const QList<QString> &files) { +// QList<QUrl> urls; +// urls.reserve(files.count()); +// for (const QString &file : files) +// urls += QUrl::fromLocalFile(file); +// emit filesSelected(urls); +// }); + connect(m_dialog, &QQuickFileDialogImpl::currentFileChanged, this, &QQuickPlatformFileDialog::currentChanged); + connect(m_dialog, &QQuickFileDialogImpl::currentFolderChanged, this, &QQuickPlatformFileDialog::directoryEntered); + connect(m_dialog, &QQuickFileDialogImpl::filterSelected, this, &QQuickPlatformFileDialog::filterSelected); + + // We would do this in QQuickFileDialogImpl, but we need to ensure that folderChanged() + // is connected to directoryEntered() before setting it to ensure that the QQuickFileDialog is notified. + if (m_dialog->currentFolder().isEmpty()) + m_dialog->setCurrentFolder(QUrl::fromLocalFile(QDir().absolutePath())); +} + +bool QQuickPlatformFileDialog::isValid() const +{ + return m_dialog; +} + +bool QQuickPlatformFileDialog::defaultNameFilterDisables() const +{ + return false; +} + +void QQuickPlatformFileDialog::setDirectory(const QUrl &directory) +{ + if (!m_dialog) + return; + + m_dialog->setCurrentFolder(directory); +} + +QUrl QQuickPlatformFileDialog::directory() const +{ + if (!m_dialog) + return {}; + + return m_dialog->currentFolder(); +} + +void QQuickPlatformFileDialog::selectFile(const QUrl &file) +{ + if (!m_dialog) + return; + + m_dialog->setSelectedFile(file); +} + +QList<QUrl> QQuickPlatformFileDialog::selectedFiles() const +{ + // TODO: support for multiple selected files + return { m_dialog->currentFile() }; +} + +void QQuickPlatformFileDialog::setFilter() +{ +} + +void QQuickPlatformFileDialog::selectNameFilter(const QString &filter) +{ + // There is a bit of a problem with order here - QQuickFileDialog::onShow() + // is called before our show(), but it needs to set the selected filter + // (which we can't do in our show() because we don't know about QQuickFileDialog). + // So, delay setting the filter until we're shown. This shouldn't be an issue + // in practice, since it doesn't make sense for the filter to programmatically + // change while the dialog is visible. + m_pendingNameFilter = filter; +} + +QString QQuickPlatformFileDialog::selectedNameFilter() const +{ + return m_dialog->selectedNameFilter()->name(); +} + +void QQuickPlatformFileDialog::exec() +{ + qCWarning(lcQuickPlatformFileDialog) << "exec() is not supported for the Qt Quick FileDialog fallback"; +} + +bool QQuickPlatformFileDialog::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) +{ + qCDebug(lcQuickPlatformFileDialog) << "show called with flags" << flags << + "modality" << modality << "parent" << parent; + if (!m_dialog) + return false; + + if (!parent) + return false; + + auto quickWindow = qobject_cast<QQuickWindow*>(parent); + if (!quickWindow) { + qmlInfo(this->parent()) << "Parent window (" << parent << ") of non-native dialog is not a QQuickWindow"; + return false; + } + m_dialog->setParent(parent); + m_dialog->resetParentItem(); + + auto popupPrivate = QQuickPopupPrivate::get(m_dialog); + popupPrivate->getAnchors()->setCenterIn(m_dialog->parentItem()); + + QSharedPointer<QFileDialogOptions> options = QPlatformFileDialogHelper::options(); + m_dialog->setTitle(options->windowTitle()); + m_dialog->setOptions(options); + m_dialog->selectNameFilter(m_pendingNameFilter); + m_pendingNameFilter.clear(); + m_dialog->setAcceptLabel(options->isLabelExplicitlySet(QFileDialogOptions::Accept) + ? options->labelText(QFileDialogOptions::Accept) : QString()); + m_dialog->setRejectLabel(options->isLabelExplicitlySet(QFileDialogOptions::Reject) + ? options->labelText(QFileDialogOptions::Reject) : QString()); + + m_dialog->open(); + return true; +} + +void QQuickPlatformFileDialog::hide() +{ + if (!m_dialog) + return; + + m_dialog->close(); +} + +QQuickFileDialogImpl *QQuickPlatformFileDialog::dialog() const +{ + return m_dialog; +} + +QT_END_NAMESPACE diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qquickplatformfiledialog_p.h b/src/quickdialogs2/quickdialogs2quickimpl/qquickplatformfiledialog_p.h new file mode 100644 index 00000000..80463531 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qquickplatformfiledialog_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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$ +** +****************************************************************************/ + +#ifndef QQUICKPLATFORMFILEDIALOG_P_H +#define QQUICKPLATFORMFILEDIALOG_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/qpa/qplatformdialoghelper.h> + +#include "qtquickdialogs2quickimplglobal_p.h" + +QT_BEGIN_NAMESPACE + +class QQuickFileDialogImpl; +class QWindow; + +class Q_QUICKDIALOGS2QUICKIMPL_PRIVATE_EXPORT QQuickPlatformFileDialog : public QPlatformFileDialogHelper +{ + Q_OBJECT + +public: + explicit QQuickPlatformFileDialog(QObject *parent); + ~QQuickPlatformFileDialog() = default; + + bool isValid() const; + bool defaultNameFilterDisables() const override; + void setDirectory(const QUrl &directory) override; + QUrl directory() const override; + void selectFile(const QUrl &file) override; + QList<QUrl> selectedFiles() const override; + void setFilter() override; + void selectNameFilter(const QString &filter) override; + QString selectedNameFilter() const override; + + void exec() override; + bool show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) override; + void hide() override; + + QQuickFileDialogImpl *dialog() const; + +private: + QQuickFileDialogImpl *m_dialog = nullptr; + QString m_pendingNameFilter; +}; + +QT_END_NAMESPACE + +#endif // QQUICKPLATFORMFILEDIALOG_P_H diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qtquickdialogs2quickimplforeign_p.h b/src/quickdialogs2/quickdialogs2quickimpl/qtquickdialogs2quickimplforeign_p.h new file mode 100644 index 00000000..104199da --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qtquickdialogs2quickimplforeign_p.h @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 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$ +** +****************************************************************************/ + +#ifndef QTQUICKDIALOGS2QUICKIMPLFOREIGN_P_H +#define QTQUICKDIALOGS2QUICKIMPLFOREIGN_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQml/qqml.h> +#include <QtQuickDialogs2Utils/private/qquickfilenamefilter_p.h> +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> +#include <QtQuickTemplates2/private/qquickcontrol_p.h> +#include <QtQuickTemplates2/private/qquickdialog_p.h> +#include <QtQuickTemplates2/private/qquickicon_p.h> +#include <QtQuickTemplates2/private/qquickpopup_p.h> + +QT_BEGIN_NAMESPACE + +struct QQuickFileNameFilterForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QQuickFileNameFilter) + QML_ADDED_IN_VERSION(6, 2) +}; + +// TODO: remove these ones when not needed (QTBUG-88179) + +// verticalPadding, etc. +struct QQuickControlForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QQuickControl) + QML_ADDED_IN_VERSION(2, 0) +}; + +struct QQuickAbstractButtonForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QQuickAbstractButton) + QML_ADDED_IN_VERSION(2, 0) +}; + +struct QQuickIconForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QQuickIcon) + QML_ADDED_IN_VERSION(6, 2) +}; + +// For leftInset, etc. +struct QQuickPopupForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QQuickPopup) + QML_ADDED_IN_VERSION(2, 0) +}; + +struct QQuickDialogForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QQuickDialog) + QML_ADDED_IN_VERSION(2, 1) +}; + +QT_END_NAMESPACE + +#endif // QTQUICKDIALOGS2QUICKIMPLFOREIGN_P_H diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qtquickdialogs2quickimplglobal_p.h b/src/quickdialogs2/quickdialogs2quickimpl/qtquickdialogs2quickimplglobal_p.h new file mode 100644 index 00000000..799c9cd0 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2quickimpl/qtquickdialogs2quickimplglobal_p.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 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$ +** +****************************************************************************/ + +#ifndef QTQUICKDIALOGS2QUICKIMPLGLOBAL_P_H +#define QTQUICKDIALOGS2QUICKIMPLGLOBAL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qglobal.h> +#include <QtQml/private/qqmlglobal_p.h> + +QT_BEGIN_NAMESPACE + +#ifndef QT_STATIC +# if defined(QT_BUILD_QUICKDIALOGS2QUICKIMPL_LIB) +# define Q_QUICKDIALOGS2QUICKIMPL_PRIVATE_EXPORT Q_DECL_EXPORT +# else +# define Q_QUICKDIALOGS2QUICKIMPL_PRIVATE_EXPORT Q_DECL_IMPORT +# endif +#else +# define Q_QUICKDIALOGS2QUICKIMPL_PRIVATE_EXPORT +#endif + +QT_END_NAMESPACE + +Q_QUICKDIALOGS2QUICKIMPL_PRIVATE_EXPORT void qml_register_types_QtQuick_Dialogs_quick_impl(); + +#endif // QTQUICKDIALOGS2QUICKIMPLGLOBAL_P_H diff --git a/src/quickdialogs2/quickdialogs2utils/CMakeLists.txt b/src/quickdialogs2/quickdialogs2utils/CMakeLists.txt new file mode 100644 index 00000000..0242ffcf --- /dev/null +++ b/src/quickdialogs2/quickdialogs2utils/CMakeLists.txt @@ -0,0 +1,23 @@ +# This library exists because QuickDialogs2 and QuickDialogs2QuickImpl +# both need QQuickNameFilter. QQuickNameFilter was originally in +# QuickDialogs2. Since QuickDialogs2 already links to +# QuickDialogs2QuickImpl, making the latter link to the former (to get +# access to QQuickNameFilter) would result in a circular dependency, +# so we have this library as a result. + +qt_internal_add_module(QuickDialogs2Utils + SOURCES + qquickfilenamefilter.cpp + qquickfilenamefilter_p.h + qtquickdialogs2utilsglobal_p.h + DEFINES + QT_BUILD_QUICKDIALOGS2UTILS_LIB + QT_NO_CAST_FROM_ASCII + QT_NO_CAST_TO_ASCII + INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_SOURCE_DIR} + LIBRARIES + Qt::GuiPrivate + PUBLIC_LIBRARIES + Qt::Core +) diff --git a/src/quickdialogs2/quickdialogs2utils/qquickfilenamefilter.cpp b/src/quickdialogs2/quickdialogs2utils/qquickfilenamefilter.cpp new file mode 100644 index 00000000..8029977a --- /dev/null +++ b/src/quickdialogs2/quickdialogs2utils/qquickfilenamefilter.cpp @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 "qquickfilenamefilter_p.h" + +#include <QtCore/qloggingcategory.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcFileNameFilter, "qt.quick.dialogs.qquickfilenamefilter") + +QQuickFileNameFilter::QQuickFileNameFilter(QObject *parent) + : QObject(parent), m_index(-1) +{ +} + +int QQuickFileNameFilter::index() const +{ + return m_index; +} + +void QQuickFileNameFilter::setIndex(int index) +{ + if (m_index == index) + return; + + m_index = index; + emit indexChanged(index); +} + +QString QQuickFileNameFilter::name() const +{ + return m_name; +} + +QStringList QQuickFileNameFilter::extensions() const +{ + return m_extensions; +} + +QStringList QQuickFileNameFilter::globs() const +{ + return m_globs; +} + +QSharedPointer<QFileDialogOptions> QQuickFileNameFilter::options() const +{ + return m_options; +} + +void QQuickFileNameFilter::setOptions(const QSharedPointer<QFileDialogOptions> &options) +{ + m_options = options; +} + +static QString extractName(const QString &filter) +{ + return filter.left(filter.indexOf(QLatin1Char('(')) - 1); +} + +static QString extractExtension(QStringView filter) +{ + return filter.mid(filter.indexOf(QLatin1Char('.')) + 1).toString(); +} + +static void extractExtensionsAndGlobs(QStringView filter, QStringList &extensions, QStringList &globs) +{ + extensions.clear(); + globs.clear(); + + const int from = filter.indexOf(QLatin1Char('(')); + const int to = filter.lastIndexOf(QLatin1Char(')')) - 1; + if (from >= 0 && from < to) { + const QStringView ref = filter.mid(from + 1, to - from); + const QList<QStringView> exts = ref.split(QLatin1Char(' '), Qt::SkipEmptyParts); + // For example, given the filter "HTML files (*.html *.htm)", + // "ref" would be "*.html" and "*.htm". + for (const QStringView &ref : exts) { + extensions += extractExtension(ref); + globs += ref.toString(); + } + } +} + +void QQuickFileNameFilter::update(const QString &filter) +{ + const QStringList filters = nameFilters(); + + const int oldIndex = m_index; + const QString oldName = m_name; + const QStringList oldExtensions = m_extensions; + const QStringList oldGlobs = m_globs; + + m_index = filters.indexOf(filter); + m_name = extractName(filter); + extractExtensionsAndGlobs(filter, m_extensions, m_globs); + + if (oldIndex != m_index) + emit indexChanged(m_index); + if (oldName != m_name) + emit nameChanged(m_name); + if (oldExtensions != m_extensions) + emit extensionsChanged(m_extensions); + if (oldGlobs != m_globs) + emit globsChanged(m_globs); + + qCDebug(lcFileNameFilter).nospace() << "update called on " << this << " of " << parent() + << " with filter " << filter << " (current filters are " << filters << "):" + << "\n old index=" << oldIndex << "new index=" << m_index + << "\n old name=" << oldName << "new name=" << m_name + << "\n old extensions=" << oldExtensions << "new extensions=" << m_extensions + << "\n old glob=s" << oldGlobs << "new globs=" << m_globs; +} + +QStringList QQuickFileNameFilter::nameFilters() const +{ + return m_options ? m_options->nameFilters() : QStringList(); +} + +QString QQuickFileNameFilter::nameFilter(int index) const +{ + return m_options ? m_options->nameFilters().value(index) : QString(); +} + +QT_END_NAMESPACE diff --git a/src/quickdialogs2/quickdialogs2utils/qquickfilenamefilter_p.h b/src/quickdialogs2/quickdialogs2utils/qquickfilenamefilter_p.h new file mode 100644 index 00000000..61b9c07f --- /dev/null +++ b/src/quickdialogs2/quickdialogs2utils/qquickfilenamefilter_p.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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$ +** +****************************************************************************/ + +#ifndef QQUICKFILENAMEFILTER_P_H +#define QQUICKFILENAMEFILTER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qobject.h> +#include <QtCore/qsharedpointer.h> +#include <QtCore/qstringlist.h> +#include <QtGui/qpa/qplatformdialoghelper.h> + +#include "qtquickdialogs2utilsglobal_p.h" + +QT_BEGIN_NAMESPACE + +class Q_QUICKDIALOGS2UTILS_PRIVATE_EXPORT QQuickFileNameFilter : public QObject +{ + Q_OBJECT + Q_PROPERTY(int index READ index WRITE setIndex NOTIFY indexChanged FINAL) + Q_PROPERTY(QString name READ name NOTIFY nameChanged FINAL) + Q_PROPERTY(QStringList extensions READ extensions NOTIFY extensionsChanged FINAL) + Q_PROPERTY(QStringList globs READ globs NOTIFY globsChanged FINAL) + +public: + explicit QQuickFileNameFilter(QObject *parent = nullptr); + + int index() const; + void setIndex(int index); + + QString name() const; + QStringList extensions() const; + QStringList globs() const; + + QSharedPointer<QFileDialogOptions> options() const; + void setOptions(const QSharedPointer<QFileDialogOptions> &options); + + void update(const QString &filter); + +Q_SIGNALS: + void indexChanged(int index); + void nameChanged(const QString &name); + void extensionsChanged(const QStringList &extensions); + void globsChanged(const QStringList &globs); + +private: + QStringList nameFilters() const; + QString nameFilter(int index) const; + + int m_index; + QString m_name; + QStringList m_extensions; + QStringList m_globs; + QSharedPointer<QFileDialogOptions> m_options; +}; + +QT_END_NAMESPACE + +#endif // QQUICKFILENAMEFILTER_P_H diff --git a/src/quickdialogs2/quickdialogs2utils/qtquickdialogs2utilsglobal_p.h b/src/quickdialogs2/quickdialogs2utils/qtquickdialogs2utilsglobal_p.h new file mode 100644 index 00000000..7728db23 --- /dev/null +++ b/src/quickdialogs2/quickdialogs2utils/qtquickdialogs2utilsglobal_p.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 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$ +** +****************************************************************************/ + +#ifndef QTQUICKDIALOGS2UTILSGLOBAL_P_H +#define QTQUICKDIALOGS2UTILSGLOBAL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +#ifndef QT_STATIC +# if defined(QT_BUILD_QUICKDIALOGS2UTILS_LIB) +# define Q_QUICKDIALOGS2UTILS_PRIVATE_EXPORT Q_DECL_EXPORT +# else +# define Q_QUICKDIALOGS2UTILS_PRIVATE_EXPORT Q_DECL_IMPORT +# endif +#else +# define Q_QUICKDIALOGS2UTILS_PRIVATE_EXPORT +#endif + +QT_END_NAMESPACE + +#endif // QTQUICKDIALOGS2UTILSUTILSGLOBAL_P_H diff --git a/src/quicktemplates2/qquickdeferredexecute_p_p.h b/src/quicktemplates2/qquickdeferredexecute_p_p.h index 29d95ee9..55750a6a 100644 --- a/src/quicktemplates2/qquickdeferredexecute_p_p.h +++ b/src/quicktemplates2/qquickdeferredexecute_p_p.h @@ -50,6 +50,7 @@ #include <QtCore/qglobal.h> #include <QtQuickTemplates2/private/qquickdeferredpointer_p_p.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> #include <QtQml/private/qqmlvme_p.h> @@ -59,9 +60,9 @@ class QString; class QObject; namespace QtQuickPrivate { - void beginDeferred(QObject *object, const QString &property); - void cancelDeferred(QObject *object, const QString &property); - void completeDeferred(QObject *object, const QString &property); + Q_QUICKTEMPLATES2_PRIVATE_EXPORT void beginDeferred(QObject *object, const QString &property); + Q_QUICKTEMPLATES2_PRIVATE_EXPORT void cancelDeferred(QObject *object, const QString &property); + Q_QUICKTEMPLATES2_PRIVATE_EXPORT void completeDeferred(QObject *object, const QString &property); } template<typename T> diff --git a/src/quicktemplates2/qquickdialog.cpp b/src/quicktemplates2/qquickdialog.cpp index 6d8bd2ad..a6cb0274 100644 --- a/src/quicktemplates2/qquickdialog.cpp +++ b/src/quicktemplates2/qquickdialog.cpp @@ -158,6 +158,18 @@ QPlatformDialogHelper::ButtonRole QQuickDialogPrivate::buttonRole(QQuickAbstract return attached ? attached->buttonRole() : QPlatformDialogHelper::InvalidRole; } +void QQuickDialogPrivate::handleAccept() +{ + Q_Q(QQuickDialog); + q->accept(); +} + +void QQuickDialogPrivate::handleReject() +{ + Q_Q(QQuickDialog); + q->reject(); +} + void QQuickDialogPrivate::handleClick(QQuickAbstractButton *button) { Q_Q(QQuickDialog); @@ -180,16 +192,21 @@ void QQuickDialogPrivate::handleClick(QQuickAbstractButton *button) } QQuickDialog::QQuickDialog(QObject *parent) - : QQuickPopup(*(new QQuickDialogPrivate), parent) + : QQuickDialog(*(new QQuickDialogPrivate), parent) +{ +} + +QQuickDialog::QQuickDialog(QQuickDialogPrivate &dd, QObject *parent) + : QQuickPopup(dd, parent) { Q_D(QQuickDialog); - connect(d->popupItem, &QQuickPopupItem::titleChanged, this, &QQuickDialog::titleChanged); - connect(d->popupItem, &QQuickPopupItem::headerChanged, this, &QQuickDialog::headerChanged); - connect(d->popupItem, &QQuickPopupItem::footerChanged, this, &QQuickDialog::footerChanged); - connect(d->popupItem, &QQuickPopupItem::implicitHeaderWidthChanged, this, &QQuickDialog::implicitHeaderWidthChanged); - connect(d->popupItem, &QQuickPopupItem::implicitHeaderHeightChanged, this, &QQuickDialog::implicitHeaderHeightChanged); - connect(d->popupItem, &QQuickPopupItem::implicitFooterWidthChanged, this, &QQuickDialog::implicitFooterWidthChanged); - connect(d->popupItem, &QQuickPopupItem::implicitFooterHeightChanged, this, &QQuickDialog::implicitFooterHeightChanged); + QObject::connect(d->popupItem, &QQuickPopupItem::titleChanged, this, &QQuickDialog::titleChanged); + QObject::connect(d->popupItem, &QQuickPopupItem::headerChanged, this, &QQuickDialog::headerChanged); + QObject::connect(d->popupItem, &QQuickPopupItem::footerChanged, this, &QQuickDialog::footerChanged); + QObject::connect(d->popupItem, &QQuickPopupItem::implicitHeaderWidthChanged, this, &QQuickDialog::implicitHeaderWidthChanged); + QObject::connect(d->popupItem, &QQuickPopupItem::implicitHeaderHeightChanged, this, &QQuickDialog::implicitHeaderHeightChanged); + QObject::connect(d->popupItem, &QQuickPopupItem::implicitFooterWidthChanged, this, &QQuickDialog::implicitFooterWidthChanged); + QObject::connect(d->popupItem, &QQuickPopupItem::implicitFooterHeightChanged, this, &QQuickDialog::implicitFooterHeightChanged); } /*! @@ -251,16 +268,16 @@ void QQuickDialog::setHeader(QQuickItem *header) return; if (QQuickDialogButtonBox *buttonBox = qobject_cast<QQuickDialogButtonBox *>(oldHeader)) { - disconnect(buttonBox, &QQuickDialogButtonBox::accepted, this, &QQuickDialog::accept); - disconnect(buttonBox, &QQuickDialogButtonBox::rejected, this, &QQuickDialog::reject); + QObjectPrivate::disconnect(buttonBox, &QQuickDialogButtonBox::accepted, d, &QQuickDialogPrivate::handleAccept); + QObjectPrivate::disconnect(buttonBox, &QQuickDialogButtonBox::rejected, d, &QQuickDialogPrivate::handleReject); QObjectPrivate::disconnect(buttonBox, &QQuickDialogButtonBox::clicked, d, &QQuickDialogPrivate::handleClick); if (d->buttonBox == buttonBox) d->buttonBox = nullptr; } if (QQuickDialogButtonBox *buttonBox = qobject_cast<QQuickDialogButtonBox *>(header)) { - connect(buttonBox, &QQuickDialogButtonBox::accepted, this, &QQuickDialog::accept); - connect(buttonBox, &QQuickDialogButtonBox::rejected, this, &QQuickDialog::reject); + QObjectPrivate::connect(buttonBox, &QQuickDialogButtonBox::accepted, d, &QQuickDialogPrivate::handleAccept); + QObjectPrivate::connect(buttonBox, &QQuickDialogButtonBox::rejected, d, &QQuickDialogPrivate::handleReject); QObjectPrivate::connect(buttonBox, &QQuickDialogButtonBox::clicked, d, &QQuickDialogPrivate::handleClick); d->buttonBox = buttonBox; buttonBox->setStandardButtons(d->standardButtons); @@ -299,15 +316,15 @@ void QQuickDialog::setFooter(QQuickItem *footer) return; if (QQuickDialogButtonBox *buttonBox = qobject_cast<QQuickDialogButtonBox *>(oldFooter)) { - disconnect(buttonBox, &QQuickDialogButtonBox::accepted, this, &QQuickDialog::accept); - disconnect(buttonBox, &QQuickDialogButtonBox::rejected, this, &QQuickDialog::reject); + QObjectPrivate::disconnect(buttonBox, &QQuickDialogButtonBox::accepted, d, &QQuickDialogPrivate::handleAccept); + QObjectPrivate::disconnect(buttonBox, &QQuickDialogButtonBox::rejected, d, &QQuickDialogPrivate::handleReject); QObjectPrivate::disconnect(buttonBox, &QQuickDialogButtonBox::clicked, d, &QQuickDialogPrivate::handleClick); if (d->buttonBox == buttonBox) d->buttonBox = nullptr; } if (QQuickDialogButtonBox *buttonBox = qobject_cast<QQuickDialogButtonBox *>(footer)) { - connect(buttonBox, &QQuickDialogButtonBox::accepted, this, &QQuickDialog::accept); - connect(buttonBox, &QQuickDialogButtonBox::rejected, this, &QQuickDialog::reject); + QObjectPrivate::connect(buttonBox, &QQuickDialogButtonBox::accepted, d, &QQuickDialogPrivate::handleAccept); + QObjectPrivate::connect(buttonBox, &QQuickDialogButtonBox::rejected, d, &QQuickDialogPrivate::handleReject); QObjectPrivate::connect(buttonBox, &QQuickDialogButtonBox::clicked, d, &QQuickDialogPrivate::handleClick); d->buttonBox = buttonBox; buttonBox->setStandardButtons(d->standardButtons); diff --git a/src/quicktemplates2/qquickdialog_p.h b/src/quicktemplates2/qquickdialog_p.h index 660da081..b09375ce 100644 --- a/src/quicktemplates2/qquickdialog_p.h +++ b/src/quicktemplates2/qquickdialog_p.h @@ -129,6 +129,8 @@ Q_SIGNALS: void implicitFooterHeightChanged(); protected: + QQuickDialog(QQuickDialogPrivate &dd, QObject *parent); + #if QT_CONFIG(accessibility) QAccessible::Role accessibleRole() const override; void accessibilityActiveChanged(bool active) override; diff --git a/src/quicktemplates2/qquickdialog_p_p.h b/src/quicktemplates2/qquickdialog_p_p.h index 20dce56c..eb40c918 100644 --- a/src/quicktemplates2/qquickdialog_p_p.h +++ b/src/quicktemplates2/qquickdialog_p_p.h @@ -57,7 +57,7 @@ QT_BEGIN_NAMESPACE class QQuickAbstractButton; class QQuickDialogButtonBox; -class QQuickDialogPrivate : public QQuickPopupPrivate +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDialogPrivate : public QQuickPopupPrivate { Q_DECLARE_PUBLIC(QQuickDialog) @@ -69,7 +69,9 @@ public: static QPlatformDialogHelper::ButtonRole buttonRole(QQuickAbstractButton *button); - void handleClick(QQuickAbstractButton *button); + virtual void handleAccept(); + virtual void handleReject(); + virtual void handleClick(QQuickAbstractButton *button); int result = 0; QString title; diff --git a/src/quicktemplates2/qquickdialogbuttonbox.cpp b/src/quicktemplates2/qquickdialogbuttonbox.cpp index d6ed366f..447d3a2d 100644 --- a/src/quicktemplates2/qquickdialogbuttonbox.cpp +++ b/src/quicktemplates2/qquickdialogbuttonbox.cpp @@ -396,6 +396,11 @@ void QQuickDialogButtonBoxPrivate::handleClick() } } +QString QQuickDialogButtonBoxPrivate::buttonText(QPlatformDialogHelper::StandardButton standardButton) +{ + return QPlatformTheme::removeMnemonics(QGuiApplicationPrivate::platformTheme()->standardButtonText(standardButton)); +} + QQuickAbstractButton *QQuickDialogButtonBoxPrivate::createStandardButton(QPlatformDialogHelper::StandardButton standardButton) { Q_Q(QQuickDialogButtonBox); @@ -414,7 +419,7 @@ QQuickAbstractButton *QQuickDialogButtonBoxPrivate::createStandardButton(QPlatfo QQuickDialogButtonBoxAttached *attached = qobject_cast<QQuickDialogButtonBoxAttached *>(qmlAttachedPropertiesObject<QQuickDialogButtonBox>(button, true)); QQuickDialogButtonBoxAttachedPrivate::get(attached)->standardButton = standardButton; attached->setButtonRole(QPlatformDialogHelper::buttonRole(standardButton)); - button->setText(QPlatformTheme::removeMnemonics(QGuiApplicationPrivate::platformTheme()->standardButtonText(standardButton))); + button->setText(buttonText(standardButton)); delegate->completeCreate(); return button; } @@ -457,8 +462,7 @@ void QQuickDialogButtonBoxPrivate::updateLanguage() const QPlatformDialogHelper::StandardButton standardButton = boxAttachedPrivate->standardButton; // The button might be a custom one with explicitly specified text, so we shouldn't change it in that case. if (standardButton != QPlatformDialogHelper::NoButton) { - const QString buttonText = QGuiApplicationPrivate::platformTheme()->standardButtonText(standardButton); - button->setText(QPlatformTheme::removeMnemonics(buttonText)); + button->setText(buttonText(standardButton)); } } --i; diff --git a/src/quicktemplates2/qquickdialogbuttonbox_p_p.h b/src/quicktemplates2/qquickdialogbuttonbox_p_p.h index 22da9e6d..5e08b2ad 100644 --- a/src/quicktemplates2/qquickdialogbuttonbox_p_p.h +++ b/src/quicktemplates2/qquickdialogbuttonbox_p_p.h @@ -53,7 +53,7 @@ QT_BEGIN_NAMESPACE -class QQuickDialogButtonBoxPrivate : public QQuickContainerPrivate +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDialogButtonBoxPrivate : public QQuickContainerPrivate { Q_DECLARE_PUBLIC(QQuickDialogButtonBox) @@ -75,6 +75,8 @@ public: void handleClick(); + static QString buttonText(QPlatformDialogHelper::StandardButton standardButton); + QQuickAbstractButton *createStandardButton(QPlatformDialogHelper::StandardButton button); void removeStandardButtons(); diff --git a/src/quicktemplates2/qquickitemdelegate_p_p.h b/src/quicktemplates2/qquickitemdelegate_p_p.h index f712a212..d7604fbe 100644 --- a/src/quicktemplates2/qquickitemdelegate_p_p.h +++ b/src/quicktemplates2/qquickitemdelegate_p_p.h @@ -52,7 +52,7 @@ QT_BEGIN_NAMESPACE -class QQuickItemDelegatePrivate : public QQuickAbstractButtonPrivate +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickItemDelegatePrivate : public QQuickAbstractButtonPrivate { Q_DECLARE_PUBLIC(QQuickItemDelegate) diff --git a/src/quicktemplates2/qquickpopup.cpp b/src/quicktemplates2/qquickpopup.cpp index 292d52a3..44d0d8f4 100644 --- a/src/quicktemplates2/qquickpopup.cpp +++ b/src/quicktemplates2/qquickpopup.cpp @@ -44,6 +44,7 @@ #include "qquickcontrol_p_p.h" #include "qquickdialog_p.h" +#include <QtCore/qloggingcategory.h> #include <QtQml/qqmlinfo.h> #include <QtQuick/qquickitem.h> #include <QtQuick/private/qquicktransition_p.h> @@ -52,6 +53,7 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcDimmer, "qt.quick.controls.popup.dimmer") +Q_LOGGING_CATEGORY(lcPopup, "qt.quick.controls.popup") /*! \qmltype Popup @@ -2450,6 +2452,7 @@ void QQuickPopup::classBegin() void QQuickPopup::componentComplete() { Q_D(QQuickPopup); + qCDebug(lcPopup) << "componentComplete"; if (!parentItem()) resetParentItem(); diff --git a/src/quicktemplates2/qquickpopup_p_p.h b/src/quicktemplates2/qquickpopup_p_p.h index 57183011..5ce1d291 100644 --- a/src/quicktemplates2/qquickpopup_p_p.h +++ b/src/quicktemplates2/qquickpopup_p_p.h @@ -68,7 +68,7 @@ class QQuickPopupItem; class QQuickPopupPrivate; class QQuickPopupPositioner; -class QQuickPopupTransitionManager : public QQuickTransitionManager +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopupTransitionManager : public QQuickTransitionManager { public: QQuickPopupTransitionManager(QQuickPopupPrivate *popup); @@ -83,7 +83,7 @@ private: QQuickPopupPrivate *popup = nullptr; }; -class Q_AUTOTEST_EXPORT QQuickPopupPrivate +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopupPrivate : public QObjectPrivate , public QQuickItemChangeListener , public QQuickPaletteProviderPrivateBase<QQuickPopup, QQuickPopupPrivate> diff --git a/src/quicktemplates2/qquickpopupitem_p.h b/src/quicktemplates2/qquickpopupitem_p.h index db444ddc..2947a6e3 100644 --- a/src/quicktemplates2/qquickpopupitem_p.h +++ b/src/quicktemplates2/qquickpopupitem_p.h @@ -56,7 +56,7 @@ QT_BEGIN_NAMESPACE class QQuickPopup; class QQuickPopupItemPrivate; -class QQuickPopupItem : public QQuickPage +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopupItem : public QQuickPage { Q_OBJECT diff --git a/src/quicktemplates2/qquickpopupitem_p_p.h b/src/quicktemplates2/qquickpopupitem_p_p.h index b3224be7..48b35aa2 100644 --- a/src/quicktemplates2/qquickpopupitem_p_p.h +++ b/src/quicktemplates2/qquickpopupitem_p_p.h @@ -54,7 +54,7 @@ QT_BEGIN_NAMESPACE class QQuickPopup; -class QQuickPopupItemPrivate : public QQuickPagePrivate +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopupItemPrivate : public QQuickPagePrivate { Q_DECLARE_PUBLIC(QQuickPopupItem) diff --git a/sync.profile b/sync.profile index dc6ea940..cfc77d99 100644 --- a/sync.profile +++ b/sync.profile @@ -1,6 +1,9 @@ %modules = ( # path to module name map "QtQuickControls2" => "$basedir/src/quickcontrols2", "QtQuickControls2Impl" => "$basedir/src/quickcontrols2impl", + "QtQuickDialogs2" => "$basedir/src/quickdialogs2/quickdialogs2", + "QtQuickDialogs2Utils" => "$basedir/src/quickdialogs2/quickdialogs2utils", + "QtQuickDialogs2QuickImpl" => "$basedir/src/quickdialogs2/quickdialogs2quickimpl", "QtQuickTemplates2" => "$basedir/src/quicktemplates2", ); %moduleheaders = ( # restrict the module headers to those found in relative path diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index 5d7302c3..ef299bb7 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(controls) add_subdirectory(cursor) add_subdirectory(customization) add_subdirectory(designer) +add_subdirectory(dialogs) add_subdirectory(focus) add_subdirectory(font) add_subdirectory(palette) diff --git a/tests/auto/dialogs/CMakeLists.txt b/tests/auto/dialogs/CMakeLists.txt new file mode 100644 index 00000000..26eb933f --- /dev/null +++ b/tests/auto/dialogs/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(qquickfiledialogimpl) diff --git a/tests/auto/dialogs/dialogs.pro b/tests/auto/dialogs/dialogs.pro new file mode 100644 index 00000000..004641ec --- /dev/null +++ b/tests/auto/dialogs/dialogs.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs +SUBDIRS += \ + qquickfiledialogimpl diff --git a/tests/auto/dialogs/qquickfiledialogimpl/BLACKLIST b/tests/auto/dialogs/qquickfiledialogimpl/BLACKLIST new file mode 100644 index 00000000..822b92dd --- /dev/null +++ b/tests/auto/dialogs/qquickfiledialogimpl/BLACKLIST @@ -0,0 +1,7 @@ +# QTBUG-92094 +[tabFocusNavigation] +* + +# QTBUG-92585 +[fileMode:OpenFiles] +* diff --git a/tests/auto/dialogs/qquickfiledialogimpl/CMakeLists.txt b/tests/auto/dialogs/qquickfiledialogimpl/CMakeLists.txt new file mode 100644 index 00000000..631cb9e8 --- /dev/null +++ b/tests/auto/dialogs/qquickfiledialogimpl/CMakeLists.txt @@ -0,0 +1,40 @@ +# Collect test data +file(GLOB_RECURSE test_data_glob + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + data/*) +list(APPEND test_data ${test_data_glob}) + +qt_internal_add_test(tst_qquickfiledialogimpl + SOURCES + ../../shared/qtest_quickcontrols.h + ../../shared/util.cpp ../../shared/util.h + ../../shared/visualtestutil.cpp ../../shared/visualtestutil.h + tst_qquickfiledialogimpl.cpp + DEFINES + QQC2_IMPORT_PATH=\\\"${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/imports\\\" + PUBLIC_LIBRARIES + Qt::CorePrivate + Qt::Gui + Qt::GuiPrivate + Qt::QmlPrivate + Qt::QuickControls2 + Qt::QuickControls2Private + Qt::QuickDialogs2Private + Qt::QuickDialogs2QuickImplPrivate + Qt::QuickDialogs2UtilsPrivate + Qt::QuickPrivate + Qt::QuickTemplates2Private + Qt::QuickTest + Qt::TestPrivate + TESTDATA ${test_data} +) + +qt_internal_extend_target(tst_qquickfiledialogimpl CONDITION ANDROID OR IOS + DEFINES + QT_QMLTEST_DATADIR=\\\":/data\\\" +) + +qt_internal_extend_target(tst_qquickfiledialogimpl CONDITION NOT ANDROID AND NOT IOS + DEFINES + QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/data\\\" +) diff --git a/tests/auto/dialogs/qquickfiledialogimpl/data/acceptRejectLabel.qml b/tests/auto/dialogs/qquickfiledialogimpl/data/acceptRejectLabel.qml new file mode 100644 index 00000000..3cd526e3 --- /dev/null +++ b/tests/auto/dialogs/qquickfiledialogimpl/data/acceptRejectLabel.qml @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +ApplicationWindow { + id: window + width: 640 + height: 480 + + property alias dialog: dialog + + FileDialog { + id: dialog + acceptLabel: "AcceptTest" + rejectLabel: "RejectTest" + } +} diff --git a/tests/auto/dialogs/qquickfiledialogimpl/data/bindAllTxtHtmlNameFilters.qml b/tests/auto/dialogs/qquickfiledialogimpl/data/bindAllTxtHtmlNameFilters.qml new file mode 100644 index 00000000..5010c422 --- /dev/null +++ b/tests/auto/dialogs/qquickfiledialogimpl/data/bindAllTxtHtmlNameFilters.qml @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +ApplicationWindow { + width: 640 + height: 480 + + property alias dialog: dialog + + FileDialog { + id: dialog + objectName: "FileDialog" + nameFilters: ["All files (*)", "Text files (*.txt)", "HTML files (*.html)"] + } +} diff --git a/tests/auto/dialogs/qquickfiledialogimpl/data/bindCurrentFolder.qml b/tests/auto/dialogs/qquickfiledialogimpl/data/bindCurrentFolder.qml new file mode 100644 index 00000000..03cd61bc --- /dev/null +++ b/tests/auto/dialogs/qquickfiledialogimpl/data/bindCurrentFolder.qml @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +ApplicationWindow { + id: window + width: 640 + height: 480 + + property alias dialog: dialog + required property url initialFolder + + FileDialog { + id: dialog + objectName: "FileDialog" + currentFolder: window.initialFolder + } +} diff --git a/tests/auto/dialogs/qquickfiledialogimpl/data/bindTitle.qml b/tests/auto/dialogs/qquickfiledialogimpl/data/bindTitle.qml new file mode 100644 index 00000000..60657f58 --- /dev/null +++ b/tests/auto/dialogs/qquickfiledialogimpl/data/bindTitle.qml @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +ApplicationWindow { + width: 640 + height: 480 + + property alias dialog: dialog + + FileDialog { + id: dialog + title: "Test Title" + } +} diff --git a/tests/auto/dialogs/qquickfiledialogimpl/data/bindTxtHtmlNameFilters.qml b/tests/auto/dialogs/qquickfiledialogimpl/data/bindTxtHtmlNameFilters.qml new file mode 100644 index 00000000..1e312fbc --- /dev/null +++ b/tests/auto/dialogs/qquickfiledialogimpl/data/bindTxtHtmlNameFilters.qml @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +ApplicationWindow { + width: 640 + height: 480 + + property alias dialog: dialog + + FileDialog { + id: dialog + objectName: "FileDialog" + nameFilters: ["Text files (*.txt)", "HTML files (*.html)"] + } +} diff --git a/tests/auto/dialogs/qquickfiledialogimpl/data/fileDialog.qml b/tests/auto/dialogs/qquickfiledialogimpl/data/fileDialog.qml new file mode 100644 index 00000000..febad45c --- /dev/null +++ b/tests/auto/dialogs/qquickfiledialogimpl/data/fileDialog.qml @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +ApplicationWindow { + width: 640 + height: 480 + + property alias dialog: dialog + + function doneAccepted() { + dialog.done(FileDialog.Accepted) + } + + function doneRejected() { + dialog.done(FileDialog.Rejected) + } + + FileDialog { + id: dialog + objectName: "FileDialog" + } +} diff --git a/tests/auto/dialogs/qquickfiledialogimpl/qquickfiledialogimpl.pro b/tests/auto/dialogs/qquickfiledialogimpl/qquickfiledialogimpl.pro new file mode 100644 index 00000000..3f6e83d7 --- /dev/null +++ b/tests/auto/dialogs/qquickfiledialogimpl/qquickfiledialogimpl.pro @@ -0,0 +1,14 @@ +CONFIG += testcase +TARGET = tst_qquickfiledialogimpl +SOURCES += tst_qquickfiledialogimpl.cpp + +macos:CONFIG -= app_bundle + +QT += core-private gui-private testlib qml-private quick-private qmltest quicktemplates2-private + +include (../../shared/util.pri) + +TESTDATA = data/* + +OTHER_FILES += \ + data/*.qml diff --git a/tests/auto/dialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp b/tests/auto/dialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp new file mode 100644 index 00000000..a8fe4c15 --- /dev/null +++ b/tests/auto/dialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp @@ -0,0 +1,1349 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite 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 "../../shared/dialogtestutil.h" +#include "../../shared/util.h" +#include "../../shared/visualtestutil.h" + +#include <QtTest/qtest.h> +#include <QtTest/qsignalspy.h> +#include <QtQml/qqmlfile.h> +#include <QtQuick/private/qquicklistview_p.h> +#include <QtQuickTest/quicktest.h> +#include <QtQuickDialogs2/private/qquickfiledialog_p.h> +#include <QtQuickDialogs2QuickImpl/private/qquickplatformfiledialog_p.h> +#include <QtQuickDialogs2QuickImpl/private/qquickfiledialogdelegate_p.h> +#include <QtQuickDialogs2QuickImpl/private/qquickfolderbreadcrumbbar_p.h> +#include <QtQuickDialogs2QuickImpl/private/qquickfolderbreadcrumbbar_p_p.h> +#include <QtQuickDialogs2Utils/private/qquickfilenamefilter_p.h> +#include <QtQuickTemplates2/private/qquickapplicationwindow_p.h> +#include <QtQuickTemplates2/private/qquickcombobox_p.h> +#include <QtQuickTemplates2/private/qquickdialogbuttonbox_p.h> +#include <QtQuickTemplates2/private/qquickdialogbuttonbox_p_p.h> +#include <QtQuickTemplates2/private/qquicklabel_p.h> +#include <QtQuickTemplates2/private/qquickoverlay_p.h> + +using namespace QQuickDialogTestUtil; +using namespace QQuickVisualTestUtil; + +class tst_QQuickFileDialogImpl : public QQmlDataTest +{ + Q_OBJECT + +private slots: + void initTestCase() override; + void cleanupTestCase(); + + void defaults(); + void chooseFileViaStandardButtons(); + void chooseFileViaDoubleClick(); + void chooseFileViaTextEdit(); + void chooseFileViaEnter(); + void bindCurrentFolder_data(); + void bindCurrentFolder(); + void changeFolderViaStandardButtons(); + void changeFolderViaDoubleClick(); + void chooseFolderViaTextEdit(); + void chooseFolderViaEnter(); + void cancelDialogWhileTextEditHasFocus(); + void goUp(); + void goUpWhileTextEditHasFocus(); + void goIntoLargeFolder(); + void keyAndShortcutHandling(); + void bindNameFilters(); + void changeNameFilters(); + void changeNameFiltersAfterChangingFolder(); + void tabFocusNavigation(); + void acceptRejectLabel(); + void bindTitle(); + void itemsDisabledWhenNecessary(); + void fileMode_data(); + void fileMode(); + void defaultSuffix_data(); + void defaultSuffix(); + void done_data(); + void done(); + +private: + QQuickAbstractButton *findDialogButton(QQuickDialogButtonBox *box, const QString &buttonText); + void enterText(QWindow *window, const QString &textToEnter); + + QTemporaryDir tempDir; + QScopedPointer<QFile> tempFile1; + QScopedPointer<QFile> tempFile2; + QDir tempSubDir; + QDir tempSubSubDir; + QScopedPointer<QFile> tempSubFile1; + QScopedPointer<QFile> tempSubFile2; + QDir oldCurrentDir; +}; + +QQuickAbstractButton *tst_QQuickFileDialogImpl::findDialogButton(QQuickDialogButtonBox *box, const QString &buttonText) +{ + for (int i = 0; i < box->count(); ++i) { + auto button = qobject_cast<QQuickAbstractButton*>(box->itemAt(i)); + if (button && button->text().toUpper() == buttonText.toUpper()) + return button; + } + return nullptr; +} + +void tst_QQuickFileDialogImpl::enterText(QWindow *window, const QString &textToEnter) +{ + for (int i = 0; i < textToEnter.size(); ++i) { + const QChar key = textToEnter.at(i); + QTest::keyClick(window, key.toLatin1()); + } +} + +void tst_QQuickFileDialogImpl::initTestCase() +{ + QQmlDataTest::initTestCase(); + + QVERIFY(tempDir.isValid()); + // QTEST_QUICKCONTROLS_MAIN constructs the test case object once, + // and then calls qRun() for each style, and qRun() calls initTestCase(). + // So, we need to check if we've already made the temporary directory. + // Note that this is only necessary if the test is run with more than one style. + if (!QDir(tempDir.path()).isEmpty()) + return; + + /* + Create a couple of files within the temporary directory. The structure is: + + [temp directory] + ├── sub-dir + │ ├── sub-sub-dir + │ ├── sub-file1.txt + │ └── sub-file2.txt + ├── file1.txt + └── file2.txt + */ + tempSubDir = QDir(tempDir.path()); + QVERIFY2(tempSubDir.mkdir("sub-dir"), qPrintable(QString::fromLatin1( + "Failed to make sub-directory \"sub-dir\" in %1. Permissions are: %2") + .arg(tempDir.path()).arg(QDebug::toString(QFileInfo(tempDir.path()).permissions())))); + QVERIFY(tempSubDir.cd("sub-dir")); + + tempSubSubDir = QDir(tempSubDir.path()); + QVERIFY2(tempSubSubDir.mkdir("sub-sub-dir"), qPrintable(QString::fromLatin1( + "Failed to make sub-directory \"sub-sub-dir\" in %1. Permissions are: %2") + .arg(tempSubDir.path()).arg(QDebug::toString(QFileInfo(tempSubDir.path()).permissions())))); + QVERIFY(tempSubSubDir.cd("sub-sub-dir")); + + tempSubFile1.reset(new QFile(tempSubDir.path() + "/sub-file1.txt")); + QVERIFY(tempSubFile1->open(QIODevice::ReadWrite)); + + tempSubFile2.reset(new QFile(tempSubDir.path() + "/sub-file2.txt")); + QVERIFY(tempSubFile2->open(QIODevice::ReadWrite)); + + tempFile1.reset(new QFile(tempDir.path() + "/file1.txt")); + QVERIFY(tempFile1->open(QIODevice::ReadWrite)); + + tempFile2.reset(new QFile(tempDir.path() + "/file2.txt")); + QVERIFY(tempFile2->open(QIODevice::ReadWrite)); + + // Ensure that each test starts off in the temporary directory. + oldCurrentDir = QDir::current(); + QDir::setCurrent(tempDir.path()); +} + +void tst_QQuickFileDialogImpl::cleanupTestCase() +{ + // Just in case... + QDir::setCurrent(oldCurrentDir.path()); +} + +bool verifyFileDialogDelegates(QQuickListView *fileDialogListView, const QStringList &expectedFiles, + QString &failureMessage) +{ + if (QQuickTest::qIsPolishScheduled(fileDialogListView)) { + if (!QQuickTest::qWaitForItemPolished(fileDialogListView)) { + failureMessage = QLatin1String("Failed to polish fileDialogListView"); + return false; + } + } + + QStringList actualFiles; + for (int i = 0; i < fileDialogListView->count(); ++i) { + auto delegate = qobject_cast<QQuickFileDialogDelegate*>(findViewDelegateItem(fileDialogListView, i)); + if (!delegate) { + failureMessage = QString::fromLatin1("Delegate at index %1 is null").arg(i); + return false; + } + + // Need to call absoluteFilePath on Windows; see comment in dialogtestutil.h. + actualFiles.append(QFileInfo(delegate->file().toLocalFile()).absoluteFilePath()); + } + + if (actualFiles != expectedFiles) { + failureMessage = QString::fromLatin1("Mismatch in actual vs expected " + "delegates in fileDialogListView:\n expected: %1\n actual: %2") + .arg(QDebug::toString(expectedFiles)).arg(QDebug::toString(actualFiles)); + return false; + } + + return true; +} + +bool verifyBreadcrumbDelegates(QQuickFolderBreadcrumbBar *breadcrumbBar, const QUrl &expectedFolder, + QString &failureMessage) +{ + if (!breadcrumbBar) { + failureMessage = QLatin1String("breadcrumbBar is null"); + return false; + } + + auto breadcrumbBarListView = qobject_cast<QQuickListView*>(breadcrumbBar->contentItem()); + if (!breadcrumbBarListView) { + failureMessage = QLatin1String("breadcrumbBar's ListView is null"); + return false; + } + + if (QQuickTest::qIsPolishScheduled(breadcrumbBarListView)) { + if (!QQuickTest::qWaitForItemPolished(breadcrumbBarListView)) { + failureMessage = QLatin1String("Failed to polish breadcrumbBarListView"); + return false; + } + } + + QStringList actualCrumbs; + for (int i = 0; i < breadcrumbBarListView->count(); ++i) { + auto delegate = qobject_cast<QQuickAbstractButton*>(findViewDelegateItem(breadcrumbBarListView, i)); + if (!delegate) { + // It's a separator or some other non-crumb item. + continue; + } + + actualCrumbs.append(delegate->text()); + } + + QStringList expectedCrumbs = QQuickFolderBreadcrumbBarPrivate::crumbPathsForFolder(expectedFolder); + for (int i = 0; i < expectedCrumbs.size(); ++i) { + QString &crumbPath = expectedCrumbs[i]; + crumbPath = QQuickFolderBreadcrumbBarPrivate::folderBaseName(crumbPath); + } + + if (actualCrumbs != expectedCrumbs) { + failureMessage = QString::fromLatin1("Mismatch in actual vs expected " + "delegates in breadcrumbBarListView:\n expected: %1\n actual: %2") + .arg(QDebug::toString(expectedCrumbs)).arg(QDebug::toString(actualCrumbs)); + return false; + } + + return true; +} + +void tst_QQuickFileDialogImpl::defaults() +{ + QQuickApplicationHelper helper(this, "fileDialog.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + + QQuickWindow *window = helper.window; + window->show(); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickFileDialog *dialog = window->property("dialog").value<QQuickFileDialog*>(); + QVERIFY(dialog); + COMPARE_URL(dialog->selectedFile(), QUrl()); + // It should default to the current directory. + COMPARE_URL(dialog->currentFolder(), QUrl::fromLocalFile(QDir().absolutePath())); + // The first file in the directory should be selected, but not until the dialog is actually open, + // as QQuickFileDialogImpl hasn't been created yet. + COMPARE_URL(dialog->currentFile(), QUrl()); + COMPARE_URLS(dialog->currentFiles(), {}); + QCOMPARE(dialog->title(), QString()); + + dialog->open(); + QQuickFileDialogImpl *quickDialog = window->findChild<QQuickFileDialogImpl*>(); + QTRY_VERIFY(quickDialog->isOpened()); + QVERIFY(quickDialog); + COMPARE_URL(quickDialog->selectedFile(), QUrl()); + COMPARE_URL(quickDialog->currentFolder(), QUrl::fromLocalFile(QDir().absolutePath())); + COMPARE_URL(dialog->currentFile(), QUrl::fromLocalFile(tempSubDir.path())); + COMPARE_URLS(dialog->currentFiles(), { QUrl::fromLocalFile(tempSubDir.path()) }); + COMPARE_URL(quickDialog->currentFile(), QUrl::fromLocalFile(tempSubDir.path())); + QCOMPARE(quickDialog->title(), QString()); +} + +void tst_QQuickFileDialogImpl::chooseFileViaStandardButtons() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Select the delegate by clicking once. + QSignalSpy dialogFileChangedSpy(dialogHelper.dialog, SIGNAL(selectedFileChanged())); + QVERIFY(dialogFileChangedSpy.isValid()); + QSignalSpy dialogCurrentFileChangedSpy(dialogHelper.dialog, SIGNAL(currentFileChanged())); + QVERIFY(dialogCurrentFileChangedSpy.isValid()); + QSignalSpy quickDialogCurrentFileChangedSpy(dialogHelper.quickDialog, SIGNAL(currentFileChanged(const QUrl &))); + QVERIFY(quickDialogCurrentFileChangedSpy.isValid()); + + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + QQuickFileDialogDelegate *delegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 2, delegate)); + COMPARE_URL(delegate->file(), QUrl::fromLocalFile(tempFile2->fileName())); + QVERIFY(clickButton(delegate)); + COMPARE_URL(dialogHelper.quickDialog->currentFile(), QUrl::fromLocalFile(tempFile2->fileName())); + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempFile2->fileName())); + COMPARE_URLS(dialogHelper.dialog->currentFiles(), { QUrl::fromLocalFile(tempFile2->fileName()) }); + // Only currentFile-related signals should be emitted. + QCOMPARE(dialogFileChangedSpy.count(), 0); + QCOMPARE(dialogCurrentFileChangedSpy.count(), 1); + QCOMPARE(quickDialogCurrentFileChangedSpy.count(), 1); + + // Click the "Open" button. + QVERIFY(dialogHelper.quickDialog->footer()); + auto dialogButtonBox = dialogHelper.quickDialog->footer()->findChild<QQuickDialogButtonBox*>(); + QVERIFY(dialogButtonBox); + QQuickAbstractButton* openButton = findDialogButton(dialogButtonBox, "Open"); + QVERIFY(openButton); + QVERIFY(clickButton(openButton)); + COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl::fromLocalFile(tempFile2->fileName())); + COMPARE_URLS(dialogHelper.dialog->selectedFiles(), { QUrl::fromLocalFile(tempFile2->fileName()) }); + QCOMPARE(dialogFileChangedSpy.count(), 1); + COMPARE_URL(dialogHelper.quickDialog->selectedFile(), QUrl::fromLocalFile(tempFile2->fileName())); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); + QVERIFY(!dialogHelper.dialog->isVisible()); +} + +void tst_QQuickFileDialogImpl::chooseFileViaDoubleClick() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Select the delegate by double-clicking. + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + QQuickFileDialogDelegate *delegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 2, delegate)); + COMPARE_URL(delegate->file(), QUrl::fromLocalFile(tempFile2->fileName())) + QVERIFY(doubleClickButton(delegate)); + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempFile2->fileName())) + COMPARE_URLS(dialogHelper.dialog->currentFiles(), { QUrl::fromLocalFile(tempFile2->fileName()) }) + COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl::fromLocalFile(tempFile2->fileName())) + COMPARE_URLS(dialogHelper.dialog->selectedFiles(), { QUrl::fromLocalFile(tempFile2->fileName()) }) + QVERIFY(!dialogHelper.dialog->isVisible()); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); +} + +void tst_QQuickFileDialogImpl::chooseFileViaTextEdit() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Get the text edit visible with Ctrl+L. + const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L); + QTest::keySequence(dialogHelper.window(), editPathKeySequence); + auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); + QVERIFY(breadcrumbBar); + QVERIFY(breadcrumbBar->textField()->isVisible()); + QCOMPARE(breadcrumbBar->textField()->text(), dialogHelper.dialog->currentFolder().toLocalFile()); + QCOMPARE(breadcrumbBar->textField()->selectedText(), breadcrumbBar->textField()->text()); + + // Enter the path to the file in the text edit. + enterText(dialogHelper.window(), tempFile2->fileName()); + QCOMPARE(breadcrumbBar->textField()->text(), tempFile2->fileName()); + + // Hit enter to accept. + QTest::keyClick(dialogHelper.window(), Qt::Key_Return); + COMPARE_URL(dialogHelper.quickDialog->selectedFile(), QUrl::fromLocalFile(tempFile2->fileName())); + COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl::fromLocalFile(tempFile2->fileName())); + COMPARE_URLS(dialogHelper.dialog->selectedFiles(), { QUrl::fromLocalFile(tempFile2->fileName()) }); + QVERIFY(!dialogHelper.dialog->isVisible()); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); +} + +void tst_QQuickFileDialogImpl::chooseFileViaEnter() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Before moving down, the first delegate in the view should be selected and have focus. + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempSubDir.path())); + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + QQuickFileDialogDelegate *subDirDelegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, subDirDelegate)); + COMPARE_URL(subDirDelegate->file(), QUrl::fromLocalFile(tempSubDir.path())); + QVERIFY(subDirDelegate->hasActiveFocus()); + + // Select the first file in the view by navigating with the down key. + QTest::keyClick(dialogHelper.window(), Qt::Key_Down); + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempFile1->fileName())); + + // Select the delegate by pressing enter. + QTest::keyClick(dialogHelper.window(), Qt::Key_Return); + COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl::fromLocalFile(tempFile1->fileName())); + COMPARE_URLS(dialogHelper.dialog->selectedFiles(), { QUrl::fromLocalFile(tempFile1->fileName()) }); + QVERIFY(!dialogHelper.dialog->isVisible()); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); + QCOMPARE(dialogHelper.dialog->result(), QQuickFileDialog::Accepted); +} + +void tst_QQuickFileDialogImpl::bindCurrentFolder_data() +{ + QTest::addColumn<QUrl>("initialFolder"); + QTest::addColumn<QUrl>("expectedFolder"); + QTest::addColumn<QStringList>("expectedVisibleFiles"); + + const auto currentDirUrl = QUrl::fromLocalFile(QDir::current().path()); + const auto tempSubDirUrl = QUrl::fromLocalFile(tempSubDir.path()); + const auto tempSubFile1Url = QUrl::fromLocalFile(tempSubFile1->fileName()); + + const QStringList currentDirFiles = { tempSubDir.path(), tempFile1->fileName(), tempFile2->fileName() }; + const QStringList tempSubDirFiles = { tempSubSubDir.path(), tempSubFile1->fileName(), tempSubFile2->fileName() }; + + // Setting the folder to "sub-dir" should result in "sub-file1.txt" and "sub-file2.txt" being visible. + QTest::addRow("sub-dir") << tempSubDirUrl << tempSubDirUrl << tempSubDirFiles; + // Setting a file as the folder shouldn't work, and the dialog shouldn't change its folder. + QTest::addRow("sub-dir/sub-file1.txt") << tempSubFile1Url << currentDirUrl << currentDirFiles; +} + +void tst_QQuickFileDialogImpl::bindCurrentFolder() +{ + QFETCH(QUrl, initialFolder); + QFETCH(QUrl, expectedFolder); + QFETCH(QStringList, expectedVisibleFiles); + + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindCurrentFolder.qml", {}, + {{ "initialFolder", initialFolder }}); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + COMPARE_URL(dialogHelper.dialog->currentFolder(), expectedFolder); + + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + QString failureMessage; + // Even waiting for ListView polish and that the FolderListModel's status is ready aren't enough + // on Windows, apparently, as sometimes there just aren't any delegates by the time we do the check. + // So, we use QTRY_VERIFY2 each time we call this function just to be safe. + QTRY_VERIFY2(verifyFileDialogDelegates(fileDialogListView, expectedVisibleFiles, failureMessage), qPrintable(failureMessage)); + + // Check that the breadcrumb bar is correct by constructing the expected files from the expectedFolder. + auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); + QVERIFY(breadcrumbBar); + QVERIFY2(verifyBreadcrumbDelegates(breadcrumbBar, expectedFolder, failureMessage), qPrintable(failureMessage)); +} + +void tst_QQuickFileDialogImpl::changeFolderViaStandardButtons() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Select the delegate by clicking once. + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + QQuickFileDialogDelegate *delegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, delegate)); + COMPARE_URL(delegate->file(), QUrl::fromLocalFile(tempSubDir.path())); + QVERIFY(clickButton(delegate)); + // The selectedFile and currentFolder shouldn't change yet. + COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl()); + COMPARE_URLS(dialogHelper.dialog->selectedFiles(), {}); + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempDir.path())); + // The currentFile should, though. + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempSubDir.path())); + COMPARE_URLS(dialogHelper.dialog->currentFiles(), { QUrl::fromLocalFile(tempSubDir.path()) }); + + // Click the "Open" button. The dialog should navigate to that directory, but still be open. + QVERIFY(dialogHelper.quickDialog->footer()); + auto dialogButtonBox = dialogHelper.quickDialog->footer()->findChild<QQuickDialogButtonBox*>(); + QVERIFY(dialogButtonBox); + QQuickAbstractButton* openButton = findDialogButton(dialogButtonBox, "Open"); + QVERIFY(openButton); + QVERIFY(clickButton(openButton)); + COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl()); + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempSubDir.path())); + QVERIFY(dialogHelper.dialog->isVisible()); + + dialogHelper.dialog->close(); + QVERIFY(!dialogHelper.dialog->isVisible()); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); +} + +void tst_QQuickFileDialogImpl::changeFolderViaDoubleClick() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Select the "sub-dir" delegate by double-clicking. + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + QQuickFileDialogDelegate *subDirDelegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, subDirDelegate)); + COMPARE_URL(subDirDelegate->file(), QUrl::fromLocalFile(tempSubDir.path())); + QVERIFY(doubleClickButton(subDirDelegate)); + // The first file in the directory should be selected, which is "sub-sub-dir". + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempSubSubDir.path())); + COMPARE_URLS(dialogHelper.dialog->currentFiles(), { QUrl::fromLocalFile(tempSubSubDir.path()) }); + QQuickFileDialogDelegate *subSubDirDelegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, subSubDirDelegate)); + QCOMPARE(subSubDirDelegate->isHighlighted(), true); + COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl()); + COMPARE_URLS(dialogHelper.dialog->selectedFiles(), {}); + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempSubDir.path())); + // Since we only chose a folder, the dialog should still be open. + QVERIFY(dialogHelper.dialog->isVisible()); + + dialogHelper.dialog->close(); + QVERIFY(!dialogHelper.dialog->isVisible()); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); +} + +void tst_QQuickFileDialogImpl::chooseFolderViaTextEdit() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Get the text edit visible with Ctrl+L. + const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L); + QTest::keySequence(dialogHelper.window(), editPathKeySequence); + auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); + QVERIFY(breadcrumbBar); + QVERIFY(breadcrumbBar->textField()->isVisible()); + QCOMPARE(breadcrumbBar->textField()->text(), dialogHelper.dialog->currentFolder().toLocalFile()); + QCOMPARE(breadcrumbBar->textField()->selectedText(), breadcrumbBar->textField()->text()); + + // Enter the path to the folder in the text edit. + enterText(dialogHelper.window(), tempSubDir.path()); + QCOMPARE(breadcrumbBar->textField()->text(), tempSubDir.path()); + + // Hit enter to accept. + QTest::keyClick(dialogHelper.window(), Qt::Key_Return); + // The first file in the directory should be selected, which is "sub-sub-dir". + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempSubSubDir.path())); + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + QQuickFileDialogDelegate *subSubDirDelegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, subSubDirDelegate)); + QCOMPARE(subSubDirDelegate->isHighlighted(), true); + COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl()); + COMPARE_URLS(dialogHelper.dialog->selectedFiles(), {}); + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempSubDir.path())); + QTRY_VERIFY(dialogHelper.dialog->isVisible()); + + dialogHelper.dialog->close(); + QVERIFY(!dialogHelper.dialog->isVisible()); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); +} + +void tst_QQuickFileDialogImpl::chooseFolderViaEnter() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Fhe first delegate in the view should be selected and have focus. + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempDir.path())); + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempSubDir.path())); + COMPARE_URLS(dialogHelper.dialog->currentFiles(), { QUrl::fromLocalFile(tempSubDir.path()) }); + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + QQuickFileDialogDelegate *subDirDelegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, subDirDelegate)); + COMPARE_URL(subDirDelegate->file(), QUrl::fromLocalFile(tempSubDir.path())); + QVERIFY(subDirDelegate->hasActiveFocus()); + + // Select the delegate by pressing enter. + QTest::keyClick(dialogHelper.window(), Qt::Key_Return); + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempSubDir.path())); + // The first file in the new directory should be selected, which is "sub-sub-dir". + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempSubSubDir.path())); + // Since we only chose a folder, the dialog should still be open. + QVERIFY(dialogHelper.dialog->isVisible()); + + dialogHelper.dialog->close(); + QVERIFY(!dialogHelper.dialog->isVisible()); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); +} + +void tst_QQuickFileDialogImpl::cancelDialogWhileTextEditHasFocus() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Get the text edit visible with Ctrl+L. + const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L); + QTest::keySequence(dialogHelper.window(), editPathKeySequence); + auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); + QVERIFY(breadcrumbBar); + QVERIFY(breadcrumbBar->textField()->hasActiveFocus()); + + // Close it via the cancel button. + auto dialogButtonBox = dialogHelper.quickDialog->footer()->findChild<QQuickDialogButtonBox*>(); + QVERIFY(dialogButtonBox); + const QString cancelText = QQuickDialogButtonBoxPrivate::buttonText(QPlatformDialogHelper::Cancel); + auto cancelButton = findDialogButton(dialogButtonBox, cancelText); + QVERIFY(cancelButton); + QVERIFY(clickButton(cancelButton)); + + // Open it again. The text field should not be visible, but the breadcrumb bar itself should be. + dialogHelper.dialog->open(); + QVERIFY(dialogHelper.dialog->isVisible()); + QTRY_VERIFY(dialogHelper.quickDialog->isOpened()); + QVERIFY(breadcrumbBar->isVisible()); + // The ListView that contains the breadcrumb delegates should be visible. + QVERIFY(breadcrumbBar->contentItem()->isVisible()); + QVERIFY(!breadcrumbBar->textField()->isVisible()); +} + +void tst_QQuickFileDialogImpl::goUp() +{ + // Open the dialog. Start off in "sub-dir". + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindCurrentFolder.qml", {}, + {{ "initialFolder", QUrl::fromLocalFile(tempSubDir.path()) }}); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Go up a directory via the button next to the breadcrumb bar. + auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); + QVERIFY(breadcrumbBar); + auto barListView = qobject_cast<QQuickListView*>(breadcrumbBar->contentItem()); + QVERIFY(barListView); + if (QQuickTest::qIsPolishScheduled(barListView)) + QVERIFY(QQuickTest::qWaitForItemPolished(barListView)); + QVERIFY(clickButton(breadcrumbBar->upButton())); + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempDir.path())); + // The previous directory that we were in should now be selected (matches e.g. Windows and Ubuntu). + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempSubDir.path())); + COMPARE_URLS(dialogHelper.dialog->currentFiles(), { QUrl::fromLocalFile(tempSubDir.path()) }); + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + QQuickFileDialogDelegate *subDirDelegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, subDirDelegate)); + QCOMPARE(subDirDelegate->isHighlighted(), true); + + // Go up a directory via the keyboard shortcut next to the breadcrumb bar. + const auto goUpKeySequence = QKeySequence(Qt::ALT | Qt::Key_Up); + QTest::keySequence(dialogHelper.window(), goUpKeySequence); + QDir tempParentDir(tempDir.path()); + QVERIFY(tempParentDir.cdUp()); + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempParentDir.path())); + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempDir.path())); + COMPARE_URLS(dialogHelper.dialog->currentFiles(), { QUrl::fromLocalFile(tempDir.path()) }); +} + +void tst_QQuickFileDialogImpl::goUpWhileTextEditHasFocus() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindCurrentFolder.qml", {}, + {{ "initialFolder", QUrl::fromLocalFile(tempSubDir.path()) }}); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Get the text edit visible with Ctrl+L. + const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L); + QTest::keySequence(dialogHelper.window(), editPathKeySequence); + auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); + QVERIFY(breadcrumbBar); + QVERIFY(breadcrumbBar->textField()->hasActiveFocus()); + + // Go up a directory via the button next to the breadcrumb bar. + auto barListView = qobject_cast<QQuickListView*>(breadcrumbBar->contentItem()); + QVERIFY(barListView); + if (QQuickTest::qIsPolishScheduled(barListView)) + QVERIFY(QQuickTest::qWaitForItemPolished(barListView)); + QVERIFY(clickButton(breadcrumbBar->upButton())); + // The path should have changed to the parent directory. + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempDir.path())); + // The text edit should be hidden when it loses focus. + QVERIFY(!breadcrumbBar->textField()->hasActiveFocus()); + QVERIFY(!breadcrumbBar->textField()->isVisible()); + // The focus should be given to the first delegate. + QVERIFY(dialogHelper.window()->activeFocusItem()); + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + QQuickFileDialogDelegate *firstDelegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, firstDelegate)); + QCOMPARE(dialogHelper.window()->activeFocusItem(), firstDelegate); +} + +void tst_QQuickFileDialogImpl::goIntoLargeFolder() +{ + // Create a throwaway directory with a lot of folders within it... + QTemporaryDir anotherTempDir; + QVERIFY(anotherTempDir.isValid()); + for (int i = 0; i < 30; ++i) { + QDir newDir(anotherTempDir.path()); + QVERIFY(newDir.exists()); + // Pad with zeroes so that the directories are ordered as we expect. + QVERIFY(newDir.mkdir(QString::fromLatin1("dir%1").arg(i, 2, 10, QLatin1Char('0')))); + } + + // ... and within one of those folders, more folders. + QDir dir20(anotherTempDir.path() + "/dir20"); + QVERIFY(dir20.exists()); + for (int i = 0; i < 30; ++i) { + QDir newDir(dir20.path()); + QVERIFY(newDir.exists()); + QVERIFY(newDir.mkdir(QString::fromLatin1("subdir%1").arg(i, 2, 10, QLatin1Char('0')))); + } + + // Open the dialog. Start off in the throwaway directory. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindCurrentFolder.qml", {}, + {{ "initialFolder", QUrl::fromLocalFile(anotherTempDir.path()) }}); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // If the screen is so tall that the contentItem is not vertically larger than the view, + // then the test makes no sense. + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + if (QQuickTest::qIsPolishScheduled(fileDialogListView)) + QVERIFY(QQuickTest::qWaitForItemPolished(fileDialogListView)); + // Just to be safe, make sure it's at least twice as big. + if (fileDialogListView->contentItem()->height() < fileDialogListView->height() * 2) { + QSKIP(qPrintable(QString::fromLatin1("Expected height of fileDialogListView's contentItem (%1)" \ + " to be at least twice as big as the ListView's height (%2)") + .arg(fileDialogListView->contentItem()->height()).arg(fileDialogListView->height()))); + } + + // Scroll down to dir20. The view should be somewhere past the middle. + QVERIFY(QMetaObject::invokeMethod(fileDialogListView, "positionViewAtIndex", Qt::DirectConnection, + Q_ARG(int, 20), Q_ARG(int, QQuickItemView::PositionMode::Center))); + QVERIFY(fileDialogListView->contentY() > 0); + + // Go into it. The view should start at the top of the directory, not at the same contentY + // that it had in the previous directory. + QQuickFileDialogDelegate *dir20Delegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 20, dir20Delegate)); + COMPARE_URL(dir20Delegate->file(), QUrl::fromLocalFile(dir20.path())); + QVERIFY(doubleClickButton(dir20Delegate)); + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(dir20.path())); + COMPARE_URL(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(dir20.path())); + QCOMPARE(fileDialogListView->contentY(), 0); +} + +void tst_QQuickFileDialogImpl::keyAndShortcutHandling() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Get the text edit visible with Ctrl+L. + const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L); + QTest::keySequence(dialogHelper.window(), editPathKeySequence); + auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); + QVERIFY(breadcrumbBar); + QVERIFY(breadcrumbBar->textField()->isVisible()); + QCOMPARE(breadcrumbBar->textField()->text(), dialogHelper.dialog->currentFolder().toLocalFile()); + QCOMPARE(breadcrumbBar->textField()->selectedText(), breadcrumbBar->textField()->text()); + + // Ctrl+L shouldn't hide it. + QTest::keySequence(dialogHelper.window(), editPathKeySequence); + QVERIFY(breadcrumbBar->textField()->isVisible()); + + // Cancel it with the escape key. + QTest::keyClick(dialogHelper.window(), Qt::Key_Escape); + QVERIFY(!breadcrumbBar->textField()->isVisible()); + QVERIFY(dialogHelper.dialog->isVisible()); + + // Make it visible. + QTest::keySequence(dialogHelper.window(), editPathKeySequence); + QVERIFY(breadcrumbBar->textField()->isVisible()); + + // Cancel it with the escape key again. + QTest::keyClick(dialogHelper.window(), Qt::Key_Escape); + QVERIFY(!breadcrumbBar->textField()->isVisible()); + QVERIFY(dialogHelper.dialog->isVisible()); + + // Pressing escape now should close the dialog. + QTest::keyClick(dialogHelper.window(), Qt::Key_Escape); + QVERIFY(!dialogHelper.dialog->isVisible()); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); +} + +void tst_QQuickFileDialogImpl::bindNameFilters() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindTxtHtmlNameFilters.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Only "sub-dir", "text1.txt" and "text2.txt" should be visible, since *.txt is the first filter. + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + QString failureMessage; + QTRY_VERIFY2(verifyFileDialogDelegates(fileDialogListView, + { tempSubDir.path(), tempFile1->fileName(), tempFile2->fileName() }, failureMessage), qPrintable(failureMessage)); +} + +void tst_QQuickFileDialogImpl::changeNameFilters() +{ + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + + // Open the dialog and check that selectedNameFilter is correct. + // By default, QFileDialogOptions::defaultNameFilterString() is used. + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + QCOMPARE(dialogHelper.dialog->selectedNameFilter()->name(), "All Files"); + QCOMPARE(dialogHelper.quickDialog->selectedNameFilter()->name(), "All Files"); + QCOMPARE(dialogHelper.dialog->selectedNameFilter()->index(), 0); + QCOMPARE(dialogHelper.quickDialog->selectedNameFilter()->index(), 0); + QCOMPARE(dialogHelper.dialog->selectedNameFilter()->extensions(), { "*" }); + QCOMPARE(dialogHelper.quickDialog->selectedNameFilter()->extensions(), { "*" }); + QCOMPARE(dialogHelper.dialog->selectedNameFilter()->globs(), { "*" }); + QCOMPARE(dialogHelper.quickDialog->selectedNameFilter()->globs(), { "*" }); + + // Close the dialog. + dialogHelper.dialog->close(); + QVERIFY(!dialogHelper.dialog->isVisible()); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); + + // Set .txt and .html filters. + QSignalSpy nameFiltersChangedSpy(dialogHelper.dialog, SIGNAL(nameFiltersChanged())); + QVERIFY(nameFiltersChangedSpy.isValid()); + const QStringList nameFilters = { "Text files (*.txt)", "HTML files (*.html)" }; + dialogHelper.dialog->setNameFilters(nameFilters); + QCOMPARE(dialogHelper.dialog->nameFilters(), nameFilters); + QCOMPARE(nameFiltersChangedSpy.count(), 1); + QCOMPARE(dialogHelper.dialog->selectedNameFilter()->name(), "Text files"); + QCOMPARE(dialogHelper.dialog->selectedNameFilter()->index(), 0); + QCOMPARE(dialogHelper.dialog->selectedNameFilter()->extensions(), { "txt" }); + QCOMPARE(dialogHelper.dialog->selectedNameFilter()->globs(), { "*.txt" }); + + // Re-open the dialog. + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + // QQuickFileDialogImpl's values only get set before opening. + QCOMPARE(dialogHelper.quickDialog->selectedNameFilter()->name(), "Text files"); + QCOMPARE(dialogHelper.quickDialog->selectedNameFilter()->index(), 0); + QCOMPARE(dialogHelper.quickDialog->selectedNameFilter()->extensions(), { "txt" }); + QCOMPARE(dialogHelper.quickDialog->selectedNameFilter()->globs(), { "*.txt" }); + + // Only "sub-dir", "text1.txt" and "text2.txt" should be visible, since *.txt is the first filter. + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + QString failureMessage; + QTRY_VERIFY2(verifyFileDialogDelegates(fileDialogListView, + { tempSubDir.path(), tempFile1->fileName(), tempFile2->fileName() }, failureMessage), qPrintable(failureMessage)); + + // Open the ComboBox's popup. + const QQuickComboBox *comboBox = dialogHelper.quickDialog->findChild<QQuickComboBox*>(); + QVERIFY(comboBox); + const QPoint comboBoxCenterPos = comboBox->mapToScene({ comboBox->width() / 2, comboBox->height() / 2 }).toPoint(); + QTest::mouseClick(dialogHelper.window(), Qt::LeftButton, Qt::NoModifier, comboBoxCenterPos); + QTRY_VERIFY(comboBox->popup()->isOpened()); + + // Select the .html delegate and close the combobox popup. The only visible entry should be the sub-dir. + QQuickListView *comboBoxPopupListView = qobject_cast<QQuickListView*>(comboBox->popup()->contentItem()); + QVERIFY(comboBoxPopupListView); + { + QQuickAbstractButton *htmlDelegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(comboBoxPopupListView, 1, htmlDelegate)); + QVERIFY(clickButton(htmlDelegate)); + } + QTRY_VERIFY(!comboBox->popup()->isVisible()); + // Use QTRY_VERIFY2 here to fix a failure on QEMU armv7 (QT_QPA_PLATFORM=offscreen). + // Not sure why it's necessary. + QTRY_VERIFY2(verifyFileDialogDelegates(fileDialogListView, { tempSubDir.path() }, failureMessage), qPrintable(failureMessage)); + + // Open the popup again. + QTest::mouseClick(dialogHelper.window(), Qt::LeftButton, Qt::NoModifier, comboBoxCenterPos); + QTRY_VERIFY(comboBox->popup()->isOpened()); + + // Select .txt and close the combobox popup. The original entries should be visible. + { + QQuickAbstractButton *txtDelegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(comboBoxPopupListView, 0, txtDelegate)); + QCOMPARE(txtDelegate->text(), nameFilters.at(0)); + QVERIFY(clickButton(txtDelegate)); + } + QTRY_VERIFY(!comboBox->popup()->isVisible()); + QTRY_VERIFY2(verifyFileDialogDelegates(fileDialogListView, + { tempSubDir.path(), tempFile1->fileName(), tempFile2->fileName() }, failureMessage), qPrintable(failureMessage)); +} + +void tst_QQuickFileDialogImpl::changeNameFiltersAfterChangingFolder() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindAllTxtHtmlNameFilters.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Go into the "sub-dir" folder. + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + QString failureMessage; + QTRY_VERIFY2(verifyFileDialogDelegates(fileDialogListView, + { tempSubDir.path(), tempFile1->fileName(), tempFile2->fileName() }, failureMessage), qPrintable(failureMessage)); + QQuickFileDialogDelegate *subDirDelegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, subDirDelegate)); + QVERIFY(doubleClickButton(subDirDelegate)); + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempSubDir.path())); + COMPARE_URL(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(tempSubDir.path())); + + // Open the ComboBox's popup. + const QQuickComboBox *comboBox = dialogHelper.quickDialog->findChild<QQuickComboBox*>(); + QVERIFY(comboBox); + const QPoint comboBoxCenterPos = comboBox->mapToScene({ comboBox->width() / 2, comboBox->height() / 2 }).toPoint(); + QTest::mouseClick(dialogHelper.window(), Qt::LeftButton, Qt::NoModifier, comboBoxCenterPos); + QTRY_VERIFY(comboBox->popup()->isOpened()); + + // Select the .html delegate, close the combobox popup, and ensure that the change had an effect. + QQuickListView *comboBoxPopupListView = qobject_cast<QQuickListView*>(comboBox->popup()->contentItem()); + QVERIFY(comboBoxPopupListView); + { + QQuickAbstractButton *htmlDelegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(comboBoxPopupListView, 2, htmlDelegate)); + QVERIFY(clickButton(htmlDelegate)); + } + QTRY_VERIFY(!comboBox->popup()->isVisible()); + // There are no HTML files in "sub-dir", so there should only be the one "sub-sub-dir" delegate. + QTRY_VERIFY2(verifyFileDialogDelegates(fileDialogListView, { tempSubSubDir.path() }, failureMessage), qPrintable(failureMessage)); +} + +void tst_QQuickFileDialogImpl::tabFocusNavigation() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindTxtHtmlNameFilters.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + QList<QQuickItem*> expectedFocusItems; + + // The initial focus should be on the first delegate. + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + QQuickFileDialogDelegate *firstDelegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, firstDelegate)); + expectedFocusItems << firstDelegate; + + // Tab should move to the name filters combobox. + QQuickComboBox *comboBox = dialogHelper.quickDialog->findChild<QQuickComboBox*>(); + QVERIFY(comboBox); + expectedFocusItems << comboBox; + + // Next, the left-most dialog button. + auto dialogButtonBox = dialogHelper.quickDialog->footer()->findChild<QQuickDialogButtonBox*>(); + QVERIFY(dialogButtonBox); + QCOMPARE(dialogButtonBox->count(), 2); + auto leftMostButton = qobject_cast<QQuickAbstractButton*>(dialogButtonBox->itemAt(0)); + QVERIFY(leftMostButton); + expectedFocusItems << leftMostButton; + + // Then, the right-most dialog button. + auto rightMostButton = qobject_cast<QQuickAbstractButton*>(dialogButtonBox->itemAt(1)); + QVERIFY(rightMostButton); + expectedFocusItems << rightMostButton; + + // Then, the up button. + auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); + QVERIFY(breadcrumbBar); + expectedFocusItems << breadcrumbBar->upButton(); + + // Finally, add each bread crumb delegate. + for (int i = 0; i < fileDialogListView->count(); ++i) { + QQuickFileDialogDelegate *delegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, i, delegate)); + expectedFocusItems << delegate; + } + + // Tab through each item, checking the focus after each. + for (auto expectedFocusItem : qAsConst(expectedFocusItems)) { + // Check the focus item first so that we account for the first item. + // Print detailed failure message as workaround for QTBUG-92102. + QVERIFY2(dialogHelper.window()->activeFocusItem() == expectedFocusItem, qPrintable(QString::fromLatin1( + "\n Actual: %1\n Expected: %2").arg(QDebug::toString(dialogHelper.window()->activeFocusItem())) + .arg(QDebug::toString(expectedFocusItem)))); + + if (expectedFocusItem != expectedFocusItems.last()) + QTest::keyClick(dialogHelper.window(), Qt::Key_Tab); + } + + // Ensure the order is reversed when shift-tabbing. + std::reverse(expectedFocusItems.begin(), expectedFocusItems.end()); + // We know the first (last) item has focus already, so skip it. + expectedFocusItems.removeFirst(); + for (auto expectedFocusItem : qAsConst(expectedFocusItems)) { + QTest::keyClick(dialogHelper.window(), Qt::Key_Tab, Qt::ShiftModifier); + + QCOMPARE(dialogHelper.window()->activeFocusItem(), expectedFocusItem); + } +} + +void tst_QQuickFileDialogImpl::acceptRejectLabel() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "acceptRejectLabel.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Check that the accept and reject buttons' labels have changed. + auto dialogButtonBox = dialogHelper.quickDialog->footer()->findChild<QQuickDialogButtonBox*>(); + QVERIFY(dialogButtonBox); + QVERIFY(findDialogButton(dialogButtonBox, "AcceptTest")); + QVERIFY(findDialogButton(dialogButtonBox, "RejectTest")); + + // Close the dialog. + dialogHelper.dialog->close(); + QVERIFY(!dialogHelper.dialog->isVisible()); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); + + // Reset back to the default text. + dialogHelper.dialog->resetAcceptLabel(); + dialogHelper.dialog->resetRejectLabel(); + + // Re-open the dialog. + dialogHelper.dialog->open(); + QVERIFY(dialogHelper.dialog->isVisible()); + QTRY_VERIFY(dialogHelper.quickDialog->isOpened()); + + // Check that the defaults are back. + const QString openText = QQuickDialogButtonBoxPrivate::buttonText(QPlatformDialogHelper::Open); + QVERIFY2(findDialogButton(dialogButtonBox, openText), qPrintable(QString::fromLatin1( + "Failed to find dialog button with text \"%1\"").arg(openText))); + const QString cancelText = QQuickDialogButtonBoxPrivate::buttonText(QPlatformDialogHelper::Cancel); + QVERIFY2(findDialogButton(dialogButtonBox, cancelText), qPrintable(QString::fromLatin1( + "Failed to find dialog button with text \"%1\"").arg(cancelText))); +} + +void tst_QQuickFileDialogImpl::bindTitle() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindTitle.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Open the dialog and check that the correct title is displayed. + QQuickFileDialog *dialog = dialogHelper.window()->property("dialog").value<QQuickFileDialog*>(); + QVERIFY(dialog); + const QString expectedTitle = QLatin1String("Test Title"); + QCOMPARE(dialogHelper.dialog->title(), expectedTitle); + QCOMPARE(dialogHelper.quickDialog->title(), expectedTitle); + auto header = dialogHelper.quickDialog->header(); + QVERIFY(header); + auto dialogTitleBarLabel = dialogHelper.quickDialog->header()->findChild<QQuickLabel*>("dialogTitleBarLabel"); + QVERIFY(dialogTitleBarLabel); + QCOMPARE(dialogTitleBarLabel->text(), expectedTitle); +} + +void tst_QQuickFileDialogImpl::itemsDisabledWhenNecessary() +{ + QTemporaryDir anotherTempDir; + QVERIFY(anotherTempDir.isValid()); + QDir subDir(anotherTempDir.path()); + QVERIFY(subDir.mkdir("emptyDir")); + QVERIFY(subDir.cd("emptyDir")); + + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindCurrentFolder.qml", {}, + {{ "initialFolder", QUrl::fromLocalFile(subDir.path()) }}); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(subDir.path())); + COMPARE_URL(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(subDir.path())); + + // We opened it in a folder that has no files, so the Open button should be disabled. + QVERIFY(dialogHelper.quickDialog->footer()); + auto dialogButtonBox = dialogHelper.quickDialog->footer()->findChild<QQuickDialogButtonBox*>(); + QVERIFY(dialogButtonBox); + QQuickAbstractButton* openButton = findDialogButton(dialogButtonBox, "Open"); + QVERIFY(openButton); + QCOMPARE(openButton->isEnabled(), false); + + // Now go up. The Open button should now be enabled. + auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); + QVERIFY(breadcrumbBar); + QVERIFY(clickButton(breadcrumbBar->upButton())); + QCOMPARE(openButton->isEnabled(), true); + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(anotherTempDir.path())); + COMPARE_URL(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(anotherTempDir.path())); + + // Get the text edit visible with Ctrl+L. The Open button should now be disabled. + const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L); + QTest::keySequence(dialogHelper.window(), editPathKeySequence); + QVERIFY(breadcrumbBar->textField()->isVisible()); + QCOMPARE(openButton->isEnabled(), false); + + // Hide it with the escape key. The Open button should now be enabled. + QTest::keyClick(dialogHelper.window(), Qt::Key_Escape); + QVERIFY(!breadcrumbBar->textField()->isVisible()); + QCOMPARE(openButton->isEnabled(), true); +} + +void tst_QQuickFileDialogImpl::fileMode_data() +{ + QTest::addColumn<QQuickFileDialog::FileMode>("fileMode"); + + QTest::newRow("OpenFile") << QQuickFileDialog::OpenFile; + QTest::newRow("OpenFiles") << QQuickFileDialog::OpenFiles; + QTest::newRow("SaveFile") << QQuickFileDialog::SaveFile; +} + +void tst_QQuickFileDialogImpl::fileMode() +{ + QFETCH(QQuickFileDialog::FileMode, fileMode); + + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); + dialogHelper.dialog->setFileMode(fileMode); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + // Select the first file (not a directory). + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + QQuickFileDialogDelegate *tempFile1Delegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 1, tempFile1Delegate)); + COMPARE_URL(tempFile1Delegate->file(), QUrl::fromLocalFile(tempFile1->fileName())); + QVERIFY(clickButton(tempFile1Delegate)); + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempFile1->fileName())); + COMPARE_URLS(dialogHelper.dialog->currentFiles(), { QUrl::fromLocalFile(tempFile1->fileName()) }); + + // All modes should support opening an existing file, so the Open button should be enabled. + QVERIFY(dialogHelper.quickDialog->footer()); + auto dialogButtonBox = dialogHelper.quickDialog->footer()->findChild<QQuickDialogButtonBox*>(); + QVERIFY(dialogButtonBox); + QQuickAbstractButton* openButton = findDialogButton(dialogButtonBox, "Open"); + QVERIFY(openButton); + QCOMPARE(openButton->isEnabled(), true); + + // Only the OpenFiles mode should allow multiple files to be selected, however. + QQuickFileDialogDelegate *tempFile2Delegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 2, tempFile2Delegate)); + COMPARE_URL(tempFile2Delegate->file(), QUrl::fromLocalFile(tempFile2->fileName())); + QTest::keyPress(dialogHelper.window(), Qt::Key_Shift); + QVERIFY(clickButton(tempFile2Delegate)); + QTest::keyRelease(dialogHelper.window(), Qt::Key_Shift); + if (fileMode == QQuickFileDialog::OpenFiles) { + // currentFile() always points to the first file in the list of selected files. + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempFile1->fileName())); + const QList<QUrl> expectedSelectedFiles = { + QUrl::fromLocalFile(tempFile1->fileName()), QUrl::fromLocalFile(tempFile2->fileName()) }; + COMPARE_URLS(dialogHelper.dialog->currentFiles(), expectedSelectedFiles); + } else { + // OpenFile and SaveFile dialogs should have tempFile2 selected since it was clicked, + // but the shift should do nothing, so tempFile1 should no longer be selected. + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempFile2->fileName())); + COMPARE_URLS(dialogHelper.dialog->currentFiles(), { QUrl::fromLocalFile(tempFile2->fileName()) }); + } + + // Get the text edit visible with Ctrl+L. + const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L); + QTest::keySequence(dialogHelper.window(), editPathKeySequence); + auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); + QVERIFY(breadcrumbBar); + QVERIFY(breadcrumbBar->textField()->isVisible()); + + // Typing in the name of an non-existent file should only work for SaveFile. + const QString nonExistentFilePath = "/foo/bar.txt"; + enterText(dialogHelper.window(), nonExistentFilePath); + QCOMPARE(breadcrumbBar->textField()->text(), nonExistentFilePath); + QTest::keyClick(dialogHelper.window(), Qt::Key_Return); + if (fileMode == QQuickFileDialog::SaveFile) { + COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl::fromLocalFile(nonExistentFilePath)); + COMPARE_URLS(dialogHelper.dialog->selectedFiles(), { QUrl::fromLocalFile(nonExistentFilePath) }); + QVERIFY(!dialogHelper.dialog->isVisible()); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); + } else { + // For OpenFile(s), we do what Qt Quick Dialogs 1.x did, and restore the previous (valid) dir path. + // The currentFile(s) should remain unchanged too. + QVERIFY(dialogHelper.dialog->isVisible()); + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempDir.path())); + QCOMPARE(breadcrumbBar->textField()->text(), tempDir.path()); + + // Should be unchanged from the last time. + if (fileMode == QQuickFileDialog::OpenFiles) { + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempFile1->fileName())); + const QList<QUrl> expectedSelectedFiles = { + QUrl::fromLocalFile(tempFile1->fileName()), QUrl::fromLocalFile(tempFile2->fileName()) }; + COMPARE_URLS(dialogHelper.dialog->currentFiles(), expectedSelectedFiles); + } else { // OpenFile + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempFile2->fileName())); + COMPARE_URLS(dialogHelper.dialog->currentFiles(), { QUrl::fromLocalFile(tempFile2->fileName()) }); + } + } +} + +void tst_QQuickFileDialogImpl::defaultSuffix_data() +{ + QTest::addColumn<QString>("defaultSuffix"); + + QTest::newRow("txt") << "txt"; + QTest::newRow(".txt") << ".txt"; +} + +void tst_QQuickFileDialogImpl::defaultSuffix() +{ + QFETCH(QString, defaultSuffix); + + // Simplify the test by using a directory with no files, and add a single file there. + QFile tempFile1(tempSubSubDir.path() + "/file1"); + QVERIFY(tempFile1.open(QIODevice::ReadWrite)); + + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindCurrentFolder.qml", {}, + {{ "initialFolder", QUrl::fromLocalFile(tempSubSubDir.path()) }}); + dialogHelper.dialog->setDefaultSuffix(defaultSuffix); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempSubSubDir.path())); + + // There should be one extension-less file: "file1". + auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); + QVERIFY(fileDialogListView); + QString failureMessage; + const QStringList expectedVisibleFiles = { tempFile1.fileName() }; + QTRY_VERIFY2(verifyFileDialogDelegates(fileDialogListView, expectedVisibleFiles, failureMessage), qPrintable(failureMessage)); + + // Choose the delegate. The suffix should be added to the delegates. + QQuickFileDialogDelegate *file1Delegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, file1Delegate)); + COMPARE_URL(file1Delegate->file(), QUrl::fromLocalFile(tempFile1.fileName())); + QVERIFY(doubleClickButton(file1Delegate)); + QVERIFY(!dialogHelper.dialog->isVisible()); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); + const QUrl fileUrlWithSuffix = QUrl::fromLocalFile(tempFile1.fileName() + ".txt"); + COMPARE_URL(dialogHelper.dialog->selectedFile(), fileUrlWithSuffix); + COMPARE_URLS(dialogHelper.dialog->selectedFiles(), { fileUrlWithSuffix }); +} + +void tst_QQuickFileDialogImpl::done_data() +{ + QTest::addColumn<QQuickFileDialog::StandardCode>("result"); + + QTest::newRow("Accepted") << QQuickFileDialog::Accepted; + QTest::newRow("Rejected") << QQuickFileDialog::Rejected; +} + +void tst_QQuickFileDialogImpl::done() +{ + QFETCH(QQuickFileDialog::StandardCode, result); + + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + switch (result) { + case QQuickFileDialog::Accepted: + QVERIFY(QMetaObject::invokeMethod(dialogHelper.window(), "doneAccepted")); + break; + case QQuickFileDialog::Rejected: + QVERIFY(QMetaObject::invokeMethod(dialogHelper.window(), "doneRejected")); + break; + } + + QVERIFY(!dialogHelper.dialog->isVisible()); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); + QCOMPARE(dialogHelper.dialog->result(), result); +} + +int main(int argc, char *argv[]) +{ + // We need to set this attribute, and this (defining main() ourselves and + // calling QTEST_MAIN_IMPL) seems to be the nicest way to do it without + // duplicating too much code. + // We also don't want to run this for every style, as each one will have + // different ways of implementing the dialogs. + QCoreApplication::setAttribute(Qt::AA_DontUseNativeDialogs); + // For now we only test one style. + QQuickStyle::setStyle("Basic"); + QTEST_MAIN_IMPL(tst_QQuickFileDialogImpl) +} + +#include "tst_qquickfiledialogimpl.moc" diff --git a/tests/auto/shared/dialogtestutil.h b/tests/auto/shared/dialogtestutil.h new file mode 100644 index 00000000..c10c4001 --- /dev/null +++ b/tests/auto/shared/dialogtestutil.h @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite 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$ +** +****************************************************************************/ + +#ifndef QQUICKDIALOGTESTUTIL_H +#define QQUICKDIALOGTESTUTIL_H + +#include <QtQuickTemplates2/private/qquickapplicationwindow_p.h> + +#include "util.h" +#include "visualtestutil.h" + +// We need these for Windows, because FolderListModel returns a lowercase drive letter; e.g.: +// "file:///c:/blah.txt", whereas other API returns "file:///C:/blah.txt". +#define COMPARE_URL(url1, url2) \ + QCOMPARE(QFileInfo(url1.toLocalFile()).absoluteFilePath(), QFileInfo(url2.toLocalFile()).absoluteFilePath()); + +// Store a copy of the arguments in case { ... } list initializer syntax is used as an argument, +// which could result in two different lists being created and passed to std::transform() +// (and would also require it to be enclosed in parentheses everywhere it's used). +#define COMPARE_URLS(actualUrls, expectedUrls) \ +{ \ + const QList<QUrl> actualUrlsCopy = actualUrls; \ + QList<QString> actualPaths; \ + std::transform(actualUrlsCopy.begin(), actualUrlsCopy.end(), std::back_insert_iterator(actualPaths), \ + [](const QUrl &url) { return QFileInfo(url.toLocalFile()).absoluteFilePath(); }); \ + const QList<QUrl> expectedUrlsCopy = expectedUrls; \ + QList<QString> expectedPaths; \ + std::transform(expectedUrlsCopy.begin(), expectedUrlsCopy.end(), std::back_insert_iterator(expectedPaths), \ + [](const QUrl &url) { return QFileInfo(url.toLocalFile()).absoluteFilePath(); }); \ + QCOMPARE(actualPaths, expectedPaths); \ +} + +namespace QQuickDialogTestUtil +{ + +// Saves duplicating a bunch of code in every test. +template<typename DialogType, typename QuickDialogType> +class DialogTestHelper +{ +public: + DialogTestHelper(QQmlDataTest *testCase, const QString &testFilePath, + const QStringList &qmlImportPaths = {}, const QVariantMap &initialProperties = {}) : + appHelper(testCase, testFilePath, qmlImportPaths, initialProperties) + { + if (!appHelper.ready) + return; + + dialog = appHelper.window->property("dialog").value<DialogType*>(); + if (!dialog) { + appHelper.errorMessage = "\"dialog\" property is not valid"; + return; + } + + appHelper.window->show(); + appHelper.window->requestActivate(); + } + + Q_REQUIRED_RESULT bool isWindowInitialized() const + { + return appHelper.ready; + } + + Q_REQUIRED_RESULT bool waitForWindowActive() + { + return QTest::qWaitForWindowActive(appHelper.window); + } + + bool openDialog() + { + dialog->open(); + if (!dialog->isVisible()) { + appHelper.errorMessage = "Dialog is not visible"; + return false; + } + + // We might want to call this function more than once, + // and we only need to get these members the first time. + if (!quickDialog) { + quickDialog = appHelper.window->findChild<QuickDialogType*>(); + if (!quickDialog) { + appHelper.errorMessage = "Can't find Qt Quick-based dialog"; + return false; + } + } + + return true; + } + + bool isQuickDialogOpen() const + { + return quickDialog->isOpened(); + } + + QQuickWindow *window() const + { + return appHelper.window; + } + + const char *failureMessage() const + { + return appHelper.errorMessage.constData(); + } + + QQuickVisualTestUtil::QQuickApplicationHelper appHelper; + DialogType *dialog = nullptr; + QuickDialogType *quickDialog = nullptr; +}; + +} + +#endif // QQUICKDIALOGTESTUTIL_H diff --git a/tests/auto/shared/visualtestutil.h b/tests/auto/shared/visualtestutil.h index 215a487b..76c44821 100644 --- a/tests/auto/shared/visualtestutil.h +++ b/tests/auto/shared/visualtestutil.h @@ -127,6 +127,20 @@ namespace QQuickVisualTestUtil QQuickItem* findViewDelegateItem(QQuickItemView *itemView, int index, FindViewDelegateItemFlags flags = FindViewDelegateItemFlag::PositionViewAtIndex); + /*! + \internal + + Same as above except allows use in QTRY_* functions without having to call it again + afterwards to assign the delegate. + */ + template<typename T> + bool findViewDelegateItem(QQuickItemView *itemView, int index, T &delegateItem, + FindViewDelegateItemFlags flags = FindViewDelegateItemFlag::PositionViewAtIndex) + { + delegateItem = qobject_cast<T>(findViewDelegateItem(itemView, index, flags)); + return delegateItem != nullptr; + } + class QQuickApplicationHelper { public: diff --git a/tests/manual/dialogs/CMakeLists.txt b/tests/manual/dialogs/CMakeLists.txt new file mode 100644 index 00000000..d99d64b9 --- /dev/null +++ b/tests/manual/dialogs/CMakeLists.txt @@ -0,0 +1,31 @@ +if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(dialogs LANGUAGES C CXX ASM) + find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_manual_test(dialogs + GUI + SOURCES + dialogs.cpp + PUBLIC_LIBRARIES + Qt::Gui + Qt::Qml + Qt::QuickControls2 +) + +# Resources: +set(qmake_immediate_resource_files + "dialogs.qml" + "FileDialogPage.qml" + "StringListView.qml" + "qmldir" + "Theme.qml" +) + +qt_internal_add_resource(dialogs "qmake_immediate" + PREFIX + "/" + FILES + ${qmake_immediate_resource_files} +) diff --git a/tests/manual/dialogs/FileDialogPage.qml b/tests/manual/dialogs/FileDialogPage.qml new file mode 100644 index 00000000..9faad47e --- /dev/null +++ b/tests/manual/dialogs/FileDialogPage.qml @@ -0,0 +1,357 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs +import QtQuick.Layouts + +import "." + +ColumnLayout { + property alias dialog: fileDialog + + // Put it all in another ColumnLayout so we can easily add margins. + ColumnLayout { + Layout.leftMargin: 12 + Layout.rightMargin: 12 + Layout.topMargin: 12 + Layout.bottomMargin: 12 + + GroupBox { + title: qsTr("Dialog properties") + + Layout.fillWidth: true + + GridLayout { + columns: 2 + anchors.fill: parent + + Label { + text: qsTr("modality") + + Layout.alignment: Qt.AlignTop + Layout.minimumWidth: ApplicationWindow.window.width * 0.2 + Layout.maximumWidth: ApplicationWindow.window.width * 0.2 + } + ButtonGroup { + id: modalityButtonGroup + buttons: modalityColumnLayout.children + } + ColumnLayout { + id: modalityColumnLayout + + RadioButton { + text: qsTr("Qt.NonModal") + + readonly property int modality: Qt.NonModal + } + RadioButton { + text: qsTr("Qt.WindowModal") + checked: true + + readonly property int modality: Qt.WindowModal + } + RadioButton { + text: qsTr("Qt.ApplicationModal") + + readonly property int modality: Qt.ApplicationModal + } + } + + Label { + text: qsTr("result") + } + TextField { + id: resultTextField + text: fileDialog.result === 1 ? qsTr("Accepted") : qsTr("Rejected") + readOnly: true + enabled: false + } + + Label { + text: qsTr("title") + } + TextField { + id: titleTextField + text: qsTr("Choose a file") + } + } + } + + GroupBox { + title: qsTr("FileDialog properties") + + Layout.fillWidth: true + + GridLayout { + columns: 2 + anchors.fill: parent + + Label { + text: qsTr("acceptLabel") + + Layout.minimumWidth: ApplicationWindow.window.width * 0.2 + Layout.maximumWidth: ApplicationWindow.window.width * 0.2 + } + TextField { + id: acceptLabelTextField + text: qsTr("OK") + } + + Label { + text: qsTr("currentFile") + } + TextField { + id: currentFileTextField + text: fileDialog.currentFile + readOnly: true + selectByMouse: true + + Layout.fillWidth: true + } + + Label { + text: qsTr("currentFolder") + } + TextField { + id: currentFolderTextField + text: fileDialog.currentFolder + readOnly: true + selectByMouse: true + + Layout.fillWidth: true + } + + Label { + text: qsTr("currentFiles") + + Layout.alignment: Qt.AlignTop + } + StringListView { + id: currentFilesListView + // QTBUG-72906 + model: [].concat(fileDialog.currentFiles) + } + + Label { + text: qsTr("fileOptions") + + Layout.alignment: Qt.AlignTop + } + ColumnLayout { + id: fileOptionsColumnLayout + + CheckBox { + id: dontResolveSymlinksCheckBox + text: qsTr("DontResolveSymlinks") + + readonly property int fileOption: checked ? FileDialog.DontResolveSymlinks : 0 + } + CheckBox { + id: dontConfirmOverwriteCheckBox + text: qsTr("DontConfirmOverwrite") + + readonly property int fileOption: checked ? FileDialog.DontConfirmOverwrite : 0 + } + CheckBox { + id: readOnlyCheckBox + text: qsTr("ReadOnly") + + readonly property int fileOption: checked ? FileDialog.ReadOnly : 0 + } + CheckBox { + id: hideNameFilterDetailsCheckBox + text: qsTr("HideNameFilterDetails") + + readonly property int fileOption: checked ? FileDialog.HideNameFilterDetails : 0 + } + } + + Label { + text: qsTr("fileMode") + + Layout.alignment: Qt.AlignTop + } + ButtonGroup { + id: fileModeButtonGroup + buttons: fileModeColumnLayout.children + } + ColumnLayout { + id: fileModeColumnLayout + + RadioButton { + text: qsTr("OpenFile") + + readonly property int fileMode: FileDialog.OpenFile + } + RadioButton { + text: qsTr("OpenFiles") + checked: true + + readonly property int fileMode: FileDialog.OpenFiles + } + RadioButton { + text: qsTr("SaveFile") + + readonly property int fileMode: FileDialog.SaveFile + } + } + + Label { + text: qsTr("nameFilters") + } + TextField { + id: nameFiltersTextField + text: ["Text files (*.txt)", "HTML files (*.html)"].join(",") + + Layout.fillWidth: true + + ToolTip.text: qsTr("For this example, a comma-separated string") + ToolTip.visible: hovered + ToolTip.delay: Theme.toolTipDelay + } + + Label { + text: qsTr("rejectLabel") + } + TextField { + id: rejectLabelTextField + text: qsTr("Cancel") + } + + Label { + text: qsTr("selectedFile") + } + TextField { + id: selectedFileTextField + text: fileDialog.selectedFile + readOnly: true + selectByMouse: true + + Layout.fillWidth: true + } + + Label { + text: qsTr("selectedFiles") + + Layout.alignment: Qt.AlignTop + } + StringListView { + id: selectedFilesListView + // QTBUG-72906 + model: [].concat(fileDialog.selectedFiles) + } + + Label { + text: qsTr("selectedNameFilter.name") + } + TextField { + id: selectedNameFilterNameTextField + text: fileDialog.selectedNameFilter.name + readOnly: true + selectByMouse: true + + Layout.fillWidth: true + } + + Label { + text: qsTr("selectedNameFilter.globs") + + Layout.alignment: Qt.AlignTop + } + StringListView { + id: selectedNameFilterGlobsListView + // QTBUG-72906 + model: [].concat(fileDialog.selectedNameFilter.globs) + } + + Label { + text: qsTr("selectedNameFilter.index") + } + TextField { + id: selectedNameFilterIndexTextField + text: fileDialog.selectedNameFilter.index + readOnly: true + selectByMouse: true + + Layout.fillWidth: true + } + + Label { + text: qsTr("selectedNameFilter.extensions") + + Layout.alignment: Qt.AlignTop + } + StringListView { + id: selectedNameFilterExtensionsListView + // QTBUG-72906 + model: [].concat(fileDialog.selectedNameFilter.extensions) + } + } + } + + FileDialog { + id: fileDialog + + modality: modalityButtonGroup.checkedButton.modality + title: titleTextField.text + + acceptLabel: acceptLabelTextField.text + fileMode: fileModeButtonGroup.checkedButton.fileMode + options: dontResolveSymlinksCheckBox.fileOption + | dontConfirmOverwriteCheckBox.fileOption + | readOnlyCheckBox.fileOption + | hideNameFilterDetailsCheckBox.fileOption + nameFilters: nameFiltersTextField.text.split(",") + rejectLabel: rejectLabelTextField.text + } + } +} diff --git a/tests/manual/dialogs/StringListView.qml b/tests/manual/dialogs/StringListView.qml new file mode 100644 index 00000000..4a229404 --- /dev/null +++ b/tests/manual/dialogs/StringListView.qml @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "." + +ListView { + id: root + clip: true + boundsBehavior: Flickable.StopAtBounds + + Layout.fillWidth: true + Layout.preferredHeight: count > 0 ? 128 : noneLabel.implicitHeight + + ScrollBar.vertical: ScrollBar {} + + delegate: TextField { + width: root.width + text: modelData + readOnly: true + selectByMouse: true + } + + Label { + id: noneLabel + text: qsTr("(None)") + visible: root.count === 0 + } +} diff --git a/tests/manual/dialogs/Theme.qml b/tests/manual/dialogs/Theme.qml new file mode 100644 index 00000000..8114f14e --- /dev/null +++ b/tests/manual/dialogs/Theme.qml @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +pragma Singleton + +import QtQml + +QtObject { + readonly property int toolTipDelay: 1000 +} diff --git a/tests/manual/dialogs/dialogs.cpp b/tests/manual/dialogs/dialogs.cpp new file mode 100644 index 00000000..8bab70e2 --- /dev/null +++ b/tests/manual/dialogs/dialogs.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qsettings.h> +#include <QtGui/qguiapplication.h> +#include <QtQml/qqmlapplicationengine.h> +#include <QtQuickControls2/qquickstyle.h> + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QCoreApplication::setApplicationName("dialogs-manual-test"); + QCoreApplication::setOrganizationName("QtProject"); + + // Use native dialogs by default unless the user asked us not to. + QSettings settings; + const QVariant useNativeDialogs = settings.value("useNativeDialogs"); + if (useNativeDialogs.isValid()) { + if (!useNativeDialogs.toBool()) + QCoreApplication::setAttribute(Qt::AA_DontUseNativeDialogs); + } else { + // Set the default here so that we can use an alias in QML. + // Without this it defaults to CheckBox's checked default value, which is false. + settings.setValue("useNativeDialogs", true); + } + + QQmlApplicationEngine engine; + engine.setInitialProperties({{ "style", QQuickStyle::name() }}); + engine.load(QUrl(QStringLiteral("qrc:/dialogs.qml"))); + + return app.exec(); +} diff --git a/tests/manual/dialogs/dialogs.pro b/tests/manual/dialogs/dialogs.pro new file mode 100644 index 00000000..abb31492 --- /dev/null +++ b/tests/manual/dialogs/dialogs.pro @@ -0,0 +1,11 @@ +TEMPLATE = app +TARGET = dialogs +QT += qml quickcontrols2 + +SOURCES += dialogs.cpp +RESOURCES += \ + dialogs.qml \ + FileDialogPage.qml \ + StringListView.qml \ + qmldir \ + Theme.qml diff --git a/tests/manual/dialogs/dialogs.qml b/tests/manual/dialogs/dialogs.qml new file mode 100644 index 00000000..d1f54c08 --- /dev/null +++ b/tests/manual/dialogs/dialogs.qml @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Qt.labs.settings + +ApplicationWindow { + id: window + width: 800 + height: 600 + title: "dialogs - style: " + style + visible: true + + required property string style + + Component.onCompleted: { + x = Screen.width / 2 - width / 2 + y = Screen.height / 2 - height / 2 + } + + Settings { + id: settings + + property alias useNativeDialogs: useNativeDialogsCheckBox.checked + } + + Page { + anchors.fill: parent + + header: TabBar { + id: tabBar + + TabButton { + text: qsTr("FileDialog") + } + TabButton { + text: qsTr("Coming Soon...") + } + } + + ScrollView { + id: scrollView + anchors.fill: parent + clip: true + + StackLayout { + id: stackLayout + currentIndex: tabBar.currentIndex + width: scrollView.width + + FileDialogPage {} + } + } + } + + footer: ToolBar { + RowLayout { + anchors.fill: parent + + CheckBox { + id: useNativeDialogsCheckBox + text: qsTr("Use Native Dialogs (requires restart)") + checked: settings.useNativeDialogs + } + + Item { + Layout.fillWidth: true + } + + Button { + text: qsTr("Open") + + onClicked: stackLayout.children[stackLayout.currentIndex].dialog.open() + } + } + } +} diff --git a/tests/manual/dialogs/qmldir b/tests/manual/dialogs/qmldir new file mode 100644 index 00000000..4f4a6b7b --- /dev/null +++ b/tests/manual/dialogs/qmldir @@ -0,0 +1,3 @@ +module org.qtproject.examples.dialogs + +singleton Theme 1.0 Theme.qml |