Vector Graphics in Qt 6.8

Two-dimensional vector graphics has been quite prevalent in recent Qt release notes, and it is something we have plans to continue exploring in the releases to come. This blog takes a look at some of the options you have, as a Qt developer.

Reducing Binary Size of Qt Applications with Qt 6.8 - Part 1

Embrace the concept of faster and smaller binaries to elevate your application's  user experience — Qt Configure Options is the magic that makes this happen.

In Qt 6, we approach this challenge with a few enhancements under the hood, and additional information in the Qt documentation on how to use Qt build tools to achieve reductions in binary sizes and better runtime behavior.

The Qt framework consist of over fifty modules which you can easily select to be deployed with the application as needed. However, in some cases you may wish to optimize the binary size further. Read on for more intelligence on how you can do this.

Qt Contributors Summit 2024 - A Closer Look at QDoc and the Documentation Infrastructure Team

At the Qt Contributors Summit 2024, in a session together with Nicholas Bennett, I had the opportunity to present the latest developments in QDoc and give an overview of the Documentation Infrastructure team's work. Our team, though small, plays a vital role in maintaining and improving the tools and infrastructure behind Qt's comprehensive documentation. In this post, I’ll walk you through some key points from my part of the presentation, focusing on our recent developments and future plans for QDoc.

Qt for MCUs 2.5.4 LTS Released

Qt for MCUs 2.5.4 LTS (Long-Term Support) has been released and is available for download. This patch release provides bug fixes and other improvements while maintaining source compatibility with Qt for MCUs 2.5. It does not add any new functionality.

Implementing an Audio Mixer, Part 1

Motivation

When using Qt Multimedia to play audio files, it’s common to use QMediaPlayer, as it supports a larger variety of formats than QSound and QSoundEffect. Consider a Qt application with several audio sources; for example, different notification sounds that may play simultaneously. We want to avoid cutting notification sounds off when a new one is triggered, and we don’t want to construct a queue for notification sounds, as sounds will play at the incorrect time. We instead want these sounds to overlap and play simultaneously.

Ideally, an application with audio has one output stream to the system mixer. This way in the mixer control, different applications can be set to different volume levels. However, a QMediaPlayer instance can only play one audio source at a time, so each notification would have to construct a new QMediaPlayer. Each player in turn opens its own stream to the system.

The result is a huge number of streams to the system mixer being opened and closed all the time, as well as QMediaPlayers constantly being constructed and destructed.

To resolve this, the application needs a mixer of its own. It will open a single stream to the system and combine all the audio into the one stream.

Before we can implement this, we first need to understand how PCM audio works.

PCM

As defined by Wikipedia:

Pulse-code modulation (PCM) is a method used to digitally represent sampled analog signals. It is the standard form of digital audio in computers, compact discs, digital telephony and other digital audio applications. In a PCM stream, the amplitude of the analog signal is sampled at uniform intervals, and each sample is quantized to the nearest value within a range of digital steps.

Here you can see how points are sampled in uniform intervals and quantized to the closest number that can be represented.

Sampling and quantization of a signal (red) for 4-bit LPCM over a time domain at specific frequency.

Image Source: Wikipedia

Description from Wikipedia: Sampling and quantization of a signal (red) for 4-bit LPCM over a time domain at specific frequency.

Think of a PCM stream as a humongous array of bytes. More specifically, it’s an array of samples, which are either integer or float values and a certain number of bytes in size. The samples are these discrete amplitude values from a waveform, organized contiguously. Think of the each element as being a y-value of a point along the wave, with the index representing an offset from x=0 at a uniform time interval.

Here is a graph of discretely sampled points along a sinusoidal waveform similar to the one above:

A discrete waveform

Image Source: Wikimedia Commons

Description from Wikimedia Commons: Image of a discrete time sinusoid

Let’s say we have an audio waveform that is a simple sine wave, like the above examples. Each point taken at discrete intervals along the curve here is a sample, and together they approximate a continuous waveform. The distance between the samples along the x-axis is a time delta; this is the sample period. The sample rate is the inverse of this, the number of samples that are played in one second. The typical standard sample rate for audio on CDs is 44100 Hz – we can’t really hear that this data is discrete (plus, the resultant sound wave from air movement is in fact a continuous waveform).

We also have to consider the y-axis here, which represents the amplitude of the waveform at each sampled point. In the image above, amplitude A is normalized such that A\in[−1,1]. In digital audio, there are a few different ways to represent amplitude. We can’t represent all real numbers on a computer, so the representation of the range of values varies in precision.

For example, let’s say we have two different representations of the wave above: 8-bit signed integer and 16-bit signed integer. The normalized value 1 from the image above maps to (2^{8}\div{2})−1=127 with 8-bit representation and (2^{16}\div2)−1=32767 with 16-bit. Therefore, with 16-bit representation, we have 128 times as many possible values to represent the same range; it is more precise, but the required size to store each 16-bit sample is double that of 8-bit samples.

We call the chosen representation, and thus the size of each sample, the bitdepth. Some common bitdepths are 16-bit int, 24-bit int, and 32-bit float, but there are many others in existence.

Let’s consider a huge stream of 16-bit samples and a sample rate of 44100 Hz. We write samples to the audio device periodically with a fixed-size buffer; let’s say it is 4096 bytes. The device will play each sample in the buffer at the aforementioned rate. Since each sample is a contiguous 2-byte short, we can fit 2048 samples into the buffer at once. We need to write 44100 samples in one second, so the whole buffer will be written around 21.5 times per second.

What if we have two different waveforms though, and what if one starts halfway through the other one? How do we mix them so that this buffer contains the data from both sources?

Waveform Superimposition

In the study of waves, you can superimpose two waves by adding them together. Let’s say we have two different discrete wave approximations, each represented by 20 signed 8-bit integer values. To superimpose them, for each index, add the values at that index. Some of these sums will exceed the limits of 8-bit representation, so we clamp them at the end to avoid signed integer overflow. This is known as hard clipping and is the phenomenon responsible for digital overdrive distortion.

x Wave 1 (y_1) Wave 2 (y_2) Sum (y_1+y_2) Clamped Sum
0 +60 −100 −40 −40
1 −120 +80 −40 −40
2 +40 +70 +110 +110
3 −110 −100 −210 −128
4 +50 −110 −60 −60
5 −100 +60 −40 −40
6 +70 +50 +120 +120
7 −120 −120 −240 −128
8 +80 −100 −20 −20
9 −80 +40 −40 −40
10 +90 +80 +170 +127
11 −100 −90 −190 −128
12 +60 −120 −60 −60
13 −120 +70 −50 −50
14 +80 −120 −40 −40
15 −110 +80 −30 −30
16 +90 −100 −10 −10
17 −110 +90 −20 −20
18 +100 −110 −10 −10
19 −120 −120 −240 −128

Now let’s implement this in C++. We’ll start small, and just combine two samples.

Note: we will use qint types here, but qint16 will be the same as int16_t and short on most systems, and similarly qint32 will correspond to int32_t and int.

qint16 combineSamples(qint32 samp1, qint32 samp2)
{
    const auto sum = samp1 + samp2;

    if (std::numeric_limits<qint16>::max() < sum)
        return std::numeric_limits<qint16>::max();

    if (std::numeric_limits<qint16>::min() > sum)
        return std::numeric_limits<qint16>::min();

    return sum;
}

This is quite a simple implementation. We use a function combineSamples and pass in two 16-bit values, but they will be converted to 32-bit as arguments and summed. This sum is clamped to the limits of 16-bit integer representation using std::numeric_limits in the <limits> header of the standard library. We then return the sum, at which point it is re-converted to a 16-bit value.

Combining Samples for an Arbitrary Number of Audio Streams

Now consider an arbitrary number of audio streams n. For each sample position, we must sum the samples of all n streams.

Let’s assume we have some sort of audio stream type (we’ll implement it later), and a list called mStreams containing pointers to instances of this stream type. We need to implement a function that loops through mStreams and makes calls to our combineSamples function, accumulating a sum into a new buffer.

Assume each stream in mStreams has a member function read(char *, qint64). We can copy one sample into a char * by passing it to read, along with a qint64 representing the size of a sample (bitdepth). Remember that our bitdepth is 16-bit integer, so this size is just sizeof(qint16).

Using read on all the streams in mStreams and calling combineSamples to accumulate a sum might look something like this:

qint16 accumulatedSum = 0;

