1 /*
2  * sheet.c: Implements the sheet management and per-sheet storage
3  *
4  * Copyright (C) 2000-2007 Jody Goldberg (jody@gnome.org)
5  * Copyright (C) 1997-1999 Miguel de Icaza (miguel@kernel.org)
6  * Copyright (C) 1999-2009 Morten Welinder (terra@gnome.org)
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License as
10  * published by the Free Software Foundation; either version 2 of the
11  * License, or (at your option) version 3.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
21  * USA
22  */
23 #include <gnumeric-config.h>
24 #include <gnumeric.h>
25 #include <sheet.h>
26 
27 #include <sheet-view.h>
28 #include <command-context.h>
29 #include <sheet-control.h>
30 #include <sheet-style.h>
31 #include <sheet-conditions.h>
32 #include <workbook-priv.h>
33 #include <workbook-control.h>
34 #include <workbook-view.h>
35 #include <parse-util.h>
36 #include <dependent.h>
37 #include <value.h>
38 #include <number-match.h>
39 #include <clipboard.h>
40 #include <selection.h>
41 #include <ranges.h>
42 #include <print-info.h>
43 #include <mstyle.h>
44 #include <style-color.h>
45 #include <style-font.h>
46 #include <application.h>
47 #include <commands.h>
48 #include <cellspan.h>
49 #include <cell.h>
50 #include <sheet-merge.h>
51 #include <sheet-private.h>
52 #include <expr-name.h>
53 #include <expr.h>
54 #include <rendered-value.h>
55 #include <gnumeric-conf.h>
56 #include <sheet-object-impl.h>
57 #include <sheet-object-cell-comment.h>
58 #include <tools/gnm-solver.h>
59 #include <hlink.h>
60 #include <sheet-filter.h>
61 #include <sheet-filter-combo.h>
62 #include <gnm-sheet-slicer.h>
63 #include <tools/scenarios.h>
64 #include <cell-draw.h>
65 #include <sort.h>
66 #include <gutils.h>
67 #include <goffice/goffice.h>
68 
69 #include <gnm-i18n.h>
70 #include <gsf/gsf-impl-utils.h>
71 #include <stdlib.h>
72 #include <string.h>
73 
74 static gboolean debug_redraw;
75 
76 static GnmSheetSize *
gnm_sheet_size_copy(GnmSheetSize * size)77 gnm_sheet_size_copy (GnmSheetSize *size)
78 {
79 	GnmSheetSize *res = g_new (GnmSheetSize, 1);
80 	*res = *size;
81 	return res;
82 }
83 
84 GType
gnm_sheet_size_get_type(void)85 gnm_sheet_size_get_type (void)
86 {
87 	static GType t = 0;
88 
89 	if (t == 0) {
90 		t = g_boxed_type_register_static ("GnmSheetSize",
91 			 (GBoxedCopyFunc)gnm_sheet_size_copy,
92 			 (GBoxedFreeFunc)g_free);
93 	}
94 	return t;
95 }
96 
97 enum {
98 	DETACHED_FROM_WORKBOOK,
99 	LAST_SIGNAL
100 };
101 
102 static guint signals[LAST_SIGNAL] = { 0 };
103 
104 typedef struct {
105 	GObjectClass parent;
106 
107 	void (*detached_from_workbook) (Sheet *, Workbook *wb);
108 } GnmSheetClass;
109 typedef Sheet GnmSheet;
110 
111 enum {
112 	PROP_0,
113 	PROP_SHEET_TYPE,
114 	PROP_WORKBOOK,
115 	PROP_NAME,
116 	PROP_RTL,
117 	PROP_VISIBILITY,
118 	PROP_DISPLAY_FORMULAS,
119 	PROP_DISPLAY_ZEROS,
120 	PROP_DISPLAY_GRID,
121 	PROP_DISPLAY_COLUMN_HEADER,
122 	PROP_DISPLAY_ROW_HEADER,
123 	PROP_DISPLAY_OUTLINES,
124 	PROP_DISPLAY_OUTLINES_BELOW,
125 	PROP_DISPLAY_OUTLINES_RIGHT,
126 
127 	PROP_PROTECTED,
128 	PROP_PROTECTED_ALLOW_EDIT_OBJECTS,
129 	PROP_PROTECTED_ALLOW_EDIT_SCENARIOS,
130 	PROP_PROTECTED_ALLOW_CELL_FORMATTING,
131 	PROP_PROTECTED_ALLOW_COLUMN_FORMATTING,
132 	PROP_PROTECTED_ALLOW_ROW_FORMATTING,
133 	PROP_PROTECTED_ALLOW_INSERT_COLUMNS,
134 	PROP_PROTECTED_ALLOW_INSERT_ROWS,
135 	PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS,
136 	PROP_PROTECTED_ALLOW_DELETE_COLUMNS,
137 	PROP_PROTECTED_ALLOW_DELETE_ROWS,
138 	PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS,
139 	PROP_PROTECTED_ALLOW_SORT_RANGES,
140 	PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS,
141 	PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE,
142 	PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS,
143 
144 	PROP_CONVENTIONS,
145 	PROP_USE_R1C1,
146 
147 	PROP_TAB_FOREGROUND,
148 	PROP_TAB_BACKGROUND,
149 	PROP_ZOOM_FACTOR,
150 
151 	PROP_COLUMNS,
152 	PROP_ROWS
153 };
154 
155 static void gnm_sheet_finalize (GObject *obj);
156 
157 static GObjectClass *parent_class;
158 
159 static void
col_row_collection_resize(ColRowCollection * infos,int size)160 col_row_collection_resize (ColRowCollection *infos, int size)
161 {
162 	int end_idx = COLROW_SEGMENT_INDEX (size);
163 	int i = infos->info->len - 1;
164 
165 	while (i >= end_idx) {
166 		ColRowSegment *segment = g_ptr_array_index (infos->info, i);
167 		if (segment) {
168 			g_free (segment);
169 			g_ptr_array_index (infos->info, i) = NULL;
170 		}
171 		i--;
172 	}
173 
174 	g_ptr_array_set_size (infos->info, end_idx);
175 }
176 
177 static void
sheet_set_direction(Sheet * sheet,gboolean text_is_rtl)178 sheet_set_direction (Sheet *sheet, gboolean text_is_rtl)
179 {
180 	GnmRange r;
181 
182 	text_is_rtl = !!text_is_rtl;
183 	if (text_is_rtl == sheet->text_is_rtl)
184 		return;
185 
186 	sheet_mark_dirty (sheet);
187 
188 	sheet->text_is_rtl = text_is_rtl;
189 	sheet->priv->reposition_objects.col = 0;
190 	sheet_range_calc_spans (sheet,
191 				range_init_full_sheet (&r, sheet),
192 				GNM_SPANCALC_RE_RENDER);
193 }
194 
195 static void
sheet_set_visibility(Sheet * sheet,GnmSheetVisibility visibility)196 sheet_set_visibility (Sheet *sheet, GnmSheetVisibility visibility)
197 {
198 	if (sheet->visibility == visibility)
199 		return;
200 
201 	sheet->visibility = visibility;
202 	sheet_mark_dirty (sheet);
203 }
204 
205 static void
cb_re_render_formulas(G_GNUC_UNUSED gpointer unused,GnmCell * cell,G_GNUC_UNUSED gpointer user)206 cb_re_render_formulas (G_GNUC_UNUSED gpointer unused,
207 		       GnmCell *cell,
208 		       G_GNUC_UNUSED gpointer user)
209 {
210 	if (gnm_cell_has_expr (cell)) {
211 		gnm_cell_unrender (cell);
212 		sheet_cell_queue_respan (cell);
213 	}
214 }
215 
216 static void
re_render_formulas(Sheet const * sheet)217 re_render_formulas (Sheet const *sheet)
218 {
219 	sheet_cell_foreach (sheet, (GHFunc)cb_re_render_formulas, NULL);
220 }
221 
222 static void
sheet_set_conventions(Sheet * sheet,GnmConventions const * convs)223 sheet_set_conventions (Sheet *sheet, GnmConventions const *convs)
224 {
225 	if (sheet->convs == convs)
226 		return;
227 	gnm_conventions_unref (sheet->convs);
228 	sheet->convs = gnm_conventions_ref (convs);
229 	if (sheet->display_formulas)
230 		re_render_formulas (sheet);
231 	SHEET_FOREACH_VIEW (sheet, sv,
232 		sv->edit_pos_changed.content = TRUE;);
233 	sheet_mark_dirty (sheet);
234 }
235 
236 GnmConventions const *
sheet_get_conventions(Sheet const * sheet)237 sheet_get_conventions (Sheet const *sheet)
238 {
239 	g_return_val_if_fail (IS_SHEET (sheet), gnm_conventions_default);
240 
241 	return sheet->convs;
242 }
243 
244 static void
cb_sheet_set_hide_zeros(G_GNUC_UNUSED gpointer unused,GnmCell * cell,G_GNUC_UNUSED gpointer user)245 cb_sheet_set_hide_zeros (G_GNUC_UNUSED gpointer unused,
246 			 GnmCell *cell,
247 			 G_GNUC_UNUSED gpointer user)
248 {
249 	if (gnm_cell_is_zero (cell))
250 		gnm_cell_unrender (cell);
251 }
252 
253 static void
sheet_set_hide_zeros(Sheet * sheet,gboolean hide)254 sheet_set_hide_zeros (Sheet *sheet, gboolean hide)
255 {
256 	hide = !!hide;
257 	if (sheet->hide_zero == hide)
258 		return;
259 
260 	sheet->hide_zero = hide;
261 	sheet_mark_dirty (sheet);
262 
263 	sheet_cell_foreach (sheet, (GHFunc)cb_sheet_set_hide_zeros, NULL);
264 }
265 
266 static void
sheet_set_name(Sheet * sheet,char const * new_name)267 sheet_set_name (Sheet *sheet, char const *new_name)
268 {
269 	Workbook *wb = sheet->workbook;
270 	gboolean attached;
271 	Sheet *sucker;
272 	char *new_name_unquoted;
273 
274 	g_return_if_fail (new_name != NULL);
275 
276 	/* No change whatsoever.  */
277 	if (go_str_compare (sheet->name_unquoted, new_name) == 0)
278 		return;
279 
280 	/* Mark the sheet dirty unless this is the initial name.  */
281 	if (sheet->name_unquoted)
282 		sheet_mark_dirty (sheet);
283 
284 	sucker = wb ? workbook_sheet_by_name (wb, new_name) : NULL;
285 	if (sucker && sucker != sheet) {
286 		/*
287 		 * Prevent a name clash.  With this you can swap names by
288 		 * setting just the two names.
289 		 */
290 		char *sucker_name = workbook_sheet_get_free_name (wb, new_name, TRUE, FALSE);
291 #if 0
292 		g_warning ("Renaming %s to %s to avoid clash.\n", sucker->name_unquoted, sucker_name);
293 #endif
294 		g_object_set (sucker, "name", sucker_name, NULL);
295 		g_free (sucker_name);
296 	}
297 
298 	attached = wb != NULL &&
299 		sheet->index_in_wb != -1 &&
300 		sheet->name_case_insensitive;
301 
302 	/* FIXME: maybe have workbook_sheet_detach_internal for this.  */
303 	if (attached)
304 		g_hash_table_remove (wb->sheet_hash_private,
305 				     sheet->name_case_insensitive);
306 
307 	/* Copy before free.  */
308 	new_name_unquoted = g_strdup (new_name);
309 
310 	g_free (sheet->name_unquoted);
311 	g_free (sheet->name_quoted);
312 	g_free (sheet->name_unquoted_collate_key);
313 	g_free (sheet->name_case_insensitive);
314 	sheet->name_unquoted = new_name_unquoted;
315 	sheet->name_quoted = g_string_free
316 		(gnm_expr_conv_quote (sheet->convs, new_name_unquoted),
317 		 FALSE);
318 	sheet->name_unquoted_collate_key =
319 		g_utf8_collate_key (new_name_unquoted, -1);
320 	sheet->name_case_insensitive =
321 		g_utf8_casefold (new_name_unquoted, -1);
322 
323 	/* FIXME: maybe have workbook_sheet_attach_internal for this.  */
324 	if (attached)
325 		g_hash_table_insert (wb->sheet_hash_private,
326 				     sheet->name_case_insensitive,
327 				     sheet);
328 
329 	if (!sheet->being_constructed &&
330 	    sheet->sheet_type == GNM_SHEET_DATA) {
331 		/* We have to fix the Sheet_Title name */
332 		GnmNamedExpr *nexpr;
333 		GnmParsePos pp;
334 
335 		parse_pos_init_sheet (&pp, sheet);
336 		nexpr = expr_name_lookup (&pp, "Sheet_Title");
337 		if (nexpr) {
338 			GnmExprTop const *texpr =
339 				gnm_expr_top_new_constant
340 				(value_new_string (sheet->name_unquoted));
341 			expr_name_set_expr (nexpr, texpr);
342 		}
343 	}
344 }
345 
346 struct resize_colrow {
347 	Sheet *sheet;
348 	gboolean is_cols;
349 	double scale;
350 };
351 
352 static gboolean
cb_colrow_compute_pixels_from_pts(GnmColRowIter const * iter,gpointer data_)353 cb_colrow_compute_pixels_from_pts (GnmColRowIter const *iter,
354 				   gpointer data_)
355 {
356 	struct resize_colrow *data = data_;
357 	colrow_compute_pixels_from_pts ((ColRowInfo *)iter->cri,
358 					data->sheet, data->is_cols,
359 					data->scale);
360 	return FALSE;
361 }
362 
363 static void
cb_clear_rendered_cells(G_GNUC_UNUSED gpointer ignored,GnmCell * cell)364 cb_clear_rendered_cells (G_GNUC_UNUSED gpointer ignored, GnmCell *cell)
365 {
366 	if (gnm_cell_get_rendered_value (cell) != NULL) {
367 		sheet_cell_queue_respan (cell);
368 		gnm_cell_unrender (cell);
369 	}
370 }
371 
372 /**
373  * sheet_range_unrender:
374  * @sheet: sheet to change
375  * @r: (nullable): range to unrender
376  *
377  * Unrenders all cells in the given range.  If @r is %NULL, the all cells
378  * in the sheet are unrendered.
379  */
380 void
sheet_range_unrender(Sheet * sheet,GnmRange const * r)381 sheet_range_unrender (Sheet *sheet, GnmRange const *r)
382 {
383 	GPtrArray *cells = sheet_cells (sheet, r);
384 	unsigned ui;
385 
386 	for (ui = 0; ui < cells->len; ui++) {
387 		GnmCell *cell = g_ptr_array_index (cells, ui);
388 		gnm_cell_unrender (cell);
389 	}
390 
391 	g_ptr_array_unref (cells);
392 }
393 
394 
395 static void
sheet_scale_changed(Sheet * sheet,gboolean cols_rescaled,gboolean rows_rescaled)396 sheet_scale_changed (Sheet *sheet, gboolean cols_rescaled, gboolean rows_rescaled)
397 {
398 	g_return_if_fail (cols_rescaled || rows_rescaled);
399 
400 	/* Then every column and row */
401 	if (cols_rescaled) {
402 		struct resize_colrow closure;
403 
404 		closure.sheet = sheet;
405 		closure.is_cols = TRUE;
406 		closure.scale = colrow_compute_pixel_scale (sheet, TRUE);
407 
408 		colrow_compute_pixels_from_pts (&sheet->cols.default_style,
409 						sheet, TRUE, closure.scale);
410 		sheet_colrow_foreach (sheet, TRUE, 0, -1,
411 				      cb_colrow_compute_pixels_from_pts,
412 				      &closure);
413 	}
414 	if (rows_rescaled) {
415 		struct resize_colrow closure;
416 
417 		closure.sheet = sheet;
418 		closure.is_cols = FALSE;
419 		closure.scale = colrow_compute_pixel_scale (sheet, FALSE);
420 
421 		colrow_compute_pixels_from_pts (&sheet->rows.default_style,
422 						sheet, FALSE, closure.scale);
423 		sheet_colrow_foreach (sheet, FALSE, 0, -1,
424 				      cb_colrow_compute_pixels_from_pts,
425 				      &closure);
426 	}
427 
428 	sheet_cell_foreach (sheet, (GHFunc)&cb_clear_rendered_cells, NULL);
429 	SHEET_FOREACH_CONTROL (sheet, view, control, sc_scale_changed (control););
430 }
431 
432 static void
sheet_set_display_formulas(Sheet * sheet,gboolean display)433 sheet_set_display_formulas (Sheet *sheet, gboolean display)
434 {
435 	display = !!display;
436 	if (sheet->display_formulas == display)
437 		return;
438 
439 	sheet->display_formulas = display;
440 	sheet_mark_dirty (sheet);
441 	if (!sheet->being_constructed)
442 		sheet_scale_changed (sheet, TRUE, FALSE);
443 }
444 
445 static void
sheet_set_zoom_factor(Sheet * sheet,double factor)446 sheet_set_zoom_factor (Sheet *sheet, double factor)
447 {
448 	if (fabs (factor - sheet->last_zoom_factor_used) < 1e-6)
449 		return;
450 	sheet->last_zoom_factor_used = factor;
451 	if (!sheet->being_constructed)
452 		sheet_scale_changed (sheet, TRUE, TRUE);
453 }
454 
455 static void
gnm_sheet_set_property(GObject * object,guint property_id,GValue const * value,GParamSpec * pspec)456 gnm_sheet_set_property (GObject *object, guint property_id,
457 			GValue const *value, GParamSpec *pspec)
458 {
459 	Sheet *sheet = (Sheet *)object;
460 
461 	switch (property_id) {
462 	case PROP_SHEET_TYPE:
463 		/* Construction-time only */
464 		sheet->sheet_type = g_value_get_enum (value);
465 		break;
466 	case PROP_WORKBOOK:
467 		/* Construction-time only */
468 		sheet->workbook = g_value_get_object (value);
469 		break;
470 	case PROP_NAME:
471 		sheet_set_name (sheet, g_value_get_string (value));
472 		break;
473 	case PROP_RTL:
474 		sheet_set_direction (sheet, g_value_get_boolean (value));
475 		break;
476 	case PROP_VISIBILITY:
477 		sheet_set_visibility (sheet, g_value_get_enum (value));
478 		break;
479 	case PROP_DISPLAY_FORMULAS:
480 		sheet_set_display_formulas (sheet, g_value_get_boolean (value));
481 		break;
482 	case PROP_DISPLAY_ZEROS:
483 		sheet_set_hide_zeros (sheet, !g_value_get_boolean (value));
484 		break;
485 	case PROP_DISPLAY_GRID:
486 		sheet->hide_grid = !g_value_get_boolean (value);
487 		break;
488 	case PROP_DISPLAY_COLUMN_HEADER:
489 		sheet->hide_col_header = !g_value_get_boolean (value);
490 		break;
491 	case PROP_DISPLAY_ROW_HEADER:
492 		sheet->hide_row_header = !g_value_get_boolean (value);
493 		break;
494 	case PROP_DISPLAY_OUTLINES:
495 		sheet->display_outlines = !!g_value_get_boolean (value);
496 		break;
497 	case PROP_DISPLAY_OUTLINES_BELOW:
498 		sheet->outline_symbols_below = !!g_value_get_boolean (value);
499 		break;
500 	case PROP_DISPLAY_OUTLINES_RIGHT:
501 		sheet->outline_symbols_right = !!g_value_get_boolean (value);
502 		break;
503 
504 	case PROP_PROTECTED:
505 		sheet->is_protected = !!g_value_get_boolean (value);
506 		break;
507 	case PROP_PROTECTED_ALLOW_EDIT_OBJECTS:
508 		sheet->protected_allow.edit_objects = !!g_value_get_boolean (value);
509 		break;
510 	case PROP_PROTECTED_ALLOW_EDIT_SCENARIOS:
511 		sheet->protected_allow.edit_scenarios = !!g_value_get_boolean (value);
512 		break;
513 	case PROP_PROTECTED_ALLOW_CELL_FORMATTING:
514 		sheet->protected_allow.cell_formatting = !!g_value_get_boolean (value);
515 		break;
516 	case PROP_PROTECTED_ALLOW_COLUMN_FORMATTING:
517 		sheet->protected_allow.column_formatting = !!g_value_get_boolean (value);
518 		break;
519 	case PROP_PROTECTED_ALLOW_ROW_FORMATTING:
520 		sheet->protected_allow.row_formatting = !!g_value_get_boolean (value);
521 		break;
522 	case PROP_PROTECTED_ALLOW_INSERT_COLUMNS:
523 		sheet->protected_allow.insert_columns = !!g_value_get_boolean (value);
524 		break;
525 	case PROP_PROTECTED_ALLOW_INSERT_ROWS:
526 		sheet->protected_allow.insert_rows = !!g_value_get_boolean (value);
527 		break;
528 	case PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS:
529 		sheet->protected_allow.insert_hyperlinks = !!g_value_get_boolean (value);
530 		break;
531 	case PROP_PROTECTED_ALLOW_DELETE_COLUMNS:
532 		sheet->protected_allow.delete_columns = !!g_value_get_boolean (value);
533 		break;
534 	case PROP_PROTECTED_ALLOW_DELETE_ROWS:
535 		sheet->protected_allow.delete_rows = !!g_value_get_boolean (value);
536 		break;
537 	case PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS:
538 		sheet->protected_allow.select_locked_cells = !!g_value_get_boolean (value);
539 		break;
540 	case PROP_PROTECTED_ALLOW_SORT_RANGES:
541 		sheet->protected_allow.sort_ranges = !!g_value_get_boolean (value);
542 		break;
543 	case PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS:
544 		sheet->protected_allow.edit_auto_filters = !!g_value_get_boolean (value);
545 		break;
546 	case PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE:
547 		sheet->protected_allow.edit_pivottable = !!g_value_get_boolean (value);
548 		break;
549 	case PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS:
550 		sheet->protected_allow.select_unlocked_cells = !!g_value_get_boolean (value);
551 		break;
552 
553 	case PROP_CONVENTIONS:
554 		sheet_set_conventions (sheet, g_value_get_boxed (value));
555 		break;
556 	case PROP_USE_R1C1: /* convenience api */
557 		sheet_set_conventions (sheet, !!g_value_get_boolean (value)
558 			? gnm_conventions_xls_r1c1 : gnm_conventions_default);
559 		break;
560 
561 	case PROP_TAB_FOREGROUND: {
562 		GnmColor *color = g_value_dup_boxed (value);
563 		style_color_unref (sheet->tab_text_color);
564 		sheet->tab_text_color = color;
565 		sheet_mark_dirty (sheet);
566 		break;
567 	}
568 	case PROP_TAB_BACKGROUND: {
569 		GnmColor *color = g_value_dup_boxed (value);
570 		style_color_unref (sheet->tab_color);
571 		sheet->tab_color = color;
572 		sheet_mark_dirty (sheet);
573 		break;
574 	}
575 	case PROP_ZOOM_FACTOR:
576 		sheet_set_zoom_factor (sheet, g_value_get_double (value));
577 		break;
578 	case PROP_COLUMNS:
579 		/* Construction-time only */
580 		sheet->size.max_cols = g_value_get_int (value);
581 		break;
582 	case PROP_ROWS:
583 		/* Construction-time only */
584 		sheet->size.max_rows = g_value_get_int (value);
585 		break;
586 	default:
587 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
588 		break;
589 	}
590 }
591 
592 static void
gnm_sheet_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)593 gnm_sheet_get_property (GObject *object, guint property_id,
594 			GValue *value, GParamSpec *pspec)
595 {
596 	Sheet *sheet = (Sheet *)object;
597 
598 	switch (property_id) {
599 	case PROP_SHEET_TYPE:
600 		g_value_set_enum (value, sheet->sheet_type);
601 		break;
602 	case PROP_WORKBOOK:
603 		g_value_set_object (value, sheet->workbook);
604 		break;
605 	case PROP_NAME:
606 		g_value_set_string (value, sheet->name_unquoted);
607 		break;
608 	case PROP_RTL:
609 		g_value_set_boolean (value, sheet->text_is_rtl);
610 		break;
611 	case PROP_VISIBILITY:
612 		g_value_set_enum (value, sheet->visibility);
613 		break;
614 	case PROP_DISPLAY_FORMULAS:
615 		g_value_set_boolean (value, sheet->display_formulas);
616 		break;
617 	case PROP_DISPLAY_ZEROS:
618 		g_value_set_boolean (value, !sheet->hide_zero);
619 		break;
620 	case PROP_DISPLAY_GRID:
621 		g_value_set_boolean (value, !sheet->hide_grid);
622 		break;
623 	case PROP_DISPLAY_COLUMN_HEADER:
624 		g_value_set_boolean (value, !sheet->hide_col_header);
625 		break;
626 	case PROP_DISPLAY_ROW_HEADER:
627 		g_value_set_boolean (value, !sheet->hide_row_header);
628 		break;
629 	case PROP_DISPLAY_OUTLINES:
630 		g_value_set_boolean (value, sheet->display_outlines);
631 		break;
632 	case PROP_DISPLAY_OUTLINES_BELOW:
633 		g_value_set_boolean (value, sheet->outline_symbols_below);
634 		break;
635 	case PROP_DISPLAY_OUTLINES_RIGHT:
636 		g_value_set_boolean (value, sheet->outline_symbols_right);
637 		break;
638 
639 	case PROP_PROTECTED:
640 		g_value_set_boolean (value, sheet->is_protected);
641 		break;
642 	case PROP_PROTECTED_ALLOW_EDIT_OBJECTS:
643 		g_value_set_boolean (value, sheet->protected_allow.edit_objects);
644 		break;
645 	case PROP_PROTECTED_ALLOW_EDIT_SCENARIOS:
646 		g_value_set_boolean (value, sheet->protected_allow.edit_scenarios);
647 		break;
648 	case PROP_PROTECTED_ALLOW_CELL_FORMATTING:
649 		g_value_set_boolean (value, sheet->protected_allow.cell_formatting);
650 		break;
651 	case PROP_PROTECTED_ALLOW_COLUMN_FORMATTING:
652 		g_value_set_boolean (value, sheet->protected_allow.column_formatting);
653 		break;
654 	case PROP_PROTECTED_ALLOW_ROW_FORMATTING:
655 		g_value_set_boolean (value, sheet->protected_allow.row_formatting);
656 		break;
657 	case PROP_PROTECTED_ALLOW_INSERT_COLUMNS:
658 		g_value_set_boolean (value, sheet->protected_allow.insert_columns);
659 		break;
660 	case PROP_PROTECTED_ALLOW_INSERT_ROWS:
661 		g_value_set_boolean (value, sheet->protected_allow.insert_rows);
662 		break;
663 	case PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS:
664 		g_value_set_boolean (value, sheet->protected_allow.insert_hyperlinks);
665 		break;
666 	case PROP_PROTECTED_ALLOW_DELETE_COLUMNS:
667 		g_value_set_boolean (value, sheet->protected_allow.delete_columns);
668 		break;
669 	case PROP_PROTECTED_ALLOW_DELETE_ROWS:
670 		g_value_set_boolean (value, sheet->protected_allow.delete_rows);
671 		break;
672 	case PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS:
673 		g_value_set_boolean (value, sheet->protected_allow.select_locked_cells);
674 		break;
675 	case PROP_PROTECTED_ALLOW_SORT_RANGES:
676 		g_value_set_boolean (value, sheet->protected_allow.sort_ranges);
677 		break;
678 	case PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS:
679 		g_value_set_boolean (value, sheet->protected_allow.edit_auto_filters);
680 		break;
681 	case PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE:
682 		g_value_set_boolean (value, sheet->protected_allow.edit_pivottable);
683 		break;
684 	case PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS:
685 		g_value_set_boolean (value, sheet->protected_allow.select_unlocked_cells);
686 		break;
687 
688 	case PROP_CONVENTIONS:
689 		g_value_set_boxed (value, sheet->convs);
690 		break;
691 	case PROP_USE_R1C1: /* convenience api */
692 		g_value_set_boolean (value, sheet->convs->r1c1_addresses);
693 		break;
694 
695 	case PROP_TAB_FOREGROUND:
696 		g_value_set_boxed (value, sheet->tab_text_color);
697 		break;
698 	case PROP_TAB_BACKGROUND:
699 		g_value_set_boxed (value, sheet->tab_color);
700 		break;
701 	case PROP_ZOOM_FACTOR:
702 		g_value_set_double (value, sheet->last_zoom_factor_used);
703 		break;
704 	case PROP_COLUMNS:
705 		g_value_set_int (value, sheet->size.max_cols);
706 		break;
707 	case PROP_ROWS:
708 		g_value_set_int (value, sheet->size.max_rows);
709 		break;
710 	default:
711 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
712 		break;
713 	}
714 }
715 
716 static void
gnm_sheet_constructed(GObject * obj)717 gnm_sheet_constructed (GObject *obj)
718 {
719 	Sheet *sheet = SHEET (obj);
720 	int ht;
721 	GnmStyle *style;
722 
723 	if (parent_class->constructed)
724 		parent_class->constructed (obj);
725 
726 	/* Now sheet_type, max_cols, and max_rows have been set.  */
727 	sheet->being_constructed = FALSE;
728 
729 	col_row_collection_resize (&sheet->cols, sheet->size.max_cols);
730 	col_row_collection_resize (&sheet->rows, sheet->size.max_rows);
731 
732 	sheet->priv->reposition_objects.col = sheet->size.max_cols;
733 	sheet->priv->reposition_objects.row = sheet->size.max_rows;
734 
735 	range_init_full_sheet (&sheet->priv->unhidden_region, sheet);
736 	sheet_style_init (sheet);
737 
738 	sheet_conditions_init (sheet);
739 
740 	sheet->deps = gnm_dep_container_new (sheet);
741 
742 	switch (sheet->sheet_type) {
743 	case GNM_SHEET_XLM:
744 		sheet->display_formulas = TRUE;
745 		break;
746 	case GNM_SHEET_OBJECT:
747 		sheet->hide_grid = TRUE;
748 		sheet->hide_col_header = sheet->hide_row_header = TRUE;
749 		colrow_compute_pixels_from_pts (&sheet->rows.default_style,
750 						sheet, FALSE, -1);
751 		colrow_compute_pixels_from_pts (&sheet->cols.default_style,
752 						sheet, TRUE, -1);
753 		break;
754 	case GNM_SHEET_DATA: {
755 		/* We have to add permanent names */
756 		GnmExprTop const *texpr;
757 
758 		if (sheet->name_unquoted)
759 			texpr =	gnm_expr_top_new_constant
760 				(value_new_string (sheet->name_unquoted));
761 		else
762 			texpr = gnm_expr_top_new_constant
763 				(value_new_error_REF (NULL));
764 		expr_name_perm_add (sheet, "Sheet_Title",
765 				    texpr, FALSE);
766 
767 		texpr = gnm_expr_top_new_constant
768 				(value_new_error_REF (NULL));
769 		expr_name_perm_add (sheet, "Print_Area",
770 				    texpr, FALSE);
771 		break;
772 	}
773 	default:
774 		g_assert_not_reached ();
775 	}
776 
777 	style = sheet_style_default (sheet);
778 	ht = gnm_style_get_pango_height (style,
779 					 sheet->rendered_values->context, 1);
780 	gnm_style_unref (style);
781 	ht += GNM_ROW_MARGIN + GNM_ROW_MARGIN + 1;
782 	if (ht > sheet_row_get_default_size_pixels (sheet)) {
783 		sheet_row_set_default_size_pixels (sheet, ht);
784 	}
785 
786 	sheet_scale_changed (sheet, TRUE, TRUE);
787 }
788 
789 static guint
cell_set_hash(GnmCell const * key)790 cell_set_hash (GnmCell const *key)
791 {
792 	guint32 r = key->pos.row;
793 	guint32 c = key->pos.col;
794 	guint32 h;
795 
796 	h = r;
797 	h *= (guint32)123456789;
798 	h ^= c;
799 	h *= (guint32)123456789;
800 
801 	return h;
802 }
803 
804 static gint
cell_set_equal(GnmCell const * a,GnmCell const * b)805 cell_set_equal (GnmCell const *a, GnmCell const *b)
806 {
807 	return (a->pos.row == b->pos.row && a->pos.col == b->pos.col);
808 }
809 
810 static void
gnm_sheet_init(Sheet * sheet)811 gnm_sheet_init (Sheet *sheet)
812 {
813 	PangoContext *context;
814 
815 	sheet->priv = g_new0 (SheetPrivate, 1);
816 	sheet->being_constructed = TRUE;
817 
818 	sheet->sheet_views = g_ptr_array_new ();
819 
820 	/* Init, focus, and load handle setting these if/when necessary */
821 	sheet->priv->recompute_visibility = TRUE;
822 	sheet->priv->recompute_spans = TRUE;
823 
824 	sheet->is_protected = FALSE;
825 	sheet->protected_allow.edit_scenarios		= FALSE;
826 	sheet->protected_allow.cell_formatting		= FALSE;
827 	sheet->protected_allow.column_formatting	= FALSE;
828 	sheet->protected_allow.row_formatting		= FALSE;
829 	sheet->protected_allow.insert_columns		= FALSE;
830 	sheet->protected_allow.insert_rows		= FALSE;
831 	sheet->protected_allow.insert_hyperlinks	= FALSE;
832 	sheet->protected_allow.delete_columns		= FALSE;
833 	sheet->protected_allow.delete_rows		= FALSE;
834 	sheet->protected_allow.select_locked_cells	= TRUE;
835 	sheet->protected_allow.sort_ranges		= FALSE;
836 	sheet->protected_allow.edit_auto_filters	= FALSE;
837 	sheet->protected_allow.edit_pivottable		= FALSE;
838 	sheet->protected_allow.select_unlocked_cells	= TRUE;
839 
840 	sheet->hide_zero = FALSE;
841 	sheet->display_outlines = TRUE;
842 	sheet->outline_symbols_below = TRUE;
843 	sheet->outline_symbols_right = TRUE;
844 	sheet->tab_color = NULL;
845 	sheet->tab_text_color = NULL;
846 	sheet->visibility = GNM_SHEET_VISIBILITY_VISIBLE;
847 #ifdef GNM_WITH_GTK
848 	sheet->text_is_rtl = (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL);
849 #else
850 	sheet->text_is_rtl = FALSE;
851 #endif
852 
853 	sheet->sheet_objects = NULL;
854 	sheet->max_object_extent.col = sheet->max_object_extent.row = 0;
855 
856 	sheet->solver_parameters = gnm_solver_param_new (sheet);
857 
858 	sheet->cols.max_used = -1;
859 	sheet->cols.info = g_ptr_array_new ();
860 	sheet_col_set_default_size_pts (sheet, 48);
861 
862 	sheet->rows.max_used = -1;
863 	sheet->rows.info = g_ptr_array_new ();
864 	// 12.75 might be overwritten later
865 	sheet_row_set_default_size_pts (sheet, 12.75);
866 
867 	sheet->print_info = gnm_print_information_new (FALSE);
868 
869 	sheet->filters = NULL;
870 	sheet->scenarios = NULL;
871 	sheet->sort_setups = NULL;
872 	sheet->list_merged = NULL;
873 	sheet->hash_merged = g_hash_table_new ((GHashFunc)&gnm_cellpos_hash,
874 					       (GCompareFunc)&gnm_cellpos_equal);
875 
876 	sheet->cell_hash = g_hash_table_new ((GHashFunc)&cell_set_hash,
877 					     (GCompareFunc)&cell_set_equal);
878 
879 	/* Init preferences */
880 	sheet->convs = gnm_conventions_ref (gnm_conventions_default);
881 
882 	/* FIXME: probably not here.  */
883 	/* See also gtk_widget_create_pango_context ().  */
884 	sheet->last_zoom_factor_used = -1;  /* Overridden later */
885 	context = gnm_pango_context_get ();
886 	sheet->rendered_values = gnm_rvc_new (context, 5000);
887 	g_object_unref (context);
888 
889 	/* Init menu states */
890 	sheet->priv->enable_showhide_detail = TRUE;
891 
892 	sheet->names = gnm_named_expr_collection_new ();
893 	sheet->style_data = NULL;
894 
895 	sheet->index_in_wb = -1;
896 
897 	sheet->pending_redraw = g_array_new (FALSE, FALSE, sizeof (GnmRange));
898 	sheet->pending_redraw_src = 0;
899 	debug_redraw = gnm_debug_flag ("redraw-ranges");
900 }
901 
902 static Sheet the_invalid_sheet;
903 Sheet *invalid_sheet = &the_invalid_sheet;
904 
905 static void
gnm_sheet_class_init(GObjectClass * gobject_class)906 gnm_sheet_class_init (GObjectClass *gobject_class)
907 {
908 	if (GNM_MAX_COLS > 364238) {
909 		/* Oh, yeah?  */
910 		g_warning (_("This is a special version of Gnumeric.  It has been compiled\n"
911 			     "with support for a very large number of columns.  Access to the\n"
912 			     "column named TRUE may conflict with the constant of the same\n"
913 			     "name.  Expect weirdness."));
914 	}
915 
916 	parent_class = g_type_class_peek_parent (gobject_class);
917 
918 	gobject_class->set_property	= gnm_sheet_set_property;
919 	gobject_class->get_property	= gnm_sheet_get_property;
920 	gobject_class->finalize         = gnm_sheet_finalize;
921 	gobject_class->constructed      = gnm_sheet_constructed;
922 
923         g_object_class_install_property (gobject_class, PROP_SHEET_TYPE,
924 		 g_param_spec_enum ("sheet-type",
925 				    P_("Sheet Type"),
926 				    P_("Which type of sheet this is."),
927 				    GNM_SHEET_TYPE_TYPE,
928 				    GNM_SHEET_DATA,
929 				    GSF_PARAM_STATIC |
930 				    G_PARAM_READWRITE |
931 				    G_PARAM_CONSTRUCT_ONLY));
932 	g_object_class_install_property (gobject_class, PROP_WORKBOOK,
933 		g_param_spec_object ("workbook",
934 				     P_("Parent workbook"),
935 				     P_("The workbook in which this sheet lives"),
936 				     GNM_WORKBOOK_TYPE,
937 				     GSF_PARAM_STATIC |
938 				     G_PARAM_READWRITE |
939 				     G_PARAM_CONSTRUCT_ONLY));
940         g_object_class_install_property (gobject_class, PROP_NAME,
941 		 g_param_spec_string ("name",
942 				      P_("Name"),
943 				      P_("The name of the sheet."),
944 				      NULL,
945 				      GSF_PARAM_STATIC |
946 				      G_PARAM_READWRITE));
947         g_object_class_install_property (gobject_class, PROP_RTL,
948 		 g_param_spec_boolean ("text-is-rtl",
949 				       P_("text-is-rtl"),
950 				       P_("Text goes from right to left."),
951 				       FALSE,
952 				       GSF_PARAM_STATIC |
953 				       G_PARAM_READWRITE));
954         g_object_class_install_property (gobject_class, PROP_VISIBILITY,
955 		 g_param_spec_enum ("visibility",
956 				    P_("Visibility"),
957 				    P_("How visible the sheet is."),
958 				    GNM_SHEET_VISIBILITY_TYPE,
959 				    GNM_SHEET_VISIBILITY_VISIBLE,
960 				    GSF_PARAM_STATIC |
961 				    G_PARAM_READWRITE));
962 	g_object_class_install_property (gobject_class, PROP_DISPLAY_FORMULAS,
963 		 g_param_spec_boolean ("display-formulas",
964 				       P_("Display Formul\303\246"),
965 				       P_("Control whether formul\303\246 are shown instead of values."),
966 				       FALSE,
967 				       GSF_PARAM_STATIC |
968 				       G_PARAM_READWRITE));
969 	g_object_class_install_property (gobject_class, PROP_DISPLAY_ZEROS,
970 		 g_param_spec_boolean ("display-zeros", _("Display Zeros"),
971 				       _("Control whether zeros are shown are blanked out."),
972 				       TRUE,
973 				       GSF_PARAM_STATIC |
974 				       G_PARAM_READWRITE));
975 	g_object_class_install_property (gobject_class, PROP_DISPLAY_GRID,
976 		 g_param_spec_boolean ("display-grid", _("Display Grid"),
977 				       _("Control whether the grid is shown."),
978 				       TRUE,
979 				       GSF_PARAM_STATIC |
980 				       G_PARAM_READWRITE));
981 	g_object_class_install_property (gobject_class, PROP_DISPLAY_COLUMN_HEADER,
982 		 g_param_spec_boolean ("display-column-header",
983 				       P_("Display Column Headers"),
984 				       P_("Control whether column headers are shown."),
985 				       FALSE,
986 				       GSF_PARAM_STATIC |
987 				       G_PARAM_READWRITE));
988 	g_object_class_install_property (gobject_class, PROP_DISPLAY_ROW_HEADER,
989 		 g_param_spec_boolean ("display-row-header",
990 				       P_("Display Row Headers"),
991 				       P_("Control whether row headers are shown."),
992 				       FALSE,
993 				       GSF_PARAM_STATIC |
994 				       G_PARAM_READWRITE));
995 	g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES,
996 		 g_param_spec_boolean ("display-outlines",
997 				       P_("Display Outlines"),
998 				       P_("Control whether outlines are shown."),
999 				       TRUE,
1000 				       GSF_PARAM_STATIC |
1001 				       G_PARAM_READWRITE));
1002 	g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES_BELOW,
1003 		 g_param_spec_boolean ("display-outlines-below",
1004 				       P_("Display Outlines Below"),
1005 				       P_("Control whether outline symbols are shown below."),
1006 				       TRUE,
1007 				       GSF_PARAM_STATIC |
1008 				       G_PARAM_READWRITE));
1009 	g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES_RIGHT,
1010 		 g_param_spec_boolean ("display-outlines-right",
1011 				       P_("Display Outlines Right"),
1012 				       P_("Control whether outline symbols are shown to the right."),
1013 				       TRUE,
1014 				       GSF_PARAM_STATIC |
1015 				       G_PARAM_READWRITE));
1016 
1017         g_object_class_install_property (gobject_class, PROP_PROTECTED,
1018 		 g_param_spec_boolean ("protected",
1019 				       P_("Protected"),
1020 				       P_("Sheet is protected."),
1021 				       FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1022 	g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_OBJECTS,
1023 		g_param_spec_boolean ("protected-allow-edit-objects",
1024 				      P_("Protected Allow Edit objects"),
1025 				      P_("Allow objects to be edited while a sheet is protected"),
1026 				      FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1027 	g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_SCENARIOS,
1028 		g_param_spec_boolean ("protected-allow-edit-scenarios",
1029 				      P_("Protected allow edit scenarios"),
1030 				      P_("Allow scenarios to be edited while a sheet is protected"),
1031 				      FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1032 	g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_CELL_FORMATTING,
1033 		g_param_spec_boolean ("protected-allow-cell-formatting",
1034 				      P_("Protected allow cell formatting"),
1035 				      P_("Allow cell format changes while a sheet is protected"),
1036 				      FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1037 	g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_COLUMN_FORMATTING,
1038 		g_param_spec_boolean ("protected-allow-column-formatting",
1039 				      P_("Protected allow column formatting"),
1040 				      P_("Allow column formatting while a sheet is protected"),
1041 				      FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1042 	g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_ROW_FORMATTING,
1043 		g_param_spec_boolean ("protected-allow-row-formatting",
1044 				      P_("Protected allow row formatting"),
1045 				      P_("Allow row formatting while a sheet is protected"),
1046 				      FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1047 	g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_COLUMNS,
1048 		g_param_spec_boolean ("protected-allow-insert-columns",
1049 				      P_("Protected allow insert columns"),
1050 				      P_("Allow columns to be inserted while a sheet is protected"),
1051 				      FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1052 	g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_ROWS,
1053 		g_param_spec_boolean ("protected-allow-insert-rows",
1054 				      P_("Protected allow insert rows"),
1055 				      P_("Allow rows to be inserted while a sheet is protected"),
1056 				      FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1057 	g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS,
1058 		g_param_spec_boolean ("protected-allow-insert-hyperlinks",
1059 				      P_("Protected allow insert hyperlinks"),
1060 				      P_("Allow hyperlinks to be inserted while a sheet is protected"),
1061 				      FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1062 	g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_DELETE_COLUMNS,
1063 		g_param_spec_boolean ("protected-allow-delete-columns",
1064 				      P_("Protected allow delete columns"),
1065 				      P_("Allow columns to be deleted while a sheet is protected"),
1066 				      FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1067 	g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_DELETE_ROWS,
1068 		g_param_spec_boolean ("protected-allow-delete-rows",
1069 				      P_("Protected allow delete rows"),
1070 				      P_("Allow rows to be deleted while a sheet is protected"),
1071 				      FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1072 	g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS,
1073 		g_param_spec_boolean ("protected-allow-select-locked-cells",
1074 				      P_("Protected allow select locked cells"),
1075 				      P_("Allow the user to select locked cells while a sheet is protected"),
1076 				      TRUE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1077 	g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SORT_RANGES,
1078 		g_param_spec_boolean ("protected-allow-sort-ranges",
1079 				      P_("Protected allow sort ranges"),
1080 				      P_("Allow ranges to be sorted while a sheet is protected"),
1081 				      FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1082 	g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS,
1083 		g_param_spec_boolean ("protected-allow-edit-auto-filters",
1084 				      P_("Protected allow edit auto filters"),
1085 				      P_("Allow auto filters to be edited while a sheet is protected"),
1086 				      FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1087 	g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE,
1088 		g_param_spec_boolean ("protected-allow-edit-pivottable",
1089 				      P_("Protected allow edit pivottable"),
1090 				      P_("Allow pivottable to be edited while a sheet is protected"),
1091 				      FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1092 	g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS,
1093 		g_param_spec_boolean ("protected-allow-select-unlocked-cells",
1094 				      P_("Protected allow select unlocked cells"),
1095 				      P_("Allow the user to select unlocked cells while a sheet is protected"),
1096 				      TRUE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1097 
1098 	g_object_class_install_property
1099 		(gobject_class, PROP_CONVENTIONS,
1100 		 g_param_spec_boxed ("conventions",
1101 				     P_("Display convention for expressions (default Gnumeric A1)"),
1102 				     P_("How to format displayed expressions, (A1 vs R1C1, function names, ...)"),
1103 				     gnm_conventions_get_type (),
1104 				     GSF_PARAM_STATIC |
1105 				     G_PARAM_READWRITE));
1106 	g_object_class_install_property (gobject_class, PROP_USE_R1C1, /* convenience wrapper to CONVENTIONS */
1107 		g_param_spec_boolean ("use-r1c1",
1108 				      P_("Display convention for expressions as XLS_R1C1 vs default"),
1109 				      P_("How to format displayed expressions, (a convenience api)"),
1110 				      FALSE,
1111 				      GSF_PARAM_STATIC |
1112 				      G_PARAM_READWRITE));
1113 
1114 	g_object_class_install_property (gobject_class, PROP_TAB_FOREGROUND,
1115 		g_param_spec_boxed ("tab-foreground",
1116 				    P_("Tab Foreground"),
1117 				    P_("The foreground color of the tab."),
1118 				    GNM_COLOR_TYPE,
1119 				    GSF_PARAM_STATIC |
1120 				    G_PARAM_READWRITE));
1121 	g_object_class_install_property (gobject_class, PROP_TAB_BACKGROUND,
1122 		g_param_spec_boxed ("tab-background",
1123 				    P_("Tab Background"),
1124 				    P_("The background color of the tab."),
1125 				    GNM_COLOR_TYPE,
1126 				    GSF_PARAM_STATIC |
1127 				    G_PARAM_READWRITE));
1128 
1129 	/* What is this doing in sheet?  */
1130 	g_object_class_install_property (gobject_class, PROP_ZOOM_FACTOR,
1131 		g_param_spec_double ("zoom-factor",
1132 				     P_("Zoom Factor"),
1133 				     P_("The level of zoom used for this sheet."),
1134 				     0.1, 5.0,
1135 				     1.0,
1136 				     GSF_PARAM_STATIC |
1137 				     G_PARAM_CONSTRUCT |
1138 				     G_PARAM_READWRITE));
1139 
1140 	g_object_class_install_property (gobject_class, PROP_COLUMNS,
1141 		g_param_spec_int ("columns",
1142 				  P_("Columns"),
1143 				  P_("Columns number in the sheet"),
1144 				  0, GNM_MAX_COLS, GNM_DEFAULT_COLS,
1145 				  GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1146 
1147 	g_object_class_install_property (gobject_class, PROP_ROWS,
1148 		g_param_spec_int ("rows",
1149 				  P_("Rows"),
1150 				  P_("Rows number in the sheet"),
1151 				  0, GNM_MAX_ROWS, GNM_DEFAULT_ROWS,
1152 				  GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1153 
1154 	signals[DETACHED_FROM_WORKBOOK] = g_signal_new
1155 		("detached_from_workbook",
1156 		 GNM_SHEET_TYPE,
1157 		 G_SIGNAL_RUN_LAST,
1158 		 G_STRUCT_OFFSET (GnmSheetClass, detached_from_workbook),
1159 		 NULL, NULL,
1160 		 g_cclosure_marshal_VOID__OBJECT,
1161 		 G_TYPE_NONE, 1, GNM_WORKBOOK_TYPE);
1162 
1163 }
1164 
GSF_CLASS(GnmSheet,gnm_sheet,gnm_sheet_class_init,gnm_sheet_init,G_TYPE_OBJECT)1165 GSF_CLASS (GnmSheet, gnm_sheet,
1166 	   gnm_sheet_class_init, gnm_sheet_init, G_TYPE_OBJECT)
1167 
1168 /* ------------------------------------------------------------------------- */
1169 
1170 GType
1171 gnm_sheet_type_get_type (void)
1172 {
1173   static GType etype = 0;
1174   if (etype == 0) {
1175 	  static const GEnumValue values[] = {
1176 		  { GNM_SHEET_DATA,   "GNM_SHEET_DATA",   "data"   },
1177 		  { GNM_SHEET_OBJECT, "GNM_SHEET_OBJECT", "object" },
1178 		  { GNM_SHEET_XLM,    "GNM_SHEET_XLM",    "xlm"    },
1179 		  { 0, NULL, NULL }
1180 	  };
1181 	  etype = g_enum_register_static ("GnmSheetType", values);
1182   }
1183   return etype;
1184 }
1185 
1186 GType
gnm_sheet_visibility_get_type(void)1187 gnm_sheet_visibility_get_type (void)
1188 {
1189   static GType etype = 0;
1190   if (etype == 0) {
1191 	  static GEnumValue const values[] = {
1192 		  { GNM_SHEET_VISIBILITY_VISIBLE, "GNM_SHEET_VISIBILITY_VISIBLE", "visible" },
1193 		  { GNM_SHEET_VISIBILITY_HIDDEN, "GNM_SHEET_VISIBILITY_HIDDEN", "hidden" },
1194 		  { GNM_SHEET_VISIBILITY_VERY_HIDDEN, "GNM_SHEET_VISIBILITY_VERY_HIDDEN", "very-hidden" },
1195 		  { 0, NULL, NULL }
1196 	  };
1197 	  etype = g_enum_register_static ("GnmSheetVisibility", values);
1198   }
1199   return etype;
1200 }
1201 
1202 /* ------------------------------------------------------------------------- */
1203 
1204 static gboolean
powerof_2(int i)1205 powerof_2 (int i)
1206 {
1207 	return i > 0 && (i & (i - 1)) == 0;
1208 }
1209 
1210 gboolean
gnm_sheet_valid_size(int cols,int rows)1211 gnm_sheet_valid_size (int cols, int rows)
1212 {
1213 	return (cols >= GNM_MIN_COLS &&
1214 		cols <= GNM_MAX_COLS &&
1215 		powerof_2 (cols) &&
1216 		rows >= GNM_MIN_ROWS &&
1217 		rows <= GNM_MAX_ROWS &&
1218 		powerof_2 (rows)
1219 #if 0
1220        	&& 0x80000000u / (unsigned)(cols / 2) >= (unsigned)rows
1221 #endif
1222 		);
1223 }
1224 
1225 /**
1226  * gnm_sheet_suggest_size:
1227  * @cols: (inout): number of columns
1228  * @rows: (inout): number of rows
1229  *
1230  * This function produces a valid sheet size that is reasonable for data
1231  * of @cols columns by @rows rows.  If possible, this will be a size bigger
1232  * in both dimensions.  However, that is not always possible and when it is
1233  * not, the suggested will be smaller in one or both directions.
1234  */
1235 void
gnm_sheet_suggest_size(int * cols,int * rows)1236 gnm_sheet_suggest_size (int *cols, int *rows)
1237 {
1238 	int c = GNM_DEFAULT_COLS;
1239 	int r = GNM_DEFAULT_ROWS;
1240 
1241 	while (c < *cols && c < GNM_MAX_COLS)
1242 		c *= 2;
1243 
1244 	while (r < *rows && r < GNM_MAX_ROWS)
1245 		r *= 2;
1246 
1247 	while (!gnm_sheet_valid_size (c, r)) {
1248 		/* Darn!  Too large.  */
1249 		if (*cols >= GNM_MIN_COLS && c > GNM_MIN_COLS)
1250 			c /= 2;
1251 		else if (*rows >= GNM_MIN_ROWS && r > GNM_MIN_ROWS)
1252 			r /= 2;
1253 		else if (c > GNM_MIN_COLS)
1254 			c /= 2;
1255 		else
1256 			r /= 2;
1257 	}
1258 
1259 	*cols = c;
1260 	*rows = r;
1261 }
1262 
1263 static void gnm_sheet_resize_main (Sheet *sheet, int cols, int rows,
1264 				   GOCmdContext *cc, GOUndo **pundo);
1265 
1266 static void
cb_sheet_resize(Sheet * sheet,const GnmSheetSize * data,GOCmdContext * cc)1267 cb_sheet_resize (Sheet *sheet, const GnmSheetSize *data, GOCmdContext *cc)
1268 {
1269 	gnm_sheet_resize_main (sheet, data->max_cols, data->max_rows,
1270 			       cc, NULL);
1271 }
1272 
1273 static void
gnm_sheet_resize_main(Sheet * sheet,int cols,int rows,GOCmdContext * cc,GOUndo ** pundo)1274 gnm_sheet_resize_main (Sheet *sheet, int cols, int rows,
1275 		       GOCmdContext *cc, GOUndo **pundo)
1276 {
1277 	int old_cols, old_rows;
1278 	GnmStyle **common_col_styles = NULL;
1279 	GnmStyle **common_row_styles = NULL;
1280 
1281 	if (pundo) *pundo = NULL;
1282 
1283 	old_cols = gnm_sheet_get_max_cols (sheet);
1284 	old_rows = gnm_sheet_get_max_rows (sheet);
1285 	if (old_cols == cols && old_rows == rows)
1286 		return;
1287 
1288 	sheet->workbook->sheet_size_cached = FALSE;
1289 
1290 	/* ---------------------------------------- */
1291 	/* Gather styles we want to copy into new areas.  */
1292 
1293 	if (cols > old_cols) {
1294 		int r;
1295 		common_row_styles = sheet_style_most_common (sheet, FALSE);
1296 		for (r = 0; r < old_rows; r++)
1297 			gnm_style_ref (common_row_styles[r]);
1298 	}
1299 	if (rows > old_rows) {
1300 		int c;
1301 		common_col_styles = sheet_style_most_common (sheet, TRUE);
1302 		for (c = 0; c < old_cols; c++)
1303 			gnm_style_ref (common_col_styles[c]);
1304 	}
1305 
1306 	/* ---------------------------------------- */
1307 	/* Remove the columns and rows that will disappear.  */
1308 
1309 	if (cols < old_cols) {
1310 		GOUndo *u = NULL;
1311 		gboolean err;
1312 
1313 		err = sheet_delete_cols (sheet, cols, G_MAXINT,
1314 					 pundo ? &u : NULL, cc);
1315 		if (pundo)
1316 			*pundo = go_undo_combine (*pundo, u);
1317 		if (err)
1318 			goto handle_error;
1319 	}
1320 
1321 	if (rows < old_rows) {
1322 		GOUndo *u = NULL;
1323 		gboolean err;
1324 
1325 		err = sheet_delete_rows (sheet, rows, G_MAXINT,
1326 					 pundo ? &u : NULL, cc);
1327 		if (pundo)
1328 			*pundo = go_undo_combine (*pundo, u);
1329 		if (err)
1330 			goto handle_error;
1331 	}
1332 
1333 	/* ---------------------------------------- */
1334 	/* Restrict selection.  (Not undone.)  */
1335 
1336 	SHEET_FOREACH_VIEW (sheet, sv,
1337 		{
1338 			GnmRange new_full;
1339 			GSList *l;
1340 			GSList *sel = selection_get_ranges (sv, TRUE);
1341 			gboolean any = FALSE;
1342 			GnmCellPos vis;
1343 			sv_selection_reset (sv);
1344 			range_init (&new_full, 0, 0, cols - 1, rows - 1);
1345 			vis = new_full.start;
1346 			for (l = sel; l; l = l->next) {
1347 				GnmRange *r = l->data;
1348 				GnmRange newr;
1349 				if (range_intersection (&newr, r, &new_full)) {
1350 					sv_selection_add_range (sv, &newr);
1351 					vis = newr.start;
1352 					any = TRUE;
1353 				}
1354 				g_free (r);
1355 			}
1356 			g_slist_free (sel);
1357 			if (!any)
1358 				sv_selection_add_pos (sv, 0, 0,
1359 						      GNM_SELECTION_MODE_ADD);
1360 			gnm_sheet_view_make_cell_visible (sv, vis.col, vis.row, FALSE);
1361 		});
1362 
1363 	/* ---------------------------------------- */
1364 	/* Resize column and row containers.  */
1365 
1366 	col_row_collection_resize (&sheet->cols, cols);
1367 	col_row_collection_resize (&sheet->rows, rows);
1368 
1369 	/* ---------------------------------------- */
1370 	/* Resize the dependency containers.  */
1371 
1372 	{
1373 		GSList *l, *linked = NULL;
1374 		/* FIXME: what about dependents in other workbooks?  */
1375 		WORKBOOK_FOREACH_DEPENDENT
1376 			(sheet->workbook, dep,
1377 
1378 			 if (dependent_is_linked (dep)) {
1379 				 dependent_unlink (dep);
1380 				 linked = g_slist_prepend (linked, dep);
1381 			 });
1382 
1383 		gnm_dep_container_resize (sheet->deps, rows);
1384 
1385 		for (l = linked; l; l = l->next) {
1386 			GnmDependent *dep = l->data;
1387 			dependent_link (dep);
1388 		}
1389 
1390 		g_slist_free (linked);
1391 
1392 		workbook_queue_all_recalc (sheet->workbook);
1393 	}
1394 
1395 	/* ---------------------------------------- */
1396 	/* Resize the styles.  */
1397 
1398 	sheet_style_resize (sheet, cols, rows);
1399 
1400 	/* ---------------------------------------- */
1401 	/* Actually change the properties.  */
1402 
1403 	sheet->size.max_cols = cols;
1404 	sheet->cols.max_used = MIN (sheet->cols.max_used, cols - 1);
1405 	sheet->size.max_rows = rows;
1406 	sheet->rows.max_used = MIN (sheet->rows.max_used, rows - 1);
1407 
1408 	if (old_cols != cols)
1409 		g_object_notify (G_OBJECT (sheet), "columns");
1410 	if (old_rows != rows)
1411 		g_object_notify (G_OBJECT (sheet), "rows");
1412 
1413 	if (pundo) {
1414 		GnmSheetSize *data = g_new (GnmSheetSize, 1);
1415 		GOUndo *u;
1416 
1417 		data->max_cols = old_cols;
1418 		data->max_rows = old_rows;
1419 		u = go_undo_binary_new (sheet, data,
1420 					(GOUndoBinaryFunc)cb_sheet_resize,
1421 					NULL, g_free);
1422 		*pundo = go_undo_combine (*pundo, u);
1423 	}
1424 
1425 	range_init_full_sheet (&sheet->priv->unhidden_region, sheet);
1426 
1427 	/* ---------------------------------------- */
1428 	/* Apply styles to new areas.  */
1429 
1430 	if (cols > old_cols) {
1431 		int r = 0;
1432 		while (r < old_rows) {
1433 			int r2 = r;
1434 			GnmStyle *mstyle = common_row_styles[r];
1435 			GnmRange rng;
1436 			while (r2 + 1 < old_rows &&
1437 			       mstyle == common_row_styles[r2 + 1])
1438 				r2++;
1439 			range_init (&rng, old_cols, r, cols - 1, r2);
1440 			gnm_style_ref (mstyle);
1441 			sheet_apply_style (sheet, &rng, mstyle);
1442 			r = r2 + 1;
1443 		}
1444 
1445 		for (r = 0; r < old_rows; r++)
1446 			gnm_style_unref (common_row_styles[r]);
1447 
1448 		g_free (common_row_styles);
1449 	}
1450 
1451 	if (rows > old_rows) {
1452 		int c = 0;
1453 
1454 		while (c < old_cols) {
1455 			int c2 = c;
1456 			GnmStyle *mstyle = common_col_styles[c];
1457 			GnmRange rng;
1458 			while (c2 + 1 < old_cols &&
1459 			       mstyle == common_col_styles[c2 + 1])
1460 				c2++;
1461 			range_init (&rng, c, old_rows, c2, rows - 1);
1462 			gnm_style_ref (mstyle);
1463 			sheet_apply_style (sheet, &rng, mstyle);
1464 			c = c2 + 1;
1465 		}
1466 
1467 		if (cols > old_cols) {
1468 			/*
1469 			 * Expanded in both directions.  One could argue about
1470 			 * what style to use down here, but we choose the
1471 			 * last column style.
1472 			 */
1473 			GnmStyle *mstyle = common_col_styles[old_cols - 1];
1474 			GnmRange rng;
1475 
1476 			range_init (&rng,
1477 				    old_cols, old_rows,
1478 				    cols - 1, rows - 1);
1479 			gnm_style_ref (mstyle);
1480 			sheet_apply_style (sheet, &rng, mstyle);
1481 		}
1482 
1483 		for (c = 0; c < old_cols; c++)
1484 			gnm_style_unref (common_col_styles[c]);
1485 		g_free (common_col_styles);
1486 	}
1487 
1488 	/* ---------------------------------------- */
1489 
1490 	sheet_redraw_all (sheet, TRUE);
1491 	return;
1492 
1493  handle_error:
1494 	if (pundo) {
1495 		go_undo_undo_with_data (*pundo, cc);
1496 		g_object_unref (*pundo);
1497 		*pundo = NULL;
1498 	}
1499 }
1500 
1501 /**
1502  * gnm_sheet_resize:
1503  * @sheet: #Sheet
1504  * @cols: the new columns number.
1505  * @rows: the new rows number.
1506  * @cc: #GOCmdContext.
1507  * @perr: (out): will be %TRUE on error.
1508  *
1509  * Returns: (transfer full): the newly allocated #GOUndo.
1510  **/
1511 GOUndo *
gnm_sheet_resize(Sheet * sheet,int cols,int rows,GOCmdContext * cc,gboolean * perr)1512 gnm_sheet_resize (Sheet *sheet, int cols, int rows,
1513 		  GOCmdContext *cc, gboolean *perr)
1514 {
1515 	GOUndo *undo = NULL;
1516 
1517 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
1518 	g_return_val_if_fail (gnm_sheet_valid_size (cols, rows), NULL);
1519 
1520 	if (cols < sheet->size.max_cols || rows < sheet->size.max_rows) {
1521 		GSList *overlap, *l;
1522 		gboolean bad = FALSE;
1523 		GnmRange r;
1524 
1525 		r.start.col = r.start.row = 0;
1526 		r.end.col = MIN (cols, sheet->size.max_cols) - 1;
1527 		r.end.row = MIN (rows, sheet->size.max_rows) - 1;
1528 
1529 		overlap = gnm_sheet_merge_get_overlap (sheet, &r);
1530 		for (l = overlap; l && !bad; l = l->next) {
1531 			GnmRange const *m = l->data;
1532 			if (!range_contained (m, &r)) {
1533 				bad = TRUE;
1534 				gnm_cmd_context_error_splits_merge (cc, m);
1535 			}
1536 		}
1537 		g_slist_free (overlap);
1538 		if (bad) {
1539 			*perr = TRUE;
1540 			return NULL;
1541 		}
1542 	}
1543 
1544 	gnm_sheet_resize_main (sheet, cols, rows, cc, &undo);
1545 
1546 	*perr = FALSE;
1547 	return undo;
1548 }
1549 
1550 
1551 /**
1552  * sheet_new_with_type:
1553  * @wb: #Workbook
1554  * @name: An unquoted name
1555  * @type: @GnmSheetType
1556  * @columns: The number of columns for the sheet
1557  * @rows: The number of rows for the sheet
1558  *
1559  * Create a new Sheet of type @type, and associate it with @wb.
1560  * The type cannot be changed later.
1561  *
1562  * Returns: (transfer full): the newly allocated sheet.
1563  **/
1564 Sheet *
sheet_new_with_type(Workbook * wb,char const * name,GnmSheetType type,int columns,int rows)1565 sheet_new_with_type (Workbook *wb, char const *name, GnmSheetType type,
1566 		     int columns, int rows)
1567 {
1568 	Sheet *sheet;
1569 
1570 	g_return_val_if_fail (wb != NULL, NULL);
1571 	g_return_val_if_fail (name != NULL, NULL);
1572 	g_return_val_if_fail (gnm_sheet_valid_size (columns, rows), NULL);
1573 
1574 	sheet = g_object_new (GNM_SHEET_TYPE,
1575 			      "workbook", wb,
1576 			      "sheet-type", type,
1577 			      "columns", columns,
1578 			      "rows", rows,
1579 			      "name", name,
1580 			      "zoom-factor", gnm_conf_get_core_gui_window_zoom (),
1581 			      NULL);
1582 
1583 	if (type == GNM_SHEET_OBJECT)
1584 		print_info_set_paper_orientation (sheet->print_info, GTK_PAGE_ORIENTATION_LANDSCAPE);
1585 
1586 	return sheet;
1587 }
1588 
1589 /**
1590  * sheet_new:
1591  * @wb: #Workbook
1592  * @name: The name for the sheet (unquoted).
1593  * @columns: The requested columns number.
1594  * @rows: The requested rows number.
1595  *
1596  * Create a new Sheet of type SHEET_DATA, and associate it with @wb.
1597  * The type cannot be changed later
1598  *
1599  * Returns: (transfer full): the newly allocated sheet.
1600  **/
1601 Sheet *
sheet_new(Workbook * wb,char const * name,int columns,int rows)1602 sheet_new (Workbook *wb, char const *name, int columns, int rows)
1603 {
1604 	return sheet_new_with_type (wb, name, GNM_SHEET_DATA, columns, rows);
1605 }
1606 
1607 /****************************************************************************/
1608 
1609 void
sheet_redraw_all(Sheet const * sheet,gboolean headers)1610 sheet_redraw_all (Sheet const *sheet, gboolean headers)
1611 {
1612 	/* We potentially do a lot of recalcs as part of this, so make sure
1613 	   stuff that caches sub-computations see the whole thing instead
1614 	   of clearing between cells.  */
1615 	gnm_app_recalc_start ();
1616 	SHEET_FOREACH_CONTROL (sheet, view, control,
1617 		sc_redraw_all (control, headers););
1618 	gnm_app_recalc_finish ();
1619 }
1620 
1621 static GnmValue *
cb_clear_rendered_values(GnmCellIter const * iter,G_GNUC_UNUSED gpointer user)1622 cb_clear_rendered_values (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
1623 {
1624 	gnm_cell_unrender (iter->cell);
1625 	return NULL;
1626 }
1627 
1628 /**
1629  * sheet_range_calc_spans:
1630  * @sheet: The sheet,
1631  * @r:     the region to update.
1632  * @flags:
1633  *
1634  * This is used to re-calculate cell dimensions and re-render
1635  * a cell's text. eg. if a format has changed we need to re-render
1636  * the cached version of the rendered text in the cell.
1637  **/
1638 void
sheet_range_calc_spans(Sheet * sheet,GnmRange const * r,GnmSpanCalcFlags flags)1639 sheet_range_calc_spans (Sheet *sheet, GnmRange const *r, GnmSpanCalcFlags flags)
1640 {
1641 	if (flags & GNM_SPANCALC_RE_RENDER)
1642 		sheet_foreach_cell_in_range
1643 			(sheet, CELL_ITER_IGNORE_NONEXISTENT, r,
1644 			 cb_clear_rendered_values, NULL);
1645 	sheet_queue_respan (sheet, r->start.row, r->end.row);
1646 
1647 	/* Redraw the new region in case the span changes */
1648 	sheet_redraw_range (sheet, r);
1649 }
1650 
1651 static void
sheet_redraw_partial_row(Sheet const * sheet,int const row,int const start_col,int const end_col)1652 sheet_redraw_partial_row (Sheet const *sheet, int const row,
1653 			  int const start_col, int const end_col)
1654 {
1655 	GnmRange r;
1656 	range_init (&r, start_col, row, end_col, row);
1657 	SHEET_FOREACH_CONTROL (sheet, view, control,
1658 		sc_redraw_range (control, &r););
1659 }
1660 
1661 static void
sheet_redraw_cell(GnmCell const * cell)1662 sheet_redraw_cell (GnmCell const *cell)
1663 {
1664 	CellSpanInfo const * span;
1665 	int start_col, end_col, row;
1666 	GnmRange const *merged;
1667 	Sheet *sheet;
1668 	ColRowInfo *ri;
1669 
1670 	g_return_if_fail (cell != NULL);
1671 
1672 	sheet = cell->base.sheet;
1673 	merged = gnm_sheet_merge_is_corner (sheet, &cell->pos);
1674 	if (merged != NULL) {
1675 		SHEET_FOREACH_CONTROL (sheet, view, control,
1676 			sc_redraw_range (control, merged););
1677 		return;
1678 	}
1679 
1680 	row = cell->pos.row;
1681 	start_col = end_col = cell->pos.col;
1682 	ri = sheet_row_get (sheet, row);
1683 	span = row_span_get (ri, start_col);
1684 
1685 	if (span) {
1686 		start_col = span->left;
1687 		end_col = span->right;
1688 	}
1689 
1690 	sheet_redraw_partial_row (sheet, row, start_col, end_col);
1691 }
1692 
1693 static void
sheet_cell_calc_span(GnmCell * cell,GnmSpanCalcFlags flags)1694 sheet_cell_calc_span (GnmCell *cell, GnmSpanCalcFlags flags)
1695 {
1696 	CellSpanInfo const * span;
1697 	int left, right;
1698 	int min_col, max_col, row;
1699 	gboolean render = (flags & GNM_SPANCALC_RE_RENDER) != 0;
1700 	gboolean const resize = (flags & GNM_SPANCALC_RESIZE) != 0;
1701 	gboolean existing = FALSE;
1702 	GnmRange const *merged;
1703 	Sheet *sheet;
1704 	ColRowInfo *ri;
1705 
1706 	g_return_if_fail (cell != NULL);
1707 
1708 	sheet = cell->base.sheet;
1709 	row = cell->pos.row;
1710 
1711 	/* Render and Size any unrendered cells */
1712 	if ((flags & GNM_SPANCALC_RENDER) && gnm_cell_get_rendered_value (cell) == NULL)
1713 		render = TRUE;
1714 
1715 	if (render) {
1716 		if (!gnm_cell_has_expr (cell))
1717 			gnm_cell_render_value ((GnmCell *)cell, TRUE);
1718 		else
1719 			gnm_cell_unrender (cell);
1720 	} else if (resize) {
1721 		/* FIXME: what was wanted here?  */
1722 		/* rendered_value_calc_size (cell); */
1723 	}
1724 
1725 	/* Is there an existing span ? clear it BEFORE calculating new one */
1726 	ri = sheet_row_get (sheet, row);
1727 	span = row_span_get (ri, cell->pos.col);
1728 	if (span != NULL) {
1729 		GnmCell const * const other = span->cell;
1730 
1731 		min_col = span->left;
1732 		max_col = span->right;
1733 
1734 		/* A different cell used to span into this cell, respan that */
1735 		if (cell != other) {
1736 			int other_left, other_right;
1737 
1738 			cell_unregister_span (other);
1739 			cell_calc_span (other, &other_left, &other_right);
1740 			if (min_col > other_left)
1741 				min_col = other_left;
1742 			if (max_col < other_right)
1743 				max_col = other_right;
1744 
1745 			if (other_left != other_right)
1746 				cell_register_span (other, other_left, other_right);
1747 		} else
1748 			existing = TRUE;
1749 	} else
1750 		min_col = max_col = cell->pos.col;
1751 
1752 	merged = gnm_sheet_merge_is_corner (sheet, &cell->pos);
1753 	if (NULL != merged) {
1754 		if (existing) {
1755 			if (min_col > merged->start.col)
1756 				min_col = merged->start.col;
1757 			if (max_col < merged->end.col)
1758 				max_col = merged->end.col;
1759 		} else {
1760 			sheet_redraw_cell (cell);
1761 			return;
1762 		}
1763 	} else {
1764 		/* Calculate the span of the cell */
1765 		cell_calc_span (cell, &left, &right);
1766 		if (min_col > left)
1767 			min_col = left;
1768 		if (max_col < right)
1769 			max_col = right;
1770 
1771 		/* This cell already had an existing span */
1772 		if (existing) {
1773 			/* If it changed, remove the old one */
1774 			if (left != span->left || right != span->right)
1775 				cell_unregister_span (cell);
1776 			else
1777 				/* unchanged, short curcuit adding the span again */
1778 				left = right;
1779 		}
1780 
1781 		if (left != right)
1782 			cell_register_span (cell, left, right);
1783 	}
1784 
1785 	sheet_redraw_partial_row (sheet, row, min_col, max_col);
1786 }
1787 
1788 /**
1789  * sheet_apply_style: (skip)
1790  * @sheet: the sheet in which can be found
1791  * @range: the range to which should be applied
1792  * @style: (transfer full): A #GnmStyle partial style
1793  *
1794  * A mid level routine that applies the supplied partial style @style to the
1795  * target @range and performs the necessary respanning and redrawing.
1796  **/
1797 void
sheet_apply_style(Sheet * sheet,GnmRange const * range,GnmStyle * style)1798 sheet_apply_style (Sheet       *sheet,
1799 		   GnmRange const *range,
1800 		   GnmStyle      *style)
1801 {
1802 	GnmSpanCalcFlags spanflags = gnm_style_required_spanflags (style);
1803 	sheet_style_apply_range (sheet, range, style);
1804 	/* This also redraws the range: */
1805 	sheet_range_calc_spans (sheet, range, spanflags);
1806 }
1807 
1808 /**
1809  * sheet_apply_style_gi: (rename-to sheet_apply_style)
1810  * @sheet: the sheet in which can be found
1811  * @range: the range to which should be applied
1812  * @style: A #GnmStyle partial style
1813  *
1814  * A mid level routine that applies the supplied partial style @style to the
1815  * target @range and performs the necessary respanning and redrawing.
1816  **/
1817 void
sheet_apply_style_gi(Sheet * sheet,GnmRange const * range,GnmStyle * style)1818 sheet_apply_style_gi (Sheet *sheet, GnmRange const *range, GnmStyle *style)
1819 {
1820 	GnmSpanCalcFlags spanflags = gnm_style_required_spanflags (style);
1821 	gnm_style_ref (style);
1822 	sheet_style_apply_range (sheet, range, style);
1823 	/* This also redraws the range: */
1824 	sheet_range_calc_spans (sheet, range, spanflags);
1825 }
1826 
1827 static void
sheet_apply_style_cb(GnmSheetRange * sr,GnmStyle * style)1828 sheet_apply_style_cb (GnmSheetRange *sr,
1829 		      GnmStyle      *style)
1830 {
1831 	gnm_style_ref (style);
1832 	sheet_apply_style (sr->sheet, &sr->range, style);
1833 	sheet_flag_style_update_range (sr->sheet, &sr->range);
1834 }
1835 
1836 /**
1837  * sheet_apply_style_undo:
1838  * @sr: (transfer full): #GnmSheetRange
1839  * @style: (transfer none): #GnmStyle
1840  *
1841  * Returns: (transfer full): the new #GOUndo.
1842  **/
1843 GOUndo *
sheet_apply_style_undo(GnmSheetRange * sr,GnmStyle * style)1844 sheet_apply_style_undo (GnmSheetRange *sr,
1845 			GnmStyle      *style)
1846 {
1847 	gnm_style_ref (style);
1848 	return go_undo_binary_new
1849 		(sr, (gpointer)style,
1850 		 (GOUndoBinaryFunc) sheet_apply_style_cb,
1851 		 (GFreeFunc) gnm_sheet_range_free,
1852 		 (GFreeFunc) gnm_style_unref);
1853 }
1854 
1855 
1856 /**
1857  * sheet_apply_border:
1858  * @sheet: #Sheet to change
1859  * @range: #GnmRange around which to place borders
1860  * @borders: (array fixed-size=8): Border styles to set.
1861  */
1862 void
sheet_apply_border(Sheet * sheet,GnmRange const * range,GnmBorder * borders[GNM_STYLE_BORDER_EDGE_MAX])1863 sheet_apply_border (Sheet *sheet,
1864 		    GnmRange const *range,
1865 		    GnmBorder *borders[GNM_STYLE_BORDER_EDGE_MAX])
1866 {
1867 	GnmSpanCalcFlags spanflags = GNM_SPANCALC_RE_RENDER | GNM_SPANCALC_RESIZE;
1868 	sheet_style_apply_border (sheet, range, borders);
1869 	/* This also redraws the range: */
1870 	sheet_range_calc_spans (sheet, range, spanflags);
1871 }
1872 
1873 /****************************************************************************/
1874 
1875 static ColRowInfo *
sheet_row_new(Sheet * sheet)1876 sheet_row_new (Sheet *sheet)
1877 {
1878 	ColRowInfo *ri;
1879 
1880 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
1881 
1882 	ri = col_row_info_new ();
1883 	*ri = sheet->rows.default_style;
1884 	ri->is_default = FALSE;
1885 	ri->needs_respan = TRUE;
1886 
1887 	return ri;
1888 }
1889 
1890 static ColRowInfo *
sheet_col_new(Sheet * sheet)1891 sheet_col_new (Sheet *sheet)
1892 {
1893 	ColRowInfo *ci;
1894 
1895 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
1896 
1897 	ci = col_row_info_new ();
1898 	*ci = sheet->cols.default_style;
1899 	ci->is_default = FALSE;
1900 
1901 	return ci;
1902 }
1903 
1904 static void
sheet_colrow_add(Sheet * sheet,ColRowInfo * cp,gboolean is_cols,int n)1905 sheet_colrow_add (Sheet *sheet, ColRowInfo *cp, gboolean is_cols, int n)
1906 {
1907 	ColRowCollection *info = is_cols ? &sheet->cols : &sheet->rows;
1908 	ColRowSegment **psegment = (ColRowSegment **)&COLROW_GET_SEGMENT (info, n);
1909 
1910 	g_return_if_fail (n >= 0);
1911 	g_return_if_fail (n < colrow_max (is_cols, sheet));
1912 
1913 	if (*psegment == NULL)
1914 		*psegment = g_new0 (ColRowSegment, 1);
1915 	colrow_free ((*psegment)->info[COLROW_SUB_INDEX (n)]);
1916 	(*psegment)->info[COLROW_SUB_INDEX (n)] = cp;
1917 
1918 	if (cp->outline_level > info->max_outline_level)
1919 		info->max_outline_level = cp->outline_level;
1920 	if (n > info->max_used) {
1921 		info->max_used = n;
1922 		sheet->priv->resize_scrollbar = TRUE;
1923 	}
1924 }
1925 
1926 static void
sheet_reposition_objects(Sheet const * sheet,GnmCellPos const * pos)1927 sheet_reposition_objects (Sheet const *sheet, GnmCellPos const *pos)
1928 {
1929 	GSList *ptr;
1930 	for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = ptr->next )
1931 		sheet_object_update_bounds (GNM_SO (ptr->data), pos);
1932 }
1933 
1934 /**
1935  * sheet_flag_status_update_cell:
1936  * @cell: The cell that has changed.
1937  *
1938  *    flag the sheet as requiring an update to the status display
1939  *    if the supplied cell location is the edit cursor, or part of the
1940  *    selected region.
1941  *
1942  * Will cause the format toolbar, the edit area, and the auto expressions to be
1943  * updated if appropriate.
1944  **/
1945 void
sheet_flag_status_update_cell(GnmCell const * cell)1946 sheet_flag_status_update_cell (GnmCell const *cell)
1947 {
1948 	SHEET_FOREACH_VIEW (cell->base.sheet, sv,
1949 		gnm_sheet_view_flag_status_update_pos (sv, &cell->pos););
1950 }
1951 
1952 /**
1953  * sheet_flag_status_update_range:
1954  * @sheet:
1955  * @range: (nullable): GnmRange, or %NULL for full sheet
1956  *
1957  *    flag the sheet as requiring an update to the status display
1958  *    if the supplied cell location contains the edit cursor, or intersects of
1959  *    the selected region.
1960  *
1961  * Will cause the format toolbar, the edit area, and the auto expressions to be
1962  * updated if appropriate.
1963  **/
1964 void
sheet_flag_status_update_range(Sheet const * sheet,GnmRange const * range)1965 sheet_flag_status_update_range (Sheet const *sheet, GnmRange const *range)
1966 {
1967 	SHEET_FOREACH_VIEW (sheet, sv,
1968 		gnm_sheet_view_flag_status_update_range (sv, range););
1969 }
1970 
1971 /**
1972  * sheet_flag_style_update_range:
1973  * @sheet: The sheet being changed
1974  * @range: the range that is changing.
1975  *
1976  * Flag format changes that will require updating the format indicators.
1977  **/
1978 void
sheet_flag_style_update_range(Sheet const * sheet,GnmRange const * range)1979 sheet_flag_style_update_range (Sheet const *sheet, GnmRange const *range)
1980 {
1981 	SHEET_FOREACH_VIEW (sheet, sv,
1982 		gnm_sheet_view_flag_style_update_range (sv, range););
1983 }
1984 
1985 /**
1986  * sheet_flag_recompute_spans:
1987  * @sheet:
1988  *
1989  * Flag the sheet as requiring a full span recomputation the next time
1990  * sheet_update is called.
1991  **/
1992 void
sheet_flag_recompute_spans(Sheet const * sheet)1993 sheet_flag_recompute_spans (Sheet const *sheet)
1994 {
1995 	sheet->priv->recompute_spans = TRUE;
1996 }
1997 
1998 static gboolean
cb_outline_level(GnmColRowIter const * iter,gpointer data)1999 cb_outline_level (GnmColRowIter const *iter, gpointer data)
2000 {
2001 	int *outline_level = data;
2002 	if (*outline_level < iter->cri->outline_level)
2003 		*outline_level  = iter->cri->outline_level;
2004 	return FALSE;
2005 }
2006 
2007 /**
2008  * sheet_colrow_fit_gutter:
2009  * @sheet: Sheet to change.
2010  * @is_cols: %TRUE for columns, %FALSE for rows.
2011  *
2012  * Find the current max outline level.
2013  **/
2014 static int
sheet_colrow_fit_gutter(Sheet const * sheet,gboolean is_cols)2015 sheet_colrow_fit_gutter (Sheet const *sheet, gboolean is_cols)
2016 {
2017 	int outline_level = 0;
2018 	sheet_colrow_foreach (sheet, is_cols, 0, -1,
2019 			      cb_outline_level, &outline_level);
2020 	return outline_level;
2021 }
2022 
2023 /**
2024  * sheet_objects_max_extent:
2025  * @sheet:
2026  *
2027  * Utility routine to calculate the maximum extent of objects in this sheet.
2028  */
2029 static void
sheet_objects_max_extent(Sheet * sheet)2030 sheet_objects_max_extent (Sheet *sheet)
2031 {
2032 	GnmCellPos max_pos = { 0, 0 };
2033 	GSList *ptr;
2034 
2035 	for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = ptr->next ) {
2036 		SheetObject *so = GNM_SO (ptr->data);
2037 
2038 		if (max_pos.col < so->anchor.cell_bound.end.col)
2039 			max_pos.col = so->anchor.cell_bound.end.col;
2040 		if (max_pos.row < so->anchor.cell_bound.end.row)
2041 			max_pos.row = so->anchor.cell_bound.end.row;
2042 	}
2043 
2044 	if (sheet->max_object_extent.col != max_pos.col ||
2045 	    sheet->max_object_extent.row != max_pos.row) {
2046 		sheet->max_object_extent = max_pos;
2047 		sheet_scrollbar_config (sheet);
2048 	}
2049 }
2050 
2051 /**
2052  * sheet_update_only_grid:
2053  * @sheet: #Sheet
2054  *
2055  * Should be called after a logical command has finished processing
2056  * to request redraws for any pending events
2057  **/
2058 void
sheet_update_only_grid(Sheet const * sheet)2059 sheet_update_only_grid (Sheet const *sheet)
2060 {
2061 	SheetPrivate *p;
2062 
2063 	g_return_if_fail (IS_SHEET (sheet));
2064 
2065 	p = sheet->priv;
2066 
2067 	if (p->objects_changed) {
2068 		p->objects_changed = FALSE;
2069 		sheet_objects_max_extent ((Sheet *)sheet);
2070 	}
2071 
2072 	/* be careful these can toggle flags */
2073 	if (p->recompute_max_col_group) {
2074 		sheet_colrow_gutter ((Sheet *)sheet, TRUE,
2075 			sheet_colrow_fit_gutter (sheet, TRUE));
2076 		sheet->priv->recompute_max_col_group = FALSE;
2077 	}
2078 	if (p->recompute_max_row_group) {
2079 		sheet_colrow_gutter ((Sheet *)sheet, FALSE,
2080 			sheet_colrow_fit_gutter (sheet, FALSE));
2081 		sheet->priv->recompute_max_row_group = FALSE;
2082 	}
2083 
2084 	SHEET_FOREACH_VIEW (sheet, sv, {
2085 		if (sv->reposition_selection) {
2086 			sv->reposition_selection = FALSE;
2087 
2088 			/* when moving we cleared the selection before
2089 			 * arriving in here.
2090 			 */
2091 			if (sv->selections != NULL)
2092 				sv_selection_set (sv, &sv->edit_pos_real,
2093 						  sv->cursor.base_corner.col,
2094 						  sv->cursor.base_corner.row,
2095 						  sv->cursor.move_corner.col,
2096 						  sv->cursor.move_corner.row);
2097 		}
2098 	});
2099 
2100 	if (p->recompute_spans) {
2101 		p->recompute_spans = FALSE;
2102 		/* FIXME : I would prefer to use GNM_SPANCALC_RENDER rather than
2103 		 * RE_RENDER.  It only renders those cells which are not
2104 		 * rendered.  The trouble is that when a col changes size we
2105 		 * need to rerender, but currently nothing marks that.
2106 		 *
2107 		 * hmm, that suggests an approach.  maybe I can install a per
2108 		 * col flag.  Then add a flag clearing loop after the
2109 		 * sheet_calc_span.
2110 		 */
2111 #if 0
2112 		sheet_calc_spans (sheet, GNM_SPANCALC_RESIZE|GNM_SPANCALC_RE_RENDER |
2113 				  (p->recompute_visibility ?
2114 				   SPANCALC_NO_DRAW : GNM_SPANCALC_SIMPLE));
2115 #endif
2116 		sheet_queue_respan (sheet, 0, gnm_sheet_get_last_row (sheet));
2117 	}
2118 
2119 	if (p->reposition_objects.row < gnm_sheet_get_max_rows (sheet) ||
2120 	    p->reposition_objects.col < gnm_sheet_get_max_cols (sheet)) {
2121 		SHEET_FOREACH_VIEW (sheet, sv, {
2122 			if (!p->resize && gnm_sheet_view_is_frozen (sv)) {
2123 				if (p->reposition_objects.col < sv->unfrozen_top_left.col ||
2124 				    p->reposition_objects.row < sv->unfrozen_top_left.row) {
2125 					gnm_sheet_view_resize (sv, FALSE);
2126 				}
2127 			}
2128 		});
2129 		sheet_reposition_objects (sheet, &p->reposition_objects);
2130 		p->reposition_objects.row = gnm_sheet_get_max_rows (sheet);
2131 		p->reposition_objects.col = gnm_sheet_get_max_cols (sheet);
2132 	}
2133 
2134 	if (p->resize) {
2135 		p->resize = FALSE;
2136 		SHEET_FOREACH_VIEW (sheet, sv, { gnm_sheet_view_resize (sv, FALSE); });
2137 	}
2138 
2139 	if (p->recompute_visibility) {
2140 		/* TODO : There is room for some opimization
2141 		 * We only need to force complete visibility recalculation
2142 		 * (which we do in sheet_compute_visible_region)
2143 		 * if a row or col before the start of the visible region.
2144 		 * If we are REALLY smart we could even accumulate the size differential
2145 		 * and use that.
2146 		 */
2147 		p->recompute_visibility = FALSE;
2148 		p->resize_scrollbar = FALSE; /* compute_visible_region does this */
2149 		SHEET_FOREACH_CONTROL(sheet, view, control,
2150 			sc_recompute_visible_region (control, TRUE););
2151 		sheet_redraw_all (sheet, TRUE);
2152 	}
2153 
2154 	if (p->resize_scrollbar) {
2155 		sheet_scrollbar_config (sheet);
2156 		p->resize_scrollbar = FALSE;
2157 	}
2158 
2159 	if (p->filters_changed) {
2160 		p->filters_changed = FALSE;
2161 		SHEET_FOREACH_CONTROL (sheet, sv, sc,
2162 			wb_control_menu_state_update (sc_wbc (sc), MS_ADD_VS_REMOVE_FILTER););
2163 	}
2164 }
2165 
2166 /**
2167  * sheet_update:
2168  * @sheet: #Sheet
2169  *
2170  * Should be called after a logical command has finished processing to request
2171  * redraws for any pending events, and to update the various status regions
2172  **/
2173 void
sheet_update(Sheet const * sheet)2174 sheet_update (Sheet const *sheet)
2175 {
2176 	g_return_if_fail (IS_SHEET (sheet));
2177 
2178 	sheet_update_only_grid (sheet);
2179 
2180 	SHEET_FOREACH_VIEW (sheet, sv, gnm_sheet_view_update (sv););
2181 }
2182 
2183 /**
2184  * sheet_cell_get:
2185  * @sheet:  The sheet where we want to locate the cell
2186  * @col:    the cell column
2187  * @row:    the cell row
2188  *
2189  * Return value: (nullable): a #GnmCell, or %NULL if the cell does not exist
2190  **/
2191 GnmCell *
sheet_cell_get(Sheet const * sheet,int col,int row)2192 sheet_cell_get (Sheet const *sheet, int col, int row)
2193 {
2194 	GnmCell *cell;
2195 	GnmCell key;
2196 
2197 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
2198 
2199 	key.pos.col = col;
2200 	key.pos.row = row;
2201 	cell = g_hash_table_lookup (sheet->cell_hash, &key);
2202 
2203 	return cell;
2204 }
2205 
2206 /**
2207  * sheet_cell_fetch:
2208  * @sheet:  The sheet where we want to locate the cell
2209  * @col:    the cell column
2210  * @row:    the cell row
2211  *
2212  * Return value: a #GnmCell containing at (@col,@row).
2213  * If no cell existed at that location before, it is created.
2214  **/
2215 GnmCell *
sheet_cell_fetch(Sheet * sheet,int col,int row)2216 sheet_cell_fetch (Sheet *sheet, int col, int row)
2217 {
2218 	GnmCell *cell;
2219 
2220 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
2221 
2222 	cell = sheet_cell_get (sheet, col, row);
2223 	if (!cell)
2224 		cell = sheet_cell_create (sheet, col, row);
2225 
2226 	return cell;
2227 }
2228 
2229 /**
2230  * sheet_colrow_can_group:
2231  * @sheet: #Sheet
2232  * @r: A #GnmRange
2233  * @is_cols: %TRUE for columns, %FALSE for rows.
2234  *
2235  * Returns: %TRUE if the cols/rows in @r.start -> @r.end can be grouped,
2236  * %FALSE otherwise. You can invert the result if you need to find out if a
2237  * group can be ungrouped.
2238  **/
2239 gboolean
sheet_colrow_can_group(Sheet * sheet,GnmRange const * r,gboolean is_cols)2240 sheet_colrow_can_group (Sheet *sheet, GnmRange const *r, gboolean is_cols)
2241 {
2242 	ColRowInfo const *start_cri, *end_cri;
2243 	int start, end;
2244 
2245 	g_return_val_if_fail (IS_SHEET (sheet), FALSE);
2246 
2247 	if (is_cols) {
2248 		start = r->start.col;
2249 		end = r->end.col;
2250 	} else {
2251 		start = r->start.row;
2252 		end = r->end.row;
2253 	}
2254 	start_cri = sheet_colrow_fetch (sheet, start, is_cols);
2255 	end_cri = sheet_colrow_fetch (sheet, end, is_cols);
2256 
2257 	/* Groups on outline level 0 (no outline) may always be formed */
2258 	if (start_cri->outline_level == 0 || end_cri->outline_level == 0)
2259 		return TRUE;
2260 
2261 	/* We just won't group a group that already exists (or doesn't), it's useless */
2262 	return (colrow_find_outline_bound (sheet, is_cols, start, start_cri->outline_level, FALSE) != start ||
2263 		colrow_find_outline_bound (sheet, is_cols, end, end_cri->outline_level, TRUE) != end);
2264 }
2265 
2266 gboolean
sheet_colrow_group_ungroup(Sheet * sheet,GnmRange const * r,gboolean is_cols,gboolean group)2267 sheet_colrow_group_ungroup (Sheet *sheet, GnmRange const *r,
2268 			    gboolean is_cols, gboolean group)
2269 {
2270 	int i, new_max, start, end;
2271 	int const step = group ? 1 : -1;
2272 
2273 	g_return_val_if_fail (IS_SHEET (sheet), FALSE);
2274 
2275 	/* Can we group/ungroup ? */
2276 	if (group != sheet_colrow_can_group (sheet, r, is_cols))
2277 		return FALSE;
2278 
2279 	if (is_cols) {
2280 		start = r->start.col;
2281 		end = r->end.col;
2282 	} else {
2283 		start = r->start.row;
2284 		end = r->end.row;
2285 	}
2286 
2287 	/* Set new outline for each col/row and find highest outline level */
2288 	new_max = (is_cols ? &sheet->cols : &sheet->rows)->max_outline_level;
2289 	for (i = start; i <= end; i++) {
2290 		ColRowInfo *cri = sheet_colrow_fetch (sheet, i, is_cols);
2291 		int const new_level = cri->outline_level + step;
2292 
2293 		if (new_level >= 0) {
2294 			col_row_info_set_outline (cri, new_level, FALSE);
2295 			if (new_max < new_level)
2296 				new_max = new_level;
2297 		}
2298 	}
2299 
2300 	if (!group)
2301 		new_max = sheet_colrow_fit_gutter (sheet, is_cols);
2302 
2303 	sheet_colrow_gutter (sheet, is_cols, new_max);
2304 	SHEET_FOREACH_VIEW (sheet, sv,
2305 		gnm_sheet_view_redraw_headers (sv, is_cols, !is_cols, NULL););
2306 
2307 	return TRUE;
2308 }
2309 
2310 /**
2311  * sheet_colrow_gutter:
2312  * @sheet:
2313  * @is_cols: %TRUE for columns, %FALSE for rows.
2314  * @max_outline:
2315  *
2316  * Set the maximum outline levels for cols or rows.
2317  */
2318 void
sheet_colrow_gutter(Sheet * sheet,gboolean is_cols,int max_outline)2319 sheet_colrow_gutter (Sheet *sheet, gboolean is_cols, int max_outline)
2320 {
2321 	ColRowCollection *infos;
2322 
2323 	g_return_if_fail (IS_SHEET (sheet));
2324 
2325 	infos = is_cols ? &(sheet->cols) : &(sheet->rows);
2326 	if (infos->max_outline_level != max_outline) {
2327 		sheet->priv->resize = TRUE;
2328 		infos->max_outline_level = max_outline;
2329 	}
2330 }
2331 
2332 struct sheet_extent_data {
2333 	GnmRange range;
2334 	gboolean spans_and_merges_extend;
2335 	gboolean ignore_empties;
2336 	gboolean include_hidden;
2337 };
2338 
2339 static void
cb_sheet_get_extent(G_GNUC_UNUSED gpointer ignored,gpointer value,gpointer data)2340 cb_sheet_get_extent (G_GNUC_UNUSED gpointer ignored, gpointer value, gpointer data)
2341 {
2342 	GnmCell const *cell = (GnmCell const *) value;
2343 	struct sheet_extent_data *res = data;
2344 	Sheet *sheet = cell->base.sheet;
2345 	ColRowInfo *ri = NULL;
2346 
2347 	if (res->ignore_empties && gnm_cell_is_empty (cell))
2348 		return;
2349 	if (!res->include_hidden) {
2350 		ri = sheet_col_get (sheet, cell->pos.col);
2351 		if (!ri->visible)
2352 			return;
2353 		ri = sheet_row_get (sheet, cell->pos.row);
2354 		if (!ri->visible)
2355 			return;
2356 	}
2357 
2358 	/* Remember the first cell is the min and max */
2359 	if (res->range.start.col > cell->pos.col)
2360 		res->range.start.col = cell->pos.col;
2361 	if (res->range.end.col < cell->pos.col)
2362 		res->range.end.col = cell->pos.col;
2363 	if (res->range.start.row > cell->pos.row)
2364 		res->range.start.row = cell->pos.row;
2365 	if (res->range.end.row < cell->pos.row)
2366 		res->range.end.row = cell->pos.row;
2367 
2368 	if (!res->spans_and_merges_extend)
2369 		return;
2370 
2371 	/* Cannot span AND merge */
2372 	if (gnm_cell_is_merged (cell)) {
2373 		GnmRange const *merged =
2374 			gnm_sheet_merge_is_corner (sheet, &cell->pos);
2375 		res->range = range_union (&res->range, merged);
2376 	} else {
2377 		CellSpanInfo const *span;
2378 		if (ri == NULL)
2379 			ri = sheet_row_get (sheet, cell->pos.row);
2380 		if (ri->needs_respan)
2381 			row_calc_spans (ri, cell->pos.row, sheet);
2382 		span = row_span_get (ri, cell->pos.col);
2383 		if (NULL != span) {
2384 			if (res->range.start.col > span->left)
2385 				res->range.start.col = span->left;
2386 			if (res->range.end.col < span->right)
2387 				res->range.end.col = span->right;
2388 		}
2389 	}
2390 }
2391 
2392 /**
2393  * sheet_get_extent:
2394  * @sheet: the sheet
2395  * @spans_and_merges_extend: optionally extend region for spans and merges.
2396  * @include_hidden: whether to include the content of hidden cells.
2397  *
2398  * calculates the area occupied by cell data.
2399  *
2400  * NOTE: When spans_and_merges_extend is %TRUE, this function will calculate
2401  * all spans.  That might be expensive.
2402  *
2403  * NOTE: This refers to *visible* contents.  Cells with empty values, including
2404  * formulas with such values, are *ignored.
2405  *
2406  * Return value: the range.
2407  **/
2408 GnmRange
sheet_get_extent(Sheet const * sheet,gboolean spans_and_merges_extend,gboolean include_hidden)2409 sheet_get_extent (Sheet const *sheet, gboolean spans_and_merges_extend, gboolean include_hidden)
2410 {
2411 	static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2412 	struct sheet_extent_data closure;
2413 	GSList *ptr;
2414 
2415 	g_return_val_if_fail (IS_SHEET (sheet), dummy);
2416 
2417 	closure.range.start.col = gnm_sheet_get_last_col (sheet) + 1;
2418 	closure.range.start.row = gnm_sheet_get_last_row (sheet) + 1;
2419 	closure.range.end.col   = 0;
2420 	closure.range.end.row   = 0;
2421 	closure.spans_and_merges_extend = spans_and_merges_extend;
2422 	closure.include_hidden = include_hidden;
2423 	closure.ignore_empties = TRUE;
2424 
2425 	sheet_cell_foreach (sheet, &cb_sheet_get_extent, &closure);
2426 
2427 	for (ptr = sheet->sheet_objects; ptr; ptr = ptr->next) {
2428 		SheetObject *so = GNM_SO (ptr->data);
2429 
2430 		closure.range.start.col = MIN (so->anchor.cell_bound.start.col,
2431 					       closure.range.start.col);
2432 		closure.range.start.row = MIN (so->anchor.cell_bound.start.row,
2433 					       closure.range.start.row);
2434 		closure.range.end.col = MAX (so->anchor.cell_bound.end.col,
2435 					     closure.range.end.col);
2436 		closure.range.end.row = MAX (so->anchor.cell_bound.end.row,
2437 					     closure.range.end.row);
2438 	}
2439 
2440 	if (closure.range.start.col > gnm_sheet_get_last_col (sheet))
2441 		closure.range.start.col = 0;
2442 	if (closure.range.start.row > gnm_sheet_get_last_row (sheet))
2443 		closure.range.start.row = 0;
2444 	if (closure.range.end.col < 0)
2445 		closure.range.end.col = 0;
2446 	if (closure.range.end.row < 0)
2447 		closure.range.end.row = 0;
2448 
2449 	return closure.range;
2450 }
2451 
2452 /**
2453  * sheet_get_cells_extent:
2454  * @sheet: the sheet
2455  *
2456  * Calculates the area occupied by cells, including empty cells.
2457  *
2458  * Return value: the range.
2459  **/
2460 GnmRange
sheet_get_cells_extent(Sheet const * sheet)2461 sheet_get_cells_extent (Sheet const *sheet)
2462 {
2463 	static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2464 	struct sheet_extent_data closure;
2465 
2466 	g_return_val_if_fail (IS_SHEET (sheet), dummy);
2467 
2468 	closure.range.start.col = gnm_sheet_get_last_col (sheet);
2469 	closure.range.start.row = gnm_sheet_get_last_row (sheet);
2470 	closure.range.end.col   = 0;
2471 	closure.range.end.row   = 0;
2472 	closure.spans_and_merges_extend = FALSE;
2473 	closure.include_hidden = TRUE;
2474 	closure.ignore_empties = FALSE;
2475 
2476 	sheet_cell_foreach (sheet, &cb_sheet_get_extent, &closure);
2477 
2478 	return closure.range;
2479 }
2480 
2481 
2482 GnmRange *
sheet_get_nominal_printarea(Sheet const * sheet)2483 sheet_get_nominal_printarea (Sheet const *sheet)
2484 {
2485 	GnmNamedExpr *nexpr;
2486 	GnmValue *val;
2487 	GnmParsePos pos;
2488 	GnmRange *r;
2489 	GnmRangeRef const *r_ref;
2490 	gint max_rows;
2491 	gint max_cols;
2492 
2493 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
2494 
2495 	parse_pos_init_sheet (&pos, sheet);
2496 	nexpr = expr_name_lookup (&pos, "Print_Area");
2497 	if (nexpr == NULL)
2498 		return NULL;
2499 
2500 	val = gnm_expr_top_get_range (nexpr->texpr);
2501 	r_ref = val ? value_get_rangeref (val) : NULL;
2502 	if (r_ref == NULL) {
2503 		value_release (val);
2504 		return NULL;
2505 	}
2506 
2507 	r = g_new0 (GnmRange, 1);
2508 	range_init_rangeref (r, r_ref);
2509 	value_release (val);
2510 
2511 	if (r->end.col >= (max_cols = gnm_sheet_get_max_cols (sheet)))
2512 		r->end.col = max_cols - 1;
2513 	if (r->end.row >= (max_rows = gnm_sheet_get_max_rows (sheet)))
2514 		r->end.row = max_rows - 1;
2515 	if (r->start.col < 0)
2516 		r->start.col = 0;
2517 	if (r->start.row < 0)
2518 		r->start.row = 0;
2519 
2520 	return r;
2521 }
2522 
2523 GnmRange
sheet_get_printarea(Sheet const * sheet,gboolean include_styles,gboolean ignore_printarea)2524 sheet_get_printarea (Sheet const *sheet,
2525 		     gboolean include_styles,
2526 		     gboolean ignore_printarea)
2527 {
2528 	static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2529 	GnmRange print_area;
2530 
2531 	g_return_val_if_fail (IS_SHEET (sheet), dummy);
2532 
2533 	if (!ignore_printarea) {
2534 		GnmRange *r = sheet_get_nominal_printarea (sheet);
2535 		if (r != NULL) {
2536 			print_area = *r;
2537 			g_free (r);
2538 			return print_area;
2539 		}
2540 	}
2541 
2542 	print_area = sheet_get_extent (sheet, TRUE, FALSE);
2543 	if (include_styles)
2544 		sheet_style_get_extent (sheet, &print_area);
2545 
2546 	return print_area;
2547 }
2548 
2549 struct cb_fit {
2550 	int max;
2551 	gboolean ignore_strings;
2552 };
2553 
2554 /* find the maximum width in a range.  */
2555 static GnmValue *
cb_max_cell_width(GnmCellIter const * iter,struct cb_fit * data)2556 cb_max_cell_width (GnmCellIter const *iter, struct cb_fit *data)
2557 {
2558 	int width;
2559 	GnmCell *cell = iter->cell;
2560 	GnmRenderedValue *rv;
2561 
2562 	if (gnm_cell_is_merged (cell))
2563 		return NULL;
2564 
2565 	/*
2566 	 * Special handling for manual recalc.  We need to eval newly
2567 	 * entered expressions.  gnm_cell_render_value will do that for us,
2568 	 * but we want to short-circuit some strings early.
2569 	 */
2570 	if (cell->base.flags & GNM_CELL_HAS_NEW_EXPR)
2571 		gnm_cell_eval (cell);
2572 
2573 	if (data->ignore_strings && VALUE_IS_STRING (cell->value))
2574 		return NULL;
2575 
2576 	/* Variable width cell must be re-rendered */
2577 	rv = gnm_cell_get_rendered_value (cell);
2578 	if (rv == NULL || rv->variable_width)
2579 		gnm_cell_render_value (cell, FALSE);
2580 
2581 	/* Make sure things are as-if drawn.  */
2582 	cell_finish_layout (cell, NULL, iter->ci->size_pixels, TRUE);
2583 
2584 	width = gnm_cell_rendered_width (cell) + gnm_cell_rendered_offset (cell);
2585 	if (width > data->max)
2586 		data->max = width;
2587 
2588 	return NULL;
2589 }
2590 
2591 /**
2592  * sheet_col_size_fit_pixels:
2593  * @sheet: The sheet
2594  * @col: the column that we want to query
2595  * @srow: starting row.
2596  * @erow: ending row.
2597  * @ignore_strings: skip cells containing string values.
2598  *
2599  * This routine computes the ideal size for the column to make the contents all
2600  * cells in the column visible.
2601  *
2602  * Returns: Maximum size in pixels INCLUDING margins and grid lines
2603  *          or 0 if there are no cells.
2604  **/
2605 int
sheet_col_size_fit_pixels(Sheet * sheet,int col,int srow,int erow,gboolean ignore_strings)2606 sheet_col_size_fit_pixels (Sheet *sheet, int col, int srow, int erow,
2607 			   gboolean ignore_strings)
2608 {
2609 	struct cb_fit data;
2610 	ColRowInfo *ci = sheet_col_get (sheet, col);
2611 	if (ci == NULL)
2612 		return 0;
2613 
2614 	data.max = -1;
2615 	data.ignore_strings = ignore_strings;
2616 	sheet_foreach_cell_in_region (sheet,
2617 		CELL_ITER_IGNORE_NONEXISTENT |
2618 		CELL_ITER_IGNORE_HIDDEN |
2619 		CELL_ITER_IGNORE_FILTERED,
2620 		col, srow, col, erow,
2621 		(CellIterFunc)&cb_max_cell_width, &data);
2622 
2623 	/* Reset to the default width if the column was empty */
2624 	if (data.max <= 0)
2625 		return 0;
2626 
2627 	/* GnmCell width does not include margins or far grid line*/
2628 	return data.max + GNM_COL_MARGIN + GNM_COL_MARGIN + 1;
2629 }
2630 
2631 /* find the maximum height in a range. */
2632 static GnmValue *
cb_max_cell_height(GnmCellIter const * iter,struct cb_fit * data)2633 cb_max_cell_height (GnmCellIter const *iter, struct cb_fit *data)
2634 {
2635 	int height;
2636 	GnmCell *cell = iter->cell;
2637 
2638 	if (gnm_cell_is_merged (cell))
2639 		return NULL;
2640 
2641 	/*
2642 	 * Special handling for manual recalc.  We need to eval newly
2643 	 * entered expressions.  gnm_cell_render_value will do that for us,
2644 	 * but we want to short-circuit some strings early.
2645 	 */
2646 	if (cell->base.flags & GNM_CELL_HAS_NEW_EXPR)
2647 		gnm_cell_eval (cell);
2648 
2649 	if (data->ignore_strings && VALUE_IS_STRING (cell->value))
2650 		return NULL;
2651 
2652 	if (!VALUE_IS_STRING (cell->value)) {
2653 		/*
2654 		 * Mildly cheating to avoid performance problems, See bug
2655 		 * 359392.  This assumes that non-strings do not wrap and
2656 		 * that they are all the same height, more or less.
2657 		 */
2658 		Sheet const *sheet = cell->base.sheet;
2659 		height =  gnm_style_get_pango_height (gnm_cell_get_effective_style (cell),
2660 						      sheet->rendered_values->context,
2661 						      sheet->last_zoom_factor_used);
2662 	} else {
2663 		(void)gnm_cell_fetch_rendered_value (cell, TRUE);
2664 
2665 		/* Make sure things are as-if drawn.  Inhibit #####s.  */
2666 		cell_finish_layout (cell, NULL, iter->ci->size_pixels, FALSE);
2667 
2668 		height = gnm_cell_rendered_height (cell);
2669 	}
2670 
2671 	if (height > data->max)
2672 		data->max = height;
2673 
2674 	return NULL;
2675 }
2676 
2677 /**
2678  * sheet_row_size_fit_pixels:
2679  * @sheet: The sheet
2680  * @row: the row that we want to query
2681  * @scol: starting column.
2682  * @ecol: ending column.
2683  * @ignore_strings: skip cells containing string values.
2684  *
2685  * This routine computes the ideal size for the row to make all data fit
2686  * properly.
2687  *
2688  * Returns: Maximum size in pixels INCLUDING margins and grid lines
2689  *          or 0 if there are no cells.
2690  **/
2691 int
sheet_row_size_fit_pixels(Sheet * sheet,int row,int scol,int ecol,gboolean ignore_strings)2692 sheet_row_size_fit_pixels (Sheet *sheet, int row, int scol, int ecol,
2693 			   gboolean ignore_strings)
2694 {
2695 	struct cb_fit data;
2696 	ColRowInfo const *ri = sheet_row_get (sheet, row);
2697 	if (ri == NULL)
2698 		return 0;
2699 
2700 	data.max = -1;
2701 	data.ignore_strings = ignore_strings;
2702 	sheet_foreach_cell_in_region (sheet,
2703 		CELL_ITER_IGNORE_NONEXISTENT |
2704 		CELL_ITER_IGNORE_HIDDEN |
2705 		CELL_ITER_IGNORE_FILTERED,
2706 		scol, row,
2707 		ecol, row,
2708 		(CellIterFunc)&cb_max_cell_height, &data);
2709 
2710 	/* Reset to the default width if the column was empty */
2711 	if (data.max <= 0)
2712 		return 0;
2713 
2714 	/* GnmCell height does not include margins or bottom grid line */
2715 	return data.max + GNM_ROW_MARGIN + GNM_ROW_MARGIN + 1;
2716 }
2717 
2718 struct recalc_span_closure {
2719 	Sheet *sheet;
2720 	int col;
2721 };
2722 
2723 static gboolean
cb_recalc_spans_in_col(GnmColRowIter const * iter,gpointer user)2724 cb_recalc_spans_in_col (GnmColRowIter const *iter, gpointer user)
2725 {
2726 	struct recalc_span_closure *closure = user;
2727 	int const col = closure->col;
2728 	int left, right;
2729 	CellSpanInfo const *span = row_span_get (iter->cri, col);
2730 
2731 	if (span) {
2732 		/* If there is an existing span see if it changed */
2733 		GnmCell const * const cell = span->cell;
2734 		cell_calc_span (cell, &left, &right);
2735 		if (left != span->left || right != span->right) {
2736 			cell_unregister_span (cell);
2737 			cell_register_span (cell, left, right);
2738 		}
2739 	} else {
2740 		/* If there is a cell see if it started to span */
2741 		GnmCell const * const cell = sheet_cell_get (closure->sheet, col, iter->pos);
2742 		if (cell) {
2743 			cell_calc_span (cell, &left, &right);
2744 			if (left != right)
2745 				cell_register_span (cell, left, right);
2746 		}
2747 	}
2748 
2749 	return FALSE;
2750 }
2751 
2752 /**
2753  * sheet_recompute_spans_for_col:
2754  * @sheet: the sheet
2755  * @col:   The column that changed
2756  *
2757  * This routine recomputes the column span for the cells that touches
2758  * the column.
2759  */
2760 void
sheet_recompute_spans_for_col(Sheet * sheet,int col)2761 sheet_recompute_spans_for_col (Sheet *sheet, int col)
2762 {
2763 	struct recalc_span_closure closure;
2764 	closure.sheet = sheet;
2765 	closure.col = col;
2766 
2767 	sheet_colrow_foreach (sheet, FALSE, 0, -1,
2768 			      &cb_recalc_spans_in_col, &closure);
2769 }
2770 
2771 /****************************************************************************/
2772 typedef struct {
2773 	GnmValue *val;
2774 	GnmExprTop const *texpr;
2775 	GnmRange expr_bound;
2776 } closure_set_cell_value;
2777 
2778 static GnmValue *
cb_set_cell_content(GnmCellIter const * iter,closure_set_cell_value * info)2779 cb_set_cell_content (GnmCellIter const *iter, closure_set_cell_value *info)
2780 {
2781 	GnmExprTop const *texpr = info->texpr;
2782 	GnmCell *cell;
2783 
2784 	cell = iter->cell;
2785 	if (!cell)
2786 		cell = sheet_cell_create (iter->pp.sheet,
2787 					  iter->pp.eval.col,
2788 					  iter->pp.eval.row);
2789 
2790 	/*
2791 	 * If we are overwriting an array, we need to clear things here
2792 	 * or gnm_cell_set_expr/gnm_cell_set_value will complain.
2793 	 */
2794 	if (cell->base.texpr && gnm_expr_top_is_array (cell->base.texpr))
2795 		gnm_cell_cleanout (cell);
2796 
2797 	if (texpr != NULL) {
2798 		if (!range_contains (&info->expr_bound,
2799 				     iter->pp.eval.col, iter->pp.eval.row)) {
2800 			GnmExprRelocateInfo rinfo;
2801 
2802 			rinfo.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE;
2803 			rinfo.pos = iter->pp;
2804 			rinfo.origin.start = iter->pp.eval;
2805 			rinfo.origin.end   = iter->pp.eval;
2806 			rinfo.origin_sheet = iter->pp.sheet;
2807 			rinfo.target_sheet = iter->pp.sheet;
2808 			rinfo.col_offset = 0;
2809 			rinfo.row_offset = 0;
2810 			texpr = gnm_expr_top_relocate (texpr, &rinfo, FALSE);
2811 		}
2812 
2813 		gnm_cell_set_expr (cell, texpr);
2814 	} else
2815 		gnm_cell_set_value (cell, value_dup (info->val));
2816 	return NULL;
2817 }
2818 
2819 static GnmValue *
cb_clear_non_corner(GnmCellIter const * iter,GnmRange const * merged)2820 cb_clear_non_corner (GnmCellIter const *iter, GnmRange const *merged)
2821 {
2822 	if (merged->start.col != iter->pp.eval.col ||
2823 	    merged->start.row != iter->pp.eval.row)
2824 		gnm_cell_set_value (iter->cell, value_new_empty ());
2825 	return NULL;
2826 }
2827 
2828 /**
2829  * sheet_range_set_expr_cb:
2830  * @sr: #GnmSheetRange
2831  * @texpr: #GnmExprTop
2832  *
2833  *
2834  * Does NOT check for array division.
2835  **/
2836 static void
sheet_range_set_expr_cb(GnmSheetRange const * sr,GnmExprTop const * texpr)2837 sheet_range_set_expr_cb (GnmSheetRange const *sr, GnmExprTop const *texpr)
2838 {
2839 	closure_set_cell_value	closure;
2840 	GSList *merged, *ptr;
2841 
2842 	g_return_if_fail (sr != NULL);
2843 	g_return_if_fail (texpr != NULL);
2844 
2845 	closure.texpr = texpr;
2846 	gnm_expr_top_get_boundingbox (closure.texpr,
2847 				      sr->sheet,
2848 				      &closure.expr_bound);
2849 
2850 	sheet_region_queue_recalc (sr->sheet, &sr->range);
2851 	/* Store the parsed result creating any cells necessary */
2852 	sheet_foreach_cell_in_range
2853 		(sr->sheet, CELL_ITER_ALL, &sr->range,
2854 		 (CellIterFunc)&cb_set_cell_content, &closure);
2855 
2856 	merged = gnm_sheet_merge_get_overlap (sr->sheet, &sr->range);
2857 	for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
2858 		GnmRange const *tmp = ptr->data;
2859 		sheet_foreach_cell_in_range
2860 			(sr->sheet, CELL_ITER_IGNORE_BLANK, tmp,
2861 			 (CellIterFunc)&cb_clear_non_corner,
2862 			 (gpointer)tmp);
2863 	}
2864 	g_slist_free (merged);
2865 
2866 	sheet_region_queue_recalc (sr->sheet, &sr->range);
2867 	sheet_flag_status_update_range (sr->sheet, &sr->range);
2868 	sheet_queue_respan (sr->sheet, sr->range.start.row,
2869 			    sr->range.end.row);
2870 }
2871 
2872 /**
2873  * sheet_range_set_expr_undo:
2874  * @sr: (transfer full): #GnmSheetRange
2875  * @texpr: (transfer none): #GnmExprTop
2876  *
2877  * Returns: (transfer full): the newly created #GOUndo.
2878  **/
2879 GOUndo *
sheet_range_set_expr_undo(GnmSheetRange * sr,GnmExprTop const * texpr)2880 sheet_range_set_expr_undo (GnmSheetRange *sr, GnmExprTop const  *texpr)
2881 {
2882 	gnm_expr_top_ref (texpr);
2883 	return go_undo_binary_new
2884 		(sr, (gpointer)texpr,
2885 		 (GOUndoBinaryFunc) sheet_range_set_expr_cb,
2886 		 (GFreeFunc) gnm_sheet_range_free,
2887 		 (GFreeFunc) gnm_expr_top_unref);
2888 }
2889 
2890 
2891 /**
2892  * sheet_range_set_text:
2893  * @pos: The position from which to parse an expression.
2894  * @r:  The range to fill
2895  * @str: The text to be parsed and assigned.
2896  *
2897  * Does NOT check for array division.
2898  * Does NOT redraw
2899  * Does NOT generate spans.
2900  **/
2901 void
sheet_range_set_text(GnmParsePos const * pos,GnmRange const * r,char const * str)2902 sheet_range_set_text (GnmParsePos const *pos, GnmRange const *r, char const *str)
2903 {
2904 	closure_set_cell_value	closure;
2905 	GSList *merged, *ptr;
2906 	Sheet *sheet;
2907 
2908 	g_return_if_fail (pos != NULL);
2909 	g_return_if_fail (r != NULL);
2910 	g_return_if_fail (str != NULL);
2911 
2912 	sheet = pos->sheet;
2913 
2914 	parse_text_value_or_expr (pos, str,
2915 				  &closure.val, &closure.texpr);
2916 
2917 	if (closure.texpr)
2918 		gnm_expr_top_get_boundingbox (closure.texpr,
2919 					      sheet,
2920 					      &closure.expr_bound);
2921 
2922 	/* Store the parsed result creating any cells necessary */
2923 	sheet_foreach_cell_in_range (sheet, CELL_ITER_ALL, r,
2924 		(CellIterFunc)&cb_set_cell_content, &closure);
2925 
2926 	merged = gnm_sheet_merge_get_overlap (sheet, r);
2927 	for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
2928 		GnmRange const *tmp = ptr->data;
2929 		sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_BLANK, tmp,
2930 			(CellIterFunc)&cb_clear_non_corner, (gpointer)tmp);
2931 	}
2932 	g_slist_free (merged);
2933 
2934 	sheet_region_queue_recalc (sheet, r);
2935 
2936 	value_release (closure.val);
2937 	if (closure.texpr)
2938 		gnm_expr_top_unref (closure.texpr);
2939 
2940 	sheet_flag_status_update_range (sheet, r);
2941 }
2942 
2943 static void
sheet_range_set_text_cb(GnmSheetRange const * sr,gchar const * text)2944 sheet_range_set_text_cb (GnmSheetRange const *sr, gchar const *text)
2945 {
2946 	GnmParsePos pos;
2947 
2948 	pos.eval = sr->range.start;
2949 	pos.sheet = sr->sheet;
2950 	pos.wb = sr->sheet->workbook;
2951 
2952 	sheet_range_set_text (&pos, &sr->range, text);
2953 	sheet_region_queue_recalc (sr->sheet, &sr->range);
2954 	sheet_flag_status_update_range (sr->sheet, &sr->range);
2955 	sheet_queue_respan (sr->sheet, sr->range.start.row,
2956 			    sr->range.end.row);
2957 	sheet_redraw_range (sr->sheet, &sr->range);
2958 }
2959 
2960 /**
2961  * sheet_range_set_text_undo:
2962  * @sr: (transfer full): #GnmSheetRange
2963  * @text: (transfer none): text for range
2964  *
2965  * Returns: (transfer full): the newly created #GOUndo.
2966  **/
2967 GOUndo *
sheet_range_set_text_undo(GnmSheetRange * sr,char const * text)2968 sheet_range_set_text_undo (GnmSheetRange *sr,
2969 			   char const *text)
2970 {
2971 	return go_undo_binary_new
2972 		(sr, g_strdup (text),
2973 		 (GOUndoBinaryFunc) sheet_range_set_text_cb,
2974 		 (GFreeFunc) gnm_sheet_range_free,
2975 		 (GFreeFunc) g_free);
2976 }
2977 
2978 
2979 static GnmValue *
cb_set_markup(GnmCellIter const * iter,PangoAttrList * markup)2980 cb_set_markup (GnmCellIter const *iter, PangoAttrList *markup)
2981 {
2982 	GnmCell *cell;
2983 
2984 	cell = iter->cell;
2985 	if (!cell)
2986 		return NULL;
2987 
2988 	if (VALUE_IS_STRING (cell->value)) {
2989 		GOFormat *fmt;
2990 		GnmValue *val = value_dup (cell->value);
2991 
2992 		fmt = go_format_new_markup (markup, TRUE);
2993 		value_set_fmt (val, fmt);
2994 		go_format_unref (fmt);
2995 
2996 		gnm_cell_cleanout (cell);
2997 		gnm_cell_assign_value (cell, val);
2998 	}
2999 	return NULL;
3000 }
3001 
3002 static void
sheet_range_set_markup_cb(GnmSheetRange const * sr,PangoAttrList * markup)3003 sheet_range_set_markup_cb (GnmSheetRange const *sr, PangoAttrList *markup)
3004 {
3005 	sheet_foreach_cell_in_range
3006 		(sr->sheet, CELL_ITER_ALL, &sr->range,
3007 		 (CellIterFunc)&cb_set_markup, markup);
3008 
3009 	sheet_region_queue_recalc (sr->sheet, &sr->range);
3010 	sheet_flag_status_update_range (sr->sheet, &sr->range);
3011 	sheet_queue_respan (sr->sheet, sr->range.start.row,
3012 			    sr->range.end.row);
3013 }
3014 
3015 /**
3016  * sheet_range_set_markup_undo:
3017  * @sr: (transfer full): #GnmSheetRange
3018  * @markup: (transfer none) (nullable): #PangoAttrList
3019  *
3020  * Returns: (transfer full) (nullable): the newly created #GOUndo.
3021  **/
3022 GOUndo *
sheet_range_set_markup_undo(GnmSheetRange * sr,PangoAttrList * markup)3023 sheet_range_set_markup_undo (GnmSheetRange *sr, PangoAttrList *markup)
3024 {
3025 	if (markup == NULL)
3026 		return NULL;
3027 	return go_undo_binary_new
3028 		(sr, pango_attr_list_ref (markup),
3029 		 (GOUndoBinaryFunc) sheet_range_set_markup_cb,
3030 		 (GFreeFunc) gnm_sheet_range_free,
3031 		 (GFreeFunc) pango_attr_list_unref);
3032 }
3033 
3034 /**
3035  * sheet_cell_get_value:
3036  * @sheet: Sheet
3037  * @col: Source column
3038  * @row: Source row
3039  *
3040  * Returns: (transfer none) (nullable): the cell's current value.  The return
3041  * value will be %NULL only when the cell does not exist.
3042  **/
3043 GnmValue const *
sheet_cell_get_value(Sheet * sheet,int const col,int const row)3044 sheet_cell_get_value (Sheet *sheet, int const col, int const row)
3045 {
3046 	GnmCell *cell;
3047 
3048 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
3049 
3050 	cell = sheet_cell_get (sheet, col, row);
3051 
3052 	return cell ? cell->value : NULL;
3053 }
3054 
3055 /**
3056  * sheet_cell_set_text:
3057  * @cell: A cell.
3058  * @str: the text to set.
3059  * @markup: (allow-none): an optional PangoAttrList.
3060  *
3061  * Marks the sheet as dirty
3062  * Clears old spans.
3063  * Flags status updates
3064  * Queues recalcs
3065  */
3066 void
sheet_cell_set_text(GnmCell * cell,char const * text,PangoAttrList * markup)3067 sheet_cell_set_text (GnmCell *cell, char const *text, PangoAttrList *markup)
3068 {
3069 	GnmExprTop const *texpr;
3070 	GnmValue *val;
3071 	GnmParsePos pp;
3072 
3073 	g_return_if_fail (cell != NULL);
3074 	g_return_if_fail (text != NULL);
3075 	g_return_if_fail (!gnm_cell_is_nonsingleton_array (cell));
3076 
3077 	parse_text_value_or_expr (parse_pos_init_cell (&pp, cell),
3078 		text, &val, &texpr);
3079 
3080 	/* Queue a redraw before in case the span changes */
3081 	sheet_redraw_cell (cell);
3082 
3083 	if (texpr != NULL) {
3084 		gnm_cell_set_expr (cell, texpr);
3085 		gnm_expr_top_unref (texpr);
3086 
3087 		/*
3088 		 * Queue recalc before spanning.  Otherwise spanning may
3089 		 * create a bogus rendered value, see #495879.
3090 		 */
3091 		cell_queue_recalc (cell);
3092 
3093 		/* Clear spans from _other_ cells */
3094 		sheet_cell_calc_span (cell, GNM_SPANCALC_SIMPLE);
3095 	} else {
3096 		g_return_if_fail (val != NULL);
3097 
3098 		if (markup != NULL && VALUE_IS_STRING (val)) {
3099 			gboolean quoted = (text[0] == '\'');
3100 			PangoAttrList *adj_markup;
3101 			GOFormat *fmt;
3102 
3103 			if (quoted) {
3104 				/* We ate the quote.  Adjust.  Ugh.  */
3105 				adj_markup = pango_attr_list_copy (markup);
3106 				go_pango_attr_list_erase (adj_markup, 0, 1);
3107 			} else
3108 				adj_markup = markup;
3109 
3110 			fmt = go_format_new_markup (adj_markup, TRUE);
3111 			value_set_fmt (val, fmt);
3112 			go_format_unref (fmt);
3113 			if (quoted)
3114 				pango_attr_list_unref (adj_markup);
3115 		}
3116 
3117 		gnm_cell_set_value (cell, val);
3118 
3119 		/* Queue recalc before spanning, see above.  */
3120 		cell_queue_recalc (cell);
3121 
3122 		sheet_cell_calc_span (cell, GNM_SPANCALC_RESIZE | GNM_SPANCALC_RENDER);
3123 	}
3124 
3125 	sheet_flag_status_update_cell (cell);
3126 }
3127 
3128 /**
3129  * sheet_cell_set_text_gi: (rename-to sheet_cell_set_text)
3130  * @sheet: #Sheet
3131  * @col: column number
3132  * @row: row number
3133  * @str: the text to set.
3134  *
3135  * Sets the contents of a cell.
3136  */
3137 void
sheet_cell_set_text_gi(Sheet * sheet,int col,int row,char const * str)3138 sheet_cell_set_text_gi (Sheet *sheet, int col, int row, char const *str)
3139 {
3140 	sheet_cell_set_text (sheet_cell_fetch (sheet, col, row), str, NULL);
3141 }
3142 
3143 
3144 /**
3145  * sheet_cell_set_expr:
3146  * @cell: #GnmCell
3147  * @texpr: New expression for @cell.
3148  *
3149  * Marks the sheet as dirty
3150  * Clears old spans.
3151  * Flags status updates
3152  * Queues recalcs
3153  */
3154 void
sheet_cell_set_expr(GnmCell * cell,GnmExprTop const * texpr)3155 sheet_cell_set_expr (GnmCell *cell, GnmExprTop const *texpr)
3156 {
3157 	gnm_cell_set_expr (cell, texpr);
3158 
3159 	/* clear spans from _other_ cells */
3160 	sheet_cell_calc_span (cell, GNM_SPANCALC_SIMPLE);
3161 
3162 	cell_queue_recalc (cell);
3163 	sheet_flag_status_update_cell (cell);
3164 }
3165 
3166 /**
3167  * sheet_cell_set_value: (skip)
3168  * @cell: #GnmCell
3169  * @v: (transfer full): #GnmValue
3170  *
3171  * Stores, without copying, the supplied value.  It marks the
3172  * sheet as dirty.
3173  *
3174  * The value is rendered and spans are calculated.  It queues a redraw
3175  * and checks to see if the edit region or selection content changed.
3176  *
3177  * NOTE : This DOES check for array partitioning.
3178  */
3179 void
sheet_cell_set_value(GnmCell * cell,GnmValue * v)3180 sheet_cell_set_value (GnmCell *cell, GnmValue *v)
3181 {
3182 	/* TODO : if the value is unchanged do not assign it */
3183 	gnm_cell_set_value (cell, v);
3184 	sheet_cell_calc_span (cell, GNM_SPANCALC_RESIZE | GNM_SPANCALC_RENDER);
3185 	cell_queue_recalc (cell);
3186 	sheet_flag_status_update_cell (cell);
3187 }
3188 
3189 /**
3190  * sheet_cell_set_value_gi: (rename-to sheet_cell_set_value)
3191  * @sheet: #Sheet
3192  * @col: column number
3193  * @row: row number
3194  * @v: #GnmValue
3195  *
3196  * Set the value of the cell at (@col,@row) to @v.
3197  *
3198  * The value is rendered and spans are calculated.  It queues a redraw
3199  * and checks to see if the edit region or selection content changed.
3200  */
3201 void
sheet_cell_set_value_gi(Sheet * sheet,int col,int row,GnmValue * v)3202 sheet_cell_set_value_gi (Sheet *sheet, int col, int row, GnmValue *v)
3203 {
3204 	// This version exists because not all versions of pygobject
3205 	// understand transfer-full parameters
3206 	sheet_cell_set_value (sheet_cell_fetch (sheet, col, row),
3207 			      value_dup (v));
3208 }
3209 
3210 /****************************************************************************/
3211 
3212 /*
3213  * This routine is used to queue the redraw regions for the
3214  * cell region specified.
3215  *
3216  * It is usually called before a change happens to a region,
3217  * and after the change has been done to queue the regions
3218  * for the old contents and the new contents.
3219  *
3220  * It intelligently handles spans and merged ranges
3221  */
3222 void
sheet_range_bounding_box(Sheet const * sheet,GnmRange * bound)3223 sheet_range_bounding_box (Sheet const *sheet, GnmRange *bound)
3224 {
3225 	GSList *ptr;
3226 	int row;
3227 	GnmRange r = *bound;
3228 
3229 	g_return_if_fail (range_is_sane	(bound));
3230 
3231 	/* Check the first and last columns for spans and extend the region to
3232 	 * include the maximum extent.
3233 	 */
3234 	for (row = r.start.row; row <= r.end.row; row++){
3235 		ColRowInfo const *ri = sheet_row_get (sheet, row);
3236 
3237 		if (ri != NULL) {
3238 			CellSpanInfo const * span0;
3239 
3240 			if (ri->needs_respan)
3241 				row_calc_spans ((ColRowInfo *)ri, row, sheet);
3242 
3243 			span0 = row_span_get (ri, r.start.col);
3244 
3245 			if (span0 != NULL) {
3246 				if (bound->start.col > span0->left)
3247 					bound->start.col = span0->left;
3248 				if (bound->end.col < span0->right)
3249 					bound->end.col = span0->right;
3250 			}
3251 			if (r.start.col != r.end.col) {
3252 				CellSpanInfo const * span1 =
3253 					row_span_get (ri, r.end.col);
3254 
3255 				if (span1 != NULL) {
3256 					if (bound->start.col > span1->left)
3257 						bound->start.col = span1->left;
3258 					if (bound->end.col < span1->right)
3259 						bound->end.col = span1->right;
3260 				}
3261 			}
3262 			/* skip segments with no cells */
3263 		} else if (row == COLROW_SEGMENT_START (row)) {
3264 			ColRowSegment const * const segment =
3265 				COLROW_GET_SEGMENT (&(sheet->rows), row);
3266 			if (segment == NULL)
3267 				row = COLROW_SEGMENT_END (row);
3268 		}
3269 	}
3270 
3271 	/* TODO : this may get expensive if there are alot of merged ranges */
3272 	/* no need to iterate, one pass is enough */
3273 	for (ptr = sheet->list_merged ; ptr != NULL ; ptr = ptr->next) {
3274 		GnmRange const * const test = ptr->data;
3275 		if (r.start.row <= test->end.row || r.end.row >= test->start.row) {
3276 			if (bound->start.col > test->start.col)
3277 				bound->start.col = test->start.col;
3278 			if (bound->end.col < test->end.col)
3279 				bound->end.col = test->end.col;
3280 			if (bound->start.row > test->start.row)
3281 				bound->start.row = test->start.row;
3282 			if (bound->end.row < test->end.row)
3283 				bound->end.row = test->end.row;
3284 		}
3285 	}
3286 }
3287 
3288 void
sheet_redraw_region(Sheet const * sheet,int start_col,int start_row,int end_col,int end_row)3289 sheet_redraw_region (Sheet const *sheet,
3290 		     int start_col, int start_row,
3291 		     int end_col,   int end_row)
3292 {
3293 	GnmRange r;
3294 	g_return_if_fail (IS_SHEET (sheet));
3295 
3296 	range_init (&r, start_col, start_row, end_col, end_row);
3297 	sheet_redraw_range (sheet, &r);
3298 }
3299 
3300 
3301 /**
3302  * sheet_redraw_range:
3303  * @sheet: sheet to redraw
3304  * @range: range to redraw
3305  *
3306  * Redraw the indicated range, or at least the visible parts of it.
3307  */
3308 void
sheet_redraw_range(Sheet const * sheet,GnmRange const * range)3309 sheet_redraw_range (Sheet const *sheet, GnmRange const *range)
3310 {
3311 	GnmRange bound;
3312 
3313 	g_return_if_fail (IS_SHEET (sheet));
3314 	g_return_if_fail (range != NULL);
3315 
3316 	// We potentially do a lot of recalcs as part of this, so make sure
3317 	// stuff that caches sub-computations see the whole thing instead
3318 	// of clearing between cells.
3319 	gnm_app_recalc_start ();
3320 
3321 	bound = *range;
3322 	sheet_range_bounding_box (sheet, &bound);
3323 
3324 	SHEET_FOREACH_CONTROL (sheet, view, control,
3325 		sc_redraw_range (control, &bound););
3326 
3327 	gnm_app_recalc_finish ();
3328 }
3329 
3330 static gboolean
cb_pending_redraw_handler(Sheet * sheet)3331 cb_pending_redraw_handler (Sheet *sheet)
3332 {
3333 	unsigned ui, len;
3334 	GArray *arr = sheet->pending_redraw;
3335 
3336 	// It's possible that more redraws will arrive as we process these
3337 	// so be careful only to touch the right ones.
3338 
3339 	if (debug_redraw)
3340 		g_printerr ("Entering redraw with %u ranges\n", arr->len);
3341 	if (arr->len >= 2) {
3342 		gnm_range_simplify (arr);
3343 		if (debug_redraw)
3344 			g_printerr ("Down to %u ranges\n", arr->len);
3345 	}
3346 
3347 	// Lock down the length we handle here
3348 	len = arr->len;
3349 	for (ui = 0; ui < len; ui++) {
3350 		GnmRange const *r = &g_array_index (arr, GnmRange, ui);
3351 		if (debug_redraw)
3352 			g_printerr ("Redrawing %s\n", range_as_string (r));
3353 		sheet_redraw_range (sheet, r);
3354 	}
3355 	g_array_remove_range (arr, 0, len);
3356 
3357 	if (arr->len == 0) {
3358 		sheet->pending_redraw_src = 0;
3359 		return FALSE;
3360 	} else
3361 		return TRUE;
3362 }
3363 
3364 /**
3365  * sheet_queue_redraw_range:
3366  * @sheet: sheet to redraw
3367  * @range: range to redraw
3368  *
3369  * This queues a redraw for the indicated range.  The redraw will happen
3370  * when Gnumeric returns to the gui main loop.
3371  */
3372 void
sheet_queue_redraw_range(Sheet * sheet,GnmRange const * range)3373 sheet_queue_redraw_range (Sheet *sheet, GnmRange const *range)
3374 {
3375 	g_return_if_fail (IS_SHEET (sheet));
3376 	g_return_if_fail (range != NULL);
3377 
3378 	if (sheet->workbook->being_loaded) {
3379 		if (debug_redraw)
3380 			g_printerr ("Ignoring redraw of %s during loading\n", range_as_string (range));
3381 		return;
3382 	}
3383 
3384 	if (debug_redraw)
3385 		g_printerr ("Adding redraw %s\n", range_as_string (range));
3386 
3387 	g_array_append_val (sheet->pending_redraw, *range);
3388 
3389 	if (sheet->pending_redraw_src == 0)
3390 		sheet->pending_redraw_src =
3391 			g_timeout_add (0,
3392 				       (GSourceFunc)cb_pending_redraw_handler,
3393 				       sheet);
3394 }
3395 
3396 
3397 /****************************************************************************/
3398 
3399 gboolean
sheet_col_is_hidden(Sheet const * sheet,int col)3400 sheet_col_is_hidden (Sheet const *sheet, int col)
3401 {
3402 	ColRowInfo const * const res = sheet_col_get (sheet, col);
3403 	return (res != NULL && !res->visible);
3404 }
3405 
3406 gboolean
sheet_row_is_hidden(Sheet const * sheet,int row)3407 sheet_row_is_hidden (Sheet const *sheet, int row)
3408 {
3409 	ColRowInfo const * const res = sheet_row_get (sheet, row);
3410 	return (res != NULL && !res->visible);
3411 }
3412 
3413 
3414 /**
3415  * sheet_find_boundary_horizontal:
3416  * @sheet: The Sheet
3417  * @col: The column from which to begin searching.
3418  * @move_row: The row in which to search for the edge of the range.
3419  * @base_row: The height of the area being moved.
3420  * @count:      units to extend the selection vertically
3421  * @jump_to_boundaries: Jump to range boundaries.
3422  *
3423  * Calculate the column index for the column which is @count units
3424  * from @start_col doing bounds checking.  If @jump_to_boundaries is
3425  * %TRUE then @count must be 1 and the jump is to the edge of the logical range.
3426  *
3427  * This routine implements the logic necessary for ctrl-arrow style
3428  * movement.  That is more complicated than simply finding the last in a list
3429  * of cells with content.  If you are at the end of a range it will find the
3430  * start of the next.  Make sure that is the sort of behavior you want before
3431  * calling this.
3432  *
3433  * Returns: the column index.
3434  **/
3435 int
sheet_find_boundary_horizontal(Sheet * sheet,int start_col,int move_row,int base_row,int count,gboolean jump_to_boundaries)3436 sheet_find_boundary_horizontal (Sheet *sheet, int start_col, int move_row,
3437 				int base_row, int count,
3438 				gboolean jump_to_boundaries)
3439 {
3440 	gboolean find_nonblank = sheet_is_cell_empty (sheet, start_col, move_row);
3441 	gboolean keep_looking = FALSE;
3442 	int new_col, prev_col, lagged_start_col, max_col = gnm_sheet_get_last_col (sheet);
3443 	int iterations = 0;
3444 	GnmRange check_merge;
3445 	GnmRange const * const bound = &sheet->priv->unhidden_region;
3446 
3447 	/* Jumping to bounds requires steping cell by cell */
3448 	g_return_val_if_fail (count == 1 || count == -1 || !jump_to_boundaries, start_col);
3449 	g_return_val_if_fail (IS_SHEET (sheet), start_col);
3450 
3451 	if (move_row < base_row) {
3452 		check_merge.start.row = move_row;
3453 		check_merge.end.row = base_row;
3454 	} else {
3455 		check_merge.end.row = move_row;
3456 		check_merge.start.row = base_row;
3457 	}
3458 
3459 	do {
3460 		GSList *merged, *ptr;
3461 
3462 		lagged_start_col = check_merge.start.col = check_merge.end.col = start_col;
3463 		merged = gnm_sheet_merge_get_overlap (sheet, &check_merge);
3464 		for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3465 			GnmRange const * const r = ptr->data;
3466 			if (count > 0) {
3467 				if (start_col < r->end.col)
3468 					start_col = r->end.col;
3469 			} else {
3470 				if (start_col > r->start.col)
3471 					start_col = r->start.col;
3472 			}
3473 		}
3474 		g_slist_free (merged);
3475 	} while (start_col != lagged_start_col);
3476 	new_col = prev_col = start_col;
3477 
3478 	do {
3479 		new_col += count;
3480 		++iterations;
3481 
3482 		if (new_col < bound->start.col)
3483 			return MIN (bound->start.col, max_col);
3484 		if (new_col > bound->end.col)
3485 			return MIN (bound->end.col, max_col);
3486 
3487 		keep_looking = sheet_col_is_hidden (sheet, new_col);
3488 		if (jump_to_boundaries) {
3489 			if (new_col > sheet->cols.max_used) {
3490 				if (count > 0)
3491 					return (find_nonblank || iterations == 1)?
3492 						MIN (bound->end.col, max_col):
3493 						MIN (prev_col, max_col);
3494 				new_col = sheet->cols.max_used;
3495 			}
3496 
3497 			keep_looking |= (sheet_is_cell_empty (sheet, new_col, move_row) == find_nonblank);
3498 			if (keep_looking)
3499 				prev_col = new_col;
3500 			else if (!find_nonblank) {
3501 				/*
3502 				 * Handle special case where we are on the last
3503 				 * non-NULL cell
3504 				 */
3505 				if (iterations == 1)
3506 					keep_looking = find_nonblank = TRUE;
3507 				else
3508 					new_col = prev_col;
3509 			}
3510 		}
3511 	} while (keep_looking);
3512 
3513 	return MIN (new_col, max_col);
3514 }
3515 
3516 /**
3517  * sheet_find_boundary_vertical:
3518  * @sheet: The Sheet
3519  * @move_col: The col in which to search for the edge of the range.
3520  * @row: The row from which to begin searching.
3521  * @base_col: The width of the area being moved.
3522  * @count: units to extend the selection vertically
3523  * @jump_to_boundaries: Jump to range boundaries.
3524  *
3525  * Calculate the row index for the row which is @count units
3526  * from @start_row doing bounds checking.  If @jump_to_boundaries is
3527  * %TRUE then @count must be 1 and the jump is to the edge of the logical range.
3528  *
3529  * This routine implements the logic necessary for ctrl-arrow style
3530  * movement.  That is more complicated than simply finding the last in a list
3531  * of cells with content.  If you are at the end of a range it will find the
3532  * start of the next.  Make sure that is the sort of behavior you want before
3533  * calling this.
3534  *
3535  * Returns: the row index.
3536  **/
3537 int
sheet_find_boundary_vertical(Sheet * sheet,int move_col,int start_row,int base_col,int count,gboolean jump_to_boundaries)3538 sheet_find_boundary_vertical (Sheet *sheet, int move_col, int start_row,
3539 			      int base_col, int count,
3540 			      gboolean jump_to_boundaries)
3541 {
3542 	gboolean find_nonblank = sheet_is_cell_empty (sheet, move_col, start_row);
3543 	gboolean keep_looking = FALSE;
3544 	int new_row, prev_row, lagged_start_row, max_row = gnm_sheet_get_last_row (sheet);
3545 	int iterations = 0;
3546 	GnmRange check_merge;
3547 	GnmRange const * const bound = &sheet->priv->unhidden_region;
3548 
3549 	/* Jumping to bounds requires steping cell by cell */
3550 	g_return_val_if_fail (count == 1 || count == -1 || !jump_to_boundaries, start_row);
3551 	g_return_val_if_fail (IS_SHEET (sheet), start_row);
3552 
3553 	if (move_col < base_col) {
3554 		check_merge.start.col = move_col;
3555 		check_merge.end.col = base_col;
3556 	} else {
3557 		check_merge.end.col = move_col;
3558 		check_merge.start.col = base_col;
3559 	}
3560 
3561 	do {
3562 		GSList *merged, *ptr;
3563 
3564 		lagged_start_row = check_merge.start.row = check_merge.end.row = start_row;
3565 		merged = gnm_sheet_merge_get_overlap (sheet, &check_merge);
3566 		for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3567 			GnmRange const * const r = ptr->data;
3568 			if (count > 0) {
3569 				if (start_row < r->end.row)
3570 					start_row = r->end.row;
3571 			} else {
3572 				if (start_row > r->start.row)
3573 					start_row = r->start.row;
3574 			}
3575 		}
3576 		g_slist_free (merged);
3577 	} while (start_row != lagged_start_row);
3578 	new_row = prev_row = start_row;
3579 
3580 	do {
3581 		new_row += count;
3582 		++iterations;
3583 
3584 		if (new_row < bound->start.row)
3585 			return MIN (bound->start.row, max_row);
3586 		if (new_row > bound->end.row)
3587 			return MIN (bound->end.row, max_row);
3588 
3589 		keep_looking = sheet_row_is_hidden (sheet, new_row);
3590 		if (jump_to_boundaries) {
3591 			if (new_row > sheet->rows.max_used) {
3592 				if (count > 0)
3593 					return (find_nonblank || iterations == 1)?
3594 						MIN (bound->end.row, max_row):
3595 						MIN (prev_row, max_row);
3596 				new_row = sheet->rows.max_used;
3597 			}
3598 
3599 			keep_looking |= (sheet_is_cell_empty (sheet, move_col, new_row) == find_nonblank);
3600 			if (keep_looking)
3601 				prev_row = new_row;
3602 			else if (!find_nonblank) {
3603 				/*
3604 				 * Handle special case where we are on the last
3605 				 * non-NULL cell
3606 				 */
3607 				if (iterations == 1)
3608 					keep_looking = find_nonblank = TRUE;
3609 				else
3610 					new_row = prev_row;
3611 			}
3612 		}
3613 	} while (keep_looking);
3614 
3615 	return MIN (new_row, max_row);
3616 }
3617 
3618 typedef enum {
3619 	CHECK_AND_LOAD_START = 1,
3620 	CHECK_END = 2,
3621 	LOAD_END  = 4
3622 } ArrayCheckFlags;
3623 
3624 typedef struct {
3625 	Sheet const *sheet;
3626 	int flags;
3627 	int start, end;
3628 	GnmRange const *ignore;
3629 
3630 	GnmRange	error;
3631 } ArrayCheckData;
3632 
3633 static gboolean
cb_check_array_horizontal(GnmColRowIter const * iter,gpointer data_)3634 cb_check_array_horizontal (GnmColRowIter const *iter, gpointer data_)
3635 {
3636 	ArrayCheckData *data = data_;
3637 	gboolean is_array = FALSE;
3638 
3639 	if (data->flags & CHECK_AND_LOAD_START  &&	/* Top */
3640 	    (is_array = gnm_cell_array_bound (
3641 			sheet_cell_get (data->sheet, iter->pos, data->start),
3642 			&data->error)) &&
3643 	    data->error.start.row < data->start &&
3644 	    (data->ignore == NULL ||
3645 	     !range_contained (&data->error, data->ignore)))
3646 	    return TRUE;
3647 
3648 	if (data->flags & LOAD_END)
3649 		is_array = gnm_cell_array_bound (
3650 			sheet_cell_get (data->sheet, iter->pos, data->end),
3651 			&data->error);
3652 
3653 	return (data->flags & CHECK_END &&
3654 		is_array &&
3655 		data->error.end.row > data->end &&	/* Bottom */
3656 		(data->ignore == NULL ||
3657 		 !range_contained (&data->error, data->ignore)));
3658 }
3659 
3660 static gboolean
cb_check_array_vertical(GnmColRowIter const * iter,gpointer data_)3661 cb_check_array_vertical (GnmColRowIter const *iter, gpointer data_)
3662 {
3663 	ArrayCheckData *data = data_;
3664 	gboolean is_array = FALSE;
3665 
3666 	if (data->flags & CHECK_AND_LOAD_START &&	/* Left */
3667 	    (is_array = gnm_cell_array_bound (
3668 			sheet_cell_get (data->sheet, data->start, iter->pos),
3669 			&data->error)) &&
3670 	    data->error.start.col < data->start &&
3671 	    (data->ignore == NULL ||
3672 	     !range_contained (&data->error, data->ignore)))
3673 	    return TRUE;
3674 
3675 	if (data->flags & LOAD_END)
3676 		is_array = gnm_cell_array_bound (
3677 			sheet_cell_get (data->sheet, data->end, iter->pos),
3678 			&data->error);
3679 
3680 	return (data->flags & CHECK_END &&
3681 		is_array &&
3682 		data->error.end.col > data->end &&	/* Right */
3683 		(data->ignore == NULL ||
3684 		 !range_contained (&data->error, data->ignore)));
3685 }
3686 
3687 /**
3688  * sheet_range_splits_array:
3689  * @sheet: The sheet.
3690  * @r: The range to check
3691  * @ignore: (nullable): a range in which it is ok to have an array.
3692  * @cc: (nullable): place to report an error.
3693  * @cmd: (nullable): cmd name used with @cc.
3694  *
3695  * Check the outer edges of range @sheet!@r to ensure that if an array is
3696  * within it then the entire array is within the range.  @ignore is useful when
3697  * src and dest ranges may overlap.
3698  *
3699  * Returns: %TRUE if an array would be split.
3700  **/
3701 gboolean
sheet_range_splits_array(Sheet const * sheet,GnmRange const * r,GnmRange const * ignore,GOCmdContext * cc,char const * cmd)3702 sheet_range_splits_array (Sheet const *sheet,
3703 			  GnmRange const *r, GnmRange const *ignore,
3704 			  GOCmdContext *cc, char const *cmd)
3705 {
3706 	ArrayCheckData closure;
3707 
3708 	g_return_val_if_fail (r->start.col <= r->end.col, FALSE);
3709 	g_return_val_if_fail (r->start.row <= r->end.row, FALSE);
3710 
3711 	closure.sheet = sheet;
3712 	closure.ignore = ignore;
3713 
3714 	closure.start = r->start.row;
3715 	closure.end = r->end.row;
3716 	if (closure.start <= 0) {
3717 		closure.flags = (closure.end < sheet->rows.max_used)
3718 			? CHECK_END | LOAD_END
3719 			: 0;
3720 	} else if (closure.end < sheet->rows.max_used)
3721 		closure.flags = (closure.start == closure.end)
3722 			? CHECK_AND_LOAD_START | CHECK_END
3723 			: CHECK_AND_LOAD_START | CHECK_END | LOAD_END;
3724 	else
3725 		closure.flags = CHECK_AND_LOAD_START;
3726 
3727 	if (closure.flags &&
3728 	    sheet_colrow_foreach (sheet, TRUE,
3729 				  r->start.col, r->end.col,
3730 				  cb_check_array_horizontal, &closure)) {
3731 		if (cc)
3732 			gnm_cmd_context_error_splits_array (cc,
3733 				cmd, &closure.error);
3734 		return TRUE;
3735 	}
3736 
3737 	closure.start = r->start.col;
3738 	closure.end = r->end.col;
3739 	if (closure.start <= 0) {
3740 		closure.flags = (closure.end < sheet->cols.max_used)
3741 			? CHECK_END | LOAD_END
3742 			: 0;
3743 	} else if (closure.end < sheet->cols.max_used)
3744 		closure.flags = (closure.start == closure.end)
3745 			? CHECK_AND_LOAD_START | CHECK_END
3746 			: CHECK_AND_LOAD_START | CHECK_END | LOAD_END;
3747 	else
3748 		closure.flags = CHECK_AND_LOAD_START;
3749 
3750 	if (closure.flags &&
3751 	    sheet_colrow_foreach (sheet, FALSE,
3752 				  r->start.row, r->end.row,
3753 				  cb_check_array_vertical, &closure)) {
3754 		if (cc)
3755 			gnm_cmd_context_error_splits_array (cc,
3756 				cmd, &closure.error);
3757 		return TRUE;
3758 	}
3759 	return FALSE;
3760 }
3761 
3762 /**
3763  * sheet_range_splits_region:
3764  * @sheet: the sheet.
3765  * @r: The range whose boundaries are checked
3766  * @ignore: An optional range in which it is ok to have arrays and merges
3767  * @cc: The context that issued the command
3768  * @cmd: The translated command name.
3769  *
3770  * A utility to see whether moving the range @r will split any arrays
3771  * or merged regions.
3772  * Returns: whether any arrays or merged regions will be split.
3773  */
3774 gboolean
sheet_range_splits_region(Sheet const * sheet,GnmRange const * r,GnmRange const * ignore,GOCmdContext * cc,char const * cmd_name)3775 sheet_range_splits_region (Sheet const *sheet,
3776 			   GnmRange const *r, GnmRange const *ignore,
3777 			   GOCmdContext *cc, char const *cmd_name)
3778 {
3779 	GSList *merged;
3780 
3781 	g_return_val_if_fail (IS_SHEET (sheet), FALSE);
3782 
3783 	/* Check for array subdivision */
3784 	if (sheet_range_splits_array (sheet, r, ignore, cc, cmd_name))
3785 		return TRUE;
3786 
3787 	merged = gnm_sheet_merge_get_overlap (sheet, r);
3788 	if (merged) {
3789 		GSList *ptr;
3790 
3791 		for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3792 			GnmRange const *m = ptr->data;
3793 			if (ignore != NULL && range_contained (m, ignore))
3794 				continue;
3795 			if (!range_contained (m, r))
3796 				break;
3797 		}
3798 		g_slist_free (merged);
3799 
3800 		if (cc != NULL && ptr != NULL) {
3801 			go_cmd_context_error_invalid (cc, cmd_name,
3802 				_("Target region contains merged cells"));
3803 			return TRUE;
3804 		}
3805 	}
3806 	return FALSE;
3807 }
3808 
3809 /**
3810  * sheet_ranges_split_region:
3811  * @sheet: the sheet.
3812  * @ranges: (element-type GnmRange): A list of ranges to check.
3813  * @cc: The context that issued the command
3814  * @cmd: The translated command name.
3815  *
3816  * A utility to see whether moving any of the ranges @ranges will split any
3817  * arrays or merged regions.
3818  * Returns: whether any arrays or merged regions will be split.
3819  */
3820 gboolean
sheet_ranges_split_region(Sheet const * sheet,GSList const * ranges,GOCmdContext * cc,char const * cmd)3821 sheet_ranges_split_region (Sheet const * sheet, GSList const *ranges,
3822 			   GOCmdContext *cc, char const *cmd)
3823 {
3824 	GSList const *l;
3825 
3826 	/* Check for array subdivision */
3827 	for (l = ranges; l != NULL; l = l->next) {
3828 		GnmRange const *r = l->data;
3829 		if (sheet_range_splits_region (sheet, r, NULL, cc, cmd))
3830 			return TRUE;
3831 	}
3832 	return FALSE;
3833 }
3834 
3835 static GnmValue *
cb_cell_is_array(GnmCellIter const * iter,G_GNUC_UNUSED gpointer user)3836 cb_cell_is_array (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
3837 {
3838 	return gnm_cell_is_array (iter->cell) ? VALUE_TERMINATE : NULL;
3839 }
3840 
3841 /**
3842  * sheet_range_contains_merges_or_arrays:
3843  * @sheet: The sheet
3844  * @r: the range to check.
3845  * @cc: an optional place to report errors.
3846  * @cmd:
3847  * @merges: if %TRUE, check for merges.
3848  * @arrays: if %TRUE, check for arrays.
3849  *
3850  * Check to see if the target region @sheet!@r contains any merged regions or
3851  * arrays.  Report an error to the @cc if it is supplied.
3852  * Returns: %TRUE if the target region @sheet!@r contains any merged regions or
3853  * arrays.
3854  **/
3855 gboolean
sheet_range_contains_merges_or_arrays(Sheet const * sheet,GnmRange const * r,GOCmdContext * cc,char const * cmd,gboolean merges,gboolean arrays)3856 sheet_range_contains_merges_or_arrays (Sheet const *sheet, GnmRange const *r,
3857 				       GOCmdContext *cc, char const *cmd,
3858 				       gboolean merges, gboolean arrays)
3859 {
3860 	g_return_val_if_fail (IS_SHEET (sheet), FALSE);
3861 
3862 	if (merges) {
3863 		GSList *merged = gnm_sheet_merge_get_overlap (sheet, r);
3864 		if (merged != NULL) {
3865 			if (cc != NULL)
3866 				go_cmd_context_error_invalid
3867 					(cc, cmd,
3868 					 _("cannot operate on merged cells"));
3869 			g_slist_free (merged);
3870 			return TRUE;
3871 		}
3872 	}
3873 
3874 	if (arrays) {
3875 		if (sheet_foreach_cell_in_range (
3876 			    (Sheet *)sheet, CELL_ITER_IGNORE_NONEXISTENT, r,
3877 			    cb_cell_is_array, NULL)) {
3878 			if (cc != NULL)
3879 				go_cmd_context_error_invalid
3880 					(cc, cmd,
3881 					 _("cannot operate on array formul\303\246"));
3882 			return TRUE;
3883 		}
3884 	}
3885 
3886 	return FALSE;
3887 }
3888 
3889 /***************************************************************************/
3890 
3891 /**
3892  * sheet_colrow_get_default:
3893  * @sheet:
3894  * @is_cols: %TRUE for columns, %FALSE for rows.
3895  *
3896  * Returns: (transfer none): the default #ColRowInfo.
3897  */
3898 ColRowInfo const *
sheet_colrow_get_default(Sheet const * sheet,gboolean is_cols)3899 sheet_colrow_get_default (Sheet const *sheet, gboolean is_cols)
3900 {
3901 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
3902 
3903 	return is_cols ? &sheet->cols.default_style : &sheet->rows.default_style;
3904 }
3905 
3906 static void
sheet_colrow_optimize1(int max,int max_used,ColRowCollection * collection)3907 sheet_colrow_optimize1 (int max, int max_used, ColRowCollection *collection)
3908 {
3909 	int i;
3910 	int first_unused = max_used + 1;
3911 
3912 	for (i = COLROW_SEGMENT_START (first_unused);
3913 	     i < max;
3914 	     i += COLROW_SEGMENT_SIZE) {
3915 		ColRowSegment *segment = COLROW_GET_SEGMENT (collection, i);
3916 		int j;
3917 		gboolean any = FALSE;
3918 
3919 		if (!segment)
3920 			continue;
3921 		for (j = 0; j < COLROW_SEGMENT_SIZE; j++) {
3922 			ColRowInfo *info = segment->info[j];
3923 			if (!info)
3924 				continue;
3925 			if (i + j >= first_unused &&
3926 			    col_row_info_equal (&collection->default_style, info)) {
3927 				colrow_free (info);
3928 				segment->info[j] = NULL;
3929 			} else {
3930 				any = TRUE;
3931 				if (i + j >= first_unused)
3932 					max_used = i + j;
3933 			}
3934 		}
3935 
3936 		if (!any) {
3937 			g_free (segment);
3938 			COLROW_GET_SEGMENT (collection, i) = NULL;
3939 		}
3940 	}
3941 
3942 	collection->max_used = max_used;
3943 }
3944 
3945 void
sheet_colrow_optimize(Sheet * sheet)3946 sheet_colrow_optimize (Sheet *sheet)
3947 {
3948 	GnmRange extent;
3949 
3950 	g_return_if_fail (IS_SHEET (sheet));
3951 
3952 	extent = sheet_get_cells_extent (sheet);
3953 
3954 	sheet_colrow_optimize1 (gnm_sheet_get_max_cols (sheet),
3955 				extent.end.col,
3956 				&sheet->cols);
3957 	sheet_colrow_optimize1 (gnm_sheet_get_max_rows (sheet),
3958 				extent.end.row,
3959 				&sheet->rows);
3960 }
3961 
3962 /**
3963  * sheet_col_get:
3964  * @col: column number
3965  *
3966  * Returns: (transfer none) (nullable): A #ColRowInfo for the column, or %NULL
3967  * if none has been allocated yet.
3968  */
3969 ColRowInfo *
sheet_col_get(Sheet const * sheet,int col)3970 sheet_col_get (Sheet const *sheet, int col)
3971 {
3972 	ColRowSegment *segment;
3973 
3974 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
3975 	g_return_val_if_fail (col < gnm_sheet_get_max_cols (sheet), NULL);
3976 	g_return_val_if_fail (col >= 0, NULL);
3977 
3978 	segment = COLROW_GET_SEGMENT (&(sheet->cols), col);
3979 	if (segment != NULL)
3980 		return segment->info[COLROW_SUB_INDEX (col)];
3981 	return NULL;
3982 }
3983 
3984 /**
3985  * sheet_row_get:
3986  * @row: row number
3987  *
3988  * Returns: (transfer none) (nullable): A #ColRowInfo for the row, or %NULL
3989  * if none has been allocated yet.
3990  */
3991 ColRowInfo *
sheet_row_get(Sheet const * sheet,int row)3992 sheet_row_get (Sheet const *sheet, int row)
3993 {
3994 	ColRowSegment *segment;
3995 
3996 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
3997 	g_return_val_if_fail (row < gnm_sheet_get_max_rows (sheet), NULL);
3998 	g_return_val_if_fail (row >= 0, NULL);
3999 
4000 	segment = COLROW_GET_SEGMENT (&(sheet->rows), row);
4001 	if (segment != NULL)
4002 		return segment->info[COLROW_SUB_INDEX (row)];
4003 	return NULL;
4004 }
4005 
4006 ColRowInfo *
sheet_colrow_get(Sheet const * sheet,int colrow,gboolean is_cols)4007 sheet_colrow_get (Sheet const *sheet, int colrow, gboolean is_cols)
4008 {
4009 	if (is_cols)
4010 		return sheet_col_get (sheet, colrow);
4011 	return sheet_row_get (sheet, colrow);
4012 }
4013 
4014 /**
4015  * sheet_col_fetch:
4016  * @col: column number
4017  *
4018  * Returns: (transfer none): The #ColRowInfo for column @col.  This result
4019  * will not be the default #ColRowInfo and may be changed.
4020  */
4021 ColRowInfo *
sheet_col_fetch(Sheet * sheet,int pos)4022 sheet_col_fetch (Sheet *sheet, int pos)
4023 {
4024 	ColRowInfo *cri = sheet_col_get (sheet, pos);
4025 	if (NULL == cri && NULL != (cri = sheet_col_new (sheet)))
4026 		sheet_colrow_add (sheet, cri, TRUE, pos);
4027 	return cri;
4028 }
4029 
4030 /**
4031  * sheet_row_fetch:
4032  * @row: row number
4033  *
4034  * Returns: (transfer none): The #ColRowInfo for row @row.  This result
4035  * will not be the default #ColRowInfo and may be changed.
4036  */
4037 ColRowInfo *
sheet_row_fetch(Sheet * sheet,int pos)4038 sheet_row_fetch (Sheet *sheet, int pos)
4039 {
4040 	ColRowInfo *cri = sheet_row_get (sheet, pos);
4041 	if (NULL == cri && NULL != (cri = sheet_row_new (sheet)))
4042 		sheet_colrow_add (sheet, cri, FALSE, pos);
4043 	return cri;
4044 }
4045 
4046 ColRowInfo *
sheet_colrow_fetch(Sheet * sheet,int colrow,gboolean is_cols)4047 sheet_colrow_fetch (Sheet *sheet, int colrow, gboolean is_cols)
4048 {
4049 	if (is_cols)
4050 		return sheet_col_fetch (sheet, colrow);
4051 	return sheet_row_fetch (sheet, colrow);
4052 }
4053 
4054 /**
4055  * sheet_col_get_info:
4056  * @col: column number
4057  *
4058  * Returns: (transfer none): The #ColRowInfo for column @col.  The may be
4059  * the default #ColRowInfo for columns and should not be changed.
4060  */
4061 ColRowInfo const *
sheet_col_get_info(Sheet const * sheet,int col)4062 sheet_col_get_info (Sheet const *sheet, int col)
4063 {
4064 	ColRowInfo *ci = sheet_col_get (sheet, col);
4065 
4066 	if (ci != NULL)
4067 		return ci;
4068 	return &sheet->cols.default_style;
4069 }
4070 
4071 /**
4072  * sheet_row_get_info:
4073  * @row: column number
4074  *
4075  * Returns: (transfer none): The #ColRowInfo for row @row.  The may be
4076  * the default #ColRowInfo for rows and should not be changed.
4077  */
4078 ColRowInfo const *
sheet_row_get_info(Sheet const * sheet,int row)4079 sheet_row_get_info (Sheet const *sheet, int row)
4080 {
4081 	ColRowInfo *ri = sheet_row_get (sheet, row);
4082 
4083 	if (ri != NULL)
4084 		return ri;
4085 	return &sheet->rows.default_style;
4086 }
4087 
4088 ColRowInfo const *
sheet_colrow_get_info(Sheet const * sheet,int colrow,gboolean is_cols)4089 sheet_colrow_get_info (Sheet const *sheet, int colrow, gboolean is_cols)
4090 {
4091 	return is_cols
4092 		? sheet_col_get_info (sheet, colrow)
4093 		: sheet_row_get_info (sheet, colrow);
4094 }
4095 
4096 /**
4097  * sheet_colrow_foreach:
4098  * @sheet: #Sheet
4099  * @is_cols: %TRUE for columns, %FALSE for rows.
4100  * @first:	start position (inclusive)
4101  * @last:	stop position (inclusive), -1 meaning end-of-sheet
4102  * @callback: (scope call): A callback function which should return %TRUE
4103  *    to stop the iteration.
4104  * @user_data:	A baggage pointer.
4105  *
4106  * Iterates through the existing rows or columns within the range supplied.
4107  * If a callback returns %TRUE, iteration stops.
4108  **/
4109 gboolean
sheet_colrow_foreach(Sheet const * sheet,gboolean is_cols,int first,int last,ColRowHandler callback,gpointer user_data)4110 sheet_colrow_foreach (Sheet const *sheet,
4111 		      gboolean is_cols,
4112 		      int first, int last,
4113 		      ColRowHandler callback,
4114 		      gpointer user_data)
4115 {
4116 	ColRowCollection const *infos;
4117 	GnmColRowIter iter;
4118 	ColRowSegment const *segment;
4119 	int sub, inner_last, i;
4120 
4121 	g_return_val_if_fail (IS_SHEET (sheet), TRUE);
4122 
4123 	if (last == -1)
4124 		last = colrow_max (is_cols, sheet) - 1;
4125 	infos = is_cols ? &sheet->cols : &sheet->rows;
4126 
4127 	/* clip */
4128 	if (last > infos->max_used)
4129 		last = infos->max_used;
4130 
4131 	for (i = first; i <= last ; ) {
4132 		segment = COLROW_GET_SEGMENT (infos, i);
4133 		sub = COLROW_SUB_INDEX(i);
4134 		inner_last = (COLROW_SEGMENT_INDEX (last) == COLROW_SEGMENT_INDEX (i))
4135 			? COLROW_SUB_INDEX (last)+1 : COLROW_SEGMENT_SIZE;
4136 		iter.pos = i;
4137 		i += COLROW_SEGMENT_SIZE - sub;
4138 		if (segment == NULL)
4139 			continue;
4140 
4141 		for (; sub < inner_last; sub++, iter.pos++) {
4142 			iter.cri = segment->info[sub];
4143 			if (iter.cri != NULL && (*callback)(&iter, user_data))
4144 				return TRUE;
4145 		}
4146 	}
4147 
4148 	return FALSE;
4149 }
4150 
4151 
4152 /*****************************************************************************/
4153 
4154 static gint
cell_ordering(gconstpointer a_,gconstpointer b_)4155 cell_ordering (gconstpointer a_, gconstpointer b_)
4156 {
4157 	GnmCell const *a = *(GnmCell **)a_;
4158 	GnmCell const *b = *(GnmCell **)b_;
4159 
4160 	if (a->pos.row != b->pos.row)
4161 		return a->pos.row - b->pos.row;
4162 
4163 	return a->pos.col - b->pos.col;
4164 }
4165 
4166 /**
4167  * sheet_cells:
4168  * @sheet: a #Sheet
4169  * @r: (nullable): a #GnmRange
4170  *
4171  * Retrieves an array of all cells inside @r.
4172  * Returns: (element-type GnmCell) (transfer container): the cells array.
4173  **/
4174 GPtrArray *
sheet_cells(Sheet * sheet,const GnmRange * r)4175 sheet_cells (Sheet *sheet, const GnmRange *r)
4176 {
4177 	GPtrArray *res = g_ptr_array_new ();
4178 	GHashTableIter hiter;
4179 	gpointer value;
4180 
4181 	g_hash_table_iter_init (&hiter, sheet->cell_hash);
4182 	while (g_hash_table_iter_next (&hiter, NULL, &value)) {
4183 		GnmCell *cell = value;
4184 		if (!r || range_contains (r, cell->pos.col, cell->pos.row))
4185 			g_ptr_array_add (res, cell);
4186 	}
4187 	g_ptr_array_sort (res, cell_ordering);
4188 
4189 	return res;
4190 }
4191 
4192 
4193 
4194 #define SWAP_INT(a,b) do { int t; t = a; a = b; b = t; } while (0)
4195 
4196 /**
4197  * sheet_foreach_cell_in_range:
4198  * @sheet: #Sheet
4199  * @flags:
4200  * @r: #GnmRange
4201  * @callback: (scope call): #CellFilterFunc
4202  * @closure: user data.
4203  *
4204  * For each existing cell in the range specified, invoke the
4205  * callback routine.  If the only_existing flag is passed, then
4206  * callbacks are only invoked for existing cells.
4207  *
4208  * Note: this function does not honour the CELL_ITER_IGNORE_SUBTOTAL flag.
4209  *
4210  * Returns: (transfer none): the value returned by the callback, which can be:
4211  *    non-%NULL on error, or VALUE_TERMINATE if some invoked routine requested
4212  *    to stop (by returning non-%NULL).
4213  *
4214  * NOTE: between 0.56 and 0.57, the traversal order changed.  The order is now
4215  *
4216  *        1    2    3
4217  *        4    5    6
4218  *        7    8    9
4219  *
4220  * (This appears to be the order in which XL looks at the values of ranges.)
4221  * If your code depends on any particular ordering, please add a very visible
4222  * comment near the call.
4223  */
4224 GnmValue *
sheet_foreach_cell_in_range(Sheet * sheet,CellIterFlags flags,GnmRange const * r,CellIterFunc callback,gpointer closure)4225 sheet_foreach_cell_in_range (Sheet *sheet, CellIterFlags flags,
4226 			     GnmRange const *r,
4227 			     CellIterFunc callback,
4228 			     gpointer     closure)
4229 {
4230 	return sheet_foreach_cell_in_region (sheet, flags,
4231 					     r->start.col, r->start.row,
4232 					     r->end.col, r->end.row,
4233 					     callback, closure);
4234 }
4235 
4236 
4237 /**
4238  * sheet_foreach_cell_in_region:
4239  * @sheet: #Sheet
4240  * @flags:
4241  * @start_col: Starting column
4242  * @start_row: Starting row
4243  * @end_col: Ending column, -1 meaning last
4244  * @end_row: Ending row, -1 meaning last
4245  * @callback: (scope call): #CellFilterFunc
4246  * @closure: user data.
4247  *
4248  * For each existing cell in the range specified, invoke the
4249  * callback routine.  If the only_existing flag is passed, then
4250  * callbacks are only invoked for existing cells.
4251  *
4252  * Note: this function does not honour the CELL_ITER_IGNORE_SUBTOTAL flag.
4253  *
4254  * Returns: (transfer none): the value returned by the callback, which can be:
4255  *    non-%NULL on error, or VALUE_TERMINATE if some invoked routine requested
4256  *    to stop (by returning non-%NULL).
4257  *
4258  * NOTE: between 0.56 and 0.57, the traversal order changed.  The order is now
4259  *
4260  *        1    2    3
4261  *        4    5    6
4262  *        7    8    9
4263  *
4264  * (This appears to be the order in which XL looks at the values of ranges.)
4265  * If your code depends on any particular ordering, please add a very visible
4266  * comment near the call.
4267  */
4268 GnmValue *
sheet_foreach_cell_in_region(Sheet * sheet,CellIterFlags flags,int start_col,int start_row,int end_col,int end_row,CellIterFunc callback,void * closure)4269 sheet_foreach_cell_in_region (Sheet *sheet, CellIterFlags flags,
4270 			     int start_col, int start_row,
4271 			     int end_col,   int end_row,
4272 			     CellIterFunc callback, void *closure)
4273 {
4274 	GnmValue *cont;
4275 	GnmCellIter iter;
4276 	gboolean const visibility_matters = (flags & CELL_ITER_IGNORE_HIDDEN) != 0;
4277 	gboolean const ignore_filtered = (flags & CELL_ITER_IGNORE_FILTERED) != 0;
4278 	gboolean const only_existing = (flags & CELL_ITER_IGNORE_NONEXISTENT) != 0;
4279 	gboolean const ignore_empty = (flags & CELL_ITER_IGNORE_EMPTY) != 0;
4280 	gboolean ignore;
4281 	gboolean use_celllist;
4282 	guint64 range_size;
4283 
4284 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
4285 	g_return_val_if_fail (callback != NULL, NULL);
4286 
4287 	// For convenience
4288 	if (end_col == -1) end_col = gnm_sheet_get_last_col (sheet);
4289 	if (end_row == -1) end_row = gnm_sheet_get_last_row (sheet);
4290 
4291 	iter.pp.sheet = sheet;
4292 	iter.pp.wb = sheet->workbook;
4293 
4294 	if (start_col > end_col)
4295 		SWAP_INT (start_col, end_col);
4296 	if (end_col < 0 || start_col > gnm_sheet_get_last_col (sheet))
4297 		return NULL;
4298 	start_col = MAX (0, start_col);
4299 	end_col = MIN (end_col, gnm_sheet_get_last_col (sheet));
4300 
4301 	if (start_row > end_row)
4302 		SWAP_INT (start_row, end_row);
4303 	if (end_row < 0 || start_row > gnm_sheet_get_last_row (sheet))
4304 		return NULL;
4305 	start_row = MAX (0, start_row);
4306 	end_row = MIN (end_row, gnm_sheet_get_last_row (sheet));
4307 
4308 	range_size = (guint64)(end_row - start_row + 1) * (end_col - start_col + 1);
4309 	use_celllist =
4310 		only_existing &&
4311 		range_size > g_hash_table_size (sheet->cell_hash) + 1000;
4312 	if (use_celllist) {
4313 		GPtrArray *all_cells;
4314 		int last_row = -1, last_col = -1;
4315 		GnmValue *res = NULL;
4316 		unsigned ui;
4317 		GnmRange r;
4318 
4319 		if (gnm_debug_flag ("sheet-foreach"))
4320 			g_printerr ("Using celllist for area of size %d\n",
4321 				    (int)range_size);
4322 
4323 		range_init (&r, start_col, start_row, end_col, end_row);
4324 		all_cells = sheet_cells (sheet, &r);
4325 
4326 		for (ui = 0; ui < all_cells->len; ui++) {
4327 			GnmCell *cell = g_ptr_array_index (all_cells, ui);
4328 
4329 			iter.cell = cell;
4330 			iter.pp.eval.row = cell->pos.row;
4331 			iter.pp.eval.col = cell->pos.col;
4332 
4333 			if (iter.pp.eval.row != last_row) {
4334 				last_row = iter.pp.eval.row;
4335 				iter.ri = sheet_row_get (iter.pp.sheet, last_row);
4336 			}
4337 			if (iter.ri == NULL) {
4338 				g_critical ("Cell without row data -- please report");
4339 				continue;
4340 			}
4341 			if (visibility_matters && !iter.ri->visible)
4342 				continue;
4343 			if (ignore_filtered && iter.ri->in_filter && !iter.ri->visible)
4344 				continue;
4345 
4346 			if (iter.pp.eval.col != last_col) {
4347 				last_col = iter.pp.eval.col;
4348 				iter.ci = sheet_col_get (iter.pp.sheet, last_col);
4349 			}
4350 			if (iter.ci == NULL) {
4351 				g_critical ("Cell without column data -- please report");
4352 				continue;
4353 			}
4354 			if (visibility_matters && !iter.ci->visible)
4355 				continue;
4356 
4357 			ignore = (ignore_empty &&
4358 				  VALUE_IS_EMPTY (cell->value) &&
4359 				  !gnm_cell_needs_recalc (cell));
4360 			if (ignore)
4361 				continue;
4362 
4363 			res = (*callback) (&iter, closure);
4364 			if (res != NULL)
4365 				break;
4366 		}
4367 
4368 		g_ptr_array_free (all_cells, TRUE);
4369 		return res;
4370 	}
4371 
4372 	for (iter.pp.eval.row = start_row;
4373 	     iter.pp.eval.row <= end_row;
4374 	     ++iter.pp.eval.row) {
4375 		iter.ri = sheet_row_get (iter.pp.sheet, iter.pp.eval.row);
4376 
4377 		/* no need to check visibility, that would require a colinfo to exist */
4378 		if (iter.ri == NULL) {
4379 			if (only_existing) {
4380 				/* skip segments with no cells */
4381 				if (iter.pp.eval.row == COLROW_SEGMENT_START (iter.pp.eval.row)) {
4382 					ColRowSegment const *segment =
4383 						COLROW_GET_SEGMENT (&(sheet->rows), iter.pp.eval.row);
4384 					if (segment == NULL)
4385 						iter.pp.eval.row = COLROW_SEGMENT_END (iter.pp.eval.row);
4386 				}
4387 			} else {
4388 				iter.cell = NULL;
4389 				for (iter.pp.eval.col = start_col; iter.pp.eval.col <= end_col; ++iter.pp.eval.col) {
4390 					cont = (*callback) (&iter, closure);
4391 					if (cont != NULL)
4392 						return cont;
4393 				}
4394 			}
4395 
4396 			continue;
4397 		}
4398 
4399 		if (visibility_matters && !iter.ri->visible)
4400 			continue;
4401 		if (ignore_filtered && iter.ri->in_filter && !iter.ri->visible)
4402 			continue;
4403 
4404 		for (iter.pp.eval.col = start_col; iter.pp.eval.col <= end_col; ++iter.pp.eval.col) {
4405 			iter.ci = sheet_col_get (sheet, iter.pp.eval.col);
4406 			if (iter.ci != NULL) {
4407 				if (visibility_matters && !iter.ci->visible)
4408 					continue;
4409 				iter.cell = sheet_cell_get (sheet,
4410 					iter.pp.eval.col, iter.pp.eval.row);
4411 			} else
4412 				iter.cell = NULL;
4413 
4414 			ignore = (iter.cell == NULL)
4415 				? (only_existing || ignore_empty)
4416 				: (ignore_empty && VALUE_IS_EMPTY (iter.cell->value) &&
4417 				   !gnm_cell_needs_recalc (iter.cell));
4418 
4419 			if (ignore) {
4420 				if (iter.pp.eval.col == COLROW_SEGMENT_START (iter.pp.eval.col)) {
4421 					ColRowSegment const *segment =
4422 						COLROW_GET_SEGMENT (&(sheet->cols), iter.pp.eval.col);
4423 					if (segment == NULL)
4424 						iter.pp.eval.col = COLROW_SEGMENT_END (iter.pp.eval.col);
4425 				}
4426 				continue;
4427 			}
4428 
4429 			cont = (*callback) (&iter, closure);
4430 			if (cont != NULL)
4431 				return cont;
4432 		}
4433 	}
4434 	return NULL;
4435 }
4436 
4437 /**
4438  * sheet_cell_foreach:
4439  * @sheet: #Sheet
4440  * @callback: (scope call):
4441  * @data:
4442  *
4443  * Call @callback with an argument of @data for each cell in the sheet
4444  **/
4445 void
sheet_cell_foreach(Sheet const * sheet,GHFunc callback,gpointer data)4446 sheet_cell_foreach (Sheet const *sheet, GHFunc callback, gpointer data)
4447 {
4448 	g_return_if_fail (IS_SHEET (sheet));
4449 
4450 	g_hash_table_foreach (sheet->cell_hash, callback, data);
4451 }
4452 
4453 /**
4454  * sheet_cells_count:
4455  * @sheet: #Sheet
4456  *
4457  * Returns the number of cells with content in the current workbook.
4458  **/
4459 unsigned
sheet_cells_count(Sheet const * sheet)4460 sheet_cells_count (Sheet const *sheet)
4461 {
4462 	return g_hash_table_size (sheet->cell_hash);
4463 }
4464 
4465 static void
cb_sheet_cells_collect(G_GNUC_UNUSED gpointer unused,GnmCell const * cell,GPtrArray * cells)4466 cb_sheet_cells_collect (G_GNUC_UNUSED gpointer unused,
4467 			GnmCell const *cell,
4468 			GPtrArray *cells)
4469 {
4470 	GnmEvalPos *ep = eval_pos_init_cell (g_new (GnmEvalPos, 1), cell);
4471 	g_ptr_array_add (cells, ep);
4472 }
4473 
4474 /**
4475  * sheet_cell_positions:
4476  * @sheet: The sheet to find cells in.
4477  * @comments: If true, include cells with only comments also.
4478  *
4479  * Collects a GPtrArray of GnmEvalPos pointers for all cells in a sheet.
4480  * No particular order should be assumed.
4481  * Returns: (element-type GnmEvalPos) (transfer full): the newly created array
4482  **/
4483 GPtrArray *
sheet_cell_positions(Sheet * sheet,gboolean comments)4484 sheet_cell_positions (Sheet *sheet, gboolean comments)
4485 {
4486 	GPtrArray *cells = g_ptr_array_new ();
4487 
4488 	g_return_val_if_fail (IS_SHEET (sheet), cells);
4489 
4490 	sheet_cell_foreach (sheet, (GHFunc)cb_sheet_cells_collect, cells);
4491 
4492 	if (comments) {
4493 		GnmRange r;
4494 		GSList *scomments, *ptr;
4495 
4496 		range_init_full_sheet (&r, sheet);
4497 		scomments = sheet_objects_get (sheet, &r, GNM_CELL_COMMENT_TYPE);
4498 		for (ptr = scomments; ptr; ptr = ptr->next) {
4499 			GnmComment *c = ptr->data;
4500 			GnmRange const *loc = sheet_object_get_range (GNM_SO (c));
4501 			GnmCell *cell = sheet_cell_get (sheet, loc->start.col, loc->start.row);
4502 			if (!cell) {
4503 				/* If cell does not exist, we haven't seen it...  */
4504 				GnmEvalPos *ep = g_new (GnmEvalPos, 1);
4505 				ep->sheet = sheet;
4506 				ep->eval.col = loc->start.col;
4507 				ep->eval.row = loc->start.row;
4508 				g_ptr_array_add (cells, ep);
4509 			}
4510 		}
4511 		g_slist_free (scomments);
4512 	}
4513 
4514 	return cells;
4515 }
4516 
4517 
4518 static GnmValue *
cb_fail_if_exist(GnmCellIter const * iter,G_GNUC_UNUSED gpointer user)4519 cb_fail_if_exist (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
4520 {
4521 	return gnm_cell_is_empty (iter->cell) ? NULL : VALUE_TERMINATE;
4522 }
4523 
4524 /**
4525  * sheet_is_region_empty:
4526  * @sheet: sheet to check
4527  * @r: region to check
4528  *
4529  * Returns: %TRUE if the specified region of the @sheet does not
4530  * contain any cells
4531  */
4532 gboolean
sheet_is_region_empty(Sheet * sheet,GnmRange const * r)4533 sheet_is_region_empty (Sheet *sheet, GnmRange const *r)
4534 {
4535 	g_return_val_if_fail (IS_SHEET (sheet), TRUE);
4536 
4537 	return sheet_foreach_cell_in_range (
4538 		sheet, CELL_ITER_IGNORE_BLANK, r,
4539 		cb_fail_if_exist, NULL) == NULL;
4540 }
4541 
4542 gboolean
sheet_is_cell_empty(Sheet * sheet,int col,int row)4543 sheet_is_cell_empty (Sheet *sheet, int col, int row)
4544 {
4545 	GnmCell const *cell = sheet_cell_get (sheet, col, row);
4546 	return gnm_cell_is_empty (cell);
4547 }
4548 
4549 /**
4550  * sheet_cell_add_to_hash:
4551  * @sheet The sheet where the cell is inserted
4552  * @cell  The cell, it should already have col/pos pointers
4553  *        initialized pointing to the correct ColRowInfo
4554  *
4555  * GnmCell::pos must be valid before this is called.  The position is used as the
4556  * hash key.
4557  */
4558 static void
sheet_cell_add_to_hash(Sheet * sheet,GnmCell * cell)4559 sheet_cell_add_to_hash (Sheet *sheet, GnmCell *cell)
4560 {
4561 	g_return_if_fail (cell->pos.col < gnm_sheet_get_max_cols (sheet));
4562 	g_return_if_fail (cell->pos.row < gnm_sheet_get_max_rows (sheet));
4563 
4564 	cell->base.flags |= GNM_CELL_IN_SHEET_LIST;
4565 	/* NOTE:
4566 	 *   fetching the col/row here serve 3 functions
4567 	 *   1) obsolete: we used to store the pointer in the cell
4568 	 *   2) Expanding col/row.max_used
4569 	 *   3) Creating an entry in the COLROW_SEGMENT.  Lots and lots of
4570 	 *	things use those to help limit iteration
4571 	 *
4572 	 * For now just call col_fetch even though it is not necessary to
4573 	 * ensure that 2,3 still happen.  Alot will need rewriting to avoid
4574 	 * these requirements.
4575 	 **/
4576 	(void)sheet_col_fetch (sheet, cell->pos.col);
4577 	(void)sheet_row_fetch (sheet, cell->pos.row);
4578 
4579 	gnm_cell_unrender (cell);
4580 
4581 	g_hash_table_insert (sheet->cell_hash, cell, cell);
4582 
4583 	if (gnm_sheet_merge_is_corner (sheet, &cell->pos))
4584 		cell->base.flags |= GNM_CELL_IS_MERGED;
4585 }
4586 
4587 #undef USE_CELL_POOL
4588 
4589 #ifdef USE_CELL_POOL
4590 /* The pool from which all cells are allocated.  */
4591 static GOMemChunk *cell_pool;
4592 #else
4593 static int cell_allocations = 0;
4594 #endif
4595 
4596 static GnmCell *
cell_new(void)4597 cell_new (void)
4598 {
4599 	GnmCell *cell =
4600 #ifdef USE_CELL_POOL
4601 		go_mem_chunk_alloc0 (cell_pool)
4602 #else
4603 		(cell_allocations++, g_slice_new0 (GnmCell))
4604 #endif
4605 	;
4606 
4607 	cell->base.flags = DEPENDENT_CELL;
4608 	return cell;
4609 }
4610 
4611 
4612 static void
cell_free(GnmCell * cell)4613 cell_free (GnmCell *cell)
4614 {
4615 	g_return_if_fail (cell != NULL);
4616 
4617 	gnm_cell_cleanout (cell);
4618 #ifdef USE_CELL_POOL
4619 	go_mem_chunk_free (cell_pool, cell);
4620 #else
4621 	cell_allocations--, g_slice_free1 (sizeof (*cell), cell);
4622 #endif
4623 }
4624 
4625 /**
4626  * gnm_sheet_cell_init: (skip)
4627  */
4628 void
gnm_sheet_cell_init(void)4629 gnm_sheet_cell_init (void)
4630 {
4631 #ifdef USE_CELL_POOL
4632 	cell_pool = go_mem_chunk_new ("cell pool",
4633 				       sizeof (GnmCell),
4634 				       128 * 1024 - 128);
4635 #endif
4636 }
4637 
4638 #ifdef USE_CELL_POOL
4639 static void
cb_cell_pool_leak(gpointer data,G_GNUC_UNUSED gpointer user)4640 cb_cell_pool_leak (gpointer data, G_GNUC_UNUSED gpointer user)
4641 {
4642 	GnmCell const *cell = data;
4643 	g_printerr ("Leaking cell %p at %s\n", (void *)cell, cell_name (cell));
4644 }
4645 #endif
4646 
4647 /**
4648  * gnm_sheet_cell_shutdown: (skip)
4649  */
4650 void
gnm_sheet_cell_shutdown(void)4651 gnm_sheet_cell_shutdown (void)
4652 {
4653 #ifdef USE_CELL_POOL
4654 	go_mem_chunk_foreach_leak (cell_pool, cb_cell_pool_leak, NULL);
4655 	go_mem_chunk_destroy (cell_pool, FALSE);
4656 	cell_pool = NULL;
4657 #else
4658 	if (cell_allocations)
4659 		g_printerr ("Leaking %d cells.\n", cell_allocations);
4660 #endif
4661 }
4662 
4663 /****************************************************************************/
4664 
4665 /**
4666  * sheet_cell_create:
4667  * @sheet: #Sheet
4668  * @col:
4669  * @row:
4670  *
4671  * Creates a new cell and adds it to the sheet hash.
4672  **/
4673 GnmCell *
sheet_cell_create(Sheet * sheet,int col,int row)4674 sheet_cell_create (Sheet *sheet, int col, int row)
4675 {
4676 	GnmCell *cell;
4677 
4678 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
4679 	g_return_val_if_fail (col >= 0, NULL);
4680 	g_return_val_if_fail (col < gnm_sheet_get_max_cols (sheet), NULL);
4681 	g_return_val_if_fail (row >= 0, NULL);
4682 	g_return_val_if_fail (row < gnm_sheet_get_max_rows (sheet), NULL);
4683 
4684 	cell = cell_new ();
4685 	cell->base.sheet = sheet;
4686 	cell->pos.col = col;
4687 	cell->pos.row = row;
4688 	cell->value = value_new_empty ();
4689 
4690 	sheet_cell_add_to_hash (sheet, cell);
4691 	return cell;
4692 }
4693 
4694 /**
4695  * sheet_cell_remove_from_hash:
4696  * @sheet:
4697  * @cell:
4698  *
4699  * Removes a cell from the sheet hash, clears any spans, and unlinks it from
4700  * the dependent collection.
4701  */
4702 static void
sheet_cell_remove_from_hash(Sheet * sheet,GnmCell * cell)4703 sheet_cell_remove_from_hash (Sheet *sheet, GnmCell *cell)
4704 {
4705 	cell_unregister_span (cell);
4706 	if (gnm_cell_expr_is_linked (cell))
4707 		dependent_unlink (GNM_CELL_TO_DEP (cell));
4708 	g_hash_table_remove (sheet->cell_hash, cell);
4709 	cell->base.flags &= ~(GNM_CELL_IN_SHEET_LIST|GNM_CELL_IS_MERGED);
4710 }
4711 
4712 /**
4713  * sheet_cell_destroy:
4714  * @sheet:
4715  * @cell:
4716  * @queue_recalc:
4717  *
4718  * Remove the cell from the web of dependencies of a
4719  *        sheet.  Do NOT redraw.
4720  **/
4721 static void
sheet_cell_destroy(Sheet * sheet,GnmCell * cell,gboolean queue_recalc)4722 sheet_cell_destroy (Sheet *sheet, GnmCell *cell, gboolean queue_recalc)
4723 {
4724 	if (gnm_cell_expr_is_linked (cell)) {
4725 		/* if it needs recalc then its depends are already queued
4726 		 * check recalc status before we unlink
4727 		 */
4728 		queue_recalc &= !gnm_cell_needs_recalc (cell);
4729 		dependent_unlink (GNM_CELL_TO_DEP (cell));
4730 	}
4731 
4732 	if (queue_recalc)
4733 		cell_foreach_dep (cell, (GnmDepFunc)dependent_queue_recalc, NULL);
4734 
4735 	sheet_cell_remove_from_hash (sheet, cell);
4736 	cell_free (cell);
4737 }
4738 
4739 /**
4740  * sheet_cell_remove:
4741  * @sheet:
4742  * @cell:
4743  * @redraw:
4744  * @queue_recalc:
4745  *
4746  * Remove the cell from the web of dependencies of a
4747  *        sheet.  Do NOT free the cell, optionally redraw it, optionally
4748  *        queue it for recalc.
4749  **/
4750 void
sheet_cell_remove(Sheet * sheet,GnmCell * cell,gboolean redraw,gboolean queue_recalc)4751 sheet_cell_remove (Sheet *sheet, GnmCell *cell,
4752 		   gboolean redraw, gboolean queue_recalc)
4753 {
4754 	g_return_if_fail (cell != NULL);
4755 	g_return_if_fail (IS_SHEET (sheet));
4756 
4757 	/* Queue a redraw on the region used by the cell being removed */
4758 	if (redraw) {
4759 		sheet_redraw_region (sheet,
4760 				     cell->pos.col, cell->pos.row,
4761 				     cell->pos.col, cell->pos.row);
4762 		sheet_flag_status_update_cell (cell);
4763 	}
4764 
4765 	sheet_cell_destroy (sheet, cell, queue_recalc);
4766 }
4767 
4768 static GnmValue *
cb_free_cell(GnmCellIter const * iter,G_GNUC_UNUSED gpointer user)4769 cb_free_cell (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
4770 {
4771 	sheet_cell_destroy (iter->pp.sheet, iter->cell, FALSE);
4772 	return NULL;
4773 }
4774 
4775 /**
4776  * sheet_col_destroy:
4777  * @sheet:
4778  * @col:
4779  * @free_cells:
4780  *
4781  * Destroys a ColRowInfo from the Sheet with all of its cells
4782  */
4783 static void
sheet_col_destroy(Sheet * sheet,int const col,gboolean free_cells)4784 sheet_col_destroy (Sheet *sheet, int const col, gboolean free_cells)
4785 {
4786 	ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->cols), col);
4787 	int const sub = COLROW_SUB_INDEX (col);
4788 	ColRowInfo *ci = NULL;
4789 
4790 	if (*segment == NULL)
4791 		return;
4792 	ci = (*segment)->info[sub];
4793 	if (ci == NULL)
4794 		return;
4795 
4796 	if (sheet->cols.max_outline_level > 0 &&
4797 	    sheet->cols.max_outline_level == ci->outline_level)
4798 		sheet->priv->recompute_max_col_group = TRUE;
4799 
4800 	if (free_cells)
4801 		sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4802 					      col, 0, col, -1,
4803 					      &cb_free_cell, NULL);
4804 
4805 	(*segment)->info[sub] = NULL;
4806 	colrow_free (ci);
4807 
4808 	/* Use >= just in case things are screwed up */
4809 	if (col >= sheet->cols.max_used) {
4810 		int i = col;
4811 		while (--i >= 0 && sheet_col_get (sheet, i) == NULL)
4812 		    ;
4813 		sheet->cols.max_used = i;
4814 	}
4815 }
4816 
4817 /*
4818  * Destroys a row ColRowInfo
4819  */
4820 static void
sheet_row_destroy(Sheet * sheet,int const row,gboolean free_cells)4821 sheet_row_destroy (Sheet *sheet, int const row, gboolean free_cells)
4822 {
4823 	ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->rows), row);
4824 	int const sub = COLROW_SUB_INDEX (row);
4825 	ColRowInfo *ri = NULL;
4826 
4827 	if (*segment == NULL)
4828 		return;
4829 	ri = (*segment)->info[sub];
4830 	if (ri == NULL)
4831 		return;
4832 
4833 	if (sheet->rows.max_outline_level > 0 &&
4834 	    sheet->rows.max_outline_level == ri->outline_level)
4835 		sheet->priv->recompute_max_row_group = TRUE;
4836 
4837 	if (free_cells)
4838 		sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4839 					      0, row, -1, row,
4840 					      &cb_free_cell, NULL);
4841 
4842 	/* Rows have span lists, destroy them too */
4843 	row_destroy_span (ri);
4844 
4845 	(*segment)->info[sub] = NULL;
4846 	colrow_free (ri);
4847 
4848 	/* Use >= just in case things are screwed up */
4849 	if (row >= sheet->rows.max_used) {
4850 		int i = row;
4851 		while (--i >= 0 && sheet_row_get (sheet, i) == NULL)
4852 		    ;
4853 		sheet->rows.max_used = i;
4854 	}
4855 }
4856 
4857 static void
cb_remove_allcells(G_GNUC_UNUSED gpointer ignore0,GnmCell * cell,G_GNUC_UNUSED gpointer ignore1)4858 cb_remove_allcells (G_GNUC_UNUSED gpointer ignore0, GnmCell *cell, G_GNUC_UNUSED gpointer ignore1)
4859 {
4860 	cell->base.flags &= ~GNM_CELL_IN_SHEET_LIST;
4861 	cell_free (cell);
4862 }
4863 
4864 void
sheet_destroy_contents(Sheet * sheet)4865 sheet_destroy_contents (Sheet *sheet)
4866 {
4867 	GSList *filters;
4868 	int i;
4869 
4870 	/* By the time we reach here dependencies should have been shut down */
4871 	g_return_if_fail (sheet->deps == NULL);
4872 
4873 	/* A simple test to see if this has already been run. */
4874 	if (sheet->hash_merged == NULL)
4875 		return;
4876 
4877 	{
4878 		GSList *tmp = sheet->slicers;
4879 		sheet->slicers = NULL;
4880 		g_slist_free_full (tmp, (GDestroyNotify)gnm_sheet_slicer_clear_sheet);
4881 	}
4882 
4883 	/* These contain SheetObjects, remove them first */
4884 	filters = g_slist_copy (sheet->filters);
4885 	g_slist_foreach (filters, (GFunc)gnm_filter_remove, NULL);
4886 	g_slist_foreach (filters, (GFunc)gnm_filter_unref, NULL);
4887 	g_slist_free (filters);
4888 
4889 	if (sheet->sheet_objects) {
4890 		/* The list is changed as we remove */
4891 		GSList *objs = g_slist_copy (sheet->sheet_objects);
4892 		GSList *ptr;
4893 		for (ptr = objs; ptr != NULL ; ptr = ptr->next) {
4894 			SheetObject *so = GNM_SO (ptr->data);
4895 			if (so != NULL)
4896 				sheet_object_clear_sheet (so);
4897 		}
4898 		g_slist_free (objs);
4899 		if (sheet->sheet_objects != NULL)
4900 			g_warning ("There is a problem with sheet objects");
4901 	}
4902 
4903 	/* The memory is managed by Sheet::list_merged */
4904 	g_hash_table_destroy (sheet->hash_merged);
4905 	sheet->hash_merged = NULL;
4906 
4907 	g_slist_free_full (sheet->list_merged, g_free);
4908 	sheet->list_merged = NULL;
4909 
4910 	/* Clear the row spans 1st */
4911 	for (i = sheet->rows.max_used; i >= 0 ; --i)
4912 		row_destroy_span (sheet_row_get (sheet, i));
4913 
4914 	/* Remove all the cells */
4915 	sheet_cell_foreach (sheet, (GHFunc) &cb_remove_allcells, NULL);
4916 	g_hash_table_destroy (sheet->cell_hash);
4917 
4918 	/* Delete in ascending order to avoid decrementing max_used each time */
4919 	for (i = 0; i <= sheet->cols.max_used; ++i)
4920 		sheet_col_destroy (sheet, i, FALSE);
4921 
4922 	for (i = 0; i <= sheet->rows.max_used; ++i)
4923 		sheet_row_destroy (sheet, i, FALSE);
4924 
4925 	/* Free segments too */
4926 	col_row_collection_resize (&sheet->cols, 0);
4927 	g_ptr_array_free (sheet->cols.info, TRUE);
4928 	sheet->cols.info = NULL;
4929 
4930 	col_row_collection_resize (&sheet->rows, 0);
4931 	g_ptr_array_free (sheet->rows.info, TRUE);
4932 	sheet->rows.info = NULL;
4933 
4934 	g_clear_object (&sheet->solver_parameters);
4935 }
4936 
4937 /**
4938  * sheet_destroy:
4939  * @sheet: the sheet to destroy
4940  *
4941  * Please note that you need to detach this sheet before
4942  * calling this routine or you will get a warning.
4943  */
4944 static void
sheet_destroy(Sheet * sheet)4945 sheet_destroy (Sheet *sheet)
4946 {
4947 	g_return_if_fail (IS_SHEET (sheet));
4948 
4949 	if (sheet->sheet_views->len > 0)
4950 		g_warning ("Unexpected left over views");
4951 
4952 	if (sheet->print_info) {
4953 		gnm_print_info_free (sheet->print_info);
4954 		sheet->print_info = NULL;
4955 	}
4956 
4957 	style_color_unref (sheet->tab_color);
4958 	sheet->tab_color = NULL;
4959 	style_color_unref (sheet->tab_text_color);
4960 	sheet->tab_text_color = NULL;
4961 
4962 	gnm_app_clipboard_invalidate_sheet (sheet);
4963 }
4964 
4965 static void
gnm_sheet_finalize(GObject * obj)4966 gnm_sheet_finalize (GObject *obj)
4967 {
4968 	Sheet *sheet = SHEET (obj);
4969 	gboolean debug_FMR = gnm_debug_flag ("sheet-fmr");
4970 
4971 	sheet_destroy (sheet);
4972 
4973 	g_clear_object (&sheet->solver_parameters);
4974 
4975 	gnm_conventions_unref (sheet->convs);
4976 	sheet->convs = NULL;
4977 
4978 	g_list_free_full (sheet->scenarios, g_object_unref);
4979 	sheet->scenarios = NULL;
4980 
4981 	if (sheet->sort_setups != NULL)
4982 		g_hash_table_unref (sheet->sort_setups);
4983 
4984 	dependents_invalidate_sheet (sheet, TRUE);
4985 
4986 	sheet_destroy_contents (sheet);
4987 
4988 	if (sheet->slicers != NULL) {
4989 		g_warning ("DataSlicer list should be NULL");
4990 	}
4991 	if (sheet->filters != NULL) {
4992 		g_warning ("Filter list should be NULL");
4993 	}
4994 	if (sheet->sheet_objects != NULL) {
4995 		g_warning ("Sheet object list should be NULL");
4996 	}
4997 	if (sheet->list_merged != NULL) {
4998 		g_warning ("Merged list should be NULL");
4999 	}
5000 	if (sheet->hash_merged != NULL) {
5001 		g_warning ("Merged hash should be NULL");
5002 	}
5003 
5004 	sheet_style_shutdown (sheet);
5005 	sheet_conditions_uninit (sheet);
5006 
5007 	if (sheet->pending_redraw_src) {
5008 		g_source_remove (sheet->pending_redraw_src);
5009 		sheet->pending_redraw_src = 0;
5010 	}
5011 	g_array_free (sheet->pending_redraw, TRUE);
5012 
5013 	if (debug_FMR) {
5014 		g_printerr ("Sheet %p is %s\n", sheet, sheet->name_quoted);
5015 	}
5016 	g_free (sheet->name_quoted);
5017 	g_free (sheet->name_unquoted);
5018 	g_free (sheet->name_unquoted_collate_key);
5019 	g_free (sheet->name_case_insensitive);
5020 	/* Poison */
5021 	sheet->name_quoted = (char *)0xdeadbeef;
5022 	sheet->name_unquoted = (char *)0xdeadbeef;
5023 	g_free (sheet->priv);
5024 	g_ptr_array_free (sheet->sheet_views, TRUE);
5025 
5026 	gnm_rvc_free (sheet->rendered_values);
5027 
5028 	if (debug_FMR) {
5029 		/* Keep object around. */
5030 		return;
5031 	}
5032 
5033 	G_OBJECT_CLASS (parent_class)->finalize (obj);
5034 }
5035 
5036 /*****************************************************************************/
5037 
5038 /*
5039  * cb_empty_cell: A callback for sheet_foreach_cell_in_region
5040  *     removes/clear all of the cells in the specified region.
5041  *     Does NOT queue a redraw.
5042  *
5043  * WARNING : This does NOT regenerate spans that were interrupted by
5044  * this cell and can now continue.
5045  */
5046 static GnmValue *
cb_empty_cell(GnmCellIter const * iter,gpointer user)5047 cb_empty_cell (GnmCellIter const *iter, gpointer user)
5048 {
5049 	int clear_flags = GPOINTER_TO_INT (user);
5050 #if 0
5051 	/* TODO : here and other places flag a need to update the
5052 	 * row/col maxima.
5053 	 */
5054 	if (row >= sheet->rows.max_used || col >= sheet->cols.max_used) { }
5055 #endif
5056 
5057 	sheet_cell_remove (iter->pp.sheet, iter->cell, FALSE,
5058 		(clear_flags & CLEAR_RECALC_DEPS) &&
5059 		iter->pp.wb->recursive_dirty_enabled);
5060 
5061 	return NULL;
5062 }
5063 
5064 /**
5065  * sheet_clear_region:
5066  * @sheet: the sheet being changed
5067  * @start_col: Starting column
5068  * @start_row: Starting row
5069  * @end_col: Ending column
5070  * @end_row: Ending row
5071  * @clear_flags: flags indicating what to clear
5072  * @cc: (nullable): command context for error reporting
5073  *
5074  * Clears a region of cells, formats, object, etc. as indicated by
5075  * @clear_flags.
5076  */
5077 void
sheet_clear_region(Sheet * sheet,int start_col,int start_row,int end_col,int end_row,SheetClearFlags clear_flags,GOCmdContext * cc)5078 sheet_clear_region (Sheet *sheet,
5079 		    int start_col, int start_row,
5080 		    int end_col, int end_row,
5081 		    SheetClearFlags clear_flags,
5082 		    GOCmdContext *cc)
5083 {
5084 	GnmRange r;
5085 
5086 	g_return_if_fail (IS_SHEET (sheet));
5087 	g_return_if_fail (start_col <= end_col);
5088 	g_return_if_fail (start_row <= end_row);
5089 
5090 	r.start.col = start_col;
5091 	r.start.row = start_row;
5092 	r.end.col = end_col;
5093 	r.end.row = end_row;
5094 
5095 	if (clear_flags & CLEAR_VALUES && !(clear_flags & CLEAR_NOCHECKARRAY) &&
5096 	    sheet_range_splits_array (sheet, &r, NULL, cc, _("Clear")))
5097 		return;
5098 
5099 	/* Queue a redraw for cells being modified */
5100 	if (clear_flags & (CLEAR_VALUES|CLEAR_FORMATS))
5101 		sheet_redraw_region (sheet,
5102 				     start_col, start_row,
5103 				     end_col, end_row);
5104 
5105 	/* Clear the style in the region (new_default will ref the style for us). */
5106 	if (clear_flags & CLEAR_FORMATS) {
5107 		sheet_style_set_range (sheet, &r, sheet_style_default (sheet));
5108 		sheet_range_calc_spans (sheet, &r, GNM_SPANCALC_RE_RENDER|GNM_SPANCALC_RESIZE);
5109 		rows_height_update (sheet, &r, TRUE);
5110 	}
5111 
5112 	if (clear_flags & CLEAR_OBJECTS)
5113 		sheet_objects_clear (sheet, &r, G_TYPE_NONE, NULL);
5114 	else if (clear_flags & CLEAR_COMMENTS)
5115 		sheet_objects_clear (sheet, &r, GNM_CELL_COMMENT_TYPE, NULL);
5116 
5117 	/* TODO : how to handle objects ? */
5118 	if (clear_flags & CLEAR_VALUES) {
5119 		/* Remove or empty the cells depending on
5120 		 * whether or not there are comments
5121 		 */
5122 		sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_NONEXISTENT,
5123 			start_col, start_row, end_col, end_row,
5124 			&cb_empty_cell, GINT_TO_POINTER (clear_flags));
5125 
5126 		if (!(clear_flags & CLEAR_NORESPAN)) {
5127 			sheet_queue_respan (sheet, start_row, end_row);
5128 			sheet_flag_status_update_range (sheet, &r);
5129 		}
5130 	}
5131 
5132 	if (clear_flags & CLEAR_MERGES) {
5133 		GSList *merged, *ptr;
5134 		merged = gnm_sheet_merge_get_overlap (sheet, &r);
5135 		for (ptr = merged ; ptr != NULL ; ptr = ptr->next)
5136 			gnm_sheet_merge_remove (sheet, ptr->data);
5137 		g_slist_free (merged);
5138 	}
5139 
5140 	if (clear_flags & CLEAR_RECALC_DEPS)
5141 		sheet_region_queue_recalc (sheet, &r);
5142 
5143 	/* Always redraw */
5144 	sheet_redraw_all (sheet, FALSE);
5145 }
5146 
5147 static void
sheet_clear_region_cb(GnmSheetRange * sr,int * flags)5148 sheet_clear_region_cb (GnmSheetRange *sr, int *flags)
5149 {
5150 	sheet_clear_region (sr->sheet,
5151 			  sr->range.start.col, sr->range.start.row,
5152 			  sr->range.end.col, sr->range.end.row,
5153 			  *flags | CLEAR_NOCHECKARRAY, NULL);
5154 }
5155 
5156 
5157 /**
5158  * sheet_clear_region_undo:
5159  * @sr: #GnmSheetRange
5160  * @clear_flags: flags.
5161  *
5162  * Returns: (transfer full): the new #GOUndo.
5163  **/
sheet_clear_region_undo(GnmSheetRange * sr,int clear_flags)5164 GOUndo *sheet_clear_region_undo (GnmSheetRange *sr, int clear_flags)
5165 {
5166 	int *flags = g_new(int, 1);
5167 	*flags = clear_flags;
5168 	return go_undo_binary_new
5169 		(sr, (gpointer)flags,
5170 		 (GOUndoBinaryFunc) sheet_clear_region_cb,
5171 		 (GFreeFunc) gnm_sheet_range_free,
5172 		 (GFreeFunc) g_free);
5173 }
5174 
5175 
5176 /*****************************************************************************/
5177 
5178 void
sheet_mark_dirty(Sheet * sheet)5179 sheet_mark_dirty (Sheet *sheet)
5180 {
5181 	g_return_if_fail (IS_SHEET (sheet));
5182 
5183 	if (sheet->workbook)
5184 		workbook_mark_dirty (sheet->workbook);
5185 }
5186 
5187 /****************************************************************************/
5188 
5189 static void
sheet_cells_deps_move(GnmExprRelocateInfo * rinfo)5190 sheet_cells_deps_move (GnmExprRelocateInfo *rinfo)
5191 {
5192 	Sheet *sheet = rinfo->origin_sheet;
5193 	GPtrArray *deps = sheet_cells (sheet, &rinfo->origin);
5194 	unsigned ui;
5195 
5196 	/* Phase 1: collect all cells and remove them from hash.  */
5197 	for (ui = 0; ui < deps->len; ui++) {
5198 		GnmCell *cell = g_ptr_array_index (deps, ui);
5199 		gboolean needs_recalc = gnm_cell_needs_recalc (cell);
5200 		sheet_cell_remove_from_hash (sheet, cell);
5201 		if (needs_recalc) /* Do we need this now? */
5202 			cell->base.flags |= DEPENDENT_NEEDS_RECALC;
5203 	}
5204 
5205 	/* Phase 2: add all non-cell deps with positions */
5206 	SHEET_FOREACH_DEPENDENT
5207 		(sheet, dep, {
5208 			GnmCellPos const *pos;
5209 			if (!dependent_is_cell (dep) &&
5210 			    dependent_has_pos (dep) &&
5211 			    (pos = dependent_pos (dep)) &&
5212 			    range_contains (&rinfo->origin, pos->col, pos->row)) {
5213 				dependent_unlink (dep);
5214 				g_ptr_array_add (deps, dep);
5215 			}
5216 		});
5217 
5218 	/* Phase 3: move everything and add cells to hash.  */
5219 	for (ui = 0; ui < deps->len; ui++) {
5220 		GnmDependent *dep = g_ptr_array_index (deps, ui);
5221 
5222 		dependent_move (dep, rinfo->col_offset, rinfo->row_offset);
5223 
5224 		if (dependent_is_cell (dep))
5225 			sheet_cell_add_to_hash (sheet, GNM_DEP_TO_CELL (dep));
5226 
5227 		if (dep->texpr)
5228 			dependent_link (dep);
5229 	}
5230 
5231 	g_ptr_array_free (deps, TRUE);
5232 }
5233 
5234 /* Moves the headers to their new location */
5235 static void
sheet_colrow_move(Sheet * sheet,gboolean is_cols,int const old_pos,int const new_pos)5236 sheet_colrow_move (Sheet *sheet, gboolean is_cols,
5237 		   int const old_pos, int const new_pos)
5238 {
5239 	ColRowSegment *segment = COLROW_GET_SEGMENT (is_cols ? &sheet->cols : &sheet->rows, old_pos);
5240 	ColRowInfo *info = segment
5241 		? segment->info[COLROW_SUB_INDEX (old_pos)]
5242 		: NULL;
5243 
5244 	g_return_if_fail (old_pos >= 0);
5245 	g_return_if_fail (new_pos >= 0);
5246 
5247 	if (info == NULL)
5248 		return;
5249 
5250 	/* Update the position */
5251 	segment->info[COLROW_SUB_INDEX (old_pos)] = NULL;
5252 	sheet_colrow_add (sheet, info, is_cols, new_pos);
5253 }
5254 
5255 static void
sheet_colrow_set_collapse(Sheet * sheet,gboolean is_cols,int pos)5256 sheet_colrow_set_collapse (Sheet *sheet, gboolean is_cols, int pos)
5257 {
5258 	ColRowInfo *cri;
5259 	ColRowInfo const *vs = NULL;
5260 
5261 	if (pos < 0 || pos >= colrow_max (is_cols, sheet))
5262 		return;
5263 
5264 	/* grab the next or previous col/row */
5265 	if ((is_cols ? sheet->outline_symbols_right : sheet->outline_symbols_below)) {
5266 		if (pos > 0)
5267 			vs = sheet_colrow_get (sheet, pos-1, is_cols);
5268 	} else if ((pos+1) < colrow_max (is_cols, sheet))
5269 		vs = sheet_colrow_get (sheet, pos+1, is_cols);
5270 
5271 	/* handle the case where an empty col/row should be marked collapsed */
5272 	cri = sheet_colrow_get (sheet, pos, is_cols);
5273 	if (cri != NULL)
5274 		cri->is_collapsed = (vs != NULL && !vs->visible &&
5275 				     vs->outline_level > cri->outline_level);
5276 	else if (vs != NULL && !vs->visible && vs->outline_level > 0) {
5277 		cri = sheet_colrow_fetch (sheet, pos, is_cols);
5278 		cri->is_collapsed = TRUE;
5279 	}
5280 }
5281 
5282 static void
combine_undo(GOUndo ** pundo,GOUndo * u)5283 combine_undo (GOUndo **pundo, GOUndo *u)
5284 {
5285 	if (pundo)
5286 		*pundo = go_undo_combine (*pundo, u);
5287 	else
5288 		g_object_unref (u);
5289 }
5290 
5291 typedef gboolean (*ColRowInsDelFunc) (Sheet *sheet, int idx, int count,
5292 				      GOUndo **pundo, GOCmdContext *cc);
5293 
5294 typedef struct {
5295 	ColRowInsDelFunc func;
5296 	Sheet *sheet;
5297 	gboolean is_cols;
5298 	int pos;
5299 	int count;
5300 	ColRowStateList *states;
5301 	int state_start;
5302 } ColRowInsDelData;
5303 
5304 static void
cb_undo_insdel(ColRowInsDelData * data)5305 cb_undo_insdel (ColRowInsDelData *data)
5306 {
5307 	data->func (data->sheet, data->pos, data->count, NULL, NULL);
5308 	colrow_set_states (data->sheet, data->is_cols,
5309 			   data->state_start, data->states);
5310 }
5311 
5312 static void
cb_undo_insdel_free(ColRowInsDelData * data)5313 cb_undo_insdel_free (ColRowInsDelData *data)
5314 {
5315 	colrow_state_list_destroy (data->states);
5316 	g_free (data);
5317 }
5318 
5319 static gboolean
sheet_insdel_colrow(Sheet * sheet,int pos,int count,GOUndo ** pundo,GOCmdContext * cc,gboolean is_cols,gboolean is_insert,const char * description,ColRowInsDelFunc opposite)5320 sheet_insdel_colrow (Sheet *sheet, int pos, int count,
5321 		     GOUndo **pundo, GOCmdContext *cc,
5322 		     gboolean is_cols, gboolean is_insert,
5323 		     const char *description,
5324 		     ColRowInsDelFunc opposite)
5325 {
5326 
5327 	GnmRange kill_zone;    /* The range whose contents will be lost.  */
5328 	GnmRange move_zone;    /* The range whose contents will be moved.  */
5329 	GnmRange change_zone;  /* The union of kill_zone and move_zone.  */
5330 	int i, last_pos, max_used_pos;
5331 	int kill_start, kill_end, move_start, move_end;
5332 	int scount = is_insert ? count : -count;
5333 	ColRowStateList *states = NULL;
5334 	GnmExprRelocateInfo reloc_info;
5335 	GSList *l;
5336 	gboolean sticky_end = TRUE;
5337 
5338 	g_return_val_if_fail (IS_SHEET (sheet), TRUE);
5339 	g_return_val_if_fail (count > 0, TRUE);
5340 
5341 	/*
5342 	 * The main undo for an insert col/row is delete col/row and vice versa.
5343 	 * In addition to that, we collect undo information that the main undo
5344 	 * operation will not restore -- for example the contents of the kill
5345 	 * zone.
5346 	 */
5347 	if (pundo) *pundo = NULL;
5348 
5349 	last_pos = colrow_max (is_cols, sheet) - 1;
5350 	max_used_pos = is_cols ? sheet->cols.max_used : sheet->rows.max_used;
5351 	if (is_insert) {
5352 		kill_start = last_pos - (count - 1);
5353 		kill_end = last_pos;
5354 		move_start = pos;
5355 		move_end = kill_start - 1;
5356 	} else {
5357 		int max_count = last_pos + 1 - pos;
5358 		if (count > max_count) {
5359 			sticky_end = FALSE;
5360 			count = max_count;
5361 		}
5362 		kill_start = pos;
5363 		kill_end = pos + (count - 1);
5364 		move_start = kill_end + 1;
5365 		move_end = last_pos;
5366 	}
5367 	(is_cols ? range_init_cols : range_init_rows)
5368 		(&kill_zone, sheet, kill_start, kill_end);
5369 	(is_cols ? range_init_cols : range_init_rows)
5370 		(&move_zone, sheet, move_start, move_end);
5371 	change_zone = range_union (&kill_zone, &move_zone);
5372 
5373 	/* 0. Check displaced/deleted region and ensure arrays aren't divided. */
5374 	if (sheet_range_splits_array (sheet, &kill_zone, NULL, cc, description))
5375 		return TRUE;
5376 	if (move_start <= move_end &&
5377 	    sheet_range_splits_array (sheet, &move_zone, NULL, cc, description))
5378 		return TRUE;
5379 
5380 	/*
5381 	 * At this point we're committed.  Anything that can go wrong should
5382 	 * have been ruled out already.
5383 	 */
5384 
5385 	if (0) {
5386 		g_printerr ("Action = %s at %d count %d\n", description, pos, count);
5387 		g_printerr ("Kill zone: %s\n", range_as_string (&kill_zone));
5388 	}
5389 
5390 	/* 1. Delete all columns/rows in the kill zone */
5391 	if (pundo) {
5392 		combine_undo (pundo, clipboard_copy_range_undo (sheet, &kill_zone));
5393 		states = colrow_get_states (sheet, is_cols, kill_start, kill_end);
5394 	}
5395 	for (i = MIN (max_used_pos, kill_end); i >= kill_start; --i)
5396 		(is_cols ? sheet_col_destroy : sheet_row_destroy)
5397 			(sheet, i, TRUE);
5398 	/* Brutally discard auto filter objects.  Collect the rest for undo.  */
5399 	sheet_objects_clear (sheet, &kill_zone, GNM_FILTER_COMBO_TYPE, NULL);
5400 	sheet_objects_clear (sheet, &kill_zone, G_TYPE_NONE, pundo);
5401 
5402 	reloc_info.reloc_type = is_cols ? GNM_EXPR_RELOCATE_COLS : GNM_EXPR_RELOCATE_ROWS;
5403 	reloc_info.sticky_end = sticky_end;
5404 	reloc_info.origin_sheet = reloc_info.target_sheet = sheet;
5405 	parse_pos_init_sheet (&reloc_info.pos, sheet);
5406 
5407 	// 2. Get rid of style dependents, see #741197.
5408 	sheet_conditions_link_unlink_dependents (sheet, &change_zone, FALSE);
5409 
5410 	/* 3. Invalidate references to kill zone.  */
5411 	if (is_insert) {
5412 		/* Done in the next step. */
5413 	} else {
5414 		reloc_info.origin = kill_zone;
5415 		/* Force invalidation: */
5416 		reloc_info.col_offset = is_cols ? last_pos + 1 : 0;
5417 		reloc_info.row_offset = is_cols ? 0 : last_pos + 1;
5418 		combine_undo (pundo, dependents_relocate (&reloc_info));
5419 	}
5420 
5421 	/* 4. Fix references to the cells which are moving */
5422 	reloc_info.origin = is_insert ? change_zone : move_zone;
5423 	reloc_info.col_offset = is_cols ? scount : 0;
5424 	reloc_info.row_offset = is_cols ? 0 : scount;
5425 	combine_undo (pundo, dependents_relocate (&reloc_info));
5426 
5427 	/* 5. Move the cells */
5428 	sheet_cells_deps_move (&reloc_info);
5429 
5430 	/* 6. Move the columns/rows to their new location.  */
5431 	if (is_insert) {
5432 		/* From right to left */
5433 		for (i = max_used_pos; i >= pos ; --i)
5434 			sheet_colrow_move (sheet, is_cols, i, i + count);
5435 	} else {
5436 		/* From left to right */
5437 		for (i = pos + count ; i <= max_used_pos; ++i)
5438 			sheet_colrow_move (sheet, is_cols, i, i - count);
5439 	}
5440 	sheet_colrow_set_collapse (sheet, is_cols, pos);
5441 	sheet_colrow_set_collapse (sheet, is_cols,
5442 				   is_insert ? pos + count : last_pos - (count - 1));
5443 
5444 	/* 7. Move formatting.  */
5445 	sheet_style_insdel_colrow (&reloc_info);
5446 	sheet_conditions_link_unlink_dependents (sheet, NULL, TRUE);
5447 
5448 	/* 8. Move objects.  */
5449 	sheet_objects_relocate (&reloc_info, FALSE, pundo);
5450 
5451 	/* 9. Move merges.  */
5452 	gnm_sheet_merge_relocate (&reloc_info, pundo);
5453 
5454 	/* 10. Move filters.  */
5455 	gnm_sheet_filter_insdel_colrow (sheet, is_cols, is_insert, pos, count, pundo);
5456 
5457 	/* Notify sheet of pending updates */
5458 	sheet_mark_dirty (sheet);
5459 	sheet->priv->recompute_visibility = TRUE;
5460 	sheet_flag_recompute_spans (sheet);
5461 	sheet_flag_status_update_range (sheet, &change_zone);
5462 	if (is_cols)
5463 		sheet->priv->reposition_objects.col = pos;
5464 	else
5465 		sheet->priv->reposition_objects.row = pos;
5466 
5467 	/* WARNING WARNING WARNING
5468 	 * This is bad practice and should not really be here.
5469 	 * However, we need to ensure that update is run before
5470 	 * gnm_sheet_view_panes_insdel_colrow plays with frozen panes, updating those can
5471 	 * trigger redraws before sheet_update has been called. */
5472 	sheet_update (sheet);
5473 
5474 	SHEET_FOREACH_VIEW (sheet, sv,
5475 			    gnm_sheet_view_panes_insdel_colrow (sv, is_cols, is_insert, pos, count););
5476 
5477 	/* The main undo is the opposite operation.  */
5478 	if (pundo) {
5479 		ColRowInsDelData *data;
5480 		GOUndo *u;
5481 
5482 		data = g_new (ColRowInsDelData, 1);
5483 		data->func = opposite;
5484 		data->sheet = sheet;
5485 		data->is_cols = is_cols;
5486 		data->pos = pos;
5487 		data->count = count;
5488 		data->states = states;
5489 		data->state_start = kill_start;
5490 
5491 		u = go_undo_unary_new (data, (GOUndoUnaryFunc)cb_undo_insdel,
5492 				       (GFreeFunc)cb_undo_insdel_free);
5493 
5494 		combine_undo (pundo, u);
5495 	}
5496 
5497 	/* Reapply all filters.  */
5498 	for (l = sheet->filters; l; l = l->next) {
5499 		GnmFilter *filter = l->data;
5500 		gnm_filter_reapply (filter);
5501 	}
5502 
5503 	return FALSE;
5504 }
5505 
5506 /**
5507  * sheet_insert_cols:
5508  * @sheet: #Sheet
5509  * @col: At which position we want to insert
5510  * @count: The number of columns to be inserted
5511  * @pundo: (out): (transfer full): (allow-none): undo closure
5512  * @cc: The command context
5513  **/
5514 gboolean
sheet_insert_cols(Sheet * sheet,int col,int count,GOUndo ** pundo,GOCmdContext * cc)5515 sheet_insert_cols (Sheet *sheet, int col, int count,
5516 		   GOUndo **pundo, GOCmdContext *cc)
5517 {
5518 	return sheet_insdel_colrow (sheet, col, count, pundo, cc,
5519 				    TRUE, TRUE,
5520 				    _("Insert Columns"),
5521 				    sheet_delete_cols);
5522 }
5523 
5524 /**
5525  * sheet_delete_cols:
5526  * @sheet: The sheet
5527  * @col:     At which position we want to start deleting columns
5528  * @count:   The number of columns to be deleted
5529  * @pundo: (out): (transfer full): (allow-none): undo closure
5530  * @cc: The command context
5531  */
5532 gboolean
sheet_delete_cols(Sheet * sheet,int col,int count,GOUndo ** pundo,GOCmdContext * cc)5533 sheet_delete_cols (Sheet *sheet, int col, int count,
5534 		   GOUndo **pundo, GOCmdContext *cc)
5535 {
5536 	return sheet_insdel_colrow (sheet, col, count, pundo, cc,
5537 				    TRUE, FALSE,
5538 				    _("Delete Columns"),
5539 				    sheet_insert_cols);
5540 }
5541 
5542 /**
5543  * sheet_insert_rows:
5544  * @sheet: The sheet
5545  * @row: At which position we want to insert
5546  * @count: The number of rows to be inserted
5547  * @pundo: (out): (transfer full): (allow-none): undo closure
5548  * @cc: The command context
5549  */
5550 gboolean
sheet_insert_rows(Sheet * sheet,int row,int count,GOUndo ** pundo,GOCmdContext * cc)5551 sheet_insert_rows (Sheet *sheet, int row, int count,
5552 		   GOUndo **pundo, GOCmdContext *cc)
5553 {
5554 	return sheet_insdel_colrow (sheet, row, count, pundo, cc,
5555 				    FALSE, TRUE,
5556 				    _("Insert Rows"),
5557 				    sheet_delete_rows);
5558 }
5559 
5560 /**
5561  * sheet_delete_rows:
5562  * @sheet: The sheet
5563  * @row: At which position we want to start deleting rows
5564  * @count: The number of rows to be deleted
5565  * @pundo: (out): (transfer full): (allow-none): undo closure
5566  * @cc: The command context
5567  */
5568 gboolean
sheet_delete_rows(Sheet * sheet,int row,int count,GOUndo ** pundo,GOCmdContext * cc)5569 sheet_delete_rows (Sheet *sheet, int row, int count,
5570 		   GOUndo **pundo, GOCmdContext *cc)
5571 {
5572 	return sheet_insdel_colrow (sheet, row, count, pundo, cc,
5573 				    FALSE, FALSE,
5574 				    _("Delete Rows"),
5575 				    sheet_insert_rows);
5576 }
5577 
5578 /*
5579  * Callback for sheet_foreach_cell_in_region to remove a cell from the sheet
5580  * hash, unlink from the dependent collection and put it in a temporary list.
5581  */
5582 static GnmValue *
cb_collect_cell(GnmCellIter const * iter,gpointer user)5583 cb_collect_cell (GnmCellIter const *iter, gpointer user)
5584 {
5585 	GList ** l = user;
5586 	GnmCell *cell = iter->cell;
5587 	gboolean needs_recalc = gnm_cell_needs_recalc (cell);
5588 
5589 	sheet_cell_remove_from_hash (iter->pp.sheet, cell);
5590 	*l = g_list_prepend (*l, cell);
5591 	if (needs_recalc)
5592 		cell->base.flags |= DEPENDENT_NEEDS_RECALC;
5593 	return NULL;
5594 }
5595 
5596 /**
5597  * sheet_move_range:
5598  * @cc: The command context
5599  * @rinfo:
5600  * @pundo: (out) (optional) (transfer full): undo object
5601  *
5602  * Move a range as specified in @rinfo report warnings to @cc.
5603  * if @pundo is non-%NULL, invalidate references to the
5604  * target region that are being cleared, and store the undo information
5605  * in @pundo.  If it is %NULL do NOT INVALIDATE.
5606  **/
5607 void
sheet_move_range(GnmExprRelocateInfo const * rinfo,GOUndo ** pundo,GOCmdContext * cc)5608 sheet_move_range (GnmExprRelocateInfo const *rinfo,
5609 		  GOUndo **pundo, GOCmdContext *cc)
5610 {
5611 	GList *cells = NULL;
5612 	GnmCell  *cell;
5613 	GnmRange  dst;
5614 	gboolean out_of_range;
5615 
5616 	g_return_if_fail (rinfo != NULL);
5617 	g_return_if_fail (IS_SHEET (rinfo->origin_sheet));
5618 	g_return_if_fail (IS_SHEET (rinfo->target_sheet));
5619 	g_return_if_fail (rinfo->origin_sheet != rinfo->target_sheet ||
5620 			  rinfo->col_offset != 0 ||
5621 			  rinfo->row_offset != 0);
5622 
5623 	dst = rinfo->origin;
5624 	out_of_range = range_translate (&dst, rinfo->target_sheet,
5625 					rinfo->col_offset, rinfo->row_offset);
5626 
5627 	/* Redraw the src region in case anything was spanning */
5628 	sheet_redraw_range (rinfo->origin_sheet, &rinfo->origin);
5629 
5630 	/* 1. invalidate references to any cells in the destination range that
5631 	 * are not shared with the src.  This must be done before the references
5632 	 * to from the src range are adjusted because they will point into
5633 	 * the destination.
5634 	 */
5635 	if (pundo != NULL) {
5636 		*pundo = NULL;
5637 		if (!out_of_range) {
5638 			GSList *invalid;
5639 			GnmExprRelocateInfo reloc_info;
5640 
5641 			/* We need to be careful about invalidating references
5642 			 * to the old content of the destination region.  We
5643 			 * only invalidate references to regions that are
5644 			 * actually lost.  However, this care is only necessary
5645 			 * if the source and target sheets are the same.
5646 			 *
5647 			 * Handle dst cells being pasted over
5648 			 */
5649 			if (rinfo->origin_sheet == rinfo->target_sheet &&
5650 			    range_overlap (&rinfo->origin, &dst))
5651 				invalid = range_split_ranges (&rinfo->origin, &dst);
5652 			else
5653 				invalid = g_slist_append (NULL, gnm_range_dup (&dst));
5654 
5655 			reloc_info.origin_sheet = reloc_info.target_sheet = rinfo->target_sheet;
5656 
5657 			/* send to infinity to invalidate, but try to assist
5658 			 * the relocation heuristics only move in 1
5659 			 * dimension if possible to give us a chance to be
5660 			 * smart about partial invalidations */
5661 			reloc_info.col_offset = gnm_sheet_get_max_cols (rinfo->target_sheet);
5662 			reloc_info.row_offset = gnm_sheet_get_max_rows (rinfo->target_sheet);
5663 			reloc_info.sticky_end = TRUE;
5664 			if (rinfo->col_offset == 0) {
5665 				reloc_info.col_offset = 0;
5666 				reloc_info.reloc_type = GNM_EXPR_RELOCATE_ROWS;
5667 			} else if (rinfo->row_offset == 0) {
5668 				reloc_info.row_offset = 0;
5669 				reloc_info.reloc_type = GNM_EXPR_RELOCATE_COLS;
5670 			} else
5671 				reloc_info.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE;
5672 
5673 			parse_pos_init_sheet (&reloc_info.pos,
5674 					      rinfo->origin_sheet);
5675 
5676 			while (invalid) {
5677 				GnmRange *r = invalid->data;
5678 				invalid = g_slist_remove (invalid, r);
5679 				if (!range_overlap (r, &rinfo->origin)) {
5680 					reloc_info.origin = *r;
5681 					combine_undo (pundo,
5682 						      dependents_relocate (&reloc_info));
5683 				}
5684 				g_free (r);
5685 			}
5686 
5687 			/*
5688 			 * DO NOT handle src cells moving out the bounds.
5689 			 * that is handled elsewhere.
5690 			 */
5691 		}
5692 
5693 		/* 2. Fix references to and from the cells which are moving */
5694 		combine_undo (pundo, dependents_relocate (rinfo));
5695 	}
5696 
5697 	/* 3. Collect the cells */
5698 	sheet_foreach_cell_in_range (rinfo->origin_sheet,
5699 				     CELL_ITER_IGNORE_NONEXISTENT,
5700 				     &rinfo->origin,
5701 				     &cb_collect_cell, &cells);
5702 
5703 	/* Reverse list so that we start at the top left (simplifies arrays). */
5704 	cells = g_list_reverse (cells);
5705 
5706 	/* 4. Clear the target area and invalidate references to it */
5707 	if (!out_of_range)
5708 		/* we can clear content but not styles from the destination
5709 		 * region without worrying if it overlaps with the source,
5710 		 * because we have already extracted the content.  However,
5711 		 * we do need to queue anything that depends on the region for
5712 		 * recalc. */
5713 		sheet_clear_region (rinfo->target_sheet,
5714 				    dst.start.col, dst.start.row,
5715 				    dst.end.col, dst.end.row,
5716 				    CLEAR_VALUES|CLEAR_RECALC_DEPS, cc);
5717 
5718 	/* 5. Slide styles BEFORE the cells so that spans get computed properly */
5719 	sheet_style_relocate (rinfo);
5720 
5721 	/* 6. Insert the cells back */
5722 	for (; cells != NULL ; cells = g_list_remove (cells, cell)) {
5723 		cell = cells->data;
5724 
5725 		/* check for out of bounds and delete if necessary */
5726 		if ((cell->pos.col + rinfo->col_offset) >= gnm_sheet_get_max_cols (rinfo->target_sheet) ||
5727 		    (cell->pos.row + rinfo->row_offset) >= gnm_sheet_get_max_rows (rinfo->target_sheet)) {
5728 			cell_free (cell);
5729 			continue;
5730 		}
5731 
5732 		/* Update the location */
5733 		cell->base.sheet = rinfo->target_sheet;
5734 		cell->pos.col += rinfo->col_offset;
5735 		cell->pos.row += rinfo->row_offset;
5736 		sheet_cell_add_to_hash (rinfo->target_sheet, cell);
5737 		if (gnm_cell_has_expr (cell))
5738 			dependent_link (GNM_CELL_TO_DEP (cell));
5739 	}
5740 
5741 	/* 7. Move objects in the range */
5742 	sheet_objects_relocate (rinfo, TRUE, pundo);
5743 	gnm_sheet_merge_relocate (rinfo, pundo);
5744 
5745 	/* 8. Notify sheet of pending update */
5746 	sheet_flag_recompute_spans (rinfo->origin_sheet);
5747 	sheet_flag_status_update_range (rinfo->origin_sheet, &rinfo->origin);
5748 }
5749 
5750 static void
sheet_colrow_default_calc(Sheet * sheet,double units,gboolean is_cols,gboolean is_pts)5751 sheet_colrow_default_calc (Sheet *sheet, double units,
5752 			   gboolean is_cols, gboolean is_pts)
5753 {
5754 	ColRowInfo *cri = is_cols
5755 		? &sheet->cols.default_style
5756 		: &sheet->rows.default_style;
5757 
5758 	g_return_if_fail (units > 0.);
5759 
5760 	if (gnm_debug_flag ("colrow")) {
5761 		g_printerr ("Setting default %s size to %g%s\n",
5762 			    is_cols ? "column" : "row",
5763 			    units,
5764 			    is_pts ? "pts" : "px");
5765 	}
5766 
5767 	cri->is_default	= TRUE;
5768 	cri->hard_size	= FALSE;
5769 	cri->visible	= TRUE;
5770 	cri->spans	= NULL;
5771 
5772 	if (is_pts) {
5773 		cri->size_pts = units;
5774 		colrow_compute_pixels_from_pts (cri, sheet, is_cols, -1);
5775 	} else {
5776 		cri->size_pixels = units;
5777 		colrow_compute_pts_from_pixels (cri, sheet, is_cols, -1);
5778 	}
5779 }
5780 
5781 /************************************************************************/
5782 /* Col width support routines.
5783  */
5784 
5785 /**
5786  * sheet_col_get_distance_pts:
5787  *
5788  * Return the number of points between from_col to to_col
5789  * measured from the upper left corner.
5790  */
5791 double
sheet_col_get_distance_pts(Sheet const * sheet,int from,int to)5792 sheet_col_get_distance_pts (Sheet const *sheet, int from, int to)
5793 {
5794 	ColRowInfo const *ci;
5795 	double dflt, pts = 0., sign = 1.;
5796 	int i;
5797 
5798 	g_return_val_if_fail (IS_SHEET (sheet), 1.);
5799 
5800 	if (from > to) {
5801 		int const tmp = to;
5802 		to = from;
5803 		from = tmp;
5804 		sign = -1.;
5805 	}
5806 
5807 	g_return_val_if_fail (from >= 0, 1.);
5808 	g_return_val_if_fail (to <= gnm_sheet_get_max_cols (sheet), 1.);
5809 
5810 	/* Do not use sheet_colrow_foreach, it ignores empties */
5811 	dflt = sheet->cols.default_style.size_pts;
5812 	for (i = from ; i < to ; ++i) {
5813 		if (NULL == (ci = sheet_col_get (sheet, i)))
5814 			pts += dflt;
5815 		else if (ci->visible)
5816 			pts += ci->size_pts;
5817 	}
5818 
5819 	if (sheet->display_formulas)
5820 		pts *= 2.;
5821 
5822 	return pts * sign;
5823 }
5824 
5825 /**
5826  * sheet_col_get_distance_pixels:
5827  *
5828  * Return the number of pixels between from_col to to_col
5829  * measured from the upper left corner.
5830  */
5831 int
sheet_col_get_distance_pixels(Sheet const * sheet,int from,int to)5832 sheet_col_get_distance_pixels (Sheet const *sheet, int from, int to)
5833 {
5834 	ColRowInfo const *ci;
5835 	int dflt, pixels = 0, sign = 1;
5836 	int i;
5837 
5838 	g_return_val_if_fail (IS_SHEET (sheet), 1.);
5839 
5840 	if (from > to) {
5841 		int const tmp = to;
5842 		to = from;
5843 		from = tmp;
5844 		sign = -1;
5845 	}
5846 
5847 	g_return_val_if_fail (from >= 0, 1);
5848 	g_return_val_if_fail (to <= gnm_sheet_get_max_cols (sheet), 1);
5849 
5850 	/* Do not use sheet_colrow_foreach, it ignores empties */
5851 	dflt = sheet_col_get_default_size_pixels (sheet);
5852 	for (i = from ; i < to ; ++i) {
5853 		if (NULL == (ci = sheet_col_get (sheet, i)))
5854 			pixels += dflt;
5855 		else if (ci->visible)
5856 			pixels += ci->size_pixels;
5857 	}
5858 
5859 	return pixels * sign;
5860 }
5861 
5862 /**
5863  * sheet_col_set_size_pts:
5864  * @sheet:	 The sheet
5865  * @col:	 The col
5866  * @width_pts:	 The desired width in pts
5867  * @set_by_user: %TRUE if this was done by a user (ie, user manually
5868  *               set the width)
5869  *
5870  * Sets width of a col in pts, INCLUDING left and right margins, and the far
5871  * grid line.  This is a low level internal routine.  It does NOT redraw,
5872  * or reposition objects.
5873  **/
5874 void
sheet_col_set_size_pts(Sheet * sheet,int col,double width_pts,gboolean set_by_user)5875 sheet_col_set_size_pts (Sheet *sheet, int col, double width_pts,
5876 			gboolean set_by_user)
5877 {
5878 	ColRowInfo *ci;
5879 
5880 	g_return_if_fail (IS_SHEET (sheet));
5881 	g_return_if_fail (width_pts > 0.0);
5882 
5883 	ci = sheet_col_fetch (sheet, col);
5884 	ci->hard_size = set_by_user;
5885 	if (ci->size_pts == width_pts)
5886 		return;
5887 
5888 	ci->size_pts = width_pts;
5889 	colrow_compute_pixels_from_pts (ci, sheet, TRUE, -1);
5890 
5891 	sheet->priv->recompute_visibility = TRUE;
5892 	sheet_flag_recompute_spans (sheet);
5893 	if (sheet->priv->reposition_objects.col > col)
5894 		sheet->priv->reposition_objects.col = col;
5895 }
5896 
5897 void
sheet_col_set_size_pixels(Sheet * sheet,int col,int width_pixels,gboolean set_by_user)5898 sheet_col_set_size_pixels (Sheet *sheet, int col, int width_pixels,
5899 			   gboolean set_by_user)
5900 {
5901 	ColRowInfo *ci;
5902 
5903 	g_return_if_fail (IS_SHEET (sheet));
5904 	g_return_if_fail (width_pixels > 0.0);
5905 
5906 	ci = sheet_col_fetch (sheet, col);
5907 	ci->hard_size = set_by_user;
5908 	if (ci->size_pixels == width_pixels)
5909 		return;
5910 
5911 	ci->size_pixels = width_pixels;
5912 	colrow_compute_pts_from_pixels (ci, sheet, TRUE, -1);
5913 
5914 	sheet->priv->recompute_visibility = TRUE;
5915 	sheet_flag_recompute_spans (sheet);
5916 	if (sheet->priv->reposition_objects.col > col)
5917 		sheet->priv->reposition_objects.col = col;
5918 }
5919 
5920 /**
5921  * sheet_col_get_default_size_pts:
5922  *
5923  * Return the default number of pts in a column, including margins.
5924  * This function returns the raw sum, no rounding etc.
5925  */
5926 double
sheet_col_get_default_size_pts(Sheet const * sheet)5927 sheet_col_get_default_size_pts (Sheet const *sheet)
5928 {
5929 	g_return_val_if_fail (IS_SHEET (sheet), 1.);
5930 	return sheet->cols.default_style.size_pts;
5931 }
5932 
5933 int
sheet_col_get_default_size_pixels(Sheet const * sheet)5934 sheet_col_get_default_size_pixels (Sheet const *sheet)
5935 {
5936 	g_return_val_if_fail (IS_SHEET (sheet), 1.);
5937 	return sheet->cols.default_style.size_pixels;
5938 }
5939 
5940 void
sheet_col_set_default_size_pts(Sheet * sheet,double width_pts)5941 sheet_col_set_default_size_pts (Sheet *sheet, double width_pts)
5942 {
5943 	g_return_if_fail (IS_SHEET (sheet));
5944 	g_return_if_fail (width_pts > 0.);
5945 
5946 	sheet_colrow_default_calc (sheet, width_pts, TRUE, TRUE);
5947 	sheet->priv->recompute_visibility = TRUE;
5948 	sheet_flag_recompute_spans (sheet);
5949 	sheet->priv->reposition_objects.col = 0;
5950 }
5951 void
sheet_col_set_default_size_pixels(Sheet * sheet,int width_pixels)5952 sheet_col_set_default_size_pixels (Sheet *sheet, int width_pixels)
5953 {
5954 	g_return_if_fail (IS_SHEET (sheet));
5955 
5956 	sheet_colrow_default_calc (sheet, width_pixels, TRUE, FALSE);
5957 	sheet->priv->recompute_visibility = TRUE;
5958 	sheet_flag_recompute_spans (sheet);
5959 	sheet->priv->reposition_objects.col = 0;
5960 }
5961 
5962 /**************************************************************************/
5963 /* Row height support routines
5964  */
5965 
5966 /**
5967  * sheet_row_get_distance_pts:
5968  *
5969  * Return the number of points between from_row to to_row
5970  * measured from the upper left corner.
5971  */
5972 double
sheet_row_get_distance_pts(Sheet const * sheet,int from,int to)5973 sheet_row_get_distance_pts (Sheet const *sheet, int from, int to)
5974 {
5975 	ColRowSegment const *segment;
5976 	ColRowInfo const *ri;
5977 	double const default_size = sheet->rows.default_style.size_pts;
5978 	double pts = 0., sign = 1.;
5979 	int i;
5980 
5981 	g_return_val_if_fail (IS_SHEET (sheet), 1.);
5982 
5983 	if (from > to) {
5984 		int const tmp = to;
5985 		to = from;
5986 		from = tmp;
5987 		sign = -1.;
5988 	}
5989 
5990 	g_return_val_if_fail (from >= 0, 1.);
5991 	g_return_val_if_fail (to <= gnm_sheet_get_max_rows (sheet), 1.);
5992 
5993 	/* Do not use sheet_colrow_foreach, it ignores empties.
5994 	 * Optimize this so that long jumps are not quite so horrific
5995 	 * for performance.
5996 	 */
5997 	for (i = from ; i < to ; ++i) {
5998 		segment = COLROW_GET_SEGMENT (&(sheet->rows), i);
5999 
6000 		if (segment != NULL) {
6001 			ri = segment->info[COLROW_SUB_INDEX (i)];
6002 			if (ri == NULL)
6003 				pts += default_size;
6004 			else if (ri->visible)
6005 				pts += ri->size_pts;
6006 		} else {
6007 			int segment_end = COLROW_SEGMENT_END (i)+1;
6008 			if (segment_end > to)
6009 				segment_end = to;
6010 			pts += default_size * (segment_end - i);
6011 			i = segment_end-1;
6012 		}
6013 	}
6014 
6015 	return pts*sign;
6016 }
6017 
6018 /**
6019  * sheet_row_get_distance_pixels:
6020  *
6021  * Return the number of pixels between from_row to to_row
6022  * measured from the upper left corner.
6023  */
6024 int
sheet_row_get_distance_pixels(Sheet const * sheet,int from,int to)6025 sheet_row_get_distance_pixels (Sheet const *sheet, int from, int to)
6026 {
6027 	ColRowInfo const *ci;
6028 	int dflt, pixels = 0, sign = 1;
6029 	int i;
6030 
6031 	g_return_val_if_fail (IS_SHEET (sheet), 1.);
6032 
6033 	if (from > to) {
6034 		int const tmp = to;
6035 		to = from;
6036 		from = tmp;
6037 		sign = -1;
6038 	}
6039 
6040 	g_return_val_if_fail (from >= 0, 1);
6041 	g_return_val_if_fail (to <= gnm_sheet_get_max_rows (sheet), 1);
6042 
6043 	/* Do not use sheet_colrow_foreach, it ignores empties */
6044 	dflt =  sheet_row_get_default_size_pixels (sheet);
6045 	for (i = from ; i < to ; ++i) {
6046 		if (NULL == (ci = sheet_row_get (sheet, i)))
6047 			pixels += dflt;
6048 		else if (ci->visible)
6049 			pixels += ci->size_pixels;
6050 	}
6051 
6052 	return pixels * sign;
6053 }
6054 
6055 /**
6056  * sheet_row_set_size_pts:
6057  * @sheet:	 The sheet
6058  * @row:	 The row
6059  * @height_pts:	 The desired height in pts
6060  * @set_by_user: %TRUE if this was done by a user (ie, user manually
6061  *               set the height)
6062  *
6063  * Sets height of a row in pts, INCLUDING top and bottom margins, and the lower
6064  * grid line.  This is a low level internal routine.  It does NOT redraw,
6065  * or reposition objects.
6066  */
6067 void
sheet_row_set_size_pts(Sheet * sheet,int row,double height_pts,gboolean set_by_user)6068 sheet_row_set_size_pts (Sheet *sheet, int row, double height_pts,
6069 			gboolean set_by_user)
6070 {
6071 	ColRowInfo *ri;
6072 
6073 	g_return_if_fail (IS_SHEET (sheet));
6074 	g_return_if_fail (height_pts > 0.0);
6075 
6076 	ri = sheet_row_fetch (sheet, row);
6077 	ri->hard_size = set_by_user;
6078 	if (ri->size_pts == height_pts)
6079 		return;
6080 
6081 	ri->size_pts = height_pts;
6082 	colrow_compute_pixels_from_pts (ri, sheet, FALSE, -1);
6083 
6084 	sheet->priv->recompute_visibility = TRUE;
6085 	if (sheet->priv->reposition_objects.row > row)
6086 		sheet->priv->reposition_objects.row = row;
6087 }
6088 
6089 /**
6090  * sheet_row_set_size_pixels:
6091  * @sheet:	 The sheet
6092  * @row:	 The row
6093  * @height_pixels: The desired height
6094  * @set_by_user: %TRUE if this was done by a user (ie, user manually
6095  *                      set the width)
6096  *
6097  * Sets height of a row in pixels, INCLUDING top and bottom margins, and the lower
6098  * grid line.
6099  */
6100 void
sheet_row_set_size_pixels(Sheet * sheet,int row,int height_pixels,gboolean set_by_user)6101 sheet_row_set_size_pixels (Sheet *sheet, int row, int height_pixels,
6102 			   gboolean set_by_user)
6103 {
6104 	ColRowInfo *ri;
6105 
6106 	g_return_if_fail (IS_SHEET (sheet));
6107 	g_return_if_fail (height_pixels > 0);
6108 
6109 	ri = sheet_row_fetch (sheet, row);
6110 	ri->hard_size = set_by_user;
6111 	if (ri->size_pixels == height_pixels)
6112 		return;
6113 
6114 	ri->size_pixels = height_pixels;
6115 	colrow_compute_pts_from_pixels (ri, sheet, FALSE, -1);
6116 
6117 	sheet->priv->recompute_visibility = TRUE;
6118 	if (sheet->priv->reposition_objects.row > row)
6119 		sheet->priv->reposition_objects.row = row;
6120 }
6121 
6122 /**
6123  * sheet_row_get_default_size_pts:
6124  *
6125  * Return the default number of units in a row, including margins.
6126  * This function returns the raw sum, no rounding etc.
6127  */
6128 double
sheet_row_get_default_size_pts(Sheet const * sheet)6129 sheet_row_get_default_size_pts (Sheet const *sheet)
6130 {
6131 	g_return_val_if_fail (IS_SHEET (sheet), 1.);
6132 	return sheet->rows.default_style.size_pts;
6133 }
6134 
6135 int
sheet_row_get_default_size_pixels(Sheet const * sheet)6136 sheet_row_get_default_size_pixels (Sheet const *sheet)
6137 {
6138 	g_return_val_if_fail (IS_SHEET (sheet), 1.);
6139 	return sheet->rows.default_style.size_pixels;
6140 }
6141 
6142 void
sheet_row_set_default_size_pts(Sheet * sheet,double height_pts)6143 sheet_row_set_default_size_pts (Sheet *sheet, double height_pts)
6144 {
6145 	g_return_if_fail (IS_SHEET (sheet));
6146 
6147 	sheet_colrow_default_calc (sheet, height_pts, FALSE, TRUE);
6148 	sheet->priv->recompute_visibility = TRUE;
6149 	sheet->priv->reposition_objects.row = 0;
6150 }
6151 
6152 void
sheet_row_set_default_size_pixels(Sheet * sheet,int height_pixels)6153 sheet_row_set_default_size_pixels (Sheet *sheet, int height_pixels)
6154 {
6155 	g_return_if_fail (IS_SHEET (sheet));
6156 
6157 	sheet_colrow_default_calc (sheet, height_pixels, FALSE, FALSE);
6158 	sheet->priv->recompute_visibility = TRUE;
6159 	sheet->priv->reposition_objects.row = 0;
6160 }
6161 
6162 /****************************************************************************/
6163 
6164 void
sheet_scrollbar_config(Sheet const * sheet)6165 sheet_scrollbar_config (Sheet const *sheet)
6166 {
6167 	g_return_if_fail (IS_SHEET (sheet));
6168 
6169 	SHEET_FOREACH_CONTROL (sheet, view, control,
6170 		sc_scrollbar_config (control););
6171 }
6172 
6173 /*****************************************************************************/
6174 typedef struct
6175 {
6176 	gboolean is_column;
6177 	Sheet *sheet;
6178 } closure_clone_colrow;
6179 
6180 static gboolean
sheet_clone_colrow_info_item(GnmColRowIter const * iter,void * user_data)6181 sheet_clone_colrow_info_item (GnmColRowIter const *iter, void *user_data)
6182 {
6183 	closure_clone_colrow const *closure = user_data;
6184 	ColRowInfo *new_colrow = sheet_colrow_fetch (closure->sheet,
6185 		iter->pos, closure->is_column);
6186 	col_row_info_copy (new_colrow, iter->cri);
6187 	return FALSE;
6188 }
6189 
6190 static void
sheet_dup_colrows(Sheet const * src,Sheet * dst)6191 sheet_dup_colrows (Sheet const *src, Sheet *dst)
6192 {
6193 	closure_clone_colrow closure;
6194 	int max_col = MIN (gnm_sheet_get_max_cols (src), gnm_sheet_get_max_cols (dst)),
6195 	    max_row = MIN (gnm_sheet_get_max_rows (src), gnm_sheet_get_max_rows (dst));
6196 
6197 	closure.sheet = dst;
6198 	closure.is_column = TRUE;
6199 	sheet_colrow_foreach  (src, TRUE, 0, max_col - 1,
6200 			       &sheet_clone_colrow_info_item, &closure);
6201 	closure.is_column = FALSE;
6202 	sheet_colrow_foreach (src, FALSE, 0, max_row - 1,
6203 			      &sheet_clone_colrow_info_item, &closure);
6204 
6205 	sheet_col_set_default_size_pixels (dst,
6206 		sheet_col_get_default_size_pixels (src));
6207 	sheet_row_set_default_size_pixels (dst,
6208 		sheet_row_get_default_size_pixels (src));
6209 
6210 	dst->cols.max_outline_level = src->cols.max_outline_level;
6211 	dst->rows.max_outline_level = src->rows.max_outline_level;
6212 }
6213 
6214 static void
sheet_dup_styles(Sheet const * src,Sheet * dst)6215 sheet_dup_styles (Sheet const *src, Sheet *dst)
6216 {
6217 	static GnmCellPos const	corner = { 0, 0 };
6218 	GnmRange	 r;
6219 	GnmStyleList	*styles;
6220 
6221 	sheet_style_set_auto_pattern_color (
6222 		dst, sheet_style_get_auto_pattern_color (src));
6223 
6224 	styles = sheet_style_get_range (src, range_init_full_sheet (&r, src));
6225 	sheet_style_set_list (dst, &corner, styles, NULL, NULL);
6226 	style_list_free	(styles);
6227 }
6228 
6229 static void
sheet_dup_merged_regions(Sheet const * src,Sheet * dst)6230 sheet_dup_merged_regions (Sheet const *src, Sheet *dst)
6231 {
6232 	GSList *ptr;
6233 
6234 	for (ptr = src->list_merged ; ptr != NULL ; ptr = ptr->next)
6235 		gnm_sheet_merge_add (dst, ptr->data, FALSE, NULL);
6236 }
6237 
6238 static void
sheet_dup_names(Sheet const * src,Sheet * dst)6239 sheet_dup_names (Sheet const *src, Sheet *dst)
6240 {
6241 	GSList *names = gnm_named_expr_collection_list (src->names);
6242 	GSList *l;
6243 	GnmParsePos dst_pp;
6244 
6245 	if (names == NULL)
6246 		return;
6247 
6248 	parse_pos_init_sheet (&dst_pp, dst);
6249 
6250 	/* Pass 1: add placeholders.  */
6251 	for (l = names; l; l = l->next) {
6252 		GnmNamedExpr *src_nexpr = l->data;
6253 		char const *name = expr_name_name (src_nexpr);
6254 		GnmNamedExpr *dst_nexpr =
6255 			gnm_named_expr_collection_lookup (dst->names, name);
6256 		GnmExprTop const *texpr;
6257 
6258 		if (dst_nexpr)
6259 			continue;
6260 
6261 		texpr = gnm_expr_top_new_constant (value_new_empty ());
6262 		expr_name_add (&dst_pp, name, texpr , NULL, TRUE, NULL);
6263 	}
6264 
6265 	/* Pass 2: assign the right expression.  */
6266 	for (l = names; l; l = l->next) {
6267 		GnmNamedExpr *src_nexpr = l->data;
6268 		char const *name = expr_name_name (src_nexpr);
6269 		GnmNamedExpr *dst_nexpr =
6270 			gnm_named_expr_collection_lookup (dst->names, name);
6271 		GnmExprTop const *texpr;
6272 
6273 		if (!dst_nexpr) {
6274 			g_warning ("Trouble while duplicating name %s", name);
6275 			continue;
6276 		}
6277 
6278 		if (!dst_nexpr->is_editable)
6279 			continue;
6280 
6281 		texpr = gnm_expr_top_relocate_sheet (src_nexpr->texpr, src, dst);
6282 		expr_name_set_expr (dst_nexpr, texpr);
6283 	}
6284 
6285 	g_slist_free (names);
6286 }
6287 
6288 static void
cb_sheet_cell_copy(G_GNUC_UNUSED gpointer unused,gpointer key,gpointer new_sheet_param)6289 cb_sheet_cell_copy (G_GNUC_UNUSED gpointer unused, gpointer key, gpointer new_sheet_param)
6290 {
6291 	GnmCell const *cell = key;
6292 	Sheet *dst = new_sheet_param;
6293 	Sheet *src;
6294 	GnmExprTop const *texpr;
6295 
6296 	g_return_if_fail (dst != NULL);
6297 	g_return_if_fail (cell != NULL);
6298 
6299 	src = cell->base.sheet;
6300 	texpr = cell->base.texpr;
6301 
6302 	if (texpr && gnm_expr_top_is_array_corner (texpr)) {
6303 		int cols, rows;
6304 
6305 		texpr = gnm_expr_top_relocate_sheet (texpr, src, dst);
6306 		gnm_expr_top_get_array_size (texpr, &cols, &rows);
6307 
6308 		gnm_cell_set_array_formula (dst,
6309 			cell->pos.col, cell->pos.row,
6310 			cell->pos.col + cols - 1,
6311 			cell->pos.row + rows - 1,
6312 			gnm_expr_top_new (gnm_expr_copy (gnm_expr_top_get_array_expr (texpr))));
6313 
6314 		gnm_expr_top_unref (texpr);
6315 	} else if (texpr && gnm_expr_top_is_array_elem (texpr, NULL, NULL)) {
6316 		/* Not a corner -- ignore.  */
6317 	} else {
6318 		GnmCell *new_cell = sheet_cell_create (dst, cell->pos.col, cell->pos.row);
6319 		if (gnm_cell_has_expr (cell)) {
6320 			texpr = gnm_expr_top_relocate_sheet (texpr, src, dst);
6321 			gnm_cell_set_expr_and_value (new_cell, texpr, value_new_empty (), TRUE);
6322 			gnm_expr_top_unref (texpr);
6323 		} else
6324 			gnm_cell_set_value (new_cell, value_dup (cell->value));
6325 	}
6326 }
6327 
6328 static void
sheet_dup_cells(Sheet const * src,Sheet * dst)6329 sheet_dup_cells (Sheet const *src, Sheet *dst)
6330 {
6331 	sheet_cell_foreach (src, &cb_sheet_cell_copy, dst);
6332 	sheet_region_queue_recalc (dst, NULL);
6333 }
6334 
6335 static void
sheet_dup_filters(Sheet const * src,Sheet * dst)6336 sheet_dup_filters (Sheet const *src, Sheet *dst)
6337 {
6338 	GSList *ptr;
6339 	for (ptr = src->filters ; ptr != NULL ; ptr = ptr->next)
6340 		gnm_filter_dup (ptr->data, dst);
6341 	dst->filters = g_slist_reverse (dst->filters);
6342 }
6343 
6344 /**
6345  * sheet_dup:
6346  * @source_sheet: #Sheet
6347  *
6348  * Create a new Sheet and return it.
6349  * Returns: (transfer full): the newly allocated #Sheet.
6350  **/
6351 Sheet *
sheet_dup(Sheet const * src)6352 sheet_dup (Sheet const *src)
6353 {
6354 	Workbook *wb;
6355 	Sheet *dst;
6356 	char *name;
6357 	GList *l;
6358 
6359 	g_return_val_if_fail (IS_SHEET (src), NULL);
6360 	g_return_val_if_fail (src->workbook != NULL, NULL);
6361 
6362 	wb = src->workbook;
6363 	name = workbook_sheet_get_free_name (wb, src->name_unquoted,
6364 					     TRUE, TRUE);
6365 	dst = sheet_new_with_type (wb, name, src->sheet_type,
6366 				   src->size.max_cols, src->size.max_rows);
6367 	g_free (name);
6368 
6369 	dst->protected_allow = src->protected_allow;
6370 	g_object_set (dst,
6371 		"zoom-factor",		    src->last_zoom_factor_used,
6372 		"text-is-rtl",		    src->text_is_rtl,
6373 		"visibility",		    src->visibility,
6374 		"protected",		    src->is_protected,
6375 		"display-formulas",	    src->display_formulas,
6376 		"display-zeros",	   !src->hide_zero,
6377 		"display-grid",		   !src->hide_grid,
6378 		"display-column-header",   !src->hide_col_header,
6379 		"display-row-header",	   !src->hide_row_header,
6380 		"display-outlines",	    src->display_outlines,
6381 		"display-outlines-below",   src->outline_symbols_below,
6382 		"display-outlines-right",   src->outline_symbols_right,
6383 		"conventions",		    src->convs,
6384 		"tab-foreground",	    src->tab_text_color,
6385 		"tab-background",	    src->tab_color,
6386 		NULL);
6387 
6388 	gnm_print_info_free (dst->print_info);
6389 	dst->print_info = gnm_print_info_dup (src->print_info);
6390 
6391 	sheet_dup_styles         (src, dst);
6392 	sheet_dup_merged_regions (src, dst);
6393 	sheet_dup_colrows	 (src, dst);
6394 	sheet_dup_names		 (src, dst);
6395 	sheet_dup_cells		 (src, dst);
6396 	sheet_objects_dup	 (src, dst, NULL);
6397 	sheet_dup_filters	 (src, dst); /* must be after objects */
6398 
6399 #warning selection is in view
6400 #warning freeze/thaw is in view
6401 
6402 	g_object_unref (dst->solver_parameters);
6403 	dst->solver_parameters = gnm_solver_param_dup (src->solver_parameters, dst);
6404 
6405 	for (l = src->scenarios; l; l = l->next) {
6406 		GnmScenario *src_sc = l->data;
6407 		GnmScenario *dst_sc = gnm_scenario_dup (src_sc, dst);
6408 		dst->scenarios = g_list_prepend (dst->scenarios, dst_sc);
6409 	}
6410 	dst->scenarios = g_list_reverse (dst->scenarios);
6411 
6412 	sheet_mark_dirty (dst);
6413 	sheet_redraw_all (dst, TRUE);
6414 
6415 	return dst;
6416 }
6417 
6418 /**
6419  * sheet_set_outline_direction:
6420  * @sheet: the sheet
6421  * @is_cols: %TRUE for columns, %FALSE for rows.
6422  *
6423  * When changing the placement of outline collapse markers the flags
6424  * need to be recomputed.
6425  **/
6426 void
sheet_set_outline_direction(Sheet * sheet,gboolean is_cols)6427 sheet_set_outline_direction (Sheet *sheet, gboolean is_cols)
6428 {
6429 	unsigned i;
6430 	g_return_if_fail (IS_SHEET (sheet));
6431 
6432 	/* not particularly efficient, but this is not a hot spot */
6433 	for (i = colrow_max (is_cols, sheet); i-- > 0 ; )
6434 		sheet_colrow_set_collapse (sheet, is_cols, i);
6435 }
6436 
6437 /**
6438  * sheet_get_view:
6439  * @sheet:
6440  * @wbv:
6441  *
6442  * Find the SheetView corresponding to the supplied @wbv.
6443  * Returns: (transfer none): the view.
6444  */
6445 SheetView *
sheet_get_view(Sheet const * sheet,WorkbookView const * wbv)6446 sheet_get_view (Sheet const *sheet, WorkbookView const *wbv)
6447 {
6448 	if (sheet == NULL)
6449 		return NULL;
6450 
6451 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
6452 
6453 	SHEET_FOREACH_VIEW (sheet, view, {
6454 		if (sv_wbv (view) == wbv)
6455 			return view;
6456 	});
6457 	return NULL;
6458 }
6459 
6460 static gboolean
cb_queue_respan(GnmColRowIter const * iter,void * user_data)6461 cb_queue_respan (GnmColRowIter const *iter, void *user_data)
6462 {
6463 	((ColRowInfo *)(iter->cri))->needs_respan = TRUE;
6464 	return FALSE;
6465 }
6466 
6467 /**
6468  * sheet_queue_respan:
6469  * @sheet:
6470  * @start_row:
6471  * @end_row:
6472  *
6473  * queues a span generation for the selected rows.
6474  * the caller is responsible for queuing a redraw
6475  **/
6476 void
sheet_queue_respan(Sheet const * sheet,int start_row,int end_row)6477 sheet_queue_respan (Sheet const *sheet, int start_row, int end_row)
6478 {
6479 	sheet_colrow_foreach (sheet, FALSE, start_row, end_row,
6480 			      cb_queue_respan, NULL);
6481 }
6482 
6483 void
sheet_cell_queue_respan(GnmCell * cell)6484 sheet_cell_queue_respan (GnmCell *cell)
6485 {
6486 	ColRowInfo *ri = sheet_row_get (cell->base.sheet, cell->pos.row);
6487 	ri->needs_respan = TRUE;
6488 }
6489 
6490 
6491 /**
6492  * sheet_get_comment:
6493  * @sheet: #Sheet const *
6494  * @pos: #GnmCellPos const *
6495  *
6496  * If there is a cell comment at @pos in @sheet return it.
6497  *
6498  * Caller does get a reference to the object if it exists.
6499  * Returns: (transfer full): the comment or %NULL.
6500  **/
6501 GnmComment *
sheet_get_comment(Sheet const * sheet,GnmCellPos const * pos)6502 sheet_get_comment (Sheet const *sheet, GnmCellPos const *pos)
6503 {
6504 	GnmRange r;
6505 	GSList *comments;
6506 	GnmComment *res;
6507 
6508 	GnmRange const *mr;
6509 
6510 	mr = gnm_sheet_merge_contains_pos (sheet, pos);
6511 
6512 	if (mr)
6513 		comments = sheet_objects_get (sheet, mr, GNM_CELL_COMMENT_TYPE);
6514 	else {
6515 		r.start = r.end = *pos;
6516 		comments = sheet_objects_get (sheet, &r, GNM_CELL_COMMENT_TYPE);
6517 	}
6518 	if (!comments)
6519 		return NULL;
6520 
6521 	/* This assumes just one comment per cell.  */
6522 	res = comments->data;
6523 	g_slist_free (comments);
6524 	return res;
6525 }
6526 
6527 static GnmValue *
cb_find_extents(GnmCellIter const * iter,GnmCellPos * extent)6528 cb_find_extents (GnmCellIter const *iter, GnmCellPos *extent)
6529 {
6530 	if (extent->col < iter->pp.eval.col)
6531 		extent->col = iter->pp.eval.col;
6532 	if (extent->row < iter->pp.eval.row)
6533 		extent->row = iter->pp.eval.row;
6534 	return NULL;
6535 }
6536 
6537 /**
6538  * sheet_range_trim:
6539  * @sheet: sheet cells are contained on
6540  * @r:	   range to trim empty cells from
6541  * @cols:  trim from right
6542  * @rows:  trim from bottom
6543  *
6544  * This removes empty rows/cols from the
6545  * right hand or bottom edges of the range
6546  * depending on the value of @cols or @rows.
6547  *
6548  * Returns: %TRUE if the range was totally empty.
6549  **/
6550 gboolean
sheet_range_trim(Sheet const * sheet,GnmRange * r,gboolean cols,gboolean rows)6551 sheet_range_trim (Sheet const *sheet, GnmRange *r,
6552 		  gboolean cols, gboolean rows)
6553 {
6554 	GnmCellPos extent = { -1, -1 };
6555 
6556 	g_return_val_if_fail (IS_SHEET (sheet), TRUE);
6557 	g_return_val_if_fail (r != NULL, TRUE);
6558 
6559 	sheet_foreach_cell_in_range (
6560 		(Sheet *)sheet, CELL_ITER_IGNORE_BLANK, r,
6561 		(CellIterFunc) cb_find_extents, &extent);
6562 
6563 	if (extent.col < 0 || extent.row < 0)
6564 		return TRUE;
6565 	if (cols)
6566 		r->end.col = extent.col;
6567 	if (rows)
6568 		r->end.row = extent.row;
6569 	return FALSE;
6570 }
6571 
6572 /**
6573  * sheet_range_has_heading:
6574  * @sheet: Sheet to check
6575  * @src: GnmRange to check
6576  * @top: Flag
6577  *
6578  * Checks for a header row in @sheet!@src.  If top is true it looks for a
6579  * header row from the top and if false it looks for a header col from the
6580  * left
6581  *
6582  * Returns: %TRUE if @src seems to have a heading
6583  **/
6584 gboolean
sheet_range_has_heading(Sheet const * sheet,GnmRange const * src,gboolean top,gboolean ignore_styles)6585 sheet_range_has_heading (Sheet const *sheet, GnmRange const *src,
6586 			gboolean top, gboolean ignore_styles)
6587 {
6588 	GnmCell const *a, *b;
6589 	int length, i;
6590 
6591 	/* There is only one row or col */
6592 	if (top) {
6593 		if (src->end.row <= src->start.row)
6594 			return FALSE;
6595 		length = src->end.col - src->start.col + 1;
6596 	} else {
6597 		if (src->end.col <= src->start.col)
6598 			return FALSE;
6599 		length = src->end.row - src->start.row + 1;
6600 	}
6601 
6602 	for (i = 0; i < length; i++) {
6603 		if (top) {
6604 			a = sheet_cell_get (sheet,
6605 				src->start.col + i, src->start.row);
6606 			b = sheet_cell_get (sheet,
6607 				src->start.col + i, src->start.row + 1);
6608 		} else {
6609 			a = sheet_cell_get (sheet,
6610 				src->start.col, src->start.row + i);
6611 			b = sheet_cell_get (sheet,
6612 				src->start.col + 1, src->start.row + i);
6613 		}
6614 
6615 		/* be anal */
6616 		if (a == NULL || a->value == NULL || b == NULL || b->value == NULL)
6617 			continue;
6618 
6619 		if (VALUE_IS_NUMBER (a->value)) {
6620 			if (!VALUE_IS_NUMBER (b->value))
6621 				return TRUE;
6622 			/* check for style differences */
6623 		} else if (a->value->v_any.type != b->value->v_any.type)
6624 			return TRUE;
6625 
6626 		/* Look for style differences */
6627 		if (!ignore_styles &&
6628 		    !gnm_style_equal_header (gnm_cell_get_style (a),
6629 					     gnm_cell_get_style (b), top))
6630 			return TRUE;
6631 	}
6632 
6633 	return FALSE;
6634 }
6635 
6636 /**
6637  * gnm_sheet_foreach_name:
6638  * @sheet: #Sheet
6639  * @func: (scope call): #GHFunc
6640  * @data: user data.
6641  *
6642  * Executes @func for each name in @sheet.
6643  **/
6644 void
gnm_sheet_foreach_name(Sheet const * sheet,GHFunc func,gpointer data)6645 gnm_sheet_foreach_name (Sheet const *sheet, GHFunc func, gpointer data)
6646 {
6647 	g_return_if_fail (IS_SHEET (sheet));
6648 
6649 	if (sheet->names)
6650 		gnm_named_expr_collection_foreach (sheet->names, func, data);
6651 }
6652 
6653 /**
6654  * gnm_sheet_get_size:
6655  * @sheet: #Sheet
6656  *
6657  * Returns: (transfer none): the sheet size.
6658  **/
6659 GnmSheetSize const *
gnm_sheet_get_size(Sheet const * sheet)6660 gnm_sheet_get_size (Sheet const *sheet)
6661 {
6662 	static const GnmSheetSize default_size = {
6663 		GNM_DEFAULT_COLS, GNM_DEFAULT_ROWS
6664 	};
6665 
6666 	if (G_UNLIKELY (!sheet)) {
6667 		g_warning ("NULL sheet in gnm_sheet_get_size!");
6668 		/* FIXME: This needs to go.  */
6669 		return &default_size;
6670 	}
6671 
6672 	if (G_UNLIKELY (sheet->being_constructed))
6673 		g_warning ("Access to sheet size during construction!");
6674 
6675 	return &sheet->size;
6676 }
6677 
6678 /**
6679  * gnm_sheet_get_size2:
6680  * @sheet: #Sheet, might be %NULL
6681  * @wb: #Workbook, must be non %NULL if @sheet is %NULL
6682  *
6683  * Returns: (transfer none): the sheet size if @sheet is non %NULL, or the
6684  * default sheet size for @wb.
6685  **/
6686 GnmSheetSize const *
gnm_sheet_get_size2(Sheet const * sheet,Workbook const * wb)6687 gnm_sheet_get_size2 (Sheet const *sheet, Workbook const *wb)
6688 {
6689 	return sheet
6690 		? gnm_sheet_get_size (sheet)
6691 		: workbook_get_sheet_size (wb);
6692 }
6693 
6694 void
gnm_sheet_set_solver_params(Sheet * sheet,GnmSolverParameters * param)6695 gnm_sheet_set_solver_params (Sheet *sheet, GnmSolverParameters *param)
6696 {
6697 	g_return_if_fail (IS_SHEET (sheet));
6698 	g_return_if_fail (GNM_IS_SOLVER_PARAMETERS (param));
6699 
6700 	g_object_ref (param);
6701 	g_object_unref (sheet->solver_parameters);
6702 	sheet->solver_parameters = param;
6703 }
6704 
6705 /* ------------------------------------------------------------------------- */
6706 
6707 /**
6708  * gnm_sheet_scenario_new: (skip)
6709  * @sheet:  #Sheet
6710  * @name: the new scenario name.
6711  *
6712  * Returns: (transfer full): the newly created #GnmScenario.
6713  **/
6714 GnmScenario *
gnm_sheet_scenario_new(Sheet * sheet,const char * name)6715 gnm_sheet_scenario_new (Sheet *sheet, const char *name)
6716 {
6717 	GnmScenario *sc;
6718 	char *actual_name;
6719 
6720 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
6721 	g_return_val_if_fail (name != NULL, NULL);
6722 
6723 	/* Check if a scenario having the same name already exists. */
6724 	if (gnm_sheet_scenario_find (sheet, name)) {
6725 		GString *str = g_string_new (NULL);
6726 		gchar   *tmp;
6727 		int     i, j, len;
6728 
6729 		len = strlen (name);
6730 		if (len > 1 && name [len - 1] == ']') {
6731 			for (i = len - 2; i > 0; i--) {
6732 				if (! g_ascii_isdigit (name [i]))
6733 					break;
6734 			}
6735 
6736 			tmp = g_strdup (name);
6737 			if (i > 0 && name [i] == '[')
6738 				tmp [i] = '\0';
6739 		} else
6740 			tmp = g_strdup (name);
6741 
6742 		for (j = 1; ; j++) {
6743 			g_string_printf (str, "%s [%d]", tmp, j);
6744 			if (!gnm_sheet_scenario_find (sheet, str->str)) {
6745 				actual_name = g_string_free (str, FALSE);
6746 				str = NULL;
6747 				break;
6748 			}
6749 		}
6750 		if (str)
6751 			g_string_free (str, TRUE);
6752 		g_free (tmp);
6753 	} else
6754 		actual_name = g_strdup (name);
6755 
6756 	sc = gnm_scenario_new (actual_name, sheet);
6757 
6758 	g_free (actual_name);
6759 
6760 	return sc;
6761 }
6762 
6763 /**
6764  * gnm_sheet_scenario_find:
6765  * @sheet:  #Sheet
6766  * @name: the scenario name.
6767  *
6768  * Returns: (transfer none): the newly created #GnmScenario.
6769  **/
6770 GnmScenario *
gnm_sheet_scenario_find(Sheet * sheet,const char * name)6771 gnm_sheet_scenario_find (Sheet *sheet, const char *name)
6772 {
6773 	GList *l;
6774 
6775 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
6776 	g_return_val_if_fail (name != NULL, NULL);
6777 
6778 	for (l = sheet->scenarios; l; l = l->next) {
6779 		GnmScenario *sc = l->data;
6780 		if (strcmp (name, sc->name) == 0)
6781 			return sc;
6782 	}
6783 
6784 	return NULL;
6785 }
6786 
6787 /**
6788  * gnm_sheet_scenario_add:
6789  * @sheet:  #Sheet
6790  * @sc: (transfer full): #GnmScenario
6791  *
6792  **/
6793 void
gnm_sheet_scenario_add(Sheet * sheet,GnmScenario * sc)6794 gnm_sheet_scenario_add (Sheet *sheet, GnmScenario *sc)
6795 {
6796 	g_return_if_fail (IS_SHEET (sheet));
6797 	g_return_if_fail (GNM_IS_SCENARIO (sc));
6798 
6799 	/* We take ownership of the ref.  */
6800 	sheet->scenarios = g_list_append (sheet->scenarios, sc);
6801 }
6802 
6803 void
gnm_sheet_scenario_remove(Sheet * sheet,GnmScenario * sc)6804 gnm_sheet_scenario_remove (Sheet *sheet, GnmScenario *sc)
6805 {
6806 	g_return_if_fail (IS_SHEET (sheet));
6807 	g_return_if_fail (GNM_IS_SCENARIO (sc));
6808 
6809 	sheet->scenarios = g_list_remove (sheet->scenarios, sc);
6810 	g_object_unref (sc);
6811 }
6812 
6813 /* ------------------------------------------------------------------------- */
6814 
6815 /**
6816  * gnm_sheet_get_sort_setups:
6817  * @sheet: #Sheet
6818  *
6819  * Returns: (transfer none): the sort setups for @sheet.
6820  **/
6821 GHashTable *
gnm_sheet_get_sort_setups(Sheet * sheet)6822 gnm_sheet_get_sort_setups (Sheet *sheet)
6823 {
6824 	GHashTable *hash = sheet->sort_setups;
6825 
6826 	if (hash == NULL)
6827 		hash = sheet->sort_setups =
6828 			g_hash_table_new_full
6829 			(g_str_hash, g_str_equal,
6830 			 g_free, (GDestroyNotify)gnm_sort_data_destroy);
6831 
6832 	return hash;
6833 }
6834 
6835 void
gnm_sheet_add_sort_setup(Sheet * sheet,char * key,gpointer setup)6836 gnm_sheet_add_sort_setup (Sheet *sheet, char *key, gpointer setup)
6837 {
6838 	GHashTable *hash = gnm_sheet_get_sort_setups (sheet);
6839 
6840 	g_hash_table_insert (hash, key, setup);
6841 }
6842 
6843 /**
6844  * gnm_sheet_find_sort_setup:
6845  * @sheet: #Sheet
6846  * @key:
6847  *
6848  * Returns: (transfer none): the found sort setup or %NULL.
6849  **/
6850 gconstpointer
gnm_sheet_find_sort_setup(Sheet * sheet,char const * key)6851 gnm_sheet_find_sort_setup (Sheet *sheet, char const *key)
6852 {
6853 	if (sheet->sort_setups == NULL)
6854 		return NULL;
6855 	return g_hash_table_lookup (sheet->sort_setups, key);
6856 }
6857 
6858 /**
6859  * sheet_date_conv:
6860  * @sheet: #Sheet
6861  *
6862  * Returns: (transfer none): the date conventions in effect for the sheet.
6863  * This is purely a convenience function to access the conventions used
6864  * for the workbook.  All sheets in a workbook share the same date
6865  * conventions.
6866  **/
6867 GODateConventions const *
sheet_date_conv(Sheet const * sheet)6868 sheet_date_conv (Sheet const *sheet)
6869 {
6870 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
6871 	return workbook_date_conv (sheet->workbook);
6872 }
6873