Un tutoriel pour débuter avec KDE 3 & QT 3

Comment coder un dialogue de "Préférences pour l'utilisateur"

Un tutoriel de programmation écrit par Andreas Nicolai

Traduction française par Anne-Marie Mahfouf

Le but de ce tutoriel

Dans la plupart des applications KDE, les préférences de l'utilisateur peuvent être changées et sauvegardées. Cela nécessite habituellement :

  1. un moyen facile pour accéder aux différents éléments de configuration depuis les widgets et les classes
  2. un moyen pour sauvegarder et restaurer les préférences définies par l'utilisateur
  3. un dialogue de préférences habituel (qui inclut des boutons "Appliquer" et "Défauts")

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 dont vous avez besoin

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.

Contenu

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).




Partie I : Débuter 

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 :


settingstutorial.h
#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_


settingstutorial.cpp
#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.


Partie II : La théorie ennuyante 

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 :


configuration.h
#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


configuration.cpp
#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


Lorsque vous avez ajouté la nouvelle classe de configuration au projet et l'avez compilé, vous êtes prêt pour la partie 3.

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!


Partie III : Lire et écrire les éléments de configuration 

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 :

  1. mettez un m_ devant votre variable membre, de cette façon vous savez que vous avez affaire à une variable membre et elle peut être distinguée des variables locales
  2. le nom des variables commence par une lettre en minuscule puis la première lettre d'un mot est en majuscule par exemple int m_bigTextSize
  3. lorsque vous créez une fonction membre qui extrait le contenu d'une variable membre, nommez-la avec le même nom mais sans le 'm_', soit par exemple int bigTextSize();
  4. mettez 'set' devant le nom pour les fonctions qui fixent des valeurs comme void setBigTextSize(int size); par exemple.

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 :


configuration.h
#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...


configuration.cpp
#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 :-)
Maintenant, nous pouvons écrire les propriétés dans le fichier de configuration. Chaque entrée consiste en une clé et sa valeur. La clé est une chaîne de caractère normale (texte) et la valeur peut être de n'importe quel type de donnée fourni par la bibliothèque QT. La clé est aussi simplement un texte normal et comme dans un programme complexe, vous pouvez avoir plusieurs clés portant le même nom, on utilise aussi des groupes. Pour écrire dans un certain groupe de propriété, vous le déterminez avant par
    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


Partie IV : Créer les pages de configuration 

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 :

  1. name: PrefGeneralLayout
  2. caption: General (ceci est optionnel car le nom ne sera pas utilisé, mais cela aide à garder une vue d'ensemble sur les widgets)

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 :

  1. cliquez avec le bouton droit de la souris dans le cadre et choisissez "Lay Out Vertically" (votre cadre va se réduire à sa taille minimum)
  2. insérez un ressort vertical (avec l'icône du ressort) sous le cadre
  3. cliquez avec le bouton droit de la souris dans le widget et choisissez "Lay Out Vertically" (votre cadre va s'agrandir pour atteindre le maximum horizontallement et le ressort va occuper l'espace libre sous le cadre).

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 :

  1. Un KColorButton appelé "m_colorBtn"
  2. Un KPushButton appelé "m_fontBtn"
  3. Un QLabel appelé "m_fontLabel"

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 :-)

  1. créer un cadre avec les propriétés frameShape=Box et frameShadow=Sunken
  2. créer deux QLabels à l'intérieur du cadre et leur donner les textes "Text color:" et "Font:"
  3. créer deux ressorts horizontaux dans chaque ligne à côté des boutons
  4. fixer "size policy" pour les ressorts à gauche sur "fixed"
  5. créer un KColorButton et lui donner le nom de "m_colorBtn"
  6. créer un KPushButton, et lui donner le nom de "m_fontBtn", écrivez dedans "Choose..."
  7. aligner les QLabels, les ressorts et les boutons en rangs et colonnes (imaginez un tableau)
  8. cliquer avec le bouton droit de la souris dans le cadre et choisir "Lay Out in a Grid" (le cadre doit rétrécir et les widgets se positionnent comme dans une grille. Si cela ne fonctionne pas correctement, cliquez avec le bouton droit de la souris dans le cadre à nouveau et sélectionnez "Break Layout" pour ré-arranger les boutons/ressorts/étiquettes et reréer une grille jusqu'à ce que le positionnement soit correct).
  9. agrandir un peu le cadre et cliquez avec le bouton droit de la souris dans le cadre et choisir "Break Layout"
  10. ajouter un QLabel en dessous des 6 widgets (qui devraient être bien alignés maintenant)
  11. donner au QLabel le nom de "m_fontLabel" et changer les propriétés suivantes de l'étiquette
  12. agrandir ce QLabel de police de caractère pour qu'il soit aussi large que les widgets au-dessus
  13. créer à nouveau un positionnement en grille dans le cadre
  14. ajouter un ressort vertical en-dessous du cadre
  15. créer un positionnement vertical dans le widget
  16. fixer la marge du positionnement sur zéro
  17. sauvegarder le widget :-)

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.


prefgeneral.h
#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


prefgeneral.cpp
#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


Partie V : Implantation basique du dialogue de configuration 

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 ...) :


