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.
This commit is contained in:
Patrick Valsecchi 2016-07-15 12:11:43 +02:00
parent 235204fc3d
commit 24bde35ce6
61 changed files with 908 additions and 233 deletions

View File

@ -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

View File

@ -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 );
};

View File

@ -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

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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 );
};

View File

@ -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

View File

@ -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 );

View File

@ -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;
}

View File

@ -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 ) ) );

View File

@ -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();
}
/*

View File

@ -479,7 +479,8 @@ void QgsIdentifyResultsDialog::addFeature( QgsVectorLayer *vlayer, const QgsFeat
if ( i >= fields.count() )
break;
if ( vlayer->editFormConfig().widgetType( i ) == "Hidden" )
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( vlayer, fields[i].name() );
if ( setup.type() == "Hidden" )
{
continue;
}
@ -498,7 +499,7 @@ void QgsIdentifyResultsDialog::addFeature( QgsVectorLayer *vlayer, const QgsFeat
attrItem->setData( 1, Qt::UserRole, value );
value = representValue( vlayer, fields.at( i ).name(), attrs.at( i ) );
value = representValue( vlayer, setup, fields.at( i ).name(), attrs.at( i ) );
attrItem->setSortData( 1, value );
bool foundLinks = false;
QString links = QgsStringUtils::insertLinks( value, &foundLinks );
@ -543,7 +544,8 @@ void QgsIdentifyResultsDialog::addFeature( QgsVectorLayer *vlayer, const QgsFeat
continue;
QString value = fields.at( i ).displayString( attrs.at( i ) );
QString value2 = representValue( vlayer, fields.at( i ).name(), value );
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( vlayer, fields.at( i ).name() );
QString value2 = representValue( vlayer, setup, fields.at( i ).name(), value );
tblResults->setRowCount( j + 1 );
@ -664,13 +666,12 @@ QgsIdentifyPlotCurve::~QgsIdentifyPlotCurve()
}
}
QString QgsIdentifyResultsDialog::representValue( QgsVectorLayer* vlayer, const QString& fieldName, const QVariant& value )
QString QgsIdentifyResultsDialog::representValue( QgsVectorLayer* vlayer, const QgsEditorWidgetSetup& setup, const QString& fieldName, const QVariant& value )
{
QVariant cache;
QMap<QString, QVariant>& layerCaches = mWidgetCaches[vlayer->id()];
QString widgetType = vlayer->editFormConfig().widgetType( fieldName );
QgsEditorWidgetFactory* factory = QgsEditorWidgetRegistry::instance()->factory( widgetType );
QgsEditorWidgetFactory* factory = QgsEditorWidgetRegistry::instance()->factory( setup.type() );
int idx = vlayer->fieldNameIndex( fieldName );
@ -683,11 +684,11 @@ QString QgsIdentifyResultsDialog::representValue( QgsVectorLayer* vlayer, const
}
else
{
cache = factory->createCache( vlayer, idx, vlayer->editFormConfig().widgetConfig( fieldName ) );
cache = factory->createCache( vlayer, idx, setup.config() );
layerCaches.insert( fieldName, cache );
}
return factory->representValue( vlayer, idx, vlayer->editFormConfig().widgetConfig( fieldName ), cache, value );
return factory->representValue( vlayer, idx, setup.config(), cache, value );
}
void QgsIdentifyResultsDialog::addFeature( QgsRasterLayer *layer,
@ -1502,7 +1503,8 @@ void QgsIdentifyResultsDialog::attributeValueChanged( QgsFeatureId fid, int idx,
if ( item->data( 0, Qt::UserRole + 1 ).toInt() == idx )
{
value = representValue( vlayer, fld.name(), val );
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( vlayer, fld.name() );
value = representValue( vlayer, setup, fld.name(), val );
QgsTreeWidgetItem* treeItem = static_cast< QgsTreeWidgetItem* >( item );
treeItem->setSortData( 1, value );

View File

@ -41,6 +41,7 @@ class QgsHighlight;
class QgsMapCanvas;
class QgsDockWidget;
class QgsMapLayerAction;
class QgsEditorWidgetSetup;
class QwtPlotCurve;
@ -217,7 +218,7 @@ class APP_EXPORT QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdenti
void mapLayerActionDestroyed();
private:
QString representValue( QgsVectorLayer* vlayer, const QString& fieldName, const QVariant& value );
QString representValue( QgsVectorLayer* vlayer, const QgsEditorWidgetSetup& setup, const QString& fieldName, const QVariant& value );
enum ItemDataRole
{

View File

@ -27,6 +27,7 @@
#include "qgsvectordataprovider.h"
#include "qgsattributeeditor.h"
#include "qgsstatisticalsummary.h"
#include "qgseditorwidgetregistry.h"
#include <limits>
#include <QComboBox>
@ -116,8 +117,8 @@ void QgsMergeAttributesDialog::createTableWidgetContents()
mHiddenAttributes.clear();
for ( int idx = 0; idx < mFields.count(); ++idx )
{
if ( mVectorLayer->editFormConfig().widgetType( idx ) == "Hidden" ||
mVectorLayer->editFormConfig().widgetType( idx ) == "Immutable" )
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( mVectorLayer, mFields.at( idx ).name() );
if ( setup.type() == "Hidden" || setup.type() == "Immutable" )
{
mHiddenAttributes.insert( idx );
continue;
@ -125,7 +126,7 @@ void QgsMergeAttributesDialog::createTableWidgetContents()
mTableWidget->setColumnCount( col + 1 );
QComboBox *cb = createMergeComboBox( mFields.at( idx ) .type() );
QComboBox *cb = createMergeComboBox( mFields.at( idx ).type() );
if ( pkAttrList.contains( idx ) )
{
cb->setCurrentIndex( cb->findData( "skip" ) );

View File

@ -16,6 +16,7 @@
#include "qgseditformconfig.h"
#include "qgsproject.h"
#include "qgsrelationmanager.h"
//#include "qgseditorwidgetregistry.h"
QgsAttributeEditorContainer::~QgsAttributeEditorContainer()
{
@ -27,30 +28,14 @@ QgsEditFormConfig::QgsEditFormConfig()
{
}
QString QgsEditFormConfig::widgetType( int fieldIdx ) const
{
if ( fieldIdx < 0 || fieldIdx >= d->mFields.count() )
return "TextEdit";
return d->mEditorWidgetTypes.value( d->mFields.at( fieldIdx ).name(), "TextEdit" );
}
QString QgsEditFormConfig::widgetType( const QString& fieldName ) const
{
return d->mEditorWidgetTypes.value( fieldName, "TextEdit" );
return d->mEditorWidgetTypes.value( fieldName );
}
QgsEditorWidgetConfig QgsEditFormConfig::widgetConfig( int fieldIdx ) const
QgsEditorWidgetConfig QgsEditFormConfig::widgetConfig( const QString& fieldName ) const
{
if ( fieldIdx < 0 || fieldIdx >= d->mFields.count() )
return QgsEditorWidgetConfig();
return d->mWidgetConfigs.value( d->mFields.at( fieldIdx ).name() );
}
QgsEditorWidgetConfig QgsEditFormConfig::widgetConfig( const QString& widgetName ) const
{
return d->mWidgetConfigs.value( widgetName );
return d->mWidgetConfigs.value( fieldName );
}
void QgsEditFormConfig::setFields( const QgsFields& fields, const QMap<QString, QString>& attributeAliasMap )
@ -443,38 +428,40 @@ void QgsEditFormConfig::readXml( const QDomNode& node )
}
//// TODO: MAKE THIS MORE GENERIC, SO INDIVIDUALL WIDGETS CAN NOT ONLY SAVE STRINGS
/// SEE QgsEditorWidgetFactory::writeConfig
QDomElement widgetsElem = node.namedItem( "widgets" ).toElement();
QDomNodeList widgetConfigsElems = widgetsElem.childNodes();
for ( int i = 0; i < widgetConfigsElems.size(); ++i )
{
QgsEditorWidgetConfig cfg;
const QDomElement wdgElem = widgetConfigsElems.at( i ).toElement();
const QDomElement cfgElem = wdgElem.namedItem( "config" ).toElement();
const QgsEditorWidgetConfig widgetConfig = parseEditorWidgetConfig( cfgElem );
setWidgetConfig( wdgElem.attribute( "name" ), widgetConfig );
}
}
QDomElement wdgElem = widgetConfigsElems.at( i ).toElement();
QgsEditorWidgetConfig QgsEditFormConfig::parseEditorWidgetConfig( const QDomElement& cfgElem )
{
QgsEditorWidgetConfig cfg;
//// TODO: MAKE THIS MORE GENERIC, SO INDIVIDUALL WIDGETS CAN NOT ONLY SAVE STRINGS
/// SEE QgsEditorWidgetFactory::writeConfig
for ( int j = 0; j < cfgElem.attributes().size(); ++j )
{
const QDomAttr attr = cfgElem.attributes().item( j ).toAttr();
cfg.insert( attr.name(), attr.value() );
}
QDomElement cfgElem = wdgElem.namedItem( "config" ).toElement();
for ( int j = 0; j < cfgElem.attributes().size(); ++j )
{
QDomAttr attr = cfgElem.attributes().item( j ).toAttr();
cfg.insert( attr.name(), attr.value() );
}
QDomNodeList optionElements = cfgElem.elementsByTagName( "option" );
for ( int j = 0; j < optionElements.size(); ++j )
{
QString key = optionElements.at( j ).toElement().attribute( "key" );
QString value = optionElements.at( j ).toElement().attribute( "value" );
cfg.insert( key, value );
}
setWidgetConfig( wdgElem.attribute( "name" ), cfg );
const QDomNodeList optionElements = cfgElem.elementsByTagName( "option" );
for ( int j = 0; j < optionElements.size(); ++j )
{
const QDomElement option = optionElements.at( j ).toElement();
const QString key = option.attribute( "key" );
const QString value = option.attribute( "value" );
cfg.insert( key, value );
}
//// END TODO
return cfg;
}
void QgsEditFormConfig::writeXml( QDomNode& node ) const

View File

@ -177,15 +177,7 @@ class CORE_EXPORT QgsEditFormConfig
/**
* 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
* Don't use this directly. Prefere the use of QgsEditorWidgetRegistry::instance()->findBestType.
*
* @param fieldName The name of the field
*
@ -218,32 +210,24 @@ class CORE_EXPORT 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
* Don't use this directly. Prefere the use of QgsEditorWidgetRegistry::instance()->findBestConfig.
*
* @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
@ -257,11 +241,11 @@ class CORE_EXPORT 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 widget.
*
* @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
@ -411,6 +395,11 @@ class CORE_EXPORT QgsEditFormConfig
*/
explicit QgsEditFormConfig();
/**
* Parse the XML for the config of one editor widget.
*/
static QgsEditorWidgetConfig parseEditorWidgetConfig( const QDomElement& cfgElem );
private:
/**

View File

@ -238,6 +238,16 @@ bool QgsField::convertCompatible( QVariant& v ) const
return true;
}
void QgsField::setEditorWidgetSetup( const QgsEditorWidgetSetup& v )
{
d->editorWidgetSetup = v;
}
const QgsEditorWidgetSetup& QgsField::editorWidgetSetup() const
{
return d->editorWidgetSetup;
}
/***************************************************************************
* This class is considered CRITICAL and any change MUST be accompanied with
* full unit tests in testqgsfield.cpp.

View File

@ -33,6 +33,42 @@ class QgsFieldsPrivate;
* See details in QEP #17
****************************************************************************/
#include "qgseditorwidgetconfig.h"
/** \ingroup core
* Holder for the widget type and its configuration for a field.
*
* @note added in QGIS 3.0
*/
class GUI_EXPORT QgsEditorWidgetSetup
{
public:
/**
* Constructor
*/
QgsEditorWidgetSetup( const QString& type, const QgsEditorWidgetConfig& config ) : mType( type ), mConfig( config ) {}
QgsEditorWidgetSetup() {}
/**
* @return the widget type to use
*/
QString type() const { return mType; }
/**
* @return the widget configuration to used
*/
QgsEditorWidgetConfig config() const { return mConfig; }
/**
* @return true if there is no widget configured.
*/
bool isNull() const { return mType.isEmpty(); }
private:
QString mType;
QgsEditorWidgetConfig mConfig;
};
/** \class QgsField
* \ingroup core
* Encapsulate a field in an attribute table or data source.
@ -175,6 +211,20 @@ class CORE_EXPORT QgsField
return QVariant::fromValue( *this );
}
/**
* 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;
private:
QSharedDataPointer<QgsFieldPrivate> d;

View File

@ -93,6 +93,8 @@ class QgsFieldPrivate : public QSharedData
//! Comment
QString comment;
QgsEditorWidgetSetup editorWidgetSetup;
};

View File

@ -344,7 +344,7 @@ QgsVectorLayerUndoCommandDeleteAttribute::QgsVectorLayerUndoCommandDeleteAttribu
QgsFields::FieldOrigin origin = fields.fieldOrigin( mFieldIndex );
mOriginIndex = fields.fieldOriginIndex( mFieldIndex );
mProviderField = ( origin == QgsFields::OriginProvider );
mOldEditorWidgetConfig = mBuffer->L->editFormConfig().widgetConfig( mFieldIndex );
mOldEditorWidgetConfig = mBuffer->L->editFormConfig().widgetConfig( fields.field( mFieldIndex ).name() );
if ( !mProviderField )
{

View File

@ -84,6 +84,7 @@ SET(QGIS_GUI_SRCS
auth/qgsauthtrustedcasdialog.cpp
editorwidgets/core/qgseditorconfigwidget.cpp
editorwidgets/core/qgseditorwidgetautoconf.cpp
editorwidgets/core/qgseditorwidgetfactory.cpp
editorwidgets/core/qgseditorwidgetregistry.cpp
editorwidgets/core/qgseditorwidgetwrapper.cpp

View File

@ -67,10 +67,8 @@ QWidget* QgsAttributeTableDelegate::createEditor( QWidget *parent, const QStyleO
int fieldIdx = index.model()->data( index, QgsAttributeTableModel::FieldIndexRole ).toInt();
QString widgetType = vl->editFormConfig().widgetType( fieldIdx );
QgsEditorWidgetConfig cfg = vl->editFormConfig().widgetConfig( fieldIdx );
QgsAttributeEditorContext context( masterModel( index.model() )->editorContext(), QgsAttributeEditorContext::Popup );
QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, vl, fieldIdx, cfg, nullptr, parent, context );
QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( vl, fieldIdx, nullptr, parent, context );
QWidget* w = eww->widget();
w->setAutoFillBackground( true );

View File

@ -339,13 +339,13 @@ void QgsAttributeTableModel::loadAttributes()
for ( int idx = 0; idx < fields.count(); ++idx )
{
const QString widgetType = layer()->editFormConfig().widgetType( idx );
QgsEditorWidgetFactory* widgetFactory = QgsEditorWidgetRegistry::instance()->factory( widgetType );
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( layer(), fields[idx].name() );
QgsEditorWidgetFactory* widgetFactory = QgsEditorWidgetRegistry::instance()->factory( setup.type() );
if ( widgetFactory )
{
mWidgetFactories.append( widgetFactory );
mWidgetConfigs.append( layer()->editFormConfig().widgetConfig( idx ) );
mAttributeWidgetCaches.append( widgetFactory->createCache( layer(), idx, mWidgetConfigs.last() ) );
mWidgetConfigs.append( setup.config() );
mAttributeWidgetCaches.append( widgetFactory->createCache( layer(), idx, setup.config() ) );
attributes << idx;
}

View File

@ -26,6 +26,7 @@
#include "qgsvectordataprovider.h"
#include "qgsvectorlayercache.h"
#include "qgsorganizetablecolumnsdialog.h"
#include "qgseditorwidgetregistry.h"
#include <QClipboard>
#include <QDialog>
@ -141,7 +142,7 @@ void QgsDualView::columnBoxInit()
if ( fieldIndex == -1 )
continue;
if ( mLayerCache->layer()->editFormConfig().widgetType( fieldIndex ) != "Hidden" )
if ( QgsEditorWidgetRegistry::instance()->findBest( mLayerCache->layer(), field.name() ).type() != "Hidden" )
{
QIcon icon = mLayerCache->layer()->fields().iconForField( fieldIndex );
QString text = field.name();

View File

@ -0,0 +1,112 @@
/***************************************************************************
qgseditorwidgetautoconf.cpp
---------------------
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. *
* *
***************************************************************************/
#include "qgseditorwidgetautoconf.h"
#include "qgseditorwidgetregistry.h"
/** \ingroup gui
* Widget auto conf plugin that guesses what widget type to use in function of what the widgets support.
*
* @note not available in Python bindings
* @note added in QGIS 3.0
*/
class FromFactoriesPlugin: public QgsEditorWidgetAutoConfPlugin
{
public:
virtual QgsEditorWidgetSetup editorWidgetSetup( const QgsVectorLayer* vl, const QString& fieldName, int& score ) const override
{
int bestScore = 0;
QString bestType;
const QMap<QString, QgsEditorWidgetFactory*> factories = QgsEditorWidgetRegistry::instance()->factories();
for ( QMap<QString, QgsEditorWidgetFactory*>::const_iterator i = factories.begin(); i != factories.end(); ++i )
{
const int index = vl->fieldNameIndex( fieldName );
if ( index >= 0 )
{
const int score = i.value()->fieldScore( vl, index );
if ( score > bestScore )
{
bestType = i.key();
bestScore = score;
}
}
}
if ( bestScore > 0 )
{
score = 10;
return QgsEditorWidgetSetup( bestType, QgsEditorWidgetConfig() );
}
return QgsEditorWidgetSetup();
}
};
/** \ingroup gui
* Widget auto conf plugin that reads the widget setup to use from what the data provider says.
*
* @note not available in Python bindings
* @note added in QGIS 3.0
*/
class FromDbTablePlugin: public QgsEditorWidgetAutoConfPlugin
{
public:
virtual QgsEditorWidgetSetup editorWidgetSetup( const QgsVectorLayer* vl, const QString& fieldName, int& score ) const override
{
QgsField field = vl->fields().field( fieldName );
if ( !field.editorWidgetSetup().isNull() )
{
score = 20;
return field.editorWidgetSetup();
}
else
{
return QgsEditorWidgetSetup();
}
}
};
///@cond PRIVATE
QgsEditorWidgetAutoConf::QgsEditorWidgetAutoConf()
{
registerPlugin( new FromFactoriesPlugin() );
registerPlugin( new FromDbTablePlugin() );
}
QgsEditorWidgetSetup QgsEditorWidgetAutoConf::editorWidgetSetup( const QgsVectorLayer* vl, const QString& fieldName ) const
{
QgsEditorWidgetSetup result( "TextEdit", QgsEditorWidgetConfig() );
if ( vl->fields().indexFromName( fieldName ) >= 0 )
{
int bestScore = 0;
Q_FOREACH ( QSharedPointer<QgsEditorWidgetAutoConfPlugin> cur, plugins )
{
int score = 0;
const QgsEditorWidgetSetup curResult = cur->editorWidgetSetup( vl, fieldName, score );
if ( score > bestScore )
{
result = curResult;
bestScore = score;
}
}
}
return result;
}
void QgsEditorWidgetAutoConf::registerPlugin( QgsEditorWidgetAutoConfPlugin* plugin )
{
plugins.append( QSharedPointer<QgsEditorWidgetAutoConfPlugin>( plugin ) );
}
///@endcond

