Skip to content

Commit a0507ee

Browse files
authoredAug 16, 2021
Merge pull request #44533 from troopa81/feat_dms_connexion_widget
[ExternalStorage] Add widget to configure/edit and visualize external storage
2 parents 7b7323c + 7d89628 commit a0507ee

30 files changed

+2978
-131
lines changed
 

‎python/core/auto_generated/externalstorage/qgsexternalstorage.sip.in

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ and registered in :py:class:`QgsExternalStorageRegistry`.
2929
virtual QString type() const = 0;
3030
%Docstring
3131
Unique identifier of the external storage type.
32+
%End
33+
34+
virtual QString displayName() const = 0;
35+
%Docstring
36+
Returns the translated external storage name, which should be used for any
37+
user-visible display of the external storage name.
3238
%End
3339

3440
QgsExternalStorageStoredContent *store( const QString &filePath, const QString &url, const QString &authCfg = QString(), Qgis::ActionStart storingMode = Qgis::ActionStart::Deferred ) const /Factory/;

‎python/gui/auto_generated/editorwidgets/core/qgswidgetwrapper.sip.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ changed status of the widget will be saved.
5151
enum Property
5252
{
5353
RootPath,
54-
DocumentViewerContent
54+
DocumentViewerContent,
55+
StorageUrl
5556
};
5657

5758
static const QgsPropertiesDefinition &propertyDefinitions();

‎python/gui/auto_generated/qgsexternalresourcewidget.sip.in

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
// doesn't add this include to the file where the code from
1717
// ConvertToSubClassCode goes.
1818
#include <qgsexternalresourcewidget.h>
19+
20+
#include <qgsexternalstoragefilewidget.h>
1921
%End
2022

2123

@@ -59,9 +61,9 @@ documentPath returns the path of the current document in the widget
5961
%End
6062
void setDocumentPath( const QVariant &documentPath );
6163

62-
QgsFileWidget *fileWidget();
64+
QgsExternalStorageFileWidget *fileWidget();
6365
%Docstring
64-
access the file widget to allow its configuration
66+
Returns file widget to allow its configuration
6567
%End
6668

6769
bool fileWidgetVisible() const;
@@ -133,6 +135,55 @@ is set to :py:class:`QgsFileWidget`.RelativeDefaultPath.
133135
%Docstring
134136
Configures the base path which should be used if the relativeStorage property
135137
is set to :py:class:`QgsFileWidget`.RelativeDefaultPath.
138+
%End
139+
140+
void setStorageType( const QString &storageType );
141+
%Docstring
142+
Set ``storageType`` storage type unique identifier as defined in :py:class:`QgsExternalStorageRegistry` or
143+
null QString if there is no storage defined, only file selection.
144+
145+
.. seealso:: :py:func:`storageType`
146+
147+
.. versionadded:: 3.22
148+
%End
149+
150+
QString storageType() const;
151+
%Docstring
152+
Returns storage type unique identifier as defined in :py:class:`QgsExternalStorageRegistry`.
153+
Returns null QString if there is no storage defined, only file selection.
154+
155+
.. seealso:: :py:func:`setStorageType`
156+
157+
.. versionadded:: 3.22
158+
%End
159+
160+
void setStorageAuthConfigId( const QString &authCfg );
161+
%Docstring
162+
Sets the authentication configuration ID to be used for the current external storage (if
163+
defined)
164+
165+
.. versionadded:: 3.22
166+
%End
167+
168+
QString storageAuthConfigId() const;
169+
%Docstring
170+
Returns the authentication configuration ID used for the current external storage (if defined)
171+
172+
.. versionadded:: 3.22
173+
%End
174+
175+
void setMessageBar( QgsMessageBar *messageBar );
176+
%Docstring
177+
Set ``messageBar`` to report messages
178+
179+
.. versionadded:: 3.22
180+
%End
181+
182+
QgsMessageBar *messageBar() const;
183+
%Docstring
184+
Returns message bar used to report messages
185+
186+
.. versionadded:: 3.22
136187
%End
137188

138189
signals:
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/gui/qgsexternalstoragefilewidget.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
11+
12+
13+
class QgsExternalStorageFileWidget : QgsFileWidget
14+
{
15+
%Docstring(signature="appended")
16+
The :py:class:`QgsExternalStorageFileWidget` class creates a widget for selecting a file or a folder
17+
and stores it to a given external storage backend if defined
18+
19+
.. versionadded:: 3.22
20+
%End
21+
22+
%TypeHeaderCode
23+
#include "qgsexternalstoragefilewidget.h"
24+
%End
25+
%ConvertToSubClassCode
26+
if ( qobject_cast<QgsExternalStorageFileWidget *>( sipCpp ) )
27+
sipType = sipType_QgsExternalStorageFileWidget;
28+
else
29+
sipType = NULL;
30+
%End
31+
public:
32+
33+
explicit QgsExternalStorageFileWidget( QWidget *parent /TransferThis/ = 0 );
34+
%Docstring
35+
QgsExternalStorageFileWidget creates a widget for selecting a file or a folder.
36+
%End
37+
38+
void setStorageType( const QString &storageType );
39+
%Docstring
40+
Set ``storageType`` storage type unique identifier as defined in :py:class:`QgsExternalStorageRegistry` or
41+
null QString if there is no storage defined.
42+
If no external storage has been defined, QgsExternalStorageFileWidget will only update file path according to
43+
selected files.
44+
45+
.. seealso:: :py:func:`storageType`
46+
47+
.. versionadded:: 3.22
48+
%End
49+
50+
QString storageType() const;
51+
%Docstring
52+
Returns storage type unique identifier as defined in :py:class:`QgsExternalStorageRegistry`.
53+
Returns null QString if there is no storage defined, only file selection.
54+
55+
.. seealso:: :py:func:`setStorageType`
56+
57+
.. versionadded:: 3.22
58+
%End
59+
60+
QgsExternalStorage *externalStorage() const;
61+
%Docstring
62+
Returns external storage used to store selected file names, None if none have been defined.
63+
If no external storage has been defined, QgsExternalStorageFileWidget will only update file path according to
64+
selected files.
65+
66+
.. seealso:: :py:func:`setStorageType`
67+
68+
.. versionadded:: 3.22
69+
%End
70+
71+
void setStorageAuthConfigId( const QString &authCfg );
72+
%Docstring
73+
Sets the authentication configuration ID to be used for the current external storage (if
74+
defined)
75+
76+
.. versionadded:: 3.22
77+
%End
78+
79+
const QString &storageAuthConfigId() const;
80+
%Docstring
81+
Returns the authentication configuration ID used for the current external storage (if defined)
82+
83+
.. versionadded:: 3.22
84+
%End
85+
86+
void setStorageUrlExpression( const QString &urlExpression );
87+
%Docstring
88+
Set ``urlExpression`` expression, which once evaluated, provide the URL used to store selected
89+
documents. This is used only if an external storage has been defined
90+
91+
.. seealso:: :py:func:`setStorageType`
92+
93+
.. versionadded:: 3.22
94+
%End
95+
96+
QString storageUrlExpressionString() const;
97+
%Docstring
98+
Returns the original, unmodified expression string, which once evaluated, provide the
99+
URL used to store selected documents. This is used only if an external storage has been defined.
100+
Returns null if no expression has been set.
101+
102+
.. seealso:: :py:func:`setStorageUrlExpression`
103+
104+
.. versionadded:: 3.22
105+
%End
106+
107+
QgsExpression *storageUrlExpression() const;
108+
%Docstring
109+
Returns expression, which once evaluated, provide the URL used to store selected
110+
documents. This is used only if an external storage has been defined.
111+
Returns null if no expression has been set.
112+
113+
.. seealso:: :py:func:`setStorageUrlExpression`
114+
115+
.. versionadded:: 3.22
116+
%End
117+
118+
void setExpressionContext( const QgsExpressionContext &context );
119+
%Docstring
120+
Set expression context to be used when for storage URL expression evaluation
121+
122+
.. seealso:: :py:func:`setStorageUrlExpression`
123+
124+
.. versionadded:: 3.22
125+
%End
126+
127+
const QgsExpressionContext &expressionContext() const;
128+
%Docstring
129+
Returns expression context used for storage url expression evaluation
130+
131+
.. seealso:: :py:func:`storageUrlExpression`
132+
133+
.. versionadded:: 3.22
134+
%End
135+
136+
void setMessageBar( QgsMessageBar *messageBar );
137+
%Docstring
138+
Set ``messageBar`` to report messages
139+
140+
.. versionadded:: 3.22
141+
%End
142+
143+
QgsMessageBar *messageBar() const;
144+
%Docstring
145+
Returns message bar used to report messages
146+
147+
.. versionadded:: 3.22
148+
%End
149+
150+
static QgsExpressionContextScope *createFileWidgetScope();
151+
%Docstring
152+
Creates and Returns an expression context scope specific to QgsExternalStorageFileWidget
153+
It defines the variable containing the user selected file name
154+
155+
.. versionadded:: 3.22
156+
%End
157+
158+
protected:
159+
160+
virtual void updateLayout();
161+
162+
163+
virtual void setSelectedFileNames( QStringList fileNames );
164+
165+
166+
void addFileWidgetScope();
167+
%Docstring
168+
Add file widget specific scope to expression context
169+
%End
170+
171+
};
172+
173+
/************************************************************************
174+
* This file has been generated automatically from *
175+
* *
176+
* src/gui/qgsexternalstoragefilewidget.h *
177+
* *
178+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
179+
************************************************************************/

‎python/gui/auto_generated/qgsfilewidget.sip.in

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010

1111

