Search code examples
gtkocamllablgtk

How to write new widgets implementing MVC with lablgtk2?


I am writing a family of new widgets for lablgtk2, the OCaml bindings for Gtk+. Some of these widgets can edit or present a fairly complex information, I am therefore interested in using model-view-controler or subject-observer, similar to what can be found in the GTree module.

This module defines a GTree.model and a GTree.view class, each having signals which can be connected to, and a GTree.model can be attached to one or more GTree.view's.

Imitating this organisation in pure OCaml is not that trivial, because the code available in the library is a binding of the C-library. I need to go through the following steps:

  1. Defining new widgets
  2. Defining new signals
  3. Triggering these new signals
  4. Defining new models

I could go through 1 and 2 but I am not sure how to do 3 and 4. How to do these right?

Defining new widgets

The definition of new widgets itself is not problematic. The new widget is typically a specialised version of the Gnome canvas or a composite. In the former case, our new widget can inherit from the Gnome canvas as a GObj.widget and in the latter case, we can use the GObj.widget provided by the container used to hold the composite. This typically looks like

class view () =
  let vbox = GPack.vbox () in
  …
  object(self)
    inherit GObj.widget vbox#as_widget
    …
  end

Defining new signals

The bindings give plenty of examples for code defining new signals, so that we can define new signals for our widgets, as illustrated by the following snippet, considering the simple case of signals without parameters:

open GtkSignal

module Event =
struct
  let plop : ([>`widget], unit -> unit) t = {
    name = "plop_event";
    classe = `widget;
    marshaller = marshal_unit;
  }
  let fizz : ([>`widget], unit -> unit) t = {
    name = "fizz_event";
    classe = `widget;
    marshaller = marshal_unit;
  }
end

class pill_signals obj =
object (self)
  inherit ['a] GObj.gobject_signals (obj :> Gtk.widget Gobject.obj)
  method plop = self#connect Event.plop
  method fizz = self#connect Event.fizz
end

With these definitions, our view widget can expose these signals by defining an appropriate connect method:

  method connect =
    new pill_signals obj

Triggering the new signals

It seems that the function GtkSignal.emit serves the purpose of emitting a signal to an object, triggering the registered callbacks. This functions as the following signature:

val emit :
  'a Gobject.obj ->
  sgn:('a, 'b) GtkSignal.t ->
  emitter:(cont:('c Gobject.data_set array -> 'd) -> 'b) ->
  conv:(Gobject.g_value -> 'd) -> 'b

The first two parameters are self-explaining, but it is not that clear, what the two remaining ones are. Unfortunately, there is no use example in lablgtk source code, as signals are emitted from the C-side of the code. These two arguments seems to be related with the preparation of the arguments of the signal, materialised as a 'c Gobject.data_set array and the retrieval of the yielded value with the argument labeled ~conv. Nevertheless, the role of the ~cont-argument in the emitter still has to be cleared.

Defining the new model

The tricky part in the definition of the model, is that it should inherit from GObj.object in order to be able to send an receive signals. Unfortunately, there is no function allowing to directly define a minimal Gtk+ object. The farthest I went in this direction was

module Model =
struct
  let create () =
    GtkObject.make ~classe:"GObject" []
end

let model () =
  new model (Model.create ())

Calling the function model to instantiate the corresponding object yields the message:

Gtk-CRITICAL **: IA__gtk_object_sink: assertion 'GTK_IS_OBJECT (object)' failed

Clearly, there is something fishy here, most probably the parameter list (the empty list in the snippet above) was too small.


Solution

  • LablGTK provides a nice interface to Gtk signaling mechanisms, which allows us to use it without tinkering with GtkSignal and marshalling functions. This interface is provided by GUtil and is neatly documented.


    How to use GUtil, as described in the module documentation

    To add ML signals to a LablGTK object:

    {[
       class mywidget_signals obj ~mysignal1 ~mysignal2 = object
         inherit somewidget_signals obj
         inherit add_ml_signals obj [mysignal1#disconnect; mysignal2#disconnect]
         method mysignal1 = mysignal1#connect ~after
         method mysignal2 = mysignal2#connect ~after
       end
    
       class mywidget obj = object (self)
         inherit somewidget obj
         val mysignal1 = new signal obj
         val mysignal2 = new signal obj
         method connect = new mywidget_signals obj ~mysignal1 ~mysignal2
         method call1 = mysignal1#call
         method call2 = mysignal2#call
       end
    ]}
    

    You can also add ML signals to an arbitrary object; just inherit from ml_signals in place of widget_signals and add_ml_signals.

    {[ 
      class mysignals ~mysignal1 ~mysignal2 = object
         inherit ml_signals [mysignal1#disconnect; mysignal2#disconnect]
         method mysignal1 = mysignal1#connect ~after
         method mysignal2 = mysignal2#connect ~after
       end
    ]}
    

    It is now easy to address the points 1, 2, 3, and 4 above:

    1. This is fine
    2. Use GUtil to define new signals instead of GtkSignal
    3. Triggering the new signals is accomplished by the call method of ['a] GUtil.signal.
    4. Since we do not use GtkSignal anymore, there is actually no problem.