sheet.c 112 KB
Newer Older
1
/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
Jody Goldberg's avatar
Jody Goldberg committed
2

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>
Arturo Espinosa's avatar
Arturo Espinosa committed
11
#include "gnumeric.h"
12
#include "command-context.h"
13
#include "sheet-control-gui.h"
Jody Goldberg's avatar
Jody Goldberg committed
14 15
#include "sheet.h"
#include "sheet-style.h"
16 17
#include "workbook-control.h"
#include "workbook-view.h"
18
#include "workbook-private.h"
19
#include "workbook-edit.h"
20
#include "parse-util.h"
21
#include "gnumeric-util.h"
22
#include "eval.h"
23
#include "value.h"
24 25
#include "number-match.h"
#include "format.h"
26
#include "clipboard.h"
27
#include "selection.h"
28
#include "ranges.h"
29
#include "print-info.h"
30
#include "mstyle.h"
31
#include "application.h"
Jody Goldberg's avatar
Jody Goldberg committed
32
#include "commands.h"
33
#include "cellspan.h"
Jody Goldberg's avatar
Jody Goldberg committed
34
#include "cell.h"
35
#include "sheet-merge.h"
36
#include "sheet-private.h"
37
#include "expr-name.h"
38
#include "rendered-value.h"
39 40
#include "sheet-object-impl.h"
#include "sheet-object-cell-comment.h"
Arturo Espinosa's avatar
Arturo Espinosa committed
41

42 43 44 45 46
#include <gnome.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

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

50 51 52 53
void
sheet_unant (Sheet *sheet)
{
	GList *l;
54

55 56 57 58
	g_return_if_fail (sheet != NULL);

	if (!sheet->ants)
		return;
59

60 61 62 63 64 65 66 67 68
	for (l = sheet->ants; l != NULL; l = l->next) {
		Range *ss = l->data;
		g_free (ss);
	}

	g_list_free (sheet->ants);
	sheet->ants = NULL;

	SHEET_FOREACH_CONTROL (sheet, control,
69
			       sc_unant (control););
70 71 72 73 74 75 76 77 78 79 80 81
}

void
sheet_ant (Sheet *sheet, GList *ranges)
{
	GList *l;

	g_return_if_fail (sheet != NULL);
	g_return_if_fail (ranges != NULL);

	if (sheet->ants != NULL)
		sheet_unant (sheet);
82

83 84 85 86 87 88 89 90 91
	/*
	 * We need to copy the whole selection to the
	 * 'ant' list which contains all currently anted
	 * regions on the sheet. Every sheet-control-gui
	 * look at that list to ant/unant things
	 */
	for (l = ranges; l != NULL; l = l->next) {
		Range *ss = l->data;

92
		sheet->ants = g_list_prepend (sheet->ants, range_dup (ss));
93 94
	}
	sheet->ants = g_list_reverse (sheet->ants);
95

96
	SHEET_FOREACH_CONTROL (sheet, control,
97
		sc_ant (control););
98 99
}

Arturo Espinosa's avatar
Arturo Espinosa committed
100
void
101
sheet_redraw_all (Sheet const *sheet)
Arturo Espinosa's avatar
Arturo Espinosa committed
102
{
103
	SHEET_FOREACH_CONTROL (sheet, control,
104
		sc_redraw_all (control););
Arturo Espinosa's avatar
Arturo Espinosa committed
105 106
}

107
void
Jody Goldberg's avatar
Jody Goldberg committed
108
sheet_redraw_headers (Sheet const *sheet,
109 110
		      gboolean col, gboolean row,
		      Range const *r /* optional == NULL */)
111
{
112
	SHEET_FOREACH_CONTROL (sheet, control,
113
		sc_redraw_headers (control, col, row, r););
114 115
}

Miguel de Icaza's avatar
Miguel de Icaza committed
116
void
117
sheet_rename (Sheet *sheet, char const *new_name)
Miguel de Icaza's avatar
Miguel de Icaza committed
118 119 120 121
{
	g_return_if_fail (IS_SHEET (sheet));
	g_return_if_fail (new_name != NULL);

122 123 124 125
	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
126 127
}

128
SheetControlGUI *
129
sheet_new_scg (Sheet *sheet)
130
{
131 132
	GtkObject *scg;
	Range const *r;
133

134 135
	r = selection_first_range (sheet, NULL, NULL);
	g_return_val_if_fail (r != NULL, NULL);
136

137
	scg = sheet_control_gui_new (sheet);
138
	/* Set the visible bound, not the logical bound */
139
	sc_cursor_bound (SHEET_CONTROL (scg), r);
140
	sheet->s_controls = g_list_prepend (sheet->s_controls, scg);
141

142
	return SHEET_CONTROL_GUI (scg);
143 144 145
}

void
146
sheet_detach_control (SheetControl *sc)
147
{
148 149 150 151 152
	Sheet *sheet;
	
	g_return_if_fail (IS_SHEET_CONTROL (sc));
	sheet = sc_sheet (sc);
	g_return_if_fail (IS_SHEET (sheet));
153

154 155
	sheet->s_controls = g_list_remove (sheet->s_controls, sc);
	sc_invalidate_sheet (sc);
156 157
}