for (auto *stream : mStreams)
{
    // call stream->read(char *, qint64)
    // to read a sample from the stream into streamSample
    qint16 streamSample;
    stream->read(reinterpret_cast<char *>(&streamSample), sizeof(qint16)));
    
    // accumulate
    accumulatedSum = combineSamples(sample, accumulatedSum);
}

The first pass will add samples from the first stream to zero, effectively copying it to accumulatedSum. When we move to another stream, the samples from the second stream will be added to those copied values from the first stream. This continues, so the call to combineSamples for a third stream would combine the third stream’s sample with the sum of the first two. We continue to add directly into the buffer until we have combined all the streams.

Combining All Samples for a Buffer

Now let’s use this concept to add all the samples for a buffer. We’ll make a function that takes a buffer char *data and its size qint64 maxSize. We’ll write our accumulated samples into this buffer, reading all samples from the streams and adding them using the method above.

The function signature looks like this:

void readData(char *data, qint64 maxSize);

Let’s achieve more efficiency by using a constexpr variable for the bitdepth:

constexpr qint16 bitDepth = sizeof(qint16);

There’s no reason to call sizeof multiple times, especially considering sizeof(qint16) can be evaluated as a literal at compile-time.

With the size of each sample and the size of the buffer, we can get the total number of samples to write:

const qint16 numSamples = maxSize / bitDepth;

For each stream in mStreams we need to read each sample up to numSamples. As the sample index increments, a pointer to the buffer data needs to also be incremented, so we can write our results at the correct location in the buffer.

That looks like this:

void readData(char *data, qint64 maxSize)
{
    // start with 0 in the buffer
    memset(data, 0, maxSize);

    constexpr qint16 bitDepth = sizeof(qint16);
    const qint16 numSamples = maxSize / bitDepth;

    for (auto *stream : mStreams)
    {
        // this pointer will be incremented across the buffer
        auto *cursor = reinterpret_cast<qint16 *>(data);
        qint16 sample;

        for (int i = 0; i < numSamples; ++i, ++cursor)
            if (stream->read(reinterpret_cast<char *>(&sample), bitDepth))
                *cursor = combineSamples(sample, *cursor);
    }
}

The idea here is that we can start playing new audio sources by adding new streams to mStreams. If we add a second stream halfway through a first stream playing, the next buffer for the first stream will be combined with the first buffer of this new stream. When we’re done playing a stream, we just drop it from the list.

 

Next Steps

In Part 2, we’ll use Qt Multimedia to fully implement our mixer, connect to our audio device, and test it on some audio files.

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post Implementing an Audio Mixer, Part 1 appeared first on KDAB.

Release 4.2.0: New Felgo Hot Reload, OAuth 2.0 Plugin, Universal Links, Android 14 & Qt Creator 14

The Felgo 4.2.0 update adds many new features and improvements for Felgo QML Hot Reload. These include support for singletons, better handling of JavaScript and bindings changes, and improved debugging and error-handling capabilities. The release also adds a new RFC-conform OAuth 2.0 plugin, universal app link handling for your app, and updates to Felgo Plugins, Android 14 Support, and Qt Creator 14 for the latest Google Play Store requirements. Read on to learn more about the release.

Qt and Trivial Relocation (Part 5)

In the previous posts of this series (if you’ve missed them: parts 1, 2, 3, and 4), we have learned about relocation and trivial relocation.

We have explored what relocation means, what trivial relocation means, and how it can be used to optimize the implementation of certain data structures, such as the reallocation of a vector-like container (std::vector, QVector and so on).

Furthermore, we have explored how trivial relocation is connected to move assignments and how some types may be trivially relocatable for move construction but not for assignments. This property can be used to further optimize other operations, such as swaps, erasure, and swap-based algorithms such as std::sort or std::rotate.

In this blog post, we’ll have a look at trivial relocation from a “Standard C++” point of view.

Is trivial relocation allowed in Standard C++?

That’s probably a question we should have asked as soon as we started this journey. Of course, the answer is no, it is not allowed!

Remember how trivial relocation works: we use memcpy a source object(‘s representation) into some storage, and claim that operation realizes the equivalent of move-constructing the source into that storage, plus destroying the source.

The problem is that one can’t just put data into some storage and pretend that an object exists in there. This is only allowed for a specific set of types, such as trivially copyable types. (Note that if a type is trivially copyable, then Qt automatically considers it trivially relocatable.)

However, as we have discussed, many interesting types (QString, std::vector, std::unique_ptr, …) are not trivially copyable, but they would still benefit from trivial relocatability.

Qt simply ignores the Standard wording and just uses trivial relocatability because it makes our code faster, reduces template bloat, reduces compilation times, and so on. I call this “Undefined Behavior That Works In Practice”: yes, the operation is illegal, but so are many others. Qt is in good company here; many other popular libraries employ the same “illegal” optimization — to name a few: Folly, EASTL, BSL, and possibly others; a survey is available here.

Towards standardization

A few years ago, P1144 (“std::is_trivially_relocatable”) emerged. This proposal (which, by the way, has reached its eleventh revision at the time of this writing) was inspired by the work in many existing libraries (including Qt) and introduced the necessary language and library facilities in order to give well-defined semantics to relocation and trivial relocation. Despite its many iterations, it never made it over the “finish line”, voted into C++.

Some time after P1144’s initial revision, P2786 (“Trivial Relocatability For C++26”) was proposed. This was an alternative design, w.r.t. P1144, with different relocation semantics and a different set of enablers.

P1144 and P2786 “competed” for a little while; then, during the ISO C++ meeting in Tokyo (March 2024), the Evolution Working Group voted to adopt P2786 for C++26.

That’s great news, right?

Well, not really. After some analysis, it turned out that P2786’s design is limiting and not user-friendly, to the point that there have been serious concerns that existing libraries may not make use of it at all. In particular, P2786’s relocation semantics do not match Qt’s. With my Qt hat on, it soon became clear that we could not have used P2786 in Qt to replace our own implementation of trivial relocation. This fact raised some serious concerns.

For this reason, I co-authored a “petition paper” (P3236), together with many other library authors, asking EWG to reconsider its decision of going ahead with P2786.

I also have analyzed in detail the problems that I have with P2786’s design in a separate paper (P3233). I don’t want to go through the complete list of issues in a blog post — please read the paper and send feedback. 🙂

In June 2024, during the ISO C++ meeting in St. Louis, I presented P3233 to EWG. If you’re interested and/or want a summary of the issues I raised, check out the slides that I used during my presentation.

P3278R0 (“Analysis of interaction between relocation, assignment, and swap”) was also presented, making many of the remarks that I’ve also made in my paper; I consider a good sign that different authors independently reached the same conclusions.

Eventually, EWG voted to take P2786 back, given the issues raised. I consider it a victory in the face of the danger of standardizing something that does not match the current practices. Still, I understand the frustration at pouring a tremendous amount of work into getting a feature into C++, only to get asked to go back to the drawing board.

So what now?

I hope I can find the time in the next few months to work more on trivial relocation, and present more papers at the next ISO C++ meeting in Wrocław. I think the goal to have trivial relocatability in C++26 is doable; we “just” have to iron out the details.

Regarding the two competing proposals (P1144 versus P2786), in P3236 I actually make an argument that they should be “merged”: each one has some design characteristics that are missing in the other.

Therefore, the trivial relocation story is anything but over. Stay tuned for more updates, hopefully in not the too distant future. 🙂

In the meanwhile, thank you for reading so far.

Overview about all installments:

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post Qt and Trivial Relocation (Part 5) appeared first on KDAB.

Formatting Selected Text in QML

Motivation

Let’s say we’re working on a QML project that involves a TextEdit.

There’s some text in it:

here is some text

We want to select part of this text and hit ctrl+B to make it bold:

here is some text

In Qt Widgets, this is trivial, but not so much in QML – we can get font.bold of the entire TextEdit, but not of just the text in the selection. We have to implement formattable selections manually.

To do this, there are two approaches we’ll look at:

  1. The first is to hack it together by getting the formatted text from the selection and editing this. Rather than setting properties of selected text, this solution actually inserts or removes formatting symbols from the underlying rich text source.
  2. The other way to do this is to create a QML object that is implemented in C++ and exposed to TextEdit as a property. This way we can make use of QTextDocument and QTextCursor to actually set text properties within the selection area. This more closely follows the patterns expected in Qt.

