Ski Resorts Webcams
Ski Resorts Webcams is a simple Qt Quick application made for viewing ski resorts webcams images. This article explains how the Ski Resorts Webcams app was created and the main aspects of its design.
Article Metadata
Tested with
Compatibility
Article
Contents |
Introduction
Ski Resorts Webcams is a simple Qt Quick application made for viewing ski resorts webcams images.The main goal when creating this app was to create a "internet image viewer" with a data file independent of the Qt App. The application is made of various parts who provide functions to the user.
When browsing on different lists of examples I saw a very interesting "Windows Phone 7" style app.I tried to reproduce the same and adapted it to reach the result I wanted. The app if fully made of QML and Javascript. Each part of this Wiki shows an overview of the process I followed in order to create a part of the app. Please watch the video (inside the video part) in order to have a real overview of how I used Qt Quick components to make the app !
A word about design
When I first saw Symbian/Nokia Belle, I was really seduced by the design. Coming from an old N80, I owned several devices and very often the design of each application was very different. When Qt Quick was first released, I was instantly interested by this new possibility. Making a bet with a friend, I told him it was possible to create a "WP7" like application under Qt, make it smooth and fast for Symbian devices. I ended up by making the simplest application possible, without many sub-menus and complex navigation. The goal was to make a fast, simple and nice looking application, in the respect of Nokia Belle UI style. You can see the result the video.
- Design
The core
The core of the app is made of one VisualItemModel who holds 3 rectangles and some intel to switch between them. Each of the 3 rectangles are composed of small rectangles which contains all the items such as the text and so on. There're lists made of a VisualItemModel with ListModel as model combined with a ListView in order to show the items. Here is the entire code of the first "Rectangle" (the countries menu):
VisualItemModel {
id: itemModel
// Countries list
Rectangle {
id: stationrect
width: main.width*2; height: view.height
color: "#00000000"
visible:true
Text {
id: headingText
anchors.leftMargin: 10
clip: false
anchors.fill: parent
wrapMode: Text.NoWrap
font { family:fontName; pointSize: 31}
smooth: true
style: Text.Raised
opacity:1
color: "white"
z:2
}
Text {id: countriesText; color: textColor; text: "Countries"
font {family:fontName; bold: false; pointSize: 20}
smooth: true
style: Text.Raised
anchors.leftMargin: 10
anchors {top: parent.top; left:parent.left; topMargin:110}
opacity: 0
z:2}
Rectangle {
id: rectangle1
x: 0
width: main.width*2;
height: 180
color: "#000"
opacity: 1
z:0
Image{
height: main.height
opacity: 0.400
source: "backgroundtop.jpg"
anchors.fill:parent
}
}
Rectangle {
z:-2
color: "#00000000"
anchors{ left:parent.left; right:parent.right; bottom:parent.bottom; top:parent.top
leftMargin: 10; rightMargin:10; bottomMargin:10; topMargin:200 }
VisualDataModel {
id: countryItemModel
model :ListModel {
id: countryModel
}
delegate:
Rectangle{
width: countryText.width+10; height: countryText.height+10
color: "#00000000"
id:countryItem
Text{id:countryText
smooth: true
style: Text.Raised
x:10
font{family:fontName; bold: true ;pointSize: 12} color: textColor
text: pays
}
MouseArea{
anchors.fill: parent
onClicked: {countryList.currentIndex = countryList.indexAt(countryItem.x, countryItem.y)}
onDoubleClicked: {view.currentIndex=1}
}
}
}
ListView {
id: countryList
boundsBehavior: Flickable.DragAndOvershootBounds
anchors.fill:parent
model: countryItemModel
orientation: ListView.Vertical
flickDeceleration: 2000
cacheBuffer: 1000
highlightFollowsCurrentItem: true
highlight: Rectangle { color: "#00000000"; border {color:"#ffffff"; width:2} radius: 5}
currentIndex: -1
onCurrentIndexChanged: {
if(currentIndex!==-1)
{
highlightItem.width=currentItem.width+5;
if(ImageLoader.countryloaded[currentIndex]===0){
Parser.parsepays(currentIndex);
}
ImageLoader.createAlbums(ImageLoader.nompays[currentIndex]);
ImageLoader.createAlbumName(ImageLoader.nompays[currentIndex]);
}
}
}
}
}
...In order to "feed the beast", I choose to use javascript functions to "push" the data into the list, I regrouped all the javascript function in a file, so that the very fluent and not buggy Qml would not be spoiled by buggy javascripts functions.
Here is the result for the countries list:
function createPays()
{
for(var i=0; i<nompays.length; i++) {
countryModel.append({"pays":nompays[i]});
}
}
Basic, but it works well and I'm satisfied with the performance so far. The swapping between the items are not shown here but some examples are available on the net, so it should not be difficult for you to find it.
The result is shown here :
Stations: Made of a Rectangle with a picture for the background (trick to hide the list scrolling) + TextElement
Countries / Ads : TextElement/Inneractive
Countries list: ListView inside Rectangle
Back/Quit,Favourite Country,Enjoy streaming music,More: Toolbar
All that gives a nice "Windows Phone 7" like browsing, I have been really happy with the result but I have to say that I did not spend most of my time on this part of the app.
Please note that the font is depended on your system font (my N8 is a slightly tweaked one so this is not the basic font).
Feeding the core (XML Parsing)
Here comes the part with which I had to do the hardest job. In order to make the application interesting for everyone of us, I choose to make it so that I could update the data without release a new version on the store. To succeed, I store a XML file "somewhere on the Internet" and every-time the app is launched, a HTTP request is sent to get the XML file and parse it in order to store the data received.
For the full version, this represents a 3 thousands lines file. I also use a DTD file in order to check the data integrity while editing it.
Here is the main part of the XML parser (i used javascript):
I should improve the "coding style" and the quality of the comments this summer.
To comment what the function is doing here, I create a HTTP request and, when the state of the request change, I check if everything is fine. If so, I start the parsing of the data. When the parsing is done, I change the state of the application in order to show the main view.
So, for example, when you click on a resort, the following function is called (I commented it quite much):
function parsestations(p,s){
station = getElementsByTagName(pays[p], 'station');......... // We get the current country resorts
ImageLoader.stationloaded[p][s]=1; ............// We keep track that this resorts is its country has been parsed
var w=0;
ImageLoader.webcam2D[p][s]=new Array(); ...............// For this country and resort, we create a new array
webcams = getElementsByTagName(station[s], 'imaweb'); ........// We get the webcams url from the "station" array
for (w in webcams) {
ImageLoader.webcam2D[p][s]=ImageLoader.webcam2D[p][s].concat(webcams[w].firstChild.nodeValue) // For each webcam, we add the value in the global array
}
}That is all for this part, my point of view about the interaction between Javascript and Qt Quick will be described in the "Conclusion" part.
Loading the data
A very interesting aspect of Qml is that it's possible to mix very easily Javascript functions to the Qml part. So I regrouped all the "intel" of the app inside Javascripts files and used Qt Quick to provide the best UI possible.
Looking at coding, it gives the following result, when a resort is selected:
onCurrentIndexChanged: {.............// We trigger a change
if(currentIndex!==-1) .....// If an item is really selected
{
highlightItem.width=currentItem.width+5;...//We add a nice effect to it
if(ImageLoader.stationloaded[countryList.currentIndex][currentIndex]===0){...//If we did not parse this resort before
Parser.parsestations(countryList.currentIndex,currentIndex);....//We parse it
}
ImageLoader.createImages(currentIndex,ImageLoader.nompays[countryList.currentIndex]);...//We create all the releated webcams images
}
}Display Webcam image
To display the webcam image, a Flickable area is used. It comes with a Image component, a BusyIndicator, a PinchArea and a MouseArea. States with the help of Transitions are used to represent the image with 180° angle.
A small challenge that I had to face was the size limit of a image. Indeed, when loading a too big image the application started to slow down and it was not smooth anymore, I had to add the following code in the Image component in order to reduce the size of large images:
sourceSize.width: 2000
sourceSize.height: 500
Transition and rotation of the image is coded with the following QML code:
states:[
State {
name: "normal"
PropertyChanges { target: itemRotation; angle: 0 }}
,State {
name: "rotated"
PropertyChanges { target: itemRotation; angle: 180 }
}
]
transitions: Transition {
RotationAnimation { properties: "angle"; from:if (imagedetail.state == "rotated"){0}else{-180}
duration: 1000 }
}
transform: Rotation {
id: itemRotation
origin.x: imagedetail.width /2;
axis.y: 1; axis.z: 0
}
You can have a overview of the image component inside the video.
Saving a favourite (database interaction)
For this part, I used the following Example. It allowed me to create very quickly a "Save and Load" function to my app. The following code is used to save and reset the favourite resort:
function save() {
//On sauve le pays//
if ((countryList.currentIndex>=0) && (albumList.currentIndex>=0)){
Storage.setSetting("CurrentCountry",countryList.currentIndex);
Storage.setSetting("CurrentStation",albumList.currentIndex);
textsave.text=countryModel.get(countryList.currentIndex).pays+ "\n"+albumModel.get(albumList.currentIndex).album+"\nsaved as favourite"
}
else{
textsave.text="Please select a country\nand a resort first"
}
}
function reset() {
//On sauve le pays//
Storage.setSetting("CurrentCountry",-1);
Storage.setSetting("CurrentStation",-1);
textsave.text="\nNo favourite"
}I implemented the function inside the "More" page of the app, very easily, using a ButtonRow and a TextField, the result is quite nice looking :
- DataBaseInteraction
Conclusion
I would say that creating this application taught me a lot about Qml and Javascript. My conclusion is that QML provide THE perfect skeleton to any app, mobile or desktop. I am used to learn Java™ in my engineering school and I have to say I am really pleased by the creation of this application. Qt Quick is really much simpler to use than Swing or AWT (even if these last ones are not "mobile" focused) and pretty much straight forward, so this allowed me to save time for focusing on other parts of the application.
Video
Here is a 6 minutes video in which I'm trying to show you the nice aspects of Qt Quick in my application. I also explain the few challenges I had to face (with the tricks I found).
Download
And older version (3.6) is available on the Nokia Store. The newest version is waiting for QA approval.
If you own a N9, you can find it here, this is the latest version to date.
Please note that the 2€ version is just here for anyone who wish to support my work and help me in my studies. If you do not wish to help me, the free version is more than enough to just check resorts weather.


Chintandave er - added draft category
hi Pipould,
As the article is not yet completed by you, I have added draft category in this article. once you done with this article, remove the draft category.
Let me know if you need any other help in writing article.
Chintan.Chintandave er 20:06, 9 March 2012 (EET)
Pipould -
hi,
Sure, thank you :) That's my first wiki article so I hope it will be usefull and everyone will like it !
Br,
Leopold.Pipould 01:09, 10 March 2012 (EET)