158 159 160 161 162
/*
 * sheet_new
 * @wb              Workbook
 * @name            Unquoted name
 */
Arturo Espinosa's avatar
Arturo Espinosa committed
163
Sheet *
164
sheet_new (Workbook *wb, char const *name)
Arturo Espinosa's avatar
Arturo Espinosa committed
165
{
166
	Sheet  *sheet;
Arturo Espinosa's avatar
Arturo Espinosa committed
167

Miguel de Icaza's avatar
Miguel de Icaza committed
168 169
	g_return_val_if_fail (wb != NULL, NULL);
	g_return_val_if_fail (name != NULL, NULL);
170

Arturo Espinosa's avatar
Arturo Espinosa committed
171
	sheet = g_new0 (Sheet, 1);
172
	sheet->priv = g_new0 (SheetPrivate, 1);
Jody Goldberg's avatar
Jody Goldberg committed
173
#ifdef ENABLE_BONOBO
174 175
	sheet->priv->corba_server = NULL;
	sheet->priv->sheet_vectors = NULL;
Jody Goldberg's avatar
Jody Goldberg committed
176
#endif
177 178

	/* Init, focus, and load handle setting these if/when necessary */
179 180 181 182 183 184 185 186
	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;
187 188
	sheet->priv->reposition_objects.row = SHEET_MAX_ROWS;
	sheet->priv->reposition_objects.col = SHEET_MAX_COLS;
Jody Goldberg's avatar
Jody Goldberg committed
189

190
	sheet->priv->auto_expr_timer = 0;
191

192
	sheet->signature = SHEET_SIGNATURE;
Arturo Espinosa's avatar
Arturo Espinosa committed
193
	sheet->workbook = wb;
194 195
	sheet->name_unquoted = g_strdup (name);
	sheet->name_quoted = sheet_name_quote (name);
196
	sheet_style_init (sheet);
197

198
	sheet->sheet_objects = NULL;
199
	sheet->max_object_extent.col = sheet->max_object_extent.row = 0;
200

201
	sheet->last_zoom_factor_used = 1.0;
202 203
	sheet->solver_parameters.options.assume_linear_model = TRUE;
	sheet->solver_parameters.options.assume_non_negative = TRUE;
204 205 206 207
	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;
208

209
	sheet->cols.max_used = -1;
210
	g_ptr_array_set_size (sheet->cols.info = g_ptr_array_new (),
211
			      COLROW_SEGMENT_INDEX (SHEET_MAX_COLS-1)+1);
212 213 214
	sheet_col_set_default_size_pts (sheet, 48);

	sheet->rows.max_used = -1;
215
	g_ptr_array_set_size (sheet->rows.info = g_ptr_array_new (),
216
			      COLROW_SEGMENT_INDEX (SHEET_MAX_ROWS-1)+1);
217 218
	sheet_row_set_default_size_pts (sheet, 12.75);

219
	sheet->print_info = print_info_new ();
220

221 222 223
	sheet->list_merged = NULL;
	sheet->hash_merged = g_hash_table_new ((GHashFunc)&cellpos_hash,
					       (GCompareFunc)&cellpos_cmp);
224

225 226 227
	sheet->deps	 = dependency_data_new ();
	sheet->cell_hash = g_hash_table_new ((GHashFunc)&cellpos_hash,
					     (GCompareFunc)&cellpos_cmp);
228

229
	sheet_selection_add (sheet, 0, 0);
Arturo Espinosa's avatar
Arturo Espinosa committed
230

231
	/* Force the zoom change inorder to initialize things */
232
	sheet_set_zoom_factor (sheet, 1.0, TRUE, TRUE);
233

234 235
	sheet->pristine = TRUE;
	sheet->modified = FALSE;
236

237
	/* Init preferences */
238
	sheet->display_formulas = FALSE;
239 240 241 242
	sheet->hide_zero = FALSE;
	sheet->hide_grid = FALSE;
	sheet->hide_col_header = FALSE;
	sheet->hide_row_header = FALSE;
243 244 245
	sheet->display_outlines = TRUE;
	sheet->outline_symbols_below = TRUE;
	sheet->outline_symbols_right = TRUE;
246
	sheet->frozen_corner.col = sheet->frozen_corner.row = -1;
247

248 249 250 251 252 253
	/* Init menu states */
	sheet->priv->enable_insert_rows = TRUE;
	sheet->priv->enable_insert_cols = TRUE;
	sheet->priv->enable_insert_cells = TRUE;
	sheet->priv->enable_paste_special = TRUE;
	sheet->priv->enable_showhide_detail = TRUE;
254

255 256
	sheet->names = NULL;

Arturo Espinosa's avatar
Arturo Espinosa committed
257 258 259
	return sheet;
}

260 261
static int
compute_pixels_from_pts (Sheet const *sheet, float pts, gboolean const horizontal)
262
{
263
	double const scale =
264 265
		sheet->last_zoom_factor_used *
		application_display_dpi_get (horizontal) / 72.;
266

267 268 269 270 271 272 273 274
	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);
275
}
276

