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.

Cloud-Native Software-in-the-Loop Testing with Qt and Squish

In today's fast-paced software development environment, the need for efficient and accurate testing methodologies has never been more crucial. As applications become increasingly complex and distributed, traditional testing approaches often fall short. This is where cloud-native software-in-the-loop (SIL) testing comes into play. Qt, a powerful cross-platform application development framework, along with Squish, Qt Group's automated UI testing tool, enable this modern testing pattern.

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.

Qt for Android Supported Versions Guidelines

Qt for Android usually supports a wide range of Android versions, some very old. To keep the supported versions to a level that’s maintainable by Qt, especially for LTS releases which are expected to live for three years, Qt for Android is adopting new guidelines for selecting the supported versions for a given Qt release in the hope that this effort would make the selection clear and transparent, and help shape proper expectations of support for each Qt for Android release.

Qt Performance and Tools Update Part 1

Performance optimisation matters when you are trying to get your application working in a resource-constrained environment. This is typically the case in embedded but also in some desktop scenarious you may run short on resources so it’s not a matter without significance on desktop either.

What we mean by performance here is the ability to get the application running to fulfill its purpose, in practice typically meaning sufficient FPS in the UI and meeting other nonfunctional requirements, such as startup time, memory consumption and CPU/GPU load.

There have been a number of discussions on Qt performance aspects and as we have been working on a number of related items we thought now could be a good time to provide a summary of all the activities and tools we have. You can optimise the performance of your application by utilising them and also use them in testing. We have been working on improving existing performance tools as well as adding new ones and providing guidelines, so let’s look at the latest additions. This post is starting a stream of blog posts to help you with performance optimisation and provide a view to our activities in this area.

Qt for Android: a Novel Approach

This blog post is the third one in a series in which we will examine an exciting feature, a change that is first coming out as a Technology Preview in 6.7. Previously, there have been blog posts about Qt Quick in Android as a View and Qt Tools for Android Studio. It is so new that even we do not know how to call this novel approach yet 😊 Give us a name proposal in comments! In this blog post let us look at a few things: workflow, tooling, needed Qt modules, and a small glance at the technical mechanism. 

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.

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

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 4)

In the last post of this series we learned that:

  • erasing elements from the middle of a vector can be implemented, in general, via a series of move assignments, move constructions, swaps, destructions
  • for types with value semantics, the exact strategy does not really matter
  • for types with write-through reference semantics, the strategy matters, because the outcome is different
  • for this reason, the Standard Library specifies exactly which strategy is employed: move assignments and destructions
  • Qt optimizes erasure of trivially relocatable types by using memmove to compact the tail.

We ended up realizing that this creates a contradiction: a type with write-through reference semantics like std::tuple<int &> is trivially relocatable (move construction and destruction of the source can be replaced by moving bytes), but it cannot be erased via byte manipulations. However, if we were to mark that type as trivial relocatable (via Q_DECLARE_TYPEINFO) then Qt would use byte manipulations, breaking our programs!

The conclusion of the last post was that we need to change something in our models:

  • maybe std::vector should use a different strategy when erasing elements
  • maybe types like std::tuple<int &> 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.

In this post we will explore these possibilities and reach some conclusions.

Changing the behavior of vector erasure

As we have already discussed in the previous blog posts, it is possible to implement erasure in a number of ways, which are not equivalent. The Standard Library chose a specific implementation strategy (move-assign the elements after the ones to be destroyed to the left; destroy the moved-from last elements). Changing it now, over 26 years after the fact, sounds extremely scary; std::vector is such a central class that surely such a change would break somebody’s code.

That doesn’t mean that we can’t at least reason about a possible change there!

Also, there is another library that we keep talking about in these blog posts. This other library has a much smaller userbase than the Standard Library, and that has fewer regards with breaking backwards compatibility. We should certainly reason about that library as well. I’m talking about Qt, of course 🙂

Change the behavior of vector erasure what, exactly?

