Clipboard: check mime type before pasting image.
[gnumeric.git] / src / workbook.c
blob8b30d1b5b3676096aefb788ce674b258981658cc
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /*
4 * workbook.c: workbook model and manipulation utilities
6 * Authors:
7 * Miguel de Icaza (miguel@gnu.org).
8 * Jody Goldberg (jody@gnome.org)
10 * (C) 1998, 1999, 2000 Miguel de Icaza
11 * (C) 2000-2001 Ximian, Inc.
12 * (C) 2002-2007 Jody Goldberg
13 * Copyright (C) 1999-2009 Morten Welinder (terra@gnome.org)
15 #include <gnumeric-config.h>
16 #include "gnumeric.h"
17 #include "workbook-priv.h"
18 #include "compilation.h"
20 #include "workbook-view.h"
21 #include "workbook-control.h"
22 #include "command-context.h"
23 #include "application.h"
24 #include "gnumeric-conf.h"
25 #include "sheet.h"
26 #include "sheet-view.h"
27 #include "sheet-control.h"
28 #include "cell.h"
29 #include "expr.h"
30 #include "expr-name.h"
31 #include "dependent.h"
32 #include "value.h"
33 #include "ranges.h"
34 #include "history.h"
35 #include "commands.h"
36 #include "libgnumeric.h"
37 #include "gutils.h"
38 #include "gnm-marshalers.h"
39 #include "style-color.h"
40 #include "sheet-style.h"
41 #include "sheet-object-graph.h"
43 #include <goffice/goffice.h>
45 #include <gsf/gsf-doc-meta-data.h>
46 #include <gsf/gsf-impl-utils.h>
47 #include <gsf/gsf-meta-names.h>
48 #include "gnm-i18n.h"
49 #include <string.h>
50 #include <errno.h>
52 /**
53 * Workbook:
54 * @wb_views: (element-type WorkbookView):
55 **/
57 enum {
58 PROP_0,
59 PROP_RECALC_MODE
61 enum {
62 SHEET_ORDER_CHANGED,
63 SHEET_ADDED,
64 SHEET_DELETED,
65 LAST_SIGNAL
67 static guint signals[LAST_SIGNAL] = { 0 };
68 static GObjectClass *workbook_parent_class;
70 static void
71 cb_saver_finalize (Workbook *wb, GOFileSaver *saver)
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;
78 static void
79 cb_exporter_finalize (Workbook *wb, GOFileSaver *saver)
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);
87 void
88 workbook_update_history (Workbook *wb, GnmFileSaveAsStyle type)
90 g_return_if_fail (GNM_IS_WORKBOOK (wb));
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);
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);
110 break;
114 void
115 workbook_update_graphs (Workbook *wb)
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));
123 g_slist_free (graphs);
124 }));
128 static void
129 workbook_dispose (GObject *wb_object)
131 Workbook *wb = WORKBOOK (wb_object);
132 GSList *sheets, *ptr;
133 GSList *controls = NULL;
135 wb->during_destruction = TRUE;
137 if (wb->file_saver)
138 workbook_set_saveinfo (wb, GO_FILE_FL_AUTO, NULL);
139 if (wb->file_exporter)
140 workbook_set_saveinfo (wb, GO_FILE_FL_WRITE_ONLY, NULL);
141 workbook_set_last_export_uri (wb, NULL);
143 // Remove all the sheet controls to avoid displaying while we exit
144 // However, hold on to a ref for each -- dialogs like to refer
145 // to ->wbcg during destruction
146 WORKBOOK_FOREACH_CONTROL (wb, view, control,
147 controls = g_slist_prepend (controls, g_object_ref (control));
148 wb_control_sheet_remove_all (control););
150 /* Get rid of all the views */
151 WORKBOOK_FOREACH_VIEW (wb, wbv, {
152 wb_view_detach_from_workbook (wbv);
153 g_object_unref (wbv);
155 if (wb->wb_views != NULL)
156 g_warning ("Unexpected left over views");
158 command_list_release (wb->undo_commands);
159 wb->undo_commands = NULL;
160 command_list_release (wb->redo_commands);
161 wb->redo_commands = NULL;
163 dependents_workbook_destroy (wb);
165 /* Copy the set of sheets, the list changes under us. */
166 sheets = workbook_sheets (wb);
168 /* Remove all contents while all sheets still exist */
169 for (ptr = sheets; ptr != NULL ; ptr = ptr->next) {
170 Sheet *sheet = ptr->data;
171 GnmRange r;
173 sheet_destroy_contents (sheet);
174 range_init_full_sheet (&r, sheet);
175 sheet_style_set_range (sheet, &r, sheet_style_default (sheet));
178 /* Now remove the sheets themselves */
179 for (ptr = sheets; ptr != NULL ; ptr = ptr->next) {
180 Sheet *sheet = ptr->data;
181 workbook_sheet_delete (sheet);
183 g_slist_free (sheets);
185 // Now get rid of the control refs
186 g_slist_free_full (controls, g_object_unref);
188 workbook_parent_class->dispose (wb_object);
191 static void
192 workbook_finalize (GObject *obj)
194 Workbook *wb = WORKBOOK (obj);
196 /* Remove ourselves from the list of workbooks. */
197 gnm_app_workbook_list_remove (wb);
199 if (wb->sheet_local_functions) {
200 g_hash_table_destroy (wb->sheet_local_functions);
201 wb->sheet_local_functions = NULL;
204 /* Now do deletions that will put this workbook into a weird
205 state. Careful here. */
206 g_hash_table_destroy (wb->sheet_hash_private);
207 wb->sheet_hash_private = NULL;
209 g_ptr_array_free (wb->sheets, TRUE);
210 wb->sheets = NULL;
212 workbook_parent_class->finalize (obj);
215 static void
216 workbook_init (GObject *object)
218 Workbook *wb = WORKBOOK (object);
220 wb->is_placeholder = FALSE;
221 wb->wb_views = NULL;
222 wb->sheets = g_ptr_array_new ();
223 wb->sheet_hash_private = g_hash_table_new (g_str_hash, g_str_equal);
224 wb->sheet_order_dependents = NULL;
225 wb->sheet_local_functions = NULL;
226 wb->names = gnm_named_expr_collection_new ();
228 /* Nothing to undo or redo */
229 wb->undo_commands = wb->redo_commands = NULL;
231 /* default to no iteration */
232 wb->iteration.enabled = TRUE;
233 wb->iteration.max_number = 100;
234 wb->iteration.tolerance = .001;
235 wb->recalc_auto = TRUE;
237 workbook_set_1904 (wb, FALSE);
239 wb->file_format_level = GO_FILE_FL_NEW;
240 wb->file_export_format_level = GO_FILE_FL_NEW;
241 wb->file_saver = NULL;
242 wb->file_exporter = NULL;
243 wb->last_export_uri = NULL;
245 wb->during_destruction = FALSE;
246 wb->being_reordered = FALSE;
247 wb->recursive_dirty_enabled = TRUE;
249 gnm_app_workbook_list_add (wb);
252 static void
253 workbook_get_property (GObject *object, guint property_id,
254 GValue *value, GParamSpec *pspec)
256 Workbook *wb = (Workbook *)object;
258 switch (property_id) {
259 case PROP_RECALC_MODE:
260 g_value_set_boolean (value, wb->recalc_auto);
261 break;
262 default:
263 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
264 break;
268 static void
269 workbook_set_property (GObject *object, guint property_id,
270 const GValue *value, GParamSpec *pspec)
272 Workbook *wb = (Workbook *)object;
274 switch (property_id) {
275 case PROP_RECALC_MODE:
276 workbook_set_recalcmode (wb, g_value_get_boolean (value));
277 break;
278 default:
279 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
280 break;
284 static GObject *
285 workbook_constructor (GType type,
286 guint n_construct_properties,
287 GObjectConstructParam *construct_params)
289 GObject *obj;
290 Workbook *wb;
291 static int count = 0;
292 gboolean is_unique;
293 GOFileSaver *def_save = go_file_saver_get_default ();
294 char const *extension = NULL;
296 obj = workbook_parent_class->constructor
297 (type, n_construct_properties, construct_params);
298 wb = WORKBOOK (obj);
300 if (def_save != NULL)
301 extension = go_file_saver_get_extension (def_save);
302 if (extension == NULL)
303 extension = "gnumeric";
305 /* Assign a default name */
306 do {
307 char *name, *nameutf8, *uri;
309 count++;
310 nameutf8 = g_strdup_printf (_("Book%d.%s"), count, extension);
311 name = g_filename_from_utf8 (nameutf8, -1, NULL, NULL, NULL);
312 if (!name) {
313 name = g_strdup_printf ("Book%d.%s", count, extension);
315 uri = go_filename_to_uri (name);
317 is_unique = go_doc_set_uri (GO_DOC (wb), uri);
319 g_free (uri);
320 g_free (name);
321 g_free (nameutf8);
322 } while (!is_unique);
324 gnm_insert_meta_date (GO_DOC (wb), GSF_META_NAME_DATE_CREATED);
326 return obj;
329 static void
330 workbook_class_init (GObjectClass *gobject_class)
332 workbook_parent_class = g_type_class_peek_parent (gobject_class);
334 gobject_class->constructor = workbook_constructor;
335 gobject_class->set_property = workbook_set_property;
336 gobject_class->get_property = workbook_get_property;
337 gobject_class->finalize = workbook_finalize;
338 gobject_class->dispose = workbook_dispose;
340 g_object_class_install_property (gobject_class, PROP_RECALC_MODE,
341 g_param_spec_boolean ("recalc-mode",
342 P_("Recalc mode"),
343 P_("Enable automatic recalculation."),
344 TRUE,
345 GSF_PARAM_STATIC |
346 G_PARAM_READWRITE));
348 signals[SHEET_ORDER_CHANGED] = g_signal_new ("sheet_order_changed",
349 GNM_WORKBOOK_TYPE,
350 G_SIGNAL_RUN_LAST,
351 G_STRUCT_OFFSET (WorkbookClass, sheet_order_changed),
352 NULL, NULL,
353 g_cclosure_marshal_VOID__VOID,
354 G_TYPE_NONE,
355 0, G_TYPE_NONE);
357 signals[SHEET_ADDED] = g_signal_new ("sheet_added",
358 GNM_WORKBOOK_TYPE,
359 G_SIGNAL_RUN_LAST,
360 G_STRUCT_OFFSET (WorkbookClass, sheet_added),
361 NULL, NULL,
362 g_cclosure_marshal_VOID__VOID,
363 G_TYPE_NONE,
364 0, G_TYPE_NONE);
366 signals[SHEET_DELETED] = g_signal_new ("sheet_deleted",
367 GNM_WORKBOOK_TYPE,
368 G_SIGNAL_RUN_LAST,
369 G_STRUCT_OFFSET (WorkbookClass, sheet_deleted),
370 NULL, NULL,
371 g_cclosure_marshal_VOID__VOID,
372 G_TYPE_NONE,
373 0, G_TYPE_NONE);
377 * workbook_new:
379 * Returns: A new empty #Workbook with a unique name.
381 Workbook *
382 workbook_new (void)
384 return g_object_new (GNM_WORKBOOK_TYPE, NULL);
388 * workbook_sheet_name_strip_number:
389 * @name: name to strip number from
390 * @number: returns the number stripped off, or 1 if no number.
392 * Gets a name in the form of "Sheet (10)", "Stuff" or "Dummy ((((,"
393 * and returns the real name of the sheet "Sheet ", "Stuff", "Dummy ((((,"
394 * without the copy number.
396 static void
397 workbook_sheet_name_strip_number (char *name, unsigned int *number)
399 char *end, *p, *pend;
400 unsigned long ul;
402 *number = 1;
403 g_return_if_fail (*name != 0);
405 end = name + strlen (name) - 1;
406 if (*end != ')')
407 return;
409 for (p = end; p > name; p--)
410 if (!g_ascii_isdigit (p[-1]))
411 break;
413 if (p == name || p[-1] != '(')
414 return;
416 errno = 0;
417 ul = strtoul (p, &pend, 10);
418 if (pend != end || ul != (unsigned int)ul || errno == ERANGE)
419 return;
421 *number = (unsigned)ul;
422 p[-1] = 0;
426 * workbook_new_with_sheets:
427 * @sheet_count: initial number of sheets to create.
429 * Returns: a #Workbook with @sheet_count allocated
430 * sheets on it
432 Workbook *
433 workbook_new_with_sheets (int sheet_count)
435 Workbook *wb = workbook_new ();
436 int cols = gnm_conf_get_core_workbook_n_cols ();
437 int rows = gnm_conf_get_core_workbook_n_rows ();
438 if (!gnm_sheet_valid_size (cols, rows))
439 gnm_sheet_suggest_size (&cols, &rows);
440 while (sheet_count-- > 0)
441 workbook_sheet_add (wb, -1, cols, rows);
442 go_doc_set_dirty (GO_DOC (wb), FALSE);
443 go_doc_set_pristine (GO_DOC (wb), TRUE);
444 return wb;
448 * workbook_set_saveinfo:
449 * @wb: the workbook to modify
450 * @lev: the file format level
451 * @saver: (nullable): the file saver.
453 * If level is sufficiently advanced, assign the info.
455 * Returns: %TRUE if save info was set and history may require updating
457 * FIXME : Add a check to ensure the name is unique.
459 gboolean
460 workbook_set_saveinfo (Workbook *wb, GOFileFormatLevel level, GOFileSaver *fs)
462 g_return_val_if_fail (wb != NULL, FALSE);
463 g_return_val_if_fail (level > GO_FILE_FL_NONE && level < GO_FILE_FL_LAST,
464 FALSE);
466 if (level != GO_FILE_FL_AUTO) {
467 if (wb->file_exporter != NULL)
468 g_object_weak_unref (G_OBJECT (wb->file_exporter),
469 (GWeakNotify) cb_exporter_finalize, wb);
470 workbook_set_file_exporter (wb, fs);
471 if (fs != NULL)
472 g_object_weak_ref (G_OBJECT (fs),
473 (GWeakNotify) cb_exporter_finalize, wb);
474 } else {
475 if (wb->file_saver != NULL)
476 g_object_weak_unref (G_OBJECT (wb->file_saver),
477 (GWeakNotify) cb_saver_finalize, wb);
479 wb->file_saver = fs;
480 if (fs != NULL)
481 g_object_weak_ref (G_OBJECT (fs),
482 (GWeakNotify) cb_saver_finalize, wb);
485 if (level != GO_FILE_FL_AUTO) {
486 wb->file_export_format_level = level;
487 return FALSE;
489 wb->file_format_level = level;
490 return TRUE;
494 * workbook_get_file_saver:
495 * @wb: #Workbook
497 * Returns: (transfer none): the saver for the Workbook.
499 GOFileSaver *
500 workbook_get_file_saver (Workbook *wb)
502 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
504 return wb->file_saver;
508 * workbook_get_file_exporter:
509 * @wb: #Workbook
511 * Returns: (transfer none): the exporter for the Workbook.
513 GOFileSaver *
514 workbook_get_file_exporter (Workbook *wb)
516 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
518 return wb->file_exporter;
522 * workbook_get_last_export_uri:
523 * @wb: #Workbook
525 * Returns: (transfer none): the URI for export.
527 gchar const *
528 workbook_get_last_export_uri (Workbook *wb)
530 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
532 return wb->last_export_uri;
535 void
536 workbook_set_file_exporter (Workbook *wb, GOFileSaver *fs)
538 wb->file_exporter = fs;
539 WORKBOOK_FOREACH_CONTROL (wb, wbv, wbc,
540 wb_control_menu_state_update (wbc, MS_FILE_EXPORT_IMPORT););
543 void
544 workbook_set_last_export_uri (Workbook *wb, const gchar *uri)
546 char *s = g_strdup (uri);
547 g_free (wb->last_export_uri);
548 wb->last_export_uri = s;
549 WORKBOOK_FOREACH_CONTROL (wb, wbv, wbc,
550 wb_control_menu_state_update (wbc, MS_FILE_EXPORT_IMPORT););
555 * workbook_foreach_cell_in_range:
556 * @pos: The position the range is relative to.
557 * @cell_range: A value containing a range;
558 * @flags: if TRUE only existing cells are sent to the handler.
559 * @handler: (scope call): The operator to apply to each cell.
560 * @closure: User data.
562 * The supplied value must be a cellrange.
563 * The range bounds are calculated relative to the eval position
564 * and normalized.
565 * For each existing cell in the range specified, invoke the
566 * callback routine. If the only_existing flag is TRUE, then
567 * callbacks are only invoked for existing cells.
569 * Note: this function does not honour the CELL_ITER_IGNORE_SUBTOTAL flag.
571 * Return value:
572 * non-NULL on error, or VALUE_TERMINATE if some invoked routine requested
573 * to stop (by returning non-NULL).
575 GnmValue *
576 workbook_foreach_cell_in_range (GnmEvalPos const *pos,
577 GnmValue const *cell_range,
578 CellIterFlags flags,
579 CellIterFunc handler,
580 gpointer closure)
582 GnmRange r;
583 Sheet *start_sheet, *end_sheet;
585 g_return_val_if_fail (pos != NULL, NULL);
586 g_return_val_if_fail (cell_range != NULL, NULL);
587 g_return_val_if_fail (VALUE_IS_CELLRANGE (cell_range), NULL);
589 gnm_rangeref_normalize (&cell_range->v_range.cell, pos,
590 &start_sheet, &end_sheet, &r);
592 if (start_sheet != end_sheet) {
593 GnmValue *res;
594 Workbook const *wb = start_sheet->workbook;
595 int i = start_sheet->index_in_wb;
596 int stop = end_sheet->index_in_wb;
597 if (i > stop) { int tmp = i; i = stop ; stop = tmp; }
599 g_return_val_if_fail (end_sheet->workbook == wb, VALUE_TERMINATE);
601 for (; i <= stop ; i++) {
602 res = sheet_foreach_cell_in_range (
603 g_ptr_array_index (wb->sheets, i), flags,
604 r.start.col, r.start.row, r.end.col, r.end.row,
605 handler, closure);
606 if (res != NULL)
607 return res;
609 return NULL;
612 return sheet_foreach_cell_in_range (start_sheet, flags,
613 r.start.col, r.start.row, r.end.col, r.end.row,
614 handler, closure);
618 * workbook_cells:
619 * @wb: The workbook to find cells in.
620 * @comments: If true, include cells with only comments also.
621 * @vis: How visible a sheet needs to be in order to be considered.
623 * Collects a GPtrArray of GnmEvalPos pointers for all cells in a workbook.
624 * No particular order should be assumed.
626 * Returns: (element-type GnmEvalPos) (transfer container): the cells array
628 GPtrArray *
629 workbook_cells (Workbook *wb, gboolean comments, GnmSheetVisibility vis)
631 GPtrArray *cells = g_ptr_array_new ();
633 g_return_val_if_fail (wb != NULL, cells);
635 WORKBOOK_FOREACH_SHEET (wb, sheet, {
636 size_t oldlen = cells->len;
637 GPtrArray *scells;
639 if (sheet->visibility > vis)
640 continue;
642 scells = sheet_cell_positions (sheet, comments);
643 g_ptr_array_set_size (cells, oldlen + scells->len);
644 memcpy (&g_ptr_array_index (cells, oldlen),
645 &g_ptr_array_index (scells, 0),
646 scells->len * sizeof (GnmEvalPos *));
648 g_ptr_array_free (scells, TRUE);
651 return cells;
654 GnmExprSharer *
655 workbook_share_expressions (Workbook *wb, gboolean freeit)
657 GnmExprSharer *es = gnm_expr_sharer_new ();
659 WORKBOOK_FOREACH_DEPENDENT (wb, dep, {
660 if (dependent_is_cell (dep)) {
661 /* Hopefully safe, even when linked. */
662 dep->texpr = gnm_expr_sharer_share (es, dep->texpr);
663 } else {
664 /* Not sure we want to touch this here. */
668 if (gnm_debug_flag ("expr-sharer")) {
669 g_printerr ("Sharing report for %s\n", go_doc_get_uri (GO_DOC (wb)));
670 gnm_expr_sharer_report (es);
673 if (freeit) {
674 gnm_expr_sharer_destroy (es);
675 es = NULL;
678 return es;
681 void
682 workbook_optimize_style (Workbook *wb)
684 WORKBOOK_FOREACH_SHEET (wb, sheet, {
685 sheet_style_optimize (sheet);
690 * workbook_foreach_name:
691 * @wb: #Workbook
692 * @globals_only: whether to apply only to global names.
693 * @func: (scope call): The operator to apply to each cell.
694 * @data: User data.
697 void
698 workbook_foreach_name (Workbook const *wb, gboolean globals_only,
699 GHFunc func, gpointer data)
701 g_return_if_fail (GNM_IS_WORKBOOK (wb));
703 if (wb->names)
704 gnm_named_expr_collection_foreach (wb->names, func, data);
706 if (!globals_only) {
707 WORKBOOK_FOREACH_SHEET (wb, sheet, {
708 gnm_sheet_foreach_name (sheet, func, data);
714 gboolean
715 workbook_enable_recursive_dirty (Workbook *wb, gboolean enable)
717 gboolean old;
719 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), FALSE);
721 old = wb->recursive_dirty_enabled;
722 wb->recursive_dirty_enabled = enable;
723 return old;
726 void
727 workbook_set_recalcmode (Workbook *wb, gboolean is_auto)
729 g_return_if_fail (GNM_IS_WORKBOOK (wb));
731 is_auto = !!is_auto;
732 if (is_auto == wb->recalc_auto)
733 return;
735 wb->recalc_auto = is_auto;
736 g_object_notify (G_OBJECT (wb), "recalc-mode");
739 gboolean
740 workbook_get_recalcmode (Workbook const *wb)
742 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), FALSE);
743 return wb->recalc_auto;
746 void
747 workbook_iteration_enabled (Workbook *wb, gboolean enable)
749 g_return_if_fail (GNM_IS_WORKBOOK (wb));
750 wb->iteration.enabled = enable;
753 void
754 workbook_iteration_max_number (Workbook *wb, int max_number)
756 g_return_if_fail (GNM_IS_WORKBOOK (wb));
757 g_return_if_fail (max_number >= 0);
758 wb->iteration.max_number = max_number;
761 void
762 workbook_iteration_tolerance (Workbook *wb, double tolerance)
764 g_return_if_fail (GNM_IS_WORKBOOK (wb));
765 g_return_if_fail (tolerance >= 0);
767 wb->iteration.tolerance = tolerance;
770 void
771 workbook_attach_view (WorkbookView *wbv)
773 Workbook *wb;
775 g_return_if_fail (GNM_IS_WORKBOOK_VIEW (wbv));
777 wb = wb_view_get_workbook (wbv);
778 g_return_if_fail (GNM_IS_WORKBOOK (wb));
780 if (wb->wb_views == NULL)
781 wb->wb_views = g_ptr_array_new ();
782 g_ptr_array_add (wb->wb_views, wbv);
785 void
786 workbook_detach_view (WorkbookView *wbv)
788 g_return_if_fail (GNM_IS_WORKBOOK_VIEW (wbv));
789 g_return_if_fail (GNM_IS_WORKBOOK (wbv->wb));
791 WORKBOOK_FOREACH_SHEET (wbv->wb, sheet, {
792 SheetView *sv = sheet_get_view (sheet, wbv);
793 sv_dispose (sv);
796 g_ptr_array_remove (wbv->wb->wb_views, wbv);
797 if (wbv->wb->wb_views->len == 0) {
798 g_ptr_array_free (wbv->wb->wb_views, TRUE);
799 wbv->wb->wb_views = NULL;
803 /*****************************************************************************/
806 * workbook_sheets:
807 * @wb: #Workbook
809 * Get an ordered list of the sheets in the workbook
811 * Returns: (element-type Sheet) (transfer container): the sheets list.
813 GSList *
814 workbook_sheets (Workbook const *wb)
816 GSList *list = NULL;
818 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
820 if (wb->sheets) {
821 int i = wb->sheets->len;
822 while (i-- > 0)
823 list = g_slist_prepend (list,
824 g_ptr_array_index (wb->sheets, i));
827 return list;
831 workbook_sheet_count (Workbook const *wb)
833 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), 0);
835 return wb->sheets ? wb->sheets->len : 0;
838 static void
839 pre_sheet_index_change (Workbook *wb)
841 g_return_if_fail (!wb->being_reordered);
843 wb->being_reordered = TRUE;
845 if (wb->sheet_order_dependents != NULL)
846 g_hash_table_foreach (wb->sheet_order_dependents,
847 (GHFunc)dependent_unlink,
848 NULL);
851 static void
852 post_sheet_index_change (Workbook *wb)
854 g_return_if_fail (wb->being_reordered);
856 if (wb->sheet_order_dependents != NULL)
857 g_hash_table_foreach (wb->sheet_order_dependents,
858 (GHFunc)dependent_link,
859 NULL);
861 wb->being_reordered = FALSE;
863 if (wb->during_destruction)
864 return;
866 g_signal_emit (G_OBJECT (wb), signals[SHEET_ORDER_CHANGED], 0);
869 static void
870 workbook_sheet_index_update (Workbook *wb, int start)
872 int i;
874 for (i = wb->sheets->len ; i-- > start ; ) {
875 Sheet *sheet = g_ptr_array_index (wb->sheets, i);
876 sheet->index_in_wb = i;
881 * workbook_sheet_by_index:
882 * @wb: workbook to lookup the sheet on
883 * @i: the sheet index we are looking for.
885 * Return value: (transfer none) (nullable): A #Sheet
887 Sheet *
888 workbook_sheet_by_index (Workbook const *wb, int i)
890 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
891 g_return_val_if_fail (i >= -1, NULL);
893 // i = -1 is special, return NULL
894 if (i == -1 || i >= (int)wb->sheets->len)
895 return NULL;
897 return g_ptr_array_index (wb->sheets, i);
901 * workbook_sheet_by_name:
902 * @wb: workbook to lookup the sheet on
903 * @sheet_name: the sheet name we are looking for. This is case insensitive.
905 * Return value: (transfer none) (nullable): A #Sheet
907 Sheet *
908 workbook_sheet_by_name (Workbook const *wb, char const *name)
910 Sheet *sheet;
911 char *tmp;
913 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
914 g_return_val_if_fail (name != NULL, NULL);
916 tmp = g_utf8_casefold (name, -1);
917 sheet = g_hash_table_lookup (wb->sheet_hash_private, tmp);
918 g_free (tmp);
920 return sheet;
924 * Find a sheet to focus on, left or right of sheet_index.
926 static Sheet *
927 workbook_focus_other_sheet (Workbook *wb, Sheet *sheet)
929 int i;
930 Sheet *focus = NULL;
931 int sheet_index = sheet->index_in_wb;
933 for (i = sheet_index; !focus && --i >= 0; ) {
934 Sheet *this_sheet = g_ptr_array_index (wb->sheets, i);
935 if (this_sheet->visibility == GNM_SHEET_VISIBILITY_VISIBLE)
936 focus = this_sheet;
939 for (i = sheet_index; !focus && ++i < (int)wb->sheets->len; ) {
940 Sheet *this_sheet = g_ptr_array_index (wb->sheets, i);
941 if (this_sheet->visibility == GNM_SHEET_VISIBILITY_VISIBLE)
942 focus = this_sheet;
945 WORKBOOK_FOREACH_VIEW (wb, wbv, {
946 if (sheet == wb_view_cur_sheet (wbv))
947 wb_view_sheet_focus (wbv, focus);
950 return focus;
954 * workbook_sheet_remove_controls:
955 * @wb: #Workbook
956 * @sheet: #Sheet
958 * Remove the visible #SheetControls of a sheet and shut them down politely.
960 * Returns %TRUE if there are any remaining sheets visible
962 static gboolean
963 workbook_sheet_remove_controls (Workbook *wb, Sheet *sheet)
965 Sheet *focus = NULL;
967 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), TRUE);
968 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
969 g_return_val_if_fail (sheet->workbook == wb, TRUE);
970 g_return_val_if_fail (workbook_sheet_by_name (wb, sheet->name_unquoted) == sheet, TRUE);
972 /* Finish any object editing */
973 SHEET_FOREACH_CONTROL (sheet, view, control,
974 sc_mode_edit (control););
976 /* If not exiting, adjust the focus for any views whose focus sheet
977 * was the one being deleted, and prepare to recalc */
978 if (!wb->during_destruction)
979 focus = workbook_focus_other_sheet (wb, sheet);
981 WORKBOOK_FOREACH_CONTROL (wb, wbv, wbc,
982 wb_control_sheet_remove (wbc, sheet););
984 return focus != NULL;
988 * workbook_sheet_attach_at_pos:
989 * @wb: A #Workbook
990 * @new_sheet: A #Sheet
991 * @pos: position to attach @new_sheet at, -1 meaning at the end
993 * Add @new_sheet to @wb, placing it at @pos.
995 void
996 workbook_sheet_attach_at_pos (Workbook *wb, Sheet *new_sheet, int pos)
998 g_return_if_fail (GNM_IS_WORKBOOK (wb));
999 g_return_if_fail (IS_SHEET (new_sheet));
1000 g_return_if_fail (new_sheet->workbook == wb);
1001 g_return_if_fail (pos >= -1 && pos <= (int)wb->sheets->len);
1003 if (pos == -1)
1004 pos = wb->sheets->len;
1006 pre_sheet_index_change (wb);
1008 g_object_ref (new_sheet);
1009 go_ptr_array_insert (wb->sheets, (gpointer)new_sheet, pos);
1010 workbook_sheet_index_update (wb, pos);
1011 g_hash_table_insert (wb->sheet_hash_private,
1012 new_sheet->name_case_insensitive,
1013 new_sheet);
1015 WORKBOOK_FOREACH_VIEW (wb, view,
1016 wb_view_sheet_add (view, new_sheet););
1018 /* Do not signal until after adding the views [#314208] */
1019 post_sheet_index_change (wb);
1021 go_doc_set_dirty (GO_DOC (wb), TRUE);
1025 * workbook_sheet_attach:
1026 * @wb: A #Workbook
1027 * @new_sheet: (transfer full): A #Sheet to attach
1029 * Add @new_sheet to @wb, placing it at the end. SURPRISE: This assumes
1030 * a ref to the sheet.
1032 void
1033 workbook_sheet_attach (Workbook *wb, Sheet *new_sheet)
1035 workbook_sheet_attach_at_pos (wb, new_sheet, -1);
1036 /* Balance the ref added by the above call. */
1037 g_object_unref (new_sheet);
1041 * workbook_sheet_add:
1042 * @wb: a #Workbook.
1043 * @pos: position to add, -1 meaning at end.
1044 * @columns: the sheet columns number.
1045 * @rows: the sheet rows number.
1047 * Create and name a new sheet, putting it at position @pos. The sheet
1048 * returned is not ref'd. (The ref belongs to the workbook.)
1050 * Return value: (transfer none): the new sheet.
1052 Sheet *
1053 workbook_sheet_add (Workbook *wb, int pos, int columns, int rows)
1055 char *name = workbook_sheet_get_free_name (wb, _("Sheet"), TRUE, FALSE);
1056 Sheet *new_sheet = sheet_new (wb, name, columns, rows);
1057 g_free (name);
1059 workbook_sheet_attach_at_pos (wb, new_sheet, pos);
1061 /* FIXME: Why here? */
1062 g_signal_emit (G_OBJECT (wb), signals[SHEET_ADDED], 0);
1064 g_object_unref (new_sheet);
1066 return new_sheet;
1070 * workbook_sheet_add_with_type:
1071 * @wb: a workbook.
1072 * @sheet_type: the sheet type.
1073 * @pos: position to add, -1 meaning append.
1074 * @columns: the sheet columns number.
1075 * @rows: the sheet rows number.
1077 * Create and name a new sheet, putting it at position @pos. The sheet
1078 * returned is not ref'd. (The ref belongs to the workbook.)
1080 * Return value: (transfer none): the new sheet.
1082 Sheet *
1083 workbook_sheet_add_with_type (Workbook *wb, GnmSheetType sheet_type, int pos, int columns, int rows)
1085 char *name = workbook_sheet_get_free_name (wb, (sheet_type == GNM_SHEET_OBJECT)? _("Graph"): _("Sheet"), TRUE, FALSE);
1086 Sheet *new_sheet = sheet_new_with_type (wb, name, sheet_type, columns, rows);
1087 g_free (name);
1089 workbook_sheet_attach_at_pos (wb, new_sheet, pos);
1091 /* FIXME: Why here? */
1092 g_signal_emit (G_OBJECT (wb), signals[SHEET_ADDED], 0);
1094 g_object_unref (new_sheet);
1096 return new_sheet;
1100 * workbook_sheet_delete:
1101 * @sheet: the #Sheet that we want to delete from its workbook
1103 * This function detaches the given sheet from its parent workbook and
1104 * invalidates all references to the deleted sheet from other sheets and
1105 * clears all references in the clipboard to this sheet.
1107 void
1108 workbook_sheet_delete (Sheet *sheet)
1110 Workbook *wb;
1111 int sheet_index;
1113 g_return_if_fail (IS_SHEET (sheet));
1114 g_return_if_fail (GNM_IS_WORKBOOK (sheet->workbook));
1116 wb = sheet->workbook;
1117 sheet_index = sheet->index_in_wb;
1119 if (gnm_debug_flag ("sheets"))
1120 g_printerr ("Removing sheet %s from %s\n",
1121 sheet->name_unquoted,
1122 go_doc_get_uri (GO_DOC (wb)));
1124 gnm_app_clipboard_invalidate_sheet (sheet);
1126 if (!wb->during_destruction) {
1127 workbook_focus_other_sheet (wb, sheet);
1128 /* During destruction this was already done. */
1129 dependents_invalidate_sheet (sheet, FALSE);
1130 workbook_sheet_remove_controls (wb, sheet);
1133 /* All is fine, remove the sheet */
1134 pre_sheet_index_change (wb);
1135 g_ptr_array_remove_index (wb->sheets, sheet_index);
1136 workbook_sheet_index_update (wb, sheet_index);
1137 sheet->index_in_wb = -1;
1138 g_hash_table_remove (wb->sheet_hash_private, sheet->name_case_insensitive);
1139 post_sheet_index_change (wb);
1141 /* Clear the controls first, before we potentially update */
1142 SHEET_FOREACH_VIEW (sheet, view, sv_dispose (view););
1144 g_signal_emit_by_name (G_OBJECT (sheet), "detached_from_workbook", wb);
1145 g_object_unref (sheet);
1147 if (!wb->during_destruction)
1148 go_doc_set_dirty (GO_DOC (wb), TRUE);
1149 g_signal_emit (G_OBJECT (wb), signals[SHEET_DELETED], 0);
1151 if (!wb->during_destruction)
1152 workbook_queue_all_recalc (wb);
1156 * workbook_sheet_move:
1157 * @sheet: #Sheet to move
1158 * @direction: number of spots to move, positive for right and negative
1159 * for left.
1161 * Moves the sheet up or down @direction spots in the sheet list
1162 * If @direction is negative, move left. If positive, move right.
1164 void
1165 workbook_sheet_move (Sheet *sheet, int direction)
1167 Workbook *wb;
1168 gint old_pos, new_pos;
1170 g_return_if_fail (IS_SHEET (sheet));
1172 wb = sheet->workbook;
1174 pre_sheet_index_change (wb);
1175 old_pos = sheet->index_in_wb;
1176 new_pos = old_pos + direction;
1178 if (0 <= new_pos && new_pos < workbook_sheet_count (wb)) {
1179 int min_pos = MIN (old_pos, new_pos);
1180 int max_pos = MAX (old_pos, new_pos);
1182 g_ptr_array_remove_index (wb->sheets, old_pos);
1183 go_ptr_array_insert (wb->sheets, sheet, new_pos);
1185 for (; max_pos >= min_pos ; max_pos--) {
1186 Sheet *sheet = g_ptr_array_index (wb->sheets, max_pos);
1187 sheet->index_in_wb = max_pos;
1191 post_sheet_index_change (wb);
1193 go_doc_set_dirty (GO_DOC (wb), TRUE);
1197 * workbook_sheet_get_free_name:
1198 * @wb: #Workbook for which the new name can be used
1199 * @base: base for the name, e. g. "Sheet"
1200 * @always_suffix: if true, add suffix even if the name "base" is not in use.
1201 * @handle_counter: strip counter if necessary
1203 * Gets a new unquoted name for a sheets such that it does not exist on the
1204 * workbook.
1206 * Returns: (transfer full): a unique sheet name
1208 char *
1209 workbook_sheet_get_free_name (Workbook *wb,
1210 char const *base,
1211 gboolean always_suffix,
1212 gboolean handle_counter)
1214 char const *name_format;
1215 char *name, *base_name;
1216 unsigned int i = 0;
1217 int limit;
1219 g_return_val_if_fail (wb != NULL, NULL);
1221 if (!always_suffix && (workbook_sheet_by_name (wb, base) == NULL))
1222 return g_strdup (base); /* Name not in use */
1224 base_name = g_strdup (base);
1225 if (handle_counter) {
1226 workbook_sheet_name_strip_number (base_name, &i);
1227 name_format = "%s(%u)";
1228 } else
1229 name_format = "%s%u";
1231 limit = workbook_sheet_count (wb) + 2;
1232 name = g_malloc (strlen (base_name) + strlen (name_format) + 10);
1233 while (limit-- > 0) {
1234 i++;
1235 sprintf (name, name_format, base_name, i);
1236 if (workbook_sheet_by_name (wb, name) == NULL) {
1237 g_free (base_name);
1238 return name;
1242 /* We should not get here. */
1243 g_warning ("There is trouble at the mill.");
1245 g_free (name);
1246 g_free (base_name);
1247 name = g_strdup_printf ("%s (%i)", base, 2);
1248 return name;
1252 * workbook_sheet_rename:
1253 * @wb: #Workbook in which to rename sheets
1254 * @sheet_indices: (element-type void): list of sheet indices (ignore -1)
1255 * @new_names: (element-type utf8): list of new names
1257 * Adjusts the names of the sheets. We assume that everything is
1258 * valid. If in doubt call workbook_sheet_reorder_check first.
1260 * Returns: %FALSE when it was successful
1262 gboolean
1263 workbook_sheet_rename (Workbook *wb,
1264 GSList *sheet_indices,
1265 GSList *new_names,
1266 G_GNUC_UNUSED GOCmdContext *cc)
1268 GSList *sheet_index = sheet_indices;
1269 GSList *new_name = new_names;
1271 while (new_name && sheet_index) {
1272 int ix = GPOINTER_TO_INT (sheet_index->data);
1273 const char *name = new_name->data;
1274 if (ix != -1)
1275 g_hash_table_remove (wb->sheet_hash_private, name);
1276 sheet_index = sheet_index->next;
1277 new_name = new_name->next;
1280 sheet_index = sheet_indices;
1281 new_name = new_names;
1282 while (new_name && sheet_index) {
1283 int ix = GPOINTER_TO_INT (sheet_index->data);
1284 const char *name = new_name->data;
1285 if (ix != -1) {
1286 Sheet *sheet = workbook_sheet_by_index (wb, ix);
1287 g_object_set (sheet, "name", name, NULL);
1289 sheet_index = sheet_index->next;
1290 new_name = new_name->next;
1293 return FALSE;
1297 * workbook_find_command:
1298 * @wb: #Workbook
1299 * @is_undo: undo vs redo
1300 * @cmd: command
1302 * Returns: the 1 based index of the @key command, or 0 if it is not found
1303 * (which would be a programmer error).
1305 unsigned
1306 workbook_find_command (Workbook *wb, gboolean is_undo, gpointer cmd)
1308 GSList *ptr;
1309 unsigned n = 1;
1311 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), 0);
1313 ptr = is_undo ? wb->undo_commands : wb->redo_commands;
1314 for ( ; ptr != NULL ; ptr = ptr->next, n++)
1315 if (ptr->data == cmd)
1316 return n;
1317 g_warning ("%s command : %p not found", is_undo ? "undo" : "redo", cmd);
1318 return 0;
1322 * workbook_sheet_reorder:
1323 * @wb: workbook to reorder
1324 * @new_order: (element-type Sheet): list of #Sheet
1326 * Adjusts the order of the sheets.
1328 * Returns %FALSE when it was successful
1330 gboolean
1331 workbook_sheet_reorder (Workbook *wb, GSList *new_order)
1333 GSList *ptr;
1334 Sheet *sheet;
1335 unsigned pos = 0;
1337 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), FALSE);
1338 g_return_val_if_fail (g_slist_length (new_order) == wb->sheets->len, FALSE);
1340 pre_sheet_index_change (wb);
1342 for (ptr = new_order; NULL != ptr ; ptr = ptr->next, pos++) {
1343 g_ptr_array_index (wb->sheets, pos) = sheet = ptr->data;
1344 sheet->index_in_wb = pos;
1347 post_sheet_index_change (wb);
1349 return FALSE;
1353 * workbook_date_conv:
1354 * @wb: Workbook
1356 * Returns: the date convention in effect for the workbook.
1358 GODateConventions const *
1359 workbook_date_conv (Workbook const *wb)
1361 g_return_val_if_fail (wb != NULL, NULL);
1362 return wb->date_conv;
1366 * workbook_set_date_conv:
1367 * @wb: workbook
1368 * @date_conv: new date convention
1370 * Sets the date convention @date_conv.
1371 * NOTE : THIS IS NOT A SMART ROUTINE. If you want to actually change this
1372 * We'll need to recalc and rerender everything. That will need to be done
1373 * externally.
1375 void
1376 workbook_set_date_conv (Workbook *wb, GODateConventions const *date_conv)
1378 g_return_if_fail (GNM_IS_WORKBOOK (wb));
1379 g_return_if_fail (date_conv != NULL);
1381 wb->date_conv = date_conv;
1384 void
1385 workbook_set_1904 (Workbook *wb, gboolean base1904)
1387 GODateConventions const *date_conv =
1388 go_date_conv_from_str (base1904 ? "Apple:1904" : "Lotus:1900");
1389 workbook_set_date_conv (wb, date_conv);
1393 * workbook_get_sheet_size:
1394 * @wb: #Workbook
1396 * Returns: (transfer none): the current sheet size for @wb.
1398 GnmSheetSize const *
1399 workbook_get_sheet_size (Workbook const *wb)
1401 if (wb == NULL || workbook_sheet_count (wb) == 0) {
1402 static const GnmSheetSize max_size = {
1403 GNM_MAX_COLS, GNM_MAX_ROWS
1406 return &max_size;
1409 return gnm_sheet_get_size (workbook_sheet_by_index (wb, 0));
1412 /* ------------------------------------------------------------------------- */
1414 typedef struct {
1415 Sheet *sheet;
1416 GSList *properties;
1417 } WorkbookSheetStateSheet;
1419 struct _WorkbookSheetState {
1420 GSList *properties;
1421 int n_sheets;
1422 WorkbookSheetStateSheet *sheets;
1423 unsigned ref_count;
1427 WorkbookSheetState *
1428 workbook_sheet_state_new (const Workbook *wb)
1430 int i;
1431 WorkbookSheetState *wss = g_new (WorkbookSheetState, 1);
1433 wss->properties = go_object_properties_collect (G_OBJECT (wb));
1434 wss->n_sheets = workbook_sheet_count (wb);
1435 wss->sheets = g_new (WorkbookSheetStateSheet, wss->n_sheets);
1436 for (i = 0; i < wss->n_sheets; i++) {
1437 WorkbookSheetStateSheet *wsss = wss->sheets + i;
1438 wsss->sheet = g_object_ref (workbook_sheet_by_index (wb, i));
1439 wsss->properties = go_object_properties_collect (G_OBJECT (wsss->sheet));
1441 wss->ref_count = 1;
1442 return wss;
1445 void
1446 workbook_sheet_state_free (WorkbookSheetState *wss)
1448 int i;
1450 if (!wss || wss->ref_count-- > 1)
1451 return;
1453 go_object_properties_free (wss->properties);
1455 for (i = 0; i < wss->n_sheets; i++) {
1456 WorkbookSheetStateSheet *wsss = wss->sheets + i;
1457 g_object_unref (wsss->sheet);
1458 go_object_properties_free (wsss->properties);
1460 g_free (wss->sheets);
1461 g_free (wss);
1464 static WorkbookSheetState *
1465 workbook_sheet_state_ref (WorkbookSheetState *wss)
1467 wss->ref_count++;
1468 return wss;
1471 GType
1472 workbook_sheet_state_get_type (void)
1474 static GType t = 0;
1476 if (t == 0) {
1477 t = g_boxed_type_register_static ("WorkbookSheetState",
1478 (GBoxedCopyFunc)workbook_sheet_state_ref,
1479 (GBoxedFreeFunc)workbook_sheet_state_free);
1481 return t;
1484 void
1485 workbook_sheet_state_restore (Workbook *wb, const WorkbookSheetState *wss)
1487 int i;
1489 /* Get rid of sheets that shouldn't be there. */
1490 for (i = workbook_sheet_count (wb) ; i-- > 0; ) {
1491 Sheet *sheet = workbook_sheet_by_index (wb, i);
1492 int j;
1493 for (j = 0; j < wss->n_sheets; j++)
1494 if (sheet == wss->sheets[j].sheet)
1495 break;
1496 if (j == wss->n_sheets)
1497 workbook_sheet_delete (sheet);
1500 /* Attach new sheets and handle order. */
1501 for (i = 0; i < wss->n_sheets; i++) {
1502 Sheet *sheet = wss->sheets[i].sheet;
1503 if (sheet->index_in_wb != i) {
1504 if (sheet->index_in_wb == -1) {
1505 workbook_sheet_attach_at_pos (wb, sheet, i);
1506 dependents_revive_sheet (sheet);
1507 } else {
1509 * There might be a smarter way of getting more
1510 * sheets into place faster. This will at
1511 * least work.
1513 workbook_sheet_move (sheet, i - sheet->index_in_wb);
1516 go_object_properties_apply (G_OBJECT (sheet),
1517 wss->sheets[i].properties,
1518 TRUE);
1521 go_object_properties_apply (G_OBJECT (wb), wss->properties, TRUE);
1525 workbook_sheet_state_size (const WorkbookSheetState *wss)
1527 int size = 1 + g_slist_length (wss->properties);
1528 int i;
1529 for (i = 0; i < wss->n_sheets; i++) {
1530 WorkbookSheetStateSheet *wsss = wss->sheets + i;
1531 size += 5; /* For ->sheet. */
1532 size += g_slist_length (wsss->properties);
1534 return size;
1537 GNM_BEGIN_KILL_SWITCH_WARNING
1538 char *
1539 workbook_sheet_state_diff (const WorkbookSheetState *wss_a, const WorkbookSheetState *wss_b)
1541 enum {
1542 WSS_SHEET_RENAMED = 1,
1543 WSS_SHEET_ADDED = 2,
1544 WSS_SHEET_TAB_COLOR = 4,
1545 WSS_SHEET_PROPERTIES = 8,
1546 WSS_SHEET_DELETED = 16,
1547 WSS_SHEET_ORDER = 32,
1548 WSS_FUNNY = 0x40000000
1549 } what = 0;
1550 int ia;
1551 int n = 0;
1552 int n_added, n_deleted = 0;
1554 for (ia = 0; ia < wss_a->n_sheets; ia++) {
1555 Sheet *sheet = wss_a->sheets[ia].sheet;
1556 int ib;
1557 GSList *pa, *pb;
1558 int diff = 0;
1560 for (ib = 0; ib < wss_b->n_sheets; ib++)
1561 if (sheet == wss_b->sheets[ib].sheet)
1562 break;
1563 if (ib == wss_b->n_sheets) {
1564 what |= WSS_SHEET_DELETED;
1565 n++;
1566 n_deleted++;
1567 continue;
1570 if (ia != ib) {
1571 what |= WSS_SHEET_ORDER;
1572 /* We do not count reordered sheet. */
1575 pa = wss_a->sheets[ia].properties;
1576 pb = wss_b->sheets[ib].properties;
1577 for (; pa && pb; pa = pa->next->next, pb = pb->next->next) {
1578 GParamSpec *pspec = pa->data;
1579 const GValue *va = pa->next->data;
1580 const GValue *vb = pb->next->data;
1581 if (pspec != pb->data)
1582 break;
1584 if (g_param_values_cmp (pspec, va, vb) == 0)
1585 continue;
1587 diff = 1;
1588 if (strcmp (pspec->name, "name") == 0)
1589 what |= WSS_SHEET_RENAMED;
1590 else if (strcmp (pspec->name, "tab-foreground") == 0)
1591 what |= WSS_SHEET_TAB_COLOR;
1592 else if (strcmp (pspec->name, "tab-background") == 0)
1593 what |= WSS_SHEET_TAB_COLOR;
1594 else
1595 what |= WSS_SHEET_PROPERTIES;
1598 if (pa || pb)
1599 what |= WSS_FUNNY;
1600 n += diff;
1603 n_added = wss_b->n_sheets - (wss_a->n_sheets - n_deleted);
1604 if (n_added) {
1605 what |= WSS_SHEET_ADDED;
1606 n += n_added;
1609 switch (what) {
1610 case WSS_SHEET_RENAMED:
1611 return g_strdup_printf (ngettext ("Renaming sheet", "Renaming %d sheets", n), n);
1612 case WSS_SHEET_ADDED:
1613 return g_strdup_printf (ngettext ("Adding sheet", "Adding %d sheets", n), n);
1614 case WSS_SHEET_ADDED | WSS_SHEET_ORDER:
1616 * This is most likely just a sheet inserted, but it just
1617 * might be a compound operation. Lie.
1619 return g_strdup_printf (ngettext ("Inserting sheet", "Inserting %d sheets", n), n);
1620 case WSS_SHEET_TAB_COLOR:
1621 return g_strdup (_("Changing sheet tab colors"));
1622 case WSS_SHEET_PROPERTIES:
1623 return g_strdup (_("Changing sheet properties"));
1624 case WSS_SHEET_DELETED:
1625 case WSS_SHEET_DELETED | WSS_SHEET_ORDER:
1627 * This is most likely just a sheet delete, but it just
1628 * might be a compound operation. Lie.
1630 return g_strdup_printf (ngettext ("Deleting sheet", "Deleting %d sheets", n), n);
1631 case WSS_SHEET_ORDER:
1632 return g_strdup (_("Changing sheet order"));
1633 default:
1634 return g_strdup (_("Reorganizing Sheets"));
1637 GNM_END_KILL_SWITCH_WARNING
1639 /* ------------------------------------------------------------------------- */
1641 GSF_CLASS (Workbook, workbook,
1642 workbook_class_init, workbook_init,
1643 GO_TYPE_DOC)