sheet.c 102 KB
Newer Older
Jody Goldberg's avatar
Jody Goldberg committed
1 2
/* vim: set sw=8: */

Arturo Espinosa's avatar
Arturo Espinosa committed
3 4 5 6 7
/*
 * Sheet.c:  Implements the sheet management and per-sheet storage
 *
 * Author:
 *  Miguel de Icaza (miguel@gnu.org)
8
 *  Jody Goldberg (jgoldbeg@home.com)
Arturo Espinosa's avatar
Arturo Espinosa committed
9
 */
10
#include <config.h>
Michael Meeks's avatar
Michael Meeks committed
11
#include <ctype.h>
Arturo Espinosa's avatar
Arturo Espinosa committed
12
#include <gnome.h>
13
#include <string.h>
Arturo Espinosa's avatar
Arturo Espinosa committed
14
#include "gnumeric.h"
15
#include "command-context.h"
16
#include "sheet-control-gui.h"
Jody Goldberg's avatar
Jody Goldberg committed
17 18
#include "sheet.h"
#include "sheet-style.h"
19 20
#include "workbook-control.h"
#include "workbook-view.h"
21
#include "workbook.h"
22
#include "workbook-edit.h"
23
#include "parse-util.h"
24
#include "gnumeric-util.h"
25
#include "eval.h"
26
#include "value.h"
27 28
#include "number-match.h"
#include "format.h"
29
#include "clipboard.h"
30
#include "selection.h"
31
#include "ranges.h"
32
#include "print-info.h"
33
#include "mstyle.h"
34
#include "application.h"
Jody Goldberg's avatar
Jody Goldberg committed
35
#include "commands.h"
36
#include "cellspan.h"
Jody Goldberg's avatar
Jody Goldberg committed
37
#include "cell.h"
38
#include "sheet-merge.h"
39
#include "dependent.h"
40
#include "sheet-private.h"
41
#include "expr-name.h"
42
#include "rendered-value.h"
43 44
#include "sheet-object-impl.h"
#include "sheet-object-cell-comment.h"
Arturo Espinosa's avatar
Arturo Espinosa committed
45

46 47
static void sheet_redraw_partial_row (Sheet const *sheet, int row,
				      int start_col, int end_col);
48

Arturo Espinosa's avatar
Arturo Espinosa committed
49
void
50
sheet_redraw_all (Sheet const *sheet)
Arturo Espinosa's avatar
Arturo Espinosa committed
51
{
52
	SHEET_FOREACH_CONTROL (sheet, control,
53
		scg_redraw_all (control););
Arturo Espinosa's avatar
Arturo Espinosa committed
54 55
}

56
void
Jody Goldberg's avatar
Jody Goldberg committed
57
sheet_redraw_headers (Sheet const *sheet,
58 59
		      gboolean col, gboolean row,
		      Range const *r /* optional == NULL */)
60
{
61
	SHEET_FOREACH_CONTROL (sheet, control,
62
		scg_redraw_headers (control, col, row, r););
63 64
}

Miguel de Icaza's avatar
Miguel de Icaza committed
65
void
66
sheet_rename (Sheet *sheet, char const *new_name)
Miguel de Icaza's avatar
Miguel de Icaza committed
67 68 69 70
{
	g_return_if_fail (IS_SHEET (sheet));
	g_return_if_fail (new_name != NULL);

71 72 73 74
	g_free (sheet->name_quoted);
	g_free (sheet->name_unquoted);
	sheet->name_unquoted = g_strdup (new_name);
	sheet->name_quoted = sheet_name_quote (new_name);
Miguel de Icaza's avatar
Miguel de Icaza committed
75 76
}

77
SheetControlGUI *
78
sheet_new_scg (Sheet *sheet)
79
{
80
	GtkWidget *scg;
81

82
	g_return_val_if_fail (IS_SHEET (sheet), NULL);
83

84
	scg = sheet_control_gui_new (sheet);
85

86
	scg_set_cursor_bounds (SHEET_CONTROL_GUI (scg),
87 88
		sheet->cursor.base_corner.col, sheet->cursor.base_corner.row,
		sheet->cursor.move_corner.col, sheet->cursor.move_corner.row);
89
	sheet->s_controls = g_list_prepend (sheet->s_controls, scg);
90

91
	return SHEET_CONTROL_GUI (scg);
92 93 94
}

void
95
sheet_detach_scg (SheetControlGUI *scg)
96
{
97 98
	g_return_if_fail (IS_SHEET_CONTROL_GUI (scg));
	g_return_if_fail (IS_SHEET (scg->sheet));
99

100 101
	scg->sheet->s_controls = g_list_remove (scg->sheet->s_controls, scg);
	scg->sheet = NULL;
102 103
}

104 105 106 107 108
/*
 * sheet_new
 * @wb              Workbook
 * @name            Unquoted name
 */