View File

@ -0,0 +1,88 @@
/***************************************************************************
qgseditorwidgetautoconf.h
---------------------
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. *
* *
***************************************************************************/
#ifndef QGSEDITORWIDGETAUTOCONF_H
#define QGSEDITORWIDGETAUTOCONF_H
#include <QList>
#include <QSharedPointer>
class QgsVectorLayer;
class QgsEditorWidgetSetup;
/** \ingroup gui
* Base class for plugins allowing to pick automatically a widget type for editing fields.
*
* @note added in QGIS 3.0
*/
class GUI_EXPORT QgsEditorWidgetAutoConfPlugin
{
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 ) const = 0;
};
///@cond PRIVATE
/** \ingroup gui
* Class that allows to register plugins to pick automatically a widget type for editing fields.
* This class has only one instance, owned by the QgsEditorWidgetRegistry singleton
*
* The plugins are instances of QgsEditorWidgetAutoConfPlugin.
* @note added in QGIS 3.0
* @note not available in Python bindings
*/
class GUI_EXPORT QgsEditorWidgetAutoConf
{
public:
/**
* Register the default plugins.
*/
QgsEditorWidgetAutoConf();
/**
* Iterate over the plugins and return the setup of the plugin returning the highest score.
*
* @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
*
* @return The best widget setup that was found
*/
QgsEditorWidgetSetup editorWidgetSetup( const QgsVectorLayer* vl, const QString& fieldName ) const;
/**
* Register a new plugin.
*
* @param plugin The plugin (ownership is transfered)
*/
void registerPlugin( QgsEditorWidgetAutoConfPlugin* plugin );
private:
QList<QSharedPointer<QgsEditorWidgetAutoConfPlugin> > plugins;
};
///@endcond
#endif // QGSEDITORWIDGETAUTOCONF_H

