fpc/docs/gtk1.tex
2001-07-10 21:52:18 +00:00

592 lines
27 KiB
TeX

\documentclass[10pt]{article}
\usepackage{a4}
\usepackage{epsfig}
\usepackage{listings}
\lstset{language=Delphi}%
\lstset{basicstyle=\sffamily\small}%
\lstset{commentstyle=\itshape}%
\lstset{keywordstyle=\bfseries}%
\lstset{blankstring=true}%
\newif\ifpdf
\ifx\pdfoutput\undefined
\pdffalse
\else
\pdfoutput=1
\pdftrue
\fi
\begin{document}
\title{Programming GTK in Free Pascal}
\author{Florian Kl\"ampfl\\and\\Micha\"el Van Canneyt}
\date{July 2000}
\maketitle
\section{Introduction}
The GTK library is a popular widget library for the X-Windows system.
It is used as the basis for the GIMP graphical manipulation program
and for the GNOME application framework. With its ports to Microsoft
Windows and BeOS, it allows to program a graphical interface for any
application in a platform independent way.
GTK is implemented in C, but it is possible to access its functionality from
Free Pascal. For this, its headers have been translated to Pascal,
so a program written in Free Pascal can make use of the functionality
offered by GTK and its companion libraries GDK and GLIB. In fact, there is
an open source project (Lazarus) which makes use of GTK in order to build
an open-source alternative to the visual classes offered by Inprise's
Delphi.
This article intends to present an introduction to programming GTK in
Free Pascal. It by no means covers all of the functionality that GTK
offers, as this would probably require a complete manual.
The first section gives some general considerations on the GTK toolkit.
\section{GTK is a C library}
Since GTK is an external library, some import units describing the calls in
the libraries are needed. Three libraries make up the GTK programming kit:
\begin{description}
\item[glib] this library contains some general programming tools, and
defines platform independent types, which are used throughout the other
libraries. To use this library, it is sufficient to include the
\lstinline|glib| unit in your \lstinline|uses| clause.
\item[gdk] encapsulates the Windowing system (X or Windows) underlying GTK.
It contains routines to draw on the screen, and react to various mouse or
keyboard events. To use these
routines, the \lstinline|gdk| unit must be included in the \lstinline|uses|
clause of a unit or program.
\item[gtk] contains the widget library. This is a series of controls such
as edit boxes, drop-down lists and many more, which are organised in an OOP
structure. Since the library is written in C, there is no programming
language support for this structure.
All definitions of the gtk library are contained in the gtk unit, which
must be included in the \lstinline|uses| clause of any program or unit that needs their
functionality.
\end{description}
The GTK toolkit was programmed in C. This has some consequences for the
Pascal interface, since some C constructs do not port easily to Pascal.
When using the Pascal translation of the C headers, the following must be
kept in mind:
\begin{enumerate}
\item Reserved words: Pascal reserved words in types, record element names
etc. have been prepended with the word 'the'. For example \lstinline|label|
becomes \lstinline|thelabel|.
\item Functions and procedures have been kept with the same names.
\item Types have been prepended with T, that is, the C type
\lstinline|GtkWidget| has become \lstinline|TGtkWidget|.
\item Pointers to types have been defined as the type name, prepended with a
P. \lstinline|GtkWidget *| becomes \lstinline|PGtkWidget|.
\item Records with bit-size elements: C allows to store parts of a record in
individual bits; whereas in Pascal, the minimum size of an element in a
record is a byte. To accommodate this, functions were defined to retrieve
or set single bits from a record. The functions to retrieve a bit
have the name of the record field. The procedure to set a bit has
the name of the field prepended with 'set\_'.
For example
\begin{lstlisting}[language=c]{cpackedstruct}
struct SomeStruct
{
gchar *title;
guint visible : 1;
guint resizeable : 1;
};
\end{lstlisting}
translates to
\begin{lstlisting}{ppackedstruct}
TSomeStruct = record
title : Pgchar;
flag0 : word;
end;
function visible(var a: TGtkCListColumn): guint;
procedure set_visible(var a: TGtkCListColumn; __visible: guint);
function resizeable(var a: TGtkCListColumn): guint;
procedure set_resizeable(var a: TGtkCListColumn;__resizeable: guint);
\end{lstlisting}
\item Macros. Many C macros have not been translated. The typecasting
macros have been dropped, since they're useless under Pascal.
Macros to access record members have been translated, but they are to be
considered as {read-only}. So they can be used to retrieve a value, but
not to store one. e.g.
\begin{lstlisting}{macro}
function GTK_WIDGET_FLAGS(wid : pgtkwidget) : longint;
\end{lstlisting}
can be used to retrieve the widget flags, but not to set them. so things like
\begin{lstlisting}{invaliduseofmacro}
GTK_WIDGET_FLAGS(wid):=GTK_WIDGET_FLAGS(wid) and someflag;
\end{lstlisting}
will not work, since this is a function, and NOT a macro as in C.
\item Calling conventions: A C compiler uses another calling convention
than the Free Pascal compiler. Since many GTK functions need a callback,
these callback must use the C calling convention. This means that every
function that is called by GTK code, should have the \lstinline|cdecl|
modifier as a part of its declaration.
\end{enumerate}
Compiling a GTK application is no different than compiling any other Free
Pascal application. The only thing that needs to be done is to tell the free
Pascal compiler where the gtk, gdk and glib libraries are located on your
system. This can be done with the \verb|-Fl| command-line switch. For
example, supposing the gtk library is located in \verb|/usr/X11/lib|, the
following command-line could be used to compile your application:
\begin{verbatim}
ppc386 -Fl/usr/X11/lib mygtkapp.pp
\end{verbatim}
This example supposes that the gtk unit is be in your unit search path. If
it is not, you can add it with the \verb|-Fu| switch.
\section{The bricks of a GTK application}
The building-blocks of a a GTK application are the {\em widgets}.
Widgets are the equivalent of Delphi's controls. And although GTK
is not an object oriented library, the library defines a record
\lstinline|TGtkWidget| which contains all settings common to all
widgets; all widgets start with this record, and add their own
specific data to it. This creates a tree-like structure with all
the widgets present in the GTK library, to which your own widgets
can be added.
All functions that create a particular widget return a pointer
to a \lstinline|TGtkWidget| record. It is not recommended to
manipulate the contents of the widget record directly; GTK offers
many functions to manipulate the members of the record, e.g.
\lstinline|gtk_widget_set_parent| or \lstinline|gtk_widget_get_name|.
To this set of functions, each new widget adds a few functions that are
specific to this particular widget.
Each widget has a specific function and a specific look; there are many
widgets to choose from. A complete list of widgets is outside the scope of
this article; the GTK reference manual offers an overview of available
widgets. In general it can be said that most widgets one would expect
are present in the GTK library: Edit fields, buttons, check-boxes, various
lists, menus, combo-boxes, tree views, and some pre-defined dialogs.
Any of these widgets is created with a \lstinline|gtk_WIDGET NAME_new| call.
This call can accept arguments; The number and type of arguments depend
on the widget.
For example, to create a button that displays a text, the call is defined
as follows:
\begin{lstlisting}{}
gtk_button_new_with_label(ALAbel : PChar)
\end{lstlisting}
All widgets can be destroyed with the \lstinline|gtk_widget_destroy| call,
irrespective of their type.
\section{Showing things on the screen}
To show things on the screen, it is necessary to create a window. A window
is created with the the \lstinline|gtk_window_new| call. This call accepts
as an argument the type of window to be created.
Creating a window creates it's structure in memory, but doesn't show it on
screen. To show this window on the screen,a call to the
\lstinline|gtk_widget_show| function is needed, as can been seen in
example 1.
\lstinputlisting{gtkex/ex1.pp}
If the window contains widgets, the \lstinline|gtk_widget_show| function
must be called for each widget.
Looking at example 1, one notices 2 special calls: \lstinline|gtk_init| and
\lstinline|gtk_main|. These calls should be present in any program that uses
the GTK library.
The first call initialises the GTK library. Among other things, it reads
the command-line to see e.g. which display should be used.
The second call is the heart of the GTK widget library: It starts the
message loop of GTK. This call will not return, unless somewhere else
in the program \lstinline|gtk_main_quit| is called. As long as the call
doesn't return, GTK will wait for events such as mouse clicks, key-presses
and so on. It will handle these events, but it will not notify you of any
of these events except if you specifically ask for it.
A window by itself is of course not very interesting. To make it more
interesting, some elements should be added.
Adding a widget to a parent is done with the \lstinline|gtk_container_add|
call. This call places a widget in a container. A container is a widget
which can contain other widgets; not all widgets are containers, however.
Example 2 shows how to add a widget (a button) to a container (the window
in this case). It also shows that the container has some specific
properties, which can be manipulated as well (in this case, the border
width). Since not each widget is a container, the window pointer must be
typecasted to \lstinline|GTK_CONTAINER| in order to be accepted by the
container handling calls.
\lstinputlisting{gtkex/ex2.pp}
Adding more than 1 widget to a container is not trivial in GTK. The reason
for this is that GTK has not been designed to set widgets at a specific
location in their parent widget. Instead, GTK asks that you 'pack' your
objects in their parent widget. This means that if the parent widget is
resized, it's child widgets are resized as well, depending on the packing
options that were set.
One of the reasons that the GTK library was set up this way, is that the
size of a widget is not well-defined. For instance, the size of a button
depends on whether it is the default widget of the window or not. Given
that this is so, the placement of such a button is not well-defined either.
The most common ways of packing widgets in a parent widget are the
following:
\begin{enumerate}
\item using a vertical box.
\item using a horizontal box.
\item using a table.
\end{enumerate}
We'll discuss these ways in the subsequent. There are other ways, but these
are probably the most important ones.
\subsection{Using boxes}
A horizontal or vertical box can be used to contain a row or column of
widgets. Various options can be set to modify the spacing between the
widgets, the alignment of the widgets in the box, or the behaviour of
the box when the user resizes the parent widget. Boxes work only in
one direction. The widgets inside a horizontal box always have the height of
the box, and widgets in a vertical box always have the width of the vertical
box.
You can create a horizontal box with the \lstinline|gtk_hbox_new| call.
It accepts 2 arguments: The first one is a boolean. It tells GTK whether the
children should have the same space in the box. The second one is an
integer, which tells GTK how much space to leave between the widgets in the
box. Likewise, a vertical box can be created with the
\lstinline|gtk_vbox_new| call. This call accepts the same arguments as the
first box.
Adding widgets to a box happens with the \lstinline|gtk_box_pack_start| or
\lstinline|gtk_box_pack_end| calls. The former adds a widget at the start
of the box, the latter adds a widget at the end of the box. Both functions
accept the same arguments:
\begin{lstlisting}{boxarguments}
(Box : PGtkBox; Widget: PGtkWidget;
expand gboolean; fill : gboolean;padding : guint);
\end{lstlisting}
The \lstinline|expand| argument tells the box whether it should take the
size of it's parent widget, or whether it should resize itself so that it is
just large enough to fit the widgets. The latter allows to justify the
widgets in the box (but only if the box is {\em not} homogeneous.
If the box should keep the size of it's parent, then the \lstinline|fill|
argument decides what is done with the extra space available.
If \lstinline|fill| is \lstinline|True| then the extra space is
divided over the widgets. If \lstinline|fill| is \lstinline|False| then the
extra space is put in between the widgets.
The \lstinline|padding| adding tells the box to add extra space for this
particular widget.
The following program shows the use of a box:
\lstinputlisting{gtkex/ex3.pp}
What the program does is the following: It creates a window, which it splits
up in two halves by means of the \lstinline|totalbox| widget. This is a
vertical box, which will contain two other boxes: a vertical box and a
horizontal box. Each of these two boxes is filled with buttons.
The behaviour of the boxes can be seen when the window is resized.
The effect of the various arguments to the pack calls can be seen by
changing the arguments and recompiling the example.
\subsection{Using tables}
A table is used to set widgets in a grid inside the parent widget. It acts
like a grid with cells, in which you can 'hang' your widgets. If the user
resizes the parent widget, then the size of the grid cells changes, and
the widgets will change their location and size, based on the size of the
new grid cells.
To create a table to manage a window's layout, the \lstinline|gtk_table_new|
call is used. It accepts 3 arguments: the number of rows, the number of
columns and a boolean which tells GTK whether the cells should always have
the same size or not.
To add a widget to a table, the \lstinline|gtk_table_attach| call is used.
It is declared as
\begin{lstlisting}{}
gtk_table_attach(Table: PGtkTable;Widget: PGtkWidget;
Left, right, top, bottom : guint;
Xoptions,Yoptions : TGtkAttachOptions;
Xpadding,Ypadding : Guint);
\end{lstlisting}
The first two options of this call are self-explanatory. The next four
options, however, need some explanation. Contrary to what the name 'table'
suggests, these do {\em not} specify the coordinates of cells; instead, they
specify the grid lines that delimit the cells.
\begin{figure}
\caption{Placing widgets in a table.\label{fig:table}}
\begin{center}
\ifpdf
\epsfig{file=table.pdf}
\else
\epsfig{file=table.eps}
\fi
\end{center}
\end{figure}
Figure \ref{fig:table} represents a table with 5 rows and 5 columns, with
cells of the same size. The call to create this table could be:
\begin{lstlisting}{}
maintable:=gtk_table_new(5,5,TRUE);
\end{lstlisting}
To hang a widget in this table, so it starts in cell (2,1) and ends in cell
(3,2), where the cells are counted starting with 0, it is necessary to tell
GTK that the widget starts at horizontal grid line 2, and ends at horizontal
grid line 4. Vertically, it starts at grid line 1, and ends at grid line 3.
This means that the following call would place the widget at its correct
location:
\begin{lstlisting}{}
gtk_table_attach(maintable,mybutton,
2,4,1,3,
GTK_EXPAND OR GTK_FILL,GTK_EXPAND OR GTK_FILL,
0,0);
\end{lstlisting}
GTK delivers a shorter form of this call:
\begin{lstlisting}{}
gtk_table_attach_defaults(maintable,mybutton,2,4,1,3);
\end{lstlisting}
The parameter \lstinline|GTK_EXPAND or GTK_FILL| tells GTK that the widget
should always take up the full space assigned to it.
The following example program illustrates the use of a table in a gtk
application:
\lstinputlisting{gtkex/ex4.pp}
The example creates a table with 6 rows and 6 columns. It places 3 buttons,
each at a different location in the table, with different sizes. The first
button has a width and height of 1 cell and is located at cell (1,1). The
second has a width and height of two cells, and is located at cell (3,1).
The last button is 4 cells wide and has a height of 1 cell, and is located
at cell (1,4). When the window is resized, the cells are resized as well,
and the buttons follow the size of the cells.
{\em Remark:} because the table has homogeneous cells, the minimum width
and height of the cells is determined by the first button (in this case).
Since all cells must have the same size, this means that the minimum size
of the window is 6 times the size of the first button (plus a border).
\section{Reacting to user input}
So far, the example programs did not react to button clicks or any other user
action other than closing the window. To make a window respond to user
actions, it is necessary to install signal callbacks or event handlers.
The difference between signals and events is that signals come from the GTK
toolkit. Events come from the underlying window system (X or Windows).
For example, 'button\_pressed' is an event that is generated by the window
system when the user clicks with his mouse. It is possible to react to
this event.
On the other hand, a button widget defines a signal 'clicked'.
The 'clicked' event will occur when the user clicks on the button.
So, many signals that are defined by GTK widgets are just a translation
of events to something specific for that widget.
Since calls to connect to a signal or to an event are the same, in what
follows the discussion will be restricted to signals, but all that is
said is also true for events.
GTK has essentially 2 ways to install signal callbacks. The only difference
between these calls is the arguments that the callback function accepts.
The first way to install a callback is using the
\lstinline|gtk_signal_connect| function. This function is declared as
follows:
\begin{lstlisting}{}
TGtkSignalFunc = procedure ;cdecl;
Function gtk_signal_connect(TheObject:PGtkObject;Name:Pgchar;
Func:TGtkSignalFunc;Data:gpointer):guint;cdecl;
\end{lstlisting}
The first argument of this call (\lstinline|TheObject|) is the object
(usually a widget) to which you want to assign an event handler. The second
parameter, \lstinline|Name|, is the event you wish to catch with this
callback (an example could be 'clicked' for a button). The third argument
(\lstinline|Func|) is the function that should be called when the event occurs.
The \lstinline|Data| argument is a pointer to arbitrary data. This pointer
will be passed on to the callback function \lstinline|func| when the event
occurs.
The \lstinline|gtk_signal_connect| function returns an integer. This integer
is the number of the callback for this event. It is possible to attach more
than one callback to an event in GTK. When the event occurs, the callbacks
will be executed in the order that they have been attached to the widget.
The declaration of the \lstinline|TGtkSignalFunc| type requires that every
callback function that is passed to GTK must be typecast. Since GTK defines
only one function to set a signal handler, this is necessary, since
callbacks can have different forms. This mechanism is error-prone, since
in this manner it is possible to pass a function to GTK which has the wrong
number of arguments.
However, most callbacks must be of the form:
\begin{lstlisting}{}
Function (Widget : PGtkWidget; Data : GPointer) : guint;cdecl;
\end{lstlisting}
Such a callback function accepts 2 arguments: the first argument
(\lstinline|Widget|) is the widget which caused the event
(for example, the button which was clicked). The second argument is a
pointer to arbitrary data. This is the pointer that was passed as
\lstinline|Data| when the callback was installed.
Signals are identified by their name. The GTK reference guide contains a
complete list of supported signals.
The first example shows how a handler for the 'destroy' signal of the
window is installed. When the window-manager kills the window, this
signal is sent. The \lstinline|gtk_main_quit| instructs GTK that it
should stop processing X events and exit the \lstinline|gtk_main| call.
A second method to connect a callback to a signal is using the
\lstinline|gtk_signal_connect_object| call. This call is defined as
follows:
\begin{lstlisting}{}
Function gtk_signal_connect_object(theobject:PGtkObject;
name:Pgchar;
func:TGtkSignalFunc;
slot_object:PGtkObject):guint;cdecl
\end{lstlisting}
It is similar in function to the \lstinline|gtk_signal_connect| function,
only it doesn't allow to pass arbitrary data to the signal handler. Instead,
the handler must {\em always} be of the following form:
\begin{lstlisting}{}
Function (AnObject : PGtkObject);
\end{lstlisting}
The \lstinline|slot_object| pointer that was provided in the call to
\lstinline|gtk_signal_connect_object| will be passed as the
\lstinline|AnObject| argument to this function. Many GTK functions have the
above form; this makes it possible to attach a GTK internal function to a
signal.
To illustrate this, the second example is modified so that clicking the button
will also close the window:
\lstinputlisting{gtkex/ex5.pp}
In the example, the call to \lstinline|gtk_signal_connect_object| will
connect the 'clicked' signal of the button to the
\lstinline|gtk_widget_destroy| function of GTK, and passes the pointer to
the window widget to it. When the user clicks the button, the
\lstinline|gtk_widget_destroy| function will be called with as argument the
pointer of the window object. As a result, the window widget will be
destroyed, it's 'destroy' signal will be activated, and the
\lstinline|gtk_main_quit| function will be called through the program's
'destroy' handler for the window.
Since the signal handler connect call returns an integer by which it can
be identified, it is possible to manipulate or even remove the handler
once it has been installed.
For instance it is possible to temporarily disable a signal handler with the
\lstinline|gtk_signal_handler_block| call, and to enable it again with the
\lstinline|gtk_signal_handler_unblock| call. An example of how to do this
can be found in the following example:
\lstinputlisting{gtkex/ex6.pp}
There are other things that one can do with signal handlers, but a complete
discussion of all possibilities is outside the scope of this article.
Some widgets do not have their own window; i.e. they do not receive events
from the underlying windowing system. An example of such a widget is a
label. A label just draws it's text on it's parent widget, and nothing else.
To be able to respond to certain events, an event-box must be used, and the
window-less widget must be placed in it. An event-box can be created with
the \lstinline|gtk_event_box_new| call. This call accepts no arguments.
To this event-box, a window-less widget can be added. The event-bow will
then capture events for the window-less widget. The following example shows
how to use an event-box to detect when the mouse pointer is moved over a
label:
\lstinputlisting{gtkex/ex7.pp}
If the mouse pointer is moved over the first label, the text of the second
label is adapted accordingly. The example also shows the use of
\lstinline|gtk_widget_show_all|, which shows a widget and all widgets
contained in it.
\section{A touch of style}
The look of a GTK application is controlled through the use of styles. A
style controls the colors in which a widget is drawn, in various states.
As an example: a widget may be drawn differently depending on whether it
has focus or not. How to draw the widget in each of this states is described
in the style of the widget.
GTK recognizes the following states of a widget:
\begin{description}
\item[NORMAL] The normal state of a widget. No mouse over it.
\item[PRELIGHT] Is the state of a widget when the mouse is over it.
\item[ACTIVE] Is the state of a widget when it is pressed or
clicked.
\item[INSENSITIVE] if the widgets is disabled ('grayed').
\item[SELECTED] When the object is selected.
The GTK unit has a constant for each of these states; it is the above name
with \lstinline|GTK_STATE_| prefixed, so e.g. \lstinline|GTK_STATE_NORMAL|
for the normal state.
\end{description}
Each widget class has a default style in which it is drawn. If you wish to
change the way all these widgets look, you should change the default style
of this class. If you want to change the way one particular widget looks,
you should make a new style, and apply it to that particular widget. It is
possible to make a copy of an existing style and modify the copy before
applying it.
It is also possible to change the default style of widgets. Changing the
default style of widgets will have effect on all widgets created after the
new style was set. Widgets created before that will be unaffected.
The following example shows how to set the color of a label. It takes a
copy of the standard label style, and modifies it so the foreground color
becomes red. It then applies the modified style to the first label.
The second label is unaffected by this change
\lstinputlisting{gtkex/ex8.pp}
The last example shows how to change the color of a button when the mouse
moves over it.
\lstinputlisting{gtkex/ex9.pp}
\section{Carrying on}
In the previous sections, some basic concepts of GTK have been introduced.
However, GTK is a big toolkit and much more can be said about it. It is
outside of the scope of the current article to describe all Widgets in the
GTK library. The range of offered widgets is broad, and there probably is a
widget for each conceivable task. If there is a missing widget, it is always
possible to write a new widget.
In principle, therefore, GTK is suitable to write large applications, also
when writing in Free Pascal. However, the fact that it is written in C and
it's interface is C oriented, justifies the writing of a small Pascal Object
Oriented wrapper around it.
The following arguments show the need for such a wrapper:
\begin{enumerate}
\item C has no object-oriented language constructs. This makes it necessary
to do a lot of typecasts in GTK calls. Using Classes or Objects, this is no
longer necessary, and will improve code readability substantially.
\item C uses null-terminated strings. It can be useful to wrap calls that
need a null-terminated string into one that accepts a normal string as an
argument. Using ansistrings will make the conversion to null-terminated
strings easier.
\item The signal mechanism of GTK destroys the strong type checking of
Pascal. When compiling the statement
\begin{lstlisting}{}
Button.OnClick:=@MyForm.OnButtonClick
\end{lstlisting}
The compiler checks that the \lstinline|OnButtonClick| method can be
assigned to the \lstinline|OnClick| event. Under GTK, it is possible
to pass any function as a handler for a signal, as in
\begin{lstlisting}{}
gtk_signal_connect (PGTKOBJECT (window), 'destroy',
GTK_SIGNAL_FUNC (@destroy), NULL);
\end{lstlisting}
This can lead to errors if the \lstinline|destroy| procedure accepts a
different number of arguments, or other arguments than the ones that
GTK provides. Therefore it would be a good idea to implement methods that
would force type checking, e.g:
\begin{lstlisting}{}
Button.AddOnClick(@MyForm.OnButtonClick);
\end{lstlisting}
Such a statement would only compile if the \lstinline|OnButtonClick| would
be of the right type.
\end{enumerate}
Additional benefits of making such a wrapper would be simpler code, and
hence better readability. Both improve the maintainability of the code as well,
which are all important factors when writing a large application.
\end{document}