diff --git a/docs/gtk5.tex b/docs/gtk5.tex new file mode 100644 index 0000000000..cf628653d3 --- /dev/null +++ b/docs/gtk5.tex @@ -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} \ No newline at end of file