277
static void
278
colrow_compute_pts_from_pixels (Sheet const *sheet, ColRowInfo *info, gboolean const horizontal)
279 280 281 282 283
{
	double const scale =
	    sheet->last_zoom_factor_used *
	    application_display_dpi_get (horizontal) / 72.;

284
	info->size_pts = info->size_pixels / scale;
285
}
286

287 288 289 290 291 292 293 294 295 296 297 298 299
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;
}

300 301
/****************************************************************************/

302
static void
303
cb_recalc_span0 (gpointer ignored, gpointer value, gpointer flags)
304
{
305 306 307 308
	sheet_cell_calc_span (value, GPOINTER_TO_INT (flags));
}

void
Jody Goldberg's avatar
Jody Goldberg committed
309
sheet_calc_spans (Sheet const *sheet, SpanCalcFlags flags)
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
{
	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
326
 *
327 328 329 330 331
 * 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
332
sheet_range_calc_spans (Sheet *sheet, Range r, SpanCalcFlags flags)
333 334 335 336 337 338 339 340
{
	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);

341
	sheet_foreach_cell_in_range (sheet, TRUE,
342 343 344 345
				     r.start.col, r.start.row,
				     r.end.col, r.end.row,
				     cb_recalc_span1,
				     GINT_TO_POINTER (flags));
346 347 348 349 350

	/* 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);
351 352
}

353
void
Jody Goldberg's avatar
Jody Goldberg committed
354
sheet_cell_calc_span (Cell const *cell, SpanCalcFlags flags)
355 356 357
{
	CellSpanInfo const * span;
	int left, right;
Jody Goldberg's avatar
Jody Goldberg committed
358
	int min_col, max_col;
359
	gboolean render = (flags & SPANCALC_RE_RENDER);
Jody Goldberg's avatar
Jody Goldberg committed
360 361
	gboolean const resize = (flags & SPANCALC_RESIZE);
	gboolean existing = FALSE;
362
	Range const *merged;
363 364 365

	g_return_if_fail (cell != NULL);

366 367 368
	/* Render & Size any unrendered cells */
	if ((flags & SPANCALC_RENDER) && cell->rendered_value == NULL)
		render = TRUE;
369

370
	if (render)
371 372
		cell_render_value ((Cell *)cell, TRUE);
	else if (resize)
373 374
		rendered_value_calc_size (cell);

Jody Goldberg's avatar
Jody Goldberg committed
375
	/* Is there an existing span ? clear it BEFORE calculating new one */
Jody Goldberg's avatar
Jody Goldberg committed
376
	span = row_span_get (cell->row_info, cell->pos.col);
377
	if (span != NULL) {
Jody Goldberg's avatar
Jody Goldberg committed
378
		Cell const * const other = span->cell;
Jody Goldberg's avatar
Jody Goldberg committed
379 380 381

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

383
		/* A different cell used to span into this cell, respan that */
Jody Goldberg's avatar
Jody Goldberg committed
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
		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;

402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
	merged = sheet_merge_is_corner (cell->base.sheet, &cell->pos);
	if (NULL != merged) {
		if (existing) {
			if (min_col > merged->start.col)
				min_col = merged->start.col;
			if (max_col < merged->end.col)
				max_col = merged->end.col;
		} else {
			sheet_redraw_cell (cell);
			return;
		}
	} else {
		/* 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 */
		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;
		}
430

431 432 433
		if (left != right)
			cell_register_span (cell, left, right);
	}
434

435
	sheet_redraw_partial_row (cell->base.sheet, cell->pos.row,
Jody Goldberg's avatar
Jody Goldberg committed
436
				  min_col, max_col);
437 438
}

Jody Goldberg's avatar
Jody Goldberg committed
439
/**
440
 * sheet_apply_style :
Jody Goldberg's avatar
Jody Goldberg committed
441 442 443 444
 * @sheet: the sheet in which can be found
 * @range: the range to which should be applied
 * @style: the style
 *
445 446 447 448
 * 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
449 450
 */
void
451 452 453
sheet_apply_style (Sheet       *sheet,
		   Range const *range,
		   MStyle      *style)
Jody Goldberg's avatar
Jody Goldberg committed
454 455 456
{
	SpanCalcFlags const spanflags = required_updates_for_style (style);

457
	sheet_style_apply_range (sheet, range, style);
Jody Goldberg's avatar
Jody Goldberg committed
458 459 460
	sheet_range_calc_spans (sheet, *range, spanflags);

	if (spanflags != SPANCALC_SIMPLE)
461
		rows_height_update (sheet, range, TRUE);
Jody Goldberg's avatar
Jody Goldberg committed
462 463 464 465

	sheet_redraw_range (sheet, range);
}

466 467
/****************************************************************************/

468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
/**
 * 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) {
484
			WORKBOOK_VIEW_FOREACH_CONTROL (view, control,
485 486 487 488 489
				wb_control_zoom_feedback (control););
		}
	});
}

490 491 492 493 494 495
/**
 * 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.
496
 * @respan : recalculate the spans.
497
 */
