adg: updated for GTK+2 support
[adg.git] / src / adg / adg-gtk-area-gtk2.c
blob1855bb251f27c023c29361aaa3ab232ff49d3f00
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.
21 /**
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.
49 * Since: 1.0
50 **/
53 /**
54 * AdgGtkArea:
56 * All fields are private and should not be used directly.
57 * Use its public methods instead.
59 * Since: 1.0
60 **/
63 #include "adg-internal.h"
64 #include <gtk/gtk.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)
82 enum {
83 PROP_0,
84 PROP_CANVAS,
85 PROP_FACTOR,
86 PROP_AUTOZOOM,
87 PROP_RENDER_MAP
90 enum {
91 CANVAS_CHANGED,
92 EXTENTS_CHANGED,
93 LAST_SIGNAL
97 static void _adg_dispose (GObject *object);
98 static void _adg_get_property (GObject *object,
99 guint prop_id,
100 GValue *value,
101 GParamSpec *pspec);
102 static void _adg_set_property (GObject *object,
103 guint prop_id,
104 const GValue *value,
105 GParamSpec *pspec);
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,
120 cairo_matrix_t *map,
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 };
133 static void
134 adg_gtk_area_class_init(AdgGtkAreaClass *klass)
136 GObjectClass *gobject_class;
137 GtkWidgetClass *widget_class;
138 GParamSpec *param;
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",
159 P_("Canvas"),
160 P_("The canvas to be shown"),
161 ADG_TYPE_CANVAS,
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",
166 P_("Factor"),
167 P_("The factor to use while zooming in and out (usually with the mouse wheel)"),
168 1., G_MAXDOUBLE, 1.05,
169 G_PARAM_READWRITE);
170 g_object_class_install_property(gobject_class, PROP_FACTOR, param);
172 param = g_param_spec_boolean("autozoom",
173 P_("Autozoom"),
174 P_("When enabled, automatically adjust the zoom in global space at every size allocation"),
175 FALSE,
176 G_PARAM_READWRITE);
177 g_object_class_install_property(gobject_class, PROP_AUTOZOOM, param);
179 param = g_param_spec_boxed("render-map",
180 P_("Render Map"),
181 P_("The transformation to be applied on the canvas before rendering it"),
182 CAIRO_GOBJECT_TYPE_MATRIX,
183 G_PARAM_READWRITE);
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.
194 * Since: 1.0
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),
200 NULL, NULL,
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.
218 * Since: 1.0
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),
224 NULL, NULL,
225 adg_marshal_VOID__POINTER,
226 G_TYPE_NONE, 1, G_TYPE_POINTER);
229 static void
230 adg_gtk_area_init(AdgGtkArea *area)
232 AdgGtkAreaPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(area,
233 ADG_GTK_TYPE_AREA,
234 AdgGtkAreaPrivate);
236 data->canvas = NULL;
237 data->factor = 1.05;
238 data->autozoom = FALSE;
239 cairo_matrix_init_identity(&data->render_map);
241 data->initialized = FALSE;
242 data->x_event = 0;
243 data->y_event = 0;
245 area->data = data;
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 |
251 GDK_SCROLL_MASK);
254 static void
255 _adg_dispose(GObject *object)
257 AdgGtkAreaPrivate *data = ((AdgGtkArea *) object)->data;
259 if (data->canvas) {
260 g_object_unref(data->canvas);
261 data->canvas = NULL;
264 if (_ADG_OLD_OBJECT_CLASS->dispose)
265 _ADG_OLD_OBJECT_CLASS->dispose(object);
268 static void
269 _adg_get_property(GObject *object, guint prop_id,
270 GValue *value, GParamSpec *pspec)
272 AdgGtkAreaPrivate *data = ((AdgGtkArea *) object)->data;
274 switch (prop_id) {
275 case PROP_CANVAS:
276 g_value_set_object(value, data->canvas);
277 break;
278 case PROP_FACTOR:
279 g_value_set_double(value, data->factor);
280 break;
281 case PROP_AUTOZOOM:
282 g_value_set_boolean(value, data->autozoom);
283 break;
284 case PROP_RENDER_MAP:
285 g_value_set_boxed(value, &data->render_map);
286 break;
287 default:
288 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
289 break;
293 static void
294 _adg_set_property(GObject *object, guint prop_id,
295 const GValue *value, GParamSpec *pspec)
297 AdgGtkArea *area;
298 AdgGtkAreaPrivate *data;
299 AdgCanvas *new_canvas, *old_canvas;
301 area = (AdgGtkArea *) object;
302 data = area->data;
304 switch (prop_id) {
305 case PROP_CANVAS:
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);
316 break;
317 case PROP_FACTOR:
318 data->factor = g_value_get_double(value);
319 break;
320 case PROP_AUTOZOOM:
321 data->autozoom = g_value_get_boolean(value);
322 break;
323 case PROP_RENDER_MAP:
324 adg_matrix_copy(&data->render_map, g_value_get_boxed(value));
325 break;
326 default:
327 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
328 break;
334 * adg_gtk_area_new:
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
342 * Since: 1.0
344 GtkWidget *
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
355 * to @canvas.
357 * Returns: the newly created widget
359 * Since: 1.0
361 GtkWidget *
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
375 * unreferenced.
377 * Since: 1.0
379 void
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
394 * %NULL on errors
396 * Since: 1.0
398 AdgCanvas *
399 adg_gtk_area_get_canvas(AdgGtkArea *area)
401 AdgGtkAreaPrivate *data;
403 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
405 data = area->data;
407 return data->canvas;
411 * adg_gtk_area_set_render_map:
412 * @area: an #AdgGtkArea object
413 * @map: the new map
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.
419 * <note><para>
420 * The render map is an implementation detail and this function
421 * is expected to be used only by #AdgGtkArea derived objects.
422 * </para></note>
424 * Since: 1.0
426 void
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:
443 * |[
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);
448 * ]|
450 * <note><para>
451 * The render map is an implementation detail and this function
452 * is expected to be used only by #AdgGtkArea derived objects.
453 * </para></note>
455 * Since: 1.0
457 void
458 adg_gtk_area_transform_render_map(AdgGtkArea *area,
459 const cairo_matrix_t *transformation,
460 AdgTransformMode mode)
462 AdgGtkAreaPrivate *data;
463 cairo_matrix_t map;
465 g_return_if_fail(ADG_GTK_IS_AREA(area));
466 g_return_if_fail(transformation != NULL);
468 data = area->data;
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
484 * Since: 1.0
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);
493 data = area->data;
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
519 * Since: 1.0
521 const CpmlExtents *
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
539 * Since: 1.0
541 gdouble
542 adg_gtk_area_get_zoom(AdgGtkArea *area)
544 AdgGtkAreaPrivate *data;
546 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
548 data = area->data;
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.
560 * Since: 1.0
562 void
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
580 * Since: 1.0
582 gdouble
583 adg_gtk_area_get_factor(AdgGtkArea *area)
585 AdgGtkAreaPrivate *data;
587 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
589 data = area->data;
590 return data->factor;
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.
604 * Since: 1.0
606 void
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
618 * the @area object.
620 * Returns: the current autozoom state
622 * Since: 1.0
624 gboolean
625 adg_gtk_area_has_autozoom(AdgGtkArea *area)
627 AdgGtkAreaPrivate *data;
629 g_return_val_if_fail(ADG_GTK_IS_AREA(area), FALSE);
631 data = area->data;
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.
643 void
644 adg_gtk_area_reset(AdgGtkArea *area)
646 AdgGtkAreaPrivate *data;
647 GtkWidget *parent;
648 const CpmlExtents *sheet;
649 GtkAllocation allocation;
650 CpmlPair size;
651 gdouble zoom;
653 g_return_if_fail(ADG_GTK_IS_AREA(area));
655 data = area->data;
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)
660 return;
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.
684 * Since: 1.0
686 void
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.
701 * Since: 1.0
703 void
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);
712 static void
713 _adg_size_request(GtkWidget *widget, GtkRequisition *requisition)
715 AdgGtkArea *area;
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
739 * those situations.
741 * Since: 1.0
743 static void
744 _adg_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
746 AdgGtkArea *area;
747 AdgGtkAreaPrivate *data;
748 const CpmlExtents *sheet;
749 CpmlVector size;
750 gdouble factor;
752 if (_ADG_OLD_WIDGET_CLASS->size_allocate)
753 _ADG_OLD_WIDGET_CLASS->size_allocate(widget, allocation);
755 area = (AdgGtkArea *) widget;
756 data = area->data;
758 sheet = _adg_get_extents(area);
759 if (!sheet->is_defined || sheet->size.x <= 0 || sheet->size.y <= 0)
760 return;
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 */
770 factor = 1;
771 } else {
772 /* Not the first allocation and autozoom off: keep the old map */
773 return;
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;
791 static gboolean
792 _adg_expose_event(GtkWidget *widget, GdkEventExpose *event)
794 AdgGtkAreaPrivate *data;
795 AdgCanvas *canvas;
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);
804 cairo_destroy(cr);
807 if (_ADG_OLD_WIDGET_CLASS->expose_event == NULL)
808 return FALSE;
810 return _ADG_OLD_WIDGET_CLASS->expose_event(widget, event);
813 static gboolean
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;
819 double factor, x, y;
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;
830 x = event->x;
831 y = event->y;
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 */
843 return TRUE;
846 if (_ADG_OLD_WIDGET_CLASS->scroll_event == NULL)
847 return FALSE;
849 return _ADG_OLD_WIDGET_CLASS->scroll_event(widget, event);
852 static gboolean
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)
864 return FALSE;
866 return _ADG_OLD_WIDGET_CLASS->button_press_event(widget, event);
869 static gboolean
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;
875 double x, y;
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 */
898 return TRUE;
901 if (_ADG_OLD_WIDGET_CLASS->motion_notify_event == NULL)
902 return FALSE;
904 return _ADG_OLD_WIDGET_CLASS->motion_notify_event(widget, event);
907 static gboolean
908 _adg_get_map(GtkWidget *widget, gboolean local_space,
909 cairo_matrix_t *map, cairo_matrix_t *inverted)
911 AdgGtkAreaPrivate *data;
912 AdgEntity *entity;
914 data = ((AdgGtkArea *) widget)->data;
915 entity = (AdgEntity *) data->canvas;
916 if (entity == NULL)
917 return FALSE;
919 if (local_space) {
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);
925 } else {
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;
933 static void
934 _adg_set_map(GtkWidget *widget,
935 gboolean local_space,
936 const cairo_matrix_t *map)
938 AdgGtkAreaPrivate *data;
939 AdgEntity *entity;
941 data = ((AdgGtkArea *) widget)->data;
942 entity = (AdgEntity *) data->canvas;
944 if (entity == NULL)
945 return;
947 if (local_space) {
948 /* TODO: this forcibly overwrites any local transformation */
949 adg_entity_set_local_map(entity, map);
950 } else {
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);
958 static void
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;
969 AdgCanvas *canvas;
970 CpmlExtents old_extents;
972 data = area->data;
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;
979 AdgEntity *entity;
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;