12+
13+
1214
class QgsFileWidget : QWidget
1315
{
1416
%Docstring(signature="appended")
@@ -257,6 +259,40 @@ the appearance and behavior of the line edit portion of the widget.
257259
void fileChanged( const QString &path );
258260
%Docstring
259261
Emitted whenever the current file or directory ``path`` is changed.
262+
%End
263+
264+
protected:
265+
266+
virtual void updateLayout();
267+
%Docstring
268+
Update buttons visibility
269+
%End
270+
271+
virtual void setSelectedFileNames( QStringList fileNames );
272+
%Docstring
273+
Called whenever user select ``fileNames`` from dialog
274+
%End
275+
276+
static bool isMultiFiles( const QString &path );
277+
%Docstring
278+
Returns true if ``path`` is a multifiles
279+
%End
280+
281+
void setFilePaths( const QStringList &filePaths );
282+
%Docstring
283+
Update filePath according to ``filePaths`` list
284+
%End
285+
286+
287+
288+
QString toUrl( const QString &path ) const;
289+
%Docstring
290+
returns a HTML code with a link to the given file path
291+
%End
292+
293+
QString relativePath( const QString &filePath, bool removeRelative ) const;
294+
%Docstring
295+
Returns a filePath with relative path options applied (or not) !
260296
%End
261297

262298
};

‎python/gui/gui_auto.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
%Include auto_generated/qgsfilecontentsourcelineedit.sip
8181
%Include auto_generated/qgsfiledownloaderdialog.sip
8282
%Include auto_generated/qgsfilewidget.sip
83+
%Include auto_generated/qgsexternalstoragefilewidget.sip
8384
%Include auto_generated/qgsfilterlineedit.sip
8485
%Include auto_generated/qgsfindfilesbypatternwidget.sip
8586
%Include auto_generated/qgsfloatingwidget.sip

‎src/core/externalstorage/qgsexternalstorage.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ class CORE_EXPORT QgsExternalStorage
4747
*/
4848
virtual QString type() const = 0;
4949

50+
/**
51+
* Returns the translated external storage name, which should be used for any
52+
* user-visible display of the external storage name.
53+
*/
54+
virtual QString displayName() const = 0;
55+
5056
/**
5157
* Stores file \a filePath to the \a url for this project external storage.
5258
* Storing process is run in background.

‎src/core/externalstorage/qgssimplecopyexternalstorage.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ QString QgsSimpleCopyExternalStorage::type() const
100100
return QStringLiteral( "SimpleCopy" );
101101
};
102102

103+
QString QgsSimpleCopyExternalStorage::displayName() const
104+
{
105+
return QObject::tr( "Simple copy" );
106+
};
107+
103108
QgsExternalStorageStoredContent *QgsSimpleCopyExternalStorage::doStore( const QString &filePath, const QString &url, const QString &authcfg ) const
104109
{
105110
return new QgsSimpleCopyExternalStorageStoredContent( filePath, url, authcfg );

‎src/core/externalstorage/qgssimplecopyexternalstorage_p.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class CORE_EXPORT QgsSimpleCopyExternalStorage : public QgsExternalStorage
4040

4141
QString type() const override;
4242

43+
QString displayName() const override;
44+
4345
QgsExternalStorageStoredContent *doStore( const QString &filePath, const QString &url, const QString &authcfg = QString() ) const override;
4446

4547
QgsExternalStorageFetchedContent *doFetch( const QString &url, const QString &authConfig = QString() ) const override;

‎src/gui/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ set(QGIS_GUI_SRCS
465465
qgsfieldvalueslineedit.cpp
466466
qgsfilecontentsourcelineedit.cpp
467467
qgsfilewidget.cpp
468+
qgsexternalstoragefilewidget.cpp
468469
qgsfilterlineedit.cpp
469470
qgsfindfilesbypatternwidget.cpp
470471
qgsfloatingwidget.cpp
@@ -715,6 +716,7 @@ set(QGIS_GUI_HDRS
715716
qgsfilecontentsourcelineedit.h
716717
qgsfiledownloaderdialog.h
717718
qgsfilewidget.h
719+
qgsexternalstoragefilewidget.h
718720
qgsfilterlineedit.h
719721
qgsfindfilesbypatternwidget.h
720722
qgsfloatingwidget.h

‎src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ void QgsEditorWidgetRegistry::initEditors( QgsMapCanvas *mapCanvas, QgsMessageBa
6363
registerWidget( QStringLiteral( "Color" ), new QgsColorWidgetFactory( tr( "Color" ) ) );
6464
registerWidget( QStringLiteral( "RelationReference" ), new QgsRelationReferenceFactory( tr( "Relation Reference" ), mapCanvas, messageBar ) );
6565
registerWidget( QStringLiteral( "DateTime" ), new QgsDateTimeEditFactory( tr( "Date/Time" ) ) );
66-
registerWidget( QStringLiteral( "ExternalResource" ), new QgsExternalResourceWidgetFactory( tr( "Attachment" ) ) );
66+
registerWidget( QStringLiteral( "ExternalResource" ), new QgsExternalResourceWidgetFactory( tr( "Attachment" ), messageBar ) );
6767
registerWidget( QStringLiteral( "KeyValue" ), new QgsKeyValueWidgetFactory( tr( "Key/Value" ) ) );
6868
registerWidget( QStringLiteral( "List" ), new QgsListWidgetFactory( tr( "List" ) ) );
6969
registerWidget( QStringLiteral( "Binary" ), new QgsBinaryWidgetFactory( tr( "Binary (BLOB)" ), messageBar ) );

‎src/gui/editorwidgets/core/qgswidgetwrapper.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ const QgsPropertiesDefinition &QgsWidgetWrapper::propertyDefinitions()
2828
properties =
2929
{
3030
{ RootPath, QgsPropertyDefinition( "propertyRootPath", QgsPropertyDefinition::DataTypeString, QObject::tr( "Root path" ), QObject::tr( "string of variable length representing root path to attachment" ) ) },
31-
{ DocumentViewerContent, QgsPropertyDefinition( "documentViewerContent", QgsPropertyDefinition::DataTypeString, QObject::tr( "Document viewer content" ), QObject::tr( "string" ) + "<b>NoContent</b>|<b>Image</b>|<b>Web</b>" ) }
31+
{ DocumentViewerContent, QgsPropertyDefinition( "documentViewerContent", QgsPropertyDefinition::DataTypeString, QObject::tr( "Document viewer content" ), QObject::tr( "string" ) + "<b>NoContent</b>|<b>Image</b>|<b>Web</b>" ) },
32+
{ 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" ) ) }
3233
};
3334
}
3435
return properties;

‎src/gui/editorwidgets/core/qgswidgetwrapper.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ class GUI_EXPORT QgsWidgetWrapper : public QObject
7676
enum Property
7777
{
7878
RootPath = 0, //!< Root path for external resource
79-
DocumentViewerContent //!< Document type for external resource
79+
DocumentViewerContent, //!< Document type for external resource
80+
StorageUrl //!< Storage URL for external resource
8081
};
8182

8283
/**

‎src/gui/editorwidgets/qgsexternalresourceconfigdlg.cpp

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,13 @@
2222
#include "qgsvectorlayer.h"
2323
#include "qgspropertyoverridebutton.h"
2424
#include "qgseditorwidgetwrapper.h"
25+
#include "qgsexternalstorage.h"
26+
#include "qgsexternalstorageregistry.h"
27+
#include "qgsexpressioncontextutils.h"
28+
#include "qgsexternalstoragefilewidget.h"
2529

2630
#include <QFileDialog>
31+
#include <QComboBox>
2732

2833
class QgsExternalResourceWidgetWrapper;
2934

@@ -32,6 +37,20 @@ QgsExternalResourceConfigDlg::QgsExternalResourceConfigDlg( QgsVectorLayer *vl,
3237
{
3338
setupUi( this );
3439

40+
mStorageType->addItem( tr( "Select Existing file" ), QString() );
41+
for ( QgsExternalStorage *storage : QgsApplication::externalStorageRegistry()->externalStorages() )
42+
{
43+
mStorageType->addItem( storage->displayName(), storage->type() );
44+
}
45+
46+
mExternalStorageGroupBox->setVisible( false );
47+
48+
initializeDataDefinedButton( mStorageUrlPropertyOverrideButton, QgsEditorWidgetWrapper::StorageUrl );
49+
mStorageUrlPropertyOverrideButton->registerVisibleWidget( mStorageUrlExpression );
50+
mStorageUrlPropertyOverrideButton->registerExpressionWidget( mStorageUrlExpression );
51+
mStorageUrlPropertyOverrideButton->registerVisibleWidget( mStorageUrl, false );
52+
mStorageUrlPropertyOverrideButton->registerExpressionContextGenerator( this );
53+
3554
// By default, uncheck some options
3655
mUseLink->setChecked( false );
3756
mFullUrl->setChecked( false );
@@ -69,6 +88,7 @@ QgsExternalResourceConfigDlg::QgsExternalResourceConfigDlg( QgsVectorLayer *vl,
6988
mRelativeButtonGroup->setId( mRelativeDefault, QgsFileWidget::RelativeDefaultPath );
7089
mRelativeProject->setChecked( true );
7190

91+
connect( mStorageType, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsExternalResourceConfigDlg::changeStorageType );
7292
connect( mFileWidgetGroupBox, &QGroupBox::toggled, this, &QgsEditorConfigWidget::changed );
7393
connect( mFileWidgetButtonGroupBox, &QGroupBox::toggled, this, &QgsEditorConfigWidget::changed );
7494
connect( mFileWidgetFilterLineEdit, &QLineEdit::textChanged, this, &QgsEditorConfigWidget::changed );
@@ -82,6 +102,7 @@ QgsExternalResourceConfigDlg::QgsExternalResourceConfigDlg( QgsVectorLayer *vl,
82102
{ mDocumentViewerContentSettingsWidget->setEnabled( ( QgsExternalResourceWidget::DocumentViewerContent )idx != QgsExternalResourceWidget::NoContent ); } );
83103
connect( mDocumentViewerHeight, static_cast<void ( QSpinBox::* )( int )>( &QSpinBox::valueChanged ), this, &QgsEditorConfigWidget::changed );
84104
connect( mDocumentViewerWidth, static_cast<void ( QSpinBox::* )( int )>( &QSpinBox::valueChanged ), this, &QgsEditorConfigWidget::changed );
105+
connect( mStorageUrlExpression, &QLineEdit::textChanged, this, &QgsEditorConfigWidget::changed );
85106

86107
mDocumentViewerContentComboBox->addItem( tr( "No Content" ), QgsExternalResourceWidget::NoContent );
87108
mDocumentViewerContentComboBox->addItem( tr( "Image" ), QgsExternalResourceWidget::Image );
@@ -135,6 +156,11 @@ QVariantMap QgsExternalResourceConfigDlg::config()
135156
{
136157
QVariantMap cfg;
137158

159+
cfg.insert( QStringLiteral( "StorageType" ), mStorageType->currentData() );
160+
cfg.insert( QStringLiteral( "StorageAuthConfigId" ), mAuthSettingsProtocol->configId() );
161+
if ( !mStorageUrl->text().isEmpty() )
162+
cfg.insert( QStringLiteral( "StorageUrl" ), mStorageUrl->text() );
163+
138164
cfg.insert( QStringLiteral( "FileWidget" ), mFileWidgetGroupBox->isChecked() );
139165
cfg.insert( QStringLiteral( "FileWidgetButton" ), mFileWidgetButtonGroupBox->isChecked() );
140166
cfg.insert( QStringLiteral( "FileWidgetFilter" ), mFileWidgetFilterLineEdit->text() );
@@ -152,10 +178,11 @@ QVariantMap QgsExternalResourceConfigDlg::config()
152178
cfg.insert( QStringLiteral( "DefaultRoot" ), mRootPath->text() );
153179

154180
// Save Storage Mode
155-
cfg.insert( QStringLiteral( "StorageMode" ), mStorageButtonGroup->checkedId() );
181+
cfg.insert( QStringLiteral( "StorageMode" ), mStorageModeGroupBox->isVisible() ?
182+
mStorageButtonGroup->checkedId() : QgsFileWidget::GetFile );
156183

157184
// Save Relative Paths option
158-
if ( mRelativeGroupBox->isChecked() )
185+
if ( mRelativeGroupBox->isVisible() && mRelativeGroupBox->isChecked() )
159186
{
160187
cfg.insert( QStringLiteral( "RelativeStorage" ), mRelativeButtonGroup->checkedId() );
161188
}
@@ -174,6 +201,16 @@ QVariantMap QgsExternalResourceConfigDlg::config()
174201

175202
void QgsExternalResourceConfigDlg::setConfig( const QVariantMap &config )
176203
{
204+
if ( config.contains( QStringLiteral( "StorageType" ) ) )
205+
{
206+
const int index = mStorageType->findData( config.value( QStringLiteral( "StorageType" ) ) );
207+
if ( index >= 0 )
208+
mStorageType->setCurrentIndex( index );
209+
}
210+
211+
mAuthSettingsProtocol->setConfigId( config.value( QStringLiteral( "StorageAuthConfigId" ) ).toString() );
212+
mStorageUrl->setText( config.value( QStringLiteral( "StorageUrl" ) ).toString() );
213+
177214
if ( config.contains( QStringLiteral( "FileWidget" ) ) )
178215
{
179216
mFileWidgetGroupBox->setChecked( config.value( QStringLiteral( "FileWidget" ) ).toBool() );
@@ -240,3 +277,30 @@ void QgsExternalResourceConfigDlg::setConfig( const QVariantMap &config )
240277
}
241278
}
242279
}
280+
281+
QgsExpressionContext QgsExternalResourceConfigDlg::createExpressionContext() const
282+
{
283+
QgsExpressionContext context = QgsEditorConfigWidget::createExpressionContext();
284+
context << QgsExpressionContextUtils::formScope( );
285+
context << QgsExpressionContextUtils::parentFormScope( );
286+
287+
QgsExpressionContextScope *fileWidgetScope = QgsExternalStorageFileWidget::createFileWidgetScope();
288+
context << fileWidgetScope;
289+
290+
context.setHighlightedVariables( fileWidgetScope->variableNames() );
291+
return context;
292+
}
293+
294+
void QgsExternalResourceConfigDlg::changeStorageType( int storageTypeIndex )
295+
{
296+
// first one in combo box is not an external storage
297+
mExternalStorageGroupBox->setVisible( storageTypeIndex > 0 );
298+
299+
// for now, we store only files in external storage
300+
mStorageModeGroupBox->setVisible( !storageTypeIndex );
301+
302+
// Absolute path are mandatory when using external storage
303+
mRelativeGroupBox->setVisible( !storageTypeIndex );
304+
305+
emit changed();
306+
}

‎src/gui/editorwidgets/qgsexternalresourceconfigdlg.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class GUI_EXPORT QgsExternalResourceConfigDlg : public QgsEditorConfigWidget, pr
3838
//! Constructor for QgsExternalResourceConfigDlg
3939
explicit QgsExternalResourceConfigDlg( QgsVectorLayer *vl, int fieldIdx, QWidget *parent = nullptr );
4040

41+
QgsExpressionContext createExpressionContext() const override;
42+
4143
// QgsEditorConfigWidget interface
4244
public:
4345
QVariantMap config() override;
@@ -49,6 +51,10 @@ class GUI_EXPORT QgsExternalResourceConfigDlg : public QgsEditorConfigWidget, pr
4951

5052
//! Modify RelativeDefault according to mRootPath content
5153
void enableRelativeDefault();
54+
55+
//! change storage type according to index from storage type combo box
56+
void changeStorageType( int storageTypeIndex );
57+
5258
};
5359

5460
#endif // QGSEXTERNALRESOURCECONFIGDLG_H

‎src/gui/editorwidgets/qgsexternalresourcewidgetfactory.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@
1919
#include "qgsexternalresourcewidgetwrapper.h"
2020
#include "qgsexternalresourceconfigdlg.h"
2121

22-
QgsExternalResourceWidgetFactory::QgsExternalResourceWidgetFactory( const QString &name )
22+
QgsExternalResourceWidgetFactory::QgsExternalResourceWidgetFactory( const QString &name, QgsMessageBar *messageBar )
2323
: QgsEditorWidgetFactory( name )
24+
, mMessageBar( messageBar )
2425
{
2526
}
2627

2728
QgsEditorWidgetWrapper *QgsExternalResourceWidgetFactory::create( QgsVectorLayer *vl, int fieldIdx, QWidget *editor, QWidget *parent ) const
2829
{
29-
return new QgsExternalResourceWidgetWrapper( vl, fieldIdx, editor, parent );
30+
return new QgsExternalResourceWidgetWrapper( vl, fieldIdx, editor, mMessageBar, parent );
3031
}
3132

3233
QgsEditorConfigWidget *QgsExternalResourceWidgetFactory::configWidget( QgsVectorLayer *vl, int fieldIdx, QWidget *parent ) const

‎src/gui/editorwidgets/qgsexternalresourcewidgetfactory.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
SIP_NO_FILE
2424

25+
class QgsMessageBar;
2526

2627
/**
2728
* \ingroup gui
@@ -35,15 +36,18 @@ class GUI_EXPORT QgsExternalResourceWidgetFactory : public QgsEditorWidgetFactor
3536

3637
/**
3738
* Constructor for QgsExternalResourceWidgetFactory, where \a name is a human-readable
38-
* name for the factory.
39+
* name for the factory and \a messageBar the message bar used to report messages.
3940
*/
40-
QgsExternalResourceWidgetFactory( const QString &name );
41+
QgsExternalResourceWidgetFactory( const QString &name, QgsMessageBar *messageBar );
4142

