Développement des interfaces graphiques GTK+3

Environnement

Répertoire de travail ~/media/dplus/python-dev/

Structure pour le développement d’application graphique

Installer python et gtk3 + test

Article original Des interfaces graphiques en Python et GTK
Auteur : Wizix

Avant de commencer à créer vos GUI (Graphical User Interface), il va falloir installer PyGObject et ses dépendances. PyGObject est un module Python qui donne l’accès aux développeurs aux bibliothèques basées sur GObject comme, dans le cas qui nous intéresse, GTK+. On va donc pouvoir utiliser GTK+ avec Python ! ^^

Voici les dépendances requises pour l’utiliser :

  • GTK+ 3
  • Python 3.1 au minimum
  • gobject-introspection

Installer les dépendances : gtk3, python-gobject (or python2-gobject for Python 2)

apt-get

sudo apt-get install python3-gi

pacman

sudo pacman -S gtk3 python-gobject

Test

nano hello_world.py
#!/usr/bin/env python
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
Gtk.init(None)
Hello = Gtk.MessageDialog(message_type=Gtk.MessageType.INFO,
                          buttons=Gtk.ButtonsType.OK,
                          text="Hello world!",
                          secondary_text="This is an example dialog.")
Hello.run()

Ouvrir un terminal dans le répertoire de travail

python hello_world.py

Une fenêtre s’ouvre

GTK+

Je vous promet que vous avez tout intérêt à bien vous entendre avec cette documentation. En effet, vous allez y passer du temps car il est juste impossible de retenir toutes les méthodes offertes par GTK+. Je me dois donc de vous montrer comment la lire : rien de bien compliqué je vous rassure, elle est vraiment très bien faite et complète.

Bien, rendez-vous sur ce lien : http://lazka.github.io/pgi-docs/Gtk-3.0/. Vous l’aurez compris, c’est de cette documentation que l’on va parler ici.

Descendez la page et arrêtez vous à la section API. Comme vous pouvez le voir, la documentation se décompose en dix grandes parties :

Partie Description
Functions Partie que nous n’utiliserons pas, elle rassemble les différentes fonctions pour contrôler ou voir l’état de GTK.
Callbacks De même, elle vous sera inutile. Elle rassemble les callbacks (mot que nous étudierons après) des différents widgets (pareil) de GTK.
Interfaces Les interfaces de GTK.
Classes LA partie qui nous intéresses le plus. Cette partie référence tous les widgets que vous allez pouvoir utiliser pour construire votre interface. J’en ai compté à peu près 258. Sachant que chaque widget à ses propres méthodes et signaux, j’espère que vous comprenez désormais l’intérêt de cette documentation.
Hierarchy Partie un peu spéciale, car contrairement aux autres, elle ne vous renseigne pas sur l’utilité de telle ou telle chose mais sur comment est organisé GTK.
Structures Documente tous les types de données créés par GTK. Nous utiliserons seulement le Gtk.TreeIter dans la seconde partie de ce tutoriel.
Flags Les différents drapeaux utilisés par les widgets pour décrire leur état. Je n’en ai personnellement jamais utilisé un seul. :-°
Enums Rassemble les différentes énumérations de GTK. Par exemple, si vous souhaitez centrer un widget, vous allez utiliser Gtk.Align.CENTER
Constants Les constantes de GTK.
Symbol Mapping Si vous avez vu une fonction appelée en C, utilisez ces tableaux pour savoir comment l’appeler avec Python (une recherche sur la page est beaucoup plus efficace que de chercher à la main :p ).

Je vais vous détailler le fonctionnement de la partie classes. Tous les widgets sont classés par ordre alphabétique. Je vous laisse trouver la classe Gtk.Button qui permet de créer des boutons. C’est bon, vous y êtes ?

Au tout début de la page, vous avez le droit à l’arbre présentant de quelles classes hérite un bouton. Comme vous pouvez le voir, c’est complexe. Les classes en bleu sont des interfaces tandis que celles en gris peuvent être instanciées.

Arbre généalogique d'un bouton de GTK+ 3

Dessous vous avez un exemple et toutes les subclasses c’est à dire les classes qui hérites de Gtk.Button. Vous trouvez ensuite une liste complète de toutes les méthodes, cliquez sur une pour voir en détail ce qu’elle fait, ce qu’elle prend comme argument, ce qu’elle retourne.

Viennent ensuite les méthodes virtuelles. C’est grâce à elles que vous allez pouvoir émettre des signaux manuellement (ne vous inquiétez pas si vous ne savez pas de quoi je parle, nous verrons ça dans un prochain chapitre).

Les propriétés du widgets sont en-dessous. Chaque propriété peut être changée soit grâce à une méthode soit manuellement. Par exemple :

button.set_label('Je suis le texte dans le bouton : un label quoi')
# sera équivalent à 
button.props.label = 'Je suis le texte dans le bouton : un label quoi'

Code: Changement du texte d’un bouton

Je vous conseille cependant de passer par les setteurs/getteurs.