View File

@ -116,10 +116,10 @@ QgsEditorWidgetConfig QgsEditorWidgetFactory::readConfig( const QDomElement& con
return QgsEditorWidgetConfig();
}
bool QgsEditorWidgetFactory::isFieldSupported( QgsVectorLayer* vl, int fieldIdx )
unsigned int QgsEditorWidgetFactory::fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const
{
Q_UNUSED( vl )
Q_UNUSED( fieldIdx )
return true;
return 5;
}

View File

@ -113,9 +113,9 @@ class GUI_EXPORT QgsEditorWidgetFactory
* @param fieldIdx The field index
* @return True if the type is supported for this field
*
* @see isFieldSupported( QgsVectorLayer* vl, ind fieldIdx )
* @see fieldScore( const QgsVectorLayer* vl, ind fieldIdx )
*/
inline bool supportsField( QgsVectorLayer* vl, int fieldIdx ) { return isFieldSupported( vl, fieldIdx ); }
inline bool supportsField( const QgsVectorLayer* vl, int fieldIdx ) { return fieldScore( vl, fieldIdx ) > 0; }
/**
* Returns a list of widget types which this editor widget supports.
@ -187,19 +187,25 @@ class GUI_EXPORT 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;
private:
QString mName;

View File

@ -54,6 +54,7 @@ QgsEditorWidgetRegistry* QgsEditorWidgetRegistry::instance()
void QgsEditorWidgetRegistry::initEditors( QgsMapCanvas *mapCanvas, QgsMessageBar *messageBar )
{
QgsEditorWidgetRegistry *reg = instance();
reg->registerWidget( "TextEdit", new QgsTextEditWidgetFactory( tr( "Text Edit" ) ) );
reg->registerWidget( "Classification", new QgsClassificationWidgetWrapperFactory( tr( "Classification" ) ) );
reg->registerWidget( "Range", new QgsRangeWidgetFactory( tr( "Range" ) ) );
reg->registerWidget( "UniqueValues", new QgsUniqueValueWidgetFactory( tr( "Unique Values" ) ) );
@ -62,7 +63,6 @@ void QgsEditorWidgetRegistry::initEditors( QgsMapCanvas *mapCanvas, QgsMessageBa
reg->registerWidget( "Enumeration", new QgsEnumerationWidgetFactory( tr( "Enumeration" ) ) );
reg->registerWidget( "Hidden", new QgsHiddenWidgetFactory( tr( "Hidden" ) ) );
reg->registerWidget( "CheckBox", new QgsCheckboxWidgetFactory( tr( "Check Box" ) ) );
reg->registerWidget( "TextEdit", new QgsTextEditWidgetFactory( tr( "Text Edit" ) ) );
reg->registerWidget( "ValueRelation", new QgsValueRelationWidgetFactory( tr( "Value Relation" ) ) );
reg->registerWidget( "UuidGenerator", new QgsUuidWidgetFactory( tr( "Uuid Generator" ) ) );
reg->registerWidget( "Photo", new QgsPhotoWidgetFactory( tr( "Photo" ) ) );
@ -89,6 +89,23 @@ QgsEditorWidgetRegistry::~QgsEditorWidgetRegistry()
qDeleteAll( mWidgetFactories );
}
QgsEditorWidgetSetup QgsEditorWidgetRegistry::findBest( const QgsVectorLayer* vl, const QString& fieldName ) const
{
const QString fromConfig = vl->editFormConfig().widgetType( fieldName );
if ( !fromConfig.isNull() )
{
return QgsEditorWidgetSetup( fromConfig, vl->editFormConfig().widgetConfig( fieldName ) );
}
return autoConf.editorWidgetSetup( vl, fieldName );
}
QgsEditorWidgetWrapper* QgsEditorWidgetRegistry::create( QgsVectorLayer* vl, int fieldIdx, QWidget* editor, QWidget* parent, const QgsAttributeEditorContext &context )
{
const QString fieldName = vl->fields().field( fieldIdx ).name();
const QgsEditorWidgetSetup setup = findBest( vl, fieldName );
return create( setup.type(), vl, fieldIdx, setup.config(), editor, parent, context );
}
QgsEditorWidgetWrapper* QgsEditorWidgetRegistry::create( const QString& widgetId, QgsVectorLayer* vl, int fieldIdx, const QgsEditorWidgetConfig& config, QWidget* editor, QWidget* parent, const QgsAttributeEditorContext &context )
{
if ( mWidgetFactories.contains( widgetId ) )
@ -271,8 +288,13 @@ void QgsEditorWidgetRegistry::writeMapLayer( QgsMapLayer* mapLayer, QDomElement&
QgsFields fields = vectorLayer->fields();
for ( int idx = 0; idx < fields.count(); ++idx )
{
QgsField field = fields.at( idx );
const QString& widgetType = vectorLayer->editFormConfig().widgetType( idx );
const QgsField field = fields.at( idx );
const QString& widgetType = vectorLayer->editFormConfig().widgetType( field.name() );
if ( widgetType.isNull() )
{
// Don't save widget config if it is not manually edited
continue;
}
if ( !mWidgetFactories.contains( widgetType ) )
{
QgsMessageLog::logMessage( tr( "Could not save unknown editor widget type '%1'." ).arg( widgetType ) );
@ -293,7 +315,7 @@ void QgsEditorWidgetRegistry::writeMapLayer( QgsMapLayer* mapLayer, QDomElement&
ewv2CfgElem.setAttribute( "constraint", vectorLayer->editFormConfig().expression( idx ) );
ewv2CfgElem.setAttribute( "constraintDescription", vectorLayer->editFormConfig().expressionDescription( idx ) );
mWidgetFactories[widgetType]->writeConfig( vectorLayer->editFormConfig().widgetConfig( idx ), ewv2CfgElem, doc, vectorLayer, idx );
mWidgetFactories[widgetType]->writeConfig( vectorLayer->editFormConfig().widgetConfig( field.name() ), ewv2CfgElem, doc, vectorLayer, idx );
editTypeElement.appendChild( ewv2CfgElem );
}

View File

@ -21,6 +21,7 @@
#include "qgseditorwidgetconfig.h"
#include "qgseditorwidgetfactory.h"
#include "qgsattributeeditorcontext.h"
#include "qgseditorwidgetautoconf.h"
class QgsMapLayer;
class QDomNode;
@ -67,6 +68,16 @@ class GUI_EXPORT QgsEditorWidgetRegistry : public QObject
*/
~QgsEditorWidgetRegistry();
/**
* 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 fieldName The field name 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.
@ -89,6 +100,24 @@ class GUI_EXPORT QgsEditorWidgetRegistry : public QObject
QWidget* parent,
const QgsAttributeEditorContext& context = QgsAttributeEditorContext() );
/**
* Create an attribute editor widget wrapper of the best type for a given field.
* The editor may be NULL if you want the widget wrapper to create a default widget.
*
* @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
* @param editor An editor widget which will be used instead of an autocreated widget
* @param parent The parent which will be used for the created wrapper and the created widget
* @param context The editor context (not available in python bindings)
*
* @return A new widget wrapper
*/
QgsEditorWidgetWrapper* create( QgsVectorLayer* vl,
int fieldIdx,
QWidget* editor,
QWidget* parent,
const QgsAttributeEditorContext& context = QgsAttributeEditorContext() );
QgsSearchWidgetWrapper* createSearchWidget( const QString& widgetId,
QgsVectorLayer* vl,
int fieldIdx,
@ -141,6 +170,13 @@ class GUI_EXPORT QgsEditorWidgetRegistry : public QObject
*/
bool registerWidget( const QString& widgetId, QgsEditorWidgetFactory* widgetFactory );
/**
* Register a new auto-conf plugin.
*
* @param plugin The plugin (ownership is transfered)
*/
void registerAutoConfPlugin( QgsEditorWidgetAutoConfPlugin* plugin ) { autoConf.registerPlugin( plugin ); }
protected:
QgsEditorWidgetRegistry();
@ -199,6 +235,7 @@ class GUI_EXPORT QgsEditorWidgetRegistry : public QObject
QMap<QString, QgsEditorWidgetFactory*> mWidgetFactories;
QMap<const char*, QPair<int, QString> > mFactoriesByType;
QgsEditorWidgetAutoConf autoConf;
};
#endif // QGSEDITORWIDGETREGISTRY_H

