Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"On map identification" option of relation reference widget not working properly #22762

Closed
qgib opened this issue May 11, 2016 · 6 comments
Closed
Labels
Bug Either a bug report, or a bug fix. Let's hope for the latter! Forms

Comments

@qgib
Copy link
Contributor

qgib commented May 11, 2016

Author Name: zicke - (zicke -)
Original Redmine Issue: 14805
Affected QGIS version: master
Redmine category:relations
Assignee: Matthias Kuhn


The "On map identification" option does not work if "Add feature" dialog is open. The identification works if you first add the (geometryless) feature and close the dialog (saving feature is not needed) and then identify the geometry.

@qgib
Copy link
Contributor Author

qgib commented Apr 30, 2017

Author Name: Giovanni Manghi (@gioman)


  • regression was configured as 0
  • easy_fix was configured as 0

@qgib
Copy link
Contributor Author

qgib commented Aug 18, 2018

Author Name: Simon South (@simonsouth)


This problem still occurs when trying to add related features (through a many-to-many relation) via the attribute table. It happens because the "Add Feature" dialog is "shown by calling its @exec()@ method":

dialog->exec();
, effectively "making the dialog application-modal":https://doc.qt.io/qt-5/qdialog.html#modal-dialogs and preventing the user from interacting with the main window while it is open (so the feature-identification tool cannot function). Issue #19596 addressed the same problem occurring outside this specific context.

For the courageous, a simple workaround is to apply a similar fix by changing "line 40 of qgsguivectorlayertools.cpp":

bool added = a.addFeature( defaultValues );
from

bool added = a.addFeature( defaultValues );
</code>

to

bool added = a.addFeature( defaultValues, false );
</code>

which effectively makes the dialog non-modal and allows the feature-identification tool to function normally.

This isn't really a solution, though:

  • There's no parent-child relationship between the attribute table and the "Add Feature" dialog (nor any obvious way of specifying one presently), so one window or the other tends to get lost behind the application.

  • Along the same lines, there's no way to specify "window modality":https://doc.qt.io/qt-5/qt.html#WindowModality-enum for the dialog, which is probably what's really needed here.

  • The user is no longer blocked from clicking on the attribute table's controls while the dialog is open, so it may be possible to get the application or the layer in an inconsistent state.

I think a "proper" fix would involve refactoring the "Add Feature" dialog's logic, currently spread across "QgsFeatureAction::addFeature()":

