Accueil > Les langages de scripting > Intégration d’AppleScript et de Cocoa

Intégration d’AppleScript et de Cocoa

Par Brad Dominy, le 22/02/2001

Traduit par Thierry, le 25/09/2002

Une fois encore, bienvenue ! La dernière fois, nous avons abordé quelques-unes des idées qui pourraient pousser un développeur à ajouter le support d’AppleScript à ses applications. Une application scriptable peut être intégrée à d’autres applications pour accomplir une série de taches de manière autonome et intelligente. Cela ajoute de la valeur à l’application en faisant d’elle l’élément d’un plus grand shéma, celui ci étant contrôlé par l’utilisateur pour accomplir une action.

Bien que cela ait toujours été vrai pour les applications scriptables, la possibilité d’implémenter le scripting était bien plus difficile sous Mac OS classic et l’API Toolbox Mac. Mac OS X a introduit un nouvel ensemble d’outils de programmation et de frameworks qui incluent le scripting du sol au plafond, pour dire ainsi. Les outils sont Project Builder et Interface Builder, qui sont utilisés pour écrire votre code programme et concevoir votre interface utilisateur.

Cocoa est l’ensemble évolué de frameworks hérités de l’époque NeXTStep, et vous deviez l’imaginer comme un ensemble d’objets que vous pouvez arranger, avec vos propres objets, pour créer votre application. En utilisant ces nouvelles technologies, vous obtenez le support du scripting gratuitement. La dernière fois, nous avons démontré comment créer une application scriptable en quatre étapes en activant simplement le suppport du scripting compris dans Cocoa.

Cette fois, nous allons nous glisser derrière le rideau et nous attaquer à la description du système de scripting Cocoa dans sa globalité. Nous allons commencer par écrire un peu de code ObjC pour accroître le support de ce système intégré de scripting.

Révision des Objets pour Cocoa et AppleScript à la fois

Pour commencer, tout le monde doit comprendre ce que sont les objets et comment ils sont utilisés dans la programmation orientée objet, sachant qu’AppleScript et Cocoa ont tous les deux une nature orientée objet. Vous pouvez imaginer un objet comme un container de lignes de code qui est caractérisé par une utilisation particilière au sein d’une application. Ces objets peuvent contenir des variables qui servent à capturer l’état d’un objet et définissent les méthodes qui opèrent dessus. Vous pouvez apparenter l’état d’un objet à ses propriétés. Pour illustrer ceci, imaginez un véhicule caractérisé par sa fabrique, son modèle, sa couleur, son prix, etc. Les méthodes sont des actions qui effectuent quelque chose avec l’objet. Dans notre exemple, “accélérer” serait une méthode qui augmenterait la vélocité du véhicule, la vélocité étant une propriété de ce véhicule particulier.

Cela mène à deux nouvelles idées, celles des classes et des instances. Les classes représentent le type générique des objets créés. La classe “véhicule” comprend tous les types de transports motorisés à quatre roues, elle ne référence, en langage orienté objet, aucune voiture, mais elle se comporte comme un modèle à partir duquel des voitures particulières seront créées.

Les objets créés à partir des définitions de la classe sont appelées des instances, et elles contiennent chacune toutes les propriétés décrites dans ces définitions. Vous devez aussi vous souvenir que les classes sont, pour la plupart, des notions abstraites qui modélisent l’aspect des futures objets. C’est l’instance de ces objets qui agit réellement dans vos programmes.

Une dernière notion se rapportant à la POO (Programmation Orientée Objet) dont nous devons discuter concerne l’idée d’héritage. Pour revenir à notre image précédente sur les véhicules, une voiture est une description abstraite du type véhicule. Un véhicule peut être une voiture, mais il peut aussi être un camion ou un avion ou tout ce qui tourne autour. Donc, une classe véhicule serait la ’super-classe’, ou la classe de laquelle la classe voiture descendrait. La classe voiture est une sous-classe de la classe véhicule. De manière similaire, la classe véhicule peut elle aussi avoir une super-classe, comme, disons, une classe abstraite appelée “transport”. Cela peut continuer encore et encore, mais doit se terminer à la super-classe la plus haute, qui n’est qu’autre que la classe de l’objet racine (root). Vous ne pouvez avoir plus général que ça !

