QML Circular Slider

A highly customisable circular slider written in QML.

Following is the list of properties of the Circular Slider.

  • trackWidth
  • progressWidth
  • handleWidth
  • handleHeight
  • handleRadius
  • startAngle
  • endAngle
  • minValue
  • maxValue
  • value
  • angle [readonly]
  • capStyle
  • trackColor
  • progressColor
  • handleColor
  • stepSize
  • snap
  • handle [Component]
  • pressed [readonly]
  • hideTrack
  • hideProgress

I have seen many Circular Dial implementation which were either written in C++ or using Canvas in QML. I thought of creating my own component where I can provide option to make it more customisable so that it can be used in all kind of applications.

I have decided to use Qt Quick Shapes to create the track and progress same as what I have done for my last component Circular Progress Bar. And provide customisation option not only limited to colors but also other properties like handle.

Let’s have look the anatomy of the Slider. Below is the image of the CircularSlider with basic properties.

  • The Track and Progress are created using Shape component with PathAngleArc.
  • Default handle is rounded rectangle which is loaded using a Loader inside the handleItem. By setting the property z to 2 we make sure that the handle is rendered always on top when we use any child item inside the slider for progress indicator.
  • A MouseArea is used which covers the whole component but only accepts mouse clicks along the slider track with a given minimum width.
  • A QtObject which is used to hide the internal details of the component from outside. This is the object inside which we hide all the internal properties and functions which are used for calculations.

Demo Screenshots

Basic properties Demo
Custom Handle Items
Custom Progress Indicator

SOURCE CODE:

https://github.com/arunpkqt/RadialBarDemo

CircularSlider is now available in Qt Marketplace. https://marketplace.qt.io/products/circularslider

QML RadialBarShape

In my previous blog post I have shown how to create a RadialBar using QQuickPaintedItem. Now I have rewritten the RadialBar using the Qt Quick Shapes module which was available in Qt since the version Qt 5.10.

Here in this post I will explain how to use Qt Quick Shapes to create the Radial Bar Shape and with the exact set of properties which were available in the previous component.

If you look at the documentation of the Shapes module here you will find that there are few QML types available in which the main types which we are going to use are Shape & ShapePath.

Shape is a basic type which renders a path either by generating geometry via QPainterPath and manual triangulation or by using a GPU vendor extension like GL_NV_path_rendering. Unlike the QtQuickPaintedItem the path never gets rasterized in software which avoid the performance penalty for texture uploads.

To make sure the feature parity with the previous RadialBar component I have created all the properties (except the property size) same as that like below.

import QtQuick 2.15
import QtQuick.Shapes 1.15

Item {
    id: control

    implicitWidth: 200
    implicitHeight: 200

    enum DialType {
        FullDial,
        MinToMax,
        NoDial
    }

    property real startAngle: 0
    property real spanAngle: 360
    property real minValue: 0
    property real maxValue: 100
    property real value: 0
    property int dialWidth: 15

    property color backgroundColor: "transparent"
    property color dialColor: "#FF505050"
    property color progressColor: "#FFA51BAB"

    property int penStyle: Qt.RoundCap
    property int dialType: RadialBarShape.DialType.FullDial
}

I have also moved some of the properties inside a QtObject to keep it as private and to be used only inside the component like below.

QtObject {
        id: internals

        property bool isFullDial: control.dialType === RadialBarShape.DialType.FullDial
        property bool isNoDial: control.dialType === RadialBarShape.DialType.NoDial

        property real baseRadius: Math.min(control.width / 2, control.height / 2)
        property real radiusOffset: internals.isFullDial ? control.dialWidth / 2
                                                         : control.dialWidth / 2
        property real actualSpanAngle: internals.isFullDial ? 360 : control.spanAngle

        property color transparentColor: "transparent"
        property color dialColor: internals.isNoDial ? internals.transparentColor
                                                     : control.dialColor
    }

And the main part of the component is the actual Shape which consists of three ShapePath each for the Background, Dial & the Progress.The complete code of the Shape is given below.

Shape {
        id: shape
        anchors.fill: parent
        layer.enabled: true
        layer.samples: 8

        ShapePath {
            id: pathBackground
            strokeColor: internals.transparentColor
            fillColor: control.backgroundColor
            capStyle: control.penStyle

            PathAngleArc {
                radiusX: internals.baseRadius - control.dialWidth
                radiusY: internals.baseRadius - control.dialWidth
                centerX: control.width / 2
                centerY: control.height / 2
                startAngle: 0
                sweepAngle: 360
            }
        }

        ShapePath {
            id: pathDial
            strokeColor: control.dialColor
            fillColor: internals.transparentColor
            strokeWidth: control.dialWidth
            capStyle: control.penStyle

            PathAngleArc {
                radiusX: internals.baseRadius - internals.radiusOffset
                radiusY: internals.baseRadius - internals.radiusOffset
                centerX: control.width / 2
                centerY: control.height / 2
                startAngle: control.startAngle - 90
                sweepAngle: internals.actualSpanAngle
            }
        }

        ShapePath {
            id: pathProgress
            strokeColor: control.progressColor
            fillColor: internals.transparentColor
            strokeWidth: control.dialWidth
            capStyle: control.penStyle

            PathAngleArc {
                radiusX: internals.baseRadius - internals.radiusOffset
                radiusY: internals.baseRadius - internals.radiusOffset
                centerX: control.width / 2
                centerY: control.height / 2
                startAngle: control.startAngle - 90
                sweepAngle: (internals.actualSpanAngle / control.maxValue * control.value)
            }
        }
    }

