From 56127039f5ac27ffba7ac2ae0dba789e68663aeb Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Tue, 11 May 2021 10:42:30 +0200 Subject: [PATCH 01/11] [ExternalStorage] Add widget to configure/edit and visualize external storage --- .../core/qgswidgetwrapper.sip.in | 3 +- .../qgsexternalresourcewidget.sip.in | 49 + .../gui/auto_generated/qgsfilewidget.sip.in | 120 +++ .../core/qgseditorwidgetregistry.cpp | 2 +- .../editorwidgets/core/qgswidgetwrapper.cpp | 3 +- src/gui/editorwidgets/core/qgswidgetwrapper.h | 3 +- .../qgsexternalresourceconfigdlg.cpp | 55 ++ .../qgsexternalresourceconfigdlg.h | 6 + .../qgsexternalresourcewidgetfactory.cpp | 5 +- .../qgsexternalresourcewidgetfactory.h | 8 +- .../qgsexternalresourcewidgetwrapper.cpp | 38 +- .../qgsexternalresourcewidgetwrapper.h | 7 +- src/gui/qgsexternalresourcewidget.cpp | 170 +++- src/gui/qgsexternalresourcewidget.h | 56 ++ src/gui/qgsfilewidget.cpp | 312 ++++-- src/gui/qgsfilewidget.h | 138 ++- .../qgsexternalresourceconfigdlg.ui | 98 +- .../testqgsexternalresourcewidgetwrapper.cpp | 904 +++++++++++++++++- tests/src/gui/testqgsfilewidget.cpp | 735 +++++++++++++- 19 files changed, 2597 insertions(+), 115 deletions(-) diff --git a/python/gui/auto_generated/editorwidgets/core/qgswidgetwrapper.sip.in b/python/gui/auto_generated/editorwidgets/core/qgswidgetwrapper.sip.in index a4f1c59b1a8b..820910e56ccf 100644 --- a/python/gui/auto_generated/editorwidgets/core/qgswidgetwrapper.sip.in +++ b/python/gui/auto_generated/editorwidgets/core/qgswidgetwrapper.sip.in @@ -51,7 +51,8 @@ changed status of the widget will be saved. enum Property { RootPath, - DocumentViewerContent + DocumentViewerContent, + StorageUrl }; static const QgsPropertiesDefinition &propertyDefinitions(); diff --git a/python/gui/auto_generated/qgsexternalresourcewidget.sip.in b/python/gui/auto_generated/qgsexternalresourcewidget.sip.in index 37a80f1f8053..332fdbf0d673 100644 --- a/python/gui/auto_generated/qgsexternalresourcewidget.sip.in +++ b/python/gui/auto_generated/qgsexternalresourcewidget.sip.in @@ -133,6 +133,55 @@ is set to :py:class:`QgsFileWidget`.RelativeDefaultPath. %Docstring Configures the base path which should be used if the relativeStorage property is set to :py:class:`QgsFileWidget`.RelativeDefaultPath. +%End + + void setStorageType( const QString &storageType ); +%Docstring +Set ``storageType`` storage type unique identifier as defined in :py:class:`QgsExternalStorageRegistry` or +null QString if there is no storage defined, only file selection. + +.. seealso:: :py:func:`storageType` + +.. versionadded:: 3.22 +%End + + QString storageType() const; +%Docstring +Get storage type unique identifier as defined in :py:class:`QgsExternalStorageRegistry`. +Returns null QString if there is no storage defined, only file selection. + +.. seealso:: :py:func:`setStorageType` + +.. versionadded:: 3.22 +%End + + void setStorageAuthConfigId( const QString &authCfg ); +%Docstring +Sets the authentication configuration ID to be used for the current external storage (if +defined) + +.. versionadded:: 3.22 +%End + + const QString &storageAuthConfigId() const; +%Docstring +Returns the authentication configuration ID used for the current external storage (if defined) + +.. versionadded:: 3.22 +%End + + void setMessageBar( QgsMessageBar *messageBar ); +%Docstring +Set ``messageBar`` to report messages + +.. versionadded:: 3.22 +%End + + QgsMessageBar *messageBar() const; +%Docstring +Returns message bar used to report messages + +.. versionadded:: 3.22 %End signals: diff --git a/python/gui/auto_generated/qgsfilewidget.sip.in b/python/gui/auto_generated/qgsfilewidget.sip.in index ff15abe1997b..746142a5a4da 100644 --- a/python/gui/auto_generated/qgsfilewidget.sip.in +++ b/python/gui/auto_generated/qgsfilewidget.sip.in @@ -9,6 +9,8 @@ + + class QgsFileWidget : QWidget { %Docstring(signature="appended") @@ -75,6 +77,104 @@ Sets the current file ``path``. void setReadOnly( bool readOnly ); %Docstring Sets whether the widget should be read only. +%End + + void setStorageType( const QString &storageType ); +%Docstring +Set ``storageType`` storage type unique identifier as defined in :py:class:`QgsExternalStorageRegistry` or +null QString if there is no storage defined. +If no external storage has been defined, QgsFileWidget will only update file path according to +selected files. + +.. seealso:: :py:func:`storageType` + +.. versionadded:: 3.22 +%End + + QString storageType() const; +%Docstring +Get storage type unique identifier as defined in :py:class:`QgsExternalStorageRegistry`. +Returns null QString if there is no storage defined, only file selection. + +.. seealso:: :py:func:`setStorageType` + +.. versionadded:: 3.22 +%End + + QgsExternalStorage *externalStorage() const; +%Docstring +Returns external storage used to store selected file names, None if none have been defined. +If no external storage has been defined, QgsFileWidget will only update file path according to +selected files. + +.. seealso:: :py:func:`setStorageType` + +.. versionadded:: 3.22 +%End + + void setStorageAuthConfigId( const QString &authCfg ); +%Docstring +Sets the authentication configuration ID to be used for the current external storage (if +defined) + +.. versionadded:: 3.22 +%End + + const QString &storageAuthConfigId() const; +%Docstring +Returns the authentication configuration ID used for the current external storage (if defined) + +.. versionadded:: 3.22 +%End + + void setStorageUrlExpression( const QString &urlExpression ); +%Docstring +Set ``urlExpression`` expression, which once evaluated, provide the URL used to store selected +documents. This is used only if an external storage has been defined + +.. seealso:: :py:func:`setStorageType` + +.. versionadded:: 3.22 +%End + + QString storageUrlExpressionString() const; +%Docstring +Returns the original, unmodified expression string, which once evaluated, provide the +URL used to store selected documents. This is used only if an external storage has been defined. +Returns null if no expression has been set. + +.. seealso:: :py:func:`setStorageUrlExpression` + +.. versionadded:: 3.22 +%End + + QgsExpression *storageUrlExpression() const; +%Docstring +Returns expression, which once evaluated, provide the URL used to store selected +documents. This is used only if an external storage has been defined. +Returns null if no expression has been set. + +.. seealso:: :py:func:`setStorageUrlExpression` + +.. versionadded:: 3.22 +%End + + void setExpressionContext( const QgsExpressionContext &context ); +%Docstring +Set expression context to be used when for storage URL expression evaluation + +.. seealso:: :py:func:`setStorageUrlExpression` + +.. versionadded:: 3.22 +%End + + const QgsExpressionContext &expressionContext() const; +%Docstring +Returns expression context used for storage url expression evaluation + +.. seealso:: :py:func:`storageUrlExpression` + +.. versionadded:: 3.22 %End QString dialogTitle() const; @@ -250,6 +350,26 @@ Returns a pointer to the widget's line edit, which can be used to customize the appearance and behavior of the line edit portion of the widget. .. versionadded:: 3.0 +%End + + void setMessageBar( QgsMessageBar *messageBar ); +%Docstring +Set ``messageBar`` to report messages + +.. versionadded:: 3.22 +%End + + QgsMessageBar *messageBar() const; +%Docstring +Returns message bar used to report messages + +.. versionadded:: 3.22 +%End + + static QgsExpressionContextScope *createFileWidgetScope(); +%Docstring +Creates and Returns an expression context scope specific to QgsFileWidget +It defines the variable containing the user selected file name %End signals: diff --git a/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp b/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp index 63b8548dde66..4049d28e203b 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp @@ -63,7 +63,7 @@ void QgsEditorWidgetRegistry::initEditors( QgsMapCanvas *mapCanvas, QgsMessageBa registerWidget( QStringLiteral( "Color" ), new QgsColorWidgetFactory( tr( "Color" ) ) ); registerWidget( QStringLiteral( "RelationReference" ), new QgsRelationReferenceFactory( tr( "Relation Reference" ), mapCanvas, messageBar ) ); registerWidget( QStringLiteral( "DateTime" ), new QgsDateTimeEditFactory( tr( "Date/Time" ) ) ); - registerWidget( QStringLiteral( "ExternalResource" ), new QgsExternalResourceWidgetFactory( tr( "Attachment" ) ) ); + registerWidget( QStringLiteral( "ExternalResource" ), new QgsExternalResourceWidgetFactory( tr( "Attachment" ), messageBar ) ); registerWidget( QStringLiteral( "KeyValue" ), new QgsKeyValueWidgetFactory( tr( "Key/Value" ) ) ); registerWidget( QStringLiteral( "List" ), new QgsListWidgetFactory( tr( "List" ) ) ); registerWidget( QStringLiteral( "Binary" ), new QgsBinaryWidgetFactory( tr( "Binary (BLOB)" ), messageBar ) ); diff --git a/src/gui/editorwidgets/core/qgswidgetwrapper.cpp b/src/gui/editorwidgets/core/qgswidgetwrapper.cpp index b16e5c512e9b..3bf45bd1a360 100644 --- a/src/gui/editorwidgets/core/qgswidgetwrapper.cpp +++ b/src/gui/editorwidgets/core/qgswidgetwrapper.cpp @@ -28,7 +28,8 @@ const QgsPropertiesDefinition &QgsWidgetWrapper::propertyDefinitions() properties = { { RootPath, QgsPropertyDefinition( "propertyRootPath", QgsPropertyDefinition::DataTypeString, QObject::tr( "Root path" ), QObject::tr( "string of variable length representing root path to attachment" ) ) }, - { DocumentViewerContent, QgsPropertyDefinition( "documentViewerContent", QgsPropertyDefinition::DataTypeString, QObject::tr( "Document viewer content" ), QObject::tr( "string" ) + "NoContent|Image|Web" ) } + { DocumentViewerContent, QgsPropertyDefinition( "documentViewerContent", QgsPropertyDefinition::DataTypeString, QObject::tr( "Document viewer content" ), QObject::tr( "string" ) + "NoContent|Image|Web" ) }, + { StorageUrl, QgsPropertyDefinition( "storageUrl", QgsPropertyDefinition::DataTypeString, QObject::tr( "Storage Url" ), QObject::tr( "String of variable length representing the URL used to store document with an external storage" ) ) } }; } return properties; diff --git a/src/gui/editorwidgets/core/qgswidgetwrapper.h b/src/gui/editorwidgets/core/qgswidgetwrapper.h index 4074112ed879..e3d57d7d53e1 100644 --- a/src/gui/editorwidgets/core/qgswidgetwrapper.h +++ b/src/gui/editorwidgets/core/qgswidgetwrapper.h @@ -76,7 +76,8 @@ class GUI_EXPORT QgsWidgetWrapper : public QObject enum Property { RootPath = 0, //!< Root path for external resource - DocumentViewerContent //!< Document type for external resource + DocumentViewerContent, //!< Document type for external resource + StorageUrl //!< Storage URL for external resource }; /** diff --git a/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp b/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp index 2af10be0d9b9..a5286fcc16f4 100644 --- a/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp +++ b/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp @@ -22,8 +22,12 @@ #include "qgsvectorlayer.h" #include "qgspropertyoverridebutton.h" #include "qgseditorwidgetwrapper.h" +#include "qgsexternalstorage.h" +#include "qgsexternalstorageregistry.h" +#include "qgsexpressioncontextutils.h" #include +#include class QgsExternalResourceWidgetWrapper; @@ -32,6 +36,20 @@ QgsExternalResourceConfigDlg::QgsExternalResourceConfigDlg( QgsVectorLayer *vl, { setupUi( this ); + mStorageType->addItem( tr( "Select existing file" ), QString() ); + for ( QgsExternalStorage *storage : QgsApplication::externalStorageRegistry()->externalStorages() ) + { + mStorageType->addItem( tr( "Store with %1" ).arg( storage->type() ), storage->type() ); + } + + mExternalStorageGroupBox->setVisible( false ); + + initializeDataDefinedButton( mStorageUrlPropertyOverrideButton, QgsEditorWidgetWrapper::StorageUrl ); + mStorageUrlPropertyOverrideButton->registerVisibleWidget( mStorageUrlExpression ); + mStorageUrlPropertyOverrideButton->registerExpressionWidget( mStorageUrlExpression ); + mStorageUrlPropertyOverrideButton->registerVisibleWidget( mStorageUrl, false ); + mStorageUrlPropertyOverrideButton->registerExpressionContextGenerator( this ); + // By default, uncheck some options mUseLink->setChecked( false ); mFullUrl->setChecked( false ); @@ -69,6 +87,7 @@ QgsExternalResourceConfigDlg::QgsExternalResourceConfigDlg( QgsVectorLayer *vl, mRelativeButtonGroup->setId( mRelativeDefault, QgsFileWidget::RelativeDefaultPath ); mRelativeProject->setChecked( true ); + connect( mStorageType, static_cast( &QComboBox::currentIndexChanged ), this, &QgsExternalResourceConfigDlg::changeStorageType ); connect( mFileWidgetGroupBox, &QGroupBox::toggled, this, &QgsEditorConfigWidget::changed ); connect( mFileWidgetButtonGroupBox, &QGroupBox::toggled, this, &QgsEditorConfigWidget::changed ); connect( mFileWidgetFilterLineEdit, &QLineEdit::textChanged, this, &QgsEditorConfigWidget::changed ); @@ -82,6 +101,7 @@ QgsExternalResourceConfigDlg::QgsExternalResourceConfigDlg( QgsVectorLayer *vl, { mDocumentViewerContentSettingsWidget->setEnabled( ( QgsExternalResourceWidget::DocumentViewerContent )idx != QgsExternalResourceWidget::NoContent ); } ); connect( mDocumentViewerHeight, static_cast( &QSpinBox::valueChanged ), this, &QgsEditorConfigWidget::changed ); connect( mDocumentViewerWidth, static_cast( &QSpinBox::valueChanged ), this, &QgsEditorConfigWidget::changed ); + connect( mStorageUrlExpression, &QLineEdit::textChanged, this, &QgsEditorConfigWidget::changed ); mDocumentViewerContentComboBox->addItem( tr( "No Content" ), QgsExternalResourceWidget::NoContent ); mDocumentViewerContentComboBox->addItem( tr( "Image" ), QgsExternalResourceWidget::Image ); @@ -135,6 +155,11 @@ QVariantMap QgsExternalResourceConfigDlg::config() { QVariantMap cfg; + cfg.insert( QStringLiteral( "StorageType" ), mStorageType->currentData() ); + cfg.insert( QStringLiteral( "StorageAuthConfigId" ), mAuthSettingsProtocol->configId() ); + if ( !mStorageUrl->text().isEmpty() ) + cfg.insert( QStringLiteral( "StorageUrl" ), mStorageUrl->text() ); + cfg.insert( QStringLiteral( "FileWidget" ), mFileWidgetGroupBox->isChecked() ); cfg.insert( QStringLiteral( "FileWidgetButton" ), mFileWidgetButtonGroupBox->isChecked() ); cfg.insert( QStringLiteral( "FileWidgetFilter" ), mFileWidgetFilterLineEdit->text() ); @@ -174,6 +199,16 @@ QVariantMap QgsExternalResourceConfigDlg::config() void QgsExternalResourceConfigDlg::setConfig( const QVariantMap &config ) { + if ( config.contains( QStringLiteral( "StorageType" ) ) ) + { + const int index = mStorageType->findData( config.value( QStringLiteral( "StorageType" ) ) ); + if ( index >= 0 ) + mStorageType->setCurrentIndex( index ); + } + + mAuthSettingsProtocol->setConfigId( config.value( QStringLiteral( "StorageAuthConfigId" ) ).toString() ); + mStorageUrl->setText( config.value( QStringLiteral( "StorageUrl" ) ).toString() ); + if ( config.contains( QStringLiteral( "FileWidget" ) ) ) { mFileWidgetGroupBox->setChecked( config.value( QStringLiteral( "FileWidget" ) ).toBool() ); @@ -240,3 +275,23 @@ void QgsExternalResourceConfigDlg::setConfig( const QVariantMap &config ) } } } + +QgsExpressionContext QgsExternalResourceConfigDlg::createExpressionContext() const +{ + QgsExpressionContext context = QgsEditorConfigWidget::createExpressionContext(); + context << QgsExpressionContextUtils::formScope( ); + context << QgsExpressionContextUtils::parentFormScope( ); + + QgsExpressionContextScope *fileWidgetScope = QgsFileWidget::createFileWidgetScope(); + context << fileWidgetScope; + + context.setHighlightedVariables( fileWidgetScope->variableNames() ); + return context; +} + +void QgsExternalResourceConfigDlg::changeStorageType( int storageType ) +{ + // first one in combo box is not an external storage + mExternalStorageGroupBox->setVisible( storageType ); + emit changed(); +} diff --git a/src/gui/editorwidgets/qgsexternalresourceconfigdlg.h b/src/gui/editorwidgets/qgsexternalresourceconfigdlg.h index bcfe612131c6..411dcfbdc8b3 100644 --- a/src/gui/editorwidgets/qgsexternalresourceconfigdlg.h +++ b/src/gui/editorwidgets/qgsexternalresourceconfigdlg.h @@ -38,6 +38,8 @@ class GUI_EXPORT QgsExternalResourceConfigDlg : public QgsEditorConfigWidget, pr //! Constructor for QgsExternalResourceConfigDlg explicit QgsExternalResourceConfigDlg( QgsVectorLayer *vl, int fieldIdx, QWidget *parent = nullptr ); + QgsExpressionContext createExpressionContext() const override; + // QgsEditorConfigWidget interface public: QVariantMap config() override; @@ -49,6 +51,10 @@ class GUI_EXPORT QgsExternalResourceConfigDlg : public QgsEditorConfigWidget, pr //! Modify RelativeDefault according to mRootPath content void enableRelativeDefault(); + + //! change storage type according to index from storage type combo box + void changeStorageType( int index ); + }; #endif // QGSEXTERNALRESOURCECONFIGDLG_H diff --git a/src/gui/editorwidgets/qgsexternalresourcewidgetfactory.cpp b/src/gui/editorwidgets/qgsexternalresourcewidgetfactory.cpp index 0aff7109993d..754f17e97ca2 100644 --- a/src/gui/editorwidgets/qgsexternalresourcewidgetfactory.cpp +++ b/src/gui/editorwidgets/qgsexternalresourcewidgetfactory.cpp @@ -19,14 +19,15 @@ #include "qgsexternalresourcewidgetwrapper.h" #include "qgsexternalresourceconfigdlg.h" -QgsExternalResourceWidgetFactory::QgsExternalResourceWidgetFactory( const QString &name ) +QgsExternalResourceWidgetFactory::QgsExternalResourceWidgetFactory( const QString &name, QgsMessageBar *messageBar ) : QgsEditorWidgetFactory( name ) + , mMessageBar( messageBar ) { } QgsEditorWidgetWrapper *QgsExternalResourceWidgetFactory::create( QgsVectorLayer *vl, int fieldIdx, QWidget *editor, QWidget *parent ) const { - return new QgsExternalResourceWidgetWrapper( vl, fieldIdx, editor, parent ); + return new QgsExternalResourceWidgetWrapper( vl, fieldIdx, editor, mMessageBar, parent ); } QgsEditorConfigWidget *QgsExternalResourceWidgetFactory::configWidget( QgsVectorLayer *vl, int fieldIdx, QWidget *parent ) const diff --git a/src/gui/editorwidgets/qgsexternalresourcewidgetfactory.h b/src/gui/editorwidgets/qgsexternalresourcewidgetfactory.h index 6587e1aecdb6..d4b0a616fa5f 100644 --- a/src/gui/editorwidgets/qgsexternalresourcewidgetfactory.h +++ b/src/gui/editorwidgets/qgsexternalresourcewidgetfactory.h @@ -22,6 +22,7 @@ SIP_NO_FILE +class QgsMessageBar; /** * \ingroup gui @@ -35,15 +36,18 @@ class GUI_EXPORT QgsExternalResourceWidgetFactory : public QgsEditorWidgetFactor /** * Constructor for QgsExternalResourceWidgetFactory, where \a name is a human-readable - * name for the factory. + * name for the factory and \a messageBar the message bar used to report messages. */ - QgsExternalResourceWidgetFactory( const QString &name ); + QgsExternalResourceWidgetFactory( const QString &name, QgsMessageBar *messageBar ); // QgsEditorWidgetFactory interface public: QgsEditorWidgetWrapper *create( QgsVectorLayer *vl, int fieldIdx, QWidget *editor, QWidget *parent ) const override; QgsEditorConfigWidget *configWidget( QgsVectorLayer *vl, int fieldIdx, QWidget *parent ) const override; unsigned int fieldScore( const QgsVectorLayer *vl, int fieldIdx ) const override; + + private: + QgsMessageBar *mMessageBar = nullptr; }; #endif // QGSEXTERNALRESOURCEWIDGETFACTORY_H diff --git a/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp b/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp index 047bcf2dce29..8b294ce8378a 100644 --- a/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp @@ -26,9 +26,9 @@ #include "qgsexpressioncontextutils.h" -QgsExternalResourceWidgetWrapper::QgsExternalResourceWidgetWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor, QWidget *parent ) +QgsExternalResourceWidgetWrapper::QgsExternalResourceWidgetWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor, QgsMessageBar *messageBar, QWidget *parent ) : QgsEditorWidgetWrapper( layer, fieldIdx, editor, parent ) - + , mMessageBar( messageBar ) { } @@ -121,6 +121,11 @@ void QgsExternalResourceWidgetWrapper::setFeature( const QgsFeature &feature ) { updateProperties( feature ); QgsEditorWidgetWrapper::setFeature( feature ); + + if ( mQgsWidget ) + { + updateFileWidgetExpressionContext(); + } } QWidget *QgsExternalResourceWidgetWrapper::createWidget( QWidget *parent ) @@ -152,9 +157,21 @@ void QgsExternalResourceWidgetWrapper::initWidget( QWidget *editor ) if ( mQgsWidget ) { + mQgsWidget->setMessageBar( mMessageBar ); + mQgsWidget->fileWidget()->setStorageMode( QgsFileWidget::GetFile ); const QVariantMap cfg = config(); + mPropertyCollection.loadVariant( cfg.value( QStringLiteral( "PropertyCollection" ) ), propertyDefinitions() ); + + mQgsWidget->setStorageType( cfg.value( QStringLiteral( "StorageType" ) ).toString() ); + mQgsWidget->setStorageAuthConfigId( cfg.value( QStringLiteral( "StorageAuthConfigId" ) ).toString() ); + + mQgsWidget->fileWidget()->setStorageUrlExpression( mPropertyCollection.isActive( QgsWidgetWrapper::StorageUrl ) ? + mPropertyCollection.property( QgsWidgetWrapper::StorageUrl ).asExpression() : + QgsExpression::quotedValue( cfg.value( QStringLiteral( "StorageUrl" ) ).toString() ) ); + + updateFileWidgetExpressionContext(); if ( cfg.contains( QStringLiteral( "UseLink" ) ) ) { @@ -165,7 +182,6 @@ void QgsExternalResourceWidgetWrapper::initWidget( QWidget *editor ) mQgsWidget->fileWidget()->setFullUrl( cfg.value( QStringLiteral( "FullUrl" ) ).toBool() ); } - mPropertyCollection.loadVariant( cfg.value( QStringLiteral( "PropertyCollection" ) ), propertyDefinitions() ); if ( !mPropertyCollection.isActive( QgsWidgetWrapper::RootPath ) ) { mQgsWidget->setDefaultRoot( cfg.value( QStringLiteral( "DefaultRoot" ) ).toString() ); @@ -304,3 +320,19 @@ void QgsExternalResourceWidgetWrapper::updateConstraintWidgetStatus() } } } + +void QgsExternalResourceWidgetWrapper::updateFileWidgetExpressionContext() +{ + if ( !mQgsWidget || !layer() ) + return; + + QgsExpressionContext expressionContext( layer()->createExpressionContext() ); + expressionContext.setFeature( formFeature() ); + expressionContext.appendScope( QgsExpressionContextUtils::formScope( formFeature() ) ); + if ( context().parentFormFeature().isValid() ) + { + expressionContext.appendScope( QgsExpressionContextUtils::parentFormScope( context().parentFormFeature() ) ); + } + + mQgsWidget->fileWidget()->setExpressionContext( expressionContext ); +} diff --git a/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h b/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h index 155136b66695..d6a75d8a0309 100644 --- a/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h +++ b/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h @@ -52,8 +52,9 @@ class GUI_EXPORT QgsExternalResourceWidgetWrapper : public QgsEditorWidgetWrappe * new widget should be autogenerated. * * A \a parent widget for this widget wrapper and the created widget can also be specified. + * A \a messageBar to report messages can also be specified */ - explicit QgsExternalResourceWidgetWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor = nullptr, QWidget *parent = nullptr ); + explicit QgsExternalResourceWidgetWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor = nullptr, QgsMessageBar *messageBar = nullptr, QWidget *parent = nullptr ); // QgsEditorWidgetWrapper interface public: @@ -65,6 +66,9 @@ class GUI_EXPORT QgsExternalResourceWidgetWrapper : public QgsEditorWidgetWrappe void initWidget( QWidget *editor ) override; bool valid() const override; + // update file widget current expression context according to layer, feature, and parent feature + void updateFileWidgetExpressionContext(); + public slots: void setFeature( const QgsFeature &feature ) override; void setEnabled( bool enabled ) override; @@ -90,6 +94,7 @@ class GUI_EXPORT QgsExternalResourceWidgetWrapper : public QgsEditorWidgetWrappe QLabel *mLabel = nullptr; QgsAttributeForm *mForm = nullptr; QgsExternalResourceWidget *mQgsWidget = nullptr; + QgsMessageBar *mMessageBar = nullptr; friend class TestQgsExternalResourceWidgetWrapper; diff --git a/src/gui/qgsexternalresourcewidget.cpp b/src/gui/qgsexternalresourcewidget.cpp index 4b50d488c806..b32fbdcf33f8 100644 --- a/src/gui/qgsexternalresourcewidget.cpp +++ b/src/gui/qgsexternalresourcewidget.cpp @@ -18,17 +18,24 @@ #include "qgspixmaplabel.h" #include "qgsproject.h" #include "qgsapplication.h" +#include "qgsnetworkaccessmanager.h" +#include "qgstaskmanager.h" +#include "qgsexternalstorage.h" +#include "qgsmessagebar.h" #include #include #include #include #include +#include +#include +#include +#include #ifdef WITH_QTWEBKIT #include #endif - QgsExternalResourceWidget::QgsExternalResourceWidget( QWidget *parent ) : QWidget( parent ) { @@ -50,6 +57,16 @@ QgsExternalResourceWidget::QgsExternalResourceWidget( QWidget *parent ) layout->addWidget( mWebView, 2, 0 ); #endif + mLoadingLabel = new QLabel( this ); + layout->addWidget( mLoadingLabel, 3, 0 ); + mLoadingMovie = new QMovie( QgsApplication::iconPath( QStringLiteral( "/mIconLoading.gif" ) ), QByteArray(), this ); + mLoadingMovie->setScaledSize( QSize( 32, 32 ) ); + mLoadingLabel->setMovie( mLoadingMovie ); + + mErrorLabel = new QLabel( this ); + layout->addWidget( mErrorLabel, 4, 0 ); + mErrorLabel->setPixmap( QPixmap( QgsApplication::iconPath( QStringLiteral( "/mIconWarning.svg" ) ) ) ); + updateDocumentViewer(); setLayout( layout ); @@ -134,6 +151,10 @@ void QgsExternalResourceWidget::setReadOnly( bool readOnly ) void QgsExternalResourceWidget::updateDocumentViewer() { + mErrorLabel->setVisible( false ); + mLoadingLabel->setVisible( false ); + mLoadingMovie->stop(); + #ifdef WITH_QTWEBKIT mWebView->setVisible( mDocumentViewerContent == Web ); #endif @@ -212,51 +233,132 @@ void QgsExternalResourceWidget::setRelativeStorage( QgsFileWidget::RelativeStora mRelativeStorage = relativeStorage; } -void QgsExternalResourceWidget::loadDocument( const QString &path ) +void QgsExternalResourceWidget::setStorageType( const QString &storageType ) { - QString resolvedPath; + mFileWidget->setStorageType( storageType ); +} - if ( path.isEmpty() ) - { +QString QgsExternalResourceWidget::storageType() const +{ + return mFileWidget->storageType(); +} + +void QgsExternalResourceWidget::setStorageAuthConfigId( const QString &authCfg ) +{ + mFileWidget->setStorageAuthConfigId( authCfg ); +} + +const QString &QgsExternalResourceWidget::storageAuthConfigId() const +{ + return mFileWidget->storageAuthConfigId(); +} + +void QgsExternalResourceWidget::setMessageBar( QgsMessageBar *messageBar ) +{ + mFileWidget->setMessageBar( messageBar ); +} + +QgsMessageBar *QgsExternalResourceWidget::messageBar() const +{ + return mFileWidget->messageBar(); +} + +void QgsExternalResourceWidget::updateDocumentContent( const QString &filePath ) +{ #ifdef WITH_QTWEBKIT - if ( mDocumentViewerContent == Web ) - { - mWebView->setUrl( QUrl( QStringLiteral( "about:blank" ) ) ); - } + if ( mDocumentViewerContent == Web ) + { + mWebView->load( QUrl::fromEncoded( filePath.toUtf8() ) ); + mWebView->page()->settings()->setAttribute( QWebSettings::LocalStorageEnabled, true ); + } #endif - if ( mDocumentViewerContent == Image ) - { + + if ( mDocumentViewerContent == Image ) + { + // use an image reader to ensure image orientation and transforms are correctly handled + QImageReader ir( filePath ); + ir.setAutoTransform( true ); + const QPixmap pm = QPixmap::fromImage( ir.read() ); + if ( !pm.isNull() ) + mPixmapLabel->setPixmap( pm ); + else mPixmapLabel->clear(); - updateDocumentViewer(); - } } - else - { - resolvedPath = resolvePath( path ); + updateDocumentViewer(); +} + +void QgsExternalResourceWidget::clearContent() +{ #ifdef WITH_QTWEBKIT - if ( mDocumentViewerContent == Web ) - { - mWebView->load( QUrl::fromEncoded( resolvedPath.toUtf8() ) ); - mWebView->page()->settings()->setAttribute( QWebSettings::LocalStorageEnabled, true ); - } + if ( mDocumentViewerContent == Web ) + { + mWebView->setUrl( QUrl( QStringLiteral( "about:blank" ) ) ); + } #endif + if ( mDocumentViewerContent == Image ) + { + mPixmapLabel->clear(); + updateDocumentViewer(); + } +} + +void QgsExternalResourceWidget::loadDocument( const QString &path ) +{ + if ( path.isEmpty() || path == QgsApplication::nullRepresentation() ) + { + clearContent(); + } + else if ( mDocumentViewerContent != NoContent ) + { + const QString resolvedPath = resolvePath( path ); - if ( mDocumentViewerContent == Image ) + if ( mFileWidget->externalStorage() ) { - // use an image reader to ensure image orientation and transforms are correctly handled - QImageReader ir( resolvedPath ); - ir.setAutoTransform( true ); - const QPixmap pm = QPixmap::fromImage( ir.read() ); - if ( !pm.isNull() ) - { - mPixmapLabel->setPixmap( pm ); - } - else + QgsExternalStorageFetchedContent *content = mFileWidget->externalStorage()->fetch( resolvedPath, storageAuthConfigId() ); + + auto onFetchFinished = [ = ] { - mPixmapLabel->clear(); - } - updateDocumentViewer(); + if ( content->status() == Qgis::ContentStatus::Failed ) + { + mWebView->setVisible( false ); + mPixmapLabel->setVisible( false ); + mLoadingLabel->setVisible( false ); + mLoadingMovie->stop(); + mErrorLabel->setVisible( true ); + + if ( messageBar() ) + { + messageBar()->pushWarning( tr( "Fetching External Resource" ), + tr( "Error while fetching external resource '%1' : %2" ).arg( path, content->errorString() ) ); + } + } + else if ( content->status() == Qgis::ContentStatus::Finished ) + { + const QString filePath = mDocumentViewerContent == Web + ? QString( "file://%1" ).arg( content->filePath() ) + : content->filePath(); + + updateDocumentContent( filePath ); + } + + content->deleteLater(); + }; + + mWebView->setVisible( false ); + mPixmapLabel->setVisible( false ); + mErrorLabel->setVisible( false ); + mLoadingLabel->setVisible( true ); + mLoadingMovie->start(); + connect( content, &QgsExternalStorageFetchedContent::fetched, onFetchFinished ); + connect( content, &QgsExternalStorageFetchedContent::errorOccurred, onFetchFinished ); + connect( content, &QgsExternalStorageFetchedContent::canceled, onFetchFinished ); + + content->fetch(); + } + else + { + updateDocumentContent( resolvedPath ); } } } diff --git a/src/gui/qgsexternalresourcewidget.h b/src/gui/qgsexternalresourcewidget.h index ebb1e5d4e711..756344e4a480 100644 --- a/src/gui/qgsexternalresourcewidget.h +++ b/src/gui/qgsexternalresourcewidget.h @@ -19,6 +19,7 @@ class QWebView; class QgsPixmapLabel; +class QgsMessageBar; #include #include @@ -143,6 +144,47 @@ class GUI_EXPORT QgsExternalResourceWidget : public QWidget */ void setDefaultRoot( const QString &defaultRoot ); + /** + * Set \a storageType storage type unique identifier as defined in QgsExternalStorageRegistry or + * null QString if there is no storage defined, only file selection. + * \see storageType + * \since QGIS 3.22 + */ + void setStorageType( const QString &storageType ); + + /** + * Get storage type unique identifier as defined in QgsExternalStorageRegistry. + * Returns null QString if there is no storage defined, only file selection. + * \see setStorageType + * \since QGIS 3.22 + */ + QString storageType() const; + + /** + * Sets the authentication configuration ID to be used for the current external storage (if + * defined) + * \since QGIS 3.22 + */ + void setStorageAuthConfigId( const QString &authCfg ); + + /** + * Returns the authentication configuration ID used for the current external storage (if defined) + * \since QGIS 3.22 + */ + const QString &storageAuthConfigId() const; + + /** + * Set \a messageBar to report messages + * \since 3.22 + */ + void setMessageBar( QgsMessageBar *messageBar ); + + /** + * Returns message bar used to report messages + * \since 3.22 + */ + QgsMessageBar *messageBar() const; + signals: //! emitteed as soon as the current document changes void valueChanged( const QString & ); @@ -153,6 +195,16 @@ class GUI_EXPORT QgsExternalResourceWidget : public QWidget private: void updateDocumentViewer(); + /** + * update document content with \a filePath + */ + void updateDocumentContent( const QString &filePath ); + + /** + * Clear content from widget + */ + void clearContent(); + QString resolvePath( const QString &path ); //! properties @@ -170,7 +222,11 @@ class GUI_EXPORT QgsExternalResourceWidget : public QWidget //! This webview is used as a container to display the picture QWebView *mWebView = nullptr; #endif + QLabel *mLoadingLabel = nullptr; + QLabel *mErrorLabel = nullptr; + QMovie *mLoadingMovie = nullptr; + friend class TestQgsExternalResourceWidgetWrapper; }; #endif // QGSEXTERNALRESOURCEWIDGET_H diff --git a/src/gui/qgsfilewidget.cpp b/src/gui/qgsfilewidget.cpp index 8affcf3b31aa..463e73d4a22a 100644 --- a/src/gui/qgsfilewidget.cpp +++ b/src/gui/qgsfilewidget.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include "qgssettings.h" #include "qgsfilterlineedit.h" @@ -32,6 +34,11 @@ #include "qgsapplication.h" #include "qgsfileutils.h" #include "qgsmimedatautils.h" +#include "qgsexternalstorage.h" +#include "qgsexternalstorageregistry.h" +#include "qgsmessagebar.h" + +#define FILEPATH_VARIABLE "selected_file_path" QgsFileWidget::QgsFileWidget( QWidget *parent ) : QWidget( parent ) @@ -52,10 +59,7 @@ QgsFileWidget::QgsFileWidget( QWidget *parent ) mLinkLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); mLinkLabel->setTextFormat( Qt::RichText ); mLinkLabel->hide(); // do not show by default - mLinkEditButton = new QToolButton( this ); - mLinkEditButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) ); - connect( mLinkEditButton, &QToolButton::clicked, this, &QgsFileWidget::editLink ); - mLinkEditButton->hide(); // do not show by default + mLayout->addWidget( mLinkLabel ); // otherwise, use the traditional QLineEdit subclass mLineEdit = new QgsFileDropEdit( this ); @@ -64,12 +68,31 @@ QgsFileWidget::QgsFileWidget( QWidget *parent ) connect( mLineEdit, &QLineEdit::textChanged, this, &QgsFileWidget::textEdited ); mLayout->addWidget( mLineEdit ); + mLinkEditButton = new QToolButton( this ); + mLinkEditButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) ); + mLayout->addWidget( mLinkEditButton ); + connect( mLinkEditButton, &QToolButton::clicked, this, &QgsFileWidget::editLink ); + mLinkEditButton->hide(); // do not show by default + mFileWidgetButton = new QToolButton( this ); mFileWidgetButton->setText( QChar( 0x2026 ) ); mFileWidgetButton->setToolTip( tr( "Browse" ) ); connect( mFileWidgetButton, &QAbstractButton::clicked, this, &QgsFileWidget::openFileDialog ); mLayout->addWidget( mFileWidgetButton ); + mProgressLabel = new QLabel( this ); + mLayout->addWidget( mProgressLabel ); + mProgressLabel->hide(); + + mProgressBar = new QProgressBar( this ); + mLayout->addWidget( mProgressBar ); + mProgressBar->hide(); + + mCancelButton = new QToolButton( this ); + mLayout->addWidget( mCancelButton ); + mCancelButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mTaskCancel.svg" ) ) ); + mCancelButton->hide(); + setLayout( mLayout ); } @@ -115,6 +138,89 @@ void QgsFileWidget::setReadOnly( bool readOnly ) updateLayout(); } +void QgsFileWidget::setStorageType( const QString &storageType ) +{ + if ( storageType.isEmpty() ) + mExternalStorage = nullptr; + + else + { + mExternalStorage = QgsApplication::externalStorageRegistry()->externalStorageFromType( storageType ); + if ( !mExternalStorage ) + { + QgsDebugMsg( QStringLiteral( "Invalid storage type: %1" ).arg( storageType ) ); + return; + } + addFileWidgetScope(); + } +} + +QString QgsFileWidget::storageType() const +{ + return mExternalStorage ? mExternalStorage->type() : QString(); +} + +QgsExternalStorage *QgsFileWidget::externalStorage() const +{ + return mExternalStorage; +} + +void QgsFileWidget::setStorageAuthConfigId( const QString &authCfg ) +{ + mAuthCfg = authCfg; +} + +const QString &QgsFileWidget::storageAuthConfigId() const +{ + return mAuthCfg; +} + +void QgsFileWidget::setStorageUrlExpression( const QString &urlExpression ) +{ + mStorageUrlExpression.reset( new QgsExpression( urlExpression ) ); +} + +QgsExpression *QgsFileWidget::storageUrlExpression() const +{ + return mStorageUrlExpression.get(); +} + +QString QgsFileWidget::storageUrlExpressionString() const +{ + return mStorageUrlExpression ? mStorageUrlExpression->expression() : QString(); +} + + +void QgsFileWidget::setExpressionContext( const QgsExpressionContext &context ) +{ + mScope = nullptr; // deleted by old context when we override it with the new one + mExpressionContext = context; + addFileWidgetScope(); +} + +void QgsFileWidget::addFileWidgetScope() +{ + if ( !mExternalStorage || mScope ) + return; + + mScope = createFileWidgetScope(); + mExpressionContext << mScope; +} + +QgsExpressionContextScope *QgsFileWidget::createFileWidgetScope() +{ + QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "FileWidget" ) ); + scope->addVariable( QgsExpressionContextScope::StaticVariable( + QStringLiteral( FILEPATH_VARIABLE ), + QString(), true, false, tr( "User selected absolute filepath" ) ) ); + return scope; +} + +const QgsExpressionContext &QgsFileWidget::expressionContext() const +{ + return mExpressionContext; +} + QString QgsFileWidget::dialogTitle() const { return mDialogTitle; @@ -157,12 +263,17 @@ void QgsFileWidget::setFileWidgetButtonVisible( bool visible ) mFileWidgetButton->setVisible( visible ); } +bool QgsFileWidget::isMultiFiles( const QString &path ) +{ + return path.contains( QStringLiteral( "\" \"" ) ); +} + void QgsFileWidget::textEdited( const QString &path ) { mFilePath = path; mLinkLabel->setText( toUrl( path ) ); // Show tooltip if multiple files are selected - if ( path.contains( QStringLiteral( "\" \"" ) ) ) + if ( isMultiFiles( path ) ) { mLineEdit->setToolTip( tr( "Selected files:
  • %1

" ).arg( splitFilePaths( path ).join( QLatin1String( "
  • " ) ) ) ); } @@ -242,41 +353,36 @@ QgsFilterLineEdit *QgsFileWidget::lineEdit() return mLineEdit; } +void QgsFileWidget::setMessageBar( QgsMessageBar *messageBar ) +{ + mMessageBar = messageBar; +} + +QgsMessageBar *QgsFileWidget::messageBar() const +{ + return mMessageBar; +} + void QgsFileWidget::updateLayout() { - mLayout->removeWidget( mLineEdit ); - mLayout->removeWidget( mLinkLabel ); - mLayout->removeWidget( mLinkEditButton ); + mProgressLabel->setVisible( mStoreInProgress ); + mProgressBar->setVisible( mStoreInProgress ); + mCancelButton->setVisible( mStoreInProgress ); - mLinkEditButton->setVisible( mUseLink && !mReadOnly ); + const bool linkVisible = mUseLink && !mIsLinkEdited; + mLineEdit->setVisible( !mStoreInProgress && !linkVisible ); + mLinkLabel->setVisible( !mStoreInProgress && linkVisible ); + mLinkEditButton->setVisible( !mStoreInProgress && mUseLink && !mReadOnly ); + + mFileWidgetButton->setVisible( !mStoreInProgress ); mFileWidgetButton->setEnabled( !mReadOnly ); mLineEdit->setEnabled( !mReadOnly ); - if ( mUseLink && !mIsLinkEdited ) - { - mLayout->insertWidget( 0, mLinkLabel ); - mLineEdit->setVisible( false ); - mLinkLabel->setVisible( true ); + mLinkEditButton->setIcon( linkVisible && !mReadOnly ? + QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) : + QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveEdits.svg" ) ) ); - if ( !mReadOnly ) - { - mLayout->insertWidget( 1, mLinkEditButton ); - mLinkEditButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) ); - } - } - else - { - mLayout->insertWidget( 0, mLineEdit ); - mLineEdit->setVisible( true ); - mLinkLabel->setVisible( false ); - - if ( mIsLinkEdited ) - { - mLayout->insertWidget( 1, mLinkEditButton ); - mLinkEditButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveEdits.svg" ) ) ); - } - } } void QgsFileWidget::openFileDialog() @@ -354,15 +460,20 @@ void QgsFileWidget::openFileDialog() return; if ( mStorageMode != GetMultipleFiles ) + fileNames << fileName; + + setSelectedFileNames( fileNames ); +} + +void QgsFileWidget::setSelectedFileNames( QStringList fileNames ) +{ + Q_ASSERT( fileNames.count() ); + + QgsSettings settings; + + for ( int i = 0; i < fileNames.length(); i++ ) { - fileName = QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileName ).absoluteFilePath() ) ); - } - else - { - for ( int i = 0; i < fileNames.length(); i++ ) - { - fileNames.replace( i, QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileNames.at( i ) ).absoluteFilePath() ) ) ); - } + fileNames.replace( i, QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileNames.at( i ) ).absoluteFilePath() ) ) ); } // Store the last used path: @@ -370,40 +481,127 @@ void QgsFileWidget::openFileDialog() { case GetFile: case SaveFile: - settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileName ).absolutePath() ); + case GetMultipleFiles: + settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileNames.first() ).absolutePath() ); break; case GetDirectory: - settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), fileName ); - break; - case GetMultipleFiles: - settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileNames.first( ) ).absolutePath() ); + settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), fileNames.first() ); break; } // Handle relative Path storage - if ( mStorageMode != GetMultipleFiles ) + for ( int i = 0; i < fileNames.length(); i++ ) { - fileName = relativePath( fileName, true ); - setFilePath( fileName ); + fileNames.replace( i, relativePath( fileNames.at( i ), true ) ); + } + + // store files first, update filePath later + if ( mExternalStorage ) + { + if ( !mStorageUrlExpression->prepare( &mExpressionContext ) ) + { + if ( messageBar() ) + { + messageBar()->pushWarning( tr( "Storing External resource" ), + tr( "Storage URL expression is invalid : %1" ).arg( mStorageUrlExpression->evalErrorString() ) ); + } + + QgsDebugMsg( tr( "Storage URL expression is invalid : %1" ).arg( mStorageUrlExpression->evalErrorString() ) ); + return; + } + + storeExternalFiles( fileNames ); } else { - for ( int i = 0; i < fileNames.length(); i++ ) + setFilePaths( fileNames ); + } +} + +void QgsFileWidget::storeExternalFiles( QStringList fileNames, QStringList storedUrls ) +{ + const QString filePath = fileNames.takeFirst(); + + mProgressLabel->setText( tr( "Storing file %1 ..." ).arg( QFileInfo( filePath ).baseName() ) ); + mStoreInProgress = true; + updateLayout(); + + Q_ASSERT( mScope ); + mScope->setVariable( QStringLiteral( FILEPATH_VARIABLE ), filePath ); + + QVariant url = mStorageUrlExpression->evaluate( &mExpressionContext ); + if ( !url.isValid() ) + { + if ( messageBar() ) + { + messageBar()->pushWarning( tr( "Storing External resource" ), + tr( "Storage URL expression is invalid : %1" ).arg( mStorageUrlExpression->evalErrorString() ) ); + } + + mStoreInProgress = false; + updateLayout(); + + return; + } + + QgsExternalStorageStoredContent *storedContent = mExternalStorage->store( filePath, url.toString(), mAuthCfg ); + + connect( storedContent, &QgsExternalStorageStoredContent::progressChanged, mProgressBar, &QProgressBar::setValue ); + connect( mCancelButton, &QToolButton::clicked, storedContent, &QgsExternalStorageStoredContent::cancel ); + + auto onStoreFinished = [ = ] + { + mStoreInProgress = false; + updateLayout(); + storedContent->deleteLater(); + + if ( storedContent->status() == Qgis::ContentStatus::Failed && messageBar() ) + { + messageBar()->pushWarning( tr( "Storing External resource" ), + tr( "Storing file '%1' to url '%2' has failed : %3" ).arg( filePath, url.toString(), storedContent->errorString() ) ); + } + + if ( storedContent->status() != Qgis::ContentStatus::Finished ) + return; + + QStringList newStoredUrls = storedUrls; + newStoredUrls << storedContent->url(); + + // every thing has been stored, we update filepath + if ( fileNames.isEmpty() ) { - fileNames.replace( i, relativePath( fileNames.at( i ), true ) ); + setFilePaths( newStoredUrls ); } - if ( fileNames.length() > 1 ) + else + storeExternalFiles( fileNames, newStoredUrls ); + }; + + connect( storedContent, &QgsExternalStorageStoredContent::stored, onStoreFinished ); + connect( storedContent, &QgsExternalStorageStoredContent::canceled, onStoreFinished ); + connect( storedContent, &QgsExternalStorageStoredContent::errorOccurred, onStoreFinished ); + + storedContent->store(); +} + +void QgsFileWidget::setFilePaths( const QStringList &filePaths ) +{ + if ( mStorageMode != GetMultipleFiles ) + { + setFilePath( filePaths.first() ); + } + else + { + if ( filePaths.length() > 1 ) { - setFilePath( QStringLiteral( "\"%1\"" ).arg( fileNames.join( QLatin1String( "\" \"" ) ) ) ); + setFilePath( QStringLiteral( "\"%1\"" ).arg( filePaths.join( QLatin1String( "\" \"" ) ) ) ); } else { - setFilePath( fileNames.first( ) ); + setFilePath( filePaths.first( ) ); } } } - QString QgsFileWidget::relativePath( const QString &filePath, bool removeRelative ) const { QString RelativePath; @@ -440,6 +638,11 @@ QString QgsFileWidget::toUrl( const QString &path ) const return QgsApplication::nullRepresentation(); } + if ( isMultiFiles( path ) ) + { + return QStringLiteral( "%1" ).arg( path ); + } + QString urlStr = relativePath( path, false ); QUrl url = QUrl::fromUserInput( urlStr ); if ( !url.isValid() || !url.isLocalFile() ) @@ -463,7 +666,6 @@ QString QgsFileWidget::toUrl( const QString &path ) const } - ///@cond PRIVATE diff --git a/src/gui/qgsfilewidget.h b/src/gui/qgsfilewidget.h index 633d319254fd..7a235d2dc193 100644 --- a/src/gui/qgsfilewidget.h +++ b/src/gui/qgsfilewidget.h @@ -20,14 +20,20 @@ class QLabel; class QToolButton; class QVariant; -class QgsFileDropEdit; class QHBoxLayout; +class QProgressBar; +class QgsExternalStorage; +class QgsFileDropEdit; +class QgsMessageBar; + #include #include #include "qgis_gui.h" #include "qgis_sip.h" #include "qgshighlightablelineedit.h" +#include "qgsexpressioncontext.h" + /** * \ingroup gui @@ -46,6 +52,7 @@ class GUI_EXPORT QgsFileWidget : public QWidget #endif Q_OBJECT + Q_PROPERTY( QString storageType READ storageType WRITE setStorageType ) Q_PROPERTY( bool fileWidgetButtonVisible READ fileWidgetButtonVisible WRITE setFileWidgetButtonVisible ) Q_PROPERTY( bool useLink READ useLink WRITE setUseLink ) Q_PROPERTY( bool fullUrl READ fullUrl WRITE setFullUrl ) @@ -55,6 +62,8 @@ class GUI_EXPORT QgsFileWidget : public QWidget Q_PROPERTY( StorageMode storageMode READ storageMode WRITE setStorageMode ) Q_PROPERTY( RelativeStorage relativeStorage READ relativeStorage WRITE setRelativeStorage ) Q_PROPERTY( QFileDialog::Options options READ options WRITE setOptions ) + Q_PROPERTY( QString auth READ storageAuthConfigId WRITE setStorageAuthConfigId ) + Q_PROPERTY( QString storageUrlExpression READ storageUrlExpressionString WRITE setStorageUrlExpression ) public: @@ -116,6 +125,86 @@ class GUI_EXPORT QgsFileWidget : public QWidget */ void setReadOnly( bool readOnly ); + /** + * Set \a storageType storage type unique identifier as defined in QgsExternalStorageRegistry or + * null QString if there is no storage defined. + * If no external storage has been defined, QgsFileWidget will only update file path according to + * selected files. + * \see storageType + * \since QGIS 3.22 + */ + void setStorageType( const QString &storageType ); + + /** + * Get storage type unique identifier as defined in QgsExternalStorageRegistry. + * Returns null QString if there is no storage defined, only file selection. + * \see setStorageType + * \since QGIS 3.22 + */ + QString storageType() const; + + /** + * Returns external storage used to store selected file names, nullptr if none have been defined. + * If no external storage has been defined, QgsFileWidget will only update file path according to + * selected files. + * \see setStorageType + * \since QGIS 3.22 + */ + QgsExternalStorage *externalStorage() const; + + /** + * Sets the authentication configuration ID to be used for the current external storage (if + * defined) + * \since QGIS 3.22 + */ + void setStorageAuthConfigId( const QString &authCfg ); + + /** + * Returns the authentication configuration ID used for the current external storage (if defined) + * \since QGIS 3.22 + */ + const QString &storageAuthConfigId() const; + + /** + * Set \a urlExpression expression, which once evaluated, provide the URL used to store selected + * documents. This is used only if an external storage has been defined + * \see setStorageType(), externalStorage() + * \since 3.22 + */ + void setStorageUrlExpression( const QString &urlExpression ); + + /** + * Returns the original, unmodified expression string, which once evaluated, provide the + * URL used to store selected documents. This is used only if an external storage has been defined. + * Returns null if no expression has been set. + * \see setStorageUrlExpression() + * \since 3.22 + */ + QString storageUrlExpressionString() const; + + /** + * Returns expression, which once evaluated, provide the URL used to store selected + * documents. This is used only if an external storage has been defined. + * Returns null if no expression has been set. + * \see setStorageUrlExpression() + * \since 3.22 + */ + QgsExpression *storageUrlExpression() const; + + /** + * Set expression context to be used when for storage URL expression evaluation + * \see setStorageUrlExpression + * \since 3.22 + */ + void setExpressionContext( const QgsExpressionContext &context ); + + /** + * Returns expression context used for storage url expression evaluation + * \see storageUrlExpression + * \since 3.22 + */ + const QgsExpressionContext &expressionContext() const; + /** * Returns the open file dialog title. * @@ -285,6 +374,24 @@ class GUI_EXPORT QgsFileWidget : public QWidget */ QgsFilterLineEdit *lineEdit(); + /** + * Set \a messageBar to report messages + * \since QGIS 3.22 + */ + void setMessageBar( QgsMessageBar *messageBar ); + + /** + * Returns message bar used to report messages + * \since QGIS 3.22 + */ + QgsMessageBar *messageBar() const; + + /** + * Creates and Returns an expression context scope specific to QgsFileWidget + * It defines the variable containing the user selected file name + */ + static QgsExpressionContextScope *createFileWidgetScope(); + signals: /** @@ -300,12 +407,31 @@ class GUI_EXPORT QgsFileWidget : public QWidget private: void updateLayout(); + // called whenever user select file names from dialog + void setSelectedFileNames( QStringList fileNames ); + + // add file widget specific scope to expression context + void addFileWidgetScope(); + + // stores \a fileNames files using current external storage. + // This is a recursive method, \a storedUrls contains urls for previously stored + // fileNames. When all files have been successfully stored, current mFilePath + // is updated + void storeExternalFiles( QStringList fileNames, QStringList storedUrls = QStringList() ); + + // Returns true if \a path is a multifiles + static bool isMultiFiles( const QString &path ); + + // Update filePath according to a file path list + void setFilePaths( const QStringList &filePaths ); + QString mFilePath; bool mButtonVisible = true; bool mUseLink = false; bool mFullUrl = false; bool mReadOnly = false; bool mIsLinkEdited = false; + bool mStoreInProgress = false; QString mDialogTitle; QString mFilter; QString mSelectedFilter; @@ -314,12 +440,21 @@ class GUI_EXPORT QgsFileWidget : public QWidget StorageMode mStorageMode = GetFile; RelativeStorage mRelativeStorage = Absolute; QFileDialog::Options mOptions = QFileDialog::Options(); + QgsExternalStorage *mExternalStorage = nullptr; + QString mAuthCfg; + std::unique_ptr< QgsExpression > mStorageUrlExpression; + QgsExpressionContext mExpressionContext; + QgsExpressionContextScope *mScope = nullptr; QLabel *mLinkLabel = nullptr; QgsFileDropEdit *mLineEdit = nullptr; QToolButton *mLinkEditButton = nullptr; QToolButton *mFileWidgetButton = nullptr; QHBoxLayout *mLayout = nullptr; + QLabel *mProgressLabel = nullptr; + QProgressBar *mProgressBar = nullptr; + QToolButton *mCancelButton = nullptr; + QgsMessageBar *mMessageBar = nullptr; //! returns a HTML code with a link to the given file path QString toUrl( const QString &path ) const; @@ -328,6 +463,7 @@ class GUI_EXPORT QgsFileWidget : public QWidget QString relativePath( const QString &filePath, bool removeRelative ) const; friend class TestQgsFileWidget; + friend class TestQgsExternalResourceWidgetWrapper; }; ///@cond PRIVATE diff --git a/src/ui/editorwidgets/qgsexternalresourceconfigdlg.ui b/src/ui/editorwidgets/qgsexternalresourceconfigdlg.ui index b42331f86659..43363606aeda 100644 --- a/src/ui/editorwidgets/qgsexternalresourceconfigdlg.ui +++ b/src/ui/editorwidgets/qgsexternalresourceconfigdlg.ui @@ -8,7 +8,7 @@ 0 0 481 - 690 + 803 @@ -46,11 +46,87 @@ 0 0 - 481 - 690 + 467 + 835 + + + + Type + + + + + + + + + + + + External storage + + + + + + + + Store URL + + + + + + + + + + + + + + + + + + + + + + Authentication + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 0 + 0 + + + + + + + + + + @@ -198,7 +274,7 @@ - + 0 @@ -478,6 +554,11 @@ + + QgsPropertyOverrideButton + QToolButton +
    qgspropertyoverridebutton.h
    +
    QgsSpinBox QSpinBox @@ -490,9 +571,10 @@ 1 - QgsPropertyOverrideButton - QToolButton -
    qgspropertyoverridebutton.h
    + QgsAuthSettingsWidget + QWidget +
    auth/qgsauthsettingswidget.h
    + 1
    @@ -520,7 +602,7 @@ - + diff --git a/tests/src/gui/testqgsexternalresourcewidgetwrapper.cpp b/tests/src/gui/testqgsexternalresourcewidgetwrapper.cpp index 01ebd79e1c69..cfbb74337359 100644 --- a/tests/src/gui/testqgsexternalresourcewidgetwrapper.cpp +++ b/tests/src/gui/testqgsexternalresourcewidgetwrapper.cpp @@ -19,9 +19,19 @@ #include "qgsvectorlayer.h" #include "qgsexternalresourcewidgetwrapper.h" #include "qgsexternalresourcewidget.h" +#include "qgsexternalstorage.h" +#include "qgsexternalstorageregistry.h" +#include "qgspixmaplabel.h" +#include "qgsmessagebar.h" +#include #include #include +#include + +#ifdef WITH_QTWEBKIT +#include +#endif /** * @ingroup UnitTests @@ -37,12 +47,156 @@ class TestQgsExternalResourceWidgetWrapper : public QObject void cleanupTestCase();// will be called after the last testfunction was executed. void init();// will be called before each testfunction is executed. void cleanup();// will be called after every testfunction. - void test_setNullValues(); + void testSetNullValues(); + + void testUrlStorageExpression(); + void testLoadExternalDocument_data(); + void testLoadExternalDocument(); + void testLoadNullExternalDocument_data(); + void testLoadNullExternalDocument(); + void testStoreExternalDocument_data(); + void testStoreExternalDocument(); + void testStoreExternalDocumentError_data(); + void testStoreExternalDocumentError(); + void testStoreExternalDocumentCancel_data(); + void testStoreExternalDocumentCancel(); + void testStoreExternalDocumentNoExpression_data(); + void testStoreExternalDocumentNoExpression(); private: std::unique_ptr vl; }; +class QgsTestExternalStorageFetchedContent + : public QgsExternalStorageFetchedContent +{ + Q_OBJECT + + public: + + QgsTestExternalStorageFetchedContent( bool cached ) + : QgsExternalStorageFetchedContent() + , mCached( cached ) + { + } + + void fetch() override + { + if ( mCached ) + { + mStatus = Qgis::ContentStatus::Finished; + emit fetched(); + } + else + mStatus = Qgis::ContentStatus::Running; + } + + + QString filePath() const override + { + return TEST_DATA_DIR + QStringLiteral( "/sample_image.png" ); + } + + void emitFetched() + { + mStatus = Qgis::ContentStatus::Finished; + emit fetched(); + } + + void emitErrorOccurred() + { + mStatus = Qgis::ContentStatus::Failed; + mErrorString = QStringLiteral( "an error" ); + emit errorOccurred( mErrorString ); + } + + void cancel() override {} + + private: + + bool mCached = false; +}; + +class QgsTestExternalStorageStoredContent + : public QgsExternalStorageStoredContent +{ + Q_OBJECT + + public: + + QgsTestExternalStorageStoredContent( const QString &url ) + : QgsExternalStorageStoredContent(), + mUrl( url ) + { + } + + void store() override + { + mStatus = Qgis::ContentStatus::Running; + } + + void emitStored() + { + mStatus = Qgis::ContentStatus::Finished; + emit stored(); + } + + void emitErrorOccurred() + { + mStatus = Qgis::ContentStatus::Failed; + mErrorString = "an error"; + emit errorOccurred( mErrorString ); + } + + void cancel() override + { + mStatus = Qgis::ContentStatus::Canceled; + emit canceled(); + }; + + QString url() const override + { + return mUrl.endsWith( "/" ) ? QString( "http://www.test.com/here/myfile.txt" ) + : mUrl; + } + + private: + + QString mUrl; +}; + + +class QgsTestExternalStorage : public QgsExternalStorage +{ + public: + + QString type() const override { return QStringLiteral( "test" ); } + + QgsExternalStorageStoredContent *doStore( const QString &filePath, const QString &url, const QString &authcfg = QString() ) const override + { + Q_UNUSED( authcfg ); + Q_UNUSED( filePath ); + + sStoreContent = new QgsTestExternalStorageStoredContent( url ); + return sStoreContent; + } + + QgsExternalStorageFetchedContent *doFetch( const QString &url, const QString &authcfg = QString() ) const override + { + Q_UNUSED( authcfg ); + + sFetchContent = new QgsTestExternalStorageFetchedContent( url.endsWith( QStringLiteral( "cached.txt" ) ) ); + + return sFetchContent; + } + + static QPointer sFetchContent; + static QPointer sStoreContent; +}; + +QPointer QgsTestExternalStorage::sFetchContent; +QPointer QgsTestExternalStorage::sStoreContent; + void TestQgsExternalResourceWidgetWrapper::initTestCase() { // Set up the QgsSettings environment @@ -52,6 +206,8 @@ void TestQgsExternalResourceWidgetWrapper::initTestCase() QgsApplication::init(); QgsApplication::initQgis(); + + QgsApplication::externalStorageRegistry()->registerExternalStorage( new QgsTestExternalStorage() ); } void TestQgsExternalResourceWidgetWrapper::cleanupTestCase() @@ -61,16 +217,24 @@ void TestQgsExternalResourceWidgetWrapper::cleanupTestCase() void TestQgsExternalResourceWidgetWrapper::init() { - vl = std::make_unique( QStringLiteral( "NoGeometry?field=link:string" ), + vl = std::make_unique( QStringLiteral( "NoGeometry?field=type:string&field=url:string" ), QStringLiteral( "myvl" ), QLatin1String( "memory" ) ); + + QgsFeature feat1( vl->fields(), 1 ); + feat1.setAttribute( QStringLiteral( "type" ), QStringLiteral( "type1" ) ); + vl->dataProvider()->addFeature( feat1 ); + + QgsFeature feat2( vl->fields(), 2 ); + feat2.setAttribute( QStringLiteral( "type" ), QStringLiteral( "type2" ) ); + vl->dataProvider()->addFeature( feat2 ); } void TestQgsExternalResourceWidgetWrapper::cleanup() { } -void TestQgsExternalResourceWidgetWrapper::test_setNullValues() +void TestQgsExternalResourceWidgetWrapper::testSetNullValues() { QgsExternalResourceWidgetWrapper ww( vl.get(), 0, nullptr, nullptr ); QWidget *widget = ww.createWidget( nullptr ); @@ -100,5 +264,739 @@ void TestQgsExternalResourceWidgetWrapper::test_setNullValues() delete widget; } +void TestQgsExternalResourceWidgetWrapper::testUrlStorageExpression() +{ + // test that everything related to Url storage expresssion is correctly set + // according to configuration + + QVariantMap globalVariables; + globalVariables.insert( "myurl", "http://url.test.com/" ); + QgsApplication::setCustomVariables( globalVariables ); + + QgsExternalResourceWidgetWrapper ww( vl.get(), 0, nullptr, nullptr ); + QWidget *widget = ww.createWidget( nullptr ); + QVERIFY( widget ); + + QVariantMap config; + config.insert( QStringLiteral( "StorageType" ), QStringLiteral( "test" ) ); + QgsPropertyCollection propertyCollection; + propertyCollection.setProperty( QgsWidgetWrapper::StorageUrl, QgsProperty::fromExpression( + "@myurl || @layer_name || '/' || \"type\" || '/' " + "|| attribute( @current_feature, 'type' ) " + "|| '/' || $id || '/test'", true ) ); + config.insert( QStringLiteral( "PropertyCollection" ), propertyCollection.toVariant( QgsWidgetWrapper::propertyDefinitions() ) ); + ww.setConfig( config ); + + QgsFeature feat = vl->getFeature( 1 ); + QVERIFY( feat.isValid() ); + ww.setFeature( feat ); + + ww.initWidget( widget ); + QVERIFY( ww.mQgsWidget ); + QVERIFY( ww.mQgsWidget->fileWidget() ); + + QCOMPARE( ww.mQgsWidget->fileWidget()->storageType(), QStringLiteral( "test" ) ); + QgsExpression *expression = ww.mQgsWidget->fileWidget()->storageUrlExpression(); + QVERIFY( expression ); + + QgsExpressionContext expressionContext = ww.mQgsWidget->fileWidget()->expressionContext(); + QVERIFY( expression->prepare( &expressionContext ) ); + QCOMPARE( expression->evaluate( &expressionContext ).toString(), + QStringLiteral( "http://url.test.com/myvl/type1/type1/1/test" ) ); + + feat = vl->getFeature( 2 ); + QVERIFY( feat.isValid() ); + ww.setFeature( feat ); + + expressionContext = ww.mQgsWidget->fileWidget()->expressionContext(); + QVERIFY( expression->prepare( &expressionContext ) ); + QCOMPARE( expression->evaluate( &expressionContext ).toString(), + QStringLiteral( "http://url.test.com/myvl/type2/type2/2/test" ) ); +} + +void TestQgsExternalResourceWidgetWrapper::testLoadExternalDocument_data() +{ + QTest::addColumn( "documentType" ); + + QTest::newRow( "image" ) << static_cast( QgsExternalResourceWidget::Image ); +#ifdef WITH_QTWEBKIT + QTest::newRow( "webview" ) << static_cast( QgsExternalResourceWidget::Web ); +#endif +} + +void TestQgsExternalResourceWidgetWrapper::testLoadExternalDocument() +{ + // test to load a document to be accessed with an external storage + QEventLoop loop; + + QFETCH( int, documentType ); + + QgsMessageBar *messageBar = new QgsMessageBar; + QgsExternalResourceWidgetWrapper ww( vl.get(), 0, nullptr, messageBar, nullptr ); + + QWidget *widget = ww.createWidget( nullptr ); + QVERIFY( widget ); + + QVariantMap config; + config.insert( QStringLiteral( "StorageType" ), QStringLiteral( "test" ) ); + config.insert( QStringLiteral( "DocumentViewer" ), documentType ); + ww.setConfig( config ); + + QgsFeature feat = vl->getFeature( 1 ); + QVERIFY( feat.isValid() ); + ww.setFeature( feat ); + + ww.initWidget( widget ); + QVERIFY( ww.mQgsWidget ); + QVERIFY( ww.mQgsWidget->fileWidget() ); + QCOMPARE( ww.mQgsWidget->fileWidget()->storageType(), QStringLiteral( "test" ) ); + + widget->show(); + if ( documentType == QgsExternalResourceWidget::Image ) + { + QVERIFY( ww.mQgsWidget->mPixmapLabel->isVisible() ); +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + QVERIFY( !ww.mQgsWidget->mPixmapLabel->pixmap() ); +#else + QVERIFY( ww.mQgsWidget->mPixmapLabel->pixmap( Qt::ReturnByValue ).isNull() ); +#endif + } +#ifdef WITH_QTWEBKIT + else if ( documentType == QgsExternalResourceWidget::Web ) + + { + QVERIFY( ww.mQgsWidget->mWebView->isVisible() ); + QCOMPARE( ww.mQgsWidget->mWebView->url().toString(), QStringLiteral( "about:blank" ) ); + } +#endif + + QVERIFY( !ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::NotRunning ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + + // ---------------------------------------------------- + // load url + // ---------------------------------------------------- + ww.setValues( QStringLiteral( "/home/test/myfile.txt" ), QVariantList() ); + + // content still null, fetching in progress... + QVERIFY( !ww.mQgsWidget->mPixmapLabel->isVisible() ); + QVERIFY( !ww.mQgsWidget->mWebView->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::Running ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + QVERIFY( !messageBar->currentItem() ); + + QVERIFY( QgsTestExternalStorage::sFetchContent ); + + QgsTestExternalStorage::sFetchContent->emitFetched(); + QCoreApplication::processEvents(); + + if ( documentType == QgsExternalResourceWidget::Image ) + { + QVERIFY( ww.mQgsWidget->mPixmapLabel->isVisible() ); +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + QVERIFY( ww.mQgsWidget->mPixmapLabel->pixmap() ); +#else + QVERIFY( !ww.mQgsWidget->mPixmapLabel->pixmap( Qt::ReturnByValue ).isNull() ); +#endif + } +#ifdef WITH_QTWEBKIT + else if ( documentType == QgsExternalResourceWidget::Web ) + { + QVERIFY( ww.mQgsWidget->mWebView->isVisible() ); + QVERIFY( ww.mQgsWidget->mWebView->url().isValid() ); + QCOMPARE( ww.mQgsWidget->mWebView->url().toString(), QStringLiteral( "file://%1/sample_image.png" ).arg( TEST_DATA_DIR ) ); + } +#endif + + QVERIFY( !ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::NotRunning ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + QVERIFY( !messageBar->currentItem() ); + + // wait for the fetch content object to be destroyed + connect( QgsTestExternalStorage::sFetchContent, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !QgsTestExternalStorage::sFetchContent ); + + // ---------------------------------------------------- + // load a cached url + // ---------------------------------------------------- + ww.setValues( QStringLiteral( "/home/test/cached.txt" ), QVariantList() ); + + // cached, no fetching, content is OK + QVERIFY( !ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::NotRunning ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + QVERIFY( !messageBar->currentItem() ); + + QVERIFY( QgsTestExternalStorage::sFetchContent ); + + if ( documentType == QgsExternalResourceWidget::Image ) + { + QVERIFY( ww.mQgsWidget->mPixmapLabel->isVisible() ); +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + QVERIFY( ww.mQgsWidget->mPixmapLabel->pixmap() ); +#else + QVERIFY( !ww.mQgsWidget->mPixmapLabel->pixmap( Qt::ReturnByValue ).isNull() ); +#endif + } +#ifdef WITH_QTWEBKIT + else if ( documentType == QgsExternalResourceWidget::Web ) + { + QVERIFY( ww.mQgsWidget->mWebView->isVisible() ); + QVERIFY( ww.mQgsWidget->mWebView->url().isValid() ); + QCOMPARE( ww.mQgsWidget->mWebView->url().toString(), QStringLiteral( "file://%1/sample_image.png" ).arg( TEST_DATA_DIR ) ); + } +#endif + + // wait for the fetch content object to be destroyed + connect( QgsTestExternalStorage::sFetchContent, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !QgsTestExternalStorage::sFetchContent ); + + // ---------------------------------------------------- + // load an error url + // ---------------------------------------------------- + ww.setValues( QStringLiteral( "/home/test/error.txt" ), QVariantList() ); + + // still no error, fetching in progress... + if ( documentType == QgsExternalResourceWidget::Image ) + QVERIFY( !ww.mQgsWidget->mPixmapLabel->isVisible() ); + +#ifdef WITH_QTWEBKIT + else if ( documentType == QgsExternalResourceWidget::Web ) + QVERIFY( !ww.mQgsWidget->mWebView->isVisible() ); + +#endif + + QVERIFY( ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::Running ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + QVERIFY( !messageBar->currentItem() ); + + QVERIFY( QgsTestExternalStorage::sFetchContent ); + + QgsTestExternalStorage::sFetchContent->emitErrorOccurred(); + QCoreApplication::processEvents(); + + QVERIFY( !ww.mQgsWidget->mPixmapLabel->isVisible() ); +#ifdef WITH_QTWEBKIT + QVERIFY( !ww.mQgsWidget->mWebView->isVisible() ); +#endif + + QVERIFY( !ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::NotRunning ); + QVERIFY( ww.mQgsWidget->mErrorLabel->isVisible() ); + QVERIFY( messageBar->currentItem() ); + + // wait for the fetch content object to be destroyed + connect( QgsTestExternalStorage::sFetchContent, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !QgsTestExternalStorage::sFetchContent ); + + delete widget; + delete messageBar; +} + +void TestQgsExternalResourceWidgetWrapper::testLoadNullExternalDocument_data() +{ + QTest::addColumn( "documentType" ); + + QTest::newRow( "image" ) << static_cast( QgsExternalResourceWidget::Image ); +#ifdef WITH_QTWEBKIT + QTest::newRow( "webview" ) << static_cast( QgsExternalResourceWidget::Web ); +#endif +} + +void TestQgsExternalResourceWidgetWrapper::testLoadNullExternalDocument() +{ + // Check that widget doesn't try to load a document which value is NULL + QFETCH( int, documentType ); + + QgsMessageBar *messageBar = new QgsMessageBar; + QgsExternalResourceWidgetWrapper ww( vl.get(), 0, nullptr, messageBar, nullptr ); + + QWidget *widget = ww.createWidget( nullptr ); + QVERIFY( widget ); + + QVariantMap config; + config.insert( QStringLiteral( "StorageType" ), QStringLiteral( "test" ) ); + config.insert( QStringLiteral( "DocumentViewer" ), documentType ); + ww.setConfig( config ); + + QgsFeature feat = vl->getFeature( 1 ); + QVERIFY( feat.isValid() ); + ww.setFeature( feat ); + + ww.initWidget( widget ); + QVERIFY( ww.mQgsWidget ); + QVERIFY( ww.mQgsWidget->fileWidget() ); + QCOMPARE( ww.mQgsWidget->fileWidget()->storageType(), QStringLiteral( "test" ) ); + + widget->show(); + if ( documentType == QgsExternalResourceWidget::Image ) + { + QVERIFY( ww.mQgsWidget->mPixmapLabel->isVisible() ); +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + QVERIFY( !ww.mQgsWidget->mPixmapLabel->pixmap() ); +#else + QVERIFY( ww.mQgsWidget->mPixmapLabel->pixmap( Qt::ReturnByValue ).isNull() ); +#endif + } +#ifdef WITH_QTWEBKIT + else if ( documentType == QgsExternalResourceWidget::Web ) + + { + QVERIFY( ww.mQgsWidget->mWebView->isVisible() ); + QCOMPARE( ww.mQgsWidget->mWebView->url().toString(), QStringLiteral( "about:blank" ) ); + } +#endif + + QVERIFY( !ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::NotRunning ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + + // load null url + ww.setValues( QString(), QVariantList() ); + + QVERIFY( !QgsTestExternalStorage::sFetchContent ); + + // content is null + if ( documentType == QgsExternalResourceWidget::Image ) + { + QVERIFY( ww.mQgsWidget->mPixmapLabel->isVisible() ); +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + QVERIFY( !ww.mQgsWidget->mPixmapLabel->pixmap() ); +#else + QVERIFY( ww.mQgsWidget->mPixmapLabel->pixmap( Qt::ReturnByValue ).isNull() ); +#endif + } +#ifdef WITH_QTWEBKIT + else if ( documentType == QgsExternalResourceWidget::Web ) + + { + QVERIFY( ww.mQgsWidget->mWebView->isVisible() ); + QCOMPARE( ww.mQgsWidget->mWebView->url().toString(), QStringLiteral( "about:blank" ) ); + } +#endif + + QVERIFY( !ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() != QMovie::Running ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + QVERIFY( !messageBar->currentItem() ); + +} + +void TestQgsExternalResourceWidgetWrapper::testStoreExternalDocument_data() +{ + QTest::addColumn( "documentType" ); + + QTest::newRow( "image" ) << static_cast( QgsExternalResourceWidget::Image ); +#ifdef WITH_QTWEBKIT + QTest::newRow( "webview" ) << static_cast( QgsExternalResourceWidget::Web ); +#endif +} + +void TestQgsExternalResourceWidgetWrapper::testStoreExternalDocument() +{ + QFETCH( int, documentType ); + + QEventLoop loop; + QgsMessageBar *messageBar = new QgsMessageBar; + QgsExternalResourceWidgetWrapper ww( vl.get(), 1, nullptr, messageBar, nullptr ); + + QWidget *widget = ww.createWidget( nullptr ); + QVERIFY( widget ); + + QVariantMap config; + config.insert( QStringLiteral( "StorageType" ), QStringLiteral( "test" ) ); + config.insert( QStringLiteral( "DocumentViewer" ), documentType ); + + QgsPropertyCollection propertyCollection; + propertyCollection.setProperty( QgsWidgetWrapper::StorageUrl, QgsProperty::fromExpression( + "'http://mytest.com/' || $id || '/' " + " || file_name(@selected_file_path)", true ) ); + config.insert( QStringLiteral( "PropertyCollection" ), propertyCollection.toVariant( QgsWidgetWrapper::propertyDefinitions() ) ); + ww.setConfig( config ); + + QgsFeature feat = vl->getFeature( 1 ); + QVERIFY( feat.isValid() ); + ww.setFeature( feat ); + + ww.initWidget( widget ); + QVERIFY( ww.mQgsWidget ); + + QgsFileWidget *fileWidget = ww.mQgsWidget->fileWidget(); + QVERIFY( fileWidget ); + QCOMPARE( fileWidget->storageType(), QStringLiteral( "test" ) ); + + widget->show(); + ww.mQgsWidget->setReadOnly( false ); + + if ( documentType == QgsExternalResourceWidget::Image ) + { + QVERIFY( ww.mQgsWidget->mPixmapLabel->isVisible() ); +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + QVERIFY( !ww.mQgsWidget->mPixmapLabel->pixmap() ); +#else + QVERIFY( ww.mQgsWidget->mPixmapLabel->pixmap( Qt::ReturnByValue ).isNull() ); +#endif + } +#ifdef WITH_QTWEBKIT + else if ( documentType == QgsExternalResourceWidget::Web ) + { + QVERIFY( ww.mQgsWidget->mWebView->isVisible() ); + QCOMPARE( ww.mQgsWidget->mWebView->url().toString(), QStringLiteral( "about:blank" ) ); + } +#endif + + QVERIFY( !ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::NotRunning ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + + // ---------------------------------------------------- + // store one document + // ---------------------------------------------------- + fileWidget->setSelectedFileNames( QStringList() << QStringLiteral( "/home/test/myfile.txt" ) ); + + QVERIFY( QgsTestExternalStorage::sStoreContent ); + + QVERIFY( !ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::NotRunning ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + + QgsTestExternalStorage::sStoreContent->emitStored(); + QCoreApplication::processEvents(); + + QVERIFY( !messageBar->currentItem() ); + + // wait for the store content object to be destroyed + connect( QgsTestExternalStorage::sStoreContent, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !QgsTestExternalStorage::sStoreContent ); + + QCOMPARE( ww.value().toString(), QStringLiteral( "http://mytest.com/1/myfile.txt" ) ); + + delete widget; + delete messageBar; +} + +void TestQgsExternalResourceWidgetWrapper::testStoreExternalDocumentError_data() +{ + QTest::addColumn( "documentType" ); + + QTest::newRow( "image" ) << static_cast( QgsExternalResourceWidget::Image ); +#ifdef WITH_QTWEBKIT + QTest::newRow( "webview" ) << static_cast( QgsExternalResourceWidget::Web ); +#endif +} + +void TestQgsExternalResourceWidgetWrapper::testStoreExternalDocumentError() +{ + QFETCH( int, documentType ); + + QEventLoop loop; + QgsMessageBar *messageBar = new QgsMessageBar; + QgsExternalResourceWidgetWrapper ww( vl.get(), 1, nullptr, messageBar, nullptr ); + + QWidget *widget = ww.createWidget( nullptr ); + QVERIFY( widget ); + + QVariantMap config; + config.insert( QStringLiteral( "StorageType" ), QStringLiteral( "test" ) ); + config.insert( QStringLiteral( "DocumentViewer" ), documentType ); + QgsPropertyCollection propertyCollection; + propertyCollection.setProperty( QgsWidgetWrapper::StorageUrl, QgsProperty::fromExpression( + "'http://mytest.com/' || $id || '/' " + " || file_name(@selected_file_path)", true ) ); + config.insert( QStringLiteral( "PropertyCollection" ), propertyCollection.toVariant( QgsWidgetWrapper::propertyDefinitions() ) ); + ww.setConfig( config ); + + QgsFeature feat = vl->getFeature( 1 ); + QVERIFY( feat.isValid() ); + ww.setFeature( feat ); + + ww.initWidget( widget ); + QVERIFY( ww.mQgsWidget ); + + QgsFileWidget *fileWidget = ww.mQgsWidget->fileWidget(); + QVERIFY( fileWidget ); + QCOMPARE( fileWidget->storageType(), QStringLiteral( "test" ) ); + + widget->show(); + ww.mQgsWidget->setReadOnly( false ); + + if ( documentType == QgsExternalResourceWidget::Image ) + { + QVERIFY( ww.mQgsWidget->mPixmapLabel->isVisible() ); +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + QVERIFY( !ww.mQgsWidget->mPixmapLabel->pixmap() ); +#else + QVERIFY( ww.mQgsWidget->mPixmapLabel->pixmap( Qt::ReturnByValue ).isNull() ); +#endif + } +#ifdef WITH_QTWEBKIT + else if ( documentType == QgsExternalResourceWidget::Web ) + { + QVERIFY( ww.mQgsWidget->mWebView->isVisible() ); + QCOMPARE( ww.mQgsWidget->mWebView->url().toString(), QStringLiteral( "about:blank" ) ); + } +#endif + + QVERIFY( !ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::NotRunning ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + + fileWidget->setSelectedFileNames( QStringList() << QStringLiteral( "/home/test/error.txt" ) ); + + QVERIFY( QgsTestExternalStorage::sStoreContent ); + + QVERIFY( !ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::NotRunning ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + + QgsTestExternalStorage::sStoreContent->emitErrorOccurred(); + QCoreApplication::processEvents(); + + if ( documentType == QgsExternalResourceWidget::Image ) + { + QVERIFY( ww.mQgsWidget->mPixmapLabel->isVisible() ); +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + QVERIFY( !ww.mQgsWidget->mPixmapLabel->pixmap() ); +#else + QVERIFY( ww.mQgsWidget->mPixmapLabel->pixmap( Qt::ReturnByValue ).isNull() ); +#endif + } +#ifdef WITH_QTWEBKIT + else if ( documentType == QgsExternalResourceWidget::Web ) + { + QVERIFY( ww.mQgsWidget->mWebView->isVisible() ); + QCOMPARE( ww.mQgsWidget->mWebView->url().toString(), QStringLiteral( "about:blank" ) ); + } +#endif + + QVERIFY( !ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::NotRunning ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + QVERIFY( messageBar->currentItem() ); + + // wait for the store content object to be destroyed + connect( QgsTestExternalStorage::sStoreContent, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !QgsTestExternalStorage::sStoreContent ); + + // value hasn't changed, same as before we try to store + QVERIFY( ww.value().toString().isEmpty() ); + + delete widget; + delete messageBar; +} + +void TestQgsExternalResourceWidgetWrapper::testStoreExternalDocumentCancel_data() +{ + QTest::addColumn( "documentType" ); + + QTest::newRow( "image" ) << static_cast( QgsExternalResourceWidget::Image ); +#ifdef WITH_QTWEBKIT + QTest::newRow( "webview" ) << static_cast( QgsExternalResourceWidget::Web ); +#endif +} + +void TestQgsExternalResourceWidgetWrapper::testStoreExternalDocumentCancel() +{ + QFETCH( int, documentType ); + + QEventLoop loop; + QgsMessageBar *messageBar = new QgsMessageBar; + QgsExternalResourceWidgetWrapper ww( vl.get(), 1, nullptr, messageBar, nullptr ); + + QWidget *widget = ww.createWidget( nullptr ); + QVERIFY( widget ); + + QVariantMap config; + config.insert( QStringLiteral( "StorageType" ), QStringLiteral( "test" ) ); + config.insert( QStringLiteral( "DocumentViewer" ), documentType ); + QgsPropertyCollection propertyCollection; + propertyCollection.setProperty( QgsWidgetWrapper::StorageUrl, QgsProperty::fromExpression( + "'http://mytest.com/' || $id || '/' " + " || file_name(@selected_file_path)", true ) ); + config.insert( QStringLiteral( "PropertyCollection" ), propertyCollection.toVariant( QgsWidgetWrapper::propertyDefinitions() ) ); + ww.setConfig( config ); + + QgsFeature feat = vl->getFeature( 1 ); + QVERIFY( feat.isValid() ); + ww.setFeature( feat ); + + ww.initWidget( widget ); + QVERIFY( ww.mQgsWidget ); + + QgsFileWidget *fileWidget = ww.mQgsWidget->fileWidget(); + QVERIFY( fileWidget ); + QCOMPARE( fileWidget->storageType(), QStringLiteral( "test" ) ); + + widget->show(); + ww.mQgsWidget->setReadOnly( false ); + + if ( documentType == QgsExternalResourceWidget::Image ) + { + QVERIFY( ww.mQgsWidget->mPixmapLabel->isVisible() ); +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + QVERIFY( !ww.mQgsWidget->mPixmapLabel->pixmap() ); +#else + QVERIFY( ww.mQgsWidget->mPixmapLabel->pixmap( Qt::ReturnByValue ).isNull() ); +#endif + } +#ifdef WITH_QTWEBKIT + else if ( documentType == QgsExternalResourceWidget::Web ) + { + QVERIFY( ww.mQgsWidget->mWebView->isVisible() ); + QCOMPARE( ww.mQgsWidget->mWebView->url().toString(), QStringLiteral( "about:blank" ) ); + } +#endif + + QVERIFY( !ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::NotRunning ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + + // ---------------------------------------------------- + // store one document and cancel it + // ---------------------------------------------------- + fileWidget->setSelectedFileNames( QStringList() << QStringLiteral( "/home/test/myfile.txt" ) ); + + QVERIFY( QgsTestExternalStorage::sStoreContent ); + + QVERIFY( !ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::NotRunning ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + + QgsTestExternalStorage::sStoreContent->cancel(); + QCoreApplication::processEvents(); + + if ( documentType == QgsExternalResourceWidget::Image ) + { + QVERIFY( ww.mQgsWidget->mPixmapLabel->isVisible() ); +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + QVERIFY( !ww.mQgsWidget->mPixmapLabel->pixmap() ); +#else + QVERIFY( ww.mQgsWidget->mPixmapLabel->pixmap( Qt::ReturnByValue ).isNull() ); +#endif + } +#ifdef WITH_QTWEBKIT + else if ( documentType == QgsExternalResourceWidget::Web ) + { + QVERIFY( ww.mQgsWidget->mWebView->isVisible() ); + QCOMPARE( ww.mQgsWidget->mWebView->url().toString(), QStringLiteral( "about:blank" ) ); + } +#endif + + QVERIFY( !ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::NotRunning ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + QVERIFY( !messageBar->currentItem() ); + + // wait for the store content object to be destroyed + connect( QgsTestExternalStorage::sStoreContent, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !QgsTestExternalStorage::sStoreContent ); + + QVERIFY( ww.value().toString().isEmpty() ); + + delete widget; + delete messageBar; +} + +void TestQgsExternalResourceWidgetWrapper::testStoreExternalDocumentNoExpression_data() +{ + QTest::addColumn( "documentType" ); + + QTest::newRow( "image" ) << static_cast( QgsExternalResourceWidget::Image ); +#ifdef WITH_QTWEBKIT + QTest::newRow( "webview" ) << static_cast( QgsExternalResourceWidget::Web ); +#endif +} + +void TestQgsExternalResourceWidgetWrapper::testStoreExternalDocumentNoExpression() +{ + // store a document using a raw URL, not an expression + + QFETCH( int, documentType ); + + QEventLoop loop; + QgsMessageBar *messageBar = new QgsMessageBar; + QgsExternalResourceWidgetWrapper ww( vl.get(), 1, nullptr, messageBar, nullptr ); + + QWidget *widget = ww.createWidget( nullptr ); + QVERIFY( widget ); + + QVariantMap config; + config.insert( QStringLiteral( "StorageType" ), QStringLiteral( "test" ) ); + config.insert( QStringLiteral( "DocumentViewer" ), documentType ); + config.insert( QStringLiteral( "StorageUrl" ), QStringLiteral( "http://www.test.com/here/" ) ); + ww.setConfig( config ); + + QgsFeature feat = vl->getFeature( 1 ); + QVERIFY( feat.isValid() ); + ww.setFeature( feat ); + + ww.initWidget( widget ); + QVERIFY( ww.mQgsWidget ); + + QgsFileWidget *fileWidget = ww.mQgsWidget->fileWidget(); + QVERIFY( fileWidget ); + QCOMPARE( fileWidget->storageType(), QStringLiteral( "test" ) ); + + widget->show(); + ww.mQgsWidget->setReadOnly( false ); + + if ( documentType == QgsExternalResourceWidget::Image ) + { + QVERIFY( ww.mQgsWidget->mPixmapLabel->isVisible() ); +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + QVERIFY( !ww.mQgsWidget->mPixmapLabel->pixmap() ); +#else + QVERIFY( ww.mQgsWidget->mPixmapLabel->pixmap( Qt::ReturnByValue ).isNull() ); +#endif + } +#ifdef WITH_QTWEBKIT + else if ( documentType == QgsExternalResourceWidget::Web ) + { + QVERIFY( ww.mQgsWidget->mWebView->isVisible() ); + QCOMPARE( ww.mQgsWidget->mWebView->url().toString(), QStringLiteral( "about:blank" ) ); + } +#endif + + QVERIFY( !ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::NotRunning ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + + // store one document + fileWidget->setSelectedFileNames( QStringList() << QStringLiteral( "/home/test/myfile.txt" ) ); + + QVERIFY( QgsTestExternalStorage::sStoreContent ); + + QVERIFY( !ww.mQgsWidget->mLoadingLabel->isVisible() ); + QVERIFY( ww.mQgsWidget->mLoadingMovie->state() == QMovie::NotRunning ); + QVERIFY( !ww.mQgsWidget->mErrorLabel->isVisible() ); + + QgsTestExternalStorage::sStoreContent->emitStored(); + QCoreApplication::processEvents(); + + QVERIFY( !messageBar->currentItem() ); + + // wait for the store content object to be destroyed + connect( QgsTestExternalStorage::sStoreContent, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !QgsTestExternalStorage::sStoreContent ); + + QCOMPARE( ww.value().toString(), QStringLiteral( "http://www.test.com/here/myfile.txt" ) ); + + delete widget; + delete messageBar; +} + + + QGSTEST_MAIN( TestQgsExternalResourceWidgetWrapper ) #include "testqgsexternalresourcewidgetwrapper.moc" diff --git a/tests/src/gui/testqgsfilewidget.cpp b/tests/src/gui/testqgsfilewidget.cpp index 198d9015fbe5..e19f394fe1ef 100644 --- a/tests/src/gui/testqgsfilewidget.cpp +++ b/tests/src/gui/testqgsfilewidget.cpp @@ -22,8 +22,16 @@ #include "qgsbrowsermodel.h" #include "qgslayeritem.h" #include "qgsdirectoryitem.h" +#include "qgsexternalstorage.h" +#include "qgsexternalstorageregistry.h" +#include "qgsmessagebar.h" +#include "qgsexpressioncontextutils.h" #include +#include +#include +#include + class TestQgsFileWidget: public QObject { Q_OBJECT @@ -37,12 +45,99 @@ class TestQgsFileWidget: public QObject void testDroppedFiles(); void testMultipleFiles(); void testSplitFilePaths(); + void testLayout_data(); + void testLayout(); + void testStoring(); + void testStoring_data(); + void testStoringSeveralFiles_data(); + void testStoringSeveralFiles(); + void testStoringSeveralFilesError_data(); + void testStoringSeveralFilesError(); + void testStoringSeveralFilesCancel_data(); + void testStoringSeveralFilesCancel(); + void testStoringDirectory_data(); + void testStoringDirectory(); + void testStoringChangeFeature(); + void testStoringBadExpression_data(); + void testStoringBadExpression(); +}; + +class QgsTestExternalStorageStoredContent : public QgsExternalStorageStoredContent +{ + Q_OBJECT + + public: + + QgsTestExternalStorageStoredContent( const QString &filePath, const QString &url ) + : QgsExternalStorageStoredContent(), + mUrl( filePath.endsWith( QStringLiteral( "mydir" ) ) ? url + "mydir/" : url ) + {} + + void store() override + { + mStatus = Qgis::ContentStatus::Running; + } + + void cancel() override + { + mStatus = Qgis::ContentStatus::Canceled; + emit canceled(); + }; + + void error() + { + mStatus = Qgis::ContentStatus::Failed; + mErrorString = QStringLiteral( "error" ); + emit errorOccurred( mErrorString ); + } + + void finish() + { + mStatus = Qgis::ContentStatus::Finished; + emit stored(); + } + + QString url() const override + { + return mUrl; + } + + private: + + QString mUrl; }; -void TestQgsFileWidget::initTestCase() +class QgsTestExternalStorage : public QgsExternalStorage { + public: + + QString type() const override { return QStringLiteral( "test" ); } + + static QPointer sCurrentStoredContent; + protected: + + QgsExternalStorageStoredContent *doStore( const QString &filePath, const QString &url, const QString &authcfg = QString() ) const override + { + Q_UNUSED( authcfg ); + sCurrentStoredContent = new QgsTestExternalStorageStoredContent( filePath, url ); + return sCurrentStoredContent; + } + + QgsExternalStorageFetchedContent *doFetch( const QString &url, const QString &authcfg = QString() ) const override + { + Q_UNUSED( url ); + Q_UNUSED( authcfg ); + return nullptr; + } +}; + +QPointer QgsTestExternalStorage::sCurrentStoredContent; + +void TestQgsFileWidget::initTestCase() +{ + QgsApplication::externalStorageRegistry()->registerExternalStorage( new QgsTestExternalStorage() ); } void TestQgsFileWidget::cleanupTestCase() @@ -201,7 +296,6 @@ void TestQgsFileWidget::testMultipleFiles() } - void TestQgsFileWidget::testSplitFilePaths() { const QString path = QString( TEST_DATA_DIR + QStringLiteral( "/bug5598.shp" ) ); @@ -213,8 +307,645 @@ void TestQgsFileWidget::testSplitFilePaths() QCOMPARE( QgsFileWidget::splitFilePaths( path ), QStringList() << path ); } +void TestQgsFileWidget::testLayout_data() +{ + QTest::addColumn( "storageType" ); + QTest::newRow( "without external storage" ) << QString(); + QTest::newRow( "with external storage" ) << QStringLiteral( "test" ); +} + +void TestQgsFileWidget::testLayout() +{ + // test correct buttons are displayed according to different mode and interactions + + QFETCH( QString, storageType ); + + QgsFileWidget w; + w.setStorageType( storageType ); + w.show(); + + QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); + QIcon saveIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveEdits.svg" ) ); + + // with link, read-only + w.setReadOnly( true ); + w.setUseLink( true ); + + QVERIFY( w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + // with link, edit mode + w.setReadOnly( false ); + + QVERIFY( w.mLinkLabel->isVisible() ); + QVERIFY( w.mLinkEditButton->isVisible() ); + QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + // with link, edit mode, we edit the link + w.editLink(); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( w.mLinkEditButton->isVisible() ); + QCOMPARE( w.mLinkEditButton->icon(), saveIcon ); + QVERIFY( w.mLineEdit->isVisible() ); + QVERIFY( w.mLineEdit->isEnabled() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + // with link, edit mode, we finish editing the link + w.editLink(); + + QVERIFY( w.mLinkLabel->isVisible() ); + QVERIFY( w.mLinkEditButton->isVisible() ); + QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + // without link, read-only + w.setUseLink( false ); + w.setReadOnly( true ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( w.mLineEdit->isVisible() ); + QVERIFY( !w.mLineEdit->isEnabled() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + // without link, edit mode + w.setReadOnly( false ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( w.mLineEdit->isVisible() ); + QVERIFY( w.mLineEdit->isEnabled() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); +} +void TestQgsFileWidget::testStoring_data() +{ + QTest::addColumn( "useLink" ); + + QTest::newRow( "use link" ) << true; + QTest::newRow( "don't use link" ) << false; +} + +void TestQgsFileWidget::testStoring() +{ + // test widget when an external storage is used + + QFETCH( bool, useLink ); + + QgsFileWidget w; + w.show(); + + QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); + + w.setStorageType( "test" ); + w.setStorageUrlExpression( "'http://test.url.com/test/' || file_name(@selected_file_path)" ); + + // start edit mode + w.setUseLink( useLink ); + w.setReadOnly( false ); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + w.setSelectedFileNames( QStringList() << QStringLiteral( "myfile" ) ); + + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QVERIFY( w.mCancelButton->isVisible() ); + + // link is not yet updated + QVERIFY( w.mLinkLabel->text().isEmpty() ); + + QgsTestExternalStorage::sCurrentStoredContent->finish(); + QCoreApplication::processEvents(); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + if ( useLink ) + QCOMPARE( w.mLinkLabel->text(), QStringLiteral( "myfile" ) ); + else + QCOMPARE( w.mLineEdit->text(), QStringLiteral( "http://test.url.com/test/myfile" ) ); +} + + +void TestQgsFileWidget::testStoringSeveralFiles_data() +{ + QTest::addColumn( "useLink" ); + + QTest::newRow( "use link" ) << true; + QTest::newRow( "don't use link" ) << false; +} + +void TestQgsFileWidget::testStoringSeveralFiles() +{ + // test widget when storing several files with an external storage + QEventLoop loop; + QFETCH( bool, useLink ); + + QgsFileWidget w; + w.show(); + + QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); + + w.setStorageType( "test" ); + w.setStorageUrlExpression( "'http://test.url.com/test/' || file_name(@selected_file_path)" ); + w.setStorageMode( QgsFileWidget::GetMultipleFiles ); + + // start edit mode + w.setUseLink( useLink ); + w.setReadOnly( false ); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + w.setSelectedFileNames( QStringList() << QStringLiteral( "myfile1" ) << QStringLiteral( "myfile2" ) ); + + QPointer content1 = QgsTestExternalStorage::sCurrentStoredContent; + QVERIFY( content1 ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QVERIFY( w.mCancelButton->isVisible() ); + QVERIFY( w.mLinkLabel->text().isEmpty() ); + + QgsTestExternalStorage::sCurrentStoredContent->finish(); + QCoreApplication::processEvents(); + + // second file is being stored + QVERIFY( content1 ); + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + QVERIFY( content1 != QgsTestExternalStorage::sCurrentStoredContent ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QVERIFY( w.mCancelButton->isVisible() ); + QVERIFY( w.mLinkLabel->text().isEmpty() ); + + // wait for first file content to be destroyed + connect( content1, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !content1 ); + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + + // end second store + QgsTestExternalStorage::sCurrentStoredContent->finish(); + QCoreApplication::processEvents(); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + if ( useLink ) + QCOMPARE( w.mLinkLabel->text(), QStringLiteral( "\"http://test.url.com/test/myfile1\" \"http://test.url.com/test/myfile2\"" ) ); + else + QCOMPARE( w.mLineEdit->text(), QStringLiteral( "\"http://test.url.com/test/myfile1\" \"http://test.url.com/test/myfile2\"" ) ); + + // wait for second file content to be destroyed + connect( QgsTestExternalStorage::sCurrentStoredContent, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !QgsTestExternalStorage::sCurrentStoredContent ); +} + +void TestQgsFileWidget::testStoringSeveralFilesError_data() +{ + QTest::addColumn( "useLink" ); + + QTest::newRow( "use link" ) << true; + QTest::newRow( "don't use link" ) << false; +} + +void TestQgsFileWidget::testStoringSeveralFilesError() +{ + // test widget when storing several files with an external storage and an error occured + QEventLoop loop; + QFETCH( bool, useLink ); + + QgsFileWidget w; + QgsMessageBar messageBar; + w.show(); + + QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); + + w.setStorageType( "test" ); + w.setStorageUrlExpression( "'http://test.url.com/test/' || file_name(@selected_file_path)" ); + w.setMessageBar( &messageBar ); + + // start edit mode + w.setUseLink( useLink ); + w.setReadOnly( false ); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + w.setSelectedFileNames( QStringList() << QStringLiteral( "myfile1" ) << QStringLiteral( "error.txt" ) ); + + QPointer content1 = QgsTestExternalStorage::sCurrentStoredContent; + QVERIFY( content1 ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QVERIFY( w.mCancelButton->isVisible() ); + QVERIFY( w.mLinkLabel->text().isEmpty() ); + QVERIFY( !messageBar.currentItem() ); + + QgsTestExternalStorage::sCurrentStoredContent->finish(); + QCoreApplication::processEvents(); + + // second file is being stored + QVERIFY( content1 ); + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + QVERIFY( content1 != QgsTestExternalStorage::sCurrentStoredContent ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QVERIFY( w.mCancelButton->isVisible() ); + QVERIFY( w.mLinkLabel->text().isEmpty() ); + + // wait for first file content to be destroyed + connect( content1, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !content1 ); + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + + // error while storing second file + QgsTestExternalStorage::sCurrentStoredContent->error(); + QCoreApplication::processEvents(); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + if ( useLink ) + QVERIFY( w.mLinkLabel->text().isEmpty() ); + else + QVERIFY( w.mLineEdit->text().isEmpty() ); + QVERIFY( messageBar.currentItem() ); + + // wait for second file content to be destroyed + connect( QgsTestExternalStorage::sCurrentStoredContent, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !QgsTestExternalStorage::sCurrentStoredContent ); +} + + +void TestQgsFileWidget::testStoringSeveralFilesCancel_data() +{ + QTest::addColumn( "useLink" ); + + QTest::newRow( "use link" ) << true; + QTest::newRow( "don't use link" ) << false; +} + +void TestQgsFileWidget::testStoringSeveralFilesCancel() +{ + // test widget when storing several files with an external storage and user cancel operation + QEventLoop loop; + QFETCH( bool, useLink ); + + QgsFileWidget w; + w.show(); + + QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); + + w.setStorageType( "test" ); + w.setStorageUrlExpression( "'http://test.url.com/test/' || file_name(@selected_file_path)" ); + + // start edit mode + w.setUseLink( useLink ); + w.setReadOnly( false ); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + w.setSelectedFileNames( QStringList() << QStringLiteral( "myfile1" ) << QStringLiteral( "error.txt" ) ); + + QPointer content1 = QgsTestExternalStorage::sCurrentStoredContent; + QVERIFY( content1 ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QVERIFY( w.mCancelButton->isVisible() ); + QVERIFY( w.mLinkLabel->text().isEmpty() ); + + QgsTestExternalStorage::sCurrentStoredContent->finish(); + QCoreApplication::processEvents(); + + // second file is being stored + QVERIFY( content1 ); + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + QVERIFY( content1 != QgsTestExternalStorage::sCurrentStoredContent ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QVERIFY( w.mCancelButton->isVisible() ); + QVERIFY( w.mLinkLabel->text().isEmpty() ); + + // wait for first file content to be destroyed + connect( content1, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !content1 ); + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + + // cancel while storing second file + QgsTestExternalStorage::sCurrentStoredContent->cancel(); + QCoreApplication::processEvents(); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + if ( useLink ) + QVERIFY( w.mLinkLabel->text().isEmpty() ); + else + QVERIFY( w.mLineEdit->text().isEmpty() ); + + // wait for second file content to be destroyed + connect( QgsTestExternalStorage::sCurrentStoredContent, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !QgsTestExternalStorage::sCurrentStoredContent ); +} + + +void TestQgsFileWidget::testStoringChangeFeature() +{ + // test widget with external storage to store files with different features + + QgsFileWidget w; + w.show(); + + QgsFields fields; + fields.append( QgsField( QStringLiteral( "myfield" ), QVariant::String ) ); + + QgsFeature f1( fields ); + f1.setAttribute( QStringLiteral( "myfield" ), QStringLiteral( "val1" ) ); + + w.setStorageType( "test" ); + w.setStorageUrlExpression( "'http://test.url.com/' || attribute( @current_feature, 'myfield' )" ); + + QgsExpressionContext expressionContext; + expressionContext.appendScope( QgsExpressionContextUtils::formScope( f1 ) ); + w.setExpressionContext( expressionContext ); + + w.setUseLink( false ); + w.setReadOnly( false ); + + w.setSelectedFileNames( QStringList() << QStringLiteral( "blank" ) ); + + QgsTestExternalStorage::sCurrentStoredContent->finish(); + + QCOMPARE( w.mLineEdit->text(), QStringLiteral( "http://test.url.com/val1" ) ); + + QgsFeature f2( fields ); + f2.setAttribute( QStringLiteral( "myfield" ), QStringLiteral( "val2" ) ); + + QgsExpressionContext expressionContext2; + expressionContext2.appendScope( QgsExpressionContextUtils::formScope( f2 ) ); + w.setExpressionContext( expressionContext2 ); + + w.setSelectedFileNames( QStringList() << QStringLiteral( "blank" ) ); + + QgsTestExternalStorage::sCurrentStoredContent->finish(); + + QCOMPARE( w.mLineEdit->text(), QStringLiteral( "http://test.url.com/val2" ) ); +} + +void TestQgsFileWidget::testStoringBadExpression_data() +{ + QTest::addColumn( "useLink" ); + + QTest::newRow( "use link" ) << true; + QTest::newRow( "don't use link" ) << false; +} + +void TestQgsFileWidget::testStoringBadExpression() +{ + // test widget when an external storage is used and the given expression if incorrect + + QFETCH( bool, useLink ); + + QgsFileWidget w; + w.show(); + + QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); + + w.setStorageType( "test" ); + w.setStorageUrlExpression( "'http://test.url.com/test/' || file_name(@not_existing_variable)" ); + + // start edit mode + w.setUseLink( useLink ); + w.setReadOnly( false ); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + w.setSelectedFileNames( QStringList() << QStringLiteral( "myfile" ) ); + + QVERIFY( !QgsTestExternalStorage::sCurrentStoredContent ); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + // link is not updated + QVERIFY( w.mLinkLabel->text().isEmpty() ); +} + +void TestQgsFileWidget::testStoringDirectory_data() +{ + QTest::addColumn( "useLink" ); + + QTest::newRow( "use link" ) << true; + QTest::newRow( "don't use link" ) << false; +} + +void TestQgsFileWidget::testStoringDirectory() +{ + // test widget when storing a directory with an external storage + QEventLoop loop; + QFETCH( bool, useLink ); + + QgsFileWidget w; + w.show(); + + QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); + + w.setStorageType( "test" ); + w.setStorageUrlExpression( "'http://test.url.com/test/'" ); + w.setStorageMode( QgsFileWidget::GetDirectory ); + + // start edit mode + w.setUseLink( useLink ); + w.setReadOnly( false ); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + w.setSelectedFileNames( QStringList() << "/tmp/mydir" ); + + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QVERIFY( w.mCancelButton->isVisible() ); + QVERIFY( w.mLinkLabel->text().isEmpty() ); + + QgsTestExternalStorage::sCurrentStoredContent->finish(); + QCoreApplication::processEvents(); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + if ( useLink ) + QCOMPARE( w.mLinkLabel->text(), QStringLiteral( "" ) ); + else + QCOMPARE( w.mLineEdit->text(), QStringLiteral( "http://test.url.com/test/mydir/" ) ); + + // wait for file content to be destroyed + connect( QgsTestExternalStorage::sCurrentStoredContent, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !QgsTestExternalStorage::sCurrentStoredContent ); +} QGSTEST_MAIN( TestQgsFileWidget ) #include "testqgsfilewidget.moc" From 16c916b7231f36d40a9ac7a57b862bfa2da17569 Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Tue, 3 Aug 2021 14:19:49 +0200 Subject: [PATCH 02/11] fix typos and doc --- python/gui/auto_generated/qgsexternalresourcewidget.sip.in | 2 +- python/gui/auto_generated/qgsfilewidget.sip.in | 2 +- src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h | 4 +++- src/gui/qgsexternalresourcewidget.h | 2 +- src/gui/qgsfilewidget.h | 2 +- tests/src/gui/testqgsfilewidget.cpp | 2 +- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/python/gui/auto_generated/qgsexternalresourcewidget.sip.in b/python/gui/auto_generated/qgsexternalresourcewidget.sip.in index 332fdbf0d673..7f10e1c766a0 100644 --- a/python/gui/auto_generated/qgsexternalresourcewidget.sip.in +++ b/python/gui/auto_generated/qgsexternalresourcewidget.sip.in @@ -147,7 +147,7 @@ null QString if there is no storage defined, only file selection. QString storageType() const; %Docstring -Get storage type unique identifier as defined in :py:class:`QgsExternalStorageRegistry`. +Returns storage type unique identifier as defined in :py:class:`QgsExternalStorageRegistry`. Returns null QString if there is no storage defined, only file selection. .. seealso:: :py:func:`setStorageType` diff --git a/python/gui/auto_generated/qgsfilewidget.sip.in b/python/gui/auto_generated/qgsfilewidget.sip.in index 746142a5a4da..a80b087c64c0 100644 --- a/python/gui/auto_generated/qgsfilewidget.sip.in +++ b/python/gui/auto_generated/qgsfilewidget.sip.in @@ -93,7 +93,7 @@ selected files. QString storageType() const; %Docstring -Get storage type unique identifier as defined in :py:class:`QgsExternalStorageRegistry`. +Returns storage type unique identifier as defined in :py:class:`QgsExternalStorageRegistry`. Returns null QString if there is no storage defined, only file selection. .. seealso:: :py:func:`setStorageType` diff --git a/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h b/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h index d6a75d8a0309..7b301da8f70f 100644 --- a/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h +++ b/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h @@ -66,7 +66,9 @@ class GUI_EXPORT QgsExternalResourceWidgetWrapper : public QgsEditorWidgetWrappe void initWidget( QWidget *editor ) override; bool valid() const override; - // update file widget current expression context according to layer, feature, and parent feature + /** + * Update file widget current expression context according to layer, feature, and parent feature + */ void updateFileWidgetExpressionContext(); public slots: diff --git a/src/gui/qgsexternalresourcewidget.h b/src/gui/qgsexternalresourcewidget.h index 756344e4a480..e2d4b1e2a4f1 100644 --- a/src/gui/qgsexternalresourcewidget.h +++ b/src/gui/qgsexternalresourcewidget.h @@ -153,7 +153,7 @@ class GUI_EXPORT QgsExternalResourceWidget : public QWidget void setStorageType( const QString &storageType ); /** - * Get storage type unique identifier as defined in QgsExternalStorageRegistry. + * Returns storage type unique identifier as defined in QgsExternalStorageRegistry. * Returns null QString if there is no storage defined, only file selection. * \see setStorageType * \since QGIS 3.22 diff --git a/src/gui/qgsfilewidget.h b/src/gui/qgsfilewidget.h index 7a235d2dc193..a6a47df274bd 100644 --- a/src/gui/qgsfilewidget.h +++ b/src/gui/qgsfilewidget.h @@ -136,7 +136,7 @@ class GUI_EXPORT QgsFileWidget : public QWidget void setStorageType( const QString &storageType ); /** - * Get storage type unique identifier as defined in QgsExternalStorageRegistry. + * Returns storage type unique identifier as defined in QgsExternalStorageRegistry. * Returns null QString if there is no storage defined, only file selection. * \see setStorageType * \since QGIS 3.22 diff --git a/tests/src/gui/testqgsfilewidget.cpp b/tests/src/gui/testqgsfilewidget.cpp index e19f394fe1ef..e3518552c510 100644 --- a/tests/src/gui/testqgsfilewidget.cpp +++ b/tests/src/gui/testqgsfilewidget.cpp @@ -587,7 +587,7 @@ void TestQgsFileWidget::testStoringSeveralFilesError_data() void TestQgsFileWidget::testStoringSeveralFilesError() { - // test widget when storing several files with an external storage and an error occured + // test widget when storing several files with an external storage and an error occurred QEventLoop loop; QFETCH( bool, useLink ); From 74512ffde909cca172d35f6603bd40dc5efd3eda Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Wed, 4 Aug 2021 11:42:22 +0200 Subject: [PATCH 03/11] Separate QgsFileWidget from QgsExternalStorageFileWidget --- .../qgsexternalresourcewidget.sip.in | 4 +- .../qgsexternalstoragefilewidget.sip.in | 181 ++++ .../gui/auto_generated/qgsfilewidget.sip.in | 132 +-- python/gui/gui_auto.sip | 1 + src/gui/CMakeLists.txt | 2 + .../qgsexternalresourceconfigdlg.cpp | 3 +- .../qgsexternalresourcewidgetwrapper.cpp | 1 + src/gui/qgsexternalresourcewidget.cpp | 5 +- src/gui/qgsexternalresourcewidget.h | 9 +- src/gui/qgsexternalstoragefilewidget.cpp | 265 ++++++ src/gui/qgsexternalstoragefilewidget.h | 194 +++++ src/gui/qgsfilewidget.cpp | 226 +---- src/gui/qgsfilewidget.h | 133 +-- tests/src/gui/CMakeLists.txt | 1 + .../testqgsexternalresourcewidgetwrapper.cpp | 9 +- .../gui/testqgsexternalstoragefilewidget.cpp | 791 ++++++++++++++++++ tests/src/gui/testqgsfilewidget.cpp | 737 ---------------- 17 files changed, 1488 insertions(+), 1206 deletions(-) create mode 100644 python/gui/auto_generated/qgsexternalstoragefilewidget.sip.in create mode 100644 src/gui/qgsexternalstoragefilewidget.cpp create mode 100644 src/gui/qgsexternalstoragefilewidget.h create mode 100644 tests/src/gui/testqgsexternalstoragefilewidget.cpp diff --git a/python/gui/auto_generated/qgsexternalresourcewidget.sip.in b/python/gui/auto_generated/qgsexternalresourcewidget.sip.in index 7f10e1c766a0..54c8b949f31e 100644 --- a/python/gui/auto_generated/qgsexternalresourcewidget.sip.in +++ b/python/gui/auto_generated/qgsexternalresourcewidget.sip.in @@ -59,9 +59,9 @@ documentPath returns the path of the current document in the widget %End void setDocumentPath( const QVariant &documentPath ); - QgsFileWidget *fileWidget(); + QgsExternalStorageFileWidget *fileWidget(); %Docstring -access the file widget to allow its configuration +Returns file widget to allow its configuration %End bool fileWidgetVisible() const; diff --git a/python/gui/auto_generated/qgsexternalstoragefilewidget.sip.in b/python/gui/auto_generated/qgsexternalstoragefilewidget.sip.in new file mode 100644 index 000000000000..ccf710bcb0fb --- /dev/null +++ b/python/gui/auto_generated/qgsexternalstoragefilewidget.sip.in @@ -0,0 +1,181 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsexternalstoragefilewidget.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class QgsExternalStorageFileWidget : QgsFileWidget +{ +%Docstring(signature="appended") +The :py:class:`QgsExternalStorageFileWidget` class creates a widget for selecting a file or a folder +and stores it to a given external storage backend if defined + +.. versionadded:: 3.22 +%End + +%TypeHeaderCode +#include "qgsexternalstoragefilewidget.h" +%End +%ConvertToSubClassCode + if ( qobject_cast( sipCpp ) ) + sipType = sipType_QgsExternalStorageFileWidget; + else + sipType = NULL; +%End + public: + + explicit QgsExternalStorageFileWidget( QWidget *parent /TransferThis/ = 0 ); +%Docstring +QgsExternalStorageFileWidget creates a widget for selecting a file or a folder. +%End + + void setStorageType( const QString &storageType ); +%Docstring +Set ``storageType`` storage type unique identifier as defined in :py:class:`QgsExternalStorageRegistry` or +null QString if there is no storage defined. +If no external storage has been defined, QgsExternalStorageFileWidget will only update file path according to +selected files. + +.. seealso:: :py:func:`storageType` + +.. versionadded:: 3.22 +%End + + QString storageType() const; +%Docstring +Returns storage type unique identifier as defined in :py:class:`QgsExternalStorageRegistry`. +Returns null QString if there is no storage defined, only file selection. + +.. seealso:: :py:func:`setStorageType` + +.. versionadded:: 3.22 +%End + + QgsExternalStorage *externalStorage() const; +%Docstring +Returns external storage used to store selected file names, None if none have been defined. +If no external storage has been defined, QgsExternalStorageFileWidget will only update file path according to +selected files. + +.. seealso:: :py:func:`setStorageType` + +.. versionadded:: 3.22 +%End + + void setStorageAuthConfigId( const QString &authCfg ); +%Docstring +Sets the authentication configuration ID to be used for the current external storage (if +defined) + +.. versionadded:: 3.22 +%End + + const QString &storageAuthConfigId() const; +%Docstring +Returns the authentication configuration ID used for the current external storage (if defined) + +.. versionadded:: 3.22 +%End + + void setStorageUrlExpression( const QString &urlExpression ); +%Docstring +Set ``urlExpression`` expression, which once evaluated, provide the URL used to store selected +documents. This is used only if an external storage has been defined + +.. seealso:: :py:func:`setStorageType` + +.. versionadded:: 3.22 +%End + + QString storageUrlExpressionString() const; +%Docstring +Returns the original, unmodified expression string, which once evaluated, provide the +URL used to store selected documents. This is used only if an external storage has been defined. +Returns null if no expression has been set. + +.. seealso:: :py:func:`setStorageUrlExpression` + +.. versionadded:: 3.22 +%End + + QgsExpression *storageUrlExpression() const; +%Docstring +Returns expression, which once evaluated, provide the URL used to store selected +documents. This is used only if an external storage has been defined. +Returns null if no expression has been set. + +.. seealso:: :py:func:`setStorageUrlExpression` + +.. versionadded:: 3.22 +%End + + void setExpressionContext( const QgsExpressionContext &context ); +%Docstring +Set expression context to be used when for storage URL expression evaluation + +.. seealso:: :py:func:`setStorageUrlExpression` + +.. versionadded:: 3.22 +%End + + const QgsExpressionContext &expressionContext() const; +%Docstring +Returns expression context used for storage url expression evaluation + +.. seealso:: :py:func:`storageUrlExpression` + +.. versionadded:: 3.22 +%End + + void setMessageBar( QgsMessageBar *messageBar ); +%Docstring +Set ``messageBar`` to report messages + +.. versionadded:: 3.22 +%End + + QgsMessageBar *messageBar() const; +%Docstring +Returns message bar used to report messages + +.. versionadded:: 3.22 +%End + + static QgsExpressionContextScope *createFileWidgetScope(); +%Docstring +Creates and Returns an expression context scope specific to QgsExternalStorageFileWidget +It defines the variable containing the user selected file name + +.. versionadded:: 3.22 +%End + + protected: + + virtual void updateLayout(); + + + virtual void setSelectedFileNames( QStringList fileNames ); + + + void addFileWidgetScope(); + + void storeExternalFiles( QStringList fileNames, QStringList storedUrls = QStringList() ); + + + + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsexternalstoragefilewidget.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/auto_generated/qgsfilewidget.sip.in b/python/gui/auto_generated/qgsfilewidget.sip.in index a80b087c64c0..9e57b25a9a06 100644 --- a/python/gui/auto_generated/qgsfilewidget.sip.in +++ b/python/gui/auto_generated/qgsfilewidget.sip.in @@ -77,104 +77,6 @@ Sets the current file ``path``. void setReadOnly( bool readOnly ); %Docstring Sets whether the widget should be read only. -%End - - void setStorageType( const QString &storageType ); -%Docstring -Set ``storageType`` storage type unique identifier as defined in :py:class:`QgsExternalStorageRegistry` or -null QString if there is no storage defined. -If no external storage has been defined, QgsFileWidget will only update file path according to -selected files. - -.. seealso:: :py:func:`storageType` - -.. versionadded:: 3.22 -%End - - QString storageType() const; -%Docstring -Returns storage type unique identifier as defined in :py:class:`QgsExternalStorageRegistry`. -Returns null QString if there is no storage defined, only file selection. - -.. seealso:: :py:func:`setStorageType` - -.. versionadded:: 3.22 -%End - - QgsExternalStorage *externalStorage() const; -%Docstring -Returns external storage used to store selected file names, None if none have been defined. -If no external storage has been defined, QgsFileWidget will only update file path according to -selected files. - -.. seealso:: :py:func:`setStorageType` - -.. versionadded:: 3.22 -%End - - void setStorageAuthConfigId( const QString &authCfg ); -%Docstring -Sets the authentication configuration ID to be used for the current external storage (if -defined) - -.. versionadded:: 3.22 -%End - - const QString &storageAuthConfigId() const; -%Docstring -Returns the authentication configuration ID used for the current external storage (if defined) - -.. versionadded:: 3.22 -%End - - void setStorageUrlExpression( const QString &urlExpression ); -%Docstring -Set ``urlExpression`` expression, which once evaluated, provide the URL used to store selected -documents. This is used only if an external storage has been defined - -.. seealso:: :py:func:`setStorageType` - -.. versionadded:: 3.22 -%End - - QString storageUrlExpressionString() const; -%Docstring -Returns the original, unmodified expression string, which once evaluated, provide the -URL used to store selected documents. This is used only if an external storage has been defined. -Returns null if no expression has been set. - -.. seealso:: :py:func:`setStorageUrlExpression` - -.. versionadded:: 3.22 -%End - - QgsExpression *storageUrlExpression() const; -%Docstring -Returns expression, which once evaluated, provide the URL used to store selected -documents. This is used only if an external storage has been defined. -Returns null if no expression has been set. - -.. seealso:: :py:func:`setStorageUrlExpression` - -.. versionadded:: 3.22 -%End - - void setExpressionContext( const QgsExpressionContext &context ); -%Docstring -Set expression context to be used when for storage URL expression evaluation - -.. seealso:: :py:func:`setStorageUrlExpression` - -.. versionadded:: 3.22 -%End - - const QgsExpressionContext &expressionContext() const; -%Docstring -Returns expression context used for storage url expression evaluation - -.. seealso:: :py:func:`storageUrlExpression` - -.. versionadded:: 3.22 %End QString dialogTitle() const; @@ -352,31 +254,33 @@ the appearance and behavior of the line edit portion of the widget. .. versionadded:: 3.0 %End - void setMessageBar( QgsMessageBar *messageBar ); -%Docstring -Set ``messageBar`` to report messages + signals: -.. versionadded:: 3.22 + void fileChanged( const QString &path ); +%Docstring +Emitted whenever the current file or directory ``path`` is changed. %End - QgsMessageBar *messageBar() const; -%Docstring -Returns message bar used to report messages + protected: -.. versionadded:: 3.22 -%End + virtual void updateLayout(); + + virtual void setSelectedFileNames( QStringList fileNames ); - static QgsExpressionContextScope *createFileWidgetScope(); + static bool isMultiFiles( const QString &path ); + + void setFilePaths( const QStringList &filePaths ); + + + + QString toUrl( const QString &path ) const; %Docstring -Creates and Returns an expression context scope specific to QgsFileWidget -It defines the variable containing the user selected file name +returns a HTML code with a link to the given file path %End - signals: - - void fileChanged( const QString &path ); + QString relativePath( const QString &filePath, bool removeRelative ) const; %Docstring -Emitted whenever the current file or directory ``path`` is changed. +Returns a filePath with relative path options applied (or not) ! %End }; diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index 1fd2c7ed24c5..63cf3909b8e6 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -80,6 +80,7 @@ %Include auto_generated/qgsfilecontentsourcelineedit.sip %Include auto_generated/qgsfiledownloaderdialog.sip %Include auto_generated/qgsfilewidget.sip +%Include auto_generated/qgsexternalstoragefilewidget.sip %Include auto_generated/qgsfilterlineedit.sip %Include auto_generated/qgsfindfilesbypatternwidget.sip %Include auto_generated/qgsfloatingwidget.sip diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index b434d86b9124..e75d98fc54c3 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -465,6 +465,7 @@ set(QGIS_GUI_SRCS qgsfieldvalueslineedit.cpp qgsfilecontentsourcelineedit.cpp qgsfilewidget.cpp + qgsexternalstoragefilewidget.cpp qgsfilterlineedit.cpp qgsfindfilesbypatternwidget.cpp qgsfloatingwidget.cpp @@ -715,6 +716,7 @@ set(QGIS_GUI_HDRS qgsfilecontentsourcelineedit.h qgsfiledownloaderdialog.h qgsfilewidget.h + qgsexternalstoragefilewidget.h qgsfilterlineedit.h qgsfindfilesbypatternwidget.h qgsfloatingwidget.h diff --git a/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp b/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp index a5286fcc16f4..c87db74bcb57 100644 --- a/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp +++ b/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp @@ -25,6 +25,7 @@ #include "qgsexternalstorage.h" #include "qgsexternalstorageregistry.h" #include "qgsexpressioncontextutils.h" +#include "qgsexternalstoragefilewidget.h" #include #include @@ -282,7 +283,7 @@ QgsExpressionContext QgsExternalResourceConfigDlg::createExpressionContext() con context << QgsExpressionContextUtils::formScope( ); context << QgsExpressionContextUtils::parentFormScope( ); - QgsExpressionContextScope *fileWidgetScope = QgsFileWidget::createFileWidgetScope(); + QgsExpressionContextScope *fileWidgetScope = QgsExternalStorageFileWidget::createFileWidgetScope(); context << fileWidgetScope; context.setHighlightedVariables( fileWidgetScope->variableNames() ); diff --git a/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp b/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp index 8b294ce8378a..623816cac573 100644 --- a/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp @@ -24,6 +24,7 @@ #include "qgsfilterlineedit.h" #include "qgsapplication.h" #include "qgsexpressioncontextutils.h" +#include "qgsexternalstoragefilewidget.h" QgsExternalResourceWidgetWrapper::QgsExternalResourceWidgetWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor, QgsMessageBar *messageBar, QWidget *parent ) diff --git a/src/gui/qgsexternalresourcewidget.cpp b/src/gui/qgsexternalresourcewidget.cpp index b32fbdcf33f8..8f2999028ef7 100644 --- a/src/gui/qgsexternalresourcewidget.cpp +++ b/src/gui/qgsexternalresourcewidget.cpp @@ -22,6 +22,7 @@ #include "qgstaskmanager.h" #include "qgsexternalstorage.h" #include "qgsmessagebar.h" +#include "qgsexternalstoragefilewidget.h" #include #include @@ -45,7 +46,7 @@ QgsExternalResourceWidget::QgsExternalResourceWidget( QWidget *parent ) QGridLayout *layout = new QGridLayout(); layout->setContentsMargins( 0, 0, 0, 0 ); - mFileWidget = new QgsFileWidget( this ); + mFileWidget = new QgsExternalStorageFileWidget( this ); layout->addWidget( mFileWidget, 0, 0 ); mFileWidget->setVisible( mFileWidgetVisible ); @@ -93,7 +94,7 @@ void QgsExternalResourceWidget::setDocumentPath( const QVariant &path ) mFileWidget->setFilePath( path.toString() ); } -QgsFileWidget *QgsExternalResourceWidget::fileWidget() +QgsExternalStorageFileWidget *QgsExternalResourceWidget::fileWidget() { return mFileWidget; } diff --git a/src/gui/qgsexternalresourcewidget.h b/src/gui/qgsexternalresourcewidget.h index e2d4b1e2a4f1..a7bf5febe614 100644 --- a/src/gui/qgsexternalresourcewidget.h +++ b/src/gui/qgsexternalresourcewidget.h @@ -20,6 +20,7 @@ class QWebView; class QgsPixmapLabel; class QgsMessageBar; +class QgsExternalStorageFileWidget; #include #include @@ -86,8 +87,10 @@ class GUI_EXPORT QgsExternalResourceWidget : public QWidget QVariant documentPath( QVariant::Type type = QVariant::String ) const; void setDocumentPath( const QVariant &documentPath ); - //! access the file widget to allow its configuration - QgsFileWidget *fileWidget(); + /** + * Returns file widget to allow its configuration + */ + QgsExternalStorageFileWidget *fileWidget(); //! returns if the file widget is visible in the widget bool fileWidgetVisible() const; @@ -216,7 +219,7 @@ class GUI_EXPORT QgsExternalResourceWidget : public QWidget QString mDefaultRoot; // configured default root path for QgsFileWidget::RelativeStorage::RelativeDefaultPath //! UI objects - QgsFileWidget *mFileWidget = nullptr; + QgsExternalStorageFileWidget *mFileWidget = nullptr; QgsPixmapLabel *mPixmapLabel = nullptr; #ifdef WITH_QTWEBKIT //! This webview is used as a container to display the picture diff --git a/src/gui/qgsexternalstoragefilewidget.cpp b/src/gui/qgsexternalstoragefilewidget.cpp new file mode 100644 index 000000000000..d1230aaa17a9 --- /dev/null +++ b/src/gui/qgsexternalstoragefilewidget.cpp @@ -0,0 +1,265 @@ +/*************************************************************************** + qgsexternalstoragefilewidget.cpp + -------------------------------------- + Date : August 2021 + Copyright : (C) 2021 by Julien Cabieces + Email : julien dot cabieces at oslandia dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsexternalstoragefilewidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qgssettings.h" +#include "qgsfilterlineedit.h" +#include "qgsfocuskeeper.h" +#include "qgslogger.h" +#include "qgsproject.h" +#include "qgsapplication.h" +#include "qgsfileutils.h" +#include "qgsmimedatautils.h" +#include "qgsexternalstorage.h" +#include "qgsexternalstorageregistry.h" +#include "qgsmessagebar.h" + +#define FILEPATH_VARIABLE "selected_file_path" + +QgsExternalStorageFileWidget::QgsExternalStorageFileWidget( QWidget *parent ) + : QgsFileWidget( parent ) +{ + mProgressLabel = new QLabel( this ); + mLayout->addWidget( mProgressLabel ); + mProgressLabel->hide(); + + mProgressBar = new QProgressBar( this ); + mLayout->addWidget( mProgressBar ); + mProgressBar->hide(); + + mCancelButton = new QToolButton( this ); + mLayout->addWidget( mCancelButton ); + mCancelButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mTaskCancel.svg" ) ) ); + mCancelButton->hide(); + + setLayout( mLayout ); +} + +void QgsExternalStorageFileWidget::setStorageType( const QString &storageType ) +{ + if ( storageType.isEmpty() ) + mExternalStorage = nullptr; + + else + { + mExternalStorage = QgsApplication::externalStorageRegistry()->externalStorageFromType( storageType ); + if ( !mExternalStorage ) + { + QgsDebugMsg( QStringLiteral( "Invalid storage type: %1" ).arg( storageType ) ); + return; + } + addFileWidgetScope(); + } +} + +QString QgsExternalStorageFileWidget::storageType() const +{ + return mExternalStorage ? mExternalStorage->type() : QString(); +} + +QgsExternalStorage *QgsExternalStorageFileWidget::externalStorage() const +{ + return mExternalStorage; +} + +void QgsExternalStorageFileWidget::setStorageAuthConfigId( const QString &authCfg ) +{ + mAuthCfg = authCfg; +} + +const QString &QgsExternalStorageFileWidget::storageAuthConfigId() const +{ + return mAuthCfg; +} + +void QgsExternalStorageFileWidget::setStorageUrlExpression( const QString &urlExpression ) +{ + mStorageUrlExpression.reset( new QgsExpression( urlExpression ) ); +} + +QgsExpression *QgsExternalStorageFileWidget::storageUrlExpression() const +{ + return mStorageUrlExpression.get(); +} + +QString QgsExternalStorageFileWidget::storageUrlExpressionString() const +{ + return mStorageUrlExpression ? mStorageUrlExpression->expression() : QString(); +} + + +void QgsExternalStorageFileWidget::setExpressionContext( const QgsExpressionContext &context ) +{ + mScope = nullptr; // deleted by old context when we override it with the new one + mExpressionContext = context; + addFileWidgetScope(); +} + +void QgsExternalStorageFileWidget::addFileWidgetScope() +{ + if ( !mExternalStorage || mScope ) + return; + + mScope = createFileWidgetScope(); + mExpressionContext << mScope; +} + +QgsExpressionContextScope *QgsExternalStorageFileWidget::createFileWidgetScope() +{ + QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "FileWidget" ) ); + scope->addVariable( QgsExpressionContextScope::StaticVariable( + QStringLiteral( FILEPATH_VARIABLE ), + QString(), true, false, tr( "User selected absolute filepath" ) ) ); + return scope; +} + +const QgsExpressionContext &QgsExternalStorageFileWidget::expressionContext() const +{ + return mExpressionContext; +} + + +void QgsExternalStorageFileWidget::setMessageBar( QgsMessageBar *messageBar ) +{ + mMessageBar = messageBar; +} + +QgsMessageBar *QgsExternalStorageFileWidget::messageBar() const +{ + return mMessageBar; +} + +void QgsExternalStorageFileWidget::updateLayout() +{ + mProgressLabel->setVisible( mStoreInProgress ); + mProgressBar->setVisible( mStoreInProgress ); + mCancelButton->setVisible( mStoreInProgress ); + + const bool linkVisible = mUseLink && !mIsLinkEdited; + + mLineEdit->setVisible( !mStoreInProgress && !linkVisible ); + mLinkLabel->setVisible( !mStoreInProgress && linkVisible ); + mLinkEditButton->setVisible( !mStoreInProgress && mUseLink && !mReadOnly ); + + mFileWidgetButton->setVisible( mButtonVisible && !mStoreInProgress ); + mFileWidgetButton->setEnabled( !mReadOnly ); + mLineEdit->setEnabled( !mReadOnly ); + + mLinkEditButton->setIcon( linkVisible && !mReadOnly ? + QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) : + QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveEdits.svg" ) ) ); +} + +void QgsExternalStorageFileWidget::setSelectedFileNames( QStringList fileNames ) +{ + Q_ASSERT( fileNames.count() ); + + // store files first, update filePath later + if ( mExternalStorage ) + { + if ( !mStorageUrlExpression->prepare( &mExpressionContext ) ) + { + if ( messageBar() ) + { + messageBar()->pushWarning( tr( "Storing External resource" ), + tr( "Storage URL expression is invalid : %1" ).arg( mStorageUrlExpression->evalErrorString() ) ); + } + + QgsDebugMsg( tr( "Storage URL expression is invalid : %1" ).arg( mStorageUrlExpression->evalErrorString() ) ); + return; + } + + storeExternalFiles( fileNames ); + } + else + { + QgsFileWidget::setSelectedFileNames( fileNames ); + } +} + +void QgsExternalStorageFileWidget::storeExternalFiles( QStringList fileNames, QStringList storedUrls ) +{ + const QString filePath = fileNames.takeFirst(); + + mProgressLabel->setText( tr( "Storing file %1 ..." ).arg( QFileInfo( filePath ).baseName() ) ); + mStoreInProgress = true; + updateLayout(); + + Q_ASSERT( mScope ); + mScope->setVariable( QStringLiteral( FILEPATH_VARIABLE ), filePath ); + + QVariant url = mStorageUrlExpression->evaluate( &mExpressionContext ); + if ( !url.isValid() ) + { + if ( messageBar() ) + { + messageBar()->pushWarning( tr( "Storing External resource" ), + tr( "Storage URL expression is invalid : %1" ).arg( mStorageUrlExpression->evalErrorString() ) ); + } + + mStoreInProgress = false; + updateLayout(); + + return; + } + + QgsExternalStorageStoredContent *storedContent = mExternalStorage->store( filePath, url.toString(), mAuthCfg ); + + connect( storedContent, &QgsExternalStorageStoredContent::progressChanged, mProgressBar, &QProgressBar::setValue ); + connect( mCancelButton, &QToolButton::clicked, storedContent, &QgsExternalStorageStoredContent::cancel ); + + auto onStoreFinished = [ = ] + { + mStoreInProgress = false; + updateLayout(); + storedContent->deleteLater(); + + if ( storedContent->status() == Qgis::ContentStatus::Failed && messageBar() ) + { + messageBar()->pushWarning( tr( "Storing External resource" ), + tr( "Storing file '%1' to url '%2' has failed : %3" ).arg( filePath, url.toString(), storedContent->errorString() ) ); + } + + if ( storedContent->status() != Qgis::ContentStatus::Finished ) + return; + + QStringList newStoredUrls = storedUrls; + newStoredUrls << storedContent->url(); + + // every thing has been stored, we update filepath + if ( fileNames.isEmpty() ) + { + setFilePaths( newStoredUrls ); + } + else + storeExternalFiles( fileNames, newStoredUrls ); + }; + + connect( storedContent, &QgsExternalStorageStoredContent::stored, onStoreFinished ); + connect( storedContent, &QgsExternalStorageStoredContent::canceled, onStoreFinished ); + connect( storedContent, &QgsExternalStorageStoredContent::errorOccurred, onStoreFinished ); + + storedContent->store(); +} diff --git a/src/gui/qgsexternalstoragefilewidget.h b/src/gui/qgsexternalstoragefilewidget.h new file mode 100644 index 000000000000..1f0713d26dcc --- /dev/null +++ b/src/gui/qgsexternalstoragefilewidget.h @@ -0,0 +1,194 @@ +/*************************************************************************** + qgsexternalstoragefilewidget.h + -------------------------------------- + Date : August 2021 + Copyright : (C) 2021 by Julien Cabieces + Email : julien dot cabieces at oslandia dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSEXTERNALSTORAGEFILEWIDGET_H +#define QGSEXTERNALSTORAGEFILEWIDGET_H + +class QLabel; +class QToolButton; +class QVariant; +class QHBoxLayout; +class QProgressBar; +class QgsExternalStorage; +class QgsMessageBar; + +#include +#include + +#include "qgsfilewidget.h" +#include "qgsexpressioncontext.h" + +/** + * \ingroup gui + * \brief The QgsExternalStorageFileWidget class creates a widget for selecting a file or a folder + * and stores it to a given external storage backend if defined + * \since 3.22 + */ +class GUI_EXPORT QgsExternalStorageFileWidget : public QgsFileWidget +{ + +#ifdef SIP_RUN + SIP_CONVERT_TO_SUBCLASS_CODE + if ( qobject_cast( sipCpp ) ) + sipType = sipType_QgsExternalStorageFileWidget; + else + sipType = NULL; + SIP_END +#endif + + Q_OBJECT + Q_PROPERTY( QString storageType READ storageType WRITE setStorageType ) + Q_PROPERTY( QString auth READ storageAuthConfigId WRITE setStorageAuthConfigId ) + Q_PROPERTY( QString storageUrlExpression READ storageUrlExpressionString WRITE setStorageUrlExpression ) + + public: + + /** + * \brief QgsExternalStorageFileWidget creates a widget for selecting a file or a folder. + */ + explicit QgsExternalStorageFileWidget( QWidget *parent SIP_TRANSFERTHIS = nullptr ); + + /** + * Set \a storageType storage type unique identifier as defined in QgsExternalStorageRegistry or + * null QString if there is no storage defined. + * If no external storage has been defined, QgsExternalStorageFileWidget will only update file path according to + * selected files. + * \see storageType + * \since QGIS 3.22 + */ + void setStorageType( const QString &storageType ); + + /** + * Returns storage type unique identifier as defined in QgsExternalStorageRegistry. + * Returns null QString if there is no storage defined, only file selection. + * \see setStorageType + * \since QGIS 3.22 + */ + QString storageType() const; + + /** + * Returns external storage used to store selected file names, nullptr if none have been defined. + * If no external storage has been defined, QgsExternalStorageFileWidget will only update file path according to + * selected files. + * \see setStorageType + * \since QGIS 3.22 + */ + QgsExternalStorage *externalStorage() const; + + /** + * Sets the authentication configuration ID to be used for the current external storage (if + * defined) + * \since QGIS 3.22 + */ + void setStorageAuthConfigId( const QString &authCfg ); + + /** + * Returns the authentication configuration ID used for the current external storage (if defined) + * \since QGIS 3.22 + */ + const QString &storageAuthConfigId() const; + + /** + * Set \a urlExpression expression, which once evaluated, provide the URL used to store selected + * documents. This is used only if an external storage has been defined + * \see setStorageType(), externalStorage() + * \since 3.22 + */ + void setStorageUrlExpression( const QString &urlExpression ); + + /** + * Returns the original, unmodified expression string, which once evaluated, provide the + * URL used to store selected documents. This is used only if an external storage has been defined. + * Returns null if no expression has been set. + * \see setStorageUrlExpression() + * \since 3.22 + */ + QString storageUrlExpressionString() const; + + /** + * Returns expression, which once evaluated, provide the URL used to store selected + * documents. This is used only if an external storage has been defined. + * Returns null if no expression has been set. + * \see setStorageUrlExpression() + * \since 3.22 + */ + QgsExpression *storageUrlExpression() const; + + /** + * Set expression context to be used when for storage URL expression evaluation + * \see setStorageUrlExpression + * \since 3.22 + */ + void setExpressionContext( const QgsExpressionContext &context ); + + /** + * Returns expression context used for storage url expression evaluation + * \see storageUrlExpression + * \since 3.22 + */ + const QgsExpressionContext &expressionContext() const; + + /** + * Set \a messageBar to report messages + * \since QGIS 3.22 + */ + void setMessageBar( QgsMessageBar *messageBar ); + + /** + * Returns message bar used to report messages + * \since QGIS 3.22 + */ + QgsMessageBar *messageBar() const; + + /** + * Creates and Returns an expression context scope specific to QgsExternalStorageFileWidget + * It defines the variable containing the user selected file name + * \since 3.22 + */ + static QgsExpressionContextScope *createFileWidgetScope(); + + protected: + + void updateLayout() override; + + void setSelectedFileNames( QStringList fileNames ) override; + + // add file widget specific scope to expression context + void addFileWidgetScope(); + + // stores \a fileNames files using current external storage. + // This is a recursive method, \a storedUrls contains urls for previously stored + // fileNames. When all files have been successfully stored, current mFilePath + // is updated + void storeExternalFiles( QStringList fileNames, QStringList storedUrls = QStringList() ); + + bool mStoreInProgress = false; + + QgsExternalStorage *mExternalStorage = nullptr; + QString mAuthCfg; + std::unique_ptr< QgsExpression > mStorageUrlExpression; + QgsExpressionContext mExpressionContext; + QgsExpressionContextScope *mScope = nullptr; + + QLabel *mProgressLabel = nullptr; + QProgressBar *mProgressBar = nullptr; + QToolButton *mCancelButton = nullptr; + QgsMessageBar *mMessageBar = nullptr; + + friend class TestQgsExternalResourceWidgetWrapper; + friend class TestQgsExternalStorageFileWidget; +}; + +#endif // QGSEXTERNALSTORAGEFILEWIDGET_H diff --git a/src/gui/qgsfilewidget.cpp b/src/gui/qgsfilewidget.cpp index 463e73d4a22a..1caaa42f82c7 100644 --- a/src/gui/qgsfilewidget.cpp +++ b/src/gui/qgsfilewidget.cpp @@ -23,8 +23,6 @@ #include #include #include -#include -#include #include "qgssettings.h" #include "qgsfilterlineedit.h" @@ -34,11 +32,6 @@ #include "qgsapplication.h" #include "qgsfileutils.h" #include "qgsmimedatautils.h" -#include "qgsexternalstorage.h" -#include "qgsexternalstorageregistry.h" -#include "qgsmessagebar.h" - -#define FILEPATH_VARIABLE "selected_file_path" QgsFileWidget::QgsFileWidget( QWidget *parent ) : QWidget( parent ) @@ -80,19 +73,6 @@ QgsFileWidget::QgsFileWidget( QWidget *parent ) connect( mFileWidgetButton, &QAbstractButton::clicked, this, &QgsFileWidget::openFileDialog ); mLayout->addWidget( mFileWidgetButton ); - mProgressLabel = new QLabel( this ); - mLayout->addWidget( mProgressLabel ); - mProgressLabel->hide(); - - mProgressBar = new QProgressBar( this ); - mLayout->addWidget( mProgressBar ); - mProgressBar->hide(); - - mCancelButton = new QToolButton( this ); - mLayout->addWidget( mCancelButton ); - mCancelButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mTaskCancel.svg" ) ) ); - mCancelButton->hide(); - setLayout( mLayout ); } @@ -138,89 +118,6 @@ void QgsFileWidget::setReadOnly( bool readOnly ) updateLayout(); } -void QgsFileWidget::setStorageType( const QString &storageType ) -{ - if ( storageType.isEmpty() ) - mExternalStorage = nullptr; - - else - { - mExternalStorage = QgsApplication::externalStorageRegistry()->externalStorageFromType( storageType ); - if ( !mExternalStorage ) - { - QgsDebugMsg( QStringLiteral( "Invalid storage type: %1" ).arg( storageType ) ); - return; - } - addFileWidgetScope(); - } -} - -QString QgsFileWidget::storageType() const -{ - return mExternalStorage ? mExternalStorage->type() : QString(); -} - -QgsExternalStorage *QgsFileWidget::externalStorage() const -{ - return mExternalStorage; -} - -void QgsFileWidget::setStorageAuthConfigId( const QString &authCfg ) -{ - mAuthCfg = authCfg; -} - -const QString &QgsFileWidget::storageAuthConfigId() const -{ - return mAuthCfg; -} - -void QgsFileWidget::setStorageUrlExpression( const QString &urlExpression ) -{ - mStorageUrlExpression.reset( new QgsExpression( urlExpression ) ); -} - -QgsExpression *QgsFileWidget::storageUrlExpression() const -{ - return mStorageUrlExpression.get(); -} - -QString QgsFileWidget::storageUrlExpressionString() const -{ - return mStorageUrlExpression ? mStorageUrlExpression->expression() : QString(); -} - - -void QgsFileWidget::setExpressionContext( const QgsExpressionContext &context ) -{ - mScope = nullptr; // deleted by old context when we override it with the new one - mExpressionContext = context; - addFileWidgetScope(); -} - -void QgsFileWidget::addFileWidgetScope() -{ - if ( !mExternalStorage || mScope ) - return; - - mScope = createFileWidgetScope(); - mExpressionContext << mScope; -} - -QgsExpressionContextScope *QgsFileWidget::createFileWidgetScope() -{ - QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "FileWidget" ) ); - scope->addVariable( QgsExpressionContextScope::StaticVariable( - QStringLiteral( FILEPATH_VARIABLE ), - QString(), true, false, tr( "User selected absolute filepath" ) ) ); - return scope; -} - -const QgsExpressionContext &QgsFileWidget::expressionContext() const -{ - return mExpressionContext; -} - QString QgsFileWidget::dialogTitle() const { return mDialogTitle; @@ -353,36 +250,20 @@ QgsFilterLineEdit *QgsFileWidget::lineEdit() return mLineEdit; } -void QgsFileWidget::setMessageBar( QgsMessageBar *messageBar ) -{ - mMessageBar = messageBar; -} - -QgsMessageBar *QgsFileWidget::messageBar() const -{ - return mMessageBar; -} - void QgsFileWidget::updateLayout() { - mProgressLabel->setVisible( mStoreInProgress ); - mProgressBar->setVisible( mStoreInProgress ); - mCancelButton->setVisible( mStoreInProgress ); - const bool linkVisible = mUseLink && !mIsLinkEdited; - mLineEdit->setVisible( !mStoreInProgress && !linkVisible ); - mLinkLabel->setVisible( !mStoreInProgress && linkVisible ); - mLinkEditButton->setVisible( !mStoreInProgress && mUseLink && !mReadOnly ); + mLineEdit->setVisible( !linkVisible ); + mLinkLabel->setVisible( linkVisible ); + mLinkEditButton->setVisible( mUseLink && !mReadOnly ); - mFileWidgetButton->setVisible( !mStoreInProgress ); mFileWidgetButton->setEnabled( !mReadOnly ); mLineEdit->setEnabled( !mReadOnly ); mLinkEditButton->setIcon( linkVisible && !mReadOnly ? QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveEdits.svg" ) ) ); - } void QgsFileWidget::openFileDialog() @@ -462,15 +343,6 @@ void QgsFileWidget::openFileDialog() if ( mStorageMode != GetMultipleFiles ) fileNames << fileName; - setSelectedFileNames( fileNames ); -} - -void QgsFileWidget::setSelectedFileNames( QStringList fileNames ) -{ - Q_ASSERT( fileNames.count() ); - - QgsSettings settings; - for ( int i = 0; i < fileNames.length(); i++ ) { fileNames.replace( i, QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileNames.at( i ) ).absoluteFilePath() ) ) ); @@ -489,98 +361,20 @@ void QgsFileWidget::setSelectedFileNames( QStringList fileNames ) break; } - // Handle relative Path storage - for ( int i = 0; i < fileNames.length(); i++ ) - { - fileNames.replace( i, relativePath( fileNames.at( i ), true ) ); - } - - // store files first, update filePath later - if ( mExternalStorage ) - { - if ( !mStorageUrlExpression->prepare( &mExpressionContext ) ) - { - if ( messageBar() ) - { - messageBar()->pushWarning( tr( "Storing External resource" ), - tr( "Storage URL expression is invalid : %1" ).arg( mStorageUrlExpression->evalErrorString() ) ); - } - - QgsDebugMsg( tr( "Storage URL expression is invalid : %1" ).arg( mStorageUrlExpression->evalErrorString() ) ); - return; - } - - storeExternalFiles( fileNames ); - } - else - { - setFilePaths( fileNames ); - } + setSelectedFileNames( fileNames ); } -void QgsFileWidget::storeExternalFiles( QStringList fileNames, QStringList storedUrls ) +void QgsFileWidget::setSelectedFileNames( QStringList fileNames ) { - const QString filePath = fileNames.takeFirst(); - - mProgressLabel->setText( tr( "Storing file %1 ..." ).arg( QFileInfo( filePath ).baseName() ) ); - mStoreInProgress = true; - updateLayout(); - - Q_ASSERT( mScope ); - mScope->setVariable( QStringLiteral( FILEPATH_VARIABLE ), filePath ); + Q_ASSERT( fileNames.count() ); - QVariant url = mStorageUrlExpression->evaluate( &mExpressionContext ); - if ( !url.isValid() ) + // Handle relative Path storage + for ( int i = 0; i < fileNames.length(); i++ ) { - if ( messageBar() ) - { - messageBar()->pushWarning( tr( "Storing External resource" ), - tr( "Storage URL expression is invalid : %1" ).arg( mStorageUrlExpression->evalErrorString() ) ); - } - - mStoreInProgress = false; - updateLayout(); - - return; + fileNames.replace( i, relativePath( fileNames.at( i ), true ) ); } - QgsExternalStorageStoredContent *storedContent = mExternalStorage->store( filePath, url.toString(), mAuthCfg ); - - connect( storedContent, &QgsExternalStorageStoredContent::progressChanged, mProgressBar, &QProgressBar::setValue ); - connect( mCancelButton, &QToolButton::clicked, storedContent, &QgsExternalStorageStoredContent::cancel ); - - auto onStoreFinished = [ = ] - { - mStoreInProgress = false; - updateLayout(); - storedContent->deleteLater(); - - if ( storedContent->status() == Qgis::ContentStatus::Failed && messageBar() ) - { - messageBar()->pushWarning( tr( "Storing External resource" ), - tr( "Storing file '%1' to url '%2' has failed : %3" ).arg( filePath, url.toString(), storedContent->errorString() ) ); - } - - if ( storedContent->status() != Qgis::ContentStatus::Finished ) - return; - - QStringList newStoredUrls = storedUrls; - newStoredUrls << storedContent->url(); - - // every thing has been stored, we update filepath - if ( fileNames.isEmpty() ) - { - setFilePaths( newStoredUrls ); - } - else - storeExternalFiles( fileNames, newStoredUrls ); - }; - - connect( storedContent, &QgsExternalStorageStoredContent::stored, onStoreFinished ); - connect( storedContent, &QgsExternalStorageStoredContent::canceled, onStoreFinished ); - connect( storedContent, &QgsExternalStorageStoredContent::errorOccurred, onStoreFinished ); - - storedContent->store(); + setFilePaths( fileNames ); } void QgsFileWidget::setFilePaths( const QStringList &filePaths ) diff --git a/src/gui/qgsfilewidget.h b/src/gui/qgsfilewidget.h index a6a47df274bd..50d6f0da6596 100644 --- a/src/gui/qgsfilewidget.h +++ b/src/gui/qgsfilewidget.h @@ -21,10 +21,7 @@ class QLabel; class QToolButton; class QVariant; class QHBoxLayout; -class QProgressBar; -class QgsExternalStorage; class QgsFileDropEdit; -class QgsMessageBar; #include #include @@ -32,7 +29,6 @@ class QgsMessageBar; #include "qgis_gui.h" #include "qgis_sip.h" #include "qgshighlightablelineedit.h" -#include "qgsexpressioncontext.h" /** @@ -52,7 +48,6 @@ class GUI_EXPORT QgsFileWidget : public QWidget #endif Q_OBJECT - Q_PROPERTY( QString storageType READ storageType WRITE setStorageType ) Q_PROPERTY( bool fileWidgetButtonVisible READ fileWidgetButtonVisible WRITE setFileWidgetButtonVisible ) Q_PROPERTY( bool useLink READ useLink WRITE setUseLink ) Q_PROPERTY( bool fullUrl READ fullUrl WRITE setFullUrl ) @@ -62,8 +57,6 @@ class GUI_EXPORT QgsFileWidget : public QWidget Q_PROPERTY( StorageMode storageMode READ storageMode WRITE setStorageMode ) Q_PROPERTY( RelativeStorage relativeStorage READ relativeStorage WRITE setRelativeStorage ) Q_PROPERTY( QFileDialog::Options options READ options WRITE setOptions ) - Q_PROPERTY( QString auth READ storageAuthConfigId WRITE setStorageAuthConfigId ) - Q_PROPERTY( QString storageUrlExpression READ storageUrlExpressionString WRITE setStorageUrlExpression ) public: @@ -125,86 +118,6 @@ class GUI_EXPORT QgsFileWidget : public QWidget */ void setReadOnly( bool readOnly ); - /** - * Set \a storageType storage type unique identifier as defined in QgsExternalStorageRegistry or - * null QString if there is no storage defined. - * If no external storage has been defined, QgsFileWidget will only update file path according to - * selected files. - * \see storageType - * \since QGIS 3.22 - */ - void setStorageType( const QString &storageType ); - - /** - * Returns storage type unique identifier as defined in QgsExternalStorageRegistry. - * Returns null QString if there is no storage defined, only file selection. - * \see setStorageType - * \since QGIS 3.22 - */ - QString storageType() const; - - /** - * Returns external storage used to store selected file names, nullptr if none have been defined. - * If no external storage has been defined, QgsFileWidget will only update file path according to - * selected files. - * \see setStorageType - * \since QGIS 3.22 - */ - QgsExternalStorage *externalStorage() const; - - /** - * Sets the authentication configuration ID to be used for the current external storage (if - * defined) - * \since QGIS 3.22 - */ - void setStorageAuthConfigId( const QString &authCfg ); - - /** - * Returns the authentication configuration ID used for the current external storage (if defined) - * \since QGIS 3.22 - */ - const QString &storageAuthConfigId() const; - - /** - * Set \a urlExpression expression, which once evaluated, provide the URL used to store selected - * documents. This is used only if an external storage has been defined - * \see setStorageType(), externalStorage() - * \since 3.22 - */ - void setStorageUrlExpression( const QString &urlExpression ); - - /** - * Returns the original, unmodified expression string, which once evaluated, provide the - * URL used to store selected documents. This is used only if an external storage has been defined. - * Returns null if no expression has been set. - * \see setStorageUrlExpression() - * \since 3.22 - */ - QString storageUrlExpressionString() const; - - /** - * Returns expression, which once evaluated, provide the URL used to store selected - * documents. This is used only if an external storage has been defined. - * Returns null if no expression has been set. - * \see setStorageUrlExpression() - * \since 3.22 - */ - QgsExpression *storageUrlExpression() const; - - /** - * Set expression context to be used when for storage URL expression evaluation - * \see setStorageUrlExpression - * \since 3.22 - */ - void setExpressionContext( const QgsExpressionContext &context ); - - /** - * Returns expression context used for storage url expression evaluation - * \see storageUrlExpression - * \since 3.22 - */ - const QgsExpressionContext &expressionContext() const; - /** * Returns the open file dialog title. * @@ -374,24 +287,6 @@ class GUI_EXPORT QgsFileWidget : public QWidget */ QgsFilterLineEdit *lineEdit(); - /** - * Set \a messageBar to report messages - * \since QGIS 3.22 - */ - void setMessageBar( QgsMessageBar *messageBar ); - - /** - * Returns message bar used to report messages - * \since QGIS 3.22 - */ - QgsMessageBar *messageBar() const; - - /** - * Creates and Returns an expression context scope specific to QgsFileWidget - * It defines the variable containing the user selected file name - */ - static QgsExpressionContextScope *createFileWidgetScope(); - signals: /** @@ -404,20 +299,13 @@ class GUI_EXPORT QgsFileWidget : public QWidget void textEdited( const QString &path ); void editLink(); - private: - void updateLayout(); - - // called whenever user select file names from dialog - void setSelectedFileNames( QStringList fileNames ); + protected: - // add file widget specific scope to expression context - void addFileWidgetScope(); + // update buttons visibility + virtual void updateLayout(); - // stores \a fileNames files using current external storage. - // This is a recursive method, \a storedUrls contains urls for previously stored - // fileNames. When all files have been successfully stored, current mFilePath - // is updated - void storeExternalFiles( QStringList fileNames, QStringList storedUrls = QStringList() ); + // called whenever user select file names from dialog + virtual void setSelectedFileNames( QStringList fileNames ); // Returns true if \a path is a multifiles static bool isMultiFiles( const QString &path ); @@ -431,7 +319,6 @@ class GUI_EXPORT QgsFileWidget : public QWidget bool mFullUrl = false; bool mReadOnly = false; bool mIsLinkEdited = false; - bool mStoreInProgress = false; QString mDialogTitle; QString mFilter; QString mSelectedFilter; @@ -440,21 +327,12 @@ class GUI_EXPORT QgsFileWidget : public QWidget StorageMode mStorageMode = GetFile; RelativeStorage mRelativeStorage = Absolute; QFileDialog::Options mOptions = QFileDialog::Options(); - QgsExternalStorage *mExternalStorage = nullptr; - QString mAuthCfg; - std::unique_ptr< QgsExpression > mStorageUrlExpression; - QgsExpressionContext mExpressionContext; - QgsExpressionContextScope *mScope = nullptr; QLabel *mLinkLabel = nullptr; QgsFileDropEdit *mLineEdit = nullptr; QToolButton *mLinkEditButton = nullptr; QToolButton *mFileWidgetButton = nullptr; QHBoxLayout *mLayout = nullptr; - QLabel *mProgressLabel = nullptr; - QProgressBar *mProgressBar = nullptr; - QToolButton *mCancelButton = nullptr; - QgsMessageBar *mMessageBar = nullptr; //! returns a HTML code with a link to the given file path QString toUrl( const QString &path ) const; @@ -463,6 +341,7 @@ class GUI_EXPORT QgsFileWidget : public QWidget QString relativePath( const QString &filePath, bool removeRelative ) const; friend class TestQgsFileWidget; + friend class TestQgsExternalStorageFileWidget; friend class TestQgsExternalResourceWidgetWrapper; }; diff --git a/tests/src/gui/CMakeLists.txt b/tests/src/gui/CMakeLists.txt index 79b27c51c087..4775bb53d1c3 100644 --- a/tests/src/gui/CMakeLists.txt +++ b/tests/src/gui/CMakeLists.txt @@ -35,6 +35,7 @@ set(TESTS testqgsdockwidget.cpp testqgsfieldexpressionwidget.cpp testqgsfilewidget.cpp + testqgsexternalstoragefilewidget.cpp testqgsfocuswatcher.cpp testqgshtmlwidgetwrapper.cpp testqgsmapcanvas.cpp diff --git a/tests/src/gui/testqgsexternalresourcewidgetwrapper.cpp b/tests/src/gui/testqgsexternalresourcewidgetwrapper.cpp index cfbb74337359..f7fdf61df812 100644 --- a/tests/src/gui/testqgsexternalresourcewidgetwrapper.cpp +++ b/tests/src/gui/testqgsexternalresourcewidgetwrapper.cpp @@ -23,6 +23,7 @@ #include "qgsexternalstorageregistry.h" #include "qgspixmaplabel.h" #include "qgsmessagebar.h" +#include "qgsexternalstoragefilewidget.h" #include #include @@ -628,7 +629,7 @@ void TestQgsExternalResourceWidgetWrapper::testStoreExternalDocument() ww.initWidget( widget ); QVERIFY( ww.mQgsWidget ); - QgsFileWidget *fileWidget = ww.mQgsWidget->fileWidget(); + QgsExternalStorageFileWidget *fileWidget = ww.mQgsWidget->fileWidget(); QVERIFY( fileWidget ); QCOMPARE( fileWidget->storageType(), QStringLiteral( "test" ) ); @@ -721,7 +722,7 @@ void TestQgsExternalResourceWidgetWrapper::testStoreExternalDocumentError() ww.initWidget( widget ); QVERIFY( ww.mQgsWidget ); - QgsFileWidget *fileWidget = ww.mQgsWidget->fileWidget(); + QgsExternalStorageFileWidget *fileWidget = ww.mQgsWidget->fileWidget(); QVERIFY( fileWidget ); QCOMPARE( fileWidget->storageType(), QStringLiteral( "test" ) ); @@ -832,7 +833,7 @@ void TestQgsExternalResourceWidgetWrapper::testStoreExternalDocumentCancel() ww.initWidget( widget ); QVERIFY( ww.mQgsWidget ); - QgsFileWidget *fileWidget = ww.mQgsWidget->fileWidget(); + QgsExternalStorageFileWidget *fileWidget = ww.mQgsWidget->fileWidget(); QVERIFY( fileWidget ); QCOMPARE( fileWidget->storageType(), QStringLiteral( "test" ) ); @@ -943,7 +944,7 @@ void TestQgsExternalResourceWidgetWrapper::testStoreExternalDocumentNoExpression ww.initWidget( widget ); QVERIFY( ww.mQgsWidget ); - QgsFileWidget *fileWidget = ww.mQgsWidget->fileWidget(); + QgsExternalStorageFileWidget *fileWidget = ww.mQgsWidget->fileWidget(); QVERIFY( fileWidget ); QCOMPARE( fileWidget->storageType(), QStringLiteral( "test" ) ); diff --git a/tests/src/gui/testqgsexternalstoragefilewidget.cpp b/tests/src/gui/testqgsexternalstoragefilewidget.cpp new file mode 100644 index 000000000000..4ecd0eb375d7 --- /dev/null +++ b/tests/src/gui/testqgsexternalstoragefilewidget.cpp @@ -0,0 +1,791 @@ +/*************************************************************************** + testqgsexternalstoragefilewidget.cpp + -------------------------------------- + Date : August 2021 + Copyright : (C) 2021 Julien Cabieces + Email : julien dot cabieces at oslandia dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#include "qgstest.h" + +#include "qgsexternalstoragefilewidget.h" +#include "qgsmimedatautils.h" +#include "qgsdataitem.h" +#include "qgsbrowsermodel.h" +#include "qgslayeritem.h" +#include "qgsdirectoryitem.h" +#include "qgsexternalstorage.h" +#include "qgsexternalstorageregistry.h" +#include "qgsmessagebar.h" +#include "qgsexpressioncontextutils.h" +#include + +#include +#include +#include + +class TestQgsExternalStorageFileWidget: public QObject +{ + Q_OBJECT + private slots: + void initTestCase(); // will be called before the first testfunction is executed. + void cleanupTestCase(); // will be called after the last testfunction was executed. + void init(); // will be called before each testfunction is executed. + void cleanup(); // will be called after every testfunction. + void testLayout_data(); + void testLayout(); + void testStoring(); + void testStoring_data(); + void testStoringSeveralFiles_data(); + void testStoringSeveralFiles(); + void testStoringSeveralFilesError_data(); + void testStoringSeveralFilesError(); + void testStoringSeveralFilesCancel_data(); + void testStoringSeveralFilesCancel(); + void testStoringDirectory_data(); + void testStoringDirectory(); + void testStoringChangeFeature(); + void testStoringBadExpression_data(); + void testStoringBadExpression(); +}; + +class QgsTestExternalStorageStoredContent : public QgsExternalStorageStoredContent +{ + Q_OBJECT + + public: + + QgsTestExternalStorageStoredContent( const QString &filePath, const QString &url ) + : QgsExternalStorageStoredContent(), + mUrl( filePath.endsWith( QStringLiteral( "mydir" ) ) ? url + "mydir/" : url ) + {} + + void store() override + { + mStatus = Qgis::ContentStatus::Running; + } + + void cancel() override + { + mStatus = Qgis::ContentStatus::Canceled; + emit canceled(); + }; + + void error() + { + mStatus = Qgis::ContentStatus::Failed; + mErrorString = QStringLiteral( "error" ); + emit errorOccurred( mErrorString ); + } + + void finish() + { + mStatus = Qgis::ContentStatus::Finished; + emit stored(); + } + + QString url() const override + { + return mUrl; + } + + private: + + QString mUrl; + +}; + +class QgsTestExternalStorage : public QgsExternalStorage +{ + public: + + QString type() const override { return QStringLiteral( "test" ); } + + static QPointer sCurrentStoredContent; + + protected: + + QgsExternalStorageStoredContent *doStore( const QString &filePath, const QString &url, const QString &authcfg = QString() ) const override + { + Q_UNUSED( authcfg ); + sCurrentStoredContent = new QgsTestExternalStorageStoredContent( filePath, url ); + return sCurrentStoredContent; + } + + QgsExternalStorageFetchedContent *doFetch( const QString &url, const QString &authcfg = QString() ) const override + { + Q_UNUSED( url ); + Q_UNUSED( authcfg ); + return nullptr; + } +}; + +QPointer QgsTestExternalStorage::sCurrentStoredContent; + +void TestQgsExternalStorageFileWidget::initTestCase() +{ + QgsApplication::externalStorageRegistry()->registerExternalStorage( new QgsTestExternalStorage() ); +} + +void TestQgsExternalStorageFileWidget::cleanupTestCase() +{ +} + +void TestQgsExternalStorageFileWidget::init() +{ +} + +void TestQgsExternalStorageFileWidget::cleanup() +{ +} + +void TestQgsExternalStorageFileWidget::testLayout_data() +{ + QTest::addColumn( "storageType" ); + + QTest::newRow( "without external storage" ) << QString(); + QTest::newRow( "with external storage" ) << QStringLiteral( "test" ); +} + +void TestQgsExternalStorageFileWidget::testLayout() +{ + // test correct buttons are displayed according to different mode and interactions + + QFETCH( QString, storageType ); + + QgsExternalStorageFileWidget w; + w.setStorageType( storageType ); + w.show(); + + QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); + QIcon saveIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveEdits.svg" ) ); + + // with link, read-only + w.setReadOnly( true ); + w.setUseLink( true ); + + QVERIFY( w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + // with link, edit mode + w.setReadOnly( false ); + + QVERIFY( w.mLinkLabel->isVisible() ); + QVERIFY( w.mLinkEditButton->isVisible() ); + QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + // with link, edit mode, we edit the link + w.editLink(); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( w.mLinkEditButton->isVisible() ); + QCOMPARE( w.mLinkEditButton->icon(), saveIcon ); + QVERIFY( w.mLineEdit->isVisible() ); + QVERIFY( w.mLineEdit->isEnabled() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + // with link, edit mode, we finish editing the link + w.editLink(); + + QVERIFY( w.mLinkLabel->isVisible() ); + QVERIFY( w.mLinkEditButton->isVisible() ); + QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + // without link, read-only + w.setUseLink( false ); + w.setReadOnly( true ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( w.mLineEdit->isVisible() ); + QVERIFY( !w.mLineEdit->isEnabled() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + // without link, edit mode + w.setReadOnly( false ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( w.mLineEdit->isVisible() ); + QVERIFY( w.mLineEdit->isEnabled() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); +} + +void TestQgsExternalStorageFileWidget::testStoring_data() +{ + QTest::addColumn( "useLink" ); + + QTest::newRow( "use link" ) << true; + QTest::newRow( "don't use link" ) << false; +} + +void TestQgsExternalStorageFileWidget::testStoring() +{ + // test widget when an external storage is used + + QFETCH( bool, useLink ); + + QgsExternalStorageFileWidget w; + w.show(); + + QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); + + w.setStorageType( "test" ); + w.setStorageUrlExpression( "'http://test.url.com/test/' || file_name(@selected_file_path)" ); + + // start edit mode + w.setUseLink( useLink ); + w.setReadOnly( false ); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + w.setSelectedFileNames( QStringList() << QStringLiteral( "myfile" ) ); + + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QVERIFY( w.mCancelButton->isVisible() ); + + // link is not yet updated + QVERIFY( w.mLinkLabel->text().isEmpty() ); + + QgsTestExternalStorage::sCurrentStoredContent->finish(); + QCoreApplication::processEvents(); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + if ( useLink ) + QCOMPARE( w.mLinkLabel->text(), QStringLiteral( "myfile" ) ); + else + QCOMPARE( w.mLineEdit->text(), QStringLiteral( "http://test.url.com/test/myfile" ) ); +} + + +void TestQgsExternalStorageFileWidget::testStoringSeveralFiles_data() +{ + QTest::addColumn( "useLink" ); + + QTest::newRow( "use link" ) << true; + QTest::newRow( "don't use link" ) << false; +} + +void TestQgsExternalStorageFileWidget::testStoringSeveralFiles() +{ + // test widget when storing several files with an external storage + QEventLoop loop; + QFETCH( bool, useLink ); + + QgsExternalStorageFileWidget w; + w.show(); + + QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); + + w.setStorageType( "test" ); + w.setStorageUrlExpression( "'http://test.url.com/test/' || file_name(@selected_file_path)" ); + w.setStorageMode( QgsFileWidget::GetMultipleFiles ); + + // start edit mode + w.setUseLink( useLink ); + w.setReadOnly( false ); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + w.setSelectedFileNames( QStringList() << QStringLiteral( "myfile1" ) << QStringLiteral( "myfile2" ) ); + + QPointer content1 = QgsTestExternalStorage::sCurrentStoredContent; + QVERIFY( content1 ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QVERIFY( w.mCancelButton->isVisible() ); + QVERIFY( w.mLinkLabel->text().isEmpty() ); + + QgsTestExternalStorage::sCurrentStoredContent->finish(); + QCoreApplication::processEvents(); + + // second file is being stored + QVERIFY( content1 ); + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + QVERIFY( content1 != QgsTestExternalStorage::sCurrentStoredContent ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QVERIFY( w.mCancelButton->isVisible() ); + QVERIFY( w.mLinkLabel->text().isEmpty() ); + + // wait for first file content to be destroyed + connect( content1, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !content1 ); + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + + // end second store + QgsTestExternalStorage::sCurrentStoredContent->finish(); + QCoreApplication::processEvents(); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + if ( useLink ) + QCOMPARE( w.mLinkLabel->text(), QStringLiteral( "\"http://test.url.com/test/myfile1\" \"http://test.url.com/test/myfile2\"" ) ); + else + QCOMPARE( w.mLineEdit->text(), QStringLiteral( "\"http://test.url.com/test/myfile1\" \"http://test.url.com/test/myfile2\"" ) ); + + // wait for second file content to be destroyed + connect( QgsTestExternalStorage::sCurrentStoredContent, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !QgsTestExternalStorage::sCurrentStoredContent ); +} + +void TestQgsExternalStorageFileWidget::testStoringSeveralFilesError_data() +{ + QTest::addColumn( "useLink" ); + + QTest::newRow( "use link" ) << true; + QTest::newRow( "don't use link" ) << false; +} + +void TestQgsExternalStorageFileWidget::testStoringSeveralFilesError() +{ + // test widget when storing several files with an external storage and an error occurred + QEventLoop loop; + QFETCH( bool, useLink ); + + QgsExternalStorageFileWidget w; + QgsMessageBar messageBar; + w.show(); + + QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); + + w.setStorageType( "test" ); + w.setStorageUrlExpression( "'http://test.url.com/test/' || file_name(@selected_file_path)" ); + w.setMessageBar( &messageBar ); + + // start edit mode + w.setUseLink( useLink ); + w.setReadOnly( false ); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + w.setSelectedFileNames( QStringList() << QStringLiteral( "myfile1" ) << QStringLiteral( "error.txt" ) ); + + QPointer content1 = QgsTestExternalStorage::sCurrentStoredContent; + QVERIFY( content1 ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QVERIFY( w.mCancelButton->isVisible() ); + QVERIFY( w.mLinkLabel->text().isEmpty() ); + QVERIFY( !messageBar.currentItem() ); + + QgsTestExternalStorage::sCurrentStoredContent->finish(); + QCoreApplication::processEvents(); + + // second file is being stored + QVERIFY( content1 ); + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + QVERIFY( content1 != QgsTestExternalStorage::sCurrentStoredContent ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QVERIFY( w.mCancelButton->isVisible() ); + QVERIFY( w.mLinkLabel->text().isEmpty() ); + + // wait for first file content to be destroyed + connect( content1, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !content1 ); + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + + // error while storing second file + QgsTestExternalStorage::sCurrentStoredContent->error(); + QCoreApplication::processEvents(); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + if ( useLink ) + QVERIFY( w.mLinkLabel->text().isEmpty() ); + else + QVERIFY( w.mLineEdit->text().isEmpty() ); + QVERIFY( messageBar.currentItem() ); + + // wait for second file content to be destroyed + connect( QgsTestExternalStorage::sCurrentStoredContent, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !QgsTestExternalStorage::sCurrentStoredContent ); +} + + +void TestQgsExternalStorageFileWidget::testStoringSeveralFilesCancel_data() +{ + QTest::addColumn( "useLink" ); + + QTest::newRow( "use link" ) << true; + QTest::newRow( "don't use link" ) << false; +} + +void TestQgsExternalStorageFileWidget::testStoringSeveralFilesCancel() +{ + // test widget when storing several files with an external storage and user cancel operation + QEventLoop loop; + QFETCH( bool, useLink ); + + QgsExternalStorageFileWidget w; + w.show(); + + QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); + + w.setStorageType( "test" ); + w.setStorageUrlExpression( "'http://test.url.com/test/' || file_name(@selected_file_path)" ); + + // start edit mode + w.setUseLink( useLink ); + w.setReadOnly( false ); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + w.setSelectedFileNames( QStringList() << QStringLiteral( "myfile1" ) << QStringLiteral( "error.txt" ) ); + + QPointer content1 = QgsTestExternalStorage::sCurrentStoredContent; + QVERIFY( content1 ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QVERIFY( w.mCancelButton->isVisible() ); + QVERIFY( w.mLinkLabel->text().isEmpty() ); + + QgsTestExternalStorage::sCurrentStoredContent->finish(); + QCoreApplication::processEvents(); + + // second file is being stored + QVERIFY( content1 ); + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + QVERIFY( content1 != QgsTestExternalStorage::sCurrentStoredContent ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QVERIFY( w.mCancelButton->isVisible() ); + QVERIFY( w.mLinkLabel->text().isEmpty() ); + + // wait for first file content to be destroyed + connect( content1, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !content1 ); + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + + // cancel while storing second file + QgsTestExternalStorage::sCurrentStoredContent->cancel(); + QCoreApplication::processEvents(); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + if ( useLink ) + QVERIFY( w.mLinkLabel->text().isEmpty() ); + else + QVERIFY( w.mLineEdit->text().isEmpty() ); + + // wait for second file content to be destroyed + connect( QgsTestExternalStorage::sCurrentStoredContent, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !QgsTestExternalStorage::sCurrentStoredContent ); +} + + +void TestQgsExternalStorageFileWidget::testStoringChangeFeature() +{ + // test widget with external storage to store files with different features + + QgsExternalStorageFileWidget w; + w.show(); + + QgsFields fields; + fields.append( QgsField( QStringLiteral( "myfield" ), QVariant::String ) ); + + QgsFeature f1( fields ); + f1.setAttribute( QStringLiteral( "myfield" ), QStringLiteral( "val1" ) ); + + w.setStorageType( "test" ); + w.setStorageUrlExpression( "'http://test.url.com/' || attribute( @current_feature, 'myfield' )" ); + + QgsExpressionContext expressionContext; + expressionContext.appendScope( QgsExpressionContextUtils::formScope( f1 ) ); + w.setExpressionContext( expressionContext ); + + w.setUseLink( false ); + w.setReadOnly( false ); + + w.setSelectedFileNames( QStringList() << QStringLiteral( "blank" ) ); + + QgsTestExternalStorage::sCurrentStoredContent->finish(); + + QCOMPARE( w.mLineEdit->text(), QStringLiteral( "http://test.url.com/val1" ) ); + + QgsFeature f2( fields ); + f2.setAttribute( QStringLiteral( "myfield" ), QStringLiteral( "val2" ) ); + + QgsExpressionContext expressionContext2; + expressionContext2.appendScope( QgsExpressionContextUtils::formScope( f2 ) ); + w.setExpressionContext( expressionContext2 ); + + w.setSelectedFileNames( QStringList() << QStringLiteral( "blank" ) ); + + QgsTestExternalStorage::sCurrentStoredContent->finish(); + + QCOMPARE( w.mLineEdit->text(), QStringLiteral( "http://test.url.com/val2" ) ); +} + +void TestQgsExternalStorageFileWidget::testStoringBadExpression_data() +{ + QTest::addColumn( "useLink" ); + + QTest::newRow( "use link" ) << true; + QTest::newRow( "don't use link" ) << false; +} + +void TestQgsExternalStorageFileWidget::testStoringBadExpression() +{ + // test widget when an external storage is used and the given expression if incorrect + + QFETCH( bool, useLink ); + + QgsExternalStorageFileWidget w; + w.show(); + + QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); + + w.setStorageType( "test" ); + w.setStorageUrlExpression( "'http://test.url.com/test/' || file_name(@not_existing_variable)" ); + + // start edit mode + w.setUseLink( useLink ); + w.setReadOnly( false ); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + w.setSelectedFileNames( QStringList() << QStringLiteral( "myfile" ) ); + + QVERIFY( !QgsTestExternalStorage::sCurrentStoredContent ); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + // link is not updated + QVERIFY( w.mLinkLabel->text().isEmpty() ); +} + +void TestQgsExternalStorageFileWidget::testStoringDirectory_data() +{ + QTest::addColumn( "useLink" ); + + QTest::newRow( "use link" ) << true; + QTest::newRow( "don't use link" ) << false; +} + +void TestQgsExternalStorageFileWidget::testStoringDirectory() +{ + // test widget when storing a directory with an external storage + QEventLoop loop; + QFETCH( bool, useLink ); + + QgsExternalStorageFileWidget w; + w.show(); + + QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); + + w.setStorageType( "test" ); + w.setStorageUrlExpression( "'http://test.url.com/test/'" ); + w.setStorageMode( QgsFileWidget::GetDirectory ); + + // start edit mode + w.setUseLink( useLink ); + w.setReadOnly( false ); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + w.setSelectedFileNames( QStringList() << "/tmp/mydir" ); + + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QVERIFY( w.mCancelButton->isVisible() ); + QVERIFY( w.mLinkLabel->text().isEmpty() ); + + QgsTestExternalStorage::sCurrentStoredContent->finish(); + QCoreApplication::processEvents(); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + if ( useLink ) + QCOMPARE( w.mLinkLabel->text(), QStringLiteral( "" ) ); + else + QCOMPARE( w.mLineEdit->text(), QStringLiteral( "http://test.url.com/test/mydir/" ) ); + + // wait for file content to be destroyed + connect( QgsTestExternalStorage::sCurrentStoredContent, &QObject::destroyed, &loop, &QEventLoop::quit ); + loop.exec(); + QVERIFY( !QgsTestExternalStorage::sCurrentStoredContent ); +} + +QGSTEST_MAIN( TestQgsExternalStorageFileWidget ) +#include "testqgsexternalstoragefilewidget.moc" diff --git a/tests/src/gui/testqgsfilewidget.cpp b/tests/src/gui/testqgsfilewidget.cpp index e3518552c510..eeb0c51723fb 100644 --- a/tests/src/gui/testqgsfilewidget.cpp +++ b/tests/src/gui/testqgsfilewidget.cpp @@ -22,16 +22,8 @@ #include "qgsbrowsermodel.h" #include "qgslayeritem.h" #include "qgsdirectoryitem.h" -#include "qgsexternalstorage.h" -#include "qgsexternalstorageregistry.h" -#include "qgsmessagebar.h" -#include "qgsexpressioncontextutils.h" #include -#include -#include -#include - class TestQgsFileWidget: public QObject { Q_OBJECT @@ -45,99 +37,10 @@ class TestQgsFileWidget: public QObject void testDroppedFiles(); void testMultipleFiles(); void testSplitFilePaths(); - void testLayout_data(); - void testLayout(); - void testStoring(); - void testStoring_data(); - void testStoringSeveralFiles_data(); - void testStoringSeveralFiles(); - void testStoringSeveralFilesError_data(); - void testStoringSeveralFilesError(); - void testStoringSeveralFilesCancel_data(); - void testStoringSeveralFilesCancel(); - void testStoringDirectory_data(); - void testStoringDirectory(); - void testStoringChangeFeature(); - void testStoringBadExpression_data(); - void testStoringBadExpression(); -}; - -class QgsTestExternalStorageStoredContent : public QgsExternalStorageStoredContent -{ - Q_OBJECT - - public: - - QgsTestExternalStorageStoredContent( const QString &filePath, const QString &url ) - : QgsExternalStorageStoredContent(), - mUrl( filePath.endsWith( QStringLiteral( "mydir" ) ) ? url + "mydir/" : url ) - {} - - void store() override - { - mStatus = Qgis::ContentStatus::Running; - } - - void cancel() override - { - mStatus = Qgis::ContentStatus::Canceled; - emit canceled(); - }; - - void error() - { - mStatus = Qgis::ContentStatus::Failed; - mErrorString = QStringLiteral( "error" ); - emit errorOccurred( mErrorString ); - } - - void finish() - { - mStatus = Qgis::ContentStatus::Finished; - emit stored(); - } - - QString url() const override - { - return mUrl; - } - - private: - - QString mUrl; - -}; - -class QgsTestExternalStorage : public QgsExternalStorage -{ - public: - - QString type() const override { return QStringLiteral( "test" ); } - - static QPointer sCurrentStoredContent; - - protected: - - QgsExternalStorageStoredContent *doStore( const QString &filePath, const QString &url, const QString &authcfg = QString() ) const override - { - Q_UNUSED( authcfg ); - sCurrentStoredContent = new QgsTestExternalStorageStoredContent( filePath, url ); - return sCurrentStoredContent; - } - - QgsExternalStorageFetchedContent *doFetch( const QString &url, const QString &authcfg = QString() ) const override - { - Q_UNUSED( url ); - Q_UNUSED( authcfg ); - return nullptr; - } }; -QPointer QgsTestExternalStorage::sCurrentStoredContent; - void TestQgsFileWidget::initTestCase() { - QgsApplication::externalStorageRegistry()->registerExternalStorage( new QgsTestExternalStorage() ); } void TestQgsFileWidget::cleanupTestCase() @@ -307,645 +210,5 @@ void TestQgsFileWidget::testSplitFilePaths() QCOMPARE( QgsFileWidget::splitFilePaths( path ), QStringList() << path ); } -void TestQgsFileWidget::testLayout_data() -{ - QTest::addColumn( "storageType" ); - - QTest::newRow( "without external storage" ) << QString(); - QTest::newRow( "with external storage" ) << QStringLiteral( "test" ); -} - -void TestQgsFileWidget::testLayout() -{ - // test correct buttons are displayed according to different mode and interactions - - QFETCH( QString, storageType ); - - QgsFileWidget w; - w.setStorageType( storageType ); - w.show(); - - QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); - QIcon saveIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveEdits.svg" ) ); - - // with link, read-only - w.setReadOnly( true ); - w.setUseLink( true ); - - QVERIFY( w.mLinkLabel->isVisible() ); - QVERIFY( !w.mLinkEditButton->isVisible() ); - QVERIFY( !w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( !w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - - // with link, edit mode - w.setReadOnly( false ); - - QVERIFY( w.mLinkLabel->isVisible() ); - QVERIFY( w.mLinkEditButton->isVisible() ); - QCOMPARE( w.mLinkEditButton->icon(), editIcon ); - QVERIFY( !w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - - // with link, edit mode, we edit the link - w.editLink(); - - QVERIFY( !w.mLinkLabel->isVisible() ); - QVERIFY( w.mLinkEditButton->isVisible() ); - QCOMPARE( w.mLinkEditButton->icon(), saveIcon ); - QVERIFY( w.mLineEdit->isVisible() ); - QVERIFY( w.mLineEdit->isEnabled() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - - // with link, edit mode, we finish editing the link - w.editLink(); - - QVERIFY( w.mLinkLabel->isVisible() ); - QVERIFY( w.mLinkEditButton->isVisible() ); - QCOMPARE( w.mLinkEditButton->icon(), editIcon ); - QVERIFY( !w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - - // without link, read-only - w.setUseLink( false ); - w.setReadOnly( true ); - - QVERIFY( !w.mLinkLabel->isVisible() ); - QVERIFY( !w.mLinkEditButton->isVisible() ); - QVERIFY( w.mLineEdit->isVisible() ); - QVERIFY( !w.mLineEdit->isEnabled() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( !w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - - // without link, edit mode - w.setReadOnly( false ); - - QVERIFY( !w.mLinkLabel->isVisible() ); - QVERIFY( !w.mLinkEditButton->isVisible() ); - QVERIFY( w.mLineEdit->isVisible() ); - QVERIFY( w.mLineEdit->isEnabled() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); -} - -void TestQgsFileWidget::testStoring_data() -{ - QTest::addColumn( "useLink" ); - - QTest::newRow( "use link" ) << true; - QTest::newRow( "don't use link" ) << false; -} - -void TestQgsFileWidget::testStoring() -{ - // test widget when an external storage is used - - QFETCH( bool, useLink ); - - QgsFileWidget w; - w.show(); - - QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); - - w.setStorageType( "test" ); - w.setStorageUrlExpression( "'http://test.url.com/test/' || file_name(@selected_file_path)" ); - - // start edit mode - w.setUseLink( useLink ); - w.setReadOnly( false ); - - QVERIFY( useLink == w.mLinkLabel->isVisible() ); - QVERIFY( useLink == w.mLinkEditButton->isVisible() ); - if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); - QVERIFY( useLink != w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - - w.setSelectedFileNames( QStringList() << QStringLiteral( "myfile" ) ); - - QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); - - QVERIFY( !w.mLinkLabel->isVisible() ); - QVERIFY( !w.mLinkEditButton->isVisible() ); - QVERIFY( !w.mLineEdit->isVisible() ); - QVERIFY( !w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mProgressLabel->isVisible() ); - QVERIFY( w.mProgressBar->isVisible() ); - QVERIFY( w.mCancelButton->isVisible() ); - - // link is not yet updated - QVERIFY( w.mLinkLabel->text().isEmpty() ); - - QgsTestExternalStorage::sCurrentStoredContent->finish(); - QCoreApplication::processEvents(); - - QVERIFY( useLink == w.mLinkLabel->isVisible() ); - QVERIFY( useLink == w.mLinkEditButton->isVisible() ); - if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); - QVERIFY( useLink != w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - if ( useLink ) - QCOMPARE( w.mLinkLabel->text(), QStringLiteral( "myfile" ) ); - else - QCOMPARE( w.mLineEdit->text(), QStringLiteral( "http://test.url.com/test/myfile" ) ); -} - - -void TestQgsFileWidget::testStoringSeveralFiles_data() -{ - QTest::addColumn( "useLink" ); - - QTest::newRow( "use link" ) << true; - QTest::newRow( "don't use link" ) << false; -} - -void TestQgsFileWidget::testStoringSeveralFiles() -{ - // test widget when storing several files with an external storage - QEventLoop loop; - QFETCH( bool, useLink ); - - QgsFileWidget w; - w.show(); - - QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); - - w.setStorageType( "test" ); - w.setStorageUrlExpression( "'http://test.url.com/test/' || file_name(@selected_file_path)" ); - w.setStorageMode( QgsFileWidget::GetMultipleFiles ); - - // start edit mode - w.setUseLink( useLink ); - w.setReadOnly( false ); - - QVERIFY( useLink == w.mLinkLabel->isVisible() ); - QVERIFY( useLink == w.mLinkEditButton->isVisible() ); - if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); - QVERIFY( useLink != w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - - w.setSelectedFileNames( QStringList() << QStringLiteral( "myfile1" ) << QStringLiteral( "myfile2" ) ); - - QPointer content1 = QgsTestExternalStorage::sCurrentStoredContent; - QVERIFY( content1 ); - - QVERIFY( !w.mLinkLabel->isVisible() ); - QVERIFY( !w.mLinkEditButton->isVisible() ); - QVERIFY( !w.mLineEdit->isVisible() ); - QVERIFY( !w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mProgressLabel->isVisible() ); - QVERIFY( w.mProgressBar->isVisible() ); - QVERIFY( w.mCancelButton->isVisible() ); - QVERIFY( w.mLinkLabel->text().isEmpty() ); - - QgsTestExternalStorage::sCurrentStoredContent->finish(); - QCoreApplication::processEvents(); - - // second file is being stored - QVERIFY( content1 ); - QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); - QVERIFY( content1 != QgsTestExternalStorage::sCurrentStoredContent ); - - QVERIFY( !w.mLinkLabel->isVisible() ); - QVERIFY( !w.mLinkEditButton->isVisible() ); - QVERIFY( !w.mLineEdit->isVisible() ); - QVERIFY( !w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mProgressLabel->isVisible() ); - QVERIFY( w.mProgressBar->isVisible() ); - QVERIFY( w.mCancelButton->isVisible() ); - QVERIFY( w.mLinkLabel->text().isEmpty() ); - - // wait for first file content to be destroyed - connect( content1, &QObject::destroyed, &loop, &QEventLoop::quit ); - loop.exec(); - QVERIFY( !content1 ); - QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); - - // end second store - QgsTestExternalStorage::sCurrentStoredContent->finish(); - QCoreApplication::processEvents(); - - QVERIFY( useLink == w.mLinkLabel->isVisible() ); - QVERIFY( useLink == w.mLinkEditButton->isVisible() ); - if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); - QVERIFY( useLink != w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - if ( useLink ) - QCOMPARE( w.mLinkLabel->text(), QStringLiteral( "\"http://test.url.com/test/myfile1\" \"http://test.url.com/test/myfile2\"" ) ); - else - QCOMPARE( w.mLineEdit->text(), QStringLiteral( "\"http://test.url.com/test/myfile1\" \"http://test.url.com/test/myfile2\"" ) ); - - // wait for second file content to be destroyed - connect( QgsTestExternalStorage::sCurrentStoredContent, &QObject::destroyed, &loop, &QEventLoop::quit ); - loop.exec(); - QVERIFY( !QgsTestExternalStorage::sCurrentStoredContent ); -} - -void TestQgsFileWidget::testStoringSeveralFilesError_data() -{ - QTest::addColumn( "useLink" ); - - QTest::newRow( "use link" ) << true; - QTest::newRow( "don't use link" ) << false; -} - -void TestQgsFileWidget::testStoringSeveralFilesError() -{ - // test widget when storing several files with an external storage and an error occurred - QEventLoop loop; - QFETCH( bool, useLink ); - - QgsFileWidget w; - QgsMessageBar messageBar; - w.show(); - - QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); - - w.setStorageType( "test" ); - w.setStorageUrlExpression( "'http://test.url.com/test/' || file_name(@selected_file_path)" ); - w.setMessageBar( &messageBar ); - - // start edit mode - w.setUseLink( useLink ); - w.setReadOnly( false ); - - QVERIFY( useLink == w.mLinkLabel->isVisible() ); - QVERIFY( useLink == w.mLinkEditButton->isVisible() ); - if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); - QVERIFY( useLink != w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - - w.setSelectedFileNames( QStringList() << QStringLiteral( "myfile1" ) << QStringLiteral( "error.txt" ) ); - - QPointer content1 = QgsTestExternalStorage::sCurrentStoredContent; - QVERIFY( content1 ); - - QVERIFY( !w.mLinkLabel->isVisible() ); - QVERIFY( !w.mLinkEditButton->isVisible() ); - QVERIFY( !w.mLineEdit->isVisible() ); - QVERIFY( !w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mProgressLabel->isVisible() ); - QVERIFY( w.mProgressBar->isVisible() ); - QVERIFY( w.mCancelButton->isVisible() ); - QVERIFY( w.mLinkLabel->text().isEmpty() ); - QVERIFY( !messageBar.currentItem() ); - - QgsTestExternalStorage::sCurrentStoredContent->finish(); - QCoreApplication::processEvents(); - - // second file is being stored - QVERIFY( content1 ); - QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); - QVERIFY( content1 != QgsTestExternalStorage::sCurrentStoredContent ); - - QVERIFY( !w.mLinkLabel->isVisible() ); - QVERIFY( !w.mLinkEditButton->isVisible() ); - QVERIFY( !w.mLineEdit->isVisible() ); - QVERIFY( !w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mProgressLabel->isVisible() ); - QVERIFY( w.mProgressBar->isVisible() ); - QVERIFY( w.mCancelButton->isVisible() ); - QVERIFY( w.mLinkLabel->text().isEmpty() ); - - // wait for first file content to be destroyed - connect( content1, &QObject::destroyed, &loop, &QEventLoop::quit ); - loop.exec(); - QVERIFY( !content1 ); - QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); - - // error while storing second file - QgsTestExternalStorage::sCurrentStoredContent->error(); - QCoreApplication::processEvents(); - - QVERIFY( useLink == w.mLinkLabel->isVisible() ); - QVERIFY( useLink == w.mLinkEditButton->isVisible() ); - if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); - QVERIFY( useLink != w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - if ( useLink ) - QVERIFY( w.mLinkLabel->text().isEmpty() ); - else - QVERIFY( w.mLineEdit->text().isEmpty() ); - QVERIFY( messageBar.currentItem() ); - - // wait for second file content to be destroyed - connect( QgsTestExternalStorage::sCurrentStoredContent, &QObject::destroyed, &loop, &QEventLoop::quit ); - loop.exec(); - QVERIFY( !QgsTestExternalStorage::sCurrentStoredContent ); -} - - -void TestQgsFileWidget::testStoringSeveralFilesCancel_data() -{ - QTest::addColumn( "useLink" ); - - QTest::newRow( "use link" ) << true; - QTest::newRow( "don't use link" ) << false; -} - -void TestQgsFileWidget::testStoringSeveralFilesCancel() -{ - // test widget when storing several files with an external storage and user cancel operation - QEventLoop loop; - QFETCH( bool, useLink ); - - QgsFileWidget w; - w.show(); - - QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); - - w.setStorageType( "test" ); - w.setStorageUrlExpression( "'http://test.url.com/test/' || file_name(@selected_file_path)" ); - - // start edit mode - w.setUseLink( useLink ); - w.setReadOnly( false ); - - QVERIFY( useLink == w.mLinkLabel->isVisible() ); - QVERIFY( useLink == w.mLinkEditButton->isVisible() ); - if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); - QVERIFY( useLink != w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - - w.setSelectedFileNames( QStringList() << QStringLiteral( "myfile1" ) << QStringLiteral( "error.txt" ) ); - - QPointer content1 = QgsTestExternalStorage::sCurrentStoredContent; - QVERIFY( content1 ); - - QVERIFY( !w.mLinkLabel->isVisible() ); - QVERIFY( !w.mLinkEditButton->isVisible() ); - QVERIFY( !w.mLineEdit->isVisible() ); - QVERIFY( !w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mProgressLabel->isVisible() ); - QVERIFY( w.mProgressBar->isVisible() ); - QVERIFY( w.mCancelButton->isVisible() ); - QVERIFY( w.mLinkLabel->text().isEmpty() ); - - QgsTestExternalStorage::sCurrentStoredContent->finish(); - QCoreApplication::processEvents(); - - // second file is being stored - QVERIFY( content1 ); - QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); - QVERIFY( content1 != QgsTestExternalStorage::sCurrentStoredContent ); - - QVERIFY( !w.mLinkLabel->isVisible() ); - QVERIFY( !w.mLinkEditButton->isVisible() ); - QVERIFY( !w.mLineEdit->isVisible() ); - QVERIFY( !w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mProgressLabel->isVisible() ); - QVERIFY( w.mProgressBar->isVisible() ); - QVERIFY( w.mCancelButton->isVisible() ); - QVERIFY( w.mLinkLabel->text().isEmpty() ); - - // wait for first file content to be destroyed - connect( content1, &QObject::destroyed, &loop, &QEventLoop::quit ); - loop.exec(); - QVERIFY( !content1 ); - QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); - - // cancel while storing second file - QgsTestExternalStorage::sCurrentStoredContent->cancel(); - QCoreApplication::processEvents(); - - QVERIFY( useLink == w.mLinkLabel->isVisible() ); - QVERIFY( useLink == w.mLinkEditButton->isVisible() ); - if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); - QVERIFY( useLink != w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - if ( useLink ) - QVERIFY( w.mLinkLabel->text().isEmpty() ); - else - QVERIFY( w.mLineEdit->text().isEmpty() ); - - // wait for second file content to be destroyed - connect( QgsTestExternalStorage::sCurrentStoredContent, &QObject::destroyed, &loop, &QEventLoop::quit ); - loop.exec(); - QVERIFY( !QgsTestExternalStorage::sCurrentStoredContent ); -} - - -void TestQgsFileWidget::testStoringChangeFeature() -{ - // test widget with external storage to store files with different features - - QgsFileWidget w; - w.show(); - - QgsFields fields; - fields.append( QgsField( QStringLiteral( "myfield" ), QVariant::String ) ); - - QgsFeature f1( fields ); - f1.setAttribute( QStringLiteral( "myfield" ), QStringLiteral( "val1" ) ); - - w.setStorageType( "test" ); - w.setStorageUrlExpression( "'http://test.url.com/' || attribute( @current_feature, 'myfield' )" ); - - QgsExpressionContext expressionContext; - expressionContext.appendScope( QgsExpressionContextUtils::formScope( f1 ) ); - w.setExpressionContext( expressionContext ); - - w.setUseLink( false ); - w.setReadOnly( false ); - - w.setSelectedFileNames( QStringList() << QStringLiteral( "blank" ) ); - - QgsTestExternalStorage::sCurrentStoredContent->finish(); - - QCOMPARE( w.mLineEdit->text(), QStringLiteral( "http://test.url.com/val1" ) ); - - QgsFeature f2( fields ); - f2.setAttribute( QStringLiteral( "myfield" ), QStringLiteral( "val2" ) ); - - QgsExpressionContext expressionContext2; - expressionContext2.appendScope( QgsExpressionContextUtils::formScope( f2 ) ); - w.setExpressionContext( expressionContext2 ); - - w.setSelectedFileNames( QStringList() << QStringLiteral( "blank" ) ); - - QgsTestExternalStorage::sCurrentStoredContent->finish(); - - QCOMPARE( w.mLineEdit->text(), QStringLiteral( "http://test.url.com/val2" ) ); -} - -void TestQgsFileWidget::testStoringBadExpression_data() -{ - QTest::addColumn( "useLink" ); - - QTest::newRow( "use link" ) << true; - QTest::newRow( "don't use link" ) << false; -} - -void TestQgsFileWidget::testStoringBadExpression() -{ - // test widget when an external storage is used and the given expression if incorrect - - QFETCH( bool, useLink ); - - QgsFileWidget w; - w.show(); - - QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); - - w.setStorageType( "test" ); - w.setStorageUrlExpression( "'http://test.url.com/test/' || file_name(@not_existing_variable)" ); - - // start edit mode - w.setUseLink( useLink ); - w.setReadOnly( false ); - - QVERIFY( useLink == w.mLinkLabel->isVisible() ); - QVERIFY( useLink == w.mLinkEditButton->isVisible() ); - if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); - QVERIFY( useLink != w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - - w.setSelectedFileNames( QStringList() << QStringLiteral( "myfile" ) ); - - QVERIFY( !QgsTestExternalStorage::sCurrentStoredContent ); - - QVERIFY( useLink == w.mLinkLabel->isVisible() ); - QVERIFY( useLink == w.mLinkEditButton->isVisible() ); - if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); - QVERIFY( useLink != w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - - // link is not updated - QVERIFY( w.mLinkLabel->text().isEmpty() ); -} - -void TestQgsFileWidget::testStoringDirectory_data() -{ - QTest::addColumn( "useLink" ); - - QTest::newRow( "use link" ) << true; - QTest::newRow( "don't use link" ) << false; -} - -void TestQgsFileWidget::testStoringDirectory() -{ - // test widget when storing a directory with an external storage - QEventLoop loop; - QFETCH( bool, useLink ); - - QgsFileWidget w; - w.show(); - - QIcon editIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ); - - w.setStorageType( "test" ); - w.setStorageUrlExpression( "'http://test.url.com/test/'" ); - w.setStorageMode( QgsFileWidget::GetDirectory ); - - // start edit mode - w.setUseLink( useLink ); - w.setReadOnly( false ); - - QVERIFY( useLink == w.mLinkLabel->isVisible() ); - QVERIFY( useLink == w.mLinkEditButton->isVisible() ); - if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); - QVERIFY( useLink != w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - - w.setSelectedFileNames( QStringList() << "/tmp/mydir" ); - - QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); - - QVERIFY( !w.mLinkLabel->isVisible() ); - QVERIFY( !w.mLinkEditButton->isVisible() ); - QVERIFY( !w.mLineEdit->isVisible() ); - QVERIFY( !w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mProgressLabel->isVisible() ); - QVERIFY( w.mProgressBar->isVisible() ); - QVERIFY( w.mCancelButton->isVisible() ); - QVERIFY( w.mLinkLabel->text().isEmpty() ); - - QgsTestExternalStorage::sCurrentStoredContent->finish(); - QCoreApplication::processEvents(); - - QVERIFY( useLink == w.mLinkLabel->isVisible() ); - QVERIFY( useLink == w.mLinkEditButton->isVisible() ); - if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); - QVERIFY( useLink != w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - if ( useLink ) - QCOMPARE( w.mLinkLabel->text(), QStringLiteral( "" ) ); - else - QCOMPARE( w.mLineEdit->text(), QStringLiteral( "http://test.url.com/test/mydir/" ) ); - - // wait for file content to be destroyed - connect( QgsTestExternalStorage::sCurrentStoredContent, &QObject::destroyed, &loop, &QEventLoop::quit ); - loop.exec(); - QVERIFY( !QgsTestExternalStorage::sCurrentStoredContent ); -} - QGSTEST_MAIN( TestQgsFileWidget ) #include "testqgsfilewidget.moc" From 7a7964c767f7ef4797a9f4350fe0b08e9b8135bf Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Wed, 4 Aug 2021 12:02:54 +0200 Subject: [PATCH 04/11] Fix typo & return string by value not ref --- python/gui/auto_generated/qgsexternalresourcewidget.sip.in | 2 +- src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp | 2 +- src/gui/qgsexternalresourcewidget.cpp | 2 +- src/gui/qgsexternalresourcewidget.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python/gui/auto_generated/qgsexternalresourcewidget.sip.in b/python/gui/auto_generated/qgsexternalresourcewidget.sip.in index 54c8b949f31e..43cb1124ba7c 100644 --- a/python/gui/auto_generated/qgsexternalresourcewidget.sip.in +++ b/python/gui/auto_generated/qgsexternalresourcewidget.sip.in @@ -163,7 +163,7 @@ defined) .. versionadded:: 3.22 %End - const QString &storageAuthConfigId() const; + QString storageAuthConfigId() const; %Docstring Returns the authentication configuration ID used for the current external storage (if defined) diff --git a/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp b/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp index c87db74bcb57..2563a29f1ed3 100644 --- a/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp +++ b/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp @@ -37,7 +37,7 @@ QgsExternalResourceConfigDlg::QgsExternalResourceConfigDlg( QgsVectorLayer *vl, { setupUi( this ); - mStorageType->addItem( tr( "Select existing file" ), QString() ); + mStorageType->addItem( tr( "Select Existing file" ), QString() ); for ( QgsExternalStorage *storage : QgsApplication::externalStorageRegistry()->externalStorages() ) { mStorageType->addItem( tr( "Store with %1" ).arg( storage->type() ), storage->type() ); diff --git a/src/gui/qgsexternalresourcewidget.cpp b/src/gui/qgsexternalresourcewidget.cpp index 8f2999028ef7..919172cbc190 100644 --- a/src/gui/qgsexternalresourcewidget.cpp +++ b/src/gui/qgsexternalresourcewidget.cpp @@ -249,7 +249,7 @@ void QgsExternalResourceWidget::setStorageAuthConfigId( const QString &authCfg ) mFileWidget->setStorageAuthConfigId( authCfg ); } -const QString &QgsExternalResourceWidget::storageAuthConfigId() const +QString QgsExternalResourceWidget::storageAuthConfigId() const { return mFileWidget->storageAuthConfigId(); } diff --git a/src/gui/qgsexternalresourcewidget.h b/src/gui/qgsexternalresourcewidget.h index a7bf5febe614..3b6dac7ba094 100644 --- a/src/gui/qgsexternalresourcewidget.h +++ b/src/gui/qgsexternalresourcewidget.h @@ -174,7 +174,7 @@ class GUI_EXPORT QgsExternalResourceWidget : public QWidget * Returns the authentication configuration ID used for the current external storage (if defined) * \since QGIS 3.22 */ - const QString &storageAuthConfigId() const; + QString storageAuthConfigId() const; /** * Set \a messageBar to report messages From 36813e737d73d129ed2b2a2f29a29b80bbcc83b6 Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Wed, 4 Aug 2021 12:52:17 +0200 Subject: [PATCH 05/11] Fix doc --- .../qgsexternalstoragefilewidget.sip.in | 8 +++----- python/gui/auto_generated/qgsfilewidget.sip.in | 12 ++++++++++++ src/gui/qgsexternalstoragefilewidget.h | 8 ++++++-- src/gui/qgsfilewidget.h | 16 ++++++++++++---- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/python/gui/auto_generated/qgsexternalstoragefilewidget.sip.in b/python/gui/auto_generated/qgsexternalstoragefilewidget.sip.in index ccf710bcb0fb..3e8f91bde21f 100644 --- a/python/gui/auto_generated/qgsexternalstoragefilewidget.sip.in +++ b/python/gui/auto_generated/qgsexternalstoragefilewidget.sip.in @@ -164,11 +164,9 @@ It defines the variable containing the user selected file name void addFileWidgetScope(); - - void storeExternalFiles( QStringList fileNames, QStringList storedUrls = QStringList() ); - - - +%Docstring +Add file widget specific scope to expression context +%End }; diff --git a/python/gui/auto_generated/qgsfilewidget.sip.in b/python/gui/auto_generated/qgsfilewidget.sip.in index 9e57b25a9a06..a29b4a387cc0 100644 --- a/python/gui/auto_generated/qgsfilewidget.sip.in +++ b/python/gui/auto_generated/qgsfilewidget.sip.in @@ -264,12 +264,24 @@ Emitted whenever the current file or directory ``path`` is changed. protected: virtual void updateLayout(); +%Docstring +Update buttons visibility +%End virtual void setSelectedFileNames( QStringList fileNames ); +%Docstring +Called whenever user select ``fileNames`` from dialog +%End static bool isMultiFiles( const QString &path ); +%Docstring +Returns true if ``path`` is a multifiles +%End void setFilePaths( const QStringList &filePaths ); +%Docstring +Update filePath according to ``filePaths`` list +%End diff --git a/src/gui/qgsexternalstoragefilewidget.h b/src/gui/qgsexternalstoragefilewidget.h index 1f0713d26dcc..ae734d906041 100644 --- a/src/gui/qgsexternalstoragefilewidget.h +++ b/src/gui/qgsexternalstoragefilewidget.h @@ -34,7 +34,7 @@ class QgsMessageBar; * \ingroup gui * \brief The QgsExternalStorageFileWidget class creates a widget for selecting a file or a folder * and stores it to a given external storage backend if defined - * \since 3.22 + * \since QGIS 3.22 */ class GUI_EXPORT QgsExternalStorageFileWidget : public QgsFileWidget { @@ -165,9 +165,13 @@ class GUI_EXPORT QgsExternalStorageFileWidget : public QgsFileWidget void setSelectedFileNames( QStringList fileNames ) override; - // add file widget specific scope to expression context + /** + * Add file widget specific scope to expression context + */ void addFileWidgetScope(); + private: + // stores \a fileNames files using current external storage. // This is a recursive method, \a storedUrls contains urls for previously stored // fileNames. When all files have been successfully stored, current mFilePath diff --git a/src/gui/qgsfilewidget.h b/src/gui/qgsfilewidget.h index 50d6f0da6596..99d573bb7ed6 100644 --- a/src/gui/qgsfilewidget.h +++ b/src/gui/qgsfilewidget.h @@ -301,16 +301,24 @@ class GUI_EXPORT QgsFileWidget : public QWidget protected: - // update buttons visibility + /** + * Update buttons visibility + */ virtual void updateLayout(); - // called whenever user select file names from dialog + /** + * Called whenever user select \a fileNames from dialog + */ virtual void setSelectedFileNames( QStringList fileNames ); - // Returns true if \a path is a multifiles + /** + * Returns true if \a path is a multifiles + */ static bool isMultiFiles( const QString &path ); - // Update filePath according to a file path list + /** + * Update filePath according to \a filePaths list + */ void setFilePaths( const QStringList &filePaths ); QString mFilePath; From bbf2429d6b70fefe3a30ce8e58a4e5789238ffea Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Wed, 4 Aug 2021 16:46:23 +0200 Subject: [PATCH 06/11] Fix sip incomplete type QgsExternalStorageFileWidget --- python/gui/auto_generated/qgsexternalresourcewidget.sip.in | 2 ++ src/gui/qgsexternalresourcewidget.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/python/gui/auto_generated/qgsexternalresourcewidget.sip.in b/python/gui/auto_generated/qgsexternalresourcewidget.sip.in index 43cb1124ba7c..7cc4cd72b665 100644 --- a/python/gui/auto_generated/qgsexternalresourcewidget.sip.in +++ b/python/gui/auto_generated/qgsexternalresourcewidget.sip.in @@ -16,6 +16,8 @@ // doesn't add this include to the file where the code from // ConvertToSubClassCode goes. #include + +#include %End diff --git a/src/gui/qgsexternalresourcewidget.h b/src/gui/qgsexternalresourcewidget.h index 3b6dac7ba094..e2002c126c4a 100644 --- a/src/gui/qgsexternalresourcewidget.h +++ b/src/gui/qgsexternalresourcewidget.h @@ -36,6 +36,8 @@ class QgsExternalStorageFileWidget; // doesn't add this include to the file where the code from // ConvertToSubClassCode goes. #include + +#include % End #endif From b1fa33386f23a40c1863369a836db03510295b2a Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Thu, 5 Aug 2021 09:35:04 +0200 Subject: [PATCH 07/11] Disable relative path and directory storing when there is an external storage --- .../editorwidgets/qgsexternalresourceconfigdlg.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp b/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp index 2563a29f1ed3..5bc12711eabf 100644 --- a/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp +++ b/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp @@ -178,10 +178,11 @@ QVariantMap QgsExternalResourceConfigDlg::config() cfg.insert( QStringLiteral( "DefaultRoot" ), mRootPath->text() ); // Save Storage Mode - cfg.insert( QStringLiteral( "StorageMode" ), mStorageButtonGroup->checkedId() ); + cfg.insert( QStringLiteral( "StorageMode" ), mStorageModeGroupBox->isVisible() ? + mStorageButtonGroup->checkedId() : QgsFileWidget::GetFile ); // Save Relative Paths option - if ( mRelativeGroupBox->isChecked() ) + if ( mRelativeGroupBox->isVisible() && mRelativeGroupBox->isChecked() ) { cfg.insert( QStringLiteral( "RelativeStorage" ), mRelativeButtonGroup->checkedId() ); } @@ -294,5 +295,12 @@ void QgsExternalResourceConfigDlg::changeStorageType( int storageType ) { // first one in combo box is not an external storage mExternalStorageGroupBox->setVisible( storageType ); + + // for now, we store only files in external storage + mStorageModeGroupBox->setVisible( !storageType ); + + // Absolute path are mandatory when using external storage + mRelativeGroupBox->setVisible( !storageType ); + emit changed(); } From d5d90b753bb6a7fde66e21bc0d365482d58a9fa2 Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Thu, 5 Aug 2021 09:54:37 +0200 Subject: [PATCH 08/11] Update tooltips --- .../qgsexternalresourceconfigdlg.ui | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/ui/editorwidgets/qgsexternalresourceconfigdlg.ui b/src/ui/editorwidgets/qgsexternalresourceconfigdlg.ui index 43363606aeda..ee30fe571963 100644 --- a/src/ui/editorwidgets/qgsexternalresourceconfigdlg.ui +++ b/src/ui/editorwidgets/qgsexternalresourceconfigdlg.ui @@ -58,7 +58,11 @@ - + + + <html><head/><body>Way of dealing with attachment file<p>"Select existing file" allows you to pick an existing file from the file system or set an existing URL external resource.</p><p>Other items allows you to pick a local resource and store it on an external storage system. You cannot use relative path in this mode and you can only pick file and not directory.</p></p></body></html> + + @@ -79,10 +83,18 @@ - + + + Url used to store file selected from the attachment widget. + + - + + + Url used to store file selected from the attachment widget. + + @@ -128,7 +140,7 @@ - + Path @@ -602,7 +614,7 @@ - + From 902907e18aaaea57a8b6a61d5f7a814b989c49ed Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Mon, 9 Aug 2021 17:17:33 +0200 Subject: [PATCH 09/11] Fix bad progress bar behavior --- src/gui/qgsexternalstoragefilewidget.cpp | 3 +- .../gui/testqgsexternalstoragefilewidget.cpp | 107 ++++++++++-------- 2 files changed, 61 insertions(+), 49 deletions(-) diff --git a/src/gui/qgsexternalstoragefilewidget.cpp b/src/gui/qgsexternalstoragefilewidget.cpp index d1230aaa17a9..80903b2b82e1 100644 --- a/src/gui/qgsexternalstoragefilewidget.cpp +++ b/src/gui/qgsexternalstoragefilewidget.cpp @@ -203,8 +203,9 @@ void QgsExternalStorageFileWidget::storeExternalFiles( QStringList fileNames, QS { const QString filePath = fileNames.takeFirst(); - mProgressLabel->setText( tr( "Storing file %1 ..." ).arg( QFileInfo( filePath ).baseName() ) ); + mProgressLabel->setText( tr( "Storing file %1 ..." ).arg( QFileInfo( filePath ).fileName() ) ); mStoreInProgress = true; + mProgressBar->setValue( 0 ); updateLayout(); Q_ASSERT( mScope ); diff --git a/tests/src/gui/testqgsexternalstoragefilewidget.cpp b/tests/src/gui/testqgsexternalstoragefilewidget.cpp index 4ecd0eb375d7..327fd668298e 100644 --- a/tests/src/gui/testqgsexternalstoragefilewidget.cpp +++ b/tests/src/gui/testqgsexternalstoragefilewidget.cpp @@ -86,6 +86,11 @@ class QgsTestExternalStorageStoredContent : public QgsExternalStorageStoredConte emit errorOccurred( mErrorString ); } + void setProgress( double progress ) + { + emit progressChanged( progress ); + } + void finish() { mStatus = Qgis::ContentStatus::Finished; @@ -275,47 +280,60 @@ void TestQgsExternalStorageFileWidget::testStoring() w.setUseLink( useLink ); w.setReadOnly( false ); - QVERIFY( useLink == w.mLinkLabel->isVisible() ); - QVERIFY( useLink == w.mLinkEditButton->isVisible() ); - if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); - QVERIFY( useLink != w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - - w.setSelectedFileNames( QStringList() << QStringLiteral( "myfile" ) ); - - QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); - - QVERIFY( !w.mLinkLabel->isVisible() ); - QVERIFY( !w.mLinkEditButton->isVisible() ); - QVERIFY( !w.mLineEdit->isVisible() ); - QVERIFY( !w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mProgressLabel->isVisible() ); - QVERIFY( w.mProgressBar->isVisible() ); - QVERIFY( w.mCancelButton->isVisible() ); - - // link is not yet updated - QVERIFY( w.mLinkLabel->text().isEmpty() ); - - QgsTestExternalStorage::sCurrentStoredContent->finish(); - QCoreApplication::processEvents(); - - QVERIFY( useLink == w.mLinkLabel->isVisible() ); - QVERIFY( useLink == w.mLinkEditButton->isVisible() ); - if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); - QVERIFY( useLink != w.mLineEdit->isVisible() ); - QVERIFY( w.mFileWidgetButton->isVisible() ); - QVERIFY( w.mFileWidgetButton->isEnabled() ); - QVERIFY( !w.mProgressLabel->isVisible() ); - QVERIFY( !w.mProgressBar->isVisible() ); - QVERIFY( !w.mCancelButton->isVisible() ); - if ( useLink ) - QCOMPARE( w.mLinkLabel->text(), QStringLiteral( "myfile" ) ); - else - QCOMPARE( w.mLineEdit->text(), QStringLiteral( "http://test.url.com/test/myfile" ) ); + const QStringList fileNames = QStringList() << "myfile1.txt" << "myfile2.txt" ; + for ( QString fileName : fileNames ) + { + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + + QString currentLabel = useLink ? w.mLinkLabel->text() : w.mLineEdit->text(); + + w.setSelectedFileNames( QStringList() << fileName ); + + QVERIFY( QgsTestExternalStorage::sCurrentStoredContent ); + + QVERIFY( !w.mLinkLabel->isVisible() ); + QVERIFY( !w.mLinkEditButton->isVisible() ); + QVERIFY( !w.mLineEdit->isVisible() ); + QVERIFY( !w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mProgressLabel->isVisible() ); + QVERIFY( w.mProgressBar->isVisible() ); + QCOMPARE( w.mProgressBar->value(), 0 ); + QVERIFY( w.mCancelButton->isVisible() ); + + // link is not yet updated + if ( useLink ) + QCOMPARE( currentLabel, w.mLinkLabel->text() ); + else + QCOMPARE( currentLabel, w.mLineEdit->text() ); + + QgsTestExternalStorage::sCurrentStoredContent->setProgress( 50 ); + QVERIFY( w.mProgressBar->isVisible() ); + QCOMPARE( w.mProgressBar->value(), 50 ); + + QgsTestExternalStorage::sCurrentStoredContent->finish(); + + QVERIFY( useLink == w.mLinkLabel->isVisible() ); + QVERIFY( useLink == w.mLinkEditButton->isVisible() ); + if ( useLink ) QCOMPARE( w.mLinkEditButton->icon(), editIcon ); + QVERIFY( useLink != w.mLineEdit->isVisible() ); + QVERIFY( w.mFileWidgetButton->isVisible() ); + QVERIFY( w.mFileWidgetButton->isEnabled() ); + QVERIFY( !w.mProgressLabel->isVisible() ); + QVERIFY( !w.mProgressBar->isVisible() ); + QVERIFY( !w.mCancelButton->isVisible() ); + if ( useLink ) + QCOMPARE( w.mLinkLabel->text(), QStringLiteral( "%1" ).arg( fileName ) ); + else + QCOMPARE( w.mLineEdit->text(), QStringLiteral( "http://test.url.com/test/%1" ).arg( fileName ) ); + } } @@ -371,7 +389,6 @@ void TestQgsExternalStorageFileWidget::testStoringSeveralFiles() QVERIFY( w.mLinkLabel->text().isEmpty() ); QgsTestExternalStorage::sCurrentStoredContent->finish(); - QCoreApplication::processEvents(); // second file is being stored QVERIFY( content1 ); @@ -395,7 +412,6 @@ void TestQgsExternalStorageFileWidget::testStoringSeveralFiles() // end second store QgsTestExternalStorage::sCurrentStoredContent->finish(); - QCoreApplication::processEvents(); QVERIFY( useLink == w.mLinkLabel->isVisible() ); QVERIFY( useLink == w.mLinkEditButton->isVisible() ); @@ -471,7 +487,6 @@ void TestQgsExternalStorageFileWidget::testStoringSeveralFilesError() QVERIFY( !messageBar.currentItem() ); QgsTestExternalStorage::sCurrentStoredContent->finish(); - QCoreApplication::processEvents(); // second file is being stored QVERIFY( content1 ); @@ -495,7 +510,6 @@ void TestQgsExternalStorageFileWidget::testStoringSeveralFilesError() // error while storing second file QgsTestExternalStorage::sCurrentStoredContent->error(); - QCoreApplication::processEvents(); QVERIFY( useLink == w.mLinkLabel->isVisible() ); QVERIFY( useLink == w.mLinkEditButton->isVisible() ); @@ -570,7 +584,6 @@ void TestQgsExternalStorageFileWidget::testStoringSeveralFilesCancel() QVERIFY( w.mLinkLabel->text().isEmpty() ); QgsTestExternalStorage::sCurrentStoredContent->finish(); - QCoreApplication::processEvents(); // second file is being stored QVERIFY( content1 ); @@ -594,7 +607,6 @@ void TestQgsExternalStorageFileWidget::testStoringSeveralFilesCancel() // cancel while storing second file QgsTestExternalStorage::sCurrentStoredContent->cancel(); - QCoreApplication::processEvents(); QVERIFY( useLink == w.mLinkLabel->isVisible() ); QVERIFY( useLink == w.mLinkEditButton->isVisible() ); @@ -765,7 +777,6 @@ void TestQgsExternalStorageFileWidget::testStoringDirectory() QVERIFY( w.mLinkLabel->text().isEmpty() ); QgsTestExternalStorage::sCurrentStoredContent->finish(); - QCoreApplication::processEvents(); QVERIFY( useLink == w.mLinkLabel->isVisible() ); QVERIFY( useLink == w.mLinkEditButton->isVisible() ); From 53e16e85c061d5a69c8ab21dbdd6efa5ed72b666 Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Wed, 11 Aug 2021 16:17:55 +0200 Subject: [PATCH 10/11] add external storage display name --- .../externalstorage/qgsexternalstorage.sip.in | 6 ++++++ src/core/externalstorage/qgsexternalstorage.h | 6 ++++++ src/core/externalstorage/qgssimplecopyexternalstorage.cpp | 5 +++++ src/core/externalstorage/qgssimplecopyexternalstorage_p.h | 2 ++ src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp | 2 +- tests/src/gui/testqgsexternalresourcewidgetwrapper.cpp | 2 ++ tests/src/gui/testqgsexternalstoragefilewidget.cpp | 2 ++ 7 files changed, 24 insertions(+), 1 deletion(-) diff --git a/python/core/auto_generated/externalstorage/qgsexternalstorage.sip.in b/python/core/auto_generated/externalstorage/qgsexternalstorage.sip.in index 716139e9ee55..01fae80f9e1d 100644 --- a/python/core/auto_generated/externalstorage/qgsexternalstorage.sip.in +++ b/python/core/auto_generated/externalstorage/qgsexternalstorage.sip.in @@ -29,6 +29,12 @@ and registered in :py:class:`QgsExternalStorageRegistry`. virtual QString type() const = 0; %Docstring Unique identifier of the external storage type. +%End + + virtual QString displayName() const = 0; +%Docstring +Returns the translated external storage name, which should be used for any +user-visible display of the external storage name. %End QgsExternalStorageStoredContent *store( const QString &filePath, const QString &url, const QString &authCfg = QString(), Qgis::ActionStart storingMode = Qgis::ActionStart::Deferred ) const /Factory/; diff --git a/src/core/externalstorage/qgsexternalstorage.h b/src/core/externalstorage/qgsexternalstorage.h index d4cb43cea908..821f090bf0f1 100644 --- a/src/core/externalstorage/qgsexternalstorage.h +++ b/src/core/externalstorage/qgsexternalstorage.h @@ -47,6 +47,12 @@ class CORE_EXPORT QgsExternalStorage */ virtual QString type() const = 0; + /** + * Returns the translated external storage name, which should be used for any + * user-visible display of the external storage name. + */ + virtual QString displayName() const = 0; + /** * Stores file \a filePath to the \a url for this project external storage. * Storing process is run in background. diff --git a/src/core/externalstorage/qgssimplecopyexternalstorage.cpp b/src/core/externalstorage/qgssimplecopyexternalstorage.cpp index 110621034dbf..3e15ba9466af 100644 --- a/src/core/externalstorage/qgssimplecopyexternalstorage.cpp +++ b/src/core/externalstorage/qgssimplecopyexternalstorage.cpp @@ -100,6 +100,11 @@ QString QgsSimpleCopyExternalStorage::type() const return QStringLiteral( "SimpleCopy" ); }; +QString QgsSimpleCopyExternalStorage::displayName() const +{ + return QObject::tr( "Simple copy" ); +}; + QgsExternalStorageStoredContent *QgsSimpleCopyExternalStorage::doStore( const QString &filePath, const QString &url, const QString &authcfg ) const { return new QgsSimpleCopyExternalStorageStoredContent( filePath, url, authcfg ); diff --git a/src/core/externalstorage/qgssimplecopyexternalstorage_p.h b/src/core/externalstorage/qgssimplecopyexternalstorage_p.h index c336f76b60ef..da90fc6bd055 100644 --- a/src/core/externalstorage/qgssimplecopyexternalstorage_p.h +++ b/src/core/externalstorage/qgssimplecopyexternalstorage_p.h @@ -40,6 +40,8 @@ class CORE_EXPORT QgsSimpleCopyExternalStorage : public QgsExternalStorage QString type() const override; + QString displayName() const override; + QgsExternalStorageStoredContent *doStore( const QString &filePath, const QString &url, const QString &authcfg = QString() ) const override; QgsExternalStorageFetchedContent *doFetch( const QString &url, const QString &authConfig = QString() ) const override; diff --git a/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp b/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp index 5bc12711eabf..0c6d56cfc20c 100644 --- a/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp +++ b/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp @@ -40,7 +40,7 @@ QgsExternalResourceConfigDlg::QgsExternalResourceConfigDlg( QgsVectorLayer *vl, mStorageType->addItem( tr( "Select Existing file" ), QString() ); for ( QgsExternalStorage *storage : QgsApplication::externalStorageRegistry()->externalStorages() ) { - mStorageType->addItem( tr( "Store with %1" ).arg( storage->type() ), storage->type() ); + mStorageType->addItem( storage->displayName(), storage->type() ); } mExternalStorageGroupBox->setVisible( false ); diff --git a/tests/src/gui/testqgsexternalresourcewidgetwrapper.cpp b/tests/src/gui/testqgsexternalresourcewidgetwrapper.cpp index f7fdf61df812..e5472f095779 100644 --- a/tests/src/gui/testqgsexternalresourcewidgetwrapper.cpp +++ b/tests/src/gui/testqgsexternalresourcewidgetwrapper.cpp @@ -173,6 +173,8 @@ class QgsTestExternalStorage : public QgsExternalStorage QString type() const override { return QStringLiteral( "test" ); } + QString displayName() const override { return QStringLiteral( "Test" ); } + QgsExternalStorageStoredContent *doStore( const QString &filePath, const QString &url, const QString &authcfg = QString() ) const override { Q_UNUSED( authcfg ); diff --git a/tests/src/gui/testqgsexternalstoragefilewidget.cpp b/tests/src/gui/testqgsexternalstoragefilewidget.cpp index 327fd668298e..6cbff5b4e009 100644 --- a/tests/src/gui/testqgsexternalstoragefilewidget.cpp +++ b/tests/src/gui/testqgsexternalstoragefilewidget.cpp @@ -114,6 +114,8 @@ class QgsTestExternalStorage : public QgsExternalStorage QString type() const override { return QStringLiteral( "test" ); } + QString displayName() const override { return QStringLiteral( "Test" ); } + static QPointer sCurrentStoredContent; protected: From 7d89628f382d80c1a0612552979479bc4b26470f Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Wed, 11 Aug 2021 16:20:40 +0200 Subject: [PATCH 11/11] s/storageType/storageTypeIndex --- src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp | 8 ++++---- src/gui/editorwidgets/qgsexternalresourceconfigdlg.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp b/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp index 0c6d56cfc20c..2ef35cdfc443 100644 --- a/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp +++ b/src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp @@ -291,16 +291,16 @@ QgsExpressionContext QgsExternalResourceConfigDlg::createExpressionContext() con return context; } -void QgsExternalResourceConfigDlg::changeStorageType( int storageType ) +void QgsExternalResourceConfigDlg::changeStorageType( int storageTypeIndex ) { // first one in combo box is not an external storage - mExternalStorageGroupBox->setVisible( storageType ); + mExternalStorageGroupBox->setVisible( storageTypeIndex > 0 ); // for now, we store only files in external storage - mStorageModeGroupBox->setVisible( !storageType ); + mStorageModeGroupBox->setVisible( !storageTypeIndex ); // Absolute path are mandatory when using external storage - mRelativeGroupBox->setVisible( !storageType ); + mRelativeGroupBox->setVisible( !storageTypeIndex ); emit changed(); } diff --git a/src/gui/editorwidgets/qgsexternalresourceconfigdlg.h b/src/gui/editorwidgets/qgsexternalresourceconfigdlg.h index 411dcfbdc8b3..3b5ee40e0faf 100644 --- a/src/gui/editorwidgets/qgsexternalresourceconfigdlg.h +++ b/src/gui/editorwidgets/qgsexternalresourceconfigdlg.h @@ -53,7 +53,7 @@ class GUI_EXPORT QgsExternalResourceConfigDlg : public QgsEditorConfigWidget, pr void enableRelativeDefault(); //! change storage type according to index from storage type combo box - void changeStorageType( int index ); + void changeStorageType( int storageTypeIndex ); };