doc: update copyright line for 2021
[adg.git] / src / adg / adg-gtk-area.c
blob19c90ef2df39b39408917e811ab68a5ef8d94625
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2021 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 widget specifically designed to contain
24 * an ADG canvas
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 minimum size of the widget will depend on the canvas content.
33 * The default implementation reacts to some mouse events: if you drag
34 * the mouse keeping the wheel pressed, the canvas will be translated
35 * (in local space by default and in global space if <keycap>SHIFT</keycap>
36 * is pressed); if the mouse wheel is rotated, the canvas will be scaled
37 * up or down according to the wheel direction by the factor specified
38 * in the #AdgGtkArea:factor property (again, in local space by default
39 * and in global space if <keycap>SHIFT</keycap> is pressed). The
40 * adg_gtk_area_get_zoom() method could be used to retrieve the current
41 * zoom coefficient.
43 * A new transformation layer is present between the global space
44 * and the rendering: the #AdgGtkArea:render-map matrix. This
45 * transformation is applied just before the rendering and it is
46 * used to align and/or apply the zoom coefficient to the canvas
47 * without affecting the other layers. Local transformations,
48 * instead, are directly applied to the local matrix of the canvas.
50 * Since: 1.0
51 **/
54 /**
55 * AdgGtkArea:
57 * All fields are private and should not be used directly.
58 * Use its public methods instead.
60 * Since: 1.0
61 **/
63 /**
64 * AdgGtkAreaClass:
65 * @canvas_changed: signals that a new #AdgCanvas is bound to this widget.
66 * @extents_changed: signals that the extents on the underling #AdgCanvas
67 * has been changed.
69 * The default @canvas_changed resets the internal initialization flag, so at
70 * the first call to the <function>size_allocate</function> method the zoom
71 * factor is set to 1.
73 * The default @extents_changed signal does not do anything: it is intended as
74 * a hook for derived class for refreshing GUI elements (such as scrollbars)
75 * whenever the boundary box changes.
77 * Since: 1.0
78 **/
81 #include "adg-internal.h"
82 #include <gtk/gtk.h>
84 #include "adg-container.h"
85 #include "adg-table.h"
86 #include "adg-title-block.h"
87 #include <adg-canvas.h>
88 #include "adg-gtk-utils.h"
89 #include "adg-cairo-fallback.h"
91 #include "adg-gtk-area.h"
92 #include "adg-gtk-area-private.h"
94 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_gtk_area_parent_class)
95 #define _ADG_OLD_WIDGET_CLASS ((GtkWidgetClass *) adg_gtk_area_parent_class)
98 G_DEFINE_TYPE_WITH_PRIVATE(AdgGtkArea, adg_gtk_area, GTK_TYPE_DRAWING_AREA)
100 enum {
101 PROP_0,
102 PROP_CANVAS,
103 PROP_FACTOR,
104 PROP_AUTOZOOM,
105 PROP_RENDER_MAP
108 enum {
109 CANVAS_CHANGED,
110 EXTENTS_CHANGED,
111 LAST_SIGNAL
115 static guint _adg_signals[LAST_SIGNAL] = { 0 };
118 static const CpmlExtents *
119 _adg_get_extents(AdgGtkArea *area)
121 AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private(area);
122 CpmlExtents old_extents = data->extents;
123 AdgCanvas *canvas = data->canvas;
125 data->extents.is_defined = FALSE;
127 if (ADG_IS_CANVAS(canvas)) {
128 const CpmlExtents *extents;
129 AdgEntity *entity;
131 entity = (AdgEntity *) canvas;
133 adg_entity_arrange(entity);
134 extents = adg_entity_get_extents(entity);
136 if (extents != NULL) {
137 data->extents = *extents;
138 adg_canvas_apply_margins(canvas, &data->extents);
139 cpml_extents_transform(&data->extents, &data->render_map);
143 if (!cpml_extents_equal(&data->extents, &old_extents))
144 g_signal_emit(area, _adg_signals[EXTENTS_CHANGED], 0, &old_extents);
146 return &data->extents;
150 static void
151 _adg_get_property(GObject *object, guint prop_id,
152 GValue *value, GParamSpec *pspec)
154 AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) object);
156 switch (prop_id) {
157 case PROP_CANVAS:
158 g_value_set_object(value, data->canvas);
159 break;
160 case PROP_FACTOR:
161 g_value_set_double(value, data->factor);
162 break;
163 case PROP_AUTOZOOM:
164 g_value_set_boolean(value, data->autozoom);
165 break;
166 case PROP_RENDER_MAP:
167 g_value_set_boxed(value, &data->render_map);
168 break;
169 default:
170 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
171 break;
175 static void
176 _adg_set_property(GObject *object, guint prop_id,
177 const GValue *value, GParamSpec *pspec)
179 AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) object);
180 AdgCanvas *new_canvas, *old_canvas;
182 switch (prop_id) {
183 case PROP_CANVAS:
184 new_canvas = g_value_get_object(value);
185 old_canvas = data->canvas;
186 if (new_canvas != old_canvas) {
187 if (new_canvas != NULL)
188 g_object_ref(new_canvas);
189 if (old_canvas != NULL)
190 g_object_unref(old_canvas);
191 data->canvas = new_canvas;
192 g_signal_emit(object, _adg_signals[CANVAS_CHANGED], 0, old_canvas);
194 break;
195 case PROP_FACTOR:
196 data->factor = g_value_get_double(value);
197 break;
198 case PROP_AUTOZOOM:
199 data->autozoom = g_value_get_boolean(value);
200 break;
201 case PROP_RENDER_MAP:
202 adg_matrix_copy(&data->render_map, g_value_get_boxed(value));
203 break;
204 default:
205 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
206 break;
211 static void
212 _adg_dispose(GObject *object)
214 AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) object);
216 if (data->canvas) {
217 g_object_unref(data->canvas);
218 data->canvas = NULL;
221 if (_ADG_OLD_OBJECT_CLASS->dispose)
222 _ADG_OLD_OBJECT_CLASS->dispose(object);
226 * _adg_size_allocate:
227 * @widget: an #AdgGtkArea widget
228 * @allocation: the new allocation struct
230 * Scales the drawing according to the new allocation if
231 * #AdgGtkArea:autozoom is <constant>TRUE</constant>.
233 * TODO: the current implementation initially centers the canvas
234 * on the allocation space. Further allocations (due to a
235 * window resizing, for example) use the top/left corner of the
236 * canvas as reference point. Plan different policies for either
237 * those situations.
239 * Since: 1.0
241 static void
242 _adg_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
244 AdgGtkArea *area = (AdgGtkArea *) widget;
245 AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private(area);
246 const CpmlExtents *sheet;
247 CpmlVector size;
248 gdouble factor;
250 if (_ADG_OLD_WIDGET_CLASS->size_allocate)
251 _ADG_OLD_WIDGET_CLASS->size_allocate(widget, allocation);
253 sheet = _adg_get_extents(area);
254 if (!sheet->is_defined || sheet->size.x <= 0 || sheet->size.y <= 0)
255 return;
257 size.x = allocation->width;
258 size.y = allocation->height;
260 if (data->autozoom) {
261 /* Adjust the zoom according to the allocation and sheet size */
262 factor = MIN(size.x / sheet->size.x, size.y / sheet->size.y);
263 } else if (!data->initialized) {
264 /* First allocation with autozoom off: keep the current zoom */
265 factor = 1;
266 } else {
267 /* Not the first allocation and autozoom off: keep the old map */
268 return;
271 if (!data->initialized) {
272 /* TODO: plan different attachment policies other than centering */
273 cairo_matrix_init_translate(&data->render_map,
274 (size.x - sheet->size.x) / 2 - sheet->org.x,
275 (size.y - sheet->size.y) / 2 - sheet->org.y);
276 data->initialized = TRUE;
279 /* TODO: plan other reference points other than left/top (x0, y0) */
280 data->render_map.x0 *= factor;
281 data->render_map.y0 *= factor;
282 data->render_map.xx *= factor;
283 data->render_map.yy *= factor;
286 static gboolean
287 _adg_get_map(GtkWidget *widget, gboolean local_space,
288 cairo_matrix_t *map, cairo_matrix_t *inverted)
290 AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) widget);
291 AdgEntity *entity = (AdgEntity *) data->canvas;
293 if (entity == NULL)
294 return FALSE;
296 if (local_space) {
297 adg_matrix_copy(map, adg_entity_get_local_map(entity));
299 /* The inverted map is subject to the global matrix */
300 adg_matrix_copy(inverted, adg_entity_get_global_matrix(entity));
301 adg_matrix_transform(inverted, map, ADG_TRANSFORM_BEFORE);
302 } else {
303 adg_matrix_copy(map, adg_entity_get_global_map(entity));
304 adg_matrix_copy(inverted, map);
307 return cairo_matrix_invert(inverted) == CAIRO_STATUS_SUCCESS;
310 static void
311 _adg_set_map(GtkWidget *widget,
312 gboolean local_space,
313 const cairo_matrix_t *map)
315 AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) widget);
316 AdgEntity *entity = (AdgEntity *) data->canvas;
318 if (entity == NULL)
319 return;
321 if (local_space) {
322 /* TODO: this forcibly overwrites any local transformation */
323 adg_entity_set_local_map(entity, map);
324 } else {
325 adg_matrix_transform(&data->render_map, map, ADG_TRANSFORM_BEFORE);
328 /* This will emit the extents-changed signal when applicable */
329 _adg_get_extents((AdgGtkArea *) widget);
332 static gboolean
333 _adg_scroll_event(GtkWidget *widget, GdkEventScroll *event)
335 gboolean zoom_in, zoom_out, local_space, global_space;
336 cairo_matrix_t map, inverted;
338 zoom_in = event->direction == GDK_SCROLL_UP;
339 zoom_out = event->direction == GDK_SCROLL_DOWN;
340 local_space = (event->state & ADG_GTK_MODIFIERS) == 0;
341 global_space = (event->state & ADG_GTK_MODIFIERS) == GDK_SHIFT_MASK;
343 if ((zoom_in || zoom_out) && (local_space || global_space) &&
344 _adg_get_map(widget, local_space, &map, &inverted)) {
345 AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) widget);
346 double factor = zoom_in ? data->factor : 1. / data->factor;
347 gdouble x = event->x;
348 gdouble y = event->y;
350 cairo_matrix_transform_point(&inverted, &x, &y);
351 cairo_matrix_scale(&map, factor, factor);
352 cairo_matrix_translate(&map, x/factor - x, y/factor - y);
354 _adg_set_map(widget, local_space, &map);
356 gtk_widget_queue_draw(widget);
358 /* Avoid to chain up the default handler:
359 * this event has been grabbed by this function */
360 return TRUE;
363 if (_ADG_OLD_WIDGET_CLASS->scroll_event == NULL)
364 return FALSE;
366 return _ADG_OLD_WIDGET_CLASS->scroll_event(widget, event);
369 static gboolean
370 _adg_button_press_event(GtkWidget *widget, GdkEventButton *event)
372 if (event->type == GDK_BUTTON_PRESS && event->button == 2) {
373 AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) widget);
374 /* Remember the starting coordinates of a (probable) translation */
375 data->x_event = event->x;
376 data->y_event = event->y;
379 if (_ADG_OLD_WIDGET_CLASS->button_press_event == NULL)
380 return FALSE;
382 return _ADG_OLD_WIDGET_CLASS->button_press_event(widget, event);
385 static gboolean
386 _adg_motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
388 gboolean translating, local_space, global_space;
389 cairo_matrix_t map, inverted;
391 translating = (event->state & GDK_BUTTON2_MASK) == GDK_BUTTON2_MASK;
392 local_space = (event->state & ADG_GTK_MODIFIERS) == 0;
393 global_space = (event->state & ADG_GTK_MODIFIERS) == GDK_SHIFT_MASK;
395 if (translating && (local_space || global_space) &&
396 _adg_get_map(widget, local_space, &map, &inverted)) {
397 AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) widget);
398 gdouble x = event->x - data->x_event;
399 gdouble y = event->y - data->y_event;
401 cairo_matrix_transform_distance(&inverted, &x, &y);
402 cairo_matrix_translate(&map, x, y);
403 data->x_event = event->x;
404 data->y_event = event->y;
406 _adg_set_map(widget, local_space, &map);
408 gtk_widget_queue_draw(widget);
410 /* Avoid to chain up the default handler:
411 * this event has been grabbed by this function */
412 return TRUE;
415 if (_ADG_OLD_WIDGET_CLASS->motion_notify_event == NULL)
416 return FALSE;
418 return _ADG_OLD_WIDGET_CLASS->motion_notify_event(widget, event);
421 static void
422 _adg_canvas_changed(AdgGtkArea *area, AdgCanvas *old_canvas)
424 AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private(area);
425 data->initialized = FALSE;
428 #ifdef GTK2_ENABLED
430 static void
431 _adg_size_request(GtkWidget *widget, GtkRequisition *requisition)
433 AdgGtkArea *area = (AdgGtkArea *) widget;
434 const CpmlExtents *extents = _adg_get_extents(area);
436 if (extents->is_defined) {
437 requisition->width = extents->size.x;
438 requisition->height = extents->size.y;
442 static gboolean
443 _adg_expose_event(GtkWidget *widget, GdkEventExpose *event)
445 AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) widget);
446 AdgCanvas *canvas = data->canvas;
448 if (canvas != NULL && event->window != NULL) {
449 cairo_t *cr = gdk_cairo_create(event->window);
450 cairo_transform(cr, &data->render_map);
451 adg_entity_render((AdgEntity *) canvas, cr);
452 cairo_destroy(cr);
455 if (_ADG_OLD_WIDGET_CLASS->expose_event == NULL)
456 return FALSE;
458 return _ADG_OLD_WIDGET_CLASS->expose_event(widget, event);
461 #endif
463 #ifdef GTK3_ENABLED
465 static void
466 _adg_get_preferred_height(GtkWidget *widget,
467 gint *minimum_height, gint *natural_height)
469 AdgGtkArea *area;
470 const CpmlExtents *extents;
472 area = (AdgGtkArea *) widget;
473 extents = _adg_get_extents(area);
475 if (extents->is_defined) {
476 *minimum_height = extents->size.y;
477 *natural_height = *minimum_height;
481 static void
482 _adg_get_preferred_height_for_width(GtkWidget *widget, gint width,
483 gint *minimum_height, gint *natural_height)
485 AdgGtkArea *area;
486 const CpmlExtents *extents;
488 area = (AdgGtkArea *) widget;
489 extents = _adg_get_extents(area);
491 if (extents->is_defined && extents->size.x > 0) {
492 *minimum_height = extents->size.y;
493 *natural_height = *minimum_height * width / extents->size.x;
497 static void
498 _adg_get_preferred_width(GtkWidget *widget,
499 gint *minimum_width, gint *natural_width)
501 AdgGtkArea *area;
502 const CpmlExtents *extents;
504 area = (AdgGtkArea *) widget;
505 extents = _adg_get_extents(area);
507 if (extents->is_defined) {
508 *minimum_width = extents->size.x;
509 *natural_width = *minimum_width;
513 static void
514 _adg_get_preferred_width_for_height(GtkWidget *widget, gint height,
515 gint *minimum_width, gint *natural_width)
517 AdgGtkArea *area;
518 const CpmlExtents *extents;
520 area = (AdgGtkArea *) widget;
521 extents = _adg_get_extents(area);
523 if (extents->is_defined && extents->size.y > 0) {
524 *minimum_width = extents->size.x;
525 *natural_width = *minimum_width * height / extents->size.y;
529 static gboolean
530 _adg_draw(GtkWidget *widget, cairo_t *cr)
532 AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) widget);
533 AdgCanvas *canvas = data->canvas;
535 if (canvas != NULL) {
536 cairo_transform(cr, &data->render_map);
537 adg_entity_render((AdgEntity *) canvas, cr);
540 return FALSE;
543 #endif
545 static void
546 adg_gtk_area_class_init(AdgGtkAreaClass *klass)
548 GObjectClass *gobject_class;
549 GtkWidgetClass *widget_class;
550 GParamSpec *param;
552 gobject_class = (GObjectClass *) klass;
553 widget_class = (GtkWidgetClass *) klass;
555 gobject_class->dispose = _adg_dispose;
556 gobject_class->get_property = _adg_get_property;
557 gobject_class->set_property = _adg_set_property;
559 #ifdef GTK2_ENABLED
560 widget_class->size_request = _adg_size_request;
561 widget_class->expose_event = _adg_expose_event;
562 #endif
564 #ifdef GTK3_ENABLED
565 widget_class->get_preferred_height = _adg_get_preferred_height;
566 widget_class->get_preferred_height_for_width = _adg_get_preferred_height_for_width;
567 widget_class->get_preferred_width = _adg_get_preferred_width;
568 widget_class->get_preferred_width_for_height = _adg_get_preferred_width_for_height;
569 widget_class->draw = _adg_draw;
570 #endif
572 widget_class->size_allocate = _adg_size_allocate;
573 widget_class->scroll_event = _adg_scroll_event;
574 widget_class->button_press_event = _adg_button_press_event;
575 widget_class->motion_notify_event = _adg_motion_notify_event;
577 klass->canvas_changed = _adg_canvas_changed;
579 param = g_param_spec_object("canvas",
580 P_("Canvas"),
581 P_("The canvas to be shown"),
582 ADG_TYPE_CANVAS,
583 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
584 g_object_class_install_property(gobject_class, PROP_CANVAS, param);
586 param = g_param_spec_double("factor",
587 P_("Factor"),
588 P_("The factor to use while zooming in and out (usually with the mouse wheel)"),
589 1., G_MAXDOUBLE, 1.05,
590 G_PARAM_READWRITE);
591 g_object_class_install_property(gobject_class, PROP_FACTOR, param);
593 param = g_param_spec_boolean("autozoom",
594 P_("Autozoom"),
595 P_("When enabled, automatically adjust the zoom in global space at every size allocation"),
596 FALSE,
597 G_PARAM_READWRITE);
598 g_object_class_install_property(gobject_class, PROP_AUTOZOOM, param);
600 param = g_param_spec_boxed("render-map",
601 P_("Render Map"),
602 P_("The transformation to be applied on the canvas before rendering it"),
603 CAIRO_GOBJECT_TYPE_MATRIX,
604 G_PARAM_READWRITE);
605 g_object_class_install_property(gobject_class, PROP_RENDER_MAP, param);
608 * AdgGtkArea::canvas-changed:
609 * @area: an #AdgGtkArea
610 * @old_canvas: (type AdgCanvas*): the old #AdgCanvas object
612 * Emitted after the canvas bound to @area has been changed. The old
613 * canvas accessible from @old_canvas while the new canvas can be got
614 * with the usual API, e.g. adg_gtk_area_get_canvas().
616 * Since: 1.0
618 _adg_signals[CANVAS_CHANGED] =
619 g_signal_new("canvas-changed", ADG_GTK_TYPE_AREA,
620 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
621 G_STRUCT_OFFSET(AdgGtkAreaClass, canvas_changed),
622 NULL, NULL,
623 g_cclosure_marshal_VOID__OBJECT,
624 G_TYPE_NONE, 1, ADG_TYPE_CANVAS);
627 * AdgGtkArea::extents-changed:
628 * @area: an #AdgGtkArea
629 * @old_extents: the old #CpmlExtents struct
631 * Emitted when the extents of @area have been changed.
632 * The old extents are always compared to the new ones,
633 * so when the extents are recalculated but the result
634 * is the same the signal is not emitted.
636 * The extents of #AdgGtkArea are subject to the render
637 * map, so changing the #AdgGtkArea:render-map property
638 * will emit this signal too.
640 * Since: 1.0
642 _adg_signals[EXTENTS_CHANGED] =
643 g_signal_new("extents-changed", ADG_GTK_TYPE_AREA,
644 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
645 G_STRUCT_OFFSET(AdgGtkAreaClass, extents_changed),
646 NULL, NULL,
647 g_cclosure_marshal_VOID__POINTER,
648 G_TYPE_NONE, 1, G_TYPE_POINTER);
651 static void
652 adg_gtk_area_init(AdgGtkArea *area)
654 AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private(area);
655 data->canvas = NULL;
656 data->factor = 1.05;
657 data->autozoom = FALSE;
658 cairo_matrix_init_identity(&data->render_map);
659 data->initialized = FALSE;
660 data->x_event = 0;
661 data->y_event = 0;
663 /* Enable GDK events to catch wheel rotation and drag */
664 gtk_widget_add_events((GtkWidget *) area,
665 GDK_BUTTON_PRESS_MASK |
666 GDK_BUTTON2_MOTION_MASK |
667 GDK_SCROLL_MASK);
674 * adg_gtk_area_new:
676 * Creates a new empty #AdgGtkArea. The widget is useful only after
677 * an #AdgCanvas has been added either using the #AdgGtkArea:canvas
678 * property or with adg_gtk_area_set_canvas().
680 * Returns: the newly created widget
682 * Since: 1.0
684 GtkWidget *
685 adg_gtk_area_new(void)
687 return g_object_new(ADG_GTK_TYPE_AREA, NULL);
691 * adg_gtk_area_new_with_canvas:
692 * @canvas: the #AdgCanvas shown by this widget
694 * Creates a new #AdgGtkArea and sets the #AdgGtkArea:canvas property
695 * to @canvas.
697 * Returns: the newly created widget
699 * Since: 1.0
701 GtkWidget *
702 adg_gtk_area_new_with_canvas(AdgCanvas *canvas)
704 g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL);
706 return g_object_new(ADG_GTK_TYPE_AREA, "canvas", canvas, NULL);
710 * adg_gtk_area_set_canvas:
711 * @area: an #AdgGtkArea
712 * @canvas: the new #AdgCanvas
714 * Sets a new canvas on @area. The old canvas, if presents, is
715 * unreferenced.
717 * Since: 1.0
719 void
720 adg_gtk_area_set_canvas(AdgGtkArea *area, AdgCanvas *canvas)
722 g_return_if_fail(ADG_GTK_IS_AREA(area));
723 g_object_set(area, "canvas", canvas, NULL);
727 * adg_gtk_area_get_canvas:
728 * @area: an #AdgGtkArea
730 * Gets the canvas associated to @area. The returned canvas
731 * is owned by @area and should not be modified or freed.
733 * Returns: (transfer none): the requested #AdgCanvas object or <constant>NULL</constant> on errors.
735 * Since: 1.0
737 AdgCanvas *
738 adg_gtk_area_get_canvas(AdgGtkArea *area)
740 AdgGtkAreaPrivate *data;
742 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
744 data = adg_gtk_area_get_instance_private(area);
745 return data->canvas;
749 * adg_gtk_area_set_render_map:
750 * @area: an #AdgGtkArea object
751 * @map: the new map
753 * Sets the new render transformation of @area to @map: the
754 * old map is discarded. If @map is <constant>NULL</constant>,
755 * the render map is left unchanged.
757 * <note><para>
758 * The render map is an implementation detail and this function
759 * is expected to be used only by #AdgGtkArea derived objects.
760 * </para></note>
762 * Since: 1.0
764 void
765 adg_gtk_area_set_render_map(AdgGtkArea *area, const cairo_matrix_t *map)
767 g_return_if_fail(ADG_GTK_IS_AREA(area));
768 g_object_set(area, "render-map", map, NULL);
772 * adg_gtk_area_transform_render_map:
773 * @area: an #AdgGtkArea object
774 * @transformation: the transformation to apply
775 * @mode: how @transformation should be applied
777 * Convenient function to change the render map of @area by
778 * applying @tranformation using the @mode operator. This is
779 * logically equivalent to the following:
781 * <informalexample><programlisting language="C">
782 * cairo_matrix_t map;
783 * adg_matrix_copy(&map, adg_gtk_area_get_render_map(area));
784 * adg_matrix_transform(&map, transformation, mode);
785 * adg_gtk_area_set_render_map(area, &map);
786 * </programlisting></informalexample>
788 * <note><para>
789 * The render map is an implementation detail and this function
790 * is expected to be used only by #AdgGtkArea derived objects.
791 * </para></note>
793 * Since: 1.0
795 void
796 adg_gtk_area_transform_render_map(AdgGtkArea *area,
797 const cairo_matrix_t *transformation,
798 AdgTransformMode mode)
800 AdgGtkAreaPrivate *data;
801 cairo_matrix_t map;
803 g_return_if_fail(ADG_GTK_IS_AREA(area));
804 g_return_if_fail(transformation != NULL);
806 data = adg_gtk_area_get_instance_private(area);
808 adg_matrix_copy(&map, &data->render_map);
809 adg_matrix_transform(&map, transformation, mode);
811 g_object_set(area, "render-map", &map, NULL);
815 * adg_gtk_area_get_render_map:
816 * @area: an #AdgGtkArea object
818 * Gets the render map.
820 * Returns: the requested map or <constant>NULL</constant> on errors.
822 * Since: 1.0
824 const cairo_matrix_t *
825 adg_gtk_area_get_render_map(AdgGtkArea *area)
827 AdgGtkAreaPrivate *data;
829 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
831 data = adg_gtk_area_get_instance_private(area);
832 return &data->render_map;
836 * adg_gtk_area_get_extents:
837 * @area: an #AdgGtkArea
839 * Gets the extents of the canvas bound to @area. The returned
840 * struct is owned by @area and should not modified or freed.
842 * The extents of an #AdgGtkArea instance are the extents of
843 * its canvas (as returned by adg_entity_get_extents()) with
844 * the margins added to it and the #AdgGtkArea:render-map
845 * transformation applied.
847 * If @area does not have any canvas associated to it or the
848 * canvas is invalid or empty, an undefined #CpmlExtents
849 * struct will be returned.
851 * The canvas will be updated, meaning adg_entity_arrange()
852 * is called before the extents computation.
854 * Returns: the extents of the @area canvas or <constant>NULL</constant> on errors.
856 * Since: 1.0
858 const CpmlExtents *
859 adg_gtk_area_get_extents(AdgGtkArea *area)
861 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
863 return _adg_get_extents(area);
867 * adg_gtk_area_get_zoom:
868 * @area: an #AdgGtkArea
870 * Gets the last zoom coefficient applied on the canvas of @area.
871 * If the #AdgGtkArea:autozoom property is <constant>FALSE</constant>,
872 * the value returned should be always 1.
874 * Returns: the current zoom coefficient.
876 * Since: 1.0
878 gdouble
879 adg_gtk_area_get_zoom(AdgGtkArea *area)
881 AdgGtkAreaPrivate *data;
883 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
885 data = adg_gtk_area_get_instance_private(area);
886 return data->render_map.xx;
890 * adg_gtk_area_set_factor:
891 * @area: an #AdgGtkArea
892 * @factor: the new zoom factor
894 * Sets a new zoom factor to @area. If the factor is less than
895 * 1, it will be clamped to 1.
897 * Since: 1.0
899 void
900 adg_gtk_area_set_factor(AdgGtkArea *area, gdouble factor)
902 g_return_if_fail(ADG_GTK_IS_AREA(area));
903 g_object_set(area, "factor", factor, NULL);
907 * adg_gtk_area_get_factor:
908 * @area: an #AdgGtkArea
910 * Gets the zoom factor associated to @area. The zoom factor is
911 * directly used to zoom in (that is, the default zoom factor of
912 * 1.05 will zoom of 5% every iteration) and it is reversed while
913 * zooming out (that is, the default factor will be 1/1.05).
915 * Returns: the requested zoom factor or 0 on error
917 * Since: 1.0
919 gdouble
920 adg_gtk_area_get_factor(AdgGtkArea *area)
922 AdgGtkAreaPrivate *data;
924 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
926 data = adg_gtk_area_get_instance_private(area);
927 return data->factor;
931 * adg_gtk_area_switch_autozoom:
932 * @area: an #AdgGtkArea
933 * @state: the new autozoom state
935 * Sets the #AdgGtkArea:autozoom property of @area to @state. When the
936 * autozoom feature is enabled, @area reacts to any size allocation
937 * by adjusting its zoom coefficient in global space. This means the
938 * drawing will fill the available space (keeping its aspect ratio)
939 * when resizing the window.
941 * Since: 1.0
943 void
944 adg_gtk_area_switch_autozoom(AdgGtkArea *area, gboolean state)
946 g_return_if_fail(ADG_GTK_IS_AREA(area));
947 g_object_set(area, "autozoom", state, NULL);
951 * adg_gtk_area_has_autozoom:
952 * @area: an #AdgGtkArea
954 * Gets the current state of the #AdgGtkArea:autozoom property on
955 * the @area object.
957 * Returns: the current autozoom state
959 * Since: 1.0
961 gboolean
962 adg_gtk_area_has_autozoom(AdgGtkArea *area)
964 AdgGtkAreaPrivate *data;
966 g_return_val_if_fail(ADG_GTK_IS_AREA(area), FALSE);
968 data = adg_gtk_area_get_instance_private(area);
969 return data->autozoom;
973 * adg_gtk_area_reset:
974 * @area: an #AdgGtkArea
976 * Forcibly resets the zoom ratio and position of the canvas bound
977 * to @area. This means the canvas will be scaled and centered on
978 * the current available space.
980 void
981 adg_gtk_area_reset(AdgGtkArea *area)
983 AdgGtkAreaPrivate *data;
984 GtkWidget *parent;
985 const CpmlExtents *sheet;
986 GtkAllocation allocation;
987 CpmlPair size;
988 gdouble zoom;
990 g_return_if_fail(ADG_GTK_IS_AREA(area));
992 data = adg_gtk_area_get_instance_private(area);
993 cairo_matrix_init_identity(&data->render_map);
995 sheet = _adg_get_extents(area);
996 if (!sheet->is_defined || sheet->size.x <= 0 || sheet->size.y <= 0)
997 return;
999 parent = gtk_widget_get_parent((GtkWidget *) area);
1000 gtk_widget_get_allocation(parent, &allocation);
1001 size.x = allocation.width;
1002 size.y = allocation.height;
1003 zoom = MIN(size.x / sheet->size.x, size.y / sheet->size.y);
1005 cairo_matrix_scale(&data->render_map, zoom, zoom);
1006 cairo_matrix_translate(&data->render_map,
1007 (size.x / zoom - sheet->size.x) / 2 - sheet->org.x,
1008 (size.y / zoom - sheet->size.y) / 2 - sheet->org.y);
1010 /* Trigger a resize trying to hide the scrollbars on the parent */
1011 gtk_widget_queue_resize(parent);
1015 * adg_gtk_area_canvas_changed:
1016 * @area: an #AdgGtkArea
1017 * @old_canvas: the old canvas bound to @area
1019 * Emits the #AdgGtkArea::canvas-changed signal on @area.
1021 * Since: 1.0
1023 void
1024 adg_gtk_area_canvas_changed(AdgGtkArea *area, AdgCanvas *old_canvas)
1026 g_return_if_fail(ADG_GTK_IS_AREA(area));
1028 g_signal_emit(area, _adg_signals[CANVAS_CHANGED], 0, old_canvas);
1032 * adg_gtk_area_extents_changed:
1033 * @area: an #AdgGtkArea
1034 * @old_extents: the old extents of @area
1036 * Emits the #AdgGtkArea::extents-changed signal on @area.
1038 * Since: 1.0
1040 void
1041 adg_gtk_area_extents_changed(AdgGtkArea *area, const CpmlExtents *old_extents)
1043 g_return_if_fail(ADG_GTK_IS_AREA(area));
1045 g_signal_emit(area, _adg_signals[EXTENTS_CHANGED], 0, old_extents);