diff options
author | Matthias Clasen <mclasen@redhat.com> | 2009-08-31 00:20:39 -0400 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2009-08-31 00:20:39 -0400 |
commit | 97d4b25d02e53024cf3e690a0ce68062c1cf9c24 (patch) | |
tree | ce12b35132527018b7474720a6cd49b56454d44d /demos | |
parent | ab93e1b8e8ecd69658fd445cb3c2b91d41ae510e (diff) | |
download | gtk+-97d4b25d02e53024cf3e690a0ce68062c1cf9c24.tar.gz |
Add an offscreen window demo
Diffstat (limited to 'demos')
-rw-r--r-- | demos/gtk-demo/Makefile.am | 1 | ||||
-rw-r--r-- | demos/gtk-demo/offscreen_window.c | 575 |
2 files changed, 576 insertions, 0 deletions
diff --git a/demos/gtk-demo/Makefile.am b/demos/gtk-demo/Makefile.am index 22662c43b0..f1fc24826a 100644 --- a/demos/gtk-demo/Makefile.am +++ b/demos/gtk-demo/Makefile.am @@ -28,6 +28,7 @@ demos = \ links.c \ list_store.c \ menus.c \ + offscreen_window.c \ panes.c \ pickers.c \ pixbufs.c \ diff --git a/demos/gtk-demo/offscreen_window.c b/demos/gtk-demo/offscreen_window.c new file mode 100644 index 0000000000..15db3d5956 --- /dev/null +++ b/demos/gtk-demo/offscreen_window.c @@ -0,0 +1,575 @@ +/* Offscreen windows + * + * Offscreen windows can be used to transform parts of a widget + * hierarchy. Note that the rotated button is fully functional. + */ +#define _GNU_SOURCE +#include <math.h> +#include <gtk/gtk.h> + +#define GTK_TYPE_ROTATED_BIN (gtk_rotated_bin_get_type ()) +#define GTK_ROTATED_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_ROTATED_BIN, GtkRotatedBin)) +#define GTK_ROTATED_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_ROTATED_BIN, GtkRotatedBinClass)) +#define GTK_IS_ROTATED_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_ROTATED_BIN)) +#define GTK_IS_ROTATED_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_ROTATED_BIN)) +#define GTK_ROTATED_BIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_ROTATED_BIN, GtkRotatedBinClass)) + +typedef struct _GtkRotatedBin GtkRotatedBin; +typedef struct _GtkRotatedBinClass GtkRotatedBinClass; + +struct _GtkRotatedBin +{ + GtkContainer container; + + GtkWidget *child; + GdkWindow *offscreen_window; + gdouble angle; +}; + +struct _GtkRotatedBinClass +{ + GtkContainerClass parent_class; +}; + +GType gtk_rotated_bin_get_type (void) G_GNUC_CONST; +GtkWidget* gtk_rotated_bin_new (void); +void gtk_rotated_bin_set_angle (GtkRotatedBin *bin, + gdouble angle); + +/*** implementation ***/ + +static void gtk_rotated_bin_realize (GtkWidget *widget); +static void gtk_rotated_bin_unrealize (GtkWidget *widget); +static void gtk_rotated_bin_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void gtk_rotated_bin_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static gboolean gtk_rotated_bin_damage (GtkWidget *widget, + GdkEventExpose *event); +static gboolean gtk_rotated_bin_expose (GtkWidget *widget, + GdkEventExpose *offscreen); + +static void gtk_rotated_bin_add (GtkContainer *container, + GtkWidget *child); +static void gtk_rotated_bin_remove (GtkContainer *container, + GtkWidget *widget); +static void gtk_rotated_bin_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data); +static GType gtk_rotated_bin_child_type (GtkContainer *container); + +G_DEFINE_TYPE (GtkRotatedBin, gtk_rotated_bin, GTK_TYPE_CONTAINER); + +static void +to_child (GtkRotatedBin *bin, + double widget_x, + double widget_y, + double *x_out, + double *y_out) +{ + GtkAllocation child_area; + double x, y, xr, yr; + double c, s; + double w, h; + + sincos (bin->angle, &s, &c); + child_area = bin->child->allocation; + + w = c * child_area.width + s * child_area.height; + h = s * child_area.width + c * child_area.height; + + x = widget_x; + y = widget_y; + + x -= (w - child_area.width) / 2; + y -= (h - child_area.height) / 2; + + x -= child_area.width / 2; + y -= child_area.height / 2; + + xr = x * c + y * s; + yr = y * c - x * s; + x = xr; + y = yr; + + x += child_area.width / 2; + y += child_area.height / 2; + + *x_out = x; + *y_out = y; +} + +static void +to_parent (GtkRotatedBin *bin, + double offscreen_x, + double offscreen_y, + double *x_out, + double *y_out) +{ + GtkAllocation child_area; + double x, y, xr, yr; + double c, s; + double w, h; + + sincos (bin->angle, &s, &c); + child_area = bin->child->allocation; + + w = c * child_area.width + s * child_area.height; + h = s * child_area.width + c * child_area.height; + + x = offscreen_x; + y = offscreen_y; + + x -= child_area.width / 2; + y -= child_area.height / 2; + + xr = x * c - y * s; + yr = x * s + y * c; + x = xr; + y = yr; + + x += child_area.width / 2; + y += child_area.height / 2; + + x -= (w - child_area.width) / 2; + y -= (h - child_area.height) / 2; + + *x_out = x; + *y_out = y; +} + +static void +gtk_rotated_bin_class_init (GtkRotatedBinClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + widget_class->realize = gtk_rotated_bin_realize; + widget_class->unrealize = gtk_rotated_bin_unrealize; + widget_class->size_request = gtk_rotated_bin_size_request; + widget_class->size_allocate = gtk_rotated_bin_size_allocate; + widget_class->expose_event = gtk_rotated_bin_expose; + + g_signal_override_class_closure (g_signal_lookup ("damage-event", GTK_TYPE_WIDGET), + GTK_TYPE_ROTATED_BIN, + g_cclosure_new (G_CALLBACK (gtk_rotated_bin_damage), + NULL, NULL)); + + container_class->add = gtk_rotated_bin_add; + container_class->remove = gtk_rotated_bin_remove; + container_class->forall = gtk_rotated_bin_forall; + container_class->child_type = gtk_rotated_bin_child_type; +} + +static void +gtk_rotated_bin_init (GtkRotatedBin *bin) +{ + GTK_WIDGET_UNSET_FLAGS (bin, GTK_NO_WINDOW); +} + +GtkWidget * +gtk_rotated_bin_new (void) +{ + return g_object_new (GTK_TYPE_ROTATED_BIN, NULL); +} + +static GdkWindow * +pick_offscreen_child (GdkWindow *offscreen_window, + double widget_x, + double widget_y, + GtkRotatedBin *bin) +{ + GtkAllocation child_area; + double x, y; + + if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) + { + to_child (bin, widget_x, widget_y, &x, &y); + + child_area = bin->child->allocation; + + if (x >= 0 && x < child_area.width && + y >= 0 && y < child_area.height) + return bin->offscreen_window; + } + + return NULL; +} + +static void +offscreen_window_to_parent (GdkWindow *offscreen_window, + double offscreen_x, + double offscreen_y, + double *parent_x, + double *parent_y, + GtkRotatedBin *bin) +{ + to_parent (bin, offscreen_x, offscreen_y, parent_x, parent_y); +} + +static void +offscreen_window_from_parent (GdkWindow *window, + double parent_x, + double parent_y, + double *offscreen_x, + double *offscreen_y, + GtkRotatedBin *bin) +{ + to_child (bin, parent_x, parent_y, offscreen_x, offscreen_y); +} + +static void +gtk_rotated_bin_realize (GtkWidget *widget) +{ + GtkRotatedBin *bin = GTK_ROTATED_BIN (widget); + GdkWindowAttr attributes; + gint attributes_mask; + gint border_width; + GtkRequisition child_requisition; + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + + border_width = GTK_CONTAINER (widget)->border_width; + + attributes.x = widget->allocation.x + border_width; + attributes.y = widget->allocation.y + border_width; + attributes.width = widget->allocation.width - 2 * border_width; + attributes.height = widget->allocation.height - 2 * border_width; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.event_mask = gtk_widget_get_events (widget) + | GDK_EXPOSURE_MASK + | GDK_POINTER_MOTION_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_SCROLL_MASK + | GDK_ENTER_NOTIFY_MASK + | GDK_LEAVE_NOTIFY_MASK; + + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.wclass = GDK_INPUT_OUTPUT; + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + gdk_window_set_user_data (widget->window, widget); + g_signal_connect (widget->window, "pick-embedded-child", + G_CALLBACK (pick_offscreen_child), bin); + + attributes.window_type = GDK_WINDOW_OFFSCREEN; + + child_requisition.width = child_requisition.height = 0; + if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) + { + attributes.width = bin->child->allocation.width; + attributes.height = bin->child->allocation.height; + } + bin->offscreen_window = gdk_window_new (gtk_widget_get_root_window (widget), + &attributes, attributes_mask); + gdk_window_set_user_data (bin->offscreen_window, widget); + if (bin->child) + gtk_widget_set_parent_window (bin->child, bin->offscreen_window); + gdk_offscreen_window_set_embedder (bin->offscreen_window, widget->window); + g_signal_connect (bin->offscreen_window, "to-embedder", + G_CALLBACK (offscreen_window_to_parent), bin); + g_signal_connect (bin->offscreen_window, "from-embedder", + G_CALLBACK (offscreen_window_from_parent), bin); + + widget->style = gtk_style_attach (widget->style, widget->window); + + gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); + gtk_style_set_background (widget->style, bin->offscreen_window, GTK_STATE_NORMAL); + gdk_window_show (bin->offscreen_window); +} + +static void +gtk_rotated_bin_unrealize (GtkWidget *widget) +{ + GtkRotatedBin *bin = GTK_ROTATED_BIN (widget); + + gdk_window_set_user_data (bin->offscreen_window, NULL); + gdk_window_destroy (bin->offscreen_window); + bin->offscreen_window = NULL; + + GTK_WIDGET_CLASS (gtk_rotated_bin_parent_class)->unrealize (widget); +} + +static GType +gtk_rotated_bin_child_type (GtkContainer *container) +{ + GtkRotatedBin *bin = GTK_ROTATED_BIN (container); + + if (bin->child) + return G_TYPE_NONE; + + return GTK_TYPE_WIDGET; +} + +static void +gtk_rotated_bin_add (GtkContainer *container, + GtkWidget *widget) +{ + GtkRotatedBin *bin = GTK_ROTATED_BIN (container); + + if (!bin->child) + { + gtk_widget_set_parent_window (widget, bin->offscreen_window); + gtk_widget_set_parent (widget, GTK_WIDGET (bin)); + bin->child = widget; + } + else + g_warning ("GtkRotatedBin cannot have more than one child\n"); +} + +static void +gtk_rotated_bin_remove (GtkContainer *container, + GtkWidget *widget) +{ + GtkRotatedBin *bin = GTK_ROTATED_BIN (container); + gboolean was_visible; + + was_visible = GTK_WIDGET_VISIBLE (widget); + + if (bin->child == widget) + { + gtk_widget_unparent (widget); + + bin->child = NULL; + + if (was_visible && GTK_WIDGET_VISIBLE (container)) + gtk_widget_queue_resize (GTK_WIDGET (container)); + } +} + +static void +gtk_rotated_bin_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + GtkRotatedBin *bin = GTK_ROTATED_BIN (container); + + g_return_if_fail (callback != NULL); + + if (bin->child) + (*callback) (bin->child, callback_data); +} + +void +gtk_rotated_bin_set_angle (GtkRotatedBin *bin, + gdouble angle) +{ + g_return_if_fail (GTK_IS_ROTATED_BIN (bin)); + + bin->angle = angle; + gtk_widget_queue_resize (GTK_WIDGET (bin)); + + /* TODO: Really needs to resent pointer events if over the rotated window */ +} + +static void +gtk_rotated_bin_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GtkRotatedBin *bin = GTK_ROTATED_BIN (widget); + GtkRequisition child_requisition; + double s, c; + double w, h; + + child_requisition.width = 0; + child_requisition.height = 0; + + if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) + gtk_widget_size_request (bin->child, &child_requisition); + + sincos (bin->angle, &s, &c); + w = c * child_requisition.width + s * child_requisition.height; + h = s * child_requisition.width + c * child_requisition.height; + + requisition->width = GTK_CONTAINER (widget)->border_width * 2 + w; + requisition->height = GTK_CONTAINER (widget)->border_width * 2 + h; +} + +static void +gtk_rotated_bin_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkRotatedBin *bin = GTK_ROTATED_BIN (widget); + gint border_width; + gint w, h; + gdouble s, c; + + widget->allocation = *allocation; + + border_width = GTK_CONTAINER (widget)->border_width; + + w = allocation->width - border_width * 2; + h = allocation->height - border_width * 2; + + if (GTK_WIDGET_REALIZED (widget)) + gdk_window_move_resize (widget->window, + allocation->x + border_width, + allocation->y + border_width, + w, h); + + if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) + { + GtkRequisition child_requisition; + GtkAllocation child_allocation; + + sincos (bin->angle, &s, &c); + + gtk_widget_get_child_requisition (bin->child, &child_requisition); + child_allocation.x = 0; + child_allocation.y = 0; + child_allocation.height = child_requisition.height; + if (c == 0.0) + child_allocation.width = h / s; + else if (s == 0.0) + child_allocation.width = w / c; + else + child_allocation.width = MIN ((w - s * child_allocation.height) / c, + (h - c * child_allocation.height) / s); + + if (GTK_WIDGET_REALIZED (widget)) + gdk_window_move_resize (bin->offscreen_window, + child_allocation.x, + child_allocation.y, + child_allocation.width, + child_allocation.height); + + child_allocation.x = child_allocation.y = 0; + gtk_widget_size_allocate (bin->child, &child_allocation); + } +} + +static gboolean +gtk_rotated_bin_damage (GtkWidget *widget, + GdkEventExpose *event) +{ + gdk_window_invalidate_rect (widget->window, NULL, FALSE); + + return TRUE; +} + +static gboolean +gtk_rotated_bin_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GtkRotatedBin *bin = GTK_ROTATED_BIN (widget); + gint width, height; + gdouble s, c; + gdouble w, h; + + if (GTK_WIDGET_DRAWABLE (widget)) + { + if (event->window == widget->window) + { + GdkPixmap *pixmap; + GtkAllocation child_area; + cairo_t *cr; + + if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) + { + pixmap = gdk_offscreen_window_get_pixmap (bin->offscreen_window); + child_area = bin->child->allocation; + + cr = gdk_cairo_create (widget->window); + + /* transform */ + sincos (bin->angle, &s, &c); + w = c * child_area.width + s * child_area.height; + h = s * child_area.width + c * child_area.height; + + cairo_translate (cr, (w - child_area.width) / 2, (h - child_area.height) / 2); + cairo_translate (cr, child_area.width / 2, child_area.height / 2); + cairo_rotate (cr, bin->angle); + cairo_translate (cr, -child_area.width / 2, -child_area.height / 2); + + /* clip */ + gdk_drawable_get_size (pixmap, &width, &height); + cairo_rectangle (cr, 0, 0, width, height); + cairo_clip (cr); + /* paint */ + gdk_cairo_set_source_pixmap (cr, pixmap, 0, 0); + cairo_paint (cr); + + cairo_destroy (cr); + } + } + else if (event->window == bin->offscreen_window) + { + gtk_paint_flat_box (widget->style, event->window, + GTK_STATE_NORMAL, GTK_SHADOW_NONE, + &event->area, widget, "blah", + 0, 0, -1, -1); + + if (bin->child) + gtk_container_propagate_expose (GTK_CONTAINER (widget), + bin->child, + event); + } + } + + return FALSE; +} + +/*** ***/ + +static void +scale_changed (GtkRange *range, + GtkRotatedBin *bin) +{ + gtk_rotated_bin_set_angle (bin, gtk_range_get_value (range)); +} + +static GtkWidget *window = NULL; + +GtkWidget * +do_offscreen_window (GtkWidget *do_widget) +{ + if (!window) + { + GtkWidget *bin, *vbox, *scale, *button; + GdkColor black; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_screen (GTK_WINDOW (window), + gtk_widget_get_screen (do_widget)); + gtk_window_set_title (GTK_WINDOW (window), "Offscreen windows"); + + g_signal_connect (window, "destroy", + G_CALLBACK (gtk_widget_destroyed), &window); + + gdk_color_parse ("black", &black); + gtk_widget_modify_bg (window, GTK_STATE_NORMAL, &black); + gtk_container_set_border_width (GTK_CONTAINER (window), 10); + + vbox = gtk_vbox_new (0, FALSE); + scale = gtk_hscale_new_with_range (0, G_PI/2, 0.01); + gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE); + + button = gtk_button_new_with_label ("A Button"); + bin = gtk_rotated_bin_new (); + + g_signal_connect (scale, "value-changed", G_CALLBACK (scale_changed), bin); + + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), bin, TRUE, TRUE, 0); + gtk_container_add (GTK_CONTAINER (bin), button); + } + + if (!GTK_WIDGET_VISIBLE (window)) + gtk_widget_show_all (window); + else + { + gtk_widget_destroy (window); + window = NULL; + } + + return window; +} + |