programming

Dial and StyleItem

Submitted by mimec on 2014-08-15

As I promised, I'm starting a new series of articles dedicated to QtQuick. When I started playing with QtQuick and the QtQuick Controls module, I noticed that it implements QtQuick equivalents of most widgets, and Dial is one of the few that are missing. Perhaps QDial is not a very frequently used widget, but in certain kinds of QtQuick applications, especially for mobile devices, a dial control can come in handy:

Dial

A bit of googling quickly revealed that a Dial control exists as an experimental part of the QtQuick Controls module (you can find it here), but for whatever reason it never made it into the official package.

Here's my final version of Dial.qml after some tweaking:

import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Private 1.0

Control {
    id: root

    width: 100
    height: 100

    property alias minimumValue: range.minimumValue
    property alias maximumValue: range.maximumValue
    property alias value: range.value
    property alias stepSize: range.stepSize
    property alias pressed: mouseArea.pressed

    property bool notchesVisible: false
    property real notchSize: 1
    property bool activeFocusOnPress: false

    activeFocusOnTab: true

    Accessible.role: Accessible.Dial
    Accessible.name: range.value

    Keys.onLeftPressed: { range.value -= range.stepSize; }

    Keys.onRightPressed: { range.value += range.stepSize; }

    RangeModel {
        id: range
        minimumValue: 0
        maximumValue: 1
        stepSize: 0
        value: 0
    }

    MouseArea {
        id: mouseArea
        anchors.fill: parent
        hoverEnabled: true

        onPositionChanged: {
            if ( mouseArea.pressed )
                range.value = mouseArea.valueFromPoint( mouseArea.mouseX, mouseArea.mouseY );
        }

        onPressed: {
            range.value = mouseArea.valueFromPoint( mouseArea.mouseX, mouseArea.mouseY );
            if ( root.activeFocusOnPress )
                root.focus = true;
        }

        onWheel: { range.value += wheel.angleDelta.y * range.stepSize / 120.0; }

        function valueFromPoint( x, y ) {
            var yy = root.height / 2 - y;
            var xx = x - root.width / 2;

            var angle = ( xx || yy ) ? Math.atan2( yy, xx ) : 0;

            if ( angle < -Math.PI / 2 )
                angle += 2 * Math.PI;

            var dist = 0;
            var minv = range.minimumValue;
            var maxv = range.maximumValue;

            if ( minimumValue < 0 ) {
                dist = -range.minimumValue;
                minv = 0;
                maxv = range.maximumValue + dist;
            }

            var v = ( minv + ( maxv - minv ) * ( Math.PI * 4 / 3 - angle ) / ( Math.PI * 10 / 6 ) );

            if ( dist > 0 )
                v -= dist;

            return Math.max( range.minimumValue, Math.min( range.maximumValue, v ) );
        }
    }

    style: Qt.createComponent( "DialStyle.qml" )
}

Note: for copyright purposes, the code snippets are licensed under the BSD-style open source license, just like the original code.

The code is very straightforward. The internal RangeModel object is used for handling minimum, maximum and step values (the Slider uses it too). The valueFromPoint() function converts the position of the mouse to the angle and then to the actual value. I renamed "tickmarksEnabled" to "notchesVisible" and added "notchSize" for compatibility with QDial. I also removed "wrapping", because the underlying StyleItem doesn't support it anyway. Finally, I added the missing mouse wheel and keyboard support.

What about drawing the control? The original Dial contains a StyleItem, another internal component of QtQuick Controls, which uses the application's default QStyle to paint items so that they look just like widgets. Now, a little digression. Qt5 has two separate UI modules - QtGui for handling windows and QtWidgets which implements the widgets. The former can be used without the latter, for example if your applications uses OpenGL or QtQuick or a custom painting engine and doesn't need widgets. By excluding widgets, you can reduce dependencies by a few megabytes.

