The Qt® 4 Resource Center

The Signal Spy

In some situations it can be very helpful to be able to look at the signals sent and received from and to a widget. This is where the signal spying class comes in handy. It makes it possible to intercept the emitted signals with their arguments and makes understanding complex dependencies easier.

Introduction

The signal spyer class is being developed by Prashanth Udupa, an experienced Qt developer. He provides a good reference documentation including a working example. There is also a Qt 3 version, which includes, among other things, in-depth look at spying in Qt 3.

In order to understand where this class is useful, we will start with a custom widget showing colors and converting values between the RGB and HSV color-spaces.

The Color Widget

The ColorWidget class implements a color picker widget. It can be connected to slider (or dials, spinboxes, etc.) for both RGB and HSV. A change in one color-space, is reflected in the other. This is where the complexity enters. Because the conversion isn't 100% perfect, a change is RGB can affect the HSV side so that another change is triggered on the RGB side. It sounds complex, but the signal spyer will help us understand.

Before delving deeper into the details, example 1 shows the class declaration for the color widget. As can be seen, it is derived from a QFrame. The color shown in the widget can be accessed as a QColor, or through each individual factor of either color-space. The only event handled is the paint event.

class ColorWidget : public QFrame
{
  Q_OBJECT
  
public:
  ColorWidget( QWidget *parent=0 );
  
  const QColor &getColor( void );
  
public slots:
  void setColor( const QColor& );
  void setR( int );
...
  void setV( int );
  
signals:
  void rChanged( int );
...
  void vChanged( int );
  
protected:
  void paintEvent( QPaintEvent *ev );

private:
  QColor m_color;
};
Example 4

The purpose of the widget is to show a dialog, either with RGB slider, HSV sliders, or both. Wait for the user to pick a color and then use getColor to get the final color.

The handling of the paint event is slightly more complex than it would have been in Qt 3. The drawContents method is gone, instead, one has to explicitly call drawFrame. This isn't a heave burden, and the actual code is shown in example 2.

void ColorWidget::paintEvent( QPaintEvent *ev )
{
  QPainter p( this );
  QRect contents = contentsRect();
  contents.adjust( 0, 0 ,-lineWidth(), -lineWidth() );

  drawFrame( &p );
  p.setPen( m_color );
  p.setBrush( m_color );
  p.drawRect( contents );
}
Example 4

Now for the nasty part - color-space conversions. All the set-ers internally calls the setColor method. In that method, the color is changed, and if any one of the factors changed, such a signal is emitted. It is important to realize that the RGB color-space is considered to be primary, i.e. the HSV changes are only considered if the RGB side changes. This prevents loops where an RGB change triggers an HSV change that triggers an RGB change that triggers...

The actual color-space conversion is performed inside the QColor class. This is due to two reasons: I'm feeling lazy, and the conversion works exactly as any other conversion that Qt feels necessary. Example 3 shows the setColor method and one of the factor set-ers.

void ColorWidget::setColor( const QColor &color )
{
  int r,g,b,h,s,v;
  int m_r, m_g, m_b, m_h, m_s, m_v;
  
  color.getRgb( &r, &g, &b );
  color.getHsv( &h, &s, &v );
  
  m_color.getRgb( &m_r, &m_g, &m_b );
  m_color.getHsv( &m_h, &m_s, &m_v );
  
  m_color = color;
  
  bool changed = false;
  
  if( r != m_r )
  {
    changed = true;
    emit( rChanged( r ) );
  }
  
...

  if( changed )
  {  
    if( h != m_h )
      emit( hChanged( h ) );

...
  
    repaint();
  }
}

...

void ColorWidget::setB( int v )
{
  int r, g, b;
  
  m_color.getRgb( &r, &g, &b );
  b = v;
  
  setColor( QColor::fromRgb( r, g, b ) );
}
Example 4

The Dialog

To be able to use this, we will have to create a dialog. The creation and set-up is fairly straight forward. It simply creates the widgets, connects all the sliders to the color widget, puts everyting in a layout and then shows it. The interested reader can see the code by downloading the tar-ball at then end of the article. The resulting dialog (including the signal spy, which comes next) is shown as figure 1.


Figure 1

Adding the Spy

For the actual spying to work, we will have to add a QTextEdit widget to show the results from the spy. For some odd reason, text appending does not work as I want it to in Qt 4.0.x, so I use a Q3TextEdit. The creation and set-up of the spy and widget together with the needed connections are shown in example 4. For the Q3TextEdit to work properly, the line QT += qt3support has to be added to the pro-file for QMake, but that is well described in Trolltech's documentation.

  Q3TextEdit *te = new Q3TextEdit( dlg );

  Q4puGenericSignalSpy *spy = new Q4puGenericSignalSpy( dlg );
  spy->spyOn( cw );

  QObject::connect( spy, SIGNAL( caughtSignal( const QString& ) ), te, SLOT( append( const QString& ) ) );
  QObject::connect( spy, SIGNAL( caughtSlot( const QString& ) ), te, SLOT( append( const QString& ) ) );
Example 4

Figure 2 shows how a slider move works. First, setR is invoked from the R slider, this causes an rChanged signal to be emitted, but also a hChanged signal. The signal moves the H slider, which triggers the setH slot. This change is stopped in the setColor method and the change is over.


Figure 2

Figure 3 shows a slightly more complex change. First setR is invoked, which, as in the example above, changed both the R and H values. However, this time the H change is big enough to affect the R signal again. Luckily the second R change is small enough to be stopped in the setColor method without emitting any further signals.


Figure 3

Conclusions

First of all, the complete source code for this example can be downloaded from here.

Trying to debug complex signals and slot dependencies can be a tedious task. Making the right connections is usually easy to do, and having made the wrong ones is easy to detect. However, when signals leads to new signals calling the original slot, the ability to overhear the signals and slots being called and their arguments is very helpful. This class is definitly a permanent item in my toolbox.

By: Johan Thelin