[AdgPath] Using a proper approach to check a joint for convexity
[adg.git] / adg / adg-widget.c
blobe2e0d87221457517e8d32a74827fdd76ec74ab86
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,
36 PROP_FACTOR
39 enum {
40 CANVAS_CHANGED,
41 LAST_SIGNAL
45 static void dispose (GObject *object);
46 static void get_property (GObject *object,
47 guint prop_id,
48 GValue *value,
49 GParamSpec *pspec);
50 static void set_property (GObject *object,
51 guint prop_id,
52 const GValue *value,
53 GParamSpec *pspec);
54 static void set_canvas (AdgWidget *widget,
55 AdgCanvas *canvas);
56 static gboolean expose_event (GtkWidget *widget,
57 GdkEventExpose *event);
58 static gboolean scroll_event (GtkWidget *widget,
59 GdkEventScroll *event);
60 static gboolean button_press_event (GtkWidget *widget,
61 GdkEventButton *event);
62 static gboolean motion_notify_event (GtkWidget *widget,
63 GdkEventMotion *event);
64 static gboolean get_transformation (GtkWidget *widget,
65 AdgMatrix *model,
66 AdgMatrix *inverted);
67 static void set_transformation (GtkWidget *widget,
68 const AdgMatrix *model);
70 static guint signals[LAST_SIGNAL] = { 0 };
73 G_DEFINE_TYPE(AdgWidget, adg_widget, GTK_TYPE_DRAWING_AREA);
76 static void
77 adg_widget_class_init(AdgWidgetClass *klass)
79 GObjectClass *gobject_class;
80 GtkWidgetClass *widget_class;
81 GParamSpec *param;
83 gobject_class = (GObjectClass *) klass;
84 widget_class = (GtkWidgetClass *) klass;
86 g_type_class_add_private(klass, sizeof(AdgWidgetPrivate));
88 gobject_class->dispose = dispose;
89 gobject_class->get_property = get_property;
90 gobject_class->set_property = set_property;
92 widget_class->expose_event = expose_event;
93 widget_class->scroll_event = scroll_event;
94 widget_class->button_press_event = button_press_event;
95 widget_class->motion_notify_event = motion_notify_event;
97 param = g_param_spec_object("canvas",
98 P_("Canvas"),
99 P_("The canvas to be shown by this widget"),
100 ADG_TYPE_CANVAS,
101 G_PARAM_CONSTRUCT|G_PARAM_READWRITE);
102 g_object_class_install_property(gobject_class, PROP_CANVAS, param);
104 param = g_param_spec_double("factor",
105 P_("Factor"),
106 P_("The factor used in zooming in and out"),
107 1., G_MAXDOUBLE, 1.05,
108 G_PARAM_CONSTRUCT|G_PARAM_READWRITE);
109 g_object_class_install_property(gobject_class, PROP_FACTOR, param);
112 * AdgWidget::canvas-changed:
113 * @widget: an #AdgWidget
115 * Emitted when the widget has a new canvas.
117 signals[CANVAS_CHANGED] = g_signal_new("canvas-changed", ADG_TYPE_WIDGET,
118 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
119 G_STRUCT_OFFSET(AdgWidgetClass, canvas_changed),
120 NULL, NULL,
121 g_cclosure_marshal_VOID__VOID,
122 G_TYPE_NONE, 0);
125 static void
126 adg_widget_init(AdgWidget *widget)
128 AdgWidgetPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE(widget,
129 ADG_TYPE_WIDGET,
130 AdgWidgetPrivate);
132 priv->canvas = NULL;
133 priv->factor = 1.05;
134 priv->x_event = 0;
135 priv->y_event = 0;
137 widget->priv = priv;
139 /* Enable GDK events to catch wheel rotation and drag */
140 gtk_widget_add_events((GtkWidget *) widget,
141 GDK_BUTTON2_MOTION_MASK|GDK_SCROLL_MASK);
144 static void
145 dispose(GObject *object)
147 AdgWidgetPrivate *priv = ((AdgWidget *) object)->priv;
149 if (priv->canvas != NULL) {
150 g_object_unref(priv->canvas);
151 priv->canvas = NULL;
154 G_OBJECT_CLASS(adg_widget_parent_class)->dispose(object);
157 static void
158 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
160 AdgWidget *widget = (AdgWidget *) object;
162 switch (prop_id) {
164 case PROP_CANVAS:
165 g_value_set_object(value, widget->priv->canvas);
166 break;
168 case PROP_FACTOR:
169 g_value_set_double(value, widget->priv->factor);
170 break;
172 default:
173 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
174 break;
178 static void
179 set_property(GObject *object,
180 guint prop_id, const GValue *value, GParamSpec *pspec)
182 AdgWidget *widget = (AdgWidget *) object;
184 switch (prop_id) {
186 case PROP_CANVAS:
187 set_canvas(widget, g_value_get_object(value));
188 break;
190 case PROP_FACTOR:
191 widget->priv->factor = g_value_get_double(value);
192 break;
194 default:
195 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
196 break;
202 * adg_widget_new:
203 * @path: the #AdgPath to stroke
205 * Creates a new #AdgWidget.
207 * Return value: the newly created widget
209 GtkWidget *
210 adg_widget_new(AdgCanvas *canvas)
212 g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL);
214 return (GtkWidget *) g_object_new(ADG_TYPE_WIDGET, "canvas", canvas, NULL);
219 * adg_widget_get_canvas:
220 * @widget: an #AdgWidget
222 * Gets the canvas associated to @widget.
224 * Return value: the requested #AdgCanvas object or %NULL on errors
226 AdgCanvas *
227 adg_widget_get_canvas(AdgWidget *widget)
229 g_return_val_if_fail(ADG_IS_WIDGET(widget), NULL);
231 return widget->priv->canvas;
235 * adg_widget_set_canvas:
236 * @widget: an #AdgWidget
237 * @canvas: the new #AdgCanvas
239 * Sets a new canvas on @widget. The old canvas, if presents, is
240 * unreferenced.
242 void
243 adg_widget_set_canvas(AdgWidget *widget, AdgCanvas *canvas)
245 g_return_if_fail(ADG_IS_WIDGET(widget));
247 set_canvas(widget, canvas);
249 g_object_notify((GObject *) widget, "canvas");
253 * adg_widget_get_factor:
254 * @widget: an #AdgWidget
256 * Gets the zoom factor associated to @widget. The zoom factor is
257 * directly used to zoom in (that is, the default zoom factor of
258 * 1.05 will zoom of 5% every iteration) and it is reversed while
259 * zooming out (that is, the default factor will use 1/1.05).
261 * Return value: the requested zoom factor or 0 on error
263 gdouble
264 adg_widget_get_factor(AdgWidget *widget)
266 g_return_val_if_fail(ADG_IS_WIDGET(widget), 0.);
268 return widget->priv->factor;
272 * adg_widget_set_factor:
273 * @widget: an #AdgWidget
274 * @factor: the new zoom factor
276 * Sets a new zoom factor to @widget. If the factor is less than
277 * 1, it will be clamped to 1.
279 void
280 adg_widget_set_factor(AdgWidget *widget, gdouble factor)
282 g_return_if_fail(ADG_IS_WIDGET(widget));
284 widget->priv->factor = CLAMP(factor, 1., G_MAXDOUBLE);
286 g_object_notify((GObject *) widget, "factor");
290 static void
291 set_canvas(AdgWidget *widget, AdgCanvas *canvas)
293 if (widget->priv->canvas != NULL)
294 g_object_unref(widget->priv->canvas);
296 widget->priv->canvas = canvas;
298 if (canvas != NULL)
299 g_object_ref(widget->priv->canvas);
301 g_signal_emit(widget, signals[CANVAS_CHANGED], 0);
304 static gboolean
305 expose_event(GtkWidget *widget, GdkEventExpose *event)
307 AdgCanvas *canvas;
308 GtkWidgetClass *parent_class;
310 canvas = ((AdgWidget *) widget)->priv->canvas;
311 parent_class = (GtkWidgetClass *) adg_widget_parent_class;
313 if (canvas != NULL) {
314 cairo_t *cr = gdk_cairo_create(widget->window);
315 adg_entity_render((AdgEntity *) canvas, cr);
316 cairo_destroy(cr);
319 if (parent_class->expose_event == NULL)
320 return FALSE;
322 return parent_class->expose_event(widget, event);
325 static gboolean
326 scroll_event(GtkWidget *widget, GdkEventScroll *event)
328 GtkWidgetClass *parent_class;
329 AdgWidgetPrivate *priv;
330 AdgMatrix model, inverted;
332 parent_class = (GtkWidgetClass *) adg_widget_parent_class;
333 priv = ((AdgWidget *) widget)->priv;
335 if ((event->direction == GDK_SCROLL_UP ||
336 event->direction == GDK_SCROLL_DOWN) &&
337 get_transformation(widget, &model, &inverted)) {
338 double factor, x, y;
340 if (event->direction == GDK_SCROLL_UP) {
341 factor = priv->factor;
342 } else {
343 factor = 1. / priv->factor;
346 x = event->x;
347 y = event->y;
349 cairo_matrix_transform_point(&inverted, &x, &y);
351 cairo_matrix_scale(&model, factor, factor);
352 cairo_matrix_translate(&model, x/factor - x, y/factor - y);
354 set_transformation(widget, &model);
356 gtk_widget_queue_draw(widget);
359 if (parent_class->scroll_event == NULL)
360 return FALSE;
362 return parent_class->scroll_event(widget, event);
365 static gboolean
366 button_press_event(GtkWidget *widget, GdkEventButton *event)
368 GtkWidgetClass *parent_class;
369 AdgWidgetPrivate *priv;
371 parent_class = (GtkWidgetClass *) adg_widget_parent_class;
372 priv = ((AdgWidget *) widget)->priv;
374 if (event->type == GDK_BUTTON_PRESS && event->button == 2) {
375 priv->x_event = event->x;
376 priv->y_event = event->y;
379 if (parent_class->button_press_event == NULL)
380 return FALSE;
382 return parent_class->button_press_event(widget, event);
385 static gboolean
386 motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
388 GtkWidgetClass *parent_class;
389 AdgWidgetPrivate *priv;
390 AdgMatrix model, inverted;
392 parent_class = (GtkWidgetClass *) adg_widget_parent_class;
393 priv = ((AdgWidget *) widget)->priv;
395 if ((event->state & GDK_BUTTON2_MASK) > 0 &&
396 get_transformation(widget, &model, &inverted)) {
397 double x, y;
399 x = event->x - priv->x_event;
400 y = event->y - priv->y_event;
402 cairo_matrix_transform_distance(&inverted, &x, &y);
403 cairo_matrix_translate(&model, x, y);
404 priv->x_event = event->x;
405 priv->y_event = event->y;
407 set_transformation(widget, &model);
409 gtk_widget_queue_draw(widget);
412 if (parent_class->motion_notify_event == NULL)
413 return FALSE;
415 return parent_class->motion_notify_event(widget, event);
418 static gboolean
419 get_transformation(GtkWidget *widget, AdgMatrix *model, AdgMatrix *inverted)
421 AdgCanvas *canvas;
422 const AdgMatrix *src;
424 canvas = ((AdgWidget *) widget)->priv->canvas;
425 if (canvas == NULL)
426 return FALSE;
428 src = adg_container_get_model_transformation((AdgContainer *) canvas);
430 adg_matrix_copy(model, src);
431 adg_matrix_copy(inverted, src);
432 return cairo_matrix_invert(inverted) == CAIRO_STATUS_SUCCESS;
435 static void
436 set_transformation(GtkWidget *widget, const AdgMatrix *model)
438 AdgCanvas *canvas = ((AdgWidget *) widget)->priv->canvas;
440 if (canvas != NULL)
441 adg_container_set_model_transformation((AdgContainer *) canvas, model);