Qt Desktop Days – Day 4

Wait, there’s more? Yes, indeed. Qt Desktop Days delivered a lot of great content we’ve just got to share. 

VLC and Qt, a history

If you need to play the widest variety of audio, video, or streaming formats on the planet, you probably know about VLC (the “cone player”). But did you know that VLC uses Qt? They didn’t always. Hear the history of this interesting project from Jean-Baptiste Kempf, one of the lead developers on VLC, a project started by rebellious French university students over two decades ago that is still going strong today.

We learn from Jean-Baptiste some interesting platform constraints of the VLC project (like unbelievably, they still support OS/2!), and how their abstraction architecture has been able to grow and thrive without software bloat despite years of changing software, multiple new platforms, and loads of new features. We also learn what factors drove the switch from wxWidgets to Qt and what the team did to keep their high-performance video codecs working smoothly in their upcoming port from Qt4 to Qt5. If you’re tackling your own open-source project, the dynamic success of VLC as delivered by Jean-Baptiste might be just the inspiration you need.

 

 

Test smarter using Coco

If you saw our earlier talk about using Squish, you’ll have an idea of how easy it can be to automate UI testing and how that type of testing can really complement your testing regime. In this talk, Nick Medeiros from froglogic delves further into testing finesse by showing us how to get the most out of code coverage with Coco. By taking an example Qt app, Nick shows us how to beef up our test suite with code coverage.

Nick starts by explaining how code coverage identifies untested code, redundant tests, and dead code. He also shows how sophisticated code coverage has become with multiple condition coverage and modified condition/decision coverage. After walking us through how to instrument our Qt apps, Nick gives us a live demo of code coverage in action. He also shows us how to make our tests run more efficiently while using less human brainpower. He ends by explaining how to use code coverage with continuous integration and outsourced QA black box testing, showing that it can be adapted to a very modern workflow.

 

 

How to breathe new QML life into QWidget-based apps from the 2000s

If you’ve ever seen Tantacrul’s YouTube channel, you know he loves destroying bad music software design with a no-punches-pulled criticism and the viral entertainment factor that puts those videos in front of millions of potential customers. What if he turned his critical eye towards your software? That brings us to the last talk on day four from Vasily Pereverzev, the engineering team lead for MuseScore. After MuseScore got skewered by Tantacrul, what do you think they did? They hired him to redesign their UI!

Vasily shares with us the MuseScore story. As the world’s most popular software for music notation, they already used QWidgets. But in their new fully reconsidered (and Tantacrul-approved) UI design, they needed a lot of fluid animation and customization of components – areas much better served by QML. Vasily shares the three options they considered in adding QML to their app, and why after everything was considered they went with a hybrid approach. If you’re considering an app facelift from QWidgets to QML, there’s a lot to learn from Vasily including where it makes the most sense, where you’re better to stick with QWidgets, and how to get the two UI frameworks to play together harmoniously.

 

 

 

Visit the Qt Desktop Days playlist on our Youtube channel here.

The post Qt Desktop Days – Day 4 appeared first on KDAB.

Deploying app and generating offline installers for Windows Qt Installer Framework tutorial

Development is not an only part of product delivering – deployment and maintenance are both equally important parts of the product lifecycle. That is why Qt lend us a hand by providing Installer Framework. It is a set of tools that allows not only to create good-looking and functional installers but also update the app, provide tools for maintaining it, and much more. In this tutorial, you will learn the basics of Qt Installer Framework and find out how to generate your first offline installer for Windows. Let’s go!

 

How to get Qt Installer Framework? 

Qt Installer framework can be easily installed via Qt Maintenance tool or downloaded from Qt repository. When using a maintenance tool, it can be found in the Developer and Designer Tools section:

 

After download it should be stored in [QtDirectory]/Tools/QtInstallerFramework

How to generate installer with Qt Installer Framework? 

1. Create a deployment folder 

Before generating an installer, you have to prepare your app for deployment. The first step is to create a deployment folder where all necessary files for app execution and installer generation will be stored. Inside it, you should create two directories: „config” and „packages”.

 

As the name suggests, the first directory will contain configuration files for your installer – in this case a single XML file. What should this file contain?

 

2.  Create a config file

The configuration file consists of some general information about the app: installation directory, name of the installer, etc. You can make some interesting things by modifying this file – for example, add a „run after installation” checkbox, modify installer UI or even add remote repositories for fetching app updates. You can learn about all the available tags in the Qt Installer documentation.

 

Let’s create such a file. The basic one should look like this:

<?xml version="1.0" encoding="UTF-8"?>
<Installer>
    <Name>APP NAME</Name>
    <Version>1.0.0</Version>
    <Title>INSTALLER WINDOW TITLE</Title>
    <Publisher>Scythe Studio</Publisher>
    <StartMenuDir>START_MENU_DIR</StartMenuDir>
    <TargetDir>@HomeDir@/APP_DIR</TargetDir>
</Installer>

Although it is minimalistic, for now, this would be enough.

 

3. Create a package 

Before you go any further, you should learn what the package actually is. The package is a module that contains a certain version or some parts of the app. If you include many packages, the installation can be customized – users can select what they want to install. Qt itself is a great example of such approach. Let’s take a look at the tree structure in the Qt installer:

 

As you see, the framework is divided into several packages containing kits for different platforms and modules with optionals functionality like a web engine.

 

According to the documentation, the packages directory should have following structure:

-packages
    - com.vendor.root
        - data
        - meta
    - com.vendor.root.component1
        - data
        - meta
    - com.vendor.root.component1.subcomponent1
        - data
        - meta
    - com.vendor.root.component2
        - data
        - meta

With this knowledge, you can now start creating the first packages for our app. To make things clear and simple, for now, we’ll roll on with only two packages: a 64-bit app version and a 32-bit one.

 

Begin by creating proper directories for the packages. We will stick to the „com.developername.shortpackagename” naming pattern:

 

 

What about packages content? Let’s begin populating them!

 

4. Add metadata 

Each package should contain two main directories inside: „data” and „meta”. First will contain executables, libraries, and other files necessary for running the app, while second hold information about the package. The „Meta” folder will contain only two files, in this case, so start with it.

 

 

Metadata should consist of „license.txt” and „package.xml” files. The text file is obvious – it provides the installer with license content. It doesn’t have any required format, but HTML tags are supported if you need some styling.

 

Now let’s talk about „package.xml” – this one is more interesting. This file contains information about the package, which will be presented in the installer window. By putting additional tags you can add custom installer pages, translations, dependencies between other packages and many more. We will talk about this file again, in another part of the tutorial, which will be published in the future. The „package.xml” content should look like this for now:

 

<?xml version="1.0" encoding="UTF-8"?>
<Package>
    <DisplayName>PACKAGE NAME</DisplayName>
    <Description>This is going to install APP NAME on your machine</Description>
    <Version>1.0.0</Version>
    <ReleaseDate>2020-06-17</ReleaseDate>
    <Licenses>
        <License name="LICENSE TITLE" file="license.txt" />
    </Licenses>
    <Default>true</Default>
</Package>

Most of the tags are self-explanatory. If you want to learn more about other tags see documentation.

 

5. Fill data  directory

When you add necessary files to the „meta” directory, move one to the „data” folder. This directory contains all the app files that the package holds. When installing the package, its contents will be unpacked to the target directory. In order to keep the installation folder clear, we suggest creating an additional subfolder in the data directory.

 

As there is already a large number of folders we talked about, we’ll call this one „package files directory”. With that keeping track would be simpler.

 

Now the crucial part of deployment preparation begins. Inside the package files directory, you need to add a substantial amount of files needed to run an app outside the Qt Creator IDE: all the executables, Qt and external libraries, dll files, etc. Fortunately, the Qt Framework will help us achieve this by providing a set of tools for deployment.

 

Now add the executable file to the package files directory. Simply copy it from the build folder. Now open the command line terminal in the package files directory. It is needed to run „windeployqt.exe” – a tool created for automatic loading of all the Qt dll files needed for running the app. To run this tool use this command:

[QtFolder]\[QtVersionThatYouUse]\[SelectedKitDirectory]\bin\windeployqt.exe APPNAME.exe --qmldir [DirectoryContainingProjectQMLSourcecode] --compiler-runtime

This command adds (almost) all necessary binaries and files that you need to launch a QML based app. The -qmldir flag is used to scan your QML code and add all the needed runtime libraries from the Qt Quick module. After this flag put your project directory – don’t worry it will be scanned recursively. If your app is not QML based, you can skip this flag.

 

In the tutorial case the full command should look like this:

C:\Qt\5.15.0\mingw81_64\bin\windeployqt.exe QtInstallerTutorial.exe --qmldir C:\Projects\QtInstallerTutorial --compiler-runtime

 

When the tool finishes its work you can see that now the package files directory is getting crowded.

Currently, windeployqt tool tends to ship a lot of unnecessary files here, but even now there is no every file you need. Not yet…

 

6. Add the  compiler-specific files

If you try to run the application now you’ll get an error pop-up showing info about missing files. Depending on the configuration, compiler-specific and some other libraries must be redistributed along with your application. According to the documentation, the basic ones are:

  • Qt
    • QT5CORE.DLL – The QtCore runtime

    • QT5GUI.DLL – The QtGui runtime

    • QT5WIDGETS.DLL – The QtWidgets runtime

  • VC++ 14.0 (2015)

    • VCCORLIB140.DLL, VCRUNTIME140D.DLL – The C runtime

    • MSVCP140.DLL – The C++ runtime

  • MinGW
    • LIBWINPTHREAD-1.DLL

    • LIBGCC_S_DW2-1.DLL

    • LIBSTDC++-6.DLL

 

However, to make sure that you didn’t miss anything, following Qt docs advice, we suggest using the Dependency Walker tool. After running it you can easily see what dependencies are missing.

 

In this case, the LIBGCC_S_SEH-1.DLL and LIBSTDC++-6.DLL are missing. You can find them in the Qt folder, inside the kit directory:

 

Just copy them to your package files directory. Before finishing the deployment preparation, you should try running the .exe file, as a good practice. This way you will make sure that the app has all the dependencies in place. Currently, the app is still unable to run. We skipped copying LIBWINPTHREAD-1.DLL on purpose, to show that checking if everything is fine, before ending this step is essential. To end this step simply copy LIBWINPTHREAD-1.DLL from the kit directory and your package should be ready for distribution!

 

