1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
4 * workbook.c: workbook model and manipulation utilities
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>
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"
26 #include "sheet-view.h"
27 #include "sheet-control.h"
30 #include "expr-name.h"
31 #include "dependent.h"
36 #include "libgnumeric.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>
54 * @wb_views: (element-type WorkbookView):
67 static guint signals
[LAST_SIGNAL
] = { 0 };
68 static GObjectClass
*workbook_parent_class
;
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
;
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
);
88 workbook_update_history (Workbook
*wb
, GnmFileSaveAsStyle type
)
90 g_return_if_fail (GNM_IS_WORKBOOK (wb
));
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
)
98 gnm_app_history_add (wb
->doc
.uri
, mimetype
);
101 case GNM_FILE_SAVE_AS_STYLE_EXPORT
:
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
)
108 gnm_app_history_add (wb
->last_export_uri
, mimetype
);
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
);
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
;
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
;
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
);
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
);
212 workbook_parent_class
->finalize (obj
);
216 workbook_init (GObject
*object
)
218 Workbook
*wb
= WORKBOOK (object
);
220 wb
->is_placeholder
= FALSE
;
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
);
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
);
263 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
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
));
279 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
285 workbook_constructor (GType type
,
286 guint n_construct_properties
,
287 GObjectConstructParam
*construct_params
)
291 static int count
= 0;
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
);
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 */
307 char *name
, *nameutf8
, *uri
;
310 nameutf8
= g_strdup_printf (_("Book%d.%s"), count
, extension
);
311 name
= g_filename_from_utf8 (nameutf8
, -1, NULL
, NULL
, NULL
);
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
);
322 } while (!is_unique
);
324 gnm_insert_meta_date (GO_DOC (wb
), GSF_META_NAME_DATE_CREATED
);
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",
343 P_("Enable automatic recalculation."),
348 signals
[SHEET_ORDER_CHANGED
] = g_signal_new ("sheet_order_changed",
351 G_STRUCT_OFFSET (WorkbookClass
, sheet_order_changed
),
353 g_cclosure_marshal_VOID__VOID
,
357 signals
[SHEET_ADDED
] = g_signal_new ("sheet_added",
360 G_STRUCT_OFFSET (WorkbookClass
, sheet_added
),
362 g_cclosure_marshal_VOID__VOID
,
366 signals
[SHEET_DELETED
] = g_signal_new ("sheet_deleted",
369 G_STRUCT_OFFSET (WorkbookClass
, sheet_deleted
),
371 g_cclosure_marshal_VOID__VOID
,
379 * Returns: A new empty #Workbook with a unique name.
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.
397 workbook_sheet_name_strip_number (char *name
, unsigned int *number
)
399 char *end
, *p
, *pend
;
403 g_return_if_fail (*name
!= 0);
405 end
= name
+ strlen (name
) - 1;
409 for (p
= end
; p
> name
; p
--)
410 if (!g_ascii_isdigit (p
[-1]))
413 if (p
== name
|| p
[-1] != '(')
417 ul
= strtoul (p
, &pend
, 10);
418 if (pend
!= end
|| ul
!= (unsigned int)ul
|| errno
== ERANGE
)
421 *number
= (unsigned)ul
;
426 * workbook_new_with_sheets:
427 * @sheet_count: initial number of sheets to create.
429 * Returns: a #Workbook with @sheet_count allocated
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
);
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.
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
,
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
);
472 g_object_weak_ref (G_OBJECT (fs
),
473 (GWeakNotify
) cb_exporter_finalize
, wb
);
475 if (wb
->file_saver
!= NULL
)
476 g_object_weak_unref (G_OBJECT (wb
->file_saver
),
477 (GWeakNotify
) cb_saver_finalize
, wb
);
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
;
489 wb
->file_format_level
= level
;
494 * workbook_get_file_saver:
497 * Returns: (transfer none): the saver for the Workbook.
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:
511 * Returns: (transfer none): the exporter for the Workbook.
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:
525 * Returns: (transfer none): the URI for export.
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
;
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
););
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
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.
572 * non-NULL on error, or VALUE_TERMINATE if some invoked routine requested
573 * to stop (by returning non-NULL).
576 workbook_foreach_cell_in_range (GnmEvalPos
const *pos
,
577 GnmValue
const *cell_range
,
579 CellIterFunc handler
,
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
) {
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
,
612 return sheet_foreach_cell_in_range (start_sheet
, flags
,
613 r
.start
.col
, r
.start
.row
, r
.end
.col
, r
.end
.row
,
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
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
;
639 if (sheet
->visibility
> vis
)
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
);
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
);
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
);
674 gnm_expr_sharer_destroy (es
);
682 workbook_optimize_style (Workbook
*wb
)
684 WORKBOOK_FOREACH_SHEET (wb
, sheet
, {
685 sheet_style_optimize (sheet
);
690 * workbook_foreach_name:
692 * @globals_only: whether to apply only to global names.
693 * @func: (scope call): The operator to apply to each cell.
698 workbook_foreach_name (Workbook
const *wb
, gboolean globals_only
,
699 GHFunc func
, gpointer data
)
701 g_return_if_fail (GNM_IS_WORKBOOK (wb
));
704 gnm_named_expr_collection_foreach (wb
->names
, func
, data
);
707 WORKBOOK_FOREACH_SHEET (wb
, sheet
, {
708 gnm_sheet_foreach_name (sheet
, func
, data
);
715 workbook_enable_recursive_dirty (Workbook
*wb
, gboolean enable
)
719 g_return_val_if_fail (GNM_IS_WORKBOOK (wb
), FALSE
);
721 old
= wb
->recursive_dirty_enabled
;
722 wb
->recursive_dirty_enabled
= enable
;
727 workbook_set_recalcmode (Workbook
*wb
, gboolean is_auto
)
729 g_return_if_fail (GNM_IS_WORKBOOK (wb
));
732 if (is_auto
== wb
->recalc_auto
)
735 wb
->recalc_auto
= is_auto
;
736 g_object_notify (G_OBJECT (wb
), "recalc-mode");
740 workbook_get_recalcmode (Workbook
const *wb
)
742 g_return_val_if_fail (GNM_IS_WORKBOOK (wb
), FALSE
);
743 return wb
->recalc_auto
;
747 workbook_iteration_enabled (Workbook
*wb
, gboolean enable
)
749 g_return_if_fail (GNM_IS_WORKBOOK (wb
));
750 wb
->iteration
.enabled
= enable
;
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
;
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
;
771 workbook_attach_view (WorkbookView
*wbv
)
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
);
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
);
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 /*****************************************************************************/
809 * Get an ordered list of the sheets in the workbook
811 * Returns: (element-type Sheet) (transfer container): the sheets list.
814 workbook_sheets (Workbook
const *wb
)
818 g_return_val_if_fail (GNM_IS_WORKBOOK (wb
), NULL
);
821 int i
= wb
->sheets
->len
;
823 list
= g_slist_prepend (list
,
824 g_ptr_array_index (wb
->sheets
, i
));
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;
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
,
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
,
861 wb
->being_reordered
= FALSE
;
863 if (wb
->during_destruction
)
866 g_signal_emit (G_OBJECT (wb
), signals
[SHEET_ORDER_CHANGED
], 0);
870 workbook_sheet_index_update (Workbook
*wb
, int start
)
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
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
)
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
908 workbook_sheet_by_name (Workbook
const *wb
, char const *name
)
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
);
924 * Find a sheet to focus on, left or right of sheet_index.
927 workbook_focus_other_sheet (Workbook
*wb
, Sheet
*sheet
)
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
)
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
)
945 WORKBOOK_FOREACH_VIEW (wb
, wbv
, {
946 if (sheet
== wb_view_cur_sheet (wbv
))
947 wb_view_sheet_focus (wbv
, focus
);
954 * workbook_sheet_remove_controls:
958 * Remove the visible #SheetControls of a sheet and shut them down politely.
960 * Returns %TRUE if there are any remaining sheets visible
963 workbook_sheet_remove_controls (Workbook
*wb
, Sheet
*sheet
)
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:
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.
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
);
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
,
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:
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.
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:
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.
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
);
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
);
1070 * workbook_sheet_add_with_type:
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.
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
);
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
);
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.
1108 workbook_sheet_delete (Sheet
*sheet
)
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
1161 * Moves the sheet up or down @direction spots in the sheet list
1162 * If @direction is negative, move left. If positive, move right.
1165 workbook_sheet_move (Sheet
*sheet
, int direction
)
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
1206 * Returns: (transfer full): a unique sheet name
1209 workbook_sheet_get_free_name (Workbook
*wb
,
1211 gboolean always_suffix
,
1212 gboolean handle_counter
)
1214 char const *name_format
;
1215 char *name
, *base_name
;
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)";
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) {
1235 sprintf (name
, name_format
, base_name
, i
);
1236 if (workbook_sheet_by_name (wb
, name
) == NULL
) {
1242 /* We should not get here. */
1243 g_warning ("There is trouble at the mill.");
1247 name
= g_strdup_printf ("%s (%i)", base
, 2);
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
1263 workbook_sheet_rename (Workbook
*wb
,
1264 GSList
*sheet_indices
,
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
;
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
;
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
;
1297 * workbook_find_command:
1299 * @is_undo: undo vs redo
1302 * Returns: the 1 based index of the @key command, or 0 if it is not found
1303 * (which would be a programmer error).
1306 workbook_find_command (Workbook
*wb
, gboolean is_undo
, gpointer cmd
)
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
)
1317 g_warning ("%s command : %p not found", is_undo
? "undo" : "redo", cmd
);
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
1331 workbook_sheet_reorder (Workbook
*wb
, GSList
*new_order
)
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
);
1353 * workbook_date_conv:
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:
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
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
;
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:
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
1409 return gnm_sheet_get_size (workbook_sheet_by_index (wb
, 0));
1412 /* ------------------------------------------------------------------------- */
1417 } WorkbookSheetStateSheet
;
1419 struct _WorkbookSheetState
{
1422 WorkbookSheetStateSheet
*sheets
;
1427 WorkbookSheetState
*
1428 workbook_sheet_state_new (const Workbook
*wb
)
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
));
1446 workbook_sheet_state_free (WorkbookSheetState
*wss
)
1450 if (!wss
|| wss
->ref_count
-- > 1)
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
);
1464 static WorkbookSheetState
*
1465 workbook_sheet_state_ref (WorkbookSheetState
*wss
)
1472 workbook_sheet_state_get_type (void)
1477 t
= g_boxed_type_register_static ("WorkbookSheetState",
1478 (GBoxedCopyFunc
)workbook_sheet_state_ref
,
1479 (GBoxedFreeFunc
)workbook_sheet_state_free
);
1485 workbook_sheet_state_restore (Workbook
*wb
, const WorkbookSheetState
*wss
)
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
);
1493 for (j
= 0; j
< wss
->n_sheets
; j
++)
1494 if (sheet
== wss
->sheets
[j
].sheet
)
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
);
1509 * There might be a smarter way of getting more
1510 * sheets into place faster. This will at
1513 workbook_sheet_move (sheet
, i
- sheet
->index_in_wb
);
1516 go_object_properties_apply (G_OBJECT (sheet
),
1517 wss
->sheets
[i
].properties
,
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
);
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
);
1537 GNM_BEGIN_KILL_SWITCH_WARNING
1539 workbook_sheet_state_diff (const WorkbookSheetState
*wss_a
, const WorkbookSheetState
*wss_b
)
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
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
;
1560 for (ib
= 0; ib
< wss_b
->n_sheets
; ib
++)
1561 if (sheet
== wss_b
->sheets
[ib
].sheet
)
1563 if (ib
== wss_b
->n_sheets
) {
1564 what
|= WSS_SHEET_DELETED
;
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
)
1584 if (g_param_values_cmp (pspec
, va
, vb
) == 0)
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
;
1595 what
|= WSS_SHEET_PROPERTIES
;
1603 n_added
= wss_b
->n_sheets
- (wss_a
->n_sheets
- n_deleted
);
1605 what
|= WSS_SHEET_ADDED
;
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"));
1634 return g_strdup (_("Reorganizing Sheets"));
1637 GNM_END_KILL_SWITCH_WARNING
1639 /* ------------------------------------------------------------------------- */
1641 GSF_CLASS (Workbook
, workbook
,
1642 workbook_class_init
, workbook_init
,