[build] Ignoring libtool m4 files
[adg.git] / adg / adg-widget.c
blob1b78f6d38e4bdc288a342346f74c12cdf3d46e60
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009 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:widget
23 * @title: AdgWidget
24 * @short_description: A #GtkWidget specifically designed to contain
25 * an #AdgCanvas entity
27 * This is a #GtkDrawingArea derived object that provides an easy way
28 * to show an ADG based canvas. Its default implementation reacts to
29 * some mouse events: if you drag the mouse by keepeng the wheel pressed
30 * the canvas is translated by translating its model transformation;
31 * if the mouse wheel is rotated, the model transformation of the canvas
32 * is scaled up or down (accordling to the wheel direction) by a factor
33 * specified by the #AdgWidget:factor property.
34 **/
36 /**
37 * AdgWidget:
39 * All fields are private and should not be used directly.
40 * Use its public methods instead.
41 **/
44 #include "adg-widget.h"
45 #include "adg-widget-private.h"
46 #include "adg-intl.h"
49 enum {
50 PROP_0,
51 PROP_CANVAS,
52 PROP_FACTOR
55 enum {
56 CANVAS_CHANGED,
57 LAST_SIGNAL
61 static void dispose (GObject *object);
62 static void get_property (GObject *object,
63 guint prop_id,
64 GValue *value,
65 GParamSpec *pspec);
66 static void set_property (GObject *object,
67 guint prop_id,
68 const GValue *value,
69 GParamSpec *pspec);
70 static void set_canvas (AdgWidget *widget,
71 AdgCanvas *canvas);
72 static gboolean expose_event (GtkWidget *widget,
73 GdkEventExpose *event);
74 static gboolean scroll_event (GtkWidget *widget,
75 GdkEventScroll *event);
76 static gboolean button_press_event (GtkWidget *widget,
77 GdkEventButton *event);
78 static gboolean motion_notify_event (GtkWidget *widget,
79 GdkEventMotion *event);
80 static gboolean get_transformation (GtkWidget *widget,
81 AdgMatrix *model,
82 AdgMatrix *inverted);
83 static void set_transformation (GtkWidget *widget,
84 const AdgMatrix *model);
86 static guint signals[LAST_SIGNAL] = { 0 };
89 G_DEFINE_TYPE(AdgWidget, adg_widget, GTK_TYPE_DRAWING_AREA);
92 static void
93 adg_widget_class_init(AdgWidgetClass *klass)
95 GObjectClass *gobject_class;
96 GtkWidgetClass *widget_class;
97 GParamSpec *param;
99 gobject_class = (GObjectClass *) klass;
100 widget_class = (GtkWidgetClass *) klass;
102 g_type_class_add_private(klass, sizeof(AdgWidgetPrivate));
104 gobject_class->dispose = dispose;
105 gobject_class->get_property = get_property;
106 gobject_class->set_property = set_property;
108 widget_class->expose_event = expose_event;
109 widget_class->scroll_event = scroll_event;
110 widget_class->button_press_event = button_press_event;
111 widget_class->motion_notify_event = motion_notify_event;
113 param = g_param_spec_object("canvas",
114 P_("Canvas"),
115 P_("The canvas to be shown by this widget"),
116 ADG_TYPE_CANVAS,
117 G_PARAM_CONSTRUCT|G_PARAM_READWRITE);
118 g_object_class_install_property(gobject_class, PROP_CANVAS, param);
120 param = g_param_spec_double("factor",
121 P_("Factor"),
122 P_("The factor used in zooming in and out"),
123 1., G_MAXDOUBLE, 1.05,
124 G_PARAM_CONSTRUCT|G_PARAM_READWRITE);
125 g_object_class_install_property(gobject_class, PROP_FACTOR, param);
128 * AdgWidget::canvas-changed:
129 * @widget: an #AdgWidget
131 * Emitted when the widget has a new canvas.
133 signals[CANVAS_CHANGED] = g_signal_new("canvas-changed", ADG_TYPE_WIDGET,
134 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
135 G_STRUCT_OFFSET(AdgWidgetClass, canvas_changed),
136 NULL, NULL,
137 g_cclosure_marshal_VOID__VOID,
138 G_TYPE_NONE, 0);
141 static void
142 adg_widget_init(AdgWidget *widget)
144 AdgWidgetPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE(widget,
145 ADG_TYPE_WIDGET,
146 AdgWidgetPrivate);
148 priv->canvas = NULL;
149 priv->factor = 1.05;
150 priv->x_event = 0;
151 priv->y_event = 0;
153 widget->priv = priv;
155 /* Enable GDK events to catch wheel rotation and drag */
156 gtk_widget_add_events((GtkWidget *) widget,
157 GDK_BUTTON2_MOTION_MASK|GDK_SCROLL_MASK);
160 static void
161 dispose(GObject *object)
163 AdgWidgetPrivate *priv = ((AdgWidget *) object)->priv;
165 if (priv->canvas != NULL) {
166 g_object_unref(priv->canvas);
167 priv->canvas = NULL;
170 G_OBJECT_CLASS(adg_widget_parent_class)->dispose(object);
173 static void
174 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
176 AdgWidget *widget = (AdgWidget *) object;
178 switch (prop_id) {
180 case PROP_CANVAS:
181 g_value_set_object(value, widget->priv->canvas);
182 break;
184 case PROP_FACTOR:
185 g_value_set_double(value, widget->priv->factor);
186 break;
188 default:
189 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
190 break;
194 static void
195 set_property(GObject *object,
196 guint prop_id, const GValue *value, GParamSpec *pspec)
198 AdgWidget *widget = (AdgWidget *) object;
200 switch (prop_id) {
202 case PROP_CANVAS:
203 set_canvas(widget, g_value_get_object(value));
204 break;
206 case PROP_FACTOR:
207 widget->priv->factor = g_value_get_double(value);
208 break;
210 default:
211 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
212 break;
218 * adg_widget_new:
219 * @path: the #AdgPath to stroke
221 * Creates a new #AdgWidget.
223 * Return value: the newly created widget
225 GtkWidget *
226 adg_widget_new(AdgCanvas *canvas)
228 g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL);
230 return (GtkWidget *) g_object_new(ADG_TYPE_WIDGET, "canvas", canvas, NULL);
235 * adg_widget_get_canvas:
236 * @widget: an #AdgWidget
238 * Gets the canvas associated to @widget.
240 * Return value: the requested #AdgCanvas object or %NULL on errors
242 AdgCanvas *
243 adg_widget_get_canvas(AdgWidget *widget)
245 g_return_val_if_fail(ADG_IS_WIDGET(widget), NULL);
247 return widget->priv->canvas;
251 * adg_widget_set_canvas:
252 * @widget: an #AdgWidget
253 * @canvas: the new #AdgCanvas
255 * Sets a new canvas on @widget. The old canvas, if presents, is
256 * unreferenced.
258 void
259 adg_widget_set_canvas(AdgWidget *widget, AdgCanvas *canvas)
261 g_return_if_fail(ADG_IS_WIDGET(widget));
263 set_canvas(widget, canvas);
265 g_object_notify((GObject *) widget, "canvas");
269 * adg_widget_get_factor:
270 * @widget: an #AdgWidget
272 * Gets the zoom factor associated to @widget. The zoom factor is
273 * directly used to zoom in (that is, the default zoom factor of
274 * 1.05 will zoom of 5% every iteration) and it is reversed while
275 * zooming out (that is, the default factor will use 1/1.05).
277 * Return value: the requested zoom factor or 0 on error
279 gdouble
280 adg_widget_get_factor(AdgWidget *widget)
282 g_return_val_if_fail(ADG_IS_WIDGET(widget), 0.);
284 return widget->priv->factor;
288 * adg_widget_set_factor:
289 * @widget: an #AdgWidget
290 * @factor: the new zoom factor
292 * Sets a new zoom factor to @widget. If the factor is less than
293 * 1, it will be clamped to 1.
295 void
296 adg_widget_set_factor(AdgWidget *widget, gdouble factor)
298 g_return_if_fail(ADG_IS_WIDGET(widget));
300 widget->priv->factor = CLAMP(factor, 1., G_MAXDOUBLE);
302 g_object_notify((GObject *) widget, "factor");
306 static void
307 set_canvas(AdgWidget *widget, AdgCanvas *canvas)
309 if (widget->priv->canvas != NULL)
310 g_object_unref(widget->priv->canvas);
312 widget->priv->canvas = canvas;
314 if (canvas != NULL)
315 g_object_ref(widget->priv->canvas);
317 g_signal_emit(widget, signals[CANVAS_CHANGED], 0);
320 static gboolean
321 expose_event(GtkWidget *widget, GdkEventExpose *event)
323 AdgCanvas *canvas;
324 GtkWidgetClass *parent_class;
326 canvas = ((AdgWidget *) widget)->priv->canvas;
327 parent_class = (GtkWidgetClass *) adg_widget_parent_class;
329 if (canvas != NULL) {
330 cairo_t *cr = gdk_cairo_create(widget->window);
331 adg_entity_render((AdgEntity *) canvas, cr);
332 cairo_destroy(cr);
335 if (parent_class->expose_event == NULL)
336 return FALSE;
338 return parent_class->expose_event(widget, event);
341 static gboolean
342 scroll_event(GtkWidget *widget, GdkEventScroll *event)
344 GtkWidgetClass *parent_class;
345 AdgWidgetPrivate *priv;
346 AdgMatrix model, inverted;
348 parent_class = (GtkWidgetClass *) adg_widget_parent_class;
349 priv = ((AdgWidget *) widget)->priv;
351 if ((event->direction == GDK_SCROLL_UP ||
352 event->direction == GDK_SCROLL_DOWN) &&
353 get_transformation(widget, &model, &inverted)) {
354 double factor, x, y;
356 if (event->direction == GDK_SCROLL_UP) {
357 factor = priv->factor;
358 } else {
359 factor = 1. / priv->factor;
362 x = event->x;
363 y = event->y;
365 cairo_matrix_transform_point(&inverted, &x, &y);
367 cairo_matrix_scale(&model, factor, factor);
368 cairo_matrix_translate(&model, x/factor - x, y/factor - y);
370 set_transformation(widget, &model);
372 gtk_widget_queue_draw(widget);
375 if (parent_class->scroll_event == NULL)
376 return FALSE;
378 return parent_class->scroll_event(widget, event);
381 static gboolean
382 button_press_event(GtkWidget *widget, GdkEventButton *event)
384 GtkWidgetClass *parent_class;
385 AdgWidgetPrivate *priv;
387 parent_class = (GtkWidgetClass *) adg_widget_parent_class;
388 priv = ((AdgWidget *) widget)->priv;
390 if (event->type == GDK_BUTTON_PRESS && event->button == 2) {
391 priv->x_event = event->x;
392 priv->y_event = event->y;
395 if (parent_class->button_press_event == NULL)
396 return FALSE;
398 return parent_class->button_press_event(widget, event);
401 static gboolean
402 motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
404 GtkWidgetClass *parent_class;
405 AdgWidgetPrivate *priv;
406 AdgMatrix model, inverted;
408 parent_class = (GtkWidgetClass *) adg_widget_parent_class;
409 priv = ((AdgWidget *) widget)->priv;
411 if ((event->state & GDK_BUTTON2_MASK) > 0 &&
412 get_transformation(widget, &model, &inverted)) {
413 double x, y;
415 x = event->x - priv->x_event;
416 y = event->y - priv->y_event;
418 cairo_matrix_transform_distance(&inverted, &x, &y);
419 cairo_matrix_translate(&model, x, y);
420 priv->x_event = event->x;
421 priv->y_event = event->y;
423 set_transformation(widget, &model);
425 gtk_widget_queue_draw(widget);
428 if (parent_class->motion_notify_event == NULL)
429 return FALSE;
431 return parent_class->motion_notify_event(widget, event);
434 static gboolean
435 get_transformation(GtkWidget *widget, AdgMatrix *model, AdgMatrix *inverted)
437 AdgCanvas *canvas;
438 const AdgMatrix *src;
440 canvas = ((AdgWidget *) widget)->priv->canvas;
441 if (canvas == NULL)
442 return FALSE;
444 src = adg_container_get_model_transformation((AdgContainer *) canvas);
446 adg_matrix_copy(model, src);
447 adg_matrix_copy(inverted, src);
448 return cairo_matrix_invert(inverted) == CAIRO_STATUS_SUCCESS;
451 static void
452 set_transformation(GtkWidget *widget, const AdgMatrix *model)
454 AdgCanvas *canvas = ((AdgWidget *) widget)->priv->canvas;
456 if (canvas != NULL)
457 adg_container_set_model_transformation((AdgContainer *) canvas, model);