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.

Qt for MCUs 2.8 LTS released

We are thrilled to announce the release of Qt for MCUs 2.8 LTS, which comes with new exciting GUI building blocks, improvements to build tools workflows, extended support for Infineon TRAVEO T2G microcontrollers, and much more. Qt for MCUs 2.8 is a Long-Term Support version, offering increased stability throughout your development. As such, it is the preferred version for all new projects. Standard Support will be available for 18 months, until December 2025.

Qt 6.7.2 Released

We have released Qt 6.7.2. As a patch release, Qt 6.7.2 does not introduce new features but contains more than 200 bug fixes, security updates, and other improvements to the top of the Qt 6.7.1 release. See more information about the most important changes and bug fixes from Qt 6.7.2 release note.

Improving the Safety and Security of Digital Products Made Available in the European Union - Understanding the European Cyber Resilience Act (CRA)

"Cyber threats evolve fast, they are increasingly complex and adaptable. To make sure our citizens and infrastructures are protected, we need to think several steps ahead, Europe's resilient and autonomous Cybersecurity Shield will mean we can utilise our expertise and knowledge to detect and react faster, limit potential damages and increase our resilience. Investing in cybersecurity means investing in the healthy future of our online environments and in our strategic autonomy."

Student project: Vector Map Rendering with Qt

Introduction

During the last six months, the Qt Group in Oslo hosted three students from NTNU in Gjøvik: Cecilia Bratlie, Eimen Oueslati, and Nils Petter Skalerud. Together, we worked on a proof of concept that might show us the direction to move forward with QtLocation. For the three students, this project served as their bachelor thesis, which they successfully defended last week in Gjøvik. We are very pleased to congratulate the students on their excellent work and to provide a short summary here for everyone interested in the potential future of QtLocation. 

Try new Qt Insight 1.9!

Exciting news for Qt Insight with the latest 1.9 release!

Qt Insight has new additions to the funnels and filters, allowing you to make deeper discoveries about your application usage. Two different funnel analytics, finer-grained geographic filtering options for regions, and many smaller improvements and bug fixes will enhance your insight.

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…
  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 and Trivial Relocation (Part 1)

The container classes introduced in Qt 4 (Tulip, for the aficionados) had an interesting optimization: the ability to turn certain operations on the contained objects into byte-level manipulations.

Example: vector reallocation

Consider the reallocation of a QVector<T>: when the vector is full and we want to insert a new value (of type T), the vector has to allocate a bigger block of memory.

Simplifying, the way this is done for a generic type T is:

  1. Allocate a bigger block of memory.
  2. Move-construct the T objects from the current storage into the new memory.

    Elements are being moved. The red, underlined elements in the source represent moved-from objects. C will be moved next.

  3. Destroy the moved-from objects in old current storage.
  4. Deallocate the old block of memory.
  5. Update bookkeeping (adjust data pointer, size, capacity, as needed).

In pseudocode, this looks something like this:

template &lt;typename T&gt;
vector&lt;T&gt;::reallocate_impl(size_t new_capacity)
{
    assert(m_size &lt;= new_capacity);
    T *new_storage = allocate(new_capacity);

    std::uninitialized_move(m_begin, m_begin + m_size, new_storage);
    std::destroy(m_begin, m_begin + m_size);

    deallocate(m_begin);
    m_begin = new_storage;
    m_capacity = new_capacity;
}

Depending on the operation that causes the reallocation (push_back, insert in the middle, simple reserve) a new element may also be added to the just allocated block of memory; that’s not really relevant here. I am also deliberately ignoring a lot of details, such as exception handling, move_if_noexcept, out of memory, allocators and similar.

The optimization

Types like int don’t do anything when they are constructed or destructed. If an int object is simply memcpy-ed to another place in memory, you would get the same integer in another place. Moreover, there would be nothing to do to destroy the original integer.

Therefore, if you have a QVector<int>, the above approach has some steps that are not needed. There’s a much faster way to achieve the same result:

  1. Allocate a bigger block of memory.
  2. memcpy all the data from the old storage to the new one.
  3. Deallocate the old storage.
  4. Update bookkeeping.

In pseudocode:

template &lt;typename T&gt;
vector&lt;T&gt;::reallocate_impl(size_t new_capacity)
{
    assert(m_size &lt;= new_capacity);
    T *new_storage = allocate(new_capacity);

    if constexpr (/* ... magic ... */) {
        std::memcpy(new_storage, m_begin, m_size * sizeof(T));
    } else if constexpr (std::is_nothrow_move_constructible_v&lt;T&gt;) {
        std::uninitialized_move(m_begin, m_begin + m_size, new_storage);
        std::destroy(m_begin, m_begin + m_size);
    } else {
        // ...
    }

    deallocate(m_begin);
    m_begin = new_storage;
    m_capacity = new_capacity;
}

What is going on here?

Qt is exploiting the fact that, for some datatypes, move-construction of an object A into a new object B, followed by the destruction of A, is equivalent / implementable as a byte copy (memcpy of A‘s object representation) into some suitable storage. In pseudocode:

// Given:
T *ptrA = ~~~;   // points to a valid object
T *ptrB = ~~~;   // points to uninitialized storage

// Then this:
new (ptrB) T(std::move(*ptrA));   // move-construct A into new storage (placement new)
ptrA-&gt;~T();                       // destroy A

// can be implemented like this:
memcpy(ptrB, ptrA, sizeof(T));

The combination of move-construction followed by destruction was happening in the generic code for vector reallocation. Instead of that, in the optimized version, we memcpy each object.

Note that in the optimized version we never call the move constructor of T; and we never call the destructor of A. They are both realized by the call to memcpy.

It’s easy to convince ourselves that this works for a type like int, where we can copy its object representation and end up with the same value.

