Introduction
QML, also called Qt* Quick, is a declarative language that is built on top of the Qt* libraries for rapid application development. Qt* Quick is a new addition to the family of Qt* technologies that bring together a very fast standards compliant JavaScript engine and the Qt* C++ libraries. While Qt* Quick does emphasize graphical and touch based applications, developers are building all types of rich, interactive components that extend QML and promise to make QML a very wide reaching and interesting programming environment.
In this tutorial, we are going to walk through the creation of a sample QML application that includes a virtual keyboard component. By the end, you should have a good understanding of how QML lets a developer rapidly create an application, how to create components for your application and how to integrate your QML with C++ and compile
For more details on designing MeeGo* applications see the MeeGo* Application Design Guidelines.
Create a new Qt* Quick Application Project
Open Qt* Creator and select File -> New File or Project ->Qt* Quick Project -> Qt* Quick Application
Name this project "VirtualKeyBoard"
A new project with a default main.qml will be created
Override, main.qml with the contents given below
main.qmlimport QtQuick 1.0 Rectangle { id: window width: 600; height: 380 color: "#282828" }
This will give us a rectangular gray screen, the canvas for our application.
QML Components for your application
Let's start by setting up our directory structure.
Right click on the QML section of the your new project, select "Add New" and create a new QML file named. Now a new dialogue box should appear that will allow you to create a new QML file. Click on the browse button and create a new directory named "components". Lastly, give the name "LineEdit.qml" to your new file.
Here is the contents of LineEdit.qml.
import QtQuick 1.0
FocusScope {
id: scope
//FocusScope needs to bind to visual properties of the children
property alias color: rectangle.color
property alias text : displayText.text
x: rectangle.x
y: rectangle.y
width: rectangle.width
height: rectangle.height
Rectangle {
id: rectangle
//property alias text : displayText.text
/* set the default values for the LineEdit */
// when active there should be an orange border
border.color: "orange"
border.width: scope.activeFocus ? 5 : 0
// Properties that are passed in to this Item
width: parent.width
height: parent.height
// Properties that are given a value
radius: 10
focus: false
Text {
id: displayText
anchors {
left: parent.left;
verticalCenter: parent.verticalCenter;
margins: 6
}
// The width must be set for eliding to work properly
width: parent.width - 6
// Text properties
font.pixelSize: parent.height * .6; // Text should fit in the the field
elide: Text.ElideLeft // Scroll the text to the left when it is longer
// than the width of the LineEdit
color: "#343434"
smooth: true; // Use font smoothing
font.bold: true // font is bold
font.family: "Times New Roman"
}
}
MouseArea { anchors.fill: parent; onClicked: { scope.focus = true } }
}
Qt* Quick allows you to quickly create QML components simply by creating a file that begins with an uppercase character and contains only one high-level item. This higher level item can be referred to in other QML files simply by referring to the name of the file. You will see how to create and use the LineEdit component later on in our main.qml.
Walk through of the LineEdit.qml file
Line 1: the first line of every QML file should be to import the Qt* Quick libraries.
import QtQuick 1.0
Line 3: on line three, we declare a FocusScope. A FocusScope defines a boundary for a focus.
FocusScope { id: scope
Lines 9-12: A focusScope by itself does not have any physical dimensions and must inherit the dimensions of its child element.
//FocusScope needs to bind to visual properties of the children property alias color: rectangle.color property alias text : displayText.text x: rectangle.x y: rectangle.y width: rectangle.width height: rectangle.height
Line 14: The Rectangle element is one of the most basic of all QML elements. On line 15, we give the Rectangle an indentifier that can be used to reference this Rectangle object in other parts of the code.
Rectangle { id: rectangle
Lines 21 and 22 define that this Rectangle should have an orange border. When it has the focus the width of the border is 5 pixels and when it does not the width is 0 pixels. This border gives a visual indicator that the use has clicked on the LineEdit.
// when active there should be an orange border border.color: "orange" border.width: scope.activeFocus ? 5 : 0
Lines 25-30 are setting the width, height, radius and focus properties of this LineEdit. These properties are the default values and can be overridden when a LineEdit is instantiated. Notice that the width and height properties are set to the parents elements width and height. In this case, the parent is the FocusScope and if you examine the FocusScope, it is simply using the value of the rectangle since the FocusScope element is not a visual element, the properties of its children need to be exposed to the FocusScope. Also note that there is no default value specified therefore we must set it when instantiating a LineEdit.
// Properties that are passed in to this Item width: parent.width height: parent.height // Properties that are given a value radius: 10 focus: false
Line 32-33: On this line, we instantiate a Text element and give it a name.
Text { id: displayText anchors {
Line 34-38: Here we describe how the Text element is to be positioned. In QML, visual items are often laid out relatively to each other using anchors. In this example, we have the following lines.
anchors { left: parent.left; verticalCenter: parent.verticalCenter; margins: 6 }
These can be read as:
- The left anchor of the Text element is positioned next to it's parent's left anchor, in other words, the text will start on the left side of the Rectangle.
- The verticalCenter of the Text element is positioned the same a the parent's vertical center, in other words, text should be in the vertical center of the Rectangle
- The top, right, bottom and left sides of the Text element have a margin of 6 pixels.
Lines 41-50: Here the width, size, color, smoothing, eliding, bold and font family properties are all set. Setting the elide property to Text.ElideLeft means that if the text typed fills the width of the Text element the text will begin to scroll to the left, keeping all displayed text inside the boundaries of the Text element.
// The width must be set for eliding to work properly width: parent.width - 6 // Text properties font.pixelSize: parent.height * .6; // Text should fit in the the field elide: Text.ElideLeft // Scroll the text to the left when it is longer // than the width of the LineEdit color: "#343434" smooth: true; // Use font smoothing font.bold: true // font is bold font.family: "Times New Roman"
Lastly line 53: A MouseArea is defined that fills it's entire parent. When the MouseArea is clicked then the parent receives the focus.
MouseArea { anchors.fill: parent; onClicked: { scope.focus = true } }
Using QML Components in main.qml
Now that we have a LineEdit component, let's look at our main.qml which declares two of them. The reason for declaring two of them is so later when we attach a virtual keyboard, we can click between them and show that the focus determines which lineEdit receives the typed characters.
Here is the new code for main.qml
import QtQuick 1.0
import "components"
import "components/ops.js" as Ops
Rectangle {
id: window
width: 600; height: 380
color: "#444444"
property string rotateLeft: "\u2939"
property string rotateRight: "\u2935"
property string leftArrow: "\u2190"
property string upperArrow: "\u2191"
property string division : "\u00f7"
property string multiplication : "\u00d7"
property string squareRoot : "\u221a"
property string plusminus : "\u00b1"
// if the background is clicked then other Items lose focus
MouseArea {
anchors.fill: parent
onClicked: { parent.focus = true; vk.state = "inactive"; }
}
Column {
spacing: 8
anchors { fill: parent; margins: 10 }
LineEdit {
id: lineEdit1
width: parent.width; height: 64
MouseArea {
anchors.fill: parent
onClicked: {
vk.state = "active";
parent.focus = true
}
}
}
LineEdit {
id: lineEdit2
width: parent.width; height: 64
MouseArea {
anchors.fill: parent
onClicked: { vk.state = "active"; parent.focus = true }
}
}
}
VirtualKeyboard {
id: vk;
onButtonPressed: {
console.log("Keyboard sent: " + op)
Ops.doOperation(op)
}
}
}
Creating an In-Application Virtual Keyboard: The Button Component
A virtual keyboard is usually created by arranging key buttons in a particular order. So a Button is a natural fit for a new QML component.
Right click on the QML section of the your new project, select "Add New" and create a new QML file named "Button.qml". The new dialogue box should appear that will allow you to create a new QML file. Click on the browse button and create a new directory named "components". Lastly, give the name "Button.qml" to your new file.
import QtQuick 1.0
BorderImage {
id: button
property alias text: buttonText.text
property string op: buttonText.op
property alias shiftText: buttonShiftText.text
property string shiftOp: buttonShiftText.op
signal clicked(string op)
width: 50
height: 50
source: "images/button.png"; clip: true
border { left: 10; top: 10; right: 10; bottom: 10 }
Rectangle {
id: shade
anchors {fill: button;}
radius: 10;
color: "black";
opacity: 0
}
Text {
id: buttonText
anchors { centerIn: parent; }
font.family: "Times New Roman"
font.pixelSize: parent.width > parent.height ? parent.height * .6 : parent.width * .6
style: Text.Sunken;
color: "black";
styleColor: "black";
smooth: true
}
Text {
id: buttonShiftText
anchors {
top: parent.top
right: parent.right
rightMargin: 8
}
font.pixelSize: parent.width > parent.height ? parent.height * .25 : parent.width * .25
// style: Text.Sunken;
color: "white";
styleColor: "black";
smooth: true
}
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: button.clicked(op)
}
states: State {
name: "pressed"; when: mouseArea.pressed == true
PropertyChanges { target: shade; opacity: .3 }
}
}
The Button is a more interesting component then the LineEdit for a couple of reasons. First of all, the Button introduces QML Signals. Signals allow QML elements to send messages and data to each other. Secondly, the Button introduces the ImageBorder element which is used to define a background that can scale in size while maintaining particular aspect ratios for the borders.
QML Signal and Handler System
QML is built on top of Qt* and therefore inherits Qt*'s meta-object and signal systems. Signals defined in Qt* C++ can be sent or received in QML.
Signals allow Qt* objects to communicate between each other. For example, the MouseArea clicked signal notifies other elements that the mouse has been clicked within the area.
The Button element defines the clicked signal that sends the character that the user touched. Notice that the event is triggered from the MouseArea's onClicked function.
Creating an In-Application Virtual Keyboard: The Keyboard Component
Here is the source code for VirtualKeyboard.qml
import QtQuick 1.0
Rectangle {
id: vk
property string rotateLeft: "\u2939"
property string rotateRight: "\u2935"
property string leftArrow: "\u2190"
property string upperArrow: "\u2191"
property string division : "\u00f7"
property string multiplication : "\u00d7"
property string squareRoot : "\u221a"
property string plusminus : "\u00b1"
signal buttonPressed(string op)
// span the parent's width
anchors {
left: parent.left
right: parent.right
}
// A virtual keyboard (VK) is attached the the bottom of an Item
// and slides up when activated.
// this item is called the surface
property Item surface: parent
// By default the VK's height is at the bottom of the surface
y: surface.height
// Depending on the parent's focus, set the initial state
state: parent.activeFocus ? "active" : "inactive"
Column {
id: vkc;
anchors {
horizontalCenter: parent.horizontalCenter
margins: 4
}
spacing: 6
Row {
spacing: 6
anchors.horizontalCenter: parent.horizontalCenter
Button { text: "q"; op: "q"; shiftText: "1"; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "w"; op: "w"; shiftText: "2"; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "e"; op: "e"; shiftText: "3"; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "r"; op: "r"; shiftText: "4"; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "t"; op: "t"; shiftText: "5"; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "y"; op: "y"; shiftText: "6"; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "u"; op: "u"; shiftText: "7"; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "i"; op: "i"; shiftText: "8"; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "o"; op: "o"; shiftText: "9"; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "p"; op: "p"; shiftText: "0"; Component.onCompleted: clicked.connect(buttonPressed)}
}
Row {
spacing: 6
anchors.horizontalCenter: parent.horizontalCenter
Button { text: "a"; op: "a"; shiftText: "@"; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "s"; op: "s"; shiftText: "#"; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "d"; op: "d"; shiftText: "%"; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "f"; op: "f"; shiftText: "&"; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "g"; op: "g"; shiftText: "*"; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "h"; op: "h"; shiftText: "-"; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "j"; op: "j"; shiftText: "+"; shiftOp: "+" ; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "k"; op: "k"; shiftText: "="; shiftOp: "=" ; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "l"; op: "l"; shiftText: "/"; shiftOp: "/" ; Component.onCompleted: clicked.connect(buttonPressed)}
}
Row {
spacing: 6
anchors.horizontalCenter: parent.horizontalCenter
Button { text: "z"; op: "z"; shiftText: "_"; shiftOp: "_" ; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "x"; op: "x"; shiftText: "$"; shiftOp: "$" ; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "c"; op: "c"; shiftText: "^"; shiftOp: "%" ; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "v"; op: "v"; shiftText: "\""; shiftOp: "&" ; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "b"; op: "b"; shiftText: "\'"; shiftOp: "*" ; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "n"; op: "n"; shiftText: ":"; shiftOp: "-" ; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "m"; op: "m"; shiftText: ";"; shiftOp: "+" ; Component.onCompleted: clicked.connect(buttonPressed)}
}
Row {
spacing: 6
anchors.horizontalCenter: parent.horizontalCenter
Button { text: upperArrow; op: upperArrow ; Component.onCompleted: clicked.connect(buttonPressed)}
Button { text: "Alt"; op: "" ; Component.onCompleted: clicked.connect(buttonPressed)}
Button { width: 200; height: vk.h; text: "Space"; op: " "; Component.onCompleted: clicked.connect(buttonPressed) }
Button { width: 80; text: ".com"; op: ".com"; Component.onCompleted: clicked.connect(buttonPressed) }
Button { text: leftArrow; op: leftArrow; Component.onCompleted: clicked.connect(buttonPressed) }
}
}
states: [
State {
name: "active"
PropertyChanges { target: vk; y: (surface.height - vkc.height); }
},
State {
name: "inactive"
PropertyChanges { target: vk; y: surface.height + 8; }
}
]
transitions: Transition {
NumberAnimation {
target: vk
property: "y"
easing.type: "OutQuad"
duration: 250
}
}
}
There are a couple of features to point out here. First of all, the string, "Component.onCompleted: clicked.connect(buttonPressed)}" is repeated on each button. This takes the button's signal clicked and connects it to the Virtual Keyboards buttonPressed signal. This means that every time a button sends the clicked signal, the virtual keyboard will send the buttonPressed signal.
Of course, the virtual keyboard should hide when it's not in use. After the column/row layout of the virtual keyboard is defined, two states active and inactive are created, as well as, a transition which slides the keyboard up and down in a quarter of a second.
Adding Javascript
Arguably, the Javascript file could easily be inlined in one of the QML files. However, it is useful to know how to separate them out. In the case, of this application the main.qml files imports Javascript file with the doOperation() function to allow the actively focused LineEdit to receive the characters typed on the virtual keyboard. The doOperation function also implements the backspace and spacebar operations.
Javascript can be called from QML by including the line:
import "components/ops.js" as Ops.
Here is the file ops.js
function doOperation(op) {
var t = (lineEdit1.activeFocus ? lineEdit1.text : lineEdit2.text)
if (op == leftArrow) {
t = t.toString().slice(0, -1)
if (t.length == 0) {
t = ""
}
} else {
t = t + op.toString()
}
lineEdit1.activeFocus ? lineEdit1.text = t : lineEdit2.text = t
}
Congratulations
Here is a screenshot of the in-application virtual keyboard.
Exercises for the Reader
At this point, you have a minimal but functional in-application virtual keyboard. There are many ways in which this virtual keyboard can be improved. Here are some suggested improvements that will help you exercise your newly formed QML developer skills.
-
Currently, the keyboard does not react if a use types on a hardware keyboard. Change the QML so that both keyboard strokes and mouse clicks active the virtual keyboard's buttons.
-
Implement the SHIFT key. This should include changing the button's text, op and shiftops properties.
-
Implement a specialized keyboard layout such as a numberpad or a keyboard that is specialized for a particular program or game.
-
Localize the virtual keyboard for another language.
-
Implement your own interesting extension to the virtual keyboard.
SAMPLE SOURCE CODE - SUBJECT TO THE TERMS OF SAMPLE CODE LICENSE AGREEMENT, http://software.intel.com/en-us/articles/intel-sample-source-code-license-agreement/ Copyright 2011 Intel Corporation THIS FILE IS PROVIDED "AS IS" WITH NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS.