build: adg-canvas.h is generated by configure
[adg.git] / src / adg / adg-gtk-area-gtk2.c
bloba97f0c0216a0b8ec2a4bf06c6b6c4d739931e40c
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"
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)
81 enum {
82 PROP_0,
83 PROP_CANVAS,
84 PROP_FACTOR,
85 PROP_AUTOZOOM,
86 PROP_RENDER_MAP
89 enum {
90 CANVAS_CHANGED,
91 EXTENTS_CHANGED,
92 LAST_SIGNAL
96 static void _adg_dispose (GObject *object);
97 static void _adg_get_property (GObject *object,
98 guint prop_id,
99 GValue *value,
100 GParamSpec *pspec);
101 static void _adg_set_property (GObject *object,
102 guint prop_id,
103 const GValue *value,
104 GParamSpec *pspec);
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,
119 cairo_matrix_t *map,
120 cairo_matrix_t *inverted);
121 static void _adg_set_map (GtkWidget *widget,
122 gboolean local_space,
123 const cairo_matrix_t *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 };
132 static void
133 adg_gtk_area_class_init(AdgGtkAreaClass *klass)
135 GObjectClass *gobject_class;
136 GtkWidgetClass *widget_class;
137 GParamSpec *param;
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",
158 P_("Canvas"),
159 P_("The canvas to be shown"),
160 ADG_TYPE_CANVAS,
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",
165 P_("Factor"),
166 P_("The factor to use while zooming in and out (usually with the mouse wheel)"),
167 1., G_MAXDOUBLE, 1.05,
168 G_PARAM_READWRITE);
169 g_object_class_install_property(gobject_class, PROP_FACTOR, param);
171 param = g_param_spec_boolean("autozoom",
172 P_("Autozoom"),
173 P_("When enabled, automatically adjust the zoom in global space at every size allocation"),
174 FALSE,
175 G_PARAM_READWRITE);
176 g_object_class_install_property(gobject_class, PROP_AUTOZOOM, param);
178 param = g_param_spec_boxed("render-map",
179 P_("Render Map"),
180 P_("The transformation to be applied on the canvas before rendering it"),
181 ADG_TYPE_MATRIX,
182 G_PARAM_READWRITE);
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.
193 * Since: 1.0
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),
199 NULL, NULL,
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.
217 * Since: 1.0
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),
223 NULL, NULL,
224 adg_marshal_VOID__POINTER,
225 G_TYPE_NONE, 1, G_TYPE_POINTER);
228 static void
229 adg_gtk_area_init(AdgGtkArea *area)
231 AdgGtkAreaPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(area,
232 ADG_GTK_TYPE_AREA,
233 AdgGtkAreaPrivate);
235 data->canvas = NULL;
236 data->factor = 1.05;
237 data->autozoom = FALSE;
238 cairo_matrix_init_identity(&data->render_map);
240 data->initialized = FALSE;
241 data->x_event = 0;
242 data->y_event = 0;
244 area->data = data;
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 |
250 GDK_SCROLL_MASK);
253 static void
254 _adg_dispose(GObject *object)
256 AdgGtkAreaPrivate *data = ((AdgGtkArea *) object)->data;
258 if (data->canvas) {
259 g_object_unref(data->canvas);
260 data->canvas = NULL;
263 if (_ADG_OLD_OBJECT_CLASS->dispose)
264 _ADG_OLD_OBJECT_CLASS->dispose(object);
267 static void
268 _adg_get_property(GObject *object, guint prop_id,
269 GValue *value, GParamSpec *pspec)
271 AdgGtkAreaPrivate *data = ((AdgGtkArea *) object)->data;
273 switch (prop_id) {
274 case PROP_CANVAS:
275 g_value_set_object(value, data->canvas);
276 break;
277 case PROP_FACTOR:
278 g_value_set_double(value, data->factor);
279 break;
280 case PROP_AUTOZOOM:
281 g_value_set_boolean(value, data->autozoom);
282 break;
283 case PROP_RENDER_MAP:
284 g_value_set_boxed(value, &data->render_map);
285 break;
286 default:
287 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
288 break;
292 static void
293 _adg_set_property(GObject *object, guint prop_id,
294 const GValue *value, GParamSpec *pspec)
296 AdgGtkArea *area;
297 AdgGtkAreaPrivate *data;
298 AdgCanvas *new_canvas, *old_canvas;
300 area = (AdgGtkArea *) object;
301 data = area->data;
303 switch (prop_id) {
304 case PROP_CANVAS:
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);
315 break;
316 case PROP_FACTOR:
317 data->factor = g_value_get_double(value);
318 break;
319 case PROP_AUTOZOOM:
320 data->autozoom = g_value_get_boolean(value);
321 break;
322 case PROP_RENDER_MAP:
323 adg_matrix_copy(&data->render_map, g_value_get_boxed(value));
324 break;
325 default:
326 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
327 break;
333 * adg_gtk_area_new:
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
341 * Since: 1.0
343 GtkWidget *
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
354 * to @canvas.
356 * Returns: the newly created widget
358 * Since: 1.0
360 GtkWidget *
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
374 * unreferenced.
376 * Since: 1.0
378 void
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
393 * %NULL on errors
395 * Since: 1.0
397 AdgCanvas *
398 adg_gtk_area_get_canvas(AdgGtkArea *area)
400 AdgGtkAreaPrivate *data;
402 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
404 data = area->data;
406 return data->canvas;
410 * adg_gtk_area_set_render_map:
411 * @area: an #AdgGtkArea object
412 * @map: the new map
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.
418 * <note><para>
419 * The render map is an implementation detail and this function
420 * is expected to be used only by #AdgGtkArea derived objects.
421 * </para></note>
423 * Since: 1.0
425 void
426 adg_gtk_area_set_render_map(AdgGtkArea *area, const cairo_matrix_t *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:
442 * |[
443 * cairo_matrix_t map;
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);
447 * ]|
449 * <note><para>
450 * The render map is an implementation detail and this function
451 * is expected to be used only by #AdgGtkArea derived objects.
452 * </para></note>
454 * Since: 1.0
456 void
457 adg_gtk_area_transform_render_map(AdgGtkArea *area,
458 const cairo_matrix_t *transformation,
459 AdgTransformMode mode)
461 AdgGtkAreaPrivate *data;
462 cairo_matrix_t map;
464 g_return_if_fail(ADG_GTK_IS_AREA(area));
465 g_return_if_fail(transformation != NULL);
467 data = area->data;
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
483 * Since: 1.0
485 const cairo_matrix_t *
486 adg_gtk_area_get_render_map(AdgGtkArea *area)
488 AdgGtkAreaPrivate *data;
490 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
492 data = area->data;
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
518 * Since: 1.0
520 const CpmlExtents *
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
538 * Since: 1.0
540 gdouble
541 adg_gtk_area_get_zoom(AdgGtkArea *area)
543 AdgGtkAreaPrivate *data;
545 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
547 data = area->data;
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.
559 * Since: 1.0
561 void
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
579 * Since: 1.0
581 gdouble
582 adg_gtk_area_get_factor(AdgGtkArea *area)
584 AdgGtkAreaPrivate *data;
586 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
588 data = area->data;
589 return data->factor;
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.
603 * Since: 1.0
605 void
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
617 * the @area object.
619 * Returns: the current autozoom state
621 * Since: 1.0
623 gboolean
624 adg_gtk_area_has_autozoom(AdgGtkArea *area)
626 AdgGtkAreaPrivate *data;
628 g_return_val_if_fail(ADG_GTK_IS_AREA(area), FALSE);
630 data = area->data;
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.
642 void
643 adg_gtk_area_reset(AdgGtkArea *area)
645 AdgGtkAreaPrivate *data;
646 GtkWidget *parent;
647 const CpmlExtents *sheet;
648 GtkAllocation allocation;
649 CpmlPair size;
650 gdouble zoom;
652 g_return_if_fail(ADG_GTK_IS_AREA(area));
654 data = area->data;
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)
659 return;
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.
683 * Since: 1.0
685 void
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.
700 * Since: 1.0
702 void
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);
711 static void
712 _adg_size_request(GtkWidget *widget, GtkRequisition *requisition)
714 AdgGtkArea *area;
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
738 * those situations.
740 * Since: 1.0
742 static void
743 _adg_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
745 AdgGtkArea *area;
746 AdgGtkAreaPrivate *data;
747 const CpmlExtents *sheet;
748 CpmlVector size;
749 gdouble factor;
751 if (_ADG_OLD_WIDGET_CLASS->size_allocate)
752 _ADG_OLD_WIDGET_CLASS->size_allocate(widget, allocation);
754 area = (AdgGtkArea *) widget;
755 data = area->data;
757 sheet = _adg_get_extents(area);
758 if (!sheet->is_defined || sheet->size.x <= 0 || sheet->size.y <= 0)
759 return;
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 */
769 factor = 1;
770 } else {
771 /* Not the first allocation and autozoom off: keep the old map */
772 return;
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;
790 static gboolean
791 _adg_expose_event(GtkWidget *widget, GdkEventExpose *event)
793 AdgGtkAreaPrivate *data;
794 AdgCanvas *canvas;
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);
803 cairo_destroy(cr);
806 if (_ADG_OLD_WIDGET_CLASS->expose_event == NULL)
807 return FALSE;
809 return _ADG_OLD_WIDGET_CLASS->expose_event(widget, event);
812 static gboolean
813 _adg_scroll_event(GtkWidget *widget, GdkEventScroll *event)
815 gboolean zoom_in, zoom_out, local_space, global_space;
816 cairo_matrix_t map, inverted;
817 AdgGtkAreaPrivate *data;
818 double factor, x, y;
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;
829 x = event->x;
830 y = event->y;
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 */
842 return TRUE;
845 if (_ADG_OLD_WIDGET_CLASS->scroll_event == NULL)
846 return FALSE;
848 return _ADG_OLD_WIDGET_CLASS->scroll_event(widget, event);
851 static gboolean
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)
863 return FALSE;
865 return _ADG_OLD_WIDGET_CLASS->button_press_event(widget, event);
868 static gboolean
869 _adg_motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
871 gboolean translating, local_space, global_space;
872 cairo_matrix_t map, inverted;
873 AdgGtkAreaPrivate *data;
874 double x, y;
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 */
897 return TRUE;
900 if (_ADG_OLD_WIDGET_CLASS->motion_notify_event == NULL)
901 return FALSE;
903 return _ADG_OLD_WIDGET_CLASS->motion_notify_event(widget, event);
906 static gboolean
907 _adg_get_map(GtkWidget *widget, gboolean local_space,
908 cairo_matrix_t *map, cairo_matrix_t *inverted)
910 AdgGtkAreaPrivate *data;
911 AdgEntity *entity;
913 data = ((AdgGtkArea *) widget)->data;
914 entity = (AdgEntity *) data->canvas;
915 if (entity == NULL)
916 return FALSE;
918 if (local_space) {
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);
924 } else {
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;
932 static void
933 _adg_set_map(GtkWidget *widget,
934 gboolean local_space,
935 const cairo_matrix_t *map)
937 AdgGtkAreaPrivate *data;
938 AdgEntity *entity;
940 data = ((AdgGtkArea *) widget)->data;
941 entity = (AdgEntity *) data->canvas;
943 if (entity == NULL)
944 return;
946 if (local_space) {
947 /* TODO: this forcibly overwrites any local transformation */
948 adg_entity_set_local_map(entity, map);
949 } else {
950 adg_matrix_transform(&data->render_map, map, ADG_TRANSFORM_BEFORE);
953 /* This will emit the extents-changed signal when applicable */
954 _adg_get_extents((AdgGtkArea *) widget);
957 static void
958 _adg_canvas_changed(AdgGtkArea *area, AdgCanvas *old_canvas)
960 AdgGtkAreaPrivate *data = area->data;
961 data->initialized = FALSE;
964 static const CpmlExtents *
965 _adg_get_extents(AdgGtkArea *area)
967 AdgGtkAreaPrivate *data;
968 AdgCanvas *canvas;
969 CpmlExtents old_extents;
971 data = area->data;
972 old_extents = data->extents;
973 data->extents.is_defined = FALSE;
974 canvas = data->canvas;
976 if (ADG_IS_CANVAS(canvas)) {
977 const CpmlExtents *extents;
978 AdgEntity *entity;
980 entity = (AdgEntity *) canvas;
982 adg_entity_arrange(entity);
983 extents = adg_entity_get_extents(entity);
985 if (extents != NULL) {
986 data->extents = *extents;
987 adg_canvas_apply_margins(canvas, &data->extents);
988 cpml_extents_transform(&data->extents, &data->render_map);
992 if (!cpml_extents_equal(&data->extents, &old_extents))
993 g_signal_emit(area, _adg_signals[EXTENTS_CHANGED], 0, &old_extents);
995 return &data->extents;