diff --git a/docs/adg/Makefile.am b/docs/adg/Makefile.am index b13f62ca..1679c9cc 100644 --- a/docs/adg/Makefile.am +++ b/docs/adg/Makefile.am @@ -1,96 +1,97 @@ include $(top_srcdir)/build/Makefile.am.common DOC_MODULE= adg DOC_MAIN_SGML_FILE= $(DOC_MODULE)-docs.xml DOC_SOURCE_DIR= $(top_srcdir)/src/adg SCANGOBJ_OPTIONS= SCAN_OPTIONS= --rebuild-types \ --rebuild-sections MKDB_OPTIONS= --name-space=adg \ --sgml-mode \ --output-format=xml \ --default-includes="$(DOC_MODULE)-1/$(DOC_MODULE).h" MKHTML_OPTIONS= FIXXREF_OPTIONS= --extra-dir=$(top_srcdir)/docs/cpml/html \ $(EXTRA_DIR) HFILE_GLOB= $(DOC_SOURCE_DIR)/*.h EXTRA_HFILES= $(top_builddir)/src/adg/adg-canvas.h CFILE_GLOB= $(DOC_SOURCE_DIR)/*.c IGNORE_HFILES= adg.h \ adg-adim-private.h \ adg-alignment-private.h \ adg-arrow-private.h \ + adg-cairo-fallback.h \ adg-canvas-private.h \ adg-color-style-private.h \ adg-container-private.h \ adg-dash-private.h \ adg-dim-private.h \ adg-dim-style-private.h \ adg-dress-private.h \ adg-edges-private.h \ adg-entity-private.h \ adg-fill-style-private.h \ adg-font-style-private.h \ adg-forward-declarations.h \ adg-gtk-area-private.h \ adg-gtk-layout-private.h \ adg-hatch-private.h \ adg-internal.h \ adg-introspection.h \ adg-ldim-private.h \ adg-line-style-private.h \ adg-logo-private.h \ adg-marker-private.h \ adg-marshal.h \ adg-matrix-fallback.h \ adg-model-private.h \ adg-pango-style-private.h \ adg-path-private.h \ adg-projection-private.h \ adg-rdim-private.h \ adg-ruled-fill-private.h \ adg-stroke-private.h \ adg-table-private.h \ adg-table-style-private.h \ adg-text-internal.h \ adg-text-private.h \ adg-title-block-private.h \ adg-toy-text-private.h \ adg-trail-private.h \ adg-type-builtins.h \ test-internal.h HTML_IMAGES= content_files= CONTRIBUTING.xml \ HACKING.xml \ NEWS.xml \ README.xml \ TODO.xml expand_content_files= GTKDOC_CFLAGS= -I$(top_srcdir)/src \ $(CAIRO_CFLAGS) \ $(CAIRO_GOBJECT_CFLAGS) \ $(GOBJECT2_CFLAGS) GTKDOC_LIBS= $(top_builddir)/src/adg/libadg-1.la \ $(top_builddir)/src/cpml/libcpml-1.la if HAVE_GTK2 GTKDOC_CFLAGS+= $(GTK2_CFLAGS) GTKDOC_LIBS+= $(GTK2_LIBS) endif if HAVE_PANGO GTKDOC_CFLAGS+= $(PANGO_CFLAGS) GTKDOC_LIBS+= $(PANGO_LIBS) endif GTKDOC_LIBS+= $(CAIRO_LIBS) \ $(CAIRO_GOBJECT_LIBS) \ $(GOBJECT2_LIBS) include $(top_srcdir)/build/Makefile.am.gtk-doc DISTCLEANFILES= $(DOC_MODULE)-sections.txt \ $(DOC_MODULE).types EXTRA_DIST+= $(content_files) diff --git a/src/adg/adg-cairo-fallback.c b/src/adg/adg-cairo-fallback.c index 752670cd..54fbc89b 100644 --- a/src/adg/adg-cairo-fallback.c +++ b/src/adg/adg-cairo-fallback.c @@ -1,92 +1,78 @@ /* ADG - Automatic Drawing Generation * Copyright (C) 2007-2019 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -/** - * SECTION:adg-cairo-fallback - * @title: Cairo wrappers - * @short_description: GObject wrappers for cairo structs - * - * If GObject support has not been compiled in cairo, either by - * explicitely disabling it or because the installed cairo version - * does not provide them, a compatible selection of wrappers used - * by ADG is provided anyway. - * - * Since: 1.0 - **/ - - #include "adg-internal.h" #include #include #include "adg-cairo-fallback.h" #if CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 14, 0) void cairo_surface_set_device_scale(cairo_surface_t *surface, double x_scale, double y_scale) { } #endif /* cairo < 1.14.0 */ #ifdef ADG_MISSING_GBOXED_MATRIX GType cairo_gobject_matrix_get_type(void) { static GType matrix_type = 0; if (G_UNLIKELY(matrix_type == 0)) matrix_type = g_boxed_type_register_static("CairoMatrix", (GBoxedCopyFunc) cairo_gobject_cairo_matrix_copy, g_free); return matrix_type; } cairo_matrix_t * cairo_gobject_cairo_matrix_copy(const cairo_matrix_t *matrix) { return g_memdup(matrix, sizeof(cairo_matrix_t)); } #endif /* ADG_MISSING_GBOXED_MATRIX */ #ifdef ADG_MISSING_GBOXED_PATTERN GType cairo_gobject_pattern_get_type(void) { static GType pattern_type = 0; if (G_UNLIKELY(pattern_type == 0)) pattern_type = g_boxed_type_register_static("CairoPattern", (GBoxedCopyFunc) cairo_pattern_reference, (GBoxedFreeFunc) cairo_pattern_destroy); return pattern_type; } #endif /* ADG_MISSING_GBOXED_PATTERN */ diff --git a/src/adg/adg-canvas.c b/src/adg/adg-canvas.c index 917fd429..8a2541fc 100644 --- a/src/adg/adg-canvas.c +++ b/src/adg/adg-canvas.c @@ -1,1879 +1,1888 @@ /* ADG - Automatic Drawing Generation * Copyright (C) 2007-2019 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-canvas * @short_description: The drawing container * * The canvas is the toplevel entity of an ADG drawing. It can be * bound to a GTK+ widget, such as #AdgGtkArea, or manually rendered * to a custom surface. * * Tipically, the canvas contains the description and properties of * the media used, such as such as size (if relevant), margins, * border and paddings. This approach clearly follows the block model * of the CSS specification. * * The paddings specify the distance between the entities contained * by the canvas and the border. The margins specify the distance * between the canvas border and the media extents. * * The canvas (hence the media) size can be explicitely specified * by directly writing to the #AdgCanvas:size property or using any * valid setter, such as adg_canvas_set_size(), * adg_canvas_set_size_explicit() or the convenient * adg_canvas_set_paper() GTK+ wrapper. You can also set explicitely * only one dimension and let the other one be computed automatically. * This can be done by setting it to 0. * The ratio between the media unit (typically points on printing * surfaces and pixels on video surfaces) can be changed with * adg_canvas_set_factor(). * * By default either width and height are autocalculated, i.e. they * are initially set to 0. In this case the arrange() phase is executed. * Margins and paddings are then added to the extents to get the * border coordinates and the final bounding box. * * Instead, when the size is explicitely set, the final bounding * box is forcibly set to this value without taking the canvas * extents into account. The margins are then subtracted to get * the coordinates of the border. In this case the paddings are * simply ignoredby the arrange phase. They are still used by * adg_canvas_autoscale() though, if called. * * Since: 1.0 **/ /** * AdgCanvas: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ /** * ADG_CANVAS_ERROR: * * Error domain for canvas processing. Errors in this domain will be from the * #AdgCanvasError enumeration. See #GError for information on error domains. * * Since: 1.0 **/ /** * AdgCanvasError: * @ADG_CANVAS_ERROR_SURFACE: Invalid surface type. * @ADG_CANVAS_ERROR_CAIRO: The underlying cairo library returned an error. * * Error codes returned by #AdgCanvas methods. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-container.h" #include "adg-table.h" #include "adg-title-block.h" #include "adg-style.h" #include "adg-color-style.h" #include "adg-dress.h" #include "adg-param-dress.h" #include #include "adg-canvas-private.h" #ifdef CAIRO_HAS_PS_SURFACE #include #endif #ifdef CAIRO_HAS_PDF_SURFACE #include #endif #ifdef CAIRO_HAS_SVG_SURFACE #include #endif #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_canvas_parent_class) #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_canvas_parent_class) G_DEFINE_TYPE_WITH_PRIVATE(AdgCanvas, adg_canvas, ADG_TYPE_CONTAINER) enum { PROP_0, PROP_SIZE, PROP_FACTOR, PROP_SCALES, PROP_BACKGROUND_DRESS, PROP_FRAME_DRESS, PROP_TITLE_BLOCK, PROP_TOP_MARGIN, PROP_RIGHT_MARGIN, PROP_BOTTOM_MARGIN, PROP_LEFT_MARGIN, PROP_HAS_FRAME, PROP_TOP_PADDING, PROP_RIGHT_PADDING, PROP_BOTTOM_PADDING, PROP_LEFT_PADDING }; static void _adg_dispose (GObject *object); static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void _adg_global_changed (AdgEntity *entity); static void _adg_local_changed (AdgEntity *entity); static void _adg_invalidate (AdgEntity *entity); static void _adg_arrange (AdgEntity *entity); static void _adg_render (AdgEntity *entity, cairo_t *cr); static void _adg_apply_paddings (AdgCanvas *canvas, CpmlExtents *extents); static void _adg_update_margin (AdgCanvas *canvas, gdouble *margin, gdouble *side, gdouble new_margin); +/** + * adg_canvas_error_quark: + * + * Registers an error quark specific for #AdgCanvas. + * + * Returns: The error quark used for #AdgCanvas errors. + * + * Since: 1.0 + **/ GQuark adg_canvas_error_quark(void) { static GQuark q; if G_UNLIKELY (q == 0) q = g_quark_from_static_string("adg-canvas-error-quark"); return q; } static void adg_canvas_class_init(AdgCanvasClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; entity_class->global_changed = _adg_global_changed; entity_class->local_changed = _adg_local_changed; entity_class->invalidate = _adg_invalidate; entity_class->arrange = _adg_arrange; entity_class->render = _adg_render; param = g_param_spec_boxed("size", P_("Canvas Size"), P_("The size set on this canvas: use 0 to have an automatic dimension based on the canvas extents"), CPML_TYPE_PAIR, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_SIZE, param); param = g_param_spec_double("factor", P_("Factor"), P_("Global space units per point (1 point == 1/72 inch)"), 0, G_MAXDOUBLE, 1, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FACTOR, param); param = g_param_spec_boxed("scales", P_("Valid Scales"), P_("List of scales to be tested while autoscaling the drawing"), G_TYPE_STRV, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_SCALES, param); param = adg_param_spec_dress("background-dress", P_("Background Dress"), P_("The color dress to use for the canvas background"), ADG_DRESS_COLOR_BACKGROUND, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_BACKGROUND_DRESS, param); param = adg_param_spec_dress("frame-dress", P_("Frame Dress"), P_("Line dress to use while drawing the frame around the canvas"), ADG_DRESS_LINE_FRAME, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FRAME_DRESS, param); param = g_param_spec_object("title-block", P_("Title Block"), P_("The title block to assign to this canvas"), ADG_TYPE_TITLE_BLOCK, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_TITLE_BLOCK, param); param = g_param_spec_double("top-margin", P_("Top Margin"), P_("The margin (in global space) to leave above the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_TOP_MARGIN, param); param = g_param_spec_double("right-margin", P_("Right Margin"), P_("The margin (in global space) to leave empty at the right of the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_RIGHT_MARGIN, param); param = g_param_spec_double("bottom-margin", P_("Bottom Margin"), P_("The margin (in global space) to leave empty below the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_BOTTOM_MARGIN, param); param = g_param_spec_double("left-margin", P_("Left Margin"), P_("The margin (in global space) to leave empty at the left of the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LEFT_MARGIN, param); param = g_param_spec_boolean("has-frame", P_("Has Frame Flag"), P_("If enabled, a frame using the frame dress will be drawn around the canvas extents, taking into account the margins"), TRUE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_HAS_FRAME, param); param = g_param_spec_double("top-padding", P_("Top Padding"), P_("The padding (in global space) to leave empty above between the drawing and the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_TOP_PADDING, param); param = g_param_spec_double("right-padding", P_("Right Padding"), P_("The padding (in global space) to leave empty at the right between the drawing and the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_RIGHT_PADDING, param); param = g_param_spec_double("bottom-padding", P_("Bottom Padding"), P_("The padding (in global space) to leave empty below between the drawing and the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_BOTTOM_PADDING, param); param = g_param_spec_double("left-padding", P_("Left Padding"), P_("The padding (in global space) to leave empty at the left between the drawing and the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LEFT_PADDING, param); } static void adg_canvas_init(AdgCanvas *canvas) { AdgCanvasPrivate *data = adg_canvas_get_instance_private(canvas); const gchar *scales[] = { "10:1", "5:1", "3:1", "2:1", "1:1", "1:2", "1:3", "1:5", "1:10", NULL }; data->size.x = 0; data->size.y = 0; data->factor = 1; data->scales = g_strdupv((gchar **) scales); data->background_dress = ADG_DRESS_COLOR_BACKGROUND; data->frame_dress = ADG_DRESS_LINE_FRAME; data->title_block = NULL; data->top_margin = 15; data->right_margin = 15; data->bottom_margin = 15; data->left_margin = 15; data->has_frame = TRUE; data->top_padding = 15; data->right_padding = 15; data->bottom_padding = 15; data->left_padding = 15; } static void _adg_dispose(GObject *object) { AdgCanvas *canvas = (AdgCanvas *) object; AdgCanvasPrivate *data = adg_canvas_get_instance_private(canvas); if (data->title_block) { g_object_unref(data->title_block); data->title_block = NULL; } if (data->scales != NULL) { g_strfreev(data->scales); data->scales = NULL; } if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgCanvasPrivate *data = adg_canvas_get_instance_private((AdgCanvas *) object); switch (prop_id) { case PROP_SIZE: g_value_set_boxed(value, &data->size); break; case PROP_FACTOR: g_value_set_double(value, data->factor); break; case PROP_SCALES: g_value_set_boxed(value, data->scales); break; case PROP_BACKGROUND_DRESS: g_value_set_enum(value, data->background_dress); break; case PROP_FRAME_DRESS: g_value_set_enum(value, data->frame_dress); break; case PROP_TITLE_BLOCK: g_value_set_object(value, data->title_block); break; case PROP_TOP_MARGIN: g_value_set_double(value, data->top_margin); break; case PROP_RIGHT_MARGIN: g_value_set_double(value, data->right_margin); break; case PROP_BOTTOM_MARGIN: g_value_set_double(value, data->bottom_margin); break; case PROP_LEFT_MARGIN: g_value_set_double(value, data->left_margin); break; case PROP_HAS_FRAME: g_value_set_boolean(value, data->has_frame); break; case PROP_TOP_PADDING: g_value_set_double(value, data->top_padding); break; case PROP_RIGHT_PADDING: g_value_set_double(value, data->right_padding); break; case PROP_BOTTOM_PADDING: g_value_set_double(value, data->bottom_padding); break; case PROP_LEFT_PADDING: g_value_set_double(value, data->left_padding); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgCanvas *canvas = (AdgCanvas *) object; AdgCanvasPrivate *data = adg_canvas_get_instance_private(canvas); AdgTitleBlock *title_block; gdouble factor; switch (prop_id) { case PROP_SIZE: cpml_pair_copy(&data->size, g_value_get_boxed(value)); break; case PROP_FACTOR: factor = g_value_get_double(value); g_return_if_fail(factor > 0); data->factor = factor; break; case PROP_SCALES: g_strfreev(data->scales); data->scales = g_value_dup_boxed(value); break; case PROP_BACKGROUND_DRESS: data->background_dress = g_value_get_enum(value); break; case PROP_FRAME_DRESS: data->frame_dress = g_value_get_enum(value); break; case PROP_TITLE_BLOCK: title_block = g_value_get_object(value); if (title_block) { g_object_ref(title_block); adg_entity_set_parent((AdgEntity *) title_block, (AdgEntity *) canvas); } if (data->title_block) g_object_unref(data->title_block); data->title_block = title_block; break; case PROP_TOP_MARGIN: _adg_update_margin(canvas, &data->top_margin, &data->size.y, g_value_get_double(value)); break; case PROP_RIGHT_MARGIN: _adg_update_margin(canvas, &data->right_margin, &data->size.x, g_value_get_double(value)); break; case PROP_BOTTOM_MARGIN: _adg_update_margin(canvas, &data->bottom_margin, &data->size.y, g_value_get_double(value)); break; case PROP_LEFT_MARGIN: _adg_update_margin(canvas, &data->left_margin, &data->size.x, g_value_get_double(value)); break; case PROP_HAS_FRAME: data->has_frame = g_value_get_boolean(value); break; case PROP_TOP_PADDING: data->top_padding = g_value_get_double(value); break; case PROP_RIGHT_PADDING: data->right_padding = g_value_get_double(value); break; case PROP_BOTTOM_PADDING: data->bottom_padding = g_value_get_double(value); break; case PROP_LEFT_PADDING: data->left_padding = g_value_get_double(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_canvas_new: * * Creates a new empty canvas object. * * Returns: (transfer full): the newly created canvas. * * Since: 1.0 **/ AdgCanvas * adg_canvas_new(void) { return g_object_new(ADG_TYPE_CANVAS, NULL); } /** * adg_canvas_set_size: * @canvas: an #AdgCanvas * @size: (transfer none): the new size for the canvas * * Sets a specific size on @canvas. The x and/or y * component of @size can be set to 0, in which case * the exact value will be autocalculated, that is the * size component returned by adg_entity_get_extents() * on @canvas will be used instead. * * Since: 1.0 **/ void adg_canvas_set_size(AdgCanvas *canvas, const CpmlPair *size) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(size != NULL); g_object_set(canvas, "size", size, NULL); } /** * adg_canvas_set_size_explicit: * @canvas: an #AdgCanvas * @x: the new width of the canvas or 0 to reset * @y: the new height of the canvas or 0 to reset * * A convenient function to set the size of @canvas using * explicit coordinates. Check adg_canvas_set_size() for * further details. * * Since: 1.0 **/ void adg_canvas_set_size_explicit(AdgCanvas *canvas, gdouble x, gdouble y) { CpmlPair size; size.x = x; size.y = y; adg_canvas_set_size(canvas, &size); } /** * adg_canvas_get_size: * @canvas: an #AdgCanvas * * Gets the specific size set on @canvas. The x and/or y * components of the returned #CpmlPair could be 0, in which * case the size returned by adg_entity_get_extents() on @canvas * will be used instead. * * Returns: (transfer none): the explicit size set on this canvas or NULL on errors. * * Since: 1.0 **/ const CpmlPair * adg_canvas_get_size(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL); data = adg_canvas_get_instance_private(canvas); return &data->size; } /** * adg_canvas_set_factor: * @canvas: an #AdgCanvas * @factor: the new factor: must be greater than 0 * * The ADG library is intentionally unit agnostic, i.e. the global space is * represented in whatever unit you want. There are a couple of cairo APIs * that explicitely requires points though, most notably * cairo_pdf_surface_set_size() and cairo_ps_surface_set_size(). * * On PDF and postscript surfaces, the AdgCanvas:factor property will be the * number typography points per global space units: * https://en.wikipedia.org/wiki/Point_(typography) * * For other surfaces, the factor will be used to scale the final surface to * the specific number of pixels. * * As an example, if you are working in millimeters you will set a factor of * 2.83465 on PDF and postscript surfaces (1 mm = 2.83465 points) so the * drawing will show the correct scale on paper. For X11 and PNG surfaces you * will set it depending on the resolution you want to get, e.g. if the * drawing is 100x200 mm and you want a 1000x2000 image, just set it to 10. * * Since: 1.0 **/ void adg_canvas_set_factor(AdgCanvas *canvas, double factor) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(factor > 0); g_object_set(canvas, "factor", factor, NULL); } /** * adg_canvas_get_factor: * @canvas: an #AdgCanvas * * Gets the current factor of @canvas. See adg_canvas_set_factor() to learn * what a factor is in this context. * * Returns: the canvas factor or 0 on error. * * Since: 1.0 **/ double adg_canvas_get_factor(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->factor; } /** * adg_canvas_set_scales: * @canvas: an #AdgCanvas * @...: NULL terminated list of strings * * Sets the scales allowed by @canvas. Every scale identifies a * specific factor to be applied to the local matrix of @canvas. * When adg_canvas_autoscale() will be called, the greatest * scale that can render every entity inside a box of * #AdgCanvas:size dimensions will be applied. The drawing will * be centered inside that box. * * Every scale should be expressed with a string in the form of * "x:y", where x and y are positive integers that identifies * numerator an denominator of a fraction. That string itself * will be put into the title block when used. * * Since: 1.0 **/ void adg_canvas_set_scales(AdgCanvas *canvas, ...) { va_list var_args; va_start(var_args, canvas); adg_canvas_set_scales_valist(canvas, var_args); va_end(var_args); } /** * adg_canvas_set_scales_valist: * @canvas: an #AdgCanvas * @var_args: NULL terminated list of strings * * Vararg variant of adg_canvas_set_scales(). * * Since: 1.0 **/ void adg_canvas_set_scales_valist(AdgCanvas *canvas, va_list var_args) { gchar **scales; const gchar *scale; gint n; g_return_if_fail(ADG_IS_CANVAS(canvas)); scales = NULL; n = 0; while ((scale = va_arg(var_args, const gchar *)) != NULL) { ++n; scales = g_realloc(scales, sizeof(const gchar *) * (n + 1)); scales[n-1] = g_strdup(scale); scales[n] = NULL; } g_object_set(canvas, "scales", scales, NULL); g_strfreev(scales); } /** * adg_canvas_set_scales_array: (rename-to adg_canvas_set_scales) * @canvas: an #AdgCanvas * @scales: (array zero-terminated=1) (allow-none): NULL terminated array of scales * * Array variant of adg_canvas_set_scales(). * * Since: 1.0 **/ void adg_canvas_set_scales_array(AdgCanvas *canvas, gchar **scales) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "scales", scales, NULL); } /** * adg_canvas_get_scales: * @canvas: an #AdgCanvas * * Gets the list of scales set on @canvas: check adg_canvas_set_scales() * to get an idea of what scales are supposed to be. * * If no scales are set, NULL is returned. * * Returns: (element-type utf8) (transfer none): the NULL terminated list of valid scales. * * Since: 1.0 **/ gchar ** adg_canvas_get_scales(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL); data = adg_canvas_get_instance_private(canvas); return data->scales; } /** * adg_canvas_autoscale: * @canvas: an #AdgCanvas * * Applies one scale per time, in the order they have been provided * in the adg_canvas_set_scales() call, until the drawing can be * entirely contained into the current paper. If successful, the * scale of the title block is changed accordingly and the drawing * is centered inside the paper. * * The paddings are taken into account while computing the drawing * extents. * * Since: 1.0 **/ void adg_canvas_autoscale(AdgCanvas *canvas) { AdgCanvasPrivate *data; gchar **p_scale; AdgEntity *entity; cairo_matrix_t map; CpmlExtents extents; AdgTitleBlock *title_block; CpmlPair delta; g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(_ADG_OLD_ENTITY_CLASS->arrange != NULL); data = adg_canvas_get_instance_private(canvas); entity = (AdgEntity *) canvas; title_block = data->title_block; /* Manually calling the arrange() method instead of emitting the "arrange" * signal does not invalidate the global matrix: let's do it right now */ adg_entity_global_changed(entity); for (p_scale = data->scales; p_scale != NULL && *p_scale != NULL; ++p_scale) { const gchar *scale = *p_scale; gdouble factor = adg_scale_factor(scale); if (factor <= 0) continue; cairo_matrix_init_scale(&map, factor, factor); adg_entity_set_local_map(entity, &map); adg_entity_local_changed(entity); /* Arrange the entities inside the canvas, but not the canvas itself, * just to get the bounding box of the drawing without the paper */ _ADG_OLD_ENTITY_CLASS->arrange(entity); cpml_extents_copy(&extents, adg_entity_get_extents(entity)); /* Just in case @canvas is empty */ if (! extents.is_defined) return; _adg_apply_paddings(canvas, &extents); if (title_block != NULL) adg_title_block_set_scale(title_block, scale); /* Bail out if paper size is not specified or invalid */ if (data->size.x <= 0 || data->size.y <= 0) break; /* If the drawing extents are fully contained inside the paper size, * center the drawing in the paper and bail out */ delta.x = data->size.x - extents.size.x; delta.y = data->size.y - extents.size.y; if (delta.x >= 0 && delta.y >= 0) { cairo_matrix_t transform; cairo_matrix_init_translate(&transform, delta.x / 2 - extents.org.x, delta.y / 2 - extents.org.y); adg_entity_transform_local_map(entity, &transform, ADG_TRANSFORM_AFTER); break; } } } /** * adg_canvas_set_background_dress: * @canvas: an #AdgCanvas * @dress: the new #AdgDress to use * * Sets a new background dress for rendering @canvas: the new * dress must be a color dress. * * Since: 1.0 **/ void adg_canvas_set_background_dress(AdgCanvas *canvas, AdgDress dress) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "background-dress", dress, NULL); } /** * adg_canvas_get_background_dress: * @canvas: an #AdgCanvas * * Gets the background dress to be used in rendering @canvas. * * Returns: the current background dress. * * Since: 1.0 **/ AdgDress adg_canvas_get_background_dress(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), ADG_DRESS_UNDEFINED); data = adg_canvas_get_instance_private(canvas); return data->background_dress; } /** * adg_canvas_set_frame_dress: * @canvas: an #AdgCanvas * @dress: the new #AdgDress to use * * Sets the #AdgCanvas:frame-dress property of @canvas to @dress: * the new dress must be a line dress. * * Since: 1.0 **/ void adg_canvas_set_frame_dress(AdgCanvas *canvas, AdgDress dress) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "frame-dress", dress, NULL); } /** * adg_canvas_get_frame_dress: * @canvas: an #AdgCanvas * * Gets the frame dress to be used in rendering the border of @canvas. * * Returns: the current frame dress. * * Since: 1.0 **/ AdgDress adg_canvas_get_frame_dress(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), ADG_DRESS_UNDEFINED); data = adg_canvas_get_instance_private(canvas); return data->frame_dress; } /** * adg_canvas_set_title_block: * @canvas: an #AdgCanvas * @title_block: (transfer full): a title block * * Sets the #AdgCanvas:title-block property of @canvas to @title_block. * * Although a title block entity could be added to @canvas in the usual * way, that is using the adg_container_add() method, assigning a title * block with adg_canvas_set_title_block() is somewhat different: * * - @title_block will be automatically attached to the bottom right * corner of to the @canvas frame (this could be accomplished in the * usual way too, by resetting the right and bottom paddings); * - the @title_block boundary box is not taken into account while * computing the extents of @canvas. * * Since: 1.0 **/ void adg_canvas_set_title_block(AdgCanvas *canvas, AdgTitleBlock *title_block) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(title_block == NULL || ADG_IS_TITLE_BLOCK(title_block)); g_object_set(canvas, "title-block", title_block, NULL); } /** * adg_canvas_get_title_block: * @canvas: an #AdgCanvas * * Gets the #AdgTitleBlock object of @canvas: check * adg_canvas_set_title_block() for details. * * The returned entity is owned by @canvas and should not be * modified or freed. * * Returns: (transfer none): the title block object or NULL. * * Since: 1.0 **/ AdgTitleBlock * adg_canvas_get_title_block(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL); data = adg_canvas_get_instance_private(canvas); return data->title_block; } /** * adg_canvas_set_top_margin: * @canvas: an #AdgCanvas * @value: the new margin, in global space * * Changes the top margin of @canvas by setting #AdgCanvas:top-margin * to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_top_margin(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "top-margin", value, NULL); } /** * adg_canvas_get_top_margin: * @canvas: an #AdgCanvas * * Gets the top margin (in global space) of @canvas. * * Returns: the requested margin or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_top_margin(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->top_margin; } /** * adg_canvas_set_right_margin: * @canvas: an #AdgCanvas * @value: the new margin, in global space * * Changes the right margin of @canvas by setting * #AdgCanvas:right-margin to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_right_margin(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "right-margin", value, NULL); } /** * adg_canvas_get_right_margin: * @canvas: an #AdgCanvas * * Gets the right margin (in global space) of @canvas. * * Returns: the requested margin or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_right_margin(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->right_margin; } /** * adg_canvas_set_bottom_margin: * @canvas: an #AdgCanvas * @value: the new margin, in global space * * Changes the bottom margin of @canvas by setting * #AdgCanvas:bottom-margin to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_bottom_margin(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "bottom-margin", value, NULL); } /** * adg_canvas_get_bottom_margin: * @canvas: an #AdgCanvas * * Gets the bottom margin (in global space) of @canvas. * * Returns: the requested margin or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_bottom_margin(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->bottom_margin; } /** * adg_canvas_set_left_margin: * @canvas: an #AdgCanvas * @value: the new margin, in global space * * Changes the left margin of @canvas by setting * #AdgCanvas:left-margin to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_left_margin(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "left-margin", value, NULL); } /** * adg_canvas_get_left_margin: * @canvas: an #AdgCanvas * * Gets the left margin (in global space) of @canvas. * * Returns: the requested margin or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_left_margin(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->left_margin; } /** * adg_canvas_set_margins: * @canvas: an #AdgCanvas * @top: top margin, in global space * @right: right margin, in global space * @bottom: bottom margin, in global space * @left: left margin, in global space * * Convenient function to set all the margins at once. * * Since: 1.0 **/ void adg_canvas_set_margins(AdgCanvas *canvas, gdouble top, gdouble right, gdouble bottom, gdouble left) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "top-margin", top, "right-margin", right, "bottom-margin", bottom, "left-margin", left, NULL); } /** * adg_canvas_get_margins: * @canvas: an #AdgCanvas * @top: (out) (nullable): where to store the top margin or NULL * @right: (out) (nullable): where to store the right margin or NULL * @bottom: (out) (nullable): where to store the bottom margin or NULL * @left: (out) (nullable): where to store the left margin or NULL * * Convenient function to get all the margins at once. * * Since: 1.0 **/ void adg_canvas_get_margins(AdgCanvas *canvas, gdouble *top, gdouble *right, gdouble *bottom, gdouble *left) { AdgCanvasPrivate *data; g_return_if_fail(ADG_IS_CANVAS(canvas)); data = adg_canvas_get_instance_private(canvas); if (top != NULL) { *top = data->top_margin; } if (right != NULL) { *right = data->right_margin; } if (bottom != NULL) { *bottom = data->bottom_margin; } if (left != NULL) { *left = data->left_margin; } } /** * adg_canvas_apply_margins: * @canvas: an #AdgCanvas * @extents: where apply the margins * * A convenient function to apply the margins of @canvas to the * arbitrary #CpmlExtents struct @extents. "Apply" means @extents * are enlarged of the specific margin values. * * Since: 1.0 **/ void adg_canvas_apply_margins(AdgCanvas *canvas, CpmlExtents *extents) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(extents != NULL); if (extents->is_defined) { AdgCanvasPrivate *data = adg_canvas_get_instance_private(canvas); extents->org.x -= data->left_margin; extents->org.y -= data->top_margin; extents->size.x += data->left_margin + data->right_margin; extents->size.y += data->top_margin + data->bottom_margin; } } /** * adg_canvas_switch_frame: * @canvas: an #AdgCanvas * @new_state: the new flag status * * Sets a new status on the #AdgCanvas:has-frame * property: TRUE means a border around * the canvas extents (less the margins) should be rendered. * * Since: 1.0 **/ void adg_canvas_switch_frame(AdgCanvas *canvas, gboolean new_state) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "has-frame", new_state, NULL); } /** * adg_canvas_has_frame: * @canvas: an #AdgCanvas * * Gets the current status of the #AdgCanvas:has-frame property, * that is whether a border around the canvas extents (less the * margins) should be rendered (TRUE) or not * (FALSE). * * Returns: the current status of the frame flag. * * Since: 1.0 **/ gboolean adg_canvas_has_frame(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), FALSE); data = adg_canvas_get_instance_private(canvas); return data->has_frame; } /** * adg_canvas_set_top_padding: * @canvas: an #AdgCanvas * @value: the new padding, in global space * * Changes the top padding of @canvas by setting * #AdgCanvas:top-padding to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_top_padding(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "top-padding", value, NULL); } /** * adg_canvas_get_top_padding: * @canvas: an #AdgCanvas * * Gets the top padding (in global space) of @canvas. * * Returns: the requested padding or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_top_padding(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->top_padding; } /** * adg_canvas_set_right_padding: * @canvas: an #AdgCanvas * @value: the new padding, in global space * * Changes the right padding of @canvas by setting #AdgCanvas:right-padding * to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_right_padding(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "right-padding", value, NULL); } /** * adg_canvas_get_right_padding: * @canvas: an #AdgCanvas * * Gets the right padding (in global space) of @canvas. * * Returns: the requested padding or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_right_padding(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->right_padding; } /** * adg_canvas_set_bottom_padding: * @canvas: an #AdgCanvas * @value: the new padding, in global space * * Changes the bottom padding of @canvas by setting * #AdgCanvas:bottom-padding to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_bottom_padding(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "bottom-padding", value, NULL); } /** * adg_canvas_get_bottom_padding: * @canvas: an #AdgCanvas * * Gets the bottom padding (in global space) of @canvas. * * Returns: the requested padding or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_bottom_padding(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->bottom_padding; } /** * adg_canvas_set_left_padding: * @canvas: an #AdgCanvas * @value: the new padding, in global space * * Changes the left padding of @canvas by setting * #AdgCanvas:left-padding to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_left_padding(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "left-padding", value, NULL); } /** * adg_canvas_get_left_padding: * @canvas: an #AdgCanvas * * Gets the left padding (in global space) of @canvas. * * Returns: the requested padding or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_left_padding(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->left_padding; } /** * adg_canvas_set_paddings: * @canvas: an #AdgCanvas * @top: top padding, in global space * @right: right padding, in global space * @bottom: bottom padding, in global space * @left: left padding, in global space * * Convenient function to set all the paddings at once. * * Since: 1.0 **/ void adg_canvas_set_paddings(AdgCanvas *canvas, gdouble top, gdouble right, gdouble bottom, gdouble left) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "top-padding", top, "right-padding", right, "bottom-padding", bottom, "left-padding", left, NULL); } /** * adg_canvas_get_paddings: * @canvas: an #AdgCanvas * @top: (out) (nullable): where to store the top padding or NULL * @right: (out) (nullable): where to store the right padding or NULL * @bottom: (out) (nullable): where to store the bottom padding or NULL * @left: (out) (nullable): where to store the left padding or NULL * * Convenient function to get all the paddings at once. * * Since: 1.0 **/ void adg_canvas_get_paddings(AdgCanvas *canvas, gdouble *top, gdouble *right, gdouble *bottom, gdouble *left) { AdgCanvasPrivate *data; g_return_if_fail(ADG_IS_CANVAS(canvas)); data = adg_canvas_get_instance_private(canvas); if (top != NULL) { *top = data->top_padding; } if (right != NULL) { *right = data->right_padding; } if (bottom != NULL) { *bottom = data->bottom_padding; } if (left != NULL) { *left = data->left_padding; } } static void _adg_global_changed(AdgEntity *entity) { AdgCanvasPrivate *data = adg_canvas_get_instance_private((AdgCanvas *) entity); if (_ADG_OLD_ENTITY_CLASS->global_changed) _ADG_OLD_ENTITY_CLASS->global_changed(entity); if (data->title_block) adg_entity_global_changed((AdgEntity *) data->title_block); } static void _adg_local_changed(AdgEntity *entity) { AdgCanvasPrivate *data = adg_canvas_get_instance_private((AdgCanvas *) entity); AdgTitleBlock *title_block = data->title_block; if (_ADG_OLD_ENTITY_CLASS->local_changed) _ADG_OLD_ENTITY_CLASS->local_changed(entity); if (title_block != NULL) { const gchar *scale = adg_title_block_get_scale(title_block); if (scale != NULL && scale[0] != '\0') { const cairo_matrix_t *map = adg_entity_get_local_map(entity); gdouble factor = adg_scale_factor(scale); if (map->xx != factor || map->yy != factor) adg_title_block_set_scale(title_block, "---"); } adg_entity_local_changed((AdgEntity *) title_block); } } static void _adg_invalidate(AdgEntity *entity) { AdgCanvasPrivate *data = adg_canvas_get_instance_private((AdgCanvas *) entity); if (_ADG_OLD_ENTITY_CLASS->invalidate) _ADG_OLD_ENTITY_CLASS->invalidate(entity); if (data->title_block) adg_entity_invalidate((AdgEntity *) data->title_block); } static void _adg_arrange(AdgEntity *entity) { AdgCanvas *canvas; AdgCanvasPrivate *data; CpmlExtents extents; if (_ADG_OLD_ENTITY_CLASS->arrange) _ADG_OLD_ENTITY_CLASS->arrange(entity); cpml_extents_copy(&extents, adg_entity_get_extents(entity)); /* The extents should be defined, otherwise there is no drawing */ g_return_if_fail(extents.is_defined); canvas = (AdgCanvas *) entity; data = adg_canvas_get_instance_private(canvas); _adg_apply_paddings(canvas, &extents); if (data->size.x > 0 || data->size.y > 0) { const cairo_matrix_t *global = adg_entity_get_global_matrix(entity); CpmlExtents paper; paper.org.x = 0; paper.org.y = 0; paper.size.x = data->size.x; paper.size.y = data->size.y; cairo_matrix_transform_point(global, &paper.org.x, &paper.org.y); cairo_matrix_transform_distance(global, &paper.size.x, &paper.size.y); if (data->size.x > 0) { extents.org.x = paper.org.x; extents.size.x = paper.size.x; } if (data->size.y > 0) { extents.org.y = paper.org.y; extents.size.y = paper.size.y; } } if (data->title_block) { AdgEntity *title_block_entity; const CpmlExtents *title_block_extents; CpmlPair shift; title_block_entity = (AdgEntity *) data->title_block; adg_entity_arrange(title_block_entity); title_block_extents = adg_entity_get_extents(title_block_entity); shift.x = extents.org.x + extents.size.x - title_block_extents->org.x - title_block_extents->size.x; shift.y = extents.org.y + extents.size.y - title_block_extents->org.y - title_block_extents->size.y; /* The following block could be optimized by skipping tiny shift, * usually left by rounding errors */ if (shift.x != 0 || shift.y != 0) { cairo_matrix_t unglobal, map; adg_matrix_copy(&unglobal, adg_entity_get_global_matrix(entity)); cairo_matrix_invert(&unglobal); cairo_matrix_transform_distance(&unglobal, &shift.x, &shift.y); cairo_matrix_init_translate(&map, shift.x, shift.y); adg_entity_transform_global_map(title_block_entity, &map, ADG_TRANSFORM_AFTER); adg_entity_global_changed(title_block_entity); adg_entity_arrange(title_block_entity); title_block_extents = adg_entity_get_extents(title_block_entity); cpml_extents_add(&extents, title_block_extents); } } /* Impose the new extents */ adg_entity_set_extents(entity, &extents); } static void _adg_render(AdgEntity *entity, cairo_t *cr) { AdgCanvasPrivate *data = adg_canvas_get_instance_private((AdgCanvas *) entity); const CpmlExtents *extents = adg_entity_get_extents(entity); cairo_save(cr); /* Background fill */ cairo_rectangle(cr, extents->org.x - data->left_margin, extents->org.y - data->top_margin, extents->size.x + data->left_margin + data->right_margin, extents->size.y + data->top_margin + data->bottom_margin); adg_entity_apply_dress(entity, data->background_dress, cr); cairo_fill(cr); /* Frame line */ if (data->has_frame) { cairo_rectangle(cr, extents->org.x, extents->org.y, extents->size.x, extents->size.y); cairo_transform(cr, adg_entity_get_global_matrix(entity)); adg_entity_apply_dress(entity, data->frame_dress, cr); cairo_stroke(cr); } cairo_restore(cr); if (data->title_block) adg_entity_render((AdgEntity *) data->title_block, cr); if (_ADG_OLD_ENTITY_CLASS->render) _ADG_OLD_ENTITY_CLASS->render(entity, cr); } static void _adg_apply_paddings(AdgCanvas *canvas, CpmlExtents *extents) { AdgCanvasPrivate *data = adg_canvas_get_instance_private(canvas); extents->org.x -= data->left_padding; extents->size.x += data->left_padding + data->right_padding; extents->org.y -= data->top_padding; extents->size.y += data->top_padding + data->bottom_padding; } /** * adg_canvas_export: * @canvas: an #AdgCanvas * @type: (type gint): the export format * @file: the name of the resulting file * @gerror: (allow-none): return location for errors * * A helper function that provides a bare export functionality. * It basically exports the drawing in @canvas in the @file * in the @type format. Any error will be reported in @gerror, * if not NULL. * * Returns: TRUE on success, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_canvas_export(AdgCanvas *canvas, cairo_surface_type_t type, const gchar *file, GError **gerror) { AdgEntity *entity; const CpmlExtents *extents; gdouble top, bottom, left, right, width, height, factor; cairo_surface_t *surface; cairo_t *cr; cairo_status_t status; g_return_val_if_fail(ADG_IS_CANVAS(canvas), FALSE); g_return_val_if_fail(file != NULL, FALSE); g_return_val_if_fail(gerror == NULL || *gerror == NULL, FALSE); entity = (AdgEntity *) canvas; adg_entity_arrange(entity); extents = adg_entity_get_extents(entity); factor = adg_canvas_get_factor(canvas); top = factor * adg_canvas_get_top_margin(canvas); bottom = factor * adg_canvas_get_bottom_margin(canvas); left = factor * adg_canvas_get_left_margin(canvas); right = factor * adg_canvas_get_right_margin(canvas); width = factor * extents->size.x + left + right; height = factor * extents->size.y + top + bottom; switch (type) { #ifdef CAIRO_HAS_PNG_FUNCTIONS case CAIRO_SURFACE_TYPE_IMAGE: surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height); break; #endif #ifdef CAIRO_HAS_PDF_SURFACE case CAIRO_SURFACE_TYPE_PDF: surface = cairo_pdf_surface_create(file, width, height); break; #endif #ifdef CAIRO_HAS_PS_SURFACE case CAIRO_SURFACE_TYPE_PS: surface = cairo_ps_surface_create(file, width, height); break; #endif #ifdef CAIRO_HAS_SVG_SURFACE case CAIRO_SURFACE_TYPE_SVG: surface = cairo_svg_surface_create(file, width, height); break; #endif default: surface = NULL; break; } if (surface == NULL) { g_set_error(gerror, ADG_CANVAS_ERROR, ADG_CANVAS_ERROR_SURFACE, "unable to handle surface type '%d'", type); return FALSE; } cairo_surface_set_device_offset(surface, left, top); cairo_surface_set_device_scale(surface, factor, factor); cr = cairo_create(surface); cairo_surface_destroy(surface); adg_entity_render(ADG_ENTITY(canvas), cr); if (cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE) { status = cairo_surface_write_to_png(surface, file); } else { cairo_show_page(cr); status = cairo_status(cr); } cairo_destroy(cr); if (status != CAIRO_STATUS_SUCCESS) { g_set_error(gerror, ADG_CANVAS_ERROR, ADG_CANVAS_ERROR_CAIRO, "cairo reported '%s'", cairo_status_to_string(status)); return FALSE; } return TRUE; } #if GTK3_ENABLED || GTK2_ENABLED #include static void _adg_update_margin(AdgCanvas *canvas, gdouble *margin, gdouble *side, gdouble new_margin) { GtkPageSetup *page_setup; gdouble old_margin; old_margin = *margin; *margin = new_margin; page_setup = adg_canvas_get_page_setup(canvas); if (page_setup == NULL) return; *side += old_margin - new_margin; } /** * adg_canvas_set_paper: * @canvas: an #AdgCanvas * @paper_name: a paper name * @orientation: the page orientation * * A convenient function to set size and margins of @canvas * using a @paper_name and an @orientation value. This should * be a PWG 5101.1-2002 paper name and it will be passed as * is to gtk_paper_size_new(), so use any valid name accepted * by that function. * * This has the same effect as creating a #GtkPaperSetup object * and binding it to @canvas with adg_canvas_set_page_setup(): * check its documentation for knowing the implications. * * To reset the size to its default behavior (i.e. autocalculate * it from the entities) just call adg_canvas_set_size_explicit() * passing 0, 0. * * If you want to use your own margins on a named paper size, * set them after the call to this function. * * Since: 1.0 **/ void adg_canvas_set_paper(AdgCanvas *canvas, const gchar *paper_name, GtkPageOrientation orientation) { GtkPageSetup *page_setup; GtkPaperSize *paper_size; g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(paper_name != NULL); page_setup = gtk_page_setup_new(); paper_size = gtk_paper_size_new(paper_name); gtk_page_setup_set_paper_size(page_setup, paper_size); gtk_page_setup_set_orientation(page_setup, orientation); gtk_paper_size_free(paper_size); adg_canvas_set_page_setup(canvas, page_setup); g_object_unref(page_setup); } /** * adg_canvas_set_page_setup: * @canvas: an #AdgCanvas * @page_setup: (allow-none) (transfer none): the page setup * * A convenient function to setup the page of @canvas so it can * also be subsequentially used for printing. It is allowed to * pass NULL as @page_setup to restore the * default page setup. * * When a canvas is bound to a page setup, the paper size is kept * constant. This implies increasing or decreasing the margins * respectively decreases or increases the page size of the * relevant dimension, e.g. increasing the right margin decreases * the width (the x component of the size). * * A reference to @page_setup is added, so there is no need to keep * alive this object after a call to this function. @page_setup can * be retrieved at any time with adg_canvas_get_page_setup(). * * The size and margins provided by @page_setup are immediately * used to set size and margins of @canvas. This means using * NULL as @page_setup just releases the * reference to the previous @page_setup object... all the page * settings are still retained. * * If you want to use your own margins on a page setup, * set them on canvas after the call to this * function or set them on @page_setup before. * * * // By default, canvas does not have an explicit size * adg_canvas_set_page_setup(canvas, a4); * g_object_unref(a4); * // Now canvas has size and margins specified by a4 * // (and a4 should be a prefilled GtkPageSetup object). * adg_canvas_set_page_setup(canvas, NULL); * // Now canvas is no more bound to a4 and that object (if * // not referenced anywhere else) can be garbage-collected. * // The page setup of canvas has not changed though. * adg_canvas_set_size_explicit(canvas, 0, 0); * // Now the page size of canvas has been restored to * // their default behavior. * * * Since: 1.0 **/ void adg_canvas_set_page_setup(AdgCanvas *canvas, GtkPageSetup *page_setup) { gdouble top, right, bottom, left; CpmlPair size; g_return_if_fail(ADG_IS_CANVAS(canvas)); if (page_setup == NULL) { /* By convention, NULL resets the default page but * does not change any other property */ g_object_set_data((GObject *) canvas, "_adg_page_setup", NULL); return; } g_return_if_fail(GTK_IS_PAGE_SETUP(page_setup)); top = gtk_page_setup_get_top_margin(page_setup, GTK_UNIT_POINTS); right = gtk_page_setup_get_right_margin(page_setup, GTK_UNIT_POINTS); bottom = gtk_page_setup_get_bottom_margin(page_setup, GTK_UNIT_POINTS); left = gtk_page_setup_get_left_margin(page_setup, GTK_UNIT_POINTS); size.x = gtk_page_setup_get_page_width(page_setup, GTK_UNIT_POINTS); size.y = gtk_page_setup_get_page_height(page_setup, GTK_UNIT_POINTS); adg_canvas_set_size(canvas, &size); adg_canvas_set_margins(canvas, top, right, bottom, left); g_object_ref(page_setup); g_object_set_data_full((GObject *) canvas, "_adg_page_setup", page_setup, g_object_unref); } /** * adg_canvas_get_page_setup: * @canvas: an #AdgCanvas * * If adg_canvas_set_page_setup() is called, a #GtkPageSetup object * is created and bound to @canvas. This metho returns a pointer * to that internal object or NULL if * adg_canvas_set_page_setup() has not been called before. * * Returns: (allow-none) (transfer none): the #GtkPageSetup with size and margins of @canvas of NULL on no setup found or errors. * * Since: 1.0 **/ GtkPageSetup * adg_canvas_get_page_setup(AdgCanvas *canvas) { g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL); return g_object_get_data((GObject *) canvas, "_adg_page_setup"); } #else static void _adg_update_margin(AdgCanvas *canvas, gdouble *margin, gdouble *side, gdouble new_margin) { *margin = new_margin; } #endif diff --git a/src/adg/adg-dim.c b/src/adg/adg-dim.c index 27c7e7e6..ae0172e5 100644 --- a/src/adg/adg-dim.c +++ b/src/adg/adg-dim.c @@ -1,1842 +1,1844 @@ /* ADG - Automatic Drawing Generation * Copyright (C) 2007-2019 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-dim * @short_description: Root abstract class for all dimension entities * * The #AdgDim class is the base stub of all the dimension entities. * * Since: 1.0 **/ /** * AdgDim: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ /** * AdgDimClass: * @quote_angle: virtual method that must return the rotation angle of the * quote (in radians) of the current dimension. * @default_value: abstract virtual method that must return the default value * (as a newly allocated string to be freed with g_free()) of * the current dimension. * * The default implementation of @quote_angle flips the quote if it should be * rotated in the bottom right half of the circle, that is: * * * if 1/3 PI <= angle <= -3/4 PI; then angle += PI. * * * The virtual method @default_value instead *must* be implemented by any * derived class. The default implementation will trigger an error if called. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-text-internal.h" #include "adg-container.h" #include "adg-alignment.h" #include "adg-model.h" #include "adg-trail.h" #include "adg-point.h" #include "adg-marker.h" #include "adg-dim-style.h" #include "adg-dress.h" #include "adg-param-dress.h" #include "adg-dim.h" #include "adg-dim-private.h" #include #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_dim_parent_class) #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_dim_parent_class) /* A convenience macro for ORing two AdgThreeState values */ #define OR_3S(a,b) ( \ ((a) == ADG_THREE_STATE_ON || (b) == ADG_THREE_STATE_ON) ? ADG_THREE_STATE_ON : \ ((a) == ADG_THREE_STATE_UNKNOWN && (b) == ADG_THREE_STATE_UNKNOWN) ? ADG_THREE_STATE_UNKNOWN : \ ADG_THREE_STATE_OFF ) G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(AdgDim, adg_dim, ADG_TYPE_ENTITY) enum { PROP_0, PROP_DIM_DRESS, PROP_REF1, PROP_REF2, PROP_POS, PROP_LEVEL, PROP_OUTSIDE, PROP_DETACHED, PROP_VALUE, PROP_MIN, PROP_MAX }; static void _adg_dispose (GObject *object); static void _adg_finalize (GObject *object); static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void _adg_global_changed (AdgEntity *entity); static void _adg_local_changed (AdgEntity *entity); static void _adg_invalidate (AdgEntity *entity); static void _adg_arrange (AdgEntity *entity); static gboolean _adg_compute_geometry (AdgDim *dim); static gchar * _adg_default_value (AdgDim *dim); static gdouble _adg_quote_angle (gdouble angle); static gboolean _adg_set_outside (AdgDim *dim, AdgThreeState outside); static gboolean _adg_set_detached (AdgDim *dim, AdgThreeState detached); static gboolean _adg_set_value (AdgDim *dim, const gchar *value); static gboolean _adg_set_min (AdgDim *dim, const gchar *min); static gboolean _adg_set_max (AdgDim *dim, const gchar *max); static gboolean _adg_replace (const GMatchInfo *match_info, GString *result, gpointer user_data); static gchar * _adg_text_expand (AdgDimReplaceData *data); static void adg_dim_class_init(AdgDimClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->finalize = _adg_finalize; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; entity_class->global_changed = _adg_global_changed; entity_class->local_changed = _adg_local_changed; entity_class->invalidate = _adg_invalidate; entity_class->arrange = _adg_arrange; klass->compute_geometry = _adg_compute_geometry; klass->quote_angle = _adg_quote_angle; klass->default_value = _adg_default_value; param = adg_param_spec_dress("dim-dress", P_("Dimension Dress"), P_("The dress to use for rendering this dimension"), ADG_DRESS_DIMENSION, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_DIM_DRESS, param); param = g_param_spec_boxed("ref1", P_("First Reference"), P_("First reference point of the dimension"), ADG_TYPE_POINT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_REF1, param); param = g_param_spec_boxed("ref2", P_("Second Reference"), P_("Second reference point of the dimension"), ADG_TYPE_POINT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_REF2, param); param = g_param_spec_boxed("pos", P_("Position"), P_("The reference position of the quote: it will be combined with \"level\" to get the real quote position"), ADG_TYPE_POINT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_POS, param); param = g_param_spec_double("level", P_("Level"), P_("The dimension level, that is the factor to multiply the baseline spacing (defined in the dimension style) to get the offset from pos where the quote should be rendered"), -G_MAXDOUBLE, G_MAXDOUBLE, 1.0, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LEVEL, param); param = g_param_spec_enum("outside", P_("Outside"), P_("Whether the arrows must be inside the extension lines (ADG_THREE_STATE_OFF), must be extended outside the extension lines (ADG_THREE_STATE_ON) or should be automatically handled depending on the available space"), ADG_TYPE_THREE_STATE, ADG_THREE_STATE_UNKNOWN, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_OUTSIDE, param); param = g_param_spec_enum("detached", P_("Detached Quote"), P_("Where the quote must be positioned: in the middle of the base line (ADG_THREE_STATE_OFF), near the pos point (ADG_THREE_STATE_ON) or should be automatically deducted depending on the available space"), ADG_TYPE_THREE_STATE, ADG_THREE_STATE_UNKNOWN, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_DETACHED, param); param = g_param_spec_string("value", P_("Value Template"), P_("The template string to be used for generating the set value of the quote"), NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); g_object_class_install_property(gobject_class, PROP_VALUE, param); param = g_param_spec_string("min", P_("Minimum Value or Low Tolerance"), P_("The minimum value allowed or the lowest tolerance from value (depending of the dimension style): set to NULL to suppress"), NULL, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_MIN, param); param = g_param_spec_string("max", P_("Maximum Value or High Tolerance"), P_("The maximum value allowed or the highest tolerance from value (depending of the dimension style): set to NULL to suppress"), NULL, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_MAX, param); } static void adg_dim_init(AdgDim *dim) { AdgDimPrivate *data = adg_dim_get_instance_private(dim); data->dim_dress = ADG_DRESS_DIMENSION; data->ref1 = NULL; data->ref2 = NULL; data->pos = NULL; data->level = 1; data->outside = ADG_THREE_STATE_UNKNOWN; data->detached = ADG_THREE_STATE_UNKNOWN; data->min = NULL; data->max = NULL; data->geometry.computed = FALSE; data->geometry.notice = NULL; #if 0 /* This one is G_PARAM_CONSTRUCT, so set by property inizialization */ data->value = NULL #endif } static void _adg_dispose(GObject *object) { AdgEntity *entity = (AdgEntity *) object; AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) object); if (data->quote.entity) { g_object_unref(data->quote.entity); data->quote.entity = NULL; } if (data->ref1) data->ref1 = adg_entity_point(entity, data->ref1, NULL); if (data->ref2) data->ref2 = adg_entity_point(entity, data->ref2, NULL); if (data->pos) data->pos = adg_entity_point(entity, data->pos, NULL); if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_finalize(GObject *object) { AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) object); g_free(data->value); g_free(data->min); g_free(data->max); if (_ADG_OLD_OBJECT_CLASS->finalize) _ADG_OLD_OBJECT_CLASS->finalize(object); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) object); switch (prop_id) { case PROP_DIM_DRESS: g_value_set_enum(value, data->dim_dress); break; case PROP_REF1: g_value_set_boxed(value, data->ref1); break; case PROP_REF2: g_value_set_boxed(value, data->ref2); break; case PROP_POS: g_value_set_boxed(value, data->pos); break; case PROP_LEVEL: g_value_set_double(value, data->level); break; case PROP_OUTSIDE: g_value_set_enum(value, data->outside); break; case PROP_DETACHED: g_value_set_enum(value, data->detached); break; case PROP_VALUE: g_value_set_string(value, data->value); break; case PROP_MIN: g_value_set_string(value, data->min); break; case PROP_MAX: g_value_set_string(value, data->max); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgEntity *entity = (AdgEntity *) object; AdgDim *dim = (AdgDim *) object; AdgDimPrivate *data = adg_dim_get_instance_private(dim); switch (prop_id) { case PROP_DIM_DRESS: data->dim_dress = g_value_get_enum(value); break; case PROP_REF1: data->ref1 = adg_entity_point(entity, data->ref1, g_value_get_boxed(value)); break; case PROP_REF2: data->ref2 = adg_entity_point(entity, data->ref2, g_value_get_boxed(value)); break; case PROP_POS: data->pos = adg_entity_point(entity, data->pos, g_value_get_boxed(value)); break; case PROP_LEVEL: data->level = g_value_get_double(value); break; case PROP_OUTSIDE: _adg_set_outside(dim, g_value_get_enum(value)); break; case PROP_DETACHED: _adg_set_detached(dim, g_value_get_enum(value)); break; case PROP_VALUE: _adg_set_value(dim, g_value_get_string(value)); break; case PROP_MIN: _adg_set_min(dim, g_value_get_string(value)); break; case PROP_MAX: _adg_set_max(dim, g_value_get_string(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_dim_get_dim_style: * @dim: an #AdgDim * * Gets the internal cached dim style of @dim. * * Returns: (transfer none): the internal AdgDimStyle style. * * Since: 1.0 **/ AdgDimStyle * adg_dim_get_dim_style(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->dim_style; } /** * adg_dim_set_dim_dress: * @dim: an #AdgDim * @dress: the new #AdgDress to use * * Sets a new dimension dress to @dim. The new dress must be * related to the original dress for this property: you cannot * set a dress used for line styles to a dress managing fonts. * * The check is done by calling adg_dress_are_related() with * @dress and the previous dress as arguments. Check out its * documentation for details on what is a related dress. * * Since: 1.0 **/ void adg_dim_set_dim_dress(AdgDim *dim, AdgDress dress) { g_return_if_fail(ADG_IS_DIM(dim)); g_object_set(dim, "dim-dress", dress, NULL); } /** * adg_dim_get_dim_dress: * @dim: an #AdgDim * * Gets the dimension dress to be used in rendering @dim. * * Returns: (transfer none): the current dimension dress. * * Since: 1.0 **/ AdgDress adg_dim_get_dim_dress(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), ADG_DRESS_UNDEFINED); data = adg_dim_get_instance_private(dim); return data->dim_dress; } /** * adg_dim_set_ref1: * @dim: an #AdgDim * @ref1: the new point to use as first reference * * Sets the #AdgDim:ref1 property to @ref1. The old point * is silently discarded, unreferencing its model if that * point was bound to a named pair (hence, possibly destroying * the model if this was the last reference). * * @ref1 can be NULL, in which case the * point is destroyed. * * Since: 1.0 **/ void adg_dim_set_ref1(AdgDim *dim, const AdgPoint *ref1) { g_return_if_fail(ADG_IS_DIM(dim)); g_object_set(dim, "ref1", ref1, NULL); } /** * adg_dim_set_ref1_explicit: * @dim: an #AdgDim * @x: x coordinate of the first reference point * @y: y coordinate of the first reference point * * Sets the #AdgDim:ref1 property to the (@x, @y) explicit * coordinates. The old point is silently discarded, * unreferencing its model if that point was bound to a named * pair (hence, possibly destroying the model if this was the * last reference). * * Since: 1.0 **/ void adg_dim_set_ref1_explicit(AdgDim *dim, gdouble x, gdouble y) { AdgPoint *point = adg_point_new(); adg_point_set_pair_explicit(point, x, y); adg_dim_set_ref1(dim, point); adg_point_destroy(point); } /** * adg_dim_set_ref1_from_pair: * @dim: an #AdgDim * @ref1: the coordinates pair of the first reference point * * Convenient function to set the #AdgDim:ref1 property using a * pair instead of explicit coordinates. * * Since: 1.0 **/ void adg_dim_set_ref1_from_pair(AdgDim *dim, const CpmlPair *ref1) { g_return_if_fail(ref1 != NULL); adg_dim_set_ref1_explicit(dim, ref1->x, ref1->y); } /** * adg_dim_set_ref1_from_model: * @dim: an #AdgDim * @model: the source #AdgModel * @ref1: a named pair in @model * * Binds #AdgDim:ref1 to the @ref1 named pair of @model. If @model * is NULL, the point will be unset. In any case, * the old point is silently discarded, unreferencing its model * if that point was bound to a named pair (hence, possibly destroying * the model if this was the last reference). * * The assignment is lazy so @ref1 could be not be present in @model. * Anyway, at the first access to this point an error will be raised * if the named pair is still missing. * * Since: 1.0 **/ void adg_dim_set_ref1_from_model(AdgDim *dim, AdgModel *model, const gchar *ref1) { AdgPoint *point = adg_point_new(); adg_point_set_pair_from_model(point, model, ref1); adg_dim_set_ref1(dim, point); adg_point_destroy(point); } /** * adg_dim_get_ref1: * @dim: an #AdgDim * * Gets the #AdgDim:ref1 point of @dim. * * The returned point is internally owned and must not be freed * or modified. Anyway it is not const because a call to * adg_point_update() with the returned value must be able to * modify the internal cache. * * Returns: (transfer none): the first reference point. * * Since: 1.0 **/ AdgPoint * adg_dim_get_ref1(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->ref1; } /** * adg_dim_set_ref2: * @dim: an #AdgDim * @ref2: the new point to use as second reference * * Sets the #AdgDim:ref2 property to @ref2. The old point * is silently discarded, unreferencing its model if that * point was bound to a named pair (hence, possibly destroying * the model if it was the last reference). * * @ref2 can be NULL, in which case * the point is destroyed. * * Since: 1.0 **/ void adg_dim_set_ref2(AdgDim *dim, const AdgPoint *ref2) { g_return_if_fail(ADG_IS_DIM(dim)); g_object_set(dim, "ref2", ref2, NULL); } /** * adg_dim_set_ref2_explicit: * @dim: an #AdgDim * @x: x coordinate of the second reference point * @y: y coordinate of the second reference point * * Sets the #AdgDim:ref2 property to the (@x, @y) explicit * coordinates. The old point is silently discarded, * unreferencing its model if that point was bound to a named * pair (hence, possibly destroying the model if this was the * last reference). * * Since: 1.0 **/ void adg_dim_set_ref2_explicit(AdgDim *dim, gdouble x, gdouble y) { AdgPoint *point = adg_point_new(); adg_point_set_pair_explicit(point, x, y); adg_dim_set_ref2(dim, point); adg_point_destroy(point); } /** * adg_dim_set_ref2_from_pair: * @dim: an #AdgDim * @ref2: the coordinates pair of the second reference point * * Convenient function to set the #AdgDim:ref2 property using a * pair instead of explicit coordinates. * * Since: 1.0 **/ void adg_dim_set_ref2_from_pair(AdgDim *dim, const CpmlPair *ref2) { g_return_if_fail(ref2 != NULL); adg_dim_set_ref2_explicit(dim, ref2->x, ref2->y); } /** * adg_dim_set_ref2_from_model: * @dim: an #AdgDim * @model: the source #AdgModel * @ref2: a named pair in @model * * Binds #AdgDim:ref2 to the @ref2 named pair of @model. If @model * is NULL, the point will be unset. In any * case, the old point is silently discarded, unreferencing its * model if that point was bound to a named pair (hence, possibly * destroying the model if this was the last reference). * * The assignment is lazy so @ref2 could be not be present in @model. * Anyway, at the first access to this point an error will be raised * if the named pair is still missing. * * Since: 1.0 **/ void adg_dim_set_ref2_from_model(AdgDim *dim, AdgModel *model, const gchar *ref2) { AdgPoint *point = adg_point_new(); adg_point_set_pair_from_model(point, model, ref2); adg_dim_set_ref2(dim, point); adg_point_destroy(point); } /** * adg_dim_get_ref2: * @dim: an #AdgDim * * Gets the #AdgDim:ref2 point of @dim. * * The returned point is internally owned and must not be freed * or modified. Anyway it is not const because a call to * adg_point_update() with the returned value must be able to * modify the internal cache. * * Returns: (transfer none): the second reference point. * * Since: 1.0 **/ AdgPoint * adg_dim_get_ref2(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->ref2; } /** * adg_dim_set_pos: * @dim: an #AdgDim * @pos: the new point to use as position * * Sets the #AdgDim:pos property of @dim to @pos. The old point * is silently discarded, unreferencing its model if that * point was bound to a named pair (hence, possibly destroying * the model if it was the last reference). * * @pos can be NULL, in which case the * point is destroyed. * * Since: 1.0 **/ void adg_dim_set_pos(AdgDim *dim, const AdgPoint *pos) { g_return_if_fail(ADG_IS_DIM(dim)); g_object_set(dim, "pos", pos, NULL); } /** * adg_dim_set_pos_explicit: * @dim: an #AdgDim * @x: x coordinate of the position * @y: y coordinate of the position * * Sets the #AdgDim:pos property to the (@x, @y) explicit * coordinates. The old point is silently discarded, * unreferencing its model if that point was bound to a named * pair (hence, possibly destroying the model if this was the * last reference). * * Since: 1.0 **/ void adg_dim_set_pos_explicit(AdgDim *dim, gdouble x, gdouble y) { AdgPoint *point = adg_point_new(); adg_point_set_pair_explicit(point, x, y); adg_dim_set_pos(dim, point); adg_point_destroy(point); } /** * adg_dim_set_pos_from_pair: * @dim: an #AdgDim * @pos: the coordinates pair of the position point * * Convenient function to set the #AdgDim:pos property using a * pair instead of explicit coordinates. * * Since: 1.0 **/ void adg_dim_set_pos_from_pair(AdgDim *dim, const CpmlPair *pos) { g_return_if_fail(pos != NULL); adg_dim_set_pos_explicit(dim, pos->x, pos->y); } /** * adg_dim_set_pos_from_model: * @dim: an #AdgDim * @model: the source #AdgModel * @pos: a named pair in @model * * Binds #AdgDim:pos to the @pos named pair of @model. If @model * is NULL, the point will be unset. In any * case, the old point is silently discarded, unreferencing its * model if that point was bound to a named pair (hence, * possibly destroying the model if this was the last reference). * * The assignment is lazy so @pos could be not be present in @model. * Anyway, at the first access to this point an error will be raised * if the named pair is still missing. * * Since: 1.0 **/ void adg_dim_set_pos_from_model(AdgDim *dim, AdgModel *model, const gchar *pos) { AdgPoint *point = adg_point_new(); adg_point_set_pair_from_model(point, model, pos); adg_dim_set_pos(dim, point); adg_point_destroy(point); } /** * adg_dim_get_pos: * @dim: an #AdgDim * * Gets the #AdgDim:pos point of @dim. * * The returned point is internally owned and must not be freed * or modified. Anyway it is not const because a call to * adg_point_update() with the returned value must be able to * modify the internal cache. * * Returns: (transfer none): the position point. * * Since: 1.0 **/ AdgPoint * adg_dim_get_pos(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->pos; } /** * adg_dim_set_level: * @dim: an #AdgDim * @level: the new level * * Sets a new level for this dimension. The level is used to * stack the quotes using a spacing value from dim_style * (specified in global space). * * Since: 1.0 **/ void adg_dim_set_level(AdgDim *dim, gdouble level) { AdgDimPrivate *data; g_return_if_fail(ADG_IS_DIM(dim)); data = adg_dim_get_instance_private(dim); data->level = level; g_object_notify((GObject *) dim, "level"); } /** * adg_dim_get_level: * @dim: an #AdgDim * * Gets the level of this dimension. * * Returns: the level value. * * Since: 1.0 **/ gdouble adg_dim_get_level(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), 0); data = adg_dim_get_instance_private(dim); return data->level; } /** * adg_dim_set_outside: * @dim: an #AdgDim * @outside: the new outside state * * Sets a new state for the #AdgDim:outside flag: check the property * documentation for further details. * * Since: 1.0 **/ void adg_dim_set_outside(AdgDim *dim, AdgThreeState outside) { g_return_if_fail(ADG_IS_DIM(dim)); if (_adg_set_outside(dim, outside)) g_object_notify((GObject *) dim, "outside"); } /** * adg_dim_get_outside: * @dim: an #AdgDim * * Gets the state of the #AdgDim:outside property: check the property * documentation for further details. * * Returns: the current flag state. * * Since: 1.0 **/ AdgThreeState adg_dim_get_outside(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), ADG_THREE_STATE_UNKNOWN); data = adg_dim_get_instance_private(dim); return data->outside; } /** * adg_dim_set_detached: * @dim: an #AdgDim * @detached: the new detached state * * Sets a new state for the #AdgDim:detached flag: check the property * documentation for further details. * * This is used only by dimensions where detaching has meaning. * In some cases, such as with #AdgRDim dimensions, this property is * not used. * * Since: 1.0 **/ void adg_dim_set_detached(AdgDim *dim, AdgThreeState detached) { g_return_if_fail(ADG_IS_DIM(dim)); if (_adg_set_detached(dim, detached)) g_object_notify((GObject *) dim, "detached"); } /** * adg_dim_get_detached: * @dim: an #AdgDim * * Gets the state of the #AdgDim:detached property: check the property * documentation for further details. * * Returns: the current flag state. * * Since: 1.0 **/ AdgThreeState adg_dim_get_detached(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), ADG_THREE_STATE_UNKNOWN); data = adg_dim_get_instance_private(dim); return data->detached; } /** * adg_dim_set_value: * @dim: an #AdgDim * @value: (allow-none): the value text * * Explicitely sets the text to use as value. If @value * is NULL or was never set, an automatic * text is calculated using the format specified in the current * #AdgDimStyle and getting its value by calling * the default_value virtual method. * * Inside the template string, the "<>" tag (or whatever specified * by the #AdgDimStyle:number-tag property) is substituted with the * string returned by default_value. * * Since: 1.0 **/ void adg_dim_set_value(AdgDim *dim, const gchar *value) { g_return_if_fail(ADG_IS_DIM(dim)); if (_adg_set_value(dim, value)) g_object_notify((GObject *) dim, "value"); } /** * adg_dim_get_value: * @dim: an #AdgDim * * Gets the value text. The string is internally owned and * must not be freed or modified. * * Returns: (transfer none): the value text. * * Since: 1.0 **/ const gchar * adg_dim_get_value(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->value; } /** * adg_dim_get_text: * @dim: an #AdgDim * @value: the raw value of the quote * * Gets the final text to show as nominal value into the quote. The string is * the same returned by adg_dim_get_value() with the tag properly expanded. * * The string substituted to the tag is formatted according to the * #AdgDimStyle:number-format and #AdgDimStyle:number-arguments properties. * See the #AdgDimStyle documentation for further details. * * Returns: (transfer full): the final text of the quote. * * Since: 1.0 **/ gchar * adg_dim_get_text(AdgDim *dim, gdouble value) { AdgDimStyle *dim_style; const gchar *format; const gchar *arguments; AdgDimReplaceData data; gchar *raw, *result; GRegex *regex; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); dim_style = adg_dim_get_dim_style(dim); if (dim_style == NULL) { dim_style = (AdgDimStyle *) adg_entity_style((AdgEntity *) dim, adg_dim_get_dim_dress(dim)); } format = adg_dim_style_get_number_format(dim_style); if (format == NULL) { return NULL; } arguments = adg_dim_style_get_number_arguments(dim_style); if (arguments == NULL) { return g_strdup(format); } /* Expand the values */ data.dim_style = dim_style; data.value = value; data.format = format; data.argument = arguments; data.regex = g_regex_new("(?NULL * as @min to disable it. * * Since: 1.0 **/ void adg_dim_set_min(AdgDim *dim, const gchar *min) { g_return_if_fail(ADG_IS_DIM(dim)); if (_adg_set_min(dim, min)) g_object_notify((GObject *) dim, "min"); } /** * adg_dim_get_min: * @dim: an #AdgDim * * Gets the minimum value text or NULL * on minimum value disabled. * * The string is internally owned and must not be freed or modified. * * Returns: (transfer none): the mimimum value text. * * Since: 1.0 **/ const gchar * adg_dim_get_min(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->min; } /** * adg_dim_set_max: * @dim: an #AdgDim * @max: (allow-none): the new maximum value * * Sets the maximum value. Use NULL * as @max to disable it. * * Since: 1.0 **/ void adg_dim_set_max(AdgDim *dim, const gchar *max) { g_return_if_fail(ADG_IS_DIM(dim)); if (_adg_set_max(dim, max)) g_object_notify((GObject *) dim, "max"); } /** * adg_dim_get_max: * @dim: an #AdgDim * * Gets the maximum value text or NULL * on maximum value disabled. * * The string is internally owned and must not be freed or modified. * * Returns: (transfer none): the maximum value text. * * Since: 1.0 **/ const gchar * adg_dim_get_max(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->max; } /** * adg_dim_get_quote: * @dim: an #AdgDim * * Gets the quote entity, if any. This function is valid only after * the #AdgDim implementation of the arrange() virtual method has * been called. * * The returned entity is owned by @dim and should not be * modified or freed. * * * This function is only useful in new dimension implementations. * * * Returns: (transfer none): the quote entity. * * Since: 1.0 **/ AdgAlignment * adg_dim_get_quote(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->quote.entity; } /** * adg_dim_quote_angle: * @dim: an #AdgDim * @angle: an angle (in radians) * * * This function is only useful in new dimension implementations. * * * Converts @angle accordling to the style of @dim. Any quote angle * should be validated by this method because every dimensioning * style has its own convention regardling the text rotation. * * Returns: the angle to use (always in radians). * * Since: 1.0 **/ gdouble adg_dim_quote_angle(AdgDim *dim, gdouble angle) { AdgDimClass *klass; g_return_val_if_fail(ADG_IS_DIM(dim), angle); klass = ADG_DIM_GET_CLASS(dim); if (klass->quote_angle == NULL) return angle; return klass->quote_angle(angle); } /** * adg_dim_has_geometry: * @dim: an #AdgDim * * * This function is only useful in new dimension implementations. * * * Checks if the geometry data of @dim has already been computed. * * Returns: TRUE if the geometry has already been computed, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_dim_has_geometry(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), FALSE); data = adg_dim_get_instance_private(dim); return data->geometry.computed; } /** * adg_dim_switch_geometry: * @dim: an #AdgDim * @computed: the new computed state * * * This function is only useful in new dimension implementations. * * * Sets the computed state of @dim to @computed. This is an internal flag that * keeps track of when the geometry data is up to date. * * Since: 1.0 **/ void adg_dim_switch_geometry(AdgDim *dim, gboolean computed) { AdgDimPrivate *data; g_return_if_fail(ADG_IS_DIM(dim)); data = adg_dim_get_instance_private(dim); data->geometry.computed = computed; } /** * adg_dim_get_geometry_notice: * @dim: an #AdgDim * * Gets the geometry message of @dim, i.e. a notice that explains why a * geometry computation has failed. This message can be used for debugging * purposes, e.g. to know why it is not possible to draw a dimension. * * Returns: the geometry notice or NULL on no notification. * * Since: 1.0 **/ const gchar * adg_dim_get_geometry_notice(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->geometry.notice; } /** * adg_dim_set_geometry_notice: * @dim: an #AdgDim * @notice: new notice message * * * This function is only useful in new dimension implementations. * * * Sets the geometry message of @dim to @notice whenever a geometry * computation has failed. This message can later be read with * adg_dim_get_geometry_notice(): see its documentation for further details. * * Since: 1.0 **/ void adg_dim_set_geometry_notice(AdgDim *dim, const gchar *notice) { AdgDimPrivate *data; g_return_if_fail(ADG_IS_DIM(dim)); data = adg_dim_get_instance_private(dim); if (data->geometry.notice != NULL) g_free(data->geometry.notice); data->geometry.notice = g_strdup(notice); } /** * adg_dim_geometry_missing: * @dim: an #AdgDim * @subject: what is missing * * * This function is only useful in new dimension implementations. * * * Wrapper around adg_dim_set_geometry_notice() that sets a default * notification message when a reference is missing. * * Since: 1.0 **/ void adg_dim_geometry_missing(AdgDim *dim, const gchar *subject) { gchar *notice; g_return_if_fail(subject != NULL); notice = g_strdup_printf(_("'%s' is missing"), subject); adg_dim_set_geometry_notice(dim, notice); g_free(notice); } /** - * adg_dim_geometry_coincidents: + * adg_dim_geometry_coincident: * @dim: an #AdgDim - * @subject: what is missing + * @first: the name of the first point + * @second: the name of the second point + * @pos: the coordinates of both points * * * This function is only useful in new dimension implementations. * * * Wrapper around adg_dim_set_geometry_notice() that sets a default * notification message when two references that must be different are * coincidents. * * Since: 1.0 **/ void adg_dim_geometry_coincident(AdgDim *dim, const gchar *first, const gchar *second, const CpmlPair *pos) { gchar *notice; g_return_if_fail(first != NULL); g_return_if_fail(second != NULL); g_return_if_fail(pos != NULL); notice = g_strdup_printf(_("'%s' and '%s' cannot be coincident (%lf, %lf)"), first, second, pos->x, pos->y); adg_dim_set_geometry_notice(dim, notice); g_free(notice); } /** * adg_dim_compute_geometry: * @dim: an #AdgDim * * * This function is only useful in new dimension implementations. * * * Updates the geometry data of @dim, i.e. a set of support data (coordinates, * offsets, shifts) needed in the arrange() phase to build up the entity. * * The data is cached, so further calls just return TRUE. Use * adg_entity_invalidate() to force a recomputation. * * Returns: TRUE if the geometry has already been computed, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_dim_compute_geometry(AdgDim *dim) { AdgDimPrivate *data; AdgDimClass *klass; g_return_val_if_fail(ADG_IS_DIM(dim), FALSE); data = adg_dim_get_instance_private(dim); if (data->geometry.computed) return TRUE; /* compute_geometry virtual method explicitely set to NULL means the * entity does not have any geometry data, so just set computed to TRUE */ klass = ADG_DIM_GET_CLASS(dim); if (klass->compute_geometry != NULL && ! klass->compute_geometry(dim)) return FALSE; data->geometry.computed = TRUE; return TRUE; } static void _adg_global_changed(AdgEntity *entity) { AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) entity); if (_ADG_OLD_ENTITY_CLASS->global_changed) _ADG_OLD_ENTITY_CLASS->global_changed(entity); if (data->quote.entity) adg_entity_global_changed((AdgEntity *) data->quote.entity); } static void _adg_local_changed(AdgEntity *entity) { AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) entity); if (_ADG_OLD_ENTITY_CLASS->local_changed) _ADG_OLD_ENTITY_CLASS->local_changed(entity); if (data->quote.entity) adg_entity_local_changed((AdgEntity *) data->quote.entity); } static void _adg_invalidate(AdgEntity *entity) { AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) entity); if (data->quote.value) { g_object_unref(data->quote.value); data->quote.value = NULL; } if (data->quote.entity) adg_entity_invalidate((AdgEntity *) data->quote.entity); if (data->ref1) adg_point_invalidate(data->ref1); if (data->ref2) adg_point_invalidate(data->ref2); if (data->pos) adg_point_invalidate(data->pos); data->geometry.computed = FALSE; if (data->geometry.notice != NULL) { g_free(data->geometry.notice); data->geometry.notice = NULL; } if (_ADG_OLD_ENTITY_CLASS->invalidate) _ADG_OLD_ENTITY_CLASS->invalidate(entity); } static void _adg_arrange(AdgEntity *entity) { AdgDim *dim; AdgDimPrivate *data; AdgEntity *quote_entity; AdgContainer *quote_container; AdgEntity *value_entity; AdgEntity *min_entity; AdgEntity *max_entity; const CpmlPair *shift; cairo_matrix_t map; dim = (AdgDim *) entity; data = adg_dim_get_instance_private(dim); /* Resolve the dim style */ if (data->dim_style == NULL) data->dim_style = (AdgDimStyle *) adg_entity_style(entity, data->dim_dress); if (data->quote.entity == NULL) data->quote.entity = g_object_new(ADG_TYPE_ALIGNMENT, "local-mix", ADG_MIX_NONE, "parent", dim, NULL); quote_entity = (AdgEntity *) data->quote.entity; quote_container = (AdgContainer *) data->quote.entity; if (data->quote.value == NULL) { AdgDimClass *klass; AdgDress dress; const gchar *tag; gchar *value; gchar *text; klass = ADG_DIM_GET_CLASS(dim); dress = adg_dim_style_get_value_dress(data->dim_style); tag = adg_dim_style_get_number_tag(data->dim_style); value = klass->default_value ? klass->default_value(dim) : NULL; data->quote.value = g_object_new(ADG_TYPE_BEST_TEXT, "local-mix", ADG_MIX_PARENT, "font-dress", dress, NULL); adg_container_add(quote_container, (AdgEntity *) data->quote.value); if (data->value) text = adg_string_replace(data->value, tag, value); else text = g_strdup(value); g_free(value); adg_textual_set_text(data->quote.value, text); g_free(text); } if (data->quote.min == NULL && data->min != NULL) { AdgDress dress = adg_dim_style_get_min_dress(data->dim_style); data->quote.min = g_object_new(ADG_TYPE_BEST_TEXT, "local-mix", ADG_MIX_PARENT, "font-dress", dress, NULL); adg_container_add(quote_container, (AdgEntity *) data->quote.min); adg_textual_set_text(data->quote.min, data->min); } if (data->quote.max == NULL && data->max != NULL) { AdgDress dress = adg_dim_style_get_max_dress(data->dim_style); data->quote.max = g_object_new(ADG_TYPE_BEST_TEXT, "local-mix", ADG_MIX_PARENT, "font-dress", dress, NULL); adg_container_add(quote_container, (AdgEntity *) data->quote.max); adg_textual_set_text(data->quote.max, data->max); } value_entity = (AdgEntity *) data->quote.value; min_entity = (AdgEntity *) data->quote.min; max_entity = (AdgEntity *) data->quote.max; shift = adg_dim_style_get_quote_shift(data->dim_style); adg_entity_set_global_map(quote_entity, adg_matrix_identity()); adg_entity_global_changed(quote_entity); cairo_matrix_init_translate(&map, 0, shift->y); adg_entity_set_global_map(value_entity, &map); adg_entity_arrange(value_entity); /* Limit values (min and max) */ if (min_entity != NULL || max_entity != NULL) { const CpmlPair *limits_shift; gdouble spacing; CpmlPair size; CpmlPair org_min, org_max; limits_shift = adg_dim_style_get_limits_shift(data->dim_style); spacing = adg_dim_style_get_limits_spacing(data->dim_style); size = adg_entity_get_extents(value_entity)->size; org_min.x = size.x + limits_shift->x; org_min.y = -size.y / 2 + limits_shift->y; org_max = org_min; if (min_entity && max_entity) { /* Prearrange the min entity to get its extents */ adg_entity_arrange(min_entity); size = adg_entity_get_extents(min_entity)->size; org_min.y += (size.y + spacing) / 2; org_max.y = org_min.y - size.y - spacing; } if (min_entity != NULL) { cairo_matrix_init_translate(&map, org_min.x, org_min.y); adg_entity_set_global_map(min_entity, &map); adg_entity_arrange(min_entity); } if (max_entity != NULL) { cairo_matrix_init_translate(&map, org_max.x, org_max.y); adg_entity_set_global_map(max_entity, &map); adg_entity_arrange(max_entity); } } } static gboolean _adg_compute_geometry(AdgDim *dim) { g_warning(_("AdgDim::compute_geometry not implemented for '%s'"), g_type_name(G_TYPE_FROM_INSTANCE(dim))); return FALSE; } static gchar * _adg_default_value(AdgDim *dim) { g_warning(_("AdgDim::default_value not implemented for '%s'"), g_type_name(G_TYPE_FROM_INSTANCE(dim))); return g_strdup("undef"); } static gdouble _adg_quote_angle(gdouble angle) { angle = cpml_angle(angle); if (angle > G_PI / 3 || angle <= -G_PI_4 * 3) angle = cpml_angle(angle + G_PI); return angle; } static gboolean _adg_set_outside(AdgDim *dim, AdgThreeState outside) { AdgDimPrivate *data; g_return_val_if_fail(adg_is_enum_value(outside, ADG_TYPE_THREE_STATE), FALSE); data = adg_dim_get_instance_private(dim); if (data->outside == outside) return FALSE; data->outside = outside; return TRUE; } static gboolean _adg_set_detached(AdgDim *dim, AdgThreeState detached) { AdgDimPrivate *data; g_return_val_if_fail(adg_is_enum_value(detached, ADG_TYPE_THREE_STATE), FALSE); data = adg_dim_get_instance_private(dim); if (data->detached == detached) return FALSE; data->detached = detached; return TRUE; } static gboolean _adg_set_value(AdgDim *dim, const gchar *value) { AdgDimPrivate *data = adg_dim_get_instance_private(dim); if (g_strcmp0(value, data->value) == 0) return FALSE; g_free(data->value); data->value = g_strdup(value); if (data->quote.value) { g_object_unref(data->quote.value); data->quote.value = NULL; } return TRUE; } static gboolean _adg_set_min(AdgDim *dim, const gchar *min) { AdgDimPrivate *data = adg_dim_get_instance_private(dim); if (g_strcmp0(min, data->min) == 0) return FALSE; g_free(data->min); data->min = g_strdup(min); if (data->quote.min) { g_object_unref(data->quote.min); data->quote.min = NULL; } return TRUE; } static gboolean _adg_set_max(AdgDim *dim, const gchar *max) { AdgDimPrivate *data = adg_dim_get_instance_private(dim); if (g_strcmp0(max, data->max) == 0) return FALSE; g_free(data->max); data->max = g_strdup(max); if (data->quote.max) { g_object_unref(data->quote.max); data->quote.max = NULL; } return TRUE; } static gboolean _adg_replace(const GMatchInfo *match_info, GString *result, gpointer user_data) { AdgDimReplaceData *data; gdouble value; gchar *format; gchar buffer[256]; data = (AdgDimReplaceData *) user_data; value = data->value; if (! adg_dim_style_convert(data->dim_style, &value, *data->argument)) { /* Conversion failed: invalid argument? */ g_return_val_if_reached(TRUE); return TRUE; } format = g_match_info_fetch(match_info, 0); /* This should never happen */ g_return_val_if_fail(format != NULL, TRUE); /* Consume the recently used argument */ ++ data->argument; g_ascii_formatd(buffer, 256, format, value); g_free(format); g_string_append(result, buffer); /* Set the valorized flag */ if (value != 0) { data->valorized = ADG_THREE_STATE_ON; } else if (data->valorized == ADG_THREE_STATE_UNKNOWN) { data->valorized = ADG_THREE_STATE_OFF; } return FALSE; } static gchar * _adg_text_expand(AdgDimReplaceData *data) { GString *result; const gchar *bog, *eog; gchar *string; AdgThreeState valorized; gssize len; valorized = ADG_THREE_STATE_UNKNOWN; result = g_string_new(""); eog = adg_unescaped_strchr(data->format, ')'); /* Expand eventual groups found in the same nesting level */ while ((bog = adg_unescaped_strchr(data->format, '(')) != NULL) { /* If eog precedes bog, it means that bog is in another nest */ if (eog != NULL && eog < bog) { break; } len = bog - data->format; /* Parse template before the bog */ data->valorized = ADG_THREE_STATE_UNKNOWN; string = g_regex_replace_eval(data->regex, data->format, len, 0, 0, _adg_replace, data, NULL); valorized = OR_3S(valorized, data->valorized); data->format += len+1; g_string_append(result, string); g_free(string); /* Recursively expand the group */ string = _adg_text_expand(data); valorized = OR_3S(valorized, data->valorized); g_string_append(result, string); g_free(string); /* Ensure there is a matching closing parenthesis */ if (*data->format != ')') { g_string_free(result, TRUE); g_return_val_if_reached(NULL); return NULL; } /* Skip the closing parenthesis */ ++ data->format; eog = adg_unescaped_strchr(data->format, ')'); } /* Expand until closing parenthesis (End Of Group) or '\0' */ len = eog == NULL ? strlen(data->format) : (eog - data->format); data->valorized = ADG_THREE_STATE_UNKNOWN; string = g_regex_replace_eval(data->regex, data->format, len, 0, 0, _adg_replace, data, NULL); data->format += len; g_string_append(result, string); g_free(string); /* Store the final valorized state */ valorized = OR_3S(valorized, data->valorized); data->valorized = valorized; /* Drop the result only if we are inside a group */ if (*data->format && valorized == ADG_THREE_STATE_OFF) { g_string_free(result, TRUE); return g_strdup(""); } return g_string_free(result, FALSE); } diff --git a/src/adg/adg-model.c b/src/adg/adg-model.c index 9c27dd22..d2d63d44 100644 --- a/src/adg/adg-model.c +++ b/src/adg/adg-model.c @@ -1,767 +1,767 @@ /* ADG - Automatic Drawing Generation * Copyright (C) 2007-2019 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-model * @short_description: The base class of the ADG model infrastructure * * A model is a conceptual representation of something. From an ADG * user point of view, it is a collection of data and rules that * defines how an object should be represented on a drawing. * * Because #AdgModel instances are only a conceptual idea, they are * not renderable (that is, #AdgModel is not derived from #AdgEntity). * Instead, it must be passed as subject to entities such as #AdgStroke * or #AdgHatch. * * The relationships between model and view are handled by dependencies: * whenever an #AdgModel changes (that is the #AdgModel::changed signal is * emitted), every dependency of the model (#AdgEntity instances) is * invalidated with adg_entity_invalidate(). * * To help the interaction between model and view another concept is * introduced: named pairs. This provides a way to abstract real values (the * coordinates stored in #CpmlPair) by accessing them using a string. To easily * the access of named pairs from the view, use #AdgPoint instead of #CpmlPair. * * Since: 1.0 **/ /** * AdgModel: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ /** * AdgModelClass: * @named_pair: virtual method that returns the #CpmlPair bound to a * given name. * @set_named_pair: signal for defining or undefining a new named pair. * @clear: signal for removing the internal cache data, if any. * @reset: signal used to redefine a model from scratch. * @add_dependency: signal for adding a new dependency. * @remove_dependency: signal used to remove an old dependency. * @changed: signal for emitting an #AdgModel::changed signal. * * * The default @named_pair implementation looks up the #CpmlPair in an internal * #GHashTable that uses the pair name as key and the #CpmlPair struct as value. * * The default @set_named_pair implementation can be used for either adding * (if the #CpmlPair is not NULL) or removing (if #CpmlPair * is NULL) an item from the named pairs hash table. * * The default handler for @clear signals does not do anything. * * The default @reset involves the clearing of the internal cache data * (done by emitting the #AdgModel::clear signal) and the destruction of the * internal named pair hash table. * * The default @add_dependency and @remove_dependency implementations add and * remove items from an internal #GSList of #AdgEntity. * * The default handler of the @changed signal calls adg_entity_invalidate() * on every dependency by using adg_model_foreach_dependency(). * * Since: 1.0 **/ /** * AdgDependencyFunc: * @model: the #AdgModel * @entity: the #AdgEntity dependent on @model * @user_data: a general purpose pointer * * Callback used by adg_model_foreach_dependency(). * * Since: 1.0 **/ /** * AdgNamedPairFunc: * @model: the #AdgModel * @name: the name of the named pair * @pair: an #CpmlPair * @user_data: a general purpose pointer * * Callback used by adg_model_foreach_named_pair(). * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-model.h" #include "adg-model-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_model_parent_class) G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(AdgModel, adg_model, G_TYPE_OBJECT) enum { PROP_0, PROP_DEPENDENCY }; enum { ADD_DEPENDENCY, REMOVE_DEPENDENCY, SET_NAMED_PAIR, CLEAR, RESET, CHANGED, LAST_SIGNAL }; static void _adg_dispose (GObject *object); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void _adg_add_dependency (AdgModel *model, AdgEntity *entity); static void _adg_remove_dependency (AdgModel *model, AdgEntity *entity); static const CpmlPair * _adg_named_pair (AdgModel *model, const gchar *name); static void _adg_reset (AdgModel *model); static void _adg_set_named_pair (AdgModel *model, const gchar *name, const CpmlPair *pair); static void _adg_changed (AdgModel *model); static void _adg_named_pair_wrapper (gpointer key, gpointer value, gpointer user_data); static void _adg_invalidate_wrapper (AdgModel *model, AdgEntity *entity, gpointer user_data); static guint _adg_signals[LAST_SIGNAL] = { 0 }; static void adg_model_class_init(AdgModelClass *klass) { GObjectClass *gobject_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->set_property = _adg_set_property; klass->add_dependency = _adg_add_dependency; klass->remove_dependency = _adg_remove_dependency; klass->named_pair = _adg_named_pair; klass->set_named_pair = _adg_set_named_pair; klass->clear = NULL; klass->reset = _adg_reset; klass->changed = _adg_changed; param = g_param_spec_object("dependency", P_("Dependency"), P_("Can be used to add a new dependency from this model (this entity will be invalidated on model changed)"), ADG_TYPE_ENTITY, G_PARAM_WRITABLE); g_object_class_install_property(gobject_class, PROP_DEPENDENCY, param); /** * AdgModel::add-dependency: * @model: an #AdgModel * @entity: an #AdgEntity that depends on @model * * Adds @entity to @model. After that @entity will depend on @model, * that is #AdgModel::changed on @model will invalidate @entity. * * Since: 1.0 **/ _adg_signals[ADD_DEPENDENCY] = g_signal_new("add-dependency", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AdgModelClass, add_dependency), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, ADG_TYPE_ENTITY); /** * AdgModel::remove-dependency: * @model: an #AdgModel * @entity: the #AdgEntity that does not depend on @model anymore * * Removes the @entity from @model, that is @entity will not depend * on @model anymore. * * Since: 1.0 **/ _adg_signals[REMOVE_DEPENDENCY] = g_signal_new("remove-dependency", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AdgModelClass, remove_dependency), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, ADG_TYPE_ENTITY); /** * AdgModel::set-named-pair: * @model: an #AdgModel * @name: an arbitrary name * @pair: an #CpmlPair * * Adds, updates or deletes a named pair, accordling to the given * parameters. * * If @pair is NULL, the @name named pair is * searched and deleted. If it is not found, a warning is raised. * * Otherwise, the @name named pair is searched: if it is found, * its data are updated with @pair. If it is not found, a new * named pair is created using @name and @pair. * * Since: 1.0 **/ _adg_signals[SET_NAMED_PAIR] = g_signal_new("set-named-pair", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AdgModelClass, set_named_pair), NULL, NULL, adg_marshal_VOID__STRING_POINTER, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_POINTER); /** * AdgModel::clear: * @model: an #AdgModel * * * This signal is only useful in model implementations. * * * Removes any information from @model cached by the implementation * code. Useful to force a recomputation of the cache when something * in the model has changed. * * Since: 1.0 **/ _adg_signals[CLEAR] = g_signal_new("clear", ADG_TYPE_MODEL, G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET(AdgModelClass, clear), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * AdgModel::reset: * @model: an #AdgModel * * Resets the state of @model by destroying any named pair * associated to it. This step also involves the emission of the * #AdgModel::clear signal. * * This signal is intended to be used while redefining the model. * A typical usage would be in these terms: * * * adg_model_reset(model); * // Definition of model. This also requires the redefinition of * // the named pairs because the old ones have been destroyed. * ... * adg_model_changed(model); * * * Since: 1.0 **/ _adg_signals[RESET] = g_signal_new("reset", ADG_TYPE_MODEL, G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET(AdgModelClass, reset), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * AdgModel::changed: * @model: an #AdgModel * * Notificates that the model has changed. By default, all the * dependent entities are invalidated. * * Since: 1.0 **/ _adg_signals[CHANGED] = g_signal_new("changed", ADG_TYPE_MODEL, G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET(AdgModelClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void adg_model_init(AdgModel *model) { AdgModelPrivate *data = adg_model_get_instance_private(model); data->dependencies = NULL; } static void _adg_dispose(GObject *object) { static gboolean is_disposed = FALSE; if (G_UNLIKELY(!is_disposed)) { AdgModel *model = (AdgModel *) object; AdgModelPrivate *data = adg_model_get_instance_private(model); AdgEntity *entity; /* Remove all the dependencies: this will emit a * "remove-dependency" signal for every dependency, dropping * all references from entities to this model */ while (data->dependencies != NULL) { entity = (AdgEntity *) data->dependencies->data; adg_model_remove_dependency(model, entity); } g_signal_emit(model, _adg_signals[RESET], 0); } if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_DEPENDENCY: g_signal_emit(object, _adg_signals[ADD_DEPENDENCY], 0, g_value_get_object(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); } } /** * adg_model_add_dependency: * @model: an #AdgModel * @entity: an #AdgEntity * * * This function is only useful in entity implementations. * * * Emits a #AdgModel::add-dependency signal on @model passing @entity * as argument. This will add a reference to @entity owned by @model. * * Since: 1.0 **/ void adg_model_add_dependency(AdgModel *model, AdgEntity *entity) { g_return_if_fail(ADG_IS_MODEL(model)); g_return_if_fail(ADG_IS_ENTITY(entity)); g_object_set(model, "dependency", entity, NULL); } /** * adg_model_remove_dependency: * @model: an #AdgModel * @entity: an #AdgEntity * * * This function is only useful in entity implementations. * * * Emits a #AdgModel::remove-dependency signal on @model passing * @entity as argument. @entity must be inside @model. * * Note that @model will own a reference to @entity and it * may be the last reference held: this means removing an entity * from the model can destroy it. * * Since: 1.0 **/ void adg_model_remove_dependency(AdgModel *model, AdgEntity *entity) { g_return_if_fail(ADG_IS_MODEL(model)); g_return_if_fail(ADG_IS_ENTITY(entity)); g_signal_emit(model, _adg_signals[REMOVE_DEPENDENCY], 0, entity); } /** * adg_model_get_dependencies: * @model: an #AdgModel - *. + * * Gets the list of entities dependending on @model. This list * is owned by @model and must not be modified or freed. * * Returns: (transfer none) (element-type Adg.Entity): a #GSList of dependencies or NULL on error. * * Since: 1.0 **/ const GSList * adg_model_get_dependencies(AdgModel *model) { AdgModelPrivate *data; g_return_val_if_fail(ADG_IS_MODEL(model), NULL); data = adg_model_get_instance_private(model); return data->dependencies; } /** * adg_model_foreach_dependency: * @model: an #AdgModel * @callback: (scope call): the entity callback * @user_data: general purpose user data passed "as is" to @callback * * Invokes @callback on each entity linked to @model. * * Since: 1.0 **/ void adg_model_foreach_dependency(AdgModel *model, AdgDependencyFunc callback, gpointer user_data) { AdgModelPrivate *data; GSList *dependency; AdgEntity *entity; g_return_if_fail(ADG_IS_MODEL(model)); g_return_if_fail(callback != NULL); data = adg_model_get_instance_private(model); dependency = data->dependencies; while (dependency) { entity = dependency->data; if (entity != NULL && ADG_IS_ENTITY(entity)) callback(model, entity, user_data); dependency = dependency->next; } } /** * adg_model_set_named_pair: * @model: an #AdgModel * @name: the name to associate to the pair * @pair: the #CpmlPair * * * This function is only useful in model definitions, such as * inside an #AdgTrailCallback function or while constructing * an #AdgPath instance. * * * Emits a #AdgModel::set-named-pair signal on @model passing * @name and @pair as arguments. * * Since: 1.0 **/ void adg_model_set_named_pair(AdgModel *model, const gchar *name, const CpmlPair *pair) { g_return_if_fail(ADG_IS_MODEL(model)); g_return_if_fail(name != NULL); g_signal_emit(model, _adg_signals[SET_NAMED_PAIR], 0, name, pair); } /** * adg_model_set_named_pair_explicit: * @model: an #AdgModel * @name: the name to associate to the pair * @x: the x coordinate of the point * @y: the y coordinate of the point * * * This function is only useful in model definitions, such as * inside an #AdgTrailCallback function or while constructing * an #AdgPath instance. * * * Convenient wrapper on adg_model_set_named_pair() that accepts * explicit coordinates. * * Since: 1.0 **/ void adg_model_set_named_pair_explicit(AdgModel *model, const gchar *name, gdouble x, gdouble y) { CpmlPair pair; pair.x = x; pair.y = y; adg_model_set_named_pair(model, name, &pair); } /** * adg_model_get_named_pair: * @model: an #AdgModel * @name: the name of the pair to get * * Gets the @name named pair associated to @model. The returned * pair is owned by @model and must not be modified or freed. * * Returns: the requested #CpmlPair or NULL if not found. * * Since: 1.0 **/ const CpmlPair * adg_model_get_named_pair(AdgModel *model, const gchar *name) { AdgModelClass *klass; g_return_val_if_fail(ADG_IS_MODEL(model), NULL); g_return_val_if_fail(name != NULL, NULL); klass = ADG_MODEL_GET_CLASS(model); if (klass->named_pair == NULL) return NULL; return klass->named_pair(model, name); } /** * adg_model_foreach_named_pair: * @model: an #AdgModel * @callback: (scope call): the named pair callback * @user_data: general purpose user data passed "as is" to @callback * * Invokes @callback for each named pair set on @model. This can * be used, for example, to retrieve all the named pairs of a @model * or to duplicate a transformed version of every named pair. * * Since: 1.0 **/ void adg_model_foreach_named_pair(AdgModel *model, AdgNamedPairFunc callback, gpointer user_data) { AdgModelPrivate *data; AdgWrapperHelper helper; g_return_if_fail(ADG_IS_MODEL(model)); g_return_if_fail(callback != NULL); data = adg_model_get_instance_private(model); if (data->named_pairs == NULL) return; helper.callback = callback; helper.model = model; helper.user_data = user_data; g_hash_table_foreach(data->named_pairs, _adg_named_pair_wrapper, &helper); } /** * adg_model_clear: * @model: an #AdgModel * * * This function is only useful new model implementations. * * * Emits the #AdgModel::clear signal on @model. * * Since: 1.0 **/ void adg_model_clear(AdgModel *model) { g_return_if_fail(ADG_IS_MODEL(model)); g_signal_emit(model, _adg_signals[CLEAR], 0); } /** * adg_model_reset: * @model: an #AdgModel * * Emits the #AdgModel::reset signal on @model. * * Since: 1.0 **/ void adg_model_reset(AdgModel *model) { g_return_if_fail(ADG_IS_MODEL(model)); g_signal_emit(model, _adg_signals[RESET], 0); } /** * adg_model_changed: * @model: an #AdgModel * * * This function is only useful in entity implementations. * * * Emits the #AdgModel::changed signal on @model. * * Since: 1.0 **/ void adg_model_changed(AdgModel *model) { g_return_if_fail(ADG_IS_MODEL(model)); g_signal_emit(model, _adg_signals[CHANGED], 0); } static void _adg_add_dependency(AdgModel *model, AdgEntity *entity) { AdgModelPrivate *data; /* Do not add NULL values */ if (entity == NULL) return; data = adg_model_get_instance_private(model); /* The prepend operation is more efficient */ data->dependencies = g_slist_prepend(data->dependencies, entity); g_object_ref(entity); } static void _adg_remove_dependency(AdgModel *model, AdgEntity *entity) { AdgModelPrivate *data = adg_model_get_instance_private(model); GSList *node = g_slist_find(data->dependencies, entity); if (node == NULL) { g_warning(_("%s: attempting to remove the nonexistent dependency " "on the entity with type %s from a model of type %s"), G_STRLOC, g_type_name(G_OBJECT_TYPE(entity)), g_type_name(G_OBJECT_TYPE(model))); return; } data->dependencies = g_slist_delete_link(data->dependencies, node); g_object_unref(entity); } static void _adg_reset(AdgModel *model) { AdgModelPrivate *data = adg_model_get_instance_private(model); adg_model_clear(model); if (data->named_pairs) { g_hash_table_destroy(data->named_pairs); data->named_pairs = NULL; } } static void _adg_set_named_pair(AdgModel *model, const gchar *name, const CpmlPair *pair) { AdgModelPrivate *data = adg_model_get_instance_private(model); GHashTable **hash = &data->named_pairs; gchar *key; CpmlPair *value; if (pair == NULL) { /* Delete mode: raise a warning if @name is not found */ if (*hash == NULL || !g_hash_table_remove(*hash, name)) g_warning(_("%s: attempting to remove nonexistent '%s' named pair"), G_STRLOC, name); return; } /* Insert or update mode */ key = g_strdup(name); value = cpml_pair_dup(pair); if (*hash == NULL) *hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); g_hash_table_insert(*hash, key, value); } static const CpmlPair * _adg_named_pair(AdgModel *model, const gchar *name) { AdgModelPrivate *data = adg_model_get_instance_private(model); if (data->named_pairs == NULL) return NULL; return g_hash_table_lookup(data->named_pairs, name); } static void _adg_changed(AdgModel *model) { /* Invalidate all the entities dependent on this model */ adg_model_foreach_dependency(model, _adg_invalidate_wrapper, NULL); } static void _adg_named_pair_wrapper(gpointer key, gpointer value, gpointer user_data) { const gchar *name; CpmlPair *pair; AdgWrapperHelper *helper; name = key; pair = value; helper = user_data; helper->callback(helper->model, name, pair, helper->user_data); } static void _adg_invalidate_wrapper(AdgModel *model, AdgEntity *entity, gpointer user_data) { adg_entity_invalidate(entity); } diff --git a/src/adg/adg-param-dress.c b/src/adg/adg-param-dress.c index f6eae824..0ed3ca5b 100644 --- a/src/adg/adg-param-dress.c +++ b/src/adg/adg-param-dress.c @@ -1,166 +1,168 @@ /* ADG - Automatic Drawing Generation * Copyright (C) 2007-2019 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-param-dress * @Section_Id:AdgParamDress * @title: AdgParamDress * @short_description: Metadata for dress specification * * The %ADG_TYPE_PARAM_DRESS type is a parameter specification * compatible with %G_TYPE_PARAM_ENUM that provides additional * validation: it rejects values that are incompatibles (that * is, that are not related) with the current one. Check the * adg_dress_are_related() documentation for details on what * related means. * * Internally, the value setting is performed by calling the * adg_dress_set() API. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-dress.h" #include "adg-param-dress.h" +#define ADG_PARAM_SPEC_DRESS(pspec) (G_TYPE_CHECK_INSTANCE_CAST((pspec), ADG_TYPE_PARAM_DRESS, AdgParamSpecDress)) + typedef struct _AdgParamSpecDress AdgParamSpecDress; struct _AdgParamSpecDress { GParamSpecEnum parent; AdgDress source_dress; }; static void _adg_param_dress_init (GParamSpec *pspec); static void _adg_param_dress_set_default (GParamSpec *pspec, GValue *value); static gboolean _adg_param_dress_validate (GParamSpec *pspec, GValue *value); static gint _adg_param_dress_cmp (GParamSpec *pspec, const GValue *value1, const GValue *value2); GType adg_param_dress_get_type(void) { static GType type = 0; if (G_UNLIKELY(type == 0)) { /* const */ GParamSpecTypeInfo pspec_info = { sizeof(AdgParamSpecDress), /* instance_size */ 0, /* n_preallocs */ _adg_param_dress_init, G_TYPE_INVALID, /* value_type */ NULL, /* finalize */ _adg_param_dress_set_default, _adg_param_dress_validate, _adg_param_dress_cmp, }; pspec_info.value_type = ADG_TYPE_DRESS; type = g_param_type_register_static(g_intern_static_string("AdgParamDress"), &pspec_info); } return type; } /** * adg_param_spec_dress: * @name: canonical name * @nick: nickname of the param * @blurb: brief desciption * @dress: the #AdgDress dress * @flags: a combination of #GParamFlags * * Creates a param spec to hold a dress value. This is similar to * g_param_spec_enum() but rejects a new dress value if it is not * related with the old one. The setting is performed via * adg_dress_set(), so check its documentation for details. * * Returns: (transfer full): the newly allocated #GParamSpec. * * Since: 1.0 **/ GParamSpec * adg_param_spec_dress(const gchar *name, const gchar *nick, const gchar *blurb, AdgDress dress, GParamFlags flags) { AdgParamSpecDress *dspec; dspec = g_param_spec_internal(ADG_TYPE_PARAM_DRESS, name, nick, blurb, flags); dspec->source_dress = dress; return (GParamSpec *) dspec; } static void _adg_param_dress_init(GParamSpec *pspec) { AdgParamSpecDress *dspec = ADG_PARAM_SPEC_DRESS(pspec); dspec->source_dress = ADG_DRESS_UNDEFINED; } static void _adg_param_dress_set_default(GParamSpec *pspec, GValue *value) { value->data[0].v_long = ADG_PARAM_SPEC_DRESS(pspec)->source_dress; } static gboolean _adg_param_dress_validate(GParamSpec *pspec, GValue *value) { AdgParamSpecDress *dspec; AdgDress *dress; AdgDress wanted_dress; dspec = ADG_PARAM_SPEC_DRESS(pspec); dress = (AdgDress *) &value->data[0].v_long; wanted_dress = *dress; /* Fallback to the source dress, returned in case of errors */ *dress = dspec->source_dress; /* This method will fail (that is, it leaves *dress untouched) * if the current *dress value (source_dress) and wanted_dress * are not related */ adg_dress_set(dress, wanted_dress); return *dress != wanted_dress; } static gint _adg_param_dress_cmp(GParamSpec *pspec, const GValue *value1, const GValue *value2) { glong v1 = value1->data[0].v_long; glong v2 = value2->data[0].v_long; return v1 < v2 ? -1 : (v1 > v2); } diff --git a/src/adg/adg-param-dress.h b/src/adg/adg-param-dress.h index e7ea5db1..a8d14a53 100644 --- a/src/adg/adg-param-dress.h +++ b/src/adg/adg-param-dress.h @@ -1,47 +1,46 @@ /* ADG - Automatic Drawing Generation * Copyright (C) 2007-2019 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_PARAM_DRESS_H__ #define __ADG_PARAM_DRESS_H__ G_BEGIN_DECLS #define ADG_TYPE_PARAM_DRESS (adg_param_dress_get_type()) -#define ADG_PARAM_SPEC_DRESS(pspec) (G_TYPE_CHECK_INSTANCE_CAST((pspec), ADG_TYPE_PARAM_DRESS, AdgParamSpecDress)) #define ADG_IS_PARAM_DRESS(pspec) (G_TYPE_CHECK_INSTANCE_TYPE((pspec), ADG_TYPE_PARAM_DRESS)) GType adg_param_dress_get_type (void); GParamSpec * adg_param_spec_dress (const gchar *name, const gchar *nick, const gchar *blurb, AdgDress dress, GParamFlags flags); G_END_DECLS #endif /* __ADG_PARAM_DRESS_H__ */