Raccourcis Python pour Sketch et Skencil


Auteur

Philippe Blayo


Chapeau

Le paysage des logiciels libres de dessin vectoriel diffère beaucoup de celui du bitmap. Pour le bitmap, un logiciel, Gimp, demeure sans rival, alors que pour le vectoriel, plusieurs logiciels cohabitent avec chacun des atouts et des faiblesses. Parmi eux, Skencil (anciennement appelé Sketch) est le seul qui soit scriptable.


Des scripts en Python

Python est le langage dans lequel de tels scripts peuvent être conçus. D'autres logiciels libres qui gravitent dans le monde de l'image sont également scriptables en Python : Gimp (depuis sa version 2), Blender et Scribus. Dans le cas de Skencil, il s'agit également du langage dans lequel le logiciel lui-même est écrit (majoritairement), ce qui permet d'envisager un accès très naturel à l'ensemble des objets qu'il manipule.

De Sketch à Skencil

En langue anglaise, sketch signifie esquisse. Comme il s'agit d'un terme très commun, la décision a été prise à l'automne 2003 de le remplacer par Skencil. Ce dernier avait notamment l'avantage de commencer par les mêmes lettres, ce qui permettait de préserver le .sk comme extension des fichiers sources. La version 0.6.16 est la première publiée sous le nom Skencil. C'est donc ce dernier qui sera employé dans l'article. Par contre, en interne, le terme employé dans les sources reste Sketch (c'est le package principal). C'est pourquoi il apparaîtra dans les portions de code.


Les raccourcis clavier

Cet article aborde le scripting de Skencil sous l'angle des raccourcis clavier. Leur définition ne passe en effet pas par des fichiers de configuration classiques, mais par du code Python. De tels raccourcis peuvent s'avérer bien utile, mais cette possibilité n'est malheureusement pas documentée (si ce n'est par un bout de code qui traîne sur une liste de diffusion). Aussi m'a-t-il paru intéressant de détailler cet aspect pour introduire le scripting.

Une grande partie de la personnalisation de Skencil passe par le fichier userhooks.py situé dans le répertoire .sketch/ à la racine de chaque utilisateur. Comme son extension l'indique, il renferme du code Python que Skencil exécute chaque fois qu'il est lancé.

Ce fichier est utilisé notamment pour signifier l'ajout de scripts et de greffons (dont la réalisation fera l'objet d'un prochain article). Il l'est également pour définir (ou redéfinir) des raccourcis claviers. Chaque utilisateur peut ainsi choisir les siens. Si l'on souhaite par exemple associer

on peut insérer le code suivant dans userhooks.py :

 import Sketch.UI.mainwindow
 import Sketch.UI.canvas
 
 def find_command(name):
     """Renvoie l'objet commande"""
     for cmd in (Sketch.UI.mainwindow.command_list
                 + Sketch.UI.canvas.command_list):
         if cmd.name == name:
             return cmd
     return None
 
 # raccourcis clavier
 find_command("CreateRectangle").key_stroke = "r"
 find_command("SelectAll").key_stroke = "C-a"

On définit une fonction find_command() qui prend un nom en paramètre et renvoie la commande que désigne ce nom. Leur attribut key_stroke est ensuite positionné à la séquence de touches souhaitée.

C'est Bernhard Herzog (l'auteur de Skencil) qui a proposé cette manière de procéder. Elle présente l'avantage d'ajouter le rappel du raccourci à l'entrée des menus qui lui correspond.

Les nouveaux raccourcis sont visibles dans les menus

A l'examen du parcours de sa boucle for, on se rend compte que la fonction find_command() ne permet d'accéder qu'aux commandes définies dans l'interface utilisateur (UI.mainwindow et UI.canvas). C'est là que se trouvent la plus grande partie des commandes (70 dans mainwindow et 31 dans canvas), mais toutes n'y figurent pas. Celles d'édition des noeuds d'une courbe, par exemple, se trouvent ailleurs.

[Note : les autres commandes se répartissent dans différents fichiers des objets graphiques de Sketch/Graphics/ (bezier.py, ellipse.py, image.py, maskgroup.py, text.py). Celles des noeuds d'une courbe sont dans bezier.py.]

Heureusement, ces autres commandes sont accessibles par l'intermédiaire d'une simple liste (command_classes) qui réside dans le package Sketch lui-même :

 commands = (Sketch.UI.mainwindow.command_list
             + Sketch.UI.canvas.command_list)
 
 # ajout des autres commandes
 for cmd_class in Sketch.command_classes:
     commands += cmd_class.commands
 # nouvelle fonction find_command()
 def find_command(name):
     for cmd in commands:
         if cmd.name == name:
             return cmd
     return None

La définition des raccourcis ne change pas :

 find_command("CreateRectangle").key_stroke = "r"
 find_command("SelectAll").key_stroke = "C-a"

On voit cependant poindre une difficulté : chaque raccourci provoque un parcours de la liste des commandes. Cela convient pour deux raccourcis, mais conduirait à un ralentissement pour un nombre beaucoup plus élevé. Pour faire face à cette possibilité, il est préférable de passer par un dictionnaire des commandes qui associe les noms des commandes aux objets commandes eux-mêmes :

 catalog = {}
 for cmd in commands:
     catalog[cmd.name] = cmd
 
 catalog["CreateRectangle"].key_stroke = "r"
 catalog["SelectAll"].key_stroke = "C-a"

Ainsi, la liste des commandes n'est parcourue qu'une seule fois quelque soit le nombre de raccourcis.

Trouver le nom d'une commande

Muni de ces informations, essayons de créer un raccourci (disons [e]) pour fusionner (fermer) deux noeuds d'une courbe (une opération très utile pour laquelle aucun raccourci n'est définit par défaut). Il faut d'abord trouver comment se nomme cette commande. Pour cela, il existe plusieurs possibilités. La première qui vient à l'esprit est d'afficher tous les noms (cmd.name). Comme il en existe 150, les passer en revue peut s'avérer fastidieux (d'autant qu'on ignore quel terme Skencil emploie en interne). Mieux vaut donc afficher également l'entrée du menu qui invoque la commande (cmd.menu_name) :

 for cmd in commands:
     print cmd.menu_name,' : ', cmd.name

Le lancement de skencil provoque l'affichage de 150 lignes parmi lesquelles

 ...
 Couper la courbe  :  OpenNodes
 Fermer les noeuds  :  CloseNodes
 ...

On reconnaît l'entrée Fermer les noeuds qui nous est familière (la traduction est préservée). Il ne reste plus qu'à positionner le raccourci :

 catalog["CloseNodes"].key_stroke = "e"
Raccourci du menu courbe

Une autre manière de trouver un nom consiste à explorer les sources de Skencil (langage interprété oblige, elles sont déjà présentes). L'examen des fichiers mainwindow.py et canvas.py indique que les noms des commandes y sont passés en paramètre d'une fonction AddCmd() (ou de ses variantes AddModeCmd(), AddSelCmd() et AddDocCmd()). Un grep Add.*Cmd permettrait donc de sélectionner les lignes contenant des noms de commandes. Mais comme les fichiers à explorer se répartissent dans plusieurs répertoires (Sketch/Graphics/ et Sketch/UI/), mieux vaut se placer à la racine de Skencil (cd /usr/lib/ske*/Sketch) et lancer la recherche depuis cet endroit :

 $ grep "^[^#d]*Add.*Cmd("   Graphics/* UI/*

ou plus simplement :

 $ grep "^[^#d]*Add.*Cmd("   */*

Ce qui abouti à l'affichage de 136 lignes parmi lesquelles

 ...
 Graphics/bezier.py:    AddCmd(commands, OpenNodes, _("Cut Curve"),...
 Graphics/bezier.py:    AddCmd(commands, CloseNodes, _("Close Nodes"),
 ...

L'entrée du menu ("Close Nodes") permet à nouveau d'identifier la bonne commande, même s'il s'agit cette fois de la version originale (non traduite). La présence du nom des fichiers permet en plus de restreindre assez vite la recherche.

[Note : l'expression régulière ^[^#d]*Add.*Cmd( passée à grep a été affinée par rapport au simple Add.*Cmd :

^[^#d]*
élimine les lignes commentées ([^#]) ainsi que celles où les fonctions elles-mêmes sont définies (def AddCmd(), def AddModeCmd(), ...) et qui contiennent donc le d de def ([^d]).
(
exiger la parenthèse élimine les quelques invocations de AddCmd où aucun nom de commande n'est définit.]

Peut-être avez-vous remarqué que la première solution affichait 150 commandes et la deuxième 136. Il y a donc des doublons. Le nombre exact de commandes est accessible par le nombre d'entrées dans le dictionnaire (il n'y a qu'une entrée par nom).

 print len(catalog)

affiche 132.

Pour rassembler tous les éléments abordés, voici le code complet à placer dans userhooks.py pour définir les trois raccourcis évoqués :

 import Sketch.UI.mainwindow
 import Sketch.UI.canvas
 # liste des commandes 
 commands = (Sketch.UI.mainwindow.command_list
             + Sketch.UI.canvas.command_list)
 for cmd_class in Sketch.command_classes:
     commands += cmd_class.commands
 
 # catalogue des commandes
 catalog={}
 for cmd in commands:
     catalog[cmd.name] = cmd
 
 catalog["CloseNodes"].key_stroke = "e"
 catalog["SelectAll"].key_stroke = "C-a"
 catalog["CreateRectangle"].key_stroke = "r"


Conclusion

La définition de raccourcis nous a permit d'entrevoir la facilité avec laquelle il est possible d'accéder à des objets au coeur de Skencil. Il s'agissait d'un prélude à un prochain article où on verra comment utiliser cet accès pour l'écrire de scripts et de greffons.


Références



Retour à la page principale

Valid XHTML 1.0 Strict