Note that the dll files we mentioned before may differ depending on the compiler you use. Also do not forget about applying steps 4 to 7 for all packages you created: in this case, we repeated everything for the 32-bit package.

 

 

7. Generate installer 

When all of the packages are prepared for deployment you can finally generate an installer. To do it, the „binarycreator” tool from Qt Installer Framework will be needed.

 

First, open the terminal inside the deployment folder.

 

Now launch binarycreator.exe using the following command:

[Qt folder]\Tools\QtInstallerFramework\[QtInstallerVersion]\bin\binarycreator.exe -c config/config.xml -p packages -f NAMEOFTHEINSTALLER

The -c flag tells binarycreator where to look for the config file, the -p flag informs where the packages are located, while -f shows binarycreator that you are creating an offline installer. To learn more about available flags look at the documentation.

 

In the case of the tutorial full command looks like this:

C:\Qt\Tools\QtInstallerFramework\4.0\bin\binarycreator.exe -c config/config.xml -p packages -f QtInstallerTutorial

Don’t worry after hitting enter – your terminal is not frozen. Installer generator can take some time to finish its work. After a while a fresh installer should appear in your deployment directory:

 

The functional installer should look as follows:

Notice that the installer language was automatically changed to system language – Polish, in this case.

 

Congratulations – you have generated your first installer! Now feel free to test it out and make sure that everything was set up properly. Installer generated this way can be handled directly to the end-user. The only thing that users need to do is to launch it and proceed with the provided instructions.

 

It is also worth mentioning that after installing an app with your new installer the maintenance tool for your app is automatically provided. It allows users to add or delete packages, update app (if you set up repositories) or uninstall the whole app.

 

Other purposes

A good application for the installer generator is definitely using it with the CI/CD system on your repository. With that you can automate the whole process, so the client would always have the newest version of the installer. Every time you merge any changes to the master, proper commands would be executed, without the need of doing anything manually.

 

Another interesting fact is that with the newest version of the Qt Installer Framework 4.0 the new CLI has been added, allowing you to do some interesting things. The main functionality it provides is interacting with installers and maintenance tools: installing, updating, and modifying the app with command lines.

 

It can come in handy in cases like auto-updating – your app can simply run a maintenance tool when it gets information from the API that the new version is available or even check for updates directly from the maintenance tool. You can also call functions like uninstaller from the app. If you want to learn more about it, check out the CLI documentation page.

 

Summary 

 

That is all for this entry. Now you know how to generate an offline installer for the Windows Qt app using Qt Installer Framework. Make sure to check out other entries on the Scythe-Studio blog and like our Facebook profile to make sure you won’t miss future Qt tutorials.

Porting from Qt 5 to Qt 6 using Qt5Compat library

Porting from Qt 5 to Qt 6 has been intentionally kept easy. There has been a conscious effort throughout the development of Qt 6 to maintain as much source compatibility with Qt 5 as possible. Still, some effort is involved in porting. This short post summarizes some of the steps required when porting to Qt 6.

Release 3.7.0: Build Your Desktop Qt App with Cloud Builds and Preview Code with Online QML Web Editor

This post of the Felgo 3.7.0 series highlights the Felgo tooling improvements that come with the latest update. The QML Web Editor features a new WebAssembly Live Preview and you can now use Felgo Cloud Builds to build Qt projects for desktop like you are used to for Android or iOS. Finally, the new release also brings a lot of improvements for QML Hot Reload for Felgo Live.

   

Opening links in a new window with QWebEngineView — Redirect links to a separate floating browser window

It's quite a common practice to use QWebEngineView as a documentation (or document) browser in PyQt5 applications as it allows the documentation to be created using familiar tools. You can build HTML documentation and bundle it with your application (or host them remotely) then allow your users to browse them within the app.

However, this raises an issue when the document contains links to external resources -- how should these links be handled? It's a weird user experience if your users, through clicking a series of links, can end up on Google or Facebook inside your application. One way to avoid this is to enforce opening of certain links in external windows or browsers, ensuring your documentation browser remains just that.

In this quick tutorial we'll look at how to implement custom link handling in your Qt browser and use this to redirect clicked links to different windows or the user's desktop browser.

The skeleton web browser code is shown below. We'll modify this to add the open in new window behavior.

python
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineView

import os
import sys


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.browser = QWebEngineView()
        self.browser.setUrl(QUrl("https://www.learnpyqt.com"))
        self.setCentralWidget(self.browser)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec_()

python
from PySide2.QtCore import QUrl
from PySide2.QtWidgets import QMainWindow, QApplication
from PySide2.QtWebEngineWidgets import QWebEngineView

import os
import sys


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.browser = QWebEngineView()
        self.browser.setUrl(QUrl("https://www.learnpyqt.com"))
        self.setCentralWidget(self.browser)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec_()


This creates our basic browser window and navigates to the LearnPyQt homepage. All links will open within the same browser window (normal browser behavior).

The basic browser The basic browser window.

Popping up a new window for each link

To override the default navigation behavior we need to create a customized QWebEnginePage class. This is the Qt class which handles viewing (and editing) of web documents, such as web pages within the QWebEngineView.

