123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975 |
- \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{September 2000}
- \maketitle
- \section{Introduction}
- In this second article on programming the GTK toolkit, a more advanced use
- of the GTK library is presented. Techniques to create a new GTK widget
- are discussed by creating two custom widgets.
- The first widget is realized by combining existing GTK widgets to create
- a new widget, a GTKFileEdit component, modeled after the TFileEdit component
- found in the RXLib library for Delphi.
- When constructing the second widget, the focus will be on how a widget
- should draw itself in GTK.
- \section{Preliminaries}
- Whatever the method used when creating new GTK widgets, it is necessary to
- split the functionality of the widget in 2 parts.
- The first part is the functionality that is common to all instances of the
- new widget. This part is by far the most important one, and is implemented
- in the 'class record'. This record will be initialized with a class
- initialization function. It will also contain pointers to callbacks to
- draw a particular instance or callbacks to react on user events.
- The second part concerns the particular instance of the widget that is
- created, it contains the data that determines the state of an instance
- after it is created, it is the actual object created by the user. This
- part of the widget is implemented in the 'Object record'. For this record
- also there is a initalization function.
- When the two records have been defined, some standard methods must be
- implemented in order to integrate the new widget in the GTK library.
- Implementing some methods for the user to manipulate the properties
- of the new widget finishes the creation of a new widget.
- Since GTK is implemented in C, the programmer must obey some rules in order
- to preserve the object-oriented aspect of the GTK library. More precisely,
- when defining the class and object records, care must be taken to specify
- the parent object or class as the first element in the newly created structure. This
- will allow typecasting of the widget to its parent objects.
- Taking a look at the \lstinline|TGtkContainer| widget, we see that the declaration
- of the object record starts with the declaration of its parent widget
- \lstinline|TGtkWidget|:
- \begin{lstlisting}{}
- TGtkContainer = record
- widget : TGtkWidget;
- focus_child : PGtkWidget;
- flag0 : longint;
- resize_widgets : PGSList;
- end;
- \end{lstlisting}
- The same is true for the \lstinline|TGtkContainerClass| record:
- \begin{lstlisting}{}
- TGtkContainerClass = record
- parent_class : TGtkWidgetClass;
- n_child_args : guint;
- // ...
- end;
- \end{lstlisting}
- For both the components that will be made, such records will be made.
- \section{A filename edit component}
- The \lstinline|TGTKFileEdit| component presented here is composed out of three
- other components; first of all a single line edit control, in which the
- user can type a filename if he wishes. The second is a button. The button
- is always placed on the right edge of the edit control, and has the same
- height. The third component is an image component, which is used to display
- an image on the button\footnote{In GTK a button does not necessarily contains a
- caption, it is an empty placeholder, which can be filled with whatever
- you want, in this case an image. To have the button display a caption,
- a label is placed in it.}
- Since the edit and button component must be kept together, we use a
- \lstinline|TGtkHBox| as the 'Parent' component, and this component will be
- used to keep the edit and button control. There is no need to consider the
- image component, since it will be placed inside the button.
- Having decided that, the structure of the record for the instance of the
- component is more or less determined:
- \begin{lstlisting}{}
- Type
- PGtkFileEdit = ^TGtkFileEdit;
- TGtkFileEdit = Record
- Box : TGtkHBox;
- Edit : PGtkEntry;
- Button : PGtkButton;
- Image : PGtkPixmap;
- Dialog : PGtkFileSelection;
- end;
- \end{lstlisting}
- The first field of the record contains the parent record, as required
- by the OOP structure of GTK. The other fields are used to contain references
- to the other controls used. The \lstinline|Dialog| field will be filled with the
- reference to the file selection dialog which is created when the user clicks
- the button, at all other times it will contain a \lstinline|nil| pointer.
- Remark that the first field is a record, and all other fields are pointers.
- Since the fields of the record are 'Public' the user can access the button
- and edit components, and set or read their properties, and set additional
- signals. (e.g. a 'change' signal for the edit component)
- The class record for the {TGTKFileEdit} component should contain as a first
- field the parent class record, in this case \lstinline|TgtkHBoxClass|. Furthermore
- in the class record the default bitmap that will be displayed on the button
- will be stored. For this two fields are needed; one to keep the bitmap
- (\lstinline|DefaultPixmap|, and
- another one to keep a bitmask that is used to determine the transparant
- pixels in the bitmap (\lstinline|DefaultBitMap|):
- \begin{lstlisting}{}
- PGtkFileEditClass = ^TGtkFileEditClass;
- TGtkFileEditClass = Record
- Parent_Class : TgtkHBoxClass;
- DefaultPixmap : PGdkPixmap;
- DefaultBitMap : PGdkBitmap;
- end;
- \end{lstlisting}
- As usual, a pointer type is defined which points to the record. The fields
- of the class record will be filled in by the initialization code for our
- component, as will be shown below.
- A new widget must be registered with GTK by calling the
- \lstinline|gtk_type_unique| function. This function returns a unique
- identifier that can be used to refer to your new widget. This value
- must be accessible when creating new instances.
- Usually, this is done by registering the component with the GTK library
- inside a function which returns this unique ID to the user:
- The \lstinline|GtkFileEdit_get_type| function.
- When this function is called for the first time, it will register
- the new class with GTK, which will in turn supply a unique ID for the
- new component. This ID is returned and also stored, and will be returned
- the next times when the \lstinline|GTKFileEdit_get_type| function is called.
- The \lstinline|GTKFileEdit_get_type| function looks like this
- \lstinline|gtk\_type\_unique|:
- \begin{lstlisting}{}
- Function GtkFileEdit_get_type : Guint;cdecl;
- Const
- GtkFileEditInfo : TGtkTypeInfo =
- (type_name : 'GtkFileEdit';
- object_size : SizeOf(TGtkFileEdit);
- class_size : SizeOf(TGtkFileEditClass);
- class_init_func : TGtkClassInitFunc(@GtkFileEditClassInit);
- object_init_func : TGtkObjectInitFunc(@GtkFileEditInit);
- reserved_1 : Nil;
- reserved_2 : Nil;
- base_class_init_func : Nil
- );
- begin
- if (GtkFileEditType=0) then
- GtkFileEditType:=gtk_type_unique(gtk_hbox_get_type,@GtkFileEditInfo);
- Result:=GtkFileEditType;
- end;
- \end{lstlisting}
- Registering the new widget is done by passing a \lstinline|TGtkTypeInfo|
- record to \lstinline|gtk_type_unique|, where the fields of this record
- are filled with the following information:
- \begin{description}
- \item[type\_name] Contains the name of the type that must be registered.
- \item[object\_size] The size of the object record. GTK itself will allocate
- the memory when an new instance of the object is created, so it must know the
- size of the object.
- \item[class\_size] The size of the class object. Only one instance of this
- record will be created (by GTK)
- \item[class\_init\_func] The address of a function that will initialize the
- class record. This function accepts as a single arument a pointer to the
- class record to be initialized. This function will normally be called only
- once.
- \item[object\_init\_func] The address of a function that will initialize
- an instance of the object. The function must accept as a single argument
- a pointer to an instance of the object. This instance will be created by
- GTK. This function is called for each instance of the object.
- \end{description}
- The other three fields of the record are unfortunately not documented, so
- they are left blank.
- Along with the \lstinline|TGtkTypeInfo| record, the tyoe the type of the
- parent class (acquired with its own \lstinline|gtk_hbox_get_type|
- function) is passed to the \lstinline|gtk_type_unique| function.
- If a \lstinline|class_init_func| was specified when registering the new type,
- then GTK will call this method; it should initialize any class-specific
- data in the class record. In the case of the \lstinline|GTKFileEdit|, the bitmap
- which is used to fill the button is loaded:
- \begin{lstlisting}{}
- Procedure GtkFileEditClassInit (CObj : PGtkFileEditClass);cdecl;
-
- begin
- With Cobj^ do
- DefaultPixMap:=gdk_pixmap_create_from_xpm(Nil,@DefaultBitmap,
- Nil,'fileopen.xpm');
- end;
- \end{lstlisting}
- The \lstinline|gdk_pixmap_create_from_xpm| does 2 things: It loads a bitmap
- from the \textsf{fileopen.xpm} file and returns a PGdkPixmap pointer.
- At the same time it returns a pointer to a bitmask which designates the
- transparant regions of the bitmap.
- The result of this function is stored in the class record, so the bitmap
- is available when a new instance of the class is created.
- The \lstinline|GtkFileEditClassInit| and \lstinline|GtkFileEdit_get_type|
- functions are not called automatically by GTK. There are basically
- 2 solutions to do this as described below.
- The first one is specific to Free Pascal: the \lstinline|GtkFileEdit_get_type|
- can be called from the unit initialization code; This means that the objects
- are registered with GTK, even if they're not used. It also means that the
- GTK library must be initialized first, and hence should also be initialized
- in the initialization code of some unit.
- The second method is the method used in C: The function to create a new
- instance of the \lstinline|TGTKFileEdit| class, \lstinline|GTKFileEdit_new|,
- calls the \lstinline|get_type| function to register the class if needed,
- as follows:
- \begin{lstlisting}{}
- Function GtkFileEdit_new : PGtkWidget;cdecl;
- begin
- Result:=gtk_type_new(GtkFIleEdit_get_type)
- end;
- \end{lstlisting}
- When the first instance of the \lstinline|GTKFileEdit| widget is created, the
- call to \lstinline|GtkFileEdit_get_type| will register the widget class
- first. Subsequent calls to create a new instance will just use the stored
- value of the ID that identifies the \lstinline|GTKFileEdit| class.
- To be able to create an instance of the \lstinline|GTKFileEdit| class, one
- more procedure must be implemented, as can be seen from the class
- registration code: \lstinline|GtkFileEditInit|. This procedure will
- initialize (i.e. create) a new instance of the class; it should do
- whatever is necessary so the instance is ready for use.
- In the case of the \lstinline|GTKFileEdit| class, this simply means that
- all widgets of which the class is composed, must be created and placed to
- gether. This is shown in the following code:
- \begin{lstlisting}{}
- Procedure GtkFileEditInit (Obj : PGtkFileEdit);cdecl;
- Var
- PClass : PGtkFileEditClass;
- begin
- PClass:=PGtkFileEditClass(PGtkObject(Obj)^.klass);
- With Obj^ do
- begin
- Edit := PgtkEntry(gtk_entry_new);
- Button := PgtkButton(gtk_button_new);
- Image := PgtkPixMap(gtk_pixmap_new(PClass^.DefaultPixmap,
- PClass^.DefaultBitmap));
- gtk_container_add(PGtkContainer(Button),PGtkWidget(Image));
- gtk_box_pack_start(PgtkBox(Obj),PGtkWidget(Edit),True,True,0);
- gtk_box_pack_start(PgtkBox(Obj),PGtkWidget(Button),False,True,0);
- gtk_signal_connect(PgtkObject(Button),'clicked',
- TGtkSignalFunc(@GtkFileEditButtonClick),Obj);
- end;
- gtk_widget_show_all(PGtkWidget(Obj));
- end;
- \end{lstlisting}
- The code is self explanatory; the sub-widgets are created, and a reference
- to them is stored in the fields of our instance record. Note that the
- ancestor (a \lstinline|gtkHbox|) is not initialized, this has been done
- already by the OOP mechanism of GTK.
- After the objects are created, they are put together in the horizontal
- box, with the options chosen in such a way that the composed widget scales
- well if needed. The bitmap image is of course placed in the button.
- Lastly, a signal handler is added to the button, so that when it is clicked,
- we can take appropriate action (i.e. show a dialog to select a file).
- Note that as the \lstinline|Data| parameter for the signal, the reference
- to the \lstinline|GTKFileEdit| instance is passed.
- Now the class is ready to be created and shown. However, it doesn't do
- anything useful yet. The callback for the button click must still be used.
- The callback for the button must create a file selection dialog, show it,
- and when it has been closed by a click on the 'OK' button, it should set
- the text of the edit widget to the name of the selected file.
- In order to do this, some extra callbacks are needed, as can be seen in the
- following code:
- \begin{lstlisting}{}
- Procedure GtkFileEditButtonClick (Obj : PGtkObject; Data : PgtkFileEdit);cdecl;
- Var
- Dialog : PGtkFileSelection;
-
- begin
- Dialog := PGtkFileSelection(gtk_file_selection_new('Please select a file'));
- Data^.Dialog:=Dialog;
- gtk_signal_connect(PGTKObject(Dialog^.ok_button),'clicked',
- TGTKSignalFunc(@GtkStoreFileName),data);
- gtk_signal_connect_object (PGtkObject((Dialog)^.ok_button),'clicked',
- TGTKSIGNALFUNC (@gtk_widget_destroy), PgtkObject(Dialog));
- gtk_signal_connect_object (PGtkObject((Dialog)^.cancel_button),'clicked',
- TGTKSIGNALFUNC (@gtk_widget_destroy), PgtkObject(Dialog));
- gtk_widget_show(PgtkWidget(dialog));
- end;
- \end{lstlisting}
- The listing shows that an instance of the file selection dialog is created,
- and that its signals are set up so that when the user clicks the 'Cancel'
- button, the file selection dialog is simply destroyed, and when the 'OK'
- button is selected, first a callback is called in which the name of the
- selected file will be retrieved, and secondly the file selection dialog
- is destroyed.
- Two remarks concerning this code are in order:
- \begin{enumerate}
- \item The order in which the signals are connected to the 'clicked' event of
- the OK button is important, since they will be triggered in the order that
- they were connected.
- \item A reference to the dialog is stored in the \lstinline|GTKFileEdit|
- instance, and the reference to the \lstinline|GTKFileEdit| is passed as the
- \lstinline|Data| parameter of the signal.
- \end{enumerate}
- Finally, when the 'OK' button of the file selection dialog is clicked, the
- following callback is executed to store the filename in the edit widget of
- the \lstinline|GTKFileEdit| widget:
- \begin{lstlisting}{}
- Procedure GtkStoreFileName(Button : PgtkButton;
- TheRec : PGtkFileEdit); cdecl;
- begin
- With TheRec^ do
- begin
- gtk_entry_set_text(Edit,gtk_file_selection_get_filename(Dialog));
- Dialog:=Nil;
- end;
- end;
- \end{lstlisting}
- The callback also removes the reference to the file selection dialog. This
- could also have been done by explicitly setting a 'destroy' signal handler
- for the dialog, but since the dialog is destroyed after the 'OK' button is
- clicked, it is done here.
- Now the \lstinline|GTKFileEdit| is ready for use. It is possible to add
- some utility functions to the class, for instance one to get or set set
- the filename:
- \begin{lstlisting}{}
- Procedure GtkFileEdit_set_filename (Obj : PGtkFileEdit; FileName : String);cdecl;
- begin
- gtk_entry_set_text(Obj^.Edit,PChar(FileName));
- end;
- Function GtkFileEdit_get_filename (Obj : PGtkFileEdit) : String;cdecl;
- begin
- Result:=StrPas(gtk_entry_get_text(Obj^.Edit));
- end;
- \end{lstlisting}
- The widget can now be used like any other GTK widget:
- \begin{lstlisting}{}
- program ex1;
- {$mode objfpc}
- uses
- glib,gtk,fileedit;
- procedure destroy(widget : pGtkWidget ; data: pgpointer ); cdecl;
- begin
- gtk_main_quit();
- end;
- var
- window,
- fileed,
- box,
- Button : PgtkWidget;
-
- begin
- gtk_init (@argc, @argv);
- window := gtk_window_new (GTK_WINDOW_TOPLEVEL);
- fileed := gtkfileedit_new;
- gtk_container_set_border_width(GTK_CONTAINER(Window),5);
- box:=gtk_vbox_new(true,10);
- button:=gtk_button_new_with_label('Quit');
- gtk_box_pack_start(pgtkbox(box),PGtkWidget(fileed),False,False,0);
- gtk_box_pack_start(pgtkbox(box),pgtkWidget(button),True,False,0);
- gtk_container_add(GTK_Container(window),box);
- gtk_signal_connect (PGTKOBJECT (window), 'destroy',
- GTK_SIGNAL_FUNC (@destroy), NULL);
- gtk_signal_connect_object(PgtkObject(button),'clicked',
- GTK_SIGNAL_FUNC(@gtk_widget_destroy),
- PGTKOBJECT(window));
- gtk_widget_show_all (window);
- gtk_main ();
- end.
- \end{lstlisting}
- The result will look something like figure \ref{fig:fileedit}
- \begin{figure}[h]
- \begin{center}
- \caption{The GTKFileEdit in action}\label{fig:fileedit}
- \vspace{3mm}
- \epsfig{file=gtk2ex/ex1.png}
- \end{center}
- \end{figure}
- This widget is of course not finished, it can be enhanced in many ways:
- Some additional functionality would be to provide a filter for the dialog,
- or to set the directory initialiy displayed, provide a title for the dialog,
- set a different image on the button, verify that the selected file exists,
- and so on. these can be added in much the same way that the
- \lstinline|GTKFileEdit_get_filename| and
- \lstinline|GTKFileEdit_set_filename| were implemented.
- The fact that the parts making up the widget, such as the button and the edit
- widgets, are available as fields in the instance record makes it possible
- for the user to set additional properties, provided by these widgets. One
- could imagine the user connecting to the 'changed' signal of the edit, to
- check whether or not the filename being typed exists, and enabling or
- disabling other widgets accordingly. The usage of the file selection dialog
- itself also makes this clear.
- \section{A LED digit widget}
- The second widget to be presented in this article is a widget displaying
- a LED digit; such as found in many CD-Player displays or digital clocks.
- This will demonstrate how to draw a widget on the screen.
- A descendent which reacts to mouse clicks will also be created, which will
- demonstrate how to react to user events such as mouse clicks.
- A digit consists out of 7 segments, which can be either lit or not lit
- (dimmed). For each of the 10 digits (0..9) the state of each of the segments
- must be specified. For this we introduce some types and constants:
- \begin{lstlisting}{}
- Type
- TLEDSegment = (lsTop,lsCenter,lsBottom,
- lsLeftTop,lsRightTop,
- lsLeftBottom, lsRightBottom);
- TLedSegments = Array[TLedSegment] of boolean;
-
- Const
- DigitSegments : Array[0..9] of TLEDSegments =
- (
- (true,false,true,true,true,true,true), // 0
- (false,false,false,false,true,false,true), // 1
- (true,true,true,false,true,true,false), // 2
- (true,true,true,false,true,false,true), // 3
- (false,true,false,true,true,false,true), // 4
- (true,true,true,true,false,false,true), // 5
- (true,true,true,true,false,true,true), // 6
- (true,false,false,false,true,false,true), // 7
- (true,true,true,true,true,true,true), // 8
- (true,true,true,true,true,false,true) // 9
- );
- \end{lstlisting}
- The meaning of each of these types and the constant is obvious.
- Each segment is drawn between 2 points, located on a rectangle
- with 6 points, as shown in figure \ref{fig:corners}
- \begin{figure}
- \begin{center}
- \caption{Corners of a digit}\label{fig:corners}
- \epsfig{file=gtk2ex/corners.png}
- \end{center}
- \end{figure}
- Each segment is drawn between 2 corners: a start corner and an end corner.
- For each segment the start and end corner are stored in the
- \lstinline|SegmentCorners| array.
- \begin{lstlisting}{}
- Type
- TSegmentCorners = Array [1..2] of Byte;
- Const
- SegmentCorners : Array [TLEDSegment] of TSegmentCorners =
- (
- (1,2),
- (3,4),
- (5,6),
- (1,3),
- (2,4),
- (3,5),
- (4,6)
- );
- \end{lstlisting}
- These constants will facilitate the drawing of the digit later on.
- For the digit widget, 2 records must again be introduced; one for the class,
- and one for the instances of objects:
- \begin{lstlisting}{}
- Type
- TPoint = Record
- X,Y : gint;
- end;
- PGtkDigit = ^TGtkDigit;
- TGtkDigit = Record
- ParentWidget : TGtkWidget;
- borderwidth,
- digit : guint;
- Corners : Array [1..6] of TPoint;
- end;
-
- PGtkDigitClass = ^TGtkDigitClass;
- TGtkDigitClass = Record
- Parent_Class : TGtkWidgetClass;
- end;
- \end{lstlisting}
- The class record \lstinline|TGtkDigitClass| contains no extra information
- in this case, it has the parent class record as its ony field, as required
- bythe GTK object model. It could however be used to store some default values to
- be applied to new widgets, as was the case for the \lstinline|GTKFileEdit|
- widget.
- The object record contains three extra fields:
- \begin{description}
- \item[borderwidth] The distance between the segments and the border of
- the widget.
- \item[digit] The digit to be displayed.
- \item[Corners] this array contains the locations of each of the corners
- between which the segments will be drawn.
- \end{description}
- The \lstinline|GTKDigit| class must be registered with GTK, and this happens
- in the same manner as before:
- \begin{lstlisting}{}
- Function GtkDigit_get_type : Guint;cdecl;
- Const
- GtkDigitInfo : TGtkTypeInfo =
- (type_name : 'GtkDigit';
- object_size : SizeOf(TGtkDigit);
- class_size : SizeOf(TGtkDigitClass);
- class_init_func : TGtkClassInitFunc(@GtkDigitClassInit);
- object_init_func : TGtkObjectInitFunc(@GtkDigitInit);
- reserved_1 : Nil;
- reserved_2 : Nil;
- base_class_init_func : Nil
- );
- begin
- if (GtkDigitType=0) then
- GtkDigitType:=gtk_type_unique(gtk_widget_get_type,@GtkDigitInfo);
- Result:=GtkDigitType;
- end;
- \end{lstlisting}
- In the class initialization code, the real difference between this widget
- and the previous one becomes clear:
- \begin{lstlisting}{}
- Procedure GtkDigitClassInit (CObj : PGtkDigitClass);cdecl;
- begin
- With PGtkWidgetClass(Cobj)^ do
- begin
- size_request:=@GTKDigitSizeRequest;
- expose_event:=@GTKDigitExpose;
- size_allocate:=@GTKDigitSizeAllocate;
- end;
- end;
- \end{lstlisting}
- Here GTK is told that, in order to determine the size of the widget,
- it should first call \lstinline|GTKDigitSizeRequest|; this will provide
- GTK with an initial size for the object. After GTK has placed all widgets
- in the window, and has determined the sizes and positions it will allocate
- to each widget in the form, it will call \lstinline|GTKDigitSizeAllocate|
- to notify the \lstinline|GTKDigit| widget of the size it is being allocated.
- Finally, the \lstinline|expose_event| callback is set; this informs GTK that
- when a part of the widget should be drawn (because it is visible to the
- user), \lstinline|GTKDigitExpose| should be called. There are actually 2
- callbacks to draw a widget; one of them is
- the \lstinline|draw| function and the other is the (here used)
- \lstinline|expose| function. The \lstinline|draw| function of
- \lstinline|GTKWidget| just generates an expose event for the entire widget,
- and for the current widget this is enough. There are, however, cases where
- it may be necessary to differentiate between the two for optmization
- purposes.
- The object initialization function \lstinline|| simply initializes all fields to their
- default values:
- \begin{lstlisting}{}
- Procedure GtkDigitInit (Obj : PGtkDigit);cdecl;
- Var I : longint;
- begin
- gtk_widget_set_flags(pgtkWidget(obj),GTK_NO_WINDOW);
- With Obj^ do
- begin
- Digit:=0;
- BorderWidth:=2;
- For I:=1 to 6 do
- with Corners[i] do
- begin
- X:=0;
- Y:=0;
- end;
- end;
- end;
- \end{lstlisting}
- The interesting thing in the initialization function is the call to
- \lstinline|gtk_widget_set_flags|; this tells GTK that the
- \lstinline|GtkDigit| does not need its own window. Indeed, it will
- use its parent window to draw itself when needed.
- This also means that no extra resources must be allocated for the widget.
- The \lstinline|size_request| callback will in our case simply ask for some
- default size for the digit:
- \begin{lstlisting}{}
- Procedure GTKDigitSizeRequest (Widget : PGtkWidget;
- Request : PGtkRequisition);cdecl;
-
- Var BW : guint;
-
- begin
- With PGTKDigit(Widget)^ do
- BW:=BorderWidth;
- With Request^ do
- begin
- Width:=20+2*BW;
- Height:=40+2*BW;
- end;
- end;
- \end{lstlisting}
- usually, GTK will allocate a size at least equal to the size requested. It
- may however be more than this.
- When GTK has decided what the real size of the widget will be, the
- \lstinline|GTKDigitSizeAllocate| will be called:
- \begin{lstlisting}{}
- procedure GTKDigitSizeAllocate(Widget : PGTKWidget;
- Allocation : PGTKAllocation);cdecl;
-
- begin
- Widget^.Allocation:=Allocation^;
- SetDigitCorners(PGtkDigit(Widget),False);
- end;
- \end{lstlisting}
- This procedure first of all stores the allocated size in the widget, and
- then it calls \lstinline|SetDigitCorners| to calculate the positions of
- the corners of the segments; this is done as follows:
- \begin{lstlisting}{}
- Procedure SetDigitCorners(Digit : PGtkDigit; IgnoreOffset : Boolean);
- Var
- BW : guint;
- W,H,SX,SY : gint;
- i : longint;
- Widget : PGTKWidget;
-
- begin
- Widget:=PGTKWidget(Digit);
- BW:=Digit^.Borderwidth;
- If IgnoreOffset then
- begin
- SX:=0;
- SY:=0;
- end
- else
- begin
- SX:=Widget^.Allocation.x;
- SY:=Widget^.Allocation.y;
- end;
- W:=Widget^.Allocation.Width-2*BW;
- H:=(Widget^.Allocation.Height-2*BW) div 2;
- With PGTKDigit(Widget)^ do
- For I:=1 to 6 do
- begin
- Case I of
- 1,3,5 : Corners[i].X:=SX+BW;
- 2,4,6 : Corners[i].X:=SX+BW+W;
- end;
- Case I of
- 1,2 : Corners[i].Y:=SY+BW;
- 3,4 : Corners[i].Y:=SY+BW+H;
- 5,6 : Corners[i].Y:=SY+BW+2*H
- end;
- end;
- end;
- \end{lstlisting}
- Since the \lstinline|GTKDigit| will draw on its parents window, it must
- take into account the offset (x,y) of the allocated size. The reason that
- this is parametrized with the \lstinline|IgnoreOffset| parameter will become
- clear when the descendent widget is introduced.
- This function could be adapted to give e.g. a slight tilt to the digits.
- Remains to implement the \lstinline|expose_event| callback:
- \begin{lstlisting}{}
- Function GTKDigitExpose (Widget : PGTKWidget;
- ExposeEvent : PGDKEventExpose) : gint;cdecl;
- Var
- Segment : TLedSegment;
-
- begin
- With PGTKDigit(Widget)^ do
- For Segment:=lsTop to lsRightBottom do
- if DigitSegments[Digit][Segment] then
- gdk_draw_line(widget^.window,
- PgtkStyle(widget^.thestyle)^.fg_gc[widget^.state],
- Corners[SegmentCorners[Segment][1]].X,
- Corners[SegmentCorners[Segment][1]].Y,
- Corners[SegmentCorners[Segment][2]].X,
- Corners[SegmentCorners[Segment][2]].Y
- )
- else
- gdk_draw_line(widget^.window,
- PgtkStyle(widget^.thestyle)^.bg_gc[widget^.state],
- Corners[SegmentCorners[Segment][1]].X,
- Corners[SegmentCorners[Segment][1]].Y,
- Corners[SegmentCorners[Segment][2]].X,
- Corners[SegmentCorners[Segment][2]].Y
- );
-
- end;
- \end{lstlisting}
- Here the need for the types and constants, introduced in the
- beginning of this section becomes obvious; without them, a huge
- case statement would be needed to draw all needed segments.
- Note that when a segment of our digit is not 'lit', it is drawn in the
- background color. When the digit to be displayed changes, the segments
- that are no longer lit, must be 'dimmed' again.
- Finally we provide 2 methods to get and set the digit to be dislayed:
- \begin{lstlisting}{}
- Procedure GtkDigit_set_digit (Obj : PGtkDigit; Digit : guint);cdecl;
- begin
- if Digit in [0..9] then
- begin
- Obj^.Digit:=Digit;
- gtk_widget_draw(PGTKWidget(Obj),Nil);
- end;
- end;
- Function GtkDigit_get_digit (Obj : PGtkDigit) : guint;cdecl;
- begin
- Result:=Obj^.Digit;
- end;
- \end{lstlisting}
- Obviously, when setting the digit to be displayed, the widget must be
- redrawn, or the display would not change till the next expose event.
- Calling \lstinline|gtk_widget_draw| ensures that the digit will be displayed
- correctly.
- Now the widget is ready for use; it can be created and put on a window
- in the same manner as the \lstinline|GTKFileEdit| control; the code will
- not be shown, but is available separately.
- The result is shown in figure \ref{fig:ex2}.
- \begin{figure}
- \begin{center}
- \caption{The GTKDigit widget in action.}\label{fig:ex2}
- \epsfig{file=gtk2ex/ex2.png}
- \end{center}
- \end{figure}
- The widget can be improved in many ways. The segments can be tilted, a
- bigger width can be used; the can have rounded edges and so on.
- The widget as presented here doesn't react on user events; it has no way
- of doing that, since it doesn't have an own window; Therefore a descendent
- is made which creates its own window, and which will react on mouse clicks;
- this widget will be called \lstinline|GTKActiveDigit|.
- The lstinline|GTKActiveDigit| widget is a descendent from its inactive
- counterpart. Therefore the class and object records will be (almost) empty:
- \begin{lstlisting}{}
- Type
- PGtkActiveDigit = ^TGtkActiveDigit;
- TGtkActiveDigit = Record
- ParentWidget : TGtkDigit;
- Button : guint8;
- end;
-
- PGtkActiveDigitClass = ^TGtkActiveDigitClass;
- TGtkActiveDigitClass = Record
- Parent_Class : TGtkDigitClass;
- end;
- \end{lstlisting}
- The \lstinline|Button| field is used to store which button was used to click
- on the digit.
- The registration of the new widget is similar to the one for
- \lstinline|GTKDigit|, and doesn't need more explanation:
- \begin{lstlisting}{}
- Const
- GtkActiveDigitType : guint = 0;
- Function GtkActiveDigit_get_type : Guint;cdecl;
- Const
- GtkActiveDigitInfo : TGtkTypeInfo =
- (type_name : 'GtkActiveDigit';
- object_size : SizeOf(TGtkActiveDigit);
- class_size : SizeOf(TGtkActiveDigitClass);
- class_init_func : TGtkClassInitFunc(@GtkActiveDigitClassInit);
- object_init_func : TGtkObjectInitFunc(@GtkActiveDigitInit);
- reserved_1 : Nil;
- reserved_2 : Nil;
- base_class_init_func : Nil
- );
- begin
- if (GtkActiveDigitType=0) then
- GtkActiveDigitType:=gtk_type_unique(gtkdigit_get_type,@GtkActiveDigitInfo);
- Result:=GtkActiveDigitType;
- end;
- Function GtkActiveDigit_new : PGtkWidget;cdecl;
- begin
- Result:=gtk_type_new(GtkActiveDigit_get_type)
- end;
- \end{lstlisting}
- The first real difference is in the class initialization routine:
- \begin{lstlisting}{}
- Procedure GtkActiveDigitClassInit (CObj : PGtkActiveDigitClass);cdecl;
- begin
- With PGtkWidgetClass(Cobj)^ do
- begin
- realize := @GtkActiveDigitRealize;
- size_allocate := @GtkActiveDigitSizeAllocate;
- button_press_event:=@GtkActiveDigitButtonPress;
- button_release_event:=@GtkActiveDigitButtonRelease;
- end;
- end;
- \end{lstlisting}
- The \lstinline|realize| and \lstinline|size_allocate| of the parent widget
- \lstinline|GTKDigit| are overriden here. Also 2 events callbacks are
- assigned in order to react on mouse clicks.
- The object initialization function must undo some work that was done
- ba the parent's initialization function:
- \begin{lstlisting}{}
- Procedure GtkActiveDigitInit (Obj : PGtkActiveDigit);cdecl;
- begin
- gtk_widget_unset_flags(pgtkWidget(obj),GTK_NO_WINDOW);
- With Obj^ do
- Button:=0;
- end;
- \end{lstlisting}
- This is necessary, because the \lstinline|GTKActiveDigit| will create it's
- own window.
- For this widget, the \lstinline|realize| callback must do a little more
- work. It must create a window on which the digit will be drawn. The window
- is created with some default settings, and the event mask for the window
- is set such that the window will respond to mouse clicks:
- \begin{lstlisting}{}
- Procedure GtkActiveDigitRealize(widget : PgtkWidget);cdecl;
- Var
- attr : TGDKWindowAttr;
- Mask : gint;
-
- begin
- GTK_WIDGET_SET_FLAGS(widget,GTK_REALIZED);
- With Attr do
- begin
- x := widget^.allocation.x;
- y := widget^.allocation.y;
- width:=widget^.allocation.width;
- height:=widget^.allocation.height;
- wclass:=GDK_INPUT_OUTPUT;
- window_type:=gdk_window_child;
- event_mask:=gtk_widget_get_events(widget) or GDK_EXPOSURE_MASK or
- GDK_BUTTON_PRESS_MASK OR GDK_BUTTON_RELEASE_MASK;
- visual:=gtk_widget_get_visual(widget);
- colormap:=gtk_widget_get_colormap(widget);
- end;
- Mask:=GDK_WA_X or GDK_WA_Y or GDK_WA_VISUAL or GDK_WA_COLORMAP;
- widget^.Window:=gdk_window_new(widget^.parent^.window,@attr,mask);
- widget^.thestyle:=gtk_style_attach(widget^.thestyle,widget^.window);
- gdk_window_set_user_data(widget^.window,widget);
- gtk_style_set_background(widget^.thestyle,widget^.window,GTK_STATE_ACTIVE);
- end;
- \end{lstlisting}
- After the window was created, its userdata is set to the widget. This
- ensures that the events which occur in the window are passed on to our
- widget by GTK. Finally the background of the window is set to some
- other style than the default style.
- The size allocation event should in principle do the same as that for the
- \lstinline|GTKDigit| widget, with the exeption that the calculation of the
- corners for the segments must now not be done relative to the parent window:
- \begin{lstlisting}{}
- procedure GTKActiveDigitSizeAllocate(Widget : PGTKWidget;
- Allocation : PGTKAllocation);cdecl;
- begin
- Widget^.allocation:=Allocation^;
- if GTK_WIDGET_REALIZED(widget) then
- gdk_window_move_resize(widget^.window,
- Allocation^.x,
- Allocation^.y,
- Allocation^.width,
- Allocation^.height);
- SetDigitCorners(PGTKDigit(Widget),True);
- end;
- \end{lstlisting}
- This explains the need for the \lstinline|IgnoreOffset| parameter in the
- \lstinline|SetDigitCorners| function.
- All that is left is to implement the mouse click events:
- \begin{lstlisting}{}
- Function GtkActiveDigitButtonPress(Widget: PGtKWidget;
- Event : PGdkEventButton) : gint;cdecl;
- begin
- PGTKActiveDigit(Widget)^.Button:=Event^.Button;
- end;
- Function GtkActiveDigitButtonRelease(Widget: PGtKWidget;
- Event : PGdkEventButton) : gint;cdecl;
- Var
- Digit : PGtkDigit;
- D : guint;
- begin
- Digit:=PGTKDigit(Widget);
- D:=gtkdigit_get_digit(Digit);
- If PGTKActiveDigit(Digit)^.Button=Event^.Button then
- begin
- If Event^.Button=1 then
- GTKDigit_set_digit(Digit,D+1)
- else if Event^.Button=3 then
- GTKDigit_set_digit(Digit,D-1)
- else
- GTKDigit_set_digit(Digit,0);
- end;
- PGTKActiveDigit(Digit)^.Button:=0;
- end;
- \end{lstlisting}
- As can be seen, the digit will be incremented when the left mouse button
- is clicked. The digit is decremented when the right button is clicked.
- On systems with 3 mouse buttons, a click on the middle mouse button will
- reset the digit to 0.
- After all this, the widget is ready for use, and should look more or less
- like the one in figure \ref{fig:ex3}.
- \begin{figure}[h]
- \begin{center}
- \caption{The GTKActiveDigit in action.}\label{fig:ex3}
- \epsfig{file=gtk2ex/ex3.png}
- \end{center}
- \end{figure}
- The widgets presented here are not complete; many improvements can be made,
- but their main purpose was to demonstrate that implementing some new widgets
- is very easy and can be achieved with little effort; what is more, the OOP
- structure of GTK is very suitable for the implementation of small
- enhancements to existing components, as was shown with the last widget
- presented.
- \end{document}
|