Arturo Espinosa's avatar
Arturo Espinosa committed
498
void
499
sheet_set_zoom_factor (Sheet *sheet, double f, gboolean force, gboolean update)
500
{
501
	struct resize_colrow closure;
502
	double factor;
Jody Goldberg's avatar
Jody Goldberg committed
503

504
	g_return_if_fail (sheet != NULL);
505

Jody Goldberg's avatar
Jody Goldberg committed
506
	/* Bound zoom between 10% and 500% */
507
	factor = (f < .1) ? .1 : ((f > 5.) ? 5. : f);
508 509
	if (!force) {
		double const diff = sheet->last_zoom_factor_used - factor;
510

511 512 513
		if (-.0001 < diff && diff < .0001)
			return;
	}
514

Arturo Espinosa's avatar
Arturo Espinosa committed
515
	sheet->last_zoom_factor_used = factor;
516

Miguel de Icaza's avatar
Miguel de Icaza committed
517
	/* First, the default styles */
518 519
	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
520 521

	/* Then every column and row */
522 523
	closure.sheet = sheet;
	closure.horizontal = TRUE;
524 525
	colrow_foreach (&sheet->cols, 0, SHEET_MAX_COLS-1,
			&cb_colrow_compute_pixels_from_pts, &closure);
526
	closure.horizontal = FALSE;
527 528
	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
529

530
	SHEET_FOREACH_CONTROL (sheet, control, sc_set_zoom_factor (control););
531 532

	/*
533 534
	 * The font size does not scale linearly with the zoom factor
	 * we will need to recalculate the pixel sizes of all cells.
535 536
	 * We also need to render any cells which have not yet been
	 * rendered.
537
	 */
538 539 540 541 542 543
	if (update) {
		sheet->priv->recompute_spans = TRUE;
		sheet->priv->reposition_objects.col =
			sheet->priv->reposition_objects.row = 0;
		sheet_update_only_grid (sheet);

544 545
		if (sheet->workbook)
			sheet_update_zoom_controls (sheet);
546
	}
547 548
}

Arturo Espinosa's avatar
Arturo Espinosa committed
549 550 551
ColRowInfo *
sheet_row_new (Sheet *sheet)
{
552
	ColRowInfo *ri = g_new (ColRowInfo, 1);
553 554

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

556
	*ri = sheet->rows.default_style;
557 558

	return ri;
Arturo Espinosa's avatar
Arturo Espinosa committed
559 560 561 562 563
}

ColRowInfo *
sheet_col_new (Sheet *sheet)
{
564
	ColRowInfo *ci = g_new (ColRowInfo, 1);
565

566
	g_return_val_if_fail (IS_SHEET (sheet), NULL);
567

568
	*ci = sheet->cols.default_style;
569

570
	return ci;
Arturo Espinosa's avatar
Arturo Espinosa committed
571 572 573 574 575
}

void
sheet_col_add (Sheet *sheet, ColRowInfo *cp)
{
576
	int const col = cp->pos;
577
	ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->cols), col);
578 579 580 581 582

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

	if (*segment == NULL)
583
		*segment = g_new0 (ColRowSegment, 1);
584
	(*segment)->info[COLROW_SUB_INDEX (col)] = cp;
585

586 587
	if (cp->outline_level > sheet->cols.max_outline_level)
		sheet->cols.max_outline_level = cp->outline_level;
588 589
	if (col > sheet->cols.max_used){
		sheet->cols.max_used = col;
590
		sheet->priv->resize_scrollbar = TRUE;
Arturo Espinosa's avatar
Arturo Espinosa committed
591
	}
Arturo Espinosa's avatar
Arturo Espinosa committed
592 593 594 595 596
}

void
sheet_row_add (Sheet *sheet, ColRowInfo *rp)
{
597
	int const row = rp->pos;
598
	ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->rows), row);
599 600 601 602 603

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

	if (*segment == NULL)
604
		*segment = g_new0 (ColRowSegment, 1);
605
	(*segment)->info[COLROW_SUB_INDEX (row)] = rp;
606

607 608
	if (rp->outline_level > sheet->rows.max_outline_level)
		sheet->rows.max_outline_level = rp->outline_level;
609 610
	if (rp->pos > sheet->rows.max_used){
		sheet->rows.max_used = row;
611
		sheet->priv->resize_scrollbar = TRUE;
Arturo Espinosa's avatar
Arturo Espinosa committed
612
	}
Arturo Espinosa's avatar
Arturo Espinosa committed
613 614 615
}

ColRowInfo *
616
sheet_col_get_info (Sheet const *sheet, int col)
617
{
618
	ColRowInfo *ci = sheet_col_get (sheet, col);
619

620 621
	if (ci != NULL)
		return ci;
622
	return (ColRowInfo *) &sheet->cols.default_style;
623 624
}

625
ColRowInfo *
626
sheet_row_get_info (Sheet const *sheet, int row)
627
{
628
	ColRowInfo *ri = sheet_row_get (sheet, row);
629

630 631
	if (ri != NULL)
		return ri;
632
	return (ColRowInfo *) &sheet->rows.default_style;
633 634
}

Jody Goldberg's avatar
Jody Goldberg committed
635
static void
636
sheet_reposition_objects (Sheet const *sheet, CellPos const *pos)
637
{
638
	GList *ptr;
639

640
	g_return_if_fail (IS_SHEET (sheet));
641

642
	for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = ptr->next )
