1 /*
2  * goc-item.c :
3  *
4  * Copyright (C) 2008-2009 Jean Brefort (jean.brefort@normalesup.org)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
19  * USA
20  */
21 
22 #include <goffice/goffice-config.h>
23 #include <goffice/goffice.h>
24 #include <gsf/gsf-impl-utils.h>
25 #include <glib/gi18n-lib.h>
26 
27 #ifdef GOFFICE_WITH_GTK
28 static void cb_hierarchy_changed (const GocItem *item);
29 #endif
30 
31 
32 /**
33  * SECTION:goc-item
34  * @short_description: Base canvas item.
35  *
36  * #GocItem is the virtual base object for canvas items.
37 **/
38 
39 /**
40  * _GocItem:
41  * @base: the parent object.
42  * @canvas: the canvas in which the item is displayed.
43  * @parent: the parent item.
44  * @cached_bounds: whether bounds have been cached in @x0, @y0, @x1 and @y1.
45  * @visible: whether the item is visible or hidden. A visible item needs to lie
46  * in the visible region of the canvas to be really visible.
47  * @realized: whether the item is realized.
48  * @x0: the lowest horizontal bound of the item.
49  * @y0: the lowest vertical bound of the item.
50  * @x1: the highest horizontal bound of the item.
51  * @y1: the highest vertical bound of the item.
52  * @op: the #cairo_operator_t to use when drawing the item.
53  * @transform: the #cairo_matrix_t to apply to the item.
54  * @transformed: whether @transform is not unity.
55  * @priv: private data.
56  *
57  * <para>
58  * #GocItem contains the following fields:
59  * <informaltable pgwide="1" frame="none" role="struct">
60  * <tgroup cols="2"><colspec colwidth="2*"/><colspec colwidth="8*"/>
61  * <tbody>
62  * <row>
63  * <entry>double x0;</entry>
64  * <entry>The lowest horizontal bound of the item.</entry>
65  * </row>
66  * <row>
67  * <entry>double y0;</entry>
68  * <entry>The lowest vertical bound of the item.</entry>
69  * </row>
70  * <row>
71  * <entry>double x1;</entry>
72  * <entry>The highest horizontal bound of the item.</entry>
73  * </row>
74  * <row>
75  * <entry>double y1;</entry>
76  * <entry>The highest vertical bound of the item.</entry>
77  * </row>
78  * </tbody></tgroup></informaltable>
79  * </para>
80  *
81  * The various fields should not be accessed directly except from children
82  * objects which must update @x0, @y0, @x1, and @y1 from their #update_bounds
83  * method. All other fields are read-only and not documented.
84  **/
85 
86 enum {
87 	ITEM_PROP_0,
88 	ITEM_PROP_CANVAS,
89 	ITEM_PROP_PARENT
90 };
91 
92 /**
93  * GocItemClass:
94  * @base: the parent class
95  * @distance: returns the distance between the item and the point defined by
96  * @x and @y. When the distance is larger than a few pixels, the result is not
97  * relevant, so an approximate value or even G_MAXDOUBLE might be returned.
98  * @draw: draws the item to the cairo context.
99  * @draw_region: draws the item in the region defined by @x0, @y0, @x1 and @y1.
100  * Should return TRUE when successfull. If FALSE is returned, @draw will be
101  * called. There is no need to implement both methods for an item. Large items
102  * should implement @draw_region.
103  * @update_bounds: updates the bounds stored in #GocItem as fields #x0, #y0,
104  * #x1,and #y1.
105  * @button_pressed: callback for a button press event.
106  * @button2_pressed: callback for a double click event.
107  * @button_released: callback for a button release event.
108  * @motion: callback for a motion event.
109  * @enter_notify: callback for an enter notify event.
110  * @leave_notify: callback for a leave notify event.
111  * @realize: callback for a realizes event.
112  * @unrealize: callback for an unrealize event.
113  * @key_pressed: callback for a key press event.
114  * @key_released: callback for a key release event.
115  * @notify_scrolled: callback for a notify scrolled event. This is useful to
116  * reposition children of the GtkLayout parent of the canvas to their new
117  * position.
118  * @get_window: returns the #GdkWindow for the item if any.
119  *
120  * The base class for all canvas items.
121  **/
122 
123 static GObjectClass *item_parent_class;
124 
125 static GParamSpecPool *style_property_spec_pool;
126 static GQuark quark_property_parser;
127 static GQuark quark_style_context;
128 
129 static gboolean
goc_item_button_pressed(GocItem * item,int button,double x,double y)130 goc_item_button_pressed (GocItem *item, int button, double x, double y)
131 {
132 	return (item->parent)?
133 		GOC_ITEM_GET_CLASS (item->parent)->button_pressed (GOC_ITEM (item->parent), button, x, y):
134 		FALSE;
135 }
136 
137 static gboolean
goc_item_button2_pressed(GocItem * item,int button,double x,double y)138 goc_item_button2_pressed (GocItem *item, int button, double x, double y)
139 {
140 	return (item->parent)?
141 		GOC_ITEM_GET_CLASS (item->parent)->button2_pressed (GOC_ITEM (item->parent), button, x, y):
142 		FALSE;
143 }
144 
145 static gboolean
goc_item_button_released(GocItem * item,int button,double x,double y)146 goc_item_button_released (GocItem *item, int button, double x, double y)
147 {
148 	return (item->parent)?
149 		GOC_ITEM_GET_CLASS (item->parent)->button_released (GOC_ITEM (item->parent), button, x, y):
150 		FALSE;
151 }
152 
153 static gboolean
goc_item_motion(GocItem * item,double x,double y)154 goc_item_motion (GocItem *item, double x, double y)
155 {
156 	return (item->parent)?
157 		GOC_ITEM_GET_CLASS (item->parent)->motion (GOC_ITEM (item->parent), x, y):
158 		FALSE;
159 }
160 
161 static gboolean
goc_item_enter_notify(GocItem * item,double x,double y)162 goc_item_enter_notify (GocItem *item, double x, double y)
163 {
164 	return (item->parent)?
165 		GOC_ITEM_GET_CLASS (item->parent)->enter_notify (GOC_ITEM (item->parent), x, y):
166 		FALSE;
167 }
168 
169 static gboolean
goc_item_leave_notify(GocItem * item,double x,double y)170 goc_item_leave_notify (GocItem *item, double x, double y)
171 {
172 	return (item->parent)?
173 		GOC_ITEM_GET_CLASS (item->parent)->leave_notify (GOC_ITEM (item->parent), x, y):
174 		FALSE;
175 }
176 
177 #ifdef GOFFICE_WITH_GTK
178 static gboolean
goc_item_key_pressed(GocItem * item,GdkEventKey * ev)179 goc_item_key_pressed (GocItem *item, GdkEventKey* ev)
180 {
181 	return (item->parent)?
182 		GOC_ITEM_GET_CLASS (item->parent)->key_pressed (GOC_ITEM (item->parent), ev):
183 		FALSE;
184 }
185 
186 static gboolean
goc_item_key_released(GocItem * item,GdkEventKey * ev)187 goc_item_key_released (GocItem *item, GdkEventKey* ev)
188 {
189 	return (item->parent)?
190 		GOC_ITEM_GET_CLASS (item->parent)->key_released (GOC_ITEM (item->parent), ev):
191 		FALSE;
192 }
193 #endif
194 
195 static void
goc_item_realize(GocItem * item)196 goc_item_realize (GocItem *item)
197 {
198 	if (item->realized)
199 		g_warning ("Duplicate realize for %p %s\n",
200 			   item,
201 			   g_type_name_from_instance ((GTypeInstance*)item));
202 	else
203 		item->realized = TRUE;
204 }
205 
206 static void
goc_item_unrealize(GocItem * item)207 goc_item_unrealize (GocItem *item)
208 {
209 	if (item->realized)
210 		item->realized = FALSE;
211 	else
212 		g_warning ("Duplicate unrealize for %p %s\n",
213 			   item,
214 			   g_type_name_from_instance ((GTypeInstance*)item));
215 }
216 
217 static void
goc_item_dispose(GObject * object)218 goc_item_dispose (GObject *object)
219 {
220 	GocItem *item = GOC_ITEM (object);
221 	GtkStyleContext *context;
222 
223 	if (item->canvas) {
224 		item->cached_bounds = TRUE; /* avoids a call to update_bounds */
225 		goc_item_invalidate (item);
226 	}
227 
228 	if (item->parent != NULL)
229 		goc_group_remove_child (item->parent, item);
230 
231 #ifdef GOFFICE_WITH_GTK
232 	context = g_object_get_qdata (G_OBJECT (item), quark_style_context);
233 	if (context) {
234 		g_signal_handlers_disconnect_by_func
235 			(object, G_CALLBACK (cb_hierarchy_changed), NULL);
236 		gtk_style_context_set_parent (context, NULL);
237 		g_object_set_qdata (object, quark_style_context, NULL);
238 	}
239 #endif
240 
241 	item_parent_class->dispose (object);
242 }
243 
244 static void
goc_item_get_property(GObject * gobject,guint param_id,GValue * value,GParamSpec * pspec)245 goc_item_get_property (GObject *gobject, guint param_id,
246 		       GValue *value, GParamSpec *pspec)
247 {
248 	GocItem *item = GOC_ITEM (gobject);
249 
250 	switch (param_id) {
251 	case ITEM_PROP_CANVAS:
252 		g_value_set_object (value, item->canvas);
253 		break;
254 
255 	case ITEM_PROP_PARENT:
256 		g_value_set_object (value, item->parent);
257 		break;
258 
259 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
260 		return; /* NOTE : RETURN */
261 	}
262 }
263 
264 static void
goc_item_class_init(GocItemClass * item_klass)265 goc_item_class_init (GocItemClass *item_klass)
266 {
267 	GObjectClass *obj_klass = (GObjectClass *) item_klass;
268 	item_parent_class = g_type_class_peek_parent (item_klass);
269 
270 	obj_klass->dispose = goc_item_dispose;
271 	obj_klass->get_property = goc_item_get_property;
272 
273 	g_object_class_install_property (obj_klass, ITEM_PROP_CANVAS,
274 		g_param_spec_object ("canvas",
275 			_("Canvas"),
276 			_("The canvas object on which the item resides"),
277 			GOC_TYPE_CANVAS,
278 			GSF_PARAM_STATIC | G_PARAM_READABLE));
279 	g_object_class_install_property (obj_klass, ITEM_PROP_PARENT,
280 		g_param_spec_object ("parent",
281 			_("Parent"),
282 			_("The group in which the item resides"),
283 			GOC_TYPE_GROUP,
284 			GSF_PARAM_STATIC | G_PARAM_READABLE));
285 
286 	item_klass->realize = goc_item_realize;
287 	item_klass->unrealize = goc_item_unrealize;
288 	item_klass->button_pressed = goc_item_button_pressed;
289 	item_klass->button2_pressed = goc_item_button2_pressed;
290 	item_klass->button_released = goc_item_button_released;
291 	item_klass->motion = goc_item_motion;
292 	item_klass->enter_notify = goc_item_enter_notify;
293 	item_klass->leave_notify = goc_item_leave_notify;
294 #ifdef GOFFICE_WITH_GTK
295 	item_klass->key_pressed = goc_item_key_pressed;
296 	item_klass->key_released = goc_item_key_released;
297 #endif
298 
299 	style_property_spec_pool = g_param_spec_pool_new (FALSE);
300 	quark_property_parser = g_quark_from_static_string ("gtk-rc-property-parser");
301 	quark_style_context = g_quark_from_static_string ("GocItem::style-context");
302 }
303 
304 static void
goc_item_init(GocItem * item)305 goc_item_init (GocItem *item)
306 {
307 	item->visible = TRUE;
308 	cairo_matrix_init_identity (&item->transform);
309 }
310 
311 GSF_CLASS_FULL (GocItem, goc_item, NULL, NULL,
312 	   goc_item_class_init, NULL, goc_item_init,
313 	   G_TYPE_OBJECT, G_TYPE_FLAG_ABSTRACT, {})
314 
315 static void
_goc_item_update_bounds(GocItem * item)316 _goc_item_update_bounds (GocItem *item)
317 {
318 	GocItemClass *klass = GOC_ITEM_GET_CLASS (item);
319 	g_return_if_fail (klass != NULL);
320 
321 	if (klass->update_bounds)
322 		klass->update_bounds (item);
323 	item->cached_bounds = TRUE;
324 }
325 
326 /**
327  * goc_item_new:
328  * @parent: parent #GocGroup for the new item
329  * @type: #GType of the new item
330  * @first_arg_name: property name or %NULL
331  * @...: value for the first property, followed optionally by more
332  *  name/value pairs, followed by %NULL
333  *
334  * Creates a new item of type @type in group @group. Properties can be
335  * set just the same way they are in #g_object_new.
336  * Returns: (transfer none): the newly created #GocItem.
337  **/
338 GocItem*
goc_item_new(GocGroup * parent,GType type,const gchar * first_arg_name,...)339 goc_item_new (GocGroup *parent, GType type, const gchar *first_arg_name, ...)
340 {
341 	GocItem *item;
342 	va_list args;
343 
344 	g_return_val_if_fail (GOC_IS_GROUP (parent), NULL);
345 
346 	va_start (args, first_arg_name);
347 	item = GOC_ITEM (g_object_new_valist (type, first_arg_name, args));
348 	va_end (args);
349 	g_return_val_if_fail ((item != NULL), NULL);
350 
351 	goc_group_add_child (parent, item);
352 	goc_item_invalidate (item);
353 
354 	return item;
355 }
356 
357 /**
358  * goc_item_destroy:
359  * @item: #GocItem
360  *
361  * Destroys @item, removes it from its parent group and updates the canvas
362  * accordingly.
363  **/
364 void
goc_item_destroy(GocItem * item)365 goc_item_destroy (GocItem *item)
366 {
367 	g_object_run_dispose (G_OBJECT (item));
368 	g_object_unref (item);
369 }
370 
371 /**
372  * goc_item_set:
373  * @item: #GocItem
374  * @first_arg_name: property name or %NULL
375  * @...: value for the first property, followed optionally by more
376  *  name/value pairs, followed by %NULL
377  *
378  * Set properties and updates the canvas. Using #g_object_set instead would
379  * set the properties, but not update the canvas.
380  **/
381 void
goc_item_set(GocItem * item,const gchar * first_arg_name,...)382 goc_item_set (GocItem *item, const gchar *first_arg_name, ...)
383 {
384 	va_list args;
385 
386 	goc_item_invalidate (item);
387 
388 	va_start (args, first_arg_name);
389 	g_object_set_valist (G_OBJECT (item), first_arg_name, args);
390 	va_end (args);
391 
392 	goc_item_invalidate (item);
393 }
394 
395 /**
396  * goc_item_distance:
397  * @item: #GocItem
398  * @x: horizontal position
399  * @y: vertical position
400  * @near_item: where to store the nearest item
401  *
402  * Evaluates the distance between the point with canvas coordinates @x,@y
403  * and the nearest point of @item. @near_item is set with either @item or
404  * its appropriate child.
405  * Returns: the evaluated distance.
406  **/
407 double
goc_item_distance(GocItem * item,double x,double y,GocItem ** near_item)408 goc_item_distance (GocItem *item, double x, double y, GocItem **near_item)
409 {
410 	GocItemClass *klass = GOC_ITEM_GET_CLASS (item);
411 	g_return_val_if_fail (klass != NULL, G_MAXDOUBLE);
412 
413 	return (klass->distance)?
414 		klass->distance (item, x, y, near_item): G_MAXDOUBLE;
415 }
416 
417 /**
418  * goc_item_draw:
419  * @item: #GocItem
420  * @cr: #cairo_t
421  *
422  * Renders @item using @cr. There is no need to call this function directly.
423  * Invalidating the item is the way to go.
424  **/
425 void
goc_item_draw(GocItem const * item,cairo_t * cr)426 goc_item_draw (GocItem const *item, cairo_t *cr)
427 {
428 	GocItemClass *klass = GOC_ITEM_GET_CLASS (item);
429 	g_return_if_fail (klass != NULL);
430 
431 	if (klass->draw != NULL)
432 		klass->draw (item, cr);
433 }
434 
435 /**
436  * goc_item_draw_region:
437  * @item: #GocItem
438  * @cr: #cairo_t
439  * @x0: the lowest horizontal bound of the region to draw
440  * @y0: the lowest vertical bound of the region to draw
441  * @x1: the highest horizontal bound of the region to draw
442  * @y1: the highest vertical bound of the region to draw
443  *
444  * Renders @item using @cr, limiting all drawings to the region limited by
445  * @x0, @y0, @x1, and @y1. If this function returns %FALSE, #goc_item_draw
446  * should be called. There is no need to call this function directly.
447  * Invalidating the item is the way to go.
448  * Returns: %TRUE if successful.
449  **/
450 gboolean
goc_item_draw_region(GocItem const * item,cairo_t * cr,double x0,double y0,double x1,double y1)451 goc_item_draw_region (GocItem const *item, cairo_t *cr,
452 		      double x0, double y0,
453 		      double x1, double y1)
454 {
455 	GocItemClass *klass = GOC_ITEM_GET_CLASS (item);
456 	g_return_val_if_fail (klass != NULL, FALSE);
457 
458 	return (klass->draw_region != NULL)?
459 		klass->draw_region (item, cr, x0, y0, x1, y1):
460 		FALSE;
461 }
462 
463 static void
goc_item_maybe_invalidate(GocItem * item,gboolean ignore_visibility)464 goc_item_maybe_invalidate (GocItem *item, gboolean ignore_visibility)
465 {
466 	GocGroup const *parent;
467 	double x0, y0, x1, y1;
468 
469 	parent = item->parent;
470 	if (!parent)
471 		return;
472 
473 	if (!item->canvas || !goc_canvas_get_realized (item->canvas))
474 		return;
475 
476 	if (!ignore_visibility && !item->visible)
477 		return;
478 
479 	if (!item->cached_bounds)
480 		_goc_item_update_bounds (GOC_ITEM (item)); /* don't care about const */
481 	x0 = item->x0;
482 	y0 = item->y0;
483 	x1 = item->x1;
484 	y1 = item->y1;
485 	goc_group_adjust_bounds (parent, &x0, &y0, &x1, &y1);
486 	goc_canvas_invalidate (item->canvas, x0, y0, x1, y1);
487 }
488 
489 /**
490  * goc_item_invalidate:
491  * @item: #GocItem
492  *
493  * Force a redraw of @item bounding region.
494  **/
495 void
goc_item_invalidate(GocItem * item)496 goc_item_invalidate (GocItem *item)
497 {
498 	g_return_if_fail (GOC_IS_ITEM (item));
499 
500 	goc_item_maybe_invalidate (item, FALSE);
501 }
502 
503 /**
504  * goc_item_set_visible:
505  * @item: #GocItem
506  * @visible: whether the item should be visible
507  *
508  * Either hides or shows @item as appropriate..
509  **/
510 void
goc_item_set_visible(GocItem * item,gboolean visible)511 goc_item_set_visible (GocItem *item, gboolean visible)
512 {
513 	g_return_if_fail (GOC_IS_ITEM (item));
514 
515 	visible = (visible != FALSE);
516 	if (visible != item->visible) {
517 		item->visible = visible;
518 		goc_item_maybe_invalidate (item, TRUE);
519 	}
520 }
521 
522 /**
523  * goc_item_show:
524  * @item: #GocItem
525  *
526  * Makes @item visible.
527  **/
528 void
goc_item_show(GocItem * item)529 goc_item_show (GocItem *item)
530 {
531 	goc_item_set_visible (item, TRUE);
532 }
533 
534 /**
535  * goc_item_hide:
536  * @item: #GocItem
537  *
538  * Hides @item.
539  **/
540 void
goc_item_hide(GocItem * item)541 goc_item_hide (GocItem *item)
542 {
543 	goc_item_set_visible (item, FALSE);
544 }
545 
546 /**
547  * goc_item_is_visible:
548  * @item: #GocItem
549  *
550  * Returns: %TRUE if @item is visible.
551  **/
552 gboolean
goc_item_is_visible(GocItem * item)553 goc_item_is_visible (GocItem *item)
554 {
555 	g_return_val_if_fail (GOC_IS_ITEM (item), FALSE);
556 	return item->visible;
557 }
558 
559 /**
560  * goc_item_get_bounds:
561  * @item: #GocItem
562  * @x0: where to store the lowest horizontal bound
563  * @y0: where to store the lowest vertical bound
564  * @x1: where to store the highest horizontal bound
565  * @y1: where to store the highest vertical bound
566  *
567  * Retrieves the bounds of @item in canvas coordinates.
568  **/
569 void
goc_item_get_bounds(GocItem const * item,double * x0,double * y0,double * x1,double * y1)570 goc_item_get_bounds (GocItem const *item, double *x0, double *y0, double *x1, double *y1)
571 {
572 	g_return_if_fail (GOC_IS_ITEM (item));
573 	if (!item->cached_bounds) {
574 		_goc_item_update_bounds (GOC_ITEM (item)); /* don't care about const */
575 	}
576 	*x0 = item->x0;
577 	*y0 = item->y0;
578 	*x1 = item->x1;
579 	*y1 = item->y1;
580 }
581 
582 /**
583  * goc_item_bounds_changed:
584  * @item: #GocItem
585  *
586  * This function needs to be called each time the bounds of @item change. It
587  * is normally called from inside the implementation of items derived classes.
588  **/
589 void
goc_item_bounds_changed(GocItem * item)590 goc_item_bounds_changed (GocItem *item)
591 {
592 	g_return_if_fail (GOC_IS_ITEM (item));
593 	if (item->cached_bounds) {
594 		GocItem *cur = item;
595 		goc_item_invalidate (item);
596 		do {
597 			cur->cached_bounds = FALSE;
598 			cur = GOC_ITEM (cur->parent);
599 		} while (cur);
600 	}
601 	goc_item_invalidate (item);
602 }
603 
604 /**
605  * goc_item_grab:
606  * @item: #GocItem
607  *
608  * Grabs the item. This function will fail if another item is grabbed.
609  **/
610 void
goc_item_grab(GocItem * item)611 goc_item_grab (GocItem *item)
612 {
613 	if (item == goc_canvas_get_grabbed_item (item->canvas))
614 		return;
615 	g_return_if_fail (GOC_IS_ITEM (item));
616 	goc_canvas_grab_item (item->canvas, item);
617 }
618 
619 /**
620  * goc_item_ungrab:
621  * @item: #GocItem
622  *
623  * Ungrabs the item. This function will fail if @item is not grabbed.
624  **/
625 void
goc_item_ungrab(GocItem * item)626 goc_item_ungrab	(GocItem *item)
627 {
628 	g_return_if_fail (item == goc_canvas_get_grabbed_item (item->canvas));
629 	goc_canvas_ungrab_item (item->canvas);
630 }
631 
632 static void
goc_item_reordered(GocItem * item,int n)633 goc_item_reordered (GocItem *item, int n)
634 {
635 #ifdef GOFFICE_WITH_GTK
636 	GocGroup *group = item->parent;
637 	int cur = goc_group_find_child (group, item);
638 	while (TRUE) {
639 		GocItem *child = goc_group_get_child (group, cur);
640 		GdkWindow *window;
641 		if (!child)
642 			break;
643 		window = goc_item_get_window (child);
644 		if (window) {
645 			if (n > 0)
646 				gdk_window_raise (window);
647 			else
648 				gdk_window_lower (window);
649 		}
650 		cur += (n > 0 ? +1 : -1);
651 	}
652 #endif
653 }
654 
655 /**
656  * goc_item_raise:
657  * @item: #GocItem
658  * @n: the rank change
659  *
660  * Raises @item by @n steps inside its parent #GocGroup (or less if the list
661  * is too short) in the item list so that it is displayed nearer the top of
662  * the items stack.
663  **/
664 void
goc_item_raise(GocItem * item,int n)665 goc_item_raise (GocItem *item, int n)
666 {
667 	GocGroup *group = item->parent;
668 	GPtrArray *children = goc_group_get_children (group);
669 	int len = children->len;
670 	int ix = goc_group_find_child (group, item);
671 	if (n > len - 1 - ix) n = len - 1 - ix;
672 	g_ptr_array_remove_index (children, ix);
673 	g_ptr_array_insert (children, ix + n, item);
674 	goc_item_invalidate (item);
675 	goc_item_reordered (item, n);
676 	g_ptr_array_unref (children);
677 }
678 
679 /**
680  * goc_item_lower:
681  * @item: #GocItem
682  * @n: the rank change
683  *
684  * Lowers @item by @n steps inside its parent #GocGroup (or less if the list
685  * is too short) in the item list so that it is displayed more deeply in the
686  * items stack.
687  **/
688 void
goc_item_lower(GocItem * item,int n)689 goc_item_lower (GocItem *item, int n)
690 {
691 	GocGroup *group = item->parent;
692 	GPtrArray *children = goc_group_get_children (group);
693 	int ix = goc_group_find_child (group, item);
694 	if (n > ix) n = ix;
695 
696 	g_ptr_array_remove_index (children, ix);
697 	g_ptr_array_insert (children, ix - n, item);
698 	goc_item_invalidate (item);
699 	goc_item_reordered (item, -n);
700 	g_ptr_array_unref (children);
701 }
702 
703 /**
704  * goc_item_lower_to_bottom:
705  * @item: #GocItem
706  *
707  * Lowers @item to bottom inside its parent #GocGroup so that it will be at
708  * least partly hidden by any overlapping item.
709  **/
710 void
goc_item_lower_to_bottom(GocItem * item)711 goc_item_lower_to_bottom (GocItem *item)
712 {
713 	goc_item_lower (item, G_MAXINT);
714 }
715 
716 /**
717  * goc_item_raise_to_top:
718  * @item: #GocItem
719  *
720  * Raises @item to front so that it becomes the toplevel item inside
721  * its parent #GocGroup.
722  **/
723 void
goc_item_raise_to_top(GocItem * item)724 goc_item_raise_to_top (GocItem *item)
725 {
726 	goc_item_raise (item, G_MAXINT);
727 }
728 
729 void
_goc_item_realize(GocItem * item)730 _goc_item_realize (GocItem *item)
731 {
732 	if (!item->realized) {
733 		GocItemClass *klass = GOC_ITEM_GET_CLASS (item);
734 		klass->realize (item);
735 	}
736 }
737 
738 void
_goc_item_unrealize(GocItem * item)739 _goc_item_unrealize (GocItem *item)
740 {
741 	if (item->realized) {
742 		GocItemClass *klass = GOC_ITEM_GET_CLASS (item);
743 		klass->unrealize (item);
744 	}
745 }
746 
747 /**
748  * goc_item_get_parent:
749  * @item: #GocItem
750  *
751  * Returns: (transfer none): The item parent #GocGroup.
752  **/
753 GocGroup *
goc_item_get_parent(GocItem * item)754 goc_item_get_parent (GocItem *item)
755 {
756 	return item->parent;
757 }
758 
759 #ifdef GOFFICE_WITH_GTK
760 /**
761  * goc_item_get_window:
762  * @item: #GocItem
763  *
764  * Returns: (transfer none): The #GdkWindow associated with the item if any or
765  * NULL. Only #GocWidget owns a #GdkWindow.
766  **/
767 GdkWindow *
goc_item_get_window(GocItem * item)768 goc_item_get_window (GocItem *item)
769 {
770 	GocItemClass *klass = GOC_ITEM_GET_CLASS (item);
771 	return (klass->get_window)? klass->get_window (item): NULL;
772 }
773 #endif
774 
775 /**
776  * goc_item_set_operator: (skip)
777  * @item: #GocItem
778  * @op: #cairo_operator_t
779  *
780  * Set the operator used when drawing the item.
781  */
782 void
goc_item_set_operator(GocItem * item,cairo_operator_t op)783 goc_item_set_operator  (GocItem *item, cairo_operator_t op)
784 {
785 	item->op = op;
786 	goc_item_invalidate (item);
787 }
788 
789 /**
790  * goc_item_get_operator: (skip)
791  * @item: #GocItem
792  *
793  * Returns: the operator used when drawing the item.
794  */
795 cairo_operator_t
goc_item_get_operator(GocItem * item)796 goc_item_get_operator  (GocItem *item)
797 {
798 	return item->op;
799 }
800 
801 #define matrix_epsilon 1e-12
802 /**
803  * goc_item_set_transform: (skip)
804  * @item: #GocItem
805  * @m: #cairo_matrix_t
806  *
807  * Set the matrix used to transform the item.
808  */
809 void
goc_item_set_transform(GocItem * item,cairo_matrix_t * m)810 goc_item_set_transform (GocItem *item, cairo_matrix_t *m)
811 {
812 	item->transformed = fabs (m->xx - 1.) > matrix_epsilon
813 						|| fabs (m->xy) > matrix_epsilon
814 						|| fabs (m->xy) > matrix_epsilon
815 						|| fabs (m->yx) > matrix_epsilon
816 						|| fabs (m->yy - 1.) > matrix_epsilon
817 						|| fabs (m->x0) > matrix_epsilon
818 						|| fabs (m->y0) > matrix_epsilon;
819 	if (item->transformed)
820 		item->transform = *m;
821 	else
822 		cairo_matrix_init_identity (&item->transform);
823 }
824 
825 void
_goc_item_transform(GocItem const * item,cairo_t * cr,gboolean scaled)826 _goc_item_transform (GocItem const *item, cairo_t *cr, gboolean scaled)
827 {
828 	double scale = item->canvas? item->canvas->pixels_per_unit: 1.0;
829 	cairo_matrix_t m = item->transform, buf,
830 		sc = {scale, 0., 0., scale, 0., 0.};
831 	while ((item = GOC_ITEM (item->parent)))
832 		if (item->transformed) {
833 			cairo_matrix_multiply (&buf, &m, &item->transform);
834 			m = buf;
835 		}
836 	if (scaled) {
837 		cairo_matrix_multiply (&buf, &m, &sc);
838 		m = buf;
839 	}
840 	cairo_transform (cr, &m);
841 }
842 
843 #ifdef GOFFICE_WITH_GTK
844 
845 static void
cb_hierarchy_changed(const GocItem * item)846 cb_hierarchy_changed (const GocItem *item)
847 {
848 	GtkStyleContext *context = goc_item_get_style_context (item);
849 	GtkStyleContext *pcontext;
850 	GtkWidgetPath *path;
851 
852 	if (item->parent) {
853 		pcontext = goc_item_get_style_context (GOC_ITEM (item->parent));
854 	} else if (item->canvas &&
855 		   GOC_ITEM (item->canvas->root) == item) {
856 		pcontext = gtk_widget_get_style_context (GTK_WIDGET (item->canvas));
857 	} else {
858 		pcontext = NULL;
859 	}
860 	if (pcontext)
861 		path = gtk_widget_path_copy (gtk_style_context_get_path (pcontext));
862 	else
863 		path = gtk_widget_path_new ();
864 
865 	gtk_widget_path_append_type (path, G_TYPE_FROM_INSTANCE (item));
866 #if GTK_CHECK_VERSION(3,20,0)
867 	gtk_widget_path_iter_set_object_name (path, -1, G_OBJECT_TYPE_NAME (item));
868 #endif
869 	gtk_style_context_set_path (context, path);
870 	gtk_widget_path_free (path);
871 	gtk_style_context_set_parent (context, pcontext);
872 }
873 
874 
875 /**
876  * goc_item_get_style_context:
877  * @item: #GocItem
878  *
879  * Returns: (transfer none): The style context to use for the item.
880  */
881 GtkStyleContext *
goc_item_get_style_context(const GocItem * item)882 goc_item_get_style_context (const GocItem *item)
883 {
884 	GtkStyleContext *context;
885 	g_return_val_if_fail (GOC_IS_ITEM (item), NULL);
886 
887 	context = g_object_get_qdata (G_OBJECT (item), quark_style_context);
888 	if (!context) {
889 		context = gtk_style_context_new ();
890 		g_object_set_qdata_full (G_OBJECT (item),
891 					 quark_style_context,
892 					 context,
893 					 g_object_unref);
894 
895 		g_signal_connect (G_OBJECT (item),
896 				  "notify::parent",
897 				  G_CALLBACK (cb_hierarchy_changed),
898 				  NULL);
899 		g_signal_connect (G_OBJECT (item),
900 				  "notify::canvas",
901 				  G_CALLBACK (cb_hierarchy_changed),
902 				  NULL);
903 		cb_hierarchy_changed (item);
904 	}
905 
906 	return context;
907 }
908 #endif
909 
910 /**
911  * goc_item_duplicate:
912  * @item: #GocItem
913  * @parent: #GocGroup
914  *
915  * Creates a new GocItem identical to @item inside the parent GocGroup if not
916  * NULL.
917  *
918  * Returns: (transfer none): The duplicated item or NULL if the duplication was
919  * not possible.
920  */
921 GocItem*
goc_item_duplicate(GocItem * item,GocGroup * parent)922 goc_item_duplicate (GocItem *item, GocGroup *parent)
923 {
924 	GocItemClass *klass;
925 	GocItem *ret;
926 
927 	g_return_val_if_fail (GOC_IS_ITEM (item), NULL);
928 
929 	klass = GOC_ITEM_GET_CLASS (item);
930 	if (klass->copy == NULL)
931 		return NULL;
932 
933 	ret = GOC_ITEM ((parent)? goc_item_new (parent, G_OBJECT_TYPE (item), NULL):
934 							  g_object_new (G_OBJECT_TYPE (item), NULL));
935 
936 	klass->copy (ret, item);
937 	return ret;
938 }
939 
940 /**
941  * goc_item_copy:
942  * source: #GocItem
943  * dest: #GocItem
944  *
945  * Copies @source properties to @dest. The two items must be of the same type
946  * and their common class needs a @copy member.
947  **/
948 void
goc_item_copy(GocItem * dest,GocItem * source)949 goc_item_copy (GocItem *dest, GocItem *source)
950 {
951 	GocItemClass *klass = GOC_ITEM_GET_CLASS (source);
952 
953 	g_return_if_fail (GOC_IS_ITEM (source));
954 	g_return_if_fail (GOC_IS_ITEM (dest));
955 	g_return_if_fail (klass == GOC_ITEM_GET_CLASS (dest));
956 	g_return_if_fail (klass->copy);
957 	dest->visible = source->visible;
958 	dest->op = source->op;
959 	dest->transform = source->transform;
960 	dest->transformed = source->transformed;
961 	klass->copy (dest, source);
962 }
963