17.21 DRAWING SHAPES, TEXT, AND IMAGES IN GNOMEGTK


17.21 DRAWING SHAPES, TEXT, AND IMAGES IN GNOME/GTK+

Graphics programming in GNOME/GTK+ is done with the help of the GnomeCanvas widget. This widget knows how to display the items that are supplied to its container widget, which is an object of type GnomeCanvasGroup. One first constructs a canvas object and then retrieves its container widget by

 GtkWidget* canvas;                                                   //(A) GnomeCanvasGroup* rootGroup;                                         //(B) . . . . . . canvas = gnome_canvas_new();                                         //(C) rootGroup = gnome_canvas_root( GNOME_CANVAS( canvas ) );             //(D) 

where rootGroup is the container widget for the canvas object constructed in line (C). A graphics object, such as a shape, can then be added to the container widget by

 item = gnome_canvas_item_new( rootGroup,                             //(E1)                     gnome_canvas_rect_get_type(),                    //(E2)                     //now specify values for the                     // attributes of rectangular                     // shape      ); 

where the invocation gnome_canvas_item_new in line (E1) causes a graphics item to be added to the rootGroup, the type of the item determined by the second argument (in line (E2)), and its various properties—such as the location, the fill pattern, the type of border—by the rest of the arguments.[32]

Typically, after creating a gnomeCanvas object, you would set its width and height and declare the location of the origin and the size of the viewable area through invocations such as

     . . .     canvas = gnome_canvas_new();     gtk_widget_set_usize( canvas, WIDTH, HEIGHT );                   //(F)     gnome_canvas_set_scroll_region( GNOME_CANVAS( canvas ),          //(G1)                                         0.0,                         //(G2)                                         0.0,                         //(G3)                                         WIDTH,                       //(G4)                                         HEIGHT );                    //(G5)     . . . 

In this case, the viewable area, as set by gnome_canvas_set_scroll_region, in line (G1) fills the entire canvas. However, in general, by making the viewable area smaller than the canvas, one can create and manipulate a large graphics object even though only a portion of the object can be seen at a time. Through the arguments in lines (G2) through (G5), the above declaration also sets the origin at the upper left corner of the viewable area; the location coordinates specified for the graphics items are with respect to this origin. Sometimes it is more convenient to place the origin at the center of the viewable area, which can be done by the following statements:

      . . .      canvas = gnome_canvas_new();      gtk_widget_set_usize( canvas, WIDTH, HEIGHT );      gnome_canvas_set_scroll_region( GNOME_CANVAS( canvas ),                                       -WIDTH/2,                                       -HEIGHT/2,                                       WIDTH/2,                                       HEIGHT/2 );      . . . 

The following example places on a canvas a filled rectangle in line (H1), an ellipse in line (I1), a hollow rectangle in line (J), a text string in line (K1), an image in line (L), and a rotated polygon in line (N1). The statements that create each of these items are mostly self-explaining. The four coordinate values supplied to the rectangle item in lines (H2) through (H6) specify the coordinates of the top left corner and the bottom right corner. The four coordinates values supplied to the ellipse item in lines (I2) through (I6) do the same for the minimum bounding rectangle of the ellipse. An unfilled shape can be obtained by leaving out the specification of the fill_color field, as we do for the rectangle in line (J).

For the text string item in line (K1), the exact positioning of the string in relation to the coordinates specified by the "x" and the "y" fields in lines (K6) and (K7) is controlled by the symbolic constant value of the "anchor" field in line (K9). For the value shown, GTK_ANCHOR_W, the west end (meaning, the left end) of the string will coincide with the pixel coordinates (20, 100).[33]

An image is displayed in a canvas by first creating a GdkImlibImage object from the pixel data file. In line (L) of the program, we refer to an XPM image[34] which consists of a string array named allthatjazz. This array is supplied by the file allthatjazz.xpm that is pulled into the program through an include declaration at the beginning. The image object thus created can then be rendered by a call shown in lines (M1) through (M9).

As was the case with the text item, the exact positioning of a rendered image in relation to the coordinates supplied to the fields "x" and "y" is controlled by the value of the field "anchor". With the value GTK_ANCHOR_W, the left edge of the image will have its middle pixel at the coordinates (200, 200).

The last item placed on the canvas consists of a polygon item created in line (N1). A general polygon is supplied as an array of GnomeCanvasPoints to the "points" field. As shown in line (N3), this array is constructed by a call to the function makePolygon that is defined in line (S).

We have used the polygon item to also illustrate how affine transformations can be specified in GNOME/GTK+. As was the case with Qt, a 3 × 3 affine transformation matrix is specified by constructing an array of its six elements m11, m12, m21, m22, dx, and dy, where m11 and m22 control scale; m11, m12, m21 and m22 together control rotation provided certain constraints are satisfied by the four numbers; and where dx and dy control the translation of the origin. The easiest way to specify an affine transform in GNOME/GTK+ is to first declare an array of six doubles and to then specify the rotations, translations, or scale, as needed:

      double affine[6];      ...      item = ....;      art_affine_rotate( affine, 45.0 );      gnome_canvas_item_affine_relative( item, affine );      art_affine_translate( affine, 400, 250 );      gnome_canvas_item_affine_relative( item, affine ); 

These statements would cause the graphical item item to rotate by 45 degrees clockwise around the origin of the viewable area and to then get translated by 400 pixels horizontally and 250 pixels vertically. The statements in lines (O) through (R) of the program create this rotation and translation for the polygon item of line (N1).

 
//RenderGraphics.c #include <gnome.h> #include "allthatjazz.xpm" gint eventDestroy( GtkWidget* widget, GdkEvent* event, gpointer data ); GnomeCanvasPoints* makePolygon( gint h, gint v ); #define HORIZ 100 #define VERT 50 #define WIDTH 550 #define HEIGHT 400 double affine[6]; int main( int argc, char* argv[] ) { GtkWidget* app; GtkWidget* canvas; GnomeCanvasGroup* rootGroup; GnomeCanvasItem* item; GdkImlibImage* image; gnome_init( "Graphics Demo", "1.0", argc, argv ); app = gnome_app_new( "canvasorder", "Gnome Canvas Order" ); gtk_signal_connect( GTK_OBJECT( app ), "destroy" GTK_SIGNAL_FUNC( eventDestroy ), NULL ); canvas = gnome_canvas_new(); gtk_widget_set_usize( canvas, WIDTH, HEIGHT ); gnome_canvas_set_scroll_region( GNOME_CANVAS( canvas ), 0.0, 0.0, WIDTH, HEIGHT ); rootGroup = gnome_canvas_root( GNOME_CANVAS( canvas ) ); gnome_app_set_contents( GNOME_APP( app ), canvas ); item = gnome_canvas_item_new( rootGroup, //(H1) gnome_canv_as_rect_get_type(), //(H2) "x1", (double) ( 20 ), //(H3) "y1", (double) ( 20 ), //(H4) "x2", (double) ( 20 + HORIZ ), //(H5) "y2", (double) ( 20 + VERT ), //(H6) "fill_color", "red", //(H7) "outline_color", "black", //(H8) NULL); //(H9) item = gnome_canvas_item_new( rootGroup, //(I1) grome_canvas_ellipse_get_type(), //(I2) "x1", (double) ( 220 ), //(I3) "y1", (double) ( 20 ), //(I4) "x2", (double) ( 220 + HORIZ ), //(I5) "y2", (double) ( 20 + VERT ), //(I6) "fill_color", "blue", //(I7) "outline_color","black", //(I8) NULL ); //(I9) item = gnome_canvas_item_new( rootGroup, //(J) gnome_canvas_rect_get_type(), "x1", (double) 400, "y1", (double) 20, "x2", (double) 400 + HORIZ, "y2", (double) 20 + VERT, "outline_color", "white", NULL ); item = gnome_canvas_item_new( rootGroup, //(K1) gnome_canvas_text_get_type(), //(K2) "text", "The hungry brown fox" //(K3) " jumped over a lazy dog"., //(K4) "font", "12x24", //(K5) "x", (double) 20, //(K6) "y", (double) 100, //(K7) "fill_color", "magenta", //(K8) "anchor", GTK_ANCHOR_W, //(K9) NULL); //(K10) image = gdk_imlib_create_image_from_xpm_data( allthatjazz );//(L) item = gnome_canvas_item_new( rootGroup, //(M1) gnome_canvas_image_get_type(), //(M2) "image", image, //(M3) "x", (double) 20, //(M4) "y", (double) 250, //(M5) "width", (double) 200, //(M6) "height", (double) 200, //(M7) "anchor", GTK_ANCHOR_W , //(M8) NULL); //(M9) item = gnome_canvas_item_new( rootGroup, //(N1) gnome_canvas_polygon_get_type(), //(N2) "points", makePolygon( 50, 50 ), //(N3) "fill_color", "red", //(N4) "outline_color", "black", //(N5) NULL ); //(N6) art_affine_rotate( affine, 45.0 ); //(O) gnome_canvas_item_affine_relative( item, affine ); //(P) art_affine_translate( affine, 400, 250 ); //(Q) gnome_canvas_item_affine_relative( item, affine ); //(R) gtk_widget_show_all( app ); gtk_main(); gdk_imlib_destroy_image( image ); exit( 0 ); } gint eventDestroy(GtkWidget* widget, GdkEvent* event, gpointer data) { gtk_main_quit(); return 0; } GnomeCanvasPoints* makePolygon( gint h, gint v ) { //(S) int i; GnomeCanvasPoints* points; static const gint xy[ 10 ] = {-1.-1, 1,-1,1,1,-1,1,-1,-1}; points = gnome_canvas_points_new( 5 ); for (i=0; i < 10; i += 2 ) { points->coords[i] = ( xy[i] * h ); points->coords[i + 1] = ( xy[i +1] * v); } return points; }

The executable for this code can be generated by the following makefile

 
#Makefile_GTK_RenderGraphics CC=gcc LDLIBS='gnome-config --libs gnomeui' CFLAGS=-Wall -g 'gnome-config --cflags gnomeui' RenderGraphics: RenderGraphics.o Makefile_gtk_RenderGraphics $(CC) RenderGraphics.o $(LDLIBS) -o RenderGraphics RenderGraphics.o RenderGraphics.c $(CC) $(CFLAGS) -c RenderGraphics.c clean rm -f RenderGraphics rm -f RenderGraphics.o

The program produces the output shown in Figure 17.40.

click to expand
Figure 17.40

Our next program will show how to trap the mouse events for a rudimentary free-form sketching program in GNOME/GTK+. The mouse events are of the following types in GNOME/GTK+:

           GDK_BUTTON_PRESS Mouse button pressed           GDK_2BUTTON_PRESS Double-click any mouse button           GDK_3BUTTON_PRESS Triple-click any mouse button           GDK_BUTTON_RELEASE Mouse button released           GDK_DRAG_ENTER A dragging mouse entered window           GDK_DRAG_LEAVE A dragging mouse left the window           GDK_DRAG_MOTION A dragging mouse moved within window           GDK_MOTION_NOTIFY Mouse moved 

Of these, our program would need to capture and process only the events GDK_BUTTON_PRESS and GTK_2BUTTON_PRESS since, like in the earlier Qt program, we wish to use one click to record the location of the mouse cursor in the window and a double-click to show a polyline connecting all the points recorded so far.[35] The occurrence of any mouse event generates the signal event. So, in line (T) of the program below, we connect this signal with our event processing function canvasEvent as shown here:

    gtk_signal_connect( GTK_OBJECT(canvas), "event",                        GTK_SIGNAL_FUNC( canvasEvent ), NULL ); 

As for the event processing function itself, its header looks like

    gboolean canvasEvent(GtkWidget* widget, GdkEventButton* eventButton);

If we had wanted to capture other mouse events, such as those related to the motion and the dragging of the mouse, to whether or not the mouse cursor was inside the window, and so on, the parameter would be of type GdkEvent*. But since we are only interested in mouse button action here, making it of type GdkEventButton* is sufficient.

The switch block inside the callback function in line (U) checks the type of the GdkEventButton object received by the function for the following types: GDK_BUTTON_PRESS, GDK_2BUTTON_PRESS, and GDK_3BUTTON_PRESS. For each of these cases, the callback function tries to determine the identity of the button pressed by testing the value of the button field, as in

       GdkEventButton* eventButton;       ...       if ( eventButton->button == 1 )       ... 

The button equals 1 for the left button, 2 for the middle button, and 3 for the right button.

In lines (V2) and (V3), the x and the y fields of a button action event return the two coordinates of the mouse cursor at the moment the event was generated. The program below stores away these coordinates in the array points, which is of type GnomeCanvasPoints*. When the user double-clicks the left button, this array is copied over into another array of the same type, but whose size equals the number of points entered by the user. This copying over is done by the function makePolyline in line (X). Double-clicking also causes the points to be joined by a segmented straight line by the invocation of gnome_canvas_line_get_type in line (W).

 
//Sketch.c #include <gnome.h> #define WIDTH 400 #define HEIGHT 300 int pointIndex = 0; GnomeCanvasPoints* points; GtkWidget *canvas; GnomeCanvasGroup* rootGroup; GnomeCanvasItem* item; gint eventDestroy(GtkWidget *widget, GdkEvent *event,gpointer data); gboolean canvasEvent(GtkWidget *widget, GdkEventButton* event ); GnomeCanvasPoints* makePolyline(); int main(int argc,char *argv[]) { GtkWidget *app; gnome_init("Sketch","1.0",argc,argv); app = gnome_app_new("sketch", "Mak e a free-form sketch"); gtk_signal_connect( GTK_OBJECT(app),"destroy", GTK_SIGNAL_FUNC(eventDestroy),NULL); points = gnome_canvas_points_new( 1000 ); canvas = gnome_canvas_new(); gtk_widget_set_usize(canvas,WIDTH,HEIGHT); gnome_canvas_set_scroll_region( GNOME_C ANVAS( canvas ), 0.0, 0.0, WIDTH, HEIGHT ); rootGroup = gnome_canvas_root( GNOME_CANVAS( c anvas ) ); gnome_app_set_contents(GNOME_APP(app), canvas ); gtk_signal_connect( GTK_OBJECT(canvas), "event", //(T) GTK_SIGNAL_FUNC( canvasEvent ), NULL); gtk_widget_show_all(app); gtk_main(); exit(O); } gboolean canvasEvent(GtkWidget* widget, GdkEventButton* eventButton) { gint xpoint; gint ypoint; switch( eventButton->type ) { //(U) case GDK_BUTTON_PRESS: if (eventButton->button == 1 ) { //(V1) xpoint = eventButton->x; //(V2) point = eventButton->y; //(V3) points->coords[ pointIndex ] = xpoint; //(V4) points->coords[ pointIndex + 1] = ypoint; / //(V5) pointIndex += 2; } break; case GDK_2BUTTON_PRESS: if ( eventButton->button == 1 ) { item = gnome_canvas_item_new( rootGroup, gnome_canvas_line_get_type(), //(W) "points", makePolyline(), "fill_color", "red", "width_pixels", 4, NULL ); } break; case GDK_3BUTTON_PRESS: if ( eventButton->button == 2 ) { gnome_canvas_points_free( points ); exit(0); } break; default: break; } return(TRUE); } gint eventDestroy( GtkWidget* widget, GdkEvent* event, gpointer data){ gtk_main_quit(); gnome_canvas_points_free( points ); return(0); } GnomeCanvasPoints* makePolyline() { //(X) int i; int N = pointIndex/2 - 1; GnomeCanvasPoints* newPoints = gnome_canvas_points_new( N ); for (i = 0; i < pointIndex - 2; i++) newPoints->coords[ i ] = points->coords[ i ]; return newPoints; }

The following make file can be used to create an executable for this program:

 
CC=gcc LDLIBS='gnome-config --libs gnomeui' CFLAGS=-Wall -g 'gnome-config --cflags gnomeui' Sketch: Sketch.o Makefile_Sketch $(CC) Sketch.o $(LDLIBS) -o Sketch Sketch.o: Sketch.c $(CC) $(CFLAGS) -c Sketch.c clean: rm -f Sketch rm -f Sketch.o

Figure 17.41 shows the GUI for this program and a partial sketch made on it.

click to expand
Figure 17.41

[32]An item in a grouping, such as the rootGroup above, can be another group. In this fashion, one can create of a hierarchical arrangement of groups of graphics objects. Each group in such a hierarchy can be manipulated as a unit.

[33]Other possible values for the "anchor" field are GTK_ANCHOR_E, GTK_ANCHOR_CENTER, GTK_ANCHOR_N, GTK_ANCHOR_S, GTK_ANCHOR_NE, GTK_ANCHOR_NW, GTK_ANCHOR_SE, and GTK_ANCHOR_SW with obvious meanings.

[34]XPM stands for X PixMap. An XPM stores a color image using an ASCII character based format—very much like what XBM does for black-and-white images XPM allows you to define symbolic names for the colors needed in an image. More precisely, an XPM file is an array of strings. The name of this array is the argument to the gdk_imlib_create_image_from_xpm_data function. Since all the information in an XPM file is contained in a C-legal data structure—an array—an XPM file can be #included in a C program, as we have done in the example here.

[35]We should mention that a GnomeCanvas widget has the ability to pass those mouse events to the graphics items it contains that have the x and the y coordinate information associated with them. This makes it possible to write callback functions for the graphical items displayed in a window. Such functions can be used, for example, to drag a graphics item in the window.




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net