View File

@ -68,3 +68,9 @@ QMap<const char*, int> QgsCheckboxWidgetFactory::supportedWidgetTypes()
map.insert( QGroupBox::staticMetaObject.className(), 10 );
return map;
}
unsigned int QgsCheckboxWidgetFactory::fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const
{
const QVariant::Type type = vl->fields().field( fieldIdx ).type();
return type == QVariant::Bool ? 20 : 5;
}

View File

@ -36,6 +36,7 @@ class GUI_EXPORT QgsCheckboxWidgetFactory : public QgsEditorWidgetFactory
QgsEditorWidgetConfig readConfig( const QDomElement& configElement, QgsVectorLayer* layer, int fieldIdx ) override;
void writeConfig( const QgsEditorWidgetConfig& config, QDomElement& configElement, QDomDocument& doc, const QgsVectorLayer* layer, int fieldIdx ) override;
QMap<const char*, int> supportedWidgetTypes() override;
unsigned int fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const override;
};
#endif // QGSCHECKBOXWIDGETFACTORY_H

View File

@ -33,3 +33,17 @@ QgsEditorConfigWidget* QgsColorWidgetFactory::configWidget( QgsVectorLayer* vl,
{
return new QgsDummyConfigDlg( vl, fieldIdx, parent, QObject::tr( "Field contains a color." ) );
}
unsigned int QgsColorWidgetFactory::fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const
{
const QgsField field = vl->fields().field( fieldIdx );
const QVariant::Type type = field.type();
if ( type == QVariant::Color )
{
return 20;
}
else
{
return 5;
}
}

View File

@ -32,6 +32,7 @@ class GUI_EXPORT QgsColorWidgetFactory : public QgsEditorWidgetFactory
public:
QgsEditorWidgetWrapper* create( QgsVectorLayer* vl, int fieldIdx, QWidget* editor, QWidget* parent ) const override;
QgsEditorConfigWidget* configWidget( QgsVectorLayer* vl, int fieldIdx, QWidget* parent ) const override;
unsigned int fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const override;
};
#endif // QGSCOLORWIDGETFACTORY_H