In Qt 6.7, the TextEdit QML element does have a cursorSelection property that works in this way, and by dissecting its implementation, we can write a pseudo-backport for other Qt versions.

Before we do this, let’s take a look at the hacky QML/JS solution.

Hacky Approach

We start by focusing on just making ctrl+B bold shortcuts work:

TextEdit {
    id: txtEdit

    anchors.fill: parent
    selectByMouse: true
    textFormat: TextEdit.RichText
}

Shortcut {
    sequence: StandardKey.Bold
    onActivated: {
        if (txtEdit.selectedText.length > 0)
        {
            const start = txtEdit.selectionStart
            const end = txtEdit.selectionEnd
            let sel = txtEdit.getFormattedText(start, end)
                             .split("<!--StartFragment-->")[1]
                             .split("<!--EndFragment-->")[0]
            txtEdit.remove(start, end)
            if (sel.includes("font-weight:600;"))
                sel = sel.replace("font-weight:600;", "")
            else
                sel = "<b>" + sel + "</b>"
            txtEdit.insert(txtEdit.cursorPosition, sel)
            txtEdit.select(start, end)
        }
    }
}

Notice that we actually remove and replace the selected text, and reselect the insertion manually.

We can set up similar shortcuts for italics and underline trivially, but what if we want to set font properties of only the text in the selected area?

To keep things simple, let’s see what happens if we want to set just the font family and size:

FontDialog {
    id: fontDlg
}

Shortcut {
    id: fontShortcut

    property string sel: ""
    property int start: 0
    property int end: 0

    sequence: StandardKey.Find
    onActivated: {
        if (txtEdit.selectedText.length > 0)
        {
            start = txtEdit.selectionStart
            end = txtEdit.selectionEnd
            sel = txtEdit.getFormattedText(start, end)
                         .split("<!--StartFragment-->")[1]
                         .split("<!--EndFragment-->")[0]
            fontDlg.open()
        }
    }
}

Connections {
    target: fontDlg

    function onAccepted() {
        txtEdit.remove(fontShortcut.start, fontShortcut.end)
        if (fontShortcut.sel.includes("font-family:")) {
            let fontToReplace = fontShortcut.sel.split("font-family:'")[1].split("';")[0]
            fontShortcut.sel = fontShortcut.sel.replace(fontToReplace, fontDlg.font.family)
        } else {
            fontShortcut.sel = "<span style=\"font-family: '"
                             + fontDlg.font.family + "'; font-size:"
                             + (fontDlg.font.pixelSize ? fontDlg.font.pixelSize
                                                       : fontDlg.font.pointSize)
                             + "\">" + fontShortcut.sel + "</span>"
        }
        txtEdit.insert(txtEdit.cursorPosition, fontShortcut.sel)
        txtEdit.select(fontShortcut.start, fontShortcut.end)
    }
}

If we start messing with other font style properties like italic, bold, spacing, etc., we will end up with almost unreadably nasty string manipulation here.

This solution is overall hacky, as we replace HTML-formatted text from a snipped out section. It would be more Qt-idiomatic to retrieve QFont info from a selection and set the properties without editing raw rich text. Furthermore, it’s better to do as much logic as possible in C++ rather than with JavaScript in QML.

Implementation of cursorSelection in Qt 6.7 QML

Let’s take a look at the cursorSelection property of QtQuick TextEdit in Qt 6.7.

By looking at its property declaration in qquicktextedit_p.h, the type of cursorSelection is QQuickTextSelection.

This type is very basic. It has four read/write properties.

Here is the header qquicktextselection_p.h:

class Q_QUICK_EXPORT QQuickTextSelection : public QObject
{
    Q_OBJECT

    Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged FINAL)
    Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged FINAL)
    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL)
    Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged FINAL)

    QML_ANONYMOUS
    QML_ADDED_IN_VERSION(6, 7)

public:
    explicit QQuickTextSelection(QObject *parent = nullptr);

    QString text() const;
    void setText(const QString &text);

    QFont font() const;
    void setFont(const QFont &font);

    QColor color() const;
    void setColor(QColor color);

    Qt::Alignment alignment() const;
    void setAlignment(Qt::Alignment align);

Q_SIGNALS:
    void textChanged();
    void fontChanged();
    void colorChanged();
    void alignmentChanged();

private:
    QTextCursor cursor() const;
    void updateFromCharFormat(const QTextCharFormat &fmt);
    void updateFromBlockFormat();

private:
    QTextCursor m_cursor;
    QTextCharFormat m_charFormat;
    QTextBlockFormat m_blockFormat;
    QQuickTextDocument *m_doc = nullptr;
    QQuickTextControl *m_control = nullptr;
};

Notice we’ve got these private data members:

QTextCursor m_cursor;
QTextCharFormat m_charFormat;
QTextBlockFormat m_blockFormat;
QQuickTextDocument *m_doc = nullptr;
QQuickTextControl *m_control = nullptr;

The m_doc and m_control are retrieved from the TextEdit which parents the selection object. The object is always constructed by a QQuickTextEdit, so in the constructor, the parent is cast to one using qmlobject_cast. Then we set these two fields.