What about a more complicated type, like QString?

It still works! When we memcpy a QString, we end up creating a new QString object that points to the same payload as the original, so that part is fine. Now, normally, when we create a copy of a QString, we would need to increase its reference counter. But here we are not creating a new copy: we are moving from the original string, and destroying the original. The consequence is that the reference counter of the string does not need to change; in the end the total number of QString objects pointing to that payload is the same. This is ensured by the fact that we are not running QString’s destructor over the original object.

QString is trivially relocatable: we can memcpy the QString object (just a pointer, really) and deallocate the original without running its destructor. This achieves the same effects as move-construction + destruction.

We can do even better

We have just established that we can use memcpy instead of move constructing one object (from the old storage into the new) and destroying the old object.

Now, QVector has to repeat this operation for multiple objects; since they are all stored contiguously in memory, then QVector can take a shortcut, and simply do a bulk memcpy of its contents!

This can be pushed even further: QVector simply calls realloc when reallocating, which is even better, because it allows to grow the allocated area in-place. Otherwise, realloc itself will move the data in memory. Simple, easy and efficient.

Terminology

In Qt we call a type that can be moved in memory via memcpy relocatable, but this specific jargon means different things to different people. I’ll try to clarify it in the rest of this post, as we go along.

For the moment, let’s try to be slightly more precise than Qt is, and use this vocabulary:

  • “relocation” means to move-construct an object, and destroy the source;
  • “trivial relocation” means to do the same, but achieve it via a simple memcpy.

However, please take this definition with a grain of salt. In the next instalments we will need to tweak it, as we discover some interesting properties of relocation.

Does this mean that QVector uses trivial relocation for all types?

No, it doesn’t. While it works for things like int, QPoint, QString, or QPen, this optimization is not safe to do in general: for some types Qt cannot replace move construction + destruction with a memcpy.

For instance, consider a type like a string class which employes the so-called Short/Small String Optimization, or SSO. This would be for instance std::string on most implementations. (For more background about SSO, see here.)

class string
{
    // may point to the heap, or into `m_buffer` if the string
    // is short enough:
    char *m_begin;   
    
    size_t m_size;
    size_t m_capacity;
    
    char m_buffer[SSO_BUFFER_SIZE];
};

string s = "Hello";

What would happen if we tried to replace a move-construction + destruction of a such a string class with a memcpy? Well, nothing good! In the source object the m_begin pointer may be pointing into itself.

If we try to relocate that string into another string via a memcpy, the new string’s pointer will point to the old object’s buffer; but the old object has been destroyed!

In fact, the move constructor of this string type would probably look something like this.


The bottom line is that there is no way to know “from the outside” if a generic type T can be moved in memory via memcpy. Qt must assume that it cannot do that, and therefore Qt by default sticks to the vector reallocation algorithm that moves constructs and destroys each element.

The types that cannot be moved in memory via memcpy are, generally speaking, types whose objects invariants depend on their specific address in memory. This is the case of self-referential types (like the string type), but also types which are referenced to externally (for instance, a node in a linked list is pointed to by the successor and the predecessor in the list, so we can’t just move it around in memory without breaking those links).

So how does Qt know that QString can be trivially relocated?

QString isn’t “hardcoded” to be special; any type can tell Qt that it is safe to be trivially relocated, and it does so by using a macro:

class MyClass { 
    ~~~ 
};

Q_DECLARE_TYPEINFO(MyClass, Q_RELOCATABLE_TYPE);

The macro expands to a template specialization that says that MyClass is opting in the optimization. For QString the macro is here, just hidden behind another macro.

Since most Qt value classes are pimpled (they just wrap a pointer to the private class, allocated on the heap; for more info, see here and here) it turns out that most of them benefit from this optimization, which is likely why it got introduced in the first place.

Q_DECLARE_TYPEINFO is public API; you are supposed to mark your own trivially relocatable types with it.

What does Q_RELOCATABLE_TYPE mean?

According to the documentation:

Q_RELOCATABLE_TYPE specifies that Type has a constructor and/or a destructor but can be moved in memory using memcpy().

This seems to pretty much match our previous definition of trivial relocatable type. The wording is however a bit imprecise: it does not clarify what “moved in memory” really means; that is, what sequence of operations is realized through the call to memcpy.

Also, to nitpick, the wording should be talking about a type that has copy or move constructors, assignments, and/or a destructor. If any of those are user-defined, then the type is no longer trivially copyable. We are already authorized by C++ to use memcpy for trivially copyable types; in fact, if a type is trivially copyable, then Qt automatically considers it to be trivially relocatable.

I thought it was Q_MOVABLE_TYPE?

In Qt 4 the macro was indeed called Q_MOVABLE_TYPE. That name however clashed with “move semantics” (introduced later in C++: Qt 4 was released in 2005!), so Qt wanted to move away from it, in order to avoid any confusion. While that spelling is still around, in Qt 5 and 6 one should prefer the name Q_RELOCATABLE_TYPE.

To nitpick, it should have really been called Q_TRIVIALLY_RELOCATABLE_TYPE, but that ship has sailed.

Does Qt only use trivial relocation for QVector reallocation?

No, it also uses it in a few other places. For instance:

  • QVarLengthArray is also a vector-like data structure, and uses a number of similar optimizations;
  • QVariant (Qt’s equivalent of std::any) allocates memory in order to store the object it contains, but it also has a “small object optimization”, where the held object is stored directly inside the QVariant object. SOO is enabled only if:
    1. the target object is small enough to fit (obviously);
    2. but also only if the object is trivially relocatable.

    In this sense, QVariant move constructor is type-erased; if SSO is in use, then the buffer is simply copied over into the new variant (as in, memcpy), and the old one is cleared.


