1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009,2010,2011,2012 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 #GtkWidget specifically designed to contain
24 * an #AdgCanvas entity
26 * This is a #GtkDrawingArea derived object that provides an easy way
27 * to show an ADG based canvas. The associated canvas can be set
28 * directly with the adg_gtk_area_new_with_canvas() constructor
29 * function or by using adg_gtk_area_set_canvas().
31 * The 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 %SHIFT is pressed);
36 * if the mouse wheel is rotated the canvas will be scaled up or down
37 * according to the wheel direction by the factor specified in the
38 * #AdgGtkArea:factor property (again, in local space by default and
39 * in global space if %SHIFT is pressed). The adg_gtk_area_get_zoom()
40 * method could be used to retrieve the current zoom coefficient.
42 * A new transformation layer is present between the global space
43 * and the rendering: the #AdgGtkArea:render-map matrix. This
44 * transformation is applied just before the rendering and it is
45 * used to align and/or apply the zoom coefficient to the canvas
46 * without affecting the other layers. Local transformations,
47 * instead, are directly applied to the local matrix of the canvas.
56 * All fields are private and should not be used directly.
57 * Use its public methods instead.
63 #include "adg-internal.h"
66 #include "adg-container.h"
67 #include "adg-table.h"
68 #include "adg-title-block.h"
69 #include "adg-canvas.h"
70 #include "adg-gtk-utils.h"
72 #include "adg-gtk-area.h"
73 #include "adg-gtk-area-private.h"
75 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_gtk_area_parent_class)
76 #define _ADG_OLD_WIDGET_CLASS ((GtkWidgetClass *) adg_gtk_area_parent_class)
79 G_DEFINE_TYPE(AdgGtkArea
, adg_gtk_area
, GTK_TYPE_DRAWING_AREA
)
96 static void _adg_dispose (GObject
*object
);
97 static void _adg_get_property (GObject
*object
,
101 static void _adg_set_property (GObject
*object
,
105 static void _adg_size_request (GtkWidget
*widget
,
106 GtkRequisition
*requisition
);
107 static void _adg_size_allocate (GtkWidget
*widget
,
108 GtkAllocation
*allocation
);
109 static gboolean
_adg_expose_event (GtkWidget
*widget
,
110 GdkEventExpose
*event
);
111 static gboolean
_adg_scroll_event (GtkWidget
*widget
,
112 GdkEventScroll
*event
);
113 static gboolean
_adg_button_press_event (GtkWidget
*widget
,
114 GdkEventButton
*event
);
115 static gboolean
_adg_motion_notify_event(GtkWidget
*widget
,
116 GdkEventMotion
*event
);
117 static gboolean
_adg_get_map (GtkWidget
*widget
,
118 gboolean local_space
,
120 AdgMatrix
*inverted
);
121 static void _adg_set_map (GtkWidget
*widget
,
122 gboolean local_space
,
123 const AdgMatrix
*map
);
124 static void _adg_canvas_changed (AdgGtkArea
*area
,
125 AdgCanvas
*old_canvas
);
126 static const CpmlExtents
*
127 _adg_get_extents (AdgGtkArea
*area
);
129 static guint _adg_signals
[LAST_SIGNAL
] = { 0 };
133 adg_gtk_area_class_init(AdgGtkAreaClass
*klass
)
135 GObjectClass
*gobject_class
;
136 GtkWidgetClass
*widget_class
;
139 gobject_class
= (GObjectClass
*) klass
;
140 widget_class
= (GtkWidgetClass
*) klass
;
142 g_type_class_add_private(klass
, sizeof(AdgGtkAreaPrivate
));
144 gobject_class
->dispose
= _adg_dispose
;
145 gobject_class
->get_property
= _adg_get_property
;
146 gobject_class
->set_property
= _adg_set_property
;
148 widget_class
->size_request
= _adg_size_request
;
149 widget_class
->size_allocate
= _adg_size_allocate
;
150 widget_class
->expose_event
= _adg_expose_event
;
151 widget_class
->scroll_event
= _adg_scroll_event
;
152 widget_class
->button_press_event
= _adg_button_press_event
;
153 widget_class
->motion_notify_event
= _adg_motion_notify_event
;
155 klass
->canvas_changed
= _adg_canvas_changed
;
157 param
= g_param_spec_object("canvas",
159 P_("The canvas to be shown"),
161 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT
);
162 g_object_class_install_property(gobject_class
, PROP_CANVAS
, param
);
164 param
= g_param_spec_double("factor",
166 P_("The factor to use while zooming in and out (usually with the mouse wheel)"),
167 1., G_MAXDOUBLE
, 1.05,
169 g_object_class_install_property(gobject_class
, PROP_FACTOR
, param
);
171 param
= g_param_spec_boolean("autozoom",
173 P_("When enabled, automatically adjust the zoom in global space at every size allocation"),
176 g_object_class_install_property(gobject_class
, PROP_AUTOZOOM
, param
);
178 param
= g_param_spec_boxed("render-map",
180 P_("The transformation to be applied on the canvas before rendering it"),
183 g_object_class_install_property(gobject_class
, PROP_RENDER_MAP
, param
);
186 * AdgGtkArea::canvas-changed:
187 * @area: an #AdgGtkArea
188 * @old_canvas: the old #AdgCanvas object
190 * Emitted when the #AdgGtkArea has a new canvas. If the new canvas
191 * is the same as the old one, the signal is not emitted.
195 _adg_signals
[CANVAS_CHANGED
] =
196 g_signal_new("canvas-changed", ADG_GTK_TYPE_AREA
,
197 G_SIGNAL_RUN_LAST
|G_SIGNAL_NO_RECURSE
,
198 G_STRUCT_OFFSET(AdgGtkAreaClass
, canvas_changed
),
200 adg_marshal_VOID__OBJECT
,
201 G_TYPE_NONE
, 1, ADG_TYPE_CANVAS
);
204 * AdgGtkArea::extents-changed:
205 * @area: an #AdgGtkArea
206 * @old_extents: the old #CpmlExtents struct
208 * Emitted when the extents of @area have been changed.
209 * The old extents are always compared to the new ones,
210 * so when the extents are recalculated but the result
211 * is the same the signal is not emitted.
213 * The extents of #AdgGtkArea are subject to the render
214 * map, so changing the #AdgGtkArea:render-map property
215 * will emit this signal too.
219 _adg_signals
[EXTENTS_CHANGED
] =
220 g_signal_new("extents-changed", ADG_GTK_TYPE_AREA
,
221 G_SIGNAL_RUN_LAST
|G_SIGNAL_NO_RECURSE
,
222 G_STRUCT_OFFSET(AdgGtkAreaClass
, extents_changed
),
224 adg_marshal_VOID__POINTER
,
225 G_TYPE_NONE
, 1, G_TYPE_POINTER
);
229 adg_gtk_area_init(AdgGtkArea
*area
)
231 AdgGtkAreaPrivate
*data
= G_TYPE_INSTANCE_GET_PRIVATE(area
,
237 data
->autozoom
= FALSE
;
238 cairo_matrix_init_identity(&data
->render_map
);
240 data
->initialized
= FALSE
;
246 /* Enable GDK events to catch wheel rotation and drag */
247 gtk_widget_add_events((GtkWidget
*) area
,
248 GDK_BUTTON_PRESS_MASK
|
249 GDK_BUTTON2_MOTION_MASK
|
254 _adg_dispose(GObject
*object
)
256 AdgGtkAreaPrivate
*data
= ((AdgGtkArea
*) object
)->data
;
259 g_object_unref(data
->canvas
);
263 if (_ADG_OLD_OBJECT_CLASS
->dispose
)
264 _ADG_OLD_OBJECT_CLASS
->dispose(object
);
268 _adg_get_property(GObject
*object
, guint prop_id
,
269 GValue
*value
, GParamSpec
*pspec
)
271 AdgGtkAreaPrivate
*data
= ((AdgGtkArea
*) object
)->data
;
275 g_value_set_object(value
, data
->canvas
);
278 g_value_set_double(value
, data
->factor
);
281 g_value_set_boolean(value
, data
->autozoom
);
283 case PROP_RENDER_MAP
:
284 g_value_set_boxed(value
, &data
->render_map
);
287 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
293 _adg_set_property(GObject
*object
, guint prop_id
,
294 const GValue
*value
, GParamSpec
*pspec
)
297 AdgGtkAreaPrivate
*data
;
298 AdgCanvas
*new_canvas
, *old_canvas
;
300 area
= (AdgGtkArea
*) object
;
305 new_canvas
= g_value_get_object(value
);
306 old_canvas
= data
->canvas
;
307 if (new_canvas
!= old_canvas
) {
308 if (new_canvas
!= NULL
)
309 g_object_ref(new_canvas
);
310 if (old_canvas
!= NULL
)
311 g_object_unref(old_canvas
);
312 data
->canvas
= new_canvas
;
313 g_signal_emit(area
, _adg_signals
[CANVAS_CHANGED
], 0, old_canvas
);
317 data
->factor
= g_value_get_double(value
);
320 data
->autozoom
= g_value_get_boolean(value
);
322 case PROP_RENDER_MAP
:
323 adg_matrix_copy(&data
->render_map
, g_value_get_boxed(value
));
326 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
335 * Creates a new empty #AdgGtkArea. The widget is useful only after
336 * an #AdgCanvas has been added either using the #AdgGtkArea:canvas
337 * property or with adg_gtk_area_set_canvas().
339 * Returns: the newly created widget
344 adg_gtk_area_new(void)
346 return g_object_new(ADG_GTK_TYPE_AREA
, NULL
);
350 * adg_gtk_area_new_with_canvas:
351 * @canvas: the #AdgCanvas shown by this widget
353 * Creates a new #AdgGtkArea and sets the #AdgGtkArea:canvas property
356 * Returns: the newly created widget
361 adg_gtk_area_new_with_canvas(AdgCanvas
*canvas
)
363 g_return_val_if_fail(ADG_IS_CANVAS(canvas
), NULL
);
365 return g_object_new(ADG_GTK_TYPE_AREA
, "canvas", canvas
, NULL
);
369 * adg_gtk_area_set_canvas:
370 * @area: an #AdgGtkArea
371 * @canvas: the new #AdgCanvas
373 * Sets a new canvas on @area. The old canvas, if presents, is
379 adg_gtk_area_set_canvas(AdgGtkArea
*area
, AdgCanvas
*canvas
)
381 g_return_if_fail(ADG_GTK_IS_AREA(area
));
382 g_object_set(area
, "canvas", canvas
, NULL
);
386 * adg_gtk_area_get_canvas:
387 * @area: an #AdgGtkArea
389 * Gets the canvas associated to @area. The returned canvas
390 * is owned by @area and should not be modified or freed.
392 * Returns: (transfer none): the requested #AdgCanvas object or
398 adg_gtk_area_get_canvas(AdgGtkArea
*area
)
400 AdgGtkAreaPrivate
*data
;
402 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), NULL
);
410 * adg_gtk_area_set_render_map:
411 * @area: an #AdgGtkArea object
414 * Sets the new render transformation of @area to @map:
415 * the old map is discarded. If @map is %NULL, the render
416 * map is left unchanged.
419 * The render map is an implementation detail and this function
420 * is expected to be used only by #AdgGtkArea derived objects.
426 adg_gtk_area_set_render_map(AdgGtkArea
*area
, const AdgMatrix
*map
)
428 g_return_if_fail(ADG_GTK_IS_AREA(area
));
429 g_object_set(area
, "render-map", map
, NULL
);
433 * adg_gtk_area_transform_render_map:
434 * @area: an #AdgGtkArea object
435 * @transformation: the transformation to apply
436 * @mode: how @transformation should be applied
438 * Convenient function to change the render map of @area by
439 * applying @tranformation using the @mode operator. This is
440 * logically equivalent to the following:
444 * adg_matrix_copy(&map, adg_gtk_area_get_render_map(area));
445 * adg_matrix_transform(&map, transformation, mode);
446 * adg_gtk_area_set_render_map(area, &map);
450 * The render map is an implementation detail and this function
451 * is expected to be used only by #AdgGtkArea derived objects.
457 adg_gtk_area_transform_render_map(AdgGtkArea
*area
,
458 const AdgMatrix
*transformation
,
459 AdgTransformMode mode
)
461 AdgGtkAreaPrivate
*data
;
464 g_return_if_fail(ADG_GTK_IS_AREA(area
));
465 g_return_if_fail(transformation
!= NULL
);
469 adg_matrix_copy(&map
, &data
->render_map
);
470 adg_matrix_transform(&map
, transformation
, mode
);
472 g_object_set(area
, "render-map", &map
, NULL
);
476 * adg_gtk_area_get_render_map:
477 * @area: an #AdgGtkArea object
479 * Gets the render map.
481 * Returns: the requested map or %NULL on errors
486 adg_gtk_area_get_render_map(AdgGtkArea
*area
)
488 AdgGtkAreaPrivate
*data
;
490 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), NULL
);
494 return &data
->render_map
;
498 * adg_gtk_area_get_extents:
499 * @area: an #AdgGtkArea
501 * Gets the extents of the canvas bound to @area. The returned
502 * struct is owned by @area and should not modified or freed.
504 * The extents of an #AdgGtkArea instance are the extents of
505 * its canvas (as returned by adg_entity_get_extents()) with
506 * the margins added to it and the #AdgGtkArea:render-map
507 * transformation applied.
509 * If @area does not have any canvas associated to it or the
510 * canvas is invalid or empty, an undefined #CpmlExtents
511 * struct will be returned.
513 * The canvas will be updated, meaning adg_entity_arrange()
514 * is called before the extents computation.
516 * Returns: the extents of the @area canvas or %NULL on errors
521 adg_gtk_area_get_extents(AdgGtkArea
*area
)
523 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), NULL
);
525 return _adg_get_extents(area
);
529 * adg_gtk_area_get_zoom:
530 * @area: an #AdgGtkArea
532 * Gets the last zoom coefficient applied on the canvas of @area.
533 * If the #AdgGtkArea:autozoom property is %FALSE, the value
534 * returned should be always %1.
536 * Returns: the current zoom coefficient
541 adg_gtk_area_get_zoom(AdgGtkArea
*area
)
543 AdgGtkAreaPrivate
*data
;
545 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), 0.);
548 return data
->render_map
.xx
;
552 * adg_gtk_area_set_factor:
553 * @area: an #AdgGtkArea
554 * @factor: the new zoom factor
556 * Sets a new zoom factor to @area. If the factor is less than
557 * 1, it will be clamped to 1.
562 adg_gtk_area_set_factor(AdgGtkArea
*area
, gdouble factor
)
564 g_return_if_fail(ADG_GTK_IS_AREA(area
));
565 g_object_set(area
, "factor", factor
, NULL
);
569 * adg_gtk_area_get_factor:
570 * @area: an #AdgGtkArea
572 * Gets the zoom factor associated to @area. The zoom factor is
573 * directly used to zoom in (that is, the default zoom factor of
574 * 1.05 will zoom of 5% every iteration) and it is reversed while
575 * zooming out (that is, the default factor will be 1/1.05).
577 * Returns: the requested zoom factor or 0 on error
582 adg_gtk_area_get_factor(AdgGtkArea
*area
)
584 AdgGtkAreaPrivate
*data
;
586 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), 0.);
593 * adg_gtk_area_switch_autozoom:
594 * @area: an #AdgGtkArea
595 * @state: the new autozoom state
597 * Sets the #AdgGtkArea:autozoom property of @area to @state. When the
598 * autozoom feature is enabled, @area reacts to any size allocation
599 * by adjusting its zoom coefficient in global space. This means the
600 * drawing will fill the available space (keeping its aspect ratio)
601 * when resizing the window.
606 adg_gtk_area_switch_autozoom(AdgGtkArea
*area
, gboolean state
)
608 g_return_if_fail(ADG_GTK_IS_AREA(area
));
609 g_object_set(area
, "autozoom", state
, NULL
);
613 * adg_gtk_area_has_autozoom:
614 * @area: an #AdgGtkArea
616 * Gets the current state of the #AdgGtkArea:autozoom property on
619 * Returns: the current autozoom state
624 adg_gtk_area_has_autozoom(AdgGtkArea
*area
)
626 AdgGtkAreaPrivate
*data
;
628 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), FALSE
);
631 return data
->autozoom
;
635 * adg_gtk_area_reset:
636 * @area: an #AdgGtkArea
638 * Forcibly resets the zoom ratio and position of the canvas bound
639 * to @area. This means the canvas will be scaled and centered on
640 * the current available space.
643 adg_gtk_area_reset(AdgGtkArea
*area
)
645 AdgGtkAreaPrivate
*data
;
647 const CpmlExtents
*sheet
;
648 GtkAllocation allocation
;
652 g_return_if_fail(ADG_GTK_IS_AREA(area
));
655 cairo_matrix_init_identity(&data
->render_map
);
657 sheet
= _adg_get_extents(area
);
658 if (!sheet
->is_defined
|| sheet
->size
.x
<= 0 || sheet
->size
.y
<= 0)
661 parent
= gtk_widget_get_parent((GtkWidget
*) area
);
662 gtk_widget_get_allocation(parent
, &allocation
);
663 size
.x
= allocation
.width
;
664 size
.y
= allocation
.height
;
665 zoom
= MIN(size
.x
/ sheet
->size
.x
, size
.y
/ sheet
->size
.y
);
667 cairo_matrix_scale(&data
->render_map
, zoom
, zoom
);
668 cairo_matrix_translate(&data
->render_map
,
669 (size
.x
/ zoom
- sheet
->size
.x
) / 2 - sheet
->org
.x
,
670 (size
.y
/ zoom
- sheet
->size
.y
) / 2 - sheet
->org
.y
);
672 /* Trigger a resize trying to hide the scrollbars on the parent */
673 gtk_widget_queue_resize(parent
);
677 * adg_gtk_area_canvas_changed:
678 * @area: an #AdgGtkArea
679 * @old_canvas: the old canvas bound to @area
681 * Emits the #AdgGtkArea::canvas-changed signal on @area.
686 adg_gtk_area_canvas_changed(AdgGtkArea
*area
, AdgCanvas
*old_canvas
)
688 g_return_if_fail(ADG_GTK_IS_AREA(area
));
690 g_signal_emit(area
, _adg_signals
[CANVAS_CHANGED
], 0, old_canvas
);
694 * adg_gtk_area_extents_changed:
695 * @area: an #AdgGtkArea
696 * @old_extents: the old extents of @area
698 * Emits the #AdgGtkArea::extents-changed signal on @area.
703 adg_gtk_area_extents_changed(AdgGtkArea
*area
, const CpmlExtents
*old_extents
)
705 g_return_if_fail(ADG_GTK_IS_AREA(area
));
707 g_signal_emit(area
, _adg_signals
[EXTENTS_CHANGED
], 0, old_extents
);
712 _adg_size_request(GtkWidget
*widget
, GtkRequisition
*requisition
)
715 const CpmlExtents
*extents
;
717 area
= (AdgGtkArea
*) widget
;
718 extents
= _adg_get_extents(area
);
720 if (extents
->is_defined
) {
721 requisition
->width
= extents
->size
.x
;
722 requisition
->height
= extents
->size
.y
;
727 * _adg_size_allocate:
728 * @widget: an #AdgGtkArea widget
729 * @allocation: the new allocation struct
731 * Scales the drawing according to the new allocation if
732 * #AdgGtkArea:autozoom is %TRUE.
734 * TODO: the current implementation initially centers the canvas
735 * on the allocation space. Further allocations (due to a
736 * window resizing, for example) use the top/left corner of the
737 * canvas as reference point. Plan different policies for either
743 _adg_size_allocate(GtkWidget
*widget
, GtkAllocation
*allocation
)
746 AdgGtkAreaPrivate
*data
;
747 const CpmlExtents
*sheet
;
751 if (_ADG_OLD_WIDGET_CLASS
->size_allocate
)
752 _ADG_OLD_WIDGET_CLASS
->size_allocate(widget
, allocation
);
754 area
= (AdgGtkArea
*) widget
;
757 sheet
= _adg_get_extents(area
);
758 if (!sheet
->is_defined
|| sheet
->size
.x
<= 0 || sheet
->size
.y
<= 0)
761 size
.x
= allocation
->width
;
762 size
.y
= allocation
->height
;
764 if (data
->autozoom
) {
765 /* Adjust the zoom according to the allocation and sheet size */
766 factor
= MIN(size
.x
/ sheet
->size
.x
, size
.y
/ sheet
->size
.y
);
767 } else if (!data
->initialized
) {
768 /* First allocation with autozoom off: keep the current zoom */
771 /* Not the first allocation and autozoom off: keep the old map */
775 if (!data
->initialized
) {
776 /* TODO: plan different attachment policies other than centering */
777 cairo_matrix_init_translate(&data
->render_map
,
778 (size
.x
- sheet
->size
.x
) / 2 - sheet
->org
.x
,
779 (size
.y
- sheet
->size
.y
) / 2 - sheet
->org
.y
);
780 data
->initialized
= TRUE
;
783 /* TODO: plan other reference points other than left/top (x0, y0) */
784 data
->render_map
.x0
*= factor
;
785 data
->render_map
.y0
*= factor
;
786 data
->render_map
.xx
*= factor
;
787 data
->render_map
.yy
*= factor
;
791 _adg_expose_event(GtkWidget
*widget
, GdkEventExpose
*event
)
793 AdgGtkAreaPrivate
*data
;
796 data
= ((AdgGtkArea
*) widget
)->data
;
797 canvas
= data
->canvas
;
799 if (canvas
!= NULL
&& event
->window
!= NULL
) {
800 cairo_t
*cr
= gdk_cairo_create(event
->window
);
801 cairo_transform(cr
, &data
->render_map
);
802 adg_entity_render((AdgEntity
*) canvas
, cr
);
806 if (_ADG_OLD_WIDGET_CLASS
->expose_event
== NULL
)
809 return _ADG_OLD_WIDGET_CLASS
->expose_event(widget
, event
);
813 _adg_scroll_event(GtkWidget
*widget
, GdkEventScroll
*event
)
815 gboolean zoom_in
, zoom_out
, local_space
, global_space
;
816 AdgMatrix map
, inverted
;
817 AdgGtkAreaPrivate
*data
;
820 zoom_in
= event
->direction
== GDK_SCROLL_UP
;
821 zoom_out
= event
->direction
== GDK_SCROLL_DOWN
;
822 local_space
= (event
->state
& ADG_GTK_MODIFIERS
) == 0;
823 global_space
= (event
->state
& ADG_GTK_MODIFIERS
) == GDK_SHIFT_MASK
;
825 if ((zoom_in
|| zoom_out
) && (local_space
|| global_space
) &&
826 _adg_get_map(widget
, local_space
, &map
, &inverted
)) {
827 data
= ((AdgGtkArea
*) widget
)->data
;
828 factor
= zoom_in
? data
->factor
: 1. / data
->factor
;
832 cairo_matrix_transform_point(&inverted
, &x
, &y
);
833 cairo_matrix_scale(&map
, factor
, factor
);
834 cairo_matrix_translate(&map
, x
/factor
- x
, y
/factor
- y
);
836 _adg_set_map(widget
, local_space
, &map
);
838 gtk_widget_queue_draw(widget
);
840 /* Avoid to chain up the default handler:
841 * this event has been grabbed by this function */
845 if (_ADG_OLD_WIDGET_CLASS
->scroll_event
== NULL
)
848 return _ADG_OLD_WIDGET_CLASS
->scroll_event(widget
, event
);
852 _adg_button_press_event(GtkWidget
*widget
, GdkEventButton
*event
)
854 AdgGtkAreaPrivate
*data
= ((AdgGtkArea
*) widget
)->data
;
856 if (event
->type
== GDK_BUTTON_PRESS
&& event
->button
== 2) {
857 /* Remember the starting coordinates of a (probable) translation */
858 data
->x_event
= event
->x
;
859 data
->y_event
= event
->y
;
862 if (_ADG_OLD_WIDGET_CLASS
->button_press_event
== NULL
)
865 return _ADG_OLD_WIDGET_CLASS
->button_press_event(widget
, event
);
869 _adg_motion_notify_event(GtkWidget
*widget
, GdkEventMotion
*event
)
871 gboolean translating
, local_space
, global_space
;
872 AdgMatrix map
, inverted
;
873 AdgGtkAreaPrivate
*data
;
876 translating
= (event
->state
& GDK_BUTTON2_MASK
) == GDK_BUTTON2_MASK
;
877 local_space
= (event
->state
& ADG_GTK_MODIFIERS
) == 0;
878 global_space
= (event
->state
& ADG_GTK_MODIFIERS
) == GDK_SHIFT_MASK
;
880 if (translating
&& (local_space
|| global_space
) &&
881 _adg_get_map(widget
, local_space
, &map
, &inverted
)) {
882 data
= ((AdgGtkArea
*) widget
)->data
;
883 x
= event
->x
- data
->x_event
;
884 y
= event
->y
- data
->y_event
;
886 cairo_matrix_transform_distance(&inverted
, &x
, &y
);
887 cairo_matrix_translate(&map
, x
, y
);
888 data
->x_event
= event
->x
;
889 data
->y_event
= event
->y
;
891 _adg_set_map(widget
, local_space
, &map
);
893 gtk_widget_queue_draw(widget
);
895 /* Avoid to chain up the default handler:
896 * this event has been grabbed by this function */
900 if (_ADG_OLD_WIDGET_CLASS
->motion_notify_event
== NULL
)
903 return _ADG_OLD_WIDGET_CLASS
->motion_notify_event(widget
, event
);
907 _adg_get_map(GtkWidget
*widget
, gboolean local_space
,
908 AdgMatrix
*map
, AdgMatrix
*inverted
)
910 AdgGtkAreaPrivate
*data
;
913 data
= ((AdgGtkArea
*) widget
)->data
;
914 entity
= (AdgEntity
*) data
->canvas
;
919 adg_matrix_copy(map
, adg_entity_get_local_map(entity
));
921 /* The inverted map is subject to the global matrix */
922 adg_matrix_copy(inverted
, adg_entity_get_global_matrix(entity
));
923 adg_matrix_transform(inverted
, map
, ADG_TRANSFORM_BEFORE
);
925 adg_matrix_copy(map
, adg_entity_get_global_map(entity
));
926 adg_matrix_copy(inverted
, map
);
929 return cairo_matrix_invert(inverted
) == CAIRO_STATUS_SUCCESS
;
933 _adg_set_map(GtkWidget
*widget
, gboolean local_space
, const AdgMatrix
*map
)
935 AdgGtkAreaPrivate
*data
;
938 data
= ((AdgGtkArea
*) widget
)->data
;
939 entity
= (AdgEntity
*) data
->canvas
;
945 /* TODO: this forcibly overwrites any local transformation */
946 adg_entity_set_local_map(entity
, map
);
948 adg_matrix_transform(&data
->render_map
, map
, ADG_TRANSFORM_BEFORE
);
951 /* This will emit the extents-changed signal when applicable */
952 _adg_get_extents((AdgGtkArea
*) widget
);
956 _adg_canvas_changed(AdgGtkArea
*area
, AdgCanvas
*old_canvas
)
958 AdgGtkAreaPrivate
*data
= area
->data
;
959 data
->initialized
= FALSE
;
962 static const CpmlExtents
*
963 _adg_get_extents(AdgGtkArea
*area
)
965 AdgGtkAreaPrivate
*data
;
967 CpmlExtents old_extents
;
970 old_extents
= data
->extents
;
971 data
->extents
.is_defined
= FALSE
;
972 canvas
= data
->canvas
;
974 if (ADG_IS_CANVAS(canvas
)) {
975 const CpmlExtents
*extents
;
978 entity
= (AdgEntity
*) canvas
;
980 adg_entity_arrange(entity
);
981 extents
= adg_entity_get_extents(entity
);
983 if (extents
!= NULL
) {
984 data
->extents
= *extents
;
985 adg_canvas_apply_margins(canvas
, &data
->extents
);
986 cpml_extents_transform(&data
->extents
, &data
->render_map
);
990 if (!cpml_extents_equal(&data
->extents
, &old_extents
))
991 g_signal_emit(area
, _adg_signals
[EXTENTS_CHANGED
], 0, &old_extents
);
993 return &data
->extents
;