And the example usage can be found below.

QtObject {
        id: feeder

        property real value: 0

        SequentialAnimation on value {
            loops: Animation.Infinite
            NumberAnimation { to: 100; duration: 2000 }
            NumberAnimation { to: 0; duration: 2000 }
        }
    }

    ColumnLayout {
        anchors.centerIn: parent
        spacing: 10
        RowLayout {
            Layout.fillWidth: true
            spacing: 20
            RadialBarShape {
                value: feeder.value
            }
            RadialBarShape {
                Layout.preferredHeight: 300
                Layout.preferredWidth: 300
                progressColor: "#e6436d"
                value: feeder.value
                spanAngle: 270
                dialType: RadialBarShape.DialType.FullDial
                backgroundColor: "#6272a4"
                penStyle: Qt.FlatCap
                dialColor: "transparent"
            }
            RadialBarShape {
                progressColor: "#8be9fd"
                value: feeder.value
                spanAngle: 270
                dialType: RadialBarShape.DialType.NoDial
                dialColor: Qt.darker("#8be9fd", 3.8)
                penStyle: Qt.SquareCap
                dialWidth: 10
            }
            RadialBarShape {
                id: newone
                progressColor: "#50fa7b"
                value: feeder.value
                startAngle: 90
                spanAngle: 270
                dialType: RadialBarShape.DialType.MinToMax
                backgroundColor: "#6272a4"
                dialWidth: 5
            }
        }
    }

Source Code

https://github.com/arunpkqt/RadialBarDemo/blob/master/RadialBarShape.qml

Screenshot

Shared QML Component Library

Here is my latest post on QML after a long time.

In this post I am going to explain how you can bundle a set of QML files to a module and make it shareable so that any other QML application can load and use it. If you have any of the component which is defined using C++ class then it is better to go with the plugin approach which is explained here.

To create a QML module which contains all teh QML files we need to follow some set of rules which Qt defined. More details can be found here.

  1. The folder structure where we keep the QML files and assets should be in the format
-imports
 - com
   - apk // this could be the name of your Organization/Project
     - components
     - fonts
     - icons

2. We should keep a special file called qmldir inside the components folder which will have the list of QML components inside that folder. More details can be found here.

3. Add a new QRC file which includes all our QML files and the same can be used by thirdparty application to load this library. Below is the example file.

<RCC>
    <qresource prefix="/">
        <file>imports/com/apk/components/Button.qml</file>
        <file>imports/com/apk/components/CheckBox.qml</file>
        <file>imports/com/apk/components/SpinBox.qml</file>
        <file>imports/com/apk/components/qmldir</file>
    </qresource>
</RCC>

4. The component library can now be used in any of the third party applicatio by just importing the above mentioned imports folder in the pro file and load the qrc file.

How to use Component Library in a project

As mentioned in above steps what you got is the imports directory (contains all the QML & qmldir file and other assets if any) and the imports.qrc files.

  1. Create your test project where you want to use this library and in the pro file add the path to the qmldir file for the QMake to find and parse information about the library.
QML_IMPORT_PATH = "path/to/the/imports"

2. Load the imports.qrc file to the project by adding it in the pro file by using

RESOURCES += imports.qrc

3. We need to also pass the path to qmldir to the QQmlEngine to search for the installed modules. This path may be a local filesystem directory, a Qt Resource path (:/imports), a Qt Resource url (qrc:/imports) or a URL. As we have already included the qrc file in aforementioned step we could use

engine.addImportPath(":/imports");

4. In the QML file import the module as

import com.apk.components 1.0 as Apk

Example code

import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14

import com.apk.components 1.0 as Apk

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Shared QML Components")

    ColumnLayout {
        anchors.centerIn: parent

        Apk.Button {
        }

        Apk.CheckBox {
        }

        Apk.SpinBox {
        }
    }
}

Git Repo

Here you find the example code https://github.com/arunpkqt/SharedComponentLibrary

QML RadialBar component

Radial Bar is a highly customizable QML component created using QQuickPaintedItem class.
This is a useful component to be used for any QML application to show the status of progress.

Following are the list of main properties supported by this component.

  • startAngle
  • spanAngle
  • minValue
  • maxValue
  • value
  • dialWidth
  • backgroundColor
  • foregroundColor
  • progressColor
  • textColor
  • penStyle
  • suffixText
  • showText
  • dialType
  • textFont

The property penStyle can be used to provide two styles which Qt is providing which are Qt::FlatCap & Qt::RoundCap.

The property DialType is provided as custom enum which will have following properties.

  • FullDial
  • MinToMax
  • NoDial

