From Theory to Practice: Qt’s Role in Aalto University’s C++ Course
Bridging the gap between academia and industry is key to fostering the next generation of software developers. Qt's University & Talent Network, launched in June 2024, is committed to equipping students with real-world skills. Through strategic university collaborations, Qt provides hands-on learning experiences that bring programming concepts to life.

Qt GUI on STM32: Building Efficient Embedded Applications
Products from the STM32 family have been a popular target for embedded Qt applications for quite a long time. One […]
Qt Creator 16 released
We are happy to announce the release of Qt Creator 16!

Extended Security Maintenance for Qt 5.15 begins May 2025
Standard support for Qt 5.15 will end after 26th of May 2025, as communicated earlier. After the support period, the release will be in EoS (End of Support) state and will only be supported through additional services, including Extended Security Maintenance for Qt 5.15, Extended Support, and Professional Services. Customers distributing applications and embedded devices using Qt 5.15 software should evaluate how to access technical support and maintenance patches which may include security fixes. Standard commercials terms still apply after End of Support.

Model/View Drag and Drop in Qt - Part 2
Model/View Drag and Drop in Qt - Part 2
In the previous blog, you learned all about moving items within a single view, to reorder them.
In part 2, we are still talking about moving items, and still about inserting them between existing items (never overwriting items) but this time the user can move items from one view to another. A typical use case is a list of available items on the left, and a list of selected items on the right (one concrete example would be to let the user customize which buttons should appear in a toolbar). This also often includes reordering items in the right-side list, the good news being that this comes for free (no extra code needed).

Moving a row between treeviews, step 1

Moving a row between treeviews, step 2

Moving a row between treeviews, step 3
With Model/View separation
Example code for flat models and example code for tree models.
Setting up the view on the drag side
To allow dragging items out of the view, make sure to do the following:
☑ Call view->setDragDropMode(QAbstractItemView::DragOnly)
(or DragDrop
if it should support both).
☑ Call view->setDragDropOverwriteMode(false)
so that QTableView
calls removeRows when moving rows, rather than just clearing their cells
☑ Call view->setDefaultDropAction(Qt::MoveAction)
so it's a move and not a copy
Setting up the model on the drag side
To implement dragging items out of a model, you need to implement the following:
class CountryModel : public QAbstractTableModel
{
~~~
Qt::ItemFlags flags(const QModelIndex &index) const override
{
if (!index.isValid())
return {}; // depending on whether you want drops as well (next section)
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
}
// the default is "return supportedDropActions()", let's be explicit
Qt::DropActions supportedDragActions() const override { return Qt::MoveAction; }
QMimeData *mimeData(const QModelIndexList &indexes) const override; // see below
bool removeRows(int position, int rows, const QModelIndex &parent) override; // see below
};
More precisely, the check-list is the following:
☑ Reimplement flags()
to add Qt::ItemIsDragEnabled
in the case of a valid index
☑ Reimplement supportedDragActions()
to return Qt::MoveAction
☑ Reimplement mimeData()
to serialize the complete data for the dragged items. If the views are always in the same process, you can get away with serializing only node pointers (if you have that, e.g. for tree models) and application PID (to refuse dropping onto another process). Otherwise you can encode the actual data, like this:
QMimeData *CountryModel::mimeData(const QModelIndexList &indexes) const
{
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
for (const QModelIndex &index : indexes) {
// This calls operator<<(QDataStream &stream, const CountryData &countryData), which you must implement
stream << m_data.at(index.row());
}
QMimeData *mimeData = new QMimeData;
mimeData->setData(s_mimeType, encodedData);
return mimeData;
}
s_mimeType
is the name of the type of data (make up a name, it usually starts with application/x-
)
☑ Reimplement removeRows()
, it will be called after a successful drop. For instance, if your data is in a vector called m_data
, the implementation would look like this:
bool CountryModel::removeRows(int position, int rows, const QModelIndex &parent)
{
beginRemoveRows(parent, position, position + rows - 1);
for (int row = 0; row < rows; ++row)
m_data.removeAt(position);
endRemoveRows();
return true;
}
Setting up the view on the drop side
☑ Call view->setDragDropMode(QAbstractItemView::DragDrop)
(already done if both views should support dragging and dropping)
Setting up the model on the drop side
To implement dropping items into a model (between existing items), you need to implement the following:
class DropModel : public QAbstractTableModel
{
~~~
Qt::ItemFlags flags(const QModelIndex &index) const override
{
if (!index.isValid())
return Qt::ItemIsDropEnabled;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable; // and optionally Qt::ItemIsDragEnabled (previous section)
}
// the default is "copy only", change it
Qt::DropActions supportedDropActions() const override { return Qt::MoveAction; }
QStringList mimeTypes() const override { return {QString::fromLatin1(s_mimeType)}; }
bool dropMimeData(const QMimeData *mimeData, Qt::DropAction action,
int row, int column, const QModelIndex &parent) override; // see below
};
☑ Reimplement supportedDropActions()
to return Qt::MoveAction
☑ Reimplement flags()
For a valid index, make sure Qt::ItemIsDropEnabled
is NOT set (except for tree models where we need to drop onto items in order to insert a first child).
For the invalid index, add Qt::ItemIsDropEnabled
, to allow dropping between items.
☑ Reimplement mimeTypes()
and return the name of the MIME type used by the mimeData()
function on the drag side.
☑ Reimplement dropMimeData()
to deserialize the data and insert new rows.
In the special case of in-process tree models, clone the dragged nodes.
In both cases, once you're done, return true, so that the drag side then deletes the dragged rows by calling removeRows()
on its model.
bool DropModel::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
~~~ // safety checks, see full example code
if (row == -1) // drop into empty area = append
row = rowCount(parent);
// decode data
const QByteArray encodedData = mimeData->data(s_mimeType);
QDataStream stream(encodedData);
QVector<CountryData> newCountries;
while (!stream.atEnd()) {
CountryData countryData;
stream >> countryData;
newCountries.append(countryData);
}
// insert new countries
beginInsertRows(parent, row, row + newCountries.count() - 1);
for (const CountryData &countryData : newCountries)
m_data.insert(row++, countryData);
endInsertRows();
return true; // let the view handle deletion on the source side by calling removeRows there
}
Using item widgets
Example code can be found following this link.
For all kinds of widgets
On the "drag" side:
☑ Call widget->setDragDropMode(QAbstractItemView::DragOnly)
or DragDrop
if it should support both
☑ Call widget->setDefaultDropAction(Qt::MoveAction)
so the drag starts as a move right away
On the "drop" side:
☑ Call widget->setDragDropMode(QAbstractItemView::DropOnly)
or DragDrop
if it should support both
☑ Reimplement supportedDropActions()
to return only Qt::MoveAction
Additional requirements for QTableWidget
When using QTableWidget
, in addition to the common steps above you need to:
On the "drag" side:
☑ Call item->setFlags(item->flags() & ~Qt::ItemIsDropEnabled);
for each item, to disable dropping onto items.
☑ Call widget->setDragDropOverwriteMode(false)
so that after a move the rows are removed rather than cleared
On the "drop" side:
☑ Call widget->setDragDropOverwriteMode(false)
so that it inserts rows instead of replacing cells (the default is false
for the other views anyway)
☑ Another problem is that the items created by a drop will automatically get the Qt::ItemIsDropEnabled
flag, which you don't want. To solve this, use widget->setItemPrototype()
with an item that has the right flags (see the example).
Additional requirements for QTreeWidget
When using QTreeWidget
, you cannot disable dropping onto items (which creates a child of the item).
You could call item->setFlags(item->flags() & ~Qt::ItemIsDropEnabled);
on your own items, but when QTreeWidget
creates new items upon a drop, you cannot prevent them from having the flag Qt::ItemIsDropEnabled
set. The prototype solution used above for QTableWidget
doesn't exist for QTreeWidget
.
This means, if you want to let the user build and reorganize an actual tree, you can use QTreeWidget
. But if you just want a flat multi-column list, then you should use QTreeView
(see previous section on model/view separation).
Addendum: Move/copy items between views
If the user should be able to choose between copying and moving items, follow the previous section and make the following changes.
With Model/View separation
On the "drag" side:
☑ Call view->setDefaultDropAction(...)
to choose whether the default should be move or copy. The user can press Shift to force a move, and Ctrl to force a copy.
☑ Reimplement supportedDragActions()
in the model to return Qt::MoveAction | Qt::CopyAction
On the "drop" side:
☑ Reimplement supportedDropActions()
in the model to return Qt::MoveAction | Qt::CopyAction
The good news is that there's nothing else to do.
Using item widgets
On the "drag" side:
☑ Call widget->setDefaultDropAction(...)
to choose whether the default should be move or copy. The user can press Shift to force a move, and Ctrl to force a copy.
Until Qt 6.10 there was no setSupportedDragActions()
method in the item widget classes (that was QTBUG-87465, I implemented it for 6.10). Fortunately the default behavior is to use what supportedDropActions()
returns so if you just want move and copy in both, reimplementing supportedDropActions()
is enough.
On the "drop" side:
☑ Reimplement supportedDropActions()
in the item widget class to return Qt::MoveAction | Qt::CopyAction
The good news is that there's nothing else to do.
Improvements to Qt
While writing and testing these code examples, I improved the following things in Qt:
- QTBUG-1387 "Drag and drop multiple columns with item views. Dragging a row and dropping it in a column > 0 creates multiple rows.", fixed in 6.8.1
- QTBUG-36831 "Drop indicator painted as single pixel when not shown" fixed in 6.8.1
- QTBUG-87465 ItemWidgets: add supportedDragActions()/setSupportedDragActions(), implemented in 6.10
Conclusion
In the next blog post of this series, you will learn how to move (or copy) onto existing items, rather than between them.
The post Model/View Drag and Drop in Qt - Part 2 appeared first on KDAB.
Qt Contributor Summit 2025: May 7-8 (+9), Registration, Venue, and More!
Enriching Automotive UX: Combining Qt, Figma and Third-Party 3D Engines
Over the past 5 years or so it has become abundantly apparent that automotive consumers expect that when they enter their car they are greeted by 2D controls that are not only responsive and intuitive, but also by 3D graphics that are functional and fluid. From simple seat controls, to the complexities of ADAS scenes driven by real time vehicle sensor data, three-dimensional interfaces are paramount in assisting end users to understand their vehicle's status and its location on the roadway with respect to its surroundings. Companies like Rivian and Toyota are leveling up their driving experience by leveraging 3D engines that were once delegated to strictly video game development. The introduction of gamification to the automotive user experience has also caused a seismic shift in hardware, with many cars now requiring the GPU compute power to process vehicle sensor data and visualize it in 3D as well. However, 3D rendering in automotive is not all strictly business; for example, Rivian's cel-shaded take on 3D is a playful one that hearkens back to their idyllic tagline: "Keep the world adventurous forever". Ford is enhancing their user experience in the Mustang by using 3D to visualize vehicle modes, track mode, and even to create retro-inspired clusters.

Qt Framework and its Hidden Gems for Medical Software - Why is it a Perfect Choice?
3D Rendering Solutions in Qt – an Overview
Qt’s 3D offering is changing, so we decided to look at different options for rendering 3D content in Qt.
Continue reading 3D Rendering Solutions in Qt – an Overview at basysKom GmbH.
Qt CAN Bus Example – How to start?
I welcome you to another blog post. The last one discussed a form of communication between devices using Qt Bluetooth Low Energy. […]
FOSDEM 2025: Our Qt Highlights
Qt Creator 16 RC released
We are happy to announce the release of Qt Creator 16 RC!

