6.2. Creating a New Widget

The creation of a completely new widget is the most complex kind of widgets a developer may create.

Warning

We recommened the reader to be certain that existing widgets cannot satisfy display and interaction requirements.

Tip

An updated version of Qt’s Analog Clock Example is provided in the CUT examples. This example explains how to create a simple widget that draws its own presentation. The updates include: the use of real/float datatypes for coordinates; access to colors using palette; and a WAF project. This can be found at examples/analogclock.

In order to develop a new widget, it is important that the developer understands how palette, fonts, coordinates and size/layout works in Qt.

The new widget will most likely inherit from QWidget. If not, it should inherit from a class that at some point in its ancestors inherits from it.

We encourage that completely New Widgets are developed in C++ and bound to Python.

6.2.1. update() and paintEvent() methods

The QWidget class provides the update() method. Invoking this method indicates to Qt that the widget has changes that affect its presentation, and should queue a paintEvent() when it estimates necessary. Developer should never invoke paintEvent() directly.

When properties that influence presentation are set with new values, the update() method should be invoked.

When Qt requests a widget to update its presentation, it uses a QPaintEvent. The method that receives the QPaintEvent, and implements the drawing instructions of the widget is the paintEvent(). Qt uses properties to store the configuration values of the widget, and then paintEvent() method is used to prepare the pixmap for the presentation. During the course of the paintEvent() implementation properties can used to draw the pixmap according to the final user’s requests.

6.2.2. event(QEvent*) and specialized event methods

The event(QEvent*) method is declared in the QObject class but the QWidget has a reimplementation of it. This reimplemented method processes many interaction events common to widgets and provides developers with more handy methods to override and customize specific behaviour.

Developers are encourage to override these specialized methods. In case a developer overrides a common behaviour, you should still call the method from the QWidget implementation.

Note

Commonly overriden methods are moveEvent to get coordinates of the widget, and the mousePressEvent()/mouseReleaseEvent() to implement interaction.

A list of the events handled by QWidget and a basic description is included:

actionEvent

widget’s action have changed.

changeEvent

widget state has changed. Examples: font, enabled, palette.

closeEvent

widget received a close request from the window system. Default behaviour is to accept widget and close the window.

contextMenuEvent

commonly triggered by right clicking on a widget, or using the context menu key.

dragEnterEvent, dragLeaveEvent, dragMoveEvent

While a drag operation is in progress, these methods are called when entering the area of the widget, when leaving the area of the widget, or while moving above the widget.

dropEvent

the drag operation finished via drop.

enterEvent, leaveEvent, mouseMoveEvent

event send when the mouse cursor enters/leaves or moves on the area of the widget.

focusInEvent, focusOutEvent

widget has gained/lost keyboard focus.

hideEvent, showEvent

sent when a widget is set to hidden/shown.

keyPressEvent, keyReleaseEvent

event that indicates a key has been pressed or released.

mouseDoubleClickEvent, mousePressEvent, mouseReleaseEvent

events triggered when double clicking, when a mouse button is pressed, or released.

moveEvent

the widget has moved to a different position.

resizeEvent

the widget’s size has changed.

tabletEvent

events specific to digitizer tablets, such as WACOM products.

wheelEvent

mouse or touchpad/trackpad wheel events.

See also

Please refer to QWidget documentation for complete documentation on events.

6.2.3. Colors and Palette

All QWidgets have a reference to a QPalette object. The QWidget::palette() is propagated from the the QApplication and modified according to context. The developer is expected to use it to get the proper colors using ColorRole.

Qt prepares and makes available the palete member in all QWidget. This palette is defined by the system, and the Control GUI Guidelines indicates the ELT uses the system defined values to set its color scheme.

Warning

Do not deviate from the QWidget’s palette() values if possible.

When creating QPen and QBrush, please use QPalette::color() and the appropiate ColorRole to obtain colors rom the color scheme. A complete explanation with examples can be found at QPalette.

6.2.4. Fonts

Fonts are the main manner a GUI communicates information to a User. It is essential to be congruent with the font, since any deviation from the standard one will result in the user having to guess why the font changed.

Warning

Please use QWidget’s font() to get the current font to render text.