By creating a custom QWebEnginePage class we are able to intercept the .acceptNavigationRequest signal and implement custom behavior. For example, we can decline links to block navigation, alter the links to navigate somewhere else, or (as we're doing here) open up custom viewers. In our specific case we will both decline the default behavior, by returning Falseand implement our own, by opening a new window.

For our new windows, we create a custom web engine view (the same type we have in the main window), set the URL and then display the window. Note that we need to keep a reference to the created window (in the external_windows list) so it isn’t destroyed on exiting this method.

For everything else, we pass through to the handler on the parent class with super().acceptNavigationRequest().

python
from PyQt5.QtWebEngineWidgets import QWebEnginePage

class CustomWebEnginePage(QWebEnginePage):
    """ Custom WebEnginePage to customize how we handle link navigation """
    # Store external windows.
    external_windows = []

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        if _type == QWebEnginePage.NavigationTypeLinkClicked:
            w = QWebEngineView()
            w.setUrl(url)
            w.show()

            # Keep reference to external window, so it isn't cleared up.
            self.external_windows.append(w)
            return False
        return super().acceptNavigationRequest(url,  _type, isMainFrame)

To use our custom page class we need to set it on the browser with .setPage(). After this, any navigation is sent through the custom page instance and our acceptNavigationRequest handler.

python
        self.browser = QWebEngineView()
        self.browser.setPage(CustomWebEnginePage(self))
        self.browser.setUrl(QUrl("http://google.com"))

The full working example is shown below.

python
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage

import os
import sys


class CustomWebEnginePage(QWebEnginePage):
    """ Custom WebEnginePage to customize how we handle link navigation """
    # Store external windows.
    external_windows = []

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        if _type == QWebEnginePage.NavigationTypeLinkClicked:
            w = QWebEngineView()
            w.setUrl(url)
            w.show()

            # Keep reference to external window, so it isn't cleared up.
            self.external_windows.append(w)
            return False
        return super().acceptNavigationRequest(url,  _type, isMainFrame)


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.browser = QWebEngineView()
        self.browser.setPage(CustomWebEnginePage(self))
        self.browser.setUrl(QUrl("https://www.learnpyqt.com"))
        self.setCentralWidget(self.browser)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec_()

python
from PySide2.QtCore import QUrl
from PySide2.QtWidgets import QMainWindow, QApplication
from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEnginePage

import os
import sys


class CustomWebEnginePage(QWebEnginePage):
    """ Custom WebEnginePage to customize how we handle link navigation """
    # Store external windows.
    external_windows = []

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        if _type == QWebEnginePage.NavigationTypeLinkClicked:
            w = QWebEngineView()
            w.setUrl(url)
            w.show()

            # Keep reference to external window, so it isn't cleared up.
            self.external_windows.append(w)
            return False
        return super().acceptNavigationRequest(url,  _type, isMainFrame)


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.browser = QWebEngineView()
        self.browser.setPage(CustomWebEnginePage(self))
        self.browser.setUrl(QUrl("https://www.learnpyqt.com"))
        self.setCentralWidget(self.browser)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec_()

If you run this example and click on a link in the page, a new window will be opened for every link you click. You can continue to navigate within those external windows as normal -- they use the standard QWebEnginePage class, so don't have our open in new window behavior.

Links are opened in a separate window Links are opened in a new window.

Conditionally popping up a new window

Sometimes you only want "external" links to be popped up into a separate window -- navigation within your documentation should stay within the documentation browser window, and only external links (not from your documentation) popped up into a separate window.

Since we have access to the URL being navigated this is pretty straightforward. We can just compare that URL to some pattern (here we're using the hostname via url.host()), and choose to either pop a new window, or pass it to the default handler.

python
class CustomWebEnginePage(QWebEnginePage):
    """ Custom WebEnginePage to customize how we handle link navigation """
    # Store external windows.
    external_windows = []

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        if (_type == QWebEnginePage.NavigationTypeLinkClicked and
            url.host() != 'www.learnpyqt.com'):
            # Pop up external links into a new window.
            w = QWebEngineView()
            w.setUrl(url)
            w.show()

            # Keep reference to external window, so it isn't cleared up.
            self.external_windows.append(w)
            return False
        return super().acceptNavigationRequest(url,  _type, isMainFrame)

Navigating around the LearnPyQt site now stays within the main browser, but external links (such as to the forum) are popped out into a separate window.

External window popped up for external links The external window is popped up for external links only.

Reusing an external window

In this first example we're creating a new window for every link, rather than creating a new window if none exists and then sending all subsequent link clicks to that same window. To get this second behavior we just need to hold a single reference to the external window and check if it exists before creating a new one.

python
class WebEnginePage(QWebEnginePage):
    # Store second window.
    external_window = None

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        print(url, _type, isMainFrame)
        if _type == QWebEnginePage.NavigationTypeLinkClicked:
            if not self.external_window:
                self.external_window = QWebEngineView()

            self.external_window.setUrl(url)
            self.external_window.show()
            return False

        return super().acceptNavigationRequest(url,  _type, isMainFrame)

Putting this into our example gives us the following complete example.

python
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage

import sys


class CustomWebEnginePage(QWebEnginePage):
    # Store second window.
    external_window = None

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        print(url, _type, isMainFrame)
        if _type == QWebEnginePage.NavigationTypeLinkClicked:
            if not self.external_window:
                self.external_window = QWebEngineView()

            self.external_window.setUrl(url)
            self.external_window.show()
            return False

        return super().acceptNavigationRequest(url,  _type, isMainFrame)


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.browser = QWebEngineView()
        self.browser.setPage(CustomWebEnginePage(self))
        self.browser.setUrl(QUrl("https://www.learnpyqt.com"))
        self.setCentralWidget(self.browser)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec_()

python
from PySide2.QtCore import QUrl
from PySide2.QtWidgets import QMainWindow, QApplication
from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEnginePage

import sys


class CustomWebEnginePage(QWebEnginePage):
    # Store second window.
    external_window = None

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        print(url, _type, isMainFrame)
        if _type == QWebEnginePage.NavigationTypeLinkClicked:
            if not self.external_window:
                self.external_window = QWebEngineView()

            self.external_window.setUrl(url)
            self.external_window.show()
            return False

        return super().acceptNavigationRequest(url,  _type, isMainFrame)


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.browser = QWebEngineView()
        self.browser.setPage(CustomWebEnginePage(self))
        self.browser.setUrl(QUrl("https://www.learnpyqt.com"))
        self.setCentralWidget(self.browser)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec_()

When clicking on a link in the browser window a new window is created. You can browse in that window as normal, but if you click a new link in the parent window, the current page will be replaced with that link.

Opening links in the user's default browser

You might also want to consider popping up external links in the users default browser. This allows them to bookmark the links, or browse as normally, rather than in the restricted browser view your app provides.

For this we don't need to create a window, we can just send the url to the system default handler. This is accomplished by passing the url to the QDesktopServices.openUrl() method. The full working example is shown below.

python
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
from PyQt5.QtGui import QDesktopServices

import os
import sys


class CustomWebEnginePage(QWebEnginePage):
    """ Custom WebEnginePage to customize how we handle link navigation """
    # Store external windows.
    external_windows = []

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        if (_type == QWebEnginePage.NavigationTypeLinkClicked and
            url.host() != 'www.learnpyqt.com'):
            # Send the URL to the system default URL handler.
            QDesktopServices.openUrl(url)
            return False
        return super().acceptNavigationRequest(url,  _type, isMainFrame)


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.browser = QWebEngineView()
        self.browser.setPage(CustomWebEnginePage(self))
        self.browser.setUrl(QUrl("https://www.learnpyqt.com"))
        self.setCentralWidget(self.browser)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec_()

python
from PySide2.QtCore import QUrl
from PySide2.QtWidgets import QMainWindow, QApplication
from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
from PySide2.QtGui import QDesktopServices

import os
import sys


class CustomWebEnginePage(QWebEnginePage):
    """ Custom WebEnginePage to customize how we handle link navigation """
    # Store external windows.
    external_windows = []

    def acceptNavigationRequest(self, url,  _type, isMainFrame):
        if (_type == QWebEnginePage.NavigationTypeLinkClicked and
            url.host() != 'www.learnpyqt.com'):
            # Send the URL to the system default URL handler.
            QDesktopServices.openUrl(url)
            return False
        return super().acceptNavigationRequest(url,  _type, isMainFrame)


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.browser = QWebEngineView()
        self.browser.setPage(CustomWebEnginePage(self))
        self.browser.setUrl(QUrl("https://www.learnpyqt.com"))
        self.setCentralWidget(self.browser)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec_()


If you run this and click on any external links, you'll see them open in your system's default browser window (here showing Chrome).

Link opened in default browser window

For more, see my PyQt5 bookCreate GUI Applications with Python & Qt5. The hands-on guide to building desktop apps with Python (PySide2 edition now available).

How to interface Qt with Android Java code

Software development is demanding and often requires us to use languages or solutions that are not part of our main technology stack. There are many reasons for that. Sometimes we have to use solutions that are already implemented as it would take too much time to recreate them. There also might be a case, when we can’t use our main technology due to technical or financial reasons. In such circumstances, the possibility of using a different programming language from the one previously used can be beneficial. This allows our applications to be more flexible.

 

Qt and QML provide solutions to almost every problem. However there are some platform-specific edge cases when using native code can make things nice and easy – all thanks to Qt tools allowing you to use native code in your project. In this post, we will take a closer look at how to interface Android Java code with Qt. Let’s get down to business!

 

Interfacing Qt and Java – what will be needed?

 

For calling Android Java code inside Qt we will need a Qt Android Extras module. It includes classes to help you create applications for Android. Although this module can only be used if we have chosen the Android kit for our project.

Android Extras module contains QAndroidJniObject class, which provides APIs to call Java code from C++. This is exactly what we are looking for.
To use this module, we must add this declaration in .pro file:

 

QT += androidextras

 

Pointing where additional Java files are located is also needed. In our case, we will stick to the „android/src/package_name” format:

 

android {
    ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
    OTHER_FILES += android/src/ScytheStudio
}

 

Time for practice – Qt & Java!

 

Let’s create a simple example of Java class – a more complex code will be presented later in that post. Remember to put the .java file in ANDROID_PACKAGE_SOURCE_DIR :

 

package ScytheStudio;

public class SomeJavaClass {

    public static int counter = 0;
    public int id;

    public SomeJavaClass() {
        this.id = counter;
        System.out.println("Created SomeJavaClass object with id: " + this.id);
        counter++;
    }

    public String sayHello() {
        return "SomeJavaClass object number: " + id + " say hello :)";
    }

    public static double multiply(double a, double b) {
        return  a * b;
    }
}

Now we can write some Qt code. Begin with adding the import statement:

 

#include <QtAndroidExtras>

 

After that we need to check if the program is able to recognize our Java class. To do this we can use simple if statement:

 

//check if program can find our java class and use it
if(QAndroidJniObject::isClassAvailable("ScytheStudio/SomeJavaClass")) {
    .
    .
    .
else {
    qDebug() << "SOME JAVA CLASS UNAVAIABLE!";
}

 

At this moment we should have access to a custom Java class. To create its instance, call QAndroidJniObject constructor with the name of Java class as a parameter:

 

QAndroidJniObject someJavaObject = QAndroidJniObject("ScytheStudio/SomeJavaClass");

 

 

Console output should be as follows:

I System.out: Created SomeJavaClass object with id: 0

 

To use object method we call QAndroidJniObject::callObjectMethod. Although we determined the return type of function in Java code, Java types are not equal to C++ and Qt ones. We need to specify what type does this method return – that is why callObjectMethod is a template function. We use jstring because the sayHello method returns Java String object.

 

 

//calling object method
qDebug() << someJavaObject.callObjectMethod<jstring>("sayHello").toString();

 

You should see the following message in console:

D libQt_code_x86.so: „SomeJavaClass object number: 0 say hello :)”

 

Using static multiply function may seem a little more complicated. As before, we need to provide: return type, name of the class, and methods. Only difference to methods without any arguments is a method signature in callObjectMethod written as “(Argument1, Argument2, Argument3…)ReturnType“.

 

For our method, we use two integers as arguments and expect one integer result value. The signature of an integer is the „I” mark. In the documentation, you can find all JNI types and their signatures.

 

Our method signature will look like this: (II)I. In brackets, we have two „I” marks for our arguments and one „I” mark outside brackets for the return value. If we had a function that takes char and float as arguments and returns a string, its signature would look like this: „(CF)Ljava/lang/String”.

Finally, we pass the parameter values ( 2 and 3 ):

 

//calling static method
qDebug() << "2 * 3 equals: " << QAndroidJniObject::callStaticMethod<jint>("ScytheStudio/SomeJavaClass",                                                                                   "multiply",                                                                              "(II)I",
2, 3);

 

We can see that the method work as expected:

D libQt_code_x86.so: 2 * 3 equals: 6

 

Real-life example – using androidextras

 

Imagine that there is an existing mobile application, which you maintain. The client wants a feature of simple battery saving mode. What we want to achieve is reduce power consumption when the battery level is low – for example by changing the app theme to dark.

 

Let’s start by creating CustomBatteryManager class. This class will check the current battery level every second and emit a proper signal when it changes.

CustomBatteryManager.h

 

#ifndef CUSTOMBATTERYMANAGER_H
#define CUSTOMBATTERYMANAGER_H

#include <QObject>
#include <QtAndroidExtras>
#include <QTimer>

class CustomBatteryManager : public QObject
{
    Q_OBJECT

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

    Q_PROPERTY(int batteryLevel READ batteryLevel WRITE setBatteryLevel NOTIFY batteryLevelChanged)

    int batteryLevel() const;
    void checkBatteryLevel();

public slots:
    void setBatteryLevel(int batteryLevel);

signals:

    void batteryLevelChanged(int batteryLevel);

private:
    int _batteryLevel = 0;
    QTimer *_timer;

};

#endif // CUSTOMBATTERYMANAGER_H

 

CustomBatteryManager.cpp

 

#include "CustomBatteryManager.h"

CustomBatteryManager::CustomBatteryManager(QObject *parent) : QObject(parent), _timer(new QTimer())
{
    connect(_timer, &QTimer::timeout, this, &CustomBatteryManager::checkBatteryLevel);
    _timer->setInterval(1000);
    _timer->start();
}

int CustomBatteryManager::batteryLevel() const
{
    return _batteryLevel;
}

void CustomBatteryManager::checkBatteryLevel()
{
    QAndroidJniObject activity = QtAndroid::androidActivity();
    QAndroidJniObject context = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;");

    QAndroidJniObject batteryManager;
    QAndroidJniObject batteryManagerServiceName = QAndroidJniObject::fromString("batterymanager");
    jint batteryManagerCapacityConstant = QAndroidJniObject::getStaticField<jint>("android/os/BatteryManager", "BATTERY_PROPERTY_CAPACITY");

    batteryManager = context.callObjectMethod("getSystemService",
                                                  "(Ljava/lang/String;)Ljava/lang/Object;",
                                                   batteryManagerServiceName.object<jstring>());


    jint actualBatteryLevel = batteryManager.callMethod<jint>("getIntProperty", "(I)I", batteryManagerCapacityConstant);
    qDebug() << "ACTUAL BATTERY LEVEL: " << _batteryLevel;
    setBatteryLevel(actualBatteryLevel);
}

void CustomBatteryManager::setBatteryLevel(int batteryLevel)
{
    if (_batteryLevel == batteryLevel) {
       return;
   }

    _batteryLevel = batteryLevel;
    emit batteryLevelChanged(_batteryLevel);
}

 

Take a closer look at the constructor. To check the current battery level, we use a timer that calls the checkBatteryLevel function every second.

 

In checkBatteryLevel method, we are using the Android BatteryManager class to get the battery level.
First, we need to get the actual context of the application. To do that we use QtAndroid::androidActivity() which returns a handle to this application’s main Activity, and then call getApplicationContext on the activity object to get context.

 

After that, we want to create batteryManager object. To do that we will use the method getSystemService. This method takes the name of a particular service as an argument.

So we need to look at the Android Context documentation and find the name of the service we are interested in. For Battery manager it will be „batterymanager”. Let’s save this value in the auxiliary batteryManagerServiceName variable.

 

Now all you have to do is call the previously mentioned getSystemService method with the proper argument.

Next, we create jint batteryManagerCapacityConstatnt variable, because the actual battery level is stored as a static constant in the Battery manager class. Finally, we get battery value using getIntProperty method with batteryManagerCapacityConstatn as an argument and set our _batteryLevel value one the one received.

 

To use our CustomBatteryManager we can expose it to QML. If you don’t know how to do it, you can have a look at Qt documentation on this topic.

 

Now implement a simple UI to test the custom battery manager.

 

import QtQuick.Window 2.2
import QtQuick 2.15

Window {
  id: root
  visible: true

  ListView {
    id: listView

    anchors.fill: parent

    model: 50

    delegate: Rectangle {
      id: delegateItem

      width: listView.width
      height: 50

      color: customBatteryManager.batteryLevel > 30 ? "white" : "black"

      Behavior on color {
        PropertyAnimation {
          duration: 300
        }
      }
      Text {
        id: delegateText

        anchors {
          left: parent.left
          leftMargin: 10
          verticalCenter: parent.verticalCenter
        }

        text: qsTr("element") + " : " + index

        color: customBatteryManager.batteryLevel > 30 ? "black" : "white"
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter

        Behavior on color {
          PropertyAnimation {
            duration: 300
          }
        }
      }
    }
  }
}

 

To test it, I will use an android emulator and change the battery level through emulator options.

 

qt android battery level

 

Summary

That’s the end of this tutorial. Now you know that you can easily use Android Java code in Qt. You have learned how to create objects, call up methods, and how to use them all on the example of our application. Thanks to the newly acquired knowledge you will be able to enrich your projects.

 

If you are still hungry for knowledge, check out other tutorials at Scythe-Studio blog and make sure to like our Facebook page – this way you won’t miss any new post 🙂

Qt 3D Changes in Qt 6

Overview

Qt 6 is nearly upon us. While this has not been addressed by other publications, Qt 3D is also introducing a number of changes with this major release. This includes changes in the public API that will bring a number of new features and many internal changes to improve performance and leverage new, low-level graphics features introduced in QtBase. I will focus on API changes now, while my colleague, Paul Lemire, will cover other changes in a follow up post.

Distribution of Qt 3D for Qt 6

Before looking at what has changed in the API, the first big change concerns how Qt 3D is distributed. Qt 3D has been one of the core modules that ship with every Qt release. Starting with Qt 6, however, Qt 3D will be distributed as a separate source-only package. Qt 6 will ship with a number of such modules, and will use conan to make it easy for those modules to be built. This means that users interested in using Qt 3D will need to compile once for every relevant platform.

Since it ships independently, Qt 3D will also most likely be on a different release cycle than the main Qt releases. We will be able to release more, frequent minor releases with new features and bug fixes.

Another consequence of this is that Qt 3D will not be bound by the same binary compatibility constraints as the rest of Qt. We do however, aim to preserve source compatibility for the foreseeable future.

Basic Geometry Types

The first API change is minimal, but, unfortunately, it is source-incompatible. You will need to change your code in order to compile against these changes.

In order to make developing new aspects that access geometry data more straight forward, we have moved a number of classes that relate to that from the Qt3DRender aspect to the Qt3DCore aspect. These include QBuffer, QAttribute and QGeometry.

When using the QML API, impact should be minimal, the Buffer element still exists, and importing the Render module implicitly imports the Core module anyway. You may have to change your code if you’ve been using module aliases, though.

In C++, this affects which namespace these classes live in, which is potentially more disruptive. So if you were using Qt3DRender::QBuffer (often required to avoid clash with QBuffer class in QtCore), you would now need to use Qt3DCore::QBuffer, and so on…

If you need to write code that targets both Qt5 and Qt6, one trick you can use to ease the porting is to use namespace aliases, like this:

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <Qt3DCore/QBuffer>
namespace Qt3DGeometry = Qt3DCore;
#else
#include <Qt3DRender/QBuffer>
namespace Qt3DGeometry = Qt3DRender;
#endif

void prepareBuffer(Qt3DGeometry::QBuffer *buffer) {
    ...
}

The main reason this was done is so that all aspects could have access to the complete description of a mesh. Potential collision detection or physics simulation aspects don’t need to have their own representation of a mesh, separate from the one used for rendering.

So QBuffer, QAttribute and QGeometry are now in Qt3DCore. But this is not enough to completely describe a mesh.

Changes in Creating Geometry

A mesh is typically made of a collection of vertices. Each vertex will have several properties (positions, normal, texture coordinates, etc.) associated to it. The data for those properties is stored somewhere in memory. So in order to register a mesh with Qt 3D, you need:

  • A QGeometry instance that is simply a collection of QAttribute instances
  • Each QAttribute instance to define the details of a vertex attribute. For example, for the position, it would include the number of components (usually 3), the type of the component (usually floats), the name of the attribute as it will be exposed to the shaders (usually “position” or QAttribute::defaultNormalAttributeName(), if you are using Qt3D built-in materials), etc.
  • Each QAttribute to also point to a QBuffer instance. This may be the same for all attributes, or it may be different, especially for attribute data that needs to be updated often.

But this is still incomplete. We are missing details such as how many points make up the mesh and what type of primitives these points make up (triangles, strips, lines, etc) and more.

Prior to Qt 6, these details were stored on a Qt3DRender::QGeometryRenderer class. The name is obviously very rendering-related (understatement), so we couldn’t just move that class.

For these reasons, Qt 6 introduces a new class, Qt3DCore::QGeometryView. It includes a pointer to a QGeometry and completely defines a mesh. It just doesn’t render it. This is useful as the core representation of a mesh that can then be used for rendering, bounding volume specifications, picking, and much more.

Bounding Volume Handling

One of the very first things Qt 3D needs to do before rendering is compute the bounding volume of the mesh. This is needed for view frustum culling and picking. Internally, the render aspect builds a bounding volume hierarchy to quickly find objects in space. To compute the bounding volume, it needs to go through all the active vertices of a mesh. Although this is cached, it can take time the first time the object is rendered or any of its details change.

Furthermore, up to now, this was completely internal to Qt 3D’s rendering backend and the results were not available to the user for using in the rest of the application.

So Qt 6 introduces a QBoundingVolume component which serves two purposes:

  • it has implicit minimum point and maximum point properties that contain the result of the bounding volume computations done by the backend. This can be used by the application.
  • it has explicit minimum point and maximum point properties which the user can set. This will prevent the backend from having to calculate the bounds in order to build the bounding volume hierarchy.

The minimum and maximum extents points are the corners of the axis-aligned box that fits around the geometry.

But how does QBoundingVolume know which mesh to work on? Easy — it has a view property which points to a QGeometryView instance!

Reading bounding volume extents

So if you need to query the extents of a mesh, you can use the implicit values:

Entity {
   components: [
       Mesh {
           source: "..."
           onImplicitMinPointChanged: console.log(implicitMinPoint)
       },
       PhongMaterial { diffuse: "green" },
       Transform { ... }
    ]
}

Note that if the backend needs to compute the bounding volume, this is done at the next frame using the thread pool. So the implicit properties might not be immediately available when updating the mesh.

If you need the extents immediately after setting or modifying the mesh, you can call QBoundingVolume::updateImplicitBounds() method, which will do the computations and update the implicit properties.

Setting bounding volume extents

But you know the extents. You can set them explicitly to stop Qt 3D from computing them:

Entity {
   components: [
       Mesh {
           source: "..."
           minPoint: Qt.vector3d(-.5, -.5, -.5)
           maxPoint: Qt.vector3d(.5, .5, .5)
       },
       PhongMaterial { diffuse: "green" },
       Transform { ... }
    ]
}

Note that, since setting the explicit bounds disables the computation of the bounding volume in the backend, the implicit properties will NOT be updated in this case.

Mesh Rendering

Now, before everyone goes and adds QBoundingVolume components to all their entities, one other thing: QGeometryRenderer, in the Qt3DRender module, now derives from QBoundingVolume. So, it will also have all the extents properties.

It also means you can provide it with a geometry view to tell it what to draw, rather than providing a QGeometry and all the other details.

It still, however, has all the old properties that are now taken care of by the QGeometryView instance. If that is defined, all the legacy properties will be ignored. We will deprecate them soon and remove them in Qt 7.

So what happens if you provide both a QBoundingVolume and a QGeometryRenderer component to an entity? In this case, the actual bounding volume component takes precedence over the geometry renderer. If it specifies explicit bounds, those will be used for the entity.

The main use case for that is to specify a simpler geometry for the purpose of bounding volume computation. If you don’t know the extents of a mesh but you know that a simpler mesh (with much fewer vertices) completely wraps the object you want to render, using that simpler mesh can be a good way of speeding up the computations that Qt 3D needs to do.

New Core Aspect

Most of these computations took place previously in the Render aspect. Since this is now in core, and in order to fit in with Qt 3D’s general architecture, we introduce a new Core aspect. This aspect will be started automatically if you are using Scene3D or Qt3DWindow. In cases where you are creating your own aspect engine, it should also automatically be started as long as the render aspect is in use via the new aspect dependency API (see below).

The core aspect will take care of the all the bounding volume updates for the entities that use the new geometry view-based API (legacy scenes using a QGeometryRenderer instances without using views will continue to be updated by the rendering aspect).

The Core aspect also introduces a new QCoreSettings component. Like the QRenderSettings component, a single instance can be created. It is, by convention, attached to the root entity.

Currently, its only purpose is to be able to completely disable bounding volume updating. If you are not using picking and have disabled view frustum culling, bounding volumes are actually of no use to Qt 3D. You can disable all the jobs which are related to bounding volume updates by setting QCoreSettings::boundingVolumesEnabled to false. Note that implicit extent vertices on QBoundingVolume component will then not be updated.

New Aspect and AspectJob API

 

The base class for aspects, QAbstractAspect, has gained a few useful virtual methods:

  • QAbstractAspect::dependencies() should return the list of aspect names that should be started automatically if an instance of this aspect is registered.
  • QAbstractAspect::jobsDone() is called on the main thread when all the jobs that the aspect has scheduled for a given frame have completed. Each aspect has the opportunity to take the results of the jobs and act upon them. It is called every frame.
  • QAbstractAspect::frameDone() is called when all the aspects have completed the jobs AND the job post processing. In the case of the render aspect, this is when rendering actually starts for that frame.

 

Similarly, jobs have gained a number of virtual methods on QAspectJob:

  • QAspectJob::isRequired() is called before a job is submitted. When building jobs, aspects will build graphs of jobs with various dependencies. It’s often easier to build the same graph every frame, but not all jobs might have something to do on a given frame. For example, the picking job has nothing to do if there are no object pickers or the mouse has not moved. The run method can test for this and return early, but this still causes the job to be scheduled onto a thread in the pool, with all the associated, sometime expensive locking. If QAspectJob::isRequired() returns false, the job will not be submitted to the thread pool and processing will continue with its dependent jobs.
  • QAspectJob::postFrame() is called on the main thread once all the jobs are completed. This is place where most jobs can safely update the foreground classes with the results of the backend computations (such as bounding volume sizes, picking hits, etc).

Picking Optimization

We have introduced optimization for picking. A QPickingProxy component has been introduced, deriving from QBoundingVolume. If it has an associated geometry view, that mesh will be used instead of the rendered mesh for picking tests. This applies to QObjectPicker and the QRayCaster and QScreenRayCaster classes. Since precise picking (when QPickingSettings is not set to use bounding volume picking) needs to look at every primitive (triangle, line, vertex), it can be very slow. Using QPickingProxy makes it possible to provide a much simpler mesh for the purpose of picking.

Entity {
    components: [
        GeometryRenderer { view: actualView },
        PickingProxy { view: simpleView }
        ...
]
...

So for example, you can provide a down sampled mesh, such as the bunny on the right (which only includes 5% of the total of primitives in the original mesh), to get fast picking results.

Of course, the picking results (the local coordinate, the index of the picked primitive, etc) will all be defined relative to the picking proxy mesh (not the rendered mesh).

 

Finally, QRayCaster and QScreenRayCaster now have pick() methods, which do a ray casting test synchronously, whereas the pre-existing trigger() methods would schedule a test for the next frame. This will block the caller until completed and return the list of hits. Thus, it’s possible for an application to implement event delegation. For example, if the user right clicks, the application can decide to do something different depending on the type of the closest object, or display a context menu if nothing was hit.

Conclusion

As you can see, Qt 3D for Qt 6 has quite a few changes. My colleague, Paul Lemire, will go through many more internal changes in a later post. We hope this ensures an on-going successful future for Qt 3D in the Qt 6 series.

KDAB provides a number of services around Qt 3D, including mentoring your team and embedding Qt 3D code into your application, among others. You can find out more about these services here.

About KDAB

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

Subscribe to KDAB TV for similar informative short video content.

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

The post Qt 3D Changes in Qt 6 appeared first on KDAB.

Desktop styling with Qt Quick Controls

Qt Quick Controls 1 was our first UI framework for QML. The controls were mostly written in QML without much C++. At the time, QML was still a new technology, which meant that we didn’t have enough experience with designing for performance to know what to expect. So the styling API ended up inefficient by design, with many fat delegates that used extensive amounts of JavaScript, bindings, introspection, Loaders, and QObjects for both control logic and styling. It also had a linking dependency to Qt Widgets to get native styling and Widget-based dialogs. And without the QML compiler that we have today, this all ended up as a rather slow and messy approach. When we realized that it also didn’t perform well on embedded hardware, it was time to rethink the solution. And the result was Qt Quick Controls 2.

Teaching QLocale more about number formats

QLocale looks after all localisation (or L10n) within Qt; while Qt 6 has swept away a few last fragments of L10n built into other things, that now consistently use the C locale (and advise you to use QLocale if you need L10n), it's also seen some significant improvements to how QLocale does those things, particularly in relation to numeric texts and surrogate pairs.

The QResource System — Using the QResource system to package additional data with your applications

Building applications takes more than just code. Usually your interface will need icons for actions, you may want to add illustrations or branding logos, or perhaps your application will need to load data files to pre-populate widgets. These data files are separate from the source code of your application but will ultimately need to be packaged and distributed with it in order for it to work.

Distributing data files with applications is a common cause of problems. If you reference data files with paths, your application won't work properly unless the exact same paths are available on the target computer. This can get even trickier when packaging applications for cross-platform (Windows, macOS and Linux) use. Thankfully, Qt comes to the rescue with it's resource system. Resources are bundled into Python files which can be distributed along with your source code, guaranteeing they will continue to work on other platforms. You can manage resources through Qt Designer and use the resource library to load icons (and other data) in your apps.

The example application

To demonstrate how to use the resource system, we'll create a simple application that uses two data files -- in this case, two icon image files. However, remember you can package any type of data you like as resources, including data files that your application depends on.

The example below shows a single button, which when pressed changes it's icon.

python
import sys

from PyQt5 import QtGui, QtWidgets

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Hello World")
        self.button = QtWidgets.QPushButton("My button")

        icon = QtGui.QIcon("animal-penguin.png")
        self.button.setIcon(icon)
        self.button.clicked.connect(self.change_icon)

        self.setCentralWidget(self.button)

        self.show()

    def change_icon(self):
        icon = QtGui.QIcon("animal-monkey.png")
        self.button.setIcon(icon)


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()

python
import sys

from PySide2 import QtGui, QtWidgets

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Hello World")
        self.button = QtWidgets.QPushButton("My button")

        icon = QtGui.QIcon("animal-penguin.png")
        self.button.setIcon(icon)
        self.button.clicked.connect(self.change_icon)

        self.setCentralWidget(self.button)

        self.show()

    def change_icon(self):
        icon = QtGui.QIcon("animal-monkey.png")
        self.button.setIcon(icon)



app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()

Save this file to your computer as app.py. Run it and you'll see the window appear with the button, but with no icons visible. This is what happens when you create QIcon() objects with files that cannot be found -- they are simply omitted. If you've had issues when packaging your applications, this will be familiar to you.

No icons showing Without the icons available, nothing is shown.

Download and place the animal-penguin.png and animal-monkey.png icons in the same folder as the script, and run it again. These icons are from the Fugue icon set by Yusuke Kamiyamane. Run the application again and you'll see the icons as expected.

Icons showing With the icons in the same folder as the script, they are now visible.

Now the application is working as expected, we'll switch to using the QResource system to load these two files. First we need to define our QRC file and add our resources to it.

This might seem like overkill for 2 files, but as your projects get larger the advantages become clearer!

The QRC file

The core of the Qt Resources system is the resource file or QRC. The .qrc file is a simple XML file, which can be opened in any text editor.

You can also create QRC files and add and remove resources using Qt Designer, which we'll cover later.

Simple QRC example

A very simple resource file is shown below, containing a single resource (a single icon animal-penguin.png we might add to a button).

xml
<!DOCTYPE RCC>
<RCC version="1.0">
    <qresource prefix="icons">
        <file alias="animal-penguin.png">animal-penguin.png</file>
        <file alias="animal-monkey.png">animal-monkey.png</file>
    </qresource>
</RCC>

The name between the <file> </file> tags is the path to the file, relative to the resource file. The alias is the name which this resource will be known by from within your application. You can use this rename icons to something more logical or simpler in your app, while keeping the original name externally.

For example, if we want to use the name penguin.png internally, we can change these lines to.

xml
<file alias="penguin.png">animal-penguin.png</file>
<file alias="monkey.png">animal-monkey.png</file>

This only changes the name used inside your application, the filename remains unchanged.

Outside this tag is the qresource tag which specifies a prefix. This is a namespace which can be used to group resources together. This is effectively a virtual folder, under which nested resources can all be found. With our current structure, our two icons are grouped under the virtual folder icons/.

Save the following file as resources.qrc, this is our resource file which we'll use from now on.

xml
<!DOCTYPE RCC>
<RCC version="1.0">
    <qresource prefix="icons">
        <file alias="penguin.png">animal-penguin.png</file>
        <file alias="monkey.png">animal-monkey.png</file>
    </qresource>
</RCC>

Using a QRC file

To use a .qrc file in your application you first need to compile it to Python. PyQt5 comes with a command line tool to do this, which takes a .qrc file as input and outputs a Python file containing the compiled data. This can then be imported into your app as for any other Python file or module.

To compile our resources.qrc file to a Python file named resources.py we can use --

bash
pyrcc5 resources.qrc -o resources.py

bash
pyside2-rcc resources.qrc -o resources.py

To use the resource file in our application we need to make a few small changes. Firstly, we need to import resources at the top of our app, to load the resources into the Qt resource system, and then secondly we need to update the path to the icon file to use the resource path format as follows:

The prefix :/ indicates that this is a resource path. The first name "icons" is the prefix namespace and the filename is taken from the file alias, both as defined in our resources.qrc file.

The updated application is shown below.

python
import sys

from PyQt5 import QtGui, QtWidgets

import resources

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Hello World")
        self.button = QtWidgets.QPushButton("My button")

        icon = QtGui.QIcon(":/icons/penguin.png")
        self.button.setIcon(icon)
        self.button.clicked.connect(self.change_icon)

        self.setCentralWidget(self.button)

        self.show()

    def change_icon(self):
        icon = QtGui.QIcon(":/icons/monkey.png")
        self.button.setIcon(icon)


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()

python
import sys

from PySide2 import QtGui, QtWidgets

import resources

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Hello World")
        self.button = QtWidgets.QPushButton("My button")

        icon = QtGui.QIcon(":/icons/penguin.png")
        self.button.setIcon(icon)
        self.button.clicked.connect(self.change_icon)

        self.setCentralWidget(self.button)

        self.show()

    def change_icon(self):
        icon = QtGui.QIcon(":/icons/monkey.png")
        self.button.setIcon(icon)


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()

If you run this now, it will look exactly as before, but now the icons are being loaded from the resources.py compile resource file.

Icons visible Icons visible, loading from the QRC file.

Resources in Qt Designer and Qt Creator

While it's fairly straightforward to manage your resources by editing the QRC file directly, Qt Designer can also be used to edit the resource library. This allows you to see all the icons (and other data) visually, rearrange them and edit them by drag-and-drop.

Adding Resources in Qt Designer

If you're using the standalone Qt Designer, the resource browser is available as a dockable widget, visible in the bottom right by default. If the Resource Browser is hidden you can show it through the "View" menu on the toolbar.

To add, edit and remove resource files click on the pencil icon in the Resource browser panel. This will open the resource editor.

Standalone Qt Designer view Standalone Qt Designer view

In the resource editor view you can open an existing resource file by clicking on the document folder icon (middle icon) on the bottom left.

Edit Resources in Qt Designer Edit Resources in Qt Designer

On the left hand panel you can also create and delete resource files from your UI. While on the right you can create new prefixes, add files to the prefix and delete items. Changes to the resource file are saved automatically.

Adding Resources in Qt Creator

In order to be able to add icons using the Qt Resource system from within Qt Creator you need to have an active Qt Project, and add both your UI and resource files to it.

If you don't have a Qt Creator project set up you can create one in your existing source folder. Qt Creator will prompt before overwriting any of your files. Click on "+ New", choose "Qt for Python - Empty" for project type. Select the folder above your source folder for "Create in", and provide the name of your source folder as the project name. You can delete any files created, except the .pyproject which holds the project settings.

Select the location Select the location

To add resources to your existing project, select the "Edit" view on the left hand panel. You will see a file tree browser in the left hand panel. Right-click on the folder and choose "Add existing files…" and add your existing .qrc file to the project.

The Edit view, showing the added files The Edit view, showing the added files

The UI doesn't update when you add/remove things here, this seems to be a bug in Qt Creator. If you close and re-open Qt Creator the files will be there.

Once you have added the QRC file to the file listing you should be able to expand the file as if it were a folder, and browser the resources within. You can also add and remove resources using this interface.

Using resources in Qt Creator and Qt Designer

Once the Resource file is loaded you will be able to access it from the designer properties. The screenshot below shows the Designer with our counter app open, and the increment button selected. The icon for the button can be chosen by clicking the small black down arrow and selecting "Choose Resource…"

Select the location Select the location

The Resource chooser window that appears allows you to pick icons from the resource file(s) in the project to use in your UI.

Select the location Select the location

Selecting the icons from the resource file in this way ensures that they will always work, as long as you compile and bundle the compiled resource file with your app.

Using a QRC file with compiled UI files

If you're designing your UIs in Qt Designer and compiling the resulting UI file to Python, then UI compiler automatically adds imports to a compiled version of your Qt Resource file for you. For example, if you run the following --

bash
pyuic5 mainwindow.ui -o MainWindow.py

bash
pyside2-uic mainwindow.ui -o MainWindow.py

This build process also adds imports to MainWindow.py for the compiled version of the resources using in the UI, in our case resources.qrc. This means you do not need to import the resources separately into your app. However, we still need to build them, and use the specific name that is used for the import in MainWindow.py, here resources_rc.

bash
pyrcc5 resources.qrc -o resources_rc.py

bash
pyside2-rcc resources.qrc -o resources_rc.py

The command line tools follow the pattern <resource name>_rc.py when adding imports for the resource file, so you will need to follow this when compiling resources yourself. You can check your compiled UI file (e.g. MainWindow.py) to double check the name of the import if you have problems.

When to use QResource?

You may be wondering when (or even whether) you should use the QResource system.

The main advantage comes when packaging and distributing your applications. Because your data is bundled with the Python source code, you eliminate all the potential path problems and guarantee your data files will be accessible to your app. You can also use Qt Designer to manage and group icons for your application. The downside of course is you need to re-compile your resources any time you add/remove new resources.

Whether this trade-off is worth it for your project is up to you, but if you plan to distribute your application to other people it almost always is.

See the complete PyQt5 tutorial, from first principles to complete applications with Python & Qt5.

Where you can learn together - Qt Goes Virt. Seminars in 2020-2021

2020 has been a roller coaster. As everyone continues to navigate through these challenging times, the Qt team is excited to provide a space for like-minded peers to watch and ask questions. Gather around for 10 hours with the Qt product team, industry thought leaders and the Qt Ecosystem in your school of choice: Embedded, Desktop, and UI Design! 

Using QProcess to run external programs — Run background programs without impacting your UI

So far we've looked at how to run work in separate threads, allowing you to do complex tasks without interrupting your UI. This works great when using Python libraries to accomplish tasks, but sometimes you want to run external applications, passing parameters and getting the results.

In this tutorial we'll look at QProcess, the Qt system for running external programs from within your own app.

The external program

To be able to test running external programs with QProcess we need to have something to run. Here we'll create a simple Python script for that purpose, which we can then launch from within our application. Put the following in a file, and save it with the name dummy_script.py.

I'm using Python here to be sure it works on all platforms. If you have an existing command line tool you'd like to test with, you can substitute that instead.

Don't worry too much about the contents of this script, it's just a series of print (stream write) statements with a half second wait after. This simulates a long-running external program which is printing out periodic status messages. Later we'll see how to extract data from this output.

python
import sys
import time


def flush_then_wait():
    sys.stdout.flush()
    sys.stderr.flush()
    time.sleep(0.5)


sys.stdout.write("Script stdout 1\n")
sys.stdout.write("Script stdout 2\n")
sys.stdout.write("Script stdout 3\n")
sys.stderr.write("Total time: 00:05:00\n")
sys.stderr.write("Total complete: 10%\n")
flush_then_wait()

sys.stdout.write("name=Martin\n")
sys.stdout.write("Script stdout 4\n")
sys.stdout.write("Script stdout 5\n")
sys.stderr.write("Total complete: 30%\n")
flush_then_wait()

sys.stderr.write("Elapsed time: 00:00:10\n")
sys.stderr.write("Elapsed time: 00:00:50\n")
sys.stderr.write("Total complete: 50%\n")
sys.stdout.write("country=Nederland\n")
flush_then_wait()

sys.stderr.write("Elapsed time: 00:01:10\n")
sys.stderr.write("Total complete: 100%\n")
sys.stdout.write("Script stdout 6\n")
sys.stdout.write("Script stdout 7\n")
sys.stdout.write("website=www.learnpyqt.com\n")
flush_then_wait()


Now we have our dummy_script.py we can run it from within our Qt application.

Basic application

To experiment with running programs through QProcess we need a skeleton application. This is shown below -- a simple window with a QPushButton and QTextArea. Pressing the push button calls our custom slot start_process, in which we'll execute our external process.

python
from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QPlainTextEdit, 
                                QVBoxLayout, QWidget)
from PyQt5.QtCore import QProcess
import sys


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()

        self.btn = QPushButton("Execute")
        self.btn.pressed.connect(self.start_process)
        self.text = QPlainTextEdit()
        self.text.setReadOnly(True)

        l = QVBoxLayout()
        l.addWidget(self.btn)
        l.addWidget(self.text)

        w = QWidget()
        w.setLayout(l)

        self.setCentralWidget(w)

    def start_process(self):
        # We'll run our process here.
        pass

app = QApplication(sys.argv)

w = MainWindow()
w.show()

app.exec_()

Make sure it works, there's not much to look at yet -- pressing the button doesn't do anything either.

The skeleton application window The skeleton application.

Using QProcess to execute external applications.

Executing external programs is fairly straightforward with QProcess. First you create a QProcess object and then call .start() passing in the command to execute and a list of string arguments.

python
p = QProcess()
p.start("<program>", [<arguments>])

For our example we're running the custom dummy_script.py script with Python, so our executable is python (or python3) and our arguments are just dummy_script.py.

python
p = QProcess()
p.start("python3", ['dummy_script.py'])

If you are running another command line program you'd need to specify arguments for it. For example, using ffmpeg to extract information from a video file.

python
p = QProcess()
p.start("ffprobe", ['-show_format', '-show_streams', 'a.mp4.py'])

Use this same approach with your own command line program, remembering to split the arguments up into individual items in the list.

We can take the p.start("python3", ['dummy_script.py']) example and add it to our application skeleton as follows. We also add a helper method message() to write messages into our text box in the UI.

python
from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QPlainTextEdit, 
                                QVBoxLayout, QWidget)
from PyQt5.QtCore import QProcess
import sys


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()

        self.btn = QPushButton("Execute")
        self.btn.pressed.connect(self.start_process)
        self.text = QPlainTextEdit()
        self.text.setReadOnly(True)

        l = QVBoxLayout()
        l.addWidget(self.btn)
        l.addWidget(self.text)

        w = QWidget()
        w.setLayout(l)

        self.setCentralWidget(w)

    def message(self, s):
        self.text.appendPlainText(s)        

    def start_process(self):
        self.message("Executing process.")
        self.p = QProcess()  # Keep a reference to the QProcess (e.g. on self) while it's running.
        self.p.start("python3", ['dummy_script.py'])


app = QApplication(sys.argv)

w = MainWindow()
w.show()

app.exec_()

Notice that you must keep a reference to the created QProcess object while it's running, e.g. on self.p. If not, then the object will be deleted prematurely and you'll see a QProcess: Destroyed while process ("python3") is still running. error.

If you run this example and press the button, nothing will happen. The external script is running but you can't see the output.

Execution message is shown The execution message is shown, but not much else.

If you press the button repeatedly, you may find that you see a message like this --

bash
QProcess: Destroyed while process ("python3") is still running.

This is because if you press the button while a process is already running, creating the new process replaces the reference to the existing QProcess object in self.p, deleting it. We can avoid this by checking the value of self.p before executing a new process, and hooking up a finished signal to reset it back to None, e.g.

python
from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QPlainTextEdit, 
                                QVBoxLayout, QWidget)