Sous Cocoa, La classe de l’objet racine est appelée NSObject, et toutes les autres classes d’objets Cocoa en sont des sous-classes. Cette relation super-classe / sous-classes entre les objets est référencée en tant que hiérarchie objet, le sommet de cette hiérarchie étant toujours NSObject avec des classes de plus en plus spécifiques héritant les unes des autres. Vous définirez un type différent quand vous aurez à séparer des objets génériques les uns des autres, comme vous sépareriez les voitures des camions ou des avions puisqu’ayant perçu que tous les véhicules ne sont pas des voitures.

En ce moment, vous vous demandez peut être “Quel intérêt d’avoir toutes ces classes si je veux juste fabriquer une voiture ?”. L’idée est que les objets héritent des propriétés et des méthodes de leurs super-classes, ce qui veut dire que vous pouvez définir des propriétés ou des actions communes dans une super-classe et ne pas avoir à vous en soucier au moment de créer des instances de la sous-classe.

Ainsi, la classe véhicule pourrait définir une variable pour gérer sa vélocité sous forme de nombre et aussi une méthode appelée “accélérer” qui augmenterait la valeur de cette variable. Alors, à la création de la sous-classes voiture, camion, train, avion, etc…, elles héritent toute de la variable vélocité et de la méthode accélérer. Je peux donc régler la vélocité et l’augmenter avec la méthode accélérer, sans code supplémentaire.

Bien sûr, vous pouvez surpasser les méthodes dans les sous-classes. Par exemple, si vous souhaitiez limiter la vélocité d’une voiture à 120 km/h, la classe voiture pourrait définir sa propre méthode accélérer avec en plus un contrôle sur le respect de cette limite.

Il y a beaucoup d’autres aspects de la programmation orientée objet, mais l’essentiel, concernant de près ou de loin le scripting, tient dans les notions de hiérarchie d’objets et d’héritages des variables et méthodes des super-classes.

La hiérarchie des objets AppleScript

Si vous connaissez bien AppleScript, vous avez sûrement déjà vu le lien entre la syntaxe AppleScript et cette hiérarchie objet.

tell application "MyApp"
get the first word of the first paragraph of document 1
end tell

Ceci est un extrait AppleScript typique. Ici, la hiérarchie part de l’objet générique application MyApp, puis passe par l’objet document document 1, l’objet paragraphe first paragraph, pour aboutir à l’objet mot first word. Ces objets “cartographie”, en langage AppleScript, les éléments et leurs propriétés. Regardons encore un peu plus de syntaxe AppleScript, puis nous les assembleront.

tell application "Graphics App"
set the color of window 1 to "blue"
end tell

L’objet culminant de ce script est l’application Graphics App. Ce script recherche un élément identifié par le mot window avec une propriété color et un index à 1 (cela aurait aussi pu être first, last, mid, ou tout autre identifiant de suite), puis il envoie une commande set à l’application avec en arguments la propriété de l’identifiant de l’élément et la nouvelle valeur de cette propriété.

Analyse de “ScriptableApp”

Ligne par ligne “activate” est une commande vers l’application “ScriptableApp”.

La commande AppleScript set avec comme argument une variable script “appName” et comme résultat “name” envoyée à l’application.

La commande AppleScript set avec comme argument une variable script “appVersion” et comme résultat “version” envoyée à l’application.

La commande AppleScript set avec comme argument une variable script “numWins” et la propriété compteur de la collection d’éléments fenêtre de l’application.

La commande AppleScript set avec comme argument une variable script “numDocs” et la propriété compteur de la collection d’éléments document de l’application.

La commande AppleScript set avec comme argument une variable script “w” et la propriété index du dernier élément de la collection de fenêtre retournée par la commande get avec comme identifiant “every” envoyée à l’application.

Close est une commande envoyée à l’application.