4243
// QgsEditorWidgetFactory interface
4344
public:
4445
QgsEditorWidgetWrapper *create( QgsVectorLayer *vl, int fieldIdx, QWidget *editor, QWidget *parent ) const override;
4546
QgsEditorConfigWidget *configWidget( QgsVectorLayer *vl, int fieldIdx, QWidget *parent ) const override;
4647
unsigned int fieldScore( const QgsVectorLayer *vl, int fieldIdx ) const override;
48+
49+
private:
50+
QgsMessageBar *mMessageBar = nullptr;
4751
};
4852

4953
#endif // QGSEXTERNALRESOURCEWIDGETFACTORY_H

‎src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@
2424
#include "qgsfilterlineedit.h"
2525
#include "qgsapplication.h"
2626
#include "qgsexpressioncontextutils.h"
27+
#include "qgsexternalstoragefilewidget.h"
2728

2829

29-
QgsExternalResourceWidgetWrapper::QgsExternalResourceWidgetWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor, QWidget *parent )
30+
QgsExternalResourceWidgetWrapper::QgsExternalResourceWidgetWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor, QgsMessageBar *messageBar, QWidget *parent )
3031
: QgsEditorWidgetWrapper( layer, fieldIdx, editor, parent )
31-
32+
, mMessageBar( messageBar )
3233
{
3334
}
3435

@@ -121,6 +122,11 @@ void QgsExternalResourceWidgetWrapper::setFeature( const QgsFeature &feature )
121122
{
122123
updateProperties( feature );
123124
QgsEditorWidgetWrapper::setFeature( feature );
125+
126+
if ( mQgsWidget )
127+
{
128+
updateFileWidgetExpressionContext();
129+
}
124130
}
125131

126132
QWidget *QgsExternalResourceWidgetWrapper::createWidget( QWidget *parent )
@@ -152,9 +158,21 @@ void QgsExternalResourceWidgetWrapper::initWidget( QWidget *editor )
152158

