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