Merge pull request #3467 from pvalsecc/arrays

[FEATURE] Add support for array attributes
This commit is contained in:
Nyall Dawson 2016-09-16 06:13:34 +10:00 committed by GitHub
commit 8746932193
49 changed files with 1357 additions and 148 deletions

View File

@ -24,13 +24,17 @@ class QgsField
* @param prec Field precision. Usually decimal places but may also be
* used in conjunction with other fields types (eg. variable character fields)
* @param comment Comment for the field
* @param subType If the field is a collection, its element's type. When
* all the elements don't need to have the same type, leave
* this to QVariant::Invalid.
*/
QgsField( const QString& name = QString(),
QVariant::Type type = QVariant::Invalid,
const QString& typeName = QString(),
int len = 0,
int prec = 0,
const QString& comment = QString() );
const QString& comment = QString(),
QVariant::Type subType = QVariant::Invalid );
/** Copy constructor
*/
@ -59,6 +63,14 @@ class QgsField
//! Gets variant type of the field as it will be retrieved from data source
QVariant::Type type() const;
/**
* If the field is a collection, gets its element's type.
* When all the elements don't need to have the same type, this returns
* QVariant::Invalid.
* @note added in QGIS 3.0
*/
QVariant::Type subType() const;
/**
* Gets the field type. Field types vary depending on the data source. Examples
* are char, int, double, blob, geometry, etc. The type is stored exactly as
@ -103,6 +115,14 @@ class QgsField
*/
void setType( QVariant::Type type );
/**
* If the field is a collection, set its element's type.
* When all the elements don't need to have the same type, set this to
* QVariant::Invalid.
* @note added in QGIS 3.0
*/
void setSubType( QVariant::Type subType );
/**
* Set the field type.
* @param typeName Field type

View File

@ -302,7 +302,7 @@ class QgsVectorDataProvider : QgsDataProvider
struct NativeType
{
NativeType( const QString& typeDesc, const QString& typeName, QVariant::Type type, int minLen = 0, int maxLen = 0, int minPrec = 0, int maxPrec = 0 );
NativeType( const QString& typeDesc, const QString& typeName, QVariant::Type type, int minLen = 0, int maxLen = 0, int minPrec = 0, int maxPrec = 0, QVariant::Type subType = QVariant::Invalid );
QString mTypeDesc;
QString mTypeName;
@ -311,6 +311,7 @@ class QgsVectorDataProvider : QgsDataProvider
int mMaxLen;
int mMinPrec;
int mMaxPrec;
QVariant::Type mSubType;
};
/**

View File

@ -75,6 +75,13 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
*/
static QgsEditorWidgetWrapper* fromWidget( QWidget* widget );
/**
* Check if the given widget or one of its parent is a QTableView.
* @param parent the widget to check
* @return true if yes
*/
static bool isInTable( const QWidget* parent );
/**
* Is used to enable or disable the edit functionality of the managed widget.
* By default this will enable or disable the whole widget

View File

@ -96,6 +96,7 @@
%Include qgshtmlannotationitem.sip
%Include qgsidentifymenu.sip
%Include qgskeyvaluewidget.sip
%Include qgslistwidget.sip
%Include qgslegendfilterbutton.sip
%Include qgslegendinterface.sip
%Include qgslimitedrandomcolorrampdialog.sip
@ -161,6 +162,7 @@
%Include qgssourceselectdialog.sip
%Include qgssublayersdialog.sip
%Include qgssvgannotationitem.sip
%Include qgstablewidgetbase.sip
%Include qgstabwidget.sip
%Include qgstablewidgetitem.sip
%Include qgstextannotationitem.sip

View File

@ -2,7 +2,7 @@
* Widget allowing to edit a QVariantMap, using a table.
* @note added in QGIS 3.0
*/
class QgsKeyValueWidget : public QWidget
class QgsKeyValueWidget : public QgsTableWidgetBase
{
%TypeHeaderCode
#include "qgskeyvaluewidget.h"

View File

@ -0,0 +1,32 @@
/** \ingroup gui
* Widget allowing to edit a QVariantList, using a table.
* @note added in QGIS 3.0
*/
class QgsListWidget : public QgsTableWidgetBase
{
%TypeHeaderCode
#include "qgslistwidget.h"
%End
public:
/**
* Constructor.
*/
explicit QgsListWidget( QVariant::Type subType, QWidget* parent = nullptr );
/**
* Set the initial value of the widget.
*/
void setList( const QVariantList& list );
/**
* Get the edit value.
* @return the QVariantList
*/
QVariantList list() const;
/**
* Check the content is valid
* @return true if valid
*/
bool valid() const;
};

View File

@ -0,0 +1,28 @@
/** \ingroup gui
* Base widget allowing to edit a collection, using a table.
* @note added in QGIS 3.0
*/
class QgsTableWidgetBase: public QWidget
{
%TypeHeaderCode
#include "qgstablewidgetbase.h"
%End
public:
/**
* Constructor.
*/
explicit QgsTableWidgetBase( QWidget* parent );
protected:
/**
* Initialise the table with the given model.
* Must be called once in the child class' constructor.
*/
void init( QAbstractTableModel* model );
signals:
/**
* Emitted each time a key or a value is changed.
*/
void valueChanged();
};

View File

@ -346,6 +346,7 @@ void QgsFieldCalculator::populateOutputFieldTypes()
mOutputFieldTypeComboBox->setItemData( i, typelist[i].mMaxLen, Qt::UserRole + 3 );
mOutputFieldTypeComboBox->setItemData( i, typelist[i].mMinPrec, Qt::UserRole + 4 );
mOutputFieldTypeComboBox->setItemData( i, typelist[i].mMaxPrec, Qt::UserRole + 5 );
mOutputFieldTypeComboBox->setItemData( i, static_cast<int>( typelist[i].mSubType ), Qt::UserRole + 6 );
}
mOutputFieldTypeComboBox->blockSignals( false );
mOutputFieldTypeComboBox->setCurrentIndex( 0 );

View File

@ -67,7 +67,10 @@ class APP_EXPORT QgsFieldCalculator: public QDialog, private Ui::QgsFieldCalcula
static_cast< QVariant::Type >( mOutputFieldTypeComboBox->itemData( mOutputFieldTypeComboBox->currentIndex(), Qt::UserRole ).toInt() ),
mOutputFieldTypeComboBox->itemData( mOutputFieldTypeComboBox->currentIndex(), Qt::UserRole + 1 ).toString(),
mOutputFieldWidthSpinBox->value(),
mOutputFieldPrecisionSpinBox->value() );
mOutputFieldPrecisionSpinBox->value(),
QString(),
static_cast< QVariant::Type >( mOutputFieldTypeComboBox->itemData( mOutputFieldTypeComboBox->currentIndex(), Qt::UserRole + 6 ).toInt() )
);
}
/** Idx of changed attribute*/

View File

@ -5074,6 +5074,22 @@ QString QgsExpression::formatPreviewString( const QVariant& value )
}
return tr( "<i>&lt;map: %1&gt;</i>" ).arg( mapStr );
}
else if ( value.type() == QVariant::List || value.type() == QVariant::StringList )
{
QString listStr;
const QVariantList list = value.toList();
for ( QVariantList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
{
if ( !listStr.isEmpty() ) listStr.append( ", " );
listStr.append( formatPreviewString( *it ) );
if ( listStr.length() > MAX_PREVIEW + 3 )
{
listStr = QString( tr( "%1..." ) ).arg( listStr.left( MAX_PREVIEW ) );
break;
}
}
return tr( "<i>&lt;list: %1&gt;</i>" ).arg( listStr );
}
else
{
return value.toString();

View File

@ -54,6 +54,7 @@ void QgsExpressionFieldBuffer::writeXml( QDomNode& layerNode, QDomDocument& docu
fldElem.setAttribute( "comment", fld.field.comment() );
fldElem.setAttribute( "length", fld.field.length() );
fldElem.setAttribute( "type", fld.field.type() );
fldElem.setAttribute( "subType", fld.field.subType() );
fldElem.setAttribute( "typeName", fld.field.typeName() );
expressionFieldsElem.appendChild( fldElem );
@ -79,9 +80,10 @@ void QgsExpressionFieldBuffer::readXml( const QDomNode& layerNode )
int precision = field.attribute( "precision" ).toInt();
int length = field.attribute( "length" ).toInt();
QVariant::Type type = static_cast< QVariant::Type >( field.attribute( "type" ).toInt() );
QVariant::Type subType = static_cast< QVariant::Type >( field.attribute( "subType", 0 ).toInt() );
QString typeName = field.attribute( "typeName" );
mExpressions.append( ExpressionField( exp, QgsField( name, type, typeName, length, precision, comment ) ) );
mExpressions.append( ExpressionField( exp, QgsField( name, type, typeName, length, precision, comment, subType ) ) );
}
}
}

View File