from PyQt5.QtCore import QProcess
import sys


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()

        self.p = None  # Default empty value.

        self.btn = QPushButton("Execute")
        self.btn.pressed.connect(self.start_process)
        self.text = QPlainTextEdit()
        self.text.setReadOnly(True)

        l = QVBoxLayout()
        l.addWidget(self.btn)
        l.addWidget(self.text)

        w = QWidget()
        w.setLayout(l)

        self.setCentralWidget(w)

    def message(self, s):
        self.text.appendPlainText(s)

    def start_process(self):
        if self.p is None:  # No process running.
            self.message("Executing process")
            self.p = QProcess()  # Keep a reference to the QProcess (e.g. on self) while it's running.
            self.p.finished.connect(self.process_finished)  # Clean up once complete.
            self.p.start("python3", ['dummy_script.py'])

    def process_finished(self):
        self.message("Process finished.")
        self.p = None        


app = QApplication(sys.argv)

w = MainWindow()
w.show()

app.exec_()

Running this now, you can start the process and -- once it has completed -- start it again. Each time the process completes, you'll see the "Process finished." message in the text box.

Process finished message is shown The Process finished message is shown once it completes.

Getting data from the QProcess

So far we've executed an external program and been notified when it started and stopped, but know nothing about what it's doing. This is fine in some cases, where you just want the job to run, but often you'll want some more detailed feedback. Helpfully, QProcess provides a number of signals which can be used to track the progress and state of processes.

