Skip to content
Permalink
Browse files

Add easy methods for temporarily blocking signals

- QgsSignalBlocker: RAII signal blocking class. Used for temporarily
  blocking signals from a QObject for the lifetime of QgsSignalBlocker
  object.

- easy shortcut "whileBlocking( QObject* )" function. Temporarily
  blocks signals from a QObject while calling a single method from the
  object.

  Usage:
    whileBlocking( checkBox )->setChecked( true );
    whileBlocking( spinBox )->setValue( 50 );

  No signals will be emitted when calling these methods.

based on Boojum's code from
http://stackoverflow.com/questions/3556687/prevent-firing-signals-in-qt
  • Loading branch information
nyalldawson committed Apr 5, 2016
1 parent 882f6f8 commit ca642e44ff6f1dee76ac1b3978aa8ef01b4cfcd2
@@ -936,9 +936,7 @@ void QgsComposer::updateStatusZoom()
//current zoomLevel
double zoomLevel = mView->transform().m11() * 100 / scale100;

mStatusZoomCombo->blockSignals( true );
mStatusZoomCombo->lineEdit()->setText( tr( "%1%" ).arg( zoomLevel, 0, 'f', 1 ) );
mStatusZoomCombo->blockSignals( false );
whileBlocking( mStatusZoomCombo )->lineEdit()->setText( tr( "%1%" ).arg( zoomLevel, 0, 'f', 1 ) );
}

void QgsComposer::statusZoomCombo_currentIndexChanged( int index )
@@ -948,9 +946,7 @@ void QgsComposer::statusZoomCombo_currentIndexChanged( int index )
{
mView->setZoomLevel( selectedZoom );
//update zoom combobox text for correct format (one decimal place, trailing % sign)
mStatusZoomCombo->blockSignals( true );
mStatusZoomCombo->lineEdit()->setText( tr( "%1%" ).arg( selectedZoom * 100.0, 0, 'f', 1 ) );
mStatusZoomCombo->blockSignals( false );
whileBlocking( mStatusZoomCombo )->lineEdit()->setText( tr( "%1%" ).arg( selectedZoom * 100.0, 0, 'f', 1 ) );
}
}

@@ -1087,9 +1083,7 @@ void QgsComposer::on_mActionAtlasPreview_triggered( bool checked )
tr( "Atlas in not currently enabled for this composition!" ),
QMessageBox::Ok,
QMessageBox::Ok );
mActionAtlasPreview->blockSignals( true );
mActionAtlasPreview->setChecked( false );
mActionAtlasPreview->blockSignals( false );
whileBlocking( mActionAtlasPreview )->setChecked( false );
mStatusAtlasLabel->setText( QString() );
return;
}
@@ -1217,9 +1211,7 @@ void QgsComposer::atlasPageComboEditingFinished()