Arturo Espinosa's avatar
Arturo Espinosa committed
109
Sheet *
110
sheet_new (Workbook *wb, char const *name)
Arturo Espinosa's avatar
Arturo Espinosa committed
111
{
112
	Sheet  *sheet;
Arturo Espinosa's avatar
Arturo Espinosa committed
113

Miguel de Icaza's avatar
Miguel de Icaza committed
114 115
	g_return_val_if_fail (wb != NULL, NULL);
	g_return_val_if_fail (name != NULL, NULL);
116

Arturo Espinosa's avatar
Arturo Espinosa committed
117
	sheet = g_new0 (Sheet, 1);
118
	sheet->priv = g_new0 (SheetPrivate, 1);
Jody Goldberg's avatar
Jody Goldberg committed
119
#ifdef ENABLE_BONOBO
120 121
	sheet->priv->corba_server = NULL;
	sheet->priv->sheet_vectors = NULL;
Jody Goldberg's avatar
Jody Goldberg committed
122
#endif
123 124

	/* Init, focus, and load handle setting these if/when necessary */
125 126 127 128 129 130 131 132
	sheet->priv->edit_pos.location_changed = TRUE;
	sheet->priv->edit_pos.content_changed  = TRUE;
	sheet->priv->edit_pos.format_changed   = TRUE;

	sheet->priv->selection_content_changed = TRUE;
	sheet->priv->reposition_selection = TRUE;
	sheet->priv->recompute_visibility = TRUE;
	sheet->priv->recompute_spans = TRUE;
133 134
	sheet->priv->reposition_objects.row = SHEET_MAX_ROWS;
	sheet->priv->reposition_objects.col = SHEET_MAX_COLS;
Jody Goldberg's avatar
Jody Goldberg committed
135

136 137
	sheet->priv->auto_expr_idle_id = 0;

138
	sheet->signature = SHEET_SIGNATURE;
Arturo Espinosa's avatar
Arturo Espinosa committed
139
	sheet->workbook = wb;
140 141
	sheet->name_unquoted = g_strdup (name);
	sheet->name_quoted = sheet_name_quote (name);
142
	sheet_style_init (sheet);
143

144
	sheet->sheet_objects = NULL;
145
	sheet->max_object_extent.col = sheet->max_object_extent.row = 0;
146

147
	sheet->last_zoom_factor_used = 1.0;
148 149
	sheet->solver_parameters.options.assume_linear_model = TRUE;
	sheet->solver_parameters.options.assume_non_negative = TRUE;
150 151 152 153
	sheet->solver_parameters.input_entry_str = g_strdup ("");
	sheet->solver_parameters.problem_type = SolverMaximize;
	sheet->solver_parameters.constraints = NULL;
	sheet->solver_parameters.target_cell = NULL;
154

155
	sheet->cols.max_used = -1;
156
	g_ptr_array_set_size (sheet->cols.info = g_ptr_array_new (),
157
			      COLROW_SEGMENT_INDEX (SHEET_MAX_COLS-1)+1);
158 159 160
	sheet_col_set_default_size_pts (sheet, 48);

	sheet->rows.max_used = -1;
161
	g_ptr_array_set_size (sheet->rows.info = g_ptr_array_new (),
162
			      COLROW_SEGMENT_INDEX (SHEET_MAX_ROWS-1)+1);
163 164
	sheet_row_set_default_size_pts (sheet, 12.75);

165
	sheet->print_info = print_info_new ();
166

167 168 169
	sheet->list_merged = NULL;
	sheet->hash_merged = g_hash_table_new ((GHashFunc)&cellpos_hash,
					       (GCompareFunc)&cellpos_cmp);
170

171 172 173
	sheet->deps	 = dependency_data_new ();
	sheet->cell_hash = g_hash_table_new ((GHashFunc)&cellpos_hash,
					     (GCompareFunc)&cellpos_cmp);
174

175
	sheet_selection_add (sheet, 0, 0);
Arturo Espinosa's avatar
Arturo Espinosa committed
176

177
	/* Force the zoom change inorder to initialize things */
178
	sheet_set_zoom_factor (sheet, 1.0, TRUE, TRUE);
179

180 181
	sheet->pristine = TRUE;
	sheet->modified = FALSE;
182

183
	/* Init preferences */
184
	sheet->display_formulas = FALSE;
185 186 187 188
	sheet->hide_zero = FALSE;
	sheet->hide_grid = FALSE;
	sheet->hide_col_header = FALSE;
	sheet->hide_row_header = FALSE;
189
	sheet->frozen_corner.col = sheet->frozen_corner.row = -1;
190

191 192
	sheet->names = NULL;

Arturo Espinosa's avatar
Arturo Espinosa committed
193 194 195
	return sheet;
}

196 197
static int
compute_pixels_from_pts (Sheet const *sheet, float pts, gboolean const horizontal)
198
{
199
	double const scale =
200 201
		sheet->last_zoom_factor_used *
		application_display_dpi_get (horizontal) / 72.;
202

203 204 205 206 207 208 209 210
	return (int)(pts * scale + 0.5);
}

static void
colrow_compute_pixels_from_pts (Sheet const *sheet, ColRowInfo *info, gboolean const horizontal)
{
	info->size_pixels = compute_pixels_from_pts (sheet, info->size_pts,
						     horizontal);
211
}
212

213
static void
214
colrow_compute_pts_from_pixels (Sheet const *sheet, ColRowInfo *info, gboolean const horizontal)
215 216 217 218 219
{
	double const scale =
	    sheet->last_zoom_factor_used *
	    application_display_dpi_get (horizontal) / 72.;

220
	info->size_pts = info->size_pixels / scale;
221
}
222

223 224 225 226 227 228 229 230 231 232 233 234 235
struct resize_colrow {
	Sheet *sheet;
	gboolean horizontal;
};

static gboolean
cb_colrow_compute_pixels_from_pts (ColRowInfo *info, void *data)
{
	struct resize_colrow *closure = data;
	colrow_compute_pixels_from_pts (closure->sheet, info, closure->horizontal);
	return FALSE;
}

236 237
/****************************************************************************/

238
static void
239
cb_recalc_span0 (gpointer ignored, gpointer value, gpointer flags)
240
{
241 242 243 244
	sheet_cell_calc_span (value, GPOINTER_TO_INT (flags));
}

void
Jody Goldberg's avatar
Jody Goldberg committed
245
sheet_calc_spans (Sheet const *sheet, SpanCalcFlags flags)
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
{
	g_hash_table_foreach (sheet->cell_hash, cb_recalc_span0, GINT_TO_POINTER (flags));
}

static Value *
cb_recalc_span1 (Sheet *sheet, int col, int row, Cell *cell, gpointer flags)
{
	sheet_cell_calc_span (cell, GPOINTER_TO_INT (flags));
	return NULL;
}

/**
 * sheet_range_calc_spans:
 * @sheet: The sheet,
 * @r:     the region to update.
 * @render_text: whether to re-render the text in cells
262
 *
263 264 265 266 267
 * This is used to re-calculate cell dimensions and re-render
 * a cell's text. eg. if a format has changed we need to re-render
 * the cached version of the rendered text in the cell.
 **/
void
Jody Goldberg's avatar
Jody Goldberg committed
268
sheet_range_calc_spans (Sheet *sheet, Range r, SpanCalcFlags flags)
269 270 271 272 273 274 275 276
{
	sheet->modified = TRUE;

	/* Redraw the original region in case the span changes */
	sheet_redraw_cell_region (sheet,
				  r.start.col, r.start.row,
				  r.end.col, r.end.row);

277
	sheet_foreach_cell_in_range (sheet, TRUE,
278 279 280 281 282 283 284 285 286
				  r.start.col, r.start.row,
				  r.end.col, r.end.row,
				  cb_recalc_span1,
				  GINT_TO_POINTER (flags));

	/* Redraw the new region in case the span changes */
	sheet_redraw_cell_region (sheet,
				  r.start.col, r.start.row,
				  r.end.col, r.end.row);
287 288
}