In order to reconcile our definition of (trivial) relocatability with the behavior of erase, we would need to implement erase in terms of move constructions and destructions.

Thankfully, this can be done. Suppose again we want to erase the second element from a vector:

Same problem: how to erase B from the vector?

We can implement this by destroying the second element, and move-constructing the third element over it:

First steps: B gets destroyed; C is move-constructed over it.

Then we destroy the moved-from third element, and move-construct the fourth element over it.

Destroy the moved-from C object, move-construct D over it

We keep going until the end, where we have just a moved-from element to destroy:

Destroy moved-from D. It’s the last element, nothing else to move around. Update bookkeeping and we’re done.

Erasure can use relocation

The fact that this particular implementation strategy is viable gives us some very interesting insights.

Let’s look at the sequence of operations once again:

  1. Destroy the element at position 1 (B)
  2. Move construct the element at position 2 into position 1 (C)
  3. Destroy the element at position 2 (moved-from C)
  4. Move construct the element at position 3 into position 2 (D)
  5. Destroy the element at position 3 (moved-from D)

We can look at this sequence in two different ways:

  • firstly, as pairs of “destroy + move construct” operations, leaving a last element to destroy (1+2, 3+4, 5)
  • or, more interestingly, as an initial destruction, followed by a series of relocations. (1, 2+3, 4+5).

Move assignments for value types

Let’s analyze the first way: pairs of “destruction + move construction” operations. This analysis will allow us to grasp the fundamental difference between value types (like QString, or std::unique_ptr<T>) from reference types (like std::tuple<int &>).

We have already established that for a value type it does not matter what the erasure strategy is; the outcome is the same. For a write-through reference type, the strategy matters; using move assignments instead of destructions/move constructions pairs will yield a different result.

We can now appreciate the difference: for a value type, a move assignment is equivalent to destruction of the target, followed by move construction. This is why, when dealing with value types, using the above strategy for erasure gives us the same results as a strategy based on move assignments (like the one that std::vector uses).

For a write-through reference type, this is not true: a move assignment is not equivalent to destroying the target and move-constructing the source in its place. Again, consider std::tuple<int &>:

int a = 1;
int b = 2;

using T = std::tuple<int &>;
T ta(a); 
T tb(b); 

#if USE_MOVE_ASSIGN
// move-assignment from tb
ta = std::move(tb);          // a = 2, b = 2
#else
// destroy ta + move-construct from tb
ta.~T();
new (&ta) T(std::move(tb));  // a = 1, b = 2
#endif

It is worth noticing the behavior of move assigments is completely orthogonal to whether a type is trivially relocatable or not. Both QString and std::tuple<int &>: are trivially relocatable (according to our prior definition), but QString has value semantics, std::tuple<int &>: has not. In other words, this is another property of a type which cannot be “detected” from the outside the type itself (just like trivial relocation cannot be detected).

Trivial relocation and erasure

Let’s now tackle the second way of looking at erasure: destruction of an element, followed by a series of relocations.

This means that if a type is trivially relocatable, then the erasure strategy shown above can be implemented by an initial destruction followed by a memory copy for each subsequent element. Of course, these byte copies can be coalesced in a single memmove of the tail:

This is precisely what Qt does when erasing elements of a trivially relocatable type! At least now we have a justification for it.

So which erasure strategy does QVector use exactly?

If Qt uses trivial relocation to erase elements from a QVector of trivially relocatable types, does this mean that QVector also uses (non-trivial) relocation when erasing objects of other types?

Well… no, not really. Qt is way, way, way less strict than the C++ Standard Library: the exact strategies for erasure, insertion etc. are simply undocumented and can and will change at any time. (In fact, as I’m writing this blog post, I can see commits that change these algorithms…) The strategies are also inconsistent between different containers.

For instance, consider a non-empty QVector of size S. If I erase the first element, exactly how many operations are going to happen? 𝒪(S), right?

