# Image Viewer (Python)

Un peu plus qu'une simple application « Hello world » - écrire un visionneur d'images en GTK.
Jonh Wendell
jwendell@gnome.org
Johannes Schmid
jhs@gnome.org

# 1 Visionneur d'images

      Dans ce tutoriel, nous allons écrire une application GTK très simple qui charge et affiche un fichier image. Vous allez apprendre comment :

        écrire une interface graphique GTK en Python,
        travailler avec des événements en connectant des signaux à des gestionnaires de signaux,
        mettre en forme des interfaces utilisateur GTK avec des conteneurs,
        charger et afficher des fichiers image.

      Vous avez besoin de ce qui suit pour pouvoir suivre ce tutoriel :

        l'installation du paquet Anjuta IDE,
        des connaissances de base du langage de programmation python.

# Création d'un projet dans Anjuta

Avant de commencer à programmer, vous devez ouvrir un nouveau projet dans Anjuta. Ceci crée tous les fichiers qui vous sont nécessaires pour construire et exécuter votre programme plus tard. C'est aussi utile pour tout regrouper en un seul endroit.

Lancez Anjuta et cliquez sur Fichier Nouveau Projet pour ouvrir l'assistant de création de projet.

Sélectionnez PyGTK (automake) dans l'onglet Python, cliquez sur Continuer et saisissez vos informations sur les pages suivantes. Mettez image-viewer comme nom de projet et de répertoire.

Assurez-vous d'avoir désactivé Utiliser GtkBuilder pour l'interface utilisateur car nous allons créer l'interface utilisateur manuellement dans cet exemple. Consultez l'exemple Guitar-Tuner pour un exemple d'utilisation du constructeur d'interfaces.

Cliquez sur Appliquer et le projet est créé. Ouvrez src/image_viewer.py depuis l'onglet Projet ou l'onglet Fichiers. Il contient quelques lignes de code très basique.

# Une première application Gtk

Voyons à quoi ressemble une application Gtk basique écrite en Python :

from gi.repository import Gtk, GdkPixbuf, Gdk import os, sys

class GUI: def __init__(self): window = Gtk.Window() window.set_title ("Hello World") window.connect_after('destroy', self.destroy)

window.show_all()

def destroy(window, self): Gtk.main_quit()

def main(): app = GUI() Gtk.main()

if __name__ == "__main__": sys.exit(main())

Regardons ce qui se passe :

La première ligne importe l'espace de nom Gtk (c.-à-d. cela inclut la bibliothèque Gtk). Les bibliothèques sont fournies par « GObject Introspection (gi) » qui fournit les liens de langage à beaucoup de bibliothèques GNOME.

La méthode `__init__` de la classe `GUI` crée une fenêtre `Gtk.Window` (vide), définit son titre, puis connecte un signal pour quitter l'application quand la fenêtre est fermée. C'est globalement très simple. Nous en verrons un peu plus sur les signaux plus tard.

Ensuite est définie la méthode `destroy` qui quitte juste l'application. Elle est appelée par le signal connecté ci-dessus.

Le reste du fichier effectue l'initialisation pour Gtk et affiche l'interface graphique.

Ce programme est prêt à être utilisé, donc essayez-le en utilisant le menu Exécuter Exécuter. Cela devrait vous afficher une fenêtre vide.

# Les signaux

Les signaux sont un des concepts clé de la programmation Gtk. Chaque fois que quelque chose arrive à un objet, il émet un signal ; par exemple, quand un bouton est cliqué, il renvoie le signal `clicked`. Si vous voulez que votre programme réagisse en conséquence, il faut connecter une fonction (un « gestionnaire de signal ») à ce signal. Voici un exemple :

def button_clicked () : print "you clicked me!"

b = new Gtk.Button ("Click me") b.connect_after ('clicked', button_clicked)

Les deux dernières lignes créent un bouton `Gtk.Button` appelé `b` et connectent son signal `clicked` à la fonction `button_clicked` définie au-dessus. Chaque fois que ce bouton est cliqué, le code de la fonction `button_clicked` est exécuté. Il affiche juste un message dans cet exemple.

# Les conteneurs : agencement de l'interface utilisateur

Les éléments graphiques (les contrôles, comme les boutons ou les étiquettes) peuvent être disposés dans la fenêtre à l'aide de conteneurs. Vous pouvez organiser l'agencement en mélangeant différents types de conteneurs, comme des boîtes ou des grilles.