289
void
Jody Goldberg's avatar
Jody Goldberg committed
290
sheet_cell_calc_span (Cell const *cell, SpanCalcFlags flags)
291 292 293
{
	CellSpanInfo const * span;
	int left, right;
Jody Goldberg's avatar
Jody Goldberg committed
294
	int min_col, max_col;
295
	gboolean render = (flags & SPANCALC_RE_RENDER);
Jody Goldberg's avatar
Jody Goldberg committed
296 297
	gboolean const resize = (flags & SPANCALC_RESIZE);
	gboolean existing = FALSE;
298 299 300

	g_return_if_fail (cell != NULL);

301 302 303
	/* Render & Size any unrendered cells */
	if ((flags & SPANCALC_RENDER) && cell->rendered_value == NULL)
		render = TRUE;
304

305
	if (render)
306 307
		cell_render_value ((Cell *)cell, TRUE);
	else if (resize)
308 309
		rendered_value_calc_size (cell);

310
	if (sheet_merge_is_corner (cell->base.sheet, &cell->pos)) {
311 312 313 314
		sheet_redraw_cell (cell);
		return;
	}

Jody Goldberg's avatar
Jody Goldberg committed
315
	/* Is there an existing span ? clear it BEFORE calculating new one */
Jody Goldberg's avatar
Jody Goldberg committed
316
	span = row_span_get (cell->row_info, cell->pos.col);
317
	if (span != NULL) {
Jody Goldberg's avatar
Jody Goldberg committed
318
		Cell const * const other = span->cell;
Jody Goldberg's avatar
Jody Goldberg committed
319 320 321

		min_col = span->left;
		max_col = span->right;
Jody Goldberg's avatar
Jody Goldberg committed
322

323
		/* A different cell used to span into this cell, respan that */
Jody Goldberg's avatar
Jody Goldberg committed
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
		if (cell != other) {
			int other_left, other_right;

			cell_calc_span (other, &other_left, &other_right);
			if (min_col > other_left)
				min_col = other_left;
			if (max_col < other_right)
				max_col = other_right;

			/* no need to test, other span definitely changed */
			cell_unregister_span (other);
			if (other_left != other_right)
				cell_register_span (other, other_left, other_right);
		} else
			existing = TRUE;
	} else
		min_col = max_col = cell->pos.col;

	/* Calculate the span of the cell */
	cell_calc_span (cell, &left, &right);
	if (min_col > left)
		min_col = left;
	if (max_col < right)
		max_col = right;

	/* This cell already had an existing span */
350 351 352 353 354 355 356 357
	if (existing) {
		/* If it changed, remove the old one */
		if (left != span->left || right != span->right)
			cell_unregister_span (cell);
		else
			/* unchaged, short curcuit adding the span again */
			left = right;
	}
358 359 360 361

	if (left != right)
		cell_register_span (cell, left, right);

362
	sheet_redraw_partial_row (cell->base.sheet, cell->pos.row,
Jody Goldberg's avatar
Jody Goldberg committed
363
				  min_col, max_col);
364 365
}

Jody Goldberg's avatar
Jody Goldberg committed
366
/**
367
 * sheet_apply_style :
Jody Goldberg's avatar
Jody Goldberg committed
368 369 370 371
 * @sheet: the sheet in which can be found
 * @range: the range to which should be applied
 * @style: the style
 *
372 373 374 375
 * A mid level routine that applies the supplied partial style @style to the
 * target @range and performs the necessary respanning and redrawing.
 *
 * It absorbs the style reference.
Jody Goldberg's avatar
Jody Goldberg committed
376 377
 */
void
378 379 380
sheet_apply_style (Sheet       *sheet,
		   Range const *range,
		   MStyle      *style)
Jody Goldberg's avatar
Jody Goldberg committed
381 382 383
{
	SpanCalcFlags const spanflags = required_updates_for_style (style);

384
	sheet_style_apply_range (sheet, range, style);
Jody Goldberg's avatar
Jody Goldberg committed
385 386 387 388 389 390 391 392
	sheet_range_calc_spans (sheet, *range, spanflags);

	if (spanflags != SPANCALC_SIMPLE)
		rows_height_update (sheet, range);

	sheet_redraw_range (sheet, range);
}

393 394
/****************************************************************************/

395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
/**
 * sheet_update_zoom_controls:
 *
 * This routine is run every time the zoom has changed.  It checks
 * what the status of various toolbar feedback controls should be
 *
 * FIXME: This will at some point become a sheet view function.
 */
static void
sheet_update_zoom_controls (Sheet *sheet)
{
	g_return_if_fail (sheet != NULL);

	WORKBOOK_FOREACH_VIEW (sheet->workbook, view,
	{
		if (wb_view_cur_sheet (view) == sheet) {
411
			WORKBOOK_VIEW_FOREACH_CONTROL (view, control,
412 413 414 415 416
				wb_control_zoom_feedback (control););
		}
	});
}

417 418 419 420 421 422
/**
 * sheet_set_zoom_factor : Change the zoom factor.
 * @sheet : The sheet
 * @f : The new zoom
 * @force : Force the zoom to change irrespective of its current value.
 *          Most callers will want to say FALSE.
423
 * @respan : recalculate the spans.
424
 */
