4.3.3. Widgets WAF Module
Under 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.:
1from wtools.module import declare_qtcshlib
2
3# NOTE: WAF autoimports Core Gui and Widgets. But eventually this will be removed.
4# developers should always explicitly declared dependencies.
5
6# NOTE: The name of the target can be different from the name of the directory.
7declare_qtcshlib(
8 target='cutExamplesWidgets',
9 use='erfa'
10)
The declare_qtcshlib 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 waf 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>/widgetname.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:
widget-library/
├── widgets
│ ├── src
│ │ ├── include
│ │ │ ├── cut
│ │ │ │ └── examples
│ │ │ │ └── widgets
│ │ │ │ └── widgets
│ │ │ │ └── QeExampleDmsLabel.hpp
│ │ │ └── QeExamplesWidgets.h
│ │ └── QeExampleDmsLabel.cpp
│ └── wscript
└── wscript
Two files are needed to create a widget:
widget-library/widgets/src/include/cut/examples/widgets/widgets/QeExampleDmsLabel.hpp
and
widget-library/widgets/src/QeExampleDmsLabel.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 – in this case, a QLabel.
QeExampleDmsLabel.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 erfa.
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.
1#ifndef QEEXAMPLEDMSLABEL_HPP
2#define QEEXAMPLEDMSLABEL_HPP
3
4#include <QObject>
5#include <QString>
6#include <QVariant>
7#include <QWidget>
8#include <QLabel>
9
10/**
11 * @brief A text label that prints from a radian, it representation in Degree/Minute/Seconds notation.
12 * @class QeExampleDmsLabel CutExampleWidgets.h CutExampleWidgets.h
13 * @ingroup widgets_widgets
14 *
15 * QeExampleDmsLabel allows to represent in +-DDD:MM:SS.sss format an angle.
16 * This representation is used for Declination coordinates.
17 * It uses erfa for conversions.
18 */
19class QeExampleDmsLabel : public QLabel {
20 Q_OBJECT
21
22 /**
23 * @brief Value to represent in radians
24 * @accessors getRadians(), setRadians(double)
25 */
26 Q_PROPERTY(double radians MEMBER m_radians READ getRadians WRITE setRadians NOTIFY radiansChanged )
27
28 /**
29 * @brief Precision used in the subsecond section of the label.
30 * By default, millisecond precision is used.
31 * @accessors getPrecision(), setPrecision(int)
32 */
33 Q_PROPERTY(int precision MEMBER m_precision READ getPrecision WRITE setPrecision NOTIFY precisionChanged )
34
35public:
36 explicit QeExampleDmsLabel(QWidget *parent = Q_NULLPTR);
37 ~QeExampleDmsLabel();
38 double getRadians(){ return m_radians; };
39 int getPrecision(){ return m_precision; };
40
41signals:
42 /**
43 * @brief Indicates that the radians value has been changed.
44 */
45 void radiansChanged(double newValue);
46
47 /**
48 * @brief Indicates the precision configuration has been changed.
49 */
50 void precisionChanged(int newValue);
51
52public slots:
53 void setRadians(double newValue){this->m_radians = newValue; emit radiansChanged(newValue); this->update();};
54 void setPrecision(int newValue){this->m_precision = newValue; emit precisionChanged(newValue); this->update();};
55 void update();
56
57private:
58 double m_radians = 0.0;
59 int m_precision = 3;
60};
61
62
63#endif // QEEXAMPLEDMSLABEL_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 enqueues 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:
1#include <erfa.h>
2#include "include/cut/examples/widgets/widgets/QeExampleDmsLabel.hpp"
3#if WAF
4#include "include/cut/examples/widgets/widgets/QeExampleDmsLabel.moc"
5#endif
6
7QeExampleDmsLabel::QeExampleDmsLabel(QWidget *parent) :
8 QLabel(parent)
9{
10
11}
12
13QeExampleDmsLabel::~QeExampleDmsLabel()
14{
15}
16
17void QeExampleDmsLabel::update()
18{
19 int values[4];
20 char sign;
21 eraA2af(this->m_precision, this->m_radians, &sign, values );
22 //qDebug() << "Values: " << values[0] << " " << values[1] << " " << values[2] << " " << values[3];
23 this->setText(QString("%1%2:%3:%4.%5")
24 .arg(QChar(sign))
25 .arg(values[0], 3, 10, QChar('0'))
26 .arg(values[1], 2, 10, QChar('0'))
27 .arg(values[2], 2, 10, QChar('0'))
28 .arg(values[3], this->m_precision, 10, QChar('0')));
29 QLabel::update();
30}
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 eraA2af() method from erfa 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:
1#ifndef QEEXAMPLESWIDGET_H
2#define QEEXAMPLESWIDGET_H
3
4#include "cut/examples/widgets/widgets/QeExampleDmsLabel.hpp"
5
6#endif // QEEXAMPLESWIDGET_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.