QQuickTextSelection::QQuickTextSelection(QObject *parent)
    : QObject(parent)
{
    // When QQuickTextEdit creates its cursorSelection, it passes itself as the parent
    if (auto *textEdit = qmlobject_cast<QQuickTextEdit *>(parent)) {
        m_doc = textEdit->textDocument();
        m_control = QQuickTextEditPrivate::get(textEdit)->control;
        // ...
        // ...

Now what are m_charFormat and m_blockFormat?

Text documents are composed of a list of text blocks, which can be paragraphs, lists, tables, images, etc. Thus, a block format represents an individual block’s alignment formatting. Char format contains formatting information at the character level, like font family, weight, style, size, color, and so forth.

To initialize these, we need to get the cursor from the text control.

QTextCursor QQuickTextSelection::cursor() const
{
    if (m_control)
        return m_control->textCursor();
    return m_cursor;
}

The cursor will give us a char format and a block format, which we use to get the font / color / alignment at the cursor’s location.

QFont QQuickTextSelection::font() const
{
    return cursor().charFormat().font();
}

// ...

QColor QQuickTextSelection::color() const
{
    return cursor().charFormat().foreground().color();
}

// ...

Qt::Alignment QQuickTextSelection::alignment() const
{
    return cursor().blockFormat().alignment();
}

currentCharFormatChanged is emitted by QQuickTextControl when the cursor moves or the document’s contents change. If this format is indeed different from the fields of the selection object, we must update them and emit the selection’s signals, just as we would in setters. Since we keep track of block alignment too, we have to do the same when the cursor moves and block format is different.

QQuickTextSelection::QQuickTextSelection(QObject *parent)
    : QObject(parent)
{
    // When QQuickTextEdit creates its cursorSelection, it passes itself as the parent
    if (auto *textEdit = qmlobject_cast<QQuickTextEdit *>(parent)) {
        m_doc = textEdit->textDocument();
        m_control = QQuickTextEditPrivate::get(textEdit)->control;
        connect(m_control, &QQuickTextControl::currentCharFormatChanged,
                this, &QQuickTextSelection::updateFromCharFormat);
        connect(m_control, &QQuickTextControl::cursorPositionChanged,
                this, &QQuickTextSelection::updateFromBlockFormat);
    }
}

// ...
// ...
// ...

inline void QQuickTextSelection::updateFromCharFormat(const QTextCharFormat &fmt)
{
    if (fmt.font() != m_charFormat.font())
        emit fontChanged();
    if (fmt.foreground().color() != m_charFormat.foreground().color())
        emit colorChanged();

    m_charFormat = fmt;
}

inline void QQuickTextSelection::updateFromBlockFormat()
{
    QTextBlockFormat fmt = cursor().blockFormat();

    if (fmt.alignment() != m_blockFormat.alignment())
        emit alignmentChanged();

    m_blockFormat = fmt;
}

Here are the setters for the properties, which use the cursor to access and mutate the character or block properties at its position.

void QQuickTextSelection::setText(const QString &text)
{
    auto cur = cursor();
    if (cur.selectedText() == text)
        return;

    cur.insertText(text);
    emit textChanged();
}

// ...

void QQuickTextSelection::setFont(const QFont &font)
{
    auto cur = cursor();
    if (cur.selection().isEmpty())
        cur.select(QTextCursor::WordUnderCursor);

    if (font == cur.charFormat().font())
        return;

    QTextCharFormat fmt;
    fmt.setFont(font);
    cur.mergeCharFormat(fmt);
    emit fontChanged();
}

// ...

void QQuickTextSelection::setColor(QColor color)
{
    auto cur = cursor();
    if (cur.selection().isEmpty())
        cur.select(QTextCursor::WordUnderCursor);

    if (color == cur.charFormat().foreground().color())
        return;

    QTextCharFormat fmt;
    fmt.setForeground(color);
    cur.mergeCharFormat(fmt);
    emit colorChanged();
}

// ...

void QQuickTextSelection::setAlignment(Qt::Alignment align)
{
    if (align == alignment())
        return;

    QTextBlockFormat format;
    format.setAlignment(align);
    cursor().mergeBlockFormat(format);
    emit alignmentChanged();
}

Now, we want to do something like this in our code. The issue is that this implementation resides in the Qt source code itself, and cursorSelection is a property of QQuickTextEdit. If we want to do something like this without changing Qt source code, we have to use attached properties.

Implementing an Attached Property

Using CursorSelection as an attached property for a TextEdit in QML might look something like this:

Item {
    // ...
    // ...
    // ...
    
    Shortcut {
        // ctrl+B to toggle bold / not bold for selection
        sequence: StandardKey.Bold
        onActivated: {
            txtEdit.CursorSelection.font = Qt.font({
                bold: txtEdit.CursorSelection.font.bold !== true
            })
        }
    }

    TextEdit {
        id: txtEdit

        // ...
        
        CursorSelection.font {
            bold: false
            italic: false
            underline: false
        }
    }
}

To create our own attached property, we have to create two classes: CursorSelectionAttached and CursorSelection.

CursorSelectionAttached will contain the implementation of the selection, while CursorSelection serves as the attaching type, using the qmlAttachedProperties() method to expose the signals and properties of an instance of CursorSelectionAttached to the parent to which it is attached.

CursorSelection also needs the QML_ATTACHED() macro in its header declaration, and we must specify that it has an attached property with the macro QML_DECLARE_TYPEINFO() outside the class scope.

Thus, CursorSelection will just look like this:

// CursorSelection.h

class CursorSelection : public QObject
{
    Q_OBJECT
    QML_ATTACHED(CursorSelectionAttached)
    QML_ELEMENT

public:
    static CursorSelectionAttached *qmlAttachedProperties(QObject *object);
};

QML_DECLARE_TYPEINFO(CursorSelection, QML_HAS_ATTACHED_PROPERTIES)

Where the entire implementation is just this function definition:

// CursorSelection.cpp

CursorSelectionAttached *CursorSelection::qmlAttachedProperties(QObject *object)
{
    if (auto *textEdit = qobject_cast<QQuickTextEdit *>(object))
        return new CursorSelectionAttached(textEdit);
    return nullptr;
}

Notice that we perform the qobject_cast here and forward the result as the parent of the attached object. This way we only construct an attached object if we can cast the parent object to a TextEdit.

Now, let’s see how CursorSelectionAttached should be implemented. We begin with the constructor:

// we know that parent will be a QQuickTextEdit *
CursorSelectionAttached::CursorSelectionAttached(QQuickTextEdit *parent) noexcept
    : QObject(parent)
    , mEdit(parent)            // this is the TextEdit we are attached to 
{
    // make sure the QTextDocument exists
    const auto *const quickDoc = mEdit->textDocument(); // QQuickTextDocument *
    auto *doc = quickDoc->textDocument();               // QTextDocument *
    Q_ASSERT(doc != nullptr);

    // retrieve QTextCursor from the QTextDocument
    mCursor = QTextCursor(doc);

    // When deselecting, the cursor position and anchor are
    // set to the TextEdit's cursor position
    connect(mEdit, &QQuickTextEdit::selectedTextChanged,
            this, &CursorSelectionAttached::moveAnchorIfDeselected);
    
    connect(mEdit, &QQuickTextEdit::cursorPositionChanged,
            this, &CursorSelectionAttached::updatePosition);
    
    // if we set a format with no selection, we keep it in an optional
    // then when new text is added, it will have this formatting
    // for example, with no selection we press ctrl+B and then start
    // typing. we expect the text to be bold.
    connect(mEdit->textDocument()->textDocument(),
            &QTextDocument::contentsChange,
            this,
            &CursorSelectionAttached::applyFormatToNewTextIfNeeded);
}

Note that we connect to these three slots:

  • moveAnchorIfDeselected
  • updatePosition
  • applyFormatToNewTextIfNeeded

Let’s investigate the purpose of these.

moveAnchorIfDeselected is invoked when the TextEdit’s selected text changes. A QTextCursor has an anchor, which controls selection area. If text is being selected, the anchor is fixed in place where the selection is started, and the cursor position moves independently of the anchor. The selection area is located between the two positions. When a cursor moves without selecting anything, the anchor is located at and moves along with the cursor position.

Thus, when a cursor’s position is moved, we need to know if the anchor should be moved with it.

Since we invoke moveAnchorIfDeselected when the selected text changes, we know that if the selection is now empty, this means there was a selection that has been deselected. Thus, the cursor and anchor should be equal to one another.

void CursorSelectionAttached::moveAnchorIfDeselected()
{
    if (mEdit->selectedText().isEmpty())
        mCursor.setPosition(mEdit->cursorPosition(), QTextCursor::MoveAnchor);
}

updatePosition is invoked when the TextEdit’s cursor position changes. Depending on the TextEdit’s selection start and end positions, there are a few ways the cursor could be updated.

If there is no selected area in the TextEdit, the cursor and anchor should move together. If a selection’s start and end position both change, we must move the cursor twice: once to the start position, with the anchor moving, and once to the end position, with the anchor fixed in place. If the selection area is being resized, for example by dragging or using Shift+ArrowKeys, the cursor should move with the anchor fixed in place.

void CursorSelectionAttached::updatePosition()
{
    // if there's no selection, just move the cursor & anchor
    if (mEdit->selectionEnd() == mEdit->selectionStart())
    {
        mCursor.setPosition(mEdit->cursorPosition(), QTextCursor::MoveAnchor);
    }

    // if both the start and end need to be updated:
    // move cursor and anchor to selection start, and
    // move cursor to selection end while keeping anchor at start
    //
    // we have to make sure the anchor is moved correctly so the
    // whole selection matches up -- otherwise cursor selection 
    // start or end might be in the middle of the actual
    // selection, wherever the anchor is
    else if (mEdit->selectionStart() != mCursor.selectionStart() &&
             mEdit->selectionEnd() != mCursor.selectionEnd())
    {
        mCursor.setPosition(mEdit->selectionStart(), QTextCursor::MoveAnchor);
        mCursor.setPosition(mEdit->selectionEnd(), QTextCursor::KeepAnchor);
    }

    // these two cases are for selection dragging, only start or
    // end will move, so anchor stays in place
    else if (mEdit->selectionStart() != mCursor.selectionStart())
    {
        mCursor.setPosition(mEdit->selectionStart(), QTextCursor::KeepAnchor);
    }
    else if (mEdit->selectionEnd() != mCursor.selectionEnd())
    {
        mCursor.setPosition(mEdit->selectionEnd(), QTextCursor::KeepAnchor);
    }
}

applyFormatToNewTextIfNeeded is invoked when the contents of the text document change. This is because font properties might be set without an active selection. In this case, the expected behavior is for the characters added afterwards will have these properties.

For example, if the font family is changed with no selection, and we start typing, we expect our text to be in this new font. To do this, we need an optional in which we can save a format to apply to new text if needed, or otherwise contains nullopt. We will call it mOptFormat. It can be set in property setters, which you will see later. For now, we just make sure to use it when the text document content changes and there exists a value in the optional.

void CursorSelectionAttached::applyFormatToNewTextIfNeeded(int from, int charsRemoved, int charsAdded)
{
    if (charsAdded && mOptFormat)
    {
        mCursor.setPosition(mCursor.position() - 1, QTextCursor::KeepAnchor);
        mCursor.mergeCharFormat(mOptFormat.value());
        mOptFormat.reset();
    }
}

Now, let’s take a look at the properties to expose to QML, and how they can be retrieved and set using the cursor. Like the QQuickTextSelection implementation, we will have properties text and font. We can implement the others as well, but for the sake of brevity, we will just focus on these two.

Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged FINAL)
Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged FINAL)

We’ll need to declare and define these getters and setters, and declare the signals:

Getters:

