[AdgGtkArea] Improved zoom handling and added adg_gtk_area_get_zoom()
[adg.git] / src / adg-gtk / adg-gtk-area.c
blob84333d3113e7ab6dd143b38815d48c5e61d10021
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009,2010 Nicola Fontana <ntd at entidi.it>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 /**
22 * SECTION:adg-gtk-area
23 * @short_description: A #GtkWidget specifically designed to contain
24 * an #AdgCanvas entity
26 * This is a #GtkDrawingArea derived object that provides an easy way
27 * to show an ADG based canvas. The associated canvas can be set
28 * directly with the adg_gtk_area_new_with_canvas() constructor
29 * function or by using adg_gtk_area_set_canvas().
31 * The default minimum size of this widget will depend on the canvas
32 * content. The global matrix of the #AdgCanvas will be adjusted to
33 * expose the drawing in the proper position. The empty space around
34 * the drawing can be changed by setting the margins of the underlying
35 * #AdgCanvas object.
37 * The default implementation reacts to some mouse events: if you drag
38 * the mouse keeping the wheel pressed the canvas will be translated;
39 * if the mouse wheel is rotated the canvas will be scaled up or down
40 * (accordingly to the wheel direction) by the factor specified in the
41 * #AdgGtkArea:factor property.
42 **/
45 /**
46 * AdgGtkArea:
48 * All fields are private and should not be used directly.
49 * Use its public methods instead.
50 **/
53 #include "adg-gtk-internal.h"
54 #include "adg-gtk-area.h"
55 #include "adg-gtk-area-private.h"
56 #include "adg-gtk-marshal.h"
58 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_gtk_area_parent_class)
59 #define _ADG_OLD_WIDGET_CLASS ((GtkWidgetClass *) adg_gtk_area_parent_class)
62 G_DEFINE_TYPE(AdgGtkArea, adg_gtk_area, GTK_TYPE_DRAWING_AREA);
64 enum {
65 PROP_0,
66 PROP_CANVAS,
67 PROP_FACTOR,
68 PROP_AUTOZOOM
71 enum {
72 CANVAS_CHANGED,
73 EXTENTS_CHANGED,
74 LAST_SIGNAL
78 static void _adg_dispose (GObject *object);
79 static void _adg_get_property (GObject *object,
80 guint prop_id,
81 GValue *value,
82 GParamSpec *pspec);
83 static void _adg_set_property (GObject *object,
84 guint prop_id,
85 const GValue *value,
86 GParamSpec *pspec);
87 static void _adg_size_request (GtkWidget *widget,
88 GtkRequisition *requisition);
89 static void _adg_size_allocate (GtkWidget *widget,
90 GtkAllocation *allocation);
91 static gboolean _adg_expose_event (GtkWidget *widget,
92 GdkEventExpose *event);
93 static gboolean _adg_scroll_event (GtkWidget *widget,
94 GdkEventScroll *event);
95 static gboolean _adg_button_press_event (GtkWidget *widget,
96 GdkEventButton *event);
97 static gboolean _adg_motion_notify_event(GtkWidget *widget,
98 GdkEventMotion *event);
99 static gboolean _adg_get_map (GtkWidget *widget,
100 gboolean local_space,
101 AdgMatrix *map,
102 AdgMatrix *inverted);
103 static void _adg_set_map (GtkWidget *widget,
104 gboolean local_space,
105 const AdgMatrix *map);
106 static const CpmlExtents *
107 _adg_get_extents (AdgGtkArea *area);
109 static guint _adg_signals[LAST_SIGNAL] = { 0 };
112 static void
113 adg_gtk_area_class_init(AdgGtkAreaClass *klass)
115 GObjectClass *gobject_class;
116 GtkWidgetClass *widget_class;
117 GParamSpec *param;
119 gobject_class = (GObjectClass *) klass;
120 widget_class = (GtkWidgetClass *) klass;
122 g_type_class_add_private(klass, sizeof(AdgGtkAreaPrivate));
124 gobject_class->dispose = _adg_dispose;
125 gobject_class->get_property = _adg_get_property;
126 gobject_class->set_property = _adg_set_property;
128 widget_class->size_request = _adg_size_request;
129 widget_class->size_allocate = _adg_size_allocate;
130 widget_class->expose_event = _adg_expose_event;
131 widget_class->scroll_event = _adg_scroll_event;
132 widget_class->button_press_event = _adg_button_press_event;
133 widget_class->motion_notify_event = _adg_motion_notify_event;
135 param = g_param_spec_object("canvas",
136 P_("Canvas"),
137 P_("The canvas to be shown"),
138 ADG_TYPE_CANVAS,
139 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
140 g_object_class_install_property(gobject_class, PROP_CANVAS, param);
142 param = g_param_spec_double("factor",
143 P_("Factor"),
144 P_("The factor used in zooming in and out"),
145 1., G_MAXDOUBLE, 1.05,
146 G_PARAM_READWRITE);
147 g_object_class_install_property(gobject_class, PROP_FACTOR, param);
149 param = g_param_spec_boolean("autozoom",
150 P_("Autozoom"),
151 P_("When enabled, automatically adjust the zoom factor in global space at every size allocation"),
152 FALSE,
153 G_PARAM_READWRITE);
154 g_object_class_install_property(gobject_class, PROP_AUTOZOOM, param);
157 * AdgGtkArea::canvas-changed:
158 * @area: an #AdgGtkArea
159 * @old_canvas: the old #AdgCanvas object
161 * Emitted when the #AdgGtkArea has a new canvas. If the new canvas
162 * is the same as the old one, the signal is not emitted.
164 _adg_signals[CANVAS_CHANGED] =
165 g_signal_new("canvas-changed", ADG_GTK_TYPE_AREA,
166 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
167 G_STRUCT_OFFSET(AdgGtkAreaClass, canvas_changed),
168 NULL, NULL,
169 adg_gtk_marshal_VOID__OBJECT,
170 G_TYPE_NONE, 1, ADG_TYPE_CANVAS);
173 * AdgGtkArea::extents-changed:
174 * @area: an #AdgGtkArea
175 * @old_extents: the old #CpmlExtents struct
177 * Emitted when the extents of @area have been changed.
178 * The old extents are always compared to the new ones,
179 * so when the extents are recalculated but the result
180 * is the same the signal is not emitted.
182 _adg_signals[EXTENTS_CHANGED] =
183 g_signal_new("extents-changed", ADG_GTK_TYPE_AREA,
184 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
185 G_STRUCT_OFFSET(AdgGtkAreaClass, extents_changed),
186 NULL, NULL,
187 adg_gtk_marshal_VOID__POINTER,
188 G_TYPE_NONE, 1, G_TYPE_POINTER);
191 static void
192 adg_gtk_area_init(AdgGtkArea *area)
194 AdgGtkAreaPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(area,
195 ADG_GTK_TYPE_AREA,
196 AdgGtkAreaPrivate);
198 data->canvas = NULL;
199 data->factor = 1.05;
200 data->autozoom = FALSE;
202 data->x_event = 0;
203 data->y_event = 0;
204 data->initial_zoom = 0;
206 area->data = data;
208 /* Enable GDK events to catch wheel rotation and drag */
209 gtk_widget_add_events((GtkWidget *) area,
210 GDK_BUTTON_PRESS_MASK |
211 GDK_BUTTON2_MOTION_MASK |
212 GDK_SCROLL_MASK);
215 static void
216 _adg_dispose(GObject *object)
218 AdgGtkAreaPrivate *data = ((AdgGtkArea *) object)->data;
220 if (data->canvas) {
221 g_object_unref(data->canvas);
222 data->canvas = NULL;
225 if (_ADG_OLD_OBJECT_CLASS->dispose)
226 _ADG_OLD_OBJECT_CLASS->dispose(object);
229 static void
230 _adg_get_property(GObject *object, guint prop_id,
231 GValue *value, GParamSpec *pspec)
233 AdgGtkAreaPrivate *data = ((AdgGtkArea *) object)->data;
235 switch (prop_id) {
236 case PROP_CANVAS:
237 g_value_set_object(value, data->canvas);
238 break;
239 case PROP_FACTOR:
240 g_value_set_double(value, data->factor);
241 break;
242 case PROP_AUTOZOOM:
243 g_value_set_boolean(value, data->autozoom);
244 break;
245 default:
246 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
247 break;
251 static void
252 _adg_set_property(GObject *object, guint prop_id,
253 const GValue *value, GParamSpec *pspec)
255 AdgGtkArea *area;
256 AdgGtkAreaPrivate *data;
257 AdgCanvas *canvas;
259 area = (AdgGtkArea *) object;
260 data = area->data;
262 switch (prop_id) {
263 case PROP_CANVAS:
264 canvas = g_value_get_object(value);
265 if (canvas)
266 g_object_ref(canvas);
267 if (data->canvas)
268 g_object_unref(data->canvas);
269 if (data->canvas != canvas) {
270 data->canvas = canvas;
271 g_signal_emit(area, _adg_signals[CANVAS_CHANGED], 0);
273 break;
274 case PROP_FACTOR:
275 data->factor = g_value_get_double(value);
276 break;
277 case PROP_AUTOZOOM:
278 data->autozoom = g_value_get_boolean(value);
279 break;
280 default:
281 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
282 break;
288 * adg_gtk_area_new:
290 * Creates a new empty #AdgGtkArea. The widget is useful only after
291 * an #AdgCanvas has been added either using the #AdgGtkArea:canvas
292 * property or with adg_gtk_area_set_canvas().
294 * Returns: the newly created widget
296 GtkWidget *
297 adg_gtk_area_new(void)
299 return g_object_new(ADG_GTK_TYPE_AREA, NULL);
303 * adg_gtk_area_new_with_canvas:
304 * @canvas: the #AdgCanvas shown by this widget
306 * Creates a new #AdgGtkArea and sets the #AdgGtkArea:canvas property
307 * to @canvas.
309 * Returns: the newly created widget
311 GtkWidget *
312 adg_gtk_area_new_with_canvas(AdgCanvas *canvas)
314 g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL);
316 return g_object_new(ADG_GTK_TYPE_AREA, "canvas", canvas, NULL);
320 * adg_gtk_area_set_canvas:
321 * @area: an #AdgGtkArea
322 * @canvas: the new #AdgCanvas
324 * Sets a new canvas on @area. The old canvas, if presents, is
325 * unreferenced.
327 void
328 adg_gtk_area_set_canvas(AdgGtkArea *area, AdgCanvas *canvas)
330 g_return_if_fail(ADG_GTK_IS_AREA(area));
331 g_object_set(area, "canvas", canvas, NULL);
335 * adg_gtk_area_get_canvas:
336 * @area: an #AdgGtkArea
338 * Gets the canvas associated to @area.
340 * Returns: the requested #AdgCanvas object or %NULL on errors
342 AdgCanvas *
343 adg_gtk_area_get_canvas(AdgGtkArea *area)
345 AdgGtkAreaPrivate *data;
347 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
349 data = area->data;
351 return data->canvas;
355 * adg_gtk_area_get_extents:
356 * @area: an #AdgGtkArea
358 * Gets the extents of the canvas bound to @area. The returned
359 * struct is owned by @area and should not modified or freed.
360 * This is a convenient function that gets the extents with
361 * adg_entity_get_extents() and add to it the margins of the
362 * canvas .
364 * If @area still does not have any canvas associated to it or
365 * the canvas is invalid or empty, %NULL is returned.
367 * The canvas will be updated, meaning the adg_entity_arrange()
368 * will be called before the extents computation.
370 * Returns: the extents of the @area canvas or %NULL on errors
372 const CpmlExtents *
373 adg_gtk_area_get_extents(AdgGtkArea *area)
375 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
377 return _adg_get_extents(area);
381 * adg_gtk_area_get_zoom:
382 * @area: an #AdgGtkArea
384 * Gets the current zoom factor applied on the canvas of @area.
385 * If the #AdgGtkArea:autozoom property is %FALSE, the value
386 * returned is always %1.
388 * Returns: the current zoom factor
390 gdouble
391 adg_gtk_area_get_zoom(AdgGtkArea *area)
393 AdgGtkAreaPrivate *data;
395 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
397 data = area->data;
398 return data->zoom;
402 * adg_gtk_area_set_factor:
403 * @area: an #AdgGtkArea
404 * @factor: the new zoom factor
406 * Sets a new zoom factor to @area. If the factor is less than
407 * 1, it will be clamped to 1.
409 void
410 adg_gtk_area_set_factor(AdgGtkArea *area, gdouble factor)
412 g_return_if_fail(ADG_GTK_IS_AREA(area));
413 g_object_set(area, "factor", factor, NULL);
417 * adg_gtk_area_get_factor:
418 * @area: an #AdgGtkArea
420 * Gets the zoom factor associated to @area. The zoom factor is
421 * directly used to zoom in (that is, the default zoom factor of
422 * 1.05 will zoom of 5% every iteration) and it is reversed while
423 * zooming out (that is, the default factor will use 1/1.05).
425 * Returns: the requested zoom factor or 0 on error
427 gdouble
428 adg_gtk_area_get_factor(AdgGtkArea *area)
430 AdgGtkAreaPrivate *data;
432 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
434 data = area->data;
435 return data->factor;
439 * adg_gtk_area_switch_autozoom:
440 * @area: an #AdgGtkArea
441 * @state: the new autozoom state
443 * Sets the #AdgGtkArea:autozoom property of @area to @state. When the
444 * autozoom feature is enabled, @area reacts to any size allocation
445 * by adjusting its zoom factor in global space. This means the
446 * drawing will fill the available space (keeping its aspect ratio)
447 * when resizing the window.
449 void
450 adg_gtk_area_switch_autozoom(AdgGtkArea *area, gboolean state)
452 g_return_if_fail(ADG_GTK_IS_AREA(area));
453 g_object_set(area, "autozoom", state, NULL);
457 * adg_gtk_area_has_autozoom:
458 * @area: an #AdgGtkArea
460 * Gets the current state of the #AdgGtkArea:autozoom property on
461 * the @area object.
463 * Returns: the current autozoom state
465 gboolean
466 adg_gtk_area_has_autozoom(AdgGtkArea *area)
468 AdgGtkAreaPrivate *data;
470 g_return_val_if_fail(ADG_GTK_IS_AREA(area), FALSE);
472 data = area->data;
473 return data->autozoom;
477 * adg_gtk_area_canvas_changed:
478 * @area: an #AdgGtkArea
479 * @canvas: the old canvas bound to @area
481 * Emits the #AdgGtkArea::canvas-changed signal on @area.
483 void
484 adg_gtk_area_canvas_changed(AdgGtkArea *area, AdgCanvas *old_canvas)
486 g_return_if_fail(ADG_GTK_IS_AREA(area));
488 g_signal_emit(area, _adg_signals[CANVAS_CHANGED], 0, old_canvas);
492 * adg_gtk_area_extents_changed:
493 * @area: an #AdgGtkArea
494 * @old_extents: the old extents of @area
496 * Emits the #AdgGtkArea::extents-changed signal on @area.
498 void
499 adg_gtk_area_extents_changed(AdgGtkArea *area, const CpmlExtents *old_extents)
501 g_return_if_fail(ADG_GTK_IS_AREA(area));
503 g_signal_emit(area, _adg_signals[EXTENTS_CHANGED], 0, old_extents);
507 static void
508 _adg_size_request(GtkWidget *widget, GtkRequisition *requisition)
510 const CpmlExtents *extents = _adg_get_extents((AdgGtkArea *) widget);
512 if (extents != NULL && extents->is_defined) {
513 requisition->width = extents->size.x;
514 requisition->height = extents->size.y;
519 * _adg_size_allocate:
520 * @widget: an #AdgGtkArea widget
521 * @allocation: the new allocation struct
523 * Scales the drawing according to the new allocation.
525 * TODO: the current implementation keeps the point in the center
526 * of the #AdgCanvas at the center of the new #AdgGtkArea. This is
527 * not what a user would likely expect because all the eventual
528 * translations in global space will be lost. In other words the
529 * resulting drawing is always centered also when the original
530 * drawing (that is, the drawing before the new size allocation)
531 * was not.
533 static void
534 _adg_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
536 AdgGtkArea *area;
537 AdgGtkAreaPrivate *data;
538 AdgCanvas *canvas;
539 AdgEntity *entity;
540 CpmlVector *initial_size;
541 CpmlVector size;
542 gdouble top, right, bottom, left;
543 gdouble vmargins, hmargins;
544 AdgPair ratio;
545 AdgMatrix map;
547 if (_ADG_OLD_WIDGET_CLASS->size_allocate)
548 _ADG_OLD_WIDGET_CLASS->size_allocate(widget, allocation);
550 area = (AdgGtkArea *) widget;
551 data = area->data;
552 canvas = data->canvas;
554 if (canvas == NULL)
555 return;
557 top = adg_canvas_get_top_margin(canvas);
558 right = adg_canvas_get_right_margin(canvas);
559 bottom = adg_canvas_get_bottom_margin(canvas);
560 left = adg_canvas_get_left_margin(canvas);
561 vmargins = top + bottom;
562 hmargins = left + right;
564 /* Check if the allocated space is enough:
565 * if not, there is not much we can do... */
566 g_return_if_fail(allocation->width > hmargins);
567 g_return_if_fail(allocation->height > vmargins);
569 entity = (AdgEntity *) canvas;
570 initial_size = &data->initial_size;
571 size.x = allocation->width - hmargins;
572 size.y = allocation->height - vmargins;
574 adg_matrix_copy(&map, adg_entity_get_global_map(entity));
576 if (data->initial_zoom <= 0) {
577 /* First call: register the initial size and zoom */
578 const CpmlExtents *extents = _adg_get_extents(area);
580 if (extents == NULL || !extents->is_defined)
581 return;
583 if (extents->size.x <= 0 || extents->size.y <= 0)
584 return;
586 *initial_size = size;
587 data->initial_zoom = 1;
590 if (data->autozoom) {
591 /* Adjust the zoom according to the initial size */
592 ratio.x = data->initial_zoom * size.x / initial_size->x;
593 ratio.y = data->initial_zoom * size.y / initial_size->y;
594 data->zoom = MIN(ratio.x, ratio.y);
597 map.x0 = (size.x - initial_size->x * data->zoom) / 2;
598 map.y0 = (size.y - initial_size->y * data->zoom) / 2;
599 map.xx = map.yy = data->zoom;
600 adg_entity_set_global_map(entity, &map);
603 static gboolean
604 _adg_expose_event(GtkWidget *widget, GdkEventExpose *event)
606 AdgGtkAreaPrivate *data;
607 AdgCanvas *canvas;
609 data = ((AdgGtkArea *) widget)->data;
610 canvas = data->canvas;
612 if (canvas && event->window) {
613 cairo_t *cr = gdk_cairo_create(event->window);
614 cairo_translate(cr,
615 adg_canvas_get_left_margin(canvas),
616 adg_canvas_get_top_margin(canvas));
617 adg_entity_render((AdgEntity *) canvas, cr);
618 cairo_destroy(cr);
621 if (_ADG_OLD_WIDGET_CLASS->expose_event == NULL)
622 return FALSE;
624 return _ADG_OLD_WIDGET_CLASS->expose_event(widget, event);
627 static gboolean
628 _adg_scroll_event(GtkWidget *widget, GdkEventScroll *event)
630 gboolean zoom_in, zoom_out, local_space, global_space;
631 AdgMatrix map, inverted;
632 AdgGtkAreaPrivate *data;
633 double factor, x, y;
635 zoom_in = event->direction == GDK_SCROLL_UP;
636 zoom_out = event->direction == GDK_SCROLL_DOWN;
637 local_space = (event->state & ADG_GTK_MODIFIERS) == 0;
638 global_space = (event->state & ADG_GTK_MODIFIERS) == GDK_SHIFT_MASK;
640 if ((zoom_in || zoom_out) && (local_space || global_space) &&
641 _adg_get_map(widget, local_space, &map, &inverted)) {
642 data = ((AdgGtkArea *) widget)->data;
643 factor = zoom_in ? data->factor : 1. / data->factor;
644 x = event->x;
645 y = event->y;
647 cairo_matrix_transform_point(&inverted, &x, &y);
648 cairo_matrix_scale(&map, factor, factor);
649 cairo_matrix_translate(&map, x/factor - x, y/factor - y);
651 _adg_set_map(widget, local_space, &map);
653 gtk_widget_queue_draw(widget);
656 if (_ADG_OLD_WIDGET_CLASS->scroll_event == NULL)
657 return FALSE;
659 return _ADG_OLD_WIDGET_CLASS->scroll_event(widget, event);
662 static gboolean
663 _adg_button_press_event(GtkWidget *widget, GdkEventButton *event)
665 AdgGtkAreaPrivate *data = ((AdgGtkArea *) widget)->data;
667 if (event->type == GDK_BUTTON_PRESS && event->button == 2) {
668 /* Remember the starting coordinates of a (probable) translation */
669 data->x_event = event->x;
670 data->y_event = event->y;
673 if (_ADG_OLD_WIDGET_CLASS->button_press_event == NULL)
674 return FALSE;
676 return _ADG_OLD_WIDGET_CLASS->button_press_event(widget, event);
679 static gboolean
680 _adg_motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
682 gboolean translating, local_space, global_space;
683 AdgMatrix map, inverted;
684 AdgGtkAreaPrivate *data;
685 double x, y;
687 translating = (event->state & GDK_BUTTON2_MASK) == GDK_BUTTON2_MASK;
688 local_space = (event->state & ADG_GTK_MODIFIERS) == 0;
689 global_space = (event->state & ADG_GTK_MODIFIERS) == GDK_SHIFT_MASK;
691 if (translating && (local_space || global_space) &&
692 _adg_get_map(widget, local_space, &map, &inverted)) {
693 data = ((AdgGtkArea *) widget)->data;
694 x = event->x - data->x_event;
695 y = event->y - data->y_event;
697 cairo_matrix_transform_distance(&inverted, &x, &y);
698 cairo_matrix_translate(&map, x, y);
699 data->x_event = event->x;
700 data->y_event = event->y;
702 _adg_set_map(widget, local_space, &map);
704 gtk_widget_queue_draw(widget);
707 if (_ADG_OLD_WIDGET_CLASS->motion_notify_event == NULL)
708 return FALSE;
710 return _ADG_OLD_WIDGET_CLASS->motion_notify_event(widget, event);
713 static gboolean
714 _adg_get_map(GtkWidget *widget, gboolean local_space,
715 AdgMatrix *map, AdgMatrix *inverted)
717 AdgGtkAreaPrivate *data;
718 AdgEntity *entity;
720 data = ((AdgGtkArea *) widget)->data;
721 entity = (AdgEntity *) data->canvas;
722 if (entity == NULL)
723 return FALSE;
725 if (local_space) {
726 adg_matrix_copy(map, adg_entity_get_local_map(entity));
728 /* The inverted map is subject to the global matrix */
729 adg_matrix_copy(inverted, adg_entity_get_global_matrix(entity));
730 adg_matrix_transform(inverted, map, ADG_TRANSFORM_BEFORE);
731 } else {
732 adg_matrix_copy(map, adg_entity_get_global_map(entity));
733 adg_matrix_copy(inverted, map);
736 return cairo_matrix_invert(inverted) == CAIRO_STATUS_SUCCESS;
739 static void
740 _adg_set_map(GtkWidget *widget, gboolean local_space, const AdgMatrix *map)
742 AdgGtkAreaPrivate *data;
743 AdgEntity *entity;
745 data = ((AdgGtkArea *) widget)->data;
746 entity = (AdgEntity *) data->canvas;
748 if (entity == NULL)
749 return;
751 if (local_space)
752 adg_entity_set_local_map(entity, map);
753 else
754 adg_entity_set_global_map(entity, map);
756 _adg_get_extents((AdgGtkArea *) widget);
759 static const CpmlExtents *
760 _adg_get_extents(AdgGtkArea *area)
762 AdgGtkAreaPrivate *data;
763 AdgCanvas *canvas;
764 CpmlExtents old_extents;
765 gdouble top, right, bottom, left;
767 data = area->data;
768 old_extents = data->extents;
769 data->extents.is_defined = FALSE;
771 canvas = data->canvas;
773 if (ADG_IS_CANVAS(canvas)) {
774 const CpmlExtents *extents;
775 AdgEntity *entity;
777 entity = (AdgEntity *) canvas;
779 adg_entity_arrange(entity);
781 extents = adg_entity_get_extents(entity);
782 if (extents != NULL && extents->is_defined) {
783 top = adg_canvas_get_top_margin(canvas);
784 right = adg_canvas_get_right_margin(canvas);
785 bottom = adg_canvas_get_bottom_margin(canvas);
786 left = adg_canvas_get_left_margin(canvas);
788 data->extents = *extents;
789 data->extents.org.x -= left;
790 data->extents.org.y -= top;
791 data->extents.size.x += left + right;
792 data->extents.size.y += top + bottom;
793 data->extents.is_defined = TRUE;
797 if (!cpml_extents_equal(&data->extents, &old_extents))
798 g_signal_emit(area, _adg_signals[EXTENTS_CHANGED], 0, &old_extents);
800 return &data->extents;