153159
if ( mQgsWidget )
154160
{
161+
mQgsWidget->setMessageBar( mMessageBar );
162+
155163
mQgsWidget->fileWidget()->setStorageMode( QgsFileWidget::GetFile );
156164

157165
const QVariantMap cfg = config();
166+
mPropertyCollection.loadVariant( cfg.value( QStringLiteral( "PropertyCollection" ) ), propertyDefinitions() );
167+
168+
mQgsWidget->setStorageType( cfg.value( QStringLiteral( "StorageType" ) ).toString() );
169+
mQgsWidget->setStorageAuthConfigId( cfg.value( QStringLiteral( "StorageAuthConfigId" ) ).toString() );
170+
171+
mQgsWidget->fileWidget()->setStorageUrlExpression( mPropertyCollection.isActive( QgsWidgetWrapper::StorageUrl ) ?
172+
mPropertyCollection.property( QgsWidgetWrapper::StorageUrl ).asExpression() :
173+
QgsExpression::quotedValue( cfg.value( QStringLiteral( "StorageUrl" ) ).toString() ) );
174+
175+
updateFileWidgetExpressionContext();
158176

159177
if ( cfg.contains( QStringLiteral( "UseLink" ) ) )
160178
{
@@ -165,7 +183,6 @@ void QgsExternalResourceWidgetWrapper::initWidget( QWidget *editor )
165183
mQgsWidget->fileWidget()->setFullUrl( cfg.value( QStringLiteral( "FullUrl" ) ).toBool() );
166184
}
167185

168-
mPropertyCollection.loadVariant( cfg.value( QStringLiteral( "PropertyCollection" ) ), propertyDefinitions() );
169186
if ( !mPropertyCollection.isActive( QgsWidgetWrapper::RootPath ) )
170187
{
171188
mQgsWidget->setDefaultRoot( cfg.value( QStringLiteral( "DefaultRoot" ) ).toString() );
@@ -304,3 +321,19 @@ void QgsExternalResourceWidgetWrapper::updateConstraintWidgetStatus()
304321
}
305322
}
306323
}
324+
325+
void QgsExternalResourceWidgetWrapper::updateFileWidgetExpressionContext()
326+
{
327+
if ( !mQgsWidget || !layer() )
328+
return;
329+
330+
QgsExpressionContext expressionContext( layer()->createExpressionContext() );
331+
expressionContext.setFeature( formFeature() );
332+
expressionContext.appendScope( QgsExpressionContextUtils::formScope( formFeature() ) );
333+
if ( context().parentFormFeature().isValid() )
334+
{
335+
expressionContext.appendScope( QgsExpressionContextUtils::parentFormScope( context().parentFormFeature() ) );
336+
}
337+
338+
mQgsWidget->fileWidget()->setExpressionContext( expressionContext );
339+
}

‎src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,9 @@ class GUI_EXPORT QgsExternalResourceWidgetWrapper : public QgsEditorWidgetWrappe
5252
* new widget should be autogenerated.
5353
*
5454
* A \a parent widget for this widget wrapper and the created widget can also be specified.
55+
* A \a messageBar to report messages can also be specified
5556
*/
56-
explicit QgsExternalResourceWidgetWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor = nullptr, QWidget *parent = nullptr );
57+
explicit QgsExternalResourceWidgetWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor = nullptr, QgsMessageBar *messageBar = nullptr, QWidget *parent = nullptr );
5758

5859
// QgsEditorWidgetWrapper interface
5960
public:
@@ -65,6 +66,11 @@ class GUI_EXPORT QgsExternalResourceWidgetWrapper : public QgsEditorWidgetWrappe
6566
void initWidget( QWidget *editor ) override;
6667
bool valid() const override;
6768

69+
/**
70+
* Update file widget current expression context according to layer, feature, and parent feature
71+
*/
72+
void updateFileWidgetExpressionContext();
73+
6874
public slots:
6975
void setFeature( const QgsFeature &feature ) override;
7076
void setEnabled( bool enabled ) override;
@@ -90,6 +96,7 @@ class GUI_EXPORT QgsExternalResourceWidgetWrapper : public QgsEditorWidgetWrappe
9096
QLabel *mLabel = nullptr;
9197
QgsAttributeForm *mForm = nullptr;
9298
QgsExternalResourceWidget *mQgsWidget = nullptr;
99+
QgsMessageBar *mMessageBar = nullptr;
93100

94101
friend class TestQgsExternalResourceWidgetWrapper;
95102

‎src/gui/qgsexternalresourcewidget.cpp

Lines changed: 139 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,25 @@
1818
#include "qgspixmaplabel.h"
1919
#include "qgsproject.h"
2020
#include "qgsapplication.h"
21+
#include "qgsnetworkaccessmanager.h"
22+
#include "qgstaskmanager.h"
23+
#include "qgsexternalstorage.h"
24+
#include "qgsmessagebar.h"
25+
#include "qgsexternalstoragefilewidget.h"
2126

2227
#include <QDir>
2328
#include <QGridLayout>
2429
#include <QVariant>
2530
#include <QSettings>
2631
#include <QImageReader>
32+
#include <QToolButton>
33+
#include <QMimeType>
34+
#include <QMimeDatabase>
35+
#include <QMovie>
2736
#ifdef WITH_QTWEBKIT
2837
#include <QWebView>
2938
#endif
3039

31-
3240
QgsExternalResourceWidget::QgsExternalResourceWidget( QWidget *parent )
3341
: QWidget( parent )
3442
{
@@ -38,7 +46,7 @@ QgsExternalResourceWidget::QgsExternalResourceWidget( QWidget *parent )
3846
QGridLayout *layout = new QGridLayout();
3947
layout->setContentsMargins( 0, 0, 0, 0 );
4048

41-
mFileWidget = new QgsFileWidget( this );
49+
mFileWidget = new QgsExternalStorageFileWidget( this );
4250
layout->addWidget( mFileWidget, 0, 0 );
4351
mFileWidget->setVisible( mFileWidgetVisible );
4452

@@ -50,6 +58,16 @@ QgsExternalResourceWidget::QgsExternalResourceWidget( QWidget *parent )
5058
layout->addWidget( mWebView, 2, 0 );
5159
#endif
5260

61+
mLoadingLabel = new QLabel( this );
62+
layout->addWidget( mLoadingLabel, 3, 0 );
63+
mLoadingMovie = new QMovie( QgsApplication::iconPath( QStringLiteral( "/mIconLoading.gif" ) ), QByteArray(), this );
64+
mLoadingMovie->setScaledSize( QSize( 32, 32 ) );
65+
mLoadingLabel->setMovie( mLoadingMovie );
66+
67+
mErrorLabel = new QLabel( this );
68+
layout->addWidget( mErrorLabel, 4, 0 );
69+
mErrorLabel->setPixmap( QPixmap( QgsApplication::iconPath( QStringLiteral( "/mIconWarning.svg" ) ) ) );
70+
5371
updateDocumentViewer();
5472

5573
setLayout( layout );
@@ -76,7 +94,7 @@ void QgsExternalResourceWidget::setDocumentPath( const QVariant &path )
7694
mFileWidget->setFilePath( path.toString() );
7795
}
7896

79-
QgsFileWidget *QgsExternalResourceWidget::fileWidget()
97+
QgsExternalStorageFileWidget *QgsExternalResourceWidget::fileWidget()
8098
{
8199
return mFileWidget;
82100
}
@@ -134,6 +152,10 @@ void QgsExternalResourceWidget::setReadOnly( bool readOnly )
134152