If you're familiar with running external processes using subprocess in Python, you may be familiar with streams. These are file-like objects you use to retrieve data from a running process. The two standard streams are standard output and standard error. The former receives result data the application is outputting, while the second receives diagnostic or error messages. Depending on what you're interested in, both of these can be useful -- many programs (like our dummy_script.py output progress information to the standard error stream.

In Qt land, the same principles apply. The QProcess object has two signals .readyReadStandardOutput and .readyReadStandardError which are used to notify when data is available in the respective streams. We can then read from the process to get the latest data.

Below is an example setup for a QProcess, which connects up readyReadStandardOutput and .readyReadStandardError as well as tracking state changes and finish signals.

python
p = QProcess()
p.readyReadStandardOutput.connect(self.handle_stdout)
p.readyReadStandardError.connect(self.handle_stderr)
p.stateChanged.connect(self.handle_state)
p.finished.connect(self.cleanup)
p.start("python", ["dummy_script.py"])

The .stateChanged signal fires whenever the process status changes. Valid values -- defined in the QProcess.ProcessState enum -- are shown below.

Constant Value Description
QProcess.NotRunning 0 The process is not running.
QProcess.Starting 1 The process is starting, but the program has not yet been invoked.
QProcess.Running 2 The process is running and is ready for reading and writing.

Putting that into our example and implementing the handler methods for each gives us the following complete code.

python
from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QPlainTextEdit, 
                                QVBoxLayout, QWidget)