[[nodiscard]] QString text() const;
[[nodiscard]] QFont font() const;

Setters:

void setText(const QString &text);
void setFont(const QFont &font);

Signals:

void textChanged();
void fontChanged();

The getter and setter implementations will look very similar to the previous implementations shown for QQuickTextSelection, with some minor differences.

Getter implementations:

QString CursorSelectionAttached::text() const
{
    return mCursor.selectedText();
}

QFont CursorSelectionAttached::font() const
{
    // simply get the font at the cursor position using charFormat
    auto ret = mCursor.charFormat().font();

    // if the cursor is at the start of a selection, we need to take the font
    // at the position right in front of it. otherwise, the font will refer to the 
    // character at the position right before the selection begins
    if (mCursor.hasSelection() && mCursor.position() == mCursor.selectionStart())
    {
        auto cur = mCursor;
        cur.setPosition(cur.position() + 1);
        ret = cur.charFormat().font();
    }
    return ret;
}

Setter implementations:

void CursorSelectionAttached::setText(const QString &text)
{
    if (mCursor.selectedText() == text)
        return;

    mCursor.insertText(text);
    emit textChanged();
}

void CursorSelectionAttached::setFont(const QFont &font)
{
    if (font == mCursor.charFormat().font())
        return;

    QTextCharFormat fmt = mCursor.charFormat();
    fmt.setFont(font, QTextCharFormat::FontPropertiesSpecifiedOnly);

    // when no selection, formatting must be set on the next insertion
    if (mCursor.selection().isEmpty())
        mOptFormat = fmt;
    else
        mCursor.mergeCharFormat(fmt);

    emit fontChanged();
}

The only thing that needs to be done now is override the destructor, which can just be set to default:

~CursorSelectionAttached() override = default;

Now we have all the implementation we need to use the attached property. If we put the two classes in one header file, it will look like this:

#pragma once

#include <QObject>
#include <QTextCursor>
#include <QtQml>
#include <optional>

class QQuickTextEdit;

class CursorSelectionAttached : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged FINAL)
    Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged FINAL)
    QML_ANONYMOUS

public:
    explicit CursorSelectionAttached(QQuickTextEdit *parent) noexcept;
    ~CursorSelectionAttached() override = default;
    [[nodiscard]] QString text() const;
    [[nodiscard]] QFont font() const;
    void setText(const QString &text);
    void setFont(const QFont &font);

signals:
    void textChanged();
    void fontChanged();

private slots:
    void moveAnchorIfDeselected();
    void updatePosition();
    void applyFormatToNewTextIfNeeded(int from, int charsRemoved, int charsAdded);

private:
    QTextCursor mCursor;
    QQuickTextEdit *mEdit;
    std::optional<QTextCharFormat> mOptFormat;
};

class CursorSelection : public QObject
{
    Q_OBJECT
    QML_ATTACHED(CursorSelectionAttached)
    QML_ELEMENT

public:
    static CursorSelectionAttached *qmlAttachedProperties(QObject *object);
};

QML_DECLARE_TYPEINFO(CursorSelection, QML_HAS_ATTACHED_PROPERTIES)

With this header, an implementation file containing the definitions, and a call to qmlRegisterUncreatableType<CursorSelection> in your main.cpp, the attached property can be used in QML.

Final Remarks

Though this is not a perfect backport, this code allows us to set font properties for selected text in QML in a nearly identical way to its implementation in Qt 6.7. This is especially useful to implement any kind of richtext editing in a QML application, where this functionality is severely lacking in any Qt version prior to 6.7. Hopefully this is a helpful guide to backporting features, implementing attached properties, and doing more sane text editing in QML apps. 🙂

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post Formatting Selected Text in QML appeared first on KDAB.

Behind the Scenes of Embedded Updates

An over-the-air (OTA) update capability is an increasingly critical part of any embedded product to close cybersecurity vulnerabilities, allow just-in-time product rollouts, stomp out bugs, and deliver new features. We’ve talked about some of the key structural elements that go into an embedded OTA architecture before. But what about the back end? Let’s address some of those considerations now.

The challenges of embedded connectivity

The ideal of a constant Internet connection is more aspiration than reality for many embedded devices. Sporadic connections, costly cellular or roaming charges, and limited bandwidth are common hurdles. These conditions necessitate smart management of update payloads and robust retry strategies that can withstand interruptions, resuming where they left off without getting locked in a continually restarting update cycle.

There are other ways to manage spotty connections. Consider using less frequent update schedules or empower users to initiate updates. These strategies however have trade-offs, including the potential to miss critical security patches. One way to strike a balance is to implement updates as either optional or mandatory, or flag updates as mandatory only when critical, allowing users to pace out updates when embedded connectivity isn’t reliable.

To USB or not to USB

When network access is very unreliable, or even just plain absent, then USB updates are indispensable for updating device software. These updates can also serve as effective emergency measures or for in-field support. While the process of downloading and preparing a USB update can often be beyond a normal user’s capability, it’s a critical fallback and useful tool for technical personnel.

OTA servers: SaaS or self-hosted

Deciding between software as a service (SaaS) and self-hosted options for your OTA server is a decision that impacts not just the update experience but also compliance with industry and privacy regulations. While SaaS solutions can offer ease and reliability, certain scenarios may necessitate on-premise servers. If you do need to host an OTA server yourself, you’ll need to supply the server hardware and assign a maintenance team to manage it. But you may not have to build it all from scratch – you can still call in the experts with proven experience in setting up self-hosted OTA solutions.

Certificates: The bedrock of OTA security

SSL certificates are non-negotiable for genuine and secure OTA updates. They verify your company as the authentic source of updates. Choosing algorithms with the longest (comparatively equivalent) key lengths will extend the reliable lifespan of these certificates. However, remember that certificates do expire; having a game plan in place to deal with expired certificates will allow you to avoid the panic of an emergency scramble if it should happen unexpectedly.