There’s another instance of using trivial relocation which deserves a special discussion: certain algorithms are optimized in Qt for trivially relocatable types. For instance, inserting or erasing elements from a QVector of a trivially relocatable type is optimized as well.

This has some important consequences. Since this post is already overly long, I am going to analyze them in the next instalment of this series. 🙂

Thank you for reading so far.

Overview about the following 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 1) 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.

C++ & Rust, Servo Web & Qt, Qt Quick & JSON, Embedded – blogs & videos, Hotspot 1.5, Qt 6.7, GCompris in India, Events

 

&

96

 

Welcome to April’s Newsletter from KDAB

It must be spring! Blogs are sprouting out all over 🌱 😊

 

We kick off with Part 3 of Mixing C++ and Rust for Fun and Profit, followed by Embedding the Servo Web Engine in Qt and Recursive Instantiation with Qt Quick and JSON.

 

Then we offer two new blogs in our Embedded Development series, and, still on the embedded theme, some videos on Reducing Your Qt Embedded Development Cycle Time plus a report (with demos) from Embedded World 2024.

 

After that, there’s Hotspot 1.5 and the Qt 6.7 releases, and a different kind of glimpse into the open source contributor world, with GCompris in India.

 

Lastly, you will find important updates on coming events in 2024, including next month’s Oxidize conference in Berlin, where the focus is on Industrial Rust Use. Enjoy!

­

Mixing C++ and Rust for Fun and Profit

Part 3: How not to invent the wheel

by Loren Burkholder

In the two previous posts (Part 1 and Part 2), we looked at how to build bindings between C++ and Rust from scratch.

 

However, while building a binding generator from scratch is fun, it’s not necessarily an efficient way to integrate Rust into your C++ project.

 

Let’s look at some existing technologies for mixing C++ and Rust that you can easily deploy today.

 

Read on.

­

Embedding the Servo Web Engine in Qt

Using CXX-Qt to integrate a web rendering engine written in Rust

by Andrew Hayzen and Magnus Groß

The Rust ecosystem has been brewing up a new web rendering engine called Servo…. , now under the stewardship of the Linux Foundation….

 

At KDAB we managed to embed the Servo web engine inside Qt, by using our CXX-Qt library as a bridge between Rust and C++.

This means that we can now use Servo as an alternative to Chromium for webviews in Qt applications.

 

Read the blog.

More about CXX-Qt. About Servo.

­

Recursive Instantiation with Qt Quick and JSON – Factory Design Techniques, Part 1

by Javier O. Cordero Pérez

This is the first in a series of blogs Javier has written, in response to a request for an architecture for remote real time instantiation and updating of arbitrary QML components.

 

This entry shows how you can use a simple variation of the factory method pattern in QML for instantiating arbitrary components.

 

Read the blog.

­

Mastering the Embedded Update Process

– the first of our two new blogs on Embedded

by Andreas Holzammer

The importance of updating your product after it’s in the field cannot be overstated. Not only is it essential for customer satisfaction with feature updates and bug fixes, but also for addressing security vulnerabilities.

 

In this post, we’ll look at some key considerations and methodologies for updating embedded systems.

 

Read on.

­

Streamlining Strategies for Embedded Software Development

by Nathan Collins

Developing embedded software is notoriously difficult – how can we simplify the process? Fortunately, there are lots of techniques you can use daily . . .

 

Read on.

­

Reducing Qt Devpt. Embedded Cycle Time

Video series from Christoph Sterz

Christoph Sterz is known for his ability to make the impossible possible, producing top-class results, often on limited hardware. This month he added three more videos to this series, where he reveals some of the tricks he’s learnt over the years that save time on embedded. Check them out.

A 7 minute tutorial on Christoph’s favorite debugging tool for Qt apps.

Some surprising and useful ways you didn’t know GammaRay can help.

Find more tutorials for GammaRay here.

Learn how to bundle your executables and much more, in this 17 minute master class from Christoph.

­

Hotspot v1.5.0 released

The Linux perf GUI for Performance Analysis

One of KDAB’s most successful R&D projects gets a major update.

 

Hotspot v1.5.0 comes packed with a wealth of code cleanups, bug fixes and new functionality. Most notably, the disassembly view has been further improved with better searching, highlighting and faster performance.

 

Find out more.

Check out the changelog.

­

Qt 6.7 is released

with support from C++ 20

Just in case you hadn’t noticed, earlier this month Qt released its latest version, with lots of updates, big and small. Check out the details here or, if you want to go deeper, in the release notes.

 

Way down at the bottom of that page the many folk who made this release possible are listed, including around fifteen or so from KDAB. Hats off to all of them, and thanks!

­

GCompris in India

An example of its global spread, from a contributor’s perspective

The final episode in our series about this open source teaching aid, supported by KDE.

 

It’s amazing how, the things these folk get up to (mostly in their spare time), can cheer you up, even on a bad hair day 😉

 

Watch the video.

­

Events

Last month’s Embedded World 2024 in Nuremberg, Germany, went amazingly well, with attendee numbers up to pre-pandemic level and lots of visitors to KDAB’s booth.

Check out the 5 on-site videos about some of the demos we had on display –>.

 

More about our presence at Embedded World. We’ll be back next year!

 

A selection of events to come in 2024 follows.

Oxidize, 05/28-30

Berlin, Germany

Two day of applied insights from Rust innovators

We’re co-hosting, with Ferrous Systems and Slint. Come and talk to us about C++ and Rust.

 

With workshops on May 28th, and a talks program on May 29th-30th that includes Building an LLM pre-training data pipeline in Rust at Aleph Alpha, you don’t want to miss this one.

 

Check out the program and sign up!

FlutterCon, 07/3-5

Berlin, Germany

We’re silver sponsors and will be exhibiting and running a workshop. Speakers TBA after May 1st. Save the dates!

