adg: provide example on how to customize a style
[adg.git] / src / adg / adg-gtk-area.c
blob7eaa863389e0c8fc2bb12c072c1a156de48459ac
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2017 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(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;
122 AdgCanvas *canvas;
123 CpmlExtents old_extents;
125 data = area->data;
126 old_extents = data->extents;
127 data->extents.is_defined = FALSE;
128 canvas = data->canvas;
130 if (ADG_IS_CANVAS(canvas)) {
131 const CpmlExtents *extents;
132 AdgEntity *entity;
134 entity = (AdgEntity *) canvas;
136 adg_entity_arrange(entity);
137 extents = adg_entity_get_extents(entity);
139 if (extents != NULL) {
140 data->extents = *extents;
141 adg_canvas_apply_margins(canvas, &data->extents);
142 cpml_extents_transform(&data->extents, &data->render_map);
146 if (!cpml_extents_equal(&data->extents, &old_extents))
147 g_signal_emit(area, _adg_signals[EXTENTS_CHANGED], 0, &old_extents);
149 return &data->extents;
153 static void
154 _adg_get_property(GObject *object, guint prop_id,
155 GValue *value, GParamSpec *pspec)
157 AdgGtkAreaPrivate *data = ((AdgGtkArea *) object)->data;
159 switch (prop_id) {
160 case PROP_CANVAS:
161 g_value_set_object(value, data->canvas);
162 break;
163 case PROP_FACTOR:
164 g_value_set_double(value, data->factor);
165 break;
166 case PROP_AUTOZOOM:
167 g_value_set_boolean(value, data->autozoom);
168 break;
169 case PROP_RENDER_MAP:
170 g_value_set_boxed(value, &data->render_map);
171 break;
172 default:
173 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
174 break;
178 static void
179 _adg_set_property(GObject *object, guint prop_id,
180 const GValue *value, GParamSpec *pspec)
182 AdgGtkArea *area;
183 AdgGtkAreaPrivate *data;
184 AdgCanvas *new_canvas, *old_canvas;
186 area = (AdgGtkArea *) object;
187 data = area->data;
189 switch (prop_id) {
190 case PROP_CANVAS:
191 new_canvas = g_value_get_object(value);
192 old_canvas = data->canvas;
193 if (new_canvas != old_canvas) {
194 if (new_canvas != NULL)
195 g_object_ref(new_canvas);
196 if (old_canvas != NULL)
197 g_object_unref(old_canvas);
198 data->canvas = new_canvas;
199 g_signal_emit(area, _adg_signals[CANVAS_CHANGED], 0, old_canvas);
201 break;
202 case PROP_FACTOR:
203 data->factor = g_value_get_double(value);
204 break;
205 case PROP_AUTOZOOM:
206 data->autozoom = g_value_get_boolean(value);
207 break;
208 case PROP_RENDER_MAP:
209 adg_matrix_copy(&data->render_map, g_value_get_boxed(value));
210 break;
211 default:
212 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
213 break;
218 static void
219 _adg_dispose(GObject *object)
221 AdgGtkAreaPrivate *data = ((AdgGtkArea *) object)->data;
223 if (data->canvas) {
224 g_object_unref(data->canvas);
225 data->canvas = NULL;
228 if (_ADG_OLD_OBJECT_CLASS->dispose)
229 _ADG_OLD_OBJECT_CLASS->dispose(object);
233 * _adg_size_allocate:
234 * @widget: an #AdgGtkArea widget
235 * @allocation: the new allocation struct
237 * Scales the drawing according to the new allocation if
238 * #AdgGtkArea:autozoom is <constant>TRUE</constant>.
240 * TODO: the current implementation initially centers the canvas
241 * on the allocation space. Further allocations (due to a
242 * window resizing, for example) use the top/left corner of the
243 * canvas as reference point. Plan different policies for either
244 * those situations.
246 * Since: 1.0
248 static void
249 _adg_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
251 AdgGtkArea *area;
252 AdgGtkAreaPrivate *data;
253 const CpmlExtents *sheet;
254 CpmlVector size;
255 gdouble factor;
257 if (_ADG_OLD_WIDGET_CLASS->size_allocate)
258 _ADG_OLD_WIDGET_CLASS->size_allocate(widget, allocation);
260 area = (AdgGtkArea *) widget;
261 data = area->data;
263 sheet = _adg_get_extents(area);
264 if (!sheet->is_defined || sheet->size.x <= 0 || sheet->size.y <= 0)
265 return;
267 size.x = allocation->width;
268 size.y = allocation->height;
270 if (data->autozoom) {
271 /* Adjust the zoom according to the allocation and sheet size */
272 factor = MIN(size.x / sheet->size.x, size.y / sheet->size.y);
273 } else if (!data->initialized) {
274 /* First allocation with autozoom off: keep the current zoom */
275 factor = 1;
276 } else {
277 /* Not the first allocation and autozoom off: keep the old map */
278 return;
281 if (!data->initialized) {
282 /* TODO: plan different attachment policies other than centering */
283 cairo_matrix_init_translate(&data->render_map,
284 (size.x - sheet->size.x) / 2 - sheet->org.x,
285 (size.y - sheet->size.y) / 2 - sheet->org.y);
286 data->initialized = TRUE;
289 /* TODO: plan other reference points other than left/top (x0, y0) */
290 data->render_map.x0 *= factor;
291 data->render_map.y0 *= factor;
292 data->render_map.xx *= factor;
293 data->render_map.yy *= factor;
296 static gboolean
297 _adg_get_map(GtkWidget *widget, gboolean local_space,
298 cairo_matrix_t *map, cairo_matrix_t *inverted)
300 AdgGtkAreaPrivate *data;
301 AdgEntity *entity;
303 data = ((AdgGtkArea *) widget)->data;
304 entity = (AdgEntity *) data->canvas;
305 if (entity == NULL)
306 return FALSE;
308 if (local_space) {
309 adg_matrix_copy(map, adg_entity_get_local_map(entity));
311 /* The inverted map is subject to the global matrix */
312 adg_matrix_copy(inverted, adg_entity_get_global_matrix(entity));
313 adg_matrix_transform(inverted, map, ADG_TRANSFORM_BEFORE);
314 } else {
315 adg_matrix_copy(map, adg_entity_get_global_map(entity));
316 adg_matrix_copy(inverted, map);
319 return cairo_matrix_invert(inverted) == CAIRO_STATUS_SUCCESS;
322 static void
323 _adg_set_map(GtkWidget *widget,
324 gboolean local_space,
325 const cairo_matrix_t *map)
327 AdgGtkAreaPrivate *data;
328 AdgEntity *entity;
330 data = ((AdgGtkArea *) widget)->data;
331 entity = (AdgEntity *) data->canvas;
333 if (entity == NULL)
334 return;
336 if (local_space) {
337 /* TODO: this forcibly overwrites any local transformation */
338 adg_entity_set_local_map(entity, map);
339 } else {
340 adg_matrix_transform(&data->render_map, map, ADG_TRANSFORM_BEFORE);
343 /* This will emit the extents-changed signal when applicable */
344 _adg_get_extents((AdgGtkArea *) widget);
347 static gboolean
348 _adg_scroll_event(GtkWidget *widget, GdkEventScroll *event)
350 gboolean zoom_in, zoom_out, local_space, global_space;
351 cairo_matrix_t map, inverted;
352 AdgGtkAreaPrivate *data;
353 double factor, x, y;
355 zoom_in = event->direction == GDK_SCROLL_UP;
356 zoom_out = event->direction == GDK_SCROLL_DOWN;
357 local_space = (event->state & ADG_GTK_MODIFIERS) == 0;
358 global_space = (event->state & ADG_GTK_MODIFIERS) == GDK_SHIFT_MASK;
360 if ((zoom_in || zoom_out) && (local_space || global_space) &&
361 _adg_get_map(widget, local_space, &map, &inverted)) {
362 data = ((AdgGtkArea *) widget)->data;
363 factor = zoom_in ? data->factor : 1. / data->factor;
364 x = event->x;
365 y = event->y;
367 cairo_matrix_transform_point(&inverted, &x, &y);
368 cairo_matrix_scale(&map, factor, factor);
369 cairo_matrix_translate(&map, x/factor - x, y/factor - y);
371 _adg_set_map(widget, local_space, &map);
373 gtk_widget_queue_draw(widget);
375 /* Avoid to chain up the default handler:
376 * this event has been grabbed by this function */
377 return TRUE;
380 if (_ADG_OLD_WIDGET_CLASS->scroll_event == NULL)
381 return FALSE;
383 return _ADG_OLD_WIDGET_CLASS->scroll_event(widget, event);
386 static gboolean
387 _adg_button_press_event(GtkWidget *widget, GdkEventButton *event)
389 AdgGtkAreaPrivate *data = ((AdgGtkArea *) widget)->data;
391 if (event->type == GDK_BUTTON_PRESS && event->button == 2) {
392 /* Remember the starting coordinates of a (probable) translation */
393 data->x_event = event->x;
394 data->y_event = event->y;
397 if (_ADG_OLD_WIDGET_CLASS->button_press_event == NULL)
398 return FALSE;
400 return _ADG_OLD_WIDGET_CLASS->button_press_event(widget, event);
403 static gboolean
404 _adg_motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
406 gboolean translating, local_space, global_space;
407 cairo_matrix_t map, inverted;
408 AdgGtkAreaPrivate *data;
409 double x, y;
411 translating = (event->state & GDK_BUTTON2_MASK) == GDK_BUTTON2_MASK;
412 local_space = (event->state & ADG_GTK_MODIFIERS) == 0;
413 global_space = (event->state & ADG_GTK_MODIFIERS) == GDK_SHIFT_MASK;
415 if (translating && (local_space || global_space) &&
416 _adg_get_map(widget, local_space, &map, &inverted)) {
417 data = ((AdgGtkArea *) widget)->data;
418 x = event->x - data->x_event;
419 y = event->y - data->y_event;
421 cairo_matrix_transform_distance(&inverted, &x, &y);
422 cairo_matrix_translate(&map, x, y);
423 data->x_event = event->x;
424 data->y_event = event->y;
426 _adg_set_map(widget, local_space, &map);
428 gtk_widget_queue_draw(widget);
430 /* Avoid to chain up the default handler:
431 * this event has been grabbed by this function */
432 return TRUE;
435 if (_ADG_OLD_WIDGET_CLASS->motion_notify_event == NULL)
436 return FALSE;
438 return _ADG_OLD_WIDGET_CLASS->motion_notify_event(widget, event);
441 static void
442 _adg_canvas_changed(AdgGtkArea *area, AdgCanvas *old_canvas)
444 AdgGtkAreaPrivate *data = area->data;
445 data->initialized = FALSE;
448 #ifdef GTK2_ENABLED
450 static void
451 _adg_size_request(GtkWidget *widget, GtkRequisition *requisition)
453 AdgGtkArea *area;
454 const CpmlExtents *extents;
456 area = (AdgGtkArea *) widget;
457 extents = _adg_get_extents(area);
459 if (extents->is_defined) {
460 requisition->width = extents->size.x;
461 requisition->height = extents->size.y;
465 static gboolean
466 _adg_expose_event(GtkWidget *widget, GdkEventExpose *event)
468 AdgGtkAreaPrivate *data;
469 AdgCanvas *canvas;
471 data = ((AdgGtkArea *) widget)->data;
472 canvas = data->canvas;
474 if (canvas != NULL && event->window != NULL) {
475 cairo_t *cr = gdk_cairo_create(event->window);
476 cairo_transform(cr, &data->render_map);
477 adg_entity_render((AdgEntity *) canvas, cr);
478 cairo_destroy(cr);
481 if (_ADG_OLD_WIDGET_CLASS->expose_event == NULL)
482 return FALSE;
484 return _ADG_OLD_WIDGET_CLASS->expose_event(widget, event);
487 #endif
489 #ifdef GTK3_ENABLED
491 static void
492 _adg_get_preferred_height(GtkWidget *widget,
493 gint *minimum_height, gint *natural_height)
495 AdgGtkArea *area;
496 const CpmlExtents *extents;
498 area = (AdgGtkArea *) widget;
499 extents = _adg_get_extents(area);
501 if (extents->is_defined) {
502 *minimum_height = extents->size.y;
503 *natural_height = *minimum_height;
507 static void
508 _adg_get_preferred_height_for_width(GtkWidget *widget, gint width,
509 gint *minimum_height, gint *natural_height)
511 AdgGtkArea *area;
512 const CpmlExtents *extents;
514 area = (AdgGtkArea *) widget;
515 extents = _adg_get_extents(area);
517 if (extents->is_defined && extents->size.x > 0) {
518 *minimum_height = extents->size.y;
519 *natural_height = *minimum_height * width / extents->size.x;
523 static void
524 _adg_get_preferred_width(GtkWidget *widget,
525 gint *minimum_width, gint *natural_width)
527 AdgGtkArea *area;
528 const CpmlExtents *extents;
530 area = (AdgGtkArea *) widget;
531 extents = _adg_get_extents(area);
533 if (extents->is_defined) {
534 *minimum_width = extents->size.x;
535 *natural_width = *minimum_width;
539 static void
540 _adg_get_preferred_width_for_height(GtkWidget *widget, gint height,
541 gint *minimum_width, gint *natural_width)
543 AdgGtkArea *area;
544 const CpmlExtents *extents;
546 area = (AdgGtkArea *) widget;
547 extents = _adg_get_extents(area);
549 if (extents->is_defined && extents->size.y > 0) {
550 *minimum_width = extents->size.x;
551 *natural_width = *minimum_width * height / extents->size.y;
555 static gboolean
556 _adg_draw(GtkWidget *widget, cairo_t *cr)
558 AdgGtkAreaPrivate *data;
559 AdgCanvas *canvas;
561 data = ((AdgGtkArea *) widget)->data;
562 canvas = data->canvas;
564 if (canvas != NULL) {
565 cairo_transform(cr, &data->render_map);
566 adg_entity_render((AdgEntity *) canvas, cr);
569 return FALSE;
572 #endif
574 static void
575 adg_gtk_area_class_init(AdgGtkAreaClass *klass)
577 GObjectClass *gobject_class;
578 GtkWidgetClass *widget_class;
579 GParamSpec *param;
581 gobject_class = (GObjectClass *) klass;
582 widget_class = (GtkWidgetClass *) klass;
584 g_type_class_add_private(klass, sizeof(AdgGtkAreaPrivate));
586 gobject_class->dispose = _adg_dispose;
587 gobject_class->get_property = _adg_get_property;
588 gobject_class->set_property = _adg_set_property;
590 #ifdef GTK2_ENABLED
591 widget_class->size_request = _adg_size_request;
592 widget_class->expose_event = _adg_expose_event;
593 #endif
595 #ifdef GTK3_ENABLED
596 widget_class->get_preferred_height = _adg_get_preferred_height;
597 widget_class->get_preferred_height_for_width = _adg_get_preferred_height_for_width;
598 widget_class->get_preferred_width = _adg_get_preferred_width;
599 widget_class->get_preferred_width_for_height = _adg_get_preferred_width_for_height;
600 widget_class->draw = _adg_draw;
601 #endif
603 widget_class->size_allocate = _adg_size_allocate;
604 widget_class->scroll_event = _adg_scroll_event;
605 widget_class->button_press_event = _adg_button_press_event;
606 widget_class->motion_notify_event = _adg_motion_notify_event;
608 klass->canvas_changed = _adg_canvas_changed;
610 param = g_param_spec_object("canvas",
611 P_("Canvas"),
612 P_("The canvas to be shown"),
613 ADG_TYPE_CANVAS,
614 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
615 g_object_class_install_property(gobject_class, PROP_CANVAS, param);
617 param = g_param_spec_double("factor",
618 P_("Factor"),
619 P_("The factor to use while zooming in and out (usually with the mouse wheel)"),
620 1., G_MAXDOUBLE, 1.05,
621 G_PARAM_READWRITE);
622 g_object_class_install_property(gobject_class, PROP_FACTOR, param);
624 param = g_param_spec_boolean("autozoom",
625 P_("Autozoom"),
626 P_("When enabled, automatically adjust the zoom in global space at every size allocation"),
627 FALSE,
628 G_PARAM_READWRITE);
629 g_object_class_install_property(gobject_class, PROP_AUTOZOOM, param);
631 param = g_param_spec_boxed("render-map",
632 P_("Render Map"),
633 P_("The transformation to be applied on the canvas before rendering it"),
634 CAIRO_GOBJECT_TYPE_MATRIX,
635 G_PARAM_READWRITE);
636 g_object_class_install_property(gobject_class, PROP_RENDER_MAP, param);
639 * AdgGtkArea::canvas-changed:
640 * @area: an #AdgGtkArea
641 * @old_canvas: (type AdgCanvas*): the old #AdgCanvas object
643 * Emitted after the canvas bound to @area has been changed. The old
644 * canvas accessible from @old_canvas while the new canvas can be got
645 * with the usual API, e.g. adg_gtk_area_get_canvas().
647 * Since: 1.0
649 _adg_signals[CANVAS_CHANGED] =
650 g_signal_new("canvas-changed", ADG_GTK_TYPE_AREA,
651 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
652 G_STRUCT_OFFSET(AdgGtkAreaClass, canvas_changed),
653 NULL, NULL,
654 g_cclosure_marshal_VOID__OBJECT,
655 G_TYPE_NONE, 1, ADG_TYPE_CANVAS);
658 * AdgGtkArea::extents-changed:
659 * @area: an #AdgGtkArea
660 * @old_extents: the old #CpmlExtents struct
662 * Emitted when the extents of @area have been changed.
663 * The old extents are always compared to the new ones,
664 * so when the extents are recalculated but the result
665 * is the same the signal is not emitted.
667 * The extents of #AdgGtkArea are subject to the render
668 * map, so changing the #AdgGtkArea:render-map property
669 * will emit this signal too.
671 * Since: 1.0
673 _adg_signals[EXTENTS_CHANGED] =
674 g_signal_new("extents-changed", ADG_GTK_TYPE_AREA,
675 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
676 G_STRUCT_OFFSET(AdgGtkAreaClass, extents_changed),
677 NULL, NULL,
678 g_cclosure_marshal_VOID__POINTER,
679 G_TYPE_NONE, 1, G_TYPE_POINTER);
682 static void
683 adg_gtk_area_init(AdgGtkArea *area)
685 AdgGtkAreaPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(area,
686 ADG_GTK_TYPE_AREA,
687 AdgGtkAreaPrivate);
689 data->canvas = NULL;
690 data->factor = 1.05;
691 data->autozoom = FALSE;
692 cairo_matrix_init_identity(&data->render_map);
694 data->initialized = FALSE;
695 data->x_event = 0;
696 data->y_event = 0;
698 area->data = data;
700 /* Enable GDK events to catch wheel rotation and drag */
701 gtk_widget_add_events((GtkWidget *) area,
702 GDK_BUTTON_PRESS_MASK |
703 GDK_BUTTON2_MOTION_MASK |
704 GDK_SCROLL_MASK);
711 * adg_gtk_area_new:
713 * Creates a new empty #AdgGtkArea. The widget is useful only after
714 * an #AdgCanvas has been added either using the #AdgGtkArea:canvas
715 * property or with adg_gtk_area_set_canvas().
717 * Returns: the newly created widget
719 * Since: 1.0
721 GtkWidget *
722 adg_gtk_area_new(void)
724 return g_object_new(ADG_GTK_TYPE_AREA, NULL);
728 * adg_gtk_area_new_with_canvas:
729 * @canvas: the #AdgCanvas shown by this widget
731 * Creates a new #AdgGtkArea and sets the #AdgGtkArea:canvas property
732 * to @canvas.
734 * Returns: the newly created widget
736 * Since: 1.0
738 GtkWidget *
739 adg_gtk_area_new_with_canvas(AdgCanvas *canvas)
741 g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL);
743 return g_object_new(ADG_GTK_TYPE_AREA, "canvas", canvas, NULL);
747 * adg_gtk_area_set_canvas:
748 * @area: an #AdgGtkArea
749 * @canvas: the new #AdgCanvas
751 * Sets a new canvas on @area. The old canvas, if presents, is
752 * unreferenced.
754 * Since: 1.0
756 void
757 adg_gtk_area_set_canvas(AdgGtkArea *area, AdgCanvas *canvas)
759 g_return_if_fail(ADG_GTK_IS_AREA(area));
760 g_object_set(area, "canvas", canvas, NULL);
764 * adg_gtk_area_get_canvas:
765 * @area: an #AdgGtkArea
767 * Gets the canvas associated to @area. The returned canvas
768 * is owned by @area and should not be modified or freed.
770 * Returns: (transfer none): the requested #AdgCanvas object or <constant>NULL</constant> on errors.
772 * Since: 1.0
774 AdgCanvas *
775 adg_gtk_area_get_canvas(AdgGtkArea *area)
777 AdgGtkAreaPrivate *data;
779 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
781 data = area->data;
783 return data->canvas;
787 * adg_gtk_area_set_render_map:
788 * @area: an #AdgGtkArea object
789 * @map: the new map
791 * Sets the new render transformation of @area to @map: the
792 * old map is discarded. If @map is <constant>NULL</constant>,
793 * the render map is left unchanged.
795 * <note><para>
796 * The render map is an implementation detail and this function
797 * is expected to be used only by #AdgGtkArea derived objects.
798 * </para></note>
800 * Since: 1.0
802 void
803 adg_gtk_area_set_render_map(AdgGtkArea *area, const cairo_matrix_t *map)
805 g_return_if_fail(ADG_GTK_IS_AREA(area));
806 g_object_set(area, "render-map", map, NULL);
810 * adg_gtk_area_transform_render_map:
811 * @area: an #AdgGtkArea object
812 * @transformation: the transformation to apply
813 * @mode: how @transformation should be applied
815 * Convenient function to change the render map of @area by
816 * applying @tranformation using the @mode operator. This is
817 * logically equivalent to the following:
819 * <informalexample><programlisting language="C">
820 * cairo_matrix_t map;
821 * adg_matrix_copy(&map, adg_gtk_area_get_render_map(area));
822 * adg_matrix_transform(&map, transformation, mode);
823 * adg_gtk_area_set_render_map(area, &map);
824 * </programlisting></informalexample>
826 * <note><para>
827 * The render map is an implementation detail and this function
828 * is expected to be used only by #AdgGtkArea derived objects.
829 * </para></note>
831 * Since: 1.0
833 void
834 adg_gtk_area_transform_render_map(AdgGtkArea *area,
835 const cairo_matrix_t *transformation,
836 AdgTransformMode mode)
838 AdgGtkAreaPrivate *data;
839 cairo_matrix_t map;
841 g_return_if_fail(ADG_GTK_IS_AREA(area));
842 g_return_if_fail(transformation != NULL);
844 data = area->data;
846 adg_matrix_copy(&map, &data->render_map);
847 adg_matrix_transform(&map, transformation, mode);
849 g_object_set(area, "render-map", &map, NULL);
853 * adg_gtk_area_get_render_map:
854 * @area: an #AdgGtkArea object
856 * Gets the render map.
858 * Returns: the requested map or <constant>NULL</constant> on errors.
860 * Since: 1.0
862 const cairo_matrix_t *
863 adg_gtk_area_get_render_map(AdgGtkArea *area)
865 AdgGtkAreaPrivate *data;
867 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
869 data = area->data;
871 return &data->render_map;
875 * adg_gtk_area_get_extents:
876 * @area: an #AdgGtkArea
878 * Gets the extents of the canvas bound to @area. The returned
879 * struct is owned by @area and should not modified or freed.
881 * The extents of an #AdgGtkArea instance are the extents of
882 * its canvas (as returned by adg_entity_get_extents()) with
883 * the margins added to it and the #AdgGtkArea:render-map
884 * transformation applied.
886 * If @area does not have any canvas associated to it or the
887 * canvas is invalid or empty, an undefined #CpmlExtents
888 * struct will be returned.
890 * The canvas will be updated, meaning adg_entity_arrange()
891 * is called before the extents computation.
893 * Returns: the extents of the @area canvas or <constant>NULL</constant> on errors.
895 * Since: 1.0
897 const CpmlExtents *
898 adg_gtk_area_get_extents(AdgGtkArea *area)
900 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
902 return _adg_get_extents(area);
906 * adg_gtk_area_get_zoom:
907 * @area: an #AdgGtkArea
909 * Gets the last zoom coefficient applied on the canvas of @area.
910 * If the #AdgGtkArea:autozoom property is <constant>FALSE</constant>,
911 * the value returned should be always 1.
913 * Returns: the current zoom coefficient.
915 * Since: 1.0
917 gdouble
918 adg_gtk_area_get_zoom(AdgGtkArea *area)
920 AdgGtkAreaPrivate *data;
922 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
924 data = area->data;
925 return data->render_map.xx;
929 * adg_gtk_area_set_factor:
930 * @area: an #AdgGtkArea
931 * @factor: the new zoom factor
933 * Sets a new zoom factor to @area. If the factor is less than
934 * 1, it will be clamped to 1.
936 * Since: 1.0
938 void
939 adg_gtk_area_set_factor(AdgGtkArea *area, gdouble factor)
941 g_return_if_fail(ADG_GTK_IS_AREA(area));
942 g_object_set(area, "factor", factor, NULL);
946 * adg_gtk_area_get_factor:
947 * @area: an #AdgGtkArea
949 * Gets the zoom factor associated to @area. The zoom factor is
950 * directly used to zoom in (that is, the default zoom factor of
951 * 1.05 will zoom of 5% every iteration) and it is reversed while
952 * zooming out (that is, the default factor will be 1/1.05).
954 * Returns: the requested zoom factor or 0 on error
956 * Since: 1.0
958 gdouble
959 adg_gtk_area_get_factor(AdgGtkArea *area)
961 AdgGtkAreaPrivate *data;
963 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
965 data = area->data;
966 return data->factor;
970 * adg_gtk_area_switch_autozoom:
971 * @area: an #AdgGtkArea
972 * @state: the new autozoom state
974 * Sets the #AdgGtkArea:autozoom property of @area to @state. When the
975 * autozoom feature is enabled, @area reacts to any size allocation
976 * by adjusting its zoom coefficient in global space. This means the
977 * drawing will fill the available space (keeping its aspect ratio)
978 * when resizing the window.
980 * Since: 1.0
982 void
983 adg_gtk_area_switch_autozoom(AdgGtkArea *area, gboolean state)
985 g_return_if_fail(ADG_GTK_IS_AREA(area));
986 g_object_set(area, "autozoom", state, NULL);
990 * adg_gtk_area_has_autozoom:
991 * @area: an #AdgGtkArea
993 * Gets the current state of the #AdgGtkArea:autozoom property on
994 * the @area object.
996 * Returns: the current autozoom state
998 * Since: 1.0
1000 gboolean
1001 adg_gtk_area_has_autozoom(AdgGtkArea *area)
1003 AdgGtkAreaPrivate *data;
1005 g_return_val_if_fail(ADG_GTK_IS_AREA(area), FALSE);
1007 data = area->data;
1008 return data->autozoom;
1012 * adg_gtk_area_reset:
1013 * @area: an #AdgGtkArea
1015 * Forcibly resets the zoom ratio and position of the canvas bound
1016 * to @area. This means the canvas will be scaled and centered on
1017 * the current available space.
1019 void
1020 adg_gtk_area_reset(AdgGtkArea *area)
1022 AdgGtkAreaPrivate *data;
1023 GtkWidget *parent;
1024 const CpmlExtents *sheet;
1025 GtkAllocation allocation;
1026 CpmlPair size;
1027 gdouble zoom;
1029 g_return_if_fail(ADG_GTK_IS_AREA(area));
1031 data = area->data;
1032 cairo_matrix_init_identity(&data->render_map);
1034 sheet = _adg_get_extents(area);
1035 if (!sheet->is_defined || sheet->size.x <= 0 || sheet->size.y <= 0)
1036 return;
1038 parent = gtk_widget_get_parent((GtkWidget *) area);
1039 gtk_widget_get_allocation(parent, &allocation);
1040 size.x = allocation.width;
1041 size.y = allocation.height;
1042 zoom = MIN(size.x / sheet->size.x, size.y / sheet->size.y);
1044 cairo_matrix_scale(&data->render_map, zoom, zoom);
1045 cairo_matrix_translate(&data->render_map,
1046 (size.x / zoom - sheet->size.x) / 2 - sheet->org.x,
1047 (size.y / zoom - sheet->size.y) / 2 - sheet->org.y);
1049 /* Trigger a resize trying to hide the scrollbars on the parent */
1050 gtk_widget_queue_resize(parent);
1054 * adg_gtk_area_canvas_changed:
1055 * @area: an #AdgGtkArea
1056 * @old_canvas: the old canvas bound to @area
1058 * Emits the #AdgGtkArea::canvas-changed signal on @area.
1060 * Since: 1.0
1062 void
1063 adg_gtk_area_canvas_changed(AdgGtkArea *area, AdgCanvas *old_canvas)
1065 g_return_if_fail(ADG_GTK_IS_AREA(area));
1067 g_signal_emit(area, _adg_signals[CANVAS_CHANGED], 0, old_canvas);
1071 * adg_gtk_area_extents_changed:
1072 * @area: an #AdgGtkArea
1073 * @old_extents: the old extents of @area
1075 * Emits the #AdgGtkArea::extents-changed signal on @area.
1077 * Since: 1.0
1079 void
1080 adg_gtk_area_extents_changed(AdgGtkArea *area, const CpmlExtents *old_extents)
1082 g_return_if_fail(ADG_GTK_IS_AREA(area));
1084 g_signal_emit(area, _adg_signals[EXTENTS_CHANGED], 0, old_extents);