from PyQt5.QtCore import QProcess
import sys


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()

        self.p = None

        self.btn = QPushButton("Execute")
        self.btn.pressed.connect(self.start_process)
        self.text = QPlainTextEdit()
        self.text.setReadOnly(True)

        l = QVBoxLayout()
        l.addWidget(self.btn)
        l.addWidget(self.text)

        w = QWidget()
        w.setLayout(l)

        self.setCentralWidget(w)

    def message(self, s):
        self.text.appendPlainText(s)

    def start_process(self):
        if self.p is None:  # No process running.
            self.message("Executing process")
            self.p = QProcess()  # Keep a reference to the QProcess (e.g. on self) while it's running.
            self.p.readyReadStandardOutput.connect(self.handle_stdout)
            self.p.readyReadStandardError.connect(self.handle_stderr)
            self.p.stateChanged.connect(self.handle_state)
            self.p.finished.connect(self.process_finished)  # Clean up once complete.
            self.p.start("python3", ['dummy_script.py'])

    def handle_stderr(self):
        data = self.p.readAllStandardError()
        stderr = bytes(data).decode("utf8")
        self.message(stderr)

    def handle_stdout(self):
        data = self.p.readAllStandardOutput()
        stdout = bytes(data).decode("utf8")
        self.message(stdout)

    def handle_state(self, state):
        states = {   
            QProcess.NotRunning: 'Not running',
            QProcess.Starting: 'Starting',
            QProcess.Running: 'Running',
        }
        state_name = states[state]
        self.message(f"State changed: {state_name}")

    def process_finished(self):
        self.message("Process finished.")
        self.p = None        