SIGGRAPH, 07/28-08/01

Denver, USA

Registration is now open! Check out the options and register. Best rates if you register by Friday, 17th May.

Qt Contributor Summit, 09/5-6

Würzburg, Germany

Attention Qt Developers! This event is now back-to-back with KDE Akademy in the same town. Save the dates!!

KDE Akademy, 09/7-12

Würzburg, Germany

The call for proposals is out for talks on Sept 7th and 8th. Submit a talk!

CppCon, 09/15-20

Aurora, Colorado, USA

Get ready for the biggest and best C++ conference in the world. Save the dates!

RustLab, 11/9-11

Florence, Italy

The call for proposals is open. There’ll be two kinds of talks: 25-min ‘inspirational’ and 40-min ‘deep-dives’, all in English and it happens in Florence. What’s not to love?!

Meeting C++ 2024, 11/14-16

Berlin, Germany & online

With C++ committee members likely to be passing through (and giving talks) en route to the ISO C++ meeting in Poland a week later, it’s not to be missed. Save the dates!

­

The post C++ & Rust, Servo Web & Qt, Qt Quick & JSON, Embedded – blogs & videos, Hotspot 1.5, Qt 6.7, GCompris in India, Events appeared first on KDAB.

Hotspot v1.5.0 released

Hotspot is a standalone GUI designed to provide a user-friendly interface for analyzing performance data. It takes a perf.data file, parses and evaluates its contents, and presents the results in a visually appealing and easily understandable manner. Our goal with Hotspot is to offer a modern alternative to perf report, making performance analysis on Linux systems more intuitive and efficient.

ChangeLog for Hotspot v1.5.0

It comes packed with a wealth of code cleanups, bug fixes and new functionality. Most notably, the disassembly view has been further improved with better searching, highlighting and faster performance.

Furthermore, we reworked the authentication mechanism to allow perf record to be run directly, with elevated priveleges, via pkexec, obsoleting the error prone old mechanism (see also https://nvd.nist.gov/vuln/detail/CVE-2023-28144).

We now also fully support Qt6 and KF6, while keeping compatibility with Qt5 and KF5. The AppImage below is still built with Qt5 but it might be the last time that we do this. The next version might become Qt6 only.

Many thanks to the various contributors who help build this software, both by writing code as well as reporting bugs.

To get a more detailed scope over all the changes in this new release, check out the full changelog on GitHub. More information about Hotspot can be obtained on its GitHub page or by watching this video.

Happy profiling everyone 🚀

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 Hotspot v1.5.0 released appeared first on KDAB.

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.

Use Compute Shader in Qt Quick

Use Compute Shader in Qt Quick

With this blog post, we introduce the QtQuickComputeItem - a Qt Quick item that allows you to easily integrate compute shader into your Qt Quick Code.
Compute
Shader are used to perform arbitrary computations on the GPU. For
example, the screenshot below shows a Qt Quick application that
generates Gray Scott Reaction Diffusion patterns.  The simulation is executed by a compute shader that is configured directly in QML.

Continue reading Use Compute Shader in Qt Quick at basysKom GmbH.

Q&A: How Do I Display Images in PySide6? — Using QLabel to easily add images to your applications

Adding images to your application is a common requirement, whether you're building an image/photo viewer, or just want to add some decoration to your GUI. Unfortunately, because of how this is done in Qt, it can be a little bit tricky to work out at first.

In this short tutorial, we will look at how you can insert an external image into your PySide6 application layout, using both code and Qt Designer.

Which widget to use?

Since you're wanting to insert an image you might be expecting to use a widget named QImage or similar, but that would make a bit too much sense! QImage is actually Qt's image object type, which is used to store the actual image data for use within your application. The widget you use to display an image is QLabel.

The primary use of QLabel is of course to add labels to a UI, but it also has the ability to display an image — or pixmap — instead, covering the entire area of the widget. Below we'll look at how to use QLabel to display a widget in your applications.

Using Qt Designer

First, create a MainWindow object in Qt Designer and add a "Label" to it. You can find Label at in Display Widgets in the bottom of the left hand panel. Drag this onto the QMainWindow to add it.

MainWindow with a single QLabel added MainWindow with a single QLabel added

Next, with the Label selected, look in the right hand QLabel properties panel for the pixmap property (scroll down to the blue region). From the property editor dropdown select "Choose File…" and select an image file to insert.

As you can see, the image is inserted, but the image is kept at its original size, cropped to the boundaries of theQLabel box. You need to resize the QLabel to be able to see the entire image.

In the same controls panel, click to enable scaledContents.

When scaledContents is enabled the image is resized to the fit the bounding box of the QLabel widget. This shows the entire image at all times, although it does not respect the aspect ratio of the image if you resize the widget.

You can now save your UI to file (e.g. as mainwindow.ui).

To view the resulting UI, we can use the standard application template below. This loads the .ui file we've created (mainwindow.ui) creates the window and starts up the application.

PySide6
import sys
from PySide6 import QtWidgets
from PySide6.QtUiTools import QUiLoader

loader = QUiLoader()
app = QtWidgets.QApplication(sys.argv)
window = loader.load("mainwindow.ui", None)
window.show()
app.exec()

Running the above code will create a window, with the image displayed in the middle.

QtDesigner application showing a Cat QtDesigner application showing a Cat

Using Code

Instead of using Qt Designer, you might also want to show an image in your application through code. As before we use a QLabel widget and add a pixmap image to it. This is done using the QLabel method .setPixmap(). The full code is shown below.

PySide6
import sys
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QMainWindow, QApplication, QLabel

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()
        self.title = "Image Viewer"
        self.setWindowTitle(self.title)

        label = QLabel(self)
        pixmap = QPixmap('cat.jpg')
        label.setPixmap(pixmap)
        self.setCentralWidget(label)
        self.resize(pixmap.width(), pixmap.height())


app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec())

