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