Plasmoid in pure QML and JavaScript

image

There was not a single post on the hub about creating a plasmoid in pure QML using JavaScript. This post is intended to correct this shortcoming.

A bit of theory and history


A plasmoid is a widget for the Plasma shell that appeared in KDE 4. At the stage of the appearance of the first versions of KDE 4, all plasmoids were written exclusively in C ++, then python support appeared. In C ++, this was, say, not very easy (due to the complexity, knowledge of all the basics of the language, etc.), in python it was already much easier. Since KDE is written in C ++ using Qt, it would be preferable to use this very Qt (at least in KDE they think so). When QML appeared, KDE immediately made full support for creating plasmoids in QML + JS + C ++, and now they insist on creating plasmoids exclusively in QML.

On a habr there were already posts about creation of plasmoids in Python and C ++, now the turn has come for QML. But this series of posts (I expect to write more than one post) is not just about creating a plasmoid using a bunch of QML + JS + C ++, but also about all the pitfalls that a novice widget developer may encounter. The fact is that there is still too little information about QML plasmoids on techbase.kde.org , because of this, I constantly had discussions with the developers on # kde and # plasma @ irc.freenode.net . By the way, they all help absolutely always and in all cases, without exception, for which they are a big and fat plus in karma.

Tools


Generally speaking, you can use any text editor to write a plasmoid, but relatively recently plasmate was born . Plasmate is part of the Plasma SDK , it’s a mini-IDE (mini, because there are still a lot of bugs, a lot of unrealized features, and in general it’s still an alpha version, however, it is already quite suitable for writing plasmoids) to create everything that is connected with Plasma : plasmoids, window switches (alt-tab), kwin effects, plasma themes, etc.

Using Ubuntu as an example, everything is very simple:

sudo apt-get install kde-sdk

or even simpler:

sudo apt-get install plasmate


But for proper installation, you need to install kubuntu-ppa backports , you can read about it here .

After installation, run plasmate, click Plasma Widget . We call the future plasmoid - set the value Addon name , click on the Create button and get into the editor window. By default, the " Preview " mode is enabled , it is especially convenient if you have two monitors - on one you can place a window with a widget, on the other you can leave the code editor. So then the most interesting.

image



Plasma QML Components


At the moment, there are already a lot of plasma components to describe any visual interface, and not only visual, but also interfaces for working with data (for example, DataSource ).

All components can be found at http://api.kde.org , namely here . PlasmaExtraComponents and PlasmaComponents are usually used to build the visual interface , but most often the last one is enough. A detailed description can be found there.

When creating a plasmoid in plasmate, the future widget's workpiece is ready for installation on the desktop. I want to draw your attention to the workpiece right away - the necessary imports are already connected in it and even i18n is used, which is very useful for localization. By the way, at the moment QtQuick 1.1 is indicated in the blank, although you may already have Qt5 with QtQuick 2.0 in your system . If you try to connect it - plasmate will swear because plasma in the latest KDE (and before version 5.0) does not have QtQuick 2 support.

So, we already see the simple “Hello world” in the blank. But in order to do something more complicated, you need to realize something, namely: any information about the system, computer, and everything else in the plasmoid is not available, because he simply does not have access to all of the above. And since QML is the technology most often tied to C ++, it is the same with plasmoids - all data is provided by the so-called DataSource and DataEngine .

DataSource is a bit of a data provider in QML from C ++. For example, we can’t find out the current time in QML, but using C ++ and the corresponding header files is quite. In this case, we need to write a DataEngine that will supply this information (time) to all plasmoids that use it as a source ( DataSource ), by the way, the plasmoids in this case can be written in any language.

DataEngine is the same C ++ code that sends data to QML. Of course, it needs to be written according to the special rules of KDE, but in this post I will not delve into this, maybe in the following, if anyone is interested. And by the way, DateEngine can also be written not only in C ++.