That would be true in Qt 5, but it’s not true any more in Qt 6! In Qt 6 the operation is 𝒪(1): QVector in Qt 6 has a “prepend optimization”: there may be empty capacity at both ends of the storage. So, in order to erase the first element, it’s sufficient to destroy it and update bookkeeping (move the “begin” pointer forward). This is just one amongst many other similar examples where Qt containers have changed behavior between Qt releases; other changes do happen even between minor releases.

The corollary is of this lack of guarantees is quite strong: do not ever store types that don’t have value semantics in Qt containers! Qt will break your programs when its containers change behavior. It’s a when, not an if.

Thankfully, the majority of types we deal with in Qt applications have value semantics, so they are not affected.

Could we just ban non-value types from containers?

There is one last question left. Who in the world uses std::tuple<int &> as a vector’s value type?

While that may sound very unlikely, in reality there are many other types for which assignment is different than destruction+move construction.

For instance: allocator-aware types (std::string, std::vector, etc.) may exhibit non-value behavior with certain allocators. An example of such an allocator is std::pmr::polymorphic_allocator, which does not propagate on move assignment. Erasing a std::pmr::string from a vector must have a very specific outcome, dictated by its reference semantics; but surely we can’t outlaw vector of strings!

Even if we could decide to ban “some” types, how do we figure out their assignment semantics?. A Rule Of Five type may or may not have value semantics, and there is no way to know about that “from the outside”.

So, this line of reasoning leads to nowhere.

Conclusions

In this post we started with a list of questions. In search for answers, we have have seen that:

  • for some types (“value” types), move assignment is equivalent to destruction of the target followed by move construction from the source, while for other types this is not true
  • we can’t just ban the latter types from being stored in containers. They’re useful, and in general we have no means to detect them!
  • vector erasure can be implemented in terms of relocations: this fact potentially allows a vector implementation (like QVector) to use trivial relocation when erasing elements
  • Qt containers assume value semantics from the objects they hold, and do not document the exact strategies used by their algorithms
  • we can’t change std::vector erasure behavior, because it would break existing code.

In the next post we will finish our exploration of trivial relocability, and we’ll look at C++ standardization. Thank you for reading!

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 4) appeared first on KDAB.

Laying Out Components with Qt Quick and JSON

I was tasked to come up with a simple architecture for remote real time instantiation of arbitrary QML components. I’ve split my findings into 3 blog entries, each one covering a slightly different topic. Part 1 focuses on the software design pattern used to dynamically instantiate components. Part 2 shows how to layout these dynamic components by incorporating QML’ s positioning and layout APIs. The last entry, consisting of Parts 3 and 4, addresses the anchors API and important safety aspects.

This is Part 2: Laying Out Components with Qt Quick and JSON

Now that we know how to instantiate trees of components, the next thing we should be able to do is lay them out on screen. If you’ve watched our Introduction to QML series, you’ll know Qt provides 4 ways to place objects in QML:

  1. Using the point coordinate system, where we pass x and y coordinates.
  2. Using Item Positioners
  3. Using Qt Quick Layouts, which are like positioners but can also resize their items using attached properties
  4. Using anchors, which allows us to link the item’s center, baseline, and edges, to anchors from other items.

For maximum flexibility, the factory design approach should support all four methods of component placement.

  • 1. The coordinate system we get almost for free. To use it, we can assign the values for x and y from onItemChanged in the instantiator Loader, as seen in Part I:
    // Root component of the factory and nodes
    Component {
        id: loaderComp
        required property var modelData
        Loader {
            id: instantiator
            // ...
            onItemChanged: {
                // ...
                if (typeof(modelData.x) === "number")
                    loaderComp.x = modelData.x;
                if (typeof(modelData.y) === "number")
                    loaderComp.y = modelData.y;
                // ...
            }
        }
    }
  • 2. &  3…Item Positioners and Qt Quick Layouts work in very similar ways. So, let’s have a look at how to approach Qt Quick Layouts, which is the most sophisticated of the two. Let’s remember how Qt Quick Layouts are commonly used in the first place: First, we import QtQuick.Layouts. Then, instantiate any of these Layout components: https://doc.qt.io/qt-6/qtquick-layouts-qmlmodule.html, and set dimensions to it, often by means of attached properties (https://doc.qt.io/qt-6/qml-qtquick-layouts-layout.html#attached-properties). For the outermost Layout in the QML stack, we might use one of the previous APIs to achieve this. Here’s a simple example for how that looks:
import QtQuick.Layouts

Item {
    ColumnLayout {
        Button {
            text: "1st button"
            Layout.fillWidth: true
        }
        Button {
            text: "2nd button"
            Layout.fillWidth: true
        }
    }
}

Now, for the Layouts API to work in our factory, the recursion described in Part I must be in place.

In addition to that, we need to take into account a property of the Loader object component: Loader inherits from Item. The items loaded by the Loader component are actually children of Loader and, as a result, must be placed relative to the loader, not its parent. This means we shouldn’t be setting Layout attached properties onto the instantiated components, but instead should set them on the Loader that is parent to our item, IDed as instantiator.

Here’s an example of what the model could define. As you can see, I’ve replaced the dot used for attached properties with an underscore.

    property var factoryModel: [
        {
            "component": "ColumnLayout",
            "children": [
                {
                    "component": "Button",
                    "text": "1st button",
                    "Layout_fillWidth": true
                },
                {
                    "component": "Button",
                    "text": "2nd button",
                    "Layout_fillWidth": true
                }
            ]
        }
    ]

Here’s what we will do, based on that model:

    // Root component of the factory and nodes
    Component {
        id: loaderComp
        Loader {
            id: instantiator
            required property var modelData
            sourceComponent: switch (modelData.component) {
                case "Button":
                return buttonComp;
                case "Column":
                return columnComp;
                case "ColumnLayout":
                return columnLayoutComp;
            }
            onItemChanged: {
                // Pass children
                if (typeof(modelData.children) === "object")
                    item.model = modelData.children;

                // Layouts
                if (typeof(modelData.Layout_fillWidth) === "bool") {
                    // Apply fillWidth to the container instead of the item
                    instantiator.Layout.fillWidth = modelData.Layout_fillWidth;
                    // Anchor the item to the container so that it produces the desired behavior
                    item.anchors.left = loaderComp.left;
                    item.anchors.right = loaderComp.right;
                }

                // Button properties
                switch (modelData.component) {
                    case "Button":
                    // If the model contains certain value, we may assign it:
                    if (typeof(modelData.text) === "string")
                        item.text = modelData.text;
                    break;
                }
                // ...
            }
        }
    }

As you can see, the attached property is set on the instantiator which acts as a container, and the component item is then anchored to that container. I do not simply anchor all children to fill the parent Loader because different components have different default sizes, and the Loader is agnostic of its children’s sizes.

Here’s the implementation for the Button, Column, and ColumnLayout components. Feel free to modify the JSON from factoryModel to use Column instead of ColumnLayouts, or any componentizations that you implement yourself.

    Component {
        id: buttonComp
        Button {
            property alias children: itemRepeater.model
            children: Repeater {
                id: itemRepeater
                delegate: loaderComp
            }
        }
    }
    Component {
        id: columnComp
        Column {
            property alias model: itemRepeater.model
            children: Repeater {
                id: itemRepeater
                delegate: loaderComp
            }
        }
    }
    Component {
        id: columnLayoutComp
        ColumnLayout {
            property alias model: itemRepeater.model
            children: Repeater {
                id: itemRepeater
                delegate: loaderComp
            }
        }
    }
  • 4. Anchors will be covered in the next entry. Some complications and security implications arise due to the fact that anchors can point to IDs, which is why I think they deserve their own separate article.

To summarize, we can dynamically attach attributes to our dynamically instantiated components to configure QML layouts. It’s important to keep in mind that the Loader will hold our dynamic component as its children, so we must assign our dimensions to the Loader and have the child mimic its behavior, possibly by anchoring to it, but this could also be done the other way around.

