Introspection: add basic io
[gnumeric.git] / src / sheet.c
blob262548e62b1c5d5f21698fff75e4bda6b86fbf17
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /*
4 * sheet.c: Implements the sheet management and per-sheet storage
6 * Copyright (C) 2000-2007 Jody Goldberg (jody@gnome.org)
7 * Copyright (C) 1997-1999 Miguel de Icaza (miguel@kernel.org)
8 * Copyright (C) 1999-2009 Morten Welinder (terra@gnome.org)
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License as
12 * published by the Free Software Foundation; either version 2 of the
13 * License, or (at your option) version 3.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
23 * USA
25 #include <gnumeric-config.h>
26 #include "gnumeric.h"
27 #include "sheet.h"
29 #include "sheet-view.h"
30 #include "command-context.h"
31 #include "sheet-control.h"
32 #include "sheet-style.h"
33 #include "workbook-priv.h"
34 #include "workbook-control.h"
35 #include "workbook-view.h"
36 #include "parse-util.h"
37 #include "dependent.h"
38 #include "value.h"
39 #include "number-match.h"
40 #include "clipboard.h"
41 #include "selection.h"
42 #include "ranges.h"
43 #include "print-info.h"
44 #include "mstyle.h"
45 #include "style-color.h"
46 #include "style-font.h"
47 #include "application.h"
48 #include "commands.h"
49 #include "cellspan.h"
50 #include "cell.h"
51 #include "sheet-merge.h"
52 #include "sheet-private.h"
53 #include "expr-name.h"
54 #include "expr.h"
55 #include "rendered-value.h"
56 #include "gnumeric-conf.h"
57 #include "sheet-object-impl.h"
58 #include "sheet-object-cell-comment.h"
59 #include <tools/gnm-solver.h>
60 #include "hlink.h"
61 #include "sheet-filter.h"
62 #include "sheet-filter-combo.h"
63 #include "gnm-sheet-slicer.h"
64 #include "scenarios.h"
65 #include "cell-draw.h"
66 #include "sort.h"
67 #include "gutils.h"
68 #include <goffice/goffice.h>
70 #include "gnm-i18n.h"
71 #include <gsf/gsf-impl-utils.h>
72 #include <stdlib.h>
73 #include <string.h>
75 static GnmSheetSize *
76 gnm_sheet_size_copy (GnmSheetSize *size)
78 GnmSheetSize *res = g_new (GnmSheetSize, 1);
79 *res = *size;
80 return res;
83 GType
84 gnm_sheet_size_get_type (void)
86 static GType t = 0;
88 if (t == 0) {
89 t = g_boxed_type_register_static ("GnmSheetSize",
90 (GBoxedCopyFunc)gnm_sheet_size_copy,
91 (GBoxedFreeFunc)g_free);
93 return t;
96 enum {
97 DETACHED_FROM_WORKBOOK,
98 LAST_SIGNAL
101 static guint signals [LAST_SIGNAL] = { 0 };
103 typedef struct {
104 GObjectClass parent;
106 void (*detached_from_workbook) (Sheet *, Workbook *wb);
107 } GnmSheetClass;
108 typedef Sheet GnmSheet;
110 enum {
111 PROP_0,
112 PROP_SHEET_TYPE,
113 PROP_WORKBOOK,
114 PROP_NAME,
115 PROP_RTL,
116 PROP_VISIBILITY,
117 PROP_DISPLAY_FORMULAS,
118 PROP_DISPLAY_ZEROS,
119 PROP_DISPLAY_GRID,
120 PROP_DISPLAY_COLUMN_HEADER,
121 PROP_DISPLAY_ROW_HEADER,
122 PROP_DISPLAY_OUTLINES,
123 PROP_DISPLAY_OUTLINES_BELOW,
124 PROP_DISPLAY_OUTLINES_RIGHT,
126 PROP_PROTECTED,
127 PROP_PROTECTED_ALLOW_EDIT_OBJECTS,
128 PROP_PROTECTED_ALLOW_EDIT_SCENARIOS,
129 PROP_PROTECTED_ALLOW_CELL_FORMATTING,
130 PROP_PROTECTED_ALLOW_COLUMN_FORMATTING,
131 PROP_PROTECTED_ALLOW_ROW_FORMATTING,
132 PROP_PROTECTED_ALLOW_INSERT_COLUMNS,
133 PROP_PROTECTED_ALLOW_INSERT_ROWS,
134 PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS,
135 PROP_PROTECTED_ALLOW_DELETE_COLUMNS,
136 PROP_PROTECTED_ALLOW_DELETE_ROWS,
137 PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS,
138 PROP_PROTECTED_ALLOW_SORT_RANGES,
139 PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS,
140 PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE,
141 PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS,
143 PROP_CONVENTIONS,
144 PROP_USE_R1C1,
146 PROP_TAB_FOREGROUND,
147 PROP_TAB_BACKGROUND,
148 PROP_ZOOM_FACTOR,
150 PROP_COLUMNS,
151 PROP_ROWS
154 static void gnm_sheet_finalize (GObject *obj);
156 static GObjectClass *parent_class;
158 static void
159 sheet_set_direction (Sheet *sheet, gboolean text_is_rtl)
161 GnmRange r;
163 text_is_rtl = !!text_is_rtl;
164 if (text_is_rtl == sheet->text_is_rtl)
165 return;
167 sheet_mark_dirty (sheet);
169 sheet->text_is_rtl = text_is_rtl;
170 sheet->priv->reposition_objects.col = 0;
171 sheet_range_calc_spans (sheet,
172 range_init_full_sheet (&r, sheet),
173 GNM_SPANCALC_RE_RENDER);
176 static void
177 sheet_set_visibility (Sheet *sheet, GnmSheetVisibility visibility)
179 if (sheet->visibility == visibility)
180 return;
182 sheet->visibility = visibility;
183 sheet_mark_dirty (sheet);
186 static void
187 cb_re_render_formulas (G_GNUC_UNUSED gpointer unused,
188 GnmCell *cell,
189 G_GNUC_UNUSED gpointer user)
191 if (gnm_cell_has_expr (cell)) {
192 gnm_cell_unrender (cell);
193 sheet_cell_queue_respan (cell);
197 static void
198 re_render_formulas (Sheet const *sheet)
200 sheet_cell_foreach (sheet, (GHFunc)cb_re_render_formulas, NULL);
203 static void
204 sheet_set_conventions (Sheet *sheet, GnmConventions const *convs)
206 if (sheet->convs == convs)
207 return;
208 sheet->convs = convs;
209 if (sheet->display_formulas)
210 re_render_formulas (sheet);
211 SHEET_FOREACH_VIEW (sheet, sv,
212 sv->edit_pos_changed.content = TRUE;);
213 sheet_mark_dirty (sheet);
216 GnmConventions const *
217 sheet_get_conventions (Sheet const *sheet)
219 g_return_val_if_fail (IS_SHEET (sheet), gnm_conventions_default);
221 return sheet->convs;
224 static void
225 cb_sheet_set_hide_zeros (G_GNUC_UNUSED gpointer unused,
226 GnmCell *cell,
227 G_GNUC_UNUSED gpointer user)
229 if (gnm_cell_is_zero (cell))
230 gnm_cell_unrender (cell);
233 static void
234 sheet_set_hide_zeros (Sheet *sheet, gboolean hide)
236 hide = !!hide;
237 if (sheet->hide_zero == hide)
238 return;
240 sheet->hide_zero = hide;
241 sheet_mark_dirty (sheet);
243 sheet_cell_foreach (sheet, (GHFunc)cb_sheet_set_hide_zeros, NULL);
246 static void
247 sheet_set_name (Sheet *sheet, char const *new_name)
249 Workbook *wb = sheet->workbook;
250 gboolean attached;
251 Sheet *sucker;
252 char *new_name_unquoted;
254 g_return_if_fail (new_name != NULL);
256 /* No change whatsoever. */
257 if (go_str_compare (sheet->name_unquoted, new_name) == 0)
258 return;
260 /* Mark the sheet dirty unless this is the initial name. */
261 if (sheet->name_unquoted)
262 sheet_mark_dirty (sheet);
264 sucker = wb ? workbook_sheet_by_name (wb, new_name) : NULL;
265 if (sucker && sucker != sheet) {
267 * Prevent a name clash. With this you can swap names by
268 * setting just the two names.
270 char *sucker_name = workbook_sheet_get_free_name (wb, new_name, TRUE, FALSE);
271 #if 0
272 g_warning ("Renaming %s to %s to avoid clash.\n", sucker->name_unquoted, sucker_name);
273 #endif
274 g_object_set (sucker, "name", sucker_name, NULL);
275 g_free (sucker_name);
278 attached = wb != NULL &&
279 sheet->index_in_wb != -1 &&
280 sheet->name_case_insensitive;
282 /* FIXME: maybe have workbook_sheet_detach_internal for this. */
283 if (attached)
284 g_hash_table_remove (wb->sheet_hash_private,
285 sheet->name_case_insensitive);
287 /* Copy before free. */
288 new_name_unquoted = g_strdup (new_name);
290 g_free (sheet->name_unquoted);
291 g_free (sheet->name_quoted);
292 g_free (sheet->name_unquoted_collate_key);
293 g_free (sheet->name_case_insensitive);
294 sheet->name_unquoted = new_name_unquoted;
295 sheet->name_quoted = g_string_free
296 (gnm_expr_conv_quote (sheet->convs, new_name_unquoted),
297 FALSE);
298 sheet->name_unquoted_collate_key =
299 g_utf8_collate_key (new_name_unquoted, -1);
300 sheet->name_case_insensitive =
301 g_utf8_casefold (new_name_unquoted, -1);
303 /* FIXME: maybe have workbook_sheet_attach_internal for this. */
304 if (attached)
305 g_hash_table_insert (wb->sheet_hash_private,
306 sheet->name_case_insensitive,
307 sheet);
309 if (!sheet->being_constructed &&
310 sheet->sheet_type == GNM_SHEET_DATA) {
311 /* We have to fix the Sheet_Title name */
312 GnmNamedExpr *nexpr;
313 GnmParsePos pp;
315 parse_pos_init_sheet (&pp, sheet);
316 nexpr = expr_name_lookup (&pp, "Sheet_Title");
317 if (nexpr) {
318 GnmExprTop const *texpr =
319 gnm_expr_top_new_constant
320 (value_new_string (sheet->name_unquoted));
321 expr_name_set_expr (nexpr, texpr);
326 struct resize_colrow {
327 Sheet *sheet;
328 gboolean is_cols;
329 double scale;
332 static gboolean
333 cb_colrow_compute_pixels_from_pts (GnmColRowIter const *iter,
334 struct resize_colrow *data)
336 colrow_compute_pixels_from_pts ((ColRowInfo *)iter->cri,
337 data->sheet, data->is_cols,
338 data->scale);
339 return FALSE;
342 static void
343 cb_clear_rendered_cells (G_GNUC_UNUSED gpointer ignored, GnmCell *cell)
345 if (gnm_cell_get_rendered_value (cell) != NULL) {
346 sheet_cell_queue_respan (cell);
347 gnm_cell_unrender (cell);
351 static void
352 sheet_scale_changed (Sheet *sheet, gboolean cols_rescaled, gboolean rows_rescaled)
354 g_return_if_fail (cols_rescaled || rows_rescaled);
356 /* Then every column and row */
357 if (cols_rescaled) {
358 struct resize_colrow closure;
360 closure.sheet = sheet;
361 closure.is_cols = TRUE;
362 closure.scale = colrow_compute_pixel_scale (sheet, TRUE);
364 colrow_compute_pixels_from_pts (&sheet->cols.default_style,
365 sheet, TRUE, closure.scale);
366 colrow_foreach (&sheet->cols, 0, gnm_sheet_get_last_col (sheet),
367 (ColRowHandler)&cb_colrow_compute_pixels_from_pts, &closure);
369 if (rows_rescaled) {
370 struct resize_colrow closure;
372 closure.sheet = sheet;
373 closure.is_cols = FALSE;
374 closure.scale = colrow_compute_pixel_scale (sheet, FALSE);
376 colrow_compute_pixels_from_pts (&sheet->rows.default_style,
377 sheet, FALSE, closure.scale);
378 colrow_foreach (&sheet->rows, 0, gnm_sheet_get_last_row (sheet),
379 (ColRowHandler)&cb_colrow_compute_pixels_from_pts, &closure);
382 sheet_cell_foreach (sheet, (GHFunc)&cb_clear_rendered_cells, NULL);
383 SHEET_FOREACH_CONTROL (sheet, view, control, sc_scale_changed (control););
386 static void
387 sheet_set_display_formulas (Sheet *sheet, gboolean display)
389 display = !!display;
390 if (sheet->display_formulas == display)
391 return;
393 sheet->display_formulas = display;
394 sheet_mark_dirty (sheet);
395 if (!sheet->being_constructed)
396 sheet_scale_changed (sheet, TRUE, FALSE);
399 static void
400 sheet_set_zoom_factor (Sheet *sheet, double factor)
402 if (fabs (factor - sheet->last_zoom_factor_used) < 1e-6)
403 return;
404 sheet->last_zoom_factor_used = factor;
405 if (!sheet->being_constructed)
406 sheet_scale_changed (sheet, TRUE, TRUE);
409 static void
410 gnm_sheet_set_property (GObject *object, guint property_id,
411 GValue const *value, GParamSpec *pspec)
413 Sheet *sheet = (Sheet *)object;
415 switch (property_id) {
416 case PROP_SHEET_TYPE:
417 /* Construction-time only */
418 sheet->sheet_type = g_value_get_enum (value);
419 break;
420 case PROP_WORKBOOK:
421 /* Construction-time only */
422 sheet->workbook = g_value_get_object (value);
423 break;
424 case PROP_NAME:
425 sheet_set_name (sheet, g_value_get_string (value));
426 break;
427 case PROP_RTL:
428 sheet_set_direction (sheet, g_value_get_boolean (value));
429 break;
430 case PROP_VISIBILITY:
431 sheet_set_visibility (sheet, g_value_get_enum (value));
432 break;
433 case PROP_DISPLAY_FORMULAS:
434 sheet_set_display_formulas (sheet, g_value_get_boolean (value));
435 break;
436 case PROP_DISPLAY_ZEROS:
437 sheet_set_hide_zeros (sheet, !g_value_get_boolean (value));
438 break;
439 case PROP_DISPLAY_GRID:
440 sheet->hide_grid = !g_value_get_boolean (value);
441 break;
442 case PROP_DISPLAY_COLUMN_HEADER:
443 sheet->hide_col_header = !g_value_get_boolean (value);
444 break;
445 case PROP_DISPLAY_ROW_HEADER:
446 sheet->hide_row_header = !g_value_get_boolean (value);
447 break;
448 case PROP_DISPLAY_OUTLINES:
449 sheet->display_outlines = !!g_value_get_boolean (value);
450 break;
451 case PROP_DISPLAY_OUTLINES_BELOW:
452 sheet->outline_symbols_below = !!g_value_get_boolean (value);
453 break;
454 case PROP_DISPLAY_OUTLINES_RIGHT:
455 sheet->outline_symbols_right = !!g_value_get_boolean (value);
456 break;
458 case PROP_PROTECTED :
459 sheet->is_protected = !!g_value_get_boolean (value);
460 break;
461 case PROP_PROTECTED_ALLOW_EDIT_OBJECTS :
462 sheet->protected_allow.edit_objects = !!g_value_get_boolean (value);
463 break;
464 case PROP_PROTECTED_ALLOW_EDIT_SCENARIOS :
465 sheet->protected_allow.edit_scenarios = !!g_value_get_boolean (value);
466 break;
467 case PROP_PROTECTED_ALLOW_CELL_FORMATTING :
468 sheet->protected_allow.cell_formatting = !!g_value_get_boolean (value);
469 break;
470 case PROP_PROTECTED_ALLOW_COLUMN_FORMATTING :
471 sheet->protected_allow.column_formatting = !!g_value_get_boolean (value);
472 break;
473 case PROP_PROTECTED_ALLOW_ROW_FORMATTING :
474 sheet->protected_allow.row_formatting = !!g_value_get_boolean (value);
475 break;
476 case PROP_PROTECTED_ALLOW_INSERT_COLUMNS :
477 sheet->protected_allow.insert_columns = !!g_value_get_boolean (value);
478 break;
479 case PROP_PROTECTED_ALLOW_INSERT_ROWS :
480 sheet->protected_allow.insert_rows = !!g_value_get_boolean (value);
481 break;
482 case PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS :
483 sheet->protected_allow.insert_hyperlinks = !!g_value_get_boolean (value);
484 break;
485 case PROP_PROTECTED_ALLOW_DELETE_COLUMNS :
486 sheet->protected_allow.delete_columns = !!g_value_get_boolean (value);
487 break;
488 case PROP_PROTECTED_ALLOW_DELETE_ROWS :
489 sheet->protected_allow.delete_rows = !!g_value_get_boolean (value);
490 break;
491 case PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS :
492 sheet->protected_allow.select_locked_cells = !!g_value_get_boolean (value);
493 break;
494 case PROP_PROTECTED_ALLOW_SORT_RANGES :
495 sheet->protected_allow.sort_ranges = !!g_value_get_boolean (value);
496 break;
497 case PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS :
498 sheet->protected_allow.edit_auto_filters = !!g_value_get_boolean (value);
499 break;
500 case PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE :
501 sheet->protected_allow.edit_pivottable = !!g_value_get_boolean (value);
502 break;
503 case PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS :
504 sheet->protected_allow.select_unlocked_cells = !!g_value_get_boolean (value);
505 break;
507 case PROP_CONVENTIONS:
508 sheet_set_conventions (sheet, g_value_get_pointer (value));
509 break;
510 case PROP_USE_R1C1: /* convenience api */
511 sheet_set_conventions (sheet, !!g_value_get_boolean (value)
512 ? gnm_conventions_xls_r1c1 : gnm_conventions_default);
513 break;
515 case PROP_TAB_FOREGROUND: {
516 GnmColor *color = g_value_dup_boxed (value);
517 style_color_unref (sheet->tab_text_color);
518 sheet->tab_text_color = color;
519 sheet_mark_dirty (sheet);
520 break;
522 case PROP_TAB_BACKGROUND: {
523 GnmColor *color = g_value_dup_boxed (value);
524 style_color_unref (sheet->tab_color);
525 sheet->tab_color = color;
526 sheet_mark_dirty (sheet);
527 break;
529 case PROP_ZOOM_FACTOR:
530 sheet_set_zoom_factor (sheet, g_value_get_double (value));
531 break;
532 case PROP_COLUMNS:
533 /* Construction-time only */
534 sheet->size.max_cols = g_value_get_int (value);
535 break;
536 case PROP_ROWS:
537 /* Construction-time only */
538 sheet->size.max_rows = g_value_get_int (value);
539 break;
540 default:
541 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
542 break;
546 static void
547 gnm_sheet_get_property (GObject *object, guint property_id,
548 GValue *value, GParamSpec *pspec)
550 Sheet *sheet = (Sheet *)object;
552 switch (property_id) {
553 case PROP_SHEET_TYPE:
554 g_value_set_enum (value, sheet->sheet_type);
555 break;
556 case PROP_WORKBOOK:
557 g_value_set_object (value, sheet->workbook);
558 break;
559 case PROP_NAME:
560 g_value_set_string (value, sheet->name_unquoted);
561 break;
562 case PROP_RTL:
563 g_value_set_boolean (value, sheet->text_is_rtl);
564 break;
565 case PROP_VISIBILITY:
566 g_value_set_enum (value, sheet->visibility);
567 break;
568 case PROP_DISPLAY_FORMULAS:
569 g_value_set_boolean (value, sheet->display_formulas);
570 break;
571 case PROP_DISPLAY_ZEROS:
572 g_value_set_boolean (value, !sheet->hide_zero);
573 break;
574 case PROP_DISPLAY_GRID:
575 g_value_set_boolean (value, !sheet->hide_grid);
576 break;
577 case PROP_DISPLAY_COLUMN_HEADER:
578 g_value_set_boolean (value, !sheet->hide_col_header);
579 break;
580 case PROP_DISPLAY_ROW_HEADER:
581 g_value_set_boolean (value, !sheet->hide_row_header);
582 break;
583 case PROP_DISPLAY_OUTLINES:
584 g_value_set_boolean (value, sheet->display_outlines);
585 break;
586 case PROP_DISPLAY_OUTLINES_BELOW:
587 g_value_set_boolean (value, sheet->outline_symbols_below);
588 break;
589 case PROP_DISPLAY_OUTLINES_RIGHT:
590 g_value_set_boolean (value, sheet->outline_symbols_right);
591 break;
593 case PROP_PROTECTED :
594 g_value_set_boolean (value, sheet->is_protected);
595 break;
596 case PROP_PROTECTED_ALLOW_EDIT_OBJECTS :
597 g_value_set_boolean (value, sheet->protected_allow.edit_objects);
598 break;
599 case PROP_PROTECTED_ALLOW_EDIT_SCENARIOS :
600 g_value_set_boolean (value, sheet->protected_allow.edit_scenarios);
601 break;
602 case PROP_PROTECTED_ALLOW_CELL_FORMATTING :
603 g_value_set_boolean (value, sheet->protected_allow.cell_formatting);
604 break;
605 case PROP_PROTECTED_ALLOW_COLUMN_FORMATTING :
606 g_value_set_boolean (value, sheet->protected_allow.column_formatting);
607 break;
608 case PROP_PROTECTED_ALLOW_ROW_FORMATTING :
609 g_value_set_boolean (value, sheet->protected_allow.row_formatting);
610 break;
611 case PROP_PROTECTED_ALLOW_INSERT_COLUMNS :
612 g_value_set_boolean (value, sheet->protected_allow.insert_columns);
613 break;
614 case PROP_PROTECTED_ALLOW_INSERT_ROWS :
615 g_value_set_boolean (value, sheet->protected_allow.insert_rows);
616 break;
617 case PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS :
618 g_value_set_boolean (value, sheet->protected_allow.insert_hyperlinks);
619 break;
620 case PROP_PROTECTED_ALLOW_DELETE_COLUMNS :
621 g_value_set_boolean (value, sheet->protected_allow.delete_columns);
622 break;
623 case PROP_PROTECTED_ALLOW_DELETE_ROWS :
624 g_value_set_boolean (value, sheet->protected_allow.delete_rows);
625 break;
626 case PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS :
627 g_value_set_boolean (value, sheet->protected_allow.select_locked_cells);
628 break;
629 case PROP_PROTECTED_ALLOW_SORT_RANGES :
630 g_value_set_boolean (value, sheet->protected_allow.sort_ranges);
631 break;
632 case PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS :
633 g_value_set_boolean (value, sheet->protected_allow.edit_auto_filters);
634 break;
635 case PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE :
636 g_value_set_boolean (value, sheet->protected_allow.edit_pivottable);
637 break;
638 case PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS :
639 g_value_set_boolean (value, sheet->protected_allow.select_unlocked_cells);
640 break;
642 case PROP_CONVENTIONS:
643 g_value_set_pointer (value, (gpointer)sheet->convs);
644 break;
645 case PROP_USE_R1C1: /* convenience api */
646 g_value_set_boolean (value, sheet->convs->r1c1_addresses);
647 break;
649 case PROP_TAB_FOREGROUND:
650 g_value_set_boxed (value, sheet->tab_text_color);
651 break;
652 case PROP_TAB_BACKGROUND:
653 g_value_set_boxed (value, sheet->tab_color);
654 break;
655 case PROP_ZOOM_FACTOR:
656 g_value_set_double (value, sheet->last_zoom_factor_used);
657 break;
658 case PROP_COLUMNS:
659 g_value_set_int (value, sheet->size.max_cols);
660 break;
661 case PROP_ROWS:
662 g_value_set_int (value, sheet->size.max_rows);
663 break;
664 default:
665 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
666 break;
670 static void
671 gnm_sheet_constructed (GObject *obj)
673 Sheet *sheet = SHEET (obj);
675 if (parent_class->constructed)
676 parent_class->constructed (obj);
678 /* Now sheet_type, max_cols, and max_rows have been set. */
679 sheet->being_constructed = FALSE;
681 colrow_resize (&sheet->cols, sheet->size.max_cols);
682 colrow_resize (&sheet->rows, sheet->size.max_rows);
684 sheet->priv->reposition_objects.col = sheet->size.max_cols;
685 sheet->priv->reposition_objects.row = sheet->size.max_rows;
687 range_init_full_sheet (&sheet->priv->unhidden_region, sheet);
688 sheet_style_init (sheet);
690 sheet->deps = gnm_dep_container_new (sheet);
692 switch (sheet->sheet_type) {
693 case GNM_SHEET_XLM:
694 sheet->display_formulas = TRUE;
695 break;
696 case GNM_SHEET_OBJECT:
697 sheet->hide_grid = TRUE;
698 sheet->hide_col_header = sheet->hide_row_header = TRUE;
699 colrow_compute_pixels_from_pts (&sheet->rows.default_style,
700 sheet, FALSE, -1);
701 colrow_compute_pixels_from_pts (&sheet->cols.default_style,
702 sheet, TRUE, -1);
703 break;
704 case GNM_SHEET_DATA: {
705 /* We have to add permanent names */
706 GnmExprTop const *texpr;
708 if (sheet->name_unquoted)
709 texpr = gnm_expr_top_new_constant
710 (value_new_string (sheet->name_unquoted));
711 else
712 texpr = gnm_expr_top_new_constant
713 (value_new_error_REF (NULL));
714 expr_name_perm_add (sheet, "Sheet_Title",
715 texpr, FALSE);
717 texpr = gnm_expr_top_new_constant
718 (value_new_error_REF (NULL));
719 expr_name_perm_add (sheet, "Print_Area",
720 texpr, FALSE);
721 break;
723 default:
724 g_assert_not_reached ();
727 sheet_scale_changed (sheet, TRUE, TRUE);
730 static guint
731 cell_set_hash (GnmCell const *key)
733 guint32 r = key->pos.row;
734 guint32 c = key->pos.col;
735 guint32 h;
737 h = r;
738 h *= (guint32)123456789;
739 h ^= c;
740 h *= (guint32)123456789;
742 return h;
745 static gint
746 cell_set_equal (GnmCell const *a, GnmCell const *b)
748 return (a->pos.row == b->pos.row && a->pos.col == b->pos.col);
751 static void
752 gnm_sheet_init (Sheet *sheet)
754 PangoContext *context;
756 sheet->priv = g_new0 (SheetPrivate, 1);
757 sheet->being_constructed = TRUE;
759 sheet->sheet_views = g_ptr_array_new ();
761 /* Init, focus, and load handle setting these if/when necessary */
762 sheet->priv->recompute_visibility = TRUE;
763 sheet->priv->recompute_spans = TRUE;
765 sheet->is_protected = FALSE;
766 sheet->protected_allow.edit_scenarios = FALSE;
767 sheet->protected_allow.cell_formatting = FALSE;
768 sheet->protected_allow.column_formatting = FALSE;
769 sheet->protected_allow.row_formatting = FALSE;
770 sheet->protected_allow.insert_columns = FALSE;
771 sheet->protected_allow.insert_rows = FALSE;
772 sheet->protected_allow.insert_hyperlinks = FALSE;
773 sheet->protected_allow.delete_columns = FALSE;
774 sheet->protected_allow.delete_rows = FALSE;
775 sheet->protected_allow.select_locked_cells = TRUE;
776 sheet->protected_allow.sort_ranges = FALSE;
777 sheet->protected_allow.edit_auto_filters = FALSE;
778 sheet->protected_allow.edit_pivottable = FALSE;
779 sheet->protected_allow.select_unlocked_cells = TRUE;
781 sheet->hide_zero = FALSE;
782 sheet->display_outlines = TRUE;
783 sheet->outline_symbols_below = TRUE;
784 sheet->outline_symbols_right = TRUE;
785 sheet->tab_color = NULL;
786 sheet->tab_text_color = NULL;
787 sheet->visibility = GNM_SHEET_VISIBILITY_VISIBLE;
788 #ifdef GNM_WITH_GTK
789 sheet->text_is_rtl = (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL);
790 #else
791 sheet->text_is_rtl = FALSE;
792 #endif
794 sheet->sheet_objects = NULL;
795 sheet->max_object_extent.col = sheet->max_object_extent.row = 0;
797 sheet->solver_parameters = gnm_solver_param_new (sheet);
799 sheet->cols.max_used = -1;
800 sheet->cols.info = g_ptr_array_new ();
801 sheet_col_set_default_size_pts (sheet, 48);
803 sheet->rows.max_used = -1;
804 sheet->rows.info = g_ptr_array_new ();
805 sheet_row_set_default_size_pts (sheet, 12.75);
807 sheet->print_info = gnm_print_information_new (FALSE);
809 sheet->filters = NULL;
810 sheet->scenarios = NULL;
811 sheet->sort_setups = NULL;
812 sheet->list_merged = NULL;
813 sheet->hash_merged = g_hash_table_new ((GHashFunc)&gnm_cellpos_hash,
814 (GCompareFunc)&gnm_cellpos_equal);
816 sheet->cell_hash = g_hash_table_new ((GHashFunc)&cell_set_hash,
817 (GCompareFunc)&cell_set_equal);
819 /* Init preferences */
820 sheet->convs = gnm_conventions_default;
822 /* FIXME: probably not here. */
823 /* See also gtk_widget_create_pango_context (). */
824 sheet->last_zoom_factor_used = -1; /* Overridden later */
825 context = gnm_pango_context_get ();
826 sheet->rendered_values = gnm_rvc_new (context, 5000);
827 g_object_unref (context);
829 /* Init menu states */
830 sheet->priv->enable_showhide_detail = TRUE;
832 sheet->names = gnm_named_expr_collection_new ();
833 sheet->style_data = NULL;
835 sheet->index_in_wb = -1;
838 static Sheet the_invalid_sheet;
839 Sheet *invalid_sheet = &the_invalid_sheet;
841 static void
842 gnm_sheet_class_init (GObjectClass *gobject_class)
844 if (GNM_MAX_COLS > 364238) {
845 /* Oh, yeah? */
846 g_warning (_("This is a special version of Gnumeric. It has been compiled\n"
847 "with support for a very large number of columns. Access to the\n"
848 "column named TRUE may conflict with the constant of the same\n"
849 "name. Expect weirdness."));
852 parent_class = g_type_class_peek_parent (gobject_class);
854 gobject_class->set_property = gnm_sheet_set_property;
855 gobject_class->get_property = gnm_sheet_get_property;
856 gobject_class->finalize = gnm_sheet_finalize;
857 gobject_class->constructed = gnm_sheet_constructed;
859 g_object_class_install_property (gobject_class, PROP_SHEET_TYPE,
860 g_param_spec_enum ("sheet-type",
861 P_("Sheet Type"),
862 P_("Which type of sheet this is."),
863 GNM_SHEET_TYPE_TYPE,
864 GNM_SHEET_DATA,
865 GSF_PARAM_STATIC |
866 G_PARAM_READWRITE |
867 G_PARAM_CONSTRUCT_ONLY));
868 g_object_class_install_property (gobject_class, PROP_WORKBOOK,
869 g_param_spec_object ("workbook",
870 P_("Parent workbook"),
871 P_("The workbook in which this sheet lives"),
872 GNM_WORKBOOK_TYPE,
873 GSF_PARAM_STATIC |
874 G_PARAM_READWRITE |
875 G_PARAM_CONSTRUCT_ONLY));
876 g_object_class_install_property (gobject_class, PROP_NAME,
877 g_param_spec_string ("name",
878 P_("Name"),
879 P_("The name of the sheet."),
880 NULL,
881 GSF_PARAM_STATIC |
882 G_PARAM_READWRITE));
883 g_object_class_install_property (gobject_class, PROP_RTL,
884 g_param_spec_boolean ("text-is-rtl",
885 P_("text-is-rtl"),
886 P_("Text goes from right to left."),
887 FALSE,
888 GSF_PARAM_STATIC |
889 G_PARAM_READWRITE));
890 g_object_class_install_property (gobject_class, PROP_VISIBILITY,
891 g_param_spec_enum ("visibility",
892 P_("Visibility"),
893 P_("How visible the sheet is."),
894 GNM_SHEET_VISIBILITY_TYPE,
895 GNM_SHEET_VISIBILITY_VISIBLE,
896 GSF_PARAM_STATIC |
897 G_PARAM_READWRITE));
898 g_object_class_install_property (gobject_class, PROP_DISPLAY_FORMULAS,
899 g_param_spec_boolean ("display-formulas",
900 P_("Display Formul\303\246"),
901 P_("Control whether formul\303\246 are shown instead of values."),
902 FALSE,
903 GSF_PARAM_STATIC |
904 G_PARAM_READWRITE));
905 g_object_class_install_property (gobject_class, PROP_DISPLAY_ZEROS,
906 g_param_spec_boolean ("display-zeros", _("Display Zeros"),
907 _("Control whether zeros are shown are blanked out."),
908 TRUE,
909 GSF_PARAM_STATIC |
910 G_PARAM_READWRITE));
911 g_object_class_install_property (gobject_class, PROP_DISPLAY_GRID,
912 g_param_spec_boolean ("display-grid", _("Display Grid"),
913 _("Control whether the grid is shown."),
914 TRUE,
915 GSF_PARAM_STATIC |
916 G_PARAM_READWRITE));
917 g_object_class_install_property (gobject_class, PROP_DISPLAY_COLUMN_HEADER,
918 g_param_spec_boolean ("display-column-header",
919 P_("Display Column Headers"),
920 P_("Control whether column headers are shown."),
921 FALSE,
922 GSF_PARAM_STATIC |
923 G_PARAM_READWRITE));
924 g_object_class_install_property (gobject_class, PROP_DISPLAY_ROW_HEADER,
925 g_param_spec_boolean ("display-row-header",
926 P_("Display Row Headers"),
927 P_("Control whether row headers are shown."),
928 FALSE,
929 GSF_PARAM_STATIC |
930 G_PARAM_READWRITE));
931 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES,
932 g_param_spec_boolean ("display-outlines",
933 P_("Display Outlines"),
934 P_("Control whether outlines are shown."),
935 TRUE,
936 GSF_PARAM_STATIC |
937 G_PARAM_READWRITE));
938 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES_BELOW,
939 g_param_spec_boolean ("display-outlines-below",
940 P_("Display Outlines Below"),
941 P_("Control whether outline symbols are shown below."),
942 TRUE,
943 GSF_PARAM_STATIC |
944 G_PARAM_READWRITE));
945 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES_RIGHT,
946 g_param_spec_boolean ("display-outlines-right",
947 P_("Display Outlines Right"),
948 P_("Control whether outline symbols are shown to the right."),
949 TRUE,
950 GSF_PARAM_STATIC |
951 G_PARAM_READWRITE));
953 g_object_class_install_property (gobject_class, PROP_PROTECTED,
954 g_param_spec_boolean ("protected",
955 P_("Protected"),
956 P_("Sheet is protected."),
957 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
958 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_OBJECTS,
959 g_param_spec_boolean ("protected-allow-edit-objects",
960 P_("Protected Allow Edit objects"),
961 P_("Allow objects to be edited while a sheet is protected"),
962 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
963 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_SCENARIOS,
964 g_param_spec_boolean ("protected-allow-edit-scenarios",
965 P_("Protected allow edit scenarios"),
966 P_("Allow scenarios to be edited while a sheet is protected"),
967 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
968 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_CELL_FORMATTING,
969 g_param_spec_boolean ("protected-allow-cell-formatting",
970 P_("Protected allow cell formatting"),
971 P_("Allow cell format changes while a sheet is protected"),
972 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
973 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_COLUMN_FORMATTING,
974 g_param_spec_boolean ("protected-allow-column-formatting",
975 P_("Protected allow column formatting"),
976 P_("Allow column formatting while a sheet is protected"),
977 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
978 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_ROW_FORMATTING,
979 g_param_spec_boolean ("protected-allow-row-formatting",
980 P_("Protected allow row formatting"),
981 P_("Allow row formatting while a sheet is protected"),
982 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
983 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_COLUMNS,
984 g_param_spec_boolean ("protected-allow-insert-columns",
985 P_("Protected allow insert columns"),
986 P_("Allow columns to be inserted while a sheet is protected"),
987 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
988 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_ROWS,
989 g_param_spec_boolean ("protected-allow-insert-rows",
990 P_("Protected allow insert rows"),
991 P_("Allow rows to be inserted while a sheet is protected"),
992 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
993 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS,
994 g_param_spec_boolean ("protected-allow-insert-hyperlinks",
995 P_("Protected allow insert hyperlinks"),
996 P_("Allow hyperlinks to be inserted while a sheet is protected"),
997 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
998 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_DELETE_COLUMNS,
999 g_param_spec_boolean ("protected-allow-delete-columns",
1000 P_("Protected allow delete columns"),
1001 P_("Allow columns to be deleted while a sheet is protected"),
1002 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1003 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_DELETE_ROWS,
1004 g_param_spec_boolean ("protected-allow-delete-rows",
1005 P_("Protected allow delete rows"),
1006 P_("Allow rows to be deleted while a sheet is protected"),
1007 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1008 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS,
1009 g_param_spec_boolean ("protected-allow-select-locked-cells",
1010 P_("Protected allow select locked cells"),
1011 P_("Allow the user to select locked cells while a sheet is protected"),
1012 TRUE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1013 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SORT_RANGES,
1014 g_param_spec_boolean ("protected-allow-sort-ranges",
1015 P_("Protected allow sort ranges"),
1016 P_("Allow ranges to be sorted while a sheet is protected"),
1017 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1018 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS,
1019 g_param_spec_boolean ("protected-allow-edit-auto-filters",
1020 P_("Protected allow edit auto filters"),
1021 P_("Allow auto filters to be edited while a sheet is protected"),
1022 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1023 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE,
1024 g_param_spec_boolean ("protected-allow-edit-pivottable",
1025 P_("Protected allow edit pivottable"),
1026 P_("Allow pivottable to be edited while a sheet is protected"),
1027 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1028 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS,
1029 g_param_spec_boolean ("protected-allow-select-unlocked-cells",
1030 P_("Protected allow select unlocked cells"),
1031 P_("Allow the user to select unlocked cells while a sheet is protected"),
1032 TRUE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1034 g_object_class_install_property (gobject_class, PROP_CONVENTIONS,
1035 g_param_spec_pointer ("conventions",
1036 P_("Display convention for expressions (default Gnumeric A1)"),
1037 P_("How to format displayed expressions, (A1 vs R1C1, function names, ...)"),
1038 GSF_PARAM_STATIC |
1039 G_PARAM_READWRITE));
1040 g_object_class_install_property (gobject_class, PROP_USE_R1C1, /* convenience wrapper to CONVENTIONS */
1041 g_param_spec_boolean ("use-r1c1",
1042 P_("Display convention for expressions as XLS_R1C1 vs default"),
1043 P_("How to format displayed expressions, (a convenience api)"),
1044 FALSE,
1045 GSF_PARAM_STATIC |
1046 G_PARAM_READWRITE));
1048 g_object_class_install_property (gobject_class, PROP_TAB_FOREGROUND,
1049 g_param_spec_boxed ("tab-foreground",
1050 P_("Tab Foreground"),
1051 P_("The foreground color of the tab."),
1052 GNM_COLOR_TYPE,
1053 GSF_PARAM_STATIC |
1054 G_PARAM_READWRITE));
1055 g_object_class_install_property (gobject_class, PROP_TAB_BACKGROUND,
1056 g_param_spec_boxed ("tab-background",
1057 P_("Tab Background"),
1058 P_("The background color of the tab."),
1059 GNM_COLOR_TYPE,
1060 GSF_PARAM_STATIC |
1061 G_PARAM_READWRITE));
1063 /* What is this doing in sheet? */
1064 g_object_class_install_property (gobject_class, PROP_ZOOM_FACTOR,
1065 g_param_spec_double ("zoom-factor",
1066 P_("Zoom Factor"),
1067 P_("The level of zoom used for this sheet."),
1068 0.1, 5.0,
1069 1.0,
1070 GSF_PARAM_STATIC |
1071 G_PARAM_CONSTRUCT |
1072 G_PARAM_READWRITE));
1074 g_object_class_install_property (gobject_class, PROP_COLUMNS,
1075 g_param_spec_int ("columns",
1076 P_("Columns"),
1077 P_("Columns number in the sheet"),
1078 0, GNM_MAX_COLS, GNM_DEFAULT_COLS,
1079 GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1081 g_object_class_install_property (gobject_class, PROP_ROWS,
1082 g_param_spec_int ("rows",
1083 P_("Rows"),
1084 P_("Rows number in the sheet"),
1085 0, GNM_MAX_ROWS, GNM_DEFAULT_ROWS,
1086 GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1088 signals[DETACHED_FROM_WORKBOOK] = g_signal_new
1089 ("detached_from_workbook",
1090 GNM_SHEET_TYPE,
1091 G_SIGNAL_RUN_LAST,
1092 G_STRUCT_OFFSET (GnmSheetClass, detached_from_workbook),
1093 NULL, NULL,
1094 g_cclosure_marshal_VOID__OBJECT,
1095 G_TYPE_NONE, 1, GNM_WORKBOOK_TYPE);
1099 GSF_CLASS (GnmSheet, gnm_sheet,
1100 gnm_sheet_class_init, gnm_sheet_init, G_TYPE_OBJECT)
1102 /* ------------------------------------------------------------------------- */
1104 GType
1105 gnm_sheet_type_get_type (void)
1107 static GType etype = 0;
1108 if (etype == 0) {
1109 static const GEnumValue values[] = {
1110 { GNM_SHEET_DATA, "GNM_SHEET_DATA", "data" },
1111 { GNM_SHEET_OBJECT, "GNM_SHEET_OBJECT", "object" },
1112 { GNM_SHEET_XLM, "GNM_SHEET_XLM", "xlm" },
1113 { 0, NULL, NULL }
1115 etype = g_enum_register_static ("GnmSheetType", values);
1117 return etype;
1120 GType
1121 gnm_sheet_visibility_get_type (void)
1123 static GType etype = 0;
1124 if (etype == 0) {
1125 static GEnumValue const values[] = {
1126 { GNM_SHEET_VISIBILITY_VISIBLE, "GNM_SHEET_VISIBILITY_VISIBLE", "visible" },
1127 { GNM_SHEET_VISIBILITY_HIDDEN, "GNM_SHEET_VISIBILITY_HIDDEN", "hidden" },
1128 { GNM_SHEET_VISIBILITY_VERY_HIDDEN, "GNM_SHEET_VISIBILITY_VERY_HIDDEN", "very-hidden" },
1129 { 0, NULL, NULL }
1131 etype = g_enum_register_static ("GnmSheetVisibility", values);
1133 return etype;
1136 /* ------------------------------------------------------------------------- */
1138 static gboolean
1139 powerof_2 (int i)
1141 return i > 0 && (i & (i - 1)) == 0;
1144 gboolean
1145 gnm_sheet_valid_size (int cols, int rows)
1147 return (cols >= GNM_MIN_COLS &&
1148 cols <= GNM_MAX_COLS &&
1149 powerof_2 (cols) &&
1150 rows >= GNM_MIN_ROWS &&
1151 rows <= GNM_MAX_ROWS &&
1152 powerof_2 (rows)
1153 #if 0
1154 && 0x80000000u / (unsigned)(cols / 2) >= (unsigned)rows
1155 #endif
1159 void
1160 gnm_sheet_suggest_size (int *cols, int *rows)
1162 int c = GNM_DEFAULT_COLS;
1163 int r = GNM_DEFAULT_ROWS;
1165 while (c < *cols && c < GNM_MAX_COLS)
1166 c *= 2;
1168 while (r < *rows && r < GNM_MAX_ROWS)
1169 r *= 2;
1171 while (!gnm_sheet_valid_size (c, r)) {
1172 /* Darn! Too large. */
1173 if (*cols >= GNM_MIN_COLS && c > GNM_MIN_COLS)
1174 c /= 2;
1175 else if (*rows >= GNM_MIN_ROWS && r > GNM_MIN_ROWS)
1176 r /= 2;
1177 else if (c > GNM_MIN_COLS)
1178 c /= 2;
1179 else
1180 r /= 2;
1183 *cols = c;
1184 *rows = r;
1187 static void gnm_sheet_resize_main (Sheet *sheet, int cols, int rows,
1188 GOCmdContext *cc, GOUndo **pundo);
1190 static void
1191 cb_sheet_resize (Sheet *sheet, const GnmSheetSize *data, GOCmdContext *cc)
1193 gnm_sheet_resize_main (sheet, data->max_cols, data->max_rows,
1194 cc, NULL);
1197 static void
1198 gnm_sheet_resize_main (Sheet *sheet, int cols, int rows,
1199 GOCmdContext *cc, GOUndo **pundo)
1201 int old_cols, old_rows;
1202 GnmStyle **common_col_styles = NULL;
1203 GnmStyle **common_row_styles = NULL;
1205 if (pundo) *pundo = NULL;
1207 old_cols = gnm_sheet_get_max_cols (sheet);
1208 old_rows = gnm_sheet_get_max_rows (sheet);
1209 if (old_cols == cols && old_rows == rows)
1210 return;
1212 /* ---------------------------------------- */
1213 /* Gather styles we want to copy into new areas. */
1215 if (cols > old_cols) {
1216 int r;
1217 common_row_styles = sheet_style_most_common (sheet, FALSE);
1218 for (r = 0; r < old_rows; r++)
1219 gnm_style_ref (common_row_styles[r]);
1221 if (rows > old_rows) {
1222 int c;
1223 common_col_styles = sheet_style_most_common (sheet, TRUE);
1224 for (c = 0; c < old_cols; c++)
1225 gnm_style_ref (common_col_styles[c]);
1228 /* ---------------------------------------- */
1229 /* Remove the columns and rows that will disappear. */
1231 if (cols < old_cols) {
1232 GOUndo *u = NULL;
1233 gboolean err;
1235 err = sheet_delete_cols (sheet, cols, G_MAXINT,
1236 pundo ? &u : NULL, cc);
1237 if (pundo)
1238 *pundo = go_undo_combine (*pundo, u);
1239 if (err)
1240 goto handle_error;
1243 if (rows < old_rows) {
1244 GOUndo *u = NULL;
1245 gboolean err;
1247 err = sheet_delete_rows (sheet, rows, G_MAXINT,
1248 pundo ? &u : NULL, cc);
1249 if (pundo)
1250 *pundo = go_undo_combine (*pundo, u);
1251 if (err)
1252 goto handle_error;
1255 /* ---------------------------------------- */
1256 /* Restrict selection. (Not undone.) */
1258 SHEET_FOREACH_VIEW (sheet, sv,
1260 GnmRange new_full;
1261 GSList *l;
1262 GSList *sel = selection_get_ranges (sv, TRUE);
1263 gboolean any = FALSE;
1264 GnmCellPos vis;
1265 sv_selection_reset (sv);
1266 range_init (&new_full, 0, 0, cols - 1, rows - 1);
1267 vis = new_full.start;
1268 for (l = sel; l; l = l->next) {
1269 GnmRange *r = l->data;
1270 GnmRange newr;
1271 if (range_intersection (&newr, r, &new_full)) {
1272 sv_selection_add_range (sv, &newr);
1273 vis = newr.start;
1274 any = TRUE;
1276 g_free (r);
1278 g_slist_free (sel);
1279 if (!any)
1280 sv_selection_add_pos (sv, 0, 0,
1281 GNM_SELECTION_MODE_ADD);
1282 sv_make_cell_visible (sv, vis.col, vis.row, FALSE);
1285 /* ---------------------------------------- */
1286 /* Resize column and row containers. */
1288 colrow_resize (&sheet->cols, cols);
1289 colrow_resize (&sheet->rows, rows);
1291 /* ---------------------------------------- */
1292 /* Resize the dependency containers. */
1295 GSList *l, *linked = NULL;
1296 /* FIXME: what about dependents in other workbooks? */
1297 WORKBOOK_FOREACH_DEPENDENT
1298 (sheet->workbook, dep,
1300 if (dependent_is_linked (dep)) {
1301 dependent_unlink (dep);
1302 linked = g_slist_prepend (linked, dep);
1305 gnm_dep_container_resize (sheet->deps, rows);
1307 for (l = linked; l; l = l->next) {
1308 GnmDependent *dep = l->data;
1309 dependent_link (dep);
1312 g_slist_free (linked);
1314 workbook_queue_all_recalc (sheet->workbook);
1317 /* ---------------------------------------- */
1318 /* Resize the styles. */
1320 sheet_style_resize (sheet, cols, rows);
1322 /* ---------------------------------------- */
1323 /* Actually change the properties. */
1325 sheet->size.max_cols = cols;
1326 sheet->cols.max_used = MIN (sheet->cols.max_used, cols - 1);
1327 sheet->size.max_rows = rows;
1328 sheet->rows.max_used = MIN (sheet->rows.max_used, rows - 1);
1330 if (old_cols != cols)
1331 g_object_notify (G_OBJECT (sheet), "columns");
1332 if (old_rows != rows)
1333 g_object_notify (G_OBJECT (sheet), "rows");
1335 if (pundo) {
1336 GnmSheetSize *data = g_new (GnmSheetSize, 1);
1337 GOUndo *u;
1339 data->max_cols = old_cols;
1340 data->max_rows = old_rows;
1341 u = go_undo_binary_new (sheet, data,
1342 (GOUndoBinaryFunc)cb_sheet_resize,
1343 NULL, g_free);
1344 *pundo = go_undo_combine (*pundo, u);
1347 range_init_full_sheet (&sheet->priv->unhidden_region, sheet);
1349 /* ---------------------------------------- */
1350 /* Apply styles to new areas. */
1352 if (cols > old_cols) {
1353 int r = 0;
1354 while (r < old_rows) {
1355 int r2 = r;
1356 GnmStyle *mstyle = common_row_styles[r];
1357 GnmRange rng;
1358 while (r2 + 1 < old_rows &&
1359 mstyle == common_row_styles[r2 + 1])
1360 r2++;
1361 range_init (&rng, old_cols, r, cols - 1, r2);
1362 gnm_style_ref (mstyle);
1363 sheet_apply_style (sheet, &rng, mstyle);
1364 r = r2 + 1;
1367 for (r = 0; r < old_rows; r++)
1368 gnm_style_unref (common_row_styles[r]);
1370 g_free (common_row_styles);
1373 if (rows > old_rows) {
1374 int c = 0;
1376 while (c < old_cols) {
1377 int c2 = c;
1378 GnmStyle *mstyle = common_col_styles[c];
1379 GnmRange rng;
1380 while (c2 + 1 < old_cols &&
1381 mstyle == common_col_styles[c2 + 1])
1382 c2++;
1383 range_init (&rng, c, old_rows, c2, rows - 1);
1384 gnm_style_ref (mstyle);
1385 sheet_apply_style (sheet, &rng, mstyle);
1386 c = c2 + 1;
1389 if (cols > old_cols) {
1391 * Expanded in both directions. One could argue about
1392 * what style to use down here, but we choose the
1393 * last column style.
1395 GnmStyle *mstyle = common_col_styles[old_cols - 1];
1396 GnmRange rng;
1398 range_init (&rng,
1399 old_cols, old_rows,
1400 cols - 1, rows - 1);
1401 gnm_style_ref (mstyle);
1402 sheet_apply_style (sheet, &rng, mstyle);
1405 for (c = 0; c < old_cols; c++)
1406 gnm_style_unref (common_col_styles[c]);
1407 g_free (common_col_styles);
1410 /* ---------------------------------------- */
1412 sheet_redraw_all (sheet, TRUE);
1413 return;
1415 handle_error:
1416 if (pundo) {
1417 go_undo_undo_with_data (*pundo, cc);
1418 g_object_unref (*pundo);
1419 *pundo = NULL;
1424 * gnm_sheet_resize:
1425 * @sheet: #Sheet
1426 * @cols: the new columns number.
1427 * @rows: the new rows number.
1428 * @cc: #GOCmdContext.
1429 * @perr: will be %TRUE on error.
1431 * Returns: (transfer full): the newly allocated #GOUndo.
1433 GOUndo *
1434 gnm_sheet_resize (Sheet *sheet, int cols, int rows,
1435 GOCmdContext *cc, gboolean *perr)
1437 GOUndo *undo = NULL;
1439 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1440 g_return_val_if_fail (gnm_sheet_valid_size (cols, rows), NULL);
1442 if (cols < sheet->size.max_cols || rows < sheet->size.max_rows) {
1443 GSList *overlap, *l;
1444 gboolean bad = FALSE;
1445 GnmRange r;
1447 r.start.col = r.start.row = 0;
1448 r.end.col = MIN (cols, sheet->size.max_cols) - 1;
1449 r.end.row = MIN (rows, sheet->size.max_rows) - 1;
1451 overlap = gnm_sheet_merge_get_overlap (sheet, &r);
1452 for (l = overlap; l && !bad; l = l->next) {
1453 GnmRange const *m = l->data;
1454 if (!range_contained (m, &r)) {
1455 bad = TRUE;
1456 gnm_cmd_context_error_splits_merge (cc, m);
1459 g_slist_free (overlap);
1460 if (bad) {
1461 *perr = TRUE;
1462 return NULL;
1466 gnm_sheet_resize_main (sheet, cols, rows, cc, &undo);
1468 *perr = FALSE;
1469 return undo;
1474 * sheet_new_with_type:
1475 * @wb: #Workbook
1476 * @name: An unquoted name
1477 * @type: @GnmSheetType
1478 * @columns: The number of columns for the sheet
1479 * @rows: The number of rows for the sheet
1481 * Create a new Sheet of type @type, and associate it with @wb.
1482 * The type cannot be changed later.
1483 * Returns: (transfer full): the newly allocated sheet.
1485 Sheet *
1486 sheet_new_with_type (Workbook *wb, char const *name, GnmSheetType type,
1487 int columns, int rows)
1489 Sheet *sheet;
1491 g_return_val_if_fail (wb != NULL, NULL);
1492 g_return_val_if_fail (name != NULL, NULL);
1493 g_return_val_if_fail (gnm_sheet_valid_size (columns, rows), NULL);
1495 sheet = g_object_new (GNM_SHEET_TYPE,
1496 "workbook", wb,
1497 "sheet-type", type,
1498 "columns", columns,
1499 "rows", rows,
1500 "name", name,
1501 "zoom-factor", gnm_conf_get_core_gui_window_zoom (),
1502 NULL);
1504 if (type == GNM_SHEET_OBJECT)
1505 print_info_set_paper_orientation (sheet->print_info, GTK_PAGE_ORIENTATION_LANDSCAPE);
1507 return sheet;
1511 * sheet_new:
1512 * @wb: #Workbook
1513 * @name: The name for the sheet (unquoted).
1514 * @columns: The requested columns number.
1515 * @rows: The requested rows number.
1517 * Create a new Sheet of type SHEET_DATA, and associate it with @wb.
1518 * The type can not be changed later
1519 * Returns: (transfer full): the newly allocated sheet.
1521 Sheet *
1522 sheet_new (Workbook *wb, char const *name, int columns, int rows)
1524 return sheet_new_with_type (wb, name, GNM_SHEET_DATA, columns, rows);
1527 /****************************************************************************/
1529 void
1530 sheet_redraw_all (Sheet const *sheet, gboolean headers)
1532 /* We potentially do a lot of recalcs as part of this, so make sure
1533 stuff that caches sub-computations see the whole thing instead
1534 of clearing between cells. */
1535 gnm_app_recalc_start ();
1536 SHEET_FOREACH_CONTROL (sheet, view, control,
1537 sc_redraw_all (control, headers););
1538 gnm_app_recalc_finish ();
1541 static GnmValue *
1542 cb_clear_rendered_values (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
1544 gnm_cell_unrender (iter->cell);
1545 return NULL;
1549 * sheet_range_calc_spans:
1550 * @sheet: The sheet,
1551 * @r: the region to update.
1552 * @flags:
1554 * This is used to re-calculate cell dimensions and re-render
1555 * a cell's text. eg. if a format has changed we need to re-render
1556 * the cached version of the rendered text in the cell.
1558 void
1559 sheet_range_calc_spans (Sheet *sheet, GnmRange const *r, GnmSpanCalcFlags flags)
1561 if (flags & GNM_SPANCALC_RE_RENDER)
1562 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_NONEXISTENT,
1563 r->start.col, r->start.row, r->end.col, r->end.row,
1564 cb_clear_rendered_values, NULL);
1565 sheet_queue_respan (sheet, r->start.row, r->end.row);
1567 /* Redraw the new region in case the span changes */
1568 sheet_redraw_range (sheet, r);
1571 static void
1572 sheet_redraw_partial_row (Sheet const *sheet, int const row,
1573 int const start_col, int const end_col)
1575 GnmRange r;
1576 range_init (&r, start_col, row, end_col, row);
1577 SHEET_FOREACH_CONTROL (sheet, view, control,
1578 sc_redraw_range (control, &r););
1581 static void
1582 sheet_redraw_cell (GnmCell const *cell)
1584 CellSpanInfo const * span;
1585 int start_col, end_col, row;
1586 GnmRange const *merged;
1587 Sheet *sheet;
1588 ColRowInfo *ri;
1590 g_return_if_fail (cell != NULL);
1592 sheet = cell->base.sheet;
1593 merged = gnm_sheet_merge_is_corner (sheet, &cell->pos);
1594 if (merged != NULL) {
1595 SHEET_FOREACH_CONTROL (sheet, view, control,
1596 sc_redraw_range (control, merged););
1597 return;
1600 row = cell->pos.row;
1601 start_col = end_col = cell->pos.col;
1602 ri = sheet_row_get (sheet, row);
1603 span = row_span_get (ri, start_col);
1605 if (span) {
1606 start_col = span->left;
1607 end_col = span->right;
1610 sheet_redraw_partial_row (sheet, row, start_col, end_col);
1613 static void
1614 sheet_cell_calc_span (GnmCell *cell, GnmSpanCalcFlags flags)
1616 CellSpanInfo const * span;
1617 int left, right;
1618 int min_col, max_col, row;
1619 gboolean render = (flags & GNM_SPANCALC_RE_RENDER) != 0;
1620 gboolean const resize = (flags & GNM_SPANCALC_RESIZE) != 0;
1621 gboolean existing = FALSE;
1622 GnmRange const *merged;
1623 Sheet *sheet;
1624 ColRowInfo *ri;
1626 g_return_if_fail (cell != NULL);
1628 sheet = cell->base.sheet;
1629 row = cell->pos.row;
1631 /* Render & Size any unrendered cells */
1632 if ((flags & GNM_SPANCALC_RENDER) && gnm_cell_get_rendered_value (cell) == NULL)
1633 render = TRUE;
1635 if (render) {
1636 if (!gnm_cell_has_expr (cell))
1637 gnm_cell_render_value ((GnmCell *)cell, TRUE);
1638 else
1639 gnm_cell_unrender (cell);
1640 } else if (resize) {
1641 /* FIXME: what was wanted here? */
1642 /* rendered_value_calc_size (cell); */
1645 /* Is there an existing span ? clear it BEFORE calculating new one */
1646 ri = sheet_row_get (sheet, row);
1647 span = row_span_get (ri, cell->pos.col);
1648 if (span != NULL) {
1649 GnmCell const * const other = span->cell;
1651 min_col = span->left;
1652 max_col = span->right;
1654 /* A different cell used to span into this cell, respan that */
1655 if (cell != other) {
1656 int other_left, other_right;
1658 cell_unregister_span (other);
1659 cell_calc_span (other, &other_left, &other_right);
1660 if (min_col > other_left)
1661 min_col = other_left;
1662 if (max_col < other_right)
1663 max_col = other_right;
1665 if (other_left != other_right)
1666 cell_register_span (other, other_left, other_right);
1667 } else
1668 existing = TRUE;
1669 } else
1670 min_col = max_col = cell->pos.col;
1672 merged = gnm_sheet_merge_is_corner (sheet, &cell->pos);
1673 if (NULL != merged) {
1674 if (existing) {
1675 if (min_col > merged->start.col)
1676 min_col = merged->start.col;
1677 if (max_col < merged->end.col)
1678 max_col = merged->end.col;
1679 } else {
1680 sheet_redraw_cell (cell);
1681 return;
1683 } else {
1684 /* Calculate the span of the cell */
1685 cell_calc_span (cell, &left, &right);
1686 if (min_col > left)
1687 min_col = left;
1688 if (max_col < right)
1689 max_col = right;
1691 /* This cell already had an existing span */
1692 if (existing) {
1693 /* If it changed, remove the old one */
1694 if (left != span->left || right != span->right)
1695 cell_unregister_span (cell);
1696 else
1697 /* unchanged, short curcuit adding the span again */
1698 left = right;
1701 if (left != right)
1702 cell_register_span (cell, left, right);
1705 sheet_redraw_partial_row (sheet, row, min_col, max_col);
1709 * sheet_apply_style: (skip)
1710 * @sheet: the sheet in which can be found
1711 * @range: the range to which should be applied
1712 * @style: (transfer full): A #GnmStyle partial style
1714 * A mid level routine that applies the supplied partial style @style to the
1715 * target @range and performs the necessary respanning and redrawing.
1717 void
1718 sheet_apply_style (Sheet *sheet,
1719 GnmRange const *range,
1720 GnmStyle *style)
1722 GnmSpanCalcFlags spanflags = gnm_style_required_spanflags (style);
1723 sheet_style_apply_range (sheet, range, style);
1724 /* This also redraws the range: */
1725 sheet_range_calc_spans (sheet, range, spanflags);
1729 * sheet_apply_style_gi: (rename-to sheet_apply_style)
1730 * @sheet: the sheet in which can be found
1731 * @range: the range to which should be applied
1732 * @style: A #GnmStyle partial style
1734 * A mid level routine that applies the supplied partial style @style to the
1735 * target @range and performs the necessary respanning and redrawing.
1737 void
1738 sheet_apply_style_gi (Sheet *sheet, GnmRange const *range, GnmStyle *style)
1740 GnmSpanCalcFlags spanflags = gnm_style_required_spanflags (style);
1741 gnm_style_ref (style);
1742 sheet_style_apply_range (sheet, range, style);
1743 /* This also redraws the range: */
1744 sheet_range_calc_spans (sheet, range, spanflags);
1747 static void
1748 sheet_apply_style_cb (GnmSheetRange *sr,
1749 GnmStyle *style)
1751 gnm_style_ref (style);
1752 sheet_apply_style (sr->sheet, &sr->range, style);
1753 sheet_flag_style_update_range (sr->sheet, &sr->range);
1757 * sheet_apply_style_undo:
1758 * @sr: #GnmSheetRange
1759 * @style: #GnmStyle
1761 * Returns: (transfer full): the new #GOUndo.
1763 GOUndo *
1764 sheet_apply_style_undo (GnmSheetRange *sr,
1765 GnmStyle *style)
1767 gnm_style_ref (style);
1768 return go_undo_binary_new
1769 (sr, (gpointer)style,
1770 (GOUndoBinaryFunc) sheet_apply_style_cb,
1771 (GFreeFunc) gnm_sheet_range_free,
1772 (GFreeFunc) gnm_style_unref);
1777 void
1778 sheet_apply_border (Sheet *sheet,
1779 GnmRange const *range,
1780 GnmBorder **borders)
1782 GnmSpanCalcFlags spanflags = GNM_SPANCALC_RE_RENDER | GNM_SPANCALC_RESIZE;
1783 sheet_style_apply_border (sheet, range, borders);
1784 /* This also redraws the range: */
1785 sheet_range_calc_spans (sheet, range, spanflags);
1788 /****************************************************************************/
1790 static ColRowInfo *
1791 sheet_row_new (Sheet *sheet)
1793 ColRowInfo *ri;
1795 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1797 ri = col_row_info_new ();
1798 *ri = sheet->rows.default_style;
1799 ri->is_default = FALSE;
1800 ri->needs_respan = TRUE;
1802 return ri;
1805 static ColRowInfo *
1806 sheet_col_new (Sheet *sheet)
1808 ColRowInfo *ci;
1810 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1812 ci = col_row_info_new ();
1813 *ci = sheet->cols.default_style;
1814 ci->is_default = FALSE;
1816 return ci;
1819 static void
1820 sheet_colrow_add (Sheet *sheet, ColRowInfo *cp, gboolean is_cols, int n)
1822 ColRowCollection *info = is_cols ? &sheet->cols : &sheet->rows;
1823 ColRowSegment **psegment = (ColRowSegment **)&COLROW_GET_SEGMENT (info, n);
1825 g_return_if_fail (n >= 0);
1826 g_return_if_fail (n < colrow_max (is_cols, sheet));
1828 if (*psegment == NULL)
1829 *psegment = g_new0 (ColRowSegment, 1);
1830 colrow_free ((*psegment)->info[COLROW_SUB_INDEX (n)]);
1831 (*psegment)->info[COLROW_SUB_INDEX (n)] = cp;
1833 if (cp->outline_level > info->max_outline_level)
1834 info->max_outline_level = cp->outline_level;
1835 if (n > info->max_used) {
1836 info->max_used = n;
1837 sheet->priv->resize_scrollbar = TRUE;
1841 static void
1842 sheet_reposition_objects (Sheet const *sheet, GnmCellPos const *pos)
1844 GSList *ptr;
1845 for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = ptr->next )
1846 sheet_object_update_bounds (GNM_SO (ptr->data), pos);
1850 * sheet_flag_status_update_cell:
1851 * @cell: The cell that has changed.
1853 * flag the sheet as requiring an update to the status display
1854 * if the supplied cell location is the edit cursor, or part of the
1855 * selected region.
1857 * Will cause the format toolbar, the edit area, and the auto expressions to be
1858 * updated if appropriate.
1860 void
1861 sheet_flag_status_update_cell (GnmCell const *cell)
1863 SHEET_FOREACH_VIEW (cell->base.sheet, sv,
1864 sv_flag_status_update_pos (sv, &cell->pos););
1868 * sheet_flag_status_update_range:
1869 * @sheet:
1870 * @range: If NULL then force an update.
1872 * flag the sheet as requiring an update to the status display
1873 * if the supplied cell location contains the edit cursor, or intersects of
1874 * the selected region.
1876 * Will cause the format toolbar, the edit area, and the auto expressions to be
1877 * updated if appropriate.
1879 void
1880 sheet_flag_status_update_range (Sheet const *sheet, GnmRange const *range)
1882 SHEET_FOREACH_VIEW (sheet, sv,
1883 sv_flag_status_update_range (sv, range););
1887 * sheet_flag_style_update_range:
1888 * @sheet: The sheet being changed
1889 * @range: the range that is changing.
1891 * Flag format changes that will require updating the format indicators.
1893 void
1894 sheet_flag_style_update_range (Sheet const *sheet, GnmRange const *range)
1896 SHEET_FOREACH_VIEW (sheet, sv,
1897 sv_flag_style_update_range (sv, range););
1901 * sheet_flag_recompute_spans:
1902 * @sheet:
1904 * Flag the sheet as requiring a full span recomputation the next time
1905 * sheet_update is called.
1907 void
1908 sheet_flag_recompute_spans (Sheet const *sheet)
1910 sheet->priv->recompute_spans = TRUE;
1913 static gboolean
1914 cb_outline_level (GnmColRowIter const *iter, int *outline_level)
1916 if (*outline_level < iter->cri->outline_level)
1917 *outline_level = iter->cri->outline_level;
1918 return FALSE;
1922 * sheet_colrow_fit_gutter:
1923 * @sheet: Sheet to change for.
1924 * @is_cols: Column gutter or row gutter?
1926 * Find the current max outline level.
1928 static int
1929 sheet_colrow_fit_gutter (Sheet const *sheet, gboolean is_cols)
1931 int outline_level = 0;
1932 colrow_foreach (is_cols ? &sheet->cols : &sheet->rows,
1933 0, colrow_max (is_cols, sheet) - 1,
1934 (ColRowHandler)cb_outline_level, &outline_level);
1935 return outline_level;
1939 * sheet_update_only_grid:
1940 * @sheet: #Sheet
1942 * Should be called after a logical command has finished processing
1943 * to request redraws for any pending events
1945 void
1946 sheet_update_only_grid (Sheet const *sheet)
1948 SheetPrivate *p;
1950 g_return_if_fail (IS_SHEET (sheet));
1952 p = sheet->priv;
1954 /* be careful these can toggle flags */
1955 if (p->recompute_max_col_group) {
1956 sheet_colrow_gutter ((Sheet *)sheet, TRUE,
1957 sheet_colrow_fit_gutter (sheet, TRUE));
1958 sheet->priv->recompute_max_col_group = FALSE;
1960 if (p->recompute_max_row_group) {
1961 sheet_colrow_gutter ((Sheet *)sheet, FALSE,
1962 sheet_colrow_fit_gutter (sheet, FALSE));
1963 sheet->priv->recompute_max_row_group = FALSE;
1966 SHEET_FOREACH_VIEW (sheet, sv, {
1967 if (sv->reposition_selection) {
1968 sv->reposition_selection = FALSE;
1970 /* when moving we cleared the selection before
1971 * arriving in here.
1973 if (sv->selections != NULL)
1974 sv_selection_set (sv, &sv->edit_pos_real,
1975 sv->cursor.base_corner.col,
1976 sv->cursor.base_corner.row,
1977 sv->cursor.move_corner.col,
1978 sv->cursor.move_corner.row);
1982 if (p->recompute_spans) {
1983 p->recompute_spans = FALSE;
1984 /* FIXME : I would prefer to use GNM_SPANCALC_RENDER rather than
1985 * RE_RENDER. It only renders those cells which are not
1986 * rendered. The trouble is that when a col changes size we
1987 * need to rerender, but currently nothing marks that.
1989 * hmm, that suggests an approach. maybe I can install a per
1990 * col flag. Then add a flag clearing loop after the
1991 * sheet_calc_span.
1993 #if 0
1994 sheet_calc_spans (sheet, GNM_SPANCALC_RESIZE|GNM_SPANCALC_RE_RENDER |
1995 (p->recompute_visibility ?
1996 SPANCALC_NO_DRAW : GNM_SPANCALC_SIMPLE));
1997 #endif
1998 sheet_queue_respan (sheet, 0, gnm_sheet_get_last_row (sheet));
2001 if (p->reposition_objects.row < gnm_sheet_get_max_rows (sheet) ||
2002 p->reposition_objects.col < gnm_sheet_get_max_cols (sheet)) {
2003 SHEET_FOREACH_VIEW (sheet, sv, {
2004 if (!p->resize && sv_is_frozen (sv)) {
2005 if (p->reposition_objects.col < sv->unfrozen_top_left.col ||
2006 p->reposition_objects.row < sv->unfrozen_top_left.row) {
2007 SHEET_VIEW_FOREACH_CONTROL(sv, control,
2008 sc_resize (control, FALSE););
2012 sheet_reposition_objects (sheet, &p->reposition_objects);
2013 p->reposition_objects.row = gnm_sheet_get_max_rows (sheet);
2014 p->reposition_objects.col = gnm_sheet_get_max_cols (sheet);
2017 if (p->resize) {
2018 p->resize = FALSE;
2019 SHEET_FOREACH_CONTROL (sheet, sv, control, sc_resize (control, FALSE););
2022 if (p->recompute_visibility) {
2023 /* TODO : There is room for some opimization
2024 * We only need to force complete visibility recalculation
2025 * (which we do in sheet_compute_visible_region)
2026 * if a row or col before the start of the visible region.
2027 * If we are REALLY smart we could even accumulate the size differential
2028 * and use that.
2030 p->recompute_visibility = FALSE;
2031 p->resize_scrollbar = FALSE; /* compute_visible_region does this */
2032 SHEET_FOREACH_CONTROL(sheet, view, control,
2033 sc_recompute_visible_region (control, TRUE););
2034 sheet_redraw_all (sheet, TRUE);
2037 if (p->resize_scrollbar) {
2038 sheet_scrollbar_config (sheet);
2039 p->resize_scrollbar = FALSE;
2041 if (p->filters_changed) {
2042 p->filters_changed = FALSE;
2043 SHEET_FOREACH_CONTROL (sheet, sv, sc,
2044 wb_control_menu_state_update (sc_wbc (sc), MS_ADD_VS_REMOVE_FILTER););
2049 * sheet_update:
2050 * @sheet: #Sheet
2052 * Should be called after a logical command has finished processing to request
2053 * redraws for any pending events, and to update the various status regions
2055 void
2056 sheet_update (Sheet const *sheet)
2058 g_return_if_fail (IS_SHEET (sheet));
2060 sheet_update_only_grid (sheet);
2062 SHEET_FOREACH_VIEW (sheet, sv, sv_update (sv););
2066 * sheet_cell_get:
2067 * @sheet: The sheet where we want to locate the cell
2068 * @col: the cell column
2069 * @row: the cell row
2071 * Return value: (nullable): a #GnmCell, or %NULL if the cell does not exist
2073 GnmCell *
2074 sheet_cell_get (Sheet const *sheet, int col, int row)
2076 GnmCell *cell;
2077 GnmCell key;
2079 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2081 key.pos.col = col;
2082 key.pos.row = row;
2083 cell = g_hash_table_lookup (sheet->cell_hash, &key);
2085 return cell;
2089 * sheet_cell_fetch:
2090 * @sheet: The sheet where we want to locate the cell
2091 * @col: the cell column
2092 * @row: the cell row
2094 * Return value: a #GnmCell containing at (@col,@row).
2095 * If no cell existed at that location before, it is created.
2097 GnmCell *
2098 sheet_cell_fetch (Sheet *sheet, int col, int row)
2100 GnmCell *cell;
2102 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2104 cell = sheet_cell_get (sheet, col, row);
2105 if (!cell)
2106 cell = sheet_cell_create (sheet, col, row);
2108 return cell;
2112 * sheet_colrow_can_group:
2113 * @sheet: #Sheet
2114 * @r: A #GnmRange
2115 * @is_cols: boolean
2117 * Returns TRUE if the cols/rows in @r.start -> @r.end can be grouped, return
2118 * FALSE otherwise. You can invert the result if you need to find out if a
2119 * group can be ungrouped.
2121 gboolean
2122 sheet_colrow_can_group (Sheet *sheet, GnmRange const *r, gboolean is_cols)
2124 ColRowInfo const *start_cri, *end_cri;
2125 int start, end;
2127 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
2129 if (is_cols) {
2130 start = r->start.col;
2131 end = r->end.col;
2132 } else {
2133 start = r->start.row;
2134 end = r->end.row;
2136 start_cri = sheet_colrow_fetch (sheet, start, is_cols);
2137 end_cri = sheet_colrow_fetch (sheet, end, is_cols);
2139 /* Groups on outline level 0 (no outline) may always be formed */
2140 if (start_cri->outline_level == 0 || end_cri->outline_level == 0)
2141 return TRUE;
2143 /* We just won't group a group that already exists (or doesn't), it's useless */
2144 return (colrow_find_outline_bound (sheet, is_cols, start, start_cri->outline_level, FALSE) != start ||
2145 colrow_find_outline_bound (sheet, is_cols, end, end_cri->outline_level, TRUE) != end);
2148 gboolean
2149 sheet_colrow_group_ungroup (Sheet *sheet, GnmRange const *r,
2150 gboolean is_cols, gboolean group)
2152 int i, new_max, start, end;
2153 int const step = group ? 1 : -1;
2155 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
2157 /* Can we group/ungroup ? */
2158 if (group != sheet_colrow_can_group (sheet, r, is_cols))
2159 return FALSE;
2161 if (is_cols) {
2162 start = r->start.col;
2163 end = r->end.col;
2164 } else {
2165 start = r->start.row;
2166 end = r->end.row;
2169 /* Set new outline for each col/row and find highest outline level */
2170 new_max = (is_cols ? &sheet->cols : &sheet->rows)->max_outline_level;
2171 for (i = start; i <= end; i++) {
2172 ColRowInfo *cri = sheet_colrow_fetch (sheet, i, is_cols);
2173 int const new_level = cri->outline_level + step;
2175 if (new_level >= 0) {
2176 colrow_set_outline (cri, new_level, FALSE);
2177 if (new_max < new_level)
2178 new_max = new_level;
2182 if (!group)
2183 new_max = sheet_colrow_fit_gutter (sheet, is_cols);
2185 sheet_colrow_gutter (sheet, is_cols, new_max);
2186 SHEET_FOREACH_VIEW (sheet, sv,
2187 sv_redraw_headers (sv, is_cols, !is_cols, NULL););
2189 return TRUE;
2193 * sheet_colrow_gutter:
2194 * @sheet:
2195 * @is_cols:
2196 * @max_outline:
2198 * Set the maximum outline levels for cols or rows.
2200 void
2201 sheet_colrow_gutter (Sheet *sheet, gboolean is_cols, int max_outline)
2203 ColRowCollection *infos;
2205 g_return_if_fail (IS_SHEET (sheet));
2207 infos = is_cols ? &(sheet->cols) : &(sheet->rows);
2208 if (infos->max_outline_level != max_outline) {
2209 sheet->priv->resize = TRUE;
2210 infos->max_outline_level = max_outline;
2214 struct sheet_extent_data {
2215 GnmRange range;
2216 gboolean spans_and_merges_extend;
2217 gboolean ignore_empties;
2218 gboolean include_hidden;
2221 static void
2222 cb_sheet_get_extent (G_GNUC_UNUSED gpointer ignored, gpointer value, gpointer data)
2224 GnmCell const *cell = (GnmCell const *) value;
2225 struct sheet_extent_data *res = data;
2226 Sheet *sheet = cell->base.sheet;
2227 ColRowInfo *ri = NULL;
2229 if (res->ignore_empties && gnm_cell_is_empty (cell))
2230 return;
2231 if (!res->include_hidden) {
2232 ri = sheet_col_get (sheet, cell->pos.col);
2233 if (!ri->visible)
2234 return;
2235 ri = sheet_row_get (sheet, cell->pos.row);
2236 if (!ri->visible)
2237 return;
2240 /* Remember the first cell is the min & max */
2241 if (res->range.start.col > cell->pos.col)
2242 res->range.start.col = cell->pos.col;
2243 if (res->range.end.col < cell->pos.col)
2244 res->range.end.col = cell->pos.col;
2245 if (res->range.start.row > cell->pos.row)
2246 res->range.start.row = cell->pos.row;
2247 if (res->range.end.row < cell->pos.row)
2248 res->range.end.row = cell->pos.row;
2250 if (!res->spans_and_merges_extend)
2251 return;
2253 /* Cannot span AND merge */
2254 if (gnm_cell_is_merged (cell)) {
2255 GnmRange const *merged =
2256 gnm_sheet_merge_is_corner (sheet, &cell->pos);
2257 res->range = range_union (&res->range, merged);
2258 } else {
2259 CellSpanInfo const *span;
2260 if (ri == NULL)
2261 ri = sheet_row_get (sheet, cell->pos.row);
2262 if (ri->needs_respan)
2263 row_calc_spans (ri, cell->pos.row, sheet);
2264 span = row_span_get (ri, cell->pos.col);
2265 if (NULL != span) {
2266 if (res->range.start.col > span->left)
2267 res->range.start.col = span->left;
2268 if (res->range.end.col < span->right)
2269 res->range.end.col = span->right;
2275 * sheet_get_extent:
2276 * @sheet: the sheet
2277 * @spans_and_merges_extend: optionally extend region for spans and merges.
2278 * @include_hidden: whether to include the content of hidden cells.
2280 * calculates the area occupied by cell data.
2282 * NOTE: When spans_and_merges_extend is TRUE, this function will calculate
2283 * all spans. That might be expensive.
2285 * NOTE: This refers to *visible* contents. Cells with empty values, including
2286 * formulas with such values, are *ignored.
2288 * Return value: the range.
2290 GnmRange
2291 sheet_get_extent (Sheet const *sheet, gboolean spans_and_merges_extend, gboolean include_hidden)
2293 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2294 struct sheet_extent_data closure;
2295 GSList *ptr;
2297 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2299 closure.range.start.col = gnm_sheet_get_last_col (sheet) + 1;
2300 closure.range.start.row = gnm_sheet_get_last_row (sheet) + 1;
2301 closure.range.end.col = 0;
2302 closure.range.end.row = 0;
2303 closure.spans_and_merges_extend = spans_and_merges_extend;
2304 closure.include_hidden = include_hidden;
2305 closure.ignore_empties = TRUE;
2307 sheet_cell_foreach (sheet, &cb_sheet_get_extent, &closure);
2309 for (ptr = sheet->sheet_objects; ptr; ptr = ptr->next) {
2310 SheetObject *so = GNM_SO (ptr->data);
2312 closure.range.start.col = MIN (so->anchor.cell_bound.start.col,
2313 closure.range.start.col);
2314 closure.range.start.row = MIN (so->anchor.cell_bound.start.row,
2315 closure.range.start.row);
2316 closure.range.end.col = MAX (so->anchor.cell_bound.end.col,
2317 closure.range.end.col);
2318 closure.range.end.row = MAX (so->anchor.cell_bound.end.row,
2319 closure.range.end.row);
2322 if (closure.range.start.col > gnm_sheet_get_last_col (sheet))
2323 closure.range.start.col = 0;
2324 if (closure.range.start.row > gnm_sheet_get_last_row (sheet))
2325 closure.range.start.row = 0;
2326 if (closure.range.end.col < 0)
2327 closure.range.end.col = 0;
2328 if (closure.range.end.row < 0)
2329 closure.range.end.row = 0;
2331 return closure.range;
2335 * sheet_get_cells_extent:
2336 * @sheet: the sheet
2338 * calculates the area occupied by cells, including empty cells.
2340 * Return value: the range.
2342 GnmRange
2343 sheet_get_cells_extent (Sheet const *sheet)
2345 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2346 struct sheet_extent_data closure;
2348 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2350 closure.range.start.col = gnm_sheet_get_last_col (sheet);
2351 closure.range.start.row = gnm_sheet_get_last_row (sheet);
2352 closure.range.end.col = 0;
2353 closure.range.end.row = 0;
2354 closure.spans_and_merges_extend = FALSE;
2355 closure.include_hidden = TRUE;
2356 closure.ignore_empties = FALSE;
2358 sheet_cell_foreach (sheet, &cb_sheet_get_extent, &closure);
2360 return closure.range;
2364 GnmRange *
2365 sheet_get_nominal_printarea (Sheet const *sheet)
2367 GnmNamedExpr *nexpr;
2368 GnmValue *val;
2369 GnmParsePos pos;
2370 GnmRange *r;
2371 GnmRangeRef const *r_ref;
2372 gint max_rows;
2373 gint max_cols;
2375 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2377 parse_pos_init_sheet (&pos, sheet);
2378 nexpr = expr_name_lookup (&pos, "Print_Area");
2379 if (nexpr == NULL)
2380 return NULL;
2382 val = gnm_expr_top_get_range (nexpr->texpr);
2383 r_ref = val ? value_get_rangeref (val) : NULL;
2384 if (r_ref == NULL) {
2385 value_release (val);
2386 return NULL;
2389 r = g_new0 (GnmRange, 1);
2390 range_init_rangeref (r, r_ref);
2391 value_release (val);
2393 if (r->end.col >= (max_cols = gnm_sheet_get_max_cols (sheet)))
2394 r->end.col = max_cols - 1;
2395 if (r->end.row >= (max_rows = gnm_sheet_get_max_rows (sheet)))
2396 r->end.row = max_rows - 1;
2397 if (r->start.col < 0)
2398 r->start.col = 0;
2399 if (r->start.row < 0)
2400 r->start.row = 0;
2402 return r;
2405 GnmRange
2406 sheet_get_printarea (Sheet const *sheet,
2407 gboolean include_styles,
2408 gboolean ignore_printarea)
2410 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2411 GnmRange print_area;
2413 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2415 if (!ignore_printarea) {
2416 GnmRange *r = sheet_get_nominal_printarea (sheet);
2417 if (r != NULL) {
2418 print_area = *r;
2419 g_free (r);
2420 return print_area;
2424 print_area = sheet_get_extent (sheet, TRUE, FALSE);
2425 if (include_styles)
2426 sheet_style_get_extent (sheet, &print_area);
2428 return print_area;
2431 struct cb_fit {
2432 int max;
2433 gboolean ignore_strings;
2436 /* find the maximum width in a range. */
2437 static GnmValue *
2438 cb_max_cell_width (GnmCellIter const *iter, struct cb_fit *data)
2440 int width;
2441 GnmCell *cell = iter->cell;
2442 GnmRenderedValue *rv;
2444 if (gnm_cell_is_merged (cell))
2445 return NULL;
2448 * Special handling for manual recalc. We need to eval newly
2449 * entered expressions. gnm_cell_render_value will do that for us,
2450 * but we want to short-circuit some strings early.
2452 if (cell->base.flags & GNM_CELL_HAS_NEW_EXPR)
2453 gnm_cell_eval (cell);
2455 if (data->ignore_strings && VALUE_IS_STRING (cell->value))
2456 return NULL;
2458 /* Variable width cell must be re-rendered */
2459 rv = gnm_cell_get_rendered_value (cell);
2460 if (rv == NULL || rv->variable_width)
2461 gnm_cell_render_value (cell, FALSE);
2463 /* Make sure things are as-if drawn. */
2464 cell_finish_layout (cell, NULL, iter->ci->size_pixels, TRUE);
2466 width = gnm_cell_rendered_width (cell) + gnm_cell_rendered_offset (cell);
2467 if (width > data->max)
2468 data->max = width;
2470 return NULL;
2474 * sheet_col_size_fit_pixels:
2475 * @sheet: The sheet
2476 * @col: the column that we want to query
2477 * @srow: starting row.
2478 * @erow: ending row.
2479 * @ignore_strings: skip cells containing string values.
2481 * This routine computes the ideal size for the column to make the contents all
2482 * cells in the column visible.
2484 * Returns: Maximum size in pixels INCLUDING margins and grid lines
2485 * or 0 if there are no cells.
2488 sheet_col_size_fit_pixels (Sheet *sheet, int col, int srow, int erow,
2489 gboolean ignore_strings)
2491 struct cb_fit data;
2492 ColRowInfo *ci = sheet_col_get (sheet, col);
2493 if (ci == NULL)
2494 return 0;
2496 data.max = -1;
2497 data.ignore_strings = ignore_strings;
2498 sheet_foreach_cell_in_range (sheet,
2499 CELL_ITER_IGNORE_NONEXISTENT |
2500 CELL_ITER_IGNORE_HIDDEN |
2501 CELL_ITER_IGNORE_FILTERED,
2502 col, srow, col, erow,
2503 (CellIterFunc)&cb_max_cell_width, &data);
2505 /* Reset to the default width if the column was empty */
2506 if (data.max <= 0)
2507 return 0;
2509 /* GnmCell width does not include margins or far grid line*/
2510 return data.max + GNM_COL_MARGIN + GNM_COL_MARGIN + 1;
2513 /* find the maximum height in a range. */
2514 static GnmValue *
2515 cb_max_cell_height (GnmCellIter const *iter, struct cb_fit *data)
2517 int height;
2518 GnmCell *cell = iter->cell;
2520 if (gnm_cell_is_merged (cell))
2521 return NULL;
2524 * Special handling for manual recalc. We need to eval newly
2525 * entered expressions. gnm_cell_render_value will do that for us,
2526 * but we want to short-circuit some strings early.
2528 if (cell->base.flags & GNM_CELL_HAS_NEW_EXPR)
2529 gnm_cell_eval (cell);
2531 if (data->ignore_strings && VALUE_IS_STRING (cell->value))
2532 return NULL;
2534 if (!VALUE_IS_STRING (cell->value)) {
2536 * Mildly cheating to avoid performance problems, See bug
2537 * 359392. This assumes that non-strings do not wrap and
2538 * that they are all the same height, more or less.
2540 Sheet const *sheet = cell->base.sheet;
2541 height = gnm_style_get_pango_height (gnm_cell_get_style (cell),
2542 sheet->rendered_values->context,
2543 sheet->last_zoom_factor_used);
2544 } else {
2545 (void)gnm_cell_fetch_rendered_value (cell, TRUE);
2547 /* Make sure things are as-if drawn. Inhibit #####s. */
2548 cell_finish_layout (cell, NULL, iter->ci->size_pixels, FALSE);
2550 height = gnm_cell_rendered_height (cell);
2553 if (height > data->max)
2554 data->max = height;
2556 return NULL;
2560 * sheet_row_size_fit_pixels:
2561 * @sheet: The sheet
2562 * @row: the row that we want to query
2563 * @scol: starting column.
2564 * @ecol: ending column.
2565 * @ignore_strings: skip cells containing string values.
2567 * This routine computes the ideal size for the row to make all data fit
2568 * properly.
2570 * Returns: Maximum size in pixels INCLUDING margins and grid lines
2571 * or 0 if there are no cells.
2574 sheet_row_size_fit_pixels (Sheet *sheet, int row, int scol, int ecol,
2575 gboolean ignore_strings)
2577 struct cb_fit data;
2578 ColRowInfo const *ri = sheet_row_get (sheet, row);
2579 if (ri == NULL)
2580 return 0;
2582 data.max = -1;
2583 data.ignore_strings = ignore_strings;
2584 sheet_foreach_cell_in_range (sheet,
2585 CELL_ITER_IGNORE_NONEXISTENT |
2586 CELL_ITER_IGNORE_HIDDEN |
2587 CELL_ITER_IGNORE_FILTERED,
2588 scol, row,
2589 ecol, row,
2590 (CellIterFunc)&cb_max_cell_height, &data);
2592 /* Reset to the default width if the column was empty */
2593 if (data.max <= 0)
2594 return 0;
2596 /* GnmCell height does not include margins or bottom grid line */
2597 return data.max + GNM_ROW_MARGIN + GNM_ROW_MARGIN + 1;
2600 struct recalc_span_closure {
2601 Sheet *sheet;
2602 int col;
2605 static gboolean
2606 cb_recalc_spans_in_col (GnmColRowIter const *iter, gpointer user)
2608 struct recalc_span_closure *closure = user;
2609 int const col = closure->col;
2610 int left, right;
2611 CellSpanInfo const *span = row_span_get (iter->cri, col);
2613 if (span) {
2614 /* If there is an existing span see if it changed */
2615 GnmCell const * const cell = span->cell;
2616 cell_calc_span (cell, &left, &right);
2617 if (left != span->left || right != span->right) {
2618 cell_unregister_span (cell);
2619 cell_register_span (cell, left, right);
2621 } else {
2622 /* If there is a cell see if it started to span */
2623 GnmCell const * const cell = sheet_cell_get (closure->sheet, col, iter->pos);
2624 if (cell) {
2625 cell_calc_span (cell, &left, &right);
2626 if (left != right)
2627 cell_register_span (cell, left, right);
2631 return FALSE;
2635 * sheet_recompute_spans_for_col:
2636 * @sheet: the sheet
2637 * @col: The column that changed
2639 * This routine recomputes the column span for the cells that touches
2640 * the column.
2642 void
2643 sheet_recompute_spans_for_col (Sheet *sheet, int col)
2645 struct recalc_span_closure closure;
2646 closure.sheet = sheet;
2647 closure.col = col;
2649 colrow_foreach (&sheet->rows, 0, gnm_sheet_get_last_row (sheet),
2650 &cb_recalc_spans_in_col, &closure);
2653 /****************************************************************************/
2654 typedef struct {
2655 GnmValue *val;
2656 GnmExprTop const *texpr;
2657 GnmRange expr_bound;
2658 } closure_set_cell_value;
2660 static GnmValue *
2661 cb_set_cell_content (GnmCellIter const *iter, closure_set_cell_value *info)
2663 GnmExprTop const *texpr = info->texpr;
2664 GnmCell *cell;
2666 cell = iter->cell;
2667 if (!cell)
2668 cell = sheet_cell_create (iter->pp.sheet,
2669 iter->pp.eval.col,
2670 iter->pp.eval.row);
2673 * If we are overwriting an array, we need to clear things here
2674 * or gnm_cell_set_expr/gnm_cell_set_value will complain.
2676 if (cell->base.texpr && gnm_expr_top_is_array (cell->base.texpr))
2677 gnm_cell_cleanout (cell);
2679 if (texpr != NULL) {
2680 if (!range_contains (&info->expr_bound,
2681 iter->pp.eval.col, iter->pp.eval.row)) {
2682 GnmExprRelocateInfo rinfo;
2684 rinfo.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE;
2685 rinfo.pos = iter->pp;
2686 rinfo.origin.start = iter->pp.eval;
2687 rinfo.origin.end = iter->pp.eval;
2688 rinfo.origin_sheet = iter->pp.sheet;
2689 rinfo.target_sheet = iter->pp.sheet;
2690 rinfo.col_offset = 0;
2691 rinfo.row_offset = 0;
2692 texpr = gnm_expr_top_relocate (texpr, &rinfo, FALSE);
2695 gnm_cell_set_expr (cell, texpr);
2696 } else
2697 gnm_cell_set_value (cell, value_dup (info->val));
2698 return NULL;
2701 static GnmValue *
2702 cb_clear_non_corner (GnmCellIter const *iter, GnmRange const *merged)
2704 if (merged->start.col != iter->pp.eval.col ||
2705 merged->start.row != iter->pp.eval.row)
2706 gnm_cell_set_value (iter->cell, value_new_empty ());
2707 return NULL;
2711 * sheet_range_set_expr_cb:
2712 * @sr: #GnmSheetRange
2713 * @texpr: #GnmExprTop
2716 * Does NOT check for array division.
2718 static void
2719 sheet_range_set_expr_cb (GnmSheetRange const *sr, GnmExprTop const *texpr)
2721 closure_set_cell_value closure;
2722 GSList *merged, *ptr;
2724 g_return_if_fail (sr != NULL);
2725 g_return_if_fail (texpr != NULL);
2727 closure.texpr = texpr;
2728 gnm_expr_top_get_boundingbox (closure.texpr,
2729 sr->sheet,
2730 &closure.expr_bound);
2732 sheet_region_queue_recalc (sr->sheet, &sr->range);
2733 /* Store the parsed result creating any cells necessary */
2734 sheet_foreach_cell_in_range
2735 (sr->sheet, CELL_ITER_ALL,
2736 sr->range.start.col, sr->range.start.row,
2737 sr->range.end.col, sr->range.end.row,
2738 (CellIterFunc)&cb_set_cell_content, &closure);
2740 merged = gnm_sheet_merge_get_overlap (sr->sheet, &sr->range);
2741 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
2742 GnmRange const *tmp = ptr->data;
2743 sheet_foreach_cell_in_range
2744 (sr->sheet, CELL_ITER_IGNORE_BLANK,
2745 tmp->start.col, tmp->start.row,
2746 tmp->end.col, tmp->end.row,
2747 (CellIterFunc)&cb_clear_non_corner,
2748 (gpointer)tmp);
2750 g_slist_free (merged);
2752 sheet_region_queue_recalc (sr->sheet, &sr->range);
2753 sheet_flag_status_update_range (sr->sheet, &sr->range);
2754 sheet_queue_respan (sr->sheet, sr->range.start.row,
2755 sr->range.end.row);
2759 * sheet_range_set_expr_undo:
2760 * @sr: #GnmSheetRange
2761 * @texpr: #GnmExprTop
2763 * Returns: (transfer full): the newly created #GOUndo.
2765 GOUndo *
2766 sheet_range_set_expr_undo (GnmSheetRange *sr, GnmExprTop const *texpr)
2768 gnm_expr_top_ref (texpr);
2769 return go_undo_binary_new
2770 (sr, (gpointer)texpr,
2771 (GOUndoBinaryFunc) sheet_range_set_expr_cb,
2772 (GFreeFunc) gnm_sheet_range_free,
2773 (GFreeFunc) gnm_expr_top_unref);
2778 * sheet_range_set_text:
2779 * @pos: The position from which to parse an expression.
2780 * @r: The range to fill
2781 * @str: The text to be parsed and assigned.
2783 * Does NOT check for array division.
2784 * Does NOT redraw
2785 * Does NOT generate spans.
2787 void
2788 sheet_range_set_text (GnmParsePos const *pos, GnmRange const *r, char const *str)
2790 closure_set_cell_value closure;
2791 GSList *merged, *ptr;
2792 Sheet *sheet;
2794 g_return_if_fail (pos != NULL);
2795 g_return_if_fail (r != NULL);
2796 g_return_if_fail (str != NULL);
2798 sheet = pos->sheet;
2800 parse_text_value_or_expr (pos, str,
2801 &closure.val, &closure.texpr);
2803 if (closure.texpr)
2804 gnm_expr_top_get_boundingbox (closure.texpr,
2805 sheet,
2806 &closure.expr_bound);
2808 /* Store the parsed result creating any cells necessary */
2809 sheet_foreach_cell_in_range (sheet, CELL_ITER_ALL,
2810 r->start.col, r->start.row, r->end.col, r->end.row,
2811 (CellIterFunc)&cb_set_cell_content, &closure);
2813 merged = gnm_sheet_merge_get_overlap (sheet, r);
2814 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
2815 GnmRange const *tmp = ptr->data;
2816 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_BLANK,
2817 tmp->start.col, tmp->start.row,
2818 tmp->end.col, tmp->end.row,
2819 (CellIterFunc)&cb_clear_non_corner, (gpointer)tmp);
2821 g_slist_free (merged);
2823 sheet_region_queue_recalc (sheet, r);
2825 value_release (closure.val);
2826 if (closure.texpr)
2827 gnm_expr_top_unref (closure.texpr);
2829 sheet_flag_status_update_range (sheet, r);
2832 static void
2833 sheet_range_set_text_cb (GnmSheetRange const *sr, gchar const *text)
2835 GnmParsePos pos;
2837 pos.eval = sr->range.start;
2838 pos.sheet = sr->sheet;
2839 pos.wb = sr->sheet->workbook;
2841 sheet_range_set_text (&pos, &sr->range, text);
2842 sheet_region_queue_recalc (sr->sheet, &sr->range);
2843 sheet_flag_status_update_range (sr->sheet, &sr->range);
2844 sheet_queue_respan (sr->sheet, sr->range.start.row,
2845 sr->range.end.row);
2846 sheet_redraw_range (sr->sheet, &sr->range);
2850 * sheet_range_set_text_undo:
2851 * @sr: #GnmSheetRange
2852 * @text:
2854 * Returns: (transfer full): the newly created #GOUndo.
2856 GOUndo *
2857 sheet_range_set_text_undo (GnmSheetRange *sr,
2858 char const *text)
2860 return go_undo_binary_new
2861 (sr, g_strdup (text),
2862 (GOUndoBinaryFunc) sheet_range_set_text_cb,
2863 (GFreeFunc) gnm_sheet_range_free,
2864 (GFreeFunc) g_free);
2868 static GnmValue *
2869 cb_set_markup (GnmCellIter const *iter, PangoAttrList *markup)
2871 GnmCell *cell;
2873 cell = iter->cell;
2874 if (!cell)
2875 return NULL;
2877 if (VALUE_IS_STRING (cell->value)) {
2878 GOFormat *fmt;
2879 GnmValue *val = value_dup (cell->value);
2881 fmt = go_format_new_markup (markup, TRUE);
2882 value_set_fmt (val, fmt);
2883 go_format_unref (fmt);
2885 gnm_cell_cleanout (cell);
2886 gnm_cell_assign_value (cell, val);
2888 return NULL;
2891 static void
2892 sheet_range_set_markup_cb (GnmSheetRange const *sr, PangoAttrList *markup)
2894 sheet_foreach_cell_in_range
2895 (sr->sheet, CELL_ITER_ALL,
2896 sr->range.start.col, sr->range.start.row,
2897 sr->range.end.col, sr->range.end.row,
2898 (CellIterFunc)&cb_set_markup, markup);
2900 sheet_region_queue_recalc (sr->sheet, &sr->range);
2901 sheet_flag_status_update_range (sr->sheet, &sr->range);
2902 sheet_queue_respan (sr->sheet, sr->range.start.row,
2903 sr->range.end.row);
2907 * sheet_range_set_markup_undo:
2908 * @sr: #GnmSheetRange
2909 * @markup: #PangoAttrList
2911 * Returns: (transfer full): the newly created #GOUndo.
2913 GOUndo *
2914 sheet_range_set_markup_undo (GnmSheetRange *sr, PangoAttrList *markup)
2916 if (markup == NULL)
2917 return NULL;
2918 return go_undo_binary_new
2919 (sr, pango_attr_list_ref (markup),
2920 (GOUndoBinaryFunc) sheet_range_set_markup_cb,
2921 (GFreeFunc) gnm_sheet_range_free,
2922 (GFreeFunc) pango_attr_list_unref);
2926 * sheet_cell_get_value:
2927 * @sheet: Sheet
2928 * @col: Source column
2929 * @row: Source row
2931 * Returns: (transfer none) (nullable): the cell's current value.
2933 GnmValue const *
2934 sheet_cell_get_value (Sheet *sheet, int const col, int const row)
2936 GnmCell *cell;
2938 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2940 cell = sheet_cell_get (sheet, col, row);
2942 return cell ? cell->value : NULL;
2946 * sheet_cell_set_text:
2947 * @cell: A cell.
2948 * @str: the text to set.
2949 * @markup: (allow-none): an optional PangoAttrList.
2951 * Marks the sheet as dirty
2952 * Clears old spans.
2953 * Flags status updates
2954 * Queues recalcs
2956 void
2957 sheet_cell_set_text (GnmCell *cell, char const *text, PangoAttrList *markup)
2959 GnmExprTop const *texpr;
2960 GnmValue *val;
2961 GnmParsePos pp;
2963 g_return_if_fail (cell != NULL);
2964 g_return_if_fail (text != NULL);
2965 g_return_if_fail (!gnm_cell_is_nonsingleton_array (cell));
2967 parse_text_value_or_expr (parse_pos_init_cell (&pp, cell),
2968 text, &val, &texpr);
2970 /* Queue a redraw before in case the span changes */
2971 sheet_redraw_cell (cell);
2973 if (texpr != NULL) {
2974 gnm_cell_set_expr (cell, texpr);
2975 gnm_expr_top_unref (texpr);
2978 * Queue recalc before spanning. Otherwise spanning may
2979 * create a bogus rendered value, see #495879.
2981 cell_queue_recalc (cell);
2983 /* Clear spans from _other_ cells */
2984 sheet_cell_calc_span (cell, GNM_SPANCALC_SIMPLE);
2985 } else {
2986 g_return_if_fail (val != NULL);
2988 if (markup != NULL && VALUE_IS_STRING (val)) {
2989 gboolean quoted = (text[0] == '\'');
2990 PangoAttrList *adj_markup;
2991 GOFormat *fmt;
2993 if (quoted) {
2994 /* We ate the quote. Adjust. Ugh. */
2995 adj_markup = pango_attr_list_copy (markup);
2996 go_pango_attr_list_erase (adj_markup, 0, 1);
2997 } else
2998 adj_markup = markup;
3000 fmt = go_format_new_markup (adj_markup, TRUE);
3001 value_set_fmt (val, fmt);
3002 go_format_unref (fmt);
3003 if (quoted)
3004 pango_attr_list_unref (adj_markup);
3007 gnm_cell_set_value (cell, val);
3009 /* Queue recalc before spanning, see above. */
3010 cell_queue_recalc (cell);
3012 sheet_cell_calc_span (cell, GNM_SPANCALC_RESIZE | GNM_SPANCALC_RENDER);
3015 sheet_flag_status_update_cell (cell);
3019 * sheet_cell_set_text_gi: (rename-to sheet_cell_set_text)
3020 * @sheet: #Sheet
3021 * @col: column number
3022 * @row: row number
3023 * @str: the text to set.
3025 * Sets the contents of a cell.
3027 void
3028 sheet_cell_set_text_gi (Sheet *sheet, int col, int row, char const *str)
3030 sheet_cell_set_text (sheet_cell_fetch (sheet, col, row), str, NULL);
3035 * sheet_cell_set_expr:
3037 * Marks the sheet as dirty
3038 * Clears old spans.
3039 * Flags status updates
3040 * Queues recalcs
3042 void
3043 sheet_cell_set_expr (GnmCell *cell, GnmExprTop const *texpr)
3045 gnm_cell_set_expr (cell, texpr);
3047 /* clear spans from _other_ cells */
3048 sheet_cell_calc_span (cell, GNM_SPANCALC_SIMPLE);
3050 cell_queue_recalc (cell);
3051 sheet_flag_status_update_cell (cell);
3055 * sheet_cell_set_value: (skip)
3056 * @cell: #GnmCell
3057 * @v: (transfer full): #GnmValue
3059 * Stores, without copying, the supplied value. It marks the
3060 * sheet as dirty.
3062 * The value is rendered and spans are calculated. It queues a redraw
3063 * and checks to see if the edit region or selection content changed.
3065 * NOTE : This DOES check for array partitioning.
3067 void
3068 sheet_cell_set_value (GnmCell *cell, GnmValue *v)
3070 /* TODO : if the value is unchanged do not assign it */
3071 gnm_cell_set_value (cell, v);
3072 sheet_cell_calc_span (cell, GNM_SPANCALC_RESIZE | GNM_SPANCALC_RENDER);
3073 cell_queue_recalc (cell);
3074 sheet_flag_status_update_cell (cell);
3078 * sheet_cell_set_value_gi: (rename-to sheet_cell_set_value)
3079 * @sheet: #Sheet
3080 * @col: column number
3081 * @row: row number
3082 * @v: #GnmValue
3084 * Set the the value of the cell at (@col,@row) to @v.
3086 * The value is rendered and spans are calculated. It queues a redraw
3087 * and checks to see if the edit region or selection content changed.
3089 void
3090 sheet_cell_set_value_gi (Sheet *sheet, int col, int row, GnmValue *v)
3092 // This version exists because not all versions of pygobject
3093 // understand transfer-full parameters
3094 sheet_cell_set_value (sheet_cell_fetch (sheet, col, row),
3095 value_dup (v));
3098 /****************************************************************************/
3101 * This routine is used to queue the redraw regions for the
3102 * cell region specified.
3104 * It is usually called before a change happens to a region,
3105 * and after the change has been done to queue the regions
3106 * for the old contents and the new contents.
3108 * It intelligently handles spans and merged ranges
3110 void
3111 sheet_range_bounding_box (Sheet const *sheet, GnmRange *bound)
3113 GSList *ptr;
3114 int row;
3115 GnmRange r = *bound;
3117 g_return_if_fail (range_is_sane (bound));
3119 /* Check the first and last columns for spans and extend the region to
3120 * include the maximum extent.
3122 for (row = r.start.row; row <= r.end.row; row++){
3123 ColRowInfo const *ri = sheet_row_get (sheet, row);
3125 if (ri != NULL) {
3126 CellSpanInfo const * span0;
3128 if (ri->needs_respan)
3129 row_calc_spans ((ColRowInfo *)ri, row, sheet);
3131 span0 = row_span_get (ri, r.start.col);
3133 if (span0 != NULL) {
3134 if (bound->start.col > span0->left)
3135 bound->start.col = span0->left;
3136 if (bound->end.col < span0->right)
3137 bound->end.col = span0->right;
3139 if (r.start.col != r.end.col) {
3140 CellSpanInfo const * span1 =
3141 row_span_get (ri, r.end.col);
3143 if (span1 != NULL) {
3144 if (bound->start.col > span1->left)
3145 bound->start.col = span1->left;
3146 if (bound->end.col < span1->right)
3147 bound->end.col = span1->right;
3150 /* skip segments with no cells */
3151 } else if (row == COLROW_SEGMENT_START (row)) {
3152 ColRowSegment const * const segment =
3153 COLROW_GET_SEGMENT (&(sheet->rows), row);
3154 if (segment == NULL)
3155 row = COLROW_SEGMENT_END (row);
3159 /* TODO : this may get expensive if there are alot of merged ranges */
3160 /* no need to iterate, one pass is enough */
3161 for (ptr = sheet->list_merged ; ptr != NULL ; ptr = ptr->next) {
3162 GnmRange const * const test = ptr->data;
3163 if (r.start.row <= test->end.row || r.end.row >= test->start.row) {
3164 if (bound->start.col > test->start.col)
3165 bound->start.col = test->start.col;
3166 if (bound->end.col < test->end.col)
3167 bound->end.col = test->end.col;
3168 if (bound->start.row > test->start.row)
3169 bound->start.row = test->start.row;
3170 if (bound->end.row < test->end.row)
3171 bound->end.row = test->end.row;
3176 void
3177 sheet_redraw_region (Sheet const *sheet,
3178 int start_col, int start_row,
3179 int end_col, int end_row)
3181 GnmRange bound;
3183 g_return_if_fail (IS_SHEET (sheet));
3186 * Getting the bounding box causes row respans to be done if
3187 * needed. That can be expensive, so just redraw the whole
3188 * sheet if the row count is too big.
3190 if (end_row - start_row > 500) {
3191 sheet_redraw_all (sheet, FALSE);
3192 return;
3195 /* We potentially do a lot of recalcs as part of this, so make sure
3196 stuff that caches sub-computations see the whole thing instead
3197 of clearing between cells. */
3198 gnm_app_recalc_start ();
3200 sheet_range_bounding_box (sheet,
3201 range_init (&bound, start_col, start_row, end_col, end_row));
3202 SHEET_FOREACH_CONTROL (sheet, view, control,
3203 sc_redraw_range (control, &bound););
3205 gnm_app_recalc_finish ();
3208 void
3209 sheet_redraw_range (Sheet const *sheet, GnmRange const *range)
3211 g_return_if_fail (IS_SHEET (sheet));
3212 g_return_if_fail (range != NULL);
3214 sheet_redraw_region (sheet,
3215 range->start.col, range->start.row,
3216 range->end.col, range->end.row);
3219 /****************************************************************************/
3221 gboolean
3222 sheet_col_is_hidden (Sheet const *sheet, int col)
3224 ColRowInfo const * const res = sheet_col_get (sheet, col);
3225 return (res != NULL && !res->visible);
3228 gboolean
3229 sheet_row_is_hidden (Sheet const *sheet, int row)
3231 ColRowInfo const * const res = sheet_row_get (sheet, row);
3232 return (res != NULL && !res->visible);
3237 * sheet_find_boundary_horizontal:
3238 * @sheet: The Sheet
3239 * @col: The column from which to begin searching.
3240 * @move_row: The row in which to search for the edge of the range.
3241 * @base_row: The height of the area being moved.
3242 * @count: units to extend the selection vertically
3243 * @jump_to_boundaries: Jump to range boundaries.
3245 * Calculate the column index for the column which is @n units
3246 * from @start_col doing bounds checking. If @jump_to_boundaries is
3247 * TRUE @n must be 1 and the jump is to the edge of the logical range.
3249 * NOTE : This routine implements the logic necasary for ctrl-arrow style
3250 * movement. That is more compilcated than simply finding the last in a list
3251 * of cells with content. If you are at the end of a range it will find the
3252 * start of the next. Make sure that is the sort of behavior you want before
3253 * calling this.
3254 * Returns: the column inex.
3257 sheet_find_boundary_horizontal (Sheet *sheet, int start_col, int move_row,
3258 int base_row, int count,
3259 gboolean jump_to_boundaries)
3261 gboolean find_nonblank = sheet_is_cell_empty (sheet, start_col, move_row);
3262 gboolean keep_looking = FALSE;
3263 int new_col, prev_col, lagged_start_col, max_col = gnm_sheet_get_last_col (sheet);
3264 int iterations = 0;
3265 GnmRange check_merge;
3266 GnmRange const * const bound = &sheet->priv->unhidden_region;
3268 /* Jumping to bounds requires steping cell by cell */
3269 g_return_val_if_fail (count == 1 || count == -1 || !jump_to_boundaries, start_col);
3270 g_return_val_if_fail (IS_SHEET (sheet), start_col);
3272 if (move_row < base_row) {
3273 check_merge.start.row = move_row;
3274 check_merge.end.row = base_row;
3275 } else {
3276 check_merge.end.row = move_row;
3277 check_merge.start.row = base_row;
3280 do {
3281 GSList *merged, *ptr;
3283 lagged_start_col = check_merge.start.col = check_merge.end.col = start_col;
3284 merged = gnm_sheet_merge_get_overlap (sheet, &check_merge);
3285 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3286 GnmRange const * const r = ptr->data;
3287 if (count > 0) {
3288 if (start_col < r->end.col)
3289 start_col = r->end.col;
3290 } else {
3291 if (start_col > r->start.col)
3292 start_col = r->start.col;
3295 g_slist_free (merged);
3296 } while (start_col != lagged_start_col);
3297 new_col = prev_col = start_col;
3299 do {
3300 new_col += count;
3301 ++iterations;
3303 if (new_col < bound->start.col)
3304 return MIN (bound->start.col, max_col);
3305 if (new_col > bound->end.col)
3306 return MIN (bound->end.col, max_col);
3308 keep_looking = sheet_col_is_hidden (sheet, new_col);
3309 if (jump_to_boundaries) {
3310 if (new_col > sheet->cols.max_used) {
3311 if (count > 0)
3312 return (find_nonblank || iterations == 1)?
3313 MIN (bound->end.col, max_col):
3314 MIN (prev_col, max_col);
3315 new_col = sheet->cols.max_used;
3318 keep_looking |= (sheet_is_cell_empty (sheet, new_col, move_row) == find_nonblank);
3319 if (keep_looking)
3320 prev_col = new_col;
3321 else if (!find_nonblank) {
3323 * Handle special case where we are on the last
3324 * non-null cell
3326 if (iterations == 1)
3327 keep_looking = find_nonblank = TRUE;
3328 else
3329 new_col = prev_col;
3332 } while (keep_looking);
3334 return MIN (new_col, max_col);
3338 * sheet_find_boundary_vertical:
3339 * @sheet: The Sheet *
3340 * @move_col: The col in which to search for the edge of the range.
3341 * @row: The row from which to begin searching.
3342 * @base_col: The width of the area being moved.
3343 * @count: units to extend the selection vertically
3344 * @jump_to_boundaries: Jump to range boundaries.
3346 * Calculate the row index for the row which is @n units
3347 * from @start_row doing bounds checking. If @jump_to_boundaries is
3348 * TRUE @n must be 1 and the jump is to the edge of the logical range.
3350 * NOTE : This routine implements the logic necasary for ctrl-arrow style
3351 * movement. That is more compilcated than simply finding the last in a list
3352 * of cells with content. If you are at the end of a range it will find the
3353 * start of the next. Make sure that is the sort of behavior you want before
3354 * calling this.
3355 * Returns: the row index.
3358 sheet_find_boundary_vertical (Sheet *sheet, int move_col, int start_row,
3359 int base_col, int count,
3360 gboolean jump_to_boundaries)
3362 gboolean find_nonblank = sheet_is_cell_empty (sheet, move_col, start_row);
3363 gboolean keep_looking = FALSE;
3364 int new_row, prev_row, lagged_start_row, max_row = gnm_sheet_get_last_row (sheet);
3365 int iterations = 0;
3366 GnmRange check_merge;
3367 GnmRange const * const bound = &sheet->priv->unhidden_region;
3369 /* Jumping to bounds requires steping cell by cell */
3370 g_return_val_if_fail (count == 1 || count == -1 || !jump_to_boundaries, start_row);
3371 g_return_val_if_fail (IS_SHEET (sheet), start_row);
3373 if (move_col < base_col) {
3374 check_merge.start.col = move_col;
3375 check_merge.end.col = base_col;
3376 } else {
3377 check_merge.end.col = move_col;
3378 check_merge.start.col = base_col;
3381 do {
3382 GSList *merged, *ptr;
3384 lagged_start_row = check_merge.start.row = check_merge.end.row = start_row;
3385 merged = gnm_sheet_merge_get_overlap (sheet, &check_merge);
3386 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3387 GnmRange const * const r = ptr->data;
3388 if (count > 0) {
3389 if (start_row < r->end.row)
3390 start_row = r->end.row;
3391 } else {
3392 if (start_row > r->start.row)
3393 start_row = r->start.row;
3396 g_slist_free (merged);
3397 } while (start_row != lagged_start_row);
3398 new_row = prev_row = start_row;
3400 do {
3401 new_row += count;
3402 ++iterations;
3404 if (new_row < bound->start.row)
3405 return MIN (bound->start.row, max_row);
3406 if (new_row > bound->end.row)
3407 return MIN (bound->end.row, max_row);
3409 keep_looking = sheet_row_is_hidden (sheet, new_row);
3410 if (jump_to_boundaries) {
3411 if (new_row > sheet->rows.max_used) {
3412 if (count > 0)
3413 return (find_nonblank || iterations == 1)?
3414 MIN (bound->end.row, max_row):
3415 MIN (prev_row, max_row);
3416 new_row = sheet->rows.max_used;
3419 keep_looking |= (sheet_is_cell_empty (sheet, move_col, new_row) == find_nonblank);
3420 if (keep_looking)
3421 prev_row = new_row;
3422 else if (!find_nonblank) {
3424 * Handle special case where we are on the last
3425 * non-null cell
3427 if (iterations == 1)
3428 keep_looking = find_nonblank = TRUE;
3429 else
3430 new_row = prev_row;
3433 } while (keep_looking);
3435 return MIN (new_row, max_row);
3438 typedef enum {
3439 CHECK_AND_LOAD_START = 1,
3440 CHECK_END = 2,
3441 LOAD_END = 4
3442 } ArrayCheckFlags;
3444 typedef struct {
3445 Sheet const *sheet;
3446 int flags;
3447 int start, end;
3448 GnmRange const *ignore;
3450 GnmRange error;
3451 } ArrayCheckData;
3453 static gboolean
3454 cb_check_array_horizontal (GnmColRowIter const *iter, ArrayCheckData *data)
3456 gboolean is_array = FALSE;
3458 if (data->flags & CHECK_AND_LOAD_START && /* Top */
3459 (is_array = gnm_cell_array_bound (
3460 sheet_cell_get (data->sheet, iter->pos, data->start),
3461 &data->error)) &&
3462 data->error.start.row < data->start &&
3463 (data->ignore == NULL ||
3464 !range_contained (&data->error, data->ignore)))
3465 return TRUE;
3467 if (data->flags & LOAD_END)
3468 is_array = gnm_cell_array_bound (
3469 sheet_cell_get (data->sheet, iter->pos, data->end),
3470 &data->error);
3472 return (data->flags & CHECK_END &&
3473 is_array &&
3474 data->error.end.row > data->end && /* Bottom */
3475 (data->ignore == NULL ||
3476 !range_contained (&data->error, data->ignore)));
3479 static gboolean
3480 cb_check_array_vertical (GnmColRowIter const *iter, ArrayCheckData *data)
3482 gboolean is_array = FALSE;
3484 if (data->flags & CHECK_AND_LOAD_START && /* Left */
3485 (is_array = gnm_cell_array_bound (
3486 sheet_cell_get (data->sheet, data->start, iter->pos),
3487 &data->error)) &&
3488 data->error.start.col < data->start &&
3489 (data->ignore == NULL ||
3490 !range_contained (&data->error, data->ignore)))
3491 return TRUE;
3493 if (data->flags & LOAD_END)
3494 is_array = gnm_cell_array_bound (
3495 sheet_cell_get (data->sheet, data->end, iter->pos),
3496 &data->error);
3498 return (data->flags & CHECK_END &&
3499 is_array &&
3500 data->error.end.col > data->end && /* Right */
3501 (data->ignore == NULL ||
3502 !range_contained (&data->error, data->ignore)));
3506 * sheet_range_splits_array:
3507 * @sheet: The sheet.
3508 * @r: The range to check
3509 * @ignore: (nullable): a range in which it is ok to have an array.
3510 * @cc: (nullable): place to report an error.
3511 * @cmd: (nullable): cmd name used with @cc.
3513 * Check the outer edges of range @sheet!@r to ensure that if an array is
3514 * within it then the entire array is within the range. @ignore is useful when
3515 * src & dest ranges may overlap.
3517 * Returns: TRUE if an array would be split.
3519 gboolean
3520 sheet_range_splits_array (Sheet const *sheet,
3521 GnmRange const *r, GnmRange const *ignore,
3522 GOCmdContext *cc, char const *cmd)
3524 ArrayCheckData closure;
3526 g_return_val_if_fail (r->start.col <= r->end.col, FALSE);
3527 g_return_val_if_fail (r->start.row <= r->end.row, FALSE);
3529 closure.sheet = sheet;
3530 closure.ignore = ignore;
3532 closure.start = r->start.row;
3533 closure.end = r->end.row;
3534 if (closure.start <= 0) {
3535 closure.flags = (closure.end < sheet->rows.max_used)
3536 ? CHECK_END | LOAD_END
3537 : 0;
3538 } else if (closure.end < sheet->rows.max_used)
3539 closure.flags = (closure.start == closure.end)
3540 ? CHECK_AND_LOAD_START | CHECK_END
3541 : CHECK_AND_LOAD_START | CHECK_END | LOAD_END;
3542 else
3543 closure.flags = CHECK_AND_LOAD_START;
3545 if (closure.flags &&
3546 colrow_foreach (&sheet->cols, r->start.col, r->end.col,
3547 (ColRowHandler) cb_check_array_horizontal, &closure)) {
3548 if (cc)
3549 gnm_cmd_context_error_splits_array (cc,
3550 cmd, &closure.error);
3551 return TRUE;
3554 closure.start = r->start.col;
3555 closure.end = r->end.col;
3556 if (closure.start <= 0) {
3557 closure.flags = (closure.end < sheet->cols.max_used)
3558 ? CHECK_END | LOAD_END
3559 : 0;
3560 } else if (closure.end < sheet->cols.max_used)
3561 closure.flags = (closure.start == closure.end)
3562 ? CHECK_AND_LOAD_START | CHECK_END
3563 : CHECK_AND_LOAD_START | CHECK_END | LOAD_END;
3564 else
3565 closure.flags = CHECK_AND_LOAD_START;
3567 if (closure.flags &&
3568 colrow_foreach (&sheet->rows, r->start.row, r->end.row,
3569 (ColRowHandler) cb_check_array_vertical, &closure)) {
3570 if (cc)
3571 gnm_cmd_context_error_splits_array (cc,
3572 cmd, &closure.error);
3573 return TRUE;
3575 return FALSE;
3579 * sheet_range_splits_region:
3580 * @sheet: the sheet.
3581 * @r: The range whose boundaries are checked
3582 * @ignore: An optional range in which it is ok to have arrays and merges
3583 * @cc: The context that issued the command
3584 * @cmd: The translated command name.
3586 * A utility to see whether moving the range @r will split any arrays
3587 * or merged regions.
3588 * Returns: whether any arrays or merged regions will be split.
3590 gboolean
3591 sheet_range_splits_region (Sheet const *sheet,
3592 GnmRange const *r, GnmRange const *ignore,
3593 GOCmdContext *cc, char const *cmd_name)
3595 GSList *merged;
3597 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
3599 /* Check for array subdivision */
3600 if (sheet_range_splits_array (sheet, r, ignore, cc, cmd_name))
3601 return TRUE;
3603 merged = gnm_sheet_merge_get_overlap (sheet, r);
3604 if (merged) {
3605 GSList *ptr;
3607 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3608 GnmRange const *m = ptr->data;
3609 if (ignore != NULL && range_contained (m, ignore))
3610 continue;
3611 if (!range_contained (m, r))
3612 break;
3614 g_slist_free (merged);
3616 if (cc != NULL && ptr != NULL) {
3617 go_cmd_context_error_invalid (cc, cmd_name,
3618 _("Target region contains merged cells"));
3619 return TRUE;
3622 return FALSE;
3626 * sheet_ranges_split_region:
3627 * @sheet: the sheet.
3628 * @ranges: (element-type GnmRange): A list of ranges to check.
3629 * @cc: The context that issued the command
3630 * @cmd: The translated command name.
3632 * A utility to see whether moving any of the ranges @ranges will split any
3633 * arrays or merged regions.
3634 * Returns: whether any arrays or merged regions will be splitted.
3636 gboolean
3637 sheet_ranges_split_region (Sheet const * sheet, GSList const *ranges,
3638 GOCmdContext *cc, char const *cmd)
3640 GSList const *l;
3642 /* Check for array subdivision */
3643 for (l = ranges; l != NULL; l = l->next) {
3644 GnmRange const *r = l->data;
3645 if (sheet_range_splits_region (sheet, r, NULL, cc, cmd))
3646 return TRUE;
3648 return FALSE;
3651 static GnmValue *
3652 cb_cell_is_array (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
3654 return gnm_cell_is_array (iter->cell) ? VALUE_TERMINATE : NULL;
3658 * sheet_range_contains_merges_or_arrays:
3659 * @sheet: The sheet
3660 * @r: the range to check.
3661 * @cc: an optional place to report errors.
3662 * @cmd:
3663 * @merges: if %TRUE, check for merges.
3664 * @arrays: if %TRUE, check for arrays.
3666 * Check to see if the target region @sheet!@r contains any merged regions or
3667 * arrays. Report an error to the @cc if it is supplied.
3668 * Returns: %TRUE if the target region @sheet!@r contains any merged regions or
3669 * arrays.
3671 gboolean
3672 sheet_range_contains_merges_or_arrays (Sheet const *sheet, GnmRange const *r,
3673 GOCmdContext *cc, char const *cmd,
3674 gboolean merges, gboolean arrays)
3676 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
3678 if (merges) {
3679 GSList *merged = gnm_sheet_merge_get_overlap (sheet, r);
3680 if (merged != NULL) {
3681 if (cc != NULL)
3682 go_cmd_context_error_invalid
3683 (cc, cmd,
3684 _("cannot operate on merged cells"));
3685 g_slist_free (merged);
3686 return TRUE;
3690 if (arrays) {
3691 if (sheet_foreach_cell_in_range (
3692 (Sheet *)sheet, CELL_ITER_IGNORE_NONEXISTENT,
3693 r->start.col, r->start.row, r->end.col, r->end.row,
3694 cb_cell_is_array, NULL)) {
3695 if (cc != NULL)
3696 go_cmd_context_error_invalid
3697 (cc, cmd,
3698 _("cannot operate on array formul\303\246"));
3699 return TRUE;
3703 return FALSE;
3706 /***************************************************************************/
3709 * sheet_colrow_get_default:
3710 * @sheet:
3711 * @is_cols:
3713 * Returns: (transfer none): the default #ColRowInfo.
3715 ColRowInfo const *
3716 sheet_colrow_get_default (Sheet const *sheet, gboolean is_cols)
3718 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3720 return is_cols ? &sheet->cols.default_style : &sheet->rows.default_style;
3723 static void
3724 sheet_colrow_optimize1 (int max, int max_used, ColRowCollection *collection)
3726 int i;
3727 int first_unused = max_used + 1;
3729 for (i = COLROW_SEGMENT_START (first_unused);
3730 i < max;
3731 i += COLROW_SEGMENT_SIZE) {
3732 ColRowSegment *segment = COLROW_GET_SEGMENT (collection, i);
3733 int j;
3734 gboolean any = FALSE;
3736 if (!segment)
3737 continue;
3738 for (j = 0; j < COLROW_SEGMENT_SIZE; j++) {
3739 ColRowInfo *info = segment->info[j];
3740 if (!info)
3741 continue;
3742 if (i + j >= first_unused &&
3743 colrow_equal (&collection->default_style, info)) {
3744 colrow_free (info);
3745 segment->info[j] = NULL;
3746 } else {
3747 any = TRUE;
3748 if (i + j >= first_unused)
3749 max_used = i + j;
3753 if (!any) {
3754 g_free (segment);
3755 COLROW_GET_SEGMENT (collection, i) = NULL;
3759 collection->max_used = max_used;
3762 void
3763 sheet_colrow_optimize (Sheet *sheet)
3765 GnmRange extent;
3767 g_return_if_fail (IS_SHEET (sheet));
3769 extent = sheet_get_cells_extent (sheet);
3771 sheet_colrow_optimize1 (gnm_sheet_get_max_cols (sheet),
3772 extent.end.col,
3773 &sheet->cols);
3774 sheet_colrow_optimize1 (gnm_sheet_get_max_rows (sheet),
3775 extent.end.row,
3776 &sheet->rows);
3780 * sheet_col_get:
3781 * @col: column number
3783 * Returns: (transfer none) (nullable): A #ColRowInfo for the column, or %NULL
3784 * if none has been allocated yet.
3786 ColRowInfo *
3787 sheet_col_get (Sheet const *sheet, int col)
3789 ColRowSegment *segment;
3791 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3792 g_return_val_if_fail (col < gnm_sheet_get_max_cols (sheet), NULL);
3793 g_return_val_if_fail (col >= 0, NULL);
3795 segment = COLROW_GET_SEGMENT (&(sheet->cols), col);
3796 if (segment != NULL)
3797 return segment->info[COLROW_SUB_INDEX (col)];
3798 return NULL;
3802 * sheet_row_get:
3803 * @row: row number
3805 * Returns: (transfer none) (nullable): A #ColRowInfo for the row, or %NULL
3806 * if none has been allocated yet.
3808 ColRowInfo *
3809 sheet_row_get (Sheet const *sheet, int row)
3811 ColRowSegment *segment;
3813 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3814 g_return_val_if_fail (row < gnm_sheet_get_max_rows (sheet), NULL);
3815 g_return_val_if_fail (row >= 0, NULL);
3817 segment = COLROW_GET_SEGMENT (&(sheet->rows), row);
3818 if (segment != NULL)
3819 return segment->info[COLROW_SUB_INDEX (row)];
3820 return NULL;
3823 ColRowInfo *
3824 sheet_colrow_get (Sheet const *sheet, int colrow, gboolean is_cols)
3826 if (is_cols)
3827 return sheet_col_get (sheet, colrow);
3828 return sheet_row_get (sheet, colrow);
3832 * sheet_col_fetch:
3834 * Returns an allocated column: either an existing one, or a fresh copy
3836 ColRowInfo *
3837 sheet_col_fetch (Sheet *sheet, int pos)
3839 ColRowInfo *cri = sheet_col_get (sheet, pos);
3840 if (NULL == cri && NULL != (cri = sheet_col_new (sheet)))
3841 sheet_colrow_add (sheet, cri, TRUE, pos);
3842 return cri;
3846 * sheet_row_fetch:
3848 * Returns an allocated row: either an existing one, or a fresh copy
3850 ColRowInfo *
3851 sheet_row_fetch (Sheet *sheet, int pos)
3853 ColRowInfo *cri = sheet_row_get (sheet, pos);
3854 if (NULL == cri && NULL != (cri = sheet_row_new (sheet)))
3855 sheet_colrow_add (sheet, cri, FALSE, pos);
3856 return cri;
3859 ColRowInfo *
3860 sheet_colrow_fetch (Sheet *sheet, int colrow, gboolean is_cols)
3862 if (is_cols)
3863 return sheet_col_fetch (sheet, colrow);
3864 return sheet_row_fetch (sheet, colrow);
3867 ColRowInfo const *
3868 sheet_col_get_info (Sheet const *sheet, int col)
3870 ColRowInfo *ci = sheet_col_get (sheet, col);
3872 if (ci != NULL)
3873 return ci;
3874 return &sheet->cols.default_style;
3877 ColRowInfo const *
3878 sheet_row_get_info (Sheet const *sheet, int row)
3880 ColRowInfo *ri = sheet_row_get (sheet, row);
3882 if (ri != NULL)
3883 return ri;
3884 return &sheet->rows.default_style;
3887 ColRowInfo const *
3888 sheet_colrow_get_info (Sheet const *sheet, int colrow, gboolean is_cols)
3890 return is_cols
3891 ? sheet_col_get_info (sheet, colrow)
3892 : sheet_row_get_info (sheet, colrow);
3895 /*****************************************************************************/
3897 static gint
3898 cell_ordering (gconstpointer a_, gconstpointer b_)
3900 GnmCell const *a = *(GnmCell **)a_;
3901 GnmCell const *b = *(GnmCell **)b_;
3903 if (a->pos.row != b->pos.row)
3904 return a->pos.row - b->pos.row;
3906 return a->pos.col - b->pos.col;
3910 * sheet_cells:
3911 * @sheet: a #Sheet
3912 * @r: (nullable): a #GnmRange
3914 * Retrieves an array of all cells inside @r.
3915 * Returns: (element-type GnmCell) (transfer container): the cells array.
3917 GPtrArray *
3918 sheet_cells (Sheet *sheet, const GnmRange *r)
3920 GPtrArray *res = g_ptr_array_new ();
3921 GHashTableIter hiter;
3922 gpointer value;
3924 g_hash_table_iter_init (&hiter, sheet->cell_hash);
3925 while (g_hash_table_iter_next (&hiter, NULL, &value)) {
3926 GnmCell *cell = value;
3927 if (!r || range_contains (r, cell->pos.col, cell->pos.row))
3928 g_ptr_array_add (res, cell);
3930 g_ptr_array_sort (res, cell_ordering);
3932 return res;
3937 #define SWAP_INT(a,b) do { int t; t = a; a = b; b = t; } while (0)
3940 * sheet_foreach_cell_in_range:
3941 * @sheet: #Sheet
3942 * @flags:
3943 * @start_col:
3944 * @start_row:
3945 * @end_col:
3946 * @end_row:
3947 * @callback: (scope call): #CellFiletrFunc
3948 * @closure: user data.
3950 * For each existing cell in the range specified, invoke the
3951 * callback routine. If the only_existing flag is passed, then
3952 * callbacks are only invoked for existing cells.
3954 * Note: this function does not honour the CELL_ITER_IGNORE_SUBTOTAL flag.
3956 * Returns: (transfer none): the value returned by the callback, which can be :
3957 * non-NULL on error, or VALUE_TERMINATE if some invoked routine requested
3958 * to stop (by returning non-NULL).
3960 * NOTE: between 0.56 and 0.57, the traversal order changed. The order is now
3962 * 1 2 3
3963 * 4 5 6
3964 * 7 8 9
3966 * (This appears to be the order in which XL looks at the values of ranges.)
3967 * If your code depends on any particular ordering, please add a very visible
3968 * comment near the call.
3970 GnmValue *
3971 sheet_foreach_cell_in_range (Sheet *sheet, CellIterFlags flags,
3972 int start_col, int start_row,
3973 int end_col, int end_row,
3974 CellIterFunc callback, void *closure)
3976 GnmValue *cont;
3977 GnmCellIter iter;
3978 gboolean const visiblity_matters = (flags & CELL_ITER_IGNORE_HIDDEN) != 0;
3979 gboolean const ignore_filtered = (flags & CELL_ITER_IGNORE_FILTERED) != 0;
3980 gboolean const only_existing = (flags & CELL_ITER_IGNORE_NONEXISTENT) != 0;
3981 gboolean const ignore_empty = (flags & CELL_ITER_IGNORE_EMPTY) != 0;
3982 gboolean ignore;
3983 gboolean use_celllist;
3984 guint64 range_size;
3986 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3987 g_return_val_if_fail (callback != NULL, NULL);
3989 iter.pp.sheet = sheet;
3990 iter.pp.wb = sheet->workbook;
3992 if (start_col > end_col)
3993 SWAP_INT (start_col, end_col);
3994 if (end_col < 0 || start_col > gnm_sheet_get_last_col (sheet))
3995 return NULL;
3996 start_col = MAX (0, start_col);
3997 end_col = MIN (end_col, gnm_sheet_get_last_col (sheet));
3999 if (start_row > end_row)
4000 SWAP_INT (start_row, end_row);
4001 if (end_row < 0 || start_row > gnm_sheet_get_last_row (sheet))
4002 return NULL;
4003 start_row = MAX (0, start_row);
4004 end_row = MIN (end_row, gnm_sheet_get_last_row (sheet));
4006 range_size = (guint64)(end_row - start_row + 1) * (end_col - start_col + 1);
4007 use_celllist =
4008 only_existing &&
4009 range_size > g_hash_table_size (sheet->cell_hash) + 1000;
4010 if (use_celllist) {
4011 GPtrArray *all_cells;
4012 int last_row = -1, last_col = -1;
4013 GnmValue *res = NULL;
4014 unsigned ui;
4015 GnmRange r;
4017 if (gnm_debug_flag ("sheet-foreach"))
4018 g_printerr ("Using celllist for area of size %d\n",
4019 (int)range_size);
4021 range_init (&r, start_col, start_row, end_col, end_row);
4022 all_cells = sheet_cells (sheet, &r);
4024 for (ui = 0; ui < all_cells->len; ui++) {
4025 GnmCell *cell = g_ptr_array_index (all_cells, ui);
4027 iter.cell = cell;
4028 iter.pp.eval.row = cell->pos.row;
4029 iter.pp.eval.col = cell->pos.col;
4031 if (iter.pp.eval.row != last_row) {
4032 last_row = iter.pp.eval.row;
4033 iter.ri = sheet_row_get (iter.pp.sheet, last_row);
4035 if (visiblity_matters && !iter.ri->visible)
4036 continue;
4037 if (ignore_filtered && iter.ri->in_filter && !iter.ri->visible)
4038 continue;
4040 if (iter.pp.eval.col != last_col) {
4041 last_col = iter.pp.eval.col;
4042 iter.ci = sheet_col_get (iter.pp.sheet, last_col);
4044 if (visiblity_matters && !iter.ci->visible)
4045 continue;
4047 ignore = (ignore_empty &&
4048 VALUE_IS_EMPTY (cell->value) &&
4049 !gnm_cell_needs_recalc (cell));
4050 if (ignore)
4051 continue;
4053 res = (*callback) (&iter, closure);
4054 if (res != NULL)
4055 break;
4058 g_ptr_array_free (all_cells, TRUE);
4059 return res;
4062 for (iter.pp.eval.row = start_row;
4063 iter.pp.eval.row <= end_row;
4064 ++iter.pp.eval.row) {
4065 iter.ri = sheet_row_get (iter.pp.sheet, iter.pp.eval.row);
4067 /* no need to check visiblity, that would require a colinfo to exist */
4068 if (iter.ri == NULL) {
4069 if (only_existing) {
4070 /* skip segments with no cells */
4071 if (iter.pp.eval.row == COLROW_SEGMENT_START (iter.pp.eval.row)) {
4072 ColRowSegment const *segment =
4073 COLROW_GET_SEGMENT (&(sheet->rows), iter.pp.eval.row);
4074 if (segment == NULL)
4075 iter.pp.eval.row = COLROW_SEGMENT_END (iter.pp.eval.row);
4077 } else {
4078 iter.cell = NULL;
4079 for (iter.pp.eval.col = start_col; iter.pp.eval.col <= end_col; ++iter.pp.eval.col) {
4080 cont = (*callback) (&iter, closure);
4081 if (cont != NULL)
4082 return cont;
4086 continue;
4089 if (visiblity_matters && !iter.ri->visible)
4090 continue;
4091 if (ignore_filtered && iter.ri->in_filter && !iter.ri->visible)
4092 continue;
4094 for (iter.pp.eval.col = start_col; iter.pp.eval.col <= end_col; ++iter.pp.eval.col) {
4095 iter.ci = sheet_col_get (sheet, iter.pp.eval.col);
4096 if (iter.ci != NULL) {
4097 if (visiblity_matters && !iter.ci->visible)
4098 continue;
4099 iter.cell = sheet_cell_get (sheet,
4100 iter.pp.eval.col, iter.pp.eval.row);
4101 } else
4102 iter.cell = NULL;
4104 ignore = (iter.cell == NULL)
4105 ? (only_existing || ignore_empty)
4106 : (ignore_empty && VALUE_IS_EMPTY (iter.cell->value) &&
4107 !gnm_cell_needs_recalc (iter.cell));
4109 if (ignore) {
4110 if (iter.pp.eval.col == COLROW_SEGMENT_START (iter.pp.eval.col)) {
4111 ColRowSegment const *segment =
4112 COLROW_GET_SEGMENT (&(sheet->cols), iter.pp.eval.col);
4113 if (segment == NULL)
4114 iter.pp.eval.col = COLROW_SEGMENT_END (iter.pp.eval.col);
4116 continue;
4119 cont = (*callback) (&iter, closure);
4120 if (cont != NULL)
4121 return cont;
4124 return NULL;
4128 * sheet_cell_foreach:
4129 * @sheet: #Sheet
4130 * @callback: (scope call):
4131 * @data:
4133 * Call @callback with an argument of @data for each cell in the sheet
4135 void
4136 sheet_cell_foreach (Sheet const *sheet, GHFunc callback, gpointer data)
4138 g_return_if_fail (IS_SHEET (sheet));
4140 g_hash_table_foreach (sheet->cell_hash, callback, data);
4144 * sheet_cells_count:
4145 * @sheet: #Sheet
4147 * Returns the number of cells with content in the current workbook.
4149 unsigned
4150 sheet_cells_count (Sheet const *sheet)
4152 return g_hash_table_size (sheet->cell_hash);
4155 static void
4156 cb_sheet_cells_collect (G_GNUC_UNUSED gpointer unused,
4157 GnmCell const *cell,
4158 GPtrArray *cells)
4160 GnmEvalPos *ep = eval_pos_init_cell (g_new (GnmEvalPos, 1), cell);
4161 g_ptr_array_add (cells, ep);
4165 * sheet_cell_positions:
4166 * @sheet: The sheet to find cells in.
4167 * @comments: If true, include cells with only comments also.
4169 * Collects a GPtrArray of GnmEvalPos pointers for all cells in a sheet.
4170 * No particular order should be assumed.
4171 * Returns: (element-type GnmEvalPos) (transfer full): the newly created array
4173 GPtrArray *
4174 sheet_cell_positions (Sheet *sheet, gboolean comments)
4176 GPtrArray *cells = g_ptr_array_new ();
4178 g_return_val_if_fail (IS_SHEET (sheet), cells);
4180 sheet_cell_foreach (sheet, (GHFunc)cb_sheet_cells_collect, cells);
4182 if (comments) {
4183 GnmRange r;
4184 GSList *scomments, *ptr;
4186 range_init_full_sheet (&r, sheet);
4187 scomments = sheet_objects_get (sheet, &r, GNM_CELL_COMMENT_TYPE);
4188 for (ptr = scomments; ptr; ptr = ptr->next) {
4189 GnmComment *c = ptr->data;
4190 GnmRange const *loc = sheet_object_get_range (GNM_SO (c));
4191 GnmCell *cell = sheet_cell_get (sheet, loc->start.col, loc->start.row);
4192 if (!cell) {
4193 /* If cell does not exist, we haven't seen it... */
4194 GnmEvalPos *ep = g_new (GnmEvalPos, 1);
4195 ep->sheet = sheet;
4196 ep->eval.col = loc->start.col;
4197 ep->eval.row = loc->start.row;
4198 g_ptr_array_add (cells, ep);
4201 g_slist_free (scomments);
4204 return cells;
4208 static GnmValue *
4209 cb_fail_if_exist (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
4211 return gnm_cell_is_empty (iter->cell) ? NULL : VALUE_TERMINATE;
4215 * sheet_is_region_empty:
4216 * @sheet: sheet to check
4217 * @r: region to check
4219 * Returns TRUE if the specified region of the @sheet does not
4220 * contain any cells
4222 gboolean
4223 sheet_is_region_empty (Sheet *sheet, GnmRange const *r)
4225 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
4227 return sheet_foreach_cell_in_range (
4228 sheet, CELL_ITER_IGNORE_BLANK,
4229 r->start.col, r->start.row, r->end.col, r->end.row,
4230 cb_fail_if_exist, NULL) == NULL;
4233 gboolean
4234 sheet_is_cell_empty (Sheet *sheet, int col, int row)
4236 GnmCell const *cell = sheet_cell_get (sheet, col, row);
4237 return gnm_cell_is_empty (cell);
4241 * sheet_cell_add_to_hash:
4242 * @sheet The sheet where the cell is inserted
4243 * @cell The cell, it should already have col/pos pointers
4244 * initialized pointing to the correct ColRowInfo
4246 * GnmCell::pos must be valid before this is called. The position is used as the
4247 * hash key.
4249 static void
4250 sheet_cell_add_to_hash (Sheet *sheet, GnmCell *cell)
4252 g_return_if_fail (cell->pos.col < gnm_sheet_get_max_cols (sheet));
4253 g_return_if_fail (cell->pos.row < gnm_sheet_get_max_rows (sheet));
4255 cell->base.flags |= GNM_CELL_IN_SHEET_LIST;
4256 /* NOTE :
4257 * fetching the col/row here serve 3 functions
4258 * 1) obsolete: we used to store the pointer in the cell
4259 * 2) Expanding col/row.max_used
4260 * 3) Creating an entry in the COLROW_SEGMENT. Lots and lots of
4261 * things use those to help limit iteration
4263 * For now just call col_fetch even though it is not necessary to
4264 * ensure that 2,3 still happen. Alot will need rewriting to avoid
4265 * these requirements.
4267 (void)sheet_col_fetch (sheet, cell->pos.col);
4268 (void)sheet_row_fetch (sheet, cell->pos.row);
4270 gnm_cell_unrender (cell);
4272 g_hash_table_insert (sheet->cell_hash, cell, cell);
4274 if (gnm_sheet_merge_is_corner (sheet, &cell->pos))
4275 cell->base.flags |= GNM_CELL_IS_MERGED;
4278 #undef USE_CELL_POOL
4280 #ifdef USE_CELL_POOL
4281 /* The pool from which all cells are allocated. */
4282 static GOMemChunk *cell_pool;
4283 #else
4284 static int cell_allocations = 0;
4285 #endif
4287 static GnmCell *
4288 cell_new (void)
4290 GnmCell *cell =
4291 #ifdef USE_CELL_POOL
4292 go_mem_chunk_alloc0 (cell_pool)
4293 #else
4294 (cell_allocations++, g_slice_new0 (GnmCell))
4295 #endif
4298 cell->base.flags = DEPENDENT_CELL;
4299 return cell;
4303 static void
4304 cell_free (GnmCell *cell)
4306 g_return_if_fail (cell != NULL);
4308 gnm_cell_cleanout (cell);
4309 #ifdef USE_CELL_POOL
4310 go_mem_chunk_free (cell_pool, cell);
4311 #else
4312 cell_allocations--, g_slice_free1 (sizeof (*cell), cell);
4313 #endif
4316 void
4317 gnm_sheet_cell_init (void)
4319 #ifdef USE_CELL_POOL
4320 cell_pool = go_mem_chunk_new ("cell pool",
4321 sizeof (GnmCell),
4322 128 * 1024 - 128);
4323 #endif
4326 #ifdef USE_CELL_POOL
4327 static void
4328 cb_cell_pool_leak (gpointer data, G_GNUC_UNUSED gpointer user)
4330 GnmCell const *cell = data;
4331 g_printerr ("Leaking cell %p at %s\n", (void *)cell, cell_name (cell));
4333 #endif
4335 void
4336 gnm_sheet_cell_shutdown (void)
4338 #ifdef USE_CELL_POOL
4339 go_mem_chunk_foreach_leak (cell_pool, cb_cell_pool_leak, NULL);
4340 go_mem_chunk_destroy (cell_pool, FALSE);
4341 cell_pool = NULL;
4342 #else
4343 if (cell_allocations)
4344 g_printerr ("Leaking %d cells.\n", cell_allocations);
4345 #endif
4348 /****************************************************************************/
4351 * sheet_cell_create:
4352 * @sheet: #Sheet
4353 * @col:
4354 * @row:
4356 * Creates a new cell and adds it to the sheet hash.
4358 GnmCell *
4359 sheet_cell_create (Sheet *sheet, int col, int row)
4361 GnmCell *cell;
4363 g_return_val_if_fail (IS_SHEET (sheet), NULL);
4364 g_return_val_if_fail (col >= 0, NULL);
4365 g_return_val_if_fail (col < gnm_sheet_get_max_cols (sheet), NULL);
4366 g_return_val_if_fail (row >= 0, NULL);
4367 g_return_val_if_fail (row < gnm_sheet_get_max_rows (sheet), NULL);
4369 cell = cell_new ();
4370 cell->base.sheet = sheet;
4371 cell->pos.col = col;
4372 cell->pos.row = row;
4373 cell->value = value_new_empty ();
4375 sheet_cell_add_to_hash (sheet, cell);
4376 return cell;
4380 * sheet_cell_remove_from_hash:
4381 * @sheet:
4382 * @cell:
4384 * Removes a cell from the sheet hash, clears any spans, and unlinks it from
4385 * the dependent collection.
4387 static void
4388 sheet_cell_remove_from_hash (Sheet *sheet, GnmCell *cell)
4390 cell_unregister_span (cell);
4391 if (gnm_cell_expr_is_linked (cell))
4392 dependent_unlink (GNM_CELL_TO_DEP (cell));
4393 g_hash_table_remove (sheet->cell_hash, cell);
4394 cell->base.flags &= ~(GNM_CELL_IN_SHEET_LIST|GNM_CELL_IS_MERGED);
4398 * sheet_cell_destroy:
4399 * @sheet:
4400 * @cell:
4401 * @queue_recalc:
4403 * Remove the cell from the web of dependencies of a
4404 * sheet. Do NOT redraw.
4406 static void
4407 sheet_cell_destroy (Sheet *sheet, GnmCell *cell, gboolean queue_recalc)
4409 if (gnm_cell_expr_is_linked (cell)) {
4410 /* if it needs recalc then its depends are already queued
4411 * check recalc status before we unlink
4413 queue_recalc &= !gnm_cell_needs_recalc (cell);
4414 dependent_unlink (GNM_CELL_TO_DEP (cell));
4417 if (queue_recalc)
4418 cell_foreach_dep (cell, (GnmDepFunc)dependent_queue_recalc, NULL);
4420 sheet_cell_remove_from_hash (sheet, cell);
4421 cell_free (cell);
4425 * sheet_cell_remove:
4426 * @sheet:
4427 * @cell:
4428 * @redraw:
4429 * @queue_recalc:
4431 * Remove the cell from the web of dependencies of a
4432 * sheet. Do NOT free the cell, optionally redraw it, optionally
4433 * queue it for recalc.
4435 void
4436 sheet_cell_remove (Sheet *sheet, GnmCell *cell,
4437 gboolean redraw, gboolean queue_recalc)
4439 g_return_if_fail (cell != NULL);
4440 g_return_if_fail (IS_SHEET (sheet));
4442 /* Queue a redraw on the region used by the cell being removed */
4443 if (redraw) {
4444 sheet_redraw_region (sheet,
4445 cell->pos.col, cell->pos.row,
4446 cell->pos.col, cell->pos.row);
4447 sheet_flag_status_update_cell (cell);
4450 sheet_cell_destroy (sheet, cell, queue_recalc);
4453 static GnmValue *
4454 cb_free_cell (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
4456 sheet_cell_destroy (iter->pp.sheet, iter->cell, FALSE);
4457 return NULL;
4461 * sheet_col_destroy:
4462 * @sheet:
4463 * @col:
4464 * @free_cells:
4466 * Destroys a ColRowInfo from the Sheet with all of its cells
4468 static void
4469 sheet_col_destroy (Sheet *sheet, int const col, gboolean free_cells)
4471 ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->cols), col);
4472 int const sub = COLROW_SUB_INDEX (col);
4473 ColRowInfo *ci = NULL;
4475 if (*segment == NULL)
4476 return;
4477 ci = (*segment)->info[sub];
4478 if (ci == NULL)
4479 return;
4481 if (sheet->cols.max_outline_level > 0 &&
4482 sheet->cols.max_outline_level == ci->outline_level)
4483 sheet->priv->recompute_max_col_group = TRUE;
4485 if (free_cells)
4486 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4487 col, 0, col, gnm_sheet_get_last_row (sheet),
4488 &cb_free_cell, NULL);
4490 (*segment)->info[sub] = NULL;
4491 colrow_free (ci);
4493 /* Use >= just in case things are screwed up */
4494 if (col >= sheet->cols.max_used) {
4495 int i = col;
4496 while (--i >= 0 && sheet_col_get (sheet, i) == NULL)
4498 sheet->cols.max_used = i;
4503 * Destroys a row ColRowInfo
4505 static void
4506 sheet_row_destroy (Sheet *sheet, int const row, gboolean free_cells)
4508 ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->rows), row);
4509 int const sub = COLROW_SUB_INDEX (row);
4510 ColRowInfo *ri = NULL;
4512 if (*segment == NULL)
4513 return;
4514 ri = (*segment)->info[sub];
4515 if (ri == NULL)
4516 return;
4518 if (sheet->rows.max_outline_level > 0 &&
4519 sheet->rows.max_outline_level == ri->outline_level)
4520 sheet->priv->recompute_max_row_group = TRUE;
4522 if (free_cells)
4523 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4524 0, row, gnm_sheet_get_last_col (sheet), row,
4525 &cb_free_cell, NULL);
4527 /* Rows have span lists, destroy them too */
4528 row_destroy_span (ri);
4530 (*segment)->info[sub] = NULL;
4531 colrow_free (ri);
4533 /* Use >= just in case things are screwed up */
4534 if (row >= sheet->rows.max_used) {
4535 int i = row;
4536 while (--i >= 0 && sheet_row_get (sheet, i) == NULL)
4538 sheet->rows.max_used = i;
4542 static void
4543 cb_remove_allcells (G_GNUC_UNUSED gpointer ignore0, GnmCell *cell, G_GNUC_UNUSED gpointer ignore1)
4545 cell->base.flags &= ~GNM_CELL_IN_SHEET_LIST;
4546 cell_free (cell);
4549 void
4550 sheet_destroy_contents (Sheet *sheet)
4552 GSList *filters;
4553 int i;
4555 /* By the time we reach here dependencies should have been shut down */
4556 g_return_if_fail (sheet->deps == NULL);
4558 /* A simple test to see if this has already been run. */
4559 if (sheet->hash_merged == NULL)
4560 return;
4563 GSList *tmp = sheet->slicers;
4564 sheet->slicers = NULL;
4565 g_slist_free_full (tmp, (GDestroyNotify)gnm_sheet_slicer_clear_sheet);
4568 /* These contain SheetObjects, remove them first */
4569 filters = g_slist_copy (sheet->filters);
4570 g_slist_foreach (filters, (GFunc)gnm_filter_remove, NULL);
4571 g_slist_foreach (filters, (GFunc)gnm_filter_unref, NULL);
4572 g_slist_free (filters);
4574 if (sheet->sheet_objects) {
4575 /* The list is changed as we remove */
4576 GSList *objs = g_slist_copy (sheet->sheet_objects);
4577 GSList *ptr;
4578 for (ptr = objs; ptr != NULL ; ptr = ptr->next) {
4579 SheetObject *so = GNM_SO (ptr->data);
4580 if (so != NULL)
4581 sheet_object_clear_sheet (so);
4583 g_slist_free (objs);
4584 if (sheet->sheet_objects != NULL)
4585 g_warning ("There is a problem with sheet objects");
4588 /* The memory is managed by Sheet::list_merged */
4589 g_hash_table_destroy (sheet->hash_merged);
4590 sheet->hash_merged = NULL;
4592 g_slist_free_full (sheet->list_merged, g_free);
4593 sheet->list_merged = NULL;
4595 /* Clear the row spans 1st */
4596 for (i = sheet->rows.max_used; i >= 0 ; --i)
4597 row_destroy_span (sheet_row_get (sheet, i));
4599 /* Remove all the cells */
4600 sheet_cell_foreach (sheet, (GHFunc) &cb_remove_allcells, NULL);
4601 g_hash_table_destroy (sheet->cell_hash);
4603 /* Delete in ascending order to avoid decrementing max_used each time */
4604 for (i = 0; i <= sheet->cols.max_used; ++i)
4605 sheet_col_destroy (sheet, i, FALSE);
4607 for (i = 0; i <= sheet->rows.max_used; ++i)
4608 sheet_row_destroy (sheet, i, FALSE);
4610 /* Free segments too */
4611 colrow_resize (&sheet->cols, 0);
4612 g_ptr_array_free (sheet->cols.info, TRUE);
4613 sheet->cols.info = NULL;
4615 colrow_resize (&sheet->rows, 0);
4616 g_ptr_array_free (sheet->rows.info, TRUE);
4617 sheet->rows.info = NULL;
4619 g_clear_object (&sheet->solver_parameters);
4623 * sheet_destroy:
4624 * @sheet: the sheet to destroy
4626 * Please note that you need to detach this sheet before
4627 * calling this routine or you will get a warning.
4629 static void
4630 sheet_destroy (Sheet *sheet)
4632 g_return_if_fail (IS_SHEET (sheet));
4634 if (sheet->sheet_views->len > 0)
4635 g_warning ("Unexpected left over views");
4637 if (sheet->print_info) {
4638 gnm_print_info_free (sheet->print_info);
4639 sheet->print_info = NULL;
4642 style_color_unref (sheet->tab_color);
4643 sheet->tab_color = NULL;
4644 style_color_unref (sheet->tab_text_color);
4645 sheet->tab_text_color = NULL;
4647 gnm_app_clipboard_invalidate_sheet (sheet);
4650 static void
4651 gnm_sheet_finalize (GObject *obj)
4653 Sheet *sheet = SHEET (obj);
4654 gboolean debug_FMR = gnm_debug_flag ("sheet-fmr");
4656 sheet_destroy (sheet);
4658 g_clear_object (&sheet->solver_parameters);
4660 g_list_free_full (sheet->scenarios, g_object_unref);
4661 sheet->scenarios = NULL;
4663 if (sheet->sort_setups != NULL)
4664 g_hash_table_unref (sheet->sort_setups);
4666 dependents_invalidate_sheet (sheet, TRUE);
4668 sheet_destroy_contents (sheet);
4670 if (sheet->slicers != NULL) {
4671 g_warning ("DataSlicer list should be NULL");
4673 if (sheet->filters != NULL) {
4674 g_warning ("Filter list should be NULL");
4676 if (sheet->sheet_objects != NULL) {
4677 g_warning ("Sheet object list should be NULL");
4679 if (sheet->list_merged != NULL) {
4680 g_warning ("Merged list should be NULL");
4682 if (sheet->hash_merged != NULL) {
4683 g_warning ("Merged hash should be NULL");
4686 sheet_style_shutdown (sheet);
4688 (void) g_idle_remove_by_data (sheet);
4690 if (debug_FMR) {
4691 g_printerr ("Sheet %p is %s\n", sheet, sheet->name_quoted);
4693 g_free (sheet->name_quoted);
4694 g_free (sheet->name_unquoted);
4695 g_free (sheet->name_unquoted_collate_key);
4696 g_free (sheet->name_case_insensitive);
4697 /* Poison */
4698 sheet->name_quoted = (char *)0xdeadbeef;
4699 sheet->name_unquoted = (char *)0xdeadbeef;
4700 g_free (sheet->priv);
4701 g_ptr_array_free (sheet->sheet_views, TRUE);
4703 gnm_rvc_free (sheet->rendered_values);
4705 if (debug_FMR) {
4706 /* Keep object around. */
4707 return;
4710 G_OBJECT_CLASS (parent_class)->finalize (obj);
4713 /*****************************************************************************/
4716 * cb_empty_cell: A callback for sheet_foreach_cell_in_range
4717 * removes/clear all of the cells in the specified region.
4718 * Does NOT queue a redraw.
4720 * WARNING : This does NOT regenerate spans that were interrupted by
4721 * this cell and can now continue.
4723 static GnmValue *
4724 cb_empty_cell (GnmCellIter const *iter, gpointer user)
4726 int clear_flags = GPOINTER_TO_INT (user);
4727 #if 0
4728 /* TODO : here and other places flag a need to update the
4729 * row/col maxima.
4731 if (row >= sheet->rows.max_used || col >= sheet->cols.max_used) { }
4732 #endif
4734 sheet_cell_remove (iter->pp.sheet, iter->cell, FALSE,
4735 (clear_flags & CLEAR_RECALC_DEPS) &&
4736 iter->pp.wb->recursive_dirty_enabled);
4738 return NULL;
4742 * sheet_clear_region:
4743 * @sheet:
4744 * @start_col:
4745 * @start_row:
4746 * @end_col:
4747 * @end_row:
4748 * @clear_flags: If this is TRUE then styles are erased.
4749 * @cc: (nullable):
4751 * Clears are region of cells
4753 * We assemble a list of cells to destroy, since we will be making changes
4754 * to the structure being manipulated by the sheet_foreach_cell_in_range routine
4756 void
4757 sheet_clear_region (Sheet *sheet,
4758 int start_col, int start_row,
4759 int end_col, int end_row,
4760 int clear_flags,
4761 GOCmdContext *cc)
4763 GnmRange r;
4765 g_return_if_fail (IS_SHEET (sheet));
4766 g_return_if_fail (start_col <= end_col);
4767 g_return_if_fail (start_row <= end_row);
4769 r.start.col = start_col;
4770 r.start.row = start_row;
4771 r.end.col = end_col;
4772 r.end.row = end_row;
4774 if (clear_flags & CLEAR_VALUES && !(clear_flags & CLEAR_NOCHECKARRAY) &&
4775 sheet_range_splits_array (sheet, &r, NULL, cc, _("Clear")))
4776 return;
4778 /* Queue a redraw for cells being modified */
4779 if (clear_flags & (CLEAR_VALUES|CLEAR_FORMATS))
4780 sheet_redraw_region (sheet,
4781 start_col, start_row,
4782 end_col, end_row);
4784 /* Clear the style in the region (new_default will ref the style for us). */
4785 if (clear_flags & CLEAR_FORMATS) {
4786 sheet_style_set_range (sheet, &r, sheet_style_default (sheet));
4787 sheet_range_calc_spans (sheet, &r, GNM_SPANCALC_RE_RENDER|GNM_SPANCALC_RESIZE);
4788 rows_height_update (sheet, &r, TRUE);
4791 if (clear_flags & CLEAR_OBJECTS)
4792 sheet_objects_clear (sheet, &r, G_TYPE_NONE, NULL);
4793 else if (clear_flags & CLEAR_COMMENTS)
4794 sheet_objects_clear (sheet, &r, GNM_CELL_COMMENT_TYPE, NULL);
4796 /* TODO : how to handle objects ? */
4797 if (clear_flags & CLEAR_VALUES) {
4798 /* Remove or empty the cells depending on
4799 * whether or not there are comments
4801 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4802 start_col, start_row, end_col, end_row,
4803 &cb_empty_cell, GINT_TO_POINTER (clear_flags));
4805 if (!(clear_flags & CLEAR_NORESPAN)) {
4806 sheet_queue_respan (sheet, start_row, end_row);
4807 sheet_flag_status_update_range (sheet, &r);
4811 if (clear_flags & CLEAR_MERGES) {
4812 GSList *merged, *ptr;
4813 merged = gnm_sheet_merge_get_overlap (sheet, &r);
4814 for (ptr = merged ; ptr != NULL ; ptr = ptr->next)
4815 gnm_sheet_merge_remove (sheet, ptr->data);
4816 g_slist_free (merged);
4819 if (clear_flags & CLEAR_RECALC_DEPS)
4820 sheet_region_queue_recalc (sheet, &r);
4822 /* Always redraw */
4823 sheet_redraw_all (sheet, FALSE);
4826 static void
4827 sheet_clear_region_cb (GnmSheetRange *sr, int *flags)
4829 sheet_clear_region (sr->sheet,
4830 sr->range.start.col, sr->range.start.row,
4831 sr->range.end.col, sr->range.end.row,
4832 *flags | CLEAR_NOCHECKARRAY, NULL);
4837 * sheet_clear_region_undo:
4838 * @sr: #GnmSheetRange
4839 * @clear_flags: flags.
4841 * Returns: (transfer full): the new #GOUndo.
4843 GOUndo *sheet_clear_region_undo (GnmSheetRange *sr, int clear_flags)
4845 int *flags = g_new(int, 1);
4846 *flags = clear_flags;
4847 return go_undo_binary_new
4848 (sr, (gpointer)flags,
4849 (GOUndoBinaryFunc) sheet_clear_region_cb,
4850 (GFreeFunc) gnm_sheet_range_free,
4851 (GFreeFunc) g_free);
4855 /*****************************************************************************/
4857 void
4858 sheet_mark_dirty (Sheet *sheet)
4860 g_return_if_fail (IS_SHEET (sheet));
4862 if (sheet->workbook)
4863 go_doc_set_dirty (GO_DOC (sheet->workbook), TRUE);
4866 /****************************************************************************/
4868 static void
4869 sheet_cells_deps_move (GnmExprRelocateInfo *rinfo)
4871 Sheet *sheet = rinfo->origin_sheet;
4872 GPtrArray *deps = sheet_cells (sheet, &rinfo->origin);
4873 unsigned ui;
4875 /* Phase 1: collect all cells and remove them from hash. */
4876 for (ui = 0; ui < deps->len; ui++) {
4877 GnmCell *cell = g_ptr_array_index (deps, ui);
4878 gboolean needs_recalc = gnm_cell_needs_recalc (cell);
4879 sheet_cell_remove_from_hash (sheet, cell);
4880 if (needs_recalc) /* Do we need this now? */
4881 cell->base.flags |= DEPENDENT_NEEDS_RECALC;
4884 /* Phase 2: add all non-cell deps with positions */
4885 SHEET_FOREACH_DEPENDENT
4886 (sheet, dep, {
4887 GnmCellPos const *pos;
4888 if (!dependent_is_cell (dep) &&
4889 dependent_has_pos (dep) &&
4890 (pos = dependent_pos (dep)) &&
4891 range_contains (&rinfo->origin, pos->col, pos->row)) {
4892 dependent_unlink (dep);
4893 g_ptr_array_add (deps, dep);
4897 /* Phase 3: move everything and add cells to hash. */
4898 for (ui = 0; ui < deps->len; ui++) {
4899 GnmDependent *dep = g_ptr_array_index (deps, ui);
4901 dependent_move (dep, rinfo->col_offset, rinfo->row_offset);
4903 if (dependent_is_cell (dep))
4904 sheet_cell_add_to_hash (sheet, GNM_DEP_TO_CELL (dep));
4906 if (dep->texpr)
4907 dependent_link (dep);
4910 g_ptr_array_free (deps, TRUE);
4913 /* Moves the headers to their new location */
4914 static void
4915 sheet_colrow_move (Sheet *sheet, gboolean is_cols,
4916 int const old_pos, int const new_pos)
4918 ColRowSegment *segment = COLROW_GET_SEGMENT (is_cols ? &sheet->cols : &sheet->rows, old_pos);
4919 ColRowInfo *info = segment
4920 ? segment->info[COLROW_SUB_INDEX (old_pos)]
4921 : NULL;
4923 g_return_if_fail (old_pos >= 0);
4924 g_return_if_fail (new_pos >= 0);
4926 if (info == NULL)
4927 return;
4929 /* Update the position */
4930 segment->info[COLROW_SUB_INDEX (old_pos)] = NULL;
4931 sheet_colrow_add (sheet, info, is_cols, new_pos);
4934 static void
4935 sheet_colrow_set_collapse (Sheet *sheet, gboolean is_cols, int pos)
4937 ColRowInfo *cri;
4938 ColRowInfo const *vs = NULL;
4940 if (pos < 0 || pos >= colrow_max (is_cols, sheet))
4941 return;
4943 /* grab the next or previous col/row */
4944 if ((is_cols ? sheet->outline_symbols_right : sheet->outline_symbols_below)) {
4945 if (pos > 0)
4946 vs = sheet_colrow_get (sheet, pos-1, is_cols);
4947 } else if ((pos+1) < colrow_max (is_cols, sheet))
4948 vs = sheet_colrow_get (sheet, pos+1, is_cols);
4950 /* handle the case where an empty col/row should be marked collapsed */
4951 cri = sheet_colrow_get (sheet, pos, is_cols);
4952 if (cri != NULL)
4953 cri->is_collapsed = (vs != NULL && !vs->visible &&
4954 vs->outline_level > cri->outline_level);
4955 else if (vs != NULL && !vs->visible && vs->outline_level > 0) {
4956 cri = sheet_colrow_fetch (sheet, pos, is_cols);
4957 cri->is_collapsed = TRUE;
4961 static void
4962 combine_undo (GOUndo **pundo, GOUndo *u)
4964 if (pundo)
4965 *pundo = go_undo_combine (*pundo, u);
4966 else
4967 g_object_unref (u);
4970 typedef gboolean (*ColRowInsDelFunc) (Sheet *sheet, int idx, int count,
4971 GOUndo **pundo, GOCmdContext *cc);
4973 typedef struct {
4974 ColRowInsDelFunc func;
4975 Sheet *sheet;
4976 gboolean is_cols;
4977 int pos;
4978 int count;
4979 ColRowStateList *states;
4980 int state_start;
4981 } ColRowInsDelData;
4983 static void
4984 cb_undo_insdel (ColRowInsDelData *data)
4986 data->func (data->sheet, data->pos, data->count, NULL, NULL);
4987 colrow_set_states (data->sheet, data->is_cols,
4988 data->state_start, data->states);
4991 static void
4992 cb_undo_insdel_free (ColRowInsDelData *data)
4994 colrow_state_list_destroy (data->states);
4995 g_free (data);
4998 static gboolean
4999 sheet_insdel_colrow (Sheet *sheet, int pos, int count,
5000 GOUndo **pundo, GOCmdContext *cc,
5001 gboolean is_cols, gboolean is_insert,
5002 const char *description,
5003 ColRowInsDelFunc opposite)
5006 GnmRange kill_zone; /* The range whose contents will be lost. */
5007 GnmRange move_zone; /* The range whose contents will be moved. */
5008 GnmRange change_zone; /* The union of kill_zone and move_zone. */
5009 int i, last_pos, max_used_pos;
5010 int kill_start, kill_end, move_start, move_end;
5011 int scount = is_insert ? count : -count;
5012 ColRowStateList *states = NULL;
5013 GnmExprRelocateInfo reloc_info;
5014 GSList *l;
5015 gboolean sticky_end = TRUE;
5017 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
5018 g_return_val_if_fail (count > 0, TRUE);
5021 * The main undo for an insert col/row is delete col/row and vice versa.
5022 * In addition to that, we collect undo information that the main undo
5023 * operation will not restore -- for example the contents of the kill
5024 * zone.
5026 if (pundo) *pundo = NULL;
5028 last_pos = colrow_max (is_cols, sheet) - 1;
5029 max_used_pos = is_cols ? sheet->cols.max_used : sheet->rows.max_used;
5030 if (is_insert) {
5031 kill_start = last_pos - (count - 1);
5032 kill_end = last_pos;
5033 move_start = pos;
5034 move_end = kill_start - 1;
5035 } else {
5036 int max_count = last_pos + 1 - pos;
5037 if (count > max_count) {
5038 sticky_end = FALSE;
5039 count = max_count;
5041 kill_start = pos;
5042 kill_end = pos + (count - 1);
5043 move_start = kill_end + 1;
5044 move_end = last_pos;
5046 (is_cols ? range_init_cols : range_init_rows)
5047 (&kill_zone, sheet, kill_start, kill_end);
5048 (is_cols ? range_init_cols : range_init_rows)
5049 (&move_zone, sheet, move_start, move_end);
5050 change_zone = range_union (&kill_zone, &move_zone);
5052 /* 0. Check displaced/deleted region and ensure arrays aren't divided. */
5053 if (sheet_range_splits_array (sheet, &kill_zone, NULL, cc, description))
5054 return TRUE;
5055 if (move_start <= move_end &&
5056 sheet_range_splits_array (sheet, &move_zone, NULL, cc, description))
5057 return TRUE;
5060 * At this point we're committed. Anything that can go wrong should
5061 * have been ruled out already.
5064 if (0) {
5065 g_printerr ("Action = %s at %d count %d\n", description, pos, count);
5066 g_printerr ("Kill zone: %s\n", range_as_string (&kill_zone));
5069 /* 1. Delete all columns/rows in the kill zone */
5070 if (pundo) {
5071 combine_undo (pundo, clipboard_copy_range_undo (sheet, &kill_zone));
5072 states = colrow_get_states (sheet, is_cols, kill_start, kill_end);
5074 for (i = MIN (max_used_pos, kill_end); i >= kill_start; --i)
5075 (is_cols ? sheet_col_destroy : sheet_row_destroy)
5076 (sheet, i, TRUE);
5077 /* Brutally discard auto filter objects. Collect the rest for undo. */
5078 sheet_objects_clear (sheet, &kill_zone, GNM_FILTER_COMBO_TYPE, NULL);
5079 sheet_objects_clear (sheet, &kill_zone, G_TYPE_NONE, pundo);
5081 reloc_info.reloc_type = is_cols ? GNM_EXPR_RELOCATE_COLS : GNM_EXPR_RELOCATE_ROWS;
5082 reloc_info.sticky_end = sticky_end;
5083 reloc_info.origin_sheet = reloc_info.target_sheet = sheet;
5084 parse_pos_init_sheet (&reloc_info.pos, sheet);
5086 /* 2. Get rid of style dependents, see #741197. */
5087 sheet_style_clear_style_dependents (sheet, &change_zone);
5089 /* 3. Invalidate references to kill zone. */
5090 if (is_insert) {
5091 /* Done in the next step. */
5092 } else {
5093 reloc_info.origin = kill_zone;
5094 /* Force invalidation: */
5095 reloc_info.col_offset = is_cols ? last_pos + 1 : 0;
5096 reloc_info.row_offset = is_cols ? 0 : last_pos + 1;
5097 combine_undo (pundo, dependents_relocate (&reloc_info));
5100 /* 4. Fix references to the cells which are moving */
5101 reloc_info.origin = is_insert ? change_zone : move_zone;
5102 reloc_info.col_offset = is_cols ? scount : 0;
5103 reloc_info.row_offset = is_cols ? 0 : scount;
5104 combine_undo (pundo, dependents_relocate (&reloc_info));
5106 /* 5. Move the cells */
5107 sheet_cells_deps_move (&reloc_info);
5109 /* 6. Move the columns/rows to their new location. */
5110 if (is_insert) {
5111 /* From right to left */
5112 for (i = max_used_pos; i >= pos ; --i)
5113 sheet_colrow_move (sheet, is_cols, i, i + count);
5114 } else {
5115 /* From left to right */
5116 for (i = pos + count ; i <= max_used_pos; ++i)
5117 sheet_colrow_move (sheet, is_cols, i, i - count);
5119 sheet_colrow_set_collapse (sheet, is_cols, pos);
5120 sheet_colrow_set_collapse (sheet, is_cols,
5121 is_insert ? pos + count : last_pos - (count - 1));
5123 /* 7. Move formatting. */
5124 sheet_style_insdel_colrow (&reloc_info);
5126 /* 8. Move objects. */
5127 sheet_objects_relocate (&reloc_info, FALSE, pundo);
5129 /* 9. Move merges. */
5130 gnm_sheet_merge_relocate (&reloc_info, pundo);
5132 /* 10. Move filters. */
5133 gnm_sheet_filter_insdel_colrow (sheet, is_cols, is_insert, pos, count, pundo);
5135 /* Notify sheet of pending updates */
5136 sheet_mark_dirty (sheet);
5137 sheet->priv->recompute_visibility = TRUE;
5138 sheet_flag_recompute_spans (sheet);
5139 sheet_flag_status_update_range (sheet, &change_zone);
5140 if (is_cols)
5141 sheet->priv->reposition_objects.col = pos;
5142 else
5143 sheet->priv->reposition_objects.row = pos;
5145 /* WARNING WARNING WARNING
5146 * This is bad practice and should not really be here.
5147 * However, we need to ensure that update is run before
5148 * sv_panes_insdel_colrow plays with frozen panes, updating those can
5149 * trigger redraws before sheet_update has been called. */
5150 sheet_update (sheet);
5152 SHEET_FOREACH_VIEW (sheet, sv,
5153 sv_panes_insdel_colrow (sv, is_cols, is_insert, pos, count););
5155 /* The main undo is the opposite operation. */
5156 if (pundo) {
5157 ColRowInsDelData *data;
5158 GOUndo *u;
5160 data = g_new (ColRowInsDelData, 1);
5161 data->func = opposite;
5162 data->sheet = sheet;
5163 data->is_cols = is_cols;
5164 data->pos = pos;
5165 data->count = count;
5166 data->states = states;
5167 data->state_start = kill_start;
5169 u = go_undo_unary_new (data, (GOUndoUnaryFunc)cb_undo_insdel,
5170 (GFreeFunc)cb_undo_insdel_free);
5172 combine_undo (pundo, u);
5175 /* Reapply all filters. */
5176 for (l = sheet->filters; l; l = l->next) {
5177 GnmFilter *filter = l->data;
5178 gnm_filter_reapply (filter);
5181 return FALSE;
5185 * sheet_insert_cols:
5186 * @sheet: #Sheet
5187 * @col: At which position we want to insert
5188 * @count: The number of columns to be inserted
5189 * @pundo: (out): (transfer full): (allow-none): undo closure
5190 * @cc:
5192 gboolean
5193 sheet_insert_cols (Sheet *sheet, int col, int count,
5194 GOUndo **pundo, GOCmdContext *cc)
5196 return sheet_insdel_colrow (sheet, col, count, pundo, cc,
5197 TRUE, TRUE,
5198 _("Insert Columns"),
5199 sheet_delete_cols);
5203 * sheet_delete_cols:
5204 * @sheet: The sheet
5205 * @col: At which position we want to start deleting columns
5206 * @count: The number of columns to be deleted
5207 * @pundo: (out): (transfer full): (allow-none): undo closure
5208 * @cc: The command context
5210 gboolean
5211 sheet_delete_cols (Sheet *sheet, int col, int count,
5212 GOUndo **pundo, GOCmdContext *cc)
5214 return sheet_insdel_colrow (sheet, col, count, pundo, cc,
5215 TRUE, FALSE,
5216 _("Delete Columns"),
5217 sheet_insert_cols);
5221 * sheet_insert_rows:
5222 * @sheet: The sheet
5223 * @row: At which position we want to insert
5224 * @count: The number of rows to be inserted
5225 * @pundo: (out): (transfer full): (allow-none): undo closure
5226 * @cc: The command context
5228 gboolean
5229 sheet_insert_rows (Sheet *sheet, int row, int count,
5230 GOUndo **pundo, GOCmdContext *cc)
5232 return sheet_insdel_colrow (sheet, row, count, pundo, cc,
5233 FALSE, TRUE,
5234 _("Insert Rows"),
5235 sheet_delete_rows);
5239 * sheet_delete_rows:
5240 * @sheet: The sheet
5241 * @row: At which position we want to start deleting rows
5242 * @count: The number of rows to be deleted
5243 * @pundo: (out): (transfer full): (allow-none): undo closure
5244 * @cc: The command context
5246 gboolean
5247 sheet_delete_rows (Sheet *sheet, int row, int count,
5248 GOUndo **pundo, GOCmdContext *cc)
5250 return sheet_insdel_colrow (sheet, row, count, pundo, cc,
5251 FALSE, FALSE,
5252 _("Delete Rows"),
5253 sheet_insert_rows);
5257 * Callback for sheet_foreach_cell_in_range to remove a cell from the sheet
5258 * hash, unlink from the dependent collection and put it in a temporary list.
5260 static GnmValue *
5261 cb_collect_cell (GnmCellIter const *iter, gpointer user)
5263 GList ** l = user;
5264 GnmCell *cell = iter->cell;
5265 gboolean needs_recalc = gnm_cell_needs_recalc (cell);
5267 sheet_cell_remove_from_hash (iter->pp.sheet, cell);
5268 *l = g_list_prepend (*l, cell);
5269 if (needs_recalc)
5270 cell->base.flags |= DEPENDENT_NEEDS_RECALC;
5271 return NULL;
5275 * sheet_move_range:
5276 * @cc:
5277 * @rinfo:
5278 * @pundo: optionally NULL, caller releases result
5280 * Move a range as specified in @rinfo report warnings to @cc.
5281 * if @pundo is non NULL, invalidate references to the
5282 * target region that are being cleared, and store the undo information
5283 * in @pundo. If it is NULL do NOT INVALIDATE.
5285 void
5286 sheet_move_range (GnmExprRelocateInfo const *rinfo,
5287 GOUndo **pundo, GOCmdContext *cc)
5289 GList *cells = NULL;
5290 GnmCell *cell;
5291 GnmRange dst;
5292 gboolean out_of_range;
5294 g_return_if_fail (rinfo != NULL);
5295 g_return_if_fail (IS_SHEET (rinfo->origin_sheet));
5296 g_return_if_fail (IS_SHEET (rinfo->target_sheet));
5297 g_return_if_fail (rinfo->origin_sheet != rinfo->target_sheet ||
5298 rinfo->col_offset != 0 ||
5299 rinfo->row_offset != 0);
5301 dst = rinfo->origin;
5302 out_of_range = range_translate (&dst, rinfo->target_sheet,
5303 rinfo->col_offset, rinfo->row_offset);
5305 /* Redraw the src region in case anything was spanning */
5306 sheet_redraw_range (rinfo->origin_sheet, &rinfo->origin);
5308 /* 1. invalidate references to any cells in the destination range that
5309 * are not shared with the src. This must be done before the references
5310 * to from the src range are adjusted because they will point into
5311 * the destination.
5313 if (pundo != NULL) {
5314 *pundo = NULL;
5315 if (!out_of_range) {
5316 GSList *invalid;
5317 GnmExprRelocateInfo reloc_info;
5319 /* We need to be careful about invalidating references
5320 * to the old content of the destination region. We
5321 * only invalidate references to regions that are
5322 * actually lost. However, this care is only necessary
5323 * if the source and target sheets are the same.
5325 * Handle dst cells being pasted over
5327 if (rinfo->origin_sheet == rinfo->target_sheet &&
5328 range_overlap (&rinfo->origin, &dst))
5329 invalid = range_split_ranges (&rinfo->origin, &dst);
5330 else
5331 invalid = g_slist_append (NULL, gnm_range_dup (&dst));
5333 reloc_info.origin_sheet = reloc_info.target_sheet = rinfo->target_sheet;
5335 /* send to infinity to invalidate, but try to assist
5336 * the relocation heuristics only move in 1
5337 * dimension if possible to give us a chance to be
5338 * smart about partial invalidations */
5339 reloc_info.col_offset = gnm_sheet_get_max_cols (rinfo->target_sheet);
5340 reloc_info.row_offset = gnm_sheet_get_max_rows (rinfo->target_sheet);
5341 reloc_info.sticky_end = TRUE;
5342 if (rinfo->col_offset == 0) {
5343 reloc_info.col_offset = 0;
5344 reloc_info.reloc_type = GNM_EXPR_RELOCATE_ROWS;
5345 } else if (rinfo->row_offset == 0) {
5346 reloc_info.row_offset = 0;
5347 reloc_info.reloc_type = GNM_EXPR_RELOCATE_COLS;
5348 } else
5349 reloc_info.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE;
5351 parse_pos_init_sheet (&reloc_info.pos,
5352 rinfo->origin_sheet);
5354 while (invalid) {
5355 GnmRange *r = invalid->data;
5356 invalid = g_slist_remove (invalid, r);
5357 if (!range_overlap (r, &rinfo->origin)) {
5358 reloc_info.origin = *r;
5359 combine_undo (pundo,
5360 dependents_relocate (&reloc_info));
5362 g_free (r);
5366 * DO NOT handle src cells moving out the bounds.
5367 * that is handled elsewhere.
5371 /* 2. Fix references to and from the cells which are moving */
5372 combine_undo (pundo, dependents_relocate (rinfo));
5375 /* 3. Collect the cells */
5376 sheet_foreach_cell_in_range (rinfo->origin_sheet, CELL_ITER_IGNORE_NONEXISTENT,
5377 rinfo->origin.start.col, rinfo->origin.start.row,
5378 rinfo->origin.end.col, rinfo->origin.end.row,
5379 &cb_collect_cell, &cells);
5381 /* Reverse list so that we start at the top left (simplifies arrays). */
5382 cells = g_list_reverse (cells);
5384 /* 4. Clear the target area & invalidate references to it */
5385 if (!out_of_range)
5386 /* we can clear content but not styles from the destination
5387 * region without worrying if it overlaps with the source,
5388 * because we have already extracted the content. However,
5389 * we do need to queue anything that depends on the region for
5390 * recalc. */
5391 sheet_clear_region (rinfo->target_sheet,
5392 dst.start.col, dst.start.row,
5393 dst.end.col, dst.end.row,
5394 CLEAR_VALUES|CLEAR_RECALC_DEPS, cc);
5396 /* 5. Slide styles BEFORE the cells so that spans get computed properly */
5397 sheet_style_relocate (rinfo);
5399 /* 6. Insert the cells back */
5400 for (; cells != NULL ; cells = g_list_remove (cells, cell)) {
5401 cell = cells->data;
5403 /* check for out of bounds and delete if necessary */
5404 if ((cell->pos.col + rinfo->col_offset) >= gnm_sheet_get_max_cols (rinfo->target_sheet) ||
5405 (cell->pos.row + rinfo->row_offset) >= gnm_sheet_get_max_rows (rinfo->target_sheet)) {
5406 cell_free (cell);
5407 continue;
5410 /* Update the location */
5411 cell->base.sheet = rinfo->target_sheet;
5412 cell->pos.col += rinfo->col_offset;
5413 cell->pos.row += rinfo->row_offset;
5414 sheet_cell_add_to_hash (rinfo->target_sheet, cell);
5415 if (gnm_cell_has_expr (cell))
5416 dependent_link (GNM_CELL_TO_DEP (cell));
5419 /* 7. Move objects in the range */
5420 sheet_objects_relocate (rinfo, TRUE, pundo);
5421 gnm_sheet_merge_relocate (rinfo, pundo);
5423 /* 8. Notify sheet of pending update */
5424 sheet_flag_recompute_spans (rinfo->origin_sheet);
5425 sheet_flag_status_update_range (rinfo->origin_sheet, &rinfo->origin);
5428 static void
5429 sheet_colrow_default_calc (Sheet *sheet, double units,
5430 gboolean is_cols, gboolean is_pts)
5432 ColRowInfo *cri = is_cols
5433 ? &sheet->cols.default_style
5434 : &sheet->rows.default_style;
5436 g_return_if_fail (units > 0.);
5438 cri->is_default = TRUE;
5439 cri->hard_size = FALSE;
5440 cri->visible = TRUE;
5441 cri->spans = NULL;
5443 if (is_pts) {
5444 cri->size_pts = units;
5445 colrow_compute_pixels_from_pts (cri, sheet, is_cols, -1);
5446 } else {
5447 cri->size_pixels = units;
5448 colrow_compute_pts_from_pixels (cri, sheet, is_cols, -1);
5452 /************************************************************************/
5453 /* Col width support routines.
5457 * sheet_col_get_distance_pts:
5459 * Return the number of points between from_col to to_col
5460 * measured from the upper left corner.
5462 double
5463 sheet_col_get_distance_pts (Sheet const *sheet, int from, int to)
5465 ColRowInfo const *ci;
5466 double dflt, pts = 0., sign = 1.;
5467 int i;
5469 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5471 if (from > to) {
5472 int const tmp = to;
5473 to = from;
5474 from = tmp;
5475 sign = -1.;
5478 g_return_val_if_fail (from >= 0, 1.);
5479 g_return_val_if_fail (to <= gnm_sheet_get_max_cols (sheet), 1.);
5481 /* Do not use colrow_foreach, it ignores empties */
5482 dflt = sheet->cols.default_style.size_pts;
5483 for (i = from ; i < to ; ++i) {
5484 if (NULL == (ci = sheet_col_get (sheet, i)))
5485 pts += dflt;
5486 else if (ci->visible)
5487 pts += ci->size_pts;
5490 if (sheet->display_formulas)
5491 pts *= 2.;
5493 return pts * sign;
5497 * sheet_col_get_distance_pixels:
5499 * Return the number of pixels between from_col to to_col
5500 * measured from the upper left corner.
5503 sheet_col_get_distance_pixels (Sheet const *sheet, int from, int to)
5505 ColRowInfo const *ci;
5506 int dflt, pixels = 0, sign = 1;
5507 int i;
5509 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5511 if (from > to) {
5512 int const tmp = to;
5513 to = from;
5514 from = tmp;
5515 sign = -1;
5518 g_return_val_if_fail (from >= 0, 1);
5519 g_return_val_if_fail (to <= gnm_sheet_get_max_cols (sheet), 1);
5521 /* Do not use colrow_foreach, it ignores empties */
5522 dflt = sheet_col_get_default_size_pixels (sheet);
5523 for (i = from ; i < to ; ++i) {
5524 if (NULL == (ci = sheet_col_get (sheet, i)))
5525 pixels += dflt;
5526 else if (ci->visible)
5527 pixels += ci->size_pixels;
5530 return pixels * sign;
5534 * sheet_col_set_size_pts:
5535 * @sheet: The sheet
5536 * @col: The col
5537 * @width_pts: The desired widtht in pts
5538 * @set_by_user: TRUE if this was done by a user (ie, user manually
5539 * set the width)
5541 * Sets width of a col in pts, INCLUDING left and right margins, and the far
5542 * grid line. This is a low level internal routine. It does NOT redraw,
5543 * or reposition objects.
5545 void
5546 sheet_col_set_size_pts (Sheet *sheet, int col, double width_pts,
5547 gboolean set_by_user)
5549 ColRowInfo *ci;
5551 g_return_if_fail (IS_SHEET (sheet));
5552 g_return_if_fail (width_pts > 0.0);
5554 ci = sheet_col_fetch (sheet, col);
5555 ci->hard_size = set_by_user;
5556 if (ci->size_pts == width_pts)
5557 return;
5559 ci->size_pts = width_pts;
5560 colrow_compute_pixels_from_pts (ci, sheet, TRUE, -1);
5562 sheet->priv->recompute_visibility = TRUE;
5563 sheet_flag_recompute_spans (sheet);
5564 if (sheet->priv->reposition_objects.col > col)
5565 sheet->priv->reposition_objects.col = col;
5568 void
5569 sheet_col_set_size_pixels (Sheet *sheet, int col, int width_pixels,
5570 gboolean set_by_user)
5572 ColRowInfo *ci;
5574 g_return_if_fail (IS_SHEET (sheet));
5575 g_return_if_fail (width_pixels > 0.0);
5577 ci = sheet_col_fetch (sheet, col);
5578 ci->hard_size = set_by_user;
5579 if (ci->size_pixels == width_pixels)
5580 return;
5582 ci->size_pixels = width_pixels;
5583 colrow_compute_pts_from_pixels (ci, sheet, TRUE, -1);
5585 sheet->priv->recompute_visibility = TRUE;
5586 sheet_flag_recompute_spans (sheet);
5587 if (sheet->priv->reposition_objects.col > col)
5588 sheet->priv->reposition_objects.col = col;
5592 * sheet_col_get_default_size_pts:
5594 * Return the default number of pts in a column, including margins.
5595 * This function returns the raw sum, no rounding etc.
5597 double
5598 sheet_col_get_default_size_pts (Sheet const *sheet)
5600 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5601 return sheet->cols.default_style.size_pts;
5605 sheet_col_get_default_size_pixels (Sheet const *sheet)
5607 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5608 return sheet->cols.default_style.size_pixels;
5611 void
5612 sheet_col_set_default_size_pts (Sheet *sheet, double width_pts)
5614 g_return_if_fail (IS_SHEET (sheet));
5615 g_return_if_fail (width_pts > 0.);
5617 sheet_colrow_default_calc (sheet, width_pts, TRUE, TRUE);
5618 sheet->priv->recompute_visibility = TRUE;
5619 sheet_flag_recompute_spans (sheet);
5620 sheet->priv->reposition_objects.col = 0;
5622 void
5623 sheet_col_set_default_size_pixels (Sheet *sheet, int width_pixels)
5625 g_return_if_fail (IS_SHEET (sheet));
5627 sheet_colrow_default_calc (sheet, width_pixels, TRUE, FALSE);
5628 sheet->priv->recompute_visibility = TRUE;
5629 sheet_flag_recompute_spans (sheet);
5630 sheet->priv->reposition_objects.col = 0;
5633 /**************************************************************************/
5634 /* Row height support routines
5638 * sheet_row_get_distance_pts:
5640 * Return the number of points between from_row to to_row
5641 * measured from the upper left corner.
5643 double
5644 sheet_row_get_distance_pts (Sheet const *sheet, int from, int to)
5646 ColRowSegment const *segment;
5647 ColRowInfo const *ri;
5648 double const default_size = sheet->rows.default_style.size_pts;
5649 double pts = 0., sign = 1.;
5650 int i;
5652 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5654 if (from > to) {
5655 int const tmp = to;
5656 to = from;
5657 from = tmp;
5658 sign = -1.;
5661 g_return_val_if_fail (from >= 0, 1.);
5662 g_return_val_if_fail (to <= gnm_sheet_get_max_rows (sheet), 1.);
5664 /* Do not use colrow_foreach, it ignores empties.
5665 * Optimize this so that long jumps are not quite so horrific
5666 * for performance.
5668 for (i = from ; i < to ; ++i) {
5669 segment = COLROW_GET_SEGMENT (&(sheet->rows), i);
5671 if (segment != NULL) {
5672 ri = segment->info[COLROW_SUB_INDEX (i)];
5673 if (ri == NULL)
5674 pts += default_size;
5675 else if (ri->visible)
5676 pts += ri->size_pts;
5677 } else {
5678 int segment_end = COLROW_SEGMENT_END (i)+1;
5679 if (segment_end > to)
5680 segment_end = to;
5681 pts += default_size * (segment_end - i);
5682 i = segment_end-1;
5686 return pts*sign;
5690 * sheet_row_get_distance_pixels:
5692 * Return the number of pixels between from_row to to_row
5693 * measured from the upper left corner.
5696 sheet_row_get_distance_pixels (Sheet const *sheet, int from, int to)
5698 ColRowInfo const *ci;
5699 int dflt, pixels = 0, sign = 1;
5700 int i;
5702 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5704 if (from > to) {
5705 int const tmp = to;
5706 to = from;
5707 from = tmp;
5708 sign = -1;
5711 g_return_val_if_fail (from >= 0, 1);
5712 g_return_val_if_fail (to <= gnm_sheet_get_max_rows (sheet), 1);
5714 /* Do not use colrow_foreach, it ignores empties */
5715 dflt = sheet_row_get_default_size_pixels (sheet);
5716 for (i = from ; i < to ; ++i) {
5717 if (NULL == (ci = sheet_row_get (sheet, i)))
5718 pixels += dflt;
5719 else if (ci->visible)
5720 pixels += ci->size_pixels;
5723 return pixels * sign;
5727 * sheet_row_set_size_pts:
5728 * @sheet: The sheet
5729 * @row: The row
5730 * @height_pts: The desired height in pts
5731 * @set_by_user: TRUE if this was done by a user (ie, user manually
5732 * set the height)
5734 * Sets height of a row in pts, INCLUDING top and bottom margins, and the lower
5735 * grid line. This is a low level internal routine. It does NOT redraw,
5736 * or reposition objects.
5738 void
5739 sheet_row_set_size_pts (Sheet *sheet, int row, double height_pts,
5740 gboolean set_by_user)
5742 ColRowInfo *ri;
5744 g_return_if_fail (IS_SHEET (sheet));
5745 g_return_if_fail (height_pts > 0.0);
5747 ri = sheet_row_fetch (sheet, row);
5748 ri->hard_size = set_by_user;
5749 if (ri->size_pts == height_pts)
5750 return;
5752 ri->size_pts = height_pts;
5753 colrow_compute_pixels_from_pts (ri, sheet, FALSE, -1);
5755 sheet->priv->recompute_visibility = TRUE;
5756 if (sheet->priv->reposition_objects.row > row)
5757 sheet->priv->reposition_objects.row = row;
5761 * sheet_row_set_size_pixels:
5762 * @sheet: The sheet
5763 * @row: The row
5764 * @height_pixels: The desired height
5765 * @set_by_user: TRUE if this was done by a user (ie, user manually
5766 * set the width)
5768 * Sets height of a row in pixels, INCLUDING top and bottom margins, and the lower
5769 * grid line.
5771 void
5772 sheet_row_set_size_pixels (Sheet *sheet, int row, int height_pixels,
5773 gboolean set_by_user)
5775 ColRowInfo *ri;
5777 g_return_if_fail (IS_SHEET (sheet));
5778 g_return_if_fail (height_pixels > 0);
5780 ri = sheet_row_fetch (sheet, row);
5781 ri->hard_size = set_by_user;
5782 if (ri->size_pixels == height_pixels)
5783 return;
5785 ri->size_pixels = height_pixels;
5786 colrow_compute_pts_from_pixels (ri, sheet, FALSE, -1);
5788 sheet->priv->recompute_visibility = TRUE;
5789 if (sheet->priv->reposition_objects.row > row)
5790 sheet->priv->reposition_objects.row = row;
5794 * sheet_row_get_default_size_pts:
5796 * Return the defaul number of units in a row, including margins.
5797 * This function returns the raw sum, no rounding etc.
5799 double
5800 sheet_row_get_default_size_pts (Sheet const *sheet)
5802 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5803 return sheet->rows.default_style.size_pts;
5807 sheet_row_get_default_size_pixels (Sheet const *sheet)
5809 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5810 return sheet->rows.default_style.size_pixels;
5813 void
5814 sheet_row_set_default_size_pts (Sheet *sheet, double height_pts)
5816 g_return_if_fail (IS_SHEET (sheet));
5818 sheet_colrow_default_calc (sheet, height_pts, FALSE, TRUE);
5819 sheet->priv->recompute_visibility = TRUE;
5820 sheet->priv->reposition_objects.row = 0;
5823 void
5824 sheet_row_set_default_size_pixels (Sheet *sheet, int height_pixels)
5826 g_return_if_fail (IS_SHEET (sheet));
5828 sheet_colrow_default_calc (sheet, height_pixels, FALSE, FALSE);
5829 sheet->priv->recompute_visibility = TRUE;
5830 sheet->priv->reposition_objects.row = 0;
5833 /****************************************************************************/
5835 void
5836 sheet_scrollbar_config (Sheet const *sheet)
5838 g_return_if_fail (IS_SHEET (sheet));
5840 SHEET_FOREACH_CONTROL (sheet, view, control,
5841 sc_scrollbar_config (control););
5844 /*****************************************************************************/
5845 typedef struct
5847 gboolean is_column;
5848 Sheet *sheet;
5849 } closure_clone_colrow;
5851 static gboolean
5852 sheet_clone_colrow_info_item (GnmColRowIter const *iter, void *user_data)
5854 closure_clone_colrow const *closure = user_data;
5855 ColRowInfo *new_colrow = sheet_colrow_fetch (closure->sheet,
5856 iter->pos, closure->is_column);
5857 colrow_copy (new_colrow, iter->cri);
5858 return FALSE;
5861 static void
5862 sheet_dup_colrows (Sheet const *src, Sheet *dst)
5864 closure_clone_colrow closure;
5865 int max_col = MIN (gnm_sheet_get_max_cols (src), gnm_sheet_get_max_cols (dst)),
5866 max_row = MIN (gnm_sheet_get_max_rows (src), gnm_sheet_get_max_rows (dst));
5868 closure.sheet = dst;
5869 closure.is_column = TRUE;
5870 colrow_foreach (&src->cols, 0, max_col - 1,
5871 &sheet_clone_colrow_info_item, &closure);
5872 closure.is_column = FALSE;
5873 colrow_foreach (&src->rows, 0, max_row - 1,
5874 &sheet_clone_colrow_info_item, &closure);
5876 sheet_col_set_default_size_pixels (dst,
5877 sheet_col_get_default_size_pixels (src));
5878 sheet_row_set_default_size_pixels (dst,
5879 sheet_row_get_default_size_pixels (src));
5881 dst->cols.max_outline_level = src->cols.max_outline_level;
5882 dst->rows.max_outline_level = src->rows.max_outline_level;
5885 static void
5886 sheet_dup_styles (Sheet const *src, Sheet *dst)
5888 static GnmCellPos const corner = { 0, 0 };
5889 GnmRange r;
5890 GnmStyleList *styles;
5892 sheet_style_set_auto_pattern_color (
5893 dst, sheet_style_get_auto_pattern_color (src));
5895 styles = sheet_style_get_range (src, range_init_full_sheet (&r, src));
5896 sheet_style_set_list (dst, &corner, styles, NULL, NULL);
5897 style_list_free (styles);
5900 static void
5901 sheet_dup_merged_regions (Sheet const *src, Sheet *dst)
5903 GSList *ptr;
5905 for (ptr = src->list_merged ; ptr != NULL ; ptr = ptr->next)
5906 gnm_sheet_merge_add (dst, ptr->data, FALSE, NULL);
5909 static void
5910 sheet_dup_names (Sheet const *src, Sheet *dst)
5912 GSList *names = gnm_named_expr_collection_list (src->names);
5913 GSList *l;
5914 GnmParsePos dst_pp;
5916 if (names == NULL)
5917 return;
5919 parse_pos_init_sheet (&dst_pp, dst);
5921 /* Pass 1: add placeholders. */
5922 for (l = names; l; l = l->next) {
5923 GnmNamedExpr *src_nexpr = l->data;
5924 char const *name = expr_name_name (src_nexpr);
5925 GnmNamedExpr *dst_nexpr =
5926 gnm_named_expr_collection_lookup (dst->names, name);
5927 GnmExprTop const *texpr;
5929 if (dst_nexpr)
5930 continue;
5932 texpr = gnm_expr_top_new_constant (value_new_empty ());
5933 expr_name_add (&dst_pp, name, texpr , NULL, TRUE, NULL);
5936 /* Pass 2: assign the right expression. */
5937 for (l = names; l; l = l->next) {
5938 GnmNamedExpr *src_nexpr = l->data;
5939 char const *name = expr_name_name (src_nexpr);
5940 GnmNamedExpr *dst_nexpr =
5941 gnm_named_expr_collection_lookup (dst->names, name);
5942 GnmExprTop const *texpr;
5944 if (!dst_nexpr) {
5945 g_warning ("Trouble while duplicating name %s", name);
5946 continue;
5949 if (!dst_nexpr->is_editable)
5950 continue;
5952 texpr = gnm_expr_top_relocate_sheet (src_nexpr->texpr, src, dst);
5953 expr_name_set_expr (dst_nexpr, texpr);
5956 g_slist_free (names);
5959 static void
5960 cb_sheet_cell_copy (G_GNUC_UNUSED gpointer unused, gpointer key, gpointer new_sheet_param)
5962 GnmCell const *cell = key;
5963 Sheet *dst = new_sheet_param;
5964 Sheet *src;
5965 GnmExprTop const *texpr;
5967 g_return_if_fail (dst != NULL);
5968 g_return_if_fail (cell != NULL);
5970 src = cell->base.sheet;
5971 texpr = cell->base.texpr;
5973 if (texpr && gnm_expr_top_is_array_corner (texpr)) {
5974 int cols, rows;
5976 texpr = gnm_expr_top_relocate_sheet (texpr, src, dst);
5977 gnm_expr_top_get_array_size (texpr, &cols, &rows);
5979 gnm_cell_set_array_formula (dst,
5980 cell->pos.col, cell->pos.row,
5981 cell->pos.col + cols - 1,
5982 cell->pos.row + rows - 1,
5983 gnm_expr_top_new (gnm_expr_copy (gnm_expr_top_get_array_expr (texpr))));
5985 gnm_expr_top_unref (texpr);
5986 } else if (texpr && gnm_expr_top_is_array_elem (texpr, NULL, NULL)) {
5987 /* Not a corner -- ignore. */
5988 } else {
5989 GnmCell *new_cell = sheet_cell_create (dst, cell->pos.col, cell->pos.row);
5990 if (gnm_cell_has_expr (cell)) {
5991 texpr = gnm_expr_top_relocate_sheet (texpr, src, dst);
5992 gnm_cell_set_expr_and_value (new_cell, texpr, value_new_empty (), TRUE);
5993 gnm_expr_top_unref (texpr);
5994 } else
5995 gnm_cell_set_value (new_cell, value_dup (cell->value));
5999 static void
6000 sheet_dup_cells (Sheet const *src, Sheet *dst)
6002 sheet_cell_foreach (src, &cb_sheet_cell_copy, dst);
6003 sheet_region_queue_recalc (dst, NULL);
6006 static void
6007 sheet_dup_filters (Sheet const *src, Sheet *dst)
6009 GSList *ptr;
6010 for (ptr = src->filters ; ptr != NULL ; ptr = ptr->next)
6011 gnm_filter_dup (ptr->data, dst);
6012 dst->filters = g_slist_reverse (dst->filters);
6016 * sheet_dup:
6017 * @source_sheet: #Sheet
6019 * Create a new Sheet and return it.
6020 * Returns: (transfer full): the newly allocated #Sheet.
6022 Sheet *
6023 sheet_dup (Sheet const *src)
6025 Workbook *wb;
6026 Sheet *dst;
6027 char *name;
6028 GList *l;
6030 g_return_val_if_fail (IS_SHEET (src), NULL);
6031 g_return_val_if_fail (src->workbook != NULL, NULL);
6033 wb = src->workbook;
6034 name = workbook_sheet_get_free_name (wb, src->name_unquoted,
6035 TRUE, TRUE);
6036 dst = sheet_new_with_type (wb, name, src->sheet_type,
6037 src->size.max_cols, src->size.max_rows);
6038 g_free (name);
6040 dst->protected_allow = src->protected_allow;
6041 g_object_set (dst,
6042 "zoom-factor", src->last_zoom_factor_used,
6043 "text-is-rtl", src->text_is_rtl,
6044 "visibility", src->visibility,
6045 "protected", src->is_protected,
6046 "display-formulas", src->display_formulas,
6047 "display-zeros", !src->hide_zero,
6048 "display-grid", !src->hide_grid,
6049 "display-column-header", !src->hide_col_header,
6050 "display-row-header", !src->hide_row_header,
6051 "display-outlines", src->display_outlines,
6052 "display-outlines-below", src->outline_symbols_below,
6053 "display-outlines-right", src->outline_symbols_right,
6054 "conventions", src->convs,
6055 "tab-foreground", src->tab_text_color,
6056 "tab-background", src->tab_color,
6057 NULL);
6059 gnm_print_info_free (dst->print_info);
6060 dst->print_info = gnm_print_info_dup (src->print_info);
6062 sheet_dup_styles (src, dst);
6063 sheet_dup_merged_regions (src, dst);
6064 sheet_dup_colrows (src, dst);
6065 sheet_dup_names (src, dst);
6066 sheet_dup_cells (src, dst);
6067 sheet_objects_dup (src, dst, NULL);
6068 sheet_dup_filters (src, dst); /* must be after objects */
6070 #warning selection is in view
6071 #warning freeze/thaw is in view
6073 g_object_unref (dst->solver_parameters);
6074 dst->solver_parameters = gnm_solver_param_dup (src->solver_parameters, dst);
6076 for (l = src->scenarios; l; l = l->next) {
6077 GnmScenario *src_sc = l->data;
6078 GnmScenario *dst_sc = gnm_scenario_dup (src_sc, dst);
6079 dst->scenarios = g_list_prepend (dst->scenarios, dst_sc);
6081 dst->scenarios = g_list_reverse (dst->scenarios);
6083 sheet_mark_dirty (dst);
6084 sheet_redraw_all (dst, TRUE);
6086 return dst;
6090 * sheet_set_outline_direction:
6091 * @sheet: the sheet
6092 * @is_cols: use cols or rows
6094 * When changing the placement of outline collapse markers the flags
6095 * need to be recomputed.
6097 void
6098 sheet_set_outline_direction (Sheet *sheet, gboolean is_cols)
6100 unsigned i;
6101 g_return_if_fail (IS_SHEET (sheet));
6103 /* not particularly efficient, but this is not a hot spot */
6104 for (i = colrow_max (is_cols, sheet); i-- > 0 ; )
6105 sheet_colrow_set_collapse (sheet, is_cols, i);
6109 * sheet_get_view:
6110 * @sheet:
6111 * @wbv:
6113 * Find the SheetView corresponding to the supplied @wbv.
6114 * Returns: (transfer none): the view.
6116 SheetView *
6117 sheet_get_view (Sheet const *sheet, WorkbookView const *wbv)
6119 if (sheet == NULL)
6120 return NULL;
6122 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6124 SHEET_FOREACH_VIEW (sheet, view, {
6125 if (sv_wbv (view) == wbv)
6126 return view;
6128 return NULL;
6131 static gboolean
6132 cb_queue_respan (GnmColRowIter const *iter, void *user_data)
6134 ((ColRowInfo *)(iter->cri))->needs_respan = TRUE;
6135 return FALSE;
6139 * sheet_queue_respan:
6140 * @sheet:
6141 * @start_row:
6142 * @end_row:
6144 * queues a span generation for the selected rows.
6145 * the caller is responsible for queuing a redraw
6147 void
6148 sheet_queue_respan (Sheet const *sheet, int start_row, int end_row)
6150 colrow_foreach (&sheet->rows, start_row, end_row,
6151 cb_queue_respan, NULL);
6154 void
6155 sheet_cell_queue_respan (GnmCell *cell)
6157 ColRowInfo *ri = sheet_row_get (cell->base.sheet, cell->pos.row);
6158 ri->needs_respan = TRUE;
6163 * sheet_get_comment:
6164 * @sheet: #Sheet const *
6165 * @pos: #GnmCellPos const *
6167 * If there is a cell comment at @pos in @sheet return it.
6169 * Caller does get a reference to the object if it exists.
6170 * Returns: (transfer full): the comment or %NULL.
6172 GnmComment *
6173 sheet_get_comment (Sheet const *sheet, GnmCellPos const *pos)
6175 GnmRange r;
6176 GSList *comments;
6177 GnmComment *res;
6179 GnmRange const *mr;
6181 mr = gnm_sheet_merge_contains_pos (sheet, pos);
6183 if (mr)
6184 comments = sheet_objects_get (sheet, mr, GNM_CELL_COMMENT_TYPE);
6185 else {
6186 r.start = r.end = *pos;
6187 comments = sheet_objects_get (sheet, &r, GNM_CELL_COMMENT_TYPE);
6189 if (!comments)
6190 return NULL;
6192 /* This assumes just one comment per cell. */
6193 res = comments->data;
6194 g_slist_free (comments);
6195 return res;
6198 static GnmValue *
6199 cb_find_extents (GnmCellIter const *iter, GnmCellPos *extent)
6201 if (extent->col < iter->pp.eval.col)
6202 extent->col = iter->pp.eval.col;
6203 if (extent->row < iter->pp.eval.row)
6204 extent->row = iter->pp.eval.row;
6205 return NULL;
6209 * sheet_range_trim:
6210 * @sheet: sheet cells are contained on
6211 * @r: range to trim empty cells from
6212 * @cols: trim from right
6213 * @rows: trim from bottom
6215 * This removes empty rows/cols from the
6216 * right hand or bottom edges of the range
6217 * depending on the value of @cols or @rows.
6219 * Return value: TRUE if the range was totally empty.
6221 gboolean
6222 sheet_range_trim (Sheet const *sheet, GnmRange *r,
6223 gboolean cols, gboolean rows)
6225 GnmCellPos extent = { -1, -1 };
6227 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
6228 g_return_val_if_fail (r != NULL, TRUE);
6230 sheet_foreach_cell_in_range (
6231 (Sheet *)sheet, CELL_ITER_IGNORE_BLANK,
6232 r->start.col, r->start.row, r->end.col, r->end.row,
6233 (CellIterFunc) cb_find_extents, &extent);
6235 if (extent.col < 0 || extent.row < 0)
6236 return TRUE;
6237 if (cols)
6238 r->end.col = extent.col;
6239 if (rows)
6240 r->end.row = extent.row;
6241 return FALSE;
6245 * sheet_range_has_heading:
6246 * @sheet: Sheet to check
6247 * @src: GnmRange to check
6248 * @top: Flag
6250 * Checks for a header row in @sheet!@src. If top is true it looks for a
6251 * header row from the top and if false it looks for a header col from the
6252 * left
6254 * Returns: TRUE if @src seems to have a heading
6256 gboolean
6257 sheet_range_has_heading (Sheet const *sheet, GnmRange const *src,
6258 gboolean top, gboolean ignore_styles)
6260 GnmCell const *a, *b;
6261 int length, i;
6263 /* There is only one row or col */
6264 if (top) {
6265 if (src->end.row <= src->start.row)
6266 return FALSE;
6267 length = src->end.col - src->start.col + 1;
6268 } else {
6269 if (src->end.col <= src->start.col)
6270 return FALSE;
6271 length = src->end.row - src->start.row + 1;
6274 for (i = 0; i < length; i++) {
6275 if (top) {
6276 a = sheet_cell_get (sheet,
6277 src->start.col + i, src->start.row);
6278 b = sheet_cell_get (sheet,
6279 src->start.col + i, src->start.row + 1);
6280 } else {
6281 a = sheet_cell_get (sheet,
6282 src->start.col, src->start.row + i);
6283 b = sheet_cell_get (sheet,
6284 src->start.col + 1, src->start.row + i);
6287 /* be anal */
6288 if (a == NULL || a->value == NULL || b == NULL || b->value == NULL)
6289 continue;
6291 if (VALUE_IS_NUMBER (a->value)) {
6292 if (!VALUE_IS_NUMBER (b->value))
6293 return TRUE;
6294 /* check for style differences */
6295 } else if (a->value->v_any.type != b->value->v_any.type)
6296 return TRUE;
6298 /* Look for style differences */
6299 if (!ignore_styles &&
6300 !gnm_style_equal_header (gnm_cell_get_style (a),
6301 gnm_cell_get_style (b), top))
6302 return TRUE;
6305 return FALSE;
6309 * gnm_sheet_foreach_name:
6310 * @sheet: #Sheet
6311 * @func: (scope call): #GHFunc
6312 * @data: user data.
6314 * Executes @func for each name in @sheet.
6316 void
6317 gnm_sheet_foreach_name (Sheet const *sheet, GHFunc func, gpointer data)
6319 g_return_if_fail (IS_SHEET (sheet));
6321 if (sheet->names)
6322 gnm_named_expr_collection_foreach (sheet->names, func, data);
6326 * gnm_sheet_get_size:
6327 * @sheet: #Sheet
6329 * Returns: (transfer none): the sheet size.
6331 GnmSheetSize const *
6332 gnm_sheet_get_size (Sheet const *sheet)
6334 static const GnmSheetSize default_size = {
6335 GNM_DEFAULT_COLS, GNM_DEFAULT_ROWS
6338 if (G_UNLIKELY (!sheet)) {
6339 g_warning ("NULL sheet in gnm_sheet_get_size!");
6340 /* FIXME: This needs to go. */
6341 return &default_size;
6344 if (G_UNLIKELY (sheet->being_constructed))
6345 g_warning ("Access to sheet size during construction!");
6347 return &sheet->size;
6351 * gnm_sheet_get_size2:
6352 * @sheet: #Sheet, might be %NULL
6353 * @wb: #Workbook, must be non %NULL if @sheet is %NULL
6355 * Returns: (transfer none): the sheet size if @sheet is non %NULL, or the
6356 * default sheet size for @wb.
6358 GnmSheetSize const *
6359 gnm_sheet_get_size2 (Sheet const *sheet, Workbook const *wb)
6361 return sheet
6362 ? gnm_sheet_get_size (sheet)
6363 : workbook_get_sheet_size (wb);
6366 void
6367 gnm_sheet_set_solver_params (Sheet *sheet, GnmSolverParameters *param)
6369 g_return_if_fail (IS_SHEET (sheet));
6370 g_return_if_fail (GNM_IS_SOLVER_PARAMETERS (param));
6372 g_object_ref (param);
6373 g_object_unref (sheet->solver_parameters);
6374 sheet->solver_parameters = param;
6377 /* ------------------------------------------------------------------------- */
6380 * gnm_sheet_scenario_new:
6381 * @sheet:  #Sheet
6382 * @name: the new scenario name.
6384 * Returns: (transfer full): the newly created #GnmScenario.
6386 GnmScenario *
6387 gnm_sheet_scenario_new (Sheet *sheet, const char *name)
6389 GnmScenario *sc;
6390 char *actual_name;
6392 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6393 g_return_val_if_fail (name != NULL, NULL);
6395 /* Check if a scenario having the same name already exists. */
6396 if (gnm_sheet_scenario_find (sheet, name)) {
6397 GString *str = g_string_new (NULL);
6398 gchar *tmp;
6399 int i, j, len;
6401 len = strlen (name);
6402 if (len > 1 && name [len - 1] == ']') {
6403 for (i = len - 2; i > 0; i--) {
6404 if (! g_ascii_isdigit (name [i]))
6405 break;
6408 tmp = g_strdup (name);
6409 if (i > 0 && name [i] == '[')
6410 tmp [i] = '\0';
6411 } else
6412 tmp = g_strdup (name);
6414 for (j = 1; ; j++) {
6415 g_string_printf (str, "%s [%d]", tmp, j);
6416 if (!gnm_sheet_scenario_find (sheet, str->str)) {
6417 actual_name = g_string_free (str, FALSE);
6418 str = NULL;
6419 break;
6422 if (str)
6423 g_string_free (str, TRUE);
6424 g_free (tmp);
6425 } else
6426 actual_name = g_strdup (name);
6428 sc = gnm_scenario_new (actual_name, sheet);
6430 g_free (actual_name);
6432 return sc;
6436 * gnm_sheet_scenario_find:
6437 * @sheet:  #Sheet
6438 * @name: the scenario name.
6440 * Returns: (transfer none): the newly created #GnmScenario.
6442 GnmScenario *
6443 gnm_sheet_scenario_find (Sheet *sheet, const char *name)
6445 GList *l;
6447 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6448 g_return_val_if_fail (name != NULL, NULL);
6450 for (l = sheet->scenarios; l; l = l->next) {
6451 GnmScenario *sc = l->data;
6452 if (strcmp (name, sc->name) == 0)
6453 return sc;
6456 return NULL;
6460 * gnm_sheet_scenario_add:
6461 * @sheet:  #Sheet
6462 * @sc: (transfer full): #GnmScenario
6465 void
6466 gnm_sheet_scenario_add (Sheet *sheet, GnmScenario *sc)
6468 g_return_if_fail (IS_SHEET (sheet));
6469 g_return_if_fail (GNM_IS_SCENARIO (sc));
6471 /* We take ownership of the ref. */
6472 sheet->scenarios = g_list_append (sheet->scenarios, sc);
6475 void
6476 gnm_sheet_scenario_remove (Sheet *sheet, GnmScenario *sc)
6478 g_return_if_fail (IS_SHEET (sheet));
6479 g_return_if_fail (GNM_IS_SCENARIO (sc));
6481 sheet->scenarios = g_list_remove (sheet->scenarios, sc);
6482 g_object_unref (sc);
6485 /* ------------------------------------------------------------------------- */
6488 * gnm_sheet_get_sort_setups:
6489 * @sheet: #Sheet
6491 * Returns: (transfer none): the sort setups for @sheet.
6493 GHashTable *
6494 gnm_sheet_get_sort_setups (Sheet *sheet)
6496 GHashTable *hash = sheet->sort_setups;
6498 if (hash == NULL)
6499 hash = sheet->sort_setups =
6500 g_hash_table_new_full
6501 (g_str_hash, g_str_equal,
6502 g_free, (GDestroyNotify)gnm_sort_data_destroy);
6504 return hash;
6507 void
6508 gnm_sheet_add_sort_setup (Sheet *sheet, char *key, gpointer setup)
6510 GHashTable *hash = gnm_sheet_get_sort_setups (sheet);
6512 g_hash_table_insert (hash, key, setup);
6516 * gnm_sheet_find_sort_setup:
6517 * @sheet: #Sheet
6518 * @key:
6520 * Returns: (transfer none): the found sort setup or %NULL.
6522 gconstpointer
6523 gnm_sheet_find_sort_setup (Sheet *sheet, char const *key)
6525 if (sheet->sort_setups == NULL)
6526 return NULL;
6527 return g_hash_table_lookup (sheet->sort_setups, key);