View File

@ -114,3 +114,18 @@ QMap<const char*, int> QgsDateTimeEditFactory::supportedWidgetTypes()
map.insert( QgsDateTimeEdit::staticMetaObject.className(), 10 );
return map;
}
unsigned int QgsDateTimeEditFactory::fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const
{
const QgsField field = vl->fields().field( fieldIdx );
const QVariant::Type type = field.type();
const QgsEditorWidgetConfig config = vl->editFormConfig().widgetConfig( field.name() );
if ( type == QVariant::DateTime || config.contains( "field_format" ) )
{
return 20;
}
else
{
return 5;
}
}

View File

@ -42,6 +42,7 @@ class GUI_EXPORT QgsDateTimeEditFactory : public QgsEditorWidgetFactory
QString representValue( QgsVectorLayer* vl, int fieldIdx, const QgsEditorWidgetConfig& config, const QVariant& cache, const QVariant& value ) const override;
Qt::AlignmentFlag alignmentFlag( QgsVectorLayer *vl, int fieldIdx, const QgsEditorWidgetConfig &config ) const override;
virtual QMap<const char*, int> supportedWidgetTypes() override;
unsigned int fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const override;
};
#endif // QGSDATETIMEEDITFACTORY_H

View File

@ -36,12 +36,12 @@ QgsEditorConfigWidget* QgsEnumerationWidgetFactory::configWidget( QgsVectorLayer
}
bool QgsEnumerationWidgetFactory::isFieldSupported( QgsVectorLayer* vl, int fieldIdx )
unsigned int QgsEnumerationWidgetFactory::fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const
{
QStringList list;
vl->dataProvider()->enumValues( fieldIdx, list );
if ( !list.isEmpty() )
return true;
return 20;
else
return false;
return 0;
}

View File

@ -33,8 +33,7 @@ class GUI_EXPORT QgsEnumerationWidgetFactory : public QgsEditorWidgetFactory
QgsEditorWidgetWrapper* create( QgsVectorLayer* vl, int fieldIdx, QWidget* editor, QWidget* parent ) const override;
QgsEditorConfigWidget* configWidget( QgsVectorLayer* vl, int fieldIdx, QWidget* parent ) const override;
private:
bool isFieldSupported( QgsVectorLayer* vl, int fieldIdx ) override;
unsigned int fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const override;
};
#endif // QGSENUMERATIONWIDGETFACTORY_H

View File

@ -119,10 +119,10 @@ QgsEditorWidgetConfig QgsExternalResourceWidgetFactory::readConfig( const QDomEl
return cfg;
}
bool QgsExternalResourceWidgetFactory::isFieldSupported( QgsVectorLayer* vl, int fieldIdx )
unsigned int QgsExternalResourceWidgetFactory::fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const
{
if ( vl->fields().at( fieldIdx ).type() == QVariant::String )
return true;
return 5;
return false;
return 0;
}

View File

@ -35,13 +35,11 @@ class GUI_EXPORT QgsExternalResourceWidgetFactory : public QgsEditorWidgetFactor
QgsEditorWidgetWrapper* create( QgsVectorLayer* vl, int fieldIdx, QWidget* editor, QWidget* parent ) const override;
QgsEditorConfigWidget* configWidget( QgsVectorLayer* vl, int fieldIdx, QWidget* parent ) const override;
// QgsEditorWidgetFactory interface
public:
void writeConfig( const QgsEditorWidgetConfig& config, QDomElement& configElement, QDomDocument& doc, const QgsVectorLayer* layer, int fieldIdx ) override;
unsigned int fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const override;
private:
QgsEditorWidgetConfig readConfig( const QDomElement& configElement, QgsVectorLayer* layer, int fieldIdx ) override;
bool isFieldSupported( QgsVectorLayer* vl, int fieldIdx ) override;
};
#endif // QGSEXTERNALRESOURCEWIDGETFACTORY_H

View File

@ -71,18 +71,9 @@ void QgsRangeWidgetFactory::writeConfig( const QgsEditorWidgetConfig& config, QD
}
}
bool QgsRangeWidgetFactory::isFieldSupported( QgsVectorLayer* vl, int fieldIdx )
unsigned int QgsRangeWidgetFactory::fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const
{
switch ( vl->fields().at( fieldIdx ).type() )
{
case QVariant::LongLong:
case QVariant::Double:
case QVariant::Int:
return true;
default:
return false;
}
return vl->fields().at( fieldIdx ).isNumeric() ? 20 : 0;
}
QMap<const char*, int> QgsRangeWidgetFactory::supportedWidgetTypes()

View File

@ -37,7 +37,7 @@ class GUI_EXPORT QgsRangeWidgetFactory : public QgsEditorWidgetFactory
virtual QMap<const char*, int> supportedWidgetTypes() override;
private:
virtual bool isFieldSupported( QgsVectorLayer *vl, int fieldIdx ) override;
virtual unsigned int fieldScore( const QgsVectorLayer *vl, int fieldIdx ) const override;
};
#endif // QGSRANGEWIDGETFACTORY_H

View File

@ -62,3 +62,10 @@ QgsEditorWidgetConfig QgsTextEditWidgetFactory::readConfig( const QDomElement& c
return cfg;
}
unsigned int QgsTextEditWidgetFactory::fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const
{
Q_UNUSED( vl )
Q_UNUSED( fieldIdx )
return 10;
}

View File

@ -34,9 +34,8 @@ class GUI_EXPORT QgsTextEditWidgetFactory : public QgsEditorWidgetFactory
QgsSearchWidgetWrapper* createSearchWidget( QgsVectorLayer* vl, int fieldIdx, QWidget* parent ) const override;
QgsEditorConfigWidget* configWidget( QgsVectorLayer* vl, int fieldIdx, QWidget* parent ) const override;
// QgsEditorWidgetFactory interface
public:
void writeConfig( const QgsEditorWidgetConfig& config, QDomElement& configElement, QDomDocument& doc, const QgsVectorLayer* layer, int fieldIdx ) override;
unsigned int fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const override;
private:
QgsEditorWidgetConfig readConfig( const QDomElement& configElement, QgsVectorLayer* layer, int fieldIdx ) override;

View File

@ -33,3 +33,9 @@ QgsEditorConfigWidget* QgsUuidWidgetFactory::configWidget( QgsVectorLayer* vl, i
{
return new QgsDummyConfigDlg( vl, fieldIdx, parent, QObject::tr( "Read-only field that generates a UUID if empty." ) );
}
unsigned int QgsUuidWidgetFactory::fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const
{
const QVariant::Type type = vl->fields().field( fieldIdx ).type();
return type == QVariant::String ? 5 : 0;
}

View File

@ -32,6 +32,7 @@ class GUI_EXPORT QgsUuidWidgetFactory : public QgsEditorWidgetFactory
public:
QgsEditorWidgetWrapper* create( QgsVectorLayer* vl, int fieldIdx, QWidget* editor, QWidget* parent ) const override;
QgsEditorConfigWidget* configWidget( QgsVectorLayer* vl, int fieldIdx, QWidget* parent ) const override;
unsigned int fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const override;
};
#endif // QGSUUIDWIDGETFACTORY_H

View File