643
		sheet_object_position (SHEET_OBJECT (ptr->data), pos);
644 645
}

Jody Goldberg's avatar
Jody Goldberg committed
646 647 648 649 650 651
/**
 * 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
652
 * @cell : The cell that has changed.
Jody Goldberg's avatar
Jody Goldberg committed
653 654 655 656 657
 *
 * 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
658
sheet_flag_status_update_cell (Cell const *cell)
Jody Goldberg's avatar
Jody Goldberg committed
659
{
Jody Goldberg's avatar
Jody Goldberg committed
660 661 662
	Sheet const *sheet = cell->base.sheet;
	CellPos const *pos = &cell->pos;

Jody Goldberg's avatar
Jody Goldberg committed
663 664 665
	/* if a part of the selected region changed value update
	 * the auto expressions
	 */
Jody Goldberg's avatar
Jody Goldberg committed
666
	if (sheet_is_cell_selected (sheet, pos->col, pos->row))
667
		sheet->priv->selection_content_changed = TRUE;
Jody Goldberg's avatar
Jody Goldberg committed
668 669 670 671

	/* If the edit cell changes value update the edit area
	 * and the format toolbar
	 */
672 673
	if (pos->col == sheet->edit_pos.col &&
	    pos->row == sheet->edit_pos.row) {
674
		sheet->priv->edit_pos.content_changed =
675 676
		sheet->priv->edit_pos.format_changed  = TRUE;
	}
Jody Goldberg's avatar
Jody Goldberg committed
677 678 679 680 681 682 683 684 685
}

/**
 * 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 :
686
 * @range : If NULL then force an update.
Jody Goldberg's avatar
Jody Goldberg committed
687 688 689 690 691
 *
 * Will cause the format toolbar, the edit area, and the auto expressions to be
 * updated if appropriate.
 */
void
692
sheet_flag_status_update_range (Sheet const *sheet, Range const *range)
Jody Goldberg's avatar
Jody Goldberg committed
693
{
694 695
	/* Force an update */
	if (range == NULL) {
696
		sheet->priv->selection_content_changed = TRUE;
697 698 699
		sheet->priv->edit_pos.location_changed =
		sheet->priv->edit_pos.content_changed =
		sheet->priv->edit_pos.format_changed = TRUE;
700 701 702
		return;
	}

Jody Goldberg's avatar
Jody Goldberg committed
703 704 705 706
	/* if a part of the selected region changed value update
	 * the auto expressions
	 */
	if (sheet_is_range_selected (sheet, range))
707
		sheet->priv->selection_content_changed = TRUE;
Jody Goldberg's avatar
Jody Goldberg committed
708 709 710 711

	/* If the edit cell changes value update the edit area
	 * and the format toolbar
	 */
712
	if (range_contains (range, sheet->edit_pos.col, sheet->edit_pos.row)) {
713 714 715 716 717 718 719 720 721 722 723 724 725 726 727
		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)
{
728
	if (range_contains (range, sheet->edit_pos.col, sheet->edit_pos.row))
729
		sheet->priv->edit_pos.format_changed = TRUE;
Jody Goldberg's avatar
Jody Goldberg committed
730 731
}

732 733 734 735 736 737 738 739 740 741 742 743 744 745
/**
 * 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;
}

746 747 748 749 750 751 752 753 754 755 756 757
/**
 * sheet_flag_recompute_spans:
 *    flag the sheet as requiring a full span recomputation.
 *
 * @sheet :
 */
void
sheet_flag_recompute_spans (Sheet const *sheet)
{
	sheet->priv->recompute_spans = TRUE;
}

758 759 760 761 762
/**
 * 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
763
 */
764
void
765
sheet_update_only_grid (Sheet const *sheet)
766
{
Jody Goldberg's avatar
Jody Goldberg committed
767
	SheetPrivate *p;
768

769
	g_return_if_fail (IS_SHEET (sheet));
770

771
	p = sheet->priv;
Jody Goldberg's avatar
Jody Goldberg committed
772

773
	if (p->reposition_selection) {
774
		p->reposition_selection = FALSE;
775 776 777 778 779 780 781 782 783 784 785
                /* 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);
786 787
	}

788 789
	if (p->recompute_spans) {
		p->recompute_spans = FALSE;
790 791 792 793 794 795 796 797 798 799
		/* 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
800
				  (p->recompute_visibility ?
801
				   SPANCALC_NO_DRAW : SPANCALC_SIMPLE));
802 803
	}

804 805 806 807 808
	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
809 810 811
	}

	if (p->recompute_visibility) {
Jody Goldberg's avatar
Jody Goldberg committed
812 813
		/* TODO : There is room for some opimization
		 * We only need to force complete visibility recalculation
814 815
		 * (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
816 817 818
		 * If we are REALLY smart we could even accumulate the size differential
		 * and use that.
		 */
Jody Goldberg's avatar
Jody Goldberg committed
819
		p->recompute_visibility = FALSE;
820 821
		p->resize_scrollbar = FALSE; /* compute_visible_region does this */
		SHEET_FOREACH_CONTROL(sheet, control,
822
			sc_compute_visible_region (control, TRUE););
Jody Goldberg's avatar
Jody Goldberg committed
823
		sheet_update_cursor_pos (sheet);
Jody Goldberg's avatar
Jody Goldberg committed
824
		sheet_redraw_all (sheet);
825
	}
Jody Goldberg's avatar
Jody Goldberg committed
826

827
	if (p->resize_scrollbar) {
828
		sheet_scrollbar_config (sheet);
829 830
		p->resize_scrollbar = FALSE;
	}
831
}
832

833 834 835 836 837 838 839 840 841
static void
auto_expr_timer_clear (SheetPrivate *p)
{
	if (p->auto_expr_timer != 0) {
		gtk_timeout_remove (p->auto_expr_timer);
		p->auto_expr_timer = 0;
	}
}

842
static gboolean
843
cb_sheet_update_auto_expr (gpointer data)
844 845 846 847 848
{
	Sheet *sheet = (Sheet *) data;
	SheetPrivate *p;

	p = sheet->priv;
849 850 851 852 853 854 855
	WORKBOOK_FOREACH_VIEW (sheet->workbook, view,
	{
		if (wb_view_cur_sheet (view) == sheet)
			wb_view_auto_expr_recalc (view, TRUE);
	});

	p->auto_expr_timer = 0;
856 857 858
	return FALSE;
}

859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877
/**
 * 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;
878 879
		WORKBOOK_FOREACH_VIEW (sheet->workbook, view,
		{
880 881
			if (wb_view_cur_sheet (view) == sheet)
				wb_view_edit_line_set (view, NULL);
882 883 884
		});
	}

885 886
	if (p->edit_pos.format_changed) {
		p->edit_pos.format_changed = FALSE;
887 888
		WORKBOOK_FOREACH_VIEW (sheet->workbook, view,
		{
Jody Goldberg's avatar
Jody Goldberg committed
889 890
			if (wb_view_cur_sheet (view) == sheet)
				wb_view_format_feedback (view, TRUE);
891
		});
Jody Goldberg's avatar
Jody Goldberg committed
892 893
	}

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

898
		p->edit_pos.location_changed = FALSE;
899 900 901
		WORKBOOK_FOREACH_VIEW (sheet->workbook, view,
		{
			if (wb_view_cur_sheet (view) == sheet) {
902
				WORKBOOK_VIEW_FOREACH_CONTROL (view, control,
903 904 905 906 907
					wb_control_selection_descr_set (control, new_pos););
			}
		});
	}

908 909 910 911 912 913 914 915 916
	if (p->selection_content_changed) {
		int const lag = application_auto_expr_recalc_lag ();
		p->selection_content_changed = FALSE;
		if (p->auto_expr_timer == 0 || lag < 0) {
			auto_expr_timer_clear (p);
			p->auto_expr_timer = gtk_timeout_add (abs (lag), /* seems ok */
				cb_sheet_update_auto_expr, (gpointer) sheet);
		}
	}
