Skip to content

Commit 1869929

Browse files
committed
[FEATURE ] allow to use URLs for attribute forms
1 parent d06043f commit 1869929

File tree

9 files changed

+137
-29
lines changed

9 files changed

+137
-29
lines changed

python/core/qgseditformconfig.sip.in

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ Constructor for TabData
6464
CodeSourceEnvironment
6565
};
6666

67+
enum FormPath
68+
{
69+
Original,
70+
LocalCopy
71+
};
72+
6773
QgsEditFormConfig( const QgsEditFormConfig &o );
6874
%Docstring
6975
Copy constructor
@@ -109,17 +115,21 @@ Get the active layout style for the attribute editor for this layer
109115
Set the active layout style for the attribute editor for this layer
110116
%End
111117

112-
QString uiForm() const;
118+
QString uiForm( FormPath path = LocalCopy ) const;
113119
%Docstring
114-
Get path to the .ui form. Only meaningful with EditorLayout.UiFileLayout.
120+
Get path to the .ui form. Only meaningful with EditorLayout.UiFileLayout
121+
If the form is from a URL and ``path`` is Original, the original URL
122+
of the UI form is returned instead of the local copy.
115123
%End
116124

117-
void setUiForm( const QString &ui );
125+
bool setUiForm( const QString &ui, QString *errMsg /Out/ = 0 );
118126
%Docstring
119127
Set path to the .ui form.
120-
When a string is provided, the layout style will be set to EditorLayout.UiFileLayout,
128+
When a string is provided in ``ui``, the layout style will be set to EditorLayout.UiFileLayout,
121129
if an empty or a null string is provided, the layout style will be set to
122130
EditorLayout.GeneratedLayout.
131+
If ``ui`` is a URL, a local copy of the file will be made and will be used to create the forms
132+
``context`` is provided to save error messages
123133
%End
124134

125135
bool setWidgetConfig( const QString &widgetName, const QVariantMap &config );

python/core/qgsproject.sip.in

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,16 @@ provider.
930930
Returns the current auxiliary storage.
931931

932932
.. versionadded:: 3.0
933+
%End
934+
935+
void addUiFormLocalCopy( QTemporaryFile *file );
936+
%Docstring
937+
Save a pointer to temporary file to keep local copy
938+
available of downloaded UI forms during project's life
939+
940+
:param file: the pointer to the temporary file
941+
942+
.. versionadded:: 3.2
933943
%End
934944

935945
const QgsProjectMetadata &metadata() const;

src/app/qgsattributesformproperties.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "qgsattributetypedialog.h"
1818
#include "qgsattributerelationedit.h"
1919
#include "qgsattributesforminitcode.h"
20+
#include "qgisapp.h"
2021

2122
QgsAttributesFormProperties::QgsAttributesFormProperties( QgsVectorLayer *layer, QWidget *parent )
2223
: QWidget( parent )
@@ -168,7 +169,7 @@ void QgsAttributesFormProperties::initLayoutConfig()
168169
mEditorLayoutComboBox_currentIndexChanged( mEditorLayoutComboBox->currentIndex() );
169170

170171
QgsEditFormConfig cfg = mLayer->editFormConfig();
171-
mEditFormLineEdit->setText( cfg.uiForm() );
172+
mEditFormLineEdit->setText( cfg.uiForm( QgsEditFormConfig::Original ) );
172173
}
173174

174175
void QgsAttributesFormProperties::initInitPython()
@@ -683,7 +684,9 @@ void QgsAttributesFormProperties::apply()
683684
editFormConfig.addTab( createAttributeEditorWidget( tabItem, nullptr, false ) );
684685
}
685686

686-
editFormConfig.setUiForm( mEditFormLineEdit->text() );
687+
QString *errMsg = new QString();
688+
if ( !editFormConfig.setUiForm( mEditFormLineEdit->text(), errMsg ) )
689+
QgisApp::instance()->messageBar()->pushMessage( *errMsg, Qgis::Warning );
687690

688691
editFormConfig.setLayout( ( QgsEditFormConfig::EditorLayout ) mEditorLayoutComboBox->currentIndex() );
689692

src/core/qgseditformconfig.cpp

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
***************************************************************************/
1515
#include "qgseditformconfig_p.h"
1616
#include "qgseditformconfig.h"
17+
#include "qgsnetworkcontentfetcher.h"
1718
#include "qgspathresolver.h"
1819
#include "qgsproject.h"
1920
#include "qgsreadwritecontext.h"
@@ -146,22 +147,75 @@ void QgsEditFormConfig::setLayout( QgsEditFormConfig::EditorLayout editorLayout
146147
d->mConfiguredRootContainer = true;
147148
}
148149

