Rendre vos applications Cocoa-Java scriptables
Toute application Mac devrait offrir un minimum de support pour AppleScript –c’est ce qu’Apple recommande.
Cependant, ajouter une interface AppleScript devient rapidement un mystère en partie dû au manque de documentation et d’outils.
Pour certains, l’interfaçage AppleScript est devenu une fonctionnalité qui pourrait être intéressante ; mais ils ne prennent pas le temps de savoir comment l’implémenter.
Mais les choses ont changé. Les frameworks sont là pour vous décharger du travail pénible. Ajouter un interfaçage AppleScript ne relève plus de la magie noire. Et Cocoa, en particulier, fait un très bon travail en simplifiant l’implémentation de l’interface.
Par chance, comme la plupart des technologies AppKit, Cocoa signifie Cocoa. Il est donc aussi simple d’ajouter une interface scriptable à une application Cocoa-Java qu’à une application Cocoa Objective-C. Ce n’est pas bien documenté, mais c’est sur le point de changer.
Cet article vous montrera les opérations les plus habituelles à effectuer lors de l’implémentation de l’interfaçage AppleScript d’une application Cocoa-Java. Le but principal est de vous montrer que cela fonctionne comme vous l’espérez. Si un problème survient, vous aurez au moins la fierté d’en être l’auteur !
Première étapes
Pour commencer, nous utiliserons Xcode pour créer une simple application Cocoa-Java que nous utiliserons tout au long de cet article.
- Créez un projet “Cocoa-Java Document-Based Application” dans Xcode et appelez-le “ScriptableCJ”.
- Double-cliquez sur l’élément “ScriptableCJ” dans le groupe “Targets”, que vous trouverez dans le colume “Groups & Files” de la fenêtre principale.
- Sélectionnez la vue Expert de la section “Info.plist Entries”. Un tableau contenant toutes les valeurs préfixées apparaitra. Il vous permettra d’en ajouter d’autres.
- Ajoutez une nouvelle entrée “NSAppleScriptEnabled” et positionnez sa valeur à “YES”.
- Compilez et exécutez l’application (voir les astuces dans la section ci-dessous).

Et voilà, vous avez une application Cocoa-Java scriptable. Vous ne me croyez pas ? Exécutez le script suivant dans Script Editor et voyez.
tell application "ScriptableCJ"
set name of front document to "Hiya World"
end tell

Ce petit script lancera ScriptableCJ, si elle n’est pas déjà lancée, et changera le nom du document le plus en avant.
Il est intéressant de noter que, comme toute autre application scriptable, vous pouvez utiliser les commandes et classes proposées par ScriptableCJ en regardant son dictionnaire avec Script Editor. Vous remarquerez qu’il contient les éléments Standard suite et Text suite.
Ces deux éléments sont apportés par Cocoa automatiquement.
Nous ajouterons bientôt nos propres commandes, mais vous pouvez déjà en apprendre plus sur ces deux éléments en lisant la documentation développeur.
Devenir utile : ajouter des propriétés
Pour faire connaître à AppleScript les nouvelles fonctionnalités de notre application, nous devons lui fournir deux fichiers.
Ces deux fichiers ont le même nom, mais une extension différente.
Le premier fichier utilise l’extension “.scriptSuite”.
Ce fichier informe AppleScript et Cocoa sur la structure de nos nouvelles classes, élements, propriétés et commandes apportées par la nouvelle application. Il renseigne sur leurs relations et comment ils sont liés au code Obj-C ou Java.
Le second fichier et le fichier terminologie et se termine par l’extension “.scriptTerminology”.
AppleScript fait de son mieux pour ressembler à un langage humain et ce fichier explique comment faire pour des commandes spécifiques que nous ajoutons.
Stocker les informations sur le langage naturel dans un fichier séparé de la structure de données permet une traduction sans danger.
Les deux fichiers utilisent le format plist pour structurer leur contenu. Dans ce cas, les notations XML ou Key=Value String.
D’un point de vue structurel, le style XML est préférable ; cependant pour des raisons de lisibilité, nous utiliserons la notation Key=Value String dans cet article.
Maintenant que nous connaissons l’existence des fichiers scriptSuite et scriptTerminology, préparons-nous à les ajouter.
Pour cela, nous allons transformer ScriptableCJ en un mini éditeur de texte.
Ajouter un NSTextView
- Ouvrir MyDocument.nib avec Interface Builder.
- Supprimer le NSTextField “Your document contents here”, affiché par défaut, de la fenêtre document.
- Ajouter un NSTextView et modifier ses dimensions.
- Sauvegardez.
Maintenant, mettons AppleScript et Cocoa en relation avec NSTextView.
- Créez deux nouveaux fichiers, MysSuite.scriptSuite et MySuite.scriptTerminology.
Vous pouvez choisir n’importe quel nom à la place de “MySuite”, pourvu que les deux fichiers aient le même nom et que ce nom corresponde à la valeur de la clé Name (cf. ci-dessous). - Dans MySuite.scriptSuite, ajoutez ce qui suit :
{ "Name" = "MySuite"; "AppleEventCode" = "CJst"; "Classes" = { "NSApplication" = { "Superclass" = "NSCoreSuite.NSApplication"; "ToManyRelationships" = { "orderedDocuments" = { "Type" = "MyDocument"; "AppleEventCode" = "docu"; }; }; "AppleEventCode" = "capp"; }; "MyDocument" = { "Superclass" = "NSCoreSuite.NSDocument"; "AppleEventCode" = "docu"; "ToOneRelationships" = { "myContents" = { "Type" = "NSTextStorage"; "AppleEventCode" = "tact"; }; }; }; }; "Synonyms" = { "tact" = "NSTextSuite.NSTextStorage"; }; } - Dans MySuite.scriptTerminology, ajouter ce qui suit :
{ "Name" = "MySuite"; "Description" = "This is my hand made suite"; "Classes" = { "NSApplication" = { "Name" = "application"; "PluralName" = "applications"; "Description" = "The top level scripting object."; }; "MyDocument" = { "Name" = "document"; "PluralName" = "documents"; "Description" = "A ScriptableCJ document."; }; }; "Synonyms" = { "tact" = { "Name" = "text contents"; "Description" = "The textual contents of the document"; }; }; }
Ces deux fichiers utilisés ensemble indiquent au système que ScriptableCJ possède un ou plusieurs documents.
Chaque document contient un accès aux données texte appelé text contents.
Ceci indique que pour obtenir le contenu de text contents, la méthode myContents doit être appelée, et que cette méthode appartient à l’objet MyDocument.
C’est tout ce dont nous avons besoin pour le moment.
Comme auparavant, une description détaillée de ces fichiers se trouve dans la documentation développeur sur votre machine.
Tout le reste est en place ; il ne reste plus qu’à ajouter les méthodes d’accès pour TextView.
- Ouvrez MyDocument et ajoutez un outlet qui sera utilisé pour pointer sur TextView.
/** IBOutlet **/ public NSTextView mainTextView; - Ajoutez la méthode myContents() pour retourner le texte contenu dans le champ texte.
Grâce aux fichiers scripts que nous venons de créer, cette méthode sera appelée automatiquement quand AppleScript aura besoin de l’information de text contentspublic NSTextStorage myContents() { return mainTextView.textStorage(); } - Si appleScript essaie de modifier text contents, Cocoa essaiera automatiquement de trouver la méthode set de myContents.
Nous allons donc lui en fournir une.
Pour que les choses restent simples, nous créerons simplement une fonctionnalité de remplacement de texte.public void setMyContents( NSTextStorage inStorage ) { mainTextView.textStorage().setAttributedString( inStorage ); } - Ouvrez MyDocument.nib dans Interface Builder.
- Double-cliquez sur “File’s Owner”, ajoutez un outlet appelé mainTextView, et changez-le au type NSTextView.
- Connectez l’outlet au champ texte comme d’habitude. Si vous ne l’avez jamais fait auparavant, revenez à la vue Instances de MyDocument.nib, en maintenant la touche ctrl enfoncée, et glissez la souris depuis File’s Owner jusqu’au champ texte que nous venons d’ajouter.
Cliquez sur connect dans la fenêtre d’information. - Sauvegardez les modifications.
C’est tout.
Nous avons ajouté un TextView,
indiqué à Cocoa et AppleScript qu’il existe et la manière d’y accéder.
Enfin, nous avons implémenté le code en Java pour accéder au contenu du champ texte.
Quittez Script Editor (si besoin est), recompilez et testez l’application avec le script suivant :
tell application "ScriptableCJ"
set the text contents of the front document to "hello there again"
end tell
Pas très impressionnant n’est-ce-pas ?
Pourquoi ne pas ajouter un petit compteur de mot à notre petite application et la modifier ?
Maintenant les choses deviennent plus intéressantes.
tell application "ScriptableCJ"
count the words in the text contents of the front document
end tell
Ajouter une commande
Nous avons déjà quelque chose qui ressemble à un petit éditeur de texte, et nous allons continuer à l’améliorer en ajoutant une commande d’export.
Pour que les choses soient claires, nous utiliserons des instructions d’écriture sur console pour remplacer les opérations relatives au système de fichiers, mais l’interface AppleScript restera inchangée.
Comme d’habitude, nous avons besoin de modifier les fichiers Suite et Terminology pour informer AppleScript des nouveaux export de commandes.
- Ouvrez MySuite.scriptSuite.
- Ajoutez la section suivante à la classe MyDocument :
"SupportedCommands" = { "Export" = "handleExport:"; }; - Ajoutez une section Commands après la section Classes en tapant ce qui suit :
"Commands" = { "Export" = { "AppleEventClassCode" = "CJst"; "AppleEventCode" = "Expt"; "CommandClass" = "NSScriptCommand"; }; };
Ces deux nouvelles sections indiquent au système que la classe MyDocument peut gérer la commande Export et qu’elle le fait au travers de la méthode handleExport().
Notez que bien que nous implémenterons handleExport comme une méthode Java, les deux points au style Objective-C doivent apparaître dans le fichier scriptSuite.
La valeur de CommandClass est utilisée pour spécifier une classe qui représente une instruction.
Cette classe doit être de type NSScriptCommand ou une de ses sous-classes.
Avec les modifications que nous avons effectuées sur scriptSuite, le comportement par défaut de NSScriptCommand fera que la méthode (handleExport()) soit appelée sur l’objet en question (MyDocument).
C’est exactement ce que nous voulons pour cette instance, et il n’y a donc rien à faire de plus sophistiqué à ce point.
Avant d’implémenter la méthode handleExport, nous devons décrire la syntaxe AppleScript pour la commande.
- Ouvrez le fichier MySuite.scriptTerminology
- Après la section Classes, ajoutez la section Commands où nous décrirons la commande Export :
"Commands" = { "Export" = { "Description" = "My Custom Export command"; "Name" = "export"; }; };
Pour une vérification rapide, recompilez l’application et relancez Script Editor pour ouvrir de nouveau le dictionnaire.
Il devrait maintenant contenir la commande export.
Maintenant, pour implémenter la commande export :
- Ouvrez le fichier MyDocument.java
- Ajoutez la méthode handleExport :
public void handleExport( NSScriptCommand inCommand )
{
System.out.println( “handleExport called” );
}
- Recompilez
Les fichiers au complet sont disponibles : MySuite.scriptSuite, MySuite.scriptTerminology,
Avec ces changements en place, nous pouvons maintenant tester notre nouvelle commande.
tell application "ScriptableCJ"
-- Open Console to see the results
tell application "Console" to activate
export front document
end tell

