Developing My First Plasmoid: The QML Code

Back in February I posted the code to the data engine I developed for my plastmoid.  At the time I’d wanted to clean up my plasmoid before posting it on here, however, I’ve become stuck on a key feature so I was hoping that maybe by posting the code I could get some help.  (As well as provide an example to others)

First of all, plasmoids need a certain structure to the folders. Here’s what mine looks like:

.
├── contents
│   └── ui
│   ├── Button.qml
│   ├── Button.qml~
│   ├── content
│   │   ├── ItemDelegate.qml
│   │   └── ItemDelegate.qml~
│   ├── flickrhelpers
│   │   ├── addtogroup.py
│   │   ├── addtogroup.py~
│   │   ├── removefromgroup.py
│   │   └── removefromgroup.py~
│   ├── flickrviews.py~
│   ├── flickrviews.qml
│   ├── flickrviews.qml~
│   └── test.qml~
├── metadata.desktop
└── metadata.desktop~

As you’ll see, I need to have a lot more in the ui folder, but I haven’t taken that optimization step yet. First off is the metadata.desktop file. A lot of this is self-explanatory, but one important part is the X-Plasma-RequiredExtensions which allows me to launch the flickrhelpers files.


[Desktop Entry]
Name=flickr views
Comment=A widget for flickr views
Icon=chronometer

X-Plasma-API=declarativeappletscript
X-Plasma-MainScript=ui/flickrviews.qml
X-Plasma-DefaultSize=750,700
X-Plasma-RequiredExtensions=LaunchApp

X-KDE-PluginInfo-Author=Eric Mesa
X-KDE-PluginInfo-Email=
X-KDE-PluginInfo-Website=http:/server.ericsbinaryworld.com/
X-KDE-PluginInfo-Category=Internet
X-KDE-PluginInfo-Name=org.kde.flickrviews
X-KDE-PluginInfo-Version=0.2

X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-EnabledByDefault=true
X-KDE-ServiceTypes=Plasma/Applet
Type=Service

Moving on, we go to Button.qml which allows us to definte a button once and then use it throughout the main file without cluttering things up too much.

import QtQuick 1.0