Arturo Espinosa's avatar
Arturo Espinosa committed
425
void
426
sheet_set_zoom_factor (Sheet *sheet, double f, gboolean force, gboolean update)
427
{
428
	struct resize_colrow closure;
429
	double factor;
Jody Goldberg's avatar
Jody Goldberg committed
430

431
	g_return_if_fail (sheet != NULL);
432

Jody Goldberg's avatar
Jody Goldberg committed
433
	/* Bound zoom between 10% and 500% */
434
	factor = (f < .1) ? .1 : ((f > 5.) ? 5. : f);
435 436
	if (!force) {
		double const diff = sheet->last_zoom_factor_used - factor;
437

438 439 440
		if (-.0001 < diff && diff < .0001)
			return;
	}
441

Arturo Espinosa's avatar
Arturo Espinosa committed
442
	sheet->last_zoom_factor_used = factor;
443

Miguel de Icaza's avatar
Miguel de Icaza committed
444
	/* First, the default styles */
445 446
	colrow_compute_pixels_from_pts (sheet, &sheet->rows.default_style, FALSE);
	colrow_compute_pixels_from_pts (sheet, &sheet->cols.default_style, TRUE);
Miguel de Icaza's avatar
Miguel de Icaza committed
447 448

	/* Then every column and row */
449 450
	closure.sheet = sheet;
	closure.horizontal = TRUE;
451 452
	colrow_foreach (&sheet->cols, 0, SHEET_MAX_COLS-1,
			&cb_colrow_compute_pixels_from_pts, &closure);
453
	closure.horizontal = FALSE;
454 455
	colrow_foreach (&sheet->rows, 0, SHEET_MAX_ROWS-1,
			&cb_colrow_compute_pixels_from_pts, &closure);
Miguel de Icaza's avatar
Miguel de Icaza committed
456

457
	SHEET_FOREACH_CONTROL (sheet, control,
458
		scg_set_zoom_factor (control, factor););
459 460

	/*
461 462
	 * The font size does not scale linearly with the zoom factor
	 * we will need to recalculate the pixel sizes of all cells.
463 464
	 * We also need to render any cells which have not yet been
	 * rendered.
465
	 */
466 467 468 469 470 471
	if (update) {
		sheet->priv->recompute_spans = TRUE;
		sheet->priv->reposition_objects.col =
			sheet->priv->reposition_objects.row = 0;
		sheet_update_only_grid (sheet);

472 473
		if (sheet->workbook)
			sheet_update_zoom_controls (sheet);
474
	}
475 476
}

Arturo Espinosa's avatar
Arturo Espinosa committed
477 478 479
ColRowInfo *
sheet_row_new (Sheet *sheet)
{
480
	ColRowInfo *ri = g_new (ColRowInfo, 1);
481 482

	g_return_val_if_fail (IS_SHEET (sheet), NULL);
Miguel de Icaza's avatar
Miguel de Icaza committed
483

484
	*ri = sheet->rows.default_style;
485 486

	return ri;
Arturo Espinosa's avatar
Arturo Espinosa committed
487 488 489 490 491
}

ColRowInfo *
sheet_col_new (Sheet *sheet)
{
492
	ColRowInfo *ci = g_new (ColRowInfo, 1);
493

494
	g_return_val_if_fail (IS_SHEET (sheet), NULL);
495

496
	*ci = sheet->cols.default_style;
497

498
	return ci;
Arturo Espinosa's avatar
Arturo Espinosa committed
499 500 501 502 503
}

void
sheet_col_add (Sheet *sheet, ColRowInfo *cp)
{
504
	int const col = cp->pos;
505
	ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->cols), col);
506 507 508 509 510

	g_return_if_fail (col >= 0);
	g_return_if_fail (col < SHEET_MAX_COLS);

	if (*segment == NULL)
511
		*segment = g_new0 (ColRowSegment, 1);
512
	(*segment)->info[COLROW_SUB_INDEX (col)] = cp;
513 514 515

	if (col > sheet->cols.max_used){
		sheet->cols.max_used = col;
516
		sheet->priv->resize_scrollbar = TRUE;
Arturo Espinosa's avatar
Arturo Espinosa committed
517
	}
Arturo Espinosa's avatar
Arturo Espinosa committed
518 519 520 521 522
}

void
sheet_row_add (Sheet *sheet, ColRowInfo *rp)
{
523
	int const row = rp->pos;
524
	ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->rows), row);
525 526 527 528 529

	g_return_if_fail (row >= 0);
	g_return_if_fail (row < SHEET_MAX_ROWS);

	if (*segment == NULL)
530
		*segment = g_new0 (ColRowSegment, 1);
531
	(*segment)->info[COLROW_SUB_INDEX (row)] = rp;
532 533 534

	if (rp->pos > sheet->rows.max_used){
		sheet->rows.max_used = row;
535
		sheet->priv->resize_scrollbar = TRUE;
Arturo Espinosa's avatar
Arturo Espinosa committed
536
	}
Arturo Espinosa's avatar
Arturo Espinosa committed
537 538 539
}

ColRowInfo *
540
sheet_col_get_info (Sheet const *sheet, int col)
541
{
542
	ColRowInfo *ci = sheet_col_get (sheet, col);
543

544 545
	if (ci != NULL)
		return ci;
546
	return (ColRowInfo *) &sheet->cols.default_style;
547 548
}

549
ColRowInfo *
550
sheet_row_get_info (Sheet const *sheet, int row)
551
{
552
	ColRowInfo *ri = sheet_row_get (sheet, row);
553

554 555
	if (ri != NULL)
		return ri;
556
	return (ColRowInfo *) &sheet->rows.default_style;
557 558
}

Jody Goldberg's avatar
Jody Goldberg committed
559
static void
560
sheet_reposition_objects (Sheet const *sheet, CellPos const *pos)
561
{
562
	GList *ptr;
563

564
	g_return_if_fail (IS_SHEET (sheet));
565

566
	for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = ptr->next )
567
		sheet_object_position (SHEET_OBJECT (ptr->data), pos);
568 569
}

Jody Goldberg's avatar
Jody Goldberg committed
570 571 572 573 574 575
/**
 * sheet_flag_status_update_cell:
 *    flag the sheet as requiring an update to the status display
 *    if the supplied cell location is the edit cursor, or part of the
 *    selected region.
 *
Jody Goldberg's avatar
Jody Goldberg committed
576
 * @cell : The cell that has changed.
Jody Goldberg's avatar
Jody Goldberg committed
577 578 579 580 581
 *
 * Will cause the format toolbar, the edit area, and the auto expressions to be
 * updated if appropriate.
 */
