1
\documentclass[10pt]{article}
5
\lstset{language=Delphi}%
6
\lstset{basicstyle=\sffamily\small}%
7
\lstset{commentstyle=\itshape}%
8
\lstset{keywordstyle=\bfseries}%
9
%\lstset{blankstring=true}%
11
\ifx\pdfoutput\undefined
18
\title{Programming GTK in Free Pascal}
19
\author{Florian Kl\"ampfl\\and\\Micha\"el Van Canneyt}
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.
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.
31
When constructing the second widget, the focus will be on how a widget
32
should draw itself in GTK.
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.
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.
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.
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.
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|:
64
TGtkContainer = record
66
focus_child : PGtkWidget;
68
resize_widgets : PGSList;
71
The same is true for the \lstinline|TGtkContainerClass| record:
73
TGtkContainerClass = record
74
parent_class : TGtkWidgetClass;
79
For both the components that will be made, such records will be made.
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.}
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.
97
Having decided that, the structure of the record for the instance of the
98
component is more or less determined:
101
PGtkFileEdit = ^TGtkFileEdit;
102
TGtkFileEdit = Record
107
Dialog : PGtkFileSelection;
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.
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)
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|):
129
PGtkFileEditClass = ^TGtkFileEditClass;
130
TGtkFileEditClass = Record
131
Parent_Class : TgtkHBoxClass;
132
DefaultPixmap : PGdkPixmap;
133
DefaultBitMap : PGdkBitmap;
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.
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.
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.
153
The \lstinline|GTKFileEdit_get_type| function looks like this
154
\lstinline|gtk\_type\_unique|:
156
Function GtkFileEdit_get_type : Guint;cdecl;
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);
167
base_class_init_func : Nil
171
if (GtkFileEditType=0) then
172
GtkFileEditType:=gtk_type_unique(gtk_hbox_get_type,@GtkFileEditInfo);
173
Result:=GtkFileEditType;
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:
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
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
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.
195
The other three fields of the record are unfortunately not documented, so
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.
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:
207
Procedure GtkFileEditClassInit (CObj : PGtkFileEditClass);cdecl;
211
DefaultPixMap:=gdk_pixmap_create_from_xpm(Nil,@DefaultBitmap,
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.
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.
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.
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.
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,
238
Function GtkFileEdit_new : PGtkWidget;cdecl;
241
Result:=gtk_type_new(GtkFIleEdit_get_type)
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.
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.
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:
259
Procedure GtkFileEditInit (Obj : PGtkFileEdit);cdecl;
262
PClass : PGtkFileEditClass;
265
PClass:=PGtkFileEditClass(PGtkObject(Obj)^.klass);
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);
278
gtk_widget_show_all(PGtkWidget(Obj));
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.
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.
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.
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.
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.
302
In order to do this, some extra callbacks are needed, as can be seen in the
305
Procedure GtkFileEditButtonClick (Obj : PGtkObject; Data : PgtkFileEdit);cdecl;
308
Dialog : PGtkFileSelection;
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));
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
328
Two remarks concerning this code are in order:
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
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.
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:
341
Procedure GtkStoreFileName(Button : PgtkButton;
342
TheRec : PGtkFileEdit); cdecl;
347
gtk_entry_set_text(Edit,gtk_file_selection_get_filename(Dialog));
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.
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
361
Procedure GtkFileEdit_set_filename (Obj : PGtkFileEdit; FileName : String);cdecl;
364
gtk_entry_set_text(Obj^.Edit,PChar(FileName));
367
Function GtkFileEdit_get_filename (Obj : PGtkFileEdit) : String;cdecl;
370
Result:=StrPas(gtk_entry_get_text(Obj^.Edit));
373
The widget can now be used like any other GTK widget:
382
procedure destroy(widget : pGtkWidget ; data: pgpointer ); cdecl;
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),
408
gtk_widget_show_all (window);
412
The result will look something like figure \ref{fig:fileedit}
416
\caption{The GTKFileEdit in action}\label{fig:fileedit}
418
\epsfig{file=gtk2ex/ex1.png}
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.
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.
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.
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.
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:
451
TLEDSegment = (lsTop,lsCenter,lsBottom,
452
lsLeftTop,lsRightTop,
453
lsLeftBottom, lsRightBottom);
454
TLedSegments = Array[TLedSegment] of boolean;
457
DigitSegments : Array[0..9] of TLEDSegments =
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
471
The meaning of each of these types and the constant is obvious.
473
Each segment is drawn between 2 points, located on a rectangle
474
with 6 points, as shown in figure \ref{fig:corners}
477
\caption{Corners of a digit}\label{fig:corners}
478
\epsfig{file=gtk2ex/corners.png}
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.
486
TSegmentCorners = Array [1..2] of Byte;
489
SegmentCorners : Array [TLEDSegment] of TSegmentCorners =
500
These constants will facilitate the drawing of the digit later on.
502
For the digit widget, 2 records must again be introduced; one for the class,
503
and one for the instances of objects:
510
PGtkDigit = ^TGtkDigit;
512
ParentWidget : TGtkWidget;
515
Corners : Array [1..6] of TPoint;
518
PGtkDigitClass = ^TGtkDigitClass;
519
TGtkDigitClass = Record
520
Parent_Class : TGtkWidgetClass;
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|
529
The object record contains three extra fields:
531
\item[borderwidth] The distance between the segments and the border of
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.
537
The \lstinline|GTKDigit| class must be registered with GTK, and this happens
538
in the same manner as before:
540
Function GtkDigit_get_type : Guint;cdecl;
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);
551
base_class_init_func : Nil
555
if (GtkDigitType=0) then
556
GtkDigitType:=gtk_type_unique(gtk_widget_get_type,@GtkDigitInfo);
557
Result:=GtkDigitType;
560
In the class initialization code, the real difference between this widget
561
and the previous one becomes clear:
563
Procedure GtkDigitClassInit (CObj : PGtkDigitClass);cdecl;
566
With PGtkWidgetClass(Cobj)^ do
568
size_request:=@GTKDigitSizeRequest;
569
expose_event:=@GTKDigitExpose;
570
size_allocate:=@GTKDigitSizeAllocate;
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.
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
592
The object initialization function \lstinline|| simply initializes all fields to their
595
Procedure GtkDigitInit (Obj : PGtkDigit);cdecl;
600
gtk_widget_set_flags(pgtkWidget(obj),GTK_NO_WINDOW);
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.
620
The \lstinline|size_request| callback will in our case simply ask for some
621
default size for the digit:
623
Procedure GTKDigitSizeRequest (Widget : PGtkWidget;
624
Request : PGtkRequisition);cdecl;
629
With PGTKDigit(Widget)^ do
638
usually, GTK will allocate a size at least equal to the size requested. It
639
may however be more than this.
641
When GTK has decided what the real size of the widget will be, the
642
\lstinline|GTKDigitSizeAllocate| will be called:
644
procedure GTKDigitSizeAllocate(Widget : PGTKWidget;
645
Allocation : PGTKAllocation);cdecl;
648
Widget^.Allocation:=Allocation^;
649
SetDigitCorners(PGtkDigit(Widget),False);
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:
656
Procedure SetDigitCorners(Digit : PGtkDigit; IgnoreOffset : Boolean);
665
Widget:=PGTKWidget(Digit);
666
BW:=Digit^.Borderwidth;
674
SX:=Widget^.Allocation.x;
675
SY:=Widget^.Allocation.y;
677
W:=Widget^.Allocation.Width-2*BW;
678
H:=(Widget^.Allocation.Height-2*BW) div 2;
679
With PGTKDigit(Widget)^ do
683
1,3,5 : Corners[i].X:=SX+BW;
684
2,4,6 : Corners[i].X:=SX+BW+W;
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
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.
699
This function could be adapted to give e.g. a slight tilt to the digits.
701
Remains to implement the \lstinline|expose_event| callback:
703
Function GTKDigitExpose (Widget : PGTKWidget;
704
ExposeEvent : PGDKEventExpose) : gint;cdecl;
707
Segment : TLedSegment;
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
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
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.
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.
739
Finally we provide 2 methods to get and set the digit to be dislayed:
741
Procedure GtkDigit_set_digit (Obj : PGtkDigit; Digit : guint);cdecl;
744
if Digit in [0..9] then
747
gtk_widget_draw(PGTKWidget(Obj),Nil);
751
Function GtkDigit_get_digit (Obj : PGtkDigit) : guint;cdecl;
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
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.
766
The result is shown in figure \ref{fig:ex2}.
769
\caption{The GTKDigit widget in action.}\label{fig:ex2}
770
\epsfig{file=gtk2ex/ex2.png}
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.
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|.
782
The lstinline|GTKActiveDigit| widget is a descendent from its inactive
783
counterpart. Therefore the class and object records will be (almost) empty:
786
PGtkActiveDigit = ^TGtkActiveDigit;
787
TGtkActiveDigit = Record
788
ParentWidget : TGtkDigit;
792
PGtkActiveDigitClass = ^TGtkActiveDigitClass;
793
TGtkActiveDigitClass = Record
794
Parent_Class : TGtkDigitClass;
797
The \lstinline|Button| field is used to store which button was used to click
800
The registration of the new widget is similar to the one for
801
\lstinline|GTKDigit|, and doesn't need more explanation:
804
GtkActiveDigitType : guint = 0;
806
Function GtkActiveDigit_get_type : Guint;cdecl;
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);
817
base_class_init_func : Nil
821
if (GtkActiveDigitType=0) then
822
GtkActiveDigitType:=gtk_type_unique(gtkdigit_get_type,@GtkActiveDigitInfo);
823
Result:=GtkActiveDigitType;
827
Function GtkActiveDigit_new : PGtkWidget;cdecl;
830
Result:=gtk_type_new(GtkActiveDigit_get_type)
833
The first real difference is in the class initialization routine:
835
Procedure GtkActiveDigitClassInit (CObj : PGtkActiveDigitClass);cdecl;
838
With PGtkWidgetClass(Cobj)^ do
840
realize := @GtkActiveDigitRealize;
841
size_allocate := @GtkActiveDigitSizeAllocate;
842
button_press_event:=@GtkActiveDigitButtonPress;
843
button_release_event:=@GtkActiveDigitButtonRelease;
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.
851
The object initialization function must undo some work that was done
852
ba the parent's initialization function:
854
Procedure GtkActiveDigitInit (Obj : PGtkActiveDigit);cdecl;
857
gtk_widget_unset_flags(pgtkWidget(obj),GTK_NO_WINDOW);
862
This is necessary, because the \lstinline|GTKActiveDigit| will create it's
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:
870
Procedure GtkActiveDigitRealize(widget : PgtkWidget);cdecl;
873
attr : TGDKWindowAttr;
877
GTK_WIDGET_SET_FLAGS(widget,GTK_REALIZED);
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);
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);
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.
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:
907
procedure GTKActiveDigitSizeAllocate(Widget : PGTKWidget;
908
Allocation : PGTKAllocation);cdecl;
910
Widget^.allocation:=Allocation^;
911
if GTK_WIDGET_REALIZED(widget) then
912
gdk_window_move_resize(widget^.window,
917
SetDigitCorners(PGTKDigit(Widget),True);
920
This explains the need for the \lstinline|IgnoreOffset| parameter in the
921
\lstinline|SetDigitCorners| function.
923
All that is left is to implement the mouse click events:
925
Function GtkActiveDigitButtonPress(Widget: PGtKWidget;
926
Event : PGdkEventButton) : gint;cdecl;
929
PGTKActiveDigit(Widget)^.Button:=Event^.Button;
932
Function GtkActiveDigitButtonRelease(Widget: PGtKWidget;
933
Event : PGdkEventButton) : gint;cdecl;
940
Digit:=PGTKDigit(Widget);
941
D:=gtkdigit_get_digit(Digit);
942
If PGTKActiveDigit(Digit)^.Button=Event^.Button then
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)
949
GTKDigit_set_digit(Digit,0);
951
PGTKActiveDigit(Digit)^.Button:=0;
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.
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}.
964
\caption{The GTKActiveDigit in action.}\label{fig:ex3}
965
\epsfig{file=gtk2ex/ex3.png}
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