Introspection fixes.
[gnumeric.git] / src / sheet.c
blob410ede78af717d115131be155f6e5d6299ca0e6f
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 gnm_conventions_unref (sheet->convs);
209 sheet->convs = gnm_conventions_ref (convs);
210 if (sheet->display_formulas)
211 re_render_formulas (sheet);
212 SHEET_FOREACH_VIEW (sheet, sv,
213 sv->edit_pos_changed.content = TRUE;);
214 sheet_mark_dirty (sheet);
217 GnmConventions const *
218 sheet_get_conventions (Sheet const *sheet)
220 g_return_val_if_fail (IS_SHEET (sheet), gnm_conventions_default);
222 return sheet->convs;
225 static void
226 cb_sheet_set_hide_zeros (G_GNUC_UNUSED gpointer unused,
227 GnmCell *cell,
228 G_GNUC_UNUSED gpointer user)
230 if (gnm_cell_is_zero (cell))
231 gnm_cell_unrender (cell);
234 static void
235 sheet_set_hide_zeros (Sheet *sheet, gboolean hide)
237 hide = !!hide;
238 if (sheet->hide_zero == hide)
239 return;
241 sheet->hide_zero = hide;
242 sheet_mark_dirty (sheet);
244 sheet_cell_foreach (sheet, (GHFunc)cb_sheet_set_hide_zeros, NULL);
247 static void
248 sheet_set_name (Sheet *sheet, char const *new_name)
250 Workbook *wb = sheet->workbook;
251 gboolean attached;
252 Sheet *sucker;
253 char *new_name_unquoted;
255 g_return_if_fail (new_name != NULL);
257 /* No change whatsoever. */
258 if (go_str_compare (sheet->name_unquoted, new_name) == 0)
259 return;
261 /* Mark the sheet dirty unless this is the initial name. */
262 if (sheet->name_unquoted)
263 sheet_mark_dirty (sheet);
265 sucker = wb ? workbook_sheet_by_name (wb, new_name) : NULL;
266 if (sucker && sucker != sheet) {
268 * Prevent a name clash. With this you can swap names by
269 * setting just the two names.
271 char *sucker_name = workbook_sheet_get_free_name (wb, new_name, TRUE, FALSE);
272 #if 0
273 g_warning ("Renaming %s to %s to avoid clash.\n", sucker->name_unquoted, sucker_name);
274 #endif
275 g_object_set (sucker, "name", sucker_name, NULL);
276 g_free (sucker_name);
279 attached = wb != NULL &&
280 sheet->index_in_wb != -1 &&
281 sheet->name_case_insensitive;
283 /* FIXME: maybe have workbook_sheet_detach_internal for this. */
284 if (attached)
285 g_hash_table_remove (wb->sheet_hash_private,
286 sheet->name_case_insensitive);
288 /* Copy before free. */
289 new_name_unquoted = g_strdup (new_name);
291 g_free (sheet->name_unquoted);
292 g_free (sheet->name_quoted);
293 g_free (sheet->name_unquoted_collate_key);
294 g_free (sheet->name_case_insensitive);
295 sheet->name_unquoted = new_name_unquoted;
296 sheet->name_quoted = g_string_free
297 (gnm_expr_conv_quote (sheet->convs, new_name_unquoted),
298 FALSE);
299 sheet->name_unquoted_collate_key =
300 g_utf8_collate_key (new_name_unquoted, -1);
301 sheet->name_case_insensitive =
302 g_utf8_casefold (new_name_unquoted, -1);
304 /* FIXME: maybe have workbook_sheet_attach_internal for this. */
305 if (attached)
306 g_hash_table_insert (wb->sheet_hash_private,
307 sheet->name_case_insensitive,
308 sheet);
310 if (!sheet->being_constructed &&
311 sheet->sheet_type == GNM_SHEET_DATA) {
312 /* We have to fix the Sheet_Title name */
313 GnmNamedExpr *nexpr;
314 GnmParsePos pp;
316 parse_pos_init_sheet (&pp, sheet);
317 nexpr = expr_name_lookup (&pp, "Sheet_Title");
318 if (nexpr) {
319 GnmExprTop const *texpr =
320 gnm_expr_top_new_constant
321 (value_new_string (sheet->name_unquoted));
322 expr_name_set_expr (nexpr, texpr);
327 struct resize_colrow {
328 Sheet *sheet;
329 gboolean is_cols;
330 double scale;
333 static gboolean
334 cb_colrow_compute_pixels_from_pts (GnmColRowIter const *iter,
335 struct resize_colrow *data)
337 colrow_compute_pixels_from_pts ((ColRowInfo *)iter->cri,
338 data->sheet, data->is_cols,
339 data->scale);
340 return FALSE;
343 static void
344 cb_clear_rendered_cells (G_GNUC_UNUSED gpointer ignored, GnmCell *cell)
346 if (gnm_cell_get_rendered_value (cell) != NULL) {
347 sheet_cell_queue_respan (cell);
348 gnm_cell_unrender (cell);
352 static void
353 sheet_scale_changed (Sheet *sheet, gboolean cols_rescaled, gboolean rows_rescaled)
355 g_return_if_fail (cols_rescaled || rows_rescaled);
357 /* Then every column and row */
358 if (cols_rescaled) {
359 struct resize_colrow closure;
361 closure.sheet = sheet;
362 closure.is_cols = TRUE;
363 closure.scale = colrow_compute_pixel_scale (sheet, TRUE);
365 colrow_compute_pixels_from_pts (&sheet->cols.default_style,
366 sheet, TRUE, closure.scale);
367 col_row_collection_foreach (&sheet->cols,
368 0, gnm_sheet_get_last_col (sheet),
369 (ColRowHandler)&cb_colrow_compute_pixels_from_pts, &closure);
371 if (rows_rescaled) {
372 struct resize_colrow closure;
374 closure.sheet = sheet;
375 closure.is_cols = FALSE;
376 closure.scale = colrow_compute_pixel_scale (sheet, FALSE);
378 colrow_compute_pixels_from_pts (&sheet->rows.default_style,
379 sheet, FALSE, closure.scale);
380 col_row_collection_foreach (&sheet->rows, 0, gnm_sheet_get_last_row (sheet),
381 (ColRowHandler)&cb_colrow_compute_pixels_from_pts, &closure);
384 sheet_cell_foreach (sheet, (GHFunc)&cb_clear_rendered_cells, NULL);
385 SHEET_FOREACH_CONTROL (sheet, view, control, sc_scale_changed (control););
388 static void
389 sheet_set_display_formulas (Sheet *sheet, gboolean display)
391 display = !!display;
392 if (sheet->display_formulas == display)
393 return;
395 sheet->display_formulas = display;
396 sheet_mark_dirty (sheet);
397 if (!sheet->being_constructed)
398 sheet_scale_changed (sheet, TRUE, FALSE);
401 static void
402 sheet_set_zoom_factor (Sheet *sheet, double factor)
404 if (fabs (factor - sheet->last_zoom_factor_used) < 1e-6)
405 return;
406 sheet->last_zoom_factor_used = factor;
407 if (!sheet->being_constructed)
408 sheet_scale_changed (sheet, TRUE, TRUE);
411 static void
412 gnm_sheet_set_property (GObject *object, guint property_id,
413 GValue const *value, GParamSpec *pspec)
415 Sheet *sheet = (Sheet *)object;
417 switch (property_id) {
418 case PROP_SHEET_TYPE:
419 /* Construction-time only */
420 sheet->sheet_type = g_value_get_enum (value);
421 break;
422 case PROP_WORKBOOK:
423 /* Construction-time only */
424 sheet->workbook = g_value_get_object (value);
425 break;
426 case PROP_NAME:
427 sheet_set_name (sheet, g_value_get_string (value));
428 break;
429 case PROP_RTL:
430 sheet_set_direction (sheet, g_value_get_boolean (value));
431 break;
432 case PROP_VISIBILITY:
433 sheet_set_visibility (sheet, g_value_get_enum (value));
434 break;
435 case PROP_DISPLAY_FORMULAS:
436 sheet_set_display_formulas (sheet, g_value_get_boolean (value));
437 break;
438 case PROP_DISPLAY_ZEROS:
439 sheet_set_hide_zeros (sheet, !g_value_get_boolean (value));
440 break;
441 case PROP_DISPLAY_GRID:
442 sheet->hide_grid = !g_value_get_boolean (value);
443 break;
444 case PROP_DISPLAY_COLUMN_HEADER:
445 sheet->hide_col_header = !g_value_get_boolean (value);
446 break;
447 case PROP_DISPLAY_ROW_HEADER:
448 sheet->hide_row_header = !g_value_get_boolean (value);
449 break;
450 case PROP_DISPLAY_OUTLINES:
451 sheet->display_outlines = !!g_value_get_boolean (value);
452 break;
453 case PROP_DISPLAY_OUTLINES_BELOW:
454 sheet->outline_symbols_below = !!g_value_get_boolean (value);
455 break;
456 case PROP_DISPLAY_OUTLINES_RIGHT:
457 sheet->outline_symbols_right = !!g_value_get_boolean (value);
458 break;
460 case PROP_PROTECTED:
461 sheet->is_protected = !!g_value_get_boolean (value);
462 break;
463 case PROP_PROTECTED_ALLOW_EDIT_OBJECTS:
464 sheet->protected_allow.edit_objects = !!g_value_get_boolean (value);
465 break;
466 case PROP_PROTECTED_ALLOW_EDIT_SCENARIOS:
467 sheet->protected_allow.edit_scenarios = !!g_value_get_boolean (value);
468 break;
469 case PROP_PROTECTED_ALLOW_CELL_FORMATTING:
470 sheet->protected_allow.cell_formatting = !!g_value_get_boolean (value);
471 break;
472 case PROP_PROTECTED_ALLOW_COLUMN_FORMATTING:
473 sheet->protected_allow.column_formatting = !!g_value_get_boolean (value);
474 break;
475 case PROP_PROTECTED_ALLOW_ROW_FORMATTING:
476 sheet->protected_allow.row_formatting = !!g_value_get_boolean (value);
477 break;
478 case PROP_PROTECTED_ALLOW_INSERT_COLUMNS:
479 sheet->protected_allow.insert_columns = !!g_value_get_boolean (value);
480 break;
481 case PROP_PROTECTED_ALLOW_INSERT_ROWS:
482 sheet->protected_allow.insert_rows = !!g_value_get_boolean (value);
483 break;
484 case PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS:
485 sheet->protected_allow.insert_hyperlinks = !!g_value_get_boolean (value);
486 break;
487 case PROP_PROTECTED_ALLOW_DELETE_COLUMNS:
488 sheet->protected_allow.delete_columns = !!g_value_get_boolean (value);
489 break;
490 case PROP_PROTECTED_ALLOW_DELETE_ROWS:
491 sheet->protected_allow.delete_rows = !!g_value_get_boolean (value);
492 break;
493 case PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS:
494 sheet->protected_allow.select_locked_cells = !!g_value_get_boolean (value);
495 break;
496 case PROP_PROTECTED_ALLOW_SORT_RANGES:
497 sheet->protected_allow.sort_ranges = !!g_value_get_boolean (value);
498 break;
499 case PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS:
500 sheet->protected_allow.edit_auto_filters = !!g_value_get_boolean (value);
501 break;
502 case PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE:
503 sheet->protected_allow.edit_pivottable = !!g_value_get_boolean (value);
504 break;
505 case PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS:
506 sheet->protected_allow.select_unlocked_cells = !!g_value_get_boolean (value);
507 break;
509 case PROP_CONVENTIONS:
510 sheet_set_conventions (sheet, g_value_get_boxed (value));
511 break;
512 case PROP_USE_R1C1: /* convenience api */
513 sheet_set_conventions (sheet, !!g_value_get_boolean (value)
514 ? gnm_conventions_xls_r1c1 : gnm_conventions_default);
515 break;
517 case PROP_TAB_FOREGROUND: {
518 GnmColor *color = g_value_dup_boxed (value);
519 style_color_unref (sheet->tab_text_color);
520 sheet->tab_text_color = color;
521 sheet_mark_dirty (sheet);
522 break;
524 case PROP_TAB_BACKGROUND: {
525 GnmColor *color = g_value_dup_boxed (value);
526 style_color_unref (sheet->tab_color);
527 sheet->tab_color = color;
528 sheet_mark_dirty (sheet);
529 break;
531 case PROP_ZOOM_FACTOR:
532 sheet_set_zoom_factor (sheet, g_value_get_double (value));
533 break;
534 case PROP_COLUMNS:
535 /* Construction-time only */
536 sheet->size.max_cols = g_value_get_int (value);
537 break;
538 case PROP_ROWS:
539 /* Construction-time only */
540 sheet->size.max_rows = g_value_get_int (value);
541 break;
542 default:
543 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
544 break;
548 static void
549 gnm_sheet_get_property (GObject *object, guint property_id,
550 GValue *value, GParamSpec *pspec)
552 Sheet *sheet = (Sheet *)object;
554 switch (property_id) {
555 case PROP_SHEET_TYPE:
556 g_value_set_enum (value, sheet->sheet_type);
557 break;
558 case PROP_WORKBOOK:
559 g_value_set_object (value, sheet->workbook);
560 break;
561 case PROP_NAME:
562 g_value_set_string (value, sheet->name_unquoted);
563 break;
564 case PROP_RTL:
565 g_value_set_boolean (value, sheet->text_is_rtl);
566 break;
567 case PROP_VISIBILITY:
568 g_value_set_enum (value, sheet->visibility);
569 break;
570 case PROP_DISPLAY_FORMULAS:
571 g_value_set_boolean (value, sheet->display_formulas);
572 break;
573 case PROP_DISPLAY_ZEROS:
574 g_value_set_boolean (value, !sheet->hide_zero);
575 break;
576 case PROP_DISPLAY_GRID:
577 g_value_set_boolean (value, !sheet->hide_grid);
578 break;
579 case PROP_DISPLAY_COLUMN_HEADER:
580 g_value_set_boolean (value, !sheet->hide_col_header);
581 break;
582 case PROP_DISPLAY_ROW_HEADER:
583 g_value_set_boolean (value, !sheet->hide_row_header);
584 break;
585 case PROP_DISPLAY_OUTLINES:
586 g_value_set_boolean (value, sheet->display_outlines);
587 break;
588 case PROP_DISPLAY_OUTLINES_BELOW:
589 g_value_set_boolean (value, sheet->outline_symbols_below);
590 break;
591 case PROP_DISPLAY_OUTLINES_RIGHT:
592 g_value_set_boolean (value, sheet->outline_symbols_right);
593 break;
595 case PROP_PROTECTED:
596 g_value_set_boolean (value, sheet->is_protected);
597 break;
598 case PROP_PROTECTED_ALLOW_EDIT_OBJECTS:
599 g_value_set_boolean (value, sheet->protected_allow.edit_objects);
600 break;
601 case PROP_PROTECTED_ALLOW_EDIT_SCENARIOS:
602 g_value_set_boolean (value, sheet->protected_allow.edit_scenarios);
603 break;
604 case PROP_PROTECTED_ALLOW_CELL_FORMATTING:
605 g_value_set_boolean (value, sheet->protected_allow.cell_formatting);
606 break;
607 case PROP_PROTECTED_ALLOW_COLUMN_FORMATTING:
608 g_value_set_boolean (value, sheet->protected_allow.column_formatting);
609 break;
610 case PROP_PROTECTED_ALLOW_ROW_FORMATTING:
611 g_value_set_boolean (value, sheet->protected_allow.row_formatting);
612 break;
613 case PROP_PROTECTED_ALLOW_INSERT_COLUMNS:
614 g_value_set_boolean (value, sheet->protected_allow.insert_columns);
615 break;
616 case PROP_PROTECTED_ALLOW_INSERT_ROWS:
617 g_value_set_boolean (value, sheet->protected_allow.insert_rows);
618 break;
619 case PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS:
620 g_value_set_boolean (value, sheet->protected_allow.insert_hyperlinks);
621 break;
622 case PROP_PROTECTED_ALLOW_DELETE_COLUMNS:
623 g_value_set_boolean (value, sheet->protected_allow.delete_columns);
624 break;
625 case PROP_PROTECTED_ALLOW_DELETE_ROWS:
626 g_value_set_boolean (value, sheet->protected_allow.delete_rows);
627 break;
628 case PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS:
629 g_value_set_boolean (value, sheet->protected_allow.select_locked_cells);
630 break;
631 case PROP_PROTECTED_ALLOW_SORT_RANGES:
632 g_value_set_boolean (value, sheet->protected_allow.sort_ranges);
633 break;
634 case PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS:
635 g_value_set_boolean (value, sheet->protected_allow.edit_auto_filters);
636 break;
637 case PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE:
638 g_value_set_boolean (value, sheet->protected_allow.edit_pivottable);
639 break;
640 case PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS:
641 g_value_set_boolean (value, sheet->protected_allow.select_unlocked_cells);
642 break;
644 case PROP_CONVENTIONS:
645 g_value_set_boxed (value, sheet->convs);
646 break;
647 case PROP_USE_R1C1: /* convenience api */
648 g_value_set_boolean (value, sheet->convs->r1c1_addresses);
649 break;
651 case PROP_TAB_FOREGROUND:
652 g_value_set_boxed (value, sheet->tab_text_color);
653 break;
654 case PROP_TAB_BACKGROUND:
655 g_value_set_boxed (value, sheet->tab_color);
656 break;
657 case PROP_ZOOM_FACTOR:
658 g_value_set_double (value, sheet->last_zoom_factor_used);
659 break;
660 case PROP_COLUMNS:
661 g_value_set_int (value, sheet->size.max_cols);
662 break;
663 case PROP_ROWS:
664 g_value_set_int (value, sheet->size.max_rows);
665 break;
666 default:
667 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
668 break;
672 static void
673 gnm_sheet_constructed (GObject *obj)
675 Sheet *sheet = SHEET (obj);
677 if (parent_class->constructed)
678 parent_class->constructed (obj);
680 /* Now sheet_type, max_cols, and max_rows have been set. */
681 sheet->being_constructed = FALSE;
683 col_row_collection_resize (&sheet->cols, sheet->size.max_cols);
684 col_row_collection_resize (&sheet->rows, sheet->size.max_rows);
686 sheet->priv->reposition_objects.col = sheet->size.max_cols;
687 sheet->priv->reposition_objects.row = sheet->size.max_rows;
689 range_init_full_sheet (&sheet->priv->unhidden_region, sheet);
690 sheet_style_init (sheet);
692 sheet->deps = gnm_dep_container_new (sheet);
694 switch (sheet->sheet_type) {
695 case GNM_SHEET_XLM:
696 sheet->display_formulas = TRUE;
697 break;
698 case GNM_SHEET_OBJECT:
699 sheet->hide_grid = TRUE;
700 sheet->hide_col_header = sheet->hide_row_header = TRUE;
701 colrow_compute_pixels_from_pts (&sheet->rows.default_style,
702 sheet, FALSE, -1);
703 colrow_compute_pixels_from_pts (&sheet->cols.default_style,
704 sheet, TRUE, -1);
705 break;
706 case GNM_SHEET_DATA: {
707 /* We have to add permanent names */
708 GnmExprTop const *texpr;
710 if (sheet->name_unquoted)
711 texpr = gnm_expr_top_new_constant
712 (value_new_string (sheet->name_unquoted));
713 else
714 texpr = gnm_expr_top_new_constant
715 (value_new_error_REF (NULL));
716 expr_name_perm_add (sheet, "Sheet_Title",
717 texpr, FALSE);
719 texpr = gnm_expr_top_new_constant
720 (value_new_error_REF (NULL));
721 expr_name_perm_add (sheet, "Print_Area",
722 texpr, FALSE);
723 break;
725 default:
726 g_assert_not_reached ();
729 sheet_scale_changed (sheet, TRUE, TRUE);
732 static guint
733 cell_set_hash (GnmCell const *key)
735 guint32 r = key->pos.row;
736 guint32 c = key->pos.col;
737 guint32 h;
739 h = r;
740 h *= (guint32)123456789;
741 h ^= c;
742 h *= (guint32)123456789;
744 return h;
747 static gint
748 cell_set_equal (GnmCell const *a, GnmCell const *b)
750 return (a->pos.row == b->pos.row && a->pos.col == b->pos.col);
753 static void
754 gnm_sheet_init (Sheet *sheet)
756 PangoContext *context;
758 sheet->priv = g_new0 (SheetPrivate, 1);
759 sheet->being_constructed = TRUE;
761 sheet->sheet_views = g_ptr_array_new ();
763 /* Init, focus, and load handle setting these if/when necessary */
764 sheet->priv->recompute_visibility = TRUE;
765 sheet->priv->recompute_spans = TRUE;
767 sheet->is_protected = FALSE;
768 sheet->protected_allow.edit_scenarios = FALSE;
769 sheet->protected_allow.cell_formatting = FALSE;
770 sheet->protected_allow.column_formatting = FALSE;
771 sheet->protected_allow.row_formatting = FALSE;
772 sheet->protected_allow.insert_columns = FALSE;
773 sheet->protected_allow.insert_rows = FALSE;
774 sheet->protected_allow.insert_hyperlinks = FALSE;
775 sheet->protected_allow.delete_columns = FALSE;
776 sheet->protected_allow.delete_rows = FALSE;
777 sheet->protected_allow.select_locked_cells = TRUE;
778 sheet->protected_allow.sort_ranges = FALSE;
779 sheet->protected_allow.edit_auto_filters = FALSE;
780 sheet->protected_allow.edit_pivottable = FALSE;
781 sheet->protected_allow.select_unlocked_cells = TRUE;
783 sheet->hide_zero = FALSE;
784 sheet->display_outlines = TRUE;
785 sheet->outline_symbols_below = TRUE;
786 sheet->outline_symbols_right = TRUE;
787 sheet->tab_color = NULL;
788 sheet->tab_text_color = NULL;
789 sheet->visibility = GNM_SHEET_VISIBILITY_VISIBLE;
790 #ifdef GNM_WITH_GTK
791 sheet->text_is_rtl = (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL);
792 #else
793 sheet->text_is_rtl = FALSE;
794 #endif
796 sheet->sheet_objects = NULL;
797 sheet->max_object_extent.col = sheet->max_object_extent.row = 0;
799 sheet->solver_parameters = gnm_solver_param_new (sheet);
801 sheet->cols.max_used = -1;
802 sheet->cols.info = g_ptr_array_new ();
803 sheet_col_set_default_size_pts (sheet, 48);
805 sheet->rows.max_used = -1;
806 sheet->rows.info = g_ptr_array_new ();
807 sheet_row_set_default_size_pts (sheet, 12.75);
809 sheet->print_info = gnm_print_information_new (FALSE);
811 sheet->filters = NULL;
812 sheet->scenarios = NULL;
813 sheet->sort_setups = NULL;
814 sheet->list_merged = NULL;
815 sheet->hash_merged = g_hash_table_new ((GHashFunc)&gnm_cellpos_hash,
816 (GCompareFunc)&gnm_cellpos_equal);
818 sheet->cell_hash = g_hash_table_new ((GHashFunc)&cell_set_hash,
819 (GCompareFunc)&cell_set_equal);
821 /* Init preferences */
822 sheet->convs = gnm_conventions_ref (gnm_conventions_default);
824 /* FIXME: probably not here. */
825 /* See also gtk_widget_create_pango_context (). */
826 sheet->last_zoom_factor_used = -1; /* Overridden later */
827 context = gnm_pango_context_get ();
828 sheet->rendered_values = gnm_rvc_new (context, 5000);
829 g_object_unref (context);
831 /* Init menu states */
832 sheet->priv->enable_showhide_detail = TRUE;
834 sheet->names = gnm_named_expr_collection_new ();
835 sheet->style_data = NULL;
837 sheet->index_in_wb = -1;
840 static Sheet the_invalid_sheet;
841 Sheet *invalid_sheet = &the_invalid_sheet;
843 static void
844 gnm_sheet_class_init (GObjectClass *gobject_class)
846 if (GNM_MAX_COLS > 364238) {
847 /* Oh, yeah? */
848 g_warning (_("This is a special version of Gnumeric. It has been compiled\n"
849 "with support for a very large number of columns. Access to the\n"
850 "column named TRUE may conflict with the constant of the same\n"
851 "name. Expect weirdness."));
854 parent_class = g_type_class_peek_parent (gobject_class);
856 gobject_class->set_property = gnm_sheet_set_property;
857 gobject_class->get_property = gnm_sheet_get_property;
858 gobject_class->finalize = gnm_sheet_finalize;
859 gobject_class->constructed = gnm_sheet_constructed;
861 g_object_class_install_property (gobject_class, PROP_SHEET_TYPE,
862 g_param_spec_enum ("sheet-type",
863 P_("Sheet Type"),
864 P_("Which type of sheet this is."),
865 GNM_SHEET_TYPE_TYPE,
866 GNM_SHEET_DATA,
867 GSF_PARAM_STATIC |
868 G_PARAM_READWRITE |
869 G_PARAM_CONSTRUCT_ONLY));
870 g_object_class_install_property (gobject_class, PROP_WORKBOOK,
871 g_param_spec_object ("workbook",
872 P_("Parent workbook"),
873 P_("The workbook in which this sheet lives"),
874 GNM_WORKBOOK_TYPE,
875 GSF_PARAM_STATIC |
876 G_PARAM_READWRITE |
877 G_PARAM_CONSTRUCT_ONLY));
878 g_object_class_install_property (gobject_class, PROP_NAME,
879 g_param_spec_string ("name",
880 P_("Name"),
881 P_("The name of the sheet."),
882 NULL,
883 GSF_PARAM_STATIC |
884 G_PARAM_READWRITE));
885 g_object_class_install_property (gobject_class, PROP_RTL,
886 g_param_spec_boolean ("text-is-rtl",
887 P_("text-is-rtl"),
888 P_("Text goes from right to left."),
889 FALSE,
890 GSF_PARAM_STATIC |
891 G_PARAM_READWRITE));
892 g_object_class_install_property (gobject_class, PROP_VISIBILITY,
893 g_param_spec_enum ("visibility",
894 P_("Visibility"),
895 P_("How visible the sheet is."),
896 GNM_SHEET_VISIBILITY_TYPE,
897 GNM_SHEET_VISIBILITY_VISIBLE,
898 GSF_PARAM_STATIC |
899 G_PARAM_READWRITE));
900 g_object_class_install_property (gobject_class, PROP_DISPLAY_FORMULAS,
901 g_param_spec_boolean ("display-formulas",
902 P_("Display Formul\303\246"),
903 P_("Control whether formul\303\246 are shown instead of values."),
904 FALSE,
905 GSF_PARAM_STATIC |
906 G_PARAM_READWRITE));
907 g_object_class_install_property (gobject_class, PROP_DISPLAY_ZEROS,
908 g_param_spec_boolean ("display-zeros", _("Display Zeros"),
909 _("Control whether zeros are shown are blanked out."),
910 TRUE,
911 GSF_PARAM_STATIC |
912 G_PARAM_READWRITE));
913 g_object_class_install_property (gobject_class, PROP_DISPLAY_GRID,
914 g_param_spec_boolean ("display-grid", _("Display Grid"),
915 _("Control whether the grid is shown."),
916 TRUE,
917 GSF_PARAM_STATIC |
918 G_PARAM_READWRITE));
919 g_object_class_install_property (gobject_class, PROP_DISPLAY_COLUMN_HEADER,
920 g_param_spec_boolean ("display-column-header",
921 P_("Display Column Headers"),
922 P_("Control whether column headers are shown."),
923 FALSE,
924 GSF_PARAM_STATIC |
925 G_PARAM_READWRITE));
926 g_object_class_install_property (gobject_class, PROP_DISPLAY_ROW_HEADER,
927 g_param_spec_boolean ("display-row-header",
928 P_("Display Row Headers"),
929 P_("Control whether row headers are shown."),
930 FALSE,
931 GSF_PARAM_STATIC |
932 G_PARAM_READWRITE));
933 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES,
934 g_param_spec_boolean ("display-outlines",
935 P_("Display Outlines"),
936 P_("Control whether outlines are shown."),
937 TRUE,
938 GSF_PARAM_STATIC |
939 G_PARAM_READWRITE));
940 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES_BELOW,
941 g_param_spec_boolean ("display-outlines-below",
942 P_("Display Outlines Below"),
943 P_("Control whether outline symbols are shown below."),
944 TRUE,
945 GSF_PARAM_STATIC |
946 G_PARAM_READWRITE));
947 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES_RIGHT,
948 g_param_spec_boolean ("display-outlines-right",
949 P_("Display Outlines Right"),
950 P_("Control whether outline symbols are shown to the right."),
951 TRUE,
952 GSF_PARAM_STATIC |
953 G_PARAM_READWRITE));
955 g_object_class_install_property (gobject_class, PROP_PROTECTED,
956 g_param_spec_boolean ("protected",
957 P_("Protected"),
958 P_("Sheet is protected."),
959 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
960 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_OBJECTS,
961 g_param_spec_boolean ("protected-allow-edit-objects",
962 P_("Protected Allow Edit objects"),
963 P_("Allow objects to be edited while a sheet is protected"),
964 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
965 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_SCENARIOS,
966 g_param_spec_boolean ("protected-allow-edit-scenarios",
967 P_("Protected allow edit scenarios"),
968 P_("Allow scenarios to be edited while a sheet is protected"),
969 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
970 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_CELL_FORMATTING,
971 g_param_spec_boolean ("protected-allow-cell-formatting",
972 P_("Protected allow cell formatting"),
973 P_("Allow cell format changes while a sheet is protected"),
974 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
975 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_COLUMN_FORMATTING,
976 g_param_spec_boolean ("protected-allow-column-formatting",
977 P_("Protected allow column formatting"),
978 P_("Allow column formatting while a sheet is protected"),
979 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
980 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_ROW_FORMATTING,
981 g_param_spec_boolean ("protected-allow-row-formatting",
982 P_("Protected allow row formatting"),
983 P_("Allow row formatting while a sheet is protected"),
984 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
985 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_COLUMNS,
986 g_param_spec_boolean ("protected-allow-insert-columns",
987 P_("Protected allow insert columns"),
988 P_("Allow columns to be inserted while a sheet is protected"),
989 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
990 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_ROWS,
991 g_param_spec_boolean ("protected-allow-insert-rows",
992 P_("Protected allow insert rows"),
993 P_("Allow rows to be inserted while a sheet is protected"),
994 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
995 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS,
996 g_param_spec_boolean ("protected-allow-insert-hyperlinks",
997 P_("Protected allow insert hyperlinks"),
998 P_("Allow hyperlinks to be inserted while a sheet is protected"),
999 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1000 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_DELETE_COLUMNS,
1001 g_param_spec_boolean ("protected-allow-delete-columns",
1002 P_("Protected allow delete columns"),
1003 P_("Allow columns to be deleted while a sheet is protected"),
1004 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1005 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_DELETE_ROWS,
1006 g_param_spec_boolean ("protected-allow-delete-rows",
1007 P_("Protected allow delete rows"),
1008 P_("Allow rows to be deleted while a sheet is protected"),
1009 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1010 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS,
1011 g_param_spec_boolean ("protected-allow-select-locked-cells",
1012 P_("Protected allow select locked cells"),
1013 P_("Allow the user to select locked cells while a sheet is protected"),
1014 TRUE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1015 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SORT_RANGES,
1016 g_param_spec_boolean ("protected-allow-sort-ranges",
1017 P_("Protected allow sort ranges"),
1018 P_("Allow ranges to be sorted while a sheet is protected"),
1019 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1020 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS,
1021 g_param_spec_boolean ("protected-allow-edit-auto-filters",
1022 P_("Protected allow edit auto filters"),
1023 P_("Allow auto filters to be edited while a sheet is protected"),
1024 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1025 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE,
1026 g_param_spec_boolean ("protected-allow-edit-pivottable",
1027 P_("Protected allow edit pivottable"),
1028 P_("Allow pivottable to be edited while a sheet is protected"),
1029 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1030 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS,
1031 g_param_spec_boolean ("protected-allow-select-unlocked-cells",
1032 P_("Protected allow select unlocked cells"),
1033 P_("Allow the user to select unlocked cells while a sheet is protected"),
1034 TRUE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1036 g_object_class_install_property
1037 (gobject_class, PROP_CONVENTIONS,
1038 g_param_spec_boxed ("conventions",
1039 P_("Display convention for expressions (default Gnumeric A1)"),
1040 P_("How to format displayed expressions, (A1 vs R1C1, function names, ...)"),
1041 gnm_conventions_get_type (),
1042 GSF_PARAM_STATIC |
1043 G_PARAM_READWRITE));
1044 g_object_class_install_property (gobject_class, PROP_USE_R1C1, /* convenience wrapper to CONVENTIONS */
1045 g_param_spec_boolean ("use-r1c1",
1046 P_("Display convention for expressions as XLS_R1C1 vs default"),
1047 P_("How to format displayed expressions, (a convenience api)"),
1048 FALSE,
1049 GSF_PARAM_STATIC |
1050 G_PARAM_READWRITE));
1052 g_object_class_install_property (gobject_class, PROP_TAB_FOREGROUND,
1053 g_param_spec_boxed ("tab-foreground",
1054 P_("Tab Foreground"),
1055 P_("The foreground color of the tab."),
1056 GNM_COLOR_TYPE,
1057 GSF_PARAM_STATIC |
1058 G_PARAM_READWRITE));
1059 g_object_class_install_property (gobject_class, PROP_TAB_BACKGROUND,
1060 g_param_spec_boxed ("tab-background",
1061 P_("Tab Background"),
1062 P_("The background color of the tab."),
1063 GNM_COLOR_TYPE,
1064 GSF_PARAM_STATIC |
1065 G_PARAM_READWRITE));
1067 /* What is this doing in sheet? */
1068 g_object_class_install_property (gobject_class, PROP_ZOOM_FACTOR,
1069 g_param_spec_double ("zoom-factor",
1070 P_("Zoom Factor"),
1071 P_("The level of zoom used for this sheet."),
1072 0.1, 5.0,
1073 1.0,
1074 GSF_PARAM_STATIC |
1075 G_PARAM_CONSTRUCT |
1076 G_PARAM_READWRITE));
1078 g_object_class_install_property (gobject_class, PROP_COLUMNS,
1079 g_param_spec_int ("columns",
1080 P_("Columns"),
1081 P_("Columns number in the sheet"),
1082 0, GNM_MAX_COLS, GNM_DEFAULT_COLS,
1083 GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1085 g_object_class_install_property (gobject_class, PROP_ROWS,
1086 g_param_spec_int ("rows",
1087 P_("Rows"),
1088 P_("Rows number in the sheet"),
1089 0, GNM_MAX_ROWS, GNM_DEFAULT_ROWS,
1090 GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1092 signals[DETACHED_FROM_WORKBOOK] = g_signal_new
1093 ("detached_from_workbook",
1094 GNM_SHEET_TYPE,
1095 G_SIGNAL_RUN_LAST,
1096 G_STRUCT_OFFSET (GnmSheetClass, detached_from_workbook),
1097 NULL, NULL,
1098 g_cclosure_marshal_VOID__OBJECT,
1099 G_TYPE_NONE, 1, GNM_WORKBOOK_TYPE);
1103 GSF_CLASS (GnmSheet, gnm_sheet,
1104 gnm_sheet_class_init, gnm_sheet_init, G_TYPE_OBJECT)
1106 /* ------------------------------------------------------------------------- */
1108 GType
1109 gnm_sheet_type_get_type (void)
1111 static GType etype = 0;
1112 if (etype == 0) {
1113 static const GEnumValue values[] = {
1114 { GNM_SHEET_DATA, "GNM_SHEET_DATA", "data" },
1115 { GNM_SHEET_OBJECT, "GNM_SHEET_OBJECT", "object" },
1116 { GNM_SHEET_XLM, "GNM_SHEET_XLM", "xlm" },
1117 { 0, NULL, NULL }
1119 etype = g_enum_register_static ("GnmSheetType", values);
1121 return etype;
1124 GType
1125 gnm_sheet_visibility_get_type (void)
1127 static GType etype = 0;
1128 if (etype == 0) {
1129 static GEnumValue const values[] = {
1130 { GNM_SHEET_VISIBILITY_VISIBLE, "GNM_SHEET_VISIBILITY_VISIBLE", "visible" },
1131 { GNM_SHEET_VISIBILITY_HIDDEN, "GNM_SHEET_VISIBILITY_HIDDEN", "hidden" },
1132 { GNM_SHEET_VISIBILITY_VERY_HIDDEN, "GNM_SHEET_VISIBILITY_VERY_HIDDEN", "very-hidden" },
1133 { 0, NULL, NULL }
1135 etype = g_enum_register_static ("GnmSheetVisibility", values);
1137 return etype;
1140 /* ------------------------------------------------------------------------- */
1142 static gboolean
1143 powerof_2 (int i)
1145 return i > 0 && (i & (i - 1)) == 0;
1148 gboolean
1149 gnm_sheet_valid_size (int cols, int rows)
1151 return (cols >= GNM_MIN_COLS &&
1152 cols <= GNM_MAX_COLS &&
1153 powerof_2 (cols) &&
1154 rows >= GNM_MIN_ROWS &&
1155 rows <= GNM_MAX_ROWS &&
1156 powerof_2 (rows)
1157 #if 0
1158 && 0x80000000u / (unsigned)(cols / 2) >= (unsigned)rows
1159 #endif
1163 void
1164 gnm_sheet_suggest_size (int *cols, int *rows)
1166 int c = GNM_DEFAULT_COLS;
1167 int r = GNM_DEFAULT_ROWS;
1169 while (c < *cols && c < GNM_MAX_COLS)
1170 c *= 2;
1172 while (r < *rows && r < GNM_MAX_ROWS)
1173 r *= 2;
1175 while (!gnm_sheet_valid_size (c, r)) {
1176 /* Darn! Too large. */
1177 if (*cols >= GNM_MIN_COLS && c > GNM_MIN_COLS)
1178 c /= 2;
1179 else if (*rows >= GNM_MIN_ROWS && r > GNM_MIN_ROWS)
1180 r /= 2;
1181 else if (c > GNM_MIN_COLS)
1182 c /= 2;
1183 else
1184 r /= 2;
1187 *cols = c;
1188 *rows = r;
1191 static void gnm_sheet_resize_main (Sheet *sheet, int cols, int rows,
1192 GOCmdContext *cc, GOUndo **pundo);
1194 static void
1195 cb_sheet_resize (Sheet *sheet, const GnmSheetSize *data, GOCmdContext *cc)
1197 gnm_sheet_resize_main (sheet, data->max_cols, data->max_rows,
1198 cc, NULL);
1201 static void
1202 gnm_sheet_resize_main (Sheet *sheet, int cols, int rows,
1203 GOCmdContext *cc, GOUndo **pundo)
1205 int old_cols, old_rows;
1206 GnmStyle **common_col_styles = NULL;
1207 GnmStyle **common_row_styles = NULL;
1209 if (pundo) *pundo = NULL;
1211 old_cols = gnm_sheet_get_max_cols (sheet);
1212 old_rows = gnm_sheet_get_max_rows (sheet);
1213 if (old_cols == cols && old_rows == rows)
1214 return;
1216 /* ---------------------------------------- */
1217 /* Gather styles we want to copy into new areas. */
1219 if (cols > old_cols) {
1220 int r;
1221 common_row_styles = sheet_style_most_common (sheet, FALSE);
1222 for (r = 0; r < old_rows; r++)
1223 gnm_style_ref (common_row_styles[r]);
1225 if (rows > old_rows) {
1226 int c;
1227 common_col_styles = sheet_style_most_common (sheet, TRUE);
1228 for (c = 0; c < old_cols; c++)
1229 gnm_style_ref (common_col_styles[c]);
1232 /* ---------------------------------------- */
1233 /* Remove the columns and rows that will disappear. */
1235 if (cols < old_cols) {
1236 GOUndo *u = NULL;
1237 gboolean err;
1239 err = sheet_delete_cols (sheet, cols, G_MAXINT,
1240 pundo ? &u : NULL, cc);
1241 if (pundo)
1242 *pundo = go_undo_combine (*pundo, u);
1243 if (err)
1244 goto handle_error;
1247 if (rows < old_rows) {
1248 GOUndo *u = NULL;
1249 gboolean err;
1251 err = sheet_delete_rows (sheet, rows, G_MAXINT,
1252 pundo ? &u : NULL, cc);
1253 if (pundo)
1254 *pundo = go_undo_combine (*pundo, u);
1255 if (err)
1256 goto handle_error;
1259 /* ---------------------------------------- */
1260 /* Restrict selection. (Not undone.) */
1262 SHEET_FOREACH_VIEW (sheet, sv,
1264 GnmRange new_full;
1265 GSList *l;
1266 GSList *sel = selection_get_ranges (sv, TRUE);
1267 gboolean any = FALSE;
1268 GnmCellPos vis;
1269 sv_selection_reset (sv);
1270 range_init (&new_full, 0, 0, cols - 1, rows - 1);
1271 vis = new_full.start;
1272 for (l = sel; l; l = l->next) {
1273 GnmRange *r = l->data;
1274 GnmRange newr;
1275 if (range_intersection (&newr, r, &new_full)) {
1276 sv_selection_add_range (sv, &newr);
1277 vis = newr.start;
1278 any = TRUE;
1280 g_free (r);
1282 g_slist_free (sel);
1283 if (!any)
1284 sv_selection_add_pos (sv, 0, 0,
1285 GNM_SELECTION_MODE_ADD);
1286 sv_make_cell_visible (sv, vis.col, vis.row, FALSE);
1289 /* ---------------------------------------- */
1290 /* Resize column and row containers. */
1292 col_row_collection_resize (&sheet->cols, cols);
1293 col_row_collection_resize (&sheet->rows, rows);
1295 /* ---------------------------------------- */
1296 /* Resize the dependency containers. */
1299 GSList *l, *linked = NULL;
1300 /* FIXME: what about dependents in other workbooks? */
1301 WORKBOOK_FOREACH_DEPENDENT
1302 (sheet->workbook, dep,
1304 if (dependent_is_linked (dep)) {
1305 dependent_unlink (dep);
1306 linked = g_slist_prepend (linked, dep);
1309 gnm_dep_container_resize (sheet->deps, rows);
1311 for (l = linked; l; l = l->next) {
1312 GnmDependent *dep = l->data;
1313 dependent_link (dep);
1316 g_slist_free (linked);
1318 workbook_queue_all_recalc (sheet->workbook);
1321 /* ---------------------------------------- */
1322 /* Resize the styles. */
1324 sheet_style_resize (sheet, cols, rows);
1326 /* ---------------------------------------- */
1327 /* Actually change the properties. */
1329 sheet->size.max_cols = cols;
1330 sheet->cols.max_used = MIN (sheet->cols.max_used, cols - 1);
1331 sheet->size.max_rows = rows;
1332 sheet->rows.max_used = MIN (sheet->rows.max_used, rows - 1);
1334 if (old_cols != cols)
1335 g_object_notify (G_OBJECT (sheet), "columns");
1336 if (old_rows != rows)
1337 g_object_notify (G_OBJECT (sheet), "rows");
1339 if (pundo) {
1340 GnmSheetSize *data = g_new (GnmSheetSize, 1);
1341 GOUndo *u;
1343 data->max_cols = old_cols;
1344 data->max_rows = old_rows;
1345 u = go_undo_binary_new (sheet, data,
1346 (GOUndoBinaryFunc)cb_sheet_resize,
1347 NULL, g_free);
1348 *pundo = go_undo_combine (*pundo, u);
1351 range_init_full_sheet (&sheet->priv->unhidden_region, sheet);
1353 /* ---------------------------------------- */
1354 /* Apply styles to new areas. */
1356 if (cols > old_cols) {
1357 int r = 0;
1358 while (r < old_rows) {
1359 int r2 = r;
1360 GnmStyle *mstyle = common_row_styles[r];
1361 GnmRange rng;
1362 while (r2 + 1 < old_rows &&
1363 mstyle == common_row_styles[r2 + 1])
1364 r2++;
1365 range_init (&rng, old_cols, r, cols - 1, r2);
1366 gnm_style_ref (mstyle);
1367 sheet_apply_style (sheet, &rng, mstyle);
1368 r = r2 + 1;
1371 for (r = 0; r < old_rows; r++)
1372 gnm_style_unref (common_row_styles[r]);
1374 g_free (common_row_styles);
1377 if (rows > old_rows) {
1378 int c = 0;
1380 while (c < old_cols) {
1381 int c2 = c;
1382 GnmStyle *mstyle = common_col_styles[c];
1383 GnmRange rng;
1384 while (c2 + 1 < old_cols &&
1385 mstyle == common_col_styles[c2 + 1])
1386 c2++;
1387 range_init (&rng, c, old_rows, c2, rows - 1);
1388 gnm_style_ref (mstyle);
1389 sheet_apply_style (sheet, &rng, mstyle);
1390 c = c2 + 1;
1393 if (cols > old_cols) {
1395 * Expanded in both directions. One could argue about
1396 * what style to use down here, but we choose the
1397 * last column style.
1399 GnmStyle *mstyle = common_col_styles[old_cols - 1];
1400 GnmRange rng;
1402 range_init (&rng,
1403 old_cols, old_rows,
1404 cols - 1, rows - 1);
1405 gnm_style_ref (mstyle);
1406 sheet_apply_style (sheet, &rng, mstyle);
1409 for (c = 0; c < old_cols; c++)
1410 gnm_style_unref (common_col_styles[c]);
1411 g_free (common_col_styles);
1414 /* ---------------------------------------- */
1416 sheet_redraw_all (sheet, TRUE);
1417 return;
1419 handle_error:
1420 if (pundo) {
1421 go_undo_undo_with_data (*pundo, cc);
1422 g_object_unref (*pundo);
1423 *pundo = NULL;
1428 * gnm_sheet_resize:
1429 * @sheet: #Sheet
1430 * @cols: the new columns number.
1431 * @rows: the new rows number.
1432 * @cc: #GOCmdContext.
1433 * @perr: will be %TRUE on error.
1435 * Returns: (transfer full): the newly allocated #GOUndo.
1437 GOUndo *
1438 gnm_sheet_resize (Sheet *sheet, int cols, int rows,
1439 GOCmdContext *cc, gboolean *perr)
1441 GOUndo *undo = NULL;
1443 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1444 g_return_val_if_fail (gnm_sheet_valid_size (cols, rows), NULL);
1446 if (cols < sheet->size.max_cols || rows < sheet->size.max_rows) {
1447 GSList *overlap, *l;
1448 gboolean bad = FALSE;
1449 GnmRange r;
1451 r.start.col = r.start.row = 0;
1452 r.end.col = MIN (cols, sheet->size.max_cols) - 1;
1453 r.end.row = MIN (rows, sheet->size.max_rows) - 1;
1455 overlap = gnm_sheet_merge_get_overlap (sheet, &r);
1456 for (l = overlap; l && !bad; l = l->next) {
1457 GnmRange const *m = l->data;
1458 if (!range_contained (m, &r)) {
1459 bad = TRUE;
1460 gnm_cmd_context_error_splits_merge (cc, m);
1463 g_slist_free (overlap);
1464 if (bad) {
1465 *perr = TRUE;
1466 return NULL;
1470 gnm_sheet_resize_main (sheet, cols, rows, cc, &undo);
1472 *perr = FALSE;
1473 return undo;
1478 * sheet_new_with_type:
1479 * @wb: #Workbook
1480 * @name: An unquoted name
1481 * @type: @GnmSheetType
1482 * @columns: The number of columns for the sheet
1483 * @rows: The number of rows for the sheet
1485 * Create a new Sheet of type @type, and associate it with @wb.
1486 * The type cannot be changed later.
1487 * Returns: (transfer full): the newly allocated sheet.
1489 Sheet *
1490 sheet_new_with_type (Workbook *wb, char const *name, GnmSheetType type,
1491 int columns, int rows)
1493 Sheet *sheet;
1495 g_return_val_if_fail (wb != NULL, NULL);
1496 g_return_val_if_fail (name != NULL, NULL);
1497 g_return_val_if_fail (gnm_sheet_valid_size (columns, rows), NULL);
1499 sheet = g_object_new (GNM_SHEET_TYPE,
1500 "workbook", wb,
1501 "sheet-type", type,
1502 "columns", columns,
1503 "rows", rows,
1504 "name", name,
1505 "zoom-factor", gnm_conf_get_core_gui_window_zoom (),
1506 NULL);
1508 if (type == GNM_SHEET_OBJECT)
1509 print_info_set_paper_orientation (sheet->print_info, GTK_PAGE_ORIENTATION_LANDSCAPE);
1511 return sheet;
1515 * sheet_new:
1516 * @wb: #Workbook
1517 * @name: The name for the sheet (unquoted).
1518 * @columns: The requested columns number.
1519 * @rows: The requested rows number.
1521 * Create a new Sheet of type SHEET_DATA, and associate it with @wb.
1522 * The type can not be changed later
1523 * Returns: (transfer full): the newly allocated sheet.
1525 Sheet *
1526 sheet_new (Workbook *wb, char const *name, int columns, int rows)
1528 return sheet_new_with_type (wb, name, GNM_SHEET_DATA, columns, rows);
1531 /****************************************************************************/
1533 void
1534 sheet_redraw_all (Sheet const *sheet, gboolean headers)
1536 /* We potentially do a lot of recalcs as part of this, so make sure
1537 stuff that caches sub-computations see the whole thing instead
1538 of clearing between cells. */
1539 gnm_app_recalc_start ();
1540 SHEET_FOREACH_CONTROL (sheet, view, control,
1541 sc_redraw_all (control, headers););
1542 gnm_app_recalc_finish ();
1545 static GnmValue *
1546 cb_clear_rendered_values (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
1548 gnm_cell_unrender (iter->cell);
1549 return NULL;
1553 * sheet_range_calc_spans:
1554 * @sheet: The sheet,
1555 * @r: the region to update.
1556 * @flags:
1558 * This is used to re-calculate cell dimensions and re-render
1559 * a cell's text. eg. if a format has changed we need to re-render
1560 * the cached version of the rendered text in the cell.
1562 void
1563 sheet_range_calc_spans (Sheet *sheet, GnmRange const *r, GnmSpanCalcFlags flags)
1565 if (flags & GNM_SPANCALC_RE_RENDER)
1566 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_NONEXISTENT,
1567 r->start.col, r->start.row, r->end.col, r->end.row,
1568 cb_clear_rendered_values, NULL);
1569 sheet_queue_respan (sheet, r->start.row, r->end.row);
1571 /* Redraw the new region in case the span changes */
1572 sheet_redraw_range (sheet, r);
1575 static void
1576 sheet_redraw_partial_row (Sheet const *sheet, int const row,
1577 int const start_col, int const end_col)
1579 GnmRange r;
1580 range_init (&r, start_col, row, end_col, row);
1581 SHEET_FOREACH_CONTROL (sheet, view, control,
1582 sc_redraw_range (control, &r););
1585 static void
1586 sheet_redraw_cell (GnmCell const *cell)
1588 CellSpanInfo const * span;
1589 int start_col, end_col, row;
1590 GnmRange const *merged;
1591 Sheet *sheet;
1592 ColRowInfo *ri;
1594 g_return_if_fail (cell != NULL);
1596 sheet = cell->base.sheet;
1597 merged = gnm_sheet_merge_is_corner (sheet, &cell->pos);
1598 if (merged != NULL) {
1599 SHEET_FOREACH_CONTROL (sheet, view, control,
1600 sc_redraw_range (control, merged););
1601 return;
1604 row = cell->pos.row;
1605 start_col = end_col = cell->pos.col;
1606 ri = sheet_row_get (sheet, row);
1607 span = row_span_get (ri, start_col);
1609 if (span) {
1610 start_col = span->left;
1611 end_col = span->right;
1614 sheet_redraw_partial_row (sheet, row, start_col, end_col);
1617 static void
1618 sheet_cell_calc_span (GnmCell *cell, GnmSpanCalcFlags flags)
1620 CellSpanInfo const * span;
1621 int left, right;
1622 int min_col, max_col, row;
1623 gboolean render = (flags & GNM_SPANCALC_RE_RENDER) != 0;
1624 gboolean const resize = (flags & GNM_SPANCALC_RESIZE) != 0;
1625 gboolean existing = FALSE;
1626 GnmRange const *merged;
1627 Sheet *sheet;
1628 ColRowInfo *ri;
1630 g_return_if_fail (cell != NULL);
1632 sheet = cell->base.sheet;
1633 row = cell->pos.row;
1635 /* Render & Size any unrendered cells */
1636 if ((flags & GNM_SPANCALC_RENDER) && gnm_cell_get_rendered_value (cell) == NULL)
1637 render = TRUE;
1639 if (render) {
1640 if (!gnm_cell_has_expr (cell))
1641 gnm_cell_render_value ((GnmCell *)cell, TRUE);
1642 else
1643 gnm_cell_unrender (cell);
1644 } else if (resize) {
1645 /* FIXME: what was wanted here? */
1646 /* rendered_value_calc_size (cell); */
1649 /* Is there an existing span ? clear it BEFORE calculating new one */
1650 ri = sheet_row_get (sheet, row);
1651 span = row_span_get (ri, cell->pos.col);
1652 if (span != NULL) {
1653 GnmCell const * const other = span->cell;
1655 min_col = span->left;
1656 max_col = span->right;
1658 /* A different cell used to span into this cell, respan that */
1659 if (cell != other) {
1660 int other_left, other_right;
1662 cell_unregister_span (other);
1663 cell_calc_span (other, &other_left, &other_right);
1664 if (min_col > other_left)
1665 min_col = other_left;
1666 if (max_col < other_right)
1667 max_col = other_right;
1669 if (other_left != other_right)
1670 cell_register_span (other, other_left, other_right);
1671 } else
1672 existing = TRUE;
1673 } else
1674 min_col = max_col = cell->pos.col;
1676 merged = gnm_sheet_merge_is_corner (sheet, &cell->pos);
1677 if (NULL != merged) {
1678 if (existing) {
1679 if (min_col > merged->start.col)
1680 min_col = merged->start.col;
1681 if (max_col < merged->end.col)
1682 max_col = merged->end.col;
1683 } else {
1684 sheet_redraw_cell (cell);
1685 return;
1687 } else {
1688 /* Calculate the span of the cell */
1689 cell_calc_span (cell, &left, &right);
1690 if (min_col > left)
1691 min_col = left;
1692 if (max_col < right)
1693 max_col = right;
1695 /* This cell already had an existing span */
1696 if (existing) {
1697 /* If it changed, remove the old one */
1698 if (left != span->left || right != span->right)
1699 cell_unregister_span (cell);
1700 else
1701 /* unchanged, short curcuit adding the span again */
1702 left = right;
1705 if (left != right)
1706 cell_register_span (cell, left, right);
1709 sheet_redraw_partial_row (sheet, row, min_col, max_col);
1713 * sheet_apply_style: (skip)
1714 * @sheet: the sheet in which can be found
1715 * @range: the range to which should be applied
1716 * @style: (transfer full): A #GnmStyle partial style
1718 * A mid level routine that applies the supplied partial style @style to the
1719 * target @range and performs the necessary respanning and redrawing.
1721 void
1722 sheet_apply_style (Sheet *sheet,
1723 GnmRange const *range,
1724 GnmStyle *style)
1726 GnmSpanCalcFlags spanflags = gnm_style_required_spanflags (style);
1727 sheet_style_apply_range (sheet, range, style);
1728 /* This also redraws the range: */
1729 sheet_range_calc_spans (sheet, range, spanflags);
1733 * sheet_apply_style_gi: (rename-to sheet_apply_style)
1734 * @sheet: the sheet in which can be found
1735 * @range: the range to which should be applied
1736 * @style: A #GnmStyle partial style
1738 * A mid level routine that applies the supplied partial style @style to the
1739 * target @range and performs the necessary respanning and redrawing.
1741 void
1742 sheet_apply_style_gi (Sheet *sheet, GnmRange const *range, GnmStyle *style)
1744 GnmSpanCalcFlags spanflags = gnm_style_required_spanflags (style);
1745 gnm_style_ref (style);
1746 sheet_style_apply_range (sheet, range, style);
1747 /* This also redraws the range: */
1748 sheet_range_calc_spans (sheet, range, spanflags);
1751 static void
1752 sheet_apply_style_cb (GnmSheetRange *sr,
1753 GnmStyle *style)
1755 gnm_style_ref (style);
1756 sheet_apply_style (sr->sheet, &sr->range, style);
1757 sheet_flag_style_update_range (sr->sheet, &sr->range);
1761 * sheet_apply_style_undo:
1762 * @sr: #GnmSheetRange
1763 * @style: #GnmStyle
1765 * Returns: (transfer full): the new #GOUndo.
1767 GOUndo *
1768 sheet_apply_style_undo (GnmSheetRange *sr,
1769 GnmStyle *style)
1771 gnm_style_ref (style);
1772 return go_undo_binary_new
1773 (sr, (gpointer)style,
1774 (GOUndoBinaryFunc) sheet_apply_style_cb,
1775 (GFreeFunc) gnm_sheet_range_free,
1776 (GFreeFunc) gnm_style_unref);
1781 void
1782 sheet_apply_border (Sheet *sheet,
1783 GnmRange const *range,
1784 GnmBorder **borders)
1786 GnmSpanCalcFlags spanflags = GNM_SPANCALC_RE_RENDER | GNM_SPANCALC_RESIZE;
1787 sheet_style_apply_border (sheet, range, borders);
1788 /* This also redraws the range: */
1789 sheet_range_calc_spans (sheet, range, spanflags);
1792 /****************************************************************************/
1794 static ColRowInfo *
1795 sheet_row_new (Sheet *sheet)
1797 ColRowInfo *ri;
1799 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1801 ri = col_row_info_new ();
1802 *ri = sheet->rows.default_style;
1803 ri->is_default = FALSE;
1804 ri->needs_respan = TRUE;
1806 return ri;
1809 static ColRowInfo *
1810 sheet_col_new (Sheet *sheet)
1812 ColRowInfo *ci;
1814 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1816 ci = col_row_info_new ();
1817 *ci = sheet->cols.default_style;
1818 ci->is_default = FALSE;
1820 return ci;
1823 static void
1824 sheet_colrow_add (Sheet *sheet, ColRowInfo *cp, gboolean is_cols, int n)
1826 ColRowCollection *info = is_cols ? &sheet->cols : &sheet->rows;
1827 ColRowSegment **psegment = (ColRowSegment **)&COLROW_GET_SEGMENT (info, n);
1829 g_return_if_fail (n >= 0);
1830 g_return_if_fail (n < colrow_max (is_cols, sheet));
1832 if (*psegment == NULL)
1833 *psegment = g_new0 (ColRowSegment, 1);
1834 colrow_free ((*psegment)->info[COLROW_SUB_INDEX (n)]);
1835 (*psegment)->info[COLROW_SUB_INDEX (n)] = cp;
1837 if (cp->outline_level > info->max_outline_level)
1838 info->max_outline_level = cp->outline_level;
1839 if (n > info->max_used) {
1840 info->max_used = n;
1841 sheet->priv->resize_scrollbar = TRUE;
1845 static void
1846 sheet_reposition_objects (Sheet const *sheet, GnmCellPos const *pos)
1848 GSList *ptr;
1849 for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = ptr->next )
1850 sheet_object_update_bounds (GNM_SO (ptr->data), pos);
1854 * sheet_flag_status_update_cell:
1855 * @cell: The cell that has changed.
1857 * flag the sheet as requiring an update to the status display
1858 * if the supplied cell location is the edit cursor, or part of the
1859 * selected region.
1861 * Will cause the format toolbar, the edit area, and the auto expressions to be
1862 * updated if appropriate.
1864 void
1865 sheet_flag_status_update_cell (GnmCell const *cell)
1867 SHEET_FOREACH_VIEW (cell->base.sheet, sv,
1868 sv_flag_status_update_pos (sv, &cell->pos););
1872 * sheet_flag_status_update_range:
1873 * @sheet:
1874 * @range: If NULL then force an update.
1876 * flag the sheet as requiring an update to the status display
1877 * if the supplied cell location contains the edit cursor, or intersects of
1878 * the selected region.
1880 * Will cause the format toolbar, the edit area, and the auto expressions to be
1881 * updated if appropriate.
1883 void
1884 sheet_flag_status_update_range (Sheet const *sheet, GnmRange const *range)
1886 SHEET_FOREACH_VIEW (sheet, sv,
1887 sv_flag_status_update_range (sv, range););
1891 * sheet_flag_style_update_range:
1892 * @sheet: The sheet being changed
1893 * @range: the range that is changing.
1895 * Flag format changes that will require updating the format indicators.
1897 void
1898 sheet_flag_style_update_range (Sheet const *sheet, GnmRange const *range)
1900 SHEET_FOREACH_VIEW (sheet, sv,
1901 sv_flag_style_update_range (sv, range););
1905 * sheet_flag_recompute_spans:
1906 * @sheet:
1908 * Flag the sheet as requiring a full span recomputation the next time
1909 * sheet_update is called.
1911 void
1912 sheet_flag_recompute_spans (Sheet const *sheet)
1914 sheet->priv->recompute_spans = TRUE;
1917 static gboolean
1918 cb_outline_level (GnmColRowIter const *iter, int *outline_level)
1920 if (*outline_level < iter->cri->outline_level)
1921 *outline_level = iter->cri->outline_level;
1922 return FALSE;
1926 * sheet_colrow_fit_gutter:
1927 * @sheet: Sheet to change for.
1928 * @is_cols: Column gutter or row gutter?
1930 * Find the current max outline level.
1932 static int
1933 sheet_colrow_fit_gutter (Sheet const *sheet, gboolean is_cols)
1935 int outline_level = 0;
1936 col_row_collection_foreach (is_cols ? &sheet->cols : &sheet->rows,
1937 0, colrow_max (is_cols, sheet) - 1,
1938 (ColRowHandler)cb_outline_level, &outline_level);
1939 return outline_level;
1943 * sheet_update_only_grid:
1944 * @sheet: #Sheet
1946 * Should be called after a logical command has finished processing
1947 * to request redraws for any pending events
1949 void
1950 sheet_update_only_grid (Sheet const *sheet)
1952 SheetPrivate *p;
1954 g_return_if_fail (IS_SHEET (sheet));
1956 p = sheet->priv;
1958 /* be careful these can toggle flags */
1959 if (p->recompute_max_col_group) {
1960 sheet_colrow_gutter ((Sheet *)sheet, TRUE,
1961 sheet_colrow_fit_gutter (sheet, TRUE));
1962 sheet->priv->recompute_max_col_group = FALSE;
1964 if (p->recompute_max_row_group) {
1965 sheet_colrow_gutter ((Sheet *)sheet, FALSE,
1966 sheet_colrow_fit_gutter (sheet, FALSE));
1967 sheet->priv->recompute_max_row_group = FALSE;
1970 SHEET_FOREACH_VIEW (sheet, sv, {
1971 if (sv->reposition_selection) {
1972 sv->reposition_selection = FALSE;
1974 /* when moving we cleared the selection before
1975 * arriving in here.
1977 if (sv->selections != NULL)
1978 sv_selection_set (sv, &sv->edit_pos_real,
1979 sv->cursor.base_corner.col,
1980 sv->cursor.base_corner.row,
1981 sv->cursor.move_corner.col,
1982 sv->cursor.move_corner.row);
1986 if (p->recompute_spans) {
1987 p->recompute_spans = FALSE;
1988 /* FIXME : I would prefer to use GNM_SPANCALC_RENDER rather than
1989 * RE_RENDER. It only renders those cells which are not
1990 * rendered. The trouble is that when a col changes size we
1991 * need to rerender, but currently nothing marks that.
1993 * hmm, that suggests an approach. maybe I can install a per
1994 * col flag. Then add a flag clearing loop after the
1995 * sheet_calc_span.
1997 #if 0
1998 sheet_calc_spans (sheet, GNM_SPANCALC_RESIZE|GNM_SPANCALC_RE_RENDER |
1999 (p->recompute_visibility ?
2000 SPANCALC_NO_DRAW : GNM_SPANCALC_SIMPLE));
2001 #endif
2002 sheet_queue_respan (sheet, 0, gnm_sheet_get_last_row (sheet));
2005 if (p->reposition_objects.row < gnm_sheet_get_max_rows (sheet) ||
2006 p->reposition_objects.col < gnm_sheet_get_max_cols (sheet)) {
2007 SHEET_FOREACH_VIEW (sheet, sv, {
2008 if (!p->resize && sv_is_frozen (sv)) {
2009 if (p->reposition_objects.col < sv->unfrozen_top_left.col ||
2010 p->reposition_objects.row < sv->unfrozen_top_left.row) {
2011 SHEET_VIEW_FOREACH_CONTROL(sv, control,
2012 sc_resize (control, FALSE););
2016 sheet_reposition_objects (sheet, &p->reposition_objects);
2017 p->reposition_objects.row = gnm_sheet_get_max_rows (sheet);
2018 p->reposition_objects.col = gnm_sheet_get_max_cols (sheet);
2021 if (p->resize) {
2022 p->resize = FALSE;
2023 SHEET_FOREACH_CONTROL (sheet, sv, control, sc_resize (control, FALSE););
2026 if (p->recompute_visibility) {
2027 /* TODO : There is room for some opimization
2028 * We only need to force complete visibility recalculation
2029 * (which we do in sheet_compute_visible_region)
2030 * if a row or col before the start of the visible region.
2031 * If we are REALLY smart we could even accumulate the size differential
2032 * and use that.
2034 p->recompute_visibility = FALSE;
2035 p->resize_scrollbar = FALSE; /* compute_visible_region does this */
2036 SHEET_FOREACH_CONTROL(sheet, view, control,
2037 sc_recompute_visible_region (control, TRUE););
2038 sheet_redraw_all (sheet, TRUE);
2041 if (p->resize_scrollbar) {
2042 sheet_scrollbar_config (sheet);
2043 p->resize_scrollbar = FALSE;
2045 if (p->filters_changed) {
2046 p->filters_changed = FALSE;
2047 SHEET_FOREACH_CONTROL (sheet, sv, sc,
2048 wb_control_menu_state_update (sc_wbc (sc), MS_ADD_VS_REMOVE_FILTER););
2053 * sheet_update:
2054 * @sheet: #Sheet
2056 * Should be called after a logical command has finished processing to request
2057 * redraws for any pending events, and to update the various status regions
2059 void
2060 sheet_update (Sheet const *sheet)
2062 g_return_if_fail (IS_SHEET (sheet));
2064 sheet_update_only_grid (sheet);
2066 SHEET_FOREACH_VIEW (sheet, sv, sv_update (sv););
2070 * sheet_cell_get:
2071 * @sheet: The sheet where we want to locate the cell
2072 * @col: the cell column
2073 * @row: the cell row
2075 * Return value: (nullable): a #GnmCell, or %NULL if the cell does not exist
2077 GnmCell *
2078 sheet_cell_get (Sheet const *sheet, int col, int row)
2080 GnmCell *cell;
2081 GnmCell key;
2083 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2085 key.pos.col = col;
2086 key.pos.row = row;
2087 cell = g_hash_table_lookup (sheet->cell_hash, &key);
2089 return cell;
2093 * sheet_cell_fetch:
2094 * @sheet: The sheet where we want to locate the cell
2095 * @col: the cell column
2096 * @row: the cell row
2098 * Return value: a #GnmCell containing at (@col,@row).
2099 * If no cell existed at that location before, it is created.
2101 GnmCell *
2102 sheet_cell_fetch (Sheet *sheet, int col, int row)
2104 GnmCell *cell;
2106 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2108 cell = sheet_cell_get (sheet, col, row);
2109 if (!cell)
2110 cell = sheet_cell_create (sheet, col, row);
2112 return cell;
2116 * sheet_colrow_can_group:
2117 * @sheet: #Sheet
2118 * @r: A #GnmRange
2119 * @is_cols: boolean
2121 * Returns TRUE if the cols/rows in @r.start -> @r.end can be grouped, return
2122 * FALSE otherwise. You can invert the result if you need to find out if a
2123 * group can be ungrouped.
2125 gboolean
2126 sheet_colrow_can_group (Sheet *sheet, GnmRange const *r, gboolean is_cols)
2128 ColRowInfo const *start_cri, *end_cri;
2129 int start, end;
2131 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
2133 if (is_cols) {
2134 start = r->start.col;
2135 end = r->end.col;
2136 } else {
2137 start = r->start.row;
2138 end = r->end.row;
2140 start_cri = sheet_colrow_fetch (sheet, start, is_cols);
2141 end_cri = sheet_colrow_fetch (sheet, end, is_cols);
2143 /* Groups on outline level 0 (no outline) may always be formed */
2144 if (start_cri->outline_level == 0 || end_cri->outline_level == 0)
2145 return TRUE;
2147 /* We just won't group a group that already exists (or doesn't), it's useless */
2148 return (colrow_find_outline_bound (sheet, is_cols, start, start_cri->outline_level, FALSE) != start ||
2149 colrow_find_outline_bound (sheet, is_cols, end, end_cri->outline_level, TRUE) != end);
2152 gboolean
2153 sheet_colrow_group_ungroup (Sheet *sheet, GnmRange const *r,
2154 gboolean is_cols, gboolean group)
2156 int i, new_max, start, end;
2157 int const step = group ? 1 : -1;
2159 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
2161 /* Can we group/ungroup ? */
2162 if (group != sheet_colrow_can_group (sheet, r, is_cols))
2163 return FALSE;
2165 if (is_cols) {
2166 start = r->start.col;
2167 end = r->end.col;
2168 } else {
2169 start = r->start.row;
2170 end = r->end.row;
2173 /* Set new outline for each col/row and find highest outline level */
2174 new_max = (is_cols ? &sheet->cols : &sheet->rows)->max_outline_level;
2175 for (i = start; i <= end; i++) {
2176 ColRowInfo *cri = sheet_colrow_fetch (sheet, i, is_cols);
2177 int const new_level = cri->outline_level + step;
2179 if (new_level >= 0) {
2180 col_row_info_set_outline (cri, new_level, FALSE);
2181 if (new_max < new_level)
2182 new_max = new_level;
2186 if (!group)
2187 new_max = sheet_colrow_fit_gutter (sheet, is_cols);
2189 sheet_colrow_gutter (sheet, is_cols, new_max);
2190 SHEET_FOREACH_VIEW (sheet, sv,
2191 sv_redraw_headers (sv, is_cols, !is_cols, NULL););
2193 return TRUE;
2197 * sheet_colrow_gutter:
2198 * @sheet:
2199 * @is_cols:
2200 * @max_outline:
2202 * Set the maximum outline levels for cols or rows.
2204 void
2205 sheet_colrow_gutter (Sheet *sheet, gboolean is_cols, int max_outline)
2207 ColRowCollection *infos;
2209 g_return_if_fail (IS_SHEET (sheet));
2211 infos = is_cols ? &(sheet->cols) : &(sheet->rows);
2212 if (infos->max_outline_level != max_outline) {
2213 sheet->priv->resize = TRUE;
2214 infos->max_outline_level = max_outline;
2218 struct sheet_extent_data {
2219 GnmRange range;
2220 gboolean spans_and_merges_extend;
2221 gboolean ignore_empties;
2222 gboolean include_hidden;
2225 static void
2226 cb_sheet_get_extent (G_GNUC_UNUSED gpointer ignored, gpointer value, gpointer data)
2228 GnmCell const *cell = (GnmCell const *) value;
2229 struct sheet_extent_data *res = data;
2230 Sheet *sheet = cell->base.sheet;
2231 ColRowInfo *ri = NULL;
2233 if (res->ignore_empties && gnm_cell_is_empty (cell))
2234 return;
2235 if (!res->include_hidden) {
2236 ri = sheet_col_get (sheet, cell->pos.col);
2237 if (!ri->visible)
2238 return;
2239 ri = sheet_row_get (sheet, cell->pos.row);
2240 if (!ri->visible)
2241 return;
2244 /* Remember the first cell is the min & max */
2245 if (res->range.start.col > cell->pos.col)
2246 res->range.start.col = cell->pos.col;
2247 if (res->range.end.col < cell->pos.col)
2248 res->range.end.col = cell->pos.col;
2249 if (res->range.start.row > cell->pos.row)
2250 res->range.start.row = cell->pos.row;
2251 if (res->range.end.row < cell->pos.row)
2252 res->range.end.row = cell->pos.row;
2254 if (!res->spans_and_merges_extend)
2255 return;
2257 /* Cannot span AND merge */
2258 if (gnm_cell_is_merged (cell)) {
2259 GnmRange const *merged =
2260 gnm_sheet_merge_is_corner (sheet, &cell->pos);
2261 res->range = range_union (&res->range, merged);
2262 } else {
2263 CellSpanInfo const *span;
2264 if (ri == NULL)
2265 ri = sheet_row_get (sheet, cell->pos.row);
2266 if (ri->needs_respan)
2267 row_calc_spans (ri, cell->pos.row, sheet);
2268 span = row_span_get (ri, cell->pos.col);
2269 if (NULL != span) {
2270 if (res->range.start.col > span->left)
2271 res->range.start.col = span->left;
2272 if (res->range.end.col < span->right)
2273 res->range.end.col = span->right;
2279 * sheet_get_extent:
2280 * @sheet: the sheet
2281 * @spans_and_merges_extend: optionally extend region for spans and merges.
2282 * @include_hidden: whether to include the content of hidden cells.
2284 * calculates the area occupied by cell data.
2286 * NOTE: When spans_and_merges_extend is TRUE, this function will calculate
2287 * all spans. That might be expensive.
2289 * NOTE: This refers to *visible* contents. Cells with empty values, including
2290 * formulas with such values, are *ignored.
2292 * Return value: the range.
2294 GnmRange
2295 sheet_get_extent (Sheet const *sheet, gboolean spans_and_merges_extend, gboolean include_hidden)
2297 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2298 struct sheet_extent_data closure;
2299 GSList *ptr;
2301 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2303 closure.range.start.col = gnm_sheet_get_last_col (sheet) + 1;
2304 closure.range.start.row = gnm_sheet_get_last_row (sheet) + 1;
2305 closure.range.end.col = 0;
2306 closure.range.end.row = 0;
2307 closure.spans_and_merges_extend = spans_and_merges_extend;
2308 closure.include_hidden = include_hidden;
2309 closure.ignore_empties = TRUE;
2311 sheet_cell_foreach (sheet, &cb_sheet_get_extent, &closure);
2313 for (ptr = sheet->sheet_objects; ptr; ptr = ptr->next) {
2314 SheetObject *so = GNM_SO (ptr->data);
2316 closure.range.start.col = MIN (so->anchor.cell_bound.start.col,
2317 closure.range.start.col);
2318 closure.range.start.row = MIN (so->anchor.cell_bound.start.row,
2319 closure.range.start.row);
2320 closure.range.end.col = MAX (so->anchor.cell_bound.end.col,
2321 closure.range.end.col);
2322 closure.range.end.row = MAX (so->anchor.cell_bound.end.row,
2323 closure.range.end.row);
2326 if (closure.range.start.col > gnm_sheet_get_last_col (sheet))
2327 closure.range.start.col = 0;
2328 if (closure.range.start.row > gnm_sheet_get_last_row (sheet))
2329 closure.range.start.row = 0;
2330 if (closure.range.end.col < 0)
2331 closure.range.end.col = 0;
2332 if (closure.range.end.row < 0)
2333 closure.range.end.row = 0;
2335 return closure.range;
2339 * sheet_get_cells_extent:
2340 * @sheet: the sheet
2342 * calculates the area occupied by cells, including empty cells.
2344 * Return value: the range.
2346 GnmRange
2347 sheet_get_cells_extent (Sheet const *sheet)
2349 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2350 struct sheet_extent_data closure;
2352 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2354 closure.range.start.col = gnm_sheet_get_last_col (sheet);
2355 closure.range.start.row = gnm_sheet_get_last_row (sheet);
2356 closure.range.end.col = 0;
2357 closure.range.end.row = 0;
2358 closure.spans_and_merges_extend = FALSE;
2359 closure.include_hidden = TRUE;
2360 closure.ignore_empties = FALSE;
2362 sheet_cell_foreach (sheet, &cb_sheet_get_extent, &closure);
2364 return closure.range;
2368 GnmRange *
2369 sheet_get_nominal_printarea (Sheet const *sheet)
2371 GnmNamedExpr *nexpr;
2372 GnmValue *val;
2373 GnmParsePos pos;
2374 GnmRange *r;
2375 GnmRangeRef const *r_ref;
2376 gint max_rows;
2377 gint max_cols;
2379 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2381 parse_pos_init_sheet (&pos, sheet);
2382 nexpr = expr_name_lookup (&pos, "Print_Area");
2383 if (nexpr == NULL)
2384 return NULL;
2386 val = gnm_expr_top_get_range (nexpr->texpr);
2387 r_ref = val ? value_get_rangeref (val) : NULL;
2388 if (r_ref == NULL) {
2389 value_release (val);
2390 return NULL;
2393 r = g_new0 (GnmRange, 1);
2394 range_init_rangeref (r, r_ref);
2395 value_release (val);
2397 if (r->end.col >= (max_cols = gnm_sheet_get_max_cols (sheet)))
2398 r->end.col = max_cols - 1;
2399 if (r->end.row >= (max_rows = gnm_sheet_get_max_rows (sheet)))
2400 r->end.row = max_rows - 1;
2401 if (r->start.col < 0)
2402 r->start.col = 0;
2403 if (r->start.row < 0)
2404 r->start.row = 0;
2406 return r;
2409 GnmRange
2410 sheet_get_printarea (Sheet const *sheet,
2411 gboolean include_styles,
2412 gboolean ignore_printarea)
2414 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2415 GnmRange print_area;
2417 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2419 if (!ignore_printarea) {
2420 GnmRange *r = sheet_get_nominal_printarea (sheet);
2421 if (r != NULL) {
2422 print_area = *r;
2423 g_free (r);
2424 return print_area;
2428 print_area = sheet_get_extent (sheet, TRUE, FALSE);
2429 if (include_styles)
2430 sheet_style_get_extent (sheet, &print_area);
2432 return print_area;
2435 struct cb_fit {
2436 int max;
2437 gboolean ignore_strings;
2440 /* find the maximum width in a range. */
2441 static GnmValue *
2442 cb_max_cell_width (GnmCellIter const *iter, struct cb_fit *data)
2444 int width;
2445 GnmCell *cell = iter->cell;
2446 GnmRenderedValue *rv;
2448 if (gnm_cell_is_merged (cell))
2449 return NULL;
2452 * Special handling for manual recalc. We need to eval newly
2453 * entered expressions. gnm_cell_render_value will do that for us,
2454 * but we want to short-circuit some strings early.
2456 if (cell->base.flags & GNM_CELL_HAS_NEW_EXPR)
2457 gnm_cell_eval (cell);
2459 if (data->ignore_strings && VALUE_IS_STRING (cell->value))
2460 return NULL;
2462 /* Variable width cell must be re-rendered */
2463 rv = gnm_cell_get_rendered_value (cell);
2464 if (rv == NULL || rv->variable_width)
2465 gnm_cell_render_value (cell, FALSE);
2467 /* Make sure things are as-if drawn. */
2468 cell_finish_layout (cell, NULL, iter->ci->size_pixels, TRUE);
2470 width = gnm_cell_rendered_width (cell) + gnm_cell_rendered_offset (cell);
2471 if (width > data->max)
2472 data->max = width;
2474 return NULL;
2478 * sheet_col_size_fit_pixels:
2479 * @sheet: The sheet
2480 * @col: the column that we want to query
2481 * @srow: starting row.
2482 * @erow: ending row.
2483 * @ignore_strings: skip cells containing string values.
2485 * This routine computes the ideal size for the column to make the contents all
2486 * cells in the column visible.
2488 * Returns: Maximum size in pixels INCLUDING margins and grid lines
2489 * or 0 if there are no cells.
2492 sheet_col_size_fit_pixels (Sheet *sheet, int col, int srow, int erow,
2493 gboolean ignore_strings)
2495 struct cb_fit data;
2496 ColRowInfo *ci = sheet_col_get (sheet, col);
2497 if (ci == NULL)
2498 return 0;
2500 data.max = -1;
2501 data.ignore_strings = ignore_strings;
2502 sheet_foreach_cell_in_range (sheet,
2503 CELL_ITER_IGNORE_NONEXISTENT |
2504 CELL_ITER_IGNORE_HIDDEN |
2505 CELL_ITER_IGNORE_FILTERED,
2506 col, srow, col, erow,
2507 (CellIterFunc)&cb_max_cell_width, &data);
2509 /* Reset to the default width if the column was empty */
2510 if (data.max <= 0)
2511 return 0;
2513 /* GnmCell width does not include margins or far grid line*/
2514 return data.max + GNM_COL_MARGIN + GNM_COL_MARGIN + 1;
2517 /* find the maximum height in a range. */
2518 static GnmValue *
2519 cb_max_cell_height (GnmCellIter const *iter, struct cb_fit *data)
2521 int height;
2522 GnmCell *cell = iter->cell;
2524 if (gnm_cell_is_merged (cell))
2525 return NULL;
2528 * Special handling for manual recalc. We need to eval newly
2529 * entered expressions. gnm_cell_render_value will do that for us,
2530 * but we want to short-circuit some strings early.
2532 if (cell->base.flags & GNM_CELL_HAS_NEW_EXPR)
2533 gnm_cell_eval (cell);
2535 if (data->ignore_strings && VALUE_IS_STRING (cell->value))
2536 return NULL;
2538 if (!VALUE_IS_STRING (cell->value)) {
2540 * Mildly cheating to avoid performance problems, See bug
2541 * 359392. This assumes that non-strings do not wrap and
2542 * that they are all the same height, more or less.
2544 Sheet const *sheet = cell->base.sheet;
2545 height = gnm_style_get_pango_height (gnm_cell_get_style (cell),
2546 sheet->rendered_values->context,
2547 sheet->last_zoom_factor_used);
2548 } else {
2549 (void)gnm_cell_fetch_rendered_value (cell, TRUE);
2551 /* Make sure things are as-if drawn. Inhibit #####s. */
2552 cell_finish_layout (cell, NULL, iter->ci->size_pixels, FALSE);
2554 height = gnm_cell_rendered_height (cell);
2557 if (height > data->max)
2558 data->max = height;
2560 return NULL;
2564 * sheet_row_size_fit_pixels:
2565 * @sheet: The sheet
2566 * @row: the row that we want to query
2567 * @scol: starting column.
2568 * @ecol: ending column.
2569 * @ignore_strings: skip cells containing string values.
2571 * This routine computes the ideal size for the row to make all data fit
2572 * properly.
2574 * Returns: Maximum size in pixels INCLUDING margins and grid lines
2575 * or 0 if there are no cells.
2578 sheet_row_size_fit_pixels (Sheet *sheet, int row, int scol, int ecol,
2579 gboolean ignore_strings)
2581 struct cb_fit data;
2582 ColRowInfo const *ri = sheet_row_get (sheet, row);
2583 if (ri == NULL)
2584 return 0;
2586 data.max = -1;
2587 data.ignore_strings = ignore_strings;
2588 sheet_foreach_cell_in_range (sheet,
2589 CELL_ITER_IGNORE_NONEXISTENT |
2590 CELL_ITER_IGNORE_HIDDEN |
2591 CELL_ITER_IGNORE_FILTERED,
2592 scol, row,
2593 ecol, row,
2594 (CellIterFunc)&cb_max_cell_height, &data);
2596 /* Reset to the default width if the column was empty */
2597 if (data.max <= 0)
2598 return 0;
2600 /* GnmCell height does not include margins or bottom grid line */
2601 return data.max + GNM_ROW_MARGIN + GNM_ROW_MARGIN + 1;
2604 struct recalc_span_closure {
2605 Sheet *sheet;
2606 int col;
2609 static gboolean
2610 cb_recalc_spans_in_col (GnmColRowIter const *iter, gpointer user)
2612 struct recalc_span_closure *closure = user;
2613 int const col = closure->col;
2614 int left, right;
2615 CellSpanInfo const *span = row_span_get (iter->cri, col);
2617 if (span) {
2618 /* If there is an existing span see if it changed */
2619 GnmCell const * const cell = span->cell;
2620 cell_calc_span (cell, &left, &right);
2621 if (left != span->left || right != span->right) {
2622 cell_unregister_span (cell);
2623 cell_register_span (cell, left, right);
2625 } else {
2626 /* If there is a cell see if it started to span */
2627 GnmCell const * const cell = sheet_cell_get (closure->sheet, col, iter->pos);
2628 if (cell) {
2629 cell_calc_span (cell, &left, &right);
2630 if (left != right)
2631 cell_register_span (cell, left, right);
2635 return FALSE;
2639 * sheet_recompute_spans_for_col:
2640 * @sheet: the sheet
2641 * @col: The column that changed
2643 * This routine recomputes the column span for the cells that touches
2644 * the column.
2646 void
2647 sheet_recompute_spans_for_col (Sheet *sheet, int col)
2649 struct recalc_span_closure closure;
2650 closure.sheet = sheet;
2651 closure.col = col;
2653 col_row_collection_foreach (&sheet->rows, 0, gnm_sheet_get_last_row (sheet),
2654 &cb_recalc_spans_in_col, &closure);
2657 /****************************************************************************/
2658 typedef struct {
2659 GnmValue *val;
2660 GnmExprTop const *texpr;
2661 GnmRange expr_bound;
2662 } closure_set_cell_value;
2664 static GnmValue *
2665 cb_set_cell_content (GnmCellIter const *iter, closure_set_cell_value *info)
2667 GnmExprTop const *texpr = info->texpr;
2668 GnmCell *cell;
2670 cell = iter->cell;
2671 if (!cell)
2672 cell = sheet_cell_create (iter->pp.sheet,
2673 iter->pp.eval.col,
2674 iter->pp.eval.row);
2677 * If we are overwriting an array, we need to clear things here
2678 * or gnm_cell_set_expr/gnm_cell_set_value will complain.
2680 if (cell->base.texpr && gnm_expr_top_is_array (cell->base.texpr))
2681 gnm_cell_cleanout (cell);
2683 if (texpr != NULL) {
2684 if (!range_contains (&info->expr_bound,
2685 iter->pp.eval.col, iter->pp.eval.row)) {
2686 GnmExprRelocateInfo rinfo;
2688 rinfo.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE;
2689 rinfo.pos = iter->pp;
2690 rinfo.origin.start = iter->pp.eval;
2691 rinfo.origin.end = iter->pp.eval;
2692 rinfo.origin_sheet = iter->pp.sheet;
2693 rinfo.target_sheet = iter->pp.sheet;
2694 rinfo.col_offset = 0;
2695 rinfo.row_offset = 0;
2696 texpr = gnm_expr_top_relocate (texpr, &rinfo, FALSE);
2699 gnm_cell_set_expr (cell, texpr);
2700 } else
2701 gnm_cell_set_value (cell, value_dup (info->val));
2702 return NULL;
2705 static GnmValue *
2706 cb_clear_non_corner (GnmCellIter const *iter, GnmRange const *merged)
2708 if (merged->start.col != iter->pp.eval.col ||
2709 merged->start.row != iter->pp.eval.row)
2710 gnm_cell_set_value (iter->cell, value_new_empty ());
2711 return NULL;
2715 * sheet_range_set_expr_cb:
2716 * @sr: #GnmSheetRange
2717 * @texpr: #GnmExprTop
2720 * Does NOT check for array division.
2722 static void
2723 sheet_range_set_expr_cb (GnmSheetRange const *sr, GnmExprTop const *texpr)
2725 closure_set_cell_value closure;
2726 GSList *merged, *ptr;
2728 g_return_if_fail (sr != NULL);
2729 g_return_if_fail (texpr != NULL);
2731 closure.texpr = texpr;
2732 gnm_expr_top_get_boundingbox (closure.texpr,
2733 sr->sheet,
2734 &closure.expr_bound);
2736 sheet_region_queue_recalc (sr->sheet, &sr->range);
2737 /* Store the parsed result creating any cells necessary */
2738 sheet_foreach_cell_in_range
2739 (sr->sheet, CELL_ITER_ALL,
2740 sr->range.start.col, sr->range.start.row,
2741 sr->range.end.col, sr->range.end.row,
2742 (CellIterFunc)&cb_set_cell_content, &closure);
2744 merged = gnm_sheet_merge_get_overlap (sr->sheet, &sr->range);
2745 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
2746 GnmRange const *tmp = ptr->data;
2747 sheet_foreach_cell_in_range
2748 (sr->sheet, CELL_ITER_IGNORE_BLANK,
2749 tmp->start.col, tmp->start.row,
2750 tmp->end.col, tmp->end.row,
2751 (CellIterFunc)&cb_clear_non_corner,
2752 (gpointer)tmp);
2754 g_slist_free (merged);
2756 sheet_region_queue_recalc (sr->sheet, &sr->range);
2757 sheet_flag_status_update_range (sr->sheet, &sr->range);
2758 sheet_queue_respan (sr->sheet, sr->range.start.row,
2759 sr->range.end.row);
2763 * sheet_range_set_expr_undo:
2764 * @sr: #GnmSheetRange
2765 * @texpr: #GnmExprTop
2767 * Returns: (transfer full): the newly created #GOUndo.
2769 GOUndo *
2770 sheet_range_set_expr_undo (GnmSheetRange *sr, GnmExprTop const *texpr)
2772 gnm_expr_top_ref (texpr);
2773 return go_undo_binary_new
2774 (sr, (gpointer)texpr,
2775 (GOUndoBinaryFunc) sheet_range_set_expr_cb,
2776 (GFreeFunc) gnm_sheet_range_free,
2777 (GFreeFunc) gnm_expr_top_unref);
2782 * sheet_range_set_text:
2783 * @pos: The position from which to parse an expression.
2784 * @r: The range to fill
2785 * @str: The text to be parsed and assigned.
2787 * Does NOT check for array division.
2788 * Does NOT redraw
2789 * Does NOT generate spans.
2791 void
2792 sheet_range_set_text (GnmParsePos const *pos, GnmRange const *r, char const *str)
2794 closure_set_cell_value closure;
2795 GSList *merged, *ptr;
2796 Sheet *sheet;
2798 g_return_if_fail (pos != NULL);
2799 g_return_if_fail (r != NULL);
2800 g_return_if_fail (str != NULL);
2802 sheet = pos->sheet;
2804 parse_text_value_or_expr (pos, str,
2805 &closure.val, &closure.texpr);
2807 if (closure.texpr)
2808 gnm_expr_top_get_boundingbox (closure.texpr,
2809 sheet,
2810 &closure.expr_bound);
2812 /* Store the parsed result creating any cells necessary */
2813 sheet_foreach_cell_in_range (sheet, CELL_ITER_ALL,
2814 r->start.col, r->start.row, r->end.col, r->end.row,
2815 (CellIterFunc)&cb_set_cell_content, &closure);
2817 merged = gnm_sheet_merge_get_overlap (sheet, r);
2818 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
2819 GnmRange const *tmp = ptr->data;
2820 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_BLANK,
2821 tmp->start.col, tmp->start.row,
2822 tmp->end.col, tmp->end.row,
2823 (CellIterFunc)&cb_clear_non_corner, (gpointer)tmp);
2825 g_slist_free (merged);
2827 sheet_region_queue_recalc (sheet, r);
2829 value_release (closure.val);
2830 if (closure.texpr)
2831 gnm_expr_top_unref (closure.texpr);
2833 sheet_flag_status_update_range (sheet, r);
2836 static void
2837 sheet_range_set_text_cb (GnmSheetRange const *sr, gchar const *text)
2839 GnmParsePos pos;
2841 pos.eval = sr->range.start;
2842 pos.sheet = sr->sheet;
2843 pos.wb = sr->sheet->workbook;
2845 sheet_range_set_text (&pos, &sr->range, text);
2846 sheet_region_queue_recalc (sr->sheet, &sr->range);
2847 sheet_flag_status_update_range (sr->sheet, &sr->range);
2848 sheet_queue_respan (sr->sheet, sr->range.start.row,
2849 sr->range.end.row);
2850 sheet_redraw_range (sr->sheet, &sr->range);
2854 * sheet_range_set_text_undo:
2855 * @sr: #GnmSheetRange
2856 * @text:
2858 * Returns: (transfer full): the newly created #GOUndo.
2860 GOUndo *
2861 sheet_range_set_text_undo (GnmSheetRange *sr,
2862 char const *text)
2864 return go_undo_binary_new
2865 (sr, g_strdup (text),
2866 (GOUndoBinaryFunc) sheet_range_set_text_cb,
2867 (GFreeFunc) gnm_sheet_range_free,
2868 (GFreeFunc) g_free);
2872 static GnmValue *
2873 cb_set_markup (GnmCellIter const *iter, PangoAttrList *markup)
2875 GnmCell *cell;
2877 cell = iter->cell;
2878 if (!cell)
2879 return NULL;
2881 if (VALUE_IS_STRING (cell->value)) {
2882 GOFormat *fmt;
2883 GnmValue *val = value_dup (cell->value);
2885 fmt = go_format_new_markup (markup, TRUE);
2886 value_set_fmt (val, fmt);
2887 go_format_unref (fmt);
2889 gnm_cell_cleanout (cell);
2890 gnm_cell_assign_value (cell, val);
2892 return NULL;
2895 static void
2896 sheet_range_set_markup_cb (GnmSheetRange const *sr, PangoAttrList *markup)
2898 sheet_foreach_cell_in_range
2899 (sr->sheet, CELL_ITER_ALL,
2900 sr->range.start.col, sr->range.start.row,
2901 sr->range.end.col, sr->range.end.row,
2902 (CellIterFunc)&cb_set_markup, markup);
2904 sheet_region_queue_recalc (sr->sheet, &sr->range);
2905 sheet_flag_status_update_range (sr->sheet, &sr->range);
2906 sheet_queue_respan (sr->sheet, sr->range.start.row,
2907 sr->range.end.row);
2911 * sheet_range_set_markup_undo:
2912 * @sr: #GnmSheetRange
2913 * @markup: #PangoAttrList
2915 * Returns: (transfer full): the newly created #GOUndo.
2917 GOUndo *
2918 sheet_range_set_markup_undo (GnmSheetRange *sr, PangoAttrList *markup)
2920 if (markup == NULL)
2921 return NULL;
2922 return go_undo_binary_new
2923 (sr, pango_attr_list_ref (markup),
2924 (GOUndoBinaryFunc) sheet_range_set_markup_cb,
2925 (GFreeFunc) gnm_sheet_range_free,
2926 (GFreeFunc) pango_attr_list_unref);
2930 * sheet_cell_get_value:
2931 * @sheet: Sheet
2932 * @col: Source column
2933 * @row: Source row
2935 * Returns: (transfer none) (nullable): the cell's current value.
2937 GnmValue const *
2938 sheet_cell_get_value (Sheet *sheet, int const col, int const row)
2940 GnmCell *cell;
2942 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2944 cell = sheet_cell_get (sheet, col, row);
2946 return cell ? cell->value : NULL;
2950 * sheet_cell_set_text:
2951 * @cell: A cell.
2952 * @str: the text to set.
2953 * @markup: (allow-none): an optional PangoAttrList.
2955 * Marks the sheet as dirty
2956 * Clears old spans.
2957 * Flags status updates
2958 * Queues recalcs
2960 void
2961 sheet_cell_set_text (GnmCell *cell, char const *text, PangoAttrList *markup)
2963 GnmExprTop const *texpr;
2964 GnmValue *val;
2965 GnmParsePos pp;
2967 g_return_if_fail (cell != NULL);
2968 g_return_if_fail (text != NULL);
2969 g_return_if_fail (!gnm_cell_is_nonsingleton_array (cell));
2971 parse_text_value_or_expr (parse_pos_init_cell (&pp, cell),
2972 text, &val, &texpr);
2974 /* Queue a redraw before in case the span changes */
2975 sheet_redraw_cell (cell);
2977 if (texpr != NULL) {
2978 gnm_cell_set_expr (cell, texpr);
2979 gnm_expr_top_unref (texpr);
2982 * Queue recalc before spanning. Otherwise spanning may
2983 * create a bogus rendered value, see #495879.
2985 cell_queue_recalc (cell);
2987 /* Clear spans from _other_ cells */
2988 sheet_cell_calc_span (cell, GNM_SPANCALC_SIMPLE);
2989 } else {
2990 g_return_if_fail (val != NULL);
2992 if (markup != NULL && VALUE_IS_STRING (val)) {
2993 gboolean quoted = (text[0] == '\'');
2994 PangoAttrList *adj_markup;
2995 GOFormat *fmt;
2997 if (quoted) {
2998 /* We ate the quote. Adjust. Ugh. */
2999 adj_markup = pango_attr_list_copy (markup);
3000 go_pango_attr_list_erase (adj_markup, 0, 1);
3001 } else
3002 adj_markup = markup;
3004 fmt = go_format_new_markup (adj_markup, TRUE);
3005 value_set_fmt (val, fmt);
3006 go_format_unref (fmt);
3007 if (quoted)
3008 pango_attr_list_unref (adj_markup);
3011 gnm_cell_set_value (cell, val);
3013 /* Queue recalc before spanning, see above. */
3014 cell_queue_recalc (cell);
3016 sheet_cell_calc_span (cell, GNM_SPANCALC_RESIZE | GNM_SPANCALC_RENDER);
3019 sheet_flag_status_update_cell (cell);
3023 * sheet_cell_set_text_gi: (rename-to sheet_cell_set_text)
3024 * @sheet: #Sheet
3025 * @col: column number
3026 * @row: row number
3027 * @str: the text to set.
3029 * Sets the contents of a cell.
3031 void
3032 sheet_cell_set_text_gi (Sheet *sheet, int col, int row, char const *str)
3034 sheet_cell_set_text (sheet_cell_fetch (sheet, col, row), str, NULL);
3039 * sheet_cell_set_expr:
3041 * Marks the sheet as dirty
3042 * Clears old spans.
3043 * Flags status updates
3044 * Queues recalcs
3046 void
3047 sheet_cell_set_expr (GnmCell *cell, GnmExprTop const *texpr)
3049 gnm_cell_set_expr (cell, texpr);
3051 /* clear spans from _other_ cells */
3052 sheet_cell_calc_span (cell, GNM_SPANCALC_SIMPLE);
3054 cell_queue_recalc (cell);
3055 sheet_flag_status_update_cell (cell);
3059 * sheet_cell_set_value: (skip)
3060 * @cell: #GnmCell
3061 * @v: (transfer full): #GnmValue
3063 * Stores, without copying, the supplied value. It marks the
3064 * sheet as dirty.
3066 * The value is rendered and spans are calculated. It queues a redraw
3067 * and checks to see if the edit region or selection content changed.
3069 * NOTE : This DOES check for array partitioning.
3071 void
3072 sheet_cell_set_value (GnmCell *cell, GnmValue *v)
3074 /* TODO : if the value is unchanged do not assign it */
3075 gnm_cell_set_value (cell, v);
3076 sheet_cell_calc_span (cell, GNM_SPANCALC_RESIZE | GNM_SPANCALC_RENDER);
3077 cell_queue_recalc (cell);
3078 sheet_flag_status_update_cell (cell);
3082 * sheet_cell_set_value_gi: (rename-to sheet_cell_set_value)
3083 * @sheet: #Sheet
3084 * @col: column number
3085 * @row: row number
3086 * @v: #GnmValue
3088 * Set the the value of the cell at (@col,@row) to @v.
3090 * The value is rendered and spans are calculated. It queues a redraw
3091 * and checks to see if the edit region or selection content changed.
3093 void
3094 sheet_cell_set_value_gi (Sheet *sheet, int col, int row, GnmValue *v)
3096 // This version exists because not all versions of pygobject
3097 // understand transfer-full parameters
3098 sheet_cell_set_value (sheet_cell_fetch (sheet, col, row),
3099 value_dup (v));
3102 /****************************************************************************/
3105 * This routine is used to queue the redraw regions for the
3106 * cell region specified.
3108 * It is usually called before a change happens to a region,
3109 * and after the change has been done to queue the regions
3110 * for the old contents and the new contents.
3112 * It intelligently handles spans and merged ranges
3114 void
3115 sheet_range_bounding_box (Sheet const *sheet, GnmRange *bound)
3117 GSList *ptr;
3118 int row;
3119 GnmRange r = *bound;
3121 g_return_if_fail (range_is_sane (bound));
3123 /* Check the first and last columns for spans and extend the region to
3124 * include the maximum extent.
3126 for (row = r.start.row; row <= r.end.row; row++){
3127 ColRowInfo const *ri = sheet_row_get (sheet, row);
3129 if (ri != NULL) {
3130 CellSpanInfo const * span0;
3132 if (ri->needs_respan)
3133 row_calc_spans ((ColRowInfo *)ri, row, sheet);
3135 span0 = row_span_get (ri, r.start.col);
3137 if (span0 != NULL) {
3138 if (bound->start.col > span0->left)
3139 bound->start.col = span0->left;
3140 if (bound->end.col < span0->right)
3141 bound->end.col = span0->right;
3143 if (r.start.col != r.end.col) {
3144 CellSpanInfo const * span1 =
3145 row_span_get (ri, r.end.col);
3147 if (span1 != NULL) {
3148 if (bound->start.col > span1->left)
3149 bound->start.col = span1->left;
3150 if (bound->end.col < span1->right)
3151 bound->end.col = span1->right;
3154 /* skip segments with no cells */
3155 } else if (row == COLROW_SEGMENT_START (row)) {
3156 ColRowSegment const * const segment =
3157 COLROW_GET_SEGMENT (&(sheet->rows), row);
3158 if (segment == NULL)
3159 row = COLROW_SEGMENT_END (row);
3163 /* TODO : this may get expensive if there are alot of merged ranges */
3164 /* no need to iterate, one pass is enough */
3165 for (ptr = sheet->list_merged ; ptr != NULL ; ptr = ptr->next) {
3166 GnmRange const * const test = ptr->data;
3167 if (r.start.row <= test->end.row || r.end.row >= test->start.row) {
3168 if (bound->start.col > test->start.col)
3169 bound->start.col = test->start.col;
3170 if (bound->end.col < test->end.col)
3171 bound->end.col = test->end.col;
3172 if (bound->start.row > test->start.row)
3173 bound->start.row = test->start.row;
3174 if (bound->end.row < test->end.row)
3175 bound->end.row = test->end.row;
3180 void
3181 sheet_redraw_region (Sheet const *sheet,
3182 int start_col, int start_row,
3183 int end_col, int end_row)
3185 GnmRange bound;
3187 g_return_if_fail (IS_SHEET (sheet));
3190 * Getting the bounding box causes row respans to be done if
3191 * needed. That can be expensive, so just redraw the whole
3192 * sheet if the row count is too big.
3194 if (end_row - start_row > 500) {
3195 sheet_redraw_all (sheet, FALSE);
3196 return;
3199 /* We potentially do a lot of recalcs as part of this, so make sure
3200 stuff that caches sub-computations see the whole thing instead
3201 of clearing between cells. */
3202 gnm_app_recalc_start ();
3204 sheet_range_bounding_box (sheet,
3205 range_init (&bound, start_col, start_row, end_col, end_row));
3206 SHEET_FOREACH_CONTROL (sheet, view, control,
3207 sc_redraw_range (control, &bound););
3209 gnm_app_recalc_finish ();
3212 void
3213 sheet_redraw_range (Sheet const *sheet, GnmRange const *range)
3215 g_return_if_fail (IS_SHEET (sheet));
3216 g_return_if_fail (range != NULL);
3218 sheet_redraw_region (sheet,
3219 range->start.col, range->start.row,
3220 range->end.col, range->end.row);
3223 /****************************************************************************/
3225 gboolean
3226 sheet_col_is_hidden (Sheet const *sheet, int col)
3228 ColRowInfo const * const res = sheet_col_get (sheet, col);
3229 return (res != NULL && !res->visible);
3232 gboolean
3233 sheet_row_is_hidden (Sheet const *sheet, int row)
3235 ColRowInfo const * const res = sheet_row_get (sheet, row);
3236 return (res != NULL && !res->visible);
3241 * sheet_find_boundary_horizontal:
3242 * @sheet: The Sheet
3243 * @col: The column from which to begin searching.
3244 * @move_row: The row in which to search for the edge of the range.
3245 * @base_row: The height of the area being moved.
3246 * @count: units to extend the selection vertically
3247 * @jump_to_boundaries: Jump to range boundaries.
3249 * Calculate the column index for the column which is @n units
3250 * from @start_col doing bounds checking. If @jump_to_boundaries is
3251 * TRUE @n must be 1 and the jump is to the edge of the logical range.
3253 * NOTE : This routine implements the logic necasary for ctrl-arrow style
3254 * movement. That is more compilcated than simply finding the last in a list
3255 * of cells with content. If you are at the end of a range it will find the
3256 * start of the next. Make sure that is the sort of behavior you want before
3257 * calling this.
3258 * Returns: the column inex.
3261 sheet_find_boundary_horizontal (Sheet *sheet, int start_col, int move_row,
3262 int base_row, int count,
3263 gboolean jump_to_boundaries)
3265 gboolean find_nonblank = sheet_is_cell_empty (sheet, start_col, move_row);
3266 gboolean keep_looking = FALSE;
3267 int new_col, prev_col, lagged_start_col, max_col = gnm_sheet_get_last_col (sheet);
3268 int iterations = 0;
3269 GnmRange check_merge;
3270 GnmRange const * const bound = &sheet->priv->unhidden_region;
3272 /* Jumping to bounds requires steping cell by cell */
3273 g_return_val_if_fail (count == 1 || count == -1 || !jump_to_boundaries, start_col);
3274 g_return_val_if_fail (IS_SHEET (sheet), start_col);
3276 if (move_row < base_row) {
3277 check_merge.start.row = move_row;
3278 check_merge.end.row = base_row;
3279 } else {
3280 check_merge.end.row = move_row;
3281 check_merge.start.row = base_row;
3284 do {
3285 GSList *merged, *ptr;
3287 lagged_start_col = check_merge.start.col = check_merge.end.col = start_col;
3288 merged = gnm_sheet_merge_get_overlap (sheet, &check_merge);
3289 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3290 GnmRange const * const r = ptr->data;
3291 if (count > 0) {
3292 if (start_col < r->end.col)
3293 start_col = r->end.col;
3294 } else {
3295 if (start_col > r->start.col)
3296 start_col = r->start.col;
3299 g_slist_free (merged);
3300 } while (start_col != lagged_start_col);
3301 new_col = prev_col = start_col;
3303 do {
3304 new_col += count;
3305 ++iterations;
3307 if (new_col < bound->start.col)
3308 return MIN (bound->start.col, max_col);
3309 if (new_col > bound->end.col)
3310 return MIN (bound->end.col, max_col);
3312 keep_looking = sheet_col_is_hidden (sheet, new_col);
3313 if (jump_to_boundaries) {
3314 if (new_col > sheet->cols.max_used) {
3315 if (count > 0)
3316 return (find_nonblank || iterations == 1)?
3317 MIN (bound->end.col, max_col):
3318 MIN (prev_col, max_col);
3319 new_col = sheet->cols.max_used;
3322 keep_looking |= (sheet_is_cell_empty (sheet, new_col, move_row) == find_nonblank);
3323 if (keep_looking)
3324 prev_col = new_col;
3325 else if (!find_nonblank) {
3327 * Handle special case where we are on the last
3328 * non-null cell
3330 if (iterations == 1)
3331 keep_looking = find_nonblank = TRUE;
3332 else
3333 new_col = prev_col;
3336 } while (keep_looking);
3338 return MIN (new_col, max_col);
3342 * sheet_find_boundary_vertical:
3343 * @sheet: The Sheet *
3344 * @move_col: The col in which to search for the edge of the range.
3345 * @row: The row from which to begin searching.
3346 * @base_col: The width of the area being moved.
3347 * @count: units to extend the selection vertically
3348 * @jump_to_boundaries: Jump to range boundaries.
3350 * Calculate the row index for the row which is @n units
3351 * from @start_row doing bounds checking. If @jump_to_boundaries is
3352 * TRUE @n must be 1 and the jump is to the edge of the logical range.
3354 * NOTE : This routine implements the logic necasary for ctrl-arrow style
3355 * movement. That is more compilcated than simply finding the last in a list
3356 * of cells with content. If you are at the end of a range it will find the
3357 * start of the next. Make sure that is the sort of behavior you want before
3358 * calling this.
3359 * Returns: the row index.
3362 sheet_find_boundary_vertical (Sheet *sheet, int move_col, int start_row,
3363 int base_col, int count,
3364 gboolean jump_to_boundaries)
3366 gboolean find_nonblank = sheet_is_cell_empty (sheet, move_col, start_row);
3367 gboolean keep_looking = FALSE;
3368 int new_row, prev_row, lagged_start_row, max_row = gnm_sheet_get_last_row (sheet);
3369 int iterations = 0;
3370 GnmRange check_merge;
3371 GnmRange const * const bound = &sheet->priv->unhidden_region;
3373 /* Jumping to bounds requires steping cell by cell */
3374 g_return_val_if_fail (count == 1 || count == -1 || !jump_to_boundaries, start_row);
3375 g_return_val_if_fail (IS_SHEET (sheet), start_row);
3377 if (move_col < base_col) {
3378 check_merge.start.col = move_col;
3379 check_merge.end.col = base_col;
3380 } else {
3381 check_merge.end.col = move_col;
3382 check_merge.start.col = base_col;
3385 do {
3386 GSList *merged, *ptr;
3388 lagged_start_row = check_merge.start.row = check_merge.end.row = start_row;
3389 merged = gnm_sheet_merge_get_overlap (sheet, &check_merge);
3390 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3391 GnmRange const * const r = ptr->data;
3392 if (count > 0) {
3393 if (start_row < r->end.row)
3394 start_row = r->end.row;
3395 } else {
3396 if (start_row > r->start.row)
3397 start_row = r->start.row;
3400 g_slist_free (merged);
3401 } while (start_row != lagged_start_row);
3402 new_row = prev_row = start_row;
3404 do {
3405 new_row += count;
3406 ++iterations;
3408 if (new_row < bound->start.row)
3409 return MIN (bound->start.row, max_row);
3410 if (new_row > bound->end.row)
3411 return MIN (bound->end.row, max_row);
3413 keep_looking = sheet_row_is_hidden (sheet, new_row);
3414 if (jump_to_boundaries) {
3415 if (new_row > sheet->rows.max_used) {
3416 if (count > 0)
3417 return (find_nonblank || iterations == 1)?
3418 MIN (bound->end.row, max_row):
3419 MIN (prev_row, max_row);
3420 new_row = sheet->rows.max_used;
3423 keep_looking |= (sheet_is_cell_empty (sheet, move_col, new_row) == find_nonblank);
3424 if (keep_looking)
3425 prev_row = new_row;
3426 else if (!find_nonblank) {
3428 * Handle special case where we are on the last
3429 * non-null cell
3431 if (iterations == 1)
3432 keep_looking = find_nonblank = TRUE;
3433 else
3434 new_row = prev_row;
3437 } while (keep_looking);
3439 return MIN (new_row, max_row);
3442 typedef enum {
3443 CHECK_AND_LOAD_START = 1,
3444 CHECK_END = 2,
3445 LOAD_END = 4
3446 } ArrayCheckFlags;
3448 typedef struct {
3449 Sheet const *sheet;
3450 int flags;
3451 int start, end;
3452 GnmRange const *ignore;
3454 GnmRange error;
3455 } ArrayCheckData;
3457 static gboolean
3458 cb_check_array_horizontal (GnmColRowIter const *iter, ArrayCheckData *data)
3460 gboolean is_array = FALSE;
3462 if (data->flags & CHECK_AND_LOAD_START && /* Top */
3463 (is_array = gnm_cell_array_bound (
3464 sheet_cell_get (data->sheet, iter->pos, data->start),
3465 &data->error)) &&
3466 data->error.start.row < data->start &&
3467 (data->ignore == NULL ||
3468 !range_contained (&data->error, data->ignore)))
3469 return TRUE;
3471 if (data->flags & LOAD_END)
3472 is_array = gnm_cell_array_bound (
3473 sheet_cell_get (data->sheet, iter->pos, data->end),
3474 &data->error);
3476 return (data->flags & CHECK_END &&
3477 is_array &&
3478 data->error.end.row > data->end && /* Bottom */
3479 (data->ignore == NULL ||
3480 !range_contained (&data->error, data->ignore)));
3483 static gboolean
3484 cb_check_array_vertical (GnmColRowIter const *iter, ArrayCheckData *data)
3486 gboolean is_array = FALSE;
3488 if (data->flags & CHECK_AND_LOAD_START && /* Left */
3489 (is_array = gnm_cell_array_bound (
3490 sheet_cell_get (data->sheet, data->start, iter->pos),
3491 &data->error)) &&
3492 data->error.start.col < data->start &&
3493 (data->ignore == NULL ||
3494 !range_contained (&data->error, data->ignore)))
3495 return TRUE;
3497 if (data->flags & LOAD_END)
3498 is_array = gnm_cell_array_bound (
3499 sheet_cell_get (data->sheet, data->end, iter->pos),
3500 &data->error);
3502 return (data->flags & CHECK_END &&
3503 is_array &&
3504 data->error.end.col > data->end && /* Right */
3505 (data->ignore == NULL ||
3506 !range_contained (&data->error, data->ignore)));
3510 * sheet_range_splits_array:
3511 * @sheet: The sheet.
3512 * @r: The range to check
3513 * @ignore: (nullable): a range in which it is ok to have an array.
3514 * @cc: (nullable): place to report an error.
3515 * @cmd: (nullable): cmd name used with @cc.
3517 * Check the outer edges of range @sheet!@r to ensure that if an array is
3518 * within it then the entire array is within the range. @ignore is useful when
3519 * src & dest ranges may overlap.
3521 * Returns: TRUE if an array would be split.
3523 gboolean
3524 sheet_range_splits_array (Sheet const *sheet,
3525 GnmRange const *r, GnmRange const *ignore,
3526 GOCmdContext *cc, char const *cmd)
3528 ArrayCheckData closure;
3530 g_return_val_if_fail (r->start.col <= r->end.col, FALSE);
3531 g_return_val_if_fail (r->start.row <= r->end.row, FALSE);
3533 closure.sheet = sheet;
3534 closure.ignore = ignore;
3536 closure.start = r->start.row;
3537 closure.end = r->end.row;
3538 if (closure.start <= 0) {
3539 closure.flags = (closure.end < sheet->rows.max_used)
3540 ? CHECK_END | LOAD_END
3541 : 0;
3542 } else if (closure.end < sheet->rows.max_used)
3543 closure.flags = (closure.start == closure.end)
3544 ? CHECK_AND_LOAD_START | CHECK_END
3545 : CHECK_AND_LOAD_START | CHECK_END | LOAD_END;
3546 else
3547 closure.flags = CHECK_AND_LOAD_START;
3549 if (closure.flags &&
3550 col_row_collection_foreach (&sheet->cols, r->start.col, r->end.col,
3551 (ColRowHandler) cb_check_array_horizontal, &closure)) {
3552 if (cc)
3553 gnm_cmd_context_error_splits_array (cc,
3554 cmd, &closure.error);
3555 return TRUE;
3558 closure.start = r->start.col;
3559 closure.end = r->end.col;
3560 if (closure.start <= 0) {
3561 closure.flags = (closure.end < sheet->cols.max_used)
3562 ? CHECK_END | LOAD_END
3563 : 0;
3564 } else if (closure.end < sheet->cols.max_used)
3565 closure.flags = (closure.start == closure.end)
3566 ? CHECK_AND_LOAD_START | CHECK_END
3567 : CHECK_AND_LOAD_START | CHECK_END | LOAD_END;
3568 else
3569 closure.flags = CHECK_AND_LOAD_START;
3571 if (closure.flags &&
3572 col_row_collection_foreach (&sheet->rows, r->start.row, r->end.row,
3573 (ColRowHandler) cb_check_array_vertical, &closure)) {
3574 if (cc)
3575 gnm_cmd_context_error_splits_array (cc,
3576 cmd, &closure.error);
3577 return TRUE;
3579 return FALSE;
3583 * sheet_range_splits_region:
3584 * @sheet: the sheet.
3585 * @r: The range whose boundaries are checked
3586 * @ignore: An optional range in which it is ok to have arrays and merges
3587 * @cc: The context that issued the command
3588 * @cmd: The translated command name.
3590 * A utility to see whether moving the range @r will split any arrays
3591 * or merged regions.
3592 * Returns: whether any arrays or merged regions will be split.
3594 gboolean
3595 sheet_range_splits_region (Sheet const *sheet,
3596 GnmRange const *r, GnmRange const *ignore,
3597 GOCmdContext *cc, char const *cmd_name)
3599 GSList *merged;
3601 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
3603 /* Check for array subdivision */
3604 if (sheet_range_splits_array (sheet, r, ignore, cc, cmd_name))
3605 return TRUE;
3607 merged = gnm_sheet_merge_get_overlap (sheet, r);
3608 if (merged) {
3609 GSList *ptr;
3611 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3612 GnmRange const *m = ptr->data;
3613 if (ignore != NULL && range_contained (m, ignore))
3614 continue;
3615 if (!range_contained (m, r))
3616 break;
3618 g_slist_free (merged);
3620 if (cc != NULL && ptr != NULL) {
3621 go_cmd_context_error_invalid (cc, cmd_name,
3622 _("Target region contains merged cells"));
3623 return TRUE;
3626 return FALSE;
3630 * sheet_ranges_split_region:
3631 * @sheet: the sheet.
3632 * @ranges: (element-type GnmRange): A list of ranges to check.
3633 * @cc: The context that issued the command
3634 * @cmd: The translated command name.
3636 * A utility to see whether moving any of the ranges @ranges will split any
3637 * arrays or merged regions.
3638 * Returns: whether any arrays or merged regions will be splitted.
3640 gboolean
3641 sheet_ranges_split_region (Sheet const * sheet, GSList const *ranges,
3642 GOCmdContext *cc, char const *cmd)
3644 GSList const *l;
3646 /* Check for array subdivision */
3647 for (l = ranges; l != NULL; l = l->next) {
3648 GnmRange const *r = l->data;
3649 if (sheet_range_splits_region (sheet, r, NULL, cc, cmd))
3650 return TRUE;
3652 return FALSE;
3655 static GnmValue *
3656 cb_cell_is_array (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
3658 return gnm_cell_is_array (iter->cell) ? VALUE_TERMINATE : NULL;
3662 * sheet_range_contains_merges_or_arrays:
3663 * @sheet: The sheet
3664 * @r: the range to check.
3665 * @cc: an optional place to report errors.
3666 * @cmd:
3667 * @merges: if %TRUE, check for merges.
3668 * @arrays: if %TRUE, check for arrays.
3670 * Check to see if the target region @sheet!@r contains any merged regions or
3671 * arrays. Report an error to the @cc if it is supplied.
3672 * Returns: %TRUE if the target region @sheet!@r contains any merged regions or
3673 * arrays.
3675 gboolean
3676 sheet_range_contains_merges_or_arrays (Sheet const *sheet, GnmRange const *r,
3677 GOCmdContext *cc, char const *cmd,
3678 gboolean merges, gboolean arrays)
3680 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
3682 if (merges) {
3683 GSList *merged = gnm_sheet_merge_get_overlap (sheet, r);
3684 if (merged != NULL) {
3685 if (cc != NULL)
3686 go_cmd_context_error_invalid
3687 (cc, cmd,
3688 _("cannot operate on merged cells"));
3689 g_slist_free (merged);
3690 return TRUE;
3694 if (arrays) {
3695 if (sheet_foreach_cell_in_range (
3696 (Sheet *)sheet, CELL_ITER_IGNORE_NONEXISTENT,
3697 r->start.col, r->start.row, r->end.col, r->end.row,
3698 cb_cell_is_array, NULL)) {
3699 if (cc != NULL)
3700 go_cmd_context_error_invalid
3701 (cc, cmd,
3702 _("cannot operate on array formul\303\246"));
3703 return TRUE;
3707 return FALSE;
3710 /***************************************************************************/
3713 * sheet_colrow_get_default:
3714 * @sheet:
3715 * @is_cols:
3717 * Returns: (transfer none): the default #ColRowInfo.
3719 ColRowInfo const *
3720 sheet_colrow_get_default (Sheet const *sheet, gboolean is_cols)
3722 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3724 return is_cols ? &sheet->cols.default_style : &sheet->rows.default_style;
3727 static void
3728 sheet_colrow_optimize1 (int max, int max_used, ColRowCollection *collection)
3730 int i;
3731 int first_unused = max_used + 1;
3733 for (i = COLROW_SEGMENT_START (first_unused);
3734 i < max;
3735 i += COLROW_SEGMENT_SIZE) {
3736 ColRowSegment *segment = COLROW_GET_SEGMENT (collection, i);
3737 int j;
3738 gboolean any = FALSE;
3740 if (!segment)
3741 continue;
3742 for (j = 0; j < COLROW_SEGMENT_SIZE; j++) {
3743 ColRowInfo *info = segment->info[j];
3744 if (!info)
3745 continue;
3746 if (i + j >= first_unused &&
3747 col_row_info_equal (&collection->default_style, info)) {
3748 colrow_free (info);
3749 segment->info[j] = NULL;
3750 } else {
3751 any = TRUE;
3752 if (i + j >= first_unused)
3753 max_used = i + j;
3757 if (!any) {
3758 g_free (segment);
3759 COLROW_GET_SEGMENT (collection, i) = NULL;
3763 collection->max_used = max_used;
3766 void
3767 sheet_colrow_optimize (Sheet *sheet)
3769 GnmRange extent;
3771 g_return_if_fail (IS_SHEET (sheet));
3773 extent = sheet_get_cells_extent (sheet);
3775 sheet_colrow_optimize1 (gnm_sheet_get_max_cols (sheet),
3776 extent.end.col,
3777 &sheet->cols);
3778 sheet_colrow_optimize1 (gnm_sheet_get_max_rows (sheet),
3779 extent.end.row,
3780 &sheet->rows);
3784 * sheet_col_get:
3785 * @col: column number
3787 * Returns: (transfer none) (nullable): A #ColRowInfo for the column, or %NULL
3788 * if none has been allocated yet.
3790 ColRowInfo *
3791 sheet_col_get (Sheet const *sheet, int col)
3793 ColRowSegment *segment;
3795 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3796 g_return_val_if_fail (col < gnm_sheet_get_max_cols (sheet), NULL);
3797 g_return_val_if_fail (col >= 0, NULL);
3799 segment = COLROW_GET_SEGMENT (&(sheet->cols), col);
3800 if (segment != NULL)
3801 return segment->info[COLROW_SUB_INDEX (col)];
3802 return NULL;
3806 * sheet_row_get:
3807 * @row: row number
3809 * Returns: (transfer none) (nullable): A #ColRowInfo for the row, or %NULL
3810 * if none has been allocated yet.
3812 ColRowInfo *
3813 sheet_row_get (Sheet const *sheet, int row)
3815 ColRowSegment *segment;
3817 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3818 g_return_val_if_fail (row < gnm_sheet_get_max_rows (sheet), NULL);
3819 g_return_val_if_fail (row >= 0, NULL);
3821 segment = COLROW_GET_SEGMENT (&(sheet->rows), row);
3822 if (segment != NULL)
3823 return segment->info[COLROW_SUB_INDEX (row)];
3824 return NULL;
3827 ColRowInfo *
3828 sheet_colrow_get (Sheet const *sheet, int colrow, gboolean is_cols)
3830 if (is_cols)
3831 return sheet_col_get (sheet, colrow);
3832 return sheet_row_get (sheet, colrow);
3836 * sheet_col_fetch:
3837 * @col: column number
3839 * Returns: (transfer none): The #ColRowInfo for column @col. This result
3840 * will not be the default #ColRowInfo and may be changed.
3842 ColRowInfo *
3843 sheet_col_fetch (Sheet *sheet, int pos)
3845 ColRowInfo *cri = sheet_col_get (sheet, pos);
3846 if (NULL == cri && NULL != (cri = sheet_col_new (sheet)))
3847 sheet_colrow_add (sheet, cri, TRUE, pos);
3848 return cri;
3852 * sheet_row_fetch:
3853 * @row: row number
3855 * Returns: (transfer none): The #ColRowInfo for row @row. This result
3856 * will not be the default #ColRowInfo and may be changed.
3858 ColRowInfo *
3859 sheet_row_fetch (Sheet *sheet, int pos)
3861 ColRowInfo *cri = sheet_row_get (sheet, pos);
3862 if (NULL == cri && NULL != (cri = sheet_row_new (sheet)))
3863 sheet_colrow_add (sheet, cri, FALSE, pos);
3864 return cri;
3867 ColRowInfo *
3868 sheet_colrow_fetch (Sheet *sheet, int colrow, gboolean is_cols)
3870 if (is_cols)
3871 return sheet_col_fetch (sheet, colrow);
3872 return sheet_row_fetch (sheet, colrow);
3876 * sheet_col_get_info:
3877 * @col: column number
3879 * Returns: (transfer none): The #ColRowInfo for column @col. The may be
3880 * the default #ColRowInfo for columns and should not be changed.
3882 ColRowInfo const *
3883 sheet_col_get_info (Sheet const *sheet, int col)
3885 ColRowInfo *ci = sheet_col_get (sheet, col);
3887 if (ci != NULL)
3888 return ci;
3889 return &sheet->cols.default_style;
3893 * sheet_row_get_info:
3894 * @row: column number
3896 * Returns: (transfer none): The #ColRowInfo for row @row. The may be
3897 * the default #ColRowInfo for rows and should not be changed.
3899 ColRowInfo const *
3900 sheet_row_get_info (Sheet const *sheet, int row)
3902 ColRowInfo *ri = sheet_row_get (sheet, row);
3904 if (ri != NULL)
3905 return ri;
3906 return &sheet->rows.default_style;
3909 ColRowInfo const *
3910 sheet_colrow_get_info (Sheet const *sheet, int colrow, gboolean is_cols)
3912 return is_cols
3913 ? sheet_col_get_info (sheet, colrow)
3914 : sheet_row_get_info (sheet, colrow);
3917 /*****************************************************************************/
3919 static gint
3920 cell_ordering (gconstpointer a_, gconstpointer b_)
3922 GnmCell const *a = *(GnmCell **)a_;
3923 GnmCell const *b = *(GnmCell **)b_;
3925 if (a->pos.row != b->pos.row)
3926 return a->pos.row - b->pos.row;
3928 return a->pos.col - b->pos.col;
3932 * sheet_cells:
3933 * @sheet: a #Sheet
3934 * @r: (nullable): a #GnmRange
3936 * Retrieves an array of all cells inside @r.
3937 * Returns: (element-type GnmCell) (transfer container): the cells array.
3939 GPtrArray *
3940 sheet_cells (Sheet *sheet, const GnmRange *r)
3942 GPtrArray *res = g_ptr_array_new ();
3943 GHashTableIter hiter;
3944 gpointer value;
3946 g_hash_table_iter_init (&hiter, sheet->cell_hash);
3947 while (g_hash_table_iter_next (&hiter, NULL, &value)) {
3948 GnmCell *cell = value;
3949 if (!r || range_contains (r, cell->pos.col, cell->pos.row))
3950 g_ptr_array_add (res, cell);
3952 g_ptr_array_sort (res, cell_ordering);
3954 return res;
3959 #define SWAP_INT(a,b) do { int t; t = a; a = b; b = t; } while (0)
3962 * sheet_foreach_cell_in_range:
3963 * @sheet: #Sheet
3964 * @flags:
3965 * @start_col:
3966 * @start_row:
3967 * @end_col:
3968 * @end_row:
3969 * @callback: (scope call): #CellFiletrFunc
3970 * @closure: user data.
3972 * For each existing cell in the range specified, invoke the
3973 * callback routine. If the only_existing flag is passed, then
3974 * callbacks are only invoked for existing cells.
3976 * Note: this function does not honour the CELL_ITER_IGNORE_SUBTOTAL flag.
3978 * Returns: (transfer none): the value returned by the callback, which can be:
3979 * non-NULL on error, or VALUE_TERMINATE if some invoked routine requested
3980 * to stop (by returning non-NULL).
3982 * NOTE: between 0.56 and 0.57, the traversal order changed. The order is now
3984 * 1 2 3
3985 * 4 5 6
3986 * 7 8 9
3988 * (This appears to be the order in which XL looks at the values of ranges.)
3989 * If your code depends on any particular ordering, please add a very visible
3990 * comment near the call.
3992 GnmValue *
3993 sheet_foreach_cell_in_range (Sheet *sheet, CellIterFlags flags,
3994 int start_col, int start_row,
3995 int end_col, int end_row,
3996 CellIterFunc callback, void *closure)
3998 GnmValue *cont;
3999 GnmCellIter iter;
4000 gboolean const visibility_matters = (flags & CELL_ITER_IGNORE_HIDDEN) != 0;
4001 gboolean const ignore_filtered = (flags & CELL_ITER_IGNORE_FILTERED) != 0;
4002 gboolean const only_existing = (flags & CELL_ITER_IGNORE_NONEXISTENT) != 0;
4003 gboolean const ignore_empty = (flags & CELL_ITER_IGNORE_EMPTY) != 0;
4004 gboolean ignore;
4005 gboolean use_celllist;
4006 guint64 range_size;
4008 g_return_val_if_fail (IS_SHEET (sheet), NULL);
4009 g_return_val_if_fail (callback != NULL, NULL);
4011 iter.pp.sheet = sheet;
4012 iter.pp.wb = sheet->workbook;
4014 if (start_col > end_col)
4015 SWAP_INT (start_col, end_col);
4016 if (end_col < 0 || start_col > gnm_sheet_get_last_col (sheet))
4017 return NULL;
4018 start_col = MAX (0, start_col);
4019 end_col = MIN (end_col, gnm_sheet_get_last_col (sheet));
4021 if (start_row > end_row)
4022 SWAP_INT (start_row, end_row);
4023 if (end_row < 0 || start_row > gnm_sheet_get_last_row (sheet))
4024 return NULL;
4025 start_row = MAX (0, start_row);
4026 end_row = MIN (end_row, gnm_sheet_get_last_row (sheet));
4028 range_size = (guint64)(end_row - start_row + 1) * (end_col - start_col + 1);
4029 use_celllist =
4030 only_existing &&
4031 range_size > g_hash_table_size (sheet->cell_hash) + 1000;
4032 if (use_celllist) {
4033 GPtrArray *all_cells;
4034 int last_row = -1, last_col = -1;
4035 GnmValue *res = NULL;
4036 unsigned ui;
4037 GnmRange r;
4039 if (gnm_debug_flag ("sheet-foreach"))
4040 g_printerr ("Using celllist for area of size %d\n",
4041 (int)range_size);
4043 range_init (&r, start_col, start_row, end_col, end_row);
4044 all_cells = sheet_cells (sheet, &r);
4046 for (ui = 0; ui < all_cells->len; ui++) {
4047 GnmCell *cell = g_ptr_array_index (all_cells, ui);
4049 iter.cell = cell;
4050 iter.pp.eval.row = cell->pos.row;
4051 iter.pp.eval.col = cell->pos.col;
4053 if (iter.pp.eval.row != last_row) {
4054 last_row = iter.pp.eval.row;
4055 iter.ri = sheet_row_get (iter.pp.sheet, last_row);
4057 if (visibility_matters && !iter.ri->visible)
4058 continue;
4059 if (ignore_filtered && iter.ri->in_filter && !iter.ri->visible)
4060 continue;
4062 if (iter.pp.eval.col != last_col) {
4063 last_col = iter.pp.eval.col;
4064 iter.ci = sheet_col_get (iter.pp.sheet, last_col);
4066 if (visibility_matters && !iter.ci->visible)
4067 continue;
4069 ignore = (ignore_empty &&
4070 VALUE_IS_EMPTY (cell->value) &&
4071 !gnm_cell_needs_recalc (cell));
4072 if (ignore)
4073 continue;
4075 res = (*callback) (&iter, closure);
4076 if (res != NULL)
4077 break;
4080 g_ptr_array_free (all_cells, TRUE);
4081 return res;
4084 for (iter.pp.eval.row = start_row;
4085 iter.pp.eval.row <= end_row;
4086 ++iter.pp.eval.row) {
4087 iter.ri = sheet_row_get (iter.pp.sheet, iter.pp.eval.row);
4089 /* no need to check visibility, that would require a colinfo to exist */
4090 if (iter.ri == NULL) {
4091 if (only_existing) {
4092 /* skip segments with no cells */
4093 if (iter.pp.eval.row == COLROW_SEGMENT_START (iter.pp.eval.row)) {
4094 ColRowSegment const *segment =
4095 COLROW_GET_SEGMENT (&(sheet->rows), iter.pp.eval.row);
4096 if (segment == NULL)
4097 iter.pp.eval.row = COLROW_SEGMENT_END (iter.pp.eval.row);
4099 } else {
4100 iter.cell = NULL;
4101 for (iter.pp.eval.col = start_col; iter.pp.eval.col <= end_col; ++iter.pp.eval.col) {
4102 cont = (*callback) (&iter, closure);
4103 if (cont != NULL)
4104 return cont;
4108 continue;
4111 if (visibility_matters && !iter.ri->visible)
4112 continue;
4113 if (ignore_filtered && iter.ri->in_filter && !iter.ri->visible)
4114 continue;
4116 for (iter.pp.eval.col = start_col; iter.pp.eval.col <= end_col; ++iter.pp.eval.col) {
4117 iter.ci = sheet_col_get (sheet, iter.pp.eval.col);
4118 if (iter.ci != NULL) {
4119 if (visibility_matters && !iter.ci->visible)
4120 continue;
4121 iter.cell = sheet_cell_get (sheet,
4122 iter.pp.eval.col, iter.pp.eval.row);
4123 } else
4124 iter.cell = NULL;
4126 ignore = (iter.cell == NULL)
4127 ? (only_existing || ignore_empty)
4128 : (ignore_empty && VALUE_IS_EMPTY (iter.cell->value) &&
4129 !gnm_cell_needs_recalc (iter.cell));
4131 if (ignore) {
4132 if (iter.pp.eval.col == COLROW_SEGMENT_START (iter.pp.eval.col)) {
4133 ColRowSegment const *segment =
4134 COLROW_GET_SEGMENT (&(sheet->cols), iter.pp.eval.col);
4135 if (segment == NULL)
4136 iter.pp.eval.col = COLROW_SEGMENT_END (iter.pp.eval.col);
4138 continue;
4141 cont = (*callback) (&iter, closure);
4142 if (cont != NULL)
4143 return cont;
4146 return NULL;
4150 * sheet_cell_foreach:
4151 * @sheet: #Sheet
4152 * @callback: (scope call):
4153 * @data:
4155 * Call @callback with an argument of @data for each cell in the sheet
4157 void
4158 sheet_cell_foreach (Sheet const *sheet, GHFunc callback, gpointer data)
4160 g_return_if_fail (IS_SHEET (sheet));
4162 g_hash_table_foreach (sheet->cell_hash, callback, data);
4166 * sheet_cells_count:
4167 * @sheet: #Sheet
4169 * Returns the number of cells with content in the current workbook.
4171 unsigned
4172 sheet_cells_count (Sheet const *sheet)
4174 return g_hash_table_size (sheet->cell_hash);
4177 static void
4178 cb_sheet_cells_collect (G_GNUC_UNUSED gpointer unused,
4179 GnmCell const *cell,
4180 GPtrArray *cells)
4182 GnmEvalPos *ep = eval_pos_init_cell (g_new (GnmEvalPos, 1), cell);
4183 g_ptr_array_add (cells, ep);
4187 * sheet_cell_positions:
4188 * @sheet: The sheet to find cells in.
4189 * @comments: If true, include cells with only comments also.
4191 * Collects a GPtrArray of GnmEvalPos pointers for all cells in a sheet.
4192 * No particular order should be assumed.
4193 * Returns: (element-type GnmEvalPos) (transfer full): the newly created array
4195 GPtrArray *
4196 sheet_cell_positions (Sheet *sheet, gboolean comments)
4198 GPtrArray *cells = g_ptr_array_new ();
4200 g_return_val_if_fail (IS_SHEET (sheet), cells);
4202 sheet_cell_foreach (sheet, (GHFunc)cb_sheet_cells_collect, cells);
4204 if (comments) {
4205 GnmRange r;
4206 GSList *scomments, *ptr;
4208 range_init_full_sheet (&r, sheet);
4209 scomments = sheet_objects_get (sheet, &r, GNM_CELL_COMMENT_TYPE);
4210 for (ptr = scomments; ptr; ptr = ptr->next) {
4211 GnmComment *c = ptr->data;
4212 GnmRange const *loc = sheet_object_get_range (GNM_SO (c));
4213 GnmCell *cell = sheet_cell_get (sheet, loc->start.col, loc->start.row);
4214 if (!cell) {
4215 /* If cell does not exist, we haven't seen it... */
4216 GnmEvalPos *ep = g_new (GnmEvalPos, 1);
4217 ep->sheet = sheet;
4218 ep->eval.col = loc->start.col;
4219 ep->eval.row = loc->start.row;
4220 g_ptr_array_add (cells, ep);
4223 g_slist_free (scomments);
4226 return cells;
4230 static GnmValue *
4231 cb_fail_if_exist (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
4233 return gnm_cell_is_empty (iter->cell) ? NULL : VALUE_TERMINATE;
4237 * sheet_is_region_empty:
4238 * @sheet: sheet to check
4239 * @r: region to check
4241 * Returns TRUE if the specified region of the @sheet does not
4242 * contain any cells
4244 gboolean
4245 sheet_is_region_empty (Sheet *sheet, GnmRange const *r)
4247 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
4249 return sheet_foreach_cell_in_range (
4250 sheet, CELL_ITER_IGNORE_BLANK,
4251 r->start.col, r->start.row, r->end.col, r->end.row,
4252 cb_fail_if_exist, NULL) == NULL;
4255 gboolean
4256 sheet_is_cell_empty (Sheet *sheet, int col, int row)
4258 GnmCell const *cell = sheet_cell_get (sheet, col, row);
4259 return gnm_cell_is_empty (cell);
4263 * sheet_cell_add_to_hash:
4264 * @sheet The sheet where the cell is inserted
4265 * @cell The cell, it should already have col/pos pointers
4266 * initialized pointing to the correct ColRowInfo
4268 * GnmCell::pos must be valid before this is called. The position is used as the
4269 * hash key.
4271 static void
4272 sheet_cell_add_to_hash (Sheet *sheet, GnmCell *cell)
4274 g_return_if_fail (cell->pos.col < gnm_sheet_get_max_cols (sheet));
4275 g_return_if_fail (cell->pos.row < gnm_sheet_get_max_rows (sheet));
4277 cell->base.flags |= GNM_CELL_IN_SHEET_LIST;
4278 /* NOTE:
4279 * fetching the col/row here serve 3 functions
4280 * 1) obsolete: we used to store the pointer in the cell
4281 * 2) Expanding col/row.max_used
4282 * 3) Creating an entry in the COLROW_SEGMENT. Lots and lots of
4283 * things use those to help limit iteration
4285 * For now just call col_fetch even though it is not necessary to
4286 * ensure that 2,3 still happen. Alot will need rewriting to avoid
4287 * these requirements.
4289 (void)sheet_col_fetch (sheet, cell->pos.col);
4290 (void)sheet_row_fetch (sheet, cell->pos.row);
4292 gnm_cell_unrender (cell);
4294 g_hash_table_insert (sheet->cell_hash, cell, cell);
4296 if (gnm_sheet_merge_is_corner (sheet, &cell->pos))
4297 cell->base.flags |= GNM_CELL_IS_MERGED;
4300 #undef USE_CELL_POOL
4302 #ifdef USE_CELL_POOL
4303 /* The pool from which all cells are allocated. */
4304 static GOMemChunk *cell_pool;
4305 #else
4306 static int cell_allocations = 0;
4307 #endif
4309 static GnmCell *
4310 cell_new (void)
4312 GnmCell *cell =
4313 #ifdef USE_CELL_POOL
4314 go_mem_chunk_alloc0 (cell_pool)
4315 #else
4316 (cell_allocations++, g_slice_new0 (GnmCell))
4317 #endif
4320 cell->base.flags = DEPENDENT_CELL;
4321 return cell;
4325 static void
4326 cell_free (GnmCell *cell)
4328 g_return_if_fail (cell != NULL);
4330 gnm_cell_cleanout (cell);
4331 #ifdef USE_CELL_POOL
4332 go_mem_chunk_free (cell_pool, cell);
4333 #else
4334 cell_allocations--, g_slice_free1 (sizeof (*cell), cell);
4335 #endif
4339 * gnm_sheet_cell_init: (skip)
4341 void
4342 gnm_sheet_cell_init (void)
4344 #ifdef USE_CELL_POOL
4345 cell_pool = go_mem_chunk_new ("cell pool",
4346 sizeof (GnmCell),
4347 128 * 1024 - 128);
4348 #endif
4351 #ifdef USE_CELL_POOL
4352 static void
4353 cb_cell_pool_leak (gpointer data, G_GNUC_UNUSED gpointer user)
4355 GnmCell const *cell = data;
4356 g_printerr ("Leaking cell %p at %s\n", (void *)cell, cell_name (cell));
4358 #endif
4361 * gnm_sheet_cell_shutdown: (skip)
4363 void
4364 gnm_sheet_cell_shutdown (void)
4366 #ifdef USE_CELL_POOL
4367 go_mem_chunk_foreach_leak (cell_pool, cb_cell_pool_leak, NULL);
4368 go_mem_chunk_destroy (cell_pool, FALSE);
4369 cell_pool = NULL;
4370 #else
4371 if (cell_allocations)
4372 g_printerr ("Leaking %d cells.\n", cell_allocations);
4373 #endif
4376 /****************************************************************************/
4379 * sheet_cell_create:
4380 * @sheet: #Sheet
4381 * @col:
4382 * @row:
4384 * Creates a new cell and adds it to the sheet hash.
4386 GnmCell *
4387 sheet_cell_create (Sheet *sheet, int col, int row)
4389 GnmCell *cell;
4391 g_return_val_if_fail (IS_SHEET (sheet), NULL);
4392 g_return_val_if_fail (col >= 0, NULL);
4393 g_return_val_if_fail (col < gnm_sheet_get_max_cols (sheet), NULL);
4394 g_return_val_if_fail (row >= 0, NULL);
4395 g_return_val_if_fail (row < gnm_sheet_get_max_rows (sheet), NULL);
4397 cell = cell_new ();
4398 cell->base.sheet = sheet;
4399 cell->pos.col = col;
4400 cell->pos.row = row;
4401 cell->value = value_new_empty ();
4403 sheet_cell_add_to_hash (sheet, cell);
4404 return cell;
4408 * sheet_cell_remove_from_hash:
4409 * @sheet:
4410 * @cell:
4412 * Removes a cell from the sheet hash, clears any spans, and unlinks it from
4413 * the dependent collection.
4415 static void
4416 sheet_cell_remove_from_hash (Sheet *sheet, GnmCell *cell)
4418 cell_unregister_span (cell);
4419 if (gnm_cell_expr_is_linked (cell))
4420 dependent_unlink (GNM_CELL_TO_DEP (cell));
4421 g_hash_table_remove (sheet->cell_hash, cell);
4422 cell->base.flags &= ~(GNM_CELL_IN_SHEET_LIST|GNM_CELL_IS_MERGED);
4426 * sheet_cell_destroy:
4427 * @sheet:
4428 * @cell:
4429 * @queue_recalc:
4431 * Remove the cell from the web of dependencies of a
4432 * sheet. Do NOT redraw.
4434 static void
4435 sheet_cell_destroy (Sheet *sheet, GnmCell *cell, gboolean queue_recalc)
4437 if (gnm_cell_expr_is_linked (cell)) {
4438 /* if it needs recalc then its depends are already queued
4439 * check recalc status before we unlink
4441 queue_recalc &= !gnm_cell_needs_recalc (cell);
4442 dependent_unlink (GNM_CELL_TO_DEP (cell));
4445 if (queue_recalc)
4446 cell_foreach_dep (cell, (GnmDepFunc)dependent_queue_recalc, NULL);
4448 sheet_cell_remove_from_hash (sheet, cell);
4449 cell_free (cell);
4453 * sheet_cell_remove:
4454 * @sheet:
4455 * @cell:
4456 * @redraw:
4457 * @queue_recalc:
4459 * Remove the cell from the web of dependencies of a
4460 * sheet. Do NOT free the cell, optionally redraw it, optionally
4461 * queue it for recalc.
4463 void
4464 sheet_cell_remove (Sheet *sheet, GnmCell *cell,
4465 gboolean redraw, gboolean queue_recalc)
4467 g_return_if_fail (cell != NULL);
4468 g_return_if_fail (IS_SHEET (sheet));
4470 /* Queue a redraw on the region used by the cell being removed */
4471 if (redraw) {
4472 sheet_redraw_region (sheet,
4473 cell->pos.col, cell->pos.row,
4474 cell->pos.col, cell->pos.row);
4475 sheet_flag_status_update_cell (cell);
4478 sheet_cell_destroy (sheet, cell, queue_recalc);
4481 static GnmValue *
4482 cb_free_cell (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
4484 sheet_cell_destroy (iter->pp.sheet, iter->cell, FALSE);
4485 return NULL;
4489 * sheet_col_destroy:
4490 * @sheet:
4491 * @col:
4492 * @free_cells:
4494 * Destroys a ColRowInfo from the Sheet with all of its cells
4496 static void
4497 sheet_col_destroy (Sheet *sheet, int const col, gboolean free_cells)
4499 ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->cols), col);
4500 int const sub = COLROW_SUB_INDEX (col);
4501 ColRowInfo *ci = NULL;
4503 if (*segment == NULL)
4504 return;
4505 ci = (*segment)->info[sub];
4506 if (ci == NULL)
4507 return;
4509 if (sheet->cols.max_outline_level > 0 &&
4510 sheet->cols.max_outline_level == ci->outline_level)
4511 sheet->priv->recompute_max_col_group = TRUE;
4513 if (free_cells)
4514 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4515 col, 0, col, gnm_sheet_get_last_row (sheet),
4516 &cb_free_cell, NULL);
4518 (*segment)->info[sub] = NULL;
4519 colrow_free (ci);
4521 /* Use >= just in case things are screwed up */
4522 if (col >= sheet->cols.max_used) {
4523 int i = col;
4524 while (--i >= 0 && sheet_col_get (sheet, i) == NULL)
4526 sheet->cols.max_used = i;
4531 * Destroys a row ColRowInfo
4533 static void
4534 sheet_row_destroy (Sheet *sheet, int const row, gboolean free_cells)
4536 ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->rows), row);
4537 int const sub = COLROW_SUB_INDEX (row);
4538 ColRowInfo *ri = NULL;
4540 if (*segment == NULL)
4541 return;
4542 ri = (*segment)->info[sub];
4543 if (ri == NULL)
4544 return;
4546 if (sheet->rows.max_outline_level > 0 &&
4547 sheet->rows.max_outline_level == ri->outline_level)
4548 sheet->priv->recompute_max_row_group = TRUE;
4550 if (free_cells)
4551 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4552 0, row, gnm_sheet_get_last_col (sheet), row,
4553 &cb_free_cell, NULL);
4555 /* Rows have span lists, destroy them too */
4556 row_destroy_span (ri);
4558 (*segment)->info[sub] = NULL;
4559 colrow_free (ri);
4561 /* Use >= just in case things are screwed up */
4562 if (row >= sheet->rows.max_used) {
4563 int i = row;
4564 while (--i >= 0 && sheet_row_get (sheet, i) == NULL)
4566 sheet->rows.max_used = i;
4570 static void
4571 cb_remove_allcells (G_GNUC_UNUSED gpointer ignore0, GnmCell *cell, G_GNUC_UNUSED gpointer ignore1)
4573 cell->base.flags &= ~GNM_CELL_IN_SHEET_LIST;
4574 cell_free (cell);
4577 void
4578 sheet_destroy_contents (Sheet *sheet)
4580 GSList *filters;
4581 int i;
4583 /* By the time we reach here dependencies should have been shut down */
4584 g_return_if_fail (sheet->deps == NULL);
4586 /* A simple test to see if this has already been run. */
4587 if (sheet->hash_merged == NULL)
4588 return;
4591 GSList *tmp = sheet->slicers;
4592 sheet->slicers = NULL;
4593 g_slist_free_full (tmp, (GDestroyNotify)gnm_sheet_slicer_clear_sheet);
4596 /* These contain SheetObjects, remove them first */
4597 filters = g_slist_copy (sheet->filters);
4598 g_slist_foreach (filters, (GFunc)gnm_filter_remove, NULL);
4599 g_slist_foreach (filters, (GFunc)gnm_filter_unref, NULL);
4600 g_slist_free (filters);
4602 if (sheet->sheet_objects) {
4603 /* The list is changed as we remove */
4604 GSList *objs = g_slist_copy (sheet->sheet_objects);
4605 GSList *ptr;
4606 for (ptr = objs; ptr != NULL ; ptr = ptr->next) {
4607 SheetObject *so = GNM_SO (ptr->data);
4608 if (so != NULL)
4609 sheet_object_clear_sheet (so);
4611 g_slist_free (objs);
4612 if (sheet->sheet_objects != NULL)
4613 g_warning ("There is a problem with sheet objects");
4616 /* The memory is managed by Sheet::list_merged */
4617 g_hash_table_destroy (sheet->hash_merged);
4618 sheet->hash_merged = NULL;
4620 g_slist_free_full (sheet->list_merged, g_free);
4621 sheet->list_merged = NULL;
4623 /* Clear the row spans 1st */
4624 for (i = sheet->rows.max_used; i >= 0 ; --i)
4625 row_destroy_span (sheet_row_get (sheet, i));
4627 /* Remove all the cells */
4628 sheet_cell_foreach (sheet, (GHFunc) &cb_remove_allcells, NULL);
4629 g_hash_table_destroy (sheet->cell_hash);
4631 /* Delete in ascending order to avoid decrementing max_used each time */
4632 for (i = 0; i <= sheet->cols.max_used; ++i)
4633 sheet_col_destroy (sheet, i, FALSE);
4635 for (i = 0; i <= sheet->rows.max_used; ++i)
4636 sheet_row_destroy (sheet, i, FALSE);
4638 /* Free segments too */
4639 col_row_collection_resize (&sheet->cols, 0);
4640 g_ptr_array_free (sheet->cols.info, TRUE);
4641 sheet->cols.info = NULL;
4643 col_row_collection_resize (&sheet->rows, 0);
4644 g_ptr_array_free (sheet->rows.info, TRUE);
4645 sheet->rows.info = NULL;
4647 g_clear_object (&sheet->solver_parameters);
4651 * sheet_destroy:
4652 * @sheet: the sheet to destroy
4654 * Please note that you need to detach this sheet before
4655 * calling this routine or you will get a warning.
4657 static void
4658 sheet_destroy (Sheet *sheet)
4660 g_return_if_fail (IS_SHEET (sheet));
4662 if (sheet->sheet_views->len > 0)
4663 g_warning ("Unexpected left over views");
4665 if (sheet->print_info) {
4666 gnm_print_info_free (sheet->print_info);
4667 sheet->print_info = NULL;
4670 style_color_unref (sheet->tab_color);
4671 sheet->tab_color = NULL;
4672 style_color_unref (sheet->tab_text_color);
4673 sheet->tab_text_color = NULL;
4675 gnm_app_clipboard_invalidate_sheet (sheet);
4678 static void
4679 gnm_sheet_finalize (GObject *obj)
4681 Sheet *sheet = SHEET (obj);
4682 gboolean debug_FMR = gnm_debug_flag ("sheet-fmr");
4684 sheet_destroy (sheet);
4686 g_clear_object (&sheet->solver_parameters);
4688 gnm_conventions_unref (sheet->convs);
4689 sheet->convs = NULL;
4691 g_list_free_full (sheet->scenarios, g_object_unref);
4692 sheet->scenarios = NULL;
4694 if (sheet->sort_setups != NULL)
4695 g_hash_table_unref (sheet->sort_setups);
4697 dependents_invalidate_sheet (sheet, TRUE);
4699 sheet_destroy_contents (sheet);
4701 if (sheet->slicers != NULL) {
4702 g_warning ("DataSlicer list should be NULL");
4704 if (sheet->filters != NULL) {
4705 g_warning ("Filter list should be NULL");
4707 if (sheet->sheet_objects != NULL) {
4708 g_warning ("Sheet object list should be NULL");
4710 if (sheet->list_merged != NULL) {
4711 g_warning ("Merged list should be NULL");
4713 if (sheet->hash_merged != NULL) {
4714 g_warning ("Merged hash should be NULL");
4717 sheet_style_shutdown (sheet);
4719 (void) g_idle_remove_by_data (sheet);
4721 if (debug_FMR) {
4722 g_printerr ("Sheet %p is %s\n", sheet, sheet->name_quoted);
4724 g_free (sheet->name_quoted);
4725 g_free (sheet->name_unquoted);
4726 g_free (sheet->name_unquoted_collate_key);
4727 g_free (sheet->name_case_insensitive);
4728 /* Poison */
4729 sheet->name_quoted = (char *)0xdeadbeef;
4730 sheet->name_unquoted = (char *)0xdeadbeef;
4731 g_free (sheet->priv);
4732 g_ptr_array_free (sheet->sheet_views, TRUE);
4734 gnm_rvc_free (sheet->rendered_values);
4736 if (debug_FMR) {
4737 /* Keep object around. */
4738 return;
4741 G_OBJECT_CLASS (parent_class)->finalize (obj);
4744 /*****************************************************************************/
4747 * cb_empty_cell: A callback for sheet_foreach_cell_in_range
4748 * removes/clear all of the cells in the specified region.
4749 * Does NOT queue a redraw.
4751 * WARNING : This does NOT regenerate spans that were interrupted by
4752 * this cell and can now continue.
4754 static GnmValue *
4755 cb_empty_cell (GnmCellIter const *iter, gpointer user)
4757 int clear_flags = GPOINTER_TO_INT (user);
4758 #if 0
4759 /* TODO : here and other places flag a need to update the
4760 * row/col maxima.
4762 if (row >= sheet->rows.max_used || col >= sheet->cols.max_used) { }
4763 #endif
4765 sheet_cell_remove (iter->pp.sheet, iter->cell, FALSE,
4766 (clear_flags & CLEAR_RECALC_DEPS) &&
4767 iter->pp.wb->recursive_dirty_enabled);
4769 return NULL;
4773 * sheet_clear_region:
4774 * @sheet:
4775 * @start_col:
4776 * @start_row:
4777 * @end_col:
4778 * @end_row:
4779 * @clear_flags: If this is TRUE then styles are erased.
4780 * @cc: (nullable):
4782 * Clears are region of cells
4784 * We assemble a list of cells to destroy, since we will be making changes
4785 * to the structure being manipulated by the sheet_foreach_cell_in_range routine
4787 void
4788 sheet_clear_region (Sheet *sheet,
4789 int start_col, int start_row,
4790 int end_col, int end_row,
4791 int clear_flags,
4792 GOCmdContext *cc)
4794 GnmRange r;
4796 g_return_if_fail (IS_SHEET (sheet));
4797 g_return_if_fail (start_col <= end_col);
4798 g_return_if_fail (start_row <= end_row);
4800 r.start.col = start_col;
4801 r.start.row = start_row;
4802 r.end.col = end_col;
4803 r.end.row = end_row;
4805 if (clear_flags & CLEAR_VALUES && !(clear_flags & CLEAR_NOCHECKARRAY) &&
4806 sheet_range_splits_array (sheet, &r, NULL, cc, _("Clear")))
4807 return;
4809 /* Queue a redraw for cells being modified */
4810 if (clear_flags & (CLEAR_VALUES|CLEAR_FORMATS))
4811 sheet_redraw_region (sheet,
4812 start_col, start_row,
4813 end_col, end_row);
4815 /* Clear the style in the region (new_default will ref the style for us). */
4816 if (clear_flags & CLEAR_FORMATS) {
4817 sheet_style_set_range (sheet, &r, sheet_style_default (sheet));
4818 sheet_range_calc_spans (sheet, &r, GNM_SPANCALC_RE_RENDER|GNM_SPANCALC_RESIZE);
4819 rows_height_update (sheet, &r, TRUE);
4822 if (clear_flags & CLEAR_OBJECTS)
4823 sheet_objects_clear (sheet, &r, G_TYPE_NONE, NULL);
4824 else if (clear_flags & CLEAR_COMMENTS)
4825 sheet_objects_clear (sheet, &r, GNM_CELL_COMMENT_TYPE, NULL);
4827 /* TODO : how to handle objects ? */
4828 if (clear_flags & CLEAR_VALUES) {
4829 /* Remove or empty the cells depending on
4830 * whether or not there are comments
4832 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4833 start_col, start_row, end_col, end_row,
4834 &cb_empty_cell, GINT_TO_POINTER (clear_flags));
4836 if (!(clear_flags & CLEAR_NORESPAN)) {
4837 sheet_queue_respan (sheet, start_row, end_row);
4838 sheet_flag_status_update_range (sheet, &r);
4842 if (clear_flags & CLEAR_MERGES) {
4843 GSList *merged, *ptr;
4844 merged = gnm_sheet_merge_get_overlap (sheet, &r);
4845 for (ptr = merged ; ptr != NULL ; ptr = ptr->next)
4846 gnm_sheet_merge_remove (sheet, ptr->data);
4847 g_slist_free (merged);
4850 if (clear_flags & CLEAR_RECALC_DEPS)
4851 sheet_region_queue_recalc (sheet, &r);
4853 /* Always redraw */
4854 sheet_redraw_all (sheet, FALSE);
4857 static void
4858 sheet_clear_region_cb (GnmSheetRange *sr, int *flags)
4860 sheet_clear_region (sr->sheet,
4861 sr->range.start.col, sr->range.start.row,
4862 sr->range.end.col, sr->range.end.row,
4863 *flags | CLEAR_NOCHECKARRAY, NULL);
4868 * sheet_clear_region_undo:
4869 * @sr: #GnmSheetRange
4870 * @clear_flags: flags.
4872 * Returns: (transfer full): the new #GOUndo.
4874 GOUndo *sheet_clear_region_undo (GnmSheetRange *sr, int clear_flags)
4876 int *flags = g_new(int, 1);
4877 *flags = clear_flags;
4878 return go_undo_binary_new
4879 (sr, (gpointer)flags,
4880 (GOUndoBinaryFunc) sheet_clear_region_cb,
4881 (GFreeFunc) gnm_sheet_range_free,
4882 (GFreeFunc) g_free);
4886 /*****************************************************************************/
4888 void
4889 sheet_mark_dirty (Sheet *sheet)
4891 g_return_if_fail (IS_SHEET (sheet));
4893 if (sheet->workbook)
4894 go_doc_set_dirty (GO_DOC (sheet->workbook), TRUE);
4897 /****************************************************************************/
4899 static void
4900 sheet_cells_deps_move (GnmExprRelocateInfo *rinfo)
4902 Sheet *sheet = rinfo->origin_sheet;
4903 GPtrArray *deps = sheet_cells (sheet, &rinfo->origin);
4904 unsigned ui;
4906 /* Phase 1: collect all cells and remove them from hash. */
4907 for (ui = 0; ui < deps->len; ui++) {
4908 GnmCell *cell = g_ptr_array_index (deps, ui);
4909 gboolean needs_recalc = gnm_cell_needs_recalc (cell);
4910 sheet_cell_remove_from_hash (sheet, cell);
4911 if (needs_recalc) /* Do we need this now? */
4912 cell->base.flags |= DEPENDENT_NEEDS_RECALC;
4915 /* Phase 2: add all non-cell deps with positions */
4916 SHEET_FOREACH_DEPENDENT
4917 (sheet, dep, {
4918 GnmCellPos const *pos;
4919 if (!dependent_is_cell (dep) &&
4920 dependent_has_pos (dep) &&
4921 (pos = dependent_pos (dep)) &&
4922 range_contains (&rinfo->origin, pos->col, pos->row)) {
4923 dependent_unlink (dep);
4924 g_ptr_array_add (deps, dep);
4928 /* Phase 3: move everything and add cells to hash. */
4929 for (ui = 0; ui < deps->len; ui++) {
4930 GnmDependent *dep = g_ptr_array_index (deps, ui);
4932 dependent_move (dep, rinfo->col_offset, rinfo->row_offset);
4934 if (dependent_is_cell (dep))
4935 sheet_cell_add_to_hash (sheet, GNM_DEP_TO_CELL (dep));
4937 if (dep->texpr)
4938 dependent_link (dep);
4941 g_ptr_array_free (deps, TRUE);
4944 /* Moves the headers to their new location */
4945 static void
4946 sheet_colrow_move (Sheet *sheet, gboolean is_cols,
4947 int const old_pos, int const new_pos)
4949 ColRowSegment *segment = COLROW_GET_SEGMENT (is_cols ? &sheet->cols : &sheet->rows, old_pos);
4950 ColRowInfo *info = segment
4951 ? segment->info[COLROW_SUB_INDEX (old_pos)]
4952 : NULL;
4954 g_return_if_fail (old_pos >= 0);
4955 g_return_if_fail (new_pos >= 0);
4957 if (info == NULL)
4958 return;
4960 /* Update the position */
4961 segment->info[COLROW_SUB_INDEX (old_pos)] = NULL;
4962 sheet_colrow_add (sheet, info, is_cols, new_pos);
4965 static void
4966 sheet_colrow_set_collapse (Sheet *sheet, gboolean is_cols, int pos)
4968 ColRowInfo *cri;
4969 ColRowInfo const *vs = NULL;
4971 if (pos < 0 || pos >= colrow_max (is_cols, sheet))
4972 return;
4974 /* grab the next or previous col/row */
4975 if ((is_cols ? sheet->outline_symbols_right : sheet->outline_symbols_below)) {
4976 if (pos > 0)
4977 vs = sheet_colrow_get (sheet, pos-1, is_cols);
4978 } else if ((pos+1) < colrow_max (is_cols, sheet))
4979 vs = sheet_colrow_get (sheet, pos+1, is_cols);
4981 /* handle the case where an empty col/row should be marked collapsed */
4982 cri = sheet_colrow_get (sheet, pos, is_cols);
4983 if (cri != NULL)
4984 cri->is_collapsed = (vs != NULL && !vs->visible &&
4985 vs->outline_level > cri->outline_level);
4986 else if (vs != NULL && !vs->visible && vs->outline_level > 0) {
4987 cri = sheet_colrow_fetch (sheet, pos, is_cols);
4988 cri->is_collapsed = TRUE;
4992 static void
4993 combine_undo (GOUndo **pundo, GOUndo *u)
4995 if (pundo)
4996 *pundo = go_undo_combine (*pundo, u);
4997 else
4998 g_object_unref (u);
5001 typedef gboolean (*ColRowInsDelFunc) (Sheet *sheet, int idx, int count,
5002 GOUndo **pundo, GOCmdContext *cc);
5004 typedef struct {
5005 ColRowInsDelFunc func;
5006 Sheet *sheet;
5007 gboolean is_cols;
5008 int pos;
5009 int count;
5010 ColRowStateList *states;
5011 int state_start;
5012 } ColRowInsDelData;
5014 static void
5015 cb_undo_insdel (ColRowInsDelData *data)
5017 data->func (data->sheet, data->pos, data->count, NULL, NULL);
5018 colrow_set_states (data->sheet, data->is_cols,
5019 data->state_start, data->states);
5022 static void
5023 cb_undo_insdel_free (ColRowInsDelData *data)
5025 colrow_state_list_destroy (data->states);
5026 g_free (data);
5029 static gboolean
5030 sheet_insdel_colrow (Sheet *sheet, int pos, int count,
5031 GOUndo **pundo, GOCmdContext *cc,
5032 gboolean is_cols, gboolean is_insert,
5033 const char *description,
5034 ColRowInsDelFunc opposite)
5037 GnmRange kill_zone; /* The range whose contents will be lost. */
5038 GnmRange move_zone; /* The range whose contents will be moved. */
5039 GnmRange change_zone; /* The union of kill_zone and move_zone. */
5040 int i, last_pos, max_used_pos;
5041 int kill_start, kill_end, move_start, move_end;
5042 int scount = is_insert ? count : -count;
5043 ColRowStateList *states = NULL;
5044 GnmExprRelocateInfo reloc_info;
5045 GSList *l;
5046 gboolean sticky_end = TRUE;
5048 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
5049 g_return_val_if_fail (count > 0, TRUE);
5052 * The main undo for an insert col/row is delete col/row and vice versa.
5053 * In addition to that, we collect undo information that the main undo
5054 * operation will not restore -- for example the contents of the kill
5055 * zone.
5057 if (pundo) *pundo = NULL;
5059 last_pos = colrow_max (is_cols, sheet) - 1;
5060 max_used_pos = is_cols ? sheet->cols.max_used : sheet->rows.max_used;
5061 if (is_insert) {
5062 kill_start = last_pos - (count - 1);
5063 kill_end = last_pos;
5064 move_start = pos;
5065 move_end = kill_start - 1;
5066 } else {
5067 int max_count = last_pos + 1 - pos;
5068 if (count > max_count) {
5069 sticky_end = FALSE;
5070 count = max_count;
5072 kill_start = pos;
5073 kill_end = pos + (count - 1);
5074 move_start = kill_end + 1;
5075 move_end = last_pos;
5077 (is_cols ? range_init_cols : range_init_rows)
5078 (&kill_zone, sheet, kill_start, kill_end);
5079 (is_cols ? range_init_cols : range_init_rows)
5080 (&move_zone, sheet, move_start, move_end);
5081 change_zone = range_union (&kill_zone, &move_zone);
5083 /* 0. Check displaced/deleted region and ensure arrays aren't divided. */
5084 if (sheet_range_splits_array (sheet, &kill_zone, NULL, cc, description))
5085 return TRUE;
5086 if (move_start <= move_end &&
5087 sheet_range_splits_array (sheet, &move_zone, NULL, cc, description))
5088 return TRUE;
5091 * At this point we're committed. Anything that can go wrong should
5092 * have been ruled out already.
5095 if (0) {
5096 g_printerr ("Action = %s at %d count %d\n", description, pos, count);
5097 g_printerr ("Kill zone: %s\n", range_as_string (&kill_zone));
5100 /* 1. Delete all columns/rows in the kill zone */
5101 if (pundo) {
5102 combine_undo (pundo, clipboard_copy_range_undo (sheet, &kill_zone));
5103 states = colrow_get_states (sheet, is_cols, kill_start, kill_end);
5105 for (i = MIN (max_used_pos, kill_end); i >= kill_start; --i)
5106 (is_cols ? sheet_col_destroy : sheet_row_destroy)
5107 (sheet, i, TRUE);
5108 /* Brutally discard auto filter objects. Collect the rest for undo. */
5109 sheet_objects_clear (sheet, &kill_zone, GNM_FILTER_COMBO_TYPE, NULL);
5110 sheet_objects_clear (sheet, &kill_zone, G_TYPE_NONE, pundo);
5112 reloc_info.reloc_type = is_cols ? GNM_EXPR_RELOCATE_COLS : GNM_EXPR_RELOCATE_ROWS;
5113 reloc_info.sticky_end = sticky_end;
5114 reloc_info.origin_sheet = reloc_info.target_sheet = sheet;
5115 parse_pos_init_sheet (&reloc_info.pos, sheet);
5117 /* 2. Get rid of style dependents, see #741197. */
5118 sheet_style_clear_style_dependents (sheet, &change_zone);
5120 /* 3. Invalidate references to kill zone. */
5121 if (is_insert) {
5122 /* Done in the next step. */
5123 } else {
5124 reloc_info.origin = kill_zone;
5125 /* Force invalidation: */
5126 reloc_info.col_offset = is_cols ? last_pos + 1 : 0;
5127 reloc_info.row_offset = is_cols ? 0 : last_pos + 1;
5128 combine_undo (pundo, dependents_relocate (&reloc_info));
5131 /* 4. Fix references to the cells which are moving */
5132 reloc_info.origin = is_insert ? change_zone : move_zone;
5133 reloc_info.col_offset = is_cols ? scount : 0;
5134 reloc_info.row_offset = is_cols ? 0 : scount;
5135 combine_undo (pundo, dependents_relocate (&reloc_info));
5137 /* 5. Move the cells */
5138 sheet_cells_deps_move (&reloc_info);
5140 /* 6. Move the columns/rows to their new location. */
5141 if (is_insert) {
5142 /* From right to left */
5143 for (i = max_used_pos; i >= pos ; --i)
5144 sheet_colrow_move (sheet, is_cols, i, i + count);
5145 } else {
5146 /* From left to right */
5147 for (i = pos + count ; i <= max_used_pos; ++i)
5148 sheet_colrow_move (sheet, is_cols, i, i - count);
5150 sheet_colrow_set_collapse (sheet, is_cols, pos);
5151 sheet_colrow_set_collapse (sheet, is_cols,
5152 is_insert ? pos + count : last_pos - (count - 1));
5154 /* 7. Move formatting. */
5155 sheet_style_insdel_colrow (&reloc_info);
5157 /* 8. Move objects. */
5158 sheet_objects_relocate (&reloc_info, FALSE, pundo);
5160 /* 9. Move merges. */
5161 gnm_sheet_merge_relocate (&reloc_info, pundo);
5163 /* 10. Move filters. */
5164 gnm_sheet_filter_insdel_colrow (sheet, is_cols, is_insert, pos, count, pundo);
5166 /* Notify sheet of pending updates */
5167 sheet_mark_dirty (sheet);
5168 sheet->priv->recompute_visibility = TRUE;
5169 sheet_flag_recompute_spans (sheet);
5170 sheet_flag_status_update_range (sheet, &change_zone);
5171 if (is_cols)
5172 sheet->priv->reposition_objects.col = pos;
5173 else
5174 sheet->priv->reposition_objects.row = pos;
5176 /* WARNING WARNING WARNING
5177 * This is bad practice and should not really be here.
5178 * However, we need to ensure that update is run before
5179 * sv_panes_insdel_colrow plays with frozen panes, updating those can
5180 * trigger redraws before sheet_update has been called. */
5181 sheet_update (sheet);
5183 SHEET_FOREACH_VIEW (sheet, sv,
5184 sv_panes_insdel_colrow (sv, is_cols, is_insert, pos, count););
5186 /* The main undo is the opposite operation. */
5187 if (pundo) {
5188 ColRowInsDelData *data;
5189 GOUndo *u;
5191 data = g_new (ColRowInsDelData, 1);
5192 data->func = opposite;
5193 data->sheet = sheet;
5194 data->is_cols = is_cols;
5195 data->pos = pos;
5196 data->count = count;
5197 data->states = states;
5198 data->state_start = kill_start;
5200 u = go_undo_unary_new (data, (GOUndoUnaryFunc)cb_undo_insdel,
5201 (GFreeFunc)cb_undo_insdel_free);
5203 combine_undo (pundo, u);
5206 /* Reapply all filters. */
5207 for (l = sheet->filters; l; l = l->next) {
5208 GnmFilter *filter = l->data;
5209 gnm_filter_reapply (filter);
5212 return FALSE;
5216 * sheet_insert_cols:
5217 * @sheet: #Sheet
5218 * @col: At which position we want to insert
5219 * @count: The number of columns to be inserted
5220 * @pundo: (out): (transfer full): (allow-none): undo closure
5221 * @cc:
5223 gboolean
5224 sheet_insert_cols (Sheet *sheet, int col, int count,
5225 GOUndo **pundo, GOCmdContext *cc)
5227 return sheet_insdel_colrow (sheet, col, count, pundo, cc,
5228 TRUE, TRUE,
5229 _("Insert Columns"),
5230 sheet_delete_cols);
5234 * sheet_delete_cols:
5235 * @sheet: The sheet
5236 * @col: At which position we want to start deleting columns
5237 * @count: The number of columns to be deleted
5238 * @pundo: (out): (transfer full): (allow-none): undo closure
5239 * @cc: The command context
5241 gboolean
5242 sheet_delete_cols (Sheet *sheet, int col, int count,
5243 GOUndo **pundo, GOCmdContext *cc)
5245 return sheet_insdel_colrow (sheet, col, count, pundo, cc,
5246 TRUE, FALSE,
5247 _("Delete Columns"),
5248 sheet_insert_cols);
5252 * sheet_insert_rows:
5253 * @sheet: The sheet
5254 * @row: At which position we want to insert
5255 * @count: The number of rows to be inserted
5256 * @pundo: (out): (transfer full): (allow-none): undo closure
5257 * @cc: The command context
5259 gboolean
5260 sheet_insert_rows (Sheet *sheet, int row, int count,
5261 GOUndo **pundo, GOCmdContext *cc)
5263 return sheet_insdel_colrow (sheet, row, count, pundo, cc,
5264 FALSE, TRUE,
5265 _("Insert Rows"),
5266 sheet_delete_rows);
5270 * sheet_delete_rows:
5271 * @sheet: The sheet
5272 * @row: At which position we want to start deleting rows
5273 * @count: The number of rows to be deleted
5274 * @pundo: (out): (transfer full): (allow-none): undo closure
5275 * @cc: The command context
5277 gboolean
5278 sheet_delete_rows (Sheet *sheet, int row, int count,
5279 GOUndo **pundo, GOCmdContext *cc)
5281 return sheet_insdel_colrow (sheet, row, count, pundo, cc,
5282 FALSE, FALSE,
5283 _("Delete Rows"),
5284 sheet_insert_rows);
5288 * Callback for sheet_foreach_cell_in_range to remove a cell from the sheet
5289 * hash, unlink from the dependent collection and put it in a temporary list.
5291 static GnmValue *
5292 cb_collect_cell (GnmCellIter const *iter, gpointer user)
5294 GList ** l = user;
5295 GnmCell *cell = iter->cell;
5296 gboolean needs_recalc = gnm_cell_needs_recalc (cell);
5298 sheet_cell_remove_from_hash (iter->pp.sheet, cell);
5299 *l = g_list_prepend (*l, cell);
5300 if (needs_recalc)
5301 cell->base.flags |= DEPENDENT_NEEDS_RECALC;
5302 return NULL;
5306 * sheet_move_range:
5307 * @cc:
5308 * @rinfo:
5309 * @pundo: optionally NULL, caller releases result
5311 * Move a range as specified in @rinfo report warnings to @cc.
5312 * if @pundo is non NULL, invalidate references to the
5313 * target region that are being cleared, and store the undo information
5314 * in @pundo. If it is NULL do NOT INVALIDATE.
5316 void
5317 sheet_move_range (GnmExprRelocateInfo const *rinfo,
5318 GOUndo **pundo, GOCmdContext *cc)
5320 GList *cells = NULL;
5321 GnmCell *cell;
5322 GnmRange dst;
5323 gboolean out_of_range;
5325 g_return_if_fail (rinfo != NULL);
5326 g_return_if_fail (IS_SHEET (rinfo->origin_sheet));
5327 g_return_if_fail (IS_SHEET (rinfo->target_sheet));
5328 g_return_if_fail (rinfo->origin_sheet != rinfo->target_sheet ||
5329 rinfo->col_offset != 0 ||
5330 rinfo->row_offset != 0);
5332 dst = rinfo->origin;
5333 out_of_range = range_translate (&dst, rinfo->target_sheet,
5334 rinfo->col_offset, rinfo->row_offset);
5336 /* Redraw the src region in case anything was spanning */
5337 sheet_redraw_range (rinfo->origin_sheet, &rinfo->origin);
5339 /* 1. invalidate references to any cells in the destination range that
5340 * are not shared with the src. This must be done before the references
5341 * to from the src range are adjusted because they will point into
5342 * the destination.
5344 if (pundo != NULL) {
5345 *pundo = NULL;
5346 if (!out_of_range) {
5347 GSList *invalid;
5348 GnmExprRelocateInfo reloc_info;
5350 /* We need to be careful about invalidating references
5351 * to the old content of the destination region. We
5352 * only invalidate references to regions that are
5353 * actually lost. However, this care is only necessary
5354 * if the source and target sheets are the same.
5356 * Handle dst cells being pasted over
5358 if (rinfo->origin_sheet == rinfo->target_sheet &&
5359 range_overlap (&rinfo->origin, &dst))
5360 invalid = range_split_ranges (&rinfo->origin, &dst);
5361 else
5362 invalid = g_slist_append (NULL, gnm_range_dup (&dst));
5364 reloc_info.origin_sheet = reloc_info.target_sheet = rinfo->target_sheet;
5366 /* send to infinity to invalidate, but try to assist
5367 * the relocation heuristics only move in 1
5368 * dimension if possible to give us a chance to be
5369 * smart about partial invalidations */
5370 reloc_info.col_offset = gnm_sheet_get_max_cols (rinfo->target_sheet);
5371 reloc_info.row_offset = gnm_sheet_get_max_rows (rinfo->target_sheet);
5372 reloc_info.sticky_end = TRUE;
5373 if (rinfo->col_offset == 0) {
5374 reloc_info.col_offset = 0;
5375 reloc_info.reloc_type = GNM_EXPR_RELOCATE_ROWS;
5376 } else if (rinfo->row_offset == 0) {
5377 reloc_info.row_offset = 0;
5378 reloc_info.reloc_type = GNM_EXPR_RELOCATE_COLS;
5379 } else
5380 reloc_info.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE;
5382 parse_pos_init_sheet (&reloc_info.pos,
5383 rinfo->origin_sheet);
5385 while (invalid) {
5386 GnmRange *r = invalid->data;
5387 invalid = g_slist_remove (invalid, r);
5388 if (!range_overlap (r, &rinfo->origin)) {
5389 reloc_info.origin = *r;
5390 combine_undo (pundo,
5391 dependents_relocate (&reloc_info));
5393 g_free (r);
5397 * DO NOT handle src cells moving out the bounds.
5398 * that is handled elsewhere.
5402 /* 2. Fix references to and from the cells which are moving */
5403 combine_undo (pundo, dependents_relocate (rinfo));
5406 /* 3. Collect the cells */
5407 sheet_foreach_cell_in_range (rinfo->origin_sheet, CELL_ITER_IGNORE_NONEXISTENT,
5408 rinfo->origin.start.col, rinfo->origin.start.row,
5409 rinfo->origin.end.col, rinfo->origin.end.row,
5410 &cb_collect_cell, &cells);
5412 /* Reverse list so that we start at the top left (simplifies arrays). */
5413 cells = g_list_reverse (cells);
5415 /* 4. Clear the target area & invalidate references to it */
5416 if (!out_of_range)
5417 /* we can clear content but not styles from the destination
5418 * region without worrying if it overlaps with the source,
5419 * because we have already extracted the content. However,
5420 * we do need to queue anything that depends on the region for
5421 * recalc. */
5422 sheet_clear_region (rinfo->target_sheet,
5423 dst.start.col, dst.start.row,
5424 dst.end.col, dst.end.row,
5425 CLEAR_VALUES|CLEAR_RECALC_DEPS, cc);
5427 /* 5. Slide styles BEFORE the cells so that spans get computed properly */
5428 sheet_style_relocate (rinfo);
5430 /* 6. Insert the cells back */
5431 for (; cells != NULL ; cells = g_list_remove (cells, cell)) {
5432 cell = cells->data;
5434 /* check for out of bounds and delete if necessary */
5435 if ((cell->pos.col + rinfo->col_offset) >= gnm_sheet_get_max_cols (rinfo->target_sheet) ||
5436 (cell->pos.row + rinfo->row_offset) >= gnm_sheet_get_max_rows (rinfo->target_sheet)) {
5437 cell_free (cell);
5438 continue;
5441 /* Update the location */
5442 cell->base.sheet = rinfo->target_sheet;
5443 cell->pos.col += rinfo->col_offset;
5444 cell->pos.row += rinfo->row_offset;
5445 sheet_cell_add_to_hash (rinfo->target_sheet, cell);
5446 if (gnm_cell_has_expr (cell))
5447 dependent_link (GNM_CELL_TO_DEP (cell));
5450 /* 7. Move objects in the range */
5451 sheet_objects_relocate (rinfo, TRUE, pundo);
5452 gnm_sheet_merge_relocate (rinfo, pundo);
5454 /* 8. Notify sheet of pending update */
5455 sheet_flag_recompute_spans (rinfo->origin_sheet);
5456 sheet_flag_status_update_range (rinfo->origin_sheet, &rinfo->origin);
5459 static void
5460 sheet_colrow_default_calc (Sheet *sheet, double units,
5461 gboolean is_cols, gboolean is_pts)
5463 ColRowInfo *cri = is_cols
5464 ? &sheet->cols.default_style
5465 : &sheet->rows.default_style;
5467 g_return_if_fail (units > 0.);
5469 cri->is_default = TRUE;
5470 cri->hard_size = FALSE;
5471 cri->visible = TRUE;
5472 cri->spans = NULL;
5474 if (is_pts) {
5475 cri->size_pts = units;
5476 colrow_compute_pixels_from_pts (cri, sheet, is_cols, -1);
5477 } else {
5478 cri->size_pixels = units;
5479 colrow_compute_pts_from_pixels (cri, sheet, is_cols, -1);
5483 /************************************************************************/
5484 /* Col width support routines.
5488 * sheet_col_get_distance_pts:
5490 * Return the number of points between from_col to to_col
5491 * measured from the upper left corner.
5493 double
5494 sheet_col_get_distance_pts (Sheet const *sheet, int from, int to)
5496 ColRowInfo const *ci;
5497 double dflt, pts = 0., sign = 1.;
5498 int i;
5500 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5502 if (from > to) {
5503 int const tmp = to;
5504 to = from;
5505 from = tmp;
5506 sign = -1.;
5509 g_return_val_if_fail (from >= 0, 1.);
5510 g_return_val_if_fail (to <= gnm_sheet_get_max_cols (sheet), 1.);
5512 /* Do not use col_row_collection_foreach, it ignores empties */
5513 dflt = sheet->cols.default_style.size_pts;
5514 for (i = from ; i < to ; ++i) {
5515 if (NULL == (ci = sheet_col_get (sheet, i)))
5516 pts += dflt;
5517 else if (ci->visible)
5518 pts += ci->size_pts;
5521 if (sheet->display_formulas)
5522 pts *= 2.;
5524 return pts * sign;
5528 * sheet_col_get_distance_pixels:
5530 * Return the number of pixels between from_col to to_col
5531 * measured from the upper left corner.
5534 sheet_col_get_distance_pixels (Sheet const *sheet, int from, int to)
5536 ColRowInfo const *ci;
5537 int dflt, pixels = 0, sign = 1;
5538 int i;
5540 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5542 if (from > to) {
5543 int const tmp = to;
5544 to = from;
5545 from = tmp;
5546 sign = -1;
5549 g_return_val_if_fail (from >= 0, 1);
5550 g_return_val_if_fail (to <= gnm_sheet_get_max_cols (sheet), 1);
5552 /* Do not use col_row_collection_foreach, it ignores empties */
5553 dflt = sheet_col_get_default_size_pixels (sheet);
5554 for (i = from ; i < to ; ++i) {
5555 if (NULL == (ci = sheet_col_get (sheet, i)))
5556 pixels += dflt;
5557 else if (ci->visible)
5558 pixels += ci->size_pixels;
5561 return pixels * sign;
5565 * sheet_col_set_size_pts:
5566 * @sheet: The sheet
5567 * @col: The col
5568 * @width_pts: The desired widtht in pts
5569 * @set_by_user: TRUE if this was done by a user (ie, user manually
5570 * set the width)
5572 * Sets width of a col in pts, INCLUDING left and right margins, and the far
5573 * grid line. This is a low level internal routine. It does NOT redraw,
5574 * or reposition objects.
5576 void
5577 sheet_col_set_size_pts (Sheet *sheet, int col, double width_pts,
5578 gboolean set_by_user)
5580 ColRowInfo *ci;
5582 g_return_if_fail (IS_SHEET (sheet));
5583 g_return_if_fail (width_pts > 0.0);
5585 ci = sheet_col_fetch (sheet, col);
5586 ci->hard_size = set_by_user;
5587 if (ci->size_pts == width_pts)
5588 return;
5590 ci->size_pts = width_pts;
5591 colrow_compute_pixels_from_pts (ci, sheet, TRUE, -1);
5593 sheet->priv->recompute_visibility = TRUE;
5594 sheet_flag_recompute_spans (sheet);
5595 if (sheet->priv->reposition_objects.col > col)
5596 sheet->priv->reposition_objects.col = col;
5599 void
5600 sheet_col_set_size_pixels (Sheet *sheet, int col, int width_pixels,
5601 gboolean set_by_user)
5603 ColRowInfo *ci;
5605 g_return_if_fail (IS_SHEET (sheet));
5606 g_return_if_fail (width_pixels > 0.0);
5608 ci = sheet_col_fetch (sheet, col);
5609 ci->hard_size = set_by_user;
5610 if (ci->size_pixels == width_pixels)
5611 return;
5613 ci->size_pixels = width_pixels;
5614 colrow_compute_pts_from_pixels (ci, sheet, TRUE, -1);
5616 sheet->priv->recompute_visibility = TRUE;
5617 sheet_flag_recompute_spans (sheet);
5618 if (sheet->priv->reposition_objects.col > col)
5619 sheet->priv->reposition_objects.col = col;
5623 * sheet_col_get_default_size_pts:
5625 * Return the default number of pts in a column, including margins.
5626 * This function returns the raw sum, no rounding etc.
5628 double
5629 sheet_col_get_default_size_pts (Sheet const *sheet)
5631 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5632 return sheet->cols.default_style.size_pts;
5636 sheet_col_get_default_size_pixels (Sheet const *sheet)
5638 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5639 return sheet->cols.default_style.size_pixels;
5642 void
5643 sheet_col_set_default_size_pts (Sheet *sheet, double width_pts)
5645 g_return_if_fail (IS_SHEET (sheet));
5646 g_return_if_fail (width_pts > 0.);
5648 sheet_colrow_default_calc (sheet, width_pts, TRUE, TRUE);
5649 sheet->priv->recompute_visibility = TRUE;
5650 sheet_flag_recompute_spans (sheet);
5651 sheet->priv->reposition_objects.col = 0;
5653 void
5654 sheet_col_set_default_size_pixels (Sheet *sheet, int width_pixels)
5656 g_return_if_fail (IS_SHEET (sheet));
5658 sheet_colrow_default_calc (sheet, width_pixels, TRUE, FALSE);
5659 sheet->priv->recompute_visibility = TRUE;
5660 sheet_flag_recompute_spans (sheet);
5661 sheet->priv->reposition_objects.col = 0;
5664 /**************************************************************************/
5665 /* Row height support routines
5669 * sheet_row_get_distance_pts:
5671 * Return the number of points between from_row to to_row
5672 * measured from the upper left corner.
5674 double
5675 sheet_row_get_distance_pts (Sheet const *sheet, int from, int to)
5677 ColRowSegment const *segment;
5678 ColRowInfo const *ri;
5679 double const default_size = sheet->rows.default_style.size_pts;
5680 double pts = 0., sign = 1.;
5681 int i;
5683 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5685 if (from > to) {
5686 int const tmp = to;
5687 to = from;
5688 from = tmp;
5689 sign = -1.;
5692 g_return_val_if_fail (from >= 0, 1.);
5693 g_return_val_if_fail (to <= gnm_sheet_get_max_rows (sheet), 1.);
5695 /* Do not use col_row_collection_foreach, it ignores empties.
5696 * Optimize this so that long jumps are not quite so horrific
5697 * for performance.
5699 for (i = from ; i < to ; ++i) {
5700 segment = COLROW_GET_SEGMENT (&(sheet->rows), i);
5702 if (segment != NULL) {
5703 ri = segment->info[COLROW_SUB_INDEX (i)];
5704 if (ri == NULL)
5705 pts += default_size;
5706 else if (ri->visible)
5707 pts += ri->size_pts;
5708 } else {
5709 int segment_end = COLROW_SEGMENT_END (i)+1;
5710 if (segment_end > to)
5711 segment_end = to;
5712 pts += default_size * (segment_end - i);
5713 i = segment_end-1;
5717 return pts*sign;
5721 * sheet_row_get_distance_pixels:
5723 * Return the number of pixels between from_row to to_row
5724 * measured from the upper left corner.
5727 sheet_row_get_distance_pixels (Sheet const *sheet, int from, int to)
5729 ColRowInfo const *ci;
5730 int dflt, pixels = 0, sign = 1;
5731 int i;
5733 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5735 if (from > to) {
5736 int const tmp = to;
5737 to = from;
5738 from = tmp;
5739 sign = -1;
5742 g_return_val_if_fail (from >= 0, 1);
5743 g_return_val_if_fail (to <= gnm_sheet_get_max_rows (sheet), 1);
5745 /* Do not use col_row_collection_foreach, it ignores empties */
5746 dflt = sheet_row_get_default_size_pixels (sheet);
5747 for (i = from ; i < to ; ++i) {
5748 if (NULL == (ci = sheet_row_get (sheet, i)))
5749 pixels += dflt;
5750 else if (ci->visible)
5751 pixels += ci->size_pixels;
5754 return pixels * sign;
5758 * sheet_row_set_size_pts:
5759 * @sheet: The sheet
5760 * @row: The row
5761 * @height_pts: The desired height in pts
5762 * @set_by_user: TRUE if this was done by a user (ie, user manually
5763 * set the height)
5765 * Sets height of a row in pts, INCLUDING top and bottom margins, and the lower
5766 * grid line. This is a low level internal routine. It does NOT redraw,
5767 * or reposition objects.
5769 void
5770 sheet_row_set_size_pts (Sheet *sheet, int row, double height_pts,
5771 gboolean set_by_user)
5773 ColRowInfo *ri;
5775 g_return_if_fail (IS_SHEET (sheet));
5776 g_return_if_fail (height_pts > 0.0);
5778 ri = sheet_row_fetch (sheet, row);
5779 ri->hard_size = set_by_user;
5780 if (ri->size_pts == height_pts)
5781 return;
5783 ri->size_pts = height_pts;
5784 colrow_compute_pixels_from_pts (ri, sheet, FALSE, -1);
5786 sheet->priv->recompute_visibility = TRUE;
5787 if (sheet->priv->reposition_objects.row > row)
5788 sheet->priv->reposition_objects.row = row;
5792 * sheet_row_set_size_pixels:
5793 * @sheet: The sheet
5794 * @row: The row
5795 * @height_pixels: The desired height
5796 * @set_by_user: TRUE if this was done by a user (ie, user manually
5797 * set the width)
5799 * Sets height of a row in pixels, INCLUDING top and bottom margins, and the lower
5800 * grid line.
5802 void
5803 sheet_row_set_size_pixels (Sheet *sheet, int row, int height_pixels,
5804 gboolean set_by_user)
5806 ColRowInfo *ri;
5808 g_return_if_fail (IS_SHEET (sheet));
5809 g_return_if_fail (height_pixels > 0);
5811 ri = sheet_row_fetch (sheet, row);
5812 ri->hard_size = set_by_user;
5813 if (ri->size_pixels == height_pixels)
5814 return;
5816 ri->size_pixels = height_pixels;
5817 colrow_compute_pts_from_pixels (ri, sheet, FALSE, -1);
5819 sheet->priv->recompute_visibility = TRUE;
5820 if (sheet->priv->reposition_objects.row > row)
5821 sheet->priv->reposition_objects.row = row;
5825 * sheet_row_get_default_size_pts:
5827 * Return the defaul number of units in a row, including margins.
5828 * This function returns the raw sum, no rounding etc.
5830 double
5831 sheet_row_get_default_size_pts (Sheet const *sheet)
5833 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5834 return sheet->rows.default_style.size_pts;
5838 sheet_row_get_default_size_pixels (Sheet const *sheet)
5840 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5841 return sheet->rows.default_style.size_pixels;
5844 void
5845 sheet_row_set_default_size_pts (Sheet *sheet, double height_pts)
5847 g_return_if_fail (IS_SHEET (sheet));
5849 sheet_colrow_default_calc (sheet, height_pts, FALSE, TRUE);
5850 sheet->priv->recompute_visibility = TRUE;
5851 sheet->priv->reposition_objects.row = 0;
5854 void
5855 sheet_row_set_default_size_pixels (Sheet *sheet, int height_pixels)
5857 g_return_if_fail (IS_SHEET (sheet));
5859 sheet_colrow_default_calc (sheet, height_pixels, FALSE, FALSE);
5860 sheet->priv->recompute_visibility = TRUE;
5861 sheet->priv->reposition_objects.row = 0;
5864 /****************************************************************************/
5866 void
5867 sheet_scrollbar_config (Sheet const *sheet)
5869 g_return_if_fail (IS_SHEET (sheet));
5871 SHEET_FOREACH_CONTROL (sheet, view, control,
5872 sc_scrollbar_config (control););
5875 /*****************************************************************************/
5876 typedef struct
5878 gboolean is_column;
5879 Sheet *sheet;
5880 } closure_clone_colrow;
5882 static gboolean
5883 sheet_clone_colrow_info_item (GnmColRowIter const *iter, void *user_data)
5885 closure_clone_colrow const *closure = user_data;
5886 ColRowInfo *new_colrow = sheet_colrow_fetch (closure->sheet,
5887 iter->pos, closure->is_column);
5888 col_row_info_copy (new_colrow, iter->cri);
5889 return FALSE;
5892 static void
5893 sheet_dup_colrows (Sheet const *src, Sheet *dst)
5895 closure_clone_colrow closure;
5896 int max_col = MIN (gnm_sheet_get_max_cols (src), gnm_sheet_get_max_cols (dst)),
5897 max_row = MIN (gnm_sheet_get_max_rows (src), gnm_sheet_get_max_rows (dst));
5899 closure.sheet = dst;
5900 closure.is_column = TRUE;
5901 col_row_collection_foreach (&src->cols, 0, max_col - 1,
5902 &sheet_clone_colrow_info_item, &closure);
5903 closure.is_column = FALSE;
5904 col_row_collection_foreach (&src->rows, 0, max_row - 1,
5905 &sheet_clone_colrow_info_item, &closure);
5907 sheet_col_set_default_size_pixels (dst,
5908 sheet_col_get_default_size_pixels (src));
5909 sheet_row_set_default_size_pixels (dst,
5910 sheet_row_get_default_size_pixels (src));
5912 dst->cols.max_outline_level = src->cols.max_outline_level;
5913 dst->rows.max_outline_level = src->rows.max_outline_level;
5916 static void
5917 sheet_dup_styles (Sheet const *src, Sheet *dst)
5919 static GnmCellPos const corner = { 0, 0 };
5920 GnmRange r;
5921 GnmStyleList *styles;
5923 sheet_style_set_auto_pattern_color (
5924 dst, sheet_style_get_auto_pattern_color (src));
5926 styles = sheet_style_get_range (src, range_init_full_sheet (&r, src));
5927 sheet_style_set_list (dst, &corner, styles, NULL, NULL);
5928 style_list_free (styles);
5931 static void
5932 sheet_dup_merged_regions (Sheet const *src, Sheet *dst)
5934 GSList *ptr;
5936 for (ptr = src->list_merged ; ptr != NULL ; ptr = ptr->next)
5937 gnm_sheet_merge_add (dst, ptr->data, FALSE, NULL);
5940 static void
5941 sheet_dup_names (Sheet const *src, Sheet *dst)
5943 GSList *names = gnm_named_expr_collection_list (src->names);
5944 GSList *l;
5945 GnmParsePos dst_pp;
5947 if (names == NULL)
5948 return;
5950 parse_pos_init_sheet (&dst_pp, dst);
5952 /* Pass 1: add placeholders. */
5953 for (l = names; l; l = l->next) {
5954 GnmNamedExpr *src_nexpr = l->data;
5955 char const *name = expr_name_name (src_nexpr);
5956 GnmNamedExpr *dst_nexpr =
5957 gnm_named_expr_collection_lookup (dst->names, name);
5958 GnmExprTop const *texpr;
5960 if (dst_nexpr)
5961 continue;
5963 texpr = gnm_expr_top_new_constant (value_new_empty ());
5964 expr_name_add (&dst_pp, name, texpr , NULL, TRUE, NULL);
5967 /* Pass 2: assign the right expression. */
5968 for (l = names; l; l = l->next) {
5969 GnmNamedExpr *src_nexpr = l->data;
5970 char const *name = expr_name_name (src_nexpr);
5971 GnmNamedExpr *dst_nexpr =
5972 gnm_named_expr_collection_lookup (dst->names, name);
5973 GnmExprTop const *texpr;
5975 if (!dst_nexpr) {
5976 g_warning ("Trouble while duplicating name %s", name);
5977 continue;
5980 if (!dst_nexpr->is_editable)
5981 continue;
5983 texpr = gnm_expr_top_relocate_sheet (src_nexpr->texpr, src, dst);
5984 expr_name_set_expr (dst_nexpr, texpr);
5987 g_slist_free (names);
5990 static void
5991 cb_sheet_cell_copy (G_GNUC_UNUSED gpointer unused, gpointer key, gpointer new_sheet_param)
5993 GnmCell const *cell = key;
5994 Sheet *dst = new_sheet_param;
5995 Sheet *src;
5996 GnmExprTop const *texpr;
5998 g_return_if_fail (dst != NULL);
5999 g_return_if_fail (cell != NULL);
6001 src = cell->base.sheet;
6002 texpr = cell->base.texpr;
6004 if (texpr && gnm_expr_top_is_array_corner (texpr)) {
6005 int cols, rows;
6007 texpr = gnm_expr_top_relocate_sheet (texpr, src, dst);
6008 gnm_expr_top_get_array_size (texpr, &cols, &rows);
6010 gnm_cell_set_array_formula (dst,
6011 cell->pos.col, cell->pos.row,
6012 cell->pos.col + cols - 1,
6013 cell->pos.row + rows - 1,
6014 gnm_expr_top_new (gnm_expr_copy (gnm_expr_top_get_array_expr (texpr))));
6016 gnm_expr_top_unref (texpr);
6017 } else if (texpr && gnm_expr_top_is_array_elem (texpr, NULL, NULL)) {
6018 /* Not a corner -- ignore. */
6019 } else {
6020 GnmCell *new_cell = sheet_cell_create (dst, cell->pos.col, cell->pos.row);
6021 if (gnm_cell_has_expr (cell)) {
6022 texpr = gnm_expr_top_relocate_sheet (texpr, src, dst);
6023 gnm_cell_set_expr_and_value (new_cell, texpr, value_new_empty (), TRUE);
6024 gnm_expr_top_unref (texpr);
6025 } else
6026 gnm_cell_set_value (new_cell, value_dup (cell->value));
6030 static void
6031 sheet_dup_cells (Sheet const *src, Sheet *dst)
6033 sheet_cell_foreach (src, &cb_sheet_cell_copy, dst);
6034 sheet_region_queue_recalc (dst, NULL);
6037 static void
6038 sheet_dup_filters (Sheet const *src, Sheet *dst)
6040 GSList *ptr;
6041 for (ptr = src->filters ; ptr != NULL ; ptr = ptr->next)
6042 gnm_filter_dup (ptr->data, dst);
6043 dst->filters = g_slist_reverse (dst->filters);
6047 * sheet_dup:
6048 * @source_sheet: #Sheet
6050 * Create a new Sheet and return it.
6051 * Returns: (transfer full): the newly allocated #Sheet.
6053 Sheet *
6054 sheet_dup (Sheet const *src)
6056 Workbook *wb;
6057 Sheet *dst;
6058 char *name;
6059 GList *l;
6061 g_return_val_if_fail (IS_SHEET (src), NULL);
6062 g_return_val_if_fail (src->workbook != NULL, NULL);
6064 wb = src->workbook;
6065 name = workbook_sheet_get_free_name (wb, src->name_unquoted,
6066 TRUE, TRUE);
6067 dst = sheet_new_with_type (wb, name, src->sheet_type,
6068 src->size.max_cols, src->size.max_rows);
6069 g_free (name);
6071 dst->protected_allow = src->protected_allow;
6072 g_object_set (dst,
6073 "zoom-factor", src->last_zoom_factor_used,
6074 "text-is-rtl", src->text_is_rtl,
6075 "visibility", src->visibility,
6076 "protected", src->is_protected,
6077 "display-formulas", src->display_formulas,
6078 "display-zeros", !src->hide_zero,
6079 "display-grid", !src->hide_grid,
6080 "display-column-header", !src->hide_col_header,
6081 "display-row-header", !src->hide_row_header,
6082 "display-outlines", src->display_outlines,
6083 "display-outlines-below", src->outline_symbols_below,
6084 "display-outlines-right", src->outline_symbols_right,
6085 "conventions", src->convs,
6086 "tab-foreground", src->tab_text_color,
6087 "tab-background", src->tab_color,
6088 NULL);
6090 gnm_print_info_free (dst->print_info);
6091 dst->print_info = gnm_print_info_dup (src->print_info);
6093 sheet_dup_styles (src, dst);
6094 sheet_dup_merged_regions (src, dst);
6095 sheet_dup_colrows (src, dst);
6096 sheet_dup_names (src, dst);
6097 sheet_dup_cells (src, dst);
6098 sheet_objects_dup (src, dst, NULL);
6099 sheet_dup_filters (src, dst); /* must be after objects */
6101 #warning selection is in view
6102 #warning freeze/thaw is in view
6104 g_object_unref (dst->solver_parameters);
6105 dst->solver_parameters = gnm_solver_param_dup (src->solver_parameters, dst);
6107 for (l = src->scenarios; l; l = l->next) {
6108 GnmScenario *src_sc = l->data;
6109 GnmScenario *dst_sc = gnm_scenario_dup (src_sc, dst);
6110 dst->scenarios = g_list_prepend (dst->scenarios, dst_sc);
6112 dst->scenarios = g_list_reverse (dst->scenarios);
6114 sheet_mark_dirty (dst);
6115 sheet_redraw_all (dst, TRUE);
6117 return dst;
6121 * sheet_set_outline_direction:
6122 * @sheet: the sheet
6123 * @is_cols: use cols or rows
6125 * When changing the placement of outline collapse markers the flags
6126 * need to be recomputed.
6128 void
6129 sheet_set_outline_direction (Sheet *sheet, gboolean is_cols)
6131 unsigned i;
6132 g_return_if_fail (IS_SHEET (sheet));
6134 /* not particularly efficient, but this is not a hot spot */
6135 for (i = colrow_max (is_cols, sheet); i-- > 0 ; )
6136 sheet_colrow_set_collapse (sheet, is_cols, i);
6140 * sheet_get_view:
6141 * @sheet:
6142 * @wbv:
6144 * Find the SheetView corresponding to the supplied @wbv.
6145 * Returns: (transfer none): the view.
6147 SheetView *
6148 sheet_get_view (Sheet const *sheet, WorkbookView const *wbv)
6150 if (sheet == NULL)
6151 return NULL;
6153 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6155 SHEET_FOREACH_VIEW (sheet, view, {
6156 if (sv_wbv (view) == wbv)
6157 return view;
6159 return NULL;
6162 static gboolean
6163 cb_queue_respan (GnmColRowIter const *iter, void *user_data)
6165 ((ColRowInfo *)(iter->cri))->needs_respan = TRUE;
6166 return FALSE;
6170 * sheet_queue_respan:
6171 * @sheet:
6172 * @start_row:
6173 * @end_row:
6175 * queues a span generation for the selected rows.
6176 * the caller is responsible for queuing a redraw
6178 void
6179 sheet_queue_respan (Sheet const *sheet, int start_row, int end_row)
6181 col_row_collection_foreach (&sheet->rows, start_row, end_row,
6182 cb_queue_respan, NULL);
6185 void
6186 sheet_cell_queue_respan (GnmCell *cell)
6188 ColRowInfo *ri = sheet_row_get (cell->base.sheet, cell->pos.row);
6189 ri->needs_respan = TRUE;
6194 * sheet_get_comment:
6195 * @sheet: #Sheet const *
6196 * @pos: #GnmCellPos const *
6198 * If there is a cell comment at @pos in @sheet return it.
6200 * Caller does get a reference to the object if it exists.
6201 * Returns: (transfer full): the comment or %NULL.
6203 GnmComment *
6204 sheet_get_comment (Sheet const *sheet, GnmCellPos const *pos)
6206 GnmRange r;
6207 GSList *comments;
6208 GnmComment *res;
6210 GnmRange const *mr;
6212 mr = gnm_sheet_merge_contains_pos (sheet, pos);
6214 if (mr)
6215 comments = sheet_objects_get (sheet, mr, GNM_CELL_COMMENT_TYPE);
6216 else {
6217 r.start = r.end = *pos;
6218 comments = sheet_objects_get (sheet, &r, GNM_CELL_COMMENT_TYPE);
6220 if (!comments)
6221 return NULL;
6223 /* This assumes just one comment per cell. */
6224 res = comments->data;
6225 g_slist_free (comments);
6226 return res;
6229 static GnmValue *
6230 cb_find_extents (GnmCellIter const *iter, GnmCellPos *extent)
6232 if (extent->col < iter->pp.eval.col)
6233 extent->col = iter->pp.eval.col;
6234 if (extent->row < iter->pp.eval.row)
6235 extent->row = iter->pp.eval.row;
6236 return NULL;
6240 * sheet_range_trim:
6241 * @sheet: sheet cells are contained on
6242 * @r: range to trim empty cells from
6243 * @cols: trim from right
6244 * @rows: trim from bottom
6246 * This removes empty rows/cols from the
6247 * right hand or bottom edges of the range
6248 * depending on the value of @cols or @rows.
6250 * Return value: TRUE if the range was totally empty.
6252 gboolean
6253 sheet_range_trim (Sheet const *sheet, GnmRange *r,
6254 gboolean cols, gboolean rows)
6256 GnmCellPos extent = { -1, -1 };
6258 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
6259 g_return_val_if_fail (r != NULL, TRUE);
6261 sheet_foreach_cell_in_range (
6262 (Sheet *)sheet, CELL_ITER_IGNORE_BLANK,
6263 r->start.col, r->start.row, r->end.col, r->end.row,
6264 (CellIterFunc) cb_find_extents, &extent);
6266 if (extent.col < 0 || extent.row < 0)
6267 return TRUE;
6268 if (cols)
6269 r->end.col = extent.col;
6270 if (rows)
6271 r->end.row = extent.row;
6272 return FALSE;
6276 * sheet_range_has_heading:
6277 * @sheet: Sheet to check
6278 * @src: GnmRange to check
6279 * @top: Flag
6281 * Checks for a header row in @sheet!@src. If top is true it looks for a
6282 * header row from the top and if false it looks for a header col from the
6283 * left
6285 * Returns: TRUE if @src seems to have a heading
6287 gboolean
6288 sheet_range_has_heading (Sheet const *sheet, GnmRange const *src,
6289 gboolean top, gboolean ignore_styles)
6291 GnmCell const *a, *b;
6292 int length, i;
6294 /* There is only one row or col */
6295 if (top) {
6296 if (src->end.row <= src->start.row)
6297 return FALSE;
6298 length = src->end.col - src->start.col + 1;
6299 } else {
6300 if (src->end.col <= src->start.col)
6301 return FALSE;
6302 length = src->end.row - src->start.row + 1;
6305 for (i = 0; i < length; i++) {
6306 if (top) {
6307 a = sheet_cell_get (sheet,
6308 src->start.col + i, src->start.row);
6309 b = sheet_cell_get (sheet,
6310 src->start.col + i, src->start.row + 1);
6311 } else {
6312 a = sheet_cell_get (sheet,
6313 src->start.col, src->start.row + i);
6314 b = sheet_cell_get (sheet,
6315 src->start.col + 1, src->start.row + i);
6318 /* be anal */
6319 if (a == NULL || a->value == NULL || b == NULL || b->value == NULL)
6320 continue;
6322 if (VALUE_IS_NUMBER (a->value)) {
6323 if (!VALUE_IS_NUMBER (b->value))
6324 return TRUE;
6325 /* check for style differences */
6326 } else if (a->value->v_any.type != b->value->v_any.type)
6327 return TRUE;
6329 /* Look for style differences */
6330 if (!ignore_styles &&
6331 !gnm_style_equal_header (gnm_cell_get_style (a),
6332 gnm_cell_get_style (b), top))
6333 return TRUE;
6336 return FALSE;
6340 * gnm_sheet_foreach_name:
6341 * @sheet: #Sheet
6342 * @func: (scope call): #GHFunc
6343 * @data: user data.
6345 * Executes @func for each name in @sheet.
6347 void
6348 gnm_sheet_foreach_name (Sheet const *sheet, GHFunc func, gpointer data)
6350 g_return_if_fail (IS_SHEET (sheet));
6352 if (sheet->names)
6353 gnm_named_expr_collection_foreach (sheet->names, func, data);
6357 * gnm_sheet_get_size:
6358 * @sheet: #Sheet
6360 * Returns: (transfer none): the sheet size.
6362 GnmSheetSize const *
6363 gnm_sheet_get_size (Sheet const *sheet)
6365 static const GnmSheetSize default_size = {
6366 GNM_DEFAULT_COLS, GNM_DEFAULT_ROWS
6369 if (G_UNLIKELY (!sheet)) {
6370 g_warning ("NULL sheet in gnm_sheet_get_size!");
6371 /* FIXME: This needs to go. */
6372 return &default_size;
6375 if (G_UNLIKELY (sheet->being_constructed))
6376 g_warning ("Access to sheet size during construction!");
6378 return &sheet->size;
6382 * gnm_sheet_get_size2:
6383 * @sheet: #Sheet, might be %NULL
6384 * @wb: #Workbook, must be non %NULL if @sheet is %NULL
6386 * Returns: (transfer none): the sheet size if @sheet is non %NULL, or the
6387 * default sheet size for @wb.
6389 GnmSheetSize const *
6390 gnm_sheet_get_size2 (Sheet const *sheet, Workbook const *wb)
6392 return sheet
6393 ? gnm_sheet_get_size (sheet)
6394 : workbook_get_sheet_size (wb);
6397 void
6398 gnm_sheet_set_solver_params (Sheet *sheet, GnmSolverParameters *param)
6400 g_return_if_fail (IS_SHEET (sheet));
6401 g_return_if_fail (GNM_IS_SOLVER_PARAMETERS (param));
6403 g_object_ref (param);
6404 g_object_unref (sheet->solver_parameters);
6405 sheet->solver_parameters = param;
6408 /* ------------------------------------------------------------------------- */
6411 * gnm_sheet_scenario_new:
6412 * @sheet:  #Sheet
6413 * @name: the new scenario name.
6415 * Returns: (transfer full): the newly created #GnmScenario.
6417 GnmScenario *
6418 gnm_sheet_scenario_new (Sheet *sheet, const char *name)
6420 GnmScenario *sc;
6421 char *actual_name;
6423 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6424 g_return_val_if_fail (name != NULL, NULL);
6426 /* Check if a scenario having the same name already exists. */
6427 if (gnm_sheet_scenario_find (sheet, name)) {
6428 GString *str = g_string_new (NULL);
6429 gchar *tmp;
6430 int i, j, len;
6432 len = strlen (name);
6433 if (len > 1 && name [len - 1] == ']') {
6434 for (i = len - 2; i > 0; i--) {
6435 if (! g_ascii_isdigit (name [i]))
6436 break;
6439 tmp = g_strdup (name);
6440 if (i > 0 && name [i] == '[')
6441 tmp [i] = '\0';
6442 } else
6443 tmp = g_strdup (name);
6445 for (j = 1; ; j++) {
6446 g_string_printf (str, "%s [%d]", tmp, j);
6447 if (!gnm_sheet_scenario_find (sheet, str->str)) {
6448 actual_name = g_string_free (str, FALSE);
6449 str = NULL;
6450 break;
6453 if (str)
6454 g_string_free (str, TRUE);
6455 g_free (tmp);
6456 } else
6457 actual_name = g_strdup (name);
6459 sc = gnm_scenario_new (actual_name, sheet);
6461 g_free (actual_name);
6463 return sc;
6467 * gnm_sheet_scenario_find:
6468 * @sheet:  #Sheet
6469 * @name: the scenario name.
6471 * Returns: (transfer none): the newly created #GnmScenario.
6473 GnmScenario *
6474 gnm_sheet_scenario_find (Sheet *sheet, const char *name)
6476 GList *l;
6478 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6479 g_return_val_if_fail (name != NULL, NULL);
6481 for (l = sheet->scenarios; l; l = l->next) {
6482 GnmScenario *sc = l->data;
6483 if (strcmp (name, sc->name) == 0)
6484 return sc;
6487 return NULL;
6491 * gnm_sheet_scenario_add:
6492 * @sheet:  #Sheet
6493 * @sc: (transfer full): #GnmScenario
6496 void
6497 gnm_sheet_scenario_add (Sheet *sheet, GnmScenario *sc)
6499 g_return_if_fail (IS_SHEET (sheet));
6500 g_return_if_fail (GNM_IS_SCENARIO (sc));
6502 /* We take ownership of the ref. */
6503 sheet->scenarios = g_list_append (sheet->scenarios, sc);
6506 void
6507 gnm_sheet_scenario_remove (Sheet *sheet, GnmScenario *sc)
6509 g_return_if_fail (IS_SHEET (sheet));
6510 g_return_if_fail (GNM_IS_SCENARIO (sc));
6512 sheet->scenarios = g_list_remove (sheet->scenarios, sc);
6513 g_object_unref (sc);
6516 /* ------------------------------------------------------------------------- */
6519 * gnm_sheet_get_sort_setups:
6520 * @sheet: #Sheet
6522 * Returns: (transfer none): the sort setups for @sheet.
6524 GHashTable *
6525 gnm_sheet_get_sort_setups (Sheet *sheet)
6527 GHashTable *hash = sheet->sort_setups;
6529 if (hash == NULL)
6530 hash = sheet->sort_setups =
6531 g_hash_table_new_full
6532 (g_str_hash, g_str_equal,
6533 g_free, (GDestroyNotify)gnm_sort_data_destroy);
6535 return hash;
6538 void
6539 gnm_sheet_add_sort_setup (Sheet *sheet, char *key, gpointer setup)
6541 GHashTable *hash = gnm_sheet_get_sort_setups (sheet);
6543 g_hash_table_insert (hash, key, setup);
6547 * gnm_sheet_find_sort_setup:
6548 * @sheet: #Sheet
6549 * @key:
6551 * Returns: (transfer none): the found sort setup or %NULL.
6553 gconstpointer
6554 gnm_sheet_find_sort_setup (Sheet *sheet, char const *key)
6556 if (sheet->sort_setups == NULL)
6557 return NULL;
6558 return g_hash_table_lookup (sheet->sort_setups, key);