1 /*
2 * sheet-object.c: Implements the sheet object manipulation for Gnumeric
3 *
4 * Author:
5 * Miguel de Icaza (miguel@kernel.org)
6 * Michael Meeks (mmeeks@gnu.org)
7 * Jody Goldberg (jody@gnome.org)
8 */
9 #include <gnumeric-config.h>
10 #include <glib/gi18n-lib.h>
11 #include <gnumeric.h>
12 #include <sheet-object.h>
13
14 #include <sheet.h>
15 #include <dependent.h>
16 #include <sheet-view.h>
17 #include <sheet-control.h>
18 #include <sheet-control-gui.h>
19 #include <sheet-private.h>
20 #include <dialogs/dialogs.h>
21 #include <sheet-object-impl.h>
22 #include <expr.h>
23 #include <ranges.h>
24 #include <commands.h>
25 #include <gui-util.h>
26
27 #include <gnm-pane-impl.h>
28 #include <gnm-so-line.h>
29 #include <gnm-so-filled.h>
30 #include <sheet-control-gui-priv.h>
31 #include <sheet-object-cell-comment.h>
32 #include <sheet-object-widget.h>
33 #include <sheet-object-graph.h>
34 #include <sheet-object-image.h>
35 #include <sheet-filter-combo.h>
36 #include <wbc-gtk-impl.h>
37 #include <graph.h>
38 #include <print.h>
39 #include <goffice/goffice.h>
40 #include <application.h>
41 #include <gutils.h>
42
43 #include <libxml/globals.h>
44 #include <gsf/gsf-impl-utils.h>
45
46 #include <string.h>
47
48 static gboolean debug_sheet_objects;
49
50 static guint so_create_view_src;
51 static GPtrArray *so_create_view_sos;
52
53 /* GType code for SheetObjectAnchor */
54 static SheetObjectAnchor *
sheet_object_anchor_copy(SheetObjectAnchor * soa)55 sheet_object_anchor_copy (SheetObjectAnchor * soa)
56 {
57 SheetObjectAnchor *res = g_new (SheetObjectAnchor, 1);
58 *res = *soa;
59 return res;
60 }
61
62 GType
sheet_object_anchor_get_type(void)63 sheet_object_anchor_get_type (void)
64 {
65 static GType t = 0;
66
67 if (t == 0) {
68 t = g_boxed_type_register_static ("SheetObjectAnchor",
69 (GBoxedCopyFunc)sheet_object_anchor_copy,
70 (GBoxedFreeFunc)g_free);
71 }
72 return t;
73 }
74
75 GType
gnm_sheet_object_anchor_mode_get_type(void)76 gnm_sheet_object_anchor_mode_get_type (void)
77 {
78 static GType etype = 0;
79 if (etype == 0) {
80 static GEnumValue const values[] = {
81 { GNM_SO_ANCHOR_TWO_CELLS, "GNM_SO_ANCHOR_TWO_CELLS", "two-cells" },
82 { GNM_SO_ANCHOR_ONE_CELL, "GNM_SO_ANCHOR_ONE_CELL", "one-cell" },
83 { GNM_SO_ANCHOR_ABSOLUTE, "GNM_SO_ANCHOR_ABSOLUTE", "absolute" },
84 { 0, NULL, NULL }
85 };
86 etype = g_enum_register_static ("GnmSOAnchorMode", values);
87 }
88 return etype;
89 }
90
91
92 /* Returns the class for a SheetObject */
93 #define SO_CLASS(so) GNM_SO_CLASS(G_OBJECT_GET_CLASS(so))
94
95 enum {
96 SO_PROP_0 = 0,
97 SO_PROP_NAME
98 };
99
100 enum {
101 BOUNDS_CHANGED,
102 UNREALIZED,
103 LAST_SIGNAL
104 };
105 static guint signals [LAST_SIGNAL] = { 0 };
106 static GObjectClass *parent_klass;
107 static GQuark sov_so_quark;
108 static GQuark sov_container_quark;
109
110 void
sheet_object_set_print_flag(SheetObject * so,gboolean * print)111 sheet_object_set_print_flag (SheetObject *so, gboolean *print)
112 {
113 g_return_if_fail (GNM_IS_SO (so));
114
115 if (*print)
116 so->flags |= SHEET_OBJECT_PRINT;
117 else
118 so->flags &= ~SHEET_OBJECT_PRINT;
119 }
120
121 gboolean
sheet_object_get_print_flag(SheetObject * so)122 sheet_object_get_print_flag (SheetObject *so)
123 {
124 return (so->flags & SHEET_OBJECT_PRINT) != 0;
125 }
126
127
128 static void
cb_so_size_position(SheetObject * so,SheetControl * sc)129 cb_so_size_position (SheetObject *so, SheetControl *sc)
130 {
131 WBCGtk *wbcg;
132
133 g_return_if_fail (GNM_IS_SCG (sc));
134
135 wbcg = scg_wbcg ((SheetControlGUI *)sc);
136
137 if (wbcg->edit_line.guru != NULL) {
138 GtkWidget *w = wbcg->edit_line.guru;
139 wbc_gtk_detach_guru (wbcg);
140 gtk_widget_destroy (w);
141 }
142
143 dialog_so_size (wbcg, G_OBJECT (so));
144 }
145
146 static void
cb_so_snap_to_grid(SheetObject * so,SheetControl * sc)147 cb_so_snap_to_grid (SheetObject *so, SheetControl *sc)
148 {
149 SheetObjectAnchor *snapped =
150 sheet_object_anchor_dup (sheet_object_get_anchor (so));
151 GnmSOAnchorMode mode = snapped->mode;
152 snapped->mode = GNM_SO_ANCHOR_TWO_CELLS;
153 snapped->offset[0] = snapped->offset[1] = 0.;
154 snapped->offset[2] = snapped->offset[3] = 1.;
155 if (mode != GNM_SO_ANCHOR_TWO_CELLS) {
156 double pts[4];
157 sheet_object_anchor_to_pts (snapped, so->sheet, pts);
158 snapped->mode = mode;
159 sheet_object_pts_to_anchor (snapped, so->sheet, pts);
160 }
161 cmd_objects_move (sc_wbc (sc),
162 g_slist_prepend (NULL, so),
163 g_slist_prepend (NULL, snapped),
164 FALSE, _("Snap object to grid"));
165 }
166 static void
cb_so_pull_to_front(SheetObject * so,SheetControl * sc)167 cb_so_pull_to_front (SheetObject *so, SheetControl *sc)
168 {
169 cmd_object_raise (sc_wbc (sc), so, cmd_object_pull_to_front);
170 }
171 static void
cb_so_pull_forward(SheetObject * so,SheetControl * sc)172 cb_so_pull_forward (SheetObject *so, SheetControl *sc)
173 {
174 cmd_object_raise (sc_wbc (sc), so, cmd_object_pull_forward);
175 }
176 static void
cb_so_push_backward(SheetObject * so,SheetControl * sc)177 cb_so_push_backward (SheetObject *so, SheetControl *sc)
178 {
179 cmd_object_raise (sc_wbc (sc), so, cmd_object_push_backward);
180 }
181 static void
cb_so_push_to_back(SheetObject * so,SheetControl * sc)182 cb_so_push_to_back (SheetObject *so, SheetControl *sc)
183 {
184 cmd_object_raise (sc_wbc (sc), so, cmd_object_push_to_back);
185 }
186 static void
cb_so_delete(SheetObject * so,SheetControl * sc)187 cb_so_delete (SheetObject *so, SheetControl *sc)
188 {
189 cmd_objects_delete (sc_wbc (sc),
190 go_slist_create (so, NULL), NULL);
191 }
192 static void
cb_so_print(SheetObject * so,SheetControl * sc)193 cb_so_print (SheetObject *so, SheetControl *sc)
194 {
195 GPtrArray *a = g_ptr_array_new ();
196 g_ptr_array_add (a, so);
197 gnm_print_so (sc_wbc (sc), a, NULL);
198 g_ptr_array_unref (a);
199 }
200 void
sheet_object_get_editor(SheetObject * so,SheetControl * sc)201 sheet_object_get_editor (SheetObject *so, SheetControl *sc)
202 {
203 WBCGtk *wbcg;
204
205 g_return_if_fail (GNM_IS_SO (so));
206 g_return_if_fail (SO_CLASS (so));
207 g_return_if_fail (GNM_IS_SCG (sc));
208
209 wbcg = scg_wbcg ((SheetControlGUI *)sc);
210
211 if (wbcg->edit_line.guru != NULL) {
212 GtkWidget *w = wbcg->edit_line.guru;
213 wbc_gtk_detach_guru (wbcg);
214 gtk_widget_destroy (w);
215 }
216
217 if (SO_CLASS(so)->user_config)
218 SO_CLASS(so)->user_config (so, sc);
219 }
220 static void
cb_so_cut(SheetObject * so,SheetControl * sc)221 cb_so_cut (SheetObject *so, SheetControl *sc)
222 {
223 gnm_app_clipboard_cut_copy_obj (sc_wbc (sc), TRUE, sc_view (sc),
224 go_slist_create (so, NULL));
225 }
226 static void
cb_so_copy(SheetObject * so,SheetControl * sc)227 cb_so_copy (SheetObject *so, SheetControl *sc)
228 {
229 gnm_app_clipboard_cut_copy_obj (sc_wbc (sc), FALSE, sc_view (sc),
230 go_slist_create (so, NULL));
231 }
232
233 gboolean
sheet_object_can_print(SheetObject const * so)234 sheet_object_can_print (SheetObject const *so)
235 {
236 g_return_val_if_fail (GNM_IS_SO (so), FALSE);
237 return (so->flags & SHEET_OBJECT_IS_VISIBLE) &&
238 (so->flags & SHEET_OBJECT_PRINT) &&
239 SO_CLASS (so)->draw_cairo != NULL;
240 }
241
242 gboolean
sheet_object_can_resize(SheetObject const * so)243 sheet_object_can_resize (SheetObject const *so)
244 {
245 g_return_val_if_fail (GNM_IS_SO (so), FALSE);
246 return so->flags & SHEET_OBJECT_CAN_RESIZE;
247 }
248
249 gboolean
sheet_object_can_edit(SheetObject const * so)250 sheet_object_can_edit (SheetObject const *so)
251 {
252 g_return_val_if_fail (GNM_IS_SO (so), FALSE);
253 return so->flags & SHEET_OBJECT_CAN_EDIT;
254 }
255
256 static gboolean
sheet_object_can_prop(SheetObject const * so)257 sheet_object_can_prop (SheetObject const *so)
258 {
259 g_return_val_if_fail (GNM_IS_SO (so), FALSE);
260 return (sheet_object_can_edit (so) && (SO_CLASS(so)->user_config != NULL));
261 }
262
263 static void
sheet_object_populate_menu_real(SheetObject * so,GPtrArray * actions)264 sheet_object_populate_menu_real (SheetObject *so, GPtrArray *actions)
265 {
266 unsigned i;
267 if (so->sheet->sheet_type == GNM_SHEET_OBJECT) {
268 static SheetObjectAction const so_actions [] = {
269 { "gtk-properties", NULL, NULL, 0, sheet_object_get_editor, sheet_object_can_prop},
270 { NULL, NULL, NULL, 0, NULL, NULL },
271 { "edit-copy", N_("_Copy"), NULL, 0, cb_so_copy, NULL },
272 };
273 for (i = 0 ; i < G_N_ELEMENTS (so_actions); i++)
274 g_ptr_array_add (actions, (gpointer) (so_actions + i));
275 } else {
276 static SheetObjectAction const so_actions [] = {
277 { GTK_STOCK_PROPERTIES, NULL, NULL, 0, sheet_object_get_editor, sheet_object_can_prop},
278 { NULL, NULL, NULL, 0, NULL, NULL },
279 #warning "Two highly dubious icon names here"
280 { GTK_STOCK_LEAVE_FULLSCREEN, N_("Size _& Position"), NULL, 0, cb_so_size_position, NULL },
281 { GTK_STOCK_FULLSCREEN, N_("_Snap to Grid"), NULL, 0, cb_so_snap_to_grid, NULL },
282 { NULL, N_("_Order"), NULL, 1, NULL, NULL },
283 { NULL, N_("Pul_l to Front"), NULL, 0, cb_so_pull_to_front, NULL },
284 { NULL, N_("Pull _Forward"), NULL, 0, cb_so_pull_forward, NULL },
285 { NULL, N_("Push _Backward"), NULL, 0, cb_so_push_backward, NULL },
286 { NULL, N_("Pus_h to Back"), NULL, 0, cb_so_push_to_back, NULL },
287 { NULL, NULL, NULL, -1, NULL, NULL },
288 { NULL, NULL, NULL, 0, NULL, NULL },
289 { "edit-cut", N_("Cu_t"), NULL, 0, cb_so_cut, NULL },
290 { "edit-copy", N_("_Copy"), NULL, 0, cb_so_copy, NULL },
291 { "edit-delete", N_("_Delete"), NULL, 0, cb_so_delete, NULL },
292 { NULL, NULL, NULL, 0, NULL, NULL },
293 { "document-print", N_("Print"), NULL, 0, cb_so_print, sheet_object_can_print},
294 };
295 for (i = 0; i < G_N_ELEMENTS (so_actions); i++)
296 g_ptr_array_add (actions, (gpointer) (so_actions + i));
297 }
298 }
299
300 /**
301 * sheet_object_populate_menu:
302 * @so: #SheetObject
303 * @actions: (inout) (transfer full) (element-type SheetObjectAction): #GPtrArray
304 *
305 * Get a list of the actions that can be performed on @so
306 **/
307 void
sheet_object_populate_menu(SheetObject * so,GPtrArray * actions)308 sheet_object_populate_menu (SheetObject *so, GPtrArray *actions)
309 {
310 g_return_if_fail (NULL != so);
311
312 GNM_SO_CLASS (G_OBJECT_GET_CLASS(so))->populate_menu (so, actions);
313 }
314
315 void
sheet_object_set_name(SheetObject * so,const char * name)316 sheet_object_set_name (SheetObject *so, const char *name)
317 {
318 if (name == so->name)
319 return;
320
321 g_free (so->name);
322 so->name = g_strdup (name);
323
324 g_object_notify (G_OBJECT (so), "name");
325 }
326
327 static void
sheet_object_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)328 sheet_object_get_property (GObject *obj, guint param_id,
329 GValue *value, GParamSpec *pspec)
330 {
331 SheetObject *so = GNM_SO (obj);
332
333 switch (param_id) {
334 case SO_PROP_NAME:
335 g_value_set_string (value, so->name);
336 break;
337 default:
338 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
339 break;
340 }
341 }
342
343 static void
sheet_object_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)344 sheet_object_set_property (GObject *obj, guint param_id,
345 GValue const *value, GParamSpec *pspec)
346 {
347 SheetObject *so = GNM_SO (obj);
348
349 switch (param_id) {
350 case SO_PROP_NAME:
351 sheet_object_set_name (so, g_value_get_string (value));
352 break;
353 default:
354 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
355 return;
356 }
357 }
358
359
360 static void
sheet_object_finalize(GObject * object)361 sheet_object_finalize (GObject *object)
362 {
363 SheetObject *so = GNM_SO (object);
364 if (so->sheet != NULL)
365 sheet_object_clear_sheet (so);
366 g_free (so->name);
367 parent_klass->finalize (object);
368 }
369
370 static void
sheet_object_init(GObject * object)371 sheet_object_init (GObject *object)
372 {
373 int i;
374 SheetObject *so = GNM_SO (object);
375
376 so->sheet = NULL;
377 so->flags = SHEET_OBJECT_IS_VISIBLE | SHEET_OBJECT_PRINT |
378 SHEET_OBJECT_CAN_RESIZE | SHEET_OBJECT_CAN_EDIT |
379 SHEET_OBJECT_MOVE_WITH_CELLS | SHEET_OBJECT_SIZE_WITH_CELLS;
380
381 /* Store the logical position as A1 */
382 so->anchor.cell_bound.start.col = so->anchor.cell_bound.start.row = 0;
383 so->anchor.cell_bound.end.col = so->anchor.cell_bound.end.row = 1;
384 so->anchor.base.direction = GOD_ANCHOR_DIR_UNKNOWN;
385
386 for (i = 4; i-- > 0 ;)
387 so->anchor.offset [i] = 0.;
388 }
389
390 static void
so_default_size(G_GNUC_UNUSED SheetObject const * so,double * width,double * height)391 so_default_size (G_GNUC_UNUSED SheetObject const *so, double *width, double *height)
392 {
393 /* Provide some defaults (derived classes may want to override) */
394 *width = 72.;
395 *height = 72.;
396 }
397
398 static void
sheet_object_class_init(GObjectClass * klass)399 sheet_object_class_init (GObjectClass *klass)
400 {
401 SheetObjectClass *sheet_object_class = GNM_SO_CLASS (klass);
402
403 parent_klass = g_type_class_peek_parent (klass);
404
405 klass->finalize = sheet_object_finalize;
406 klass->get_property = sheet_object_get_property;
407 klass->set_property = sheet_object_set_property;
408
409 sheet_object_class->populate_menu = sheet_object_populate_menu_real;
410 sheet_object_class->user_config = NULL;
411 sheet_object_class->rubber_band_directly = FALSE;
412 sheet_object_class->interactive = FALSE;
413 sheet_object_class->default_size = so_default_size;
414 sheet_object_class->xml_export_name = NULL;
415 sheet_object_class->foreach_dep = NULL;
416
417 g_object_class_install_property
418 (klass, SO_PROP_NAME,
419 g_param_spec_string ("name", NULL, NULL, NULL,
420 GSF_PARAM_STATIC | G_PARAM_READWRITE));
421
422 signals [BOUNDS_CHANGED] = g_signal_new ("bounds-changed",
423 GNM_SO_TYPE,
424 G_SIGNAL_RUN_LAST,
425 G_STRUCT_OFFSET (SheetObjectClass, bounds_changed),
426 NULL, NULL,
427 g_cclosure_marshal_VOID__VOID,
428 G_TYPE_NONE, 0);
429 signals [UNREALIZED] = g_signal_new ("unrealized",
430 GNM_SO_TYPE,
431 G_SIGNAL_RUN_LAST,
432 G_STRUCT_OFFSET (SheetObjectClass, unrealized),
433 NULL, NULL,
434 g_cclosure_marshal_VOID__VOID,
435 G_TYPE_NONE, 0);
436 }
437
GSF_CLASS(SheetObject,sheet_object,sheet_object_class_init,sheet_object_init,G_TYPE_OBJECT)438 GSF_CLASS (SheetObject, sheet_object,
439 sheet_object_class_init, sheet_object_init,
440 G_TYPE_OBJECT)
441
442 /**
443 * sheet_object_get_view:
444 * @so: #SheetObject
445 * @container: #SheetObjectViewContainer
446 *
447 * Returns: (transfer none) (nullable): the found #SheetObjectView or %NULL.
448 **/
449 SheetObjectView *
450 sheet_object_get_view (SheetObject const *so, SheetObjectViewContainer *container)
451 {
452 GList *l;
453
454 g_return_val_if_fail (GNM_IS_SO (so), NULL);
455
456 for (l = so->realized_list; l; l = l->next) {
457 SheetObjectView *view = GNM_SO_VIEW (l->data);
458 if (container == g_object_get_qdata (G_OBJECT (view), sov_container_quark))
459 return view;
460 }
461
462 return NULL;
463 }
464
465 /**
466 * sheet_object_update_bounds:
467 * @so: The sheet object
468 * @p: (nullable): A position marking the top left of the region
469 * needing relocation (default == A1)
470 *
471 * update the bounds of an object that intersects the region whose top left
472 * is @p. This is used when an objects position is anchored to cols/rows
473 * and they change position.
474 **/
475 void
sheet_object_update_bounds(SheetObject * so,GnmCellPos const * pos)476 sheet_object_update_bounds (SheetObject *so, GnmCellPos const *pos)
477 {
478 gboolean is_hidden = TRUE;
479 int i, end;
480
481 g_return_if_fail (GNM_IS_SO (so));
482
483 if (pos != NULL &&
484 so->anchor.cell_bound.end.col < pos->col &&
485 so->anchor.cell_bound.end.row < pos->row)
486 return;
487
488 if (so->anchor.mode != GNM_SO_ANCHOR_TWO_CELLS) {
489 double x[4];
490 sheet_object_anchor_to_pts (&so->anchor, so->sheet, x);
491 sheet_object_pts_to_anchor (&so->anchor, so->sheet, x);
492 }
493
494 switch (so->anchor.mode) {
495 default:
496 case GNM_SO_ANCHOR_TWO_CELLS:
497 /* Are all cols hidden ? */
498 end = so->anchor.cell_bound.end.col;
499 i = so->anchor.cell_bound.start.col;
500 while (i <= end && is_hidden)
501 is_hidden &= sheet_col_is_hidden (so->sheet, i++);
502
503 /* Are all rows hidden ? */
504 if (!is_hidden) {
505 is_hidden = TRUE;
506 end = so->anchor.cell_bound.end.row;
507 i = so->anchor.cell_bound.start.row;
508 while (i <= end && is_hidden)
509 is_hidden &= sheet_row_is_hidden (so->sheet, i++);
510 }
511 break;
512 case GNM_SO_ANCHOR_ONE_CELL:
513 /* Should we really hide if the row or column is hidden? */
514 is_hidden = sheet_col_is_hidden (so->sheet, so->anchor.cell_bound.start.col) ||
515 sheet_row_is_hidden (so->sheet, so->anchor.cell_bound.start.row);
516 break;
517 case GNM_SO_ANCHOR_ABSOLUTE:
518 is_hidden = FALSE;
519 break;
520 }
521 if (is_hidden)
522 so->flags &= ~SHEET_OBJECT_IS_VISIBLE;
523 else
524 so->flags |= SHEET_OBJECT_IS_VISIBLE;
525
526 g_signal_emit (so, signals [BOUNDS_CHANGED], 0);
527 }
528
529 /**
530 * sheet_object_get_sheet:
531 * @so: #SheetObject
532 *
533 * A small utility to help keep the implementation of SheetObjects modular.
534 * Returns: (transfer none): the #Sheet owning the object.
535 **/
536 Sheet *
sheet_object_get_sheet(SheetObject const * so)537 sheet_object_get_sheet (SheetObject const *so)
538 {
539 g_return_val_if_fail (GNM_IS_SO (so), NULL);
540
541 return so->sheet;
542 }
543
544 static gboolean
cb_create_views(void)545 cb_create_views (void)
546 {
547 int pass;
548
549 for (pass = 1; pass <= 3; pass++) {
550 unsigned ui, l = so_create_view_sos->len;
551 for (ui = 0; ui < l; ui++) {
552 SheetObject *so = g_ptr_array_index (so_create_view_sos, ui);
553 SHEET_FOREACH_CONTROL
554 (so->sheet, view, control,
555 if (pass == 2)
556 sc_object_create_view (control, so);
557 else
558 sc_freeze_object_view (control, pass == 1);
559 );
560 }
561 }
562 g_ptr_array_set_size (so_create_view_sos, 0);
563 so_create_view_src = 0;
564 return FALSE;
565 }
566
567 /**
568 * sheet_object_set_sheet:
569 * @so:
570 * @sheet:
571 *
572 * Adds a reference to the object.
573 **/
574 void
sheet_object_set_sheet(SheetObject * so,Sheet * sheet)575 sheet_object_set_sheet (SheetObject *so, Sheet *sheet)
576 {
577 g_return_if_fail (GNM_IS_SO (so));
578 g_return_if_fail (IS_SHEET (sheet));
579
580 if (sheet == so->sheet)
581 return;
582
583 g_return_if_fail (so->sheet == NULL);
584 if (debug_sheet_objects)
585 g_return_if_fail (g_slist_find (sheet->sheet_objects, so) == NULL);
586
587 so->sheet = sheet;
588 if (SO_CLASS (so)->assign_to_sheet &&
589 SO_CLASS (so)->assign_to_sheet (so, sheet)) {
590 so->sheet = NULL;
591 return;
592 }
593
594 g_object_ref (so);
595 sheet->sheet_objects = g_slist_prepend (sheet->sheet_objects, so);
596 /* Update object bounds for absolute and one cell anchored objects */
597 if (so->anchor.mode != GNM_SO_ANCHOR_TWO_CELLS) {
598 double x[4];
599 sheet_object_anchor_to_pts (&so->anchor, sheet, x);
600 sheet_object_pts_to_anchor (&so->anchor, sheet, x);
601 }
602
603 sheet->priv->objects_changed = TRUE;
604
605 g_ptr_array_add (so_create_view_sos, so);
606 if (!so_create_view_src) {
607 so_create_view_src =
608 g_timeout_add_full (
609 G_PRIORITY_DEFAULT_IDLE,
610 0,
611 (GSourceFunc)cb_create_views,
612 NULL,
613 NULL);
614 }
615 }
616
617 /**
618 * sheet_object_clear_sheet:
619 * @so: #SheetObject
620 *
621 * Removes @so from its container, unrealizes all views, disconnects the
622 * associated data and unrefs the object
623 **/
624 void
sheet_object_clear_sheet(SheetObject * so)625 sheet_object_clear_sheet (SheetObject *so)
626 {
627 GSList *ptr;
628 unsigned ui;
629
630 g_return_if_fail (GNM_IS_SO (so));
631
632 if (so->sheet == NULL) /* already removed */
633 return;
634
635 g_return_if_fail (IS_SHEET (so->sheet));
636
637 ptr = g_slist_find (so->sheet->sheet_objects, so);
638 g_return_if_fail (ptr != NULL);
639
640 /* clear any pending attempts to create views */
641 for (ui = 0; ui < so_create_view_sos->len; ui++) {
642 if (so == g_ptr_array_index (so_create_view_sos, ui)) {
643 g_ptr_array_remove_index (so_create_view_sos, ui);
644 break;
645 }
646 }
647
648 while (so->realized_list != NULL) {
649 g_object_set_qdata (G_OBJECT (so->realized_list->data), sov_so_quark, NULL);
650 g_object_unref (so->realized_list->data);
651 so->realized_list = g_list_remove (so->realized_list, so->realized_list->data);
652
653 }
654 g_signal_emit (so, signals [UNREALIZED], 0);
655
656 if (SO_CLASS (so)->remove_from_sheet &&
657 SO_CLASS (so)->remove_from_sheet (so))
658 return;
659
660 so->sheet->sheet_objects = g_slist_remove_link (so->sheet->sheet_objects, ptr);
661 g_slist_free (ptr);
662
663 if (so->anchor.cell_bound.end.col == so->sheet->max_object_extent.col ||
664 so->anchor.cell_bound.end.row == so->sheet->max_object_extent.row)
665 so->sheet->priv->objects_changed = TRUE;
666
667 so->sheet = NULL;
668 g_object_unref (so);
669 }
670
671 static void
cb_sheet_object_invalidate_sheet(GnmDependent * dep,G_GNUC_UNUSED SheetObject * so,gpointer user)672 cb_sheet_object_invalidate_sheet (GnmDependent *dep, G_GNUC_UNUSED SheetObject *so, gpointer user)
673 {
674 Sheet *sheet = user;
675 GnmExprRelocateInfo rinfo;
676 GnmExprTop const *texpr;
677 gboolean save_invalidated = sheet->being_invalidated;
678 gboolean dep_sheet_invalidated = (dep->sheet == sheet);
679
680 if (!dep->texpr)
681 return;
682
683 sheet->being_invalidated = TRUE;
684 rinfo.reloc_type = GNM_EXPR_RELOCATE_INVALIDATE_SHEET;
685 texpr = gnm_expr_top_relocate (dep->texpr, &rinfo, FALSE);
686 if (!texpr && dep_sheet_invalidated) {
687 texpr = dep->texpr;
688 gnm_expr_top_ref (texpr);
689 }
690
691 sheet->being_invalidated = save_invalidated;
692
693 if (texpr) {
694 gboolean was_linked = dependent_is_linked (dep);
695 dependent_set_expr (dep, texpr);
696 gnm_expr_top_unref (texpr);
697 if (dep_sheet_invalidated)
698 dep->sheet = NULL;
699 else if (was_linked)
700 dependent_link (dep);
701 }
702 }
703
704 void
sheet_object_invalidate_sheet(SheetObject * so,Sheet const * sheet)705 sheet_object_invalidate_sheet (SheetObject *so, Sheet const *sheet)
706 {
707 sheet_object_foreach_dep (so, cb_sheet_object_invalidate_sheet,
708 (gpointer)sheet);
709 }
710
711 /**
712 * sheet_object_foreach_dep:
713 * @so: #SheetObject
714 * @func: (scope call): #SheetObjectForeachDepFunc
715 * @user: user data
716 *
717 * Loops over each dependent contained in a sheet object and call the handler.
718 **/
719 void
sheet_object_foreach_dep(SheetObject * so,SheetObjectForeachDepFunc func,gpointer user)720 sheet_object_foreach_dep (SheetObject *so,
721 SheetObjectForeachDepFunc func,
722 gpointer user)
723 {
724 if (SO_CLASS (so)->foreach_dep)
725 SO_CLASS (so)->foreach_dep (so, func, user);
726 }
727
728 /**
729 * sheet_object_new_view:
730 * @so:
731 * @container:
732 *
733 * Asks @so to create a view for @container.
734 * Returns: (transfer none): the new #SheetObjectView.
735 **/
736 SheetObjectView *
sheet_object_new_view(SheetObject * so,SheetObjectViewContainer * container)737 sheet_object_new_view (SheetObject *so, SheetObjectViewContainer *container)
738 {
739 SheetObjectView *view;
740
741 g_return_val_if_fail (GNM_IS_SO (so), NULL);
742 g_return_val_if_fail (NULL != container, NULL);
743
744 view = sheet_object_get_view (so, container);
745 if (view != NULL)
746 return NULL;
747
748 view = SO_CLASS (so)->new_view (so, container);
749
750 if (NULL == view)
751 return NULL;
752
753 g_return_val_if_fail (GNM_IS_SO_VIEW (view), NULL);
754
755 /* Store some useful information */
756 g_object_set_qdata (G_OBJECT (view), sov_so_quark, so);
757 g_object_set_qdata (G_OBJECT (view), sov_container_quark, container);
758 so->realized_list = g_list_prepend (so->realized_list, view);
759 sheet_object_update_bounds (so, NULL);
760
761 return view;
762 }
763
764 /**
765 * sheet_object_draw_cairo:
766 *
767 * Draw a sheet object using cairo.
768 *
769 *
770 * We are assuming that the cairo context is set up so that the top
771 * left of the bounds is (0,0). Note that this
772 * is the real top left cell, not necessarily the cell with to which we are
773 * anchored.
774 *
775 **/
776 void
sheet_object_draw_cairo(SheetObject const * so,cairo_t * cr,gboolean rtl)777 sheet_object_draw_cairo (SheetObject const *so, cairo_t *cr, gboolean rtl)
778 {
779 if (SO_CLASS (so)->draw_cairo) {
780 SheetObjectAnchor const *anchor;
781 double x = 0., y = 0., width, height, cell_width, cell_height;
782 anchor = sheet_object_get_anchor (so);
783 if (anchor->mode == GNM_SO_ANCHOR_ABSOLUTE) {
784 x = anchor->offset[0];
785 y = anchor->offset[1];
786 if (sheet_object_can_resize (so)) {
787 width = anchor->offset[2];
788 height = anchor->offset[3];
789 } else
790 sheet_object_default_size ((SheetObject *) so, &width, &height);
791 if (rtl)
792 x = -x - width;
793 } else {
794 cell_width = sheet_col_get_distance_pts (so->sheet,
795 anchor->cell_bound.start.col,
796 anchor->cell_bound.start.col + 1);
797 cell_height = sheet_row_get_distance_pts (so->sheet,
798 anchor->cell_bound.start.row,
799 anchor->cell_bound.start.row + 1);
800 x = cell_width * anchor->offset[0];
801
802 y = cell_height * anchor->offset[1];
803 if (anchor->mode == GNM_SO_ANCHOR_TWO_CELLS) {
804 cell_width = sheet_col_get_distance_pts (so->sheet,
805 anchor->cell_bound.end.col,
806 anchor->cell_bound.end.col + 1);
807 cell_height = sheet_row_get_distance_pts (so->sheet,
808 anchor->cell_bound.end.row,
809 anchor->cell_bound.end.row + 1);
810
811 if (rtl)
812 x = cell_width * (1 - anchor->offset[2]);
813
814 if (sheet_object_can_resize (so)) {
815 width = sheet_col_get_distance_pts (so->sheet,
816 anchor->cell_bound.start.col,
817 anchor->cell_bound.end.col + 1);
818 height = sheet_row_get_distance_pts (so->sheet,
819 anchor->cell_bound.start.row,
820 anchor->cell_bound.end.row + 1);
821 width -= x;
822 height -= y;
823 width -= cell_width * (1. - ((rtl)? anchor->offset[0]: anchor->offset[2]));
824 height -= cell_height * (1 - anchor->offset[3]);
825 } else
826 sheet_object_default_size ((SheetObject *) so, &width, &height);
827 } else {
828 if (sheet_object_can_resize (so)) {
829 width = anchor->offset[2];
830 height = anchor->offset[3];
831 } else
832 sheet_object_default_size ((SheetObject *) so, &width, &height);
833 if (rtl)
834 x = cell_width * (1 - anchor->offset[0]) - width;
835 }
836 }
837
838 /* we don't need to save/restore cairo, the caller must do it */
839 cairo_translate (cr, x, y);
840 SO_CLASS (so)->draw_cairo (so, cr, width, height);
841 }
842 }
843
844 void
sheet_object_draw_cairo_sized(SheetObject const * so,cairo_t * cr,double width,double height)845 sheet_object_draw_cairo_sized (SheetObject const *so, cairo_t *cr, double width, double height)
846 {
847 SO_CLASS (so)->draw_cairo (so, cr, width, height);
848 }
849
850 /**
851 * sheet_object_get_range:
852 * @so: the #SheetObject to query
853 *
854 * Returns: (transfer none): the #GnmRange used for @so.
855 */
856 GnmRange const *
sheet_object_get_range(SheetObject const * so)857 sheet_object_get_range (SheetObject const *so)
858 {
859 g_return_val_if_fail (GNM_IS_SO (so), NULL);
860
861 return &so->anchor.cell_bound;
862 }
863
864 /**
865 * sheet_object_get_anchor:
866 * @so: #SheetObject
867 *
868 * Returns: (transfer none): the #SheetObjectAnchor for @so.
869 **/
870 SheetObjectAnchor const *
sheet_object_get_anchor(SheetObject const * so)871 sheet_object_get_anchor (SheetObject const *so)
872 {
873 g_return_val_if_fail (GNM_IS_SO (so), NULL);
874
875 return &so->anchor;
876 }
877
878 void
sheet_object_set_anchor(SheetObject * so,SheetObjectAnchor const * anchor)879 sheet_object_set_anchor (SheetObject *so, SheetObjectAnchor const *anchor)
880 {
881 g_return_if_fail (GNM_IS_SO (so));
882
883 so->anchor = *anchor;
884 if (so->sheet != NULL) {
885 so->sheet->priv->objects_changed = TRUE;
886 sheet_object_update_bounds (so, NULL);
887 }
888 }
889
890 SheetObjectAnchor *
sheet_object_anchor_dup(SheetObjectAnchor const * src)891 sheet_object_anchor_dup (SheetObjectAnchor const *src)
892 {
893 SheetObjectAnchor *res = g_memdup (src, sizeof (SheetObjectAnchor));
894 return res;
895 }
896
897 static double
cell_offset_calc_pt(Sheet const * sheet,int i,gboolean is_col,double offset)898 cell_offset_calc_pt (Sheet const *sheet, int i, gboolean is_col, double offset)
899 {
900 ColRowInfo const *cri = sheet_colrow_get_info (sheet, i, is_col);
901 return offset * cri->size_pts;
902 }
903
904 /**
905 * sheet_object_default_size:
906 * @so: The sheet object
907 * @w: (out): a ptr into which to store the default_width.
908 * @h: (out): a ptr into which to store the default_height.
909 *
910 * Measurements are in pts.
911 **/
912 void
sheet_object_default_size(SheetObject * so,double * w,double * h)913 sheet_object_default_size (SheetObject *so, double *w, double *h)
914 {
915 g_return_if_fail (GNM_IS_SO (so));
916 g_return_if_fail (w != NULL);
917 g_return_if_fail (h != NULL);
918
919 SO_CLASS (so)->default_size (so, w, h);
920 }
921
922 /**
923 * sheet_object_position_pts_get:
924 * @so: The sheet object
925 * @coords: (out) (array fixed-size=4): coordinates
926 *
927 * Calculate the position of the object @so in pts from the logical position in
928 * the object.
929 **/
930 void
sheet_object_position_pts_get(SheetObject const * so,double * coords)931 sheet_object_position_pts_get (SheetObject const *so, double *coords)
932 {
933 g_return_if_fail (GNM_IS_SO (so));
934 sheet_object_anchor_to_pts (&so->anchor, so->sheet, coords);
935 }
936
937 void
sheet_object_anchor_to_pts(SheetObjectAnchor const * anchor,Sheet const * sheet,double * res_pts)938 sheet_object_anchor_to_pts (SheetObjectAnchor const *anchor,
939 Sheet const *sheet, double *res_pts)
940 {
941 GnmRange const *r;
942
943 g_return_if_fail (res_pts != NULL);
944
945 r = &anchor->cell_bound;
946
947 if (anchor->mode != GNM_SO_ANCHOR_ABSOLUTE) {
948 res_pts [0] = sheet_col_get_distance_pts (sheet, 0,
949 r->start.col);
950 res_pts [1] = sheet_row_get_distance_pts (sheet, 0,
951 r->start.row);
952 if (anchor->mode == GNM_SO_ANCHOR_TWO_CELLS) {
953 res_pts [2] = res_pts [0] + sheet_col_get_distance_pts (sheet,
954 r->start.col, r->end.col);
955 res_pts [3] = res_pts [1] + sheet_row_get_distance_pts (sheet,
956 r->start.row, r->end.row);
957
958 res_pts [0] += cell_offset_calc_pt (sheet, r->start.col,
959 TRUE, anchor->offset [0]);
960 res_pts [1] += cell_offset_calc_pt (sheet, r->start.row,
961 FALSE, anchor->offset [1]);
962 res_pts [2] += cell_offset_calc_pt (sheet, r->end.col,
963 TRUE, anchor->offset [2]);
964 res_pts [3] += cell_offset_calc_pt (sheet, r->end.row,
965 FALSE, anchor->offset [3]);
966 } else {
967 res_pts [0] += cell_offset_calc_pt (sheet, r->start.col,
968 TRUE, anchor->offset [0]);
969 res_pts [1] += cell_offset_calc_pt (sheet, r->start.row,
970 FALSE, anchor->offset [1]);
971 res_pts[2] = res_pts [0] + anchor->offset [2];
972 res_pts[3] = res_pts [1] + anchor->offset [3];
973 }
974 } else {
975 res_pts [0] = anchor->offset [0];
976 res_pts [1] = anchor->offset [1];
977 res_pts[2] = res_pts [0] + anchor->offset [2];
978 res_pts[3] = res_pts [1] + anchor->offset [3];
979 }
980 }
981
982 void
sheet_object_pts_to_anchor(SheetObjectAnchor * anchor,Sheet const * sheet,double const * res_pts)983 sheet_object_pts_to_anchor (SheetObjectAnchor *anchor,
984 Sheet const *sheet, double const *res_pts)
985 {
986 int col, row;
987 double x, y, tmp = 0;
988 ColRowInfo const *ci;
989 /* find end column */
990 col = x = 0;
991 do {
992 ci = sheet_col_get_info (sheet, col);
993 if (ci->visible) {
994 tmp = ci->size_pts;
995 if (res_pts[0] <= x + tmp)
996 break;
997 x += tmp;
998 }
999 } while (++col < gnm_sheet_get_last_col (sheet));
1000 if (col == gnm_sheet_get_last_col (sheet)) {
1001 /* not sure this will occur */
1002 col--;
1003 x -= tmp;
1004 }
1005 anchor->cell_bound.start.col = col;
1006 anchor->offset[0] = (anchor->mode == GNM_SO_ANCHOR_ABSOLUTE)?
1007 res_pts[0]: (res_pts[0] - x) / tmp;
1008 /* find start row */
1009 row = y = 0;
1010 do {
1011 ci = sheet_row_get_info (sheet, row);
1012 if (ci->visible) {
1013 tmp = ci->size_pts;
1014 if (res_pts[1] <= y + tmp)
1015 break;
1016 y += tmp;
1017 }
1018 } while (++row < gnm_sheet_get_last_row (sheet));
1019 if (row == gnm_sheet_get_last_row (sheet)) {
1020 /* not sure this will occur */
1021 row--;
1022 y -= tmp;
1023 }
1024 anchor->cell_bound.start.row = row;
1025 anchor->offset[1] = (anchor->mode == GNM_SO_ANCHOR_ABSOLUTE)?
1026 res_pts[1]: (res_pts[1] - y) / tmp;
1027
1028 /* find end column */
1029 do {
1030 ci = sheet_col_get_info (sheet, col);
1031 if (ci->visible) {
1032 tmp = ci->size_pts;
1033 if (res_pts[2] <= x + tmp)
1034 break;
1035 x += tmp;
1036 }
1037 } while (++col < gnm_sheet_get_last_col (sheet));
1038 if (col == gnm_sheet_get_last_col (sheet)) {
1039 /* not sure this will occur */
1040 col--;
1041 x -= tmp;
1042 }
1043 anchor->cell_bound.end.col = col;
1044 anchor->offset[2] = (anchor->mode == GNM_SO_ANCHOR_TWO_CELLS)?
1045 (res_pts[2] - x) / tmp: res_pts[2] - res_pts[0];
1046 /* find end row */
1047 do {
1048 ci = sheet_row_get_info (sheet, row);
1049 if (ci->visible) {
1050 tmp = ci->size_pts;
1051 if (res_pts[3] <= y + tmp)
1052 break;
1053 y += tmp;
1054 }
1055 } while (++row < gnm_sheet_get_last_row (sheet));
1056 if (row == gnm_sheet_get_last_row (sheet)) {
1057 /* not sure this will occur */
1058 row--;
1059 y -= tmp;
1060 }
1061 anchor->cell_bound.end.row = row;
1062 anchor->offset[3] = (anchor->mode == GNM_SO_ANCHOR_TWO_CELLS)?
1063 (res_pts[3] - y) / tmp: res_pts[3] - res_pts[1];
1064 }
1065
1066 void
sheet_object_anchor_to_offset_pts(SheetObjectAnchor const * anchor,Sheet const * sheet,double * res_pts)1067 sheet_object_anchor_to_offset_pts (SheetObjectAnchor const *anchor,
1068 Sheet const *sheet, double *res_pts)
1069 {
1070 GnmRange const *r;
1071
1072 g_return_if_fail (res_pts != NULL);
1073
1074 r = &anchor->cell_bound;
1075
1076 if (anchor->mode != GNM_SO_ANCHOR_ABSOLUTE) {
1077 res_pts [0] = cell_offset_calc_pt (sheet, r->start.col,
1078 TRUE, anchor->offset [0]);
1079 res_pts [1] = cell_offset_calc_pt (sheet, r->start.row,
1080 FALSE, anchor->offset [1]);
1081 if (anchor->mode == GNM_SO_ANCHOR_TWO_CELLS) {
1082 res_pts [2] = cell_offset_calc_pt (sheet, r->end.col,
1083 TRUE, anchor->offset [2]);
1084 res_pts [3] = cell_offset_calc_pt (sheet, r->end.row,
1085 FALSE, anchor->offset [3]);
1086 }
1087 }
1088 }
1089
1090 static void
clear_sheet(SheetObject * so,GOUndo ** pundo)1091 clear_sheet (SheetObject *so, GOUndo **pundo)
1092 {
1093 if (pundo) {
1094 GOUndo *u = go_undo_binary_new
1095 (g_object_ref (so),
1096 so->sheet,
1097 (GOUndoBinaryFunc)sheet_object_set_sheet,
1098 (GFreeFunc) g_object_unref,
1099 NULL);
1100 *pundo = go_undo_combine (*pundo, u);
1101 }
1102
1103 sheet_object_clear_sheet (so);
1104 }
1105
1106
1107 /**
1108 * sheet_objects_relocate:
1109 * @rinfo: details on what should be moved.
1110 * @update: Should we do the bound_update now, or leave it for later.
1111 * if FALSE honour the move_with_cells flag.
1112 * @pundo: (optional) (out): add dropped objects to ::objects
1113 *
1114 * Uses the relocation info and the anchors to decide whether or not, and how
1115 * to relocate objects when the grid moves (eg ins/del col/row).
1116 **/
1117 void
sheet_objects_relocate(GnmExprRelocateInfo const * rinfo,gboolean update,GOUndo ** pundo)1118 sheet_objects_relocate (GnmExprRelocateInfo const *rinfo, gboolean update,
1119 GOUndo **pundo)
1120 {
1121 GSList *ptr, *next;
1122 GnmRange dest;
1123 gboolean change_sheets;
1124
1125 g_return_if_fail (rinfo != NULL);
1126 g_return_if_fail (IS_SHEET (rinfo->origin_sheet));
1127 g_return_if_fail (IS_SHEET (rinfo->target_sheet));
1128
1129 dest = rinfo->origin;
1130 range_translate (&dest, rinfo->target_sheet,
1131 rinfo->col_offset, rinfo->row_offset);
1132 change_sheets = (rinfo->origin_sheet != rinfo->target_sheet);
1133
1134 /* Clear the destination range on the target sheet */
1135 if (change_sheets) {
1136 GSList *copy = g_slist_copy (rinfo->target_sheet->sheet_objects);
1137 for (ptr = copy; ptr != NULL ; ptr = ptr->next ) {
1138 SheetObject *so = GNM_SO (ptr->data);
1139 GnmRange const *r = &so->anchor.cell_bound;
1140 if (range_contains (&dest, r->start.col, r->start.row)) {
1141 clear_sheet (so, pundo);
1142 }
1143 }
1144 g_slist_free (copy);
1145 }
1146
1147 ptr = rinfo->origin_sheet->sheet_objects;
1148 for (; ptr != NULL ; ptr = next ) {
1149 SheetObject *so = GNM_SO (ptr->data);
1150 GnmRange r = so->anchor.cell_bound;
1151
1152 next = ptr->next;
1153 if ((so->anchor.mode == GNM_SO_ANCHOR_ABSOLUTE) ||
1154 (update && 0 == (so->flags & SHEET_OBJECT_MOVE_WITH_CELLS)))
1155 continue;
1156 if (range_contains (&rinfo->origin, r.start.col, r.start.row)) {
1157 /* FIXME : just moving the range is insufficent for all anchor types */
1158 /* Toss any objects that would be clipped. */
1159 if (range_translate (&r, rinfo->origin_sheet,
1160 rinfo->col_offset, rinfo->row_offset)) {
1161 clear_sheet (so, pundo);
1162 continue;
1163 }
1164 so->anchor.cell_bound = r;
1165
1166 if (change_sheets) {
1167 g_object_ref (so);
1168 sheet_object_clear_sheet (so);
1169 sheet_object_set_sheet (so, rinfo->target_sheet);
1170 g_object_unref (so);
1171 } else if (update)
1172 sheet_object_update_bounds (so, NULL);
1173 } else if (!change_sheets &&
1174 range_contains (&dest, r.start.col, r.start.row)) {
1175 clear_sheet (so, pundo);
1176 continue;
1177 }
1178 }
1179
1180 rinfo->origin_sheet->priv->objects_changed = TRUE;
1181 if (change_sheets)
1182 rinfo->target_sheet->priv->objects_changed = TRUE;
1183 }
1184
1185 /**
1186 * sheet_objects_get:
1187 * @sheet: the sheet.
1188 * @r: (nullable): #GnmRange to look in
1189 * @t: The type of object to lookup, %G_TYPE_NONE for any.
1190 *
1191 * Returns: (element-type SheetObject) (transfer container): a list
1192 * containing all objects of exactly the specified type (inheritance does
1193 * not count) that are completely contained in @r.
1194 **/
1195 GSList *
sheet_objects_get(Sheet const * sheet,GnmRange const * r,GType t)1196 sheet_objects_get (Sheet const *sheet, GnmRange const *r, GType t)
1197 {
1198 GSList *res = NULL;
1199 GSList *ptr;
1200
1201 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1202
1203 for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = ptr->next ) {
1204 GObject *obj = G_OBJECT (ptr->data);
1205
1206 if (t == G_TYPE_NONE || t == G_OBJECT_TYPE (obj)) {
1207 SheetObject *so = GNM_SO (obj);
1208 if (r == NULL || range_contained (&so->anchor.cell_bound, r))
1209 res = g_slist_prepend (res, so);
1210 }
1211 }
1212 return g_slist_reverse (res);
1213 }
1214
1215 /**
1216 * sheet_objects_clear:
1217 * @sheet: the sheet.
1218 * @r: (nullable): #GnmRange to look in
1219 * @t #GType
1220 * @pundo: (out) (nullable):
1221 *
1222 * Removes the objects in the region.
1223 **/
1224 void
sheet_objects_clear(Sheet const * sheet,GnmRange const * r,GType t,GOUndo ** pundo)1225 sheet_objects_clear (Sheet const *sheet, GnmRange const *r, GType t,
1226 GOUndo **pundo)
1227 {
1228 GSList *ptr, *next;
1229
1230 g_return_if_fail (IS_SHEET (sheet));
1231
1232 for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = next ) {
1233 GObject *obj = G_OBJECT (ptr->data);
1234 next = ptr->next;
1235 if ((t == G_TYPE_NONE && G_OBJECT_TYPE (obj) != GNM_FILTER_COMBO_TYPE)
1236 || t == G_OBJECT_TYPE (obj)) {
1237 SheetObject *so = GNM_SO (obj);
1238 if (r == NULL || range_contained (&so->anchor.cell_bound, r))
1239 clear_sheet (so, pundo);
1240 }
1241 }
1242 }
1243
1244 /**
1245 * sheet_object_dup:
1246 * @so: a #SheetObject to duplicate
1247 *
1248 * Returns: (transfer full) (nullable): A copy of @so that is not attached
1249 * to a sheet.
1250 **/
1251 SheetObject *
sheet_object_dup(SheetObject const * so)1252 sheet_object_dup (SheetObject const *so)
1253 {
1254 SheetObject *new_so = NULL;
1255
1256 if (!SO_CLASS (so)->copy)
1257 return NULL;
1258
1259 new_so = g_object_new (G_OBJECT_TYPE (so), NULL);
1260
1261 g_return_val_if_fail (new_so != NULL, NULL);
1262
1263 SO_CLASS (so)->copy (new_so, so);
1264 new_so->flags = so->flags;
1265 new_so->anchor = so->anchor;
1266
1267 return new_so;
1268 }
1269
1270 static void
cb_sheet_objects_dup(GnmDependent * dep,SheetObject * so,gpointer user)1271 cb_sheet_objects_dup (GnmDependent *dep, SheetObject *so, gpointer user)
1272 {
1273 Sheet *src = user;
1274 Sheet *dst = sheet_object_get_sheet (so);
1275 GnmExprTop const *texpr;
1276
1277 if (!dep->texpr)
1278 return;
1279
1280 texpr = gnm_expr_top_relocate_sheet (dep->texpr, src, dst);
1281 if (texpr != dep->texpr) {
1282 gboolean was_linked= dependent_is_linked (dep);
1283 dependent_set_expr (dep, texpr);
1284 if (was_linked)
1285 dependent_link (dep);
1286 }
1287 gnm_expr_top_unref (texpr);
1288 }
1289
1290
1291 /**
1292 * sheet_objects_dup:
1293 * @src: The source sheet to read the objects from
1294 * @dst: The destination sheet to attach the objects to
1295 * @range: (nullable): #GnmRange to look in
1296 *
1297 * Clones the objects of the src sheet and attaches them into the dst sheet
1298 **/
1299 void
sheet_objects_dup(Sheet const * src,Sheet * dst,GnmRange * range)1300 sheet_objects_dup (Sheet const *src, Sheet *dst, GnmRange *range)
1301 {
1302 GSList *list;
1303
1304 g_return_if_fail (IS_SHEET (dst));
1305 g_return_if_fail (dst->sheet_objects == NULL);
1306
1307 for (list = src->sheet_objects; list != NULL; list = list->next) {
1308 SheetObject *so = list->data;
1309 if (range == NULL || range_overlap (range, &so->anchor.cell_bound)) {
1310 SheetObject *new_so = sheet_object_dup (so);
1311 if (new_so != NULL) {
1312 sheet_object_set_sheet (new_so, dst);
1313 sheet_object_foreach_dep (new_so, cb_sheet_objects_dup,
1314 (gpointer)src);
1315 g_object_unref (new_so);
1316 }
1317 }
1318 }
1319
1320 dst->sheet_objects = g_slist_reverse (dst->sheet_objects);
1321 }
1322
1323
1324 /**
1325 * sheet_object_direction_set:
1326 * @so: The sheet object that we are calculating the direction for
1327 * @coords: (in) (array fixed-size=4): array of coordinates in L,T,R,B order
1328 *
1329 * Sets the object direction from the given the new coordinates
1330 * The original coordinates are assumed to be normalized (so that top
1331 * is above bottom and right is at the right of left)
1332 **/
1333 void
sheet_object_direction_set(SheetObject * so,gdouble const * coords)1334 sheet_object_direction_set (SheetObject *so, gdouble const *coords)
1335 {
1336 if (so->anchor.base.direction == GOD_ANCHOR_DIR_UNKNOWN)
1337 return;
1338
1339 so->anchor.base.direction = GOD_ANCHOR_DIR_NONE_MASK;
1340
1341 if (coords [1] < coords [3])
1342 so->anchor.base.direction |= GOD_ANCHOR_DIR_DOWN;
1343 if (coords [0] < coords [2])
1344 so->anchor.base.direction |= GOD_ANCHOR_DIR_RIGHT;
1345 }
1346
1347 /**
1348 * sheet_object_rubber_band_directly:
1349 * @so:
1350 *
1351 * Returns: %TRUE if we should draw the object as we are laying it out on
1352 * an sheet. If %FALSE we draw a rectangle where the object is going to go
1353 **/
1354 gboolean
sheet_object_rubber_band_directly(G_GNUC_UNUSED SheetObject const * so)1355 sheet_object_rubber_band_directly (G_GNUC_UNUSED SheetObject const *so)
1356 {
1357 return FALSE;
1358 }
1359
1360 /**
1361 * sheet_object_anchor_init:
1362 * @anchor: #SheetObjectAnchor
1363 * @r: (nullable): #GnmRange to look in
1364 * @offsets: (in) (array fixed-size=4) (nullable):
1365 * @direction: #GODrawingAnchorDir
1366 * @mode: #GnmSOAnchorMode
1367 *
1368 * A utility routine to initialize an anchor. Useful in case we change fields
1369 * in the future and want to ensure that everything is initialized.
1370 **/
1371 void
sheet_object_anchor_init(SheetObjectAnchor * anchor,GnmRange const * r,const double * offsets,GODrawingAnchorDir direction,GnmSOAnchorMode mode)1372 sheet_object_anchor_init (SheetObjectAnchor *anchor,
1373 GnmRange const *r, const double *offsets,
1374 GODrawingAnchorDir direction,
1375 GnmSOAnchorMode mode)
1376 {
1377 int i;
1378
1379 if (r == NULL) {
1380 static GnmRange const defaultVal = { { 0, 0 }, { 1, 1 } };
1381 r = &defaultVal;
1382 }
1383 anchor->cell_bound = *r;
1384
1385 if (offsets == NULL) {
1386 static double const defaultVal [4] = { 0., 0., 0., 0. };
1387 offsets = defaultVal;
1388 }
1389 for (i = 4; i-- > 0 ; )
1390 anchor->offset[i] = offsets [i];
1391
1392 anchor->base.direction = direction;
1393 anchor->mode = mode;
1394 /* TODO : add sanity checking to handle offsets past edges of col/row */
1395 }
1396
1397 /*****************************************************************************/
1398
1399 /**
1400 * sheet_object_get_stacking:
1401 * @so: #SheetObject
1402 *
1403 * Returns: @so's position in the stack of sheet objects.
1404 **/
1405 gint
sheet_object_get_stacking(SheetObject * so)1406 sheet_object_get_stacking (SheetObject *so)
1407 {
1408 g_return_val_if_fail (so != NULL, 0);
1409 g_return_val_if_fail (so->sheet != NULL, 0);
1410
1411 return g_slist_index (so->sheet->sheet_objects, so);
1412 }
1413
1414 gint
sheet_object_adjust_stacking(SheetObject * so,gint offset)1415 sheet_object_adjust_stacking (SheetObject *so, gint offset)
1416 {
1417 GList *l;
1418 GSList **ptr, *node = NULL;
1419 int i, target, cur = 0;
1420
1421 g_return_val_if_fail (so != NULL, 0);
1422 g_return_val_if_fail (so->sheet != NULL, 0);
1423
1424 for (ptr = &so->sheet->sheet_objects ; *ptr ; ptr = &(*ptr)->next, cur++)
1425 if ((*ptr)->data == so) {
1426 node = *ptr;
1427 *ptr = (*ptr)->next;
1428 break;
1429 }
1430
1431 g_return_val_if_fail (node != NULL, 0);
1432
1433 /* Start at the begining when moving things towards the front */
1434 if (offset > 0) {
1435 ptr = &so->sheet->sheet_objects;
1436 i = 0;
1437 } else
1438 i = cur;
1439
1440 for (target = cur - offset; *ptr && i < target ; ptr = &(*ptr)->next)
1441 i++;
1442
1443 node->next = *ptr;
1444 *ptr = node;
1445
1446 /* TODO : Move this to the container */
1447 for (l = so->realized_list; l; l = l->next) {
1448 GocItem *item = GOC_ITEM (l->data);
1449 if (offset > 0)
1450 goc_item_raise (item, offset);
1451 else
1452 goc_item_lower (item, - offset);
1453 }
1454 return cur - i;
1455 }
1456
1457 void
sheet_object_set_anchor_mode(SheetObject * so,GnmSOAnchorMode const * mode)1458 sheet_object_set_anchor_mode (SheetObject *so, GnmSOAnchorMode const *mode)
1459 {
1460 /* FIXME: adjust offsets according to the old and new modes */
1461 double pts[4];
1462
1463 if (so->anchor.mode == *mode)
1464 return;
1465 sheet_object_anchor_to_pts (&so->anchor, so->sheet, pts);
1466 so->anchor.mode = *mode;
1467 sheet_object_pts_to_anchor (&so->anchor, so->sheet, pts);
1468 }
1469
1470 /*****************************************************************************/
1471
1472 static GObjectClass *view_parent_class;
1473
1474 void
sheet_object_view_set_bounds(SheetObjectView * sov,double const * coords,gboolean visible)1475 sheet_object_view_set_bounds (SheetObjectView *sov,
1476 double const *coords, gboolean visible)
1477 {
1478 SheetObjectViewClass *klass;
1479
1480 g_return_if_fail (GNM_IS_SO_VIEW (sov));
1481 klass = GNM_SO_VIEW_GET_CLASS (sov);
1482 if (NULL != klass->set_bounds)
1483 (klass->set_bounds) (sov, coords, visible);
1484 }
1485
1486 /**
1487 * sheet_object_view_get_item:
1488 * @sov: #SheetObjectView
1489 *
1490 * Returns: (transfer none): the #GocItem implementing @sov
1491 **/
1492 GocItem *
sheet_object_view_get_item(SheetObjectView * sov)1493 sheet_object_view_get_item (SheetObjectView *sov)
1494 {
1495 g_return_val_if_fail (GNM_IS_SO_VIEW (sov), NULL);
1496
1497 return goc_group_get_child (GOC_GROUP (sov), 0);
1498 }
1499
1500
1501 /**
1502 * sheet_object_view_get_so:
1503 * @sov: #SheetObjectView
1504 *
1505 * Returns: (transfer none): the #SheetObject owning @view.
1506 **/
1507 SheetObject *
sheet_object_view_get_so(SheetObjectView * view)1508 sheet_object_view_get_so (SheetObjectView *view)
1509 {
1510 return g_object_get_qdata (G_OBJECT (view), sov_so_quark);
1511 }
1512
1513 static gboolean
sheet_object_view_enter_notify(GocItem * item,double x,double y)1514 sheet_object_view_enter_notify (GocItem *item, double x, double y)
1515 {
1516 SheetObject *so;
1517
1518 if (GNM_IS_PANE (item->canvas) && scg_wbcg (GNM_SIMPLE_CANVAS (item->canvas)->scg)->new_object) {
1519 GnmItemGrid *grid = GNM_PANE (item->canvas)->grid;
1520 return GOC_ITEM_GET_CLASS (grid)->enter_notify (GOC_ITEM (grid), x, y);
1521 }
1522
1523 so = (SheetObject *) g_object_get_qdata (G_OBJECT (item), sov_so_quark);
1524 gnm_widget_set_cursor_type (GTK_WIDGET (item->canvas),
1525 (so->flags & SHEET_OBJECT_CAN_PRESS) ? GDK_HAND2 : GDK_ARROW);
1526 return FALSE;
1527 }
1528
1529 static void
cb_so_menu_activate(GObject * menu,GocItem * view)1530 cb_so_menu_activate (GObject *menu, GocItem *view)
1531 {
1532 SheetObjectAction const *a = g_object_get_data (menu, "action");
1533
1534 if (a->func) {
1535 SheetObject *so = sheet_object_view_get_so (GNM_SO_VIEW (view));
1536 gpointer data = g_object_get_data (G_OBJECT (view->canvas), "sheet-control");
1537
1538 if (data == NULL)
1539 data = GNM_SIMPLE_CANVAS (view->canvas)->scg;
1540
1541 (a->func) (so, GNM_SHEET_CONTROL (data));
1542 }
1543 }
1544
1545 static void
cb_ptr_array_free(GPtrArray * actions)1546 cb_ptr_array_free (GPtrArray *actions)
1547 {
1548 g_ptr_array_free (actions, TRUE);
1549 }
1550
1551 /**
1552 * sheet_object_build_menu:
1553 * @view: #SheetObjectView
1554 * @actions: (element-type SheetObjectAction): array of actions.
1555 * @i: index of first action to add in the array.
1556 *
1557 * Builds the contextual menu for @view.
1558 * Returns: (transfer full): the newly constructed #GtkMenu.
1559 **/
1560 GtkWidget *
sheet_object_build_menu(SheetObjectView * view,GPtrArray const * actions,unsigned * i)1561 sheet_object_build_menu (SheetObjectView *view,
1562 GPtrArray const *actions, unsigned *i)
1563 {
1564 SheetObjectAction const *a;
1565 GtkWidget *item, *menu = gtk_menu_new ();
1566
1567 while (*i < actions->len) {
1568 a = g_ptr_array_index (actions, *i);
1569 (*i)++;
1570 if (a->submenu < 0)
1571 break;
1572 if (a->icon != NULL) {
1573 if (a->label != NULL) {
1574 item = gtk_image_menu_item_new_with_mnemonic (_(a->label));
1575 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item),
1576 gtk_image_new_from_icon_name (a->icon, GTK_ICON_SIZE_MENU));
1577 } else
1578 item = gtk_image_menu_item_new_from_stock (a->icon, NULL);
1579 } else if (a->label != NULL)
1580 item = gtk_menu_item_new_with_mnemonic (_(a->label));
1581 else
1582 item = gtk_separator_menu_item_new ();
1583 if (a->submenu > 0)
1584 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item),
1585 sheet_object_build_menu (view, actions, i));
1586 else if (a->label != NULL || a->icon != NULL) { /* not a separator or menu */
1587 g_object_set_data (G_OBJECT (item), "action", (gpointer)a);
1588 g_signal_connect_object (G_OBJECT (item), "activate",
1589 G_CALLBACK (cb_so_menu_activate), view, 0);
1590 gtk_widget_set_sensitive (item, a->enable_func == NULL
1591 || a->enable_func (sheet_object_view_get_so (view)));
1592 }
1593 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1594 }
1595 return menu;
1596 }
1597
1598 static gboolean
sheet_object_view_button_pressed(GocItem * item,int button,double x,double y)1599 sheet_object_view_button_pressed (GocItem *item, int button, double x, double y)
1600 {
1601 GnmPane *pane;
1602 SheetObject *so;
1603 if (GNM_IS_PANE (item->canvas)) {
1604 if (scg_wbcg (GNM_SIMPLE_CANVAS (item->canvas)->scg)->new_object) {
1605 GnmItemGrid *grid = GNM_PANE (item->canvas)->grid;
1606 return GOC_ITEM_GET_CLASS (grid)->button_pressed (GOC_ITEM (grid), button, x, y);
1607 }
1608
1609 if (button > 3)
1610 return FALSE;
1611
1612 pane = GNM_PANE (item->canvas);
1613 so = (SheetObject *) g_object_get_qdata (G_OBJECT (item), sov_so_quark);
1614
1615 x *= goc_canvas_get_pixels_per_unit (item->canvas);
1616 y *= goc_canvas_get_pixels_per_unit (item->canvas);
1617 /* cb_sheet_object_widget_canvas_event calls even if selected */
1618 if (NULL == g_hash_table_lookup (pane->drag.ctrl_pts, so)) {
1619 SheetObjectClass *soc =
1620 G_TYPE_INSTANCE_GET_CLASS (so, GNM_SO_TYPE, SheetObjectClass);
1621 GdkEventButton *event = (GdkEventButton *) goc_canvas_get_cur_event (item->canvas);
1622
1623 if (soc->interactive && button != 3)
1624 return FALSE;
1625
1626 if (!(event->state & GDK_SHIFT_MASK))
1627 scg_object_unselect (pane->simple.scg, NULL);
1628 scg_object_select (pane->simple.scg, so);
1629 if (NULL == g_hash_table_lookup (pane->drag.ctrl_pts, so))
1630 return FALSE; /* protected ? */
1631 }
1632
1633 if (button < 3)
1634 gnm_pane_object_start_resize (pane, button, x, y, so, 8, FALSE);
1635 else
1636 gnm_pane_display_object_menu (pane, so, goc_canvas_get_cur_event (item->canvas));
1637 } else {
1638 if (button == 3) {
1639 GPtrArray *actions = g_ptr_array_new ();
1640 GtkWidget *menu;
1641 unsigned i = 0;
1642
1643 so = (SheetObject *) g_object_get_qdata (G_OBJECT (item), sov_so_quark);
1644 sheet_object_populate_menu (so, actions);
1645
1646 if (actions->len == 0) {
1647 g_ptr_array_free (actions, TRUE);
1648 return FALSE;
1649 }
1650
1651 menu = sheet_object_build_menu
1652 (sheet_object_get_view (so, (SheetObjectViewContainer *) item->canvas),
1653 actions, &i);
1654 g_object_set_data_full (G_OBJECT (menu), "actions", actions,
1655 (GDestroyNotify) cb_ptr_array_free);
1656 gtk_widget_show_all (menu);
1657 gnumeric_popup_menu (GTK_MENU (menu),
1658 goc_canvas_get_cur_event (item->canvas));
1659 }
1660 }
1661 return TRUE;
1662 }
1663
1664 static gboolean
sheet_object_view_button2_pressed(GocItem * item,int button,double x,double y)1665 sheet_object_view_button2_pressed (GocItem *item, int button, double x, double y)
1666 {
1667 if (button == 1 && !GNM_IS_PANE (item->canvas)) {
1668 SheetControl *sc = GNM_SHEET_CONTROL (g_object_get_data (G_OBJECT (item->canvas), "sheet-control"));
1669 SheetObject *so = (SheetObject *) g_object_get_qdata (G_OBJECT (item), sov_so_quark);
1670
1671 if (sc && sheet_object_can_edit (so))
1672 sheet_object_get_editor (so, sc);
1673 }
1674 return TRUE;
1675 }
1676
1677 static void
sheet_object_view_finalize(GObject * obj)1678 sheet_object_view_finalize (GObject *obj)
1679 {
1680 SheetObject *so = (SheetObject *) g_object_get_qdata (obj, sov_so_quark);
1681 if (so)
1682 so->realized_list = g_list_remove (so->realized_list, obj);
1683 view_parent_class->finalize (obj);
1684 }
1685
1686 static void
sheet_object_view_class_init(GocItemClass * item_klass)1687 sheet_object_view_class_init (GocItemClass *item_klass)
1688 {
1689 GObjectClass *obj_klass = (GObjectClass *) item_klass;
1690 view_parent_class = g_type_class_peek_parent (item_klass);
1691
1692 obj_klass->finalize = sheet_object_view_finalize;
1693
1694 item_klass->enter_notify = sheet_object_view_enter_notify;
1695 item_klass->button_pressed = sheet_object_view_button_pressed;
1696 item_klass->button2_pressed = sheet_object_view_button2_pressed;
1697 }
1698
GSF_CLASS(SheetObjectView,sheet_object_view,sheet_object_view_class_init,NULL,GOC_TYPE_GROUP)1699 GSF_CLASS (SheetObjectView, sheet_object_view,
1700 sheet_object_view_class_init, NULL,
1701 GOC_TYPE_GROUP)
1702
1703 /*****************************************************************************/
1704
1705 GType
1706 sheet_object_imageable_get_type (void)
1707 {
1708 static GType type = 0;
1709
1710 if (!type) {
1711 static GTypeInfo const type_info = {
1712 sizeof (SheetObjectImageableIface), /* class_size */
1713 NULL, /* base_init */
1714 NULL, /* base_finalize */
1715 NULL, NULL, NULL, 0, 0, NULL, NULL
1716 };
1717
1718 type = g_type_register_static (G_TYPE_INTERFACE,
1719 "SheetObjectImageable", &type_info, 0);
1720 }
1721
1722 return type;
1723 }
1724
1725 #define GNM_SO_IMAGEABLE_CLASS(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), GNM_SO_IMAGEABLE_TYPE, SheetObjectImageableIface))
1726
1727 GtkTargetList *
sheet_object_get_target_list(SheetObject const * so)1728 sheet_object_get_target_list (SheetObject const *so)
1729 {
1730 if (!GNM_IS_SO_IMAGEABLE (so))
1731 return NULL;
1732
1733 return GNM_SO_IMAGEABLE_CLASS (so)->get_target_list (so);
1734 }
1735
1736 /**
1737 * sheet_object_write_image:
1738 * @so: #SheetObject
1739 * @format: (nullable): image format to use
1740 * @resolution: export resolution
1741 * @output: destination
1742 * @err: (out) (optional) (nullable): error indication
1743 *
1744 * Saves a sheet object as an image to @output. If an error occurs, @err will
1745 * be set.
1746 **/
1747 void
sheet_object_write_image(SheetObject const * so,char const * format,double resolution,GsfOutput * output,GError ** err)1748 sheet_object_write_image (SheetObject const *so, char const *format, double resolution,
1749 GsfOutput *output, GError **err)
1750 {
1751 g_return_if_fail (GNM_IS_SO_IMAGEABLE (so));
1752 g_return_if_fail (GSF_IS_OUTPUT (output));
1753
1754 GNM_SO_IMAGEABLE_CLASS (so)->write_image (so, format, resolution,
1755 output, err);
1756 }
1757
1758 /**
1759 * sheet_object_save_as_image:
1760 * @so: #SheetObject
1761 * @format: (nullable): image format to use
1762 * @resolution: export resolution
1763 * @url: destination url
1764 * @err: (out) (optional) (nullable): error indication
1765 *
1766 * Saves a sheet object as an image to @url. If an error occurs, @err
1767 * will be set.
1768 **/
1769 void
sheet_object_save_as_image(SheetObject const * so,char const * format,double resolution,const char * url,GError ** err)1770 sheet_object_save_as_image (SheetObject const *so,
1771 char const *format,
1772 double resolution,
1773 const char *url,
1774 GError **err)
1775 {
1776 GsfOutput *dst;
1777
1778 g_return_if_fail (GNM_IS_SO_IMAGEABLE (so));
1779 g_return_if_fail (url != NULL);
1780
1781 dst = go_file_create (url, err);
1782 if (!dst)
1783 return;
1784
1785 sheet_object_write_image (so, format, resolution, dst, err);
1786 gsf_output_close (dst);
1787 g_object_unref (dst);
1788 }
1789
1790 /*****************************************************************************/
1791
1792 GType
sheet_object_exportable_get_type(void)1793 sheet_object_exportable_get_type (void)
1794 {
1795 static GType type = 0;
1796
1797 if (!type) {
1798 static GTypeInfo const type_info = {
1799 sizeof (SheetObjectExportableIface), /* class_size */
1800 NULL, /* base_init */
1801 NULL, /* base_finalize */
1802 NULL, NULL, NULL, 0, 0, NULL, NULL
1803 };
1804
1805 type = g_type_register_static (G_TYPE_INTERFACE,
1806 "SheetObjectExportable", &type_info, 0);
1807 }
1808
1809 return type;
1810 }
1811
1812 #define GNM_SO_EXPORTABLE_CLASS(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), GNM_SO_EXPORTABLE_TYPE, SheetObjectExportableIface))
1813
1814 GtkTargetList *
sheet_object_exportable_get_target_list(SheetObject const * so)1815 sheet_object_exportable_get_target_list (SheetObject const *so)
1816 {
1817 if (!GNM_IS_SO_EXPORTABLE (so))
1818 return NULL;
1819
1820 return GNM_SO_EXPORTABLE_CLASS (so)->get_target_list (so);
1821 }
1822
1823 void
sheet_object_write_object(SheetObject const * so,char const * format,GsfOutput * output,GError ** err,GnmConventions const * convs)1824 sheet_object_write_object (SheetObject const *so, char const *format,
1825 GsfOutput *output, GError **err,
1826 GnmConventions const *convs)
1827 {
1828 GnmLocale *locale;
1829
1830 g_return_if_fail (GNM_IS_SO_EXPORTABLE (so));
1831
1832 locale = gnm_push_C_locale ();
1833 GNM_SO_EXPORTABLE_CLASS (so)->
1834 write_object (so, format, output, err, convs);
1835 gnm_pop_C_locale (locale);
1836 }
1837
1838 /**
1839 * sheet_object_move_undo:
1840 * @objects: (element-type SheetObject):
1841 * @objects_created:
1842 *
1843 * Returns: (transfer full): the newly allocated #GOUndo.
1844 **/
1845 GOUndo *
sheet_object_move_undo(GSList * objects,gboolean objects_created)1846 sheet_object_move_undo (GSList *objects, gboolean objects_created)
1847 {
1848 GOUndo *undo = NULL;
1849 GSList *objs = objects;
1850
1851 g_return_val_if_fail (NULL != objects, NULL);
1852
1853 for (; objs; objs = objs->next) {
1854 SheetObject *obj = objs->data;
1855 SheetObjectAnchor *tmp;
1856
1857 if (objects_created) {
1858 undo = go_undo_combine
1859 (undo,
1860 go_undo_unary_new
1861 (g_object_ref (obj),
1862 (GOUndoUnaryFunc) sheet_object_clear_sheet,
1863 (GFreeFunc) g_object_unref));
1864 }
1865
1866 tmp = g_new (SheetObjectAnchor, 1);
1867 *tmp = *sheet_object_get_anchor (obj);
1868 undo = go_undo_combine
1869 (undo, go_undo_binary_new
1870 (g_object_ref (obj), tmp,
1871 (GOUndoBinaryFunc) sheet_object_set_anchor,
1872 (GFreeFunc) g_object_unref,
1873 (GFreeFunc) g_free));
1874 }
1875 return undo;
1876 }
1877
1878 /**
1879 * sheet_object_move_do:
1880 * @objects: (element-type SheetObject):
1881 * @anchors: (element-type SheetObjectAnchor):
1882 * @objects_created:
1883 *
1884 * Returns: (transfer full): the newly allocated #GOUndo.
1885 **/
1886 GOUndo *
sheet_object_move_do(GSList * objects,GSList * anchors,gboolean objects_created)1887 sheet_object_move_do (GSList *objects, GSList *anchors,
1888 gboolean objects_created)
1889 {
1890 GOUndo *undo = NULL;
1891 GSList *objs = objects, *anchs = anchors;
1892
1893 g_return_val_if_fail (NULL != objects, NULL);
1894 g_return_val_if_fail (NULL != anchors, NULL);
1895 g_return_val_if_fail (g_slist_length (objects)
1896 == g_slist_length (anchors), NULL);
1897
1898 for (; objs && anchs; objs = objs->next, anchs = anchs->next) {
1899 SheetObject *obj = objs->data;
1900 SheetObjectAnchor *anch = anchs->data;
1901 SheetObjectAnchor *tmp;
1902
1903 if (objects_created) {
1904 undo = go_undo_combine
1905 (undo,
1906 go_undo_binary_new
1907 (g_object_ref (obj),
1908 sheet_object_get_sheet (obj),
1909 (GOUndoBinaryFunc) sheet_object_set_sheet,
1910 (GFreeFunc) g_object_unref,
1911 NULL));
1912 }
1913 tmp = g_new (SheetObjectAnchor, 1);
1914 *tmp = *anch;
1915 undo = go_undo_combine
1916 (go_undo_binary_new
1917 (g_object_ref (obj), tmp,
1918 (GOUndoBinaryFunc) sheet_object_set_anchor,
1919 (GFreeFunc) g_object_unref,
1920 (GFreeFunc) g_free), undo);
1921 }
1922 return undo;
1923 }
1924
1925
1926 /*****************************************************************************/
1927
1928 /**
1929 * sheet_objects_init: (skip)
1930 */
1931 void
sheet_objects_init(void)1932 sheet_objects_init (void)
1933 {
1934 debug_sheet_objects = gnm_debug_flag ("sheet-objects");
1935 so_create_view_sos = g_ptr_array_new ();
1936
1937 GNM_SO_LINE_TYPE;
1938 GNM_SO_FILLED_TYPE;
1939 GNM_SO_GRAPH_TYPE;
1940 GNM_SO_IMAGE_TYPE;
1941 GNM_GO_DATA_SCALAR_TYPE;
1942 GNM_GO_DATA_VECTOR_TYPE;
1943 GNM_GO_DATA_MATRIX_TYPE;
1944 GNM_CELL_COMMENT_TYPE;
1945
1946 sheet_object_widget_register ();
1947 sov_so_quark = g_quark_from_static_string ("SheetObject");
1948 sov_container_quark = g_quark_from_static_string ("SheetObjectViewContainer");
1949 }
1950
1951 /**
1952 * sheet_objects_shutdown: (skip)
1953 */
1954 void
sheet_objects_shutdown(void)1955 sheet_objects_shutdown (void)
1956 {
1957 if (so_create_view_src != 0) {
1958 g_source_remove (so_create_view_src);
1959 so_create_view_src = 0;
1960 }
1961
1962 g_ptr_array_free (so_create_view_sos, TRUE);
1963 so_create_view_sos = NULL;
1964 }
1965