149-
QString QgsEditFormConfig::uiForm() const
150+
QString QgsEditFormConfig::uiForm( FormPath path ) const
150151
{
151-
return d->mUiFormPath;
152+
if ( path == Original && !d->mUiFormUrl.isEmpty() )
153+
return d->mUiFormUrl;
154+
else
155+
return d->mUiFormPath;
152156
}
153157

154-
void QgsEditFormConfig::setUiForm( const QString &ui )
158+
bool QgsEditFormConfig::setUiForm( const QString &ui, QString *errMsg )
155159
{
156-
if ( ui.isEmpty() || ui.isNull() )
160+
bool success = false;
161+
162+
if ( !ui.isEmpty() && ui == d->mUiFormUrl && !d->mUiFormPath.isEmpty() )
163+
{
164+
// do not download again if URL did not change and was correctly loaded before
165+
return success;
166+
}
167+
168+
// if the ui points to a URL make a local copy
169+
QString formPath = ui;
170+
QString formUrl = QString();
171+
if ( !ui.isEmpty() && !QUrl::fromUserInput( ui ).isLocalFile() )
172+
{
173+
formPath = QString();
174+
formUrl = ui;
175+
176+
QgsNetworkContentFetcher fetcher;
177+
QEventLoop loop;
178+
QObject::connect( &fetcher, &QgsNetworkContentFetcher::finished, &loop, &QEventLoop::quit );
179+
fetcher.fetchContent( QUrl( ui ) );
180+
181+
//wait until form is fetched
182+
loop.exec( QEventLoop::ExcludeUserInputEvents );
183+
184+
QNetworkReply *reply = fetcher.reply();
185+
if ( reply )
186+
{
187+
QTemporaryFile *localFile = new QTemporaryFile( QStringLiteral( "XXXXXX.ui" ) );
188+
if ( localFile->open() )
189+
{
190+
localFile->write( reply->readAll() );
191+
localFile->close();
192+
success = true;
193+
QgsProject::instance()->addUiFormLocalCopy( localFile );
194+
formPath = localFile->fileName();
195+
}
196+
}
197+
if ( !success && errMsg )
198+
{
199+
*errMsg = QString( "Could not load UI from %1" ).arg( ui );
200+
}
201+
}
202+
else
203+
{
204+
success = true;
205+
}
206+
207+
if ( formPath.isEmpty() )
157208
{
158209
setLayout( GeneratedLayout );
159210
}
160211
else
161212
{
162213
setLayout( UiFileLayout );
163214
}
164-
d->mUiFormPath = ui;
215+
d->mUiFormPath = formPath;
216+
d->mUiFormUrl = formUrl;
217+
218+
return success;
165219
}
166220