In the next entry I’ll be covering how to implement anchors and the security implications for which dynamically instantiating components from JSON might not be a good idea after all. Our previous entry is Recursive Instantiation with Qt Quick and JSON.

Reference

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 Laying Out Components with Qt Quick and JSON appeared first on KDAB.

Qt and Trivial Relocation (Part 2)

In the last post of this series we discussed the usage of trivial relocation in order to optimize move construction followed by the destruction of the source.

To quickly recap:

  • objects of certain datatypes (“trivially relocatable” types) can be moved in memory by simply moving bytes;
  • this can be used to optimize certain bulk operations (such as vector reallocation);
  • if we have a custom datatype which is trivially relocatable, we can mark it using the Q_DECLARE_TYPEINFO macro so that Qt considers it as such.

In this instalment we are going to explore the relationships between trivial relocation and move assignments.

A different example: vector erasure

Last time we started our investigation of trivial relocation by considering an important use-case: reallocating a vector. This happens when a vector reaches its capacity, but more storage is needed.

Let’s now consider a different operation: erasing an element from the middle of a QVector.

How do we go about it?

QVector&lt;QString&gt; v{ "A", "B", "C", "D" };

v.erase(v.begin() + 1);  // remove B, second element


We can easily come up with a few possible implementation strategies:

1. We could move-assign the “tail” one position to the left, one element at a time, left from right:

Move-assign C over B, move-assign D over (moved-from) C, and so on.

At the end of the sequence of move operations, we would end up with the expected contents in the vector (B has been overwritten), however there is a last moved-from element still around:

After the move assignments, there is a tail of moved-from element(s) that needs to be destroyed.

Since it’s at the tail, we can easily destroy it, update our bookkeeping, and that’s it:

Almost done: destroy the last element, and reduce size by 1.

2. Alternatively, we could std::rotate the tail of the vector so that B (the element to destroy) ends up at the end:

… it’s a rotate!

std::rotate will execute a series of swaps in order to, well, perform a rotation of the tail. After it’s done, we’re left with an element in the last position that needs to be destroyed (in this case, it’s B itself):

We still have a tail of elements to destroy.

Just like the previous example, we can then simply destroy the last element, and update our vector’s bookkeeping (its size has decreased by 1):

Destroying the last element.


Of course, there are other possibilities as well.

“At the end of the day”, one may think, “aren’t these all equivalent?”: they all end up removing B from the vector, and they just differ in how to get there. So why do we even bother discussing this? Surely Qt has chosen an efficient strategy, and whatever it is, it’s just an implementation detail.

… is it truly? As we’ll soon discover, things are way more complicated than this.

Trivial Relocatability for erasure

Let’s go back to our discussion about trivial relocability. If you read the last blog post, you should know that QString is trivially relocatable: we can move QString objects in memory by moving their byte representation. (This is not necessarily the case for std::string.)

You shouldn’t then be surprised by what comes next: QVector can use trivial relocation to optimize vector erasure for these types!

How, exactly? Let’s go back to our QVector<QString> from which we want to erase the second element:

In this case, QVector first destroys the element to be removed:

Then, we need to compact the tail over the destroyed element. Since QString is trivially relocatable, this is done via a memcpy as well! Well, to be pedantic: a memmove, since the source and destination ranges overlap.

After this we’re basically done: there’s no last element to destroy, all we need to do is update the vector’s bookkeeping.


Once more, this optimization constitutes a massive performance gain compared to having to shuffle individual elements around. You can see it in action inside QVector here.

Is this optimization legal? What was relocation again?

In the first instalment we defined relocation as the combination of move construction, followed by the destruction of the source. We also defined trivial relocation as “the same effects, realized by copying bytes” — in particular, without any calls to a type’s move constructor or destructor.

Our motivating example was optimizing the reallocation of a vector, where indeed the vector has move construct elements into the new storage, and destroy elements in the old storage.

