.. -*- rst -*- .. -*- coding: utf-8 -*- ======================================= Trois manière d'utiliser Gtk avec OCaml ======================================= :date: 2014-02-09 :tags: ocaml, gtk :summary: |summary| :logo: /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…