135153
void QgsExternalResourceWidget::updateDocumentViewer()
136154
{
155+
mErrorLabel->setVisible( false );
156+
mLoadingLabel->setVisible( false );
157+
mLoadingMovie->stop();
158+
137159
#ifdef WITH_QTWEBKIT
138160
mWebView->setVisible( mDocumentViewerContent == Web );
139161
#endif
@@ -212,51 +234,132 @@ void QgsExternalResourceWidget::setRelativeStorage( QgsFileWidget::RelativeStora
212234
mRelativeStorage = relativeStorage;
213235
}
214236

215-
void QgsExternalResourceWidget::loadDocument( const QString &path )
237+
void QgsExternalResourceWidget::setStorageType( const QString &storageType )
216238
{
217-
QString resolvedPath;
239+
mFileWidget->setStorageType( storageType );
240+
}
218241

219-
if ( path.isEmpty() )
220-
{
242+
QString QgsExternalResourceWidget::storageType() const
243+
{
244+
return mFileWidget->storageType();
245+
}
246+
247+
void QgsExternalResourceWidget::setStorageAuthConfigId( const QString &authCfg )
248+
{
249+
mFileWidget->setStorageAuthConfigId( authCfg );
250+
}
251+
252+
QString QgsExternalResourceWidget::storageAuthConfigId() const
253+
{
254+
return mFileWidget->storageAuthConfigId();
255+
}
256+
257+
void QgsExternalResourceWidget::setMessageBar( QgsMessageBar *messageBar )
258+
{
259+
mFileWidget->setMessageBar( messageBar );
260+
}
261+
262+
QgsMessageBar *QgsExternalResourceWidget::messageBar() const
263+
{
264+
return mFileWidget->messageBar();
265+
}
266+
267+
void QgsExternalResourceWidget::updateDocumentContent( const QString &filePath )
268+
{
221269
#ifdef WITH_QTWEBKIT
222-
if ( mDocumentViewerContent == Web )
223-
{
224-
mWebView->setUrl( QUrl( QStringLiteral( "about:blank" ) ) );
225-
}
270+
if ( mDocumentViewerContent == Web )
271+
{
272+
mWebView->load( QUrl::fromEncoded( filePath.toUtf8() ) );
273+
mWebView->page()->settings()->setAttribute( QWebSettings::LocalStorageEnabled, true );
274+
}
226275
#endif
227-
if ( mDocumentViewerContent == Image )
228-
{
276+
277+
if ( mDocumentViewerContent == Image )
278+
{
279+
// use an image reader to ensure image orientation and transforms are correctly handled
280+
QImageReader ir( filePath );
281+
ir.setAutoTransform( true );
282+
const QPixmap pm = QPixmap::fromImage( ir.read() );
283+
if ( !pm.isNull() )
284+
mPixmapLabel->setPixmap( pm );
285+
else
229286
mPixmapLabel->clear();
230-
updateDocumentViewer();
231-
}
232287
}
233-
else
234-
{
235-
resolvedPath = resolvePath( path );
236288

289+
updateDocumentViewer();
290+
}
291+
292+
void QgsExternalResourceWidget::clearContent()
293+
{
237294
#ifdef WITH_QTWEBKIT
238-
if ( mDocumentViewerContent == Web )
239-
{
240-
mWebView->load( QUrl::fromEncoded( resolvedPath.toUtf8() ) );
241-
mWebView->page()->settings()->setAttribute( QWebSettings::LocalStorageEnabled, true );
242-
}
295+
if ( mDocumentViewerContent == Web )
296+
{
297+
mWebView->setUrl( QUrl( QStringLiteral( "about:blank" ) ) );
298+
}
243299
#endif
300+
if ( mDocumentViewerContent == Image )
301+
{
302+
mPixmapLabel->clear();
303+
updateDocumentViewer();
304+
}
305+
}
306+
307+
void QgsExternalResourceWidget::loadDocument( const QString &path )
308+
{
309+
if ( path.isEmpty() || path == QgsApplication::nullRepresentation() )
310+
{
311+
clearContent();
312+
}
313+
else if ( mDocumentViewerContent != NoContent )
314+
{
315+
const QString resolvedPath = resolvePath( path );
244316

245-
if ( mDocumentViewerContent == Image )
317+
if ( mFileWidget->externalStorage() )
246318
{
247-
// use an image reader to ensure image orientation and transforms are correctly handled
248-
QImageReader ir( resolvedPath );
249-
ir.setAutoTransform( true );
250-
const QPixmap pm = QPixmap::fromImage( ir.read() );
251-
if ( !pm.isNull() )
252-
{
253-
mPixmapLabel->setPixmap( pm );
254-
}
255-
else
319+
QgsExternalStorageFetchedContent *content = mFileWidget->externalStorage()->fetch( resolvedPath, storageAuthConfigId() );
320+
321+
auto onFetchFinished = [ = ]
256322
{
257-
mPixmapLabel->clear();
258-
}
259-
updateDocumentViewer();
323+
if ( content->status() == Qgis::ContentStatus::Failed )
324+
{
325+
mWebView->setVisible( false );
326+
mPixmapLabel->setVisible( false );
327+
mLoadingLabel->setVisible( false );
328+
mLoadingMovie->stop();
329+
mErrorLabel->setVisible( true );
330+
331+
if ( messageBar() )
332+
{
333+
messageBar()->pushWarning( tr( "Fetching External Resource" ),
334+
tr( "Error while fetching external resource '%1' : %2" ).arg( path, content->errorString() ) );
335+
}
336+
}
337+
else if ( content->status() == Qgis::ContentStatus::Finished )
338+
{
339+
const QString filePath = mDocumentViewerContent == Web
340+
? QString( "file://%1" ).arg( content->filePath() )
341+
: content->filePath();
342+
343+
updateDocumentContent( filePath );
344+
}
345+
346+
content->deleteLater();
347+
};
348+
349+
mWebView->setVisible( false );
350+
mPixmapLabel->setVisible( false );
351+
mErrorLabel->setVisible( false );
352+
mLoadingLabel->setVisible( true );
353+
mLoadingMovie->start();
354+
connect( content, &QgsExternalStorageFetchedContent::fetched, onFetchFinished );
355+
connect( content, &QgsExternalStorageFetchedContent::errorOccurred, onFetchFinished );
356+
connect( content, &QgsExternalStorageFetchedContent::canceled, onFetchFinished );
357+
358+
content->fetch();
359+
}
360+
else
361+
{
362+
updateDocumentContent( resolvedPath );
260363
}
261364
}
262365
}

‎src/gui/qgsexternalresourcewidget.h

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
class QWebView;
2121
class QgsPixmapLabel;
22+
class QgsMessageBar;
23+
class QgsExternalStorageFileWidget;
2224

2325
#include <QWidget>
2426
#include <QVariant>
@@ -34,6 +36,8 @@ class QgsPixmapLabel;
3436
// doesn't add this include to the file where the code from
3537
// ConvertToSubClassCode goes.
3638
#include <qgsexternalresourcewidget.h>
39+
40+
#include <qgsexternalstoragefilewidget.h>
3741
% End
3842
#endif
3943

@@ -85,8 +89,10 @@ class GUI_EXPORT QgsExternalResourceWidget : public QWidget
8589
QVariant documentPath( QVariant::Type type = QVariant::String ) const;
8690
void setDocumentPath( const QVariant &documentPath );
8791

88-
//! access the file widget to allow its configuration
89-
QgsFileWidget *fileWidget();
92+
/**
93+
* Returns file widget to allow its configuration
94+
*/
95+
QgsExternalStorageFileWidget *fileWidget();
9096

9197
//! returns if the file widget is visible in the widget
9298
bool fileWidgetVisible() const;
@@ -143,6 +149,47 @@ class GUI_EXPORT QgsExternalResourceWidget : public QWidget
143149
*/
144150
void setDefaultRoot( const QString &defaultRoot );
145151

152+
/**
153+
* Set \a storageType storage type unique identifier as defined in QgsExternalStorageRegistry or
154+
* null QString if there is no storage defined, only file selection.
155+
* \see storageType
156+
* \since QGIS 3.22
157+
*/
158+
void setStorageType( const QString &storageType );
159+
160+
/**
161+
* Returns storage type unique identifier as defined in QgsExternalStorageRegistry.
162+
* Returns null QString if there is no storage defined, only file selection.
163+
* \see setStorageType
164+
* \since QGIS 3.22
165+
*/
166+
QString storageType() const;
167+
168+
/**
169+
* Sets the authentication configuration ID to be used for the current external storage (if
170+
* defined)
171+
* \since QGIS 3.22
172+
*/
173+
void setStorageAuthConfigId( const QString &authCfg );
174+
175+
/**
176+
* Returns the authentication configuration ID used for the current external storage (if defined)
177+
* \since QGIS 3.22
178+
*/
179+
QString storageAuthConfigId() const;
180+
181+
/**
182+
* Set \a messageBar to report messages
183+
* \since 3.22
184+
*/
185+
void setMessageBar( QgsMessageBar *messageBar );
186+
187+
/**
188+
* Returns message bar used to report messages
189+
* \since 3.22
190+
*/
191+
QgsMessageBar *messageBar() const;
192+
146193
signals:
147194
//! emitteed as soon as the current document changes
148195
void valueChanged( const QString & );
@@ -153,6 +200,16 @@ class GUI_EXPORT QgsExternalResourceWidget : public QWidget
153200
private:
154201
void updateDocumentViewer();
155202

203+
/**
204+
* update document content with \a filePath
205+
*/
206+
void updateDocumentContent( const QString &filePath );
207+
208+
/**
209+
* Clear content from widget
210+
*/
211+
void clearContent();
212+
156213
QString resolvePath( const QString &path );
157214

158215
//! properties
@@ -164,13 +221,17 @@ class GUI_EXPORT QgsExternalResourceWidget : public QWidget
164221
QString mDefaultRoot; // configured default root path for QgsFileWidget::RelativeStorage::RelativeDefaultPath
165222

166223
//! UI objects
167-
QgsFileWidget *mFileWidget = nullptr;
224+
QgsExternalStorageFileWidget *mFileWidget = nullptr;
168225
QgsPixmapLabel *mPixmapLabel = nullptr;
169226
#ifdef WITH_QTWEBKIT
170227
//! This webview is used as a container to display the picture
171228
QWebView *mWebView = nullptr;
172229
#endif
230+
QLabel *mLoadingLabel = nullptr;
231+
QLabel *mErrorLabel = nullptr;
232+
QMovie *mLoadingMovie = nullptr;
173233

234+
friend class TestQgsExternalResourceWidgetWrapper;
174235
};
175236

176237
#endif // QGSEXTERNALRESOURCEWIDGET_H
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
/***************************************************************************
2+
qgsexternalstoragefilewidget.cpp
3+
--------------------------------------
4+
Date : August 2021
5+
Copyright : (C) 2021 by Julien Cabieces
6+
Email : julien dot cabieces at oslandia dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgsexternalstoragefilewidget.h"
17+
18+
#include <QLineEdit>
19+
#include <QToolButton>
20+
#include <QLabel>
21+
#include <QGridLayout>
22+
#include <QUrl>
23+
#include <QDropEvent>
24+
#include <QRegularExpression>
25+
#include <QProgressBar>
26+
27+
#include "qgssettings.h"
28+
#include "qgsfilterlineedit.h"
29+
#include "qgsfocuskeeper.h"
30+
#include "qgslogger.h"
31+
#include "qgsproject.h"
32+
#include "qgsapplication.h"
33+
#include "qgsfileutils.h"
34+
#include "qgsmimedatautils.h"
35+
#include "qgsexternalstorage.h"
36+
#include "qgsexternalstorageregistry.h"
37+
#include "qgsmessagebar.h"
38+
39+
#define FILEPATH_VARIABLE "selected_file_path"
40+
41+
QgsExternalStorageFileWidget::QgsExternalStorageFileWidget( QWidget *parent )
42+
: QgsFileWidget( parent )
43+
{
44+
mProgressLabel = new QLabel( this );
45+
mLayout->addWidget( mProgressLabel );
46+
mProgressLabel->hide();
47+
48+
mProgressBar = new QProgressBar( this );
49+
mLayout->addWidget( mProgressBar );
50+
mProgressBar->hide();
51+
52+
mCancelButton = new QToolButton( this );
53+
mLayout->addWidget( mCancelButton );
54+
mCancelButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mTaskCancel.svg" ) ) );
55+
mCancelButton->hide();
56+
57+
setLayout( mLayout );
58+
}
59+
60+
void QgsExternalStorageFileWidget::setStorageType( const QString &storageType )
61+
{
62+
if ( storageType.isEmpty() )
63+
mExternalStorage = nullptr;
64+
65+
else
66+
{
67+
mExternalStorage = QgsApplication::externalStorageRegistry()->externalStorageFromType( storageType );
68+
if ( !mExternalStorage )
69+
{
70+
QgsDebugMsg( QStringLiteral( "Invalid storage type: %1" ).arg( storageType ) );
71+
return;
72+
}
73+
addFileWidgetScope();
74+
}
75+
}
76+
77+
QString QgsExternalStorageFileWidget::storageType() const
78+
{
79+
return mExternalStorage ? mExternalStorage->type() : QString();
80+
}
81+
82+
QgsExternalStorage *QgsExternalStorageFileWidget::externalStorage() const
83+
{
84+
return mExternalStorage;
85+
}
86+
87+
void QgsExternalStorageFileWidget::setStorageAuthConfigId( const QString &authCfg )
88+
{
89+
mAuthCfg = authCfg;
90+
}
91+
92+
const QString &QgsExternalStorageFileWidget::storageAuthConfigId() const
93+
{
94+
return mAuthCfg;
95+
}
96+
97+
void QgsExternalStorageFileWidget::setStorageUrlExpression( const QString &urlExpression )
98+
{
99+
mStorageUrlExpression.reset( new QgsExpression( urlExpression ) );
100+
}
101+
102+
QgsExpression *QgsExternalStorageFileWidget::storageUrlExpression() const
103+
{
104+
return mStorageUrlExpression.get();
105+
}
106+
107+
QString QgsExternalStorageFileWidget::storageUrlExpressionString() const
108+
{
109+
return mStorageUrlExpression ? mStorageUrlExpression->expression() : QString();
110+
}
111+
112+
113+
void QgsExternalStorageFileWidget::setExpressionContext( const QgsExpressionContext &context )
114+
{
115+
mScope = nullptr; // deleted by old context when we override it with the new one
116+
mExpressionContext = context;
117+
addFileWidgetScope();
118+
}
119+
120+
void QgsExternalStorageFileWidget::addFileWidgetScope()
121+
{
122+
if ( !mExternalStorage || mScope )
123+
return;
124+
125+
mScope = createFileWidgetScope();
126+
mExpressionContext << mScope;
127+
}
128+
129+
QgsExpressionContextScope *QgsExternalStorageFileWidget::createFileWidgetScope()
130+
{
131+
QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "FileWidget" ) );
132+
scope->addVariable( QgsExpressionContextScope::StaticVariable(
133+
QStringLiteral( FILEPATH_VARIABLE ),
134+
QString(), true, false, tr( "User selected absolute filepath" ) ) );
135+
return scope;
136+
}
137+
138+
const QgsExpressionContext &QgsExternalStorageFileWidget::expressionContext() const
139+
{
140+
return mExpressionContext;
141+
}
142+
143+
144+
void QgsExternalStorageFileWidget::setMessageBar( QgsMessageBar *messageBar )
145+
{
146+
mMessageBar = messageBar;
147+
}
148+
149+
QgsMessageBar *QgsExternalStorageFileWidget::messageBar() const
150+
{
151+
return mMessageBar;
152+
}
153+
154+
void QgsExternalStorageFileWidget::updateLayout()
155+
{
156+
mProgressLabel->setVisible( mStoreInProgress );
157+
mProgressBar->setVisible( mStoreInProgress );
158+
mCancelButton->setVisible( mStoreInProgress );
159+
160+
const bool linkVisible = mUseLink && !mIsLinkEdited;
161+
162+
mLineEdit->setVisible( !mStoreInProgress && !linkVisible );
163+
mLinkLabel->setVisible( !mStoreInProgress && linkVisible );
164+
mLinkEditButton->setVisible( !mStoreInProgress && mUseLink && !mReadOnly );
165+
166+
mFileWidgetButton->setVisible( mButtonVisible && !mStoreInProgress );
167+
mFileWidgetButton->setEnabled( !mReadOnly );
168+
mLineEdit->setEnabled( !mReadOnly );
169+
170+
mLinkEditButton->setIcon( linkVisible && !mReadOnly ?
171+
QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) :
172+
QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveEdits.svg" ) ) );
173+
}
174+
175+
void QgsExternalStorageFileWidget::setSelectedFileNames( QStringList fileNames )
176+
{
177+
Q_ASSERT( fileNames.count() );
178+
179+
// store files first, update filePath later
180+
if ( mExternalStorage )
181+
{
182+
if ( !mStorageUrlExpression->prepare( &mExpressionContext ) )
183+
{
184+
if ( messageBar() )
185+
{
186+
messageBar()->pushWarning( tr( "Storing External resource" ),
187+
tr( "Storage URL expression is invalid : %1" ).arg( mStorageUrlExpression->evalErrorString() ) );
188+
}
189+
190+
QgsDebugMsg( tr( "Storage URL expression is invalid : %1" ).arg( mStorageUrlExpression->evalErrorString() ) );
191+
return;
192+
}
193+
194+
storeExternalFiles( fileNames );
195+
}
196+
else
197+
{
198+
QgsFileWidget::setSelectedFileNames( fileNames );
199+
}
200+
}
201+
202+
void QgsExternalStorageFileWidget::storeExternalFiles( QStringList fileNames, QStringList storedUrls )
203+
{
204+
const QString filePath = fileNames.takeFirst();
205+
206+
mProgressLabel->setText( tr( "Storing file %1 ..." ).arg( QFileInfo( filePath ).fileName() ) );
207+
mStoreInProgress = true;
208+
mProgressBar->setValue( 0 );
209+
updateLayout();
210+
211+
Q_ASSERT( mScope );
212+
mScope->setVariable( QStringLiteral( FILEPATH_VARIABLE ), filePath );
213+
214+
QVariant url = mStorageUrlExpression->evaluate( &mExpressionContext );
215+
if ( !url.isValid() )
216+
{
217+
if ( messageBar() )
218+
{
219+
messageBar()->pushWarning( tr( "Storing External resource" ),
220+
tr( "Storage URL expression is invalid : %1" ).arg( mStorageUrlExpression->evalErrorString() ) );
221+
}
222+
223+
mStoreInProgress = false;
224+
updateLayout();
225+
226+
return;
227+
}
228+
229+
QgsExternalStorageStoredContent *storedContent = mExternalStorage->store( filePath, url.toString(), mAuthCfg );
230+
231+
connect( storedContent, &QgsExternalStorageStoredContent::progressChanged, mProgressBar, &QProgressBar::setValue );
232+
connect( mCancelButton, &QToolButton::clicked, storedContent, &QgsExternalStorageStoredContent::cancel );
233+
234+
auto onStoreFinished = [ = ]
235+
{
236+
mStoreInProgress = false;
237+
updateLayout();
238+
storedContent->deleteLater();
239+
240+
if ( storedContent->status() == Qgis::ContentStatus::Failed && messageBar() )
241+
{
242+
messageBar()->pushWarning( tr( "Storing External resource" ),
243+
tr( "Storing file '%1' to url '%2' has failed : %3" ).arg( filePath, url.toString(), storedContent->errorString() ) );
244+
}
245+
246+
if ( storedContent->status() != Qgis::ContentStatus::Finished )
247+
return;
248+
249+
QStringList newStoredUrls = storedUrls;
250+
newStoredUrls << storedContent->url();
251+
252+
// every thing has been stored, we update filepath
253+
if ( fileNames.isEmpty() )
254+
{
255+
setFilePaths( newStoredUrls );
256+
}
257+
else
258+
storeExternalFiles( fileNames, newStoredUrls );
259+
};
260+
261+
connect( storedContent, &QgsExternalStorageStoredContent::stored, onStoreFinished );
262+
connect( storedContent, &QgsExternalStorageStoredContent::canceled, onStoreFinished );
263+
connect( storedContent, &QgsExternalStorageStoredContent::errorOccurred, onStoreFinished );
264+
265+
storedContent->store();
266+
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/***************************************************************************
2+
qgsexternalstoragefilewidget.h
3+
--------------------------------------
4+
Date : August 2021
5+
Copyright : (C) 2021 by Julien Cabieces
6+
Email : julien dot cabieces at oslandia dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#ifndef QGSEXTERNALSTORAGEFILEWIDGET_H
17+
#define QGSEXTERNALSTORAGEFILEWIDGET_H
18+
19+
class QLabel;
20+
class QToolButton;
21+
class QVariant;
22+
class QHBoxLayout;
23+
class QProgressBar;
24+
class QgsExternalStorage;
25+
class QgsMessageBar;
26+
27+
#include <QWidget>
28+
#include <QFileDialog>
29+
30+
#include "qgsfilewidget.h"
31+
#include "qgsexpressioncontext.h"
32+
33+
/**
34+
* \ingroup gui
35+
* \brief The QgsExternalStorageFileWidget class creates a widget for selecting a file or a folder
36+
* and stores it to a given external storage backend if defined
37+
* \since QGIS 3.22
38+
*/
39+
class GUI_EXPORT QgsExternalStorageFileWidget : public QgsFileWidget
40+
{
41+
42+
#ifdef SIP_RUN
43+
SIP_CONVERT_TO_SUBCLASS_CODE
44+
if ( qobject_cast<QgsExternalStorageFileWidget *>( sipCpp ) )
45+
sipType = sipType_QgsExternalStorageFileWidget;
46+
else
47+
sipType = NULL;
48+
SIP_END
49+
#endif
50+
51+
Q_OBJECT
52+
Q_PROPERTY( QString storageType READ storageType WRITE setStorageType )
53+
Q_PROPERTY( QString auth READ storageAuthConfigId WRITE setStorageAuthConfigId )
54+
Q_PROPERTY( QString storageUrlExpression READ storageUrlExpressionString WRITE setStorageUrlExpression )
55+
56+
public:
57+
58+
/**
59+
* \brief QgsExternalStorageFileWidget creates a widget for selecting a file or a folder.
60+
*/
61+
explicit QgsExternalStorageFileWidget( QWidget *parent SIP_TRANSFERTHIS = nullptr );
62+
63+
/**
64+
* Set \a storageType storage type unique identifier as defined in QgsExternalStorageRegistry or
65+
* null QString if there is no storage defined.
66+
* If no external storage has been defined, QgsExternalStorageFileWidget will only update file path according to
67+
* selected files.
68+
* \see storageType
69+
* \since QGIS 3.22
70+
*/
71+
void setStorageType( const QString &storageType );
72+
73+
/**
74+
* Returns storage type unique identifier as defined in QgsExternalStorageRegistry.
75+
* Returns null QString if there is no storage defined, only file selection.
76+
* \see setStorageType
77+
* \since QGIS 3.22
78+
*/
79+
QString storageType() const;
80+
81+
/**
82+
* Returns external storage used to store selected file names, nullptr if none have been defined.
83+
* If no external storage has been defined, QgsExternalStorageFileWidget will only update file path according to
84+
* selected files.
85+
* \see setStorageType
86+
* \since QGIS 3.22
87+
*/
88+
QgsExternalStorage *externalStorage() const;
89+
90+
/**
91+
* Sets the authentication configuration ID to be used for the current external storage (if
92+
* defined)
93+
* \since QGIS 3.22
94+
*/
95+
void setStorageAuthConfigId( const QString &authCfg );
96+
97+
/**
98+
* Returns the authentication configuration ID used for the current external storage (if defined)
99+
* \since QGIS 3.22
100+
*/
101+
const QString &storageAuthConfigId() const;
102+
103+
/**
104+
* Set \a urlExpression expression, which once evaluated, provide the URL used to store selected
105+
* documents. This is used only if an external storage has been defined
106+
* \see setStorageType(), externalStorage()
107+
* \since 3.22
108+
*/
109+
void setStorageUrlExpression( const QString &urlExpression );
110+
111+
/**
112+
* Returns the original, unmodified expression string, which once evaluated, provide the
113+
* URL used to store selected documents. This is used only if an external storage has been defined.
114+
* Returns null if no expression has been set.
115+
* \see setStorageUrlExpression()
116+
* \since 3.22
117+
*/
118+
QString storageUrlExpressionString() const;
119+
120+
/**
121+
* Returns expression, which once evaluated, provide the URL used to store selected
122+
* documents. This is used only if an external storage has been defined.
123+
* Returns null if no expression has been set.
124+
* \see setStorageUrlExpression()
125+
* \since 3.22
126+
*/
127+
QgsExpression *storageUrlExpression() const;
128+
129+
/**
130+
* Set expression context to be used when for storage URL expression evaluation
131+
* \see setStorageUrlExpression
132+
* \since 3.22
133+
*/
134+
void setExpressionContext( const QgsExpressionContext &context );
135+
136+
/**
137+
* Returns expression context used for storage url expression evaluation
138+
* \see storageUrlExpression
139+
* \since 3.22
140+
*/
141+
const QgsExpressionContext &expressionContext() const;
142+
143+
/**
144+
* Set \a messageBar to report messages
145+
* \since QGIS 3.22
146+
*/
147+
void setMessageBar( QgsMessageBar *messageBar );
148+
149+
/**
150+
* Returns message bar used to report messages
151+
* \since QGIS 3.22
152+
*/
153+
QgsMessageBar *messageBar() const;
154+
155+
/**
156+
* Creates and Returns an expression context scope specific to QgsExternalStorageFileWidget
157+
* It defines the variable containing the user selected file name
158+
* \since 3.22
159+
*/
160+
static QgsExpressionContextScope *createFileWidgetScope();
161+
162+
protected:
163+
164+
void updateLayout() override;
165+
166+
void setSelectedFileNames( QStringList fileNames ) override;
167+
168+
/**
169+
* Add file widget specific scope to expression context
170+
*/
171+
void addFileWidgetScope();
172+
173+
private:
174+
175+
// stores \a fileNames files using current external storage.
176+
// This is a recursive method, \a storedUrls contains urls for previously stored
177+
// fileNames. When all files have been successfully stored, current mFilePath
178+
// is updated
179+
void storeExternalFiles( QStringList fileNames, QStringList storedUrls = QStringList() );
180+
181+
bool mStoreInProgress = false;
182+
183+
QgsExternalStorage *mExternalStorage = nullptr;
184+
QString mAuthCfg;
185+
std::unique_ptr< QgsExpression > mStorageUrlExpression;
186+
QgsExpressionContext mExpressionContext;
187+
QgsExpressionContextScope *mScope = nullptr;
188+
189+
QLabel *mProgressLabel = nullptr;
190+
QProgressBar *mProgressBar = nullptr;
191+
QToolButton *mCancelButton = nullptr;
192+
QgsMessageBar *mMessageBar = nullptr;
193+
194+
friend class TestQgsExternalResourceWidgetWrapper;
195+
friend class TestQgsExternalStorageFileWidget;
196+
};
197+
198+
#endif // QGSEXTERNALSTORAGEFILEWIDGET_H

‎src/gui/qgsfilewidget.cpp

Lines changed: 52 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,7 @@ QgsFileWidget::QgsFileWidget( QWidget *parent )
5252
mLinkLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
5353
mLinkLabel->setTextFormat( Qt::RichText );
5454
mLinkLabel->hide(); // do not show by default
55-
mLinkEditButton = new QToolButton( this );
56-
mLinkEditButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) );
57-
connect( mLinkEditButton, &QToolButton::clicked, this, &QgsFileWidget::editLink );
58-
mLinkEditButton->hide(); // do not show by default
55+
mLayout->addWidget( mLinkLabel );
5956