PySide6, Qt for Python
In this article, we’re going to have a look at Qt for Python, how it integrates with Qt Creator, and why you might want to consider using it, too.
Model/View Drag and Drop in Qt - Part 1
Model/View Drag and Drop in Qt - Part 1
This blog series is all about implementing drag-and-drop in the Qt model/view framework. In addition to complete code examples, you'll find checklists that you can go through to make sure that you did not forget anything in your own implementation, when something isn't working as expected.
At first, we are going to look at Drag and Drop within a single view, to change the order of the items. The view can be a list, a table or a tree, there are very little differences in what you have to do.

Moving a row in a tableview, step 1

Moving a row in a tableview, step 2

Moving a row in a tableview, step 3
The main question, however, is whether you are using QListView/QTableView/QTreeView on top of a custom item model, or QListWidget/QTableWidget/QTreeWidget with items in them. Let's explore each one in turn.
With Model/View separation
The code being discussed here is extracted from the example. That example features a flat model, while this example features a tree model. The checklist is the same for these two cases.
Setting up the view
☑ Call view->setDragDropMode(QAbstractItemView::InternalMove)
to enable the mode where only moving within the same view is allowed
☑ When using QTableView
, call view->setDragDropOverwriteMode(false)
so that it inserts rows instead of replacing cells (the default is false
for the other views anyway)
Adding drag-n-drop support to the model

Reorderable ListView

Reorderable TableView
For a model being used in QListView or QTableView, all you need is something like this:
class CountryModel : public QAbstractTableModel
{
~~~
Qt::ItemFlags flags(const QModelIndex &index) const override
{
if (!index.isValid())
return Qt::ItemIsDropEnabled; // allow dropping between items
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
}
// the default is "copy only", change it
Qt::DropActions supportedDropActions() const override { return Qt::MoveAction; }
// the default is "return supportedDropActions()", let's be explicit
Qt::DropActions supportedDragActions() const override { return Qt::MoveAction; }
QStringList mimeTypes() const override { return {QString::fromLatin1(s_mimeType)}; }
bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) override; // see below
};
The checklist for the changes you need to make in your model is therefore the following:
☑ Reimplement flags()
For a valid index, add Qt::ItemIsDragEnabled
and make sure Qt::ItemIsDropEnabled
is NOT set (except for tree models where we need to drop onto items in order to insert a first child). \
☑ Reimplement mimeTypes()
and make up a name for the mimetype (usually starting with application/x-
)
☑ Reimplement supportedDragActions()
to return Qt::MoveAction
☑ Reimplement supportedDropActions()
to return Qt::MoveAction
☑ Reimplement moveRows()
Note that this approach is only valid when using QListView
or, assuming Qt >= 6.8.0, QTableView
- see the following sections for details.
In a model that encapsulates a QVector
called m_data
, the implementation of moveRows
can look like this:
bool CountryModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
{
if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild))
return false; // invalid move, e.g. no-op (move row 2 to row 2 or to row 3)
for (int i = 0; i < count; ++i) {
m_data.move(sourceRow + i, destinationChild + (sourceRow > destinationChild ? 0 : -1));
}
endMoveRows();
return true;
}
QTreeView does not call moveRows

Reorderable treeview

Reorderable treeview with a tree model
QTreeView does not (yet?) call moveRows
in the model, so you need to:
☑ Reimplement mimeData()
to encode row numbers for flat models, and node pointers for tree models
☑ Reimplement dropMimeData()
to implement the move and return false (meaning: all done)
Note that this means a move is in fact an insertion and a deletion, so the selection isn't automatically updated to point to the moved row(s).
QTableView in Qt < 6.8.0
I implemented moving of rows in QTableView
itself for Qt 6.8.0, so that moving rows in a table view is simpler to implement (one method instead of two), more efficient, and so that selection is updated. If you're not yet using Qt >= 6.8.0 then you'll have to reimplement mimeData()
and dropMimeData()
in your model, as per the previous section.
This concludes the section on how to implement a reorderable view using a separate model class.
Using item widgets
The alternative to model/view separation is the use of the item widgets (QListWidget
, QTableWidget
or QTreeWidget
) which you populate directly by creating items.

Reorderable QListWidget

Reorderable QTableWidget

Reorderable QTreeWidget
Here's what you need to do to allow users to reorder those items.
Example code can be found following this link.
Reorderable QListWidget
☑ Call listWidget->setDragDropMode(QAbstractItemView::InternalMove)
to enable the mode where only moving within the same view is allowed
For a QListWidget
, this is all you need. That was easy!
Reorderable QTableWidget
When using QTableWidget
:
☑ Call tableWidget->setDragDropMode(QAbstractItemView::InternalMove)
☑ Call tableWidget->setDragDropOverwriteMode(false)
so that it inserts rows instead of replacing cells
☑ Call item->setFlags(item->flags() & ~Qt::ItemIsDropEnabled);
on each item, to disable dropping onto items
Note: Before Qt 6.8.0, QTableWidget
did not really support moving rows. It would instead move data into cells (like Excel). The example code shows a workaround, but since it calls code that inserts a row and deletes the old one, header data is lost in the process. My changes in Qt 6.8.0 implement support for moving rows in QTableWidget
's internal model, so it's all fixed there. If you really need this feature in older versions of Qt, consider switching to QTableView
.
Reorderable QTreeWidget
When using QTreeWidget
:
☑ Call tableWidget->setDragDropMode(QAbstractItemView::InternalMove)
☑ Call item->setFlags(item->flags() & ~Qt::ItemIsDropEnabled);
on each item, to disable dropping onto items
Conclusion about reorderable item widgets
Of course, you'll also need to iterate over the items at the end to grab the new order, like the example code does. As usual, item widgets lead to less code to write, but the runtime performance is worse than when using model/view separation. So, only use item widgets when the number of items is small (and you don't need proxy models).
Improvements to Qt
While writing and testing these code examples, I improved the following things in Qt 6.8:
- QTBUG-13873 / QTBUG-101475 - QTableView: implement moving rows by drag-n-drop
- QTBUG-69807 - Implement QTableModel::moveRows
- QTBUG-130045 - QTableView: fix dropping between items when precisely on the cell border
- QTBUG-1656 - Implement full-row drop indicator when the selection behavior is SelectRows
Conclusion
I hope this checklist will be useful when you have to implement your own reordering of items in a model or an item-widget. Please post a comment if anything appears to be incorrect or missing.
In the next blog post of this series, you will learn how to move (or even copy) items from one view to another.
The post Model/View Drag and Drop in Qt - Part 1 appeared first on KDAB.
Qt Graphs vs Qt Charts. Objective Take on the New Data Visualization Solution
The Qt Graphs is a powerful module of the Qt framework that enables developers to create interactive, both static and […]
Improvements to Mozilla's Searchfox Code Browser
Improvements to Mozilla's Searchfox Code Browser
Mozilla is the maker of the famous Firefox web browser and the birthplace of the likes of Rust and Servo (read more about Embedding the Servo Web Engine in Qt).
Firefox is a huge, multi-platform, multi-language project with 21 million lines of code back in 2020, according to their own blog post. Navigating in projects like those is always a challenge, especially at the cross-language boundaries and in platform-specific code.
To improve working with the Firefox code-base, Mozilla hosts an online code browser tailored for Firefox called Searchfox. Searchfox analyzes C++, JavaScript, various IDLs (interface definition languages), and Rust source code and makes them all browsable from a single interface with full-text search, semantic search, code navigation, test coverage report, and git blame support. It's the combination of a number of projects working together, both internal to Mozilla (like their Clang plugin for C++ analysis) and external (such as rust-analyzer maintained by Ferrous Systems).
It takes a whole repository in and separately indexes C++, Rust, JavaScript and now Java and Kotlin source code. All those analyses are then merged together across platforms, before running a cross-reference step and building the final index used by the web front-end available at searchfox.org.

Mozilla asked KDAB to help them with adding Java and Kotlin support to Searchfox in prevision of the merge of Firefox for Android into the main mozilla-central repository and enhance their C++ support with macro expansions. Let's dive into the details of those tasks.
Java/Kotlin Support
Mozilla merged the Firefox for Android source code into the main mozilla-central repository that Searchfox indexes. To add support for that new Java and Kotlin code to Searchfox, we reused open-source tooling built by Sourcegraph around the SemanticDB and SCIP code indexing formats. (Many thanks to them!)
Sourcegraph's semanticdb-javac and semanticdb-kotlinc compiler plugins are integrated into Firefox's CI system to export SemanticDB artifacts. The Searchfox indexer fetches those SemanticDB files and turns them into a SCIP index, using scip-semanticdb. That SCIP index is then consumed by the existing Searchfox-internal scip-indexer tool.
In the process, a couple of upstream contributions were made to rust-analyzer (which also emits SCIP data) and scip-semanticdb.
A few examples of Searchfox at work:
- Searching for the Java class: org.mozilla.geckoview.GeckoSession$PromptDelegate$AutocompleteRequest shows the definition, a superclass, some uses in Java source code and some uses in Kotlin tests.
- Searching for the Java interface method: org.mozilla.geckoview.Autocomplete$StorageDelegate$onAddressFetch shows the definition, a couple of users, and a couple of implementers across Java and Kotlin code.
- Querying the callers of a method with up to 2 indirections: calls-to:'org::mozilla::geckoview::Autofill::Session::getDefaultDimensions' depth:2
If you want to dive into more details, see the feature request on Bugzilla, the implementation and further discussion on GitHub and the release announcement on the mozilla dev-platform mailing list.
Java/C++ Cross-language Support
GeckoView is an Android wrapper around Gecko, the Firefox web engine. It extensively uses cross-language calls between Java and C++.
Searchfox already had support for cross-language interfaces, thanks to its IDL support. We built on top of that to support direct cross-language calls between Java and C++.
First, we identified the different ways the C++ and Java code interact and call each other. There are three ways Java methods marked with the native
keyword call into C++:
- Case A1: By default, the JVM will search for a matching C function to call based on its name. For instance, calling
org.mozilla.gecko.mozglue.GeckoLoader.nativeRun
from Java will callJava_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun
on the C++ side. - Case A2: This behavior can be overridden at runtime by calling the
JNIEnv::RegisterNatives
function on the C++ side to point at another function. - Case A3: GeckoView has a code generator that looks for Java items decorated with the
@WrapForJNI
andnative
annotations and generates a C++ class template meant to be used through the Curiously Recurring Template Pattern. This template provides anInit
static member function that does the rightJNIEnv::RegisterNatives
calls to bind the Java methods to the implementing C++ class's member functions.
We also identified two ways the C++ code calls Java methods:
- Case B1: directly with
JNIEnv::Call…
functions. - Case B2: GeckoView's code generator also looks for Java methods marked with
@WrapForJNI
(without thenative
keyword this time) and generates a C++ wrapper class and member functions with the rightJNIEnv::Call…
calls.
Only the C++ side has the complete view of the bindings; so that's where we decided to extract the information from, by extending Mozilla's existing Clang plugin.
First, we defined custom C++ annotations bound_as
and binding_to
that the clang plugin transforms into the right format for the cross-reference analysis. This means we can manually set the binding information:
class __attribute__((annotate("binding_to", "jvm", "class", "S_jvm_sample/Jni#"))) CallingJavaFromCpp
{
__attribute__((annotate("binding_to", "jvm", "method", "S_jvm_sample/Jni#javaStaticMethod().")))
static void javaStaticMethod()
{
// Wrapper code
}
__attribute__((annotate("binding_to", "jvm", "method", "S_jvm_sample/Jni#javaMethod().")))
void javaMethod()
{
// Wrapper code
}
__attribute__((annotate("binding_to", "jvm", "getter", "S_jvm_sample/Jni#javaField.")))
int javaField()
{
// Wrapper code
return 0;
}
__attribute__((annotate("binding_to", "jvm", "setter", "S_jvm_sample/Jni#javaField.")))
void javaField(int)
{
// Wrapper code
}
__attribute__((annotate("binding_to", "jvm", "const", "S_jvm_sample/Jni#javaConst.")))
static constexpr int javaConst = 5;
};
class __attribute__((annotate("bound_as", "jvm", "class", "S_jvm_sample/Jni#"))) CallingCppFromJava
{
__attribute__((annotate("bound_as", "jvm", "method", "S_jvm_sample/Jni#nativeStaticMethod().")))
static void nativeStaticMethod()
{
// Real code
}
__attribute__((annotate("bound_as", "jvm", "method", "S_jvm_sample/Jni#nativeMethod().")))
void nativeMethod()
{
// Real code
}
};
(This example is, in fact, extracted from our test suite, jni.cpp vs Jni.java.)
Then, we wrote some heuristics that try and identify cases A1 (C functions named Java_…
), A3 and B2 (C++ code generated from @WrapForJNI
decorators) and automatically generate these annotations. Cases A2 and B1 (manually calling JNIEnv::RegisterNatives
or JNIEnv::Call…
functions) are rare enough in the Firefox code base and impossible to reliably recognize; so it was decided not to cover them at the time. Developers who wish to declare such bindings could manually annotate them.
After this point, we used Searchfox's existing analysis JSON format and mostly re-used what was already available from IDL support. When triggering the context menu for a binding wrapper or bound function, the definitions in both languages are made available, with “Go to” actions that jump over the generally irrelevant binding internals.
The search results also display both sides of the bridge, for instance:
- searching for the mozilla::widget::GeckoViewSupport::Open C++ member function links to its Java binding org.mozilla.geckoview.GeckoSession$Window.open.
- searching for the org.mozilla.geckoview.GeckoSession.getCompositorFromNative Java method links to its generated C++ binding mozilla::java::GeckoSession::GetCompositor.
If you want to dive into more details, see the feature request and detailed problem analysis on Bugzilla, the implementation and further discussion on GitHub, and the release announcement on the Mozilla dev-platform mailing list.
Displaying Interactive Macro Expansions
Aside from this Java/Kotlin-related work, we also added support for displaying and interacting with macro expansions. This was inspired by KDAB's own codebrowser.dev, but improves it to:
- Display all expansion variants, if they differ across platforms or by definition:

Per-platform expansions

Per-definition expansions
- Make macros fully indexed and interactive:

In-macro context menu
This work mainly happened in the Mozsearch Clang plugin to extract macro expansions during the pre-processing stage and index them with the rest of the top-level code.
Again, if you want more details, the feature request is available on Bugzilla and the implementation and further technical discussion is on GitHub.
Summary
Because of the many technologies it makes use of, from compiler plugins and code analyzers written in many languages, to a web front-end written using the usual HTML/CSS/JS, by way of custom tooling and scripts in Rust, Python and Bash, Searchfox is a small but complex and really interesting project to work on. KDAB successfully added Java/Kotlin code indexing, including analyzing their C++ bindings, and are starting to improve Searchfox's C++ support itself, first with fully-indexed macro expansions and next with improved templates support.
The post Improvements to Mozilla's Searchfox Code Browser appeared first on KDAB.
Setting C++ Defines with CMake
Setting C++ Defines with CMake
The goal
When building C++ code with CMake, it is very common to want to set some pre-processor defines in the CMake code.
For instance, we might want to set the project's version number in a single place, in CMake code like this:
project(MyApp VERSION 1.5)
This sets the CMake variable PROJECT_VERSION
to 1.5, which we can then use to pass -DMYAPP_VERSION_STRING=1.5
to the C++ compiler. The about dialog of the application can then use this to show the application version number, like this:
const QString aboutString = QStringLiteral("My App version: %1").arg(MYAPP_VERSION_STRING);
QMessageBox::information(this, "My App", aboutString);
Similarly, we might have a boolean CMake option like START_MAXIMIZED
, which the user compiling the software can set to ON or OFF:
option(START_MAXIMIZED "Show the mainwindow maximized" OFF)
If it's ON, you would pass -DSTART_MAXIMIZED
, otherwise nothing. The C++ code will then use #ifdef
. (We'll see that there's a better way.)
#ifdef START_MAXIMIZED
w.showMaximized();
#else
w.show();
#endif
The common (but suboptimal) solution
A solution that many people use for this is the CMake function add_definitions
. It would look like this:
add_definitions(-DMYAPP_VERSION_STRING="${PROJECT_VERSION}")
if (START_MAXIMIZED)
add_definitions(-DSTART_MAXIMIZED)
endif()
Technically, this works but there are a number of issues.
First, add_definitions
is deprecated since CMake 3.12 and add_compile_definitions
should be used instead, which allows to remove the leading -D
.
More importantly, there's a major downside to this approach: changing the project version or the value of the boolean option will force CMake to rebuild every single .cpp file used in targets defined below these lines (including in subdirectories). This is because add_definitions
and add_compile_definitions
ask to pass -D
to all cpp files, instead of only those that need it. CMake doesn't know which ones need it, so it has to rebuild everything. On large real-world projects, this could take something like one hour, which is a major waste of time.
A first improvement we can do is to at least set the defines to all files in a single target (executable or library) instead of "all targets defined from now on". This can be done like this:
target_compile_definitions(myapp PRIVATE MYAPP_VERSION_STRING="${PROJECT_VERSION}")
if(START_MAXIMIZED)
target_compile_definitions(myapp PRIVATE START_MAXIMIZED)
endif()
We have narrowed the rebuilding effect a little bit, but are still rebuilding all cpp files in myapp, which could still take a long time.
The recommended solution
There is a proper way to do this, such that only the files that use these defines will be rebuilt; we simply have to ask CMake to generate a header with #define
in it and include that header in the few cpp files that need it. Then, only those will be rebuilt when the generated header changes. This is very easy to do:
configure_file(myapp_config.h.in myapp_config.h)
We have to write the input file, myapp_config.h.in
, and CMake will generate the output file, myapp_config.h
, after expanding the values of CMake variables. Our input file would look like this:
#define MYAPP_VERSION_STRING "${PROJECT_VERSION}"
#cmakedefine01 START_MAXIMIZED
A good thing about generated headers is that you can read them if you want to make sure they contain the right settings. For instance, myapp_config.h
in your build directory might look like this:
#define MYAPP_VERSION_STRING "1.5"
#define START_MAXIMIZED 1
For larger use cases, we can even make this more modular by moving the version number to another input file, say myapp_version.h.in
, so that upgrading the version doesn't rebuild the file with the showMaximized()
code and changing the boolean option doesn't rebuild the about dialog.
If you try this and you hit a "file not found" error about the generated header, that's because the build directory (where headers get generated) is missing in the include path. You can solve this by adding set(CMAKE_INCLUDE_CURRENT_DIR TRUE)
near the top of your CMakeLists.txt
file. This is part of the CMake settings that I recommend should always be set; you can make it part of your new project template and never have to think about it again.
There's just one thing left to explain: what's this #cmakedefine01
thing?
If your C++ code uses #ifdef
, you want to use #cmakedefine
, which either sets or doesn't set the define. But there's a major downside of doing that -- if you forget to include myapp_config.h
, you won't get a compile error; it will just always go to the #else
code path.
We want a solution that gives an error if the #include
is missing. The generated header should set the define to either 0 or 1 (but always set it), and the C++ code should use #if
. Then, you get a warning if the define hasn't been set and, because people tend to ignore warnings, I recommend that you upgrade it to an error by adding the compiler flag -Werror=undef
, with gcc or clang. Let me know if you are aware of an equivalent flag for MSVC.
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_compile_options(myapp PRIVATE -Werror=undef)
endif()
And these are all the pieces we need. Never use add_definitions
or add_compile_definitions
again for things that are only used by a handful of files. Use configure_file
instead, and include the generated header. You'll save a lot of time compared to recompiling files unnecessarily.
I hope this tip was useful.
For more content on CMake, we curated a collection of resources about CMake with or without Qt. Check out the videos.
The post Setting C++ Defines with CMake appeared first on KDAB.
KDAB Training Day - May 8th, 2025
KDAB Training Day - May 8th, 2025
Early-Bird tickets are on sale for the KDAB Training Day 2025 until 2025-03-31 23:59