Vous trouverez aussi une section sur les différents signaux que peut émettre un bouton.

Puis vient ensuite la description en profondeur de chaque méthode, chaque constructeur, chaque signal… Comme je vous l’ai dit, la documentation est super complète et précise, profitez-en !

Glade

Glade est un concepteur d’interface pour GTK+

Installation

sudo apt-get install glade # debian
sudo pacman -S glade

À l’ouverture de Glade devrait apparaître une fenêtre ressemblant un peu à ça :

Création d’un projet glade

Cliquer sur le + , “Crée un nouveau projet”

Pour commencer, rendez-vous dans l’inventaire, dans la section Niveaux supérieurs. Prenez la première icône et faite la glisser dans la vue ou cliquez dessus. Cette icône représente une Gtk.Window.

Se trouve à côté en italique le nom de la classe correspondante.

L’onglet Général ne concerne que le widget sélectionné.
Dans l’onglet Général , changez Identifiant par window_main.
Depuis la section Conteneurs , glisser déposer GtkBox

Glisser déposer deux widgets Label GtkLabel depuis la section Affichage

Modifiez les ID d’étiquette et le texte.
Changez l’ID de la première étiquette en lbl_hello et le texte de l’étiquette en “Bonjour”
Changez l’ID de la deuxième étiquette en lbl_count et le texte en “”

Votre fenêtre principale dans l’éditeur Glade devrait maintenant ressembler à ce qui suit.

Depuis la section Contrôle , ajouter un bouton GtkButton avec alignement au centre

Modifier la largeur et le titre de la fenêtre principale window_main (onglet Général du composant GtkWindow)

Connecter le signal du bouton

Sélectionnez le bouton en cliquant dessus (btn_hello). Cliquez sur l’onglet Signaux dans la fenêtre droite de Glade. Sous GtkButton, trouver “clicked”. Cliquez sur le texte qui se trouve à côté de l’intitulé du gestionnaire qui dit Type here. Cliquez de nouveau dessus pour l’éditer et commencez à taper. Appuyez sur la flèche vers le bas pour sélectionner le nom suggéré par Glade qui doit être on_btn_hello_clicked. Appuyez sur Entrée pour sélectionner le texte. Appuyez à nouveau sur la touche Entrée pour régler le texte.

Le code python

Créer un fichier python glade_test.py et rentrez-y ce qui suit :

#!/usr/bin/env python3
# coding: utf-8

from gi.repository import Gtk

def when_button_is_clicked(label):
    '''
    Quand le bouton est cliqué
    '''
    label.set_text('Hello world!')


builder = Gtk.Builder()
builder.add_from_file('window_main.glade') 

window = builder.get_object('main_window')
# Peut se faire dans Glade mais je préfère le faire ici, à vous de voir
window.connect('delete-event', Gtk.main_quit)

# Le handler
handler = {'on_clicked': when_button_is_clicked}
builder.connect_signals(handler)

window.show_all()
Gtk.main()

Il nous faut quelque chose capable de lire notre fichier .glade. Le Gtk.Builder est là pour ça. Comme son nom l’indique, il va nous aider à construire notre interface. On sélectionne notre fichier avec Gtk.Builder.add_from_file().

Ensuite on récupère notre Gtk.Window grâce à son identifiant avec la méthode Gtk.Builder.get_object(). En la récupérant, on a aussi toutes les options de notre fenêtre, vous n’avez donc pas à vous occuper de ça ! Ça correspond à faire ça :

window = Gtk.Window(title='Hello world')
window.set_...

Vous en conviendrez, c’est plus simple avec Glade ! ;)

On passe ensuite aux handlers. Ici j’ai créé un dictionnaire qui contient en indice le nom du signal et en valeur le callback à appeler quand ce signal est émit. Ici, notre dictionnaire ne contient qu’un seul handler, mais dans une fenêtre plus complexe, on peut facilement monter à une dizaine de signaux. Il existe un autre moyen de définir ses handlers en passant par une classe. Mais n’utilisant jamais ce système, je vous laisse regarder ici pour plus de détail.

Puis on indique au Gtk.Builder d’utiliser ce dictionnaire avec Gtk.Builder.connect_signals(). Petite précision, certains d’entre vous auront peut être remarqué que je ne prend pas de bouton en argument de mon callback. Et c’est bien vu. En fait, quand vous passez un argument avec Glade, le bouton n’est plus passé en argument automatiquement. Si vous souhaitez passer plus d’un seul argument à un callback, il va falloir passer par Python et oublier Glade pour connecter votre widget.

Le code C

le fichier makefile pour une application nommé apptest

# change application name here (executable output name)
TARGET=apptest

# compiler
CC=gcc
# debug
DEBUG=-g
# optimisation
OPT=-O0
# warnings
WARN=-Wall

PTHREAD=-pthread

CCFLAGS=$(DEBUG) $(OPT) $(WARN) $(PTHREAD) -pipe

GTKLIB=`pkg-config --cflags --libs gtk+-3.0`