917 918
}

919 920 921 922 923 924 925 926 927
/**
 * 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
 */
928
Cell *
929 930 931
sheet_cell_get (Sheet const *sheet, int col, int row)
{
	Cell *cell;
932
	CellPos pos;
933 934 935

	g_return_val_if_fail (IS_SHEET (sheet), NULL);

936 937 938
	pos.col = col;
	pos.row = row;
	cell = g_hash_table_lookup (sheet->cell_hash, &pos);
939 940 941 942 943 944 945 946 947 948 949 950 951

	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.
 */
952
Cell *
953 954 955 956 957 958 959 960 961 962 963 964 965
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;
}

966
/**
967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084
 * sheet_col_row_can_group:
 * 
 * Returns TRUE if @from to @to can be grouped, return
 * FALSE otherwise. You can invert the result if you need
 * to find out if a group can be ungrouped.
 **/
gboolean
sheet_col_row_can_group (Sheet *sheet, int from, int to, gboolean is_cols)
{
	ColRowInfo *from_cri, *to_cri;
	
	g_return_val_if_fail (sheet != NULL, FALSE);
	g_return_val_if_fail (from >= 0, FALSE);
	g_return_val_if_fail (is_cols ? to < SHEET_MAX_COLS : to < SHEET_MAX_ROWS, FALSE);

	from_cri = is_cols
		? sheet_col_fetch (sheet, from)
		: sheet_row_fetch (sheet, from);

	to_cri = is_cols
		? sheet_col_fetch (sheet, to)
		: sheet_row_fetch (sheet, to);

	/* Groups on outline level 0 (no outline) may always be formed */
	if (from_cri->outline_level == 0 || to_cri->outline_level == 0)
		return TRUE;

	/* We just won't group a group that already exists (or doesn't), it's useless */
	if (colrow_find_outline_bound (sheet, is_cols, from, from_cri->outline_level, FALSE) != from
	    || colrow_find_outline_bound (sheet, is_cols, to, to_cri->outline_level, TRUE) != to)
		return TRUE;
	else
		return FALSE;
}

static gboolean
cb_outline_level (ColRowInfo *info, int *outline_level)
{
	return (info->outline_level > *outline_level);
}