The KDAB Training Day 2025 will take place in Munich on May 8th, right after the Qt World Summit on May 6th-7th. Choose to buy a combo ticket here (for access to QtWS and Training Day) or here (for access to Training Day only).
Seats are limited, so don't wait too long if you want to participate in a specific course. Tickets include access to the selected training course, training material, lunch buffet, beverages, and coffee breaks. Note: The Training Day is held at Hotel NH Collection München Bavaria, located at the Munich Central Station (not the same location as Qt World Summit).
Why should you attend the KDAB Training Day?
With over 20 years of experience and a rich store of well-structured, constantly updated training material, KDAB offers hands-on, practical programming training in Qt/QML, Modern C++, 3D/OpenGL, Debugging & Profiling, and lately Rust - both for beginners as well as experienced developers.
All courses provided at the Training Day include central parts of the regular 3- to 4-day courses available as scheduled or customized on-site training. Choosing a compact, learning-rich one-day course, lets you experience the quality and effectiveness of KDAB’s usual training offerings.
Courses available at the KDAB Training Day 2025
- QML Application Architecture
- QML/C++ integration
- Modern C++ Paradigms
- Integrating Rust into Qt applications
- Effective Modern QML
- Integrating Custom 3D Renderers with Qt Applications
QML Application Architecture
In this training, we do a step-by-step walkthrough of how to build a QML-based embedded application from the ground up and discuss some challenges that are typically met along the way.
An important part of that journey is an investigation of where to put the boundaries between what you do in C++ and what you do in QML. We also look at some of the tools and building blocks we have available in QML that can help us achieve well-performing, well-structured, and well-maintainable applications.
This course is for
(Qt) developers looking to improve their understanding of how to construct maintainable and efficient larger-scale QML applications.
Prerequisite
Some real-world experience working on QML applications as well as a basic understanding of Qt and C++.
QML/C++ Integration
In this training, we start with a recap of fundamentals:
- How do we expose C++ API to QML?
- How do we make data available to QML?
Afterward, we explore several more advanced techniques, often widely deployed within Qt's QML modules, such as Qt Quick.
This will answer questions such as:
- How would I do a Loader like component?
- How would I do a Layout like component?
This course is for
Qt/QML developers who are familiar with the QML APIs of QtQuick and related modules and who have wondered how these are implemented and want to use similar techniques in their project-specific APIs.
Prerequisite
Some real-world experience working on QML applications as well as a basic understanding of Qt and C++.
Modern C++ Paradigms
Modern C++ emphasizes safer, more efficient, and maintainable code through higher-level abstractions that reduce error-prone manual work.
This training will explore key paradigms shaping recent C++ evolution, starting with value semantics in class design, which enhances code safety, local reasoning, and thread safety. We will examine modern C++ tools for creating value-oriented types, including move semantics, smart pointers, and other library enablers.
Next, we will look at expressive, type and value-based error handling.
Finally, we'll cover range-based programming, which enables clean, declarative code and unlocks new patterns through lazy, composable transformations.
This course is for
C++ developers who wish to improve the quality of their code, in particular those who wish to write future-proof APIs.
Prerequisites
Prior professional experience in C++. Experience with the latest C++ standards (C++20/23/26) is a plus. We will use several examples inspired by Qt APIs, so Qt knowledge is also a plus (but this is not going to be a Qt training).
Integrating Rust into Qt Applications
In this step-by-step course, we start with a Qt/C++ application and add Rust code to it piece by piece. To achieve this, we will cover:
- Use of Cargo (Rusts build system) with CMake
- Accessing Rust code from C++ with CXX (and vice-versa)
- Defining your own QObject types in Rust with CXX-Qt
We discuss when to use Rust compared to C++ to make the best of both languages and how to use them together effectively to make Qt applications safer and easier to maintain.
This course is for
Qt/C++ Developers with an interest in Rust who want to learn how to use Rust in their existing applications.
Prerequisites
Basic Qt/C++ knowledge, as well as basic Rust knowledge, is required. A working Qt installation with CMake and a working Rust installation is needed. We will provide material before the training day that participants should use to check their setup before the training.
Effective Modern QML
In this training, we look into all the new developments in QML over the last few years and how they lead to more expressive, performant, and maintainable code.
This includes:
- The qt_add_qml_module CMake API
- Declarative type registration
- The different QML compilers
- New language and library features
- New developments in Qt Quick Controls
- Usage of tools like qmllint, QML Language Server, and qmlformat
The focus will be on gradually modernizing existing codebases with new tools and practices.
This course is for
Developers who learned QML back in the days of Qt 5 and want to catch up with recent developments in QML and modernize their knowledge as well as codebases.
Prerequities
Some real-world experience with QML and a desire to learn about modern best practices.
Integrating Custom 3D Renderers with Qt Applications
Qt has long offered ways of using low-level 3d libraries such as OpenGL to do custom rendering. Whether at the Window, the Widget, or Quick Item level, the underlying rendering system can be accessed in ways that make it safe to integrate such 3rd party renderers. This remains true in the Qt 6 timeline, although the underlying rendering system has changed and OpenGL has been replaced by RHI.
In this course, we look at how the graphic stack is structured in Qt 6 and how third-party renderers can be integrated on the various platforms supported by Qt.
We then focus on the specific case of integrating Vulkan-based renderers. Vulkan is the successor to OpenGL; it's much more powerful but harder to learn. To facilitate the initial use of Vulkan, we introduce KDGpu, a library that encapsulates Vulkan while preserving the underlying concepts of pipeline objects, buffer handling, synchronization, etc.
This course is for
This course targets developers wanting to understand the recent state of the graphics stack in Qt, discover the fundamental principles of modern graphics API, and integrate their custom renderers in their applications.
Prerequisite
Prior knowledge of Qt will be required. A basic understanding of 3d graphics would be beneficial.
Video from KDAB Training Day 2023 held in Berlin
The post KDAB Training Day - May 8th, 2025 appeared first on KDAB.
CXX-Qt 0.7 Release
CXX-Qt 0.7 Release
We just released CXX-Qt version 0.7!
CXX-Qt is a set of Rust crates for creating bidirectional Rust ⇄ C++ bindings with Qt. It supports integrating Rust into C++ applications using CMake or building Rust applications with Cargo. CXX-Qt provides tools for implementing QObject subclasses in Rust that can be used from C++, QML, and JavaScript.
For 0.7, we have stabilized the cxx-qt
bridge macro API and there have been many internal refactors to ensure that we have a consistent baseline to support going forward. We encourage developers to reach out if they find any unclear areas or missing features, to help us ensure a roadmap for them, as this may be the final time we can adapt the API. In the next releases, we're looking towards stabilizing the cxx-qt-build
and getting the cxx-qt-lib
APIs ready for 1.0.
Check out the new release through the usual channels:
Some of the most notable developer-facing changes:
Stabilized #[cxx_qt::bridge] macro
CXX-Qt 0.7 reaches a major milestone by stabilizing the bridge macro that is at the heart of CXX-Qt.
You can now depend on your CXX-Qt bridges to remain compatible with future CXX-Qt versions.
As we're still pre-1.0, we may still introduce very minor breaking changes to fix critical bugs in the edge-cases of the API, but the vast majority of bridges should remain compatible with future versions.
This stabilization is also explicitly limited to the bridge API itself. Breaking changes may still occur in e.g. cxx-qt-lib
, cxx-qt-build
, and cxx-qt-cmake
. We plan to stabilize those crates in the next releases.
Naming Changes
The handling of names internally has been refactored to ensure consistency across all usages. During this process, implicit automatic case conversion has been removed, so cxx_name
and rust_name
are now used to specify differing Rust and C++ names. Since the automatic case conversion is useful, it can be explicitly enabled using per extern block attributes auto_cxx_name
and auto_rust_name
, while still complimenting CXX. For more details on how these attributes can be used, visit the attributes page in the CXX-Qt book.
// with 0.6 implicit automatic case conversion
#[cxx_qt::bridge]
mod ffi {
unsafe extern "RustQt" {
#[qobject]
#[qproperty(i32, my_number) // myNumber in C++
type MyObject = super::MyObjectRust;
fn my_method(self: &MyObject); // myMethod in C++
}
}
// with 0.7 cxx_name / rust_name
#[cxx_qt::bridge]
mod ffi {
unsafe extern "RustQt" {
#[qobject]
#[qproperty(i32, my_number, cxx_name = "myNumber")
type MyObject = super::MyObjectRust;
#[cxx_name = "myMethod"]
fn my_method(self: &MyObject);
}
}
// with 0.7 auto_cxx_name / auto_rust_name
#[cxx_qt::bridge]
mod ffi {
#[auto_cxx_name] // <-- enables automatic cxx_name generation within the `extern "RustQt"` block
unsafe extern "RustQt" {
#[qobject]
#[qproperty(i32, my_number) // myNumber in C++
type MyObject = super::MyObjectRust;
fn my_method(self: &MyObject); // myMethod in C++
}
}
cxx_file_stem Removal
In previous releases, the output filename of generated C++ files used the cxx_file_stem
attribute of the CXX-Qt bridge. This has been changed to use the filename of the Rust source file including the directory structure.
Previously, the code below would generate a C++ header path of my_file.cxxqt.h.
After the changes, the cxx_file_stem
must be removed and the generated C++ header path changes to crate-name/src/my_bridge.cxxqt.h.
This follows a similar pattern to CXX.
// crate-name/src/my_bridge.rs
// with 0.6 a file stem was specified
#[cxx_qt::bridge(cxx_file_stem = "my_file")]
mod ffi {
...
}
// with 0.7 the file path is used
#[cxx_qt::bridge]
mod ffi {
...
}
Build System Changes
The internals of the build system have changed so that dependencies are automatically detected and configured by cxx-qt-build
, libraries can pass build information to cxx-qt-build
, and a CXX-Qt CMake module is now available providing convenience wrappers around corrosion. This means that the cxx-qt-lib-headers
crate has been removed and only cxx-qt-lib
is required. With these changes, there is now no need for the -header
crates that existed before. Previously, some features were enabled by default in cxx-qt-lib.
Now these are all opt-in. We have provided full
and qt_full
as convenience to enable all features; however, we would recommend opting in to the specific features you need.
We hope to improve the API of cxx-qt-build
in the next cycle to match the internal changes and become more modular.
Further Improvements
CXX-Qt can now be successfully built for WASM, with documented steps available in the book and CI builds for WASM to ensure continued support.
Locking generation on the C++ side for all methods has been removed, which simplifies generation and improves performance. Using queue
from cxx_qt::CxxQtThread
is still safe, as it provides locking, but it is up to the developer to avoid incorrect multi-threading in C++ code (as in the CXX
crate). Note that Qt generally works well here, with the signal/slot mechanism working safely across threads.
As with most releases, there are more Qt types wrapped in cxx-qt-lib
and various other changes are detailed in the CHANGELOG.
Make sure to subscribe to the KDAB YouTube channel, where we'll post more videos on CXX-Qt in the coming weeks.
Thanks to all of our contributors that helped us with this release:
- Ben Ford
- Laurent Montel
- Matt Aber
- knox (aka @knoxfighter)
- Be Wilson
- Joshua Goins
- Alessandro Ambrosano
- Alexander Kiselev
- Alois Wohlschlager
- Darshan Phaldesai
- Jacob Alexander
- Sander Vocke
The post CXX-Qt 0.7 Release appeared first on KDAB.
10 Tips to Make Your QML Code Faster and More Maintainable
10 Tips to Make Your QML Code Faster and More Maintainable
In recent years, a lot has been happening to improve performance, maintainability and tooling of QML. Some of those improvements can only take full effect when your code follows modern best practices. Here are 10 things you can do in order to modernize your QML code and take full advantage of QML's capabilities.
1. Use qt_add_qml_module CMake API
Qt6 introduced a new CMake API to create QML modules. Not only is this more convenient than what previously had to be done manually, but it is also a prerequisite for being able to exploit most of the following tips.
By using the qt_add_qml_module, your QML code is automatically processed by qmlcachegen, which not only creates QML byte code ahead of time, but also converts parts of your QML code to C++ code, improving performance. How much of your code can be compiled to C++ depends on the quality of the input code. The following tips are all about improving your code in that regard.
add_executable(myapp main.cpp)
qt_add_qml_module(myapp
URI "org.kde.myapp"
QML_FILES Main.qml
)
2. Use declarative type registration
When creating custom types in C++ and registering them with qmlRegisterType and friends, they are not visible to the tooling at the compile time. qmlcachegen doesn't know which types exist and which properties they have. Hence, it cannot translate to C++ the code that's using them. Your experience with the QML Language Server will also suffer since it cannot autocomplete types and property names.
To fix this, your types should be registered declaratively using the QML_ELEMENT (and its friends, QML_NAMED_ELEMENT, QML_SINGLETON, etc) macros.
qmlRegisterType("org.kde.myapp", 1, 0, "MyThing");
becomes
class MyThing : public QObject
{
Q_OBJECT
QML_ELEMENT
};
The URL and version information are inferred from the qt_add_qml_module call.
3. Declare module dependencies
Sometimes your QML module depends on other modules. This can be due to importing it in the QML code, or more subtly by using types from another module in your QML-exposed C++ code. In the latter case, the dependency needs to be declared in the qt_add_qml_module call.
For example, exposing a QAbstractItemModel subclass to QML adds a dependency to the QtCore (that's where QAbstractItemModel is registered) to your module. This does not only happen when subclassing a type but also when using it as a parameter type in properties or invokables.
Another example is creating a custom QQuickItem-derived type in C++, which adds a dependency on the Qt Quick module.
To fix this, add the DEPENDENCIES declaration to qt_add_qml_module:
qt_add_qml_module(myapp
URI "org.kde.myapp"
QML_FILES Main.qml
DEPENDENCIES QtCore
)
4. Qualify property types fully
MOC needs types in C++ property definitions to be fully qualified, i.e. include the full namespace, even when inside that namespace. Not doing this will cause issues for the QML tooling.
namespace MyApp {
class MyHelper : public QObject {
Q_OBJECT
};
class MyThing : public QObject {
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(MyHelper *helper READ helper CONSTANT) // bad
Q_PROPERTY(MyApp::MyHelper *helper READ helper CONSTANT) // good
...
};
}
5. Use types
In order for qmlcachegen to generate efficient code for your bindings, it needs to know the type for properties. Avoid using 'property var' wherever possible and use concrete types. This may be built-in types like int, double, or string, or any declaratively-defined custom type. Sometimes you want to be able to use a type as a property type in QML but don't want the type to be creatable from QML directly. For this, you can register them using the QML_UNCREATABLE macro.
property var size: 10 // bad
property int size: 10 // good
property var thing // bad
property MyThing thing // good
6. Avoid parent and other generic properties
qmlcachegen can only work with the property types it knows at compile time. It cannot make any assumptions about which concrete subtype a property will hold at runtime. This means that, if a property is defined with type Item, it can only compile bindings using properties defined on Item, not any of its subtypes. This is particularly relevant for properties like 'parent' or 'contentItem'. For this reason, avoid using properties like these to look up items when not using properties defined on Item (properties like width, height, or visible are okay) and use look-ups via IDs instead.
Item {
id: thing
property int size: 10
Rectangle {
width: parent.size // bad, Item has no 'size' property
height: thing.height // good, lookup via id
color: parent.enabled ? "red" : "black" // good, Item has 'enabled' property
}
}
7. Annotate function parameters with types
In order for qmlcachegen to compile JavaScript functions, it needs to know the function's parameter and return type. For that, you need to add type annotations to the function:
function calculateArea(width: double, height: double) : double {
return width * height
}
When using signal handlers with parameters, you should explicitly specify the signal parameters by supplying a JS function or an arrow expression:
MouseArea {
onClicked: event => console.log("clicked at", event.x, event.y)
}
Not only does this make qmlcachegen happy, it also makes your code far more readable.
8. Use qualified property lookup
QML allows you to access properties from objects several times up in the parent hierarchy without explicitly specifying which object is being referenced. This is called an unqualified property look-up and generally considered bad practice since it leads to brittle and hard to reason about code. qmlcachegen also cannot properly reason about such code. So, it cannot properly compile it. You should only use qualified property lookups
Item {
id: root
property int size: 10
Rectangle {
width: size // bad, unqualified lookup
height: root.size // good, qualified lookup
}
}
Another area that needs attention is accessing model roles in a delegate. Views like ListView inject their model data as properties into the context of the delegate where they can be accessed with expressions like 'foo', 'model.foo', or 'modelData.foo'. This way, qmlcachegen has no information about the types of the roles and cannot do its job properly. To fix this, you should use required properties to fetch the model data:
ListView {
model: MyModel
delegate: ItemDelegate {
text: name // bad, lookup from context
icon.name: model.iconName // more readable, but still bad
required property bool active // good, using required property
checked: active
}
}
9. Use pragma ComponentBehavior: Bound
When defining components, either explicitly via Component {}
or implicitly when using delegates, it is common to want to refer to IDs outside of that component, and this generally works. However, theoretically any component can be used outside of the context it is defined in and, when doing that, IDs might refer to another object entirely. For this reason, qmlcachegen cannot properly compile such code.
To address this, we need to learn about pragma ComponentBehavior
. Pragmas are file-wide switches that influence the behavior of QML. By specifying pragma ComponentBehavior: Bound
at the top of the QML file, we can bind any components defined in this file to their surroundings. As a result, we cannot use the component in another place anymore but can now safely access IDs outside of it.
pragma ComponentBehavior: Bound
import QtQuick
Item {
id: root
property int delegateHeight: 10
ListView {
model: MyModel
delegate: Rectangle {
height: root.delegateHeight // good with ComponentBehavior: Bound, bad otherwise
}
}
}
A side effect of this is that accessing model data now must happen using required properties, as described in the previous point. Learn more about ComponentBehavior here.
10. Know your tools
A lot of these pitfalls are not obvious, even to seasoned QML programmers, especially when working with existing codebases. Fortunately, qmllint helps you find most of these issues and avoids introducing them. By using the QML Language Server, you can incorporate qmllint directly into your preferred IDE/editor such as Kate or Visual Studio Code.
While qmlcachegen can help boost your QML application's performance, there are performance problems it cannot help with, such as scenes that are too complex, slow C++ code, or inefficient rendering. To investigate such problems, tools like the QML profiler, Hotspot for CPU profiling, Heaptrack for memory profiling, and GammaRay for analyzing QML scenes are very helpful.
The post 10 Tips to Make Your QML Code Faster and More Maintainable appeared first on KDAB.
Translating Qt Applications
Translating a Qt application, can be a daunting task. This is an overview from Qt 5 to Qt 6 and what new functionality Qt 6.7 brings.
Continue reading Translating Qt Applications at basysKom GmbH.
Introduction to the QGraphics framework — Creating vector interfaces using the QGraphics View framework
The Qt Graphics View Framework allows you to develop fast and efficient 2D vector graphic scenes. Scenes can contain millions of items, each with their own features and behaviors. By using the Graphics View via PySide6 you get access to this highly performant graphics layer in Python. Whether you're integrating vector graphics views into an existing PySide6 application, or simply want a powerful vector graphics interface for Python, Qt's Graphics View is what you're looking for.
Some common uses of the Graphics View include data visualization, mapping applications, 2D design tools, modern data dashboards and even 2D games.
In this tutorial we'll take our first steps looking at the Qt Graphics View framework, building a scene with some simple vector items. This will allow us to familiarize ourselves with the API and coordinate system, which we'll use later to build more complex examples.
The Graphics View Framework
The Graphics View framework consists of 3 main parts QGraphicsView
, QGraphicsScene
, and QGraphicsItem
, each with different responsibilities.
The framework can be interpreted using the Model-View paradigm, with the QGraphicsScene
as the Model and the QGraphicsView
as the View. Each scene can have multiple views. The QGraphicsItems within the scene can be considered as items within the model, holding the visual data that the scene combines to define the complete image.
QGraphicsScene
is the central component that glues everything together. It acts as a whiteboard on which all items are drawn (circles, rectangles, lines, pixmaps, etc). The QGraphicsView
has the responsibility of rendering a given scene -- or part of it, with some transformation (scaling, rotating, shearing) -- to display it to the user. The view is a standard Qt widget and can be placed inside any Qt layout.
QGraphicsScene
provides some important functionalities out of the box, so we can use them to develop advanced applications without struggling with low-level details. For example --
- Collision Detection, detect a graphics item is collided with another item.
- Item Selection, gives us the ability to deal with multiple items at the same time, for example, the user can select multiple items, and when pressing delete, a function asks the scene to give the list for all selected items, and then delete them.
- Items discovery, the scene can tell us what items are present (or part of them) at a specific point or inside some defined region, for example, if the user adds an item that intersects with a forbidden area, the program will detect them and give them another (mostly red) color.
- Events Propagation, the scene receives the events and then propagates them to items.
To define a QGraphicsScene
you define it's boundaries or sceneRect which defines the x & y origins and dimensions of the scene. If you don't provide a sceneRect it will default to the minimum bounding rectangle for all child items -- updating as items are added, moved or removed. This is flexible but less efficient.
Items in the scene are represented by QGraphicsItem
objects. These are the basic building block of any 2D scene, representing a shape, pixmap or SVG image to be displayed in the scene. Each item has a relative position inside the sceneRect
and can have different transformation effects (scale, translate, rotate, shear).
Finally, the QGraphicsView
is the renderer of the scene, taking the scene and displaying it -- either wholly or in part -- to the user. The view itself can have transformations (scale, translate, rotate and shear) applied to modify the display without affecting the underlying scene. By default the view will forward mouse and keyboard events to the scene allowing for user interaction. This can be disabled by calling view.setInteractive(False)
.
A simple scene
Let's start by creating a simple scene. The following code creates QGraphicsScene
, defining a 400 x 200 scene, and then displays it in a QGraphicsView
.
import sys
from PySide6.QtWidgets import QGraphicsScene, QGraphicsView, QApplication
app = QApplication(sys.argv)
# Defining a scene rect of 400x200, with it's origin at 0,0.
# If we don't set this on creation, we can set it later with .setSceneRect
scene = QGraphicsScene(0, 0, 400, 200)
view = QGraphicsView(scene)
view.show()
app.exec()
If you run this example you'll see an empty window.
The empty graphics scene, shown in a QGraphicsView window.
Not very exciting yet -- but this is our QGraphicsView
displaying our empty scene.
As mentioned earlier, QGraphicsView
is a widget. In Qt any widgets without a parent display as windows. This is why our QGraphicsView
appears as a window on the desktop.
Adding items
Let's start adding some items to the scene. There are a number of built-in graphics items which you can customize and add to your scene. In the example below we use QGraphicsRectItem
which draws a rectangle. We create the item passing in it's dimensions, and then set it's position pen and brush before adding it to the scene.
import sys
from PySide6.QtWidgets import QGraphicsScene, QGraphicsView, QGraphicsRectItem, QApplication
from PySide6.QtGui import QBrush, QPen
from PySide6.QtCore import Qt
app = QApplication(sys.argv)
# Defining a scene rect of 400x200, with it's origin at 0,0.
# If we don't set this on creation, we can set it later with .setSceneRect
scene = QGraphicsScene(0, 0, 400, 200)
# Draw a rectangle item, setting the dimensions.
rect = QGraphicsRectItem(0, 0, 200, 50)
# Set the origin (position) of the rectangle in the scene.
rect.setPos(50, 20)
# Define the brush (fill).
brush = QBrush(Qt.red)
rect.setBrush(brush)
# Define the pen (line)
pen = QPen(Qt.cyan)
pen.setWidth(10)
rect.setPen(pen)
scene.addItem(rect)
view = QGraphicsView(scene)
view.show()
app.exec()
Running the above you'll see a single, rather ugly colored, rectangle in the scene.
A single rectangle in the scene
Adding more items is simply a case of creating the objects, customizing them and then adding them to the scene. In the example below we add an circle, using QGraphicsEllipseItem
-- a circle is just an ellipse with equal height and width.
import sys
from PySide6.QtWidgets import QGraphicsScene, QGraphicsView, QGraphicsRectItem, QGraphicsEllipseItem, QApplication
from PySide6.QtGui import QBrush, QPen
from PySide6.QtCore import Qt
app = QApplication(sys.argv)
# Defining a scene rect of 400x200, with it's origin at 0,0.
# If we don't set this on creation, we can set it later with .setSceneRect
scene = QGraphicsScene(0, 0, 400, 200)
# Draw a rectangle item, setting the dimensions.
rect = QGraphicsRectItem(0, 0, 200, 50)
# Set the origin (position) of the rectangle in the scene.
rect.setPos(50, 20)
# Define the brush (fill).
brush = QBrush(Qt.red)
rect.setBrush(brush)
# Define the pen (line)
pen = QPen(Qt.cyan)
pen.setWidth(10)
rect.setPen(pen)
ellipse = QGraphicsEllipseItem(0, 0, 100, 100)
ellipse.setPos(75, 30)
brush = QBrush(Qt.blue)
ellipse.setBrush(brush)
pen = QPen(Qt.green)
pen.setWidth(5)
ellipse.setPen(pen)
# Add the items to the scene. Items are stacked in the order they are added.
scene.addItem(ellipse)
scene.addItem(rect)
view = QGraphicsView(scene)
view.show()
app.exec()
The above code will give the following result.
A scene with two items
The order you add items affects the stacking order in the scene -- items added later will always appear on top of items added first. However, if you need more control you can set the stacking order using .setZValue
.
ellipse.setZValue(500)
rect.setZValue(200)
Now the circle (ellipse) appears above the rectangle.
Using Zvalue to order items in the scene
Try experimenting with setting the Z value of the two items -- you can set it before or after the items are in the scene, and can change it at any time.
Z in this context refers to the Z coordinate. The X & Y coordinates are the horizontal and vertical position in the scene respectively. The Z coordinate determines the relative position of items toward the front and back of the scene -- coming "out" of the screen towards the viewer.
There are also the convenience methods .stackBefore()
and .stackAfter()
which allow you to stack your QGraphicsItem
behind, or in front of another item in the scene.
ellipse.stackAfter(rect)
Making items moveable
Our two QGraphicsItem
objects are currently fixed in position where we place them, but they don't have to be! As already mentioned Qt's Graphics View framework allows items to respond to user input, for example allowing them to be dragged and dropped around the scene at will. Simple functionality like is actually already built in, you just need to enable it on each QGraphicsItem
. To do that we need to set the flag QGraphicsItem.GraphicsItemFlags.ItemIsMoveable
on the item.
The full list of graphics item flags is available here.
import sys
from PySide6.QtWidgets import QGraphicsScene, QGraphicsView, QGraphicsItem, QGraphicsRectItem, QGraphicsEllipseItem, QApplication
from PySide6.QtGui import QBrush, QPen
from PySide6.QtCore import Qt
app = QApplication(sys.argv)
# Defining a scene rect of 400x200, with it's origin at 0,0.
# If we don't set this on creation, we can set it later with .setSceneRect
scene = QGraphicsScene(0, 0, 400, 200)
# Draw a rectangle item, setting the dimensions.
rect = QGraphicsRectItem(0, 0, 200, 50)
# Set the origin (position) of the rectangle in the scene.
rect.setPos(50, 20)
# Define the brush (fill).
brush = QBrush(Qt.red)
rect.setBrush(brush)
# Define the pen (line)
pen = QPen(Qt.cyan)
pen.setWidth(10)
rect.setPen(pen)
ellipse = QGraphicsEllipseItem(0, 0, 100, 100)
ellipse.setPos(75, 30)
brush = QBrush(Qt.blue)
ellipse.setBrush(brush)
pen = QPen(Qt.green)
pen.setWidth(5)
ellipse.setPen(pen)
# Add the items to the scene. Items are stacked in the order they are added.
scene.addItem(ellipse)
scene.addItem(rect)
ellipse.setFlag(QGraphicsItem.ItemIsMovable)
view = QGraphicsView(scene)
view.show()
app.exec()
In the above example we've set ItemIsMovable
on the ellipse only. You can drag the ellipse around the scene -- including behind the rectangle -- but the rectangle itself will remain locked in place. Experiment with adding more items and configuring the moveable status.
If you want an item to be selectable you can enable this by setting the ItemIsSelectable
flag, for example here using .setFlags()
to set multiple flags at the same time.
ellipse.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
If you click on the ellipse you'll now see it surrounded by a dashed line to indicate that it is selected. We'll look at how to use item selection in more detail in a later tutorial.
A selected item in the scene, highlighted with dashed lines
Another way to create objects.
So far we've been creating items by creating the objects and then adding them to the scene. But you can also create an object in the scene directly by calling one of the helper methods on the scene itself, e.g. scene.addEllipse()
. This creates the object and returns it so you can modify it as before.
import sys
from PySide6.QtWidgets import QGraphicsScene, QGraphicsView, QGraphicsRectItem, QApplication
from PySide6.QtGui import QBrush, QPen
from PySide6.QtCore import Qt
app = QApplication(sys.argv)
scene = QGraphicsScene(0, 0, 400, 200)
rect = scene.addRect(0, 0, 200, 50)
rect.setPos(50, 20)
# Define the brush (fill).
brush = QBrush(Qt.red)
rect.setBrush(brush)
# Define the pen (line)
pen = QPen(Qt.cyan)
pen.setWidth(10)
rect.setPen(pen)
view = QGraphicsView(scene)
view.show()
app.exec()
Feel free to use whichever form you find most comfortable in your code.
You can only use this approach for the built-in QGraphicsItem
object types.
Building a more complex scene
So far we've built a simple scene using the basic QGraphicsRectItem
and QGraphicsEllipseItem
shapes. Now let's use some other QGraphicsItem
objects to build a more complex scene, including lines, text and QPixmap
(images).
from PySide6.QtCore import QPointF, Qt
from PySide6.QtWidgets import QGraphicsRectItem, QGraphicsScene, QGraphicsView, QApplication
from PySide6.QtGui import QBrush, QPainter, QPen, QPixmap, QPolygonF
import sys
app = QApplication(sys.argv)
scene = QGraphicsScene(0, 0, 400, 200)
rectitem = QGraphicsRectItem(0, 0, 360, 20)
rectitem.setPos(20, 20)
rectitem.setBrush(QBrush(Qt.red))
rectitem.setPen(QPen(Qt.cyan))
scene.addItem(rectitem)
textitem = scene.addText("QGraphics is fun!")
textitem.setPos(100, 100)
scene.addPolygon(
QPolygonF(
[
QPointF(30, 60),
QPointF(270, 40),
QPointF(400, 200),
QPointF(20, 150),
]),
QPen(Qt.darkGreen),
)
pixmap = QPixmap("cat.jpg")
pixmapitem = scene.addPixmap(pixmap)
pixmapitem.setPos(250, 70)
view = QGraphicsView(scene)
view.setRenderHint(QPainter.Antialiasing)
view.show()
app.exec()
If you run the example above you'll see the following scene.
Scene with multiple items including a rectangle, polygon, text and a pixmap.
Let's step through the code looking at the interesting bits.
Polygons are defined using a series of QPointF
objects which give the coordinates relative to the items position. So, for example if you create a polygon object with a point at 30, 20 and then move this polygon object X & Y coordinates 50, 40 then the point will be displayed at 80, 60 in the scene.
Points inside an item are always relative to the item itself, and item coordinates are always relative to the scene -- or the item's parent, if it has one. We'll take a closer look at the Graphics View coordinate system in the next tutorial.
To add an image to the scene we can open it from a file using QPixmap()
. This creates a QPixmap
object, which can then in turn add to the scene using scene.addPixmap(pixmap)
. This returns a QGraphicsPixmapItem
which is the QGraphicsItem
type for the pixmap -- a wrapper than handles displaying the pixmap in the scene. You can use this object to perform any changes to item in the scene.
The multiple layers of objects can get confusing, so it's important to choose sensible variable names which make clear the distinction between, e.g. the pixmap itself and the pixmap item that contains it.
Finally, we set the flag RenderHint,Antialiasing
on the view to smooth the edges of diagonal lines. You almost always want to enable this on your views as otherwise any rotated objects will look very ugly indeed. Below is our scene without antialiasing enabled, you can see the jagged lines on the polygon.
Scene with antialiasing disabled.
Antialiasing has a (small) performance impact however, so if you are building scenes with millions of rotated items it may in some cases make sense to turn it off.
Adding graphics views to Qt layouts
The QGraphicsView
is subclassed from QWidget
, meaning it can be placed in layouts just like any other widget. In the following example we add the view to a simple interface, with buttons which perform a basic effect on the view -- raising and lowering selected item's ZValue. This has the effect of allowing us to move items in front and behind other objects.
The full code is given below.
import sys
from PySide6.QtCore import Qt
from PySide6.QtGui import QBrush, QPainter, QPen
from PySide6.QtWidgets import (
QApplication,
QGraphicsEllipseItem,
QGraphicsItem,
QGraphicsRectItem,
QGraphicsScene,
QGraphicsView,
QHBoxLayout,
QPushButton,
QSlider,
QVBoxLayout,
QWidget,
)
class Window(QWidget):
def __init__(self):
super().__init__()
# Defining a scene rect of 400x200, with it's origin at 0,0.
# If we don't set this on creation, we can set it later with .setSceneRect
self.scene = QGraphicsScene(0, 0, 400, 200)
# Draw a rectangle item, setting the dimensions.
rect = QGraphicsRectItem(0, 0, 200, 50)
rect.setPos(50, 20)
brush = QBrush(Qt.red)
rect.setBrush(brush)
# Define the pen (line)
pen = QPen(Qt.cyan)
pen.setWidth(10)
rect.setPen(pen)
ellipse = QGraphicsEllipseItem(0, 0, 100, 100)
ellipse.setPos(75, 30)
brush = QBrush(Qt.blue)
ellipse.setBrush(brush)
pen = QPen(Qt.green)
pen.setWidth(5)
ellipse.setPen(pen)
# Add the items to the scene. Items are stacked in the order they are added.
self.scene.addItem(ellipse)
self.scene.addItem(rect)
# Set all items as moveable and selectable.
for item in self.scene.items():
item.setFlag(QGraphicsItem.ItemIsMovable)
item.setFlag(QGraphicsItem.ItemIsSelectable)
# Define our layout.
vbox = QVBoxLayout()
up = QPushButton("Up")
up.clicked.connect(self.up)
vbox.addWidget(up)
down = QPushButton("Down")
down.clicked.connect(self.down)
vbox.addWidget(down)
rotate = QSlider()
rotate.setRange(0, 360)
rotate.valueChanged.connect(self.rotate)
vbox.addWidget(rotate)
view = QGraphicsView(self.scene)
view.setRenderHint(QPainter.Antialiasing)
hbox = QHBoxLayout(self)
hbox.addLayout(vbox)
hbox.addWidget(view)
self.setLayout(hbox)
def up(self):
""" Iterate all selected items in the view, moving them forward. """
items = self.scene.selectedItems()
for item in items:
z = item.zValue()
item.setZValue(z + 1)
def down(self):
""" Iterate all selected items in the view, moving them backward. """
items = self.scene.selectedItems()
for item in items:
z = item.zValue()
item.setZValue(z - 1)
def rotate(self, value):
""" Rotate the object by the received number of degrees """
items = self.scene.selectedItems()
for item in items:
item.setRotation(value)
app = QApplication(sys.argv)
w = Window()
w.show()
app.exec()
If you run this, you will get a window like that shown below. By selecting an item in the graphics view and then clicking either the "Up" or "Down" button you can move items up and down within the scene -- behind and in front of one another. The items are all moveable, so you can drag them around too. Clicking on the slider will rotate the currently selected items by the set number of degrees.
A graphics scene with some custom controls
The raising and lowering is handled by our custom methods up
and down
, which work by iterating over the currently selected items in the scene -- retrieved using scene.selectedItems()
and then getting the items z value and increasing or decreasing it respectively.
def up(self):
""" Iterate all selected items in the view, moving them forward. """
items = self.scene.selectedItems()
for item in items:
z = item.zValue()
item.setZValue(z + 1)
While rotation is handled using the item.setRotation
method. This receives the current angle from the QSlider
and again, applies it to any currently selected items in the scene.
def rotate(self, value):
""" Rotate the object by the received number of degrees. """
items = self.scene.selectedItems()
for item in items:
item.setRotation(value)
Take a look at the QGraphicsItem documentation for some other properties you can control with widgets and try extending the interface to allow you to change them dynamically.
Hopefully this quick introduction to the Qt Graphics View framework has given you some ideas of what you can do with it. In the next tutorials we'll look at how events and user interaction can be handled on items and how to create custom & compound items for your own scenes.
KD Reports 2.3.0
KD Reports 2.3.0
We’re pleased to announce the release of KD Reports 2.3.0, the latest version of our reporting tool for Qt applications. This marks our first major update in two years, bringing several bug fixes and new features that further improve the experience of generating reports.
What is KD Reports?
KD Reports is a versatile tool for generating reports directly from Qt applications. It supports creating printable and exportable reports using code or XML, featuring elements like text, tables, charts, headers, and footers. Whether for visualizing database content, generating invoices, or producing formatted printouts, KD Reports makes it easy to create structured reports within your Qt projects.
What’s New in KD Reports 2.3.0?
The new release includes essential bug fixes and feature enhancements that make KD Reports even more robust and user-friendly.

Bug Fixes
The 2.3.0 release addresses several important issues to improve stability and compatibility. One major fix resolves an infinite loop and other problems caused by changes in QTextFormat
behavior in Qt 6.7. Right-aligned tabs, which previously didn’t work when paragraph margins were set, have also been corrected. High-DPI rendering has been improved to eliminate blurriness in displays where the device pixel ratio (DPR) is not equal to 1. Furthermore, an issue with result codes being overwritten in the KDReportsPreviewDialog
has been fixed. Finally, table borders, which were lost after upgrading to Qt 6.8, now behave as expected, maintaining their cell borders throughout.
New Features
KD Reports 2.3.0 introduces several new features aimed at providing more customization and flexibility in report generation. For instance, the AutoTableElement
now supports customization of header styling via the new setHorizontalHeaderFormatFunction
and setVerticalHeaderFormatFunction
, which are demonstrated in the PriceList example. Additionally, individual table cell formatting has been enhanced with the setCellFormatFunction
, allowing for customization of borders and padding. Text alignment within table cells has also been improved with the new setVerticalAlignment
feature, making it easy to vertically center or top-align text when using different font sizes within the same row.
The AbstractTableElement
now allows setting column constraints while leaving some columns without constraints—just pass {}
for unconstrained columns. This feature is particularly useful when setting constraints for columns further to the right. Also, the TableElement
has gained rowCount()
and columnCount()
methods, which can be used in dynamic scenarios, such as applying alternate background colors to rows.
Lastly, you can now disable the progress dialog during printing or PDF export using setProgressDialogEnabled(false)
. This is useful for applications that generate multiple documents or handle progress tracking internally, offering more control over the user interface during these operations.
You can explore all the new features and improvements in KD Reports 2.3.0 on its GitHub page. Download the latest release and check out the detailed changes to see how they can enhance your reporting tasks. Feel free to share your feedback or report any issues you encounter along the way.
The post KD Reports 2.3.0 appeared first on KDAB.
Implementing an Audio Mixer, Part 2
Implementing an Audio Mixer, Part 2
Recap
In Part 1, we covered PCM audio and superimposing waveforms, and developed an algorithm to combine an arbitrary number of audio streams into one.
Now we need to use these ideas to finish a full implementation using Qt Multimedia.
Using Qt Multimedia for Audio Device Access
So what do we need? Well, we want to use a single QAudioOutput
, which we pass an audio device and a supported audio format.
We can get those like this:
const QAudioDeviceInfo &device = QAudioDeviceInfo::defaultOutputDevice();
const QAudioFormat &format = device.preferredFormat();
Let’s construct our QAudioOutput
object using the device and format:
static QAudioOutput audioOutput(device, format);
Now, to use it to write data, we have to call start
on the audio output, passing in a QIODevice *
.
Normally we would use the QIODevice
subclass QBuffer
for a single audio buffer. But in this case, we want our own subclass of QIODevice
, so we can combine multiple buffers into one IO device.
We’ll call our subclass MixerStream
. This is where we will do our bufferCombine
, and keep our member list of streams mStreams
.
We will also need another stream type for mStreams
. For now let’s just call it DecoderStream
, forward declare it, and worry about its implementation later.
One thing that’s good to know at this point is DecoderStream
objects will get the data buffers we need by decoding audio data from a file. Because of this, we’ll need to keep our audio format from above to as a data member mFormat
. Then we can pass it to decoders when they need it.
Implementing MixerStream
Since we are subclassing QIODevice
, we need to provide reimplementations for these two protected virtual
functions:
virtual qint64 QIODevice::readData(char *data, qint64 maxSize);
virtual qint64 QIODevice::writeData(const char *data, qint64 maxSize);
We also want to provide a way to open new streams that we’ll add to mStreams
, given a filename. We’ll call this function openStream
. We can also allow looping a stream multiple times, so let’s add a parameter for that and give it a default value of 1
.
Additionally, we’ll need a user-defined destructor to delete any pointers in the list that might remain if the MixerStream
is abruptly destructed.
// mixerstream.h
#pragma once
#include <QAudioFormat>
#include <QAudioOutput>
#include <QIODevice>
class DecodeStream;
class MixerStream : public QIODevice
{
Q_OBJECT
public:
explicit MixerStream(const QAudioFormat &format);
~MixerStream();
void openStream(const QString &fileName, int loops = 1);
protected:
qint64 readData(char *data, qint64 maxSize) override;
qint64 writeData(const char *data, qint64 maxSize) override;
private:
QAudioFormat mFormat;
QList<DecodeStream *> mStreams;
};
Notice that combineSamples
isn’t in the header. It’s a pretty basic function that doesn’t require any members, so we can just implement it as a free function.
Let’s put it in a header mixer.h
and wrap it in a namespace
:
// mixer.h
#pragma once
#include <QtGlobal>
#include <limits>
namespace Mixer
{
inline qint16 combineSamples(qint32 samp1, qint32 samp2)
{
const auto sum = samp1 + samp2;
if (std::numeric_limits<qint16>::max() < sum)
return std::numeric_limits<qint16>::max();
if (std::numeric_limits<qint16>::min() > sum)
return std::numeric_limits<qint16>::min();
return sum;
}
} // namespace Mixer
There are some very basic things we can get out of the way quickly in the MixerStream
cpp file. Recall that we must implement these member functions:
explicit MixerStream(const QAudioFormat &format);
~MixerStream();
void openStream(const QString &fileName, int loops = 1);
qint64 readData(char *data, qint64 maxSize) override;
qint64 writeData(const char *data, qint64 maxSize) override;
The constructor is very simple:
MixerStream::MixerStream(const QAudioFormat &format)
: mFormat(format)
{
setOpenMode(QIODevice::ReadOnly);
}
Here we use setOpenMode
to automatically open our device in read-only mode, so we don’t have to call open()
directly from outside the class.
Also, since it’s going to be read-only, our reimplementation of QIODevice::writeData
will do nothing:
qint64 MixerStream::writeData([[maybe_unused]] const char *data,
[[maybe_unused]] qint64 maxSize)
{
Q_ASSERT_X(false, "writeData", "not implemented");
return 0;
}
The custom destructor we need is also quite simple:
MixerStream::~MixerStream()
{
while (!mStreams.empty())
delete mStreams.takeLast();
}
readData
will be almost exactly the same as the implementation we did earlier, but returning qint64
. The return value is meant to be the amount of data written, which in our case is just the maxSize
argument given to it, as we write fixed-size buffers.
Additionally, we should call qAsConst
(or std::as_const
) on mStreams
in the range-for to avoid detaching the Qt container. For more on qAsConst
and range-based for
loops, see Jesper Pederson’s blog post on the topic.
qint64 MixerStream::readData(char *data, qint64 maxSize)
{
memset(data, 0, maxSize);
constexpr qint16 bitDepth = sizeof(qint16);
const qint16 numSamples = maxSize / bitDepth;
for (auto *stream : qAsConst(mStreams))
{
auto *cursor = reinterpret_cast<qint16 *>(data);
qint16 sample;
for (int i = 0; i < numSamples; ++i, ++cursor)
if (stream->read(reinterpret_cast<char *>(&sample), bitDepth))
*cursor = Mixer::combineSamples(sample, *cursor);
}
return maxSize;
}
That only leaves us with openStream
. This one will require us to discuss DecodeStream
and its interface.
The function should construct a new DecodeStream
on the heap, which will need a file name and format. DecodeStream
, as implied by its name, needs to decode audio files to PCM data. We’ll use a QAudioDecoder
within DecodeStream
to accomplish this, and for that, we need to pass mFormat
to the constructor. We also need to pass loops
to the constructor, as each stream can have a different number of loops.
Now our constructor call will look like this:
DecodeStream(fileName, mFormat, loops);
We can then use operator<<
to add it to mStreams
.
Finally, we need to remove it from the list when it’s done. We’ll give it a Qt signal, finished
, and connect it to a lambda expression that will remove the stream from the list and delete the pointer.
Our completed openStream
function now looks like this:
void MixerStream::openStream(const QString &fileName, int loops)
{
auto *decodeStream = new DecodeStream(fileName, mFormat, loops);
mStreams << decodeStream;
connect(decodeStream, &DecodeStream::finished, this, [this, decodeStream]() {
mStreams.removeAll(decodeStream);
decodeStream->deleteLater();
});
}
Recall from earlier that we call read
on a stream, which takes a char *
to which the read data will be copied and a qint64
representing the size of the data.
This is a QIODevice
function, which will internally call readData
. Thus, DecoderStream
also needs to be a QIODevice
.
Getting PCM Data for DecodeStream
In DecodeStream
, we need readData
to spit out PCM data, so we need to decode our audio file to get its contents in PCM format. In Qt Multimedia, we use a QAudioDecoder
for this. We pass it an audio format to decode to, and a source device, in this case a QFile
file handle for our audio file.
When a QAudioDecoder
's start
method is called, it will begin decoding the source file in a non-blocking manner, emitting a signal bufferReady
when a full buffer of decoded PCM data is available.
On that signal, we can call the decoder’s read
method, which gives us a QAudioBuffer
. To store in a data member in DecodeStream
, we use a QByteArray
, which we can interact with using QBuffers
to get a QIODevice
interface for reading and writing. This is the ideal way to work with buffers of bytes to read or write in Qt.
We’ll make two QBuffers
: one for writing data to the QByteArray
(we’ll call it mInputBuffer
), and one for reading from the QByteArray
(we’ll call it mOutputBuffer
). The reason for using two buffers rather than one read/write buffer is so the read and write positions can be independent. Otherwise, we will encounter more stuttering.
So when we get the bufferReady
signal, we’ll want to do something like this:
const QAudioBuffer buffer = mDecoder.read();
mInputBuf.write(buffer.data<char>(), buffer.byteCount());
We’ll also need to have some sort of state enum
. The reason for this is that when we are finished with the stream and emit finished()
, we remove and delete the stream from a connected lambda expression, but read
might still be called before that has completed. Thus, we want to only read from the buffer when the state is Playing
.
Let’s update mixer.h
to put the enum
in namespace Mixer
:
#pragma once
#include <QtGlobal>
#include <limits>
namespace Mixer
{
enum State
{
Playing,
Stopped
};
inline qint16 combineSamples(qint32 samp1, qint32 samp2)
{
const auto sum = samp1 + samp2;
if (std::numeric_limits<qint16>::max() < sum)
return std::numeric_limits<qint16>::max();
if (std::numeric_limits<qint16>::min() > sum)
return std::numeric_limits<qint16>::min();
return sum;
}
} // namespace Mixer
Implementing DecodeStream
Now that we understand all the data members we need to use, let’s see what our header for DecodeStream
looks like:
// decodestream.h
#pragma once
#include "mixer.h"
#include <QAudioDecoder>
#include <QBuffer>
#include <QFile>
class DecodeStream : public QIODevice
{
Q_OBJECT
public:
explicit DecodeStream(const QString &fileName, const QAudioFormat &format, int loops);
protected:
qint64 readData(char *data, qint64 maxSize) override;
qint64 writeData(const char *data, qint64 maxSize) override;
signals:
void finished();
private:
QFile mSourceFile;
QByteArray mData;
QBuffer mInputBuf;
QBuffer mOutputBuf;
QAudioDecoder mDecoder;
QAudioFormat mFormat;
Mixer::State mState;
int mLoopsLeft;
};
In the constructor, we’ll initialize our private
members, open the DecodeStream
in read-only (like we did earlier), make sure we open the QFile
and QBuffer
s successfully, and finally set up our QAudioDecoder
.
DecodeStream::DecodeStream(const QString &fileName, const QAudioFormat &format, int loops)
: mSourceFile(fileName)
, mInputBuf(&mData)
, mOutputBuf(&mData)
, mFormat(format)
, mState(Mixer::Playing)
, mLoopsLeft(loops)
{
setOpenMode(QIODevice::ReadOnly);
const bool valid = mSourceFile.open(QIODevice::ReadOnly) &&
mOutputBuf.open(QIODevice::ReadOnly) &&
mInputBuf.open(QIODevice::WriteOnly);
Q_ASSERT(valid);
mDecoder.setAudioFormat(mFormat);
mDecoder.setSourceDevice(&mSourceFile);
mDecoder.start();
connect(&mDecoder, &QAudioDecoder::bufferReady, this, [this]() {
const QAudioBuffer buffer = mDecoder.read();
mInputBuf.write(buffer.data<char>(), buffer.byteCount());
});
}
Once again, our QIODevice
subclass is read-only, so our writeData
method looks like this:
qint64 DecodeStream::writeData([[maybe_unused]] const char *data,
[[maybe_unused]] qint64 maxSize)
{
Q_ASSERT_X(false, "writeData", "not implemented");
return 0;
}
Which leaves us with the last part of the implementation, DecodeStream
's readData
function.
We zero out the char *
with memset
to avoid any noise if there are areas that are not overwritten. Then we simply read from the QByteArray
into the char *
if mState
is Mixer::Playing
.
We check to see if we finished reading the file with QBuffer::atEnd()
, and if we are, we decrement the loops remaining. If it’s zero now, that was the last (or only) loop, so we set mState
to stopped, and emit finished()
. Either way we seek
back to position 0. Now if there are loops left, it starts reading from the beginning again.
qint64 DecodeStream::readData(char *data, qint64 maxSize)
{
memset(data, 0, maxSize);
if (mState == Mixer::Playing)
{
mOutputBuf.read(data, maxSize);
if (mOutputBuf.size() && mOutputBuf.atEnd())
{
if (--mLoopsLeft == 0)
{
mState = Mixer::Stopped;
emit finished();
}
mOutputBuf.seek(0);
}
}
return maxSize;
}
Now that we’ve implemented DecodeStream
, we can actually use MixerStream
to play two audio files at the same time!
Using MixerStream
Here’s an example snippet that shows how MixerStream
can be used to route two simultaneous audio streams into one system mixer channel:
const auto &device = QAudioDeviceInfo::defaultOutputDevice();
const auto &format = device.preferredFormat();
auto mixerStream = std::make_unique<MixerStream>(format);
auto *audioOutput = new QAudioOutput(device, format);
audioOutput->setVolume(0.5);
audioOutput->start(mixerStream.get());
mixerStream->openStream(QStringLiteral("/path/to/some/sound.wav"));
mixerStream->openStream(QStringLiteral("/path/to/something/else.mp3"), 3);
Final Remarks
The code in this series of posts is largely a reimplementation of Lova Widmark’s project QtMixer. Huge thanks to her for a great and lightweight implementation. Check the project out if you want to use something like this for a GPL-compliant project (and don’t mind that it uses qmake
).
The post Implementing an Audio Mixer, Part 2 appeared first on KDAB.
Implementing an Audio Mixer, Part 1
Implementing an Audio Mixer, Part 1
Motivation
When using Qt Multimedia to play audio files, it’s common to use QMediaPlayer
, as it supports a larger variety of formats than QSound
and QSoundEffect
. Consider a Qt application with several audio sources; for example, different notification sounds that may play simultaneously. We want to avoid cutting notification sounds off when a new one is triggered, and we don’t want to construct a queue for notification sounds, as sounds will play at the incorrect time. We instead want these sounds to overlap and play simultaneously.
Ideally, an application with audio has one output stream to the system mixer. This way in the mixer control, different applications can be set to different volume levels. However, a QMediaPlayer
instance can only play one audio source at a time, so each notification would have to construct a new QMediaPlayer
. Each player in turn opens its own stream to the system.
The result is a huge number of streams to the system mixer being opened and closed all the time, as well as QMediaPlayer
s constantly being constructed and destructed.
To resolve this, the application needs a mixer of its own. It will open a single stream to the system and combine all the audio into the one stream.
Before we can implement this, we first need to understand how PCM audio works.
PCM
As defined by Wikipedia:
Pulse-code modulation (PCM) is a method used to digitally represent sampled analog signals. It is the standard form of digital audio in computers, compact discs, digital telephony and other digital audio applications. In a PCM stream, the amplitude of the analog signal is sampled at uniform intervals, and each sample is quantized to the nearest value within a range of digital steps.
Here you can see how points are sampled in uniform intervals and quantized to the closest number that can be represented.

Description from Wikipedia: Sampling and quantization of a signal (red) for 4-bit LPCM over a time domain at specific frequency.
Think of a PCM stream as a humongous array of bytes. More specifically, it’s an array of samples, which are either integer or float values and a certain number of bytes in size. The samples are these discrete amplitude values from a waveform, organized contiguously. Think of the each element as being a y-value of a point along the wave, with the index representing an offset from
at a uniform time interval.
Here is a graph of discretely sampled points along a sinusoidal waveform similar to the one above:

Image Source: Wikimedia Commons
Description from Wikimedia Commons: Image of a discrete time sinusoid
Let’s say we have an audio waveform that is a simple sine wave, like the above examples. Each point taken at discrete intervals along the curve here is a sample, and together they approximate a continuous waveform. The distance between the samples along the x-axis is a time delta; this is the sample period. The sample rate is the inverse of this, the number of samples that are played in one second. The typical standard sample rate for audio on CDs is 44100 Hz - we can’t really hear that this data is discrete (plus, the resultant sound wave from air movement is in fact a continuous waveform).
We also have to consider the y-axis here, which represents the amplitude of the waveform at each sampled point. In the image above, amplitude
is normalized such that
. In digital audio, there are a few different ways to represent amplitude. We can’t represent all real numbers on a computer, so the representation of the range of values varies in precision.
For example, let’s say we have two different representations of the wave above: 8-bit signed integer and 16-bit signed integer. The normalized value
from the image above maps to
with 8-bit representation and
with 16-bit. Therefore, with 16-bit representation, we have 128 times as many possible values to represent the same range; it is more precise, but the required size to store each 16-bit sample is double that of 8-bit samples.
We call the chosen representation, and thus the size of each sample, the bitdepth. Some common bitdepths are 16-bit int, 24-bit int, and 32-bit float, but there are many others in existence.
Let’s consider a huge stream of 16-bit samples and a sample rate of 44100 Hz. We write samples to the audio device periodically with a fixed-size buffer; let’s say it is 4096 bytes. The device will play each sample in the buffer at the aforementioned rate. Since each sample is a contiguous 2-byte short
, we can fit 2048 samples into the buffer at once. We need to write 44100 samples in one second, so the whole buffer will be written around 21.5 times per second.
What if we have two different waveforms though, and what if one starts halfway through the other one? How do we mix them so that this buffer contains the data from both sources?
Waveform Superimposition
In the study of waves, you can superimpose two waves by adding them together. Let’s say we have two different discrete wave approximations, each represented by 20 signed 8-bit integer values. To superimpose them, for each index, add the values at that index. Some of these sums will exceed the limits of 8-bit representation, so we clamp them at the end to avoid signed integer overflow. This is known as hard clipping and is the phenomenon responsible for digital overdrive distortion.
x | Wave 1 (y1) | Wave 2 (y2) | Sum (y1+y2) | Clamped Sum |
---|---|---|---|---|
0 | +60 | −100 | −40 | -40 |
1 | −120 | +80 | −40 | −40 |
2 | +40 | +70 | +110 | +110 |
3 | −110 | −100 | −210 | −128 |
4 | +50 | −110 | −60 | −60 |
5 | −100 | +60 | −40 | −40 |
6 | +70 | +50 | +120 | +120 |
7 | −120 | −120 | −240 | −128 |
8 | +80 | −100 | −20 | −20 |
9 | −80 | +40 | −40 | −40 |
10 | +90 | +80 | +170 | +127 |
11 | −100 | −90 | −190 | −128 |
12 | +60 | −120 | −60 | −60 |
13 | −120 | +70 | −50 | −50 |
14 | +80 | −120 | −40 | −40 |
15 | −110 | +80 | −30 | −30 |
16 | +90 | −100 | −10 | −10 |
17 | −110 | +90 | −20 | −20 |
18 | +100 | −110 | −10 | −10 |
19 | −120 | −120 | −240 | −128 |
Now let’s implement this in C++. We’ll start small, and just combine two samples.
Note: we will use qint types here, but qint16 will be the same as int16_t and short on most systems, and similarly qint32 will correspond to int32_t and int.
qint16 combineSamples(qint32 samp1, qint32 samp2)
{
const auto sum = samp1 + samp2;
if (std::numeric_limits<qint16>::max() < sum)
return std::numeric_limits<qint16>::max();
if (std::numeric_limits<qint16>::min() > sum)
return std::numeric_limits<qint16>::min();
return sum;
}
This is quite a simple implementation. We use a function combineSamples
and pass in two 16-bit values, but they will be converted to 32-bit as arguments and summed. This sum is clamped to the limits of 16-bit integer representation using std::numeric_limits
in the <limits>
header of the standard library. We then return the sum, at which point it is re-converted to a 16-bit value.
Combining Samples for an Arbitrary Number of Audio Streams
Now consider an arbitrary number of audio streams n
. For each sample position, we must sum the samples of all n
streams.
Let’s assume we have some sort of audio stream type (we’ll implement it later), and a list called mStreams
containing pointers to instances of this stream type. We need to implement a function that loops through mStreams
and makes calls to our combineSamples
function, accumulating a sum into a new buffer.
Assume each stream in mStreams
has a member function read(char *, qint64)
. We can copy one sample into a char *
by passing it to read
, along with a qint64
representing the size of a sample (bitdepth). Remember that our bitdepth is 16-bit integer, so this size is just sizeof(qint16)
.
Using read
on all the streams in mStreams
and calling combineSamples
to accumulate a sum might look something like this:
qint16 accumulatedSum = 0;
for (auto *stream : mStreams)
{
// call stream->read(char *, qint64)
// to read a sample from the stream into streamSample
qint16 streamSample;
stream->read(reinterpret_cast<char *>(&streamSample), sizeof(qint16)));
// accumulate
accumulatedSum = combineSamples(sample, accumulatedSum);
}
The first pass will add samples from the first stream to zero, effectively copying it to accumulatedSum
. When we move to another stream, the samples from the second stream will be added to those copied values from the first stream. This continues, so the call to combineSamples
for a third stream would combine the third stream’s sample with the sum of the first two. We continue to add directly into the buffer until we have combined all the streams.
Combining All Samples for a Buffer
Now let’s use this concept to add all the samples for a buffer. We’ll make a function that takes a buffer char *data
and its size qint64 maxSize
. We’ll write our accumulated samples into this buffer, reading all samples from the streams and adding them using the method above.
The function signature looks like this:
void readData(char *data, qint64 maxSize);
Let’s achieve more efficiency by using a constexpr
variable for the bitdepth:
constexpr qint16 bitDepth = sizeof(qint16);
There’s no reason to call sizeof
multiple times, especially considering sizeof(qint16)
can be evaluated as a literal at compile-time.
With the size of each sample and the size of the buffer, we can get the total number of samples to write:
const qint16 numSamples = maxSize / bitDepth;
For each stream in mStreams
we need to read each sample up to numSamples
. As the sample index increments, a pointer to the buffer data needs to also be incremented, so we can write our results at the correct location in the buffer.
That looks like this:
void readData(char *data, qint64 maxSize)
{
// start with 0 in the buffer
memset(data, 0, maxSize);
constexpr qint16 bitDepth = sizeof(qint16);
const qint16 numSamples = maxSize / bitDepth;
for (auto *stream : mStreams)
{
// this pointer will be incremented across the buffer
auto *cursor = reinterpret_cast<qint16 *>(data);
qint16 sample;
for (int i = 0; i < numSamples; ++i, ++cursor)
if (stream->read(reinterpret_cast<char *>(&sample), bitDepth))
*cursor = combineSamples(sample, *cursor);
}
}
The idea here is that we can start playing new audio sources by adding new streams to mStreams
. If we add a second stream halfway through a first stream playing, the next buffer for the first stream will be combined with the first buffer of this new stream. When we’re done playing a stream, we just drop it from the list.
Next Steps
In Part 2, we'll use Qt Multimedia to fully implement our mixer, connect to our audio device, and test it on some audio files.
The post Implementing an Audio Mixer, Part 1 appeared first on KDAB.