diff options
Diffstat (limited to 'eel')
-rw-r--r-- | eel/.gitignore | 1 | ||||
-rwxr-xr-x | eel/check-eel | 3 | ||||
-rw-r--r-- | eel/check-program.c | 59 | ||||
-rw-r--r-- | eel/eel-art-extensions.c | 191 | ||||
-rw-r--r-- | eel/eel-art-extensions.h | 61 | ||||
-rw-r--r-- | eel/eel-canvas.c | 4149 | ||||
-rw-r--r-- | eel/eel-canvas.h | 497 | ||||
-rw-r--r-- | eel/eel-debug.c | 105 | ||||
-rw-r--r-- | eel/eel-debug.h | 40 | ||||
-rw-r--r-- | eel/eel-glib-extensions.h | 30 | ||||
-rw-r--r-- | eel/eel-graphic-effects.c | 165 | ||||
-rw-r--r-- | eel/eel-graphic-effects.h | 32 | ||||
-rw-r--r-- | eel/eel-gtk-extensions.c | 93 | ||||
-rw-r--r-- | eel/eel-gtk-extensions.h | 37 | ||||
-rw-r--r-- | eel/eel-lib-self-check-functions.c | 35 | ||||
-rw-r--r-- | eel/eel-lib-self-check-functions.h | 45 | ||||
-rw-r--r-- | eel/eel-self-checks.c | 203 | ||||
-rw-r--r-- | eel/eel-self-checks.h | 78 | ||||
-rw-r--r-- | eel/eel-stock-dialogs.c | 539 | ||||
-rw-r--r-- | eel/eel-stock-dialogs.h | 75 | ||||
-rw-r--r-- | eel/eel-string.c | 476 | ||||
-rw-r--r-- | eel/eel-string.h | 79 | ||||
-rw-r--r-- | eel/eel-vfs-extensions.c | 195 | ||||
-rw-r--r-- | eel/eel-vfs-extensions.h | 50 | ||||
-rw-r--r-- | eel/eel.h | 32 | ||||
-rw-r--r-- | eel/meson.build | 57 |
26 files changed, 7327 insertions, 0 deletions
diff --git a/eel/.gitignore b/eel/.gitignore new file mode 100644 index 0000000..6ea0174 --- /dev/null +++ b/eel/.gitignore @@ -0,0 +1 @@ +/check-program diff --git a/eel/check-eel b/eel/check-eel new file mode 100755 index 0000000..d1c67b6 --- /dev/null +++ b/eel/check-eel @@ -0,0 +1,3 @@ +#!/bin/sh +./check-program --g-fatal-warnings --sm-disable + diff --git a/eel/check-program.c b/eel/check-program.c new file mode 100644 index 0000000..1e0f0a3 --- /dev/null +++ b/eel/check-program.c @@ -0,0 +1,59 @@ +/* check-program.c: A simple driver for eel self checks. + * + * Copyright (C) 2000 Eazel, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see <http://www.gnu.org/licenses/>. + * + * Authors: Ramiro Estrugo <ramiro@eazel.com> + */ + +#include <config.h> + +#include <eel/eel-debug.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-lib-self-check-functions.h> +#include <eel/eel-self-checks.h> +#include <gdk/gdk.h> +#include <gtk/gtk.h> +#include <libxml/parser.h> +#include <stdlib.h> + +int +main (int argc, + char *argv[]) +{ +#if !defined (EEL_OMIT_SELF_CHECK) + + eel_make_warnings_and_criticals_stop_in_debugger (); + + + LIBXML_TEST_VERSION + gtk_init (&argc, + &argv); + + /* Run the checks for eel twice. */ + + eel_run_lib_self_checks (); + eel_exit_if_self_checks_failed (); + + eel_run_lib_self_checks (); + eel_exit_if_self_checks_failed (); + + eel_debug_shut_down (); + +#endif /* !EEL_OMIT_SELF_CHECK */ + + return EXIT_SUCCESS; +} diff --git a/eel/eel-art-extensions.c b/eel/eel-art-extensions.c new file mode 100644 index 0000000..f9f4c9a --- /dev/null +++ b/eel/eel-art-extensions.c @@ -0,0 +1,191 @@ +/* eel-art-extensions.c - implementation of libart extension functions. + * + * Copyright (C) 2000 Eazel, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see <http://www.gnu.org/licenses/>. + * + * Authors: Darin Adler <darin@eazel.com> + * Ramiro Estrugo <ramiro@eazel.com> + */ + +#include <config.h> + +#include "eel-art-extensions.h" +#include "eel-lib-self-check-functions.h" +#include <math.h> + +const EelDRect eel_drect_empty = { 0.0, 0.0, 0.0, 0.0 }; +const EelIRect eel_irect_empty = { 0, 0, 0, 0 }; + +static void +eel_irect_copy (EelIRect *dest, + const EelIRect *src) +{ + dest->x0 = src->x0; + dest->y0 = src->y0; + dest->x1 = src->x1; + dest->y1 = src->y1; +} + +static gboolean +eel_irect_is_empty (const EelIRect *src) +{ + return (src->x1 <= src->x0 || + src->y1 <= src->y0); +} + +void +eel_irect_union (EelIRect *dest, + const EelIRect *src1, + const EelIRect *src2) +{ + if (eel_irect_is_empty (src1)) + { + eel_irect_copy (dest, src2); + } + else if (eel_irect_is_empty (src2)) + { + eel_irect_copy (dest, src1); + } + else + { + dest->x0 = MIN (src1->x0, src2->x0); + dest->y0 = MIN (src1->y0, src2->y0); + dest->x1 = MAX (src1->x1, src2->x1); + dest->y1 = MAX (src1->y1, src2->y1); + } +} + +static void +eel_irect_intersect (EelIRect *dest, + const EelIRect *src1, + const EelIRect *src2) +{ + dest->x0 = MAX (src1->x0, src2->x0); + dest->y0 = MAX (src1->y0, src2->y0); + dest->x1 = MIN (src1->x1, src2->x1); + dest->y1 = MIN (src1->y1, src2->y1); +} + +/** + * eel_irect_get_width: + * + * @rectangle: An EelIRect. + * + * Returns: The width of the rectangle. + * + */ +int +eel_irect_get_width (EelIRect rectangle) +{ + return rectangle.x1 - rectangle.x0; +} + +/** + * eel_irect_get_height: + * + * @rectangle: An EelIRect. + * + * Returns: The height of the rectangle. + * + */ +int +eel_irect_get_height (EelIRect rectangle) +{ + return rectangle.y1 - rectangle.y0; +} + + +static void +eel_drect_copy (EelDRect *dest, + const EelDRect *src) +{ + dest->x0 = src->x0; + dest->y0 = src->y0; + dest->x1 = src->x1; + dest->y1 = src->y1; +} + +static gboolean +eel_drect_is_empty (const EelDRect *src) +{ + return (src->x1 <= src->x0 || src->y1 <= src->y0); +} + +void +eel_drect_union (EelDRect *dest, + const EelDRect *src1, + const EelDRect *src2) +{ + if (eel_drect_is_empty (src1)) + { + eel_drect_copy (dest, src2); + } + else if (eel_drect_is_empty (src2)) + { + eel_drect_copy (dest, src1); + } + else + { + dest->x0 = MIN (src1->x0, src2->x0); + dest->y0 = MIN (src1->y0, src2->y0); + dest->x1 = MAX (src1->x1, src2->x1); + dest->y1 = MAX (src1->y1, src2->y1); + } +} + +gboolean +eel_irect_hits_irect (EelIRect rectangle_a, + EelIRect rectangle_b) +{ + EelIRect intersection; + eel_irect_intersect (&intersection, &rectangle_a, &rectangle_b); + return !eel_irect_is_empty (&intersection); +} + +gboolean +eel_irect_equal (EelIRect rectangle_a, + EelIRect rectangle_b) +{ + return rectangle_a.x0 == rectangle_b.x0 + && rectangle_a.y0 == rectangle_b.y0 + && rectangle_a.x1 == rectangle_b.x1 + && rectangle_a.y1 == rectangle_b.y1; +} + +EelIRect +eel_irect_offset_by (EelIRect rectangle, + int x, + int y) +{ + rectangle.x0 += x; + rectangle.x1 += x; + rectangle.y0 += y; + rectangle.y1 += y; + + return rectangle; +} + +EelIRect +eel_irect_scale_by (EelIRect rectangle, + double scale) +{ + rectangle.x0 *= scale; + rectangle.x1 *= scale; + rectangle.y0 *= scale; + rectangle.y1 *= scale; + + return rectangle; +} diff --git a/eel/eel-art-extensions.h b/eel/eel-art-extensions.h new file mode 100644 index 0000000..b9dde53 --- /dev/null +++ b/eel/eel-art-extensions.h @@ -0,0 +1,61 @@ + +/* eel-art-extensions.h - interface of libart extension functions. + + Copyright (C) 2000 Eazel, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + see <http://www.gnu.org/licenses/>. + + Authors: Darin Adler <darin@eazel.com> + Ramiro Estrugo <ramiro@eazel.com> +*/ + +#pragma once + +#include <glib.h> + +G_BEGIN_DECLS + +typedef struct { + double x0, y0, x1, y1; +} EelDRect; + +typedef struct { + /*< public >*/ + int x0, y0, x1, y1; +} EelIRect; + +extern const EelDRect eel_drect_empty; +extern const EelIRect eel_irect_empty; + +void eel_irect_union (EelIRect *dest, + const EelIRect *src1, + const EelIRect *src2); +gboolean eel_irect_equal (EelIRect rectangle_a, + EelIRect rectangle_b); +gboolean eel_irect_hits_irect (EelIRect rectangle_a, + EelIRect rectangle_b); +EelIRect eel_irect_offset_by (EelIRect rectangle, + int x, + int y); +EelIRect eel_irect_scale_by (EelIRect rectangle, + double scale); +int eel_irect_get_width (EelIRect rectangle); +int eel_irect_get_height (EelIRect rectangle); + +void eel_drect_union (EelDRect *dest, + const EelDRect *src1, + const EelDRect *src2); + +G_END_DECLS
\ No newline at end of file diff --git a/eel/eel-canvas.c b/eel/eel-canvas.c new file mode 100644 index 0000000..de3902f --- /dev/null +++ b/eel/eel-canvas.c @@ -0,0 +1,4149 @@ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see <http://www.gnu.org/licenses/>. + */ +/* + * @NOTATION@ + */ +/* + * EelCanvas widget - Tk-like canvas widget for Gnome + * + * EelCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * + * Authors: Federico Mena <federico@nuclecu.unam.mx> + * Raph Levien <raph@gimp.org> + */ + +/* + * TO-DO list for the canvas: + * + * - Allow to specify whether EelCanvasImage sizes are in units or pixels (scale or don't scale). + * + * - GC put functions for items. + * + * - Widget item (finish it). + * + * - GList *eel_canvas_gimme_all_items_contained_in_this_area (EelCanvas *canvas, Rectangle area); + * + * - Retrofit all the primitive items with microtile support. + * + * - Curve support for line item. + * + * - Arc item (Havoc has it; to be integrated in EelCanvasEllipse). + * + * - Sane font handling API. + * + * - Get_arg methods for items: + * - How to fetch the outline width and know whether it is in pixels or units? + */ + +#include <config.h> + +#include <math.h> +#include <string.h> +#include <stdio.h> +#include <gtk/gtk.h> +#include <glib/gi18n-lib.h> +#include <cairo-gobject.h> +#include "eel-canvas.h" + +static void eel_canvas_request_update (EelCanvas *canvas); +static void group_add (EelCanvasGroup *group, + EelCanvasItem *item); +static void group_remove (EelCanvasGroup *group, + EelCanvasItem *item); +static void redraw_and_repick_if_mapped (EelCanvasItem *item); + +/*** EelCanvasItem ***/ + +/* Some convenience stuff */ +#define GCI_UPDATE_MASK (EEL_CANVAS_UPDATE_REQUESTED | EEL_CANVAS_UPDATE_DEEP) + +enum +{ + ITEM_PROP_0, + ITEM_PROP_VISIBLE +}; + +enum +{ + ITEM_DESTROY, + ITEM_EVENT, + ITEM_LAST_SIGNAL +}; + +static void eel_canvas_item_class_init (EelCanvasItemClass *klass); +static void eel_canvas_item_init (EelCanvasItem *item); +static int emit_event (EelCanvas *canvas, + GdkEvent *event); + +static guint item_signals[ITEM_LAST_SIGNAL]; + +static GObjectClass *item_parent_class; + +static gpointer accessible_item_parent_class; +static gpointer accessible_parent_class; + + +/** + * eel_canvas_item_get_type: + * + * Registers the &EelCanvasItem class if necessary, and returns the type ID + * associated to it. + * + * Return value: The type ID of the &EelCanvasItem class. + **/ +GType +eel_canvas_item_get_type (void) +{ + static GType canvas_item_type = 0; + + if (!canvas_item_type) + { + static const GTypeInfo canvas_item_info = + { + sizeof (EelCanvasItemClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) eel_canvas_item_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (EelCanvasItem), + 0, /* n_preallocs */ + (GInstanceInitFunc) eel_canvas_item_init + }; + + canvas_item_type = g_type_register_static (G_TYPE_INITIALLY_UNOWNED, + "EelCanvasItem", + &canvas_item_info, + 0); + } + + return canvas_item_type; +} + +/* Object initialization function for EelCanvasItem */ +static void +eel_canvas_item_init (EelCanvasItem *item) +{ + item->flags |= EEL_CANVAS_ITEM_VISIBLE; +} + +/* Performs post-creation operations on a canvas item (adding it to its parent + * group, etc.) + */ +static void +item_post_create_setup (EelCanvasItem *item) +{ + group_add (EEL_CANVAS_GROUP (item->parent), item); + + redraw_and_repick_if_mapped (item); +} + +/** + * eel_canvas_item_new: + * @parent: The parent group for the new item. + * @type: The object type of the item. + * @first_arg_name: A list of object argument name/value pairs, NULL-terminated, + * used to configure the item. For example, "fill_color", "black", + * "width_units", 5.0, NULL. + * @Varargs: + * + * Creates a new canvas item with @parent as its parent group. The item is + * created at the top of its parent's stack, and starts up as visible. The item + * is of the specified @type, for example, it can be + * eel_canvas_rect_get_type(). The list of object arguments/value pairs is + * used to configure the item. + * + * Return value: The newly-created item. + **/ +EelCanvasItem * +eel_canvas_item_new (EelCanvasGroup *parent, + GType type, + const gchar *first_arg_name, + ...) +{ + EelCanvasItem *item; + va_list args; + + g_return_val_if_fail (EEL_IS_CANVAS_GROUP (parent), NULL); + g_return_val_if_fail (g_type_is_a (type, eel_canvas_item_get_type ()), NULL); + + item = EEL_CANVAS_ITEM (g_object_new (type, NULL)); + + item->parent = EEL_CANVAS_ITEM (parent); + item->canvas = item->parent->canvas; + + va_start (args, first_arg_name); + g_object_set_valist (G_OBJECT (item), first_arg_name, args); + va_end (args); + + item_post_create_setup (item); + + return item; +} + +/* Set_property handler for canvas items */ +static void +eel_canvas_item_set_property (GObject *gobject, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + EelCanvasItem *item; + + g_return_if_fail (EEL_IS_CANVAS_ITEM (gobject)); + + item = EEL_CANVAS_ITEM (gobject); + + switch (param_id) + { + case ITEM_PROP_VISIBLE: + { + if (g_value_get_boolean (value)) + { + eel_canvas_item_show (item); + } + else + { + eel_canvas_item_hide (item); + } + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec); + } + break; + } +} + +/* Get_property handler for canvas items */ +static void +eel_canvas_item_get_property (GObject *gobject, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + EelCanvasItem *item; + + g_return_if_fail (EEL_IS_CANVAS_ITEM (gobject)); + + item = EEL_CANVAS_ITEM (gobject); + + switch (param_id) + { + case ITEM_PROP_VISIBLE: + { + g_value_set_boolean (value, item->flags & EEL_CANVAS_ITEM_VISIBLE); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec); + } + break; + } +} + +static void +redraw_and_repick_if_mapped (EelCanvasItem *item) +{ + if (item->flags & EEL_CANVAS_ITEM_MAPPED) + { + eel_canvas_item_request_redraw (item); + item->canvas->need_repick = TRUE; + } +} + +/* Dispose handler for canvas items */ +static void +eel_canvas_item_dispose (GObject *object) +{ + EelCanvasItem *item; + + g_return_if_fail (EEL_IS_CANVAS_ITEM (object)); + + item = EEL_CANVAS_ITEM (object); + + if (item->canvas) + { + eel_canvas_item_request_redraw (item); + + /* Make the canvas forget about us */ + + if (item == item->canvas->current_item) + { + item->canvas->current_item = NULL; + item->canvas->need_repick = TRUE; + } + + if (item == item->canvas->new_current_item) + { + item->canvas->new_current_item = NULL; + item->canvas->need_repick = TRUE; + } + + eel_canvas_item_ungrab (item); + + if (item == item->canvas->focused_item) + { + item->canvas->focused_item = NULL; + } + + /* Normal destroy stuff */ + + if (item->flags & EEL_CANVAS_ITEM_MAPPED) + { + (*EEL_CANVAS_ITEM_GET_CLASS (item)->unmap)(item); + } + + if (item->flags & EEL_CANVAS_ITEM_REALIZED) + { + (*EEL_CANVAS_ITEM_GET_CLASS (item)->unrealize)(item); + } + + if (item->parent) + { + group_remove (EEL_CANVAS_GROUP (item->parent), item); + } + + item->canvas = NULL; + } + + g_object_set_data (object, "in-destruction", GINT_TO_POINTER (1)); + g_signal_emit (object, item_signals[ITEM_DESTROY], 0); + + g_object_set_data (object, "in-destruction", NULL); + + G_OBJECT_CLASS (item_parent_class)->dispose (object); +} + +void +eel_canvas_item_destroy (EelCanvasItem *item) +{ + if (g_object_get_data (G_OBJECT (item), "in-destruction") == NULL) + { + g_object_run_dispose (G_OBJECT (item)); + } +} + +/* Realize handler for canvas items */ +static void +eel_canvas_item_realize (EelCanvasItem *item) +{ + if (item->parent && !(item->parent->flags & EEL_CANVAS_ITEM_REALIZED)) + { + (*EEL_CANVAS_ITEM_GET_CLASS (item->parent)->realize)(item->parent); + } + + if (item->parent == NULL && !gtk_widget_get_realized (GTK_WIDGET (item->canvas))) + { + gtk_widget_realize (GTK_WIDGET (item->canvas)); + } + + item->flags |= EEL_CANVAS_ITEM_REALIZED; + + eel_canvas_item_request_update (item); +} + +/* Unrealize handler for canvas items */ +static void +eel_canvas_item_unrealize (EelCanvasItem *item) +{ + if (item->flags & EEL_CANVAS_ITEM_MAPPED) + { + (*EEL_CANVAS_ITEM_GET_CLASS (item)->unmap)(item); + } + + item->flags &= ~(EEL_CANVAS_ITEM_REALIZED); +} + +/* Map handler for canvas items */ +static void +eel_canvas_item_map (EelCanvasItem *item) +{ + item->flags |= EEL_CANVAS_ITEM_MAPPED; +} + +/* Unmap handler for canvas items */ +static void +eel_canvas_item_unmap (EelCanvasItem *item) +{ + item->flags &= ~(EEL_CANVAS_ITEM_MAPPED); +} + +/* Update handler for canvas items */ +static void +eel_canvas_item_update (EelCanvasItem *item, + double i2w_dx, + double i2w_dy, + int flags) +{ + item->flags &= ~(EEL_CANVAS_ITEM_NEED_UPDATE); + item->flags &= ~(EEL_CANVAS_ITEM_NEED_DEEP_UPDATE); +} + +/* + * This routine invokes the update method of the item + * Please notice, that we take parent to canvas pixel matrix as argument + * unlike virtual method ::update, whose argument is item 2 canvas pixel + * matrix + * + * I will try to force somewhat meaningful naming for affines (Lauris) + * General naming rule is FROM2TO, where FROM and TO are abbreviations + * So p2cpx is Parent2CanvasPixel and i2cpx is Item2CanvasPixel + * I hope that this helps to keep track of what really happens + * + */ + +static void +eel_canvas_item_invoke_update (EelCanvasItem *item, + double i2w_dx, + double i2w_dy, + int flags) +{ + int child_flags; + + child_flags = flags; + + /* apply object flags to child flags */ + child_flags &= ~EEL_CANVAS_UPDATE_REQUESTED; + + if (item->flags & EEL_CANVAS_ITEM_NEED_UPDATE) + { + child_flags |= EEL_CANVAS_UPDATE_REQUESTED; + } + + if (item->flags & EEL_CANVAS_ITEM_NEED_DEEP_UPDATE) + { + child_flags |= EEL_CANVAS_UPDATE_DEEP; + } + + if (child_flags & GCI_UPDATE_MASK) + { + if (EEL_CANVAS_ITEM_GET_CLASS (item)->update) + { + EEL_CANVAS_ITEM_GET_CLASS (item)->update (item, i2w_dx, i2w_dy, child_flags); + } + } + + /* If this fail you probably forgot to chain up to + * EelCanvasItem::update from a derived class */ + g_return_if_fail (!(item->flags & EEL_CANVAS_ITEM_NEED_UPDATE)); +} + +/* + * This routine invokes the point method of the item. + * The arguments x, y should be in the parent item local coordinates. + */ + +static double +eel_canvas_item_invoke_point (EelCanvasItem *item, + double x, + double y, + int cx, + int cy, + EelCanvasItem **actual_item) +{ + /* Calculate x & y in item local coordinates */ + + if (EEL_CANVAS_ITEM_GET_CLASS (item)->point) + { + return EEL_CANVAS_ITEM_GET_CLASS (item)->point (item, x, y, cx, cy, actual_item); + } + + return 1e18; +} + +/** + * eel_canvas_item_set: + * @item: A canvas item. + * @first_arg_name: The list of object argument name/value pairs used to configure the item. + * @Varargs: + * + * Configures a canvas item. The arguments in the item are set to the specified + * values, and the item is repainted as appropriate. + **/ +void +eel_canvas_item_set (EelCanvasItem *item, + const gchar *first_arg_name, + ...) +{ + va_list args; + + va_start (args, first_arg_name); + g_object_set_valist (G_OBJECT (item), first_arg_name, args); + va_end (args); + + item->canvas->need_repick = TRUE; +} + +/** + * eel_canvas_item_move: + * @item: A canvas item. + * @dx: Horizontal offset. + * @dy: Vertical offset. + * + * Moves a canvas item by creating an affine transformation matrix for + * translation by using the specified values. This happens in item + * local coordinate system, so if you have nontrivial transform, it + * most probably does not do, what you want. + **/ +void +eel_canvas_item_move (EelCanvasItem *item, + double dx, + double dy) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (EEL_IS_CANVAS_ITEM (item)); + + if (!EEL_CANVAS_ITEM_GET_CLASS (item)->translate) + { + g_warning ("Item type %s does not implement translate method.\n", + g_type_name (G_OBJECT_TYPE (item))); + return; + } + + (*EEL_CANVAS_ITEM_GET_CLASS (item)->translate)(item, dx, dy); + + if (item->flags & EEL_CANVAS_ITEM_MAPPED) + { + item->canvas->need_repick = TRUE; + } + + if (!(item->flags & EEL_CANVAS_ITEM_NEED_DEEP_UPDATE)) + { + item->flags |= EEL_CANVAS_ITEM_NEED_DEEP_UPDATE; + if (item->parent != NULL) + { + eel_canvas_item_request_update (item->parent); + } + else + { + eel_canvas_request_update (item->canvas); + } + } +} + +static void +eel_canvas_queue_resize (EelCanvas *canvas) +{ + if (gtk_widget_is_drawable (GTK_WIDGET (canvas))) + { + gtk_widget_queue_resize (GTK_WIDGET (canvas)); + } +} + +/* Convenience function to reorder items in a group's child list. This puts the + * specified link after the "before" link. Returns TRUE if the list was changed. + */ +static gboolean +put_item_after (GList *link, + GList *before) +{ + EelCanvasGroup *parent; + + if (link == before) + { + return FALSE; + } + + parent = EEL_CANVAS_GROUP (EEL_CANVAS_ITEM (link->data)->parent); + + if (before == NULL) + { + if (link == parent->item_list) + { + return FALSE; + } + + link->prev->next = link->next; + + if (link->next) + { + link->next->prev = link->prev; + } + else + { + parent->item_list_end = link->prev; + } + + link->prev = before; + link->next = parent->item_list; + link->next->prev = link; + parent->item_list = link; + } + else + { + if ((link == parent->item_list_end) && (before == parent->item_list_end->prev)) + { + return FALSE; + } + + if (link->next) + { + link->next->prev = link->prev; + } + + if (link->prev) + { + link->prev->next = link->next; + } + else + { + parent->item_list = link->next; + parent->item_list->prev = NULL; + } + + link->prev = before; + link->next = before->next; + + link->prev->next = link; + + if (link->next) + { + link->next->prev = link; + } + else + { + parent->item_list_end = link; + } + } + return TRUE; +} + + +/** + * eel_canvas_item_raise: + * @item: A canvas item. + * @positions: Number of steps to raise the item. + * + * Raises the item in its parent's stack by the specified number of positions. + * If the number of positions is greater than the distance to the top of the + * stack, then the item is put at the top. + **/ +void +eel_canvas_item_raise (EelCanvasItem *item, + int positions) +{ + GList *link, *before; + EelCanvasGroup *parent; + + g_return_if_fail (EEL_IS_CANVAS_ITEM (item)); + g_return_if_fail (positions >= 0); + + if (!item->parent || positions == 0) + { + return; + } + + parent = EEL_CANVAS_GROUP (item->parent); + link = g_list_find (parent->item_list, item); + g_assert (link != NULL); + + for (before = link; positions && before; positions--) + { + before = before->next; + } + + if (!before) + { + before = parent->item_list_end; + } + + if (put_item_after (link, before)) + { + redraw_and_repick_if_mapped (item); + } +} + + +/** + * eel_canvas_item_lower: + * @item: A canvas item. + * @positions: Number of steps to lower the item. + * + * Lowers the item in its parent's stack by the specified number of positions. + * If the number of positions is greater than the distance to the bottom of the + * stack, then the item is put at the bottom. + **/ +void +eel_canvas_item_lower (EelCanvasItem *item, + int positions) +{ + GList *link, *before; + EelCanvasGroup *parent; + + g_return_if_fail (EEL_IS_CANVAS_ITEM (item)); + g_return_if_fail (positions >= 1); + + if (!item->parent) + { + return; + } + + parent = EEL_CANVAS_GROUP (item->parent); + link = g_list_find (parent->item_list, item); + g_assert (link != NULL); + + if (link->prev) + { + for (before = link->prev; positions && before; positions--) + { + before = before->prev; + } + } + else + { + before = NULL; + } + + if (put_item_after (link, before)) + { + redraw_and_repick_if_mapped (item); + } +} + + +/** + * eel_canvas_item_raise_to_top: + * @item: A canvas item. + * + * Raises an item to the top of its parent's stack. + **/ +void +eel_canvas_item_raise_to_top (EelCanvasItem *item) +{ + GList *link; + EelCanvasGroup *parent; + + g_return_if_fail (EEL_IS_CANVAS_ITEM (item)); + + if (!item->parent) + { + return; + } + + parent = EEL_CANVAS_GROUP (item->parent); + link = g_list_find (parent->item_list, item); + g_assert (link != NULL); + + if (put_item_after (link, parent->item_list_end)) + { + redraw_and_repick_if_mapped (item); + } +} + + +/** + * eel_canvas_item_lower_to_bottom: + * @item: A canvas item. + * + * Lowers an item to the bottom of its parent's stack. + **/ +void +eel_canvas_item_lower_to_bottom (EelCanvasItem *item) +{ + GList *link; + EelCanvasGroup *parent; + + g_return_if_fail (EEL_IS_CANVAS_ITEM (item)); + + if (!item->parent) + { + return; + } + + parent = EEL_CANVAS_GROUP (item->parent); + link = g_list_find (parent->item_list, item); + g_assert (link != NULL); + + if (put_item_after (link, NULL)) + { + redraw_and_repick_if_mapped (item); + } +} + +/** + * eel_canvas_item_send_behind: + * @item: A canvas item. + * @behind_item: The canvas item to put item behind, or NULL + * + * Moves item to a in the position in the stacking order so that + * it is placed immediately below behind_item, or at the top if + * behind_item is NULL. + **/ +void +eel_canvas_item_send_behind (EelCanvasItem *item, + EelCanvasItem *behind_item) +{ + GList *item_list; + int item_position, behind_position; + + g_return_if_fail (EEL_IS_CANVAS_ITEM (item)); + + if (behind_item == NULL) + { + eel_canvas_item_raise_to_top (item); + return; + } + + g_return_if_fail (EEL_IS_CANVAS_ITEM (behind_item)); + g_return_if_fail (item->parent == behind_item->parent); + + item_list = EEL_CANVAS_GROUP (item->parent)->item_list; + + item_position = g_list_index (item_list, item); + g_assert (item_position != -1); + behind_position = g_list_index (item_list, behind_item); + g_assert (behind_position != -1); + g_assert (item_position != behind_position); + + if (item_position == behind_position - 1) + { + return; + } + + if (item_position < behind_position) + { + eel_canvas_item_raise (item, (behind_position - 1) - item_position); + } + else + { + eel_canvas_item_lower (item, item_position - behind_position); + } +} + +/** + * eel_canvas_item_show: + * @item: A canvas item. + * + * Shows a canvas item. If the item was already shown, then no action is taken. + **/ +void +eel_canvas_item_show (EelCanvasItem *item) +{ + g_return_if_fail (EEL_IS_CANVAS_ITEM (item)); + + if (!(item->flags & EEL_CANVAS_ITEM_VISIBLE)) + { + item->flags |= EEL_CANVAS_ITEM_VISIBLE; + + if (!(item->flags & EEL_CANVAS_ITEM_REALIZED)) + { + (*EEL_CANVAS_ITEM_GET_CLASS (item)->realize)(item); + } + + if (item->parent != NULL) + { + if (!(item->flags & EEL_CANVAS_ITEM_MAPPED) && + item->parent->flags & EEL_CANVAS_ITEM_MAPPED) + { + (*EEL_CANVAS_ITEM_GET_CLASS (item)->map)(item); + } + } + else + { + if (!(item->flags & EEL_CANVAS_ITEM_MAPPED) && + gtk_widget_get_mapped (GTK_WIDGET (item->canvas))) + { + (*EEL_CANVAS_ITEM_GET_CLASS (item)->map)(item); + } + } + + redraw_and_repick_if_mapped (item); + eel_canvas_queue_resize (item->canvas); + } +} + + +/** + * eel_canvas_item_hide: + * @item: A canvas item. + * + * Hides a canvas item. If the item was already hidden, then no action is + * taken. + **/ +void +eel_canvas_item_hide (EelCanvasItem *item) +{ + g_return_if_fail (EEL_IS_CANVAS_ITEM (item)); + + if (item->flags & EEL_CANVAS_ITEM_VISIBLE) + { + item->flags &= ~EEL_CANVAS_ITEM_VISIBLE; + + redraw_and_repick_if_mapped (item); + + if (item->flags & EEL_CANVAS_ITEM_MAPPED) + { + (*EEL_CANVAS_ITEM_GET_CLASS (item)->unmap)(item); + } + + eel_canvas_queue_resize (item->canvas); + + /* No need to unrealize when we just want to hide */ + } +} + + +/* + * Prepare the window for grabbing, i.e. show it. + */ +static void +seat_grab_prepare_window (GdkSeat *seat, + GdkWindow *window, + gpointer user_data) +{ + gdk_window_show (window); +} + +/** + * eel_canvas_item_grab: + * @item: A canvas item. + * @event_mask: Mask of events that will be sent to this item. + * @cursor: If non-NULL, the cursor that will be used while the grab is active. + * @event: The event, triggering the grab, if any. + * + * Specifies that all events that match the specified event mask should be sent + * to the specified item, and also grabs the seat by calling gdk_seat_grab(). + * If @cursor is not NULL, then that cursor is used while the grab is active. + * + * Return value: If an item was already grabbed, it returns %GDK_GRAB_ALREADY_GRABBED. If + * the specified item was hidden by calling eel_canvas_item_hide(), then it + * returns %GDK_GRAB_NOT_VIEWABLE. Else, it returns the result of calling + * gdk_seat_grab(). + **/ +GdkGrabStatus +eel_canvas_item_grab (EelCanvasItem *item, + GdkEventMask event_mask, + GdkCursor *cursor, + const GdkEvent *event) +{ + GdkGrabStatus retval; + GdkDisplay *display; + GdkSeat *seat; + + g_return_val_if_fail (EEL_IS_CANVAS_ITEM (item), GDK_GRAB_NOT_VIEWABLE); + g_return_val_if_fail (gtk_widget_get_mapped (GTK_WIDGET (item->canvas)), + GDK_GRAB_NOT_VIEWABLE); + + if (item->canvas->grabbed_item) + { + return GDK_GRAB_ALREADY_GRABBED; + } + + if (!(item->flags & EEL_CANVAS_ITEM_MAPPED)) + { + return GDK_GRAB_NOT_VIEWABLE; + } + + display = gtk_widget_get_display (GTK_WIDGET (item->canvas)); + seat = gdk_display_get_default_seat (display); + + retval = gdk_seat_grab (seat, + gtk_layout_get_bin_window (GTK_LAYOUT (item->canvas)), + GDK_SEAT_CAPABILITY_ALL_POINTING, + FALSE, + cursor, + event, + seat_grab_prepare_window, + NULL); + + if (retval != GDK_GRAB_SUCCESS) + { + return retval; + } + + item->canvas->grabbed_item = item; + item->canvas->grabbed_event_mask = event_mask; + item->canvas->current_item = item; /* So that events go to the grabbed item */ + + return retval; +} + + +/** + * eel_canvas_item_ungrab: + * @item: A canvas item that holds a grab. + * + * Ungrabs the item, which must have been grabbed in the canvas, and ungrabs the + * seat. + **/ +void +eel_canvas_item_ungrab (EelCanvasItem *item) +{ + GdkDisplay *display; + GdkSeat *seat; + + g_return_if_fail (EEL_IS_CANVAS_ITEM (item)); + + if (item->canvas->grabbed_item != item) + { + return; + } + + display = gtk_widget_get_display (GTK_WIDGET (item->canvas)); + seat = gdk_display_get_default_seat (display); + + item->canvas->grabbed_item = NULL; + gdk_seat_ungrab (seat); +} + +/** + * eel_canvas_item_i2w: + * @item: A canvas item. + * @x: X coordinate to convert (input/output value). + * @y: Y coordinate to convert (input/output value). + * + * Converts a coordinate pair from item-relative coordinates to world + * coordinates. + **/ +void +eel_canvas_item_i2w (EelCanvasItem *item, + double *x, + double *y) +{ + g_return_if_fail (EEL_IS_CANVAS_ITEM (item)); + g_return_if_fail (x != NULL); + g_return_if_fail (y != NULL); + + item = item->parent; + while (item) + { + if (EEL_IS_CANVAS_GROUP (item)) + { + *x += EEL_CANVAS_GROUP (item)->xpos; + *y += EEL_CANVAS_GROUP (item)->ypos; + } + + item = item->parent; + } +} + +/* Returns whether the item is an inferior of or is equal to the parent. */ +static int +is_descendant (EelCanvasItem *item, + EelCanvasItem *parent) +{ + for (; item; item = item->parent) + { + if (item == parent) + { + return TRUE; + } + } + + return FALSE; +} + +/** + * eel_canvas_item_grab_focus: + * @item: A canvas item. + * + * Makes the specified item take the keyboard focus, so all keyboard events will + * be sent to it. If the canvas widget itself did not have the focus, it grabs + * it as well. + **/ +static void +eel_canvas_item_grab_focus (EelCanvasItem *item) +{ + EelCanvasItem *focused_item; + GdkEvent ev; + + g_return_if_fail (EEL_IS_CANVAS_ITEM (item)); + g_return_if_fail (gtk_widget_get_can_focus (GTK_WIDGET (item->canvas))); + + focused_item = item->canvas->focused_item; + + if (focused_item) + { + ev.focus_change.type = GDK_FOCUS_CHANGE; + ev.focus_change.window = gtk_layout_get_bin_window (GTK_LAYOUT (item->canvas)); + ev.focus_change.send_event = FALSE; + ev.focus_change.in = FALSE; + + emit_event (item->canvas, &ev); + } + + item->canvas->focused_item = item; + gtk_widget_grab_focus (GTK_WIDGET (item->canvas)); + + if (focused_item) + { + ev.focus_change.type = GDK_FOCUS_CHANGE; + ev.focus_change.window = gtk_layout_get_bin_window (GTK_LAYOUT (item->canvas)); + ev.focus_change.send_event = FALSE; + ev.focus_change.in = TRUE; + + emit_event (item->canvas, &ev); + } +} + + +/** + * eel_canvas_item_get_bounds: + * @item: A canvas item. + * @x1: Leftmost edge of the bounding box (return value). + * @y1: Upper edge of the bounding box (return value). + * @x2: Rightmost edge of the bounding box (return value). + * @y2: Lower edge of the bounding box (return value). + * + * Queries the bounding box of a canvas item. The bounds are returned in the + * coordinate system of the item's parent. + **/ +void +eel_canvas_item_get_bounds (EelCanvasItem *item, + double *x1, + double *y1, + double *x2, + double *y2) +{ + double tx1, ty1, tx2, ty2; + + g_return_if_fail (EEL_IS_CANVAS_ITEM (item)); + + tx1 = ty1 = tx2 = ty2 = 0.0; + + /* Get the item's bounds in its coordinate system */ + + if (EEL_CANVAS_ITEM_GET_CLASS (item)->bounds) + { + (*EEL_CANVAS_ITEM_GET_CLASS (item)->bounds)(item, &tx1, &ty1, &tx2, &ty2); + } + + /* Return the values */ + + if (x1) + { + *x1 = tx1; + } + + if (y1) + { + *y1 = ty1; + } + + if (x2) + { + *x2 = tx2; + } + + if (y2) + { + *y2 = ty2; + } +} + + +/** + * eel_canvas_item_request_update + * @item: A canvas item. + * + * To be used only by item implementations. Requests that the canvas queue an + * update for the specified item. + **/ +void +eel_canvas_item_request_update (EelCanvasItem *item) +{ + if (NULL == item->canvas) + { + return; + } + + g_return_if_fail (!item->canvas->doing_update); + + if (item->flags & EEL_CANVAS_ITEM_NEED_UPDATE) + { + return; + } + + item->flags |= EEL_CANVAS_ITEM_NEED_UPDATE; + + if (item->parent != NULL) + { + /* Recurse up the tree */ + eel_canvas_item_request_update (item->parent); + } + else + { + /* Have reached the top of the tree, make sure the update call gets scheduled. */ + eel_canvas_request_update (item->canvas); + } +} + +/** + * eel_canvas_item_request_update + * @item: A canvas item. + * + * Convenience function that informs a canvas that the specified item needs + * to be repainted. To be used by item implementations + **/ +void +eel_canvas_item_request_redraw (EelCanvasItem *item) +{ + if (item->flags & EEL_CANVAS_ITEM_MAPPED) + { + eel_canvas_request_redraw (item->canvas, + item->x1, item->y1, + item->x2 + 1, item->y2 + 1); + } +} + + + +/*** EelCanvasGroup ***/ + + +enum +{ + GROUP_PROP_0, + GROUP_PROP_X, + GROUP_PROP_Y +}; + + +static void eel_canvas_group_class_init (EelCanvasGroupClass *klass); +static void eel_canvas_group_init (EelCanvasGroup *group); +static void eel_canvas_group_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void eel_canvas_group_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void eel_canvas_group_destroy (EelCanvasItem *object); + +static void eel_canvas_group_update (EelCanvasItem *item, + double i2w_dx, + double i2w_dy, + int flags); +static void eel_canvas_group_unrealize (EelCanvasItem *item); +static void eel_canvas_group_map (EelCanvasItem *item); +static void eel_canvas_group_unmap (EelCanvasItem *item); +static void eel_canvas_group_draw (EelCanvasItem *item, + cairo_t *cr, + cairo_region_t *region); +static double eel_canvas_group_point (EelCanvasItem *item, + double x, + double y, + int cx, + int cy, + EelCanvasItem **actual_item); +static void eel_canvas_group_translate (EelCanvasItem *item, + double dx, + double dy); +static void eel_canvas_group_bounds (EelCanvasItem *item, + double *x1, + double *y1, + double *x2, + double *y2); + + +static EelCanvasItemClass *group_parent_class; + + +/** + * eel_canvas_group_get_type: + * + * Registers the &EelCanvasGroup class if necessary, and returns the type ID + * associated to it. + * + * Return value: The type ID of the &EelCanvasGroup class. + **/ +GType +eel_canvas_group_get_type (void) +{ + static GType group_type = 0; + + if (!group_type) + { + static const GTypeInfo group_info = + { + sizeof (EelCanvasGroupClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) eel_canvas_group_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (EelCanvasGroup), + 0, /* n_preallocs */ + (GInstanceInitFunc) eel_canvas_group_init + }; + + group_type = g_type_register_static (eel_canvas_item_get_type (), + "EelCanvasGroup", + &group_info, + 0); + } + + return group_type; +} + +/* Class initialization function for EelCanvasGroupClass */ +static void +eel_canvas_group_class_init (EelCanvasGroupClass *klass) +{ + GObjectClass *gobject_class; + EelCanvasItemClass *item_class; + + gobject_class = (GObjectClass *) klass; + item_class = (EelCanvasItemClass *) klass; + + group_parent_class = g_type_class_peek_parent (klass); + + gobject_class->set_property = eel_canvas_group_set_property; + gobject_class->get_property = eel_canvas_group_get_property; + + g_object_class_install_property + (gobject_class, GROUP_PROP_X, + g_param_spec_double ("x", + _("X"), + _("X"), + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE)); + g_object_class_install_property + (gobject_class, GROUP_PROP_Y, + g_param_spec_double ("y", + _("Y"), + _("Y"), + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE)); + + item_class->destroy = eel_canvas_group_destroy; + item_class->update = eel_canvas_group_update; + item_class->unrealize = eel_canvas_group_unrealize; + item_class->map = eel_canvas_group_map; + item_class->unmap = eel_canvas_group_unmap; + item_class->draw = eel_canvas_group_draw; + item_class->point = eel_canvas_group_point; + item_class->translate = eel_canvas_group_translate; + item_class->bounds = eel_canvas_group_bounds; +} + +/* Object initialization function for EelCanvasGroup */ +static void +eel_canvas_group_init (EelCanvasGroup *group) +{ + group->xpos = 0.0; + group->ypos = 0.0; +} + +/* Set_property handler for canvas groups */ +static void +eel_canvas_group_set_property (GObject *gobject, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + EelCanvasItem *item; + EelCanvasGroup *group; + double old; + gboolean moved; + + g_return_if_fail (EEL_IS_CANVAS_GROUP (gobject)); + + item = EEL_CANVAS_ITEM (gobject); + group = EEL_CANVAS_GROUP (gobject); + + moved = FALSE; + switch (param_id) + { + case GROUP_PROP_X: + { + old = group->xpos; + group->xpos = g_value_get_double (value); + if (old != group->xpos) + { + moved = TRUE; + } + } + break; + + case GROUP_PROP_Y: + { + old = group->ypos; + group->ypos = g_value_get_double (value); + if (old != group->ypos) + { + moved = TRUE; + } + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec); + } + break; + } + + if (moved) + { + item->flags |= EEL_CANVAS_ITEM_NEED_DEEP_UPDATE; + if (item->parent != NULL) + { + eel_canvas_item_request_update (item->parent); + } + else + { + eel_canvas_request_update (item->canvas); + } + } +} + +/* Get_property handler for canvas groups */ +static void +eel_canvas_group_get_property (GObject *gobject, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + EelCanvasGroup *group; + + g_return_if_fail (EEL_IS_CANVAS_GROUP (gobject)); + + group = EEL_CANVAS_GROUP (gobject); + + switch (param_id) + { + case GROUP_PROP_X: + { + g_value_set_double (value, group->xpos); + } + break; + + case GROUP_PROP_Y: + { + g_value_set_double (value, group->ypos); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec); + } + break; + } +} + +/* Destroy handler for canvas groups */ +static void +eel_canvas_group_destroy (EelCanvasItem *object) +{ + EelCanvasGroup *group; + EelCanvasItem *child; + GList *list; + + g_return_if_fail (EEL_IS_CANVAS_GROUP (object)); + + group = EEL_CANVAS_GROUP (object); + + list = group->item_list; + while (list) + { + child = list->data; + list = list->next; + + eel_canvas_item_destroy (child); + } + + if (EEL_CANVAS_ITEM_CLASS (group_parent_class)->destroy) + { + (*EEL_CANVAS_ITEM_CLASS (group_parent_class)->destroy)(object); + } +} + +/* Update handler for canvas groups */ +static void +eel_canvas_group_update (EelCanvasItem *item, + double i2w_dx, + double i2w_dy, + int flags) +{ + EelCanvasGroup *group; + GList *list; + EelCanvasItem *i; + double bbox_x0, bbox_y0, bbox_x1, bbox_y1; + gboolean first = TRUE; + + group = EEL_CANVAS_GROUP (item); + + (*group_parent_class->update)(item, i2w_dx, i2w_dy, flags); + + bbox_x0 = 0; + bbox_y0 = 0; + bbox_x1 = 0; + bbox_y1 = 0; + + for (list = group->item_list; list; list = list->next) + { + i = list->data; + + eel_canvas_item_invoke_update (i, i2w_dx + group->xpos, i2w_dy + group->ypos, flags); + + if (first) + { + first = FALSE; + bbox_x0 = i->x1; + bbox_y0 = i->y1; + bbox_x1 = i->x2; + bbox_y1 = i->y2; + } + else + { + bbox_x0 = MIN (bbox_x0, i->x1); + bbox_y0 = MIN (bbox_y0, i->y1); + bbox_x1 = MAX (bbox_x1, i->x2); + bbox_y1 = MAX (bbox_y1, i->y2); + } + } + item->x1 = bbox_x0; + item->y1 = bbox_y0; + item->x2 = bbox_x1; + item->y2 = bbox_y1; +} + +/* Unrealize handler for canvas groups */ +static void +eel_canvas_group_unrealize (EelCanvasItem *item) +{ + EelCanvasGroup *group; + GList *list; + EelCanvasItem *i; + + group = EEL_CANVAS_GROUP (item); + + /* Unmap group before children to avoid flash */ + if (item->flags & EEL_CANVAS_ITEM_MAPPED) + { + (*EEL_CANVAS_ITEM_GET_CLASS (item)->unmap)(item); + } + + for (list = group->item_list; list; list = list->next) + { + i = list->data; + + if (i->flags & EEL_CANVAS_ITEM_REALIZED) + { + (*EEL_CANVAS_ITEM_GET_CLASS (i)->unrealize)(i); + } + } + + (*group_parent_class->unrealize)(item); +} + +/* Map handler for canvas groups */ +static void +eel_canvas_group_map (EelCanvasItem *item) +{ + EelCanvasGroup *group; + GList *list; + EelCanvasItem *i; + + group = EEL_CANVAS_GROUP (item); + + for (list = group->item_list; list; list = list->next) + { + i = list->data; + + if (i->flags & EEL_CANVAS_ITEM_VISIBLE && + !(i->flags & EEL_CANVAS_ITEM_MAPPED)) + { + if (!(i->flags & EEL_CANVAS_ITEM_REALIZED)) + { + (*EEL_CANVAS_ITEM_GET_CLASS (i)->realize)(i); + } + + (*EEL_CANVAS_ITEM_GET_CLASS (i)->map)(i); + } + } + + (*group_parent_class->map)(item); +} + +/* Unmap handler for canvas groups */ +static void +eel_canvas_group_unmap (EelCanvasItem *item) +{ + EelCanvasGroup *group; + GList *list; + EelCanvasItem *i; + + group = EEL_CANVAS_GROUP (item); + + for (list = group->item_list; list; list = list->next) + { + i = list->data; + + if (i->flags & EEL_CANVAS_ITEM_MAPPED) + { + (*EEL_CANVAS_ITEM_GET_CLASS (i)->unmap)(i); + } + } + + (*group_parent_class->unmap)(item); +} + +/* Draw handler for canvas groups */ +static void +eel_canvas_group_draw (EelCanvasItem *item, + cairo_t *cr, + cairo_region_t *region) +{ + EelCanvasGroup *group; + GList *list; + EelCanvasItem *child = NULL; + + group = EEL_CANVAS_GROUP (item); + + for (list = group->item_list; list; list = list->next) + { + child = list->data; + + if ((child->flags & EEL_CANVAS_ITEM_MAPPED) && + (EEL_CANVAS_ITEM_GET_CLASS (child)->draw)) + { + GdkRectangle child_rect; + + child_rect.x = child->x1; + child_rect.y = child->y1; + child_rect.width = child->x2 - child->x1 + 1; + child_rect.height = child->y2 - child->y1 + 1; + + if (cairo_region_contains_rectangle (region, &child_rect) != CAIRO_REGION_OVERLAP_OUT) + { + EEL_CANVAS_ITEM_GET_CLASS (child)->draw (child, cr, region); + } + } + } +} + +/* Point handler for canvas groups */ +static double +eel_canvas_group_point (EelCanvasItem *item, + double x, + double y, + int cx, + int cy, + EelCanvasItem **actual_item) +{ + EelCanvasGroup *group; + GList *list; + EelCanvasItem *child, *point_item; + int x1, y1, x2, y2; + double gx, gy; + double dist, best; + int has_point; + + group = EEL_CANVAS_GROUP (item); + + x1 = cx - item->canvas->close_enough; + y1 = cy - item->canvas->close_enough; + x2 = cx + item->canvas->close_enough; + y2 = cy + item->canvas->close_enough; + + best = 0.0; + *actual_item = NULL; + + gx = x - group->xpos; + gy = y - group->ypos; + + dist = 0.0; /* keep gcc happy */ + + for (list = group->item_list; list; list = list->next) + { + child = list->data; + + if ((child->x1 > x2) || (child->y1 > y2) || (child->x2 < x1) || (child->y2 < y1)) + { + continue; + } + + point_item = NULL; /* cater for incomplete item implementations */ + + if ((child->flags & EEL_CANVAS_ITEM_MAPPED) + && EEL_CANVAS_ITEM_GET_CLASS (child)->point) + { + dist = eel_canvas_item_invoke_point (child, gx, gy, cx, cy, &point_item); + has_point = TRUE; + } + else + { + has_point = FALSE; + } + + if (has_point + && point_item + && ((int) (dist * item->canvas->pixels_per_unit + 0.5) + <= item->canvas->close_enough)) + { + best = dist; + *actual_item = point_item; + } + } + + return best; +} + +static void +eel_canvas_group_translate (EelCanvasItem *item, + double dx, + double dy) +{ + EelCanvasGroup *group; + + group = EEL_CANVAS_GROUP (item); + + group->xpos += dx; + group->ypos += dy; +} + +/* Bounds handler for canvas groups */ +static void +eel_canvas_group_bounds (EelCanvasItem *item, + double *x1, + double *y1, + double *x2, + double *y2) +{ + EelCanvasGroup *group; + EelCanvasItem *child; + GList *list; + double tx1, ty1, tx2, ty2; + double minx, miny, maxx, maxy; + int set; + + group = EEL_CANVAS_GROUP (item); + + /* Get the bounds of the first visible item */ + + child = NULL; /* Unnecessary but eliminates a warning. */ + + set = FALSE; + + for (list = group->item_list; list; list = list->next) + { + child = list->data; + + if (child->flags & EEL_CANVAS_ITEM_MAPPED) + { + set = TRUE; + eel_canvas_item_get_bounds (child, &minx, &miny, &maxx, &maxy); + break; + } + } + + /* If there were no visible items, return an empty bounding box */ + + if (!set) + { + *x1 = *y1 = *x2 = *y2 = 0.0; + return; + } + + /* Now we can grow the bounds using the rest of the items */ + + list = list->next; + + for (; list; list = list->next) + { + child = list->data; + + if (!(child->flags & EEL_CANVAS_ITEM_MAPPED)) + { + continue; + } + + eel_canvas_item_get_bounds (child, &tx1, &ty1, &tx2, &ty2); + + if (tx1 < minx) + { + minx = tx1; + } + + if (ty1 < miny) + { + miny = ty1; + } + + if (tx2 > maxx) + { + maxx = tx2; + } + + if (ty2 > maxy) + { + maxy = ty2; + } + } + + /* Make the bounds be relative to our parent's coordinate system */ + + if (item->parent) + { + minx += group->xpos; + miny += group->ypos; + maxx += group->xpos; + maxy += group->ypos; + } + + *x1 = minx; + *y1 = miny; + *x2 = maxx; + *y2 = maxy; +} + +/* Adds an item to a group */ +static void +group_add (EelCanvasGroup *group, + EelCanvasItem *item) +{ + g_object_ref_sink (item); + + if (!group->item_list) + { + group->item_list = g_list_append (group->item_list, item); + group->item_list_end = group->item_list; + } + else + { + group->item_list_end = g_list_append (group->item_list_end, item)->next; + } + + if (item->flags & EEL_CANVAS_ITEM_VISIBLE && + group->item.flags & EEL_CANVAS_ITEM_MAPPED) + { + if (!(item->flags & EEL_CANVAS_ITEM_REALIZED)) + { + (*EEL_CANVAS_ITEM_GET_CLASS (item)->realize)(item); + } + + if (!(item->flags & EEL_CANVAS_ITEM_MAPPED)) + { + (*EEL_CANVAS_ITEM_GET_CLASS (item)->map)(item); + } + } + + if (item->flags & EEL_CANVAS_ITEM_VISIBLE) + { + eel_canvas_queue_resize (EEL_CANVAS_ITEM (group)->canvas); + } +} + +/* Removes an item from a group */ +static void +group_remove (EelCanvasGroup *group, + EelCanvasItem *item) +{ + GList *children; + + g_return_if_fail (EEL_IS_CANVAS_GROUP (group)); + g_return_if_fail (EEL_IS_CANVAS_ITEM (item)); + + for (children = group->item_list; children; children = children->next) + { + if (children->data == item) + { + if (item->flags & EEL_CANVAS_ITEM_MAPPED) + { + (*EEL_CANVAS_ITEM_GET_CLASS (item)->unmap)(item); + } + + if (item->flags & EEL_CANVAS_ITEM_REALIZED) + { + (*EEL_CANVAS_ITEM_GET_CLASS (item)->unrealize)(item); + } + + if (item->flags & EEL_CANVAS_ITEM_VISIBLE) + { + eel_canvas_queue_resize (item->canvas); + } + + /* Unparent the child */ + + item->parent = NULL; + /* item->canvas = NULL; */ + g_object_unref (G_OBJECT (item)); + + /* Remove it from the list */ + + if (children == group->item_list_end) + { + group->item_list_end = children->prev; + } + + group->item_list = g_list_remove_link (group->item_list, children); + g_list_free (children); + break; + } + } +} + + +/*** EelCanvas ***/ + + +static void eel_canvas_class_init (EelCanvasClass *klass); +static void eel_canvas_init (EelCanvas *canvas); +static void eel_canvas_destroy (GtkWidget *object); +static void eel_canvas_map (GtkWidget *widget); +static void eel_canvas_unmap (GtkWidget *widget); +static void eel_canvas_realize (GtkWidget *widget); +static void eel_canvas_unrealize (GtkWidget *widget); +static void eel_canvas_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static gint eel_canvas_button (GtkWidget *widget, + GdkEventButton *event); +static gint eel_canvas_motion (GtkWidget *widget, + GdkEventMotion *event); +static gint eel_canvas_draw (GtkWidget *widget, + cairo_t *cr); +static gint eel_canvas_key (GtkWidget *widget, + GdkEventKey *event); +static gint eel_canvas_crossing (GtkWidget *widget, + GdkEventCrossing *event); +static gint eel_canvas_focus_in (GtkWidget *widget, + GdkEventFocus *event); +static gint eel_canvas_focus_out (GtkWidget *widget, + GdkEventFocus *event); +static void eel_canvas_request_update_real (EelCanvas *canvas); +static GtkLayoutClass *canvas_parent_class; + +/** + * eel_canvas_get_type: + * + * Registers the &EelCanvas class if necessary, and returns the type ID + * associated to it. + * + * Return value: The type ID of the &EelCanvas class. + **/ +GType +eel_canvas_get_type (void) +{ + static GType canvas_type = 0; + + if (!canvas_type) + { + static const GTypeInfo canvas_info = + { + sizeof (EelCanvasClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) eel_canvas_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (EelCanvas), + 0, /* n_preallocs */ + (GInstanceInitFunc) eel_canvas_init + }; + + canvas_type = g_type_register_static (gtk_layout_get_type (), + "EelCanvas", + &canvas_info, + 0); + } + + return canvas_type; +} + +static void +eel_canvas_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + break; + } +} + +static void +eel_canvas_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + break; + } +} + +static void +eel_canvas_accessible_adjustment_changed (GtkAdjustment *adjustment, + gpointer data) +{ + AtkObject *atk_obj; + + /* The scrollbars have changed */ + atk_obj = ATK_OBJECT (data); + + g_signal_emit_by_name (atk_obj, "visible-data-changed"); +} + +static void +eel_canvas_accessible_initialize (AtkObject *obj, + gpointer data) +{ + EelCanvas *canvas = data; + + if (ATK_OBJECT_CLASS (accessible_parent_class)->initialize != NULL) + { + ATK_OBJECT_CLASS (accessible_parent_class)->initialize (obj, data); + } + + gtk_accessible_set_widget (GTK_ACCESSIBLE (obj), GTK_WIDGET (data)); + g_signal_connect (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas)), + "value-changed", + G_CALLBACK (eel_canvas_accessible_adjustment_changed), + obj); + g_signal_connect (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas)), + "value-changed", + G_CALLBACK (eel_canvas_accessible_adjustment_changed), + obj); + + obj->role = ATK_ROLE_LAYERED_PANE; +} + +static gint +eel_canvas_accessible_get_n_children (AtkObject *obj) +{ + GtkAccessible *accessible; + GtkWidget *widget; + EelCanvas *canvas; + EelCanvasGroup *root_group; + + accessible = GTK_ACCESSIBLE (obj); + widget = gtk_accessible_get_widget (accessible); + + if (widget == NULL) + { + return 0; + } + + g_return_val_if_fail (EEL_IS_CANVAS (widget), 0); + + canvas = EEL_CANVAS (widget); + root_group = eel_canvas_root (canvas); + g_return_val_if_fail (root_group, 0); + + return 1; +} + +static AtkObject * +eel_canvas_accessible_ref_child (AtkObject *obj, + gint i) +{ + GtkAccessible *accessible; + GtkWidget *widget; + EelCanvas *canvas; + EelCanvasGroup *root_group; + AtkObject *atk_object; + + /* Canvas only has one child, so return NULL if index is non zero */ + if (i != 0) + { + return NULL; + } + + accessible = GTK_ACCESSIBLE (obj); + widget = gtk_accessible_get_widget (accessible); + + if (widget == NULL) + { + return NULL; + } + + canvas = EEL_CANVAS (widget); + root_group = eel_canvas_root (canvas); + g_return_val_if_fail (root_group, NULL); + + atk_object = atk_gobject_accessible_for_object (G_OBJECT (root_group)); + + return g_object_ref (atk_object); +} + +static void +eel_canvas_accessible_class_init (EelCanvasAccessibleClass *klass) +{ + AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass); + + accessible_parent_class = g_type_class_peek_parent (klass); + + atk_class->initialize = eel_canvas_accessible_initialize; + atk_class->get_n_children = eel_canvas_accessible_get_n_children; + atk_class->ref_child = eel_canvas_accessible_ref_child; +} + +static void +eel_canvas_accessible_init (EelCanvasAccessible *accessible) +{ +} + +G_DEFINE_TYPE (EelCanvasAccessible, eel_canvas_accessible, GTK_TYPE_CONTAINER_ACCESSIBLE) + +/* Class initialization function for EelCanvasClass */ +static void +eel_canvas_class_init (EelCanvasClass *klass) +{ + GObjectClass *gobject_class; + GtkWidgetClass *widget_class; + + gobject_class = (GObjectClass *) klass; + widget_class = (GtkWidgetClass *) klass; + + canvas_parent_class = g_type_class_peek_parent (klass); + + gobject_class->set_property = eel_canvas_set_property; + gobject_class->get_property = eel_canvas_get_property; + + widget_class->destroy = eel_canvas_destroy; + widget_class->map = eel_canvas_map; + widget_class->unmap = eel_canvas_unmap; + widget_class->realize = eel_canvas_realize; + widget_class->unrealize = eel_canvas_unrealize; + widget_class->size_allocate = eel_canvas_size_allocate; + widget_class->button_press_event = eel_canvas_button; + widget_class->button_release_event = eel_canvas_button; + widget_class->motion_notify_event = eel_canvas_motion; + widget_class->draw = eel_canvas_draw; + widget_class->key_press_event = eel_canvas_key; + widget_class->key_release_event = eel_canvas_key; + widget_class->enter_notify_event = eel_canvas_crossing; + widget_class->leave_notify_event = eel_canvas_crossing; + widget_class->focus_in_event = eel_canvas_focus_in; + widget_class->focus_out_event = eel_canvas_focus_out; + + klass->request_update = eel_canvas_request_update_real; + + gtk_widget_class_set_accessible_type (widget_class, eel_canvas_accessible_get_type ()); +} + +/* Callback used when the root item of a canvas is destroyed. The user should + * never ever do this, so we panic if this happens. + */ +static void +panic_root_destroyed (GtkWidget *object, + gpointer data) +{ + g_error ("Eeeek, root item %p of canvas %p was destroyed!", object, data); +} + +/* Object initialization function for EelCanvas */ +static void +eel_canvas_init (EelCanvas *canvas) +{ + guint width, height; + gtk_widget_set_can_focus (GTK_WIDGET (canvas), TRUE); + + gtk_widget_set_redraw_on_allocate (GTK_WIDGET (canvas), FALSE); + + canvas->scroll_x1 = 0.0; + canvas->scroll_y1 = 0.0; + gtk_layout_get_size (GTK_LAYOUT (canvas), + &width, &height); + canvas->scroll_x2 = width; + canvas->scroll_y2 = height; + + canvas->pixels_per_unit = 1.0; + + canvas->pick_event.type = GDK_LEAVE_NOTIFY; + canvas->pick_event.crossing.x = 0; + canvas->pick_event.crossing.y = 0; + + gtk_scrollable_set_hadjustment (GTK_SCROLLABLE (canvas), NULL); + gtk_scrollable_set_vadjustment (GTK_SCROLLABLE (canvas), NULL); + + /* Create the root item as a special case */ + + canvas->root = EEL_CANVAS_ITEM (g_object_new (eel_canvas_group_get_type (), NULL)); + canvas->root->canvas = canvas; + + g_object_ref_sink (canvas->root); + + canvas->root_destroy_id = g_signal_connect (G_OBJECT (canvas->root), + "destroy", G_CALLBACK (panic_root_destroyed), canvas); + + canvas->need_repick = TRUE; + canvas->doing_update = FALSE; +} + +/* Convenience function to remove the idle handler of a canvas */ +static void +remove_idle (EelCanvas *canvas) +{ + if (canvas->idle_id == 0) + { + return; + } + + g_source_remove (canvas->idle_id); + canvas->idle_id = 0; +} + +/* Removes the transient state of the canvas (idle handler, grabs). */ +static void +shutdown_transients (EelCanvas *canvas) +{ + /* We turn off the need_redraw flag, since if the canvas is mapped again + * it will request a redraw anyways. We do not turn off the need_update + * flag, though, because updates are not queued when the canvas remaps + * itself. + */ + if (canvas->need_redraw) + { + canvas->need_redraw = FALSE; + } + + if (canvas->grabbed_item) + { + eel_canvas_item_ungrab (canvas->grabbed_item); + } + + remove_idle (canvas); +} + +/* Destroy handler for EelCanvas */ +static void +eel_canvas_destroy (GtkWidget *object) +{ + EelCanvas *canvas; + + g_return_if_fail (EEL_IS_CANVAS (object)); + + /* remember, destroy can be run multiple times! */ + + canvas = EEL_CANVAS (object); + + g_clear_signal_handler (&canvas->root_destroy_id, G_OBJECT (canvas->root)); + if (canvas->root) + { + EelCanvasItem *root = canvas->root; + canvas->root = NULL; + eel_canvas_item_destroy (root); + g_object_unref (root); + } + + shutdown_transients (canvas); + + if (GTK_WIDGET_CLASS (canvas_parent_class)->destroy) + { + (*GTK_WIDGET_CLASS (canvas_parent_class)->destroy)(object); + } +} + +/** + * eel_canvas_new: + * @void: + * + * Creates a new empty canvas. If you wish to use the + * &EelCanvasImage item inside this canvas, then you must push the gdk_imlib + * visual and colormap before calling this function, and they can be popped + * afterwards. + * + * Return value: A newly-created canvas. + **/ +GtkWidget * +eel_canvas_new (void) +{ + return GTK_WIDGET (g_object_new (eel_canvas_get_type (), NULL)); +} + +/* Map handler for the canvas */ +static void +eel_canvas_map (GtkWidget *widget) +{ + EelCanvas *canvas; + + g_return_if_fail (EEL_IS_CANVAS (widget)); + + /* Normal widget mapping stuff */ + + if (GTK_WIDGET_CLASS (canvas_parent_class)->map) + { + (*GTK_WIDGET_CLASS (canvas_parent_class)->map)(widget); + } + + canvas = EEL_CANVAS (widget); + + /* Map items */ + + if (canvas->root->flags & EEL_CANVAS_ITEM_VISIBLE && + !(canvas->root->flags & EEL_CANVAS_ITEM_MAPPED) && + EEL_CANVAS_ITEM_GET_CLASS (canvas->root)->map) + { + (*EEL_CANVAS_ITEM_GET_CLASS (canvas->root)->map)(canvas->root); + } +} + +/* Unmap handler for the canvas */ +static void +eel_canvas_unmap (GtkWidget *widget) +{ + EelCanvas *canvas; + + g_return_if_fail (EEL_IS_CANVAS (widget)); + + canvas = EEL_CANVAS (widget); + + shutdown_transients (canvas); + + /* Unmap items */ + + if (EEL_CANVAS_ITEM_GET_CLASS (canvas->root)->unmap) + { + (*EEL_CANVAS_ITEM_GET_CLASS (canvas->root)->unmap)(canvas->root); + } + + /* Normal widget unmapping stuff */ + + if (GTK_WIDGET_CLASS (canvas_parent_class)->unmap) + { + (*GTK_WIDGET_CLASS (canvas_parent_class)->unmap)(widget); + } +} + +/* Realize handler for the canvas */ +static void +eel_canvas_realize (GtkWidget *widget) +{ + EelCanvas *canvas; + + g_return_if_fail (EEL_IS_CANVAS (widget)); + + /* Normal widget realization stuff */ + + if (GTK_WIDGET_CLASS (canvas_parent_class)->realize) + { + (*GTK_WIDGET_CLASS (canvas_parent_class)->realize)(widget); + } + + canvas = EEL_CANVAS (widget); + + gdk_window_set_events (gtk_layout_get_bin_window (GTK_LAYOUT (canvas)), + (gdk_window_get_events (gtk_layout_get_bin_window (GTK_LAYOUT (canvas))) + | GDK_EXPOSURE_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_POINTER_MOTION_MASK + | GDK_KEY_PRESS_MASK + | GDK_KEY_RELEASE_MASK + | GDK_ENTER_NOTIFY_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_FOCUS_CHANGE_MASK)); + + /* Create our own temporary pixmap gc and realize all the items */ + + (*EEL_CANVAS_ITEM_GET_CLASS (canvas->root)->realize)(canvas->root); +} + +/* Unrealize handler for the canvas */ +static void +eel_canvas_unrealize (GtkWidget *widget) +{ + EelCanvas *canvas; + + g_return_if_fail (EEL_IS_CANVAS (widget)); + + canvas = EEL_CANVAS (widget); + + shutdown_transients (canvas); + + /* Unrealize items and parent widget */ + + (*EEL_CANVAS_ITEM_GET_CLASS (canvas->root)->unrealize)(canvas->root); + + if (GTK_WIDGET_CLASS (canvas_parent_class)->unrealize) + { + (*GTK_WIDGET_CLASS (canvas_parent_class)->unrealize)(widget); + } +} + +/* Handles scrolling of the canvas. Adjusts the scrolling and zooming offset to + * keep as much as possible of the canvas scrolling region in view. + */ +static void +scroll_to (EelCanvas *canvas, + int cx, + int cy) +{ + int scroll_width, scroll_height; + int right_limit, bottom_limit; + int old_zoom_xofs, old_zoom_yofs; + int changed_x = FALSE, changed_y = FALSE; + int canvas_width, canvas_height; + GtkAllocation allocation; + GtkAdjustment *vadjustment, *hadjustment; + guint width, height; + + gtk_widget_get_allocation (GTK_WIDGET (canvas), &allocation); + canvas_width = allocation.width; + canvas_height = allocation.height; + + scroll_width = floor ((canvas->scroll_x2 - canvas->scroll_x1) * canvas->pixels_per_unit + 0.5); + scroll_height = floor ((canvas->scroll_y2 - canvas->scroll_y1) * canvas->pixels_per_unit + 0.5); + + right_limit = scroll_width - canvas_width; + bottom_limit = scroll_height - canvas_height; + + old_zoom_xofs = canvas->zoom_xofs; + old_zoom_yofs = canvas->zoom_yofs; + + if (right_limit < 0) + { + cx = 0; + if (canvas->center_scroll_region) + { + canvas->zoom_xofs = (canvas_width - scroll_width) / 2; + scroll_width = canvas_width; + } + else + { + canvas->zoom_xofs = 0; + } + } + else if (cx < 0) + { + cx = 0; + canvas->zoom_xofs = 0; + } + else if (cx > right_limit) + { + cx = right_limit; + canvas->zoom_xofs = 0; + } + else + { + canvas->zoom_xofs = 0; + } + + if (bottom_limit < 0) + { + cy = 0; + if (canvas->center_scroll_region) + { + canvas->zoom_yofs = (canvas_height - scroll_height) / 2; + scroll_height = canvas_height; + } + else + { + canvas->zoom_yofs = 0; + } + } + else if (cy < 0) + { + cy = 0; + canvas->zoom_yofs = 0; + } + else if (cy > bottom_limit) + { + cy = bottom_limit; + canvas->zoom_yofs = 0; + } + else + { + canvas->zoom_yofs = 0; + } + + if ((canvas->zoom_xofs != old_zoom_xofs) || (canvas->zoom_yofs != old_zoom_yofs)) + { + /* This can only occur, if either canvas size or widget size changes + * So I think we can request full redraw here + * More stuff - we have to mark root as needing fresh affine (Lauris) + */ + if (!(canvas->root->flags & EEL_CANVAS_ITEM_NEED_DEEP_UPDATE)) + { + canvas->root->flags |= EEL_CANVAS_ITEM_NEED_DEEP_UPDATE; + eel_canvas_request_update (canvas); + } + gtk_widget_queue_draw (GTK_WIDGET (canvas)); + } + + hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas)); + vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas)); + + if (((int) gtk_adjustment_get_value (hadjustment)) != cx) + { + gtk_adjustment_set_value (hadjustment, cx); + changed_x = TRUE; + } + + if (((int) gtk_adjustment_get_value (vadjustment)) != cy) + { + gtk_adjustment_set_value (vadjustment, cy); + changed_y = TRUE; + } + + gtk_layout_get_size (&canvas->layout, &width, &height); + if ((scroll_width != (int) width) || (scroll_height != (int) height)) + { + gtk_layout_set_size (GTK_LAYOUT (canvas), scroll_width, scroll_height); + } + + /* Signal GtkLayout that it should do a redraw. */ + if (changed_x) + { + g_signal_emit_by_name (hadjustment, "value-changed"); + } + if (changed_y) + { + g_signal_emit_by_name (vadjustment, "value-changed"); + } +} + +/* Size allocation handler for the canvas */ +static void +eel_canvas_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + EelCanvas *canvas; + GtkAdjustment *vadjustment, *hadjustment; + + g_return_if_fail (EEL_IS_CANVAS (widget)); + g_return_if_fail (allocation != NULL); + + if (GTK_WIDGET_CLASS (canvas_parent_class)->size_allocate) + { + (*GTK_WIDGET_CLASS (canvas_parent_class)->size_allocate)(widget, allocation); + } + + canvas = EEL_CANVAS (widget); + + /* Recenter the view, if appropriate */ + + hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas)); + vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas)); + + gtk_adjustment_set_page_size (hadjustment, allocation->width); + gtk_adjustment_set_page_increment (hadjustment, allocation->width / 2); + + gtk_adjustment_set_page_size (vadjustment, allocation->height); + gtk_adjustment_set_page_increment (vadjustment, allocation->height / 2); + + scroll_to (canvas, + gtk_adjustment_get_value (hadjustment), + gtk_adjustment_get_value (vadjustment)); + + g_signal_emit_by_name (hadjustment, "changed"); + g_signal_emit_by_name (vadjustment, "changed"); +} + +/* Emits an event for an item in the canvas, be it the current item, grabbed + * item, or focused item, as appropriate. + */ + +static int +emit_event (EelCanvas *canvas, + GdkEvent *event) +{ + GdkEvent ev; + gint finished; + EelCanvasItem *item; + EelCanvasItem *parent; + guint mask; + + /* Could be an old pick event */ + if (!gtk_widget_get_realized (GTK_WIDGET (canvas))) + { + return FALSE; + } + + /* Perform checks for grabbed items */ + + if (canvas->grabbed_item && + !is_descendant (canvas->current_item, canvas->grabbed_item)) + { + return FALSE; + } + + if (canvas->grabbed_item) + { + switch (event->type) + { + case GDK_ENTER_NOTIFY: + { + mask = GDK_ENTER_NOTIFY_MASK; + } + break; + + case GDK_LEAVE_NOTIFY: + { + mask = GDK_LEAVE_NOTIFY_MASK; + } + break; + + case GDK_MOTION_NOTIFY: + { + mask = GDK_POINTER_MOTION_MASK; + } + break; + + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + { + mask = GDK_BUTTON_PRESS_MASK; + } + break; + + case GDK_BUTTON_RELEASE: + { + mask = GDK_BUTTON_RELEASE_MASK; + } + break; + + case GDK_KEY_PRESS: + { + mask = GDK_KEY_PRESS_MASK; + } + break; + + case GDK_KEY_RELEASE: + { + mask = GDK_KEY_RELEASE_MASK; + } + break; + + default: + { + mask = 0; + } + break; + } + + if (!(mask & canvas->grabbed_event_mask)) + { + return FALSE; + } + } + + /* Convert to world coordinates -- we have two cases because of diferent + * offsets of the fields in the event structures. + */ + + ev = *event; + + switch (ev.type) + { + case GDK_ENTER_NOTIFY: + case GDK_LEAVE_NOTIFY: + { + eel_canvas_window_to_world (canvas, + ev.crossing.x, ev.crossing.y, + &ev.crossing.x, &ev.crossing.y); + } + break; + + case GDK_MOTION_NOTIFY: + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + case GDK_BUTTON_RELEASE: + { + eel_canvas_window_to_world (canvas, + ev.motion.x, ev.motion.y, + &ev.motion.x, &ev.motion.y); + } + break; + + default: + { + } + break; + } + + /* Choose where we send the event */ + + item = canvas->current_item; + + if (canvas->focused_item + && ((event->type == GDK_KEY_PRESS) || + (event->type == GDK_KEY_RELEASE) || + (event->type == GDK_FOCUS_CHANGE))) + { + item = canvas->focused_item; + } + + /* The event is propagated up the hierarchy (for if someone connected to + * a group instead of a leaf event), and emission is stopped if a + * handler returns TRUE, just like for GtkWidget events. + */ + + finished = FALSE; + + while (item && !finished) + { + g_object_ref (item); + + g_signal_emit ( + G_OBJECT (item), item_signals[ITEM_EVENT], 0, + &ev, &finished); + + parent = item->parent; + g_object_unref (item); + + item = parent; + } + + return finished; +} + +/* Re-picks the current item in the canvas, based on the event's coordinates. + * Also emits enter/leave events for items as appropriate. + */ +static int +pick_current_item (EelCanvas *canvas, + GdkEvent *event) +{ + int button_down; + double x, y; + int cx, cy; + int retval; + + retval = FALSE; + + /* If a button is down, we'll perform enter and leave events on the + * current item, but not enter on any other item. This is more or less + * like X pointer grabbing for canvas items. + */ + button_down = canvas->state & (GDK_BUTTON1_MASK + | GDK_BUTTON2_MASK + | GDK_BUTTON3_MASK + | GDK_BUTTON4_MASK + | GDK_BUTTON5_MASK); + if (!button_down) + { + canvas->left_grabbed_item = FALSE; + } + + /* Save the event in the canvas. This is used to synthesize enter and + * leave events in case the current item changes. It is also used to + * re-pick the current item if the current one gets deleted. Also, + * synthesize an enter event. + */ + if (event != &canvas->pick_event) + { + if ((event->type == GDK_MOTION_NOTIFY) || (event->type == GDK_BUTTON_RELEASE)) + { + /* these fields have the same offsets in both types of events */ + + canvas->pick_event.crossing.type = GDK_ENTER_NOTIFY; + canvas->pick_event.crossing.window = event->motion.window; + canvas->pick_event.crossing.send_event = event->motion.send_event; + canvas->pick_event.crossing.subwindow = NULL; + canvas->pick_event.crossing.x = event->motion.x; + canvas->pick_event.crossing.y = event->motion.y; + canvas->pick_event.crossing.mode = GDK_CROSSING_NORMAL; + canvas->pick_event.crossing.detail = GDK_NOTIFY_NONLINEAR; + canvas->pick_event.crossing.focus = FALSE; + canvas->pick_event.crossing.state = event->motion.state; + + /* these fields don't have the same offsets in both types of events */ + + if (event->type == GDK_MOTION_NOTIFY) + { + canvas->pick_event.crossing.x_root = event->motion.x_root; + canvas->pick_event.crossing.y_root = event->motion.y_root; + } + else + { + canvas->pick_event.crossing.x_root = event->button.x_root; + canvas->pick_event.crossing.y_root = event->button.y_root; + } + } + else + { + canvas->pick_event = *event; + } + } + + /* Don't do anything else if this is a recursive call */ + + if (canvas->in_repick) + { + return retval; + } + + /* LeaveNotify means that there is no current item, so we don't look for one */ + + if (canvas->pick_event.type != GDK_LEAVE_NOTIFY) + { + /* these fields don't have the same offsets in both types of events */ + + if (canvas->pick_event.type == GDK_ENTER_NOTIFY) + { + x = canvas->pick_event.crossing.x; + y = canvas->pick_event.crossing.y; + } + else + { + x = canvas->pick_event.motion.x; + y = canvas->pick_event.motion.y; + } + + /* canvas pixel coords */ + + cx = (int) (x + 0.5); + cy = (int) (y + 0.5); + + /* world coords */ + eel_canvas_c2w (canvas, cx, cy, &x, &y); + + /* find the closest item */ + if (canvas->root->flags & EEL_CANVAS_ITEM_MAPPED) + { + eel_canvas_item_invoke_point (canvas->root, x, y, cx, cy, + &canvas->new_current_item); + } + else + { + canvas->new_current_item = NULL; + } + } + else + { + canvas->new_current_item = NULL; + } + + if ((canvas->new_current_item == canvas->current_item) && !canvas->left_grabbed_item) + { + return retval; /* current item did not change */ + } + /* Synthesize events for old and new current items */ + + if ((canvas->new_current_item != canvas->current_item) + && (canvas->current_item != NULL) + && !canvas->left_grabbed_item) + { + GdkEvent new_event; + + new_event = canvas->pick_event; + new_event.type = GDK_LEAVE_NOTIFY; + + new_event.crossing.detail = GDK_NOTIFY_ANCESTOR; + new_event.crossing.subwindow = NULL; + canvas->in_repick = TRUE; + retval = emit_event (canvas, &new_event); + canvas->in_repick = FALSE; + } + + /* new_current_item may have been set to NULL during the call to emit_event() above */ + + if ((canvas->new_current_item != canvas->current_item) && button_down) + { + canvas->current_item = canvas->new_current_item; + canvas->left_grabbed_item = TRUE; + return retval; + } + + /* Handle the rest of cases */ + + canvas->left_grabbed_item = FALSE; + canvas->current_item = canvas->new_current_item; + + if (canvas->current_item != NULL) + { + GdkEvent new_event; + + new_event = canvas->pick_event; + new_event.type = GDK_ENTER_NOTIFY; + new_event.crossing.detail = GDK_NOTIFY_ANCESTOR; + new_event.crossing.subwindow = NULL; + retval = emit_event (canvas, &new_event); + } + + return retval; +} + +/* Button event handler for the canvas */ +static gint +eel_canvas_button (GtkWidget *widget, + GdkEventButton *event) +{ + EelCanvas *canvas; + int mask; + int retval; + + g_return_val_if_fail (EEL_IS_CANVAS (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + retval = FALSE; + + canvas = EEL_CANVAS (widget); + + /* Don't handle extra mouse button events */ + if (event->button > 5) + { + return FALSE; + } + + /* + * dispatch normally regardless of the event's window if an item has + * has a pointer grab in effect + */ + if (!canvas->grabbed_item && event->window != gtk_layout_get_bin_window (GTK_LAYOUT (canvas))) + { + return retval; + } + + switch (event->button) + { + case 1: + { + mask = GDK_BUTTON1_MASK; + } + break; + + case 2: + { + mask = GDK_BUTTON2_MASK; + } + break; + + case 3: + { + mask = GDK_BUTTON3_MASK; + } + break; + + case 4: + { + mask = GDK_BUTTON4_MASK; + } + break; + + case 5: + { + mask = GDK_BUTTON5_MASK; + } + break; + + default: + mask = 0; + } + + switch (event->type) + { + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + { + /* Pick the current item as if the button were not pressed, and + * then process the event. + */ + event->state ^= mask; + canvas->state = event->state; + pick_current_item (canvas, (GdkEvent *) event); + event->state ^= mask; + canvas->state = event->state; + retval = emit_event (canvas, (GdkEvent *) event); + } + break; + + case GDK_BUTTON_RELEASE: + { + /* Process the event as if the button were pressed, then repick + * after the button has been released + */ + canvas->state = event->state; + retval = emit_event (canvas, (GdkEvent *) event); + event->state ^= mask; + canvas->state = event->state; + pick_current_item (canvas, (GdkEvent *) event); + event->state ^= mask; + } + break; + + default: + g_assert_not_reached (); + } + + return retval; +} + +/* Motion event handler for the canvas */ +static gint +eel_canvas_motion (GtkWidget *widget, + GdkEventMotion *event) +{ + EelCanvas *canvas; + + g_return_val_if_fail (EEL_IS_CANVAS (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + canvas = EEL_CANVAS (widget); + + if (event->window != gtk_layout_get_bin_window (GTK_LAYOUT (canvas))) + { + return FALSE; + } + + canvas->state = event->state; + pick_current_item (canvas, (GdkEvent *) event); + return emit_event (canvas, (GdkEvent *) event); +} + +/* Key event handler for the canvas */ +static gint +eel_canvas_key (GtkWidget *widget, + GdkEventKey *event) +{ + EelCanvas *canvas; + + g_return_val_if_fail (EEL_IS_CANVAS (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + canvas = EEL_CANVAS (widget); + + if (emit_event (canvas, (GdkEvent *) event)) + { + return TRUE; + } + if (event->type == GDK_KEY_RELEASE) + { + return GTK_WIDGET_CLASS (canvas_parent_class)->key_release_event (widget, event); + } + else + { + return GTK_WIDGET_CLASS (canvas_parent_class)->key_press_event (widget, event); + } +} + + +/* Crossing event handler for the canvas */ +static gint +eel_canvas_crossing (GtkWidget *widget, + GdkEventCrossing *event) +{ + EelCanvas *canvas; + + g_return_val_if_fail (EEL_IS_CANVAS (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + canvas = EEL_CANVAS (widget); + + if (event->window != gtk_layout_get_bin_window (GTK_LAYOUT (canvas))) + { + return FALSE; + } + + canvas->state = event->state; + return pick_current_item (canvas, (GdkEvent *) event); +} + +/* Focus in handler for the canvas */ +static gint +eel_canvas_focus_in (GtkWidget *widget, + GdkEventFocus *event) +{ + EelCanvas *canvas; + + canvas = EEL_CANVAS (widget); + + if (canvas->focused_item) + { + return emit_event (canvas, (GdkEvent *) event); + } + else + { + return FALSE; + } +} + +/* Focus out handler for the canvas */ +static gint +eel_canvas_focus_out (GtkWidget *widget, + GdkEventFocus *event) +{ + EelCanvas *canvas; + + canvas = EEL_CANVAS (widget); + + if (canvas->focused_item) + { + return emit_event (canvas, (GdkEvent *) event); + } + else + { + return FALSE; + } +} + + +static cairo_region_t * +eel_cairo_get_clip_region (cairo_t *cr) +{ + cairo_rectangle_list_t *list; + cairo_region_t *region; + int i; + + list = cairo_copy_clip_rectangle_list (cr); + if (list->status == CAIRO_STATUS_CLIP_NOT_REPRESENTABLE) + { + cairo_rectangle_int_t clip_rect; + + cairo_rectangle_list_destroy (list); + + if (!gdk_cairo_get_clip_rectangle (cr, &clip_rect)) + { + return NULL; + } + return cairo_region_create_rectangle (&clip_rect); + } + + + region = cairo_region_create (); + for (i = list->num_rectangles - 1; i >= 0; --i) + { + cairo_rectangle_t *rect = &list->rectangles[i]; + cairo_rectangle_int_t clip_rect; + + clip_rect.x = floor (rect->x); + clip_rect.y = floor (rect->y); + clip_rect.width = ceil (rect->x + rect->width) - clip_rect.x; + clip_rect.height = ceil (rect->y + rect->height) - clip_rect.y; + + if (cairo_region_union_rectangle (region, &clip_rect) != CAIRO_STATUS_SUCCESS) + { + cairo_region_destroy (region); + region = NULL; + break; + } + } + + cairo_rectangle_list_destroy (list); + return region; +} + +/* Expose handler for the canvas */ +static gboolean +eel_canvas_draw (GtkWidget *widget, + cairo_t *cr) +{ + EelCanvas *canvas = EEL_CANVAS (widget); + GdkWindow *bin_window; + cairo_region_t *region; + + if (!gdk_cairo_get_clip_rectangle (cr, NULL)) + { + return FALSE; + } + + bin_window = gtk_layout_get_bin_window (GTK_LAYOUT (widget)); + + if (!gtk_cairo_should_draw_window (cr, bin_window)) + { + return FALSE; + } + + cairo_save (cr); + + gtk_cairo_transform_to_window (cr, widget, bin_window); + + region = eel_cairo_get_clip_region (cr); + if (region == NULL) + { + cairo_restore (cr); + return FALSE; + } + +#ifdef VERBOSE + g_print ("Draw\n"); +#endif + /* If there are any outstanding items that need updating, do them now */ + if (canvas->idle_id) + { + g_source_remove (canvas->idle_id); + canvas->idle_id = 0; + } + if (canvas->need_update) + { + g_return_val_if_fail (!canvas->doing_update, FALSE); + + canvas->doing_update = TRUE; + eel_canvas_item_invoke_update (canvas->root, 0, 0, 0); + + g_return_val_if_fail (canvas->doing_update, FALSE); + + canvas->doing_update = FALSE; + + canvas->need_update = FALSE; + } + + if (canvas->root->flags & EEL_CANVAS_ITEM_MAPPED) + { + EEL_CANVAS_ITEM_GET_CLASS (canvas->root)->draw (canvas->root, cr, region); + } + + cairo_restore (cr); + + /* Chain up to get exposes on child widgets */ + if (GTK_WIDGET_CLASS (canvas_parent_class)->draw) + { + GTK_WIDGET_CLASS (canvas_parent_class)->draw (widget, cr); + } + + cairo_region_destroy (region); + return FALSE; +} + +static void +do_update (EelCanvas *canvas) +{ + /* Cause the update if necessary */ + +update_again: + if (canvas->need_update) + { + g_return_if_fail (!canvas->doing_update); + + canvas->doing_update = TRUE; + eel_canvas_item_invoke_update (canvas->root, 0, 0, 0); + + g_return_if_fail (canvas->doing_update); + + canvas->doing_update = FALSE; + + canvas->need_update = FALSE; + } + + /* Pick new current item */ + + while (canvas->need_repick) + { + canvas->need_repick = FALSE; + pick_current_item (canvas, &canvas->pick_event); + } + + /* it is possible that during picking we emitted an event in which + * the user then called some function which then requested update + * of something. Without this we'd be left in a state where + * need_update would have been left TRUE and the canvas would have + * been left unpainted. */ + if (canvas->need_update) + { + goto update_again; + } +} + +/* Idle handler for the canvas. It deals with pending updates and redraws. */ +static gint +idle_handler (gpointer data) +{ + EelCanvas *canvas; + + canvas = EEL_CANVAS (data); + do_update (canvas); + + /* Reset idle id */ + canvas->idle_id = 0; + + return FALSE; +} + +/* Convenience function to add an idle handler to a canvas */ +static void +add_idle (EelCanvas *canvas) +{ + if (!canvas->idle_id) + { + /* We let the update idle handler have higher priority + * than the redraw idle handler so the canvas state + * will be updated during the expose event. canvas in + * expose_event. + */ + canvas->idle_id = g_idle_add_full (GDK_PRIORITY_REDRAW - 20, + idle_handler, canvas, NULL); + } +} + +/** + * eel_canvas_root: + * @canvas: A canvas. + * + * Queries the root group of a canvas. + * + * Return value: The root group of the specified canvas. + **/ +EelCanvasGroup * +eel_canvas_root (EelCanvas *canvas) +{ + g_return_val_if_fail (EEL_IS_CANVAS (canvas), NULL); + + return EEL_CANVAS_GROUP (canvas->root); +} + + +/** + * eel_canvas_set_scroll_region: + * @canvas: A canvas. + * @x1: Leftmost limit of the scrolling region. + * @y1: Upper limit of the scrolling region. + * @x2: Rightmost limit of the scrolling region. + * @y2: Lower limit of the scrolling region. + * + * Sets the scrolling region of a canvas to the specified rectangle. The canvas + * will then be able to scroll only within this region. The view of the canvas + * is adjusted as appropriate to display as much of the new region as possible. + **/ +void +eel_canvas_set_scroll_region (EelCanvas *canvas, + double x1, + double y1, + double x2, + double y2) +{ + double wxofs, wyofs; + int xofs, yofs; + GtkAdjustment *vadjustment, *hadjustment; + + g_return_if_fail (EEL_IS_CANVAS (canvas)); + + if ((canvas->scroll_x1 == x1) && (canvas->scroll_y1 == y1) && + (canvas->scroll_x2 == x2) && (canvas->scroll_y2 == y2)) + { + return; + } + + /* + * Set the new scrolling region. If possible, do not move the visible contents of the + * canvas. + */ + hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas)); + vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas)); + + eel_canvas_c2w (canvas, + gtk_adjustment_get_value (hadjustment) + canvas->zoom_xofs, + gtk_adjustment_get_value (vadjustment) + canvas->zoom_yofs, + /*canvas->zoom_xofs, + * canvas->zoom_yofs,*/ + &wxofs, &wyofs); + + canvas->scroll_x1 = x1; + canvas->scroll_y1 = y1; + canvas->scroll_x2 = x2; + canvas->scroll_y2 = y2; + + eel_canvas_w2c (canvas, wxofs, wyofs, &xofs, &yofs); + + scroll_to (canvas, xofs, yofs); + + canvas->need_repick = TRUE; + + if (!(canvas->root->flags & EEL_CANVAS_ITEM_NEED_DEEP_UPDATE)) + { + canvas->root->flags |= EEL_CANVAS_ITEM_NEED_DEEP_UPDATE; + eel_canvas_request_update (canvas); + } +} + + +/** + * eel_canvas_get_scroll_region: + * @canvas: A canvas. + * @x1: Leftmost limit of the scrolling region (return value). + * @y1: Upper limit of the scrolling region (return value). + * @x2: Rightmost limit of the scrolling region (return value). + * @y2: Lower limit of the scrolling region (return value). + * + * Queries the scrolling region of a canvas. + **/ +void +eel_canvas_get_scroll_region (EelCanvas *canvas, + double *x1, + double *y1, + double *x2, + double *y2) +{ + g_return_if_fail (EEL_IS_CANVAS (canvas)); + + if (x1) + { + *x1 = canvas->scroll_x1; + } + + if (y1) + { + *y1 = canvas->scroll_y1; + } + + if (x2) + { + *x2 = canvas->scroll_x2; + } + + if (y2) + { + *y2 = canvas->scroll_y2; + } +} + +/** + * eel_canvas_set_pixels_per_unit: + * @canvas: A canvas. + * @n: The number of pixels that correspond to one canvas unit. + * + * Sets the zooming factor of a canvas by specifying the number of pixels that + * correspond to one canvas unit. + **/ +void +eel_canvas_set_pixels_per_unit (EelCanvas *canvas, + double n) +{ + GtkWidget *widget; + double cx, cy; + int x1, y1; + int center_x, center_y; + GdkWindow *window; + GdkWindowAttr attributes; + gint attributes_mask; + GtkAllocation allocation; + GtkAdjustment *vadjustment, *hadjustment; + + g_return_if_fail (EEL_IS_CANVAS (canvas)); + g_return_if_fail (n > EEL_CANVAS_EPSILON); + + widget = GTK_WIDGET (canvas); + + gtk_widget_get_allocation (widget, &allocation); + center_x = allocation.width / 2; + center_y = allocation.height / 2; + + /* Find the coordinates of the screen center in units. */ + hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas)); + vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas)); + cx = (gtk_adjustment_get_value (hadjustment) + center_x) / canvas->pixels_per_unit + canvas->scroll_x1 + canvas->zoom_xofs; + cy = (gtk_adjustment_get_value (vadjustment) + center_y) / canvas->pixels_per_unit + canvas->scroll_y1 + canvas->zoom_yofs; + + /* Now calculate the new offset of the upper left corner. (round not truncate) */ + x1 = ((cx - canvas->scroll_x1) * n) - center_x + .5; + y1 = ((cy - canvas->scroll_y1) * n) - center_y + .5; + + canvas->pixels_per_unit = n; + + if (!(canvas->root->flags & EEL_CANVAS_ITEM_NEED_DEEP_UPDATE)) + { + canvas->root->flags |= EEL_CANVAS_ITEM_NEED_DEEP_UPDATE; + eel_canvas_request_update (canvas); + } + + /* Map a background None window over the bin_window to avoid + * scrolling the window scroll causing exposes. + */ + window = NULL; + if (gtk_widget_get_mapped (widget)) + { + attributes.window_type = GDK_WINDOW_CHILD; + gtk_widget_get_allocation (widget, &allocation); + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (widget); + attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK; + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL; + + window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + gdk_window_set_user_data (window, widget); + + gdk_window_show (window); + } + + scroll_to (canvas, x1, y1); + + /* If we created a an overlapping background None window, remove it how. + * + * TODO: We would like to temporarily set the bin_window background to + * None to avoid clearing the bin_window to the background, but gdk doesn't + * expose enought to let us do this, so we get a flash-effect here. At least + * it looks better than scroll + expose. + */ + if (window != NULL) + { + gdk_window_hide (window); + gdk_window_set_user_data (window, NULL); + gdk_window_destroy (window); + } + + canvas->need_repick = TRUE; +} + +/** + * eel_canvas_get_scroll_offsets: + * @canvas: A canvas. + * @cx: Horizontal scrolling offset (return value). + * @cy: Vertical scrolling offset (return value). + * + * Queries the scrolling offsets of a canvas. The values are returned in canvas + * pixel units. + **/ +void +eel_canvas_get_scroll_offsets (EelCanvas *canvas, + int *cx, + int *cy) +{ + GtkAdjustment *vadjustment, *hadjustment; + + g_return_if_fail (EEL_IS_CANVAS (canvas)); + + hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas)); + vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas)); + + if (cx) + { + *cx = gtk_adjustment_get_value (hadjustment); + } + + if (cy) + { + *cy = gtk_adjustment_get_value (vadjustment); + } +} + +/* Queues an update of the canvas */ +static void +eel_canvas_request_update (EelCanvas *canvas) +{ + EEL_CANVAS_GET_CLASS (canvas)->request_update (canvas); +} + +static void +eel_canvas_request_update_real (EelCanvas *canvas) +{ + canvas->need_update = TRUE; + add_idle (canvas); +} + +/** + * eel_canvas_request_redraw: + * @canvas: A canvas. + * @x1: Leftmost coordinate of the rectangle to be redrawn. + * @y1: Upper coordinate of the rectangle to be redrawn. + * @x2: Rightmost coordinate of the rectangle to be redrawn, plus 1. + * @y2: Lower coordinate of the rectangle to be redrawn, plus 1. + * + * Convenience function that informs a canvas that the specified rectangle needs + * to be repainted. The rectangle includes @x1 and @y1, but not @x2 and @y2. + * To be used only by item implementations. + **/ +void +eel_canvas_request_redraw (EelCanvas *canvas, + int x1, + int y1, + int x2, + int y2) +{ + GdkRectangle bbox; + + g_return_if_fail (EEL_IS_CANVAS (canvas)); + + if (!gtk_widget_is_drawable (GTK_WIDGET (canvas)) + || (x1 >= x2) || (y1 >= y2)) + { + return; + } + + bbox.x = x1; + bbox.y = y1; + bbox.width = x2 - x1; + bbox.height = y2 - y1; + + gdk_window_invalidate_rect (gtk_layout_get_bin_window (GTK_LAYOUT (canvas)), + &bbox, FALSE); +} + +/** + * eel_canvas_w2c: + * @canvas: A canvas. + * @wx: World X coordinate. + * @wy: World Y coordinate. + * @cx: X pixel coordinate (return value). + * @cy: Y pixel coordinate (return value). + * + * Converts world coordinates into canvas pixel coordinates. + **/ +void +eel_canvas_w2c (EelCanvas *canvas, + double wx, + double wy, + int *cx, + int *cy) +{ + double zoom; + + g_return_if_fail (EEL_IS_CANVAS (canvas)); + + zoom = canvas->pixels_per_unit; + + if (cx) + { + *cx = floor ((wx - canvas->scroll_x1) * zoom + canvas->zoom_xofs + 0.5); + } + if (cy) + { + *cy = floor ((wy - canvas->scroll_y1) * zoom + canvas->zoom_yofs + 0.5); + } +} + +/** + * eel_canvas_w2c: + * @canvas: A canvas. + * @world: rectangle in world coordinates. + * @canvas: rectangle in canvase coordinates. + * + * Converts rectangles in world coordinates into canvas pixel coordinates. + **/ +void +eel_canvas_w2c_rect_d (EelCanvas *canvas, + double *x1, + double *y1, + double *x2, + double *y2) +{ + eel_canvas_w2c_d (canvas, + *x1, *y1, + x1, y1); + eel_canvas_w2c_d (canvas, + *x2, *y2, + x2, y2); +} + + + +/** + * eel_canvas_w2c_d: + * @canvas: A canvas. + * @wx: World X coordinate. + * @wy: World Y coordinate. + * @cx: X pixel coordinate (return value). + * @cy: Y pixel coordinate (return value). + * + * Converts world coordinates into canvas pixel coordinates. This version + * produces coordinates in floating point coordinates, for greater precision. + **/ +void +eel_canvas_w2c_d (EelCanvas *canvas, + double wx, + double wy, + double *cx, + double *cy) +{ + double zoom; + + g_return_if_fail (EEL_IS_CANVAS (canvas)); + + zoom = canvas->pixels_per_unit; + + if (cx) + { + *cx = (wx - canvas->scroll_x1) * zoom + canvas->zoom_xofs; + } + if (cy) + { + *cy = (wy - canvas->scroll_y1) * zoom + canvas->zoom_yofs; + } +} + + +/** + * eel_canvas_c2w: + * @canvas: A canvas. + * @cx: Canvas pixel X coordinate. + * @cy: Canvas pixel Y coordinate. + * @wx: X world coordinate (return value). + * @wy: Y world coordinate (return value). + * + * Converts canvas pixel coordinates to world coordinates. + **/ +void +eel_canvas_c2w (EelCanvas *canvas, + int cx, + int cy, + double *wx, + double *wy) +{ + double zoom; + + g_return_if_fail (EEL_IS_CANVAS (canvas)); + + zoom = canvas->pixels_per_unit; + + if (wx) + { + *wx = (cx - canvas->zoom_xofs) / zoom + canvas->scroll_x1; + } + if (wy) + { + *wy = (cy - canvas->zoom_yofs) / zoom + canvas->scroll_y1; + } +} + + +/** + * eel_canvas_window_to_world: + * @canvas: A canvas. + * @winx: Window-relative X coordinate. + * @winy: Window-relative Y coordinate. + * @worldx: X world coordinate (return value). + * @worldy: Y world coordinate (return value). + * + * Converts window-relative coordinates into world coordinates. You can use + * this when you need to convert mouse coordinates into world coordinates, for + * example. + * Window coordinates are really the same as canvas coordinates now, but this + * function is here for backwards compatibility reasons. + **/ +void +eel_canvas_window_to_world (EelCanvas *canvas, + double winx, + double winy, + double *worldx, + double *worldy) +{ + g_return_if_fail (EEL_IS_CANVAS (canvas)); + + if (worldx) + { + *worldx = canvas->scroll_x1 + ((winx - canvas->zoom_xofs) + / canvas->pixels_per_unit); + } + + if (worldy) + { + *worldy = canvas->scroll_y1 + ((winy - canvas->zoom_yofs) + / canvas->pixels_per_unit); + } +} + + +/** + * eel_canvas_world_to_window: + * @canvas: A canvas. + * @worldx: World X coordinate. + * @worldy: World Y coordinate. + * @winx: X window-relative coordinate. + * @winy: Y window-relative coordinate. + * + * Converts world coordinates into window-relative coordinates. + * Window coordinates are really the same as canvas coordinates now, but this + * function is here for backwards compatibility reasons. + **/ +void +eel_canvas_world_to_window (EelCanvas *canvas, + double worldx, + double worldy, + double *winx, + double *winy) +{ + g_return_if_fail (EEL_IS_CANVAS (canvas)); + + if (winx) + { + *winx = (canvas->pixels_per_unit) * (worldx - canvas->scroll_x1) + canvas->zoom_xofs; + } + + if (winy) + { + *winy = (canvas->pixels_per_unit) * (worldy - canvas->scroll_y1) + canvas->zoom_yofs; + } +} + +static gboolean +boolean_handled_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy) +{ + gboolean continue_emission; + gboolean signal_handled; + + signal_handled = g_value_get_boolean (handler_return); + g_value_set_boolean (return_accu, signal_handled); + continue_emission = !signal_handled; + + return continue_emission; +} + +static void +eel_canvas_item_accessible_get_item_extents (EelCanvasItem *item, + GdkRectangle *rect) +{ + double bx1, bx2, by1, by2; + gint scroll_x, scroll_y; + gint x1, x2, y1, y2; + + eel_canvas_item_get_bounds (item, &bx1, &by1, &bx2, &by2); + eel_canvas_w2c_rect_d (item->canvas, &bx1, &by1, &bx2, &by2); + eel_canvas_get_scroll_offsets (item->canvas, &scroll_x, &scroll_y); + x1 = floor (bx1 + .5); + y1 = floor (by1 + .5); + x2 = floor (bx2 + .5); + y2 = floor (by2 + .5); + rect->x = x1 - scroll_x; + rect->y = y1 - scroll_y; + rect->width = x2 - x1; + rect->height = y2 - y1; +} + +static gboolean +eel_canvas_item_accessible_is_item_in_window (EelCanvasItem *item, + GdkRectangle *rect) +{ + GtkWidget *widget; + gboolean retval; + + widget = GTK_WIDGET (item->canvas); + if (gtk_widget_get_window (widget)) + { + int window_width, window_height; + + gdk_window_get_geometry (gtk_widget_get_window (widget), NULL, NULL, + &window_width, &window_height); + /* + * Check whether rectangles intersect + */ + if (rect->x + rect->width < 0 || + rect->y + rect->height < 0 || + rect->x > window_width || + rect->y > window_height) + { + retval = FALSE; + } + else + { + retval = TRUE; + } + } + else + { + retval = FALSE; + } + return retval; +} + + +static void +eel_canvas_item_accessible_get_extents (AtkComponent *component, + gint *x, + gint *y, + gint *width, + gint *height, + AtkCoordType coord_type) +{ + AtkGObjectAccessible *atk_gobj; + GObject *obj; + EelCanvasItem *item; + gint window_x, window_y; + gint toplevel_x, toplevel_y; + GdkRectangle rect; + GdkWindow *window; + GtkWidget *canvas; + + atk_gobj = ATK_GOBJECT_ACCESSIBLE (component); + obj = atk_gobject_accessible_get_object (atk_gobj); + + if (obj == NULL) + { + /* item is defunct */ + return; + } + + /* Get the CanvasItem */ + item = EEL_CANVAS_ITEM (obj); + + /* If this item has no parent canvas, something's broken */ + g_return_if_fail (GTK_IS_WIDGET (item->canvas)); + + eel_canvas_item_accessible_get_item_extents (item, &rect); + *width = rect.width; + *height = rect.height; + if (!eel_canvas_item_accessible_is_item_in_window (item, &rect)) + { + *x = G_MININT; + *y = G_MININT; + return; + } + + canvas = GTK_WIDGET (item->canvas); + window = gtk_widget_get_parent_window (canvas); + gdk_window_get_origin (window, &window_x, &window_y); + *x = rect.x + window_x; + *y = rect.y + window_y; + if (coord_type == ATK_XY_WINDOW) + { + window = gdk_window_get_toplevel (gtk_widget_get_window (canvas)); + gdk_window_get_origin (window, &toplevel_x, &toplevel_y); + *x -= toplevel_x; + *y -= toplevel_y; + } + return; +} + +static gint +eel_canvas_item_accessible_get_mdi_zorder (AtkComponent *component) +{ + AtkGObjectAccessible *atk_gobj; + GObject *g_obj; + EelCanvasItem *item; + + atk_gobj = ATK_GOBJECT_ACCESSIBLE (component); + g_obj = atk_gobject_accessible_get_object (atk_gobj); + if (g_obj == NULL) + { + /* Object is defunct */ + return -1; + } + + item = EEL_CANVAS_ITEM (g_obj); + if (item->parent) + { + return g_list_index (EEL_CANVAS_GROUP (item->parent)->item_list, item); + } + else + { + g_return_val_if_fail (item->canvas->root == item, -1); + return 0; + } +} + +static gboolean +eel_canvas_item_accessible_grab_focus (AtkComponent *component) +{ + AtkGObjectAccessible *atk_gobj; + GObject *obj; + EelCanvasItem *item; + GtkWidget *toplevel; + + atk_gobj = ATK_GOBJECT_ACCESSIBLE (component); + obj = atk_gobject_accessible_get_object (atk_gobj); + + item = EEL_CANVAS_ITEM (obj); + if (item == NULL) + { + /* item is defunct */ + return FALSE; + } + + eel_canvas_item_grab_focus (item); + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (item->canvas)); + if (gtk_widget_is_toplevel (toplevel)) + { + gtk_window_present (GTK_WINDOW (toplevel)); + } + + return TRUE; +} + +static void +eel_canvas_item_accessible_component_interface_init (AtkComponentIface *iface) +{ + g_return_if_fail (iface != NULL); + + iface->get_extents = eel_canvas_item_accessible_get_extents; + iface->get_mdi_zorder = eel_canvas_item_accessible_get_mdi_zorder; + iface->grab_focus = eel_canvas_item_accessible_grab_focus; +} + +static gboolean +eel_canvas_item_accessible_is_item_on_screen (EelCanvasItem *item) +{ + GdkRectangle rect; + + eel_canvas_item_accessible_get_item_extents (item, &rect); + return eel_canvas_item_accessible_is_item_in_window (item, &rect); +} + +static void +eel_canvas_item_accessible_initialize (AtkObject *obj, + gpointer data) +{ + if (ATK_OBJECT_CLASS (accessible_item_parent_class)->initialize != NULL) + { + ATK_OBJECT_CLASS (accessible_item_parent_class)->initialize (obj, data); + } + g_object_set_data (G_OBJECT (obj), "atk-component-layer", + GINT_TO_POINTER (ATK_LAYER_MDI)); +} + +static AtkStateSet * +eel_canvas_item_accessible_ref_state_set (AtkObject *accessible) +{ + AtkGObjectAccessible *atk_gobj; + GObject *obj; + EelCanvasItem *item; + AtkStateSet *state_set; + + state_set = ATK_OBJECT_CLASS (accessible_item_parent_class)->ref_state_set (accessible); + atk_gobj = ATK_GOBJECT_ACCESSIBLE (accessible); + obj = atk_gobject_accessible_get_object (atk_gobj); + + item = EEL_CANVAS_ITEM (obj); + if (item == NULL) + { + atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT); + } + else + { + if (item->flags & EEL_CANVAS_ITEM_VISIBLE) + { + atk_state_set_add_state (state_set, ATK_STATE_VISIBLE); + + if (eel_canvas_item_accessible_is_item_on_screen (item)) + { + atk_state_set_add_state (state_set, ATK_STATE_SHOWING); + } + } + if (gtk_widget_get_can_focus (GTK_WIDGET (item->canvas))) + { + atk_state_set_add_state (state_set, ATK_STATE_FOCUSABLE); + + if (item->canvas->focused_item == item) + { + atk_state_set_add_state (state_set, ATK_STATE_FOCUSED); + } + } + } + + return state_set; +} + +static void +eel_canvas_item_accessible_class_init (EelCanvasItemAccessibleClass *klass) +{ + AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass); + + accessible_item_parent_class = g_type_class_peek_parent (klass); + + atk_class->initialize = eel_canvas_item_accessible_initialize; + atk_class->ref_state_set = eel_canvas_item_accessible_ref_state_set; +} + +static void +eel_canvas_item_accessible_init (EelCanvasItemAccessible *self) +{ +} + +G_DEFINE_TYPE_WITH_CODE (EelCanvasItemAccessible, + eel_canvas_item_accessible, + ATK_TYPE_GOBJECT_ACCESSIBLE, + G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT, + eel_canvas_item_accessible_component_interface_init)); + +static GType eel_canvas_item_accessible_factory_get_type (void); + +typedef AtkObjectFactory EelCanvasItemAccessibleFactory; +typedef AtkObjectFactoryClass EelCanvasItemAccessibleFactoryClass; +G_DEFINE_TYPE (EelCanvasItemAccessibleFactory, eel_canvas_item_accessible_factory, + ATK_TYPE_OBJECT_FACTORY) + +static GType +eel_canvas_item_accessible_factory_get_accessible_type (void) +{ + return eel_canvas_item_accessible_get_type (); +} + +static AtkObject * +eel_canvas_item_accessible_factory_create_accessible (GObject *for_object) +{ + AtkObject *accessible; + + accessible = g_object_new (eel_canvas_item_accessible_get_type (), NULL); + atk_object_initialize (accessible, for_object); + return accessible; +} + +static void +eel_canvas_item_accessible_factory_init (EelCanvasItemAccessibleFactory *self) +{ +} + +static void +eel_canvas_item_accessible_factory_class_init (AtkObjectFactoryClass *klass) +{ + klass->create_accessible = eel_canvas_item_accessible_factory_create_accessible; + klass->get_accessible_type = eel_canvas_item_accessible_factory_get_accessible_type; +} + +/* Class initialization function for EelCanvasItemClass */ +static void +eel_canvas_item_class_init (EelCanvasItemClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + item_parent_class = g_type_class_peek_parent (klass); + + gobject_class->set_property = eel_canvas_item_set_property; + gobject_class->get_property = eel_canvas_item_get_property; + gobject_class->dispose = eel_canvas_item_dispose; + + g_object_class_install_property + (gobject_class, ITEM_PROP_VISIBLE, + g_param_spec_boolean ("visible", NULL, NULL, + TRUE, + G_PARAM_READWRITE)); + + item_signals[ITEM_EVENT] = + g_signal_new ("event", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EelCanvasItemClass, event), + boolean_handled_accumulator, NULL, + g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, 1, + GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE); + + item_signals[ITEM_DESTROY] = + g_signal_new ("destroy", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_CLEANUP | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, + G_STRUCT_OFFSET (EelCanvasItemClass, destroy), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + klass->realize = eel_canvas_item_realize; + klass->unrealize = eel_canvas_item_unrealize; + klass->map = eel_canvas_item_map; + klass->unmap = eel_canvas_item_unmap; + klass->update = eel_canvas_item_update; + + atk_registry_set_factory_type (atk_get_default_registry (), + EEL_TYPE_CANVAS_ITEM, + eel_canvas_item_accessible_factory_get_type ()); +} diff --git a/eel/eel-canvas.h b/eel/eel-canvas.h new file mode 100644 index 0000000..f406ca0 --- /dev/null +++ b/eel/eel-canvas.h @@ -0,0 +1,497 @@ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see <http://www.gnu.org/licenses/>. + */ +/* + @NOTATION@ + */ +/* EelCanvas widget - Tk-like canvas widget for Gnome + * + * EelCanvas is basically a port of the Tk toolkit's most excellent canvas + * widget. Tk is copyrighted by the Regents of the University of California, + * Sun Microsystems, and other parties. + * + * + * Authors: Federico Mena <federico@nuclecu.unam.mx> + * Raph Levien <raph@gimp.org> + */ + +#pragma once + +#include <gtk/gtk.h> +#include <gtk/gtk-a11y.h> +#include <gdk/gdk.h> +#include <stdarg.h> + +G_BEGIN_DECLS + + +/* "Small" value used by canvas stuff */ +#define EEL_CANVAS_EPSILON 1e-10 + + +/* Macros for building colors that fit in a 32-bit integer. The values are in + * [0, 255]. + */ + +#define EEL_CANVAS_COLOR(r, g, b) ((((int) (r) & 0xff) << 24) \ + | (((int) (g) & 0xff) << 16) \ + | (((int) (b) & 0xff) << 8) \ + | 0xff) + +#define EEL_CANVAS_COLOR_A(r, g, b, a) ((((int) (r) & 0xff) << 24) \ + | (((int) (g) & 0xff) << 16) \ + | (((int) (b) & 0xff) << 8) \ + | ((int) (a) & 0xff)) + + +typedef struct _EelCanvas EelCanvas; +typedef struct _EelCanvasClass EelCanvasClass; +typedef struct _EelCanvasItem EelCanvasItem; +typedef struct _EelCanvasItemClass EelCanvasItemClass; +typedef struct _EelCanvasGroup EelCanvasGroup; +typedef struct _EelCanvasGroupClass EelCanvasGroupClass; + + +/* EelCanvasItem - base item class for canvas items + * + * All canvas items are derived from EelCanvasItem. The only information a + * EelCanvasItem contains is its parent canvas, its parent canvas item group, + * and its bounding box in world coordinates. + * + * Items inside a canvas are organized in a tree of EelCanvasItemGroup nodes + * and EelCanvasItem leaves. Each canvas has a single root group, which can + * be obtained with the eel_canvas_get_root() function. + * + * The abstract EelCanvasItem class does not have any configurable or + * queryable attributes. + */ + +/* Object flags for items */ +enum { + EEL_CANVAS_ITEM_REALIZED = 1 << 4, + EEL_CANVAS_ITEM_MAPPED = 1 << 5, + EEL_CANVAS_ITEM_ALWAYS_REDRAW = 1 << 6, + EEL_CANVAS_ITEM_VISIBLE = 1 << 7, + EEL_CANVAS_ITEM_NEED_UPDATE = 1 << 8, + EEL_CANVAS_ITEM_NEED_DEEP_UPDATE = 1 << 9 +}; + +/* Update flags for items */ +enum { + EEL_CANVAS_UPDATE_REQUESTED = 1 << 0, + EEL_CANVAS_UPDATE_DEEP = 1 << 1 +}; + +#define EEL_TYPE_CANVAS_ITEM (eel_canvas_item_get_type ()) +#define EEL_CANVAS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEL_TYPE_CANVAS_ITEM, EelCanvasItem)) +#define EEL_CANVAS_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EEL_TYPE_CANVAS_ITEM, EelCanvasItemClass)) +#define EEL_IS_CANVAS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EEL_TYPE_CANVAS_ITEM)) +#define EEL_IS_CANVAS_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEL_TYPE_CANVAS_ITEM)) +#define EEL_CANVAS_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEL_TYPE_CANVAS_ITEM, EelCanvasItemClass)) + + +struct _EelCanvasItem { + GInitiallyUnowned object; + + /* Parent canvas for this item */ + EelCanvas *canvas; + + /* Parent canvas group for this item (a EelCanvasGroup) */ + EelCanvasItem *parent; + + /* Bounding box for this item (in canvas coordinates) */ + double x1, y1, x2, y2; + + /* Object flags */ + guint flags; +}; + +struct _EelCanvasItemClass { + GInitiallyUnownedClass parent_class; + + void (* destroy) (EelCanvasItem *item); + + /* Tell the item to update itself. The flags are from the update flags + * defined above. The item should update its internal state from its + * queued state, and recompute and request its repaint area. The + * update method also recomputes the bounding box of the item. + */ + void (* update) (EelCanvasItem *item, double i2w_dx, double i2w_dy, int flags); + + /* Realize an item -- create GCs, etc. */ + void (* realize) (EelCanvasItem *item); + + /* Unrealize an item */ + void (* unrealize) (EelCanvasItem *item); + + /* Map an item - normally only need by items with their own GdkWindows */ + void (* map) (EelCanvasItem *item); + + /* Unmap an item */ + void (* unmap) (EelCanvasItem *item); + + /* Draw an item of this type. (x, y) are the upper-left canvas pixel + * coordinates of the drawable, a temporary pixmap, where things get + * drawn. (width, height) are the dimensions of the drawable. + */ + void (* draw) (EelCanvasItem *item, cairo_t *cr, cairo_region_t *region); + + /* Calculate the distance from an item to the specified point. It also + * returns a canvas item which is the item itself in the case of the + * object being an actual leaf item, or a child in case of the object + * being a canvas group. (cx, cy) are the canvas pixel coordinates that + * correspond to the item-relative coordinates (x, y). + */ + double (* point) (EelCanvasItem *item, double x, double y, int cx, int cy, + EelCanvasItem **actual_item); + + void (* translate) (EelCanvasItem *item, double dx, double dy); + + /* Fetch the item's bounding box (need not be exactly tight). This + * should be in item-relative coordinates. + */ + void (* bounds) (EelCanvasItem *item, double *x1, double *y1, double *x2, double *y2); + + /* Signal: an event ocurred for an item of this type. The (x, y) + * coordinates are in the canvas world coordinate system. + */ + gboolean (* event) (EelCanvasItem *item, GdkEvent *event); + + /* Reserved for future expansion */ + gpointer spare_vmethods [4]; +}; + + +/* Standard Gtk function */ +GType eel_canvas_item_get_type (void) G_GNUC_CONST; + +/* Create a canvas item using the standard Gtk argument mechanism. The item is + * automatically inserted at the top of the specified canvas group. The last + * argument must be a NULL pointer. + */ +EelCanvasItem *eel_canvas_item_new (EelCanvasGroup *parent, GType type, + const gchar *first_arg_name, ...); + +void eel_canvas_item_destroy (EelCanvasItem *item); + +/* Configure an item using the standard Gtk argument mechanism. The last + * argument must be a NULL pointer. + */ +void eel_canvas_item_set (EelCanvasItem *item, const gchar *first_arg_name, ...); + +/* Move an item by the specified amount */ +void eel_canvas_item_move (EelCanvasItem *item, double dx, double dy); + +/* Raise an item in the z-order of its parent group by the specified number of + * positions. + */ +void eel_canvas_item_raise (EelCanvasItem *item, int positions); + +/* Lower an item in the z-order of its parent group by the specified number of + * positions. + */ +void eel_canvas_item_lower (EelCanvasItem *item, int positions); + +/* Raise an item to the top of its parent group's z-order. */ +void eel_canvas_item_raise_to_top (EelCanvasItem *item); + +/* Lower an item to the bottom of its parent group's z-order */ +void eel_canvas_item_lower_to_bottom (EelCanvasItem *item); + +/* Send an item behind another item */ +void eel_canvas_item_send_behind (EelCanvasItem *item, + EelCanvasItem *behind_item); + + +/* Show an item (make it visible). If the item is already shown, it has no + * effect. + */ +void eel_canvas_item_show (EelCanvasItem *item); + +/* Hide an item (make it invisible). If the item is already invisible, it has + * no effect. + */ +void eel_canvas_item_hide (EelCanvasItem *item); + +/* Grab the seat for the specified item. Only the events in event_mask will be + * reported. If cursor is non-NULL, it will be used during the duration of the + * grab. event is the event, triggering the grab. Returns the same values as gdk_seat_grab(). + */ +GdkGrabStatus eel_canvas_item_grab (EelCanvasItem *item, + GdkEventMask event_mask, + GdkCursor *cursor, + const GdkEvent* event); + +/* Ungrabs the seat -- the specified item must be the same that was passed to + * eel_canvas_item_grab(). + */ +void eel_canvas_item_ungrab (EelCanvasItem *item); + +/* These functions convert from a coordinate system to another. "w" is world + * coordinates and "i" is item coordinates. + */ +void eel_canvas_item_i2w (EelCanvasItem *item, double *x, double *y); + +/* Fetch the bounding box of the item. The bounding box may not be exactly + * tight, but the canvas items will do the best they can. The returned bounding + * box is in the coordinate system of the item's parent. + */ +void eel_canvas_item_get_bounds (EelCanvasItem *item, + double *x1, double *y1, double *x2, double *y2); + +/* Request that the update method eventually get called. This should be used + * only by item implementations. + */ +void eel_canvas_item_request_update (EelCanvasItem *item); + +/* Request a redraw of the bounding box of the canvas item */ +void eel_canvas_item_request_redraw (EelCanvasItem *item); + +/* EelCanvasGroup - a group of canvas items + * + * A group is a node in the hierarchical tree of groups/items inside a canvas. + * Groups serve to give a logical structure to the items. + * + * Consider a circuit editor application that uses the canvas for its schematic + * display. Hierarchically, there would be canvas groups that contain all the + * components needed for an "adder", for example -- this includes some logic + * gates as well as wires. You can move stuff around in a convenient way by + * doing a eel_canvas_item_move() of the hierarchical groups -- to move an + * adder, simply move the group that represents the adder. + * + * The following arguments are available: + * + * name type read/write description + * -------------------------------------------------------------------------------- + * x double RW X coordinate of group's origin + * y double RW Y coordinate of group's origin + */ + + +#define EEL_TYPE_CANVAS_GROUP (eel_canvas_group_get_type ()) +#define EEL_CANVAS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEL_TYPE_CANVAS_GROUP, EelCanvasGroup)) +#define EEL_CANVAS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EEL_TYPE_CANVAS_GROUP, EelCanvasGroupClass)) +#define EEL_IS_CANVAS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EEL_TYPE_CANVAS_GROUP)) +#define EEL_IS_CANVAS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEL_TYPE_CANVAS_GROUP)) +#define EEL_CANVAS_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEL_TYPE_CANVAS_GROUP, EelCanvasGroupClass)) + + +struct _EelCanvasGroup { + EelCanvasItem item; + + double xpos, ypos; + + /* Children of the group */ + GList *item_list; + GList *item_list_end; +}; + +struct _EelCanvasGroupClass { + EelCanvasItemClass parent_class; +}; + + +/* Standard Gtk function */ +GType eel_canvas_group_get_type (void) G_GNUC_CONST; + + +/*** EelCanvas ***/ + + +#define EEL_TYPE_CANVAS (eel_canvas_get_type ()) +#define EEL_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEL_TYPE_CANVAS, EelCanvas)) +#define EEL_CANVAS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EEL_TYPE_CANVAS, EelCanvasClass)) +#define EEL_IS_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EEL_TYPE_CANVAS)) +#define EEL_IS_CANVAS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEL_TYPE_CANVAS)) +#define EEL_CANVAS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEL_TYPE_CANVAS, EelCanvasClass)) + + +struct _EelCanvas { + GtkLayout layout; + + /* Root canvas group */ + EelCanvasItem *root; + + /* The item containing the mouse pointer, or NULL if none */ + EelCanvasItem *current_item; + + /* Item that is about to become current (used to track deletions and such) */ + EelCanvasItem *new_current_item; + + /* Item that holds a pointer grab, or NULL if none */ + EelCanvasItem *grabbed_item; + + /* If non-NULL, the currently focused item */ + EelCanvasItem *focused_item; + + /* Event on which selection of current item is based */ + GdkEvent pick_event; + + /* Scrolling region */ + double scroll_x1, scroll_y1; + double scroll_x2, scroll_y2; + + /* Scaling factor to be used for display */ + double pixels_per_unit; + + /* Idle handler ID */ + guint idle_id; + + /* Signal handler ID for destruction of the root item */ + gulong root_destroy_id; + + /* Internal pixel offsets when zoomed out */ + int zoom_xofs, zoom_yofs; + + /* Last known modifier state, for deferred repick when a button is down */ + int state; + + /* Event mask specified when grabbing an item */ + guint grabbed_event_mask; + + /* Tolerance distance for picking items */ + int close_enough; + + /* Whether the canvas should center the canvas in the middle of + * the window if the scroll region is smaller than the window */ + unsigned int center_scroll_region : 1; + + /* Whether items need update at next idle loop iteration */ + unsigned int need_update : 1; + + /* Are we in the midst of an update */ + unsigned int doing_update : 1; + + /* Whether the canvas needs redrawing at the next idle loop iteration */ + unsigned int need_redraw : 1; + + /* Whether current item will be repicked at next idle loop iteration */ + unsigned int need_repick : 1; + + /* For use by internal pick_current_item() function */ + unsigned int left_grabbed_item : 1; + + /* For use by internal pick_current_item() function */ + unsigned int in_repick : 1; +}; + +struct _EelCanvasClass { + GtkLayoutClass parent_class; + + /* Private Virtual methods for groping the canvas inside bonobo */ + void (* request_update) (EelCanvas *canvas); + + /* Reserved for future expansion */ + gpointer spare_vmethods [4]; +}; + + +/* Standard Gtk function */ +GType eel_canvas_get_type (void) G_GNUC_CONST; + +/* Creates a new canvas. You should check that the canvas is created with the + * proper visual and colormap. Any visual will do unless you intend to insert + * gdk_imlib images into it, in which case you should use the gdk_imlib visual. + * + * You should call eel_canvas_set_scroll_region() soon after calling this + * function to set the desired scrolling limits for the canvas. + */ +GtkWidget *eel_canvas_new (void); + +/* Returns the root canvas item group of the canvas */ +EelCanvasGroup *eel_canvas_root (EelCanvas *canvas); + +/* Sets the limits of the scrolling region, in world coordinates */ +void eel_canvas_set_scroll_region (EelCanvas *canvas, + double x1, double y1, double x2, double y2); + +/* Gets the limits of the scrolling region, in world coordinates */ +void eel_canvas_get_scroll_region (EelCanvas *canvas, + double *x1, double *y1, double *x2, double *y2); + +/* Sets the number of pixels that correspond to one unit in world coordinates */ +void eel_canvas_set_pixels_per_unit (EelCanvas *canvas, double n); + +/* Returns the scroll offsets of the canvas in canvas pixel coordinates. You + * can specify NULL for any of the values, in which case that value will not be + * queried. + */ +void eel_canvas_get_scroll_offsets (EelCanvas *canvas, int *cx, int *cy); + +/* For use only by item type implementations. Request that the canvas + * eventually redraw the specified region, specified in canvas pixel + * coordinates. The region contains (x1, y1) but not (x2, y2). + */ +void eel_canvas_request_redraw (EelCanvas *canvas, int x1, int y1, int x2, int y2); + +/* These functions convert from a coordinate system to another. "w" is world + * coordinates, "c" is canvas pixel coordinates (pixel coordinates that are + * (0,0) for the upper-left scrolling limit and something else for the + * lower-left scrolling limit). + */ +void eel_canvas_w2c_rect_d (EelCanvas *canvas, + double *x1, double *y1, + double *x2, double *y2); +void eel_canvas_w2c (EelCanvas *canvas, double wx, double wy, int *cx, int *cy); +void eel_canvas_w2c_d (EelCanvas *canvas, double wx, double wy, double *cx, double *cy); +void eel_canvas_c2w (EelCanvas *canvas, int cx, int cy, double *wx, double *wy); + +/* This function takes in coordinates relative to the GTK_LAYOUT + * (canvas)->bin_window and converts them to world coordinates. + * These days canvas coordinates and window coordinates are the same, but + * these are left for backwards compat reasons. + */ +void eel_canvas_window_to_world (EelCanvas *canvas, + double winx, double winy, double *worldx, double *worldy); + +/* This is the inverse of eel_canvas_window_to_world() */ +void eel_canvas_world_to_window (EelCanvas *canvas, + double worldx, double worldy, double *winx, double *winy); + +/* Accessible implementation */ +GType eel_canvas_accessible_get_type (void); + +typedef struct _EelCanvasAccessible EelCanvasAccessible; +struct _EelCanvasAccessible +{ + GtkContainerAccessible parent; +}; + +typedef struct _EelCanvasAccessibleClass EelCanvasAccessibleClass; +struct _EelCanvasAccessibleClass +{ + GtkContainerAccessibleClass parent_class; +}; + +GType eel_canvas_item_accessible_get_type (void); + +typedef struct _EelCanvasItemAccessible EelCanvasItemAccessible; +struct _EelCanvasItemAccessible +{ + GtkAccessible parent; +}; + +typedef struct _EelCanvasItemAccessibleClass EelCanvasItemAccessibleClass; +struct _EelCanvasItemAccessibleClass +{ + GtkAccessibleClass parent_class; +}; + +G_END_DECLS
\ No newline at end of file diff --git a/eel/eel-debug.c b/eel/eel-debug.c new file mode 100644 index 0000000..f3d94ec --- /dev/null +++ b/eel/eel-debug.c @@ -0,0 +1,105 @@ +/* + * eel-debug.c: Eel debugging aids. + * + * Copyright (C) 2000, 2001 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: Darin Adler <darin@eazel.com> + */ + +#include <config.h> +#include "eel-debug.h" + +#include <glib.h> +#include <signal.h> +#include <stdio.h> + +typedef struct +{ + gpointer data; + GFreeFunc function; +} ShutdownFunction; + +static GList *shutdown_functions; + +/* Raise a SIGINT signal to get the attention of the debugger. + * When not running under the debugger, we don't want to stop, + * so we ignore the signal for just the moment that we raise it. + */ +static void +eel_stop_in_debugger (void) +{ + void (*saved_handler) (int); + + saved_handler = signal (SIGINT, SIG_IGN); + raise (SIGINT); + signal (SIGINT, saved_handler); +} + +/* Stop in the debugger after running the default log handler. + * This makes certain kinds of messages stop in the debugger + * without making them fatal (you can continue). + */ +static void +log_handler (const char *domain, + GLogLevelFlags level, + const char *message, + gpointer data) +{ + g_log_default_handler (domain, level, message, data); + if ((level & (G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING)) != 0) + { + eel_stop_in_debugger (); + } +} + +void +eel_make_warnings_and_criticals_stop_in_debugger (void) +{ + g_log_set_default_handler (log_handler, NULL); +} + +void +eel_debug_shut_down (void) +{ + ShutdownFunction *f; + + while (shutdown_functions != NULL) + { + f = shutdown_functions->data; + shutdown_functions = g_list_remove (shutdown_functions, f); + + f->function (f->data); + g_free (f); + } +} + +void +eel_debug_call_at_shutdown (EelFunction function) +{ + eel_debug_call_at_shutdown_with_data ((GFreeFunc) function, NULL); +} + +void +eel_debug_call_at_shutdown_with_data (GFreeFunc function, + gpointer data) +{ + ShutdownFunction *f; + + f = g_new (ShutdownFunction, 1); + f->data = data; + f->function = function; + shutdown_functions = g_list_prepend (shutdown_functions, f); +} diff --git a/eel/eel-debug.h b/eel/eel-debug.h new file mode 100644 index 0000000..5d65932 --- /dev/null +++ b/eel/eel-debug.h @@ -0,0 +1,40 @@ +/* + eel-debug.h: Eel debugging aids. + + Copyright (C) 2000, 2001 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this program; if not, see <http://www.gnu.org/licenses/>. + + Author: Darin Adler <darin@eazel.com> +*/ + +#pragma once + +#include <glib.h> + +G_BEGIN_DECLS + +typedef void (* EelFunction) (void); + +void eel_make_warnings_and_criticals_stop_in_debugger (void); + +/* A way to do cleanup at exit for compatibility with shutdown tools + * like the ones in Bonobo. + */ +void eel_debug_shut_down (void); +void eel_debug_call_at_shutdown (EelFunction function); +void eel_debug_call_at_shutdown_with_data (GFreeFunc function, + gpointer data); + +G_END_DECLS
\ No newline at end of file diff --git a/eel/eel-glib-extensions.h b/eel/eel-glib-extensions.h new file mode 100644 index 0000000..3b0ea0d --- /dev/null +++ b/eel/eel-glib-extensions.h @@ -0,0 +1,30 @@ + +/* eel-glib-extensions.h - interface for new functions that conceptually + belong in glib. Perhaps some of these will be + actually rolled into glib someday. + + Copyright (C) 2000 Eazel, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + see <http://www.gnu.org/licenses/>. + + Authors: John Sullivan <sullivan@eazel.com> +*/ + +#pragma once + +#include <glib.h> + +/* A gboolean variant for bit fields. */ +typedef guint eel_boolean_bit; diff --git a/eel/eel-graphic-effects.c b/eel/eel-graphic-effects.c new file mode 100644 index 0000000..06af091 --- /dev/null +++ b/eel/eel-graphic-effects.c @@ -0,0 +1,165 @@ +/* Eel - pixbuf manipulation routines for graphical effects. + * + * Copyright (C) 2000 Eazel, Inc + * + * Author: Andy Hertzfeld <andy@eazel.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +/* This file contains pixbuf manipulation routines used for graphical effects like pre-lighting + * and selection hilighting */ + +#include <config.h> + +#include "eel-graphic-effects.h" +#include "eel-glib-extensions.h" + +#include <math.h> +#include <string.h> + +/* shared utility to create a new pixbuf from the passed-in one */ + +static GdkPixbuf * +create_new_pixbuf (GdkPixbuf *src) +{ + g_assert (gdk_pixbuf_get_colorspace (src) == GDK_COLORSPACE_RGB); + g_assert ((!gdk_pixbuf_get_has_alpha (src) + && gdk_pixbuf_get_n_channels (src) == 3) + || (gdk_pixbuf_get_has_alpha (src) + && gdk_pixbuf_get_n_channels (src) == 4)); + + return gdk_pixbuf_new (gdk_pixbuf_get_colorspace (src), + gdk_pixbuf_get_has_alpha (src), + gdk_pixbuf_get_bits_per_sample (src), + gdk_pixbuf_get_width (src), + gdk_pixbuf_get_height (src)); +} + +/* utility routine to bump the level of a color component with pinning */ + +const int HOVER_COMPONENT_ADDITION = 15; + +static guchar +lighten_component (guchar cur_value) +{ + int new_value = cur_value; + new_value = cur_value + HOVER_COMPONENT_ADDITION; + if (new_value > 255) + { + new_value = 255; + } + return (guchar) new_value; +} + +GdkPixbuf * +eel_create_spotlight_pixbuf (GdkPixbuf *src) +{ + GdkPixbuf *dest; + int i, j; + int width, height, has_alpha, src_row_stride, dst_row_stride; + guchar *target_pixels, *original_pixels; + guchar *pixsrc, *pixdest; + + g_return_val_if_fail (gdk_pixbuf_get_colorspace (src) == GDK_COLORSPACE_RGB, NULL); + g_return_val_if_fail ((!gdk_pixbuf_get_has_alpha (src) + && gdk_pixbuf_get_n_channels (src) == 3) + || (gdk_pixbuf_get_has_alpha (src) + && gdk_pixbuf_get_n_channels (src) == 4), NULL); + g_return_val_if_fail (gdk_pixbuf_get_bits_per_sample (src) == 8, NULL); + + dest = create_new_pixbuf (src); + + has_alpha = gdk_pixbuf_get_has_alpha (src); + width = gdk_pixbuf_get_width (src); + height = gdk_pixbuf_get_height (src); + dst_row_stride = gdk_pixbuf_get_rowstride (dest); + src_row_stride = gdk_pixbuf_get_rowstride (src); + target_pixels = gdk_pixbuf_get_pixels (dest); + original_pixels = gdk_pixbuf_get_pixels (src); + + for (i = 0; i < height; i++) + { + pixdest = target_pixels + i * dst_row_stride; + pixsrc = original_pixels + i * src_row_stride; + for (j = 0; j < width; j++) + { + *pixdest++ = lighten_component (*pixsrc++); + *pixdest++ = lighten_component (*pixsrc++); + *pixdest++ = lighten_component (*pixsrc++); + if (has_alpha) + { + *pixdest++ = *pixsrc++; + } + } + } + return dest; +} + +/* This routine colorizes %src by multiplying each pixel with colors in %dest. */ + +GdkPixbuf * +eel_create_colorized_pixbuf (GdkPixbuf *src, + GdkPixbuf *dest) +{ + int i, j; + int width, height, has_alpha, src_row_stride, dst_row_stride; + guchar *target_pixels; + guchar *original_pixels; + guchar *pixsrc; + guchar *pixdest; + + g_return_val_if_fail (gdk_pixbuf_get_colorspace (src) == GDK_COLORSPACE_RGB, NULL); + g_return_val_if_fail (gdk_pixbuf_get_colorspace (dest) == GDK_COLORSPACE_RGB, NULL); + + g_return_val_if_fail ((!gdk_pixbuf_get_has_alpha (src) + && gdk_pixbuf_get_n_channels (src) == 3) + || (gdk_pixbuf_get_has_alpha (src) + && gdk_pixbuf_get_n_channels (src) == 4), NULL); + g_return_val_if_fail ((!gdk_pixbuf_get_has_alpha (dest) + && gdk_pixbuf_get_n_channels (dest) == 3) + || (gdk_pixbuf_get_has_alpha (dest) + && gdk_pixbuf_get_n_channels (dest) == 4), NULL); + + g_return_val_if_fail (gdk_pixbuf_get_bits_per_sample (src) == 8, NULL); + g_return_val_if_fail (gdk_pixbuf_get_bits_per_sample (dest) == 8, NULL); + + has_alpha = gdk_pixbuf_get_has_alpha (src); + width = gdk_pixbuf_get_width (src); + height = gdk_pixbuf_get_height (src); + src_row_stride = gdk_pixbuf_get_rowstride (src); + dst_row_stride = gdk_pixbuf_get_rowstride (dest); + target_pixels = gdk_pixbuf_get_pixels (dest); + original_pixels = gdk_pixbuf_get_pixels (src); + + for (i = 0; i < height; i++) + { + pixdest = target_pixels + i * dst_row_stride; + pixsrc = original_pixels + i * src_row_stride; + for (j = 0; j < width; j++) + { + *pixdest = ((*pixsrc++) * (*pixdest)) >> 8; + pixdest++; + *pixdest = ((*pixsrc++) * (*pixdest)) >> 8; + pixdest++; + *pixdest = ((*pixsrc++) * (*pixdest)) >> 8; + pixdest++; + if (has_alpha) + { + *pixdest++ = *pixsrc++; + } + } + } + return dest; +} diff --git a/eel/eel-graphic-effects.h b/eel/eel-graphic-effects.h new file mode 100644 index 0000000..67de5f7 --- /dev/null +++ b/eel/eel-graphic-effects.h @@ -0,0 +1,32 @@ +/* + eel-graphic-effects.h: Pixmap manipulation routines for graphical effects. + + Copyright (C) 2000 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this program; if not, see <http://www.gnu.org/licenses/>. + + Authors: Andy Hertzfeld <andy@eazel.com> + */ + +#pragma once + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gdk/gdk.h> + +/* return a lightened pixbuf for pre-lighting */ +GdkPixbuf *eel_create_spotlight_pixbuf (GdkPixbuf *source_pixbuf); + +/* return a pixbuf colorized with the color specified by the parameters */ +GdkPixbuf* eel_create_colorized_pixbuf (GdkPixbuf *source_pixbuf, + GdkPixbuf *dest); diff --git a/eel/eel-gtk-extensions.c b/eel/eel-gtk-extensions.c new file mode 100644 index 0000000..45b072d --- /dev/null +++ b/eel/eel-gtk-extensions.c @@ -0,0 +1,93 @@ +/* eel-gtk-extensions.c - implementation of new functions that operate on + * gtk classes. Perhaps some of these should be + * rolled into gtk someday. + * + * Copyright (C) 1999, 2000, 2001 Eazel, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see <http://www.gnu.org/licenses/>. + * + * Authors: John Sullivan <sullivan@eazel.com> + * Ramiro Estrugo <ramiro@eazel.com> + * Darin Adler <darin@eazel.com> + */ + +#include <config.h> +#include "eel-gtk-extensions.h" + +#include "eel-glib-extensions.h" +#include "eel-string.h" + +#include <gdk/gdk.h> +#include <gtk/gtk.h> +#include <glib/gi18n-lib.h> +#include <math.h> + +/* This number is fairly arbitrary. Long enough to show a pretty long + * menu title, but not so long to make a menu grotesquely wide. + */ +#define MAXIMUM_MENU_TITLE_LENGTH 48 + +/* Used for window position & size sanity-checking. The sizes are big enough to prevent + * at least normal-sized gnome panels from obscuring the window at the screen edges. + */ +#define MINIMUM_ON_SCREEN_WIDTH 100 +#define MINIMUM_ON_SCREEN_HEIGHT 100 + + +/** + * eel_gtk_window_get_geometry_string: + * @window: a #GtkWindow + * + * Obtains the geometry string for this window, suitable for + * set_geometry_string(); assumes the window has NorthWest gravity + * + * Return value: geometry string, must be freed + **/ +char * +eel_gtk_window_get_geometry_string (GtkWindow *window) +{ + char *str; + int w, h, x, y; + + g_return_val_if_fail (GTK_IS_WINDOW (window), NULL); + g_return_val_if_fail (gtk_window_get_gravity (window) == + GDK_GRAVITY_NORTH_WEST, NULL); + + gtk_window_get_position (window, &x, &y); + gtk_window_get_size (window, &w, &h); + + str = g_strdup_printf ("%dx%d+%d+%d", w, h, x, y); + + return str; +} + +GtkMenuItem * +eel_gtk_menu_append_separator (GtkMenu *menu) +{ + return eel_gtk_menu_insert_separator (menu, -1); +} + +GtkMenuItem * +eel_gtk_menu_insert_separator (GtkMenu *menu, + int index) +{ + GtkWidget *menu_item; + + menu_item = gtk_separator_menu_item_new (); + gtk_widget_show (menu_item); + gtk_menu_shell_insert (GTK_MENU_SHELL (menu), menu_item, index); + + return GTK_MENU_ITEM (menu_item); +} diff --git a/eel/eel-gtk-extensions.h b/eel/eel-gtk-extensions.h new file mode 100644 index 0000000..0a5eac1 --- /dev/null +++ b/eel/eel-gtk-extensions.h @@ -0,0 +1,37 @@ + +/* eel-gtk-extensions.h - interface for new functions that operate on + gtk classes. Perhaps some of these should be + rolled into gtk someday. + + Copyright (C) 1999, 2000, 2001 Eazel, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + see <http://www.gnu.org/licenses/>. + + Authors: John Sullivan <sullivan@eazel.com> + Ramiro Estrugo <ramiro@eazel.com> +*/ + +#pragma once + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gtk/gtk.h> + +/* GtkWindow */ +char * eel_gtk_window_get_geometry_string (GtkWindow *window); + +/* GtkMenu and GtkMenuItem */ +GtkMenuItem * eel_gtk_menu_append_separator (GtkMenu *menu); +GtkMenuItem * eel_gtk_menu_insert_separator (GtkMenu *menu, + int index);
\ No newline at end of file diff --git a/eel/eel-lib-self-check-functions.c b/eel/eel-lib-self-check-functions.c new file mode 100644 index 0000000..af1dddf --- /dev/null +++ b/eel/eel-lib-self-check-functions.c @@ -0,0 +1,35 @@ +/* + * eel-lib-self-check-functions.c: Wrapper for all self check functions + * in Eel proper. + * + * Copyright (C) 2000 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: Darin Adler <darin@eazel.com> + */ + +#include <config.h> + +#if !defined (EEL_OMIT_SELF_CHECK) + +#include "eel-lib-self-check-functions.h" + +void +eel_run_lib_self_checks (void) +{ + EEL_LIB_FOR_EACH_SELF_CHECK_FUNCTION (EEL_CALL_SELF_CHECK_FUNCTION) +} + +#endif /* ! EEL_OMIT_SELF_CHECK */ diff --git a/eel/eel-lib-self-check-functions.h b/eel/eel-lib-self-check-functions.h new file mode 100644 index 0000000..3a21430 --- /dev/null +++ b/eel/eel-lib-self-check-functions.h @@ -0,0 +1,45 @@ +/* + eel-lib-self-check-functions.h: Wrapper and prototypes for all + self-check functions in libeel. + + Copyright (C) 2000 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this program; if not, see <http://www.gnu.org/licenses/>. + + Author: Darin Adler <darin@eazel.com> +*/ + +#pragma once + +#include "eel-self-checks.h" + +void eel_run_lib_self_checks (void); + +/* Putting the prototypes for these self-check functions in each + header file for the files they are defined in would make compiling + the self-check framework take way too long (since one file would + have to include everything). + + So we put the list of functions here instead. + + Instead of just putting prototypes here, we put this macro that + can be used to do operations on the whole list of functions. +*/ + +#define EEL_LIB_FOR_EACH_SELF_CHECK_FUNCTION(macro) \ + macro (eel_self_check_string) \ +/* Add new self-check functions to the list above this line. */ + +/* Generate prototypes for all the functions. */ +EEL_LIB_FOR_EACH_SELF_CHECK_FUNCTION (EEL_SELF_CHECK_FUNCTION_PROTOTYPE) diff --git a/eel/eel-self-checks.c b/eel/eel-self-checks.c new file mode 100644 index 0000000..0ed94f2 --- /dev/null +++ b/eel/eel-self-checks.c @@ -0,0 +1,203 @@ +/* + * eel-self-checks.c: The self-check framework. + * + * Copyright (C) 1999 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: Darin Adler <darin@eazel.com> + */ + +#include <config.h> + +#if !defined (EEL_OMIT_SELF_CHECK) + +#include "eel-self-checks.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static gboolean failed; + +static const char *current_expression; +static const char *current_file_name; +static int current_line_number; + +void +eel_exit_if_self_checks_failed (void) +{ + if (!failed) + { + return; + } + + printf ("\n"); + + exit (EXIT_FAILURE); +} + +void +eel_report_check_failure (char *result, + char *expected) +{ + if (!failed) + { + fprintf (stderr, "\n"); + } + + fprintf (stderr, "FAIL: check failed in %s, line %d\n", current_file_name, current_line_number); + fprintf (stderr, " evaluated: %s\n", current_expression); + fprintf (stderr, " expected: %s\n", expected == NULL ? "NULL" : expected); + fprintf (stderr, " got: %s\n", result == NULL ? "NULL" : result); + + failed = TRUE; + + g_free (result); + g_free (expected); +} + +static char * +eel_strdup_boolean (gboolean boolean) +{ + if (boolean == FALSE) + { + return g_strdup ("FALSE"); + } + if (boolean == TRUE) + { + return g_strdup ("TRUE"); + } + return g_strdup_printf ("gboolean(%d)", boolean); +} + +void +eel_before_check (const char *expression, + const char *file_name, + int line_number) +{ + current_expression = expression; + current_file_name = file_name; + current_line_number = line_number; +} + +void +eel_after_check (void) +{ + /* It would be good to check here if there was a memory leak. */ +} + +void +eel_check_boolean_result (gboolean result, + gboolean expected) +{ + if (result != expected) + { + eel_report_check_failure (eel_strdup_boolean (result), + eel_strdup_boolean (expected)); + } + eel_after_check (); +} + +void +eel_check_rectangle_result (EelIRect result, + int expected_x0, + int expected_y0, + int expected_x1, + int expected_y1) +{ + if (result.x0 != expected_x0 + || result.y0 != expected_y0 + || result.x1 != expected_x1 + || result.y1 != expected_y1) + { + eel_report_check_failure (g_strdup_printf ("x0=%d, y0=%d, x1=%d, y1=%d", + result.x0, + result.y0, + result.x1, + result.y1), + g_strdup_printf ("x0=%d, y0=%d, x1=%d, y1=%d", + expected_x0, + expected_y0, + expected_x1, + expected_y1)); + } + eel_after_check (); +} + +void +eel_check_integer_result (long result, + long expected) +{ + if (result != expected) + { + eel_report_check_failure (g_strdup_printf ("%ld", result), + g_strdup_printf ("%ld", expected)); + } + eel_after_check (); +} + +void +eel_check_double_result (double result, + double expected) +{ + if (result != expected) + { + eel_report_check_failure (g_strdup_printf ("%f", result), + g_strdup_printf ("%f", expected)); + } + eel_after_check (); +} + +void +eel_check_string_result (char *result, + const char *expected) +{ + gboolean match; + + /* Stricter than eel_strcmp. + * NULL does not match "" in this test. + */ + if (expected == NULL) + { + match = result == NULL; + } + else + { + match = result != NULL && strcmp (result, expected) == 0; + } + + if (!match) + { + eel_report_check_failure (result, g_strdup (expected)); + } + else + { + g_free (result); + } + eel_after_check (); +} + +void +eel_before_check_function (const char *name) +{ + fprintf (stderr, "running %s\n", name); +} + +void +eel_after_check_function (void) +{ +} + +#endif /* ! EEL_OMIT_SELF_CHECK */ diff --git a/eel/eel-self-checks.h b/eel/eel-self-checks.h new file mode 100644 index 0000000..ac48e1f --- /dev/null +++ b/eel/eel-self-checks.h @@ -0,0 +1,78 @@ +/* + eel-self-checks.h: The self-check framework. + + Copyright (C) 1999 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this program; if not, see <http://www.gnu.org/licenses/>. + + Author: Darin Adler <darin@eazel.com> +*/ + +#pragma once + +#include <glib.h> +#include <eel/eel-art-extensions.h> + +#define EEL_CHECK_RESULT(type, expression, expected_value) \ +G_STMT_START { \ + eel_before_check (#expression, __FILE__, __LINE__); \ + eel_check_##type##_result (expression, expected_value); \ +} G_STMT_END + +#define EEL_CHECK_BOOLEAN_RESULT(expression, expected_value) \ + EEL_CHECK_RESULT(boolean, expression, expected_value) +#define EEL_CHECK_INTEGER_RESULT(expression, expected_value) \ + EEL_CHECK_RESULT(integer, expression, expected_value) +#define EEL_CHECK_DOUBLE_RESULT(expression, expected_value) \ + EEL_CHECK_RESULT(double, expression, expected_value) +#define EEL_CHECK_STRING_RESULT(expression, expected_value) \ + EEL_CHECK_RESULT(string, expression, expected_value) +#define EEL_CHECK_RECTANGLE_RESULT(expression, expected_x0, expected_y0, expected_x1, expected_y1) \ +G_STMT_START { \ + eel_before_check (#expression, __FILE__, __LINE__); \ + eel_check_rectangle_result (expression, expected_x0, expected_y0, expected_x1, expected_y1); \ +} G_STMT_END + +void eel_exit_if_self_checks_failed (void); +void eel_before_check_function (const char *name); +void eel_after_check_function (void); +void eel_before_check (const char *expression, + const char *file_name, + int line_number); +void eel_after_check (void); + +/* Both 'result' and 'expected' get freed with g_free */ +void eel_report_check_failure (char *result, + char *expected); +void eel_check_boolean_result (gboolean result, + gboolean expected_value); +void eel_check_integer_result (long result, + long expected_value); +void eel_check_double_result (double result, + double expected_value); +void eel_check_rectangle_result (EelIRect result, + int expected_x0, + int expected_y0, + int expected_x1, + int expected_y1); +void eel_check_string_result (char *result, + const char *expected_value); + +#define EEL_SELF_CHECK_FUNCTION_PROTOTYPE(function) \ + void function (void); + +#define EEL_CALL_SELF_CHECK_FUNCTION(function) \ + eel_before_check_function (#function); \ + function (); \ + eel_after_check_function (); diff --git a/eel/eel-stock-dialogs.c b/eel/eel-stock-dialogs.c new file mode 100644 index 0000000..499af99 --- /dev/null +++ b/eel/eel-stock-dialogs.c @@ -0,0 +1,539 @@ +/* eel-stock-dialogs.c: Various standard dialogs for Eel. + * + * Copyright (C) 2000 Eazel, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see <http://www.gnu.org/licenses/>. + * + * Authors: Darin Adler <darin@eazel.com> + */ + +#include <config.h> +#include "eel-stock-dialogs.h" + +#include "eel-glib-extensions.h" +#include "eel-gtk-extensions.h" + +#include <glib/gi18n-lib.h> +#include <gtk/gtk.h> + +#define TIMED_WAIT_STANDARD_DURATION 2000 +#define TIMED_WAIT_MIN_TIME_UP 3000 + +#define TIMED_WAIT_MINIMUM_DIALOG_WIDTH 300 + +#define RESPONSE_DETAILS 1000 + +typedef struct +{ + EelCancelCallback cancel_callback; + gpointer callback_data; + + /* Parameters for creation of the window. */ + char *wait_message; + GtkWindow *parent_window; + + /* Timer to determine when we need to create the window. */ + guint timeout_handler_id; + + /* Window, once it's created. */ + GtkDialog *dialog; + + /* system time (microseconds) when dialog was created */ + gint64 dialog_creation_time; +} TimedWait; + +static GHashTable *timed_wait_hash_table; + +static void timed_wait_dialog_destroy_callback (GtkWidget *object, + gpointer callback_data); + +static guint +timed_wait_hash (gconstpointer value) +{ + const TimedWait *wait; + + wait = value; + + return GPOINTER_TO_UINT (wait->cancel_callback) + ^ GPOINTER_TO_UINT (wait->callback_data); +} + +static gboolean +timed_wait_hash_equal (gconstpointer value1, + gconstpointer value2) +{ + const TimedWait *wait1, *wait2; + + wait1 = value1; + wait2 = value2; + + return wait1->cancel_callback == wait2->cancel_callback + && wait1->callback_data == wait2->callback_data; +} + +static void +timed_wait_delayed_close_destroy_dialog_callback (GtkWidget *object, + gpointer callback_data) +{ + g_source_remove (GPOINTER_TO_UINT (callback_data)); +} + +static gboolean +timed_wait_delayed_close_timeout_callback (gpointer callback_data) +{ + guint handler_id; + + handler_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (callback_data), + "eel-stock-dialogs/delayed_close_handler_timeout_id")); + + g_signal_handlers_disconnect_by_func (G_OBJECT (callback_data), + G_CALLBACK (timed_wait_delayed_close_destroy_dialog_callback), + GUINT_TO_POINTER (handler_id)); + + gtk_widget_destroy (GTK_WIDGET (callback_data)); + + return FALSE; +} + +static void +timed_wait_free (TimedWait *wait) +{ + guint delayed_close_handler_id; + guint64 time_up; + + g_assert (g_hash_table_lookup (timed_wait_hash_table, wait) != NULL); + + g_hash_table_remove (timed_wait_hash_table, wait); + + g_free (wait->wait_message); + if (wait->parent_window != NULL) + { + g_object_unref (wait->parent_window); + } + if (wait->timeout_handler_id != 0) + { + g_source_remove (wait->timeout_handler_id); + } + if (wait->dialog != NULL) + { + /* Make sure to detach from the "destroy" signal, or we'll + * double-free. + */ + g_signal_handlers_disconnect_by_func (G_OBJECT (wait->dialog), + G_CALLBACK (timed_wait_dialog_destroy_callback), + wait); + + /* compute time up in milliseconds */ + time_up = (g_get_monotonic_time () - wait->dialog_creation_time) / 1000; + + if (time_up < TIMED_WAIT_MIN_TIME_UP) + { + delayed_close_handler_id = g_timeout_add (TIMED_WAIT_MIN_TIME_UP - time_up, + timed_wait_delayed_close_timeout_callback, + wait->dialog); + g_object_set_data (G_OBJECT (wait->dialog), + "eel-stock-dialogs/delayed_close_handler_timeout_id", + GUINT_TO_POINTER (delayed_close_handler_id)); + g_signal_connect (wait->dialog, "destroy", + G_CALLBACK (timed_wait_delayed_close_destroy_dialog_callback), + GUINT_TO_POINTER (delayed_close_handler_id)); + } + else + { + gtk_widget_destroy (GTK_WIDGET (wait->dialog)); + } + } + + /* And the wait object itself. */ + g_free (wait); +} + +static void +timed_wait_dialog_destroy_callback (GtkWidget *object, + gpointer callback_data) +{ + TimedWait *wait; + + wait = callback_data; + + g_assert (GTK_DIALOG (object) == wait->dialog); + + wait->dialog = NULL; + + /* When there's no cancel_callback, the originator will/must + * call eel_timed_wait_stop which will call timed_wait_free. + */ + + if (wait->cancel_callback != NULL) + { + (*wait->cancel_callback)(wait->callback_data); + timed_wait_free (wait); + } +} + +static void +trash_dialog_response_callback (GtkDialog *dialog, + int response_id, + TimedWait *wait) +{ + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +static gboolean +timed_wait_callback (gpointer callback_data) +{ + TimedWait *wait; + GtkDialog *dialog; + const char *button; + + wait = callback_data; + + /* Put up the timed wait window. */ + button = wait->cancel_callback != NULL ? _("_Cancel") : ("_OK"); + dialog = GTK_DIALOG (gtk_message_dialog_new (wait->parent_window, + 0, + GTK_MESSAGE_INFO, + GTK_BUTTONS_NONE, + NULL)); + + g_object_set (dialog, + "text", wait->wait_message, + "secondary-text", _("You can stop this operation by clicking cancel."), + NULL); + + gtk_dialog_add_button (GTK_DIALOG (dialog), button, GTK_RESPONSE_OK); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + /* The contents are often very small, causing tiny little + * dialogs with their titles clipped if you just let gtk + * sizing do its thing. This enforces a minimum width to + * make it more likely that the title won't be clipped. + */ + gtk_window_set_default_size (GTK_WINDOW (dialog), + TIMED_WAIT_MINIMUM_DIALOG_WIDTH, + -1); + wait->dialog_creation_time = g_get_monotonic_time (); + gtk_widget_show (GTK_WIDGET (dialog)); + + /* FIXME bugzilla.eazel.com 2441: + * Could parent here, but it's complicated because we + * don't want this window to go away just because the parent + * would go away first. + */ + + /* Make the dialog cancel the timed wait when it goes away. + * Connect to "destroy" instead of "response" since we want + * to be called no matter how the dialog goes away. + */ + g_signal_connect (dialog, "destroy", + G_CALLBACK (timed_wait_dialog_destroy_callback), + wait); + g_signal_connect (dialog, "response", + G_CALLBACK (trash_dialog_response_callback), + wait); + + wait->timeout_handler_id = 0; + wait->dialog = dialog; + + return FALSE; +} + +void +eel_timed_wait_start_with_duration (int duration, + EelCancelCallback cancel_callback, + gpointer callback_data, + const char *wait_message, + GtkWindow *parent_window) +{ + TimedWait *wait; + + g_return_if_fail (callback_data != NULL); + g_return_if_fail (wait_message != NULL); + g_return_if_fail (parent_window == NULL || GTK_IS_WINDOW (parent_window)); + + /* Create the timed wait record. */ + wait = g_new0 (TimedWait, 1); + wait->wait_message = g_strdup (wait_message); + wait->cancel_callback = cancel_callback; + wait->callback_data = callback_data; + wait->parent_window = parent_window; + + if (parent_window != NULL) + { + g_object_ref (parent_window); + } + + /* Start the timer. */ + wait->timeout_handler_id = g_timeout_add (duration, timed_wait_callback, wait); + + /* Put in the hash table so we can find it later. */ + if (timed_wait_hash_table == NULL) + { + timed_wait_hash_table = g_hash_table_new (timed_wait_hash, timed_wait_hash_equal); + } + g_assert (g_hash_table_lookup (timed_wait_hash_table, wait) == NULL); + g_hash_table_insert (timed_wait_hash_table, wait, wait); + g_assert (g_hash_table_lookup (timed_wait_hash_table, wait) == wait); +} + +void +eel_timed_wait_start (EelCancelCallback cancel_callback, + gpointer callback_data, + const char *wait_message, + GtkWindow *parent_window) +{ + eel_timed_wait_start_with_duration + (TIMED_WAIT_STANDARD_DURATION, + cancel_callback, callback_data, + wait_message, parent_window); +} + +void +eel_timed_wait_stop (EelCancelCallback cancel_callback, + gpointer callback_data) +{ + TimedWait key; + TimedWait *wait; + + g_return_if_fail (callback_data != NULL); + + key.cancel_callback = cancel_callback; + key.callback_data = callback_data; + wait = g_hash_table_lookup (timed_wait_hash_table, &key); + + g_return_if_fail (wait != NULL); + + timed_wait_free (wait); +} + +int +eel_run_simple_dialog (GtkWidget *parent, + gboolean ignore_close_box, + GtkMessageType message_type, + const char *primary_text, + const char *secondary_text, + ...) +{ + va_list button_title_args; + const char *button_title; + GtkWidget *dialog; + GtkWidget *top_widget, *chosen_parent; + int result; + int response_id; + + /* Parent it if asked to. */ + chosen_parent = NULL; + if (parent != NULL) + { + top_widget = gtk_widget_get_toplevel (parent); + if (GTK_IS_WINDOW (top_widget)) + { + chosen_parent = top_widget; + } + } + + /* Create the dialog. */ + dialog = gtk_message_dialog_new (GTK_WINDOW (chosen_parent), + 0, + message_type, + GTK_BUTTONS_NONE, + NULL); + + g_object_set (dialog, + "text", primary_text, + "secondary-text", secondary_text, + NULL); + + va_start (button_title_args, secondary_text); + response_id = 0; + while (1) + { + button_title = va_arg (button_title_args, const char *); + if (button_title == NULL) + { + break; + } + gtk_dialog_add_button (GTK_DIALOG (dialog), button_title, response_id); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), response_id); + response_id++; + } + va_end (button_title_args); + + /* Run it. */ + gtk_widget_show (dialog); + result = gtk_dialog_run (GTK_DIALOG (dialog)); + while ((result == GTK_RESPONSE_NONE || result == GTK_RESPONSE_DELETE_EVENT) && ignore_close_box) + { + gtk_widget_show (GTK_WIDGET (dialog)); + result = gtk_dialog_run (GTK_DIALOG (dialog)); + } + gtk_widget_destroy (dialog); + + return result; +} + +static GtkDialog * +create_message_dialog (const char *primary_text, + const char *secondary_text, + GtkMessageType type, + GtkButtonsType buttons_type, + GtkWindow *parent) +{ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (parent, + 0, + type, + buttons_type, + NULL); + if (parent) + { + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + } + + g_object_set (dialog, + "text", primary_text, + "secondary-text", secondary_text, + NULL); + + return GTK_DIALOG (dialog); +} + +static GtkDialog * +show_message_dialog (const char *primary_text, + const char *secondary_text, + GtkMessageType type, + GtkButtonsType buttons_type, + GtkWindow *parent) +{ + GtkDialog *dialog; + + dialog = create_message_dialog (primary_text, secondary_text, type, + buttons_type, parent); + gtk_widget_show (GTK_WIDGET (dialog)); + + g_signal_connect (dialog, "response", + G_CALLBACK (gtk_widget_destroy), NULL); + + return dialog; +} + +static GtkDialog * +show_ok_dialog (const char *primary_text, + const char *secondary_text, + GtkMessageType type, + GtkWindow *parent) +{ + GtkDialog *dialog; + + dialog = show_message_dialog (primary_text, secondary_text, type, + GTK_BUTTONS_OK, parent); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + return dialog; +} + +GtkDialog * +eel_show_info_dialog (const char *primary_text, + const char *secondary_text, + GtkWindow *parent) +{ + return show_ok_dialog (primary_text, + secondary_text, + GTK_MESSAGE_INFO, parent); +} + +GtkDialog * +eel_show_warning_dialog (const char *primary_text, + const char *secondary_text, + GtkWindow *parent) +{ + return show_ok_dialog (primary_text, + secondary_text, + GTK_MESSAGE_WARNING, parent); +} + + +GtkDialog * +eel_show_error_dialog (const char *primary_text, + const char *secondary_text, + GtkWindow *parent) +{ + return show_ok_dialog (primary_text, + secondary_text, + GTK_MESSAGE_ERROR, parent); +} + +/** + * eel_show_yes_no_dialog: + * + * Create and show a dialog asking a question with two choices. + * The caller needs to set up any necessary callbacks + * for the buttons. Use eel_create_question_dialog instead + * if any visual changes need to be made, to avoid flashiness. + * @question: The text of the question. + * @yes_label: The label of the "yes" button. + * @no_label: The label of the "no" button. + * @parent: The parent window for this dialog. + */ +GtkDialog * +eel_show_yes_no_dialog (const char *primary_text, + const char *secondary_text, + const char *yes_label, + const char *no_label, + GtkWindow *parent) +{ + GtkDialog *dialog = NULL; + dialog = eel_create_question_dialog (primary_text, + secondary_text, + no_label, GTK_RESPONSE_CANCEL, + yes_label, GTK_RESPONSE_YES, + GTK_WINDOW (parent)); + gtk_widget_show (GTK_WIDGET (dialog)); + return dialog; +} + +/** + * eel_create_question_dialog: + * + * Create a dialog asking a question with at least two choices. + * The caller needs to set up any necessary callbacks + * for the buttons. The dialog is not yet shown, so that the + * caller can add additional buttons or make other visual changes + * without causing flashiness. + * @question: The text of the question. + * @answer_0: The label of the leftmost button (index 0) + * @answer_1: The label of the 2nd-to-leftmost button (index 1) + * @parent: The parent window for this dialog. + */ +GtkDialog * +eel_create_question_dialog (const char *primary_text, + const char *secondary_text, + const char *answer_1, + int response_1, + const char *answer_2, + int response_2, + GtkWindow *parent) +{ + GtkDialog *dialog; + + dialog = create_message_dialog (primary_text, + secondary_text, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_NONE, + parent); + gtk_dialog_add_buttons (dialog, answer_1, response_1, answer_2, response_2, NULL); + return dialog; +} diff --git a/eel/eel-stock-dialogs.h b/eel/eel-stock-dialogs.h new file mode 100644 index 0000000..7926861 --- /dev/null +++ b/eel/eel-stock-dialogs.h @@ -0,0 +1,75 @@ + +/* eel-stock-dialogs.h: Various standard dialogs for Eel. + + Copyright (C) 2000 Eazel, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + see <http://www.gnu.org/licenses/>. + + Authors: Darin Adler <darin@eazel.com> +*/ + +#pragma once + +#include <gtk/gtk.h> + +typedef void (* EelCancelCallback) (gpointer callback_data); + +/* Dialog for cancelling something that normally is fast enough not to need a dialog. */ +void eel_timed_wait_start (EelCancelCallback cancel_callback, + gpointer callback_data, + const char *wait_message, + GtkWindow *parent_window); +void eel_timed_wait_start_with_duration (int duration, + EelCancelCallback cancel_callback, + gpointer callback_data, + const char *wait_message, + GtkWindow *parent_window); +void eel_timed_wait_stop (EelCancelCallback cancel_callback, + gpointer callback_data); + +/* Basic dialog with buttons. */ +int eel_run_simple_dialog (GtkWidget *parent, + gboolean ignore_close_box, + GtkMessageType message_type, + const char *primary_text, + const char *secondary_text, + ...); + +/* Variations on gnome stock dialogs; these do line wrapping, we don't + * bother with non-parented versions, we allow setting the title, + * primary, and secondary messages, and we return GtkDialog pointers + * instead of GtkWidget pointers. + */ +GtkDialog *eel_show_info_dialog (const char *primary_text, + const char *secondary_text, + GtkWindow *parent); +GtkDialog *eel_show_warning_dialog (const char *primary_text, + const char *secondary_text, + GtkWindow *parent); +GtkDialog *eel_show_error_dialog (const char *primary_text, + const char *secondary_text, + GtkWindow *parent); +GtkDialog *eel_show_yes_no_dialog (const char *primary_text, + const char *secondary_text, + const char *yes_label, + const char *no_label, + GtkWindow *parent); +GtkDialog *eel_create_question_dialog (const char *primary_text, + const char *secondary_text, + const char *answer_one, + int response_one, + const char *answer_two, + int response_two, + GtkWindow *parent);
\ No newline at end of file diff --git a/eel/eel-string.c b/eel/eel-string.c new file mode 100644 index 0000000..5d70089 --- /dev/null +++ b/eel/eel-string.c @@ -0,0 +1,476 @@ +/* + * eel-string.c: String routines to augment <string.h>. + * + * Copyright (C) 2000 Eazel, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see <http://www.gnu.org/licenses/>. + * + * Authors: Darin Adler <darin@eazel.com> + */ + +#include <config.h> +#include "eel-string.h" + +#include <errno.h> +#include <locale.h> +#include <stdlib.h> +#include <string.h> +#include <eel-glib-extensions.h> + +#if !defined (EEL_OMIT_SELF_CHECK) +#include "eel-lib-self-check-functions.h" +#endif + +/** + * eel_str_double_underscores: + * @string: input string + * + * This is used if you want to preserve underscore characters + * when creating a label with gtk_label_new_with_mnemonic(). + * + * Returns: a newly allocated copy of @string, + * with a doubled number of underscores. + * If @string doesn't contain underscores, returns a copy of it. + * If @string is %NULL, returns %NULL. + */ +char * +eel_str_double_underscores (const char *string) +{ + int underscores; + const char *p; + char *q; + char *escaped; + + if (string == NULL) + { + return NULL; + } + + underscores = 0; + for (p = string; *p != '\0'; p++) + { + underscores += (*p == '_'); + } + + if (underscores == 0) + { + return g_strdup (string); + } + + escaped = g_new (char, strlen (string) + underscores + 1); + for (p = string, q = escaped; *p != '\0'; p++, q++) + { + /* Add an extra underscore. */ + if (*p == '_') + { + *q++ = '_'; + } + *q = *p; + } + *q = '\0'; + + return escaped; +} + +/** + * eel_str_capitalize: + * @string: input string + * + * Returns: a newly allocated copy of @string, + * with the first letter capitalized. + * If @string is %NULL, returns %NULL. + */ +char * +eel_str_capitalize (const char *string) +{ + char *capitalized; + + if (string == NULL) + { + return NULL; + } + + capitalized = g_strdup (string); + + capitalized[0] = g_ascii_toupper (capitalized[0]); + + return capitalized; +} + +/** + * eel_str_middle_truncate: + * @string: (not nullable): input string + * truncate_length: length of the truncated string + * + * Returns: (transfer full): a newly-allocated copy of @string with its middle + * truncated and replaced with ellipsis to fit into @truncate_length characters. + * If length of @string is already small enough, returns a copy of @string. + */ +gchar * +eel_str_middle_truncate (const gchar *string, + guint truncate_length) +{ + const gchar ellipsis[] = "…"; + glong ellipsis_length; + glong length; + glong num_left_chars; + glong num_right_chars; + g_autofree gchar *left_substring = NULL; + g_autofree gchar *right_substring = NULL; + + g_return_val_if_fail (string != NULL, NULL); + g_return_val_if_fail (truncate_length > 0, NULL); + + ellipsis_length = g_utf8_strlen (ellipsis, -1); + + /* Our ellipsis string + one character on each side. */ + if (truncate_length < ellipsis_length + 2) + { + return g_strdup (string); + } + + length = g_utf8_strlen (string, -1); + + if (length <= truncate_length) + { + return g_strdup (string); + } + + num_left_chars = (truncate_length - ellipsis_length) / 2; + num_right_chars = truncate_length - num_left_chars - ellipsis_length; + + g_assert (num_left_chars > 0); + g_assert (num_right_chars > 0); + + left_substring = g_utf8_substring (string, 0, num_left_chars); + right_substring = g_utf8_substring (string, length - num_right_chars, length); + + return g_strconcat (left_substring, ellipsis, right_substring, NULL); +} + +/** + * eel_str_strip_substring_and_after: + * @string: input string + * @substring: (not nullable): substring to use in search + * + * Returns: (transfer full): a copy of @string with the first occurence of + * @substring removed, along with any trailing characters. + * If @string is %NULL, returns %NULL. + */ +char * +eel_str_strip_substring_and_after (const char *string, + const char *substring) +{ + const char *substring_position; + + g_return_val_if_fail (substring != NULL, g_strdup (string)); + g_return_val_if_fail (substring[0] != '\0', g_strdup (string)); + + if (string == NULL) + { + return NULL; + } + + substring_position = strstr (string, substring); + if (substring_position == NULL) + { + return g_strdup (string); + } + + return g_strndup (string, + substring_position - string); +} + +/** + * eel_str_replace_substring: + * @string: input string + * @substring: (not nullable): string to be replaced + * @replacement: string used as replacement + * + * Returns: (transfer full): a copy of @string with all occurences of @substring + * replaced with @replacement. + */ +char * +eel_str_replace_substring (const char *string, + const char *substring, + const char *replacement) +{ + int substring_length, replacement_length, result_length, remaining_length; + const char *p, *substring_position; + char *result, *result_position; + + g_return_val_if_fail (substring != NULL, g_strdup (string)); + g_return_val_if_fail (substring[0] != '\0', g_strdup (string)); + + if (string == NULL) + { + return NULL; + } + + substring_length = substring ? strlen (substring) : 0; + replacement_length = replacement ? strlen (replacement) : 0; + + result_length = strlen (string); + for (p = string;; p = substring_position + substring_length) + { + substring_position = strstr (p, substring); + if (substring_position == NULL) + { + break; + } + result_length += replacement_length - substring_length; + } + + result = g_malloc (result_length + 1); + + result_position = result; + for (p = string;; p = substring_position + substring_length) + { + substring_position = strstr (p, substring); + if (substring_position == NULL) + { + remaining_length = strlen (p); + memcpy (result_position, p, remaining_length); + result_position += remaining_length; + break; + } + memcpy (result_position, p, substring_position - p); + result_position += substring_position - p; + memcpy (result_position, replacement, replacement_length); + result_position += replacement_length; + } + g_assert (result_position - result == result_length); + result_position[0] = '\0'; + + return result; +} + +/** + * get_common_prefix_length: + * @str_a: first string + * @str_b: second string + * @min_required_len: the minimum number of characters required in the prefix + * + * Returns: the size of the common prefix of two strings, in characters. + * If there's no common prefix, or the common prefix is smaller than + * min_required_len, this will return -1 + */ +static int +get_common_prefix_length (char *str_a, + char *str_b, + int min_required_len) +{ + int a_len; + int b_len; + int intersection_len; + int matching_chars; + char *a; + char *b; + + a_len = g_utf8_strlen (str_a, -1); + b_len = g_utf8_strlen (str_b, -1); + + intersection_len = MIN (a_len, b_len); + if (intersection_len < min_required_len) + { + return -1; + } + + matching_chars = 0; + a = str_a; + b = str_b; + while (matching_chars < intersection_len) + { + if (g_utf8_get_char (a) != g_utf8_get_char (b)) + { + break; + } + + ++matching_chars; + + a = g_utf8_next_char (a); + b = g_utf8_next_char (b); + } + + if (matching_chars < min_required_len) + { + return -1; + } + + return matching_chars; +} + +/** + * eel_str_get_common_prefix: + * @strs: a list of strings + * @min_required_len: the minimum number of characters required in prefix + * + * Returns: (transfer full): the common prefix for strings in @strs. + * If no such prefix exists or if the common prefix is smaller than + * @min_required_len, %NULL is returned. + */ +char * +eel_str_get_common_prefix (GList *strs, + int min_required_len) +{ + GList *l; + char *common_part; + char *name; + char *truncated; + int matching_chars; + + if (strs == NULL) + { + return NULL; + } + + common_part = NULL; + for (l = strs; l != NULL; l = l->next) + { + name = l->data; + if (name == NULL) + { + g_free (common_part); + return NULL; + } + + if (l->prev == NULL) + { + common_part = g_strdup (name); + continue; + } + + matching_chars = get_common_prefix_length (common_part, name, min_required_len); + + if (matching_chars == -1) + { + g_free (common_part); + return NULL; + } + + truncated = g_utf8_substring (common_part, 0, matching_chars); + g_free (common_part); + common_part = truncated; + } + + matching_chars = g_utf8_strlen (common_part, -1); + if (matching_chars < min_required_len) + { + g_free (common_part); + return NULL; + } + + return common_part; +} + +/**************** Custom printf ***********/ + +typedef struct +{ + const char *start; + const char *end; + GString *format; + int arg_pos; + int width_pos; + int width_format_index; + int precision_pos; + int precision_format_index; +} ConversionInfo; + +enum +{ + ARG_TYPE_INVALID, + ARG_TYPE_INT, + ARG_TYPE_LONG, + ARG_TYPE_LONG_LONG, + ARG_TYPE_SIZE, + ARG_TYPE_LONG_DOUBLE, + ARG_TYPE_DOUBLE, + ARG_TYPE_POINTER +}; + +#if !defined (EEL_OMIT_SELF_CHECK) + +void +eel_self_check_string (void) +{ + EEL_CHECK_STRING_RESULT (eel_str_double_underscores (NULL), NULL); + EEL_CHECK_STRING_RESULT (eel_str_double_underscores (""), ""); + EEL_CHECK_STRING_RESULT (eel_str_double_underscores ("_"), "__"); + EEL_CHECK_STRING_RESULT (eel_str_double_underscores ("foo"), "foo"); + EEL_CHECK_STRING_RESULT (eel_str_double_underscores ("foo_bar"), "foo__bar"); + EEL_CHECK_STRING_RESULT (eel_str_double_underscores ("foo_bar_2"), "foo__bar__2"); + EEL_CHECK_STRING_RESULT (eel_str_double_underscores ("_foo"), "__foo"); + EEL_CHECK_STRING_RESULT (eel_str_double_underscores ("foo_"), "foo__"); + + EEL_CHECK_STRING_RESULT (eel_str_capitalize (NULL), NULL); + EEL_CHECK_STRING_RESULT (eel_str_capitalize (""), ""); + EEL_CHECK_STRING_RESULT (eel_str_capitalize ("foo"), "Foo"); + EEL_CHECK_STRING_RESULT (eel_str_capitalize ("Foo"), "Foo"); + + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("foo", 0), NULL); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("foo", 1), "foo"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("foo", 3), "foo"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("foo", 4), "foo"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("foo", 5), "foo"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("foo", 6), "foo"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("foo", 7), "foo"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("a_much_longer_foo", 0), NULL); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("a_much_longer_foo", 1), "a_much_longer_foo"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("a_much_longer_foo", 2), "a_much_longer_foo"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("a_much_longer_foo", 3), "a…o"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("a_much_longer_foo", 4), "a…oo"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("a_much_longer_foo", 5), "a_…oo"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("a_much_longer_foo", 6), "a_…foo"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("a_much_longer_foo", 7), "a_m…foo"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("a_much_longer_foo", 8), "a_m…_foo"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("a_much_longer_foo", 9), "a_mu…_foo"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("something_even", 8), "som…even"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("something_odd", 8), "som…_odd"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("something_even", 9), "some…even"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("something_odd", 9), "some…_odd"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("something_even", 10), "some…_even"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("something_odd", 10), "some…g_odd"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("something_even", 11), "somet…_even"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("something_odd", 11), "somet…g_odd"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("something_even", 12), "somet…g_even"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("something_odd", 12), "somet…ng_odd"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("something_even", 13), "someth…g_even"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("something_odd", 13), "something_odd"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("something_even", 14), "something_even"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("something_odd", 13), "something_odd"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("ääääääääää", 5), "ää…ää"); + EEL_CHECK_STRING_RESULT (eel_str_middle_truncate ("あぃいぅうぇえぉ", 7), "あぃい…ぇえぉ"); + + EEL_CHECK_STRING_RESULT (eel_str_strip_substring_and_after (NULL, "bar"), NULL); + EEL_CHECK_STRING_RESULT (eel_str_strip_substring_and_after ("", "bar"), ""); + EEL_CHECK_STRING_RESULT (eel_str_strip_substring_and_after ("foo", "bar"), "foo"); + EEL_CHECK_STRING_RESULT (eel_str_strip_substring_and_after ("foo bar", "bar"), "foo "); + EEL_CHECK_STRING_RESULT (eel_str_strip_substring_and_after ("foo bar xxx", "bar"), "foo "); + EEL_CHECK_STRING_RESULT (eel_str_strip_substring_and_after ("bar", "bar"), ""); + + EEL_CHECK_STRING_RESULT (eel_str_replace_substring (NULL, "foo", NULL), NULL); + EEL_CHECK_STRING_RESULT (eel_str_replace_substring (NULL, "foo", "bar"), NULL); + EEL_CHECK_STRING_RESULT (eel_str_replace_substring ("bar", "foo", NULL), "bar"); + EEL_CHECK_STRING_RESULT (eel_str_replace_substring ("", "foo", ""), ""); + EEL_CHECK_STRING_RESULT (eel_str_replace_substring ("", "foo", "bar"), ""); + EEL_CHECK_STRING_RESULT (eel_str_replace_substring ("bar", "foo", ""), "bar"); + EEL_CHECK_STRING_RESULT (eel_str_replace_substring ("xxx", "x", "foo"), "foofoofoo"); + EEL_CHECK_STRING_RESULT (eel_str_replace_substring ("fff", "f", "foo"), "foofoofoo"); + EEL_CHECK_STRING_RESULT (eel_str_replace_substring ("foofoofoo", "foo", "f"), "fff"); + EEL_CHECK_STRING_RESULT (eel_str_replace_substring ("foofoofoo", "f", ""), "oooooo"); +} + +#endif /* !EEL_OMIT_SELF_CHECK */ diff --git a/eel/eel-string.h b/eel/eel-string.h new file mode 100644 index 0000000..a833750 --- /dev/null +++ b/eel/eel-string.h @@ -0,0 +1,79 @@ +/* + eel-string.h: String routines to augment <string.h>. + + Copyright (C) 2000 Eazel, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + see <http://www.gnu.org/licenses/>. + + Authors: Darin Adler <darin@eazel.com> +*/ + +#pragma once + +#include <glib.h> +#include <string.h> +#include <stdarg.h> + +/* We use the "str" abbrevation to mean char * string, since + * "string" usually means g_string instead. We use the "istr" + * abbreviation to mean a case-insensitive char *. + */ + + +/* NULL is allowed for all the str parameters to these functions. */ + +/* Escape function for '_' character. */ +char * eel_str_double_underscores (const char *str); + +/* Capitalize a string */ +char * eel_str_capitalize (const char *str); + +/** + * eel_str_middle_truncate: + * @string: the string to truncate + * @truncate_length: the length limit at which to truncate + * + * If @string is longer than @truncate_length, replaces the middle with an + * ellipsis so the resulting string is exactly @truncate_length characters + * in length. Otherwise, returns a copy of @string. + * + * Do not use to ellipsize whole labels, only substrings that appear in them, + * e.g. file names. + * + * Returns: @string, truncated at the middle to @truncate_length or a copy + * if it was not longer than @truncate_length. + */ +gchar *eel_str_middle_truncate (const gchar *string, + guint truncate_length); + + +/* Remove all characters after the passed-in substring. */ +char * eel_str_strip_substring_and_after (const char *str, + const char *substring); + +/* Replace all occurrences of substring with replacement. */ +char * eel_str_replace_substring (const char *str, + const char *substring, + const char *replacement); + +/** + * eel_str_get_common_prefix: + * @str: set of strings + * @min_required_len: the minimum number of characters required in the prefix + * + * Returns: the common prefix for a set of strings, or NULL if there isn't a + * common prefix of length min_required_len + */ +char * eel_str_get_common_prefix (GList *strs, int min_required_len); diff --git a/eel/eel-vfs-extensions.c b/eel/eel-vfs-extensions.c new file mode 100644 index 0000000..afdf062 --- /dev/null +++ b/eel/eel-vfs-extensions.c @@ -0,0 +1,195 @@ +/* eel-vfs-extensions.c - gnome-vfs extensions. Its likely some of these will + * be part of gnome-vfs in the future. + * + * Copyright (C) 1999, 2000 Eazel, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see <http://www.gnu.org/licenses/>. + * + * Authors: Darin Adler <darin@eazel.com> + * Pavel Cisler <pavel@eazel.com> + * Mike Fleming <mfleming@eazel.com> + * John Sullivan <sullivan@eazel.com> + */ + +#include <config.h> +#include "eel-vfs-extensions.h" +#include "eel-glib-extensions.h" +#include "eel-lib-self-check-functions.h" + +#include <glib.h> +#include <glib/gi18n-lib.h> +#include <gio/gio.h> + +#include "eel-string.h" + +#include <string.h> +#include <stdlib.h> + +gboolean +eel_uri_is_starred (const gchar *uri) +{ + return g_str_has_prefix (uri, "starred:"); +} + +/* It also matches trashed folders inside Trash, + * use `eel_uri_is_trash_root` if that's not desirable. */ +gboolean +eel_uri_is_trash (const char *uri) +{ + return g_str_has_prefix (uri, "trash:"); +} + +gboolean +eel_uri_is_trash_root (const char *uri) +{ + return g_strcmp0 (uri, "trash:///") == 0; +} + +gboolean +eel_uri_is_recent (const char *uri) +{ + return g_str_has_prefix (uri, "recent:"); +} + +gboolean +eel_uri_is_search (const char *uri) +{ + return g_str_has_prefix (uri, EEL_SEARCH_URI); +} + +gboolean +eel_uri_is_other_locations (const char *uri) +{ + return g_str_has_prefix (uri, "other-locations:"); +} + +gboolean +eel_uri_is_in_xdg_dirs (const gchar *uri) +{ + GUserDirectory dir; + g_autoptr (GFile) location = NULL; + gboolean has_prefix = FALSE; + + location = g_file_new_for_uri (uri); + for (dir = 0; dir < G_USER_N_DIRECTORIES; dir++) + { + g_autoptr (GFile) xdg_dir_location = NULL; + const gchar *path; + + path = g_get_user_special_dir (dir); + if (path == NULL) + { + continue; + } + + xdg_dir_location = g_file_new_for_path (path); + has_prefix = g_file_has_prefix (location, xdg_dir_location) || + g_file_equal (location, xdg_dir_location); + + if (has_prefix) + { + break; + } + } + + return has_prefix; +} + +char * +eel_filename_get_extension_offset (const char *filename) +{ + char *end, *end2; + const char *start; + + if (filename == NULL || filename[0] == '\0') + { + return NULL; + } + + /* basename must have at least one char */ + start = filename + 1; + + end = strrchr (start, '.'); + if (end == NULL || end[1] == '\0') + { + return NULL; + } + + if (end != start) + { + if (strcmp (end, ".gz") == 0 || + strcmp (end, ".bz2") == 0 || + strcmp (end, ".sit") == 0 || + strcmp (end, ".Z") == 0 || + strcmp (end, ".bz") == 0 || + strcmp (end, ".xz") == 0) + { + end2 = end - 1; + while (end2 > start && + *end2 != '.') + { + end2--; + } + if (end2 != start) + { + end = end2; + } + } + } + + return end; +} + +char * +eel_filename_strip_extension (const char *filename_with_extension) +{ + char *filename, *end; + + if (filename_with_extension == NULL) + { + return NULL; + } + + filename = g_strdup (filename_with_extension); + end = eel_filename_get_extension_offset (filename); + + if (end && end != filename) + { + *end = '\0'; + } + + return filename; +} + +void +eel_filename_get_rename_region (const char *filename, + int *start_offset, + int *end_offset) +{ + char *filename_without_extension; + + g_return_if_fail (start_offset != NULL); + g_return_if_fail (end_offset != NULL); + + *start_offset = 0; + *end_offset = 0; + + g_return_if_fail (filename != NULL); + + filename_without_extension = eel_filename_strip_extension (filename); + *end_offset = g_utf8_strlen (filename_without_extension, -1); + + g_free (filename_without_extension); +} diff --git a/eel/eel-vfs-extensions.h b/eel/eel-vfs-extensions.h new file mode 100644 index 0000000..b90fc69 --- /dev/null +++ b/eel/eel-vfs-extensions.h @@ -0,0 +1,50 @@ + +/* eel-vfs-extensions.h - gnome-vfs extensions. Its likely some of these will + be part of gnome-vfs in the future. + + Copyright (C) 1999, 2000 Eazel, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + see <http://www.gnu.org/licenses/>. + + Authors: Darin Adler <darin@eazel.com> + Pavel Cisler <pavel@eazel.com> + Mike Fleming <mfleming@eazel.com> + John Sullivan <sullivan@eazel.com> +*/ + +#pragma once + +#include <glib.h> + +G_BEGIN_DECLS + +#define EEL_TRASH_URI "trash:" +#define EEL_SEARCH_URI "x-nautilus-search:" + +gboolean eel_uri_is_starred (const char *uri); +gboolean eel_uri_is_trash (const char *uri); +gboolean eel_uri_is_trash_root (const char *uri); +gboolean eel_uri_is_search (const char *uri); +gboolean eel_uri_is_other_locations (const char *uri); +gboolean eel_uri_is_recent (const char *uri); +gboolean eel_uri_is_in_xdg_dirs (const char *uri); + +char * eel_filename_strip_extension (const char *filename); +void eel_filename_get_rename_region (const char *filename, + int *start_offset, + int *end_offset); +char * eel_filename_get_extension_offset (const char *filename); + +G_END_DECLS diff --git a/eel/eel.h b/eel/eel.h new file mode 100644 index 0000000..cf3cdda --- /dev/null +++ b/eel/eel.h @@ -0,0 +1,32 @@ + +/* eel.h + + Copyright (C) 2001 Eazel, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + see <http://www.gnu.org/licenses/>. + + Authors: Maciej Stachowiak <mjs@eazel.com> +*/ + +#pragma once + +#include <eel/eel-art-extensions.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-graphic-effects.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-self-checks.h> +#include <eel/eel-stock-dialogs.h> +#include <eel/eel-string.h> +#include <eel/eel-vfs-extensions.h>
\ No newline at end of file diff --git a/eel/meson.build b/eel/meson.build new file mode 100644 index 0000000..e5fa746 --- /dev/null +++ b/eel/meson.build @@ -0,0 +1,57 @@ +libeel_2_sources = [ + 'eel-art-extensions.h', + 'eel-art-extensions.c', + 'eel-canvas.h', + 'eel-canvas.c', + 'eel-debug.h', + 'eel-debug.c', + 'eel-glib-extensions.h', + 'eel-graphic-effects.h', + 'eel-graphic-effects.c', + 'eel-gtk-extensions.h', + 'eel-self-checks.h', + 'eel-self-checks.c', + 'eel-stock-dialogs.h', + 'eel-stock-dialogs.c', + 'eel-string.h', + 'eel-string.c', + 'eel-vfs-extensions.h', + 'eel-vfs-extensions.c', + 'eel.h', + 'eel-gtk-extensions.c', + 'eel-lib-self-check-functions.h', + 'eel-lib-self-check-functions.c', +] + +libeel_2_deps = [ + config_h, + glib, + gtk, + libm +] + +libeel_2 = static_library( + 'eel-2', + libeel_2_sources, + dependencies: libeel_2_deps, + include_directories: nautilus_include_dirs +) + +eel_2 = declare_dependency( + link_with: libeel_2, + include_directories: nautilus_include_dirs, + dependencies: libeel_2_deps +) + +check_eel = executable( + 'check-eel', + 'check-program.c', + dependencies: [ + eel_2, + xml + ] +) + +if get_option('tests') == 'all' + test('check-eel', check_eel) +endif |