/**
 * sheet_col_row_fit_gutter:
 * @sheet: Sheet to change for.
 * @outline_level: The new outline level.
 * @is_cols: Column gutter or row gutter?
 * @group: Was the change to @outline_level a result of a group or an ungroup
 *         operation? (important to set this right)
 *
 * Attempts to change the gutter's size to fit @outline_level if possible.
 *
 * Returns : TRUE if the gutter's size was adjusted, FALSE otherwise.
 **/
static gboolean
sheet_col_row_fit_gutter (Sheet *sheet, int outline_level, gboolean is_cols, gboolean group)
{
	int gutter_size = is_cols
		? sheet->cols.max_outline_level
		: sheet->rows.max_outline_level;
	int new_gutter_size = outline_level > 0 ? outline_level + 1 : outline_level;
	gboolean adjust = FALSE;

	/* If the outline_level has been decreased we check all other
	 * outline levels and if possible decrease the indent of the gutter.
	 * If the level has been increased we check if the gutter is currently
	 * big enough to hold this outline_level and if not we increase the
	 * gutter's indent
	 */
	if (!group)
		adjust = !colrow_foreach (is_cols ? &sheet->cols : &sheet->rows,
					  0, (is_cols ? SHEET_MAX_COLS : SHEET_MAX_ROWS) - 1,
					  (ColRowHandler) cb_outline_level, &outline_level);
	else if (group && new_gutter_size > gutter_size)
		adjust = TRUE;

	if (adjust) {
		if (is_cols)
			sheet_col_row_gutter (sheet, new_gutter_size, sheet->rows.max_outline_level);
		else
			sheet_col_row_gutter (sheet, sheet->cols.max_outline_level, new_gutter_size);
	}
	
	return adjust;
}
			  
gboolean
sheet_col_row_group_ungroup (Sheet *sheet, int from, int to, gboolean is_cols,
			     gboolean inc, gboolean is_collapsed)
{
	int i;
	int highest;
	
	g_return_val_if_fail (sheet != NULL, FALSE);
	g_return_val_if_fail (from >= 0, FALSE);
	g_return_val_if_fail (is_cols ? to < SHEET_MAX_COLS : to < SHEET_MAX_ROWS, FALSE);

	/* Can we group/ungroup ? */	
	if (inc != sheet_col_row_can_group (sheet, from, to, is_cols))
		return FALSE;

	/* Set new outline for each col/row and find highest outline level */
	for (highest = -1, i = from; i <= to; i++) {
		ColRowInfo *cri = is_cols
			? sheet_col_fetch (sheet, i)
			: sheet_row_fetch (sheet, i);
		int newlevel = colrow_set_outline (cri, is_cols, inc ? +1 : -1, TRUE, is_collapsed);

		if (newlevel > highest)
			highest = newlevel;
	}

	/* Adjust the gutter's size if possible, make sure we redraw if the gutter
	 * was not changed.
	 */
	if (!sheet_col_row_fit_gutter (sheet, highest, is_cols, inc))
		SHEET_FOREACH_CONTROL (sheet, control, scg_resize (control););

	return TRUE;
1085 1086 1087
}

/**
1088 1089 1090 1091 1092 1093 1094
 * sheet_col_row_gutter :
 *
 * @sheet :
 * @col_max_outline :
 * @row_max_outline :
 *
 * Set the maximum outline levels for the cols and rows.
1095 1096
 */
void
1097 1098 1099
sheet_col_row_gutter (Sheet *sheet,
		      int col_max_outline,
		      int row_max_outline)
1100 1101 1102
{
	g_return_if_fail (IS_SHEET (sheet));

1103 1104
	sheet->cols.max_outline_level = col_max_outline;
	sheet->rows.max_outline_level = row_max_outline;
1105
	SHEET_FOREACH_CONTROL (sheet, control, sc_resize (control););
1106 1107
}

1108 1109
/**
 * sheet_get_extent_cb:
1110
 *
1111 1112 1113
 * checks the cell to see if should be used to calculate sheet extent
 **/
static void
1114
sheet_get_extent_cb (gpointer ignored, gpointer value, gpointer data)
Michael Meeks's avatar
Michael Meeks committed
1115
{
1116
	Cell *cell = (Cell *) value;
1117

1118
	if (!cell_is_blank (cell)) {
Michael Meeks's avatar
Michael Meeks committed
1119
		Range *range = (Range *)data;
1120 1121
		CellSpanInfo const *span = NULL;
		int tmp;
Michael Meeks's avatar
Michael Meeks committed
1122

1123 1124
		if (cell->pos.row < range->start.row)
			range->start.row = cell->pos.row;
Michael Meeks's avatar
Michael Meeks committed
1125

1126 1127
		if (cell->pos.row > range->end.row)
			range->end.row = cell->pos.row;
Michael Meeks's avatar
Michael Meeks committed
1128

1129
		/* FIXME : check for merged cells too */
1130
		span = row_span_get (cell->row_info, cell->pos.col);
Jody Goldberg's avatar
Jody Goldberg committed
1131
		tmp = (span != NULL) ? span->left : cell->pos.col;
1132 1133
		if (tmp < range->start.col)
			range->start.col = tmp;
Michael Meeks's avatar
Michael Meeks committed
1134

Jody Goldberg's avatar
Jody Goldberg committed
1135
		tmp = (span != NULL) ? span->right : cell->pos.col;
1136 1137
		if (tmp > range->end.col)
			range->end.col = tmp;
1138 1139 1140 1141 1142 1143
	}
}