prefdialog.h
#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


prefdialog.cpp
#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 :

  1. le type de dialogue: IconList signifie que nous avons des icônes sur la gauche du dialogue
  2. le titre du dialogue.
    Note : nous utilisons la macro i18n() qui fournit les fonctionalités pour l'internationalisation. Ce concept sera examiné dans un tutoriel différent, rappelez-vous juste pour l'instant de mettre toute chaîne de caractères qui sera affichée à l'intérieur de la fonction macro i18n().
  3. les différents boutons que le dialogue doit contenir. Dans notre cas, nous voulons avoir 4 boutons.
  4. le bouton qui est sélectionné par défaut.
  5. le widget parent
  6. le nom d'identification
  7. les drapeaux pour la fenêtre (wflags)

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 :

  1. le nom de la page qui apparaîtra dans la liste des icônes
  2. le nom de la page qui sera affiché en haut de la page de configuration
  3. l'icône qui sera utilisée dans la liste des icônes

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 ...


Partie VI : Créer et exécuter le dialogue 

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 :

  1. le pointeur au widget qui émet le signal : c'est notre KPushButton
  2. le nom du signal (à l'intérieur de la macro SIGNAL()) : c'est clicked()
  3. le pointeur au widget qui doit recevoir le signal : c'est notre fenêtre principale (this)
  4. le nom du slot (à l'intérieur de la macro SLOT()) : c'est executePreferencesDlg()

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().
La fonction applyPreferences() est vraiment simple aussi ne vais-je pas l'expliquer. Essayez de comprendre par vous-même ! (nous utilisons lespropriétés de notre objet unique de configuration qui seront retournées par la fonction Config() ).

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


Partie VII : Connecter le dialogue et l'objet de configuration 

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)...


Partie VIII : Implantation des caractéristiques pour les boutons "Default" et "Apply" 

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.


Selon moi, c'est une façon correcte de procéder car le bouton "Default" n'appartient pas à une page spécifique du dialogue mais au dialogue tout entier.

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 :

  1. un pointeur au widget parent (this -> le dialogue de préférences)
  2. le texte du message pour l'utilisateur (n'oubliez pas d'utiliser la macro i18n())
  3. le titre du dialogue
  4. le texte sur le bouton "Continue"
Quand l'utilisateur clique sur le bouton "continue", le dialogue va retourner KMessageBox::Continue et nous transférons simplement les valeurs par défaut de l'objet de configuration aux widgets. Simple et robuste ! Ceci était pour le bouton "Default". Il n'en reste plus qu'un seul !


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 :

  1. désactiver le bouton "Apply" dans les fonctions membres updateDialog() et updateConfiguration()
  2. créer un slot public enableApply()
  3. connecter tous les widgets interactifs à ce slot
Attendez, nous avons besoin de quelque chose de spécial pour l'étiquette de police de caractère. Comme QLabel n'est pas un widget interactif, il n'existe aucun signal auquel se connecter. Mais nous avons le slot chooseButtonClicked() dans la classe PrefStyle. Nous pouvons donc émettre ici un signal "fontChanged" et le connecter au slot "enableApply". Donc voici la nouvelle (et dernière) version du fichier prefstyle.h :
#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

et prefstyle.cpp
#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 :


prefdialog.h
#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 :


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 <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".


Écrit en environ 38 heures du 17 au 18 Mai 2003 par Andreas Nicolai. Si vous trouvez des erreurs (ou des fautes typographiques) ou si vous avez des suggestions ou quoique que ce soit d'autre : vous pouvez me contacter à : ghorwin AT users DOT sourceforge DOT net.
Traduction française par Anne-Marie Mahfouf.