Accurate timekeeping is also essential for validating SSL certificates. A functioning and accurate real-time clock that is regularly NTP/SNTP synchronized is critical. If timekeeping fails, your certificates won’t be validated properly, causing all sorts of issues. (We recommend reading our OTA best practice guide for advice on what to do proactively and reactively with invalidated or expired certificates.

Payload encryption: Non-negotiable

Encrypted update payloads are imperative as a safeguard against reverse-engineering and content tampering. This is true for OTA updates as well as any USB or offline updates. Leveraging the strongest possible encryption keys that your device can handle will enhance security significantly.

Accommodating the right to repair

The growing ‘right to repair’ movement and associated legislation imply that devices should support updates outside of your organization’s tightly controlled processes. This may mean that you need to provide a manual USB update to meet repair requirements without exposing systems to unauthorized OTA updates. To prevent your support team from struggling with amateur software updates, you’ll want to configure your device to set a flag when unauthorized software has been loaded. This status can be checked by support teams to invalidate support or warranty agreements.

Summary

By carefully navigating the critical aspects of OTA updates, such as choosing the right hosting option and managing SSL certificates and encryption protocols, your embedded systems can remain up-to-date and secure under any operating conditions. While this post introduces the issues involved in embedded-system updates, there is much more to consider for a comprehensive strategy. For a deeper exploration and best practices in managing an embedded product software update strategy, please visit our best practice guide, Updates Outside the App Store.

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post Behind the Scenes of Embedded Updates appeared first on KDAB.

PyQt6 & PySide6 Books updated for 2024 — Extended and updated with new examples, demos including Model View Controller architecture

Hello! Today I have released new digital updates to my book Create GUI Applications with Python & Qt.

This update brings all versions up to date with the latest developments in PyQt6 & PySide6. As well as corrections and additions to existing chapters, there are new sections dealing with form layouts, built-in dialogs and developing Qt applications using a Model View Controller (MVC) architecture. The same corrections and additions have been made to the PyQt5 & PySide2 editions.

As always, if you've previously bought a copy of the book you get these updates for free! Just go to the downloads page and enter the email you used for the purchase.

You can buy the latest editions below --

If you bought the book elsewhere (in paperback or digital) you can register to get these updates too. Email your receipt to register@pythonguis.com

Enjoy!

The Smarter Way to Rust

If you’ve been following our blog, you’re likely aware of Rust’s growing presence in embedded systems. While Rust excels in safety-by-design, it’s also common to find it integrated with C++. This strategic approach leverages the strengths of both languages, including extensive C++ capabilities honed over the years in complex embedded systems. Let’s delve into some key concepts for integrating Rust and C++.

Adding Rust to C++

If you’re adding Rust to an existing C++ project, you need to start in the right place. Begin by oxidizing (that is, converting code to Rust) areas that are bug-prone, difficult to maintain, or with security vulnerabilities. These are where Rust can offer immediate improvements. Focus on modules that are self-contained, have clean interfaces, and are primarily procedural rather than object-oriented. For example, libraries that handle media or image processing can be prime candidates for rewriting in Rust as these are often vulnerable to memory safety issues. Parsers and input handling routines also stand to benefit from Rust’s guarantees of safety.

Deciding between Rusting outside-in or inside-out

As your project scales, weigh the merits of maintaining a C++ core with Rust components versus a Rust-centric application with C++ libraries. For smaller, newer projects, starting with Rust may help you avoid the complexities of dealing with C foreign function interfaces (FFIs). This decision may hinge on your safety priorities: if your project’s core tenant is safety, then a Rust-centric approach may be preferable. Conversely, if safety is needed only in certain areas of C++ project, keeping the core in C++ could be more practical.

Another consideration is how your project handles multi-threading. Mixing threading and memory ownership between Rust and C++ is very complex and prone to mistakes. Depending on how your application uses threads, this may tilt the decision in the direction of either C++ or Rust as the main “host” application.

Keeping C++ where it excels

While Rust offers many advantages, particularly in safety, C++ has its own merits that shouldn’t be hastily dismissed. The decision to rewrite should be strategic, based on actual needs rather than a pursuit of language purity since the risk of introducing new bugs through rewriting well-tested and stable C++ code outweigh the benefits of a Rust rewrite. Time-tested C++ code, particularly in areas like signal processing or cryptography, might be best left as is. Such code is often highly optimized, stable, and less prone to memory-related issues. As the saying goes, if it’s not broken, don’t “fix” it.

Navigating Rust limitations

Despite its growing ecosystem, Rust is still relatively young. Relying on packages maintained by small teams or single individuals carries inherent risks. Moreover, as Rust is still in a period of rapid language evolution, this could result in frequent updates, posing challenges for large-scale or long-lived projects. In certain scenarios, such as very large codebases, specific embedded support requirements, or projects with long development cycles, C++ may remain the more practical choice. It is wise to use C++ where stability and longevity are important, and Rust where safety is critical but some development fluidity is acceptable.

Summary

By combining the reliability of C++ with the safety of Rust, developers can engineer systems that endure while minimizing the risk of common programming pitfalls. If you’re interested in reading more about this topic, you’ll want to read our best practice guide on Rust/C++ integration, which was created in collaboration with Ferrous Systems co-founder Florian Gilcher.

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post The Smarter Way to Rust appeared first on KDAB.

Anchoring Qt Quick Components Instantiated with JSON

You’ve reached the third and final entry of the Instantiating arbitrary Qt Quick components with JSON series. Part 1 and Part 2 can be found at their respective links.

The first part focuses on the software design pattern used to dynamically instantiate components. The second one shows how to layout these dynamic components by incorporating QML’ s positioning and layout APIs.

Part 3: Anchors and the Dangers of Remote Code Execution

On the last entry I wrote about:

1. Coordinate positioning

2. Item positioners

3. Qt Quick Layouts

Now there’s:

4. Anchors

Unfortunately for us, this is were the QML Engine becomes a limiting factor. Items in QML, instantiate QObjects. Every QObject in Qt contains an ID. We can set that ID from QML the moment we instantiate our component, like so:

Item {
    id: myID
}

Nevertheless, id is not a normal QML property. In fact it is no property at all. As a result, we’re unable to set an id after instantiation. Since neither Repeater or Instantiator allows us to set individual IDs during object creation, we have no means for naming our instantiated components, and therefore we cannot refer to them from other components. This limits our ability to anchor components to siblings that come from the repeater. We can only anchor to the parent and to siblings defined traditionally from the QML document.

With this in mind, let’s see how far can we take the use of anchors in our components factory.

But first, a quick reminder… Qt provides us with two syntaxes for using anchors, an object syntax and a shorthand for its individual properties:

anchors.left: parent.right
anchors: {
    left: parent.right
}

To keep things simple, I make use of the longer syntax in our model:

// This model lays out buttons vertically
property var factoryModel: [
    {
        "component": "RowLayout"
        "anchors": {
            "fill": "parent"
        }
        "children": [
            {
               "component": "Button"
               "text": "Button 1"
            },
            {
               "component": "Button"
               "text": "Button 2"
            }
        ]
    }
]

Let’s start by implementing fill: parent. If we were to assign the string from our model, which says “parent”, to our instantiator, the assignment would fail because we cannot assign a string where an object id is expected.

// This would fail...
instantiator.anchors.fill = modelData.anchors.fill;

// because it evaluates to this:
instantiator.anchors.fill = "parent";

Instead, we must parse the value from our model into valid JavaScript, and for that we use Javascript’s eval function. Calling eval will return whichever value the JS expression inside the string evaluates to. Here “parent” will point to the parent property relative to the context where eval is run. For the purposes of anchoring, this is fine.

Our code now looks like:

if (typeof(modelData.anchors.fill) === "string")
    instantiator.anchors.fill = eval("instantiator."
                                     + modelData.anchors.fill);

There’s a couple of issues:

  • Qt Creator’s annotator suggests not to use eval. You may ask: Why is this? Because eval treats the string from our model as a JavaScript expression. Nothing prevents a malicious actor from injecting JS code into that string that could attach a payload to our app to our app to later perform a remote code execution attack. A malicious actor might write something like:
"parent;this.onPressed=function(){/*arbitrary_code_goes_here*/};"  // Note that I don't know if this example really works

This takes care of the parent assignment, then overrides the contents of onPressed with a function that runs arbitrary code for the user to unsuspectingly execute. This is why every respectable linter and static analyzer for JavaScript will warn you not to use eval or any of its equivalent methods; in order to prevent remote code execution.

  • If security is not a big enough concern for you, eval cannot be parsed by the new JavaScript compilers introduced with Qt 6. The compiler will error out saying:

error: The use of eval() or the use of the arguments object in signal handlers is not supported when compiling qml files ahead of time. That is because it’s ambiguous if any signal parameter is called “arguments”. Similarly the string passed to eval might use “arguments”. Unfortunately we cannot distinguish between it being a parameter or the JavaScript arguments object at this point.

Part 4: Defeat Remote Code Execution by Sanitizing Inputs

My preferred way to sanitize inputs is using Regular Expressions. We want to guarantee the strings from our models evaluate to a valid anchor name.

Valid anchor names include:

  • parent
  • idName.top
  • idName.right
  • idName.bottom
  • idName.left
  • idName.baseline
  • idName.verticalCenter
  • idName.horizontalCenter

Here’s a RegEx I wrote that covers all anchors:

const anchorsRegex =
/[a-z_][a-zA-Z_0-9]*(\.(top|right|bottom|left|baseline|(vertical|horizontal)Center))?/;

We can use it to sanitize anchor inputs from the model, like so:

anchorsRegex.test(modelData.anchors.fill))

The key is to remove all characters that could be used to execute malicious content. If one of such characters must be present, then we make sure its uses are restricted, as we’ve seen here.

Here’s what my final code for attaching anchors looks like:

