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