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