/**
 * sheet_get_extent:
 * @sheet: the sheet
1144
 *
1145
 * calculates the area occupied by cell data.
1146
 *
1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160
 * 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);

1161
	g_hash_table_foreach (sheet->cell_hash, &sheet_get_extent_cb, &r);
1162 1163 1164

	if (r.start.col >= SHEET_MAX_COLS - 2)
		r.start.col = 0;
Michael Meeks's avatar
Michael Meeks committed
1165
	if (r.start.row >= SHEET_MAX_ROWS - 2)
1166 1167 1168 1169 1170 1171 1172 1173 1174
		r.start.row = 0;
	if (r.end.col < 0)
		r.end.col = 0;
	if (r.end.row < 0)
		r.end.row = 0;

	return r;
}

1175
/*
1176
 * Callback for sheet_foreach_cell_in_range to find the maximum width
1177 1178 1179 1180 1181 1182
 * in a range.
 */
static Value *
cb_max_cell_width (Sheet *sheet, int col, int row, Cell *cell,
		   int *max)
{
1183 1184 1185 1186 1187 1188 1189 1190 1191
	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);
1192 1193 1194 1195 1196
	if (width > *max)
		*max = width;
	return NULL;
}

1197
/**
1198
 * sheet_col_size_fit_pixels:
1199 1200 1201
 * @sheet: The sheet
 * @col: the column that we want to query
 *
Jody Goldberg's avatar
Jody Goldberg committed
1202 1203 1204 1205 1206
 * 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.
1207 1208
 */
int
1209
sheet_col_size_fit_pixels (Sheet *sheet, int col)
1210
{
1211
	int max = -1;
Jody Goldberg's avatar
Jody Goldberg committed
1212
	ColRowInfo *ci = sheet_col_get (sheet, col);
1213 1214
	if (ci == NULL)
		return 0;
1215

1216
	sheet_foreach_cell_in_range (sheet, TRUE,
1217 1218
				  col, 0,
				  col, SHEET_MAX_ROWS-1,
1219
				  (ForeachCellCB)&cb_max_cell_width, &max);
1220

1221
	/* Reset to the default width if the column was empty */
Jody Goldberg's avatar
Jody Goldberg committed
1222 1223
	if (max <= 0)
		return 0;
1224

Jody Goldberg's avatar
Jody Goldberg committed
1225 1226
	/* Cell width does not include margins or far grid line*/
	max += ci->margin_a + ci->margin_b + 1;
1227
	return max;
1228
}
1229

1230
/*
1231
 * Callback for sheet_foreach_cell_in_range to find the maximum height
1232 1233 1234 1235 1236 1237
 * in a range.
 */
static Value *
cb_max_cell_height (Sheet *sheet, int col, int row, Cell *cell,
		   int *max)
{
1238 1239 1240 1241 1242
	if (!cell_is_merged (cell)) {
		int const height = cell_rendered_height (cell);
		if (height > *max)
			*max = height;
	}
1243
	return NULL;
1244 1245
}

1246
/**
1247
 * sheet_row_size_fit_pixels:
1248 1249 1250 1251
 * @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
1252 1253
 * properly.
 *
Jody Goldberg's avatar
Jody Goldberg committed
1254 1255
 * Return : Maximum size in pixels INCLUDING margins and grid lines
 *          or 0 if there are no cells.
1256 1257
 */
int
1258
sheet_row_size_fit_pixels (Sheet *sheet, int row)
1259
{
1260
	int max = -1;
1261
	ColRowInfo const *ri = sheet_row_get (sheet, row);
1262 1263
	if (ri == NULL)
		return 0;
1264

1265
	sheet_foreach_cell_in_range (sheet, TRUE,
1266 1267
				  0, row,
				  SHEET_MAX_COLS-1, row,
1268
				  (ForeachCellCB)&cb_max_cell_height, &max);
1269

1270
	/* Reset to the default width if the column was empty */
Jody Goldberg's avatar
Jody Goldberg committed
1271 1272
	if (max <= 0)
		return 0;
1273

1274
	/* Cell height does not include margins or bottom grid line */
Jody Goldberg's avatar
Jody Goldberg committed
1275
	max += ri->margin_a + ri->margin_b + 1;
1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287

	/* 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
1288
	if (ri->size_pixels == (max+1))
1289
		return 0;
1290
	return max;
1291
}
1292

1293 1294 1295 1296 1297
struct recalc_span_closure {
	Sheet *sheet;
	int col;
};

1298
static gboolean
1299
cb_recalc_spans_in_col (ColRowInfo *ri, gpointer user)
1300
{
1301 1302
	struct recalc_span_closure *closure = user;
	int const col = closure->col;
1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315
	int left, right;
	CellSpanInfo const * span = row_span_get (ri, col);