tell application "ScriptableApp"

	activate
	set appName to name
	set appVersion to version
	set numWins to count of windows
	set numDocs to count of documents
	set w to the last Abstract object of (get every window)
	close w 

end tell

Celui là, un peu plus compliqué, essaie de trouver les éléments, les propriétés, les commandes et quel objet est interrogé pour faire telle ou telle chose. Vous devriez essayer d’imaginer à quoi ressemble la hiérarchie de cette application.

L’ajout du support d’AppleScript à une application Cocoa revient à fournir à l’application, mais aussi aux objets Cocoa et autres objets constituant l’application, une cartographie reliant les éléments et leurs propriétés. Bien que cela puisse paraître intimidant, nous avons la chance qu’Apple ait déjà fait ceci, pour notre plus grand plaisir, avec la Core Suite pour la plupart des objets Cocoa communément utilisés dans les applications normalement scriptables.

Core Suite

Dans le premier article, nous avons réglé la clé NSAppleScriptEnabled à YES dans le fichier info.plist de ScriptableApp.app et gagné ainsi pas mal de possibilités de scripting. Ces possibilités proviennent de l’activation de la Core Suite disponibles à toute application Cocoa. La Core Suite nous prodigue une cartographie.

Souvenez-vous que notre application Cocoa est un ramassis d’objets, basiquement une instance de NSApplication (de main.m) et une instance de NSWindow (de MainMenu.nib). L’aspect fenêtre peut être quelque peu confus si vous n’avez ni jeté un oeil aux tutoriels Cocoa, ni utiliser Project Builder et Interface Builder.

Vous pouvez utiliser Interface Builder pour créer des instances d’objets UI (User Interface) qui seront stockés dans des fichiers .nib et chargés automatiquement quand l’application sera lancée. Tout est fait de manière graphique, il n’y a donc pas de code source correspondant dans Project Builder. Il y a d’autres objets à considérer, tels que NSMenu, NSMenuItem, NSWindowController, et bien d’autres, mais ils ne sont pas scriptables de manière typique, nous ne nous attarderons donc pas dessus. La Core Suite identifie certaines classes Cocoa qui seront scriptables. Nous pouvons voir ces classes dans un fichier appelé NSCoreSuite.scriptSuite, localisé à :

/System/Library/Frameworks/Foundation.framework/Resources/NSCoreSuite.scriptSuite

Vous pouvez regarder ce fichier dans un éditeur de texte ou dans un programme appelé Property List Editor situé dans le répertoire /Developer/Applications. Ce fichier CML contient plein d’informations, je vous recommanderais donc d’utiliser le Property List Editor, juste pour voir les choses plus simplement.

NSCoreSuite.scriptSuite contient plusieurs clés top-niveau : AppleEventCode, Classes, Commands, Enumerations, Name, et ValueTypes. Pour l’instant, regardons les sections Classes et Commands. Les classes mentionnées sont AbstractObject, NSApplication, NSColor, NSDocument et NSWindow.

Dans Commands, nous voyons Close, Copy, Count, Create, Delete, Exists, Get, Move, Open, Print, Quite, Save et Set. Si vous regardez le dictionnaire de script de ScriptableApp, vous y retrouverez ces éléments. A chaque élément est associé un AppleEventCode qui lui est propre, et si vous parcourez les classes, vous y verrez des clés pour la Superclass de la classe et les SupportedCommands (commandes supportées). Aussi, gardez à l’esprit que ces classes peuvent hériter des attributs et des commandes de leur Superclass, tout comme les classes suivantes avec AbstractObject.

Screenshot
NSCoreSuite.scriptSuite in Property List Editor

La section Commands fait correspondre chaque commande avec un objet Cocoa qui est une sous-classe de NSScriptCommand. Cela correspond à la clé CommandClass que l’on trouve pour chaque commande.

