Moved AdgPair to CpmlPair
[adg.git] / src / adg / adg-gtk-area.c
blobb60c3659e68422daea2d4f2ecebde52851997dc8
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009,2010,2011,2012 Nicola Fontana <ntd at entidi.it>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
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 **/
62 /**
63 * AdgGtkAreaClass:
64 * @canvas_changed: signals that a new #AdgCanvas is bound to this widget.
65 * @extents_changed: signals that the extents on the underling #AdgCanvas
66 * has been changed.
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.
75 * Since: 1.0
76 **/
79 #include "adg-internal.h"
80 #include <gtk/gtk.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"
88 #include "adg-gtk-area.h"
89 #include "adg-gtk-area-private.h"
91 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_gtk_area_parent_class)
92 #define _ADG_OLD_WIDGET_CLASS ((GtkWidgetClass *) adg_gtk_area_parent_class)
95 G_DEFINE_TYPE(AdgGtkArea, adg_gtk_area, GTK_TYPE_DRAWING_AREA)
97 enum {
98 PROP_0,
99 PROP_CANVAS,
100 PROP_FACTOR,
101 PROP_AUTOZOOM,
102 PROP_RENDER_MAP
105 enum {
106 CANVAS_CHANGED,
107 EXTENTS_CHANGED,
108 LAST_SIGNAL
112 static void _adg_dispose (GObject *object);
113 static void _adg_get_property (GObject *object,
114 guint prop_id,
115 GValue *value,
116 GParamSpec *pspec);
117 static void _adg_set_property (GObject *object,
118 guint prop_id,
119 const GValue *value,
120 GParamSpec *pspec);
121 static void _adg_get_preferred_height (GtkWidget *widget,
122 gint *minimum_height,
123 gint *natural_height);
124 static void _adg_get_preferred_height_for_width
125 (GtkWidget *widget,
126 gint width,
127 gint *minimum_height,
128 gint *natural_height);
129 static void _adg_get_preferred_width (GtkWidget *widget,
130 gint *minimum_width,
131 gint *natural_width);
132 static void _adg_get_preferred_width_for_height
133 (GtkWidget *widget,
134 gint height,
135 gint *minimum_width,
136 gint *natural_width);
137 static void _adg_size_allocate (GtkWidget *widget,
138 GtkAllocation *allocation);
139 static gboolean _adg_draw (GtkWidget *widget,
140 cairo_t *cr);
141 static gboolean _adg_scroll_event (GtkWidget *widget,
142 GdkEventScroll *event);
143 static gboolean _adg_button_press_event (GtkWidget *widget,
144 GdkEventButton *event);
145 static gboolean _adg_motion_notify_event (GtkWidget *widget,
146 GdkEventMotion *event);
147 static gboolean _adg_get_map (GtkWidget *widget,
148 gboolean local_space,
149 AdgMatrix *map,
150 AdgMatrix *inverted);
151 static void _adg_set_map (GtkWidget *widget,
152 gboolean local_space,
153 const AdgMatrix *map);
154 static void _adg_canvas_changed (AdgGtkArea *area,
155 AdgCanvas *old_canvas);
156 static const CpmlExtents *
157 _adg_get_extents (AdgGtkArea *area);
159 static guint _adg_signals[LAST_SIGNAL] = { 0 };
162 static void
163 adg_gtk_area_class_init(AdgGtkAreaClass *klass)
165 GObjectClass *gobject_class;
166 GtkWidgetClass *widget_class;
167 GParamSpec *param;
169 gobject_class = (GObjectClass *) klass;
170 widget_class = (GtkWidgetClass *) klass;
172 g_type_class_add_private(klass, sizeof(AdgGtkAreaPrivate));
174 gobject_class->dispose = _adg_dispose;
175 gobject_class->get_property = _adg_get_property;
176 gobject_class->set_property = _adg_set_property;
178 widget_class->get_preferred_height = _adg_get_preferred_height;
179 widget_class->get_preferred_height_for_width = _adg_get_preferred_height_for_width;
180 widget_class->get_preferred_width = _adg_get_preferred_width;
181 widget_class->get_preferred_width_for_height = _adg_get_preferred_width_for_height;
182 widget_class->size_allocate = _adg_size_allocate;
183 widget_class->draw = _adg_draw;
184 widget_class->scroll_event = _adg_scroll_event;
185 widget_class->button_press_event = _adg_button_press_event;
186 widget_class->motion_notify_event = _adg_motion_notify_event;
188 klass->canvas_changed = _adg_canvas_changed;
190 param = g_param_spec_object("canvas",
191 P_("Canvas"),
192 P_("The canvas to be shown"),
193 ADG_TYPE_CANVAS,
194 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
195 g_object_class_install_property(gobject_class, PROP_CANVAS, param);
197 param = g_param_spec_double("factor",
198 P_("Factor"),
199 P_("The factor to use while zooming in and out (usually with the mouse wheel)"),
200 1., G_MAXDOUBLE, 1.05,
201 G_PARAM_READWRITE);
202 g_object_class_install_property(gobject_class, PROP_FACTOR, param);
204 param = g_param_spec_boolean("autozoom",
205 P_("Autozoom"),
206 P_("When enabled, automatically adjust the zoom in global space at every size allocation"),
207 FALSE,
208 G_PARAM_READWRITE);
209 g_object_class_install_property(gobject_class, PROP_AUTOZOOM, param);
211 param = g_param_spec_boxed("render-map",
212 P_("Render Map"),
213 P_("The transformation to be applied on the canvas before rendering it"),
214 ADG_TYPE_MATRIX,
215 G_PARAM_READWRITE);
216 g_object_class_install_property(gobject_class, PROP_RENDER_MAP, param);
219 * AdgGtkArea::canvas-changed:
220 * @area: an #AdgGtkArea
221 * @old_canvas: the old #AdgCanvas object
223 * Emitted when the #AdgGtkArea has a new canvas. If the new canvas
224 * is the same as the old one, the signal is not emitted.
226 * Since: 1.0
228 _adg_signals[CANVAS_CHANGED] =
229 g_signal_new("canvas-changed", ADG_GTK_TYPE_AREA,
230 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
231 G_STRUCT_OFFSET(AdgGtkAreaClass, canvas_changed),
232 NULL, NULL,
233 adg_marshal_VOID__OBJECT,
234 G_TYPE_NONE, 1, ADG_TYPE_CANVAS);
237 * AdgGtkArea::extents-changed:
238 * @area: an #AdgGtkArea
239 * @old_extents: the old #CpmlExtents struct
241 * Emitted when the extents of @area have been changed.
242 * The old extents are always compared to the new ones,
243 * so when the extents are recalculated but the result
244 * is the same the signal is not emitted.
246 * The extents of #AdgGtkArea are subject to the render
247 * map, so changing the #AdgGtkArea:render-map property
248 * will emit this signal too.
250 * Since: 1.0
252 _adg_signals[EXTENTS_CHANGED] =
253 g_signal_new("extents-changed", ADG_GTK_TYPE_AREA,
254 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
255 G_STRUCT_OFFSET(AdgGtkAreaClass, extents_changed),
256 NULL, NULL,
257 adg_marshal_VOID__POINTER,
258 G_TYPE_NONE, 1, G_TYPE_POINTER);
261 static void
262 adg_gtk_area_init(AdgGtkArea *area)
264 AdgGtkAreaPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(area,
265 ADG_GTK_TYPE_AREA,
266 AdgGtkAreaPrivate);
268 data->canvas = NULL;
269 data->factor = 1.05;
270 data->autozoom = FALSE;
271 cairo_matrix_init_identity(&data->render_map);
273 data->initialized = FALSE;
274 data->x_event = 0;
275 data->y_event = 0;
277 area->data = data;
279 /* Enable GDK events to catch wheel rotation and drag */
280 gtk_widget_add_events((GtkWidget *) area,
281 GDK_BUTTON_PRESS_MASK |
282 GDK_BUTTON2_MOTION_MASK |
283 GDK_SCROLL_MASK);
286 static void
287 _adg_dispose(GObject *object)
289 AdgGtkAreaPrivate *data = ((AdgGtkArea *) object)->data;
291 if (data->canvas) {
292 g_object_unref(data->canvas);
293 data->canvas = NULL;
296 if (_ADG_OLD_OBJECT_CLASS->dispose)
297 _ADG_OLD_OBJECT_CLASS->dispose(object);
300 static void
301 _adg_get_property(GObject *object, guint prop_id,
302 GValue *value, GParamSpec *pspec)
304 AdgGtkAreaPrivate *data = ((AdgGtkArea *) object)->data;
306 switch (prop_id) {
307 case PROP_CANVAS:
308 g_value_set_object(value, data->canvas);
309 break;
310 case PROP_FACTOR:
311 g_value_set_double(value, data->factor);
312 break;
313 case PROP_AUTOZOOM:
314 g_value_set_boolean(value, data->autozoom);
315 break;
316 case PROP_RENDER_MAP:
317 g_value_set_boxed(value, &data->render_map);
318 break;
319 default:
320 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
321 break;
325 static void
326 _adg_set_property(GObject *object, guint prop_id,
327 const GValue *value, GParamSpec *pspec)
329 AdgGtkArea *area;
330 AdgGtkAreaPrivate *data;
331 AdgCanvas *new_canvas, *old_canvas;
333 area = (AdgGtkArea *) object;
334 data = area->data;
336 switch (prop_id) {
337 case PROP_CANVAS:
338 new_canvas = g_value_get_object(value);
339 old_canvas = data->canvas;
340 if (new_canvas != old_canvas) {
341 if (new_canvas != NULL)
342 g_object_ref(new_canvas);
343 if (old_canvas != NULL)
344 g_object_unref(old_canvas);
345 data->canvas = new_canvas;
346 g_signal_emit(area, _adg_signals[CANVAS_CHANGED], 0, old_canvas);
348 break;
349 case PROP_FACTOR:
350 data->factor = g_value_get_double(value);
351 break;
352 case PROP_AUTOZOOM:
353 data->autozoom = g_value_get_boolean(value);
354 break;
355 case PROP_RENDER_MAP:
356 adg_matrix_copy(&data->render_map, g_value_get_boxed(value));
357 break;
358 default:
359 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
360 break;
366 * adg_gtk_area_new:
368 * Creates a new empty #AdgGtkArea. The widget is useful only after
369 * an #AdgCanvas has been added either using the #AdgGtkArea:canvas
370 * property or with adg_gtk_area_set_canvas().
372 * Returns: the newly created widget
374 * Since: 1.0
376 GtkWidget *
377 adg_gtk_area_new(void)
379 return g_object_new(ADG_GTK_TYPE_AREA, NULL);
383 * adg_gtk_area_new_with_canvas:
384 * @canvas: the #AdgCanvas shown by this widget
386 * Creates a new #AdgGtkArea and sets the #AdgGtkArea:canvas property
387 * to @canvas.
389 * Returns: the newly created widget
391 * Since: 1.0
393 GtkWidget *
394 adg_gtk_area_new_with_canvas(AdgCanvas *canvas)
396 g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL);
398 return g_object_new(ADG_GTK_TYPE_AREA, "canvas", canvas, NULL);
402 * adg_gtk_area_set_canvas:
403 * @area: an #AdgGtkArea
404 * @canvas: the new #AdgCanvas
406 * Sets a new canvas on @area. The old canvas, if presents, is
407 * unreferenced.
409 * Since: 1.0
411 void
412 adg_gtk_area_set_canvas(AdgGtkArea *area, AdgCanvas *canvas)
414 g_return_if_fail(ADG_GTK_IS_AREA(area));
415 g_object_set(area, "canvas", canvas, NULL);
419 * adg_gtk_area_get_canvas:
420 * @area: an #AdgGtkArea
422 * Gets the canvas associated to @area. The returned canvas
423 * is owned by @area and should not be modified or freed.
425 * Returns: (transfer none): the requested #AdgCanvas object or
426 * %NULL on errors
428 * Since: 1.0
430 AdgCanvas *
431 adg_gtk_area_get_canvas(AdgGtkArea *area)
433 AdgGtkAreaPrivate *data;
435 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
437 data = area->data;
439 return data->canvas;
443 * adg_gtk_area_set_render_map:
444 * @area: an #AdgGtkArea object
445 * @map: the new map
447 * Sets the new render transformation of @area to @map:
448 * the old map is discarded. If @map is %NULL, the render
449 * map is left unchanged.
451 * <note><para>
452 * The render map is an implementation detail and this function
453 * is expected to be used only by #AdgGtkArea derived objects.
454 * </para></note>
456 * Since: 1.0
458 void
459 adg_gtk_area_set_render_map(AdgGtkArea *area, const AdgMatrix *map)
461 g_return_if_fail(ADG_GTK_IS_AREA(area));
462 g_object_set(area, "render-map", map, NULL);
466 * adg_gtk_area_transform_render_map:
467 * @area: an #AdgGtkArea object
468 * @transformation: the transformation to apply
469 * @mode: how @transformation should be applied
471 * Convenient function to change the render map of @area by
472 * applying @tranformation using the @mode operator. This is
473 * logically equivalent to the following:
475 * |[
476 * AdgMatrix map;
477 * adg_matrix_copy(&map, adg_gtk_area_get_render_map(area));
478 * adg_matrix_transform(&map, transformation, mode);
479 * adg_gtk_area_set_render_map(area, &map);
480 * ]|
482 * <note><para>
483 * The render map is an implementation detail and this function
484 * is expected to be used only by #AdgGtkArea derived objects.
485 * </para></note>
487 * Since: 1.0
489 void
490 adg_gtk_area_transform_render_map(AdgGtkArea *area,
491 const AdgMatrix *transformation,
492 AdgTransformMode mode)
494 AdgGtkAreaPrivate *data;
495 AdgMatrix map;
497 g_return_if_fail(ADG_GTK_IS_AREA(area));
498 g_return_if_fail(transformation != NULL);
500 data = area->data;
502 adg_matrix_copy(&map, &data->render_map);
503 adg_matrix_transform(&map, transformation, mode);
505 g_object_set(area, "render-map", &map, NULL);
509 * adg_gtk_area_get_render_map:
510 * @area: an #AdgGtkArea object
512 * Gets the render map.
514 * Returns: the requested map or %NULL on errors
516 * Since: 1.0
518 const AdgMatrix *
519 adg_gtk_area_get_render_map(AdgGtkArea *area)
521 AdgGtkAreaPrivate *data;
523 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
525 data = area->data;
527 return &data->render_map;
531 * adg_gtk_area_get_extents:
532 * @area: an #AdgGtkArea
534 * Gets the extents of the canvas bound to @area. The returned
535 * struct is owned by @area and should not modified or freed.
537 * The extents of an #AdgGtkArea instance are the extents of
538 * its canvas (as returned by adg_entity_get_extents()) with
539 * the margins added to it and the #AdgGtkArea:render-map
540 * transformation applied.
542 * If @area does not have any canvas associated to it or the
543 * canvas is invalid or empty, an undefined #CpmlExtents
544 * struct will be returned.
546 * The canvas will be updated, meaning adg_entity_arrange()
547 * is called before the extents computation.
549 * Returns: the extents of the @area canvas or %NULL on errors
551 * Since: 1.0
553 const CpmlExtents *
554 adg_gtk_area_get_extents(AdgGtkArea *area)
556 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
558 return _adg_get_extents(area);
562 * adg_gtk_area_get_zoom:
563 * @area: an #AdgGtkArea
565 * Gets the last zoom coefficient applied on the canvas of @area.
566 * If the #AdgGtkArea:autozoom property is %FALSE, the value
567 * returned should be always %1.
569 * Returns: the current zoom coefficient
571 * Since: 1.0
573 gdouble
574 adg_gtk_area_get_zoom(AdgGtkArea *area)
576 AdgGtkAreaPrivate *data;
578 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
580 data = area->data;
581 return data->render_map.xx;
585 * adg_gtk_area_set_factor:
586 * @area: an #AdgGtkArea
587 * @factor: the new zoom factor
589 * Sets a new zoom factor to @area. If the factor is less than
590 * 1, it will be clamped to 1.
592 * Since: 1.0
594 void
595 adg_gtk_area_set_factor(AdgGtkArea *area, gdouble factor)
597 g_return_if_fail(ADG_GTK_IS_AREA(area));
598 g_object_set(area, "factor", factor, NULL);
602 * adg_gtk_area_get_factor:
603 * @area: an #AdgGtkArea
605 * Gets the zoom factor associated to @area. The zoom factor is
606 * directly used to zoom in (that is, the default zoom factor of
607 * 1.05 will zoom of 5% every iteration) and it is reversed while
608 * zooming out (that is, the default factor will be 1/1.05).
610 * Returns: the requested zoom factor or 0 on error
612 * Since: 1.0
614 gdouble
615 adg_gtk_area_get_factor(AdgGtkArea *area)
617 AdgGtkAreaPrivate *data;
619 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
621 data = area->data;
622 return data->factor;
626 * adg_gtk_area_switch_autozoom:
627 * @area: an #AdgGtkArea
628 * @state: the new autozoom state
630 * Sets the #AdgGtkArea:autozoom property of @area to @state. When the
631 * autozoom feature is enabled, @area reacts to any size allocation
632 * by adjusting its zoom coefficient in global space. This means the
633 * drawing will fill the available space (keeping its aspect ratio)
634 * when resizing the window.
636 * Since: 1.0
638 void
639 adg_gtk_area_switch_autozoom(AdgGtkArea *area, gboolean state)
641 g_return_if_fail(ADG_GTK_IS_AREA(area));
642 g_object_set(area, "autozoom", state, NULL);
646 * adg_gtk_area_has_autozoom:
647 * @area: an #AdgGtkArea
649 * Gets the current state of the #AdgGtkArea:autozoom property on
650 * the @area object.
652 * Returns: the current autozoom state
654 * Since: 1.0
656 gboolean
657 adg_gtk_area_has_autozoom(AdgGtkArea *area)
659 AdgGtkAreaPrivate *data;
661 g_return_val_if_fail(ADG_GTK_IS_AREA(area), FALSE);
663 data = area->data;
664 return data->autozoom;
668 * adg_gtk_area_reset:
669 * @area: an #AdgGtkArea
671 * Forcibly resets the zoom ratio and position of the canvas bound
672 * to @area. This means the canvas will be scaled and centered on
673 * the current available space.
675 void
676 adg_gtk_area_reset(AdgGtkArea *area)
678 AdgGtkAreaPrivate *data;
679 GtkWidget *parent;
680 const CpmlExtents *sheet;
681 CpmlPair size;
682 gdouble zoom;
684 g_return_if_fail(ADG_GTK_IS_AREA(area));
686 data = area->data;
687 cairo_matrix_init_identity(&data->render_map);
689 sheet = _adg_get_extents(area);
690 if (!sheet->is_defined || sheet->size.x <= 0 || sheet->size.y <= 0)
691 return;
693 parent = gtk_widget_get_parent((GtkWidget *) area);
694 size.x = gtk_widget_get_allocated_width(parent);
695 size.y = gtk_widget_get_allocated_height(parent);
696 zoom = MIN(size.x / sheet->size.x, size.y / sheet->size.y);
698 cairo_matrix_scale(&data->render_map, zoom, zoom);
699 cairo_matrix_translate(&data->render_map,
700 (size.x / zoom - sheet->size.x) / 2 - sheet->org.x,
701 (size.y / zoom - sheet->size.y) / 2 - sheet->org.y);
703 /* Trigger a resize trying to hide the scrollbars on the parent */
704 gtk_widget_queue_resize(parent);
708 * adg_gtk_area_canvas_changed:
709 * @area: an #AdgGtkArea
710 * @old_canvas: the old canvas bound to @area
712 * Emits the #AdgGtkArea::canvas-changed signal on @area.
714 * Since: 1.0
716 void
717 adg_gtk_area_canvas_changed(AdgGtkArea *area, AdgCanvas *old_canvas)
719 g_return_if_fail(ADG_GTK_IS_AREA(area));
721 g_signal_emit(area, _adg_signals[CANVAS_CHANGED], 0, old_canvas);
725 * adg_gtk_area_extents_changed:
726 * @area: an #AdgGtkArea
727 * @old_extents: the old extents of @area
729 * Emits the #AdgGtkArea::extents-changed signal on @area.
731 * Since: 1.0
733 void
734 adg_gtk_area_extents_changed(AdgGtkArea *area, const CpmlExtents *old_extents)
736 g_return_if_fail(ADG_GTK_IS_AREA(area));
738 g_signal_emit(area, _adg_signals[EXTENTS_CHANGED], 0, old_extents);
742 static void
743 _adg_get_preferred_height(GtkWidget *widget,
744 gint *minimum_height, gint *natural_height)
746 AdgGtkArea *area;
747 const CpmlExtents *extents;
749 area = (AdgGtkArea *) widget;
750 extents = _adg_get_extents(area);
752 if (extents->is_defined) {
753 *minimum_height = extents->size.y;
754 *natural_height = *minimum_height;
758 static void
759 _adg_get_preferred_height_for_width(GtkWidget *widget, gint width,
760 gint *minimum_height, gint *natural_height)
762 AdgGtkArea *area;
763 const CpmlExtents *extents;
765 area = (AdgGtkArea *) widget;
766 extents = _adg_get_extents(area);
768 if (extents->is_defined && extents->size.x > 0) {
769 *minimum_height = extents->size.y;
770 *natural_height = *minimum_height * width / extents->size.x;
774 static void
775 _adg_get_preferred_width(GtkWidget *widget,
776 gint *minimum_width, gint *natural_width)
778 AdgGtkArea *area;
779 const CpmlExtents *extents;
781 area = (AdgGtkArea *) widget;
782 extents = _adg_get_extents(area);
784 if (extents->is_defined) {
785 *minimum_width = extents->size.x;
786 *natural_width = *minimum_width;
790 static void
791 _adg_get_preferred_width_for_height(GtkWidget *widget, gint height,
792 gint *minimum_width, gint *natural_width)
794 AdgGtkArea *area;
795 const CpmlExtents *extents;
797 area = (AdgGtkArea *) widget;
798 extents = _adg_get_extents(area);
800 if (extents->is_defined && extents->size.y > 0) {
801 *minimum_width = extents->size.x;
802 *natural_width = *minimum_width * height / extents->size.y;
807 * _adg_size_allocate:
808 * @widget: an #AdgGtkArea widget
809 * @allocation: the new allocation struct
811 * Scales the drawing according to the new allocation if
812 * #AdgGtkArea:autozoom is %TRUE.
814 * TODO: the current implementation initially centers the canvas
815 * on the allocation space. Further allocations (due to a
816 * window resizing, for example) use the top/left corner of the
817 * canvas as reference point. Plan different policies for either
818 * those situations.
820 * Since: 1.0
822 static void
823 _adg_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
825 AdgGtkArea *area;
826 AdgGtkAreaPrivate *data;
827 const CpmlExtents *sheet;
828 CpmlVector size;
829 gdouble factor;
831 if (_ADG_OLD_WIDGET_CLASS->size_allocate)
832 _ADG_OLD_WIDGET_CLASS->size_allocate(widget, allocation);
834 area = (AdgGtkArea *) widget;
835 data = area->data;
837 sheet = _adg_get_extents(area);
838 if (!sheet->is_defined || sheet->size.x <= 0 || sheet->size.y <= 0)
839 return;
841 size.x = allocation->width;
842 size.y = allocation->height;
844 if (data->autozoom) {
845 /* Adjust the zoom according to the allocation and sheet size */
846 factor = MIN(size.x / sheet->size.x, size.y / sheet->size.y);
847 } else if (!data->initialized) {
848 /* First allocation with autozoom off: keep the current zoom */
849 factor = 1;
850 } else {
851 /* Not the first allocation and autozoom off: keep the old map */
852 return;
855 if (!data->initialized) {
856 /* TODO: plan different attachment policies other than centering */
857 cairo_matrix_init_translate(&data->render_map,
858 (size.x - sheet->size.x) / 2 - sheet->org.x,
859 (size.y - sheet->size.y) / 2 - sheet->org.y);
860 data->initialized = TRUE;
863 /* TODO: plan other reference points other than left/top (x0, y0) */
864 data->render_map.x0 *= factor;
865 data->render_map.y0 *= factor;
866 data->render_map.xx *= factor;
867 data->render_map.yy *= factor;
870 static gboolean
871 _adg_draw(GtkWidget *widget, cairo_t *cr)
873 AdgGtkAreaPrivate *data;
874 AdgCanvas *canvas;
876 data = ((AdgGtkArea *) widget)->data;
877 canvas = data->canvas;
879 if (canvas != NULL) {
880 cairo_transform(cr, &data->render_map);
881 adg_entity_render((AdgEntity *) canvas, cr);
884 return FALSE;
887 static gboolean
888 _adg_scroll_event(GtkWidget *widget, GdkEventScroll *event)
890 gboolean zoom_in, zoom_out, local_space, global_space;
891 AdgMatrix map, inverted;
892 AdgGtkAreaPrivate *data;
893 double factor, x, y;
895 zoom_in = event->direction == GDK_SCROLL_UP;
896 zoom_out = event->direction == GDK_SCROLL_DOWN;
897 local_space = (event->state & ADG_GTK_MODIFIERS) == 0;
898 global_space = (event->state & ADG_GTK_MODIFIERS) == GDK_SHIFT_MASK;
900 if ((zoom_in || zoom_out) && (local_space || global_space) &&
901 _adg_get_map(widget, local_space, &map, &inverted)) {
902 data = ((AdgGtkArea *) widget)->data;
903 factor = zoom_in ? data->factor : 1. / data->factor;
904 x = event->x;
905 y = event->y;
907 cairo_matrix_transform_point(&inverted, &x, &y);
908 cairo_matrix_scale(&map, factor, factor);
909 cairo_matrix_translate(&map, x/factor - x, y/factor - y);
911 _adg_set_map(widget, local_space, &map);
913 gtk_widget_queue_draw(widget);
915 /* Avoid to chain up the default handler:
916 * this event has been grabbed by this function */
917 return TRUE;
920 if (_ADG_OLD_WIDGET_CLASS->scroll_event == NULL)
921 return FALSE;
923 return _ADG_OLD_WIDGET_CLASS->scroll_event(widget, event);
926 static gboolean
927 _adg_button_press_event(GtkWidget *widget, GdkEventButton *event)
929 AdgGtkAreaPrivate *data = ((AdgGtkArea *) widget)->data;
931 if (event->type == GDK_BUTTON_PRESS && event->button == 2) {
932 /* Remember the starting coordinates of a (probable) translation */
933 data->x_event = event->x;
934 data->y_event = event->y;
937 if (_ADG_OLD_WIDGET_CLASS->button_press_event == NULL)
938 return FALSE;
940 return _ADG_OLD_WIDGET_CLASS->button_press_event(widget, event);
943 static gboolean
944 _adg_motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
946 gboolean translating, local_space, global_space;
947 AdgMatrix map, inverted;
948 AdgGtkAreaPrivate *data;
949 double x, y;
951 translating = (event->state & GDK_BUTTON2_MASK) == GDK_BUTTON2_MASK;
952 local_space = (event->state & ADG_GTK_MODIFIERS) == 0;
953 global_space = (event->state & ADG_GTK_MODIFIERS) == GDK_SHIFT_MASK;
955 if (translating && (local_space || global_space) &&
956 _adg_get_map(widget, local_space, &map, &inverted)) {
957 data = ((AdgGtkArea *) widget)->data;
958 x = event->x - data->x_event;
959 y = event->y - data->y_event;
961 cairo_matrix_transform_distance(&inverted, &x, &y);
962 cairo_matrix_translate(&map, x, y);
963 data->x_event = event->x;
964 data->y_event = event->y;
966 _adg_set_map(widget, local_space, &map);
968 gtk_widget_queue_draw(widget);
970 /* Avoid to chain up the default handler:
971 * this event has been grabbed by this function */
972 return TRUE;
975 if (_ADG_OLD_WIDGET_CLASS->motion_notify_event == NULL)
976 return FALSE;
978 return _ADG_OLD_WIDGET_CLASS->motion_notify_event(widget, event);
981 static gboolean
982 _adg_get_map(GtkWidget *widget, gboolean local_space,
983 AdgMatrix *map, AdgMatrix *inverted)
985 AdgGtkAreaPrivate *data;
986 AdgEntity *entity;
988 data = ((AdgGtkArea *) widget)->data;
989 entity = (AdgEntity *) data->canvas;
990 if (entity == NULL)
991 return FALSE;
993 if (local_space) {
994 adg_matrix_copy(map, adg_entity_get_local_map(entity));
996 /* The inverted map is subject to the global matrix */
997 adg_matrix_copy(inverted, adg_entity_get_global_matrix(entity));
998 adg_matrix_transform(inverted, map, ADG_TRANSFORM_BEFORE);
999 } else {
1000 adg_matrix_copy(map, adg_entity_get_global_map(entity));
1001 adg_matrix_copy(inverted, map);
1004 return cairo_matrix_invert(inverted) == CAIRO_STATUS_SUCCESS;
1007 static void
1008 _adg_set_map(GtkWidget *widget, gboolean local_space, const AdgMatrix *map)
1010 AdgGtkAreaPrivate *data;
1011 AdgEntity *entity;
1013 data = ((AdgGtkArea *) widget)->data;
1014 entity = (AdgEntity *) data->canvas;
1016 if (entity == NULL)
1017 return;
1019 if (local_space) {
1020 /* TODO: this forcibly overwrites any local transformation */
1021 adg_entity_set_local_map(entity, map);
1022 } else {
1023 adg_matrix_transform(&data->render_map, map, ADG_TRANSFORM_BEFORE);
1026 /* This will emit the extents-changed signal when applicable */
1027 _adg_get_extents((AdgGtkArea *) widget);
1030 static void
1031 _adg_canvas_changed(AdgGtkArea *area, AdgCanvas *old_canvas)
1033 AdgGtkAreaPrivate *data = area->data;
1034 data->initialized = FALSE;
1037 static const CpmlExtents *
1038 _adg_get_extents(AdgGtkArea *area)
1040 AdgGtkAreaPrivate *data;
1041 AdgCanvas *canvas;
1042 CpmlExtents old_extents;
1044 data = area->data;
1045 old_extents = data->extents;
1046 data->extents.is_defined = FALSE;
1047 canvas = data->canvas;
1049 if (ADG_IS_CANVAS(canvas)) {
1050 const CpmlExtents *extents;
1051 AdgEntity *entity;
1053 entity = (AdgEntity *) canvas;
1055 adg_entity_arrange(entity);
1056 extents = adg_entity_get_extents(entity);
1058 if (extents != NULL) {
1059 data->extents = *extents;
1060 adg_canvas_apply_margins(canvas, &data->extents);
1061 cpml_extents_transform(&data->extents, &data->render_map);
1065 if (!cpml_extents_equal(&data->extents, &old_extents))
1066 g_signal_emit(area, _adg_signals[EXTENTS_CHANGED], 0, &old_extents);
1068 return &data->extents;