/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * pikaoverlaychild.c * Copyright (C) 2009 Michael Natterer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include "widgets-types.h" #include "core/pika-utils.h" #include "pikaoverlaybox.h" #include "pikaoverlaychild.h" #include "pikawidgets-utils.h" /* local function prototypes */ static void pika_overlay_child_transform_bounds (PikaOverlayChild *child, GdkRectangle *bounds_child, GdkRectangle *bounds_box); static void pika_overlay_child_from_embedder (GdkWindow *child_window, gdouble box_x, gdouble box_y, gdouble *child_x, gdouble *child_y, PikaOverlayChild *child); static void pika_overlay_child_to_embedder (GdkWindow *child_window, gdouble child_x, gdouble child_y, gdouble *box_x, gdouble *box_y, PikaOverlayChild *child); /* public functions */ PikaOverlayChild * pika_overlay_child_new (PikaOverlayBox *box, GtkWidget *widget, gdouble xalign, gdouble yalign, gdouble angle, gdouble opacity) { PikaOverlayChild *child; g_return_val_if_fail (PIKA_IS_OVERLAY_BOX (box), NULL); g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); child = g_slice_new0 (PikaOverlayChild); child->widget = widget; child->xalign = CLAMP (xalign, 0.0, 1.0); child->yalign = CLAMP (yalign, 0.0, 1.0); child->x = 0.0; child->y = 0.0; child->has_position = FALSE; child->angle = angle; child->opacity = CLAMP (opacity, 0.0, 1.0); cairo_matrix_init_identity (&child->matrix); if (gtk_widget_get_realized (GTK_WIDGET (box))) pika_overlay_child_realize (box, child); gtk_widget_set_parent (widget, GTK_WIDGET (box)); return child; } void pika_overlay_child_free (PikaOverlayBox *box, PikaOverlayChild *child) { g_return_if_fail (PIKA_IS_OVERLAY_BOX (box)); g_return_if_fail (child != NULL); gtk_widget_unparent (child->widget); if (gtk_widget_get_realized (GTK_WIDGET (box))) pika_overlay_child_unrealize (box, child); g_slice_free (PikaOverlayChild, child); } PikaOverlayChild * pika_overlay_child_find (PikaOverlayBox *box, GtkWidget *widget) { GList *list; g_return_val_if_fail (PIKA_IS_OVERLAY_BOX (box), NULL); g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); g_return_val_if_fail (gtk_widget_get_parent (widget) == GTK_WIDGET (box), NULL); for (list = box->children; list; list = g_list_next (list)) { PikaOverlayChild *child = list->data; if (child->widget == widget) return child; } return NULL; } void pika_overlay_child_realize (PikaOverlayBox *box, PikaOverlayChild *child) { GtkWidget *widget; GdkDisplay *display; GdkScreen *screen; GdkVisual *visual; GtkAllocation child_allocation; GdkWindowAttr attributes; gint attributes_mask; g_return_if_fail (PIKA_IS_OVERLAY_BOX (box)); g_return_if_fail (child != NULL); g_return_if_fail (child->window == NULL); widget = GTK_WIDGET (box); display = gtk_widget_get_display (widget); screen = gtk_widget_get_screen (widget); visual = gdk_screen_get_rgba_visual (screen); if (visual) gtk_widget_set_visual (child->widget, visual); gtk_widget_get_allocation (child->widget, &child_allocation); if (gtk_widget_get_visible (child->widget)) { attributes.width = child_allocation.width; attributes.height = child_allocation.height; } else { attributes.width = 1; attributes.height = 1; } attributes.x = child_allocation.x; attributes.y = child_allocation.y; attributes.window_type = GDK_WINDOW_OFFSCREEN; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual (child->widget); attributes.event_mask = (gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK); attributes.cursor = gdk_cursor_new_for_display (display, GDK_LEFT_PTR); attributes_mask = (GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_CURSOR); child->window = gdk_window_new (gdk_screen_get_root_window (screen), &attributes, attributes_mask); gdk_window_set_user_data (child->window, widget); gtk_widget_set_parent_window (child->widget, child->window); gdk_offscreen_window_set_embedder (child->window, gtk_widget_get_window (widget)); g_object_unref (attributes.cursor); g_signal_connect (child->window, "from-embedder", G_CALLBACK (pika_overlay_child_from_embedder), child); g_signal_connect (child->window, "to-embedder", G_CALLBACK (pika_overlay_child_to_embedder), child); gdk_window_show (child->window); } void pika_overlay_child_unrealize (PikaOverlayBox *box, PikaOverlayChild *child) { g_return_if_fail (PIKA_IS_OVERLAY_BOX (box)); g_return_if_fail (child != NULL); g_return_if_fail (child->window != NULL); gdk_window_set_user_data (child->window, NULL); gdk_window_destroy (child->window); child->window = NULL; } void pika_overlay_child_get_preferred_width (PikaOverlayBox *box, PikaOverlayChild *child, gint *minimum, gint *natural) { g_return_if_fail (PIKA_IS_OVERLAY_BOX (box)); g_return_if_fail (child != NULL); gtk_widget_get_preferred_width (child->widget, minimum, natural); } void pika_overlay_child_get_preferred_height (PikaOverlayBox *box, PikaOverlayChild *child, gint *minimum, gint *natural) { g_return_if_fail (PIKA_IS_OVERLAY_BOX (box)); g_return_if_fail (child != NULL); gtk_widget_get_preferred_height (child->widget, minimum, natural); } void pika_overlay_child_size_allocate (PikaOverlayBox *box, PikaOverlayChild *child) { GtkWidget *widget; GtkRequisition child_requisition; GtkAllocation child_allocation; gint x; gint y; g_return_if_fail (PIKA_IS_OVERLAY_BOX (box)); g_return_if_fail (child != NULL); widget = GTK_WIDGET (box); pika_overlay_child_invalidate (box, child); gtk_widget_get_preferred_size (child->widget, &child_requisition, NULL); child_allocation.x = 0; child_allocation.y = 0; child_allocation.width = child_requisition.width; child_allocation.height = child_requisition.height; gtk_widget_size_allocate (child->widget, &child_allocation); if (gtk_widget_get_realized (GTK_WIDGET (widget))) gdk_window_move_resize (child->window, child_allocation.x, child_allocation.y, child_allocation.width, child_allocation.height); cairo_matrix_init_identity (&child->matrix); /* local transform */ cairo_matrix_rotate (&child->matrix, child->angle); if (child->has_position) { x = child->x; y = child->y; } else { GtkAllocation allocation; GdkRectangle bounds; gint border; gint available_width; gint available_height; gtk_widget_get_allocation (widget, &allocation); pika_overlay_child_transform_bounds (child, &child_allocation, &bounds); border = gtk_container_get_border_width (GTK_CONTAINER (box)); available_width = allocation.width - 2 * border; available_height = allocation.height - 2 * border; x = border; y = border; if (available_width > bounds.width) x += child->xalign * (available_width - bounds.width) - bounds.x; if (available_height > bounds.height) y += child->yalign * (available_height - bounds.height) - bounds.y; } cairo_matrix_init_translate (&child->matrix, x, y); /* local transform */ cairo_matrix_rotate (&child->matrix, child->angle); pika_overlay_child_invalidate (box, child); } static void pika_overlay_child_clip_fully_opaque (PikaOverlayChild *child, GtkContainer *container, cairo_t *cr) { GList *children; GList *list; children = gtk_container_get_children (container); for (list = children; list; list = g_list_next (list)) { GtkWidget *widget = list->data; if (pika_widget_get_fully_opaque (widget)) { GtkAllocation allocation; gint x, y; gtk_widget_get_allocation (widget, &allocation); gtk_widget_translate_coordinates (widget, child->widget, 0, 0, &x, &y); cairo_rectangle (cr, x, y, allocation.width, allocation.height); } else if (GTK_IS_CONTAINER (widget)) { pika_overlay_child_clip_fully_opaque (child, GTK_CONTAINER (widget), cr); } } g_list_free (children); } gboolean pika_overlay_child_draw (PikaOverlayBox *box, PikaOverlayChild *child, cairo_t *cr) { GtkWidget *widget; g_return_val_if_fail (PIKA_IS_OVERLAY_BOX (box), FALSE); g_return_val_if_fail (child != NULL, FALSE); g_return_val_if_fail (cr != NULL, FALSE); widget = GTK_WIDGET (box); if (gtk_cairo_should_draw_window (cr, gtk_widget_get_window (widget))) { GtkAllocation child_allocation; GdkRectangle bounds; gtk_widget_get_allocation (child->widget, &child_allocation); pika_overlay_child_transform_bounds (child, &child_allocation, &bounds); if (gtk_widget_get_visible (child->widget)) { cairo_surface_t *surface; surface = gdk_offscreen_window_get_surface (child->window); cairo_save (cr); cairo_transform (cr, &child->matrix); cairo_set_source_surface (cr, surface, 0, 0); cairo_paint_with_alpha (cr, child->opacity); pika_overlay_child_clip_fully_opaque (child, GTK_CONTAINER (child->widget), cr); cairo_clip (cr); cairo_paint (cr); cairo_restore (cr); } } if (gtk_cairo_should_draw_window (cr, child->window)) { if (! gtk_widget_get_app_paintable (child->widget)) { GtkStyleContext *style = gtk_widget_get_style_context (child->widget); gtk_render_background (style, cr, 0, 0, gtk_widget_get_allocated_width (child->widget), gtk_widget_get_allocated_height (child->widget)); } gtk_container_propagate_draw (GTK_CONTAINER (widget), child->widget, cr); return TRUE; } return FALSE; } gboolean pika_overlay_child_damage (PikaOverlayBox *box, PikaOverlayChild *child, GdkEventExpose *event) { GtkWidget *widget; g_return_val_if_fail (PIKA_IS_OVERLAY_BOX (box), FALSE); g_return_val_if_fail (child != NULL, FALSE); g_return_val_if_fail (event != NULL, FALSE); widget = GTK_WIDGET (box); if (event->window == child->window) { gint n_rects; gint i; n_rects = cairo_region_num_rectangles (event->region); for (i = 0; i < n_rects; i++) { GdkRectangle rect; GdkRectangle bounds; cairo_region_get_rectangle (event->region, i, &rect); pika_overlay_child_transform_bounds (child, &rect, &bounds); gdk_window_invalidate_rect (gtk_widget_get_window (widget), &bounds, FALSE); } return TRUE; } return FALSE; } void pika_overlay_child_invalidate (PikaOverlayBox *box, PikaOverlayChild *child) { GdkWindow *window; g_return_if_fail (PIKA_IS_OVERLAY_BOX (box)); g_return_if_fail (child != NULL); window = gtk_widget_get_window (GTK_WIDGET (box)); if (window && gtk_widget_get_visible (child->widget)) { GtkAllocation child_allocation; GdkRectangle bounds; gtk_widget_get_allocation (child->widget, &child_allocation); pika_overlay_child_transform_bounds (child, &child_allocation, &bounds); gdk_window_invalidate_rect (window, &bounds, FALSE); } } gboolean pika_overlay_child_pick (PikaOverlayBox *box, PikaOverlayChild *child, gdouble box_x, gdouble box_y) { GtkAllocation child_allocation; gdouble child_x; gdouble child_y; g_return_val_if_fail (PIKA_IS_OVERLAY_BOX (box), FALSE); g_return_val_if_fail (child != NULL, FALSE); pika_overlay_child_from_embedder (child->window, box_x, box_y, &child_x, &child_y, child); gtk_widget_get_allocation (child->widget, &child_allocation); if (child_x >= 0 && child_x < child_allocation.width && child_y >= 0 && child_y < child_allocation.height) { return TRUE; } return FALSE; } /* private functions */ static void pika_overlay_child_transform_bounds (PikaOverlayChild *child, GdkRectangle *bounds_child, GdkRectangle *bounds_box) { gdouble x1, x2, x3, x4; gdouble y1, y2, y3, y4; x1 = bounds_child->x; y1 = bounds_child->y; x2 = bounds_child->x + bounds_child->width; y2 = bounds_child->y; x3 = bounds_child->x; y3 = bounds_child->y + bounds_child->height; x4 = bounds_child->x + bounds_child->width; y4 = bounds_child->y + bounds_child->height; cairo_matrix_transform_point (&child->matrix, &x1, &y1); cairo_matrix_transform_point (&child->matrix, &x2, &y2); cairo_matrix_transform_point (&child->matrix, &x3, &y3); cairo_matrix_transform_point (&child->matrix, &x4, &y4); bounds_box->x = (gint) floor (MIN4 (x1, x2, x3, x4)); bounds_box->y = (gint) floor (MIN4 (y1, y2, y3, y4)); bounds_box->width = (gint) ceil (MAX4 (x1, x2, x3, x4)) - bounds_box->x; bounds_box->height = (gint) ceil (MAX4 (y1, y2, y3, y4)) - bounds_box->y; } static void pika_overlay_child_from_embedder (GdkWindow *child_window, gdouble box_x, gdouble box_y, gdouble *child_x, gdouble *child_y, PikaOverlayChild *child) { cairo_matrix_t inverse = child->matrix; *child_x = box_x; *child_y = box_y; cairo_matrix_invert (&inverse); cairo_matrix_transform_point (&inverse, child_x, child_y); } static void pika_overlay_child_to_embedder (GdkWindow *child_window, gdouble child_x, gdouble child_y, gdouble *box_x, gdouble *box_y, PikaOverlayChild *child) { *box_x = child_x; *box_y = child_y; cairo_matrix_transform_point (&child->matrix, box_x, box_y); }