]> Pileus Git - ~andy/gtk/blob - demos/gtk-demo/offscreen_window.c
Add another offscreen demo
[~andy/gtk] / demos / gtk-demo / offscreen_window.c
1 /* Offscreen windows/Rotated button
2  *
3  * Offscreen windows can be used to transform parts of a widget
4  * hierarchy. Note that the rotated button is fully functional.
5  */
6 #define _GNU_SOURCE
7 #include <math.h>
8 #include <gtk/gtk.h>
9
10 #define GTK_TYPE_ROTATED_BIN              (gtk_rotated_bin_get_type ())
11 #define GTK_ROTATED_BIN(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_ROTATED_BIN, GtkRotatedBin))
12 #define GTK_ROTATED_BIN_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_ROTATED_BIN, GtkRotatedBinClass))
13 #define GTK_IS_ROTATED_BIN(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_ROTATED_BIN))
14 #define GTK_IS_ROTATED_BIN_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_ROTATED_BIN))
15 #define GTK_ROTATED_BIN_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_ROTATED_BIN, GtkRotatedBinClass))
16
17 typedef struct _GtkRotatedBin   GtkRotatedBin;
18 typedef struct _GtkRotatedBinClass  GtkRotatedBinClass;
19
20 struct _GtkRotatedBin
21 {
22   GtkContainer container;
23
24   GtkWidget *child;
25   GdkWindow *offscreen_window;
26   gdouble angle;
27 };
28
29 struct _GtkRotatedBinClass
30 {
31   GtkContainerClass parent_class;
32 };
33
34 GType      gtk_rotated_bin_get_type  (void) G_GNUC_CONST;
35 GtkWidget* gtk_rotated_bin_new       (void);
36 void       gtk_rotated_bin_set_angle (GtkRotatedBin *bin,
37                                       gdouble        angle);
38
39 /*** implementation ***/
40
41 static void     gtk_rotated_bin_realize       (GtkWidget       *widget);
42 static void     gtk_rotated_bin_unrealize     (GtkWidget       *widget);
43 static void     gtk_rotated_bin_size_request  (GtkWidget       *widget,
44                                                GtkRequisition  *requisition);
45 static void     gtk_rotated_bin_size_allocate (GtkWidget       *widget,
46                                                GtkAllocation   *allocation);
47 static gboolean gtk_rotated_bin_damage        (GtkWidget       *widget,
48                                                GdkEventExpose  *event);
49 static gboolean gtk_rotated_bin_expose        (GtkWidget       *widget,
50                                                GdkEventExpose  *offscreen);
51
52 static void     gtk_rotated_bin_add           (GtkContainer    *container,
53                                                GtkWidget       *child);
54 static void     gtk_rotated_bin_remove        (GtkContainer    *container,
55                                                GtkWidget       *widget);
56 static void     gtk_rotated_bin_forall        (GtkContainer    *container,
57                                                gboolean         include_internals,
58                                                GtkCallback      callback,
59                                                gpointer         callback_data);
60 static GType    gtk_rotated_bin_child_type    (GtkContainer    *container);
61
62 G_DEFINE_TYPE (GtkRotatedBin, gtk_rotated_bin, GTK_TYPE_CONTAINER);
63
64 static void
65 to_child (GtkRotatedBin *bin,
66           double         widget_x,
67           double         widget_y,
68           double        *x_out,
69           double        *y_out)
70 {
71   GtkAllocation child_area;
72   double x, y, xr, yr;
73   double c, s;
74   double w, h;
75
76   sincos (bin->angle, &s, &c);
77   child_area = bin->child->allocation;
78
79   w = c * child_area.width + s * child_area.height;
80   h = s * child_area.width + c * child_area.height;
81
82   x = widget_x;
83   y = widget_y;
84
85   x -= (w - child_area.width) / 2;
86   y -= (h - child_area.height) / 2;
87
88   x -= child_area.width / 2;
89   y -= child_area.height / 2;
90
91   xr = x * c + y * s;
92   yr = y * c - x * s;
93   x = xr;
94   y = yr;
95
96   x += child_area.width / 2;
97   y += child_area.height / 2;
98
99   *x_out = x;
100   *y_out = y;
101 }
102
103 static void
104 to_parent (GtkRotatedBin *bin,
105            double         offscreen_x,
106            double         offscreen_y,
107            double        *x_out,
108            double        *y_out)
109 {
110   GtkAllocation child_area;
111   double x, y, xr, yr;
112   double c, s;
113   double w, h;
114
115   sincos (bin->angle, &s, &c);
116   child_area = bin->child->allocation;
117
118   w = c * child_area.width + s * child_area.height;
119   h = s * child_area.width + c * child_area.height;
120
121   x = offscreen_x;
122   y = offscreen_y;
123
124   x -= child_area.width / 2;
125   y -= child_area.height / 2;
126
127   xr = x * c - y * s;
128   yr = x * s + y * c;
129   x = xr;
130   y = yr;
131
132   x += child_area.width / 2;
133   y += child_area.height / 2;
134
135   x -= (w - child_area.width) / 2;
136   y -= (h - child_area.height) / 2;
137
138   *x_out = x;
139   *y_out = y;
140 }
141
142 static void
143 gtk_rotated_bin_class_init (GtkRotatedBinClass *klass)
144 {
145   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
146   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
147
148   widget_class->realize = gtk_rotated_bin_realize;
149   widget_class->unrealize = gtk_rotated_bin_unrealize;
150   widget_class->size_request = gtk_rotated_bin_size_request;
151   widget_class->size_allocate = gtk_rotated_bin_size_allocate;
152   widget_class->expose_event = gtk_rotated_bin_expose;
153
154   g_signal_override_class_closure (g_signal_lookup ("damage-event", GTK_TYPE_WIDGET),
155                                    GTK_TYPE_ROTATED_BIN,
156                                    g_cclosure_new (G_CALLBACK (gtk_rotated_bin_damage),
157                                                    NULL, NULL));
158
159   container_class->add = gtk_rotated_bin_add;
160   container_class->remove = gtk_rotated_bin_remove;
161   container_class->forall = gtk_rotated_bin_forall;
162   container_class->child_type = gtk_rotated_bin_child_type;
163 }
164
165 static void
166 gtk_rotated_bin_init (GtkRotatedBin *bin)
167 {
168   GTK_WIDGET_UNSET_FLAGS (bin, GTK_NO_WINDOW);
169 }
170
171 GtkWidget *
172 gtk_rotated_bin_new (void)
173 {
174   return g_object_new (GTK_TYPE_ROTATED_BIN, NULL);
175 }
176
177 static GdkWindow *
178 pick_offscreen_child (GdkWindow     *offscreen_window,
179                       double         widget_x,
180                       double         widget_y,
181                       GtkRotatedBin *bin)
182 {
183  GtkAllocation child_area;
184  double x, y;
185
186  if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
187     {
188       to_child (bin, widget_x, widget_y, &x, &y);
189
190       child_area = bin->child->allocation;
191
192       if (x >= 0 && x < child_area.width &&
193           y >= 0 && y < child_area.height)
194         return bin->offscreen_window;
195     }
196
197   return NULL;
198 }
199
200 static void
201 offscreen_window_to_parent (GdkWindow     *offscreen_window,
202                             double         offscreen_x,
203                             double         offscreen_y,
204                             double        *parent_x,
205                             double        *parent_y,
206                             GtkRotatedBin *bin)
207 {
208   to_parent (bin, offscreen_x, offscreen_y, parent_x, parent_y);
209 }
210
211 static void
212 offscreen_window_from_parent (GdkWindow     *window,
213                               double         parent_x,
214                               double         parent_y,
215                               double        *offscreen_x,
216                               double        *offscreen_y,
217                               GtkRotatedBin *bin)
218 {
219   to_child (bin, parent_x, parent_y, offscreen_x, offscreen_y);
220 }
221
222 static void
223 gtk_rotated_bin_realize (GtkWidget *widget)
224 {
225   GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
226   GdkWindowAttr attributes;
227   gint attributes_mask;
228   gint border_width;
229   GtkRequisition child_requisition;
230
231   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
232
233   border_width = GTK_CONTAINER (widget)->border_width;
234
235   attributes.x = widget->allocation.x + border_width;
236   attributes.y = widget->allocation.y + border_width;
237   attributes.width = widget->allocation.width - 2 * border_width;
238   attributes.height = widget->allocation.height - 2 * border_width;
239   attributes.window_type = GDK_WINDOW_CHILD;
240   attributes.event_mask = gtk_widget_get_events (widget)
241                         | GDK_EXPOSURE_MASK
242                         | GDK_POINTER_MOTION_MASK
243                         | GDK_BUTTON_PRESS_MASK
244                         | GDK_BUTTON_RELEASE_MASK
245                         | GDK_SCROLL_MASK
246                         | GDK_ENTER_NOTIFY_MASK
247                         | GDK_LEAVE_NOTIFY_MASK;
248
249   attributes.visual = gtk_widget_get_visual (widget);
250   attributes.colormap = gtk_widget_get_colormap (widget);
251   attributes.wclass = GDK_INPUT_OUTPUT;
252
253   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
254
255   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
256                                    &attributes, attributes_mask);
257   gdk_window_set_user_data (widget->window, widget);
258   g_signal_connect (widget->window, "pick-embedded-child",
259                     G_CALLBACK (pick_offscreen_child), bin);
260
261   attributes.window_type = GDK_WINDOW_OFFSCREEN;
262
263   child_requisition.width = child_requisition.height = 0;
264   if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
265     {
266       attributes.width = bin->child->allocation.width;
267       attributes.height = bin->child->allocation.height;
268     }
269   bin->offscreen_window = gdk_window_new (gtk_widget_get_root_window (widget),
270                                           &attributes, attributes_mask);
271   gdk_window_set_user_data (bin->offscreen_window, widget);
272   if (bin->child)
273     gtk_widget_set_parent_window (bin->child, bin->offscreen_window);
274   gdk_offscreen_window_set_embedder (bin->offscreen_window, widget->window);
275   g_signal_connect (bin->offscreen_window, "to-embedder",
276                     G_CALLBACK (offscreen_window_to_parent), bin);
277   g_signal_connect (bin->offscreen_window, "from-embedder",
278                     G_CALLBACK (offscreen_window_from_parent), bin);
279
280   widget->style = gtk_style_attach (widget->style, widget->window);
281
282   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
283   gtk_style_set_background (widget->style, bin->offscreen_window, GTK_STATE_NORMAL);
284   gdk_window_show (bin->offscreen_window);
285 }
286
287 static void
288 gtk_rotated_bin_unrealize (GtkWidget *widget)
289 {
290   GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
291
292   gdk_window_set_user_data (bin->offscreen_window, NULL);
293   gdk_window_destroy (bin->offscreen_window);
294   bin->offscreen_window = NULL;
295
296   GTK_WIDGET_CLASS (gtk_rotated_bin_parent_class)->unrealize (widget);
297 }
298
299 static GType
300 gtk_rotated_bin_child_type (GtkContainer *container)
301 {
302   GtkRotatedBin *bin = GTK_ROTATED_BIN (container);
303
304   if (bin->child)
305     return G_TYPE_NONE;
306
307   return GTK_TYPE_WIDGET;
308 }
309
310 static void
311 gtk_rotated_bin_add (GtkContainer *container,
312                      GtkWidget    *widget)
313 {
314   GtkRotatedBin *bin = GTK_ROTATED_BIN (container);
315
316   if (!bin->child)
317     {
318       gtk_widget_set_parent_window (widget, bin->offscreen_window);
319       gtk_widget_set_parent (widget, GTK_WIDGET (bin));
320       bin->child = widget;
321     }
322   else
323     g_warning ("GtkRotatedBin cannot have more than one child\n");
324 }
325
326 static void
327 gtk_rotated_bin_remove (GtkContainer *container,
328                         GtkWidget    *widget)
329 {
330   GtkRotatedBin *bin = GTK_ROTATED_BIN (container);
331   gboolean was_visible;
332
333   was_visible = GTK_WIDGET_VISIBLE (widget);
334
335   if (bin->child == widget)
336     {
337       gtk_widget_unparent (widget);
338
339       bin->child = NULL;
340
341       if (was_visible && GTK_WIDGET_VISIBLE (container))
342         gtk_widget_queue_resize (GTK_WIDGET (container));
343     }
344 }
345
346 static void
347 gtk_rotated_bin_forall (GtkContainer *container,
348                         gboolean      include_internals,
349                         GtkCallback   callback,
350                         gpointer      callback_data)
351 {
352   GtkRotatedBin *bin = GTK_ROTATED_BIN (container);
353
354   g_return_if_fail (callback != NULL);
355
356   if (bin->child)
357     (*callback) (bin->child, callback_data);
358 }
359
360 void
361 gtk_rotated_bin_set_angle (GtkRotatedBin *bin,
362                            gdouble        angle)
363 {
364   g_return_if_fail (GTK_IS_ROTATED_BIN (bin));
365
366   bin->angle = angle;
367   gtk_widget_queue_resize (GTK_WIDGET (bin));
368
369   /* TODO: Really needs to resent pointer events if over the rotated window */
370 }
371
372 static void
373 gtk_rotated_bin_size_request (GtkWidget      *widget,
374                               GtkRequisition *requisition)
375 {
376   GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
377   GtkRequisition child_requisition;
378   double s, c;
379   double w, h;
380
381   child_requisition.width = 0;
382   child_requisition.height = 0;
383
384   if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
385     gtk_widget_size_request (bin->child, &child_requisition);
386
387   sincos (bin->angle, &s, &c);
388   w = c * child_requisition.width + s * child_requisition.height;
389   h = s * child_requisition.width + c * child_requisition.height;
390
391   requisition->width = GTK_CONTAINER (widget)->border_width * 2 + w;
392   requisition->height = GTK_CONTAINER (widget)->border_width * 2 + h;
393 }
394
395 static void
396 gtk_rotated_bin_size_allocate (GtkWidget     *widget,
397                                GtkAllocation *allocation)
398 {
399   GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
400   gint border_width;
401   gint w, h;
402   gdouble s, c;
403
404   widget->allocation = *allocation;
405
406   border_width = GTK_CONTAINER (widget)->border_width;
407
408   w = allocation->width - border_width * 2;
409   h = allocation->height - border_width * 2;
410
411   if (GTK_WIDGET_REALIZED (widget))
412     gdk_window_move_resize (widget->window,
413                             allocation->x + border_width,
414                             allocation->y + border_width,
415                             w, h);
416
417   if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
418     {
419       GtkRequisition child_requisition;
420       GtkAllocation child_allocation;
421
422       sincos (bin->angle, &s, &c);
423
424       gtk_widget_get_child_requisition (bin->child, &child_requisition);
425       child_allocation.x = 0;
426       child_allocation.y = 0;
427       child_allocation.height = child_requisition.height;
428       if (c == 0.0)
429         child_allocation.width = h / s;
430       else if (s == 0.0)
431         child_allocation.width = w / c;
432       else
433         child_allocation.width = MIN ((w - s * child_allocation.height) / c,
434                                       (h - c * child_allocation.height) / s);
435
436       if (GTK_WIDGET_REALIZED (widget))
437         gdk_window_move_resize (bin->offscreen_window,
438                                 child_allocation.x,
439                                 child_allocation.y,
440                                 child_allocation.width,
441                                 child_allocation.height);
442
443       child_allocation.x = child_allocation.y = 0;
444       gtk_widget_size_allocate (bin->child, &child_allocation);
445     }
446 }
447
448 static gboolean
449 gtk_rotated_bin_damage (GtkWidget      *widget,
450                         GdkEventExpose *event)
451 {
452   gdk_window_invalidate_rect (widget->window, NULL, FALSE);
453
454   return TRUE;
455 }
456
457 static gboolean
458 gtk_rotated_bin_expose (GtkWidget      *widget,
459                         GdkEventExpose *event)
460 {
461   GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
462   gint width, height;
463   gdouble s, c;
464   gdouble w, h;
465
466   if (GTK_WIDGET_DRAWABLE (widget))
467     {
468       if (event->window == widget->window)
469         {
470           GdkPixmap *pixmap;
471           GtkAllocation child_area;
472           cairo_t *cr;
473
474           if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
475             {
476               pixmap = gdk_offscreen_window_get_pixmap (bin->offscreen_window);
477               child_area = bin->child->allocation;
478
479               cr = gdk_cairo_create (widget->window);
480
481               /* transform */
482               sincos (bin->angle, &s, &c);
483               w = c * child_area.width + s * child_area.height;
484               h = s * child_area.width + c * child_area.height;
485
486               cairo_translate (cr, (w - child_area.width) / 2, (h - child_area.height) / 2);
487               cairo_translate (cr, child_area.width / 2, child_area.height / 2);
488               cairo_rotate (cr, bin->angle);
489               cairo_translate (cr, -child_area.width / 2, -child_area.height / 2);
490
491               /* clip */
492               gdk_drawable_get_size (pixmap, &width, &height);
493               cairo_rectangle (cr, 0, 0, width, height);
494               cairo_clip (cr);
495               /* paint */
496               gdk_cairo_set_source_pixmap (cr, pixmap, 0, 0);
497               cairo_paint (cr);
498
499               cairo_destroy (cr);
500             }
501         }
502       else if (event->window == bin->offscreen_window)
503         {
504           gtk_paint_flat_box (widget->style, event->window,
505                               GTK_STATE_NORMAL, GTK_SHADOW_NONE,
506                               &event->area, widget, "blah",
507                               0, 0, -1, -1);
508
509           if (bin->child)
510             gtk_container_propagate_expose (GTK_CONTAINER (widget),
511                                             bin->child,
512                                             event);
513         }
514     }
515
516   return FALSE;
517 }
518
519 /*** ***/
520
521 static void
522 scale_changed (GtkRange      *range,
523                GtkRotatedBin *bin)
524 {
525   gtk_rotated_bin_set_angle (bin, gtk_range_get_value (range));
526 }
527
528 static GtkWidget *window = NULL;
529
530 GtkWidget *
531 do_offscreen_window (GtkWidget *do_widget)
532 {
533   if (!window)
534     {
535       GtkWidget *bin, *vbox, *scale, *button;
536       GdkColor black;
537
538       window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
539       gtk_window_set_screen (GTK_WINDOW (window),
540                              gtk_widget_get_screen (do_widget));
541       gtk_window_set_title (GTK_WINDOW (window), "Rotated widget");
542
543       g_signal_connect (window, "destroy",
544                         G_CALLBACK (gtk_widget_destroyed), &window);
545
546       gdk_color_parse ("black", &black);
547       gtk_widget_modify_bg (window, GTK_STATE_NORMAL, &black);
548       gtk_container_set_border_width (GTK_CONTAINER (window), 10);
549
550       vbox = gtk_vbox_new (0, FALSE);
551       scale = gtk_hscale_new_with_range (0, G_PI/2, 0.01);
552       gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE);
553
554       button = gtk_button_new_with_label ("A Button");
555       bin = gtk_rotated_bin_new ();
556
557       g_signal_connect (scale, "value-changed", G_CALLBACK (scale_changed), bin);
558
559       gtk_container_add (GTK_CONTAINER (window), vbox);
560       gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
561       gtk_box_pack_start (GTK_BOX (vbox), bin, TRUE, TRUE, 0);
562       gtk_container_add (GTK_CONTAINER (bin), button);
563     }
564
565   if (!GTK_WIDGET_VISIBLE (window))
566     gtk_widget_show_all (window);
567   else
568     {
569       gtk_widget_destroy (window);
570       window = NULL;
571     }
572
573   return window;
574 }
575