4.3.3. Widgets WAF Module

Under cut-widget-library/widgets, a new WAF module is provided. The artifact from this module will be one C++ shared library, that is used when compiling and running applications that uses the widgets from this project. The name of the library will be lib<target>.so.

The contents of this module wscript is the following.:

from wtools.module import declare_qt5cshlib

# NOTE: WAF autoimports Core Gui and Widgets. But eventually this will be removed.
#  developers should always explicitly declared dependencies.

# NOTE: The name of the target can be different from the name of the directory.
declare_qt5cshlib( target='CutWidgets',
                   use='QT5CORE QT5GUI QT5WIDGETS'
)

The declare_qt5cshlib command will automatically search for any UI files, and produce the necessary UIC instruction, creating the C++ file for that UI file.

Also, when installing this library, its target will be $INTROOT/lib64, which is already in the LD_LIBRARY_PATH, and therefore your program will be able to find it. Also, this will install the includes, so that developer can find them in $INTROOT/include.

Warning

Any C++ class that inherits from QObject (including widgets), should include this 3 lines of code in their implementation (.cpp) file:

#if WAF
#include "include/<path_to_header>/widgetnameone.moc"
#endif

Qt uses signal, slot and properties. Connections between them are made using instrospection utilities that are added into all classes that inherit from QObject. The MetaObject Compiler (or MOC) is in charge of generating a new file that provides that added features.

WAF needs these three lines of code to determine when a class need to have its .moc version generated (See WAF Qt5 documentation).

The files in this cut-widget-library/widgets WAF module are:

cut-widget-library/
├── widgets
│   ├── src
│   │   ├── CutDmsLabel.cpp
│   │   └── include
│   │       ├── CutWidgets
│   │       │   └── CutDmsLabel.hpp
│   │       └── CutWidgets.h
│   └── wscript
└── wscript

Two files are needed to create a widget: cut-widget-library/widgets/src/include/CutWidgets/CutDmsLabel.hpp and cut-widget-library/widgets/src/CutDmsLabel.cpp. The first one defined the class of the widget, and the second one implements the methods.

4.3.3.1. Header (.hpp)

All Qt Widgets should inherit from QWidget, or any other class that also does at some point of it inheritance tree.

CutDmsLabel.hpp listing is found below. It inherits from QLabel, which inherits from QWidget. QLabel is a widget that presents a string on the GUI. It is one of the most used widgets. In this example, we will create a new widget named CutDmsLabel, and add a new interface to it, so that it can accept radians as input values, and automatically convert that value to Degrees, Minutes, Seconds (DMS) format using slalib_c.

Any class that inherits from QObject should have inside its class declaration, as the first statement, the call to the Q_OBJECT macro. This prepares structures that Qt needs to provide introspection as part of the MetaObject system.

#ifndef CUTDMSLABEL_HPP
#define CUTDMSLABEL_HPP

#include <QObject>
#include <QLabel>

class CutDmsLabel : public QLabel {
    Q_OBJECT

    Q_PROPERTY(double radians MEMBER m_radians READ getRadians WRITE setRadians NOTIFY radiansChanged )
    Q_PROPERTY(int precision MEMBER m_precision READ getPrecision WRITE setPrecision NOTIFY precisionChanged )

public:
    explicit CutDmsLabel(QWidget *parent = Q_NULLPTR);
    ~CutDmsLabel();
    double getRadians(){ return m_radians; };
    double getPrecision(){ return m_precision; };

signals:
    void radiansChanged(double newValue);
    void precisionChanged(int newValue);

public slots:
    void setRadians(double newValue){this->m_radians = newValue; this->update();};
    void setPrecision(int newValue){this->m_precision = newValue; this->update();};
    void update();

private:
    double m_radians = 0.0;
    int m_precision = 3;
};

#endif  // CUTDMSLABEL_HPP

It is important that the developer uses Qt’s features to communicate. A widget’s interface is composed of Properties, Signals and Slots.

In this example, we have create a Property using the Q_PROPERTY macro:

  • The type of this property is double.

  • The name of the property is radians.

  • The value of the property will be stored in a MEMBER attribute of this class named m_radians. Qt does not handle set/get methods, this piece of information is mostly for the developer and not Qt.

  • the getter method, is determine after READ and is named getRadians. It is up to the developer to provide it.

  • the setter method is determine after the WRITE entry, and is named setRadians.

  • when the value of the radians property changes, Qt will automatically trigger the radiansChanged(double) signal, that includes the new value as argument.

The implementation of the getter is very simple, and consists in just a return statement.:

double getRadians(){ return m_radians; };