When rendering a widget, introducing text results most of the time having to calculate the height and width of the text. To do this, please use QFontMetrics class to calculate its dimensions. While QFont includes information on the point size of the Font and its family, QFontMetrics is used to do calculation with a QFont.

#include <QFontMetrics>
#include <QFont>

// ...

void AWidget::methodInAWidget(){
    QFontMetrics fm(this->font());
    int fh = fm.height();
    int fw = fm.horizontalAdvance("Text to Render");
}
from PySide2.QtGui import QFontMetrics
from PySide2.QtGui import QFont
from PySide2.QtWidgets import QWidget

// ...

class AWidget(QWidget):

    # ...

    def method_in_a_widget(self):
        fm = QFontMetrics(self.font());
        fh = fm.height();
        fw = fm.horizontalAdvance("Text to Render");

The height of a font never varies, but he width of a text does due to glyphs width, advance and kerning. Fonts include a kerning table, that indicates how much space should be between two pairs of letters (in case any modification is needed).

QFontMetrics horizontalAdvance() allows to quickly calculate the width of a string as rendered with the appropiate QFont.

6.2.5. Coordinates

When drawing with QPainter or QPixmap, the origin of the coordinate system is located at the top-left of the widget area, increasing downwards and eastward.

../_images/coordinatesystem-rect.png

More details on the coordinate system can be found here CoordinateSystem

Warning

Use real number alternatives. QRectF instead of QRect. QPointF instead of QPoint. This is required by Qt to be compatible with HiDPI scaling.

Tip

[Optional] Translate/Scale the coordinate system

As a first operation when drawing, the developer may introduce a translation, rotation and scaling instruction to effectevily transform the coordinate system.

A common transformation is for radial plotting:

int side = qMin(widgetWidth, widgetHeight);
painter.translate(widgetWidth / 2.0, widgetHeight / 2.0);
painter.scale(side / 100.0, side / 100.0);

This transformation puts the origin at the center of the widget, and scales the coordinates to [-100.0, 100.0]

6.2.6. save() and restore()

The QPainter object is a statefull rasterizer. It will remember translations, rotation and pen/brush configurations. The QPainter methods save() restore() operate on a stack of states that include pen/brush and transformations.

For every save() invocation a restore() call must complete the pair. This is really helpful when iterating over coordinate position to render diferent elements.

6.2.7. Widget Size and Ratio

Some widgets impose a size restrictions. For example the QDial has a circular representation that requires a 1:1 ratio between height and width. The QKeySequenceEdit widget is as tall as one line of input using the current font.

The developer of a new widget needs to consider if the widget’s size is to be constraint.

Even when the size is contraint, the surface available to draw may be bigger that what the widget needs. Expanding Policies and Default Alignment values could need to be changed in the constructor of the widget.

Also, when drawing, alignment may influence the point where the widget is drawn.

Most widgets include a minimum size. This is commonly the size where the contents would be unreadable and/or the interaction funcitonality is dimished.

6.2.8. Caching Pixmaps

While preparing the pixmap, QPainter object is used. It is possible to create new QPainter objects and draw temporary contents to these objects.

Since properties of the widget are used to draw these cached pixmaps, then when they change, the cache is invalidaded.

If cache is to be implemented, we recommed the introduction of a member invalidatePixmapCache of type bool. Properties setters should invalidate the Pixmap cache, as well as certain QWidget properties. These QWidget properties do not provide signals, but events can be used:

void AWidget::changeEvent(QEvent *event)
{
    if( event->type() == QEvent::FontChange ||
        event->type() == QEvent::PaletteChange ||
        event->type() == QEvent::StyleChange ||
        event->type() == QEvent::EnabledChange
    ){
        this->m_invalidatePixmapCache = true;
        update();
    }
    QWidget::changeEvent(event);
}

void AWidget::resizeEvent(QResizeEvent *event)
{
    this->m_invalidatePixmapCache = true;
    QWidget::resizeEvent(event);    }

To render into a QPixmap please follow this example:

void AWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    if( this->m_invalidatePixmapCache ){
        this->p_background = std::make_unique<QPixmap>(this->size());
        this->p_background->fill(Qt::transparent);
        painter->begin(p_background.get());
        // ... rendering instructions
        painter->end();
    }

    // ... normal rendering instructions
}

6.2.9. Plugin, Bindings and Example Application

All of this is covered in the Widget Library Tutorial.