@ -44,9 +44,10 @@ QgsField::QgsField( QString nam, QString typ, int len, int prec, bool num,
}
#endif
QgsField::QgsField( const QString& name, QVariant::Type type,
const QString& typeName, int len, int prec, const QString& comment )
const QString& typeName, int len, int prec, const QString& comment,
QVariant::Type subType )
{
d = new QgsFieldPrivate( name, type, typeName, len, prec, comment );
d = new QgsFieldPrivate( name, type, subType, typeName, len, prec, comment );
}
QgsField::QgsField( const QgsField &other )
@ -99,6 +100,11 @@ QVariant::Type QgsField::type() const
return d->type;
}
QVariant::Type QgsField::subType() const
{
return d->subType;
}
QString QgsField::typeName() const
{
return d->typeName;
@ -140,6 +146,11 @@ void QgsField::setType( QVariant::Type type )
d->type = type;
}
void QgsField::setSubType( QVariant::Type subType )
{
d->subType = subType;
}
void QgsField::setTypeName( const QString& typeName )
{
d->typeName = typeName;
@ -292,14 +303,15 @@ QDataStream& operator<<( QDataStream& out, const QgsField& field )
out << field.comment();
out << field.alias();
out << field.defaultValueExpression();
out << static_cast< quint32 >( field.subType() );
return out;
}
QDataStream& operator>>( QDataStream& in, QgsField& field )
{
quint32 type, length, precision;
quint32 type, subType, length, precision;
QString name, typeName, comment, alias, defaultValueExpression;
in >> name >> type >> typeName >> length >> precision >> comment >> alias >> defaultValueExpression;
in >> name >> type >> typeName >> length >> precision >> comment >> alias >> defaultValueExpression >> subType;
field.setName( name );
field.setType( static_cast< QVariant::Type >( type ) );
field.setTypeName( typeName );
@ -308,6 +320,7 @@ QDataStream& operator>>( QDataStream& in, QgsField& field )
field.setComment( comment );
field.setAlias( alias );
field.setDefaultValueExpression( defaultValueExpression );
field.setSubType( static_cast< QVariant::Type >( subType ) );
return in;
}

View File

@ -66,13 +66,17 @@ class CORE_EXPORT QgsField
* @param prec Field precision. Usually decimal places but may also be
* used in conjunction with other fields types (eg. variable character fields)
* @param comment Comment for the field
* @param subType If the field is a collection, its element's type. When
* all the elements don't need to have the same type, leave
* this to QVariant::Invalid.
*/
QgsField( const QString& name = QString(),
QVariant::Type type = QVariant::Invalid,
const QString& typeName = QString(),
int len = 0,
int prec = 0,
const QString& comment = QString() );
const QString& comment = QString(),
QVariant::Type subType = QVariant::Invalid );
/** Copy constructor
*/
@ -105,6 +109,14 @@ class CORE_EXPORT QgsField
//! Gets variant type of the field as it will be retrieved from data source
QVariant::Type type() const;
/**
* If the field is a collection, gets its element's type.
* When all the elements don't need to have the same type, this returns
* QVariant::Invalid.
* @note added in QGIS 3.0
*/
QVariant::Type subType() const;
/**
* Gets the field type. Field types vary depending on the data source. Examples
* are char, int, double, blob, geometry, etc. The type is stored exactly as
@ -149,6 +161,14 @@ class CORE_EXPORT QgsField
*/
void setType( QVariant::Type type );
/**
* If the field is a collection, set its element's type.
* When all the elements don't need to have the same type, set this to
* QVariant::Invalid.
* @note added in QGIS 3.0
*/
void setSubType( QVariant::Type subType );
/**
* Set the field type.
* @param typeName Field type

View File

@ -44,12 +44,14 @@ class QgsFieldPrivate : public QSharedData
QgsFieldPrivate( const QString& name = QString(),
QVariant::Type type = QVariant::Invalid,
QVariant::Type subType = QVariant::Invalid,
const QString& typeName = QString(),
int len = 0,
int prec = 0,
const QString& comment = QString() )
: name( name )
, type( type )
, subType( subType )
, typeName( typeName )
, length( len )
, precision( prec )
@ -61,6 +63,7 @@ class QgsFieldPrivate : public QSharedData
: QSharedData( other )
, name( other.name )
, type( other.type )
, subType( other.subType )
, typeName( other.typeName )
, length( other.length )
, precision( other.precision )
@ -74,7 +77,7 @@ class QgsFieldPrivate : public QSharedData
bool operator==( const QgsFieldPrivate& other ) const
{
return (( name == other.name ) && ( type == other.type )
return (( name == other.name ) && ( type == other.type ) && ( subType == other.subType )
&& ( length == other.length ) && ( precision == other.precision )
&& ( alias == other.alias ) && ( defaultValueExpression == other.defaultValueExpression ) );
}
@ -85,6 +88,9 @@ class QgsFieldPrivate : public QSharedData
//! Variant type
QVariant::Type type;
//! If the variant is a collection, its element's type
QVariant::Type subType;
//! Type name from provider
QString typeName;

View File

@ -352,7 +352,7 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
struct NativeType
{
NativeType( const QString& typeDesc, const QString& typeName, QVariant::Type type, int minLen = 0, int maxLen = 0, int minPrec = 0, int maxPrec = 0 )
NativeType( const QString& typeDesc, const QString& typeName, QVariant::Type type, int minLen = 0, int maxLen = 0, int minPrec = 0, int maxPrec = 0, QVariant::Type subType = QVariant::Invalid )
: mTypeDesc( typeDesc )
, mTypeName( typeName )
, mType( type )
@ -360,6 +360,7 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
, mMaxLen( maxLen )
, mMinPrec( minPrec )
, mMaxPrec( maxPrec )
, mSubType( subType )
{}
QString mTypeDesc;
@ -369,6 +370,7 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
int mMaxLen;
int mMinPrec;
int mMaxPrec;
QVariant::Type mSubType;
};
/**

View File

@ -118,6 +118,8 @@ SET(QGIS_GUI_SRCS
editorwidgets/qgshiddenwidgetfactory.cpp
editorwidgets/qgskeyvaluewidgetfactory.cpp
editorwidgets/qgskeyvaluewidgetwrapper.cpp
editorwidgets/qgslistwidgetfactory.cpp
editorwidgets/qgslistwidgetwrapper.cpp
editorwidgets/qgsmultiedittoolbutton.cpp
editorwidgets/qgsphotoconfigdlg.cpp
editorwidgets/qgsphotowidgetwrapper.cpp
@ -234,6 +236,7 @@ SET(QGIS_GUI_SRCS
qgshtmlannotationitem.cpp
qgsidentifymenu.cpp
qgskeyvaluewidget.cpp
qgslistwidget.cpp
qgslegendfilterbutton.cpp
qgslegendinterface.cpp
qgslimitedrandomcolorrampdialog.cpp
@ -299,6 +302,7 @@ SET(QGIS_GUI_SRCS
qgssublayersdialog.cpp
qgssqlcomposerdialog.cpp
qgssvgannotationitem.cpp
qgstablewidgetbase.cpp
qgstabwidget.cpp
qgstablewidgetitem.cpp
qgstextannotationitem.cpp
@ -393,6 +397,7 @@ SET(QGIS_GUI_MOC_HDRS
qgshtmlannotationitem.h
qgsidentifymenu.h
qgskeyvaluewidget.h
qgslistwidget.h
qgslegendfilterbutton.h
qgslegendinterface.h
qgslimitedrandomcolorrampdialog.h
@ -452,6 +457,7 @@ SET(QGIS_GUI_MOC_HDRS
qgsslider.h
qgssqlcomposerdialog.h
qgssublayersdialog.h
qgstablewidgetbase.h
qgstabwidget.h
qgstreewidgetitem.h
qgsunitselectionwidget.h
@ -568,6 +574,7 @@ SET(QGIS_GUI_MOC_HDRS
editorwidgets/qgsfilenamewidgetwrapper.h
editorwidgets/qgshiddenwidgetwrapper.h
editorwidgets/qgskeyvaluewidgetwrapper.h
editorwidgets/qgslistwidgetwrapper.h
editorwidgets/qgsmultiedittoolbutton.h
editorwidgets/qgsphotoconfigdlg.h
editorwidgets/qgsphotowidgetwrapper.h
@ -684,6 +691,7 @@ SET(QGIS_GUI_HDRS
editorwidgets/qgsfilenamewidgetfactory.h
editorwidgets/qgshiddenwidgetfactory.h
editorwidgets/qgskeyvaluewidgetfactory.h
editorwidgets/qgslistwidgetfactory.h
editorwidgets/qgsphotowidgetfactory.h
editorwidgets/qgsrangewidgetfactory.h
editorwidgets/qgsrelationreferencefactory.h
@ -739,7 +747,6 @@ SET(QGIS_GUI_UI_HDRS
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsexpressionbuilder.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsexpressionselectiondialogbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsgenericprojectionselectorbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgskeyvaluewidgetbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsmessagelogviewer.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsmessageviewer.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsowssourceselectbase.h
@ -747,6 +754,7 @@ SET(QGIS_GUI_UI_HDRS
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsquerybuilderbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgssqlcomposerdialogbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgssublayersdialogbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgstablewidgetbase.h
)
IF(ENABLE_MODELTEST)

View File

@ -33,6 +33,7 @@
#include "qgsfilenamewidgetfactory.h"
#include "qgshiddenwidgetfactory.h"
#include "qgskeyvaluewidgetfactory.h"
#include "qgslistwidgetfactory.h"
#include "qgsphotowidgetfactory.h"
#include "qgsrangewidgetfactory.h"
#include "qgsrelationreferencefactory.h"
@ -75,6 +76,7 @@ void QgsEditorWidgetRegistry::initEditors( QgsMapCanvas *mapCanvas, QgsMessageBa
reg->registerWidget( "DateTime", new QgsDateTimeEditFactory( tr( "Date/Time" ) ) );
reg->registerWidget( "ExternalResource", new QgsExternalResourceWidgetFactory( tr( "External Resource" ) ) );
reg->registerWidget( "KeyValue", new QgsKeyValueWidgetFactory( tr( "Key/Value" ) ) );
reg->registerWidget( "List", new QgsListWidgetFactory( tr( "List" ) ) );
}
QgsEditorWidgetRegistry::QgsEditorWidgetRegistry()

View File

@ -18,7 +18,7 @@
#include "qgsvectordataprovider.h"
#include "qgsfield.h"
#include <QWidget>
#include <QTableView>
QgsEditorWidgetWrapper::QgsEditorWidgetWrapper( QgsVectorLayer* vl, int fieldIdx, QWidget* editor, QWidget* parent )
: QgsWidgetWrapper( vl, editor, parent )
@ -169,3 +169,10 @@ bool QgsEditorWidgetWrapper::isValidConstraint() const
{
return mValidConstraint;
}
bool QgsEditorWidgetWrapper::isInTable( const QWidget* parent )
{
if ( !parent ) return false;
if ( qobject_cast<const QTableView*>( parent ) ) return true;
return isInTable( parent->parentWidget() );
}

View File

@ -95,6 +95,13 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
*/
static QgsEditorWidgetWrapper* fromWidget( QWidget* widget );
/**
* Check if the given widget or one of its parent is a QTableView.
* @param parent the widget to check
* @return true if yes
*/
static bool isInTable( const QWidget* parent );
/**
* Is used to enable or disable the edit functionality of the managed widget.
* By default this will enable or disable the whole widget

View File

@ -33,13 +33,6 @@ void QgsKeyValueWidgetWrapper::showIndeterminateState()
mWidget->setMap( QVariantMap() );
}
static bool isInTable( const QWidget* parent )
{
if ( !parent ) return false;
if ( qobject_cast<const QTableView*>( parent ) ) return true;
return isInTable( parent->parentWidget() );
}
QWidget* QgsKeyValueWidgetWrapper::createWidget( QWidget* parent )
{
if ( isInTable( parent ) )
@ -66,7 +59,7 @@ void QgsKeyValueWidgetWrapper::initWidget( QWidget* editor )
mWidget = editor->findChild<QgsKeyValueWidget*>();
}
connect( mWidget, SIGNAL( valueChanged( const QVariant& ) ), this, SIGNAL( valueChanged( const QVariant& ) ) );
connect( mWidget, SIGNAL( valueChanged() ), this, SLOT( onValueChanged() ) );
}
bool QgsKeyValueWidgetWrapper::valid() const
@ -82,4 +75,9 @@ void QgsKeyValueWidgetWrapper::setValue( const QVariant& value )
void QgsKeyValueWidgetWrapper::updateConstraintWidgetStatus( bool /*constraintValid*/ )
{
// Nothing
}
}
void QgsKeyValueWidgetWrapper::onValueChanged()
{
emit valueChanged( value() );
}