167221
bool QgsEditFormConfig::readOnly( int idx ) const
@@ -268,7 +322,9 @@ void QgsEditFormConfig::readXml( const QDomNode &node, QgsReadWriteContext &cont
268322
if ( !editFormNode.isNull() )
269323
{
270324
QDomElement e = editFormNode.toElement();
271-
d->mUiFormPath = context.pathResolver().readPath( e.text() );
325+
QString *errMsg = new QString();
326+
if ( !setUiForm( context.pathResolver().readPath( e.text() ), errMsg ) )
327+
context.pushMessage( *errMsg, Qgis::Warning );
272328
}
273329

274330
QDomNode editFormInitNode = node.namedItem( QStringLiteral( "editforminit" ) );
@@ -396,7 +452,7 @@ void QgsEditFormConfig::writeXml( QDomNode &node, const QgsReadWriteContext &con
396452
QDomDocument doc( node.ownerDocument() );
397453

398454
QDomElement efField = doc.createElement( QStringLiteral( "editform" ) );
399-
QDomText efText = doc.createTextNode( context.pathResolver().writePath( uiForm() ) );
455+
QDomText efText = doc.createTextNode( context.pathResolver().writePath( uiForm( Original ) ) );
400456
efField.appendChild( efText );
401457
node.appendChild( efField );
402458

src/core/qgseditformconfig.h

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
#include <QDomDocument>
2626

2727
#include "qgsattributeeditorelement.h"
28+
#include "qgsreadwritecontext.h"
2829

29-
class QgsReadWriteContext;
3030
class QgsRelationManager;
3131
class QgsEditFormConfigPrivate;
3232

@@ -93,6 +93,15 @@ class CORE_EXPORT QgsEditFormConfig
9393
CodeSourceEnvironment = 3 //!< Use the Python code available in the Python environment
9494
};
9595

96+
/**
97+
* The FormPath enum determins the path of the custom UI form
98+
*/
99+
enum FormPath
100+
{
101+
Original, //!< User entered directory or URL
102+
LocalCopy //!< If the Original is an URL, this is for the local copy of the file
103+
};
104+
96105
/**
97106
* Copy constructor
98107
*
@@ -135,16 +144,22 @@ class CORE_EXPORT QgsEditFormConfig
135144
//! Set the active layout style for the attribute editor for this layer
136145
void setLayout( EditorLayout editorLayout );
137146

138-
//! Get path to the .ui form. Only meaningful with EditorLayout::UiFileLayout.
139-
QString uiForm() const;
147+
/**
148+
* \brief Get path to the .ui form. Only meaningful with EditorLayout::UiFileLayout
149+
* If the form is from a URL and \a path is Original, the original URL
150+
* of the UI form is returned instead of the local copy.
151+
*/
152+
QString uiForm( FormPath path = LocalCopy ) const;
140153

141154
/**
142155
* Set path to the .ui form.
143-
* When a string is provided, the layout style will be set to EditorLayout::UiFileLayout,
156+
* When a string is provided in \a ui, the layout style will be set to EditorLayout::UiFileLayout,
144157
* if an empty or a null string is provided, the layout style will be set to
145158
* EditorLayout::GeneratedLayout.
159+
* If \a ui is a URL, a local copy of the file will be made and will be used to create the forms
160+
* \a context is provided to save error messages
146161
*/
147-
void setUiForm( const QString &ui );
162+
bool setUiForm( const QString &ui, QString *errMsg SIP_OUT = nullptr );
148163

149164
/**
150165
* Set the editor widget config for a widget which is not for a simple field.

src/core/qgseditformconfig_p.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,10 @@ class QgsEditFormConfigPrivate : public QSharedData
6565
//! Defines the default layout to use for the attribute editor (Drag and drop, UI File, Generated)
6666
QgsEditFormConfig::EditorLayout mEditorLayout = QgsEditFormConfig::GeneratedLayout;
6767

68-
//! Init form instance
68+
//! Path to the UI form
6969
QString mUiFormPath;
70+
//! URL of the UI form if taken from the web
71+
QString mUiFormUrl;
7072
//! Name of the Python form init function
7173
QString mInitFunction;
7274
//! Path of the Python external file to be loaded

src/core/qgsproject.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2687,3 +2687,8 @@ void QgsProject::setRequiredLayers( const QSet<QgsMapLayer *> &layers )
26872687
}
26882688
writeEntry( QStringLiteral( "RequiredLayers" ), QStringLiteral( "Layers" ), layerIds );
26892689
}
2690+
2691+
void QgsProject::addUiFormLocalCopy( QTemporaryFile *file )
2692+
{
2693+
mUiFormLocalCopies.append( file );
2694+
}

src/core/qgsproject.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1351,6 +1351,9 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
13511351

13521352
QgsCoordinateTransformContext mTransformContext;
13531353

1354+
// local temporary copies of downloaded UI form files
1355+
QList<QPointer<QTemporaryFile>> mUiFormLocalCopies;
1356+
13541357
QgsProjectMetadata mMetadata;
13551358

13561359
friend class QgsProjectDirtyBlocker;

src/gui/qgsattributeform.cpp

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,23 +1117,27 @@ void QgsAttributeForm::init()
11171117
if ( mContext.allowCustomUi() && mLayer->editFormConfig().layout() == QgsEditFormConfig::UiFileLayout &&
11181118
!mLayer->editFormConfig().uiForm().isEmpty() )
11191119
{
1120-
QFile file( mLayer->editFormConfig().uiForm() );
1120+
QgsDebugMsg( QString( "loading form: %1" ).arg( mLayer->editFormConfig().uiForm() ) );
1121+
QFile file( mLayer->editFormConfig().uiForm( QgsEditFormConfig::LocalCopy ) );
11211122

11221123
if ( file.open( QFile::ReadOnly ) )
11231124
{
11241125
QUiLoader loader;
11251126

1126-
QFileInfo fi( mLayer->editFormConfig().uiForm() );
1127+
QFileInfo fi( file );
11271128
loader.setWorkingDirectory( fi.dir() );
11281129
formWidget = loader.load( &file, this );
1129-
formWidget->setWindowFlags( Qt::Widget );
1130-
layout->addWidget( formWidget );
1131-
formWidget->show();
1132-
file.close();
1133-
mButtonBox = findChild<QDialogButtonBox *>();
1134-
createWrappers();
1135-
1136-
formWidget->installEventFilter( this );
1130+
if ( formWidget )
1131+
{
1132+
formWidget->setWindowFlags( Qt::Widget );
1133+
layout->addWidget( formWidget );
1134+
formWidget->show();
1135+
file.close();
1136+
mButtonBox = findChild<QDialogButtonBox *>();
1137+
createWrappers();
1138+
1139+
formWidget->installEventFilter( this );
1140+
}
11371141
}
11381142
}
11391143

0 commit comments

Comments
 (0)