aboutsummaryrefslogtreecommitdiff
path: root/content/Informatique/ocaml_gtk.rst
blob: b124cb5f50f0bf320611012f54fbdbbb6c27e094 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
.. -*- rst -*-
.. -*-  coding: utf-8 -*-

=======================================
Trois manière d'utiliser Gtk avec OCaml
=======================================

:date: 2014-02-09
:tags: ocaml, gtk
:summary: |summary|
:logo: static/images/ocaml/camel_75.jpg

.. default-role:: literal

.. figure:: |filename|/images/ocaml/camel_2.jpg
    :figwidth: 150
    :figclass: floatleft
    :alt: Pavement

    Image : `Kevin Botto`_ (creativecommons_)

.. _Kevin Botto: http://www.flickr.com/photos/kevinbotto/3251157974/
.. _creativecommons: http://creativecommons.org/licenses/by-nd/2.0/deed.fr

|summary|


.. |summary| replace::
    Créer des interfaces Gtk pour OCaml peut se réveler compliquer. On trouve
    beaucoup d'explications un peu confuses sur le net, et n'est pas facile de
    trouver comment faire.
    Je vous propose ici trois manières différentes en utilisants les différents
    outils disponibles.

Il est possible d'utiliser la bibliothèque Gtk à l'aide de la librairie
lablgtk_, qui fourni un binding pour l'ensemble de la bibliothèque. L'avantage
est de pouvoir fonctionner sur toutes les plateformes où gtk est disponible, et
de bénéficier d'une interface évoluée et agréable pour écrire son application.

L'inconvénient réside dans la bibliothèque gtk : api très compliquée,
documentation confuse, bref on se retrouve rapidement face à des problèmes là
où l'on pensait pouvoir avancer sans difficulté. De plus, on est aujourd'hui
passé à gtk3 alors que `lablgtk` utilise toujours l'api de gtk2. Cela ne pose pas
de problème dans la compilation (la compatibilité est assurée), mais peut poser
problème lors de l'utilisation d'outils tels que `glade` (voir plus loin).

.. _lablgtk:  http://lablgtk.forge.ocamlcore.org/

Tout construire à la main
=========================

C'est la première solution, qui demande de connaître Gtk : tous les objets sont
construits à la main, et le code décrit la manière de faire. L'avantage est que
le code ne dépend d'aucune ressource externes contrairement aux deux suivantes.
De plus on contrôle complètement la création de l'interface, on peut donc
choisir de paramétrer l'interface au lieu d'avoir une interface unique. Un
tutoriel complet est disponible sur le site d'`OCaml.org`_, je ne vais pas
le reprendre ici et vous encourage à le suivre.

.. _ocaml.org: http://ocaml.org/learn/tutorials/introduction_to_gtk.html

L'exemple est donné dans la console interactive. Si l'on souhaite le compiler
dans un module, il faut initialiser Gtk avant de lancer l'affichage :

.. code-block:: ocaml

    GtkMain.Main.init ();

Pour compiler un module, il est nécessaire de faire référence au package
`lablgtk2`. Voici un exemple pour compiler un fichier `hello.ml` :

.. code-block:: console

    $ ocamlfind ocamlc -package lablgtk2 -I . -c hello.ml

Maintenant il ne reste plus qu'à se plonger dans la documentation de gtk pour
construire son interface !

Utiliser Glade
==============

Glade est une interface graphique permettant de construire son application en
plaçant les contrôles de manière visuelle. Elle génère un fichier XML qui
décrit l'interface et sera chargé par l'application pour construire
l'interface. Cela permet de gagner du temps et d'éviter d'écrire le code
nécessaire pour construire son interface, on se concentre sur les actions
à exécuter lorsque l'utilisateur interagit.

.. image:: |filename|/images/glade.jpg
    :class: center
    :alt: Utilisation de glade.


Les XMLs générés par glade sont destinés à être utilisés avec gtk3. Or,
`lablgtk` utilise encore gtk2, il est donc nécessaire d'utiliser une conversion
pour pouvoir les charger par la suite. Voici une petite règle `make` qui se
charge de faire le travail :

.. code-block:: make

    %.glade2: %.glade
	    cat $< | sed 's/interface/glade-interface/' | sed 's/object/widget/' | sed 's/GtkBox/GtkVBox/' > $@

