/* dia-canvas.c * Copyright (C) 2000, 2001 James Henstridge, Arjan Molenaar * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include "dia-canvas.h" #include "dia-undo.h" #include "dia-canvas-group.h" #include "dia-canvas-i18n.h" #include "diamarshal.h" #include "../config.h" /* This is a nice place to include the marshal functions... */ #include "diamarshal.c" //#define UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE + 50) #define UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE) enum { EXTENTS_CHANGED, REDRAW_VIEW, UNDO, LAST_SIGNAL }; enum { PROP_SNAP_TO_GRID = 1, PROP_STATIC_EXTENTS, PROP_EXTENTS, PROP_ALLOW_STATE_REQUESTS, PROP_ALLOW_UNDO, PROP_GRID_INT_X, PROP_GRID_INT_Y, PROP_GRID_OFS_X, PROP_GRID_OFS_Y, PROP_GRID_COLOR, PROP_GRID_BG, PROP_PANGO_LAYOUT }; /* DiaCanvas static (private) functions. */ static void dia_canvas_init (DiaCanvas *canvas); static void dia_canvas_class_init (DiaCanvasClass *klass); static void dia_canvas_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void dia_canvas_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void dia_canvas_dispose (GObject *object); static void dia_canvas_finalize (GObject *object); static void dia_real_canvas_update (DiaCanvas *canvas); static GObjectClass *parent_class = NULL; static guint canvas_signals[LAST_SIGNAL] = { 0 }; GType dia_canvas_get_type (void) { static GType object_type = 0; if (!object_type) { static const GTypeInfo object_info = { sizeof (DiaCanvasClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) dia_canvas_class_init, (GClassFinalizeFunc) NULL, (gconstpointer) NULL, /* class_data */ sizeof (DiaCanvas), (guint16) 0, /* n_preallocs */ (GInstanceInitFunc) dia_canvas_init, }; object_type = g_type_register_static (G_TYPE_OBJECT, "DiaCanvas", &object_info, 0); } return object_type; } static void dia_canvas_class_init (DiaCanvasClass *klass) { GObjectClass *object_class; object_class = (GObjectClass*) klass; parent_class = g_type_class_peek_parent (klass); object_class->dispose = dia_canvas_dispose; object_class->finalize = dia_canvas_finalize; object_class->set_property = dia_canvas_set_property; object_class->get_property = dia_canvas_get_property; canvas_signals[EXTENTS_CHANGED] = g_signal_new ("extents_changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(DiaCanvasClass, extents_changed), NULL, NULL, dia_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER /* DiaRectangle */); //canvas_signals[MOVE] = // g_signal_new ("move", // G_TYPE_FROM_CLASS (object_class), // G_SIGNAL_RUN_FIRST, // G_STRUCT_OFFSET(DiaCanvasClass, move), // NULL, NULL, // dia_marshal_VOID__DOUBLE_DOUBLE, // G_TYPE_NONE, 2, // G_TYPE_DOUBLE, G_TYPE_DOUBLE); canvas_signals[REDRAW_VIEW] = g_signal_new ("redraw_view", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(DiaCanvasClass, redraw_view), NULL, NULL, dia_marshal_VOID__VOID, G_TYPE_NONE, 0); canvas_signals[UNDO] = g_signal_new ("undo", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(DiaCanvasClass, undo), NULL, NULL, dia_marshal_VOID__VOID, G_TYPE_NONE, 0); g_object_class_install_property (object_class, PROP_SNAP_TO_GRID, g_param_spec_boolean ("snap_to_grid", _("Snap to the grid"), _("Turn snap to grid option on and off."), FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_STATIC_EXTENTS, g_param_spec_boolean ("static_extents", _("Static extents"), _("Should the canvas extents be static or change depending on the contents."), FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_EXTENTS, g_param_spec_boxed ("extents", _("Set extents"), _("Set extents"), DIA_TYPE_RECTANGLE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_ALLOW_STATE_REQUESTS, g_param_spec_boolean ("allow_state_requests", _("Allow state requests"), _("Let canvas items query their visual state (selected, focused, grabbed)."), TRUE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_ALLOW_UNDO, g_param_spec_boolean ("allow_undo", _("Allow undo"), _("Save item properties in order to allow the user to undo them."), FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_GRID_INT_X, g_param_spec_double ("grid_int_x", _("Grid X interval"), _("Grid X interval"), 0.0, G_MAXDOUBLE, 10.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_GRID_INT_Y, g_param_spec_double ("grid_int_y", _("Grid Y interval"), _("Grid Y interval"), 0.0, G_MAXDOUBLE, 10.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_GRID_OFS_X, g_param_spec_double ("grid_ofs_x", _("Grid X offset"), _("Grid X offset"), 0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_GRID_OFS_Y, g_param_spec_double ("grid_ofs_y", _("Grid Y offset"), _("Grid Y offset"), 0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_GRID_COLOR, g_param_spec_ulong ("grid_color", _("Grid foreground color"), _("Grid foreground color"), 0, G_MAXULONG, DIA_COLOR (0,0,128), G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_GRID_BG, g_param_spec_ulong ("grid_bg", _("Grid background color"), _("Grid background color"), 0, G_MAXULONG, DIA_COLOR (200,200,200), G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_PANGO_LAYOUT, g_param_spec_object ("pango_layout", _("A newly created pango layout"), _("A newly created pango layout"), PANGO_TYPE_LAYOUT, G_PARAM_READABLE)); klass->extents_changed = NULL; //klass->move = NULL; klass->update = dia_real_canvas_update; klass->undo = NULL; } static void dia_canvas_init (DiaCanvas *canvas) { canvas->static_extents = FALSE; canvas->snap_to_grid = FALSE; //canvas->first_move = TRUE; canvas->extents.left = 0.0; canvas->extents.top = 0.0; canvas->extents.right = 0.0; canvas->extents.bottom = 0.0; canvas->root = g_object_new (DIA_TYPE_CANVAS_GROUP, NULL); canvas->root->canvas = canvas; canvas->interval_x = 10.0; canvas->interval_y = 10.0; canvas->offset_x = 0.0; canvas->offset_y = 0.0; canvas->grid_color = DIA_COLOR (0,0,128); canvas->grid_bg = DIA_COLOR (255, 255, 255); canvas->solver = dia_solver_new (); canvas->idle_id = 0; canvas->allow_state_requests = TRUE; canvas->allow_undo = FALSE; //canvas->in_undo = FALSE; canvas->undo_manager = g_object_new(dia_undo_get_type(), NULL); } static void dia_canvas_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { DiaCanvas *canvas = (DiaCanvas*) object; switch (property_id) { case PROP_SNAP_TO_GRID: //g_object_freeze_notify (object); dia_canvas_set_snap_to_grid (canvas, g_value_get_boolean (value)); //g_object_thaw_notify (object); break; case PROP_STATIC_EXTENTS: //g_object_freeze_notify (object); dia_canvas_set_static_extents (canvas, g_value_get_boolean (value)); //g_object_thaw_notify (object); break; case PROP_EXTENTS: g_object_freeze_notify (object); dia_canvas_set_extents (canvas, (DiaRectangle*)g_value_get_boxed (value)); g_object_thaw_notify (object); break; case PROP_ALLOW_STATE_REQUESTS: canvas->allow_state_requests = g_value_get_boolean (value); break; case PROP_ALLOW_UNDO: canvas->allow_undo = g_value_get_boolean (value); break; case PROP_GRID_INT_X: canvas->interval_x = g_value_get_double (value); dia_canvas_redraw_views (canvas); break; case PROP_GRID_INT_Y: canvas->interval_y = g_value_get_double (value); dia_canvas_redraw_views (canvas); break; case PROP_GRID_OFS_X: canvas->offset_x = g_value_get_double (value); dia_canvas_redraw_views (canvas); break; case PROP_GRID_OFS_Y: canvas->offset_y = g_value_get_double (value); dia_canvas_redraw_views (canvas); break; case PROP_GRID_COLOR: canvas->grid_color = g_value_get_ulong (value); dia_canvas_redraw_views (canvas); break; case PROP_GRID_BG: canvas->grid_bg = g_value_get_ulong (value); dia_canvas_redraw_views (canvas); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void dia_canvas_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { DiaCanvas *canvas = (DiaCanvas*) object; switch (property_id) { case PROP_SNAP_TO_GRID: g_value_set_boolean (value, canvas->snap_to_grid); break; case PROP_STATIC_EXTENTS: g_value_set_boolean (value, canvas->static_extents); break; case PROP_EXTENTS: g_value_set_boxed (value, &canvas->extents); break; case PROP_ALLOW_STATE_REQUESTS: g_value_set_boolean (value, canvas->allow_state_requests); break; case PROP_ALLOW_UNDO: g_value_set_boolean (value, canvas->allow_undo); break; case PROP_GRID_INT_X: g_value_set_double (value, canvas->interval_x); break; case PROP_GRID_INT_Y: g_value_set_double (value, canvas->interval_y); break; case PROP_GRID_OFS_X: g_value_set_double (value, canvas->offset_x); break; case PROP_GRID_OFS_Y: g_value_set_double (value, canvas->offset_y); break; case PROP_GRID_COLOR: g_value_set_ulong (value, canvas->grid_color); break; case PROP_GRID_BG: g_value_set_ulong (value, canvas->grid_bg); break; case PROP_PANGO_LAYOUT: g_value_set_object (value, dia_canvas_get_pango_layout()); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void dia_canvas_dispose (GObject *object) { DiaCanvas *canvas = (DiaCanvas*) object; #ifdef ENABLE_DEBUG g_message (G_STRLOC": begin. idle_id = %d", canvas->idle_id); #endif canvas->allow_undo = FALSE; /* Now get rid of undo and redo information. */ if (canvas->undo_manager) { g_object_unref (canvas->undo_manager); canvas->undo_manager = NULL; } /* Remove idle handler, we set the idle_id to -1, so it won't * be set again. */ if (canvas->idle_id) { g_source_remove (canvas->idle_id); canvas->idle_id = -1; } /* First dispose the canvas items. */ if (canvas->root) { canvas->root->canvas = NULL; g_object_unref (G_OBJECT (canvas->root)); canvas->root = NULL; } if (canvas->solver) { g_object_unref (G_OBJECT (canvas->solver)); canvas->solver = NULL; } /* Do this one last, so we're sure no canvas item try to update. */ #ifdef ENABLE_DEBUG g_message (G_STRLOC": idle_id = %d", canvas->idle_id); #endif parent_class->dispose (object); #ifdef ENABLE_DEBUG g_message (G_STRLOC": done."); #endif } static void dia_canvas_finalize (GObject *object) { DiaCanvas *canvas = (DiaCanvas*) object; #ifdef ENABLE_DEBUG g_message ("%s: begin.", G_STRLOC); #endif /* First dispose the canvas items. */ /*if (canvas->root) { canvas->root->canvas = NULL; g_object_unref (G_OBJECT (canvas->root)); canvas->root = NULL; }*/ /*if (canvas->solver) { g_object_unref (G_OBJECT (canvas->solver)); canvas->solver = NULL; }*/ parent_class->finalize (object); #ifdef ENABLE_DEBUG g_message (G_STRLOC": done."); #endif } /** * dia_real_canvas_update: * @canvas: * * The update formula consists of 3 steps: * 1. Resolve the constraints. * 2. Invoke the objects own updater. **/ static void dia_real_canvas_update (DiaCanvas *canvas) { //dia_canvas_item_update_now (canvas->root); dia_canvas_resolve_now (canvas); dia_canvas_item_update_now (canvas->root); if (!canvas->static_extents && canvas->root) dia_canvas_set_extents (canvas, &canvas->root->bounds); } static gboolean idle_handler (gpointer data) { DiaCanvas *canvas; g_assert (DIA_IS_CANVAS (data)); /* TODO: create mutex. */ // GDK_THREADS_ENTER (); canvas = DIA_CANVAS (data); DIA_CANVAS_CLASS (DIA_CANVAS_GET_CLASS (canvas))->update (canvas); /* Reset idle id */ canvas->idle_id = 0; // GDK_THREADS_LEAVE (); return FALSE; } /** * dia_canvas_new: * * Create a new canvas. * * Return value: A new canvas. **/ DiaCanvas* dia_canvas_new (void) { DiaCanvas *canvas = g_object_new(DIA_TYPE_CANVAS, NULL); g_assert (canvas != NULL); return canvas; } /** * dia_canvas_set_extents: * @canvas: * @extents: new extents for the canvas. * * Set the boundries for the canvas. Use this in combination with the * "static_extents" option. **/ void dia_canvas_set_extents (DiaCanvas *canvas, const DiaRectangle *extents) { g_return_if_fail (canvas != NULL); g_return_if_fail (DIA_IS_CANVAS (canvas)); g_return_if_fail (extents->top <= extents->bottom); g_return_if_fail (extents->left <= extents->right); if ((extents->top != canvas->extents.top) || (extents->left != canvas->extents.left) || (extents->bottom != canvas->extents.bottom) || (extents->right != canvas->extents.right)) { g_signal_emit (G_OBJECT(canvas), canvas_signals[EXTENTS_CHANGED], 0, extents); canvas->extents = *extents; g_object_notify (G_OBJECT (canvas), "extents"); } } /** * dia_canvas_set_static_extents: * @canvas: * @stat: Turn static extents on (TRUE) or off (FALSE). * * Set extents. Normally (with this option turned off) the boundries of the * canvas will grow or shrink depending on the position of the objects (so the * placement of the objects determine the actual size). * * Turn this option on if you don't want the canvas to dynamically resize. **/ void dia_canvas_set_static_extents (DiaCanvas *canvas, gboolean stat) { g_return_if_fail (DIA_IS_CANVAS (canvas)); canvas->static_extents = stat; if (!stat && canvas->root) dia_canvas_set_extents (canvas, &canvas->root->bounds); g_object_notify (G_OBJECT (canvas), "static_extents"); } /** * dia_canvas_set_snap_to_grid: * @canvas: * @snap: Turn snap on (TRUE) or off (FALSE). * * Turn the snap to grid option on or off. **/ void dia_canvas_set_snap_to_grid (DiaCanvas *canvas, gboolean snap) { g_return_if_fail (DIA_IS_CANVAS (canvas)); canvas->snap_to_grid = snap; g_object_notify (G_OBJECT (canvas), "snap_to_grid"); } /** * dia_canvas_snap_to_grid: * @canvas: * @x: X pos to be snapped. * @y: Y pos to be snapped. * * This function takes two arguments (@x and @y) and returnes them with a grid * position. If the "snap_to_grid" option is turned off, this function does * nothing. **/ void dia_canvas_snap_to_grid (DiaCanvas *canvas, gdouble *x, gdouble *y) { g_return_if_fail (DIA_IS_CANVAS (canvas)); g_return_if_fail (x != NULL); g_return_if_fail (y != NULL); if (canvas->snap_to_grid) { *x = floor ((*x / canvas->interval_x) + .5) * canvas->interval_x + canvas->offset_x; *y = floor ((*y / canvas->interval_y) + .5) * canvas->interval_y + canvas->offset_y; } } static GList* real_find_objects_in_rectangle (DiaCanvasItem *item, ArtDRect *rect) { GList *objs = NULL; DiaCanvasIter iter; if (DIA_CANVAS_ITEM_COMPOSITE (item)) return NULL; if (item->parent && (item->bounds.left > rect->x0) && (item->bounds.right < rect->x1) && (item->bounds.top > rect->y0) && (item->bounds.bottom < rect->y1)) { objs = g_list_append (objs, item); } /* ... And now for the children... */ if (DIA_IS_CANVAS_GROUPABLE (item) && dia_canvas_groupable_get_iter (DIA_CANVAS_GROUPABLE (item), &iter)) do { ArtDRect rdest; DiaCanvasItem *child = dia_canvas_groupable_value (DIA_CANVAS_GROUPABLE (item), &iter); GList *new_objs; gdouble inv[6]; art_affine_invert (inv, child->affine); art_drect_affine_transform (&rdest, rect, inv); new_objs = real_find_objects_in_rectangle (child, &rdest); if (new_objs) objs = g_list_concat (objs, new_objs); } while (dia_canvas_groupable_next (DIA_CANVAS_GROUPABLE (item), &iter)); return objs; } /** * dia_canvas_find_objects_in_rectangle: * @canvas: * @rect: A rectangle that determines the boundries in which objects should * live. * * Find all objects that are within rectangle @rect and return them in a #GList. * The returned list should be freed by the caller. * Note that the root object is not included in the selection, only * non-composite leaf objects are selected. * * NOTE: the items returned do not have their reference count incremented. * So do not g_object_unref() them. * * Return value: A list of objects that are within the rectangle. **/ GList* dia_canvas_find_objects_in_rectangle (DiaCanvas *canvas, DiaRectangle *rect) { GList *l; ArtDRect r; g_return_val_if_fail (DIA_IS_CANVAS (canvas), NULL); g_return_val_if_fail (rect != NULL, NULL); r.x0 = rect->left; r.y0 = rect->top; r.x1 = rect->right; r.y1 = rect->bottom; l = real_find_objects_in_rectangle (canvas->root, &r); return l; } /** * dia_canvas_request_update: * @canvas: * * Ask the canvas to schedule a update handler. The actual update will happen * diuring idle time or if dia_canvas_update_now() is called. **/ void dia_canvas_request_update (DiaCanvas *canvas) { g_return_if_fail (DIA_IS_CANVAS (canvas)); if (canvas->idle_id == 0) canvas->idle_id = g_idle_add_full (UPDATE_PRIORITY, idle_handler, canvas, NULL /* destroyNotify */); } /** * dia_canvas_resolve_now: * @canvas: * * Ask the constraint solver to resolve it's constraints. **/ void dia_canvas_resolve_now (DiaCanvas *canvas) { g_return_if_fail (DIA_IS_CANVAS (canvas)); dia_solver_resolve (canvas->solver); } /** * dia_canvas_update_now: * @canvas: * * Force an update of the internat state of the canvas. This will set the * canvas in a consistent state by running the update() callback of every * #DiaCanvasItem that requested an update. **/ void dia_canvas_update_now (DiaCanvas *canvas) { g_return_if_fail (DIA_IS_CANVAS (canvas)); if (canvas->idle_id != 0) { g_source_remove (canvas->idle_id); canvas->idle_id = 0; } idle_handler (canvas); } typedef struct { const DiaHandle *handle; gdouble orig_x; gdouble orig_y; gdouble glue_x; gdouble glue_y; gdouble dist; DiaCanvasItem *item; } GlueHandleHelper; static void real_glue_handle (DiaCanvasItem *item, GlueHandleHelper *helper) { gdouble x, y, dist; DiaCanvasIter iter; DiaCanvasItem *child; if (!DIA_CANVAS_ITEM_VISIBLE (helper->handle->owner)) return; /* Do not check yourself. */ if (helper->handle->owner == item) return; if (DIA_CANVAS_ITEM_GET_CLASS (item)->glue) { x = helper->orig_x; y = helper->orig_y; dist = DIA_CANVAS_ITEM_GET_CLASS (item)->glue (item, DIA_HANDLE (helper->handle), &x, &y); if (dist < helper->dist) { helper->dist = dist; helper->glue_x = x; helper->glue_y = y; helper->item = item; } } if (DIA_IS_CANVAS_GROUPABLE (item) && dia_canvas_groupable_get_iter (DIA_CANVAS_GROUPABLE (item), &iter)) do { child = dia_canvas_groupable_value (DIA_CANVAS_GROUPABLE (item), &iter); real_glue_handle(child, helper); } while (dia_canvas_groupable_next (DIA_CANVAS_GROUPABLE (item), &iter)); return; } /** * dia_canvas_glue_handle: * @canvas: Canvas to find a connection point on. * @handle: Handle to be glued to an item somewhere on the canvas. * @dest_x: (to be) position of the handle. * @dest_y: * @glue_x: [OUT] closest glue point. * @glue_y: [OUT] ,, * @item: [OUT] The item the handle should connect() to. * * Find a point to glue the handle to. Point (dest_x, dest_y) is the position * where the handle should move to if no glue point is found. This can be useful * if the handle is in a motion and you have to calculate the place where the * handle should move to. * * Return value: The distance from the handle to the closest connection point. */ gdouble dia_canvas_glue_handle (DiaCanvas *canvas, const DiaHandle *handle, const gdouble dest_x, const gdouble dest_y, gdouble *glue_x, gdouble *glue_y, DiaCanvasItem **item) { GlueHandleHelper helper; g_return_val_if_fail (DIA_IS_CANVAS (canvas), G_MAXDOUBLE); g_return_val_if_fail (DIA_IS_HANDLE (handle), G_MAXDOUBLE); g_return_val_if_fail (glue_x != NULL, G_MAXDOUBLE); g_return_val_if_fail (glue_y != NULL, G_MAXDOUBLE); helper.handle = handle; helper.orig_x = dest_x; helper.orig_y = dest_y; helper.glue_x = dest_x; helper.glue_y = dest_y; helper.dist = G_MAXDOUBLE; helper.item = NULL; // TODO: remove depricated function. //dia_canvas_group_foreach (canvas->root, (DiaCanvasItemForeachFunc) real_glue_handle, &helper); real_glue_handle(canvas->root, &helper); *glue_x = helper.glue_x; *glue_y = helper.glue_y; *item = helper.item; return helper.dist; } /** * dia_canvas_add_constraint: * @canvas: * @c: Constraint to add * * Add a constraint to the canvas' constraint solver. **/ void dia_canvas_add_constraint (DiaCanvas *canvas, DiaConstraint *c) { g_return_if_fail (DIA_IS_CANVAS (canvas)); g_return_if_fail (DIA_IS_CONSTRAINT (c)); dia_solver_add_constraint (canvas->solver, c); } /** * dia_canvas_remove_constraint: * @canvas: * @c: Constraint to be removed. * * Remove constraint @c from the canvas. **/ void dia_canvas_remove_constraint (DiaCanvas *canvas, DiaConstraint *c) { g_return_if_fail (DIA_IS_CANVAS (canvas)); g_return_if_fail (DIA_IS_CONSTRAINT (c)); dia_solver_remove_constraint (canvas->solver, c); } /** * dia_canvas_get_pango_layout: * * Returns a usable #PangoLayout object. * * Return value: A newly created PangoLayout object. **/ PangoLayout* dia_canvas_get_pango_layout (void) { /* Make the context static, this will save time for future references. */ static PangoContext *context = NULL; if (!context) { context = pango_ft2_get_context (100.0, 100.0); } return pango_layout_new (context); } /** * dia_canvas_redraw_views: * @canvas: * * This function will emit a signal that will cause all views to update and * redraw itself. There are very few cases in which this function is useful. * Calling this function often will dramatically reduce the speed of the canvas. **/ void dia_canvas_redraw_views (DiaCanvas *canvas) { g_return_if_fail (DIA_IS_CANVAS (canvas)); g_signal_emit (canvas, canvas_signals[REDRAW_VIEW], 0); } DiaUndoManager* dia_canvas_get_undo_manager (DiaCanvas *canvas) { g_return_if_fail (DIA_IS_CANVAS (canvas)); return canvas->undo_manager; } void dia_canvas_set_undo_manager (DiaCanvas *canvas, DiaUndoManager *undo_manager) { g_return_if_fail (DIA_IS_CANVAS (canvas)); g_return_if_fail (DIA_IS_UNDO_MANAGER (undo_manager)); if (canvas->undo_manager) g_object_unref (canvas->undo_manager); canvas->undo_manager = g_object_ref (undo_manager); } /* * UNDO/REDO */ /* Debug info for Undo/Redo part: */ //#define UNDOINFO(...) g_message (G_STRLOC": " __VA_ARGS__); #define UNDOINFO(...) typedef struct _DiaUndoProperty DiaUndoProperty; struct _DiaUndoProperty { DiaUndoAction entry; GObject *object; GParamSpec *pspec; GValue value; GValue redo_value; }; void dia_undo_property_undo (DiaUndoAction *entry) { DiaUndoProperty *p = (DiaUndoProperty*) entry; UNDOINFO("Applying property '%s'", p->pspec->name); // Save value for redo purposes g_object_get_property (p->object, p->pspec->name, &p->redo_value); g_object_set_property (p->object, p->pspec->name, &p->value); } void dia_undo_property_redo (DiaUndoAction *entry) { DiaUndoProperty *p = (DiaUndoProperty*) entry; UNDOINFO("Applying property '%s'", p->pspec->name); g_object_set_property (p->object, p->pspec->name, &p->redo_value); } void dia_undo_property_destroy (gpointer entry) { DiaUndoProperty *p = (DiaUndoProperty*) entry; g_object_unref (p->object); g_value_unset (&p->value); g_value_unset (&p->redo_value); g_param_spec_unref (p->pspec); } DiaUndoAction* dia_undo_property_new (GObject *object, GParamSpec *pspec, const GValue *value) { DiaUndoProperty *p; g_assert (G_IS_OBJECT (object)); p = (DiaUndoProperty*) dia_undo_action_new (sizeof (DiaUndoProperty), dia_undo_property_undo, dia_undo_property_redo, dia_undo_property_destroy); //g_message (G_STRLOC": saving property %s %p", pspec->name, object); p->object = g_object_ref (object); p->pspec = g_param_spec_ref (pspec); g_value_init (&p->value, G_PARAM_SPEC_VALUE_TYPE (pspec)); g_value_init (&p->redo_value, G_PARAM_SPEC_VALUE_TYPE (pspec)); g_value_copy (value, &p->value); return (DiaUndoAction*) p; } static void preserve (DiaCanvas *canvas, GObject *object, GParamSpec *pspec, const GValue *value, gboolean last) { gboolean new = FALSE; if (!canvas->allow_undo) return; if (!canvas->undo_manager) return; if (dia_undo_manager_in_transaction (canvas->undo_manager)) dia_undo_manager_add_undo_action (canvas->undo_manager, dia_undo_property_new (object, pspec, value)); } /** * dia_canvas_preserve: * @canvas: * @object: object that should be undone * @property_name: static const string for a property (should NOT be freed!) * @value: data to be undone * @last: append rather than prepend the property. * * Put a variable on the undo stack. The variable is described by its * @property_name and the @undo_data. * * If an @object/@property_name combination is preserved multiple times, only * the first occurance is stored. **/ void dia_canvas_preserve (DiaCanvas *canvas, GObject *object, const char *property_name, const GValue *value, gboolean last) { GParamSpec *pspec; g_return_if_fail (DIA_IS_CANVAS (canvas)); g_return_if_fail (G_IS_OBJECT (object)); g_return_if_fail (property_name != NULL); g_return_if_fail (value != NULL); pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), property_name); if (pspec == NULL) { g_warning (G_STRLOC": property '%s' doesn't not exist for class type '%s'.", property_name, G_OBJECT_TYPE_NAME (object)); return; } preserve (canvas, object, pspec, value, last); } static void preserve_property (DiaCanvas *canvas, GObject *object, const char *property_name, gboolean last) { GValue value = { 0, }; GParamSpec *pspec; pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), property_name); if (pspec == NULL) { g_warning (G_STRLOC": property '%s' doesn't exist for class type '%s'.", property_name, G_OBJECT_TYPE_NAME (object)); return; } g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); g_object_get_property (object, property_name, &value); preserve (canvas, object, pspec, &value, last); g_value_unset (&value); } /** * dia_canvas_preserve_property: * @canvas: * @object: * @property_name: Property to preserve * * This is an easy way to save properties. The value of the property is * preserved so the change can be undone by dia_canvas_undo_pop(). **/ void dia_canvas_preserve_property (DiaCanvas *canvas, GObject *object, const char *property_name) { g_return_if_fail (DIA_IS_CANVAS (canvas)); g_return_if_fail (G_IS_OBJECT (object)); g_return_if_fail (property_name != NULL); preserve_property (canvas, object, property_name, FALSE); } /** * dia_canvas_preserve_property_last: * @canvas: * @object: * @property_name: * * This is a special version of dia_canvas_preserve_property(): it preserves * a property, but adds it to the end of the list, in stead of the beginning. * This is needed for some special actions. * * This function won't be used in normal usage. **/ void dia_canvas_preserve_property_last (DiaCanvas *canvas, GObject *object, const char *property_name) { g_return_if_fail (DIA_IS_CANVAS (canvas)); g_return_if_fail (G_IS_OBJECT (object)); g_return_if_fail (property_name != NULL); preserve_property (canvas, object, property_name, TRUE); } /** * dia_canvas_push_undo: * @canvas: * @optional_comment: An optional comment. May be NULL. * * Push a bunch of preserved values on the undo stack. If no values are stored * using dia_canvas_preserve() or dia_canvas_preserve_property(), nothing is * pushed on the stack. **/ void dia_canvas_push_undo (DiaCanvas *canvas, const char* optional_comment) { g_return_if_fail (DIA_IS_CANVAS (canvas)); g_warning (G_STRLOC"This function is depricated."); return; #if 0 if (!canvas->allow_undo || !canvas->undo_manager) return; dia_undo_manager_commit_transaction (canvas->undo_manager); if (canvas->undo_entry && (((UndoEntry*) canvas->undo_entry)->undo_data->len > 0)) { canvas->undo = g_slist_prepend (canvas->undo, canvas->undo_entry); UNDOINFO("stack pushed %p %p (depth = %d)", canvas->undo_entry, canvas->undo->data, g_slist_length(canvas->undo)); canvas->undo_entry = NULL; /* undo signal is emited by preserve() as soon as canvas->undo_entry is used. */ } #endif } /** * dia_canvas_pop_undo: * @canvas: * * Undo a set of actions. Undone actions are stored on the redo-stack, so they * can be undone too. **/ void dia_canvas_pop_undo (DiaCanvas *canvas) { //UndoEntry *undo_entry = NULL; g_return_if_fail (DIA_IS_CANVAS (canvas)); #if 0 if (canvas->undo_entry && ((UndoEntry*)canvas->undo_entry)->undo_data->len > 0) { UNDOINFO("from undo_entry"); undo_entry = canvas->undo_entry; canvas->undo_entry = NULL; } else if (canvas->undo) { UNDOINFO("from undo list"); undo_entry = canvas->undo->data; canvas->undo = g_slist_delete_link (canvas->undo, canvas->undo); } if (!undo_entry) return; UNDOINFO("Ready to undo %d actions", undo_entry->undo_data->len); canvas->in_undo = TRUE; undo_entry_apply (undo_entry); //dia_canvas_update_now (canvas); canvas->in_undo = FALSE; if (canvas->undo_entry && ((UndoEntry*) canvas->undo_entry)->undo_data->len) { canvas->redo = g_slist_prepend (canvas->redo, canvas->undo_entry); canvas->undo_entry = NULL; } undo_entry_free (undo_entry); #endif } /** * dia_canvas_pop_redo: * @canvas: * * Opposite of dia_canvas_undo_pop(). Undone actions are undone themselves ;-) **/ void dia_canvas_pop_redo (DiaCanvas *canvas) { //UndoEntry *redo_entry; g_warning (G_STRLOC"This function is depricated."); #if 0 UNDOINFO(); g_return_if_fail (DIA_IS_CANVAS (canvas)); if (canvas->redo == NULL) return; redo_entry = canvas->redo->data; canvas->redo = g_slist_delete_link (canvas->redo, canvas->redo); if (canvas->undo_entry) { dia_canvas_push_undo (canvas, NULL); } canvas->in_undo = TRUE; undo_entry_apply (redo_entry); //dia_canvas_update_now (canvas); canvas->in_undo = FALSE; if (canvas->undo_entry && ((UndoEntry*) canvas->undo_entry)->undo_data->len) { canvas->undo = g_slist_prepend (canvas->undo, canvas->undo_entry); canvas->undo_entry = NULL; } undo_entry_free (redo_entry); #endif } /** * dia_canvas_clear_undo: * @canvas: * * Clear the undo stack. **/ void dia_canvas_clear_undo (DiaCanvas *canvas) { g_return_if_fail (DIA_IS_CANVAS (canvas)); g_warning (G_STRLOC"This function is depricated."); } /** * dia_canvas_clear_redo: * @canvas: * * Clear the redo stack. This should be automatically done after an * interactive operation on the canvas (e.g. an item is moved). **/ void dia_canvas_clear_redo (DiaCanvas *canvas) { g_return_if_fail (DIA_IS_CANVAS (canvas)); g_warning (G_STRLOC"This function is depricated."); } /** * dia_canvas_get_undo_depth: * @canvas: * * Return the amount of undo entries on the undo stack. The depth can be * at most the dia_canvas_get_stack_depth() + 1 (the current undo entry). * * Return value: **/ guint dia_canvas_get_undo_depth (DiaCanvas *canvas) { g_return_val_if_fail (DIA_IS_CANVAS (canvas), 0); g_warning (G_STRLOC"This function is depricated."); return 0; } /** * dia_canvas_get_redo_depth: * @canvas: * * Return the amount of undo entries on the undo stack. * * Return value: **/ guint dia_canvas_get_redo_depth (DiaCanvas *canvas) { g_return_val_if_fail (DIA_IS_CANVAS (canvas), 0); g_warning (G_STRLOC"This function is depricated."); return 0; } /** * dia_canvas_set_undo_stack_depth: * @canvas: * @depth: New depth for the undo/redo stack. * * Set a limit of the length of the undo/redo stack (default 10). **/ void dia_canvas_set_undo_stack_depth (DiaCanvas *canvas, guint depth) { g_return_if_fail (DIA_IS_CANVAS (canvas)); g_warning (G_STRLOC"This function is depricated."); } /** * dia_canvas_get_undo_stack_depth: * @canvas: * * Get the current stack depth. * * Return value: **/ guint dia_canvas_get_undo_stack_depth (DiaCanvas *canvas) { g_return_val_if_fail (DIA_IS_CANVAS (canvas), 0); g_warning (G_STRLOC"This function is depricated."); return 0; }