Skip to content
Permalink
Browse files

Smarter default edit widgets with plugins to pick them

Now the widgets factories can give a score on how good they could handle
a widget.

Additionaly, plugins can be added to choose a widget factory in function
of an external information. One of them uses a table in PostgresQL to
allow specification of the widget type and configuration.

I took the opportunity to remove a few deprecated method in relation to
this.
  • Loading branch information
Patrick Valsecchi authored and m-kuhn committed Sep 5, 2016
1 parent a1cb2be commit 7169079f915f168afc88aa42ccd451be6c3e06a9
Showing with 950 additions and 304 deletions.
  1. +19 −1 doc/api_break.dox
  2. +1 −0 python/core/core.sip
  3. +13 −52 python/core/qgseditformconfig.sip
  4. +32 −0 python/core/qgseditorwidgetsetup.sip
  5. +13 −0 python/core/qgsfield.sip
  6. +41 −0 python/gui/editorwidgets/core/qgseditorwidgetautoconf.sip
  7. +10 −4 python/gui/editorwidgets/core/qgseditorwidgetfactory.sip
  8. +19 −0 python/gui/editorwidgets/core/qgseditorwidgetregistry.sip
  9. +1 −0 python/gui/gui.sip
  10. +6 −4 src/app/ogr/qgsvectorlayersaveasdialog.cpp
  11. +3 −3 src/app/qgisapp.cpp
  12. +3 −4 src/app/qgsattributetabledialog.cpp
  13. +8 −6 src/app/qgsfieldsproperties.cpp
  14. +11 −9 src/app/qgsidentifyresultsdialog.cpp
  15. +2 −1 src/app/qgsidentifyresultsdialog.h
  16. +4 −3 src/app/qgsmergeattributesdialog.cpp
  17. +30 −62 src/core/qgseditformconfig.cpp
  18. +15 −52 src/core/qgseditformconfig.h
  19. +54 −0 src/core/qgseditorwidgetsetup.h
  20. +10 −0 src/core/qgsfield.cpp
  21. +16 −0 src/core/qgsfield.h
  22. +2 −0 src/core/qgsfield_p.h
  23. +1 −1 src/core/qgsvectorlayereditpassthrough.cpp
  24. +4 −3 src/core/qgsvectorlayerundocommand.cpp
  25. +1 −0 src/core/qgsvectorlayerundocommand.h
  26. +1 −0 src/gui/CMakeLists.txt
  27. +1 −3 src/gui/attributetable/qgsattributetabledelegate.cpp
  28. +4 −4 src/gui/attributetable/qgsattributetablemodel.cpp
  29. +2 −1 src/gui/attributetable/qgsdualview.cpp
  30. +112 −0 src/gui/editorwidgets/core/qgseditorwidgetautoconf.cpp
  31. +88 −0 src/gui/editorwidgets/core/qgseditorwidgetautoconf.h
  32. +2 −2 src/gui/editorwidgets/core/qgseditorwidgetfactory.cpp
  33. +12 −6 src/gui/editorwidgets/core/qgseditorwidgetfactory.h
  34. +28 −6 src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp
  35. +37 −0 src/gui/editorwidgets/core/qgseditorwidgetregistry.h
  36. +6 −0 src/gui/editorwidgets/qgscheckboxwidgetfactory.cpp
  37. +1 −0 src/gui/editorwidgets/qgscheckboxwidgetfactory.h
  38. +14 −0 src/gui/editorwidgets/qgscolorwidgetfactory.cpp
  39. +1 −0 src/gui/editorwidgets/qgscolorwidgetfactory.h
  40. +15 −0 src/gui/editorwidgets/qgsdatetimeeditfactory.cpp
  41. +1 −0 src/gui/editorwidgets/qgsdatetimeeditfactory.h
  42. +3 −3 src/gui/editorwidgets/qgsenumerationwidgetfactory.cpp
  43. +1 −2 src/gui/editorwidgets/qgsenumerationwidgetfactory.h
  44. +3 −3 src/gui/editorwidgets/qgsexternalresourcewidgetfactory.cpp
  45. +1 −3 src/gui/editorwidgets/qgsexternalresourcewidgetfactory.h
  46. +2 −11 src/gui/editorwidgets/qgsrangewidgetfactory.cpp
  47. +1 −1 src/gui/editorwidgets/qgsrangewidgetfactory.h
  48. +7 −0 src/gui/editorwidgets/qgstexteditwidgetfactory.cpp
  49. +1 −2 src/gui/editorwidgets/qgstexteditwidgetfactory.h
  50. +6 −0 src/gui/editorwidgets/qgsuuidwidgetfactory.cpp
  51. +1 −0 src/gui/editorwidgets/qgsuuidwidgetfactory.h
  52. +1 −4 src/gui/qgsattributeeditor.cpp
  53. +15 −18 src/gui/qgsattributeform.cpp
  54. +0 −1 src/providers/postgres/CMakeLists.txt
  55. +2 −0 src/providers/postgres/qgspostgresconn.cpp
  56. +68 −22 src/providers/postgres/qgspostgresprovider.cpp
  57. +4 −0 src/providers/postgres/qgspostgresprovider.h
  58. +4 −3 src/server/qgsserverprojectparser.cpp
  59. +4 −4 src/server/qgswmsserver.cpp
  60. +13 −0 tests/src/core/testqgsfield.cpp
  61. +1 −0 tests/src/gui/CMakeLists.txt
  62. +1 −0 tests/src/gui/testqgsattributeform.cpp
  63. +119 −0 tests/src/gui/testqgseditorwidgetregistry.cpp
  64. +23 −0 tests/src/python/test_provider_postgres.py
  65. +1 −0 tests/testdata/provider/testdata_pg.sh
  66. +24 −0 tests/testdata/provider/testdata_pg.sql