Signals on the other hand, should never be implemented. They are provided as part of the signature of a class, but Qt through the MOC compiler automatically provides an implementation. The type in the signature should match the type of the property.

void radiansChanged(double newValue);

The setter method is also simple, and accepts as argument the same type of the property. The implementation takes the value, and asigns it to the member attribute.

void setRadians(double newValue){this->m_radians = newValue; this->update();};

But at the end, it invokes this->update(). This is very important, as it enqueus a graphical update of the widget. Qt will keep track of these updates, and will request only once per frame that the widget redraws itself.

The other Property precision follows the same logic. There is one extra method defined called update(), which is same one we discussed before. We are overriding the QWidget::update() method, and we will see soon why.

4.3.3.2. Implementation (.cpp)

The constructor implementation calls immediately its parent constructor QLabel(parent). It is very important to keep this call. Qt Widgets inside a GUI form a tree structure. One QMainWindow contains a central QWidget, which in turn will contains many QLabel, QSpinBox, maybe other QWidgets. But this tree structure is kept by the parent argument in constructors. Every new widget shall include this constructor signature as well.

The file also includes the three lines starting with #if WAF` that indicates WAF that it should run the MOC compiled against this file to produce the MetaObject version of it.

Below is the listing of the implementation file:

#include <slalib.h>
#include <QDebug>
#include <QString>
#include <QChar>

#include "include/CutWidgets/CutDmsLabel.hpp"

#if WAF
#include "include/CutWidgets/CutDmsLabel.moc"
#endif

CutDmsLabel::CutDmsLabel(QWidget *parent) :
  QLabel(parent)
{
}

CutDmsLabel::~CutDmsLabel()
{
}

void CutDmsLabel::update()
{
   int values[4];
   char sign;
   slaDr2af(this->m_precision, this->m_radians, &sign, values );
   //qDebug() << "Values: " << values[0] << " " << values[1] << " " << values[2] << " " << values[3];
   this->setText(QString("%1%2:%3:%4.%5")
         .arg(QChar(sign))
         .arg(values[0], 3, 10, QChar('0'))
         .arg(values[1], 2, 10, QChar('0'))
         .arg(values[2], 2, 10, QChar('0'))
         .arg(values[3], this->m_precision, 10, QChar('0')));
   QLabel::update();
}

The update() method does all the work in this case. Since all drawing instruction we need are already provided by the QLabel parent, we only need to prepare the correct string for presentation. In this case we take the this->m_radians, and use the slaDr2af() method from slalib_c to obtain the correct values of DMS.

Then, a QString is prepared with the +DDD:MM:SS.sss format, and we use the setText() slot to assign it to our widget. But we still need to trigger the QLabel::update() method, so that it can execute its rendering instructions.

In general, when inheriting from another widget, we want to conduct our operations, and after that, execute the ones from the parent classes.

4.3.3.3. Header file

There is one extra file called cut-widget-library/widgets/src/include/CutWidgets.h. The reader may notice that it does not follow the .hpp extension convention, and it is outside of the include hierarchy. These two choices are intentionals. This file will provide access to all include files when using the Qt Designer Plugin of this widget.

This will be explained later in the bindings sections, but its contents is simple: It includes every widget delivered by this WAF module:

#ifndef CUTWIDGETS_H
#define CUTWIDGETS_H

#include "CutWidgets/CutDmsLabel.hpp"

#endif // CUTWIDGETS_H

4.3.3.4. Compilation

At this point, the reader may go back to the root of the project, and execute:

waf configure build install

This will search for the dependencies expressed in the top-level wscript file, and execute the compilation instructions for our widget.

4.3.3.5. Thoughts on Widgets Properties, Signal and Slots

It is important that developers uses slots, signals and properties for the interface of the widget. The Qt Designer application included with Qt makes use of these to allow quick design of interfaces. For example:

A developer creates a widget, that plots the trend of datapoint, but it only updates the graphics if another condition is met. For this: * A slot criteriaMet(bool) can be created. This will serve as a trigger to conduct a update() on the widget. The developer may program the criteria whereever is needed, but the widget has to be notified somehow. Slots are very useful for these cases. * A signal trendUpdated() can be used to indicate any other interested part of the application (other widgets, but also other code) that the plot has been updated. * A property with a long int representing the timestamp of the update can be kept.

In the example above, the property, or to be exact, the slot of a property setRadians(double) is used to change the value in the widget. It is a common use for widgets to have properties that allow access to the value they present. In this sense, the precision Property is a bit different. Though also saved, it is used as configuration of the widget.

Widgets are configured through method, slots or properties. Qt’s documentation includes these characteristics in their API. Properties can be used in the Qt Designer to configure widgets, so any characteristics of your new widget than can be configured is a candidate for a property.