@ -43,10 +43,7 @@ QWidget *QgsAttributeEditor::createAttributeEditor( QWidget *parent, QWidget *ed
QWidget* QgsAttributeEditor::createAttributeEditor( QWidget* parent, QWidget* editor, QgsVectorLayer* vl, int idx, const QVariant& value, QgsAttributeEditorContext& context )
{
QString widgetType = vl->editFormConfig().widgetType( idx );
QgsEditorWidgetConfig cfg = vl->editFormConfig().widgetConfig( idx );
QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, vl, idx, cfg, editor, parent, context );
QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( vl, idx, editor, parent, context );
if ( eww )
{

View File

@ -1216,17 +1216,16 @@ void QgsAttributeForm::init()
//show attribute alias if available
QString fieldName = mLayer->attributeDisplayName( idx );
const QString widgetType = mLayer->editFormConfig().widgetType( idx );
const QgsEditorWidgetSetup widgetSetup = QgsEditorWidgetRegistry::instance()->findBest( mLayer, field.name() );
if ( widgetType == "Hidden" )
if ( widgetSetup.type() == "Hidden" )
continue;
const QgsEditorWidgetConfig widgetConfig = mLayer->editFormConfig().widgetConfig( idx );
bool labelOnTop = mLayer->editFormConfig().labelOnTop( idx );
// This will also create the widget
QLabel *l = new QLabel( fieldName );
QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, idx, widgetConfig, nullptr, this, mContext );
QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetSetup.type(), mLayer, idx, widgetSetup.config(), nullptr, this, mContext );
QWidget* w = nullptr;
if ( eww )
@ -1234,13 +1233,13 @@ void QgsAttributeForm::init()
QgsAttributeFormEditorWidget* formWidget = new QgsAttributeFormEditorWidget( eww, this );
w = formWidget;
mFormEditorWidgets.insert( idx, formWidget );
formWidget->createSearchWidgetWrappers( widgetType, idx, widgetConfig, mContext );
formWidget->createSearchWidgetWrappers( widgetSetup.type(), idx, widgetSetup.config(), mContext );
l->setBuddy( eww->widget() );
}
else
{
w = new QLabel( QString( "<p style=\"color: red; font-style: italic;\">Failed to create widget with type '%1'</p>" ).arg( widgetType ) );
w = new QLabel( QString( "<p style=\"color: red; font-style: italic;\">Failed to create widget with type '%1'</p>" ).arg( widgetSetup.type() ) );
}
@ -1265,8 +1264,8 @@ void QgsAttributeForm::init()
Q_FOREACH ( const QgsRelation& rel, QgsProject::instance()->relationManager()->referencedRelations( mLayer ) )
{
QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, rel, nullptr, this );
QgsEditorWidgetConfig cfg = mLayer->editFormConfig().widgetConfig( rel.id() );
rww->setConfig( cfg );
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( mLayer, rel.id() );
rww->setConfig( setup.config() );
rww->setContext( mContext );
gridLayout->addWidget( rww->widget(), row++, 0, 1, 2 );
mWidgets.append( rww );
@ -1506,14 +1505,13 @@ QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAtt
int fldIdx = vl->fieldNameIndex( fieldDef->name() );
if ( fldIdx < vl->fields().count() && fldIdx >= 0 )
{
const QString widgetType = mLayer->editFormConfig().widgetType( fldIdx );
const QgsEditorWidgetConfig widgetConfig = mLayer->editFormConfig().widgetConfig( fldIdx );
const QgsEditorWidgetSetup widgetSetup = QgsEditorWidgetRegistry::instance()->findBest( mLayer, fieldDef->name() );
QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, fldIdx, widgetConfig, nullptr, this, mContext );
QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetSetup.type(), mLayer, fldIdx, widgetSetup.config(), nullptr, this, mContext );
QgsAttributeFormEditorWidget* w = new QgsAttributeFormEditorWidget( eww, this );
mFormEditorWidgets.insert( fldIdx, w );
w->createSearchWidgetWrappers( widgetType, fldIdx, widgetConfig, mContext );
w->createSearchWidgetWrappers( widgetSetup.type(), fldIdx, widgetSetup.config(), mContext );
newWidgetInfo.widget = w;
addWidgetWrapper( eww );
@ -1533,8 +1531,8 @@ QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAtt
const QgsAttributeEditorRelation* relDef = dynamic_cast<const QgsAttributeEditorRelation*>( widgetDef );
QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, relDef->relation(), nullptr, this );
QgsEditorWidgetConfig cfg = mLayer->editFormConfig().widgetConfig( relDef->relation().id() );
rww->setConfig( cfg );
const QgsEditorWidgetSetup widgetSetup = QgsEditorWidgetRegistry::instance()->findBest( mLayer, relDef->relation().id() );
rww->setConfig( widgetSetup.config() );
rww->setContext( context );
newWidgetInfo.widget = rww->widget();
rww->setShowLabel( relDef->showLabel() );
@ -1688,7 +1686,8 @@ void QgsAttributeForm::createWrappers()
if ( relation.isValid() )
{
QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, relation, myWidget, this );
rww->setConfig( mLayer->editFormConfig().widgetConfig( relation.id() ) );
const QgsEditorWidgetSetup widgetSetup = QgsEditorWidgetRegistry::instance()->findBest( mLayer, relation.id() );
rww->setConfig( widgetSetup.config() );
rww->setContext( mContext );
rww->widget(); // Will initialize the widget
mWidgets.append( rww );
@ -1700,11 +1699,9 @@ void QgsAttributeForm::createWrappers()
{
if ( field.name() == myWidget->objectName() )
{
const QString widgetType = mLayer->editFormConfig().widgetType( field.name() );
const QgsEditorWidgetConfig widgetConfig = mLayer->editFormConfig().widgetConfig( field.name() );
int idx = mLayer->fieldNameIndex( field.name() );
QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, idx, widgetConfig, myWidget, this, mContext );
QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( mLayer, idx, myWidget, this, mContext );
addWidgetWrapper( eww );
}
}

View File

@ -1,4 +1,3 @@
########################################################
# Files

View File

