]> Pileus Git - ~andy/gtk/blob - demos/gtk-demo/drawingarea.c
gtk-demo: Blit backing pixmap using Cairo
[~andy/gtk] / demos / gtk-demo / drawingarea.c
1 /* Drawing Area
2  *
3  * GtkDrawingArea is a blank area where you can draw custom displays
4  * of various kinds.
5  *
6  * This demo has two drawing areas. The checkerboard area shows
7  * how you can just draw something; all you have to do is write
8  * a signal handler for expose_event, as shown here.
9  *
10  * The "scribble" area is a bit more advanced, and shows how to handle
11  * events such as button presses and mouse motion. Click the mouse
12  * and drag in the scribble area to draw squiggles. Resize the window
13  * to clear the area.
14  */
15
16 #include <gtk/gtk.h>
17
18 static GtkWidget *window = NULL;
19 /* Pixmap for scribble area, to store current scribbles */
20 static GdkPixmap *pixmap = NULL;
21
22 /* Create a new pixmap of the appropriate size to store our scribbles */
23 static gboolean
24 scribble_configure_event (GtkWidget         *widget,
25                           GdkEventConfigure *event,
26                           gpointer           data)
27 {
28   cairo_t *cr;
29
30   if (pixmap)
31     g_object_unref (pixmap);
32
33   pixmap = gdk_pixmap_new (widget->window,
34                            widget->allocation.width,
35                            widget->allocation.height,
36                            -1);
37
38   /* Initialize the pixmap to white */
39   cr = gdk_cairo_create (pixmap);
40
41   cairo_set_source_rgb (cr, 1, 1, 1);
42   cairo_paint (cr);
43
44   cairo_destroy (cr);
45
46   /* We've handled the configure event, no need for further processing. */
47   return TRUE;
48 }
49
50 /* Redraw the screen from the pixmap */
51 static gboolean
52 scribble_expose_event (GtkWidget      *widget,
53                        GdkEventExpose *event,
54                        gpointer        data)
55 {
56   cairo_t *cr;
57
58   cr = gdk_cairo_create (widget->window);
59   
60   gdk_cairo_set_source_pixmap (cr, pixmap, 0, 0);
61   gdk_cairo_rectangle (cr, &event->area);
62   cairo_fill (cr);
63
64   cairo_destroy (cr);
65
66   return FALSE;
67 }
68
69 /* Draw a rectangle on the screen */
70 static void
71 draw_brush (GtkWidget *widget,
72             gdouble    x,
73             gdouble    y)
74 {
75   GdkRectangle update_rect;
76   cairo_t *cr;
77
78   update_rect.x = x - 3;
79   update_rect.y = y - 3;
80   update_rect.width = 6;
81   update_rect.height = 6;
82
83   /* Paint to the pixmap, where we store our state */
84   cr = gdk_cairo_create (pixmap);
85
86   gdk_cairo_rectangle (cr, &update_rect);
87   cairo_fill (cr);
88
89   /* Now invalidate the affected region of the drawing area. */
90   gdk_window_invalidate_rect (widget->window,
91                               &update_rect,
92                               FALSE);
93 }
94
95 static gboolean
96 scribble_button_press_event (GtkWidget      *widget,
97                              GdkEventButton *event,
98                              gpointer        data)
99 {
100   if (pixmap == NULL)
101     return FALSE; /* paranoia check, in case we haven't gotten a configure event */
102
103   if (event->button == 1)
104     draw_brush (widget, event->x, event->y);
105
106   /* We've handled the event, stop processing */
107   return TRUE;
108 }
109
110 static gboolean
111 scribble_motion_notify_event (GtkWidget      *widget,
112                               GdkEventMotion *event,
113                               gpointer        data)
114 {
115   int x, y;
116   GdkModifierType state;
117
118   if (pixmap == NULL)
119     return FALSE; /* paranoia check, in case we haven't gotten a configure event */
120
121   /* This call is very important; it requests the next motion event.
122    * If you don't call gdk_window_get_pointer() you'll only get
123    * a single motion event. The reason is that we specified
124    * GDK_POINTER_MOTION_HINT_MASK to gtk_widget_set_events().
125    * If we hadn't specified that, we could just use event->x, event->y
126    * as the pointer location. But we'd also get deluged in events.
127    * By requesting the next event as we handle the current one,
128    * we avoid getting a huge number of events faster than we
129    * can cope.
130    */
131
132   gdk_window_get_pointer (event->window, &x, &y, &state);
133
134   if (state & GDK_BUTTON1_MASK)
135     draw_brush (widget, x, y);
136
137   /* We've handled it, stop processing */
138   return TRUE;
139 }
140
141
142 static gboolean
143 checkerboard_expose (GtkWidget      *da,
144                      GdkEventExpose *event,
145                      gpointer        data)
146 {
147   gint i, j, xcount, ycount;
148   cairo_t *cr;
149
150 #define CHECK_SIZE 10
151 #define SPACING 2
152
153   /* At the start of an expose handler, a clip region of event->area
154    * is set on the window, and event->area has been cleared to the
155    * widget's background color. The docs for
156    * gdk_window_begin_paint_region() give more details on how this
157    * works.
158    */
159
160   cr = gdk_cairo_create (da->window);
161   gdk_cairo_rectangle (cr, &event->area);
162   cairo_clip (cr);
163
164   xcount = 0;
165   i = SPACING;
166   while (i < da->allocation.width)
167     {
168       j = SPACING;
169       ycount = xcount % 2; /* start with even/odd depending on row */
170       while (j < da->allocation.height)
171         {
172           if (ycount % 2)
173             cairo_set_source_rgb (cr, 0.45777, 0, 0.45777);
174           else
175             cairo_set_source_rgb (cr, 1, 1, 1);
176
177           /* If we're outside event->area, this will do nothing.
178            * It might be mildly more efficient if we handled
179            * the clipping ourselves, but again we're feeling lazy.
180            */
181           cairo_rectangle (cr, i, j, CHECK_SIZE, CHECK_SIZE);
182           cairo_fill (cr);
183
184           j += CHECK_SIZE + SPACING;
185           ++ycount;
186         }
187
188       i += CHECK_SIZE + SPACING;
189       ++xcount;
190     }
191
192   cairo_destroy (cr);
193
194   /* return TRUE because we've handled this event, so no
195    * further processing is required.
196    */
197   return TRUE;
198 }
199
200 static void
201 close_window (void)
202 {
203   window = NULL;
204
205   if (pixmap)
206     g_object_unref (pixmap);
207   pixmap = NULL;
208 }
209
210 GtkWidget *
211 do_drawingarea (GtkWidget *do_widget)
212 {
213   GtkWidget *frame;
214   GtkWidget *vbox;
215   GtkWidget *da;
216   GtkWidget *label;
217
218   if (!window)
219     {
220       window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
221       gtk_window_set_screen (GTK_WINDOW (window),
222                              gtk_widget_get_screen (do_widget));
223       gtk_window_set_title (GTK_WINDOW (window), "Drawing Area");
224
225       g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);
226
227       gtk_container_set_border_width (GTK_CONTAINER (window), 8);
228
229       vbox = gtk_vbox_new (FALSE, 8);
230       gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
231       gtk_container_add (GTK_CONTAINER (window), vbox);
232
233       /*
234        * Create the checkerboard area
235        */
236
237       label = gtk_label_new (NULL);
238       gtk_label_set_markup (GTK_LABEL (label),
239                             "<u>Checkerboard pattern</u>");
240       gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
241
242       frame = gtk_frame_new (NULL);
243       gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
244       gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
245
246       da = gtk_drawing_area_new ();
247       /* set a minimum size */
248       gtk_widget_set_size_request (da, 100, 100);
249
250       gtk_container_add (GTK_CONTAINER (frame), da);
251
252       g_signal_connect (da, "expose-event",
253                         G_CALLBACK (checkerboard_expose), NULL);
254
255       /*
256        * Create the scribble area
257        */
258
259       label = gtk_label_new (NULL);
260       gtk_label_set_markup (GTK_LABEL (label),
261                             "<u>Scribble area</u>");
262       gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
263
264       frame = gtk_frame_new (NULL);
265       gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
266       gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
267
268       da = gtk_drawing_area_new ();
269       /* set a minimum size */
270       gtk_widget_set_size_request (da, 100, 100);
271
272       gtk_container_add (GTK_CONTAINER (frame), da);
273
274       /* Signals used to handle backing pixmap */
275
276       g_signal_connect (da, "expose-event",
277                         G_CALLBACK (scribble_expose_event), NULL);
278       g_signal_connect (da,"configure-event",
279                         G_CALLBACK (scribble_configure_event), NULL);
280
281       /* Event signals */
282
283       g_signal_connect (da, "motion-notify-event",
284                         G_CALLBACK (scribble_motion_notify_event), NULL);
285       g_signal_connect (da, "button-press-event",
286                         G_CALLBACK (scribble_button_press_event), NULL);
287
288
289       /* Ask to receive events the drawing area doesn't normally
290        * subscribe to
291        */
292       gtk_widget_set_events (da, gtk_widget_get_events (da)
293                              | GDK_LEAVE_NOTIFY_MASK
294                              | GDK_BUTTON_PRESS_MASK
295                              | GDK_POINTER_MOTION_MASK
296                              | GDK_POINTER_MOTION_HINT_MASK);
297
298     }
299
300   if (!gtk_widget_get_visible (window))
301       gtk_widget_show_all (window);
302   else
303       gtk_widget_destroy (window);
304
305   return window;
306 }