Programmation sous GNUstep (1)

Nicolas Roard et Fabien Vallon 13 mars 2003

Suite a` la presentation´ du projet GNUstep parue dans le Description de l’application numer´ o 47, nous allons commencer une petite applica- tion que nous allons faire evoluer´ et etendr´ e au cours de Ce mois-ci nous allons donc commencer par quelque l’annee´ . chose de simple. Le programme que nous allons realiser´ a pour but de noter et gerer´ des tachesˆ a` faire ; le nom de l’application sera «Todo.app». News L’interface sera pour le moment tres` simple, affichant la liste des tachesˆ dans sa partie superieure´ et le – Le nouveau «text-system» (voir l’interview pa- contenu d’une tacheˆ (description, date, etc.) dans sa partie rue dans le LMF numero´ 47) a fusionne´ avec la inferieure.´ Il sera bien surˆ possible de rajouter ou suppri- branche principale deb´ ut fevrier´ . mer une tache,ˆ et memeˆ de sauver le tout dans un fichier. – Optimisations pour -gui (AppKit), no- Cela nous permettra d’aborder l’outil de RAD1 fourni tamment les methodes´ de dessin ainsi que di- avec GNUstep, Gorm, et d’introduire quelques uns des verses corrections liees´ au focus. Design Patterns2 couramment utilises´ dans une applica- – Maintenance/Corrections mineures (du dej´ a` tion GNUstep. stable) -base et -. Vous constaterez d’ailleurs au fil des articles que le fra- mework GNUstep utilise lui-memeˆ intensement´ nombre de patterns connus. Les commandes clavier sous GNUstep s’utilisent via la touche (pomme pour Apple), en gen´ eral´ on affecte cette touche a` sur Modele` – Vue – Controleurˆ un clavier de type PC. Les principales actions as- sociees´ a` la touche commande sont : Commenc¸ons par le tres` classique Modele-V` ue- – q pour Quitter Controleurˆ , que l’on peut voir sur la figure 1 page – w pour Fermer la fenetreˆ qui a le focus suivante. – h pour Cacher Ce pattern consiste a` separer´ en 3 parties une application. – o pour Ouvrir un document – Le Modele` represente´ votre partie metier´ , ’est-a-dire` – n pour Ouvrir un nouveau document la partie du code independante´ de la partie graphique – s pour Sauvegarder ou de l’interaction avec l’utilisateur. – t pour Afficher le Panneau de Fontes (si il y a) – La Vue est chargee´ de representer´ a` l’utilisateur le – c pour Afficher le Panneau de Couleurs (si il y Modele.` a) – Le Controleurˆ sert de lien entre le Modele` et la Vue. – w pour Fermer la fenetreˆ qui a le focus Cette separation´ en trois unites´ permet une conception – c pour Copier plus propre : rien n’empecheˆ de reutiliser´ votre modele` – v pour Coller ailleurs (dans une application GNUstepWeb par exemple), – x pour Supprimer ou de changer completement` votre vue en etant´ surˆ de ne Par def´ aut GNUstep n’utilise qu’un seul bouton pas affecter le reste de votre application. de la souris. Par exemple, en utilisant Gorm, on Dans notre cas l’interface graphique avec laquelle l’uti- selectionne un objet par un seul clic. En double- lisateur va interagir sera la partie «Vue». Cette interface cliquant sur l’objet on «l’ouvre» : si l’objet contient d’autres objets (cas d’une boˆıte) ou si 1Rapid Application Development 2Design Patterns de Erich Gamma, Richard Helm, Ralph Johnson, et l’objet est un conteneur, on accede` aux objets a` John Vlissides. ISBN 0201633612 l’interieur´ de celui-ci. Sinon on peut editer´ «sur place» cet objet. 1 graphique sera cre´ee´ avec Gorm. Le Modele`

