Move plugin init code
[gnumeric.git] / src / workbook.c
blobf641dbb15627375851d9c9c62483ecaa04d33c2f
2 /*
3 * workbook.c: workbook model and manipulation utilities
5 * Authors:
6 * Miguel de Icaza (miguel@gnu.org).
7 * Jody Goldberg (jody@gnome.org)
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)
14 #include <gnumeric-config.h>
15 #include <gnumeric.h>
16 #include <workbook-priv.h>
17 #include <compilation.h>
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>
42 #include <goffice/goffice.h>
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>
51 /**
52 * Workbook:
53 * @wb_views: (element-type WorkbookView):
54 **/
56 enum {
57 PROP_0,
58 PROP_RECALC_MODE
60 enum {
61 SHEET_ORDER_CHANGED,
62 SHEET_ADDED,
63 SHEET_DELETED,
64 LAST_SIGNAL
66 static guint signals[LAST_SIGNAL] = { 0 };
67 static GObjectClass *workbook_parent_class;
69 static void
70 cb_saver_finalize (Workbook *wb, GOFileSaver *saver)
72 g_return_if_fail (GO_IS_FILE_SAVER (saver));
73 g_return_if_fail (GNM_IS_WORKBOOK (wb));
74 g_return_if_fail (wb->file_saver == saver);
75 wb->file_saver = NULL;
77 static void
78 cb_exporter_finalize (Workbook *wb, GOFileSaver *saver)
80 g_return_if_fail (GO_IS_FILE_SAVER (saver));
81 g_return_if_fail (GNM_IS_WORKBOOK (wb));
82 g_return_if_fail (wb->file_exporter == saver);
83 workbook_set_file_exporter (wb, NULL);
86 void
87 workbook_update_history (Workbook *wb, GnmFileSaveAsStyle type)
89 g_return_if_fail (GNM_IS_WORKBOOK (wb));
91 switch (type) {
92 case GNM_FILE_SAVE_AS_STYLE_SAVE:
93 if (wb->doc.uri && wb->file_format_level >= GO_FILE_FL_MANUAL_REMEMBER) {
94 const char *mimetype = wb->file_saver
95 ? go_file_saver_get_mime_type (wb->file_saver)
96 : NULL;
97 gnm_app_history_add (wb->doc.uri, mimetype);
99 break;
100 case GNM_FILE_SAVE_AS_STYLE_EXPORT:
101 default:
102 if (wb->last_export_uri &&
103 wb->file_export_format_level >= GO_FILE_FL_MANUAL_REMEMBER) {
104 const char *mimetype = wb->file_exporter
105 ? go_file_saver_get_mime_type (wb->file_exporter)
106 : NULL;
107 gnm_app_history_add (wb->last_export_uri, mimetype);
109 break;
113 void
114 workbook_update_graphs (Workbook *wb)
116 WORKBOOK_FOREACH_SHEET (wb, sheet, ({
117 GSList *l, *graphs = sheet_objects_get (sheet, NULL, GNM_SO_GRAPH_TYPE);
118 for (l = graphs; l; l = l->next) {
119 SheetObject *sog = l->data;
120 gog_graph_force_update (sheet_object_graph_get_gog (sog));
122 g_slist_free (graphs);
123 }));
127 static void
128 workbook_dispose (GObject *wb_object)
130 Workbook *wb = WORKBOOK (wb_object);
131 GSList *sheets, *ptr;
132 GSList *controls = NULL;
134 wb->during_destruction = TRUE;
136 if (wb->file_saver)
137 workbook_set_saveinfo (wb, GO_FILE_FL_AUTO, NULL);
138 if (wb->file_exporter)
139 workbook_set_saveinfo (wb, GO_FILE_FL_WRITE_ONLY, NULL);
140 workbook_set_last_export_uri (wb, NULL);
142 // Remove all the sheet controls to avoid displaying while we exit
143 // However, hold on to a ref for each -- dialogs like to refer
144 // to ->wbcg during destruction
145 WORKBOOK_FOREACH_CONTROL (wb, view, control,
146 controls = g_slist_prepend (controls, g_object_ref (control));
147 wb_control_sheet_remove_all (control););
149 /* Get rid of all the views */
150 WORKBOOK_FOREACH_VIEW (wb, wbv, {
151 wb_view_detach_from_workbook (wbv);
152 g_object_unref (wbv);
154 if (wb->wb_views != NULL)
155 g_warning ("Unexpected left over views");
157 command_list_release (wb->undo_commands);
158 wb->undo_commands = NULL;
159 command_list_release (wb->redo_commands);
160 wb->redo_commands = NULL;
162 dependents_workbook_destroy (wb);
164 /* Copy the set of sheets, the list changes under us. */
165 sheets = workbook_sheets (wb);
167 /* Remove all contents while all sheets still exist */
168 for (ptr = sheets; ptr != NULL ; ptr = ptr->next) {
169 Sheet *sheet = ptr->data;
170 GnmRange r;
172 sheet_destroy_contents (sheet);
173 range_init_full_sheet (&r, sheet);
174 sheet_style_set_range (sheet, &r, sheet_style_default (sheet));
177 /* Now remove the sheets themselves */
178 for (ptr = sheets; ptr != NULL ; ptr = ptr->next) {
179 Sheet *sheet = ptr->data;
180 workbook_sheet_delete (sheet);
182 g_slist_free (sheets);
184 // Now get rid of the control refs
185 g_slist_free_full (controls, g_object_unref);
187 workbook_parent_class->dispose (wb_object);
190 static void
191 workbook_finalize (GObject *obj)
193 Workbook *wb = WORKBOOK (obj);
195 /* Remove ourselves from the list of workbooks. */
196 gnm_app_workbook_list_remove (wb);
198 if (wb->sheet_local_functions) {
199 g_hash_table_destroy (wb->sheet_local_functions);
200 wb->sheet_local_functions = NULL;
203 /* Now do deletions that will put this workbook into a weird
204 state. Careful here. */
205 g_hash_table_destroy (wb->sheet_hash_private);
206 wb->sheet_hash_private = NULL;
208 g_ptr_array_free (wb->sheets, TRUE);
209 wb->sheets = NULL;
211 workbook_parent_class->finalize (obj);
214 static void
215 workbook_init (GObject *object)
217 Workbook *wb = WORKBOOK (object);
219 wb->is_placeholder = FALSE;
220 wb->wb_views = NULL;
221 wb->sheets = g_ptr_array_new ();
222 wb->sheet_hash_private = g_hash_table_new (g_str_hash, g_str_equal);
223 wb->sheet_order_dependents = NULL;
224 wb->sheet_local_functions = NULL;
225 wb->names = gnm_named_expr_collection_new ();
227 /* Nothing to undo or redo */
228 wb->undo_commands = wb->redo_commands = NULL;
230 /* default to no iteration */
231 wb->iteration.enabled = TRUE;
232 wb->iteration.max_number = 100;
233 wb->iteration.tolerance = .001;
234 wb->recalc_auto = TRUE;
236 workbook_set_1904 (wb, FALSE);
238 wb->file_format_level = GO_FILE_FL_NEW;
239 wb->file_export_format_level = GO_FILE_FL_NEW;
240 wb->file_saver = NULL;
241 wb->file_exporter = NULL;
242 wb->last_export_uri = NULL;
244 wb->during_destruction = FALSE;
245 wb->being_reordered = FALSE;
246 wb->recursive_dirty_enabled = TRUE;
248 gnm_app_workbook_list_add (wb);
251 static void
252 workbook_get_property (GObject *object, guint property_id,
253 GValue *value, GParamSpec *pspec)
255 Workbook *wb = (Workbook *)object;
257 switch (property_id) {
258 case PROP_RECALC_MODE:
259 g_value_set_boolean (value, wb->recalc_auto);
260 break;
261 default:
262 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
263 break;
267 static void
268 workbook_set_property (GObject *object, guint property_id,
269 const GValue *value, GParamSpec *pspec)
271 Workbook *wb = (Workbook *)object;
273 switch (property_id) {
274 case PROP_RECALC_MODE:
275 workbook_set_recalcmode (wb, g_value_get_boolean (value));
276 break;
277 default:
278 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
279 break;
283 static GObject *
284 workbook_constructor (GType type,
285 guint n_construct_properties,
286 GObjectConstructParam *construct_params)
288 GObject *obj;
289 Workbook *wb;
290 static int count = 0;
291 gboolean is_unique;
292 GOFileSaver *def_save = go_file_saver_get_default ();
293 char const *extension = NULL;
295 obj = workbook_parent_class->constructor
296 (type, n_construct_properties, construct_params);
297 wb = WORKBOOK (obj);
299 if (def_save != NULL)
300 extension = go_file_saver_get_extension (def_save);
301 if (extension == NULL)
302 extension = "gnumeric";
304 /* Assign a default name */
305 do {
306 char *name, *nameutf8, *uri;
308 count++;
309 nameutf8 = g_strdup_printf (_("Book%d.%s"), count, extension);
310 name = g_filename_from_utf8 (nameutf8, -1, NULL, NULL, NULL);
311 if (!name) {
312 name = g_strdup_printf ("Book%d.%s", count, extension);
314 uri = go_filename_to_uri (name);
316 is_unique = go_doc_set_uri (GO_DOC (wb), uri);
318 g_free (uri);
319 g_free (name);
320 g_free (nameutf8);
321 } while (!is_unique);
323 gnm_insert_meta_date (GO_DOC (wb), GSF_META_NAME_DATE_CREATED);
325 return obj;
328 static void
329 workbook_class_init (GObjectClass *gobject_class)
331 workbook_parent_class = g_type_class_peek_parent (gobject_class);
333 gobject_class->constructor = workbook_constructor;
334 gobject_class->set_property = workbook_set_property;
335 gobject_class->get_property = workbook_get_property;
336 gobject_class->finalize = workbook_finalize;
337 gobject_class->dispose = workbook_dispose;
339 g_object_class_install_property (gobject_class, PROP_RECALC_MODE,
340 g_param_spec_boolean ("recalc-mode",
341 P_("Recalc mode"),
342 P_("Enable automatic recalculation."),
343 TRUE,
344 GSF_PARAM_STATIC |
345 G_PARAM_READWRITE));
347 signals[SHEET_ORDER_CHANGED] = g_signal_new ("sheet_order_changed",
348 GNM_WORKBOOK_TYPE,
349 G_SIGNAL_RUN_LAST,
350 G_STRUCT_OFFSET (WorkbookClass, sheet_order_changed),
351 NULL, NULL,
352 g_cclosure_marshal_VOID__VOID,
353 G_TYPE_NONE,
354 0, G_TYPE_NONE);
356 signals[SHEET_ADDED] = g_signal_new ("sheet_added",
357 GNM_WORKBOOK_TYPE,
358 G_SIGNAL_RUN_LAST,
359 G_STRUCT_OFFSET (WorkbookClass, sheet_added),
360 NULL, NULL,
361 g_cclosure_marshal_VOID__VOID,
362 G_TYPE_NONE,
363 0, G_TYPE_NONE);
365 signals[SHEET_DELETED] = g_signal_new ("sheet_deleted",
366 GNM_WORKBOOK_TYPE,
367 G_SIGNAL_RUN_LAST,
368 G_STRUCT_OFFSET (WorkbookClass, sheet_deleted),
369 NULL, NULL,
370 g_cclosure_marshal_VOID__VOID,
371 G_TYPE_NONE,
372 0, G_TYPE_NONE);
376 * workbook_new:
378 * Returns: A new empty #Workbook with a unique name.
380 Workbook *
381 workbook_new (void)
383 return g_object_new (GNM_WORKBOOK_TYPE, NULL);
387 * workbook_sheet_name_strip_number:
388 * @name: name to strip number from
389 * @number: returns the number stripped off, or 1 if no number.
391 * Gets a name in the form of "Sheet (10)", "Stuff" or "Dummy ((((,"
392 * and returns the real name of the sheet "Sheet ", "Stuff", "Dummy ((((,"
393 * without the copy number.
395 static void
396 workbook_sheet_name_strip_number (char *name, unsigned int *number)
398 char *end, *p, *pend;
399 unsigned long ul;
401 *number = 1;
402 g_return_if_fail (*name != 0);
404 end = name + strlen (name) - 1;
405 if (*end != ')')
406 return;
408 for (p = end; p > name; p--)
409 if (!g_ascii_isdigit (p[-1]))
410 break;
412 if (p == name || p[-1] != '(')
413 return;
415 errno = 0;
416 ul = strtoul (p, &pend, 10);
417 if (pend != end || ul != (unsigned int)ul || errno == ERANGE)
418 return;
420 *number = (unsigned)ul;
421 p[-1] = 0;
425 * workbook_new_with_sheets:
426 * @sheet_count: initial number of sheets to create.
428 * Returns: a #Workbook with @sheet_count allocated
429 * sheets on it
431 Workbook *
432 workbook_new_with_sheets (int sheet_count)
434 Workbook *wb = workbook_new ();
435 int cols = gnm_conf_get_core_workbook_n_cols ();
436 int rows = gnm_conf_get_core_workbook_n_rows ();
437 if (!gnm_sheet_valid_size (cols, rows))
438 gnm_sheet_suggest_size (&cols, &rows);
439 while (sheet_count-- > 0)
440 workbook_sheet_add (wb, -1, cols, rows);
441 go_doc_set_dirty (GO_DOC (wb), FALSE);
442 go_doc_set_pristine (GO_DOC (wb), TRUE);
443 return wb;
447 * workbook_set_saveinfo:
448 * @wb: the workbook to modify
449 * @lev: the file format level
450 * @saver: (nullable): the file saver.
452 * If level is sufficiently advanced, assign the info.
454 * Returns: %TRUE if save info was set and history may require updating
456 * FIXME : Add a check to ensure the name is unique.
458 gboolean
459 workbook_set_saveinfo (Workbook *wb, GOFileFormatLevel level, GOFileSaver *fs)
461 g_return_val_if_fail (wb != NULL, FALSE);
462 g_return_val_if_fail (level > GO_FILE_FL_NONE && level < GO_FILE_FL_LAST,
463 FALSE);
465 if (level != GO_FILE_FL_AUTO) {
466 if (wb->file_exporter != NULL)
467 g_object_weak_unref (G_OBJECT (wb->file_exporter),
468 (GWeakNotify) cb_exporter_finalize, wb);
469 workbook_set_file_exporter (wb, fs);
470 if (fs != NULL)
471 g_object_weak_ref (G_OBJECT (fs),
472 (GWeakNotify) cb_exporter_finalize, wb);
473 } else {
474 if (wb->file_saver != NULL)
475 g_object_weak_unref (G_OBJECT (wb->file_saver),
476 (GWeakNotify) cb_saver_finalize, wb);
478 wb->file_saver = fs;
479 if (fs != NULL)
480 g_object_weak_ref (G_OBJECT (fs),
481 (GWeakNotify) cb_saver_finalize, wb);
484 if (level != GO_FILE_FL_AUTO) {
485 wb->file_export_format_level = level;
486 return FALSE;
488 wb->file_format_level = level;
489 return TRUE;
493 * workbook_get_file_saver:
494 * @wb: #Workbook
496 * Returns: (transfer none): the saver for the Workbook.
498 GOFileSaver *
499 workbook_get_file_saver (Workbook *wb)
501 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
503 return wb->file_saver;
507 * workbook_get_file_exporter:
508 * @wb: #Workbook
510 * Returns: (transfer none): the exporter for the Workbook.
512 GOFileSaver *
513 workbook_get_file_exporter (Workbook *wb)
515 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
517 return wb->file_exporter;
521 * workbook_get_last_export_uri:
522 * @wb: #Workbook
524 * Returns: (transfer none): the URI for export.
526 gchar const *
527 workbook_get_last_export_uri (Workbook *wb)
529 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
531 return wb->last_export_uri;
534 void
535 workbook_set_file_exporter (Workbook *wb, GOFileSaver *fs)
537 wb->file_exporter = fs;
538 WORKBOOK_FOREACH_CONTROL (wb, wbv, wbc,
539 wb_control_menu_state_update (wbc, MS_FILE_EXPORT_IMPORT););
542 void
543 workbook_set_last_export_uri (Workbook *wb, const gchar *uri)
545 char *s = g_strdup (uri);
546 g_free (wb->last_export_uri);
547 wb->last_export_uri = s;
548 WORKBOOK_FOREACH_CONTROL (wb, wbv, wbc,
549 wb_control_menu_state_update (wbc, MS_FILE_EXPORT_IMPORT););
554 * workbook_foreach_cell_in_range:
555 * @pos: The position the range is relative to.
556 * @cell_range: A value containing a range;
557 * @flags: if TRUE only existing cells are sent to the handler.
558 * @handler: (scope call): The operator to apply to each cell.
559 * @closure: User data.
561 * The supplied value must be a cellrange.
562 * The range bounds are calculated relative to the eval position
563 * and normalized.
564 * For each existing cell in the range specified, invoke the
565 * callback routine. If the only_existing flag is TRUE, then
566 * callbacks are only invoked for existing cells.
568 * Note: this function does not honour the CELL_ITER_IGNORE_SUBTOTAL flag.
570 * Return value:
571 * non-NULL on error, or VALUE_TERMINATE if some invoked routine requested
572 * to stop (by returning non-NULL).
574 GnmValue *
575 workbook_foreach_cell_in_range (GnmEvalPos const *pos,
576 GnmValue const *cell_range,
577 CellIterFlags flags,
578 CellIterFunc handler,
579 gpointer closure)
581 GnmRange r;
582 Sheet *start_sheet, *end_sheet;
584 g_return_val_if_fail (pos != NULL, NULL);
585 g_return_val_if_fail (cell_range != NULL, NULL);
586 g_return_val_if_fail (VALUE_IS_CELLRANGE (cell_range), NULL);
588 gnm_rangeref_normalize (&cell_range->v_range.cell, pos,
589 &start_sheet, &end_sheet, &r);
591 if (start_sheet != end_sheet) {
592 GnmValue *res;
593 Workbook const *wb = start_sheet->workbook;
594 int i = start_sheet->index_in_wb;
595 int stop = end_sheet->index_in_wb;
596 if (i > stop) { int tmp = i; i = stop ; stop = tmp; }
598 g_return_val_if_fail (end_sheet->workbook == wb, VALUE_TERMINATE);
600 for (; i <= stop ; i++) {
601 res = sheet_foreach_cell_in_range (
602 g_ptr_array_index (wb->sheets, i), flags, &r,
603 handler, closure);
604 if (res != NULL)
605 return res;
607 return NULL;
610 return sheet_foreach_cell_in_range (start_sheet, flags, &r,
611 handler, closure);
615 * workbook_cells:
616 * @wb: The workbook to find cells in.
617 * @comments: If true, include cells with only comments also.
618 * @vis: How visible a sheet needs to be in order to be considered.
620 * Collects a GPtrArray of GnmEvalPos pointers for all cells in a workbook.
621 * No particular order should be assumed.
623 * Returns: (element-type GnmEvalPos) (transfer container): the cells array
625 GPtrArray *
626 workbook_cells (Workbook *wb, gboolean comments, GnmSheetVisibility vis)
628 GPtrArray *cells = g_ptr_array_new ();
630 g_return_val_if_fail (wb != NULL, cells);
632 WORKBOOK_FOREACH_SHEET (wb, sheet, {
633 size_t oldlen = cells->len;
634 GPtrArray *scells;
636 if (sheet->visibility > vis)
637 continue;
639 scells = sheet_cell_positions (sheet, comments);
640 g_ptr_array_set_size (cells, oldlen + scells->len);
641 memcpy (&g_ptr_array_index (cells, oldlen),
642 &g_ptr_array_index (scells, 0),
643 scells->len * sizeof (GnmEvalPos *));
645 g_ptr_array_free (scells, TRUE);
648 return cells;
651 GnmExprSharer *
652 workbook_share_expressions (Workbook *wb, gboolean freeit)
654 GnmExprSharer *es = gnm_expr_sharer_new ();
656 WORKBOOK_FOREACH_DEPENDENT (wb, dep, {
657 if (dependent_is_cell (dep)) {
658 /* Hopefully safe, even when linked. */
659 dep->texpr = gnm_expr_sharer_share (es, dep->texpr);
660 } else {
661 /* Not sure we want to touch this here. */
665 if (gnm_debug_flag ("expr-sharer")) {
666 g_printerr ("Sharing report for %s\n", go_doc_get_uri (GO_DOC (wb)));
667 gnm_expr_sharer_report (es);
670 if (freeit) {
671 gnm_expr_sharer_destroy (es);
672 es = NULL;
675 return es;
678 void
679 workbook_optimize_style (Workbook *wb)
681 WORKBOOK_FOREACH_SHEET (wb, sheet, {
682 sheet_style_optimize (sheet);
687 * workbook_foreach_name:
688 * @wb: #Workbook
689 * @globals_only: whether to apply only to global names.
690 * @func: (scope call): The operator to apply to each cell.
691 * @data: User data.
694 void
695 workbook_foreach_name (Workbook const *wb, gboolean globals_only,
696 GHFunc func, gpointer data)
698 g_return_if_fail (GNM_IS_WORKBOOK (wb));
700 if (wb->names)
701 gnm_named_expr_collection_foreach (wb->names, func, data);
703 if (!globals_only) {
704 WORKBOOK_FOREACH_SHEET (wb, sheet, {
705 gnm_sheet_foreach_name (sheet, func, data);
711 gboolean
712 workbook_enable_recursive_dirty (Workbook *wb, gboolean enable)
714 gboolean old;
716 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), FALSE);
718 old = wb->recursive_dirty_enabled;
719 wb->recursive_dirty_enabled = enable;
720 return old;
723 void
724 workbook_set_recalcmode (Workbook *wb, gboolean is_auto)
726 g_return_if_fail (GNM_IS_WORKBOOK (wb));
728 is_auto = !!is_auto;
729 if (is_auto == wb->recalc_auto)
730 return;
732 wb->recalc_auto = is_auto;
733 g_object_notify (G_OBJECT (wb), "recalc-mode");
736 gboolean
737 workbook_get_recalcmode (Workbook const *wb)
739 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), FALSE);
740 return wb->recalc_auto;
743 void
744 workbook_iteration_enabled (Workbook *wb, gboolean enable)
746 g_return_if_fail (GNM_IS_WORKBOOK (wb));
747 wb->iteration.enabled = enable;
750 void
751 workbook_iteration_max_number (Workbook *wb, int max_number)
753 g_return_if_fail (GNM_IS_WORKBOOK (wb));
754 g_return_if_fail (max_number >= 0);
755 wb->iteration.max_number = max_number;
758 void
759 workbook_iteration_tolerance (Workbook *wb, double tolerance)
761 g_return_if_fail (GNM_IS_WORKBOOK (wb));
762 g_return_if_fail (tolerance >= 0);
764 wb->iteration.tolerance = tolerance;
767 void
768 workbook_attach_view (WorkbookView *wbv)
770 Workbook *wb;
772 g_return_if_fail (GNM_IS_WORKBOOK_VIEW (wbv));
774 wb = wb_view_get_workbook (wbv);
775 g_return_if_fail (GNM_IS_WORKBOOK (wb));
777 if (wb->wb_views == NULL)
778 wb->wb_views = g_ptr_array_new ();
779 g_ptr_array_add (wb->wb_views, wbv);
782 void
783 workbook_detach_view (WorkbookView *wbv)
785 g_return_if_fail (GNM_IS_WORKBOOK_VIEW (wbv));
786 g_return_if_fail (GNM_IS_WORKBOOK (wbv->wb));
788 WORKBOOK_FOREACH_SHEET (wbv->wb, sheet, {
789 SheetView *sv = sheet_get_view (sheet, wbv);
790 gnm_sheet_view_dispose (sv);
793 g_ptr_array_remove (wbv->wb->wb_views, wbv);
794 if (wbv->wb->wb_views->len == 0) {
795 g_ptr_array_free (wbv->wb->wb_views, TRUE);
796 wbv->wb->wb_views = NULL;
800 /*****************************************************************************/
803 * workbook_sheets:
804 * @wb: #Workbook
806 * Get an ordered list of the sheets in the workbook
808 * Returns: (element-type Sheet) (transfer container): the sheets list.
810 GSList *
811 workbook_sheets (Workbook const *wb)
813 GSList *list = NULL;
815 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
817 if (wb->sheets) {
818 int i = wb->sheets->len;
819 while (i-- > 0)
820 list = g_slist_prepend (list,
821 g_ptr_array_index (wb->sheets, i));
824 return list;
828 workbook_sheet_count (Workbook const *wb)
830 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), 0);
832 return wb->sheets ? wb->sheets->len : 0;
835 static void
836 pre_sheet_index_change (Workbook *wb)
838 g_return_if_fail (!wb->being_reordered);
840 wb->being_reordered = TRUE;
842 if (wb->sheet_order_dependents != NULL)
843 g_hash_table_foreach (wb->sheet_order_dependents,
844 (GHFunc)dependent_unlink,
845 NULL);
848 static void
849 post_sheet_index_change (Workbook *wb)
851 g_return_if_fail (wb->being_reordered);
853 if (wb->sheet_order_dependents != NULL)
854 g_hash_table_foreach (wb->sheet_order_dependents,
855 (GHFunc)dependent_link,
856 NULL);
858 wb->being_reordered = FALSE;
860 if (wb->during_destruction)
861 return;
863 g_signal_emit (G_OBJECT (wb), signals[SHEET_ORDER_CHANGED], 0);
866 static void
867 workbook_sheet_index_update (Workbook *wb, int start)
869 int i;
871 for (i = wb->sheets->len ; i-- > start ; ) {
872 Sheet *sheet = g_ptr_array_index (wb->sheets, i);
873 sheet->index_in_wb = i;
878 * workbook_sheet_by_index:
879 * @wb: workbook to lookup the sheet on
880 * @i: the sheet index we are looking for.
882 * Return value: (transfer none) (nullable): A #Sheet
884 Sheet *
885 workbook_sheet_by_index (Workbook const *wb, int i)
887 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
888 g_return_val_if_fail (i >= -1, NULL);
890 // i = -1 is special, return NULL
891 if (i == -1 || i >= (int)wb->sheets->len)
892 return NULL;
894 return g_ptr_array_index (wb->sheets, i);
898 * workbook_sheet_by_name:
899 * @wb: workbook to lookup the sheet on
900 * @sheet_name: the sheet name we are looking for. This is case insensitive.
902 * Return value: (transfer none) (nullable): A #Sheet
904 Sheet *
905 workbook_sheet_by_name (Workbook const *wb, char const *name)
907 Sheet *sheet;
908 char *tmp;
910 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
911 g_return_val_if_fail (name != NULL, NULL);
913 tmp = g_utf8_casefold (name, -1);
914 sheet = g_hash_table_lookup (wb->sheet_hash_private, tmp);
915 g_free (tmp);
917 return sheet;
921 * Find a sheet to focus on, left or right of sheet_index.
923 static Sheet *
924 workbook_focus_other_sheet (Workbook *wb, Sheet *sheet)
926 int i;
927 Sheet *focus = NULL;
928 int sheet_index = sheet->index_in_wb;
930 for (i = sheet_index; !focus && --i >= 0; ) {
931 Sheet *this_sheet = g_ptr_array_index (wb->sheets, i);
932 if (this_sheet->visibility == GNM_SHEET_VISIBILITY_VISIBLE)
933 focus = this_sheet;
936 for (i = sheet_index; !focus && ++i < (int)wb->sheets->len; ) {
937 Sheet *this_sheet = g_ptr_array_index (wb->sheets, i);
938 if (this_sheet->visibility == GNM_SHEET_VISIBILITY_VISIBLE)
939 focus = this_sheet;
942 WORKBOOK_FOREACH_VIEW (wb, wbv, {
943 if (sheet == wb_view_cur_sheet (wbv))
944 wb_view_sheet_focus (wbv, focus);
947 return focus;
951 * workbook_sheet_remove_controls:
952 * @wb: #Workbook
953 * @sheet: #Sheet
955 * Remove the visible #SheetControls of a sheet and shut them down politely.
957 * Returns %TRUE if there are any remaining sheets visible
959 static gboolean
960 workbook_sheet_remove_controls (Workbook *wb, Sheet *sheet)
962 Sheet *focus = NULL;
964 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), TRUE);
965 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
966 g_return_val_if_fail (sheet->workbook == wb, TRUE);
967 g_return_val_if_fail (workbook_sheet_by_name (wb, sheet->name_unquoted) == sheet, TRUE);
969 /* Finish any object editing */
970 SHEET_FOREACH_CONTROL (sheet, view, control,
971 sc_mode_edit (control););
973 /* If not exiting, adjust the focus for any views whose focus sheet
974 * was the one being deleted, and prepare to recalc */
975 if (!wb->during_destruction)
976 focus = workbook_focus_other_sheet (wb, sheet);
978 WORKBOOK_FOREACH_CONTROL (wb, wbv, wbc,
979 wb_control_sheet_remove (wbc, sheet););
981 return focus != NULL;
985 * workbook_sheet_attach_at_pos:
986 * @wb: A #Workbook
987 * @new_sheet: A #Sheet
988 * @pos: position to attach @new_sheet at, -1 meaning at the end
990 * Add @new_sheet to @wb, placing it at @pos.
992 void
993 workbook_sheet_attach_at_pos (Workbook *wb, Sheet *new_sheet, int pos)
995 g_return_if_fail (GNM_IS_WORKBOOK (wb));
996 g_return_if_fail (IS_SHEET (new_sheet));
997 g_return_if_fail (new_sheet->workbook == wb);
998 g_return_if_fail (pos >= -1 && pos <= (int)wb->sheets->len);
1000 if (pos == -1)
1001 pos = wb->sheets->len;
1003 pre_sheet_index_change (wb);
1005 g_object_ref (new_sheet);
1006 g_ptr_array_insert (wb->sheets, pos, (gpointer)new_sheet);
1007 workbook_sheet_index_update (wb, pos);
1008 g_hash_table_insert (wb->sheet_hash_private,
1009 new_sheet->name_case_insensitive,
1010 new_sheet);
1012 WORKBOOK_FOREACH_VIEW (wb, view,
1013 wb_view_sheet_add (view, new_sheet););
1015 /* Do not signal until after adding the views [#314208] */
1016 post_sheet_index_change (wb);
1018 go_doc_set_dirty (GO_DOC (wb), TRUE);
1022 * workbook_sheet_attach:
1023 * @wb: A #Workbook
1024 * @new_sheet: (transfer full): A #Sheet to attach
1026 * Add @new_sheet to @wb, placing it at the end. SURPRISE: This assumes
1027 * a ref to the sheet.
1029 void
1030 workbook_sheet_attach (Workbook *wb, Sheet *new_sheet)
1032 workbook_sheet_attach_at_pos (wb, new_sheet, -1);
1033 /* Balance the ref added by the above call. */
1034 g_object_unref (new_sheet);
1038 * workbook_sheet_add:
1039 * @wb: a #Workbook.
1040 * @pos: position to add, -1 meaning at end.
1041 * @columns: the sheet columns number.
1042 * @rows: the sheet rows number.
1044 * Create and name a new sheet, putting it at position @pos. The sheet
1045 * returned is not ref'd. (The ref belongs to the workbook.)
1047 * Return value: (transfer none): the new sheet.
1049 Sheet *
1050 workbook_sheet_add (Workbook *wb, int pos, int columns, int rows)
1052 char *name = workbook_sheet_get_free_name (wb, _("Sheet"), TRUE, FALSE);
1053 Sheet *new_sheet = sheet_new (wb, name, columns, rows);
1054 g_free (name);
1056 workbook_sheet_attach_at_pos (wb, new_sheet, pos);
1058 /* FIXME: Why here? */
1059 g_signal_emit (G_OBJECT (wb), signals[SHEET_ADDED], 0);
1061 g_object_unref (new_sheet);
1063 return new_sheet;
1067 * workbook_sheet_add_with_type:
1068 * @wb: a workbook.
1069 * @sheet_type: the sheet type.
1070 * @pos: position to add, -1 meaning append.
1071 * @columns: the sheet columns number.
1072 * @rows: the sheet rows number.
1074 * Create and name a new sheet, putting it at position @pos. The sheet
1075 * returned is not ref'd. (The ref belongs to the workbook.)
1077 * Return value: (transfer none): the new sheet.
1079 Sheet *
1080 workbook_sheet_add_with_type (Workbook *wb, GnmSheetType sheet_type, int pos, int columns, int rows)
1082 char *name = workbook_sheet_get_free_name (wb, (sheet_type == GNM_SHEET_OBJECT)? _("Graph"): _("Sheet"), TRUE, FALSE);
1083 Sheet *new_sheet = sheet_new_with_type (wb, name, sheet_type, columns, rows);
1084 g_free (name);
1086 workbook_sheet_attach_at_pos (wb, new_sheet, pos);
1088 /* FIXME: Why here? */
1089 g_signal_emit (G_OBJECT (wb), signals[SHEET_ADDED], 0);
1091 g_object_unref (new_sheet);
1093 return new_sheet;
1097 * workbook_sheet_delete:
1098 * @sheet: the #Sheet that we want to delete from its workbook
1100 * This function detaches the given sheet from its parent workbook and
1101 * invalidates all references to the deleted sheet from other sheets and
1102 * clears all references in the clipboard to this sheet.
1104 void
1105 workbook_sheet_delete (Sheet *sheet)
1107 Workbook *wb;
1108 int sheet_index;
1110 g_return_if_fail (IS_SHEET (sheet));
1111 g_return_if_fail (GNM_IS_WORKBOOK (sheet->workbook));
1113 wb = sheet->workbook;
1114 sheet_index = sheet->index_in_wb;
1116 if (gnm_debug_flag ("sheets"))
1117 g_printerr ("Removing sheet %s from %s\n",
1118 sheet->name_unquoted,
1119 go_doc_get_uri (GO_DOC (wb)));
1121 gnm_app_clipboard_invalidate_sheet (sheet);
1123 if (!wb->during_destruction) {
1124 workbook_focus_other_sheet (wb, sheet);
1125 /* During destruction this was already done. */
1126 dependents_invalidate_sheet (sheet, FALSE);
1127 workbook_sheet_remove_controls (wb, sheet);
1130 /* All is fine, remove the sheet */
1131 pre_sheet_index_change (wb);
1132 g_ptr_array_remove_index (wb->sheets, sheet_index);
1133 workbook_sheet_index_update (wb, sheet_index);
1134 sheet->index_in_wb = -1;
1135 g_hash_table_remove (wb->sheet_hash_private, sheet->name_case_insensitive);
1136 post_sheet_index_change (wb);
1138 /* Clear the controls first, before we potentially update */
1139 SHEET_FOREACH_VIEW (sheet, view, gnm_sheet_view_dispose (view););
1141 g_signal_emit_by_name (G_OBJECT (sheet), "detached_from_workbook", wb);
1142 g_object_unref (sheet);
1144 if (!wb->during_destruction)
1145 go_doc_set_dirty (GO_DOC (wb), TRUE);
1146 g_signal_emit (G_OBJECT (wb), signals[SHEET_DELETED], 0);
1148 if (!wb->during_destruction)
1149 workbook_queue_all_recalc (wb);
1153 * workbook_sheet_move:
1154 * @sheet: #Sheet to move
1155 * @direction: number of spots to move, positive for right and negative
1156 * for left.
1158 * Moves the sheet up or down @direction spots in the sheet list
1159 * If @direction is negative, move left. If positive, move right.
1161 void
1162 workbook_sheet_move (Sheet *sheet, int direction)
1164 Workbook *wb;
1165 gint old_pos, new_pos;
1167 g_return_if_fail (IS_SHEET (sheet));
1169 wb = sheet->workbook;
1171 pre_sheet_index_change (wb);
1172 old_pos = sheet->index_in_wb;
1173 new_pos = old_pos + direction;
1175 if (0 <= new_pos && new_pos < workbook_sheet_count (wb)) {
1176 int min_pos = MIN (old_pos, new_pos);
1177 int max_pos = MAX (old_pos, new_pos);
1179 g_ptr_array_remove_index (wb->sheets, old_pos);
1180 g_ptr_array_insert (wb->sheets, new_pos, sheet);
1182 for (; max_pos >= min_pos ; max_pos--) {
1183 Sheet *sheet = g_ptr_array_index (wb->sheets, max_pos);
1184 sheet->index_in_wb = max_pos;
1188 post_sheet_index_change (wb);
1190 go_doc_set_dirty (GO_DOC (wb), TRUE);
1194 * workbook_sheet_get_free_name:
1195 * @wb: #Workbook for which the new name can be used
1196 * @base: base for the name, e. g. "Sheet"
1197 * @always_suffix: if true, add suffix even if the name "base" is not in use.
1198 * @handle_counter: strip counter if necessary
1200 * Gets a new unquoted name for a sheets such that it does not exist on the
1201 * workbook.
1203 * Returns: (transfer full): a unique sheet name
1205 char *
1206 workbook_sheet_get_free_name (Workbook *wb,
1207 char const *base,
1208 gboolean always_suffix,
1209 gboolean handle_counter)
1211 char const *name_format;
1212 char *name, *base_name;
1213 unsigned int i = 0;
1214 int limit;
1216 g_return_val_if_fail (wb != NULL, NULL);
1218 if (!always_suffix && (workbook_sheet_by_name (wb, base) == NULL))
1219 return g_strdup (base); /* Name not in use */
1221 base_name = g_strdup (base);
1222 if (handle_counter) {
1223 workbook_sheet_name_strip_number (base_name, &i);
1224 name_format = "%s(%u)";
1225 } else
1226 name_format = "%s%u";
1228 limit = workbook_sheet_count (wb) + 2;
1229 name = g_malloc (strlen (base_name) + strlen (name_format) + 10);
1230 while (limit-- > 0) {
1231 i++;
1232 sprintf (name, name_format, base_name, i);
1233 if (workbook_sheet_by_name (wb, name) == NULL) {
1234 g_free (base_name);
1235 return name;
1239 /* We should not get here. */
1240 g_warning ("There is trouble at the mill.");
1242 g_free (name);
1243 g_free (base_name);
1244 name = g_strdup_printf ("%s (%i)", base, 2);
1245 return name;
1249 * workbook_sheet_rename:
1250 * @wb: #Workbook in which to rename sheets
1251 * @sheet_indices: (element-type int): list of sheet indices (ignore -1)
1252 * @new_names: (element-type utf8): list of new names
1254 * Adjusts the names of the sheets. We assume that everything is
1255 * valid. If in doubt call workbook_sheet_reorder_check first.
1257 * Returns: %FALSE when it was successful
1259 gboolean
1260 workbook_sheet_rename (Workbook *wb,
1261 GSList *sheet_indices,
1262 GSList *new_names,
1263 G_GNUC_UNUSED GOCmdContext *cc)
1265 GSList *sheet_index = sheet_indices;
1266 GSList *new_name = new_names;
1268 while (new_name && sheet_index) {
1269 int ix = GPOINTER_TO_INT (sheet_index->data);
1270 const char *name = new_name->data;
1271 if (ix != -1)
1272 g_hash_table_remove (wb->sheet_hash_private, name);
1273 sheet_index = sheet_index->next;
1274 new_name = new_name->next;
1277 sheet_index = sheet_indices;
1278 new_name = new_names;
1279 while (new_name && sheet_index) {
1280 int ix = GPOINTER_TO_INT (sheet_index->data);
1281 const char *name = new_name->data;
1282 if (ix != -1) {
1283 Sheet *sheet = workbook_sheet_by_index (wb, ix);
1284 g_object_set (sheet, "name", name, NULL);
1286 sheet_index = sheet_index->next;
1287 new_name = new_name->next;
1290 return FALSE;
1294 * workbook_find_command:
1295 * @wb: #Workbook
1296 * @is_undo: undo vs redo
1297 * @cmd: command
1299 * Returns: the 1 based index of the @key command, or 0 if it is not found
1300 * (which would be a programmer error).
1302 unsigned
1303 workbook_find_command (Workbook *wb, gboolean is_undo, gpointer cmd)
1305 GSList *ptr;
1306 unsigned n = 1;
1308 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), 0);
1310 ptr = is_undo ? wb->undo_commands : wb->redo_commands;
1311 for ( ; ptr != NULL ; ptr = ptr->next, n++)
1312 if (ptr->data == cmd)
1313 return n;
1314 g_warning ("%s command : %p not found", is_undo ? "undo" : "redo", cmd);
1315 return 0;
1319 * workbook_sheet_reorder:
1320 * @wb: workbook to reorder
1321 * @new_order: (element-type Sheet): list of #Sheet
1323 * Adjusts the order of the sheets.
1325 * Returns %FALSE when it was successful
1327 gboolean
1328 workbook_sheet_reorder (Workbook *wb, GSList *new_order)
1330 GSList *ptr;
1331 Sheet *sheet;
1332 unsigned pos = 0;
1334 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), FALSE);
1335 g_return_val_if_fail (g_slist_length (new_order) == wb->sheets->len, FALSE);
1337 pre_sheet_index_change (wb);
1339 for (ptr = new_order; NULL != ptr ; ptr = ptr->next, pos++) {
1340 g_ptr_array_index (wb->sheets, pos) = sheet = ptr->data;
1341 sheet->index_in_wb = pos;
1344 post_sheet_index_change (wb);
1346 return FALSE;
1350 * workbook_date_conv:
1351 * @wb: Workbook
1353 * Returns: (transfer none): the date conventions in effect for the workbook.
1355 GODateConventions const *
1356 workbook_date_conv (Workbook const *wb)
1358 g_return_val_if_fail (wb != NULL, NULL);
1359 return wb->date_conv;
1363 * workbook_set_date_conv:
1364 * @wb: workbook
1365 * @date_conv: new date convention
1367 * Sets the date convention @date_conv.
1368 * NOTE : THIS IS NOT A SMART ROUTINE. If you want to actually change this
1369 * We'll need to recalc and rerender everything. That will need to be done
1370 * externally.
1372 void
1373 workbook_set_date_conv (Workbook *wb, GODateConventions const *date_conv)
1375 g_return_if_fail (GNM_IS_WORKBOOK (wb));
1376 g_return_if_fail (date_conv != NULL);
1378 wb->date_conv = date_conv;
1381 void
1382 workbook_set_1904 (Workbook *wb, gboolean base1904)
1384 GODateConventions const *date_conv =
1385 go_date_conv_from_str (base1904 ? "Apple:1904" : "Lotus:1900");
1386 workbook_set_date_conv (wb, date_conv);
1390 * workbook_get_sheet_size:
1391 * @wb: #Workbook
1393 * Returns: (transfer none): the current sheet size for @wb.
1395 GnmSheetSize const *
1396 workbook_get_sheet_size (Workbook const *wb)
1398 if (wb == NULL || workbook_sheet_count (wb) == 0) {
1399 static const GnmSheetSize max_size = {
1400 GNM_MAX_COLS, GNM_MAX_ROWS
1403 return &max_size;
1406 return gnm_sheet_get_size (workbook_sheet_by_index (wb, 0));
1409 /* ------------------------------------------------------------------------- */
1411 typedef struct {
1412 Sheet *sheet;
1413 GSList *properties;
1414 } WorkbookSheetStateSheet;
1416 struct _WorkbookSheetState {
1417 GSList *properties;
1418 int n_sheets;
1419 WorkbookSheetStateSheet *sheets;
1420 unsigned ref_count;
1424 WorkbookSheetState *
1425 workbook_sheet_state_new (const Workbook *wb)
1427 int i;
1428 WorkbookSheetState *wss = g_new (WorkbookSheetState, 1);
1430 wss->properties = go_object_properties_collect (G_OBJECT (wb));
1431 wss->n_sheets = workbook_sheet_count (wb);
1432 wss->sheets = g_new (WorkbookSheetStateSheet, wss->n_sheets);
1433 for (i = 0; i < wss->n_sheets; i++) {
1434 WorkbookSheetStateSheet *wsss = wss->sheets + i;
1435 wsss->sheet = g_object_ref (workbook_sheet_by_index (wb, i));
1436 wsss->properties = go_object_properties_collect (G_OBJECT (wsss->sheet));
1438 wss->ref_count = 1;
1439 return wss;
1442 void
1443 workbook_sheet_state_free (WorkbookSheetState *wss)
1445 int i;
1447 if (!wss || wss->ref_count-- > 1)
1448 return;
1450 go_object_properties_free (wss->properties);
1452 for (i = 0; i < wss->n_sheets; i++) {
1453 WorkbookSheetStateSheet *wsss = wss->sheets + i;
1454 g_object_unref (wsss->sheet);
1455 go_object_properties_free (wsss->properties);
1457 g_free (wss->sheets);
1458 g_free (wss);
1461 static WorkbookSheetState *
1462 workbook_sheet_state_ref (WorkbookSheetState *wss)
1464 wss->ref_count++;
1465 return wss;
1468 GType
1469 workbook_sheet_state_get_type (void)
1471 static GType t = 0;
1473 if (t == 0) {
1474 t = g_boxed_type_register_static ("WorkbookSheetState",
1475 (GBoxedCopyFunc)workbook_sheet_state_ref,
1476 (GBoxedFreeFunc)workbook_sheet_state_free);
1478 return t;
1481 void
1482 workbook_sheet_state_restore (Workbook *wb, const WorkbookSheetState *wss)
1484 int i;
1486 /* Get rid of sheets that shouldn't be there. */
1487 for (i = workbook_sheet_count (wb) ; i-- > 0; ) {
1488 Sheet *sheet = workbook_sheet_by_index (wb, i);
1489 int j;
1490 for (j = 0; j < wss->n_sheets; j++)
1491 if (sheet == wss->sheets[j].sheet)
1492 break;
1493 if (j == wss->n_sheets)
1494 workbook_sheet_delete (sheet);
1497 /* Attach new sheets and handle order. */
1498 for (i = 0; i < wss->n_sheets; i++) {
1499 Sheet *sheet = wss->sheets[i].sheet;
1500 if (sheet->index_in_wb != i) {
1501 if (sheet->index_in_wb == -1) {
1502 workbook_sheet_attach_at_pos (wb, sheet, i);
1503 dependents_revive_sheet (sheet);
1504 } else {
1506 * There might be a smarter way of getting more
1507 * sheets into place faster. This will at
1508 * least work.
1510 workbook_sheet_move (sheet, i - sheet->index_in_wb);
1513 go_object_properties_apply (G_OBJECT (sheet),
1514 wss->sheets[i].properties,
1515 TRUE);
1518 go_object_properties_apply (G_OBJECT (wb), wss->properties, TRUE);
1522 workbook_sheet_state_size (const WorkbookSheetState *wss)
1524 int size = 1 + g_slist_length (wss->properties);
1525 int i;
1526 for (i = 0; i < wss->n_sheets; i++) {
1527 WorkbookSheetStateSheet *wsss = wss->sheets + i;
1528 size += 5; /* For ->sheet. */
1529 size += g_slist_length (wsss->properties);
1531 return size;
1534 GNM_BEGIN_KILL_SWITCH_WARNING
1535 char *
1536 workbook_sheet_state_diff (const WorkbookSheetState *wss_a, const WorkbookSheetState *wss_b)
1538 enum {
1539 WSS_SHEET_RENAMED = 1,
1540 WSS_SHEET_ADDED = 2,
1541 WSS_SHEET_TAB_COLOR = 4,
1542 WSS_SHEET_PROPERTIES = 8,
1543 WSS_SHEET_DELETED = 16,
1544 WSS_SHEET_ORDER = 32,
1545 WSS_FUNNY = 0x40000000
1546 } what = 0;
1547 int ia;
1548 int n = 0;
1549 int n_added, n_deleted = 0;
1551 for (ia = 0; ia < wss_a->n_sheets; ia++) {
1552 Sheet *sheet = wss_a->sheets[ia].sheet;
1553 int ib;
1554 GSList *pa, *pb;
1555 int diff = 0;
1557 for (ib = 0; ib < wss_b->n_sheets; ib++)
1558 if (sheet == wss_b->sheets[ib].sheet)
1559 break;
1560 if (ib == wss_b->n_sheets) {
1561 what |= WSS_SHEET_DELETED;
1562 n++;
1563 n_deleted++;
1564 continue;
1567 if (ia != ib) {
1568 what |= WSS_SHEET_ORDER;
1569 /* We do not count reordered sheet. */
1572 pa = wss_a->sheets[ia].properties;
1573 pb = wss_b->sheets[ib].properties;
1574 for (; pa && pb; pa = pa->next->next, pb = pb->next->next) {
1575 GParamSpec *pspec = pa->data;
1576 const GValue *va = pa->next->data;
1577 const GValue *vb = pb->next->data;
1578 if (pspec != pb->data)
1579 break;
1581 if (g_param_values_cmp (pspec, va, vb) == 0)
1582 continue;
1584 diff = 1;
1585 if (strcmp (pspec->name, "name") == 0)
1586 what |= WSS_SHEET_RENAMED;
1587 else if (strcmp (pspec->name, "tab-foreground") == 0)
1588 what |= WSS_SHEET_TAB_COLOR;
1589 else if (strcmp (pspec->name, "tab-background") == 0)
1590 what |= WSS_SHEET_TAB_COLOR;
1591 else
1592 what |= WSS_SHEET_PROPERTIES;
1595 if (pa || pb)
1596 what |= WSS_FUNNY;
1597 n += diff;
1600 n_added = wss_b->n_sheets - (wss_a->n_sheets - n_deleted);
1601 if (n_added) {
1602 what |= WSS_SHEET_ADDED;
1603 n += n_added;
1606 switch (what) {
1607 case WSS_SHEET_RENAMED:
1608 return g_strdup_printf (ngettext ("Renaming sheet", "Renaming %d sheets", n), n);
1609 case WSS_SHEET_ADDED:
1610 return g_strdup_printf (ngettext ("Adding sheet", "Adding %d sheets", n), n);
1611 case WSS_SHEET_ADDED | WSS_SHEET_ORDER:
1613 * This is most likely just a sheet inserted, but it just
1614 * might be a compound operation. Lie.
1616 return g_strdup_printf (ngettext ("Inserting sheet", "Inserting %d sheets", n), n);
1617 case WSS_SHEET_TAB_COLOR:
1618 return g_strdup (_("Changing sheet tab colors"));
1619 case WSS_SHEET_PROPERTIES:
1620 return g_strdup (_("Changing sheet properties"));
1621 case WSS_SHEET_DELETED:
1622 case WSS_SHEET_DELETED | WSS_SHEET_ORDER:
1624 * This is most likely just a sheet delete, but it just
1625 * might be a compound operation. Lie.
1627 return g_strdup_printf (ngettext ("Deleting sheet", "Deleting %d sheets", n), n);
1628 case WSS_SHEET_ORDER:
1629 return g_strdup (_("Changing sheet order"));
1630 default:
1631 return g_strdup (_("Reorganizing Sheets"));
1634 GNM_END_KILL_SWITCH_WARNING
1636 /* ------------------------------------------------------------------------- */
1638 GSF_CLASS (Workbook, workbook,
1639 workbook_class_init, workbook_init,
1640 GO_TYPE_DOC)