The block of code below shows the process of creating the QLabel, creating a QPixmap object from our file cat.jpg (passed as a file path), setting this QPixmap onto the QLabel with .setPixmap() and then finally resizing the window to fit the image.

python
label = QLabel(self)
pixmap = QPixmap('cat.jpg')
label.setPixmap(pixmap)
self.setCentralWidget(label)
self.resize(pixmap.width(), pixmap.height())

Launching this code will show a window with the cat photo displayed and the window sized to the size of the image.

QMainWindow with Cat image displayed QMainWindow with Cat image displayed

Just as in Qt designer, you can call .setScaledContents(True) on your QLabel image to enable scaled mode, which resizes the image to fit the available space.

python
label = QLabel(self)
pixmap = QPixmap('cat.jpg')
label.setPixmap(pixmap)
label.setScaledContents(True)
self.setCentralWidget(label)
self.resize(pixmap.width(), pixmap.height())

Notice that you set the scaled state on the QLabel widget and not the image pixmap itself.

Conclusion

In this quick tutorial we've covered how to insert images into your Qt UIs using QLabel both from Qt Designer and directly from PySide6 code.

Drag & Drop Widgets with PySide6 — Sort widgets visually with drag and drop in a container

I had an interesting question from a reader of my PySide6 book, about how to handle dragging and dropping of widgets in a container showing the dragged widget as it is moved.

I'm interested in managing movement of a QWidget with mouse in a container. I've implemented the application with drag & drop, exchanging the position of buttons, but I want to show the motion of QPushButton, like what you see in Qt Designer. Dragging a widget should show the widget itself, not just the mouse pointer.

First, we'll implement the simple case which drags widgets without showing anything extra. Then we can extend it to answer the question. By the end of this quick tutorial we'll have a generic drag drop implementation which looks like the following.

Drag & Drop Widgets

We'll start with a simple application which creates a window using QWidget and places a series of QPushButton widgets into it.

You can substitute QPushButton for any other widget you like, e.g. QLabel. Any widget can have drag behavior implemented on it, although some input widgets will not work well as we capture the mouse events for the drag.

python
from PySide6.QtWidgets import QApplication, QHBoxLayout, QPushButton, QWidget


class Window(QWidget):
    def __init__(self):
        super().__init__()

        self.blayout = QHBoxLayout()
        for l in ["A", "B", "C", "D"]:
            btn = QPushButton(l)
            self.blayout.addWidget(btn)

        self.setLayout(self.blayout)


app = QApplication([])
w = Window()
w.show()

app.exec()


If you run this you should see something like this.

Widgets in a layout The series of QPushButton widgets in a horizontal layout.

Here we're creating a window, but the Window widget is subclassed from QWidget, meaning you can add this widget to any other layout. See later for an example of a generic object sorting widget.

QPushButton objects aren't usually draggable, so to handle the mouse movements and initiate a drag we need to implement a subclass. We can add the following to the top of the file.

python
from PySide6.QtCore import QMimeData, Qt
from PySide6.QtGui import QDrag
from PySide6.QtWidgets import QApplication, QHBoxLayout, QPushButton, QWidget


class DragButton(QPushButton):
    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)
            drag.exec(Qt.DropAction.MoveAction)


We implement a mouseMoveEvent which accepts the single e parameter of the event. We check to see if the left mouse button is pressed on this event -- as it would be when dragging -- and then initiate a drag. To start a drag, we create a QDrag object, passing in self to give us access later to the widget that was dragged. We also must pass in mime data. This is used for including information about what is dragged, particularly for passing data between applications. However, as here, it is fine to leave this empty.

Finally, we initiate a drag by calling drag.exec_(Qt.MoveAction). As with dialogs exec_() starts a new event loop, blocking the main loop until the drag is complete. The parameter Qt.MoveAction tells the drag handler what type of operation is happening, so it can show the appropriate icon tip to the user.

You can update the main window code to use our new DragButton class as follows.

python
class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setAcceptDrops(True)

        self.blayout = QHBoxLayout()
        for l in ["A", "B", "C", "D"]:
            btn = DragButton(l)
            self.blayout.addWidget(btn)

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()

If you run the code now, you can drag the buttons, but you'll notice the drag is forbidden.

Drag forbidden Dragging of the widget starts but is forbidden.

What's happening? The mouse movement is being detected by our DragButton object and the drag started, but the main window does not accept drag & drop.

To fix this we need to enable drops on the window and implement dragEnterEvent to actually accept them.

python
class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setAcceptDrops(True)

        self.blayout = QHBoxLayout()
        for l in ["A", "B", "C", "D"]:
            btn = DragButton(l)
            self.blayout.addWidget(btn)

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()


If you run this now, you'll see the drag is now accepted and you see the move icon. This indicates that the drag has started and been accepted by the window we're dragging onto. The icon shown is determined by the action we pass when calling drag.exec_().

Drag accepted Dragging of the widget starts and is accepted, showing a move icon.

Releasing the mouse button during a drag drop operation triggers a dropEvent on the widget you're currently hovering the mouse over (if it is configured to accept drops). In our case that's the window. To handle the move we need to implement the code to do this in our dropEvent method.

The drop event contains the position the mouse was at when the button was released & the drop triggered. We can use this to determine where to move the widget to.

To determine where to place the widget, we iterate over all the widgets in the layout, until we find one who's x position is greater than that of the mouse pointer. If so then when insert the widget directly to the left of this widget and exit the loop.

If we get to the end of the loop without finding a match, we must be dropping past the end of the existing items, so we increment n one further (in the else: block below).

