Première application mobile en QML pour MeeGo 1.2 Harmattan

Les nouveautés de Qt sont, actuellement, très orientées mobile. De plus, un système d'exploitation mobile est totalement basé sur Qt : MeeGo Harmattan. Tout amateur de Qt disposant d'un périphérique compatible aimera probablement développer l'une ou l'autre application sur son jouet technologique ; tel est le but de ce tutoriel, en utilisant Qt Quick, bijou arrivé dans Qt 4.7. Puisque MeeGo Harmattan ne supporte que Qt 4.7, on n'utilisera que la version 4.7.4 de Qt.

Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

MeeGo est un projet de système d'exploitation pour mobiles par Nokia et Intel, résultat de la fusion de Maemo et Moblin. Notamment, c'est celui utilisé sur le Nokia N9. Sans revenir sur l'actualité de ce système d'exploitation (quoique bien chargée : le N9 initialement prévu dans peu de pays, tentative de mise sur le marchéet d'autres), on notera simplement qu'il est basé sur un noyau Linux et utilise extensivement Qt, Qt Quick et Qt Mobility. C'est pour ces qualités qu'on l'étudiera ici.

Le but de cet article est simplement de partir d'une connaissance de base de Qt, Qt Creator et Qt Quick pour réaliser une petite application sympathique gérant les scores d'une partie de belote, le tout de manière aussi native que possible, en utilisant exclusivement QML pour l'interface, JavaScript pour la logique (et un peu de C++ pour lancer l'application, mais ce code est généré par Qt Creator). Voici ce à quoi on arrivera au terme de ce parcours :

Image non disponible
Le résultat final dans le simulateur.
Image non disponible
Le résultat final dans le simulateur (ajout de scores).

Toutes les captures et vidéos ont été prises sous Windows, mais tout devrait rester semblable sur les autres plateformes supportées par Qt (si ce n'est l'apparence des fenêtres).

II. Prérequis

Il faut avoir installé le Qt SDK 1.1.3 au minimum, avec Qt Creator, Qt 4.7.4, les composants QML pour MeeGo 1.0, Qt Mobility 1.2, la cible Harmattan pour Qt Creator, l'émulateur Harmattan, ainsi que les documentations associées (afin d'obtenir de l'aide sur les composants à l'intérieur de Qt Creator). Voici, à titre d'exemple, ce à quoi pourrait ressembler le choix de paquets (avec des versions de Qt desktop inutiles ici) :

Image non disponible

III. Créer le projet

III-A. Vidéo récapitulative

Une fois tout cela installé, on peut passer aux choses sérieuses en créant un projet sous Qt Creator. Sous Windows, on pourra le trouver dans le menu Démarrer ; sous Mac OS X, il se trouvera sous Developer. Il suffit alors de suivre les instructions de cette vidéo :

III-B. Pas à pas en images

Après avoir lancé Qt Creator, dans le menu File > New File or Project, choisir une application Qt Quick :

Image non disponible