# linker
LD=gcc
LDFLAGS=$(PTHREAD) $(GTKLIB) -export-dynamic

OBJS=    main.o

all: $(OBJS)
    $(LD) -o $(TARGET) $(OBJS) $(LDFLAGS)
    
main.o: src/main.c
    $(CC) -c $(CCFLAGS) src/main.c $(GTKLIB) -o main.o
    
clean:
    rm -f *.o $(TARGET)

ATTENTION à la syntaxe

Ecrire le code C
Ouvrez le fichier modèle main.c pour l’édition qui se trouve dans le dossier src du projet.

Nous avons besoin d’un pointeur vers chacune des étiquettes du projet afin de pouvoir mettre la main sur les étiquettes pour changer leur texte. Dans ce projet simple, deux pointeurs globaux seront définis en haut du fichier C.
Le fichier main.c

#include <gtk/gtk.h>

GtkWidget *g_lbl_hello;
GtkWidget *g_lbl_count;

int main(int argc, char *argv[])
{
    GtkBuilder      *builder; 
    GtkWidget       *window;

    gtk_init(&argc, &argv);

    builder = gtk_builder_new();
    gtk_builder_add_from_file (builder, "glade/window_main.glade", NULL);

    window = GTK_WIDGET(gtk_builder_get_object(builder, "window_main"));
    gtk_builder_connect_signals(builder, NULL);
    
    g_lbl_hello = GTK_WIDGET(gtk_builder_get_object(builder, "lbl_hello"));
    g_lbl_count = GTK_WIDGET(gtk_builder_get_object(builder, "lbl_count"));

    g_object_unref(builder);

    gtk_widget_show(window);                
    gtk_main();

    return 0;
}

void on_btn_hello_clicked()
{
    static unsigned int count = 0;
    char str_count[30] = {0};
    
    gtk_label_set_text(GTK_LABEL(g_lbl_hello), "Bonjour le monde!");
    count++;
    sprintf(str_count, "%d", count);
    gtk_label_set_text(GTK_LABEL(g_lbl_count), str_count);
}

// called when window is closed
void on_window_main_destroy()
{
    gtk_main_quit();
}

Construire et exécuter le projet

Ouvrez le répertoire du modèle dans une fenêtre de terminal et construisez le projet

make

Le projet peut être exécuté en double-cliquant sur l’icône Bonjour après avoir navigué dans le dossier du projet à l’aide d’un gestionnaire de fichiers ou à partir de la fenêtre du terminal en entrant :

./apptest

La fenêtre Packing devrait s’ouvrir.

En cliquant sur le bouton Hello, le texte “Bonjour le monde !” devrait s’afficher dans la première étiquette et incrémenter le comptage dans la deuxième étiquette.

Exemples Glade Python C GTK3

Ajouter des données et retourner la ligne sélectionnée à partir d’une ComboBox

Le fichier glade/window_combo.glade

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkWindow" id="window1">
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Test combo</property>
    <property name="default_width">250</property>
    <property name="default_height">100</property>
    <signal name="destroy" handler="on_window1_destroy" swapped="no"/>
    <child>
      <placeholder/>
    </child>
    <child>
      <object class="GtkBox" id="label1">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkComboBox" id="combobox1">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <signal name="changed" handler="on_combobox1_changed" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkLabel">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="label" translatable="yes">label</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

Le programme python window_combo.py

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gio
import sys

class Buglump:

  def __init__(self):
    self.builder = Gtk.Builder()
    self.builder.add_from_file("glade/window_combo.glade")
    self.builder.connect_signals(self)

    # the liststore
    self.liststore = Gtk.ListStore(int,str)
    self.liststore.append([0,"Select an Item:"])
    self.liststore.append([1,"Row 1"])
    self.liststore.append([2,"Row 2"])
    self.liststore.append([3,"Row 3"])
    self.liststore.append([4,"Row 4"])
    self.liststore.append([5,"Row 5"])

    # the combobox
    self.combobox = self.builder.get_object("combobox1")
    self.combobox.set_model(self.liststore)
    self.cell = Gtk.CellRendererText()
    self.combobox.pack_start(self.cell, True)
    self.combobox.add_attribute(self.cell, 'text', 1)
    self.combobox.set_active(0)

    self.window = self.builder.get_object("window1")
    self.window.show()

  def on_combobox1_changed(self, widget, data=None):
    self.index = widget.get_active()
    self.model = widget.get_model()
    self.item = self.model[self.index][1]
    #print "ComboBox Active Text is", self.item
    #print "ComboBox Active Index is", self.index
    self.builder.get_object("label1").set_text(self.item)

  def on_window1_destroy(self, object, data=None):
    #print "quit with cancel"
    Gtk.main_quit()

if __name__ == "__main__":
  main = Buglump()
  Gtk.main()

Rendre exécutable le programme

chmod +x window_combo.py

Exécuter

./windo_combo.py


Suivant la sélection affiche “Row 1”, “Row 2”,…