6057
// otherwise, use the traditional QLineEdit subclass
6158
mLineEdit = new QgsFileDropEdit( this );
@@ -64,6 +61,12 @@ QgsFileWidget::QgsFileWidget( QWidget *parent )
6461
connect( mLineEdit, &QLineEdit::textChanged, this, &QgsFileWidget::textEdited );
6562
mLayout->addWidget( mLineEdit );
6663

64+
mLinkEditButton = new QToolButton( this );
65+
mLinkEditButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) );
66+
mLayout->addWidget( mLinkEditButton );
67+
connect( mLinkEditButton, &QToolButton::clicked, this, &QgsFileWidget::editLink );
68+
mLinkEditButton->hide(); // do not show by default
69+
6770
mFileWidgetButton = new QToolButton( this );
6871
mFileWidgetButton->setText( QChar( 0x2026 ) );
6972
mFileWidgetButton->setToolTip( tr( "Browse" ) );
@@ -157,12 +160,17 @@ void QgsFileWidget::setFileWidgetButtonVisible( bool visible )
157160
mFileWidgetButton->setVisible( visible );
158161
}
159162

163+
bool QgsFileWidget::isMultiFiles( const QString &path )
164+
{
165+
return path.contains( QStringLiteral( "\" \"" ) );
166+
}
167+
160168
void QgsFileWidget::textEdited( const QString &path )
161169
{
162170
mFilePath = path;
163171
mLinkLabel->setText( toUrl( path ) );
164172
// Show tooltip if multiple files are selected
165-
if ( path.contains( QStringLiteral( "\" \"" ) ) )
173+
if ( isMultiFiles( path ) )
166174
{
167175
mLineEdit->setToolTip( tr( "Selected files:<br><ul><li>%1</li></ul><br>" ).arg( splitFilePaths( path ).join( QLatin1String( "</li><li>" ) ) ) );
168176
}
@@ -244,39 +252,18 @@ QgsFilterLineEdit *QgsFileWidget::lineEdit()
244252

245253
void QgsFileWidget::updateLayout()
246254
{
247-
mLayout->removeWidget( mLineEdit );
248-
mLayout->removeWidget( mLinkLabel );
249-
mLayout->removeWidget( mLinkEditButton );
255+
const bool linkVisible = mUseLink && !mIsLinkEdited;
250256

257+
mLineEdit->setVisible( !linkVisible );
258+
mLinkLabel->setVisible( linkVisible );
251259
mLinkEditButton->setVisible( mUseLink && !mReadOnly );
252260

253261
mFileWidgetButton->setEnabled( !mReadOnly );
254262
mLineEdit->setEnabled( !mReadOnly );
255263

256-
if ( mUseLink && !mIsLinkEdited )
257-
{
258-
mLayout->insertWidget( 0, mLinkLabel );
259-
mLineEdit->setVisible( false );
260-
mLinkLabel->setVisible( true );
261-
262-
if ( !mReadOnly )
263-
{
264-
mLayout->insertWidget( 1, mLinkEditButton );
265-
mLinkEditButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) );
266-
}
267-
}
268-
else
269-
{
270-
mLayout->insertWidget( 0, mLineEdit );
271-
mLineEdit->setVisible( true );
272-
mLinkLabel->setVisible( false );
273-
274-
if ( mIsLinkEdited )
275-
{
276-
mLayout->insertWidget( 1, mLinkEditButton );
277-
mLinkEditButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveEdits.svg" ) ) );
278-
}
279-
}
264+
mLinkEditButton->setIcon( linkVisible && !mReadOnly ?
265+
QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) :
266+
QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveEdits.svg" ) ) );
280267
}
281268

