workbook.c 32.1 KB
Newer Older
1 2
/* 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
5
 *
6
 * Authors:
7
 *    Miguel de Icaza (miguel@gnu.org).
8
 *    Jody Goldberg (jody@gnome.org)
9
 *
10
 * (C) 1998, 1999, 2000 Miguel de Icaza
11
 * (C) 2000-2001 Ximian, Inc.
12
 * (C) 2002-2006 Jody Goldberg
13
 */
14 15
#include <gnumeric-config.h>
#include "gnumeric.h"
Jody Goldberg's avatar
Jody Goldberg committed
16
#include "workbook-priv.h"
17

Jody Goldberg's avatar
Jody Goldberg committed
18 19 20 21 22
#include "workbook-view.h"
#include "workbook-control.h"
#include "command-context.h"
#include "application.h"
#include "sheet.h"
23
#include "sheet-view.h"
Jody Goldberg's avatar
Jody Goldberg committed
24
#include "sheet-control.h"
25
#include "cell.h"
Jody Goldberg's avatar
Jody Goldberg committed
26
#include "expr.h"
27
#include "expr-name.h"
28
#include "dependent.h"
29
#include "value.h"
Jody Goldberg's avatar
Jody Goldberg committed
30 31
#include "ranges.h"
#include "history.h"
Jody Goldberg's avatar
Jody Goldberg committed
32
#include "commands.h"
33
#include "libgnumeric.h"
34
#include "gutils.h"
Jody Goldberg's avatar
Jody Goldberg committed
35
#include "gnm-marshalers.h"
36
#include "style-color.h"
37 38 39

#include <goffice/app/file.h>
#include <goffice/app/io-context.h>
40
#include <goffice/utils/go-file.h>
Morten Welinder's avatar
Morten Welinder committed
41
#include <goffice/utils/go-glib-extras.h>
42

43 44 45 46
#ifdef WITH_GTK
#ifdef WITH_GNOME
#include <bonobo/bonobo-main.h>
#else
Jody Goldberg's avatar
Jody Goldberg committed
47
#include <gtk/gtkmain.h> /* for gtk_main_quit */
48
#endif
49
#endif /* WITH_GTK */
50 51

#include <gsf/gsf-doc-meta-data.h>
Jody Goldberg's avatar
Jody Goldberg committed
52
#include <gsf/gsf-impl-utils.h>
53
#include <glib/gi18n.h>
54
#include <string.h>
55
#include <errno.h>
56

57
enum {
58 59 60
	PROP_0,
};
enum {
61
	SHEET_ORDER_CHANGED,
62 63
	SHEET_ADDED,
	SHEET_DELETED,
64 65
	LAST_SIGNAL
};
Jody Goldberg's avatar
Jody Goldberg committed
66
static guint signals [LAST_SIGNAL] = { 0 };
67
static GObjectClass *workbook_parent_class;
68

69
static void
Jody Goldberg's avatar
Jody Goldberg committed
70
cb_saver_finalize (Workbook *wb, GOFileSaver *saver)
71
{
Jody Goldberg's avatar
Jody Goldberg committed
72
	g_return_if_fail (IS_GO_FILE_SAVER (saver));
Jody Goldberg's avatar
Jody Goldberg committed
73 74 75
	g_return_if_fail (IS_WORKBOOK (wb));
	g_return_if_fail (wb->file_saver == saver);
	wb->file_saver = NULL;
76 77
}

Arturo Espinosa's avatar
Arturo Espinosa committed
78
static void
79
workbook_dispose (GObject *wb_object)
80
{
81 82
	Workbook *wb = WORKBOOK (wb_object);
	GList *sheets, *ptr;
83

84
	wb->during_destruction = TRUE;
85

86 87 88
	if (wb->file_saver)
		workbook_set_saveinfo (wb, wb->file_format_level, NULL);

89 90 91
	/* Remove all the sheet controls to avoid displaying while we exit */
	WORKBOOK_FOREACH_CONTROL (wb, view, control,
		wb_control_sheet_remove_all (control););
92

Jody Goldberg's avatar
Jody Goldberg committed
93
	command_list_release (wb->undo_commands);
94
	wb->undo_commands = NULL;
95
	command_list_release (wb->redo_commands);
96
	wb->redo_commands = NULL;
Jody Goldberg's avatar
Jody Goldberg committed
97

98
	dependents_workbook_destroy (wb);
99

100 101
	/* Copy the set of sheets, the list changes under us. */
	sheets = workbook_sheets (wb);
102

103 104 105 106 107 108 109
	/* Remove all contents while all sheets still exist */
	for (ptr = sheets; ptr != NULL ; ptr = ptr->next) {
		Sheet *sheet = ptr->data;

		sheet_destroy_contents (sheet);
		/*
		 * We need to put this test BEFORE we detach
110
		 * the sheet from the workbook.  It is ugly, but should
111 112
		 * be ok for debug code.
		 */
113
		if (gnumeric_debugging > 0) {
114
			g_printerr ("Dependencies for %s:\n", sheet->name_unquoted);
115
			gnm_dep_container_dump (sheet->deps);
116
		}
117
	}
118

119
	/* Now remove the sheets themselves */
120 121 122 123
	for (ptr = sheets; ptr != NULL ; ptr = ptr->next) {
		Sheet *sheet = ptr->data;
		workbook_sheet_delete (sheet);
	}
124 125
	g_list_free (sheets);

126
	/* TODO : This should be earlier when we figure out how to deal with
127 128 129 130
	 * the issue raised by 'pristine'.
	 */
	/* Get rid of all the views */
	if (wb->wb_views != NULL) {
Jody Goldberg's avatar
Jody Goldberg committed
131
		WORKBOOK_FOREACH_VIEW (wb, view, {
132
			workbook_detach_view (view);
133
			g_object_unref (view);
134 135 136
		});
		if (wb->wb_views != NULL)
			g_warning ("Unexpected left over views");
137 138
	}

139 140 141
	if (wb->doc.uri &&
	    wb->file_format_level >= FILE_FL_MANUAL_REMEMBER)
		gnm_app_history_add (wb->doc.uri);
142

143
	workbook_parent_class->dispose (wb_object);
144 145 146
}

static void
147
workbook_finalize (GObject *obj)
148
{
149
	Workbook *wb = WORKBOOK (obj);
150

151
	/* Remove ourselves from the list of workbooks.  */
Jody Goldberg's avatar
Jody Goldberg committed
152
	gnm_app_workbook_list_remove (wb);
153

154 155
	/* Now do deletions that will put this workbook into a weird
	   state.  Careful here.  */
156
	g_hash_table_destroy (wb->sheet_hash_private);
157 158
	wb->sheet_hash_private = NULL;

159
	g_ptr_array_free (wb->sheets, TRUE);
160
	wb->sheets = NULL;
161

162 163
	/* this has no business being here */
#ifdef WITH_GTK
Jody Goldberg's avatar
Jody Goldberg committed
164
	if (initial_workbook_open_complete && gnm_app_workbook_list () == NULL)
165 166 167
#ifdef WITH_GNOME
		bonobo_main_quit ();
#else
168
		gtk_main_quit ();
169 170
#endif
#endif
171
	workbook_parent_class->finalize (obj);
172 173
}

174
static void
Jody Goldberg's avatar
Jody Goldberg committed
175
workbook_init (GObject *object)
176
{
177
	Workbook *wb = WORKBOOK (object);
178

179
	wb->is_placeholder = FALSE;
180 181
	wb->wb_views = NULL;
	wb->sheets = g_ptr_array_new ();
182
	wb->sheet_hash_private = g_hash_table_new (g_str_hash, g_str_equal);
183
	wb->sheet_order_dependents = NULL;
184
	wb->sheet_local_functions = NULL;
185
	wb->names        = NULL;
186

187 188
	/* Nothing to undo or redo */
	wb->undo_commands = wb->redo_commands = NULL;
189

190
	/* default to no iteration */
191
	wb->iteration.enabled = TRUE;
192 193
	wb->iteration.max_number = 100;
	wb->iteration.tolerance = .001;
194
	wb->recalc_auto = TRUE;
195 196

	wb->date_conv.use_1904 = FALSE;
197

198 199
	wb->file_format_level = FILE_FL_NEW;
	wb->file_saver        = NULL;
200

201
	wb->during_destruction = FALSE;
202
	wb->being_reordered    = FALSE;
203 204
	wb->recursive_dirty_enabled = TRUE;

Jody Goldberg's avatar
Jody Goldberg committed
205
	gnm_app_workbook_list_add (wb);
206 207
}

