QGIS/src/gui/editorwidgets/qgsvaluerelationwidgetwrapper.cpp
Alessandro Pasotti 4ad62aa6ff Move layerDependencies to formatter
... from the widget
2019-12-13 11:54:25 +01:00

462 lines
14 KiB
C++

/***************************************************************************
qgsvaluerelationwidgetwrapper.cpp
--------------------------------------
Date : 5.1.2014
Copyright : (C) 2014 Matthias Kuhn
Email : matthias at opengis dot ch
***************************************************************************
* *
* 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 "qgsvaluerelationwidgetwrapper.h"
#include "qgis.h"
#include "qgsfields.h"
#include "qgsproject.h"
#include "qgsvaluerelationwidgetfactory.h"
#include "qgsvectorlayer.h"
#include "qgsfilterlineedit.h"
#include "qgsfeatureiterator.h"
#include "qgsvaluerelationfieldformatter.h"
#include "qgsattributeform.h"
#include "qgsattributes.h"
#include "qgsjsonutils.h"
#include "qgspostgresstringutils.h"
#include <QHeaderView>
#include <QComboBox>
#include <QLineEdit>
#include <QTableWidget>
#include <QStringListModel>
#include <QCompleter>
#include <nlohmann/json.hpp>
using namespace nlohmann;
QgsValueRelationWidgetWrapper::QgsValueRelationWidgetWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor, QWidget *parent )
: QgsEditorWidgetWrapper( layer, fieldIdx, editor, parent )
{
}
QVariant QgsValueRelationWidgetWrapper::value() const
{
QVariant v;
if ( mComboBox )
{
int cbxIdx = mComboBox->currentIndex();
if ( cbxIdx > -1 )
{
v = mComboBox->currentData();
}
}
const int nofColumns = columnCount();
if ( mTableWidget )
{
QStringList selection;
for ( int j = 0; j < mTableWidget->rowCount(); j++ )
{
for ( int i = 0; i < nofColumns; ++i )
{
QTableWidgetItem *item = mTableWidget->item( j, i );
if ( item )
{
if ( item->checkState() == Qt::Checked )
selection << item->data( Qt::UserRole ).toString();
}
}
}
QVariantList vl;
//store as QVariantList because the field type supports data structure
for ( const QString &s : qgis::as_const( selection ) )
{
// Convert to proper type
const QVariant::Type type { fkType() };
switch ( type )
{
case QVariant::Type::Int:
vl.push_back( s.toInt() );
break;
case QVariant::Type::LongLong:
vl.push_back( s.toLongLong() );
break;
default:
vl.push_back( s );
break;
}
}
if ( layer()->fields().at( fieldIdx() ).type() == QVariant::Map ||
layer()->fields().at( fieldIdx() ).type() == QVariant::List )
{
v = vl;
}
else
{
//make string
v = QgsPostgresStringUtils::buildArray( vl );
}
}
if ( mLineEdit )
{
for ( const QgsValueRelationFieldFormatter::ValueRelationItem &item : qgis::as_const( mCache ) )
{
if ( item.value == mLineEdit->text() )
{
v = item.key;
break;
}
}
}
return v;
}
QWidget *QgsValueRelationWidgetWrapper::createWidget( QWidget *parent )
{
QgsAttributeForm *form = qobject_cast<QgsAttributeForm *>( parent );
if ( form )
connect( form, &QgsAttributeForm::widgetValueChanged, this, &QgsValueRelationWidgetWrapper::widgetValueChanged );
mExpression = config().value( QStringLiteral( "FilterExpression" ) ).toString();
if ( config( QStringLiteral( "AllowMulti" ) ).toBool() )
{
return new QTableWidget( parent );
}
else if ( config( QStringLiteral( "UseCompleter" ) ).toBool() )
{
return new QgsFilterLineEdit( parent );
}
{
return new QComboBox( parent );
}
}
void QgsValueRelationWidgetWrapper::initWidget( QWidget *editor )
{
mComboBox = qobject_cast<QComboBox *>( editor );
mTableWidget = qobject_cast<QTableWidget *>( editor );
mLineEdit = qobject_cast<QLineEdit *>( editor );
// Read current initial form values from the editor context
setFeature( context().formFeature() );
if ( mComboBox )
{
connect( mComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ),
this, static_cast<void ( QgsEditorWidgetWrapper::* )()>( &QgsEditorWidgetWrapper::emitValueChanged ), Qt::UniqueConnection );
}
else if ( mTableWidget )
{
mTableWidget->horizontalHeader()->setResizeMode( QHeaderView::Stretch );
mTableWidget->horizontalHeader()->setVisible( false );
mTableWidget->verticalHeader()->setResizeMode( QHeaderView::Stretch );
mTableWidget->verticalHeader()->setVisible( false );
mTableWidget->setShowGrid( false );
mTableWidget->setEditTriggers( QAbstractItemView::NoEditTriggers );
mTableWidget->setSelectionMode( QAbstractItemView::NoSelection );
connect( mTableWidget, &QTableWidget::itemChanged, this, static_cast<void ( QgsEditorWidgetWrapper::* )()>( &QgsEditorWidgetWrapper::emitValueChanged ), Qt::UniqueConnection );
}
else if ( mLineEdit )
{
connect( mLineEdit, &QLineEdit::textChanged, this, &QgsValueRelationWidgetWrapper::emitValueChangedInternal, Qt::UniqueConnection );
}
}
bool QgsValueRelationWidgetWrapper::valid() const
{
return mTableWidget || mLineEdit || mComboBox;
}
void QgsValueRelationWidgetWrapper::updateValues( const QVariant &value, const QVariantList & )
{
if ( mTableWidget )
{
QStringList checkList;
if ( layer()->fields().at( fieldIdx() ).type() == QVariant::Map ||
layer()->fields().at( fieldIdx() ).type() == QVariant::List )
{
checkList = value.toStringList();
}
else
{
checkList = QgsValueRelationFieldFormatter::valueToStringList( value );
}
QTableWidgetItem *lastChangedItem = nullptr;
const int nofColumns = columnCount();
// This block is needed because item->setCheckState triggers dataChanged gets back to value()
// and iterate over all items again! This can be extremely slow on large items sets.
for ( int j = 0; j < mTableWidget->rowCount(); j++ )
{
auto signalBlockedTableWidget = whileBlocking( mTableWidget );
Q_UNUSED( signalBlockedTableWidget )
for ( int i = 0; i < nofColumns; ++i )
{
QTableWidgetItem *item = mTableWidget->item( j, i );
if ( item )
{
item->setCheckState( checkList.contains( item->data( Qt::UserRole ).toString() ) ? Qt::Checked : Qt::Unchecked );
//re-set enabled state because it's lost after reloading items
item->setFlags( mEnabled ? item->flags() | Qt::ItemIsEnabled : item->flags() & ~Qt::ItemIsEnabled );
lastChangedItem = item;
}
}
}
// let's trigger the signal now, once and for all
if ( lastChangedItem )
lastChangedItem->setCheckState( checkList.contains( lastChangedItem->data( Qt::UserRole ).toString() ) ? Qt::Checked : Qt::Unchecked );
}
else if ( mComboBox )
{
// findData fails to tell a 0 from a NULL
// See: "Value relation, value 0 = NULL" - https://github.com/qgis/QGIS/issues/27803
int idx = -1; // default to not found
for ( int i = 0; i < mComboBox->count(); i++ )
{
QVariant v( mComboBox->itemData( i ) );
if ( qgsVariantEqual( v, value ) )
{
idx = i;
break;
}
}
mComboBox->setCurrentIndex( idx );
}
else if ( mLineEdit )
{
for ( const QgsValueRelationFieldFormatter::ValueRelationItem &i : qgis::as_const( mCache ) )
{
if ( i.key == value )
{
mLineEdit->setText( i.value );
break;
}
}
}
}
void QgsValueRelationWidgetWrapper::widgetValueChanged( const QString &attribute, const QVariant &newValue, bool attributeChanged )
{
// Do nothing if the value has not changed
if ( attributeChanged )
{
QVariant oldValue( value( ) );
setFormFeatureAttribute( attribute, newValue );
// Update combos if the value used in the filter expression has changed
if ( QgsValueRelationFieldFormatter::expressionRequiresFormScope( mExpression )
&& QgsValueRelationFieldFormatter::expressionFormAttributes( mExpression ).contains( attribute ) )
{
populate();
// Restore value
updateValues( value( ) );
// If the value has changed as a result of another widget's value change,
// we need to emit the signal to make sure other dependent widgets are
// updated.
if ( oldValue != value() && fieldIdx() < formFeature().fields().count() )
{
QString attributeName( formFeature().fields().names().at( fieldIdx() ) );
setFormFeatureAttribute( attributeName, value( ) );
emitValueChanged();
}
}
}
}
void QgsValueRelationWidgetWrapper::setFeature( const QgsFeature &feature )
{
setFormFeature( feature );
whileBlocking( this )->populate();
whileBlocking( this )->setValue( feature.attribute( fieldIdx() ) );
// As we block any signals, possible depending widgets will not being updated
// so we force emit signal once and for all
emitValueChanged();
// A bit of logic to set the default value if AllowNull is false and this is a new feature
// Note that this needs to be here after the cache has been created/updated by populate()
// and signals unblocked (we want this to propagate to the feature itself)
if ( formFeature().isValid()
&& ! formFeature().attribute( fieldIdx() ).isValid()
&& ! mCache.empty()
&& ! config( QStringLiteral( "AllowNull" ) ).toBool( ) )
{
// This is deferred because at the time the feature is set in one widget it is not
// set in the next, which is typically the "down" in a drill-down
QTimer::singleShot( 0, this, [ this ]
{
updateValues( mCache.at( 0 ).key );
} );
}
}
int QgsValueRelationWidgetWrapper::columnCount() const
{
return std::max( 1, config( QStringLiteral( "NofColumns" ) ).toInt() );
}
QVariant::Type QgsValueRelationWidgetWrapper::fkType() const
{
const QgsVectorLayer *layer = QgsValueRelationFieldFormatter::resolveLayer( config(), QgsProject::instance() );
if ( layer )
{
QgsFields fields = layer->fields();
int idx { fields.lookupField( config().value( QStringLiteral( "Key" ) ).toString() ) };
if ( idx >= 0 )
{
return fields.at( idx ).type();
}
}
return QVariant::Type::Invalid;
}
void QgsValueRelationWidgetWrapper::populate( )
{
// Initialize, note that signals are blocked, to avoid double signals on new features
if ( QgsValueRelationFieldFormatter::expressionRequiresFormScope( mExpression ) )
{
mCache = QgsValueRelationFieldFormatter::createCache( config( ), formFeature() );
}
else if ( mCache.empty() )
{
mCache = QgsValueRelationFieldFormatter::createCache( config( ) );
}
if ( mComboBox )
{
mComboBox->clear();
if ( config( QStringLiteral( "AllowNull" ) ).toBool( ) )
{
whileBlocking( mComboBox )->addItem( tr( "(no selection)" ), QVariant( field().type( ) ) );
}
for ( const QgsValueRelationFieldFormatter::ValueRelationItem &element : qgis::as_const( mCache ) )
{
whileBlocking( mComboBox )->addItem( element.value, element.key );
}
}
else if ( mTableWidget )
{
const int nofColumns = columnCount();
if ( ! mCache.empty() )
{
mTableWidget->setRowCount( ( mCache.size() + nofColumns - 1 ) / nofColumns );
}
else
mTableWidget->setRowCount( 1 );
mTableWidget->setColumnCount( nofColumns );
whileBlocking( mTableWidget )->clear();
int row = 0;
int column = 0;
for ( const QgsValueRelationFieldFormatter::ValueRelationItem &element : qgis::as_const( mCache ) )
{
if ( column == nofColumns )
{
row++;
column = 0;
}
QTableWidgetItem *item = nullptr;
item = new QTableWidgetItem( element.value );
item->setData( Qt::UserRole, element.key );
whileBlocking( mTableWidget )->setItem( row, column, item );
column++;
}
}
else if ( mLineEdit )
{
QStringList values;
values.reserve( mCache.size() );
for ( const QgsValueRelationFieldFormatter::ValueRelationItem &i : qgis::as_const( mCache ) )
{
values << i.value;
}
QStringListModel *m = new QStringListModel( values, mLineEdit );
QCompleter *completer = new QCompleter( m, mLineEdit );
completer->setCaseSensitivity( Qt::CaseInsensitive );
mLineEdit->setCompleter( completer );
}
}
void QgsValueRelationWidgetWrapper::showIndeterminateState()
{
const int nofColumns = columnCount();
if ( mTableWidget )
{
for ( int j = 0; j < mTableWidget->rowCount(); j++ )
{
for ( int i = 0; i < nofColumns; ++i )
{
whileBlocking( mTableWidget )->item( j, i )->setCheckState( Qt::PartiallyChecked );
}
}
}
else if ( mComboBox )
{
whileBlocking( mComboBox )->setCurrentIndex( -1 );
}
else if ( mLineEdit )
{
whileBlocking( mLineEdit )->clear();
}
}
void QgsValueRelationWidgetWrapper::setEnabled( bool enabled )
{
if ( mEnabled == enabled )
return;
mEnabled = enabled;
if ( mTableWidget )
{
auto signalBlockedTableWidget = whileBlocking( mTableWidget );
Q_UNUSED( signalBlockedTableWidget )
for ( int j = 0; j < mTableWidget->rowCount(); j++ )
{
for ( int i = 0; i < mTableWidget->columnCount(); ++i )
{
QTableWidgetItem *item = mTableWidget->item( j, i );
if ( item )
{
item->setFlags( enabled ? item->flags() | Qt::ItemIsEnabled : item->flags() & ~Qt::ItemIsEnabled );
}
}
}
}
else
QgsEditorWidgetWrapper::setEnabled( enabled );
}
void QgsValueRelationWidgetWrapper::emitValueChangedInternal( const QString &value )
{
Q_NOWARN_DEPRECATED_PUSH
emit valueChanged( value );
Q_NOWARN_DEPRECATED_POP
emit valuesChanged( value );
}