282269
void QgsFileWidget::openFileDialog()
@@ -354,56 +341,61 @@ void QgsFileWidget::openFileDialog()
354341
return;
355342

356343
if ( mStorageMode != GetMultipleFiles )
344+
fileNames << fileName;
345+
346+
for ( int i = 0; i < fileNames.length(); i++ )
357347
{
358-
fileName = QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileName ).absoluteFilePath() ) );
359-
}
360-
else
361-
{
362-
for ( int i = 0; i < fileNames.length(); i++ )
363-
{
364-
fileNames.replace( i, QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileNames.at( i ) ).absoluteFilePath() ) ) );
365-
}
348+
fileNames.replace( i, QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileNames.at( i ) ).absoluteFilePath() ) ) );
366349
}
367350

368351
// Store the last used path:
369352
switch ( mStorageMode )
370353
{
371354
case GetFile:
372355
case SaveFile:
373-
settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileName ).absolutePath() );
356+
case GetMultipleFiles:
357+
settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileNames.first() ).absolutePath() );
374358
break;
375359
case GetDirectory:
376-
settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), fileName );
377-
break;
378-
case GetMultipleFiles:
379-
settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileNames.first( ) ).absolutePath() );
360+
settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), fileNames.first() );
380361
break;
381362
}
382363

364+
setSelectedFileNames( fileNames );
365+
}
366+
367+
void QgsFileWidget::setSelectedFileNames( QStringList fileNames )
368+
{
369+
Q_ASSERT( fileNames.count() );
370+
383371
// Handle relative Path storage
372+
for ( int i = 0; i < fileNames.length(); i++ )
373+
{
374+
fileNames.replace( i, relativePath( fileNames.at( i ), true ) );
375+
}
376+
377+
setFilePaths( fileNames );
378+
}
379+
380+
void QgsFileWidget::setFilePaths( const QStringList &filePaths )
381+
{
384382
if ( mStorageMode != GetMultipleFiles )
385383
{
386-
fileName = relativePath( fileName, true );
387-
setFilePath( fileName );
384+
setFilePath( filePaths.first() );
388385
}
389386
else
390387
{
391-
for ( int i = 0; i < fileNames.length(); i++ )
392-
{
393-
fileNames.replace( i, relativePath( fileNames.at( i ), true ) );
394-
}
395-
if ( fileNames.length() > 1 )
388+
if ( filePaths.length() > 1 )
396389
{
397-
setFilePath( QStringLiteral( "\"%1\"" ).arg( fileNames.join( QLatin1String( "\" \"" ) ) ) );
390+
setFilePath( QStringLiteral( "\"%1\"" ).arg( filePaths.join( QLatin1String( "\" \"" ) ) ) );
398391
}
399392
else
400393
{
401-
setFilePath( fileNames.first( ) );
394+
setFilePath( filePaths.first( ) );
402395
}
403396
}
404397
}
405398