Par exemple, à Copy correspond un objet NSCloneCommand. Chaque fois qu’une commande est utilisée dans un script, un objet du type correspondant est créé pour s’assurer que la commande sera portée. Si vous êtes un pro de la programmation orientée objet, cela peut vous paraître un peu bizarre sachant que les commandes (méthodes) sont en général définie avec leurs objets. Cela brise donc le paradigme de la POO. Cependant, AppleScript est conçu pour un faible nombre de commandes qui opèrent sur un grand nombre d’éléments, cet arrangement rend donc l’idée plus compréhensible.

Les commandes définissent aussi un AppleEventClassCode, en plus de l’AppleEventCode, leurs arguments et leur type de valeur retournée. Une commande peut n’avoir ni argument, ni valeur retournée, et si tel est le cas, ces clés sont simplement laissées à blanc. Les types de valeurs retournées sont en général des NSString ou des NSNumber, mais ils peuvent aussi être d’autres classes. Il y a encore plein d’autres éléments dans ce fichier et nous en parlerons dans de futures articles, spécialement quand nous commencerons notre propre suite de scripts. Mais pour le moment, consacrons nous un peu plus à AppleScript.

Terminologie AppleScript

Nous venons de voir un listing de classes et de commandes scriptables dans le fichier NSCoreSuite.scriptSuite. Maintenant, nous devons nous attarder à la terminologie AppleScript utilisées par les scripteurs. La connexion est effectuée par un autre fichier appelé NSCoreSuite.scriptTerminology. Ce fichier est une ressource située à l’emplacement suivant :

/System/Library/Frameworks/Foundation.framework/Resources/English.lproj/NSCoreSuite.scriptTerminology

Ce fichier est aussi une liste de propriété XML, vous pouvez donc l’ouvrir avec Property List Editor. Différents dialectes peuvent être utilisés pour différents termes de la même classe dans NSCoreSuite.scriptSuite, bien qu’un employé d’Apple m’ait avoué que pour l’instant le seul dialecte supporté était l’anglais. Ce fichier est constitué des mêmes sections Classes et Commandes, mais elles sont utilisées pour fournir les termes utilisés dans un script et la description d’un terme dans le dictionnaire de scripting d’une application.

Screenshot NSCoreSuite.scriptTerminology dans le Property List Editor

Si vous jetez un oeil à la commande Create dans le Property List Editor, vous verrez que le nom utilisé dans un script pour appeler cette commande est “make“. De plus, si vous regardez la section Arguments de la commande Create, vous verrez la clé “ObjectClass” dont le nom est “new“, la clé “Location” dont le nom est “at“, la clé “KeyDictionary” dont le nom est “with properties“, et la clé “ObjectData” dont le nom est “with data“.

Donc, en AppleScript, quand vous dites “make new document at the beginning of every document with properties {name: “hello”}“, ces termes correspondent à des éléments de NSCoreSuite.scriptTerminology.

Héritage en action

Notre but est de créer une application Cocoa qui supporte un peu plus la Core Suite. Première étape : ouvrez l’application Project Builder et créer une nouvelle application Cocoa de type “Document-based”. Nommez la “ScriptableDocApp” et construisez le projet en choisissant “Build” à partir du menu Build ou en cliquant sur le marteau dans le coin superieur gauche de la fenêtre du projet. Ensuite, comme dans l’article précédent, nous souhaitons activer le support de la Core Suite en ajoutant :

<key>NSAppleScriptEnabled</key> <string>YES</string>

au fichier Info.plist situé dans le dossier Contents du dossier de ScriptableDocApp.app situé dans le dossier Products de la fenêtre de notre projet. Sauvegardez vos fichiers et choisissez Build and Run dans le menu Build.

Vous remarquerez que vous pouvez créer de nouveaux documents dans votre application en choisissant New dans le menu File. Essayons de scripter ce comportement. Ouvrez Script Editor et placez ceci dans un nouveau script :

tell application "ScriptableDocApp"
	activate
	set w to make new document at the beginning of documents
	--close every document
end tell

Si vous lancez ceci et observez la fenêtre de résultats, vous verrez que nous obtenons de nouveaux documents mais qu’aucune nouvelle fenêtre n’apparaît dans ScriptableDocApp.