Below is the sample usage of RadialBar component.

RadialBar {
     width: 300
     height: 300
     penStyle: Qt.RoundCap
     dialType: RadialBar.FullDial
     progressColor: "#1dc58f"
     foregroundColor: "#191a2f"
     dialWidth: 30
     startAngle: 180
     spanAngle: 70
     minValue: 0
     maxValue: 100
     value: 50
     textFont {
         family: "Halvetica"
         italic: false
         pointSize: 16
     }
     suffixText: "%"
     textColor: "#FFFFFF"
}

Screenshots:

04_Radial_Dial

Source code:

https://github.com/arunpkqt/RadialBarDemo

New version of RadialBar rewritten using Qt Quick Shape is now available here,

https://github.com/arunpkqt/RadialBarDemo/blob/master/RadialBarShape.qml

This can be used a s a drop-in replacement for the previous RadialBar component.

Related latest blog post is available here

QML RadialBarShape

Update:

A port of QML RadialBar using PyQt is available at,

http://pyjuice.com/porting-radialbar-cqt-pyqt/

QML User Login App with SQLite backend

Qt Quick provides an option for reading and writing SQLite databases using the module “QtQuick.LocalStorage”.  These databases are user-specific and QML-specific, but accessible to all QML applications. They are stored in the Databases subdirectory of QQmlEngine::offlineStoragePath(), currently as SQLite databases. Database connections are automatically closed during Javascript garbage collection.

In this blog post I am going to demonstrate how we can write a simple user based login application using QML which uses LocalStorage to store the user credentials. This demo application has following features.

  • New User Registration
  • User Login
  • Authentication and error handling
  • Password retrieval

This application is spitted into multiple screens and uses StackView show the screens. Following are the list of screens which we will be using in this demo application.

  • LogInPage
  • RegisterScreen
  • PasswordResetPage
  • UserInfoPage

The icons which you can see in this application are created by using “Font Awesome” font. I have used the FontLoader from QML for this purpose. And this font object is used for all the Text items to show their respective icons using Unicode strings.

FontLoader {
        id: fontAwesome
        name: "fontawesome"
        source: "qrc:/fontawesome-webfont.ttf"
    }

In this application I have used a PopUp item to show all the warnings and error messages of this application. This popup is placed at the bottom of the screen. I have also introduced a Timer along this popup to close it automatically after 2 seconds.

    //Popup to show messages or warnings on the bottom position of the screen
    Popup {
        id: popup
        property alias popMessage: message.text
        background: Rectangle {
            implicitWidth: rootWindow.width
            implicitHeight: 60
            color: "#b44"
        }
        y: rootWindow.height
        modal: true
        focus: true
        closePolicy: Popup.CloseOnPressOutside

        Text {
            id: message
            anchors.centerIn: parent
            font.pointSize: 12
            color: "#ffffff"
        }
        onOpened: popupClose.start()
    }

    // Popup will be closed automatically in 2 seconds after its opened
    Timer {
        id: popupClose
        interval: 1000
        onTriggered: popup.close()
    }

I have used a JavScript file “backend.js” to validate the user credentials for login and registration.

In my “main.qml” file I have used a StackView as a container and it loads “LogInPage.qml” as the initial page. And here I also initializes the Database and create a table to store user details. Instance of this database will be used across the application to store/retrieve user data.

Following are the main operation which I am performing on the database.

  • Create database and create a new table for user credentials
  • Insert user credentials in database
  • Retrieve password for the given user for authentication
  • Retrieve password for the given user and hint for password retrieval

Below is my application’s main.qml file.

main.qml:

import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.1
import QtQuick.LocalStorage 2.0

import "backend.js" as Backend

