~ubuntu-branches/debian/lenny/fpc/lenny

« back to all changes in this revision

Viewing changes to docs/gtk2.tex

  • Committer: Bazaar Package Importer
  • Author(s): Mazen Neifer, Torsten Werner, Mazen Neifer
  • Date: 2008-05-17 17:12:11 UTC
  • mfrom: (3.1.9 intrepid)
  • Revision ID: james.westby@ubuntu.com-20080517171211-9qi33xhd9evfa0kg
Tags: 2.2.0-dfsg1-9
[ Torsten Werner ]
* Add Mazen Neifer to Uploaders field.

[ Mazen Neifer ]
* Moved FPC sources into a version dependent directory from /usr/share/fpcsrc
  to /usr/share/fpcsrc/${FPCVERSION}. This allow installing more than on FPC
  release.
* Fixed far call issue in compiler preventing building huge binearies.
  (closes: #477743)
* Updated building dependencies, recomennded and suggested packages.
* Moved fppkg to fp-utils as it is just a helper tool and is not required by
  compiler.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
\documentclass[10pt]{article}
2
 
\usepackage{a4}
3
 
\usepackage{epsfig}
4
 
\usepackage{listings}
5
 
\lstset{language=Delphi}%
6
 
\lstset{basicstyle=\sffamily\small}%
7
 
\lstset{commentstyle=\itshape}%
8
 
\lstset{keywordstyle=\bfseries}%
9
 
%\lstset{blankstring=true}%
10
 
\newif\ifpdf
11
 
\ifx\pdfoutput\undefined
12
 
  \pdffalse
13
 
\else
14
 
  \pdfoutput=1
15
 
  \pdftrue
16
 
\fi
17
 
\begin{document}
18
 
\title{Programming GTK in Free Pascal}
19
 
\author{Florian Kl\"ampfl\\and\\Micha\"el Van Canneyt}
20
 
\date{September 2000}
21
 
\maketitle
22
 
\section{Introduction}
23
 
In this second article on programming the GTK toolkit, a more advanced use
24
 
of the GTK library is presented. Techniques to create a new GTK widget
25
 
are discussed by creating two custom widgets. 
26
 
 
27
 
The first widget is realized by combining existing GTK widgets to create 
28
 
a new widget, a GTKFileEdit component, modeled after the TFileEdit component
29
 
found in the RXLib library for Delphi.
30
 
 
31
 
When constructing the second widget, the focus will be on how a widget
32
 
should draw itself in GTK.
33
 
 
34
 
\section{Preliminaries}
35
 
Whatever the method used when creating new GTK widgets, it is necessary to 
36
 
split the functionality of the widget in 2 parts. 
37
 
The first part is the functionality that is common to all instances of the 
38
 
new widget. This part is by far the most important one, and is implemented 
39
 
in the 'class record'. This record will be initialized with a class 
40
 
initialization function. It will also contain pointers to callbacks to
41
 
draw a particular instance or callbacks to react on user events.
42
 
 
43
 
The second part concerns the particular instance of the widget that is 
44
 
created, it contains the data that determines the state of an instance
45
 
after it is created, it is the actual object created by the user. This 
46
 
part of the widget is implemented in the 'Object record'. For this record
47
 
also there is a initalization function.
48
 
 
49
 
When the two records have been defined, some standard methods must be 
50
 
implemented in order to integrate the new widget in the GTK library. 
51
 
Implementing some methods for the user to manipulate the properties 
52
 
of the new widget finishes the creation of a new widget.
53
 
 
54
 
Since GTK is implemented in C, the programmer must obey some rules in order
55
 
to preserve the object-oriented aspect of the GTK library. More precisely,
56
 
when defining the class and object records, care must be taken to specify 
57
 
the parent object or class as the first element in the newly created structure. This
58
 
will allow typecasting of the widget to its parent objects.
59
 
 
60
 
Taking a look at the \lstinline|TGtkContainer| widget, we see that the declaration
61
 
of the object record starts with the declaration of its parent widget 
62
 
\lstinline|TGtkWidget|:
63
 
\begin{lstlisting}{}
64
 
TGtkContainer = record
65
 
  widget : TGtkWidget;
66
 
  focus_child : PGtkWidget;
67
 
  flag0 : longint;
68
 
  resize_widgets : PGSList;
69
 
end;
70
 
\end{lstlisting}
71
 
The same is true for the \lstinline|TGtkContainerClass| record:
72
 
\begin{lstlisting}{}
73
 
TGtkContainerClass = record
74
 
  parent_class : TGtkWidgetClass;  
75
 
  n_child_args : guint;
76
 
  // ...
77
 
end;
78
 
\end{lstlisting}
79
 
For both the components that will be made, such records will be made.
80
 
 
81
 
\section{A filename edit component}
82
 
The \lstinline|TGTKFileEdit| component presented here is composed out of three 
83
 
other components; first of all a single line edit control, in which the
84
 
user can type a filename if he wishes. The second is a button. The button
85
 
is always placed on the right edge of the edit control, and has the same
86
 
height. The third component is an image component, which is used to display
87
 
an image on the button\footnote{In GTK a button does not necessarily contains a
88
 
caption, it is an empty placeholder, which can be filled with whatever 
89
 
you want, in this case an image. To have the button display a caption, 
90
 
a label is placed in it.}
91
 
 
92
 
Since the edit and button component must be kept together, we use a
93
 
\lstinline|TGtkHBox| as the 'Parent' component, and this component will be
94
 
used to keep the edit and button control. There is no need to consider the
95
 
image component, since it will be placed inside the button.
96
 
 
97
 
Having decided that, the structure of the record for the instance of the
98
 
component is more or less determined:
99
 
\begin{lstlisting}{}
100
 
Type
101
 
  PGtkFileEdit = ^TGtkFileEdit;
102
 
  TGtkFileEdit = Record
103
 
    Box : TGtkHBox;
104
 
    Edit : PGtkEntry;
105
 
    Button : PGtkButton;
106
 
    Image : PGtkPixmap;
107
 
    Dialog : PGtkFileSelection;
108
 
  end;
109
 
\end{lstlisting}
110
 
The first field of the record contains the parent record, as required
111
 
by the OOP structure of GTK. The other fields are used to contain references
112
 
to the other controls used. The \lstinline|Dialog| field will be filled with the
113
 
reference to the file selection dialog which is created when the user clicks
114
 
the button, at all other times it will contain a \lstinline|nil| pointer.
115
 
Remark that the first field is a record, and all other fields are pointers.
116
 
 
117
 
Since the fields of the record are 'Public' the user can access the button
118
 
and edit components, and set or read their properties, and set additional 
119
 
signals. (e.g. a 'change' signal for the edit component)
120
 
 
121
 
The class record for the {TGTKFileEdit} component should contain as a first 
122
 
field the parent class record, in this case \lstinline|TgtkHBoxClass|. Furthermore
123
 
in the class record the default bitmap that will be displayed on the button
124
 
will be stored. For this two fields are needed; one to keep the bitmap
125
 
(\lstinline|DefaultPixmap|, and
126
 
another one to keep a bitmask that is used to determine the transparant
127
 
pixels in the bitmap (\lstinline|DefaultBitMap|):
128
 
\begin{lstlisting}{}
129
 
  PGtkFileEditClass = ^TGtkFileEditClass;
130
 
  TGtkFileEditClass = Record
131
 
    Parent_Class : TgtkHBoxClass;
132
 
    DefaultPixmap : PGdkPixmap;
133
 
    DefaultBitMap : PGdkBitmap;
134
 
  end;
135
 
\end{lstlisting}
136
 
As usual, a pointer type is defined which points to the record. The fields
137
 
of the class record will be filled in by the initialization code for our
138
 
component, as will be shown below.
139
 
 
140
 
A new widget must be registered with GTK by calling the
141
 
\lstinline|gtk_type_unique| function. This function returns a unique
142
 
identifier that can be used to refer to your new widget. This value
143
 
must be accessible when creating new instances.
144
 
 
145
 
Usually, this is done by registering the component with the GTK library
146
 
inside a function which returns this unique ID to the user:
147
 
The \lstinline|GtkFileEdit_get_type| function. 
148
 
When this function is called for the first time, it will register
149
 
the new class with GTK, which will in turn supply a unique ID for the
150
 
new component. This ID is returned and also stored, and will be returned
151
 
the next times when the \lstinline|GTKFileEdit_get_type| function is called.
152
 
 
153
 
The \lstinline|GTKFileEdit_get_type| function looks like this
154
 
\lstinline|gtk\_type\_unique|:
155
 
\begin{lstlisting}{}
156
 
Function GtkFileEdit_get_type : Guint;cdecl;
157
 
 
158
 
Const
159
 
  GtkFileEditInfo : TGtkTypeInfo =
160
 
    (type_name : 'GtkFileEdit';   
161
 
     object_size : SizeOf(TGtkFileEdit);
162
 
     class_size : SizeOf(TGtkFileEditClass);
163
 
     class_init_func : TGtkClassInitFunc(@GtkFileEditClassInit);
164
 
     object_init_func : TGtkObjectInitFunc(@GtkFileEditInit);   
165
 
     reserved_1 : Nil;
166
 
     reserved_2 : Nil;
167
 
     base_class_init_func : Nil
168
 
    );
169
 
 
170
 
begin
171
 
  if (GtkFileEditType=0) then
172
 
    GtkFileEditType:=gtk_type_unique(gtk_hbox_get_type,@GtkFileEditInfo);
173
 
  Result:=GtkFileEditType;  
174
 
end;
175
 
\end{lstlisting}
176
 
Registering the new widget is done by passing a \lstinline|TGtkTypeInfo| 
177
 
record to \lstinline|gtk_type_unique|, where the fields of this record 
178
 
are filled with the following information:
179
 
\begin{description}
180
 
\item[type\_name] Contains the name of the type that must be registered. 
181
 
\item[object\_size] The size of the object record. GTK itself will allocate 
182
 
the memory when an new instance of the object is created, so it must know the
183
 
size of the object.  
184
 
\item[class\_size] The size of the class object. Only one instance of this
185
 
record will be created (by GTK)
186
 
\item[class\_init\_func] The address of a function that will initialize the
187
 
class record. This function accepts as a single arument a pointer to the
188
 
class record to be initialized. This function will normally be called only
189
 
once.
190
 
\item[object\_init\_func] The address of a function that will initialize 
191
 
an instance of the object. The function must accept as a single argument
192
 
a pointer to an instance of the object. This instance will be created by 
193
 
GTK. This function is called for each instance of the object.
194
 
\end{description}
195
 
The other three fields of the record are unfortunately not documented, so
196
 
they are left blank. 
197
 
 
198
 
Along with the \lstinline|TGtkTypeInfo| record, the tyoe the type of the 
199
 
parent class (acquired with its own \lstinline|gtk_hbox_get_type| 
200
 
function) is passed to the \lstinline|gtk_type_unique| function. 
201
 
 
202
 
If a \lstinline|class_init_func| was specified when registering the new type,
203
 
then GTK will call this method; it should initialize any class-specific 
204
 
data in the class record. In the case of the \lstinline|GTKFileEdit|, the bitmap 
205
 
which is used to fill the button is loaded:
206
 
\begin{lstlisting}{}
207
 
Procedure GtkFileEditClassInit (CObj : PGtkFileEditClass);cdecl;
208
 
   
209
 
begin
210
 
  With Cobj^ do
211
 
    DefaultPixMap:=gdk_pixmap_create_from_xpm(Nil,@DefaultBitmap,
212
 
                                              Nil,'fileopen.xpm');
213
 
end;
214
 
\end{lstlisting}
215
 
The \lstinline|gdk_pixmap_create_from_xpm| does 2 things: It loads a bitmap
216
 
from the \textsf{fileopen.xpm} file and returns a PGdkPixmap pointer.
217
 
At the same time it returns a pointer to a bitmask which designates the
218
 
transparant regions of the bitmap.
219
 
 
220
 
The result of this function is stored in the class record, so the bitmap
221
 
is available when a new instance of the class is created.
222
 
 
223
 
The \lstinline|GtkFileEditClassInit| and \lstinline|GtkFileEdit_get_type| 
224
 
functions are not called automatically by GTK. There are basically
225
 
2 solutions to do this as described below.
226
 
 
227
 
The first one is specific to Free Pascal: the \lstinline|GtkFileEdit_get_type|
228
 
can be called from the unit initialization code; This means that the objects 
229
 
are registered with GTK, even if they're not used. It also means that the
230
 
GTK library must be initialized first, and hence should also be initialized
231
 
in the initialization code of some unit.
232
 
 
233
 
The second method is the method used in C: The function to create a new
234
 
instance of the \lstinline|TGTKFileEdit| class, \lstinline|GTKFileEdit_new|,
235
 
calls the \lstinline|get_type| function to register the class if needed,
236
 
as follows:
237
 
\begin{lstlisting}{}
238
 
Function GtkFileEdit_new : PGtkWidget;cdecl;
239
 
 
240
 
begin
241
 
  Result:=gtk_type_new(GtkFIleEdit_get_type)
242
 
end;
243
 
\end{lstlisting}
244
 
When the first instance of the \lstinline|GTKFileEdit| widget is created, the 
245
 
call to \lstinline|GtkFileEdit_get_type| will register the widget class 
246
 
first. Subsequent calls to create a new instance will just use the stored 
247
 
value of the ID that identifies the \lstinline|GTKFileEdit| class.
248
 
 
249
 
To be able to create an instance of the \lstinline|GTKFileEdit| class, one
250
 
more procedure must be implemented, as can be seen from the class
251
 
registration code: \lstinline|GtkFileEditInit|. This procedure will 
252
 
initialize (i.e. create) a new instance of the class; it should do
253
 
whatever is necessary so the instance is ready for use.
254
 
 
255
 
In the case of the \lstinline|GTKFileEdit| class, this simply means that
256
 
all widgets of which the class is composed, must be created and placed to
257
 
gether. This is shown in the following code:
258
 
\begin{lstlisting}{}
259
 
Procedure GtkFileEditInit (Obj : PGtkFileEdit);cdecl;
260
 
 
261
 
Var
262
 
  PClass : PGtkFileEditClass;
263
 
 
264
 
begin
265
 
  PClass:=PGtkFileEditClass(PGtkObject(Obj)^.klass);
266
 
  With Obj^ do
267
 
    begin
268
 
    Edit := PgtkEntry(gtk_entry_new);
269
 
    Button := PgtkButton(gtk_button_new);
270
 
    Image := PgtkPixMap(gtk_pixmap_new(PClass^.DefaultPixmap,
271
 
                                       PClass^.DefaultBitmap));
272
 
    gtk_container_add(PGtkContainer(Button),PGtkWidget(Image));
273
 
    gtk_box_pack_start(PgtkBox(Obj),PGtkWidget(Edit),True,True,0);
274
 
    gtk_box_pack_start(PgtkBox(Obj),PGtkWidget(Button),False,True,0);
275
 
    gtk_signal_connect(PgtkObject(Button),'clicked',
276
 
                       TGtkSignalFunc(@GtkFileEditButtonClick),Obj);
277
 
    end;
278
 
  gtk_widget_show_all(PGtkWidget(Obj));
279
 
end;
280
 
\end{lstlisting}
281
 
The code is self explanatory; the sub-widgets are created, and a reference
282
 
to them is stored in the fields of our instance record. Note that the
283
 
ancestor (a \lstinline|gtkHbox|) is not initialized, this has been done 
284
 
already by the OOP mechanism of GTK. 
285
 
 
286
 
After the objects are created, they are put together in the horizontal 
287
 
box, with the options chosen in such a way that the composed widget scales
288
 
well if needed. The bitmap image is of course placed in the button.
289
 
 
290
 
Lastly, a signal handler is added to the button, so that when it is clicked,
291
 
we can take appropriate action (i.e. show a dialog to select a file). 
292
 
Note that as the \lstinline|Data| parameter for the signal, the reference 
293
 
to the \lstinline|GTKFileEdit| instance is passed.
294
 
 
295
 
Now the class is ready to be created and shown. However, it doesn't do
296
 
anything useful yet. The callback for the button click must still be used.
297
 
 
298
 
The callback for the button must create a file selection dialog, show it,
299
 
and when it has been closed by a click on the 'OK' button, it should set
300
 
the text of the edit widget to the name of the selected file.
301
 
 
302
 
In order to do this, some extra callbacks are needed, as can be seen in the
303
 
following code:
304
 
\begin{lstlisting}{}
305
 
Procedure GtkFileEditButtonClick (Obj : PGtkObject; Data : PgtkFileEdit);cdecl;
306
 
 
307
 
Var
308
 
  Dialog : PGtkFileSelection;
309
 
  
310
 
begin
311
 
  Dialog := PGtkFileSelection(gtk_file_selection_new('Please select a file'));
312
 
  Data^.Dialog:=Dialog;
313
 
  gtk_signal_connect(PGTKObject(Dialog^.ok_button),'clicked',
314
 
                     TGTKSignalFunc(@GtkStoreFileName),data);
315
 
  gtk_signal_connect_object (PGtkObject((Dialog)^.ok_button),'clicked', 
316
 
                            TGTKSIGNALFUNC (@gtk_widget_destroy), PgtkObject(Dialog));
317
 
  gtk_signal_connect_object (PGtkObject((Dialog)^.cancel_button),'clicked', 
318
 
                            TGTKSIGNALFUNC (@gtk_widget_destroy), PgtkObject(Dialog));
319
 
  gtk_widget_show(PgtkWidget(dialog));
320
 
end;
321
 
\end{lstlisting}
322
 
The listing shows that an instance of the file selection dialog is created,
323
 
and that its signals are set up so that when the user clicks the 'Cancel'
324
 
button, the file selection dialog is simply destroyed, and when the 'OK'
325
 
button is selected, first a callback is called in which the name of the
326
 
selected file will be retrieved, and secondly the file selection dialog
327
 
is destroyed. 
328
 
Two remarks concerning this code are in order:
329
 
\begin{enumerate}
330
 
\item The order in which the signals are connected to the 'clicked' event of 
331
 
the OK button is important, since they will be triggered in the order that
332
 
they were connected.
333
 
\item A reference to the dialog is stored in the \lstinline|GTKFileEdit|
334
 
instance, and the reference to the \lstinline|GTKFileEdit| is passed as the
335
 
\lstinline|Data| parameter of the signal. 
336
 
\end{enumerate}
337
 
Finally, when the 'OK' button of the file selection dialog is clicked, the
338
 
following callback is executed to store the filename in the edit widget of
339
 
the \lstinline|GTKFileEdit| widget:
340
 
\begin{lstlisting}{}
341
 
Procedure GtkStoreFileName(Button : PgtkButton; 
342
 
                           TheRec : PGtkFileEdit); cdecl;
343
 
 
344
 
begin
345
 
  With TheRec^ do 
346
 
    begin
347
 
    gtk_entry_set_text(Edit,gtk_file_selection_get_filename(Dialog));
348
 
    Dialog:=Nil;
349
 
    end;
350
 
end;
351
 
\end{lstlisting}
352
 
The callback also removes the reference to the file selection dialog. This
353
 
could also have been done by explicitly setting a 'destroy' signal handler
354
 
for the dialog, but since the dialog is destroyed after the 'OK' button is 
355
 
clicked, it is done here.
356
 
 
357
 
Now the \lstinline|GTKFileEdit| is ready for use. It is possible to add 
358
 
some utility functions to the class, for instance one to get or set set 
359
 
the filename:
360
 
\begin{lstlisting}{}
361
 
Procedure GtkFileEdit_set_filename (Obj : PGtkFileEdit; FileName : String);cdecl;
362
 
 
363
 
begin
364
 
  gtk_entry_set_text(Obj^.Edit,PChar(FileName));
365
 
end;
366
 
 
367
 
Function GtkFileEdit_get_filename (Obj : PGtkFileEdit) : String;cdecl;
368
 
 
369
 
begin
370
 
  Result:=StrPas(gtk_entry_get_text(Obj^.Edit));
371
 
end;  
372
 
\end{lstlisting}
373
 
The widget can now be used like any other GTK widget:
374
 
\begin{lstlisting}{}
375
 
program ex1;
376
 
 
377
 
{$mode objfpc}
378
 
 
379
 
uses
380
 
 glib,gtk,fileedit;
381
 
 
382
 
procedure destroy(widget : pGtkWidget ; data: pgpointer ); cdecl;
383
 
begin
384
 
  gtk_main_quit();
385
 
end;
386
 
 
387
 
var
388
 
  window,
389
 
  fileed,
390
 
  box,
391
 
  Button : PgtkWidget;
392
 
      
393
 
begin
394
 
  gtk_init (@argc, @argv);
395
 
  window := gtk_window_new (GTK_WINDOW_TOPLEVEL);
396
 
  fileed := gtkfileedit_new;
397
 
  gtk_container_set_border_width(GTK_CONTAINER(Window),5);
398
 
  box:=gtk_vbox_new(true,10);
399
 
  button:=gtk_button_new_with_label('Quit');
400
 
  gtk_box_pack_start(pgtkbox(box),PGtkWidget(fileed),False,False,0);
401
 
  gtk_box_pack_start(pgtkbox(box),pgtkWidget(button),True,False,0);
402
 
  gtk_container_add(GTK_Container(window),box);
403
 
  gtk_signal_connect (PGTKOBJECT (window), 'destroy',
404
 
                      GTK_SIGNAL_FUNC (@destroy), NULL);
405
 
  gtk_signal_connect_object(PgtkObject(button),'clicked',
406
 
                      GTK_SIGNAL_FUNC(@gtk_widget_destroy),
407
 
                      PGTKOBJECT(window));
408
 
  gtk_widget_show_all (window);
409
 
  gtk_main ();
410
 
end.
411
 
\end{lstlisting}
412
 
The result will look something like figure \ref{fig:fileedit}
413
 
 
414
 
\begin{figure}[h]
415
 
\begin{center}
416
 
\caption{The GTKFileEdit in action}\label{fig:fileedit}
417
 
\vspace{3mm}
418
 
\epsfig{file=gtk2ex/ex1.png}
419
 
\end{center}
420
 
\end{figure}    
421
 
 
422
 
This widget is of course not finished, it can be enhanced in many ways:
423
 
Some additional functionality would be to provide a filter for the dialog,
424
 
or to set the directory initialiy displayed, provide a title for the dialog,
425
 
set a different image on the button, verify that the selected file exists, 
426
 
and so on. these can be added in much the same way that the
427
 
\lstinline|GTKFileEdit_get_filename| and
428
 
\lstinline|GTKFileEdit_set_filename| were implemented.
429
 
 
430
 
The fact that the parts making up the widget, such as the button and the edit
431
 
widgets, are available as fields in the instance record makes it possible
432
 
for the user to set additional properties, provided by these widgets. One
433
 
could imagine the user connecting to the 'changed' signal of the edit, to
434
 
check whether or not the filename being typed exists, and enabling or 
435
 
disabling other widgets accordingly. The usage of the file selection dialog
436
 
itself also makes this clear. 
437
 
 
438
 
\section{A LED digit widget}
439
 
The second widget to be presented in this article is a widget displaying 
440
 
a LED digit; such as found in many CD-Player displays or digital clocks.
441
 
This will demonstrate how to draw a widget on the screen.
442
 
 
443
 
A descendent which reacts to mouse clicks will also be created, which will
444
 
demonstrate how to react to user events such as mouse clicks.
445
 
 
446
 
A digit consists out of 7 segments, which can be either lit or not lit
447
 
(dimmed). For each of the 10 digits (0..9) the state of each of the segments
448
 
must be specified. For this we introduce some types and constants:
449
 
\begin{lstlisting}{}
450
 
Type
451
 
  TLEDSegment = (lsTop,lsCenter,lsBottom,
452
 
                 lsLeftTop,lsRightTop,
453
 
                 lsLeftBottom, lsRightBottom);
454
 
  TLedSegments = Array[TLedSegment] of boolean;
455
 
  
456
 
Const 
457
 
  DigitSegments : Array[0..9] of TLEDSegments = 
458
 
    (
459
 
     (true,false,true,true,true,true,true),       // 0
460
 
     (false,false,false,false,true,false,true),   // 1
461
 
     (true,true,true,false,true,true,false),      // 2                
462
 
     (true,true,true,false,true,false,true),      // 3
463
 
     (false,true,false,true,true,false,true),     // 4
464
 
     (true,true,true,true,false,false,true),      // 5
465
 
     (true,true,true,true,false,true,true),       // 6
466
 
     (true,false,false,false,true,false,true),    // 7
467
 
     (true,true,true,true,true,true,true),        // 8
468
 
     (true,true,true,true,true,false,true)        // 9
469
 
    );
470
 
\end{lstlisting}
471
 
The meaning of each of these types and the constant is obvious.
472
 
 
473
 
Each segment is drawn between 2 points, located on a rectangle
474
 
with 6 points, as shown in figure \ref{fig:corners}
475
 
\begin{figure}
476
 
\begin{center}
477
 
\caption{Corners of a digit}\label{fig:corners}
478
 
\epsfig{file=gtk2ex/corners.png}
479
 
\end{center}
480
 
\end{figure}
481
 
Each segment is drawn between 2 corners: a start corner and an end corner.
482
 
For each segment the start and end corner are stored in the 
483
 
\lstinline|SegmentCorners| array.
484
 
\begin{lstlisting}{}
485
 
Type
486
 
  TSegmentCorners = Array [1..2] of Byte;
487
 
 
488
 
Const
489
 
  SegmentCorners : Array [TLEDSegment] of TSegmentCorners = 
490
 
    (
491
 
     (1,2),
492
 
     (3,4),
493
 
     (5,6),
494
 
     (1,3),
495
 
     (2,4),
496
 
     (3,5),
497
 
     (4,6)
498
 
    );
499
 
\end{lstlisting}
500
 
These constants will facilitate the drawing of the digit later on. 
501
 
 
502
 
For the digit widget, 2 records must again be introduced; one for the class,
503
 
and one for the instances of objects:
504
 
\begin{lstlisting}{}
505
 
Type
506
 
  TPoint = Record 
507
 
    X,Y : gint;
508
 
    end;
509
 
 
510
 
  PGtkDigit = ^TGtkDigit;  
511
 
  TGtkDigit = Record 
512
 
    ParentWidget : TGtkWidget;
513
 
    borderwidth,
514
 
    digit : guint;
515
 
    Corners : Array [1..6] of TPoint;
516
 
  end;
517
 
  
518
 
  PGtkDigitClass = ^TGtkDigitClass;
519
 
  TGtkDigitClass = Record
520
 
    Parent_Class : TGtkWidgetClass;
521
 
  end;
522
 
\end{lstlisting}
523
 
The class record \lstinline|TGtkDigitClass| contains no extra information
524
 
in this case, it has the parent class record as its ony field, as required 
525
 
bythe GTK object model. It could however be used to store some default values to 
526
 
be applied to new widgets, as was the case for the \lstinline|GTKFileEdit|
527
 
widget.
528
 
 
529
 
The object record contains three extra fields:
530
 
\begin{description}
531
 
\item[borderwidth] The distance between the segments and the border of 
532
 
the widget.
533
 
\item[digit] The digit to be displayed.
534
 
\item[Corners] this array contains the locations of each of the corners
535
 
between which the segments will be drawn.
536
 
\end{description}
537
 
The \lstinline|GTKDigit| class must be registered with GTK, and this happens
538
 
in the same manner as before:
539
 
\begin{lstlisting}{}
540
 
Function GtkDigit_get_type : Guint;cdecl;
541
 
 
542
 
Const 
543
 
  GtkDigitInfo : TGtkTypeInfo = 
544
 
    (type_name : 'GtkDigit';
545
 
     object_size : SizeOf(TGtkDigit);
546
 
     class_size : SizeOf(TGtkDigitClass);
547
 
     class_init_func : TGtkClassInitFunc(@GtkDigitClassInit);
548
 
     object_init_func : TGtkObjectInitFunc(@GtkDigitInit);
549
 
     reserved_1 : Nil;
550
 
     reserved_2 : Nil;
551
 
     base_class_init_func : Nil
552
 
    );
553
 
 
554
 
begin
555
 
  if (GtkDigitType=0) then
556
 
    GtkDigitType:=gtk_type_unique(gtk_widget_get_type,@GtkDigitInfo);
557
 
  Result:=GtkDigitType;  
558
 
end;
559
 
\end{lstlisting}
560
 
In the class initialization code, the real difference between this widget
561
 
and the previous one becomes clear:
562
 
\begin{lstlisting}{}
563
 
Procedure GtkDigitClassInit (CObj : PGtkDigitClass);cdecl;
564
 
 
565
 
begin
566
 
  With PGtkWidgetClass(Cobj)^ do 
567
 
    begin
568
 
    size_request:=@GTKDigitSizeRequest;
569
 
    expose_event:=@GTKDigitExpose;
570
 
    size_allocate:=@GTKDigitSizeAllocate;
571
 
    end;
572
 
end;
573
 
\end{lstlisting}
574
 
Here GTK is told that, in order to determine the size of the widget,
575
 
it should first call \lstinline|GTKDigitSizeRequest|; this will provide
576
 
GTK with an initial size for the object. After GTK has placed all widgets
577
 
in the window, and has determined the sizes and positions it will allocate 
578
 
to each widget in the form, it will call \lstinline|GTKDigitSizeAllocate| 
579
 
to notify the \lstinline|GTKDigit| widget of the size it is being allocated.
580
 
 
581
 
Finally, the \lstinline|expose_event| callback is set; this informs GTK that
582
 
when a part of the widget should be drawn (because it is visible to the
583
 
user), \lstinline|GTKDigitExpose| should be called. There are actually 2 
584
 
callbacks to draw a widget; one of them is
585
 
the \lstinline|draw| function and the other is the (here used) 
586
 
\lstinline|expose| function. The \lstinline|draw| function of 
587
 
\lstinline|GTKWidget| just generates an expose event for the entire widget,
588
 
and for the current widget this is enough. There are, however, cases where
589
 
it may be necessary to differentiate between the two for optmization
590
 
purposes.
591
 
 
592
 
The object initialization function \lstinline|| simply initializes all fields to their
593
 
default values:
594
 
\begin{lstlisting}{}
595
 
Procedure GtkDigitInit (Obj : PGtkDigit);cdecl;
596
 
 
597
 
Var I : longint;
598
 
 
599
 
begin
600
 
  gtk_widget_set_flags(pgtkWidget(obj),GTK_NO_WINDOW);
601
 
  With Obj^ do
602
 
    begin 
603
 
    Digit:=0;
604
 
    BorderWidth:=2;
605
 
    For I:=1 to 6 do
606
 
    with Corners[i] do
607
 
      begin
608
 
      X:=0;
609
 
      Y:=0;
610
 
      end;
611
 
    end;  
612
 
end;
613
 
\end{lstlisting}
614
 
The interesting thing in the initialization function is the call to
615
 
\lstinline|gtk_widget_set_flags|; this tells GTK that the
616
 
\lstinline|GtkDigit| does not need its own window. Indeed, it will
617
 
use its parent window to draw itself when needed.
618
 
This also means that no extra resources must be allocated for the widget.
619
 
 
620
 
The \lstinline|size_request| callback will in our case simply ask for some
621
 
default size for the digit:
622
 
\begin{lstlisting}{}
623
 
Procedure GTKDigitSizeRequest (Widget : PGtkWidget; 
624
 
                               Request : PGtkRequisition);cdecl;
625
 
   
626
 
Var BW : guint;   
627
 
                               
628
 
begin
629
 
  With PGTKDigit(Widget)^ do
630
 
    BW:=BorderWidth;
631
 
  With Request^ do
632
 
    begin
633
 
    Width:=20+2*BW;
634
 
    Height:=40+2*BW;
635
 
    end;
636
 
end;
637
 
\end{lstlisting}
638
 
usually, GTK will allocate a size at least equal to the size requested. It
639
 
may however be more than this.
640
 
 
641
 
When GTK has decided what the real size of the widget will be, the
642
 
\lstinline|GTKDigitSizeAllocate| will be called:
643
 
\begin{lstlisting}{}
644
 
procedure GTKDigitSizeAllocate(Widget : PGTKWidget;
645
 
                               Allocation : PGTKAllocation);cdecl;
646
 
  
647
 
begin
648
 
  Widget^.Allocation:=Allocation^;
649
 
  SetDigitCorners(PGtkDigit(Widget),False);
650
 
end;
651
 
\end{lstlisting}
652
 
This procedure first of all stores the allocated size in the widget, and
653
 
then it calls \lstinline|SetDigitCorners| to calculate the positions of
654
 
the corners of the segments; this is done as follows:
655
 
\begin{lstlisting}{}
656
 
Procedure SetDigitCorners(Digit : PGtkDigit; IgnoreOffset : Boolean);
657
 
 
658
 
Var
659
 
  BW : guint;
660
 
  W,H,SX,SY : gint;
661
 
  i : longint;
662
 
  Widget : PGTKWidget;
663
 
  
664
 
begin
665
 
  Widget:=PGTKWidget(Digit);
666
 
  BW:=Digit^.Borderwidth;
667
 
  If IgnoreOffset then
668
 
    begin
669
 
    SX:=0;
670
 
    SY:=0;
671
 
    end
672
 
  else
673
 
    begin
674
 
    SX:=Widget^.Allocation.x;
675
 
    SY:=Widget^.Allocation.y;
676
 
    end;
677
 
  W:=Widget^.Allocation.Width-2*BW;
678
 
  H:=(Widget^.Allocation.Height-2*BW) div 2;  
679
 
  With PGTKDigit(Widget)^ do
680
 
    For I:=1 to 6 do
681
 
      begin
682
 
      Case I of
683
 
        1,3,5 : Corners[i].X:=SX+BW;
684
 
        2,4,6 : Corners[i].X:=SX+BW+W;
685
 
      end;
686
 
      Case I of
687
 
        1,2 : Corners[i].Y:=SY+BW;
688
 
        3,4 : Corners[i].Y:=SY+BW+H;
689
 
        5,6 : Corners[i].Y:=SY+BW+2*H
690
 
      end;
691
 
      end;
692
 
end;
693
 
\end{lstlisting} 
694
 
Since the \lstinline|GTKDigit| will draw on its parents window, it must
695
 
take into account the offset (x,y) of the allocated size. The reason that
696
 
this is parametrized with the \lstinline|IgnoreOffset| parameter will become
697
 
clear when the descendent widget is introduced.
698
 
 
699
 
This function could be adapted to give e.g. a slight tilt to the digits. 
700
 
 
701
 
Remains to implement the \lstinline|expose_event| callback:
702
 
\begin{lstlisting}{}
703
 
Function GTKDigitExpose (Widget : PGTKWidget;
704
 
                         ExposeEvent : PGDKEventExpose) : gint;cdecl;
705
 
 
706
 
Var
707
 
  Segment : TLedSegment;
708
 
     
709
 
begin
710
 
  With PGTKDigit(Widget)^ do
711
 
    For Segment:=lsTop to lsRightBottom do 
712
 
      if DigitSegments[Digit][Segment] then
713
 
        gdk_draw_line(widget^.window,
714
 
                  PgtkStyle(widget^.thestyle)^.fg_gc[widget^.state],
715
 
                  Corners[SegmentCorners[Segment][1]].X,
716
 
                  Corners[SegmentCorners[Segment][1]].Y,
717
 
                  Corners[SegmentCorners[Segment][2]].X,
718
 
                  Corners[SegmentCorners[Segment][2]].Y
719
 
                  )
720
 
      else
721
 
        gdk_draw_line(widget^.window,
722
 
                  PgtkStyle(widget^.thestyle)^.bg_gc[widget^.state],
723
 
                  Corners[SegmentCorners[Segment][1]].X,
724
 
                  Corners[SegmentCorners[Segment][1]].Y,
725
 
                  Corners[SegmentCorners[Segment][2]].X,
726
 
                  Corners[SegmentCorners[Segment][2]].Y
727
 
                  );
728
 
  
729
 
end;
730
 
\end{lstlisting}
731
 
Here the need for the types and constants, introduced in the
732
 
beginning of this section becomes obvious; without them, a huge
733
 
case statement would be needed to draw all needed segments. 
734
 
 
735
 
Note that when a segment of our digit is not 'lit', it is drawn in the
736
 
background color. When the digit to be displayed changes, the segments 
737
 
that are no longer lit, must be 'dimmed' again.
738
 
 
739
 
Finally we provide 2 methods to get and set the digit to be dislayed:
740
 
\begin{lstlisting}{}
741
 
Procedure GtkDigit_set_digit (Obj : PGtkDigit; Digit : guint);cdecl;
742
 
 
743
 
begin
744
 
  if Digit in [0..9] then
745
 
    begin
746
 
    Obj^.Digit:=Digit;
747
 
    gtk_widget_draw(PGTKWidget(Obj),Nil);
748
 
    end;
749
 
end;
750
 
 
751
 
Function GtkDigit_get_digit (Obj : PGtkDigit) : guint;cdecl;
752
 
 
753
 
begin
754
 
  Result:=Obj^.Digit;
755
 
end;  
756
 
\end{lstlisting}
757
 
Obviously, when setting the digit to be displayed, the widget must be
758
 
redrawn, or the display would not change till the next expose event. 
759
 
Calling \lstinline|gtk_widget_draw| ensures that the digit will be displayed
760
 
correctly. 
761
 
 
762
 
Now the widget is ready for use; it can be created and put on a window
763
 
in the same manner as the \lstinline|GTKFileEdit| control; the code will
764
 
not be shown, but is available separately. 
765
 
 
766
 
The result is shown in figure \ref{fig:ex2}.
767
 
\begin{figure}
768
 
\begin{center}
769
 
\caption{The GTKDigit widget in action.}\label{fig:ex2}
770
 
\epsfig{file=gtk2ex/ex2.png}
771
 
\end{center}
772
 
\end{figure}
773
 
 
774
 
The widget can be improved in many ways. The segments can be tilted, a
775
 
bigger width can be used; the can have rounded edges and so on.
776
 
 
777
 
The widget as presented here doesn't react on user events; it has no way
778
 
of doing that, since it doesn't have an own window; Therefore a descendent
779
 
is made which creates its own window, and which will react on mouse clicks;
780
 
this widget will be called \lstinline|GTKActiveDigit|.
781
 
 
782
 
The lstinline|GTKActiveDigit| widget is a descendent from its inactive
783
 
counterpart. Therefore the class and object records will be (almost) empty:
784
 
\begin{lstlisting}{}
785
 
Type
786
 
  PGtkActiveDigit = ^TGtkActiveDigit;  
787
 
  TGtkActiveDigit = Record 
788
 
    ParentWidget : TGtkDigit;
789
 
    Button : guint8;
790
 
  end;
791
 
  
792
 
  PGtkActiveDigitClass = ^TGtkActiveDigitClass;
793
 
  TGtkActiveDigitClass = Record
794
 
    Parent_Class : TGtkDigitClass;
795
 
  end;
796
 
\end{lstlisting}
797
 
The \lstinline|Button| field is used to store which button was used to click
798
 
on the digit. 
799
 
 
800
 
The registration of the new widget is similar to the one for
801
 
\lstinline|GTKDigit|, and doesn't need more explanation:
802
 
\begin{lstlisting}{}
803
 
Const
804
 
  GtkActiveDigitType : guint = 0;
805
 
 
806
 
Function  GtkActiveDigit_get_type : Guint;cdecl;
807
 
 
808
 
Const 
809
 
  GtkActiveDigitInfo : TGtkTypeInfo = 
810
 
    (type_name : 'GtkActiveDigit';
811
 
     object_size : SizeOf(TGtkActiveDigit);
812
 
     class_size : SizeOf(TGtkActiveDigitClass);
813
 
     class_init_func : TGtkClassInitFunc(@GtkActiveDigitClassInit);
814
 
     object_init_func : TGtkObjectInitFunc(@GtkActiveDigitInit);
815
 
     reserved_1 : Nil;
816
 
     reserved_2 : Nil;
817
 
     base_class_init_func : Nil
818
 
    );
819
 
 
820
 
begin
821
 
  if (GtkActiveDigitType=0) then
822
 
    GtkActiveDigitType:=gtk_type_unique(gtkdigit_get_type,@GtkActiveDigitInfo);
823
 
  Result:=GtkActiveDigitType;  
824
 
end;
825
 
 
826
 
 
827
 
Function  GtkActiveDigit_new : PGtkWidget;cdecl;
828
 
 
829
 
begin
830
 
  Result:=gtk_type_new(GtkActiveDigit_get_type)  
831
 
end;
832
 
\end{lstlisting}
833
 
The first real difference is in the class initialization routine:
834
 
\begin{lstlisting}{}
835
 
Procedure GtkActiveDigitClassInit (CObj : PGtkActiveDigitClass);cdecl;
836
 
 
837
 
begin
838
 
  With PGtkWidgetClass(Cobj)^ do 
839
 
    begin
840
 
    realize := @GtkActiveDigitRealize;
841
 
    size_allocate := @GtkActiveDigitSizeAllocate;
842
 
    button_press_event:=@GtkActiveDigitButtonPress;
843
 
    button_release_event:=@GtkActiveDigitButtonRelease;
844
 
    end;
845
 
end;
846
 
\end{lstlisting}
847
 
The \lstinline|realize| and \lstinline|size_allocate| of the parent widget
848
 
\lstinline|GTKDigit| are overriden here. Also 2 events callbacks are
849
 
assigned in order to react on mouse clicks.
850
 
 
851
 
The object initialization function must undo some work that was done
852
 
ba the parent's initialization function:
853
 
\begin{lstlisting}{}
854
 
Procedure GtkActiveDigitInit (Obj : PGtkActiveDigit);cdecl;
855
 
 
856
 
begin
857
 
  gtk_widget_unset_flags(pgtkWidget(obj),GTK_NO_WINDOW);
858
 
  With Obj^ do
859
 
    Button:=0;
860
 
end;
861
 
\end{lstlisting}
862
 
This is necessary, because the \lstinline|GTKActiveDigit| will create it's
863
 
own window.
864
 
 
865
 
For this widget, the \lstinline|realize| callback must do a little more
866
 
work. It must create a window on which the digit will be drawn. The window
867
 
is created with some default settings, and the event mask for the window
868
 
is set such that the window will respond to mouse clicks:
869
 
\begin{lstlisting}{}
870
 
Procedure GtkActiveDigitRealize(widget : PgtkWidget);cdecl;
871
 
 
872
 
Var
873
 
 attr : TGDKWindowAttr;
874
 
 Mask : gint;
875
 
 
876
 
begin
877
 
  GTK_WIDGET_SET_FLAGS(widget,GTK_REALIZED);
878
 
  With Attr do
879
 
    begin
880
 
    x := widget^.allocation.x;
881
 
    y := widget^.allocation.y;
882
 
    width:=widget^.allocation.width;
883
 
    height:=widget^.allocation.height;
884
 
    wclass:=GDK_INPUT_OUTPUT;
885
 
    window_type:=gdk_window_child;
886
 
    event_mask:=gtk_widget_get_events(widget) or GDK_EXPOSURE_MASK or
887
 
                GDK_BUTTON_PRESS_MASK OR GDK_BUTTON_RELEASE_MASK;
888
 
    visual:=gtk_widget_get_visual(widget);
889
 
    colormap:=gtk_widget_get_colormap(widget);
890
 
    end;
891
 
  Mask:=GDK_WA_X or GDK_WA_Y or GDK_WA_VISUAL or GDK_WA_COLORMAP;
892
 
  widget^.Window:=gdk_window_new(widget^.parent^.window,@attr,mask);
893
 
  widget^.thestyle:=gtk_style_attach(widget^.thestyle,widget^.window);
894
 
  gdk_window_set_user_data(widget^.window,widget);  
895
 
  gtk_style_set_background(widget^.thestyle,widget^.window,GTK_STATE_ACTIVE);
896
 
end;
897
 
\end{lstlisting}
898
 
After the window was created, its userdata is set to the widget. This
899
 
ensures that the events which occur in the window are passed on to our 
900
 
widget by GTK. Finally the background of the window is set to some
901
 
other style than the default style.
902
 
 
903
 
The size allocation event should in principle do the same as that for the
904
 
\lstinline|GTKDigit| widget, with the exeption that the calculation of the
905
 
corners for the segments must now not be done relative to the parent window:
906
 
\begin{lstlisting}{}
907
 
procedure GTKActiveDigitSizeAllocate(Widget : PGTKWidget;
908
 
                               Allocation : PGTKAllocation);cdecl;
909
 
begin
910
 
  Widget^.allocation:=Allocation^;
911
 
  if GTK_WIDGET_REALIZED(widget) then
912
 
    gdk_window_move_resize(widget^.window,
913
 
                           Allocation^.x,
914
 
                           Allocation^.y,
915
 
                           Allocation^.width,
916
 
                           Allocation^.height);
917
 
  SetDigitCorners(PGTKDigit(Widget),True);
918
 
end;
919
 
\end{lstlisting}
920
 
This explains the need for the \lstinline|IgnoreOffset| parameter in the
921
 
\lstinline|SetDigitCorners| function.
922
 
 
923
 
All that is left is to implement the mouse click events:
924
 
\begin{lstlisting}{}
925
 
Function GtkActiveDigitButtonPress(Widget: PGtKWidget; 
926
 
                                    Event : PGdkEventButton) : gint;cdecl;
927
 
 
928
 
begin
929
 
  PGTKActiveDigit(Widget)^.Button:=Event^.Button;
930
 
end;
931
 
 
932
 
Function GtkActiveDigitButtonRelease(Widget: PGtKWidget; 
933
 
                                      Event : PGdkEventButton) : gint;cdecl;
934
 
 
935
 
Var
936
 
  Digit : PGtkDigit;
937
 
  D : guint;
938
 
 
939
 
begin
940
 
  Digit:=PGTKDigit(Widget);
941
 
  D:=gtkdigit_get_digit(Digit);
942
 
  If PGTKActiveDigit(Digit)^.Button=Event^.Button then
943
 
    begin
944
 
    If Event^.Button=1 then
945
 
      GTKDigit_set_digit(Digit,D+1)
946
 
    else if Event^.Button=3 then
947
 
      GTKDigit_set_digit(Digit,D-1)
948
 
    else
949
 
      GTKDigit_set_digit(Digit,0);
950
 
    end;  
951
 
  PGTKActiveDigit(Digit)^.Button:=0;
952
 
end;
953
 
\end{lstlisting}
954
 
As can be seen, the digit will be incremented when the left mouse button
955
 
is clicked. The digit is decremented when the right button is clicked. 
956
 
On systems with 3 mouse buttons, a click on the middle mouse button will
957
 
reset the digit to 0.
958
 
 
959
 
After all this, the widget is ready for use, and should look more or less
960
 
like the one in figure \ref{fig:ex3}.
961
 
 
962
 
\begin{figure}[h]
963
 
\begin{center}
964
 
\caption{The GTKActiveDigit in action.}\label{fig:ex3}
965
 
\epsfig{file=gtk2ex/ex3.png}
966
 
\end{center}
967
 
\end{figure}
968
 
 
969
 
The widgets presented here are not complete; many improvements can be made,
970
 
but their main purpose was to demonstrate that implementing some new widgets
971
 
is very easy and can be achieved with little effort; what is more, the OOP
972
 
structure of GTK is very suitable for the implementation of small
973
 
enhancements to existing components, as was shown with the last widget
974
 
presented.
975
 
\end{document}