[AdgGtkArea] Corrected zoom in size_allocate()
[adg.git] / src / adg-gtk / adg-gtk-area.c
blobe339a100626371eaea02af1508fe3a41aeb01194
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 to use while zooming in and out (usually with the mouse wheel)"),
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 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;
205 area->data = data;
207 /* Enable GDK events to catch wheel rotation and drag */
208 gtk_widget_add_events((GtkWidget *) area,
209 GDK_BUTTON_PRESS_MASK |
210 GDK_BUTTON2_MOTION_MASK |
211 GDK_SCROLL_MASK);
214 static void
215 _adg_dispose(GObject *object)
217 AdgGtkAreaPrivate *data = ((AdgGtkArea *) object)->data;
219 if (data->canvas) {
220 g_object_unref(data->canvas);
221 data->canvas = NULL;
224 if (_ADG_OLD_OBJECT_CLASS->dispose)
225 _ADG_OLD_OBJECT_CLASS->dispose(object);
228 static void
229 _adg_get_property(GObject *object, guint prop_id,
230 GValue *value, GParamSpec *pspec)
232 AdgGtkAreaPrivate *data = ((AdgGtkArea *) object)->data;
234 switch (prop_id) {
235 case PROP_CANVAS:
236 g_value_set_object(value, data->canvas);
237 break;
238 case PROP_FACTOR:
239 g_value_set_double(value, data->factor);
240 break;
241 case PROP_AUTOZOOM:
242 g_value_set_boolean(value, data->autozoom);
243 break;
244 default:
245 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
246 break;
250 static void
251 _adg_set_property(GObject *object, guint prop_id,
252 const GValue *value, GParamSpec *pspec)
254 AdgGtkArea *area;
255 AdgGtkAreaPrivate *data;
256 AdgCanvas *canvas;
258 area = (AdgGtkArea *) object;
259 data = area->data;
261 switch (prop_id) {
262 case PROP_CANVAS:
263 canvas = g_value_get_object(value);
264 if (canvas)
265 g_object_ref(canvas);
266 if (data->canvas)
267 g_object_unref(data->canvas);
268 if (data->canvas != canvas) {
269 data->canvas = canvas;
270 g_signal_emit(area, _adg_signals[CANVAS_CHANGED], 0);
272 break;
273 case PROP_FACTOR:
274 data->factor = g_value_get_double(value);
275 break;
276 case PROP_AUTOZOOM:
277 data->autozoom = g_value_get_boolean(value);
278 break;
279 default:
280 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
281 break;
287 * adg_gtk_area_new:
289 * Creates a new empty #AdgGtkArea. The widget is useful only after
290 * an #AdgCanvas has been added either using the #AdgGtkArea:canvas
291 * property or with adg_gtk_area_set_canvas().
293 * Returns: the newly created widget
295 GtkWidget *
296 adg_gtk_area_new(void)
298 return g_object_new(ADG_GTK_TYPE_AREA, NULL);
302 * adg_gtk_area_new_with_canvas:
303 * @canvas: the #AdgCanvas shown by this widget
305 * Creates a new #AdgGtkArea and sets the #AdgGtkArea:canvas property
306 * to @canvas.
308 * Returns: the newly created widget
310 GtkWidget *
311 adg_gtk_area_new_with_canvas(AdgCanvas *canvas)
313 g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL);
315 return g_object_new(ADG_GTK_TYPE_AREA, "canvas", canvas, NULL);
319 * adg_gtk_area_set_canvas:
320 * @area: an #AdgGtkArea
321 * @canvas: the new #AdgCanvas
323 * Sets a new canvas on @area. The old canvas, if presents, is
324 * unreferenced.
326 void
327 adg_gtk_area_set_canvas(AdgGtkArea *area, AdgCanvas *canvas)
329 g_return_if_fail(ADG_GTK_IS_AREA(area));
330 g_object_set(area, "canvas", canvas, NULL);
334 * adg_gtk_area_get_canvas:
335 * @area: an #AdgGtkArea
337 * Gets the canvas associated to @area.
339 * Returns: the requested #AdgCanvas object or %NULL on errors
341 AdgCanvas *
342 adg_gtk_area_get_canvas(AdgGtkArea *area)
344 AdgGtkAreaPrivate *data;
346 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
348 data = area->data;
350 return data->canvas;
354 * adg_gtk_area_get_extents:
355 * @area: an #AdgGtkArea
357 * Gets the extents of the canvas bound to @area. The returned
358 * struct is owned by @area and should not modified or freed.
359 * This is a convenient function that gets the extents of the
360 * canvas bound to @area with adg_entity_get_extents().
362 * If @area still does not have any canvas associated to it or
363 * the canvas is invalid or empty, an undefined #CpmlExtents
364 * struct will be returned.
366 * The canvas will be updated before, meaning adg_entity_arrange()
367 * is called before the extents computation.
369 * Returns: the extents of the @area canvas or %NULL on errors
371 const CpmlExtents *
372 adg_gtk_area_get_extents(AdgGtkArea *area)
374 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
376 return _adg_get_extents(area);
380 * adg_gtk_area_get_zoom:
381 * @area: an #AdgGtkArea
383 * Gets the last zoom coefficient applied on the canvas of @area.
384 * If the #AdgGtkArea:autozoom property is %FALSE, the value
385 * returned should be always %1.
387 * Returns: the current zoom coefficient
389 gdouble
390 adg_gtk_area_get_zoom(AdgGtkArea *area)
392 AdgGtkAreaPrivate *data;
394 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
396 data = area->data;
397 return data->zoom;
401 * adg_gtk_area_set_factor:
402 * @area: an #AdgGtkArea
403 * @factor: the new zoom factor
405 * Sets a new zoom factor to @area. If the factor is less than
406 * 1, it will be clamped to 1.
408 void
409 adg_gtk_area_set_factor(AdgGtkArea *area, gdouble factor)
411 g_return_if_fail(ADG_GTK_IS_AREA(area));
412 g_object_set(area, "factor", factor, NULL);
416 * adg_gtk_area_get_factor:
417 * @area: an #AdgGtkArea
419 * Gets the zoom factor associated to @area. The zoom factor is
420 * directly used to zoom in (that is, the default zoom factor of
421 * 1.05 will zoom of 5% every iteration) and it is reversed while
422 * zooming out (that is, the default factor will be 1/1.05).
424 * Returns: the requested zoom factor or 0 on error
426 gdouble
427 adg_gtk_area_get_factor(AdgGtkArea *area)
429 AdgGtkAreaPrivate *data;
431 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
433 data = area->data;
434 return data->factor;
438 * adg_gtk_area_switch_autozoom:
439 * @area: an #AdgGtkArea
440 * @state: the new autozoom state
442 * Sets the #AdgGtkArea:autozoom property of @area to @state. When the
443 * autozoom feature is enabled, @area reacts to any size allocation
444 * by adjusting its zoom coefficient in global space. This means the
445 * drawing will fill the available space (keeping its aspect ratio)
446 * when resizing the window.
448 void
449 adg_gtk_area_switch_autozoom(AdgGtkArea *area, gboolean state)
451 g_return_if_fail(ADG_GTK_IS_AREA(area));
452 g_object_set(area, "autozoom", state, NULL);
456 * adg_gtk_area_has_autozoom:
457 * @area: an #AdgGtkArea
459 * Gets the current state of the #AdgGtkArea:autozoom property on
460 * the @area object.
462 * Returns: the current autozoom state
464 gboolean
465 adg_gtk_area_has_autozoom(AdgGtkArea *area)
467 AdgGtkAreaPrivate *data;
469 g_return_val_if_fail(ADG_GTK_IS_AREA(area), FALSE);
471 data = area->data;
472 return data->autozoom;
476 * adg_gtk_area_canvas_changed:
477 * @area: an #AdgGtkArea
478 * @canvas: the old canvas bound to @area
480 * Emits the #AdgGtkArea::canvas-changed signal on @area.
482 void
483 adg_gtk_area_canvas_changed(AdgGtkArea *area, AdgCanvas *old_canvas)
485 g_return_if_fail(ADG_GTK_IS_AREA(area));
487 g_signal_emit(area, _adg_signals[CANVAS_CHANGED], 0, old_canvas);
491 * adg_gtk_area_extents_changed:
492 * @area: an #AdgGtkArea
493 * @old_extents: the old extents of @area
495 * Emits the #AdgGtkArea::extents-changed signal on @area.
497 void
498 adg_gtk_area_extents_changed(AdgGtkArea *area, const CpmlExtents *old_extents)
500 g_return_if_fail(ADG_GTK_IS_AREA(area));
502 g_signal_emit(area, _adg_signals[EXTENTS_CHANGED], 0, old_extents);
506 static void
507 _adg_size_request(GtkWidget *widget, GtkRequisition *requisition)
509 AdgGtkArea *area;
510 const CpmlExtents *extents;
512 area = (AdgGtkArea *) widget;
513 extents = _adg_get_extents(area);
515 if (extents->is_defined) {
516 AdgGtkAreaPrivate *data;
517 AdgCanvas *canvas;
518 gdouble top, right, bottom, left;
520 data = area->data;
521 canvas = data->canvas;
522 top = adg_canvas_get_top_margin(canvas);
523 right = adg_canvas_get_right_margin(canvas);
524 bottom = adg_canvas_get_bottom_margin(canvas);
525 left = adg_canvas_get_left_margin(canvas);
527 requisition->width = extents->size.x + left + right;
528 requisition->height = extents->size.y + top + bottom;
533 * _adg_size_allocate:
534 * @widget: an #AdgGtkArea widget
535 * @allocation: the new allocation struct
537 * Scales the drawing according to the new allocation.
539 * TODO: the current implementation keeps the point in the center
540 * of the #AdgCanvas at the center of the new #AdgGtkArea. This is
541 * not what a user would likely expect because all the eventual
542 * translations in global space will be lost. In other words the
543 * resulting drawing is always centered also when the original
544 * drawing (that is, the drawing before the new size allocation)
545 * was not.
547 static void
548 _adg_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
550 AdgGtkArea *area;
551 AdgGtkAreaPrivate *data;
552 AdgCanvas *canvas;
553 const CpmlExtents *extents;
554 gdouble top, right, bottom, left;
555 gdouble vmargins, hmargins;
556 AdgEntity *entity;
557 CpmlVector size;
558 AdgPair ratio;
559 AdgMatrix map;
561 if (_ADG_OLD_WIDGET_CLASS->size_allocate)
562 _ADG_OLD_WIDGET_CLASS->size_allocate(widget, allocation);
564 area = (AdgGtkArea *) widget;
565 data = area->data;
567 canvas = data->canvas;
568 if (canvas == NULL)
569 return;
571 extents = _adg_get_extents(area);
572 if (!extents->is_defined || extents->size.x <= 0 || extents->size.y <= 0)
573 return;
575 top = adg_canvas_get_top_margin(canvas);
576 right = adg_canvas_get_right_margin(canvas);
577 bottom = adg_canvas_get_bottom_margin(canvas);
578 left = adg_canvas_get_left_margin(canvas);
579 vmargins = top + bottom;
580 hmargins = left + right;
582 /* Check if the allocated space is enough:
583 * if not, there is not much we can do... */
584 g_return_if_fail(allocation->width > hmargins);
585 g_return_if_fail(allocation->height > vmargins);
587 entity = (AdgEntity *) canvas;
588 size.x = allocation->width - hmargins;
589 size.y = allocation->height - vmargins;
591 if (data->autozoom) {
592 /* Adjust the zoom according to the allocation and drawing size */
593 ratio.x = size.x / extents->size.x;
594 ratio.y = size.y / extents->size.y;
595 data->zoom = MIN(ratio.x, ratio.y);
596 } else {
597 /* Zoom default value */
598 data->zoom = 1;
601 adg_matrix_copy(&map, adg_entity_get_global_map(entity));
603 /* TODO: plan different attachment policies other than centering */
604 map.x0 = (size.x - extents->size.x * data->zoom) / 2;
605 map.y0 = (size.y - extents->size.y * data->zoom) / 2;
607 map.xx *= data->zoom;
608 map.yy *= data->zoom;
609 adg_entity_set_global_map(entity, &map);
612 static gboolean
613 _adg_expose_event(GtkWidget *widget, GdkEventExpose *event)
615 AdgGtkAreaPrivate *data;
616 AdgCanvas *canvas;
618 data = ((AdgGtkArea *) widget)->data;
619 canvas = data->canvas;
621 if (canvas && event->window) {
622 cairo_t *cr = gdk_cairo_create(event->window);
623 cairo_translate(cr,
624 adg_canvas_get_left_margin(canvas),
625 adg_canvas_get_top_margin(canvas));
626 adg_entity_render((AdgEntity *) canvas, cr);
627 cairo_destroy(cr);
630 if (_ADG_OLD_WIDGET_CLASS->expose_event == NULL)
631 return FALSE;
633 return _ADG_OLD_WIDGET_CLASS->expose_event(widget, event);
636 static gboolean
637 _adg_scroll_event(GtkWidget *widget, GdkEventScroll *event)
639 gboolean zoom_in, zoom_out, local_space, global_space;
640 AdgMatrix map, inverted;
641 AdgGtkAreaPrivate *data;
642 double factor, x, y;
644 zoom_in = event->direction == GDK_SCROLL_UP;
645 zoom_out = event->direction == GDK_SCROLL_DOWN;
646 local_space = (event->state & ADG_GTK_MODIFIERS) == 0;
647 global_space = (event->state & ADG_GTK_MODIFIERS) == GDK_SHIFT_MASK;
649 if ((zoom_in || zoom_out) && (local_space || global_space) &&
650 _adg_get_map(widget, local_space, &map, &inverted)) {
651 data = ((AdgGtkArea *) widget)->data;
652 factor = zoom_in ? data->factor : 1. / data->factor;
653 x = event->x;
654 y = event->y;
656 cairo_matrix_transform_point(&inverted, &x, &y);
657 cairo_matrix_scale(&map, factor, factor);
658 cairo_matrix_translate(&map, x/factor - x, y/factor - y);
660 _adg_set_map(widget, local_space, &map);
662 gtk_widget_queue_draw(widget);
665 if (_ADG_OLD_WIDGET_CLASS->scroll_event == NULL)
666 return FALSE;
668 return _ADG_OLD_WIDGET_CLASS->scroll_event(widget, event);
671 static gboolean
672 _adg_button_press_event(GtkWidget *widget, GdkEventButton *event)
674 AdgGtkAreaPrivate *data = ((AdgGtkArea *) widget)->data;
676 if (event->type == GDK_BUTTON_PRESS && event->button == 2) {
677 /* Remember the starting coordinates of a (probable) translation */
678 data->x_event = event->x;
679 data->y_event = event->y;
682 if (_ADG_OLD_WIDGET_CLASS->button_press_event == NULL)
683 return FALSE;
685 return _ADG_OLD_WIDGET_CLASS->button_press_event(widget, event);
688 static gboolean
689 _adg_motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
691 gboolean translating, local_space, global_space;
692 AdgMatrix map, inverted;
693 AdgGtkAreaPrivate *data;
694 double x, y;
696 translating = (event->state & GDK_BUTTON2_MASK) == GDK_BUTTON2_MASK;
697 local_space = (event->state & ADG_GTK_MODIFIERS) == 0;
698 global_space = (event->state & ADG_GTK_MODIFIERS) == GDK_SHIFT_MASK;
700 if (translating && (local_space || global_space) &&
701 _adg_get_map(widget, local_space, &map, &inverted)) {
702 data = ((AdgGtkArea *) widget)->data;
703 x = event->x - data->x_event;
704 y = event->y - data->y_event;
706 cairo_matrix_transform_distance(&inverted, &x, &y);
707 cairo_matrix_translate(&map, x, y);
708 data->x_event = event->x;
709 data->y_event = event->y;
711 _adg_set_map(widget, local_space, &map);
713 gtk_widget_queue_draw(widget);
716 if (_ADG_OLD_WIDGET_CLASS->motion_notify_event == NULL)
717 return FALSE;
719 return _ADG_OLD_WIDGET_CLASS->motion_notify_event(widget, event);
722 static gboolean
723 _adg_get_map(GtkWidget *widget, gboolean local_space,
724 AdgMatrix *map, AdgMatrix *inverted)
726 AdgGtkAreaPrivate *data;
727 AdgEntity *entity;
729 data = ((AdgGtkArea *) widget)->data;
730 entity = (AdgEntity *) data->canvas;
731 if (entity == NULL)
732 return FALSE;
734 if (local_space) {
735 adg_matrix_copy(map, adg_entity_get_local_map(entity));
737 /* The inverted map is subject to the global matrix */
738 adg_matrix_copy(inverted, adg_entity_get_global_matrix(entity));
739 adg_matrix_transform(inverted, map, ADG_TRANSFORM_BEFORE);
740 } else {
741 adg_matrix_copy(map, adg_entity_get_global_map(entity));
742 adg_matrix_copy(inverted, map);
745 return cairo_matrix_invert(inverted) == CAIRO_STATUS_SUCCESS;
748 static void
749 _adg_set_map(GtkWidget *widget, gboolean local_space, const AdgMatrix *map)
751 AdgGtkAreaPrivate *data;
752 AdgEntity *entity;
754 data = ((AdgGtkArea *) widget)->data;
755 entity = (AdgEntity *) data->canvas;
757 if (entity == NULL)
758 return;
760 if (local_space)
761 adg_entity_set_local_map(entity, map);
762 else
763 adg_entity_set_global_map(entity, map);
765 _adg_get_extents((AdgGtkArea *) widget);
768 static const CpmlExtents *
769 _adg_get_extents(AdgGtkArea *area)
771 AdgGtkAreaPrivate *data;
772 AdgCanvas *canvas;
773 CpmlExtents old_extents;
775 data = area->data;
776 old_extents = data->extents;
777 data->extents.is_defined = FALSE;
778 canvas = data->canvas;
780 if (ADG_IS_CANVAS(canvas)) {
781 const CpmlExtents *extents;
782 AdgEntity *entity;
784 entity = (AdgEntity *) canvas;
786 adg_entity_arrange(entity);
787 extents = adg_entity_get_extents(entity);
789 if (extents != NULL)
790 data->extents = *extents;
793 if (!cpml_extents_equal(&data->extents, &old_extents))
794 g_signal_emit(area, _adg_signals[EXTENTS_CHANGED], 0, &old_extents);
796 return &data->extents;