Après l’exécution de ce script, vous devriez voir “handleExport called” écrit sur la console. C’était facile non ?
Commandes spécifiques
Au tout début, quand nous avons ajouté une commande, nous avons utilisé le comportement par défaut de NSScriptCommand pour passer les informations à l’objet Document.
Que se passe-t-il quand nous invoquons une commande spécifique, mais qu’il n’existe pas d’objet préexistant adéquat auquel donner le contrôle ?
Dans ces cas là, une sous-classe de NSScriptCommand peut-être utilisée.
C’est ce que nous allons faire.
Pour illustrer ce concept, nous allons décrire une commande Import implémentée en utilisant une classe dérivée de NSScriptCommand.
Comme pour les autres exemples, nous commençons par ajouter la commande à scriptSuite.
- Ouvrez le fichier MySuite.scriptSuite
- Ajoutez la commande import directement après la commande export :
"Import" = { "AppleEventClassCode" = "CJst"; "AppleEventCode" = "Impt"; "CommandClass" = "ImportScriptCommand"; };
Maintenant, spécifions la syntaxe de la nouvelle commande :
- Ouvrez le fichier MySuite.scriptTerminology
- Dans la section commandes, nous décrivons la commande import.
"Import" = { "Description" = "My Custom import command"; "Name" = "import"; };
Maintenant, implémentez la classe ImportScriptCommand qui va gérer la commande import sans avoir besoin de spécifier directement un objet.
- Créez une nouvelle classe Java appelée ImportScriptCommand.java dérivée de NSScriptCommand :
import com.apple.cocoa.foundation.*; public class ImportScriptCommand extends NSScriptCommand { } - Ajoutez-y un constructeur. Vérifiez la documentation sur NSScriptCommand pour de plus amples informations.
public ImportScriptCommand( NSScriptCommandDescription inCommandDescription ) { super( inCommandDescription ); } - Surchargez la méthode performDefaultImplementation() pour effectuer des opérations plus spécifiques.
public Object performDefaultImplementation() { System.out.println( "Import Called" ); return "Import was indeed called"; }
Les fichiers au complet sont disponibles : MySuite.scriptSuite, MySuite.scriptTerminology,
Maintenant nous sommes prêts à recompiler et tester les modifications avec un petit script
tell application "ScriptableCJ"
import
end tell

