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
Patrick Valsecchi committed Aug 29, 2016
1 parent 235204f commit 24bde35ce67a0996e2673afa450fc1feb0cd6f53
Showing with 909 additions and 234 deletions.
  1. +17 −0 doc/api_break.dox
  2. +9 −39 python/core/qgseditformconfig.sip
  3. +45 −0 python/core/qgsfield.sip
  4. +41 −0 python/gui/editorwidgets/core/qgseditorwidgetautoconf.sip
  5. +10 −4 python/gui/editorwidgets/core/qgseditorwidgetfactory.sip
  6. +19 −0 python/gui/editorwidgets/core/qgseditorwidgetregistry.sip
  7. +1 −0 python/gui/gui.sip
  8. +6 −4 src/app/ogr/qgsvectorlayersaveasdialog.cpp
  9. +3 −3 src/app/qgisapp.cpp
  10. +3 −4 src/app/qgsattributetabledialog.cpp
  11. +5 −4 src/app/qgsfieldsproperties.cpp
  12. +11 −9 src/app/qgsidentifyresultsdialog.cpp
  13. +2 −1 src/app/qgsidentifyresultsdialog.h
  14. +4 −3 src/app/qgsmergeattributesdialog.cpp
  15. +28 −41 src/core/qgseditformconfig.cpp
  16. +13 −24 src/core/qgseditformconfig.h
  17. +10 −0 src/core/qgsfield.cpp
  18. +50 −0 src/core/qgsfield.h
  19. +2 −0 src/core/qgsfield_p.h
  20. +1 −1 src/core/qgsvectorlayerundocommand.cpp
  21. +1 −0 src/gui/CMakeLists.txt
  22. +1 −3 src/gui/attributetable/qgsattributetabledelegate.cpp
  23. +4 −4 src/gui/attributetable/qgsattributetablemodel.cpp
  24. +2 −1 src/gui/attributetable/qgsdualview.cpp
  25. +112 −0 src/gui/editorwidgets/core/qgseditorwidgetautoconf.cpp
  26. +88 −0 src/gui/editorwidgets/core/qgseditorwidgetautoconf.h
  27. +2 −2 src/gui/editorwidgets/core/qgseditorwidgetfactory.cpp
  28. +12 −6 src/gui/editorwidgets/core/qgseditorwidgetfactory.h
  29. +26 −4 src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp
  30. +37 −0 src/gui/editorwidgets/core/qgseditorwidgetregistry.h
  31. +6 −0 src/gui/editorwidgets/qgscheckboxwidgetfactory.cpp
  32. +1 −0 src/gui/editorwidgets/qgscheckboxwidgetfactory.h
  33. +14 −0 src/gui/editorwidgets/qgscolorwidgetfactory.cpp
  34. +1 −0 src/gui/editorwidgets/qgscolorwidgetfactory.h
  35. +15 −0 src/gui/editorwidgets/qgsdatetimeeditfactory.cpp
  36. +1 −0 src/gui/editorwidgets/qgsdatetimeeditfactory.h
  37. +3 −3 src/gui/editorwidgets/qgsenumerationwidgetfactory.cpp
  38. +1 −2 src/gui/editorwidgets/qgsenumerationwidgetfactory.h
  39. +3 −3 src/gui/editorwidgets/qgsexternalresourcewidgetfactory.cpp
  40. +1 −3 src/gui/editorwidgets/qgsexternalresourcewidgetfactory.h
  41. +2 −11 src/gui/editorwidgets/qgsrangewidgetfactory.cpp
  42. +1 −1 src/gui/editorwidgets/qgsrangewidgetfactory.h
  43. +7 −0 src/gui/editorwidgets/qgstexteditwidgetfactory.cpp
  44. +1 −2 src/gui/editorwidgets/qgstexteditwidgetfactory.h
  45. +6 −0 src/gui/editorwidgets/qgsuuidwidgetfactory.cpp
  46. +1 −0 src/gui/editorwidgets/qgsuuidwidgetfactory.h
  47. +1 −4 src/gui/qgsattributeeditor.cpp
  48. +15 −18 src/gui/qgsattributeform.cpp
  49. +0 −1 src/providers/postgres/CMakeLists.txt
  50. +2 −0 src/providers/postgres/qgspostgresconn.cpp
  51. +68 −22 src/providers/postgres/qgspostgresprovider.cpp
  52. +4 −0 src/providers/postgres/qgspostgresprovider.h
  53. +4 −3 src/server/qgsserverprojectparser.cpp
  54. +4 −4 src/server/qgswmsserver.cpp
  55. +13 −0 tests/src/core/testqgsfield.cpp
  56. +1 −0 tests/src/gui/CMakeLists.txt
  57. +1 −0 tests/src/gui/testqgsattributeform.cpp
  58. +119 −0 tests/src/gui/testqgseditorwidgetregistry.cpp
  59. +23 −0 tests/src/python/test_provider_postgres.py
  60. +1 −0 tests/testdata/provider/testdata_pg.sh
  61. +24 −0 tests/testdata/provider/testdata_pg.sql