@@ -506,7 +506,10 @@ place of a null pointer.</li>
\subsection qgis_api_break_3_0_QgsEditFormConfig QgsEditFormConfig

<ul>
<li>Does no longer inherit QObject
<li>Does no longer inherit QObject</li>
<li>widgetType() and widgetConfig() now reflect only the user configured values.
QgsEditorWidgetRegistry::instance()->findBest() must be used instead.</li>
<li>widgetType(), widgetConfig(), setWidgetType(), setWidgetConfig() and removeWidgetConfig() now only take a string as first parameter. Access by index has been removed.</li>
</ul>

\subsection qgis_api_break_3_0_QgsExpression QgsExpression
@@ -612,6 +615,13 @@ and the new ramp can be retrieved after executing the dialog by calling ramp().<
plugins calling this method will need to be updated.</li>
</ul>

\subsection qgis_api_break_3_0_QgsEditorWidgetRegistry QgsEditorWidgetRegistry

<ul>
<li>The signature of isFieldSupported() has been changed to return an unsigned (how good it supports the given field)
and to const-correct it.</li>
</ul>

\subsection qgis_api_break_3_0_QgsGroupWMSDataDialog QgsGroupWMSDataDialog

<ul>
@@ -847,6 +857,14 @@ plugins calling this method will need to be updated.</li>

