1 /* Offscreen windows/Rotated button
3 * Offscreen windows can be used to transform parts of a widget
4 * hierarchy. Note that the rotated button is fully functional.
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))
17 typedef struct _GtkRotatedBin GtkRotatedBin;
18 typedef struct _GtkRotatedBinClass GtkRotatedBinClass;
22 GtkContainer container;
25 GdkWindow *offscreen_window;
29 struct _GtkRotatedBinClass
31 GtkContainerClass parent_class;
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,
39 /*** implementation ***/
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);
52 static void gtk_rotated_bin_add (GtkContainer *container,
54 static void gtk_rotated_bin_remove (GtkContainer *container,
56 static void gtk_rotated_bin_forall (GtkContainer *container,
57 gboolean include_internals,
59 gpointer callback_data);
60 static GType gtk_rotated_bin_child_type (GtkContainer *container);
62 G_DEFINE_TYPE (GtkRotatedBin, gtk_rotated_bin, GTK_TYPE_CONTAINER);
65 to_child (GtkRotatedBin *bin,
71 GtkAllocation child_area;
76 sincos (bin->angle, &s, &c);
77 child_area = bin->child->allocation;
79 w = c * child_area.width + s * child_area.height;
80 h = s * child_area.width + c * child_area.height;
85 x -= (w - child_area.width) / 2;
86 y -= (h - child_area.height) / 2;
88 x -= child_area.width / 2;
89 y -= child_area.height / 2;
96 x += child_area.width / 2;
97 y += child_area.height / 2;
104 to_parent (GtkRotatedBin *bin,
110 GtkAllocation child_area;
115 sincos (bin->angle, &s, &c);
116 child_area = bin->child->allocation;
118 w = c * child_area.width + s * child_area.height;
119 h = s * child_area.width + c * child_area.height;
124 x -= child_area.width / 2;
125 y -= child_area.height / 2;
132 x += child_area.width / 2;
133 y += child_area.height / 2;
135 x -= (w - child_area.width) / 2;
136 y -= (h - child_area.height) / 2;
143 gtk_rotated_bin_class_init (GtkRotatedBinClass *klass)
145 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
146 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
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;
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),
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;
166 gtk_rotated_bin_init (GtkRotatedBin *bin)
168 GTK_WIDGET_UNSET_FLAGS (bin, GTK_NO_WINDOW);
172 gtk_rotated_bin_new (void)
174 return g_object_new (GTK_TYPE_ROTATED_BIN, NULL);
178 pick_offscreen_child (GdkWindow *offscreen_window,
183 GtkAllocation child_area;
186 if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
188 to_child (bin, widget_x, widget_y, &x, &y);
190 child_area = bin->child->allocation;
192 if (x >= 0 && x < child_area.width &&
193 y >= 0 && y < child_area.height)
194 return bin->offscreen_window;
201 offscreen_window_to_parent (GdkWindow *offscreen_window,
208 to_parent (bin, offscreen_x, offscreen_y, parent_x, parent_y);
212 offscreen_window_from_parent (GdkWindow *window,
219 to_child (bin, parent_x, parent_y, offscreen_x, offscreen_y);
223 gtk_rotated_bin_realize (GtkWidget *widget)
225 GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
226 GdkWindowAttr attributes;
227 gint attributes_mask;
229 GtkRequisition child_requisition;
231 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
233 border_width = GTK_CONTAINER (widget)->border_width;
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)
242 | GDK_POINTER_MOTION_MASK
243 | GDK_BUTTON_PRESS_MASK
244 | GDK_BUTTON_RELEASE_MASK
246 | GDK_ENTER_NOTIFY_MASK
247 | GDK_LEAVE_NOTIFY_MASK;
249 attributes.visual = gtk_widget_get_visual (widget);
250 attributes.colormap = gtk_widget_get_colormap (widget);
251 attributes.wclass = GDK_INPUT_OUTPUT;
253 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
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);
261 attributes.window_type = GDK_WINDOW_OFFSCREEN;
263 child_requisition.width = child_requisition.height = 0;
264 if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
266 attributes.width = bin->child->allocation.width;
267 attributes.height = bin->child->allocation.height;
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);
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);
280 widget->style = gtk_style_attach (widget->style, widget->window);
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);
288 gtk_rotated_bin_unrealize (GtkWidget *widget)
290 GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
292 gdk_window_set_user_data (bin->offscreen_window, NULL);
293 gdk_window_destroy (bin->offscreen_window);
294 bin->offscreen_window = NULL;
296 GTK_WIDGET_CLASS (gtk_rotated_bin_parent_class)->unrealize (widget);
300 gtk_rotated_bin_child_type (GtkContainer *container)
302 GtkRotatedBin *bin = GTK_ROTATED_BIN (container);
307 return GTK_TYPE_WIDGET;
311 gtk_rotated_bin_add (GtkContainer *container,
314 GtkRotatedBin *bin = GTK_ROTATED_BIN (container);
318 gtk_widget_set_parent_window (widget, bin->offscreen_window);
319 gtk_widget_set_parent (widget, GTK_WIDGET (bin));
323 g_warning ("GtkRotatedBin cannot have more than one child\n");
327 gtk_rotated_bin_remove (GtkContainer *container,
330 GtkRotatedBin *bin = GTK_ROTATED_BIN (container);
331 gboolean was_visible;
333 was_visible = GTK_WIDGET_VISIBLE (widget);
335 if (bin->child == widget)
337 gtk_widget_unparent (widget);
341 if (was_visible && GTK_WIDGET_VISIBLE (container))
342 gtk_widget_queue_resize (GTK_WIDGET (container));
347 gtk_rotated_bin_forall (GtkContainer *container,
348 gboolean include_internals,
349 GtkCallback callback,
350 gpointer callback_data)
352 GtkRotatedBin *bin = GTK_ROTATED_BIN (container);
354 g_return_if_fail (callback != NULL);
357 (*callback) (bin->child, callback_data);
361 gtk_rotated_bin_set_angle (GtkRotatedBin *bin,
364 g_return_if_fail (GTK_IS_ROTATED_BIN (bin));
367 gtk_widget_queue_resize (GTK_WIDGET (bin));
369 /* TODO: Really needs to resent pointer events if over the rotated window */
373 gtk_rotated_bin_size_request (GtkWidget *widget,
374 GtkRequisition *requisition)
376 GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
377 GtkRequisition child_requisition;
381 child_requisition.width = 0;
382 child_requisition.height = 0;
384 if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
385 gtk_widget_size_request (bin->child, &child_requisition);
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;
391 requisition->width = GTK_CONTAINER (widget)->border_width * 2 + w;
392 requisition->height = GTK_CONTAINER (widget)->border_width * 2 + h;
396 gtk_rotated_bin_size_allocate (GtkWidget *widget,
397 GtkAllocation *allocation)
399 GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
404 widget->allocation = *allocation;
406 border_width = GTK_CONTAINER (widget)->border_width;
408 w = allocation->width - border_width * 2;
409 h = allocation->height - border_width * 2;
411 if (GTK_WIDGET_REALIZED (widget))
412 gdk_window_move_resize (widget->window,
413 allocation->x + border_width,
414 allocation->y + border_width,
417 if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
419 GtkRequisition child_requisition;
420 GtkAllocation child_allocation;
422 sincos (bin->angle, &s, &c);
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;
429 child_allocation.width = h / s;
431 child_allocation.width = w / c;
433 child_allocation.width = MIN ((w - s * child_allocation.height) / c,
434 (h - c * child_allocation.height) / s);
436 if (GTK_WIDGET_REALIZED (widget))
437 gdk_window_move_resize (bin->offscreen_window,
440 child_allocation.width,
441 child_allocation.height);
443 child_allocation.x = child_allocation.y = 0;
444 gtk_widget_size_allocate (bin->child, &child_allocation);
449 gtk_rotated_bin_damage (GtkWidget *widget,
450 GdkEventExpose *event)
452 gdk_window_invalidate_rect (widget->window, NULL, FALSE);
458 gtk_rotated_bin_expose (GtkWidget *widget,
459 GdkEventExpose *event)
461 GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
466 if (GTK_WIDGET_DRAWABLE (widget))
468 if (event->window == widget->window)
471 GtkAllocation child_area;
474 if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
476 pixmap = gdk_offscreen_window_get_pixmap (bin->offscreen_window);
477 child_area = bin->child->allocation;
479 cr = gdk_cairo_create (widget->window);
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;
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);
492 gdk_drawable_get_size (pixmap, &width, &height);
493 cairo_rectangle (cr, 0, 0, width, height);
496 gdk_cairo_set_source_pixmap (cr, pixmap, 0, 0);
502 else if (event->window == bin->offscreen_window)
504 gtk_paint_flat_box (widget->style, event->window,
505 GTK_STATE_NORMAL, GTK_SHADOW_NONE,
506 &event->area, widget, "blah",
510 gtk_container_propagate_expose (GTK_CONTAINER (widget),
522 scale_changed (GtkRange *range,
525 gtk_rotated_bin_set_angle (bin, gtk_range_get_value (range));
528 static GtkWidget *window = NULL;
531 do_offscreen_window (GtkWidget *do_widget)
535 GtkWidget *bin, *vbox, *scale, *button;
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");
543 g_signal_connect (window, "destroy",
544 G_CALLBACK (gtk_widget_destroyed), &window);
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);
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);
554 button = gtk_button_new_with_label ("A Button");
555 bin = gtk_rotated_bin_new ();
557 g_signal_connect (scale, "value-changed", G_CALLBACK (scale_changed), bin);
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);
565 if (!GTK_WIDGET_VISIBLE (window))
566 gtk_widget_show_all (window);
569 gtk_widget_destroy (window);