adg: provided cairo-gobject fallbacks
[adg.git] / src / adg / adg-gtk-area.c
blob6921dc4026acb83a84bd5feea9a07a42ad45d0cf
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 **/
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"
87 #include "adg-cairo-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)
98 enum {
99 PROP_0,
100 PROP_CANVAS,
101 PROP_FACTOR,
102 PROP_AUTOZOOM,
103 PROP_RENDER_MAP
106 enum {
107 CANVAS_CHANGED,
108 EXTENTS_CHANGED,
109 LAST_SIGNAL
113 static void _adg_dispose (GObject *object);
114 static void _adg_get_property (GObject *object,
115 guint prop_id,
116 GValue *value,
117 GParamSpec *pspec);
118 static void _adg_set_property (GObject *object,
119 guint prop_id,
120 const GValue *value,
121 GParamSpec *pspec);
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
126 (GtkWidget *widget,
127 gint width,
128 gint *minimum_height,
129 gint *natural_height);
130 static void _adg_get_preferred_width (GtkWidget *widget,
131 gint *minimum_width,
132 gint *natural_width);
133 static void _adg_get_preferred_width_for_height
134 (GtkWidget *widget,
135 gint height,
136 gint *minimum_width,
137 gint *natural_width);
138 static void _adg_size_allocate (GtkWidget *widget,
139 GtkAllocation *allocation);
140 static gboolean _adg_draw (GtkWidget *widget,
141 cairo_t *cr);
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,
150 cairo_matrix_t *map,
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 };
163 static void
164 adg_gtk_area_class_init(AdgGtkAreaClass *klass)
166 GObjectClass *gobject_class;
167 GtkWidgetClass *widget_class;
168 GParamSpec *param;
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",
192 P_("Canvas"),
193 P_("The canvas to be shown"),
194 ADG_TYPE_CANVAS,
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",
199 P_("Factor"),
200 P_("The factor to use while zooming in and out (usually with the mouse wheel)"),
201 1., G_MAXDOUBLE, 1.05,
202 G_PARAM_READWRITE);
203 g_object_class_install_property(gobject_class, PROP_FACTOR, param);
205 param = g_param_spec_boolean("autozoom",
206 P_("Autozoom"),
207 P_("When enabled, automatically adjust the zoom in global space at every size allocation"),
208 FALSE,
209 G_PARAM_READWRITE);
210 g_object_class_install_property(gobject_class, PROP_AUTOZOOM, param);
212 param = g_param_spec_boxed("render-map",
213 P_("Render Map"),
214 P_("The transformation to be applied on the canvas before rendering it"),
215 CAIRO_GOBJECT_TYPE_MATRIX,
216 G_PARAM_READWRITE);
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.
227 * Since: 1.0
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),
233 NULL, NULL,
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.
251 * Since: 1.0
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),
257 NULL, NULL,
258 adg_marshal_VOID__POINTER,
259 G_TYPE_NONE, 1, G_TYPE_POINTER);
262 static void
263 adg_gtk_area_init(AdgGtkArea *area)
265 AdgGtkAreaPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(area,
266 ADG_GTK_TYPE_AREA,
267 AdgGtkAreaPrivate);
269 data->canvas = NULL;
270 data->factor = 1.05;
271 data->autozoom = FALSE;
272 cairo_matrix_init_identity(&data->render_map);
274 data->initialized = FALSE;
275 data->x_event = 0;
276 data->y_event = 0;
278 area->data = data;
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 |
284 GDK_SCROLL_MASK);
287 static void
288 _adg_dispose(GObject *object)
290 AdgGtkAreaPrivate *data = ((AdgGtkArea *) object)->data;
292 if (data->canvas) {
293 g_object_unref(data->canvas);
294 data->canvas = NULL;
297 if (_ADG_OLD_OBJECT_CLASS->dispose)
298 _ADG_OLD_OBJECT_CLASS->dispose(object);
301 static void
302 _adg_get_property(GObject *object, guint prop_id,
303 GValue *value, GParamSpec *pspec)
305 AdgGtkAreaPrivate *data = ((AdgGtkArea *) object)->data;
307 switch (prop_id) {
308 case PROP_CANVAS:
309 g_value_set_object(value, data->canvas);
310 break;
311 case PROP_FACTOR:
312 g_value_set_double(value, data->factor);
313 break;
314 case PROP_AUTOZOOM:
315 g_value_set_boolean(value, data->autozoom);
316 break;
317 case PROP_RENDER_MAP:
318 g_value_set_boxed(value, &data->render_map);
319 break;
320 default:
321 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
322 break;
326 static void
327 _adg_set_property(GObject *object, guint prop_id,
328 const GValue *value, GParamSpec *pspec)
330 AdgGtkArea *area;
331 AdgGtkAreaPrivate *data;
332 AdgCanvas *new_canvas, *old_canvas;
334 area = (AdgGtkArea *) object;
335 data = area->data;
337 switch (prop_id) {
338 case PROP_CANVAS:
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);
349 break;
350 case PROP_FACTOR:
351 data->factor = g_value_get_double(value);
352 break;
353 case PROP_AUTOZOOM:
354 data->autozoom = g_value_get_boolean(value);
355 break;
356 case PROP_RENDER_MAP:
357 adg_matrix_copy(&data->render_map, g_value_get_boxed(value));
358 break;
359 default:
360 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
361 break;
367 * adg_gtk_area_new:
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
375 * Since: 1.0
377 GtkWidget *
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
388 * to @canvas.
390 * Returns: the newly created widget
392 * Since: 1.0
394 GtkWidget *
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
408 * unreferenced.
410 * Since: 1.0
412 void
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
427 * %NULL on errors
429 * Since: 1.0
431 AdgCanvas *
432 adg_gtk_area_get_canvas(AdgGtkArea *area)
434 AdgGtkAreaPrivate *data;
436 g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL);
438 data = area->data;
440 return data->canvas;
444 * adg_gtk_area_set_render_map:
445 * @area: an #AdgGtkArea object
446 * @map: the new map
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.
452 * <note><para>
453 * The render map is an implementation detail and this function
454 * is expected to be used only by #AdgGtkArea derived objects.
455 * </para></note>
457 * Since: 1.0
459 void
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:
476 * |[
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);
481 * ]|
483 * <note><para>
484 * The render map is an implementation detail and this function
485 * is expected to be used only by #AdgGtkArea derived objects.
486 * </para></note>
488 * Since: 1.0
490 void
491 adg_gtk_area_transform_render_map(AdgGtkArea *area,
492 const cairo_matrix_t *transformation,
493 AdgTransformMode mode)
495 AdgGtkAreaPrivate *data;
496 cairo_matrix_t map;
498 g_return_if_fail(ADG_GTK_IS_AREA(area));
499 g_return_if_fail(transformation != NULL);
501 data = area->data;
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
517 * Since: 1.0
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);
526 data = area->data;
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
552 * Since: 1.0
554 const CpmlExtents *
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
572 * Since: 1.0
574 gdouble
575 adg_gtk_area_get_zoom(AdgGtkArea *area)
577 AdgGtkAreaPrivate *data;
579 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
581 data = area->data;
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.
593 * Since: 1.0
595 void
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
613 * Since: 1.0
615 gdouble
616 adg_gtk_area_get_factor(AdgGtkArea *area)
618 AdgGtkAreaPrivate *data;
620 g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.);
622 data = area->data;
623 return data->factor;
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.
637 * Since: 1.0
639 void
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
651 * the @area object.
653 * Returns: the current autozoom state
655 * Since: 1.0
657 gboolean
658 adg_gtk_area_has_autozoom(AdgGtkArea *area)
660 AdgGtkAreaPrivate *data;
662 g_return_val_if_fail(ADG_GTK_IS_AREA(area), FALSE);
664 data = area->data;
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.
676 void
677 adg_gtk_area_reset(AdgGtkArea *area)
679 AdgGtkAreaPrivate *data;
680 GtkWidget *parent;
681 const CpmlExtents *sheet;
682 CpmlPair size;
683 gdouble zoom;
685 g_return_if_fail(ADG_GTK_IS_AREA(area));
687 data = area->data;
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)
692 return;
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.
715 * Since: 1.0
717 void
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.
732 * Since: 1.0
734 void
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);
743 static void
744 _adg_get_preferred_height(GtkWidget *widget,
745 gint *minimum_height, gint *natural_height)
747 AdgGtkArea *area;
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;
759 static void
760 _adg_get_preferred_height_for_width(GtkWidget *widget, gint width,
761 gint *minimum_height, gint *natural_height)
763 AdgGtkArea *area;
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;
775 static void
776 _adg_get_preferred_width(GtkWidget *widget,
777 gint *minimum_width, gint *natural_width)
779 AdgGtkArea *area;
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;
791 static void
792 _adg_get_preferred_width_for_height(GtkWidget *widget, gint height,
793 gint *minimum_width, gint *natural_width)
795 AdgGtkArea *area;
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
819 * those situations.
821 * Since: 1.0
823 static void
824 _adg_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
826 AdgGtkArea *area;
827 AdgGtkAreaPrivate *data;
828 const CpmlExtents *sheet;
829 CpmlVector size;
830 gdouble factor;
832 if (_ADG_OLD_WIDGET_CLASS->size_allocate)
833 _ADG_OLD_WIDGET_CLASS->size_allocate(widget, allocation);
835 area = (AdgGtkArea *) widget;
836 data = area->data;
838 sheet = _adg_get_extents(area);
839 if (!sheet->is_defined || sheet->size.x <= 0 || sheet->size.y <= 0)
840 return;
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 */
850 factor = 1;
851 } else {
852 /* Not the first allocation and autozoom off: keep the old map */
853 return;
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;
871 static gboolean
872 _adg_draw(GtkWidget *widget, cairo_t *cr)
874 AdgGtkAreaPrivate *data;
875 AdgCanvas *canvas;
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);
885 return FALSE;
888 static gboolean
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;
894 double factor, x, y;
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;
905 x = event->x;
906 y = event->y;
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 */
918 return TRUE;
921 if (_ADG_OLD_WIDGET_CLASS->scroll_event == NULL)
922 return FALSE;
924 return _ADG_OLD_WIDGET_CLASS->scroll_event(widget, event);
927 static gboolean
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)
939 return FALSE;
941 return _ADG_OLD_WIDGET_CLASS->button_press_event(widget, event);
944 static gboolean
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;
950 double x, y;
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 */
973 return TRUE;
976 if (_ADG_OLD_WIDGET_CLASS->motion_notify_event == NULL)
977 return FALSE;
979 return _ADG_OLD_WIDGET_CLASS->motion_notify_event(widget, event);
982 static gboolean
983 _adg_get_map(GtkWidget *widget, gboolean local_space,
984 cairo_matrix_t *map, cairo_matrix_t *inverted)
986 AdgGtkAreaPrivate *data;
987 AdgEntity *entity;
989 data = ((AdgGtkArea *) widget)->data;
990 entity = (AdgEntity *) data->canvas;
991 if (entity == NULL)
992 return FALSE;
994 if (local_space) {
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);
1000 } else {
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;
1008 static void
1009 _adg_set_map(GtkWidget *widget,
1010 gboolean local_space,
1011 const cairo_matrix_t *map)
1013 AdgGtkAreaPrivate *data;
1014 AdgEntity *entity;
1016 data = ((AdgGtkArea *) widget)->data;
1017 entity = (AdgEntity *) data->canvas;
1019 if (entity == NULL)
1020 return;
1022 if (local_space) {
1023 /* TODO: this forcibly overwrites any local transformation */
1024 adg_entity_set_local_map(entity, map);
1025 } else {
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);
1033 static void
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;
1044 AdgCanvas *canvas;
1045 CpmlExtents old_extents;
1047 data = area->data;
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;
1054 AdgEntity *entity;
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;