bool QgsFeatureAction::addFeature( const QgsAttributeMap &defaultAttributes, bool showModal, QgsExpressionContextScope *scope SIP_TRANSFER )
{
if ( !mLayer || !mLayer->isEditable() )
return false;
QgsSettings settings;
bool reuseLastValues = settings.value( QStringLiteral( "qgis/digitizing/reuseLastValues" ), false ).toBool();
QgsDebugMsg( QString( "reuseLastValues: %1" ).arg( reuseLastValues ) );
QgsFields fields = mLayer->fields();
QgsAttributeMap initialAttributeValues;
for ( int idx = 0; idx < fields.count(); ++idx )
{
if ( defaultAttributes.contains( idx ) )
{
initialAttributeValues.insert( idx, defaultAttributes.value( idx ) );
}
else if ( reuseLastValues && sLastUsedValues.contains( mLayer ) && sLastUsedValues[ mLayer ].contains( idx ) )
{
initialAttributeValues.insert( idx, sLastUsedValues[ mLayer ][idx] );
}
}
// create new feature template - this will initialize the attributes to valid values, handling default
// values and field constraints
QgsExpressionContext context = mLayer->createExpressionContext();
if ( scope )
context.appendScope( scope );
QgsFeature newFeature = QgsVectorLayerUtils::createFeature( mLayer, mFeature->geometry(), initialAttributeValues,
&context );
*mFeature = newFeature;
//show the dialog to enter attribute values
//only show if enabled in settings and layer has fields
bool isDisabledAttributeValuesDlg = ( fields.count() == 0 ) || settings.value( QStringLiteral( "qgis/digitizing/disable_enter_attribute_values_dialog" ), false ).toBool();
// override application-wide setting with any layer setting
switch ( mLayer->editFormConfig().suppress() )
{
case QgsEditFormConfig::SuppressOn:
isDisabledAttributeValuesDlg = true;
break;
case QgsEditFormConfig::SuppressOff:
isDisabledAttributeValuesDlg = false;
break;
case QgsEditFormConfig::SuppressDefault:
break;
}
if ( isDisabledAttributeValuesDlg )
{
mLayer->beginEditCommand( text() );
mFeatureSaved = mLayer->addFeature( *mFeature );
if ( mFeatureSaved )
{
mLayer->endEditCommand();
mLayer->triggerRepaint();
}
else
{
mLayer->destroyEditCommand();
}
}
else
{
QgsAttributeDialog *dialog = newDialog( false );
// delete the dialog when it is closed
dialog->setAttribute( Qt::WA_DeleteOnClose );
dialog->setMode( QgsAttributeForm::AddFeatureMode );
dialog->setEditCommandMessage( text() );
connect( dialog->attributeForm(), &QgsAttributeForm::featureSaved, this, &QgsFeatureAction::onFeatureSaved );
if ( !showModal )
{
setParent( dialog ); // keep dialog until the dialog is closed and destructed
dialog->show();
mFeature = nullptr;
return true;
}
dialog->exec();
}
// Will be set in the onFeatureSaved SLOT
return mFeatureSaved;
}
and "QgsFeatureAction::newDialog()":
QgsAttributeDialog *QgsFeatureAction::newDialog( bool cloneFeature )
{
QgsFeature *f = cloneFeature ? new QgsFeature( *mFeature ) : mFeature;
QgsAttributeEditorContext context;
QgsDistanceArea myDa;
myDa.setSourceCrs( mLayer->crs(), QgsProject::instance()->transformContext() );
myDa.setEllipsoid( QgsProject::instance()->ellipsoid() );
context.setDistanceArea( myDa );
context.setVectorLayerTools( QgisApp::instance()->vectorLayerTools() );
context.setMapCanvas( QgisApp::instance()->mapCanvas() );
context.setFormMode( QgsAttributeEditorContext::StandaloneDialog );
QgsAttributeDialog *dialog = new QgsAttributeDialog( mLayer, f, cloneFeature, parentWidget(), true, context );
dialog->setWindowFlags( dialog->windowFlags() | Qt::Tool );
dialog->setObjectName( QStringLiteral( "featureactiondlg:%1:%2" ).arg( mLayer->id() ).arg( f->id() ) );
QList<QgsAction> actions = mLayer->actions()->actions( QStringLiteral( "Feature" ) );
if ( !actions.isEmpty() )
{
dialog->setContextMenuPolicy( Qt::ActionsContextMenu );
QAction *a = new QAction( tr( "Run actions" ), dialog );
a->setEnabled( false );
dialog->addAction( a );
Q_FOREACH ( const QgsAction &action, actions )
{
if ( !action.runable() )
continue;
if ( !mLayer->isEditable() && action.isEnabledOnlyWhenEditable() )
continue;
QgsFeature &feat = const_cast<QgsFeature &>( *dialog->feature() );
QgsFeatureAction *a = new QgsFeatureAction( action.name(), feat, mLayer, action.id(), -1, dialog );
dialog->addAction( a );
connect( a, &QAction::triggered, a, &QgsFeatureAction::execute );
QAbstractButton *pb = dialog->findChild<QAbstractButton *>( action.name() );
if ( pb )
connect( pb, &QAbstractButton::clicked, a, &QgsFeatureAction::execute );
}
}
return dialog;
}
, into a separate class that inherits from @QDialog@ and simply returns the user's description of a new feature (or perhaps the feature itself) to its caller. That would provide the flexibility to use the dialog in multiple contexts and a straightforward way of specifying things like the dialog's parent and modality when important.

@qgib
Copy link
Contributor Author

qgib commented Aug 19, 2018

Author Name: Giovanni Manghi (@gioman)


It seems you could provide a patch via a pull request on github?

Simon South wrote:

This problem still occurs when trying to add related features (through a many-to-many relation) via the attribute table. It happens because the "Add Feature" dialog is "shown by calling its @exec()@ method":

dialog->exec();
, effectively "making the dialog application-modal":https://doc.qt.io/qt-5/qdialog.html#modal-dialogs and preventing the user from interacting with the main window while it is open (so the feature-identification tool cannot function). Issue #19596 addressed the same problem occurring outside this specific context.