<ul>
<li>setMapRenderer() has been removed. Use setMapSettings() instead.</li>
<li>excludeAttributesWMS() and setExcludeAttributesWMS() have been renamed to excludeAttributesWms() and
setExcludeAttributesWms()</li>
<li>excludeAttributesWFS() and setExcludeAttributesWFS() have been renamed to excludeAttributesWfs() and
setExcludeAttributesWfs()</li>
<li>editorWidgetV2() and editorWidgetV2Config() have been removed and QgsEditorWidgetRegistry::instance()->findBest() must be used instead.</li>
<li>setEditorWidgetV2(), setEditorWidgetV2Config() have been removed and their equivalent in editFormConfig() must be used instead.</li>
<li>setCheckedState() is removed. Use editFormConfig()->setWidgetConfig()` instead.</li>
<li>valueMap(), valueRelation(), dateFormat(), widgetSize() have been removed. Use QgsEditorWidgetRegistry::instance()->findBest().config() instead.</li>
</ul>

\subsection qgis_api_break_3_0_QgsRenderContext QgsRenderContext
@@ -48,6 +48,7 @@
%Include qgsdistancearea.sip
%Include qgseditformconfig.sip
%Include qgseditorwidgetconfig.sip
%Include qgseditorwidgetsetup.sip
%Include qgserror.sip
%Include qgsexpression.sip
%Include qgsexpressioncontext.sip
@@ -137,19 +137,10 @@ class QgsEditFormConfig
* <li>WebView (QgsWebViewWidgetWrapper)</li>
* </ul>
*
* @param fieldIdx Index of the field
* @param fieldName The name of the field
* @param widgetType Type id of the editor widget to use
*/
void setWidgetType( int fieldIdx, const QString& widgetType );

/**
* Get the id for the editor widget used to represent the field at the given index
*
* @param fieldIdx The index of the field
*
* @return The id for the editor widget or a NULL string if not applicable
*/
QString widgetType( int fieldIdx ) const;
void setWidgetType( const QString& widgetName, const QString& widgetType );

/**
* Get the id for the editor widget used to represent the field at the given index
@@ -160,23 +151,6 @@ class QgsEditFormConfig
*/
QString widgetType( const QString& fieldName ) const;

/**
* Set the editor widget config for a field.
*
* Python: Will accept a map.
*
* Example:
* \code{.py}
* layer.setWidgetConfig( 1, { 'Layer': 'otherlayerid_1234', 'Key': 'Keyfield', 'Value': 'ValueField' } )
* \endcode
*
* @param attrIdx Index of the field
* @param config The config to set for this field
*
* @see setWidgetType() for a list of widgets and choose the widget to see the available options.
*/
void setWidgetConfig( int attrIdx, const QgsEditorWidgetConfig& config );

/**
* Set the editor widget config for a widget.
*
@@ -185,50 +159,32 @@ class QgsEditFormConfig
* layer.setWidgetConfig( 'relation_id', { 'nm-rel': 'other_relation' } )
* \endcode
*
* @param widgetName The name of the widget or field to configure
* @param fieldName The name of the field to configure
* @param config The config to set for this field
*
* @see setWidgetType() for a list of widgets and choose the widget to see the available options.
*
* @note not available in python bindings
*/
// void setWidgetConfig( const QString& widgetName, const QgsEditorWidgetConfig& config );

/**
* Get the configuration for the editor widget used to represent the field at the given index
*
* @param fieldIdx The index of the field
*
* @return The configuration for the editor widget or an empty config if the field does not exist
*/
QgsEditorWidgetConfig widgetConfig( int fieldIdx ) const;
void setWidgetConfig( const QString& fieldName, const QgsEditorWidgetConfig& config );

/**
* Get the configuration for the editor widget used to represent the field with the given name
*
* @param widgetName The name of the widget. This can be a field name or the name of an additional widget.
* @param fieldName The name of the field.
*
* @return The configuration for the editor widget or an empty config if the field does not exist
*/
QgsEditorWidgetConfig widgetConfig( const QString& widgetName ) const;

/**
* Remove the configuration for the editor widget used to represent the field at the given index
*
* @param fieldIdx The index of the field
*
* @return true if successful, false if the field does not exist
*/
bool removeWidgetConfig( int fieldIdx );
QgsEditorWidgetConfig widgetConfig( const QString& fieldName ) const;

/**
* Remove the configuration for the editor widget used to represent the field with the given name
*
* @param widgetName The name of the widget. This can be a field name or the name of an additional widget.
* @param fieldName The name of the field.
*
* @return true if successful, false if the field does not exist
*/
bool removeWidgetConfig( const QString& widgetName );
bool removeWidgetConfig( const QString& fieldName );

/**
* This returns true if the field is manually set to read only or if the field
@@ -372,4 +328,9 @@ class QgsEditFormConfig
* Deserialize drag and drop designer elements.
*/
QgsAttributeEditorElement* attributeEditorElementFromDomElement( QDomElement &elem, QgsAttributeEditorElement* parent );

/**
* Parse the XML for the config of one editor widget.
*/
static QgsEditorWidgetConfig parseEditorWidgetConfig( const QDomElement& cfgElem );
};
@@ -0,0 +1,32 @@
/** \ingroup core
* Holder for the widget type and its configuration for a field.
*/
class QgsEditorWidgetSetup
{
%TypeHeaderCode
#include <qgseditorwidgetsetup.h>
%End
public:
/**
* Constructor
*/
QgsEditorWidgetSetup( const QString& type, const QgsEditorWidgetConfig& config );
QgsEditorWidgetSetup();

/**
* @return the widget type to use
*/
QString type() const;

/**
* @return the widget configuration to used
*/
QgsEditorWidgetConfig config() const;

/**
* @return true if there is no widget configured.
*/
bool isNull() const;
};


@@ -220,6 +220,19 @@ class QgsField
//! Allows direct construction of QVariants from fields.
operator QVariant() const;

/**
* Set the editor widget setup for the field.
*
* @param v The value to set
*/
void setEditorWidgetSetup( const QgsEditorWidgetSetup& v );

/**
* Get the editor widget setup for the field.
*
* @return the value
*/
const QgsEditorWidgetSetup& editorWidgetSetup() const;
}; // class QgsField


@@ -0,0 +1,41 @@
/***************************************************************************
qgseditorwidgetautoconf.sip
---------------------
begin : July 2016
copyright : (C) 2016 by Patrick Valsecchi
email : patrick.valsecchi at camptocamp.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. *
* *
***************************************************************************/

/**
* Base class for plugins allowing to pick automatically a widget type for editing fields.
*
* @note added in QGIS 3.0
*/
class QgsEditorWidgetAutoConfPlugin
{
%TypeHeaderCode
#include <qgseditorwidgetautoconf.h>
%End

public:
/**
* Typical scores are:
* * 0: no matching type found.
* * 10: a widget has been guessed from the type of field.
* * 20: a widget has been determined from an external configuration (for example a database table)
*
* @param vl The vector layer for which this widget will be created
* @param fieldName The field name on the specified layer for which this widget will be created
* @param score Where the score is returned (default to 0)
*
* @return and integer value rating how good is the setup provided by this plugin.
*/
virtual QgsEditorWidgetSetup editorWidgetSetup( const QgsVectorLayer* vl, const QString& fieldName, int& score /Out/ ) const = 0;
};
@@ -170,17 +170,23 @@ class QgsEditorWidgetFactory
*/
virtual QgsEditorWidgetConfig readConfig( const QDomElement& configElement, QgsVectorLayer* layer, int fieldIdx );

private:
/**
* This method allows disabling this editor widget type for a certain field.
* By default, it returns true for all fields.
* By default, it returns 5 for every fields.
* Reimplement this if you only support certain fields.
*
* Typical return values are:
* * 0: not supported
* * 5: maybe support (for example, Datetime support strings depending on their content)
* * 10: basic support (this is what returns TextEdit for example, since it supports everything in a crude way)
* * 20: specialised support
*
* @param vl
* @param fieldIdx
* @return True if the field is supported.
* @return 0 if the field is not supported or a bigger number if it can (the widget with the biggest number will be
* taken by default). The default implementation returns 5..
*
* @see supportsField( QgsVectorLayer* vl, fieldIdx )
*/
virtual bool isFieldSupported( QgsVectorLayer* vl, int fieldIdx );
virtual unsigned int fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const;
};
@@ -13,6 +13,7 @@
* *
***************************************************************************/


/**
* This class manages all known edit widget factories
*/
@@ -43,6 +44,17 @@ class QgsEditorWidgetRegistry : QObject
*/
static void initEditors( QgsMapCanvas* mapCanvas = 0, QgsMessageBar* messageBar = 0 );

/**
* Find the best editor widget and its configuration for a given field.
*
* @param vl The vector layer for which this widget will be created
* @param fieldIdx The field index on the specified layer for which this widget will be created
*
* @return The id of the widget type to use and its config
*/
QgsEditorWidgetSetup findBest( const QgsVectorLayer* vl, const QString& fieldName ) const;


/**
* Create an attribute editor widget wrapper of a given type for a given field.
* The editor may be NULL if you want the widget wrapper to create a default widget.
@@ -116,4 +128,11 @@ class QgsEditorWidgetRegistry : QObject
* @return true, if successful, false, if the widgetId is already in use or widgetFactory is NULL
*/
bool registerWidget( const QString& widgetId, QgsEditorWidgetFactory* widgetFactory /Transfer/ );

/**
* Register a new auto-conf plugin.
*
* @param plugin The plugin (ownership is transfered)
*/
void registerAutoConfPlugin( QgsEditorWidgetAutoConfPlugin* plugin );
};
@@ -258,6 +258,7 @@
%Include effects/qgspainteffectwidget.sip

%Include editorwidgets/core/qgseditorconfigwidget.sip
%Include editorwidgets/core/qgseditorwidgetautoconf.sip
%Include editorwidgets/core/qgseditorwidgetfactory.sip
%Include editorwidgets/core/qgseditorwidgetregistry.sip
%Include editorwidgets/core/qgseditorwidgetwrapper.sip
@@ -249,8 +249,9 @@ void QgsVectorLayerSaveAsDialog::on_mFormatComboBox_currentIndexChanged( int idx
bool foundFieldThatCanBeExportedAsDisplayedValue = false;
for ( int i = 0; i < mLayer->fields().size(); ++i )
{
if ( mLayer->editFormConfig().widgetType( i ) != "TextEdit" &&
QgsEditorWidgetRegistry::instance()->factory( mLayer->editFormConfig().widgetType( i ) ) )
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( mLayer, mLayer->fields()[i].name() );
if ( setup.type() != "TextEdit" &&
QgsEditorWidgetRegistry::instance()->factory( setup.type() ) )
{
foundFieldThatCanBeExportedAsDisplayedValue = true;
break;
@@ -285,10 +286,11 @@ void QgsVectorLayerSaveAsDialog::on_mFormatComboBox_currentIndexChanged( int idx

if ( foundFieldThatCanBeExportedAsDisplayedValue )
{
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( mLayer, mLayer->fields()[i].name() );
QgsEditorWidgetFactory *factory = nullptr;
if ( flags == Qt::ItemIsEnabled &&
mLayer->editFormConfig().widgetType( i ) != "TextEdit" &&
( factory = QgsEditorWidgetRegistry::instance()->factory( mLayer->editFormConfig().widgetType( i ) ) ) )
setup.type() != "TextEdit" &&
( factory = QgsEditorWidgetRegistry::instance()->factory( setup.type() ) ) )
{
item = new QTableWidgetItem( tr( "Use %1" ).arg( factory->name() ) );
item->setFlags(( selectAllFields ) ? ( Qt::ItemIsEnabled | Qt::ItemIsUserCheckable ) : Qt::ItemIsUserCheckable );
@@ -6149,11 +6149,11 @@ QVariant QgisAppFieldValueConverter::convert( int idx, const QVariant& value )
{
return value;
}
QgsEditorWidgetFactory *factory = QgsEditorWidgetRegistry::instance()->factory( mLayer->editFormConfig().widgetType( idx ) );
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( mLayer, mLayer->fields().field( idx ).name() );
QgsEditorWidgetFactory *factory = QgsEditorWidgetRegistry::instance()->factory( setup.type() );
if ( factory )
{
QgsEditorWidgetConfig cfg( mLayer->editFormConfig().widgetConfig( idx ) );
return QVariant( factory->representValue( mLayer, idx, cfg, QVariant(), value ) );
return QVariant( factory->representValue( mLayer, idx, setup.config(), QVariant(), value ) );
}
return value;
}
@@ -391,7 +391,7 @@ void QgsAttributeTableDialog::columnBoxInit()
if ( idx < 0 )
continue;

if ( mLayer->editFormConfig().widgetType( idx ) != "Hidden" )
if ( QgsEditorWidgetRegistry::instance()->findBest( mLayer, field.name() ).type() != "Hidden" )
{
QIcon icon = mLayer->fields().iconForField( idx );
QString alias = mLayer->attributeDisplayName( idx );
@@ -527,10 +527,9 @@ void QgsAttributeTableDialog::filterColumnChanged( QObject* filterAction )
int fldIdx = mLayer->fieldNameIndex( fieldName );
if ( fldIdx < 0 )
return;
const QString widgetType = mLayer->editFormConfig().widgetType( fldIdx );
const QgsEditorWidgetConfig widgetConfig = mLayer->editFormConfig().widgetConfig( fldIdx );
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( mLayer, fieldName );
mCurrentSearchWidgetWrapper = QgsEditorWidgetRegistry::instance()->
createSearchWidget( widgetType, mLayer, fldIdx, widgetConfig, mFilterContainer, mEditorContext );
createSearchWidget( setup.type(), mLayer, fldIdx, setup.config(), mFilterContainer, mEditorContext );
if ( mCurrentSearchWidgetWrapper->applyDirectly() )
{
connect( mCurrentSearchWidgetWrapper, SIGNAL( expressionChanged( QString ) ), SLOT( filterQueryChanged( QString ) ) );

0 comments on commit 7169079

Please sign in to comment.
You can’t perform that action at this time.