The QtQuick Controls can work perfectly fine without QtWidgets. However, in that case they are drawn using built-in bitmap images, and they look the same on all platforms. On the other hand, when QtWidgets are enabled, the QtQuick Controls use the StyleItem components, which emulate the "native" look of widgets on the given platform. To be more specific, the style used by QtQuick Controls depends on whether you are using QGuiApplication or QApplication (the former is part of QtGui; the latter is part of QtWidgets). This information is deeply buried in the Qt documentation, so it can be quite confusing. If you dig into the Qt sources, you will find that the QtQuick.Controls.Styles module actually contains two subdirectories: "Base" with platform-independent styles and "Desktop" with ones that use StyleItem. The internal QQuickControlSettings class is used to select the right styles at run-time, and you can even use the "QT_QUICK_CONTROLS_STYLE" environment variable to use an entirely different set of styles.

Back to the main topic, embedding a StyleItem directly in the Dial is not a good idea, because it only works with the QtWidgets module and it's not possible to implement a custom or platform-independent style. That's why I changed the code to use the "style" property of the "Control" component, decoupling the style from the control, just like other QtQuick Controls do. The default implementation of the style is based on the original code, so it uses the StyleItem to do the job:

import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Private 1.0

Style {
    property Component panel: StyleItem {
        id: styleItem

        property real visualPos: control.maximumValue - control.value

        property real granularity: {
            if ( ( control.maximumValue - control.minimumValue ) < 10 )
                return 100;
            else if ( ( control.maximumValue - control.minimumValue ) > 1000 )
                return 1 / Math.ceil( ( control.maximumValue - control.minimumValue ) / 1000 );
            else
                return Math.floor( 1000 / ( control.maximumValue - control.minimumValue ) );
        }

        elementType: "dial"
        activeControl: control.notchesVisible ? "ticks" : ""
        hasFocus: control.focus
        enabled: control.enabled
        sunken: control.pressed
        maximum: control.maximumValue * styleItem.granularity
        minimum: control.minimumValue * styleItem.granularity
        value: styleItem.visualPos * styleItem.granularity
        step: Math.ceil( control.notchSize * styleItem.granularity )

        Behavior on visualPos {
            enabled: !control.pressed
            NumberAnimation {
                duration: 300
                easing.type: Easing.OutSine
            }
        }
    }
}

This code simply passes information from the control to the StyleItem. The only interesting part is the "granularity" property. The original code uses a hard-coded value of 100 instead. By increasing the number of steps internally, it's possible to have a nice looking, smooth animation when the Dial rotates. However, the hard-coded value of 100 doesn't always work, because the internal implementation of QStyle only paints the notches correctly if the total number of steps doesn't exceed 1000. That's why my code uses a dynamic granularity instead which works correctly in most cases.

A very simple, platform-independent implementation of the style could use a rotating image instead:

import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Private 1.0

Style {
    property Component panel: Image {
        id: image

        property real visualPos: control.value

        source: control.pressed ? "dial-on.png" : "dial.png"
        rotation: 180 * 7 / 6 + ( image.visualPos - control.minimumValue ) * ( 180 * 10 / 6 )
            / ( control.maximumValue - control.minimumValue )

        Behavior on visualPos {
            enabled: !control.pressed
            NumberAnimation {
                duration: 300
                easing.type: Easing.OutSine
            }
        }
    }
}

In order to create the notches, you can place a small Rectangle in a Repeater and use appropriate rotation.

Hello, QtQuick!

Submitted by mimec on 2014-07-23

It's been a while... As usual, this means that there's been a lot going on and I simply didn't have time to write. However, I'm planning to start a new series of articles dedicated to QtQuick. It's been over 2 months since I started my adventure with QtQuick, so I have some initial observations of its strong and weak points that I would like to share.

I think that the biggest problem with QtQuick is that it's hugely undervalued. Unfortunately, there are some political reasons behind this. Major software companies try to tie developers to their platforms and platform-specific frameworks. It's no big surprise that Nokia had to get rid of Qt when it was purchased by Microsoft, even though in theory a good cross-platform framework should be a goldmine for the company who owns it. The result is that many people unduly consider Qt dead.

The reality is that QtQuick has a lot of potential. It's more powerful than HTML5 when it comes to rich, platform-independent UI, and it's supported by powerful back-end Qt modules. It's free even for commercial use, and it's quite mature and still actively developed. It also supports both mobile and desktop platforms. Of course, there are also disadvantages. First, in order to use QtQuick you need to learn QML, which is not as easy as it seems, especially when attempting to create complex applications. Second, QtQuick (and Qt in general) is a toolkit, not a framework, so you have to lay down a lot of foundation before you can start being productive developing the actual application. Finally, the documentation is quite poor, and there is not too much open source code using QtQuick that you can peak into, so it's often difficult to find any useful information about a problem you're trying to solve. Because of that, learning QtQuick requires a bit of trial and error approach, but from what I can tell so far, it's worth the effort.

