+ First part of GTK article

This commit is contained in:
michael 2001-07-09 22:36:59 +00:00
parent 3a1f29ce31
commit ecd4fbace8

368
docs/gtk5.tex Normal file
View File

@ -0,0 +1,368 @@
\documentclass[10pt]{article}
\usepackage{a4}
\usepackage{epsfig}
\usepackage{listings}
\usepackage{tabularx}
\lstset{language=Delphi}%
\lstset{basicstyle=\sffamily\small}%
\lstset{commentstyle=\itshape}%
\lstset{keywordstyle=\bfseries}%
%\lstset{blankstring=true}%
\newcommand{\file}[1]{\textsf{#1}}
\newcommand{\var}[1]{\texttt{#1}}
\usepackage[pdftex]{hyperref}
\newif\ifpdf
\ifx\pdfoutput\undefined
\pdffalse
\else
\pdfoutput=1
\pdftrue
\fi
\begin{document}
\title{Programming GTK in Free Pascal: Using GDK}
\author{Florian Kl\"ampfl\\and\\Micha\"el Van Canneyt}
\date{July 2001}
\maketitle
\section{Introduction}
In this article, some of the graphics primitives from the gdk toolkit will
be demonstrated in a small game - breakout.
The GTK toolkit widgets are built upon the GDK: Graphics Drawing Kit.
The GDK does not know anything about buttons, menus checkboxes and so on.
Instead, it knows how to create windows, draw on them, handle mouse clicks
and keypresses. This functionality is used by the GTK widget set to create
usable widgets.
Sometimes, the widgets offered by GTK are not enough, and one has to fall
back on the graphics functionality of the GDK to be able to do what is
needed for a program.
Fortunately, it is not necessary to create a GTK window and handle all
GDK events to be able to use the GDK functions. The GTK widget set has a
special widget, which can be used to draw upon. This widget is the
\var{TGtkDrawingArea} widget. The use of the \var{TGtkDrawingArea} is what
will be explained below.
The GDK graphics functions will be explained using a simple arcade game,
to demonstrate that the speed of the GDK is sufficient for the creation of
simple games. The breakout game is chosen because it is conceptually simple,
requires moving graphics and can be extended in many ways.
\section{The drawing area widget}
The drawing area widget (\var{TGTKDrawingArea}) is a simple widget which
just provides a drawing window. It responds to all widget events, and adds
additionally the 'configure\_event', which is called when the widget is
realized (i.e. when the window handle is created.)
The widget has only 1 method: \var{gtk\_drawing\_area\_size}, which sets
the size of the drawing area. It is defined as follows:
\begin{verbatim}
procedure gtk_drawing_area_size(Area:PGtkDrawingArea; width:gint;height:gint)
\end{verbatim}
The arguments to this function are self-explaining.
To use the drawing area widget, one should respond to the 'expose\_event'.
This event is triggered whenever a part of the window that was invisible,
becomes visible. The event handler gets an \var{PGDKEventExpose} parameter,
which describes which area was exposed. This can be used for optimization
purposes.
To draw in the drawing area widget, the \var{Window} field of the
\var{TGTKWidget} parent can be used. This is of type \var{TGDKWindow}.
All drawing functions require a parameter of type \var{TGdkDrawable}
which can be one of the \var{TGdkWindow} or \var{TGdkPixMap} types.
\section{Graphics contexts}
Most drawing functions do not only require a drawable to draw on, they also
require a {\em Graphics Context}. A graphics context is a series of
parameters that determine how lines are drawn, what colors and font are
used etc.
The Graphics Context is an opaque record, and its members cannot be
accessed. The relevant parameters are set in a \var{TGdkGCValues} record,
which is defined as follows:
\begin{verbatim}
foreground : TGdkColor;
background : TGdkColor;
font : PGdkFont;
thefunction : TGdkfunction;
fill : TGdkFill;
tile : PGdkPixmap;
stipple : PGdkPixmap;
clip_mask : PGdkPixmap;
subwindow_mode : TGdkSubwindowMode;
ts_x_origin : gint;
ts_y_origin : gint;
clip_x_origin : gint;
clip_y_origin : gint;
graphics_exposures : gint;
line_width : gint;
line_style : TGdkLineStyle;
cap_style : TGdkCapStyle;
join_style : TGdkJoinStyle;
\end{verbatim}
The \var{ForeGround} and \var{Background} parameters determine the foreground
and background colors. \var{Font} is the default font. The \var{Fill} field
describes how areas are filled. It can be one of the following:
\begin{description}
\item[GDK\_SOLID] fill with the foreground color.
\item[GDK\_TILED] Use the pixmap specified in \var{Tile} to fill the area.
\item[GDK\_STIPPLED] Use the pixmap specified in \var{Stipple} to draw
pixels that are in the bitmap in the foreground color. Other bits are not
drawn.
\item[GDK\_OPAQUE\_STIPPLED] Same as \var{GDK\_STIPPLED} except that bits
not in the pixmap will be drawn in the background color.
\end{description}
The \var{clip\_bitmap} is used to define a clip area. The
\var{ts\_x\_origin} and \var{ts\_y\_origin} define the stipple or tile
origin. The \var{clip\_x\_origin} and \var{clip\_y\_origin} fields define
the origin of the clipping region.
\var{LineWidth} is the linewidth used when drawing lines. \var{Line\_Style}
determines how dashed lines are drawn. It can have one of the following
values:
\begin{description}
\item[GDK\_LINE\_SOLID] Lines are drawn solid.
\item[GDK\_LINE\_ON\_OFF\_DASH] Even segments are drawn, odd segments are
not.
\item[GDK\_LINE\_DOUBLE\_DASH] Even segments are drawn, Odd segments are
drawn in the background color if the fill style is \var{GDK\_SOLID}.
\end{description}
\var{cap\_style} determines how line ends are drawn. The following values are
defined:
\begin{description}
\item[GDK\_CAP\_BUTT] The lines are drawn with square ends.
\item[GDK\_CAP\_NOT\_LAST] Idem as \var{GDK\_CAP\_BUTT}, only for zero-width
lines, the last dot is not drawn.
\item[GDK\_CAP\_ROUND] The end of the line is a semicircle. The circle has
diameter equal to the linewidth, and the center is the endpoint of the line.
\item[GDK\_CAP\_PROJECTING] Idem as [GDK\_CAP\_BUTT], only the line extends
half the linewidth outside the endpoint.
\end{description}
The effect of these elements will be shown in the next section.
To set a color, a \var{TGDkColor} record must be allocated. Colors are
specified using a RGB value. Unfortunately, not all graphics cards can
show all colors. In order to find out which screen color corresponds
to the RGB-specified color, the GDK uses a colormap, and allocates a
color that matches the closest to the specified color values.
When allocating a new color, the colormap should be specified.
A colormap can be obtained from a \var{TGTKWidget} object using the
\var{gtk\_widget\_get\_colormap} function; A color can then be allocated
using the \var{gdk\_colormap\_alloc\_color} function:
\begin{verbatim}
function gdk_colormap_alloc_color(colormap:PGdkColormap;
color:PGdkColor;
writeable:gboolean;
best_match:gboolean):gboolean;
\end{verbatim}
The \var{writeable} parameter specifies whether changes in the
\var{color} using \var{gdk\_color\_change} are allowed.
\var{best\_match} specifies whether a best match should be attempted
on existing colors or an exact value is required.
The function returns \var{True} if the allocation succeeded,
\var{False} otherwise.
\section{Drawing primitives}
Using the properties introduced in the previous section, drawing can be
attempted using the drawing primitives offered by GDK. GDK offers drawing
functions for points, lines, segments, rectangles, polygons, circles, text
and bitmaps.
All functions accept as the first two parameters a \var{PGDKdrawable}, which
can be a \var{TGDKWindow} or \var{TGDkPixmap}, and a \var{PGdkGC}, a pointer
to a graphics context. These parameters are omitted from the following
declarations:
\begin{verbatim}
procedure gdk_draw_point(x,y:gint);
procedure gdk_draw_line(x1,y1,x2,y2:gint);
procedure gdk_draw_rectangle(filled,x,y,width,height:gint);
\end{verbatim}
The meaning of the parameters for these functions is obvious.
For the rectangle, care must be taken. If \var{Filled} is false (-1) then
the drawn rectangle is actually \var{Width+1}, \var{Height+1}. If it is
filled, then the width are height are as specified.
The following functions can be used to draw a series of lines:
\begin{verbatim}
procedure gdk_draw_polygon(filled:gint;points:PGdkPoint; npoints:gint);
procedure gdk_draw_segments(segs:PGdkSegment; nsegs:gint);
procedure gdk_draw_lines(points:PGdkPoint; npoints:gint);
\end{verbatim}
The \var{gdk\_draw\_polygon} polygon takes a series of dots and connects
them using lines, optionally filling them. A \var{TGDKPoint} record contains
2 fields \var{X,Y} which specify the location of a point. If needed, the
first and last points are also connected using a line.
The \var{gdk\_draw\_lines} does the same, only it cannot be filled, and it
will not connect the first and last points.
The \var{gdk\_draw\_segments} requires a series of \var{TGDKSegment}
records. These consist of 4 fields: \var{x1,y1,x2,y2}, each describing
the start and end point of a line segment. The segments will not be
connected.
The \var{gdk\_draw\_arc} can be used to draw a circle or a segment of
the circle, or an ellipse.
\begin{verbatim}
procedure gdk_draw_arc(filled,x,y,width,height,angle1,angle2 : gint);
\end{verbatim}
The \var{x,y, width} and \var{height} parameters describe a bounding
rectangle for the circle. The angles describe the start and extending
angle of the segment to be drawn: The circle segment starts at angle
\var{angle1} and ends at \var{angle1+angle2}. These angles are specified
in 1/64ths of a degree and are measured counterclockwise, starting at
the 3 o'clock direction. A circle segment drawn from 90 to 270 degrees
should therefore have as angles 90*64=5760 and 270*64=17280.
If filled is \var{True} (-1), then the segment will be connected to
the circle centre, and filled, in effect drawing a pie-slice.
Finally, for the \var{gdk\_draw\_string} function, the graphics context comes
before the graphics context:
\begin{verbatim}
procedure gdk_draw_string(drawable:PGdkDrawable; font:PGdkFont; gc:PGdkGC;
x:gint; y:gint; thestring:Pgchar);
\end{verbatim}
The meaning of the parameters for this functions should be obvious.
All this is demonstrated in the following program:
\begin{lstlisting}{}
program graphics;
{$mode objfpc}
{$h+}
uses glib,gdk,gtk,sysutils;
var
window,
area : PGtkWidget;
Function CloseApp(widget : PGtkWidget ;
event : PGdkEvent;
data : gpointer) : boolean; cdecl;
Begin
gtk_main_quit();
close_application := false;
End;
Function AllocateColor(R,G,B : Integer;
Widget : PGtkWidget) : PGdkColor;
begin
Result:=New(PgdkColor);
With Result^ do
begin
Pixel:=0;
Red:=R;
Blue:=B;
Green:=G;
end;
gdk_colormap_alloc_color(gtk_widget_get_colormap(Widget),
Result,true,False);
end;
function Exposed(Widget: PGtkWidget;
event : PGdkEventExpose;
Data : gpointer) : Integer; cdecl;
Const
Triangle : Array[1..4] of TgdkPoint =
((X:10;Y:195),
(X:110;Y:195),
(X:55;Y:145),
(X:10;Y:195));
LineStyles : Array[1..5] of TgdkLineStyle =
(GDK_LINE_SOLID, GDK_LINE_ON_OFF_DASH,
GDK_LINE_DOUBLE_DASH, GDK_LINE_ON_OFF_DASH,
GDK_LINE_SOLID);
capstyles : Array[1..5] of TgdkCapStyle =
(GDK_CAP_ROUND,GDK_CAP_NOT_LAST, GDK_CAP_BUTT,
GDK_CAP_PROJECTING, GDK_CAP_NOT_LAST);
Var
SegTriangle : Array[1..3] of TgdkSegment;
Win : pgdkWindow;
gc : PgdkGC;
i,seg : Integer;
font : PgdkFont;
Angle1,Angle2 : Longint;
begin
gc:=gdk_gc_new(widget^.Window);
Win:=widget^.window;
With Event^.area do
gdk_window_clear_area (win,x,y,width,height);
gdk_gc_set_foreground(gc,allocatecolor(0,0,0,Widget));
gdk_draw_rectangle(win,gc,0,5,5,590,390);
gdk_gc_set_foreground(gc,allocatecolor(0,0,$ffff,Widget));
for I:=10 to 50 do
gdk_draw_point(win,gc,I*10,100);
gdk_gc_set_foreground(gc,allocatecolor($ffff,0,0,Widget));
for I:=10 to 50 do
begin
gdk_gc_set_line_attributes(gc,6,LineStyles[i div 10],CapStyles[i div 10],GDK_JOIN_MITER);
gdk_draw_line(win,gc,I*10,20,I*10,90)
end;
gdk_gc_set_line_attributes(gc,1,GDK_LINE_SOLID,GDK_CAP_BUTT,GDK_JOIN_MITER);
gdk_gc_set_foreground(gc,allocatecolor($ffff,0,$ffff,Widget));
seg:=(360 div 20) * 64;
For I:=1 to 20 do
gdk_draw_arc(win,gc,0,220-I*4,200-i*4,8*i,8*i,i*seg,seg*19);
For I:=1 to 20 do
gdk_draw_arc(win,gc,-1,380-I*4,200-i*4,8*i,8*i,(i-1)*seg,seg);
gdk_gc_set_foreground(gc,allocatecolor(0,$ffff,$ffff,Widget));
gdk_draw_polygon(win,gc,0,@triangle[1],4);
For I:=1 to 4 do
Triangle[i].Y:=400-Triangle[i].y;
gdk_draw_polygon(win,gc,-1,@triangle[1],4);
gdk_gc_set_foreground(gc,allocatecolor(0,$ffff,0,Widget));
For I:=1 to 4 do
Triangle[i].X:=600-Triangle[i].x;
gdk_draw_lines(win,gc,@triangle[1],4);
For I:=1 to 3 do
begin
SegTriangle[i].X1:=Triangle[i].X;
SegTriangle[i].Y1:=400-Triangle[i].Y;
SegTriangle[i].X2:=Triangle[i+1].X;
SegTriangle[i].Y2:=400-Triangle[i+1].Y;
end;
gdk_draw_segments(win,gc,@segtriangle[1],3);
font:=gdk_font_load('-*-helvetica-bold-r-normal--*-120-*-*-*-*-iso8859-1');
gdk_gc_set_foreground(gc,allocatecolor($ffff,$ffff,0,Widget));
For I:=1 to 4 do
gdk_draw_string(win,font,gc,I*100,300,Pchar(format('String %d',[i])));
result:=0;
end;
Begin
// Initialize GTK and create the main window
gtk_init( @argc, @argv );
window := gtk_window_new( GTK_WINDOW_TOPLEVEL );
gtk_window_set_policy(PgtkWindow(Window),0,0,1);
gtk_signal_connect (GTK_OBJECT (window), 'delete_event',
GTK_SIGNAL_FUNC( @CloseApp ), NIL);
gtk_container_set_border_width (GTK_CONTAINER (window), 10);
area := gtk_drawing_area_new();
gtk_container_add( GTK_CONTAINER(window), Area);
gtk_signal_connect (GTK_OBJECT (area),'expose_event',
GTK_SIGNAL_FUNC(@Exposed),Nil);
gtk_drawing_area_size (PGTKDRAWINGAREA(area),600,400);
gtk_widget_show_all( window );
gtk_main();
end.
\end{lstlisting}
The main program startsby creating a main window,
and adding a \var{TGTKDrawingArea} to it. It then connects 2 event handlers,
one to stop the application if the window is closed (\var{CloseApp}),
the other to draw the \var{TGTKDrawingArea} when it is exposed
(\var{Exposed}). This latter contains the actual drawing routines, and is
pretty self-explaining.
Note that the allocated colors are not freed again, so this program does
contain a memory leak.
\section{Animation}
\end{document}