(interface) Notre modele` sera ici la tacheˆ a` effectuer, c’est-a-dire` un Vue L’interface est reliée au controlleur objet contenant toutes les informations liees´ a` une tacheˆ Le controlleur mets à jour l’interface donnee.´ Nous appellerons cette classe d’objets «Todo». Agit sur l’interface Un objet Todo contiendra pour le moment trois donnees´ Affiche les informations membres : une chaˆıne de caracteres` contenant la des- Utilisateur fournit les infos au controlleur Modèle Controlleur cription de la tache,ˆ une chaˆıne de caracteres` contenant répercute les actions sur le modèle une note ev´ entuellement plus detaill´ ee,´ et enfin un entier contenant l’indice de progression de la tacheˆ (sur 100). Chaque objet pourra ev´ entuellement contenir des sous- FIG. 1 – Le pattern MVC taches,ˆ c’est-a-dire` d’autres objets de classe «Todo». Pour simplifier les choses, nous allons permettre d’avoir plusieurs tachesˆ au niveau de l’application ; nous aurons ainsi simplement un tableau contenant un ou plusieurs ob- La Del´ egation´ jets «Todo» au niveau du controleurˆ . Voici donc l’interface de notre modele` (mis dans un fichier Ce pattern consiste a` renvoyer vers un objet «aidant», dit Todo.h) : del´ egu´ e,´ certains travaux. L’approche classique en pro- grammation objet pour ameliorer´ ou specialiser´ un objet #ifndef TODO H est de le sous-classer. La del´ egation´ consiste a` ne pas mo- #define TODO H difier l’objet, mais a` simplement demander certaines infos #include ou certains traitements a` un objet «d’aide». Ce pattern permet souvent de se passer de la creation´ d’une sous- @interface Todo : NSObject classe, et simplifie d’autant le programme. { 3 Pour reprendre l’analogie donnee´ par Aaron Hillegass , NSString ∗ note; le sous-classage revient a` l’approche «Robocop» : pour NSString ∗ description; ameliorer´ le policier, on emploie des dizaines de chirur- int progress; giens, et il faut connaˆıtre parfaitement le fonctionnement NSMutableArray ∗ childs; du corps humain. C’est un outil puissant, mais qui peut id parent; etreˆ complexe a` manipuler. } La del´ egation´ revient a` l’approche «K2000» : pour // Constructeur ameliorer´ Michael, on utilise simplement un outil cre´e´ −(id) initWithDescription: (NSString∗) pour lui, la voiture Kit, ayant tout ce qu’il faut comme description andNote: (NSString∗) note; gadgets indispensables a` la vie tem´ eraire´ d’un justicier a` // modifieurs roulettes. −(void) setDescription: (NSString ∗) Par exemple, quand un widget NSTableView (affichant un description; tableau ou une liste) doit s’afficher, au lieu de le sous- −(void) setNote: (NSString ∗) note; classer pour qu’il reponde´ a` nos besoins, on lui fournit un −(void) setProgress: (int) progress; objet del´ egu´ e.´ −(void) setChilds: (id) childs; Quand le NSTableView voudra se dessiner, il demandera −(void) addChild: (id) child; simplement a` son del´ egu´ e´ des choses comme «Combien −(void) setParent: (id) parent; ai-je de lignes ?» ou «Qu’est-ce qui doit etreˆ affiche´ dans −(void) removeChild: (id) child; la premiere` colonne de la troisieme` ligne ?». // accesseurs −(NSString ∗) desc; −(NSString ∗) note; Realisation´ −(int) progress; −(id) parent; Voyons comment nous pouvons appliquer ces patterns a` −(NSArray∗) childs; notre programme. @end

3«Cocoa Programming for Mac OS X» de Aaron Hillegass, ISBN 0-201-72683-1 #endif

2 Chaque objet Todo pourra donc contenir ev´ entuellement −(id) parent { return parent; } des sous-tachesˆ (stockees´ dans le tableau childs) ; on pourra acceder´ a` la tacheˆ parente si elle existe en envoyant /∗ Modifieurs ∗/ le message parent : id tacheParente = [maTache parent] ; −(void) setDescription : (NSString ∗) Voici le code de notre modele` (mis dans un fichier description { Todo.m) : [ description release]; description = [[NSString alloc] #include ”Todo.h” initWithString: description]; } @implementation Todo −(void) setNote : (NSString ∗) note { /∗ Constructeurs ∗/ [ note release]; note = [[NSString alloc] initWithString −(id) init { : note]; self = [super init]; } note = [[NSString alloc] init]; description = [[NSString alloc] init]; −(void) setProgress: (int) progress { childs = [[NSMutableArray alloc] init]; if ((progress >= 0) && (progress < 100)) parent = nil; { progress = 0; progress = progress; return self; } } }

−(id) initWithDescription: (NSString∗) −(void) addChild: (id) child { description andNote: (NSString∗) note { [ childs addObject: child]; self = [super init]; } description = [[NSString alloc] initWithString: description]; −(void) setParent: (id) parent { note = [[NSString alloc] initWithString ASSIGN ( parent, parent); : note]; } childs = [[NSMutableArray alloc] init]; parent = nil; −(void) setChilds: (id) childs { progress = 0; ASSIGN ( childs, childs); return self; } } −(void) removeChild: (id) child { /∗ Destructeur ∗/ [ childs removeObject: child]; } −(void) dealloc { RELEASE( childs); − (void) encodeWithCoder: (NSCoder∗) coder RELEASE( note); { RELEASE( description); [coder encodeObject: description]; [super dealloc]; [coder encodeObject: note]; } [coder encodeValueOfObjCType: @encode (int) at: & progress]; /∗ Accesseurs ∗/ [coder encodeObject: parent]; [coder encodeObject: childs]; −(NSString ∗) desc { return description; } } −(NSString ∗) note { return note; } −(int) progress; { return progress; } − (id) initWithCoder: (NSCoder∗) coder { −(NSArray ∗) childs { return childs; }

3 if (self = [super init]) Creation´ de l’interface graphique { [self setDescription: [coder «Pour le novice ou l’utilisateur occasionnel, l’in- decodeObject]]; terface doit etrˆ e simple et facile a` apprendre [self setNote: [coder et a` retenir. Elle ne devrait pas necessiter´ un decodeObject]]; reapr´ entissage apres` une longue absence de l’or- [coder decodeValueOfObjCType: dinateur.» @encode (int) at: & progress (guide de l’interface NeXT) ]; [self setParent: [coder Lancez Gorm : openapp Gorm.app. decodeObject]]; Creons´ une nouvelle application : Document→New Ap- [self setChilds: [coder plication. decodeObject]]; } return self; }

@end

La Vue

Memeˆ si il est tout a` fait possible de dev´ elopper l’in- terface «a` la main», il existe sous la plateforme de dev´ eloppement GNUstep un outil RAD, cloneˆ de l’Inter- face Builder d’OPENSTEP/MacOSX, appele´ Gorm4. Bien que le numero´ de version ne soit que 0.2.6, Gorm FIG. 2 – CrÂeation d'une application est dej´ a` fort utilisable (et utilise)´ pour le dev´ eloppement d’interfaces graphiques. Dans les outils (Menu Tools) cliquez sur Palettes et Ins- pector. Gorm fait partie du projet GNUstep et se trouve donc dans La fenetreˆ palette contient les divers objets necessaires´ le CVS. a` la constitution d’une interface (fenetres,ˆ bou- tons,textfields...) Dans cette fenetreˆ (voir figure 3 page suivante), en cli- quant sur les differentes´ sections, nous avons dans l’ordre Installation de Gorm et de gauche a` droite : – Les Menus et Items de menus (pour le menu de l’appli- cvs -z3 -d :pser- cation) ver :anoncvs@subversions..org :2401/cvs- – Les Fenetres/Pˆ anneaux root/gnustep co Gorm – Les Controlesˆ (Boutons/TextFields/Sliders..) cd Gorm – Les Conteneurs (Tableaux/OutlineView (Tree- make View)/TextView) make install La fenetreˆ Inspecteur represente´ les differentes´ vues de l’objet que l’on manipule ; la figure 2 nous montre par Vous pouvez aussi recup´ erer´ des packages tgz sur le site exemple dans l’inspecteur les attributs de la fenetreˆ (NS- du projet GNUstep (http://www.gnustep.org). Window) de notre application. Les inspecteurs se retrouvent tres` souvent dans les appli- 4pour Graphic Object Relationship Modeler (ou aussi GNUstep Ob- cations *step ; cela permet d’afficher au besoin plus de ject Relationship Modeler) details´ sur un document par exemple, au lieu d’encombrer

4 FIG. 4 – Guides pour positionner les objets

tion». Dans l’inspecteur, mettez l’identifieur DESCRIP- FIG. 3 – Palette TIONTAG a` cette colonne. Faites de memeˆ avec la se- conde colonne avec «Status» en titre de colonne et STA- TUSTAG en identifieur. inutilement l’ecran´ avec ces informations quand elles ne Posez ensuite un NSTextField (qui contiendra la descrip- sont pas necessaires.´ tion d’un Todo), un NSTextView (qui contiendra le detail´ Dans l’inspecteur de Gorm, nous avons les vues suivantes ev´ entuel du Todo), et un NSSlider horizontal pour la pro- (en manipulant la liste deroulante)´ : gression. – Attributs : permet de definir les attributs du widget Ajoutez les labels correspondants a` cotˆ e´ des widgets. Pour selectionn´ e´ editer´ un label, double-cliquez simplement dessus et en- – Connection : permet de definir´ les connexions entre les trez le texte («Description»,«Note»,«Progression»).Ali- differents´ objets (graphiques ou non) gnons le texte des labels a droite. – Size : la taille et le resizing de l’objet – Help : l’aide L’interface de notre programme Todo.app sera constituee´ d’un widget NSOutlineView, affichant la liste des tachesˆ de fac¸on arborescente (puisque nous aurons ev´ entuellement des sous-taches),ˆ accompagne´ de « » quelques boutons (ajouter une tacheˆ («+»), ajouter une Passons au menu ; ajoutons l’item infos . sous-tacheˆ («<»), supprimer une tacheˆ («-»)). Pour le moment, la partie inferieure´ de la fenetreˆ de Todo.app affichera directement le contenu de la tacheˆ selectionn´ ee.´ Elle contiendra deux NSTextFields (Des- cription et Note) et un NSProgressView affichant la pro- gression de la tache.ˆ On ajoutera egalement´ un bouton Attention l’ordre est important ; mettre par exemple les «Update» qui sera utilise´ pour mettre a` jour une tacheˆ Pref´ erences´ ou le Panneau d’information ailleurs que dans que l’on a modifiee.´ le sous menu Infos (qui doit etreˆ le premier menu) serait Nous changerons c¸a le mois prochain pour avoir un ins- a` priori une faute d’ergonomie (du moins, un non-respect pecteur (une fenetreˆ separ´ ee)´ sur une tacheˆ au lieu de tout de la guideline *step). inclure dans la fenetreˆ principale. La figure 5 page suivante montre le resultat´ que vous de- Allez dans la section «Conteneur» du panneau Palette et vez obtenir pour l’interface graphique. posez un widget NSOutlineView. Lorsque vous deplacez´ les objets une fois poses´ dans la fenetre,ˆ vous verrez des «aimants» (les traits rouges) permettant de positionner Connexion de la vue au controleurˆ correctement vos objets en suivant la guideline (figure 4). Nous avons maintenant une Vue (notre interface gra- Double-cliquez ensuite dessus, puis une deuxieme` fois sur phique), ainsi qu’un modele` (notre classe Todo). Il nous le titre de la colonne 1 (pour l’editer),´ et ecri´ vez «Descrip- manque un controleurˆ pour faire fonctionner le tout.

5 Creons´ notre controleurˆ . Sous Gorm, dans le panneau Document allez dans le Class Manager (figure 6). Nous allons sous-classer la classe NSObject. Cliquez sur NSObject pour la selectionner´ si ce n’est deja´ fait. Dans le Menu «Classes» de Gorm (je vous conseille de le detacher´ et de le mettre pres` du panneau Document), selectionnez´ «Create Subclass...». Une nouvelle classe apparaˆıt, appellee´ «NewClass». Double-cliquez dessus pour changer son nom en «TodoController», puis ap- puyez sur entree´ pour valider le changement de nom. Cliquez sur le rond gris dans la colonne «Outlet» res- semblant a` une espece` de prise electrique,´ et ajoutez un Outlet (Classes→Add Outlet/Action). Renommez l’outlet en «descriptionText». Ajoutez ensuite successivement les outlets «noteTextView» et «todolistView» (figure 7).

FIG. 5 – L'interface graphique de notre Application

Quelles vont etreˆ les actions du controleurˆ ? On veut pou- voir : – visualiser une tacheˆ – ajouter une tacheˆ : addTodo – ajouter une sous-tacheˆ : addSubTodo – supprimer une tacheˆ : removeTodo – mettre a` jour une tacheˆ : updateTodo FIG. 7 – Ajout des outlets Notre controleurˆ stockera la liste des tachesˆ dans un ta- bleau (NSMutableArray), les differentes´ actions seront di- Des´ electionnez´ la classe puis cliquez sur le deuxieme` rectement reliees´ aux boutons de notre interface. Un clic point gris de TodoController representant´ les Actions et sur une ligne de notre liste de tachesˆ mettra a` jour les ajoutez (toujours par Classes→Add Outlet/Action) les ac- champs Note, Description, pour visualiser le contenu de tions addTodo, addSubTodo, removeTodo, et updateTodo la tacheˆ selectionn´ ee.´ (figure 8). Le controleurˆ devra donc avoir acces` aux champs Note et Description, ainsi qu’au NSOutlineView listant les taches,ˆ pour mettre a` jour leur etat.´ Le controleurˆ aura donc des «pointeurs» vers ces widgets ; ce type de pointeurs est appelle´ «outlet» dans la terminologie GNUstep.

FIG. 8 – Ajout des actions

Notre controleurˆ est pretˆ a` etreˆ utilise.´ Re-selectionnez´ la classe TodoController dans le Class Manager et cli- quez sur le menu Classes→Instanciate. Une instance de la classe TodoController (figure 9 page suivante) se retrouve alors dans la section Object du panneau Document. Il reste a` connecter nos objets a` cette instance de la classe FIG. 6 – Le gestionnaire de classes TodoController.

6 Vous avez alors l’image qui s’affiche dans le bouton Ajouter, tirez alors la souris jusqu’au boutton Ajouter une sous-tacheˆ («<»). L’image apparaˆıt alors. Dans l’Ins- pecteur selectionnez´ alors nextKeyView. et cliquez sur «connect». Procedez´ de memeˆ entre le bouton Ajouter une sous-tacheˆ («<») et le bouton Supprimer («-»), puis entre Supprimer et le champs Description, puis entre le champs Description et le champs Note, entre le champs Note et le bouton Update, et finalement, bouclez entre le bouton Update et le bouton Ajouter («+»). Cela permettra FIG. 9 – la classe TodoController instanciÂee a` l’utilisateur de cycler entre tous les widgets en utilisant la touche «Tab». Cliquez sur l’instance de TodoController en laissant ap- Ainsi votre interface sera plus facilement utilisable au cla- puye´ la touche control . L’iconeˆ (comme vier. Pensez a` sauver vos ajouts. Source) apparaˆıt, on tire la souris jusqu’au widget NSOut- lineView utilise´ pour lister nos taches.ˆ L’iconeˆ (comme Code du Controleurˆ Target) apparaˆıt, on relache la souris. Dans l’inspec- Nous pouvons finir de nous occuper de notre controleurˆ : « » teur, cliquez sur l’outlet todolistView propose,´ puis sur dans le panneau Document, retournez dans le Class « » « » Connect . L’outlet todolistView de notre controleurˆ Manager, et selectionnez´ TodoController. Puis cliquez est ainsi connecte´ avec le widget NSOutlineView. dans le menu «Classes» de Gorm et selectionnez On fait de memeˆ pour connecter les outlets restants l’entree´ «Create Class Files». Ainsi le squelette de notre (champs Note et Description). controleurˆ sera automatiquement gen´ er´ e´ par Gorm. Nous On effectue la manipulation inverse (appui sur control, rajoutons dans le controleurˆ un tableau pour contenir la etc.), des boutons («+»,«<»,«-» et «Update») de notre liste des Todo, que l’on appelle todoArray. interface vers l’icone de l’instance TodoController pour Pour le moment notre controleurˆ reste tres` simple : connecter les Actions ; dans l’inspecteur, cliquez sur «tar- get» puis choisissez les actions du controleurˆ correspon- @interface TodoController : NSObject dantes aux boutons. { Notre controleurˆ est ainsi connecte´ avec les actions et out- id descriptionText; lets dont il a besoin. id noteTextView; id todolistView; Refaites la memeˆ manipulation entre l’outline view (la NSMutableArray ∗todoArray; liste des taches)ˆ et le controleurˆ , avec l’outline view en } tant que source et le controleurˆ en temps que cible. Dans − (void) addTodo: (id)sender; l’inspecteur, selectionnez´ l’outlet «dataSource» de l’out- − (void) addSubTodo: (id)sender; line view et cliquez sur «Connect». Refaites encore la − (void) removeTodo: (id)sender; memeˆ chose mais cette fois en connectant l’outlet «de- − (void) updateTodo: (id)sender; legate» de l’outline view au controleurˆ . Ainsi, l’outline view demandera au controleurˆ les infos necessaires´ a` son Nous allons y rajouter les methodes´ «Data Source» uti- affichage. lisees´ par l’outline view pour son affichage : Sauvegardons le tout (Document→Save As...) sous le nom Todo.gorm. − (id) outlineView: (NSOutlineView ∗) outlineView child: (int) index ofItem: ( id) item; Navigation au clavier − (int)outlineView: (NSOutlineView ∗) outlineView numberOfChildrenOfItem: (id Il est possible de connecter les objets graphiques entre ) item; eux, pour indiquer dans quel ordre les objets s’activeront − (BOOL)outlineView: (NSOutlineView ∗) quand on naviguera au clavier (touche tab). outlineView isItemExpandable: (id) item Pour cela selectionnez´ le bouton Ajouter («+») et ap- ; puyez simultanement´ sur la touche «control» (CTRL) et − (id)outlineView: (NSOutlineView ∗) le bouton de la souris. outlineView objectValueForTableColumn

7 : (NSTableColumn ∗) tableColumn byItem − (void) setProgress: (id)sender; : (id) item; // NSOutlineView Data Source Si notre objet TodoController repond´ a` ces methodes,´ − (id) outlineView: (NSOutlineView ∗) l’outline view s’en servira pour son affichage. Les outlineView child: (int) index ofItem: ( methodes´ sont assez simples en fait : id) item; – La premiere` doit retourner le fils place´ a` la position in- − (int)outlineView: (NSOutlineView ∗) dex de l’objet item. outlineView numberOfChildrenOfItem: (id – La seconde doit retourner le nombre de fils que contient ) item; un objet item. − (BOOL)outlineView: (NSOutlineView ∗) – La troisieme` methode´ doit retourner YES (vrai) si l’ob- outlineView isItemExpandable: (id) item jet passe´ en parametre` item contient ou non des fils. ; – La quatrieme` methode´ doit retourner la valeur de la − (id)outlineView: (NSOutlineView ∗) cellule pour une colonne donnee´ par rapport a` l’ob- outlineView objectValueForTableColumn jet fourni en parametre` (donc d’une ligne de l’outline : (NSTableColumn ∗) tableColumn byItem view). On peut se servir des «tags» que l’on a rentre´ : (id) item; tout a` l’heure dans Gorm pour faire la selection.´ On peut noter que le widget NSOutlineView est deri´ ve´ du // NSOutlineView methode´ del´ egu´ ee´ widget NSTableView ; son fonctionnement est un peu plus − (BOOL)outlineView: (NSOutlineView ∗) complexe (structure arborescente oblige), mais est base´ outlineView shouldSelectItem: (id) item sur les memesˆ principes. En fait, la version originale de ; cet article utilisait un NSTableView, et ne proposait pas une structure arborescente pour les Todo, mais puisqu’on @end a et´ e´ decal´ e´ d’un mois, on en a profite´ pour rajouter des #endif trucs ;) On ajoute egalement´ a` notre controleurˆ une fonction Il ne reste donc qu’a` rajouter le code correspondant (dans del´ egu´ ee´ de l’outline view, qui sera appelee´ quand l’uti- un fichier TodoController.m) : lisateur selectionnera´ un item (un Todo) ; ainsi on pourra mettre a` jour les champs Note, Description et Progression #include ”Todo.h” par rapport au Todo selectionn´ e.´ #include ”TodoController.h” L’interface du controleurˆ (on le sauvera dans un fichier TodoController.h) sera donc : @implementation TodoController

#ifndef TODOCONTROLLER H −(id) init { #define TODOCONTROLLER H self = [super init]; #include todoArray=[[NSMutableArray alloc] init]; return self; @interface TodoController : NSObject } { id descriptionText; −(void) dealloc { id noteTextView; RELEASE(todoArray); id todolistView; [super dealloc]; id sliderView; } id progressView; NSMutableArray ∗todoArray; // Actions } −(void) addTodo:(id) sender { // Actions Todo ∗aTodo = [[Todo alloc] − (void) addTodo: (id)sender; initWithDescription: [descriptionText − (void) addSubTodo: (id)sender; stringValue] − (void) removeTodo: (id)sender; andNote: [noteTextView string]]; − (void) updateTodo: (id)sender; [todoArray addObject: aTodo];

8 [aTodo release]; [todo setNote: [noteTextView string]]; [todolistView reloadData]; [todo setProgress: [progressView } doubleValue]]; [todolistView reloadData]; −(void) addSubTodo:(id) sender { } int row = [todolistView selectedRow]; } if (row != −1) { −(void) setProgress:(id) sender { id item = [todolistView itemAtRow: row [progressView setDoubleValue: [sender ]; intValue]]; Todo ∗aTodo = [[Todo alloc] } initWithDescription: [ descriptionText stringValue] /∗ methodes´ dataSource de l ’ outline view ∗/ andNote: [noteTextView string]]; − (id) outlineView: (NSOutlineView ∗) [aTodo setParent: item]; outlineView child: (int) index ofItem: ( [item addChild: aTodo]; id) item [aTodo release]; { [todolistView reloadData]; if (item == nil) // racine } { } return [todoArray objectAtIndex: index ]; −(void) removeTodo:(id) sender { } int selectedRow = [todolistView return [[item childs] objectAtIndex: index selectedRow]; ]; if (selectedRow != −1) } { id item = [todolistView itemAtRow: − (int)outlineView: (NSOutlineView ∗) selectedRow]; outlineView numberOfChildrenOfItem: (id id parent = [item parent]; ) item if (parent != nil) { { if (item == nil) return [todoArray count]; [parent removeChild: item]; return [[item childs] count]; } } else { − (BOOL)outlineView: (NSOutlineView ∗) [todoArray removeObject: item]; outlineView isItemExpandable: (id) item } { [todolistView reloadData]; if ([[item childs] count] > 0) return YES; } return NO; } }

−(void) updateTodo:(id) sender { − (id)outlineView: (NSOutlineView ∗) int selectedRow = [todolistView outlineView objectValueForTableColumn selectedRow]; : (NSTableColumn ∗) tableColumn byItem if (selectedRow != −1) : (id) item { { Todo∗ todo = [todolistView itemAtRow: if ( [[tableColumn identifier] selectedRow]; isEqualToString: @”DESCRIPTIONTAG” [todo setDescription: [descriptionText ] ) stringValue]]; {

9 if (item == nil) Les Makefiles return [[todoArray objectAtIndex: 0] desc]; GNUstep fourni son propre systeme` de gestion de projet pour les differentes´ machines et environnement sans pas- return [item desc]; ser par les complexes /automake. Le fichier ma- } kefile doit s’appeller GNUmakefile. else if ( [[tableColumn identifier] gnustep-make est base´ sur des variables d’environne- isEqualToString: @”STATUSTAG”] ) ments. Voici un exemple de GNUmakefile pour un outil return [NSString stringWithFormat:@”%i/100 (TOOL) – c’est-a-dire` une application non graphique. Les ”, [item progress]]; differents´ scripts se trouvent dans /System/Makefiles/ return [NSString stringWithString: @”VOID” include $(GNUSTEP MAKEFILES)/common.make ]; TOOL NAME= MonOutils } MonOutils OBJC FILES=main.m source.m include $(GNUSTEP MAKEFILES)/tool.make /∗ methodes´ delegate de l ’ outline view ∗/ Voici une petite liste de variables courramment utilisees´ ; − (BOOL)outlineView: (NSOutlineView ∗) On les prefix´ es par le de l’application graphique : outlineView shouldSelectItem: (id) item – SUBPROJECT= liste des sous-projets { – OBJC FILES les fichiers ObjC if (item) – C FILES les fichiers C { – HEADERS les en-tetesˆ des notre programme [descriptionText setStringValue: [item – HEADER FILES Les en-tetesˆ des notre pro- desc]]; gramme que l’on veut installer (dans le cas d’un Fra- [noteTextView setString: [item note]]; mework) [progressView setDoubleValue: [item – RESOURCE FILES les ressources progress]]; necessaires´ (les images,.gorm...) [sliderView setIntValue: [item – LOCALIZED RESOURCE FILE les res- progress]]; sources localisables return YES; – LANGUAGE Langues supportees´ } Voici donc le fichier GNUmakefile de notre application return NO; Todo.app : } include $(GNUSTEP MAKEFILES)/common.make APP NAME=Todo @end Todo OBJC FILES=main.m TodoController.m Todo.m Todo RESOURCE FILES=Todo.gorm Todo MAIN MODEL FILE=Todo.gorm include $(GNUSTEP MAKEFILES)/application. Le main de notre programme make Compilons : make Le main de notre programme se contente d’appel- Notre application se nomme Todo.app. ler l’objet NSApplication et de faire les initialisations Pour le moment, vous pouvez rajouter, modifier et suppri- necessaires´ : mer des tachesˆ ... mais il nous manque un leger´ detail´ : la sauvegarde et la lecture de fichiers Todo ! int main(int argc, const char ∗argv[]) { return NSApplicationMain(argc, argv); Archiver un objet } Archiver un objet consiste a` le transformer en un flux bi- naire, independant´ de l’architecture, qui preserv´ e l’iden- On le sauvegarde dans un fichier main.m. tite´ et les relations entre les objets et leur valeurs. Cela

10 permet par exemple d’envoyer un objet sur le reseau´ ou [coder encodeObject: parent]; de le sauver sur disque simplement. Desarchi´ ver un objet, [coder encodeObject: childs]; c’est realiser´ l’operation´ inverse, recreer´ un objet a` partir } d’un flux binaire. GNUstep permet tres` simplement cela. Pour qu’un objet puisse etreˆ archive/d´ esarchi´ ve,´ il suffit − (id) initWithCoder: (NSCoder∗) coder { qu’il reponde´ au protocole NSCoding, c’est-a-dire` qu’il if (self = [super init]) reponde´ aux messages encodeWithCoder : (pour archi- { ver un objet) et initWithCoder : (pour desarchi´ ver un ob- [self setDescription: [coder jet). decodeObject]]; Ces deux fonctions rec¸oivent un objet de type NSCoder en [self setNote: [coder parametre.` La classe abstraite NSCoder sert a` representer´ decodeObject]]; le flux binaire, et a` lui rajouter les differentes´ donnees´ [coder decodeValueOfObjCType: membres de l’objet que l’on veut archiver. En effet, l’objet @encode (int) at: & progress est responsable de l’archivage de ses donnees´ membres. Il ]; n’est pas oblige´ d’archiver toutes ses donnees´ membres [self setParent: [coder (certaines peuvent ne pas etreˆ importantes par exemple). decodeObject]]; Il faut par contre veiller a` ce que l’ordre d’archivage et [self setChilds: [coder de desarchi´ vage des donnees´ soit le memeˆ sous peine de decodeObject]]; problemes` (voir le source suivant) ! Si vous voulez archi- } ver un objet deri´ vant d’un objet qui repond´ egalement´ au return self; protocole NSCoding, vous devez alors ajouter la ligne : }

[super encodingWithCoder: coder]; Et voila` ! nos objets Todo savent desormais´ s’archiver et se desarchi´ ver. On doit maintenant ajouter des fonctions au deb´ ut de la fonction d’archivage encodeWithCoder et pour lire et sauver nos Todo dans notre controleurˆ .

self = [super initWithCoder: coder]; Modification de TodoController au deb´ ut de la fonction de desarchi´ vage initWithCoder, de fac¸on a` ce que les donnees´ membres de l’objet pere` On rajoute deux prototypes de methodes´ dans TodoCon- puissent etreˆ ev´ entuellement archivees´ si besoin est. troller.h : Dans notre cas, ce n’est pas necessaire,´ Todo deri´ vant di- ( ) saveFile: ( ) sender; rectement de NSObject. − void id ( ) loadFile: ( ) sender; Les objets du framework GNUstep savent directement − void id s’archiver ou se desarchi´ ver, il suffira donc d’utiliser la On rajoute le corps de ces fonctions dans TodoControl- fonction encodeObject : sur l’objet coder passe´ en pa- ler.m : rametre.` Par contre, pour des types C de base, il faut passer par −(void) saveFile: (id) sender { les fonctions encodeValueOfObjcType : et decodeVa- int ret; lueOfObjCType : auquel on passe une macro @encode() NSSavePanel∗ panel = [NSSavePanel definissant´ le type et l’adresse de la variable que l’on veut savePanel]; archiver. [panel setRequiredFileType: @”todo”]; Nous rajoutons donc les fonctions encodeWithCoder : [panel setDirectory: [[NSFileManager et initWithCoder : a` notre objet Todo : defaultManager] currentDirectoryPath ]]; /∗ Archivage de l ’ objet ∗/ ret = [panel runModal]; − (void) encodeWithCoder: (NSCoder∗) coder if (ret == NSFileHandlingPanelOKButton) { { [coder encodeObject: description]; [[NSArchiver [coder encodeObject: note]; archivedDataWithRootObject: [coder encodeValueOfObjCType: @encode todoArray] writeToFile: [panel (int) at: & progress]; filename] atomically: YES];

11 } as...» au controleurˆ TodoController, et connectez les aux } actions «loadFile :» et «saveFile :» maintenant presentes´ dans le controleurˆ . Sauvez le fichier Gorm, refaites un −(void) loadFile: (id) sender { make, et voila ! int ret; Vous avez maintenant une version de Todo.app qui fonc- NSOpenPanel∗ panel = [NSOpenPanel tionne, certes de fac¸on basique, mais completement.` Le openPanel]; mois prochain nous modifierons le programme pour ajou- [panel setAllowsMultipleSelection: NO]; ter un Inspecteur ... d’ici la,` n’hesitez´ pas a` passer sur [panel setDirectory: [[NSFileManager le channel #gnustep sur l’irc freenode (irc.debian.org par defaultManager] currentDirectoryPath exemple) ! ]];

ret = [panel runModalForTypes: [NSArray Nicolas Roard, arrayWithObject: @”todo”]]; Fabien Vallon, if (ret == NSOKButton) Merci a` Vincent Ricard et Cyril Siman pour la re- { lecture de cet article ! id file = [NSUnarchiver Quelques liens : unarchiveObjectWithData: Le wiki GNUstep : http://wiki.gnustep. [NSData dataWithContentsOfFile org/ : [[panel filenames] La page tutoriels du wiki : http://wiki. objectAtIndex: 0]]]; gnustep.org/index.php/Tutorials ASSIGN (todoArray, file); Un tres` bon tutorial de Yen-Ju : http: [todolistView reloadData]; //www.people.virginia.edu/˜yc2w/ } GNUstep/Tutorial/ }

Quelques remarques : on a choisi d’utiliser ici le File- Type «todo», c’est a` dire l’extension .todo, pour nos fi- chiers sauvegardes.´ Le point interessant´ est ici l’utilisa- tion des classes NSArchiver et NSUnarchiver, auquel on passe simplement le tableau todoArray contenant nos ob- jets Todo.

Modification du fichier gorm

Maintenant que tout fonctionne parfaitement et que notre application sait lire et sauver des fichiers Todos, il se- rait peut etreˆ interessant´ de permettre a` l’utilisateur de le faire... Relanc¸ons Gorm sur notre fichier Todo.gorm : gopen Todo.gorm. Allez dans le Class Manager, et rechar- gez la classe TodoController («Classes→Load Class...» puis lire le fichier TodoController.h). Maintenant, rajouter deux items au menu de notre application :

Ensuite, reliez simplement les entrees´ «Open...» et «Save

12