Apart from the fact that QtQuick has many ready to use UI components (including the Quick Controls), there are also additional benefits from writing the application partially in QML and partially in C++. When the UI is written entirely in QML, it can be designed by people with graphical skills, who don't necessarily need to know low level programming languages like C++. It's similar to how HTML/CSS templates are created independently from PHP (or whatever) code in web applications. Also the UI of the application can be fully tested without writing a single line of C++ code, which is great for rapid design and prototyping. To make it possible, simple stubs (mock objects) for application logic can be written in QML. When using a simple MVP or MVC pattern it's also possible to easily switch views - for example between the desktop and mobile versions of the application. I will write more about it in one of the next articles.

Issues and Tins

Submitted by mimec on 2013-11-13

Today I released version 1.1 of WebIssues, a major release which introduces lots of very nice and useful features. I must admit that I'm relieved that this project is finally over. It's also perfect timing, because I'm getting seriously involved in the indie game which I announced some time ago. The game is now officially called Mister Tins and it even already has a Facebook page and a blog (where I will probably post more often than here in the nearest future).

What started as a quick test project, has now become a playable and quite enjoyable game (at least for me). It's still very far from the first official release, but at least now I'm convinced that this is really what I want to do. It's what I always wanted to do and what I probably should have done a long time ago.

I'm not saying that I regret what I've been doing for the last few years. I definitely wouldn't be half as good programmer if it wasn't for WebIssues. I would even go as far as saying that I wouldn't be half the person I am today if it wasn't for all the open source projects I've been involved in. All this technical and non-technical experience should now pay off with this new project.

Of course, there's no guarantee that I will succeed. I know that there is a lot of potential in what I'm doing, and the whole idea of the game, while simple, seems quite innovative. On the other hand, there are many factors involved, and not all of them depend on me. A lot of luck is needed to provide what people need exactly in the right moment. Also, creating games is a team sport, and experience taught me that finding the right team members is not easy an task. But the best thing is that I've reached one very major goal, which was releasing WebIssues 1.1, and I can immediately concentrate on the next goal, without losing the momentum that I have.

Disabling a QLabel with links

Submitted by mimec on 2013-09-16

Qt doesn't have an equivalent of .NET's LinkLabel control, because the regular QLabel supports HTML content, including links. It's easy to create a link label using an anchor tag (i.e. <a href="...">...</a>). You can then handle the linkActivated signal or simply set the openExternalLinks property to true if you want the label to automatically open the link target in a web browser. However, there is one small limitation of the standard label widget: when you disable it, the links no longer work, but they are still drawn the same way as regular links (i.e. as blue underlined text).

Recently I had to slighty modify the notification schedule editor in WebIssues which looks like this:

Enabled labels

The links on the right make it easier to check or uncheck all options at once. However, the entire schedule needs to be disabled if sending summary emails is turned off:

Disabled labels

Note that the links are drawn in the same way as the regular disabled label and the checkboxes. How to achieve such effect?

After some research it turned out that tweaking the label's palette and/or style sheet doesn't do the job as expected. The workaround is to include the appropriate style directly in the anchor tag, for example:

QColor color = palette().color( QPalette::Disabled, QPalette::WindowText );
label->setText( QString( "<a href=\"%1\" style=\"color: %2; text-decoration: none;\">%3</a>" )
    .arg( linkUrl, color.name(), linkCaption );

Note that the disabled text color from the current palette is passed to the style definition so that it looks the same as the regular disabled label.

To make it easier, I wrote a very simple subclass of QLabel which automatically generates the correct HTML when it becomes enabled or disabled:

class LinkLabel : public QLabel
{
    Q_OBJECT
public:
    LinkLabel( const QString& caption, QWidget* parent ) : QLabel( parent ),
        m_linkUrl( "#" ),
        m_linkCaption( caption )
    {
        setTextFormat( Qt::RichText );
        updateLink();
    }

    ~LinkLabel() { }

public:
    void setLinkUrl( const QString& url )
    {
        m_linkUrl = url;
        updateLink();
    }

    const QString& linkUrl() const { return m_linkUrl; }

    void setLinkCaption( const QString& caption )
    {
        m_linkCaption = caption;
        updateLink();
    }

    const QString& linkCaption() const { return m_linkCaption; }

protected: // overrides
    void changeEvent( QEvent* e )
    {
        QLabel::changeEvent( e );
        if ( e->type() == QEvent::EnabledChange || e->type() == QEvent::PaletteChange )
            updateLink();
    }

private:
    void updateLink()
    {
        QString style;
        if ( !isEnabled() ) {
            QColor color = palette().color( QPalette::Disabled, QPalette::WindowText );
            style = QString( " style=\"color: %1; text-decoration: none;\"" ).arg( color.name() );
        }
        setText( QString( "<a href=\"%1\"%2>%3</a>" ).arg( Qt::escape( m_linkUrl ), style,
            Qt::escape( m_linkCaption ) ) );
    }

private:
    QString m_linkUrl;
    QString m_linkCaption;
};

Note that in this example the link's URL defaults to "#", which is convenient if the link is handled internally using the linkActivated signal. Of course you can overload the contstructor so that it takes the URL as a parameter. Also it's not very difficult to modify the code so that a single label could contain multiple links.

QTextBrowser vs. QWebView pt. 2

Submitted by mimec on 2013-07-19

Last week I briefly compared QTextBrowser and QWebView, the two Qt widgets which can be used for displaying rich text and HTML documents (and explained the differences between rich text and HTML). I concluded that QWebView should be used to display external web content, and for creating complex layouts, where QTextBrowser has limited capabilities. However, I also said that QWebView has its own problems. Today I will focus on these.

The entire QtWebKit component is based on WebKit, the web browser engine which also powers Safari and Chrome. However, keep in mind that the version used by Qt (especially Qt4) is quite old (last changes are from 2011). Also, in addition to common code, there are also lots of platform dependent components, so the Qt version of WebKit is not entirely the same as the one used by these browsers. What this means is that if you embed a QWebView in your application, it won't automatically have the same capabilities as Google Chrome.

The most annoying thing in QtWebKit is that printing is currently quite broken. When you print issue history or any other report in version 1.1 of the WebIssues Desktop Client, you can notice that sometimes lines are cut in half by page breaks. The same happens when exporting the report as a PDF document. This bug is already fixed upstream, but it still occurs in Qt 4.8. Unfortunately it seems that even in the latest versions of Qt printing is still broken.

Another thing to watch for when printing, though that's not really a bug, is the fact that printing the page immediately after calling setHtml() will sometimes produce no result. If the page contains any references to other resources, they are always loaded asynchronously, even if they are local files or embedded resources (which can be accessed using the qrc: protocol). The solution is to handle the loadFinished() signal from the QWebFrame.

QtWebKit has problems with certain Unicode characters. In version 1.0 of WebIssues I used the → character in history of changes, but in version 1.1 I had to replace it with », because → breaks the text layout. This is similar to this bug, however in my case this occurred not only when printing, but also QWebView rendered the text incorrectly.

Also tabs are incorrectly rendered by the QtWebKit and appear as spaces. This bug is apparently fixed in Qt 5, but not in Qt 4.8, so in the WebIssues Desktop Client I have to convert all tabs to spaces, because tabs may occur in issue comments. This isn't perfect, especially when using a non-monospace font, but it works in most cases.

I also found than the hasSelection() property of QWebView always returns true, even if there is really no text selected. Since I'm using this to determine whether the Copy action should be enabled, I'm simply checking if the built-in Copy action is enabled instead, in other words I'm calling pageAction( QWebPage::Copy )->isEnabled(). Fortunately the selectionChanged() signal is emitted correctly.

In general, I think that the whole API of QtWebKit module is quite limited, especially when compared to QTextBrowser and QTextDocument. Even though QtWebKit was first introduced in Qt 4.4, many useful functions are only available in the most recent versions. However, it is sufficient for my current needs, so despite the various bugs and problems, I'm generally satisfied with the migration from QTextBrowser to QWebView.