python
    def dropEvent(self, e):
        pos = e.position()
        widget = e.source()
        self.blayout.removeWidget(widget)

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if pos.x() < w.x():
                # We didn't drag past this widget.
                # insert to the left of it.
                break
        else:
            # We aren't on the left hand side of any widget,
            # so we're at the end. Increment 1 to insert after.
            n += 1

        self.blayout.insertWidget(n, widget)

        e.accept()

The effect of this is that if you drag 1 pixel past the start of another widget the drop will happen to the right of it, which is a bit confusing. To fix this we can adjust the cut off to use the middle of the widget using if pos.x() < w.x() + w.size().width() // 2: -- that is x + half of the width.

python
    def dropEvent(self, e):
        pos = e.position()
        widget = e.source()
        self.blayout.removeWidget(widget)

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if pos.x() < w.x() + w.size().width() // 2:
                # We didn't drag past this widget.
                # insert to the left of it.
                break
        else:
            # We aren't on the left hand side of any widget,
            # so we're at the end. Increment 1 to insert after.
            n += 1

        self.blayout.insertWidget(n, widget)

        e.accept()


The complete working drag-drop code is shown below.

python
from PySide6.QtCore import QMimeData, Qt
from PySide6.QtGui import QDrag
from PySide6.QtWidgets import QApplication, QHBoxLayout, QPushButton, QWidget


class DragButton(QPushButton):
    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)
            drag.exec(Qt.DropAction.MoveAction)


class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setAcceptDrops(True)

        self.blayout = QHBoxLayout()
        for l in ["A", "B", "C", "D"]:
            btn = DragButton(l)
            self.blayout.addWidget(btn)

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()

    def dropEvent(self, e):
        pos = e.position()
        widget = e.source()
        self.blayout.removeWidget(widget)

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if pos.x() < w.x() + w.size().width() // 2:
                # We didn't drag past this widget.
                # insert to the left of it.
                break
        else:
            # We aren't on the left hand side of any widget,
            # so we're at the end. Increment 1 to insert after.
            n += 1

        self.blayout.insertWidget(n, widget)

        e.accept()


app = QApplication([])
w = Window()
w.show()

app.exec()


Visual Drag & Drop

We now have a working drag & drop implementation. Next we'll move onto improving the UX by showing the drag visually. First we'll add support for showing the button being dragged next to the mouse point as it is dragged. That way the user knows exactly what it is they are dragging.

Qt's QDrag handler natively provides a mechanism for showing dragged objects which we can use. We can update our DragButton class to pass a pixmap image to QDrag and this will be displayed under the mouse pointer as the drag occurs. To show the widget, we just need to get a QPixmap of the widget we're dragging.

python
from PySide6.QtCore import QMimeData, Qt
from PySide6.QtGui import QDrag
from PySide6.QtWidgets import QApplication, QHBoxLayout, QPushButton, QWidget


class DragButton(QPushButton):
    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)
            drag.exec(Qt.DropAction.MoveAction)


To create the pixmap we create a QPixmap object passing in the size of the widget this event is fired on with self.size(). This creates an empty pixmap which we can then pass into self.render to render -- or draw -- the current widget onto it. That's it. Then we set the resulting pixmap on the drag object.

If you run the code with this modification you'll see something like the following --

Drag visual Dragging of the widget showing the dragged widget.

Generic Drag & Drop Container

We now have a working drag and drop behavior implemented on our window. We can take this a step further and implement a generic drag drop widget which allows us to sort arbitrary objects. In the code below we've created a new widget DragWidget which can be added to any window.

You can add items -- instances of DragItem -- which you want to be sorted, as well as setting data on them. When items are re-ordered the new order is emitted as a signal orderChanged.

python
from PySide6.QtCore import QMimeData, Qt, Signal
from PySide6.QtGui import QDrag, QPixmap
from PySide6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QVBoxLayout,
    QWidget,
)


class DragItem(QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setContentsMargins(25, 5, 25, 5)
        self.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setStyleSheet("border: 1px solid black;")
        # Store data separately from display label, but use label for default.
        self.data = self.text()

    def set_data(self, data):
        self.data = data

    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)

            pixmap = QPixmap(self.size())
            self.render(pixmap)
            drag.setPixmap(pixmap)

            drag.exec(Qt.DropAction.MoveAction)


class DragWidget(QWidget):
    """
    Generic list sorting handler.
    """

    orderChanged = Signal(list)

    def __init__(self, *args, orientation=Qt.Orientation.Vertical, **kwargs):
        super().__init__()
        self.setAcceptDrops(True)

        # Store the orientation for drag checks later.
        self.orientation = orientation

        if self.orientation == Qt.Orientation.Vertical:
            self.blayout = QVBoxLayout()
        else:
            self.blayout = QHBoxLayout()

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()

    def dropEvent(self, e):
        pos = e.position()
        widget = e.source()
        self.blayout.removeWidget(widget)

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if self.orientation == Qt.Orientation.Vertical:
                # Drag drop vertically.
                drop_here = pos.y() < w.y() + w.size().height() // 2
            else:
                # Drag drop horizontally.
                drop_here = pos.x() < w.x() + w.size().width() // 2

            if drop_here:
                break

        else:
            # We aren't on the left hand/upper side of any widget,
            # so we're at the end. Increment 1 to insert after.
            n += 1

        self.blayout.insertWidget(n, widget)
        self.orderChanged.emit(self.get_item_data())

        e.accept()

    def add_item(self, item):
        self.blayout.addWidget(item)

    def get_item_data(self):
        data = []
        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            data.append(w.data)
        return data


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.drag = DragWidget(orientation=Qt.Orientation.Vertical)
        for n, l in enumerate(["A", "B", "C", "D"]):
            item = DragItem(l)
            item.set_data(n)  # Store the data.
            self.drag.add_item(item)

        # Print out the changed order.
        self.drag.orderChanged.connect(print)

        container = QWidget()
        layout = QVBoxLayout()
        layout.addStretch(1)
        layout.addWidget(self.drag)
        layout.addStretch(1)
        container.setLayout(layout)

        self.setCentralWidget(container)


