original in en Hilaire Fernandes
en to frJohn Perr
Hilaire Fernandes est le vice-président de OFSET, une organisation de promotion de développement de logiciel éducatif libre pour Gnome. Il a aussi écrit Dr.Geo un logiciel interactif de géométrie primé et il travaille sur Dr.Genius, un autre logiciel éducatif de mathématiques pour Gnome.
Cette série d'articles est destinée aux débutants en programmation Gnome et GNU/Linux. Le choix de Python comme langage de développement évite la surcharge habituelle due aux langages compilés comme C. Des connaissances de programmation en Python sont nécessaires pour une bonne lecture de cet article.
Pour pouvoir exécuter le programme décrit dans cet article, vous aurez besoin au minimum de:
Pour installer Pyhton-Gnome et LibGlade à partir des sources:
./configure make make install |
sera suffisant. (Plus d'explications à
[
http://www.linuxgazette.com/issue38/pollman.html ]).
Vous devez aussi vérifier que la variable d'environnement Python
PYTHONPATH contient le chemin des modules Python-Gnome.
Cela peut être
/usr/local/lib/python1.5/site-packages ou
/usr/lib/python1.5/site-packages/. A cet endroit vous trouverez
tous les compléments pour Gnome et LibGlade, par exemple vous y trouverez
le module libglade.py. Pour définir PYTHONPATH
ajoutez à votre fichier .bash_profile:
PYTHONPATH=/usr/local/lib/python1.5/site-packages export PYTHONPATH |
N'oubliez pas, vous aurez peut être à démarrer votre code Python depuis un terminal pour définir cette variable.
Glade est un constructeur d'interface développé par Damon Chaplin. Il permet la construction graphique et interactive d'interfaces utilisateur (NDT: GUI en anglais). A partir de Glade, l'interface générée est sauvée dans un fichier xml ou bien directement exportée en code C afin d'être incluse dans l'arbre des sources en C. Glade permet aussi de définir le nom des gestionnaires d'évènements -des fonctions- qui seront appelées par les différents évènements de l'interface. Ce sera, par exemple, la fonction (son nom) à appeler quand une entrée de menu spécifique est choisie.
LibGlade est une bibliothèque écrite par James Henstridge pour générer directement une interface représentée par un fichier xml de Glade. L'application a juste besoin de connaître le fichier xml -qui se termine généralement par l'extension .glade- pour que LibGlade en génère l'interface. James Henstridge a aussi écrit l'adaptation Python -parmi d'autres- qui se trouve dans le paquetage Gnome-Python. LibGlade permet aussi de connecter automatiquement -presque en Python- le gestionnaire défini dans le fichier .glade avec des fonctions définies dans le code Python.
Le graphique ci-dessous montre ce mécanisme général. Pour comprendre comment l'édition de liens Python est implémentée, il est parfois nécessaire de regarder les modules Python Gtk, Gnome et LibGlade situés dans PYTHONPATH afin de les comparer à la documentation C du développeur Gtk/Gnome.
En tant que première approche de programmation Gnome-Python je propose une simple jeu de couleur où des enfants doivent reconnaître des formes de la même couleur. Cet exemple est très orienté graphisme et utilise des possibilités sympathiques de Gnome comme le Gnome Canvas et la fenêtre d'application Gnome. Les règles de ce jeu sont plutôt simples: la grille de jeu est remplie avec 16 formes -cercles, étoiles et carrés- de différentes couleurs. Ces 16 formes sont réparties en 8 paires de couleurs identiques. Vous pouvez d'abord jeter un coup d'oeil au code à la fin de ce document pour avoir une idée générale puis reprendre votre lecture ici.
Les widgets
Après avoir démarré Glade, vous obtenez deux fenêtres. L'une est la boite à outil des widgets appelée Palette. Elle vous permet de sélectionner les catégories de widgets parmi GTK+ Basic, GTK+ Additional et Gnome. Si vous n'avez pas le Gnome widget, Glade peut avoir été compilé sans le support Gnome. Vérifiez le fichier configure du paquetage source de Glade, configure --help explique les options de configuration.
L'autre fenêtre liste dans son aire principale les widgets créés.
Avec Glade, créons d'abord une fenêtre d'application Gnome (Gnome Application Window). Ce widget est une fenêtre avec une barre de menu et une barre d'outils. Les deux sont placées sur le dock manipulé. En bas de la fenêtre d'application Gnome, il y a aussi une barre d'état. Une fois la fenêtre créée, ouvrez le dialogue Widget Tree (vous le trouverez dans le menu view de Glade). Maintenant, vous pouvez consulter ce qui est positionné dans ce widget.
Ensuite, ajoutez un canvas dans la fenêtre principale du widget de l'application Gnome. Dans le dialogue properties définissez ses coordonnées maximales à 400 et sa hauteur maximale à 400.
Créez maintenant un dialogue "A propos de Gnome". Vous pouvez modifier son contenu depuis le dialogue properties dans la feuille Widget.
Tous ces widgets sont dans la catégorie Gnome de la Palette.
Enlevez maintenant les boutons et les entrées de menu inutilisés. Dans la barre d'outils enlevez les boutons Open et Save. Ensuite, éditez la barre de menu (click droit sur la barre et choisissez edit menu) et supprimez tous les menus et entrées excepté File->New, File->Exit, Setting->Preferences et Help->About.
Définir les noms des widgets et gestionnaires
Attribuez les noms suivants à ces widgets de sorte qu'ils puissent être utilisés avec ces noms dans Python:
Les noms des gestionnaires d'évènements sont des noms de fonctions à appeler quand un évènement apparaît sur un widget donné. Cela veut dire que nous allons définir des fonctions dans Python avec ces noms - enfin presque comme nous allons le voir un peu plus tard. Par exemple, quand l'utilisateur clique sur le bouton "new" nous voulons appeler une fonction pour ré-initialiser le jeu. Pour, établir cela dans Glade, vous devez d'abord sélectionner le widget puis régler les paramètres dans la feuille des signaux dans le dialogue properties.
Dans notre exemple, le signal est clicked et le gestionnaire est le nom de la fonction. Les tableaux suivants présentent tous les signaux et gestionnaires utilisés:
Dans le dialogue about (ndt: A propos):
Nom de Widget | Signal | Gestionnaire |
---|---|---|
about | clicked | gtk_widget_destroy |
about | close | gtk_widget_destroy |
about | destroy | gtk_widget_destroy |
Le gestionnaire gtk_widget_destroy est pré-défini dans GTK. Il détruit simplement le widget.
Dans la fenêtre colorApp. D'abord, Glade choisit automatiquement les signaux/gestionnaires pour les entrées de menus. Vous pouvez marquer leur noms. Je les ajoute à la fin de ce tableau. Notez que l'entrée de menu "new" et le bouton "new" partagent tous les deux le même gestionnaire, ce qui est normal puisqu'ils sont destinés au même usage:
Nom de Widget | Signal | Gestionnaire |
---|---|---|
button1 (new icon button on the toolbar |
clicked | on_new_activate |
new | activate | on_new_activate |
colorApp | destroy | on_exit1_activate |
exit1 | activate | on_exit1_activate |
about1 | activate | on_about_activate |
La touche finale
Appeler les Project Options en cliquant sur le bouton Options dans la barre d'outils de Glade. Dans la feuille General définissez les paramètres du projet comme suit:
Le fichier contenant les widgets est color.glade. Définissez le chemin comme celui de votre répertoire personnel.
Maintenant sauvegardez le fichier depuis le menu File.
Ne construisez pas le code source, nous n'utilisons pas cette possibilité.
Nous avons désormais fini avec Glade et nous pouvons commencer avec
Python.
Le code complet est situé à la fin de ce document. Il doit être sauvegardé dans le même répertoire que le fichier color.glade.
from math import cos, sin, pi from whrandom import randint from gtk import * from gnome.ui import * from GDK import * from libglade import * |
Depuis les modules math et whrandom, nous incluons des fonctions non spécifiques à Gnome comme cos, sin, randint ainsi que la constante pi. Les modules spécifiques de Gnome sont gtk, GDK et gnome.ui. En C, le fait d'inclure gnome.h inclut tous les entêtes de Gnome. En Pyhton, vous devez d'abord déterminer dans quel module est située la bibliothèque de la fonction que vous voulez utiliser. Par exemple, vous pouvez rechercher dans une fenêtre de terminal (shell) le module contenant la chaîne "canvas" avec la commande suivante:
cd /usr/local/lib/python1.5/site-packages/gnome grep canvas *.py |
Les lignes ci-dessus supposent que la bibliothèque Gnome a été installée dans/usr/local/lib/python1.5/site-packages.
Dans cet exemple Python, nous utilisons le canvas Gnome pour manipuler des formes -en fait des étoiles, des cercles et des carrés-. Un canvas est un espace de manipulation d'objets graphiques (ellipse, point, ligne, rectangle), de textes et même de widgets. En fait, un canvas peut contenir plusieurs groupes de canvas. Finalement, dans un groupe de canvas, on peut placer des éléments de canvas -nos formes. Par défaut un canvas contient un groupe de canvas appelé le groupe racine. Nous l'utiliserons pour placer nos formes.
Premièrement, on définit des variables globales:
La première fonction qui est appelée -initColor- construit les widgets depuis le fichier color.glade et connecte automatiquement les gestionnaires et les widgets:
def initColor (): global rootGroup, canvas wTree = GladeXML ("color.glade", "colorApp") dic = {"on_about_activate": on_about_activate, "on_exit1_activate": mainquit, "on_new_activate":on_new_activate} wTree.signal_autoconnect (dic) canvas = wTree.get_widget ("canvas") rootGroup = canvas.root () |
La construction des widgets est effectuée avec la fonction GladeXML. Bien sûr il faut donner le bon chemin pour le fichier color.glade file. Cette fonction construit et affiche la fenêtre de l'application Gnome colorApp que nous avons définie avec Glade. Elle retourne un objet -en fait une classe- et les méthodes associées.
Ensuite nous connectons les gestionnaires que nous avons définis
dans Python (voir plus loin) aux widgets que nous avons définis dans le
fichier color.glade. Pour cela, nous avons besoin de
construire un dictionnaire qui contient des clés pour les noms
de gestionnaires définis dans le fichier color.glade:
on_about_activate, on_exit1_activate et
on_new_activate. Les valeurs associées à ces clés sont les
noms des fonctions définies en Python.
Finalement, la méthode signal_autoconnect fait le reste
du travail pour nous.
Enfin nous récupérons la référence du canvas construit durant l'appel à GladeXML -un objet GnomeCanvas en Python- et le groupe racine du canvas -un objet GnomeCanvasGroup.
Astuces utiles
Il n'existe pas de manuel de référence concernant les bibliothèques Gnome pour Python. Toutefois il y a beaucoup de documentation sur le site Gnome qui concerne la programmation Gnome en C. Utiliser cette documentation pourra s'avérer utile mais vous aurez aussi besoin de jeter un coup d'oeil dans la bibliothèque Gnome pour Python pour pouvoir l'exploiter:
La bibliothèque est située dans /usr/local/lib/python1.5/site-packages/gnome/ ou /usr/lib/python1.5/site-packages/gnome/
Regarder dans cette bibliothèque montre plusieurs choses:
Pour chaque utilisation de Gnome dans Python, nous pouvons agir de même pour accèder à la documentation associée. Je vous laisse le soin de lire la documentation Gnome associée pour en savoir plus sur ces fonctions.
Trois gestionnaires s'auto-connectent à l'interface. Ce sont on_about_activate, on_new_activate et mainquit. Le dernier est en fait une fonction Python qui stoppe et quitte Python.
def on_about_activate(obj): "display the about dialog" about = GladeXML ("color.glade", "about").get_widget ("about") about.show () |
Ce gestionnaire ouvre le dialogue "A propos". On récupère d'abord une référence du dialogue about -en fait LibGlade le construit avec l'objet GladeXML. Souvenez-vous que GladeXML est un objet Python avec -entre autres- une méthode appelée get_widget. Cette méthode retourne un objet GtkWidgetqui contient la méthode show.
Astuces
Recherchez l'objet GtkWidgetdans le module gtk.py. Vous verrez que cet objet possède une méthode show. Le corps du gestionnaire précédent peut être écrit comme suit: GladeXML("color.glade","about").get_widget("about").show().
def on_new_activate (obj): global rootGroup, colorShape for item in colorShape: item.destroy () del colorShape[0:] buildGameArea (rootGroup) |
Ce gestionnaire reconstruit une nouvelle aire de jeu. Les formes existantes sont d'abord détruites. Les formes sont des objets GnomeCanvasItem dérivés d'objets GtkObject. La méthode destroy est située dans l'objet GtkObject. Ensuite une nouvelle aire de jeu est construite.
Définir les formes
La fonction buildGameArea coordonne la création de l'aire de jeu dans le groupe GnomeCanvasGroup. Les formes -GnomeCanvasItem- sont construites à partir d'appels à la fonction buildShape. Les formes peuvent être des cercles, des carrés ou des étoiles.
La création de la forme est réalisée par le code suivant, selon la forme créée:
item = group.add ("ellipse", x1 = x - r, y1 = y - r, x2 = x + r, y2 = y + r, fill_color = color, outline_color = "black", width_units = 2.5) [...] item = group.add ("rect", x1 = x - a, y1 = y - a, x2 = x + a, y2 = y + a, fill_color = color, outline_color = "black", width_units = 2.5) [...] item = group.add ("polygon", points = pts, fill_color = color, outline_color = "black", width_units = 2.5) |
La variable group contient une référence à l'objet GnomeCanvasGroup. Si nous regardons dans le module ui.py, GnomeCanvasGroup a une méthode add. Son premier argument, tp attend une chaîne contenant les éléments à ajouter. Ses arguments suivants sont des paires d'arguments-clés et de valeurs qui sont comparées à un dictionnaire. Pour connaître la liste complète des mots-clés, regardez dans les objets GnomeCanvasRect, GnomeCanvasEllipse et GnomeCanvasPolygon dans ui.py.
ellipse et rectangle sont similaires, les deux coordonnées en abscisse et ordonnée définissent deux points opposés du carré dans lequel elles sont inscrites, soit top-left et bottom-right. L'origine du canvas est située par défaut en haut et à gauche top-left du canvas. Le polygon attend comme valeur du mot-clé points une liste de paire de coordonnées définissant les points du polygone. Les autres arguments sont évidents à comprendre.
Lier un évènement à une forme
Maintenant, connectons un évènement à chacune des formes créées. Ceci est fait à la fin de la fonction buildShape:
item.connect ('event', shapeEvent) colorShape.append (item) |
Nous utilisons juste la méthode connect de GtkObject qui est un objet père de GnomeCanvasItem. Son premier argument est le signal. Puisque GnomeCanvasItem a un seul signal event pour couvrir tous les types d'évènement, fixons-le à event. Le second argument est le nom du gestionnaire que nous avons écrit, soit shapeEvent. Il est possible de passer des données en tant que troisième argument mais nous n'en avons pas besoin. C'est tout!
Créons maintenant le gestionnaire pour les formes:
def shapeEvent (item, event): global selectedItem, itemToSelect, colorShape if event.type == ENTER_NOTIFY and selectedItem != item: #highligh outline item.set(outline_color = 'white') elif event.type == LEAVE_NOTIFY and selectedItem != item: #unlight outline item.set(outline_color = 'black') elif event.type == BUTTON_PRESS: #select the item if not selectedItem: item.set (outline_color = 'white') selectedItem = item elif item['fill_color_gdk'] == selectedItem['fill_color_gdk'] \ and item != selectedItem: #destroy both item item.destroy () selectedItem.destroy () colorShape.remove (item) colorShape.remove (selectedItem) selectedItem, itemToSelect = None, itemToSelect - 1 if itemToSelect == 0: buildGameArea (rootGroup) return 1 |
Quand ce gestionnaire est appelé, la variable item contient une référence à la forme qui a initié l'évènement. Dans l'évènement GdkEvent nous ne sommes intéressés que par trois types d'évènements:
Je n'ai pas décrit tout le code Python indépendant de Gnome. Il ne devrait pas être si difficile que cela à comprendre. Mon principal objectif dans ce tutoriel était de montrer comment comprendre soi-même le fonctionnement des choses: rechercher dans la bibliothèque Gnome pour Python ou les entêtes C et lire la documentation Gnome pour la programmation en C. Bien sûr, j'ai aussi montré la puissance et la simplicité du canvas Gnome et de Glade/LibGlade. A partir de là, vous pouvez faire beaucoup de choses pour étendre ce code. (Les fichiers source de cet article se trouvent ici)
#!/usr/bin/python # Couleur - Teo Serie # Copyright Hilaire Fernandes 2000 # Release under the terms of the GPL licence version 2 # You can get a copy of the license at http://www.gnu.org # # Select shapes with same color # from math import cos, sin, pi from whrandom import randint from gtk import * from gnome.ui import * from GDK import * from libglade import * width, itemToSelect = 400, 8 selectedItem = rootGroup = canvas = None # to keep trace of the canvas item colorShape =[]; def on_about_activate(obj): "display the about dialog" about = GladeXML ("color.glade", "about").get_widget ("about") about.show () def on_new_activate (obj): global rootGroup, colorShape for item in colorShape: item.destroy () del colorShape[0:] buildGameArea (rootGroup) def shapeEvent (item, event): global selectedItem, itemToSelect, colorShape if event.type == ENTER_NOTIFY and selectedItem != item: #highligh outline item.set(outline_color = 'white') elif event.type == LEAVE_NOTIFY and selectedItem != item: #unlight outline item.set(outline_color = 'black') elif event.type == BUTTON_PRESS: #select the item if not selectedItem: item.set (outline_color = 'white') selectedItem = item elif item['fill_color_gdk'] == selectedItem['fill_color_gdk'] \ and item != selectedItem: #destroy both item item.destroy () selectedItem.destroy () colorShape.remove (item) colorShape.remove (selectedItem) selectedItem, itemToSelect = None, itemToSelect - 1 if itemToSelect == 0: buildGameArea (rootGroup) return 1 def buildShape (group, number, type, color): "build a shape of 'type' and 'color'" global colorShape w = width / 4 x, y, r = (number % 4) * w + w / 2, (number / 4) * w + w / 2, w / 2 - 2 if type == 'circle': item = buildCircle (group, x, y, r, color) elif type == 'squarre': item = buildSquare (group, x, y, r, color) elif type == 'star': item = buildStar (group, x, y, r, 0.4, randint (3, 15), color) elif type == 'star2': item = buildStar (group, x, y, r, 0.6, randint (3, 15), color) item.connect ('event', shapeEvent) colorShape.append (item) def buildCircle (group, x, y, r, color): item = group.add ("ellipse", x1 = x - r, y1 = y - r, x2 = x + r, y2 = y + r, fill_color = color, outline_color = "black", width_units = 2.5) return item def buildSquare (group, x, y, a, color): item = group.add ("rect", x1 = x - a, y1 = y - a, x2 = x + a, y2 = y + a, fill_color = color, outline_color = "black", width_units = 2.5) return item def buildStar (group, x, y, r, k, n, color): "k: factor to get the internal radius" "n: number of branch" angleCenter = 2 * pi / n pts = [] for i in range (n): #external points of the star pts.append (x + r * cos (i * angleCenter)) pts.append (y + r * sin (i * angleCenter)) #internal points of the star pts.append (x + r * k * cos (i * angleCenter + angleCenter / 2)) pts.append (y + r * k * sin (i * angleCenter + angleCenter / 2)) pts.append (pts[0]) pts.append (pts[1]) item = group.add ("polygon", points = pts, fill_color = color, outline_color = "black", width_units = 2.5) return item def getEmptyCell (l, n): "get the n-th non null element of l" length, i = len (l), 0 while i < length: if l[i] == 0: n = n - 1 if n < 0: return i i = i + 1 return i def buildGameArea (group): global itemToSelect, selectedItem itemColor = ['red', 'yellow', 'green', 'brown', 'blue', 'magenta', 'darkgreen', 'bisque1'] itemShape = ['circle', 'squarre', 'star', 'star2'] emptyCell = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] itemToSelect, i, selectedItem = 8, 15, None for color in itemColor: # two items of same color n = 2 while n > 0: cellRandom = randint (0, i) cellNumber = getEmptyCell (emptyCell, cellRandom) emptyCell[cellNumber] = 1 buildShape (group, cellNumber, itemShape[randint (0, 3)], color) i, n = i - 1, n - 1 def initColor (): global rootGroup, canvas wTree = GladeXML ("color.glade", "colorApp") dic = {"on_about_activate": on_about_activate, "on_exit1_activate": mainquit, "on_new_activate":on_new_activate} wTree.signal_autoconnect (dic) canvas = wTree.get_widget ("canvas") rootGroup = canvas.root () initColor () buildGameArea (rootGroup) mainloop () |