View File

@ -47,6 +47,9 @@ class GUI_EXPORT QgsKeyValueWidgetWrapper : public QgsEditorWidgetWrapper
public slots:
void setValue( const QVariant& value ) override;
private slots:
void onValueChanged();
private:
void updateConstraintWidgetStatus( bool constraintValid ) override;

View File

@ -0,0 +1,97 @@
/***************************************************************************
qgslistwidgetfactory.cpp
--------------------------------------
Date : 09.2016
Copyright : (C) 2016 Patrick Valsecchi
Email : patrick.valsecchi@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 "qgslistwidgetfactory.h"
#include "qgslistwidgetwrapper.h"
#include "qgsdummyconfigdlg.h"
#include "qgsfield.h"
#include "qgsvectorlayer.h"
#include "qgseditorwidgetregistry.h"
#include <QVariant>
#include <QSettings>
QgsListWidgetFactory::QgsListWidgetFactory( const QString& name ):
QgsEditorWidgetFactory( name )
{
}
QgsEditorWidgetWrapper* QgsListWidgetFactory::create( QgsVectorLayer *vl, int fieldIdx, QWidget *editor, QWidget *parent ) const
{
return new QgsListWidgetWrapper( vl, fieldIdx, editor, parent );
}
QgsEditorConfigWidget* QgsListWidgetFactory::configWidget( QgsVectorLayer *vl, int fieldIdx, QWidget *parent ) const
{
Q_UNUSED( vl );
Q_UNUSED( fieldIdx );
Q_UNUSED( parent );
return new QgsDummyConfigDlg( vl, fieldIdx, parent, QObject::tr( "List field" ) );
}
QgsEditorWidgetConfig QgsListWidgetFactory::readConfig( const QDomElement &configElement, QgsVectorLayer *layer, int fieldIdx )
{
Q_UNUSED( configElement );
Q_UNUSED( layer );
Q_UNUSED( fieldIdx );
return QgsEditorWidgetConfig();
}
void QgsListWidgetFactory::writeConfig( const QgsEditorWidgetConfig& config, QDomElement& configElement, QDomDocument& doc, const QgsVectorLayer* layer, int fieldIdx )
{
Q_UNUSED( config );
Q_UNUSED( configElement );
Q_UNUSED( doc );
Q_UNUSED( layer );
Q_UNUSED( fieldIdx );
}
QString QgsListWidgetFactory::representValue( QgsVectorLayer* vl, int fieldIdx, const QgsEditorWidgetConfig& config, const QVariant& cache, const QVariant& value ) const
{
Q_UNUSED( vl );
Q_UNUSED( fieldIdx );
Q_UNUSED( config );
Q_UNUSED( cache );
if ( value.isNull() )
{
QSettings settings;
return settings.value( "qgis/nullValue", "NULL" ).toString();
}
QString result;
const QVariantList list = value.toList();
for ( QVariantList::const_iterator i = list.constBegin(); i != list.constEnd(); ++i )
{
if ( !result.isEmpty() ) result.append( ", " );
result.append( i->toString() );
}
return result;
}
Qt::AlignmentFlag QgsListWidgetFactory::alignmentFlag( QgsVectorLayer *vl, int fieldIdx, const QgsEditorWidgetConfig &config ) const
{
Q_UNUSED( vl );
Q_UNUSED( fieldIdx );
Q_UNUSED( config );
return Qt::AlignLeft;
}
unsigned int QgsListWidgetFactory::fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const
{
const QgsField field = vl->fields().field( fieldIdx );
return ( field.type() == QVariant::List || field.type() == QVariant::StringList ) && field.subType() != QVariant::Invalid ? 20 : 0;
}

View File

@ -0,0 +1,46 @@
/***************************************************************************
qgslistwidgetfactory.h
--------------------------------------
Date : 09.2016
Copyright : (C) 2016 Patrick Valsecchi
Email : patrick.valsecchi@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 QGSLISTWIDGETFACTORY_H
#define QGSLISTWIDGETFACTORY_H
#include "qgseditorwidgetfactory.h"
/** @ingroup gui
* Factory for widgets for editing a QVariantList or a QStringList
* @note added in QGIS 3.0
* @note not available in Python bindings
*/
class GUI_EXPORT QgsListWidgetFactory : public QgsEditorWidgetFactory
{
public:
/**
* Constructor.
*/
QgsListWidgetFactory( const QString& name );
// QgsEditorWidgetFactory interface
public:
QgsEditorWidgetWrapper* create( QgsVectorLayer *vl, int fieldIdx, QWidget *editor, QWidget *parent ) const override;
//QgsSearchWidgetWrapper* createSearchWidget( QgsVectorLayer* vl, int fieldIdx, QWidget* parent ) const override;
QgsEditorConfigWidget* configWidget( QgsVectorLayer *vl, int fieldIdx, QWidget *parent ) const override;
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;
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;
unsigned int fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const override;
};
#endif // QGSLISTWIDGETFACTORY_H

View File

@ -0,0 +1,93 @@
/***************************************************************************
qgslistwidgetwrapper.cpp
--------------------------------------
Date : 09.2016
Copyright : (C) 2016 Patrick Valsecchi
Email : patrick.valsecchi@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 "qgslistwidgetwrapper.h"
#include "qgslistwidget.h"
#include "qgsattributeform.h"
QgsListWidgetWrapper::QgsListWidgetWrapper( QgsVectorLayer* vl, int fieldIdx, QWidget* editor, QWidget* parent ):
QgsEditorWidgetWrapper( vl, fieldIdx, editor, parent ), mWidget( nullptr )
{
}
void QgsListWidgetWrapper::showIndeterminateState()
{
mWidget->setList( QVariantList() );
}
QWidget* QgsListWidgetWrapper::createWidget( QWidget* parent )
{
if ( isInTable( parent ) )
{
// if to be put in a table, draw a border and set a decent size
QFrame* ret = new QFrame( parent );
ret->setFrameShape( QFrame::StyledPanel );
QHBoxLayout* layout = new QHBoxLayout( ret );
layout->addWidget( new QgsListWidget( field().subType(), ret ) );
ret->setMinimumSize( QSize( 320, 110 ) );
return ret;
}
else
{
return new QgsListWidget( field().subType(), parent );
}
}
void QgsListWidgetWrapper::initWidget( QWidget* editor )
{
mWidget = qobject_cast<QgsListWidget*>( editor );
if ( !mWidget )
{
mWidget = editor->findChild<QgsListWidget*>();
}
connect( mWidget, SIGNAL( valueChanged() ), this, SLOT( onValueChanged() ) );
}
bool QgsListWidgetWrapper::valid() const
{
return mWidget ? mWidget->valid() : true;
}
void QgsListWidgetWrapper::setValue( const QVariant& value )
{
mWidget->setList( value.toList() );
}
QVariant QgsListWidgetWrapper::value() const
{
QVariant::Type type = field().type();
if ( !mWidget ) return QVariant( type );
if ( type == QVariant::StringList )
{
QStringList result;
const QVariantList list = mWidget->list();
for ( QVariantList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
result.append( it->toString() );
return result;
}
else
return QVariant( mWidget->list() );
}
void QgsListWidgetWrapper::onValueChanged()
{
emit valueChanged( value() );
}
void QgsListWidgetWrapper::updateConstraintWidgetStatus( bool /*constraintValid*/ )
{
// Nothing
}

View File