ApplicationWindow {
    id: rootWindow
    visible: true
    width: 420
    height: 680
    title: qsTr("Login Demo")
    flags: Qt.FramelessWindowHint

    property color backGroundColor : "#394454"
    property color mainAppColor: "#6fda9c" //"#FA6B65"
    property color mainTextCOlor: "#f0f0f0"
    property var dataBase

    FontLoader {
        id: fontAwesome
        name: "fontawesome"
        source: "qrc:/fontawesome-webfont.ttf"
    }

    // Main stackview
    StackView{
        id: stackView
        focus: true
        anchors.fill: parent
    }

    // After loading show initial Login Page
    Component.onCompleted: {
        stackView.push("qrc:/LogInPage.qml")   //initial page
        dataBase = userDataBase()
        console.log(dataBase.version)
    }

    //Popup to show messages or warnings on the bottom postion of the screen
    Popup {
        id: popup
        property alias popMessage: message.text
        background: Rectangle {
            implicitWidth: rootWindow.width
            implicitHeight: 60
            color: "#b44"
        }

        y: rootWindow.height
        modal: true
        focus: true
        closePolicy: Popup.CloseOnPressOutside

        Text {
            id: message
            anchors.centerIn: parent
            font.pointSize: 12
            color: "#ffffff"
        }
        onOpened: popupClose.start()
    }

    // Popup will be closed automatically in 2 seconds after its opened
    Timer {
        id: popupClose
        interval: 1000
        onTriggered: popup.close()
    }

    // Create and initialize the database
    function userDataBase()
    {
        var db = LocalStorage.openDatabaseSync("UserLoginApp", "1.0", "Login example!", 1000000);
        db.transaction(function(tx) {
            tx.executeSql('CREATE TABLE IF NOT EXISTS UserDetails(username TEXT, password TEXT, hint TEXT)');
        })
        return db;
    }

    // Register New user
    function registerNewUser(uname, pword, pword2, hint)
    {
        var ret  = Backend.validateRegisterCredentials(uname, pword, pword2, hint)
        var message = ""
        switch(ret)
        {
        case 0: message = "Valid details!"
            break;
        case 1: message = "Missing credentials!"
            break;
        case 2: message = "Password does not match!"
            break;
        }

        if(0 !== ret)
        {
            popup.popMessage = message
            popup.open()
            return
        }

        dataBase.transaction(function(tx) {
            var results = tx.executeSql('SELECT password FROM UserDetails WHERE username=?;', uname);
            if(results.rows.length !== 0)
            {
                popup.popMessage = "User already exist!"
                popup.open()
                return
            }
            tx.executeSql('INSERT INTO UserDetails VALUES(?, ?, ?)', [ uname, pword, hint ]);
            showUserInfo(uname) // goto user info page
        })
    }

    // Login users
    function loginUser(uname, pword)
    {
        var ret  = Backend.validateUserCredentials(uname, pword)
        var message = ""
        if(ret)
        {
            message = "Missing credentials!"
            popup.popMessage = message
            popup.open()
            return
        }

        dataBase.transaction(function(tx) {
            var results = tx.executeSql('SELECT password FROM UserDetails WHERE username=?;', uname);
            if(results.rows.length === 0)
            {
                message = "User not registered!"
                popup.popMessage = message
                popup.open()
            }
            else if(results.rows.item(0).password !== pword)
            {
                message = "Invalid credentials!"
                popup.popMessage = message
                popup.open()
            }
            else
            {
                console.log("Login Success!")
                showUserInfo(uname)
            }
        })
    }

    // Retrieve password using password hint
    function retrievePassword(uname, phint)
    {
        var ret  = Backend.validateUserCredentials(uname, phint)
        var message = ""
        var pword = ""
        if(ret)
        {
            message = "Missing credentials!"
            popup.popMessage = message
            popup.open()
            return ""
        }
        
        dataBase.transaction(function(tx) {
            var results = tx.executeSql('SELECT password FROM UserDetails WHERE username=? AND hint=?;', [uname, phint]);
            if(results.rows.length === 0)
            {
                message = "User not found!"
                popup.popMessage = message
                popup.open()
            }
            else
            {
                pword = results.rows.item(0).password
            }
        })
        return pword
    }

    // Show UserInfo page
    function showUserInfo(uname)
    {
        stackView.replace("qrc:/UserInfoPage.qml", {"userName": uname})
    }

    // Logout and show login page
    function logoutSession()
    {
        stackView.replace("qrc:/LogInPage.qml")
    }

    // Show Password reset page
    function forgotPassword()
    {
        stackView.replace("qrc:/PasswordResetPage.qml")
    }
}

 

Below you can see the screenshots from this application.

This slideshow requires JavaScript.

Source code:

https://github.com/arunpkqt/UserLoginApp

Custom Dial Item using QQuickPaintedItem

Sometimes it is required for us to create some of the advanced controls apart from the basics which are provided by the QML. Qt provides a class called QQuickPaintedItem which can be used to create a custom item for the QML using QPainter API in the Scene Graph.

We can see that nowadays Qt and QML used widely in the automotive industry for both infotainment and instrument cluster. Currently Qt is offering custom Dial item only in the commercial version of Qt which is part of Enterprise controls.

In the past I have worked on similar kind of project in which I have created custom dial item using QDeclarativeItem in Qt 4. here in this post I am going to recreate similar kind of Dial item with minimal functionality.

We have to follow few steps to create a custom item and to use it with QML.

  1. Create a new class by sub classing QQuickPaintedItem.
  2. Define all the properties which has to be exposed to the QML as part of this Item.
  3. Re-implement the  void paint(QPainter *painter) method and perform all the drawing for our custom dial item.
  4. Expose the new Item to the QML by using qmlRegisterType 
  5. import our new module in QML file. Use this new item like any other item in QML

The dial item which I have created only has the background dial, tick marks and numbers. It does not provide the needle. For needle I am using an image which I am placing over my Dial item in the QML and set the rotation property for it.

Here is my implementation for the custom dial item.

dialitem.h

#ifndef DIALITEM_H
#define DIALITEM_H

#include <QQuickPaintedItem>

class DialItem : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(int startAngle READ getStartAngle WRITE setStartAngle NOTIFY startAngleChanged)
    Q_PROPERTY(qreal spanAngle READ getSpanAngle WRITE setSpanAngle NOTIFY spanAngleChanged)
    Q_PROPERTY(int dialWidth READ getDialWidth WRITE setDialWidth NOTIFY dialWidthChanged)
    Q_PROPERTY(QColor dialColor READ getDialColor WRITE setDialColor NOTIFY dialColorChanged)

