[build] Moved project dirs under src/
[adg.git] / src / adg / adg-widget.c
blobf69d506cea7c0fcf0cba1a0c8c85354a3c8939ff
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009,2010 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-internal.h"
44 #include "adg-canvas.h"
45 #include "adg-widget.h"
46 #include "adg-widget-private.h"
47 #include "adg-marshal.h"
49 #define PARENT_WIDGET_CLASS ((GtkWidgetClass *) adg_widget_parent_class)
52 enum {
53 PROP_0,
54 PROP_CANVAS,
55 PROP_FACTOR
58 enum {
59 CANVAS_CHANGED,
60 LAST_SIGNAL
64 static void dispose (GObject *object);
65 static void get_property (GObject *object,
66 guint prop_id,
67 GValue *value,
68 GParamSpec *pspec);
69 static void set_property (GObject *object,
70 guint prop_id,
71 const GValue *value,
72 GParamSpec *pspec);
73 static gboolean set_canvas (AdgWidget *widget,
74 AdgCanvas *canvas);
75 static gboolean set_factor (AdgWidget *widget,
76 gdouble factor);
77 static gboolean expose_event (GtkWidget *widget,
78 GdkEventExpose *event);
79 static gboolean scroll_event (GtkWidget *widget,
80 GdkEventScroll *event);
81 static gboolean button_press_event (GtkWidget *widget,
82 GdkEventButton *event);
83 static gboolean motion_notify_event (GtkWidget *widget,
84 GdkEventMotion *event);
85 static gboolean get_local_map (GtkWidget *widget,
86 AdgMatrix *map,
87 AdgMatrix *inverted);
88 static void set_local_map (GtkWidget *widget,
89 const AdgMatrix *map);
91 static guint signals[LAST_SIGNAL] = { 0 };
94 G_DEFINE_TYPE(AdgWidget, adg_widget, GTK_TYPE_DRAWING_AREA);
97 static void
98 adg_widget_class_init(AdgWidgetClass *klass)
100 GObjectClass *gobject_class;
101 GtkWidgetClass *widget_class;
102 GParamSpec *param;
104 gobject_class = (GObjectClass *) klass;
105 widget_class = (GtkWidgetClass *) klass;
107 g_type_class_add_private(klass, sizeof(AdgWidgetPrivate));
109 gobject_class->dispose = dispose;
110 gobject_class->get_property = get_property;
111 gobject_class->set_property = set_property;
113 widget_class->expose_event = expose_event;
114 widget_class->scroll_event = scroll_event;
115 widget_class->button_press_event = button_press_event;
116 widget_class->motion_notify_event = motion_notify_event;
118 param = g_param_spec_object("canvas",
119 P_("Canvas"),
120 P_("The canvas to be shown by this widget"),
121 ADG_TYPE_CANVAS,
122 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
123 g_object_class_install_property(gobject_class, PROP_CANVAS, param);
125 param = g_param_spec_double("factor",
126 P_("Factor"),
127 P_("The factor used in zooming in and out"),
128 1., G_MAXDOUBLE, 1.05,
129 G_PARAM_READWRITE);
130 g_object_class_install_property(gobject_class, PROP_FACTOR, param);
133 * AdgWidget::canvas-changed:
134 * @widget: an #AdgWidget
136 * Emitted when the widget has a new canvas.
138 signals[CANVAS_CHANGED] = g_signal_new("canvas-changed", ADG_TYPE_WIDGET,
139 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
140 G_STRUCT_OFFSET(AdgWidgetClass, canvas_changed),
141 NULL, NULL,
142 adg_marshal_VOID__VOID,
143 G_TYPE_NONE, 0);
146 static void
147 adg_widget_init(AdgWidget *widget)
149 AdgWidgetPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(widget,
150 ADG_TYPE_WIDGET,
151 AdgWidgetPrivate);
153 data->canvas = NULL;
154 data->factor = 1.05;
155 data->x_event = 0;
156 data->y_event = 0;
158 widget->data = data;
160 /* Enable GDK events to catch wheel rotation and drag */
161 gtk_widget_add_events((GtkWidget *) widget,
162 GDK_BUTTON_PRESS_MASK|
163 GDK_BUTTON2_MOTION_MASK|
164 GDK_SCROLL_MASK);
167 static void
168 dispose(GObject *object)
170 AdgWidgetPrivate *data = ((AdgWidget *) object)->data;
172 if (data->canvas != NULL) {
173 g_object_unref(data->canvas);
174 data->canvas = NULL;
177 G_OBJECT_CLASS(adg_widget_parent_class)->dispose(object);
180 static void
181 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
183 AdgWidgetPrivate *data = ((AdgWidget *) object)->data;
185 switch (prop_id) {
186 case PROP_CANVAS:
187 g_value_set_object(value, data->canvas);
188 break;
189 case PROP_FACTOR:
190 g_value_set_double(value, data->factor);
191 break;
192 default:
193 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
194 break;
198 static void
199 set_property(GObject *object,
200 guint prop_id, const GValue *value, GParamSpec *pspec)
202 AdgWidget *widget = (AdgWidget *) object;
204 switch (prop_id) {
205 case PROP_CANVAS:
206 set_canvas(widget, g_value_get_object(value));
207 break;
208 case PROP_FACTOR:
209 set_factor(widget, g_value_get_double(value));
210 break;
211 default:
212 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
213 break;
219 * adg_widget_new:
221 * Creates a new empty #AdgWidget. The widget is useful only after
222 * an #AdgCanvas has been added either using the #AdgWidget:canvas
223 * property or with adg_widget_set_canvas().
225 * Returns: the newly created widget
227 GtkWidget *
228 adg_widget_new(void)
230 return g_object_new(ADG_TYPE_WIDGET, NULL);
234 * adg_widget_new_with_canvas:
235 * @canvas: the #AdgCanvas shown by this widget
237 * Creates a new #AdgWidget and sets the #AdgWidget:canvas property
238 * to @canvas.
240 * Returns: the newly created widget
242 GtkWidget *
243 adg_widget_new_with_canvas(AdgCanvas *canvas)
245 g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL);
247 return g_object_new(ADG_TYPE_WIDGET, "canvas", canvas, NULL);
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 if (set_canvas(widget, canvas))
264 g_object_notify((GObject *) widget, "canvas");
268 * adg_widget_get_canvas:
269 * @widget: an #AdgWidget
271 * Gets the canvas associated to @widget.
273 * Returns: the requested #AdgCanvas object or %NULL on errors
275 AdgCanvas *
276 adg_widget_get_canvas(AdgWidget *widget)
278 AdgWidgetPrivate *data;
280 g_return_val_if_fail(ADG_IS_WIDGET(widget), NULL);
282 data = widget->data;
284 return data->canvas;
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 if (set_factor(widget, factor))
301 g_object_notify((GObject *) widget, "factor");
305 * adg_widget_get_factor:
306 * @widget: an #AdgWidget
308 * Gets the zoom factor associated to @widget. The zoom factor is
309 * directly used to zoom in (that is, the default zoom factor of
310 * 1.05 will zoom of 5% every iteration) and it is reversed while
311 * zooming out (that is, the default factor will use 1/1.05).
313 * Returns: the requested zoom factor or 0 on error
315 gdouble
316 adg_widget_get_factor(AdgWidget *widget)
318 AdgWidgetPrivate *data;
320 g_return_val_if_fail(ADG_IS_WIDGET(widget), 0.);
322 data = widget->data;
324 return data->factor;
328 static gboolean
329 set_canvas(AdgWidget *widget, AdgCanvas *canvas)
331 AdgWidgetPrivate *data;
333 g_return_val_if_fail(canvas == NULL || ADG_IS_CANVAS(canvas), FALSE);
335 data = widget->data;
337 if (data->canvas == canvas)
338 return FALSE;
340 if (canvas)
341 g_object_ref(canvas);
343 if (data->canvas)
344 g_object_unref(data->canvas);
346 data->canvas = canvas;
347 g_signal_emit(widget, signals[CANVAS_CHANGED], 0);
348 return TRUE;
351 static gboolean
352 set_factor(AdgWidget *widget, gdouble factor)
354 AdgWidgetPrivate *data;
356 /* A better approach would be to use the GParamSpec of this property */
357 g_return_val_if_fail(factor >= 1, FALSE);
359 data = widget->data;
361 if (data->factor == factor)
362 return FALSE;
364 data->factor = factor;
365 return TRUE;
368 static gboolean
369 expose_event(GtkWidget *widget, GdkEventExpose *event)
371 AdgWidgetPrivate *data;
372 AdgCanvas *canvas;
374 data = ((AdgWidget *) widget)->data;
375 canvas = data->canvas;
377 if (canvas != NULL) {
378 cairo_t *cr = gdk_cairo_create(widget->window);
379 adg_entity_render((AdgEntity *) canvas, cr);
380 cairo_destroy(cr);
383 if (PARENT_WIDGET_CLASS->expose_event == NULL)
384 return FALSE;
386 return PARENT_WIDGET_CLASS->expose_event(widget, event);
389 static gboolean
390 scroll_event(GtkWidget *widget, GdkEventScroll *event)
392 AdgWidgetPrivate *data;
393 AdgMatrix map, inverted;
395 data = ((AdgWidget *) widget)->data;
397 if ((event->direction == GDK_SCROLL_UP ||
398 event->direction == GDK_SCROLL_DOWN) &&
399 get_local_map(widget, &map, &inverted)) {
400 double factor, x, y;
402 if (event->direction == GDK_SCROLL_UP) {
403 factor = data->factor;
404 } else {
405 factor = 1. / data->factor;
408 x = event->x;
409 y = event->y;
411 cairo_matrix_transform_point(&inverted, &x, &y);
413 cairo_matrix_scale(&map, factor, factor);
414 cairo_matrix_translate(&map, x/factor - x, y/factor - y);
416 set_local_map(widget, &map);
418 gtk_widget_queue_draw(widget);
421 if (PARENT_WIDGET_CLASS->scroll_event == NULL)
422 return FALSE;
424 return PARENT_WIDGET_CLASS->scroll_event(widget, event);
427 static gboolean
428 button_press_event(GtkWidget *widget, GdkEventButton *event)
430 AdgWidgetPrivate *data = ((AdgWidget *) widget)->data;
432 if (event->type == GDK_BUTTON_PRESS && event->button == 2) {
433 data->x_event = event->x;
434 data->y_event = event->y;
437 if (PARENT_WIDGET_CLASS->button_press_event == NULL)
438 return FALSE;
440 return PARENT_WIDGET_CLASS->button_press_event(widget, event);
443 static gboolean
444 motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
446 AdgWidgetPrivate *data;
447 AdgMatrix map, inverted;
449 data = ((AdgWidget *) widget)->data;
451 if ((event->state & GDK_BUTTON2_MASK) > 0 &&
452 get_local_map(widget, &map, &inverted)) {
453 double x, y;
455 x = event->x - data->x_event;
456 y = event->y - data->y_event;
458 cairo_matrix_transform_distance(&inverted, &x, &y);
459 cairo_matrix_translate(&map, x, y);
460 data->x_event = event->x;
461 data->y_event = event->y;
463 set_local_map(widget, &map);
465 gtk_widget_queue_draw(widget);
468 if (PARENT_WIDGET_CLASS->motion_notify_event == NULL)
469 return FALSE;
471 return PARENT_WIDGET_CLASS->motion_notify_event(widget, event);
474 static gboolean
475 get_local_map(GtkWidget *widget, AdgMatrix *map, AdgMatrix *inverted)
477 AdgWidgetPrivate *data;
478 AdgCanvas *canvas;
480 data = ((AdgWidget *) widget)->data;
481 canvas = data->canvas;
482 if (canvas == NULL)
483 return FALSE;
485 adg_matrix_copy(map, adg_entity_get_local_map((AdgEntity *) canvas));
486 adg_matrix_copy(inverted, map);
488 return cairo_matrix_invert(inverted) == CAIRO_STATUS_SUCCESS;
491 static void
492 set_local_map(GtkWidget *widget, const AdgMatrix *map)
494 AdgWidgetPrivate *data;
495 AdgCanvas *canvas;
497 data = ((AdgWidget *) widget)->data;
498 canvas = data->canvas;
500 if (canvas != NULL)
501 adg_entity_set_local_map((AdgEntity *) canvas, map);