@ -0,0 +1,59 @@
/***************************************************************************
qgslistwidgetwrapper.h
--------------------------------------
Date : 09.2016
Copyright : (C) 2016 Patrick Valsecchi
Email : patrick.valsecchi@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 QGSLISTWIDGETWRAPPER_H
#define QGSLISTWIDGETWRAPPER_H
#include "qgseditorwidgetwrapper.h"
class QgsListWidget;
/** @ingroup gui
* Wraps a list widget.
* @note added in QGIS 3.0
* @note not available in Python bindings
*/
class GUI_EXPORT QgsListWidgetWrapper : public QgsEditorWidgetWrapper
{
Q_OBJECT
public:
/**
* Constructor.
*/
explicit QgsListWidgetWrapper( QgsVectorLayer* vl, int fieldIdx, QWidget* editor = nullptr, QWidget* parent = nullptr );
// QgsEditorWidgetWrapper interface
public:
QVariant value() const override;
void showIndeterminateState() override;
protected:
QWidget* createWidget( QWidget* parent ) override;
void initWidget( QWidget* editor ) override;
bool valid() const override;
public slots:
void setValue( const QVariant& value ) override;
private slots:
void onValueChanged();
private:
void updateConstraintWidgetStatus( bool constraintValid ) override;
QgsListWidget* mWidget;
};
#endif // QGSLISTWIDGETWRAPPER_H

View File

@ -16,13 +16,10 @@
#include "qgskeyvaluewidget.h"
QgsKeyValueWidget::QgsKeyValueWidget( QWidget* parent )
: QWidget( parent )
: QgsTableWidgetBase( parent )
, mModel( this )
{
setupUi( this );
tableView->setModel( &mModel );
connect( tableView->selectionModel(), SIGNAL( selectionChanged( const QItemSelection &, const QItemSelection & ) ), this, SLOT( onSelectionChanged() ) );
connect( &mModel, SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ), this, SLOT( onValueChanged() ) );
init( &mModel );
}
void QgsKeyValueWidget::setMap( const QVariantMap& map )
@ -31,37 +28,6 @@ void QgsKeyValueWidget::setMap( const QVariantMap& map )
mModel.setMap( map );
}
void QgsKeyValueWidget::on_addButton_clicked()
{
const QItemSelectionModel *select = tableView->selectionModel();
const int pos = select->hasSelection() ? select->selectedRows()[0].row() : 0;
mModel.insertRows( pos, 1 );
const QModelIndex index = mModel.index( pos, 0 );
tableView->scrollTo( index );
tableView->edit( index );
tableView->selectRow( pos );
}
void QgsKeyValueWidget::on_removeButton_clicked()
{
const QItemSelectionModel *select = tableView->selectionModel();
// The UI is configured to have single row selection.
if ( select->hasSelection() )
{
mModel.removeRows( select->selectedRows()[0].row(), 1 );
}
}
void QgsKeyValueWidget::onSelectionChanged()
{
removeButton->setEnabled( tableView->selectionModel()->hasSelection() );
}
void QgsKeyValueWidget::onValueChanged()
{
emit valueChanged( QVariant( map() ) );
}
///@cond PRIVATE
void QgsKeyValueModel::setMap( const QVariantMap& map )
{

View File

@ -16,7 +16,7 @@
#ifndef QGSKEYVALUEWIDGET_H
#define QGSKEYVALUEWIDGET_H
#include "ui_qgskeyvaluewidgetbase.h"
#include "qgstablewidgetbase.h"
#include <QAbstractTableModel>
#include <QMap>
@ -56,7 +56,7 @@ class GUI_EXPORT QgsKeyValueModel : public QAbstractTableModel
* Widget allowing to edit a QVariantMap, using a table.
* @note added in QGIS 3.0
*/
class GUI_EXPORT QgsKeyValueWidget: public QWidget, public Ui::QgsKeyValueWidgetBase
class GUI_EXPORT QgsKeyValueWidget: public QgsTableWidgetBase
{
Q_OBJECT
Q_PROPERTY( QVariantMap map READ map WRITE setMap )
@ -77,24 +77,6 @@ class GUI_EXPORT QgsKeyValueWidget: public QWidget, public Ui::QgsKeyValueWidget
*/
QVariantMap map() const { return mModel.map(); }
signals:
/**
* Emitted each time a key or a value is changed.
* @param value the new value
*/
void valueChanged( const QVariant& value );
private slots:
void on_addButton_clicked();
void on_removeButton_clicked();
/**
* Called when the selection is changed to enable/disable the delete button.
*/
void onSelectionChanged();
void onValueChanged();
private:
QgsKeyValueModel mModel;
};

140
src/gui/qgslistwidget.cpp Normal file
View File

@ -0,0 +1,140 @@
/***************************************************************************
qgslistwidget.cpp
--------------------------------------
Date : 08.2016
Copyright : (C) 2016 Patrick Valsecchi
Email : patrick.valsecchi@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 "qgslistwidget.h"
QgsListWidget::QgsListWidget( QVariant::Type subType, QWidget* parent )
: QgsTableWidgetBase( parent )
, mModel( subType, this )
, mSubType( subType )
{
init( &mModel );
}
void QgsListWidget::setList( const QVariantList& list )
{
removeButton->setEnabled( false );
mModel.setList( list );
}
///@cond PRIVATE
QgsListModel::QgsListModel( QVariant::Type subType, QObject *parent ) :
QAbstractTableModel( parent ),
mSubType( subType )
{
}
void QgsListModel::setList( const QVariantList& list )
{
emit beginResetModel();
mLines = list;
emit endResetModel();
}
QVariantList QgsListModel::list() const
{
QVariantList result;
for ( QVariantList::const_iterator it = mLines.constBegin(); it != mLines.constEnd(); ++it )
{
QVariant cur = *it;
if ( cur.convert( mSubType ) )
result.append( cur );
}
return result;
}
bool QgsListModel::valid() const
{
for ( QVariantList::const_iterator it = mLines.constBegin(); it != mLines.constEnd(); ++it )
{
QVariant cur = *it;
if ( !cur.convert( mSubType ) ) return false;
}
return true;
}
int QgsListModel::rowCount( const QModelIndex& parent ) const
{
Q_UNUSED( parent );
return mLines.count();
}
int QgsListModel::columnCount( const QModelIndex & parent ) const
{
Q_UNUSED( parent );
return 1;
}
QVariant QgsListModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0 )
{
return QObject::tr( "Value" );
}
return QVariant();
}
QVariant QgsListModel::data( const QModelIndex& index, int role ) const
{
if ( index.row() < 0 ||
index.row() >= mLines.count() ||
( role != Qt::DisplayRole && role != Qt::EditRole ) ||
index.column() != 0 )
{
return QVariant( mSubType );
}
return mLines.at( index.row() );
}
bool QgsListModel::setData( const QModelIndex & index, const QVariant & value, int role )
{
if ( index.row() < 0 || index.row() >= mLines.count() ||
index.column() != 0 || role != Qt::EditRole )
{
return false;
}
mLines[index.row()] = value.toString();
emit dataChanged( index, index );
return true;
}
Qt::ItemFlags QgsListModel::flags( const QModelIndex &index ) const
{
return QAbstractTableModel::flags( index ) | Qt::ItemIsEditable;
}
bool QgsListModel::insertRows( int position, int rows, const QModelIndex & parent )
{
Q_UNUSED( parent );
beginInsertRows( QModelIndex(), position, position + rows - 1 );
for ( int i = 0; i < rows; ++i )
{
mLines.insert( position, QVariant( mSubType ) );
}
endInsertRows();
return true;
}
bool QgsListModel::removeRows( int position, int rows, const QModelIndex &parent )
{
Q_UNUSED( parent );
beginRemoveRows( QModelIndex(), position, position + rows - 1 );
for ( int i = 0; i < rows; ++i )
mLines.removeAt( position );
endRemoveRows();
return true;
}
///@endcond

92
src/gui/qgslistwidget.h Normal file
View File

@ -0,0 +1,92 @@
/***************************************************************************
qgslistwidget.h
--------------------------------------
Date : 08.2016
Copyright : (C) 2016 Patrick Valsecchi
Email : patrick.valsecchi@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 QGSLISTWIDGET_H
#define QGSLISTWIDGET_H
#include "qgstablewidgetbase.h"
#include <QAbstractTableModel>
#include <QVariant>
///@cond PRIVATE
/** @ingroup gui
* Table model to edit a QVariantList.
* @note added in QGIS 3.0
* @note not available in Python bindings
*/
class GUI_EXPORT QgsListModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit QgsListModel( QVariant::Type subType, QObject *parent = 0 );
void setList( const QVariantList& list );
QVariantList list() const;
bool valid() const;
int rowCount( const QModelIndex& parent = QModelIndex() ) const override;
int columnCount( const QModelIndex& parent = QModelIndex() ) const override;
QVariant headerData( int section, Qt::Orientation orientation, int role ) const override;
QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const override;
bool setData( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ) override;
Qt::ItemFlags flags( const QModelIndex &index ) const override;
bool insertRows( int position, int rows, const QModelIndex & parent = QModelIndex() ) override;
bool removeRows( int position, int rows, const QModelIndex &parent = QModelIndex() ) override;
private:
QVariantList mLines;
QVariant::Type mSubType;
};
///@endcond
/** \ingroup gui
* Widget allowing to edit a QVariantList, using a table.
* @note added in QGIS 3.0
*/
class GUI_EXPORT QgsListWidget: public QgsTableWidgetBase
{
Q_OBJECT
Q_PROPERTY( QVariantList list READ list WRITE setList )
public:
/**
* Constructor.
*/
explicit QgsListWidget( QVariant::Type subType, QWidget* parent = nullptr );
/**
* Set the initial value of the widget.
*/
void setList( const QVariantList& list );
/**
* Get the edit value.
* @return the QVariantList
*/
QVariantList list() const { return mModel.list(); }
/**
* Check the content is valid
* @return true if valid
*/
bool valid() const { return mModel.valid(); }
private:
QgsListModel mModel;
QVariant::Type mSubType;
};
#endif // QGSKEYVALUEWIDGET_H