406-
407399
QString QgsFileWidget::relativePath( const QString &filePath, bool removeRelative ) const
408400
{
409401
QString RelativePath;
@@ -440,6 +432,11 @@ QString QgsFileWidget::toUrl( const QString &path ) const
440432
return QgsApplication::nullRepresentation();
441433
}
442434

435+
if ( isMultiFiles( path ) )
436+
{
437+
return QStringLiteral( "<a>%1</a>" ).arg( path );
438+
}
439+
443440
QString urlStr = relativePath( path, false );
444441
QUrl url = QUrl::fromUserInput( urlStr );
445442
if ( !url.isValid() || !url.isLocalFile() )
@@ -463,7 +460,6 @@ QString QgsFileWidget::toUrl( const QString &path ) const
463460
}
464461

465462

466-
467463
///@cond PRIVATE
468464

469465

‎src/gui/qgsfilewidget.h

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,17 @@
2020
class QLabel;
2121
class QToolButton;
2222
class QVariant;
23-
class QgsFileDropEdit;
2423
class QHBoxLayout;
24+
class QgsFileDropEdit;
25+
2526
#include <QWidget>
2627
#include <QFileDialog>
2728

2829
#include "qgis_gui.h"
2930
#include "qgis_sip.h"
3031
#include "qgshighlightablelineedit.h"
3132

33+
3234
/**
3335
* \ingroup gui
3436
* \brief The QgsFileWidget class creates a widget for selecting a file or a folder.
@@ -297,8 +299,27 @@ class GUI_EXPORT QgsFileWidget : public QWidget
297299
void textEdited( const QString &path );
298300
void editLink();
299301

300-
private:
301-
void updateLayout();
302+
protected:
303+
304+
/**
305+
* Update buttons visibility
306+
*/
307+
virtual void updateLayout();
308+
309+
/**
310+
* Called whenever user select \a fileNames from dialog
311+
*/
312+
virtual void setSelectedFileNames( QStringList fileNames );
313+
314+
/**
315+
* Returns true if \a path is a multifiles
316+
*/
317+
static bool isMultiFiles( const QString &path );
318+
319+
/**
320+
* Update filePath according to \a filePaths list
321+
*/
322+
void setFilePaths( const QStringList &filePaths );
302323

303324
QString mFilePath;
304325
bool mButtonVisible = true;
@@ -328,6 +349,8 @@ class GUI_EXPORT QgsFileWidget : public QWidget
328349
QString relativePath( const QString &filePath, bool removeRelative ) const;
329350

330351
friend class TestQgsFileWidget;
352+
friend class TestQgsExternalStorageFileWidget;
353+
friend class TestQgsExternalResourceWidgetWrapper;
331354
};
332355

333356
///@cond PRIVATE

‎src/ui/editorwidgets/qgsexternalresourceconfigdlg.ui

Lines changed: 102 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<x>0</x>
99
<y>0</y>
1010
<width>481</width>
11-
<height>690</height>
11+
<height>803</height>
1212
</rect>
1313
</property>
1414
<property name="windowTitle">
@@ -46,13 +46,101 @@
4646
<rect>
4747
<x>0</x>
4848
<y>0</y>
49-
<width>481</width>
50-
<height>690</height>
49+
<width>467</width>
50+
<height>835</height>
5151
</rect>
5252
</property>
5353
<layout class="QVBoxLayout" name="verticalLayout_4">
5454
<item>
55-
<widget class="QGroupBox" name="groupBox">
55+
<widget class="QGroupBox" name="groupBox_2">
56+
<property name="title">
57+
<string>Type</string>
58+
</property>
59+
<layout class="QVBoxLayout" name="verticalLayout_5">
60+
<item>
61+
<widget class="QComboBox" name="mStorageType">
62+
<property name="toolTip">
63+
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;Way of dealing with attachment file&lt;p&gt;&quot;Select existing file&quot; allows you to pick an existing file from the file system or set an existing URL external resource.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
64+
</property>
65+
</widget>
66+
</item>
67+
</layout>
68+
</widget>
69+
</item>
70+
<item>
71+
<widget class="QGroupBox" name="mExternalStorageGroupBox">
72+
<property name="title">
73+
<string>External storage</string>
74+
</property>
75+
<layout class="QVBoxLayout" name="verticalLayout_7">
76+
<item>
77+
<layout class="QHBoxLayout" name="horizontalLayout_2">
78+
<item>
79+
<widget class="QLabel" name="label_5">
80+
<property name="text">
81+
<string>Store URL</string>
82+
</property>
83+
</widget>
84+
</item>
85+
<item>
86+
<widget class="QLineEdit" name="mStorageUrl">
87+
<property name="toolTip">
88+
<string>Url used to store file selected from the attachment widget.</string>
89+
</property>
90+
</widget>
91+
</item>
92+
<item>
93+
<widget class="QLineEdit" name="mStorageUrlExpression">
94+
<property name="toolTip">
95+
<string>Url used to store file selected from the attachment widget.</string>
96+
</property>
97+
</widget>
98+
</item>
99+
<item>
100+
<widget class="QgsPropertyOverrideButton" name="mStorageUrlPropertyOverrideButton">
101+
<property name="text">
102+
<string>…</string>
103+
</property>
104+
</widget>
105+
</item>
106+
</layout>
107+
</item>
108+
<item>
109+
<widget class="QGroupBox" name="mAuthGroupBox">
110+
<property name="title">
111+
<string>Authentication</string>
112+
</property>
113+
<layout class="QVBoxLayout" name="verticalLayout_6">
114+
<property name="leftMargin">
115+
<number>6</number>
116+
</property>
117+
<property name="topMargin">
118+
<number>6</number>
119+
</property>
120+
<property name="rightMargin">
121+
<number>6</number>
122+
</property>
123+
<property name="bottomMargin">
124+
<number>6</number>
125+
</property>
126+
<item>
127+
<widget class="QgsAuthSettingsWidget" name="mAuthSettingsProtocol" native="true">
128+
<property name="sizePolicy">
129+
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
130+
<horstretch>0</horstretch>
131+
<verstretch>0</verstretch>
132+
</sizepolicy>
133+
</property>
134+
</widget>
135+
</item>
136+
</layout>
137+
</widget>
138+
</item>
139+
</layout>
140+
</widget>
141+
</item>
142+
<item>
143+
<widget class="QGroupBox" name="mPathGroupBox">
56144
<property name="title">
57145
<string>Path</string>
58146
</property>
@@ -198,7 +286,7 @@
198286
</widget>
199287
</item>
200288
<item>
201-
<widget class="QGroupBox" name="mStorageGroupBox">
289+
<widget class="QGroupBox" name="mStorageModeGroupBox">
202290
<property name="sizePolicy">
203291
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
204292
<horstretch>0</horstretch>
@@ -478,6 +566,11 @@
478566
</layout>
479567
</widget>
480568
<customwidgets>
569+
<customwidget>
570+
<class>QgsPropertyOverrideButton</class>
571+
<extends>QToolButton</extends>
572+
<header>qgspropertyoverridebutton.h</header>
573+
</customwidget>
481574
<customwidget>
482575
<class>QgsSpinBox</class>
483576
<extends>QSpinBox</extends>
@@ -490,9 +583,10 @@
490583
<container>1</container>
491584
</customwidget>
492585
<customwidget>
493-
<class>QgsPropertyOverrideButton</class>
494-
<extends>QToolButton</extends>
495-
<header>qgspropertyoverridebutton.h</header>
586+
<class>QgsAuthSettingsWidget</class>
587+
<extends>QWidget</extends>
588+
<header>auth/qgsauthsettingswidget.h</header>
589+
<container>1</container>
496590
</customwidget>
497591
</customwidgets>
498592
<tabstops>

‎tests/src/gui/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ set(TESTS
3535
testqgsdockwidget.cpp
3636
testqgsfieldexpressionwidget.cpp
3737
testqgsfilewidget.cpp
38+
testqgsexternalstoragefilewidget.cpp
3839
testqgsfocuswatcher.cpp
3940
testqgshtmlwidgetwrapper.cpp
4041
testqgsmapcanvas.cpp

‎tests/src/gui/testqgsexternalresourcewidgetwrapper.cpp

Lines changed: 904 additions & 3 deletions
Large diffs are not rendered by default.

‎tests/src/gui/testqgsexternalstoragefilewidget.cpp

Lines changed: 804 additions & 0 deletions
Large diffs are not rendered by default.

‎tests/src/gui/testqgsfilewidget.cpp

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,10 @@ class TestQgsFileWidget: public QObject
3737
void testDroppedFiles();
3838
void testMultipleFiles();
3939
void testSplitFilePaths();
40-
4140
};
4241

4342
void TestQgsFileWidget::initTestCase()
4443
{
45-
4644
}
4745

4846
void TestQgsFileWidget::cleanupTestCase()
@@ -201,7 +199,6 @@ void TestQgsFileWidget::testMultipleFiles()
201199
}
202200

203201

204-
205202
void TestQgsFileWidget::testSplitFilePaths()
206203
{
207204
const QString path = QString( TEST_DATA_DIR + QStringLiteral( "/bug5598.shp" ) );
@@ -213,8 +210,5 @@ void TestQgsFileWidget::testSplitFilePaths()
213210
QCOMPARE( QgsFileWidget::splitFilePaths( path ), QStringList() << path );
214211
}
215212

216-
217-
218-
219213
QGSTEST_MAIN( TestQgsFileWidget )
220214
#include "testqgsfilewidget.moc"

0 commit comments

Comments
 (0)
Please sign in to comment.