However, in order to optimize erasure, we cannot formally use this definition! The point is that erasure does not use move construction at all! It uses move assignments (and destructions). We must therefore carefully reason about what we are doing.

The question we should ask ourselves at this point is: is Qt making an illegal optimization here? Or instead, is it that our definition of relocability needs some refinement?

Sorry for the cliffhanger, but you’ll have to wait for the next post to solve this mystery!

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 2) appeared first on KDAB.

Qt on iOS and ITMS-91053 NSPrivacyAccessedAPITypes Error

If you develop a Felgo or Qt app for iOS and upload it to the app store via AppStore Connect, you may face a new Apple warning e-mail these days:

We noticed one or more issues with a recent submission for App Store review for the following app.

Although submission for App Store review was successful, you may want to correct the following issues in your next submission for App Store review. Once you've corrected the issues, upload a new binary to App Store Connect.

ITMS-91053: Missing API declaration - Your app’s code in the “iosproject” file references one or more APIs that require reasons, including the following API categories: NSPrivacyAccessedAPICategorySystemBootTime. While no action is required at this time, starting May 1, 2024, when you upload a new app or app update, you must include a NSPrivacyAccessedAPITypes array in your app’s privacy manifest to provide approved reasons for these APIs used by your app’s code.

ITMS-91053: Missing API declaration - Your app’s code in the “iosproject” file references one or more APIs that require reasons, including the following API categories: NSPrivacyAccessedAPICategoryDiskSpace. While no action is required at this time, starting May 1, 2024, when you upload a new app or app update, you must include a NSPrivacyAccessedAPITypes array in your app’s privacy manifest to provide approved reasons for these APIs used by your app’s code.

ITMS-91053: Missing API declaration - Your app’s code in the “iosproject” file references one or more APIs that require reasons, including the following API categories: NSPrivacyAccessedAPICategoryUserDefaults. While no action is required at this time, starting May 1, 2024, when you upload a new app or app update, you must include a NSPrivacyAccessedAPITypes array in your app’s privacy manifest to provide approved reasons for these APIs used by your app’s code.

ITMS-91053: Missing API declaration - Your app’s code in the “iosproject” file references one or more APIs that require reasons, including the following API categories: NSPrivacyAccessedAPICategoryFileTimestamp. While no action is required at this time, starting May 1, 2024, when you upload a new app or app update, you must include a NSPrivacyAccessedAPITypes array in your app’s privacy manifest to provide approved reasons for these APIs used by your app’s code.

Apple Developer Relations

Without a proper fix, Apple has rejected app submissions to App Store Connect since May 1st.

Why is ITMS-91053 happening?

In an attempt to prevent fingerprinting, Apple recently added a new policy that requires you to provide a specific reason if you are using one of the APIs that Apple identified as a possible backdoor for fingerprinting. Fingerprinting refers to using specific APIs or device signals to uniquely identify a device or user for the purpose of tracking, even though users might not be aware of that.

While you may not use those APIs directly, Qt or any third-party framework integrated into your app may. Read on to learn how to fix the issue.

About QML Efficiency: Compilers, Language Server, and Type Annotations

About QML Efficiency: Compilers, Language Server, and Type Annotations

In our last post we had a look at how to set up QML Modules and how we can benefit from the QML Linter. Today we’re going to set up the QML Language Server to get an IDE-like experience in an editor of our choice. We’ll also help the the QML Compiler generate more efficient code.

Continue reading About QML Efficiency: Compilers, Language Server, and Type Annotations at basysKom GmbH.

How To Use Modern QML Tooling in Practice

How To Use Modern QML Tooling in Practice

Qt 5.15 introduced “Automatic Type Registration”. With it, a C++ class can be marked as “QML_ELEMENT” to be automatically registered to the QML engine. Qt 6 takes this to the next level and builds all of its tooling around the so-called QML Modules. Let’s talk about what this new infrastructure means to your application in practice and how to benefit from it in an existing project.

Continue reading How To Use Modern QML Tooling in Practice at basysKom GmbH.