void
Jody Goldberg's avatar
Jody Goldberg committed
582
sheet_flag_status_update_cell (Cell const *cell)
Jody Goldberg's avatar
Jody Goldberg committed
583
{
Jody Goldberg's avatar
Jody Goldberg committed
584 585 586
	Sheet const *sheet = cell->base.sheet;
	CellPos const *pos = &cell->pos;

Jody Goldberg's avatar
Jody Goldberg committed
587 588 589
	/* if a part of the selected region changed value update
	 * the auto expressions
	 */
Jody Goldberg's avatar
Jody Goldberg committed
590
	if (sheet_is_cell_selected (sheet, pos->col, pos->row))
591
		sheet->priv->selection_content_changed = TRUE;
Jody Goldberg's avatar
Jody Goldberg committed
592 593 594 595

	/* If the edit cell changes value update the edit area
	 * and the format toolbar
	 */
596 597
	if (pos->col == sheet->edit_pos.col &&
	    pos->row == sheet->edit_pos.row) {
598
		sheet->priv->edit_pos.content_changed =
599 600
		sheet->priv->edit_pos.format_changed  = TRUE;
	}
Jody Goldberg's avatar
Jody Goldberg committed
601 602 603 604 605 606 607 608 609
}

/**
 * sheet_flag_status_update_range:
 *    flag the sheet as requiring an update to the status display
 *    if the supplied cell location contains the edit cursor, or intersects of
 *    the selected region.
 *
 * @sheet :
610
 * @range : If NULL then force an update.
Jody Goldberg's avatar
Jody Goldberg committed
611 612 613 614 615
 *
 * Will cause the format toolbar, the edit area, and the auto expressions to be
 * updated if appropriate.
 */
void
616
sheet_flag_status_update_range (Sheet const *sheet, Range const *range)
Jody Goldberg's avatar
Jody Goldberg committed
617
{
618 619
	/* Force an update */
	if (range == NULL) {
620
		sheet->priv->selection_content_changed = TRUE;
621 622 623
		sheet->priv->edit_pos.location_changed =
		sheet->priv->edit_pos.content_changed =
		sheet->priv->edit_pos.format_changed = TRUE;
624 625 626
		return;
	}

Jody Goldberg's avatar
Jody Goldberg committed
627 628 629 630
	/* if a part of the selected region changed value update
	 * the auto expressions
	 */
	if (sheet_is_range_selected (sheet, range))
631
		sheet->priv->selection_content_changed = TRUE;
Jody Goldberg's avatar
Jody Goldberg committed
632 633 634 635

	/* If the edit cell changes value update the edit area
	 * and the format toolbar
	 */
636
	if (range_contains (range, sheet->edit_pos.col, sheet->edit_pos.row)) {
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651
		sheet->priv->edit_pos.content_changed =
		sheet->priv->edit_pos.format_changed = TRUE;
	}
}

/**
 * sheet_flag_format_update_range :
 * @sheet : The sheet being changed
 * @range : the range that is changing.
 *
 * Flag format changes that will require updating the format indicators.
 */
void
sheet_flag_format_update_range (Sheet const *sheet, Range const *range)
{
652
	if (range_contains (range, sheet->edit_pos.col, sheet->edit_pos.row))
653
		sheet->priv->edit_pos.format_changed = TRUE;
Jody Goldberg's avatar
Jody Goldberg committed
654 655
}

656 657 658 659 660 661 662 663 664 665 666 667 668 669
/**
 * sheet_flag_selection_change :
 *    flag the sheet as requiring an update to the status display
 *
 * @sheet :
 *
 * Will cause auto expressions to be updated
 */
void
sheet_flag_selection_change (Sheet const *sheet)
{
	sheet->priv->selection_content_changed = TRUE;
}

670 671 672 673 674
/**
 * sheet_update_only_grid :
 *
 * Should be called after a logical command has finished processing
 * to request redraws for any pending events
Jody Goldberg's avatar
Jody Goldberg committed
675
 */
676
void
677
sheet_update_only_grid (Sheet const *sheet)
678
{
Jody Goldberg's avatar
Jody Goldberg committed
679
	SheetPrivate *p;
680

Jody Goldberg's avatar
Jody Goldberg committed
681
	g_return_if_fail (sheet != NULL);
682

683
	p = sheet->priv;
Jody Goldberg's avatar
Jody Goldberg committed
684

685 686
	if (p->reposition_selection) {
		p->reposition_selection = TRUE;
687 688 689 690 691 692 693 694 695 696 697
                /* when moving we cleared the selection before
                 * arriving in here.
                 */
                if (sheet->selections != NULL)
			sheet_selection_set ((Sheet *)sheet, /* cheat */
					     sheet->edit_pos.col,
					     sheet->edit_pos.row,
					     sheet->cursor.base_corner.col,
					     sheet->cursor.base_corner.row,
					     sheet->cursor.move_corner.col,
					     sheet->cursor.move_corner.row);
698 699
	}

700 701
	if (p->recompute_spans) {
		p->recompute_spans = FALSE;
702 703 704 705 706 707 708 709 710 711
		/* FIXME : I would prefer to use SPANCALC_RENDER rather than
		 * RE_RENDER.  It only renders those cells which are not
		 * rendered.  The trouble is that when a col changes size we
		 * need to rerender, but currently nothing marks that.
		 *
		 * hmm, that suggests an approach.  maybe I can install a per
		 * col flag.  Then add a flag clearing loop after the
		 * sheet_calc_span.
		 */
		sheet_calc_spans (sheet, SPANCALC_RESIZE|SPANCALC_RE_RENDER |
Jody Goldberg's avatar
Jody Goldberg committed
712
				  (p->recompute_visibility ?
713
				   SPANCALC_NO_DRAW : SPANCALC_SIMPLE));
714 715
	}

716 717 718 719 720
	if (p->reposition_objects.row < SHEET_MAX_ROWS ||
	    p->reposition_objects.col < SHEET_MAX_COLS) {
		sheet_reposition_objects (sheet, &p->reposition_objects);
		p->reposition_objects.row = SHEET_MAX_ROWS;
		p->reposition_objects.col = SHEET_MAX_COLS;
Jody Goldberg's avatar
Jody Goldberg committed
721 722 723
	}

	if (p->recompute_visibility) {
Jody Goldberg's avatar
Jody Goldberg committed
724 725
		/* TODO : There is room for some opimization
		 * We only need to force complete visibility recalculation
726 727
		 * (which we do in sheet_compute_visible_region)
		 * if a row or col before the start of the visible region.
Jody Goldberg's avatar
Jody Goldberg committed
728 729 730
		 * If we are REALLY smart we could even accumulate the size differential
		 * and use that.
		 */
Jody Goldberg's avatar
Jody Goldberg committed
731
		p->recompute_visibility = FALSE;
732 733 734
		p->resize_scrollbar = FALSE; /* compute_visible_region does this */
		SHEET_FOREACH_CONTROL(sheet, control,
			scg_compute_visible_region (control, TRUE););
Jody Goldberg's avatar
Jody Goldberg committed
735
		sheet_update_cursor_pos (sheet);
Jody Goldberg's avatar
Jody Goldberg committed
736
		sheet_redraw_all (sheet);
737
	}
Jody Goldberg's avatar
Jody Goldberg committed
738

739
	if (p->resize_scrollbar) {
740
		sheet_scrollbar_config (sheet);
741 742
		p->resize_scrollbar = FALSE;
	}
743
}
744

