1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009,2010,2011,2012,2013 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"
71 #include "adg-matrix-fallback.h"
73 #include "adg-gtk-area.h"
74 #include "adg-gtk-area-private.h"
76 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_gtk_area_parent_class)
77 #define _ADG_OLD_WIDGET_CLASS ((GtkWidgetClass *) adg_gtk_area_parent_class)
80 G_DEFINE_TYPE(AdgGtkArea
, adg_gtk_area
, GTK_TYPE_DRAWING_AREA
)
97 static void _adg_dispose (GObject
*object
);
98 static void _adg_get_property (GObject
*object
,
102 static void _adg_set_property (GObject
*object
,
106 static void _adg_size_request (GtkWidget
*widget
,
107 GtkRequisition
*requisition
);
108 static void _adg_size_allocate (GtkWidget
*widget
,
109 GtkAllocation
*allocation
);
110 static gboolean
_adg_expose_event (GtkWidget
*widget
,
111 GdkEventExpose
*event
);
112 static gboolean
_adg_scroll_event (GtkWidget
*widget
,
113 GdkEventScroll
*event
);
114 static gboolean
_adg_button_press_event (GtkWidget
*widget
,
115 GdkEventButton
*event
);
116 static gboolean
_adg_motion_notify_event(GtkWidget
*widget
,
117 GdkEventMotion
*event
);
118 static gboolean
_adg_get_map (GtkWidget
*widget
,
119 gboolean local_space
,
121 cairo_matrix_t
*inverted
);
122 static void _adg_set_map (GtkWidget
*widget
,
123 gboolean local_space
,
124 const cairo_matrix_t
*map
);
125 static void _adg_canvas_changed (AdgGtkArea
*area
,
126 AdgCanvas
*old_canvas
);
127 static const CpmlExtents
*
128 _adg_get_extents (AdgGtkArea
*area
);
130 static guint _adg_signals
[LAST_SIGNAL
] = { 0 };
134 adg_gtk_area_class_init(AdgGtkAreaClass
*klass
)
136 GObjectClass
*gobject_class
;
137 GtkWidgetClass
*widget_class
;
140 gobject_class
= (GObjectClass
*) klass
;
141 widget_class
= (GtkWidgetClass
*) klass
;
143 g_type_class_add_private(klass
, sizeof(AdgGtkAreaPrivate
));
145 gobject_class
->dispose
= _adg_dispose
;
146 gobject_class
->get_property
= _adg_get_property
;
147 gobject_class
->set_property
= _adg_set_property
;
149 widget_class
->size_request
= _adg_size_request
;
150 widget_class
->size_allocate
= _adg_size_allocate
;
151 widget_class
->expose_event
= _adg_expose_event
;
152 widget_class
->scroll_event
= _adg_scroll_event
;
153 widget_class
->button_press_event
= _adg_button_press_event
;
154 widget_class
->motion_notify_event
= _adg_motion_notify_event
;
156 klass
->canvas_changed
= _adg_canvas_changed
;
158 param
= g_param_spec_object("canvas",
160 P_("The canvas to be shown"),
162 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT
);
163 g_object_class_install_property(gobject_class
, PROP_CANVAS
, param
);
165 param
= g_param_spec_double("factor",
167 P_("The factor to use while zooming in and out (usually with the mouse wheel)"),
168 1., G_MAXDOUBLE
, 1.05,
170 g_object_class_install_property(gobject_class
, PROP_FACTOR
, param
);
172 param
= g_param_spec_boolean("autozoom",
174 P_("When enabled, automatically adjust the zoom in global space at every size allocation"),
177 g_object_class_install_property(gobject_class
, PROP_AUTOZOOM
, param
);
179 param
= g_param_spec_boxed("render-map",
181 P_("The transformation to be applied on the canvas before rendering it"),
182 CAIRO_GOBJECT_TYPE_MATRIX
,
184 g_object_class_install_property(gobject_class
, PROP_RENDER_MAP
, param
);
187 * AdgGtkArea::canvas-changed:
188 * @area: an #AdgGtkArea
189 * @old_canvas: the old #AdgCanvas object
191 * Emitted when the #AdgGtkArea has a new canvas. If the new canvas
192 * is the same as the old one, the signal is not emitted.
196 _adg_signals
[CANVAS_CHANGED
] =
197 g_signal_new("canvas-changed", ADG_GTK_TYPE_AREA
,
198 G_SIGNAL_RUN_LAST
|G_SIGNAL_NO_RECURSE
,
199 G_STRUCT_OFFSET(AdgGtkAreaClass
, canvas_changed
),
201 adg_marshal_VOID__OBJECT
,
202 G_TYPE_NONE
, 1, ADG_TYPE_CANVAS
);
205 * AdgGtkArea::extents-changed:
206 * @area: an #AdgGtkArea
207 * @old_extents: the old #CpmlExtents struct
209 * Emitted when the extents of @area have been changed.
210 * The old extents are always compared to the new ones,
211 * so when the extents are recalculated but the result
212 * is the same the signal is not emitted.
214 * The extents of #AdgGtkArea are subject to the render
215 * map, so changing the #AdgGtkArea:render-map property
216 * will emit this signal too.
220 _adg_signals
[EXTENTS_CHANGED
] =
221 g_signal_new("extents-changed", ADG_GTK_TYPE_AREA
,
222 G_SIGNAL_RUN_LAST
|G_SIGNAL_NO_RECURSE
,
223 G_STRUCT_OFFSET(AdgGtkAreaClass
, extents_changed
),
225 adg_marshal_VOID__POINTER
,
226 G_TYPE_NONE
, 1, G_TYPE_POINTER
);
230 adg_gtk_area_init(AdgGtkArea
*area
)
232 AdgGtkAreaPrivate
*data
= G_TYPE_INSTANCE_GET_PRIVATE(area
,
238 data
->autozoom
= FALSE
;
239 cairo_matrix_init_identity(&data
->render_map
);
241 data
->initialized
= FALSE
;
247 /* Enable GDK events to catch wheel rotation and drag */
248 gtk_widget_add_events((GtkWidget
*) area
,
249 GDK_BUTTON_PRESS_MASK
|
250 GDK_BUTTON2_MOTION_MASK
|
255 _adg_dispose(GObject
*object
)
257 AdgGtkAreaPrivate
*data
= ((AdgGtkArea
*) object
)->data
;
260 g_object_unref(data
->canvas
);
264 if (_ADG_OLD_OBJECT_CLASS
->dispose
)
265 _ADG_OLD_OBJECT_CLASS
->dispose(object
);
269 _adg_get_property(GObject
*object
, guint prop_id
,
270 GValue
*value
, GParamSpec
*pspec
)
272 AdgGtkAreaPrivate
*data
= ((AdgGtkArea
*) object
)->data
;
276 g_value_set_object(value
, data
->canvas
);
279 g_value_set_double(value
, data
->factor
);
282 g_value_set_boolean(value
, data
->autozoom
);
284 case PROP_RENDER_MAP
:
285 g_value_set_boxed(value
, &data
->render_map
);
288 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
294 _adg_set_property(GObject
*object
, guint prop_id
,
295 const GValue
*value
, GParamSpec
*pspec
)
298 AdgGtkAreaPrivate
*data
;
299 AdgCanvas
*new_canvas
, *old_canvas
;
301 area
= (AdgGtkArea
*) object
;
306 new_canvas
= g_value_get_object(value
);
307 old_canvas
= data
->canvas
;
308 if (new_canvas
!= old_canvas
) {
309 if (new_canvas
!= NULL
)
310 g_object_ref(new_canvas
);
311 if (old_canvas
!= NULL
)
312 g_object_unref(old_canvas
);
313 data
->canvas
= new_canvas
;
314 g_signal_emit(area
, _adg_signals
[CANVAS_CHANGED
], 0, old_canvas
);
318 data
->factor
= g_value_get_double(value
);
321 data
->autozoom
= g_value_get_boolean(value
);
323 case PROP_RENDER_MAP
:
324 adg_matrix_copy(&data
->render_map
, g_value_get_boxed(value
));
327 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
336 * Creates a new empty #AdgGtkArea. The widget is useful only after
337 * an #AdgCanvas has been added either using the #AdgGtkArea:canvas
338 * property or with adg_gtk_area_set_canvas().
340 * Returns: the newly created widget
345 adg_gtk_area_new(void)
347 return g_object_new(ADG_GTK_TYPE_AREA
, NULL
);
351 * adg_gtk_area_new_with_canvas:
352 * @canvas: the #AdgCanvas shown by this widget
354 * Creates a new #AdgGtkArea and sets the #AdgGtkArea:canvas property
357 * Returns: the newly created widget
362 adg_gtk_area_new_with_canvas(AdgCanvas
*canvas
)
364 g_return_val_if_fail(ADG_IS_CANVAS(canvas
), NULL
);
366 return g_object_new(ADG_GTK_TYPE_AREA
, "canvas", canvas
, NULL
);
370 * adg_gtk_area_set_canvas:
371 * @area: an #AdgGtkArea
372 * @canvas: the new #AdgCanvas
374 * Sets a new canvas on @area. The old canvas, if presents, is
380 adg_gtk_area_set_canvas(AdgGtkArea
*area
, AdgCanvas
*canvas
)
382 g_return_if_fail(ADG_GTK_IS_AREA(area
));
383 g_object_set(area
, "canvas", canvas
, NULL
);
387 * adg_gtk_area_get_canvas:
388 * @area: an #AdgGtkArea
390 * Gets the canvas associated to @area. The returned canvas
391 * is owned by @area and should not be modified or freed.
393 * Returns: (transfer none): the requested #AdgCanvas object or
399 adg_gtk_area_get_canvas(AdgGtkArea
*area
)
401 AdgGtkAreaPrivate
*data
;
403 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), NULL
);
411 * adg_gtk_area_set_render_map:
412 * @area: an #AdgGtkArea object
415 * Sets the new render transformation of @area to @map:
416 * the old map is discarded. If @map is %NULL, the render
417 * map is left unchanged.
420 * The render map is an implementation detail and this function
421 * is expected to be used only by #AdgGtkArea derived objects.
427 adg_gtk_area_set_render_map(AdgGtkArea
*area
, const cairo_matrix_t
*map
)
429 g_return_if_fail(ADG_GTK_IS_AREA(area
));
430 g_object_set(area
, "render-map", map
, NULL
);
434 * adg_gtk_area_transform_render_map:
435 * @area: an #AdgGtkArea object
436 * @transformation: the transformation to apply
437 * @mode: how @transformation should be applied
439 * Convenient function to change the render map of @area by
440 * applying @tranformation using the @mode operator. This is
441 * logically equivalent to the following:
444 * cairo_matrix_t map;
445 * adg_matrix_copy(&map, adg_gtk_area_get_render_map(area));
446 * adg_matrix_transform(&map, transformation, mode);
447 * adg_gtk_area_set_render_map(area, &map);
451 * The render map is an implementation detail and this function
452 * is expected to be used only by #AdgGtkArea derived objects.
458 adg_gtk_area_transform_render_map(AdgGtkArea
*area
,
459 const cairo_matrix_t
*transformation
,
460 AdgTransformMode mode
)
462 AdgGtkAreaPrivate
*data
;
465 g_return_if_fail(ADG_GTK_IS_AREA(area
));
466 g_return_if_fail(transformation
!= NULL
);
470 adg_matrix_copy(&map
, &data
->render_map
);
471 adg_matrix_transform(&map
, transformation
, mode
);
473 g_object_set(area
, "render-map", &map
, NULL
);
477 * adg_gtk_area_get_render_map:
478 * @area: an #AdgGtkArea object
480 * Gets the render map.
482 * Returns: the requested map or %NULL on errors
486 const cairo_matrix_t
*
487 adg_gtk_area_get_render_map(AdgGtkArea
*area
)
489 AdgGtkAreaPrivate
*data
;
491 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), NULL
);
495 return &data
->render_map
;
499 * adg_gtk_area_get_extents:
500 * @area: an #AdgGtkArea
502 * Gets the extents of the canvas bound to @area. The returned
503 * struct is owned by @area and should not modified or freed.
505 * The extents of an #AdgGtkArea instance are the extents of
506 * its canvas (as returned by adg_entity_get_extents()) with
507 * the margins added to it and the #AdgGtkArea:render-map
508 * transformation applied.
510 * If @area does not have any canvas associated to it or the
511 * canvas is invalid or empty, an undefined #CpmlExtents
512 * struct will be returned.
514 * The canvas will be updated, meaning adg_entity_arrange()
515 * is called before the extents computation.
517 * Returns: the extents of the @area canvas or %NULL on errors
522 adg_gtk_area_get_extents(AdgGtkArea
*area
)
524 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), NULL
);
526 return _adg_get_extents(area
);
530 * adg_gtk_area_get_zoom:
531 * @area: an #AdgGtkArea
533 * Gets the last zoom coefficient applied on the canvas of @area.
534 * If the #AdgGtkArea:autozoom property is %FALSE, the value
535 * returned should be always %1.
537 * Returns: the current zoom coefficient
542 adg_gtk_area_get_zoom(AdgGtkArea
*area
)
544 AdgGtkAreaPrivate
*data
;
546 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), 0.);
549 return data
->render_map
.xx
;
553 * adg_gtk_area_set_factor:
554 * @area: an #AdgGtkArea
555 * @factor: the new zoom factor
557 * Sets a new zoom factor to @area. If the factor is less than
558 * 1, it will be clamped to 1.
563 adg_gtk_area_set_factor(AdgGtkArea
*area
, gdouble factor
)
565 g_return_if_fail(ADG_GTK_IS_AREA(area
));
566 g_object_set(area
, "factor", factor
, NULL
);
570 * adg_gtk_area_get_factor:
571 * @area: an #AdgGtkArea
573 * Gets the zoom factor associated to @area. The zoom factor is
574 * directly used to zoom in (that is, the default zoom factor of
575 * 1.05 will zoom of 5% every iteration) and it is reversed while
576 * zooming out (that is, the default factor will be 1/1.05).
578 * Returns: the requested zoom factor or 0 on error
583 adg_gtk_area_get_factor(AdgGtkArea
*area
)
585 AdgGtkAreaPrivate
*data
;
587 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), 0.);
594 * adg_gtk_area_switch_autozoom:
595 * @area: an #AdgGtkArea
596 * @state: the new autozoom state
598 * Sets the #AdgGtkArea:autozoom property of @area to @state. When the
599 * autozoom feature is enabled, @area reacts to any size allocation
600 * by adjusting its zoom coefficient in global space. This means the
601 * drawing will fill the available space (keeping its aspect ratio)
602 * when resizing the window.
607 adg_gtk_area_switch_autozoom(AdgGtkArea
*area
, gboolean state
)
609 g_return_if_fail(ADG_GTK_IS_AREA(area
));
610 g_object_set(area
, "autozoom", state
, NULL
);
614 * adg_gtk_area_has_autozoom:
615 * @area: an #AdgGtkArea
617 * Gets the current state of the #AdgGtkArea:autozoom property on
620 * Returns: the current autozoom state
625 adg_gtk_area_has_autozoom(AdgGtkArea
*area
)
627 AdgGtkAreaPrivate
*data
;
629 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), FALSE
);
632 return data
->autozoom
;
636 * adg_gtk_area_reset:
637 * @area: an #AdgGtkArea
639 * Forcibly resets the zoom ratio and position of the canvas bound
640 * to @area. This means the canvas will be scaled and centered on
641 * the current available space.
644 adg_gtk_area_reset(AdgGtkArea
*area
)
646 AdgGtkAreaPrivate
*data
;
648 const CpmlExtents
*sheet
;
649 GtkAllocation allocation
;
653 g_return_if_fail(ADG_GTK_IS_AREA(area
));
656 cairo_matrix_init_identity(&data
->render_map
);
658 sheet
= _adg_get_extents(area
);
659 if (!sheet
->is_defined
|| sheet
->size
.x
<= 0 || sheet
->size
.y
<= 0)
662 parent
= gtk_widget_get_parent((GtkWidget
*) area
);
663 gtk_widget_get_allocation(parent
, &allocation
);
664 size
.x
= allocation
.width
;
665 size
.y
= allocation
.height
;
666 zoom
= MIN(size
.x
/ sheet
->size
.x
, size
.y
/ sheet
->size
.y
);
668 cairo_matrix_scale(&data
->render_map
, zoom
, zoom
);
669 cairo_matrix_translate(&data
->render_map
,
670 (size
.x
/ zoom
- sheet
->size
.x
) / 2 - sheet
->org
.x
,
671 (size
.y
/ zoom
- sheet
->size
.y
) / 2 - sheet
->org
.y
);
673 /* Trigger a resize trying to hide the scrollbars on the parent */
674 gtk_widget_queue_resize(parent
);
678 * adg_gtk_area_canvas_changed:
679 * @area: an #AdgGtkArea
680 * @old_canvas: the old canvas bound to @area
682 * Emits the #AdgGtkArea::canvas-changed signal on @area.
687 adg_gtk_area_canvas_changed(AdgGtkArea
*area
, AdgCanvas
*old_canvas
)
689 g_return_if_fail(ADG_GTK_IS_AREA(area
));
691 g_signal_emit(area
, _adg_signals
[CANVAS_CHANGED
], 0, old_canvas
);
695 * adg_gtk_area_extents_changed:
696 * @area: an #AdgGtkArea
697 * @old_extents: the old extents of @area
699 * Emits the #AdgGtkArea::extents-changed signal on @area.
704 adg_gtk_area_extents_changed(AdgGtkArea
*area
, const CpmlExtents
*old_extents
)
706 g_return_if_fail(ADG_GTK_IS_AREA(area
));
708 g_signal_emit(area
, _adg_signals
[EXTENTS_CHANGED
], 0, old_extents
);
713 _adg_size_request(GtkWidget
*widget
, GtkRequisition
*requisition
)
716 const CpmlExtents
*extents
;
718 area
= (AdgGtkArea
*) widget
;
719 extents
= _adg_get_extents(area
);
721 if (extents
->is_defined
) {
722 requisition
->width
= extents
->size
.x
;
723 requisition
->height
= extents
->size
.y
;
728 * _adg_size_allocate:
729 * @widget: an #AdgGtkArea widget
730 * @allocation: the new allocation struct
732 * Scales the drawing according to the new allocation if
733 * #AdgGtkArea:autozoom is %TRUE.
735 * TODO: the current implementation initially centers the canvas
736 * on the allocation space. Further allocations (due to a
737 * window resizing, for example) use the top/left corner of the
738 * canvas as reference point. Plan different policies for either
744 _adg_size_allocate(GtkWidget
*widget
, GtkAllocation
*allocation
)
747 AdgGtkAreaPrivate
*data
;
748 const CpmlExtents
*sheet
;
752 if (_ADG_OLD_WIDGET_CLASS
->size_allocate
)
753 _ADG_OLD_WIDGET_CLASS
->size_allocate(widget
, allocation
);
755 area
= (AdgGtkArea
*) widget
;
758 sheet
= _adg_get_extents(area
);
759 if (!sheet
->is_defined
|| sheet
->size
.x
<= 0 || sheet
->size
.y
<= 0)
762 size
.x
= allocation
->width
;
763 size
.y
= allocation
->height
;
765 if (data
->autozoom
) {
766 /* Adjust the zoom according to the allocation and sheet size */
767 factor
= MIN(size
.x
/ sheet
->size
.x
, size
.y
/ sheet
->size
.y
);
768 } else if (!data
->initialized
) {
769 /* First allocation with autozoom off: keep the current zoom */
772 /* Not the first allocation and autozoom off: keep the old map */
776 if (!data
->initialized
) {
777 /* TODO: plan different attachment policies other than centering */
778 cairo_matrix_init_translate(&data
->render_map
,
779 (size
.x
- sheet
->size
.x
) / 2 - sheet
->org
.x
,
780 (size
.y
- sheet
->size
.y
) / 2 - sheet
->org
.y
);
781 data
->initialized
= TRUE
;
784 /* TODO: plan other reference points other than left/top (x0, y0) */
785 data
->render_map
.x0
*= factor
;
786 data
->render_map
.y0
*= factor
;
787 data
->render_map
.xx
*= factor
;
788 data
->render_map
.yy
*= factor
;
792 _adg_expose_event(GtkWidget
*widget
, GdkEventExpose
*event
)
794 AdgGtkAreaPrivate
*data
;
797 data
= ((AdgGtkArea
*) widget
)->data
;
798 canvas
= data
->canvas
;
800 if (canvas
!= NULL
&& event
->window
!= NULL
) {
801 cairo_t
*cr
= gdk_cairo_create(event
->window
);
802 cairo_transform(cr
, &data
->render_map
);
803 adg_entity_render((AdgEntity
*) canvas
, cr
);
807 if (_ADG_OLD_WIDGET_CLASS
->expose_event
== NULL
)
810 return _ADG_OLD_WIDGET_CLASS
->expose_event(widget
, event
);
814 _adg_scroll_event(GtkWidget
*widget
, GdkEventScroll
*event
)
816 gboolean zoom_in
, zoom_out
, local_space
, global_space
;
817 cairo_matrix_t map
, inverted
;
818 AdgGtkAreaPrivate
*data
;
821 zoom_in
= event
->direction
== GDK_SCROLL_UP
;
822 zoom_out
= event
->direction
== GDK_SCROLL_DOWN
;
823 local_space
= (event
->state
& ADG_GTK_MODIFIERS
) == 0;
824 global_space
= (event
->state
& ADG_GTK_MODIFIERS
) == GDK_SHIFT_MASK
;
826 if ((zoom_in
|| zoom_out
) && (local_space
|| global_space
) &&
827 _adg_get_map(widget
, local_space
, &map
, &inverted
)) {
828 data
= ((AdgGtkArea
*) widget
)->data
;
829 factor
= zoom_in
? data
->factor
: 1. / data
->factor
;
833 cairo_matrix_transform_point(&inverted
, &x
, &y
);
834 cairo_matrix_scale(&map
, factor
, factor
);
835 cairo_matrix_translate(&map
, x
/factor
- x
, y
/factor
- y
);
837 _adg_set_map(widget
, local_space
, &map
);
839 gtk_widget_queue_draw(widget
);
841 /* Avoid to chain up the default handler:
842 * this event has been grabbed by this function */
846 if (_ADG_OLD_WIDGET_CLASS
->scroll_event
== NULL
)
849 return _ADG_OLD_WIDGET_CLASS
->scroll_event(widget
, event
);
853 _adg_button_press_event(GtkWidget
*widget
, GdkEventButton
*event
)
855 AdgGtkAreaPrivate
*data
= ((AdgGtkArea
*) widget
)->data
;
857 if (event
->type
== GDK_BUTTON_PRESS
&& event
->button
== 2) {
858 /* Remember the starting coordinates of a (probable) translation */
859 data
->x_event
= event
->x
;
860 data
->y_event
= event
->y
;
863 if (_ADG_OLD_WIDGET_CLASS
->button_press_event
== NULL
)
866 return _ADG_OLD_WIDGET_CLASS
->button_press_event(widget
, event
);
870 _adg_motion_notify_event(GtkWidget
*widget
, GdkEventMotion
*event
)
872 gboolean translating
, local_space
, global_space
;
873 cairo_matrix_t map
, inverted
;
874 AdgGtkAreaPrivate
*data
;
877 translating
= (event
->state
& GDK_BUTTON2_MASK
) == GDK_BUTTON2_MASK
;
878 local_space
= (event
->state
& ADG_GTK_MODIFIERS
) == 0;
879 global_space
= (event
->state
& ADG_GTK_MODIFIERS
) == GDK_SHIFT_MASK
;
881 if (translating
&& (local_space
|| global_space
) &&
882 _adg_get_map(widget
, local_space
, &map
, &inverted
)) {
883 data
= ((AdgGtkArea
*) widget
)->data
;
884 x
= event
->x
- data
->x_event
;
885 y
= event
->y
- data
->y_event
;
887 cairo_matrix_transform_distance(&inverted
, &x
, &y
);
888 cairo_matrix_translate(&map
, x
, y
);
889 data
->x_event
= event
->x
;
890 data
->y_event
= event
->y
;
892 _adg_set_map(widget
, local_space
, &map
);
894 gtk_widget_queue_draw(widget
);
896 /* Avoid to chain up the default handler:
897 * this event has been grabbed by this function */
901 if (_ADG_OLD_WIDGET_CLASS
->motion_notify_event
== NULL
)
904 return _ADG_OLD_WIDGET_CLASS
->motion_notify_event(widget
, event
);
908 _adg_get_map(GtkWidget
*widget
, gboolean local_space
,
909 cairo_matrix_t
*map
, cairo_matrix_t
*inverted
)
911 AdgGtkAreaPrivate
*data
;
914 data
= ((AdgGtkArea
*) widget
)->data
;
915 entity
= (AdgEntity
*) data
->canvas
;
920 adg_matrix_copy(map
, adg_entity_get_local_map(entity
));
922 /* The inverted map is subject to the global matrix */
923 adg_matrix_copy(inverted
, adg_entity_get_global_matrix(entity
));
924 adg_matrix_transform(inverted
, map
, ADG_TRANSFORM_BEFORE
);
926 adg_matrix_copy(map
, adg_entity_get_global_map(entity
));
927 adg_matrix_copy(inverted
, map
);
930 return cairo_matrix_invert(inverted
) == CAIRO_STATUS_SUCCESS
;
934 _adg_set_map(GtkWidget
*widget
,
935 gboolean local_space
,
936 const cairo_matrix_t
*map
)
938 AdgGtkAreaPrivate
*data
;
941 data
= ((AdgGtkArea
*) widget
)->data
;
942 entity
= (AdgEntity
*) data
->canvas
;
948 /* TODO: this forcibly overwrites any local transformation */
949 adg_entity_set_local_map(entity
, map
);
951 adg_matrix_transform(&data
->render_map
, map
, ADG_TRANSFORM_BEFORE
);
954 /* This will emit the extents-changed signal when applicable */
955 _adg_get_extents((AdgGtkArea
*) widget
);
959 _adg_canvas_changed(AdgGtkArea
*area
, AdgCanvas
*old_canvas
)
961 AdgGtkAreaPrivate
*data
= area
->data
;
962 data
->initialized
= FALSE
;
965 static const CpmlExtents
*
966 _adg_get_extents(AdgGtkArea
*area
)
968 AdgGtkAreaPrivate
*data
;
970 CpmlExtents old_extents
;
973 old_extents
= data
->extents
;
974 data
->extents
.is_defined
= FALSE
;
975 canvas
= data
->canvas
;
977 if (ADG_IS_CANVAS(canvas
)) {
978 const CpmlExtents
*extents
;
981 entity
= (AdgEntity
*) canvas
;
983 adg_entity_arrange(entity
);
984 extents
= adg_entity_get_extents(entity
);
986 if (extents
!= NULL
) {
987 data
->extents
= *extents
;
988 adg_canvas_apply_margins(canvas
, &data
->extents
);
989 cpml_extents_transform(&data
->extents
, &data
->render_map
);
993 if (!cpml_extents_equal(&data
->extents
, &old_extents
))
994 g_signal_emit(area
, _adg_signals
[EXTENTS_CHANGED
], 0, &old_extents
);
996 return &data
->extents
;