public:
    DialItem(QQuickItem *parent = 0);
    void paint(QPainter *painter);
    int getStartAngle() {return m_StartAngle;}
    qreal getSpanAngle() {return m_SpanAngle;}
    int getDialWidth() {return m_DialWidth;}
    QColor getDialColor() {return m_DialColor;}
    void setStartAngle(int angle) {m_StartAngle = angle; this->update();}
    void setSpanAngle(qreal angle) {m_SpanAngle = angle; this->update();}
    void setDialWidth(int angle) {m_DialWidth = angle; this->update();}
    void setDialColor(QColor color) {m_DialColor = color; this->update();}

signals:
    void startAngleChanged();
    void spanAngleChanged();
    void dialWidthChanged();
    void dialColorChanged();

private:
    int m_StartAngle;
    qreal m_SpanAngle;
    int m_DialWidth;
    QColor m_DialColor;
};
#endif // DIALITEM_H

dialitem.cpp

#include <QTime>
#include <QPainter>

#include "dialitem.h"

#define PI 3.14159265

DialItem::DialItem(QQuickItem *parent)
    : QQuickPaintedItem(parent)
{
    m_StartAngle = 0;
    m_SpanAngle = 180;
    m_DialWidth = 4;
    m_DialColor = QColor(0, 0, 0);
    connect(this, SIGNAL(startAngleChanged()), this, SLOT(update()));
    connect(this, SIGNAL(spanAngleChanged()), this, SLOT(update()));
    connect(this, SIGNAL(dialWidthChanged()), this, SLOT(update()));
    connect(this, SIGNAL(dialColorChanged()), this, SLOT(update()));
}

void DialItem::paint(QPainter *painter)
{
    QRectF rect = this->boundingRect();
    painter->setRenderHint(QPainter::Antialiasing);
    QPen pen = painter->pen();
    pen.setColor(Qt::black);

    //Draw outer dial
    painter->save();
    pen.setWidth(m_DialWidth);
    pen.setColor(m_DialColor);
    painter->setPen(pen);
    double startAngle = -90 - m_StartAngle;
    double spanAngle = 0 - m_SpanAngle;
    qreal offset = m_DialWidth / 2;
    painter->drawArc(rect.adjusted(offset, offset, -offset, -offset), startAngle * 16, spanAngle * 16);
    painter->restore();

    //Draw major ticks
    painter->save();
    pen.setWidth(4);
    painter->translate(width() / 2, height() / 2);
    painter->rotate(-startAngle);
    painter->setPen(pen);
    qreal angleStep = m_SpanAngle / 10;
    for(int i = 0; i < 11; ++i)
    {
        painter->drawLine(rect.width()/2 - 30, 0, rect.width()/2 - 2, 0);
        painter->rotate(angleStep);
    }
    painter->translate(-width() / 2, -height() / 2);
    painter->restore();

    //Draw minor ticks
    painter->save();
    pen.setWidth(2);
    painter->translate(width() / 2, height() / 2);
    painter->rotate(-startAngle);
    painter->setPen(pen);
    int minorTicks = 8* 10;
    qreal angleStepMin = m_SpanAngle / minorTicks;
    for(int i = 0; i < minorTicks; ++i)
    {
        painter->drawLine(rect.width()/2 - 15, 0, rect.width()/2 - 2, 0);
        painter->rotate(angleStepMin);
    }
    painter->translate(-width() / 2, -height() / 2);
    painter->restore();

    //Draw numbers
    painter->save();
    pen.setColor(Qt::white);
    painter->setPen(pen);
    const int radius = rect.width()/2 - 45;
    QFontMetrics fontMat(painter->font());
    QString numbr;
    for (double i = 0; i <= m_SpanAngle; i += m_SpanAngle/10)
    {
        QPoint p(rect.center().x() + radius * cos(-90-i * PI / 180.0), rect.center().y() - radius * sin(-90-i * PI / 180.0));        numbr = QString::number(i);
        QRect bound = fontMat.boundingRect(numbr);
        painter->drawText(QPointF((p.x() - bound.width()/2), (p.y() + bound.height()/2)), numbr);    
    }
    painter->restore();
}

And in the main.cpp we will expose the new item inside the QML.

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include "dialitem.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    //register new item to the QML
    qmlRegisterType<DialItem>("IVIControls", 1, 0, "DialItem");

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    return app.exec();
}

 

And here is my demo usage of my DialItem. Iam using a Slider item to modify the value of the Dial.

main.qml

import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Controls 1.2
import IVIControls 1.0

ApplicationWindow {
    visible: true
    width: 800
    height: 480

    Rectangle {
        anchors.fill: parent
        color: "#303030"
    }

    DialItem {
        id: speed
        objectName: "speed"
        width: 350
        height: width
        anchors.centerIn: parent
        startAngle: 30
        spanAngle: 300
        dialWidth: 6
        dialColor: "#4646FF"

        Image {
            id: needle
            source: "qrc:/images/needle.png"
            anchors.centerIn: parent
            rotation: 39 + 30 + slider.value
            Behavior on rotation { SpringAnimation { spring: 2; damping: 0.2 } }
        }
    }

    Slider {
        id: slider
        anchors.top: speed.bottom
        anchors.horizontalCenter: parent.horizontalCenter
        width: 400
        minimumValue: 0
        maximumValue: 300
    }
}

 

And finally here is my application running.

ividemoscreen

 

Source code:

https://github.com/arunpkqt/IVIDialDemo

 

Custom FileSystemModel display

The QFileSystemModel class provides a fully featured data model for the local file system provides access to the local file system, providing functions for renaming and removing files and directories, and for creating new directories.

A simple usage of this class is given below[ref: http://doc.qt.io/qt-5/qfilesystemmodel.html#details].

QFileSystemModel *model = new QFileSystemModel;
model->setRootPath(QDir::currentPath());
QTreeView *tree = new QTreeView(splitter);
tree->setModel(model);
tree->setRootIndex(model->index(QDir::currentPath()));

But in the tree view we could see only the contents of the given directory and not the given directory as a parent. So I have decided to write my own custom model which will be populated and show the parent directory and all of its children and its children and so on..

I have derived new class SandBoxItemModel from QStandardItemModel which takes the list of folder path to be shown in the view. Implementation details are given below.

Unlike actual QFileSystemModel here we don not have direct access to the underlying filesystem. It may also required for us to access the actual path of the directory or file when user clicks on it. To achieve this here I have used method “QStandardItem::setAccessibleDescription(const QString)” to set the actual path of the file/directory. And the same will be accessed and displayed on the status bar.

sandboxitemmodel.h

#ifndef SANDBOXITEMMODEL_H
#define SANDBOXITEMMODEL_H
#include <QStandardItemModel>
#include <QStandardItem>
class SandBoxItemModel : public QStandardItemModel
{
public:
    SandBoxItemModel(QObject* parent = 0);
    ~SandBoxItemModel();
    void setSandBoxDetails(QString names);
    void populateSandBoxes(const QStringList &names);
    void createDirectoryItem(QString dirName, QStandardItem *parentItem = NULL);

private:
    QStandardItem *rootItem;
    QIcon dirIcon;
    QIcon fileIcon;
};
#endif // SANDBOXITEMMODEL_H

sandboxitemmodel.cpp

#include "sandboxitemmodel.h"
#include <QApplication>
#include <QStyle>
#include <QDir>
#include <QDebug>
#include <QDirIterator>
SandBoxItemModel::SandBoxItemModel(QObject *parent)
    :QStandardItemModel(parent)
{
    rootItem = this->invisibleRootItem();
    dirIcon = QApplication::style()->standardIcon(QStyle::SP_DirIcon);      //icon for directories
    fileIcon = QApplication::style()->standardIcon(QStyle::SP_FileIcon);    //icon for files
}
SandBoxItemModel::~SandBoxItemModel()
{
}
/*
 Sandbox locations are parsed from the file which are separated by new lines.
*/
void SandBoxItemModel::setSandBoxDetails(QString names)
{
    populateSandBoxes(names.split("\n"));
}

/*
 method to populate the contents of the sandboxes parsed from the file.
*/

void SandBoxItemModel::populateSandBoxes(const QStringList &names)
{
    QString name;
    QStandardItem* parent;
    foreach (name, names) {
        if(!name.isEmpty())
        {
            name.remove("\r");
            parent = new QStandardItem(dirIcon ,name);  //create the parent directory item
            parent->setAccessibleDescription(name);     //set actual path to item
            rootItem->appendRow(parent);                //add the parent item to root item
            createDirectoryItem(name, parent);          //Iterate and populate the contents
        }
    }
}
/*
  Method to populate the contents of the given directory in recursive manner.
  Each found child will be appended to its parent item.
  Files & folders will be using its own standard icons  from current style.
  child->setAccessibleDescription() is used to set the actual path of the item
  which will be useful.
*/
void SandBoxItemModel::createDirectoryItem(QString dirName, QStandardItem *parentItem)
{
    QDir dir(dirName);
    QFileInfoList subFolders;
    QFileInfo folderName;
    QStandardItem* child;
    subFolders = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);    //get all the sub folders
    foreach (folderName, subFolders)
    {
        if(folderName.isFile())
        {
            child = new QStandardItem(fileIcon, folderName.fileName());                 //Append a file
            child->setAccessibleDescription(folderName.filePath());                     //set actual path to item
        }
        else
        {
            child = new QStandardItem(dirIcon, folderName.fileName());                  //Append a folder
            child->setAccessibleDescription(folderName.filePath());                     //set actual path to item
       }
        parentItem->appendRow(child);
        createDirectoryItem(folderName.filePath(), child);                              //Recurse its subdirectories
    }
}

Now we will create a main window class which has  a TreeView widget to show our model and data.

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTreeView>
#include <QStatusBar>
#include "sandboxitemmodel.h"
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();
    void initUI();
