Objects: don't use "pointer" as property type
[gnumeric.git] / src / sheet.c
blob5ae33b335062d25e53bf960bd2be8658b9f62d11
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /*
4 * sheet.c: Implements the sheet management and per-sheet storage
6 * Copyright (C) 2000-2007 Jody Goldberg (jody@gnome.org)
7 * Copyright (C) 1997-1999 Miguel de Icaza (miguel@kernel.org)
8 * Copyright (C) 1999-2009 Morten Welinder (terra@gnome.org)
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License as
12 * published by the Free Software Foundation; either version 2 of the
13 * License, or (at your option) version 3.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
23 * USA
25 #include <gnumeric-config.h>
26 #include "gnumeric.h"
27 #include "sheet.h"
29 #include "sheet-view.h"
30 #include "command-context.h"
31 #include "sheet-control.h"
32 #include "sheet-style.h"
33 #include "workbook-priv.h"
34 #include "workbook-control.h"
35 #include "workbook-view.h"
36 #include "parse-util.h"
37 #include "dependent.h"
38 #include "value.h"
39 #include "number-match.h"
40 #include "clipboard.h"
41 #include "selection.h"
42 #include "ranges.h"
43 #include "print-info.h"
44 #include "mstyle.h"
45 #include "style-color.h"
46 #include "style-font.h"
47 #include "application.h"
48 #include "commands.h"
49 #include "cellspan.h"
50 #include "cell.h"
51 #include "sheet-merge.h"
52 #include "sheet-private.h"
53 #include "expr-name.h"
54 #include "expr.h"
55 #include "rendered-value.h"
56 #include "gnumeric-conf.h"
57 #include "sheet-object-impl.h"
58 #include "sheet-object-cell-comment.h"
59 #include <tools/gnm-solver.h>
60 #include "hlink.h"
61 #include "sheet-filter.h"
62 #include "sheet-filter-combo.h"
63 #include "gnm-sheet-slicer.h"
64 #include "scenarios.h"
65 #include "cell-draw.h"
66 #include "sort.h"
67 #include "gutils.h"
68 #include <goffice/goffice.h>
70 #include "gnm-i18n.h"
71 #include <gsf/gsf-impl-utils.h>
72 #include <stdlib.h>
73 #include <string.h>
75 static GnmSheetSize *
76 gnm_sheet_size_copy (GnmSheetSize *size)
78 GnmSheetSize *res = g_new (GnmSheetSize, 1);
79 *res = *size;
80 return res;
83 GType
84 gnm_sheet_size_get_type (void)
86 static GType t = 0;
88 if (t == 0) {
89 t = g_boxed_type_register_static ("GnmSheetSize",
90 (GBoxedCopyFunc)gnm_sheet_size_copy,
91 (GBoxedFreeFunc)g_free);
93 return t;
96 enum {
97 DETACHED_FROM_WORKBOOK,
98 LAST_SIGNAL
101 static guint signals [LAST_SIGNAL] = { 0 };
103 typedef struct {
104 GObjectClass parent;
106 void (*detached_from_workbook) (Sheet *, Workbook *wb);
107 } GnmSheetClass;
108 typedef Sheet GnmSheet;
110 enum {
111 PROP_0,
112 PROP_SHEET_TYPE,
113 PROP_WORKBOOK,
114 PROP_NAME,
115 PROP_RTL,
116 PROP_VISIBILITY,
117 PROP_DISPLAY_FORMULAS,
118 PROP_DISPLAY_ZEROS,
119 PROP_DISPLAY_GRID,
120 PROP_DISPLAY_COLUMN_HEADER,
121 PROP_DISPLAY_ROW_HEADER,
122 PROP_DISPLAY_OUTLINES,
123 PROP_DISPLAY_OUTLINES_BELOW,
124 PROP_DISPLAY_OUTLINES_RIGHT,
126 PROP_PROTECTED,
127 PROP_PROTECTED_ALLOW_EDIT_OBJECTS,
128 PROP_PROTECTED_ALLOW_EDIT_SCENARIOS,
129 PROP_PROTECTED_ALLOW_CELL_FORMATTING,
130 PROP_PROTECTED_ALLOW_COLUMN_FORMATTING,
131 PROP_PROTECTED_ALLOW_ROW_FORMATTING,
132 PROP_PROTECTED_ALLOW_INSERT_COLUMNS,
133 PROP_PROTECTED_ALLOW_INSERT_ROWS,
134 PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS,
135 PROP_PROTECTED_ALLOW_DELETE_COLUMNS,
136 PROP_PROTECTED_ALLOW_DELETE_ROWS,
137 PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS,
138 PROP_PROTECTED_ALLOW_SORT_RANGES,
139 PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS,
140 PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE,
141 PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS,
143 PROP_CONVENTIONS,
144 PROP_USE_R1C1,
146 PROP_TAB_FOREGROUND,
147 PROP_TAB_BACKGROUND,
148 PROP_ZOOM_FACTOR,
150 PROP_COLUMNS,
151 PROP_ROWS
154 static void gnm_sheet_finalize (GObject *obj);
156 static GObjectClass *parent_class;
158 static void
159 sheet_set_direction (Sheet *sheet, gboolean text_is_rtl)
161 GnmRange r;
163 text_is_rtl = !!text_is_rtl;
164 if (text_is_rtl == sheet->text_is_rtl)
165 return;
167 sheet_mark_dirty (sheet);
169 sheet->text_is_rtl = text_is_rtl;
170 sheet->priv->reposition_objects.col = 0;
171 sheet_range_calc_spans (sheet,
172 range_init_full_sheet (&r, sheet),
173 GNM_SPANCALC_RE_RENDER);
176 static void
177 sheet_set_visibility (Sheet *sheet, GnmSheetVisibility visibility)
179 if (sheet->visibility == visibility)
180 return;
182 sheet->visibility = visibility;
183 sheet_mark_dirty (sheet);
186 static void
187 cb_re_render_formulas (G_GNUC_UNUSED gpointer unused,
188 GnmCell *cell,
189 G_GNUC_UNUSED gpointer user)
191 if (gnm_cell_has_expr (cell)) {
192 gnm_cell_unrender (cell);
193 sheet_cell_queue_respan (cell);
197 static void
198 re_render_formulas (Sheet const *sheet)
200 sheet_cell_foreach (sheet, (GHFunc)cb_re_render_formulas, NULL);
203 static void
204 sheet_set_conventions (Sheet *sheet, GnmConventions const *convs)
206 if (sheet->convs == convs)
207 return;
208 gnm_conventions_unref (sheet->convs);
209 sheet->convs = gnm_conventions_ref (convs);
210 if (sheet->display_formulas)
211 re_render_formulas (sheet);
212 SHEET_FOREACH_VIEW (sheet, sv,
213 sv->edit_pos_changed.content = TRUE;);
214 sheet_mark_dirty (sheet);
217 GnmConventions const *
218 sheet_get_conventions (Sheet const *sheet)
220 g_return_val_if_fail (IS_SHEET (sheet), gnm_conventions_default);
222 return sheet->convs;
225 static void
226 cb_sheet_set_hide_zeros (G_GNUC_UNUSED gpointer unused,
227 GnmCell *cell,
228 G_GNUC_UNUSED gpointer user)
230 if (gnm_cell_is_zero (cell))
231 gnm_cell_unrender (cell);
234 static void
235 sheet_set_hide_zeros (Sheet *sheet, gboolean hide)
237 hide = !!hide;
238 if (sheet->hide_zero == hide)
239 return;
241 sheet->hide_zero = hide;
242 sheet_mark_dirty (sheet);
244 sheet_cell_foreach (sheet, (GHFunc)cb_sheet_set_hide_zeros, NULL);
247 static void
248 sheet_set_name (Sheet *sheet, char const *new_name)
250 Workbook *wb = sheet->workbook;
251 gboolean attached;
252 Sheet *sucker;
253 char *new_name_unquoted;
255 g_return_if_fail (new_name != NULL);
257 /* No change whatsoever. */
258 if (go_str_compare (sheet->name_unquoted, new_name) == 0)
259 return;
261 /* Mark the sheet dirty unless this is the initial name. */
262 if (sheet->name_unquoted)
263 sheet_mark_dirty (sheet);
265 sucker = wb ? workbook_sheet_by_name (wb, new_name) : NULL;
266 if (sucker && sucker != sheet) {
268 * Prevent a name clash. With this you can swap names by
269 * setting just the two names.
271 char *sucker_name = workbook_sheet_get_free_name (wb, new_name, TRUE, FALSE);
272 #if 0
273 g_warning ("Renaming %s to %s to avoid clash.\n", sucker->name_unquoted, sucker_name);
274 #endif
275 g_object_set (sucker, "name", sucker_name, NULL);
276 g_free (sucker_name);
279 attached = wb != NULL &&
280 sheet->index_in_wb != -1 &&
281 sheet->name_case_insensitive;
283 /* FIXME: maybe have workbook_sheet_detach_internal for this. */
284 if (attached)
285 g_hash_table_remove (wb->sheet_hash_private,
286 sheet->name_case_insensitive);
288 /* Copy before free. */
289 new_name_unquoted = g_strdup (new_name);
291 g_free (sheet->name_unquoted);
292 g_free (sheet->name_quoted);
293 g_free (sheet->name_unquoted_collate_key);
294 g_free (sheet->name_case_insensitive);
295 sheet->name_unquoted = new_name_unquoted;
296 sheet->name_quoted = g_string_free
297 (gnm_expr_conv_quote (sheet->convs, new_name_unquoted),
298 FALSE);
299 sheet->name_unquoted_collate_key =
300 g_utf8_collate_key (new_name_unquoted, -1);
301 sheet->name_case_insensitive =
302 g_utf8_casefold (new_name_unquoted, -1);
304 /* FIXME: maybe have workbook_sheet_attach_internal for this. */
305 if (attached)
306 g_hash_table_insert (wb->sheet_hash_private,
307 sheet->name_case_insensitive,
308 sheet);
310 if (!sheet->being_constructed &&
311 sheet->sheet_type == GNM_SHEET_DATA) {
312 /* We have to fix the Sheet_Title name */
313 GnmNamedExpr *nexpr;
314 GnmParsePos pp;
316 parse_pos_init_sheet (&pp, sheet);
317 nexpr = expr_name_lookup (&pp, "Sheet_Title");
318 if (nexpr) {
319 GnmExprTop const *texpr =
320 gnm_expr_top_new_constant
321 (value_new_string (sheet->name_unquoted));
322 expr_name_set_expr (nexpr, texpr);
327 struct resize_colrow {
328 Sheet *sheet;
329 gboolean is_cols;
330 double scale;
333 static gboolean
334 cb_colrow_compute_pixels_from_pts (GnmColRowIter const *iter,
335 struct resize_colrow *data)
337 colrow_compute_pixels_from_pts ((ColRowInfo *)iter->cri,
338 data->sheet, data->is_cols,
339 data->scale);
340 return FALSE;
343 static void
344 cb_clear_rendered_cells (G_GNUC_UNUSED gpointer ignored, GnmCell *cell)
346 if (gnm_cell_get_rendered_value (cell) != NULL) {
347 sheet_cell_queue_respan (cell);
348 gnm_cell_unrender (cell);
352 static void
353 sheet_scale_changed (Sheet *sheet, gboolean cols_rescaled, gboolean rows_rescaled)
355 g_return_if_fail (cols_rescaled || rows_rescaled);
357 /* Then every column and row */
358 if (cols_rescaled) {
359 struct resize_colrow closure;
361 closure.sheet = sheet;
362 closure.is_cols = TRUE;
363 closure.scale = colrow_compute_pixel_scale (sheet, TRUE);
365 colrow_compute_pixels_from_pts (&sheet->cols.default_style,
366 sheet, TRUE, closure.scale);
367 colrow_foreach (&sheet->cols, 0, gnm_sheet_get_last_col (sheet),
368 (ColRowHandler)&cb_colrow_compute_pixels_from_pts, &closure);
370 if (rows_rescaled) {
371 struct resize_colrow closure;
373 closure.sheet = sheet;
374 closure.is_cols = FALSE;
375 closure.scale = colrow_compute_pixel_scale (sheet, FALSE);
377 colrow_compute_pixels_from_pts (&sheet->rows.default_style,
378 sheet, FALSE, closure.scale);
379 colrow_foreach (&sheet->rows, 0, gnm_sheet_get_last_row (sheet),
380 (ColRowHandler)&cb_colrow_compute_pixels_from_pts, &closure);
383 sheet_cell_foreach (sheet, (GHFunc)&cb_clear_rendered_cells, NULL);
384 SHEET_FOREACH_CONTROL (sheet, view, control, sc_scale_changed (control););
387 static void
388 sheet_set_display_formulas (Sheet *sheet, gboolean display)
390 display = !!display;
391 if (sheet->display_formulas == display)
392 return;
394 sheet->display_formulas = display;
395 sheet_mark_dirty (sheet);
396 if (!sheet->being_constructed)
397 sheet_scale_changed (sheet, TRUE, FALSE);
400 static void
401 sheet_set_zoom_factor (Sheet *sheet, double factor)
403 if (fabs (factor - sheet->last_zoom_factor_used) < 1e-6)
404 return;
405 sheet->last_zoom_factor_used = factor;
406 if (!sheet->being_constructed)
407 sheet_scale_changed (sheet, TRUE, TRUE);
410 static void
411 gnm_sheet_set_property (GObject *object, guint property_id,
412 GValue const *value, GParamSpec *pspec)
414 Sheet *sheet = (Sheet *)object;
416 switch (property_id) {
417 case PROP_SHEET_TYPE:
418 /* Construction-time only */
419 sheet->sheet_type = g_value_get_enum (value);
420 break;
421 case PROP_WORKBOOK:
422 /* Construction-time only */
423 sheet->workbook = g_value_get_object (value);
424 break;
425 case PROP_NAME:
426 sheet_set_name (sheet, g_value_get_string (value));
427 break;
428 case PROP_RTL:
429 sheet_set_direction (sheet, g_value_get_boolean (value));
430 break;
431 case PROP_VISIBILITY:
432 sheet_set_visibility (sheet, g_value_get_enum (value));
433 break;
434 case PROP_DISPLAY_FORMULAS:
435 sheet_set_display_formulas (sheet, g_value_get_boolean (value));
436 break;
437 case PROP_DISPLAY_ZEROS:
438 sheet_set_hide_zeros (sheet, !g_value_get_boolean (value));
439 break;
440 case PROP_DISPLAY_GRID:
441 sheet->hide_grid = !g_value_get_boolean (value);
442 break;
443 case PROP_DISPLAY_COLUMN_HEADER:
444 sheet->hide_col_header = !g_value_get_boolean (value);
445 break;
446 case PROP_DISPLAY_ROW_HEADER:
447 sheet->hide_row_header = !g_value_get_boolean (value);
448 break;
449 case PROP_DISPLAY_OUTLINES:
450 sheet->display_outlines = !!g_value_get_boolean (value);
451 break;
452 case PROP_DISPLAY_OUTLINES_BELOW:
453 sheet->outline_symbols_below = !!g_value_get_boolean (value);
454 break;
455 case PROP_DISPLAY_OUTLINES_RIGHT:
456 sheet->outline_symbols_right = !!g_value_get_boolean (value);
457 break;
459 case PROP_PROTECTED :
460 sheet->is_protected = !!g_value_get_boolean (value);
461 break;
462 case PROP_PROTECTED_ALLOW_EDIT_OBJECTS :
463 sheet->protected_allow.edit_objects = !!g_value_get_boolean (value);
464 break;
465 case PROP_PROTECTED_ALLOW_EDIT_SCENARIOS :
466 sheet->protected_allow.edit_scenarios = !!g_value_get_boolean (value);
467 break;
468 case PROP_PROTECTED_ALLOW_CELL_FORMATTING :
469 sheet->protected_allow.cell_formatting = !!g_value_get_boolean (value);
470 break;
471 case PROP_PROTECTED_ALLOW_COLUMN_FORMATTING :
472 sheet->protected_allow.column_formatting = !!g_value_get_boolean (value);
473 break;
474 case PROP_PROTECTED_ALLOW_ROW_FORMATTING :
475 sheet->protected_allow.row_formatting = !!g_value_get_boolean (value);
476 break;
477 case PROP_PROTECTED_ALLOW_INSERT_COLUMNS :
478 sheet->protected_allow.insert_columns = !!g_value_get_boolean (value);
479 break;
480 case PROP_PROTECTED_ALLOW_INSERT_ROWS :
481 sheet->protected_allow.insert_rows = !!g_value_get_boolean (value);
482 break;
483 case PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS :
484 sheet->protected_allow.insert_hyperlinks = !!g_value_get_boolean (value);
485 break;
486 case PROP_PROTECTED_ALLOW_DELETE_COLUMNS :
487 sheet->protected_allow.delete_columns = !!g_value_get_boolean (value);
488 break;
489 case PROP_PROTECTED_ALLOW_DELETE_ROWS :
490 sheet->protected_allow.delete_rows = !!g_value_get_boolean (value);
491 break;
492 case PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS :
493 sheet->protected_allow.select_locked_cells = !!g_value_get_boolean (value);
494 break;
495 case PROP_PROTECTED_ALLOW_SORT_RANGES :
496 sheet->protected_allow.sort_ranges = !!g_value_get_boolean (value);
497 break;
498 case PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS :
499 sheet->protected_allow.edit_auto_filters = !!g_value_get_boolean (value);
500 break;
501 case PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE :
502 sheet->protected_allow.edit_pivottable = !!g_value_get_boolean (value);
503 break;
504 case PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS :
505 sheet->protected_allow.select_unlocked_cells = !!g_value_get_boolean (value);
506 break;
508 case PROP_CONVENTIONS:
509 sheet_set_conventions (sheet, g_value_get_boxed (value));
510 break;
511 case PROP_USE_R1C1: /* convenience api */
512 sheet_set_conventions (sheet, !!g_value_get_boolean (value)
513 ? gnm_conventions_xls_r1c1 : gnm_conventions_default);
514 break;
516 case PROP_TAB_FOREGROUND: {
517 GnmColor *color = g_value_dup_boxed (value);
518 style_color_unref (sheet->tab_text_color);
519 sheet->tab_text_color = color;
520 sheet_mark_dirty (sheet);
521 break;
523 case PROP_TAB_BACKGROUND: {
524 GnmColor *color = g_value_dup_boxed (value);
525 style_color_unref (sheet->tab_color);
526 sheet->tab_color = color;
527 sheet_mark_dirty (sheet);
528 break;
530 case PROP_ZOOM_FACTOR:
531 sheet_set_zoom_factor (sheet, g_value_get_double (value));
532 break;
533 case PROP_COLUMNS:
534 /* Construction-time only */
535 sheet->size.max_cols = g_value_get_int (value);
536 break;
537 case PROP_ROWS:
538 /* Construction-time only */
539 sheet->size.max_rows = g_value_get_int (value);
540 break;
541 default:
542 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
543 break;
547 static void
548 gnm_sheet_get_property (GObject *object, guint property_id,
549 GValue *value, GParamSpec *pspec)
551 Sheet *sheet = (Sheet *)object;
553 switch (property_id) {
554 case PROP_SHEET_TYPE:
555 g_value_set_enum (value, sheet->sheet_type);
556 break;
557 case PROP_WORKBOOK:
558 g_value_set_object (value, sheet->workbook);
559 break;
560 case PROP_NAME:
561 g_value_set_string (value, sheet->name_unquoted);
562 break;
563 case PROP_RTL:
564 g_value_set_boolean (value, sheet->text_is_rtl);
565 break;
566 case PROP_VISIBILITY:
567 g_value_set_enum (value, sheet->visibility);
568 break;
569 case PROP_DISPLAY_FORMULAS:
570 g_value_set_boolean (value, sheet->display_formulas);
571 break;
572 case PROP_DISPLAY_ZEROS:
573 g_value_set_boolean (value, !sheet->hide_zero);
574 break;
575 case PROP_DISPLAY_GRID:
576 g_value_set_boolean (value, !sheet->hide_grid);
577 break;
578 case PROP_DISPLAY_COLUMN_HEADER:
579 g_value_set_boolean (value, !sheet->hide_col_header);
580 break;
581 case PROP_DISPLAY_ROW_HEADER:
582 g_value_set_boolean (value, !sheet->hide_row_header);
583 break;
584 case PROP_DISPLAY_OUTLINES:
585 g_value_set_boolean (value, sheet->display_outlines);
586 break;
587 case PROP_DISPLAY_OUTLINES_BELOW:
588 g_value_set_boolean (value, sheet->outline_symbols_below);
589 break;
590 case PROP_DISPLAY_OUTLINES_RIGHT:
591 g_value_set_boolean (value, sheet->outline_symbols_right);
592 break;
594 case PROP_PROTECTED :
595 g_value_set_boolean (value, sheet->is_protected);
596 break;
597 case PROP_PROTECTED_ALLOW_EDIT_OBJECTS :
598 g_value_set_boolean (value, sheet->protected_allow.edit_objects);
599 break;
600 case PROP_PROTECTED_ALLOW_EDIT_SCENARIOS :
601 g_value_set_boolean (value, sheet->protected_allow.edit_scenarios);
602 break;
603 case PROP_PROTECTED_ALLOW_CELL_FORMATTING :
604 g_value_set_boolean (value, sheet->protected_allow.cell_formatting);
605 break;
606 case PROP_PROTECTED_ALLOW_COLUMN_FORMATTING :
607 g_value_set_boolean (value, sheet->protected_allow.column_formatting);
608 break;
609 case PROP_PROTECTED_ALLOW_ROW_FORMATTING :
610 g_value_set_boolean (value, sheet->protected_allow.row_formatting);
611 break;
612 case PROP_PROTECTED_ALLOW_INSERT_COLUMNS :
613 g_value_set_boolean (value, sheet->protected_allow.insert_columns);
614 break;
615 case PROP_PROTECTED_ALLOW_INSERT_ROWS :
616 g_value_set_boolean (value, sheet->protected_allow.insert_rows);
617 break;
618 case PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS :
619 g_value_set_boolean (value, sheet->protected_allow.insert_hyperlinks);
620 break;
621 case PROP_PROTECTED_ALLOW_DELETE_COLUMNS :
622 g_value_set_boolean (value, sheet->protected_allow.delete_columns);
623 break;
624 case PROP_PROTECTED_ALLOW_DELETE_ROWS :
625 g_value_set_boolean (value, sheet->protected_allow.delete_rows);
626 break;
627 case PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS :
628 g_value_set_boolean (value, sheet->protected_allow.select_locked_cells);
629 break;
630 case PROP_PROTECTED_ALLOW_SORT_RANGES :
631 g_value_set_boolean (value, sheet->protected_allow.sort_ranges);
632 break;
633 case PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS :
634 g_value_set_boolean (value, sheet->protected_allow.edit_auto_filters);
635 break;
636 case PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE :
637 g_value_set_boolean (value, sheet->protected_allow.edit_pivottable);
638 break;
639 case PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS :
640 g_value_set_boolean (value, sheet->protected_allow.select_unlocked_cells);
641 break;
643 case PROP_CONVENTIONS:
644 g_value_set_boxed (value, sheet->convs);
645 break;
646 case PROP_USE_R1C1: /* convenience api */
647 g_value_set_boolean (value, sheet->convs->r1c1_addresses);
648 break;
650 case PROP_TAB_FOREGROUND:
651 g_value_set_boxed (value, sheet->tab_text_color);
652 break;
653 case PROP_TAB_BACKGROUND:
654 g_value_set_boxed (value, sheet->tab_color);
655 break;
656 case PROP_ZOOM_FACTOR:
657 g_value_set_double (value, sheet->last_zoom_factor_used);
658 break;
659 case PROP_COLUMNS:
660 g_value_set_int (value, sheet->size.max_cols);
661 break;
662 case PROP_ROWS:
663 g_value_set_int (value, sheet->size.max_rows);
664 break;
665 default:
666 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
667 break;
671 static void
672 gnm_sheet_constructed (GObject *obj)
674 Sheet *sheet = SHEET (obj);
676 if (parent_class->constructed)
677 parent_class->constructed (obj);
679 /* Now sheet_type, max_cols, and max_rows have been set. */
680 sheet->being_constructed = FALSE;
682 colrow_resize (&sheet->cols, sheet->size.max_cols);
683 colrow_resize (&sheet->rows, sheet->size.max_rows);
685 sheet->priv->reposition_objects.col = sheet->size.max_cols;
686 sheet->priv->reposition_objects.row = sheet->size.max_rows;
688 range_init_full_sheet (&sheet->priv->unhidden_region, sheet);
689 sheet_style_init (sheet);
691 sheet->deps = gnm_dep_container_new (sheet);
693 switch (sheet->sheet_type) {
694 case GNM_SHEET_XLM:
695 sheet->display_formulas = TRUE;
696 break;
697 case GNM_SHEET_OBJECT:
698 sheet->hide_grid = TRUE;
699 sheet->hide_col_header = sheet->hide_row_header = TRUE;
700 colrow_compute_pixels_from_pts (&sheet->rows.default_style,
701 sheet, FALSE, -1);
702 colrow_compute_pixels_from_pts (&sheet->cols.default_style,
703 sheet, TRUE, -1);
704 break;
705 case GNM_SHEET_DATA: {
706 /* We have to add permanent names */
707 GnmExprTop const *texpr;
709 if (sheet->name_unquoted)
710 texpr = gnm_expr_top_new_constant
711 (value_new_string (sheet->name_unquoted));
712 else
713 texpr = gnm_expr_top_new_constant
714 (value_new_error_REF (NULL));
715 expr_name_perm_add (sheet, "Sheet_Title",
716 texpr, FALSE);
718 texpr = gnm_expr_top_new_constant
719 (value_new_error_REF (NULL));
720 expr_name_perm_add (sheet, "Print_Area",
721 texpr, FALSE);
722 break;
724 default:
725 g_assert_not_reached ();
728 sheet_scale_changed (sheet, TRUE, TRUE);
731 static guint
732 cell_set_hash (GnmCell const *key)
734 guint32 r = key->pos.row;
735 guint32 c = key->pos.col;
736 guint32 h;
738 h = r;
739 h *= (guint32)123456789;
740 h ^= c;
741 h *= (guint32)123456789;
743 return h;
746 static gint
747 cell_set_equal (GnmCell const *a, GnmCell const *b)
749 return (a->pos.row == b->pos.row && a->pos.col == b->pos.col);
752 static void
753 gnm_sheet_init (Sheet *sheet)
755 PangoContext *context;
757 sheet->priv = g_new0 (SheetPrivate, 1);
758 sheet->being_constructed = TRUE;
760 sheet->sheet_views = g_ptr_array_new ();
762 /* Init, focus, and load handle setting these if/when necessary */
763 sheet->priv->recompute_visibility = TRUE;
764 sheet->priv->recompute_spans = TRUE;
766 sheet->is_protected = FALSE;
767 sheet->protected_allow.edit_scenarios = FALSE;
768 sheet->protected_allow.cell_formatting = FALSE;
769 sheet->protected_allow.column_formatting = FALSE;
770 sheet->protected_allow.row_formatting = FALSE;
771 sheet->protected_allow.insert_columns = FALSE;
772 sheet->protected_allow.insert_rows = FALSE;
773 sheet->protected_allow.insert_hyperlinks = FALSE;
774 sheet->protected_allow.delete_columns = FALSE;
775 sheet->protected_allow.delete_rows = FALSE;
776 sheet->protected_allow.select_locked_cells = TRUE;
777 sheet->protected_allow.sort_ranges = FALSE;
778 sheet->protected_allow.edit_auto_filters = FALSE;
779 sheet->protected_allow.edit_pivottable = FALSE;
780 sheet->protected_allow.select_unlocked_cells = TRUE;
782 sheet->hide_zero = FALSE;
783 sheet->display_outlines = TRUE;
784 sheet->outline_symbols_below = TRUE;
785 sheet->outline_symbols_right = TRUE;
786 sheet->tab_color = NULL;
787 sheet->tab_text_color = NULL;
788 sheet->visibility = GNM_SHEET_VISIBILITY_VISIBLE;
789 #ifdef GNM_WITH_GTK
790 sheet->text_is_rtl = (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL);
791 #else
792 sheet->text_is_rtl = FALSE;
793 #endif
795 sheet->sheet_objects = NULL;
796 sheet->max_object_extent.col = sheet->max_object_extent.row = 0;
798 sheet->solver_parameters = gnm_solver_param_new (sheet);
800 sheet->cols.max_used = -1;
801 sheet->cols.info = g_ptr_array_new ();
802 sheet_col_set_default_size_pts (sheet, 48);
804 sheet->rows.max_used = -1;
805 sheet->rows.info = g_ptr_array_new ();
806 sheet_row_set_default_size_pts (sheet, 12.75);
808 sheet->print_info = gnm_print_information_new (FALSE);
810 sheet->filters = NULL;
811 sheet->scenarios = NULL;
812 sheet->sort_setups = NULL;
813 sheet->list_merged = NULL;
814 sheet->hash_merged = g_hash_table_new ((GHashFunc)&gnm_cellpos_hash,
815 (GCompareFunc)&gnm_cellpos_equal);
817 sheet->cell_hash = g_hash_table_new ((GHashFunc)&cell_set_hash,
818 (GCompareFunc)&cell_set_equal);
820 /* Init preferences */
821 sheet->convs = gnm_conventions_ref (gnm_conventions_default);
823 /* FIXME: probably not here. */
824 /* See also gtk_widget_create_pango_context (). */
825 sheet->last_zoom_factor_used = -1; /* Overridden later */
826 context = gnm_pango_context_get ();
827 sheet->rendered_values = gnm_rvc_new (context, 5000);
828 g_object_unref (context);
830 /* Init menu states */
831 sheet->priv->enable_showhide_detail = TRUE;
833 sheet->names = gnm_named_expr_collection_new ();
834 sheet->style_data = NULL;
836 sheet->index_in_wb = -1;
839 static Sheet the_invalid_sheet;
840 Sheet *invalid_sheet = &the_invalid_sheet;
842 static void
843 gnm_sheet_class_init (GObjectClass *gobject_class)
845 if (GNM_MAX_COLS > 364238) {
846 /* Oh, yeah? */
847 g_warning (_("This is a special version of Gnumeric. It has been compiled\n"
848 "with support for a very large number of columns. Access to the\n"
849 "column named TRUE may conflict with the constant of the same\n"
850 "name. Expect weirdness."));
853 parent_class = g_type_class_peek_parent (gobject_class);
855 gobject_class->set_property = gnm_sheet_set_property;
856 gobject_class->get_property = gnm_sheet_get_property;
857 gobject_class->finalize = gnm_sheet_finalize;
858 gobject_class->constructed = gnm_sheet_constructed;
860 g_object_class_install_property (gobject_class, PROP_SHEET_TYPE,
861 g_param_spec_enum ("sheet-type",
862 P_("Sheet Type"),
863 P_("Which type of sheet this is."),
864 GNM_SHEET_TYPE_TYPE,
865 GNM_SHEET_DATA,
866 GSF_PARAM_STATIC |
867 G_PARAM_READWRITE |
868 G_PARAM_CONSTRUCT_ONLY));
869 g_object_class_install_property (gobject_class, PROP_WORKBOOK,
870 g_param_spec_object ("workbook",
871 P_("Parent workbook"),
872 P_("The workbook in which this sheet lives"),
873 GNM_WORKBOOK_TYPE,
874 GSF_PARAM_STATIC |
875 G_PARAM_READWRITE |
876 G_PARAM_CONSTRUCT_ONLY));
877 g_object_class_install_property (gobject_class, PROP_NAME,
878 g_param_spec_string ("name",
879 P_("Name"),
880 P_("The name of the sheet."),
881 NULL,
882 GSF_PARAM_STATIC |
883 G_PARAM_READWRITE));
884 g_object_class_install_property (gobject_class, PROP_RTL,
885 g_param_spec_boolean ("text-is-rtl",
886 P_("text-is-rtl"),
887 P_("Text goes from right to left."),
888 FALSE,
889 GSF_PARAM_STATIC |
890 G_PARAM_READWRITE));
891 g_object_class_install_property (gobject_class, PROP_VISIBILITY,
892 g_param_spec_enum ("visibility",
893 P_("Visibility"),
894 P_("How visible the sheet is."),
895 GNM_SHEET_VISIBILITY_TYPE,
896 GNM_SHEET_VISIBILITY_VISIBLE,
897 GSF_PARAM_STATIC |
898 G_PARAM_READWRITE));
899 g_object_class_install_property (gobject_class, PROP_DISPLAY_FORMULAS,
900 g_param_spec_boolean ("display-formulas",
901 P_("Display Formul\303\246"),
902 P_("Control whether formul\303\246 are shown instead of values."),
903 FALSE,
904 GSF_PARAM_STATIC |
905 G_PARAM_READWRITE));
906 g_object_class_install_property (gobject_class, PROP_DISPLAY_ZEROS,
907 g_param_spec_boolean ("display-zeros", _("Display Zeros"),
908 _("Control whether zeros are shown are blanked out."),
909 TRUE,
910 GSF_PARAM_STATIC |
911 G_PARAM_READWRITE));
912 g_object_class_install_property (gobject_class, PROP_DISPLAY_GRID,
913 g_param_spec_boolean ("display-grid", _("Display Grid"),
914 _("Control whether the grid is shown."),
915 TRUE,
916 GSF_PARAM_STATIC |
917 G_PARAM_READWRITE));
918 g_object_class_install_property (gobject_class, PROP_DISPLAY_COLUMN_HEADER,
919 g_param_spec_boolean ("display-column-header",
920 P_("Display Column Headers"),
921 P_("Control whether column headers are shown."),
922 FALSE,
923 GSF_PARAM_STATIC |
924 G_PARAM_READWRITE));
925 g_object_class_install_property (gobject_class, PROP_DISPLAY_ROW_HEADER,
926 g_param_spec_boolean ("display-row-header",
927 P_("Display Row Headers"),
928 P_("Control whether row headers are shown."),
929 FALSE,
930 GSF_PARAM_STATIC |
931 G_PARAM_READWRITE));
932 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES,
933 g_param_spec_boolean ("display-outlines",
934 P_("Display Outlines"),
935 P_("Control whether outlines are shown."),
936 TRUE,
937 GSF_PARAM_STATIC |
938 G_PARAM_READWRITE));
939 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES_BELOW,
940 g_param_spec_boolean ("display-outlines-below",
941 P_("Display Outlines Below"),
942 P_("Control whether outline symbols are shown below."),
943 TRUE,
944 GSF_PARAM_STATIC |
945 G_PARAM_READWRITE));
946 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES_RIGHT,
947 g_param_spec_boolean ("display-outlines-right",
948 P_("Display Outlines Right"),
949 P_("Control whether outline symbols are shown to the right."),
950 TRUE,
951 GSF_PARAM_STATIC |
952 G_PARAM_READWRITE));
954 g_object_class_install_property (gobject_class, PROP_PROTECTED,
955 g_param_spec_boolean ("protected",
956 P_("Protected"),
957 P_("Sheet is protected."),
958 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
959 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_OBJECTS,
960 g_param_spec_boolean ("protected-allow-edit-objects",
961 P_("Protected Allow Edit objects"),
962 P_("Allow objects to be edited while a sheet is protected"),
963 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
964 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_SCENARIOS,
965 g_param_spec_boolean ("protected-allow-edit-scenarios",
966 P_("Protected allow edit scenarios"),
967 P_("Allow scenarios to be edited while a sheet is protected"),
968 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
969 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_CELL_FORMATTING,
970 g_param_spec_boolean ("protected-allow-cell-formatting",
971 P_("Protected allow cell formatting"),
972 P_("Allow cell format changes while a sheet is protected"),
973 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
974 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_COLUMN_FORMATTING,
975 g_param_spec_boolean ("protected-allow-column-formatting",
976 P_("Protected allow column formatting"),
977 P_("Allow column formatting while a sheet is protected"),
978 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
979 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_ROW_FORMATTING,
980 g_param_spec_boolean ("protected-allow-row-formatting",
981 P_("Protected allow row formatting"),
982 P_("Allow row formatting while a sheet is protected"),
983 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
984 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_COLUMNS,
985 g_param_spec_boolean ("protected-allow-insert-columns",
986 P_("Protected allow insert columns"),
987 P_("Allow columns to be inserted while a sheet is protected"),
988 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
989 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_ROWS,
990 g_param_spec_boolean ("protected-allow-insert-rows",
991 P_("Protected allow insert rows"),
992 P_("Allow rows to be inserted while a sheet is protected"),
993 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
994 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS,
995 g_param_spec_boolean ("protected-allow-insert-hyperlinks",
996 P_("Protected allow insert hyperlinks"),
997 P_("Allow hyperlinks to be inserted while a sheet is protected"),
998 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
999 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_DELETE_COLUMNS,
1000 g_param_spec_boolean ("protected-allow-delete-columns",
1001 P_("Protected allow delete columns"),
1002 P_("Allow columns to be deleted while a sheet is protected"),
1003 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1004 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_DELETE_ROWS,
1005 g_param_spec_boolean ("protected-allow-delete-rows",
1006 P_("Protected allow delete rows"),
1007 P_("Allow rows to be deleted while a sheet is protected"),
1008 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1009 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS,
1010 g_param_spec_boolean ("protected-allow-select-locked-cells",
1011 P_("Protected allow select locked cells"),
1012 P_("Allow the user to select locked cells while a sheet is protected"),
1013 TRUE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1014 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SORT_RANGES,
1015 g_param_spec_boolean ("protected-allow-sort-ranges",
1016 P_("Protected allow sort ranges"),
1017 P_("Allow ranges to be sorted while a sheet is protected"),
1018 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1019 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS,
1020 g_param_spec_boolean ("protected-allow-edit-auto-filters",
1021 P_("Protected allow edit auto filters"),
1022 P_("Allow auto filters to be edited while a sheet is protected"),
1023 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1024 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE,
1025 g_param_spec_boolean ("protected-allow-edit-pivottable",
1026 P_("Protected allow edit pivottable"),
1027 P_("Allow pivottable to be edited while a sheet is protected"),
1028 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1029 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS,
1030 g_param_spec_boolean ("protected-allow-select-unlocked-cells",
1031 P_("Protected allow select unlocked cells"),
1032 P_("Allow the user to select unlocked cells while a sheet is protected"),
1033 TRUE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1035 g_object_class_install_property
1036 (gobject_class, PROP_CONVENTIONS,
1037 g_param_spec_boxed ("conventions",
1038 P_("Display convention for expressions (default Gnumeric A1)"),
1039 P_("How to format displayed expressions, (A1 vs R1C1, function names, ...)"),
1040 gnm_conventions_get_type (),
1041 GSF_PARAM_STATIC |
1042 G_PARAM_READWRITE));
1043 g_object_class_install_property (gobject_class, PROP_USE_R1C1, /* convenience wrapper to CONVENTIONS */
1044 g_param_spec_boolean ("use-r1c1",
1045 P_("Display convention for expressions as XLS_R1C1 vs default"),
1046 P_("How to format displayed expressions, (a convenience api)"),
1047 FALSE,
1048 GSF_PARAM_STATIC |
1049 G_PARAM_READWRITE));
1051 g_object_class_install_property (gobject_class, PROP_TAB_FOREGROUND,
1052 g_param_spec_boxed ("tab-foreground",
1053 P_("Tab Foreground"),
1054 P_("The foreground color of the tab."),
1055 GNM_COLOR_TYPE,
1056 GSF_PARAM_STATIC |
1057 G_PARAM_READWRITE));
1058 g_object_class_install_property (gobject_class, PROP_TAB_BACKGROUND,
1059 g_param_spec_boxed ("tab-background",
1060 P_("Tab Background"),
1061 P_("The background color of the tab."),
1062 GNM_COLOR_TYPE,
1063 GSF_PARAM_STATIC |
1064 G_PARAM_READWRITE));
1066 /* What is this doing in sheet? */
1067 g_object_class_install_property (gobject_class, PROP_ZOOM_FACTOR,
1068 g_param_spec_double ("zoom-factor",
1069 P_("Zoom Factor"),
1070 P_("The level of zoom used for this sheet."),
1071 0.1, 5.0,
1072 1.0,
1073 GSF_PARAM_STATIC |
1074 G_PARAM_CONSTRUCT |
1075 G_PARAM_READWRITE));
1077 g_object_class_install_property (gobject_class, PROP_COLUMNS,
1078 g_param_spec_int ("columns",
1079 P_("Columns"),
1080 P_("Columns number in the sheet"),
1081 0, GNM_MAX_COLS, GNM_DEFAULT_COLS,
1082 GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1084 g_object_class_install_property (gobject_class, PROP_ROWS,
1085 g_param_spec_int ("rows",
1086 P_("Rows"),
1087 P_("Rows number in the sheet"),
1088 0, GNM_MAX_ROWS, GNM_DEFAULT_ROWS,
1089 GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1091 signals[DETACHED_FROM_WORKBOOK] = g_signal_new
1092 ("detached_from_workbook",
1093 GNM_SHEET_TYPE,
1094 G_SIGNAL_RUN_LAST,
1095 G_STRUCT_OFFSET (GnmSheetClass, detached_from_workbook),
1096 NULL, NULL,
1097 g_cclosure_marshal_VOID__OBJECT,
1098 G_TYPE_NONE, 1, GNM_WORKBOOK_TYPE);
1102 GSF_CLASS (GnmSheet, gnm_sheet,
1103 gnm_sheet_class_init, gnm_sheet_init, G_TYPE_OBJECT)
1105 /* ------------------------------------------------------------------------- */
1107 GType
1108 gnm_sheet_type_get_type (void)
1110 static GType etype = 0;
1111 if (etype == 0) {
1112 static const GEnumValue values[] = {
1113 { GNM_SHEET_DATA, "GNM_SHEET_DATA", "data" },
1114 { GNM_SHEET_OBJECT, "GNM_SHEET_OBJECT", "object" },
1115 { GNM_SHEET_XLM, "GNM_SHEET_XLM", "xlm" },
1116 { 0, NULL, NULL }
1118 etype = g_enum_register_static ("GnmSheetType", values);
1120 return etype;
1123 GType
1124 gnm_sheet_visibility_get_type (void)
1126 static GType etype = 0;
1127 if (etype == 0) {
1128 static GEnumValue const values[] = {
1129 { GNM_SHEET_VISIBILITY_VISIBLE, "GNM_SHEET_VISIBILITY_VISIBLE", "visible" },
1130 { GNM_SHEET_VISIBILITY_HIDDEN, "GNM_SHEET_VISIBILITY_HIDDEN", "hidden" },
1131 { GNM_SHEET_VISIBILITY_VERY_HIDDEN, "GNM_SHEET_VISIBILITY_VERY_HIDDEN", "very-hidden" },
1132 { 0, NULL, NULL }
1134 etype = g_enum_register_static ("GnmSheetVisibility", values);
1136 return etype;
1139 /* ------------------------------------------------------------------------- */
1141 static gboolean
1142 powerof_2 (int i)
1144 return i > 0 && (i & (i - 1)) == 0;
1147 gboolean
1148 gnm_sheet_valid_size (int cols, int rows)
1150 return (cols >= GNM_MIN_COLS &&
1151 cols <= GNM_MAX_COLS &&
1152 powerof_2 (cols) &&
1153 rows >= GNM_MIN_ROWS &&
1154 rows <= GNM_MAX_ROWS &&
1155 powerof_2 (rows)
1156 #if 0
1157 && 0x80000000u / (unsigned)(cols / 2) >= (unsigned)rows
1158 #endif
1162 void
1163 gnm_sheet_suggest_size (int *cols, int *rows)
1165 int c = GNM_DEFAULT_COLS;
1166 int r = GNM_DEFAULT_ROWS;
1168 while (c < *cols && c < GNM_MAX_COLS)
1169 c *= 2;
1171 while (r < *rows && r < GNM_MAX_ROWS)
1172 r *= 2;
1174 while (!gnm_sheet_valid_size (c, r)) {
1175 /* Darn! Too large. */
1176 if (*cols >= GNM_MIN_COLS && c > GNM_MIN_COLS)
1177 c /= 2;
1178 else if (*rows >= GNM_MIN_ROWS && r > GNM_MIN_ROWS)
1179 r /= 2;
1180 else if (c > GNM_MIN_COLS)
1181 c /= 2;
1182 else
1183 r /= 2;
1186 *cols = c;
1187 *rows = r;
1190 static void gnm_sheet_resize_main (Sheet *sheet, int cols, int rows,
1191 GOCmdContext *cc, GOUndo **pundo);
1193 static void
1194 cb_sheet_resize (Sheet *sheet, const GnmSheetSize *data, GOCmdContext *cc)
1196 gnm_sheet_resize_main (sheet, data->max_cols, data->max_rows,
1197 cc, NULL);
1200 static void
1201 gnm_sheet_resize_main (Sheet *sheet, int cols, int rows,
1202 GOCmdContext *cc, GOUndo **pundo)
1204 int old_cols, old_rows;
1205 GnmStyle **common_col_styles = NULL;
1206 GnmStyle **common_row_styles = NULL;
1208 if (pundo) *pundo = NULL;
1210 old_cols = gnm_sheet_get_max_cols (sheet);
1211 old_rows = gnm_sheet_get_max_rows (sheet);
1212 if (old_cols == cols && old_rows == rows)
1213 return;
1215 /* ---------------------------------------- */
1216 /* Gather styles we want to copy into new areas. */
1218 if (cols > old_cols) {
1219 int r;
1220 common_row_styles = sheet_style_most_common (sheet, FALSE);
1221 for (r = 0; r < old_rows; r++)
1222 gnm_style_ref (common_row_styles[r]);
1224 if (rows > old_rows) {
1225 int c;
1226 common_col_styles = sheet_style_most_common (sheet, TRUE);
1227 for (c = 0; c < old_cols; c++)
1228 gnm_style_ref (common_col_styles[c]);
1231 /* ---------------------------------------- */
1232 /* Remove the columns and rows that will disappear. */
1234 if (cols < old_cols) {
1235 GOUndo *u = NULL;
1236 gboolean err;
1238 err = sheet_delete_cols (sheet, cols, G_MAXINT,
1239 pundo ? &u : NULL, cc);
1240 if (pundo)
1241 *pundo = go_undo_combine (*pundo, u);
1242 if (err)
1243 goto handle_error;
1246 if (rows < old_rows) {
1247 GOUndo *u = NULL;
1248 gboolean err;
1250 err = sheet_delete_rows (sheet, rows, G_MAXINT,
1251 pundo ? &u : NULL, cc);
1252 if (pundo)
1253 *pundo = go_undo_combine (*pundo, u);
1254 if (err)
1255 goto handle_error;
1258 /* ---------------------------------------- */
1259 /* Restrict selection. (Not undone.) */
1261 SHEET_FOREACH_VIEW (sheet, sv,
1263 GnmRange new_full;
1264 GSList *l;
1265 GSList *sel = selection_get_ranges (sv, TRUE);
1266 gboolean any = FALSE;
1267 GnmCellPos vis;
1268 sv_selection_reset (sv);
1269 range_init (&new_full, 0, 0, cols - 1, rows - 1);
1270 vis = new_full.start;
1271 for (l = sel; l; l = l->next) {
1272 GnmRange *r = l->data;
1273 GnmRange newr;
1274 if (range_intersection (&newr, r, &new_full)) {
1275 sv_selection_add_range (sv, &newr);
1276 vis = newr.start;
1277 any = TRUE;
1279 g_free (r);
1281 g_slist_free (sel);
1282 if (!any)
1283 sv_selection_add_pos (sv, 0, 0,
1284 GNM_SELECTION_MODE_ADD);
1285 sv_make_cell_visible (sv, vis.col, vis.row, FALSE);
1288 /* ---------------------------------------- */
1289 /* Resize column and row containers. */
1291 colrow_resize (&sheet->cols, cols);
1292 colrow_resize (&sheet->rows, rows);
1294 /* ---------------------------------------- */
1295 /* Resize the dependency containers. */
1298 GSList *l, *linked = NULL;
1299 /* FIXME: what about dependents in other workbooks? */
1300 WORKBOOK_FOREACH_DEPENDENT
1301 (sheet->workbook, dep,
1303 if (dependent_is_linked (dep)) {
1304 dependent_unlink (dep);
1305 linked = g_slist_prepend (linked, dep);
1308 gnm_dep_container_resize (sheet->deps, rows);
1310 for (l = linked; l; l = l->next) {
1311 GnmDependent *dep = l->data;
1312 dependent_link (dep);
1315 g_slist_free (linked);
1317 workbook_queue_all_recalc (sheet->workbook);
1320 /* ---------------------------------------- */
1321 /* Resize the styles. */
1323 sheet_style_resize (sheet, cols, rows);
1325 /* ---------------------------------------- */
1326 /* Actually change the properties. */
1328 sheet->size.max_cols = cols;
1329 sheet->cols.max_used = MIN (sheet->cols.max_used, cols - 1);
1330 sheet->size.max_rows = rows;
1331 sheet->rows.max_used = MIN (sheet->rows.max_used, rows - 1);
1333 if (old_cols != cols)
1334 g_object_notify (G_OBJECT (sheet), "columns");
1335 if (old_rows != rows)
1336 g_object_notify (G_OBJECT (sheet), "rows");
1338 if (pundo) {
1339 GnmSheetSize *data = g_new (GnmSheetSize, 1);
1340 GOUndo *u;
1342 data->max_cols = old_cols;
1343 data->max_rows = old_rows;
1344 u = go_undo_binary_new (sheet, data,
1345 (GOUndoBinaryFunc)cb_sheet_resize,
1346 NULL, g_free);
1347 *pundo = go_undo_combine (*pundo, u);
1350 range_init_full_sheet (&sheet->priv->unhidden_region, sheet);
1352 /* ---------------------------------------- */
1353 /* Apply styles to new areas. */
1355 if (cols > old_cols) {
1356 int r = 0;
1357 while (r < old_rows) {
1358 int r2 = r;
1359 GnmStyle *mstyle = common_row_styles[r];
1360 GnmRange rng;
1361 while (r2 + 1 < old_rows &&
1362 mstyle == common_row_styles[r2 + 1])
1363 r2++;
1364 range_init (&rng, old_cols, r, cols - 1, r2);
1365 gnm_style_ref (mstyle);
1366 sheet_apply_style (sheet, &rng, mstyle);
1367 r = r2 + 1;
1370 for (r = 0; r < old_rows; r++)
1371 gnm_style_unref (common_row_styles[r]);
1373 g_free (common_row_styles);
1376 if (rows > old_rows) {
1377 int c = 0;
1379 while (c < old_cols) {
1380 int c2 = c;
1381 GnmStyle *mstyle = common_col_styles[c];
1382 GnmRange rng;
1383 while (c2 + 1 < old_cols &&
1384 mstyle == common_col_styles[c2 + 1])
1385 c2++;
1386 range_init (&rng, c, old_rows, c2, rows - 1);
1387 gnm_style_ref (mstyle);
1388 sheet_apply_style (sheet, &rng, mstyle);
1389 c = c2 + 1;
1392 if (cols > old_cols) {
1394 * Expanded in both directions. One could argue about
1395 * what style to use down here, but we choose the
1396 * last column style.
1398 GnmStyle *mstyle = common_col_styles[old_cols - 1];
1399 GnmRange rng;
1401 range_init (&rng,
1402 old_cols, old_rows,
1403 cols - 1, rows - 1);
1404 gnm_style_ref (mstyle);
1405 sheet_apply_style (sheet, &rng, mstyle);
1408 for (c = 0; c < old_cols; c++)
1409 gnm_style_unref (common_col_styles[c]);
1410 g_free (common_col_styles);
1413 /* ---------------------------------------- */
1415 sheet_redraw_all (sheet, TRUE);
1416 return;
1418 handle_error:
1419 if (pundo) {
1420 go_undo_undo_with_data (*pundo, cc);
1421 g_object_unref (*pundo);
1422 *pundo = NULL;
1427 * gnm_sheet_resize:
1428 * @sheet: #Sheet
1429 * @cols: the new columns number.
1430 * @rows: the new rows number.
1431 * @cc: #GOCmdContext.
1432 * @perr: will be %TRUE on error.
1434 * Returns: (transfer full): the newly allocated #GOUndo.
1436 GOUndo *
1437 gnm_sheet_resize (Sheet *sheet, int cols, int rows,
1438 GOCmdContext *cc, gboolean *perr)
1440 GOUndo *undo = NULL;
1442 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1443 g_return_val_if_fail (gnm_sheet_valid_size (cols, rows), NULL);
1445 if (cols < sheet->size.max_cols || rows < sheet->size.max_rows) {
1446 GSList *overlap, *l;
1447 gboolean bad = FALSE;
1448 GnmRange r;
1450 r.start.col = r.start.row = 0;
1451 r.end.col = MIN (cols, sheet->size.max_cols) - 1;
1452 r.end.row = MIN (rows, sheet->size.max_rows) - 1;
1454 overlap = gnm_sheet_merge_get_overlap (sheet, &r);
1455 for (l = overlap; l && !bad; l = l->next) {
1456 GnmRange const *m = l->data;
1457 if (!range_contained (m, &r)) {
1458 bad = TRUE;
1459 gnm_cmd_context_error_splits_merge (cc, m);
1462 g_slist_free (overlap);
1463 if (bad) {
1464 *perr = TRUE;
1465 return NULL;
1469 gnm_sheet_resize_main (sheet, cols, rows, cc, &undo);
1471 *perr = FALSE;
1472 return undo;
1477 * sheet_new_with_type:
1478 * @wb: #Workbook
1479 * @name: An unquoted name
1480 * @type: @GnmSheetType
1481 * @columns: The number of columns for the sheet
1482 * @rows: The number of rows for the sheet
1484 * Create a new Sheet of type @type, and associate it with @wb.
1485 * The type cannot be changed later.
1486 * Returns: (transfer full): the newly allocated sheet.
1488 Sheet *
1489 sheet_new_with_type (Workbook *wb, char const *name, GnmSheetType type,
1490 int columns, int rows)
1492 Sheet *sheet;
1494 g_return_val_if_fail (wb != NULL, NULL);
1495 g_return_val_if_fail (name != NULL, NULL);
1496 g_return_val_if_fail (gnm_sheet_valid_size (columns, rows), NULL);
1498 sheet = g_object_new (GNM_SHEET_TYPE,
1499 "workbook", wb,
1500 "sheet-type", type,
1501 "columns", columns,
1502 "rows", rows,
1503 "name", name,
1504 "zoom-factor", gnm_conf_get_core_gui_window_zoom (),
1505 NULL);
1507 if (type == GNM_SHEET_OBJECT)
1508 print_info_set_paper_orientation (sheet->print_info, GTK_PAGE_ORIENTATION_LANDSCAPE);
1510 return sheet;
1514 * sheet_new:
1515 * @wb: #Workbook
1516 * @name: The name for the sheet (unquoted).
1517 * @columns: The requested columns number.
1518 * @rows: The requested rows number.
1520 * Create a new Sheet of type SHEET_DATA, and associate it with @wb.
1521 * The type can not be changed later
1522 * Returns: (transfer full): the newly allocated sheet.
1524 Sheet *
1525 sheet_new (Workbook *wb, char const *name, int columns, int rows)
1527 return sheet_new_with_type (wb, name, GNM_SHEET_DATA, columns, rows);
1530 /****************************************************************************/
1532 void
1533 sheet_redraw_all (Sheet const *sheet, gboolean headers)
1535 /* We potentially do a lot of recalcs as part of this, so make sure
1536 stuff that caches sub-computations see the whole thing instead
1537 of clearing between cells. */
1538 gnm_app_recalc_start ();
1539 SHEET_FOREACH_CONTROL (sheet, view, control,
1540 sc_redraw_all (control, headers););
1541 gnm_app_recalc_finish ();
1544 static GnmValue *
1545 cb_clear_rendered_values (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
1547 gnm_cell_unrender (iter->cell);
1548 return NULL;
1552 * sheet_range_calc_spans:
1553 * @sheet: The sheet,
1554 * @r: the region to update.
1555 * @flags:
1557 * This is used to re-calculate cell dimensions and re-render
1558 * a cell's text. eg. if a format has changed we need to re-render
1559 * the cached version of the rendered text in the cell.
1561 void
1562 sheet_range_calc_spans (Sheet *sheet, GnmRange const *r, GnmSpanCalcFlags flags)
1564 if (flags & GNM_SPANCALC_RE_RENDER)
1565 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_NONEXISTENT,
1566 r->start.col, r->start.row, r->end.col, r->end.row,
1567 cb_clear_rendered_values, NULL);
1568 sheet_queue_respan (sheet, r->start.row, r->end.row);
1570 /* Redraw the new region in case the span changes */
1571 sheet_redraw_range (sheet, r);
1574 static void
1575 sheet_redraw_partial_row (Sheet const *sheet, int const row,
1576 int const start_col, int const end_col)
1578 GnmRange r;
1579 range_init (&r, start_col, row, end_col, row);
1580 SHEET_FOREACH_CONTROL (sheet, view, control,
1581 sc_redraw_range (control, &r););
1584 static void
1585 sheet_redraw_cell (GnmCell const *cell)
1587 CellSpanInfo const * span;
1588 int start_col, end_col, row;
1589 GnmRange const *merged;
1590 Sheet *sheet;
1591 ColRowInfo *ri;
1593 g_return_if_fail (cell != NULL);
1595 sheet = cell->base.sheet;
1596 merged = gnm_sheet_merge_is_corner (sheet, &cell->pos);
1597 if (merged != NULL) {
1598 SHEET_FOREACH_CONTROL (sheet, view, control,
1599 sc_redraw_range (control, merged););
1600 return;
1603 row = cell->pos.row;
1604 start_col = end_col = cell->pos.col;
1605 ri = sheet_row_get (sheet, row);
1606 span = row_span_get (ri, start_col);
1608 if (span) {
1609 start_col = span->left;
1610 end_col = span->right;
1613 sheet_redraw_partial_row (sheet, row, start_col, end_col);
1616 static void
1617 sheet_cell_calc_span (GnmCell *cell, GnmSpanCalcFlags flags)
1619 CellSpanInfo const * span;
1620 int left, right;
1621 int min_col, max_col, row;
1622 gboolean render = (flags & GNM_SPANCALC_RE_RENDER) != 0;
1623 gboolean const resize = (flags & GNM_SPANCALC_RESIZE) != 0;
1624 gboolean existing = FALSE;
1625 GnmRange const *merged;
1626 Sheet *sheet;
1627 ColRowInfo *ri;
1629 g_return_if_fail (cell != NULL);
1631 sheet = cell->base.sheet;
1632 row = cell->pos.row;
1634 /* Render & Size any unrendered cells */
1635 if ((flags & GNM_SPANCALC_RENDER) && gnm_cell_get_rendered_value (cell) == NULL)
1636 render = TRUE;
1638 if (render) {
1639 if (!gnm_cell_has_expr (cell))
1640 gnm_cell_render_value ((GnmCell *)cell, TRUE);
1641 else
1642 gnm_cell_unrender (cell);
1643 } else if (resize) {
1644 /* FIXME: what was wanted here? */
1645 /* rendered_value_calc_size (cell); */
1648 /* Is there an existing span ? clear it BEFORE calculating new one */
1649 ri = sheet_row_get (sheet, row);
1650 span = row_span_get (ri, cell->pos.col);
1651 if (span != NULL) {
1652 GnmCell const * const other = span->cell;
1654 min_col = span->left;
1655 max_col = span->right;
1657 /* A different cell used to span into this cell, respan that */
1658 if (cell != other) {
1659 int other_left, other_right;
1661 cell_unregister_span (other);
1662 cell_calc_span (other, &other_left, &other_right);
1663 if (min_col > other_left)
1664 min_col = other_left;
1665 if (max_col < other_right)
1666 max_col = other_right;
1668 if (other_left != other_right)
1669 cell_register_span (other, other_left, other_right);
1670 } else
1671 existing = TRUE;
1672 } else
1673 min_col = max_col = cell->pos.col;
1675 merged = gnm_sheet_merge_is_corner (sheet, &cell->pos);
1676 if (NULL != merged) {
1677 if (existing) {
1678 if (min_col > merged->start.col)
1679 min_col = merged->start.col;
1680 if (max_col < merged->end.col)
1681 max_col = merged->end.col;
1682 } else {
1683 sheet_redraw_cell (cell);
1684 return;
1686 } else {
1687 /* Calculate the span of the cell */
1688 cell_calc_span (cell, &left, &right);
1689 if (min_col > left)
1690 min_col = left;
1691 if (max_col < right)
1692 max_col = right;
1694 /* This cell already had an existing span */
1695 if (existing) {
1696 /* If it changed, remove the old one */
1697 if (left != span->left || right != span->right)
1698 cell_unregister_span (cell);
1699 else
1700 /* unchanged, short curcuit adding the span again */
1701 left = right;
1704 if (left != right)
1705 cell_register_span (cell, left, right);
1708 sheet_redraw_partial_row (sheet, row, min_col, max_col);
1712 * sheet_apply_style: (skip)
1713 * @sheet: the sheet in which can be found
1714 * @range: the range to which should be applied
1715 * @style: (transfer full): A #GnmStyle partial style
1717 * A mid level routine that applies the supplied partial style @style to the
1718 * target @range and performs the necessary respanning and redrawing.
1720 void
1721 sheet_apply_style (Sheet *sheet,
1722 GnmRange const *range,
1723 GnmStyle *style)
1725 GnmSpanCalcFlags spanflags = gnm_style_required_spanflags (style);
1726 sheet_style_apply_range (sheet, range, style);
1727 /* This also redraws the range: */
1728 sheet_range_calc_spans (sheet, range, spanflags);
1732 * sheet_apply_style_gi: (rename-to sheet_apply_style)
1733 * @sheet: the sheet in which can be found
1734 * @range: the range to which should be applied
1735 * @style: A #GnmStyle partial style
1737 * A mid level routine that applies the supplied partial style @style to the
1738 * target @range and performs the necessary respanning and redrawing.
1740 void
1741 sheet_apply_style_gi (Sheet *sheet, GnmRange const *range, GnmStyle *style)
1743 GnmSpanCalcFlags spanflags = gnm_style_required_spanflags (style);
1744 gnm_style_ref (style);
1745 sheet_style_apply_range (sheet, range, style);
1746 /* This also redraws the range: */
1747 sheet_range_calc_spans (sheet, range, spanflags);
1750 static void
1751 sheet_apply_style_cb (GnmSheetRange *sr,
1752 GnmStyle *style)
1754 gnm_style_ref (style);
1755 sheet_apply_style (sr->sheet, &sr->range, style);
1756 sheet_flag_style_update_range (sr->sheet, &sr->range);
1760 * sheet_apply_style_undo:
1761 * @sr: #GnmSheetRange
1762 * @style: #GnmStyle
1764 * Returns: (transfer full): the new #GOUndo.
1766 GOUndo *
1767 sheet_apply_style_undo (GnmSheetRange *sr,
1768 GnmStyle *style)
1770 gnm_style_ref (style);
1771 return go_undo_binary_new
1772 (sr, (gpointer)style,
1773 (GOUndoBinaryFunc) sheet_apply_style_cb,
1774 (GFreeFunc) gnm_sheet_range_free,
1775 (GFreeFunc) gnm_style_unref);
1780 void
1781 sheet_apply_border (Sheet *sheet,
1782 GnmRange const *range,
1783 GnmBorder **borders)
1785 GnmSpanCalcFlags spanflags = GNM_SPANCALC_RE_RENDER | GNM_SPANCALC_RESIZE;
1786 sheet_style_apply_border (sheet, range, borders);
1787 /* This also redraws the range: */
1788 sheet_range_calc_spans (sheet, range, spanflags);
1791 /****************************************************************************/
1793 static ColRowInfo *
1794 sheet_row_new (Sheet *sheet)
1796 ColRowInfo *ri;
1798 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1800 ri = col_row_info_new ();
1801 *ri = sheet->rows.default_style;
1802 ri->is_default = FALSE;
1803 ri->needs_respan = TRUE;
1805 return ri;
1808 static ColRowInfo *
1809 sheet_col_new (Sheet *sheet)
1811 ColRowInfo *ci;
1813 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1815 ci = col_row_info_new ();
1816 *ci = sheet->cols.default_style;
1817 ci->is_default = FALSE;
1819 return ci;
1822 static void
1823 sheet_colrow_add (Sheet *sheet, ColRowInfo *cp, gboolean is_cols, int n)
1825 ColRowCollection *info = is_cols ? &sheet->cols : &sheet->rows;
1826 ColRowSegment **psegment = (ColRowSegment **)&COLROW_GET_SEGMENT (info, n);
1828 g_return_if_fail (n >= 0);
1829 g_return_if_fail (n < colrow_max (is_cols, sheet));
1831 if (*psegment == NULL)
1832 *psegment = g_new0 (ColRowSegment, 1);
1833 colrow_free ((*psegment)->info[COLROW_SUB_INDEX (n)]);
1834 (*psegment)->info[COLROW_SUB_INDEX (n)] = cp;
1836 if (cp->outline_level > info->max_outline_level)
1837 info->max_outline_level = cp->outline_level;
1838 if (n > info->max_used) {
1839 info->max_used = n;
1840 sheet->priv->resize_scrollbar = TRUE;
1844 static void
1845 sheet_reposition_objects (Sheet const *sheet, GnmCellPos const *pos)
1847 GSList *ptr;
1848 for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = ptr->next )
1849 sheet_object_update_bounds (GNM_SO (ptr->data), pos);
1853 * sheet_flag_status_update_cell:
1854 * @cell: The cell that has changed.
1856 * flag the sheet as requiring an update to the status display
1857 * if the supplied cell location is the edit cursor, or part of the
1858 * selected region.
1860 * Will cause the format toolbar, the edit area, and the auto expressions to be
1861 * updated if appropriate.
1863 void
1864 sheet_flag_status_update_cell (GnmCell const *cell)
1866 SHEET_FOREACH_VIEW (cell->base.sheet, sv,
1867 sv_flag_status_update_pos (sv, &cell->pos););
1871 * sheet_flag_status_update_range:
1872 * @sheet:
1873 * @range: If NULL then force an update.
1875 * flag the sheet as requiring an update to the status display
1876 * if the supplied cell location contains the edit cursor, or intersects of
1877 * the selected region.
1879 * Will cause the format toolbar, the edit area, and the auto expressions to be
1880 * updated if appropriate.
1882 void
1883 sheet_flag_status_update_range (Sheet const *sheet, GnmRange const *range)
1885 SHEET_FOREACH_VIEW (sheet, sv,
1886 sv_flag_status_update_range (sv, range););
1890 * sheet_flag_style_update_range:
1891 * @sheet: The sheet being changed
1892 * @range: the range that is changing.
1894 * Flag format changes that will require updating the format indicators.
1896 void
1897 sheet_flag_style_update_range (Sheet const *sheet, GnmRange const *range)
1899 SHEET_FOREACH_VIEW (sheet, sv,
1900 sv_flag_style_update_range (sv, range););
1904 * sheet_flag_recompute_spans:
1905 * @sheet:
1907 * Flag the sheet as requiring a full span recomputation the next time
1908 * sheet_update is called.
1910 void
1911 sheet_flag_recompute_spans (Sheet const *sheet)
1913 sheet->priv->recompute_spans = TRUE;
1916 static gboolean
1917 cb_outline_level (GnmColRowIter const *iter, int *outline_level)
1919 if (*outline_level < iter->cri->outline_level)
1920 *outline_level = iter->cri->outline_level;
1921 return FALSE;
1925 * sheet_colrow_fit_gutter:
1926 * @sheet: Sheet to change for.
1927 * @is_cols: Column gutter or row gutter?
1929 * Find the current max outline level.
1931 static int
1932 sheet_colrow_fit_gutter (Sheet const *sheet, gboolean is_cols)
1934 int outline_level = 0;
1935 colrow_foreach (is_cols ? &sheet->cols : &sheet->rows,
1936 0, colrow_max (is_cols, sheet) - 1,
1937 (ColRowHandler)cb_outline_level, &outline_level);
1938 return outline_level;
1942 * sheet_update_only_grid:
1943 * @sheet: #Sheet
1945 * Should be called after a logical command has finished processing
1946 * to request redraws for any pending events
1948 void
1949 sheet_update_only_grid (Sheet const *sheet)
1951 SheetPrivate *p;
1953 g_return_if_fail (IS_SHEET (sheet));
1955 p = sheet->priv;
1957 /* be careful these can toggle flags */
1958 if (p->recompute_max_col_group) {
1959 sheet_colrow_gutter ((Sheet *)sheet, TRUE,
1960 sheet_colrow_fit_gutter (sheet, TRUE));
1961 sheet->priv->recompute_max_col_group = FALSE;
1963 if (p->recompute_max_row_group) {
1964 sheet_colrow_gutter ((Sheet *)sheet, FALSE,
1965 sheet_colrow_fit_gutter (sheet, FALSE));
1966 sheet->priv->recompute_max_row_group = FALSE;
1969 SHEET_FOREACH_VIEW (sheet, sv, {
1970 if (sv->reposition_selection) {
1971 sv->reposition_selection = FALSE;
1973 /* when moving we cleared the selection before
1974 * arriving in here.
1976 if (sv->selections != NULL)
1977 sv_selection_set (sv, &sv->edit_pos_real,
1978 sv->cursor.base_corner.col,
1979 sv->cursor.base_corner.row,
1980 sv->cursor.move_corner.col,
1981 sv->cursor.move_corner.row);
1985 if (p->recompute_spans) {
1986 p->recompute_spans = FALSE;
1987 /* FIXME : I would prefer to use GNM_SPANCALC_RENDER rather than
1988 * RE_RENDER. It only renders those cells which are not
1989 * rendered. The trouble is that when a col changes size we
1990 * need to rerender, but currently nothing marks that.
1992 * hmm, that suggests an approach. maybe I can install a per
1993 * col flag. Then add a flag clearing loop after the
1994 * sheet_calc_span.
1996 #if 0
1997 sheet_calc_spans (sheet, GNM_SPANCALC_RESIZE|GNM_SPANCALC_RE_RENDER |
1998 (p->recompute_visibility ?
1999 SPANCALC_NO_DRAW : GNM_SPANCALC_SIMPLE));
2000 #endif
2001 sheet_queue_respan (sheet, 0, gnm_sheet_get_last_row (sheet));
2004 if (p->reposition_objects.row < gnm_sheet_get_max_rows (sheet) ||
2005 p->reposition_objects.col < gnm_sheet_get_max_cols (sheet)) {
2006 SHEET_FOREACH_VIEW (sheet, sv, {
2007 if (!p->resize && sv_is_frozen (sv)) {
2008 if (p->reposition_objects.col < sv->unfrozen_top_left.col ||
2009 p->reposition_objects.row < sv->unfrozen_top_left.row) {
2010 SHEET_VIEW_FOREACH_CONTROL(sv, control,
2011 sc_resize (control, FALSE););
2015 sheet_reposition_objects (sheet, &p->reposition_objects);
2016 p->reposition_objects.row = gnm_sheet_get_max_rows (sheet);
2017 p->reposition_objects.col = gnm_sheet_get_max_cols (sheet);
2020 if (p->resize) {
2021 p->resize = FALSE;
2022 SHEET_FOREACH_CONTROL (sheet, sv, control, sc_resize (control, FALSE););
2025 if (p->recompute_visibility) {
2026 /* TODO : There is room for some opimization
2027 * We only need to force complete visibility recalculation
2028 * (which we do in sheet_compute_visible_region)
2029 * if a row or col before the start of the visible region.
2030 * If we are REALLY smart we could even accumulate the size differential
2031 * and use that.
2033 p->recompute_visibility = FALSE;
2034 p->resize_scrollbar = FALSE; /* compute_visible_region does this */
2035 SHEET_FOREACH_CONTROL(sheet, view, control,
2036 sc_recompute_visible_region (control, TRUE););
2037 sheet_redraw_all (sheet, TRUE);
2040 if (p->resize_scrollbar) {
2041 sheet_scrollbar_config (sheet);
2042 p->resize_scrollbar = FALSE;
2044 if (p->filters_changed) {
2045 p->filters_changed = FALSE;
2046 SHEET_FOREACH_CONTROL (sheet, sv, sc,
2047 wb_control_menu_state_update (sc_wbc (sc), MS_ADD_VS_REMOVE_FILTER););
2052 * sheet_update:
2053 * @sheet: #Sheet
2055 * Should be called after a logical command has finished processing to request
2056 * redraws for any pending events, and to update the various status regions
2058 void
2059 sheet_update (Sheet const *sheet)
2061 g_return_if_fail (IS_SHEET (sheet));
2063 sheet_update_only_grid (sheet);
2065 SHEET_FOREACH_VIEW (sheet, sv, sv_update (sv););
2069 * sheet_cell_get:
2070 * @sheet: The sheet where we want to locate the cell
2071 * @col: the cell column
2072 * @row: the cell row
2074 * Return value: (nullable): a #GnmCell, or %NULL if the cell does not exist
2076 GnmCell *
2077 sheet_cell_get (Sheet const *sheet, int col, int row)
2079 GnmCell *cell;
2080 GnmCell key;
2082 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2084 key.pos.col = col;
2085 key.pos.row = row;
2086 cell = g_hash_table_lookup (sheet->cell_hash, &key);
2088 return cell;
2092 * sheet_cell_fetch:
2093 * @sheet: The sheet where we want to locate the cell
2094 * @col: the cell column
2095 * @row: the cell row
2097 * Return value: a #GnmCell containing at (@col,@row).
2098 * If no cell existed at that location before, it is created.
2100 GnmCell *
2101 sheet_cell_fetch (Sheet *sheet, int col, int row)
2103 GnmCell *cell;
2105 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2107 cell = sheet_cell_get (sheet, col, row);
2108 if (!cell)
2109 cell = sheet_cell_create (sheet, col, row);
2111 return cell;
2115 * sheet_colrow_can_group:
2116 * @sheet: #Sheet
2117 * @r: A #GnmRange
2118 * @is_cols: boolean
2120 * Returns TRUE if the cols/rows in @r.start -> @r.end can be grouped, return
2121 * FALSE otherwise. You can invert the result if you need to find out if a
2122 * group can be ungrouped.
2124 gboolean
2125 sheet_colrow_can_group (Sheet *sheet, GnmRange const *r, gboolean is_cols)
2127 ColRowInfo const *start_cri, *end_cri;
2128 int start, end;
2130 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
2132 if (is_cols) {
2133 start = r->start.col;
2134 end = r->end.col;
2135 } else {
2136 start = r->start.row;
2137 end = r->end.row;
2139 start_cri = sheet_colrow_fetch (sheet, start, is_cols);
2140 end_cri = sheet_colrow_fetch (sheet, end, is_cols);
2142 /* Groups on outline level 0 (no outline) may always be formed */
2143 if (start_cri->outline_level == 0 || end_cri->outline_level == 0)
2144 return TRUE;
2146 /* We just won't group a group that already exists (or doesn't), it's useless */
2147 return (colrow_find_outline_bound (sheet, is_cols, start, start_cri->outline_level, FALSE) != start ||
2148 colrow_find_outline_bound (sheet, is_cols, end, end_cri->outline_level, TRUE) != end);
2151 gboolean
2152 sheet_colrow_group_ungroup (Sheet *sheet, GnmRange const *r,
2153 gboolean is_cols, gboolean group)
2155 int i, new_max, start, end;
2156 int const step = group ? 1 : -1;
2158 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
2160 /* Can we group/ungroup ? */
2161 if (group != sheet_colrow_can_group (sheet, r, is_cols))
2162 return FALSE;
2164 if (is_cols) {
2165 start = r->start.col;
2166 end = r->end.col;
2167 } else {
2168 start = r->start.row;
2169 end = r->end.row;
2172 /* Set new outline for each col/row and find highest outline level */
2173 new_max = (is_cols ? &sheet->cols : &sheet->rows)->max_outline_level;
2174 for (i = start; i <= end; i++) {
2175 ColRowInfo *cri = sheet_colrow_fetch (sheet, i, is_cols);
2176 int const new_level = cri->outline_level + step;
2178 if (new_level >= 0) {
2179 colrow_set_outline (cri, new_level, FALSE);
2180 if (new_max < new_level)
2181 new_max = new_level;
2185 if (!group)
2186 new_max = sheet_colrow_fit_gutter (sheet, is_cols);
2188 sheet_colrow_gutter (sheet, is_cols, new_max);
2189 SHEET_FOREACH_VIEW (sheet, sv,
2190 sv_redraw_headers (sv, is_cols, !is_cols, NULL););
2192 return TRUE;
2196 * sheet_colrow_gutter:
2197 * @sheet:
2198 * @is_cols:
2199 * @max_outline:
2201 * Set the maximum outline levels for cols or rows.
2203 void
2204 sheet_colrow_gutter (Sheet *sheet, gboolean is_cols, int max_outline)
2206 ColRowCollection *infos;
2208 g_return_if_fail (IS_SHEET (sheet));
2210 infos = is_cols ? &(sheet->cols) : &(sheet->rows);
2211 if (infos->max_outline_level != max_outline) {
2212 sheet->priv->resize = TRUE;
2213 infos->max_outline_level = max_outline;
2217 struct sheet_extent_data {
2218 GnmRange range;
2219 gboolean spans_and_merges_extend;
2220 gboolean ignore_empties;
2221 gboolean include_hidden;
2224 static void
2225 cb_sheet_get_extent (G_GNUC_UNUSED gpointer ignored, gpointer value, gpointer data)
2227 GnmCell const *cell = (GnmCell const *) value;
2228 struct sheet_extent_data *res = data;
2229 Sheet *sheet = cell->base.sheet;
2230 ColRowInfo *ri = NULL;
2232 if (res->ignore_empties && gnm_cell_is_empty (cell))
2233 return;
2234 if (!res->include_hidden) {
2235 ri = sheet_col_get (sheet, cell->pos.col);
2236 if (!ri->visible)
2237 return;
2238 ri = sheet_row_get (sheet, cell->pos.row);
2239 if (!ri->visible)
2240 return;
2243 /* Remember the first cell is the min & max */
2244 if (res->range.start.col > cell->pos.col)
2245 res->range.start.col = cell->pos.col;
2246 if (res->range.end.col < cell->pos.col)
2247 res->range.end.col = cell->pos.col;
2248 if (res->range.start.row > cell->pos.row)
2249 res->range.start.row = cell->pos.row;
2250 if (res->range.end.row < cell->pos.row)
2251 res->range.end.row = cell->pos.row;
2253 if (!res->spans_and_merges_extend)
2254 return;
2256 /* Cannot span AND merge */
2257 if (gnm_cell_is_merged (cell)) {
2258 GnmRange const *merged =
2259 gnm_sheet_merge_is_corner (sheet, &cell->pos);
2260 res->range = range_union (&res->range, merged);
2261 } else {
2262 CellSpanInfo const *span;
2263 if (ri == NULL)
2264 ri = sheet_row_get (sheet, cell->pos.row);
2265 if (ri->needs_respan)
2266 row_calc_spans (ri, cell->pos.row, sheet);
2267 span = row_span_get (ri, cell->pos.col);
2268 if (NULL != span) {
2269 if (res->range.start.col > span->left)
2270 res->range.start.col = span->left;
2271 if (res->range.end.col < span->right)
2272 res->range.end.col = span->right;
2278 * sheet_get_extent:
2279 * @sheet: the sheet
2280 * @spans_and_merges_extend: optionally extend region for spans and merges.
2281 * @include_hidden: whether to include the content of hidden cells.
2283 * calculates the area occupied by cell data.
2285 * NOTE: When spans_and_merges_extend is TRUE, this function will calculate
2286 * all spans. That might be expensive.
2288 * NOTE: This refers to *visible* contents. Cells with empty values, including
2289 * formulas with such values, are *ignored.
2291 * Return value: the range.
2293 GnmRange
2294 sheet_get_extent (Sheet const *sheet, gboolean spans_and_merges_extend, gboolean include_hidden)
2296 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2297 struct sheet_extent_data closure;
2298 GSList *ptr;
2300 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2302 closure.range.start.col = gnm_sheet_get_last_col (sheet) + 1;
2303 closure.range.start.row = gnm_sheet_get_last_row (sheet) + 1;
2304 closure.range.end.col = 0;
2305 closure.range.end.row = 0;
2306 closure.spans_and_merges_extend = spans_and_merges_extend;
2307 closure.include_hidden = include_hidden;
2308 closure.ignore_empties = TRUE;
2310 sheet_cell_foreach (sheet, &cb_sheet_get_extent, &closure);
2312 for (ptr = sheet->sheet_objects; ptr; ptr = ptr->next) {
2313 SheetObject *so = GNM_SO (ptr->data);
2315 closure.range.start.col = MIN (so->anchor.cell_bound.start.col,
2316 closure.range.start.col);
2317 closure.range.start.row = MIN (so->anchor.cell_bound.start.row,
2318 closure.range.start.row);
2319 closure.range.end.col = MAX (so->anchor.cell_bound.end.col,
2320 closure.range.end.col);
2321 closure.range.end.row = MAX (so->anchor.cell_bound.end.row,
2322 closure.range.end.row);
2325 if (closure.range.start.col > gnm_sheet_get_last_col (sheet))
2326 closure.range.start.col = 0;
2327 if (closure.range.start.row > gnm_sheet_get_last_row (sheet))
2328 closure.range.start.row = 0;
2329 if (closure.range.end.col < 0)
2330 closure.range.end.col = 0;
2331 if (closure.range.end.row < 0)
2332 closure.range.end.row = 0;
2334 return closure.range;
2338 * sheet_get_cells_extent:
2339 * @sheet: the sheet
2341 * calculates the area occupied by cells, including empty cells.
2343 * Return value: the range.
2345 GnmRange
2346 sheet_get_cells_extent (Sheet const *sheet)
2348 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2349 struct sheet_extent_data closure;
2351 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2353 closure.range.start.col = gnm_sheet_get_last_col (sheet);
2354 closure.range.start.row = gnm_sheet_get_last_row (sheet);
2355 closure.range.end.col = 0;
2356 closure.range.end.row = 0;
2357 closure.spans_and_merges_extend = FALSE;
2358 closure.include_hidden = TRUE;
2359 closure.ignore_empties = FALSE;
2361 sheet_cell_foreach (sheet, &cb_sheet_get_extent, &closure);
2363 return closure.range;
2367 GnmRange *
2368 sheet_get_nominal_printarea (Sheet const *sheet)
2370 GnmNamedExpr *nexpr;
2371 GnmValue *val;
2372 GnmParsePos pos;
2373 GnmRange *r;
2374 GnmRangeRef const *r_ref;
2375 gint max_rows;
2376 gint max_cols;
2378 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2380 parse_pos_init_sheet (&pos, sheet);
2381 nexpr = expr_name_lookup (&pos, "Print_Area");
2382 if (nexpr == NULL)
2383 return NULL;
2385 val = gnm_expr_top_get_range (nexpr->texpr);
2386 r_ref = val ? value_get_rangeref (val) : NULL;
2387 if (r_ref == NULL) {
2388 value_release (val);
2389 return NULL;
2392 r = g_new0 (GnmRange, 1);
2393 range_init_rangeref (r, r_ref);
2394 value_release (val);
2396 if (r->end.col >= (max_cols = gnm_sheet_get_max_cols (sheet)))
2397 r->end.col = max_cols - 1;
2398 if (r->end.row >= (max_rows = gnm_sheet_get_max_rows (sheet)))
2399 r->end.row = max_rows - 1;
2400 if (r->start.col < 0)
2401 r->start.col = 0;
2402 if (r->start.row < 0)
2403 r->start.row = 0;
2405 return r;
2408 GnmRange
2409 sheet_get_printarea (Sheet const *sheet,
2410 gboolean include_styles,
2411 gboolean ignore_printarea)
2413 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2414 GnmRange print_area;
2416 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2418 if (!ignore_printarea) {
2419 GnmRange *r = sheet_get_nominal_printarea (sheet);
2420 if (r != NULL) {
2421 print_area = *r;
2422 g_free (r);
2423 return print_area;
2427 print_area = sheet_get_extent (sheet, TRUE, FALSE);
2428 if (include_styles)
2429 sheet_style_get_extent (sheet, &print_area);
2431 return print_area;
2434 struct cb_fit {
2435 int max;
2436 gboolean ignore_strings;
2439 /* find the maximum width in a range. */
2440 static GnmValue *
2441 cb_max_cell_width (GnmCellIter const *iter, struct cb_fit *data)
2443 int width;
2444 GnmCell *cell = iter->cell;
2445 GnmRenderedValue *rv;
2447 if (gnm_cell_is_merged (cell))
2448 return NULL;
2451 * Special handling for manual recalc. We need to eval newly
2452 * entered expressions. gnm_cell_render_value will do that for us,
2453 * but we want to short-circuit some strings early.
2455 if (cell->base.flags & GNM_CELL_HAS_NEW_EXPR)
2456 gnm_cell_eval (cell);
2458 if (data->ignore_strings && VALUE_IS_STRING (cell->value))
2459 return NULL;
2461 /* Variable width cell must be re-rendered */
2462 rv = gnm_cell_get_rendered_value (cell);
2463 if (rv == NULL || rv->variable_width)
2464 gnm_cell_render_value (cell, FALSE);
2466 /* Make sure things are as-if drawn. */
2467 cell_finish_layout (cell, NULL, iter->ci->size_pixels, TRUE);
2469 width = gnm_cell_rendered_width (cell) + gnm_cell_rendered_offset (cell);
2470 if (width > data->max)
2471 data->max = width;
2473 return NULL;
2477 * sheet_col_size_fit_pixels:
2478 * @sheet: The sheet
2479 * @col: the column that we want to query
2480 * @srow: starting row.
2481 * @erow: ending row.
2482 * @ignore_strings: skip cells containing string values.
2484 * This routine computes the ideal size for the column to make the contents all
2485 * cells in the column visible.
2487 * Returns: Maximum size in pixels INCLUDING margins and grid lines
2488 * or 0 if there are no cells.
2491 sheet_col_size_fit_pixels (Sheet *sheet, int col, int srow, int erow,
2492 gboolean ignore_strings)
2494 struct cb_fit data;
2495 ColRowInfo *ci = sheet_col_get (sheet, col);
2496 if (ci == NULL)
2497 return 0;
2499 data.max = -1;
2500 data.ignore_strings = ignore_strings;
2501 sheet_foreach_cell_in_range (sheet,
2502 CELL_ITER_IGNORE_NONEXISTENT |
2503 CELL_ITER_IGNORE_HIDDEN |
2504 CELL_ITER_IGNORE_FILTERED,
2505 col, srow, col, erow,
2506 (CellIterFunc)&cb_max_cell_width, &data);
2508 /* Reset to the default width if the column was empty */
2509 if (data.max <= 0)
2510 return 0;
2512 /* GnmCell width does not include margins or far grid line*/
2513 return data.max + GNM_COL_MARGIN + GNM_COL_MARGIN + 1;
2516 /* find the maximum height in a range. */
2517 static GnmValue *
2518 cb_max_cell_height (GnmCellIter const *iter, struct cb_fit *data)
2520 int height;
2521 GnmCell *cell = iter->cell;
2523 if (gnm_cell_is_merged (cell))
2524 return NULL;
2527 * Special handling for manual recalc. We need to eval newly
2528 * entered expressions. gnm_cell_render_value will do that for us,
2529 * but we want to short-circuit some strings early.
2531 if (cell->base.flags & GNM_CELL_HAS_NEW_EXPR)
2532 gnm_cell_eval (cell);
2534 if (data->ignore_strings && VALUE_IS_STRING (cell->value))
2535 return NULL;
2537 if (!VALUE_IS_STRING (cell->value)) {
2539 * Mildly cheating to avoid performance problems, See bug
2540 * 359392. This assumes that non-strings do not wrap and
2541 * that they are all the same height, more or less.
2543 Sheet const *sheet = cell->base.sheet;
2544 height = gnm_style_get_pango_height (gnm_cell_get_style (cell),
2545 sheet->rendered_values->context,
2546 sheet->last_zoom_factor_used);
2547 } else {
2548 (void)gnm_cell_fetch_rendered_value (cell, TRUE);
2550 /* Make sure things are as-if drawn. Inhibit #####s. */
2551 cell_finish_layout (cell, NULL, iter->ci->size_pixels, FALSE);
2553 height = gnm_cell_rendered_height (cell);
2556 if (height > data->max)
2557 data->max = height;
2559 return NULL;
2563 * sheet_row_size_fit_pixels:
2564 * @sheet: The sheet
2565 * @row: the row that we want to query
2566 * @scol: starting column.
2567 * @ecol: ending column.
2568 * @ignore_strings: skip cells containing string values.
2570 * This routine computes the ideal size for the row to make all data fit
2571 * properly.
2573 * Returns: Maximum size in pixels INCLUDING margins and grid lines
2574 * or 0 if there are no cells.
2577 sheet_row_size_fit_pixels (Sheet *sheet, int row, int scol, int ecol,
2578 gboolean ignore_strings)
2580 struct cb_fit data;
2581 ColRowInfo const *ri = sheet_row_get (sheet, row);
2582 if (ri == NULL)
2583 return 0;
2585 data.max = -1;
2586 data.ignore_strings = ignore_strings;
2587 sheet_foreach_cell_in_range (sheet,
2588 CELL_ITER_IGNORE_NONEXISTENT |
2589 CELL_ITER_IGNORE_HIDDEN |
2590 CELL_ITER_IGNORE_FILTERED,
2591 scol, row,
2592 ecol, row,
2593 (CellIterFunc)&cb_max_cell_height, &data);
2595 /* Reset to the default width if the column was empty */
2596 if (data.max <= 0)
2597 return 0;
2599 /* GnmCell height does not include margins or bottom grid line */
2600 return data.max + GNM_ROW_MARGIN + GNM_ROW_MARGIN + 1;
2603 struct recalc_span_closure {
2604 Sheet *sheet;
2605 int col;
2608 static gboolean
2609 cb_recalc_spans_in_col (GnmColRowIter const *iter, gpointer user)
2611 struct recalc_span_closure *closure = user;
2612 int const col = closure->col;
2613 int left, right;
2614 CellSpanInfo const *span = row_span_get (iter->cri, col);
2616 if (span) {
2617 /* If there is an existing span see if it changed */
2618 GnmCell const * const cell = span->cell;
2619 cell_calc_span (cell, &left, &right);
2620 if (left != span->left || right != span->right) {
2621 cell_unregister_span (cell);
2622 cell_register_span (cell, left, right);
2624 } else {
2625 /* If there is a cell see if it started to span */
2626 GnmCell const * const cell = sheet_cell_get (closure->sheet, col, iter->pos);
2627 if (cell) {
2628 cell_calc_span (cell, &left, &right);
2629 if (left != right)
2630 cell_register_span (cell, left, right);
2634 return FALSE;
2638 * sheet_recompute_spans_for_col:
2639 * @sheet: the sheet
2640 * @col: The column that changed
2642 * This routine recomputes the column span for the cells that touches
2643 * the column.
2645 void
2646 sheet_recompute_spans_for_col (Sheet *sheet, int col)
2648 struct recalc_span_closure closure;
2649 closure.sheet = sheet;
2650 closure.col = col;
2652 colrow_foreach (&sheet->rows, 0, gnm_sheet_get_last_row (sheet),
2653 &cb_recalc_spans_in_col, &closure);
2656 /****************************************************************************/
2657 typedef struct {
2658 GnmValue *val;
2659 GnmExprTop const *texpr;
2660 GnmRange expr_bound;
2661 } closure_set_cell_value;
2663 static GnmValue *
2664 cb_set_cell_content (GnmCellIter const *iter, closure_set_cell_value *info)
2666 GnmExprTop const *texpr = info->texpr;
2667 GnmCell *cell;
2669 cell = iter->cell;
2670 if (!cell)
2671 cell = sheet_cell_create (iter->pp.sheet,
2672 iter->pp.eval.col,
2673 iter->pp.eval.row);
2676 * If we are overwriting an array, we need to clear things here
2677 * or gnm_cell_set_expr/gnm_cell_set_value will complain.
2679 if (cell->base.texpr && gnm_expr_top_is_array (cell->base.texpr))
2680 gnm_cell_cleanout (cell);
2682 if (texpr != NULL) {
2683 if (!range_contains (&info->expr_bound,
2684 iter->pp.eval.col, iter->pp.eval.row)) {
2685 GnmExprRelocateInfo rinfo;
2687 rinfo.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE;
2688 rinfo.pos = iter->pp;
2689 rinfo.origin.start = iter->pp.eval;
2690 rinfo.origin.end = iter->pp.eval;
2691 rinfo.origin_sheet = iter->pp.sheet;
2692 rinfo.target_sheet = iter->pp.sheet;
2693 rinfo.col_offset = 0;
2694 rinfo.row_offset = 0;
2695 texpr = gnm_expr_top_relocate (texpr, &rinfo, FALSE);
2698 gnm_cell_set_expr (cell, texpr);
2699 } else
2700 gnm_cell_set_value (cell, value_dup (info->val));
2701 return NULL;
2704 static GnmValue *
2705 cb_clear_non_corner (GnmCellIter const *iter, GnmRange const *merged)
2707 if (merged->start.col != iter->pp.eval.col ||
2708 merged->start.row != iter->pp.eval.row)
2709 gnm_cell_set_value (iter->cell, value_new_empty ());
2710 return NULL;
2714 * sheet_range_set_expr_cb:
2715 * @sr: #GnmSheetRange
2716 * @texpr: #GnmExprTop
2719 * Does NOT check for array division.
2721 static void
2722 sheet_range_set_expr_cb (GnmSheetRange const *sr, GnmExprTop const *texpr)
2724 closure_set_cell_value closure;
2725 GSList *merged, *ptr;
2727 g_return_if_fail (sr != NULL);
2728 g_return_if_fail (texpr != NULL);
2730 closure.texpr = texpr;
2731 gnm_expr_top_get_boundingbox (closure.texpr,
2732 sr->sheet,
2733 &closure.expr_bound);
2735 sheet_region_queue_recalc (sr->sheet, &sr->range);
2736 /* Store the parsed result creating any cells necessary */
2737 sheet_foreach_cell_in_range
2738 (sr->sheet, CELL_ITER_ALL,
2739 sr->range.start.col, sr->range.start.row,
2740 sr->range.end.col, sr->range.end.row,
2741 (CellIterFunc)&cb_set_cell_content, &closure);
2743 merged = gnm_sheet_merge_get_overlap (sr->sheet, &sr->range);
2744 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
2745 GnmRange const *tmp = ptr->data;
2746 sheet_foreach_cell_in_range
2747 (sr->sheet, CELL_ITER_IGNORE_BLANK,
2748 tmp->start.col, tmp->start.row,
2749 tmp->end.col, tmp->end.row,
2750 (CellIterFunc)&cb_clear_non_corner,
2751 (gpointer)tmp);
2753 g_slist_free (merged);
2755 sheet_region_queue_recalc (sr->sheet, &sr->range);
2756 sheet_flag_status_update_range (sr->sheet, &sr->range);
2757 sheet_queue_respan (sr->sheet, sr->range.start.row,
2758 sr->range.end.row);
2762 * sheet_range_set_expr_undo:
2763 * @sr: #GnmSheetRange
2764 * @texpr: #GnmExprTop
2766 * Returns: (transfer full): the newly created #GOUndo.
2768 GOUndo *
2769 sheet_range_set_expr_undo (GnmSheetRange *sr, GnmExprTop const *texpr)
2771 gnm_expr_top_ref (texpr);
2772 return go_undo_binary_new
2773 (sr, (gpointer)texpr,
2774 (GOUndoBinaryFunc) sheet_range_set_expr_cb,
2775 (GFreeFunc) gnm_sheet_range_free,
2776 (GFreeFunc) gnm_expr_top_unref);
2781 * sheet_range_set_text:
2782 * @pos: The position from which to parse an expression.
2783 * @r: The range to fill
2784 * @str: The text to be parsed and assigned.
2786 * Does NOT check for array division.
2787 * Does NOT redraw
2788 * Does NOT generate spans.
2790 void
2791 sheet_range_set_text (GnmParsePos const *pos, GnmRange const *r, char const *str)
2793 closure_set_cell_value closure;
2794 GSList *merged, *ptr;
2795 Sheet *sheet;
2797 g_return_if_fail (pos != NULL);
2798 g_return_if_fail (r != NULL);
2799 g_return_if_fail (str != NULL);
2801 sheet = pos->sheet;
2803 parse_text_value_or_expr (pos, str,
2804 &closure.val, &closure.texpr);
2806 if (closure.texpr)
2807 gnm_expr_top_get_boundingbox (closure.texpr,
2808 sheet,
2809 &closure.expr_bound);
2811 /* Store the parsed result creating any cells necessary */
2812 sheet_foreach_cell_in_range (sheet, CELL_ITER_ALL,
2813 r->start.col, r->start.row, r->end.col, r->end.row,
2814 (CellIterFunc)&cb_set_cell_content, &closure);
2816 merged = gnm_sheet_merge_get_overlap (sheet, r);
2817 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
2818 GnmRange const *tmp = ptr->data;
2819 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_BLANK,
2820 tmp->start.col, tmp->start.row,
2821 tmp->end.col, tmp->end.row,
2822 (CellIterFunc)&cb_clear_non_corner, (gpointer)tmp);
2824 g_slist_free (merged);
2826 sheet_region_queue_recalc (sheet, r);
2828 value_release (closure.val);
2829 if (closure.texpr)
2830 gnm_expr_top_unref (closure.texpr);
2832 sheet_flag_status_update_range (sheet, r);
2835 static void
2836 sheet_range_set_text_cb (GnmSheetRange const *sr, gchar const *text)
2838 GnmParsePos pos;
2840 pos.eval = sr->range.start;
2841 pos.sheet = sr->sheet;
2842 pos.wb = sr->sheet->workbook;
2844 sheet_range_set_text (&pos, &sr->range, text);
2845 sheet_region_queue_recalc (sr->sheet, &sr->range);
2846 sheet_flag_status_update_range (sr->sheet, &sr->range);
2847 sheet_queue_respan (sr->sheet, sr->range.start.row,
2848 sr->range.end.row);
2849 sheet_redraw_range (sr->sheet, &sr->range);
2853 * sheet_range_set_text_undo:
2854 * @sr: #GnmSheetRange
2855 * @text:
2857 * Returns: (transfer full): the newly created #GOUndo.
2859 GOUndo *
2860 sheet_range_set_text_undo (GnmSheetRange *sr,
2861 char const *text)
2863 return go_undo_binary_new
2864 (sr, g_strdup (text),
2865 (GOUndoBinaryFunc) sheet_range_set_text_cb,
2866 (GFreeFunc) gnm_sheet_range_free,
2867 (GFreeFunc) g_free);
2871 static GnmValue *
2872 cb_set_markup (GnmCellIter const *iter, PangoAttrList *markup)
2874 GnmCell *cell;
2876 cell = iter->cell;
2877 if (!cell)
2878 return NULL;
2880 if (VALUE_IS_STRING (cell->value)) {
2881 GOFormat *fmt;
2882 GnmValue *val = value_dup (cell->value);
2884 fmt = go_format_new_markup (markup, TRUE);
2885 value_set_fmt (val, fmt);
2886 go_format_unref (fmt);
2888 gnm_cell_cleanout (cell);
2889 gnm_cell_assign_value (cell, val);
2891 return NULL;
2894 static void
2895 sheet_range_set_markup_cb (GnmSheetRange const *sr, PangoAttrList *markup)
2897 sheet_foreach_cell_in_range
2898 (sr->sheet, CELL_ITER_ALL,
2899 sr->range.start.col, sr->range.start.row,
2900 sr->range.end.col, sr->range.end.row,
2901 (CellIterFunc)&cb_set_markup, markup);
2903 sheet_region_queue_recalc (sr->sheet, &sr->range);
2904 sheet_flag_status_update_range (sr->sheet, &sr->range);
2905 sheet_queue_respan (sr->sheet, sr->range.start.row,
2906 sr->range.end.row);
2910 * sheet_range_set_markup_undo:
2911 * @sr: #GnmSheetRange
2912 * @markup: #PangoAttrList
2914 * Returns: (transfer full): the newly created #GOUndo.
2916 GOUndo *
2917 sheet_range_set_markup_undo (GnmSheetRange *sr, PangoAttrList *markup)
2919 if (markup == NULL)
2920 return NULL;
2921 return go_undo_binary_new
2922 (sr, pango_attr_list_ref (markup),
2923 (GOUndoBinaryFunc) sheet_range_set_markup_cb,
2924 (GFreeFunc) gnm_sheet_range_free,
2925 (GFreeFunc) pango_attr_list_unref);
2929 * sheet_cell_get_value:
2930 * @sheet: Sheet
2931 * @col: Source column
2932 * @row: Source row
2934 * Returns: (transfer none) (nullable): the cell's current value.
2936 GnmValue const *
2937 sheet_cell_get_value (Sheet *sheet, int const col, int const row)
2939 GnmCell *cell;
2941 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2943 cell = sheet_cell_get (sheet, col, row);
2945 return cell ? cell->value : NULL;
2949 * sheet_cell_set_text:
2950 * @cell: A cell.
2951 * @str: the text to set.
2952 * @markup: (allow-none): an optional PangoAttrList.
2954 * Marks the sheet as dirty
2955 * Clears old spans.
2956 * Flags status updates
2957 * Queues recalcs
2959 void
2960 sheet_cell_set_text (GnmCell *cell, char const *text, PangoAttrList *markup)
2962 GnmExprTop const *texpr;
2963 GnmValue *val;
2964 GnmParsePos pp;
2966 g_return_if_fail (cell != NULL);
2967 g_return_if_fail (text != NULL);
2968 g_return_if_fail (!gnm_cell_is_nonsingleton_array (cell));
2970 parse_text_value_or_expr (parse_pos_init_cell (&pp, cell),
2971 text, &val, &texpr);
2973 /* Queue a redraw before in case the span changes */
2974 sheet_redraw_cell (cell);
2976 if (texpr != NULL) {
2977 gnm_cell_set_expr (cell, texpr);
2978 gnm_expr_top_unref (texpr);
2981 * Queue recalc before spanning. Otherwise spanning may
2982 * create a bogus rendered value, see #495879.
2984 cell_queue_recalc (cell);
2986 /* Clear spans from _other_ cells */
2987 sheet_cell_calc_span (cell, GNM_SPANCALC_SIMPLE);
2988 } else {
2989 g_return_if_fail (val != NULL);
2991 if (markup != NULL && VALUE_IS_STRING (val)) {
2992 gboolean quoted = (text[0] == '\'');
2993 PangoAttrList *adj_markup;
2994 GOFormat *fmt;
2996 if (quoted) {
2997 /* We ate the quote. Adjust. Ugh. */
2998 adj_markup = pango_attr_list_copy (markup);
2999 go_pango_attr_list_erase (adj_markup, 0, 1);
3000 } else
3001 adj_markup = markup;
3003 fmt = go_format_new_markup (adj_markup, TRUE);
3004 value_set_fmt (val, fmt);
3005 go_format_unref (fmt);
3006 if (quoted)
3007 pango_attr_list_unref (adj_markup);
3010 gnm_cell_set_value (cell, val);
3012 /* Queue recalc before spanning, see above. */
3013 cell_queue_recalc (cell);
3015 sheet_cell_calc_span (cell, GNM_SPANCALC_RESIZE | GNM_SPANCALC_RENDER);
3018 sheet_flag_status_update_cell (cell);
3022 * sheet_cell_set_text_gi: (rename-to sheet_cell_set_text)
3023 * @sheet: #Sheet
3024 * @col: column number
3025 * @row: row number
3026 * @str: the text to set.
3028 * Sets the contents of a cell.
3030 void
3031 sheet_cell_set_text_gi (Sheet *sheet, int col, int row, char const *str)
3033 sheet_cell_set_text (sheet_cell_fetch (sheet, col, row), str, NULL);
3038 * sheet_cell_set_expr:
3040 * Marks the sheet as dirty
3041 * Clears old spans.
3042 * Flags status updates
3043 * Queues recalcs
3045 void
3046 sheet_cell_set_expr (GnmCell *cell, GnmExprTop const *texpr)
3048 gnm_cell_set_expr (cell, texpr);
3050 /* clear spans from _other_ cells */
3051 sheet_cell_calc_span (cell, GNM_SPANCALC_SIMPLE);
3053 cell_queue_recalc (cell);
3054 sheet_flag_status_update_cell (cell);
3058 * sheet_cell_set_value: (skip)
3059 * @cell: #GnmCell
3060 * @v: (transfer full): #GnmValue
3062 * Stores, without copying, the supplied value. It marks the
3063 * sheet as dirty.
3065 * The value is rendered and spans are calculated. It queues a redraw
3066 * and checks to see if the edit region or selection content changed.
3068 * NOTE : This DOES check for array partitioning.
3070 void
3071 sheet_cell_set_value (GnmCell *cell, GnmValue *v)
3073 /* TODO : if the value is unchanged do not assign it */
3074 gnm_cell_set_value (cell, v);
3075 sheet_cell_calc_span (cell, GNM_SPANCALC_RESIZE | GNM_SPANCALC_RENDER);
3076 cell_queue_recalc (cell);
3077 sheet_flag_status_update_cell (cell);
3081 * sheet_cell_set_value_gi: (rename-to sheet_cell_set_value)
3082 * @sheet: #Sheet
3083 * @col: column number
3084 * @row: row number
3085 * @v: #GnmValue
3087 * Set the the value of the cell at (@col,@row) to @v.
3089 * The value is rendered and spans are calculated. It queues a redraw
3090 * and checks to see if the edit region or selection content changed.
3092 void
3093 sheet_cell_set_value_gi (Sheet *sheet, int col, int row, GnmValue *v)
3095 // This version exists because not all versions of pygobject
3096 // understand transfer-full parameters
3097 sheet_cell_set_value (sheet_cell_fetch (sheet, col, row),
3098 value_dup (v));
3101 /****************************************************************************/
3104 * This routine is used to queue the redraw regions for the
3105 * cell region specified.
3107 * It is usually called before a change happens to a region,
3108 * and after the change has been done to queue the regions
3109 * for the old contents and the new contents.
3111 * It intelligently handles spans and merged ranges
3113 void
3114 sheet_range_bounding_box (Sheet const *sheet, GnmRange *bound)
3116 GSList *ptr;
3117 int row;
3118 GnmRange r = *bound;
3120 g_return_if_fail (range_is_sane (bound));
3122 /* Check the first and last columns for spans and extend the region to
3123 * include the maximum extent.
3125 for (row = r.start.row; row <= r.end.row; row++){
3126 ColRowInfo const *ri = sheet_row_get (sheet, row);
3128 if (ri != NULL) {
3129 CellSpanInfo const * span0;
3131 if (ri->needs_respan)
3132 row_calc_spans ((ColRowInfo *)ri, row, sheet);
3134 span0 = row_span_get (ri, r.start.col);
3136 if (span0 != NULL) {
3137 if (bound->start.col > span0->left)
3138 bound->start.col = span0->left;
3139 if (bound->end.col < span0->right)
3140 bound->end.col = span0->right;
3142 if (r.start.col != r.end.col) {
3143 CellSpanInfo const * span1 =
3144 row_span_get (ri, r.end.col);
3146 if (span1 != NULL) {
3147 if (bound->start.col > span1->left)
3148 bound->start.col = span1->left;
3149 if (bound->end.col < span1->right)
3150 bound->end.col = span1->right;
3153 /* skip segments with no cells */
3154 } else if (row == COLROW_SEGMENT_START (row)) {
3155 ColRowSegment const * const segment =
3156 COLROW_GET_SEGMENT (&(sheet->rows), row);
3157 if (segment == NULL)
3158 row = COLROW_SEGMENT_END (row);
3162 /* TODO : this may get expensive if there are alot of merged ranges */
3163 /* no need to iterate, one pass is enough */
3164 for (ptr = sheet->list_merged ; ptr != NULL ; ptr = ptr->next) {
3165 GnmRange const * const test = ptr->data;
3166 if (r.start.row <= test->end.row || r.end.row >= test->start.row) {
3167 if (bound->start.col > test->start.col)
3168 bound->start.col = test->start.col;
3169 if (bound->end.col < test->end.col)
3170 bound->end.col = test->end.col;
3171 if (bound->start.row > test->start.row)
3172 bound->start.row = test->start.row;
3173 if (bound->end.row < test->end.row)
3174 bound->end.row = test->end.row;
3179 void
3180 sheet_redraw_region (Sheet const *sheet,
3181 int start_col, int start_row,
3182 int end_col, int end_row)
3184 GnmRange bound;
3186 g_return_if_fail (IS_SHEET (sheet));
3189 * Getting the bounding box causes row respans to be done if
3190 * needed. That can be expensive, so just redraw the whole
3191 * sheet if the row count is too big.
3193 if (end_row - start_row > 500) {
3194 sheet_redraw_all (sheet, FALSE);
3195 return;
3198 /* We potentially do a lot of recalcs as part of this, so make sure
3199 stuff that caches sub-computations see the whole thing instead
3200 of clearing between cells. */
3201 gnm_app_recalc_start ();
3203 sheet_range_bounding_box (sheet,
3204 range_init (&bound, start_col, start_row, end_col, end_row));
3205 SHEET_FOREACH_CONTROL (sheet, view, control,
3206 sc_redraw_range (control, &bound););
3208 gnm_app_recalc_finish ();
3211 void
3212 sheet_redraw_range (Sheet const *sheet, GnmRange const *range)
3214 g_return_if_fail (IS_SHEET (sheet));
3215 g_return_if_fail (range != NULL);
3217 sheet_redraw_region (sheet,
3218 range->start.col, range->start.row,
3219 range->end.col, range->end.row);
3222 /****************************************************************************/
3224 gboolean
3225 sheet_col_is_hidden (Sheet const *sheet, int col)
3227 ColRowInfo const * const res = sheet_col_get (sheet, col);
3228 return (res != NULL && !res->visible);
3231 gboolean
3232 sheet_row_is_hidden (Sheet const *sheet, int row)
3234 ColRowInfo const * const res = sheet_row_get (sheet, row);
3235 return (res != NULL && !res->visible);
3240 * sheet_find_boundary_horizontal:
3241 * @sheet: The Sheet
3242 * @col: The column from which to begin searching.
3243 * @move_row: The row in which to search for the edge of the range.
3244 * @base_row: The height of the area being moved.
3245 * @count: units to extend the selection vertically
3246 * @jump_to_boundaries: Jump to range boundaries.
3248 * Calculate the column index for the column which is @n units
3249 * from @start_col doing bounds checking. If @jump_to_boundaries is
3250 * TRUE @n must be 1 and the jump is to the edge of the logical range.
3252 * NOTE : This routine implements the logic necasary for ctrl-arrow style
3253 * movement. That is more compilcated than simply finding the last in a list
3254 * of cells with content. If you are at the end of a range it will find the
3255 * start of the next. Make sure that is the sort of behavior you want before
3256 * calling this.
3257 * Returns: the column inex.
3260 sheet_find_boundary_horizontal (Sheet *sheet, int start_col, int move_row,
3261 int base_row, int count,
3262 gboolean jump_to_boundaries)
3264 gboolean find_nonblank = sheet_is_cell_empty (sheet, start_col, move_row);
3265 gboolean keep_looking = FALSE;
3266 int new_col, prev_col, lagged_start_col, max_col = gnm_sheet_get_last_col (sheet);
3267 int iterations = 0;
3268 GnmRange check_merge;
3269 GnmRange const * const bound = &sheet->priv->unhidden_region;
3271 /* Jumping to bounds requires steping cell by cell */
3272 g_return_val_if_fail (count == 1 || count == -1 || !jump_to_boundaries, start_col);
3273 g_return_val_if_fail (IS_SHEET (sheet), start_col);
3275 if (move_row < base_row) {
3276 check_merge.start.row = move_row;
3277 check_merge.end.row = base_row;
3278 } else {
3279 check_merge.end.row = move_row;
3280 check_merge.start.row = base_row;
3283 do {
3284 GSList *merged, *ptr;
3286 lagged_start_col = check_merge.start.col = check_merge.end.col = start_col;
3287 merged = gnm_sheet_merge_get_overlap (sheet, &check_merge);
3288 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3289 GnmRange const * const r = ptr->data;
3290 if (count > 0) {
3291 if (start_col < r->end.col)
3292 start_col = r->end.col;
3293 } else {
3294 if (start_col > r->start.col)
3295 start_col = r->start.col;
3298 g_slist_free (merged);
3299 } while (start_col != lagged_start_col);
3300 new_col = prev_col = start_col;
3302 do {
3303 new_col += count;
3304 ++iterations;
3306 if (new_col < bound->start.col)
3307 return MIN (bound->start.col, max_col);
3308 if (new_col > bound->end.col)
3309 return MIN (bound->end.col, max_col);
3311 keep_looking = sheet_col_is_hidden (sheet, new_col);
3312 if (jump_to_boundaries) {
3313 if (new_col > sheet->cols.max_used) {
3314 if (count > 0)
3315 return (find_nonblank || iterations == 1)?
3316 MIN (bound->end.col, max_col):
3317 MIN (prev_col, max_col);
3318 new_col = sheet->cols.max_used;
3321 keep_looking |= (sheet_is_cell_empty (sheet, new_col, move_row) == find_nonblank);
3322 if (keep_looking)
3323 prev_col = new_col;
3324 else if (!find_nonblank) {
3326 * Handle special case where we are on the last
3327 * non-null cell
3329 if (iterations == 1)
3330 keep_looking = find_nonblank = TRUE;
3331 else
3332 new_col = prev_col;
3335 } while (keep_looking);
3337 return MIN (new_col, max_col);
3341 * sheet_find_boundary_vertical:
3342 * @sheet: The Sheet *
3343 * @move_col: The col in which to search for the edge of the range.
3344 * @row: The row from which to begin searching.
3345 * @base_col: The width of the area being moved.
3346 * @count: units to extend the selection vertically
3347 * @jump_to_boundaries: Jump to range boundaries.
3349 * Calculate the row index for the row which is @n units
3350 * from @start_row doing bounds checking. If @jump_to_boundaries is
3351 * TRUE @n must be 1 and the jump is to the edge of the logical range.
3353 * NOTE : This routine implements the logic necasary for ctrl-arrow style
3354 * movement. That is more compilcated than simply finding the last in a list
3355 * of cells with content. If you are at the end of a range it will find the
3356 * start of the next. Make sure that is the sort of behavior you want before
3357 * calling this.
3358 * Returns: the row index.
3361 sheet_find_boundary_vertical (Sheet *sheet, int move_col, int start_row,
3362 int base_col, int count,
3363 gboolean jump_to_boundaries)
3365 gboolean find_nonblank = sheet_is_cell_empty (sheet, move_col, start_row);
3366 gboolean keep_looking = FALSE;
3367 int new_row, prev_row, lagged_start_row, max_row = gnm_sheet_get_last_row (sheet);
3368 int iterations = 0;
3369 GnmRange check_merge;
3370 GnmRange const * const bound = &sheet->priv->unhidden_region;
3372 /* Jumping to bounds requires steping cell by cell */
3373 g_return_val_if_fail (count == 1 || count == -1 || !jump_to_boundaries, start_row);
3374 g_return_val_if_fail (IS_SHEET (sheet), start_row);
3376 if (move_col < base_col) {
3377 check_merge.start.col = move_col;
3378 check_merge.end.col = base_col;
3379 } else {
3380 check_merge.end.col = move_col;
3381 check_merge.start.col = base_col;
3384 do {
3385 GSList *merged, *ptr;
3387 lagged_start_row = check_merge.start.row = check_merge.end.row = start_row;
3388 merged = gnm_sheet_merge_get_overlap (sheet, &check_merge);
3389 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3390 GnmRange const * const r = ptr->data;
3391 if (count > 0) {
3392 if (start_row < r->end.row)
3393 start_row = r->end.row;
3394 } else {
3395 if (start_row > r->start.row)
3396 start_row = r->start.row;
3399 g_slist_free (merged);
3400 } while (start_row != lagged_start_row);
3401 new_row = prev_row = start_row;
3403 do {
3404 new_row += count;
3405 ++iterations;
3407 if (new_row < bound->start.row)
3408 return MIN (bound->start.row, max_row);
3409 if (new_row > bound->end.row)
3410 return MIN (bound->end.row, max_row);
3412 keep_looking = sheet_row_is_hidden (sheet, new_row);
3413 if (jump_to_boundaries) {
3414 if (new_row > sheet->rows.max_used) {
3415 if (count > 0)
3416 return (find_nonblank || iterations == 1)?
3417 MIN (bound->end.row, max_row):
3418 MIN (prev_row, max_row);
3419 new_row = sheet->rows.max_used;
3422 keep_looking |= (sheet_is_cell_empty (sheet, move_col, new_row) == find_nonblank);
3423 if (keep_looking)
3424 prev_row = new_row;
3425 else if (!find_nonblank) {
3427 * Handle special case where we are on the last
3428 * non-null cell
3430 if (iterations == 1)
3431 keep_looking = find_nonblank = TRUE;
3432 else
3433 new_row = prev_row;
3436 } while (keep_looking);
3438 return MIN (new_row, max_row);
3441 typedef enum {
3442 CHECK_AND_LOAD_START = 1,
3443 CHECK_END = 2,
3444 LOAD_END = 4
3445 } ArrayCheckFlags;
3447 typedef struct {
3448 Sheet const *sheet;
3449 int flags;
3450 int start, end;
3451 GnmRange const *ignore;
3453 GnmRange error;
3454 } ArrayCheckData;
3456 static gboolean
3457 cb_check_array_horizontal (GnmColRowIter const *iter, ArrayCheckData *data)
3459 gboolean is_array = FALSE;
3461 if (data->flags & CHECK_AND_LOAD_START && /* Top */
3462 (is_array = gnm_cell_array_bound (
3463 sheet_cell_get (data->sheet, iter->pos, data->start),
3464 &data->error)) &&
3465 data->error.start.row < data->start &&
3466 (data->ignore == NULL ||
3467 !range_contained (&data->error, data->ignore)))
3468 return TRUE;
3470 if (data->flags & LOAD_END)
3471 is_array = gnm_cell_array_bound (
3472 sheet_cell_get (data->sheet, iter->pos, data->end),
3473 &data->error);
3475 return (data->flags & CHECK_END &&
3476 is_array &&
3477 data->error.end.row > data->end && /* Bottom */
3478 (data->ignore == NULL ||
3479 !range_contained (&data->error, data->ignore)));
3482 static gboolean
3483 cb_check_array_vertical (GnmColRowIter const *iter, ArrayCheckData *data)
3485 gboolean is_array = FALSE;
3487 if (data->flags & CHECK_AND_LOAD_START && /* Left */
3488 (is_array = gnm_cell_array_bound (
3489 sheet_cell_get (data->sheet, data->start, iter->pos),
3490 &data->error)) &&
3491 data->error.start.col < data->start &&
3492 (data->ignore == NULL ||
3493 !range_contained (&data->error, data->ignore)))
3494 return TRUE;
3496 if (data->flags & LOAD_END)
3497 is_array = gnm_cell_array_bound (
3498 sheet_cell_get (data->sheet, data->end, iter->pos),
3499 &data->error);
3501 return (data->flags & CHECK_END &&
3502 is_array &&
3503 data->error.end.col > data->end && /* Right */
3504 (data->ignore == NULL ||
3505 !range_contained (&data->error, data->ignore)));
3509 * sheet_range_splits_array:
3510 * @sheet: The sheet.
3511 * @r: The range to check
3512 * @ignore: (nullable): a range in which it is ok to have an array.
3513 * @cc: (nullable): place to report an error.
3514 * @cmd: (nullable): cmd name used with @cc.
3516 * Check the outer edges of range @sheet!@r to ensure that if an array is
3517 * within it then the entire array is within the range. @ignore is useful when
3518 * src & dest ranges may overlap.
3520 * Returns: TRUE if an array would be split.
3522 gboolean
3523 sheet_range_splits_array (Sheet const *sheet,
3524 GnmRange const *r, GnmRange const *ignore,
3525 GOCmdContext *cc, char const *cmd)
3527 ArrayCheckData closure;
3529 g_return_val_if_fail (r->start.col <= r->end.col, FALSE);
3530 g_return_val_if_fail (r->start.row <= r->end.row, FALSE);
3532 closure.sheet = sheet;
3533 closure.ignore = ignore;
3535 closure.start = r->start.row;
3536 closure.end = r->end.row;
3537 if (closure.start <= 0) {
3538 closure.flags = (closure.end < sheet->rows.max_used)
3539 ? CHECK_END | LOAD_END
3540 : 0;
3541 } else if (closure.end < sheet->rows.max_used)
3542 closure.flags = (closure.start == closure.end)
3543 ? CHECK_AND_LOAD_START | CHECK_END
3544 : CHECK_AND_LOAD_START | CHECK_END | LOAD_END;
3545 else
3546 closure.flags = CHECK_AND_LOAD_START;
3548 if (closure.flags &&
3549 colrow_foreach (&sheet->cols, r->start.col, r->end.col,
3550 (ColRowHandler) cb_check_array_horizontal, &closure)) {
3551 if (cc)
3552 gnm_cmd_context_error_splits_array (cc,
3553 cmd, &closure.error);
3554 return TRUE;
3557 closure.start = r->start.col;
3558 closure.end = r->end.col;
3559 if (closure.start <= 0) {
3560 closure.flags = (closure.end < sheet->cols.max_used)
3561 ? CHECK_END | LOAD_END
3562 : 0;
3563 } else if (closure.end < sheet->cols.max_used)
3564 closure.flags = (closure.start == closure.end)
3565 ? CHECK_AND_LOAD_START | CHECK_END
3566 : CHECK_AND_LOAD_START | CHECK_END | LOAD_END;
3567 else
3568 closure.flags = CHECK_AND_LOAD_START;
3570 if (closure.flags &&
3571 colrow_foreach (&sheet->rows, r->start.row, r->end.row,
3572 (ColRowHandler) cb_check_array_vertical, &closure)) {
3573 if (cc)
3574 gnm_cmd_context_error_splits_array (cc,
3575 cmd, &closure.error);
3576 return TRUE;
3578 return FALSE;
3582 * sheet_range_splits_region:
3583 * @sheet: the sheet.
3584 * @r: The range whose boundaries are checked
3585 * @ignore: An optional range in which it is ok to have arrays and merges
3586 * @cc: The context that issued the command
3587 * @cmd: The translated command name.
3589 * A utility to see whether moving the range @r will split any arrays
3590 * or merged regions.
3591 * Returns: whether any arrays or merged regions will be split.
3593 gboolean
3594 sheet_range_splits_region (Sheet const *sheet,
3595 GnmRange const *r, GnmRange const *ignore,
3596 GOCmdContext *cc, char const *cmd_name)
3598 GSList *merged;
3600 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
3602 /* Check for array subdivision */
3603 if (sheet_range_splits_array (sheet, r, ignore, cc, cmd_name))
3604 return TRUE;
3606 merged = gnm_sheet_merge_get_overlap (sheet, r);
3607 if (merged) {
3608 GSList *ptr;
3610 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3611 GnmRange const *m = ptr->data;
3612 if (ignore != NULL && range_contained (m, ignore))
3613 continue;
3614 if (!range_contained (m, r))
3615 break;
3617 g_slist_free (merged);
3619 if (cc != NULL && ptr != NULL) {
3620 go_cmd_context_error_invalid (cc, cmd_name,
3621 _("Target region contains merged cells"));
3622 return TRUE;
3625 return FALSE;
3629 * sheet_ranges_split_region:
3630 * @sheet: the sheet.
3631 * @ranges: (element-type GnmRange): A list of ranges to check.
3632 * @cc: The context that issued the command
3633 * @cmd: The translated command name.
3635 * A utility to see whether moving any of the ranges @ranges will split any
3636 * arrays or merged regions.
3637 * Returns: whether any arrays or merged regions will be splitted.
3639 gboolean
3640 sheet_ranges_split_region (Sheet const * sheet, GSList const *ranges,
3641 GOCmdContext *cc, char const *cmd)
3643 GSList const *l;
3645 /* Check for array subdivision */
3646 for (l = ranges; l != NULL; l = l->next) {
3647 GnmRange const *r = l->data;
3648 if (sheet_range_splits_region (sheet, r, NULL, cc, cmd))
3649 return TRUE;
3651 return FALSE;
3654 static GnmValue *
3655 cb_cell_is_array (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
3657 return gnm_cell_is_array (iter->cell) ? VALUE_TERMINATE : NULL;
3661 * sheet_range_contains_merges_or_arrays:
3662 * @sheet: The sheet
3663 * @r: the range to check.
3664 * @cc: an optional place to report errors.
3665 * @cmd:
3666 * @merges: if %TRUE, check for merges.
3667 * @arrays: if %TRUE, check for arrays.
3669 * Check to see if the target region @sheet!@r contains any merged regions or
3670 * arrays. Report an error to the @cc if it is supplied.
3671 * Returns: %TRUE if the target region @sheet!@r contains any merged regions or
3672 * arrays.
3674 gboolean
3675 sheet_range_contains_merges_or_arrays (Sheet const *sheet, GnmRange const *r,
3676 GOCmdContext *cc, char const *cmd,
3677 gboolean merges, gboolean arrays)
3679 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
3681 if (merges) {
3682 GSList *merged = gnm_sheet_merge_get_overlap (sheet, r);
3683 if (merged != NULL) {
3684 if (cc != NULL)
3685 go_cmd_context_error_invalid
3686 (cc, cmd,
3687 _("cannot operate on merged cells"));
3688 g_slist_free (merged);
3689 return TRUE;
3693 if (arrays) {
3694 if (sheet_foreach_cell_in_range (
3695 (Sheet *)sheet, CELL_ITER_IGNORE_NONEXISTENT,
3696 r->start.col, r->start.row, r->end.col, r->end.row,
3697 cb_cell_is_array, NULL)) {
3698 if (cc != NULL)
3699 go_cmd_context_error_invalid
3700 (cc, cmd,
3701 _("cannot operate on array formul\303\246"));
3702 return TRUE;
3706 return FALSE;
3709 /***************************************************************************/
3712 * sheet_colrow_get_default:
3713 * @sheet:
3714 * @is_cols:
3716 * Returns: (transfer none): the default #ColRowInfo.
3718 ColRowInfo const *
3719 sheet_colrow_get_default (Sheet const *sheet, gboolean is_cols)
3721 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3723 return is_cols ? &sheet->cols.default_style : &sheet->rows.default_style;
3726 static void
3727 sheet_colrow_optimize1 (int max, int max_used, ColRowCollection *collection)
3729 int i;
3730 int first_unused = max_used + 1;
3732 for (i = COLROW_SEGMENT_START (first_unused);
3733 i < max;
3734 i += COLROW_SEGMENT_SIZE) {
3735 ColRowSegment *segment = COLROW_GET_SEGMENT (collection, i);
3736 int j;
3737 gboolean any = FALSE;
3739 if (!segment)
3740 continue;
3741 for (j = 0; j < COLROW_SEGMENT_SIZE; j++) {
3742 ColRowInfo *info = segment->info[j];
3743 if (!info)
3744 continue;
3745 if (i + j >= first_unused &&
3746 colrow_equal (&collection->default_style, info)) {
3747 colrow_free (info);
3748 segment->info[j] = NULL;
3749 } else {
3750 any = TRUE;
3751 if (i + j >= first_unused)
3752 max_used = i + j;
3756 if (!any) {
3757 g_free (segment);
3758 COLROW_GET_SEGMENT (collection, i) = NULL;
3762 collection->max_used = max_used;
3765 void
3766 sheet_colrow_optimize (Sheet *sheet)
3768 GnmRange extent;
3770 g_return_if_fail (IS_SHEET (sheet));
3772 extent = sheet_get_cells_extent (sheet);
3774 sheet_colrow_optimize1 (gnm_sheet_get_max_cols (sheet),
3775 extent.end.col,
3776 &sheet->cols);
3777 sheet_colrow_optimize1 (gnm_sheet_get_max_rows (sheet),
3778 extent.end.row,
3779 &sheet->rows);
3783 * sheet_col_get:
3784 * @col: column number
3786 * Returns: (transfer none) (nullable): A #ColRowInfo for the column, or %NULL
3787 * if none has been allocated yet.
3789 ColRowInfo *
3790 sheet_col_get (Sheet const *sheet, int col)
3792 ColRowSegment *segment;
3794 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3795 g_return_val_if_fail (col < gnm_sheet_get_max_cols (sheet), NULL);
3796 g_return_val_if_fail (col >= 0, NULL);
3798 segment = COLROW_GET_SEGMENT (&(sheet->cols), col);
3799 if (segment != NULL)
3800 return segment->info[COLROW_SUB_INDEX (col)];
3801 return NULL;
3805 * sheet_row_get:
3806 * @row: row number
3808 * Returns: (transfer none) (nullable): A #ColRowInfo for the row, or %NULL
3809 * if none has been allocated yet.
3811 ColRowInfo *
3812 sheet_row_get (Sheet const *sheet, int row)
3814 ColRowSegment *segment;
3816 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3817 g_return_val_if_fail (row < gnm_sheet_get_max_rows (sheet), NULL);
3818 g_return_val_if_fail (row >= 0, NULL);
3820 segment = COLROW_GET_SEGMENT (&(sheet->rows), row);
3821 if (segment != NULL)
3822 return segment->info[COLROW_SUB_INDEX (row)];
3823 return NULL;
3826 ColRowInfo *
3827 sheet_colrow_get (Sheet const *sheet, int colrow, gboolean is_cols)
3829 if (is_cols)
3830 return sheet_col_get (sheet, colrow);
3831 return sheet_row_get (sheet, colrow);
3835 * sheet_col_fetch:
3836 * @col: column number
3838 * Returns: (transfer none): The #ColRowInfo for column @col. This result
3839 * will not be the default #ColRowInfo and may be changed.
3841 ColRowInfo *
3842 sheet_col_fetch (Sheet *sheet, int pos)
3844 ColRowInfo *cri = sheet_col_get (sheet, pos);
3845 if (NULL == cri && NULL != (cri = sheet_col_new (sheet)))
3846 sheet_colrow_add (sheet, cri, TRUE, pos);
3847 return cri;
3851 * sheet_row_fetch:
3852 * @row: row number
3854 * Returns: (transfer none): The #ColRowInfo for row @row. This result
3855 * will not be the default #ColRowInfo and may be changed.
3857 ColRowInfo *
3858 sheet_row_fetch (Sheet *sheet, int pos)
3860 ColRowInfo *cri = sheet_row_get (sheet, pos);
3861 if (NULL == cri && NULL != (cri = sheet_row_new (sheet)))
3862 sheet_colrow_add (sheet, cri, FALSE, pos);
3863 return cri;
3866 ColRowInfo *
3867 sheet_colrow_fetch (Sheet *sheet, int colrow, gboolean is_cols)
3869 if (is_cols)
3870 return sheet_col_fetch (sheet, colrow);
3871 return sheet_row_fetch (sheet, colrow);
3875 * sheet_col_get_info:
3876 * @col: column number
3878 * Returns: (transfer none): The #ColRowInfo for column @col. The may be
3879 * the default #ColRowInfo for columns and should not be changed.
3881 ColRowInfo const *
3882 sheet_col_get_info (Sheet const *sheet, int col)
3884 ColRowInfo *ci = sheet_col_get (sheet, col);
3886 if (ci != NULL)
3887 return ci;
3888 return &sheet->cols.default_style;
3892 * sheet_row_get_info:
3893 * @row: column number
3895 * Returns: (transfer none): The #ColRowInfo for row @row. The may be
3896 * the default #ColRowInfo for rows and should not be changed.
3898 ColRowInfo const *
3899 sheet_row_get_info (Sheet const *sheet, int row)
3901 ColRowInfo *ri = sheet_row_get (sheet, row);
3903 if (ri != NULL)
3904 return ri;
3905 return &sheet->rows.default_style;
3908 ColRowInfo const *
3909 sheet_colrow_get_info (Sheet const *sheet, int colrow, gboolean is_cols)
3911 return is_cols
3912 ? sheet_col_get_info (sheet, colrow)
3913 : sheet_row_get_info (sheet, colrow);
3916 /*****************************************************************************/
3918 static gint
3919 cell_ordering (gconstpointer a_, gconstpointer b_)
3921 GnmCell const *a = *(GnmCell **)a_;
3922 GnmCell const *b = *(GnmCell **)b_;
3924 if (a->pos.row != b->pos.row)
3925 return a->pos.row - b->pos.row;
3927 return a->pos.col - b->pos.col;
3931 * sheet_cells:
3932 * @sheet: a #Sheet
3933 * @r: (nullable): a #GnmRange
3935 * Retrieves an array of all cells inside @r.
3936 * Returns: (element-type GnmCell) (transfer container): the cells array.
3938 GPtrArray *
3939 sheet_cells (Sheet *sheet, const GnmRange *r)
3941 GPtrArray *res = g_ptr_array_new ();
3942 GHashTableIter hiter;
3943 gpointer value;
3945 g_hash_table_iter_init (&hiter, sheet->cell_hash);
3946 while (g_hash_table_iter_next (&hiter, NULL, &value)) {
3947 GnmCell *cell = value;
3948 if (!r || range_contains (r, cell->pos.col, cell->pos.row))
3949 g_ptr_array_add (res, cell);
3951 g_ptr_array_sort (res, cell_ordering);
3953 return res;
3958 #define SWAP_INT(a,b) do { int t; t = a; a = b; b = t; } while (0)
3961 * sheet_foreach_cell_in_range:
3962 * @sheet: #Sheet
3963 * @flags:
3964 * @start_col:
3965 * @start_row:
3966 * @end_col:
3967 * @end_row:
3968 * @callback: (scope call): #CellFiletrFunc
3969 * @closure: user data.
3971 * For each existing cell in the range specified, invoke the
3972 * callback routine. If the only_existing flag is passed, then
3973 * callbacks are only invoked for existing cells.
3975 * Note: this function does not honour the CELL_ITER_IGNORE_SUBTOTAL flag.
3977 * Returns: (transfer none): the value returned by the callback, which can be :
3978 * non-NULL on error, or VALUE_TERMINATE if some invoked routine requested
3979 * to stop (by returning non-NULL).
3981 * NOTE: between 0.56 and 0.57, the traversal order changed. The order is now
3983 * 1 2 3
3984 * 4 5 6
3985 * 7 8 9
3987 * (This appears to be the order in which XL looks at the values of ranges.)
3988 * If your code depends on any particular ordering, please add a very visible
3989 * comment near the call.
3991 GnmValue *
3992 sheet_foreach_cell_in_range (Sheet *sheet, CellIterFlags flags,
3993 int start_col, int start_row,
3994 int end_col, int end_row,
3995 CellIterFunc callback, void *closure)
3997 GnmValue *cont;
3998 GnmCellIter iter;
3999 gboolean const visiblity_matters = (flags & CELL_ITER_IGNORE_HIDDEN) != 0;
4000 gboolean const ignore_filtered = (flags & CELL_ITER_IGNORE_FILTERED) != 0;
4001 gboolean const only_existing = (flags & CELL_ITER_IGNORE_NONEXISTENT) != 0;
4002 gboolean const ignore_empty = (flags & CELL_ITER_IGNORE_EMPTY) != 0;
4003 gboolean ignore;
4004 gboolean use_celllist;
4005 guint64 range_size;
4007 g_return_val_if_fail (IS_SHEET (sheet), NULL);
4008 g_return_val_if_fail (callback != NULL, NULL);
4010 iter.pp.sheet = sheet;
4011 iter.pp.wb = sheet->workbook;
4013 if (start_col > end_col)
4014 SWAP_INT (start_col, end_col);
4015 if (end_col < 0 || start_col > gnm_sheet_get_last_col (sheet))
4016 return NULL;
4017 start_col = MAX (0, start_col);
4018 end_col = MIN (end_col, gnm_sheet_get_last_col (sheet));
4020 if (start_row > end_row)
4021 SWAP_INT (start_row, end_row);
4022 if (end_row < 0 || start_row > gnm_sheet_get_last_row (sheet))
4023 return NULL;
4024 start_row = MAX (0, start_row);
4025 end_row = MIN (end_row, gnm_sheet_get_last_row (sheet));
4027 range_size = (guint64)(end_row - start_row + 1) * (end_col - start_col + 1);
4028 use_celllist =
4029 only_existing &&
4030 range_size > g_hash_table_size (sheet->cell_hash) + 1000;
4031 if (use_celllist) {
4032 GPtrArray *all_cells;
4033 int last_row = -1, last_col = -1;
4034 GnmValue *res = NULL;
4035 unsigned ui;
4036 GnmRange r;
4038 if (gnm_debug_flag ("sheet-foreach"))
4039 g_printerr ("Using celllist for area of size %d\n",
4040 (int)range_size);
4042 range_init (&r, start_col, start_row, end_col, end_row);
4043 all_cells = sheet_cells (sheet, &r);
4045 for (ui = 0; ui < all_cells->len; ui++) {
4046 GnmCell *cell = g_ptr_array_index (all_cells, ui);
4048 iter.cell = cell;
4049 iter.pp.eval.row = cell->pos.row;
4050 iter.pp.eval.col = cell->pos.col;
4052 if (iter.pp.eval.row != last_row) {
4053 last_row = iter.pp.eval.row;
4054 iter.ri = sheet_row_get (iter.pp.sheet, last_row);
4056 if (visiblity_matters && !iter.ri->visible)
4057 continue;
4058 if (ignore_filtered && iter.ri->in_filter && !iter.ri->visible)
4059 continue;
4061 if (iter.pp.eval.col != last_col) {
4062 last_col = iter.pp.eval.col;
4063 iter.ci = sheet_col_get (iter.pp.sheet, last_col);
4065 if (visiblity_matters && !iter.ci->visible)
4066 continue;
4068 ignore = (ignore_empty &&
4069 VALUE_IS_EMPTY (cell->value) &&
4070 !gnm_cell_needs_recalc (cell));
4071 if (ignore)
4072 continue;
4074 res = (*callback) (&iter, closure);
4075 if (res != NULL)
4076 break;
4079 g_ptr_array_free (all_cells, TRUE);
4080 return res;
4083 for (iter.pp.eval.row = start_row;
4084 iter.pp.eval.row <= end_row;
4085 ++iter.pp.eval.row) {
4086 iter.ri = sheet_row_get (iter.pp.sheet, iter.pp.eval.row);
4088 /* no need to check visiblity, that would require a colinfo to exist */
4089 if (iter.ri == NULL) {
4090 if (only_existing) {
4091 /* skip segments with no cells */
4092 if (iter.pp.eval.row == COLROW_SEGMENT_START (iter.pp.eval.row)) {
4093 ColRowSegment const *segment =
4094 COLROW_GET_SEGMENT (&(sheet->rows), iter.pp.eval.row);
4095 if (segment == NULL)
4096 iter.pp.eval.row = COLROW_SEGMENT_END (iter.pp.eval.row);
4098 } else {
4099 iter.cell = NULL;
4100 for (iter.pp.eval.col = start_col; iter.pp.eval.col <= end_col; ++iter.pp.eval.col) {
4101 cont = (*callback) (&iter, closure);
4102 if (cont != NULL)
4103 return cont;
4107 continue;
4110 if (visiblity_matters && !iter.ri->visible)
4111 continue;
4112 if (ignore_filtered && iter.ri->in_filter && !iter.ri->visible)
4113 continue;
4115 for (iter.pp.eval.col = start_col; iter.pp.eval.col <= end_col; ++iter.pp.eval.col) {
4116 iter.ci = sheet_col_get (sheet, iter.pp.eval.col);
4117 if (iter.ci != NULL) {
4118 if (visiblity_matters && !iter.ci->visible)
4119 continue;
4120 iter.cell = sheet_cell_get (sheet,
4121 iter.pp.eval.col, iter.pp.eval.row);
4122 } else
4123 iter.cell = NULL;
4125 ignore = (iter.cell == NULL)
4126 ? (only_existing || ignore_empty)
4127 : (ignore_empty && VALUE_IS_EMPTY (iter.cell->value) &&
4128 !gnm_cell_needs_recalc (iter.cell));
4130 if (ignore) {
4131 if (iter.pp.eval.col == COLROW_SEGMENT_START (iter.pp.eval.col)) {
4132 ColRowSegment const *segment =
4133 COLROW_GET_SEGMENT (&(sheet->cols), iter.pp.eval.col);
4134 if (segment == NULL)
4135 iter.pp.eval.col = COLROW_SEGMENT_END (iter.pp.eval.col);
4137 continue;
4140 cont = (*callback) (&iter, closure);
4141 if (cont != NULL)
4142 return cont;
4145 return NULL;
4149 * sheet_cell_foreach:
4150 * @sheet: #Sheet
4151 * @callback: (scope call):
4152 * @data:
4154 * Call @callback with an argument of @data for each cell in the sheet
4156 void
4157 sheet_cell_foreach (Sheet const *sheet, GHFunc callback, gpointer data)
4159 g_return_if_fail (IS_SHEET (sheet));
4161 g_hash_table_foreach (sheet->cell_hash, callback, data);
4165 * sheet_cells_count:
4166 * @sheet: #Sheet
4168 * Returns the number of cells with content in the current workbook.
4170 unsigned
4171 sheet_cells_count (Sheet const *sheet)
4173 return g_hash_table_size (sheet->cell_hash);
4176 static void
4177 cb_sheet_cells_collect (G_GNUC_UNUSED gpointer unused,
4178 GnmCell const *cell,
4179 GPtrArray *cells)
4181 GnmEvalPos *ep = eval_pos_init_cell (g_new (GnmEvalPos, 1), cell);
4182 g_ptr_array_add (cells, ep);
4186 * sheet_cell_positions:
4187 * @sheet: The sheet to find cells in.
4188 * @comments: If true, include cells with only comments also.
4190 * Collects a GPtrArray of GnmEvalPos pointers for all cells in a sheet.
4191 * No particular order should be assumed.
4192 * Returns: (element-type GnmEvalPos) (transfer full): the newly created array
4194 GPtrArray *
4195 sheet_cell_positions (Sheet *sheet, gboolean comments)
4197 GPtrArray *cells = g_ptr_array_new ();
4199 g_return_val_if_fail (IS_SHEET (sheet), cells);
4201 sheet_cell_foreach (sheet, (GHFunc)cb_sheet_cells_collect, cells);
4203 if (comments) {
4204 GnmRange r;
4205 GSList *scomments, *ptr;
4207 range_init_full_sheet (&r, sheet);
4208 scomments = sheet_objects_get (sheet, &r, GNM_CELL_COMMENT_TYPE);
4209 for (ptr = scomments; ptr; ptr = ptr->next) {
4210 GnmComment *c = ptr->data;
4211 GnmRange const *loc = sheet_object_get_range (GNM_SO (c));
4212 GnmCell *cell = sheet_cell_get (sheet, loc->start.col, loc->start.row);
4213 if (!cell) {
4214 /* If cell does not exist, we haven't seen it... */
4215 GnmEvalPos *ep = g_new (GnmEvalPos, 1);
4216 ep->sheet = sheet;
4217 ep->eval.col = loc->start.col;
4218 ep->eval.row = loc->start.row;
4219 g_ptr_array_add (cells, ep);
4222 g_slist_free (scomments);
4225 return cells;
4229 static GnmValue *
4230 cb_fail_if_exist (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
4232 return gnm_cell_is_empty (iter->cell) ? NULL : VALUE_TERMINATE;
4236 * sheet_is_region_empty:
4237 * @sheet: sheet to check
4238 * @r: region to check
4240 * Returns TRUE if the specified region of the @sheet does not
4241 * contain any cells
4243 gboolean
4244 sheet_is_region_empty (Sheet *sheet, GnmRange const *r)
4246 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
4248 return sheet_foreach_cell_in_range (
4249 sheet, CELL_ITER_IGNORE_BLANK,
4250 r->start.col, r->start.row, r->end.col, r->end.row,
4251 cb_fail_if_exist, NULL) == NULL;
4254 gboolean
4255 sheet_is_cell_empty (Sheet *sheet, int col, int row)
4257 GnmCell const *cell = sheet_cell_get (sheet, col, row);
4258 return gnm_cell_is_empty (cell);
4262 * sheet_cell_add_to_hash:
4263 * @sheet The sheet where the cell is inserted
4264 * @cell The cell, it should already have col/pos pointers
4265 * initialized pointing to the correct ColRowInfo
4267 * GnmCell::pos must be valid before this is called. The position is used as the
4268 * hash key.
4270 static void
4271 sheet_cell_add_to_hash (Sheet *sheet, GnmCell *cell)
4273 g_return_if_fail (cell->pos.col < gnm_sheet_get_max_cols (sheet));
4274 g_return_if_fail (cell->pos.row < gnm_sheet_get_max_rows (sheet));
4276 cell->base.flags |= GNM_CELL_IN_SHEET_LIST;
4277 /* NOTE :
4278 * fetching the col/row here serve 3 functions
4279 * 1) obsolete: we used to store the pointer in the cell
4280 * 2) Expanding col/row.max_used
4281 * 3) Creating an entry in the COLROW_SEGMENT. Lots and lots of
4282 * things use those to help limit iteration
4284 * For now just call col_fetch even though it is not necessary to
4285 * ensure that 2,3 still happen. Alot will need rewriting to avoid
4286 * these requirements.
4288 (void)sheet_col_fetch (sheet, cell->pos.col);
4289 (void)sheet_row_fetch (sheet, cell->pos.row);
4291 gnm_cell_unrender (cell);
4293 g_hash_table_insert (sheet->cell_hash, cell, cell);
4295 if (gnm_sheet_merge_is_corner (sheet, &cell->pos))
4296 cell->base.flags |= GNM_CELL_IS_MERGED;
4299 #undef USE_CELL_POOL
4301 #ifdef USE_CELL_POOL
4302 /* The pool from which all cells are allocated. */
4303 static GOMemChunk *cell_pool;
4304 #else
4305 static int cell_allocations = 0;
4306 #endif
4308 static GnmCell *
4309 cell_new (void)
4311 GnmCell *cell =
4312 #ifdef USE_CELL_POOL
4313 go_mem_chunk_alloc0 (cell_pool)
4314 #else
4315 (cell_allocations++, g_slice_new0 (GnmCell))
4316 #endif
4319 cell->base.flags = DEPENDENT_CELL;
4320 return cell;
4324 static void
4325 cell_free (GnmCell *cell)
4327 g_return_if_fail (cell != NULL);
4329 gnm_cell_cleanout (cell);
4330 #ifdef USE_CELL_POOL
4331 go_mem_chunk_free (cell_pool, cell);
4332 #else
4333 cell_allocations--, g_slice_free1 (sizeof (*cell), cell);
4334 #endif
4337 void
4338 gnm_sheet_cell_init (void)
4340 #ifdef USE_CELL_POOL
4341 cell_pool = go_mem_chunk_new ("cell pool",
4342 sizeof (GnmCell),
4343 128 * 1024 - 128);
4344 #endif
4347 #ifdef USE_CELL_POOL
4348 static void
4349 cb_cell_pool_leak (gpointer data, G_GNUC_UNUSED gpointer user)
4351 GnmCell const *cell = data;
4352 g_printerr ("Leaking cell %p at %s\n", (void *)cell, cell_name (cell));
4354 #endif
4356 void
4357 gnm_sheet_cell_shutdown (void)
4359 #ifdef USE_CELL_POOL
4360 go_mem_chunk_foreach_leak (cell_pool, cb_cell_pool_leak, NULL);
4361 go_mem_chunk_destroy (cell_pool, FALSE);
4362 cell_pool = NULL;
4363 #else
4364 if (cell_allocations)
4365 g_printerr ("Leaking %d cells.\n", cell_allocations);
4366 #endif
4369 /****************************************************************************/
4372 * sheet_cell_create:
4373 * @sheet: #Sheet
4374 * @col:
4375 * @row:
4377 * Creates a new cell and adds it to the sheet hash.
4379 GnmCell *
4380 sheet_cell_create (Sheet *sheet, int col, int row)
4382 GnmCell *cell;
4384 g_return_val_if_fail (IS_SHEET (sheet), NULL);
4385 g_return_val_if_fail (col >= 0, NULL);
4386 g_return_val_if_fail (col < gnm_sheet_get_max_cols (sheet), NULL);
4387 g_return_val_if_fail (row >= 0, NULL);
4388 g_return_val_if_fail (row < gnm_sheet_get_max_rows (sheet), NULL);
4390 cell = cell_new ();
4391 cell->base.sheet = sheet;
4392 cell->pos.col = col;
4393 cell->pos.row = row;
4394 cell->value = value_new_empty ();
4396 sheet_cell_add_to_hash (sheet, cell);
4397 return cell;
4401 * sheet_cell_remove_from_hash:
4402 * @sheet:
4403 * @cell:
4405 * Removes a cell from the sheet hash, clears any spans, and unlinks it from
4406 * the dependent collection.
4408 static void
4409 sheet_cell_remove_from_hash (Sheet *sheet, GnmCell *cell)
4411 cell_unregister_span (cell);
4412 if (gnm_cell_expr_is_linked (cell))
4413 dependent_unlink (GNM_CELL_TO_DEP (cell));
4414 g_hash_table_remove (sheet->cell_hash, cell);
4415 cell->base.flags &= ~(GNM_CELL_IN_SHEET_LIST|GNM_CELL_IS_MERGED);
4419 * sheet_cell_destroy:
4420 * @sheet:
4421 * @cell:
4422 * @queue_recalc:
4424 * Remove the cell from the web of dependencies of a
4425 * sheet. Do NOT redraw.
4427 static void
4428 sheet_cell_destroy (Sheet *sheet, GnmCell *cell, gboolean queue_recalc)
4430 if (gnm_cell_expr_is_linked (cell)) {
4431 /* if it needs recalc then its depends are already queued
4432 * check recalc status before we unlink
4434 queue_recalc &= !gnm_cell_needs_recalc (cell);
4435 dependent_unlink (GNM_CELL_TO_DEP (cell));
4438 if (queue_recalc)
4439 cell_foreach_dep (cell, (GnmDepFunc)dependent_queue_recalc, NULL);
4441 sheet_cell_remove_from_hash (sheet, cell);
4442 cell_free (cell);
4446 * sheet_cell_remove:
4447 * @sheet:
4448 * @cell:
4449 * @redraw:
4450 * @queue_recalc:
4452 * Remove the cell from the web of dependencies of a
4453 * sheet. Do NOT free the cell, optionally redraw it, optionally
4454 * queue it for recalc.
4456 void
4457 sheet_cell_remove (Sheet *sheet, GnmCell *cell,
4458 gboolean redraw, gboolean queue_recalc)
4460 g_return_if_fail (cell != NULL);
4461 g_return_if_fail (IS_SHEET (sheet));
4463 /* Queue a redraw on the region used by the cell being removed */
4464 if (redraw) {
4465 sheet_redraw_region (sheet,
4466 cell->pos.col, cell->pos.row,
4467 cell->pos.col, cell->pos.row);
4468 sheet_flag_status_update_cell (cell);
4471 sheet_cell_destroy (sheet, cell, queue_recalc);
4474 static GnmValue *
4475 cb_free_cell (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
4477 sheet_cell_destroy (iter->pp.sheet, iter->cell, FALSE);
4478 return NULL;
4482 * sheet_col_destroy:
4483 * @sheet:
4484 * @col:
4485 * @free_cells:
4487 * Destroys a ColRowInfo from the Sheet with all of its cells
4489 static void
4490 sheet_col_destroy (Sheet *sheet, int const col, gboolean free_cells)
4492 ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->cols), col);
4493 int const sub = COLROW_SUB_INDEX (col);
4494 ColRowInfo *ci = NULL;
4496 if (*segment == NULL)
4497 return;
4498 ci = (*segment)->info[sub];
4499 if (ci == NULL)
4500 return;
4502 if (sheet->cols.max_outline_level > 0 &&
4503 sheet->cols.max_outline_level == ci->outline_level)
4504 sheet->priv->recompute_max_col_group = TRUE;
4506 if (free_cells)
4507 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4508 col, 0, col, gnm_sheet_get_last_row (sheet),
4509 &cb_free_cell, NULL);
4511 (*segment)->info[sub] = NULL;
4512 colrow_free (ci);
4514 /* Use >= just in case things are screwed up */
4515 if (col >= sheet->cols.max_used) {
4516 int i = col;
4517 while (--i >= 0 && sheet_col_get (sheet, i) == NULL)
4519 sheet->cols.max_used = i;
4524 * Destroys a row ColRowInfo
4526 static void
4527 sheet_row_destroy (Sheet *sheet, int const row, gboolean free_cells)
4529 ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->rows), row);
4530 int const sub = COLROW_SUB_INDEX (row);
4531 ColRowInfo *ri = NULL;
4533 if (*segment == NULL)
4534 return;
4535 ri = (*segment)->info[sub];
4536 if (ri == NULL)
4537 return;
4539 if (sheet->rows.max_outline_level > 0 &&
4540 sheet->rows.max_outline_level == ri->outline_level)
4541 sheet->priv->recompute_max_row_group = TRUE;
4543 if (free_cells)
4544 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4545 0, row, gnm_sheet_get_last_col (sheet), row,
4546 &cb_free_cell, NULL);
4548 /* Rows have span lists, destroy them too */
4549 row_destroy_span (ri);
4551 (*segment)->info[sub] = NULL;
4552 colrow_free (ri);
4554 /* Use >= just in case things are screwed up */
4555 if (row >= sheet->rows.max_used) {
4556 int i = row;
4557 while (--i >= 0 && sheet_row_get (sheet, i) == NULL)
4559 sheet->rows.max_used = i;
4563 static void
4564 cb_remove_allcells (G_GNUC_UNUSED gpointer ignore0, GnmCell *cell, G_GNUC_UNUSED gpointer ignore1)
4566 cell->base.flags &= ~GNM_CELL_IN_SHEET_LIST;
4567 cell_free (cell);
4570 void
4571 sheet_destroy_contents (Sheet *sheet)
4573 GSList *filters;
4574 int i;
4576 /* By the time we reach here dependencies should have been shut down */
4577 g_return_if_fail (sheet->deps == NULL);
4579 /* A simple test to see if this has already been run. */
4580 if (sheet->hash_merged == NULL)
4581 return;
4584 GSList *tmp = sheet->slicers;
4585 sheet->slicers = NULL;
4586 g_slist_free_full (tmp, (GDestroyNotify)gnm_sheet_slicer_clear_sheet);
4589 /* These contain SheetObjects, remove them first */
4590 filters = g_slist_copy (sheet->filters);
4591 g_slist_foreach (filters, (GFunc)gnm_filter_remove, NULL);
4592 g_slist_foreach (filters, (GFunc)gnm_filter_unref, NULL);
4593 g_slist_free (filters);
4595 if (sheet->sheet_objects) {
4596 /* The list is changed as we remove */
4597 GSList *objs = g_slist_copy (sheet->sheet_objects);
4598 GSList *ptr;
4599 for (ptr = objs; ptr != NULL ; ptr = ptr->next) {
4600 SheetObject *so = GNM_SO (ptr->data);
4601 if (so != NULL)
4602 sheet_object_clear_sheet (so);
4604 g_slist_free (objs);
4605 if (sheet->sheet_objects != NULL)
4606 g_warning ("There is a problem with sheet objects");
4609 /* The memory is managed by Sheet::list_merged */
4610 g_hash_table_destroy (sheet->hash_merged);
4611 sheet->hash_merged = NULL;
4613 g_slist_free_full (sheet->list_merged, g_free);
4614 sheet->list_merged = NULL;
4616 /* Clear the row spans 1st */
4617 for (i = sheet->rows.max_used; i >= 0 ; --i)
4618 row_destroy_span (sheet_row_get (sheet, i));
4620 /* Remove all the cells */
4621 sheet_cell_foreach (sheet, (GHFunc) &cb_remove_allcells, NULL);
4622 g_hash_table_destroy (sheet->cell_hash);
4624 /* Delete in ascending order to avoid decrementing max_used each time */
4625 for (i = 0; i <= sheet->cols.max_used; ++i)
4626 sheet_col_destroy (sheet, i, FALSE);
4628 for (i = 0; i <= sheet->rows.max_used; ++i)
4629 sheet_row_destroy (sheet, i, FALSE);
4631 /* Free segments too */
4632 colrow_resize (&sheet->cols, 0);
4633 g_ptr_array_free (sheet->cols.info, TRUE);
4634 sheet->cols.info = NULL;
4636 colrow_resize (&sheet->rows, 0);
4637 g_ptr_array_free (sheet->rows.info, TRUE);
4638 sheet->rows.info = NULL;
4640 g_clear_object (&sheet->solver_parameters);
4644 * sheet_destroy:
4645 * @sheet: the sheet to destroy
4647 * Please note that you need to detach this sheet before
4648 * calling this routine or you will get a warning.
4650 static void
4651 sheet_destroy (Sheet *sheet)
4653 g_return_if_fail (IS_SHEET (sheet));
4655 if (sheet->sheet_views->len > 0)
4656 g_warning ("Unexpected left over views");
4658 if (sheet->print_info) {
4659 gnm_print_info_free (sheet->print_info);
4660 sheet->print_info = NULL;
4663 style_color_unref (sheet->tab_color);
4664 sheet->tab_color = NULL;
4665 style_color_unref (sheet->tab_text_color);
4666 sheet->tab_text_color = NULL;
4668 gnm_app_clipboard_invalidate_sheet (sheet);
4671 static void
4672 gnm_sheet_finalize (GObject *obj)
4674 Sheet *sheet = SHEET (obj);
4675 gboolean debug_FMR = gnm_debug_flag ("sheet-fmr");
4677 sheet_destroy (sheet);
4679 g_clear_object (&sheet->solver_parameters);
4681 gnm_conventions_unref (sheet->convs);
4682 sheet->convs = NULL;
4684 g_list_free_full (sheet->scenarios, g_object_unref);
4685 sheet->scenarios = NULL;
4687 if (sheet->sort_setups != NULL)
4688 g_hash_table_unref (sheet->sort_setups);
4690 dependents_invalidate_sheet (sheet, TRUE);
4692 sheet_destroy_contents (sheet);
4694 if (sheet->slicers != NULL) {
4695 g_warning ("DataSlicer list should be NULL");
4697 if (sheet->filters != NULL) {
4698 g_warning ("Filter list should be NULL");
4700 if (sheet->sheet_objects != NULL) {
4701 g_warning ("Sheet object list should be NULL");
4703 if (sheet->list_merged != NULL) {
4704 g_warning ("Merged list should be NULL");
4706 if (sheet->hash_merged != NULL) {
4707 g_warning ("Merged hash should be NULL");
4710 sheet_style_shutdown (sheet);
4712 (void) g_idle_remove_by_data (sheet);
4714 if (debug_FMR) {
4715 g_printerr ("Sheet %p is %s\n", sheet, sheet->name_quoted);
4717 g_free (sheet->name_quoted);
4718 g_free (sheet->name_unquoted);
4719 g_free (sheet->name_unquoted_collate_key);
4720 g_free (sheet->name_case_insensitive);
4721 /* Poison */
4722 sheet->name_quoted = (char *)0xdeadbeef;
4723 sheet->name_unquoted = (char *)0xdeadbeef;
4724 g_free (sheet->priv);
4725 g_ptr_array_free (sheet->sheet_views, TRUE);
4727 gnm_rvc_free (sheet->rendered_values);
4729 if (debug_FMR) {
4730 /* Keep object around. */
4731 return;
4734 G_OBJECT_CLASS (parent_class)->finalize (obj);
4737 /*****************************************************************************/
4740 * cb_empty_cell: A callback for sheet_foreach_cell_in_range
4741 * removes/clear all of the cells in the specified region.
4742 * Does NOT queue a redraw.
4744 * WARNING : This does NOT regenerate spans that were interrupted by
4745 * this cell and can now continue.
4747 static GnmValue *
4748 cb_empty_cell (GnmCellIter const *iter, gpointer user)
4750 int clear_flags = GPOINTER_TO_INT (user);
4751 #if 0
4752 /* TODO : here and other places flag a need to update the
4753 * row/col maxima.
4755 if (row >= sheet->rows.max_used || col >= sheet->cols.max_used) { }
4756 #endif
4758 sheet_cell_remove (iter->pp.sheet, iter->cell, FALSE,
4759 (clear_flags & CLEAR_RECALC_DEPS) &&
4760 iter->pp.wb->recursive_dirty_enabled);
4762 return NULL;
4766 * sheet_clear_region:
4767 * @sheet:
4768 * @start_col:
4769 * @start_row:
4770 * @end_col:
4771 * @end_row:
4772 * @clear_flags: If this is TRUE then styles are erased.
4773 * @cc: (nullable):
4775 * Clears are region of cells
4777 * We assemble a list of cells to destroy, since we will be making changes
4778 * to the structure being manipulated by the sheet_foreach_cell_in_range routine
4780 void
4781 sheet_clear_region (Sheet *sheet,
4782 int start_col, int start_row,
4783 int end_col, int end_row,
4784 int clear_flags,
4785 GOCmdContext *cc)
4787 GnmRange r;
4789 g_return_if_fail (IS_SHEET (sheet));
4790 g_return_if_fail (start_col <= end_col);
4791 g_return_if_fail (start_row <= end_row);
4793 r.start.col = start_col;
4794 r.start.row = start_row;
4795 r.end.col = end_col;
4796 r.end.row = end_row;
4798 if (clear_flags & CLEAR_VALUES && !(clear_flags & CLEAR_NOCHECKARRAY) &&
4799 sheet_range_splits_array (sheet, &r, NULL, cc, _("Clear")))
4800 return;
4802 /* Queue a redraw for cells being modified */
4803 if (clear_flags & (CLEAR_VALUES|CLEAR_FORMATS))
4804 sheet_redraw_region (sheet,
4805 start_col, start_row,
4806 end_col, end_row);
4808 /* Clear the style in the region (new_default will ref the style for us). */
4809 if (clear_flags & CLEAR_FORMATS) {
4810 sheet_style_set_range (sheet, &r, sheet_style_default (sheet));
4811 sheet_range_calc_spans (sheet, &r, GNM_SPANCALC_RE_RENDER|GNM_SPANCALC_RESIZE);
4812 rows_height_update (sheet, &r, TRUE);
4815 if (clear_flags & CLEAR_OBJECTS)
4816 sheet_objects_clear (sheet, &r, G_TYPE_NONE, NULL);
4817 else if (clear_flags & CLEAR_COMMENTS)
4818 sheet_objects_clear (sheet, &r, GNM_CELL_COMMENT_TYPE, NULL);
4820 /* TODO : how to handle objects ? */
4821 if (clear_flags & CLEAR_VALUES) {
4822 /* Remove or empty the cells depending on
4823 * whether or not there are comments
4825 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4826 start_col, start_row, end_col, end_row,
4827 &cb_empty_cell, GINT_TO_POINTER (clear_flags));
4829 if (!(clear_flags & CLEAR_NORESPAN)) {
4830 sheet_queue_respan (sheet, start_row, end_row);
4831 sheet_flag_status_update_range (sheet, &r);
4835 if (clear_flags & CLEAR_MERGES) {
4836 GSList *merged, *ptr;
4837 merged = gnm_sheet_merge_get_overlap (sheet, &r);
4838 for (ptr = merged ; ptr != NULL ; ptr = ptr->next)
4839 gnm_sheet_merge_remove (sheet, ptr->data);
4840 g_slist_free (merged);
4843 if (clear_flags & CLEAR_RECALC_DEPS)
4844 sheet_region_queue_recalc (sheet, &r);
4846 /* Always redraw */
4847 sheet_redraw_all (sheet, FALSE);
4850 static void
4851 sheet_clear_region_cb (GnmSheetRange *sr, int *flags)
4853 sheet_clear_region (sr->sheet,
4854 sr->range.start.col, sr->range.start.row,
4855 sr->range.end.col, sr->range.end.row,
4856 *flags | CLEAR_NOCHECKARRAY, NULL);
4861 * sheet_clear_region_undo:
4862 * @sr: #GnmSheetRange
4863 * @clear_flags: flags.
4865 * Returns: (transfer full): the new #GOUndo.
4867 GOUndo *sheet_clear_region_undo (GnmSheetRange *sr, int clear_flags)
4869 int *flags = g_new(int, 1);
4870 *flags = clear_flags;
4871 return go_undo_binary_new
4872 (sr, (gpointer)flags,
4873 (GOUndoBinaryFunc) sheet_clear_region_cb,
4874 (GFreeFunc) gnm_sheet_range_free,
4875 (GFreeFunc) g_free);
4879 /*****************************************************************************/
4881 void
4882 sheet_mark_dirty (Sheet *sheet)
4884 g_return_if_fail (IS_SHEET (sheet));
4886 if (sheet->workbook)
4887 go_doc_set_dirty (GO_DOC (sheet->workbook), TRUE);
4890 /****************************************************************************/
4892 static void
4893 sheet_cells_deps_move (GnmExprRelocateInfo *rinfo)
4895 Sheet *sheet = rinfo->origin_sheet;
4896 GPtrArray *deps = sheet_cells (sheet, &rinfo->origin);
4897 unsigned ui;
4899 /* Phase 1: collect all cells and remove them from hash. */
4900 for (ui = 0; ui < deps->len; ui++) {
4901 GnmCell *cell = g_ptr_array_index (deps, ui);
4902 gboolean needs_recalc = gnm_cell_needs_recalc (cell);
4903 sheet_cell_remove_from_hash (sheet, cell);
4904 if (needs_recalc) /* Do we need this now? */
4905 cell->base.flags |= DEPENDENT_NEEDS_RECALC;
4908 /* Phase 2: add all non-cell deps with positions */
4909 SHEET_FOREACH_DEPENDENT
4910 (sheet, dep, {
4911 GnmCellPos const *pos;
4912 if (!dependent_is_cell (dep) &&
4913 dependent_has_pos (dep) &&
4914 (pos = dependent_pos (dep)) &&
4915 range_contains (&rinfo->origin, pos->col, pos->row)) {
4916 dependent_unlink (dep);
4917 g_ptr_array_add (deps, dep);
4921 /* Phase 3: move everything and add cells to hash. */
4922 for (ui = 0; ui < deps->len; ui++) {
4923 GnmDependent *dep = g_ptr_array_index (deps, ui);
4925 dependent_move (dep, rinfo->col_offset, rinfo->row_offset);
4927 if (dependent_is_cell (dep))
4928 sheet_cell_add_to_hash (sheet, GNM_DEP_TO_CELL (dep));
4930 if (dep->texpr)
4931 dependent_link (dep);
4934 g_ptr_array_free (deps, TRUE);
4937 /* Moves the headers to their new location */
4938 static void
4939 sheet_colrow_move (Sheet *sheet, gboolean is_cols,
4940 int const old_pos, int const new_pos)
4942 ColRowSegment *segment = COLROW_GET_SEGMENT (is_cols ? &sheet->cols : &sheet->rows, old_pos);
4943 ColRowInfo *info = segment
4944 ? segment->info[COLROW_SUB_INDEX (old_pos)]
4945 : NULL;
4947 g_return_if_fail (old_pos >= 0);
4948 g_return_if_fail (new_pos >= 0);
4950 if (info == NULL)
4951 return;
4953 /* Update the position */
4954 segment->info[COLROW_SUB_INDEX (old_pos)] = NULL;
4955 sheet_colrow_add (sheet, info, is_cols, new_pos);
4958 static void
4959 sheet_colrow_set_collapse (Sheet *sheet, gboolean is_cols, int pos)
4961 ColRowInfo *cri;
4962 ColRowInfo const *vs = NULL;
4964 if (pos < 0 || pos >= colrow_max (is_cols, sheet))
4965 return;
4967 /* grab the next or previous col/row */
4968 if ((is_cols ? sheet->outline_symbols_right : sheet->outline_symbols_below)) {
4969 if (pos > 0)
4970 vs = sheet_colrow_get (sheet, pos-1, is_cols);
4971 } else if ((pos+1) < colrow_max (is_cols, sheet))
4972 vs = sheet_colrow_get (sheet, pos+1, is_cols);
4974 /* handle the case where an empty col/row should be marked collapsed */
4975 cri = sheet_colrow_get (sheet, pos, is_cols);
4976 if (cri != NULL)
4977 cri->is_collapsed = (vs != NULL && !vs->visible &&
4978 vs->outline_level > cri->outline_level);
4979 else if (vs != NULL && !vs->visible && vs->outline_level > 0) {
4980 cri = sheet_colrow_fetch (sheet, pos, is_cols);
4981 cri->is_collapsed = TRUE;
4985 static void
4986 combine_undo (GOUndo **pundo, GOUndo *u)
4988 if (pundo)
4989 *pundo = go_undo_combine (*pundo, u);
4990 else
4991 g_object_unref (u);
4994 typedef gboolean (*ColRowInsDelFunc) (Sheet *sheet, int idx, int count,
4995 GOUndo **pundo, GOCmdContext *cc);
4997 typedef struct {
4998 ColRowInsDelFunc func;
4999 Sheet *sheet;
5000 gboolean is_cols;
5001 int pos;
5002 int count;
5003 ColRowStateList *states;
5004 int state_start;
5005 } ColRowInsDelData;
5007 static void
5008 cb_undo_insdel (ColRowInsDelData *data)
5010 data->func (data->sheet, data->pos, data->count, NULL, NULL);
5011 colrow_set_states (data->sheet, data->is_cols,
5012 data->state_start, data->states);
5015 static void
5016 cb_undo_insdel_free (ColRowInsDelData *data)
5018 colrow_state_list_destroy (data->states);
5019 g_free (data);
5022 static gboolean
5023 sheet_insdel_colrow (Sheet *sheet, int pos, int count,
5024 GOUndo **pundo, GOCmdContext *cc,
5025 gboolean is_cols, gboolean is_insert,
5026 const char *description,
5027 ColRowInsDelFunc opposite)
5030 GnmRange kill_zone; /* The range whose contents will be lost. */
5031 GnmRange move_zone; /* The range whose contents will be moved. */
5032 GnmRange change_zone; /* The union of kill_zone and move_zone. */
5033 int i, last_pos, max_used_pos;
5034 int kill_start, kill_end, move_start, move_end;
5035 int scount = is_insert ? count : -count;
5036 ColRowStateList *states = NULL;
5037 GnmExprRelocateInfo reloc_info;
5038 GSList *l;
5039 gboolean sticky_end = TRUE;
5041 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
5042 g_return_val_if_fail (count > 0, TRUE);
5045 * The main undo for an insert col/row is delete col/row and vice versa.
5046 * In addition to that, we collect undo information that the main undo
5047 * operation will not restore -- for example the contents of the kill
5048 * zone.
5050 if (pundo) *pundo = NULL;
5052 last_pos = colrow_max (is_cols, sheet) - 1;
5053 max_used_pos = is_cols ? sheet->cols.max_used : sheet->rows.max_used;
5054 if (is_insert) {
5055 kill_start = last_pos - (count - 1);
5056 kill_end = last_pos;
5057 move_start = pos;
5058 move_end = kill_start - 1;
5059 } else {
5060 int max_count = last_pos + 1 - pos;
5061 if (count > max_count) {
5062 sticky_end = FALSE;
5063 count = max_count;
5065 kill_start = pos;
5066 kill_end = pos + (count - 1);
5067 move_start = kill_end + 1;
5068 move_end = last_pos;
5070 (is_cols ? range_init_cols : range_init_rows)
5071 (&kill_zone, sheet, kill_start, kill_end);
5072 (is_cols ? range_init_cols : range_init_rows)
5073 (&move_zone, sheet, move_start, move_end);
5074 change_zone = range_union (&kill_zone, &move_zone);
5076 /* 0. Check displaced/deleted region and ensure arrays aren't divided. */
5077 if (sheet_range_splits_array (sheet, &kill_zone, NULL, cc, description))
5078 return TRUE;
5079 if (move_start <= move_end &&
5080 sheet_range_splits_array (sheet, &move_zone, NULL, cc, description))
5081 return TRUE;
5084 * At this point we're committed. Anything that can go wrong should
5085 * have been ruled out already.
5088 if (0) {
5089 g_printerr ("Action = %s at %d count %d\n", description, pos, count);
5090 g_printerr ("Kill zone: %s\n", range_as_string (&kill_zone));
5093 /* 1. Delete all columns/rows in the kill zone */
5094 if (pundo) {
5095 combine_undo (pundo, clipboard_copy_range_undo (sheet, &kill_zone));
5096 states = colrow_get_states (sheet, is_cols, kill_start, kill_end);
5098 for (i = MIN (max_used_pos, kill_end); i >= kill_start; --i)
5099 (is_cols ? sheet_col_destroy : sheet_row_destroy)
5100 (sheet, i, TRUE);
5101 /* Brutally discard auto filter objects. Collect the rest for undo. */
5102 sheet_objects_clear (sheet, &kill_zone, GNM_FILTER_COMBO_TYPE, NULL);
5103 sheet_objects_clear (sheet, &kill_zone, G_TYPE_NONE, pundo);
5105 reloc_info.reloc_type = is_cols ? GNM_EXPR_RELOCATE_COLS : GNM_EXPR_RELOCATE_ROWS;
5106 reloc_info.sticky_end = sticky_end;
5107 reloc_info.origin_sheet = reloc_info.target_sheet = sheet;
5108 parse_pos_init_sheet (&reloc_info.pos, sheet);
5110 /* 2. Get rid of style dependents, see #741197. */
5111 sheet_style_clear_style_dependents (sheet, &change_zone);
5113 /* 3. Invalidate references to kill zone. */
5114 if (is_insert) {
5115 /* Done in the next step. */
5116 } else {
5117 reloc_info.origin = kill_zone;
5118 /* Force invalidation: */
5119 reloc_info.col_offset = is_cols ? last_pos + 1 : 0;
5120 reloc_info.row_offset = is_cols ? 0 : last_pos + 1;
5121 combine_undo (pundo, dependents_relocate (&reloc_info));
5124 /* 4. Fix references to the cells which are moving */
5125 reloc_info.origin = is_insert ? change_zone : move_zone;
5126 reloc_info.col_offset = is_cols ? scount : 0;
5127 reloc_info.row_offset = is_cols ? 0 : scount;
5128 combine_undo (pundo, dependents_relocate (&reloc_info));
5130 /* 5. Move the cells */
5131 sheet_cells_deps_move (&reloc_info);
5133 /* 6. Move the columns/rows to their new location. */
5134 if (is_insert) {
5135 /* From right to left */
5136 for (i = max_used_pos; i >= pos ; --i)
5137 sheet_colrow_move (sheet, is_cols, i, i + count);
5138 } else {
5139 /* From left to right */
5140 for (i = pos + count ; i <= max_used_pos; ++i)
5141 sheet_colrow_move (sheet, is_cols, i, i - count);
5143 sheet_colrow_set_collapse (sheet, is_cols, pos);
5144 sheet_colrow_set_collapse (sheet, is_cols,
5145 is_insert ? pos + count : last_pos - (count - 1));
5147 /* 7. Move formatting. */
5148 sheet_style_insdel_colrow (&reloc_info);
5150 /* 8. Move objects. */
5151 sheet_objects_relocate (&reloc_info, FALSE, pundo);
5153 /* 9. Move merges. */
5154 gnm_sheet_merge_relocate (&reloc_info, pundo);
5156 /* 10. Move filters. */
5157 gnm_sheet_filter_insdel_colrow (sheet, is_cols, is_insert, pos, count, pundo);
5159 /* Notify sheet of pending updates */
5160 sheet_mark_dirty (sheet);
5161 sheet->priv->recompute_visibility = TRUE;
5162 sheet_flag_recompute_spans (sheet);
5163 sheet_flag_status_update_range (sheet, &change_zone);
5164 if (is_cols)
5165 sheet->priv->reposition_objects.col = pos;
5166 else
5167 sheet->priv->reposition_objects.row = pos;
5169 /* WARNING WARNING WARNING
5170 * This is bad practice and should not really be here.
5171 * However, we need to ensure that update is run before
5172 * sv_panes_insdel_colrow plays with frozen panes, updating those can
5173 * trigger redraws before sheet_update has been called. */
5174 sheet_update (sheet);
5176 SHEET_FOREACH_VIEW (sheet, sv,
5177 sv_panes_insdel_colrow (sv, is_cols, is_insert, pos, count););
5179 /* The main undo is the opposite operation. */
5180 if (pundo) {
5181 ColRowInsDelData *data;
5182 GOUndo *u;
5184 data = g_new (ColRowInsDelData, 1);
5185 data->func = opposite;
5186 data->sheet = sheet;
5187 data->is_cols = is_cols;
5188 data->pos = pos;
5189 data->count = count;
5190 data->states = states;
5191 data->state_start = kill_start;
5193 u = go_undo_unary_new (data, (GOUndoUnaryFunc)cb_undo_insdel,
5194 (GFreeFunc)cb_undo_insdel_free);
5196 combine_undo (pundo, u);
5199 /* Reapply all filters. */
5200 for (l = sheet->filters; l; l = l->next) {
5201 GnmFilter *filter = l->data;
5202 gnm_filter_reapply (filter);
5205 return FALSE;
5209 * sheet_insert_cols:
5210 * @sheet: #Sheet
5211 * @col: At which position we want to insert
5212 * @count: The number of columns to be inserted
5213 * @pundo: (out): (transfer full): (allow-none): undo closure
5214 * @cc:
5216 gboolean
5217 sheet_insert_cols (Sheet *sheet, int col, int count,
5218 GOUndo **pundo, GOCmdContext *cc)
5220 return sheet_insdel_colrow (sheet, col, count, pundo, cc,
5221 TRUE, TRUE,
5222 _("Insert Columns"),
5223 sheet_delete_cols);
5227 * sheet_delete_cols:
5228 * @sheet: The sheet
5229 * @col: At which position we want to start deleting columns
5230 * @count: The number of columns to be deleted
5231 * @pundo: (out): (transfer full): (allow-none): undo closure
5232 * @cc: The command context
5234 gboolean
5235 sheet_delete_cols (Sheet *sheet, int col, int count,
5236 GOUndo **pundo, GOCmdContext *cc)
5238 return sheet_insdel_colrow (sheet, col, count, pundo, cc,
5239 TRUE, FALSE,
5240 _("Delete Columns"),
5241 sheet_insert_cols);
5245 * sheet_insert_rows:
5246 * @sheet: The sheet
5247 * @row: At which position we want to insert
5248 * @count: The number of rows to be inserted
5249 * @pundo: (out): (transfer full): (allow-none): undo closure
5250 * @cc: The command context
5252 gboolean
5253 sheet_insert_rows (Sheet *sheet, int row, int count,
5254 GOUndo **pundo, GOCmdContext *cc)
5256 return sheet_insdel_colrow (sheet, row, count, pundo, cc,
5257 FALSE, TRUE,
5258 _("Insert Rows"),
5259 sheet_delete_rows);
5263 * sheet_delete_rows:
5264 * @sheet: The sheet
5265 * @row: At which position we want to start deleting rows
5266 * @count: The number of rows to be deleted
5267 * @pundo: (out): (transfer full): (allow-none): undo closure
5268 * @cc: The command context
5270 gboolean
5271 sheet_delete_rows (Sheet *sheet, int row, int count,
5272 GOUndo **pundo, GOCmdContext *cc)
5274 return sheet_insdel_colrow (sheet, row, count, pundo, cc,
5275 FALSE, FALSE,
5276 _("Delete Rows"),
5277 sheet_insert_rows);
5281 * Callback for sheet_foreach_cell_in_range to remove a cell from the sheet
5282 * hash, unlink from the dependent collection and put it in a temporary list.
5284 static GnmValue *
5285 cb_collect_cell (GnmCellIter const *iter, gpointer user)
5287 GList ** l = user;
5288 GnmCell *cell = iter->cell;
5289 gboolean needs_recalc = gnm_cell_needs_recalc (cell);
5291 sheet_cell_remove_from_hash (iter->pp.sheet, cell);
5292 *l = g_list_prepend (*l, cell);
5293 if (needs_recalc)
5294 cell->base.flags |= DEPENDENT_NEEDS_RECALC;
5295 return NULL;
5299 * sheet_move_range:
5300 * @cc:
5301 * @rinfo:
5302 * @pundo: optionally NULL, caller releases result
5304 * Move a range as specified in @rinfo report warnings to @cc.
5305 * if @pundo is non NULL, invalidate references to the
5306 * target region that are being cleared, and store the undo information
5307 * in @pundo. If it is NULL do NOT INVALIDATE.
5309 void
5310 sheet_move_range (GnmExprRelocateInfo const *rinfo,
5311 GOUndo **pundo, GOCmdContext *cc)
5313 GList *cells = NULL;
5314 GnmCell *cell;
5315 GnmRange dst;
5316 gboolean out_of_range;
5318 g_return_if_fail (rinfo != NULL);
5319 g_return_if_fail (IS_SHEET (rinfo->origin_sheet));
5320 g_return_if_fail (IS_SHEET (rinfo->target_sheet));
5321 g_return_if_fail (rinfo->origin_sheet != rinfo->target_sheet ||
5322 rinfo->col_offset != 0 ||
5323 rinfo->row_offset != 0);
5325 dst = rinfo->origin;
5326 out_of_range = range_translate (&dst, rinfo->target_sheet,
5327 rinfo->col_offset, rinfo->row_offset);
5329 /* Redraw the src region in case anything was spanning */
5330 sheet_redraw_range (rinfo->origin_sheet, &rinfo->origin);
5332 /* 1. invalidate references to any cells in the destination range that
5333 * are not shared with the src. This must be done before the references
5334 * to from the src range are adjusted because they will point into
5335 * the destination.
5337 if (pundo != NULL) {
5338 *pundo = NULL;
5339 if (!out_of_range) {
5340 GSList *invalid;
5341 GnmExprRelocateInfo reloc_info;
5343 /* We need to be careful about invalidating references
5344 * to the old content of the destination region. We
5345 * only invalidate references to regions that are
5346 * actually lost. However, this care is only necessary
5347 * if the source and target sheets are the same.
5349 * Handle dst cells being pasted over
5351 if (rinfo->origin_sheet == rinfo->target_sheet &&
5352 range_overlap (&rinfo->origin, &dst))
5353 invalid = range_split_ranges (&rinfo->origin, &dst);
5354 else
5355 invalid = g_slist_append (NULL, gnm_range_dup (&dst));
5357 reloc_info.origin_sheet = reloc_info.target_sheet = rinfo->target_sheet;
5359 /* send to infinity to invalidate, but try to assist
5360 * the relocation heuristics only move in 1
5361 * dimension if possible to give us a chance to be
5362 * smart about partial invalidations */
5363 reloc_info.col_offset = gnm_sheet_get_max_cols (rinfo->target_sheet);
5364 reloc_info.row_offset = gnm_sheet_get_max_rows (rinfo->target_sheet);
5365 reloc_info.sticky_end = TRUE;
5366 if (rinfo->col_offset == 0) {
5367 reloc_info.col_offset = 0;
5368 reloc_info.reloc_type = GNM_EXPR_RELOCATE_ROWS;
5369 } else if (rinfo->row_offset == 0) {
5370 reloc_info.row_offset = 0;
5371 reloc_info.reloc_type = GNM_EXPR_RELOCATE_COLS;
5372 } else
5373 reloc_info.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE;
5375 parse_pos_init_sheet (&reloc_info.pos,
5376 rinfo->origin_sheet);
5378 while (invalid) {
5379 GnmRange *r = invalid->data;
5380 invalid = g_slist_remove (invalid, r);
5381 if (!range_overlap (r, &rinfo->origin)) {
5382 reloc_info.origin = *r;
5383 combine_undo (pundo,
5384 dependents_relocate (&reloc_info));
5386 g_free (r);
5390 * DO NOT handle src cells moving out the bounds.
5391 * that is handled elsewhere.
5395 /* 2. Fix references to and from the cells which are moving */
5396 combine_undo (pundo, dependents_relocate (rinfo));
5399 /* 3. Collect the cells */
5400 sheet_foreach_cell_in_range (rinfo->origin_sheet, CELL_ITER_IGNORE_NONEXISTENT,
5401 rinfo->origin.start.col, rinfo->origin.start.row,
5402 rinfo->origin.end.col, rinfo->origin.end.row,
5403 &cb_collect_cell, &cells);
5405 /* Reverse list so that we start at the top left (simplifies arrays). */
5406 cells = g_list_reverse (cells);
5408 /* 4. Clear the target area & invalidate references to it */
5409 if (!out_of_range)
5410 /* we can clear content but not styles from the destination
5411 * region without worrying if it overlaps with the source,
5412 * because we have already extracted the content. However,
5413 * we do need to queue anything that depends on the region for
5414 * recalc. */
5415 sheet_clear_region (rinfo->target_sheet,
5416 dst.start.col, dst.start.row,
5417 dst.end.col, dst.end.row,
5418 CLEAR_VALUES|CLEAR_RECALC_DEPS, cc);
5420 /* 5. Slide styles BEFORE the cells so that spans get computed properly */
5421 sheet_style_relocate (rinfo);
5423 /* 6. Insert the cells back */
5424 for (; cells != NULL ; cells = g_list_remove (cells, cell)) {
5425 cell = cells->data;
5427 /* check for out of bounds and delete if necessary */
5428 if ((cell->pos.col + rinfo->col_offset) >= gnm_sheet_get_max_cols (rinfo->target_sheet) ||
5429 (cell->pos.row + rinfo->row_offset) >= gnm_sheet_get_max_rows (rinfo->target_sheet)) {
5430 cell_free (cell);
5431 continue;
5434 /* Update the location */
5435 cell->base.sheet = rinfo->target_sheet;
5436 cell->pos.col += rinfo->col_offset;
5437 cell->pos.row += rinfo->row_offset;
5438 sheet_cell_add_to_hash (rinfo->target_sheet, cell);
5439 if (gnm_cell_has_expr (cell))
5440 dependent_link (GNM_CELL_TO_DEP (cell));
5443 /* 7. Move objects in the range */
5444 sheet_objects_relocate (rinfo, TRUE, pundo);
5445 gnm_sheet_merge_relocate (rinfo, pundo);
5447 /* 8. Notify sheet of pending update */
5448 sheet_flag_recompute_spans (rinfo->origin_sheet);
5449 sheet_flag_status_update_range (rinfo->origin_sheet, &rinfo->origin);
5452 static void
5453 sheet_colrow_default_calc (Sheet *sheet, double units,
5454 gboolean is_cols, gboolean is_pts)
5456 ColRowInfo *cri = is_cols
5457 ? &sheet->cols.default_style
5458 : &sheet->rows.default_style;
5460 g_return_if_fail (units > 0.);
5462 cri->is_default = TRUE;
5463 cri->hard_size = FALSE;
5464 cri->visible = TRUE;
5465 cri->spans = NULL;
5467 if (is_pts) {
5468 cri->size_pts = units;
5469 colrow_compute_pixels_from_pts (cri, sheet, is_cols, -1);
5470 } else {
5471 cri->size_pixels = units;
5472 colrow_compute_pts_from_pixels (cri, sheet, is_cols, -1);
5476 /************************************************************************/
5477 /* Col width support routines.
5481 * sheet_col_get_distance_pts:
5483 * Return the number of points between from_col to to_col
5484 * measured from the upper left corner.
5486 double
5487 sheet_col_get_distance_pts (Sheet const *sheet, int from, int to)
5489 ColRowInfo const *ci;
5490 double dflt, pts = 0., sign = 1.;
5491 int i;
5493 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5495 if (from > to) {
5496 int const tmp = to;
5497 to = from;
5498 from = tmp;
5499 sign = -1.;
5502 g_return_val_if_fail (from >= 0, 1.);
5503 g_return_val_if_fail (to <= gnm_sheet_get_max_cols (sheet), 1.);
5505 /* Do not use colrow_foreach, it ignores empties */
5506 dflt = sheet->cols.default_style.size_pts;
5507 for (i = from ; i < to ; ++i) {
5508 if (NULL == (ci = sheet_col_get (sheet, i)))
5509 pts += dflt;
5510 else if (ci->visible)
5511 pts += ci->size_pts;
5514 if (sheet->display_formulas)
5515 pts *= 2.;
5517 return pts * sign;
5521 * sheet_col_get_distance_pixels:
5523 * Return the number of pixels between from_col to to_col
5524 * measured from the upper left corner.
5527 sheet_col_get_distance_pixels (Sheet const *sheet, int from, int to)
5529 ColRowInfo const *ci;
5530 int dflt, pixels = 0, sign = 1;
5531 int i;
5533 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5535 if (from > to) {
5536 int const tmp = to;
5537 to = from;
5538 from = tmp;
5539 sign = -1;
5542 g_return_val_if_fail (from >= 0, 1);
5543 g_return_val_if_fail (to <= gnm_sheet_get_max_cols (sheet), 1);
5545 /* Do not use colrow_foreach, it ignores empties */
5546 dflt = sheet_col_get_default_size_pixels (sheet);
5547 for (i = from ; i < to ; ++i) {
5548 if (NULL == (ci = sheet_col_get (sheet, i)))
5549 pixels += dflt;
5550 else if (ci->visible)
5551 pixels += ci->size_pixels;
5554 return pixels * sign;
5558 * sheet_col_set_size_pts:
5559 * @sheet: The sheet
5560 * @col: The col
5561 * @width_pts: The desired widtht in pts
5562 * @set_by_user: TRUE if this was done by a user (ie, user manually
5563 * set the width)
5565 * Sets width of a col in pts, INCLUDING left and right margins, and the far
5566 * grid line. This is a low level internal routine. It does NOT redraw,
5567 * or reposition objects.
5569 void
5570 sheet_col_set_size_pts (Sheet *sheet, int col, double width_pts,
5571 gboolean set_by_user)
5573 ColRowInfo *ci;
5575 g_return_if_fail (IS_SHEET (sheet));
5576 g_return_if_fail (width_pts > 0.0);
5578 ci = sheet_col_fetch (sheet, col);
5579 ci->hard_size = set_by_user;
5580 if (ci->size_pts == width_pts)
5581 return;
5583 ci->size_pts = width_pts;
5584 colrow_compute_pixels_from_pts (ci, sheet, TRUE, -1);
5586 sheet->priv->recompute_visibility = TRUE;
5587 sheet_flag_recompute_spans (sheet);
5588 if (sheet->priv->reposition_objects.col > col)
5589 sheet->priv->reposition_objects.col = col;
5592 void
5593 sheet_col_set_size_pixels (Sheet *sheet, int col, int width_pixels,
5594 gboolean set_by_user)
5596 ColRowInfo *ci;
5598 g_return_if_fail (IS_SHEET (sheet));
5599 g_return_if_fail (width_pixels > 0.0);
5601 ci = sheet_col_fetch (sheet, col);
5602 ci->hard_size = set_by_user;
5603 if (ci->size_pixels == width_pixels)
5604 return;
5606 ci->size_pixels = width_pixels;
5607 colrow_compute_pts_from_pixels (ci, sheet, TRUE, -1);
5609 sheet->priv->recompute_visibility = TRUE;
5610 sheet_flag_recompute_spans (sheet);
5611 if (sheet->priv->reposition_objects.col > col)
5612 sheet->priv->reposition_objects.col = col;
5616 * sheet_col_get_default_size_pts:
5618 * Return the default number of pts in a column, including margins.
5619 * This function returns the raw sum, no rounding etc.
5621 double
5622 sheet_col_get_default_size_pts (Sheet const *sheet)
5624 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5625 return sheet->cols.default_style.size_pts;
5629 sheet_col_get_default_size_pixels (Sheet const *sheet)
5631 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5632 return sheet->cols.default_style.size_pixels;
5635 void
5636 sheet_col_set_default_size_pts (Sheet *sheet, double width_pts)
5638 g_return_if_fail (IS_SHEET (sheet));
5639 g_return_if_fail (width_pts > 0.);
5641 sheet_colrow_default_calc (sheet, width_pts, TRUE, TRUE);
5642 sheet->priv->recompute_visibility = TRUE;
5643 sheet_flag_recompute_spans (sheet);
5644 sheet->priv->reposition_objects.col = 0;
5646 void
5647 sheet_col_set_default_size_pixels (Sheet *sheet, int width_pixels)
5649 g_return_if_fail (IS_SHEET (sheet));
5651 sheet_colrow_default_calc (sheet, width_pixels, TRUE, FALSE);
5652 sheet->priv->recompute_visibility = TRUE;
5653 sheet_flag_recompute_spans (sheet);
5654 sheet->priv->reposition_objects.col = 0;
5657 /**************************************************************************/
5658 /* Row height support routines
5662 * sheet_row_get_distance_pts:
5664 * Return the number of points between from_row to to_row
5665 * measured from the upper left corner.
5667 double
5668 sheet_row_get_distance_pts (Sheet const *sheet, int from, int to)
5670 ColRowSegment const *segment;
5671 ColRowInfo const *ri;
5672 double const default_size = sheet->rows.default_style.size_pts;
5673 double pts = 0., sign = 1.;
5674 int i;
5676 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5678 if (from > to) {
5679 int const tmp = to;
5680 to = from;
5681 from = tmp;
5682 sign = -1.;
5685 g_return_val_if_fail (from >= 0, 1.);
5686 g_return_val_if_fail (to <= gnm_sheet_get_max_rows (sheet), 1.);
5688 /* Do not use colrow_foreach, it ignores empties.
5689 * Optimize this so that long jumps are not quite so horrific
5690 * for performance.
5692 for (i = from ; i < to ; ++i) {
5693 segment = COLROW_GET_SEGMENT (&(sheet->rows), i);
5695 if (segment != NULL) {
5696 ri = segment->info[COLROW_SUB_INDEX (i)];
5697 if (ri == NULL)
5698 pts += default_size;
5699 else if (ri->visible)
5700 pts += ri->size_pts;
5701 } else {
5702 int segment_end = COLROW_SEGMENT_END (i)+1;
5703 if (segment_end > to)
5704 segment_end = to;
5705 pts += default_size * (segment_end - i);
5706 i = segment_end-1;
5710 return pts*sign;
5714 * sheet_row_get_distance_pixels:
5716 * Return the number of pixels between from_row to to_row
5717 * measured from the upper left corner.
5720 sheet_row_get_distance_pixels (Sheet const *sheet, int from, int to)
5722 ColRowInfo const *ci;
5723 int dflt, pixels = 0, sign = 1;
5724 int i;
5726 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5728 if (from > to) {
5729 int const tmp = to;
5730 to = from;
5731 from = tmp;
5732 sign = -1;
5735 g_return_val_if_fail (from >= 0, 1);
5736 g_return_val_if_fail (to <= gnm_sheet_get_max_rows (sheet), 1);
5738 /* Do not use colrow_foreach, it ignores empties */
5739 dflt = sheet_row_get_default_size_pixels (sheet);
5740 for (i = from ; i < to ; ++i) {
5741 if (NULL == (ci = sheet_row_get (sheet, i)))
5742 pixels += dflt;
5743 else if (ci->visible)
5744 pixels += ci->size_pixels;
5747 return pixels * sign;
5751 * sheet_row_set_size_pts:
5752 * @sheet: The sheet
5753 * @row: The row
5754 * @height_pts: The desired height in pts
5755 * @set_by_user: TRUE if this was done by a user (ie, user manually
5756 * set the height)
5758 * Sets height of a row in pts, INCLUDING top and bottom margins, and the lower
5759 * grid line. This is a low level internal routine. It does NOT redraw,
5760 * or reposition objects.
5762 void
5763 sheet_row_set_size_pts (Sheet *sheet, int row, double height_pts,
5764 gboolean set_by_user)
5766 ColRowInfo *ri;
5768 g_return_if_fail (IS_SHEET (sheet));
5769 g_return_if_fail (height_pts > 0.0);
5771 ri = sheet_row_fetch (sheet, row);
5772 ri->hard_size = set_by_user;
5773 if (ri->size_pts == height_pts)
5774 return;
5776 ri->size_pts = height_pts;
5777 colrow_compute_pixels_from_pts (ri, sheet, FALSE, -1);
5779 sheet->priv->recompute_visibility = TRUE;
5780 if (sheet->priv->reposition_objects.row > row)
5781 sheet->priv->reposition_objects.row = row;
5785 * sheet_row_set_size_pixels:
5786 * @sheet: The sheet
5787 * @row: The row
5788 * @height_pixels: The desired height
5789 * @set_by_user: TRUE if this was done by a user (ie, user manually
5790 * set the width)
5792 * Sets height of a row in pixels, INCLUDING top and bottom margins, and the lower
5793 * grid line.
5795 void
5796 sheet_row_set_size_pixels (Sheet *sheet, int row, int height_pixels,
5797 gboolean set_by_user)
5799 ColRowInfo *ri;
5801 g_return_if_fail (IS_SHEET (sheet));
5802 g_return_if_fail (height_pixels > 0);
5804 ri = sheet_row_fetch (sheet, row);
5805 ri->hard_size = set_by_user;
5806 if (ri->size_pixels == height_pixels)
5807 return;
5809 ri->size_pixels = height_pixels;
5810 colrow_compute_pts_from_pixels (ri, sheet, FALSE, -1);
5812 sheet->priv->recompute_visibility = TRUE;
5813 if (sheet->priv->reposition_objects.row > row)
5814 sheet->priv->reposition_objects.row = row;
5818 * sheet_row_get_default_size_pts:
5820 * Return the defaul number of units in a row, including margins.
5821 * This function returns the raw sum, no rounding etc.
5823 double
5824 sheet_row_get_default_size_pts (Sheet const *sheet)
5826 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5827 return sheet->rows.default_style.size_pts;
5831 sheet_row_get_default_size_pixels (Sheet const *sheet)
5833 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5834 return sheet->rows.default_style.size_pixels;
5837 void
5838 sheet_row_set_default_size_pts (Sheet *sheet, double height_pts)
5840 g_return_if_fail (IS_SHEET (sheet));
5842 sheet_colrow_default_calc (sheet, height_pts, FALSE, TRUE);
5843 sheet->priv->recompute_visibility = TRUE;
5844 sheet->priv->reposition_objects.row = 0;
5847 void
5848 sheet_row_set_default_size_pixels (Sheet *sheet, int height_pixels)
5850 g_return_if_fail (IS_SHEET (sheet));
5852 sheet_colrow_default_calc (sheet, height_pixels, FALSE, FALSE);
5853 sheet->priv->recompute_visibility = TRUE;
5854 sheet->priv->reposition_objects.row = 0;
5857 /****************************************************************************/
5859 void
5860 sheet_scrollbar_config (Sheet const *sheet)
5862 g_return_if_fail (IS_SHEET (sheet));
5864 SHEET_FOREACH_CONTROL (sheet, view, control,
5865 sc_scrollbar_config (control););
5868 /*****************************************************************************/
5869 typedef struct
5871 gboolean is_column;
5872 Sheet *sheet;
5873 } closure_clone_colrow;
5875 static gboolean
5876 sheet_clone_colrow_info_item (GnmColRowIter const *iter, void *user_data)
5878 closure_clone_colrow const *closure = user_data;
5879 ColRowInfo *new_colrow = sheet_colrow_fetch (closure->sheet,
5880 iter->pos, closure->is_column);
5881 colrow_copy (new_colrow, iter->cri);
5882 return FALSE;
5885 static void
5886 sheet_dup_colrows (Sheet const *src, Sheet *dst)
5888 closure_clone_colrow closure;
5889 int max_col = MIN (gnm_sheet_get_max_cols (src), gnm_sheet_get_max_cols (dst)),
5890 max_row = MIN (gnm_sheet_get_max_rows (src), gnm_sheet_get_max_rows (dst));
5892 closure.sheet = dst;
5893 closure.is_column = TRUE;
5894 colrow_foreach (&src->cols, 0, max_col - 1,
5895 &sheet_clone_colrow_info_item, &closure);
5896 closure.is_column = FALSE;
5897 colrow_foreach (&src->rows, 0, max_row - 1,
5898 &sheet_clone_colrow_info_item, &closure);
5900 sheet_col_set_default_size_pixels (dst,
5901 sheet_col_get_default_size_pixels (src));
5902 sheet_row_set_default_size_pixels (dst,
5903 sheet_row_get_default_size_pixels (src));
5905 dst->cols.max_outline_level = src->cols.max_outline_level;
5906 dst->rows.max_outline_level = src->rows.max_outline_level;
5909 static void
5910 sheet_dup_styles (Sheet const *src, Sheet *dst)
5912 static GnmCellPos const corner = { 0, 0 };
5913 GnmRange r;
5914 GnmStyleList *styles;
5916 sheet_style_set_auto_pattern_color (
5917 dst, sheet_style_get_auto_pattern_color (src));
5919 styles = sheet_style_get_range (src, range_init_full_sheet (&r, src));
5920 sheet_style_set_list (dst, &corner, styles, NULL, NULL);
5921 style_list_free (styles);
5924 static void
5925 sheet_dup_merged_regions (Sheet const *src, Sheet *dst)
5927 GSList *ptr;
5929 for (ptr = src->list_merged ; ptr != NULL ; ptr = ptr->next)
5930 gnm_sheet_merge_add (dst, ptr->data, FALSE, NULL);
5933 static void
5934 sheet_dup_names (Sheet const *src, Sheet *dst)
5936 GSList *names = gnm_named_expr_collection_list (src->names);
5937 GSList *l;
5938 GnmParsePos dst_pp;
5940 if (names == NULL)
5941 return;
5943 parse_pos_init_sheet (&dst_pp, dst);
5945 /* Pass 1: add placeholders. */
5946 for (l = names; l; l = l->next) {
5947 GnmNamedExpr *src_nexpr = l->data;
5948 char const *name = expr_name_name (src_nexpr);
5949 GnmNamedExpr *dst_nexpr =
5950 gnm_named_expr_collection_lookup (dst->names, name);
5951 GnmExprTop const *texpr;
5953 if (dst_nexpr)
5954 continue;
5956 texpr = gnm_expr_top_new_constant (value_new_empty ());
5957 expr_name_add (&dst_pp, name, texpr , NULL, TRUE, NULL);
5960 /* Pass 2: assign the right expression. */
5961 for (l = names; l; l = l->next) {
5962 GnmNamedExpr *src_nexpr = l->data;
5963 char const *name = expr_name_name (src_nexpr);
5964 GnmNamedExpr *dst_nexpr =
5965 gnm_named_expr_collection_lookup (dst->names, name);
5966 GnmExprTop const *texpr;
5968 if (!dst_nexpr) {
5969 g_warning ("Trouble while duplicating name %s", name);
5970 continue;
5973 if (!dst_nexpr->is_editable)
5974 continue;
5976 texpr = gnm_expr_top_relocate_sheet (src_nexpr->texpr, src, dst);
5977 expr_name_set_expr (dst_nexpr, texpr);
5980 g_slist_free (names);
5983 static void
5984 cb_sheet_cell_copy (G_GNUC_UNUSED gpointer unused, gpointer key, gpointer new_sheet_param)
5986 GnmCell const *cell = key;
5987 Sheet *dst = new_sheet_param;
5988 Sheet *src;
5989 GnmExprTop const *texpr;
5991 g_return_if_fail (dst != NULL);
5992 g_return_if_fail (cell != NULL);
5994 src = cell->base.sheet;
5995 texpr = cell->base.texpr;
5997 if (texpr && gnm_expr_top_is_array_corner (texpr)) {
5998 int cols, rows;
6000 texpr = gnm_expr_top_relocate_sheet (texpr, src, dst);
6001 gnm_expr_top_get_array_size (texpr, &cols, &rows);
6003 gnm_cell_set_array_formula (dst,
6004 cell->pos.col, cell->pos.row,
6005 cell->pos.col + cols - 1,
6006 cell->pos.row + rows - 1,
6007 gnm_expr_top_new (gnm_expr_copy (gnm_expr_top_get_array_expr (texpr))));
6009 gnm_expr_top_unref (texpr);
6010 } else if (texpr && gnm_expr_top_is_array_elem (texpr, NULL, NULL)) {
6011 /* Not a corner -- ignore. */
6012 } else {
6013 GnmCell *new_cell = sheet_cell_create (dst, cell->pos.col, cell->pos.row);
6014 if (gnm_cell_has_expr (cell)) {
6015 texpr = gnm_expr_top_relocate_sheet (texpr, src, dst);
6016 gnm_cell_set_expr_and_value (new_cell, texpr, value_new_empty (), TRUE);
6017 gnm_expr_top_unref (texpr);
6018 } else
6019 gnm_cell_set_value (new_cell, value_dup (cell->value));
6023 static void
6024 sheet_dup_cells (Sheet const *src, Sheet *dst)
6026 sheet_cell_foreach (src, &cb_sheet_cell_copy, dst);
6027 sheet_region_queue_recalc (dst, NULL);
6030 static void
6031 sheet_dup_filters (Sheet const *src, Sheet *dst)
6033 GSList *ptr;
6034 for (ptr = src->filters ; ptr != NULL ; ptr = ptr->next)
6035 gnm_filter_dup (ptr->data, dst);
6036 dst->filters = g_slist_reverse (dst->filters);
6040 * sheet_dup:
6041 * @source_sheet: #Sheet
6043 * Create a new Sheet and return it.
6044 * Returns: (transfer full): the newly allocated #Sheet.
6046 Sheet *
6047 sheet_dup (Sheet const *src)
6049 Workbook *wb;
6050 Sheet *dst;
6051 char *name;
6052 GList *l;
6054 g_return_val_if_fail (IS_SHEET (src), NULL);
6055 g_return_val_if_fail (src->workbook != NULL, NULL);
6057 wb = src->workbook;
6058 name = workbook_sheet_get_free_name (wb, src->name_unquoted,
6059 TRUE, TRUE);
6060 dst = sheet_new_with_type (wb, name, src->sheet_type,
6061 src->size.max_cols, src->size.max_rows);
6062 g_free (name);
6064 dst->protected_allow = src->protected_allow;
6065 g_object_set (dst,
6066 "zoom-factor", src->last_zoom_factor_used,
6067 "text-is-rtl", src->text_is_rtl,
6068 "visibility", src->visibility,
6069 "protected", src->is_protected,
6070 "display-formulas", src->display_formulas,
6071 "display-zeros", !src->hide_zero,
6072 "display-grid", !src->hide_grid,
6073 "display-column-header", !src->hide_col_header,
6074 "display-row-header", !src->hide_row_header,
6075 "display-outlines", src->display_outlines,
6076 "display-outlines-below", src->outline_symbols_below,
6077 "display-outlines-right", src->outline_symbols_right,
6078 "conventions", src->convs,
6079 "tab-foreground", src->tab_text_color,
6080 "tab-background", src->tab_color,
6081 NULL);
6083 gnm_print_info_free (dst->print_info);
6084 dst->print_info = gnm_print_info_dup (src->print_info);
6086 sheet_dup_styles (src, dst);
6087 sheet_dup_merged_regions (src, dst);
6088 sheet_dup_colrows (src, dst);
6089 sheet_dup_names (src, dst);
6090 sheet_dup_cells (src, dst);
6091 sheet_objects_dup (src, dst, NULL);
6092 sheet_dup_filters (src, dst); /* must be after objects */
6094 #warning selection is in view
6095 #warning freeze/thaw is in view
6097 g_object_unref (dst->solver_parameters);
6098 dst->solver_parameters = gnm_solver_param_dup (src->solver_parameters, dst);
6100 for (l = src->scenarios; l; l = l->next) {
6101 GnmScenario *src_sc = l->data;
6102 GnmScenario *dst_sc = gnm_scenario_dup (src_sc, dst);
6103 dst->scenarios = g_list_prepend (dst->scenarios, dst_sc);
6105 dst->scenarios = g_list_reverse (dst->scenarios);
6107 sheet_mark_dirty (dst);
6108 sheet_redraw_all (dst, TRUE);
6110 return dst;
6114 * sheet_set_outline_direction:
6115 * @sheet: the sheet
6116 * @is_cols: use cols or rows
6118 * When changing the placement of outline collapse markers the flags
6119 * need to be recomputed.
6121 void
6122 sheet_set_outline_direction (Sheet *sheet, gboolean is_cols)
6124 unsigned i;
6125 g_return_if_fail (IS_SHEET (sheet));
6127 /* not particularly efficient, but this is not a hot spot */
6128 for (i = colrow_max (is_cols, sheet); i-- > 0 ; )
6129 sheet_colrow_set_collapse (sheet, is_cols, i);
6133 * sheet_get_view:
6134 * @sheet:
6135 * @wbv:
6137 * Find the SheetView corresponding to the supplied @wbv.
6138 * Returns: (transfer none): the view.
6140 SheetView *
6141 sheet_get_view (Sheet const *sheet, WorkbookView const *wbv)
6143 if (sheet == NULL)
6144 return NULL;
6146 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6148 SHEET_FOREACH_VIEW (sheet, view, {
6149 if (sv_wbv (view) == wbv)
6150 return view;
6152 return NULL;
6155 static gboolean
6156 cb_queue_respan (GnmColRowIter const *iter, void *user_data)
6158 ((ColRowInfo *)(iter->cri))->needs_respan = TRUE;
6159 return FALSE;
6163 * sheet_queue_respan:
6164 * @sheet:
6165 * @start_row:
6166 * @end_row:
6168 * queues a span generation for the selected rows.
6169 * the caller is responsible for queuing a redraw
6171 void
6172 sheet_queue_respan (Sheet const *sheet, int start_row, int end_row)
6174 colrow_foreach (&sheet->rows, start_row, end_row,
6175 cb_queue_respan, NULL);
6178 void
6179 sheet_cell_queue_respan (GnmCell *cell)
6181 ColRowInfo *ri = sheet_row_get (cell->base.sheet, cell->pos.row);
6182 ri->needs_respan = TRUE;
6187 * sheet_get_comment:
6188 * @sheet: #Sheet const *
6189 * @pos: #GnmCellPos const *
6191 * If there is a cell comment at @pos in @sheet return it.
6193 * Caller does get a reference to the object if it exists.
6194 * Returns: (transfer full): the comment or %NULL.
6196 GnmComment *
6197 sheet_get_comment (Sheet const *sheet, GnmCellPos const *pos)
6199 GnmRange r;
6200 GSList *comments;
6201 GnmComment *res;
6203 GnmRange const *mr;
6205 mr = gnm_sheet_merge_contains_pos (sheet, pos);
6207 if (mr)
6208 comments = sheet_objects_get (sheet, mr, GNM_CELL_COMMENT_TYPE);
6209 else {
6210 r.start = r.end = *pos;
6211 comments = sheet_objects_get (sheet, &r, GNM_CELL_COMMENT_TYPE);
6213 if (!comments)
6214 return NULL;
6216 /* This assumes just one comment per cell. */
6217 res = comments->data;
6218 g_slist_free (comments);
6219 return res;
6222 static GnmValue *
6223 cb_find_extents (GnmCellIter const *iter, GnmCellPos *extent)
6225 if (extent->col < iter->pp.eval.col)
6226 extent->col = iter->pp.eval.col;
6227 if (extent->row < iter->pp.eval.row)
6228 extent->row = iter->pp.eval.row;
6229 return NULL;
6233 * sheet_range_trim:
6234 * @sheet: sheet cells are contained on
6235 * @r: range to trim empty cells from
6236 * @cols: trim from right
6237 * @rows: trim from bottom
6239 * This removes empty rows/cols from the
6240 * right hand or bottom edges of the range
6241 * depending on the value of @cols or @rows.
6243 * Return value: TRUE if the range was totally empty.
6245 gboolean
6246 sheet_range_trim (Sheet const *sheet, GnmRange *r,
6247 gboolean cols, gboolean rows)
6249 GnmCellPos extent = { -1, -1 };
6251 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
6252 g_return_val_if_fail (r != NULL, TRUE);
6254 sheet_foreach_cell_in_range (
6255 (Sheet *)sheet, CELL_ITER_IGNORE_BLANK,
6256 r->start.col, r->start.row, r->end.col, r->end.row,
6257 (CellIterFunc) cb_find_extents, &extent);
6259 if (extent.col < 0 || extent.row < 0)
6260 return TRUE;
6261 if (cols)
6262 r->end.col = extent.col;
6263 if (rows)
6264 r->end.row = extent.row;
6265 return FALSE;
6269 * sheet_range_has_heading:
6270 * @sheet: Sheet to check
6271 * @src: GnmRange to check
6272 * @top: Flag
6274 * Checks for a header row in @sheet!@src. If top is true it looks for a
6275 * header row from the top and if false it looks for a header col from the
6276 * left
6278 * Returns: TRUE if @src seems to have a heading
6280 gboolean
6281 sheet_range_has_heading (Sheet const *sheet, GnmRange const *src,
6282 gboolean top, gboolean ignore_styles)
6284 GnmCell const *a, *b;
6285 int length, i;
6287 /* There is only one row or col */
6288 if (top) {
6289 if (src->end.row <= src->start.row)
6290 return FALSE;
6291 length = src->end.col - src->start.col + 1;
6292 } else {
6293 if (src->end.col <= src->start.col)
6294 return FALSE;
6295 length = src->end.row - src->start.row + 1;
6298 for (i = 0; i < length; i++) {
6299 if (top) {
6300 a = sheet_cell_get (sheet,
6301 src->start.col + i, src->start.row);
6302 b = sheet_cell_get (sheet,
6303 src->start.col + i, src->start.row + 1);
6304 } else {
6305 a = sheet_cell_get (sheet,
6306 src->start.col, src->start.row + i);
6307 b = sheet_cell_get (sheet,
6308 src->start.col + 1, src->start.row + i);
6311 /* be anal */
6312 if (a == NULL || a->value == NULL || b == NULL || b->value == NULL)
6313 continue;
6315 if (VALUE_IS_NUMBER (a->value)) {
6316 if (!VALUE_IS_NUMBER (b->value))
6317 return TRUE;
6318 /* check for style differences */
6319 } else if (a->value->v_any.type != b->value->v_any.type)
6320 return TRUE;
6322 /* Look for style differences */
6323 if (!ignore_styles &&
6324 !gnm_style_equal_header (gnm_cell_get_style (a),
6325 gnm_cell_get_style (b), top))
6326 return TRUE;
6329 return FALSE;
6333 * gnm_sheet_foreach_name:
6334 * @sheet: #Sheet
6335 * @func: (scope call): #GHFunc
6336 * @data: user data.
6338 * Executes @func for each name in @sheet.
6340 void
6341 gnm_sheet_foreach_name (Sheet const *sheet, GHFunc func, gpointer data)
6343 g_return_if_fail (IS_SHEET (sheet));
6345 if (sheet->names)
6346 gnm_named_expr_collection_foreach (sheet->names, func, data);
6350 * gnm_sheet_get_size:
6351 * @sheet: #Sheet
6353 * Returns: (transfer none): the sheet size.
6355 GnmSheetSize const *
6356 gnm_sheet_get_size (Sheet const *sheet)
6358 static const GnmSheetSize default_size = {
6359 GNM_DEFAULT_COLS, GNM_DEFAULT_ROWS
6362 if (G_UNLIKELY (!sheet)) {
6363 g_warning ("NULL sheet in gnm_sheet_get_size!");
6364 /* FIXME: This needs to go. */
6365 return &default_size;
6368 if (G_UNLIKELY (sheet->being_constructed))
6369 g_warning ("Access to sheet size during construction!");
6371 return &sheet->size;
6375 * gnm_sheet_get_size2:
6376 * @sheet: #Sheet, might be %NULL
6377 * @wb: #Workbook, must be non %NULL if @sheet is %NULL
6379 * Returns: (transfer none): the sheet size if @sheet is non %NULL, or the
6380 * default sheet size for @wb.
6382 GnmSheetSize const *
6383 gnm_sheet_get_size2 (Sheet const *sheet, Workbook const *wb)
6385 return sheet
6386 ? gnm_sheet_get_size (sheet)
6387 : workbook_get_sheet_size (wb);
6390 void
6391 gnm_sheet_set_solver_params (Sheet *sheet, GnmSolverParameters *param)
6393 g_return_if_fail (IS_SHEET (sheet));
6394 g_return_if_fail (GNM_IS_SOLVER_PARAMETERS (param));
6396 g_object_ref (param);
6397 g_object_unref (sheet->solver_parameters);
6398 sheet->solver_parameters = param;
6401 /* ------------------------------------------------------------------------- */
6404 * gnm_sheet_scenario_new:
6405 * @sheet:  #Sheet
6406 * @name: the new scenario name.
6408 * Returns: (transfer full): the newly created #GnmScenario.
6410 GnmScenario *
6411 gnm_sheet_scenario_new (Sheet *sheet, const char *name)
6413 GnmScenario *sc;
6414 char *actual_name;
6416 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6417 g_return_val_if_fail (name != NULL, NULL);
6419 /* Check if a scenario having the same name already exists. */
6420 if (gnm_sheet_scenario_find (sheet, name)) {
6421 GString *str = g_string_new (NULL);
6422 gchar *tmp;
6423 int i, j, len;
6425 len = strlen (name);
6426 if (len > 1 && name [len - 1] == ']') {
6427 for (i = len - 2; i > 0; i--) {
6428 if (! g_ascii_isdigit (name [i]))
6429 break;
6432 tmp = g_strdup (name);
6433 if (i > 0 && name [i] == '[')
6434 tmp [i] = '\0';
6435 } else
6436 tmp = g_strdup (name);
6438 for (j = 1; ; j++) {
6439 g_string_printf (str, "%s [%d]", tmp, j);
6440 if (!gnm_sheet_scenario_find (sheet, str->str)) {
6441 actual_name = g_string_free (str, FALSE);
6442 str = NULL;
6443 break;
6446 if (str)
6447 g_string_free (str, TRUE);
6448 g_free (tmp);
6449 } else
6450 actual_name = g_strdup (name);
6452 sc = gnm_scenario_new (actual_name, sheet);
6454 g_free (actual_name);
6456 return sc;
6460 * gnm_sheet_scenario_find:
6461 * @sheet:  #Sheet
6462 * @name: the scenario name.
6464 * Returns: (transfer none): the newly created #GnmScenario.
6466 GnmScenario *
6467 gnm_sheet_scenario_find (Sheet *sheet, const char *name)
6469 GList *l;
6471 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6472 g_return_val_if_fail (name != NULL, NULL);
6474 for (l = sheet->scenarios; l; l = l->next) {
6475 GnmScenario *sc = l->data;
6476 if (strcmp (name, sc->name) == 0)
6477 return sc;
6480 return NULL;
6484 * gnm_sheet_scenario_add:
6485 * @sheet:  #Sheet
6486 * @sc: (transfer full): #GnmScenario
6489 void
6490 gnm_sheet_scenario_add (Sheet *sheet, GnmScenario *sc)
6492 g_return_if_fail (IS_SHEET (sheet));
6493 g_return_if_fail (GNM_IS_SCENARIO (sc));
6495 /* We take ownership of the ref. */
6496 sheet->scenarios = g_list_append (sheet->scenarios, sc);
6499 void
6500 gnm_sheet_scenario_remove (Sheet *sheet, GnmScenario *sc)
6502 g_return_if_fail (IS_SHEET (sheet));
6503 g_return_if_fail (GNM_IS_SCENARIO (sc));
6505 sheet->scenarios = g_list_remove (sheet->scenarios, sc);
6506 g_object_unref (sc);
6509 /* ------------------------------------------------------------------------- */
6512 * gnm_sheet_get_sort_setups:
6513 * @sheet: #Sheet
6515 * Returns: (transfer none): the sort setups for @sheet.
6517 GHashTable *
6518 gnm_sheet_get_sort_setups (Sheet *sheet)
6520 GHashTable *hash = sheet->sort_setups;
6522 if (hash == NULL)
6523 hash = sheet->sort_setups =
6524 g_hash_table_new_full
6525 (g_str_hash, g_str_equal,
6526 g_free, (GDestroyNotify)gnm_sort_data_destroy);
6528 return hash;
6531 void
6532 gnm_sheet_add_sort_setup (Sheet *sheet, char *key, gpointer setup)
6534 GHashTable *hash = gnm_sheet_get_sort_setups (sheet);
6536 g_hash_table_insert (hash, key, setup);
6540 * gnm_sheet_find_sort_setup:
6541 * @sheet: #Sheet
6542 * @key:
6544 * Returns: (transfer none): the found sort setup or %NULL.
6546 gconstpointer
6547 gnm_sheet_find_sort_setup (Sheet *sheet, char const *key)
6549 if (sheet->sort_setups == NULL)
6550 return NULL;
6551 return g_hash_table_lookup (sheet->sort_setups, key);