Component {
  id: loaderComp
  Loader {
    id: instantiator
    // ...
    onItemChanged: {
  // Anchor properties
  if (typeof(modelData.anchors) === "object") {
    // Regex for validating id and id.validAnchorline
    const anchorsRegex = /[a-z_][a-zA-Z_0-9]*(\.(top|right|bottom|left|baseline|(vertical|horizontal)Center))?/;
    // Before attempting to assign,
    // check that properties are present in object
    // and that model attributes aren't undefined.
    if ((typeof(modelData.anchors.fill) === "string")
      && anchorsRegex.test(modelData.anchors.fill)) {
      instantiator.anchors.fill = eval("instantiator."
                                         + modelData.anchors.fill);
    }
    else if ((typeof(modelData.anchors.centerIn) === "string")
      && anchorsRegex.test(modelData.anchors.centerIn)) {
      instantiator.anchors.centerIn = eval("instantiator."
                                         + modelData.anchors.centerIn);
    }
    else {
      if ((typeof(modelData.anchors.left) === "string")
        && anchorsRegex.test(modelData.anchors.left)) {
        instantiator.anchors.left = eval("instantiator."
                                         + modelData.anchors.left);
        item.anchors.left = item.parent.anchors.left;
      }
      if ((typeof(modelData.anchors.right) === "string")
        && anchorsRegex.test(modelData.anchors.right)) {
        instantiator.anchors.right = eval("instantiator."
                                         + modelData.anchors.right);
        item.anchors.right = item.parent.anchors.right;
      }
      if ((typeof(modelData.anchors.baseline) === "string")
        && anchorsRegex.test(modelData.anchors.baseline)) {
        instantiator.anchors.baseline = eval("instantiator."
                                         + modelData.anchors.baseline);
        item.anchors.top = item.parent.anchors.top;
      }
      else {
        if ((typeof(modelData.anchors.top) === "string")
          && anchorsRegex.test(modelData.anchors.top)) {
          instantiator.anchors.top = eval("instantiator."
                                         + modelData.anchors.top);
          item.anchors.top = item.parent.anchors.top;
        }
        if ((typeof(modelData.anchors.bottom) === "string")
          && anchorsRegex.test(modelData.anchors.bottom)) {
          instantiator.anchors.bottom = eval("instantiator."
                                         + modelData.anchors.bottom);
          item.anchors.bottom = item.parent.anchors.bottom;
        }
      }
    }
    // ...

Like with Layouts, we attach our anchors to the instantiator, and not just the item. The item must attach itself to the instantiator where applicable.

Then take care of the rest of the anchor’s properties like so:

        // ...
        // Anchor number properties
if (typeof(modelData.anchors.margins) !== "undefined")
  instantiator.anchors.margins = Number(modelData.anchors.margins);
if (typeof(modelData.anchors.leftMargin) !== "undefined")
  instantiator.anchors.leftMargin = Number(modelData.anchors.leftMargin);
if (typeof(modelData.anchors.rightMargin) !== "undefined")
  instantiator.anchors.rightMargin = Number(modelData.anchors.rightMargin);
if (typeof(modelData.anchors.topMargin) !== "undefined")
  instantiator.anchors.topMargin = Number(modelData.anchors.topMargin);
if (typeof(modelData.anchors.bottomMargin) !== "undefined")
  instantiator.anchors.bottomMargin = Number(modelData.anchors.bottomMargin);
if (typeof(modelData.anchors.horizontalCenterOffset) !== "undefined")
  instantiator.anchors.horizontalCenterOffset = Number(modelData.anchors.horizontalCenterOffset);
if (typeof(modelData.anchors.verticalCenterOffset) !== "undefined")
  instantiator.anchors.verticalCenterOffset = Number(modelData.anchors.verticalCenterOffset);
if (typeof(modelData.anchors.baselineOffset) !== "undefined")
  instantiator.anchors.baselineOffset = Number(modelData.anchors.baselineOffset);
      }
    }
  }
}

As you can see, we nullify the dangers of eval by sanitizing our inputs properly. This doesn’t fix our inability to anchor to sibling components or our inability to pre-compile the QML, but we can use this tool to refer to other items in the QML tree. The tree itself may come from a document loaded from a remote location, using a Loader.

Running any remote document in our QML code would also open the doors to arbitrary code execution attacks. We may not be able to sanitize an entire QML document like we can sanitize for individual properties, therefore you should only allow QML and JS file downloads from a trusted source, and always use an encrypted connection to prevent a targeted attack. Self-signed certificates are better than no certificate. Without encryption, a malicious actor could intercept the traffic and alter our code while it’s on its way.

This has been the last entry in this series. Thanks to Jan Marker for his time reviewing my code. I hope you’ve learned something from it. I personally enjoyed looking back at Part I the most, because the technique shown there has more real world applications.

Reference

  • Learn how to write Regular Expressions by playing with the editor and cheat sheet at https://regexr.com/ The more precise you learn to make your definitions, while attempting to keep them small, the better you’ll get at writing them.
About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post Anchoring Qt Quick Components Instantiated with JSON appeared first on KDAB.

QtTest Extension for VSCode

Qt Test is a framework designed for unit testing applications and libraries built with Qt. It provides all the standard features commonly found in unit testing frameworks, making it easier to write unit tests for Qt-based projects.

We’re happy to introduce the QtTest Runner – a Visual Studio Code extension for running Qt Tests via the test explorer pane.

Test Explorer

The main difference from existing test extensions is that you can now run a single test slot instead of the whole executable. Also, unlike the ctest extension, we only build the target owning the test. There is no need for big rebuilds when debugging.

Other nice-to-haves include Go To Test, which opens the corresponding .cpp file directly from the test explorer:

Go To Test

And you can now execute a slot directly from the .cpp file editor, via context menu, by selecting the slot name:

Debug Selection

Features

  • Listing and running Qt tests
  • Listing and running individual QTest test slots
  • Context-menu entry to run selected slot in the text editor
  • Build automatically before running
  • .cpp file with the test’s implementation is automatically opened when there’s a failure
  • “Go to Test” available in the test explorer

Requirements

Only CMake is supported at this time, as test discovery is done via CTest.

You’ll need the official CMake extension. See our VSCode setup for Qt blog or just grab our template to get started quickly.

You’ll also need a C++ debugger extension. This is usually either ms-vscode.cpptools or vadimcn.vscode-lldb aka CodeLLDB.

You’re expected to be using CMake’s add_test(). Confirm by running ctest in your build directory.

Settings

KDAB.QtTest.debugger

By default the extension will try to guess what the best debugger is. But you can and probably should explicitly choose which debugger to use. The best setting might be Existing Launch, which will use an existing launch config, this way you can reuse your source maps, pretty printers and launch env variables. When reusing an existing launch, its program and args are replaced by the test you’re running.

KDAB.QtTest.CheckTestLinksToQtTestLib

Only available on Linux. Turn it on in case you have non-Qt tests executables that you want to exclude from the list. It will invoke ldd and see if libQtTest.so is present. Patches are accepted for Windows and macOS support.

How it works

Test discovery is done by calling ctest --show-only=json-v1, which lists all executables in a friendly JSON format. Then, for each executable, we call -functions which lists the available slots.

If you have a weird build system to support and you know of a nice test discovery approach, file an issue request and we’ll try to guide you for a contribution.

Known Issues

  • Slots are not known at configure time, only after a build
  • Not nice if you mix non-QtTests with QtTests, as there’s no ideal way to exclude tests, however, on Linux we can call ldd and see if a test links to QtTest.

Troubleshooting

In the output pane, choose KDAB-QtTests and see if there are any errors. Before reporting a bug, clear the output pane, reproduce the bug, and copy all output, paste it in the bug report.

Try pressing the reload button if the listing seems stale.

If no tests are reported, try running ctest -N inside the build directory. If that doesn’t work either then it’s a cmake problem. Either there’s really no tests added with add_test() or the enable_testing() call is missing in the root CMakeLists.txt.

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post QtTest Extension for VSCode appeared first on KDAB.

KDDockWidgets 2.1 Released

KDDockWidgets has launched its latest version 2.1. This release comes packed with over 500 commits, offering enhanced stability over its predecessor, version 2.0, without introducing any breaking changes.

KDDockWidgets is a versatile framework for custom-tailored docking systems in Qt written by KDAB’s Sérgio Martins. For more information about its rich set of features, have a look at its GitHub repository.

What’s changed in version 2.1?

Here are the main highlights:

Bug Fixes:

For starters, KDDW 2.1 introduces a range of bug fixes aimed at enhancing stability and user experience. Less popular features like nested main-windows and auto-hide received lots of attention and window manager specific bugs such as restoring maximized windows were addressed.

KDDW is now memory-leak free, several singletons were leaking before. We’ve added a valgrind GitHub Actions workflow to prevent regressions regarding leaks.

QtQuick:

KDDW 2.1 also introduces improvements in QtQuick. New features include an API for setting affinities via QML, enabling mixing MDI with docking similar to QtWidgets, and fixing DPI issues of icons in TitleBar.qml for better scaling at 150% and 200%. Additionally, it resolves issues such as MDI widgets not raising when clicked on and various crashes related to MDI mode.

QtWidgets:

For QtWidgets, we’ve improved handling of the preferredSize argument when adding dock widgets. It was being ignored in some cases. Overriding DockWidget::closeEvent() can now be done to prevent closing. Several crashes were fixed and we’ve added a GitHub Actions workflow which runs the tests against a Qt built with AddressSanitizer.