Maintenant qu'on dispose d'un fichier au format glade2, on peut le charger dans
OCaml.

Attention, lors de la compilation, il est nécessaire d'utiliser `libglade` pour
construire l'application, celle-ci est disponible dans la librairie
`lablgtk2.glade`. Voici donc un exemple de commande pour compiler un fichier
`hello.ml` :

.. code-block:: console

    $ ocamlfind ocamlc -package lablgtk2.glade -I . -c hello.ml

.. _gtkbuilder: https://developer.gnome.org/gtk3/3.4/GtkBuilder.html

Charger le fichier xml
----------------------

Il s'agit de la solution la plus dynamique : on référence le fichier xml dans
le code, et l'interface se construit toute seule. Cette solution est présentée
sur le site de `developpez.com`_. L'exemple donné est toujours valide, il ne
faut pas oublier d'initialiser Gtk avec la commande suivante avant de lancer la
construction de l'interface :

.. code-block:: ocaml

    GtkMain.Main.init ();

L'inconvénient de cette méthode (du moins pour un développeur OCaml) est que
l'on est obligé de convertir de manière dynamique tous les objets présents dans
le XML. Il n'est pas possible de savoir au moment de la compilation si le code
que l'on va exécuter est bien valide.

En effet, les objets chargés depuis le XML nous sont retournés sous la forme
widget gtk, qu'il faut convertir en Bouton, Menu via les méthodes appropriées
(`GtkWindow.Window.cast` par exemple). On n'est donc pas à l'abri d'avoir une
erreur lors du fonctionnement du programme, alors que celui-ci a pu compiler
sans problème. Je pense que lorsqu'on cherche à développer en OCaml, ce genre
de problème peut être rédhibitoire.

.. _developpez.com: http://blog.developpez.com/damien-guichard/p7748/programmation-fonctionnelle/hello_developpez_avec_libglade_xml_1

De plus, rien ne garantie que le fichier XML ne va pas évoluer de manière
incompatible du code ; les deux étant distincts.


Utiliser lablgladecc2
---------------------

Heureusement, la librairie `lablgtk2` nous fourni un petit utilitaire nommé
`lablgladecc2` qui va convertir un fichier xml glade2 en un fichier OCaml. On
dispose donc d'un chargement dynamique du fichier xml, mais en gardant un code
cohérent, qui détectera les erreurs dès la compilation. Il s'agit en quelque
sorte d'un moyen de combiner les deux solutions précédentes.

On va ajouter une règle au makefile pour générer notre fichier OCaml :

.. code-block:: make

    %.ml: %.glade2
	    lablgladecc2 -embed $< > $@

Le fichier généré se compose d'une classe reprenant les différents composants
de notre interface, il ne nous reste plus qu'à réaliser les connexions, ainsi,
à partir d'un fichier glade nommé *gui* composé d'une fenêtre `window1`, d'un
bouton `button` et d'une entrée de menu, on peut créer le code suivant :

.. code-block:: ocaml

    let gladecc () =
      let window = new Gui.window1 () in

      window#button#connect#clicked (fun () -> prerr_endline "Ouch!");
      window#window1#connect#destroy GMain.quit;
      window#imagemenuitem5#connect#activate GMain.quit;

      window#toplevel#show ()

    let () =
        GtkMain.Main.init ();
        gladecc ();
        GMain.Main.main ()


l'objet `toplevel` est créé par `lablgladecc2` et correspond à la fenêtre
principale de notre objet.

Dans cette chaîne de compilation, le fichier xml est écrit dans le programme
OCaml (il s'agit de la signification de l'option `-embed`), ainsi, le fichier
XML n'a pas besoin de figurer parmi les ressources de l'application.

Conclusion
==========

Trois manière de faire qui répondent à trois besoin différents, entre le tout
codé et le tout dynamique, il est possible de créer des interfaces graphiques
en utilisant les capacités du langage caml sur l'inférence de type et le
contrôle de l'ensemble de l'application.

Pour ma part, je préfère la dernière solution, qui permet de conserver la
simplicité de `glade` combiné avec la force du langage OCaml. J'ai écrit cet
article suite à pas mal d'errance sur le net pour trouver les informations
nécessaires, j'espère que la documentation va évoluer par la suite et permettre
de faire ce genre de choses plus facilement…