View File

@ -0,0 +1,56 @@
/***************************************************************************
qgstablewidgetbase.cpp
--------------------------------------
Date : 08.2016
Copyright : (C) 2016 Patrick Valsecchi
Email : patrick.valsecchi@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 "qgstablewidgetbase.h"
QgsTableWidgetBase::QgsTableWidgetBase( QWidget* parent )
: QWidget( parent )
{
setupUi( this );
}
void QgsTableWidgetBase::init( QAbstractTableModel* model )
{
tableView->setModel( model );
connect( tableView->selectionModel(), SIGNAL( selectionChanged( const QItemSelection &, const QItemSelection & ) ), this, SLOT( onSelectionChanged() ) );
connect( model, SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ), this, SIGNAL( valueChanged() ) );
}
void QgsTableWidgetBase::on_addButton_clicked()
{
const QItemSelectionModel *select = tableView->selectionModel();
const int pos = select->hasSelection() ? select->selectedRows()[0].row() : 0;
QAbstractItemModel* model = tableView->model();
model->insertRows( pos, 1 );
const QModelIndex index = model->index( pos, 0 );
tableView->scrollTo( index );
tableView->edit( index );
tableView->selectRow( pos );
}
void QgsTableWidgetBase::on_removeButton_clicked()
{
const QItemSelectionModel *select = tableView->selectionModel();
// The UI is configured to have single row selection.
if ( select->hasSelection() )
{
tableView->model()->removeRows( select->selectedRows()[0].row(), 1 );
}
}
void QgsTableWidgetBase::onSelectionChanged()
{
removeButton->setEnabled( tableView->selectionModel()->hasSelection() );
}

View File

@ -0,0 +1,71 @@
/***************************************************************************
qgstablewidgetbase.h
--------------------------------------
Date : 09.2016
Copyright : (C) 2016 Patrick Valsecchi
Email : patrick.valsecchi@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 QGSTABLEWIDGETBASE_H
#define QGSTABLEWIDGETBASE_H
#include "ui_qgstablewidgetbase.h"
#include <QAbstractTableModel>
#include <QVariant>
/** \ingroup gui
* Base widget allowing to edit a collection, using a table.
*
* This widget includes buttons to add and remove rows.
* Child classes must call init(QAbstractTableModel*) from their constructor.
*
* @note added in QGIS 3.0
*/
class GUI_EXPORT QgsTableWidgetBase: public QWidget, public Ui::QgsTableWidgetBase
{
Q_OBJECT
public:
/**
* Constructor.
*/
explicit QgsTableWidgetBase( QWidget* parent );
protected:
/**
* Initialise the table with the given model.
* Must be called once in the child class' constructor.
*/
void init( QAbstractTableModel* model );
signals:
/**
* Emitted each time a key or a value is changed.
*/
void valueChanged();
private slots:
/**
* Called when the add button is clicked.
*/
void on_addButton_clicked();
/**
* Called when the remove button is clicked.
*/
void on_removeButton_clicked();
/**
* Called when the selection is changed to enable/disable the delete button.
*/
void onSelectionChanged();
};
#endif // QGSTABLEWIDGETBASE_H

View File

@ -110,14 +110,15 @@ QgsMemoryProvider::QgsMemoryProvider( const QString& uri )
QRegExp reFieldDef( "\\:"
"(int|integer|long|int8|real|double|string|date|time|datetime)" // type
"(?:\\((\\-?\\d+)" // length
"(?:\\,(\\d+))?" // precision
"\\))?"
"(?:\\,(\\d+))?" // precision
"\\))?(\\[\\])?" // array
"$", Qt::CaseInsensitive );
QStringList fields = url.allQueryItemValues( "field" );
for ( int i = 0; i < fields.size(); i++ )
{
QString name = fields.at( i );
QVariant::Type type = QVariant::String;
QVariant::Type subType = QVariant::Invalid;
QString typeName( "string" );
int length = 255;
int precision = 0;
@ -173,9 +174,14 @@ QgsMemoryProvider::QgsMemoryProvider( const QString& uri )
{
precision = reFieldDef.cap( 3 ).toInt();
}
if ( reFieldDef.cap( 4 ) != "" )
{ //array
subType = type;
type = ( subType == QVariant::String ? QVariant::StringList : QVariant::List );
}
}
if ( name != "" )
attributes.append( QgsField( name, type, typeName, length, precision ) );
attributes.append( QgsField( name, type, typeName, length, precision, "", subType ) );
}
addAttributes( attributes );
}
@ -384,6 +390,8 @@ bool QgsMemoryProvider::addAttributes( const QList<QgsField> &attributes )
case QVariant::Time:
case QVariant::DateTime:
case QVariant::LongLong:
case QVariant::StringList:
case QVariant::List:
break;
default:
QgsDebugMsg( "Field type not supported: " + it->typeName() );

View File

