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.
22 * SECTION:adg-gtk-area
23 * @short_description: A widget specifically designed to contain
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
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.
57 * All fields are private and should not be used directly.
58 * Use its public methods instead.
65 * @canvas_changed: signals that a new #AdgCanvas is bound to this widget.
66 * @extents_changed: signals that the extents on the underling #AdgCanvas
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
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.
81 #include "adg-internal.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
)
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
;
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
;
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
);
158 g_value_set_object(value
, data
->canvas
);
161 g_value_set_double(value
, data
->factor
);
164 g_value_set_boolean(value
, data
->autozoom
);
166 case PROP_RENDER_MAP
:
167 g_value_set_boxed(value
, &data
->render_map
);
170 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
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
;
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
);
196 data
->factor
= g_value_get_double(value
);
199 data
->autozoom
= g_value_get_boolean(value
);
201 case PROP_RENDER_MAP
:
202 adg_matrix_copy(&data
->render_map
, g_value_get_boxed(value
));
205 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
212 _adg_dispose(GObject
*object
)
214 AdgGtkAreaPrivate
*data
= adg_gtk_area_get_instance_private((AdgGtkArea
*) object
);
217 g_object_unref(data
->canvas
);
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
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
;
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)
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 */
267 /* Not the first allocation and autozoom off: keep the old map */
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
;
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
;
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
);
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
;
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
;
322 /* TODO: this forcibly overwrites any local transformation */
323 adg_entity_set_local_map(entity
, map
);
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
);
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 */
363 if (_ADG_OLD_WIDGET_CLASS
->scroll_event
== NULL
)
366 return _ADG_OLD_WIDGET_CLASS
->scroll_event(widget
, event
);
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
)
382 return _ADG_OLD_WIDGET_CLASS
->button_press_event(widget
, event
);
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 */
415 if (_ADG_OLD_WIDGET_CLASS
->motion_notify_event
== NULL
)
418 return _ADG_OLD_WIDGET_CLASS
->motion_notify_event(widget
, event
);
422 _adg_canvas_changed(AdgGtkArea
*area
, AdgCanvas
*old_canvas
)
424 AdgGtkAreaPrivate
*data
= adg_gtk_area_get_instance_private(area
);
425 data
->initialized
= FALSE
;
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
;
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
);
455 if (_ADG_OLD_WIDGET_CLASS
->expose_event
== NULL
)
458 return _ADG_OLD_WIDGET_CLASS
->expose_event(widget
, event
);
466 _adg_get_preferred_height(GtkWidget
*widget
,
467 gint
*minimum_height
, gint
*natural_height
)
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
;
482 _adg_get_preferred_height_for_width(GtkWidget
*widget
, gint width
,
483 gint
*minimum_height
, gint
*natural_height
)
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
;
498 _adg_get_preferred_width(GtkWidget
*widget
,
499 gint
*minimum_width
, gint
*natural_width
)
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
;
514 _adg_get_preferred_width_for_height(GtkWidget
*widget
, gint height
,
515 gint
*minimum_width
, gint
*natural_width
)
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
;
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
);
546 adg_gtk_area_class_init(AdgGtkAreaClass
*klass
)
548 GObjectClass
*gobject_class
;
549 GtkWidgetClass
*widget_class
;
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
;
560 widget_class
->size_request
= _adg_size_request
;
561 widget_class
->expose_event
= _adg_expose_event
;
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
;
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",
581 P_("The canvas to be shown"),
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",
588 P_("The factor to use while zooming in and out (usually with the mouse wheel)"),
589 1., G_MAXDOUBLE
, 1.05,
591 g_object_class_install_property(gobject_class
, PROP_FACTOR
, param
);
593 param
= g_param_spec_boolean("autozoom",
595 P_("When enabled, automatically adjust the zoom in global space at every size allocation"),
598 g_object_class_install_property(gobject_class
, PROP_AUTOZOOM
, param
);
600 param
= g_param_spec_boxed("render-map",
602 P_("The transformation to be applied on the canvas before rendering it"),
603 CAIRO_GOBJECT_TYPE_MATRIX
,
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().
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
),
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.
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
),
647 g_cclosure_marshal_VOID__POINTER
,
648 G_TYPE_NONE
, 1, G_TYPE_POINTER
);
652 adg_gtk_area_init(AdgGtkArea
*area
)
654 AdgGtkAreaPrivate
*data
= adg_gtk_area_get_instance_private(area
);
657 data
->autozoom
= FALSE
;
658 cairo_matrix_init_identity(&data
->render_map
);
659 data
->initialized
= FALSE
;
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
|
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
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
697 * Returns: the newly created widget
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
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.
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
);
749 * adg_gtk_area_set_render_map:
750 * @area: an #AdgGtkArea object
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.
758 * The render map is an implementation detail and this function
759 * is expected to be used only by #AdgGtkArea derived objects.
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>
789 * The render map is an implementation detail and this function
790 * is expected to be used only by #AdgGtkArea derived objects.
796 adg_gtk_area_transform_render_map(AdgGtkArea
*area
,
797 const cairo_matrix_t
*transformation
,
798 AdgTransformMode mode
)
800 AdgGtkAreaPrivate
*data
;
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.
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.
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.
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.
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
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
);
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.
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
957 * Returns: the current autozoom state
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.
981 adg_gtk_area_reset(AdgGtkArea
*area
)
983 AdgGtkAreaPrivate
*data
;
985 const CpmlExtents
*sheet
;
986 GtkAllocation allocation
;
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)
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.
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.
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
);