745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763
static gboolean
sheet_update_auto_expr_idle_func (gpointer data)
{
	Sheet *sheet = (Sheet *) data;
	SheetPrivate *p;

	p = sheet->priv;
	if (p->selection_content_changed) {
		p->selection_content_changed = FALSE;
		WORKBOOK_FOREACH_VIEW (sheet->workbook, view,
		{
			if (wb_view_cur_sheet (view) == sheet)
				wb_view_auto_expr_recalc (view, TRUE);
		});
	}
	p->auto_expr_idle_id = 0;
	return FALSE;
}

764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782
/**
 * sheet_update:
 *
 * Should be called after a logical command has finished processing to request
 * redraws for any pending events, and to update the various status regions
 */
void
sheet_update (Sheet const *sheet)
{
	SheetPrivate *p;

	g_return_if_fail (sheet != NULL);

	sheet_update_only_grid (sheet);

	p = sheet->priv;

	if (p->edit_pos.content_changed) {
		p->edit_pos.content_changed = FALSE;
783 784
		WORKBOOK_FOREACH_VIEW (sheet->workbook, view,
		{
785 786
			if (wb_view_cur_sheet (view) == sheet)
				wb_view_edit_line_set (view, NULL);
787 788 789
		});
	}

790 791
	if (p->edit_pos.format_changed) {
		p->edit_pos.format_changed = FALSE;
792 793
		WORKBOOK_FOREACH_VIEW (sheet->workbook, view,
		{
Jody Goldberg's avatar
Jody Goldberg committed
794 795
			if (wb_view_cur_sheet (view) == sheet)
				wb_view_format_feedback (view, TRUE);
796
		});
Jody Goldberg's avatar
Jody Goldberg committed
797 798
	}

799
	/* FIXME : decide whether to do this here or in workbook view */
800
	if (p->edit_pos.location_changed) {
801
		char const *new_pos = cell_pos_name (&sheet->edit_pos);
802

803
		p->edit_pos.location_changed = FALSE;
804 805 806
		WORKBOOK_FOREACH_VIEW (sheet->workbook, view,
		{
			if (wb_view_cur_sheet (view) == sheet) {
807
				WORKBOOK_VIEW_FOREACH_CONTROL (view, control,
808 809 810 811 812
					wb_control_selection_descr_set (control, new_pos););
			}
		});
	}

813
	if (p->selection_content_changed && p->auto_expr_idle_id == 0)
814
		p->auto_expr_idle_id = g_idle_add (sheet_update_auto_expr_idle_func, (gpointer) sheet);
815 816
}

817 818 819 820 821 822 823 824 825
/**
 * sheet_cell_get:
 * @sheet:  The sheet where we want to locate the cell
 * @col:    the cell column
 * @row:    the cell row
 *
 * Return value: a (Cell *) containing the Cell, or NULL if
 * the cell does not exist
 */
826
Cell *
827 828 829
sheet_cell_get (Sheet const *sheet, int col, int row)
{
	Cell *cell;
830
	CellPos pos;
831 832 833

	g_return_val_if_fail (IS_SHEET (sheet), NULL);

834 835 836
	pos.col = col;
	pos.row = row;
	cell = g_hash_table_lookup (sheet->cell_hash, &pos);
837 838 839 840 841 842 843 844 845 846 847 848 849

	return cell;
}

/**
 * sheet_cell_fetch:
 * @sheet:  The sheet where we want to locate the cell
 * @col:    the cell column
 * @row:    the cell row
 *
 * Return value: a (Cell *) containing the Cell at col, row.
 * If no cell existed at that location before, it is created.
 */
850
Cell *
851 852 853 854 855 856 857 858 859 860 861 862 863
sheet_cell_fetch (Sheet *sheet, int col, int row)
{
	Cell *cell;

	g_return_val_if_fail (IS_SHEET (sheet), NULL);

	cell = sheet_cell_get (sheet, col, row);
	if (!cell)
		cell = sheet_cell_new (sheet, col, row);

	return cell;
}

864 865 866 867 868 869 870 871 872 873 874 875
/**
 * sheet_col_row_set_indent :
 */
void
sheet_col_row_set_outline_level (Sheet *sheet, int index, gboolean is_cols,
				 int outline_level, gboolean is_collapsed)
{
	ColRowInfo *cri = is_cols
		? sheet_col_fetch (sheet, index)
		: sheet_row_fetch (sheet, index);
	cri->outline_level = outline_level;
	cri->is_collapsed = (is_collapsed != 0);
Jody Goldberg's avatar
Jody Goldberg committed
876 877 878
#if 0
	printf ("%d = %d %d\n", index+1, outline_level, is_collapsed);
#endif
879 880 881
}

/**
882 883 884 885 886 887 888
 * sheet_col_row_gutter :
 *
 * @sheet :
 * @col_max_outline :
 * @row_max_outline :
 *
 * Set the maximum outline levels for the cols and rows.
889 890
 */
void
891 892 893
sheet_col_row_gutter (Sheet *sheet,
		      int col_max_outline,
		      int row_max_outline)
894 895 896
{
	g_return_if_fail (IS_SHEET (sheet));

897 898
	sheet->cols.max_outline_level = col_max_outline;
	sheet->rows.max_outline_level = row_max_outline;
899 900 901
	SHEET_FOREACH_CONTROL (sheet, control, scg_set_gutters (control););
}

