Skip to content

Commit

Permalink
Smarter default edit widgets with plugins to pick them
Browse files Browse the repository at this point in the history
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 7169079
Show file tree
Hide file tree
Showing 66 changed files with 950 additions and 304 deletions.
20 changes: 19 additions & 1 deletion doc/api_break.dox
Expand Up @@ -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
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions python/core/core.sip
Expand Up @@ -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
Expand Down
65 changes: 13 additions & 52 deletions python/core/qgseditformconfig.sip
Expand Up @@ -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
Expand All @@ -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.
*
Expand All @@ -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
Expand Down Expand Up @@ -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 );
};
32 changes: 32 additions & 0 deletions python/core/qgseditorwidgetsetup.sip
@@ -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;
};


13 changes: 13 additions & 0 deletions python/core/qgsfield.sip
Expand Up @@ -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


Expand Down
41 changes: 41 additions & 0 deletions python/gui/editorwidgets/core/qgseditorwidgetautoconf.sip
@@ -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;
};
14 changes: 10 additions & 4 deletions python/gui/editorwidgets/core/qgseditorwidgetfactory.sip
Expand Up @@ -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;
};
19 changes: 19 additions & 0 deletions python/gui/editorwidgets/core/qgseditorwidgetregistry.sip
Expand Up @@ -13,6 +13,7 @@
* *
***************************************************************************/


/**
* This class manages all known edit widget factories
*/
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 );
};
1 change: 1 addition & 0 deletions python/gui/gui.sip
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions src/app/ogr/qgsvectorlayersaveasdialog.cpp
Expand Up @@ -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;
Expand Down Expand Up @@ -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 );
Expand Down
6 changes: 3 additions & 3 deletions src/app/qgisapp.cpp
Expand Up @@ -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;
}
Expand Down
7 changes: 3 additions & 4 deletions src/app/qgsattributetabledialog.cpp
Expand Up @@ -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 );
Expand Down Expand Up @@ -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 ) ) );
Expand Down

0 comments on commit 7169079

Please sign in to comment.