For the courageous, a simple workaround is to apply a similar fix by changing "line 40 of qgsguivectorlayertools.cpp":

bool added = a.addFeature( defaultValues );
from

[...]

to

[...]

which effectively makes the dialog non-modal and allows the feature-identification tool to function normally.

This isn't really a solution, though:

  • There's no parent-child relationship between the attribute table and the "Add Feature" dialog (nor any obvious way of specifying one presently), so one window or the other tends to get lost behind the application.

  • Along the same lines, there's no way to specify "window modality":https://doc.qt.io/qt-5/qt.html#WindowModality-enum for the dialog, which is probably what's really needed here.

  • The user is no longer blocked from clicking on the attribute table's controls while the dialog is open, so it may be possible to get the application or the layer in an inconsistent state.

I think a "proper" fix would involve refactoring the "Add Feature" dialog's logic, currently spread across "QgsFeatureAction::addFeature()":

bool QgsFeatureAction::addFeature( const QgsAttributeMap &defaultAttributes, bool showModal, QgsExpressionContextScope *scope SIP_TRANSFER )
{
if ( !mLayer || !mLayer->isEditable() )
return false;
QgsSettings settings;
bool reuseLastValues = settings.value( QStringLiteral( "qgis/digitizing/reuseLastValues" ), false ).toBool();
QgsDebugMsg( QString( "reuseLastValues: %1" ).arg( reuseLastValues ) );
QgsFields fields = mLayer->fields();
QgsAttributeMap initialAttributeValues;
for ( int idx = 0; idx < fields.count(); ++idx )
{
if ( defaultAttributes.contains( idx ) )
{
initialAttributeValues.insert( idx, defaultAttributes.value( idx ) );
}
else if ( reuseLastValues && sLastUsedValues.contains( mLayer ) && sLastUsedValues[ mLayer ].contains( idx ) )
{
initialAttributeValues.insert( idx, sLastUsedValues[ mLayer ][idx] );
}
}
// create new feature template - this will initialize the attributes to valid values, handling default
// values and field constraints
QgsExpressionContext context = mLayer->createExpressionContext();
if ( scope )
context.appendScope( scope );
QgsFeature newFeature = QgsVectorLayerUtils::createFeature( mLayer, mFeature->geometry(), initialAttributeValues,
&context );
*mFeature = newFeature;
//show the dialog to enter attribute values
//only show if enabled in settings and layer has fields
bool isDisabledAttributeValuesDlg = ( fields.count() == 0 ) || settings.value( QStringLiteral( "qgis/digitizing/disable_enter_attribute_values_dialog" ), false ).toBool();
// override application-wide setting with any layer setting
switch ( mLayer->editFormConfig().suppress() )
{
case QgsEditFormConfig::SuppressOn:
isDisabledAttributeValuesDlg = true;
break;
case QgsEditFormConfig::SuppressOff:
isDisabledAttributeValuesDlg = false;
break;
case QgsEditFormConfig::SuppressDefault:
break;
}
if ( isDisabledAttributeValuesDlg )
{
mLayer->beginEditCommand( text() );
mFeatureSaved = mLayer->addFeature( *mFeature );
if ( mFeatureSaved )
{
mLayer->endEditCommand();
mLayer->triggerRepaint();
}
else
{
mLayer->destroyEditCommand();
}
}
else
{
QgsAttributeDialog *dialog = newDialog( false );
// delete the dialog when it is closed
dialog->setAttribute( Qt::WA_DeleteOnClose );
dialog->setMode( QgsAttributeForm::AddFeatureMode );
dialog->setEditCommandMessage( text() );
connect( dialog->attributeForm(), &QgsAttributeForm::featureSaved, this, &QgsFeatureAction::onFeatureSaved );
if ( !showModal )
{
setParent( dialog ); // keep dialog until the dialog is closed and destructed
dialog->show();
mFeature = nullptr;
return true;
}
dialog->exec();
}
// Will be set in the onFeatureSaved SLOT
return mFeatureSaved;
}
and "QgsFeatureAction::newDialog()":
QgsAttributeDialog *QgsFeatureAction::newDialog( bool cloneFeature )
{
QgsFeature *f = cloneFeature ? new QgsFeature( *mFeature ) : mFeature;
QgsAttributeEditorContext context;
QgsDistanceArea myDa;
myDa.setSourceCrs( mLayer->crs(), QgsProject::instance()->transformContext() );
myDa.setEllipsoid( QgsProject::instance()->ellipsoid() );
context.setDistanceArea( myDa );
context.setVectorLayerTools( QgisApp::instance()->vectorLayerTools() );
context.setMapCanvas( QgisApp::instance()->mapCanvas() );
context.setFormMode( QgsAttributeEditorContext::StandaloneDialog );
QgsAttributeDialog *dialog = new QgsAttributeDialog( mLayer, f, cloneFeature, parentWidget(), true, context );
dialog->setWindowFlags( dialog->windowFlags() | Qt::Tool );
dialog->setObjectName( QStringLiteral( "featureactiondlg:%1:%2" ).arg( mLayer->id() ).arg( f->id() ) );
QList<QgsAction> actions = mLayer->actions()->actions( QStringLiteral( "Feature" ) );
if ( !actions.isEmpty() )
{
dialog->setContextMenuPolicy( Qt::ActionsContextMenu );
QAction *a = new QAction( tr( "Run actions" ), dialog );
a->setEnabled( false );
dialog->addAction( a );
Q_FOREACH ( const QgsAction &action, actions )
{
if ( !action.runable() )
continue;
if ( !mLayer->isEditable() && action.isEnabledOnlyWhenEditable() )
continue;
QgsFeature &feat = const_cast<QgsFeature &>( *dialog->feature() );
QgsFeatureAction *a = new QgsFeatureAction( action.name(), feat, mLayer, action.id(), -1, dialog );
dialog->addAction( a );
connect( a, &QAction::triggered, a, &QgsFeatureAction::execute );
QAbstractButton *pb = dialog->findChild<QAbstractButton *>( action.name() );
if ( pb )
connect( pb, &QAbstractButton::clicked, a, &QgsFeatureAction::execute );
}
}
return dialog;
}
, into a separate class that inherits from @QDialog@ and simply returns the user's description of a new feature (or perhaps the feature itself) to its caller. That would provide the flexibility to use the dialog in multiple contexts and a straightforward way of specifying things like the dialog's parent and modality when important.