app = QApplication(sys.argv)

w = MainWindow()
w.show()

app.exec_()

If you run this, you'll see the standard output, standard error, state changes and start/stop messages all being printed to the text box. Note that we convert the states back to friendly strings before output (using a dict to map from the enum values).

Logging standard output and error The output from our custom script is shown in the text box.

The output handling is a bit tricky and deserves a closer look.

python
        data = self.p.readAllStandardError()
        stderr = bytes(data).decode("utf8")
        self.message(stderr)

This is necessary because the QProcess.readAllStandardError and QProcess.readAllStandardOutput return data as bytes, wrapped in a Qt object. We must first convert this to a Python bytes() object, and then decode that bytestream to a string (here using UTF8 encoding).

Parsing data from process output

Currently we're just dumping the output from the program into the text box, but what if we wanted to extract some specific data from it? A common use case for this is to track progress from a running program, so we can show a progress bar as it completes.

In this example, our demo script dummy_script.py return a series of strings, including lines on standard error which show the current progress complete percentage -- e.g. Total complete: 50%. We can process these lines to a progress, and show this on the statusbar.

In the example below, we extract this using a custom regular expression. The simple_percent_parser function matches the standard error stream content and extracts a number between 00-100 for the progress. This value is used to update the progress bar added to the UI.

python
from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QPlainTextEdit, 
                                QVBoxLayout, QWidget, QProgressBar)
from PyQt5.QtCore import QProcess
import sys
import re

# A regular expression, to extract the % complete.
progress_re = re.compile("Total complete: (\d+)%")

def simple_percent_parser(output):
    """
    Matches lines using the progress_re regex, 
    returning a single integer for the % progress.
    """
    m = progress_re.search(output)
    if m:
        pc_complete = m.group(1)
        return int(pc_complete)


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()

        self.p = None

        self.btn = QPushButton("Execute")
        self.btn.pressed.connect(self.start_process)
        self.text = QPlainTextEdit()
        self.text.setReadOnly(True)

        self.progress = QProgressBar()
        self.progress.setRange(0, 100)

        l = QVBoxLayout()
        l.addWidget(self.btn)
        l.addWidget(self.progress)
        l.addWidget(self.text)

        w = QWidget()
        w.setLayout(l)

        self.setCentralWidget(w)

    def message(self, s):
        self.text.appendPlainText(s)

    def start_process(self):
        if self.p is None:  # No process running.
            self.message("Executing process")
            self.p = QProcess()  # Keep a reference to the QProcess (e.g. on self) while it's running.
            self.p.readyReadStandardOutput.connect(self.handle_stdout)
            self.p.readyReadStandardError.connect(self.handle_stderr)
            self.p.stateChanged.connect(self.handle_state)
            self.p.finished.connect(self.process_finished)  # Clean up once complete.
            self.p.start("python3", ['dummy_script.py'])

    def handle_stderr(self):
        data = self.p.readAllStandardError()
        stderr = bytes(data).decode("utf8")
        # Extract progress if it is in the data.
        progress = simple_percent_parser(stderr)
        if progress:
            self.progress.setValue(progress)
        self.message(stderr)

    def handle_stdout(self):
        data = self.p.readAllStandardOutput()
        stdout = bytes(data).decode("utf8")
        self.message(stdout)

    def handle_state(self, state):
        states = {   
            QProcess.NotRunning: 'Not running',
            QProcess.Starting: 'Starting',
            QProcess.Running: 'Running',
        }
        state_name = states[state]
        self.message(f"State changed: {state_name}")

    def process_finished(self):
        self.message("Process finished.")
        self.p = None        


app = QApplication(sys.argv)

w = MainWindow()
w.show()

app.exec_()

If you run this and start up a process, you'll see the progress bar gradually fill up as the progress messages are received from the dummy_script.py running through QProcess.

The progress bar is filling up. The progress bar fills up as the script completes.

This approach works well with any command line programs -- in some cases you may want to parse the standard output rather than standard error but the principles are identical. Sometimes programs will not give you a pre-calculated progress value and you'll need to get a little creative. If you like a challenge, try and modify the parser to extract the total time and elapsed time data from the dummy_script.py standard error and use this to calculate a progress bar. You can also try adapting the running for other command line programs.

Further improvements

In all of these examples we store a reference to the process in self.p, meaning we can only run a single process at once. But you are free to run as many processes as you like alongside your application. If you don't need to track information from them, you can simply store references to the processes in a list.

If you're running multiple external programs at once and do want to track their states, you may want to consider creating a manager class which does this for you. The book contains more examples, including this manager combining QProcess stdout parsing with model views to create a live progress monitor for external processes.

Process manager The process manager, showing active processes and progress

For more, see my PyQt5 bookCreate GUI Applications with Python & Qt5. The hands-on guide to building desktop apps with Python (PySide2 edition now available).

Qt Desktop Days – Day 1

If you missed Qt Desktop Days, you might be wondering what you missed. No need to worry! We’re going to give you a day-by-day summary of some of the cool things that were discussed, demoed, and explained. (We’re uploading all of the videos to our YouTube channel, but we’ll provide the direct links to each talk here as well.)

Here’s what we covered on our first day.

What’s new in Qt 6 for the desktop?

Our first video is a joint presentation. We open with a welcome to Qt Desktop Days by Matthias Kalle Dalheimer, our CEO, where he explains why we chose to organize a conference around desktop development. Despite everyone’s love affair with mobile, tablets, and IoT, desktop development is far from dead. In fact, as Kalle explains, it’s where some of the most interesting development is happening.

Then we quickly move on to Giuseppe D’Angelo who gives us a run-down on all the new and upcoming desktop-relevant features of Qt 6. If you want a preview of what’s coming up – what will impact your existing applications and what sorts of improvements we can look forward to – you won’t want to miss this one. Peppe lays out why the team decided to move to Qt 6 – the design goals and high-level features (like C++ property bindings), as well as the foundational updates, including the move to C++17, CMake, and Qt’s own 3D abstraction layer.

If you’ve been worried about the longevity of Qt Widgets, Peppe will put you at ease; he explains why widgets have been frozen but also what updates are coming – some that we’ve been waiting on for years! And Qt Quick is getting a facelift too … no more Javascript?

Layouts with Qt

If layout engines have been the bane of your existence, you will want to see the talk delivered by our Franck Arrecot that was jointly developed with Philippe Hermite from Adobe. There is no single way to build a layout, and Franck describes the advantages and disadvantages of the QLayout, QVBoxLayout, QGridLayout, QStackedLayout, and more. He shows us an example that’s simple to explain but complex enough to test various designs and code maintenance impacts. Franck delves into layout classes to explore dynamic sizing challenges, maintaining proper alignment without jumping rectangles, and constructing table headers on scrollable grids.

For those of you who have lots of data to display, Franck also does some benchmarks that show when it might make sense to switch from widget-based tables to those with manually drawn cells like QListView, QTreeView, or QTableView.

Building a desktop UI with QML