These enhancements improve the functionality and stability of KDDW 2.1 across different Qt environments.

Miscellaneous:

KDDW 2.1 brings miscellaneous updates, including an upgrade to nlohmann json v3.11.3, the addition of a standalone layouting example using the UI toolkit Slint, and extensive testing on CI. Additionally, Config::setLayoutSpacing(int) has been added for increased customization.

Learn about all the changes here. Let us know what you think.

More information about KDDockWidgets

https://youtube.com/watch?v=Ztqy4cpvkwg%3Fsi%3DCx9uTMshhbsPYJuX

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post KDDockWidgets 2.1 Released appeared first on KDAB.

Qt and Trivial Relocation (Part 3)

In the last post of this series we started exploring how to erase an element from the middle of a vector.

  • We discussed that in principle there are several different possible ways to implement erase().For instance, a vector could move-assign over the elements to be erased:

Alternatively, a vector could use rotations or some other algorithms to move the elements to be destroyed to the end:

In both cases we’re then left with an element in the rightmost position that needs to be destroyed. We can destroy it, update the vector’s size, and that’s it!

  • Perhaps with a bit of arrogance, we have therefore claimed that the specific strategy for erasure used does not really matter. A vector implementation will just use a “good one”, and we can always change it if we find out it’s slower than an alternative.Keep this in mind, because it is going to be important really soon.
  • We have also discussed that QVector optimizes erasure of trivially relocatable types by using a memmove. If the type is trivially relocatable, QVector destroys the element to be erased, and moves bytes from the tail over:

We concluded the post wondering: is this optimization legitimate? Why can QVector replace move assignments with a byte copy?

This doubt stems from the fact that our definition of relocation did not cover move assignments; it only covered move construction. Is Qt wrong? Is our definition wrong?

The reference semantics backstab

Let’s start by analyzing erase()‘s behavior once more.

Do you remember our claim that the specific strategy used does not really matter; that is, that they are all equivalent? Well, not so fast! It is actually quite imprecise to say that they are all equivalent.

They may be, as long as we deal with types which have value semantics. If we instead use a type that has reference semantics, the choices are absolutely not equivalent, and will yield different outcomes. This is because the semantics of assignment for (certain) reference types are write-through: they assign through the reference (instead of rebinding the reference).

Since we are implementing erasure in terms of assignments (or swaps, which boil down to assignments), this means that the precise sequence of operations done by erase will be visible due to its side-effects; and it also means that changing the strategy will produce different outcomes!


An example of a Standard Library type with these write-through reference semantics is std::tuple<int &>.

When one assigns such a tuple over another one, the reference inside the tuple will assign-through. This semantic is absolutely deliberate; it is used, for instance, by std::tie to build a tuple of references that can be used to unpack a given tuple:

int a; double b;

std::tie(a, b) = getTuple();  // std::tie produces std::tuple&lt;int &amp;, double &amp;&gt;
                              // assignment will write through, over a and b

For the sake of the example, instead of dealing with std::tuple, let’s strip it down to the bare minimum into a struct IntRef, so that we can focus on the behavior that we care about:

struct IntRef {
  int &amp;m_ref;

  IntRef &amp;operator=(const IntRef &amp;other)
  { 
    // write-through semantics for assignment:
    m_ref = other.m_ref;
    return *this; 
  }
};

Here’s an example of this in action:

int a = 1;
int b = 2;
IntRef ra{a};
IntRef rb{b};

ra = rb;
std::println("{}, {}", a, b); // prints 2, 2; we've "assigned through" ra.

The assignment on line 6 wrote 2 into the variable a.

Note that I had to explicitly write an assignment operator for IntRef, and implement the wanted semantics (write-through) myself. This is necessary because of the presence of a reference as a non-static data member (m_ref): the implicitly-generated assignment operator(s) are actually deleted in this case. This default by C++ is actually good, as reference members are a bad idea, and if you have them in a class you should not support assignment.


Let’s now generalize this to a std::vector<IntRef>:

int a = 1, b = 2, c = 3, d = 4, e = 5;
IntRef ra{a}, rb{b}, rc{c}, rd{d}, re{e};

std::vector&lt;IntRef&gt; v{ra, rb, rc, rd, re};

v.erase(v.begin() + 1);

std::println("{}, {}, {}, {}, {}", a, b, c, d, e);

What does this code print? Which is another way of asking: “what happens when we erase the second element from v?”.

Now this is a very tricky question to answer; depending on the implementation strategy chosen, we may end up with a completely different outcome:

  1. If we move assign the “tail” to the left one element at a time, and erase the last element, then the output is going to be 1, 3, 4, 5, 5.
  2. If we rotate the second element to the end, and destroy the end, then the output is… well, we don’t really know, do we? It depends on the implementation of std::rotate.It could be 1, 3, 4, 5, 2; but it could be something else as well.

Please take a moment to convince yourselves of this 🙂 Pencil and paper helps, otherwise, here’s a convenient Godbolt.

Vector erasure must be specified

The conclusion is that the implementation strategy does matter for types with write-through reference semantics! This is a very different outcome than what we might have expected (or desired, even).

For this reason, the Standard Library clearly documents the behavior of std::vector::erase in [vector.modifiers]:

Complexity: The destructor of T is called the number of times equal to the number of the elements erased, but the assignment operator of T is called the number of times equal to the number of elements in the vector after the erased elements.

This pretty much imposes a specific implementation strategy (move assign the tail to the left), so you can rely on the outcome, even in the presence of types with write-through reference semantics.

I’ll also add an extra consideration: even if this behavior wasn’t fully documented, due to Hyrum’s law, we still couldn’t “just change” the implementation of erase, because existing code will be affected — it will be able to tell the difference.

What about QVector? Does it document its erasure behavior? Well… let’s talk about this later, shall we?

Is IntRef trivially relocatable?

Now, here’s a million dollar question: would you say that IntRef is trivially relocatable or that it’s not?

Maybe you’re tempted to say “it is not” based on what you’ve just seen — IntRef has a “strange” behavior, different from most types that we “normally” use. That should be enough to disqualify it, shouldn’t it?

Let’s not rush to the answer just yet! For the benefit of the discussion, I am going to repeat our current definition of relocability: to move construct an object, and destroy the source object. Relocability is trivial when these two operations can be realized by simply copying bytes.

For example:

  • int is trivially relocatable. std::unique_ptr<T> is trivially relocatable. QString is trivially relocatable.
  • std::string is usually not trivially relocatable (self-referential due to SSO). std::list may also not be (the first node and/or a sentinel node of the list may contain a pointer to the std::list object itself).
  • QObject isn’t relocatable (it is not movable at all).

So, what about our IntRef?

If we stick to this very definition the correct answer is that IntRef is absolutely, unequivocally trivially relocatable. We can replace a move construction of an IntRef object, followed by the destruction of the source, with a mere memcpy; the result is completely equivalent.

IntRef satisfies our definition of trivial relocability.

If you think about it, IntRef simply contains a reference — that is, a pointer. It’s not very different from the memory layout of a std::unique_ptr from this point of view, and we know we can relocate that.

IntRef does show its reference semantics on assignment, but remember, our current definition of (trivial) relocability does not talk about assignments at all.

As a corollary, this fact means that it would be perfectly safe to grow a vector<IntRef> using memcpy when it needs a reallocation!

What about erasure?

If IntRef is trivially relocatable, can we erase an IntRef object from a vector, using memmove to do so?

Again, based on what we have seen, the answer is no, we cannot. Erasure is fully specified to use assignments, which yield side effects for a type like IntRef. If we just moved bytes around, those side effects would not be realized, breaking our programs.

The problem

We have reached a contradiction in our design:

  1. IntRef is trivially relocatable as it satisfies our definition.
  2. Qt optimizes the erasure of trivial relocatable types by using memmove, but
  3. IntRef cannot be erased by copying bytes! It needs to be erased by using move assignments.

To solve this contradiction, something has to give in:

  • maybe vector should use a different strategy when erasing elements;
  • maybe types like IntRef should not be allowed to be stored in a vector;
  • maybe Qt should not be using memmove when erasing objects of trivially relocatable type (but it can still optimize the reallocation of a vector);
  • maybe Qt’s definition of trivial relocability does not match ours, and we need to fix our definitions.

We will explore these possibilities in the next blog post. Stay tuned!

Overview about all installments:

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post Qt and Trivial Relocation (Part 3) appeared first on KDAB.