208
#if 0
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
static void
workbook_get_property (GObject *object, guint property_id,
		       GValue *value, GParamSpec *pspec)
{
	Workbook *wb = (Workbook *)object;

	switch (property_id) {
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}

static void
workbook_set_property (GObject *object, guint property_id,
		       const GValue *value, GParamSpec *pspec)
{
	Workbook *wb = (Workbook *)object;

	switch (property_id) {
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}
234
#endif
235

236
static void
Jody Goldberg's avatar
Jody Goldberg committed
237
workbook_class_init (GObjectClass *object_class)
238
{
239
	workbook_parent_class = g_type_class_peek_parent (object_class);
240

241
#if 0
242 243
	object_class->set_property = workbook_set_property;
	object_class->get_property = workbook_get_property;
244
#endif
Jody Goldberg's avatar
Jody Goldberg committed
245
	object_class->finalize = workbook_finalize;
246
	object_class->dispose = workbook_dispose;
247

248 249 250 251
	signals [SHEET_ORDER_CHANGED] = g_signal_new ("sheet_order_changed",
		WORKBOOK_TYPE,
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (WorkbookClass, sheet_order_changed),
252
		NULL, NULL,
253
		g_cclosure_marshal_VOID__VOID,
254 255
		G_TYPE_NONE,
		0, G_TYPE_NONE);
256 257 258 259 260

	signals [SHEET_ADDED] = g_signal_new ("sheet_added",
		WORKBOOK_TYPE,
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (WorkbookClass, sheet_added),
261
		NULL, NULL,
262
		g_cclosure_marshal_VOID__VOID,
263 264 265 266 267 268 269
		G_TYPE_NONE,
		0, G_TYPE_NONE);

	signals [SHEET_DELETED] = g_signal_new ("sheet_deleted",
		WORKBOOK_TYPE,
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (WorkbookClass, sheet_deleted),
270
		NULL, NULL,
271
		g_cclosure_marshal_VOID__VOID,
272 273
		G_TYPE_NONE,
		0, G_TYPE_NONE);
274
}
275 276

/**
277
 * workbook_new:
278
 *
279 280
 * Creates a new empty Workbook
 * and assigns a unique name.
281
 */
282 283
Workbook *
workbook_new (void)
284
{
285 286 287
	static int count = 0;
	gboolean is_unique;
	Workbook  *wb;
Jody Goldberg's avatar
Jody Goldberg committed
288
	GOFileSaver *def_save = go_file_saver_get_default ();
289 290 291
	char const *extension = NULL;

	if (def_save != NULL)
Jody Goldberg's avatar
Jody Goldberg committed
292
		extension = go_file_saver_get_extension (def_save);
293 294
	if (extension == NULL)
		extension = "gnumeric";
Morten Welinder's avatar
Morten Welinder committed
295

Jody Goldberg's avatar
Jody Goldberg committed
296
	wb = g_object_new (WORKBOOK_TYPE, NULL);
297 298 299

	/* Assign a default name */
	do {
300
		char *name, *nameutf8, *uri;
301 302 303 304 305 306 307

		count++;
		nameutf8 = g_strdup_printf (_("Book%d.%s"), count, extension);
		name = g_filename_from_utf8 (nameutf8, -1, NULL, NULL, NULL);
		if (!name) {
			name = g_strdup_printf ("Book%d.%s", count, extension);
		}
308
		uri = go_filename_to_uri (name);
309

310
		is_unique = go_doc_set_uri (GO_DOC (wb), uri);
311

312
		g_free (uri);
313
		g_free (name);
314
		g_free (nameutf8);
315 316
	} while (!is_unique);
	return wb;
317 318
}

319 320
/**
 * workbook_sheet_name_strip_number:
321
 * @name: name to strip number from
322
 * @number: returns the number stripped off, or 1 if no number.
323
 *
324
 * Gets a name in the form of "Sheet (10)", "Stuff" or "Dummy ((((,"
325
 * and returns the real name of the sheet "Sheet ", "Stuff", "Dummy ((((,"
326 327 328
 * without the copy number.
 **/
static void
329
workbook_sheet_name_strip_number (char *name, unsigned int *number)
330
{
331 332
	char *end, *p, *pend;
	unsigned long ul;
333

334
	*number = 1;
Morten Welinder's avatar
Morten Welinder committed
335 336 337 338
	g_return_if_fail (*name != 0);

	end = name + strlen (name) - 1;
	if (*end != ')')
339 340
		return;

341 342 343
	for (p = end; p > name; p--)
		if (!g_ascii_isdigit (p[-1]))
			break;
Morten Welinder's avatar
Morten Welinder committed
344

345 346
	if (p == name || p[-1] != '(')
		return;
Morten Welinder's avatar
Morten Welinder committed
347

348 349 350 351
	errno = 0;
	ul = strtoul (p, &pend, 10);
	if (pend != end || ul != (unsigned int)ul || errno == ERANGE)
		return;
Morten Welinder's avatar
Morten Welinder committed
352

353 354
	*number = (unsigned)ul;
	p[-1] = 0;
355 356
}

357 358 359 360 361 362 363
/**
 * workbook_new_with_sheets:
 * @sheet_count: initial number of sheets to create.
 *
 * Returns a Workbook with @sheet_count allocated
 * sheets on it
 */
Arturo Espinosa's avatar
Arturo Espinosa committed
364 365 366
Workbook *
workbook_new_with_sheets (int sheet_count)
{
367 368
	Workbook *wb = workbook_new ();
	while (sheet_count-- > 0)
369
		workbook_sheet_add (wb, -1);
370 371
	go_doc_set_dirty (GO_DOC (wb), FALSE);
	GO_DOC (wb)->pristine = TRUE;
Arturo Espinosa's avatar
Arturo Espinosa committed
372 373
	return wb;
}
374

Jon K Hellan's avatar
Jon K Hellan committed
375 376 377 378
/**
 * workbook_set_saveinfo:
 * @wb: the workbook to modify
 * @name: the file name for this worksheet.
379
 * @level: the file format level
Jon K Hellan's avatar
Jon K Hellan committed
380
 *
Jody Goldberg's avatar
Jody Goldberg committed
381
 * If level is sufficiently advanced assign the info.
Jon K Hellan's avatar
Jon K Hellan committed
382 383 384 385 386 387
 *
 * Returns : TRUE if save info was set succesfully.
 *
 * FIXME : Add a check to ensure the name is unique.
 */
gboolean
Jody Goldberg's avatar
Jody Goldberg committed
388
workbook_set_saveinfo (Workbook *wb, FileFormatLevel level, GOFileSaver *fs)
Jon K Hellan's avatar
Jon K Hellan committed
389 390 391
{
	g_return_val_if_fail (wb != NULL, FALSE);
	g_return_val_if_fail (level > FILE_FL_NONE && level <= FILE_FL_AUTO,
Jody Goldberg's avatar
Jody Goldberg committed
392
			      FALSE);
Jon K Hellan's avatar
Jon K Hellan committed
393

Jody Goldberg's avatar
Jody Goldberg committed
394
	if (level <= FILE_FL_WRITE_ONLY)
Jon K Hellan's avatar
Jon K Hellan committed
395 396 397
		return FALSE;

	wb->file_format_level = level;
Jody Goldberg's avatar
Jody Goldberg committed
398 399
	if (wb->file_saver != NULL)
		g_object_weak_unref (G_OBJECT (wb->file_saver),
Jody Goldberg's avatar
Jody Goldberg committed
400
			(GWeakNotify) cb_saver_finalize, wb);
401

402
	wb->file_saver = fs;
Jody Goldberg's avatar
Jody Goldberg committed
403 404
	if (fs != NULL)
		g_object_weak_ref (G_OBJECT (fs),
Jody Goldberg's avatar
Jody Goldberg committed
405
			(GWeakNotify) cb_saver_finalize, wb);
Morten Welinder's avatar
Morten Welinder committed
406

407
	return TRUE;
408
}
409

Jody Goldberg's avatar
Jody Goldberg committed
410
GOFileSaver *
411 412 413 414 415 416 417
workbook_get_file_saver (Workbook *wb)
{
	g_return_val_if_fail (IS_WORKBOOK (wb), NULL);

	return wb->file_saver;
}

418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
/**
 * workbook_foreach_cell_in_range :
 *
 * @pos : The position the range is relative to.
 * @cell_range : A value containing a range;
 * @only_existing : if TRUE only existing cells are sent to the handler.
 * @handler : The operator to apply to each cell.
 * @closure : User data.
 *
 * The supplied value must be a cellrange.
 * The range bounds are calculated relative to the eval position
 * and normalized.
 * For each existing cell in the range specified, invoke the
 * callback routine.  If the only_existing flag is TRUE, then
 * callbacks are only invoked for existing cells.
 *
 * Return value:
435
 *    non-NULL on error, or VALUE_TERMINATE if some invoked routine requested
436 437
 *    to stop (by returning non-NULL).
 */
Jody Goldberg's avatar
Jody Goldberg committed
438
GnmValue *
Morten Welinder's avatar
Morten Welinder committed
439
workbook_foreach_cell_in_range (GnmEvalPos const *pos,
Jody Goldberg's avatar
Jody Goldberg committed
440
				GnmValue const	*cell_range,
441 442 443
				CellIterFlags	 flags,
				CellIterFunc	 handler,
				gpointer	 closure)
444
{
Jody Goldberg's avatar
Jody Goldberg committed
445
	GnmRange  r;
446 447 448 449 450 451
	Sheet *start_sheet, *end_sheet;

	g_return_val_if_fail (pos != NULL, NULL);
	g_return_val_if_fail (cell_range != NULL, NULL);
	g_return_val_if_fail (cell_range->type == VALUE_CELLRANGE, NULL);

452 453
	gnm_rangeref_normalize (&cell_range->v_range.cell, pos,
		&start_sheet, &end_sheet, &r);
454

455
	if (start_sheet != end_sheet) {
Jody Goldberg's avatar
Jody Goldberg committed
456
		GnmValue *res;
457 458 459
		Workbook const *wb = start_sheet->workbook;
		int i = start_sheet->index_in_wb;
		int stop = end_sheet->index_in_wb;
460
		if (i > stop) { int tmp = i; i = stop ; stop = tmp; }
461

462
		g_return_val_if_fail (end_sheet->workbook == wb, VALUE_TERMINATE);
463

464
		for (; i <= stop ; i++) {
465 466 467 468
			res = sheet_foreach_cell_in_range (
				g_ptr_array_index (wb->sheets, i), flags,
				r.start.col, r.start.row, r.end.col, r.end.row,
				handler, closure);
469 470 471
			if (res != NULL)
				return res;
		}
472
		return NULL;
473
	}
474

475 476 477
	return sheet_foreach_cell_in_range (start_sheet, flags,
		r.start.col, r.start.row, r.end.col, r.end.row,
		handler, closure);
478 479
}

480 481 482 483 484 485
/**
 * workbook_cells:
 *
 * @wb : The workbook to find cells in.
 * @comments: If true, include cells with only comments also.
 *
Morten Welinder's avatar
Morten Welinder committed
486
 * Collects a GPtrArray of GnmEvalPos pointers for all cells in a workbook.
487 488 489 490 491 492 493 494 495
 * No particular order should be assumed.
 */
GPtrArray *
workbook_cells (Workbook *wb, gboolean comments)
{
	GPtrArray *cells = g_ptr_array_new ();

	g_return_val_if_fail (wb != NULL, cells);

496
	WORKBOOK_FOREACH_SHEET (wb, sheet, {
497 498 499 500 501 502 503 504 505
		int oldlen = cells->len;
		GPtrArray *scells =
			sheet_cells (sheet,
				     0, 0, SHEET_MAX_COLS, SHEET_MAX_ROWS,
				     comments);

		g_ptr_array_set_size (cells, oldlen + scells->len);
		memcpy (&g_ptr_array_index (cells, oldlen),
			&g_ptr_array_index (scells, 0),
Morten Welinder's avatar
Morten Welinder committed
506
			scells->len * sizeof (GnmEvalPos *));
507 508

		g_ptr_array_free (scells, TRUE);
509
	});
510 511 512 513

	return cells;
}

514 515 516 517 518 519
GSList *
workbook_local_functions (Workbook const *wb)
{
	return NULL;
}

520 521 522
gboolean
workbook_enable_recursive_dirty (Workbook *wb, gboolean enable)
{
523 524 525
	gboolean old;

	g_return_val_if_fail (IS_WORKBOOK (wb), FALSE);
526

527 528
	old = wb->recursive_dirty_enabled;
	wb->recursive_dirty_enabled = enable;
529 530
	return old;
}
531

532 533 534 535 536 537 538
void
workbook_autorecalc_enable (Workbook *wb, gboolean enable)
{
	g_return_if_fail (IS_WORKBOOK (wb));
	wb->recalc_auto = enable;
}

Morten Welinder's avatar
Morten Welinder committed
539
gboolean
Jody Goldberg's avatar
Jody Goldberg committed
540
workbook_autorecalc (Workbook const *wb)
Morten Welinder's avatar
Morten Welinder committed
541 542 543 544 545
{
	g_return_val_if_fail (IS_WORKBOOK (wb), FALSE);
	return wb->recalc_auto;
}

546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
void
workbook_iteration_enabled (Workbook *wb, gboolean enable)
{
	g_return_if_fail (IS_WORKBOOK (wb));
	wb->iteration.enabled = enable;
}

void
workbook_iteration_max_number (Workbook *wb, int max_number)
{
	g_return_if_fail (IS_WORKBOOK (wb));
	g_return_if_fail (max_number >= 0);
	wb->iteration.max_number = max_number;
}

void
workbook_iteration_tolerance (Workbook *wb, double tolerance)
{
	g_return_if_fail (IS_WORKBOOK (wb));
	g_return_if_fail (tolerance >= 0);

	wb->iteration.tolerance = tolerance;
}

570
void
571
workbook_attach_view (Workbook *wb, WorkbookView *wbv)
572
{
573
	g_return_if_fail (IS_WORKBOOK (wb));
574
	g_return_if_fail (IS_WORKBOOK_VIEW (wbv));
575
	g_return_if_fail (wb_view_get_workbook (wbv) == NULL);
576

577 578 579
	if (wb->wb_views == NULL)
		wb->wb_views = g_ptr_array_new ();
	g_ptr_array_add (wb->wb_views, wbv);
580
	wbv->wb = wb;
581 582 583 584 585 586 587
}

void
workbook_detach_view (WorkbookView *wbv)
{
	g_return_if_fail (IS_WORKBOOK_VIEW (wbv));
	g_return_if_fail (IS_WORKBOOK (wbv->wb));
588

589
	WORKBOOK_FOREACH_SHEET (wbv->wb, sheet, {
590
		SheetView *sv = sheet_get_view (sheet, wbv);
Morten Welinder's avatar
Morten Welinder committed
591
		sv_dispose (sv);
592 593
	});

594 595 596 597 598 599
	g_ptr_array_remove (wbv->wb->wb_views, wbv);
	if (wbv->wb->wb_views->len == 0) {
		g_ptr_array_free (wbv->wb->wb_views, TRUE);
		wbv->wb->wb_views = NULL;
	}
	wbv->wb = NULL;
600 601
}

602 603
/*****************************************************************************/

Jody Goldberg's avatar
Jody Goldberg committed
604 605 606 607
/**
 * workbook_sheets : Get an ordered list of the sheets in the workbook
 *                   The caller is required to free the list.
 */
608
GList *
Jody Goldberg's avatar
Jody Goldberg committed
609
workbook_sheets (Workbook const *wb)
610
{
611
	GList *list = NULL;
612

613 614 615 616 617 618 619 620
	g_return_val_if_fail (IS_WORKBOOK (wb), NULL);

	if (wb->sheets) {
		int i = wb->sheets->len;
		while (i-- > 0)
			list = g_list_prepend (list,
				g_ptr_array_index (wb->sheets, i));
	}
621

622 623
	return list;
}
624

625
int
Jody Goldberg's avatar
Jody Goldberg committed
626
workbook_sheet_count (Workbook const *wb)
627 628
{
	g_return_val_if_fail (IS_WORKBOOK (wb), 0);
629

630 631
	return wb->sheets ? wb->sheets->len : 0;
}
Morten Welinder's avatar
Morten Welinder committed
632

633 634 635
static void
pre_sheet_index_change (Workbook *wb)
{
636 637 638 639
	g_return_if_fail (!wb->being_reordered);

	wb->being_reordered = TRUE;

640 641
	if (wb->sheet_order_dependents != NULL)
		g_hash_table_foreach (wb->sheet_order_dependents,
642 643
				      (GHFunc)dependent_unlink,
				      NULL);
644
}
645

646 647 648
static void
post_sheet_index_change (Workbook *wb)
{
649 650
	g_return_if_fail (wb->being_reordered);

651 652
	if (wb->sheet_order_dependents != NULL)
		g_hash_table_foreach (wb->sheet_order_dependents,
653 654
				      (GHFunc)dependent_link,
				      NULL);
655

656 657
	wb->being_reordered = FALSE;

658 659 660 661
	if (wb->during_destruction)
		return;

	g_signal_emit (G_OBJECT (wb), signals [SHEET_ORDER_CHANGED], 0);
662 663 664
}

static void
665
workbook_sheet_index_update (Workbook *wb, int start)
666 667 668
{
	int i;

669 670 671 672
	for (i = wb->sheets->len ; i-- > start ; ) {
		Sheet *sheet = g_ptr_array_index (wb->sheets, i);
		sheet->index_in_wb = i;
	}
673
}
674

675
Sheet *
676
workbook_sheet_by_index (Workbook const *wb, int i)
677
{
678 679
	g_return_val_if_fail (IS_WORKBOOK (wb), NULL);
	g_return_val_if_fail ((int)wb->sheets->len > i, NULL);
680

681 682 683 684 685
	/* i = -1 is special, return NULL */

	if (i == -1)
		return NULL;

686
	return g_ptr_array_index (wb->sheets, i);
687
}
688

689 690 691
/**
 * workbook_sheet_by_name:
 * @wb: workbook to lookup the sheet on
692
 * @name: the sheet name we are looking for.
693 694 695
 *
 * Returns a pointer to a Sheet or NULL if the sheet
 * was not found.
696
 */
697
Sheet *
698
workbook_sheet_by_name (Workbook const *wb, char const *name)
699
{
700 701
	Sheet *sheet;
	char *tmp;
702

703 704 705 706 707 708 709 710
	g_return_val_if_fail (IS_WORKBOOK (wb), NULL);
	g_return_val_if_fail (name != NULL, NULL);

	tmp = g_utf8_casefold (name, -1);
	sheet = g_hash_table_lookup (wb->sheet_hash_private, tmp);
	g_free (tmp);

	return sheet;
711 712
}

713 714 715 716 717 718 719 720 721 722
/*
 * Find a sheet to focus on, left or right of sheet_index.
 */
static Sheet *
workbook_focus_other_sheet (Workbook *wb, Sheet *sheet)
{
	int i;
	Sheet *focus = NULL;
	int sheet_index = sheet->index_in_wb;

Jody Goldberg's avatar
Jody Goldberg committed
723
	for (i = sheet_index; !focus && --i >= 0; ) {
724
		Sheet *this_sheet = g_ptr_array_index (wb->sheets, i);
725
		if (this_sheet->visibility == GNM_SHEET_VISIBILITY_VISIBLE)
726 727 728
			focus = this_sheet;
	}

Jody Goldberg's avatar
Jody Goldberg committed
729
	for (i = sheet_index; !focus && ++i < (int)wb->sheets->len; ) {
730
		Sheet *this_sheet = g_ptr_array_index (wb->sheets, i);
731
		if (this_sheet->visibility == GNM_SHEET_VISIBILITY_VISIBLE)
732 733 734 735 736 737 738 739 740 741 742
			focus = this_sheet;
	}

	if (focus)
		WORKBOOK_FOREACH_VIEW (wb, wbv,
			if (sheet == wb_view_cur_sheet (wbv))
				wb_view_sheet_focus (wbv, focus);
		);

	return focus;
}
743

744
/**
745
 * workbook_sheet_remove_controls :
Jody Goldberg's avatar
Jody Goldberg committed
746 747
 * @wb : #Workbook
 * @sheet : #Sheet
748
 *
Jody Goldberg's avatar
Jody Goldberg committed
749 750 751 752
 * Remove the visible SheetControls of a sheet and shut them down politely.
 *
 * Returns TRUE if there are any remaining sheets visible
 **/
753
static gboolean
754
workbook_sheet_remove_controls (Workbook *wb, Sheet *sheet)
Dom Lachowicz's avatar
Dom Lachowicz committed
755
{
756 757
	Sheet *focus = NULL;

Jody Goldberg's avatar
Jody Goldberg committed
758 759 760 761
	g_return_val_if_fail (IS_WORKBOOK (wb), TRUE);
	g_return_val_if_fail (IS_SHEET (sheet), TRUE);
	g_return_val_if_fail (sheet->workbook == wb, TRUE);
	g_return_val_if_fail (workbook_sheet_by_name (wb, sheet->name_unquoted) == sheet, TRUE);
Dom Lachowicz's avatar
Dom Lachowicz committed
762

763
	/* Finish any object editing */
764
	SHEET_FOREACH_CONTROL (sheet, view, control,
765
		sc_mode_edit (control););
Dom Lachowicz's avatar
Dom Lachowicz committed
766

767
	/* If not exiting, adjust the focus for any views whose focus sheet
768
	 * was the one being deleted, and prepare to recalc */
769 770
	if (!wb->during_destruction)
		focus = workbook_focus_other_sheet (wb, sheet);
771

772 773
	WORKBOOK_FOREACH_CONTROL (wb, wbv, wbc,
		wb_control_sheet_remove (wbc, sheet););
Jody Goldberg's avatar
Jody Goldberg committed
774

Jody Goldberg's avatar
Jody Goldberg committed
775 776 777
	return focus != NULL;
}

778
/**
779
 * workbook_sheet_attach_at_pos :
780 781
 * @wb :
 * @new_sheet :
782
 * @pos;
783
 *
784 785
 * Add @new_sheet to @wb, placing it at @pos.  This will add a ref to
 * the sheet.
786 787
 */
void
788
workbook_sheet_attach_at_pos (Workbook *wb, Sheet *new_sheet, int pos)
789 790 791 792
{
	g_return_if_fail (IS_WORKBOOK (wb));
	g_return_if_fail (IS_SHEET (new_sheet));
	g_return_if_fail (new_sheet->workbook == wb);
793
	g_return_if_fail (pos >= 0 && pos <= (int)wb->sheets->len);
794 795 796

	pre_sheet_index_change (wb);

797
	g_object_ref (new_sheet);
798 799
	go_ptr_array_insert (wb->sheets, (gpointer)new_sheet, pos);
	workbook_sheet_index_update (wb, pos);
800
	g_hash_table_insert (wb->sheet_hash_private,
801 802 803
			     new_sheet->name_case_insensitive,
			     new_sheet);

804 805 806
	WORKBOOK_FOREACH_VIEW (wb, view,
		wb_view_sheet_add (view, new_sheet););

Jody Goldberg's avatar
Jody Goldberg committed
807 808
	/* Do not signal until after adding the views [#314208] */
	post_sheet_index_change (wb);
809

810
	go_doc_set_dirty (GO_DOC (wb), TRUE);
811 812
}

813 814 815 816 817 818 819 820
/**
 * workbook_sheet_attach:
 * @wb :
 * @new_sheet :
 *
 * Add @new_sheet to @wb, placing it at the end.  SURPRISE: This assumes
 * a ref to the sheet.
 */
821 822 823 824
void
workbook_sheet_attach (Workbook *wb, Sheet *new_sheet)
{
	workbook_sheet_attach_at_pos (wb, new_sheet, wb->sheets->len);
825 826
	/* Balance the ref added by the above call.  */
	g_object_unref (new_sheet);
827 828
}

829 830 831 832 833 834 835 836 837
/**
 * workbook_sheet_add :
 * @wb :
 * @pos : position to add, -1 meaning append.
 *
 * Create and name a new sheet, putting it at position @pos.  The sheet
 * returned is not ref'd.  (The ref belongs to the workbook.)
 */
Sheet *
838
workbook_sheet_add (Workbook *wb, int pos)
839 840 841 842 843 844 845 846 847
{
	char *name = workbook_sheet_get_free_name (wb, _("Sheet"), TRUE, FALSE);
	Sheet *new_sheet = sheet_new (wb, name);
	g_free (name);

	if (pos == -1)
		pos = wb->sheets->len;
	workbook_sheet_attach_at_pos (wb, new_sheet, pos);

848
	/* FIXME: Why here?  */
849 850 851 852 853 854 855
	g_signal_emit (G_OBJECT (wb), signals [SHEET_ADDED], 0);

	g_object_unref (new_sheet);

	return new_sheet;
}

Jody Goldberg's avatar
Jody Goldberg committed
856
/**
857 858
 * workbook_sheet_delete:
 * @sheet: the sheet that we want to delete from its workbook
Jody Goldberg's avatar
Jody Goldberg committed
859
 *
860 861 862
 * This function detaches the given sheet from its parent workbook and
 * invalidates all references to the deleted sheet from other sheets and
 * clears all references in the clipboard to this sheet.
Jody Goldberg's avatar
Jody Goldberg committed
863 864
 */
void
865
workbook_sheet_delete (Sheet *sheet)
Jody Goldberg's avatar
Jody Goldberg committed
866
{
867
	Workbook *wb;
Jody Goldberg's avatar
Jody Goldberg committed
868
	int sheet_index;
869
	gboolean still_visible_sheets = FALSE;
Jody Goldberg's avatar
Jody Goldberg committed
870

871 872 873 874 875
        g_return_if_fail (IS_SHEET (sheet));
        g_return_if_fail (IS_WORKBOOK (sheet->workbook));

	wb = sheet->workbook;
	sheet_index = sheet->index_in_wb;
Jody Goldberg's avatar
Jody Goldberg committed
876

877 878 879 880
	if (!wb->during_destruction) {
		workbook_focus_other_sheet (wb, sheet);
		/* During destruction this was already done.  */
		dependents_invalidate_sheet (sheet, FALSE);
881
		still_visible_sheets = workbook_sheet_remove_controls (wb, sheet);
882 883 884
	}

	/* All is fine, remove the sheet */
885
	pre_sheet_index_change (wb);
886
	g_ptr_array_remove_index (wb->sheets, sheet_index);
887 888
	workbook_sheet_index_update (wb, sheet_index);
	sheet->index_in_wb = -1;
889
	g_hash_table_remove (wb->sheet_hash_private, sheet->name_case_insensitive);
890
	post_sheet_index_change (wb);
891

892
	/* Clear the controls first, before we potentially update */
893
	SHEET_FOREACH_VIEW (sheet, view, sv_dispose (view););
894

895 896 897
	g_signal_emit_by_name (G_OBJECT (sheet), "detached_from_workbook", wb);
	g_object_unref (sheet);

898
	if (!wb->during_destruction)
899
		go_doc_set_dirty (GO_DOC (wb), TRUE);
900
	g_signal_emit (G_OBJECT (wb), signals[SHEET_DELETED], 0);
901

902
	if (still_visible_sheets)
903
		workbook_recalc_all (wb);
Dom Lachowicz's avatar
Dom Lachowicz committed
904 905
}

906
/**
907
 * Moves the sheet up or down @direction spots in the sheet list
908
 * If @direction is negative, move left. If positive, move right.
909
 */
910
void
911
workbook_sheet_move (Sheet *sheet, int direction)
912
{
913 914
	Workbook *wb;
	gint old_pos, new_pos;
915

916
	g_return_if_fail (IS_SHEET (sheet));
917

918
	wb = sheet->workbook;
919 920

	pre_sheet_index_change (wb);
921
        old_pos = sheet->index_in_wb;
922 923 924
	new_pos = old_pos + direction;

	if (0 <= new_pos && new_pos < workbook_sheet_count (wb)) {
925 926
		int min_pos = MIN (old_pos, new_pos);
		int max_pos = MAX (old_pos, new_pos);
927

928
		g_ptr_array_remove_index (wb->sheets, old_pos);
Jody Goldberg's avatar
Jody Goldberg committed
929
		go_ptr_array_insert (wb->sheets, sheet, new_pos);
930 931 932 933 934

		for (; max_pos >= min_pos ; max_pos--) {
			Sheet *sheet = g_ptr_array_index (wb->sheets, max_pos);
			sheet->index_in_wb = max_pos;
		}
935
	}
936 937

	post_sheet_index_change (wb);
938

939
	go_doc_set_dirty (GO_DOC (wb), TRUE);
940
}
941 942

/**
943 944 945 946
 * workbook_sheet_get_free_name:
 * @wb:   workbook to look for
 * @base: base for the name, e. g. "Sheet"
 * @always_suffix: if true, add suffix even if the name "base" is not in use.
Jody Goldberg's avatar
Jody Goldberg committed
947
 * @handle_counter : strip counter if necessary
948
 *
949 950
 * Gets a new unquoted name for a sheets such that it does not exist on the
 * workbook.
951
 *
952 953 954 955 956 957 958
 * Returns the name assigned to the sheet.
 **/
char *
workbook_sheet_get_free_name (Workbook *wb,
			      const char *base,
			      gboolean always_suffix,
			      gboolean handle_counter)
959
{
960 961
	const char *name_format;
	char *name, *base_name;
962
	unsigned int i = 0;
963
	int limit;
964

965
	g_return_val_if_fail (wb != NULL, NULL);
966

967 968
	if (!always_suffix && (workbook_sheet_by_name (wb, base) == NULL))
		return g_strdup (base); /* Name not in use */
969

970 971 972
	base_name = g_strdup (base);
	if (handle_counter) {
		workbook_sheet_name_strip_number (base_name, &i);
973
		name_format = "%s(%u)";
974
	} else
975
		name_format = "%s%u";
976

977
	limit = workbook_sheet_count (wb) + 2;
978
	name = g_malloc (strlen (base_name) + strlen (name_format) + 10);
Morten Welinder's avatar
Morten Welinder committed
979
	while (limit-- > 0) {
980
		i++;
981 982 983 984 985 986
		sprintf (name, name_format, base_name, i);
		if (workbook_sheet_by_name (wb, name) == NULL) {
			g_free (base_name);
			return name;
		}
	}
987

988 989 990
	/* We should not get here.  */
	g_warning ("There is trouble at the mill.");

991 992 993 994
	g_free (name);
	g_free (base_name);
	name = g_strdup_printf ("%s (%i)", base, 2);
	return name;
995
}
Michael Meeks's avatar
Michael Meeks committed
996

997 998 999 1000 1001 1002 1003 1004 1005 1006 1007
/**
 * workbook_sheet_rename:
 * @wb:          workbook to look for
 * @sheet_indices:   list of sheet indices (ignore -1)
 * @new_names:   list of new names
 *
 * Adjusts the names of the sheets. We assume that everything is
 * valid. If in doubt call workbook_sheet_reorder_check first.
 *
 * Returns FALSE when it was successful
 **/
1008 1009 1010 1011
gboolean
workbook_sheet_rename (Workbook *wb,
		       GSList *sheet_indices,
		       GSList *new_names,
1012
		       GOCmdContext *cc)
1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031
{
	GSList *sheet_index = sheet_indices;
	GSList *new_name = new_names;

	while (new_name && sheet_index) {
		if (-1 != GPOINTER_TO_INT (sheet_index->data)) {
			g_hash_table_remove (wb->sheet_hash_private, 
					     new_name->data);
		}
		sheet_index = sheet_index->next;
		new_name = new_name->next;
	}

	sheet_index = sheet_indices;
	new_name = new_names;
	while (new_name && sheet_index) {
		if (-1 != GPOINTER_TO_INT (sheet_index->data)) {
			Sheet *sheet = workbook_sheet_by_index 
				(wb, GPOINTER_TO_INT (sheet_index->data));
1032
			g_object_set (sheet, "name", new_name->data, NULL);
1033 1034 1035 1036 1037 1038 1039 1040
		}
		sheet_index = sheet_index->next;
		new_name = new_name->next;
	}

	return FALSE;
}

1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064
/**
 * workbook_find_command :
 * @wb : #Workbook
 * @is_undo : undo vs redo
 * @key : command
 *
 * returns the 1 based index of the @key command, or 0 if it is not found
 **/
unsigned
workbook_find_command (Workbook *wb, gboolean is_undo, gpointer cmd)
{
	GSList *ptr;
	unsigned n = 1;

	g_return_val_if_fail (IS_WORKBOOK (wb), 0);

	ptr = is_undo ? wb->undo_commands : wb->redo_commands;
	for ( ; ptr != NULL ; ptr = ptr->next, n++)
		if (ptr->data == cmd)
			return n;
	g_warning ("%s command : %p not found", is_undo ? "undo" : "redo", cmd);
	return 0;
}

1065 1066 1067
/**
 * workbook_sheet_reorder:
 * @wb:          workbook to look for
1068
 * @new_order:   list of sheets
1069 1070 1071 1072 1073 1074
 *
 * Adjusts the order of the sheets.
 *
 * Returns FALSE when it was successful
 **/
gboolean
1075
workbook_sheet_reorder (Workbook *wb, GSList *new_order)
1076
{
1077 1078 1079
	GSList   *ptr;
	Sheet    *sheet;
	unsigned  pos = 0;
1080

1081
	g_return_val_if_fail (IS_WORKBOOK (wb), FALSE);
1082
	g_return_val_if_fail (g_slist_length (new_order) == wb->sheets->len, FALSE);
1083

1084
	pre_sheet_index_change (wb);
1085

1086 1087 1088
	for (ptr = new_order; NULL != ptr ; ptr = ptr->next, pos++) {
		g_ptr_array_index (wb->sheets, pos) = sheet = ptr->data;
		sheet->index_in_wb = pos;
1089
	}
1090

1091
	post_sheet_index_change (wb);
1092

1093 1094 1095
	return FALSE;
}

1096 1097 1098 1099 1100 1101 1102 1103 1104 1105
/**
 * workbook_uses_1904 :
 * @wb :
 *
 * Does @wb use the 1904 date convention ?  This could be expanded to return a
 * locale-ish type object to get passed around.  However, since we use libc for
 * some of the formatting and parsing we can not get around setting the actual
 * locale globally and there is not much that I can think of to put in here for
 * now.  Hence I'll leave it as a boolean.
 **/
1106
GODateConventions const *
1107 1108
workbook_date_conv (Workbook const *wb)
{
1109
	g_return_val_if_fail (wb != NULL, NULL);
1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134
	return &wb->date_conv;
}

/**
 * workbook_set_1904 :
 * @wb :
 * @flag : new value
 *
 * Sets the 1904 flag to @flag and returns the old value.
 * NOTE : THIS IS NOT A SMART ROUTINE.  If you want to actually change this
 * We'll need to recalc and rerender everything.  That will nee to be done
 * externally.
 **/
gboolean
workbook_set_1904 (Workbook *wb, gboolean flag)
{
	gboolean old_val;

	g_return_val_if_fail (IS_WORKBOOK (wb), FALSE);

	old_val = wb->date_conv.use_1904;
	wb->date_conv.use_1904 = flag;
	return old_val;
}

1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187
/* ------------------------------------------------------------------------- */

typedef struct {
	Sheet *sheet;
	GSList *properties;
} WorkbookSheetStateSheet;

struct _WorkbookSheetState {
	GSList *properties;
	int n_sheets;
	WorkbookSheetStateSheet *sheets;
};


WorkbookSheetState *
workbook_sheet_state_new (const Workbook *wb)
{
	int i;
	WorkbookSheetState *wss = g_new (WorkbookSheetState, 1);

	wss->properties = go_object_properties_collect (G_OBJECT (wb));
	wss->n_sheets = workbook_sheet_count (wb);
	wss->sheets = g_new (WorkbookSheetStateSheet, wss->n_sheets);
	for (i = 0; i < wss->n_sheets; i++) {
		WorkbookSheetStateSheet *wsss = wss->sheets + i;
		wsss->sheet = g_object_ref (workbook_sheet_by_index (wb, i));
		wsss->properties = go_object_properties_collect (G_OBJECT (wsss->sheet));
	}
	return wss;
}

void
workbook_sheet_state_free (WorkbookSheetState *wss)
{
	int i;

	go_object_properties_free (wss->properties);

	for (i = 0; i < wss->n_sheets; i++) {
		WorkbookSheetStateSheet *wsss = wss->sheets + i;
		g_object_unref (wsss->sheet);
		go_object_properties_free (wsss->properties);
	}
	g_free (wss->sheets);
	g_free (wss);
}

void
workbook_sheet_state_restore (Workbook *wb, const WorkbookSheetState *wss)
{
	int i;

	/* Get rid of sheets that shouldn't be there.  */
Jody Goldberg's avatar
Jody Goldberg committed
1188
	for (i = workbook_sheet_count (wb) ; i-- > 0; ) {
1189 1190 1191 1192 1193
		Sheet *sheet = workbook_sheet_by_index (wb, i);
		int j;
		for (j = 0; j < wss->n_sheets; j++)
			if (sheet == wss->sheets[j].sheet)
				break;
1194 1195
		if (j == wss->n_sheets)
			workbook_sheet_delete (sheet);
1196 1197 1198 1199 1200 1201
	}

	/* Attach new sheets and handle order.  */
	for (i = 0; i < wss->n_sheets; i++) {
		Sheet *sheet = wss->sheets[i].sheet;
		if (sheet->index_in_wb != i) {
1202
			if (sheet->index_in_wb == -1) {
1203
				workbook_sheet_attach_at_pos (wb, sheet, i);
1204 1205
				dependents_revive_sheet (sheet);
			} else {
1206 1207 1208 1209 1210 1211
				/*
				 * There might be a smarter way of getting more
				 * sheets into place faster.  This will at
				 * least work.
				 */
				workbook_sheet_move (sheet, i - sheet->index_in_wb);
1212 1213 1214 1215 1216 1217 1218 1219