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.
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.
38 * All fields are private and should not be used directly.
39 * Use its public methods instead.
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)
64 static void dispose (GObject
*object
);
65 static void get_property (GObject
*object
,
69 static void set_property (GObject
*object
,
73 static gboolean
set_canvas (AdgWidget
*widget
,
75 static gboolean
set_factor (AdgWidget
*widget
,
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
,
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
);
98 adg_widget_class_init(AdgWidgetClass
*klass
)
100 GObjectClass
*gobject_class
;
101 GtkWidgetClass
*widget_class
;
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",
120 P_("The canvas to be shown by this widget"),
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",
127 P_("The factor used in zooming in and out"),
128 1., G_MAXDOUBLE
, 1.05,
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
),
142 adg_marshal_VOID__VOID
,
147 adg_widget_init(AdgWidget
*widget
)
149 AdgWidgetPrivate
*data
= G_TYPE_INSTANCE_GET_PRIVATE(widget
,
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
|
168 dispose(GObject
*object
)
170 AdgWidgetPrivate
*data
= ((AdgWidget
*) object
)->data
;
172 if (data
->canvas
!= NULL
) {
173 g_object_unref(data
->canvas
);
177 G_OBJECT_CLASS(adg_widget_parent_class
)->dispose(object
);
181 get_property(GObject
*object
, guint prop_id
, GValue
*value
, GParamSpec
*pspec
)
183 AdgWidgetPrivate
*data
= ((AdgWidget
*) object
)->data
;
187 g_value_set_object(value
, data
->canvas
);
190 g_value_set_double(value
, data
->factor
);
193 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
199 set_property(GObject
*object
,
200 guint prop_id
, const GValue
*value
, GParamSpec
*pspec
)
202 AdgWidget
*widget
= (AdgWidget
*) object
;
206 set_canvas(widget
, g_value_get_object(value
));
209 set_factor(widget
, g_value_get_double(value
));
212 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
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
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
240 * Returns: the newly created widget
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
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
276 adg_widget_get_canvas(AdgWidget
*widget
)
278 AdgWidgetPrivate
*data
;
280 g_return_val_if_fail(ADG_IS_WIDGET(widget
), NULL
);
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.
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
316 adg_widget_get_factor(AdgWidget
*widget
)
318 AdgWidgetPrivate
*data
;
320 g_return_val_if_fail(ADG_IS_WIDGET(widget
), 0.);
329 set_canvas(AdgWidget
*widget
, AdgCanvas
*canvas
)
331 AdgWidgetPrivate
*data
;
333 g_return_val_if_fail(canvas
== NULL
|| ADG_IS_CANVAS(canvas
), FALSE
);
337 if (data
->canvas
== canvas
)
341 g_object_ref(canvas
);
344 g_object_unref(data
->canvas
);
346 data
->canvas
= canvas
;
347 g_signal_emit(widget
, signals
[CANVAS_CHANGED
], 0);
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
);
361 if (data
->factor
== factor
)
364 data
->factor
= factor
;
369 expose_event(GtkWidget
*widget
, GdkEventExpose
*event
)
371 AdgWidgetPrivate
*data
;
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
);
383 if (PARENT_WIDGET_CLASS
->expose_event
== NULL
)
386 return PARENT_WIDGET_CLASS
->expose_event(widget
, event
);
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
)) {
402 if (event
->direction
== GDK_SCROLL_UP
) {
403 factor
= data
->factor
;
405 factor
= 1. / data
->factor
;
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
)
424 return PARENT_WIDGET_CLASS
->scroll_event(widget
, event
);
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
)
440 return PARENT_WIDGET_CLASS
->button_press_event(widget
, event
);
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
)) {
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
)
471 return PARENT_WIDGET_CLASS
->motion_notify_event(widget
, event
);
475 get_local_map(GtkWidget
*widget
, AdgMatrix
*map
, AdgMatrix
*inverted
)
477 AdgWidgetPrivate
*data
;
480 data
= ((AdgWidget
*) widget
)->data
;
481 canvas
= data
->canvas
;
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
;
492 set_local_map(GtkWidget
*widget
, const AdgMatrix
*map
)
494 AdgWidgetPrivate
*data
;
497 data
= ((AdgWidget
*) widget
)->data
;
498 canvas
= data
->canvas
;
501 adg_entity_set_local_map((AdgEntity
*) canvas
, map
);