Pour décrire le concept de fonctionnement d'une application KDE, nous allons d'abord regarder précisément le squelette de sources déjà fourni par l'Assistant d'Application. Comme nous l'avons déjà vu, nous avons un ensemble de sources et de fichiers d'en-tête qui constituent le code initial de l'application et la rendent prête à être exécutée. De ce fait, la plus simple façon d'expliquer le code est de suivre l'implantation ligne par ligne, comme il est traité durant l'exécution du programme jusqu'à ce qu'il entre dans la boucle d'événements principale et soit prêt à accepter les entrées de l'utilisateur. Ensuite, nous regarderons les fonctionnalités qui permettent les interactions avec l'utilisateur et comment certaines choses fonctionnent. C'est probablement la meilleure façon d'expliquer l'architecture et comme elle est similaire pour presque toutes les applications KDE , cela vous permettra de lire plus facilement le code source d'autres projets. En plus, vous apprendrez quoi et où changer dans le code pour faire en sorte que vos applications se comportent comme prévu.
Comme l'application commence son exécution en entrant dans la fonction main(), ce sera aussi notre point de départ pour examiner le code. La fonction main() de KScribble est implantée dans le fichier main.cpp et peut également être trouvée grâce au Navigateur de Classe en sélectionnant le sous-dossier "Fonctions" du dossier "Globaux" :
1 #include "kscribble.h" 2 3 int main(int argc, char* argv[]) { 4 KApplication app(argc,argv,"KScribble"); 5 6 if (app.isRestored()) 7 { 8 RESTORE(KScribbleApp); 9 } 10 else 11 { 12 KScribbleApp* kscribble = new KScribbleApp; 13 kscribble->show(); 14 if(argc > 1){ 15 kscribble->openFile(argv[1]); 16 } 17 } 18 return app.exec(); 19 } |
La première chose qui se passe généralement est la création d'un objet KApplication qui reçoit le nom de notre application KScribble comme troisième paramètre. Lors de la création d'une nouvelle KApplication, une nouvelle instance de KConfig est également créée, permettant d'accéder au fichier de configuration $HOME/.kde/share/config/appname + rc qui contient toutes les informations que nous voulons utiliser lors du lancement des fenêtres de l'application. Le nom passé au constructeur de app sera utilisé plus tard comme titre de la fenêtre. Ici, le code est un petit peu différent du code d'exemple donné précédemment pour convertir une application Qt en une application KDE. Une fois que l'objet KApplication est présent, nous testons si l'application est démarrée par le gestionnaire de sessions de kwm ou manuellement par l'utilisateur. Cela est déterminé en appelant isRestored() sur l'objet app qui retourne true pour la gestion de sessions et false pour un lancement manuel. Comme la gestion des sessions est une fonctionnalité importante des applications KDE et largement utilisée par l'architecture mais plus longue à expliquer, nous allons d'abord continuer par la section else&{;&}; ; ensuite, nous y reviendrons et expliquerons la fonctionnalité de gestion des sessions dans une étape ultérieure.
La section else&{;&}; crée maintenant une instance de la classe KScribbleApp à la ligne 12. Cet objet est appelé pour s'afficher à la ligne 13, comme d'habitude ; la ligne 14 détermine si un argument a été passé à la ligne de commande et, comme c'est souvent le nom d'un fichier, appelle l'objet kscribble pour l'ouvrir avec openFile(). Remarquez que nous n'avons pas appelé la méthode setTopWidget(kscribble) pour notre application - c'est déjà fait par la classe dont hérite KScribbleApp. Maintenant, nous allons regarder notre objet KScribbleApp - qu'est-ce que c'est et que fournit-il déjà ? La seule chose que nous savons jusqu'à maintenant, c'est que ça doit être un Widget pour représenter l'interface utilisateur dans la fenêtre pricipale. Allons dans l'implantation de la classe KScribbleApp qui se trouve dans le fichier kscribble.cpp ou par un clic sur l'icône de classe dans le Navigateur de Classe. L'instance est créée par le constructeur. D'abord, nous voyons qu'elle hérite de la classe KTMainWindow qui fait partie de la bibliothèque kdeui. Cette classe hérite elle-même de QWidget donc, comme d'habitude, nous avons un widget normal comme fenêtre de plus haut niveau (NdT : top-level window). KTMainWindow contient beaucoup de fonctionnalités qui sont utilisées par la classe KScribbleApp. Elle fournit une barre de menus, une barre d'outils , une barre d'état et le support de la gestion de sessions. La seule chose que nous ayons à faire en dérivant de KTMainWindow est de créer tous les objets dont nous avons besoin et de créer un autre widget qui sera géré par notre instance de KTMainWindow comme vue principale au centre de la fenêtre ; habituellement, c'est ici que l'utilisateur travaille comme une vue d'édition de texte.
Regardons le code du constructeur et voyons comment l'instance est créée :
1 KScribbleApp::KScribbleApp() 2 { 3 config=kapp->getConfig(); 4 5 6 /////////////////////////////////////////////////////////////////// 7 // call inits to invoke all other construction parts 8 initMenuBar(); 9 initToolBar(); 10 initStatusBar(); 11 initKeyAccel(); 12 initDocument(); 13 initView(); 14 15 readOptions(); 16 17 /////////////////////////////////////////////////////////////////// 18 //disable menu and toolbar items at startup 19 disableCommand(ID_FILE_SAVE); 20 disableCommand(ID_FILE_SAVE_AS); 21 disableCommand(ID_FILE_PRINT); 22 23 disableCommand(ID_EDIT_CUT); 24 disableCommand(ID_EDIT_COPY); 25 disableCommand(ID_EDIT_PASTE); 26 } |
Nous voyons que notre instance de KConfig pointe maintenant vers la configuration de l'application donc nous pourrons travailler avec les entrées du fichier de configuration ultérieurement. Ensuite, tous les composants requis par l'application sont créés par les fonctions membres correspondantes, spécifiquement à notre fenêtre principale :
initMenuBar() : construit la barre de menus
initToolBar() : construit la barre d'outils
initStatusBar() : crée la barre d'état
initKeyAccel() : définit tous les accélérateurs clavier pour notre application avec les configurations du clavier globale et spécifique à l'application
initDocument() : crée l'objet document pour la fenêtre de l'application
initView() : crée le widget principal pour notre vue dans la fenêtre principale
readOptions() : lit tous les paramètres spécifiques à l'application dans le fichier de configuration et initialise le reste de l'application, comme la liste des fichiers récemment ouverts, la position des barres et la taille de la fenêtre.
Enfin, nous désactivons certaines commandes que l'utilisateur peut activer car elles ne doivent pas être disponibles dans l'état courant de l'application. Comme nous avons maintenant un aperçu global de la façon dont est créée la fenêtre de l'application, nous allons regarder en détail comment les éléments de l'interface utilisateur sont construits par les méthodes ci-dessus.
Comme nous l'avons vu au-dessus, la barre de menus de KScribble est créée par la méthode initMenuBar(). En fait, nous créons un ensemble de QPopupMenus qui apparaissent si l'utilisateur sélectionne une entrée de menu. Ensuite, nous les insérons dans la barre de menus et nous les connectons aux entrées.
D'abord, nous créons notre recent&_;file&_;menu qui contiendra les noms des 5 derniers fichiers ouverts. Nous devons le faire en premier car ce menu est inséré dans le file&_;menu. Ensuite, nous ajoutons directement la connexion - nous récupérons juste le signal qui est émis par l'entrée de menu avec son numéro d'entrée et appelons slotFileOpenRecent( int ), qui ouvre ensuite le bon fichier de la liste des fichiers récemment ouverts.
Ensuite, nous créons notre menu "Fichier". C'est le menu qui sera visible dans la barre de menus. Les actions standards sont ensuite insérées une par une dans le menu popup - d'abord, les commandes pour créer un nouveau fichier, ouvrir un fichier, fermer un fichier... et enfin "E&&;xit" pour fermer l'application. Toutes les entrées de menu doivent être créées dans l'ordre dans lequel elles apparaîtront ensuite donc nous devons rester vigilants sur ce que nous voulons mettre et à quelle place. À titre d'exemple, regardons les entrées suivantes& ;:
file_menu->insertItem(Icon("fileopen.xpm"), i18n("&&;Open..."), ID_FILE_OPEN ); file_menu->insertItem(i18n("Open &&;recent"), recent_files_menu, ID_FILE_OPEN_RECENT ); |
La première ligne insère une entrée "Open...". Comme nous voulons qu'elle ait une icône, nous utilisons la méthode insertItem() avec le nom de l'icône. Pour comprendre le processus de chargement d'icône, nous devons savoir comment et où est déclaré Icon() - en fait, c'est une macro fournie par la classe KApplication :
#define Icon(x) kapp->getIconLoader()->loadIcon(x) |
Elle utilise, d'ailleurs, la macro interne suivante pour accéder à l'objet application :
#define kapp KApplication::getKApplication() |
Cela signifie que l'objet KApplication contient déjà une instance du chargeur d'icône - nous devons seulement y accéder ; ensuite, il chargera l'icône correpondante. Comme nos icônes viennent toutes des bibliothèques de KDE , nous n'avons pas à nous préoccuper de quoi que ce soit d'autre - elles sont installées automatiquement sur le système et nous n'avons donc pas besoin de les inclure dans le paquetage de notre application pour pouvoir les utiliser.
Après le paramètre d'icône (qui est optionnel), nous insérons le nom de l'entrée de menu par i18n("&&;Open..."). Là, nous devons signaler deux choses : premièrement, l'entrée est insérée avec la méthode i18n(). Comme pour Icon(), c'est aussi une macro définie dans kapp.h et appelle l'objet KLocale de KApplication pour traduire l'entrée dans la langue utilisée actuellement :
#define i18n(X) KApplication::getKApplication()->getLocale()->translate(X) |
Il faut signaler que quelqu'un pourrait penser "Je ne veux pas utiliser de macros" - vous pouvez le faire dans la plupart des cas. Mais ici, il est nécessaire d'utiliser i18n() car pour l'internationalisation, les fichiers de traduction correspondants doivent être générés. Comme ce processus de génération repose sur l'utilisation de la chaîne de caractères i18n, vous devez utiliser cette macro.
Comme vous l'avez peut-être déjà deviné, le symbole &&; (et commercial) dans les entrées de menu est interprété ultérieurement comme une petite ligne sous la lettre correspondante dans l'entrée de menu. Cela permet un accès rapide à la commande du menu via le clavier quand l'utilisateur appuie simultanément sur la touche Alt et la lettre soulignée.
Enfin, nous donnons à l'entrée de menu un identificateur (NdT : ID) qui est une valeur entière par laquelle nous pourrons trouver l'entrée ultérieurement. Pour garder un oeil sur les valeurs utilisées, celles-ci sont définies par des macros et sont regroupées dans le fichier resource.h de votre projet. Par souci de cohérence, ces macros sont écrites en majuscules, commencent par ID&_; et sont suivies du nom du menu et de celui de l'entrée. Cela aide énormément à se remémorer le sens de chaque entrée n'importe où dans le code ; il n'est donc plus nécessaire d'aller systématiquement dans l'implantation de la barre de menus pour chercher les entrées.
Le deuxième exemple d'entrée montre une autre variante de la méthode insertItem(). Ici, nous ajoutons le menu popup recent&_;files&_;menu comme un élément de menu. Cela signifie que l'entrée s'affiche elle-même avec la chaîne donnée "Open recent", suivie par une flèche vers la droite. Lors d'une sélection, le menu popup des fichiers récemment ouverts apparaît et l'utilisateur peut choisir un des derniers fichiers.
Enfin, il y a beaucoup d'autres manières d'insérer des éléments de menu - l'architecture rend cela aussi simple que possible. Plus d'informations sur la classe QMenuData peuvent être trouvées dans la documentation de Qt .
Maintenant, après avoir créé les menus popup file&_;menu, edit&_;menu et view&_;menu, nous devons insérer aussi un menu "Aide". Nous pourrions le faire comme pour les autres menus mais la classe KApplication fournit une méthode simple et rapide pour le faire :
help_menu = kapp->getHelpMenu(true, i18n("KScribble\n" VERSION )); |
C'est tout ce que nous avons à faire pour obtenir un menu d'aide qui contient une entrée pour le contenu de l'aide accessible avec le raccourci clavier F1, une boîte de dialogue "À propos" pour l'application et une boîte de dialogue "À propos" pour KDE (qui peut être désactivée en appelant getHelpMenu(false,...);). Le contenu de la boîte de dialogue "À propos" de notre application est à nouveau défini avec la chaîne i18n() - VERSION prend la macro qui est définie dans le fichier config.h pour le numéro de version du projet donc nous n'avons pas besoin de le changer manuellement à chaque fois que nous voulons diffuser une nouvelle version. N'hésitez pas à ajouter ici des informations sur votre application, par exemple votre nom, votre adresse électronique, le copyright, etc.
Maintenant, nous n'avons plus qu'à insérer les popups dans la barre de menus. Comme KTMainWindow construit déjà une barre de menus pour nous, il suffit de les insérer en appelant menuBar()->insertItem();.
Ce qu'il reste à faire est de connecter les entrées de menu avec les méthodes qu'elles exécuteront. C'est pourquoi, nous connectons chaque menu popup par son signal activated(& ;int& ;) à une méthode commandCallback(& ;int& ;) qui contient un switch qui appelle les méthodes correspondantes pour les entrées de menu. En plus, nous connectons le signal highlighted(& ;int& ;) des popups pour fournir de l'aide sur chaque entrée dans la barre d'état. Lorsque l'utilisateur déplace sa souris ou le focus du clavier sur une entrée, la barre d'état affiche alors le message d'aide correspondant.
Après avoir terminé avec la barre de menus, nous pouvons passer à la barre d'outils dans la section suivante. Remarquez qu'une instance de KTMainWindow peut avoir seulement une seule barre de menus visible à la fois ; c'est pourquoi, si vous voulez construire plusieurs barres de menus, vous devez les créer séparément avec des instances de KMenuBar et définir l'une d'elles comme la barre de menus courante avec la méthode appropriée de KTMainWindow. Consultez la documentation de la classe KMenuBar pour plus de détails sur ses fonctionnalités étendues, voir aussi Configuration des Barres de Menus et des Barres d'Outils.
La création de barres d'outils est bien plus simple que pour les barres de menus. Comme KTMainWindow fournit déjà des barres d'outils qui sont créées dès leur première insertion, vous êtes libre d'en créer plusieurs. Ajoutez juste les boutons pour les fonctions que vous voulez fournir :
toolBar()->insertButton(Icon("filenew.xpm"), ID_FILE_NEW, true, i18n("New File") ); |
Ceci ajoute un bouton aligné à gauche avec l'icône "filenew.xpm" et l'identificateur (NdT : ID) correspondant dans la barre d'outils . Le troisième paramètre décide si le bouton doit être activé ou non ; par défaut, nous le définissons à true car nos méthodes disableCommand() à la fin du constructeur le font automatiquement pour nous, à la fois pour les entrées du menu et de la barre d'outils . Enfin, le dernier paramètre est utilisé pour la "bulle d'aide" - quand l'utilisateur place le pointeur de la souris sur le bouton pour le mettre en surbrillance, une petite fenêtre, qui contient un court message d'aide dont le contenu est défini ici, apparaît. Enfin, les boutons de la barre d'outils sont connectés à notre méthode commandCallback() par leur signal clicked(). Lors du signal pressed(), nous permettons à l'utilisateur de recevoir le message d'aide correspondant dans la barre d'état.
Informations supplémentaires :
Comme les barres d'outils sont créées en utilisant la classe KToolBar, vous devriez consulter la documentation correspondante. Avec KToolBar, beaucoup de choses nécessaires pour une barre d'outils peuvent être réalisées, comme un popup retardé si votre bouton veut afficher un menu quand le bouton reste enfoncé ou même l'utilisation d'autres widgets comme par exemple une liste déroulante. Par défaut, la barre d'outils remplit complètement la largeur de la fenêtre ce qui est sympathique pour l'utilisation d'une seule barre. Lorsque vous en avez plus d'une, vous devriez penser à définir la taille de la barre à l'extrémité du bouton le plus à droite, ainsi les autres barres peuvent s'afficher dans la même ligne, sous la barre de menus. Nous évoquerons certaines techniques de conception et d'extension des barres d'outils dans la section Configuration des Barres de Menus et des Barres d'Outils.
La barre d'état est, comme les autres barres, déjà fournie par l'instance de KTMainWindow donc nous avons juste à insérer nos éléments dedans comme nous le souhaitons. Par défaut, l'architecture contient seulement une entrée qui affiche l'aide de la barre d'état. Pour beaucoup d'applications, ce n'est pas suffisant, vous pourrez donc ajouter les entrées dont vous avez besoin pour afficher, par exemple, des coordonnées ou autre chose.
De plus, une application ne peut avoir qu'une seule barre d'état, comme pour la barre de menus. Si vous voulez en construire plusieurs, vous devrez les créer séparément et définir la barre courante avec la méthode appropriée de KTMainWindow. La barre d'état permet aussi d'insérer des widgets qui peuvent être utilisés pour créer des zones pour afficher des barres de progression, comme KDevelop le fait. Référez-vous à la documentation de la classe KStatusBar.
Une fois rendu à la méthode initKeyAccel(), nous avons déjà construit les éléments standards de la fenêtre principale d'une application - la barre de menus, la barre d'outils et la barre d'état.
Cependant, nous n'avons pas encore défini les raccourcis clavier par lesquels l'utilisateur expérimenté qui veut uniquement travailler avec son clavier peut accéder rapidement aux commandes qui sont le plus souvent utilisées pendant une session de travail de notre programme. Pour cela, nous aurions pu insérer les touches de raccourci, par exemple, lors de l'insertion des éléments du menu mais KDE offre une bonne solution pour construire et maintenir les raccourcis clavier. Beaucoup d'utilisateurs veulent d'un côté qu'ils soient configurables et d'un autre côté les raccourcis standards doivent être les mêmes pour toutes les applications. C'est pourquoi, le Centre de Contrôle de KDE permet de configurer les raccourcis clavier standards en utilisant la classe KAccel. En plus, les bibliothèques de KDE contiennent un widget qui permet aux utilisateurs de configurer facilement les raccourcis clavier spécifiques à l'application. Comme l'architecture d'application utilise seulement des éléments de menus qui sont des actions standards comme "Nouveau" ou "Quitter", celles-ci sont définies par la méthode initKeyAccel(). Les actions standards doivent juste être connectées, pour la configuration du clavier spécifique à votre application, vous devez les insérer d'abord en spécifiant le nom du raccourci clavier et ensuite les connecter. Comme nos raccourcis sont tous présents dans la barre de menus, nous devons changer le raccourci pour les entrées du popup. Enfin, nous appelons readSettings() qui lit la configuration courante depuis la fenêtre du bureau de KDE (NdT : KDE rootwindow) où est stockée la configuration des raccourcis standards, puis les paramètres des raccourcis spécifié dans le fichier de configuration de l'application. Quand nous aurons progressé dans notre projet d'exemple, nous vous dirons aussi comment modifier avec une boîte de dialogue de configuration les raccourcis spécifiques à notre application, voir Configuration des Barres de Menus et des Barres d'Outils pour cette partie du processus de développement.
Les deux appels de fonctions membres suivants, initDocument() et initView(), permettent enfin de construire la partie que les fenêtres de l'application sont supposées fournir à l'utilisateur : une interface pour travailler avec les données que l'application est sensée manipuler ; et c'est aussi la raison pour laquelle l'architecture d'application contient trois classes *App, *View et *Doc. Pour comprendre pourquoi cette structure est utile, nous allons regarder un peu au-delà du code actuel et introduire un peu de théorie, ensuite nous reviendrons au programme pour voir comment l'architecture de KDevelop supporte ce modèle.
Typiquement, tout ce qui a été expliqué sur cette architecture est que nous avons besoin d'une instance de l'application qui contient une fenêtre principale. Cette fenêtre est chargée de fournir l'interface basique pour l'utilisateur - elle contient la barre de menus, les barres d'outils , la barre d'état et le contrôleur d'événements pour les interactions avec l'utilisateur. En plus, elle contient la zone qui est appelée la "Vue". Le but d'une vue est généralement d'afficher les données que l'utilisateur peut manipuler, par exemple un morceau de fichier texte. Cependant, le fichier texte, bien que probablement plus grand que la vue, est capable de s'afficher à l'écran, il permet à l'utilisateur d'aller dans la partie qu'il veut voir (c'est pourquoi c'est une vue) et l'utilisateur peut alors changer les données du contenu du fichier. Pour donner au programmeur une meilleure façon de séparer les parties du code de l'application, le Modèle Document-Vue a été inventé. Bien que n'étant pas un standard, il fournit la structure de fonctionnement d'une application :
un objet Contrôleur contenu par l'application
un objet Vue qui affiche les données avec lesquelles l'utilisateur veut travailler
un objet Document qui contient réellement les données qui sont manipulées.
Revenons à notre exemple de travail avec un fichier texte - là, le modèle s'applique de la façon suivante : le Document lit le contenu du fichier et fournit des méthodes aussi bien pour modifier les données que pour enregistrer à nouveau le fichier. La Vue, elle, traite les événements que l'utilisateur produit avec le clavier et la souris et utilise les méthodes de l'objet document pour manipuler les données du document.
Enfin, l'objet contrôleur est responsable de l'interaction avec l'utilisateur en fournissant les objets document et vue, ainsi que les interfaces pour envoyer des commandes comme l'ouverture et l'enregistrement. En plus, certaines méthodes de l'objet vue peuvent être fournies par des commandes accessibles par des accélérateurs clavier ou la souris sur les barres de menus et les barres d'outils .
Le modèle Document-Vue a des avantages - il sépare le code du programme à la mode "orientée objet" et par là offre plus de flexibilité ; par exemple, le même objet document peut être affiché simultanément par deux vues, soit avec une nouvelle vue dans une nouvelle fenêtre, soit en séparant la fenêtre courante qui contient alors deux objets vue qui forment la zone de vue de la fenêtre.
Si vous êtes issu des systèmes MS-Windows, vous devez être familier avec tout cela - les MFC fournissent déjà un modèle de document prêt à être utilisé. Pour les applications KDE et Qt , les choses sont un peu différentes. Qt est une boîte à outils puissante car elle fournit la majorité des classes, widgets, etc nécessaires. Mais, il n'y a eu aucune intention de gérer le modèle document-vue et comme KDE hérite de Qt , ce modèle n'a pas été non plus introduit. En fait, cela s'explique aussi par le fait que les applications X ne travaillent généralement pas en MDI (Multiple Document Interface). Chaque fenêtre principale est responsable de ses propres données et cela réduit le besoin d'un modèle de document au fait que les méthodes pour travailler sur le document sont toujours écrites dans les widgets. Actuellement, la seule exception est le projet KOffice qui fournit une suite de bureautique complète comprenant un traitement de texte, un tableur, etc. Techniquement, cela est réalisé par deux changements dans l'utilisation normale de Qt et KDE :
KOffice utilise KOM et différents mécanismes pour la communication entre objets
les applications KOffice utilisent un modèle document-vue qui permet à toutes les applications de travailler avec n'importe quelles données de KOffice.
Mais, comme l'objectif de KDevelop est d'utiliser les bibliothèques actuelles de KDE 1.1.x et Qt 1.4x, nous ne pouvons pas utiliser ce modèle par défaut - cela apparaîtra dans les prochaines versions de KDE 2 qui contiendra (probablement) deux changements majeurs par rapport à la situation actuelle :
une interface MDI pour KTMainWindow
les bibliothèques KOM qui fournissent un modèle de document.
C'est pourquoi, pour l'instant, la façon de travailler des développeurs d'applications est soit d'implanter toutes les méthodes de documents dans leur vue, soit d'essayer de reproduire eux-mêmes un modèle de document. KDevelop souscrit à cette tentative en fournissant les classes requises et les méthodes de base qui sont généralement utilisées pour un modèle Document-Vue avec les architectures d'application de Qt et KDE.
Revenons au code, vous pouvez maintenant imaginer le but des deux méthodes que nous avons mentionnées au début de cette section : les fonctions initDocument() et initView(). initDocument() construit l'objet document qui représente les données de la fenêtre de l'application et initialise les attributs de base comme le bit de modification qui indique si les données utilisées ont été modifiées par l'utilisateur. Ensuite, la méthode initView() construit le widget *View, le connecte au document et appelle la méthode setView() de KTMainWindow pour dire à la fenêtre *App d'utiliser le widget *View comme vue centrale.
Pour le développeur, il est important de savoir que durant le processus de développement, il doit :
ré-implanter dans l'objet *View les méthodes virtuelles de QWidget pour les événements du clavier et de la souris afin de pouvoir manipuler les données
ré-implanter le paintEvent() de QWidget dans l'objet *View pour redessiner (Ndt : repaint()) la vue après des modifications
compléter l'implantation pour l'impression du document via la méthode d'impression de l'objet *View
ajouter la sérialisation pour permettre à l'objet *Doc de charger et enregistrer le fichier
ajouter l'implantation de la structure des données du document à l'objet *Doc qui représente logiquement en mémoire les données du document
ajouter les méthodes auxquelles l'utilisateur peut accéder via des raccourcis clavier, des menus ou des barres d'outils .
Maintenant, après avoir créé toutes les instances de l'instance de KTMainWindow de notre application pour créer la fenêtre principale, nous devons initialiser certaines valeurs qui influencent l'apparence du programme. Pour cela, nous appelons readOptions() qui récupère toutes les valeurs et appelle les méthodes nécessaires pour définir les attributs correspondants. La bibliothèque KDE-Core contient la classe KConfig qui fournit une bonne façon de stocker les valeurs dans des fichiers de configuration et permet aussi de les relire. De plus, comme chaque instance de KApplication crée déjà son fichier de ressources, nous devons seulement accéder à ce fichier et créer nos valeurs. Comme KConfig nous donne l'objet fichier, nous devons utiliser la classe KConfigBase pour lire et écrire toutes les entrées. L'écriture est très facile avec les méthodes writeEntry() mais la lecture dépend du type d'attribut que nous voulons initialiser. Généralement, une entrée dans le fichier de configuration contient un nom de valeur et une valeur. Les valeurs qui appartiennent au même contexte peuvent être regroupées dans des groupes, c'est pourquoi nous devons définir le nom du groupe afin d'accéder ensuite à ses valeurs : le groupe doit être défini seulement une seule fois pour lire les attributs qui sont dans le même groupe. Regardons ce que nous voulons lire :
1 void KScribbleApp::readOptions() 2 { 3 4 config->setGroup("General Options"); 5 6 // bar status settings 7 bool bViewToolbar = config->readBoolEntry("Show Toolbar", true); 8 view_menu->setItemChecked(ID_VIEW_TOOLBAR, bViewToolbar); 9 if(!bViewToolbar) 10 enableToolBar(KToolBar::Hide); 11 12 bool bViewStatusbar = config->readBoolEntry("Show Statusbar", true); 13 view_menu->setItemChecked(ID_VIEW_STATUSBAR, bViewStatusbar); 14 if(!bViewStatusbar) 15 enableStatusBar(KStatusBar::Hide); 16 17 // bar position settings 18 KMenuBar::menuPosition menu_bar_pos; 19 menu_bar_pos=(KMenuBar::menuPosition)config->readNumEntry("MenuBar Position", KMenuBar::Top); 20 21 KToolBar::BarPosition tool_bar_pos; 22 tool_bar_pos=(KToolBar::BarPosition)config->readNumEntry("ToolBar Position", KToolBar::Top); 23 24 menuBar()->setMenuBarPos(menu_bar_pos); 25 toolBar()->setBarPos(tool_bar_pos); 26 27 // initialize the recent file list 28 recent_files.setAutoDelete(TRUE); 29 config->readListEntry("Recent Files",recent_files); 30 31 uint i; 32 for ( i =0 ; i < recent_files.count(); i++){ 33 recent_files_menu->insertItem(recent_files.at(i)); 34 } 35 36 QSize size=config->readSizeEntry("Geometry"); 37 if(!size.isEmpty()) 38 resize(size); 39 } |
Comme nous l'avons vu dans un des morceaux de code précédents, la première action réalisée par notre constructeur était :
config=kapp->getConfig(); |
qui positionne le pointeur config de type KConfig sur la configuration de l'application. Nous n'avons donc pas besoin de nous préoccuper de l'emplacement de ce fichier de configuration. En fait, ce fichier est, conformément au Système de Fichiers Standard de KDE (KDE FSS) (NdT : KDE FSS signifie KDE File System Standard), situé dans &$;HOME/.kde/share/config/ ; nous regarderons plus précisément le KDE FSS dans une autre section quand nous définirons l'emplacement pour l'installation des fichiers du projet. Comme le fichier de configuration est placé dans le répertoire utilisateur (NdT : user's home directory), chaque utilisateur possède sa propre apparence de l'application, excepté les valeurs qui sont situées dans le fichier de configuration global du système qui peut optionnellement être créé et installé par le programmeur dans le dossier de KDE. Mais, bien que cela puisse être utile dans certains cas, nous devrions éviter toute dépendance de l'application envers l'existence d'entrées de fichier. C'est pourquoi toutes les méthodes de lecture fournies par KConfigBase permettent d'ajouter une valeur par défaut à utiliser lorsque l'entrée n'existe pas. Une autre chose importante pour le programmeur est que le fichier de configuration est stocké sous la forme d'un fichier texte, et, pour cette raison, vous devez vous souvenir que :
l'utilisateur peut changer le fichier de configuration avec un simple éditeur de texte
si l'utilisateur veut changer les valeurs à la main, les entrées doivent être très explicites pour déterminer leur utilisation
pour les entrées qui doivent être enregistrées mais qui sont aussi critiques en terme de sécurité comme les mots de passe, vous devez chercher une solution propre pour garantir la sécurité.
Maintenant que nous connaissons les bases, nous allons analyser le code. Comme cela a été dit, nous avons juste à utiliser notre pointeur de configuration pour accéder aux valeurs. D'abord, à la ligne 4, nous définissons le groupe courant à "General Options". Cela indique que les valeurs utilisées sont des attributs relativement globaux pour l'application. Ensuite, nous lisons les valeurs pour la barre d'outils et la barre d'état - elles doivent être enregistrées lorsque l'application est fermée afin de pouvoir restaurer leur état quand l'utilisateur redémarre le programme. Comme les barres peuvent seulement être actives ou inactives, nous utilisons une valeur booléenne, c'est pourquoi notre méthode est readBoolEntry(). Le processus est identique pour les deux barres donc nous considérerons seulement les lignes 7-10 pour regarder ce qui se passe avec la barre d'outils . D'abord, nous lisons la valeur dans une variable temporaire bViewToolbar à la ligne 7. Dans le fichier, le nom de la valeur est "Show Toolbar" et, si la valeur n'est pas présente (ce qui peut être le cas lors du premier démarrage de l'application), la valeur par défaut est définie à true. Ensuite, nous positionnons la coche de l'entrée de menu pour (dés)activer la barre d'outils suivant sa valeur : nous appelons setItemChecked() pour le menu de la vue, avec l'entrée ID&_;VIEW&_;TOOLBAR et notre attribut. Enfin, nous faisons en sorte que la barre d'outils utilise cette valeur. Par défaut, la barre d'outils est visible donc nous devons faire quelque chose seulement si bViewToolbar vaut false. Avec enableToolBar() (ligne 10), nous forçons la barre à se cacher si elle est désactivée.
Ensuite, nous devons lire la position de la barre. Comme l'utilisateur peut avoir changé la position de la barre en glissant la barre avec la souris vers une autre zone de la vue, ceci doit aussi être enregistré afin de pouvoir restaurer son état. En regardant les classes KToolBar et KMenuBar, nous voyons que la position des barres peut valoir :
enum BarPosition {Top, Left, Bottom, Right, Floating, Flat} |
Comme cette valeur doit être écrite sous forme numérique, nous devons la lire avec readNumEntry() et la convertir en une valeur de position. Avec setMenuBarPos() et setBarPos(), nous disons aux barres de s'afficher.
Vous avez aussi probablement noté que le menu "Fichier" contient un menu pour les fichiers récemment utilisés. Les noms de ces fichiers sont stockés dans une liste de chaînes de caractères qui doit être enregistrée lors de la fermeture de l'application et maintenant, nous devons les lire pour restaurer le menu. D'abord, nous initialisons la liste avec les entrées enregistrées en utilisant readListEntry(). Ensuite, dans une boucle for, nous créons une entrée de menu pour chaque élément de la liste.
Enfin, nous devons juste prendre en compte la géométrie de notre fenêtre. Nous lisons son apparence avec une variable QSize contenant une valeur x et y pour la largeur et la hauteur de la fenêtre. Comme la fenêtre est initialisée par KTMainWindow, nous n'avons pas besoin de nous soucier de la valeur par défaut et nous utiliserons resize() seulement si l'entrée n'est pas vide.
Ce qui reste à expliquer dans la construction de l'application est que nous devons initialement désactiver les commandes utilisateur qui ne seraient pas disponibles si certaines instances ne correspondent pas aux critères requis. Ce sont l'enregistrement de fichier et les opérations qui utilisent le presse-papiers. Pendant la vie de l'application, nous devons y faire attention à plusieurs reprises mais cela reste vraiment simple. L'architecture nous donne seulement deux méthodes pour activer/désactiver la barre de menus et les éléments de la barre d'outils avec un seul appel à la méthode à la fois.
Au long de la section précédente, nous avons seulement surveillé ce qui se passe pendant l'appel au constructeur de notre instance de KScribbleApp qui nous donne la fenêtre principale. Après le retour à la fonction main(), nous devons appeler show() pour afficher la fenêtre. Ici, ce qui est différent d'une KApplication ou d'une QApplication, c'est que nous utilisons KTMainWindow comme instance de notre widget principal ; nous n'avons pas besoin de le définir avec setMainWidget(). Cela est fait par KTMainWindow lui-même et nous n'avons pas besoin de nous en soucier. La seule chose restante est d'interpréter la ligne de commande. Nous lisons la ligne de commande et testons si int argc est > 1, ce qui indique que l'utilisateur a lancé notre application avec kscribble nom&_;de&_;fichier&_;a&_;ouvrir. Notre fenêtre est alors sensée ouvrir le fichier ayant ce nom et appeler openDocumentFile() avec le nom du fichier.
La dernière ligne de la fonction main() fait le travail attendu : elle exécute l'instance de l'application et le programme entre dans la boucle d'événements.
Dans la section La Fonction main(), nous avons commencé à séparer le processus d'exécution par if( app.isRestored() ) et décrit le processus de lancement habituel. La suite vous donne maintenant une introduction à la gestion de sessions et comment notre application l'utilise.
Comme nous l'avons dit, la fonction main() teste si l'application a été lancée par le gestionnaire de sessions. Le gestionnaire de sessions est responsable de l'enregistrement de l'état actuel de toutes les fenêtres ouvertes de l'application sur le bureau de l'utilisateur et doit les restaurer quand l'utilisateur se connecte à nouveau, ce qui signifie que l'application n'est pas lancée par l'utilisateur mais automatiquement exécutée. La partie du code qui est exécutée était :
6 if (app.isRestored()) 7 { 8 RESTORE(KScribbleApp); 9 } |
Dans La Fonction main(), nous avons indiqué que nous testons le type de lancement en interrogeant app.isRestored(). Ensuite, la ligne 8 est exécutée. Cela ressemble à quelque chose de simple mais, en fait, il en résulte un processus d'exécution complexe que nous allons suivre dans cette section.
RESTORE(), quant à elle, est une macro fournie par KTMainWindow. Elle est remplacée par le code suivant :
if (app.isRestored()){ int n = 1; while (KTMainWindow::canBeRestored(n)){ (new KScribbleApp)->restore(n); n++; } } |
Cela restaurera toutes les fenêtres de l'application de la classe KScribbleApp en créant les instances et en appelant restore() pour la nouvelle fenêtre. Il est important de comprendre que si votre application utilise plusieurs widgets différents qui héritent de KTMainWindow, vous devez remplacer la macro et déterminer le type des widgets principaux (NdT : top widgets) en utilisant KTMainWindow::classNameOfToplevel(n) au lieu de la classe KScribbleApp.
La méthode restore() lit ensuite la partie du fichier de session qui contient les informations concernant la fenêtre. Comme KTMainWindow enregistre tout cela pour nous, nous n'avons rien de spécial à faire. Ensuite, seules les informations spécifiques à notre instance de KScribbleApp doivent être retrouvées. Souvent, c'est un fichier temporaire que nous avons créé pour enregistrer le document ou une autre initialisation qui est requise. Pour obtenir ces informations de restauration, nous devons seulement réécrire deux méthodes virtuelles de KTMainWindow, saveProperties() et readProperties(). Les informations que nous devons enregistrer pour la session sont si le fichier actuellement ouvert est modifié ou non et le nom du fichier. Si le fichier est modifié, nous obtiendrons un fichier temporaire pour l'enregistrer. Au début de la session, ces informations sont utilisées pour restaurer le contenu du document :
void KScribbleApp::readProperties(KConfig*) { QString filename = config->readEntry("filename",""); bool modified = config->readBoolEntry("modified",false); if( modified ){ bool b_canRecover; QString tempname = kapp->checkRecoverFile(filename,b_canRecover); if(b_canRecover){ doc->openDocument(tempname); doc->setModified(); QFileInfo info(filename); doc->pathName(info.absFilePath()); doc->title(info.fileName()); QFile::remove(tempname); } } else if(!filename.isEmpty()){ doc->openDocument(filename); } setCaption(kapp->appName()+": "+doc->getTitle()); } |
Comme nous avons seulement enregistré un document dans un fichier de restauration (NdT : recover file) s'il était modifié, nous positionnons le bit de modification pour indiquer que les informations n'ont pas été enregistrées dans le fichier original. Nous devons aussi faire attention au fait que le fichier de restauration possède un nom de fichier différent du fichier original qui était ouvert. C'est pourquoi, nous devons réinitialiser le nom de fichier et le chemin à l'ancien nom de fichier. Enfin, nous avons les informations que nous voulions restaurer et nous pouvons supprimer le fichier temporaire du gestionnaire de sessions.
Résumé :
Tout au long de ce chapitre, vous avez appris comment l'application est lancée, soit par un appel normal de l'utilisateur, soit par le gestionnaire de sessions. Nous avons parcouru le code pour apprendre comment les parties de l'interface visuelle de l'application sont construites et comment initialiser les attributs grâce aux entrées du fichier de configuration. Maintenant, vous pouvez exécuter l'application pour tester ces fonctions et voir comment la fenêtre du programme réagit.