En regardant notre projet dans Project Builder, vous remarquerez deux fichiers dans le dossier Classes - MyDocument.h et MyDocument.m. Ces fichiers créent une classe, MyDocument, qui est une sous-classe de NSDocument. La classe MyDocument est instanciée chaque fois que New est sélectionné dans le menu File, et c’est comme ça qu’un nouveau document est créé. Pour obtenir ce même comportement par le support du scripting, nous devons parler de MyDocument au système de scripting. Pour faire ceci, nous devrons ajouter un fichier scriptSuite et un fichier scriptTerminology à notre projet.

Selectionnez le dossier Ressources du panneau “Groups & Files” de la fenêtre projet et sélectionnez “New File” dans le menu File. Dans la boite de dialogue, sélectionnez “Empty File” et nommez le “ScriptableDocApp.scriptSuite” sans les doubles quotes, et cliquez sur Finish pour créer le nouveau fichier.

Collez y les lignes suivantes :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
<plist version="0.9">
<dict>
	<key>AppleEventCode</key>
	<string>ScDA</string>
	<key>Classes</key>
	<dict>
		<key>MyDocument</key>
		<dict>
			<key>AppleEventCode</key>
			<string>docu</string>
			<key>Superclass</key>
			<string>NSCoreSuite.NSDocument</string>
			<key>ToOneRelationships</key>
			<dict>
			</dict>
		</dict>
		<key>NSApplication</key>
		<dict>
			<key>AppleEventCode</key>
			<string>capp</string>
			<key>Superclass</key>
			<string>NSCoreSuite.NSApplication</string>
			<key>ToManyRelationships</key>
			<dict>
				<key>orderedDocuments</key>
				<dict>
					<key>AppleEventCode<</key>
					<string>docu</string>
					<key>Type</key>
					<string>MyDocument</string>
				</dict>
			</dict>
		</dict>
	</dict>
	<key>Name</key>
	<string>ScriptableDocApp</string>
</dict>>
</plist>

Ceci est notre suite de script pour notre application; les aspects les plus importants de cette suite sont qu’elle établit que le AppleEventCode de notre application sera ScDA et qu’elle liste MyDocument dans la section Classes avec comme clé de Super-classe positionnée à NSCoreSuite.NSDocument. Nous établissons aussi que la classe NSApplication a une ToManyRelationships de orderedDocuments qui sont du type MyDocument.

Maintenant nous devons ajouter un fichier de terminologie de script à notre projet. Une fois encore, assurez-vous que le dossier Resources est sélectionné et choisissez New File dans le menu File. Choisissez encore “Empty File“, nommez le “ScriptableDocApp.scriptTerminology” et cliquez sur Finish. Collez ceci dans le fichier :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
<plist version="0.9">
<dict>
	<key>Classes</key>
	<dict>
		<key>MyDocument</key>
		<dict>
			<key>Description</key>
			<string>A ScriptableDocApp document.</string>
			<key>Name</key>
			<string>document</string>
			<key>PluralName</key>
			<string>documents</string>
		</dict>
		<key>NSApplication</key>
		<dict>
			<key>Description</key>
			<string>ScriptableDocApp's top level scripting object.</string>
			<key>Name</key>
			<string>application</string>
			<key>PluralName</key>
			<string>applications</string>
		</dict>
	</dict>
	<key>Description</key>
	<string>ScriptableDocApp specific classes.</string>
	<key>Name</key>
	<string>ScriptableDocApp suite</string>
</dict>
</plist>

Cela établit la terminologie pour travailler avec MyDocument; il y est simplement dit que la classe MyDocument est référencée par le terme document. Fermez le fichier ScriptableDocApp.scriptTerminology et assurez-vous de sauvegarder vos changements.

De retour dans la fenêtre du projet, vous devriez maintenant voir dans le dossier Resources le fichier ScriptableDocApp.scriptSuite et le fichier ScriptableDocApp.scriptTerminology. Si vous en sélectionez un des deux, vous devriez voir le contenu XML dans la fenêtre d’édition. Maintenant, nous devons localiser le fichier scriptTerminology, sélectionnez donc le fichier ScriptableDocApp.scriptTerminology et choisissez “Show Info” dans le menu Project.

