Dans la plupart des applications KDE, les préférences de l'utilisateur peuvent être changées et sauvegardées. Cela nécessite habituellement :
Ce tutoriel va vous expliquer l'une des façon possible d'arriver à cela facilement en utilisant, selon ma propre opinion, un bon style de programmation. Il va vous expliquer comment créer un objet de configuration (une classe unique), comment lire et écrire les éléments de configuration (aussi appelés préférences) choisis par l'utilisateur et comment concevoir et programmer un dialogue de configuration standard pour KDE.
Ce tutoriel est écrit pour les utilisateurs de KDevelop3 (Gideon). Cependant, si vous aimez développer des programmes KDE sans utiliser une interface de développement ou si vous en utilisez une différente, ce tutoriel peut quand même vous intéresser. Chaque fois que vous verrez du texte dans un cadre bleu, un procédé autre que l'utilisation de KDevelop est expliqué.
Si vous êtes totallement nouveau en programmation KDE, veuillez regarder les tutoriels disponibles à http://developer.kde.org et http://kdevelop.kde.org. Cependant, ce tutoriel va vous expliquer beaucoup de choses sur comment utiliser QT Designer et KDevelop ensemble. Aussi peut-être après l'avoir lu vous en saurez un peu plus sur la programmation avec KDE.
Partie I : Débuter
Dans cette partie, je vais vous expliquer comment générer le
projet. Quelques précisions sur les fichiers seront données et
les fichiers moc qui sont automatiquement générés seront
expliqués.
Partie II : La théorie ennuyante
L'objet principal de configuration sera créé et le concept de
classe unique sera expliqué. Nous discuterons des avantages à
avoir un objet de configuration central et global et créerons le
squelette de base pour cette classe.
Partie III : Lire et écrire les
éléments de configuration
Nous introduisons des propriétés publiques dans l'objet de
configuration. Il y aura quelques informations sur la façon de
documenter le code source et j'expliquerai comment accéder à et
utiliser les fichiers de configuration KDE.
Partie IV : Créer des pages de configuration
Enfin, nous commencerons à concevoir les pages du dialogue de
configuration. Aussi ce chapitre sera-t-il entièrement centré sur
la conception de widgets avec QT Designer et comment dériver des classes
à partir de ceux-ci.
Partie V : Implantation basique du dialogue de
configuration
Il est maintenant temps d'implanter le code de base du dialogue et d'ajouter
les pages de configuration. Nous ne verrons toujours rien mais vous
apprendrez quelles sont les bases essentielles dans la création
d'un dialogue de préférences.
Partie VI : Créer et exécuter le
dialogue
Il est enfin temps d'afficher le dialogue. Nous changerons notre widget
principal d'un label en un bouton et nous créerons notre dialogue
dès que l'utilisateur pressera ce bouton. Aussi dans cette partie, c'est
le concept de signal et de slot de la bibliothèque QT qui sera
expliqué. De plus, nous discuterons des avantages à créer
des dialogues sur demande.
Partie VII : Connecter le dialogue et l'objet de
configuration
Dans cette partie, nous connecterons les données de configuration au
dialogue. Cela signifie que nous implanterons des fonctions qui
transférerons les données du et vers le dialogue. Nous
connecterons d'autres signals et slots et de plus cette partie explique comment
exécuter un dialogue de choix de polices de caractère.
Partie VIII : Implantation des caractéristiques pour
les boutons "Default" et "Apply"
Cette partie finale verra l'ajout des fonctionalités des boutons
"Default" et "Apply". Nous modifierons l'objet de configuration pour avoir une
place centrale pour la configuration par défaut. Et nous
améliorerons notre dialogue pour rentre actif ou non le bouton "Apply"
(juste comme nous espérons le voir fonctionner).
Tout d'abord, nous créons un nouveau projet KDE avec KDevelop :
choisissez une "Simple Application KDE" et remplissez les champs requis (je
suggère le nom "SettingsTutorial" car c'est celui que je vais utiliser
dans les exemples). Lorsque l'assistant a terminé son travail,
démarrez la compilation et voyez si le programme s'exécute comme
souhaité. Vous devez avoir un simple programme KDE "Hello World".
Si vous créez le squelette plus complexe "KDE Application Framework"
vous économiserez du travail mais ce tutoriel va détailler les
choses...
Pas de KDevelop : comme créer tout le projet à partir de rien prendrait trop de temps, je vous recommande de prendre le fichier compressé de la fin de la Partie II et de débuter avec cela.
L'assistant génère trois fichiers de code source :
| main.cpp | programme principal, crée l'objet application et l'exécute | |
| settingstutorial.h | déclaration de la fenêtre principale de l'application | |
| settingstutorial.cpp | définition de la fenêtre principale |
Veuillez ignorer le fichier main.cpp pour l'instant (dans ce tutoriel ce n'est pas important) et regardons les deux autres fichiers :
#ifndef _SETTINGSTUTORIAL_H_ #define _SETTINGSTUTORIAL_H_ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <kmainwindow.h> class SettingsTutorial : public KMainWindow { Q_OBJECT public: SettingsTutorial(); virtual ~SettingsTutorial(); }; #endif // _SETTINGSTUTORIAL_H_
#include "settingstutorial.h" #include "settingstutorial.moc" #include <qlabel.h> #include <kmainwindow.h> #include <klocale.h> SettingsTutorial::SettingsTutorial() : KMainWindow( 0, "SettingsTutorial" ) { setXMLFile("settingstutorialui.rc"); new QLabel( "Hello World", this, "hello label" ); } SettingsTutorial::~SettingsTutorial() { }
J'ai supprimé les commentaires par défaut mais, hormis cela, il devrait être semblable à celui que vous avez créé.
La fenêtre principale de notre dialogue de configuration est
dérivée de la classe KMainWindow qui nous fournit un nombre
intéressant de caractéristiques pour un widget principal (voir la
documentation de la bibliothèque KDE).
Les deux lignes dans le
constructeur de la classe PrefDialog ne font rien de spécial : la
première, 'setXMLFile()' peut être supprimée car nous
n'avons pas de fichier .rc pour la création de l'interface utilisateur
(cela sera expliqué dans un tutoriel différent). Et la seconde
crée simplement le QLabel qui affiche le texte "Hello World" (veuillez
vous référer à la documentation de la classe QLabel pour
comprendre les arguments du constructeur du QLabel).
Une courte note à propos de la ligne concernant le fichier moc. Les fichiers MOC sont des fichiers qui sont générés automatiquement et qui contiennent toutes les fonctions et les déclarations nécessaires à la compilation des macros Object. Donc chaque fois que vous créez une classe qui dérive de QObject (ou de tout enfant de QObject) vous devez vous rappeler qu'un fichier moc sera créé et que le code a besoin d'être compilé ! Comme je l'ai dit, le fichier moc contient des déclarations additionnelles et des définitions de code. Donc inclure ce fichier moc dans le fichier .cpp signifie simplement que le code du fichier moc sera compilé avec le code source des autres widgets ce qui est parfait. Si vous n'ajoutez pas la ligne qui inclut le fichier moc dans le fichier .cpp, alors KDevelop (ou plutôt les scripts autoconf/automake qui sont inclus) va générer et compiler un fichier .moc.cpp pour vous. Cela prendra plus de temps de compilation. C'est pourquoi je recommande (et c'est la pratique dans le code de KDE) d'inclure le fichier moc juste après le fichier en-tête.
C'est tout pour la première partie. Nous avons maintenant le cadre de notre application.
L'un de nos objectifs principaux est d'accéder facilement aux
éléments de configuration définis pas l'utilisateur. Ce
que nous pourrions faire serait de mettre tous les éléments de
configuration dans une classe et faire accéder les membres de cette
classe par tous les widgets et objets du programme. Imaginez que nous
créions une telle classe et l'appelions Configuration. Il ne doit y
avoir qu'un et un seul objet de configuration dans tout le programme et il doit
être disponible durant toute la vie du programme (depuis le tout premier
objet créé jusqu'au tout dernier objet qui est détruit).
Pourquoi avons-nous besoin de cela ? Lorsque nous initialisons
l'application, nous allons probablement créer beaucoup d'objets dont
certains dépendent déjà de la configuration de
l'utilisateur ! Aussi devons-nous nous assurer qu'il y a toujours une
instance et une seule de l'objet configuration qui est disponible.
Comment pouvons-nous faire cela ? Nous créons simplement une
classe unique (vous pouvez en lire plus là-dessus dans l'un des livres
"Effective C++"de Scott Meyers). C'est aussi simple que cela : nous ne
laissons pas la possibilité à l'utilisateur de créer des
instances de la classe ou de créer des copies ! Et nous faisons
cela en rendant le constructeur par défaut et la copie du
constructeur privées.
Mais - allez-vous demander - comment alors peut-on créer une telle instance de cette classe de configuration ? Une variable globale est un mauvais choix car le standard C++ ne requiert pas un certain ordre dans l'initialisation des variables globales. Aussi pouvez-vous finir en utilisant l'objet de configuration AVANT qu'il ne soit vraiment construit.Cependant, le standard C++ requiert que les variables statiques dans les fonctions soient créées au premier appel de la fonction. Aussi avons-nous seulement besoin de créer une fonction qui retourne une référence à un objet de configuration statique à l'intérieur de la fonction et nous serons assurés que cet objet existe chaque fois que nous avons besoin de lui.
Ceci est contenu dans les quelques lignes suivantes :
#ifndef CONFIGURATION_H #define CONFIGURATION_H class Configuration { public: // ajoute des membres publics (les préférences de l'utilisateur) private: Configuration(); Configuration(const Configuration&); // permet à cette fonction de créer une instance friend Configuration& Config(); }; // utilise cette fonction pour accéder aux préférences Configuration& Config(); #endif // CONFIGURATION_H
#include "configuration.h" Configuration::Configuration() { // TODO: Initialisation des préférences de l'utilisateur }; Configuration& Config() { static Configuration conf; return conf; };
Chaque fois que la fonction globale Config() est d'abord appelée, l'objet configuration sera créé et une référence à cet objet sera retournée. À partir de maintenant, vous pouvez utiliser les éléments de configuration dans toute partie du programme, incluez simplement l'en-tête de configuration et retirez une référence à cet objet via Config(). Si vous avez une variable membre m_myButtonText dans la classe de configuration, vous pouvez y accéder par exemple en utilisant Config().m_myButtonText=....
Avant que cette partie du tutoriel ne soit finie, voici une courte remarque sur la façon d'ajouter de nouvelles classes dans un projet KDevelop. Il y a deux façons différentes d'ajouter une nouvelle classe (qui consiste en son en-tête et le code source). La première est d'utiliser l'assistant de "nouvelle classe", ce qui est à la fois rapide et sécure (et facile). L'alternative est de créer les deux fichiers (<nouvelleclasse>.h et <nouvelleclasse>.cpp) par vous-même et d'utiliser le gestionnaire automake pour les ajouter au projet : ouvrez le gestionnaire automake, sélectionnez le projet en cours (fenêtre la plus basse, "settingstutorial (programme dans bin)" et choisissez "ajouter un fichier existant" dans le menu contextuel).
Si vous n'utilisez pas KDevelop vous devrez créer l'en-tête et le fichier source vous-même (ce qui est assez simple) puis vous devrez modifier le fichier makefile.am dans le sous-répertoire src. Ajoutez simplement le fichier source cpp configuration.cpp sur la ligne comprenant les fichiers sources. Cette ligne doit ressembler à cela :
settingstutorial_SOURCES = main.cpp settingstutorial.cpp configuration.cpp
Au cas où vous voudriez vous faciliter le travail (bien que je recommande de créer le projet et ses fichiers par vous-même pour mieux le comprendre) vous pouvez télécharger le projet dans son état actuel ici : settingstutorial-01.tar.gz. Veuillez noter qu'après avoir ouvert le projet dans KDevelop, vous devez lancer "automake & friends" d'abord, "configure" ensuite et enfin vous pouvez compiler le projet.
Si vous n'utilisez pas KDevelop, tapez simplement les lignes suivantes :
make -f Makefile.cvs
./configure
make
À CE POINT, NOUS AVONS REMPLI NOTRE OBJECTIF #1!
Comme vous devez probablement déjà le savoir, la plupart des programmes KDE permettent de sauvegarder les préférences de l'utilisateur. Ces préférences sont sauvegardées dans les fichiers de configuration de l'application que vous trouverez probablement dans ~/.kde/share/config/<application-rc-file>.
La bibliothèque KDE fournit déjà toutes les fonctions nécessaires pour lire et écrire vos préférences dans le fichier de configuration de l'application. Pour illustrer cela, nous créerons quelques propriétés simples dans l'objet configuration à savoir m_font, m_text et m_textColor. Veuillez noter que j'utilise la convention de nom habituelle pour les variables membres qui peut être résumée dans les règles suivantes :
Maintenant, comme je l'ai dit, nous allons ajouter des propriétés publiques dans l'objet de configuration (vous pouvez aussi les rendre privées et écrire des fonctions qui les accèdent mais, pour plus de facilité, je vais simplement utiliser des variables publiques). Et nous aurons besoin de deux fonctions qui vont lire et écrire les éléments de configuration. Le fichier de déclaration pour notre classe Configuration devient :
#ifndef CONFIGURATION_H #define CONFIGURATION_H #include <qstring.h> #include <qfont.h> #include <qcolor.h> /// Ceci est le seul et unique objet configuration. /// Les fonctions membres read() et write() peuvent être utilisées pour charger et sauvegarder /// les propriétés dans le fichier de configuration de l'application. class Configuration { public: /// Lit les données depuis le fichier de configuration de l'application. /// Si une propriété n'existe pas déjà dans le fichier de configuration, elle sera /// fixée à sa valeur pas défaut. void read(); /// Écrit les données de configuration dans le fichier de configuration de l'application. void write() const; QString m_text; ///< Le texte qui doit être affiché dans le widget principal. QFont m_font; ///< La police de caractère utilisée pour le texte. QColor m_textColor; ///< La couleur du texte utilisée dans le widget principal. private: Configuration(); Configuration(const Configuration&); friend Configuration& Config(); }; /// Retourne une référence à l'objet configuration de l'application. Configuration& Config(); #endif // CONFIGURATION_H
Il y a plusieurs choses nouvelles dans ce fichier. Tout d'abord, trois fichiers en-tête QT ont été inclus, un pour chaque classe QT que nous utilisons. Si vous ne connaissez pas ces classes, veuillez regarder dans la documentation de classes de QT.
Puis nous avons les nouvelles variables membres mentionnées ci-dessus
et les deux fonctions membres read() et write().
Rappelez-vous de rendre constantes toutes les fonctions membres qui ne changent pas l'é'tat interne de la classe !
Vous avez probablement noté que je "perds" beaucoup d'espace disque préciux en incluant des commentaires dans les fichiers source :-). De plus, ces commentaires ne sont même pas écrits comme des commentaires normaux de C++ avec deux barres obliques mais j'utilise trois barres obliques (ou alors, dans le cas des variables membres j'utilise même trois barres obliques et un <). Ceci est une façon spéciale de documenter le code source pour permettre à des générateurs de documentation comme Doxygen de produire une documentation nette et informative de votre interface de programmation. Si vous désirez savoir à quoi ressemble une telle documentation, veuillez télécharger settingstutorial-01html.tar.gz et le regarder.
Nous devons définir nos fonctions membres read() et write(). En voici le code...
#include "configuration.h" #include <kapplication.h> // pour 'kapp' #include <kconfig.h> // pour KConfig Configuration::Configuration() { read(); // lit les paramètres de configuration ou les fixe à la valeur par défaut }; void Configuration::read() { KConfig *conf=kapp->config(); // lit les options générales conf->setGroup("General"); m_text = conf->readEntry("text", "Hello World"); // lit les options de style conf->setGroup("Style"); QFont defaultFont = QFont("Helvetica"); m_font = conf->readFontEntry("font", &defaultFont); QColor defaultColor(0,0,50); m_textColor = conf->readColorEntry("textColor", &defaultColor); }; void Configuration::write() const { KConfig *conf=kapp->config(); // écrit les options générales conf->setGroup("General"); conf->writeEntry("text", m_text); // écrit les options de style conf->setGroup("Style"); conf->writeEntry("font", m_font); conf->writeEntry("textColor", m_textColor); }; Configuration& Config() { static Configuration conf; return conf; };
Il y a encore deux nouveaux fichiers, soit kapplication.h et kconfig.h. Le premier nous fournit le pointeur global vers l'objet application kapp. Le second contient la déclaration de classe de KConfig.
Nous ajoutons une seule ligne dans le constructeur qui appelle simplement la fonction membre read() et assure que lors de la création, nous avons des valeurs correctes dans notre objet configuration.
Regardons maintenant la fonction membre write() (car elle est très facile à comprendre).
KConfig *conf=kapp->config();
Cette ligne prend un pointeur de la classe de configuration de
l'application qui fournit l'accès au fichier de configuration. C'est tout
ce que nous avons besoin de faire. Tout ce qui concerne la localisation,
l'ouverture, la fermeture du fichier de configuration est exécuté
par les bibliothèques KDE. C'est ce que j'appelle confortable :-) conf->setGroup("General");Maintenant, le groupe General est fixé et toutes les clés suivantes seront écrites dans ce groupe de propriétés.
Les clés dans un même groupe doivent bien sûr être différentes et uniques !
La commande pour écrire dans un groupe est aussi simple que cela :
conf->writeEntry("font", m_font);
La fonction writeEntry() convertit automatiquement la valeur en une
chaîne et sauvegarde la clé et sa valeur dans le fichier de
configuration. Le reste de notre fonction write() devrait être
assez simple à comprendre. Nous fixons un autre groupe et écrivons
nos entrées dedans. Le nom des clés est à votre choix
mais vous devez leur donner un nom similaire à celui de la variable. Regardons maintenant la fonction read(). Tout d'abord, nous prenons le pointeur vers l'objet application de configuration. Puis nous fixons le groupe duquel nous voulons lire la valeur (celui-ci est le même que dans la fonction write()). Lire une simple chaîne de caractères est vraiment simple comme vous pouvez le voir ici :
m_text = conf->readEntry("text", "Hello World");
Le premier paramètre de la fonction readEntry() est à nouveau la clé et le second
paramètre est la valeur par défaut, dans notre cas, nous fixons le
défaut comme étant la chaîne 'Hello World'. Lorsque la
clé n'est pas trouvée ou que le fichier de configuration n'existe
pas encore, cette valeur par défaut est alors retournée.
Les autres propriétés requièrent un peu de conversion.
Comme les valeurs sont écrites en texte normal (sans aucune identification
de type), nous devons spécifier le type de retour en choisissant la
fonction "read" correcte. Pour presque tous les types de données C++ ou
QT, il existe une fonction readEntry spécifique. Regardez dans la
documentation des classes KDE/QT pour en avoir une liste.
Pour nos objectifs, nous devons simplement récupérer la police de
caractères en utilisant readFontEntry() et pour la couleur nous
utilisons la fonction readColorEntry(). Chacune de ces fonctions de
lecture nécessite un pointeur vers un objet qui contient une valeur par
défaut. Donc nous créons un objet temporaire QFont et QColor et
nous passons leur adresse aux fonctions respectives.
En tout cas, l'objet de configuration contient à la fin de la fonction de lecture read() soit les valeurs précédemment sauvegardées par l'utilisateur, soit les valeurs par défaut. Et comme nous appelons notre fonction read() dans le constructeur, nous pouvons être sûr que notre objet de configuration contient des valeurs valables dès le tout début.
À CE POINT, NOUS AVONS REMPLI NOTRE OBJECTIF #2 !
Téléchargement du projet actuel : settingstutorial-02.tar.gz
Un dialogue de préférences typique de KDE affiche des icônes sur la gauche, une page de configuration (active) sur la droite et quelques boutons en bas. Un exemple d'un tel dialogue est celui de KTouch :
![]() |
L'image suivante montre le widget de la page de configuration correspondant (créé avec QT designer) :
![]() |
Le widget dans QT designer est montré avec une barre de titre mais dès que le widget est intégré dans le dialogue de configuration, cette barre de titre disparaît et seul le contenu du widget est affiché.
Ces pages de configuration ont été créées avant le dialogue, donc c'est ce que nous allons faire maintenant. Dans KDevelop ouvrez la fenêtre "Nouveau fichier" et sélectionnez "Widget (un nouveau widget)". Le dialogue vous demande un nom de fichier pour le nouveau widget et vous allez entrer "prefgenerallayout" SANS le .ui (cela sera ajouté automatiqement). Quelques remarques sur le nom de ce fichier.
Mais tout d'abord, ajoutons le fichier .ui au projet. Le dialogue suivant s'affiche et vous devez simplement confirmer :
![]() |
Après avoir cliqué sur OK, QT designer va ouvrir le fichier et vous voici avec un widget vide.
Vous pouvez tout simplement ouvrir QT designer, créer un nouveau widget et le sauvegarder dans le sous-répertoire src sous le nom prefgenerallayout.ui. Puis ajouter à nouveau simplement ce fichier dans la ligne des fichiers source de la makefile.am et c'est terminé. C'est en fait ce que KDevelop fait pour vous dans ce cas.
Commençons par changer quelques propriétés du widget lui-même :
Vous pouvez maintenant commencer à ajouter des widgets dans cette page de configuration générale. Dans notre exemple, qui est simple, nous avons du texte à afficher aussi pourrait-on utiliser un widget KLineEdit pour le texte lui-même et un QLabel pour afficher notre information. Et faisons aussi un joli cadre autour de cela... Avant d'organiser la disposition pour le repositionnement, notre page devrait ressembler à :
![]() |
Quelques commentaires sur le nom des widgets :
Créons maintenant le cadre qui va permettre le repositionnement. Pour avoir des informations générales sur cela, je vous recommande de regarder le manuel de QT où la façon de disposer les widgets est expliquée en détail. Pour l'instant, nous devons décider si le cadre va s'agrandir lorsque les widgets s'agrandiront ou si le cadre doit rester à une taille verticale minimum et l'espace sous le cadre doit s'agrandir : en décidant ceci, nous allons savoir où placer le ressort vertical. J'ai décidé de placer le ressort sous le cadre pour que le cadre garde une taille verticale minimum. La procédure pour créer le positionnement final est la suivante :
Amusez-vous un peu avec ces différents paramètres de positionnement et après quelque temps, vous serez capable de créer facilement des dispositions de widgets complexes (utilisez la fonction de prévisualisation de QT Designer).
Il reste encore une chose à faire. Quand vous ajoutez les positionnements pour les widgets, vous noterez que deux propriétés additionnelles apparaissent dans la fenêtre des propriétés (sélectionnez le widget en cliquant sur de l'espace vide hors du cadre). Vous devez fixer la propriété layoutMargin sur 0 sinon le widget aure une bordure assez laide tout autour et cela n'ira pas avec notre dialogue. La page des préférences une fois terminée doit ressembler à cela :
![]() |
Sauvegarder maintenant le widget et fermer QT designer et dans le cas où vous avez plusieurs instances de QT designer ouvertes avec le même widget, faites attention de sauvegarder celui que vous désirez !
De retour dans KDevelop, lancez simplement make et notez que le fichier en-tête prefgenerallayout.h est automatiquement généré à partir du fichier .ui (en utilisant le programme uic) et que le fichier nouvellement créé prefgenerallayout.cpp est compilé.
Lorsque vous lancez 'make' après avoir ajouté le fichier . ui dans automake.am, il génèrera automatiquement un nouvel en-tête et un fichier source qui sera compilé. Exactement comme lorsque vous lancez "compiler" dans KDevelop...
Pour vous exercer (et aussi parce que nous en avons besoin), ajoutez une autre page de configuration qui aura pour nom de fichier prefstylelayout.ui et concevez le widget de configuration de cette page (donnez-lui le nom de PrefStyleLayout) de façon à ce qu'il soit semblable à :
![]() |
Les trois widgets que nous utiliserons plus tard sont :
Vous pouvez maintenant essayer de concevoir ce widget tout seul (ce qui est mieux pour apprendre) ou bien suivre ce simple script (ce qui vous fait gagner du temps). Faites votre choix, l'essentiel est que le widget soit conçu :-)
Ok, maintenant que vous avez créé la deuxième page, vous pouvez recompiler le projet (il devrait y avoir un fichier prefstylelayout.h à la fin)
Avant d'en arriver à la partie où vous verrez quelque chose, vous devez créer deux nouvelles classes - PrefGeneral et PrefStyle, toutes deux sont des classes enfants des widgets que nous venons de créer avec QT designer. Vous pouvez faire cela en utilisant l'assistant de création de nouvelle classe. Dans KDevelop, ouvrez l'assistant de création de nouvelle classe
![]() |
et créez une nouvelle classe PrefGeneral (entrez ceci comme nom de classe dans le champ de nom de classe) qui sera un enfant de la classe QWidget (cochez cette case) et qui est dérivée de PrefGeneralLayout (entrez ce nom dans le champ "Classe de base").
![]() |
L'assistant de classe va générer deux nouveaux fichiers : prefgeneral.h et prefgeneral.cpp.
#ifndef PREFGENERAL_H #define PREFGENERAL_H #include <qwidget.h> #include <prefgenerallayout.h> /// Ceci est l'implantation de la page des options générales de notre dialogue de préférences. class PrefGeneral : public PrefGeneralLayout { Q_OBJECT public: /// Constructeur par défaut PrefGeneral(QWidget *parent, const char *name=0, WFlags f=0); }; #endif // PREFGENERAL_H
#include "prefgeneral.h" #include "prefgeneral.moc" PrefGeneral::PrefGeneral(QWidget *parent, const char *name, WFlags f) : PrefGeneralLayout(parent, name, f) { // TODO: initialisation des widgets }
Si vous n'utilisez pas KDevelop, créez simplement ces fichiers de vous-même (et sauvegardez-les dans le sous-répertoire src). Comme d'habitude, vous devrez ajouter le fichier cpp au fichier makefile.am et il devrait compiler correctement.
Les fichiers ci-dessus sont légèrement différents de ceux générés par l'assistant de nouvelle classe car j'ai fait quelques petits changements :
Il faut faire la même chose pour PrefStyleLayout, donc nous devons créer une classe enfant du nom de PrefStyle. Vous pouvez faire ceci par vous-même ou utiliser le fichier compressé du projet actuel : settingstutorial-03.tar.gz
Maintenant, après tout ce travail, il n'y a toujours rien de plus qu'un stupide texte "Hello World" à regarder...
Voilà la vie d'un programmeur - vous pouvez travailler pendant des heures et des jours et l'utilisateur ne verra pas la différence ... et lorsque vous ne voyez rien de nouveau c'est que rien de nouveau n'a été fait. C'est "logique" du point de vue de l'utilisateur ! Mais peut-être choisissez-vous de vous concentrer sur le code de nouvelles interfaces "tape à l'oeil" comme ces personnes de Redmond le font toujours. Dans ce cas, au moins, l'utilisateur "verra" l'"amélioration"... hehe, bien, cela n'a rien à voir avec ce tutoriel, mais c'était juste pour souligner cela :-)
Il est temps d'implanter le dialogue. Nous avons à nouveau besoin d'une nouvelle classe et cette fois nous l'appelons PrefDialog et elle hérite de KDialogBase. Ouvrez donc l'assistant de nouvelle classe et créez-là. Vous devez obtenir les deux fichiers suivants (je les ai à nouveau modifiés un peu mais vous devriez toujours les reconnaître ...) :
#ifndef PREFDIALOG_H #define PREFDIALOG_H #include <qwidget.h> #include <kdialogbase.h> class PrefDialog : public KDialogBase { Q_OBJECT public: PrefDialog(QWidget *parent, const char *name=0, WFlags f=0); }; #endif // PREFDIALOG_H
#include "prefdialog.h" #include "prefdialog.moc" PrefDialog::PrefDialog(QWidget *parent, const char *name, WFlags f) : KDialogBase(parent, name, f) { }
À nouveau, sans KDevelop, vous devez créer ces fichiers par vous-même (coupez et collez-les) et vous devez aussi modifier le fichier automake.am. Je suppose que vous savez faire cela tout seul maintenant, aussi je ne vous le dirai plus à l'avenir :-)
Nous devons créer nos pages de configuration lorsque nous mettons le dialogue en place. Pour accéder à ces pages plus tard plus facilement, nous ajoutons des pointeurs vers ces pages dans les variables membres de la classe du dialogue. Aussi nous devons ajouter
Le nouveau fichier prefdialog.h est comme suit :
#ifndef PREFDIALOG_H #define PREFDIALOG_H #include <qwidget.h> #include <kdialogbase.h> class PrefGeneral; class PrefStyle; /// Le dialogue des préférences class PrefDialog : public KDialogBase { Q_OBJECT public: /// Constructeur PrefDialog(QWidget *parent, const char *name=0, WFlags f=0); private: PrefGeneral *m_prefGeneral; PrefStyle *m_prefStyle; }; #endif // PREFDIALOG_H
Utiliser les prototypes de classe aide à garder les dépendances de compilation plus basses. Imaginez ce qui arrive lorsque vous faites des changements à la disposition de la page "General" : d'abord, tous les utilisateurs de prefgenerallayout.h (ce qui veut dire tous les fichiers qui incluent prefgenerallayout.h) doivent être recompilés. Dans notre cas, prefgeneral.h inclut ce fichier donc tout utilisateur de prefgeneral.h doit être recompilé. Ce sera prefgeneral.cpp et prefdialog.cpp. Si nous incluons prefgeneral.h dans l'en-tête prefdialog.h, alors tous les utilisateurs de prefdialog.h doivent aussi être recompilés. Donc, lorsque nous utilisons des prototypes de classes au lieu d'inclure l'en-tête, nous réduisons le temps de compilation par au moins un fichier (car il est sûr que prefdialog.h sera utilisé par au moins un autre fichier).
Regardons maintenant les modifications que nous avons faites au fichier source prefdialog.cpp.
#include <qlayout.h> // pour QVBoxLayout #include <qlabel.h> // pour QLabel #include <qframe.h> // pour QFrame #include <kcolorbutton.h> // pour KColorButton #include <kpushbutton.h> // pour KPushButton #include <klocale.h> // pour i18n() #include <kiconloader.h> // pour KIconLoader #include <kglobal.h> // pour KGlobal #include "prefdialog.h" // classe PrefDialog #include "prefdialog.moc" #include "prefgeneral.h" // classe PrefGeneral #include "prefstyle.h" // classe PrefStyle PrefDialog::PrefDialog(QWidget *parent, const char *name, WFlags f) : KDialogBase(IconList, i18n("Preferences"), Default|Ok|Apply|Cancel, Ok, parent, name, f) { // ajout de la page "General options" QFrame *frame = addPage(i18n("General"), i18n("General options"), KGlobal::iconLoader()->loadIcon("kfm",KIcon::Panel,0,false) ); QVBoxLayout *frameLayout = new QVBoxLayout( frame, 0, 0 ); m_prefGeneral = new PrefGeneral(frame); frameLayout->addWidget(m_prefGeneral); // ajout de la page "Style settings" frame = addPage(i18n("Style"), i18n("Style settings"), KGlobal::iconLoader()->loadIcon("style",KIcon::Panel,0,false) ); frameLayout = new QVBoxLayout( frame, 0, 0 ); m_prefStyle = new PrefStyle(frame); frameLayout->addWidget(m_prefStyle); }
Donc, tout d'abord, nous incluons beaucoup de fichiers, principalement les fichiers en-têtes pour les widgets que nous avons dans les pages de notre dialogue. Puis nous avons besoin de certaines classes pour la génération des pages du dialogue (ceci est expliqué dans les paragraphes suivants).
La classe KDialogBase sert de prototype pour beaucoup de types de dialogues différents. Comme nous voulons un dialogue avec une liste d'icônes et des boutons par défaut, nous utilisons un constructeur spécial pour la classe de base. Les paramètres pour ce constructeur sont les suivants :
Maintenant que le dialogue lui-même est créé, nous devons ajouter les pages de configuration. Ce processus nécessite trois étapes. Voyons la première :
QFrame *frame = addPage(i18n("General"), i18n("General options"), KGlobal::iconLoader()->loadIcon("kfm",KIcon::Panel,0,false) );
addPage() est une fonction membre de KDialogBase et admet les paramètres suivants :
Les deux premiers ne posent pas de problème mais d'où allons-nous sortir l'icône ? Comme d'habitude, KDE vient à la rescousse. Du fait des différentes configurations possibles pour KDE, les icônes peuvent être installées dans différents répertoires. La classe KIconLoader nous aide à localiser les icônes dont nous avons besoin. Nous pouvons demander au chargeur global d'icônes dans l'espace de nom KGlobal de charger l'icône ayant tel nom pour nous. Les paramètres importants de la fonction loadIcon() sont le nom de l'icône ainsi que son type, celui-ci correspondant à une certaine taille d'icône. Le chargeur d'icônes essaie de localiser l'icône choisie dans les différents répertoires aui contiennent des icônes. Habituellement, pour vos propres programmes, vous voudrez fournir vos propres icônes qui vont aller dans le répertoire d'installation de l'application. Dans ce bref tutoriel, nous "empruntons" simplement deux icônes courantes qui doivent être disponibles dans toute installation KDE. Lorsque l'icône est trouvée, nous ajoutons la page et passons à l'étape 2.
QVBoxLayout *frameLayout = new QVBoxLayout( frame, 0, 0 );
La seconde étape va être de créer un nouveau positionnnement vertical pour mettre notre page de configuration. Ce positionnement sera "top-level layout" de la page que nous venons juste d'ajouter.
m_prefGeneral = new PrefGeneral(frame);
Enfin, mais non le moindre, nous créons une instance de la page de configuration "General options" et nous l'ajoutons au positionnement. Nous créons tous les objets avec new, mais nous ne les libérons jamais avec delete. Pourquoi pas ? Dès qu'un QObject est créé comme un enfant (ce qui veut dire "est possédé par") d'un autre QObject, ce "parent" prend soin d'effacer la mémoire de ses "enfants". Donc la règle pour libérer de la mémoire est très simple : chaque fois que vous créer un objet via new et que l'objet appartiendra à un QObject, il sera supprimé automatiquement, sinon vous devrez le supprimer vous-même.
Dans notre cas, l'objet QFrame est possédé par le dialogue lui-même, l'objet QVBoxLayout est possédé par le cadre et l'objet PrefGeneral object est possédé par le positionnement. Ils seront tous supprimés par leur parent lorsque le dialogue sera supprimé.
Nous faisons la même chose avec la seconde page et notre dialogue est complet. Vous devriez être capables de compiler les fichiers maintenant. Cependant, vous ne verrez toujours que le texte "Hello world" dans la fenêtre. Nous avons seulement besoin d'un moyen pour exécuter le dialogue depuis la fenêtre principale ...
Nous revenons finalement au code source de la fenêtre principale que nous avons déjà vu dans la première partie. Voici l'en-tête modifié settingstutorial.h :
#ifndef _SETTINGSTUTORIAL_H_ #define _SETTINGSTUTORIAL_H_ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <kmainwindow.h> class QMouseEvent; class KPushButton; class PrefDialog; /// Ceci est la fenêtre principale du programme du tutoriel. class SettingsTutorial : public KMainWindow { Q_OBJECT public: /// Constructeur par défaut SettingsTutorial(); public slots: /// Exécute le dialogue de préférences void executePreferencesDlg(); /// Met à jour les widgets pour que les nouveaux paramètres prennent effet. void applyPreferences(); private: KPushButton *m_theTextButton; PrefDialog *m_prefDialog; }; #endif // _SETTINGSTUTORIAL_H_
Si nous le comparons avec le listing précédent dans la Partie I, nous avons changé beaucoup de choses. Au début, le destructeur n'est pas là (nous n'en avons pas besoin). Puis nous avons ajouté un slot public executePreferencesDlg() qui exécute le dialogue de préférences. Ensuite nous avons ajouté un autre slot public applyPreferences(). La seule différence entre ces slots publics et des fonctions membres publiques est que les slots publics peuvent être connectés à des signals. J'expliquerai le mécanisme des signals et des slots un peu plus tard. Enfin, nous avons ajouté des variables membres privées : une est pour le dialogue de préférences et une pour le bouton. Puisque nous avons besoin de presser sur quelque chose pour que le dialogue s'affiche, nous allons remplacer l'étiquette "Hello world" par un bouton.
Regardons le code. Voici le nouveau fichier settingstutorial.cpp :
#include <qevent.h> // pour QMouseEvent #include <qlayout.h> // pour QVBoxLayout #include <klocale.h> // pour i18n() #include <kpushbutton.h> // pour KPushButton #include "settingstutorial.h" #include "settingstutorial.moc" #include "prefdialog.h" // classe PrefDialog #include "configuration.h" // classe Configuration et Config() SettingsTutorial::SettingsTutorial() : KMainWindow( 0, i18n("SettingsTutorial") ), m_prefDialog(0) { m_theTextButton = new KPushButton( this ); setCentralWidget(m_theTextButton); applyPreferences(); connect( m_theTextButton, SIGNAL( clicked() ), this, SLOT( executePreferencesDlg() ) ); } void SettingsTutorial::executePreferencesDlg() { // crée le dialogue à la demande if (m_prefDialog==0) m_prefDialog=new PrefDialog(this); if (m_prefDialog->exec()==QDialog::Accepted) applyPreferences(); }; void SettingsTutorial::applyPreferences() { m_theTextButton->setText( Config().m_text ); m_theTextButton->setPaletteForegroundColor( Config().m_textColor ); m_theTextButton->setFont( Config().m_font ); };
Je pense que nous devons discuter de ceci plus en détails. Le commentaire à côté des fichiers en-têtes montre quelle classe est inclue dans quel fichier. À la fois les en-têtes QT et KDE ont une très simple convention de nom. Habituellement, c'est juste le nom de classe en minuscules avec .h à la fin (comme les en-têtes que nous avons créés dans ce tutoriel).
Regardons maintenant le constructeur. L'ancien contenu a disparu et nous avons quelque chose de nouveau ici. Mais avez-vous remarqué un paramètre additionnel dans la liste d'initialisation ? Nous initialisons le pointeur au dialogue de préférences avec 0. Hé bien, nous reviendrons à cela plus tard. Regardons le code du constructeur :
m_theTextButton = new KPushButton( this );Avec cette ligne, nous créons simplement un nouveau KPushButton qui est possédé par notre fenêtre principale (rappelez-vous notre discussion dans la Partie III quand nous n'étions pas sûr s'il fallait ou non supprimer un objet).
setCentralWidget(m_theTextButton);
Ici nous définissons le bouton comme étant notre widget central. Dans un programme normal, le widget central d'une application sera vraisemblablement plus complexe, cela pourrait être au moins un éditeur de texte ou un widget de visualisation HTML. Cependant, pour le propos de ce tutoriel, un bouton est assez bon. Comme il est le widget central, le KPushButton va remplir tout le contenu de notre fenêtre principale.
Ensuite nous appelons notre fonction/slot membre applyPreferences() qui définit trois propriétés pour le bouton. Ce sont ces propriétés que nous voulons changer dans le dialogue de configuration.
Ici vous voyez l'importance de la conception de notre objet configuration. Il est très robuste et dès le tout-début, nous pouvons utiliser des fonctions qui appellent des donnés de configuration sans avoir à s'inquiéter si les valeurs sont valides ou non. Donc en fait, nous évitons de dupliquer du code source et nous pouvons utiliser la fonction membre applyPreferences() tout de suite.
Et enfin, nous "connectons" quelque chose. Laissez-moi vous expliquer un peu les signals et les slots dans QT :
Chaque fois qu'un utilisateur effectue une action sur un widget, comme presser un bouton ou déplacer un curseur, le widget avec lequel l'utilisateur interagit émet un signal. Ce signal est différent pour chaque widget et le mieux est de regarder la documentation de chaque classe QT/KDE pour connaître les signals pour un widget. Un signal peut aussi passer un paramètre ! Par exemple une QSlider va émettre un signal valueChanged() et passer une valeur entière (integer) qui est la nouvelle valeur de la règle coulissante. Cela vous évite de regarder le widget et de lire la nouvelle valeur du widget (les paramètres sont très pratiques aussi dans d'autres situations mais ce n'est pas pour ce tutoriel).
Un signal peut être lié à un slot. De façon très simple, un slot est une fonction membre qui admet le même paramètre que celui que passe le signal. Donc dans le cas de QSlider, nous pouvons créer un slot sliderValueChanged(int newValue) et le connecter au signal valueChanged() de la règle coulissante. Le code source correspondant va ressembler à :
connect(mySlider, SIGNAL( valueChanged(int) ), targetWidget, SLOT( sliderValueChanged(int) ) );mySlider et targetWidet sont des pointeurs tandis que sliderValueChanged(int) est un slot public du widget cible targetWidget. Maintenant, après que cette connexion ait été faite, chaque signal valueChanged(int) de la règle coulissante 'mySlider' va résulter en un appel à targetWidget->sliderValueChanged(int) avec la valeur en paramètre qui vient du signal. C'est aussi simple que cela.
Appliquons ceci à notre dialogue. Nous avons un widget interactif, le KPushButton. Ce widget émet le signal clicked() quand l'utilisateur clique dessus. Et nous avons le slot executePreferencesDlg() qui est disponible. Nous avons donc simplement besoin de connecter le signal au slot :
connect( m_theTextButton, SIGNAL( clicked() ), this, SLOT( executePreferencesDlg() ) );
La fonction connect() a quatre paramètres :
Regardons un peu la fonction membre executePreferencesDlg() :
if (m_prefDialog==0) m_prefDialog=new PrefDialog(this);Ceci peut paraître étrange. Pourquoi ne créons-nous pas le dialogue chaque fois que la fonction est appelée (ce qui signifierait comme un objet local qui serait détruit chaque fois que l'on quitte la fonction) ? C'est juste une question de performance. Ce n'est pas très important si nous créons le dialogue locallement mais de cette façon le dialogue ne sera créé qu'une fois lorsque le programme démarre. Nous pouvons créer le dialogue dans le constructeur de notre fenêtre principale, mais ce n'est pas chaque fois que le programme est lancé que l'utilisateur va exécuter le dialogue. Donc nous perdons un temps précieux durant le lancement du programme. En utilisant ce système de "création à la demande", le dialogue ne sera créé qu'une fois et SEULEMENT s'il sera utilisé.
Une règle d'or pour la création de dialogue pourrait être : si le dialogue est utilisé chaque fois ou presque chaque fois que le programme démarre, ne le créez qu'une seule fois durant l'initialisation du programme. Si le dialogue n'est pas appelé très souvent, créez-le sur demande comme dans notre exemple. Si le dialogue est un dialogue standard de KDE, utilisez les dialogues déjà créés de KDE libs (que ce soit un simple dialogue pour un message ou un dialogue plus complexe comme le requête à une URL).
Avant de continuer, une question rapide. Qu'arrive-t-il à notre programme si l'ordinateur se trouve à cours de mémoire et que le new échoue ? Oui, cela n'a guère de chance d'arriver mais....
if (m_prefDialog->exec()==QDialog::Accepted) applyPreferences();Enfin, nous exécutons le dialogue et si le dialogue est fermé avec "Ok", nous appelons notre fonction membre applyPreferences().
Et voilà ! Veuillez compiler le programme et testez-le. Chaque fois que vous cliquez sur le KPushButton, notre tout nouveau dialogue de préférences va s'ouvrir.
![]() |
Cependant, nous devons encore implanter ses fonctionnalités. C'est ce que nous ferons dans la Partie VII.
Le fichier compressé du projet en l'état actuel est : settingstutorial-04.tar.gz
Comment donc le dialogue et l'objet de configuration interagissent-ils ? Très simplement, chaque fois que le dialogue est affiché, les widgets devraient contenir les valeurs de l'objet de configuration. Aussi avons-nous besoin d'une fonction pour transférer les données depuis l'objet de configuration jusqu'au dialogue. Nous appellerons simplement cette fonction updateDialog().
D'un autre côté, nous avons aussi besoin d'une fonction qui transfère les éléments de configuration paramétrés dans le dialogue vers l'objet de configuration. Et cela devra être fait à chaque fois que vous appuyerez sur le bouton "Apply" ou "Ok. Nous appelons cette fonction updateConfiguration(). Bien sûr, vous êtes libre de lui donner le nom que vous voulez ...
L'implantation de ces fonctions est la suivante (n'oubliez pas la déclaration des fonctions dans le fichier en-tête !):
void PrefDialog::updateDialog() { m_prefGeneral->m_textEdit->setText( Config().m_text ); m_prefStyle->m_colorBtn->setColor( Config().m_textColor ); m_prefStyle->m_fontLabel->setFont( Config().m_font ); } void PrefDialog::updateConfiguration() { Config().m_text = m_prefGeneral->m_textEdit->text(); Config().m_textColor = m_prefStyle->m_colorBtn->color(); Config().m_font = m_prefStyle->m_fontLabel->font(); };
Ces fonctions sont vraiment simples et nous pouvons gagner du temps sur cela, n'est-ce-pas ?
Si vous avez copié et collé ces quelques lignes de code, vous allez obtenir quelques messages étranges de compilation, Habituellement, ces messages résultent d'en-têtes manquants. Nous avons utilisé les classes KLineEdit, QLabel et KColorButton donc nous devons inclure les en-têtes correspondants (je vous laisse le soin de deviner ceux dont vous avez besoin :-)
Quand devons-nous appeler ces deux fonctions de mise à jour ? Juste avant que le dialogue soit exécuté. Aussi allons-nous ajouter la ligne de code updateDialog() dans la fonction executePreferencesDlg() de notre fenêtre principale. Et nous devons appeler updateConfiguration() quand l'utilisateur accepte le dialogue (en cliquant sur "Ok"). Cela sera une autre ligne de code dans executePreferencesDlg(). Le boutton "Apply" sera un peu plus complexe à implanter , nous le verrons plus tard.
Voivi la nouvelle fonction executePreferencesDlg() :
void SettingsTutorial::executePreferencesDlg() { if (m_prefDialog==0) m_prefDialog=new PrefDialog(this); // crée le dialogue sur demande m_prefDialog->updateDialog(); // met à jour les widgets du dialogue if (m_prefDialog->exec()==QDialog::Accepted) { // exécute le dialogue m_prefDialog->updateConfiguration(); // emmagasine les éléments dans l'objet de configuration applyPreferences(); // appliquons les réglages }; };
Lorsque vous compilez et exécutez le programme, vous pouvez déjà changer deux préférences sur trois et les changements prendront effet lorque vous cliquez sur "Ok". Cependant, nous n'avons pas demandé à notre objet de configuration de sauvegarder les préférences. Cela se fait en fonction de la conception de votre programme. Habituellement, sauvegarder les nouvelle préférences juste au moment où celles-ci sont appliquées n'est pas une mauvaise idée. Vous devez toujours penser que votre application peut crasher et qu'alors les préférences seront perdues. Une autre façon de faire est de les sauvegarder lorsque le programme quitte. Mais dans ce tutoriel, nous allons choisir le moyen le plus facile et appeler simplement Config().write() dans la fonction applyPreferences(). Je ne vous montrerai pas le code cette fois-ci, il est temps que fassiez quelque chose tout seul ! :-)
Si vous avez tout fait correctement, les préférences correctes de votre dialogue seront sauvegardées et restaurées la prochaine fois que vous ouvrez l'application.
Voyons maintenant notre bouton "Choose..." pour la police de caractères qui ne fonctionne pas encore. Voici donc une autre opportunité pour connecter un signal à un slot.
Comme nous le savons déjà, le signal clicked() ne passe aucun paramètre donc nous créons un slot du nom de chooseBtnClicked() comme slot privé ("private slot") dans la classe PrefStyle. Le slot peut être privé car le signal est émis à l'intérieur de la classe PrefStyle et nous n'utiliserons jamais le slot de l'extérieur. La déclaration de la classe PrefStyle devient donc :
#ifndef PREFSTYLE_H #define PREFSTYLE_H #include <qwidget.h> #include <prefstylelayout.h> /// Implantation de la page "Style settings" du dialogue de préférences. class PrefStyle : public PrefStyleLayout { Q_OBJECT public: /// Constructeur. PrefStyle(QWidget *parent, const char *name=0, WFlags f=0); private slots: /// Appelé chaque fois que le bouton "Choose..." est cliqué. void chooseBtnClicked(); }; #endif // PREFSTYLE_H
Regardons maintenant les modifications que nous devons faire dans le fichier prefstyle.cpp :
#include <qfont.h> // pour QFont #include <kpushbutton.h> // pourKPushButton #include <kfontdialog.h> // pour KFontDialog #include <qlabel.h> #include "prefstyle.h" #include "prefstyle.moc" PrefStyle::PrefStyle(QWidget *parent, const char *name, WFlags f) : PrefStyleLayout(parent, name, f) { connect(m_fontBtn, SIGNAL( clicked() ), this, SLOT( chooseBtnClicked() ) ); } void PrefStyle::chooseBtnClicked() { QFont tmpFont = m_fontLabel->font(); int result = KFontDialog::getFont(tmpFont); if (result==KFontDialog::Accepted) m_fontLabel->setFont(tmpFont); };
Tout d'abord, veuillez noter la nouvelle ligne de code dans le constructeur : le signal clicked() du KPushButton est maintenant connecté à notre slot nouvellement créé.
Discutons maintenant l'implantation du slot lui-même. La plupart des choses devraient vous sembler familières maintenant mais le dialogue KFontDialog est quelque chose de nouveau. La plupart des dialogues les plus souvent utilisés sont fournis par les bibliothèques KDE libs. Ce dialogue de police de caractères peut être utilisé tel quel. L'espace de nom KFontDialog contient une fonction pré-définie qui demande à l'utilisateur de choisir une police. Elle est appelée getFont() et possède un seul paramètre : la police présélectionnée. Le paramètre est passé par référence et après que le dialogue soit refermé, il contient la police sélesctionnée par l'utilisateur.
Le reste est assez évident. Nous prenons d'abord la police de caractère actuelle dans l'étiquette QLabel, nous ouvrons le dialogue et si l'utilisateur a cliqué sur "Ok" pour accepter le dialogue, nous fixons la nouvelle police dans l'étiquette. Nous avons déjà un dialogue de préférences totallement fonctionnel et nos préférences sont sauvegardées et restaurées. Nous pourrions dire que c'est la fin du tutoriel...mais non, pas encore ! Les boutons "Default" et "Apply" ne fonctionnent pas encore !
Voici cependant le projet compressé en l'état actuel (si vous ne voulez pas faire ce petit travail vous-même :-) settingstutorial-05.tar.gz
Si vous n'avez besoin que d'un dialogue de préférences très simple sans boutons "Default" et "Apply", vous pouvez vous arrêter ici (mais rappelez-vous d'enlever le code relatif à ces boutons dans l'appel au constructeur de KDialogBase)...
Nous avons d'abord besoin d'un endroit central pour définir nos valeurs par défaut. Sinon, nous devrions les fixer deux fois - dans la fonction read() de l'objet de configuration et dans le dialogue de préférences quand l'utilisateur clique sur "Default". L'endroit le meilleur est bien sûr l'objet de configuration lui-même. Nous créons simplement une variable membre statique par défaut pour les valeurs par défaut et nous devons ajouter ces trois lignes au fichier configuration.h :
static const QString m_defaultText; ///< Le texte par défaut qui doit être affiché. static const QFont m_defaultFont; ///< La police de caractère par défaut pour le texte. static const QColor m_defaultTextColor; ///< La couleur par défaut pour le texte.L'initialisation est faite dans le fichier source configuration.cpp. Les variables temporaires dans la fonction membre read() ne sont plus nécessaires et peuvent être remplacées par nos variables par défaut statiques. Le nouveau fichier configuration.cpp est le suivant :
#include <kapplication.h> // pour 'kapp' #include <kconfig.h> // pour KConfig #include <klocale.h> // pour i18n() #include "configuration.h" const QString Configuration::m_defaultText = i18n("Hello World"); const QFont Configuration::m_defaultFont = QFont("Helvetica"); const QColor Configuration::m_defaultTextColor = Qt::black; Configuration::Configuration() { read(); // lit les préférences ou les fixe aux valeurs par défaut }; void Configuration::read() { KConfig *conf=kapp->config(); // lit les options générales conf->setGroup("General"); m_text = conf->readEntry("text", m_defaultText ); // lit les options de style conf->setGroup("Style"); m_font = conf->readFontEntry("font", &m_defaultFont ); m_textColor = conf->readColorEntry("textColor", &m_defaultTextColor ); }; void Configuration::write() const { KConfig *conf=kapp->config(); // écrit les options générales conf->setGroup("General"); conf->writeEntry("text", m_text); // écrit les options de style conf->setGroup("Style"); conf->writeEntry("font", m_font); conf->writeEntry("textColor", m_textColor); }; Configuration& Config() { static Configuration conf; return conf; };
Si plus tard vous voulez changer les valeurs par défaut, vous aurez simplement besoin de changer les valeur d'initialisation en haut du fichier.
Nous devons donc maintenant encore une fois modifier notre dialogue de préférences. Nous avons besoin d'une fonction qui fixe tous les widgets dans le dialogue à leur valeur par défaut chaque fois que l'utilisateur clique le bouton "Default".
Il y a eu récemment des discussions dans la liste de diffusion kde-devel sur la façon dont un bouton "Default" doit fonctionner. La grande question était : est-ce qu'un bouton "Default" doit seulement mettre les valeurs par défaut pour les widgets de la page affichée elle-même ou pour ceux de TOUTES les pages du dialogue. Il n'y a pas eu d'accord définitif sur ce point mais un consensus s'est dégagé :
Mettre toutes les pages du dialogue sur leurs valeurs par défaut mais en aviser avant l'utilisateur. Si l'utilisateur n'est pas d'accord pour que toutes les valeurs soient remises à leurs défauts, il peut dire non.
Le widget KDialogBase a une caractéristique très appréciée. Chaque fois que vous choisissez d'avoir un bouton "Default" dans votre dialogue (ceci est réalisé en spécifiant le code pour ce bouton dans le constructeur du dialogue), vous pouvez réimplanter le slot slotDefault() et cette fonction sera appelée chaque fois que le bouton "Default" est cliqué. C'est exactement ce que nous allons faire maintenant. Veuillez ajouter ces lignes au fichier prefdialog.h :
public slots: void slotDefault();et voici l'implantation de la fonction dans prefdialog.cpp :
void PrefDialog::slotDefault() { if (KMessageBox::warningContinueCancel(this, i18n("This will set the default options " "in ALL pages of the preferences dialog! Continue?"), i18n("Set default options?"), i18n("Set defaults"))==KMessageBox::Continue) { m_prefGeneral->m_textEdit->setText( Config().m_defaultText ); m_prefStyle->m_colorBtn->setColor( Config().m_defaultTextColor ); m_prefStyle->m_fontLabel->setFont( Config().m_defaultFont ); } };
La fonction KMessageBox::warningContinueCancel() ouvre un autre dialogue prédéfini qui est fourni par la bibliothèque de KDE. Veuillez regarder la documentation de la classe KMessageBox pour savoir combien de dialogues différents il existe. Pour le dialogue d'avertissement (warning), nous devons passer quatre arguments :
Comme je l'ai déjà dit, le bouton "Apply" est un peu plus compliqué. Ce n'est pas simplement à cause de l'implantation mais aussi car il y a eu de nombreuses discussions sur ce que doit être le comportement idéal d'un bouton "Apply". Voici les éléments sur lesquels les personnes sont d'accord :
Le bouton "Apply" doit être désactivé par défaut. Dès que l'utilisateur a changé une préférence, le bouton "Apply" est activé. Quand le bouton "Apply" est cliqué, les préférences doivent être transférées du dialogue dans la configuration en cours et les changements doivent être appliqués immédiatement (si possible). Puis le bouton "Apply" doit être à nouveau désactivé. L'utilisateur saura, par le changement d'état du bouton, si oui ou non il a changé des préférences.
Il y a eu quelques désaccords sur le fait que les changements doivent être appliqués immédiatement. Mais, selon ma propre expérience, la plupart des changements doivent être appliqués aussitôt. Aussi, dans ce tutoriel, nous appliquerons nos changements immédiatement.
Comment pouvons-nous coder la caractéristique d'activation et de désactivation du bouton "Apply" ? Hé bien, c'est aussi simple que cela : nous désactivons le bouton "Apply" chaque fois que nous transférons nos préférences du ou vers le dialogue (car dans ces deux cas, l'état du bouton sera mis sur "disable", c'est à dire qu'il sera désactivé). Et ensuite, nous avons seulement besoin d'un slot public pour activer le bouton à nouveau. Finalement, nous connectons ce slot à tous les signals des widgets interactifs (ou aux signals que nous émettons nous-mêmes). Nous pouvons activer et désactiver le bouton "Apply" avec la fonction membre enabledButtonApply(bool) du KDialogBase. Si nous passons 'true' comme argument, il sera activé, sinon, il sera désactivé. Voici donc ce que nous avons à faire :
#ifndef PREFSTYLE_H #define PREFSTYLE_H #include <qwidget.h> #include <prefstylelayout.h> /// Implantation de la page "Style settings" du dialogue de préférences. class PrefStyle : public PrefStyleLayout { Q_OBJECT public: /// Constructeur. PrefStyle(QWidget *parent, const char *name=0, WFlags f=0); private slots: /// Appelé chaque fois que le bouton "Choose..." est cliqué. void chooseBtnClicked(); signals: /// Sera émis lorsque l'utilisateur sélectionnera une nouvelle police de caractère. void fontChanged(); }; #endif // PREFSTYLE_H
#include <qfont.h> // pour QFont #include <qlabel.h> // pour QLabel #include <kpushbutton.h> // pour KPushButton #include <kfontdialog.h> // pour KFontDialog #include "prefstyle.h" #include "prefstyle.moc" PrefStyle::PrefStyle(QWidget *parent, const char *name, WFlags f) : PrefStyleLayout(parent, name, f) { connect(m_fontBtn, SIGNAL( clicked() ), this, SLOT( chooseBtnClicked() ) ); } void PrefStyle::chooseBtnClicked() { QFont tmpFont = m_fontLabel->font(); int result = KFontDialog::getFont(tmpFont); if (result==KFontDialog::Accepted) { m_fontLabel->setFont(tmpFont); emit fontChanged(); }; };
Les changements sont peu nombreux mais vous pouvez voir ici comment vous pouvez déclarer un signal et comment vous l'émettez (regardez la dernière ligne de code de chooseBtnClicked()). C'est vraiment très facile !
Maintenant que nous avons un signal qui peut être connecté, finissons le dialogue des préférences :
#ifndef PREFDIALOG_H #define PREFDIALOG_H #include <qwidget.h> #include <kdialogbase.h> class PrefGeneral; class PrefStyle; /// Le dialogue préférences. class PrefDialog : public KDialogBase { Q_OBJECT public: /// Constructeur PrefDialog(QWidget *parent, const char *name=0, WFlags f=0); /// Transfert les préférences de l'objet de configuration au dialogue void updateDialog(); /// Transfert les préférences du dialogue à l'objet de configuration void updateConfiguration(); public slots: /// Sera appelé lorsque le bouton "Default" est cliqué. void slotDefault(); /// Sera appelé lorsque le bouton "Apply" est cliqué. void slotApply(); /// Sera appelé lorsqu'une préférence a changé. void enableApply(); signals: /// Sera émis lorsque les nouvelles préférences doivent être appliquées void settingsChanged(); private: PrefGeneral *m_prefGeneral; PrefStyle *m_prefStyle; }; #endif // PREFDIALOG_H
Comme vous pouvez le remarquer, nous n'avons pas seulement ajouté le slot enableApply() slot ! slotApply() fonctionne exactement de la même façon que slotDefault() : il sera appelé quand le bouton "Apply" sera cliqué. Et finalement, nous ajoutons le signal settingsChanged(). Cela nous permet de notifier au reste du programme que les nouvelles préférences devront être appliquées immédiatement.
Voici le code source final du dialogue :
#include <qlayout.h> // pour QVBoxLayout #include <qlabel.h> // pour QLabel #include <qframe.h> // pour QFrame #include <kcolorbutton.h> // pour KColorButton #include <kpushbutton.h> // pour KPushButton #include <klocale.h> // pour i18n() #include <kiconloader.h> // pour KIconLoader #include <kglobal.h> // pour KGlobal #include <klineedit.h> // pour KLineEdit #include <kmessagebox.h> // pour KMessageBox #include "prefdialog.h" // classe PrefDialog #include "prefdialog.moc" #include "configuration.h" // classe Configuration et Config() #include "prefgeneral.h" // classe PrefGeneral #include "prefstyle.h" // classe PrefStyle PrefDialog::PrefDialog(QWidget *parent, const char *name, WFlags f) : KDialogBase(IconList, i18n("Preferences"), Default|Ok|Apply|Cancel, Ok, parent, name, f) { // ajoute la page "General options" QFrame *frame = addPage(i18n("General"), i18n("General options"), KGlobal::iconLoader()->loadIcon("kfm",KIcon::Panel,0,false) ); QVBoxLayout *frameLayout = new QVBoxLayout( frame, 0, 0 ); m_prefGeneral = new PrefGeneral(frame); frameLayout->addWidget(m_prefGeneral); // ajoute la page "Style settings" frame = addPage(i18n("Style"), i18n("Style settings"), KGlobal::iconLoader()->loadIcon("style",KIcon::Panel,0,false) ); frameLayout = new QVBoxLayout( frame, 0, 0 ); m_prefStyle = new PrefStyle(frame); frameLayout->addWidget(m_prefStyle); // connecte les widgets interactifs et les signals créés au slot enableApply() connect( m_prefGeneral->m_textEdit, SIGNAL( textChanged(const QString&) ), this, SLOT( enableApply() ) ); connect( m_prefStyle->m_colorBtn, SIGNAL( changed(const QColor&) ), this, SLOT( enableApply() ) ); connect( m_prefStyle, SIGNAL( fontChanged() ), this, SLOT( enableApply() ) ); }; void PrefDialog::updateDialog() { m_prefGeneral->m_textEdit->setText( Config().m_text ); m_prefStyle->m_colorBtn->setColor( Config().m_textColor ); m_prefStyle->m_fontLabel->setFont( Config().m_font ); enableButtonApply(false); // désactive à nouveau le bouton Apply }; void PrefDialog::updateConfiguration() { Config().m_text = m_prefGeneral->m_textEdit->text(); Config().m_textColor = m_prefStyle->m_colorBtn->color(); Config().m_font = m_prefStyle->m_fontLabel->font(); enableButtonApply(false); // désactive à nouveau le bouton Apply }; void PrefDialog::slotDefault() { if (KMessageBox::warningContinueCancel(this, i18n("This will set the default options " "in ALL pages of the preferences dialog! Continue?"), i18n("Set default options?"), i18n("Set defaults"))==KMessageBox::Continue) { m_prefGeneral->m_textEdit->setText( Config().m_defaultText ); m_prefStyle->m_colorBtn->setColor( Config().m_defaultTextColor ); m_prefStyle->m_fontLabel->setFont( Config().m_defaultFont ); enableApply(); // active le bouton Apply }; }; void PrefDialog::slotApply() { updateConfiguration(); // transfère les préférences à l'objet de configuration emit settingsChanged(); // applique les préférences enableButtonApply(false); // désactive à nouveau le bouton Apply }; void PrefDialog::enableApply() { enableButtonApply(true); // active le bouton Apply };
Avant de continuer à lire, essayez de trouver tous les changements que j'ai fait !
Les voici :
Premièrement, nous avons connecté les différents signals des widgets au slot enableApply(). Veuillez noter que dans la première ligne " connect ..."
connect( m_prefGeneral->m_textEdit, SIGNAL( textChanged(const QString&) ), this, SLOT( enableApply() ) );je connecte un signal qui est émis par le widget KLineEdit. Cependant, dans la troisième ligne "connect..." :
connect( m_prefStyle, SIGNAL( fontChanged() ), this, SLOT( enableApply() ) );je connecte le signal émis par la page de configuration.
Le dernier changement se trouve dans la fonction membre updateDialog(). Le bouton Apply sera désactivé chaque fois que la fonction est appelée. Ceci arrive aussi lorsque l'objet de configuration est rafraîchit (parce qu'à ce moment nous appliquons nos changements). Donc nous ajoutons aussi cette ligne à updateConfiguration().
Il y a aussi un petit changement dans la fonction slot slotDefault(). Remettre la configuration sur les valeurs par défaut devrait aussi activer le bouton Apply.
Et enfin les nouveaux slots sont implantés (les commentaires du code source devraient être suffisants). Veuillez noter que nous n'émettons le signal settingsChanged() qu'après avoir transféré les données à l'objet de configuration. Il est bien sûr nécessaire de faire cela d'abord car tous les widgets et toutes les classes du programme vont prendre leurs valeurs d'après l'objet de configuration.
Cela fonctionne très bien. Mais nous devons "attraper" le signal settingsChanged(). Sinon nous ne verrons pas les changements immédiatement. Et où pouvons-nous connecter un signal qui est émis par un dialogue ? Dans la classe qui crée le dialogue. C'est donc le dernier petit changement que nous devons faire dans le fichier settingstutorial.cpp :
void SettingsTutorial::executePreferencesDlg() { if (m_prefDialog==0) { m_prefDialog=new PrefDialog(this); // crée le dialogue sur demande // connecte au signal "settingsChanged" connect(m_prefDialog, SIGNAL( settingsChanged() ), this, SLOT( applyPreferences() ) ); } m_prefDialog->updateDialog(); // rafraîchit les widgets du dialogue if (m_prefDialog->exec()==QDialog::Accepted) { // exécute le dialogue m_prefDialog->updateConfiguration(); // sauvegarde les préférences dans l'objet de configuration applyPreferences(); // les nouvelles préférences prennent effet }; };
Nous connectons simplement le signal émis par le dialogue de préférences au slot applyPreferences() et, après ce dernier changement, nous en avons finalement terminé avec ce tutoriel (je vous laisse le tester :-).
LA FIN
Voici le fichier compressé du projet final : settingstutorial-final.tar.gz.
Veuillez regarder dans le sous dossier html. Vous y trouverez la documentation générée par doxygen.
Pour ceux qui n'auraient pas bien lu, voici à nouveau les instructions : décompressez les fichiers dans un répertoire et ouvrez le projet avec Gideon (KDevelop3). Lancez "automake & friends" puis "configure". Puis compilez le projet ou bien tapez simplement dans le répertoire du projet :
make -f Makefile.cvs
./configure
make
Ceci devrait fonctionner - vous trouverez l'exécutable dans le répertoire "src".