902 903
/**
 * sheet_get_extent_cb:
904
 *
905 906 907
 * checks the cell to see if should be used to calculate sheet extent
 **/
static void
908
sheet_get_extent_cb (gpointer ignored, gpointer value, gpointer data)
Michael Meeks's avatar
Michael Meeks committed
909
{
910
	Cell *cell = (Cell *) value;
911

912
	if (!cell_is_blank (cell)) {
Michael Meeks's avatar
Michael Meeks committed
913
		Range *range = (Range *)data;
914 915
		CellSpanInfo const *span = NULL;
		int tmp;
Michael Meeks's avatar
Michael Meeks committed
916

917 918
		if (cell->pos.row < range->start.row)
			range->start.row = cell->pos.row;
Michael Meeks's avatar
Michael Meeks committed
919

920 921
		if (cell->pos.row > range->end.row)
			range->end.row = cell->pos.row;
Michael Meeks's avatar
Michael Meeks committed
922

923
		/* FIXME : check for merged cells too */
924
		span = row_span_get (cell->row_info, cell->pos.col);
Jody Goldberg's avatar
Jody Goldberg committed
925
		tmp = (span != NULL) ? span->left : cell->pos.col;
926 927
		if (tmp < range->start.col)
			range->start.col = tmp;
Michael Meeks's avatar
Michael Meeks committed
928

Jody Goldberg's avatar
Jody Goldberg committed
929
		tmp = (span != NULL) ? span->right : cell->pos.col;
930 931
		if (tmp > range->end.col)
			range->end.col = tmp;
932 933 934 935 936 937
	}
}

/**
 * sheet_get_extent:
 * @sheet: the sheet
938
 *
939
 * calculates the area occupied by cell data.
940
 *
941 942 943 944 945 946 947 948 949 950 951 952 953 954
 * Return value: the range.
 **/
Range
sheet_get_extent (Sheet const *sheet)
{
	Range r;

	r.start.col = SHEET_MAX_COLS - 2;
	r.start.row = SHEET_MAX_ROWS - 2;
	r.end.col   = 0;
	r.end.row   = 0;

	g_return_val_if_fail (IS_SHEET (sheet), r);

955
	g_hash_table_foreach (sheet->cell_hash, &sheet_get_extent_cb, &r);
956 957 958

	if (r.start.col >= SHEET_MAX_COLS - 2)
		r.start.col = 0;
Michael Meeks's avatar
Michael Meeks committed
959
	if (r.start.row >= SHEET_MAX_ROWS - 2)
960 961 962 963 964 965 966 967 968
		r.start.row = 0;
	if (r.end.col < 0)
		r.end.col = 0;
	if (r.end.row < 0)
		r.end.row = 0;

	return r;
}

969
/*
970
 * Callback for sheet_foreach_cell_in_range to find the maximum width
971 972 973 974 975 976
 * in a range.
 */
static Value *
cb_max_cell_width (Sheet *sheet, int col, int row, Cell *cell,
		   int *max)
{
977 978 979 980 981 982 983 984 985
	int width;

	g_return_val_if_fail (cell->rendered_value != NULL, NULL);

	/* Dynamic cells must be rerendered */
	if (cell->rendered_value->dynamic_width)
		cell_render_value (cell, FALSE);

	width = cell_rendered_width (cell);
986 987 988 989 990
	if (width > *max)
		*max = width;
	return NULL;
}

991
/**
992
 * sheet_col_size_fit_pixels:
993 994 995
 * @sheet: The sheet
 * @col: the column that we want to query
 *
Jody Goldberg's avatar
Jody Goldberg committed
996 997 998 999 1000
 * This routine computes the ideal size for the column to make the contents all
 * cells in the column visible.
 *
 * Return : Maximum size in pixels INCLUDING margins and grid lines
 *          or 0 if there are no cells.
1001 1002
 */
int
1003
sheet_col_size_fit_pixels (Sheet *sheet, int col)
1004
{
1005
	int max = -1;
Jody Goldberg's avatar
Jody Goldberg committed
1006
	ColRowInfo *ci = sheet_col_get (sheet, col);
1007 1008
	if (ci == NULL)
		return 0;
1009

1010
	sheet_foreach_cell_in_range (sheet, TRUE,
1011 1012
				  col, 0,
				  col, SHEET_MAX_ROWS-1,
1013
				  (ForeachCellCB)&cb_max_cell_width, &max);
1014

1015
	/* Reset to the default width if the column was empty */
Jody Goldberg's avatar
Jody Goldberg committed
1016 1017
	if (max <= 0)
		return 0;
1018

Jody Goldberg's avatar
Jody Goldberg committed
1019 1020
	/* Cell width does not include margins or far grid line*/
	max += ci->margin_a + ci->margin_b + 1;
1021
	return max;
1022
}
1023

1024
/*
1025
 * Callback for sheet_foreach_cell_in_range to find the maximum height
1026 1027 1028 1029 1030 1031
 * in a range.
 */
static Value *
cb_max_cell_height (Sheet *sheet, int col, int row, Cell *cell,
		   int *max)
{
1032 1033 1034 1035 1036
	if (!cell_is_merged (cell)) {
		int const height = cell_rendered_height (cell);
		if (height > *max)
			*max = height;
	}
1037
	return NULL;
1038 1039
}

1040
/**
1041
 * sheet_row_size_fit_pixels:
1042 1043 1044 1045
 * @sheet: The sheet
 * @col: the row that we want to query
 *
 * This routine computes the ideal size for the row to make all data fit
1046 1047
 * properly.
 *
Jody Goldberg's avatar
Jody Goldberg committed
1048 1049
 * Return : Maximum size in pixels INCLUDING margins and grid lines
 *          or 0 if there are no cells.
1050 1051
 */