Rectangle{

id: button
width: 50; height: 43

Text{
id: buttonLabel
anchors.centerIn: parent
text: bLabel
}

property color buttonColor: "lightblue"
property color onHoverColor: "gold"
property color borderColor: "white"
property string bLabel: "button label"

signal buttonClick()
onButtonClick:{
console.log(buttonLabel.text + " clicked")
}

MouseArea{
id: buttonMouseArea
anchors.fill: parent
onClicked: buttonClick()
hoverEnabled: true
onEntered: parent.border.color = onHoverColor
onExited: parent.border.color = borderColor
}

//detmermines color of button by using the conditional operator
color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor

Next up is the ItemDelegate code. This code takes the XML from the data engine and parses it out to make the list on the left-hand side of all the photos.

import QtQuick 1.0

Item {
id: delegate
height: column.height + 40
width: delegate.ListView.view.width

Column {
id: column
x: 20; y: 20
width: parent.width - 40

Text {
id: titleText
text: title; width: parent.width; wrapMode: Text.WordWrap
font { bold: true; family: "Helvetica"; pointSize: 14 }
}

Text {
id: viewsText
text: "Views: " + views; width: parent.width; wrapMode: Text.WordWrap
font { bold: false; family: "Helvetica"; pointSize: 12 }
}
}

MouseArea {
anchors.fill: delegate
onClicked: {
delegate.ListView.view.currentIndex = index
window.photoURL = url
window.jpegURL = jpeg
window.photoID = photo
}
}

}

So, with the helper files out of the way, let’s take a look at the main code. First come the imports:

import Qt 4.7
import "content"
import QtWebKit 1.0
import org.kde.plasma.core 0.1 as PlasmaCore

Then comes the code that starts my plasmoid. All of the plasmoid is within a Rectangle. Next up is how I access my engine. This pulls all the data from the engine into my plasmoid.

Rectangle {
id: window
width: window.width
height: window.height

PlasmaCore.DataSource {
id: viewsSource
engine: "flickrviewsengine2"
interval: 86400000
Component.onCompleted: connectedSources = sources
onSourceAdded: connectSource(source)
}

Then I setup the variables I use throughout the plasmoid.

property string currentGroup: "" //this is used to hold the current views group to display
property string photoURL: "" //this is used to hold the flickr webpage of the current image
property string jpegURL: "" //this is to hold the photo that appaers in the webview
property string photoID: "" //this is to hold the photo ID that appaers in the webview
property string currentphotogroup: "" //this is to hold the photo's current group that appaers in the webview
property string nextphotogroup: "" //this is to hold the group it should go into
property bool loading: feedModel.status == XmlListModel.Loading

Then comes the XML liste model. This is what pulls all the data out of the XML. Isn’t it so nice and easy to read? It’s easier than any other XML parser I’ve used in other programming languages.

XmlListModel{
id:feedModel
xml: window.currentGroup
query: "/photos/photo"
XmlRole { name: "title"; query: "title/string()"}
XmlRole { name: "views"; query: "views/string()"}
XmlRole { name: "url"; query: "URL/string()"}
XmlRole { name: "jpeg"; query: "JPEG/string()"}
XmlRole { name: "photo"; query: "id/string()"}
}

Now we move back into visual stuff rather than program logic. We use the buttons I defined before and use them to populate some of the variables I declared above. There are a lot of buttons (one for each group) so I will just include the first two in this post:

Row {
width: window.width
height: 43
spacing: 2
//spacing: parent.width/14
Button{
id: v25
bLabel: "25"
onButtonClick:{ currentGroup = viewsSource.data["25"]["Group 25"]; window.currentphotogroup = "25"; window.nextphotogroup="50"}
}
Button{
id: v50
bLabel: "50"
onButtonClick:{ currentGroup = viewsSource.data["50"]["Group 50"]; window.currentphotogroup = "50"; window.nextphotogroup="75"}
}

Now we get to the column along the left-hand side that uses the ItemDelegate I shared above:

Row {
x: 0
y: 43
width: window.width
height: window.height
Rectangle {
width: window.width/3+10; height: window.height
color: "#efefef"

//This uses the ItemDelegate in the "contents" folder to create the list of images

ListView {
id: list
width: window.width/3; height: window.height
model: feedModel
delegate: ItemDelegate {}

highlight: Rectangle { color: "lightgray" }
highlightMoveSpeed: 9999999
}

}

This is where I define the box that holds the preview image.

Rectangle {
width: window.width/3
height: window.height
Row{
spacing: 0
WebView {
id: webView
width: 250
html: ""
//url: photoURL
}

Finally, the action buttons – the part that makes my plasmoid more useful than the commandline program I’d written. This is the part where I really need help from readers as I will explain after this section.

Column{

Button{
id: launchwebpage
width: 200
bLabel: "View on Flickr"
onButtonClick: plasmoid.openUrl(photoURL) //plasmoid.runCommand("firefox", ["http://www.google.com"]) //plasmoid.openUrl([photoURL]) //requires: X-Plasma-RequiredExtensions=LaunchApp
}
Button{
id: launchdiscuss
width: 200
bLabel: "Discuss"
onButtonClick: plasmoid.openUrl("http://www.flickr.com/groups/views"+window.currentphotogroup+"/discuss/")
}
Button{
id: addtogroup
width: 200
bLabel: "Add to Next Group"
onButtonClick: plasmoid.runCommand("/home/ermesa/bin/qml/plasmoids/flickrviews2/contents/ui/flickrhelpers/addtogroup.py", [photoID,nextphotogroup])
}
Button{
id: removefromgroup
width: 200
bLabel: "Remove from Current Group"
onButtonClick: plasmoid.runCommand("/home/ermesa/bin/qml/plasmoids/flickrviews2/contents/ui/flickrhelpers/removefromgroup.py", [photoID,currentphotogroup])
}
}

}
}
}
}

As you can see, in “Add to Next Group” and “Remove from Current Group” I am launching a python program that either adds or removes the photo from the group. However, sometimes this command fails. The biggest reason is that only 5 photos can be added to a group at a time. And I may have added some photos earlier in the day or via a cron job that I have that adds in the photos to the groups if they’ve never been in any groups. When it fails it raises an exception. I’d like to know how I can communicate that back to my plasmoid. This part is the only part where I haven’t been able to get help from the mailing lists or IRC. Without it, I have to launch the plasmoid from the cli and constantly check to see if there was a failure or success. If any readers can help me out with this, I would be so, so happy. I’d finally be done with functionality and be able to work on cleaning up the code and beautifying the plasmoid.

Author: Eric Mesa

To find out a little more about me, see About Me