Next, I will explain with the example of my plasmoid -KFilePlaces (https://bugs.kde.org/show_bug.cgi?id=180139). In fact, he is not ready yet, but I will lay out the alpha version to see what plasmoids are made of and generally feel this dough.

The creation of the plasoid "Places"


Overview of Existing DataEngine

To create a plasmoid showing the Entry Points, we first need to understand how everything works. Here you need to know two things: if this functionality already exists - most likely in the sources (in this case, in the sources of the Dolphin file manager) you can find out how the data we need is taken. If it is not there, then you can search for the corresponding DataEngine in the system and figure it out already (believe me, it is very simple). To do this, you can call Plasma Engine Explorer :

plasmaengineexplorer


A small window will come out, in which it will be possible, as the name implies, to see all installed in the DataEngine system. In my case, the required DataEngine is “places”. Immediately after we select it from the list, a tree-like list of objects that this “engine” delivers outward will appear. It looks like this for me: Here from 0 to 11 are data sources, because DataSources. This array is all entry points. To use this engine in QML you need to write something like this:




PlasmaCore.DataSource {
        id: placesDataSource
        engine: "places"
        interval: 1000   
        connectedSources: sources
    }


Here sources are all these DataSource from 0 to 11, i.e. an array. One could specify explicitly:

PlasmaCore.DataSource {
        id: placesDataSource
        engine: "places"
        interval: 1000   
        connectedSources: ["0", "2", "11"]
    }


But in my case, I need to process all entry points, so we leave “sources”.

After creating a data source, you cannot use it immediately; you must create a model. To do this, there is a " DataModel " object that simply takes data from a DataSource and from which data can already be output. Creating a DataModel is also very simple:

PlasmaCore.DataModel {
        id: placesDataModel
        dataSource: placesDataSource
    }    


Sorting and filtering data source elements

So, we got the data. But, as you can see, they do not go in order, and it is quite possible that we need to sort them by some attribute (variable). In my case, I need to show all not hidden elements ( hidden : false parameter ), and sort by isDevice parameter (device or place). To do this, use the PlasmaCore.SortFilterModel tools :

PlasmaCore.SortFilterModel {
        id: sortedEntriesDataModel
        filterRole: "isDevice"
        filterRegExp: "false"
        sourceModel: PlasmaCore.SortFilterModel {
            sourceModel: placesDataModel
            filterRole: "hidden"
            filterRegExp: "false"
            sortRole: "isDevice"
            sortOrder: "AscendingOrder"
       }
    }


Here, sortRole is the variable by which sorting will be performed, sortOrder is the order; filterRole is the variable by which the filtering will be performed (i.e., all inappropriate elements will be cut off), and filterRegExp is the expression that will be checked by checking it. There is one catch: if you want to filter by some other reason besides “isDevice”, you will have to wrap this SortFilterModel element as it did in this example with additional filtering by the “hidden” variable.

PlasmaCore.SortFilterModel is an extension of PlasmaCore.DataModel, so they are compatible.

Create a visual list

To create a list, I first recommend using the ScrollArea component from PlasmaExtras , it is needed for almost everything where the dimensions of the child are unknown. It is suitable for reasons such as:
  1. Flickable is a container. Content can be scrolled not only by the scrollbar on the side, which can be turned off, but also by mouse or touch-screen gestures.
  2. It is strongly recommended that you use instead of creating your own bike wrappers from other Plasma runtime components , such as creating an Item / Page , etc. with PlasmaComponents.ScrollBar .


We create:

PlasmaExtras.ScrollArea {
        id: entriesScrollArea
        anchors.top:                    parent.top
        anchors.left:                   parent.left
        anchors.right:                  parent.right
        anchors.horizontalCenter:       plasmoidItem.horizontalCenter
        height: 40 * (entriesListView.count + 1)
        flickableItem: ListView {


Here I deliberately stopped at the ListView to clarify: the flickableItem element is a container for any element that can be scrolled / scrolled if it does not fit in the width and / or height of the parent (our ScrollArea) element. This is useful for lists, since they are conveniently scrollable and can expand.

In order not to clutter up the screen, I specifically made the creation of the list in the spoiler:
List creation
PlasmaExtras.ScrollArea {
        id: entriesScrollArea
        anchors.top:                    parent.top
        anchors.left:                   parent.left
        anchors.right:                  parent.right
        anchors.horizontalCenter:       plasmoidItem.horizontalCenter
        height: 40 * (entriesListView.count + 1)
        flickableItem: ListView {
            id: entriesListView
            highlightRangeMode: ListView.NoHighlightRange
            orientation:        ListView.Vertical
            focus:              true
            clip:               true
            model: sortedEntriesDataModel
            delegate: listViewItemTemplate
            header: PlasmaComponents.Switch {
                id: entriesHeaderSwitch
                anchors.top:   parent.top
                anchors.left:   parent.left
                anchors.right:  devicesHeaderLabel.left
                text: i18n("Places")
                checked: true                
            }  
            highlight: Rectangle {
                id: highlightListViewItem       
                anchors.left: parent.left 
                color: "lightgrey"; 
                radius: 6; 
                opacity: 0.6
            }
        }
    }



I don’t see much point here, because list is a regular ListView element of QML, starting with QtQuick 1.0 .
Move on.

Creating a delegate for list items

It would seem, why should I pay attention to such a simple step, but I spent a lot of time with him. The fact is that the container element for the delagate must be PlasmaComponents.ListItem . If you simply enclose the items in an Item or just a container (not a ListItem), all the items will be mixed into a heap. Alas, nowhere has been written about this nuance.

We try!

If you did everything right, we will have something like this:
The code
import QtQuick 1.1
import org.kde.locale 0.1
import org.kde.plasma.components 0.1 as PlasmaComponents
import org.kde.plasma.core 0.1 as PlasmaCore
import org.kde.plasma.extras 0.1 as PlasmaExtras
Item {
    width: 200
    height: 300
    PlasmaCore.DataSource {
        id: placesDataSource
        engine: "places"
        interval: 1000   
        connectedSources: sources
    }
    PlasmaCore.DataModel {
        id: placesDataModel
        dataSource: placesDataSource
    }    
    PlasmaCore.SortFilterModel {
        id: sortedEntriesDataModel
        filterRole: "isDevice"
        filterRegExp: "false"
        sourceModel: PlasmaCore.SortFilterModel {
            sourceModel: placesDataModel
            filterRole: "hidden"
            filterRegExp: "false"
            sortRole: "isDevice"
            sortOrder: "AscendingOrder"
       }
    }
    Component {     
        id: listViewItemTemplate
        PlasmaComponents.ListItem {
            id: placeListItem
            anchors.left: plasmoidItem.left    
            anchors.right: plasmoidItem.right
            x: 20
            height: 40
            Item {
                id: listItemObject
                anchors.left: plasmoidItem.left    
                anchors.right: plasmoidItem.right
                PlasmaCore.IconItem {
                    id: placeIconItem
                    anchors.left: parent.left 
                    anchors.leftMargin: 5 
                    source: icon
                }
                PlasmaComponents.Label {    
                    id: placeNameLabel
                    anchors.left: placeIconItem.right 
                    anchors.leftMargin: 10
                    text: name
                    font.pointSize: 12                
                }
                PlasmaComponents.ProgressBar {
                    id: placeFreeSizeProgressBar
                    anchors.top:        placeNameLabel.bottom
                    anchors.left:       placeNameLabel.left
                    width:              placeNameLabel.width
                    height:             10
                    value:              kBUsed / kBSize
                    opacity:            0
                }
            }
        }
    }
    PlasmaExtras.ScrollArea {
        id: entriesScrollArea
        anchors.top:                    parent.top
        anchors.left:                   parent.left
        anchors.right:                  parent.right
        anchors.horizontalCenter:       plasmoidItem.horizontalCenter
        height: 40 * (entriesListView.count + 1)
        flickableItem: ListView {
            id: entriesListView
            highlightRangeMode: ListView.NoHighlightRange
            orientation:        ListView.Vertical
            focus:              true
            clip:               true
            model: sortedEntriesDataModel
            delegate: listViewItemTemplate
            header: PlasmaComponents.Switch {
                id: entriesHeaderSwitch
                anchors.top:   parent.top
                anchors.left:   parent.left
                anchors.right:  devicesHeaderLabel.left
                text: i18n("Places")
                checked: true
            }  
            highlight: Rectangle {
                id: highlightListViewItem       
                anchors.left: parent.left 
                color: "lightgrey"; 
                radius: 6; 
                opacity: 0.6
            }
        }
    }    
}

Or externally like this (in my case): The code can be copied and pasted into plasmate . Unfortunately, the post was too extensive, so to be continued!







Help
# plasma , # kde @ irc.freenode.net


PS I can’t understand the bug with the attribute “align: 'center'” in - after it the text is also centered and does not go astray after \
, as a result of which my post looks crooked.

Also popular now: