[AdgModel] Enhanced docs
[adg.git] / adg / adg-widget.c
blob9aa40129df4930b4e0e73b59e08321223bbf6e08
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:adg-widget
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. Its default implementation reacts to
28 * some mouse events: if you drag the mouse by keepeng the wheel pressed
29 * the canvas is translated by translating its local map; if the mouse
30 * wheel is rotated, the local map of the canvas is scaled up or down
31 * (accordling to the wheel direction) by a factor specified in the
32 * #AdgWidget:factor property.
33 **/
35 /**
36 * AdgWidget:
38 * All fields are private and should not be used directly.
39 * Use its public methods instead.
40 **/
43 #include "adg-widget.h"
44 #include "adg-widget-private.h"
45 #include "adg-intl.h"
48 enum {
49 PROP_0,
50 PROP_CANVAS,
51 PROP_FACTOR
54 enum {
55 CANVAS_CHANGED,
56 LAST_SIGNAL
60 static void dispose (GObject *object);
61 static void get_property (GObject *object,
62 guint prop_id,
63 GValue *value,
64 GParamSpec *pspec);
65 static void set_property (GObject *object,
66 guint prop_id,
67 const GValue *value,
68 GParamSpec *pspec);
69 static void set_canvas (AdgWidget *widget,
70 AdgCanvas *canvas);
71 static gboolean expose_event (GtkWidget *widget,
72 GdkEventExpose *event);
73 static gboolean scroll_event (GtkWidget *widget,
74 GdkEventScroll *event);
75 static gboolean button_press_event (GtkWidget *widget,
76 GdkEventButton *event);
77 static gboolean motion_notify_event (GtkWidget *widget,
78 GdkEventMotion *event);
79 static gboolean get_local_map (GtkWidget *widget,
80 AdgMatrix *map,
81 AdgMatrix *inverted);
82 static void set_local_map (GtkWidget *widget,
83 const AdgMatrix *map);
85 static guint signals[LAST_SIGNAL] = { 0 };
88 G_DEFINE_TYPE(AdgWidget, adg_widget, GTK_TYPE_DRAWING_AREA);
91 static void
92 adg_widget_class_init(AdgWidgetClass *klass)
94 GObjectClass *gobject_class;
95 GtkWidgetClass *widget_class;
96 GParamSpec *param;
98 gobject_class = (GObjectClass *) klass;
99 widget_class = (GtkWidgetClass *) klass;
101 g_type_class_add_private(klass, sizeof(AdgWidgetPrivate));
103 gobject_class->dispose = dispose;
104 gobject_class->get_property = get_property;
105 gobject_class->set_property = set_property;
107 widget_class->expose_event = expose_event;
108 widget_class->scroll_event = scroll_event;
109 widget_class->button_press_event = button_press_event;
110 widget_class->motion_notify_event = motion_notify_event;
112 param = g_param_spec_object("canvas",
113 P_("Canvas"),
114 P_("The canvas to be shown by this widget"),
115 ADG_TYPE_CANVAS,
116 G_PARAM_CONSTRUCT|G_PARAM_READWRITE);
117 g_object_class_install_property(gobject_class, PROP_CANVAS, param);
119 param = g_param_spec_double("factor",
120 P_("Factor"),
121 P_("The factor used in zooming in and out"),
122 1., G_MAXDOUBLE, 1.05,
123 G_PARAM_CONSTRUCT|G_PARAM_READWRITE);
124 g_object_class_install_property(gobject_class, PROP_FACTOR, param);
127 * AdgWidget::canvas-changed:
128 * @widget: an #AdgWidget
130 * Emitted when the widget has a new canvas.
132 signals[CANVAS_CHANGED] = g_signal_new("canvas-changed", ADG_TYPE_WIDGET,
133 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
134 G_STRUCT_OFFSET(AdgWidgetClass, canvas_changed),
135 NULL, NULL,
136 g_cclosure_marshal_VOID__VOID,
137 G_TYPE_NONE, 0);
140 static void
141 adg_widget_init(AdgWidget *widget)
143 AdgWidgetPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(widget,
144 ADG_TYPE_WIDGET,
145 AdgWidgetPrivate);
147 data->canvas = NULL;
148 data->factor = 1.05;
149 data->x_event = 0;
150 data->y_event = 0;
152 widget->data = data;
154 /* Enable GDK events to catch wheel rotation and drag */
155 gtk_widget_add_events((GtkWidget *) widget,
156 GDK_BUTTON2_MOTION_MASK|GDK_SCROLL_MASK);
159 static void
160 dispose(GObject *object)
162 AdgWidgetPrivate *data = ((AdgWidget *) object)->data;
164 if (data->canvas != NULL) {
165 g_object_unref(data->canvas);
166 data->canvas = NULL;
169 G_OBJECT_CLASS(adg_widget_parent_class)->dispose(object);
172 static void
173 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
175 AdgWidgetPrivate *data = ((AdgWidget *) object)->data;
177 switch (prop_id) {
179 case PROP_CANVAS:
180 g_value_set_object(value, data->canvas);
181 break;
183 case PROP_FACTOR:
184 g_value_set_double(value, data->factor);
185 break;
187 default:
188 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
189 break;
193 static void
194 set_property(GObject *object,
195 guint prop_id, const GValue *value, GParamSpec *pspec)
197 AdgWidget *widget;
198 AdgWidgetPrivate *data;
200 widget = (AdgWidget *) object;
201 data = widget->data;
203 switch (prop_id) {
205 case PROP_CANVAS:
206 set_canvas(widget, g_value_get_object(value));
207 break;
209 case PROP_FACTOR:
210 data->factor = g_value_get_double(value);
211 break;
213 default:
214 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
215 break;
221 * adg_widget_new:
222 * @path: the #AdgPath to stroke
224 * Creates a new #AdgWidget.
226 * Returns: the newly created widget
228 GtkWidget *
229 adg_widget_new(AdgCanvas *canvas)
231 g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL);
233 return (GtkWidget *) g_object_new(ADG_TYPE_WIDGET, "canvas", canvas, NULL);
238 * adg_widget_get_canvas:
239 * @widget: an #AdgWidget
241 * Gets the canvas associated to @widget.
243 * Returns: the requested #AdgCanvas object or %NULL on errors
245 AdgCanvas *
246 adg_widget_get_canvas(AdgWidget *widget)
248 AdgWidgetPrivate *data;
250 g_return_val_if_fail(ADG_IS_WIDGET(widget), NULL);
252 data = widget->data;
254 return data->canvas;
258 * adg_widget_set_canvas:
259 * @widget: an #AdgWidget
260 * @canvas: the new #AdgCanvas
262 * Sets a new canvas on @widget. The old canvas, if presents, is
263 * unreferenced.
265 void
266 adg_widget_set_canvas(AdgWidget *widget, AdgCanvas *canvas)
268 g_return_if_fail(ADG_IS_WIDGET(widget));
270 set_canvas(widget, canvas);
272 g_object_notify((GObject *) widget, "canvas");
276 * adg_widget_get_factor:
277 * @widget: an #AdgWidget
279 * Gets the zoom factor associated to @widget. The zoom factor is
280 * directly used to zoom in (that is, the default zoom factor of
281 * 1.05 will zoom of 5% every iteration) and it is reversed while
282 * zooming out (that is, the default factor will use 1/1.05).
284 * Returns: the requested zoom factor or 0 on error
286 gdouble
287 adg_widget_get_factor(AdgWidget *widget)
289 AdgWidgetPrivate *data;
291 g_return_val_if_fail(ADG_IS_WIDGET(widget), 0.);
293 data = widget->data;
295 return data->factor;
299 * adg_widget_set_factor:
300 * @widget: an #AdgWidget
301 * @factor: the new zoom factor
303 * Sets a new zoom factor to @widget. If the factor is less than
304 * 1, it will be clamped to 1.
306 void
307 adg_widget_set_factor(AdgWidget *widget, gdouble factor)
309 AdgWidgetPrivate *data;
311 g_return_if_fail(ADG_IS_WIDGET(widget));
313 data = widget->data;
314 data->factor = CLAMP(factor, 1., G_MAXDOUBLE);
316 g_object_notify((GObject *) widget, "factor");
320 static void
321 set_canvas(AdgWidget *widget, AdgCanvas *canvas)
323 AdgWidgetPrivate *data = widget->data;
325 if (data->canvas != NULL)
326 g_object_unref(data->canvas);
328 data->canvas = canvas;
330 if (canvas != NULL)
331 g_object_ref(data->canvas);
333 g_signal_emit(widget, signals[CANVAS_CHANGED], 0);
336 static gboolean
337 expose_event(GtkWidget *widget, GdkEventExpose *event)
339 AdgWidgetPrivate *data;
340 AdgCanvas *canvas;
341 GtkWidgetClass *parent_class;
343 data = ((AdgWidget *) widget)->data;
344 canvas = data->canvas;
345 parent_class = (GtkWidgetClass *) adg_widget_parent_class;
347 if (canvas != NULL) {
348 cairo_t *cr = gdk_cairo_create(widget->window);
349 adg_entity_render((AdgEntity *) canvas, cr);
350 cairo_destroy(cr);
353 if (parent_class->expose_event == NULL)
354 return FALSE;
356 return parent_class->expose_event(widget, event);
359 static gboolean
360 scroll_event(GtkWidget *widget, GdkEventScroll *event)
362 GtkWidgetClass *parent_class;
363 AdgWidgetPrivate *data;
364 AdgMatrix map, inverted;
366 parent_class = (GtkWidgetClass *) adg_widget_parent_class;
367 data = ((AdgWidget *) widget)->data;
369 if ((event->direction == GDK_SCROLL_UP ||
370 event->direction == GDK_SCROLL_DOWN) &&
371 get_local_map(widget, &map, &inverted)) {
372 double factor, x, y;
374 if (event->direction == GDK_SCROLL_UP) {
375 factor = data->factor;
376 } else {
377 factor = 1. / data->factor;
380 x = event->x;
381 y = event->y;
383 cairo_matrix_transform_point(&inverted, &x, &y);
385 cairo_matrix_scale(&map, factor, factor);
386 cairo_matrix_translate(&map, x/factor - x, y/factor - y);
388 set_local_map(widget, &map);
390 gtk_widget_queue_draw(widget);
393 if (parent_class->scroll_event == NULL)
394 return FALSE;
396 return parent_class->scroll_event(widget, event);
399 static gboolean
400 button_press_event(GtkWidget *widget, GdkEventButton *event)
402 GtkWidgetClass *parent_class;
403 AdgWidgetPrivate *data;
405 parent_class = (GtkWidgetClass *) adg_widget_parent_class;
406 data = ((AdgWidget *) widget)->data;
408 if (event->type == GDK_BUTTON_PRESS && event->button == 2) {
409 data->x_event = event->x;
410 data->y_event = event->y;
413 if (parent_class->button_press_event == NULL)
414 return FALSE;
416 return parent_class->button_press_event(widget, event);
419 static gboolean
420 motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
422 GtkWidgetClass *parent_class;
423 AdgWidgetPrivate *data;
424 AdgMatrix map, inverted;
426 parent_class = (GtkWidgetClass *) adg_widget_parent_class;
427 data = ((AdgWidget *) widget)->data;
429 if ((event->state & GDK_BUTTON2_MASK) > 0 &&
430 get_local_map(widget, &map, &inverted)) {
431 double x, y;
433 x = event->x - data->x_event;
434 y = event->y - data->y_event;
436 cairo_matrix_transform_distance(&inverted, &x, &y);
437 cairo_matrix_translate(&map, x, y);
438 data->x_event = event->x;
439 data->y_event = event->y;
441 set_local_map(widget, &map);
443 gtk_widget_queue_draw(widget);
446 if (parent_class->motion_notify_event == NULL)
447 return FALSE;
449 return parent_class->motion_notify_event(widget, event);
452 static gboolean
453 get_local_map(GtkWidget *widget, AdgMatrix *map, AdgMatrix *inverted)
455 AdgWidgetPrivate *data;
456 AdgCanvas *canvas;
458 data = ((AdgWidget *) widget)->data;
459 canvas = data->canvas;
460 if (canvas == NULL)
461 return FALSE;
463 adg_entity_get_local_map((AdgEntity *) canvas, map);
464 adg_matrix_copy(inverted, map);
466 return cairo_matrix_invert(inverted) == CAIRO_STATUS_SUCCESS;
469 static void
470 set_local_map(GtkWidget *widget, const AdgMatrix *map)
472 AdgWidgetPrivate *data;
473 AdgCanvas *canvas;
475 data = ((AdgWidget *) widget)->data;
476 canvas = data->canvas;
478 if (canvas != NULL)
479 adg_entity_set_local_map((AdgEntity *) canvas, map);