Screenshot
La fenêtre Info pour ScriptableDocApp.scriptTerminology

Dans la fenêtre info qui apparaît, vous devriez voir le fichier ScriptableDocApp.scriptTerminology listé dans le champ Name et un menu déroulant sur la droite indiquant “Localization & Platforms“. Sélectionnez “Make Localized” à partir de ce menu, et vous verrez apparaître une flèche au côté du nom du fichier dans le dossier Resources. A partir du même menu “Localization & Platforms“, choisissez “Add Localized Variant…” et dans le panneau qui apparaît, cliquez sur la flèche de droite et sélectionnez “English” (NDT : Le menu donne apparemment le choix de la langue française). Vous pouvez aussi simplement taper “English” dans le panneau.

Screenshot Ajout d’une variante localisée en anglais à notre fichier ScriptableDocApp.scriptTerminology.

Cliquez sur OK et alors vous pouvez fermer la fenêtre Info.

Choisissez Build and Run à partir du menu Build, en sauvegardant tout fichier nécessaire.

Essayez de lancer le script à partir du Script Editor, et vous devriez maintenant voir la fenêtre d’un nouveau document créée à chaque lancement du script. Si vous avez des problèmes, vous pouvez télécharger mon fichier projet ScriptableDocApp ici (6 Ko).

Il y a une autre chose intéressante que vous pouvez essayer de faire : jouer avec le fichier de Terminologie du script. Si vous changez “document” en “my document” et “documents” en “my documents” dans le fichier ScriptableDocApp.scriptTerminology, sauvegardez le fichier et reconstruisez votre application, vous changerez la mannière de référencer la classe MyDocument. Pour voir ceci, vous devrez quitter l’application Script Editor, parce qu’elle garde en cache l’ancienne version de la terminologie de ScriptableDocApp jusqu’à ce que vous relanciez Script Editor et y colliez ceci dans ce script :

tell application "ScriptableDocApp"
	activate
	set w to make new my document at the beginning of my documents
	--close every document
end tell

Lancez le et vous devriez le voir oeuvrer de la même manière qu’avant mais maintenant avec une nouvelle terminologie. Vous pouvez aussi jeter un oeil au dictionnaire du script ScriptAbleDocApp pour voir comment la terminologie a changé. Il faut faire attention à une chose quand on modifie les fichiers Suite ou Terminologie d’un script : Script Editor devra être relancé de façon à acquérir les dernières versions de ces deux fichiers. Vous pouvez sauvegarder vos scripts dans des fichiers texte entre deux redémarrages de Script Editor. C’est barbant, mais gardez à l’esprit que la syntaxe et la terminologie de vos applications ne seront pas amenées à changer souvent, le fait de les mémoriser en cache n’est donc pas une si mauvaise idée.

Autre impair que j’ai rencontré tient dans le fait que si vous nettoyer Clean le projet, la clé NSAppleScriptEnabled sera retirée du fichier Info.plist. Vous pouvez simplement la rajouter après le nettoyage. Donc, si soudainement vous trouvez que votre application ne répond pas du tout aux ordres de scripting, vérifier que le fichier Info.plist n’a pas été modifié.

La prochaine fois

Nous allons continuer à ajouter le support de la Core Suite à ScriptableDocApp, en ajoutant un peu de texte et la possibilité de charger, sauvegarder et imprimer nos documents. Cela veut dire que nous allons plonger dans l’Objective-C et écrire quelques lignes de code, donc si vous ne connaissez pas les bases de ce langage, n’hésitez pas à parcourir les tutoriels de Mike Beam (NDT : Section “Programmation Cocoa” sur Project:Omega) ou ceux qui accompagnent les outils de développement d’Apple.

A bientôt.


Textes originaux en anglais sur O’Reilly : http://www.macdevcenter.com/pub/au/780

Thierry Les langages de scripting , ,

  1. Pas encore de commentaire
  1. Pas encore de trackbacks
S'abonner aux commentaires de cet article