app = QApplication([])
w = MainWindow()
w.show()

app.exec()


Generic drag drop horizontal Generic drag-drop sorting in horizontal orientation.

You'll notice that when creating the item, you can set the label by passing it in as a parameter (just like for a normal QLabel which we've subclassed from). But you can also set a data value, which is the internal value of this item -- this is what will be emitted when the order changes, or if you call get_item_data yourself. This separates the visual representation from what is actually being sorted, meaning you can use this to sort anything not just strings.

In the example above we're passing in the enumerated index as the data, so dragging will output (via the print connected to orderChanged) something like:

python
[1, 0, 2, 3]
[1, 2, 0, 3]
[1, 0, 2, 3]
[1, 2, 0, 3]

If you remove the item.set_data(n) you'll see the labels emitted on changes.

python
['B', 'A', 'C', 'D']
['B', 'C', 'A', 'D']

We've also implemented orientation onto the DragWidget using the Qt built in flags Qt.Orientation.Vertical or Qt.Orientation.Horizontal. This setting this allows you sort items either vertically or horizontally -- the calculations are handled for both directions.

Generic drag drop vertical Generic drag-drop sorting in vertical orientation.

Adding a Visual Drop Target

If you experiment with the drag-drop tool above you'll notice that it doesn't feel completely intuitive. When dragging you don't know where an item will be inserted until you drop it. If it ends up in the wrong place, you'll then need to pick it up and re-drop it again, using guesswork to get it right.

With a bit of practice you can get the hang of it, but it would be nicer to make the behavior immediately obvious for users. Many drag-drop interfaces solve this problem by showing a preview of where the item will be dropped while dragging -- either by showing the item in the place where it will be dropped, or showing some kind of placeholder.

In this final section we'll implement this type of drag and drop preview indicator.

The first step is to define our target indicator. This is just another label, which in our example is empty, with custom styles applied to make it have a solid "shadow" like background. This makes it obviously different to the items in the list, so it stands out as something distinct.

python
from PySide6.QtCore import QMimeData, Qt, Signal
from PySide6.QtGui import QDrag, QPixmap
from PySide6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QVBoxLayout,
    QWidget,
)


class DragTargetIndicator(QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setContentsMargins(25, 5, 25, 5)
        self.setStyleSheet(
            "QLabel { background-color: #ccc; border: 1px solid black; }"
        )



We've copied the contents margins from the items in the list. If you change your list items, remember to also update the indicator dimensions to match.

The drag item is unchanged, but we need to implement some additional behavior on our DragWidget to add the target, control showing and moving it.

First we'll add the drag target indicator to the layout on our DragWidget. This is hidden to begin with, but will be shown during the drag.

python
class DragWidget(QWidget):
    """
    Generic list sorting handler.
    """

    orderChanged = Signal(list)

    def __init__(self, *args, orientation=Qt.Orientation.Vertical, **kwargs):
        super().__init__()
        self.setAcceptDrops(True)

        # Store the orientation for drag checks later.
        self.orientation = orientation

        if self.orientation == Qt.Orientation.Vertical:
            self.blayout = QVBoxLayout()
        else:
            self.blayout = QHBoxLayout()

        # Add the drag target indicator. This is invisible by default,
        # we show it and move it around while the drag is active.
        self._drag_target_indicator = DragTargetIndicator()
        self.blayout.addWidget(self._drag_target_indicator)
        self._drag_target_indicator.hide()

        self.setLayout(self.blayout)

Next we modify the DragWidget.dragMoveEvent to show the drag target indicator. We show it by inserting it into the layout and then calling .show -- inserting a widget which is already in a layout will move it. We also hide the original item which is being dragged.

In the earlier examples we determined the position on drop by removing the widget being dragged, and then iterating over what is left. Because we now need to calculate the drop location before the drop, we take a different approach.

If we wanted to do it the same way, we'd need to remove the item on drag start, hold onto it and implement re-inserting at it's old position on drag fail. That's a lot of work.

Instead, the dragged item is left in place and hidden during move.

python
    def dragMoveEvent(self, e):
        # Find the correct location of the drop target, so we can move it there.
        index = self._find_drop_location(e)
        if index is not None:
            # Inserting moves the item if its alreaady in the layout.
            self.blayout.insertWidget(index, self._drag_target_indicator)
            # Hide the item being dragged.
            e.source().hide()
            # Show the target.
            self._drag_target_indicator.show()
        e.accept()


The method self._find_drop_location finds the index where the drag target will be shown (or the item dropped when the mouse released). We'll implement that next.

The calculation of the drop location follows the same pattern as before. We iterate over the items in the layout and calculate whether our mouse drop location is to the left of each widget. If it isn't to the left of any widget, we drop on the far right.

python
    def _find_drop_location(self, e):
        pos = e.position()
        spacing = self.blayout.spacing() / 2

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()

            if self.orientation == Qt.Orientation.Vertical:
                # Drag drop vertically.
                drop_here = (
                    pos.y() >= w.y() - spacing
                    and pos.y() <= w.y() + w.size().height() + spacing
                )
            else:
                # Drag drop horizontally.
                drop_here = (
                    pos.x() >= w.x() - spacing
                    and pos.x() <= w.x() + w.size().width() + spacing
                )

            if drop_here:
                # Drop over this target.
                break

        return n

The drop location n is returned for use in the dragMoveEvent to place the drop target indicator.

Next wee need to update the get_item_data handler to ignore the drop target indicator. To do this we check w against self._drag_target_indicator and skip if it is the same. With this change the method will work as expected.

python
    def get_item_data(self):
        data = []
        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if w != self._drag_target_indicator:
                # The target indicator has no data.
                data.append(w.data)
        return data


If you run the code a this point the drag behavior will work as expected. But if you drag the widget outside of the window and drop you'll notice a problem: the target indicator will stay in place, but dropping the item won't drop the item in that position (the drop will be cancelled).

To fix that we need to implement a dragLeaveEvent which hides the indicator.

python
    def dragLeaveEvent(self, e):
        self._drag_target_indicator.hide()
        e.accept()

With those changes, the drag-drop behavior should be working as intended. The complete code is shown below.

python
from PySide6.QtCore import QMimeData, Qt, Signal
from PySide6.QtGui import QDrag, QPixmap
from PySide6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QVBoxLayout,
    QWidget,
)


