adg: cosmetic remove of double ;;
[adg.git] / src / adg / adg-gtk-area.c
blobb0f1f2f6dd958246621d0d2df7db6ea9dabf89f2
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2019 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;
446 AdgCanvas *canvas;
448 data = ((AdgGtkArea *) widget)->data;
449 canvas = data->canvas;
451 if (canvas != NULL && event->window != NULL) {
452 cairo_t *cr = gdk_cairo_create(event->window);
453 cairo_transform(cr, &data->render_map);
454 adg_entity_render((AdgEntity *) canvas, cr);
455 cairo_destroy(cr);
458 if (_ADG_OLD_WIDGET_CLASS->expose_event == NULL)
459 return FALSE;
461 return _ADG_OLD_WIDGET_CLASS->expose_event(widget, event);
464 #endif
466 #ifdef GTK3_ENABLED
468 static void
469 _adg_get_preferred_height(GtkWidget *widget,
470 gint *minimum_height, gint *natural_height)
472 AdgGtkArea *area;
473 const CpmlExtents *extents;
475 area = (AdgGtkArea *) widget;
476 extents = _adg_get_extents(area);
478 if (extents->is_defined) {
479 *minimum_height = extents->size.y;
480 *natural_height = *minimum_height;
484 static void
485 _adg_get_preferred_height_for_width(GtkWidget *widget, gint width,
486 gint *minimum_height, gint *natural_height)
488 AdgGtkArea *area;
489 const CpmlExtents *extents;
491 area = (AdgGtkArea *) widget;
492 extents = _adg_get_extents(area);
494 if (extents->is_defined && extents->size.x > 0) {
495 *minimum_height = extents->size.y;
496 *natural_height = *minimum_height * width / extents->size.x;
500 static void
501 _adg_get_preferred_width(GtkWidget *widget,
502 gint *minimum_width, gint *natural_width)
504 AdgGtkArea *area;
505 const CpmlExtents *extents;
507 area = (AdgGtkArea *) widget;
508 extents = _adg_get_extents(area);
510 if (extents->is_defined) {
511 *minimum_width = extents->size.x;
512 *natural_width = *minimum_width;
516 static void
517 _adg_get_preferred_width_for_height(GtkWidget *widget, gint height,
518 gint *minimum_width, gint *natural_width)
520 AdgGtkArea *area;
521 const CpmlExtents *extents;
523 area = (AdgGtkArea *) widget;
524 extents = _adg_get_extents(area);
526 if (extents->is_defined && extents->size.y > 0) {
527 *minimum_width = extents->size.x;
528 *natural_width = *minimum_width * height / extents->size.y;
532 static gboolean
533 _adg_draw(GtkWidget *widget, cairo_t *cr)
535 AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) widget);
536 AdgCanvas *canvas = data->canvas;
538 if (canvas != NULL) {
539 cairo_transform(cr, &data->render_map);
540 adg_entity_render((AdgEntity *) canvas, cr);
543 return FALSE;
546 #endif
548 static void
549 adg_gtk_area_class_init(AdgGtkAreaClass *klass)
551 GObjectClass *gobject_class;
552 GtkWidgetClass *widget_class;
553 GParamSpec *param;
555 gobject_class = (GObjectClass *) klass;
556 widget_class = (GtkWidgetClass *) klass;
558 gobject_class->dispose = _adg_dispose;
559 gobject_class->get_property = _adg_get_property;
560 gobject_class->set_property = _adg_set_property;
562 #ifdef GTK2_ENABLED
563 widget_class->size_request = _adg_size_request;
564 widget_class->expose_event = _adg_expose_event;
565 #endif
567 #ifdef GTK3_ENABLED
568 widget_class->get_preferred_height = _adg_get_preferred_height;
569 widget_class->get_preferred_height_for_width = _adg_get_preferred_height_for_width;
570 widget_class->get_preferred_width = _adg_get_preferred_width;
571 widget_class->get_preferred_width_for_height = _adg_get_preferred_width_for_height;
572 widget_class->draw = _adg_draw;
573 #endif
575 widget_class->size_allocate = _adg_size_allocate;
576 widget_class->scroll_event = _adg_scroll_event;
577 widget_class->button_press_event = _adg_button_press_event;
578 widget_class->motion_notify_event = _adg_motion_notify_event;
580 klass->canvas_changed = _adg_canvas_changed;
582 param = g_param_spec_object("canvas",
583 P_("Canvas"),
584 P_("The canvas to be shown"),
585 ADG_TYPE_CANVAS,
586 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
587 g_object_class_install_property(gobject_class, PROP_CANVAS, param);
589 param = g_param_spec_double("factor",
590 P_("Factor"),
591 P_("The factor to use while zooming in and out (usually with the mouse wheel)"),
592 1., G_MAXDOUBLE, 1.05,
593 G_PARAM_READWRITE);
594 g_object_class_install_property(gobject_class, PROP_FACTOR, param);
596 param = g_param_spec_boolean("autozoom",
597 P_("Autozoom"),
598 P_("When enabled, automatically adjust the zoom in global space at every size allocation"),
599 FALSE,
600 G_PARAM_READWRITE);
601 g_object_class_install_property(gobject_class, PROP_AUTOZOOM, param);
603 param = g_param_spec_boxed("render-map",
604 P_("Render Map"),
605 P_("The transformation to be applied on the canvas before rendering it"),
606 CAIRO_GOBJECT_TYPE_MATRIX,
607 G_PARAM_READWRITE);
608 g_object_class_install_property(gobject_class, PROP_RENDER_MAP, param);
611 * AdgGtkArea::canvas-changed:
612 * @area: an #AdgGtkArea
613 * @old_canvas: (type AdgCanvas*): the old #AdgCanvas object
615 * Emitted after the canvas bound to @area has been changed. The old
616 * canvas accessible from @old_canvas while the new canvas can be got
617 * with the usual API, e.g. adg_gtk_area_get_canvas().
619 * Since: 1.0
621 _adg_signals[CANVAS_CHANGED] =
622 g_signal_new("canvas-changed", ADG_GTK_TYPE_AREA,
623 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
624 G_STRUCT_OFFSET(AdgGtkAreaClass, canvas_changed),
625 NULL, NULL,
626 g_cclosure_marshal_VOID__OBJECT,
627 G_TYPE_NONE, 1, ADG_TYPE_CANVAS);
630 * AdgGtkArea::extents-changed:
631 * @area: an #AdgGtkArea
632 * @old_extents: the old #CpmlExtents struct
634 * Emitted when the extents of @area have been changed.
635 * The old extents are always compared to the new ones,
636 * so when the extents are recalculated but the result
637 * is the same the signal is not emitted.
639 * The extents of #AdgGtkArea are subject to the render
640 * map, so changing the #AdgGtkArea:render-map property
641 * will emit this signal too.
643 * Since: 1.0
645 _adg_signals[EXTENTS_CHANGED] =
646 g_signal_new("extents-changed", ADG_GTK_TYPE_AREA,
647 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
648 G_STRUCT_OFFSET(AdgGtkAreaClass, extents_changed),
649 NULL, NULL,
650 g_cclosure_marshal_VOID__POINTER,
651 G_TYPE_NONE, 1, G_TYPE_POINTER);
654 static void
655 adg_gtk_area_init(AdgGtkArea *area)
657 AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private(area);
658 data->canvas = NULL;
659 data->factor = 1.05;
660 data->autozoom = FALSE;
661 cairo_matrix_init_identity(&data->render_map);
662 data->initialized = FALSE;
663 data->x_event = 0;
664 data->y_event = 0;
666 /* Enable GDK events to catch wheel rotation and drag */
667 gtk_widget_add_events((GtkWidget *) area,
668 GDK_BUTTON_PRESS_MASK |
669 GDK_BUTTON2_MOTION_MASK |
670 GDK_SCROLL_MASK);
677 * adg_gtk_area_new:
679 * Creates a new empty #AdgGtkArea. The widget is useful only after
680 * an #AdgCanvas has been added either using the #AdgGtkArea:canvas
681 * property or with adg_gtk_area_set_canvas().
683 * Returns: the newly created widget
685 * Since: 1.0
687 GtkWidget *
688 adg_gtk_area_new(void)
690 return g_object_new(ADG_GTK_TYPE_AREA, NULL);
694 * adg_gtk_area_new_with_canvas:
695 * @canvas: the #AdgCanvas shown by this widget
697 * Creates a new #AdgGtkArea and sets the #AdgGtkArea:canvas property
698 * to @canvas.
700 * Returns: the newly created widget
702 * Since: 1.0
704 GtkWidget *
705 adg_gtk_area_new_with_canvas(AdgCanvas *canvas)
707 g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL);
709 return g_object_new(ADG_GTK_TYPE_AREA, "canvas", canvas, NULL);
713 * adg_gtk_area_set_canvas:
714 * @area: an #AdgGtkArea
715 * @canvas: the new #AdgCanvas
717 * Sets a new canvas on @area. The old canvas, if presents, is
718 * unreferenced.
720 * Since: 1.0
722 void
723 adg_gtk_area_set_canvas(AdgGtkArea *area, AdgCanvas *canvas)
725 g_return_if_fail(ADG_GTK_IS_AREA(area));
726 g_object_set(area, "canvas", canvas, NULL);
730 * adg_gtk_area_get_canvas:
731 * @area: an #AdgGtkArea
733 * Gets the canvas associated to @area. The returned canvas
734 * is owned by @area and should not be modified or freed.
736 * Returns: (transfer none): the requested #AdgCanvas object or <constant>NULL</constant> on errors.
738 * Since: 1.0
740 AdgCanvas *
741 adg_gtk_area_get_canvas(AdgGtkArea *area)
743 AdgGtkAreaPrivate *data;
745 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
747 data = adg_gtk_area_get_instance_private(area);
748 return data->canvas;
752 * adg_gtk_area_set_render_map:
753 * @area: an #AdgGtkArea object
754 * @map: the new map
756 * Sets the new render transformation of @area to @map: the
757 * old map is discarded. If @map is <constant>NULL</constant>,
758 * the render map is left unchanged.
760 * <note><para>
761 * The render map is an implementation detail and this function
762 * is expected to be used only by #AdgGtkArea derived objects.
763 * </para></note>
765 * Since: 1.0
767 void
768 adg_gtk_area_set_render_map(AdgGtkArea *area, const cairo_matrix_t *map)
770 g_return_if_fail(ADG_GTK_IS_AREA(area));
771 g_object_set(area, "render-map", map, NULL);
775 * adg_gtk_area_transform_render_map:
776 * @area: an #AdgGtkArea object
777 * @transformation: the transformation to apply
778 * @mode: how @transformation should be applied
780 * Convenient function to change the render map of @area by
781 * applying @tranformation using the @mode operator. This is
782 * logically equivalent to the following:
784 * <informalexample><programlisting language="C">
785 * cairo_matrix_t map;
786 * adg_matrix_copy(&map, adg_gtk_area_get_render_map(area));
787 * adg_matrix_transform(&map, transformation, mode);
788 * adg_gtk_area_set_render_map(area, &map);
789 * </programlisting></informalexample>
791 * <note><para>
792 * The render map is an implementation detail and this function
793 * is expected to be used only by #AdgGtkArea derived objects.
794 * </para></note>
796 * Since: 1.0
798 void
799 adg_gtk_area_transform_render_map(AdgGtkArea *area,
800 const cairo_matrix_t *transformation,
801 AdgTransformMode mode)
803 AdgGtkAreaPrivate *data;
804 cairo_matrix_t map;
806 g_return_if_fail(ADG_GTK_IS_AREA(area));
807 g_return_if_fail(transformation != NULL);
809 data = adg_gtk_area_get_instance_private(area);
811 adg_matrix_copy(&map, &data->render_map);
812 adg_matrix_transform(&map, transformation, mode);
814 g_object_set(area, "render-map", &map, NULL);
818 * adg_gtk_area_get_render_map:
819 * @area: an #AdgGtkArea object
821 * Gets the render map.
823 * Returns: the requested map or <constant>NULL</constant> on errors.
825 * Since: 1.0
827 const cairo_matrix_t *
828 adg_gtk_area_get_render_map(AdgGtkArea *area)
830 AdgGtkAreaPrivate *data;
832 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
834 data = adg_gtk_area_get_instance_private(area);
835 return &data->render_map;
839 * adg_gtk_area_get_extents:
840 * @area: an #AdgGtkArea
842 * Gets the extents of the canvas bound to @area. The returned
843 * struct is owned by @area and should not modified or freed.
845 * The extents of an #AdgGtkArea instance are the extents of
846 * its canvas (as returned by adg_entity_get_extents()) with
847 * the margins added to it and the #AdgGtkArea:render-map
848 * transformation applied.
850 * If @area does not have any canvas associated to it or the
851 * canvas is invalid or empty, an undefined #CpmlExtents
852 * struct will be returned.
854 * The canvas will be updated, meaning adg_entity_arrange()
855 * is called before the extents computation.
857 * Returns: the extents of the @area canvas or <constant>NULL</constant> on errors.
859 * Since: 1.0
861 const CpmlExtents *
862 adg_gtk_area_get_extents(AdgGtkArea *area)
864 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
866 return _adg_get_extents(area);
870 * adg_gtk_area_get_zoom:
871 * @area: an #AdgGtkArea
873 * Gets the last zoom coefficient applied on the canvas of @area.
874 * If the #AdgGtkArea:autozoom property is <constant>FALSE</constant>,
875 * the value returned should be always 1.
877 * Returns: the current zoom coefficient.
879 * Since: 1.0
881 gdouble
882 adg_gtk_area_get_zoom(AdgGtkArea *area)
884 AdgGtkAreaPrivate *data;
886 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
888 data = adg_gtk_area_get_instance_private(area);
889 return data->render_map.xx;
893 * adg_gtk_area_set_factor:
894 * @area: an #AdgGtkArea
895 * @factor: the new zoom factor
897 * Sets a new zoom factor to @area. If the factor is less than
898 * 1, it will be clamped to 1.
900 * Since: 1.0
902 void
903 adg_gtk_area_set_factor(AdgGtkArea *area, gdouble factor)
905 g_return_if_fail(ADG_GTK_IS_AREA(area));
906 g_object_set(area, "factor", factor, NULL);
910 * adg_gtk_area_get_factor:
911 * @area: an #AdgGtkArea
913 * Gets the zoom factor associated to @area. The zoom factor is
914 * directly used to zoom in (that is, the default zoom factor of
915 * 1.05 will zoom of 5% every iteration) and it is reversed while
916 * zooming out (that is, the default factor will be 1/1.05).
918 * Returns: the requested zoom factor or 0 on error
920 * Since: 1.0
922 gdouble
923 adg_gtk_area_get_factor(AdgGtkArea *area)
925 AdgGtkAreaPrivate *data;
927 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
929 data = adg_gtk_area_get_instance_private(area);
930 return data->factor;
934 * adg_gtk_area_switch_autozoom:
935 * @area: an #AdgGtkArea
936 * @state: the new autozoom state
938 * Sets the #AdgGtkArea:autozoom property of @area to @state. When the
939 * autozoom feature is enabled, @area reacts to any size allocation
940 * by adjusting its zoom coefficient in global space. This means the
941 * drawing will fill the available space (keeping its aspect ratio)
942 * when resizing the window.
944 * Since: 1.0
946 void
947 adg_gtk_area_switch_autozoom(AdgGtkArea *area, gboolean state)
949 g_return_if_fail(ADG_GTK_IS_AREA(area));
950 g_object_set(area, "autozoom", state, NULL);
954 * adg_gtk_area_has_autozoom:
955 * @area: an #AdgGtkArea
957 * Gets the current state of the #AdgGtkArea:autozoom property on
958 * the @area object.
960 * Returns: the current autozoom state
962 * Since: 1.0
964 gboolean
965 adg_gtk_area_has_autozoom(AdgGtkArea *area)
967 AdgGtkAreaPrivate *data;
969 g_return_val_if_fail(ADG_GTK_IS_AREA(area), FALSE);
971 data = adg_gtk_area_get_instance_private(area);
972 return data->autozoom;
976 * adg_gtk_area_reset:
977 * @area: an #AdgGtkArea
979 * Forcibly resets the zoom ratio and position of the canvas bound
980 * to @area. This means the canvas will be scaled and centered on
981 * the current available space.
983 void
984 adg_gtk_area_reset(AdgGtkArea *area)
986 AdgGtkAreaPrivate *data;
987 GtkWidget *parent;
988 const CpmlExtents *sheet;
989 GtkAllocation allocation;
990 CpmlPair size;
991 gdouble zoom;
993 g_return_if_fail(ADG_GTK_IS_AREA(area));
995 data = adg_gtk_area_get_instance_private(area);
996 cairo_matrix_init_identity(&data->render_map);
998 sheet = _adg_get_extents(area);
999 if (!sheet->is_defined || sheet->size.x <= 0 || sheet->size.y <= 0)
1000 return;
1002 parent = gtk_widget_get_parent((GtkWidget *) area);
1003 gtk_widget_get_allocation(parent, &allocation);
1004 size.x = allocation.width;
1005 size.y = allocation.height;
1006 zoom = MIN(size.x / sheet->size.x, size.y / sheet->size.y);
1008 cairo_matrix_scale(&data->render_map, zoom, zoom);
1009 cairo_matrix_translate(&data->render_map,
1010 (size.x / zoom - sheet->size.x) / 2 - sheet->org.x,
1011 (size.y / zoom - sheet->size.y) / 2 - sheet->org.y);
1013 /* Trigger a resize trying to hide the scrollbars on the parent */
1014 gtk_widget_queue_resize(parent);
1018 * adg_gtk_area_canvas_changed:
1019 * @area: an #AdgGtkArea
1020 * @old_canvas: the old canvas bound to @area
1022 * Emits the #AdgGtkArea::canvas-changed signal on @area.
1024 * Since: 1.0
1026 void
1027 adg_gtk_area_canvas_changed(AdgGtkArea *area, AdgCanvas *old_canvas)
1029 g_return_if_fail(ADG_GTK_IS_AREA(area));
1031 g_signal_emit(area, _adg_signals[CANVAS_CHANGED], 0, old_canvas);
1035 * adg_gtk_area_extents_changed:
1036 * @area: an #AdgGtkArea
1037 * @old_extents: the old extents of @area
1039 * Emits the #AdgGtkArea::extents-changed signal on @area.
1041 * Since: 1.0
1043 void
1044 adg_gtk_area_extents_changed(AdgGtkArea *area, const CpmlExtents *old_extents)
1046 g_return_if_fail(ADG_GTK_IS_AREA(area));
1048 g_signal_emit(area, _adg_signals[EXTENTS_CHANGED], 0, old_extents);