@@ -492,6 +492,8 @@ place of a null pointer.</li>

<ul>
<li>Does no longer inherit QObject
<li>widgetType() and widgetConfig() now reflect only the user configured values.
QgsEditorWidgetRegistry::instance()->findBest() must be used instead.</li>
</ul>

\subsection qgis_api_break_3_0_QgsExpression QgsExpression
@@ -590,6 +592,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>
@@ -817,6 +826,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
@@ -142,15 +142,6 @@ class QgsEditFormConfig
*/
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;

/**
* Get the id for the editor widget used to represent the field at the given index
*
@@ -177,40 +168,14 @@ class QgsEditFormConfig
*/
void setWidgetConfig( int attrIdx, const QgsEditorWidgetConfig& config );

/**
* Set the editor widget config for a widget.
*
* Example:
* \code{.py}
* layer.setWidgetConfig( 'relation_id', { 'nm-rel': 'other_relation' } )
* \endcode
*
* @param widgetName The name of the widget or 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;

/**
* 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;
QgsEditorWidgetConfig widgetConfig( const QString& fieldName ) const;

/**
* Remove the configuration for the editor widget used to represent the field at the given index
@@ -224,11 +189,11 @@ class QgsEditFormConfig
/**
* 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 +337,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 );
};
@@ -1,3 +1,35 @@
/** \ingroup core
* Holder for the widget type and its configuration for a field.
*/
class QgsEditorWidgetSetup
{
%TypeHeaderCode
#include <qgsfield.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;
};


/** \class QgsField
* \ingroup core
* Encapsulate a field in an attribute table or data source.
@@ -179,6 +211,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 );
};
@@ -260,6 +260,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 ) ) );
@@ -392,9 +392,9 @@ void QgsFieldsProperties::loadRelations()
if ( nmrel.fieldPairs().at( 0 ).referencingField() != relation.fieldPairs().at( 0 ).referencingField() )
nmCombo->addItem( QString( "%1 (%2)" ).arg( nmrel.referencedLayer()->name(), nmrel.fieldPairs().at( 0 ).referencedField() ), nmrel.id() );

QgsEditorWidgetConfig cfg = mLayer->editFormConfig().widgetConfig( relation.id() );
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( mLayer, relation.id() );

QVariant nmrelcfg = cfg.value( "nm-rel" );
const QVariant nmrelcfg = setup.config().value( "nm-rel" );

int idx = nmCombo->findData( nmrelcfg.toString() );

@@ -1028,8 +1028,9 @@ QgsFieldsProperties::FieldConfig::FieldConfig( QgsVectorLayer* layer, int idx )
mNotNull = layer->editFormConfig().notNull( idx );
mConstraint = layer->editFormConfig().expression( idx );
mConstraintDescription = layer->editFormConfig().expressionDescription( idx );
mEditorWidgetType = layer->editFormConfig().widgetType( idx );
mEditorWidgetConfig = layer->editFormConfig().widgetConfig( idx );
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( layer, layer->fields().field( idx ).name() );
mEditorWidgetType = setup.type();
mEditorWidgetConfig = setup.config();
}

/*

0 comments on commit 24bde35

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