Le résultat de ce script, tel qu’il est affiché dans le Script Editor, est très parlant :
"Import was indeed called"
Astuces
C’est tout. Jusqu’ici, nous avons vu rapidement la plupart des opérations habituelles, mais avant de conclure, je voudrais porter votre attention sur quelques points :
- Quand vous créez une nouvelle application, le Script Finder n’en a pas connaissance tant que l’application n’a pas été exécutée au moins une fois.
- Script Editor s’est amélioré au niveau de sa réaction vis à vis des modifications dans le code.
Cependant, si vous ouvrez un dictionnaire dans Script Editor et que vous effectuez des modifications dans Xcode, Script Editor peut ne pas prendre en compte les changements, et vous aurez besoin de le relancer. - L’ouverture d’un dictionnaire entraîne le lancement de l’application associée.
Malheureusement, en recompilant et relançant votre application depuis Xcode vous pourriez vous retrouver avec deux versions de votre application fonctionnant simultanément–ce qui est peut entraîner une confusion pour le script que vous voulez tester. - Les points-virgules : si vous choisissez le style Key=Value pour l’écriture de vos fichiers scriptSuite et scriptTerminology, n’oubliez pas les points-virgules en fin d’instruction, et après chaque accolade (excepté la toute dernière).
Je ne peux pas me rappeler le nombre de fois où les choses ne fonctionnaient pas, pour remarquer ensuite que j’avais oublié un point-virgule quelque part. - Si l’extension .scriptSuite ou .scriptTerminology est correcte, alors le code sera mis en couleur dans Xcode.
Si vous faites une erreur dans l’extension, les fichiers apparaissent en monochrome. - Enfin, vérifiez votre configuration. Cet article et toutes les autres documentations référencées dans cet article supposent que vous avez correctement installé les outils et documentations de développement.
En Conclusion
Le but de cet article était de démontrer que la mise en place d’une interface AppleScript pour les applications Cocoa/Java est très semblable aux opérations requises pour les applications Cocoa/Objective-C.
Comme vous pouvez le voir dans les exemples précédents, il n’y a pratiquement aucune différence (jusqu’à la syntaxe Objective-C des noms de méthodes). Alors, dans le doute, utilisez la documentation pour Objective-C.
Joyeux Script !

Textes originaux en anglais sur O’Reilly : Making Cocoa-Java Apps Scriptable par Mike Butler
Chargement
Commentaires récents