QGIS/src/gui/qgsattributeform.cpp

741 lines
21 KiB
C++

/***************************************************************************
qgsattributeform.cpp
--------------------------------------
Date : 3.5.2014
Copyright : (C) 2014 Matthias Kuhn
Email : matthias dot kuhn at gmx 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 "qgsattributeform.h"
#include "qgsattributeeditor.h"
#include "qgsattributeforminterface.h"
#include "qgsattributeformlegacyinterface.h"
#include "qgseditorwidgetregistry.h"
#include "qgsproject.h"
#include "qgspythonrunner.h"
#include "qgsrelationwidgetwrapper.h"
#include <QDir>
#include <QFileInfo>
#include <QFormLayout>
#include <QGridLayout>
#include <QGroupBox>
#include <QKeyEvent>
#include <QLabel>
#include <QPushButton>
#include <QScrollArea>
#include <QTabWidget>
#include <QUiLoader>
int QgsAttributeForm::sFormCounter = 0;
QgsAttributeForm::QgsAttributeForm( QgsVectorLayer* vl, const QgsFeature feature, QgsAttributeEditorContext context, QWidget* parent )
: QWidget( parent )
, mLayer( vl )
, mContext( context )
, mFormNr( sFormCounter++ )
, mIsSaving( false )
, mIsAddDialog( false )
, mEditCommandMessage( tr( "Attributes changed" ) )
{
init();
initPython();
setFeature( feature );
connect( vl, SIGNAL( attributeAdded( int ) ), this, SLOT( onAttributeAdded( int ) ) );
connect( vl, SIGNAL( attributeDeleted( int ) ), this, SLOT( onAttributeDeleted( int ) ) );
}
QgsAttributeForm::~QgsAttributeForm()
{
cleanPython();
qDeleteAll( mInterfaces );
}
void QgsAttributeForm::hideButtonBox()
{
mButtonBox->hide();
// Make sure that changes are taken into account if somebody tries to figure out if there have been some
if ( !mIsAddDialog )
connect( mLayer, SIGNAL( beforeModifiedCheck() ), this, SLOT( save() ) );
}
void QgsAttributeForm::showButtonBox()
{
mButtonBox->show();
disconnect( mLayer, SIGNAL( beforeModifiedCheck() ), this, SLOT( save() ) );
}
void QgsAttributeForm::disconnectButtonBox()
{
disconnect( mButtonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
disconnect( mButtonBox, SIGNAL( rejected() ), this, SLOT( resetValues() ) );
}
void QgsAttributeForm::addInterface( QgsAttributeFormInterface* iface )
{
mInterfaces.append( iface );
}
bool QgsAttributeForm::editable()
{
return mFeature.isValid() && mLayer->isEditable() ;
}
void QgsAttributeForm::setIsAddDialog( bool isAddDialog )
{
mIsAddDialog = isAddDialog;
synchronizeEnabledState();
}
void QgsAttributeForm::changeAttribute( const QString& field, const QVariant& value )
{
Q_FOREACH( QgsWidgetWrapper* ww, mWidgets )
{
QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
if ( eww && eww->field().name() == field )
{
eww->setValue( value );
}
}
}
void QgsAttributeForm::setFeature( const QgsFeature& feature )
{
mFeature = feature;
resetValues();
synchronizeEnabledState();
Q_FOREACH( QgsAttributeFormInterface* iface, mInterfaces )
{
iface->featureChanged();
}
}
bool QgsAttributeForm::save()
{
if ( mIsSaving )
return true;
mIsSaving = true;
bool success = true;
emit beforeSave( success );
// Somebody wants to prevent this form from saving
if ( !success )
return false;
QgsFeature updatedFeature = QgsFeature( mFeature );
if ( mFeature.isValid() || mIsAddDialog )
{
bool doUpdate = false;
// An add dialog should perform an action by default
// and not only if attributes have "changed"
if ( mIsAddDialog )
doUpdate = true;
QgsAttributes src = mFeature.attributes();
QgsAttributes dst = mFeature.attributes();
Q_FOREACH( QgsWidgetWrapper* ww, mWidgets )
{
QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
if ( eww )
{
QVariant dstVar = dst[eww->fieldIdx()];
QVariant srcVar = eww->value();
// need to check dstVar.isNull() != srcVar.isNull()
// otherwise if dstVar=NULL and scrVar=0, then dstVar = srcVar
if (( dstVar != srcVar || dstVar.isNull() != srcVar.isNull() ) && srcVar.isValid() )
{
dst[eww->fieldIdx()] = srcVar;
doUpdate = true;
}
}
}
updatedFeature.setAttributes( dst );
Q_FOREACH( QgsAttributeFormInterface* iface, mInterfaces )
{
if ( !iface->acceptChanges( updatedFeature ) )
{
doUpdate = false;
}
}
if ( doUpdate )
{
if ( mIsAddDialog )
{
mFeature.setValid( true );
mLayer->beginEditCommand( mEditCommandMessage );
bool res = mLayer->addFeature( updatedFeature );
if ( res )
{
mFeature.setAttributes( updatedFeature.attributes() );
mLayer->endEditCommand();
}
else
mLayer->destroyEditCommand();
}
else
{
mLayer->beginEditCommand( mEditCommandMessage );
int n = 0;
for ( int i = 0; i < dst.count(); ++i )
{
if (( dst[i] == src[i] && dst[i].isNull() == src[i].isNull() ) || !dst[i].isValid() )
{
QgsDebugMsg( "equal or invalid destination" );
QgsDebugMsg( QString( "dst:'%1' (type:%2,isNull:%3,isValid:%4)" )
.arg( dst[i].toString() ).arg( dst[i].typeName() ).arg( dst[i].isNull() ).arg( dst[i].isValid() ) );
QgsDebugMsg( QString( "src:'%1' (type:%2,isNull:%3,isValid:%4)" )
.arg( src[i].toString() ).arg( src[i].typeName() ).arg( src[i].isNull() ).arg( src[i].isValid() ) );
continue;
}
success &= mLayer->changeAttributeValue( mFeature.id(), i, dst[i], src[i] );
n++;
}
if ( success && n > 0 )
{
mLayer->endEditCommand();
mFeature.setAttributes( dst );
}
else
{
mLayer->destroyEditCommand();
}
}
}
}
emit featureSaved( updatedFeature );
mLayer->triggerRepaint();
mIsSaving = false;
return success;
}
void QgsAttributeForm::resetValues()
{
Q_FOREACH( QgsWidgetWrapper* ww, mWidgets )
{
ww->setFeature( mFeature );
}
}
void QgsAttributeForm::onAttributeChanged( const QVariant& value )
{
QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( sender() );
Q_ASSERT( eww );
emit attributeChanged( eww->field().name(), value );
}
void QgsAttributeForm::onAttributeAdded( int idx )
{
Q_UNUSED( idx ) // only used for Q_ASSERT
if ( mFeature.isValid() )
{
QgsAttributes attrs = mFeature.attributes();
Q_ASSERT( attrs.size() == idx );
attrs.append( QVariant() );
mFeature.setFields( &layer()->pendingFields() );
mFeature.setAttributes( attrs );
}
init();
setFeature( mFeature );
}
void QgsAttributeForm::onAttributeDeleted( int idx )
{
if ( mFeature.isValid() )
{
QgsAttributes attrs = mFeature.attributes();
attrs.remove( idx );
mFeature.setFields( &layer()->pendingFields() );
mFeature.setAttributes( attrs );
}
init();
setFeature( mFeature );
}
void QgsAttributeForm::synchronizeEnabledState()
{
bool isEditable = ( mFeature.isValid() || mIsAddDialog ) && mLayer->isEditable();
Q_FOREACH( QgsWidgetWrapper* ww, mWidgets )
{
bool fieldEditable = true;
QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
if ( eww )
{
fieldEditable = mLayer->fieldEditable( eww->fieldIdx() );
}
ww->setEnabled( isEditable && fieldEditable );
}
QPushButton* okButton = mButtonBox->button( QDialogButtonBox::Ok );
if ( okButton )
okButton->setEnabled( isEditable );
}
void QgsAttributeForm::init()
{
QWidget* formWidget = 0;
qDeleteAll( mWidgets );
mWidgets.clear();
while ( QWidget* w = this->findChild<QWidget*>() )
{
delete w;
}
delete layout();
// Get a layout
setLayout( new QGridLayout( this ) );
// Try to load Ui-File for layout
if ( mLayer->editorLayout() == QgsVectorLayer::UiFileLayout && !mLayer->editForm().isEmpty() )
{
QFile file( mLayer->editForm() );
if ( file.open( QFile::ReadOnly ) )
{
QUiLoader loader;
QFileInfo fi( mLayer->editForm() );
loader.setWorkingDirectory( fi.dir() );
formWidget = loader.load( &file, this );
formWidget->setWindowFlags( Qt::Widget );
layout()->addWidget( formWidget );
formWidget->show();
file.close();
createWrappers();
formWidget->installEventFilter( this );
}
}
// Tab layout
if ( !formWidget && mLayer->editorLayout() == QgsVectorLayer::TabLayout )
{
QTabWidget* tabWidget = new QTabWidget( this );
layout()->addWidget( tabWidget );
Q_FOREACH( QgsAttributeEditorElement *widgDef, mLayer->attributeEditorElements() )
{
QWidget* tabPage = new QWidget( tabWidget );
tabWidget->addTab( tabPage, widgDef->name() );
QGridLayout *tabPageLayout = new QGridLayout( tabPage );
if ( widgDef->type() == QgsAttributeEditorElement::AeTypeContainer )
{
QgsAttributeEditorContainer* containerDef = dynamic_cast<QgsAttributeEditorContainer*>( widgDef );
containerDef->setIsGroupBox( false ); // Toplevel widgets are tabs not groupboxes
QString dummy1;
bool dummy2;
tabPageLayout->addWidget( createWidgetFromDef( widgDef, tabPage, mLayer, mContext, dummy1, dummy2 ) );
}
else
{
QgsDebugMsg( "No support for fields in attribute editor on top level" );
}
}
formWidget = tabWidget;
}
// Autogenerate Layout
// If there is still no layout loaded (defined as autogenerate or other methods failed)
if ( !formWidget )
{
formWidget = new QWidget( this );
QGridLayout* gridLayout = new QGridLayout( formWidget );
formWidget->setLayout( gridLayout );
// put the form into a scroll area to nicely handle cases with lots of attributes
QScrollArea* scrollArea = new QScrollArea( this );
scrollArea->setWidget( formWidget );
scrollArea->setWidgetResizable( true );
scrollArea->setFrameShape( QFrame::NoFrame );
scrollArea->setFrameShadow( QFrame::Plain );
scrollArea->setFocusProxy( this );
layout()->addWidget( scrollArea );
int row = 0;
Q_FOREACH( const QgsField& field, mLayer->pendingFields().toList() )
{
int idx = mLayer->fieldNameIndex( field.name() );
//show attribute alias if available
QString fieldName = mLayer->attributeDisplayName( idx );
const QString widgetType = mLayer->editorWidgetV2( idx );
if ( widgetType == "Hidden" )
continue;
const QgsEditorWidgetConfig widgetConfig = mLayer->editorWidgetV2Config( idx );
bool labelOnTop = mLayer->labelOnTop( idx );
// This will also create the widget
QWidget *l = new QLabel( fieldName );
QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, idx, widgetConfig, 0, this, mContext );
QWidget *w = eww ? eww->widget() : new QLabel( QString( "<p style=\"color: red; font-style: italic;\">Failed to create widget with type '%1'</p>" ).arg( widgetType ) );
if ( w )
w->setObjectName( field.name() );
if ( eww )
addWidgetWrapper( eww );
if ( labelOnTop )
{
gridLayout->addWidget( l, row++, 0, 1, 2 );
gridLayout->addWidget( w, row++, 0, 1, 2 );
}
else
{
gridLayout->addWidget( l, row, 0 );
gridLayout->addWidget( w, row++, 1 );
}
}
Q_FOREACH( const QgsRelation& rel, QgsProject::instance()->relationManager()->referencedRelations( mLayer ) )
{
QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, rel, 0, this );
rww->setContext( mContext );
gridLayout->addWidget( rww->widget(), row++, 0, 1, 2 );
mWidgets.append( rww );
}
}
mButtonBox = findChild<QDialogButtonBox*>();
if ( !mButtonBox )
{
mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
mButtonBox->setObjectName( "buttonBox" );
layout()->addWidget( mButtonBox );
}
connectWrappers();
connect( mButtonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
connect( mButtonBox, SIGNAL( rejected() ), this, SLOT( resetValues() ) );
connect( mLayer, SIGNAL( editingStarted() ), this, SLOT( synchronizeEnabledState() ) );
connect( mLayer, SIGNAL( editingStopped() ), this, SLOT( synchronizeEnabledState() ) );
Q_FOREACH( QgsAttributeFormInterface* iface, mInterfaces )
{
iface->initForm();
}
}
void QgsAttributeForm::cleanPython()
{
if ( !mPyFormVarName.isNull() )
{
QString expr = QString( "if locals().has_key('%1'): del %1\n" ).arg( mPyFormVarName );
QgsPythonRunner::run( expr );
}
}
void QgsAttributeForm::initPython()
{
cleanPython();
// Init Python
if ( !mLayer->editFormInit().isEmpty() )
{
QString module = mLayer->editFormInit();
int pos = module.lastIndexOf( "." );
if ( pos >= 0 )
{
QgsPythonRunner::run( QString( "import %1" ).arg( module.left( pos ) ) );
}
/* Reload the module if the DEBUGMODE switch has been set in the module.
If set to False you have to reload QGIS to reset it to True due to Python
module caching */
QString reload = QString( "if hasattr(%1,'DEBUGMODE') and %1.DEBUGMODE:"
" reload(%1)" ).arg( module.left( pos ) );
QgsPythonRunner::run( reload );
QgsPythonRunner::run( "import inspect" );
QString numArgs;
QgsPythonRunner::eval( QString( "len(inspect.getargspec(%1)[0])" ).arg( module ), numArgs );
mPyFormVarName = QString( "_qgis_featureform_%1" ).arg( mFormNr );
QString form = QString( "%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
.arg( mPyFormVarName )
.arg(( unsigned long ) this );
QgsPythonRunner::run( form );
QgsDebugMsg( QString( "running featureForm init: %1" ).arg( mPyFormVarName ) );
// Legacy
if ( numArgs == "3" )
{
addInterface( new QgsAttributeFormLegacyInterface( module, mPyFormVarName, this ) );
}
else
{
#if 0
QString expr = QString( "%1(%2)" )
.arg( mLayer->editFormInit() )
.arg( mPyFormVarName );
QgsAttributeFormInterface* iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface*>( expr, "QgsAttributeFormInterface" );
if ( iface )
addInterface( iface );
#endif
}
}
}
QWidget* QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement *widgetDef, QWidget *parent, QgsVectorLayer *vl, QgsAttributeEditorContext &context, QString &labelText, bool &labelOnTop )
{
QWidget *newWidget = 0;
switch ( widgetDef->type() )
{
case QgsAttributeEditorElement::AeTypeField:
{
const QgsAttributeEditorField* fieldDef = dynamic_cast<const QgsAttributeEditorField*>( widgetDef );
int fldIdx = fieldDef->idx();
if ( fldIdx < vl->pendingFields().count() && fldIdx >= 0 )
{
const QString widgetType = mLayer->editorWidgetV2( fldIdx );
const QgsEditorWidgetConfig widgetConfig = mLayer->editorWidgetV2Config( fldIdx );
QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, fldIdx, widgetConfig, 0, this, mContext );
newWidget = eww->widget();
addWidgetWrapper( eww );
newWidget->setObjectName( mLayer->pendingFields()[ fldIdx ].name() );
}
labelOnTop = mLayer->labelOnTop( fieldDef->idx() );
labelText = mLayer->attributeDisplayName( fieldDef->idx() );
break;
}
case QgsAttributeEditorElement::AeTypeRelation:
{
const QgsAttributeEditorRelation* relDef = dynamic_cast<const QgsAttributeEditorRelation*>( widgetDef );
QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, relDef->relation(), 0, this );
rww->setContext( context );
newWidget = rww->widget();
mWidgets.append( rww );
labelText = QString::null;
labelOnTop = true;
break;
}
case QgsAttributeEditorElement::AeTypeContainer:
{
const QgsAttributeEditorContainer* container = dynamic_cast<const QgsAttributeEditorContainer*>( widgetDef );
QWidget* myContainer;
if ( container->isGroupBox() )
{
QGroupBox* groupBox = new QGroupBox( parent );
groupBox->setTitle( container->name() );
myContainer = groupBox;
newWidget = myContainer;
}
else
{
QScrollArea *scrollArea = new QScrollArea( parent );
myContainer = new QWidget( scrollArea );
scrollArea->setWidget( myContainer );
scrollArea->setWidgetResizable( true );
scrollArea->setFrameShape( QFrame::NoFrame );
newWidget = scrollArea;
}
QGridLayout* gbLayout = new QGridLayout( myContainer );
myContainer->setLayout( gbLayout );
int index = 0;
QList<QgsAttributeEditorElement*> children = container->children();
Q_FOREACH( QgsAttributeEditorElement* childDef, children )
{
QString labelText;
bool labelOnTop;
QWidget* editor = createWidgetFromDef( childDef, myContainer, vl, context, labelText, labelOnTop );
if ( labelText.isNull() )
{
gbLayout->addWidget( editor, index, 0, 1, 2 );
}
else
{
QLabel* mypLabel = new QLabel( labelText );
if ( labelOnTop )
{
gbLayout->addWidget( mypLabel, index, 0, 1, 2 );
++index;
gbLayout->addWidget( editor, index, 0, 1 , 2 );
}
else
{
gbLayout->addWidget( mypLabel, index, 0 );
gbLayout->addWidget( editor, index, 1 );
}
}
++index;
}
gbLayout->addItem( new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding ), index , 0 );
labelText = QString::null;
labelOnTop = true;
break;
}
default:
QgsDebugMsg( "Unknown attribute editor widget type encountered..." );
break;
}
return newWidget;
}
void QgsAttributeForm::addWidgetWrapper( QgsEditorWidgetWrapper* eww )
{
Q_FOREACH( QgsWidgetWrapper* ww, mWidgets )
{
QgsEditorWidgetWrapper* meww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
if ( meww )
{
if ( meww->field() == eww->field() )
{
connect( meww, SIGNAL( valueChanged( QVariant ) ), eww, SLOT( setValue( QVariant ) ) );
connect( eww, SIGNAL( valueChanged( QVariant ) ), meww, SLOT( setValue( QVariant ) ) );
break;
}
}
}
mWidgets.append( eww );
}
void QgsAttributeForm::createWrappers()
{
QList<QWidget*> myWidgets = findChildren<QWidget*>();
const QList<QgsField> fields = mLayer->pendingFields().toList();
Q_FOREACH( QWidget* myWidget, myWidgets )
{
// Check the widget's properties for a relation definition
QVariant vRel = myWidget->property( "qgisRelation" );
if ( vRel.isValid() )
{
QgsRelationManager* relMgr = QgsProject::instance()->relationManager();
QgsRelation relation = relMgr->relation( vRel.toString() );
if ( relation.isValid() )
{
QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, relation, myWidget, this );
rww->setConfig( QgsEditorWidgetConfig() );
rww->setContext( mContext );
mWidgets.append( rww );
}
}
else
{
Q_FOREACH( const QgsField& field, fields )
{
if ( field.name() == myWidget->objectName() )
{
const QString widgetType = mLayer->editorWidgetV2( field.name() );
const QgsEditorWidgetConfig widgetConfig = mLayer->editorWidgetV2Config( field.name() );
int idx = mLayer->fieldNameIndex( field.name() );
QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, idx, widgetConfig, myWidget, this, mContext );
addWidgetWrapper( eww );
}
}
}
}
}
void QgsAttributeForm::connectWrappers()
{
bool isFirstEww = true;
Q_FOREACH( QgsWidgetWrapper* ww, mWidgets )
{
QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
if ( eww )
{
if ( isFirstEww )
{
setFocusProxy( eww->widget() );
isFirstEww = false;
}
connect( eww, SIGNAL( valueChanged( const QVariant& ) ), this, SLOT( onAttributeChanged( const QVariant& ) ) );
}
}
}
bool QgsAttributeForm::eventFilter( QObject* object, QEvent* e )
{
Q_UNUSED( object )
if ( e->type() == QEvent::KeyPress )
{
QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>( e );
if ( keyEvent->key() == Qt::Key_Escape )
{
// Re-emit to this form so it will be forwarded to parent
event( e );
return true;
}
}
return false;
}