GTK+ / Gnome Application Development | |||
---|---|---|---|
<<< Previous | Home | Next >>> |
GnomeCanvas is easy to use; this is its virtue compared to GtkDrawingArea or some other low-level approach. This section describes how to create a canvas, and work with canvas items. It ends with a programming example.
The first decision you have to make is whether to use the canvas in GDK mode or antialiased mode. When you create a canvas widget, you must specify the mode you want; there is no way to change it later. gnome_canvas_new() creates a GDK canvas. gnome_canvas_new_aa() creates an antialiased canvas. These are shown in Figure 5.
Sometimes it matters which visual and colormap the canvas will use. In particular:
In GDK mode, if you want to use the GnomeCanvasImage item to display images, you must use Imlib's visual and colormap. GnomeCanvasImage uses Imlib to render images.
In antialiased mode, GDK's RGB buffer rendering facilities (see the section called RGB Buffers in the chapter called GDK Basics) are used to copy the RGB buffer to the screen. You must use the visual and colormap from the GDK RGB module.
To create a widget with a non-default visual and colormap, gtk_widget_push_visual() and gtk_widget_push_colormap() are used. Here is the code to create a GDK canvas that supports the image item:
GtkWidget* canvas; gtk_widget_push_visual(gdk_imlib_get_visual()); gtk_widget_push_colormap(gdk_imlib_get_colormap()); canvas = gnome_canvas_new(); gtk_widget_pop_visual(); gtk_widget_pop_colormap(); |
To create an antialiased canvas, do this:
GtkWidget* canvas; gtk_widget_push_visual(gdk_rgb_get_visual()); gtk_widget_push_colormap(gdk_rgb_get_cmap()); canvas = gnome_canvas_new_aa(); gtk_widget_pop_colormap(); gtk_widget_pop_visual(); |
#include <libgnomeui/gnome-canvas.h> |
GtkWidget* gnome_canvas_new
(void);
GtkWidget*
gnome_canvas_new_aa
(void);
Figure 5. Canvas Constructors
The canvas is practically infinite from a programmer's standpoint; however, in reality your application probably uses only a small area. When using the canvas you must specify which region is interesting to the user with gnome_canvas_set_scroll_region() (Figure 6). The scroll region is given in world coordinates. You can query the scroll region with gnome_canvas_get_scroll_region().
To add scroll bars to the canvas, simply create a GtkScrolledWindow and add the canvas to it:
GtkWidget* sw; sw = gtk_scrolled_window_new(NULL, NULL); gtk_container_add(GTK_CONTAINER(sw), canvas); |
If you want to implement scrolling via some mechanism other than the scroll bars, you can get and set the "scroll offsets." The scroll offsets are in canvas pixel coordinates; they specify the top left visible pixel. Remember that canvas pixel coordinates are relative to the scroll region.
#include <libgnomeui/gnome-canvas.h> |
void
gnome_canvas_set_scroll_region
(GnomeCanvas*
canvas, double
x1, double y1, double x2, double y2);
void
gnome_canvas_get_scroll_region
(GnomeCanvas*
canvas, double*
x1, double* y1, double* x2, double* y2);
void
gnome_canvas_scroll_to
(GnomeCanvas* canvas, gint cx, gint cy);
void
gnome_canvas_get_scroll_offsets
(GnomeCanvas*
canvas, gint* cx, gint* cy);
Figure 6. Canvas Scrolling
The canvas gives you zooming "for free"; it is included in the world-to-canvas and canvas-to-world coordinate system conversions. You can set the zoom factor with gnome_canvas_set_pixels_per_unit() (Figure 7). By default, there ratio of pixels to canvas units is 1.0, meaning no zoom. Specifying a value less than 1.0 means reduced size; greater than 1.0 means increased size.
In antialiased mode, you could achieve the same visual effect by applying a scaling affine transformation to the root canvas group. The pixels_per_unit member of the GnomeCanvas struct predates the canvas's use of affines. Still, gnome_canvas_set_pixels_per_unit() is a bit more convenient than the affine transform method, and it does work in GDK mode. (Because GDK mode uses Xlib primitives, it's nontrivial to implement arbitrary affine transformations; a future version of Gnome may do so, however.)
Most of the time you will be interested in canvas items rather than the canvas itself. Canvas items are typically very easy to use, compared to widgets; none of the standard items have any unique signals, since they are not interactive. (Since GnomeCanvasItem is a subclass of GtkObject, however, you could certainly have an item with signals if you wanted to.) The GnomeCanvasItem base class has a single signal, "event", which is used to convey all types of event. The "event" signal has no default handler; canvas items do not respond to events unless you connect handlers of your own. Figure 8 lists all the useful functions for working with the GnomeCanvasItem base class.
To create a canvas item, you use the generic gnome_canvas_item_new() (or gnome_canvas_item_newv()). This function accepts the group to place the item in, the GtkType of the GnomeCanvasItem subclass to create, and finally a NULL-terminated list of arguments to set. The argument list is purely for convenience, so you don't have to call gnome_canvas_item_set() immediately. gnome_canvas_item_new() creates a new instance of the type with gtk_type_new(), adds the item to its GnomeCanvasGroup, and schedules it to be redrawn.
To destroy an item and remove it from the canvas, simply call gtk_object_destroy(). You can also use the standard reference counting mechanism with canvas items.
You can set an item's affine using gnome_canvas_item_affine_absolute(), or compose a new affine with the item's existing affine using gnome_canvas_item_affine_relative(). These functions can be used to translate, scale, or rotate a canvas item (however, scaling and rotation only work in antialiased mode).
Items in a group are normally stacked in the order you add them, with the most recently-added item "on top" and the oldest item on the bottom. You can manipulate the stacking order with gnome_canvas_item_raise() and gnome_canvas_item_lower(). These move an item up or down by the given number of positions. It is safe to pass in a too-large value for positions; the item will be moved as far as possible and no more. You can also request that an item is moved to one extreme or the other, using gnome_canvas_item_raise_to_top() and gnome_canvas_item_lower_to_bottom.
Items can be shown and hidden; hidden items are not rendered by the canvas and do not receive events. All items are visible by default. The routines are gnome_canvas_item_show() and gnome_canvas_item_hide().
Reparenting a canvas item is straightforward; the only rule is that the new group must be on the same canvas as the old group.
gnome_canvas_item_grab_focus() is analagous to gtk_widget_grab_focus(); it sends all key events to the item with the grab. It also sends focus change events to the item (when the item gains or loses the focus).
Canvas items can grab and ungrab the mouse pointer just as a GdkWindow can; the arguments to gnome_canvas_item_grab() are exactly analagous to those of gdk_pointer_grab() (see the chapter called GDK Basics). While a canvas item has the pointer grabbed, no other item receives events. Behind the scenes, GnomeCanvas uses gdk_pointer_grab() to implement gnome_canvas_item_grab(), so an item grabbing the mouse away from other items implies the canvas grabbing the mouse away from other widgets.
The visual properties of canvas items are manipulated almost entirely via object arguments. If you skipped the chapter called The GTK+ Object and Type System, go back and read the section on object arguments now. Two functions are used to set canvas item properties: gnome_canvas_item_set() and gnome_canvas_item_setv(). These are almost but not quite equivalent to gtk_object_set() and gtk_object_setv()---they set object arguments in the same way, but they also mark the canvas item to be redrawn. So you should prefer them to the GtkObject variants. (This is something of a design bug, and future canvas versions will most likely allow you to use gtk_object_set().)
gnome_canvas_item_request_update() marks the canvas item as "dirty" and queues it to be redrawn. Internally, the canvas uses a one-shot idle function to perform redraws; that is, it waits until no more GTK+ events are pending, then redraws itself a single time. It does this by installing an idle function with gtk_idle_add() and removing it after it runs once. Thus gnome_canvas_item_request_update() can be called many times without creating an efficiency problem---it pretty much does nothing at all if an update is already pending.
#include <libgnomeui/gnome-canvas.h> |
GnomeCanvasItem*
gnome_canvas_item_new
(GnomeCanvasGroup*
parent, GtkType type, const gchar* first_arg_name, ...);
GnomeCanvasItem*
gnome_canvas_item_newv
(GnomeCanvasGroup*
parent, GtkType type, guint nargs, GtkArg* args);
void
gnome_canvas_item_set
(GnomeCanvasItem*
item, const gchar*
first_arg_name, ...);
void
gnome_canvas_item_setv
(GnomeCanvasItem*
item, guint nargs, GtkArg* args);
void
gnome_canvas_item_affine_relative
(GnomeCanvasItem*
item, const double
affine[6]);
void
gnome_canvas_item_affine_absolute
(GnomeCanvasItem*
item, const double
affine[6]);
void
gnome_canvas_item_raise
(GnomeCanvasItem*
item, int positions);
void
gnome_canvas_item_lower
(GnomeCanvasItem*
item, int positions);
void
gnome_canvas_item_raise_to_top
(GnomeCanvasItem*
item);
void
gnome_canvas_item_lower_to_bottom
(GnomeCanvasItem*
item);
void
gnome_canvas_item_show
(GnomeCanvasItem*
item);
void
gnome_canvas_item_hide
(GnomeCanvasItem*
item);
void
gnome_canvas_item_reparent
(GnomeCanvasItem*
item,
GnomeCanvasGroup*
new_group);
void
gnome_canvas_item_grab_focus
(GnomeCanvasItem*
item);
int
gnome_canvas_item_grab
(GnomeCanvasItem*
item, unsigned int
event_mask,
GdkCursor* cursor,
guint32
etime);
void
gnome_canvas_item_ungrab
(GnomeCanvasItem*
item, guint32 etime);
void
gnome_canvas_item_get_bounds
(GnomeCanvasItem*
item, double* x1, double* y1, double* x2, double* y2);
void
gnome_canvas_item_request_update
(GnomeCanvasItem*
item);
Figure 8. Using GnomeCanvasItem
The standard Gnome canvas items have only one signal, "event", which is emitted for all types of event. The canvas widget preprocesses all GDK events that it receives, and forwards some of them to canvas items. It also sythesizes certain events. Remember that X sends events only to X windows (GdkWindows), and canvas items do not have an associated GdkWindow. Thus the canvas widget must act as intermediary. Here are some of the actions it takes:
Coordinates are automatically converted to canvas world coordinates. For example, if a canvas item receives an event of type GDK_BUTTON_PRESS, the x and y fields of the event will be in world coordinates. (The raw event was received on the canvas's GdkWindow and thus had window coordinates.)
Enter/leave events are synthesized for canvas items as the mouse pointer moves across the canvas.
Events are propagated up the canvas item hierarchy, until some item's "event" signal handler returns TRUE. This works just as it does with GtkWidget; events are first sent to the bottommost or leaf canvas item, and eventually make it up to the root item.
Only user-generated events are sent to canvas items; many events you might expect to receive on a GdkWindow, such as expose and configure events, are not forwarded to canvas items.
The canvas does this work behind the scenes, so item events work intuitively and much like normal GDK events.
A canvas item event callback looks like this:
static gint item_event_callback(GnomeCanvasItem* item, GdkEvent* event, gpointer data) { switch (event->type) { case GDK_BUTTON_PRESS: break; case GDK_MOTION_NOTIFY: break; case GDK_BUTTON_RELEASE: break; default: break; } /* Returning FALSE propagates the event to parent items; * returning TRUE ends event propagation. */ return FALSE; } |
Of course, a real callback would probably examine the contents of the event and take some action in response to some of them.
This section gives a brief example program, demonstrating the user of the canvas. It does not explain the particulars of the canvas items being created; see the section called Standard Canvas Item Reference for that. Figure 9 shows the example program in action. You can drag canvas items around the screen with the left mouse button; clicking an item with the Shift key held down destroys it.
Here is the code to create an antialiased canvas. Notice the call to gdk_rgb_init(); notice that the canvas's scroll region is set; finally, notice that the GdkRGB colormap and visual are pushed when creating the canvas.
#include <gnome.h> static gint delete_event_cb(GtkWidget* window, GdkEventAny* e, gpointer data); static void create_canvas_items(GtkWidget* canvas); int main(int argc, char* argv[]) { GtkWidget* window; GtkWidget* sw; GtkWidget* canvas; gnome_init("canvas-example", "0.0", argc, argv); gdk_rgb_init(); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "Canvas Example"); gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, TRUE); gtk_signal_connect(GTK_OBJECT(window), "delete_event", GTK_SIGNAL_FUNC(delete_event_cb), NULL); sw = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_widget_push_visual(gdk_rgb_get_visual()); gtk_widget_push_colormap(gdk_rgb_get_cmap()); canvas = gnome_canvas_new_aa(); gtk_widget_pop_colormap(); gtk_widget_pop_visual(); gnome_canvas_set_scroll_region(GNOME_CANVAS(canvas), 0, 0, 600, 450); create_canvas_items(canvas); gtk_container_add(GTK_CONTAINER(sw), canvas); gtk_container_add(GTK_CONTAINER(window), sw); gtk_window_set_default_size(GTK_WINDOW(window), 300, 300); gtk_widget_show_all(window); gtk_main(); return 0; } static gint delete_event_cb(GtkWidget* window, GdkEventAny* e, gpointer data) { gtk_main_quit(); return FALSE; } |
Once the canvas has been created, the program adds some items to it, and connects a simple callback to the item's "event" signal. Here's the code:
static gint item_event(GnomeCanvasItem *item, GdkEvent *event, gpointer data) { static double x, y; double new_x, new_y; GdkCursor *fleur; static int dragging; double item_x, item_y; item_x = event->button.x; item_y = event->button.y; gnome_canvas_item_w2i(item->parent, &item_x, &item_y); switch (event->type) { case GDK_BUTTON_PRESS: switch(event->button.button) { case 1: if (event->button.state & GDK_SHIFT_MASK) { gtk_object_destroy(GTK_OBJECT(item)); } else { x = item_x; y = item_y; fleur = gdk_cursor_new(GDK_FLEUR); gnome_canvas_item_grab(item, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, fleur, event->button.time); gdk_cursor_destroy(fleur); dragging = TRUE; } break; default: break; } break; case GDK_MOTION_NOTIFY: if (dragging && (event->motion.state & GDK_BUTTON1_MASK)) { new_x = item_x; new_y = item_y; gnome_canvas_item_move(item, new_x - x, new_y - y); x = new_x; y = new_y; } break; case GDK_BUTTON_RELEASE: gnome_canvas_item_ungrab(item, event->button.time); dragging = FALSE; break; default: break; } return FALSE; } static void setup_item(GnomeCanvasItem *item) { gtk_signal_connect(GTK_OBJECT(item), "event", (GtkSignalFunc) item_event, NULL); } static void create_canvas_items(GtkWidget* canvas) { GnomeCanvasPoints* points; GnomeCanvasGroup* group; GnomeCanvasItem* item; double affine[6]; group = gnome_canvas_root(GNOME_CANVAS(canvas)); /* A polygon */ points = gnome_canvas_points_new(14); points->coords[0] = 270.0; points->coords[1] = 330.0; points->coords[2] = 270.0; points->coords[3] = 430.0; points->coords[4] = 390.0; points->coords[5] = 430.0; points->coords[6] = 390.0; points->coords[7] = 330.0; points->coords[8] = 310.0; points->coords[9] = 330.0; points->coords[10] = 310.0; points->coords[11] = 390.0; points->coords[12] = 350.0; points->coords[13] = 390.0; points->coords[14] = 350.0; points->coords[15] = 370.0; points->coords[16] = 330.0; points->coords[17] = 370.0; points->coords[18] = 330.0; points->coords[19] = 350.0; points->coords[20] = 370.0; points->coords[21] = 350.0; points->coords[22] = 370.0; points->coords[23] = 410.0; points->coords[24] = 290.0; points->coords[25] = 410.0; points->coords[26] = 290.0; points->coords[27] = 330.0; item = gnome_canvas_item_new(group, gnome_canvas_polygon_get_type (), "points", points, "fill_color", "tan", "outline_color", "black", "width_units", 3.0, NULL); setup_item(item); gnome_canvas_points_unref(points); /* Translate the polygon */ art_affine_translate(affine, -150.0, -300.0); gnome_canvas_item_affine_relative(item, affine); /* A translucent rectangle */ setup_item (gnome_canvas_item_new (group, gnome_canvas_rect_get_type(), "x1", 90.0, "y1", 40.0, "x2", 180.0, "y2", 100.0, "fill_color_rgba", 0x3cb37180, "outline_color", "black", "width_units", 4.0, NULL)); /* A translucent ellipse */ setup_item (gnome_canvas_item_new (group, gnome_canvas_ellipse_get_type(), "x1", 210.0, "y1", 80.0, "x2", 280.0, "y2", 140.0, "fill_color_rgba", 0x5f9ea080, "outline_color", "black", "width_pixels", 0, NULL)); /* Create ellipses arranged in a line; they're manipulated as a single item. */ group = GNOME_CANVAS_GROUP (gnome_canvas_item_new (group, gnome_canvas_group_get_type(), "x", 0.0, "y", 0.0, NULL)); setup_item(GNOME_CANVAS_ITEM(group)); { double xpos = 20.0; while (xpos < 300.0) { gnome_canvas_item_new(group, gnome_canvas_ellipse_get_type(), "x1", xpos, "y1", 100.0, "x2", xpos + 10.0, "y2", 110.0, "fill_color_rgba", 0x0000FFFF, "outline_color_rgba", 0xFF, NULL); xpos += 15.0; } } } |