@ -1287,6 +1287,7 @@ QString QgsPostgresConn::fieldExpression( const QgsField &fld, QString expr )
}
else if ( type.startsWith( '_' ) )
{
//TODO: add native support for arrays
return QString( "array_out(%1)::text" ).arg( expr );
}
else if ( type == "bool" )
@ -1303,6 +1304,7 @@ QString QgsPostgresConn::fieldExpression( const QgsField &fld, QString expr )
{
return QString( "st_astext(%1)" ).arg( expr );
}
//TODO: add support for hstore
else
{
return expr + "::text";

View File

@ -23,6 +23,7 @@
#include <qgsmessagelog.h>
#include <qgsrectangle.h>
#include <qgscoordinatereferencesystem.h>
#include <qgseditformconfig.h>
#include <QMessageBox>
@ -38,6 +39,7 @@
const QString POSTGRES_KEY = "postgres";
const QString POSTGRES_DESCRIPTION = "PostgreSQL/PostGIS data provider";
static const QString EDITOR_WIDGET_STYLES_TABLE = "qgis_editor_widget_styles";
inline qint64 PKINT2FID( qint32 x )
{
@ -49,6 +51,12 @@ inline qint32 FID2PKINT( qint64 x )
return QgsPostgresUtils::fid_to_int32pk( x );
}
static bool tableExists( QgsPostgresConn& conn, const QString& name )
{
QgsPostgresResult res( conn.PQexec( "SELECT COUNT(*) FROM information_schema.tables WHERE table_name=" + QgsPostgresConn::quotedValue( name ) ) );
return res.PQgetvalue( 0, 0 ).toInt() > 0;
}
QgsPostgresPrimaryKeyType
QgsPostgresProvider::pkType( const QgsField& f ) const
{
@ -1053,9 +1061,48 @@ bool QgsPostgresProvider::loadFields()
mAttributeFields.append( QgsField( fieldName, fieldType, fieldTypeName, fieldSize, fieldPrec, fieldComment ) );
}
setEditorWidgets();
return true;
}
void QgsPostgresProvider::setEditorWidgets()
{
if ( tableExists( *connectionRO(), EDITOR_WIDGET_STYLES_TABLE ) )
{
for ( int i = 0; i < mAttributeFields.count(); ++i )
{
// CREATE TABLE qgis_editor_widget_styles (schema_name TEXT NOT NULL, table_name TEXT NOT NULL, field_name TEXT NOT NULL,
// type TEXT NOT NULL, config TEXT,
// PRIMARY KEY(schema_name, table_name, field_name));
QgsField& field = mAttributeFields[i];
const QString sql = QString( "SELECT type, config FROM %1 WHERE schema_name = %2 and table_name = %3 and field_name = %4 LIMIT 1" ).
arg( EDITOR_WIDGET_STYLES_TABLE, quotedValue( mSchemaName ), quotedValue( mTableName ), quotedValue( field.name() ) );
QgsPostgresResult result( connectionRO()->PQexec( sql ) );
for ( int i = 0; i < result.PQntuples(); ++i )
{
const QString type = result.PQgetvalue( i, 0 );
QgsEditorWidgetConfig config;
if ( !result.PQgetisnull( i, 1 ) ) // Can be null and it's OK
{
const QString configTxt = result.PQgetvalue( i, 1 );
QDomDocument doc;
if ( doc.setContent( configTxt ) )
{
config = QgsEditFormConfig::parseEditorWidgetConfig( doc.documentElement() );
}
else
{
QgsMessageLog::logMessage( tr( "Cannot parse widget configuration for field %1.%2.%3\n" ).arg( mSchemaName, mTableName, field.name() ), tr( "PostGIS" ) );
}
}
field.setEditorWidgetSetup( QgsEditorWidgetSetup( type, config ) );
}
}
}
}
bool QgsPostgresProvider::hasSufficientPermsAndCapabilities()
{
QgsDebugMsg( "Checking for permissions on the relation" );
@ -1937,6 +1984,7 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist )
.arg( delim,
quotedValue( v.toString() ) );
}
//TODO: convert arrays and hstore to native types
else
{
values += delim + quotedValue( v );
@ -3965,24 +4013,23 @@ QGISEXTERN bool saveStyle( const QString& uri, const QString& qmlStyle, const QS
return false;
}
QgsPostgresResult res( conn->PQexec( "SELECT COUNT(*) FROM information_schema.tables WHERE table_name='layer_styles'" ) );
if ( res.PQgetvalue( 0, 0 ).toInt() == 0 )
if ( !tableExists( *conn, "layer_styles" ) )
{
res = conn->PQexec( "CREATE TABLE layer_styles("
"id SERIAL PRIMARY KEY"
",f_table_catalog varchar"
",f_table_schema varchar"
",f_table_name varchar"
",f_geometry_column varchar"
",styleName varchar(30)"
",styleQML xml"
",styleSLD xml"
",useAsDefault boolean"
",description text"
",owner varchar(30)"
",ui xml"
",update_time timestamp DEFAULT CURRENT_TIMESTAMP"
")" );
QgsPostgresResult res( conn->PQexec( "CREATE TABLE layer_styles("
"id SERIAL PRIMARY KEY"
",f_table_catalog varchar"
",f_table_schema varchar"
",f_table_name varchar"
",f_geometry_column varchar"
",styleName varchar(30)"
",styleQML xml"
",styleSLD xml"
",useAsDefault boolean"
",description text"
",owner varchar(30)"
",ui xml"
",update_time timestamp DEFAULT CURRENT_TIMESTAMP"
")" ) );
if ( res.PQresultStatus() != PGRES_COMMAND_OK )
{
errCause = QObject::tr( "Unable to save layer style. It's not possible to create the destination table on the database. Maybe this is due to table permissions (user=%1). Please contact your database admin" ).arg( dsUri.username() );
@ -4036,7 +4083,7 @@ QGISEXTERN bool saveStyle( const QString& uri, const QString& qmlStyle, const QS
.arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) )
.arg( QgsPostgresConn::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) );
res = conn->PQexec( checkQuery );
QgsPostgresResult res( conn->PQexec( checkQuery ) );
if ( res.PQntuples() > 0 )
{
if ( QMessageBox::question( nullptr, QObject::tr( "Save style in database" ),
@ -4111,8 +4158,7 @@ QGISEXTERN QString loadStyle( const QString& uri, QString& errCause )
return "";
}
QgsPostgresResult result( conn->PQexec( "SELECT COUNT(*) FROM information_schema.tables WHERE table_name='layer_styles'" ) );
if ( result.PQgetvalue( 0, 0 ).toInt() == 0 )
if ( !tableExists( *conn, "layer_styles" ) )
{
return "";
}
@ -4130,7 +4176,7 @@ QGISEXTERN QString loadStyle( const QString& uri, QString& errCause )
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) );
result = conn->PQexec( selectQmlQuery );
QgsPostgresResult result( conn->PQexec( selectQmlQuery ) );
QString style = result.PQntuples() == 1 ? result.PQgetvalue( 0, 0 ) : "";
conn->unref();

View File

@ -300,6 +300,10 @@ class QgsPostgresProvider : public QgsVectorDataProvider
*/
bool loadFields();
/** Set the default widget type for the fields
*/
void setEditorWidgets();
/** Convert a QgsField to work with PG */
static bool convertField( QgsField &field, const QMap<QString, QVariant> *options = nullptr );

View File

@ -811,7 +811,7 @@ void QgsServerProjectParser::addLayerProjectSettings( QDomElement& layerElem, QD
}
//edit type to text
attributeElem.setAttribute( "editType", vLayer->editFormConfig().widgetType( idx ) );
attributeElem.setAttribute( "editType", vLayer->editFormConfig().widgetType( field.name() ) );
attributeElem.setAttribute( "comment", field.comment() );
attributeElem.setAttribute( "length", field.length() );
attributeElem.setAttribute( "precision", field.precision() );
@ -1555,10 +1555,11 @@ void QgsServerProjectParser::addValueRelationLayersForLayer( const QgsVectorLaye
for ( int idx = 0; idx < vl->pendingFields().size(); idx++ )
{
if ( vl->editFormConfig().widgetType( idx ) != "ValueRelation" )
const QString name = vl->pendingFields().field( idx ).name();
if ( vl->editFormConfig().widgetType( name ) != "ValueRelation" )
continue;
QgsEditorWidgetConfig cfg( vl->editFormConfig().widgetConfig( idx ) );
QgsEditorWidgetConfig cfg( vl->editFormConfig().widgetConfig( name ) );
if ( !cfg.contains( "Layer" ) )
continue;

View File

@ -3334,11 +3334,11 @@ QDomElement QgsWmsServer::createFeatureGML(
QString QgsWmsServer::replaceValueMapAndRelation( QgsVectorLayer* vl, int idx, const QString& attributeVal )
{
if ( QgsEditorWidgetFactory *factory = QgsEditorWidgetRegistry::instance()->factory( vl->editFormConfig().widgetType( idx ) ) )
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( vl, vl->fields().field( idx ).name() );
if ( QgsEditorWidgetFactory *factory = QgsEditorWidgetRegistry::instance()->factory( setup.type() ) )
{
QgsEditorWidgetConfig cfg( vl->editFormConfig().widgetConfig( idx ) );
QString value( factory->representValue( vl, idx, cfg, QVariant(), attributeVal ) );
if ( cfg.value( "AllowMulti" ).toBool() && value.startsWith( "{" ) && value.endsWith( "}" ) )
QString value( factory->representValue( vl, idx, setup.config(), QVariant(), attributeVal ) );
if ( setup.config().value( "AllowMulti" ).toBool() && value.startsWith( "{" ) && value.endsWith( "}" ) )
{
value = value.mid( 1, value.size() - 2 );
}

View File

@ -40,6 +40,7 @@ class TestQgsField: public QObject
void displayString();
void convertCompatible();
void dataStream();
void editorWidgetSetup();
private:
};
@ -372,5 +373,17 @@ void TestQgsField::dataStream()
QCOMPARE( result.comment(), original.comment() ); //comment is NOT required for equality
}
void TestQgsField::editorWidgetSetup()
{
QgsField field;
QgsEditorWidgetConfig config;
config.insert( "a", "value_a" );
const QgsEditorWidgetSetup setup( "test", config );
field.setEditorWidgetSetup( setup );
QCOMPARE( field.editorWidgetSetup().type(), setup.type() );
QCOMPARE( field.editorWidgetSetup().config(), setup.config() );
}
QTEST_MAIN( TestQgsField )
#include "testqgsfield.moc"

View File

@ -140,4 +140,5 @@ ADD_QGIS_TEST(rubberbandtest testqgsrubberband.cpp)
ADD_QGIS_TEST(scalecombobox testqgsscalecombobox.cpp)
ADD_QGIS_TEST(spinbox testqgsspinbox.cpp)
ADD_QGIS_TEST(sqlcomposerdialog testqgssqlcomposerdialog.cpp)
ADD_QGIS_TEST(editorwidgetregistrytest testqgseditorwidgetregistry.cpp)

View File

@ -67,6 +67,7 @@ void TestQgsAttributeForm::testFieldConstraint()
// make a temporary vector layer
QString def = "Point?field=col0:integer";
QgsVectorLayer* layer = new QgsVectorLayer( def, "test", "memory" );
layer->editFormConfig().setWidgetType( 0, "TextEdit" );
// add a feature to the vector layer
QgsFeature ft( layer->dataProvider()->fields(), 1 );

View File

@ -0,0 +1,119 @@
/***************************************************************************
testqgseditorwidgetregistry.cpp
---------------------
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. *
* *
***************************************************************************/
#include <QtTest/QtTest>
#include "qgseditorwidgetregistry.h"
#include "qgseditorwidgetautoconf.h"
class TestQgsEditorWidgetRegistry: public QObject
{
Q_OBJECT
class DummyPlugin: public QgsEditorWidgetAutoConfPlugin
{
public:
QgsEditorWidgetSetup editorWidgetSetup( const QgsVectorLayer* vl, const QString& fieldName, int& score ) const override
{
Q_UNUSED( vl )
if ( fieldName == "special" )
{
score = 100;
return QgsEditorWidgetSetup( "Special", QgsEditorWidgetConfig() );
}
score = 0;
return QgsEditorWidgetSetup();
}
};
private slots:
void initTestCase()
{
QgsApplication::init();
QgsApplication::initQgis();
QgsEditorWidgetRegistry::initEditors();
QgsEditorWidgetRegistry::instance()->registerAutoConfPlugin( new DummyPlugin() );
}
void cleanupTestCase()
{
QgsApplication::exitQgis();
}
void stringType()
{
checkSimple( "string", "TextEdit" );
}
void datetimeType()
{
checkSimple( "datetime", "DateTime" );
}
void integerType()
{
checkSimple( "integer", "Range" );
}
void doubleType()
{
checkSimple( "double", "Range" );
}
void configuredType()
{
QgsVectorLayer vl( "LineString?crs=epsg:3111&field=pk:int&field=col1:string", "vl", "memory" );
QgsEditFormConfig formConfig = vl.editFormConfig();
formConfig.setWidgetType( vl.fieldNameIndex( "col1" ), "FooEdit" );
QgsEditorWidgetConfig config;
config["a"] = QVariant( 12 );
config["b"] = QVariant( "bar" );
formConfig.setWidgetConfig( "col1", config );
vl.setEditFormConfig( formConfig );
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( &vl, "col1" );
QCOMPARE( setup.type(), QString( "FooEdit" ) );
QCOMPARE( setup.config(), config );
}
void wrongFieldName()
{
const QgsVectorLayer vl( "LineString?crs=epsg:3111&field=pk:int&field=col1:string", "vl", "memory" );
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( &vl, "col2" );
// an unknown fields leads to a default setup with a TextEdit
QCOMPARE( setup.type(), QString( "TextEdit" ) );
QCOMPARE( setup.config().count(), 0 );
}
void typeFromPlugin()
{
const QgsVectorLayer vl( "LineString?crs=epsg:3111&field=pk:int&field=special:string", "vl", "memory" );
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( &vl, "special" );
QCOMPARE( setup.type(), QString( "Special" ) );
}
private:
static void checkSimple( const QString& dataType, const QString& widgetType )
{
const QgsVectorLayer vl( "LineString?crs=epsg:3111&field=pk:int&field=col1:" + dataType, "vl", "memory" );
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( &vl, "col1" );
QCOMPARE( setup.type(), widgetType );
QCOMPARE( setup.config().count(), 0 );
}
};
QTEST_MAIN( TestQgsEditorWidgetRegistry )
#include "testqgseditorwidgetregistry.moc"

View File

@ -24,6 +24,7 @@ from qgis.core import (
QgsTransactionGroup,
NULL
)
from qgis.gui import QgsEditorWidgetRegistry
from qgis.PyQt.QtCore import QSettings, QDate, QTime, QDateTime, QVariant
from qgis.testing import start_app, unittest
from utilities import unitTestDataPath
@ -48,6 +49,7 @@ class TestPyQgsPostgresProvider(unittest.TestCase, ProviderTestCase):
cls.poly_vl = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' srid=4326 type=POLYGON table="qgis_test"."some_poly_data" (geom) sql=', 'test', 'postgres')
assert cls.poly_vl.isValid()
cls.poly_provider = cls.poly_vl.dataProvider()
QgsEditorWidgetRegistry.instance().initEditors()
@classmethod
def tearDownClass(cls):
@ -312,6 +314,27 @@ class TestPyQgsPostgresProvider(unittest.TestCase, ProviderTestCase):
self.assertEqual(fet.fields()[1].name(), 'newname2')
self.assertEqual(fet.fields()[2].name(), 'another')
def testEditorWidgetTypes(self):
"""Test that editor widget types can be fetched from the qgis_editor_widget_styles table"""
vl = QgsVectorLayer('%s table="qgis_test"."widget_styles" sql=' % (self.dbconn), "widget_styles", "postgres")
self.assertTrue(vl.isValid())
fields = vl.dataProvider().fields()
setup1 = fields.field("fld1").editorWidgetSetup()
self.assertFalse(setup1.isNull())
self.assertEqual(setup1.type(), "FooEdit")
self.assertEqual(setup1.config(), {"param1": "value1", "param2": "2"})
best1 = QgsEditorWidgetRegistry.instance().findBest(vl, "fld1")
self.assertEqual(best1.type(), "FooEdit")
self.assertEqual(best1.config(), setup1.config())
self.assertTrue(fields.field("fld2").editorWidgetSetup().isNull())
best2 = QgsEditorWidgetRegistry.instance().findBest(vl, "fld2")
self.assertEqual(best2.type(), "TextEdit")
class TestPyQgsPostgresProviderCompoundKey(unittest.TestCase, ProviderTestCase):

View File

@ -6,6 +6,7 @@ SCRIPTS="
tests/testdata/provider/testdata_pg_vectorjoin.sql
"
dropdb qgis_test 2> /dev/null || true
createdb qgis_test || exit 1
for f in ${SCRIPTS}; do
psql -f $f qgis_test --set ON_ERROR_STOP=1 || exit 1

View File

@ -434,3 +434,27 @@ CREATE TABLE qgis_test.rename_table
);
INSERT INTO qgis_test.rename_table (field1,field2) VALUES ('a','b');
--------------------------------------
-- Table for editor widget types
--
CREATE TABLE qgis_editor_widget_styles
(
schema_name TEXT NOT NULL,
table_name TEXT NOT NULL,
field_name TEXT NOT NULL,
type TEXT NOT NULL,
config TEXT,
PRIMARY KEY(table_name, field_name)
);
CREATE TABLE qgis_test.widget_styles(
id int PRIMARY KEY,
fld1 TEXT,
fld2 TEXT
);
INSERT INTO qgis_editor_widget_styles VALUES
('qgis_test', 'widget_styles', 'fld1', 'FooEdit', '<config><option key="param1" value="value1"/><option key="param2" value="2"/></config>');