private:
    QTreeView* treeView;
    SandBoxItemModel* sandBoxModel;
    QStatusBar* statusBar;
private slots:
    void onSelected(QModelIndex index);
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include <QHeaderView>
#include <QFile>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    resize(800, 600);
    initUI();
}
MainWindow::~MainWindow()
{
}
void MainWindow::initUI()
{
    statusBar = new QStatusBar(this);
    this->setStatusBar(statusBar);
    sandBoxModel = new SandBoxItemModel();
    QFile file(":/resources/sandbox.txt");              //sandbox locations are read from the text file
    file.open(QIODevice::ReadOnly);
    sandBoxModel->setSandBoxDetails(file.readAll());
    treeView = new QTreeView();
    treeView->setModel(sandBoxModel);
    treeView->header()->hide();
    connect(treeView, SIGNAL(clicked(QModelIndex)), this, SLOT(onSelected(QModelIndex)));
    this->setCentralWidget(treeView);
}
/*
 This slot will get the accessible dewcription from the current selected item and show it in the status bar.
*/
void MainWindow::onSelected(QModelIndex index)
{
    QStandardItem *item = sandBoxModel->itemFromIndex(index);
    QString data = item->accessibleDescription();
    statusBar->showMessage(data);
}

As you can see in the code above in MainWidnow  class we have a QTreeView to show our folder structure and a QStatusBar to show the current selected item in the tree view when user clicks on it.

here goes the main.cpp

#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

Below is my sample “sandbox.txt” file.

D:\MySandBox
D:\MySandBox2

And here is the program running.

customtreemodel

Source code:

Git:/CustomFileSystemModel

QML List view Sort and Filter

Adding support for sorting and filtering on a list view will always be useful if the list view is much bigger and/or complex. I have created a simple demo QML based application which shows a basic list view with text item on which user can perform the filtering and sorting of items.

I have created CListModel class which is derived from QAbstractListModel which will hold the model data which is to be shown on the list view. I have also used a custom “NameRole” for the data.

Following is my CListModel class implementation.

#include <QAbstractListModel>

enum Roles {
 NameRole = Qt::UserRole + 1,
 };

//List Model
 class CListModel : public QAbstractListModel
 {
 public:
 CListModel();
 ~CListModel();

void addData(const QString &unit);

int rowCount(const QModelIndex & parent = QModelIndex()) const;

QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;

protected:
 QHash<int, QByteArray> roleNames() const;

private:
 QStringList m_names;
 };

 

#include "listmodel.h"

CListModel::CListModel()
{

}

CListModel::~CListModel()
{

}

void CListModel::addData(const QString &unit)
{
 beginInsertRows(QModelIndex(), rowCount(), rowCount());
 m_names.append(unit);
 endInsertRows();
}

int CListModel::rowCount(const QModelIndex &parent) const
{
 Q_UNUSED(parent);
 return m_names.count();
}

QVariant CListModel::data(const QModelIndex &index, int role) const
{
 if (index.row() < 0 || index.row() >= m_names.count())
 return QVariant();

const QString &name = m_names[index.row()];
 if (role == NameRole)
 return name;
 return QVariant();
}

QHash<int, QByteArray> CListModel::roleNames() const
{
 QHash<int, QByteArray> roles;
 roles[NameRole] = "name";
 return roles;
}

 

Next we need a Filter model which will perform the sort and filter functionality on our custom list view. I have created FilterProxyModel class by deriving from QSortFilterProxyModel. And additionally I have added two methods for setting the filter string and sort criteria from the QML with macro Q_INVOKABLE.

Following is my FilterProxyModel class implementation.

class FilterProxyModel : public QSortFilterProxyModel
{
 Q_OBJECT
public:

FilterProxyModel(QObject* parent = 0);

~FilterProxyModel();

Q_INVOKABLE void setFilterString(QString string);

Q_INVOKABLE void setSortOrder(bool checked);
};

 

FilterProxyModel::FilterProxyModel(QObject *parent)
 : QSortFilterProxyModel(parent)
{
 setSortOrder(false);
}

FilterProxyModel::~FilterProxyModel()
{

}

void FilterProxyModel::setFilterString(QString string)
{
 this->setFilterCaseSensitivity(Qt::CaseInsensitive);
 this->setFilterFixedString(string);
}

void FilterProxyModel::setSortOrder(bool checked)
{
 if(checked)
 {
 this->sort(0, Qt::DescendingOrder);
 }
 else
 {
 this->sort(0, Qt::AscendingOrder);
 }
}

 

Below is my QML implementation.

import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.0
import QtQuick.Controls.Styles 1.2

