[AdgWidget] Added mouse interaction to zoom and pan
[adg.git] / adg / adg-widget.c
blob042cffb3303cb60e710affcbf161f38c7b50abed
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
26 **/
28 #include "adg-widget.h"
29 #include "adg-widget-private.h"
30 #include "adg-intl.h"
33 enum {
34 PROP_0,
35 PROP_CANVAS
38 enum {
39 CANVAS_CHANGED,
40 LAST_SIGNAL
44 static void dispose (GObject *object);
45 static void get_property (GObject *object,
46 guint prop_id,
47 GValue *value,
48 GParamSpec *pspec);
49 static void set_property (GObject *object,
50 guint prop_id,
51 const GValue *value,
52 GParamSpec *pspec);
53 static void set_canvas (AdgWidget *widget,
54 AdgCanvas *canvas);
55 static gboolean expose_event (GtkWidget *widget,
56 GdkEventExpose *event);
57 static gboolean scroll_event (GtkWidget *widget,
58 GdkEventScroll *event);
59 static gboolean button_press_event (GtkWidget *widget,
60 GdkEventButton *event);
61 static gboolean motion_notify_event (GtkWidget *widget,
62 GdkEventMotion *event);
63 static gboolean get_transformation (GtkWidget *widget,
64 AdgMatrix *model,
65 AdgMatrix *inverted);
66 static void set_transformation (GtkWidget *widget,
67 const AdgMatrix *model);
69 static guint signals[LAST_SIGNAL] = { 0 };
72 G_DEFINE_TYPE(AdgWidget, adg_widget, GTK_TYPE_DRAWING_AREA);
75 static void
76 adg_widget_class_init(AdgWidgetClass *klass)
78 GObjectClass *gobject_class;
79 GtkWidgetClass *widget_class;
80 GParamSpec *param;
82 gobject_class = (GObjectClass *) klass;
83 widget_class = (GtkWidgetClass *) klass;
85 g_type_class_add_private(klass, sizeof(AdgWidgetPrivate));
87 gobject_class->dispose = dispose;
88 gobject_class->get_property = get_property;
89 gobject_class->set_property = set_property;
91 widget_class->expose_event = expose_event;
92 widget_class->scroll_event = scroll_event;
93 widget_class->button_press_event = button_press_event;
94 widget_class->motion_notify_event = motion_notify_event;
96 param = g_param_spec_object("canvas",
97 P_("Canvas"),
98 P_("The canvas to be shown by this widget"),
99 ADG_TYPE_CANVAS,
100 G_PARAM_CONSTRUCT|G_PARAM_READWRITE);
101 g_object_class_install_property(gobject_class, PROP_CANVAS, param);
104 * AdgWidget::canvas-changed:
105 * @widget: an #AdgWidget
107 * Emitted when the widget has a new canvas.
109 signals[CANVAS_CHANGED] = g_signal_new("canvas-changed", ADG_TYPE_WIDGET,
110 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
111 G_STRUCT_OFFSET(AdgWidgetClass, canvas_changed),
112 NULL, NULL,
113 g_cclosure_marshal_VOID__VOID,
114 G_TYPE_NONE, 0);
117 static void
118 adg_widget_init(AdgWidget *widget)
120 AdgWidgetPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE(widget,
121 ADG_TYPE_WIDGET,
122 AdgWidgetPrivate);
124 priv->canvas = NULL;
125 priv->x_event = 0;
126 priv->y_event = 0;
128 widget->priv = priv;
130 /* Enable GDK events to catch wheel rotation and drag */
131 gtk_widget_add_events((GtkWidget *) widget,
132 GDK_BUTTON2_MOTION_MASK|GDK_SCROLL_MASK);
135 static void
136 dispose(GObject *object)
138 AdgWidgetPrivate *priv = ((AdgWidget *) object)->priv;
140 if (priv->canvas != NULL) {
141 g_object_unref(priv->canvas);
142 priv->canvas = NULL;
145 G_OBJECT_CLASS(adg_widget_parent_class)->dispose(object);
148 static void
149 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
151 AdgWidget *widget = (AdgWidget *) object;
153 switch (prop_id) {
155 case PROP_CANVAS:
156 g_value_set_object(value, widget->priv->canvas);
157 break;
159 default:
160 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
161 break;
165 static void
166 set_property(GObject *object,
167 guint prop_id, const GValue *value, GParamSpec *pspec)
169 AdgWidget *widget = (AdgWidget *) object;
171 switch (prop_id) {
173 case PROP_CANVAS:
174 set_canvas(widget, g_value_get_object(value));
175 break;
177 default:
178 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
179 break;
185 * adg_widget_new:
186 * @path: the #AdgPath to stroke
188 * Creates a new #AdgWidget.
190 * Return value: the newly created widget
192 GtkWidget *
193 adg_widget_new(AdgCanvas *canvas)
195 g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL);
197 return (GtkWidget *) g_object_new(ADG_TYPE_WIDGET, "canvas", canvas, NULL);
202 * adg_widget_get_canvas:
203 * @widget: an #AdgWidget
205 * Gets the canvas associated to @widget.
207 * Return value: the requested #AdgCanvas object or %NULL on errors
209 AdgCanvas *
210 adg_widget_get_canvas(AdgWidget *widget)
212 g_return_val_if_fail(ADG_IS_WIDGET(widget), NULL);
214 return widget->priv->canvas;
218 * adg_widget_set_canvas:
219 * @widget: an #AdgWidget
220 * @canvas: the new #AdgCanvas
222 * Sets a new canvas on @widget. The old canvas, if presents, is
223 * unreferenced.
225 void
226 adg_widget_set_canvas(AdgWidget *widget, AdgCanvas *canvas)
228 g_return_if_fail(ADG_IS_WIDGET(widget));
230 set_canvas(widget, canvas);
232 g_object_notify((GObject *) widget, "canvas");
236 static void
237 set_canvas(AdgWidget *widget, AdgCanvas *canvas)
239 if (widget->priv->canvas != NULL)
240 g_object_unref(widget->priv->canvas);
242 widget->priv->canvas = canvas;
244 if (canvas != NULL)
245 g_object_ref(widget->priv->canvas);
247 g_signal_emit(widget, signals[CANVAS_CHANGED], 0);
250 static gboolean
251 expose_event(GtkWidget *widget, GdkEventExpose *event)
253 AdgCanvas *canvas;
254 GtkWidgetClass *parent_class;
256 canvas = ((AdgWidget *) widget)->priv->canvas;
257 parent_class = (GtkWidgetClass *) adg_widget_parent_class;
259 if (canvas != NULL) {
260 cairo_t *cr = gdk_cairo_create(widget->window);
261 adg_entity_render((AdgEntity *) canvas, cr);
262 cairo_destroy(cr);
265 if (parent_class->expose_event == NULL)
266 return FALSE;
268 return parent_class->expose_event(widget, event);
271 static gboolean
272 scroll_event(GtkWidget *widget, GdkEventScroll *event)
274 GtkWidgetClass *parent_class;
275 AdgWidgetPrivate *priv;
276 AdgMatrix model, inverted;
278 parent_class = (GtkWidgetClass *) adg_widget_parent_class;
279 priv = ((AdgWidget *) widget)->priv;
281 if ((event->direction == GDK_SCROLL_UP ||
282 event->direction == GDK_SCROLL_DOWN) &&
283 get_transformation(widget, &model, &inverted)) {
284 double factor, x, y;
286 if (event->direction == GDK_SCROLL_UP) {
287 factor = 1.05;
288 } else {
289 factor = 1. / 1.05;
292 x = event->x;
293 y = event->y;
295 cairo_matrix_transform_point(&inverted, &x, &y);
297 cairo_matrix_scale(&model, factor, factor);
298 cairo_matrix_translate(&model, x/factor - x, y/factor - y);
300 set_transformation(widget, &model);
302 gtk_widget_queue_draw(widget);
305 if (parent_class->scroll_event == NULL)
306 return FALSE;
308 return parent_class->scroll_event(widget, event);
311 static gboolean
312 button_press_event(GtkWidget *widget, GdkEventButton *event)
314 GtkWidgetClass *parent_class;
315 AdgWidgetPrivate *priv;
317 parent_class = (GtkWidgetClass *) adg_widget_parent_class;
318 priv = ((AdgWidget *) widget)->priv;
320 if (event->type == GDK_BUTTON_PRESS && event->button == 2) {
321 priv->x_event = event->x;
322 priv->y_event = event->y;
325 if (parent_class->button_press_event == NULL)
326 return FALSE;
328 return parent_class->button_press_event(widget, event);
331 static gboolean
332 motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
334 GtkWidgetClass *parent_class;
335 AdgWidgetPrivate *priv;
336 AdgMatrix model, inverted;
338 parent_class = (GtkWidgetClass *) adg_widget_parent_class;
339 priv = ((AdgWidget *) widget)->priv;
341 if ((event->state & GDK_BUTTON2_MASK) > 0 &&
342 get_transformation(widget, &model, &inverted)) {
343 double x, y;
345 x = event->x - priv->x_event;
346 y = event->y - priv->y_event;
348 cairo_matrix_transform_distance(&inverted, &x, &y);
349 cairo_matrix_translate(&model, x, y);
350 priv->x_event = event->x;
351 priv->y_event = event->y;
353 set_transformation(widget, &model);
355 gtk_widget_queue_draw(widget);
358 if (parent_class->motion_notify_event == NULL)
359 return FALSE;
361 return parent_class->motion_notify_event(widget, event);
364 static gboolean
365 get_transformation(GtkWidget *widget, AdgMatrix *model, AdgMatrix *inverted)
367 AdgCanvas *canvas;
368 const AdgMatrix *src;
370 canvas = ((AdgWidget *) widget)->priv->canvas;
371 if (canvas == NULL)
372 return FALSE;
374 src = adg_container_get_model_transformation((AdgContainer *) canvas);
376 adg_matrix_copy(model, src);
377 adg_matrix_copy(inverted, src);
378 return cairo_matrix_invert(inverted) == CAIRO_STATUS_SUCCESS;
381 static void
382 set_transformation(GtkWidget *widget, const AdgMatrix *model)
384 AdgCanvas *canvas = ((AdgWidget *) widget)->priv->canvas;
386 if (canvas != NULL)
387 adg_container_set_model_transformation((AdgContainer *) canvas, model);