The last presentation on day 1 of Qt Desktop Days – but certainly not the least – was from Prashanth N. Udupa, who explains the insights he learned developing the SCRITE desktop application with a QML UI. SCRITE is a screenwriting editor that really uses the power of QML and Qt together to build a full-featured and complex tool. With very clear, visually intriguing, and in-depth examples from SCRITE’s source, Prashanth shows us how pervasive model-view thinking needs to be within your application, how to use the meta object system to your advantage, how to emulate Qt classes within QML, and much more.

Prashanth also explain how GammaRay helped him discover an excess of resources being used by his application, both accidentally and purposefully. He shows what he did to optimize and fine-tune his application – sometimes with elegant solutions, and sometimes with a one-line fix! You don’t always get real-life examples of technology in action, and this is a great talk that shows how QML and Qt can work well together optimally even in complex non-toy applications.

 

Visit the Qt Desktop Days playlist on our Youtube channel here.

The post Qt Desktop Days – Day 1 appeared first on KDAB.

KD Reports 1.9.0 Released

KDAB has released KD Reports 1.9.0! KD Reports is a developer tool that generates printable and exportable reports from code and from XML descriptions. Reports may contain text paragraphs, tables, headlines, charts, headers and footers and more.

Starting with this release, 1.9.0, KD Reports no longer supports Qt 4. However, KD Reports 1.8.2 and lower are still available with support for Qt 4. Our future efforts with KD Reports will go towards supporting Qt 6, which is due to come out soon.

Additionally, for KD Reports 1.9.0, we’ve added AbstractTableElement::setColumnConstraints, which allows you to control the widths of columns in tables. In the more recent versions of Qt, if you let Qt decide on the width of the columns for you, it might give you an odd column width, such as 1 character wide. A column that is 1 character wide can make it almost impossible to fit an entire word on one line within the column. This new release of KD Reports will give you full control over the widths of your table columns.

You can find the full list of highlights of KDAB Reports 1.9.0 here.

Find out more about KD Reports here.

If you’d like to take a look at the documentation, click here.

About KDAB

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

Subscribe to KDAB TV for similar informative short video content.

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

The post KD Reports 1.9.0 Released appeared first on KDAB.

Platform APIs in Qt 6

While Qt solves many of the typical tasks of writing an application, there are always corner cases that Qt can not cover, or where it makes more sense to build a feature on top of the platform specific APIs, or another toolkit. One of the tasks we wanted to address for Qt 6 was to clean up and coordinate the various mechanisms we had for accessing platform-specific functionality.

Native App Integration: How to Add Smooth Animations, 3D, Charts or Mini-Games with a Custom Qt View in Xcode or Android Studio

For the third post of the Felgo 3.7.0 series, we’ll have a look how to embed Qt cross-platform views into existing native apps for iOS or Android. Instead of choosing pure native development or only developing cross-platform, you can now freely add Qt and Felgo views into your native app as well!

 

Features like 3D Scenes, Custom UI or Visual Effects are hard to create natively with Android Studio or Apple Xcode. Save time and effort by using Felgo Native App Integration for rich experiences with 3D content, shaders, graphical effects, charts, multimedia, custom controls, gamification features or unique animations. 

KDDockWidgets v1.1 has been released!

KDDockWidgets v1.1 is now available!

Although I just wrote about v1.0 last month, the 1.1 release still managed to get a few big features.

Here’s the ChangeLog:

Auto-hide/SideBar support

This was much awaited and probably the last essential feature that was missing. You can now set a dock widget to auto-hide to a side bar.

Click the icon that looks like a “pin.” The dock widget will then disappear and you’ll get an entry for it on one of the 4 side bars (top, left, right, bottom).

Click the dock widget’s entry on the side bar to show it again. It will be displayed overlaid, over the existing layout.

Click the “pin” icon again to restore the dock widget into the layout again.

Pass -w to the example to see it in action: kddockwidgets_example -w

 

Drop shadows for Floating Windows

Floating dock widgets on Windows 10 now display a nice drop-shadow. This was already the case for macOS and Linux for some window managers.

 

drop-shadow

 

FocusScope

Dock Widgets now support being a Focus Scope. You might already be familiar with QtQuick’s FocusScope. Here it’s a similar concept.

In QtWidgets, focus is per top-level window. Each QWindow remembers the widget that has focus. However, dock widgets are a bit of a hybrid between top-level and child widgets. When they are docked, they aren’t top-level anymore, but you probably still want them to remember focus. If you enable Flag_TitleBarIsFocusable, clicking on a dock widget’s title bar will focus whatever widget was previously focused, even if the dock widget is embedded in the main window.

HDPI improvements

Title bar icons (such as close, float, maximize, minimize) now have high resolution versions, meaning they won’t become pixelated at higher resolutions anymore.

Additionally, we also tested and fixed many multi-monitor scenarios with different HDPI settings on Windows.

Misc bugfixing

  • Windows: Fixed windows not honoring minimum size
  • Windows: Fixed moving windows across screens with different DPI (#72), they would get weird sizes
  • Don’t center floating windows if the user set a custom position (#75).
  • Fixed floating window’s title not correct (#74)
  • Fixed floating window borders not rendered correctly on HDPI due to rounding errors.
  • cmake/Python: don’t require pkg-config, only use if available (#68)
  • Everything else that didn’t make it to the ChangeLog

Stay tuned, 1.2 should be out soon!

Visit the KDDockWidgets GitHub page to get the update: https://github.com/KDAB/KDDockWidgets/releases/tag/v1.1.0.

You can find the source code for the KDDockWidgets 1.1 release on GitHub at: https://github.com/KDAB/KDDockWidgets

Tarballs and zipballs for 1.1 are available from: https://github.com/KDAB/KDDockWidgets/releases

Prebuilt packages for some popular Linux distributions are available at: https://build.opensuse.org/project/repositories/isv:KDAB

KDDockWidgets is available as commercial and open source (GPLv2 & GPLv3).

For commercial licensing regarding inclusion in proprietary software and/or for tailored support options contact us and ask for a 30-day free trial license.

About KDAB

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

Subscribe to KDAB TV for similar informative short video content.

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

 

The post KDDockWidgets v1.1 has been released! appeared first on KDAB.

More news on Meeting C++

 

KDAB is a proud Gold sponsor at Meeting C++. Let us get to know you more: take the C++ survey (it doesn’t take long and your data is protected). Each time you answer a poll, you’ll see the result directly. Enter the survey here…

The agenda

Meeting C++ 2020 is getting closer and already has an impressive lineup of 11 talks, 3 Amas, and 3 keynotes. More details regarding the keynotes will be announced in the coming weeks.

KDAB’s Marc Mutz is presenting his talk Partially-Formed Objects For Fun And Profit on day 3, November 14th 11:40.

Visit the schedule to read more…

Online platform

If you attended CppCon, you might already be familiar with the online platform Remo. This will also be used for this year’s Meeting C++. Jens has made an introduction video on Remo and we definitely recommend you watching it so that you get to know some nice-to-know features when attending. Watch the Remo demo here…

Tickets

There are still available tickets for the event,  go here to get them.

Be aware, on November 1st, the online tickets will get a price hike of 100 Euros so that the organizer can get a sense of the number of attendees.

 

Subscribe to the Meeting C++ Newsletter to get the updates you want directly

The post More news on Meeting C++ appeared first on KDAB.

Cutelyst 2.13 and ASql 0.19 released

Cutelyst the C++/Qt Web Framework and ASql the ASync SQL library for Qt applications got new versions.

Thanks to the work on ASql Cutelyst got some significant performance improvements on async requests, as well as a new class called ASync, which automatically detaches the current request from the processing chain, and attaches later on when it goes out of scope.

With the ASync class you capture it on your ASql lambda and once the query result arrives and the lambda is freed and the ASync object gets out of scope and continues the processing action chain.

KDAB's fix on a 10 year old bug raised my attention back to a note on QTcpSocket documentation:

Note: TCP sockets cannot be opened in QIODevice::Unbuffered mode.

Which is actually wrong, according to the source code, Cutelyst has been using buffered mode since always due that, so hopefully this new version will be a bit faster and consume less memory, it's important to notice that once the Kernel tells it's going to block QTcpSocket writes get buffered anyway.

Now talking about ASql you might notice the jump on release versions, this is because I've been experimenting some changes and didn't want to write a post at each new feature.

ASql is now at the closest API I'd like it to be, unfortunately one of my goals that would be to be able to handle it's AResult object to Grantlee/Cutelee and be able to iterate over it's records just once, but the call QVariant::canConvert with QVariantList must succeed and the class be able to be casted to QAssociativeIterable or QSequentialIterable, which I didn't managed to get it working in a way I like.

But AResult has hash() and hashes() methods that convert the data to a format that can be used in templating.

On the plus side I added iterators (if you have experience with iterators please review my code as this was the first time I wrote this kind of code) that also work on for ranged loops, and they also have faster type conversion methods, instead converting the data to a QVariant type, and them using QVariant to get the data out of it, one can just call toInt() which will get the int straight from the result set without checking each time if it's an int.

Added AMigrations which is an awesome database maintenance class that is both simple and helpful to maintain database schemas.

  • ACache class to cache special queries
  • Support for data inside QJsonValue
  • Single Row mode (the lambda get's called once per result)
  • Prepared Queries
  • Scoped Transaction class
  • Notifications - this is my favorite PostgreSQL feature, it's hard to image some other big databases lack such an useful feature.

Oh, and Cutelyst results on TechEmpower already got better thanks to ASql, hoping to see even better results when I update the tests to 2.13 that has Unbuffered and faster async handlying.

https://github.com/cutelyst/asql/releases/tag/v0.19.0

https://github.com/cutelyst/cutelyst/releases/tag/v2.13.0

Release 3.7.0: Bluetooth LE (Low Energy) with QML, Apple Sign In, Secure Keychain Storage

Felgo 3.7.0 adds new QML APIs to use Bluetooth LE from QML in your Qt and Felgo apps. This gives you a convenient way to use Bluetooth LE to connect to IoT, wearables and other custom devices, directly from QML, without any C++ code. This update also adds a lot of new components, features, improvements and fixes.

This blog post is the first one of a short series of posts to show you everything that is new with Felgo 3.7.0, so make sure to stay in the loop!

Qt OPC UA updates

Qt OPC UA updates

The Qt OPC UA module has been ported to CMake and will be part of Qt 6 right from the first release.
In addition to numerous bug fixes and improved test coverage, the open62541 plugin has been updated to open62541 v1.1 and uses OpenSSL for security support, thus removing the dependency on mbedTLS.

Continue reading Qt OPC UA updates at basysKom.