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.
64 * @canvas_changed: signals that a new #AdgCanvas is bound to this widget.
65 * @extents_changed: signals that the extents on the underling #AdgCanvas
68 * The default @canvas_changed resets the internal initialization flag, so at
69 * the first call to the size_allocate() method the zoom factor is set to %1.
71 * The default @extents_changed signal does not do anything: it is intended as
72 * a hook for derived class for refreshing GUI elements (such as scrollbars)
73 * whenever the boundary box changes.
79 #include "adg-internal.h"
82 #include "adg-container.h"
83 #include "adg-table.h"
84 #include "adg-title-block.h"
85 #include <adg-canvas.h>
86 #include "adg-gtk-utils.h"
87 #include "adg-matrix-fallback.h"
89 #include "adg-gtk-area.h"
90 #include "adg-gtk-area-private.h"
92 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_gtk_area_parent_class)
93 #define _ADG_OLD_WIDGET_CLASS ((GtkWidgetClass *) adg_gtk_area_parent_class)
96 G_DEFINE_TYPE(AdgGtkArea
, adg_gtk_area
, GTK_TYPE_DRAWING_AREA
)
113 static void _adg_dispose (GObject
*object
);
114 static void _adg_get_property (GObject
*object
,
118 static void _adg_set_property (GObject
*object
,
122 static void _adg_get_preferred_height (GtkWidget
*widget
,
123 gint
*minimum_height
,
124 gint
*natural_height
);
125 static void _adg_get_preferred_height_for_width
128 gint
*minimum_height
,
129 gint
*natural_height
);
130 static void _adg_get_preferred_width (GtkWidget
*widget
,
132 gint
*natural_width
);
133 static void _adg_get_preferred_width_for_height
137 gint
*natural_width
);
138 static void _adg_size_allocate (GtkWidget
*widget
,
139 GtkAllocation
*allocation
);
140 static gboolean
_adg_draw (GtkWidget
*widget
,
142 static gboolean
_adg_scroll_event (GtkWidget
*widget
,
143 GdkEventScroll
*event
);
144 static gboolean
_adg_button_press_event (GtkWidget
*widget
,
145 GdkEventButton
*event
);
146 static gboolean
_adg_motion_notify_event (GtkWidget
*widget
,
147 GdkEventMotion
*event
);
148 static gboolean
_adg_get_map (GtkWidget
*widget
,
149 gboolean local_space
,
151 cairo_matrix_t
*inverted
);
152 static void _adg_set_map (GtkWidget
*widget
,
153 gboolean local_space
,
154 const cairo_matrix_t
*map
);
155 static void _adg_canvas_changed (AdgGtkArea
*area
,
156 AdgCanvas
*old_canvas
);
157 static const CpmlExtents
*
158 _adg_get_extents (AdgGtkArea
*area
);
160 static guint _adg_signals
[LAST_SIGNAL
] = { 0 };
164 adg_gtk_area_class_init(AdgGtkAreaClass
*klass
)
166 GObjectClass
*gobject_class
;
167 GtkWidgetClass
*widget_class
;
170 gobject_class
= (GObjectClass
*) klass
;
171 widget_class
= (GtkWidgetClass
*) klass
;
173 g_type_class_add_private(klass
, sizeof(AdgGtkAreaPrivate
));
175 gobject_class
->dispose
= _adg_dispose
;
176 gobject_class
->get_property
= _adg_get_property
;
177 gobject_class
->set_property
= _adg_set_property
;
179 widget_class
->get_preferred_height
= _adg_get_preferred_height
;
180 widget_class
->get_preferred_height_for_width
= _adg_get_preferred_height_for_width
;
181 widget_class
->get_preferred_width
= _adg_get_preferred_width
;
182 widget_class
->get_preferred_width_for_height
= _adg_get_preferred_width_for_height
;
183 widget_class
->size_allocate
= _adg_size_allocate
;
184 widget_class
->draw
= _adg_draw
;
185 widget_class
->scroll_event
= _adg_scroll_event
;
186 widget_class
->button_press_event
= _adg_button_press_event
;
187 widget_class
->motion_notify_event
= _adg_motion_notify_event
;
189 klass
->canvas_changed
= _adg_canvas_changed
;
191 param
= g_param_spec_object("canvas",
193 P_("The canvas to be shown"),
195 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT
);
196 g_object_class_install_property(gobject_class
, PROP_CANVAS
, param
);
198 param
= g_param_spec_double("factor",
200 P_("The factor to use while zooming in and out (usually with the mouse wheel)"),
201 1., G_MAXDOUBLE
, 1.05,
203 g_object_class_install_property(gobject_class
, PROP_FACTOR
, param
);
205 param
= g_param_spec_boolean("autozoom",
207 P_("When enabled, automatically adjust the zoom in global space at every size allocation"),
210 g_object_class_install_property(gobject_class
, PROP_AUTOZOOM
, param
);
212 param
= g_param_spec_boxed("render-map",
214 P_("The transformation to be applied on the canvas before rendering it"),
215 CAIRO_GOBJECT_TYPE_MATRIX
,
217 g_object_class_install_property(gobject_class
, PROP_RENDER_MAP
, param
);
220 * AdgGtkArea::canvas-changed:
221 * @area: an #AdgGtkArea
222 * @old_canvas: the old #AdgCanvas object
224 * Emitted when the #AdgGtkArea has a new canvas. If the new canvas
225 * is the same as the old one, the signal is not emitted.
229 _adg_signals
[CANVAS_CHANGED
] =
230 g_signal_new("canvas-changed", ADG_GTK_TYPE_AREA
,
231 G_SIGNAL_RUN_LAST
|G_SIGNAL_NO_RECURSE
,
232 G_STRUCT_OFFSET(AdgGtkAreaClass
, canvas_changed
),
234 adg_marshal_VOID__OBJECT
,
235 G_TYPE_NONE
, 1, ADG_TYPE_CANVAS
);
238 * AdgGtkArea::extents-changed:
239 * @area: an #AdgGtkArea
240 * @old_extents: the old #CpmlExtents struct
242 * Emitted when the extents of @area have been changed.
243 * The old extents are always compared to the new ones,
244 * so when the extents are recalculated but the result
245 * is the same the signal is not emitted.
247 * The extents of #AdgGtkArea are subject to the render
248 * map, so changing the #AdgGtkArea:render-map property
249 * will emit this signal too.
253 _adg_signals
[EXTENTS_CHANGED
] =
254 g_signal_new("extents-changed", ADG_GTK_TYPE_AREA
,
255 G_SIGNAL_RUN_LAST
|G_SIGNAL_NO_RECURSE
,
256 G_STRUCT_OFFSET(AdgGtkAreaClass
, extents_changed
),
258 adg_marshal_VOID__POINTER
,
259 G_TYPE_NONE
, 1, G_TYPE_POINTER
);
263 adg_gtk_area_init(AdgGtkArea
*area
)
265 AdgGtkAreaPrivate
*data
= G_TYPE_INSTANCE_GET_PRIVATE(area
,
271 data
->autozoom
= FALSE
;
272 cairo_matrix_init_identity(&data
->render_map
);
274 data
->initialized
= FALSE
;
280 /* Enable GDK events to catch wheel rotation and drag */
281 gtk_widget_add_events((GtkWidget
*) area
,
282 GDK_BUTTON_PRESS_MASK
|
283 GDK_BUTTON2_MOTION_MASK
|
288 _adg_dispose(GObject
*object
)
290 AdgGtkAreaPrivate
*data
= ((AdgGtkArea
*) object
)->data
;
293 g_object_unref(data
->canvas
);
297 if (_ADG_OLD_OBJECT_CLASS
->dispose
)
298 _ADG_OLD_OBJECT_CLASS
->dispose(object
);
302 _adg_get_property(GObject
*object
, guint prop_id
,
303 GValue
*value
, GParamSpec
*pspec
)
305 AdgGtkAreaPrivate
*data
= ((AdgGtkArea
*) object
)->data
;
309 g_value_set_object(value
, data
->canvas
);
312 g_value_set_double(value
, data
->factor
);
315 g_value_set_boolean(value
, data
->autozoom
);
317 case PROP_RENDER_MAP
:
318 g_value_set_boxed(value
, &data
->render_map
);
321 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
327 _adg_set_property(GObject
*object
, guint prop_id
,
328 const GValue
*value
, GParamSpec
*pspec
)
331 AdgGtkAreaPrivate
*data
;
332 AdgCanvas
*new_canvas
, *old_canvas
;
334 area
= (AdgGtkArea
*) object
;
339 new_canvas
= g_value_get_object(value
);
340 old_canvas
= data
->canvas
;
341 if (new_canvas
!= old_canvas
) {
342 if (new_canvas
!= NULL
)
343 g_object_ref(new_canvas
);
344 if (old_canvas
!= NULL
)
345 g_object_unref(old_canvas
);
346 data
->canvas
= new_canvas
;
347 g_signal_emit(area
, _adg_signals
[CANVAS_CHANGED
], 0, old_canvas
);
351 data
->factor
= g_value_get_double(value
);
354 data
->autozoom
= g_value_get_boolean(value
);
356 case PROP_RENDER_MAP
:
357 adg_matrix_copy(&data
->render_map
, g_value_get_boxed(value
));
360 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
369 * Creates a new empty #AdgGtkArea. The widget is useful only after
370 * an #AdgCanvas has been added either using the #AdgGtkArea:canvas
371 * property or with adg_gtk_area_set_canvas().
373 * Returns: the newly created widget
378 adg_gtk_area_new(void)
380 return g_object_new(ADG_GTK_TYPE_AREA
, NULL
);
384 * adg_gtk_area_new_with_canvas:
385 * @canvas: the #AdgCanvas shown by this widget
387 * Creates a new #AdgGtkArea and sets the #AdgGtkArea:canvas property
390 * Returns: the newly created widget
395 adg_gtk_area_new_with_canvas(AdgCanvas
*canvas
)
397 g_return_val_if_fail(ADG_IS_CANVAS(canvas
), NULL
);
399 return g_object_new(ADG_GTK_TYPE_AREA
, "canvas", canvas
, NULL
);
403 * adg_gtk_area_set_canvas:
404 * @area: an #AdgGtkArea
405 * @canvas: the new #AdgCanvas
407 * Sets a new canvas on @area. The old canvas, if presents, is
413 adg_gtk_area_set_canvas(AdgGtkArea
*area
, AdgCanvas
*canvas
)
415 g_return_if_fail(ADG_GTK_IS_AREA(area
));
416 g_object_set(area
, "canvas", canvas
, NULL
);
420 * adg_gtk_area_get_canvas:
421 * @area: an #AdgGtkArea
423 * Gets the canvas associated to @area. The returned canvas
424 * is owned by @area and should not be modified or freed.
426 * Returns: (transfer none): the requested #AdgCanvas object or
432 adg_gtk_area_get_canvas(AdgGtkArea
*area
)
434 AdgGtkAreaPrivate
*data
;
436 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), NULL
);
444 * adg_gtk_area_set_render_map:
445 * @area: an #AdgGtkArea object
448 * Sets the new render transformation of @area to @map:
449 * the old map is discarded. If @map is %NULL, the render
450 * map is left unchanged.
453 * The render map is an implementation detail and this function
454 * is expected to be used only by #AdgGtkArea derived objects.
460 adg_gtk_area_set_render_map(AdgGtkArea
*area
, const cairo_matrix_t
*map
)
462 g_return_if_fail(ADG_GTK_IS_AREA(area
));
463 g_object_set(area
, "render-map", map
, NULL
);
467 * adg_gtk_area_transform_render_map:
468 * @area: an #AdgGtkArea object
469 * @transformation: the transformation to apply
470 * @mode: how @transformation should be applied
472 * Convenient function to change the render map of @area by
473 * applying @tranformation using the @mode operator. This is
474 * logically equivalent to the following:
477 * cairo_matrix_t map;
478 * adg_matrix_copy(&map, adg_gtk_area_get_render_map(area));
479 * adg_matrix_transform(&map, transformation, mode);
480 * adg_gtk_area_set_render_map(area, &map);
484 * The render map is an implementation detail and this function
485 * is expected to be used only by #AdgGtkArea derived objects.
491 adg_gtk_area_transform_render_map(AdgGtkArea
*area
,
492 const cairo_matrix_t
*transformation
,
493 AdgTransformMode mode
)
495 AdgGtkAreaPrivate
*data
;
498 g_return_if_fail(ADG_GTK_IS_AREA(area
));
499 g_return_if_fail(transformation
!= NULL
);
503 adg_matrix_copy(&map
, &data
->render_map
);
504 adg_matrix_transform(&map
, transformation
, mode
);
506 g_object_set(area
, "render-map", &map
, NULL
);
510 * adg_gtk_area_get_render_map:
511 * @area: an #AdgGtkArea object
513 * Gets the render map.
515 * Returns: the requested map or %NULL on errors
519 const cairo_matrix_t
*
520 adg_gtk_area_get_render_map(AdgGtkArea
*area
)
522 AdgGtkAreaPrivate
*data
;
524 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), NULL
);
528 return &data
->render_map
;
532 * adg_gtk_area_get_extents:
533 * @area: an #AdgGtkArea
535 * Gets the extents of the canvas bound to @area. The returned
536 * struct is owned by @area and should not modified or freed.
538 * The extents of an #AdgGtkArea instance are the extents of
539 * its canvas (as returned by adg_entity_get_extents()) with
540 * the margins added to it and the #AdgGtkArea:render-map
541 * transformation applied.
543 * If @area does not have any canvas associated to it or the
544 * canvas is invalid or empty, an undefined #CpmlExtents
545 * struct will be returned.
547 * The canvas will be updated, meaning adg_entity_arrange()
548 * is called before the extents computation.
550 * Returns: the extents of the @area canvas or %NULL on errors
555 adg_gtk_area_get_extents(AdgGtkArea
*area
)
557 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), NULL
);
559 return _adg_get_extents(area
);
563 * adg_gtk_area_get_zoom:
564 * @area: an #AdgGtkArea
566 * Gets the last zoom coefficient applied on the canvas of @area.
567 * If the #AdgGtkArea:autozoom property is %FALSE, the value
568 * returned should be always %1.
570 * Returns: the current zoom coefficient
575 adg_gtk_area_get_zoom(AdgGtkArea
*area
)
577 AdgGtkAreaPrivate
*data
;
579 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), 0.);
582 return data
->render_map
.xx
;
586 * adg_gtk_area_set_factor:
587 * @area: an #AdgGtkArea
588 * @factor: the new zoom factor
590 * Sets a new zoom factor to @area. If the factor is less than
591 * 1, it will be clamped to 1.
596 adg_gtk_area_set_factor(AdgGtkArea
*area
, gdouble factor
)
598 g_return_if_fail(ADG_GTK_IS_AREA(area
));
599 g_object_set(area
, "factor", factor
, NULL
);
603 * adg_gtk_area_get_factor:
604 * @area: an #AdgGtkArea
606 * Gets the zoom factor associated to @area. The zoom factor is
607 * directly used to zoom in (that is, the default zoom factor of
608 * 1.05 will zoom of 5% every iteration) and it is reversed while
609 * zooming out (that is, the default factor will be 1/1.05).
611 * Returns: the requested zoom factor or 0 on error
616 adg_gtk_area_get_factor(AdgGtkArea
*area
)
618 AdgGtkAreaPrivate
*data
;
620 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), 0.);
627 * adg_gtk_area_switch_autozoom:
628 * @area: an #AdgGtkArea
629 * @state: the new autozoom state
631 * Sets the #AdgGtkArea:autozoom property of @area to @state. When the
632 * autozoom feature is enabled, @area reacts to any size allocation
633 * by adjusting its zoom coefficient in global space. This means the
634 * drawing will fill the available space (keeping its aspect ratio)
635 * when resizing the window.
640 adg_gtk_area_switch_autozoom(AdgGtkArea
*area
, gboolean state
)
642 g_return_if_fail(ADG_GTK_IS_AREA(area
));
643 g_object_set(area
, "autozoom", state
, NULL
);
647 * adg_gtk_area_has_autozoom:
648 * @area: an #AdgGtkArea
650 * Gets the current state of the #AdgGtkArea:autozoom property on
653 * Returns: the current autozoom state
658 adg_gtk_area_has_autozoom(AdgGtkArea
*area
)
660 AdgGtkAreaPrivate
*data
;
662 g_return_val_if_fail(ADG_GTK_IS_AREA(area
), FALSE
);
665 return data
->autozoom
;
669 * adg_gtk_area_reset:
670 * @area: an #AdgGtkArea
672 * Forcibly resets the zoom ratio and position of the canvas bound
673 * to @area. This means the canvas will be scaled and centered on
674 * the current available space.
677 adg_gtk_area_reset(AdgGtkArea
*area
)
679 AdgGtkAreaPrivate
*data
;
681 const CpmlExtents
*sheet
;
685 g_return_if_fail(ADG_GTK_IS_AREA(area
));
688 cairo_matrix_init_identity(&data
->render_map
);
690 sheet
= _adg_get_extents(area
);
691 if (!sheet
->is_defined
|| sheet
->size
.x
<= 0 || sheet
->size
.y
<= 0)
694 parent
= gtk_widget_get_parent((GtkWidget
*) area
);
695 size
.x
= gtk_widget_get_allocated_width(parent
);
696 size
.y
= gtk_widget_get_allocated_height(parent
);
697 zoom
= MIN(size
.x
/ sheet
->size
.x
, size
.y
/ sheet
->size
.y
);
699 cairo_matrix_scale(&data
->render_map
, zoom
, zoom
);
700 cairo_matrix_translate(&data
->render_map
,
701 (size
.x
/ zoom
- sheet
->size
.x
) / 2 - sheet
->org
.x
,
702 (size
.y
/ zoom
- sheet
->size
.y
) / 2 - sheet
->org
.y
);
704 /* Trigger a resize trying to hide the scrollbars on the parent */
705 gtk_widget_queue_resize(parent
);
709 * adg_gtk_area_canvas_changed:
710 * @area: an #AdgGtkArea
711 * @old_canvas: the old canvas bound to @area
713 * Emits the #AdgGtkArea::canvas-changed signal on @area.
718 adg_gtk_area_canvas_changed(AdgGtkArea
*area
, AdgCanvas
*old_canvas
)
720 g_return_if_fail(ADG_GTK_IS_AREA(area
));
722 g_signal_emit(area
, _adg_signals
[CANVAS_CHANGED
], 0, old_canvas
);
726 * adg_gtk_area_extents_changed:
727 * @area: an #AdgGtkArea
728 * @old_extents: the old extents of @area
730 * Emits the #AdgGtkArea::extents-changed signal on @area.
735 adg_gtk_area_extents_changed(AdgGtkArea
*area
, const CpmlExtents
*old_extents
)
737 g_return_if_fail(ADG_GTK_IS_AREA(area
));
739 g_signal_emit(area
, _adg_signals
[EXTENTS_CHANGED
], 0, old_extents
);
744 _adg_get_preferred_height(GtkWidget
*widget
,
745 gint
*minimum_height
, gint
*natural_height
)
748 const CpmlExtents
*extents
;
750 area
= (AdgGtkArea
*) widget
;
751 extents
= _adg_get_extents(area
);
753 if (extents
->is_defined
) {
754 *minimum_height
= extents
->size
.y
;
755 *natural_height
= *minimum_height
;
760 _adg_get_preferred_height_for_width(GtkWidget
*widget
, gint width
,
761 gint
*minimum_height
, gint
*natural_height
)
764 const CpmlExtents
*extents
;
766 area
= (AdgGtkArea
*) widget
;
767 extents
= _adg_get_extents(area
);
769 if (extents
->is_defined
&& extents
->size
.x
> 0) {
770 *minimum_height
= extents
->size
.y
;
771 *natural_height
= *minimum_height
* width
/ extents
->size
.x
;
776 _adg_get_preferred_width(GtkWidget
*widget
,
777 gint
*minimum_width
, gint
*natural_width
)
780 const CpmlExtents
*extents
;
782 area
= (AdgGtkArea
*) widget
;
783 extents
= _adg_get_extents(area
);
785 if (extents
->is_defined
) {
786 *minimum_width
= extents
->size
.x
;
787 *natural_width
= *minimum_width
;
792 _adg_get_preferred_width_for_height(GtkWidget
*widget
, gint height
,
793 gint
*minimum_width
, gint
*natural_width
)
796 const CpmlExtents
*extents
;
798 area
= (AdgGtkArea
*) widget
;
799 extents
= _adg_get_extents(area
);
801 if (extents
->is_defined
&& extents
->size
.y
> 0) {
802 *minimum_width
= extents
->size
.x
;
803 *natural_width
= *minimum_width
* height
/ extents
->size
.y
;
808 * _adg_size_allocate:
809 * @widget: an #AdgGtkArea widget
810 * @allocation: the new allocation struct
812 * Scales the drawing according to the new allocation if
813 * #AdgGtkArea:autozoom is %TRUE.
815 * TODO: the current implementation initially centers the canvas
816 * on the allocation space. Further allocations (due to a
817 * window resizing, for example) use the top/left corner of the
818 * canvas as reference point. Plan different policies for either
824 _adg_size_allocate(GtkWidget
*widget
, GtkAllocation
*allocation
)
827 AdgGtkAreaPrivate
*data
;
828 const CpmlExtents
*sheet
;
832 if (_ADG_OLD_WIDGET_CLASS
->size_allocate
)
833 _ADG_OLD_WIDGET_CLASS
->size_allocate(widget
, allocation
);
835 area
= (AdgGtkArea
*) widget
;
838 sheet
= _adg_get_extents(area
);
839 if (!sheet
->is_defined
|| sheet
->size
.x
<= 0 || sheet
->size
.y
<= 0)
842 size
.x
= allocation
->width
;
843 size
.y
= allocation
->height
;
845 if (data
->autozoom
) {
846 /* Adjust the zoom according to the allocation and sheet size */
847 factor
= MIN(size
.x
/ sheet
->size
.x
, size
.y
/ sheet
->size
.y
);
848 } else if (!data
->initialized
) {
849 /* First allocation with autozoom off: keep the current zoom */
852 /* Not the first allocation and autozoom off: keep the old map */
856 if (!data
->initialized
) {
857 /* TODO: plan different attachment policies other than centering */
858 cairo_matrix_init_translate(&data
->render_map
,
859 (size
.x
- sheet
->size
.x
) / 2 - sheet
->org
.x
,
860 (size
.y
- sheet
->size
.y
) / 2 - sheet
->org
.y
);
861 data
->initialized
= TRUE
;
864 /* TODO: plan other reference points other than left/top (x0, y0) */
865 data
->render_map
.x0
*= factor
;
866 data
->render_map
.y0
*= factor
;
867 data
->render_map
.xx
*= factor
;
868 data
->render_map
.yy
*= factor
;
872 _adg_draw(GtkWidget
*widget
, cairo_t
*cr
)
874 AdgGtkAreaPrivate
*data
;
877 data
= ((AdgGtkArea
*) widget
)->data
;
878 canvas
= data
->canvas
;
880 if (canvas
!= NULL
) {
881 cairo_transform(cr
, &data
->render_map
);
882 adg_entity_render((AdgEntity
*) canvas
, cr
);
889 _adg_scroll_event(GtkWidget
*widget
, GdkEventScroll
*event
)
891 gboolean zoom_in
, zoom_out
, local_space
, global_space
;
892 cairo_matrix_t map
, inverted
;
893 AdgGtkAreaPrivate
*data
;
896 zoom_in
= event
->direction
== GDK_SCROLL_UP
;
897 zoom_out
= event
->direction
== GDK_SCROLL_DOWN
;
898 local_space
= (event
->state
& ADG_GTK_MODIFIERS
) == 0;
899 global_space
= (event
->state
& ADG_GTK_MODIFIERS
) == GDK_SHIFT_MASK
;
901 if ((zoom_in
|| zoom_out
) && (local_space
|| global_space
) &&
902 _adg_get_map(widget
, local_space
, &map
, &inverted
)) {
903 data
= ((AdgGtkArea
*) widget
)->data
;
904 factor
= zoom_in
? data
->factor
: 1. / data
->factor
;
908 cairo_matrix_transform_point(&inverted
, &x
, &y
);
909 cairo_matrix_scale(&map
, factor
, factor
);
910 cairo_matrix_translate(&map
, x
/factor
- x
, y
/factor
- y
);
912 _adg_set_map(widget
, local_space
, &map
);
914 gtk_widget_queue_draw(widget
);
916 /* Avoid to chain up the default handler:
917 * this event has been grabbed by this function */
921 if (_ADG_OLD_WIDGET_CLASS
->scroll_event
== NULL
)
924 return _ADG_OLD_WIDGET_CLASS
->scroll_event(widget
, event
);
928 _adg_button_press_event(GtkWidget
*widget
, GdkEventButton
*event
)
930 AdgGtkAreaPrivate
*data
= ((AdgGtkArea
*) widget
)->data
;
932 if (event
->type
== GDK_BUTTON_PRESS
&& event
->button
== 2) {
933 /* Remember the starting coordinates of a (probable) translation */
934 data
->x_event
= event
->x
;
935 data
->y_event
= event
->y
;
938 if (_ADG_OLD_WIDGET_CLASS
->button_press_event
== NULL
)
941 return _ADG_OLD_WIDGET_CLASS
->button_press_event(widget
, event
);
945 _adg_motion_notify_event(GtkWidget
*widget
, GdkEventMotion
*event
)
947 gboolean translating
, local_space
, global_space
;
948 cairo_matrix_t map
, inverted
;
949 AdgGtkAreaPrivate
*data
;
952 translating
= (event
->state
& GDK_BUTTON2_MASK
) == GDK_BUTTON2_MASK
;
953 local_space
= (event
->state
& ADG_GTK_MODIFIERS
) == 0;
954 global_space
= (event
->state
& ADG_GTK_MODIFIERS
) == GDK_SHIFT_MASK
;
956 if (translating
&& (local_space
|| global_space
) &&
957 _adg_get_map(widget
, local_space
, &map
, &inverted
)) {
958 data
= ((AdgGtkArea
*) widget
)->data
;
959 x
= event
->x
- data
->x_event
;
960 y
= event
->y
- data
->y_event
;
962 cairo_matrix_transform_distance(&inverted
, &x
, &y
);
963 cairo_matrix_translate(&map
, x
, y
);
964 data
->x_event
= event
->x
;
965 data
->y_event
= event
->y
;
967 _adg_set_map(widget
, local_space
, &map
);
969 gtk_widget_queue_draw(widget
);
971 /* Avoid to chain up the default handler:
972 * this event has been grabbed by this function */
976 if (_ADG_OLD_WIDGET_CLASS
->motion_notify_event
== NULL
)
979 return _ADG_OLD_WIDGET_CLASS
->motion_notify_event(widget
, event
);
983 _adg_get_map(GtkWidget
*widget
, gboolean local_space
,
984 cairo_matrix_t
*map
, cairo_matrix_t
*inverted
)
986 AdgGtkAreaPrivate
*data
;
989 data
= ((AdgGtkArea
*) widget
)->data
;
990 entity
= (AdgEntity
*) data
->canvas
;
995 adg_matrix_copy(map
, adg_entity_get_local_map(entity
));
997 /* The inverted map is subject to the global matrix */
998 adg_matrix_copy(inverted
, adg_entity_get_global_matrix(entity
));
999 adg_matrix_transform(inverted
, map
, ADG_TRANSFORM_BEFORE
);
1001 adg_matrix_copy(map
, adg_entity_get_global_map(entity
));
1002 adg_matrix_copy(inverted
, map
);
1005 return cairo_matrix_invert(inverted
) == CAIRO_STATUS_SUCCESS
;
1009 _adg_set_map(GtkWidget
*widget
,
1010 gboolean local_space
,
1011 const cairo_matrix_t
*map
)
1013 AdgGtkAreaPrivate
*data
;
1016 data
= ((AdgGtkArea
*) widget
)->data
;
1017 entity
= (AdgEntity
*) data
->canvas
;
1023 /* TODO: this forcibly overwrites any local transformation */
1024 adg_entity_set_local_map(entity
, map
);
1026 adg_matrix_transform(&data
->render_map
, map
, ADG_TRANSFORM_BEFORE
);
1029 /* This will emit the extents-changed signal when applicable */
1030 _adg_get_extents((AdgGtkArea
*) widget
);
1034 _adg_canvas_changed(AdgGtkArea
*area
, AdgCanvas
*old_canvas
)
1036 AdgGtkAreaPrivate
*data
= area
->data
;
1037 data
->initialized
= FALSE
;
1040 static const CpmlExtents
*
1041 _adg_get_extents(AdgGtkArea
*area
)
1043 AdgGtkAreaPrivate
*data
;
1045 CpmlExtents old_extents
;
1048 old_extents
= data
->extents
;
1049 data
->extents
.is_defined
= FALSE
;
1050 canvas
= data
->canvas
;
1052 if (ADG_IS_CANVAS(canvas
)) {
1053 const CpmlExtents
*extents
;
1056 entity
= (AdgEntity
*) canvas
;
1058 adg_entity_arrange(entity
);
1059 extents
= adg_entity_get_extents(entity
);
1061 if (extents
!= NULL
) {
1062 data
->extents
= *extents
;
1063 adg_canvas_apply_margins(canvas
, &data
->extents
);
1064 cpml_extents_transform(&data
->extents
, &data
->render_map
);
1068 if (!cpml_extents_equal(&data
->extents
, &old_extents
))
1069 g_signal_emit(area
, _adg_signals
[EXTENTS_CHANGED
], 0, &old_extents
);
1071 return &data
->extents
;