Ainsi, Qt Creator créera les fichiers nécessaires à l'exécution de l'application (les sources d'une classe QmlApplicationViewer seront automatiquement ajoutées au projet, elles se chargeront de lancer le fichier QML principal de l'application sans ligne de code requise de la part du développeur, ainsi que du support de fonctionnalités des plateformes sélectionnées plus loin dans l'assistant, telles que l'orientation de l'écran).

Au début des fichiers de la classe QmlApplicationViewer, un commentaire explicite que ces fichiers ont été générés automatiquement et qu'une prochaine version de Qt Creator pourrait y apporter des modifications.

On choisit alors le nom du projet et l'emplacement des sources (on nommera ici le projet MeeGoBelote, avec une inspiration digne de Tolkien) :

Image non disponible

On peut alors choisir les composants Qt Quick supplémentaires à ajouter comme dépendances au projet. Pour avoir des applications au look plus natif, on choisit les Qt Quick Components for MeeGo (on aurait pu choisir les Qt Quick Components for Symbian pour cibler cette autre plateforme, on n'utilisera pas dans la suite de widget spécifique à la plateforme MeeGo) :

Image non disponible

Ensuite, on choisit les plateformes pour lesquelles Qt Creator proposera de compiler par défaut, on utilisera le simulateur et la compilation croisée vers la plateforme mobile :

Image non disponible

Dernière étape de configuration, choisir l'icône de l'application et si on peut la booster (c'est-à-dire en rendre le chargement plus rapide) :

Image non disponible

Qt Creator offre alors un récapitulatif des fichiers qui seront générés et propose d'ajouter le projet comme sous-projet ou dans un système de gestion des sources :

Image non disponible

Pour que le packaging se passe bien (et ainsi déployer l'application), il faut encore ajouter quelques fichiers spécifiques à la plateforme (bien que tout soit géré de manière transparente par Qt Creator) :

Image non disponible

Les sources du projet jusqu'à présent sont disponibles.

IV. Lancer l'application

On ne va pas retarder plus le moment magique où l'on voit pour la première fois une application QML lancée sur mobile. On va ici la lancer sur le simulateur, à partir des sources fournies par Qt Creator. On aura un projet extrêmement simple, qui montre un menu et une zone avec un bouton cliquable affichant un texte.

La manipulation est simple : il faut s'assurer qu'on a bien sélectionné la cible Qt Simulator (en mode de débogage ou non), il suffit alors de cliquer sur Run (ou d'utiliser le raccourci clavier Ctrl+R).

Image personnelle

Bien que le simulateur ne la montre pas, l'application aura bien la barre de statut de MeeGo en haut de l'écran, à moins de la désactiver explicitement :

 
Sélectionnez
PageStackWindow {
     showStatusBar: false
	 // ...

V. Gérer les scores à la belote

Pour ceux qui ne connaîtraient pas ce jeu de cartes, un tour sur Wikipédia ou une autre source s'impose. On se limitera ici à la gestion des scores, rien de plus, rien de moins. Cela restera néanmoins un parcours intéressant dans Qt Quick : on utilisera un tableau pour montrer les différentes parties et les scores obtenus à chaque partie. Toutes ces données seront contenues dans un modèle (on appliquera donc le patron de conception MVD, propre à Qt, voir l'article Apostille au MVC : les délégués pour plus de détails à ce sujet en C++). Là où les choses deviennent intéressantes, c'est qu'on devra modifier le modèle à l'exécution. On repartira de la base proposée par Qt Creator.

Par la suite, les premiers morceaux de code QML arriveront, voici déjà un petit mot sur la syntaxe. QML est fortement inspiré de CSS quand on y regarde de loin. De plus près, on remarque qu'on va beaucoup plus imbriquer les éléments que ce que CSS autorise. Au niveau du paradigme, cela n'a pas grand-chose à voir non plus : alors que CSS explicite comment il faut afficher le contenu, QML ne va que décrire le contenu (avec quelques touches pour le style, évidemment, mais ce n'est pas son seul objectif).

V-A. Afficher la table des scores

Les débuts seront modestes : on va remplacer le bouton par défaut par une vue en liste connectée sur un modèle entièrement statique.

V-A-1. Le modèle

Première étape, la plus simple de toutes : créer le modèle. On crée un nouveau fichier QML dans le projet, GamesModel.qml, qui contiendra un élémentListModel. On ne pourra pas l'utiliser tel quel, il faudra l'instancier quand on en aura besoin, on lui donnera un identifiant à ce moment-là. Ce modèle contiendra une certaine quantité d'éléments ListElement, chacun étant un élément du modèle. Chacun de ces éléments contient une série de rôles : le numéro de la partie (number) et les scores obtenus par chacune des deux équipes (teamA et teamB).

 
TéléchargerSélectionnez
import QtQuick 1.1

ListModel {
    property int sumA: 0
    property int sumB: 0

    ListElement {
        number: 1
        teamA: 0
        teamB: 0
    }

    ListElement {
        number: 2
        teamA: 0
        teamB: 0
    }
}

Le lecteur attentif aura remarqué la présence de deux propriétés en début de modèle : elles serviront à stocker les sommes des points de chaque équipe.

On souhaitera avoir une quantité d'éléments telle qu'il n'y ait pas assez de place pour que tous apparaissent simultanément à l'écran lors de l'affichage, pour qu'il y ait un défilement dans la vue.

Cependant, cette manière de faire n'est pas confortable : que faire si l'on veut tester avec une cinquantaine d'éléments (même la dizaine requise à l'établissement du défilement) ? Elle convient parfaitement quand on veut toujours avoir quelques éléments fort différents dès le chargement de l'application, mais cela n'est pas le cas ici. On peut donc supprimer le modèle créé et insérer quelques lignes dans la fenêtre (on a choisi un identifiant tel qu'il ne peut pas interférer avec autre chose, il sera donc réutilisable dans le reste de l'application) :

 
TéléchargerSélectionnez
    ListModel {
        id: commonModel

        property int sumA: 0
        property int sumB: 0
    }

On disposera alors d'exactement la même chose que précédemment, sans la lourdeur d'un composant supplémentaire... mais sans données. On peut les ajouter au chargement de l'application à l'aide d'une petite fonction JavaScript :

 
TéléchargerSélectionnez
import "Belote.js" as Belote

PageStackWindow {
    id: appWindow

	// ...

    Component.onCompleted: Belote.fillModel()
 
TéléchargerSélectionnez
function fillModel() {
    for(var i = 1; i <= 15; ++i) {
        var data = {"number": i, "teamA": Math.floor(Math.random()*42), "teamB": Math.floor(Math.random()*42)};
        commonModel.append(data);
        commonModel.sumA += data.teamA;
        commonModel.sumB += data.teamB;
    }
}

Plusieurs remarques. Tout d'abord, dans le fichier QML, l'import doit s'effectuer en précisant une chaîne de caractères entourée de guillemets. Sinon, QML croit qu'on cherche à inclure un plug-in et demandera que l'on précise une version. On utilise un alias pour cette importation, c'est requis par QML pour l'importation de fichiers JavaScript (le mécanisme restant utilisable pour toute autre chose à importer, bien évidemment, comme des fichiers QML ou des plug-ins). De plus, cela rend le code plus lisible par la suite (et on pourra aussi, si les performances ne sont pas à la hauteur des attentes, déplacer le code JavaScript dans un plug-in C++ sans changer plus que les lignes d'importation).

On exécute le code JavaScript à l'aide du signal Component.onCompleted : il est émis lorsque le démarrage du composant est achevé, à en croire la documentation (soit une fois que tout l'environnement QML est établi, que l'on peut modifier les composants chargés sans risque de les mettre dans un état indéfini).

Dans le code JavaScript, pour ceux qui ne sont pas spécialistes, on a utilisé le mot-clé var pour définir des variables locales : à la fin de leur portée, elles sont détruites. Sinon, QML se plaindrait qu'il ne peut pas écrire dans des variables globales et empêcherait l'application de fonctionner (en mode de débogage avec le simulateur, il y aura un message d'erreur dans la console (1) et le simulateur montrera une page blanche). Finalement, JavaScript ne dispose pas d'une fonction de génération de nombre pseudoaléatoire aussi puissante qu'en C++ : elle ne donne qu'un nombre entre 0 et 1, qu'il faut alors transformer de telle sorte qu'il donne un score acceptable (sinon, le rendu est affreux, sachant qu'on n'a pas spécifié dans le modèle que les scores de chaque équipe devaient être entiers : QML affiche alors à l'écran toutes les décimales).

Ces données n'étant utilisées qu'à des fins de test, on n'a pas pris soin de faire en sorte qu'elles soient plausibles dans le contexte du jeu de belote.

Les sources de cette partie sont disponibles.

V-A-2. L'architecture proposée par Qt Creator

Ensuite, on va modifier la page principale de l'application. Avant cela, deux mots sur l'architecture proposée par Qt Creator.

Tout d'abord, le fichier principal, main.qml :

 
TéléchargerCacherSélectionnez

On y définit une pile d'écrans avec PageStackWindow, dont l'écran par défaut aura l'identifiant mainPage (il sera défini dans MainPage.qml). Le reste du fichier définit le menu.

L'élément ToolBarLayout définit le menu en bas de l'écran. On pourrait y ajouter d'autres boutons que celui mis par défaut (retour à un écran précédent, par exemple), mais cela n'aura pas d'utilité dans le cas présent. L'icône du menu est ajoutée avec l'élément ToolIcon, qui est accroché à droite. Quand on clique dessus, on lance le menu myMenu, tel que défini juste après.

L'élément Menu définit un menu, qui sera affiché dans la pile d'écrans (visualParent: pageStack) et qui aura une série d'items (MenuLayout).

La fonction qsTr est l'équivalent de QObject::tr() en C++ : elle permet de traduire la chaîne en fonction de la langue de l'utilisateur. Cela ne coûte rien de la mettre et facilitera grandement le travail quand il faudra traduire l'application.

On peut représenter la situation de manière graphique, en se basant sur une capture d'écran du simulateur.

Image non disponible
Toute l'application.
Image non disponible
La fenêtre (élément PageStackWindow).
Image non disponible
La page (élément MainPage).
Image non disponible
La barre d'outils (élément ToolBarLayout).
Image non disponible
Un bouton de menu (élément ToolIcon).

La page affichée ne représente pas grand intérêt supplémentaire : on définit un élément Page auquel on fait référence depuis la fenêtre. Le contenu de cette page sera rendu à l'endroit précisé.

V-A-3. La vue

Après ces éclaircissements, on peut remplacer le bouton (la MainPage dans les images ci-dessus) par la vue du modèle. Là, pas grande surprise : pour un ListModel, on utilisera une ListView. On commence donc par créer une page :

 
TéléchargerSélectionnez
import QtQuick 1.1
import com.nokia.meego 1.0

Page {
    tools: ToolBarLayout {
        ToolIcon {
            platformIconId: "toolbar-view-menu"
            anchors.right: parent.right
            onClicked: (myMenu.status == DialogStatus.Closed) ? myMenu.open() : myMenu.close()
        }
    }

    Menu {
        id: myMenu
        visualParent: pageStack
        MenuLayout {
            MenuItem { text: qsTr("Sample menu item") }
        }
    }

On rapatrie ici les outils et le menu, car ils ne seront utilisés que dans cet écran.

Comment configurer cette vue ? On réutilise le modèle défini dans la fenêtre (commonModel).

Pour chaque item à afficher, le modèle MVD oblige à utiliser un délégué. On peut le définir maintenant. Un délégué est avant tout un élément QML (un Component, un Item ou autre). On doit lui donner également un identifiant, sinon on ne pourra pas l'utiliser avec la vue sans l'y définir (2). Pour chaque item, on doit afficher une ligne (de hauteur non nulle) qui remplit toute la largeur disponible dans la vue. Cette ligne contient trois colonnes : le numéro de la partie, le score de l'équipe A et celui de l'équipe B. Il suffit alors de transposer mot pour mot cette description en QML (en effet, ce langage implémente un paradigme déclaratif : on décrit ce qu'on veut, on ne dit pas comment y arriver). On place le délégué dans un fichier MainDelegate.qml pour garder les fichiers aussi simples, courts et précis que possible.

 
TéléchargerSélectionnez
Item {
    id: listItem
    width: listView.width
    height: 55

    Row {
        anchors.verticalCenter: parent.verticalCenter
        spacing: 110

        Text {
            text: qsTr("Game %1").arg(number)
            color: "grey"
            width: 130
            font.pixelSize: 43
            font.family: "Nokia Pure Text"
        }

        Text {
            text: teamA
            color: "white"
            horizontalAlignment: Text.AlignRight
            width: 35
            font.pixelSize: 43
            font.family: "Nokia Pure Text"
        }

        Text {
            text: teamB
            color: "white"
            horizontalAlignment: Text.AlignRight
            width: 35
            font.pixelSize: 43
            font.family: "Nokia Pure Text"
        }
    }
}

L'attribut pointSize est aussi disponible pour indiquer les tailles de police. Cependant, elle dépendra notamment de la densité de pixels (en ppi, pixels per inch, souvent dit à tort dpi, dots per inch), il est donc préférable d'indiquer une taille en pixels pour les polices - une taille indiquée en points n'a que peu de chances d'avoir un rendu semblable dans le simulateur ou en vrai.

Ensuite, on passe à la vue proprement dite. Il faut lui donner une série d'éléments, principalement le délégué et le modèle. On veut remplir tout l'espace disponible, puisqu'on n'a rien d'autre à afficher dans cette zone (l'espace pour la barre d'outils est déjà réservé). À nouveau, il suffit de formaliser cette description sans changer de point de vue :

 
TéléchargerSélectionnez
    ListView {
        id: listView
        model: commonModel
        delegate: MainDelegate{}
        anchors.fill: parent

On peut encore peaufiner légèrement l'affichage en ajoutant un en-tête (pour indiquer la signification des colonnes) et un pied de liste (pour afficher les totaux, quand ils seront calculés).

 
TéléchargerSélectionnez
        header {
            Row {
                spacing: 20

                Text { text: "Number"; width: 160 }
                Text { text: "Team A" }
                Text { text: "Team B" }
            }
        }

        footer {
            Row {
                spacing: 120

                Text { text: " "; width: 107 }
                Text { text: commonModel.sumA; width: 15 }
                Text { text: commonModel.sumB; width: 15 }
            }
        }

Les largeurs ont été définies de telle sorte que l'application ait un look correct (titres alignés avec les colonnes).

Qu'obtient-on ? Une liste que l'on peut faire défiler du bout du doigt ! On est donc arrivé au résultat final... mais statique. Bien que tout client qui se respecte dira que faire le reste ne prendra presque pas de temps, on verra que tout n'est pas si trivial que cela. Il faudra faire une incursion supplémentaire dans le domaine du développement Web : JavaScript.

Image non disponible
Le résultat final dans le simulateur.

Les sources jusqu'à cette partie.

V-B. Penser la traduction

V-B-1. Dans le code

Il est toujours intéressant de prévoir son application pour la traduction, même quand on n'en a pas besoin au début. Cela ne pose aucun problème lorsqu'il n'y a aucune concaténation à effectuer, l'en-tête devient alors :

 
TéléchargerSélectionnez
        header {
            Row {
                spacing: 20

                Text { text: qsTr("Number"); width: 160 }
                Text { text: qsTr("Team A") }
                Text { text: qsTr("Team B") }
            }
        }

Par contre, les choses se compliquent quand on utilise de la concaténation : que se passe-t-il si la langue cible doit inverser des termes ? Par exemple, la solution la plus simple à imaginer et cependant la pire pour le traducteur consiste à simplement ajouter des appels à qsTr :

 
Sélectionnez
Text { text: qsTr("My friend number ") + number + qsTr(" is called ") + name }

Le traducteur souhaite mettre le nom avant le numéro : comment fait-il ? Réponse : il ne le fait pas, la traduction sera de mauvaise qualité, les utilisateurs dans cette langue penseront que l'application est vraiment mauvaise et iront voir la concurrence.

Ces cas sont rares ? Pas vraiment :

Anglais (natif) Exemple Français (natif) Exemple En suivant le modèle anglais
The %colour% stick The red stick Le bâton %colour% Le bâton rouge Le rouge bâton
%month% %day% April 1st %day% %month% 1er avril Avril 1er

Qt a trouvé une parade : arg. On peut utiliser cette solution aussi en QML :

 
Sélectionnez
Text { text: qsTr("My friend number %1 is called %2").arg(number).arg(name) }

Ainsi, le traducteur sera libre de déplacer les termes à remplacer où la langue cible l'impose.

Il en va de même pour le pluriel : qsTr dispose, comme tr, de trois paramètres, respectivement la chaîne à traduire, un commentaire pour le traducteur et un nombre. Certaines langues ont un pluriel très compliqué (le polonais), d'autres n'en ont même pas, comme le japonais (voir Les formes du pluriel dans les traductions pour des exemples en détail). On peut donc remplacer une version qui suit scrupuleusement l'usage anglais mais ne conviendra pas au français et à de multiples autres langues

 
Sélectionnez
Text { text: qsTr("There are %1 bottle").arg(number) + (number !== 1 ? "s" : "") + qsTr(" on the wall. ") }

par une version que le traducteur pourra adapter en fonction de la langue cible

 
Sélectionnez
Text { text: qsTr("There are %n bottle(s) on the wall. ", "", count) }

V-B-2. Traduire

Pour extraire les chaînes, rien de compliqué, il suffit de faire comme pour du code C++ : lancer lupdate.

 
Sélectionnez
lupdate MainPage.qml -ts i18n/base.ts

On effectue la traduction avec Qt Linguist :

 
Sélectionnez
cp i18n/base.ts i18n/qml_fr.ts
linguist i18n/qml_fr.ts

Il suffit alors de la compiler (rien de très particulier, toujours) avec lrelease :

 
Sélectionnez
lrelease i18n/*.ts

On aura supposé que l'invite de commande utilisée comprend cp (PowerShell, sous Windows ; tous les shells sous Unixoïdes et ports Windows ; sinon, utiliser copy) et que le PATH est configuré avec les outils Qt.

Les sources jusqu'à cette partie.

V-C. Ajouter de l'interactivité

Deux possibilités d'interaction, seulement : vider la liste des parties et en ajouter une nouvelle. Le plus simple est de vider le modèle, on va commencer par là. Pour chacune des actions, on va ajouter un item dans le menu et du code JavaScript derrière pour exécuter les desiderata probables de l'utilisateur.

V-C-1. Vider le modèle

On commence par ajouter une entrée au menu :

 
Sélectionnez
    Menu {
        id: myMenu
        visualParent: pageStack
        MenuLayout {
            MenuItem { text: qsTr("Clear") }
        }
    }

On a donc une nouvelle entrée, dans le menu... mais elle ne fait rien ! On remarque que le menu se ferme automatiquement, grâce à la ligne onClicked du bouton l'appelant. On utilisera le même genre de ligne ici pour effectuer quelque chose :

 
TéléchargerSélectionnez
    Menu {
        id: myMenu
        visualParent: pageStack
        MenuLayout {
            MenuItem {
                text: qsTr("Clear")
                onClicked: {
                    commonModel.clear();
                    commonModel.sumA = 0;
                    commonModel.sumB = 0;
                }
            }
        }
    }

C'est tout, ça fonctionne !
Les sources jusqu'à cette partie.

On peut cependant améliorer un peu la situation en déportant le code JavaScript dans la petite bibliothèque débutée plus haut. On utilisera donc cette fonction :

 
TéléchargerSélectionnez
function emptyModel() {
    commonModel.clear();
    commonModel.sumA = 0;
    commonModel.sumB = 0;
}

que l'on connecte au reste du code comme ceci :

 
TéléchargerSélectionnez
import "Belote.js" as Belote

PageStackWindow {
    id: appWindow

	// ...

    Menu {
        id: myMenu
        visualParent: pageStack
        MenuLayout {
            MenuItem {
                text: qsTr("Clear")
                onClicked: Belote.emptyModel()
            }

V-C-2. Ajouter une entrée

Pour une nouvelle entrée, on utilisera une nouvelle page, AddGamePage. On s'occupera d'abord de créer la page, puis seulement on verra comment l'ajouter à la suite d'écrans constituant l'application.

V-C-2-a. Créer l'écran

Le nouvel écran sera relativement simple : deux TextField pour l'entrée des données et un Button pour valider le formulaire.

On utilisera ici TextField, un composant supplémentaire à Qt Quick du projet Qt Quick Components for MeeGo, car il offre une apparence plus native que TextInput, tout en restant très portable (il suffit d'implémenter les Qt Quick Components pour la plateforme cible, c'est déjà fait pour Symbian, MeeGo et les plateformes natives).

À chaque entrée, on veut spécifier que l'on veut un entier entre zéro (capot) et cinquante (sans annonce, on peut marquer seize points : avec deux carrés et une belote, on atteint presque les cinquante, c'est un maximum largement suffisant bien que théoriquement dépassable) et montrer à l'utilisateur quand l'entrée ne convient pas (errorHighlight).

Jusqu'à présent, tout ne fonctionne pas pour le mieux : sous MeeGo 1.2 Harmattan versions 1.1 et 1.2, on peut entrer du texte dans ces zones d'entrée, mais le système d'exploitation refusera d'en tenir compte (tout ce qui est tapé sera ignoré). C'est frustrant pour l'utilisateur et tout utilisateur qui se sentira frustré n'aimera pas l'application. Il faudrait donc restreindre le clavier aux chiffres uniquement. Pour ce faire, on peut simplement ajouter une ligne de configuration aux TextField : inputMethodHints: Qt.ImhDigitsOnly. Ceci n'est documenté que dans l'API C++ de MeeGo ; il faut alors se souvenir des différences de notation des espaces de noms entre C++ et QML (remplacer les :: par .).

On a donc le code :

 
TéléchargerSélectionnez
import QtQuick 1.1
import com.nokia.meego 1.0
import "Belote.js" as Belote

Page {
    TextField {
        id: ta

        placeholderText: "Team A"
        maximumLength: 20
        errorHighlight: !acceptableInput
        validator: IntValidator{bottom: 0; top: 50;}
        inputMethodHints: Qt.ImhDigitsOnly

        anchors {
            verticalCenter: parent.verticalCenter
            horizontalCenter: parent.horizontalCenter
            bottomMargin: 5
        }
    }

    TextField {
        id: tb

        placeholderText: "Team B"
        maximumLength: 20
        errorHighlight: !acceptableInput
        validator: IntValidator{bottom: 0; top: 50;}
        inputMethodHints: Qt.ImhDigitsOnly

        anchors {
            horizontalCenter: parent.horizontalCenter
            topMargin: 75
            top: ta.top
        }
    }

    Button {
        anchors {
            horizontalCenter: parent.horizontalCenter
            topMargin: 75
            top: tb.top
        }

        text: qsTr("Add game!")
        onClicked: Belote.pushGame(ta, tb)
    }
}

On envoie les scores à la fonction suivante :

 
TéléchargerSélectionnez
function pushGame(a, b) {
    var ia = parseInt(a.text);
    var ib = parseInt(b.text);

    if(! isNaN(ia) && ! isNaN(ib))
    {
        updateModel({"number": commonModel.count + 1, "teamA": ia, "teamB": ib});
        appWindow.pageStack.pop();

        a.text = "";
        b.text = "";
    }
}

function updateModel(data) {
    commonModel.append(data);
    commonModel.sumA += data.teamA;
    commonModel.sumB += data.teamB;
}

pushGame se charge de récupérer le score de chaque équipe, de vérifier qu'on a bien des entiers (pour éviter d'additionner n'importe quoi au score, ce qui aurait pour effet de mettre les totaux à zéro) et de remettre les textes à leur valeur de départ (histoire que l'écran soit vierge pour chaque partie que l'on veut ajouter).

Cette fonction fait appel au helper updateModel, qui encapsule les spécificités de la gestion du modèle (on ajoute une partie à la liste et on met à jour les totaux).

Les sources jusqu'à cette partie sont disponibles.

V-C-2-b. Y accéder

Pour accéder à cet écran d'ajout de partie, on profitera du menu déjà ajouté à l'écran principal. QML n'est pas prévu pour initialiser des écrans à la volée, il vaut mieux ajouter celui que l'on vient de développer en tant que propriété de la fenêtre (à côté de l'écran principal).

 
TéléchargerSélectionnez
PageStackWindow {
    id: appWindow

	// ...
	
    AddGamePage {
        id: addGame
    }

Ensuite, on branche cet écran à un nouveau bouton du menu :

 
TéléchargerSélectionnez
    Menu {
        id: myMenu
        visualParent: pageStack
        MenuLayout {
            MenuItem {
                text: qsTr("Clear")
                onClicked: Belote.emptyModel()
            }

            MenuItem {
                text: qsTr("Add game")
                onClicked: pageStack.push(addGame)
            }
        }
    }

L'action liée est simple : on ajoute un écran à la pile des écrans actifs. À la fin de cet écran, on l'enlève de la pile pour revenir à l'écran précédent (pageStack.pop()).

Les sources jusqu'à cette partie sont disponibles.

V-D. Améliorer les menus

Un menu tel qu'actuellement fait est prévu pour plus d'options que ce qui est disponible. Cependant, cette application n'a que deux fonctions que l'utilisateur peut appeler. Pourquoi mettre un menu, qui nécessite deux appuis pour une seule action, quand il y en a si peu ? C'est une source de problèmes pour l'utilisateur (qui n'est pas toujours très bien réveillé lors d'un tournoi de belote ou qui a les doigts un peu gros pour la taille prévue). De même, dans l'ajout de partie, on ne propose aucune manière de revenir en arrière, alors que cela est habituel sur le reste de la plateforme.

Image non disponible
Avant.
Image non disponible
Avant.

V-D-1. Dans l'ajout

On spécifie un nouveau ToolBarLayout :

 
TéléchargerSélectionnez
Page {
    tools: ToolBarLayout {
        ToolIcon {
            iconId: "toolbar-back";
            onClicked: { pageStack.pop(); }
        }
    }

On n'y met qu'un seul bouton (le retour en arrière), l'icône étant spécifiée par la plateforme (on fait référence à l'icône toolbar-back disponible sous MeeGo et d'autres, on utilisera donc une image qui correspond au thème habituel de la plateforme). Au clic, il suffit d'enlever la page actuelle de la pile d'écrans (on peut supposer que c'est pour visualiser les scores précédents et que l'utilisateur ne veut pas effacer ce qui a pu être entré).

Les sources jusqu'à cette partie sont disponibles.

V-D-2. À la visualisation

On va ajouter deux boutons en plus du menu actuel : un pour vider le modèle, l'autre pour ajouter un nouvel élément. On dupliquera les fonctionnalités du menu. Pour cela, on procède exactement comme précédemment, à la différence que ces deux boutons ne seront pas sur la droite (l'emplacement étant déjà pris) :

 
TéléchargerSélectionnez
Page {
    tools: ToolBarLayout {
        ToolIcon {
            platformIconId: "toolbar-delete"
            anchors.left: parent.left
            onClicked: Belote.emptyModel()
        }

        ToolIcon {
            platformIconId: "toolbar-add"
            anchors.horizontalCenter: parent.horizontalCenter
            onClicked: pageStack.push(addGame)
        }

        ToolIcon {
            platformIconId: "toolbar-view-menu"
            anchors.right: parent.right
            onClicked: (myMenu.status == DialogStatus.Closed) ? myMenu.open() : myMenu.close()
        }
    }

On a maintenant :

Image non disponible
Après.
Image non disponible
Après.

Les sources jusqu'à cette partie.

VI. Déployer une application sur N9

VI-A. Préparer le périphérique

Sous MeeGo, il faut activer le mode développeur. Pour cela, il faut aller sous Settings, puis Security et Developer Mode. Il suffit de faire glisser le contrôle à côté de Developer Mode pour activer le mode développeur et les outils associés, dont un serveur SSH et une console.

Connecter le périphérique par le câble USB à l'ordinateur (le réseau WiFi pourrait fonctionner). À l'écran des applications, lancer SDK Connection, puis sélectionner USB (ou WLAN pour ceux qui essaient par WiFi). On voit alors quelques détails intéressants, dont une adresse IP et un mot de passe (il correspond à celui de l'utilisateur developer).

Il est alors possible de se connecter en SSH sur le périphérique : ssh developer@192.168.2.15, où l'adresse IP peut être changée pour correspondre à ce qui est affiché. Le mot de passe demandé sera celui affiché à cet écran.

Image personnelle

Si la clé RSA du téléphone change à un moment ou à un autre (mise à jour, par exemple) mais pas son adresse IP, SSH pourrait afficher des messages relativement peu accueillants :

 
Sélectionnez
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that the RSA host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
f7:29:d2:47:22:e9:1b:66:82:f4:5a:7b:36:57:48:fa.
Please contact your system administrator.
Add correct host key in /c/Users/Thibaut/.ssh/known_hosts to get rid of this mes
sage.
Offending key in /c/Users/Thibaut/.ssh/known_hosts:6
RSA host key for 192.168.2.15 has changed and you have requested strict checking
.
Host key verification failed.

Il suffit d'obéir et de supprimer la ligne incriminée, la prochaine connexion demandera à nouveau de valider la clé.

VI-B. Ajouter un nouveau périphérique sous Qt Creator

Avant de déployer une application, il faut que Qt Creator ait une certaine connaissance de l'environnement cible. Ces quelques étapes ne devraient pas varier pour les périphériques compatibles avec MADDE (soit ceux sous Symbian, Maemo ou MeeGo). Il faudra adapter dans la suite l'adresse IP et le mot de passe entrés.

VI-C. Déployer l'application

On configure alors un peu le projet afin de sélectionner la cible de déploiement qui vient d'être ajoutée :

Image non disponible

Il suffit alors de sélectionner la cible Harmattan pour la compilation puis de lancer le processus - Qt Creator se charge du reste : compilation, packaging, déploiement sur le périphérique, même lancement de l'application !

VII. Aller plus loin

Les sources jusqu'à présent sont disponibles.

Jusqu'à présent, on a développé une petite application relativement fonctionnelle. Cependant, ses utilisateurs vont vite revenir et demander de nouvelles fonctionnalités. Avec les bases acquises dans ce tutoriel, on a décidé d'implémenter quelques idées que voici ci-dessous :

On pourrait implémenter une règle supplémentaire (on ne peut pas gagner une partie en étant capot, sachant que les points de la belote-rebelote comptent toujours), mais cela nécessiterait des entrées supplémentaires de la part de l'utilisateur (on peut très bien ne marquer que deux points sur seize dans une manche sans qu'il y ait eu d'annonce). Il vaudrait mieux alors laisser l'application s'occuper entièrement du comptage : on marque les cartes d'une équipe ainsi que les annonces de chaque, on peut ainsi déduire les cartes que l'autre équipe a eues ainsi que les totaux en points associés. La vérification devient aisée, mais l'application n'est plus si simple.

Selon certains, l'application, bien que petite, n'est pas suffisamment rapide à démarrer. De manière plus pragmatique, pour des applications plus grandes, contenant plus d'écrans, il n'est pas imaginable de tous les charger au démarrage, sinon les utilisateurs penseront (à raison !) qu'il y a un problème sérieux. Il faut donc pour cela en différer le chargement. L'article Améliorer les performances de QML peut être un bon point de départ.

Pour une application pleinement fonctionnelle, il faut désactiver le remplissage automatique des scores. Pour une application conforme au style MeeGo, il faut proposer dès le début un écran non vide (par exemple, un petit menu proposant de créer une nouvelle partie et demandant directement d'introduire la première paire de résultats).

Ici, j'aimerais adresser un tout grand merci à toutes les personnes sans qui cet article n'aurait pas pu être ce qu'il est : Louis du Verdier et ness522 côté technique ; Claude Leloup et Erielle côté orthographique.


Error: Invalid write to global property "i"
On pourrait se passer de cet identifiant en définissant le délégué directement dans la vue

  

Copyright © 2012 Thibaut Cuvelier. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.