if ( !ok || page > mComposition->atlasComposition().numFeatures() || page < 1 )
{
mAtlasPageComboBox->blockSignals( true );
mAtlasPageComboBox->setCurrentIndex( mComposition->atlasComposition().currentFeatureNumber() );
mAtlasPageComboBox->blockSignals( false );
whileBlocking( mAtlasPageComboBox )->setCurrentIndex( mComposition->atlasComposition().currentFeatureNumber() );
}
else if ( page != mComposition->atlasComposition().currentFeatureNumber() + 1 )
{
@@ -1573,9 +1565,7 @@ void QgsComposer::dockVisibilityChanged( bool visible )
{
if ( visible )
{
mActionHidePanels->blockSignals( true );
mActionHidePanels->setChecked( false );
mActionHidePanels->blockSignals( false );
whileBlocking( mActionHidePanels )->setChecked( false );
}
}

@@ -4136,9 +4126,7 @@ void QgsComposer::setAtlasFeature( QgsMapLayer* layer, const QgsFeature& feat )
{
mComposition->setAtlasMode( QgsComposition::PreviewAtlas );
//update gui controls
mActionAtlasPreview->blockSignals( true );
mActionAtlasPreview->setChecked( true );
mActionAtlasPreview->blockSignals( false );
whileBlocking( mActionAtlasPreview )->setChecked( true );
mActionAtlasFirst->setEnabled( true );
mActionAtlasLast->setEnabled( true );
mActionAtlasNext->setEnabled( true );
@@ -545,9 +545,7 @@ void QgsComposerAttributeTableWidget::atlasToggled()
if ( !mComposerTable )
return;

mSourceComboBox->blockSignals( true );
mSourceComboBox->setCurrentIndex( mSourceComboBox->findData( mComposerTable->source() ) );
mSourceComboBox->blockSignals( false );
whileBlocking( mSourceComboBox )->setCurrentIndex( mSourceComboBox->findData( mComposerTable->source() ) );

if ( !atlasEnabled && mComposerTable->filterToAtlasFeature() )
{
@@ -646,9 +644,7 @@ void QgsComposerAttributeTableWidget::blockAllSignals( bool b )

void QgsComposerAttributeTableWidget::setMaximumNumberOfFeatures( int n )
{
mMaximumRowsSpinBox->blockSignals( true );
mMaximumRowsSpinBox->setValue( n );
mMaximumRowsSpinBox->blockSignals( false );
whileBlocking( mMaximumRowsSpinBox )->setValue( n );
}

void QgsComposerAttributeTableWidget::on_mShowOnlyVisibleFeaturesCheckBox_stateChanged( int state )
@@ -157,20 +157,12 @@ void QgsComposerImageExportOptionsDialog::clipToContentsToggled( bool state )

if ( state )
{
mWidthSpinBox->blockSignals( true );
mWidthSpinBox->setValue( 0 );
mWidthSpinBox->blockSignals( false );
mHeightSpinBox->blockSignals( true );
mHeightSpinBox->setValue( 0 );
mHeightSpinBox->blockSignals( false );
whileBlocking( mWidthSpinBox )->setValue( 0 );
whileBlocking( mHeightSpinBox )->setValue( 0 );
}
else
{
mWidthSpinBox->blockSignals( true );
mWidthSpinBox->setValue( mImageSize.width() * mResolutionSpinBox->value() / 25.4 );
mWidthSpinBox->blockSignals( false );
mHeightSpinBox->blockSignals( true );
mHeightSpinBox->setValue( mImageSize.height() * mResolutionSpinBox->value() / 25.4 );
mHeightSpinBox->blockSignals( false );
whileBlocking( mWidthSpinBox )->setValue( mImageSize.width() * mResolutionSpinBox->value() / 25.4 );
whileBlocking( mHeightSpinBox )->setValue( mImageSize.height() * mResolutionSpinBox->value() / 25.4 );
}
}
@@ -278,6 +278,54 @@ inline void ( *cast_to_fptr( void *p ) )()
}
#endif

/** RAII signal blocking class. Used for temporarily blocking signals from a QObject
* for the lifetime of QgsSignalBlocker object.
* @see whileBlocking()
* @note added in QGIS 2.16
* @note not available in Python bindings
*/
// based on Boojum's code from http://stackoverflow.com/questions/3556687/prevent-firing-signals-in-qt
template<class Object> class QgsSignalBlocker
{
public:

QgsSignalBlocker( Object* object )
: mObject( object )
, mPreviousState( object->blockSignals( true ) )
{}

~QgsSignalBlocker()
{
mObject->blockSignals( mPreviousState );
}

Object* operator->() { return mObject; }

private:

Object* mObject;
bool mPreviousState;

};

/** Temporarily blocks signals from a QObject while calling a single method from the object.
*
* Usage:
* whileBlocking( checkBox )->setChecked( true );
* whileBlocking( spinBox )->setValue( 50 );
*
* No signals will be emitted when calling these methods.
*
* @note added in QGIS 2.16
* @see QgsSignalBlocker
* @note not available in Python bindings
*/
// based on Boojum's code from http://stackoverflow.com/questions/3556687/prevent-firing-signals-in-qt
template<class Object> inline QgsSignalBlocker<Object> whileBlocking( Object* object )
{
return QgsSignalBlocker<Object>( object );
}

//
// return a string representation of a double
//
@@ -32,32 +32,6 @@

#include <limits>

///@cond PRIVATE

// RAII class to block a QObject signal until destroyed
struct SignalBlocker
{
SignalBlocker( QObject * object )
: mObject( object )
{
mObject->blockSignals( true );
}
~SignalBlocker()
{
mObject->blockSignals( false );
}
private:
QObject * mObject;

SignalBlocker( const SignalBlocker& rh );
SignalBlocker& operator=( const SignalBlocker& rh );



};

///@endcond

void QgsSizeScaleWidget::setFromSymbol()
{
if ( !mSymbol )
@@ -82,20 +56,18 @@ void QgsSizeScaleWidget::setFromSymbol()
{
if ( scaleMethodComboBox->itemData( i ).toInt() == int( expr.type() ) )
{
( SignalBlocker( scaleMethodComboBox ), scaleMethodComboBox->setCurrentIndex( i ) );
whileBlocking( scaleMethodComboBox )->setCurrentIndex( i );
break;
}
}

// the (,) is used to create the Blocker first, then call the setter
// the unamed SignalBlocker is destroyed at the end of the line (semicolumn)
( SignalBlocker( mExpressionWidget ), mExpressionWidget->setField( expr.baseExpression() ) );
( SignalBlocker( minValueSpinBox ), minValueSpinBox->setValue( expr.minValue() ) );
( SignalBlocker( maxValueSpinBox ), maxValueSpinBox->setValue( expr.maxValue() ) );
( SignalBlocker( minSizeSpinBox ), minSizeSpinBox->setValue( expr.minSize() ) );
( SignalBlocker( maxSizeSpinBox ), maxSizeSpinBox->setValue( expr.maxSize() ) );
( SignalBlocker( nullSizeSpinBox ), nullSizeSpinBox->setValue( expr.nullSize() ) );
( SignalBlocker( exponentSpinBox ), exponentSpinBox->setValue( expr.exponent() ) );
whileBlocking( mExpressionWidget )->setField( expr.baseExpression() );
whileBlocking( minValueSpinBox )->setValue( expr.minValue() );
whileBlocking( maxValueSpinBox )->setValue( expr.maxValue() );
whileBlocking( minSizeSpinBox )->setValue( expr.minSize() );
whileBlocking( maxSizeSpinBox )->setValue( expr.maxSize() );
whileBlocking( nullSizeSpinBox )->setValue( expr.nullSize() );
whileBlocking( exponentSpinBox )->setValue( expr.exponent() );
}
updatePreview();
}
@@ -329,8 +301,8 @@ void QgsSizeScaleWidget::computeFromLayerTriggered()
min = qMin( min, value );
}
}
( SignalBlocker( minValueSpinBox ), minValueSpinBox->setValue( min ) );
( SignalBlocker( maxSizeSpinBox ), maxValueSpinBox->setValue( max ) );
whileBlocking( minValueSpinBox )->setValue( min );
whileBlocking( maxValueSpinBox )->setValue( max );
updatePreview();
}

@@ -16,6 +16,7 @@
#include <QObject>
#include <QString>
#include <QApplication>
#include <QCheckBox>

//qgis includes...
#include <qgis.h>
@@ -37,6 +38,7 @@ class TestQGis : public QObject
void permissiveToInt();
void doubleToString();
void qgsround();
void signalBlocker();

private:
QString mReport;
@@ -151,5 +153,76 @@ void TestQGis::qgsround()
QCOMPARE( qgsRound( -1.5 ), -2. );
}

void TestQGis::signalBlocker()
{
QScopedPointer< QCheckBox > checkbox( new QCheckBox() );

QSignalSpy spy( checkbox.data(), SIGNAL( toggled( bool ) ) );

//first check that signals are not blocked
QVERIFY( !checkbox->signalsBlocked() );
checkbox->setChecked( true );
QCOMPARE( spy.count(), 1 );
QCOMPARE( spy.last().at( 0 ).toBool(), true );

//block signals
{
QgsSignalBlocker< QCheckBox > blocker( checkbox.data() );
QVERIFY( checkbox->signalsBlocked() );

checkbox->setChecked( false );
QVERIFY( !checkbox->isChecked() );

//should be no new signals
QCOMPARE( spy.count(), 1 );
QCOMPARE( spy.last().at( 0 ).toBool(), true );
checkbox->setChecked( true );
}

//blocker is out of scope, blocking should be removed
QVERIFY( !checkbox->signalsBlocked() );
checkbox->setChecked( false );
QCOMPARE( spy.count(), 2 );
QCOMPARE( spy.last().at( 0 ).toBool(), false );

// now check that initial blocking state is restored when QgsSignalBlocker goes out of scope
checkbox->blockSignals( true );
{
QgsSignalBlocker< QCheckBox > blocker( checkbox.data() );
QVERIFY( checkbox->signalsBlocked() );
}
// initial blocked state should be restored
QVERIFY( checkbox->signalsBlocked() );
checkbox->blockSignals( false );

// nested signal blockers
{
QgsSignalBlocker< QCheckBox > blocker( checkbox.data() );
QVERIFY( checkbox->signalsBlocked() );
{
QgsSignalBlocker< QCheckBox > blocker2( checkbox.data() );
QVERIFY( checkbox->signalsBlocked() );
}
QVERIFY( checkbox->signalsBlocked() );
}
QVERIFY( !checkbox->signalsBlocked() );

// check whileBlocking function
checkbox->setChecked( true );
QCOMPARE( spy.count(), 3 );
QCOMPARE( spy.last().at( 0 ).toBool(), true );

QVERIFY( !checkbox->signalsBlocked() );
whileBlocking( checkbox.data() )->setChecked( false );
// should have been no signals emitted
QCOMPARE( spy.count(), 3 );
// check that initial state of blocked signals was restored correctly
QVERIFY( !checkbox->signalsBlocked() );
checkbox->blockSignals( true );
QVERIFY( checkbox->signalsBlocked() );
whileBlocking( checkbox.data() )->setChecked( true );
QVERIFY( checkbox->signalsBlocked() );
}

QTEST_MAIN( TestQGis )
#include "testqgis.moc"

3 comments on commit ca642e4

@NathanW2

This comment has been minimized.

Copy link
Member

@NathanW2 NathanW2 replied Apr 5, 2016

Well that is pretty bloody sweet! Nice work

@nyalldawson

This comment has been minimized.

Copy link
Collaborator Author

@nyalldawson nyalldawson replied Apr 5, 2016

@NathanW2

This comment has been minimized.

Copy link
Member

@NathanW2 NathanW2 replied Apr 5, 2016

:)

Please sign in to comment.
You can’t perform that action at this time.