@qgib
Copy link
Contributor Author

qgib commented Aug 19, 2018

Author Name: Simon South (@simonsouth)


Giovanni Manghi wrote:

It seems you could provide a patch via a pull request on github?

Sure, but I don't consider the workaround I posted to be a suitable fix. The code needs to be refactored and this could be a significant undertaking, considering the "Add Feature" dialog is used in multiple places.

Does the idea of pulling out the UI-related portions of @QgsFeatureAction@ into a separate @QDialog@ subclass sound reasonable to you? If I could demonstrate this working, is it something the QGIS team might accept?

@qgib
Copy link
Contributor Author

qgib commented Aug 20, 2018

Author Name: Giovanni Manghi (@gioman)


Simon South wrote:

Giovanni Manghi wrote:

It seems you could provide a patch via a pull request on github?

Sure, but I don't consider the workaround I posted to be a suitable fix. The code needs to be refactored and this could be a significant undertaking, considering the "Add Feature" dialog is used in multiple places.

Does the idea of pulling out the UI-related portions of @QgsFeatureAction@ into a separate @QDialog@ subclass sound reasonable to you? If I could demonstrate this working, is it something the QGIS team might accept?

your suggestion are valuable, but to make them gain traction you should make a pull request or eventually comment the existing code, but things better done on github. Thanks!

@qgib
Copy link
Contributor Author

qgib commented Mar 9, 2019

Author Name: Giovanni Manghi (@gioman)


End of life notice: QGIS 2.18 LTR

Source:
http://blog.qgis.org/2019/03/09/end-of-life-notice-qgis-2-18-ltr/


  • resolution was changed from to end of life
  • status_id was changed from Open to Closed

@qgib qgib closed this as completed Mar 9, 2019
@qgib qgib added Bug Either a bug report, or a bug fix. Let's hope for the latter! Forms labels May 25, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Either a bug report, or a bug fix. Let's hope for the latter! Forms
Projects
None yet
Development

No branches or pull requests

1 participant