Une fenêtre `Gtk.Window` est elle-même un type de conteneur, mais vous ne pouvez y insérer directement qu'un seul élément graphique. Nous aimerions avoir deux éléments graphiques, une image et un bouton, donc nous devons placer un conteneur de « plus haute capacité » dans la fenêtre pour tout contenir. Plusieurs types de conteneurs sont disponibles, mais nous allons utiliser ici une boîte `Gtk.Box`. Une boîte `Gtk.Box` peut contenir plusieurs éléments graphiques, disposés horizontalement ou verticalement. On pourrait créer des agencements plus complexes en mettant plusieurs boîtes dans une autre et ainsi de suite.

Il existe un concepteur graphique d'interface utilisateur appelé Glade intégré à Anjuta ce qui rend la conception d'interface utilisateur vraiment facile. Dans cet exemple simple, cependant, nous allons tout programmer à la main.

Ajoutons la boîte et les éléments graphiques à la fenêtre. Insérez le code suivant dans la méthode `__init__`, juste après la ligne `window.connect_after` :

box = Gtk.Box() box.set_spacing (5) box.set_orientation (Gtk.Orientation.VERTICAL) window.add (box)

La première ligne crée une `Gtk.Box` nommée `box` et les lignes suivantes définissent deux de ses propriétés : l'`orientation` est verticale (donc les éléments graphiques sont disposés en colonne) et l'espacement entre les éléments graphiques est fixé à 5 pixels. La ligne suivante ajoute ensuite cette nouvelle `Gtk.Box` à la fenêtre.

Pour l'instant, la fenêtre ne contient qu'une boîte `Gtk.Box` vide et si vous exécutez le programme, vous ne verrez pas encore de changement (la boîte `Gtk.Box` est un conteneur transparent et vous ne le voyez donc pas).

# Placement : ajout d'éléments graphiques au conteneur

Pour ajouter d'autres éléments graphiques à la fenêtre, insérez ce code juste dessous la ligne `window.add (box)` :

self.image = Gtk.Image() box.pack_start (self.image, False, False, 0)

La première ligne crée une nouvelle image `Gtk.Image` nommée `image` qui sera utilisée pour afficher un fichier image. Comme nous en avons besoin plus tard dans la fonction de gestion de signal, nous la définissons comme étant un attribut de classe. Vous devez ajouter `image = 0` au début de la classe `GUI`. Puis, l'élément graphique image est ajouté (placé) dans le conteneur `box` à l'aide de la méthode `pack_start` de GtkBox.

`pack_start` prend 4 arguments : l'élément graphique enfant (`child`) qui doit être ajouté à la boîte `Gtk.Box` ; si la boîte `Gtk.Box` doit s'agrandir (`expand`) quand le nouvel élément graphique est ajouté ; si le nouvel élément graphique doit prendre tout le nouvel espace créé quand la boîte `Gtk.Box` s'agrandit (`fill`) ; et enfin combien d'espace libre (`padding`), en pixels, il doit y avoir entre l'élément graphique et ses voisins à l'intérieur de la `Gtk.Box`.

Si vous les laissez faire, les conteneurs Gtk (et les éléments graphiques) s'agrandissent dynamiquement pour occuper tout l'espace disponible. Vous ne positionnez pas les éléments graphiques dans la fenêtre en leur spécifiant des coordonnées x,y précises mais vous les positionnez les uns par rapport aux autres. Ceci permet un redimensionnement plus aisé de la fenêtre et les éléments graphiques devraient automatiquement prendre une taille raisonnable dans la plupart des cas.

Notez aussi de quelle manière les éléments graphiques sont organisés hiérarchiquement. Une fois placée dans la `Gtk.Box`, l'image `Gtk.Image` est considérée comme un enfant de la boîte `Gtk.Box`. Ceci vous permet de traiter tous les enfants d'un élément graphique comme un groupe ; par exemple, vous pouvez masquer la boîte `Gtk.Box` qui du coup, masque tous ses enfants en même temps.

Insérez maintenant ces deux lignes en dessous des deux que vous venez d'ajouter :

button = Gtk.Button ("Open a picture...") box.pack_start (button, False, False, 0)

Ces lignes ressemblent aux deux premières, mais cette fois elles créent un bouton `Gtk.Button` et l'ajoute à la `box`. Notez que nous paramétrons ici l'argument `expand` (le second) sur `False`, alors qu'il était sur `True` pour l'image `Gtk.Image`. Ceci va conduire à ce que l'image prenne tout l'espace disponible et le bouton seulement l'espace nécessaire. Lors d'un redimensionnement de la fenêtre, la taille du bouton reste identique, alors que la taille de l'image va augmenter pour occuper le reste de la fenêtre.

# Chargement de l'image : connexion au signal `clicked` du bouton

Quand l'utilisateur clique sur le bouton Open a picture..., une boîte de dialogue s'affiche pour lui permettre de choisir une image. Une fois ce choix effectué, la photo est chargée et affichée dans l'élément graphique image.

