TOM
 
TOM/Gtk bindings
 
 
TOM Home
TOM Tasks

FAQ
News
Highlights
Publications
Documentation
Download TOM
TOM Software
Bug Database
Mailing Lists

Mail:
tiggr at gerbil.org

Short Cuts:
Tesla
TOM/Gtk
GP
MU

Snapshots:
all of 'em
tom [an error occurred while processing this directive]
tesla [an error occurred while processing this directive]
mu [an error occurred while processing this directive]
tomgtk [an error occurred while processing this directive]

Released:
all of 'em
tom 1.1.1
tomgtk 0.11
tesla 0.91
gp 0.5
mu 1.0

Misc:
GIF free NOW!

(This is the Apr 5 1998 TOM highlight. It is included in the TOM/Gtk distribution as tomgtk.html. If that is the file you are reading now, note that all links in this file point to the TOM web site.)

In the last few days, I have written TOM bindings for Gtk/Gdk (version 0.99.9 to be precise). Although a lot of the TOM classes that wrap non-GtkObject structs, are not yet finished, all GtkObject classes are functional and complete, if not taking into account the absence of accessor methods to retrieve fields of the GtkObject and descendents structs.

General Setup

All Gtk object methods are available as methods to the equivalent TOM objects, according to a very simple name conversion scheme. For example, the Gtk function

void gtk_box_pack_start (GtkBox    *box,
			 GtkWidget *child,
			 gint       expand,
			 gint       fill,
			 gint       padding);
is available as the following instance method of the Box class:

void
  pack_start (GtkWidget, boolean, boolean, int)
             (child, expand, fill, padding)
The transformation is obvious: the gtk_box prefix is dropped: in TOM, there is no need for every method to be prefixed by gtk, and the box part is superfluous since this method is part of the Box class. All Gtk methods follow this scheme, hence the transformation can be consistently applied.

[Aside: Some of the method names become somewhat cryptic though, mostly because of the Gtk name being cryptic to begin with, like gtk_label_set, which sets the title of the label. In fact, since there are more *_set methods (like that of GtkObject, GtkImage, GtkWidget, GtkAspectFrame, and GtkAlignment), these names must be overspecified, to discern between the set method implemented by gtk_label_set, gtk_widget_set, and gtk_object_set. These have similar names (the last two have identical, but copied, source code) but provide dissimilar functionality. End Aside.]

Replacement of the Gtk methods names, which are inherently cryptic because of their C language implementation, to more natural TOM names is an option that has not yet been implemented, for the following two reasons: it is a nasty job due to the sheer number of methods (`generating' 182k of code in 4 days has been enough for me), and it would alienate those people that are proficient Gtk programmers who are accustomed to the existing names.

Example

The following (very) simple example creates a window and runs the main loop.

int
  main MutableArray arguments
{
  [gtomk init arguments];
  GtkWindow window = [GtkWindow new GTK_WINDOW_TOPLEVEL];
  [window show];
  [gtomk main];
}
A few things are noteworthy:
[gtomk init arguments];
This invocation is mandatory: it initializes Gtk/Gdk. The arguments must be mutable, since gtk_init and gdk_init can modify them, for example by handling and removing the --display :0 argument.

The receiver of this method, gtomk, is the class providing global functionality of the gtomk unit. The unit is called gtomk (pronounced `tomka') instead of gtk, to avoid a clash between the Gtk and TOM/Gtk libraries which would otherwise share the same name. Similarly, the TOM/Gdk unit is called gdomk (pronounced `domka').

GtkWindow window = [GtkWindow new GTK_WINDOW_TOPLEVEL];
Create a new top-level Gtk window. The GTK_WINDOW_TOPLEVEL constant is defined by the gtomk.Constants class.

The TOM class is called GtkWindow. Window would have been enough, since classes can be prefixed with their unit: gdomk.Pixmap and gtomk.Pixmap are different classes. However, with the TOM class names being identical to the Gtk class names, TOM wrapper objects can always be created for a given Gtk object, even when the Gtk object was not created by TOM code.

[window show];
Show the window. All widgets that are created must be sent a show before they are displayed.

[gtomk main];
Run gtk_main.
This program does not do much. It shows a window, and if the window is closed, the program will not exit. To add behavior to the window, functionality must be connected to the signals it emits.

Signals

In Gtk, everything is connected through signals. A button issues a `clicked' signal when it is being clicked, a window emits a `delete_event' signal when it receives a delete event, etc. Gtk provides a complicated interface with lots of glue code and an omnipresent reliance that everything will check and adhere to types at run time. On the other hand, TOM provides a single, clean, type-checked interface, based on objects and selectors (see highlight `The selector type' if you don't know what a selector is).

The GtkObject class provides the following method as the means of connecting to signals.

int (handler_id)
	     connect String signal_name
		  to All receiver
	       using selector sel
	      after: boolean after = TRUE
      includeSender: boolean include_sender = FALSE
  overrideArguments: boolean allow_mismatch = FALSE;
An example of the use of this method is the following call, which binds to the `clicked' event of a button.

[button connect "clicked" to self
        using selector ("v_hello")];
Now, when the button actually emits the `clicked' signal, the hello method of the object passed as self will be invoked. It is as if the button would have executed the following TOM code (with receiver being the second argument to the connect method):

[receiver hello];
Signals have arguments and may expect a return value other than void. The arguments and return type of the TOM method invoked for a given selector must match those indicated by the signal. For example, a GtkWidget's `delete_event' signal passes a pointer to the GdkEvent as an argument and expects a boolean value to be returned, with TRUE meaning that the event should be ignored, and FALSE meaning that the widget can be destroyed. The TOM selector corresponding to this signal thus becomes o_delete_event_p and the corresponding method could be:

<doc> Disallow the window being closed as long as
    the document we display has not been saved.  </doc>
boolean
  delete_event pointer gdk_event
{
  if (![doc isSaved])
    return TRUE;
}
Of course, the name parts of the method and selector are irrelevant: only the types are relevant.

In C, the Gtk signal handling functions are passed the widget responsible for emitting the signal as a first argument. In TOM, they are optional. When the includeSender: argument of the connect method is TRUE, the first argument passed to the signal handling method will be the widget (and the number of arguments that the selector accepts must be 1 more than the number of arguments provided by the signal). For example, two buttons could be connected to the same method in case they are clicked:

  [button1 connect "clicked" to self
           using selector ("v_buttonClicked_r")
           includeSender: YES];
  [button2 connect "clicked" to self
           using selector ("v_buttonClicked_r")
           includeSender: YES];
and the method being invoked could discern them:

void
  buttonClicked Button sender
{
  if (sender == button1)
    [[[stdio err] print "button 1 clicked"] nl];
  else if (sender == button2)
    [[[stdio err] print "button 2 clicked"] nl];
  else
    {
      /* This will not happen (famous last words).  */
      [[[stdio err] print "something unknown clicked"] nl];
    }
}
The sender being passed as an extra first argument must match either a reference or a pointer as the type of the first selector argument. Accepting a pointer is cheaper, since the proxy TOM object does not need to be looked up, nor does it need to be created if it does not yet exist. Obviously, if you want to do something with the object, you should accept a reference, and not a plain pointer.

Another optional argument to the connect method is after:. If after is TRUE (the usual situation), the sending object is allowed to do its job before the signal is relayed. If after is FALSE, the signal is emitted before the object does its thing. It is important that when connecting to the `destroy' signal of an object, after must be FALSE. (This restriction is related to the way that the glue between Gtk and another language is usually implemented, also in TOM. If you do not set after to FALSE, you will not get the signal.)

The last option to the connect method is the overrideArguments: method name part. If this is TRUE, the selector may accept less arguments than the signal provides, and the return type of the selector need not be compatible with the type expected by the signal. The latter is most useful if either of them is void. This option is most useful to connect signals to existing methods that were not designed to be used as the target of a signal. As example of this, the following signal connection ensures that the application quits when the window is closed, by connecting its `delete_event' to the main_quit method of the gtomk class.

[window connect "delete_event" to [gtomk self]
        using selector ("v_main_quit")
        overrideArguments: YES];

Remarks

Some remarks are left to be made concerning the TOM/Gtk bindings:
  • When Gtk passes a GtkObject to a signal, the corresponding TOM type can be a pointer or a class that corresponds to your idea of the object that is passed. Conversion from the pointer passed by Gtk to an instance of the appropriate gtomk proxy class is performed automatically. If you do not need to invoke methods of such a proxy object, or pass it on to other methods, it might be wise to accept a pointer instead of an object, as it saves the time needed for the conversion.

  • Conversion from a Gtk pointer to a TOM object uses the proxy method of the gtomk.GtkObject class:
    
    instance (id) (object)
        proxy pointer gtk_object
      create: boolean create_p = NO
    
    This method only works for instances of GtkObject and subclasses thereof. Other TOM wrapper objects, such as GdkPixmap and GtkStyle, are subclasses of GdkWrapper, which provides its own, incompatible, proxy method. The reason for this distinction is the different nature of the underlying C structs: GtkObject structs carry a lot of run-time information, whereas the other structs are plain C structs, which lack that information.

    Net effect is that non-GtkObject objects must always be accepted as a pointer type. You can convert them into a real TOM object within your signal method. Since this may be considered too expensive for transient objects (like event objects), those classes provide class methods to access the fields of the underlying struct.

    The following two methods are equivalent (taken from the scribble example). The first one shows how to create a wrapper object for non-GtkObject structs. The second one is cheaper since it avoids the creation of a wrapper object.
    
    boolean
      button_press_event (GtkDrawingArea, pointer)
                         (area, event_p)
    {
      EventButton event = [EventButton proxy event_p
                                       create: YES];
    
      if ([event button] == 1 && pixmap != nil)
        [self draw_brush_at [event position] in area];
    
      = TRUE;
    }
    
    The following implementation is cheaper and faster because it avoids the creation of a wrapper object. Because of the event type checking in the event class methods (with precondition checking enable), it is almost equally type safe:
    
    boolean
      button_press_event (GtkDrawingArea, pointer)
                         (area, event_p)
    {
      if ([EventButton button event_p] == 1 && pixmap != nil)
        [self draw_brush_at [EventButton position event_p]
              in area];
    
      = TRUE;
    }
    

  • Given a gdomk or gtomk object, you can retrieve a pointer to the underlying Gdk or Gtk object: for a class GtkFoo, the method to use is gtkFoo, which returns a pointer. Thus, if you have a GtkButton button, then the following calls all return the same pointer:
    
    [button gtkButton];
    [button gtkContainer];
    [button gtkWidget];
    [button gtkObject];
    
    since a GtkButton is a GtkContainer is a GtkWidget is a GtkObject. It may be tempting to always use gtkObject to retrieve the pointer, but using the most specific method (in this case gtkButton) has the advantage that an error will be signaled when the object actually is something different from what you think. (Note that such signaling provides a description of the problem; it does not just dump core.)
That's it for now. Enjoy. --Tiggr


Up: Highlights
 
Copyright © 1997-2002 Programmers Without Deadlines