int
1052
sheet_row_size_fit_pixels (Sheet *sheet, int row)
1053
{
1054
	int max = -1;
1055
	ColRowInfo const *ri = sheet_row_get (sheet, row);
1056 1057
	if (ri == NULL)
		return 0;
1058

1059
	sheet_foreach_cell_in_range (sheet, TRUE,
1060 1061
				  0, row,
				  SHEET_MAX_COLS-1, row,
1062
				  (ForeachCellCB)&cb_max_cell_height, &max);
1063

1064
	/* Reset to the default width if the column was empty */
Jody Goldberg's avatar
Jody Goldberg committed
1065 1066
	if (max <= 0)
		return 0;
1067

1068
	/* Cell height does not include margins or bottom grid line */
Jody Goldberg's avatar
Jody Goldberg committed
1069
	max += ri->margin_a + ri->margin_b + 1;
1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081

	/* FIXME FIXME FIXME : HACK HACK HACK
	 * if the height is 1 pixel larger than the minimum required
	 * do not bother to resize.  The current font kludges cause a
	 * problem because the 9pt font font that we display @ 96dpi is a 12
	 * pixel font.  Where as the row height was calculated using windows
	 * which uses a 10pt font @96 dpi and displays a 13pixel font.
	 *
	 * As a result the default row height is 1 pixel too large for the
	 * font.  When we run this test things then resize 1 pixel smaller for
	 * no apparent reason.
	 */
Jody Goldberg's avatar
Jody Goldberg committed
1082
	if (ri->size_pixels == (max+1))
1083
		return 0;
1084
	return max;
1085
}
1086

1087 1088 1089 1090 1091
struct recalc_span_closure {
	Sheet *sheet;
	int col;
};

1092
static gboolean
1093
cb_recalc_spans_in_col (ColRowInfo *ri, gpointer user)
1094
{
1095 1096
	struct recalc_span_closure *closure = user;
	int const col = closure->col;
1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109
	int left, right;
	CellSpanInfo const * span = row_span_get (ri, col);

	if (span) {
		/* If there is an existing span see if it changed */
		Cell const * const cell = span->cell;
		cell_calc_span (cell, &left, &right);
		if (left != span->left || right != span->right) {
			cell_unregister_span (cell);
			cell_register_span (cell, left, right);
		}
	} else {
		/* If there is a cell see if it started to span */
1110
		Cell const * const cell = sheet_cell_get (closure->sheet, col, ri->pos);
1111 1112 1113 1114 1115 1116 1117
		if (cell) {
			cell_calc_span (cell, &left, &right);
			if (left != right)
				cell_register_span (cell, left, right);
		}
	}

1118
	return FALSE;
1119 1120
}

1121 1122
/**
 * sheet_recompute_spans_for_col:
1123 1124 1125 1126 1127 1128
 * @sheet: the sheet
 * @col:   The column that changed
 *
 * This routine recomputes the column span for the cells that touches
 * the column.
 */
1129
void
1130 1131
sheet_recompute_spans_for_col (Sheet *sheet, int col)
{
1132 1133 1134 1135
	struct recalc_span_closure closure;
	closure.sheet = sheet;
	closure.col = col;

1136 1137
	colrow_foreach (&sheet->rows, 0, SHEET_MAX_ROWS-1,
			&cb_recalc_spans_in_col, &closure);
1138 1139
}

1140 1141
/****************************************************************************/

1142
/*
1143
 * Callback for sheet_foreach_cell_in_range to assign some text
1144 1145
 * to a range.
 */
1146
typedef struct {
1147
	StyleFormat *format;
1148 1149 1150 1151
	Value      *val;
	ExprTree   *expr;
} closure_set_cell_value;

1152
static Value *
1153 1154
cb_set_cell_content (Sheet *sheet, int col, int row, Cell *cell,
		     closure_set_cell_value *info)
Arturo Espinosa's avatar
Arturo Espinosa committed
1155
{
1156 1157
	if (cell == NULL)
		cell = sheet_cell_new (sheet, col, row);
Jody Goldberg's avatar
Jody Goldberg committed
1158 1159 1160
	if (info->expr != NULL)
		cell_set_expr (cell, info->expr, info->format);
	else
1161 1162
		cell_set_value (cell, value_duplicate (info->val),
				info->format);
1163 1164
	return NULL;
}
1165

1166 1167 1168 1169 1170 1171 1172 1173 1174
static Value *
cb_clear_non_corner (Sheet *sheet, int col, int row, Cell *cell,
		     Range const *merged)
{
	if (merged->start.col != col || merged->start.row != row)
		cell_set_value (cell, value_new_empty (), NULL);
	return NULL;
}

1175 1176 1177
/**
 * sheet_range_set_text :
 *
1178 1179 1180 1181
 * @pos : The position from which to parse an expression.
 * @r  :  The range to fill
 * @str : The text to be parsed and assigned.
 *
1182
 * Does NOT check for array division.
1183 1184
 * Does NOT redraw
 * Does NOT generate spans.
1185 1186
 */
void
1187
sheet_range_set_text (EvalPos const *pos, Range const *r, char const *str)
1188
{
1189
	closure_set_cell_value	closure;
1190
	GSList *merged, *ptr;
Arturo Espinosa's avatar
Arturo Espinosa committed
1191

1192 1193 1194
	g_return_if_fail (pos != NULL);
	g_return_if_fail (r != NULL);
	g_return_if_fail (str != NULL);
1195

1196 1197 1198
	closure.format = parse_text_value_or_expr (pos, str,
		&closure.val, &closure.expr,
		NULL /* TODO : Use edit_pos format ?? */);
1199

1200
	/* Store the parsed result creating any cells necessary */
1201
	sheet_foreach_cell_in_range (pos->sheet, FALSE,
1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216
				     r->start.col, r->start.row,
				     r->end.col, r->end.row,
				     (ForeachCellCB)&cb_set_cell_content,
				     &closure);

	merged = sheet_merge_get_overlap (pos->sheet, r);
	for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
		Range const *r = ptr->data;
		sheet_foreach_cell_in_range (pos->sheet, FALSE,
					     r->start.col, r->start.row,
					     r->end.col, r->end.row,
					     (ForeachCellCB)&cb_clear_non_corner,
					     (gpointer)r);
	}
	g_slist_free (merged);
1217

1218 1219 1220
	if (closure.format)
		style_format_unref (closure.format);

1221
	if (closure.val)
1222
		value_release (closure.val);
1223
	else
1224 1225 1226
		expr_tree_unref (closure.expr);

	sheet_flag_status_update_range (pos->sheet, r);
Arturo Espinosa's avatar
Arturo Espinosa committed
1227 1228
}