Connectez d'abord le signal `clicked` du bouton à une fonction de gestion de signal que vous appelez `on_open_clicked`. Mettez ce code immédiatement après la ligne `button = Gtk.Button()` où le bouton a été créé :

button.connect_after('clicked', self.on_open_clicked)

Cela connecte le signal `clicked` à la méthode `on_open_clicked` que nous définirons ci-dessous.

# Chargement de l'image : écriture de la fonction de rappel du signal

On peut maintenant créer la méthode `on_open_clicked`. Insérez ce qui suit dans le bloc de code de la classe `GUI`, après la méthode `__init__` :

def on_open_clicked (self, button): dialog = Gtk.FileChooserDialog ("Open Image", button.get_toplevel(), Gtk.FileChooserAction.OPEN); dialog.add_button (Gtk.STOCK_CANCEL, 0) dialog.add_button (Gtk.STOCK_OK, 1) dialog.set_default_response(1)

filefilter = Gtk.FileFilter () filefilter.add_pixbuf_formats () dialog.set_filter(filefilter)

if dialog.run() == 1: self.image.set_from_file(dialog.get_filename())

dialog.destroy()

C'est un peu plus compliqué que tout ce que nous avons essayé jusqu'à présent, donc décortiquons cette partie étape par étape :

La ligne commençant par `dialog` crée une boîte de dialogue Open qui permet à l'utilisateur de choisir des fichiers. Nous paramétrons trois propriétés : le titre de la boîte de dialogue ; la fonction (le type) de la boîte de dialogue (c'est une boîte de dialogue « OPEN », mais on aurait pu utiliser (`SAVE`) (enregistrer) si notre intention avait été d'enregistrer un fichier) et `transient_for`, qui définit la fenêtre parent de la boîte de dialogue.

Les deux lignes suivantes ajoutent les boutons Cancel (Annuler) et Open (Ouvrir) à la boîte de dialogue. Le second argument de la méthode `add_button` est la valeur (de type entier) qui est retournée lorsque le bouton est cliqué : 0 pour Annuler et 1 pour Ouvrir.

Notez que nous utilisons les noms de bouton de la collection (stock) Gtk au lieu de saisir manuellement « Cancel » ou « Open ». L'avantage d'utiliser les noms de la collection est que les étiquettes des boutons seront déjà traduites dans la langue de l'utilisateur.

`set_default_response` détermine le bouton qui est activé quand l'utilisateur fait un double-clic sur un fichier ou appuie sur la touche Entrée. Ici, nous utilisons le bouton Open (qui a la valeur 1).

Les trois lignes suivantes limitent la boîte de dialogue Ouvrir à l'affichage des seuls fichiers pouvant être ouverts par `Gtk.Image`. Un objet filtre est d'abord créé ; ensuite nous ajoutons tous les types de fichier pris en charge par `Gdk.Pixbuf` (ce qui inclut la plupart des formats d'image comme PNG ou JPEG) au filtre. Enfin, nous appliquons ce filtre à la boîte de dialogue Open.

`dialog.run` affiche la boîte de dialogue Open. La boîte de dialogue attend que l'utilisateur choisisse une image ; quand c'est fait, `dialog.run` retourne la valeur 1 (il retourne la valeur 0 si l'utilisateur clique sur Annuler). L'instruction `if` teste cette réponse.

Supposons que l'utilisateur a cliqué sur le bouton Ouvrir, la ligne suivante définit la propriété `file` du `Gtk.Image` au nom de fichier image sélectionné. `Gtk.Image` charge alors l'image choisie et l'affiche.

La dernière ligne de cette méthode détruit la boîte de dialogue Open car nous n'en avons plus besoin.

# Exécution de l'application

Tout le programme nécessaire est maintenant en place, donc essayez de l'exécuter. Cela devrait fonctionner ; un visionneur d'images totalement fonctionnel (et une visite éclair de Python et de Gtk) en très peu de temps !

# Implémentation de référence

Si vous rencontrez des difficultés avec ce tutoriel, comparez votre programme à ce programme de référence.

# Les étapes suivantes

Voici quelques idées sur la manière d'étendre ce simple exemple :

Faire que l'utilisateur puisse sélectionner un dossier plutôt qu'un fichier et fournir les contrôles pour naviguer parmi toutes les images d'un dossier.

Appliquer au hasard des filtres et des effets à l'image quand elle est chargée et permettre à l'utilisateur d'enregistrer l'image modifiée.

GEGL fournit de puissantes possibilités de manipulation d'image.

Permettre à l'utilisateur de charger des images depuis des sites de partage, des scanners ou d'autres sources plus sophistiquées.

Vous pouvez utiliser GIO pour gérer des transferts de fichiers en ligne et des choses du même type et GNOME Scan pour gérer les documents scannés.