(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):
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
|