@ -961,6 +961,20 @@ static QString quotedMap( const QVariantMap& map )
return "E'" + ret + "'::hstore";
}
static QString quotedList( const QVariantList& list )
{
QString ret;
for ( QVariantList::const_iterator i = list.constBegin(); i != list.constEnd(); ++i )
{
if ( !ret.isEmpty() )
{
ret += ",";
}
ret.append( doubleQuotedMapValue( i->toString() ) );
}
return "E'{" + ret + "}'";
}
QString QgsPostgresConn::quotedValue( const QVariant& value )
{
if ( value.isNull() )
@ -979,6 +993,10 @@ QString QgsPostgresConn::quotedValue( const QVariant& value )
case QVariant::Map:
return quotedMap( value.toMap() );
case QVariant::StringList:
case QVariant::List:
return quotedList( value.toList() );
case QVariant::String:
default:
return quotedString( value.toString() );

View File

@ -739,7 +739,7 @@ bool QgsPostgresFeatureIterator::getFeature( QgsPostgresResult &queryResult, int
{
QgsField fld = mSource->mFields.at( idx );
QVariant v = QgsPostgresProvider::convertValue( fld.type(), queryResult.PQgetvalue( row, col ) );
QVariant v = QgsPostgresProvider::convertValue( fld.type(), fld.subType(), queryResult.PQgetvalue( row, col ) );
primaryKeyVals << v;
if ( !subsetOfAttributes || fetchAttributes.contains( idx ) )
@ -781,7 +781,8 @@ void QgsPostgresFeatureIterator::getFeatureAttribute( int idx, QgsPostgresResult
if ( mSource->mPrimaryKeyAttrs.contains( idx ) )
return;
QVariant v = QgsPostgresProvider::convertValue( mSource->mFields.at( idx ).type(), queryResult.PQgetvalue( row, col ) );
const QgsField fld = mSource->mFields.at( idx );
QVariant v = QgsPostgresProvider::convertValue( fld.type(), fld.subType(), queryResult.PQgetvalue( row, col ) );
feature.setAttribute( idx, v );
col++;

View File

@ -220,7 +220,11 @@ QgsPostgresProvider::QgsPostgresProvider( QString const & uri )
<< QgsVectorDataProvider::NativeType( tr( "Date & Time" ), "timestamp without time zone", QVariant::DateTime, -1, -1, -1, -1 )
// complex types
<< QgsVectorDataProvider::NativeType( tr( "Map" ), "hstore", QVariant::Map, -1, -1, -1, -1 )
<< QgsVectorDataProvider::NativeType( tr( "Map" ), "hstore", QVariant::Map, -1, -1, -1, -1, QVariant::String )
<< QgsVectorDataProvider::NativeType( tr( "Array of number (integer - 32bit)" ), "int4[]", QVariant::List, -1, -1, -1, -1, QVariant::Int )
<< QgsVectorDataProvider::NativeType( tr( "Array of number (integer - 64bit)" ), "int8[]", QVariant::List, -1, -1, -1, -1, QVariant::LongLong )
<< QgsVectorDataProvider::NativeType( tr( "Array of number (double)" ), "double precision[]", QVariant::List, -1, -1, -1, -1, QVariant::Double )
<< QgsVectorDataProvider::NativeType( tr( "Array of text" ), "text[]", QVariant::StringList, -1, -1, -1, -1, QVariant::String )
;
QString key;
@ -356,9 +360,10 @@ static bool operator<( const QVariant &a, const QVariant &b )
return a.toLongLong() < b.toLongLong();
case QVariant::List:
case QVariant::StringList:
{
const QList<QVariant> &al = a.toList();
const QList<QVariant> &bl = b.toList();
const QList<QVariant> al = a.toList();
const QList<QVariant> bl = b.toList();
int i, n = qMin( al.size(), bl.size() );
for ( i = 0; i < n && al[i].type() == bl[i].type() && al[i].isNull() == bl[i].isNull() && al[i] == bl[i]; i++ )
@ -370,21 +375,6 @@ static bool operator<( const QVariant &a, const QVariant &b )
return al[i] < bl[i];
}
case QVariant::StringList:
{
const QStringList &al = a.toStringList();
const QStringList &bl = b.toStringList();
int i, n = qMin( al.size(), bl.size() );
for ( i = 0; i < n && al[i] == bl[i]; i++ )
;
if ( i == n )
return al.size() < bl.size();
else
return al[i] < bl[i];
}
case QVariant::Map:
return a.toMap() < b.toMap();
@ -884,6 +874,7 @@ bool QgsPostgresProvider::loadFields()
QString fieldComment = descrMap[tableoid][attnum];
QVariant::Type fieldType;
QVariant::Type fieldSubType = QVariant::Invalid;
if ( fieldTType == "b" )
{
@ -1025,6 +1016,7 @@ bool QgsPostgresProvider::loadFields()
else if ( fieldTypeName == "hstore" )
{
fieldType = QVariant::Map;
fieldSubType = QVariant::String;
fieldSize = -1;
}
else
@ -1036,7 +1028,8 @@ bool QgsPostgresProvider::loadFields()
if ( isArray )
{
fieldTypeName = '_' + fieldTypeName;
fieldType = QVariant::String;
fieldSubType = fieldType;
fieldType = ( fieldType == QVariant::String ? QVariant::StringList : QVariant::List );
fieldSize = -1;
}
}
@ -1068,7 +1061,7 @@ bool QgsPostgresProvider::loadFields()
mAttrPalIndexName.insert( i, fieldName );
mDefaultValues.insert( mAttributeFields.size(), defValMap[tableoid][attnum] );
mAttributeFields.append( QgsField( fieldName, fieldType, fieldTypeName, fieldSize, fieldPrec, fieldComment ) );
mAttributeFields.append( QgsField( fieldName, fieldType, fieldTypeName, fieldSize, fieldPrec, fieldComment, fieldSubType ) );
}
setEditorWidgets();
@ -1570,7 +1563,7 @@ QVariant QgsPostgresProvider::minimumValue( int index ) const
sql = QString( "SELECT %1 FROM (%2) foo" ).arg( connectionRO()->fieldExpression( fld ), sql );
QgsPostgresResult rmin( connectionRO()->PQexec( sql ) );
return convertValue( fld.type(), rmin.PQgetvalue( 0, 0 ) );
return convertValue( fld.type(), fld.subType(), rmin.PQgetvalue( 0, 0 ) );
}
catch ( PGFieldNotFound )
{
@ -1609,7 +1602,7 @@ void QgsPostgresProvider::uniqueValues( int index, QList<QVariant> &uniqueValues
if ( res.PQresultStatus() == PGRES_TUPLES_OK )
{
for ( int i = 0; i < res.PQntuples(); i++ )
uniqueValues.append( convertValue( fld.type(), res.PQgetvalue( i, 0 ) ) );
uniqueValues.append( convertValue( fld.type(), fld.subType(), res.PQgetvalue( i, 0 ) ) );
}
}
catch ( PGFieldNotFound )
@ -1746,7 +1739,7 @@ QVariant QgsPostgresProvider::maximumValue( int index ) const
QgsPostgresResult rmax( connectionRO()->PQexec( sql ) );
return convertValue( fld.type(), rmax.PQgetvalue( 0, 0 ) );
return convertValue( fld.type(), fld.subType(), rmax.PQgetvalue( 0, 0 ) );
}
catch ( PGFieldNotFound )
{
@ -1770,7 +1763,7 @@ QVariant QgsPostgresProvider::defaultValue( int fieldId ) const
QgsPostgresResult res( connectionRO()->PQexec( QString( "SELECT %1" ).arg( defVal.toString() ) ) );
return convertValue( fld.type(), res.PQgetvalue( 0, 0 ) );
return convertValue( fld.type(), fld.subType(), res.PQgetvalue( 0, 0 ) );
}
return defVal;
@ -2070,7 +2063,7 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist )
{
QgsField fld = field( attrIdx );
v = paramValue( defaultValues[ i ], defaultValues[ i ] );
features->setAttribute( attrIdx, convertValue( fld.type(), v ) );
features->setAttribute( attrIdx, convertValue( fld.type(), fld.subType(), v ) );
}
else
{
@ -2079,7 +2072,7 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist )
if ( v != value.toString() )
{
QgsField fld = field( attrIdx );
features->setAttribute( attrIdx, convertValue( fld.type(), v ) );
features->setAttribute( attrIdx, convertValue( fld.type(), fld.subType(), v ) );
}
}
@ -2092,8 +2085,9 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist )
{
for ( int i = 0; i < mPrimaryKeyAttrs.size(); ++i )
{
int idx = mPrimaryKeyAttrs.at( i );
features->setAttribute( idx, convertValue( mAttributeFields.at( idx ).type(), result.PQgetvalue( 0, i ) ) );
const int idx = mPrimaryKeyAttrs.at( i );
const QgsField fld = mAttributeFields.at( idx );
features->setAttribute( idx, convertValue( fld.type(), fld.subType(), result.PQgetvalue( 0, i ) ) );
}
}
else if ( result.PQresultStatus() != PGRES_COMMAND_OK )
@ -3485,6 +3479,20 @@ bool QgsPostgresProvider::convertField( QgsField &field, const QMap<QString, QVa
fieldPrec = -1;
break;
case QVariant::StringList:
fieldType = "_text";
fieldPrec = -1;
break;
case QVariant::List:
{
QgsField sub( "", field.subType(), "", fieldSize, fieldPrec );
if ( !convertField( sub, nullptr ) ) return false;
fieldType = "_" + sub.typeName();
fieldPrec = -1;
break;
}
case QVariant::Double:
if ( fieldPrec > 0 )
{
@ -3847,42 +3855,130 @@ QString QgsPostgresProvider::description() const
return tr( "PostgreSQL/PostGIS provider\n%1\nPostGIS %2" ).arg( pgVersion, postgisVersion );
} // QgsPostgresProvider::description()
static QVariant parseHstore( const QString& value )
static void jumpSpace( const QString& txt, int& i )
{
QRegExp recordSep( "\\s*,\\s*" );
QRegExp valueExtractor( "^(?:\"((?:\\.|.)*)\"|((?:\\.|.)*))\\s*=>\\s*(?:\"((?:\\.|.)*)\"|((?:\\.|.)*))$" );
QVariantMap result;
Q_FOREACH ( QString record, value.split( recordSep ) )
while ( i < txt.length() && txt.at( i ).isSpace() ) ++i;
}
static QString getNextString( const QString& txt, int& i, const QString& sep )
{
jumpSpace( txt, i );
QString cur = txt.mid( i );
if ( cur.startsWith( '"' ) )
{
if ( valueExtractor.exactMatch( record ) )
QRegExp stringRe( "^\"((?:\\\\.|[^\"\\\\])*)\".*" );
if ( !stringRe.exactMatch( cur ) )
{
QString key = valueExtractor.cap( 1 ) + valueExtractor.cap( 2 );
key.replace( "\\\"", "\"" ).replace( "\\\\", "\\" );
QString value = valueExtractor.cap( 3 ) + valueExtractor.cap( 4 );
value.replace( "\\\"", "\"" ).replace( "\\\\", "\\" );
result.insert( key, value );
QgsLogger::warning( "Cannot find end of double quoted string: " + txt );
return QString::null;
}
else
i += stringRe.cap( 1 ).length() + 2;
jumpSpace( txt, i );
if ( !txt.mid( i ).startsWith( sep ) && i < txt.length() )
{
QgsLogger::warning( "Error parsing hstore record: " + record );
QgsLogger::warning( "Cannot find separator: " + txt.mid( i ) );
return QString::null;
}
i += sep.length();
return stringRe.cap( 1 ).replace( "\\\"", "\"" ).replace( "\\\\", "\\" );
}
else
{
QString ret;
int sepPos = cur.indexOf( sep );
if ( sepPos < 0 )
{
i += cur.length();
return cur.trimmed();
}
i += sepPos + sep.length();
return cur.left( sepPos ).trimmed();
}
}
static QVariant parseHstore( const QString& txt )
{
QVariantMap result;
int i = 0;
while ( i < txt.length() )
{
QString key = getNextString( txt, i, "=>" );
QString value = getNextString( txt, i, "," );
if ( key.isNull() || value.isNull() )
{
QgsLogger::warning( "Error parsing hstore: " + txt );
break;
}
result.insert( key, value );
}
return result;
}
static QVariant parseOtherArray( const QString& txt, QVariant::Type subType )
{
int i = 0;
QVariantList result;
while ( i < txt.length() )
{
const QString value = getNextString( txt, i, "," );
if ( value.isNull() )
{
QgsLogger::warning( "Error parsing array: " + txt );
break;
}
result.append( QgsPostgresProvider::convertValue( subType, QVariant::Invalid, value ) );
}
return result;
}
QVariant QgsPostgresProvider::convertValue( QVariant::Type type, const QString& value )
static QVariant parseStringArray( const QString& txt )
{
if ( type == QVariant::Map )
int i = 0;
QStringList result;
while ( i < txt.length() )
{
return parseHstore( value );
const QString value = getNextString( txt, i, "," );
if ( value.isNull() )
{
QgsLogger::warning( "Error parsing array: " + txt );
break;
}
result.append( value );
}
QVariant v( value );
return result;
}
if ( !v.convert( type ) || value.isNull() )
v = QVariant( type );
static QVariant parseArray( const QString& txt, QVariant::Type type, QVariant::Type subType )
{
if ( !txt.startsWith( '{' ) || !txt.endsWith( '}' ) )
{
QgsLogger::warning( "Error parsing array, missing curly braces: " + txt );
return QVariant( type );
}
QString inner = txt.mid( 1, txt.length() - 2 );
if ( type == QVariant::StringList )
return parseStringArray( inner );
else
return parseOtherArray( inner, subType );
}
return v;
QVariant QgsPostgresProvider::convertValue( QVariant::Type type, QVariant::Type subType, const QString& value )
{
switch ( type )
{
case QVariant::Map:
return parseHstore( value );
case QVariant::StringList:
case QVariant::List:
return parseArray( value, type, subType );
default:
{
QVariant v( value );
if ( !v.convert( type ) || value.isNull() ) return QVariant( type );
return v;
}
}
}
/**

View File

@ -252,10 +252,11 @@ class QgsPostgresProvider : public QgsVectorDataProvider
/**
* Convert the postgres string representation into the given QVariant type.
* @param type the wanted type
* @param subType if type is a collection, the wanted element type
* @param value the value to convert
* @return a QVariant of the given type or a null QVariant
*/
static QVariant convertValue( QVariant::Type type, const QString& value );
static QVariant convertValue( QVariant::Type type, QVariant::Type subType, const QString& value );
signals:
/**

View File

@ -542,7 +542,8 @@ bool QgsWFSProvider::processSQL( const QString& sqlString, QString& errorMsg, QS
tablePrefix = QgsWFSUtils::removeNamespacePrefix( tablePrefix );
fieldName = tablePrefix + "." + fieldName;
}
QgsField field( fieldName, srcField.type(), srcField.typeName() );
QgsField field( srcField );
field.setName( fieldName );
if ( mapFieldNameToSrcLayerNameFieldName.contains( fieldName ) )
{
errorMsg = tr( "Field '%1': a field with the same name already exists" ).arg( field.name() );
@ -572,7 +573,8 @@ bool QgsWFSProvider::processSQL( const QString& sqlString, QString& errorMsg, QS
tablePrefix = QgsWFSUtils::removeNamespacePrefix( tablePrefix );
fieldName = tablePrefix + "." + fieldName;
}
QgsField field( fieldName, srcField.type(), srcField.typeName() );
QgsField field( srcField );
field.setName( fieldName );
mapFieldNameToSrcLayerNameFieldName[ field.name()] =
QPair<QString, QString>( typeName, srcField.name() );
mShared->mFields.append( field );
@ -618,9 +620,11 @@ bool QgsWFSProvider::processSQL( const QString& sqlString, QString& errorMsg, QS
return false;
}
QgsField field( fieldName, tableFields.at( idx ).type(), tableFields.at( idx ).typeName() );
QgsField orig = tableFields.at( idx );
QgsField field( orig );
field.setName( fieldName );
mapFieldNameToSrcLayerNameFieldName[ field.name()] =
QPair<QString, QString>( columnTableTypename, tableFields.at( idx ).name() );
QPair<QString, QString>( columnTableTypename, orig.name() );
mShared->mFields.append( field );
}
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsKeyValueWidgetBase</class>
<class>QgsTableWidgetBase</class>
<widget class="QWidget" name="QgsKeyValueWidgetBase">
<property name="geometry">
<rect>

View File

@ -2343,6 +2343,15 @@ class TestQgsExpression: public QObject
QCOMPARE( QgsExpression::formatPreviewString( QVariant( map ) ), QString( "<i>&lt;map: 1: 'One', 2: 'Two'&gt;</i>" ) );
map["3"] = "A very long string that is going to be truncated";
QCOMPARE( QgsExpression::formatPreviewString( QVariant( map ) ), QString( "<i>&lt;map: 1: 'One', 2: 'Two', 3: 'A very long string that is going to ...&gt;</i>" ) );
QVariantList list;
list << 1 << 2 << 3;
QCOMPARE( QgsExpression::formatPreviewString( QVariant( list ) ), QString( "<i>&lt;list: 1, 2, 3&gt;</i>" ) );
QStringList stringList;
stringList << "One" << "Two" << "A very long string that is going to be truncated";
QCOMPARE( QgsExpression::formatPreviewString( QVariant( stringList ) ),
QString( "<i>&lt;list: 'One', 'Two', 'A very long string that is going to be trunca...&gt;</i>" ) );
}
};

View File

@ -42,6 +42,7 @@ class TestQgsField: public QObject
void dataStream();
void displayName();
void editorWidgetSetup();
void collection();
private:
};
@ -411,5 +412,16 @@ void TestQgsField::editorWidgetSetup()
QCOMPARE( field.editorWidgetSetup().config(), setup.config() );
}
void TestQgsField::collection()
{
QgsField field( "collection", QVariant::List, "_int32", 0, 0, QString(), QVariant::Int );
QCOMPARE( field.subType(), QVariant::Int );
field.setSubType( QVariant::Double );
QCOMPARE( field.subType(), QVariant::Double );
QVariant str( "hello" );
QVERIFY( !field.convertCompatible( str ) );
}
QTEST_MAIN( TestQgsField )
#include "testqgsfield.moc"

View File

@ -142,4 +142,4 @@ ADD_QGIS_TEST(spinbox testqgsspinbox.cpp)
ADD_QGIS_TEST(sqlcomposerdialog testqgssqlcomposerdialog.cpp)
ADD_QGIS_TEST(editorwidgetregistrytest testqgseditorwidgetregistry.cpp)
ADD_QGIS_TEST(keyvaluewidgettest testqgskeyvaluewidget.cpp)
ADD_QGIS_TEST(listwidgettest testqgslistwidget.cpp)

View File

@ -78,6 +78,13 @@ class TestQgsEditorWidgetRegistry: public QObject
checkSimple( "double", "Range" );
}
void arrayType()
{
checkSimple( "double[]", "List" );
checkSimple( "int[]", "List" );
checkSimple( "string[]", "List" );
}
void configuredType()
{
QgsVectorLayer vl( "LineString?crs=epsg:3111&field=pk:int&field=col1:string", "vl", "memory" );

View File

@ -0,0 +1,126 @@
/***************************************************************************
testqgslistwidget.cpp
--------------------------------------
Date : 08 09 2016
Copyright : (C) 2016 Patrick Valsecchi
Email : patrick dot valsecchi at camptocamp dot 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 <editorwidgets/qgslistwidgetfactory.h>
#include <qgslistwidget.h>
#include <editorwidgets/core/qgseditorwidgetwrapper.h>
#include <qgsapplication.h>
class TestQgsListWidget : public QObject
{
Q_OBJECT
private slots:
void initTestCase() // will be called before the first testfunction is executed.
{
QgsApplication::init();
QgsApplication::initQgis();
}
void cleanupTestCase() // will be called after the last testfunction was executed.
{
QgsApplication::exitQgis();
}
void testStringUpdate()
{
const QgsListWidgetFactory factory( "testList" );
QgsVectorLayer vl( "Point?field=fld:string[]", "test", "memory" );
QgsEditorWidgetWrapper* wrapper = factory.create( &vl, 0, nullptr, nullptr );
QVERIFY( wrapper );
QSignalSpy spy( wrapper, SIGNAL( valueChanged( const QVariant& ) ) );
QgsListWidget* widget = qobject_cast< QgsListWidget* >( wrapper->widget() );
QVERIFY( widget );
QStringList initial;
initial << "one" << "two";
wrapper->setValue( initial );
const QVariant value = wrapper->value();
QCOMPARE( int( value.type() ), int( QVariant::StringList ) );
QCOMPARE( value.toStringList(), initial );
QCOMPARE( spy.count(), 0 );
QAbstractItemModel* model = widget->tableView->model();
model->setData( model->index( 0, 0 ), "hello" );
QCOMPARE( spy.count(), 1 );
QVERIFY( widget->valid() );
QStringList expected = initial;
expected[0] = "hello";
QVariant eventValue = spy.at( 0 ).at( 0 ).value<QVariant>();
QCOMPARE( int( eventValue.type() ), int( QVariant::StringList ) );
QCOMPARE( eventValue.toStringList(), expected );
QCOMPARE( wrapper->value().toStringList(), expected );
QCOMPARE( spy.count(), 1 );
QVERIFY( widget->valid() );
}
void testIntUpdate()
{
const QgsListWidgetFactory factory( "testList" );
QgsVectorLayer vl( "Point?field=fld:int[]", "test", "memory" );
QgsEditorWidgetWrapper* wrapper = factory.create( &vl, 0, nullptr, nullptr );
QVERIFY( wrapper );
QSignalSpy spy( wrapper, SIGNAL( valueChanged( const QVariant& ) ) );
QgsListWidget* widget = qobject_cast< QgsListWidget* >( wrapper->widget() );
QVERIFY( widget );
QVariantList initial;
initial << 1 << -2;
wrapper->setValue( initial );
const QVariant value = wrapper->value();
QCOMPARE( int( value.type() ), int( QVariant::List ) );
QCOMPARE( value.toList(), initial );
QCOMPARE( spy.count(), 0 );
QAbstractItemModel* model = widget->tableView->model();
model->setData( model->index( 0, 0 ), 3 );
QCOMPARE( spy.count(), 1 );
QVariantList expected = initial;
expected[0] = 3;
QCOMPARE( spy.count(), 1 );
QVariant eventValue = spy.at( 0 ).at( 0 ).value<QVariant>();
QCOMPARE( int( eventValue.type() ), int( QVariant::List ) );
QCOMPARE( eventValue.toList(), expected );
QCOMPARE( wrapper->value().toList(), expected );
QVERIFY( widget->valid() );
model->setData( model->index( 0, 0 ), "a" );
expected = initial;
expected.removeAt( 0 );
QVERIFY( !widget->valid() );
QCOMPARE( wrapper->value().toList(), expected );
spy.clear();
model->setData( model->index( 0, 0 ), 56 );
expected = initial;
expected[0] = 56;
QCOMPARE( spy.count(), 1 );
eventValue = spy.at( 0 ).at( 0 ).value<QVariant>();
QCOMPARE( eventValue.toList(), expected );
QCOMPARE( wrapper->value().toList(), expected );
QVERIFY( widget->valid() );
}
};
QTEST_MAIN( TestQgsListWidget )
#include "testqgslistwidget.moc"

View File

@ -24,6 +24,22 @@ class TestQgsPostgresConn: public QObject
QCOMPARE( QgsPostgresConn::quotedValue( "b \"c' \\x" ), QString( "E'b \"c'' \\\\x'" ) );
}
void quotedValueStringArray()
{
QStringList list;
list << "a" << "b \"c' \\x";
const QString actual = QgsPostgresConn::quotedValue( list );
QCOMPARE( actual, QString( "E'{\"a\",\"b \\\\\"c\\' \\\\\\\\x\"}'" ) );
}
void quotedValueIntArray()
{
QVariantList list;
list << 1 << -5;
const QString actual = QgsPostgresConn::quotedValue( list );
QCOMPARE( actual, QString( "E'{\"1\",\"-5\"}'" ) );
}
};
QTEST_MAIN( TestQgsPostgresConn )

View File

@ -9,27 +9,62 @@ class TestQgsPostgresProvider: public QObject
private slots:
void decodeHstore()
{
const QVariant decoded = QgsPostgresProvider::convertValue( QVariant::Map, "\"1\"=>\"2\", \"a\"=>\"b \\\"c'\"" );
const QVariant decoded = QgsPostgresProvider::convertValue( QVariant::Map, QVariant::String, "\"1\"=>\"2\", \"a\"=>\"b, \\\"c'\", \"backslash\"=>\"\\\\\"" );
QCOMPARE( decoded.type(), QVariant::Map );
QVariantMap expected;
expected["1"] = "2";
expected["a"] = "b \"c'";
expected["a"] = "b, \"c'";
expected["backslash"] = "\\";
qDebug() << "actual: " << decoded;
QCOMPARE( decoded.toMap(), expected );
}
void decodeHstoreNoQuote()
{
const QVariant decoded = QgsPostgresProvider::convertValue( QVariant::Map, "1=>2, a=>b" );
const QVariant decoded = QgsPostgresProvider::convertValue( QVariant::Map, QVariant::String, "1=>2, a=>b c" );
QCOMPARE( decoded.type(), QVariant::Map );
QVariantMap expected;
expected["1"] = "2";
expected["a"] = "b";
expected["a"] = "b c";
qDebug() << "actual: " << decoded;
QCOMPARE( decoded.toMap(), expected );
}
void decodeArray2StringList()
{
const QVariant decoded = QgsPostgresProvider::convertValue( QVariant::StringList, QVariant::String, "{\"1\",\"2\", \"a\\\\1\" , \"\\\\\",\"b, \\\"c'\"}" );
QCOMPARE( decoded.type(), QVariant::StringList );
QStringList expected;
expected << "1" << "2" << "a\\1" << "\\" << "b, \"c'";
qDebug() << "actual: " << decoded;
QCOMPARE( decoded.toStringList(), expected );
}
void decodeArray2StringListNoQuote()
{
const QVariant decoded = QgsPostgresProvider::convertValue( QVariant::StringList, QVariant::String, "{1,2, a ,b, c}" );
QCOMPARE( decoded.type(), QVariant::StringList );
QStringList expected;
expected << "1" << "2" << "a" << "b" << "c";
qDebug() << "actual: " << decoded;
QCOMPARE( decoded.toStringList(), expected );
}
void decodeArray2IntList()
{
const QVariant decoded = QgsPostgresProvider::convertValue( QVariant::StringList, QVariant::String, "{1, 2, 3,-5,10}" );
QCOMPARE( decoded.type(), QVariant::StringList );
QVariantList expected;
expected << QVariant( 1 ) << QVariant( 2 ) << QVariant( 3 ) << QVariant( -5 ) << QVariant( 10 );
qDebug() << "actual: " << decoded;
QCOMPARE( decoded.toList(), expected );
}
};
QTEST_MAIN( TestQgsPostgresProvider )

View File

@ -143,7 +143,7 @@ class TestPyQgsMemoryProvider(unittest.TestCase, ProviderTestCase):
layer = QgsVectorLayer("Point", "test", "memory")
provider = layer.dataProvider()
res = provider.addAttributes([QgsField("name", QVariant.String, ),
res = provider.addAttributes([QgsField("name", QVariant.String),
QgsField("age", QVariant.Int),
QgsField("size", QVariant.Double)])
assert res, "Failed to add attributes"
@ -193,7 +193,7 @@ class TestPyQgsMemoryProvider(unittest.TestCase, ProviderTestCase):
layer = QgsVectorLayer("Point", "test", "memory")
provider = layer.dataProvider()
provider.addAttributes([QgsField("name", QVariant.String, ),
provider.addAttributes([QgsField("name", QVariant.String),
QgsField("age", QVariant.Int),
QgsField("size", QVariant.Double)])
myMessage = ('Expected: %s\nGot: %s\n' %
@ -267,7 +267,7 @@ class TestPyQgsMemoryProvider(unittest.TestCase, ProviderTestCase):
layer = QgsVectorLayer("Point", "test", "memory")
provider = layer.dataProvider()
res = provider.addAttributes([QgsField("name", QVariant.String, ),
res = provider.addAttributes([QgsField("name", QVariant.String),
QgsField("age", QVariant.Int),
QgsField("size", QVariant.Double)])
layer.updateFields()

View File

@ -350,7 +350,6 @@ class TestPyQgsPostgresProvider(unittest.TestCase, ProviderTestCase):
new_f = QgsFeature(vl.fields())
new_f['pk'] = NULL
#new_f['value'] = {'x': 'a\'s "y" \\', 'z': 'end'}
new_f['value'] = {'simple': '1', 'doubleQuote': '"y"', 'quote': "'q'", 'backslash': '\\'}
r, fs = vl.dataProvider().addFeatures([new_f])
self.assertTrue(r)
@ -366,6 +365,65 @@ class TestPyQgsPostgresProvider(unittest.TestCase, ProviderTestCase):
self.assertTrue(vl.deleteFeatures([new_pk]))
self.assertTrue(vl.commitChanges())
def testStringArray(self):
vl = QgsVectorLayer('%s table="qgis_test"."string_array" sql=' % (self.dbconn), "teststringarray", "postgres")
self.assertTrue(vl.isValid())
fields = vl.dataProvider().fields()
self.assertEqual(fields.at(fields.indexFromName('value')).type(), QVariant.StringList)
self.assertEqual(fields.at(fields.indexFromName('value')).subType(), QVariant.String)
f = next(vl.getFeatures(QgsFeatureRequest()))
value_idx = vl.fieldNameIndex('value')
self.assertTrue(isinstance(f.attributes()[value_idx], list))
self.assertEqual(f.attributes()[value_idx], ['a', 'b', 'c'])
new_f = QgsFeature(vl.fields())
new_f['pk'] = NULL
new_f['value'] = ['simple', '"doubleQuote"', "'quote'", 'back\\slash']
r, fs = vl.dataProvider().addFeatures([new_f])
self.assertTrue(r)
new_pk = fs[0]['pk']
self.assertNotEqual(new_pk, NULL, fs[0].attributes())
try:
read_back = vl.getFeature(new_pk)
self.assertEqual(read_back['pk'], new_pk)
self.assertEqual(read_back['value'], new_f['value'])
finally:
self.assertTrue(vl.startEditing())
self.assertTrue(vl.deleteFeatures([new_pk]))
self.assertTrue(vl.commitChanges())
def testIntArray(self):
vl = QgsVectorLayer('%s table="qgis_test"."int_array" sql=' % (self.dbconn), "testintarray", "postgres")
self.assertTrue(vl.isValid())
fields = vl.dataProvider().fields()
self.assertEqual(fields.at(fields.indexFromName('value')).type(), QVariant.List)
self.assertEqual(fields.at(fields.indexFromName('value')).subType(), QVariant.Int)
f = next(vl.getFeatures(QgsFeatureRequest()))
value_idx = vl.fieldNameIndex('value')
self.assertTrue(isinstance(f.attributes()[value_idx], list))
self.assertEqual(f.attributes()[value_idx], [1, 2, -5])
def testDoubleArray(self):
vl = QgsVectorLayer('%s table="qgis_test"."double_array" sql=' % (self.dbconn), "testdoublearray", "postgres")
self.assertTrue(vl.isValid())
fields = vl.dataProvider().fields()
self.assertEqual(fields.at(fields.indexFromName('value')).type(), QVariant.List)
self.assertEqual(fields.at(fields.indexFromName('value')).subType(), QVariant.Double)
f = next(vl.getFeatures(QgsFeatureRequest()))
value_idx = vl.fieldNameIndex('value')
self.assertTrue(isinstance(f.attributes()[value_idx], list))
self.assertEqual(f.attributes()[value_idx], [1.1, 2, -5.12345])
class TestPyQgsPostgresProviderCompoundKey(unittest.TestCase, ProviderTestCase):

View File

@ -5,6 +5,7 @@ SCRIPTS="
tests/testdata/provider/testdata_pg_reltests.sql
tests/testdata/provider/testdata_pg_vectorjoin.sql
tests/testdata/provider/testdata_pg_hstore.sql
tests/testdata/provider/testdata_pg_array.sql
"
dropdb qgis_test 2> /dev/null || true

View File

@ -0,0 +1,37 @@
DROP TABLE IF EXISTS qgis_test.string_array;
CREATE TABLE qgis_test.string_array
(
pk SERIAL NOT NULL PRIMARY KEY,
value text[]
);
INSERT INTO qgis_test.string_array(value)
VALUES
('{a,b,c}');
DROP TABLE IF EXISTS qgis_test.int_array;
CREATE TABLE qgis_test.int_array
(
pk SERIAL NOT NULL PRIMARY KEY,
value int4[]
);
INSERT INTO qgis_test.int_array(value)
VALUES
('{1,2,-5}');
DROP TABLE IF EXISTS qgis_test.double_array;
CREATE TABLE qgis_test.double_array
(
pk SERIAL NOT NULL PRIMARY KEY,
value float8[]
);
INSERT INTO qgis_test.double_array(value)
VALUES
('{1.1,2,-5.12345}');