ApplicationWindow {
 id: appWindow
 visible: true
 width: 320
 height: 480
 color: "#6bdce4"
 property int margin: 12
 flags: Qt.FramelessWindowHint

ColumnLayout {
 id: mainLayout
 anchors.fill: parent
 anchors.margins: margin

Rectangle{
 color: "#6bdce4"
 Layout.fillWidth: true
 height: 40
 z: 2
 RowLayout {
 id: rowLayout
 anchors.fill: parent
 anchors.centerIn: parent
 TextField {
 placeholderText: "Type here.."
 Layout.fillWidth: true
 font.pointSize: 12
 style: TextFieldStyle {
 textColor: "black"
 background: Rectangle {
 radius: 5
 implicitWidth: 100
 implicitHeight: 30
 border.color: "#6bdce4"
 border.width: 1
 }
 }
 onTextChanged: {
 filterModel.setFilterString(text);
 }
 }
 CheckBox {
 text: "Descending"
 onCheckedChanged:{
 filterModel.setSortOrder(checked)
 }
 style: CheckBoxStyle {
 label: Text {
 color: "white"
 text: "Descending"
 font.pointSize: 12
 }
 indicator: Rectangle {
 implicitWidth: 20
 implicitHeight: 20
 radius: 3
 border.color: control.activeFocus ? "darkblue" : "gray"
 border.width: 1
 Rectangle {
 visible: control.checked
 color: "#555"
 border.color: "#333"
 radius: 1
 anchors.margins: 4
 anchors.fill: parent
 }
 }
 }
 }
 }
 }

ListView {
 id: view
 model: filterModel
 Layout.minimumHeight: 25
 Layout.fillHeight: true
 Layout.fillWidth: true
 cacheBuffer: 100
 spacing: 10

delegate: Rectangle{
 width: parent.width
 radius: 5
 anchors.horizontalCenter: parent.horizontalCenter
 height: 40
 color: Qt.lighter("#6bdce4", 0.8)
 Text {
 id: nameTxt
 text: name
 font.pointSize: 12
 color: "#FFFFFF"
 anchors.left: parent.left
 anchors.leftMargin: 20
 anchors.verticalCenter: parent.verticalCenter
 }
 }
 }
 }
}

 

Now first we need to create the instance of CListModel and populate the data which we want to show in the list. Then create an instance of filter model FilterProxyModel class and set the source as our actual model. Also set the role which we want to use for sorting and filtering. Now make the instance of FilterProxyModel available inside the QML file using method,

QQmlContext::setContextProperty("filterModel", &filterModel);

Below is my implementation of main.cpp file.

int main(int argc, char *argv[])
{
 QGuiApplication app(argc, argv);

 //Create and populate list model instance
 CListModel listModel;
 listModel.addData("Isaac Newton");
 listModel.addData("Alexander Graham Bell");
 listModel.addData("Leonhard Euler");
 listModel.addData("Nikola Tesla");
 listModel.addData("Charles-Augustin de Coulomb");
 listModel.addData("Andre-Marie Ampere");
 listModel.addData("Michael Faraday");
 listModel.addData("Blaise Pascal");
 listModel.addData("Anders Celsius");
 listModel.addData("James Watt");

 //Create filter model
 FilterProxyModel filterModel;
 filterModel.setSourceModel(&listModel);
 filterModel.setFilterRole(NameRole);
 filterModel.setSortRole(NameRole);

 QQmlApplicationEngine engine;
 QQmlContext* context = engine.rootContext();
 context->setContextProperty("filterModel", &filterModel);
 engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

 return app.exec();
}

Below are the screenshot from my demo application.

sortfilterdemo

Source code:

Git:/QMLSortFilterList

QSysInfo

QSysInfo class provides all information about the underlying system in which we run our application. This class provide information about the following features of the system.

  • Byte Order – platform is big-endian or little-endian
  • Windows Version – version of the Windows operating system
  • Macintosh Version – version of the Macintosh operating system
  • CPU Architecture – architecture of the CPU

Some of these constants will be available for specific operating system so we can use pre-processing symbols like Q_OS_WIN or Q_OS_OSX to check that the application is compiled under Windows or OS X.

Following is the sample code snippet which shows how to use this class to get the information about the system.

QSysInfo systemInfo;
qDebug()<< "Windows Version: " << systemInfo.windowsVersion();
qDebug()<< "Build Cpu Architecture: " << systemInfo.buildCpuArchitecture();
qDebug()<< "Current Cpu Architecture: " << systemInfo.currentCpuArchitecture();
qDebug()<< "Kernel Type: " << systemInfo.kernelType();
qDebug()<< "Kernel Version: " << systemInfo.kernelVersion();
qDebug()<< "Machine Host Name: " << systemInfo.machineHostName();
qDebug()<< "Product Type: " << systemInfo.productType();
qDebug()<< "Product Version: " << systemInfo.productVersion();
qDebug()<< "Byte Order: " << systemInfo.buildAbi();
qDebug()<< "Pretty ProductName: " << systemInfo.prettyProductName();
Output:
    Windows Version:  192
    Build Cpu Architecture:  "i386"
    Current Cpu Architecture:  "x86_64"
    Kernel Type:  "winnt"
    Kernel Version:  "10.0.10586" 
    Machine Host Name:  "My Computer"
    Product Type:  "windows"
    Product Version:  "10"
    Byte Order:  "i386-little_endian-ilp32"
    Pretty ProductName:  "Windows 10"

For additional information refer Qt docs QSysInfo.

Happy coding!