class DragTargetIndicator(QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setContentsMargins(25, 5, 25, 5)
        self.setStyleSheet(
            "QLabel { background-color: #ccc; border: 1px solid black; }"
        )


class DragItem(QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setContentsMargins(25, 5, 25, 5)
        self.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setStyleSheet("border: 1px solid black;")
        # Store data separately from display label, but use label for default.
        self.data = self.text()

    def set_data(self, data):
        self.data = data

    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)

            pixmap = QPixmap(self.size())
            self.render(pixmap)
            drag.setPixmap(pixmap)

            drag.exec(Qt.DropAction.MoveAction)
            self.show() # Show this widget again, if it's dropped outside.


class DragWidget(QWidget):
    """
    Generic list sorting handler.
    """

    orderChanged = Signal(list)

    def __init__(self, *args, orientation=Qt.Orientation.Vertical, **kwargs):
        super().__init__()
        self.setAcceptDrops(True)

        # Store the orientation for drag checks later.
        self.orientation = orientation

        if self.orientation == Qt.Orientation.Vertical:
            self.blayout = QVBoxLayout()
        else:
            self.blayout = QHBoxLayout()

        # Add the drag target indicator. This is invisible by default,
        # we show it and move it around while the drag is active.
        self._drag_target_indicator = DragTargetIndicator()
        self.blayout.addWidget(self._drag_target_indicator)
        self._drag_target_indicator.hide()

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()

    def dragLeaveEvent(self, e):
        self._drag_target_indicator.hide()
        e.accept()

    def dragMoveEvent(self, e):
        # Find the correct location of the drop target, so we can move it there.
        index = self._find_drop_location(e)
        if index is not None:
            # Inserting moves the item if its alreaady in the layout.
            self.blayout.insertWidget(index, self._drag_target_indicator)
            # Hide the item being dragged.
            e.source().hide()
            # Show the target.
            self._drag_target_indicator.show()
        e.accept()

    def dropEvent(self, e):
        widget = e.source()
        # Use drop target location for destination, then remove it.
        self._drag_target_indicator.hide()
        index = self.blayout.indexOf(self._drag_target_indicator)
        if index is not None:
            self.blayout.insertWidget(index, widget)
            self.orderChanged.emit(self.get_item_data())
            widget.show()
            self.blayout.activate()
        e.accept()

    def _find_drop_location(self, e):
        pos = e.position()
        spacing = self.blayout.spacing() / 2

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()

            if self.orientation == Qt.Orientation.Vertical:
                # Drag drop vertically.
                drop_here = (
                    pos.y() >= w.y() - spacing
                    and pos.y() <= w.y() + w.size().height() + spacing
                )
            else:
                # Drag drop horizontally.
                drop_here = (
                    pos.x() >= w.x() - spacing
                    and pos.x() <= w.x() + w.size().width() + spacing
                )

            if drop_here:
                # Drop over this target.
                break

        return n

    def add_item(self, item):
        self.blayout.addWidget(item)

    def get_item_data(self):
        data = []
        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if w != self._drag_target_indicator:
                # The target indicator has no data.
                data.append(w.data)
        return data


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.drag = DragWidget(orientation=Qt.Orientation.Vertical)
        for n, l in enumerate(["A", "B", "C", "D"]):
            item = DragItem(l)
            item.set_data(n)  # Store the data.
            self.drag.add_item(item)

        # Print out the changed order.
        self.drag.orderChanged.connect(print)

        container = QWidget()
        layout = QVBoxLayout()
        layout.addStretch(1)
        layout.addWidget(self.drag)
        layout.addStretch(1)
        container.setLayout(layout)

        self.setCentralWidget(container)


app = QApplication([])
w = MainWindow()
w.show()

app.exec()


If you run this example on macOS you may notice that the widget drag preview (the QPixmap created on DragItem) is a bit blurry. On high-resolution screens you need to set the device pixel ratio and scale up the pixmap when you create it. Below is a modified DragItem class which does this.

Update DragItem to support high resolution screens.

python
class DragItem(QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setContentsMargins(25, 5, 25, 5)
        self.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setStyleSheet("border: 1px solid black;")
        # Store data separately from display label, but use label for default.
        self.data = self.text()

    def set_data(self, data):
        self.data = data

    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)

            # Render at x2 pixel ratio to avoid blur on Retina screens.
            pixmap = QPixmap(self.size().width() * 2, self.size().height() * 2)
            pixmap.setDevicePixelRatio(2)
            self.render(pixmap)
            drag.setPixmap(pixmap)

            drag.exec(Qt.DropAction.MoveAction)
            self.show() # Show this widget again, if it's dropped outside.

That's it! We've created a generic drag-drop handled which can be added to any projects where you need to be able to reposition items within a list. You should feel free to experiment with the styling of the drag items and targets as this won't affect the behavior.