GUI: Move .ui files from goffice resources to glib resources
[gnumeric.git] / src / sheet.c
bloba947b62aeb04385e92953fe6b555cea3918258d9
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 col_row_collection_resize (ColRowCollection *infos, int size)
161 int end_idx = COLROW_SEGMENT_INDEX (size);
162 int i = infos->info->len - 1;
164 while (i >= end_idx) {
165 ColRowSegment *segment = g_ptr_array_index (infos->info, i);
166 if (segment) {
167 g_free (segment);
168 g_ptr_array_index (infos->info, i) = NULL;
170 i--;
173 g_ptr_array_set_size (infos->info, end_idx);
176 static void
177 sheet_set_direction (Sheet *sheet, gboolean text_is_rtl)
179 GnmRange r;
181 text_is_rtl = !!text_is_rtl;
182 if (text_is_rtl == sheet->text_is_rtl)
183 return;
185 sheet_mark_dirty (sheet);
187 sheet->text_is_rtl = text_is_rtl;
188 sheet->priv->reposition_objects.col = 0;
189 sheet_range_calc_spans (sheet,
190 range_init_full_sheet (&r, sheet),
191 GNM_SPANCALC_RE_RENDER);
194 static void
195 sheet_set_visibility (Sheet *sheet, GnmSheetVisibility visibility)
197 if (sheet->visibility == visibility)
198 return;
200 sheet->visibility = visibility;
201 sheet_mark_dirty (sheet);
204 static void
205 cb_re_render_formulas (G_GNUC_UNUSED gpointer unused,
206 GnmCell *cell,
207 G_GNUC_UNUSED gpointer user)
209 if (gnm_cell_has_expr (cell)) {
210 gnm_cell_unrender (cell);
211 sheet_cell_queue_respan (cell);
215 static void
216 re_render_formulas (Sheet const *sheet)
218 sheet_cell_foreach (sheet, (GHFunc)cb_re_render_formulas, NULL);
221 static void
222 sheet_set_conventions (Sheet *sheet, GnmConventions const *convs)
224 if (sheet->convs == convs)
225 return;
226 gnm_conventions_unref (sheet->convs);
227 sheet->convs = gnm_conventions_ref (convs);
228 if (sheet->display_formulas)
229 re_render_formulas (sheet);
230 SHEET_FOREACH_VIEW (sheet, sv,
231 sv->edit_pos_changed.content = TRUE;);
232 sheet_mark_dirty (sheet);
235 GnmConventions const *
236 sheet_get_conventions (Sheet const *sheet)
238 g_return_val_if_fail (IS_SHEET (sheet), gnm_conventions_default);
240 return sheet->convs;
243 static void
244 cb_sheet_set_hide_zeros (G_GNUC_UNUSED gpointer unused,
245 GnmCell *cell,
246 G_GNUC_UNUSED gpointer user)
248 if (gnm_cell_is_zero (cell))
249 gnm_cell_unrender (cell);
252 static void
253 sheet_set_hide_zeros (Sheet *sheet, gboolean hide)
255 hide = !!hide;
256 if (sheet->hide_zero == hide)
257 return;
259 sheet->hide_zero = hide;
260 sheet_mark_dirty (sheet);
262 sheet_cell_foreach (sheet, (GHFunc)cb_sheet_set_hide_zeros, NULL);
265 static void
266 sheet_set_name (Sheet *sheet, char const *new_name)
268 Workbook *wb = sheet->workbook;
269 gboolean attached;
270 Sheet *sucker;
271 char *new_name_unquoted;
273 g_return_if_fail (new_name != NULL);
275 /* No change whatsoever. */
276 if (go_str_compare (sheet->name_unquoted, new_name) == 0)
277 return;
279 /* Mark the sheet dirty unless this is the initial name. */
280 if (sheet->name_unquoted)
281 sheet_mark_dirty (sheet);
283 sucker = wb ? workbook_sheet_by_name (wb, new_name) : NULL;
284 if (sucker && sucker != sheet) {
286 * Prevent a name clash. With this you can swap names by
287 * setting just the two names.
289 char *sucker_name = workbook_sheet_get_free_name (wb, new_name, TRUE, FALSE);
290 #if 0
291 g_warning ("Renaming %s to %s to avoid clash.\n", sucker->name_unquoted, sucker_name);
292 #endif
293 g_object_set (sucker, "name", sucker_name, NULL);
294 g_free (sucker_name);
297 attached = wb != NULL &&
298 sheet->index_in_wb != -1 &&
299 sheet->name_case_insensitive;
301 /* FIXME: maybe have workbook_sheet_detach_internal for this. */
302 if (attached)
303 g_hash_table_remove (wb->sheet_hash_private,
304 sheet->name_case_insensitive);
306 /* Copy before free. */
307 new_name_unquoted = g_strdup (new_name);
309 g_free (sheet->name_unquoted);
310 g_free (sheet->name_quoted);
311 g_free (sheet->name_unquoted_collate_key);
312 g_free (sheet->name_case_insensitive);
313 sheet->name_unquoted = new_name_unquoted;
314 sheet->name_quoted = g_string_free
315 (gnm_expr_conv_quote (sheet->convs, new_name_unquoted),
316 FALSE);
317 sheet->name_unquoted_collate_key =
318 g_utf8_collate_key (new_name_unquoted, -1);
319 sheet->name_case_insensitive =
320 g_utf8_casefold (new_name_unquoted, -1);
322 /* FIXME: maybe have workbook_sheet_attach_internal for this. */
323 if (attached)
324 g_hash_table_insert (wb->sheet_hash_private,
325 sheet->name_case_insensitive,
326 sheet);
328 if (!sheet->being_constructed &&
329 sheet->sheet_type == GNM_SHEET_DATA) {
330 /* We have to fix the Sheet_Title name */
331 GnmNamedExpr *nexpr;
332 GnmParsePos pp;
334 parse_pos_init_sheet (&pp, sheet);
335 nexpr = expr_name_lookup (&pp, "Sheet_Title");
336 if (nexpr) {
337 GnmExprTop const *texpr =
338 gnm_expr_top_new_constant
339 (value_new_string (sheet->name_unquoted));
340 expr_name_set_expr (nexpr, texpr);
345 struct resize_colrow {
346 Sheet *sheet;
347 gboolean is_cols;
348 double scale;
351 static gboolean
352 cb_colrow_compute_pixels_from_pts (GnmColRowIter const *iter,
353 gpointer data_)
355 struct resize_colrow *data = data_;
356 colrow_compute_pixels_from_pts ((ColRowInfo *)iter->cri,
357 data->sheet, data->is_cols,
358 data->scale);
359 return FALSE;
362 static void
363 cb_clear_rendered_cells (G_GNUC_UNUSED gpointer ignored, GnmCell *cell)
365 if (gnm_cell_get_rendered_value (cell) != NULL) {
366 sheet_cell_queue_respan (cell);
367 gnm_cell_unrender (cell);
371 static void
372 sheet_scale_changed (Sheet *sheet, gboolean cols_rescaled, gboolean rows_rescaled)
374 g_return_if_fail (cols_rescaled || rows_rescaled);
376 /* Then every column and row */
377 if (cols_rescaled) {
378 struct resize_colrow closure;
380 closure.sheet = sheet;
381 closure.is_cols = TRUE;
382 closure.scale = colrow_compute_pixel_scale (sheet, TRUE);
384 colrow_compute_pixels_from_pts (&sheet->cols.default_style,
385 sheet, TRUE, closure.scale);
386 sheet_colrow_foreach (sheet, TRUE, 0, -1,
387 cb_colrow_compute_pixels_from_pts,
388 &closure);
390 if (rows_rescaled) {
391 struct resize_colrow closure;
393 closure.sheet = sheet;
394 closure.is_cols = FALSE;
395 closure.scale = colrow_compute_pixel_scale (sheet, FALSE);
397 colrow_compute_pixels_from_pts (&sheet->rows.default_style,
398 sheet, FALSE, closure.scale);
399 sheet_colrow_foreach (sheet, FALSE, 0, -1,
400 cb_colrow_compute_pixels_from_pts,
401 &closure);
404 sheet_cell_foreach (sheet, (GHFunc)&cb_clear_rendered_cells, NULL);
405 SHEET_FOREACH_CONTROL (sheet, view, control, sc_scale_changed (control););
408 static void
409 sheet_set_display_formulas (Sheet *sheet, gboolean display)
411 display = !!display;
412 if (sheet->display_formulas == display)
413 return;
415 sheet->display_formulas = display;
416 sheet_mark_dirty (sheet);
417 if (!sheet->being_constructed)
418 sheet_scale_changed (sheet, TRUE, FALSE);
421 static void
422 sheet_set_zoom_factor (Sheet *sheet, double factor)
424 if (fabs (factor - sheet->last_zoom_factor_used) < 1e-6)
425 return;
426 sheet->last_zoom_factor_used = factor;
427 if (!sheet->being_constructed)
428 sheet_scale_changed (sheet, TRUE, TRUE);
431 static void
432 gnm_sheet_set_property (GObject *object, guint property_id,
433 GValue const *value, GParamSpec *pspec)
435 Sheet *sheet = (Sheet *)object;
437 switch (property_id) {
438 case PROP_SHEET_TYPE:
439 /* Construction-time only */
440 sheet->sheet_type = g_value_get_enum (value);
441 break;
442 case PROP_WORKBOOK:
443 /* Construction-time only */
444 sheet->workbook = g_value_get_object (value);
445 break;
446 case PROP_NAME:
447 sheet_set_name (sheet, g_value_get_string (value));
448 break;
449 case PROP_RTL:
450 sheet_set_direction (sheet, g_value_get_boolean (value));
451 break;
452 case PROP_VISIBILITY:
453 sheet_set_visibility (sheet, g_value_get_enum (value));
454 break;
455 case PROP_DISPLAY_FORMULAS:
456 sheet_set_display_formulas (sheet, g_value_get_boolean (value));
457 break;
458 case PROP_DISPLAY_ZEROS:
459 sheet_set_hide_zeros (sheet, !g_value_get_boolean (value));
460 break;
461 case PROP_DISPLAY_GRID:
462 sheet->hide_grid = !g_value_get_boolean (value);
463 break;
464 case PROP_DISPLAY_COLUMN_HEADER:
465 sheet->hide_col_header = !g_value_get_boolean (value);
466 break;
467 case PROP_DISPLAY_ROW_HEADER:
468 sheet->hide_row_header = !g_value_get_boolean (value);
469 break;
470 case PROP_DISPLAY_OUTLINES:
471 sheet->display_outlines = !!g_value_get_boolean (value);
472 break;
473 case PROP_DISPLAY_OUTLINES_BELOW:
474 sheet->outline_symbols_below = !!g_value_get_boolean (value);
475 break;
476 case PROP_DISPLAY_OUTLINES_RIGHT:
477 sheet->outline_symbols_right = !!g_value_get_boolean (value);
478 break;
480 case PROP_PROTECTED:
481 sheet->is_protected = !!g_value_get_boolean (value);
482 break;
483 case PROP_PROTECTED_ALLOW_EDIT_OBJECTS:
484 sheet->protected_allow.edit_objects = !!g_value_get_boolean (value);
485 break;
486 case PROP_PROTECTED_ALLOW_EDIT_SCENARIOS:
487 sheet->protected_allow.edit_scenarios = !!g_value_get_boolean (value);
488 break;
489 case PROP_PROTECTED_ALLOW_CELL_FORMATTING:
490 sheet->protected_allow.cell_formatting = !!g_value_get_boolean (value);
491 break;
492 case PROP_PROTECTED_ALLOW_COLUMN_FORMATTING:
493 sheet->protected_allow.column_formatting = !!g_value_get_boolean (value);
494 break;
495 case PROP_PROTECTED_ALLOW_ROW_FORMATTING:
496 sheet->protected_allow.row_formatting = !!g_value_get_boolean (value);
497 break;
498 case PROP_PROTECTED_ALLOW_INSERT_COLUMNS:
499 sheet->protected_allow.insert_columns = !!g_value_get_boolean (value);
500 break;
501 case PROP_PROTECTED_ALLOW_INSERT_ROWS:
502 sheet->protected_allow.insert_rows = !!g_value_get_boolean (value);
503 break;
504 case PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS:
505 sheet->protected_allow.insert_hyperlinks = !!g_value_get_boolean (value);
506 break;
507 case PROP_PROTECTED_ALLOW_DELETE_COLUMNS:
508 sheet->protected_allow.delete_columns = !!g_value_get_boolean (value);
509 break;
510 case PROP_PROTECTED_ALLOW_DELETE_ROWS:
511 sheet->protected_allow.delete_rows = !!g_value_get_boolean (value);
512 break;
513 case PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS:
514 sheet->protected_allow.select_locked_cells = !!g_value_get_boolean (value);
515 break;
516 case PROP_PROTECTED_ALLOW_SORT_RANGES:
517 sheet->protected_allow.sort_ranges = !!g_value_get_boolean (value);
518 break;
519 case PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS:
520 sheet->protected_allow.edit_auto_filters = !!g_value_get_boolean (value);
521 break;
522 case PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE:
523 sheet->protected_allow.edit_pivottable = !!g_value_get_boolean (value);
524 break;
525 case PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS:
526 sheet->protected_allow.select_unlocked_cells = !!g_value_get_boolean (value);
527 break;
529 case PROP_CONVENTIONS:
530 sheet_set_conventions (sheet, g_value_get_boxed (value));
531 break;
532 case PROP_USE_R1C1: /* convenience api */
533 sheet_set_conventions (sheet, !!g_value_get_boolean (value)
534 ? gnm_conventions_xls_r1c1 : gnm_conventions_default);
535 break;
537 case PROP_TAB_FOREGROUND: {
538 GnmColor *color = g_value_dup_boxed (value);
539 style_color_unref (sheet->tab_text_color);
540 sheet->tab_text_color = color;
541 sheet_mark_dirty (sheet);
542 break;
544 case PROP_TAB_BACKGROUND: {
545 GnmColor *color = g_value_dup_boxed (value);
546 style_color_unref (sheet->tab_color);
547 sheet->tab_color = color;
548 sheet_mark_dirty (sheet);
549 break;
551 case PROP_ZOOM_FACTOR:
552 sheet_set_zoom_factor (sheet, g_value_get_double (value));
553 break;
554 case PROP_COLUMNS:
555 /* Construction-time only */
556 sheet->size.max_cols = g_value_get_int (value);
557 break;
558 case PROP_ROWS:
559 /* Construction-time only */
560 sheet->size.max_rows = g_value_get_int (value);
561 break;
562 default:
563 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
564 break;
568 static void
569 gnm_sheet_get_property (GObject *object, guint property_id,
570 GValue *value, GParamSpec *pspec)
572 Sheet *sheet = (Sheet *)object;
574 switch (property_id) {
575 case PROP_SHEET_TYPE:
576 g_value_set_enum (value, sheet->sheet_type);
577 break;
578 case PROP_WORKBOOK:
579 g_value_set_object (value, sheet->workbook);
580 break;
581 case PROP_NAME:
582 g_value_set_string (value, sheet->name_unquoted);
583 break;
584 case PROP_RTL:
585 g_value_set_boolean (value, sheet->text_is_rtl);
586 break;
587 case PROP_VISIBILITY:
588 g_value_set_enum (value, sheet->visibility);
589 break;
590 case PROP_DISPLAY_FORMULAS:
591 g_value_set_boolean (value, sheet->display_formulas);
592 break;
593 case PROP_DISPLAY_ZEROS:
594 g_value_set_boolean (value, !sheet->hide_zero);
595 break;
596 case PROP_DISPLAY_GRID:
597 g_value_set_boolean (value, !sheet->hide_grid);
598 break;
599 case PROP_DISPLAY_COLUMN_HEADER:
600 g_value_set_boolean (value, !sheet->hide_col_header);
601 break;
602 case PROP_DISPLAY_ROW_HEADER:
603 g_value_set_boolean (value, !sheet->hide_row_header);
604 break;
605 case PROP_DISPLAY_OUTLINES:
606 g_value_set_boolean (value, sheet->display_outlines);
607 break;
608 case PROP_DISPLAY_OUTLINES_BELOW:
609 g_value_set_boolean (value, sheet->outline_symbols_below);
610 break;
611 case PROP_DISPLAY_OUTLINES_RIGHT:
612 g_value_set_boolean (value, sheet->outline_symbols_right);
613 break;
615 case PROP_PROTECTED:
616 g_value_set_boolean (value, sheet->is_protected);
617 break;
618 case PROP_PROTECTED_ALLOW_EDIT_OBJECTS:
619 g_value_set_boolean (value, sheet->protected_allow.edit_objects);
620 break;
621 case PROP_PROTECTED_ALLOW_EDIT_SCENARIOS:
622 g_value_set_boolean (value, sheet->protected_allow.edit_scenarios);
623 break;
624 case PROP_PROTECTED_ALLOW_CELL_FORMATTING:
625 g_value_set_boolean (value, sheet->protected_allow.cell_formatting);
626 break;
627 case PROP_PROTECTED_ALLOW_COLUMN_FORMATTING:
628 g_value_set_boolean (value, sheet->protected_allow.column_formatting);
629 break;
630 case PROP_PROTECTED_ALLOW_ROW_FORMATTING:
631 g_value_set_boolean (value, sheet->protected_allow.row_formatting);
632 break;
633 case PROP_PROTECTED_ALLOW_INSERT_COLUMNS:
634 g_value_set_boolean (value, sheet->protected_allow.insert_columns);
635 break;
636 case PROP_PROTECTED_ALLOW_INSERT_ROWS:
637 g_value_set_boolean (value, sheet->protected_allow.insert_rows);
638 break;
639 case PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS:
640 g_value_set_boolean (value, sheet->protected_allow.insert_hyperlinks);
641 break;
642 case PROP_PROTECTED_ALLOW_DELETE_COLUMNS:
643 g_value_set_boolean (value, sheet->protected_allow.delete_columns);
644 break;
645 case PROP_PROTECTED_ALLOW_DELETE_ROWS:
646 g_value_set_boolean (value, sheet->protected_allow.delete_rows);
647 break;
648 case PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS:
649 g_value_set_boolean (value, sheet->protected_allow.select_locked_cells);
650 break;
651 case PROP_PROTECTED_ALLOW_SORT_RANGES:
652 g_value_set_boolean (value, sheet->protected_allow.sort_ranges);
653 break;
654 case PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS:
655 g_value_set_boolean (value, sheet->protected_allow.edit_auto_filters);
656 break;
657 case PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE:
658 g_value_set_boolean (value, sheet->protected_allow.edit_pivottable);
659 break;
660 case PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS:
661 g_value_set_boolean (value, sheet->protected_allow.select_unlocked_cells);
662 break;
664 case PROP_CONVENTIONS:
665 g_value_set_boxed (value, sheet->convs);
666 break;
667 case PROP_USE_R1C1: /* convenience api */
668 g_value_set_boolean (value, sheet->convs->r1c1_addresses);
669 break;
671 case PROP_TAB_FOREGROUND:
672 g_value_set_boxed (value, sheet->tab_text_color);
673 break;
674 case PROP_TAB_BACKGROUND:
675 g_value_set_boxed (value, sheet->tab_color);
676 break;
677 case PROP_ZOOM_FACTOR:
678 g_value_set_double (value, sheet->last_zoom_factor_used);
679 break;
680 case PROP_COLUMNS:
681 g_value_set_int (value, sheet->size.max_cols);
682 break;
683 case PROP_ROWS:
684 g_value_set_int (value, sheet->size.max_rows);
685 break;
686 default:
687 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
688 break;
692 static void
693 gnm_sheet_constructed (GObject *obj)
695 Sheet *sheet = SHEET (obj);
697 if (parent_class->constructed)
698 parent_class->constructed (obj);
700 /* Now sheet_type, max_cols, and max_rows have been set. */
701 sheet->being_constructed = FALSE;
703 col_row_collection_resize (&sheet->cols, sheet->size.max_cols);
704 col_row_collection_resize (&sheet->rows, sheet->size.max_rows);
706 sheet->priv->reposition_objects.col = sheet->size.max_cols;
707 sheet->priv->reposition_objects.row = sheet->size.max_rows;
709 range_init_full_sheet (&sheet->priv->unhidden_region, sheet);
710 sheet_style_init (sheet);
712 sheet->deps = gnm_dep_container_new (sheet);
714 switch (sheet->sheet_type) {
715 case GNM_SHEET_XLM:
716 sheet->display_formulas = TRUE;
717 break;
718 case GNM_SHEET_OBJECT:
719 sheet->hide_grid = TRUE;
720 sheet->hide_col_header = sheet->hide_row_header = TRUE;
721 colrow_compute_pixels_from_pts (&sheet->rows.default_style,
722 sheet, FALSE, -1);
723 colrow_compute_pixels_from_pts (&sheet->cols.default_style,
724 sheet, TRUE, -1);
725 break;
726 case GNM_SHEET_DATA: {
727 /* We have to add permanent names */
728 GnmExprTop const *texpr;
730 if (sheet->name_unquoted)
731 texpr = gnm_expr_top_new_constant
732 (value_new_string (sheet->name_unquoted));
733 else
734 texpr = gnm_expr_top_new_constant
735 (value_new_error_REF (NULL));
736 expr_name_perm_add (sheet, "Sheet_Title",
737 texpr, FALSE);
739 texpr = gnm_expr_top_new_constant
740 (value_new_error_REF (NULL));
741 expr_name_perm_add (sheet, "Print_Area",
742 texpr, FALSE);
743 break;
745 default:
746 g_assert_not_reached ();
749 sheet_scale_changed (sheet, TRUE, TRUE);
752 static guint
753 cell_set_hash (GnmCell const *key)
755 guint32 r = key->pos.row;
756 guint32 c = key->pos.col;
757 guint32 h;
759 h = r;
760 h *= (guint32)123456789;
761 h ^= c;
762 h *= (guint32)123456789;
764 return h;
767 static gint
768 cell_set_equal (GnmCell const *a, GnmCell const *b)
770 return (a->pos.row == b->pos.row && a->pos.col == b->pos.col);
773 static void
774 gnm_sheet_init (Sheet *sheet)
776 PangoContext *context;
778 sheet->priv = g_new0 (SheetPrivate, 1);
779 sheet->being_constructed = TRUE;
781 sheet->sheet_views = g_ptr_array_new ();
783 /* Init, focus, and load handle setting these if/when necessary */
784 sheet->priv->recompute_visibility = TRUE;
785 sheet->priv->recompute_spans = TRUE;
787 sheet->is_protected = FALSE;
788 sheet->protected_allow.edit_scenarios = FALSE;
789 sheet->protected_allow.cell_formatting = FALSE;
790 sheet->protected_allow.column_formatting = FALSE;
791 sheet->protected_allow.row_formatting = FALSE;
792 sheet->protected_allow.insert_columns = FALSE;
793 sheet->protected_allow.insert_rows = FALSE;
794 sheet->protected_allow.insert_hyperlinks = FALSE;
795 sheet->protected_allow.delete_columns = FALSE;
796 sheet->protected_allow.delete_rows = FALSE;
797 sheet->protected_allow.select_locked_cells = TRUE;
798 sheet->protected_allow.sort_ranges = FALSE;
799 sheet->protected_allow.edit_auto_filters = FALSE;
800 sheet->protected_allow.edit_pivottable = FALSE;
801 sheet->protected_allow.select_unlocked_cells = TRUE;
803 sheet->hide_zero = FALSE;
804 sheet->display_outlines = TRUE;
805 sheet->outline_symbols_below = TRUE;
806 sheet->outline_symbols_right = TRUE;
807 sheet->tab_color = NULL;
808 sheet->tab_text_color = NULL;
809 sheet->visibility = GNM_SHEET_VISIBILITY_VISIBLE;
810 #ifdef GNM_WITH_GTK
811 sheet->text_is_rtl = (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL);
812 #else
813 sheet->text_is_rtl = FALSE;
814 #endif
816 sheet->sheet_objects = NULL;
817 sheet->max_object_extent.col = sheet->max_object_extent.row = 0;
819 sheet->solver_parameters = gnm_solver_param_new (sheet);
821 sheet->cols.max_used = -1;
822 sheet->cols.info = g_ptr_array_new ();
823 sheet_col_set_default_size_pts (sheet, 48);
825 sheet->rows.max_used = -1;
826 sheet->rows.info = g_ptr_array_new ();
827 sheet_row_set_default_size_pts (sheet, 12.75);
829 sheet->print_info = gnm_print_information_new (FALSE);
831 sheet->filters = NULL;
832 sheet->scenarios = NULL;
833 sheet->sort_setups = NULL;
834 sheet->list_merged = NULL;
835 sheet->hash_merged = g_hash_table_new ((GHashFunc)&gnm_cellpos_hash,
836 (GCompareFunc)&gnm_cellpos_equal);
838 sheet->cell_hash = g_hash_table_new ((GHashFunc)&cell_set_hash,
839 (GCompareFunc)&cell_set_equal);
841 /* Init preferences */
842 sheet->convs = gnm_conventions_ref (gnm_conventions_default);
844 /* FIXME: probably not here. */
845 /* See also gtk_widget_create_pango_context (). */
846 sheet->last_zoom_factor_used = -1; /* Overridden later */
847 context = gnm_pango_context_get ();
848 sheet->rendered_values = gnm_rvc_new (context, 5000);
849 g_object_unref (context);
851 /* Init menu states */
852 sheet->priv->enable_showhide_detail = TRUE;
854 sheet->names = gnm_named_expr_collection_new ();
855 sheet->style_data = NULL;
857 sheet->index_in_wb = -1;
860 static Sheet the_invalid_sheet;
861 Sheet *invalid_sheet = &the_invalid_sheet;
863 static void
864 gnm_sheet_class_init (GObjectClass *gobject_class)
866 if (GNM_MAX_COLS > 364238) {
867 /* Oh, yeah? */
868 g_warning (_("This is a special version of Gnumeric. It has been compiled\n"
869 "with support for a very large number of columns. Access to the\n"
870 "column named TRUE may conflict with the constant of the same\n"
871 "name. Expect weirdness."));
874 parent_class = g_type_class_peek_parent (gobject_class);
876 gobject_class->set_property = gnm_sheet_set_property;
877 gobject_class->get_property = gnm_sheet_get_property;
878 gobject_class->finalize = gnm_sheet_finalize;
879 gobject_class->constructed = gnm_sheet_constructed;
881 g_object_class_install_property (gobject_class, PROP_SHEET_TYPE,
882 g_param_spec_enum ("sheet-type",
883 P_("Sheet Type"),
884 P_("Which type of sheet this is."),
885 GNM_SHEET_TYPE_TYPE,
886 GNM_SHEET_DATA,
887 GSF_PARAM_STATIC |
888 G_PARAM_READWRITE |
889 G_PARAM_CONSTRUCT_ONLY));
890 g_object_class_install_property (gobject_class, PROP_WORKBOOK,
891 g_param_spec_object ("workbook",
892 P_("Parent workbook"),
893 P_("The workbook in which this sheet lives"),
894 GNM_WORKBOOK_TYPE,
895 GSF_PARAM_STATIC |
896 G_PARAM_READWRITE |
897 G_PARAM_CONSTRUCT_ONLY));
898 g_object_class_install_property (gobject_class, PROP_NAME,
899 g_param_spec_string ("name",
900 P_("Name"),
901 P_("The name of the sheet."),
902 NULL,
903 GSF_PARAM_STATIC |
904 G_PARAM_READWRITE));
905 g_object_class_install_property (gobject_class, PROP_RTL,
906 g_param_spec_boolean ("text-is-rtl",
907 P_("text-is-rtl"),
908 P_("Text goes from right to left."),
909 FALSE,
910 GSF_PARAM_STATIC |
911 G_PARAM_READWRITE));
912 g_object_class_install_property (gobject_class, PROP_VISIBILITY,
913 g_param_spec_enum ("visibility",
914 P_("Visibility"),
915 P_("How visible the sheet is."),
916 GNM_SHEET_VISIBILITY_TYPE,
917 GNM_SHEET_VISIBILITY_VISIBLE,
918 GSF_PARAM_STATIC |
919 G_PARAM_READWRITE));
920 g_object_class_install_property (gobject_class, PROP_DISPLAY_FORMULAS,
921 g_param_spec_boolean ("display-formulas",
922 P_("Display Formul\303\246"),
923 P_("Control whether formul\303\246 are shown instead of values."),
924 FALSE,
925 GSF_PARAM_STATIC |
926 G_PARAM_READWRITE));
927 g_object_class_install_property (gobject_class, PROP_DISPLAY_ZEROS,
928 g_param_spec_boolean ("display-zeros", _("Display Zeros"),
929 _("Control whether zeros are shown are blanked out."),
930 TRUE,
931 GSF_PARAM_STATIC |
932 G_PARAM_READWRITE));
933 g_object_class_install_property (gobject_class, PROP_DISPLAY_GRID,
934 g_param_spec_boolean ("display-grid", _("Display Grid"),
935 _("Control whether the grid is shown."),
936 TRUE,
937 GSF_PARAM_STATIC |
938 G_PARAM_READWRITE));
939 g_object_class_install_property (gobject_class, PROP_DISPLAY_COLUMN_HEADER,
940 g_param_spec_boolean ("display-column-header",
941 P_("Display Column Headers"),
942 P_("Control whether column headers are shown."),
943 FALSE,
944 GSF_PARAM_STATIC |
945 G_PARAM_READWRITE));
946 g_object_class_install_property (gobject_class, PROP_DISPLAY_ROW_HEADER,
947 g_param_spec_boolean ("display-row-header",
948 P_("Display Row Headers"),
949 P_("Control whether row headers are shown."),
950 FALSE,
951 GSF_PARAM_STATIC |
952 G_PARAM_READWRITE));
953 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES,
954 g_param_spec_boolean ("display-outlines",
955 P_("Display Outlines"),
956 P_("Control whether outlines are shown."),
957 TRUE,
958 GSF_PARAM_STATIC |
959 G_PARAM_READWRITE));
960 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES_BELOW,
961 g_param_spec_boolean ("display-outlines-below",
962 P_("Display Outlines Below"),
963 P_("Control whether outline symbols are shown below."),
964 TRUE,
965 GSF_PARAM_STATIC |
966 G_PARAM_READWRITE));
967 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES_RIGHT,
968 g_param_spec_boolean ("display-outlines-right",
969 P_("Display Outlines Right"),
970 P_("Control whether outline symbols are shown to the right."),
971 TRUE,
972 GSF_PARAM_STATIC |
973 G_PARAM_READWRITE));
975 g_object_class_install_property (gobject_class, PROP_PROTECTED,
976 g_param_spec_boolean ("protected",
977 P_("Protected"),
978 P_("Sheet is protected."),
979 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
980 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_OBJECTS,
981 g_param_spec_boolean ("protected-allow-edit-objects",
982 P_("Protected Allow Edit objects"),
983 P_("Allow objects to be edited while a sheet is protected"),
984 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
985 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_SCENARIOS,
986 g_param_spec_boolean ("protected-allow-edit-scenarios",
987 P_("Protected allow edit scenarios"),
988 P_("Allow scenarios to be edited while a sheet is protected"),
989 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
990 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_CELL_FORMATTING,
991 g_param_spec_boolean ("protected-allow-cell-formatting",
992 P_("Protected allow cell formatting"),
993 P_("Allow cell format changes while a sheet is protected"),
994 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
995 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_COLUMN_FORMATTING,
996 g_param_spec_boolean ("protected-allow-column-formatting",
997 P_("Protected allow column formatting"),
998 P_("Allow column formatting while a sheet is protected"),
999 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1000 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_ROW_FORMATTING,
1001 g_param_spec_boolean ("protected-allow-row-formatting",
1002 P_("Protected allow row formatting"),
1003 P_("Allow row formatting while a sheet is protected"),
1004 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1005 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_COLUMNS,
1006 g_param_spec_boolean ("protected-allow-insert-columns",
1007 P_("Protected allow insert columns"),
1008 P_("Allow columns to be inserted while a sheet is protected"),
1009 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1010 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_ROWS,
1011 g_param_spec_boolean ("protected-allow-insert-rows",
1012 P_("Protected allow insert rows"),
1013 P_("Allow rows to be inserted while a sheet is protected"),
1014 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1015 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS,
1016 g_param_spec_boolean ("protected-allow-insert-hyperlinks",
1017 P_("Protected allow insert hyperlinks"),
1018 P_("Allow hyperlinks to be inserted while a sheet is protected"),
1019 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1020 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_DELETE_COLUMNS,
1021 g_param_spec_boolean ("protected-allow-delete-columns",
1022 P_("Protected allow delete columns"),
1023 P_("Allow columns to be deleted while a sheet is protected"),
1024 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1025 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_DELETE_ROWS,
1026 g_param_spec_boolean ("protected-allow-delete-rows",
1027 P_("Protected allow delete rows"),
1028 P_("Allow rows to be deleted 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_LOCKED_CELLS,
1031 g_param_spec_boolean ("protected-allow-select-locked-cells",
1032 P_("Protected allow select locked cells"),
1033 P_("Allow the user to select locked cells while a sheet is protected"),
1034 TRUE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1035 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SORT_RANGES,
1036 g_param_spec_boolean ("protected-allow-sort-ranges",
1037 P_("Protected allow sort ranges"),
1038 P_("Allow ranges to be sorted while a sheet is protected"),
1039 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1040 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS,
1041 g_param_spec_boolean ("protected-allow-edit-auto-filters",
1042 P_("Protected allow edit auto filters"),
1043 P_("Allow auto filters to be edited while a sheet is protected"),
1044 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1045 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE,
1046 g_param_spec_boolean ("protected-allow-edit-pivottable",
1047 P_("Protected allow edit pivottable"),
1048 P_("Allow pivottable to be edited while a sheet is protected"),
1049 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1050 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS,
1051 g_param_spec_boolean ("protected-allow-select-unlocked-cells",
1052 P_("Protected allow select unlocked cells"),
1053 P_("Allow the user to select unlocked cells while a sheet is protected"),
1054 TRUE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1056 g_object_class_install_property
1057 (gobject_class, PROP_CONVENTIONS,
1058 g_param_spec_boxed ("conventions",
1059 P_("Display convention for expressions (default Gnumeric A1)"),
1060 P_("How to format displayed expressions, (A1 vs R1C1, function names, ...)"),
1061 gnm_conventions_get_type (),
1062 GSF_PARAM_STATIC |
1063 G_PARAM_READWRITE));
1064 g_object_class_install_property (gobject_class, PROP_USE_R1C1, /* convenience wrapper to CONVENTIONS */
1065 g_param_spec_boolean ("use-r1c1",
1066 P_("Display convention for expressions as XLS_R1C1 vs default"),
1067 P_("How to format displayed expressions, (a convenience api)"),
1068 FALSE,
1069 GSF_PARAM_STATIC |
1070 G_PARAM_READWRITE));
1072 g_object_class_install_property (gobject_class, PROP_TAB_FOREGROUND,
1073 g_param_spec_boxed ("tab-foreground",
1074 P_("Tab Foreground"),
1075 P_("The foreground color of the tab."),
1076 GNM_COLOR_TYPE,
1077 GSF_PARAM_STATIC |
1078 G_PARAM_READWRITE));
1079 g_object_class_install_property (gobject_class, PROP_TAB_BACKGROUND,
1080 g_param_spec_boxed ("tab-background",
1081 P_("Tab Background"),
1082 P_("The background color of the tab."),
1083 GNM_COLOR_TYPE,
1084 GSF_PARAM_STATIC |
1085 G_PARAM_READWRITE));
1087 /* What is this doing in sheet? */
1088 g_object_class_install_property (gobject_class, PROP_ZOOM_FACTOR,
1089 g_param_spec_double ("zoom-factor",
1090 P_("Zoom Factor"),
1091 P_("The level of zoom used for this sheet."),
1092 0.1, 5.0,
1093 1.0,
1094 GSF_PARAM_STATIC |
1095 G_PARAM_CONSTRUCT |
1096 G_PARAM_READWRITE));
1098 g_object_class_install_property (gobject_class, PROP_COLUMNS,
1099 g_param_spec_int ("columns",
1100 P_("Columns"),
1101 P_("Columns number in the sheet"),
1102 0, GNM_MAX_COLS, GNM_DEFAULT_COLS,
1103 GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1105 g_object_class_install_property (gobject_class, PROP_ROWS,
1106 g_param_spec_int ("rows",
1107 P_("Rows"),
1108 P_("Rows number in the sheet"),
1109 0, GNM_MAX_ROWS, GNM_DEFAULT_ROWS,
1110 GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1112 signals[DETACHED_FROM_WORKBOOK] = g_signal_new
1113 ("detached_from_workbook",
1114 GNM_SHEET_TYPE,
1115 G_SIGNAL_RUN_LAST,
1116 G_STRUCT_OFFSET (GnmSheetClass, detached_from_workbook),
1117 NULL, NULL,
1118 g_cclosure_marshal_VOID__OBJECT,
1119 G_TYPE_NONE, 1, GNM_WORKBOOK_TYPE);
1123 GSF_CLASS (GnmSheet, gnm_sheet,
1124 gnm_sheet_class_init, gnm_sheet_init, G_TYPE_OBJECT)
1126 /* ------------------------------------------------------------------------- */
1128 GType
1129 gnm_sheet_type_get_type (void)
1131 static GType etype = 0;
1132 if (etype == 0) {
1133 static const GEnumValue values[] = {
1134 { GNM_SHEET_DATA, "GNM_SHEET_DATA", "data" },
1135 { GNM_SHEET_OBJECT, "GNM_SHEET_OBJECT", "object" },
1136 { GNM_SHEET_XLM, "GNM_SHEET_XLM", "xlm" },
1137 { 0, NULL, NULL }
1139 etype = g_enum_register_static ("GnmSheetType", values);
1141 return etype;
1144 GType
1145 gnm_sheet_visibility_get_type (void)
1147 static GType etype = 0;
1148 if (etype == 0) {
1149 static GEnumValue const values[] = {
1150 { GNM_SHEET_VISIBILITY_VISIBLE, "GNM_SHEET_VISIBILITY_VISIBLE", "visible" },
1151 { GNM_SHEET_VISIBILITY_HIDDEN, "GNM_SHEET_VISIBILITY_HIDDEN", "hidden" },
1152 { GNM_SHEET_VISIBILITY_VERY_HIDDEN, "GNM_SHEET_VISIBILITY_VERY_HIDDEN", "very-hidden" },
1153 { 0, NULL, NULL }
1155 etype = g_enum_register_static ("GnmSheetVisibility", values);
1157 return etype;
1160 /* ------------------------------------------------------------------------- */
1162 static gboolean
1163 powerof_2 (int i)
1165 return i > 0 && (i & (i - 1)) == 0;
1168 gboolean
1169 gnm_sheet_valid_size (int cols, int rows)
1171 return (cols >= GNM_MIN_COLS &&
1172 cols <= GNM_MAX_COLS &&
1173 powerof_2 (cols) &&
1174 rows >= GNM_MIN_ROWS &&
1175 rows <= GNM_MAX_ROWS &&
1176 powerof_2 (rows)
1177 #if 0
1178 && 0x80000000u / (unsigned)(cols / 2) >= (unsigned)rows
1179 #endif
1183 void
1184 gnm_sheet_suggest_size (int *cols, int *rows)
1186 int c = GNM_DEFAULT_COLS;
1187 int r = GNM_DEFAULT_ROWS;
1189 while (c < *cols && c < GNM_MAX_COLS)
1190 c *= 2;
1192 while (r < *rows && r < GNM_MAX_ROWS)
1193 r *= 2;
1195 while (!gnm_sheet_valid_size (c, r)) {
1196 /* Darn! Too large. */
1197 if (*cols >= GNM_MIN_COLS && c > GNM_MIN_COLS)
1198 c /= 2;
1199 else if (*rows >= GNM_MIN_ROWS && r > GNM_MIN_ROWS)
1200 r /= 2;
1201 else if (c > GNM_MIN_COLS)
1202 c /= 2;
1203 else
1204 r /= 2;
1207 *cols = c;
1208 *rows = r;
1211 static void gnm_sheet_resize_main (Sheet *sheet, int cols, int rows,
1212 GOCmdContext *cc, GOUndo **pundo);
1214 static void
1215 cb_sheet_resize (Sheet *sheet, const GnmSheetSize *data, GOCmdContext *cc)
1217 gnm_sheet_resize_main (sheet, data->max_cols, data->max_rows,
1218 cc, NULL);
1221 static void
1222 gnm_sheet_resize_main (Sheet *sheet, int cols, int rows,
1223 GOCmdContext *cc, GOUndo **pundo)
1225 int old_cols, old_rows;
1226 GnmStyle **common_col_styles = NULL;
1227 GnmStyle **common_row_styles = NULL;
1229 if (pundo) *pundo = NULL;
1231 old_cols = gnm_sheet_get_max_cols (sheet);
1232 old_rows = gnm_sheet_get_max_rows (sheet);
1233 if (old_cols == cols && old_rows == rows)
1234 return;
1236 /* ---------------------------------------- */
1237 /* Gather styles we want to copy into new areas. */
1239 if (cols > old_cols) {
1240 int r;
1241 common_row_styles = sheet_style_most_common (sheet, FALSE);
1242 for (r = 0; r < old_rows; r++)
1243 gnm_style_ref (common_row_styles[r]);
1245 if (rows > old_rows) {
1246 int c;
1247 common_col_styles = sheet_style_most_common (sheet, TRUE);
1248 for (c = 0; c < old_cols; c++)
1249 gnm_style_ref (common_col_styles[c]);
1252 /* ---------------------------------------- */
1253 /* Remove the columns and rows that will disappear. */
1255 if (cols < old_cols) {
1256 GOUndo *u = NULL;
1257 gboolean err;
1259 err = sheet_delete_cols (sheet, cols, G_MAXINT,
1260 pundo ? &u : NULL, cc);
1261 if (pundo)
1262 *pundo = go_undo_combine (*pundo, u);
1263 if (err)
1264 goto handle_error;
1267 if (rows < old_rows) {
1268 GOUndo *u = NULL;
1269 gboolean err;
1271 err = sheet_delete_rows (sheet, rows, G_MAXINT,
1272 pundo ? &u : NULL, cc);
1273 if (pundo)
1274 *pundo = go_undo_combine (*pundo, u);
1275 if (err)
1276 goto handle_error;
1279 /* ---------------------------------------- */
1280 /* Restrict selection. (Not undone.) */
1282 SHEET_FOREACH_VIEW (sheet, sv,
1284 GnmRange new_full;
1285 GSList *l;
1286 GSList *sel = selection_get_ranges (sv, TRUE);
1287 gboolean any = FALSE;
1288 GnmCellPos vis;
1289 sv_selection_reset (sv);
1290 range_init (&new_full, 0, 0, cols - 1, rows - 1);
1291 vis = new_full.start;
1292 for (l = sel; l; l = l->next) {
1293 GnmRange *r = l->data;
1294 GnmRange newr;
1295 if (range_intersection (&newr, r, &new_full)) {
1296 sv_selection_add_range (sv, &newr);
1297 vis = newr.start;
1298 any = TRUE;
1300 g_free (r);
1302 g_slist_free (sel);
1303 if (!any)
1304 sv_selection_add_pos (sv, 0, 0,
1305 GNM_SELECTION_MODE_ADD);
1306 gnm_sheet_view_make_cell_visible (sv, vis.col, vis.row, FALSE);
1309 /* ---------------------------------------- */
1310 /* Resize column and row containers. */
1312 col_row_collection_resize (&sheet->cols, cols);
1313 col_row_collection_resize (&sheet->rows, rows);
1315 /* ---------------------------------------- */
1316 /* Resize the dependency containers. */
1319 GSList *l, *linked = NULL;
1320 /* FIXME: what about dependents in other workbooks? */
1321 WORKBOOK_FOREACH_DEPENDENT
1322 (sheet->workbook, dep,
1324 if (dependent_is_linked (dep)) {
1325 dependent_unlink (dep);
1326 linked = g_slist_prepend (linked, dep);
1329 gnm_dep_container_resize (sheet->deps, rows);
1331 for (l = linked; l; l = l->next) {
1332 GnmDependent *dep = l->data;
1333 dependent_link (dep);
1336 g_slist_free (linked);
1338 workbook_queue_all_recalc (sheet->workbook);
1341 /* ---------------------------------------- */
1342 /* Resize the styles. */
1344 sheet_style_resize (sheet, cols, rows);
1346 /* ---------------------------------------- */
1347 /* Actually change the properties. */
1349 sheet->size.max_cols = cols;
1350 sheet->cols.max_used = MIN (sheet->cols.max_used, cols - 1);
1351 sheet->size.max_rows = rows;
1352 sheet->rows.max_used = MIN (sheet->rows.max_used, rows - 1);
1354 if (old_cols != cols)
1355 g_object_notify (G_OBJECT (sheet), "columns");
1356 if (old_rows != rows)
1357 g_object_notify (G_OBJECT (sheet), "rows");
1359 if (pundo) {
1360 GnmSheetSize *data = g_new (GnmSheetSize, 1);
1361 GOUndo *u;
1363 data->max_cols = old_cols;
1364 data->max_rows = old_rows;
1365 u = go_undo_binary_new (sheet, data,
1366 (GOUndoBinaryFunc)cb_sheet_resize,
1367 NULL, g_free);
1368 *pundo = go_undo_combine (*pundo, u);
1371 range_init_full_sheet (&sheet->priv->unhidden_region, sheet);
1373 /* ---------------------------------------- */
1374 /* Apply styles to new areas. */
1376 if (cols > old_cols) {
1377 int r = 0;
1378 while (r < old_rows) {
1379 int r2 = r;
1380 GnmStyle *mstyle = common_row_styles[r];
1381 GnmRange rng;
1382 while (r2 + 1 < old_rows &&
1383 mstyle == common_row_styles[r2 + 1])
1384 r2++;
1385 range_init (&rng, old_cols, r, cols - 1, r2);
1386 gnm_style_ref (mstyle);
1387 sheet_apply_style (sheet, &rng, mstyle);
1388 r = r2 + 1;
1391 for (r = 0; r < old_rows; r++)
1392 gnm_style_unref (common_row_styles[r]);
1394 g_free (common_row_styles);
1397 if (rows > old_rows) {
1398 int c = 0;
1400 while (c < old_cols) {
1401 int c2 = c;
1402 GnmStyle *mstyle = common_col_styles[c];
1403 GnmRange rng;
1404 while (c2 + 1 < old_cols &&
1405 mstyle == common_col_styles[c2 + 1])
1406 c2++;
1407 range_init (&rng, c, old_rows, c2, rows - 1);
1408 gnm_style_ref (mstyle);
1409 sheet_apply_style (sheet, &rng, mstyle);
1410 c = c2 + 1;
1413 if (cols > old_cols) {
1415 * Expanded in both directions. One could argue about
1416 * what style to use down here, but we choose the
1417 * last column style.
1419 GnmStyle *mstyle = common_col_styles[old_cols - 1];
1420 GnmRange rng;
1422 range_init (&rng,
1423 old_cols, old_rows,
1424 cols - 1, rows - 1);
1425 gnm_style_ref (mstyle);
1426 sheet_apply_style (sheet, &rng, mstyle);
1429 for (c = 0; c < old_cols; c++)
1430 gnm_style_unref (common_col_styles[c]);
1431 g_free (common_col_styles);
1434 /* ---------------------------------------- */
1436 sheet_redraw_all (sheet, TRUE);
1437 return;
1439 handle_error:
1440 if (pundo) {
1441 go_undo_undo_with_data (*pundo, cc);
1442 g_object_unref (*pundo);
1443 *pundo = NULL;
1448 * gnm_sheet_resize:
1449 * @sheet: #Sheet
1450 * @cols: the new columns number.
1451 * @rows: the new rows number.
1452 * @cc: #GOCmdContext.
1453 * @perr: will be %TRUE on error.
1455 * Returns: (transfer full): the newly allocated #GOUndo.
1457 GOUndo *
1458 gnm_sheet_resize (Sheet *sheet, int cols, int rows,
1459 GOCmdContext *cc, gboolean *perr)
1461 GOUndo *undo = NULL;
1463 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1464 g_return_val_if_fail (gnm_sheet_valid_size (cols, rows), NULL);
1466 if (cols < sheet->size.max_cols || rows < sheet->size.max_rows) {
1467 GSList *overlap, *l;
1468 gboolean bad = FALSE;
1469 GnmRange r;
1471 r.start.col = r.start.row = 0;
1472 r.end.col = MIN (cols, sheet->size.max_cols) - 1;
1473 r.end.row = MIN (rows, sheet->size.max_rows) - 1;
1475 overlap = gnm_sheet_merge_get_overlap (sheet, &r);
1476 for (l = overlap; l && !bad; l = l->next) {
1477 GnmRange const *m = l->data;
1478 if (!range_contained (m, &r)) {
1479 bad = TRUE;
1480 gnm_cmd_context_error_splits_merge (cc, m);
1483 g_slist_free (overlap);
1484 if (bad) {
1485 *perr = TRUE;
1486 return NULL;
1490 gnm_sheet_resize_main (sheet, cols, rows, cc, &undo);
1492 *perr = FALSE;
1493 return undo;
1498 * sheet_new_with_type:
1499 * @wb: #Workbook
1500 * @name: An unquoted name
1501 * @type: @GnmSheetType
1502 * @columns: The number of columns for the sheet
1503 * @rows: The number of rows for the sheet
1505 * Create a new Sheet of type @type, and associate it with @wb.
1506 * The type cannot be changed later.
1507 * Returns: (transfer full): the newly allocated sheet.
1509 Sheet *
1510 sheet_new_with_type (Workbook *wb, char const *name, GnmSheetType type,
1511 int columns, int rows)
1513 Sheet *sheet;
1515 g_return_val_if_fail (wb != NULL, NULL);
1516 g_return_val_if_fail (name != NULL, NULL);
1517 g_return_val_if_fail (gnm_sheet_valid_size (columns, rows), NULL);
1519 sheet = g_object_new (GNM_SHEET_TYPE,
1520 "workbook", wb,
1521 "sheet-type", type,
1522 "columns", columns,
1523 "rows", rows,
1524 "name", name,
1525 "zoom-factor", gnm_conf_get_core_gui_window_zoom (),
1526 NULL);
1528 if (type == GNM_SHEET_OBJECT)
1529 print_info_set_paper_orientation (sheet->print_info, GTK_PAGE_ORIENTATION_LANDSCAPE);
1531 return sheet;
1535 * sheet_new:
1536 * @wb: #Workbook
1537 * @name: The name for the sheet (unquoted).
1538 * @columns: The requested columns number.
1539 * @rows: The requested rows number.
1541 * Create a new Sheet of type SHEET_DATA, and associate it with @wb.
1542 * The type can not be changed later
1543 * Returns: (transfer full): the newly allocated sheet.
1545 Sheet *
1546 sheet_new (Workbook *wb, char const *name, int columns, int rows)
1548 return sheet_new_with_type (wb, name, GNM_SHEET_DATA, columns, rows);
1551 /****************************************************************************/
1553 void
1554 sheet_redraw_all (Sheet const *sheet, gboolean headers)
1556 /* We potentially do a lot of recalcs as part of this, so make sure
1557 stuff that caches sub-computations see the whole thing instead
1558 of clearing between cells. */
1559 gnm_app_recalc_start ();
1560 SHEET_FOREACH_CONTROL (sheet, view, control,
1561 sc_redraw_all (control, headers););
1562 gnm_app_recalc_finish ();
1565 static GnmValue *
1566 cb_clear_rendered_values (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
1568 gnm_cell_unrender (iter->cell);
1569 return NULL;
1573 * sheet_range_calc_spans:
1574 * @sheet: The sheet,
1575 * @r: the region to update.
1576 * @flags:
1578 * This is used to re-calculate cell dimensions and re-render
1579 * a cell's text. eg. if a format has changed we need to re-render
1580 * the cached version of the rendered text in the cell.
1582 void
1583 sheet_range_calc_spans (Sheet *sheet, GnmRange const *r, GnmSpanCalcFlags flags)
1585 if (flags & GNM_SPANCALC_RE_RENDER)
1586 sheet_foreach_cell_in_range
1587 (sheet, CELL_ITER_IGNORE_NONEXISTENT, r,
1588 cb_clear_rendered_values, NULL);
1589 sheet_queue_respan (sheet, r->start.row, r->end.row);
1591 /* Redraw the new region in case the span changes */
1592 sheet_redraw_range (sheet, r);
1595 static void
1596 sheet_redraw_partial_row (Sheet const *sheet, int const row,
1597 int const start_col, int const end_col)
1599 GnmRange r;
1600 range_init (&r, start_col, row, end_col, row);
1601 SHEET_FOREACH_CONTROL (sheet, view, control,
1602 sc_redraw_range (control, &r););
1605 static void
1606 sheet_redraw_cell (GnmCell const *cell)
1608 CellSpanInfo const * span;
1609 int start_col, end_col, row;
1610 GnmRange const *merged;
1611 Sheet *sheet;
1612 ColRowInfo *ri;
1614 g_return_if_fail (cell != NULL);
1616 sheet = cell->base.sheet;
1617 merged = gnm_sheet_merge_is_corner (sheet, &cell->pos);
1618 if (merged != NULL) {
1619 SHEET_FOREACH_CONTROL (sheet, view, control,
1620 sc_redraw_range (control, merged););
1621 return;
1624 row = cell->pos.row;
1625 start_col = end_col = cell->pos.col;
1626 ri = sheet_row_get (sheet, row);
1627 span = row_span_get (ri, start_col);
1629 if (span) {
1630 start_col = span->left;
1631 end_col = span->right;
1634 sheet_redraw_partial_row (sheet, row, start_col, end_col);
1637 static void
1638 sheet_cell_calc_span (GnmCell *cell, GnmSpanCalcFlags flags)
1640 CellSpanInfo const * span;
1641 int left, right;
1642 int min_col, max_col, row;
1643 gboolean render = (flags & GNM_SPANCALC_RE_RENDER) != 0;
1644 gboolean const resize = (flags & GNM_SPANCALC_RESIZE) != 0;
1645 gboolean existing = FALSE;
1646 GnmRange const *merged;
1647 Sheet *sheet;
1648 ColRowInfo *ri;
1650 g_return_if_fail (cell != NULL);
1652 sheet = cell->base.sheet;
1653 row = cell->pos.row;
1655 /* Render & Size any unrendered cells */
1656 if ((flags & GNM_SPANCALC_RENDER) && gnm_cell_get_rendered_value (cell) == NULL)
1657 render = TRUE;
1659 if (render) {
1660 if (!gnm_cell_has_expr (cell))
1661 gnm_cell_render_value ((GnmCell *)cell, TRUE);
1662 else
1663 gnm_cell_unrender (cell);
1664 } else if (resize) {
1665 /* FIXME: what was wanted here? */
1666 /* rendered_value_calc_size (cell); */
1669 /* Is there an existing span ? clear it BEFORE calculating new one */
1670 ri = sheet_row_get (sheet, row);
1671 span = row_span_get (ri, cell->pos.col);
1672 if (span != NULL) {
1673 GnmCell const * const other = span->cell;
1675 min_col = span->left;
1676 max_col = span->right;
1678 /* A different cell used to span into this cell, respan that */
1679 if (cell != other) {
1680 int other_left, other_right;
1682 cell_unregister_span (other);
1683 cell_calc_span (other, &other_left, &other_right);
1684 if (min_col > other_left)
1685 min_col = other_left;
1686 if (max_col < other_right)
1687 max_col = other_right;
1689 if (other_left != other_right)
1690 cell_register_span (other, other_left, other_right);
1691 } else
1692 existing = TRUE;
1693 } else
1694 min_col = max_col = cell->pos.col;
1696 merged = gnm_sheet_merge_is_corner (sheet, &cell->pos);
1697 if (NULL != merged) {
1698 if (existing) {
1699 if (min_col > merged->start.col)
1700 min_col = merged->start.col;
1701 if (max_col < merged->end.col)
1702 max_col = merged->end.col;
1703 } else {
1704 sheet_redraw_cell (cell);
1705 return;
1707 } else {
1708 /* Calculate the span of the cell */
1709 cell_calc_span (cell, &left, &right);
1710 if (min_col > left)
1711 min_col = left;
1712 if (max_col < right)
1713 max_col = right;
1715 /* This cell already had an existing span */
1716 if (existing) {
1717 /* If it changed, remove the old one */
1718 if (left != span->left || right != span->right)
1719 cell_unregister_span (cell);
1720 else
1721 /* unchanged, short curcuit adding the span again */
1722 left = right;
1725 if (left != right)
1726 cell_register_span (cell, left, right);
1729 sheet_redraw_partial_row (sheet, row, min_col, max_col);
1733 * sheet_apply_style: (skip)
1734 * @sheet: the sheet in which can be found
1735 * @range: the range to which should be applied
1736 * @style: (transfer full): 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 (Sheet *sheet,
1743 GnmRange const *range,
1744 GnmStyle *style)
1746 GnmSpanCalcFlags spanflags = gnm_style_required_spanflags (style);
1747 sheet_style_apply_range (sheet, range, style);
1748 /* This also redraws the range: */
1749 sheet_range_calc_spans (sheet, range, spanflags);
1753 * sheet_apply_style_gi: (rename-to sheet_apply_style)
1754 * @sheet: the sheet in which can be found
1755 * @range: the range to which should be applied
1756 * @style: A #GnmStyle partial style
1758 * A mid level routine that applies the supplied partial style @style to the
1759 * target @range and performs the necessary respanning and redrawing.
1761 void
1762 sheet_apply_style_gi (Sheet *sheet, GnmRange const *range, GnmStyle *style)
1764 GnmSpanCalcFlags spanflags = gnm_style_required_spanflags (style);
1765 gnm_style_ref (style);
1766 sheet_style_apply_range (sheet, range, style);
1767 /* This also redraws the range: */
1768 sheet_range_calc_spans (sheet, range, spanflags);
1771 static void
1772 sheet_apply_style_cb (GnmSheetRange *sr,
1773 GnmStyle *style)
1775 gnm_style_ref (style);
1776 sheet_apply_style (sr->sheet, &sr->range, style);
1777 sheet_flag_style_update_range (sr->sheet, &sr->range);
1781 * sheet_apply_style_undo:
1782 * @sr: #GnmSheetRange
1783 * @style: #GnmStyle
1785 * Returns: (transfer full): the new #GOUndo.
1787 GOUndo *
1788 sheet_apply_style_undo (GnmSheetRange *sr,
1789 GnmStyle *style)
1791 gnm_style_ref (style);
1792 return go_undo_binary_new
1793 (sr, (gpointer)style,
1794 (GOUndoBinaryFunc) sheet_apply_style_cb,
1795 (GFreeFunc) gnm_sheet_range_free,
1796 (GFreeFunc) gnm_style_unref);
1801 void
1802 sheet_apply_border (Sheet *sheet,
1803 GnmRange const *range,
1804 GnmBorder **borders)
1806 GnmSpanCalcFlags spanflags = GNM_SPANCALC_RE_RENDER | GNM_SPANCALC_RESIZE;
1807 sheet_style_apply_border (sheet, range, borders);
1808 /* This also redraws the range: */
1809 sheet_range_calc_spans (sheet, range, spanflags);
1812 /****************************************************************************/
1814 static ColRowInfo *
1815 sheet_row_new (Sheet *sheet)
1817 ColRowInfo *ri;
1819 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1821 ri = col_row_info_new ();
1822 *ri = sheet->rows.default_style;
1823 ri->is_default = FALSE;
1824 ri->needs_respan = TRUE;
1826 return ri;
1829 static ColRowInfo *
1830 sheet_col_new (Sheet *sheet)
1832 ColRowInfo *ci;
1834 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1836 ci = col_row_info_new ();
1837 *ci = sheet->cols.default_style;
1838 ci->is_default = FALSE;
1840 return ci;
1843 static void
1844 sheet_colrow_add (Sheet *sheet, ColRowInfo *cp, gboolean is_cols, int n)
1846 ColRowCollection *info = is_cols ? &sheet->cols : &sheet->rows;
1847 ColRowSegment **psegment = (ColRowSegment **)&COLROW_GET_SEGMENT (info, n);
1849 g_return_if_fail (n >= 0);
1850 g_return_if_fail (n < colrow_max (is_cols, sheet));
1852 if (*psegment == NULL)
1853 *psegment = g_new0 (ColRowSegment, 1);
1854 colrow_free ((*psegment)->info[COLROW_SUB_INDEX (n)]);
1855 (*psegment)->info[COLROW_SUB_INDEX (n)] = cp;
1857 if (cp->outline_level > info->max_outline_level)
1858 info->max_outline_level = cp->outline_level;
1859 if (n > info->max_used) {
1860 info->max_used = n;
1861 sheet->priv->resize_scrollbar = TRUE;
1865 static void
1866 sheet_reposition_objects (Sheet const *sheet, GnmCellPos const *pos)
1868 GSList *ptr;
1869 for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = ptr->next )
1870 sheet_object_update_bounds (GNM_SO (ptr->data), pos);
1874 * sheet_flag_status_update_cell:
1875 * @cell: The cell that has changed.
1877 * flag the sheet as requiring an update to the status display
1878 * if the supplied cell location is the edit cursor, or part of the
1879 * selected region.
1881 * Will cause the format toolbar, the edit area, and the auto expressions to be
1882 * updated if appropriate.
1884 void
1885 sheet_flag_status_update_cell (GnmCell const *cell)
1887 SHEET_FOREACH_VIEW (cell->base.sheet, sv,
1888 gnm_sheet_view_flag_status_update_pos (sv, &cell->pos););
1892 * sheet_flag_status_update_range:
1893 * @sheet:
1894 * @range: If NULL then force an update.
1896 * flag the sheet as requiring an update to the status display
1897 * if the supplied cell location contains the edit cursor, or intersects of
1898 * the selected region.
1900 * Will cause the format toolbar, the edit area, and the auto expressions to be
1901 * updated if appropriate.
1903 void
1904 sheet_flag_status_update_range (Sheet const *sheet, GnmRange const *range)
1906 SHEET_FOREACH_VIEW (sheet, sv,
1907 gnm_sheet_view_flag_status_update_range (sv, range););
1911 * sheet_flag_style_update_range:
1912 * @sheet: The sheet being changed
1913 * @range: the range that is changing.
1915 * Flag format changes that will require updating the format indicators.
1917 void
1918 sheet_flag_style_update_range (Sheet const *sheet, GnmRange const *range)
1920 SHEET_FOREACH_VIEW (sheet, sv,
1921 gnm_sheet_view_flag_style_update_range (sv, range););
1925 * sheet_flag_recompute_spans:
1926 * @sheet:
1928 * Flag the sheet as requiring a full span recomputation the next time
1929 * sheet_update is called.
1931 void
1932 sheet_flag_recompute_spans (Sheet const *sheet)
1934 sheet->priv->recompute_spans = TRUE;
1937 static gboolean
1938 cb_outline_level (GnmColRowIter const *iter, gpointer data)
1940 int *outline_level = data;
1941 if (*outline_level < iter->cri->outline_level)
1942 *outline_level = iter->cri->outline_level;
1943 return FALSE;
1947 * sheet_colrow_fit_gutter:
1948 * @sheet: Sheet to change for.
1949 * @is_cols: Column gutter or row gutter?
1951 * Find the current max outline level.
1953 static int
1954 sheet_colrow_fit_gutter (Sheet const *sheet, gboolean is_cols)
1956 int outline_level = 0;
1957 sheet_colrow_foreach (sheet, is_cols, 0, -1,
1958 cb_outline_level, &outline_level);
1959 return outline_level;
1963 * sheet_update_only_grid:
1964 * @sheet: #Sheet
1966 * Should be called after a logical command has finished processing
1967 * to request redraws for any pending events
1969 void
1970 sheet_update_only_grid (Sheet const *sheet)
1972 SheetPrivate *p;
1974 g_return_if_fail (IS_SHEET (sheet));
1976 p = sheet->priv;
1978 /* be careful these can toggle flags */
1979 if (p->recompute_max_col_group) {
1980 sheet_colrow_gutter ((Sheet *)sheet, TRUE,
1981 sheet_colrow_fit_gutter (sheet, TRUE));
1982 sheet->priv->recompute_max_col_group = FALSE;
1984 if (p->recompute_max_row_group) {
1985 sheet_colrow_gutter ((Sheet *)sheet, FALSE,
1986 sheet_colrow_fit_gutter (sheet, FALSE));
1987 sheet->priv->recompute_max_row_group = FALSE;
1990 SHEET_FOREACH_VIEW (sheet, sv, {
1991 if (sv->reposition_selection) {
1992 sv->reposition_selection = FALSE;
1994 /* when moving we cleared the selection before
1995 * arriving in here.
1997 if (sv->selections != NULL)
1998 sv_selection_set (sv, &sv->edit_pos_real,
1999 sv->cursor.base_corner.col,
2000 sv->cursor.base_corner.row,
2001 sv->cursor.move_corner.col,
2002 sv->cursor.move_corner.row);
2006 if (p->recompute_spans) {
2007 p->recompute_spans = FALSE;
2008 /* FIXME : I would prefer to use GNM_SPANCALC_RENDER rather than
2009 * RE_RENDER. It only renders those cells which are not
2010 * rendered. The trouble is that when a col changes size we
2011 * need to rerender, but currently nothing marks that.
2013 * hmm, that suggests an approach. maybe I can install a per
2014 * col flag. Then add a flag clearing loop after the
2015 * sheet_calc_span.
2017 #if 0
2018 sheet_calc_spans (sheet, GNM_SPANCALC_RESIZE|GNM_SPANCALC_RE_RENDER |
2019 (p->recompute_visibility ?
2020 SPANCALC_NO_DRAW : GNM_SPANCALC_SIMPLE));
2021 #endif
2022 sheet_queue_respan (sheet, 0, gnm_sheet_get_last_row (sheet));
2025 if (p->reposition_objects.row < gnm_sheet_get_max_rows (sheet) ||
2026 p->reposition_objects.col < gnm_sheet_get_max_cols (sheet)) {
2027 SHEET_FOREACH_VIEW (sheet, sv, {
2028 if (!p->resize && gnm_sheet_view_is_frozen (sv)) {
2029 if (p->reposition_objects.col < sv->unfrozen_top_left.col ||
2030 p->reposition_objects.row < sv->unfrozen_top_left.row) {
2031 gnm_sheet_view_resize (sv, FALSE);
2035 sheet_reposition_objects (sheet, &p->reposition_objects);
2036 p->reposition_objects.row = gnm_sheet_get_max_rows (sheet);
2037 p->reposition_objects.col = gnm_sheet_get_max_cols (sheet);
2040 if (p->resize) {
2041 p->resize = FALSE;
2042 SHEET_FOREACH_VIEW (sheet, sv, { gnm_sheet_view_resize (sv, FALSE); });
2045 if (p->recompute_visibility) {
2046 /* TODO : There is room for some opimization
2047 * We only need to force complete visibility recalculation
2048 * (which we do in sheet_compute_visible_region)
2049 * if a row or col before the start of the visible region.
2050 * If we are REALLY smart we could even accumulate the size differential
2051 * and use that.
2053 p->recompute_visibility = FALSE;
2054 p->resize_scrollbar = FALSE; /* compute_visible_region does this */
2055 SHEET_FOREACH_CONTROL(sheet, view, control,
2056 sc_recompute_visible_region (control, TRUE););
2057 sheet_redraw_all (sheet, TRUE);
2060 if (p->resize_scrollbar) {
2061 sheet_scrollbar_config (sheet);
2062 p->resize_scrollbar = FALSE;
2064 if (p->filters_changed) {
2065 p->filters_changed = FALSE;
2066 SHEET_FOREACH_CONTROL (sheet, sv, sc,
2067 wb_control_menu_state_update (sc_wbc (sc), MS_ADD_VS_REMOVE_FILTER););
2072 * sheet_update:
2073 * @sheet: #Sheet
2075 * Should be called after a logical command has finished processing to request
2076 * redraws for any pending events, and to update the various status regions
2078 void
2079 sheet_update (Sheet const *sheet)
2081 g_return_if_fail (IS_SHEET (sheet));
2083 sheet_update_only_grid (sheet);
2085 SHEET_FOREACH_VIEW (sheet, sv, gnm_sheet_view_update (sv););
2089 * sheet_cell_get:
2090 * @sheet: The sheet where we want to locate the cell
2091 * @col: the cell column
2092 * @row: the cell row
2094 * Return value: (nullable): a #GnmCell, or %NULL if the cell does not exist
2096 GnmCell *
2097 sheet_cell_get (Sheet const *sheet, int col, int row)
2099 GnmCell *cell;
2100 GnmCell key;
2102 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2104 key.pos.col = col;
2105 key.pos.row = row;
2106 cell = g_hash_table_lookup (sheet->cell_hash, &key);
2108 return cell;
2112 * sheet_cell_fetch:
2113 * @sheet: The sheet where we want to locate the cell
2114 * @col: the cell column
2115 * @row: the cell row
2117 * Return value: a #GnmCell containing at (@col,@row).
2118 * If no cell existed at that location before, it is created.
2120 GnmCell *
2121 sheet_cell_fetch (Sheet *sheet, int col, int row)
2123 GnmCell *cell;
2125 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2127 cell = sheet_cell_get (sheet, col, row);
2128 if (!cell)
2129 cell = sheet_cell_create (sheet, col, row);
2131 return cell;
2135 * sheet_colrow_can_group:
2136 * @sheet: #Sheet
2137 * @r: A #GnmRange
2138 * @is_cols: boolean
2140 * Returns TRUE if the cols/rows in @r.start -> @r.end can be grouped, return
2141 * FALSE otherwise. You can invert the result if you need to find out if a
2142 * group can be ungrouped.
2144 gboolean
2145 sheet_colrow_can_group (Sheet *sheet, GnmRange const *r, gboolean is_cols)
2147 ColRowInfo const *start_cri, *end_cri;
2148 int start, end;
2150 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
2152 if (is_cols) {
2153 start = r->start.col;
2154 end = r->end.col;
2155 } else {
2156 start = r->start.row;
2157 end = r->end.row;
2159 start_cri = sheet_colrow_fetch (sheet, start, is_cols);
2160 end_cri = sheet_colrow_fetch (sheet, end, is_cols);
2162 /* Groups on outline level 0 (no outline) may always be formed */
2163 if (start_cri->outline_level == 0 || end_cri->outline_level == 0)
2164 return TRUE;
2166 /* We just won't group a group that already exists (or doesn't), it's useless */
2167 return (colrow_find_outline_bound (sheet, is_cols, start, start_cri->outline_level, FALSE) != start ||
2168 colrow_find_outline_bound (sheet, is_cols, end, end_cri->outline_level, TRUE) != end);
2171 gboolean
2172 sheet_colrow_group_ungroup (Sheet *sheet, GnmRange const *r,
2173 gboolean is_cols, gboolean group)
2175 int i, new_max, start, end;
2176 int const step = group ? 1 : -1;
2178 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
2180 /* Can we group/ungroup ? */
2181 if (group != sheet_colrow_can_group (sheet, r, is_cols))
2182 return FALSE;
2184 if (is_cols) {
2185 start = r->start.col;
2186 end = r->end.col;
2187 } else {
2188 start = r->start.row;
2189 end = r->end.row;
2192 /* Set new outline for each col/row and find highest outline level */
2193 new_max = (is_cols ? &sheet->cols : &sheet->rows)->max_outline_level;
2194 for (i = start; i <= end; i++) {
2195 ColRowInfo *cri = sheet_colrow_fetch (sheet, i, is_cols);
2196 int const new_level = cri->outline_level + step;
2198 if (new_level >= 0) {
2199 col_row_info_set_outline (cri, new_level, FALSE);
2200 if (new_max < new_level)
2201 new_max = new_level;
2205 if (!group)
2206 new_max = sheet_colrow_fit_gutter (sheet, is_cols);
2208 sheet_colrow_gutter (sheet, is_cols, new_max);
2209 SHEET_FOREACH_VIEW (sheet, sv,
2210 gnm_sheet_view_redraw_headers (sv, is_cols, !is_cols, NULL););
2212 return TRUE;
2216 * sheet_colrow_gutter:
2217 * @sheet:
2218 * @is_cols:
2219 * @max_outline:
2221 * Set the maximum outline levels for cols or rows.
2223 void
2224 sheet_colrow_gutter (Sheet *sheet, gboolean is_cols, int max_outline)
2226 ColRowCollection *infos;
2228 g_return_if_fail (IS_SHEET (sheet));
2230 infos = is_cols ? &(sheet->cols) : &(sheet->rows);
2231 if (infos->max_outline_level != max_outline) {
2232 sheet->priv->resize = TRUE;
2233 infos->max_outline_level = max_outline;
2237 struct sheet_extent_data {
2238 GnmRange range;
2239 gboolean spans_and_merges_extend;
2240 gboolean ignore_empties;
2241 gboolean include_hidden;
2244 static void
2245 cb_sheet_get_extent (G_GNUC_UNUSED gpointer ignored, gpointer value, gpointer data)
2247 GnmCell const *cell = (GnmCell const *) value;
2248 struct sheet_extent_data *res = data;
2249 Sheet *sheet = cell->base.sheet;
2250 ColRowInfo *ri = NULL;
2252 if (res->ignore_empties && gnm_cell_is_empty (cell))
2253 return;
2254 if (!res->include_hidden) {
2255 ri = sheet_col_get (sheet, cell->pos.col);
2256 if (!ri->visible)
2257 return;
2258 ri = sheet_row_get (sheet, cell->pos.row);
2259 if (!ri->visible)
2260 return;
2263 /* Remember the first cell is the min & max */
2264 if (res->range.start.col > cell->pos.col)
2265 res->range.start.col = cell->pos.col;
2266 if (res->range.end.col < cell->pos.col)
2267 res->range.end.col = cell->pos.col;
2268 if (res->range.start.row > cell->pos.row)
2269 res->range.start.row = cell->pos.row;
2270 if (res->range.end.row < cell->pos.row)
2271 res->range.end.row = cell->pos.row;
2273 if (!res->spans_and_merges_extend)
2274 return;
2276 /* Cannot span AND merge */
2277 if (gnm_cell_is_merged (cell)) {
2278 GnmRange const *merged =
2279 gnm_sheet_merge_is_corner (sheet, &cell->pos);
2280 res->range = range_union (&res->range, merged);
2281 } else {
2282 CellSpanInfo const *span;
2283 if (ri == NULL)
2284 ri = sheet_row_get (sheet, cell->pos.row);
2285 if (ri->needs_respan)
2286 row_calc_spans (ri, cell->pos.row, sheet);
2287 span = row_span_get (ri, cell->pos.col);
2288 if (NULL != span) {
2289 if (res->range.start.col > span->left)
2290 res->range.start.col = span->left;
2291 if (res->range.end.col < span->right)
2292 res->range.end.col = span->right;
2298 * sheet_get_extent:
2299 * @sheet: the sheet
2300 * @spans_and_merges_extend: optionally extend region for spans and merges.
2301 * @include_hidden: whether to include the content of hidden cells.
2303 * calculates the area occupied by cell data.
2305 * NOTE: When spans_and_merges_extend is TRUE, this function will calculate
2306 * all spans. That might be expensive.
2308 * NOTE: This refers to *visible* contents. Cells with empty values, including
2309 * formulas with such values, are *ignored.
2311 * Return value: the range.
2313 GnmRange
2314 sheet_get_extent (Sheet const *sheet, gboolean spans_and_merges_extend, gboolean include_hidden)
2316 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2317 struct sheet_extent_data closure;
2318 GSList *ptr;
2320 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2322 closure.range.start.col = gnm_sheet_get_last_col (sheet) + 1;
2323 closure.range.start.row = gnm_sheet_get_last_row (sheet) + 1;
2324 closure.range.end.col = 0;
2325 closure.range.end.row = 0;
2326 closure.spans_and_merges_extend = spans_and_merges_extend;
2327 closure.include_hidden = include_hidden;
2328 closure.ignore_empties = TRUE;
2330 sheet_cell_foreach (sheet, &cb_sheet_get_extent, &closure);
2332 for (ptr = sheet->sheet_objects; ptr; ptr = ptr->next) {
2333 SheetObject *so = GNM_SO (ptr->data);
2335 closure.range.start.col = MIN (so->anchor.cell_bound.start.col,
2336 closure.range.start.col);
2337 closure.range.start.row = MIN (so->anchor.cell_bound.start.row,
2338 closure.range.start.row);
2339 closure.range.end.col = MAX (so->anchor.cell_bound.end.col,
2340 closure.range.end.col);
2341 closure.range.end.row = MAX (so->anchor.cell_bound.end.row,
2342 closure.range.end.row);
2345 if (closure.range.start.col > gnm_sheet_get_last_col (sheet))
2346 closure.range.start.col = 0;
2347 if (closure.range.start.row > gnm_sheet_get_last_row (sheet))
2348 closure.range.start.row = 0;
2349 if (closure.range.end.col < 0)
2350 closure.range.end.col = 0;
2351 if (closure.range.end.row < 0)
2352 closure.range.end.row = 0;
2354 return closure.range;
2358 * sheet_get_cells_extent:
2359 * @sheet: the sheet
2361 * calculates the area occupied by cells, including empty cells.
2363 * Return value: the range.
2365 GnmRange
2366 sheet_get_cells_extent (Sheet const *sheet)
2368 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2369 struct sheet_extent_data closure;
2371 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2373 closure.range.start.col = gnm_sheet_get_last_col (sheet);
2374 closure.range.start.row = gnm_sheet_get_last_row (sheet);
2375 closure.range.end.col = 0;
2376 closure.range.end.row = 0;
2377 closure.spans_and_merges_extend = FALSE;
2378 closure.include_hidden = TRUE;
2379 closure.ignore_empties = FALSE;
2381 sheet_cell_foreach (sheet, &cb_sheet_get_extent, &closure);
2383 return closure.range;
2387 GnmRange *
2388 sheet_get_nominal_printarea (Sheet const *sheet)
2390 GnmNamedExpr *nexpr;
2391 GnmValue *val;
2392 GnmParsePos pos;
2393 GnmRange *r;
2394 GnmRangeRef const *r_ref;
2395 gint max_rows;
2396 gint max_cols;
2398 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2400 parse_pos_init_sheet (&pos, sheet);
2401 nexpr = expr_name_lookup (&pos, "Print_Area");
2402 if (nexpr == NULL)
2403 return NULL;
2405 val = gnm_expr_top_get_range (nexpr->texpr);
2406 r_ref = val ? value_get_rangeref (val) : NULL;
2407 if (r_ref == NULL) {
2408 value_release (val);
2409 return NULL;
2412 r = g_new0 (GnmRange, 1);
2413 range_init_rangeref (r, r_ref);
2414 value_release (val);
2416 if (r->end.col >= (max_cols = gnm_sheet_get_max_cols (sheet)))
2417 r->end.col = max_cols - 1;
2418 if (r->end.row >= (max_rows = gnm_sheet_get_max_rows (sheet)))
2419 r->end.row = max_rows - 1;
2420 if (r->start.col < 0)
2421 r->start.col = 0;
2422 if (r->start.row < 0)
2423 r->start.row = 0;
2425 return r;
2428 GnmRange
2429 sheet_get_printarea (Sheet const *sheet,
2430 gboolean include_styles,
2431 gboolean ignore_printarea)
2433 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2434 GnmRange print_area;
2436 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2438 if (!ignore_printarea) {
2439 GnmRange *r = sheet_get_nominal_printarea (sheet);
2440 if (r != NULL) {
2441 print_area = *r;
2442 g_free (r);
2443 return print_area;
2447 print_area = sheet_get_extent (sheet, TRUE, FALSE);
2448 if (include_styles)
2449 sheet_style_get_extent (sheet, &print_area);
2451 return print_area;
2454 struct cb_fit {
2455 int max;
2456 gboolean ignore_strings;
2459 /* find the maximum width in a range. */
2460 static GnmValue *
2461 cb_max_cell_width (GnmCellIter const *iter, struct cb_fit *data)
2463 int width;
2464 GnmCell *cell = iter->cell;
2465 GnmRenderedValue *rv;
2467 if (gnm_cell_is_merged (cell))
2468 return NULL;
2471 * Special handling for manual recalc. We need to eval newly
2472 * entered expressions. gnm_cell_render_value will do that for us,
2473 * but we want to short-circuit some strings early.
2475 if (cell->base.flags & GNM_CELL_HAS_NEW_EXPR)
2476 gnm_cell_eval (cell);
2478 if (data->ignore_strings && VALUE_IS_STRING (cell->value))
2479 return NULL;
2481 /* Variable width cell must be re-rendered */
2482 rv = gnm_cell_get_rendered_value (cell);
2483 if (rv == NULL || rv->variable_width)
2484 gnm_cell_render_value (cell, FALSE);
2486 /* Make sure things are as-if drawn. */
2487 cell_finish_layout (cell, NULL, iter->ci->size_pixels, TRUE);
2489 width = gnm_cell_rendered_width (cell) + gnm_cell_rendered_offset (cell);
2490 if (width > data->max)
2491 data->max = width;
2493 return NULL;
2497 * sheet_col_size_fit_pixels:
2498 * @sheet: The sheet
2499 * @col: the column that we want to query
2500 * @srow: starting row.
2501 * @erow: ending row.
2502 * @ignore_strings: skip cells containing string values.
2504 * This routine computes the ideal size for the column to make the contents all
2505 * cells in the column visible.
2507 * Returns: Maximum size in pixels INCLUDING margins and grid lines
2508 * or 0 if there are no cells.
2511 sheet_col_size_fit_pixels (Sheet *sheet, int col, int srow, int erow,
2512 gboolean ignore_strings)
2514 struct cb_fit data;
2515 ColRowInfo *ci = sheet_col_get (sheet, col);
2516 if (ci == NULL)
2517 return 0;
2519 data.max = -1;
2520 data.ignore_strings = ignore_strings;
2521 sheet_foreach_cell_in_region (sheet,
2522 CELL_ITER_IGNORE_NONEXISTENT |
2523 CELL_ITER_IGNORE_HIDDEN |
2524 CELL_ITER_IGNORE_FILTERED,
2525 col, srow, col, erow,
2526 (CellIterFunc)&cb_max_cell_width, &data);
2528 /* Reset to the default width if the column was empty */
2529 if (data.max <= 0)
2530 return 0;
2532 /* GnmCell width does not include margins or far grid line*/
2533 return data.max + GNM_COL_MARGIN + GNM_COL_MARGIN + 1;
2536 /* find the maximum height in a range. */
2537 static GnmValue *
2538 cb_max_cell_height (GnmCellIter const *iter, struct cb_fit *data)
2540 int height;
2541 GnmCell *cell = iter->cell;
2543 if (gnm_cell_is_merged (cell))
2544 return NULL;
2547 * Special handling for manual recalc. We need to eval newly
2548 * entered expressions. gnm_cell_render_value will do that for us,
2549 * but we want to short-circuit some strings early.
2551 if (cell->base.flags & GNM_CELL_HAS_NEW_EXPR)
2552 gnm_cell_eval (cell);
2554 if (data->ignore_strings && VALUE_IS_STRING (cell->value))
2555 return NULL;
2557 if (!VALUE_IS_STRING (cell->value)) {
2559 * Mildly cheating to avoid performance problems, See bug
2560 * 359392. This assumes that non-strings do not wrap and
2561 * that they are all the same height, more or less.
2563 Sheet const *sheet = cell->base.sheet;
2564 height = gnm_style_get_pango_height (gnm_cell_get_style (cell),
2565 sheet->rendered_values->context,
2566 sheet->last_zoom_factor_used);
2567 } else {
2568 (void)gnm_cell_fetch_rendered_value (cell, TRUE);
2570 /* Make sure things are as-if drawn. Inhibit #####s. */
2571 cell_finish_layout (cell, NULL, iter->ci->size_pixels, FALSE);
2573 height = gnm_cell_rendered_height (cell);
2576 if (height > data->max)
2577 data->max = height;
2579 return NULL;
2583 * sheet_row_size_fit_pixels:
2584 * @sheet: The sheet
2585 * @row: the row that we want to query
2586 * @scol: starting column.
2587 * @ecol: ending column.
2588 * @ignore_strings: skip cells containing string values.
2590 * This routine computes the ideal size for the row to make all data fit
2591 * properly.
2593 * Returns: Maximum size in pixels INCLUDING margins and grid lines
2594 * or 0 if there are no cells.
2597 sheet_row_size_fit_pixels (Sheet *sheet, int row, int scol, int ecol,
2598 gboolean ignore_strings)
2600 struct cb_fit data;
2601 ColRowInfo const *ri = sheet_row_get (sheet, row);
2602 if (ri == NULL)
2603 return 0;
2605 data.max = -1;
2606 data.ignore_strings = ignore_strings;
2607 sheet_foreach_cell_in_region (sheet,
2608 CELL_ITER_IGNORE_NONEXISTENT |
2609 CELL_ITER_IGNORE_HIDDEN |
2610 CELL_ITER_IGNORE_FILTERED,
2611 scol, row,
2612 ecol, row,
2613 (CellIterFunc)&cb_max_cell_height, &data);
2615 /* Reset to the default width if the column was empty */
2616 if (data.max <= 0)
2617 return 0;
2619 /* GnmCell height does not include margins or bottom grid line */
2620 return data.max + GNM_ROW_MARGIN + GNM_ROW_MARGIN + 1;
2623 struct recalc_span_closure {
2624 Sheet *sheet;
2625 int col;
2628 static gboolean
2629 cb_recalc_spans_in_col (GnmColRowIter const *iter, gpointer user)
2631 struct recalc_span_closure *closure = user;
2632 int const col = closure->col;
2633 int left, right;
2634 CellSpanInfo const *span = row_span_get (iter->cri, col);
2636 if (span) {
2637 /* If there is an existing span see if it changed */
2638 GnmCell const * const cell = span->cell;
2639 cell_calc_span (cell, &left, &right);
2640 if (left != span->left || right != span->right) {
2641 cell_unregister_span (cell);
2642 cell_register_span (cell, left, right);
2644 } else {
2645 /* If there is a cell see if it started to span */
2646 GnmCell const * const cell = sheet_cell_get (closure->sheet, col, iter->pos);
2647 if (cell) {
2648 cell_calc_span (cell, &left, &right);
2649 if (left != right)
2650 cell_register_span (cell, left, right);
2654 return FALSE;
2658 * sheet_recompute_spans_for_col:
2659 * @sheet: the sheet
2660 * @col: The column that changed
2662 * This routine recomputes the column span for the cells that touches
2663 * the column.
2665 void
2666 sheet_recompute_spans_for_col (Sheet *sheet, int col)
2668 struct recalc_span_closure closure;
2669 closure.sheet = sheet;
2670 closure.col = col;
2672 sheet_colrow_foreach (sheet, FALSE, 0, -1,
2673 &cb_recalc_spans_in_col, &closure);
2676 /****************************************************************************/
2677 typedef struct {
2678 GnmValue *val;
2679 GnmExprTop const *texpr;
2680 GnmRange expr_bound;
2681 } closure_set_cell_value;
2683 static GnmValue *
2684 cb_set_cell_content (GnmCellIter const *iter, closure_set_cell_value *info)
2686 GnmExprTop const *texpr = info->texpr;
2687 GnmCell *cell;
2689 cell = iter->cell;
2690 if (!cell)
2691 cell = sheet_cell_create (iter->pp.sheet,
2692 iter->pp.eval.col,
2693 iter->pp.eval.row);
2696 * If we are overwriting an array, we need to clear things here
2697 * or gnm_cell_set_expr/gnm_cell_set_value will complain.
2699 if (cell->base.texpr && gnm_expr_top_is_array (cell->base.texpr))
2700 gnm_cell_cleanout (cell);
2702 if (texpr != NULL) {
2703 if (!range_contains (&info->expr_bound,
2704 iter->pp.eval.col, iter->pp.eval.row)) {
2705 GnmExprRelocateInfo rinfo;
2707 rinfo.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE;
2708 rinfo.pos = iter->pp;
2709 rinfo.origin.start = iter->pp.eval;
2710 rinfo.origin.end = iter->pp.eval;
2711 rinfo.origin_sheet = iter->pp.sheet;
2712 rinfo.target_sheet = iter->pp.sheet;
2713 rinfo.col_offset = 0;
2714 rinfo.row_offset = 0;
2715 texpr = gnm_expr_top_relocate (texpr, &rinfo, FALSE);
2718 gnm_cell_set_expr (cell, texpr);
2719 } else
2720 gnm_cell_set_value (cell, value_dup (info->val));
2721 return NULL;
2724 static GnmValue *
2725 cb_clear_non_corner (GnmCellIter const *iter, GnmRange const *merged)
2727 if (merged->start.col != iter->pp.eval.col ||
2728 merged->start.row != iter->pp.eval.row)
2729 gnm_cell_set_value (iter->cell, value_new_empty ());
2730 return NULL;
2734 * sheet_range_set_expr_cb:
2735 * @sr: #GnmSheetRange
2736 * @texpr: #GnmExprTop
2739 * Does NOT check for array division.
2741 static void
2742 sheet_range_set_expr_cb (GnmSheetRange const *sr, GnmExprTop const *texpr)
2744 closure_set_cell_value closure;
2745 GSList *merged, *ptr;
2747 g_return_if_fail (sr != NULL);
2748 g_return_if_fail (texpr != NULL);
2750 closure.texpr = texpr;
2751 gnm_expr_top_get_boundingbox (closure.texpr,
2752 sr->sheet,
2753 &closure.expr_bound);
2755 sheet_region_queue_recalc (sr->sheet, &sr->range);
2756 /* Store the parsed result creating any cells necessary */
2757 sheet_foreach_cell_in_range
2758 (sr->sheet, CELL_ITER_ALL, &sr->range,
2759 (CellIterFunc)&cb_set_cell_content, &closure);
2761 merged = gnm_sheet_merge_get_overlap (sr->sheet, &sr->range);
2762 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
2763 GnmRange const *tmp = ptr->data;
2764 sheet_foreach_cell_in_range
2765 (sr->sheet, CELL_ITER_IGNORE_BLANK, tmp,
2766 (CellIterFunc)&cb_clear_non_corner,
2767 (gpointer)tmp);
2769 g_slist_free (merged);
2771 sheet_region_queue_recalc (sr->sheet, &sr->range);
2772 sheet_flag_status_update_range (sr->sheet, &sr->range);
2773 sheet_queue_respan (sr->sheet, sr->range.start.row,
2774 sr->range.end.row);
2778 * sheet_range_set_expr_undo:
2779 * @sr: #GnmSheetRange
2780 * @texpr: #GnmExprTop
2782 * Returns: (transfer full): the newly created #GOUndo.
2784 GOUndo *
2785 sheet_range_set_expr_undo (GnmSheetRange *sr, GnmExprTop const *texpr)
2787 gnm_expr_top_ref (texpr);
2788 return go_undo_binary_new
2789 (sr, (gpointer)texpr,
2790 (GOUndoBinaryFunc) sheet_range_set_expr_cb,
2791 (GFreeFunc) gnm_sheet_range_free,
2792 (GFreeFunc) gnm_expr_top_unref);
2797 * sheet_range_set_text:
2798 * @pos: The position from which to parse an expression.
2799 * @r: The range to fill
2800 * @str: The text to be parsed and assigned.
2802 * Does NOT check for array division.
2803 * Does NOT redraw
2804 * Does NOT generate spans.
2806 void
2807 sheet_range_set_text (GnmParsePos const *pos, GnmRange const *r, char const *str)
2809 closure_set_cell_value closure;
2810 GSList *merged, *ptr;
2811 Sheet *sheet;
2813 g_return_if_fail (pos != NULL);
2814 g_return_if_fail (r != NULL);
2815 g_return_if_fail (str != NULL);
2817 sheet = pos->sheet;
2819 parse_text_value_or_expr (pos, str,
2820 &closure.val, &closure.texpr);
2822 if (closure.texpr)
2823 gnm_expr_top_get_boundingbox (closure.texpr,
2824 sheet,
2825 &closure.expr_bound);
2827 /* Store the parsed result creating any cells necessary */
2828 sheet_foreach_cell_in_range (sheet, CELL_ITER_ALL, r,
2829 (CellIterFunc)&cb_set_cell_content, &closure);
2831 merged = gnm_sheet_merge_get_overlap (sheet, r);
2832 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
2833 GnmRange const *tmp = ptr->data;
2834 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_BLANK, tmp,
2835 (CellIterFunc)&cb_clear_non_corner, (gpointer)tmp);
2837 g_slist_free (merged);
2839 sheet_region_queue_recalc (sheet, r);
2841 value_release (closure.val);
2842 if (closure.texpr)
2843 gnm_expr_top_unref (closure.texpr);
2845 sheet_flag_status_update_range (sheet, r);
2848 static void
2849 sheet_range_set_text_cb (GnmSheetRange const *sr, gchar const *text)
2851 GnmParsePos pos;
2853 pos.eval = sr->range.start;
2854 pos.sheet = sr->sheet;
2855 pos.wb = sr->sheet->workbook;
2857 sheet_range_set_text (&pos, &sr->range, text);
2858 sheet_region_queue_recalc (sr->sheet, &sr->range);
2859 sheet_flag_status_update_range (sr->sheet, &sr->range);
2860 sheet_queue_respan (sr->sheet, sr->range.start.row,
2861 sr->range.end.row);
2862 sheet_redraw_range (sr->sheet, &sr->range);
2866 * sheet_range_set_text_undo:
2867 * @sr: #GnmSheetRange
2868 * @text:
2870 * Returns: (transfer full): the newly created #GOUndo.
2872 GOUndo *
2873 sheet_range_set_text_undo (GnmSheetRange *sr,
2874 char const *text)
2876 return go_undo_binary_new
2877 (sr, g_strdup (text),
2878 (GOUndoBinaryFunc) sheet_range_set_text_cb,
2879 (GFreeFunc) gnm_sheet_range_free,
2880 (GFreeFunc) g_free);
2884 static GnmValue *
2885 cb_set_markup (GnmCellIter const *iter, PangoAttrList *markup)
2887 GnmCell *cell;
2889 cell = iter->cell;
2890 if (!cell)
2891 return NULL;
2893 if (VALUE_IS_STRING (cell->value)) {
2894 GOFormat *fmt;
2895 GnmValue *val = value_dup (cell->value);
2897 fmt = go_format_new_markup (markup, TRUE);
2898 value_set_fmt (val, fmt);
2899 go_format_unref (fmt);
2901 gnm_cell_cleanout (cell);
2902 gnm_cell_assign_value (cell, val);
2904 return NULL;
2907 static void
2908 sheet_range_set_markup_cb (GnmSheetRange const *sr, PangoAttrList *markup)
2910 sheet_foreach_cell_in_range
2911 (sr->sheet, CELL_ITER_ALL, &sr->range,
2912 (CellIterFunc)&cb_set_markup, markup);
2914 sheet_region_queue_recalc (sr->sheet, &sr->range);
2915 sheet_flag_status_update_range (sr->sheet, &sr->range);
2916 sheet_queue_respan (sr->sheet, sr->range.start.row,
2917 sr->range.end.row);
2921 * sheet_range_set_markup_undo:
2922 * @sr: #GnmSheetRange
2923 * @markup: #PangoAttrList
2925 * Returns: (transfer full): the newly created #GOUndo.
2927 GOUndo *
2928 sheet_range_set_markup_undo (GnmSheetRange *sr, PangoAttrList *markup)
2930 if (markup == NULL)
2931 return NULL;
2932 return go_undo_binary_new
2933 (sr, pango_attr_list_ref (markup),
2934 (GOUndoBinaryFunc) sheet_range_set_markup_cb,
2935 (GFreeFunc) gnm_sheet_range_free,
2936 (GFreeFunc) pango_attr_list_unref);
2940 * sheet_cell_get_value:
2941 * @sheet: Sheet
2942 * @col: Source column
2943 * @row: Source row
2945 * Returns: (transfer none) (nullable): the cell's current value.
2947 GnmValue const *
2948 sheet_cell_get_value (Sheet *sheet, int const col, int const row)
2950 GnmCell *cell;
2952 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2954 cell = sheet_cell_get (sheet, col, row);
2956 return cell ? cell->value : NULL;
2960 * sheet_cell_set_text:
2961 * @cell: A cell.
2962 * @str: the text to set.
2963 * @markup: (allow-none): an optional PangoAttrList.
2965 * Marks the sheet as dirty
2966 * Clears old spans.
2967 * Flags status updates
2968 * Queues recalcs
2970 void
2971 sheet_cell_set_text (GnmCell *cell, char const *text, PangoAttrList *markup)
2973 GnmExprTop const *texpr;
2974 GnmValue *val;
2975 GnmParsePos pp;
2977 g_return_if_fail (cell != NULL);
2978 g_return_if_fail (text != NULL);
2979 g_return_if_fail (!gnm_cell_is_nonsingleton_array (cell));
2981 parse_text_value_or_expr (parse_pos_init_cell (&pp, cell),
2982 text, &val, &texpr);
2984 /* Queue a redraw before in case the span changes */
2985 sheet_redraw_cell (cell);
2987 if (texpr != NULL) {
2988 gnm_cell_set_expr (cell, texpr);
2989 gnm_expr_top_unref (texpr);
2992 * Queue recalc before spanning. Otherwise spanning may
2993 * create a bogus rendered value, see #495879.
2995 cell_queue_recalc (cell);
2997 /* Clear spans from _other_ cells */
2998 sheet_cell_calc_span (cell, GNM_SPANCALC_SIMPLE);
2999 } else {
3000 g_return_if_fail (val != NULL);
3002 if (markup != NULL && VALUE_IS_STRING (val)) {
3003 gboolean quoted = (text[0] == '\'');
3004 PangoAttrList *adj_markup;
3005 GOFormat *fmt;
3007 if (quoted) {
3008 /* We ate the quote. Adjust. Ugh. */
3009 adj_markup = pango_attr_list_copy (markup);
3010 go_pango_attr_list_erase (adj_markup, 0, 1);
3011 } else
3012 adj_markup = markup;
3014 fmt = go_format_new_markup (adj_markup, TRUE);
3015 value_set_fmt (val, fmt);
3016 go_format_unref (fmt);
3017 if (quoted)
3018 pango_attr_list_unref (adj_markup);
3021 gnm_cell_set_value (cell, val);
3023 /* Queue recalc before spanning, see above. */
3024 cell_queue_recalc (cell);
3026 sheet_cell_calc_span (cell, GNM_SPANCALC_RESIZE | GNM_SPANCALC_RENDER);
3029 sheet_flag_status_update_cell (cell);
3033 * sheet_cell_set_text_gi: (rename-to sheet_cell_set_text)
3034 * @sheet: #Sheet
3035 * @col: column number
3036 * @row: row number
3037 * @str: the text to set.
3039 * Sets the contents of a cell.
3041 void
3042 sheet_cell_set_text_gi (Sheet *sheet, int col, int row, char const *str)
3044 sheet_cell_set_text (sheet_cell_fetch (sheet, col, row), str, NULL);
3049 * sheet_cell_set_expr:
3051 * Marks the sheet as dirty
3052 * Clears old spans.
3053 * Flags status updates
3054 * Queues recalcs
3056 void
3057 sheet_cell_set_expr (GnmCell *cell, GnmExprTop const *texpr)
3059 gnm_cell_set_expr (cell, texpr);
3061 /* clear spans from _other_ cells */
3062 sheet_cell_calc_span (cell, GNM_SPANCALC_SIMPLE);
3064 cell_queue_recalc (cell);
3065 sheet_flag_status_update_cell (cell);
3069 * sheet_cell_set_value: (skip)
3070 * @cell: #GnmCell
3071 * @v: (transfer full): #GnmValue
3073 * Stores, without copying, the supplied value. It marks the
3074 * sheet as dirty.
3076 * The value is rendered and spans are calculated. It queues a redraw
3077 * and checks to see if the edit region or selection content changed.
3079 * NOTE : This DOES check for array partitioning.
3081 void
3082 sheet_cell_set_value (GnmCell *cell, GnmValue *v)
3084 /* TODO : if the value is unchanged do not assign it */
3085 gnm_cell_set_value (cell, v);
3086 sheet_cell_calc_span (cell, GNM_SPANCALC_RESIZE | GNM_SPANCALC_RENDER);
3087 cell_queue_recalc (cell);
3088 sheet_flag_status_update_cell (cell);
3092 * sheet_cell_set_value_gi: (rename-to sheet_cell_set_value)
3093 * @sheet: #Sheet
3094 * @col: column number
3095 * @row: row number
3096 * @v: #GnmValue
3098 * Set the the value of the cell at (@col,@row) to @v.
3100 * The value is rendered and spans are calculated. It queues a redraw
3101 * and checks to see if the edit region or selection content changed.
3103 void
3104 sheet_cell_set_value_gi (Sheet *sheet, int col, int row, GnmValue *v)
3106 // This version exists because not all versions of pygobject
3107 // understand transfer-full parameters
3108 sheet_cell_set_value (sheet_cell_fetch (sheet, col, row),
3109 value_dup (v));
3112 /****************************************************************************/
3115 * This routine is used to queue the redraw regions for the
3116 * cell region specified.
3118 * It is usually called before a change happens to a region,
3119 * and after the change has been done to queue the regions
3120 * for the old contents and the new contents.
3122 * It intelligently handles spans and merged ranges
3124 void
3125 sheet_range_bounding_box (Sheet const *sheet, GnmRange *bound)
3127 GSList *ptr;
3128 int row;
3129 GnmRange r = *bound;
3131 g_return_if_fail (range_is_sane (bound));
3133 /* Check the first and last columns for spans and extend the region to
3134 * include the maximum extent.
3136 for (row = r.start.row; row <= r.end.row; row++){
3137 ColRowInfo const *ri = sheet_row_get (sheet, row);
3139 if (ri != NULL) {
3140 CellSpanInfo const * span0;
3142 if (ri->needs_respan)
3143 row_calc_spans ((ColRowInfo *)ri, row, sheet);
3145 span0 = row_span_get (ri, r.start.col);
3147 if (span0 != NULL) {
3148 if (bound->start.col > span0->left)
3149 bound->start.col = span0->left;
3150 if (bound->end.col < span0->right)
3151 bound->end.col = span0->right;
3153 if (r.start.col != r.end.col) {
3154 CellSpanInfo const * span1 =
3155 row_span_get (ri, r.end.col);
3157 if (span1 != NULL) {
3158 if (bound->start.col > span1->left)
3159 bound->start.col = span1->left;
3160 if (bound->end.col < span1->right)
3161 bound->end.col = span1->right;
3164 /* skip segments with no cells */
3165 } else if (row == COLROW_SEGMENT_START (row)) {
3166 ColRowSegment const * const segment =
3167 COLROW_GET_SEGMENT (&(sheet->rows), row);
3168 if (segment == NULL)
3169 row = COLROW_SEGMENT_END (row);
3173 /* TODO : this may get expensive if there are alot of merged ranges */
3174 /* no need to iterate, one pass is enough */
3175 for (ptr = sheet->list_merged ; ptr != NULL ; ptr = ptr->next) {
3176 GnmRange const * const test = ptr->data;
3177 if (r.start.row <= test->end.row || r.end.row >= test->start.row) {
3178 if (bound->start.col > test->start.col)
3179 bound->start.col = test->start.col;
3180 if (bound->end.col < test->end.col)
3181 bound->end.col = test->end.col;
3182 if (bound->start.row > test->start.row)
3183 bound->start.row = test->start.row;
3184 if (bound->end.row < test->end.row)
3185 bound->end.row = test->end.row;
3190 void
3191 sheet_redraw_region (Sheet const *sheet,
3192 int start_col, int start_row,
3193 int end_col, int end_row)
3195 GnmRange bound;
3197 g_return_if_fail (IS_SHEET (sheet));
3200 * Getting the bounding box causes row respans to be done if
3201 * needed. That can be expensive, so just redraw the whole
3202 * sheet if the row count is too big.
3204 if (end_row - start_row > 500) {
3205 sheet_redraw_all (sheet, FALSE);
3206 return;
3209 /* We potentially do a lot of recalcs as part of this, so make sure
3210 stuff that caches sub-computations see the whole thing instead
3211 of clearing between cells. */
3212 gnm_app_recalc_start ();
3214 sheet_range_bounding_box (sheet,
3215 range_init (&bound, start_col, start_row, end_col, end_row));
3216 SHEET_FOREACH_CONTROL (sheet, view, control,
3217 sc_redraw_range (control, &bound););
3219 gnm_app_recalc_finish ();
3222 void
3223 sheet_redraw_range (Sheet const *sheet, GnmRange const *range)
3225 g_return_if_fail (IS_SHEET (sheet));
3226 g_return_if_fail (range != NULL);
3228 sheet_redraw_region (sheet,
3229 range->start.col, range->start.row,
3230 range->end.col, range->end.row);
3233 /****************************************************************************/
3235 gboolean
3236 sheet_col_is_hidden (Sheet const *sheet, int col)
3238 ColRowInfo const * const res = sheet_col_get (sheet, col);
3239 return (res != NULL && !res->visible);
3242 gboolean
3243 sheet_row_is_hidden (Sheet const *sheet, int row)
3245 ColRowInfo const * const res = sheet_row_get (sheet, row);
3246 return (res != NULL && !res->visible);
3251 * sheet_find_boundary_horizontal:
3252 * @sheet: The Sheet
3253 * @col: The column from which to begin searching.
3254 * @move_row: The row in which to search for the edge of the range.
3255 * @base_row: The height of the area being moved.
3256 * @count: units to extend the selection vertically
3257 * @jump_to_boundaries: Jump to range boundaries.
3259 * Calculate the column index for the column which is @n units
3260 * from @start_col doing bounds checking. If @jump_to_boundaries is
3261 * TRUE @n must be 1 and the jump is to the edge of the logical range.
3263 * NOTE : This routine implements the logic necasary for ctrl-arrow style
3264 * movement. That is more compilcated than simply finding the last in a list
3265 * of cells with content. If you are at the end of a range it will find the
3266 * start of the next. Make sure that is the sort of behavior you want before
3267 * calling this.
3268 * Returns: the column inex.
3271 sheet_find_boundary_horizontal (Sheet *sheet, int start_col, int move_row,
3272 int base_row, int count,
3273 gboolean jump_to_boundaries)
3275 gboolean find_nonblank = sheet_is_cell_empty (sheet, start_col, move_row);
3276 gboolean keep_looking = FALSE;
3277 int new_col, prev_col, lagged_start_col, max_col = gnm_sheet_get_last_col (sheet);
3278 int iterations = 0;
3279 GnmRange check_merge;
3280 GnmRange const * const bound = &sheet->priv->unhidden_region;
3282 /* Jumping to bounds requires steping cell by cell */
3283 g_return_val_if_fail (count == 1 || count == -1 || !jump_to_boundaries, start_col);
3284 g_return_val_if_fail (IS_SHEET (sheet), start_col);
3286 if (move_row < base_row) {
3287 check_merge.start.row = move_row;
3288 check_merge.end.row = base_row;
3289 } else {
3290 check_merge.end.row = move_row;
3291 check_merge.start.row = base_row;
3294 do {
3295 GSList *merged, *ptr;
3297 lagged_start_col = check_merge.start.col = check_merge.end.col = start_col;
3298 merged = gnm_sheet_merge_get_overlap (sheet, &check_merge);
3299 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3300 GnmRange const * const r = ptr->data;
3301 if (count > 0) {
3302 if (start_col < r->end.col)
3303 start_col = r->end.col;
3304 } else {
3305 if (start_col > r->start.col)
3306 start_col = r->start.col;
3309 g_slist_free (merged);
3310 } while (start_col != lagged_start_col);
3311 new_col = prev_col = start_col;
3313 do {
3314 new_col += count;
3315 ++iterations;
3317 if (new_col < bound->start.col)
3318 return MIN (bound->start.col, max_col);
3319 if (new_col > bound->end.col)
3320 return MIN (bound->end.col, max_col);
3322 keep_looking = sheet_col_is_hidden (sheet, new_col);
3323 if (jump_to_boundaries) {
3324 if (new_col > sheet->cols.max_used) {
3325 if (count > 0)
3326 return (find_nonblank || iterations == 1)?
3327 MIN (bound->end.col, max_col):
3328 MIN (prev_col, max_col);
3329 new_col = sheet->cols.max_used;
3332 keep_looking |= (sheet_is_cell_empty (sheet, new_col, move_row) == find_nonblank);
3333 if (keep_looking)
3334 prev_col = new_col;
3335 else if (!find_nonblank) {
3337 * Handle special case where we are on the last
3338 * non-null cell
3340 if (iterations == 1)
3341 keep_looking = find_nonblank = TRUE;
3342 else
3343 new_col = prev_col;
3346 } while (keep_looking);
3348 return MIN (new_col, max_col);
3352 * sheet_find_boundary_vertical:
3353 * @sheet: The Sheet *
3354 * @move_col: The col in which to search for the edge of the range.
3355 * @row: The row from which to begin searching.
3356 * @base_col: The width of the area being moved.
3357 * @count: units to extend the selection vertically
3358 * @jump_to_boundaries: Jump to range boundaries.
3360 * Calculate the row index for the row which is @n units
3361 * from @start_row doing bounds checking. If @jump_to_boundaries is
3362 * TRUE @n must be 1 and the jump is to the edge of the logical range.
3364 * NOTE : This routine implements the logic necasary for ctrl-arrow style
3365 * movement. That is more compilcated than simply finding the last in a list
3366 * of cells with content. If you are at the end of a range it will find the
3367 * start of the next. Make sure that is the sort of behavior you want before
3368 * calling this.
3369 * Returns: the row index.
3372 sheet_find_boundary_vertical (Sheet *sheet, int move_col, int start_row,
3373 int base_col, int count,
3374 gboolean jump_to_boundaries)
3376 gboolean find_nonblank = sheet_is_cell_empty (sheet, move_col, start_row);
3377 gboolean keep_looking = FALSE;
3378 int new_row, prev_row, lagged_start_row, max_row = gnm_sheet_get_last_row (sheet);
3379 int iterations = 0;
3380 GnmRange check_merge;
3381 GnmRange const * const bound = &sheet->priv->unhidden_region;
3383 /* Jumping to bounds requires steping cell by cell */
3384 g_return_val_if_fail (count == 1 || count == -1 || !jump_to_boundaries, start_row);
3385 g_return_val_if_fail (IS_SHEET (sheet), start_row);
3387 if (move_col < base_col) {
3388 check_merge.start.col = move_col;
3389 check_merge.end.col = base_col;
3390 } else {
3391 check_merge.end.col = move_col;
3392 check_merge.start.col = base_col;
3395 do {
3396 GSList *merged, *ptr;
3398 lagged_start_row = check_merge.start.row = check_merge.end.row = start_row;
3399 merged = gnm_sheet_merge_get_overlap (sheet, &check_merge);
3400 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3401 GnmRange const * const r = ptr->data;
3402 if (count > 0) {
3403 if (start_row < r->end.row)
3404 start_row = r->end.row;
3405 } else {
3406 if (start_row > r->start.row)
3407 start_row = r->start.row;
3410 g_slist_free (merged);
3411 } while (start_row != lagged_start_row);
3412 new_row = prev_row = start_row;
3414 do {
3415 new_row += count;
3416 ++iterations;
3418 if (new_row < bound->start.row)
3419 return MIN (bound->start.row, max_row);
3420 if (new_row > bound->end.row)
3421 return MIN (bound->end.row, max_row);
3423 keep_looking = sheet_row_is_hidden (sheet, new_row);
3424 if (jump_to_boundaries) {
3425 if (new_row > sheet->rows.max_used) {
3426 if (count > 0)
3427 return (find_nonblank || iterations == 1)?
3428 MIN (bound->end.row, max_row):
3429 MIN (prev_row, max_row);
3430 new_row = sheet->rows.max_used;
3433 keep_looking |= (sheet_is_cell_empty (sheet, move_col, new_row) == find_nonblank);
3434 if (keep_looking)
3435 prev_row = new_row;
3436 else if (!find_nonblank) {
3438 * Handle special case where we are on the last
3439 * non-null cell
3441 if (iterations == 1)
3442 keep_looking = find_nonblank = TRUE;
3443 else
3444 new_row = prev_row;
3447 } while (keep_looking);
3449 return MIN (new_row, max_row);
3452 typedef enum {
3453 CHECK_AND_LOAD_START = 1,
3454 CHECK_END = 2,
3455 LOAD_END = 4
3456 } ArrayCheckFlags;
3458 typedef struct {
3459 Sheet const *sheet;
3460 int flags;
3461 int start, end;
3462 GnmRange const *ignore;
3464 GnmRange error;
3465 } ArrayCheckData;
3467 static gboolean
3468 cb_check_array_horizontal (GnmColRowIter const *iter, gpointer data_)
3470 ArrayCheckData *data = data_;
3471 gboolean is_array = FALSE;
3473 if (data->flags & CHECK_AND_LOAD_START && /* Top */
3474 (is_array = gnm_cell_array_bound (
3475 sheet_cell_get (data->sheet, iter->pos, data->start),
3476 &data->error)) &&
3477 data->error.start.row < data->start &&
3478 (data->ignore == NULL ||
3479 !range_contained (&data->error, data->ignore)))
3480 return TRUE;
3482 if (data->flags & LOAD_END)
3483 is_array = gnm_cell_array_bound (
3484 sheet_cell_get (data->sheet, iter->pos, data->end),
3485 &data->error);
3487 return (data->flags & CHECK_END &&
3488 is_array &&
3489 data->error.end.row > data->end && /* Bottom */
3490 (data->ignore == NULL ||
3491 !range_contained (&data->error, data->ignore)));
3494 static gboolean
3495 cb_check_array_vertical (GnmColRowIter const *iter, gpointer data_)
3497 ArrayCheckData *data = data_;
3498 gboolean is_array = FALSE;
3500 if (data->flags & CHECK_AND_LOAD_START && /* Left */
3501 (is_array = gnm_cell_array_bound (
3502 sheet_cell_get (data->sheet, data->start, iter->pos),
3503 &data->error)) &&
3504 data->error.start.col < data->start &&
3505 (data->ignore == NULL ||
3506 !range_contained (&data->error, data->ignore)))
3507 return TRUE;
3509 if (data->flags & LOAD_END)
3510 is_array = gnm_cell_array_bound (
3511 sheet_cell_get (data->sheet, data->end, iter->pos),
3512 &data->error);
3514 return (data->flags & CHECK_END &&
3515 is_array &&
3516 data->error.end.col > data->end && /* Right */
3517 (data->ignore == NULL ||
3518 !range_contained (&data->error, data->ignore)));
3522 * sheet_range_splits_array:
3523 * @sheet: The sheet.
3524 * @r: The range to check
3525 * @ignore: (nullable): a range in which it is ok to have an array.
3526 * @cc: (nullable): place to report an error.
3527 * @cmd: (nullable): cmd name used with @cc.
3529 * Check the outer edges of range @sheet!@r to ensure that if an array is
3530 * within it then the entire array is within the range. @ignore is useful when
3531 * src & dest ranges may overlap.
3533 * Returns: TRUE if an array would be split.
3535 gboolean
3536 sheet_range_splits_array (Sheet const *sheet,
3537 GnmRange const *r, GnmRange const *ignore,
3538 GOCmdContext *cc, char const *cmd)
3540 ArrayCheckData closure;
3542 g_return_val_if_fail (r->start.col <= r->end.col, FALSE);
3543 g_return_val_if_fail (r->start.row <= r->end.row, FALSE);
3545 closure.sheet = sheet;
3546 closure.ignore = ignore;
3548 closure.start = r->start.row;
3549 closure.end = r->end.row;
3550 if (closure.start <= 0) {
3551 closure.flags = (closure.end < sheet->rows.max_used)
3552 ? CHECK_END | LOAD_END
3553 : 0;
3554 } else if (closure.end < sheet->rows.max_used)
3555 closure.flags = (closure.start == closure.end)
3556 ? CHECK_AND_LOAD_START | CHECK_END
3557 : CHECK_AND_LOAD_START | CHECK_END | LOAD_END;
3558 else
3559 closure.flags = CHECK_AND_LOAD_START;
3561 if (closure.flags &&
3562 sheet_colrow_foreach (sheet, TRUE,
3563 r->start.col, r->end.col,
3564 cb_check_array_horizontal, &closure)) {
3565 if (cc)
3566 gnm_cmd_context_error_splits_array (cc,
3567 cmd, &closure.error);
3568 return TRUE;
3571 closure.start = r->start.col;
3572 closure.end = r->end.col;
3573 if (closure.start <= 0) {
3574 closure.flags = (closure.end < sheet->cols.max_used)
3575 ? CHECK_END | LOAD_END
3576 : 0;
3577 } else if (closure.end < sheet->cols.max_used)
3578 closure.flags = (closure.start == closure.end)
3579 ? CHECK_AND_LOAD_START | CHECK_END
3580 : CHECK_AND_LOAD_START | CHECK_END | LOAD_END;
3581 else
3582 closure.flags = CHECK_AND_LOAD_START;
3584 if (closure.flags &&
3585 sheet_colrow_foreach (sheet, FALSE,
3586 r->start.row, r->end.row,
3587 cb_check_array_vertical, &closure)) {
3588 if (cc)
3589 gnm_cmd_context_error_splits_array (cc,
3590 cmd, &closure.error);
3591 return TRUE;
3593 return FALSE;
3597 * sheet_range_splits_region:
3598 * @sheet: the sheet.
3599 * @r: The range whose boundaries are checked
3600 * @ignore: An optional range in which it is ok to have arrays and merges
3601 * @cc: The context that issued the command
3602 * @cmd: The translated command name.
3604 * A utility to see whether moving the range @r will split any arrays
3605 * or merged regions.
3606 * Returns: whether any arrays or merged regions will be split.
3608 gboolean
3609 sheet_range_splits_region (Sheet const *sheet,
3610 GnmRange const *r, GnmRange const *ignore,
3611 GOCmdContext *cc, char const *cmd_name)
3613 GSList *merged;
3615 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
3617 /* Check for array subdivision */
3618 if (sheet_range_splits_array (sheet, r, ignore, cc, cmd_name))
3619 return TRUE;
3621 merged = gnm_sheet_merge_get_overlap (sheet, r);
3622 if (merged) {
3623 GSList *ptr;
3625 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3626 GnmRange const *m = ptr->data;
3627 if (ignore != NULL && range_contained (m, ignore))
3628 continue;
3629 if (!range_contained (m, r))
3630 break;
3632 g_slist_free (merged);
3634 if (cc != NULL && ptr != NULL) {
3635 go_cmd_context_error_invalid (cc, cmd_name,
3636 _("Target region contains merged cells"));
3637 return TRUE;
3640 return FALSE;
3644 * sheet_ranges_split_region:
3645 * @sheet: the sheet.
3646 * @ranges: (element-type GnmRange): A list of ranges to check.
3647 * @cc: The context that issued the command
3648 * @cmd: The translated command name.
3650 * A utility to see whether moving any of the ranges @ranges will split any
3651 * arrays or merged regions.
3652 * Returns: whether any arrays or merged regions will be splitted.
3654 gboolean
3655 sheet_ranges_split_region (Sheet const * sheet, GSList const *ranges,
3656 GOCmdContext *cc, char const *cmd)
3658 GSList const *l;
3660 /* Check for array subdivision */
3661 for (l = ranges; l != NULL; l = l->next) {
3662 GnmRange const *r = l->data;
3663 if (sheet_range_splits_region (sheet, r, NULL, cc, cmd))
3664 return TRUE;
3666 return FALSE;
3669 static GnmValue *
3670 cb_cell_is_array (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
3672 return gnm_cell_is_array (iter->cell) ? VALUE_TERMINATE : NULL;
3676 * sheet_range_contains_merges_or_arrays:
3677 * @sheet: The sheet
3678 * @r: the range to check.
3679 * @cc: an optional place to report errors.
3680 * @cmd:
3681 * @merges: if %TRUE, check for merges.
3682 * @arrays: if %TRUE, check for arrays.
3684 * Check to see if the target region @sheet!@r contains any merged regions or
3685 * arrays. Report an error to the @cc if it is supplied.
3686 * Returns: %TRUE if the target region @sheet!@r contains any merged regions or
3687 * arrays.
3689 gboolean
3690 sheet_range_contains_merges_or_arrays (Sheet const *sheet, GnmRange const *r,
3691 GOCmdContext *cc, char const *cmd,
3692 gboolean merges, gboolean arrays)
3694 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
3696 if (merges) {
3697 GSList *merged = gnm_sheet_merge_get_overlap (sheet, r);
3698 if (merged != NULL) {
3699 if (cc != NULL)
3700 go_cmd_context_error_invalid
3701 (cc, cmd,
3702 _("cannot operate on merged cells"));
3703 g_slist_free (merged);
3704 return TRUE;
3708 if (arrays) {
3709 if (sheet_foreach_cell_in_range (
3710 (Sheet *)sheet, CELL_ITER_IGNORE_NONEXISTENT, r,
3711 cb_cell_is_array, NULL)) {
3712 if (cc != NULL)
3713 go_cmd_context_error_invalid
3714 (cc, cmd,
3715 _("cannot operate on array formul\303\246"));
3716 return TRUE;
3720 return FALSE;
3723 /***************************************************************************/
3726 * sheet_colrow_get_default:
3727 * @sheet:
3728 * @is_cols:
3730 * Returns: (transfer none): the default #ColRowInfo.
3732 ColRowInfo const *
3733 sheet_colrow_get_default (Sheet const *sheet, gboolean is_cols)
3735 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3737 return is_cols ? &sheet->cols.default_style : &sheet->rows.default_style;
3740 static void
3741 sheet_colrow_optimize1 (int max, int max_used, ColRowCollection *collection)
3743 int i;
3744 int first_unused = max_used + 1;
3746 for (i = COLROW_SEGMENT_START (first_unused);
3747 i < max;
3748 i += COLROW_SEGMENT_SIZE) {
3749 ColRowSegment *segment = COLROW_GET_SEGMENT (collection, i);
3750 int j;
3751 gboolean any = FALSE;
3753 if (!segment)
3754 continue;
3755 for (j = 0; j < COLROW_SEGMENT_SIZE; j++) {
3756 ColRowInfo *info = segment->info[j];
3757 if (!info)
3758 continue;
3759 if (i + j >= first_unused &&
3760 col_row_info_equal (&collection->default_style, info)) {
3761 colrow_free (info);
3762 segment->info[j] = NULL;
3763 } else {
3764 any = TRUE;
3765 if (i + j >= first_unused)
3766 max_used = i + j;
3770 if (!any) {
3771 g_free (segment);
3772 COLROW_GET_SEGMENT (collection, i) = NULL;
3776 collection->max_used = max_used;
3779 void
3780 sheet_colrow_optimize (Sheet *sheet)
3782 GnmRange extent;
3784 g_return_if_fail (IS_SHEET (sheet));
3786 extent = sheet_get_cells_extent (sheet);
3788 sheet_colrow_optimize1 (gnm_sheet_get_max_cols (sheet),
3789 extent.end.col,
3790 &sheet->cols);
3791 sheet_colrow_optimize1 (gnm_sheet_get_max_rows (sheet),
3792 extent.end.row,
3793 &sheet->rows);
3797 * sheet_col_get:
3798 * @col: column number
3800 * Returns: (transfer none) (nullable): A #ColRowInfo for the column, or %NULL
3801 * if none has been allocated yet.
3803 ColRowInfo *
3804 sheet_col_get (Sheet const *sheet, int col)
3806 ColRowSegment *segment;
3808 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3809 g_return_val_if_fail (col < gnm_sheet_get_max_cols (sheet), NULL);
3810 g_return_val_if_fail (col >= 0, NULL);
3812 segment = COLROW_GET_SEGMENT (&(sheet->cols), col);
3813 if (segment != NULL)
3814 return segment->info[COLROW_SUB_INDEX (col)];
3815 return NULL;
3819 * sheet_row_get:
3820 * @row: row number
3822 * Returns: (transfer none) (nullable): A #ColRowInfo for the row, or %NULL
3823 * if none has been allocated yet.
3825 ColRowInfo *
3826 sheet_row_get (Sheet const *sheet, int row)
3828 ColRowSegment *segment;
3830 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3831 g_return_val_if_fail (row < gnm_sheet_get_max_rows (sheet), NULL);
3832 g_return_val_if_fail (row >= 0, NULL);
3834 segment = COLROW_GET_SEGMENT (&(sheet->rows), row);
3835 if (segment != NULL)
3836 return segment->info[COLROW_SUB_INDEX (row)];
3837 return NULL;
3840 ColRowInfo *
3841 sheet_colrow_get (Sheet const *sheet, int colrow, gboolean is_cols)
3843 if (is_cols)
3844 return sheet_col_get (sheet, colrow);
3845 return sheet_row_get (sheet, colrow);
3849 * sheet_col_fetch:
3850 * @col: column number
3852 * Returns: (transfer none): The #ColRowInfo for column @col. This result
3853 * will not be the default #ColRowInfo and may be changed.
3855 ColRowInfo *
3856 sheet_col_fetch (Sheet *sheet, int pos)
3858 ColRowInfo *cri = sheet_col_get (sheet, pos);
3859 if (NULL == cri && NULL != (cri = sheet_col_new (sheet)))
3860 sheet_colrow_add (sheet, cri, TRUE, pos);
3861 return cri;
3865 * sheet_row_fetch:
3866 * @row: row number
3868 * Returns: (transfer none): The #ColRowInfo for row @row. This result
3869 * will not be the default #ColRowInfo and may be changed.
3871 ColRowInfo *
3872 sheet_row_fetch (Sheet *sheet, int pos)
3874 ColRowInfo *cri = sheet_row_get (sheet, pos);
3875 if (NULL == cri && NULL != (cri = sheet_row_new (sheet)))
3876 sheet_colrow_add (sheet, cri, FALSE, pos);
3877 return cri;
3880 ColRowInfo *
3881 sheet_colrow_fetch (Sheet *sheet, int colrow, gboolean is_cols)
3883 if (is_cols)
3884 return sheet_col_fetch (sheet, colrow);
3885 return sheet_row_fetch (sheet, colrow);
3889 * sheet_col_get_info:
3890 * @col: column number
3892 * Returns: (transfer none): The #ColRowInfo for column @col. The may be
3893 * the default #ColRowInfo for columns and should not be changed.
3895 ColRowInfo const *
3896 sheet_col_get_info (Sheet const *sheet, int col)
3898 ColRowInfo *ci = sheet_col_get (sheet, col);
3900 if (ci != NULL)
3901 return ci;
3902 return &sheet->cols.default_style;
3906 * sheet_row_get_info:
3907 * @row: column number
3909 * Returns: (transfer none): The #ColRowInfo for row @row. The may be
3910 * the default #ColRowInfo for rows and should not be changed.
3912 ColRowInfo const *
3913 sheet_row_get_info (Sheet const *sheet, int row)
3915 ColRowInfo *ri = sheet_row_get (sheet, row);
3917 if (ri != NULL)
3918 return ri;
3919 return &sheet->rows.default_style;
3922 ColRowInfo const *
3923 sheet_colrow_get_info (Sheet const *sheet, int colrow, gboolean is_cols)
3925 return is_cols
3926 ? sheet_col_get_info (sheet, colrow)
3927 : sheet_row_get_info (sheet, colrow);
3931 * sheet_colrow_foreach:
3932 * @sheet: #Sheet
3933 * @is_cols: %TRUE for columns, %FALSE for rows.
3934 * @first: start position (inclusive)
3935 * @last: stop position (inclusive), -1 meaning end-of-sheet
3936 * @callback: (scope call): A callback function which should return %TRUE
3937 * to stop the iteration.
3938 * @user_data: A baggage pointer.
3940 * Iterates through the existing rows or columns within the range supplied.
3941 * If a callback returns %TRUE, iteration stops.
3943 gboolean
3944 sheet_colrow_foreach (Sheet const *sheet,
3945 gboolean is_cols,
3946 int first, int last,
3947 ColRowHandler callback,
3948 gpointer user_data)
3950 ColRowCollection const *infos;
3951 GnmColRowIter iter;
3952 ColRowSegment const *segment;
3953 int sub, inner_last, i;
3955 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
3957 if (last == -1)
3958 last = colrow_max (is_cols, sheet) - 1;
3959 infos = is_cols ? &sheet->cols : &sheet->rows;
3961 /* clip */
3962 if (last > infos->max_used)
3963 last = infos->max_used;
3965 for (i = first; i <= last ; ) {
3966 segment = COLROW_GET_SEGMENT (infos, i);
3967 sub = COLROW_SUB_INDEX(i);
3968 inner_last = (COLROW_SEGMENT_INDEX (last) == COLROW_SEGMENT_INDEX (i))
3969 ? COLROW_SUB_INDEX (last)+1 : COLROW_SEGMENT_SIZE;
3970 iter.pos = i;
3971 i += COLROW_SEGMENT_SIZE - sub;
3972 if (segment == NULL)
3973 continue;
3975 for (; sub < inner_last; sub++, iter.pos++) {
3976 iter.cri = segment->info[sub];
3977 if (iter.cri != NULL && (*callback)(&iter, user_data))
3978 return TRUE;
3982 return FALSE;
3986 /*****************************************************************************/
3988 static gint
3989 cell_ordering (gconstpointer a_, gconstpointer b_)
3991 GnmCell const *a = *(GnmCell **)a_;
3992 GnmCell const *b = *(GnmCell **)b_;
3994 if (a->pos.row != b->pos.row)
3995 return a->pos.row - b->pos.row;
3997 return a->pos.col - b->pos.col;
4001 * sheet_cells:
4002 * @sheet: a #Sheet
4003 * @r: (nullable): a #GnmRange
4005 * Retrieves an array of all cells inside @r.
4006 * Returns: (element-type GnmCell) (transfer container): the cells array.
4008 GPtrArray *
4009 sheet_cells (Sheet *sheet, const GnmRange *r)
4011 GPtrArray *res = g_ptr_array_new ();
4012 GHashTableIter hiter;
4013 gpointer value;
4015 g_hash_table_iter_init (&hiter, sheet->cell_hash);
4016 while (g_hash_table_iter_next (&hiter, NULL, &value)) {
4017 GnmCell *cell = value;
4018 if (!r || range_contains (r, cell->pos.col, cell->pos.row))
4019 g_ptr_array_add (res, cell);
4021 g_ptr_array_sort (res, cell_ordering);
4023 return res;
4028 #define SWAP_INT(a,b) do { int t; t = a; a = b; b = t; } while (0)
4031 * sheet_foreach_cell_in_range:
4032 * @sheet: #Sheet
4033 * @flags:
4034 * @r: #GnmRange
4035 * @callback: (scope call): #CellFilterFunc
4036 * @closure: user data.
4038 * For each existing cell in the range specified, invoke the
4039 * callback routine. If the only_existing flag is passed, then
4040 * callbacks are only invoked for existing cells.
4042 * Note: this function does not honour the CELL_ITER_IGNORE_SUBTOTAL flag.
4044 * Returns: (transfer none): the value returned by the callback, which can be:
4045 * non-NULL on error, or VALUE_TERMINATE if some invoked routine requested
4046 * to stop (by returning non-NULL).
4048 * NOTE: between 0.56 and 0.57, the traversal order changed. The order is now
4050 * 1 2 3
4051 * 4 5 6
4052 * 7 8 9
4054 * (This appears to be the order in which XL looks at the values of ranges.)
4055 * If your code depends on any particular ordering, please add a very visible
4056 * comment near the call.
4058 GnmValue *
4059 sheet_foreach_cell_in_range (Sheet *sheet, CellIterFlags flags,
4060 GnmRange const *r,
4061 CellIterFunc callback,
4062 gpointer closure)
4064 return sheet_foreach_cell_in_region (sheet, flags,
4065 r->start.col, r->start.row,
4066 r->end.col, r->end.row,
4067 callback, closure);
4072 * sheet_foreach_cell_in_region:
4073 * @sheet: #Sheet
4074 * @flags:
4075 * @start_col: Starting column
4076 * @start_row: Starting row
4077 * @end_col: Ending column, -1 meaning last
4078 * @end_row: Ending row, -1 meaning last
4079 * @callback: (scope call): #CellFilterFunc
4080 * @closure: user data.
4082 * For each existing cell in the range specified, invoke the
4083 * callback routine. If the only_existing flag is passed, then
4084 * callbacks are only invoked for existing cells.
4086 * Note: this function does not honour the CELL_ITER_IGNORE_SUBTOTAL flag.
4088 * Returns: (transfer none): the value returned by the callback, which can be:
4089 * non-NULL on error, or VALUE_TERMINATE if some invoked routine requested
4090 * to stop (by returning non-NULL).
4092 * NOTE: between 0.56 and 0.57, the traversal order changed. The order is now
4094 * 1 2 3
4095 * 4 5 6
4096 * 7 8 9
4098 * (This appears to be the order in which XL looks at the values of ranges.)
4099 * If your code depends on any particular ordering, please add a very visible
4100 * comment near the call.
4102 GnmValue *
4103 sheet_foreach_cell_in_region (Sheet *sheet, CellIterFlags flags,
4104 int start_col, int start_row,
4105 int end_col, int end_row,
4106 CellIterFunc callback, void *closure)
4108 GnmValue *cont;
4109 GnmCellIter iter;
4110 gboolean const visibility_matters = (flags & CELL_ITER_IGNORE_HIDDEN) != 0;
4111 gboolean const ignore_filtered = (flags & CELL_ITER_IGNORE_FILTERED) != 0;
4112 gboolean const only_existing = (flags & CELL_ITER_IGNORE_NONEXISTENT) != 0;
4113 gboolean const ignore_empty = (flags & CELL_ITER_IGNORE_EMPTY) != 0;
4114 gboolean ignore;
4115 gboolean use_celllist;
4116 guint64 range_size;
4118 g_return_val_if_fail (IS_SHEET (sheet), NULL);
4119 g_return_val_if_fail (callback != NULL, NULL);
4121 // For convenience
4122 if (end_col == -1) end_col = gnm_sheet_get_last_col (sheet);
4123 if (end_row == -1) end_row = gnm_sheet_get_last_row (sheet);
4125 iter.pp.sheet = sheet;
4126 iter.pp.wb = sheet->workbook;
4128 if (start_col > end_col)
4129 SWAP_INT (start_col, end_col);
4130 if (end_col < 0 || start_col > gnm_sheet_get_last_col (sheet))
4131 return NULL;
4132 start_col = MAX (0, start_col);
4133 end_col = MIN (end_col, gnm_sheet_get_last_col (sheet));
4135 if (start_row > end_row)
4136 SWAP_INT (start_row, end_row);
4137 if (end_row < 0 || start_row > gnm_sheet_get_last_row (sheet))
4138 return NULL;
4139 start_row = MAX (0, start_row);
4140 end_row = MIN (end_row, gnm_sheet_get_last_row (sheet));
4142 range_size = (guint64)(end_row - start_row + 1) * (end_col - start_col + 1);
4143 use_celllist =
4144 only_existing &&
4145 range_size > g_hash_table_size (sheet->cell_hash) + 1000;
4146 if (use_celllist) {
4147 GPtrArray *all_cells;
4148 int last_row = -1, last_col = -1;
4149 GnmValue *res = NULL;
4150 unsigned ui;
4151 GnmRange r;
4153 if (gnm_debug_flag ("sheet-foreach"))
4154 g_printerr ("Using celllist for area of size %d\n",
4155 (int)range_size);
4157 range_init (&r, start_col, start_row, end_col, end_row);
4158 all_cells = sheet_cells (sheet, &r);
4160 for (ui = 0; ui < all_cells->len; ui++) {
4161 GnmCell *cell = g_ptr_array_index (all_cells, ui);
4163 iter.cell = cell;
4164 iter.pp.eval.row = cell->pos.row;
4165 iter.pp.eval.col = cell->pos.col;
4167 if (iter.pp.eval.row != last_row) {
4168 last_row = iter.pp.eval.row;
4169 iter.ri = sheet_row_get (iter.pp.sheet, last_row);
4171 if (iter.ri == NULL) {
4172 g_critical ("Cell without row data -- please report");
4173 continue;
4175 if (visibility_matters && !iter.ri->visible)
4176 continue;
4177 if (ignore_filtered && iter.ri->in_filter && !iter.ri->visible)
4178 continue;
4180 if (iter.pp.eval.col != last_col) {
4181 last_col = iter.pp.eval.col;
4182 iter.ci = sheet_col_get (iter.pp.sheet, last_col);
4184 if (iter.ci == NULL) {
4185 g_critical ("Cell without column data -- please report");
4186 continue;
4188 if (visibility_matters && !iter.ci->visible)
4189 continue;
4191 ignore = (ignore_empty &&
4192 VALUE_IS_EMPTY (cell->value) &&
4193 !gnm_cell_needs_recalc (cell));
4194 if (ignore)
4195 continue;
4197 res = (*callback) (&iter, closure);
4198 if (res != NULL)
4199 break;
4202 g_ptr_array_free (all_cells, TRUE);
4203 return res;
4206 for (iter.pp.eval.row = start_row;
4207 iter.pp.eval.row <= end_row;
4208 ++iter.pp.eval.row) {
4209 iter.ri = sheet_row_get (iter.pp.sheet, iter.pp.eval.row);
4211 /* no need to check visibility, that would require a colinfo to exist */
4212 if (iter.ri == NULL) {
4213 if (only_existing) {
4214 /* skip segments with no cells */
4215 if (iter.pp.eval.row == COLROW_SEGMENT_START (iter.pp.eval.row)) {
4216 ColRowSegment const *segment =
4217 COLROW_GET_SEGMENT (&(sheet->rows), iter.pp.eval.row);
4218 if (segment == NULL)
4219 iter.pp.eval.row = COLROW_SEGMENT_END (iter.pp.eval.row);
4221 } else {
4222 iter.cell = NULL;
4223 for (iter.pp.eval.col = start_col; iter.pp.eval.col <= end_col; ++iter.pp.eval.col) {
4224 cont = (*callback) (&iter, closure);
4225 if (cont != NULL)
4226 return cont;
4230 continue;
4233 if (visibility_matters && !iter.ri->visible)
4234 continue;
4235 if (ignore_filtered && iter.ri->in_filter && !iter.ri->visible)
4236 continue;
4238 for (iter.pp.eval.col = start_col; iter.pp.eval.col <= end_col; ++iter.pp.eval.col) {
4239 iter.ci = sheet_col_get (sheet, iter.pp.eval.col);
4240 if (iter.ci != NULL) {
4241 if (visibility_matters && !iter.ci->visible)
4242 continue;
4243 iter.cell = sheet_cell_get (sheet,
4244 iter.pp.eval.col, iter.pp.eval.row);
4245 } else
4246 iter.cell = NULL;
4248 ignore = (iter.cell == NULL)
4249 ? (only_existing || ignore_empty)
4250 : (ignore_empty && VALUE_IS_EMPTY (iter.cell->value) &&
4251 !gnm_cell_needs_recalc (iter.cell));
4253 if (ignore) {
4254 if (iter.pp.eval.col == COLROW_SEGMENT_START (iter.pp.eval.col)) {
4255 ColRowSegment const *segment =
4256 COLROW_GET_SEGMENT (&(sheet->cols), iter.pp.eval.col);
4257 if (segment == NULL)
4258 iter.pp.eval.col = COLROW_SEGMENT_END (iter.pp.eval.col);
4260 continue;
4263 cont = (*callback) (&iter, closure);
4264 if (cont != NULL)
4265 return cont;
4268 return NULL;
4272 * sheet_cell_foreach:
4273 * @sheet: #Sheet
4274 * @callback: (scope call):
4275 * @data:
4277 * Call @callback with an argument of @data for each cell in the sheet
4279 void
4280 sheet_cell_foreach (Sheet const *sheet, GHFunc callback, gpointer data)
4282 g_return_if_fail (IS_SHEET (sheet));
4284 g_hash_table_foreach (sheet->cell_hash, callback, data);
4288 * sheet_cells_count:
4289 * @sheet: #Sheet
4291 * Returns the number of cells with content in the current workbook.
4293 unsigned
4294 sheet_cells_count (Sheet const *sheet)
4296 return g_hash_table_size (sheet->cell_hash);
4299 static void
4300 cb_sheet_cells_collect (G_GNUC_UNUSED gpointer unused,
4301 GnmCell const *cell,
4302 GPtrArray *cells)
4304 GnmEvalPos *ep = eval_pos_init_cell (g_new (GnmEvalPos, 1), cell);
4305 g_ptr_array_add (cells, ep);
4309 * sheet_cell_positions:
4310 * @sheet: The sheet to find cells in.
4311 * @comments: If true, include cells with only comments also.
4313 * Collects a GPtrArray of GnmEvalPos pointers for all cells in a sheet.
4314 * No particular order should be assumed.
4315 * Returns: (element-type GnmEvalPos) (transfer full): the newly created array
4317 GPtrArray *
4318 sheet_cell_positions (Sheet *sheet, gboolean comments)
4320 GPtrArray *cells = g_ptr_array_new ();
4322 g_return_val_if_fail (IS_SHEET (sheet), cells);
4324 sheet_cell_foreach (sheet, (GHFunc)cb_sheet_cells_collect, cells);
4326 if (comments) {
4327 GnmRange r;
4328 GSList *scomments, *ptr;
4330 range_init_full_sheet (&r, sheet);
4331 scomments = sheet_objects_get (sheet, &r, GNM_CELL_COMMENT_TYPE);
4332 for (ptr = scomments; ptr; ptr = ptr->next) {
4333 GnmComment *c = ptr->data;
4334 GnmRange const *loc = sheet_object_get_range (GNM_SO (c));
4335 GnmCell *cell = sheet_cell_get (sheet, loc->start.col, loc->start.row);
4336 if (!cell) {
4337 /* If cell does not exist, we haven't seen it... */
4338 GnmEvalPos *ep = g_new (GnmEvalPos, 1);
4339 ep->sheet = sheet;
4340 ep->eval.col = loc->start.col;
4341 ep->eval.row = loc->start.row;
4342 g_ptr_array_add (cells, ep);
4345 g_slist_free (scomments);
4348 return cells;
4352 static GnmValue *
4353 cb_fail_if_exist (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
4355 return gnm_cell_is_empty (iter->cell) ? NULL : VALUE_TERMINATE;
4359 * sheet_is_region_empty:
4360 * @sheet: sheet to check
4361 * @r: region to check
4363 * Returns TRUE if the specified region of the @sheet does not
4364 * contain any cells
4366 gboolean
4367 sheet_is_region_empty (Sheet *sheet, GnmRange const *r)
4369 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
4371 return sheet_foreach_cell_in_range (
4372 sheet, CELL_ITER_IGNORE_BLANK, r,
4373 cb_fail_if_exist, NULL) == NULL;
4376 gboolean
4377 sheet_is_cell_empty (Sheet *sheet, int col, int row)
4379 GnmCell const *cell = sheet_cell_get (sheet, col, row);
4380 return gnm_cell_is_empty (cell);
4384 * sheet_cell_add_to_hash:
4385 * @sheet The sheet where the cell is inserted
4386 * @cell The cell, it should already have col/pos pointers
4387 * initialized pointing to the correct ColRowInfo
4389 * GnmCell::pos must be valid before this is called. The position is used as the
4390 * hash key.
4392 static void
4393 sheet_cell_add_to_hash (Sheet *sheet, GnmCell *cell)
4395 g_return_if_fail (cell->pos.col < gnm_sheet_get_max_cols (sheet));
4396 g_return_if_fail (cell->pos.row < gnm_sheet_get_max_rows (sheet));
4398 cell->base.flags |= GNM_CELL_IN_SHEET_LIST;
4399 /* NOTE:
4400 * fetching the col/row here serve 3 functions
4401 * 1) obsolete: we used to store the pointer in the cell
4402 * 2) Expanding col/row.max_used
4403 * 3) Creating an entry in the COLROW_SEGMENT. Lots and lots of
4404 * things use those to help limit iteration
4406 * For now just call col_fetch even though it is not necessary to
4407 * ensure that 2,3 still happen. Alot will need rewriting to avoid
4408 * these requirements.
4410 (void)sheet_col_fetch (sheet, cell->pos.col);
4411 (void)sheet_row_fetch (sheet, cell->pos.row);
4413 gnm_cell_unrender (cell);
4415 g_hash_table_insert (sheet->cell_hash, cell, cell);
4417 if (gnm_sheet_merge_is_corner (sheet, &cell->pos))
4418 cell->base.flags |= GNM_CELL_IS_MERGED;
4421 #undef USE_CELL_POOL
4423 #ifdef USE_CELL_POOL
4424 /* The pool from which all cells are allocated. */
4425 static GOMemChunk *cell_pool;
4426 #else
4427 static int cell_allocations = 0;
4428 #endif
4430 static GnmCell *
4431 cell_new (void)
4433 GnmCell *cell =
4434 #ifdef USE_CELL_POOL
4435 go_mem_chunk_alloc0 (cell_pool)
4436 #else
4437 (cell_allocations++, g_slice_new0 (GnmCell))
4438 #endif
4441 cell->base.flags = DEPENDENT_CELL;
4442 return cell;
4446 static void
4447 cell_free (GnmCell *cell)
4449 g_return_if_fail (cell != NULL);
4451 gnm_cell_cleanout (cell);
4452 #ifdef USE_CELL_POOL
4453 go_mem_chunk_free (cell_pool, cell);
4454 #else
4455 cell_allocations--, g_slice_free1 (sizeof (*cell), cell);
4456 #endif
4460 * gnm_sheet_cell_init: (skip)
4462 void
4463 gnm_sheet_cell_init (void)
4465 #ifdef USE_CELL_POOL
4466 cell_pool = go_mem_chunk_new ("cell pool",
4467 sizeof (GnmCell),
4468 128 * 1024 - 128);
4469 #endif
4472 #ifdef USE_CELL_POOL
4473 static void
4474 cb_cell_pool_leak (gpointer data, G_GNUC_UNUSED gpointer user)
4476 GnmCell const *cell = data;
4477 g_printerr ("Leaking cell %p at %s\n", (void *)cell, cell_name (cell));
4479 #endif
4482 * gnm_sheet_cell_shutdown: (skip)
4484 void
4485 gnm_sheet_cell_shutdown (void)
4487 #ifdef USE_CELL_POOL
4488 go_mem_chunk_foreach_leak (cell_pool, cb_cell_pool_leak, NULL);
4489 go_mem_chunk_destroy (cell_pool, FALSE);
4490 cell_pool = NULL;
4491 #else
4492 if (cell_allocations)
4493 g_printerr ("Leaking %d cells.\n", cell_allocations);
4494 #endif
4497 /****************************************************************************/
4500 * sheet_cell_create:
4501 * @sheet: #Sheet
4502 * @col:
4503 * @row:
4505 * Creates a new cell and adds it to the sheet hash.
4507 GnmCell *
4508 sheet_cell_create (Sheet *sheet, int col, int row)
4510 GnmCell *cell;
4512 g_return_val_if_fail (IS_SHEET (sheet), NULL);
4513 g_return_val_if_fail (col >= 0, NULL);
4514 g_return_val_if_fail (col < gnm_sheet_get_max_cols (sheet), NULL);
4515 g_return_val_if_fail (row >= 0, NULL);
4516 g_return_val_if_fail (row < gnm_sheet_get_max_rows (sheet), NULL);
4518 cell = cell_new ();
4519 cell->base.sheet = sheet;
4520 cell->pos.col = col;
4521 cell->pos.row = row;
4522 cell->value = value_new_empty ();
4524 sheet_cell_add_to_hash (sheet, cell);
4525 return cell;
4529 * sheet_cell_remove_from_hash:
4530 * @sheet:
4531 * @cell:
4533 * Removes a cell from the sheet hash, clears any spans, and unlinks it from
4534 * the dependent collection.
4536 static void
4537 sheet_cell_remove_from_hash (Sheet *sheet, GnmCell *cell)
4539 cell_unregister_span (cell);
4540 if (gnm_cell_expr_is_linked (cell))
4541 dependent_unlink (GNM_CELL_TO_DEP (cell));
4542 g_hash_table_remove (sheet->cell_hash, cell);
4543 cell->base.flags &= ~(GNM_CELL_IN_SHEET_LIST|GNM_CELL_IS_MERGED);
4547 * sheet_cell_destroy:
4548 * @sheet:
4549 * @cell:
4550 * @queue_recalc:
4552 * Remove the cell from the web of dependencies of a
4553 * sheet. Do NOT redraw.
4555 static void
4556 sheet_cell_destroy (Sheet *sheet, GnmCell *cell, gboolean queue_recalc)
4558 if (gnm_cell_expr_is_linked (cell)) {
4559 /* if it needs recalc then its depends are already queued
4560 * check recalc status before we unlink
4562 queue_recalc &= !gnm_cell_needs_recalc (cell);
4563 dependent_unlink (GNM_CELL_TO_DEP (cell));
4566 if (queue_recalc)
4567 cell_foreach_dep (cell, (GnmDepFunc)dependent_queue_recalc, NULL);
4569 sheet_cell_remove_from_hash (sheet, cell);
4570 cell_free (cell);
4574 * sheet_cell_remove:
4575 * @sheet:
4576 * @cell:
4577 * @redraw:
4578 * @queue_recalc:
4580 * Remove the cell from the web of dependencies of a
4581 * sheet. Do NOT free the cell, optionally redraw it, optionally
4582 * queue it for recalc.
4584 void
4585 sheet_cell_remove (Sheet *sheet, GnmCell *cell,
4586 gboolean redraw, gboolean queue_recalc)
4588 g_return_if_fail (cell != NULL);
4589 g_return_if_fail (IS_SHEET (sheet));
4591 /* Queue a redraw on the region used by the cell being removed */
4592 if (redraw) {
4593 sheet_redraw_region (sheet,
4594 cell->pos.col, cell->pos.row,
4595 cell->pos.col, cell->pos.row);
4596 sheet_flag_status_update_cell (cell);
4599 sheet_cell_destroy (sheet, cell, queue_recalc);
4602 static GnmValue *
4603 cb_free_cell (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
4605 sheet_cell_destroy (iter->pp.sheet, iter->cell, FALSE);
4606 return NULL;
4610 * sheet_col_destroy:
4611 * @sheet:
4612 * @col:
4613 * @free_cells:
4615 * Destroys a ColRowInfo from the Sheet with all of its cells
4617 static void
4618 sheet_col_destroy (Sheet *sheet, int const col, gboolean free_cells)
4620 ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->cols), col);
4621 int const sub = COLROW_SUB_INDEX (col);
4622 ColRowInfo *ci = NULL;
4624 if (*segment == NULL)
4625 return;
4626 ci = (*segment)->info[sub];
4627 if (ci == NULL)
4628 return;
4630 if (sheet->cols.max_outline_level > 0 &&
4631 sheet->cols.max_outline_level == ci->outline_level)
4632 sheet->priv->recompute_max_col_group = TRUE;
4634 if (free_cells)
4635 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4636 col, 0, col, -1,
4637 &cb_free_cell, NULL);
4639 (*segment)->info[sub] = NULL;
4640 colrow_free (ci);
4642 /* Use >= just in case things are screwed up */
4643 if (col >= sheet->cols.max_used) {
4644 int i = col;
4645 while (--i >= 0 && sheet_col_get (sheet, i) == NULL)
4647 sheet->cols.max_used = i;
4652 * Destroys a row ColRowInfo
4654 static void
4655 sheet_row_destroy (Sheet *sheet, int const row, gboolean free_cells)
4657 ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->rows), row);
4658 int const sub = COLROW_SUB_INDEX (row);
4659 ColRowInfo *ri = NULL;
4661 if (*segment == NULL)
4662 return;
4663 ri = (*segment)->info[sub];
4664 if (ri == NULL)
4665 return;
4667 if (sheet->rows.max_outline_level > 0 &&
4668 sheet->rows.max_outline_level == ri->outline_level)
4669 sheet->priv->recompute_max_row_group = TRUE;
4671 if (free_cells)
4672 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4673 0, row, -1, row,
4674 &cb_free_cell, NULL);
4676 /* Rows have span lists, destroy them too */
4677 row_destroy_span (ri);
4679 (*segment)->info[sub] = NULL;
4680 colrow_free (ri);
4682 /* Use >= just in case things are screwed up */
4683 if (row >= sheet->rows.max_used) {
4684 int i = row;
4685 while (--i >= 0 && sheet_row_get (sheet, i) == NULL)
4687 sheet->rows.max_used = i;
4691 static void
4692 cb_remove_allcells (G_GNUC_UNUSED gpointer ignore0, GnmCell *cell, G_GNUC_UNUSED gpointer ignore1)
4694 cell->base.flags &= ~GNM_CELL_IN_SHEET_LIST;
4695 cell_free (cell);
4698 void
4699 sheet_destroy_contents (Sheet *sheet)
4701 GSList *filters;
4702 int i;
4704 /* By the time we reach here dependencies should have been shut down */
4705 g_return_if_fail (sheet->deps == NULL);
4707 /* A simple test to see if this has already been run. */
4708 if (sheet->hash_merged == NULL)
4709 return;
4712 GSList *tmp = sheet->slicers;
4713 sheet->slicers = NULL;
4714 g_slist_free_full (tmp, (GDestroyNotify)gnm_sheet_slicer_clear_sheet);
4717 /* These contain SheetObjects, remove them first */
4718 filters = g_slist_copy (sheet->filters);
4719 g_slist_foreach (filters, (GFunc)gnm_filter_remove, NULL);
4720 g_slist_foreach (filters, (GFunc)gnm_filter_unref, NULL);
4721 g_slist_free (filters);
4723 if (sheet->sheet_objects) {
4724 /* The list is changed as we remove */
4725 GSList *objs = g_slist_copy (sheet->sheet_objects);
4726 GSList *ptr;
4727 for (ptr = objs; ptr != NULL ; ptr = ptr->next) {
4728 SheetObject *so = GNM_SO (ptr->data);
4729 if (so != NULL)
4730 sheet_object_clear_sheet (so);
4732 g_slist_free (objs);
4733 if (sheet->sheet_objects != NULL)
4734 g_warning ("There is a problem with sheet objects");
4737 /* The memory is managed by Sheet::list_merged */
4738 g_hash_table_destroy (sheet->hash_merged);
4739 sheet->hash_merged = NULL;
4741 g_slist_free_full (sheet->list_merged, g_free);
4742 sheet->list_merged = NULL;
4744 /* Clear the row spans 1st */
4745 for (i = sheet->rows.max_used; i >= 0 ; --i)
4746 row_destroy_span (sheet_row_get (sheet, i));
4748 /* Remove all the cells */
4749 sheet_cell_foreach (sheet, (GHFunc) &cb_remove_allcells, NULL);
4750 g_hash_table_destroy (sheet->cell_hash);
4752 /* Delete in ascending order to avoid decrementing max_used each time */
4753 for (i = 0; i <= sheet->cols.max_used; ++i)
4754 sheet_col_destroy (sheet, i, FALSE);
4756 for (i = 0; i <= sheet->rows.max_used; ++i)
4757 sheet_row_destroy (sheet, i, FALSE);
4759 /* Free segments too */
4760 col_row_collection_resize (&sheet->cols, 0);
4761 g_ptr_array_free (sheet->cols.info, TRUE);
4762 sheet->cols.info = NULL;
4764 col_row_collection_resize (&sheet->rows, 0);
4765 g_ptr_array_free (sheet->rows.info, TRUE);
4766 sheet->rows.info = NULL;
4768 g_clear_object (&sheet->solver_parameters);
4772 * sheet_destroy:
4773 * @sheet: the sheet to destroy
4775 * Please note that you need to detach this sheet before
4776 * calling this routine or you will get a warning.
4778 static void
4779 sheet_destroy (Sheet *sheet)
4781 g_return_if_fail (IS_SHEET (sheet));
4783 if (sheet->sheet_views->len > 0)
4784 g_warning ("Unexpected left over views");
4786 if (sheet->print_info) {
4787 gnm_print_info_free (sheet->print_info);
4788 sheet->print_info = NULL;
4791 style_color_unref (sheet->tab_color);
4792 sheet->tab_color = NULL;
4793 style_color_unref (sheet->tab_text_color);
4794 sheet->tab_text_color = NULL;
4796 gnm_app_clipboard_invalidate_sheet (sheet);
4799 static void
4800 gnm_sheet_finalize (GObject *obj)
4802 Sheet *sheet = SHEET (obj);
4803 gboolean debug_FMR = gnm_debug_flag ("sheet-fmr");
4805 sheet_destroy (sheet);
4807 g_clear_object (&sheet->solver_parameters);
4809 gnm_conventions_unref (sheet->convs);
4810 sheet->convs = NULL;
4812 g_list_free_full (sheet->scenarios, g_object_unref);
4813 sheet->scenarios = NULL;
4815 if (sheet->sort_setups != NULL)
4816 g_hash_table_unref (sheet->sort_setups);
4818 dependents_invalidate_sheet (sheet, TRUE);
4820 sheet_destroy_contents (sheet);
4822 if (sheet->slicers != NULL) {
4823 g_warning ("DataSlicer list should be NULL");
4825 if (sheet->filters != NULL) {
4826 g_warning ("Filter list should be NULL");
4828 if (sheet->sheet_objects != NULL) {
4829 g_warning ("Sheet object list should be NULL");
4831 if (sheet->list_merged != NULL) {
4832 g_warning ("Merged list should be NULL");
4834 if (sheet->hash_merged != NULL) {
4835 g_warning ("Merged hash should be NULL");
4838 sheet_style_shutdown (sheet);
4840 (void) g_idle_remove_by_data (sheet);
4842 if (debug_FMR) {
4843 g_printerr ("Sheet %p is %s\n", sheet, sheet->name_quoted);
4845 g_free (sheet->name_quoted);
4846 g_free (sheet->name_unquoted);
4847 g_free (sheet->name_unquoted_collate_key);
4848 g_free (sheet->name_case_insensitive);
4849 /* Poison */
4850 sheet->name_quoted = (char *)0xdeadbeef;
4851 sheet->name_unquoted = (char *)0xdeadbeef;
4852 g_free (sheet->priv);
4853 g_ptr_array_free (sheet->sheet_views, TRUE);
4855 gnm_rvc_free (sheet->rendered_values);
4857 if (debug_FMR) {
4858 /* Keep object around. */
4859 return;
4862 G_OBJECT_CLASS (parent_class)->finalize (obj);
4865 /*****************************************************************************/
4868 * cb_empty_cell: A callback for sheet_foreach_cell_in_region
4869 * removes/clear all of the cells in the specified region.
4870 * Does NOT queue a redraw.
4872 * WARNING : This does NOT regenerate spans that were interrupted by
4873 * this cell and can now continue.
4875 static GnmValue *
4876 cb_empty_cell (GnmCellIter const *iter, gpointer user)
4878 int clear_flags = GPOINTER_TO_INT (user);
4879 #if 0
4880 /* TODO : here and other places flag a need to update the
4881 * row/col maxima.
4883 if (row >= sheet->rows.max_used || col >= sheet->cols.max_used) { }
4884 #endif
4886 sheet_cell_remove (iter->pp.sheet, iter->cell, FALSE,
4887 (clear_flags & CLEAR_RECALC_DEPS) &&
4888 iter->pp.wb->recursive_dirty_enabled);
4890 return NULL;
4894 * sheet_clear_region:
4895 * @sheet:
4896 * @start_col:
4897 * @start_row:
4898 * @end_col:
4899 * @end_row:
4900 * @clear_flags: If this is TRUE then styles are erased.
4901 * @cc: (nullable):
4903 * Clears are region of cells
4905 * We assemble a list of cells to destroy, since we will be making changes
4906 * to the structure being manipulated by the sheet_foreach_cell_in_region routine
4908 void
4909 sheet_clear_region (Sheet *sheet,
4910 int start_col, int start_row,
4911 int end_col, int end_row,
4912 int clear_flags,
4913 GOCmdContext *cc)
4915 GnmRange r;
4917 g_return_if_fail (IS_SHEET (sheet));
4918 g_return_if_fail (start_col <= end_col);
4919 g_return_if_fail (start_row <= end_row);
4921 r.start.col = start_col;
4922 r.start.row = start_row;
4923 r.end.col = end_col;
4924 r.end.row = end_row;
4926 if (clear_flags & CLEAR_VALUES && !(clear_flags & CLEAR_NOCHECKARRAY) &&
4927 sheet_range_splits_array (sheet, &r, NULL, cc, _("Clear")))
4928 return;
4930 /* Queue a redraw for cells being modified */
4931 if (clear_flags & (CLEAR_VALUES|CLEAR_FORMATS))
4932 sheet_redraw_region (sheet,
4933 start_col, start_row,
4934 end_col, end_row);
4936 /* Clear the style in the region (new_default will ref the style for us). */
4937 if (clear_flags & CLEAR_FORMATS) {
4938 sheet_style_set_range (sheet, &r, sheet_style_default (sheet));
4939 sheet_range_calc_spans (sheet, &r, GNM_SPANCALC_RE_RENDER|GNM_SPANCALC_RESIZE);
4940 rows_height_update (sheet, &r, TRUE);
4943 if (clear_flags & CLEAR_OBJECTS)
4944 sheet_objects_clear (sheet, &r, G_TYPE_NONE, NULL);
4945 else if (clear_flags & CLEAR_COMMENTS)
4946 sheet_objects_clear (sheet, &r, GNM_CELL_COMMENT_TYPE, NULL);
4948 /* TODO : how to handle objects ? */
4949 if (clear_flags & CLEAR_VALUES) {
4950 /* Remove or empty the cells depending on
4951 * whether or not there are comments
4953 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4954 start_col, start_row, end_col, end_row,
4955 &cb_empty_cell, GINT_TO_POINTER (clear_flags));
4957 if (!(clear_flags & CLEAR_NORESPAN)) {
4958 sheet_queue_respan (sheet, start_row, end_row);
4959 sheet_flag_status_update_range (sheet, &r);
4963 if (clear_flags & CLEAR_MERGES) {
4964 GSList *merged, *ptr;
4965 merged = gnm_sheet_merge_get_overlap (sheet, &r);
4966 for (ptr = merged ; ptr != NULL ; ptr = ptr->next)
4967 gnm_sheet_merge_remove (sheet, ptr->data);
4968 g_slist_free (merged);
4971 if (clear_flags & CLEAR_RECALC_DEPS)
4972 sheet_region_queue_recalc (sheet, &r);
4974 /* Always redraw */
4975 sheet_redraw_all (sheet, FALSE);
4978 static void
4979 sheet_clear_region_cb (GnmSheetRange *sr, int *flags)
4981 sheet_clear_region (sr->sheet,
4982 sr->range.start.col, sr->range.start.row,
4983 sr->range.end.col, sr->range.end.row,
4984 *flags | CLEAR_NOCHECKARRAY, NULL);
4989 * sheet_clear_region_undo:
4990 * @sr: #GnmSheetRange
4991 * @clear_flags: flags.
4993 * Returns: (transfer full): the new #GOUndo.
4995 GOUndo *sheet_clear_region_undo (GnmSheetRange *sr, int clear_flags)
4997 int *flags = g_new(int, 1);
4998 *flags = clear_flags;
4999 return go_undo_binary_new
5000 (sr, (gpointer)flags,
5001 (GOUndoBinaryFunc) sheet_clear_region_cb,
5002 (GFreeFunc) gnm_sheet_range_free,
5003 (GFreeFunc) g_free);
5007 /*****************************************************************************/
5009 void
5010 sheet_mark_dirty (Sheet *sheet)
5012 g_return_if_fail (IS_SHEET (sheet));
5014 if (sheet->workbook)
5015 go_doc_set_dirty (GO_DOC (sheet->workbook), TRUE);
5018 /****************************************************************************/
5020 static void
5021 sheet_cells_deps_move (GnmExprRelocateInfo *rinfo)
5023 Sheet *sheet = rinfo->origin_sheet;
5024 GPtrArray *deps = sheet_cells (sheet, &rinfo->origin);
5025 unsigned ui;
5027 /* Phase 1: collect all cells and remove them from hash. */
5028 for (ui = 0; ui < deps->len; ui++) {
5029 GnmCell *cell = g_ptr_array_index (deps, ui);
5030 gboolean needs_recalc = gnm_cell_needs_recalc (cell);
5031 sheet_cell_remove_from_hash (sheet, cell);
5032 if (needs_recalc) /* Do we need this now? */
5033 cell->base.flags |= DEPENDENT_NEEDS_RECALC;
5036 /* Phase 2: add all non-cell deps with positions */
5037 SHEET_FOREACH_DEPENDENT
5038 (sheet, dep, {
5039 GnmCellPos const *pos;
5040 if (!dependent_is_cell (dep) &&
5041 dependent_has_pos (dep) &&
5042 (pos = dependent_pos (dep)) &&
5043 range_contains (&rinfo->origin, pos->col, pos->row)) {
5044 dependent_unlink (dep);
5045 g_ptr_array_add (deps, dep);
5049 /* Phase 3: move everything and add cells to hash. */
5050 for (ui = 0; ui < deps->len; ui++) {
5051 GnmDependent *dep = g_ptr_array_index (deps, ui);
5053 dependent_move (dep, rinfo->col_offset, rinfo->row_offset);
5055 if (dependent_is_cell (dep))
5056 sheet_cell_add_to_hash (sheet, GNM_DEP_TO_CELL (dep));
5058 if (dep->texpr)
5059 dependent_link (dep);
5062 g_ptr_array_free (deps, TRUE);
5065 /* Moves the headers to their new location */
5066 static void
5067 sheet_colrow_move (Sheet *sheet, gboolean is_cols,
5068 int const old_pos, int const new_pos)
5070 ColRowSegment *segment = COLROW_GET_SEGMENT (is_cols ? &sheet->cols : &sheet->rows, old_pos);
5071 ColRowInfo *info = segment
5072 ? segment->info[COLROW_SUB_INDEX (old_pos)]
5073 : NULL;
5075 g_return_if_fail (old_pos >= 0);
5076 g_return_if_fail (new_pos >= 0);
5078 if (info == NULL)
5079 return;
5081 /* Update the position */
5082 segment->info[COLROW_SUB_INDEX (old_pos)] = NULL;
5083 sheet_colrow_add (sheet, info, is_cols, new_pos);
5086 static void
5087 sheet_colrow_set_collapse (Sheet *sheet, gboolean is_cols, int pos)
5089 ColRowInfo *cri;
5090 ColRowInfo const *vs = NULL;
5092 if (pos < 0 || pos >= colrow_max (is_cols, sheet))
5093 return;
5095 /* grab the next or previous col/row */
5096 if ((is_cols ? sheet->outline_symbols_right : sheet->outline_symbols_below)) {
5097 if (pos > 0)
5098 vs = sheet_colrow_get (sheet, pos-1, is_cols);
5099 } else if ((pos+1) < colrow_max (is_cols, sheet))
5100 vs = sheet_colrow_get (sheet, pos+1, is_cols);
5102 /* handle the case where an empty col/row should be marked collapsed */
5103 cri = sheet_colrow_get (sheet, pos, is_cols);
5104 if (cri != NULL)
5105 cri->is_collapsed = (vs != NULL && !vs->visible &&
5106 vs->outline_level > cri->outline_level);
5107 else if (vs != NULL && !vs->visible && vs->outline_level > 0) {
5108 cri = sheet_colrow_fetch (sheet, pos, is_cols);
5109 cri->is_collapsed = TRUE;
5113 static void
5114 combine_undo (GOUndo **pundo, GOUndo *u)
5116 if (pundo)
5117 *pundo = go_undo_combine (*pundo, u);
5118 else
5119 g_object_unref (u);
5122 typedef gboolean (*ColRowInsDelFunc) (Sheet *sheet, int idx, int count,
5123 GOUndo **pundo, GOCmdContext *cc);
5125 typedef struct {
5126 ColRowInsDelFunc func;
5127 Sheet *sheet;
5128 gboolean is_cols;
5129 int pos;
5130 int count;
5131 ColRowStateList *states;
5132 int state_start;
5133 } ColRowInsDelData;
5135 static void
5136 cb_undo_insdel (ColRowInsDelData *data)
5138 data->func (data->sheet, data->pos, data->count, NULL, NULL);
5139 colrow_set_states (data->sheet, data->is_cols,
5140 data->state_start, data->states);
5143 static void
5144 cb_undo_insdel_free (ColRowInsDelData *data)
5146 colrow_state_list_destroy (data->states);
5147 g_free (data);
5150 static gboolean
5151 sheet_insdel_colrow (Sheet *sheet, int pos, int count,
5152 GOUndo **pundo, GOCmdContext *cc,
5153 gboolean is_cols, gboolean is_insert,
5154 const char *description,
5155 ColRowInsDelFunc opposite)
5158 GnmRange kill_zone; /* The range whose contents will be lost. */
5159 GnmRange move_zone; /* The range whose contents will be moved. */
5160 GnmRange change_zone; /* The union of kill_zone and move_zone. */
5161 int i, last_pos, max_used_pos;
5162 int kill_start, kill_end, move_start, move_end;
5163 int scount = is_insert ? count : -count;
5164 ColRowStateList *states = NULL;
5165 GnmExprRelocateInfo reloc_info;
5166 GSList *l;
5167 gboolean sticky_end = TRUE;
5169 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
5170 g_return_val_if_fail (count > 0, TRUE);
5173 * The main undo for an insert col/row is delete col/row and vice versa.
5174 * In addition to that, we collect undo information that the main undo
5175 * operation will not restore -- for example the contents of the kill
5176 * zone.
5178 if (pundo) *pundo = NULL;
5180 last_pos = colrow_max (is_cols, sheet) - 1;
5181 max_used_pos = is_cols ? sheet->cols.max_used : sheet->rows.max_used;
5182 if (is_insert) {
5183 kill_start = last_pos - (count - 1);
5184 kill_end = last_pos;
5185 move_start = pos;
5186 move_end = kill_start - 1;
5187 } else {
5188 int max_count = last_pos + 1 - pos;
5189 if (count > max_count) {
5190 sticky_end = FALSE;
5191 count = max_count;
5193 kill_start = pos;
5194 kill_end = pos + (count - 1);
5195 move_start = kill_end + 1;
5196 move_end = last_pos;
5198 (is_cols ? range_init_cols : range_init_rows)
5199 (&kill_zone, sheet, kill_start, kill_end);
5200 (is_cols ? range_init_cols : range_init_rows)
5201 (&move_zone, sheet, move_start, move_end);
5202 change_zone = range_union (&kill_zone, &move_zone);
5204 /* 0. Check displaced/deleted region and ensure arrays aren't divided. */
5205 if (sheet_range_splits_array (sheet, &kill_zone, NULL, cc, description))
5206 return TRUE;
5207 if (move_start <= move_end &&
5208 sheet_range_splits_array (sheet, &move_zone, NULL, cc, description))
5209 return TRUE;
5212 * At this point we're committed. Anything that can go wrong should
5213 * have been ruled out already.
5216 if (0) {
5217 g_printerr ("Action = %s at %d count %d\n", description, pos, count);
5218 g_printerr ("Kill zone: %s\n", range_as_string (&kill_zone));
5221 /* 1. Delete all columns/rows in the kill zone */
5222 if (pundo) {
5223 combine_undo (pundo, clipboard_copy_range_undo (sheet, &kill_zone));
5224 states = colrow_get_states (sheet, is_cols, kill_start, kill_end);
5226 for (i = MIN (max_used_pos, kill_end); i >= kill_start; --i)
5227 (is_cols ? sheet_col_destroy : sheet_row_destroy)
5228 (sheet, i, TRUE);
5229 /* Brutally discard auto filter objects. Collect the rest for undo. */
5230 sheet_objects_clear (sheet, &kill_zone, GNM_FILTER_COMBO_TYPE, NULL);
5231 sheet_objects_clear (sheet, &kill_zone, G_TYPE_NONE, pundo);
5233 reloc_info.reloc_type = is_cols ? GNM_EXPR_RELOCATE_COLS : GNM_EXPR_RELOCATE_ROWS;
5234 reloc_info.sticky_end = sticky_end;
5235 reloc_info.origin_sheet = reloc_info.target_sheet = sheet;
5236 parse_pos_init_sheet (&reloc_info.pos, sheet);
5238 /* 2. Get rid of style dependents, see #741197. */
5239 sheet_style_clear_style_dependents (sheet, &change_zone);
5241 /* 3. Invalidate references to kill zone. */
5242 if (is_insert) {
5243 /* Done in the next step. */
5244 } else {
5245 reloc_info.origin = kill_zone;
5246 /* Force invalidation: */
5247 reloc_info.col_offset = is_cols ? last_pos + 1 : 0;
5248 reloc_info.row_offset = is_cols ? 0 : last_pos + 1;
5249 combine_undo (pundo, dependents_relocate (&reloc_info));
5252 /* 4. Fix references to the cells which are moving */
5253 reloc_info.origin = is_insert ? change_zone : move_zone;
5254 reloc_info.col_offset = is_cols ? scount : 0;
5255 reloc_info.row_offset = is_cols ? 0 : scount;
5256 combine_undo (pundo, dependents_relocate (&reloc_info));
5258 /* 5. Move the cells */
5259 sheet_cells_deps_move (&reloc_info);
5261 /* 6. Move the columns/rows to their new location. */
5262 if (is_insert) {
5263 /* From right to left */
5264 for (i = max_used_pos; i >= pos ; --i)
5265 sheet_colrow_move (sheet, is_cols, i, i + count);
5266 } else {
5267 /* From left to right */
5268 for (i = pos + count ; i <= max_used_pos; ++i)
5269 sheet_colrow_move (sheet, is_cols, i, i - count);
5271 sheet_colrow_set_collapse (sheet, is_cols, pos);
5272 sheet_colrow_set_collapse (sheet, is_cols,
5273 is_insert ? pos + count : last_pos - (count - 1));
5275 /* 7. Move formatting. */
5276 sheet_style_insdel_colrow (&reloc_info);
5278 /* 8. Move objects. */
5279 sheet_objects_relocate (&reloc_info, FALSE, pundo);
5281 /* 9. Move merges. */
5282 gnm_sheet_merge_relocate (&reloc_info, pundo);
5284 /* 10. Move filters. */
5285 gnm_sheet_filter_insdel_colrow (sheet, is_cols, is_insert, pos, count, pundo);
5287 /* Notify sheet of pending updates */
5288 sheet_mark_dirty (sheet);
5289 sheet->priv->recompute_visibility = TRUE;
5290 sheet_flag_recompute_spans (sheet);
5291 sheet_flag_status_update_range (sheet, &change_zone);
5292 if (is_cols)
5293 sheet->priv->reposition_objects.col = pos;
5294 else
5295 sheet->priv->reposition_objects.row = pos;
5297 /* WARNING WARNING WARNING
5298 * This is bad practice and should not really be here.
5299 * However, we need to ensure that update is run before
5300 * gnm_sheet_view_panes_insdel_colrow plays with frozen panes, updating those can
5301 * trigger redraws before sheet_update has been called. */
5302 sheet_update (sheet);
5304 SHEET_FOREACH_VIEW (sheet, sv,
5305 gnm_sheet_view_panes_insdel_colrow (sv, is_cols, is_insert, pos, count););
5307 /* The main undo is the opposite operation. */
5308 if (pundo) {
5309 ColRowInsDelData *data;
5310 GOUndo *u;
5312 data = g_new (ColRowInsDelData, 1);
5313 data->func = opposite;
5314 data->sheet = sheet;
5315 data->is_cols = is_cols;
5316 data->pos = pos;
5317 data->count = count;
5318 data->states = states;
5319 data->state_start = kill_start;
5321 u = go_undo_unary_new (data, (GOUndoUnaryFunc)cb_undo_insdel,
5322 (GFreeFunc)cb_undo_insdel_free);
5324 combine_undo (pundo, u);
5327 /* Reapply all filters. */
5328 for (l = sheet->filters; l; l = l->next) {
5329 GnmFilter *filter = l->data;
5330 gnm_filter_reapply (filter);
5333 return FALSE;
5337 * sheet_insert_cols:
5338 * @sheet: #Sheet
5339 * @col: At which position we want to insert
5340 * @count: The number of columns to be inserted
5341 * @pundo: (out): (transfer full): (allow-none): undo closure
5342 * @cc:
5344 gboolean
5345 sheet_insert_cols (Sheet *sheet, int col, int count,
5346 GOUndo **pundo, GOCmdContext *cc)
5348 return sheet_insdel_colrow (sheet, col, count, pundo, cc,
5349 TRUE, TRUE,
5350 _("Insert Columns"),
5351 sheet_delete_cols);
5355 * sheet_delete_cols:
5356 * @sheet: The sheet
5357 * @col: At which position we want to start deleting columns
5358 * @count: The number of columns to be deleted
5359 * @pundo: (out): (transfer full): (allow-none): undo closure
5360 * @cc: The command context
5362 gboolean
5363 sheet_delete_cols (Sheet *sheet, int col, int count,
5364 GOUndo **pundo, GOCmdContext *cc)
5366 return sheet_insdel_colrow (sheet, col, count, pundo, cc,
5367 TRUE, FALSE,
5368 _("Delete Columns"),
5369 sheet_insert_cols);
5373 * sheet_insert_rows:
5374 * @sheet: The sheet
5375 * @row: At which position we want to insert
5376 * @count: The number of rows to be inserted
5377 * @pundo: (out): (transfer full): (allow-none): undo closure
5378 * @cc: The command context
5380 gboolean
5381 sheet_insert_rows (Sheet *sheet, int row, int count,
5382 GOUndo **pundo, GOCmdContext *cc)
5384 return sheet_insdel_colrow (sheet, row, count, pundo, cc,
5385 FALSE, TRUE,
5386 _("Insert Rows"),
5387 sheet_delete_rows);
5391 * sheet_delete_rows:
5392 * @sheet: The sheet
5393 * @row: At which position we want to start deleting rows
5394 * @count: The number of rows to be deleted
5395 * @pundo: (out): (transfer full): (allow-none): undo closure
5396 * @cc: The command context
5398 gboolean
5399 sheet_delete_rows (Sheet *sheet, int row, int count,
5400 GOUndo **pundo, GOCmdContext *cc)
5402 return sheet_insdel_colrow (sheet, row, count, pundo, cc,
5403 FALSE, FALSE,
5404 _("Delete Rows"),
5405 sheet_insert_rows);
5409 * Callback for sheet_foreach_cell_in_region to remove a cell from the sheet
5410 * hash, unlink from the dependent collection and put it in a temporary list.
5412 static GnmValue *
5413 cb_collect_cell (GnmCellIter const *iter, gpointer user)
5415 GList ** l = user;
5416 GnmCell *cell = iter->cell;
5417 gboolean needs_recalc = gnm_cell_needs_recalc (cell);
5419 sheet_cell_remove_from_hash (iter->pp.sheet, cell);
5420 *l = g_list_prepend (*l, cell);
5421 if (needs_recalc)
5422 cell->base.flags |= DEPENDENT_NEEDS_RECALC;
5423 return NULL;
5427 * sheet_move_range:
5428 * @cc:
5429 * @rinfo:
5430 * @pundo: optionally NULL, caller releases result
5432 * Move a range as specified in @rinfo report warnings to @cc.
5433 * if @pundo is non NULL, invalidate references to the
5434 * target region that are being cleared, and store the undo information
5435 * in @pundo. If it is NULL do NOT INVALIDATE.
5437 void
5438 sheet_move_range (GnmExprRelocateInfo const *rinfo,
5439 GOUndo **pundo, GOCmdContext *cc)
5441 GList *cells = NULL;
5442 GnmCell *cell;
5443 GnmRange dst;
5444 gboolean out_of_range;
5446 g_return_if_fail (rinfo != NULL);
5447 g_return_if_fail (IS_SHEET (rinfo->origin_sheet));
5448 g_return_if_fail (IS_SHEET (rinfo->target_sheet));
5449 g_return_if_fail (rinfo->origin_sheet != rinfo->target_sheet ||
5450 rinfo->col_offset != 0 ||
5451 rinfo->row_offset != 0);
5453 dst = rinfo->origin;
5454 out_of_range = range_translate (&dst, rinfo->target_sheet,
5455 rinfo->col_offset, rinfo->row_offset);
5457 /* Redraw the src region in case anything was spanning */
5458 sheet_redraw_range (rinfo->origin_sheet, &rinfo->origin);
5460 /* 1. invalidate references to any cells in the destination range that
5461 * are not shared with the src. This must be done before the references
5462 * to from the src range are adjusted because they will point into
5463 * the destination.
5465 if (pundo != NULL) {
5466 *pundo = NULL;
5467 if (!out_of_range) {
5468 GSList *invalid;
5469 GnmExprRelocateInfo reloc_info;
5471 /* We need to be careful about invalidating references
5472 * to the old content of the destination region. We
5473 * only invalidate references to regions that are
5474 * actually lost. However, this care is only necessary
5475 * if the source and target sheets are the same.
5477 * Handle dst cells being pasted over
5479 if (rinfo->origin_sheet == rinfo->target_sheet &&
5480 range_overlap (&rinfo->origin, &dst))
5481 invalid = range_split_ranges (&rinfo->origin, &dst);
5482 else
5483 invalid = g_slist_append (NULL, gnm_range_dup (&dst));
5485 reloc_info.origin_sheet = reloc_info.target_sheet = rinfo->target_sheet;
5487 /* send to infinity to invalidate, but try to assist
5488 * the relocation heuristics only move in 1
5489 * dimension if possible to give us a chance to be
5490 * smart about partial invalidations */
5491 reloc_info.col_offset = gnm_sheet_get_max_cols (rinfo->target_sheet);
5492 reloc_info.row_offset = gnm_sheet_get_max_rows (rinfo->target_sheet);
5493 reloc_info.sticky_end = TRUE;
5494 if (rinfo->col_offset == 0) {
5495 reloc_info.col_offset = 0;
5496 reloc_info.reloc_type = GNM_EXPR_RELOCATE_ROWS;
5497 } else if (rinfo->row_offset == 0) {
5498 reloc_info.row_offset = 0;
5499 reloc_info.reloc_type = GNM_EXPR_RELOCATE_COLS;
5500 } else
5501 reloc_info.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE;
5503 parse_pos_init_sheet (&reloc_info.pos,
5504 rinfo->origin_sheet);
5506 while (invalid) {
5507 GnmRange *r = invalid->data;
5508 invalid = g_slist_remove (invalid, r);
5509 if (!range_overlap (r, &rinfo->origin)) {
5510 reloc_info.origin = *r;
5511 combine_undo (pundo,
5512 dependents_relocate (&reloc_info));
5514 g_free (r);
5518 * DO NOT handle src cells moving out the bounds.
5519 * that is handled elsewhere.
5523 /* 2. Fix references to and from the cells which are moving */
5524 combine_undo (pundo, dependents_relocate (rinfo));
5527 /* 3. Collect the cells */
5528 sheet_foreach_cell_in_range (rinfo->origin_sheet,
5529 CELL_ITER_IGNORE_NONEXISTENT,
5530 &rinfo->origin,
5531 &cb_collect_cell, &cells);
5533 /* Reverse list so that we start at the top left (simplifies arrays). */
5534 cells = g_list_reverse (cells);
5536 /* 4. Clear the target area & invalidate references to it */
5537 if (!out_of_range)
5538 /* we can clear content but not styles from the destination
5539 * region without worrying if it overlaps with the source,
5540 * because we have already extracted the content. However,
5541 * we do need to queue anything that depends on the region for
5542 * recalc. */
5543 sheet_clear_region (rinfo->target_sheet,
5544 dst.start.col, dst.start.row,
5545 dst.end.col, dst.end.row,
5546 CLEAR_VALUES|CLEAR_RECALC_DEPS, cc);
5548 /* 5. Slide styles BEFORE the cells so that spans get computed properly */
5549 sheet_style_relocate (rinfo);
5551 /* 6. Insert the cells back */
5552 for (; cells != NULL ; cells = g_list_remove (cells, cell)) {
5553 cell = cells->data;
5555 /* check for out of bounds and delete if necessary */
5556 if ((cell->pos.col + rinfo->col_offset) >= gnm_sheet_get_max_cols (rinfo->target_sheet) ||
5557 (cell->pos.row + rinfo->row_offset) >= gnm_sheet_get_max_rows (rinfo->target_sheet)) {
5558 cell_free (cell);
5559 continue;
5562 /* Update the location */
5563 cell->base.sheet = rinfo->target_sheet;
5564 cell->pos.col += rinfo->col_offset;
5565 cell->pos.row += rinfo->row_offset;
5566 sheet_cell_add_to_hash (rinfo->target_sheet, cell);
5567 if (gnm_cell_has_expr (cell))
5568 dependent_link (GNM_CELL_TO_DEP (cell));
5571 /* 7. Move objects in the range */
5572 sheet_objects_relocate (rinfo, TRUE, pundo);
5573 gnm_sheet_merge_relocate (rinfo, pundo);
5575 /* 8. Notify sheet of pending update */
5576 sheet_flag_recompute_spans (rinfo->origin_sheet);
5577 sheet_flag_status_update_range (rinfo->origin_sheet, &rinfo->origin);
5580 static void
5581 sheet_colrow_default_calc (Sheet *sheet, double units,
5582 gboolean is_cols, gboolean is_pts)
5584 ColRowInfo *cri = is_cols
5585 ? &sheet->cols.default_style
5586 : &sheet->rows.default_style;
5588 g_return_if_fail (units > 0.);
5590 cri->is_default = TRUE;
5591 cri->hard_size = FALSE;
5592 cri->visible = TRUE;
5593 cri->spans = NULL;
5595 if (is_pts) {
5596 cri->size_pts = units;
5597 colrow_compute_pixels_from_pts (cri, sheet, is_cols, -1);
5598 } else {
5599 cri->size_pixels = units;
5600 colrow_compute_pts_from_pixels (cri, sheet, is_cols, -1);
5604 /************************************************************************/
5605 /* Col width support routines.
5609 * sheet_col_get_distance_pts:
5611 * Return the number of points between from_col to to_col
5612 * measured from the upper left corner.
5614 double
5615 sheet_col_get_distance_pts (Sheet const *sheet, int from, int to)
5617 ColRowInfo const *ci;
5618 double dflt, pts = 0., sign = 1.;
5619 int i;
5621 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5623 if (from > to) {
5624 int const tmp = to;
5625 to = from;
5626 from = tmp;
5627 sign = -1.;
5630 g_return_val_if_fail (from >= 0, 1.);
5631 g_return_val_if_fail (to <= gnm_sheet_get_max_cols (sheet), 1.);
5633 /* Do not use sheet_colrow_foreach, it ignores empties */
5634 dflt = sheet->cols.default_style.size_pts;
5635 for (i = from ; i < to ; ++i) {
5636 if (NULL == (ci = sheet_col_get (sheet, i)))
5637 pts += dflt;
5638 else if (ci->visible)
5639 pts += ci->size_pts;
5642 if (sheet->display_formulas)
5643 pts *= 2.;
5645 return pts * sign;
5649 * sheet_col_get_distance_pixels:
5651 * Return the number of pixels between from_col to to_col
5652 * measured from the upper left corner.
5655 sheet_col_get_distance_pixels (Sheet const *sheet, int from, int to)
5657 ColRowInfo const *ci;
5658 int dflt, pixels = 0, sign = 1;
5659 int i;
5661 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5663 if (from > to) {
5664 int const tmp = to;
5665 to = from;
5666 from = tmp;
5667 sign = -1;
5670 g_return_val_if_fail (from >= 0, 1);
5671 g_return_val_if_fail (to <= gnm_sheet_get_max_cols (sheet), 1);
5673 /* Do not use sheet_colrow_foreach, it ignores empties */
5674 dflt = sheet_col_get_default_size_pixels (sheet);
5675 for (i = from ; i < to ; ++i) {
5676 if (NULL == (ci = sheet_col_get (sheet, i)))
5677 pixels += dflt;
5678 else if (ci->visible)
5679 pixels += ci->size_pixels;
5682 return pixels * sign;
5686 * sheet_col_set_size_pts:
5687 * @sheet: The sheet
5688 * @col: The col
5689 * @width_pts: The desired widtht in pts
5690 * @set_by_user: TRUE if this was done by a user (ie, user manually
5691 * set the width)
5693 * Sets width of a col in pts, INCLUDING left and right margins, and the far
5694 * grid line. This is a low level internal routine. It does NOT redraw,
5695 * or reposition objects.
5697 void
5698 sheet_col_set_size_pts (Sheet *sheet, int col, double width_pts,
5699 gboolean set_by_user)
5701 ColRowInfo *ci;
5703 g_return_if_fail (IS_SHEET (sheet));
5704 g_return_if_fail (width_pts > 0.0);
5706 ci = sheet_col_fetch (sheet, col);
5707 ci->hard_size = set_by_user;
5708 if (ci->size_pts == width_pts)
5709 return;
5711 ci->size_pts = width_pts;
5712 colrow_compute_pixels_from_pts (ci, sheet, TRUE, -1);
5714 sheet->priv->recompute_visibility = TRUE;
5715 sheet_flag_recompute_spans (sheet);
5716 if (sheet->priv->reposition_objects.col > col)
5717 sheet->priv->reposition_objects.col = col;
5720 void
5721 sheet_col_set_size_pixels (Sheet *sheet, int col, int width_pixels,
5722 gboolean set_by_user)
5724 ColRowInfo *ci;
5726 g_return_if_fail (IS_SHEET (sheet));
5727 g_return_if_fail (width_pixels > 0.0);
5729 ci = sheet_col_fetch (sheet, col);
5730 ci->hard_size = set_by_user;
5731 if (ci->size_pixels == width_pixels)
5732 return;
5734 ci->size_pixels = width_pixels;
5735 colrow_compute_pts_from_pixels (ci, sheet, TRUE, -1);
5737 sheet->priv->recompute_visibility = TRUE;
5738 sheet_flag_recompute_spans (sheet);
5739 if (sheet->priv->reposition_objects.col > col)
5740 sheet->priv->reposition_objects.col = col;
5744 * sheet_col_get_default_size_pts:
5746 * Return the default number of pts in a column, including margins.
5747 * This function returns the raw sum, no rounding etc.
5749 double
5750 sheet_col_get_default_size_pts (Sheet const *sheet)
5752 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5753 return sheet->cols.default_style.size_pts;
5757 sheet_col_get_default_size_pixels (Sheet const *sheet)
5759 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5760 return sheet->cols.default_style.size_pixels;
5763 void
5764 sheet_col_set_default_size_pts (Sheet *sheet, double width_pts)
5766 g_return_if_fail (IS_SHEET (sheet));
5767 g_return_if_fail (width_pts > 0.);
5769 sheet_colrow_default_calc (sheet, width_pts, TRUE, TRUE);
5770 sheet->priv->recompute_visibility = TRUE;
5771 sheet_flag_recompute_spans (sheet);
5772 sheet->priv->reposition_objects.col = 0;
5774 void
5775 sheet_col_set_default_size_pixels (Sheet *sheet, int width_pixels)
5777 g_return_if_fail (IS_SHEET (sheet));
5779 sheet_colrow_default_calc (sheet, width_pixels, TRUE, FALSE);
5780 sheet->priv->recompute_visibility = TRUE;
5781 sheet_flag_recompute_spans (sheet);
5782 sheet->priv->reposition_objects.col = 0;
5785 /**************************************************************************/
5786 /* Row height support routines
5790 * sheet_row_get_distance_pts:
5792 * Return the number of points between from_row to to_row
5793 * measured from the upper left corner.
5795 double
5796 sheet_row_get_distance_pts (Sheet const *sheet, int from, int to)
5798 ColRowSegment const *segment;
5799 ColRowInfo const *ri;
5800 double const default_size = sheet->rows.default_style.size_pts;
5801 double pts = 0., sign = 1.;
5802 int i;
5804 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5806 if (from > to) {
5807 int const tmp = to;
5808 to = from;
5809 from = tmp;
5810 sign = -1.;
5813 g_return_val_if_fail (from >= 0, 1.);
5814 g_return_val_if_fail (to <= gnm_sheet_get_max_rows (sheet), 1.);
5816 /* Do not use sheet_colrow_foreach, it ignores empties.
5817 * Optimize this so that long jumps are not quite so horrific
5818 * for performance.
5820 for (i = from ; i < to ; ++i) {
5821 segment = COLROW_GET_SEGMENT (&(sheet->rows), i);
5823 if (segment != NULL) {
5824 ri = segment->info[COLROW_SUB_INDEX (i)];
5825 if (ri == NULL)
5826 pts += default_size;
5827 else if (ri->visible)
5828 pts += ri->size_pts;
5829 } else {
5830 int segment_end = COLROW_SEGMENT_END (i)+1;
5831 if (segment_end > to)
5832 segment_end = to;
5833 pts += default_size * (segment_end - i);
5834 i = segment_end-1;
5838 return pts*sign;
5842 * sheet_row_get_distance_pixels:
5844 * Return the number of pixels between from_row to to_row
5845 * measured from the upper left corner.
5848 sheet_row_get_distance_pixels (Sheet const *sheet, int from, int to)
5850 ColRowInfo const *ci;
5851 int dflt, pixels = 0, sign = 1;
5852 int i;
5854 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5856 if (from > to) {
5857 int const tmp = to;
5858 to = from;
5859 from = tmp;
5860 sign = -1;
5863 g_return_val_if_fail (from >= 0, 1);
5864 g_return_val_if_fail (to <= gnm_sheet_get_max_rows (sheet), 1);
5866 /* Do not use sheet_colrow_foreach, it ignores empties */
5867 dflt = sheet_row_get_default_size_pixels (sheet);
5868 for (i = from ; i < to ; ++i) {
5869 if (NULL == (ci = sheet_row_get (sheet, i)))
5870 pixels += dflt;
5871 else if (ci->visible)
5872 pixels += ci->size_pixels;
5875 return pixels * sign;
5879 * sheet_row_set_size_pts:
5880 * @sheet: The sheet
5881 * @row: The row
5882 * @height_pts: The desired height in pts
5883 * @set_by_user: TRUE if this was done by a user (ie, user manually
5884 * set the height)
5886 * Sets height of a row in pts, INCLUDING top and bottom margins, and the lower
5887 * grid line. This is a low level internal routine. It does NOT redraw,
5888 * or reposition objects.
5890 void
5891 sheet_row_set_size_pts (Sheet *sheet, int row, double height_pts,
5892 gboolean set_by_user)
5894 ColRowInfo *ri;
5896 g_return_if_fail (IS_SHEET (sheet));
5897 g_return_if_fail (height_pts > 0.0);
5899 ri = sheet_row_fetch (sheet, row);
5900 ri->hard_size = set_by_user;
5901 if (ri->size_pts == height_pts)
5902 return;
5904 ri->size_pts = height_pts;
5905 colrow_compute_pixels_from_pts (ri, sheet, FALSE, -1);
5907 sheet->priv->recompute_visibility = TRUE;
5908 if (sheet->priv->reposition_objects.row > row)
5909 sheet->priv->reposition_objects.row = row;
5913 * sheet_row_set_size_pixels:
5914 * @sheet: The sheet
5915 * @row: The row
5916 * @height_pixels: The desired height
5917 * @set_by_user: TRUE if this was done by a user (ie, user manually
5918 * set the width)
5920 * Sets height of a row in pixels, INCLUDING top and bottom margins, and the lower
5921 * grid line.
5923 void
5924 sheet_row_set_size_pixels (Sheet *sheet, int row, int height_pixels,
5925 gboolean set_by_user)
5927 ColRowInfo *ri;
5929 g_return_if_fail (IS_SHEET (sheet));
5930 g_return_if_fail (height_pixels > 0);
5932 ri = sheet_row_fetch (sheet, row);
5933 ri->hard_size = set_by_user;
5934 if (ri->size_pixels == height_pixels)
5935 return;
5937 ri->size_pixels = height_pixels;
5938 colrow_compute_pts_from_pixels (ri, sheet, FALSE, -1);
5940 sheet->priv->recompute_visibility = TRUE;
5941 if (sheet->priv->reposition_objects.row > row)
5942 sheet->priv->reposition_objects.row = row;
5946 * sheet_row_get_default_size_pts:
5948 * Return the defaul number of units in a row, including margins.
5949 * This function returns the raw sum, no rounding etc.
5951 double
5952 sheet_row_get_default_size_pts (Sheet const *sheet)
5954 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5955 return sheet->rows.default_style.size_pts;
5959 sheet_row_get_default_size_pixels (Sheet const *sheet)
5961 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5962 return sheet->rows.default_style.size_pixels;
5965 void
5966 sheet_row_set_default_size_pts (Sheet *sheet, double height_pts)
5968 g_return_if_fail (IS_SHEET (sheet));
5970 sheet_colrow_default_calc (sheet, height_pts, FALSE, TRUE);
5971 sheet->priv->recompute_visibility = TRUE;
5972 sheet->priv->reposition_objects.row = 0;
5975 void
5976 sheet_row_set_default_size_pixels (Sheet *sheet, int height_pixels)
5978 g_return_if_fail (IS_SHEET (sheet));
5980 sheet_colrow_default_calc (sheet, height_pixels, FALSE, FALSE);
5981 sheet->priv->recompute_visibility = TRUE;
5982 sheet->priv->reposition_objects.row = 0;
5985 /****************************************************************************/
5987 void
5988 sheet_scrollbar_config (Sheet const *sheet)
5990 g_return_if_fail (IS_SHEET (sheet));
5992 SHEET_FOREACH_CONTROL (sheet, view, control,
5993 sc_scrollbar_config (control););
5996 /*****************************************************************************/
5997 typedef struct
5999 gboolean is_column;
6000 Sheet *sheet;
6001 } closure_clone_colrow;
6003 static gboolean
6004 sheet_clone_colrow_info_item (GnmColRowIter const *iter, void *user_data)
6006 closure_clone_colrow const *closure = user_data;
6007 ColRowInfo *new_colrow = sheet_colrow_fetch (closure->sheet,
6008 iter->pos, closure->is_column);
6009 col_row_info_copy (new_colrow, iter->cri);
6010 return FALSE;
6013 static void
6014 sheet_dup_colrows (Sheet const *src, Sheet *dst)
6016 closure_clone_colrow closure;
6017 int max_col = MIN (gnm_sheet_get_max_cols (src), gnm_sheet_get_max_cols (dst)),
6018 max_row = MIN (gnm_sheet_get_max_rows (src), gnm_sheet_get_max_rows (dst));
6020 closure.sheet = dst;
6021 closure.is_column = TRUE;
6022 sheet_colrow_foreach (src, TRUE, 0, max_col - 1,
6023 &sheet_clone_colrow_info_item, &closure);
6024 closure.is_column = FALSE;
6025 sheet_colrow_foreach (src, FALSE, 0, max_row - 1,
6026 &sheet_clone_colrow_info_item, &closure);
6028 sheet_col_set_default_size_pixels (dst,
6029 sheet_col_get_default_size_pixels (src));
6030 sheet_row_set_default_size_pixels (dst,
6031 sheet_row_get_default_size_pixels (src));
6033 dst->cols.max_outline_level = src->cols.max_outline_level;
6034 dst->rows.max_outline_level = src->rows.max_outline_level;
6037 static void
6038 sheet_dup_styles (Sheet const *src, Sheet *dst)
6040 static GnmCellPos const corner = { 0, 0 };
6041 GnmRange r;
6042 GnmStyleList *styles;
6044 sheet_style_set_auto_pattern_color (
6045 dst, sheet_style_get_auto_pattern_color (src));
6047 styles = sheet_style_get_range (src, range_init_full_sheet (&r, src));
6048 sheet_style_set_list (dst, &corner, styles, NULL, NULL);
6049 style_list_free (styles);
6052 static void
6053 sheet_dup_merged_regions (Sheet const *src, Sheet *dst)
6055 GSList *ptr;
6057 for (ptr = src->list_merged ; ptr != NULL ; ptr = ptr->next)
6058 gnm_sheet_merge_add (dst, ptr->data, FALSE, NULL);
6061 static void
6062 sheet_dup_names (Sheet const *src, Sheet *dst)
6064 GSList *names = gnm_named_expr_collection_list (src->names);
6065 GSList *l;
6066 GnmParsePos dst_pp;
6068 if (names == NULL)
6069 return;
6071 parse_pos_init_sheet (&dst_pp, dst);
6073 /* Pass 1: add placeholders. */
6074 for (l = names; l; l = l->next) {
6075 GnmNamedExpr *src_nexpr = l->data;
6076 char const *name = expr_name_name (src_nexpr);
6077 GnmNamedExpr *dst_nexpr =
6078 gnm_named_expr_collection_lookup (dst->names, name);
6079 GnmExprTop const *texpr;
6081 if (dst_nexpr)
6082 continue;
6084 texpr = gnm_expr_top_new_constant (value_new_empty ());
6085 expr_name_add (&dst_pp, name, texpr , NULL, TRUE, NULL);
6088 /* Pass 2: assign the right expression. */
6089 for (l = names; l; l = l->next) {
6090 GnmNamedExpr *src_nexpr = l->data;
6091 char const *name = expr_name_name (src_nexpr);
6092 GnmNamedExpr *dst_nexpr =
6093 gnm_named_expr_collection_lookup (dst->names, name);
6094 GnmExprTop const *texpr;
6096 if (!dst_nexpr) {
6097 g_warning ("Trouble while duplicating name %s", name);
6098 continue;
6101 if (!dst_nexpr->is_editable)
6102 continue;
6104 texpr = gnm_expr_top_relocate_sheet (src_nexpr->texpr, src, dst);
6105 expr_name_set_expr (dst_nexpr, texpr);
6108 g_slist_free (names);
6111 static void
6112 cb_sheet_cell_copy (G_GNUC_UNUSED gpointer unused, gpointer key, gpointer new_sheet_param)
6114 GnmCell const *cell = key;
6115 Sheet *dst = new_sheet_param;
6116 Sheet *src;
6117 GnmExprTop const *texpr;
6119 g_return_if_fail (dst != NULL);
6120 g_return_if_fail (cell != NULL);
6122 src = cell->base.sheet;
6123 texpr = cell->base.texpr;
6125 if (texpr && gnm_expr_top_is_array_corner (texpr)) {
6126 int cols, rows;
6128 texpr = gnm_expr_top_relocate_sheet (texpr, src, dst);
6129 gnm_expr_top_get_array_size (texpr, &cols, &rows);
6131 gnm_cell_set_array_formula (dst,
6132 cell->pos.col, cell->pos.row,
6133 cell->pos.col + cols - 1,
6134 cell->pos.row + rows - 1,
6135 gnm_expr_top_new (gnm_expr_copy (gnm_expr_top_get_array_expr (texpr))));
6137 gnm_expr_top_unref (texpr);
6138 } else if (texpr && gnm_expr_top_is_array_elem (texpr, NULL, NULL)) {
6139 /* Not a corner -- ignore. */
6140 } else {
6141 GnmCell *new_cell = sheet_cell_create (dst, cell->pos.col, cell->pos.row);
6142 if (gnm_cell_has_expr (cell)) {
6143 texpr = gnm_expr_top_relocate_sheet (texpr, src, dst);
6144 gnm_cell_set_expr_and_value (new_cell, texpr, value_new_empty (), TRUE);
6145 gnm_expr_top_unref (texpr);
6146 } else
6147 gnm_cell_set_value (new_cell, value_dup (cell->value));
6151 static void
6152 sheet_dup_cells (Sheet const *src, Sheet *dst)
6154 sheet_cell_foreach (src, &cb_sheet_cell_copy, dst);
6155 sheet_region_queue_recalc (dst, NULL);
6158 static void
6159 sheet_dup_filters (Sheet const *src, Sheet *dst)
6161 GSList *ptr;
6162 for (ptr = src->filters ; ptr != NULL ; ptr = ptr->next)
6163 gnm_filter_dup (ptr->data, dst);
6164 dst->filters = g_slist_reverse (dst->filters);
6168 * sheet_dup:
6169 * @source_sheet: #Sheet
6171 * Create a new Sheet and return it.
6172 * Returns: (transfer full): the newly allocated #Sheet.
6174 Sheet *
6175 sheet_dup (Sheet const *src)
6177 Workbook *wb;
6178 Sheet *dst;
6179 char *name;
6180 GList *l;
6182 g_return_val_if_fail (IS_SHEET (src), NULL);
6183 g_return_val_if_fail (src->workbook != NULL, NULL);
6185 wb = src->workbook;
6186 name = workbook_sheet_get_free_name (wb, src->name_unquoted,
6187 TRUE, TRUE);
6188 dst = sheet_new_with_type (wb, name, src->sheet_type,
6189 src->size.max_cols, src->size.max_rows);
6190 g_free (name);
6192 dst->protected_allow = src->protected_allow;
6193 g_object_set (dst,
6194 "zoom-factor", src->last_zoom_factor_used,
6195 "text-is-rtl", src->text_is_rtl,
6196 "visibility", src->visibility,
6197 "protected", src->is_protected,
6198 "display-formulas", src->display_formulas,
6199 "display-zeros", !src->hide_zero,
6200 "display-grid", !src->hide_grid,
6201 "display-column-header", !src->hide_col_header,
6202 "display-row-header", !src->hide_row_header,
6203 "display-outlines", src->display_outlines,
6204 "display-outlines-below", src->outline_symbols_below,
6205 "display-outlines-right", src->outline_symbols_right,
6206 "conventions", src->convs,
6207 "tab-foreground", src->tab_text_color,
6208 "tab-background", src->tab_color,
6209 NULL);
6211 gnm_print_info_free (dst->print_info);
6212 dst->print_info = gnm_print_info_dup (src->print_info);
6214 sheet_dup_styles (src, dst);
6215 sheet_dup_merged_regions (src, dst);
6216 sheet_dup_colrows (src, dst);
6217 sheet_dup_names (src, dst);
6218 sheet_dup_cells (src, dst);
6219 sheet_objects_dup (src, dst, NULL);
6220 sheet_dup_filters (src, dst); /* must be after objects */
6222 #warning selection is in view
6223 #warning freeze/thaw is in view
6225 g_object_unref (dst->solver_parameters);
6226 dst->solver_parameters = gnm_solver_param_dup (src->solver_parameters, dst);
6228 for (l = src->scenarios; l; l = l->next) {
6229 GnmScenario *src_sc = l->data;
6230 GnmScenario *dst_sc = gnm_scenario_dup (src_sc, dst);
6231 dst->scenarios = g_list_prepend (dst->scenarios, dst_sc);
6233 dst->scenarios = g_list_reverse (dst->scenarios);
6235 sheet_mark_dirty (dst);
6236 sheet_redraw_all (dst, TRUE);
6238 return dst;
6242 * sheet_set_outline_direction:
6243 * @sheet: the sheet
6244 * @is_cols: use cols or rows
6246 * When changing the placement of outline collapse markers the flags
6247 * need to be recomputed.
6249 void
6250 sheet_set_outline_direction (Sheet *sheet, gboolean is_cols)
6252 unsigned i;
6253 g_return_if_fail (IS_SHEET (sheet));
6255 /* not particularly efficient, but this is not a hot spot */
6256 for (i = colrow_max (is_cols, sheet); i-- > 0 ; )
6257 sheet_colrow_set_collapse (sheet, is_cols, i);
6261 * sheet_get_view:
6262 * @sheet:
6263 * @wbv:
6265 * Find the SheetView corresponding to the supplied @wbv.
6266 * Returns: (transfer none): the view.
6268 SheetView *
6269 sheet_get_view (Sheet const *sheet, WorkbookView const *wbv)
6271 if (sheet == NULL)
6272 return NULL;
6274 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6276 SHEET_FOREACH_VIEW (sheet, view, {
6277 if (sv_wbv (view) == wbv)
6278 return view;
6280 return NULL;
6283 static gboolean
6284 cb_queue_respan (GnmColRowIter const *iter, void *user_data)
6286 ((ColRowInfo *)(iter->cri))->needs_respan = TRUE;
6287 return FALSE;
6291 * sheet_queue_respan:
6292 * @sheet:
6293 * @start_row:
6294 * @end_row:
6296 * queues a span generation for the selected rows.
6297 * the caller is responsible for queuing a redraw
6299 void
6300 sheet_queue_respan (Sheet const *sheet, int start_row, int end_row)
6302 sheet_colrow_foreach (sheet, FALSE, start_row, end_row,
6303 cb_queue_respan, NULL);
6306 void
6307 sheet_cell_queue_respan (GnmCell *cell)
6309 ColRowInfo *ri = sheet_row_get (cell->base.sheet, cell->pos.row);
6310 ri->needs_respan = TRUE;
6315 * sheet_get_comment:
6316 * @sheet: #Sheet const *
6317 * @pos: #GnmCellPos const *
6319 * If there is a cell comment at @pos in @sheet return it.
6321 * Caller does get a reference to the object if it exists.
6322 * Returns: (transfer full): the comment or %NULL.
6324 GnmComment *
6325 sheet_get_comment (Sheet const *sheet, GnmCellPos const *pos)
6327 GnmRange r;
6328 GSList *comments;
6329 GnmComment *res;
6331 GnmRange const *mr;
6333 mr = gnm_sheet_merge_contains_pos (sheet, pos);
6335 if (mr)
6336 comments = sheet_objects_get (sheet, mr, GNM_CELL_COMMENT_TYPE);
6337 else {
6338 r.start = r.end = *pos;
6339 comments = sheet_objects_get (sheet, &r, GNM_CELL_COMMENT_TYPE);
6341 if (!comments)
6342 return NULL;
6344 /* This assumes just one comment per cell. */
6345 res = comments->data;
6346 g_slist_free (comments);
6347 return res;
6350 static GnmValue *
6351 cb_find_extents (GnmCellIter const *iter, GnmCellPos *extent)
6353 if (extent->col < iter->pp.eval.col)
6354 extent->col = iter->pp.eval.col;
6355 if (extent->row < iter->pp.eval.row)
6356 extent->row = iter->pp.eval.row;
6357 return NULL;
6361 * sheet_range_trim:
6362 * @sheet: sheet cells are contained on
6363 * @r: range to trim empty cells from
6364 * @cols: trim from right
6365 * @rows: trim from bottom
6367 * This removes empty rows/cols from the
6368 * right hand or bottom edges of the range
6369 * depending on the value of @cols or @rows.
6371 * Return value: TRUE if the range was totally empty.
6373 gboolean
6374 sheet_range_trim (Sheet const *sheet, GnmRange *r,
6375 gboolean cols, gboolean rows)
6377 GnmCellPos extent = { -1, -1 };
6379 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
6380 g_return_val_if_fail (r != NULL, TRUE);
6382 sheet_foreach_cell_in_range (
6383 (Sheet *)sheet, CELL_ITER_IGNORE_BLANK, r,
6384 (CellIterFunc) cb_find_extents, &extent);
6386 if (extent.col < 0 || extent.row < 0)
6387 return TRUE;
6388 if (cols)
6389 r->end.col = extent.col;
6390 if (rows)
6391 r->end.row = extent.row;
6392 return FALSE;
6396 * sheet_range_has_heading:
6397 * @sheet: Sheet to check
6398 * @src: GnmRange to check
6399 * @top: Flag
6401 * Checks for a header row in @sheet!@src. If top is true it looks for a
6402 * header row from the top and if false it looks for a header col from the
6403 * left
6405 * Returns: TRUE if @src seems to have a heading
6407 gboolean
6408 sheet_range_has_heading (Sheet const *sheet, GnmRange const *src,
6409 gboolean top, gboolean ignore_styles)
6411 GnmCell const *a, *b;
6412 int length, i;
6414 /* There is only one row or col */
6415 if (top) {
6416 if (src->end.row <= src->start.row)
6417 return FALSE;
6418 length = src->end.col - src->start.col + 1;
6419 } else {
6420 if (src->end.col <= src->start.col)
6421 return FALSE;
6422 length = src->end.row - src->start.row + 1;
6425 for (i = 0; i < length; i++) {
6426 if (top) {
6427 a = sheet_cell_get (sheet,
6428 src->start.col + i, src->start.row);
6429 b = sheet_cell_get (sheet,
6430 src->start.col + i, src->start.row + 1);
6431 } else {
6432 a = sheet_cell_get (sheet,
6433 src->start.col, src->start.row + i);
6434 b = sheet_cell_get (sheet,
6435 src->start.col + 1, src->start.row + i);
6438 /* be anal */
6439 if (a == NULL || a->value == NULL || b == NULL || b->value == NULL)
6440 continue;
6442 if (VALUE_IS_NUMBER (a->value)) {
6443 if (!VALUE_IS_NUMBER (b->value))
6444 return TRUE;
6445 /* check for style differences */
6446 } else if (a->value->v_any.type != b->value->v_any.type)
6447 return TRUE;
6449 /* Look for style differences */
6450 if (!ignore_styles &&
6451 !gnm_style_equal_header (gnm_cell_get_style (a),
6452 gnm_cell_get_style (b), top))
6453 return TRUE;
6456 return FALSE;
6460 * gnm_sheet_foreach_name:
6461 * @sheet: #Sheet
6462 * @func: (scope call): #GHFunc
6463 * @data: user data.
6465 * Executes @func for each name in @sheet.
6467 void
6468 gnm_sheet_foreach_name (Sheet const *sheet, GHFunc func, gpointer data)
6470 g_return_if_fail (IS_SHEET (sheet));
6472 if (sheet->names)
6473 gnm_named_expr_collection_foreach (sheet->names, func, data);
6477 * gnm_sheet_get_size:
6478 * @sheet: #Sheet
6480 * Returns: (transfer none): the sheet size.
6482 GnmSheetSize const *
6483 gnm_sheet_get_size (Sheet const *sheet)
6485 static const GnmSheetSize default_size = {
6486 GNM_DEFAULT_COLS, GNM_DEFAULT_ROWS
6489 if (G_UNLIKELY (!sheet)) {
6490 g_warning ("NULL sheet in gnm_sheet_get_size!");
6491 /* FIXME: This needs to go. */
6492 return &default_size;
6495 if (G_UNLIKELY (sheet->being_constructed))
6496 g_warning ("Access to sheet size during construction!");
6498 return &sheet->size;
6502 * gnm_sheet_get_size2:
6503 * @sheet: #Sheet, might be %NULL
6504 * @wb: #Workbook, must be non %NULL if @sheet is %NULL
6506 * Returns: (transfer none): the sheet size if @sheet is non %NULL, or the
6507 * default sheet size for @wb.
6509 GnmSheetSize const *
6510 gnm_sheet_get_size2 (Sheet const *sheet, Workbook const *wb)
6512 return sheet
6513 ? gnm_sheet_get_size (sheet)
6514 : workbook_get_sheet_size (wb);
6517 void
6518 gnm_sheet_set_solver_params (Sheet *sheet, GnmSolverParameters *param)
6520 g_return_if_fail (IS_SHEET (sheet));
6521 g_return_if_fail (GNM_IS_SOLVER_PARAMETERS (param));
6523 g_object_ref (param);
6524 g_object_unref (sheet->solver_parameters);
6525 sheet->solver_parameters = param;
6528 /* ------------------------------------------------------------------------- */
6531 * gnm_sheet_scenario_new:
6532 * @sheet:  #Sheet
6533 * @name: the new scenario name.
6535 * Returns: (transfer full): the newly created #GnmScenario.
6537 GnmScenario *
6538 gnm_sheet_scenario_new (Sheet *sheet, const char *name)
6540 GnmScenario *sc;
6541 char *actual_name;
6543 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6544 g_return_val_if_fail (name != NULL, NULL);
6546 /* Check if a scenario having the same name already exists. */
6547 if (gnm_sheet_scenario_find (sheet, name)) {
6548 GString *str = g_string_new (NULL);
6549 gchar *tmp;
6550 int i, j, len;
6552 len = strlen (name);
6553 if (len > 1 && name [len - 1] == ']') {
6554 for (i = len - 2; i > 0; i--) {
6555 if (! g_ascii_isdigit (name [i]))
6556 break;
6559 tmp = g_strdup (name);
6560 if (i > 0 && name [i] == '[')
6561 tmp [i] = '\0';
6562 } else
6563 tmp = g_strdup (name);
6565 for (j = 1; ; j++) {
6566 g_string_printf (str, "%s [%d]", tmp, j);
6567 if (!gnm_sheet_scenario_find (sheet, str->str)) {
6568 actual_name = g_string_free (str, FALSE);
6569 str = NULL;
6570 break;
6573 if (str)
6574 g_string_free (str, TRUE);
6575 g_free (tmp);
6576 } else
6577 actual_name = g_strdup (name);
6579 sc = gnm_scenario_new (actual_name, sheet);
6581 g_free (actual_name);
6583 return sc;
6587 * gnm_sheet_scenario_find:
6588 * @sheet:  #Sheet
6589 * @name: the scenario name.
6591 * Returns: (transfer none): the newly created #GnmScenario.
6593 GnmScenario *
6594 gnm_sheet_scenario_find (Sheet *sheet, const char *name)
6596 GList *l;
6598 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6599 g_return_val_if_fail (name != NULL, NULL);
6601 for (l = sheet->scenarios; l; l = l->next) {
6602 GnmScenario *sc = l->data;
6603 if (strcmp (name, sc->name) == 0)
6604 return sc;
6607 return NULL;
6611 * gnm_sheet_scenario_add:
6612 * @sheet:  #Sheet
6613 * @sc: (transfer full): #GnmScenario
6616 void
6617 gnm_sheet_scenario_add (Sheet *sheet, GnmScenario *sc)
6619 g_return_if_fail (IS_SHEET (sheet));
6620 g_return_if_fail (GNM_IS_SCENARIO (sc));
6622 /* We take ownership of the ref. */
6623 sheet->scenarios = g_list_append (sheet->scenarios, sc);
6626 void
6627 gnm_sheet_scenario_remove (Sheet *sheet, GnmScenario *sc)
6629 g_return_if_fail (IS_SHEET (sheet));
6630 g_return_if_fail (GNM_IS_SCENARIO (sc));
6632 sheet->scenarios = g_list_remove (sheet->scenarios, sc);
6633 g_object_unref (sc);
6636 /* ------------------------------------------------------------------------- */
6639 * gnm_sheet_get_sort_setups:
6640 * @sheet: #Sheet
6642 * Returns: (transfer none): the sort setups for @sheet.
6644 GHashTable *
6645 gnm_sheet_get_sort_setups (Sheet *sheet)
6647 GHashTable *hash = sheet->sort_setups;
6649 if (hash == NULL)
6650 hash = sheet->sort_setups =
6651 g_hash_table_new_full
6652 (g_str_hash, g_str_equal,
6653 g_free, (GDestroyNotify)gnm_sort_data_destroy);
6655 return hash;
6658 void
6659 gnm_sheet_add_sort_setup (Sheet *sheet, char *key, gpointer setup)
6661 GHashTable *hash = gnm_sheet_get_sort_setups (sheet);
6663 g_hash_table_insert (hash, key, setup);
6667 * gnm_sheet_find_sort_setup:
6668 * @sheet: #Sheet
6669 * @key:
6671 * Returns: (transfer none): the found sort setup or %NULL.
6673 gconstpointer
6674 gnm_sheet_find_sort_setup (Sheet *sheet, char const *key)
6676 if (sheet->sort_setups == NULL)
6677 return NULL;
6678 return g_hash_table_lookup (sheet->sort_setups, key);
6682 * sheet_date_conv:
6683 * @sheet: #Sheet
6685 * Returns: (transfer none): the date conventions in effect for the sheet.
6686 * This is purely a convenience function to access the conventions used
6687 * for the workbook. All sheets in a workbook share the same date
6688 * conventions.
6690 GODateConventions const *
6691 sheet_date_conv (Sheet const *sheet)
6693 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6694 return workbook_date_conv (sheet->workbook);