1 
2 /*
3  * workbook.c: workbook model and manipulation utilities
4  *
5  * Authors:
6  *    Miguel de Icaza (miguel@gnu.org).
7  *    Jody Goldberg (jody@gnome.org)
8  *
9  * (C) 1998, 1999, 2000 Miguel de Icaza
10  * (C) 2000-2001 Ximian, Inc.
11  * (C) 2002-2007 Jody Goldberg
12  * Copyright (C) 1999-2009 Morten Welinder (terra@gnome.org)
13  */
14 #include <gnumeric-config.h>
15 #include <gnumeric.h>
16 #include <workbook-priv.h>
17 #include <compilation.h>
18 
19 #include <workbook-view.h>
20 #include <workbook-control.h>
21 #include <command-context.h>
22 #include <application.h>
23 #include <gnumeric-conf.h>
24 #include <sheet.h>
25 #include <sheet-view.h>
26 #include <sheet-control.h>
27 #include <cell.h>
28 #include <expr.h>
29 #include <expr-name.h>
30 #include <dependent.h>
31 #include <value.h>
32 #include <ranges.h>
33 #include <history.h>
34 #include <commands.h>
35 #include <libgnumeric.h>
36 #include <gutils.h>
37 #include <gnm-marshalers.h>
38 #include <style-color.h>
39 #include <sheet-style.h>
40 #include <sheet-object-graph.h>
41 
42 #include <goffice/goffice.h>
43 
44 #include <gsf/gsf-doc-meta-data.h>
45 #include <gsf/gsf-impl-utils.h>
46 #include <gsf/gsf-meta-names.h>
47 #include <gnm-i18n.h>
48 #include <string.h>
49 #include <errno.h>
50 
51 /**
52  * Workbook:
53  * @wb_views: (element-type WorkbookView):
54  **/
55 
56 enum {
57 	PROP_0,
58 	PROP_RECALC_MODE,
59 	PROP_BEING_LOADED
60 };
61 enum {
62 	SHEET_ORDER_CHANGED,
63 	SHEET_ADDED,
64 	SHEET_DELETED,
65 	LAST_SIGNAL
66 };
67 static guint signals[LAST_SIGNAL] = { 0 };
68 static GObjectClass *workbook_parent_class;
69 
70 static void
cb_saver_finalize(Workbook * wb,GOFileSaver * saver)71 cb_saver_finalize (Workbook *wb, GOFileSaver *saver)
72 {
73 	g_return_if_fail (GO_IS_FILE_SAVER (saver));
74 	g_return_if_fail (GNM_IS_WORKBOOK (wb));
75 	g_return_if_fail (wb->file_saver == saver);
76 	wb->file_saver = NULL;
77 }
78 static void
cb_exporter_finalize(Workbook * wb,GOFileSaver * saver)79 cb_exporter_finalize (Workbook *wb, GOFileSaver *saver)
80 {
81 	g_return_if_fail (GO_IS_FILE_SAVER (saver));
82 	g_return_if_fail (GNM_IS_WORKBOOK (wb));
83 	g_return_if_fail (wb->file_exporter == saver);
84 	workbook_set_file_exporter (wb, NULL);
85 }
86 
87 void
workbook_update_history(Workbook * wb,GnmFileSaveAsStyle type)88 workbook_update_history (Workbook *wb, GnmFileSaveAsStyle type)
89 {
90 	g_return_if_fail (GNM_IS_WORKBOOK (wb));
91 
92 	switch (type) {
93 	case GNM_FILE_SAVE_AS_STYLE_SAVE:
94 		if (wb->doc.uri && wb->file_format_level >= GO_FILE_FL_MANUAL_REMEMBER) {
95 			const char *mimetype = wb->file_saver
96 				? go_file_saver_get_mime_type (wb->file_saver)
97 				: NULL;
98 			gnm_app_history_add (wb->doc.uri, mimetype);
99 		}
100 		break;
101 	case GNM_FILE_SAVE_AS_STYLE_EXPORT:
102 	default:
103 		if (wb->last_export_uri &&
104 		    wb->file_export_format_level >= GO_FILE_FL_MANUAL_REMEMBER) {
105 			const char *mimetype = wb->file_exporter
106 				? go_file_saver_get_mime_type (wb->file_exporter)
107 				: NULL;
108 			gnm_app_history_add (wb->last_export_uri, mimetype);
109 		}
110 		break;
111 	}
112 }
113 
114 void
workbook_update_graphs(Workbook * wb)115 workbook_update_graphs (Workbook *wb)
116 {
117 	WORKBOOK_FOREACH_SHEET (wb, sheet, ({
118 		GSList *l, *graphs = sheet_objects_get (sheet, NULL, GNM_SO_GRAPH_TYPE);
119 		for (l = graphs; l; l = l->next) {
120 			SheetObject *sog = l->data;
121 			gog_graph_force_update (sheet_object_graph_get_gog (sog));
122 		}
123 		g_slist_free (graphs);
124 	}));
125 }
126 
127 
128 static void
workbook_dispose(GObject * wb_object)129 workbook_dispose (GObject *wb_object)
130 {
131 	Workbook *wb = WORKBOOK (wb_object);
132 	GSList *controls = NULL;
133 	GPtrArray *sheets;
134 	unsigned ui;
135 
136 	wb->during_destruction = TRUE;
137 
138 	if (wb->file_saver)
139 		workbook_set_saveinfo (wb, GO_FILE_FL_AUTO, NULL);
140 	if (wb->file_exporter)
141 		workbook_set_saveinfo (wb, GO_FILE_FL_WRITE_ONLY, NULL);
142 	workbook_set_last_export_uri (wb, NULL);
143 
144 	// Remove all the sheet controls to avoid displaying while we exit
145 	// However, hold on to a ref for each -- dialogs like to refer
146 	// to ->wbcg during destruction
147 	WORKBOOK_FOREACH_CONTROL (wb, view, control,
148 		controls = g_slist_prepend (controls, g_object_ref (control));
149 		wb_control_sheet_remove_all (control););
150 
151 	/* Get rid of all the views */
152 	WORKBOOK_FOREACH_VIEW (wb, wbv, {
153 		wb_view_detach_from_workbook (wbv);
154 		g_object_unref (wbv);
155 	});
156 	if (wb->wb_views != NULL)
157 		g_warning ("Unexpected left over views");
158 
159 	command_list_release (wb->undo_commands);
160 	wb->undo_commands = NULL;
161 	command_list_release (wb->redo_commands);
162 	wb->redo_commands = NULL;
163 
164 	dependents_workbook_destroy (wb);
165 
166 	/* Copy the set of sheets, the list changes under us. */
167 	sheets = g_ptr_array_sized_new (wb->sheets->len);
168 	for (ui = 0; ui < wb->sheets->len; ui++)
169 		g_ptr_array_add (sheets, g_ptr_array_index (wb->sheets, ui));
170 
171 	/* Remove all contents while all sheets still exist */
172 	for (ui = 0; ui < sheets->len; ui++) {
173 		Sheet *sheet = g_ptr_array_index (sheets, ui);
174 		GnmRange r;
175 
176 		sheet->being_destructed = TRUE;
177 
178 		sheet_destroy_contents (sheet);
179 		range_init_full_sheet (&r, sheet);
180 
181 		sheet_style_set_range (sheet, &r, sheet_style_default (sheet));
182 
183 		sheet->being_destructed = FALSE;
184 	}
185 
186 	/* Now remove the sheets themselves */
187 	for (ui = 0; ui < sheets->len; ui++) {
188 		Sheet *sheet = g_ptr_array_index (sheets, ui);
189 		workbook_sheet_delete (sheet);
190 	}
191 	g_ptr_array_unref (sheets);
192 
193 	// Now get rid of the control refs
194 	g_slist_free_full (controls, g_object_unref);
195 
196 	workbook_parent_class->dispose (wb_object);
197 }
198 
199 static void
workbook_finalize(GObject * obj)200 workbook_finalize (GObject *obj)
201 {
202 	Workbook *wb = WORKBOOK (obj);
203 
204 	/* Remove ourselves from the list of workbooks.  */
205 	gnm_app_workbook_list_remove (wb);
206 
207 	if (wb->sheet_local_functions) {
208 		g_hash_table_destroy (wb->sheet_local_functions);
209 		wb->sheet_local_functions = NULL;
210 	}
211 
212 	/* Now do deletions that will put this workbook into a weird
213 	   state.  Careful here.  */
214 	g_hash_table_destroy (wb->sheet_hash_private);
215 	wb->sheet_hash_private = NULL;
216 
217 	g_ptr_array_free (wb->sheets, TRUE);
218 	wb->sheets = NULL;
219 
220 	workbook_parent_class->finalize (obj);
221 }
222 
223 static void
workbook_init(GObject * object)224 workbook_init (GObject *object)
225 {
226 	Workbook *wb = WORKBOOK (object);
227 
228 	wb->is_placeholder = FALSE;
229 	wb->wb_views = NULL;
230 	wb->sheets = g_ptr_array_new ();
231 	wb->sheet_size_cached = FALSE;
232 	wb->sheet_hash_private = g_hash_table_new (g_str_hash, g_str_equal);
233 	wb->sheet_order_dependents = NULL;
234 	wb->sheet_local_functions = NULL;
235 	wb->names = gnm_named_expr_collection_new ();
236 
237 	/* Nothing to undo or redo */
238 	wb->undo_commands = wb->redo_commands = NULL;
239 
240 	/* default to no iteration */
241 	wb->iteration.enabled = TRUE;
242 	wb->iteration.max_number = 100;
243 	wb->iteration.tolerance = .001;
244 	wb->recalc_auto = TRUE;
245 
246 	workbook_set_1904 (wb, FALSE);
247 
248 	wb->file_format_level = GO_FILE_FL_NEW;
249 	wb->file_export_format_level = GO_FILE_FL_NEW;
250 	wb->file_saver        = NULL;
251 	wb->file_exporter     = NULL;
252 	wb->last_export_uri   = NULL;
253 
254 	wb->during_destruction = FALSE;
255 	wb->being_reordered    = FALSE;
256 	wb->recursive_dirty_enabled = TRUE;
257 	wb->being_loaded = FALSE;
258 
259 	gnm_app_workbook_list_add (wb);
260 }
261 
262 static void
workbook_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)263 workbook_get_property (GObject *object, guint property_id,
264 		       GValue *value, GParamSpec *pspec)
265 {
266 	Workbook *wb = (Workbook *)object;
267 
268 	switch (property_id) {
269 	case PROP_RECALC_MODE:
270 		g_value_set_boolean (value, wb->recalc_auto);
271 		break;
272 	case PROP_BEING_LOADED:
273 		g_value_set_boolean (value, wb->being_loaded);
274 		break;
275 	default:
276 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
277 		break;
278 	}
279 }
280 
281 static void
workbook_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)282 workbook_set_property (GObject *object, guint property_id,
283 		       const GValue *value, GParamSpec *pspec)
284 {
285 	Workbook *wb = (Workbook *)object;
286 
287 	switch (property_id) {
288 	case PROP_RECALC_MODE:
289 		workbook_set_recalcmode (wb, g_value_get_boolean (value));
290 		break;
291 	case PROP_BEING_LOADED:
292 		wb->being_loaded = g_value_get_boolean (value);
293 		break;
294 	default:
295 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
296 		break;
297 	}
298 }
299 
300 static GObject *
workbook_constructor(GType type,guint n_construct_properties,GObjectConstructParam * construct_params)301 workbook_constructor (GType type,
302 		      guint n_construct_properties,
303 		      GObjectConstructParam *construct_params)
304 {
305 	GObject *obj;
306 	Workbook *wb;
307 	static int count = 0;
308 	gboolean is_unique;
309 	GOFileSaver *def_save = go_file_saver_get_default ();
310 	char const *extension = NULL;
311 
312 	obj = workbook_parent_class->constructor
313 		(type, n_construct_properties, construct_params);
314 	wb = WORKBOOK (obj);
315 
316 	if (def_save != NULL)
317 		extension = go_file_saver_get_extension (def_save);
318 	if (extension == NULL)
319 		extension = "gnumeric";
320 
321 	/* Assign a default name */
322 	do {
323 		char *name, *nameutf8, *uri;
324 
325 		count++;
326 		nameutf8 = g_strdup_printf (_("Book%d.%s"), count, extension);
327 		name = g_filename_from_utf8 (nameutf8, -1, NULL, NULL, NULL);
328 		if (!name) {
329 			name = g_strdup_printf ("Book%d.%s", count, extension);
330 		}
331 		uri = go_filename_to_uri (name);
332 
333 		is_unique = go_doc_set_uri (GO_DOC (wb), uri);
334 
335 		g_free (uri);
336 		g_free (name);
337 		g_free (nameutf8);
338 	} while (!is_unique);
339 
340 	gnm_insert_meta_date (GO_DOC (wb), GSF_META_NAME_DATE_CREATED);
341 
342 	return obj;
343 }
344 
345 static void
workbook_class_init(GObjectClass * gobject_class)346 workbook_class_init (GObjectClass *gobject_class)
347 {
348 	workbook_parent_class = g_type_class_peek_parent (gobject_class);
349 
350 	gobject_class->constructor  = workbook_constructor;
351 	gobject_class->set_property = workbook_set_property;
352 	gobject_class->get_property = workbook_get_property;
353 	gobject_class->finalize	    = workbook_finalize;
354 	gobject_class->dispose	    = workbook_dispose;
355 
356         g_object_class_install_property (gobject_class, PROP_RECALC_MODE,
357 		 g_param_spec_boolean ("recalc-mode",
358 				       P_("Recalc mode"),
359 				       P_("Enable automatic recalculation."),
360 				       TRUE,
361 				       GSF_PARAM_STATIC |
362 				       G_PARAM_READWRITE));
363 
364         g_object_class_install_property (gobject_class, PROP_BEING_LOADED,
365 		 g_param_spec_boolean ("being-loaded",
366 				       P_("Being loaded"),
367 				       P_("Workbook is currently being loaded."),
368 				       TRUE,
369 				       GSF_PARAM_STATIC |
370 				       G_PARAM_READWRITE));
371 
372 	signals[SHEET_ORDER_CHANGED] = g_signal_new ("sheet_order_changed",
373 		GNM_WORKBOOK_TYPE,
374 		G_SIGNAL_RUN_LAST,
375 		G_STRUCT_OFFSET (WorkbookClass, sheet_order_changed),
376 		NULL, NULL,
377 		g_cclosure_marshal_VOID__VOID,
378 		G_TYPE_NONE,
379 		0, G_TYPE_NONE);
380 
381 	signals[SHEET_ADDED] = g_signal_new ("sheet_added",
382 		GNM_WORKBOOK_TYPE,
383 		G_SIGNAL_RUN_LAST,
384 		G_STRUCT_OFFSET (WorkbookClass, sheet_added),
385 		NULL, NULL,
386 		g_cclosure_marshal_VOID__VOID,
387 		G_TYPE_NONE,
388 		0, G_TYPE_NONE);
389 
390 	signals[SHEET_DELETED] = g_signal_new ("sheet_deleted",
391 		GNM_WORKBOOK_TYPE,
392 		G_SIGNAL_RUN_LAST,
393 		G_STRUCT_OFFSET (WorkbookClass, sheet_deleted),
394 		NULL, NULL,
395 		g_cclosure_marshal_VOID__VOID,
396 		G_TYPE_NONE,
397 		0, G_TYPE_NONE);
398 }
399 
400 /**
401  * workbook_new:
402  *
403  * Returns: A new empty #Workbook with a unique name.
404  **/
405 Workbook *
workbook_new(void)406 workbook_new (void)
407 {
408 	return g_object_new (GNM_WORKBOOK_TYPE, NULL);
409 }
410 
411 /**
412  * workbook_sheet_name_strip_number:
413  * @name: name to strip number from
414  * @number: returns the number stripped off, or 1 if no number.
415  *
416  * Gets a name in the form of "Sheet (10)", "Stuff" or "Dummy ((((,"
417  * and returns the real name of the sheet "Sheet ", "Stuff", "Dummy ((((,"
418  * without the copy number.
419  **/
420 static void
workbook_sheet_name_strip_number(char * name,unsigned int * number)421 workbook_sheet_name_strip_number (char *name, unsigned int *number)
422 {
423 	char *end, *p, *pend;
424 	unsigned long ul;
425 
426 	*number = 1;
427 	g_return_if_fail (*name != 0);
428 
429 	end = name + strlen (name) - 1;
430 	if (*end != ')')
431 		return;
432 
433 	for (p = end; p > name; p--)
434 		if (!g_ascii_isdigit (p[-1]))
435 			break;
436 
437 	if (p == name || p[-1] != '(')
438 		return;
439 
440 	errno = 0;
441 	ul = strtoul (p, &pend, 10);
442 	if (pend != end || ul != (unsigned int)ul || errno == ERANGE)
443 		return;
444 
445 	*number = (unsigned)ul;
446 	p[-1] = 0;
447 }
448 
449 /**
450  * workbook_new_with_sheets:
451  * @sheet_count: initial number of sheets to create.
452  *
453  * Returns: a #Workbook with @sheet_count allocated
454  * sheets on it
455  */
456 Workbook *
workbook_new_with_sheets(int sheet_count)457 workbook_new_with_sheets (int sheet_count)
458 {
459 	Workbook *wb = workbook_new ();
460 	int cols = gnm_conf_get_core_workbook_n_cols ();
461 	int rows = gnm_conf_get_core_workbook_n_rows ();
462 	if (!gnm_sheet_valid_size (cols, rows))
463 		gnm_sheet_suggest_size (&cols, &rows);
464 	while (sheet_count-- > 0)
465 		workbook_sheet_add (wb, -1, cols, rows);
466 	// Restore to pristine state
467 	go_doc_set_state (GO_DOC (wb), go_doc_get_saved_state (GO_DOC (wb)));
468 	go_doc_set_pristine (GO_DOC (wb), TRUE);
469 	return wb;
470 }
471 
472 void
workbook_mark_dirty(Workbook * wb)473 workbook_mark_dirty (Workbook *wb)
474 {
475 	go_doc_bump_state (GO_DOC (wb));
476 }
477 
478 
479 /**
480  * workbook_set_saveinfo:
481  * @wb: the workbook to modify
482  * @lev: the file format level
483  * @saver: (nullable): the file saver.
484  *
485  * If level is sufficiently advanced, assign the info.
486  *
487  * Returns: %TRUE if save info was set and history may require updating
488  *
489  * FIXME : Add a check to ensure the name is unique.
490  */
491 gboolean
workbook_set_saveinfo(Workbook * wb,GOFileFormatLevel level,GOFileSaver * fs)492 workbook_set_saveinfo (Workbook *wb, GOFileFormatLevel level, GOFileSaver *fs)
493 {
494 	g_return_val_if_fail (wb != NULL, FALSE);
495 	g_return_val_if_fail (level > GO_FILE_FL_NONE && level < GO_FILE_FL_LAST,
496 			      FALSE);
497 
498 	if (level != GO_FILE_FL_AUTO) {
499 		if (wb->file_exporter != NULL)
500 			g_object_weak_unref (G_OBJECT (wb->file_exporter),
501 					     (GWeakNotify) cb_exporter_finalize, wb);
502 		workbook_set_file_exporter (wb, fs);
503 		if (fs != NULL)
504 			g_object_weak_ref (G_OBJECT (fs),
505 					   (GWeakNotify) cb_exporter_finalize, wb);
506 	} else {
507 		if (wb->file_saver != NULL)
508 			g_object_weak_unref (G_OBJECT (wb->file_saver),
509 					     (GWeakNotify) cb_saver_finalize, wb);
510 
511 		wb->file_saver = fs;
512 		if (fs != NULL)
513 			g_object_weak_ref (G_OBJECT (fs),
514 					   (GWeakNotify) cb_saver_finalize, wb);
515 	}
516 
517 	if (level != GO_FILE_FL_AUTO) {
518 		wb->file_export_format_level = level;
519 		return FALSE;
520 	}
521 	wb->file_format_level = level;
522 	return TRUE;
523 }
524 
525 /**
526  * workbook_get_file_saver:
527  * @wb: #Workbook
528  *
529  * Returns: (transfer none): the saver for the Workbook.
530  **/
531 GOFileSaver *
workbook_get_file_saver(Workbook * wb)532 workbook_get_file_saver (Workbook *wb)
533 {
534 	g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
535 
536 	return wb->file_saver;
537 }
538 
539 /**
540  * workbook_get_file_exporter:
541  * @wb: #Workbook
542  *
543  * Returns: (transfer none): the exporter for the Workbook.
544  **/
545 GOFileSaver *
workbook_get_file_exporter(Workbook * wb)546 workbook_get_file_exporter (Workbook *wb)
547 {
548 	g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
549 
550 	return wb->file_exporter;
551 }
552 
553 /**
554  * workbook_get_last_export_uri:
555  * @wb: #Workbook
556  *
557  * Returns: (transfer none): the URI for export.
558  **/
559 gchar const *
workbook_get_last_export_uri(Workbook * wb)560 workbook_get_last_export_uri (Workbook *wb)
561 {
562 	g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
563 
564 	return wb->last_export_uri;
565 }
566 
567 void
workbook_set_file_exporter(Workbook * wb,GOFileSaver * fs)568 workbook_set_file_exporter (Workbook *wb, GOFileSaver *fs)
569 {
570 	wb->file_exporter = fs;
571 	WORKBOOK_FOREACH_CONTROL (wb, wbv, wbc,
572 				  wb_control_menu_state_update (wbc, MS_FILE_EXPORT_IMPORT););
573 }
574 
575 void
workbook_set_last_export_uri(Workbook * wb,const gchar * uri)576 workbook_set_last_export_uri (Workbook *wb, const gchar *uri)
577 {
578 	char *s = g_strdup (uri);
579 	g_free (wb->last_export_uri);
580 	wb->last_export_uri = s;
581 	WORKBOOK_FOREACH_CONTROL (wb, wbv, wbc,
582 				  wb_control_menu_state_update (wbc, MS_FILE_EXPORT_IMPORT););
583 }
584 
585 
586 /**
587  * workbook_foreach_cell_in_range:
588  * @pos: The position the range is relative to.
589  * @cell_range: A value containing a range;
590  * @flags: flags determining which cells to consider
591  * @handler: (scope call): The operator to apply to each cell.
592  * @closure: User data.
593  *
594  * The supplied value must be a cellrange.
595  * The range bounds are calculated relative to the eval position
596  * and normalized.
597  * For each existing cell in the range specified, invoke the
598  * callback routine.  If the only_existing flag is %TRUE, then
599  * callbacks are only invoked for existing cells.
600  *
601  * Note: this function does not honour the CELL_ITER_IGNORE_SUBTOTAL flag.
602  *
603  * Returns:
604  *    non-%NULL on error, or VALUE_TERMINATE if some the handler requested
605  *    to stop (by returning non-%NULL).
606  */
607 GnmValue *
workbook_foreach_cell_in_range(GnmEvalPos const * pos,GnmValue const * cell_range,CellIterFlags flags,CellIterFunc handler,gpointer closure)608 workbook_foreach_cell_in_range (GnmEvalPos const *pos,
609 				GnmValue const	*cell_range,
610 				CellIterFlags	 flags,
611 				CellIterFunc	 handler,
612 				gpointer	 closure)
613 {
614 	GnmRange  r;
615 	Sheet *start_sheet, *end_sheet;
616 
617 	g_return_val_if_fail (pos != NULL, NULL);
618 	g_return_val_if_fail (cell_range != NULL, NULL);
619 	g_return_val_if_fail (VALUE_IS_CELLRANGE (cell_range), NULL);
620 
621 	gnm_rangeref_normalize (&cell_range->v_range.cell, pos,
622 		&start_sheet, &end_sheet, &r);
623 
624 	if (start_sheet != end_sheet) {
625 		GnmValue *res;
626 		Workbook const *wb = start_sheet->workbook;
627 		int i = start_sheet->index_in_wb;
628 		int stop = end_sheet->index_in_wb;
629 		if (i > stop) { int tmp = i; i = stop ; stop = tmp; }
630 
631 		g_return_val_if_fail (end_sheet->workbook == wb, VALUE_TERMINATE);
632 
633 		for (; i <= stop ; i++) {
634 			res = sheet_foreach_cell_in_range (
635 				g_ptr_array_index (wb->sheets, i), flags, &r,
636 				handler, closure);
637 			if (res != NULL)
638 				return res;
639 		}
640 		return NULL;
641 	}
642 
643 	return sheet_foreach_cell_in_range (start_sheet, flags, &r,
644 		handler, closure);
645 }
646 
647 /**
648  * workbook_cells:
649  * @wb: The workbook to find cells in.
650  * @comments: If true, include cells with only comments also.
651  * @vis: How visible a sheet needs to be in order to be considered.
652  *
653  * Collects a GPtrArray of GnmEvalPos pointers for all cells in a workbook.
654  * No particular order should be assumed.
655  *
656  * Returns: (element-type GnmEvalPos) (transfer container): the cells array
657  */
658 GPtrArray *
workbook_cells(Workbook * wb,gboolean comments,GnmSheetVisibility vis)659 workbook_cells (Workbook *wb, gboolean comments, GnmSheetVisibility vis)
660 {
661 	GPtrArray *cells = g_ptr_array_new ();
662 
663 	g_return_val_if_fail (wb != NULL, cells);
664 
665 	WORKBOOK_FOREACH_SHEET (wb, sheet, {
666 		size_t oldlen = cells->len;
667 		GPtrArray *scells;
668 
669 		if (sheet->visibility > vis)
670 			continue;
671 
672 		scells = sheet_cell_positions (sheet, comments);
673 		if (scells->len) {
674 			g_ptr_array_set_size (cells, oldlen + scells->len);
675 			memcpy (&g_ptr_array_index (cells, oldlen),
676 				&g_ptr_array_index (scells, 0),
677 				scells->len * sizeof (GnmEvalPos *));
678 		}
679 		g_ptr_array_free (scells, TRUE);
680 	});
681 
682 	return cells;
683 }
684 
685 GnmExprSharer *
workbook_share_expressions(Workbook * wb,gboolean freeit)686 workbook_share_expressions (Workbook *wb, gboolean freeit)
687 {
688 	GnmExprSharer *es = gnm_expr_sharer_new ();
689 
690 	WORKBOOK_FOREACH_DEPENDENT (wb, dep, {
691 		if (dependent_is_cell (dep)) {
692 			/* Hopefully safe, even when linked.  */
693 			dep->texpr = gnm_expr_sharer_share (es, dep->texpr);
694 		} else {
695 			/* Not sure we want to touch this here.  */
696 		}
697 	});
698 
699 	if (gnm_debug_flag ("expr-sharer")) {
700 		g_printerr ("Sharing report for %s\n", go_doc_get_uri (GO_DOC (wb)));
701 		gnm_expr_sharer_report (es);
702 	}
703 
704 	if (freeit) {
705 		gnm_expr_sharer_unref (es);
706 		es = NULL;
707 	}
708 
709 	return es;
710 }
711 
712 void
workbook_optimize_style(Workbook * wb)713 workbook_optimize_style (Workbook *wb)
714 {
715 	WORKBOOK_FOREACH_SHEET (wb, sheet, {
716 		sheet_style_optimize (sheet);
717 	});
718 }
719 
720 /**
721  * workbook_foreach_name:
722  * @wb: #Workbook
723  * @globals_only: whether to apply only to global names.
724  * @func: (scope call): The operator to apply to each cell.
725  * @data: User data.
726  *
727  **/
728 void
workbook_foreach_name(Workbook const * wb,gboolean globals_only,GHFunc func,gpointer data)729 workbook_foreach_name (Workbook const *wb, gboolean globals_only,
730 		       GHFunc func, gpointer data)
731 {
732 	g_return_if_fail (GNM_IS_WORKBOOK (wb));
733 
734 	if (wb->names)
735 		gnm_named_expr_collection_foreach (wb->names, func, data);
736 
737 	if (!globals_only) {
738 		WORKBOOK_FOREACH_SHEET (wb, sheet, {
739 				gnm_sheet_foreach_name (sheet, func, data);
740 		});
741 	}
742 }
743 
744 
745 gboolean
workbook_enable_recursive_dirty(Workbook * wb,gboolean enable)746 workbook_enable_recursive_dirty (Workbook *wb, gboolean enable)
747 {
748 	gboolean old;
749 
750 	g_return_val_if_fail (GNM_IS_WORKBOOK (wb), FALSE);
751 
752 	old = wb->recursive_dirty_enabled;
753 	wb->recursive_dirty_enabled = enable;
754 	return old;
755 }
756 
757 void
workbook_set_recalcmode(Workbook * wb,gboolean is_auto)758 workbook_set_recalcmode (Workbook *wb, gboolean is_auto)
759 {
760 	g_return_if_fail (GNM_IS_WORKBOOK (wb));
761 
762 	is_auto = !!is_auto;
763 	if (is_auto == wb->recalc_auto)
764 		return;
765 
766 	wb->recalc_auto = is_auto;
767 	g_object_notify (G_OBJECT (wb), "recalc-mode");
768 }
769 
770 gboolean
workbook_get_recalcmode(Workbook const * wb)771 workbook_get_recalcmode (Workbook const *wb)
772 {
773 	g_return_val_if_fail (GNM_IS_WORKBOOK (wb), FALSE);
774 	return wb->recalc_auto;
775 }
776 
777 void
workbook_iteration_enabled(Workbook * wb,gboolean enable)778 workbook_iteration_enabled (Workbook *wb, gboolean enable)
779 {
780 	g_return_if_fail (GNM_IS_WORKBOOK (wb));
781 	wb->iteration.enabled = enable;
782 }
783 
784 void
workbook_iteration_max_number(Workbook * wb,int max_number)785 workbook_iteration_max_number (Workbook *wb, int max_number)
786 {
787 	g_return_if_fail (GNM_IS_WORKBOOK (wb));
788 	g_return_if_fail (max_number >= 0);
789 	wb->iteration.max_number = max_number;
790 }
791 
792 void
workbook_iteration_tolerance(Workbook * wb,double tolerance)793 workbook_iteration_tolerance (Workbook *wb, double tolerance)
794 {
795 	g_return_if_fail (GNM_IS_WORKBOOK (wb));
796 	g_return_if_fail (tolerance >= 0);
797 
798 	wb->iteration.tolerance = tolerance;
799 }
800 
801 void
workbook_attach_view(WorkbookView * wbv)802 workbook_attach_view (WorkbookView *wbv)
803 {
804 	Workbook *wb;
805 
806 	g_return_if_fail (GNM_IS_WORKBOOK_VIEW (wbv));
807 
808 	wb = wb_view_get_workbook (wbv);
809 	g_return_if_fail (GNM_IS_WORKBOOK (wb));
810 
811 	if (wb->wb_views == NULL)
812 		wb->wb_views = g_ptr_array_new ();
813 	g_ptr_array_add (wb->wb_views, wbv);
814 }
815 
816 void
workbook_detach_view(WorkbookView * wbv)817 workbook_detach_view (WorkbookView *wbv)
818 {
819 	g_return_if_fail (GNM_IS_WORKBOOK_VIEW (wbv));
820 	g_return_if_fail (GNM_IS_WORKBOOK (wbv->wb));
821 
822 	WORKBOOK_FOREACH_SHEET (wbv->wb, sheet, {
823 		SheetView *sv = sheet_get_view (sheet, wbv);
824 		gnm_sheet_view_dispose (sv);
825 	});
826 
827 	g_ptr_array_remove (wbv->wb->wb_views, wbv);
828 	if (wbv->wb->wb_views->len == 0) {
829 		g_ptr_array_free (wbv->wb->wb_views, TRUE);
830 		wbv->wb->wb_views = NULL;
831 	}
832 }
833 
834 /*****************************************************************************/
835 
836 /**
837  * workbook_sheets: (skip)
838  * @wb: #Workbook
839  *
840  * Get an ordered list of the sheets in the workbook
841  *
842  * Returns: (element-type Sheet) (transfer container): the sheets list.
843  */
844 GPtrArray *
workbook_sheets(Workbook const * wb)845 workbook_sheets (Workbook const *wb)
846 {
847 	g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
848 	return g_ptr_array_ref (wb->sheets);
849 }
850 
851 // Alternate version for the sake of introspection which is unhappy with
852 // the GPtrArray api.
853 
854 /**
855  * gnm_workbook_sheets0: (rename-to workbook_sheets)
856  * @wb: #Workbook
857  *
858  * Get an ordered list of the sheets in the workbook
859  *
860  * Returns: (element-type Sheet) (transfer full): the sheets list.
861  */
862 GSList *
gnm_workbook_sheets0(Workbook const * wb)863 gnm_workbook_sheets0 (Workbook const *wb)
864 {
865 	GSList *res = NULL;
866 	unsigned ui;
867 
868 	g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
869 
870 	for (ui = wb->sheets->len; ui--; ) {
871 		Sheet *sheet = g_ptr_array_index (wb->sheets, ui);
872 		res = g_slist_prepend (res, g_object_ref (sheet));
873 	}
874 
875 	return g_slist_reverse (res);
876 }
877 
878 int
workbook_sheet_count(Workbook const * wb)879 workbook_sheet_count (Workbook const *wb)
880 {
881 	g_return_val_if_fail (GNM_IS_WORKBOOK (wb), 0);
882 
883 	return wb->sheets ? wb->sheets->len : 0;
884 }
885 
886 static void
pre_sheet_index_change(Workbook * wb)887 pre_sheet_index_change (Workbook *wb)
888 {
889 	g_return_if_fail (!wb->being_reordered);
890 
891 	wb->being_reordered = TRUE;
892 
893 	if (wb->sheet_order_dependents != NULL)
894 		g_hash_table_foreach (wb->sheet_order_dependents,
895 				      (GHFunc)dependent_unlink,
896 				      NULL);
897 }
898 
899 static void
post_sheet_index_change(Workbook * wb)900 post_sheet_index_change (Workbook *wb)
901 {
902 	g_return_if_fail (wb->being_reordered);
903 
904 	if (wb->sheet_order_dependents != NULL)
905 		g_hash_table_foreach (wb->sheet_order_dependents,
906 				      (GHFunc)dependent_link,
907 				      NULL);
908 
909 	wb->being_reordered = FALSE;
910 
911 	if (wb->during_destruction)
912 		return;
913 
914 	g_signal_emit (G_OBJECT (wb), signals[SHEET_ORDER_CHANGED], 0);
915 }
916 
917 static void
workbook_sheet_index_update(Workbook * wb,int start)918 workbook_sheet_index_update (Workbook *wb, int start)
919 {
920 	int i;
921 
922 	for (i = wb->sheets->len ; i-- > start ; ) {
923 		Sheet *sheet = g_ptr_array_index (wb->sheets, i);
924 		sheet->index_in_wb = i;
925 	}
926 }
927 
928 /**
929  * workbook_sheet_by_index:
930  * @wb: workbook to lookup the sheet on
931  * @i: the sheet index we are looking for.
932  *
933  * Return value: (transfer none) (nullable): A #Sheet
934  */
935 Sheet *
workbook_sheet_by_index(Workbook const * wb,int i)936 workbook_sheet_by_index (Workbook const *wb, int i)
937 {
938 	g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
939 	g_return_val_if_fail (i >= -1, NULL);
940 
941 	// i = -1 is special, return NULL
942 	if (i == -1 || i >= (int)wb->sheets->len)
943 		return NULL;
944 
945 	return g_ptr_array_index (wb->sheets, i);
946 }
947 
948 /**
949  * workbook_sheet_by_name:
950  * @wb: workbook to lookup the sheet on
951  * @sheet_name: the sheet name we are looking for.  This is case insensitive.
952  *
953  * Return value: (transfer none) (nullable): A #Sheet
954  */
955 Sheet *
workbook_sheet_by_name(Workbook const * wb,char const * name)956 workbook_sheet_by_name (Workbook const *wb, char const *name)
957 {
958 	Sheet *sheet;
959 	char *tmp;
960 
961 	g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
962 	g_return_val_if_fail (name != NULL, NULL);
963 
964 	tmp = g_utf8_casefold (name, -1);
965 	sheet = g_hash_table_lookup (wb->sheet_hash_private, tmp);
966 	g_free (tmp);
967 
968 	return sheet;
969 }
970 
971 /*
972  * Find a sheet to focus on, left or right of sheet_index.
973  */
974 static Sheet *
workbook_focus_other_sheet(Workbook * wb,Sheet * sheet)975 workbook_focus_other_sheet (Workbook *wb, Sheet *sheet)
976 {
977 	int i;
978 	Sheet *focus = NULL;
979 	int sheet_index = sheet->index_in_wb;
980 
981 	for (i = sheet_index; !focus && --i >= 0; ) {
982 		Sheet *this_sheet = g_ptr_array_index (wb->sheets, i);
983 		if (this_sheet->visibility == GNM_SHEET_VISIBILITY_VISIBLE)
984 			focus = this_sheet;
985 	}
986 
987 	for (i = sheet_index; !focus && ++i < (int)wb->sheets->len; ) {
988 		Sheet *this_sheet = g_ptr_array_index (wb->sheets, i);
989 		if (this_sheet->visibility == GNM_SHEET_VISIBILITY_VISIBLE)
990 			focus = this_sheet;
991 	}
992 
993 	WORKBOOK_FOREACH_VIEW (wb, wbv, {
994 		if (sheet == wb_view_cur_sheet (wbv))
995 			wb_view_sheet_focus (wbv, focus);
996 	});
997 
998 	return focus;
999 }
1000 
1001 /**
1002  * workbook_sheet_remove_controls:
1003  * @wb: #Workbook
1004  * @sheet: #Sheet
1005  *
1006  * Remove the visible #SheetControls of a sheet and shut them down politely.
1007  *
1008  * Returns %TRUE if there are any remaining sheets visible
1009  **/
1010 static gboolean
workbook_sheet_remove_controls(Workbook * wb,Sheet * sheet)1011 workbook_sheet_remove_controls (Workbook *wb, Sheet *sheet)
1012 {
1013 	Sheet *focus = NULL;
1014 
1015 	g_return_val_if_fail (GNM_IS_WORKBOOK (wb), TRUE);
1016 	g_return_val_if_fail (IS_SHEET (sheet), TRUE);
1017 	g_return_val_if_fail (sheet->workbook == wb, TRUE);
1018 	g_return_val_if_fail (workbook_sheet_by_name (wb, sheet->name_unquoted) == sheet, TRUE);
1019 
1020 	/* Finish any object editing */
1021 	SHEET_FOREACH_CONTROL (sheet, view, control,
1022 		sc_mode_edit (control););
1023 
1024 	/* If not exiting, adjust the focus for any views whose focus sheet
1025 	 * was the one being deleted, and prepare to recalc */
1026 	if (!wb->during_destruction)
1027 		focus = workbook_focus_other_sheet (wb, sheet);
1028 
1029 	WORKBOOK_FOREACH_CONTROL (wb, wbv, wbc,
1030 		wb_control_sheet_remove (wbc, sheet););
1031 
1032 	return focus != NULL;
1033 }
1034 
1035 /**
1036  * workbook_sheet_attach_at_pos:
1037  * @wb: A #Workbook
1038  * @new_sheet: A #Sheet
1039  * @pos: position to attach @new_sheet at, -1 meaning at the end
1040  *
1041  * Add @new_sheet to @wb, placing it at @pos.
1042  */
1043 void
workbook_sheet_attach_at_pos(Workbook * wb,Sheet * new_sheet,int pos)1044 workbook_sheet_attach_at_pos (Workbook *wb, Sheet *new_sheet, int pos)
1045 {
1046 	g_return_if_fail (GNM_IS_WORKBOOK (wb));
1047 	g_return_if_fail (IS_SHEET (new_sheet));
1048 	g_return_if_fail (new_sheet->workbook == wb);
1049 	g_return_if_fail (pos >= -1 && pos <= (int)wb->sheets->len);
1050 
1051 	if (pos == -1)
1052 		pos = wb->sheets->len;
1053 
1054 	pre_sheet_index_change (wb);
1055 
1056 	g_object_ref (new_sheet);
1057 	g_ptr_array_insert (wb->sheets, pos, (gpointer)new_sheet);
1058 	workbook_sheet_index_update (wb, pos);
1059 	g_hash_table_insert (wb->sheet_hash_private,
1060 			     new_sheet->name_case_insensitive,
1061 			     new_sheet);
1062 	wb->sheet_size_cached = FALSE;
1063 
1064 	WORKBOOK_FOREACH_VIEW (wb, view,
1065 		wb_view_sheet_add (view, new_sheet););
1066 
1067 	/* Do not signal until after adding the views [#314208] */
1068 	post_sheet_index_change (wb);
1069 
1070 	workbook_mark_dirty (wb);
1071 }
1072 
1073 /**
1074  * workbook_sheet_attach:
1075  * @wb: A #Workbook
1076  * @new_sheet: (transfer full): A #Sheet to attach
1077  *
1078  * Add @new_sheet to @wb, placing it at the end.  SURPRISE: This assumes
1079  * a ref to the sheet.
1080  */
1081 void
workbook_sheet_attach(Workbook * wb,Sheet * new_sheet)1082 workbook_sheet_attach (Workbook *wb, Sheet *new_sheet)
1083 {
1084 	workbook_sheet_attach_at_pos (wb, new_sheet, -1);
1085 	/* Balance the ref added by the above call.  */
1086 	g_object_unref (new_sheet);
1087 }
1088 
1089 /**
1090  * workbook_sheet_add:
1091  * @wb: a #Workbook.
1092  * @pos: position to add, -1 meaning at end.
1093  * @columns: the sheet columns number.
1094  * @rows: the sheet rows number.
1095  *
1096  * Create and name a new sheet, putting it at position @pos.  The sheet
1097  * returned is not ref'd.  (The ref belongs to the workbook.)
1098  *
1099  * Return value: (transfer none): the new sheet.
1100  */
1101 Sheet *
workbook_sheet_add(Workbook * wb,int pos,int columns,int rows)1102 workbook_sheet_add (Workbook *wb, int pos, int columns, int rows)
1103 {
1104 	char *name = workbook_sheet_get_free_name (wb, _("Sheet"), TRUE, FALSE);
1105 	Sheet *new_sheet = sheet_new (wb, name, columns, rows);
1106 	g_free (name);
1107 
1108 	workbook_sheet_attach_at_pos (wb, new_sheet, pos);
1109 
1110 	/* FIXME: Why here?  */
1111 	g_signal_emit (G_OBJECT (wb), signals[SHEET_ADDED], 0);
1112 
1113 	g_object_unref (new_sheet);
1114 
1115 	return new_sheet;
1116 }
1117 
1118 /**
1119  * workbook_sheet_add_with_type:
1120  * @wb: a workbook.
1121  * @sheet_type: the sheet type.
1122  * @pos: position to add, -1 meaning append.
1123  * @columns: the sheet columns number.
1124  * @rows: the sheet rows number.
1125  *
1126  * Create and name a new sheet, putting it at position @pos.  The sheet
1127  * returned is not ref'd.  (The ref belongs to the workbook.)
1128  *
1129  * Return value: (transfer none): the new sheet.
1130  */
1131 Sheet *
workbook_sheet_add_with_type(Workbook * wb,GnmSheetType sheet_type,int pos,int columns,int rows)1132 workbook_sheet_add_with_type (Workbook *wb, GnmSheetType sheet_type, int pos, int columns, int rows)
1133 {
1134 	char *name = workbook_sheet_get_free_name (wb, (sheet_type == GNM_SHEET_OBJECT)? _("Graph"): _("Sheet"), TRUE, FALSE);
1135 	Sheet *new_sheet = sheet_new_with_type (wb, name, sheet_type, columns, rows);
1136 	g_free (name);
1137 
1138 	workbook_sheet_attach_at_pos (wb, new_sheet, pos);
1139 
1140 	/* FIXME: Why here?  */
1141 	g_signal_emit (G_OBJECT (wb), signals[SHEET_ADDED], 0);
1142 
1143 	g_object_unref (new_sheet);
1144 
1145 	return new_sheet;
1146 }
1147 
1148 /**
1149  * workbook_sheet_delete:
1150  * @sheet: the #Sheet that we want to delete from its workbook
1151  *
1152  * This function detaches the given sheet from its parent workbook and
1153  * invalidates all references to the deleted sheet from other sheets and
1154  * clears all references in the clipboard to this sheet.
1155  */
1156 void
workbook_sheet_delete(Sheet * sheet)1157 workbook_sheet_delete (Sheet *sheet)
1158 {
1159 	Workbook *wb;
1160 	int sheet_index;
1161 
1162         g_return_if_fail (IS_SHEET (sheet));
1163         g_return_if_fail (GNM_IS_WORKBOOK (sheet->workbook));
1164 
1165 	wb = sheet->workbook;
1166 	sheet_index = sheet->index_in_wb;
1167 
1168 	if (gnm_debug_flag ("sheets"))
1169 		g_printerr ("Removing sheet %s from %s\n",
1170 			    sheet->name_unquoted,
1171 			    go_doc_get_uri (GO_DOC (wb)));
1172 
1173 	gnm_app_clipboard_invalidate_sheet (sheet);
1174 
1175 	if (!wb->during_destruction) {
1176 		workbook_focus_other_sheet (wb, sheet);
1177 		/* During destruction this was already done.  */
1178 		dependents_invalidate_sheet (sheet, FALSE);
1179 		workbook_sheet_remove_controls (wb, sheet);
1180 	}
1181 
1182 	/* All is fine, remove the sheet */
1183 	pre_sheet_index_change (wb);
1184 	g_ptr_array_remove_index (wb->sheets, sheet_index);
1185 	wb->sheet_size_cached = FALSE;
1186 	workbook_sheet_index_update (wb, sheet_index);
1187 	sheet->index_in_wb = -1;
1188 	g_hash_table_remove (wb->sheet_hash_private, sheet->name_case_insensitive);
1189 	post_sheet_index_change (wb);
1190 
1191 	/* Clear the controls first, before we potentially update */
1192 	SHEET_FOREACH_VIEW (sheet, view, gnm_sheet_view_dispose (view););
1193 
1194 	g_signal_emit_by_name (G_OBJECT (sheet), "detached_from_workbook", wb);
1195 	g_object_unref (sheet);
1196 
1197 	if (!wb->during_destruction)
1198 		workbook_mark_dirty (wb);
1199 	g_signal_emit (G_OBJECT (wb), signals[SHEET_DELETED], 0);
1200 
1201 	if (!wb->during_destruction)
1202 		workbook_queue_all_recalc (wb);
1203 }
1204 
1205 /**
1206  * workbook_sheet_move:
1207  * @sheet: #Sheet to move
1208  * @direction: number of spots to move, positive for right and negative
1209  * for left.
1210  *
1211  * Moves the sheet up or down @direction spots in the sheet list
1212  * If @direction is negative, move left. If positive, move right.
1213  */
1214 void
workbook_sheet_move(Sheet * sheet,int direction)1215 workbook_sheet_move (Sheet *sheet, int direction)
1216 {
1217 	Workbook *wb;
1218 	gint old_pos, new_pos;
1219 
1220 	g_return_if_fail (IS_SHEET (sheet));
1221 
1222 	wb = sheet->workbook;
1223 
1224 	pre_sheet_index_change (wb);
1225         old_pos = sheet->index_in_wb;
1226 	new_pos = old_pos + direction;
1227 
1228 	if (0 <= new_pos && new_pos < workbook_sheet_count (wb)) {
1229 		int min_pos = MIN (old_pos, new_pos);
1230 		int max_pos = MAX (old_pos, new_pos);
1231 
1232 		g_ptr_array_remove_index (wb->sheets, old_pos);
1233 		g_ptr_array_insert (wb->sheets, new_pos, sheet);
1234 
1235 		for (; max_pos >= min_pos ; max_pos--) {
1236 			Sheet *sheet = g_ptr_array_index (wb->sheets, max_pos);
1237 			sheet->index_in_wb = max_pos;
1238 		}
1239 	}
1240 
1241 	post_sheet_index_change (wb);
1242 
1243 	workbook_mark_dirty (wb);
1244 }
1245 
1246 /**
1247  * workbook_sheet_get_free_name:
1248  * @wb: #Workbook for which the new name can be used
1249  * @base: base for the name, e. g. "Sheet"
1250  * @always_suffix: if true, add suffix even if the name "base" is not in use.
1251  * @handle_counter: strip counter if necessary
1252  *
1253  * Gets a new unquoted name for a sheets such that it does not exist on the
1254  * workbook.
1255  *
1256  * Returns: (transfer full): a unique sheet name
1257  **/
1258 char *
workbook_sheet_get_free_name(Workbook * wb,char const * base,gboolean always_suffix,gboolean handle_counter)1259 workbook_sheet_get_free_name (Workbook *wb,
1260 			      char const *base,
1261 			      gboolean always_suffix,
1262 			      gboolean handle_counter)
1263 {
1264 	char const *name_format;
1265 	char *name, *base_name;
1266 	unsigned int i = 0;
1267 	int limit;
1268 
1269 	g_return_val_if_fail (wb != NULL, NULL);
1270 
1271 	if (!always_suffix && (workbook_sheet_by_name (wb, base) == NULL))
1272 		return g_strdup (base); /* Name not in use */
1273 
1274 	base_name = g_strdup (base);
1275 	if (handle_counter) {
1276 		workbook_sheet_name_strip_number (base_name, &i);
1277 		name_format = "%s(%u)";
1278 	} else
1279 		name_format = "%s%u";
1280 
1281 	limit = workbook_sheet_count (wb) + 2;
1282 	name = g_malloc (strlen (base_name) + strlen (name_format) + 10);
1283 	while (limit-- > 0) {
1284 		i++;
1285 		sprintf (name, name_format, base_name, i);
1286 		if (workbook_sheet_by_name (wb, name) == NULL) {
1287 			g_free (base_name);
1288 			return name;
1289 		}
1290 	}
1291 
1292 	/* We should not get here.  */
1293 	g_warning ("There is trouble at the mill.");
1294 
1295 	g_free (name);
1296 	g_free (base_name);
1297 	name = g_strdup_printf ("%s (%i)", base, 2);
1298 	return name;
1299 }
1300 
1301 /**
1302  * workbook_sheet_rename:
1303  * @wb: #Workbook in which to rename sheets
1304  * @sheet_indices: (element-type int): list of sheet indices (ignore -1)
1305  * @new_names: (element-type utf8): list of new names
1306  *
1307  * Adjusts the names of the sheets. We assume that everything is
1308  * valid. If in doubt call workbook_sheet_reorder_check first.
1309  *
1310  * Returns: %FALSE when it was successful
1311  **/
1312 gboolean
workbook_sheet_rename(Workbook * wb,GSList * sheet_indices,GSList * new_names,G_GNUC_UNUSED GOCmdContext * cc)1313 workbook_sheet_rename (Workbook *wb,
1314 		       GSList *sheet_indices,
1315 		       GSList *new_names,
1316 		       G_GNUC_UNUSED GOCmdContext *cc)
1317 {
1318 	GSList *sheet_index = sheet_indices;
1319 	GSList *new_name = new_names;
1320 
1321 	while (new_name && sheet_index) {
1322 		int ix = GPOINTER_TO_INT (sheet_index->data);
1323 		const char *name = new_name->data;
1324 		if (ix != -1)
1325 			g_hash_table_remove (wb->sheet_hash_private, name);
1326 		sheet_index = sheet_index->next;
1327 		new_name = new_name->next;
1328 	}
1329 
1330 	sheet_index = sheet_indices;
1331 	new_name = new_names;
1332 	while (new_name && sheet_index) {
1333 		int ix = GPOINTER_TO_INT (sheet_index->data);
1334 		const char *name = new_name->data;
1335 		if (ix != -1) {
1336 			Sheet *sheet = workbook_sheet_by_index (wb, ix);
1337 			g_object_set (sheet, "name", name, NULL);
1338 		}
1339 		sheet_index = sheet_index->next;
1340 		new_name = new_name->next;
1341 	}
1342 
1343 	return FALSE;
1344 }
1345 
1346 /**
1347  * workbook_find_command:
1348  * @wb: #Workbook
1349  * @is_undo: undo vs redo
1350  * @cmd: command
1351  *
1352  * Returns: the 1 based index of the @cmd command, or 0 if it is not found
1353  * (which would be a programmer error).
1354  **/
1355 unsigned
workbook_find_command(Workbook * wb,gboolean is_undo,gpointer cmd)1356 workbook_find_command (Workbook *wb, gboolean is_undo, gpointer cmd)
1357 {
1358 	GSList *ptr;
1359 	unsigned n = 1;
1360 
1361 	g_return_val_if_fail (GNM_IS_WORKBOOK (wb), 0);
1362 
1363 	ptr = is_undo ? wb->undo_commands : wb->redo_commands;
1364 	for ( ; ptr != NULL ; ptr = ptr->next, n++)
1365 		if (ptr->data == cmd)
1366 			return n;
1367 	g_warning ("%s command : %p not found", is_undo ? "undo" : "redo", cmd);
1368 	return 0;
1369 }
1370 
1371 /**
1372  * workbook_sheet_reorder:
1373  * @wb: workbook to reorder
1374  * @new_order: (element-type Sheet): list of #Sheet
1375  *
1376  * Adjusts the order of the sheets.
1377  *
1378  * Returns %FALSE when it was successful
1379  **/
1380 gboolean
workbook_sheet_reorder(Workbook * wb,GSList * new_order)1381 workbook_sheet_reorder (Workbook *wb, GSList *new_order)
1382 {
1383 	GSList   *ptr;
1384 	Sheet    *sheet;
1385 	unsigned  pos = 0;
1386 
1387 	g_return_val_if_fail (GNM_IS_WORKBOOK (wb), FALSE);
1388 	g_return_val_if_fail (g_slist_length (new_order) == wb->sheets->len, FALSE);
1389 
1390 	pre_sheet_index_change (wb);
1391 
1392 	for (ptr = new_order; NULL != ptr ; ptr = ptr->next, pos++) {
1393 		g_ptr_array_index (wb->sheets, pos) = sheet = ptr->data;
1394 		sheet->index_in_wb = pos;
1395 	}
1396 
1397 	post_sheet_index_change (wb);
1398 
1399 	return FALSE;
1400 }
1401 
1402 /**
1403  * workbook_date_conv:
1404  * @wb: Workbook
1405  *
1406  * Returns: (transfer none): the date conventions in effect for the workbook.
1407  **/
1408 GODateConventions const *
workbook_date_conv(Workbook const * wb)1409 workbook_date_conv (Workbook const *wb)
1410 {
1411 	g_return_val_if_fail (wb != NULL, NULL);
1412 	return wb->date_conv;
1413 }
1414 
1415 /**
1416  * workbook_set_date_conv:
1417  * @wb: workbook
1418  * @date_conv: new date convention
1419  *
1420  * Sets the date convention @date_conv.
1421  * NOTE : THIS IS NOT A SMART ROUTINE.  If you want to actually change this
1422  * We'll need to recalc and rerender everything.  That will need to be done
1423  * externally.
1424  **/
1425 void
workbook_set_date_conv(Workbook * wb,GODateConventions const * date_conv)1426 workbook_set_date_conv (Workbook *wb, GODateConventions const *date_conv)
1427 {
1428 	g_return_if_fail (GNM_IS_WORKBOOK (wb));
1429 	g_return_if_fail (date_conv != NULL);
1430 
1431 	wb->date_conv = date_conv;
1432 }
1433 
1434 void
workbook_set_1904(Workbook * wb,gboolean base1904)1435 workbook_set_1904 (Workbook *wb, gboolean base1904)
1436 {
1437 	GODateConventions const *date_conv =
1438 		go_date_conv_from_str (base1904 ? "Apple:1904" : "Lotus:1900");
1439 	workbook_set_date_conv (wb, date_conv);
1440 }
1441 
1442 /**
1443  * workbook_get_sheet_size:
1444  * @wb: (nullable): #Workbook
1445  *
1446  * Returns: (transfer none): the current sheet size for @wb.  If sheets are
1447  * not of uniform size, this will be some size that is big enough in both
1448  * directions for all sheets.  That size isn't necessarily one that could
1449  * be used to create a new sheet.
1450  **/
1451 GnmSheetSize const *
workbook_get_sheet_size(Workbook const * wb)1452 workbook_get_sheet_size (Workbook const *wb)
1453 {
1454 	static const GnmSheetSize max_size = {
1455 		GNM_MAX_COLS, GNM_MAX_ROWS
1456 	};
1457 	int n = wb ? workbook_sheet_count (wb) : 0;
1458 
1459 	if (n == 0)
1460 		return &max_size;
1461 
1462 	if (!wb->sheet_size_cached) {
1463 		Workbook *wb1 = (Workbook *)wb;
1464 		int i;
1465 
1466 		wb1->sheet_size = *gnm_sheet_get_size (workbook_sheet_by_index (wb, 0));
1467 		for (i = 1; i < n; i++) {
1468 			Sheet *sheet = workbook_sheet_by_index (wb, i);
1469 			GnmSheetSize const *ss = gnm_sheet_get_size (sheet);
1470 			wb1->sheet_size.max_cols = MAX (wb->sheet_size.max_cols, ss->max_cols);
1471 			wb1->sheet_size.max_rows = MAX (wb->sheet_size.max_rows, ss->max_rows);
1472 		}
1473 
1474 		wb1->sheet_size_cached = TRUE;
1475 	}
1476 
1477 	return &wb->sheet_size;
1478 }
1479 
1480 /* ------------------------------------------------------------------------- */
1481 
1482 typedef struct {
1483 	Sheet *sheet;
1484 	GSList *properties;
1485 } WorkbookSheetStateSheet;
1486 
1487 struct _WorkbookSheetState {
1488 	GSList *properties;
1489 	int n_sheets;
1490 	WorkbookSheetStateSheet *sheets;
1491 	unsigned ref_count;
1492 };
1493 
1494 
1495 WorkbookSheetState *
workbook_sheet_state_new(const Workbook * wb)1496 workbook_sheet_state_new (const Workbook *wb)
1497 {
1498 	int i;
1499 	WorkbookSheetState *wss = g_new (WorkbookSheetState, 1);
1500 
1501 	wss->properties = go_object_properties_collect (G_OBJECT (wb));
1502 	wss->n_sheets = workbook_sheet_count (wb);
1503 	wss->sheets = g_new (WorkbookSheetStateSheet, wss->n_sheets);
1504 	for (i = 0; i < wss->n_sheets; i++) {
1505 		WorkbookSheetStateSheet *wsss = wss->sheets + i;
1506 		wsss->sheet = g_object_ref (workbook_sheet_by_index (wb, i));
1507 		wsss->properties = go_object_properties_collect (G_OBJECT (wsss->sheet));
1508 	}
1509 	wss->ref_count = 1;
1510 	return wss;
1511 }
1512 
1513 void
workbook_sheet_state_unref(WorkbookSheetState * wss)1514 workbook_sheet_state_unref (WorkbookSheetState *wss)
1515 {
1516 	int i;
1517 
1518 	if (!wss || wss->ref_count-- > 1)
1519 		return;
1520 
1521 	go_object_properties_free (wss->properties);
1522 
1523 	for (i = 0; i < wss->n_sheets; i++) {
1524 		WorkbookSheetStateSheet *wsss = wss->sheets + i;
1525 		g_object_unref (wsss->sheet);
1526 		go_object_properties_free (wsss->properties);
1527 	}
1528 	g_free (wss->sheets);
1529 	g_free (wss);
1530 }
1531 
1532 static WorkbookSheetState *
workbook_sheet_state_ref(WorkbookSheetState * wss)1533 workbook_sheet_state_ref (WorkbookSheetState *wss)
1534 {
1535 	wss->ref_count++;
1536 	return wss;
1537 }
1538 
1539 GType
workbook_sheet_state_get_type(void)1540 workbook_sheet_state_get_type (void)
1541 {
1542 	static GType t = 0;
1543 
1544 	if (t == 0) {
1545 		t = g_boxed_type_register_static ("WorkbookSheetState",
1546 			 (GBoxedCopyFunc)workbook_sheet_state_ref,
1547 			 (GBoxedFreeFunc)workbook_sheet_state_unref);
1548 	}
1549 	return t;
1550 }
1551 
1552 void
workbook_sheet_state_restore(Workbook * wb,const WorkbookSheetState * wss)1553 workbook_sheet_state_restore (Workbook *wb, const WorkbookSheetState *wss)
1554 {
1555 	int i;
1556 
1557 	/* Get rid of sheets that shouldn't be there.  */
1558 	for (i = workbook_sheet_count (wb) ; i-- > 0; ) {
1559 		Sheet *sheet = workbook_sheet_by_index (wb, i);
1560 		int j;
1561 		for (j = 0; j < wss->n_sheets; j++)
1562 			if (sheet == wss->sheets[j].sheet)
1563 				break;
1564 		if (j == wss->n_sheets)
1565 			workbook_sheet_delete (sheet);
1566 	}
1567 
1568 	/* Attach new sheets and handle order.  */
1569 	for (i = 0; i < wss->n_sheets; i++) {
1570 		Sheet *sheet = wss->sheets[i].sheet;
1571 		if (sheet->index_in_wb != i) {
1572 			if (sheet->index_in_wb == -1) {
1573 				workbook_sheet_attach_at_pos (wb, sheet, i);
1574 				dependents_revive_sheet (sheet);
1575 			} else {
1576 				/*
1577 				 * There might be a smarter way of getting more
1578 				 * sheets into place faster.  This will at
1579 				 * least work.
1580 				 */
1581 				workbook_sheet_move (sheet, i - sheet->index_in_wb);
1582 			}
1583 		}
1584 		go_object_properties_apply (G_OBJECT (sheet),
1585 					    wss->sheets[i].properties,
1586 					    TRUE);
1587 	}
1588 
1589 	go_object_properties_apply (G_OBJECT (wb), wss->properties, TRUE);
1590 }
1591 
1592 int
workbook_sheet_state_size(const WorkbookSheetState * wss)1593 workbook_sheet_state_size (const WorkbookSheetState *wss)
1594 {
1595 	int size = 1 + g_slist_length (wss->properties);
1596 	int i;
1597 	for (i = 0; i < wss->n_sheets; i++) {
1598 		WorkbookSheetStateSheet *wsss = wss->sheets + i;
1599 		size += 5;  /* For ->sheet.  */
1600 		size += g_slist_length (wsss->properties);
1601 	}
1602 	return size;
1603 }
1604 
1605 GNM_BEGIN_KILL_SWITCH_WARNING
1606 char *
workbook_sheet_state_diff(const WorkbookSheetState * wss_a,const WorkbookSheetState * wss_b)1607 workbook_sheet_state_diff (const WorkbookSheetState *wss_a, const WorkbookSheetState *wss_b)
1608 {
1609 	enum {
1610 		WSS_SHEET_RENAMED = 1,
1611 		WSS_SHEET_ADDED = 2,
1612 		WSS_SHEET_TAB_COLOR = 4,
1613 		WSS_SHEET_PROPERTIES = 8,
1614 		WSS_SHEET_DELETED = 16,
1615 		WSS_SHEET_ORDER = 32,
1616 		WSS_FUNNY = 0x40000000
1617 	} what = 0;
1618 	int ia;
1619 	int n = 0;
1620 	int n_added, n_deleted = 0;
1621 
1622 	for (ia = 0; ia < wss_a->n_sheets; ia++) {
1623 		Sheet *sheet = wss_a->sheets[ia].sheet;
1624 		int ib;
1625 		GSList *pa, *pb;
1626 		int diff = 0;
1627 
1628 		for (ib = 0; ib < wss_b->n_sheets; ib++)
1629 			if (sheet == wss_b->sheets[ib].sheet)
1630 				break;
1631 		if (ib == wss_b->n_sheets) {
1632 			what |= WSS_SHEET_DELETED;
1633 			n++;
1634 			n_deleted++;
1635 			continue;
1636 		}
1637 
1638 		if (ia != ib) {
1639 			what |= WSS_SHEET_ORDER;
1640 			/* We do not count reordered sheet.  */
1641 		}
1642 
1643 		pa = wss_a->sheets[ia].properties;
1644 		pb = wss_b->sheets[ib].properties;
1645 		for (; pa && pb; pa = pa->next->next, pb = pb->next->next) {
1646 			GParamSpec *pspec = pa->data;
1647 			const GValue *va = pa->next->data;
1648 			const GValue *vb = pb->next->data;
1649 			if (pspec != pb->data)
1650 				break;
1651 
1652 			if (g_param_values_cmp (pspec, va, vb) == 0)
1653 				continue;
1654 
1655 			diff = 1;
1656 			if (strcmp (pspec->name, "name") == 0)
1657 				what |= WSS_SHEET_RENAMED;
1658 			else if (strcmp (pspec->name, "tab-foreground") == 0)
1659 				what |= WSS_SHEET_TAB_COLOR;
1660 			else if (strcmp (pspec->name, "tab-background") == 0)
1661 				what |= WSS_SHEET_TAB_COLOR;
1662 			else
1663 				what |= WSS_SHEET_PROPERTIES;
1664 		}
1665 
1666 		if (pa || pb)
1667 			what |= WSS_FUNNY;
1668 		n += diff;
1669 	}
1670 
1671 	n_added = wss_b->n_sheets - (wss_a->n_sheets - n_deleted);
1672 	if (n_added) {
1673 		what |= WSS_SHEET_ADDED;
1674 		n += n_added;
1675 	}
1676 
1677 	switch (what) {
1678 	case WSS_SHEET_RENAMED:
1679 		return g_strdup_printf (ngettext ("Renaming sheet", "Renaming %d sheets", n), n);
1680 	case WSS_SHEET_ADDED:
1681 		return g_strdup_printf (ngettext ("Adding sheet", "Adding %d sheets", n), n);
1682 	case WSS_SHEET_ADDED | WSS_SHEET_ORDER:
1683 		/*
1684 		 * This is most likely just a sheet inserted, but it just
1685 		 * might be a compound operation.  Lie.
1686 		 */
1687 		return g_strdup_printf (ngettext ("Inserting sheet", "Inserting %d sheets", n), n);
1688 	case WSS_SHEET_TAB_COLOR:
1689 		return g_strdup (_("Changing sheet tab colors"));
1690 	case WSS_SHEET_PROPERTIES:
1691 		return g_strdup (_("Changing sheet properties"));
1692 	case WSS_SHEET_DELETED:
1693 	case WSS_SHEET_DELETED | WSS_SHEET_ORDER:
1694 		/*
1695 		 * This is most likely just a sheet delete, but it just
1696 		 * might be a compound operation.  Lie.
1697 		 */
1698 		return g_strdup_printf (ngettext ("Deleting sheet", "Deleting %d sheets", n), n);
1699 	case WSS_SHEET_ORDER:
1700 		return g_strdup (_("Changing sheet order"));
1701 	default:
1702 		return g_strdup (_("Reorganizing Sheets"));
1703 	}
1704 }
1705 GNM_END_KILL_SWITCH_WARNING
1706 
1707 /* ------------------------------------------------------------------------- */
1708 
1709 GSF_CLASS (Workbook, workbook,
1710 	   workbook_class_init, workbook_init,
1711 	   GO_TYPE_DOC)
1712