1.12.42
[gnumeric.git] / src / sheet.c
blob5ac6f5d42a7330150ec69246baf8ebff3fd937e8
1 /*
2 * sheet.c: Implements the sheet management and per-sheet storage
4 * Copyright (C) 2000-2007 Jody Goldberg (jody@gnome.org)
5 * Copyright (C) 1997-1999 Miguel de Icaza (miguel@kernel.org)
6 * Copyright (C) 1999-2009 Morten Welinder (terra@gnome.org)
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) version 3.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
21 * USA
23 #include <gnumeric-config.h>
24 #include <gnumeric.h>
25 #include <sheet.h>
27 #include <sheet-view.h>
28 #include <command-context.h>
29 #include <sheet-control.h>
30 #include <sheet-style.h>
31 #include <workbook-priv.h>
32 #include <workbook-control.h>
33 #include <workbook-view.h>
34 #include <parse-util.h>
35 #include <dependent.h>
36 #include <value.h>
37 #include <number-match.h>
38 #include <clipboard.h>
39 #include <selection.h>
40 #include <ranges.h>
41 #include <print-info.h>
42 #include <mstyle.h>
43 #include <style-color.h>
44 #include <style-font.h>
45 #include <application.h>
46 #include <commands.h>
47 #include <cellspan.h>
48 #include <cell.h>
49 #include <sheet-merge.h>
50 #include <sheet-private.h>
51 #include <expr-name.h>
52 #include <expr.h>
53 #include <rendered-value.h>
54 #include <gnumeric-conf.h>
55 #include <sheet-object-impl.h>
56 #include <sheet-object-cell-comment.h>
57 #include <tools/gnm-solver.h>
58 #include <hlink.h>
59 #include <sheet-filter.h>
60 #include <sheet-filter-combo.h>
61 #include <gnm-sheet-slicer.h>
62 #include <tools/scenarios.h>
63 #include <cell-draw.h>
64 #include <sort.h>
65 #include <gutils.h>
66 #include <goffice/goffice.h>
68 #include <gnm-i18n.h>
69 #include <gsf/gsf-impl-utils.h>
70 #include <stdlib.h>
71 #include <string.h>
73 static GnmSheetSize *
74 gnm_sheet_size_copy (GnmSheetSize *size)
76 GnmSheetSize *res = g_new (GnmSheetSize, 1);
77 *res = *size;
78 return res;
81 GType
82 gnm_sheet_size_get_type (void)
84 static GType t = 0;
86 if (t == 0) {
87 t = g_boxed_type_register_static ("GnmSheetSize",
88 (GBoxedCopyFunc)gnm_sheet_size_copy,
89 (GBoxedFreeFunc)g_free);
91 return t;
94 enum {
95 DETACHED_FROM_WORKBOOK,
96 LAST_SIGNAL
99 static guint signals[LAST_SIGNAL] = { 0 };
101 typedef struct {
102 GObjectClass parent;
104 void (*detached_from_workbook) (Sheet *, Workbook *wb);
105 } GnmSheetClass;
106 typedef Sheet GnmSheet;
108 enum {
109 PROP_0,
110 PROP_SHEET_TYPE,
111 PROP_WORKBOOK,
112 PROP_NAME,
113 PROP_RTL,
114 PROP_VISIBILITY,
115 PROP_DISPLAY_FORMULAS,
116 PROP_DISPLAY_ZEROS,
117 PROP_DISPLAY_GRID,
118 PROP_DISPLAY_COLUMN_HEADER,
119 PROP_DISPLAY_ROW_HEADER,
120 PROP_DISPLAY_OUTLINES,
121 PROP_DISPLAY_OUTLINES_BELOW,
122 PROP_DISPLAY_OUTLINES_RIGHT,
124 PROP_PROTECTED,
125 PROP_PROTECTED_ALLOW_EDIT_OBJECTS,
126 PROP_PROTECTED_ALLOW_EDIT_SCENARIOS,
127 PROP_PROTECTED_ALLOW_CELL_FORMATTING,
128 PROP_PROTECTED_ALLOW_COLUMN_FORMATTING,
129 PROP_PROTECTED_ALLOW_ROW_FORMATTING,
130 PROP_PROTECTED_ALLOW_INSERT_COLUMNS,
131 PROP_PROTECTED_ALLOW_INSERT_ROWS,
132 PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS,
133 PROP_PROTECTED_ALLOW_DELETE_COLUMNS,
134 PROP_PROTECTED_ALLOW_DELETE_ROWS,
135 PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS,
136 PROP_PROTECTED_ALLOW_SORT_RANGES,
137 PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS,
138 PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE,
139 PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS,
141 PROP_CONVENTIONS,
142 PROP_USE_R1C1,
144 PROP_TAB_FOREGROUND,
145 PROP_TAB_BACKGROUND,
146 PROP_ZOOM_FACTOR,
148 PROP_COLUMNS,
149 PROP_ROWS
152 static void gnm_sheet_finalize (GObject *obj);
154 static GObjectClass *parent_class;
156 static void
157 col_row_collection_resize (ColRowCollection *infos, int size)
159 int end_idx = COLROW_SEGMENT_INDEX (size);
160 int i = infos->info->len - 1;
162 while (i >= end_idx) {
163 ColRowSegment *segment = g_ptr_array_index (infos->info, i);
164 if (segment) {
165 g_free (segment);
166 g_ptr_array_index (infos->info, i) = NULL;
168 i--;
171 g_ptr_array_set_size (infos->info, end_idx);
174 static void
175 sheet_set_direction (Sheet *sheet, gboolean text_is_rtl)
177 GnmRange r;
179 text_is_rtl = !!text_is_rtl;
180 if (text_is_rtl == sheet->text_is_rtl)
181 return;
183 sheet_mark_dirty (sheet);
185 sheet->text_is_rtl = text_is_rtl;
186 sheet->priv->reposition_objects.col = 0;
187 sheet_range_calc_spans (sheet,
188 range_init_full_sheet (&r, sheet),
189 GNM_SPANCALC_RE_RENDER);
192 static void
193 sheet_set_visibility (Sheet *sheet, GnmSheetVisibility visibility)
195 if (sheet->visibility == visibility)
196 return;
198 sheet->visibility = visibility;
199 sheet_mark_dirty (sheet);
202 static void
203 cb_re_render_formulas (G_GNUC_UNUSED gpointer unused,
204 GnmCell *cell,
205 G_GNUC_UNUSED gpointer user)
207 if (gnm_cell_has_expr (cell)) {
208 gnm_cell_unrender (cell);
209 sheet_cell_queue_respan (cell);
213 static void
214 re_render_formulas (Sheet const *sheet)
216 sheet_cell_foreach (sheet, (GHFunc)cb_re_render_formulas, NULL);
219 static void
220 sheet_set_conventions (Sheet *sheet, GnmConventions const *convs)
222 if (sheet->convs == convs)
223 return;
224 gnm_conventions_unref (sheet->convs);
225 sheet->convs = gnm_conventions_ref (convs);
226 if (sheet->display_formulas)
227 re_render_formulas (sheet);
228 SHEET_FOREACH_VIEW (sheet, sv,
229 sv->edit_pos_changed.content = TRUE;);
230 sheet_mark_dirty (sheet);
233 GnmConventions const *
234 sheet_get_conventions (Sheet const *sheet)
236 g_return_val_if_fail (IS_SHEET (sheet), gnm_conventions_default);
238 return sheet->convs;
241 static void
242 cb_sheet_set_hide_zeros (G_GNUC_UNUSED gpointer unused,
243 GnmCell *cell,
244 G_GNUC_UNUSED gpointer user)
246 if (gnm_cell_is_zero (cell))
247 gnm_cell_unrender (cell);
250 static void
251 sheet_set_hide_zeros (Sheet *sheet, gboolean hide)
253 hide = !!hide;
254 if (sheet->hide_zero == hide)
255 return;
257 sheet->hide_zero = hide;
258 sheet_mark_dirty (sheet);
260 sheet_cell_foreach (sheet, (GHFunc)cb_sheet_set_hide_zeros, NULL);
263 static void
264 sheet_set_name (Sheet *sheet, char const *new_name)
266 Workbook *wb = sheet->workbook;
267 gboolean attached;
268 Sheet *sucker;
269 char *new_name_unquoted;
271 g_return_if_fail (new_name != NULL);
273 /* No change whatsoever. */
274 if (go_str_compare (sheet->name_unquoted, new_name) == 0)
275 return;
277 /* Mark the sheet dirty unless this is the initial name. */
278 if (sheet->name_unquoted)
279 sheet_mark_dirty (sheet);
281 sucker = wb ? workbook_sheet_by_name (wb, new_name) : NULL;
282 if (sucker && sucker != sheet) {
284 * Prevent a name clash. With this you can swap names by
285 * setting just the two names.
287 char *sucker_name = workbook_sheet_get_free_name (wb, new_name, TRUE, FALSE);
288 #if 0
289 g_warning ("Renaming %s to %s to avoid clash.\n", sucker->name_unquoted, sucker_name);
290 #endif
291 g_object_set (sucker, "name", sucker_name, NULL);
292 g_free (sucker_name);
295 attached = wb != NULL &&
296 sheet->index_in_wb != -1 &&
297 sheet->name_case_insensitive;
299 /* FIXME: maybe have workbook_sheet_detach_internal for this. */
300 if (attached)
301 g_hash_table_remove (wb->sheet_hash_private,
302 sheet->name_case_insensitive);
304 /* Copy before free. */
305 new_name_unquoted = g_strdup (new_name);
307 g_free (sheet->name_unquoted);
308 g_free (sheet->name_quoted);
309 g_free (sheet->name_unquoted_collate_key);
310 g_free (sheet->name_case_insensitive);
311 sheet->name_unquoted = new_name_unquoted;
312 sheet->name_quoted = g_string_free
313 (gnm_expr_conv_quote (sheet->convs, new_name_unquoted),
314 FALSE);
315 sheet->name_unquoted_collate_key =
316 g_utf8_collate_key (new_name_unquoted, -1);
317 sheet->name_case_insensitive =
318 g_utf8_casefold (new_name_unquoted, -1);
320 /* FIXME: maybe have workbook_sheet_attach_internal for this. */
321 if (attached)
322 g_hash_table_insert (wb->sheet_hash_private,
323 sheet->name_case_insensitive,
324 sheet);
326 if (!sheet->being_constructed &&
327 sheet->sheet_type == GNM_SHEET_DATA) {
328 /* We have to fix the Sheet_Title name */
329 GnmNamedExpr *nexpr;
330 GnmParsePos pp;
332 parse_pos_init_sheet (&pp, sheet);
333 nexpr = expr_name_lookup (&pp, "Sheet_Title");
334 if (nexpr) {
335 GnmExprTop const *texpr =
336 gnm_expr_top_new_constant
337 (value_new_string (sheet->name_unquoted));
338 expr_name_set_expr (nexpr, texpr);
343 struct resize_colrow {
344 Sheet *sheet;
345 gboolean is_cols;
346 double scale;
349 static gboolean
350 cb_colrow_compute_pixels_from_pts (GnmColRowIter const *iter,
351 gpointer data_)
353 struct resize_colrow *data = data_;
354 colrow_compute_pixels_from_pts ((ColRowInfo *)iter->cri,
355 data->sheet, data->is_cols,
356 data->scale);
357 return FALSE;
360 static void
361 cb_clear_rendered_cells (G_GNUC_UNUSED gpointer ignored, GnmCell *cell)
363 if (gnm_cell_get_rendered_value (cell) != NULL) {
364 sheet_cell_queue_respan (cell);
365 gnm_cell_unrender (cell);
369 static void
370 sheet_scale_changed (Sheet *sheet, gboolean cols_rescaled, gboolean rows_rescaled)
372 g_return_if_fail (cols_rescaled || rows_rescaled);
374 /* Then every column and row */
375 if (cols_rescaled) {
376 struct resize_colrow closure;
378 closure.sheet = sheet;
379 closure.is_cols = TRUE;
380 closure.scale = colrow_compute_pixel_scale (sheet, TRUE);
382 colrow_compute_pixels_from_pts (&sheet->cols.default_style,
383 sheet, TRUE, closure.scale);
384 sheet_colrow_foreach (sheet, TRUE, 0, -1,
385 cb_colrow_compute_pixels_from_pts,
386 &closure);
388 if (rows_rescaled) {
389 struct resize_colrow closure;
391 closure.sheet = sheet;
392 closure.is_cols = FALSE;
393 closure.scale = colrow_compute_pixel_scale (sheet, FALSE);
395 colrow_compute_pixels_from_pts (&sheet->rows.default_style,
396 sheet, FALSE, closure.scale);
397 sheet_colrow_foreach (sheet, FALSE, 0, -1,
398 cb_colrow_compute_pixels_from_pts,
399 &closure);
402 sheet_cell_foreach (sheet, (GHFunc)&cb_clear_rendered_cells, NULL);
403 SHEET_FOREACH_CONTROL (sheet, view, control, sc_scale_changed (control););
406 static void
407 sheet_set_display_formulas (Sheet *sheet, gboolean display)
409 display = !!display;
410 if (sheet->display_formulas == display)
411 return;
413 sheet->display_formulas = display;
414 sheet_mark_dirty (sheet);
415 if (!sheet->being_constructed)
416 sheet_scale_changed (sheet, TRUE, FALSE);
419 static void
420 sheet_set_zoom_factor (Sheet *sheet, double factor)
422 if (fabs (factor - sheet->last_zoom_factor_used) < 1e-6)
423 return;
424 sheet->last_zoom_factor_used = factor;
425 if (!sheet->being_constructed)
426 sheet_scale_changed (sheet, TRUE, TRUE);
429 static void
430 gnm_sheet_set_property (GObject *object, guint property_id,
431 GValue const *value, GParamSpec *pspec)
433 Sheet *sheet = (Sheet *)object;
435 switch (property_id) {
436 case PROP_SHEET_TYPE:
437 /* Construction-time only */
438 sheet->sheet_type = g_value_get_enum (value);
439 break;
440 case PROP_WORKBOOK:
441 /* Construction-time only */
442 sheet->workbook = g_value_get_object (value);
443 break;
444 case PROP_NAME:
445 sheet_set_name (sheet, g_value_get_string (value));
446 break;
447 case PROP_RTL:
448 sheet_set_direction (sheet, g_value_get_boolean (value));
449 break;
450 case PROP_VISIBILITY:
451 sheet_set_visibility (sheet, g_value_get_enum (value));
452 break;
453 case PROP_DISPLAY_FORMULAS:
454 sheet_set_display_formulas (sheet, g_value_get_boolean (value));
455 break;
456 case PROP_DISPLAY_ZEROS:
457 sheet_set_hide_zeros (sheet, !g_value_get_boolean (value));
458 break;
459 case PROP_DISPLAY_GRID:
460 sheet->hide_grid = !g_value_get_boolean (value);
461 break;
462 case PROP_DISPLAY_COLUMN_HEADER:
463 sheet->hide_col_header = !g_value_get_boolean (value);
464 break;
465 case PROP_DISPLAY_ROW_HEADER:
466 sheet->hide_row_header = !g_value_get_boolean (value);
467 break;
468 case PROP_DISPLAY_OUTLINES:
469 sheet->display_outlines = !!g_value_get_boolean (value);
470 break;
471 case PROP_DISPLAY_OUTLINES_BELOW:
472 sheet->outline_symbols_below = !!g_value_get_boolean (value);
473 break;
474 case PROP_DISPLAY_OUTLINES_RIGHT:
475 sheet->outline_symbols_right = !!g_value_get_boolean (value);
476 break;
478 case PROP_PROTECTED:
479 sheet->is_protected = !!g_value_get_boolean (value);
480 break;
481 case PROP_PROTECTED_ALLOW_EDIT_OBJECTS:
482 sheet->protected_allow.edit_objects = !!g_value_get_boolean (value);
483 break;
484 case PROP_PROTECTED_ALLOW_EDIT_SCENARIOS:
485 sheet->protected_allow.edit_scenarios = !!g_value_get_boolean (value);
486 break;
487 case PROP_PROTECTED_ALLOW_CELL_FORMATTING:
488 sheet->protected_allow.cell_formatting = !!g_value_get_boolean (value);
489 break;
490 case PROP_PROTECTED_ALLOW_COLUMN_FORMATTING:
491 sheet->protected_allow.column_formatting = !!g_value_get_boolean (value);
492 break;
493 case PROP_PROTECTED_ALLOW_ROW_FORMATTING:
494 sheet->protected_allow.row_formatting = !!g_value_get_boolean (value);
495 break;
496 case PROP_PROTECTED_ALLOW_INSERT_COLUMNS:
497 sheet->protected_allow.insert_columns = !!g_value_get_boolean (value);
498 break;
499 case PROP_PROTECTED_ALLOW_INSERT_ROWS:
500 sheet->protected_allow.insert_rows = !!g_value_get_boolean (value);
501 break;
502 case PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS:
503 sheet->protected_allow.insert_hyperlinks = !!g_value_get_boolean (value);
504 break;
505 case PROP_PROTECTED_ALLOW_DELETE_COLUMNS:
506 sheet->protected_allow.delete_columns = !!g_value_get_boolean (value);
507 break;
508 case PROP_PROTECTED_ALLOW_DELETE_ROWS:
509 sheet->protected_allow.delete_rows = !!g_value_get_boolean (value);
510 break;
511 case PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS:
512 sheet->protected_allow.select_locked_cells = !!g_value_get_boolean (value);
513 break;
514 case PROP_PROTECTED_ALLOW_SORT_RANGES:
515 sheet->protected_allow.sort_ranges = !!g_value_get_boolean (value);
516 break;
517 case PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS:
518 sheet->protected_allow.edit_auto_filters = !!g_value_get_boolean (value);
519 break;
520 case PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE:
521 sheet->protected_allow.edit_pivottable = !!g_value_get_boolean (value);
522 break;
523 case PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS:
524 sheet->protected_allow.select_unlocked_cells = !!g_value_get_boolean (value);
525 break;
527 case PROP_CONVENTIONS:
528 sheet_set_conventions (sheet, g_value_get_boxed (value));
529 break;
530 case PROP_USE_R1C1: /* convenience api */
531 sheet_set_conventions (sheet, !!g_value_get_boolean (value)
532 ? gnm_conventions_xls_r1c1 : gnm_conventions_default);
533 break;
535 case PROP_TAB_FOREGROUND: {
536 GnmColor *color = g_value_dup_boxed (value);
537 style_color_unref (sheet->tab_text_color);
538 sheet->tab_text_color = color;
539 sheet_mark_dirty (sheet);
540 break;
542 case PROP_TAB_BACKGROUND: {
543 GnmColor *color = g_value_dup_boxed (value);
544 style_color_unref (sheet->tab_color);
545 sheet->tab_color = color;
546 sheet_mark_dirty (sheet);
547 break;
549 case PROP_ZOOM_FACTOR:
550 sheet_set_zoom_factor (sheet, g_value_get_double (value));
551 break;
552 case PROP_COLUMNS:
553 /* Construction-time only */
554 sheet->size.max_cols = g_value_get_int (value);
555 break;
556 case PROP_ROWS:
557 /* Construction-time only */
558 sheet->size.max_rows = g_value_get_int (value);
559 break;
560 default:
561 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
562 break;
566 static void
567 gnm_sheet_get_property (GObject *object, guint property_id,
568 GValue *value, GParamSpec *pspec)
570 Sheet *sheet = (Sheet *)object;
572 switch (property_id) {
573 case PROP_SHEET_TYPE:
574 g_value_set_enum (value, sheet->sheet_type);
575 break;
576 case PROP_WORKBOOK:
577 g_value_set_object (value, sheet->workbook);
578 break;
579 case PROP_NAME:
580 g_value_set_string (value, sheet->name_unquoted);
581 break;
582 case PROP_RTL:
583 g_value_set_boolean (value, sheet->text_is_rtl);
584 break;
585 case PROP_VISIBILITY:
586 g_value_set_enum (value, sheet->visibility);
587 break;
588 case PROP_DISPLAY_FORMULAS:
589 g_value_set_boolean (value, sheet->display_formulas);
590 break;
591 case PROP_DISPLAY_ZEROS:
592 g_value_set_boolean (value, !sheet->hide_zero);
593 break;
594 case PROP_DISPLAY_GRID:
595 g_value_set_boolean (value, !sheet->hide_grid);
596 break;
597 case PROP_DISPLAY_COLUMN_HEADER:
598 g_value_set_boolean (value, !sheet->hide_col_header);
599 break;
600 case PROP_DISPLAY_ROW_HEADER:
601 g_value_set_boolean (value, !sheet->hide_row_header);
602 break;
603 case PROP_DISPLAY_OUTLINES:
604 g_value_set_boolean (value, sheet->display_outlines);
605 break;
606 case PROP_DISPLAY_OUTLINES_BELOW:
607 g_value_set_boolean (value, sheet->outline_symbols_below);
608 break;
609 case PROP_DISPLAY_OUTLINES_RIGHT:
610 g_value_set_boolean (value, sheet->outline_symbols_right);
611 break;
613 case PROP_PROTECTED:
614 g_value_set_boolean (value, sheet->is_protected);
615 break;
616 case PROP_PROTECTED_ALLOW_EDIT_OBJECTS:
617 g_value_set_boolean (value, sheet->protected_allow.edit_objects);
618 break;
619 case PROP_PROTECTED_ALLOW_EDIT_SCENARIOS:
620 g_value_set_boolean (value, sheet->protected_allow.edit_scenarios);
621 break;
622 case PROP_PROTECTED_ALLOW_CELL_FORMATTING:
623 g_value_set_boolean (value, sheet->protected_allow.cell_formatting);
624 break;
625 case PROP_PROTECTED_ALLOW_COLUMN_FORMATTING:
626 g_value_set_boolean (value, sheet->protected_allow.column_formatting);
627 break;
628 case PROP_PROTECTED_ALLOW_ROW_FORMATTING:
629 g_value_set_boolean (value, sheet->protected_allow.row_formatting);
630 break;
631 case PROP_PROTECTED_ALLOW_INSERT_COLUMNS:
632 g_value_set_boolean (value, sheet->protected_allow.insert_columns);
633 break;
634 case PROP_PROTECTED_ALLOW_INSERT_ROWS:
635 g_value_set_boolean (value, sheet->protected_allow.insert_rows);
636 break;
637 case PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS:
638 g_value_set_boolean (value, sheet->protected_allow.insert_hyperlinks);
639 break;
640 case PROP_PROTECTED_ALLOW_DELETE_COLUMNS:
641 g_value_set_boolean (value, sheet->protected_allow.delete_columns);
642 break;
643 case PROP_PROTECTED_ALLOW_DELETE_ROWS:
644 g_value_set_boolean (value, sheet->protected_allow.delete_rows);
645 break;
646 case PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS:
647 g_value_set_boolean (value, sheet->protected_allow.select_locked_cells);
648 break;
649 case PROP_PROTECTED_ALLOW_SORT_RANGES:
650 g_value_set_boolean (value, sheet->protected_allow.sort_ranges);
651 break;
652 case PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS:
653 g_value_set_boolean (value, sheet->protected_allow.edit_auto_filters);
654 break;
655 case PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE:
656 g_value_set_boolean (value, sheet->protected_allow.edit_pivottable);
657 break;
658 case PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS:
659 g_value_set_boolean (value, sheet->protected_allow.select_unlocked_cells);
660 break;
662 case PROP_CONVENTIONS:
663 g_value_set_boxed (value, sheet->convs);
664 break;
665 case PROP_USE_R1C1: /* convenience api */
666 g_value_set_boolean (value, sheet->convs->r1c1_addresses);
667 break;
669 case PROP_TAB_FOREGROUND:
670 g_value_set_boxed (value, sheet->tab_text_color);
671 break;
672 case PROP_TAB_BACKGROUND:
673 g_value_set_boxed (value, sheet->tab_color);
674 break;
675 case PROP_ZOOM_FACTOR:
676 g_value_set_double (value, sheet->last_zoom_factor_used);
677 break;
678 case PROP_COLUMNS:
679 g_value_set_int (value, sheet->size.max_cols);
680 break;
681 case PROP_ROWS:
682 g_value_set_int (value, sheet->size.max_rows);
683 break;
684 default:
685 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
686 break;
690 static void
691 gnm_sheet_constructed (GObject *obj)
693 Sheet *sheet = SHEET (obj);
695 if (parent_class->constructed)
696 parent_class->constructed (obj);
698 /* Now sheet_type, max_cols, and max_rows have been set. */
699 sheet->being_constructed = FALSE;
701 col_row_collection_resize (&sheet->cols, sheet->size.max_cols);
702 col_row_collection_resize (&sheet->rows, sheet->size.max_rows);
704 sheet->priv->reposition_objects.col = sheet->size.max_cols;
705 sheet->priv->reposition_objects.row = sheet->size.max_rows;
707 range_init_full_sheet (&sheet->priv->unhidden_region, sheet);
708 sheet_style_init (sheet);
710 sheet->deps = gnm_dep_container_new (sheet);
712 switch (sheet->sheet_type) {
713 case GNM_SHEET_XLM:
714 sheet->display_formulas = TRUE;
715 break;
716 case GNM_SHEET_OBJECT:
717 sheet->hide_grid = TRUE;
718 sheet->hide_col_header = sheet->hide_row_header = TRUE;
719 colrow_compute_pixels_from_pts (&sheet->rows.default_style,
720 sheet, FALSE, -1);
721 colrow_compute_pixels_from_pts (&sheet->cols.default_style,
722 sheet, TRUE, -1);
723 break;
724 case GNM_SHEET_DATA: {
725 /* We have to add permanent names */
726 GnmExprTop const *texpr;
728 if (sheet->name_unquoted)
729 texpr = gnm_expr_top_new_constant
730 (value_new_string (sheet->name_unquoted));
731 else
732 texpr = gnm_expr_top_new_constant
733 (value_new_error_REF (NULL));
734 expr_name_perm_add (sheet, "Sheet_Title",
735 texpr, FALSE);
737 texpr = gnm_expr_top_new_constant
738 (value_new_error_REF (NULL));
739 expr_name_perm_add (sheet, "Print_Area",
740 texpr, FALSE);
741 break;
743 default:
744 g_assert_not_reached ();
747 sheet_scale_changed (sheet, TRUE, TRUE);
750 static guint
751 cell_set_hash (GnmCell const *key)
753 guint32 r = key->pos.row;
754 guint32 c = key->pos.col;
755 guint32 h;
757 h = r;
758 h *= (guint32)123456789;
759 h ^= c;
760 h *= (guint32)123456789;
762 return h;
765 static gint
766 cell_set_equal (GnmCell const *a, GnmCell const *b)
768 return (a->pos.row == b->pos.row && a->pos.col == b->pos.col);
771 static void
772 gnm_sheet_init (Sheet *sheet)
774 PangoContext *context;
776 sheet->priv = g_new0 (SheetPrivate, 1);
777 sheet->being_constructed = TRUE;
779 sheet->sheet_views = g_ptr_array_new ();
781 /* Init, focus, and load handle setting these if/when necessary */
782 sheet->priv->recompute_visibility = TRUE;
783 sheet->priv->recompute_spans = TRUE;
785 sheet->is_protected = FALSE;
786 sheet->protected_allow.edit_scenarios = FALSE;
787 sheet->protected_allow.cell_formatting = FALSE;
788 sheet->protected_allow.column_formatting = FALSE;
789 sheet->protected_allow.row_formatting = FALSE;
790 sheet->protected_allow.insert_columns = FALSE;
791 sheet->protected_allow.insert_rows = FALSE;
792 sheet->protected_allow.insert_hyperlinks = FALSE;
793 sheet->protected_allow.delete_columns = FALSE;
794 sheet->protected_allow.delete_rows = FALSE;
795 sheet->protected_allow.select_locked_cells = TRUE;
796 sheet->protected_allow.sort_ranges = FALSE;
797 sheet->protected_allow.edit_auto_filters = FALSE;
798 sheet->protected_allow.edit_pivottable = FALSE;
799 sheet->protected_allow.select_unlocked_cells = TRUE;
801 sheet->hide_zero = FALSE;
802 sheet->display_outlines = TRUE;
803 sheet->outline_symbols_below = TRUE;
804 sheet->outline_symbols_right = TRUE;
805 sheet->tab_color = NULL;
806 sheet->tab_text_color = NULL;
807 sheet->visibility = GNM_SHEET_VISIBILITY_VISIBLE;
808 #ifdef GNM_WITH_GTK
809 sheet->text_is_rtl = (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL);
810 #else
811 sheet->text_is_rtl = FALSE;
812 #endif
814 sheet->sheet_objects = NULL;
815 sheet->max_object_extent.col = sheet->max_object_extent.row = 0;
817 sheet->solver_parameters = gnm_solver_param_new (sheet);
819 sheet->cols.max_used = -1;
820 sheet->cols.info = g_ptr_array_new ();
821 sheet_col_set_default_size_pts (sheet, 48);
823 sheet->rows.max_used = -1;
824 sheet->rows.info = g_ptr_array_new ();
825 sheet_row_set_default_size_pts (sheet, 12.75);
827 sheet->print_info = gnm_print_information_new (FALSE);
829 sheet->filters = NULL;
830 sheet->scenarios = NULL;
831 sheet->sort_setups = NULL;
832 sheet->list_merged = NULL;
833 sheet->hash_merged = g_hash_table_new ((GHashFunc)&gnm_cellpos_hash,
834 (GCompareFunc)&gnm_cellpos_equal);
836 sheet->cell_hash = g_hash_table_new ((GHashFunc)&cell_set_hash,
837 (GCompareFunc)&cell_set_equal);
839 /* Init preferences */
840 sheet->convs = gnm_conventions_ref (gnm_conventions_default);
842 /* FIXME: probably not here. */
843 /* See also gtk_widget_create_pango_context (). */
844 sheet->last_zoom_factor_used = -1; /* Overridden later */
845 context = gnm_pango_context_get ();
846 sheet->rendered_values = gnm_rvc_new (context, 5000);
847 g_object_unref (context);
849 /* Init menu states */
850 sheet->priv->enable_showhide_detail = TRUE;
852 sheet->names = gnm_named_expr_collection_new ();
853 sheet->style_data = NULL;
855 sheet->index_in_wb = -1;
858 static Sheet the_invalid_sheet;
859 Sheet *invalid_sheet = &the_invalid_sheet;
861 static void
862 gnm_sheet_class_init (GObjectClass *gobject_class)
864 if (GNM_MAX_COLS > 364238) {
865 /* Oh, yeah? */
866 g_warning (_("This is a special version of Gnumeric. It has been compiled\n"
867 "with support for a very large number of columns. Access to the\n"
868 "column named TRUE may conflict with the constant of the same\n"
869 "name. Expect weirdness."));
872 parent_class = g_type_class_peek_parent (gobject_class);
874 gobject_class->set_property = gnm_sheet_set_property;
875 gobject_class->get_property = gnm_sheet_get_property;
876 gobject_class->finalize = gnm_sheet_finalize;
877 gobject_class->constructed = gnm_sheet_constructed;
879 g_object_class_install_property (gobject_class, PROP_SHEET_TYPE,
880 g_param_spec_enum ("sheet-type",
881 P_("Sheet Type"),
882 P_("Which type of sheet this is."),
883 GNM_SHEET_TYPE_TYPE,
884 GNM_SHEET_DATA,
885 GSF_PARAM_STATIC |
886 G_PARAM_READWRITE |
887 G_PARAM_CONSTRUCT_ONLY));
888 g_object_class_install_property (gobject_class, PROP_WORKBOOK,
889 g_param_spec_object ("workbook",
890 P_("Parent workbook"),
891 P_("The workbook in which this sheet lives"),
892 GNM_WORKBOOK_TYPE,
893 GSF_PARAM_STATIC |
894 G_PARAM_READWRITE |
895 G_PARAM_CONSTRUCT_ONLY));
896 g_object_class_install_property (gobject_class, PROP_NAME,
897 g_param_spec_string ("name",
898 P_("Name"),
899 P_("The name of the sheet."),
900 NULL,
901 GSF_PARAM_STATIC |
902 G_PARAM_READWRITE));
903 g_object_class_install_property (gobject_class, PROP_RTL,
904 g_param_spec_boolean ("text-is-rtl",
905 P_("text-is-rtl"),
906 P_("Text goes from right to left."),
907 FALSE,
908 GSF_PARAM_STATIC |
909 G_PARAM_READWRITE));
910 g_object_class_install_property (gobject_class, PROP_VISIBILITY,
911 g_param_spec_enum ("visibility",
912 P_("Visibility"),
913 P_("How visible the sheet is."),
914 GNM_SHEET_VISIBILITY_TYPE,
915 GNM_SHEET_VISIBILITY_VISIBLE,
916 GSF_PARAM_STATIC |
917 G_PARAM_READWRITE));
918 g_object_class_install_property (gobject_class, PROP_DISPLAY_FORMULAS,
919 g_param_spec_boolean ("display-formulas",
920 P_("Display Formul\303\246"),
921 P_("Control whether formul\303\246 are shown instead of values."),
922 FALSE,
923 GSF_PARAM_STATIC |
924 G_PARAM_READWRITE));
925 g_object_class_install_property (gobject_class, PROP_DISPLAY_ZEROS,
926 g_param_spec_boolean ("display-zeros", _("Display Zeros"),
927 _("Control whether zeros are shown are blanked out."),
928 TRUE,
929 GSF_PARAM_STATIC |
930 G_PARAM_READWRITE));
931 g_object_class_install_property (gobject_class, PROP_DISPLAY_GRID,
932 g_param_spec_boolean ("display-grid", _("Display Grid"),
933 _("Control whether the grid is shown."),
934 TRUE,
935 GSF_PARAM_STATIC |
936 G_PARAM_READWRITE));
937 g_object_class_install_property (gobject_class, PROP_DISPLAY_COLUMN_HEADER,
938 g_param_spec_boolean ("display-column-header",
939 P_("Display Column Headers"),
940 P_("Control whether column headers are shown."),
941 FALSE,
942 GSF_PARAM_STATIC |
943 G_PARAM_READWRITE));
944 g_object_class_install_property (gobject_class, PROP_DISPLAY_ROW_HEADER,
945 g_param_spec_boolean ("display-row-header",
946 P_("Display Row Headers"),
947 P_("Control whether row headers are shown."),
948 FALSE,
949 GSF_PARAM_STATIC |
950 G_PARAM_READWRITE));
951 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES,
952 g_param_spec_boolean ("display-outlines",
953 P_("Display Outlines"),
954 P_("Control whether outlines are shown."),
955 TRUE,
956 GSF_PARAM_STATIC |
957 G_PARAM_READWRITE));
958 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES_BELOW,
959 g_param_spec_boolean ("display-outlines-below",
960 P_("Display Outlines Below"),
961 P_("Control whether outline symbols are shown below."),
962 TRUE,
963 GSF_PARAM_STATIC |
964 G_PARAM_READWRITE));
965 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES_RIGHT,
966 g_param_spec_boolean ("display-outlines-right",
967 P_("Display Outlines Right"),
968 P_("Control whether outline symbols are shown to the right."),
969 TRUE,
970 GSF_PARAM_STATIC |
971 G_PARAM_READWRITE));
973 g_object_class_install_property (gobject_class, PROP_PROTECTED,
974 g_param_spec_boolean ("protected",
975 P_("Protected"),
976 P_("Sheet is protected."),
977 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
978 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_OBJECTS,
979 g_param_spec_boolean ("protected-allow-edit-objects",
980 P_("Protected Allow Edit objects"),
981 P_("Allow objects to be edited while a sheet is protected"),
982 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
983 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_SCENARIOS,
984 g_param_spec_boolean ("protected-allow-edit-scenarios",
985 P_("Protected allow edit scenarios"),
986 P_("Allow scenarios to be edited while a sheet is protected"),
987 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
988 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_CELL_FORMATTING,
989 g_param_spec_boolean ("protected-allow-cell-formatting",
990 P_("Protected allow cell formatting"),
991 P_("Allow cell format changes while a sheet is protected"),
992 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
993 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_COLUMN_FORMATTING,
994 g_param_spec_boolean ("protected-allow-column-formatting",
995 P_("Protected allow column formatting"),
996 P_("Allow column formatting while a sheet is protected"),
997 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
998 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_ROW_FORMATTING,
999 g_param_spec_boolean ("protected-allow-row-formatting",
1000 P_("Protected allow row formatting"),
1001 P_("Allow row formatting while a sheet is protected"),
1002 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1003 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_COLUMNS,
1004 g_param_spec_boolean ("protected-allow-insert-columns",
1005 P_("Protected allow insert columns"),
1006 P_("Allow columns to be inserted while a sheet is protected"),
1007 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1008 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_ROWS,
1009 g_param_spec_boolean ("protected-allow-insert-rows",
1010 P_("Protected allow insert rows"),
1011 P_("Allow rows to be inserted while a sheet is protected"),
1012 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1013 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS,
1014 g_param_spec_boolean ("protected-allow-insert-hyperlinks",
1015 P_("Protected allow insert hyperlinks"),
1016 P_("Allow hyperlinks to be inserted while a sheet is protected"),
1017 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1018 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_DELETE_COLUMNS,
1019 g_param_spec_boolean ("protected-allow-delete-columns",
1020 P_("Protected allow delete columns"),
1021 P_("Allow columns to be deleted while a sheet is protected"),
1022 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1023 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_DELETE_ROWS,
1024 g_param_spec_boolean ("protected-allow-delete-rows",
1025 P_("Protected allow delete rows"),
1026 P_("Allow rows to be deleted while a sheet is protected"),
1027 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1028 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS,
1029 g_param_spec_boolean ("protected-allow-select-locked-cells",
1030 P_("Protected allow select locked cells"),
1031 P_("Allow the user to select locked cells while a sheet is protected"),
1032 TRUE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1033 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SORT_RANGES,
1034 g_param_spec_boolean ("protected-allow-sort-ranges",
1035 P_("Protected allow sort ranges"),
1036 P_("Allow ranges to be sorted while a sheet is protected"),
1037 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1038 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS,
1039 g_param_spec_boolean ("protected-allow-edit-auto-filters",
1040 P_("Protected allow edit auto filters"),
1041 P_("Allow auto filters to be edited while a sheet is protected"),
1042 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1043 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE,
1044 g_param_spec_boolean ("protected-allow-edit-pivottable",
1045 P_("Protected allow edit pivottable"),
1046 P_("Allow pivottable to be edited while a sheet is protected"),
1047 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1048 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS,
1049 g_param_spec_boolean ("protected-allow-select-unlocked-cells",
1050 P_("Protected allow select unlocked cells"),
1051 P_("Allow the user to select unlocked cells while a sheet is protected"),
1052 TRUE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1054 g_object_class_install_property
1055 (gobject_class, PROP_CONVENTIONS,
1056 g_param_spec_boxed ("conventions",
1057 P_("Display convention for expressions (default Gnumeric A1)"),
1058 P_("How to format displayed expressions, (A1 vs R1C1, function names, ...)"),
1059 gnm_conventions_get_type (),
1060 GSF_PARAM_STATIC |
1061 G_PARAM_READWRITE));
1062 g_object_class_install_property (gobject_class, PROP_USE_R1C1, /* convenience wrapper to CONVENTIONS */
1063 g_param_spec_boolean ("use-r1c1",
1064 P_("Display convention for expressions as XLS_R1C1 vs default"),
1065 P_("How to format displayed expressions, (a convenience api)"),
1066 FALSE,
1067 GSF_PARAM_STATIC |
1068 G_PARAM_READWRITE));
1070 g_object_class_install_property (gobject_class, PROP_TAB_FOREGROUND,
1071 g_param_spec_boxed ("tab-foreground",
1072 P_("Tab Foreground"),
1073 P_("The foreground color of the tab."),
1074 GNM_COLOR_TYPE,
1075 GSF_PARAM_STATIC |
1076 G_PARAM_READWRITE));
1077 g_object_class_install_property (gobject_class, PROP_TAB_BACKGROUND,
1078 g_param_spec_boxed ("tab-background",
1079 P_("Tab Background"),
1080 P_("The background color of the tab."),
1081 GNM_COLOR_TYPE,
1082 GSF_PARAM_STATIC |
1083 G_PARAM_READWRITE));
1085 /* What is this doing in sheet? */
1086 g_object_class_install_property (gobject_class, PROP_ZOOM_FACTOR,
1087 g_param_spec_double ("zoom-factor",
1088 P_("Zoom Factor"),
1089 P_("The level of zoom used for this sheet."),
1090 0.1, 5.0,
1091 1.0,
1092 GSF_PARAM_STATIC |
1093 G_PARAM_CONSTRUCT |
1094 G_PARAM_READWRITE));
1096 g_object_class_install_property (gobject_class, PROP_COLUMNS,
1097 g_param_spec_int ("columns",
1098 P_("Columns"),
1099 P_("Columns number in the sheet"),
1100 0, GNM_MAX_COLS, GNM_DEFAULT_COLS,
1101 GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1103 g_object_class_install_property (gobject_class, PROP_ROWS,
1104 g_param_spec_int ("rows",
1105 P_("Rows"),
1106 P_("Rows number in the sheet"),
1107 0, GNM_MAX_ROWS, GNM_DEFAULT_ROWS,
1108 GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1110 signals[DETACHED_FROM_WORKBOOK] = g_signal_new
1111 ("detached_from_workbook",
1112 GNM_SHEET_TYPE,
1113 G_SIGNAL_RUN_LAST,
1114 G_STRUCT_OFFSET (GnmSheetClass, detached_from_workbook),
1115 NULL, NULL,
1116 g_cclosure_marshal_VOID__OBJECT,
1117 G_TYPE_NONE, 1, GNM_WORKBOOK_TYPE);
1121 GSF_CLASS (GnmSheet, gnm_sheet,
1122 gnm_sheet_class_init, gnm_sheet_init, G_TYPE_OBJECT)
1124 /* ------------------------------------------------------------------------- */
1126 GType
1127 gnm_sheet_type_get_type (void)
1129 static GType etype = 0;
1130 if (etype == 0) {
1131 static const GEnumValue values[] = {
1132 { GNM_SHEET_DATA, "GNM_SHEET_DATA", "data" },
1133 { GNM_SHEET_OBJECT, "GNM_SHEET_OBJECT", "object" },
1134 { GNM_SHEET_XLM, "GNM_SHEET_XLM", "xlm" },
1135 { 0, NULL, NULL }
1137 etype = g_enum_register_static ("GnmSheetType", values);
1139 return etype;
1142 GType
1143 gnm_sheet_visibility_get_type (void)
1145 static GType etype = 0;
1146 if (etype == 0) {
1147 static GEnumValue const values[] = {
1148 { GNM_SHEET_VISIBILITY_VISIBLE, "GNM_SHEET_VISIBILITY_VISIBLE", "visible" },
1149 { GNM_SHEET_VISIBILITY_HIDDEN, "GNM_SHEET_VISIBILITY_HIDDEN", "hidden" },
1150 { GNM_SHEET_VISIBILITY_VERY_HIDDEN, "GNM_SHEET_VISIBILITY_VERY_HIDDEN", "very-hidden" },
1151 { 0, NULL, NULL }
1153 etype = g_enum_register_static ("GnmSheetVisibility", values);
1155 return etype;
1158 /* ------------------------------------------------------------------------- */
1160 static gboolean
1161 powerof_2 (int i)
1163 return i > 0 && (i & (i - 1)) == 0;
1166 gboolean
1167 gnm_sheet_valid_size (int cols, int rows)
1169 return (cols >= GNM_MIN_COLS &&
1170 cols <= GNM_MAX_COLS &&
1171 powerof_2 (cols) &&
1172 rows >= GNM_MIN_ROWS &&
1173 rows <= GNM_MAX_ROWS &&
1174 powerof_2 (rows)
1175 #if 0
1176 && 0x80000000u / (unsigned)(cols / 2) >= (unsigned)rows
1177 #endif
1182 * gnm_sheet_suggest_size:
1183 * @cols: (inout): number of columns
1184 * @rows: (inout): number of rows
1186 * This function produces a valid sheet valid that is reasonable for data
1187 * of @cols columns by @rows rows. If possible, this will be a size bigger
1188 * in both dimensions. However, that is not always possible and when it is
1189 * not, the suggested will be smaller in one or both directions.
1191 void
1192 gnm_sheet_suggest_size (int *cols, int *rows)
1194 int c = GNM_DEFAULT_COLS;
1195 int r = GNM_DEFAULT_ROWS;
1197 while (c < *cols && c < GNM_MAX_COLS)
1198 c *= 2;
1200 while (r < *rows && r < GNM_MAX_ROWS)
1201 r *= 2;
1203 while (!gnm_sheet_valid_size (c, r)) {
1204 /* Darn! Too large. */
1205 if (*cols >= GNM_MIN_COLS && c > GNM_MIN_COLS)
1206 c /= 2;
1207 else if (*rows >= GNM_MIN_ROWS && r > GNM_MIN_ROWS)
1208 r /= 2;
1209 else if (c > GNM_MIN_COLS)
1210 c /= 2;
1211 else
1212 r /= 2;
1215 *cols = c;
1216 *rows = r;
1219 static void gnm_sheet_resize_main (Sheet *sheet, int cols, int rows,
1220 GOCmdContext *cc, GOUndo **pundo);
1222 static void
1223 cb_sheet_resize (Sheet *sheet, const GnmSheetSize *data, GOCmdContext *cc)
1225 gnm_sheet_resize_main (sheet, data->max_cols, data->max_rows,
1226 cc, NULL);
1229 static void
1230 gnm_sheet_resize_main (Sheet *sheet, int cols, int rows,
1231 GOCmdContext *cc, GOUndo **pundo)
1233 int old_cols, old_rows;
1234 GnmStyle **common_col_styles = NULL;
1235 GnmStyle **common_row_styles = NULL;
1237 if (pundo) *pundo = NULL;
1239 old_cols = gnm_sheet_get_max_cols (sheet);
1240 old_rows = gnm_sheet_get_max_rows (sheet);
1241 if (old_cols == cols && old_rows == rows)
1242 return;
1244 /* ---------------------------------------- */
1245 /* Gather styles we want to copy into new areas. */
1247 if (cols > old_cols) {
1248 int r;
1249 common_row_styles = sheet_style_most_common (sheet, FALSE);
1250 for (r = 0; r < old_rows; r++)
1251 gnm_style_ref (common_row_styles[r]);
1253 if (rows > old_rows) {
1254 int c;
1255 common_col_styles = sheet_style_most_common (sheet, TRUE);
1256 for (c = 0; c < old_cols; c++)
1257 gnm_style_ref (common_col_styles[c]);
1260 /* ---------------------------------------- */
1261 /* Remove the columns and rows that will disappear. */
1263 if (cols < old_cols) {
1264 GOUndo *u = NULL;
1265 gboolean err;
1267 err = sheet_delete_cols (sheet, cols, G_MAXINT,
1268 pundo ? &u : NULL, cc);
1269 if (pundo)
1270 *pundo = go_undo_combine (*pundo, u);
1271 if (err)
1272 goto handle_error;
1275 if (rows < old_rows) {
1276 GOUndo *u = NULL;
1277 gboolean err;
1279 err = sheet_delete_rows (sheet, rows, G_MAXINT,
1280 pundo ? &u : NULL, cc);
1281 if (pundo)
1282 *pundo = go_undo_combine (*pundo, u);
1283 if (err)
1284 goto handle_error;
1287 /* ---------------------------------------- */
1288 /* Restrict selection. (Not undone.) */
1290 SHEET_FOREACH_VIEW (sheet, sv,
1292 GnmRange new_full;
1293 GSList *l;
1294 GSList *sel = selection_get_ranges (sv, TRUE);
1295 gboolean any = FALSE;
1296 GnmCellPos vis;
1297 sv_selection_reset (sv);
1298 range_init (&new_full, 0, 0, cols - 1, rows - 1);
1299 vis = new_full.start;
1300 for (l = sel; l; l = l->next) {
1301 GnmRange *r = l->data;
1302 GnmRange newr;
1303 if (range_intersection (&newr, r, &new_full)) {
1304 sv_selection_add_range (sv, &newr);
1305 vis = newr.start;
1306 any = TRUE;
1308 g_free (r);
1310 g_slist_free (sel);
1311 if (!any)
1312 sv_selection_add_pos (sv, 0, 0,
1313 GNM_SELECTION_MODE_ADD);
1314 gnm_sheet_view_make_cell_visible (sv, vis.col, vis.row, FALSE);
1317 /* ---------------------------------------- */
1318 /* Resize column and row containers. */
1320 col_row_collection_resize (&sheet->cols, cols);
1321 col_row_collection_resize (&sheet->rows, rows);
1323 /* ---------------------------------------- */
1324 /* Resize the dependency containers. */
1327 GSList *l, *linked = NULL;
1328 /* FIXME: what about dependents in other workbooks? */
1329 WORKBOOK_FOREACH_DEPENDENT
1330 (sheet->workbook, dep,
1332 if (dependent_is_linked (dep)) {
1333 dependent_unlink (dep);
1334 linked = g_slist_prepend (linked, dep);
1337 gnm_dep_container_resize (sheet->deps, rows);
1339 for (l = linked; l; l = l->next) {
1340 GnmDependent *dep = l->data;
1341 dependent_link (dep);
1344 g_slist_free (linked);
1346 workbook_queue_all_recalc (sheet->workbook);
1349 /* ---------------------------------------- */
1350 /* Resize the styles. */
1352 sheet_style_resize (sheet, cols, rows);
1354 /* ---------------------------------------- */
1355 /* Actually change the properties. */
1357 sheet->size.max_cols = cols;
1358 sheet->cols.max_used = MIN (sheet->cols.max_used, cols - 1);
1359 sheet->size.max_rows = rows;
1360 sheet->rows.max_used = MIN (sheet->rows.max_used, rows - 1);
1362 if (old_cols != cols)
1363 g_object_notify (G_OBJECT (sheet), "columns");
1364 if (old_rows != rows)
1365 g_object_notify (G_OBJECT (sheet), "rows");
1367 if (pundo) {
1368 GnmSheetSize *data = g_new (GnmSheetSize, 1);
1369 GOUndo *u;
1371 data->max_cols = old_cols;
1372 data->max_rows = old_rows;
1373 u = go_undo_binary_new (sheet, data,
1374 (GOUndoBinaryFunc)cb_sheet_resize,
1375 NULL, g_free);
1376 *pundo = go_undo_combine (*pundo, u);
1379 range_init_full_sheet (&sheet->priv->unhidden_region, sheet);
1381 /* ---------------------------------------- */
1382 /* Apply styles to new areas. */
1384 if (cols > old_cols) {
1385 int r = 0;
1386 while (r < old_rows) {
1387 int r2 = r;
1388 GnmStyle *mstyle = common_row_styles[r];
1389 GnmRange rng;
1390 while (r2 + 1 < old_rows &&
1391 mstyle == common_row_styles[r2 + 1])
1392 r2++;
1393 range_init (&rng, old_cols, r, cols - 1, r2);
1394 gnm_style_ref (mstyle);
1395 sheet_apply_style (sheet, &rng, mstyle);
1396 r = r2 + 1;
1399 for (r = 0; r < old_rows; r++)
1400 gnm_style_unref (common_row_styles[r]);
1402 g_free (common_row_styles);
1405 if (rows > old_rows) {
1406 int c = 0;
1408 while (c < old_cols) {
1409 int c2 = c;
1410 GnmStyle *mstyle = common_col_styles[c];
1411 GnmRange rng;
1412 while (c2 + 1 < old_cols &&
1413 mstyle == common_col_styles[c2 + 1])
1414 c2++;
1415 range_init (&rng, c, old_rows, c2, rows - 1);
1416 gnm_style_ref (mstyle);
1417 sheet_apply_style (sheet, &rng, mstyle);
1418 c = c2 + 1;
1421 if (cols > old_cols) {
1423 * Expanded in both directions. One could argue about
1424 * what style to use down here, but we choose the
1425 * last column style.
1427 GnmStyle *mstyle = common_col_styles[old_cols - 1];
1428 GnmRange rng;
1430 range_init (&rng,
1431 old_cols, old_rows,
1432 cols - 1, rows - 1);
1433 gnm_style_ref (mstyle);
1434 sheet_apply_style (sheet, &rng, mstyle);
1437 for (c = 0; c < old_cols; c++)
1438 gnm_style_unref (common_col_styles[c]);
1439 g_free (common_col_styles);
1442 /* ---------------------------------------- */
1444 sheet_redraw_all (sheet, TRUE);
1445 return;
1447 handle_error:
1448 if (pundo) {
1449 go_undo_undo_with_data (*pundo, cc);
1450 g_object_unref (*pundo);
1451 *pundo = NULL;
1456 * gnm_sheet_resize:
1457 * @sheet: #Sheet
1458 * @cols: the new columns number.
1459 * @rows: the new rows number.
1460 * @cc: #GOCmdContext.
1461 * @perr: (out): will be %TRUE on error.
1463 * Returns: (transfer full): the newly allocated #GOUndo.
1465 GOUndo *
1466 gnm_sheet_resize (Sheet *sheet, int cols, int rows,
1467 GOCmdContext *cc, gboolean *perr)
1469 GOUndo *undo = NULL;
1471 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1472 g_return_val_if_fail (gnm_sheet_valid_size (cols, rows), NULL);
1474 if (cols < sheet->size.max_cols || rows < sheet->size.max_rows) {
1475 GSList *overlap, *l;
1476 gboolean bad = FALSE;
1477 GnmRange r;
1479 r.start.col = r.start.row = 0;
1480 r.end.col = MIN (cols, sheet->size.max_cols) - 1;
1481 r.end.row = MIN (rows, sheet->size.max_rows) - 1;
1483 overlap = gnm_sheet_merge_get_overlap (sheet, &r);
1484 for (l = overlap; l && !bad; l = l->next) {
1485 GnmRange const *m = l->data;
1486 if (!range_contained (m, &r)) {
1487 bad = TRUE;
1488 gnm_cmd_context_error_splits_merge (cc, m);
1491 g_slist_free (overlap);
1492 if (bad) {
1493 *perr = TRUE;
1494 return NULL;
1498 gnm_sheet_resize_main (sheet, cols, rows, cc, &undo);
1500 *perr = FALSE;
1501 return undo;
1506 * sheet_new_with_type:
1507 * @wb: #Workbook
1508 * @name: An unquoted name
1509 * @type: @GnmSheetType
1510 * @columns: The number of columns for the sheet
1511 * @rows: The number of rows for the sheet
1513 * Create a new Sheet of type @type, and associate it with @wb.
1514 * The type cannot be changed later.
1516 * Returns: (transfer full): the newly allocated sheet.
1518 Sheet *
1519 sheet_new_with_type (Workbook *wb, char const *name, GnmSheetType type,
1520 int columns, int rows)
1522 Sheet *sheet;
1524 g_return_val_if_fail (wb != NULL, NULL);
1525 g_return_val_if_fail (name != NULL, NULL);
1526 g_return_val_if_fail (gnm_sheet_valid_size (columns, rows), NULL);
1528 sheet = g_object_new (GNM_SHEET_TYPE,
1529 "workbook", wb,
1530 "sheet-type", type,
1531 "columns", columns,
1532 "rows", rows,
1533 "name", name,
1534 "zoom-factor", gnm_conf_get_core_gui_window_zoom (),
1535 NULL);
1537 if (type == GNM_SHEET_OBJECT)
1538 print_info_set_paper_orientation (sheet->print_info, GTK_PAGE_ORIENTATION_LANDSCAPE);
1540 return sheet;
1544 * sheet_new:
1545 * @wb: #Workbook
1546 * @name: The name for the sheet (unquoted).
1547 * @columns: The requested columns number.
1548 * @rows: The requested rows number.
1550 * Create a new Sheet of type SHEET_DATA, and associate it with @wb.
1551 * The type can not be changed later
1553 * Returns: (transfer full): the newly allocated sheet.
1555 Sheet *
1556 sheet_new (Workbook *wb, char const *name, int columns, int rows)
1558 return sheet_new_with_type (wb, name, GNM_SHEET_DATA, columns, rows);
1561 /****************************************************************************/
1563 void
1564 sheet_redraw_all (Sheet const *sheet, gboolean headers)
1566 /* We potentially do a lot of recalcs as part of this, so make sure
1567 stuff that caches sub-computations see the whole thing instead
1568 of clearing between cells. */
1569 gnm_app_recalc_start ();
1570 SHEET_FOREACH_CONTROL (sheet, view, control,
1571 sc_redraw_all (control, headers););
1572 gnm_app_recalc_finish ();
1575 static GnmValue *
1576 cb_clear_rendered_values (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
1578 gnm_cell_unrender (iter->cell);
1579 return NULL;
1583 * sheet_range_calc_spans:
1584 * @sheet: The sheet,
1585 * @r: the region to update.
1586 * @flags:
1588 * This is used to re-calculate cell dimensions and re-render
1589 * a cell's text. eg. if a format has changed we need to re-render
1590 * the cached version of the rendered text in the cell.
1592 void
1593 sheet_range_calc_spans (Sheet *sheet, GnmRange const *r, GnmSpanCalcFlags flags)
1595 if (flags & GNM_SPANCALC_RE_RENDER)
1596 sheet_foreach_cell_in_range
1597 (sheet, CELL_ITER_IGNORE_NONEXISTENT, r,
1598 cb_clear_rendered_values, NULL);
1599 sheet_queue_respan (sheet, r->start.row, r->end.row);
1601 /* Redraw the new region in case the span changes */
1602 sheet_redraw_range (sheet, r);
1605 static void
1606 sheet_redraw_partial_row (Sheet const *sheet, int const row,
1607 int const start_col, int const end_col)
1609 GnmRange r;
1610 range_init (&r, start_col, row, end_col, row);
1611 SHEET_FOREACH_CONTROL (sheet, view, control,
1612 sc_redraw_range (control, &r););
1615 static void
1616 sheet_redraw_cell (GnmCell const *cell)
1618 CellSpanInfo const * span;
1619 int start_col, end_col, row;
1620 GnmRange const *merged;
1621 Sheet *sheet;
1622 ColRowInfo *ri;
1624 g_return_if_fail (cell != NULL);
1626 sheet = cell->base.sheet;
1627 merged = gnm_sheet_merge_is_corner (sheet, &cell->pos);
1628 if (merged != NULL) {
1629 SHEET_FOREACH_CONTROL (sheet, view, control,
1630 sc_redraw_range (control, merged););
1631 return;
1634 row = cell->pos.row;
1635 start_col = end_col = cell->pos.col;
1636 ri = sheet_row_get (sheet, row);
1637 span = row_span_get (ri, start_col);
1639 if (span) {
1640 start_col = span->left;
1641 end_col = span->right;
1644 sheet_redraw_partial_row (sheet, row, start_col, end_col);
1647 static void
1648 sheet_cell_calc_span (GnmCell *cell, GnmSpanCalcFlags flags)
1650 CellSpanInfo const * span;
1651 int left, right;
1652 int min_col, max_col, row;
1653 gboolean render = (flags & GNM_SPANCALC_RE_RENDER) != 0;
1654 gboolean const resize = (flags & GNM_SPANCALC_RESIZE) != 0;
1655 gboolean existing = FALSE;
1656 GnmRange const *merged;
1657 Sheet *sheet;
1658 ColRowInfo *ri;
1660 g_return_if_fail (cell != NULL);
1662 sheet = cell->base.sheet;
1663 row = cell->pos.row;
1665 /* Render and Size any unrendered cells */
1666 if ((flags & GNM_SPANCALC_RENDER) && gnm_cell_get_rendered_value (cell) == NULL)
1667 render = TRUE;
1669 if (render) {
1670 if (!gnm_cell_has_expr (cell))
1671 gnm_cell_render_value ((GnmCell *)cell, TRUE);
1672 else
1673 gnm_cell_unrender (cell);
1674 } else if (resize) {
1675 /* FIXME: what was wanted here? */
1676 /* rendered_value_calc_size (cell); */
1679 /* Is there an existing span ? clear it BEFORE calculating new one */
1680 ri = sheet_row_get (sheet, row);
1681 span = row_span_get (ri, cell->pos.col);
1682 if (span != NULL) {
1683 GnmCell const * const other = span->cell;
1685 min_col = span->left;
1686 max_col = span->right;
1688 /* A different cell used to span into this cell, respan that */
1689 if (cell != other) {
1690 int other_left, other_right;
1692 cell_unregister_span (other);
1693 cell_calc_span (other, &other_left, &other_right);
1694 if (min_col > other_left)
1695 min_col = other_left;
1696 if (max_col < other_right)
1697 max_col = other_right;
1699 if (other_left != other_right)
1700 cell_register_span (other, other_left, other_right);
1701 } else
1702 existing = TRUE;
1703 } else
1704 min_col = max_col = cell->pos.col;
1706 merged = gnm_sheet_merge_is_corner (sheet, &cell->pos);
1707 if (NULL != merged) {
1708 if (existing) {
1709 if (min_col > merged->start.col)
1710 min_col = merged->start.col;
1711 if (max_col < merged->end.col)
1712 max_col = merged->end.col;
1713 } else {
1714 sheet_redraw_cell (cell);
1715 return;
1717 } else {
1718 /* Calculate the span of the cell */
1719 cell_calc_span (cell, &left, &right);
1720 if (min_col > left)
1721 min_col = left;
1722 if (max_col < right)
1723 max_col = right;
1725 /* This cell already had an existing span */
1726 if (existing) {
1727 /* If it changed, remove the old one */
1728 if (left != span->left || right != span->right)
1729 cell_unregister_span (cell);
1730 else
1731 /* unchanged, short curcuit adding the span again */
1732 left = right;
1735 if (left != right)
1736 cell_register_span (cell, left, right);
1739 sheet_redraw_partial_row (sheet, row, min_col, max_col);
1743 * sheet_apply_style: (skip)
1744 * @sheet: the sheet in which can be found
1745 * @range: the range to which should be applied
1746 * @style: (transfer full): A #GnmStyle partial style
1748 * A mid level routine that applies the supplied partial style @style to the
1749 * target @range and performs the necessary respanning and redrawing.
1751 void
1752 sheet_apply_style (Sheet *sheet,
1753 GnmRange const *range,
1754 GnmStyle *style)
1756 GnmSpanCalcFlags spanflags = gnm_style_required_spanflags (style);
1757 sheet_style_apply_range (sheet, range, style);
1758 /* This also redraws the range: */
1759 sheet_range_calc_spans (sheet, range, spanflags);
1763 * sheet_apply_style_gi: (rename-to sheet_apply_style)
1764 * @sheet: the sheet in which can be found
1765 * @range: the range to which should be applied
1766 * @style: A #GnmStyle partial style
1768 * A mid level routine that applies the supplied partial style @style to the
1769 * target @range and performs the necessary respanning and redrawing.
1771 void
1772 sheet_apply_style_gi (Sheet *sheet, GnmRange const *range, GnmStyle *style)
1774 GnmSpanCalcFlags spanflags = gnm_style_required_spanflags (style);
1775 gnm_style_ref (style);
1776 sheet_style_apply_range (sheet, range, style);
1777 /* This also redraws the range: */
1778 sheet_range_calc_spans (sheet, range, spanflags);
1781 static void
1782 sheet_apply_style_cb (GnmSheetRange *sr,
1783 GnmStyle *style)
1785 gnm_style_ref (style);
1786 sheet_apply_style (sr->sheet, &sr->range, style);
1787 sheet_flag_style_update_range (sr->sheet, &sr->range);
1791 * sheet_apply_style_undo:
1792 * @sr: (transfer full): #GnmSheetRange
1793 * @style: (transfer none): #GnmStyle
1795 * Returns: (transfer full): the new #GOUndo.
1797 GOUndo *
1798 sheet_apply_style_undo (GnmSheetRange *sr,
1799 GnmStyle *style)
1801 gnm_style_ref (style);
1802 return go_undo_binary_new
1803 (sr, (gpointer)style,
1804 (GOUndoBinaryFunc) sheet_apply_style_cb,
1805 (GFreeFunc) gnm_sheet_range_free,
1806 (GFreeFunc) gnm_style_unref);
1811 void
1812 sheet_apply_border (Sheet *sheet,
1813 GnmRange const *range,
1814 GnmBorder **borders)
1816 GnmSpanCalcFlags spanflags = GNM_SPANCALC_RE_RENDER | GNM_SPANCALC_RESIZE;
1817 sheet_style_apply_border (sheet, range, borders);
1818 /* This also redraws the range: */
1819 sheet_range_calc_spans (sheet, range, spanflags);
1822 /****************************************************************************/
1824 static ColRowInfo *
1825 sheet_row_new (Sheet *sheet)
1827 ColRowInfo *ri;
1829 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1831 ri = col_row_info_new ();
1832 *ri = sheet->rows.default_style;
1833 ri->is_default = FALSE;
1834 ri->needs_respan = TRUE;
1836 return ri;
1839 static ColRowInfo *
1840 sheet_col_new (Sheet *sheet)
1842 ColRowInfo *ci;
1844 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1846 ci = col_row_info_new ();
1847 *ci = sheet->cols.default_style;
1848 ci->is_default = FALSE;
1850 return ci;
1853 static void
1854 sheet_colrow_add (Sheet *sheet, ColRowInfo *cp, gboolean is_cols, int n)
1856 ColRowCollection *info = is_cols ? &sheet->cols : &sheet->rows;
1857 ColRowSegment **psegment = (ColRowSegment **)&COLROW_GET_SEGMENT (info, n);
1859 g_return_if_fail (n >= 0);
1860 g_return_if_fail (n < colrow_max (is_cols, sheet));
1862 if (*psegment == NULL)
1863 *psegment = g_new0 (ColRowSegment, 1);
1864 colrow_free ((*psegment)->info[COLROW_SUB_INDEX (n)]);
1865 (*psegment)->info[COLROW_SUB_INDEX (n)] = cp;
1867 if (cp->outline_level > info->max_outline_level)
1868 info->max_outline_level = cp->outline_level;
1869 if (n > info->max_used) {
1870 info->max_used = n;
1871 sheet->priv->resize_scrollbar = TRUE;
1875 static void
1876 sheet_reposition_objects (Sheet const *sheet, GnmCellPos const *pos)
1878 GSList *ptr;
1879 for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = ptr->next )
1880 sheet_object_update_bounds (GNM_SO (ptr->data), pos);
1884 * sheet_flag_status_update_cell:
1885 * @cell: The cell that has changed.
1887 * flag the sheet as requiring an update to the status display
1888 * if the supplied cell location is the edit cursor, or part of the
1889 * selected region.
1891 * Will cause the format toolbar, the edit area, and the auto expressions to be
1892 * updated if appropriate.
1894 void
1895 sheet_flag_status_update_cell (GnmCell const *cell)
1897 SHEET_FOREACH_VIEW (cell->base.sheet, sv,
1898 gnm_sheet_view_flag_status_update_pos (sv, &cell->pos););
1902 * sheet_flag_status_update_range:
1903 * @sheet:
1904 * @range: (nullable): GnmRange, or %NULL for full sheet
1906 * flag the sheet as requiring an update to the status display
1907 * if the supplied cell location contains the edit cursor, or intersects of
1908 * the selected region.
1910 * Will cause the format toolbar, the edit area, and the auto expressions to be
1911 * updated if appropriate.
1913 void
1914 sheet_flag_status_update_range (Sheet const *sheet, GnmRange const *range)
1916 SHEET_FOREACH_VIEW (sheet, sv,
1917 gnm_sheet_view_flag_status_update_range (sv, range););
1921 * sheet_flag_style_update_range:
1922 * @sheet: The sheet being changed
1923 * @range: the range that is changing.
1925 * Flag format changes that will require updating the format indicators.
1927 void
1928 sheet_flag_style_update_range (Sheet const *sheet, GnmRange const *range)
1930 SHEET_FOREACH_VIEW (sheet, sv,
1931 gnm_sheet_view_flag_style_update_range (sv, range););
1935 * sheet_flag_recompute_spans:
1936 * @sheet:
1938 * Flag the sheet as requiring a full span recomputation the next time
1939 * sheet_update is called.
1941 void
1942 sheet_flag_recompute_spans (Sheet const *sheet)
1944 sheet->priv->recompute_spans = TRUE;
1947 static gboolean
1948 cb_outline_level (GnmColRowIter const *iter, gpointer data)
1950 int *outline_level = data;
1951 if (*outline_level < iter->cri->outline_level)
1952 *outline_level = iter->cri->outline_level;
1953 return FALSE;
1957 * sheet_colrow_fit_gutter:
1958 * @sheet: Sheet to change.
1959 * @is_cols: %TRUE for columns, %FALSE for rows.
1961 * Find the current max outline level.
1963 static int
1964 sheet_colrow_fit_gutter (Sheet const *sheet, gboolean is_cols)
1966 int outline_level = 0;
1967 sheet_colrow_foreach (sheet, is_cols, 0, -1,
1968 cb_outline_level, &outline_level);
1969 return outline_level;
1973 * sheet_update_only_grid:
1974 * @sheet: #Sheet
1976 * Should be called after a logical command has finished processing
1977 * to request redraws for any pending events
1979 void
1980 sheet_update_only_grid (Sheet const *sheet)
1982 SheetPrivate *p;
1984 g_return_if_fail (IS_SHEET (sheet));
1986 p = sheet->priv;
1988 /* be careful these can toggle flags */
1989 if (p->recompute_max_col_group) {
1990 sheet_colrow_gutter ((Sheet *)sheet, TRUE,
1991 sheet_colrow_fit_gutter (sheet, TRUE));
1992 sheet->priv->recompute_max_col_group = FALSE;
1994 if (p->recompute_max_row_group) {
1995 sheet_colrow_gutter ((Sheet *)sheet, FALSE,
1996 sheet_colrow_fit_gutter (sheet, FALSE));
1997 sheet->priv->recompute_max_row_group = FALSE;
2000 SHEET_FOREACH_VIEW (sheet, sv, {
2001 if (sv->reposition_selection) {
2002 sv->reposition_selection = FALSE;
2004 /* when moving we cleared the selection before
2005 * arriving in here.
2007 if (sv->selections != NULL)
2008 sv_selection_set (sv, &sv->edit_pos_real,
2009 sv->cursor.base_corner.col,
2010 sv->cursor.base_corner.row,
2011 sv->cursor.move_corner.col,
2012 sv->cursor.move_corner.row);
2016 if (p->recompute_spans) {
2017 p->recompute_spans = FALSE;
2018 /* FIXME : I would prefer to use GNM_SPANCALC_RENDER rather than
2019 * RE_RENDER. It only renders those cells which are not
2020 * rendered. The trouble is that when a col changes size we
2021 * need to rerender, but currently nothing marks that.
2023 * hmm, that suggests an approach. maybe I can install a per
2024 * col flag. Then add a flag clearing loop after the
2025 * sheet_calc_span.
2027 #if 0
2028 sheet_calc_spans (sheet, GNM_SPANCALC_RESIZE|GNM_SPANCALC_RE_RENDER |
2029 (p->recompute_visibility ?
2030 SPANCALC_NO_DRAW : GNM_SPANCALC_SIMPLE));
2031 #endif
2032 sheet_queue_respan (sheet, 0, gnm_sheet_get_last_row (sheet));
2035 if (p->reposition_objects.row < gnm_sheet_get_max_rows (sheet) ||
2036 p->reposition_objects.col < gnm_sheet_get_max_cols (sheet)) {
2037 SHEET_FOREACH_VIEW (sheet, sv, {
2038 if (!p->resize && gnm_sheet_view_is_frozen (sv)) {
2039 if (p->reposition_objects.col < sv->unfrozen_top_left.col ||
2040 p->reposition_objects.row < sv->unfrozen_top_left.row) {
2041 gnm_sheet_view_resize (sv, FALSE);
2045 sheet_reposition_objects (sheet, &p->reposition_objects);
2046 p->reposition_objects.row = gnm_sheet_get_max_rows (sheet);
2047 p->reposition_objects.col = gnm_sheet_get_max_cols (sheet);
2050 if (p->resize) {
2051 p->resize = FALSE;
2052 SHEET_FOREACH_VIEW (sheet, sv, { gnm_sheet_view_resize (sv, FALSE); });
2055 if (p->recompute_visibility) {
2056 /* TODO : There is room for some opimization
2057 * We only need to force complete visibility recalculation
2058 * (which we do in sheet_compute_visible_region)
2059 * if a row or col before the start of the visible region.
2060 * If we are REALLY smart we could even accumulate the size differential
2061 * and use that.
2063 p->recompute_visibility = FALSE;
2064 p->resize_scrollbar = FALSE; /* compute_visible_region does this */
2065 SHEET_FOREACH_CONTROL(sheet, view, control,
2066 sc_recompute_visible_region (control, TRUE););
2067 sheet_redraw_all (sheet, TRUE);
2070 if (p->resize_scrollbar) {
2071 sheet_scrollbar_config (sheet);
2072 p->resize_scrollbar = FALSE;
2074 if (p->filters_changed) {
2075 p->filters_changed = FALSE;
2076 SHEET_FOREACH_CONTROL (sheet, sv, sc,
2077 wb_control_menu_state_update (sc_wbc (sc), MS_ADD_VS_REMOVE_FILTER););
2082 * sheet_update:
2083 * @sheet: #Sheet
2085 * Should be called after a logical command has finished processing to request
2086 * redraws for any pending events, and to update the various status regions
2088 void
2089 sheet_update (Sheet const *sheet)
2091 g_return_if_fail (IS_SHEET (sheet));
2093 sheet_update_only_grid (sheet);
2095 SHEET_FOREACH_VIEW (sheet, sv, gnm_sheet_view_update (sv););
2099 * sheet_cell_get:
2100 * @sheet: The sheet where we want to locate the cell
2101 * @col: the cell column
2102 * @row: the cell row
2104 * Return value: (nullable): a #GnmCell, or %NULL if the cell does not exist
2106 GnmCell *
2107 sheet_cell_get (Sheet const *sheet, int col, int row)
2109 GnmCell *cell;
2110 GnmCell key;
2112 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2114 key.pos.col = col;
2115 key.pos.row = row;
2116 cell = g_hash_table_lookup (sheet->cell_hash, &key);
2118 return cell;
2122 * sheet_cell_fetch:
2123 * @sheet: The sheet where we want to locate the cell
2124 * @col: the cell column
2125 * @row: the cell row
2127 * Return value: a #GnmCell containing at (@col,@row).
2128 * If no cell existed at that location before, it is created.
2130 GnmCell *
2131 sheet_cell_fetch (Sheet *sheet, int col, int row)
2133 GnmCell *cell;
2135 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2137 cell = sheet_cell_get (sheet, col, row);
2138 if (!cell)
2139 cell = sheet_cell_create (sheet, col, row);
2141 return cell;
2145 * sheet_colrow_can_group:
2146 * @sheet: #Sheet
2147 * @r: A #GnmRange
2148 * @is_cols: %TRUE for columns, %FALSE for rows.
2150 * Returns: %TRUE if the cols/rows in @r.start -> @r.end can be grouped,
2151 * %FALSE otherwise. You can invert the result if you need to find out if a
2152 * group can be ungrouped.
2154 gboolean
2155 sheet_colrow_can_group (Sheet *sheet, GnmRange const *r, gboolean is_cols)
2157 ColRowInfo const *start_cri, *end_cri;
2158 int start, end;
2160 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
2162 if (is_cols) {
2163 start = r->start.col;
2164 end = r->end.col;
2165 } else {
2166 start = r->start.row;
2167 end = r->end.row;
2169 start_cri = sheet_colrow_fetch (sheet, start, is_cols);
2170 end_cri = sheet_colrow_fetch (sheet, end, is_cols);
2172 /* Groups on outline level 0 (no outline) may always be formed */
2173 if (start_cri->outline_level == 0 || end_cri->outline_level == 0)
2174 return TRUE;
2176 /* We just won't group a group that already exists (or doesn't), it's useless */
2177 return (colrow_find_outline_bound (sheet, is_cols, start, start_cri->outline_level, FALSE) != start ||
2178 colrow_find_outline_bound (sheet, is_cols, end, end_cri->outline_level, TRUE) != end);
2181 gboolean
2182 sheet_colrow_group_ungroup (Sheet *sheet, GnmRange const *r,
2183 gboolean is_cols, gboolean group)
2185 int i, new_max, start, end;
2186 int const step = group ? 1 : -1;
2188 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
2190 /* Can we group/ungroup ? */
2191 if (group != sheet_colrow_can_group (sheet, r, is_cols))
2192 return FALSE;
2194 if (is_cols) {
2195 start = r->start.col;
2196 end = r->end.col;
2197 } else {
2198 start = r->start.row;
2199 end = r->end.row;
2202 /* Set new outline for each col/row and find highest outline level */
2203 new_max = (is_cols ? &sheet->cols : &sheet->rows)->max_outline_level;
2204 for (i = start; i <= end; i++) {
2205 ColRowInfo *cri = sheet_colrow_fetch (sheet, i, is_cols);
2206 int const new_level = cri->outline_level + step;
2208 if (new_level >= 0) {
2209 col_row_info_set_outline (cri, new_level, FALSE);
2210 if (new_max < new_level)
2211 new_max = new_level;
2215 if (!group)
2216 new_max = sheet_colrow_fit_gutter (sheet, is_cols);
2218 sheet_colrow_gutter (sheet, is_cols, new_max);
2219 SHEET_FOREACH_VIEW (sheet, sv,
2220 gnm_sheet_view_redraw_headers (sv, is_cols, !is_cols, NULL););
2222 return TRUE;
2226 * sheet_colrow_gutter:
2227 * @sheet:
2228 * @is_cols: %TRUE for columns, %FALSE for rows.
2229 * @max_outline:
2231 * Set the maximum outline levels for cols or rows.
2233 void
2234 sheet_colrow_gutter (Sheet *sheet, gboolean is_cols, int max_outline)
2236 ColRowCollection *infos;
2238 g_return_if_fail (IS_SHEET (sheet));
2240 infos = is_cols ? &(sheet->cols) : &(sheet->rows);
2241 if (infos->max_outline_level != max_outline) {
2242 sheet->priv->resize = TRUE;
2243 infos->max_outline_level = max_outline;
2247 struct sheet_extent_data {
2248 GnmRange range;
2249 gboolean spans_and_merges_extend;
2250 gboolean ignore_empties;
2251 gboolean include_hidden;
2254 static void
2255 cb_sheet_get_extent (G_GNUC_UNUSED gpointer ignored, gpointer value, gpointer data)
2257 GnmCell const *cell = (GnmCell const *) value;
2258 struct sheet_extent_data *res = data;
2259 Sheet *sheet = cell->base.sheet;
2260 ColRowInfo *ri = NULL;
2262 if (res->ignore_empties && gnm_cell_is_empty (cell))
2263 return;
2264 if (!res->include_hidden) {
2265 ri = sheet_col_get (sheet, cell->pos.col);
2266 if (!ri->visible)
2267 return;
2268 ri = sheet_row_get (sheet, cell->pos.row);
2269 if (!ri->visible)
2270 return;
2273 /* Remember the first cell is the min and max */
2274 if (res->range.start.col > cell->pos.col)
2275 res->range.start.col = cell->pos.col;
2276 if (res->range.end.col < cell->pos.col)
2277 res->range.end.col = cell->pos.col;
2278 if (res->range.start.row > cell->pos.row)
2279 res->range.start.row = cell->pos.row;
2280 if (res->range.end.row < cell->pos.row)
2281 res->range.end.row = cell->pos.row;
2283 if (!res->spans_and_merges_extend)
2284 return;
2286 /* Cannot span AND merge */
2287 if (gnm_cell_is_merged (cell)) {
2288 GnmRange const *merged =
2289 gnm_sheet_merge_is_corner (sheet, &cell->pos);
2290 res->range = range_union (&res->range, merged);
2291 } else {
2292 CellSpanInfo const *span;
2293 if (ri == NULL)
2294 ri = sheet_row_get (sheet, cell->pos.row);
2295 if (ri->needs_respan)
2296 row_calc_spans (ri, cell->pos.row, sheet);
2297 span = row_span_get (ri, cell->pos.col);
2298 if (NULL != span) {
2299 if (res->range.start.col > span->left)
2300 res->range.start.col = span->left;
2301 if (res->range.end.col < span->right)
2302 res->range.end.col = span->right;
2308 * sheet_get_extent:
2309 * @sheet: the sheet
2310 * @spans_and_merges_extend: optionally extend region for spans and merges.
2311 * @include_hidden: whether to include the content of hidden cells.
2313 * calculates the area occupied by cell data.
2315 * NOTE: When spans_and_merges_extend is %TRUE, this function will calculate
2316 * all spans. That might be expensive.
2318 * NOTE: This refers to *visible* contents. Cells with empty values, including
2319 * formulas with such values, are *ignored.
2321 * Return value: the range.
2323 GnmRange
2324 sheet_get_extent (Sheet const *sheet, gboolean spans_and_merges_extend, gboolean include_hidden)
2326 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2327 struct sheet_extent_data closure;
2328 GSList *ptr;
2330 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2332 closure.range.start.col = gnm_sheet_get_last_col (sheet) + 1;
2333 closure.range.start.row = gnm_sheet_get_last_row (sheet) + 1;
2334 closure.range.end.col = 0;
2335 closure.range.end.row = 0;
2336 closure.spans_and_merges_extend = spans_and_merges_extend;
2337 closure.include_hidden = include_hidden;
2338 closure.ignore_empties = TRUE;
2340 sheet_cell_foreach (sheet, &cb_sheet_get_extent, &closure);
2342 for (ptr = sheet->sheet_objects; ptr; ptr = ptr->next) {
2343 SheetObject *so = GNM_SO (ptr->data);
2345 closure.range.start.col = MIN (so->anchor.cell_bound.start.col,
2346 closure.range.start.col);
2347 closure.range.start.row = MIN (so->anchor.cell_bound.start.row,
2348 closure.range.start.row);
2349 closure.range.end.col = MAX (so->anchor.cell_bound.end.col,
2350 closure.range.end.col);
2351 closure.range.end.row = MAX (so->anchor.cell_bound.end.row,
2352 closure.range.end.row);
2355 if (closure.range.start.col > gnm_sheet_get_last_col (sheet))
2356 closure.range.start.col = 0;
2357 if (closure.range.start.row > gnm_sheet_get_last_row (sheet))
2358 closure.range.start.row = 0;
2359 if (closure.range.end.col < 0)
2360 closure.range.end.col = 0;
2361 if (closure.range.end.row < 0)
2362 closure.range.end.row = 0;
2364 return closure.range;
2368 * sheet_get_cells_extent:
2369 * @sheet: the sheet
2371 * Calculates the area occupied by cells, including empty cells.
2373 * Return value: the range.
2375 GnmRange
2376 sheet_get_cells_extent (Sheet const *sheet)
2378 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2379 struct sheet_extent_data closure;
2381 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2383 closure.range.start.col = gnm_sheet_get_last_col (sheet);
2384 closure.range.start.row = gnm_sheet_get_last_row (sheet);
2385 closure.range.end.col = 0;
2386 closure.range.end.row = 0;
2387 closure.spans_and_merges_extend = FALSE;
2388 closure.include_hidden = TRUE;
2389 closure.ignore_empties = FALSE;
2391 sheet_cell_foreach (sheet, &cb_sheet_get_extent, &closure);
2393 return closure.range;
2397 GnmRange *
2398 sheet_get_nominal_printarea (Sheet const *sheet)
2400 GnmNamedExpr *nexpr;
2401 GnmValue *val;
2402 GnmParsePos pos;
2403 GnmRange *r;
2404 GnmRangeRef const *r_ref;
2405 gint max_rows;
2406 gint max_cols;
2408 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2410 parse_pos_init_sheet (&pos, sheet);
2411 nexpr = expr_name_lookup (&pos, "Print_Area");
2412 if (nexpr == NULL)
2413 return NULL;
2415 val = gnm_expr_top_get_range (nexpr->texpr);
2416 r_ref = val ? value_get_rangeref (val) : NULL;
2417 if (r_ref == NULL) {
2418 value_release (val);
2419 return NULL;
2422 r = g_new0 (GnmRange, 1);
2423 range_init_rangeref (r, r_ref);
2424 value_release (val);
2426 if (r->end.col >= (max_cols = gnm_sheet_get_max_cols (sheet)))
2427 r->end.col = max_cols - 1;
2428 if (r->end.row >= (max_rows = gnm_sheet_get_max_rows (sheet)))
2429 r->end.row = max_rows - 1;
2430 if (r->start.col < 0)
2431 r->start.col = 0;
2432 if (r->start.row < 0)
2433 r->start.row = 0;
2435 return r;
2438 GnmRange
2439 sheet_get_printarea (Sheet const *sheet,
2440 gboolean include_styles,
2441 gboolean ignore_printarea)
2443 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2444 GnmRange print_area;
2446 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2448 if (!ignore_printarea) {
2449 GnmRange *r = sheet_get_nominal_printarea (sheet);
2450 if (r != NULL) {
2451 print_area = *r;
2452 g_free (r);
2453 return print_area;
2457 print_area = sheet_get_extent (sheet, TRUE, FALSE);
2458 if (include_styles)
2459 sheet_style_get_extent (sheet, &print_area);
2461 return print_area;
2464 struct cb_fit {
2465 int max;
2466 gboolean ignore_strings;
2469 /* find the maximum width in a range. */
2470 static GnmValue *
2471 cb_max_cell_width (GnmCellIter const *iter, struct cb_fit *data)
2473 int width;
2474 GnmCell *cell = iter->cell;
2475 GnmRenderedValue *rv;
2477 if (gnm_cell_is_merged (cell))
2478 return NULL;
2481 * Special handling for manual recalc. We need to eval newly
2482 * entered expressions. gnm_cell_render_value will do that for us,
2483 * but we want to short-circuit some strings early.
2485 if (cell->base.flags & GNM_CELL_HAS_NEW_EXPR)
2486 gnm_cell_eval (cell);
2488 if (data->ignore_strings && VALUE_IS_STRING (cell->value))
2489 return NULL;
2491 /* Variable width cell must be re-rendered */
2492 rv = gnm_cell_get_rendered_value (cell);
2493 if (rv == NULL || rv->variable_width)
2494 gnm_cell_render_value (cell, FALSE);
2496 /* Make sure things are as-if drawn. */
2497 cell_finish_layout (cell, NULL, iter->ci->size_pixels, TRUE);
2499 width = gnm_cell_rendered_width (cell) + gnm_cell_rendered_offset (cell);
2500 if (width > data->max)
2501 data->max = width;
2503 return NULL;
2507 * sheet_col_size_fit_pixels:
2508 * @sheet: The sheet
2509 * @col: the column that we want to query
2510 * @srow: starting row.
2511 * @erow: ending row.
2512 * @ignore_strings: skip cells containing string values.
2514 * This routine computes the ideal size for the column to make the contents all
2515 * cells in the column visible.
2517 * Returns: Maximum size in pixels INCLUDING margins and grid lines
2518 * or 0 if there are no cells.
2521 sheet_col_size_fit_pixels (Sheet *sheet, int col, int srow, int erow,
2522 gboolean ignore_strings)
2524 struct cb_fit data;
2525 ColRowInfo *ci = sheet_col_get (sheet, col);
2526 if (ci == NULL)
2527 return 0;
2529 data.max = -1;
2530 data.ignore_strings = ignore_strings;
2531 sheet_foreach_cell_in_region (sheet,
2532 CELL_ITER_IGNORE_NONEXISTENT |
2533 CELL_ITER_IGNORE_HIDDEN |
2534 CELL_ITER_IGNORE_FILTERED,
2535 col, srow, col, erow,
2536 (CellIterFunc)&cb_max_cell_width, &data);
2538 /* Reset to the default width if the column was empty */
2539 if (data.max <= 0)
2540 return 0;
2542 /* GnmCell width does not include margins or far grid line*/
2543 return data.max + GNM_COL_MARGIN + GNM_COL_MARGIN + 1;
2546 /* find the maximum height in a range. */
2547 static GnmValue *
2548 cb_max_cell_height (GnmCellIter const *iter, struct cb_fit *data)
2550 int height;
2551 GnmCell *cell = iter->cell;
2553 if (gnm_cell_is_merged (cell))
2554 return NULL;
2557 * Special handling for manual recalc. We need to eval newly
2558 * entered expressions. gnm_cell_render_value will do that for us,
2559 * but we want to short-circuit some strings early.
2561 if (cell->base.flags & GNM_CELL_HAS_NEW_EXPR)
2562 gnm_cell_eval (cell);
2564 if (data->ignore_strings && VALUE_IS_STRING (cell->value))
2565 return NULL;
2567 if (!VALUE_IS_STRING (cell->value)) {
2569 * Mildly cheating to avoid performance problems, See bug
2570 * 359392. This assumes that non-strings do not wrap and
2571 * that they are all the same height, more or less.
2573 Sheet const *sheet = cell->base.sheet;
2574 height = gnm_style_get_pango_height (gnm_cell_get_style (cell),
2575 sheet->rendered_values->context,
2576 sheet->last_zoom_factor_used);
2577 } else {
2578 (void)gnm_cell_fetch_rendered_value (cell, TRUE);
2580 /* Make sure things are as-if drawn. Inhibit #####s. */
2581 cell_finish_layout (cell, NULL, iter->ci->size_pixels, FALSE);
2583 height = gnm_cell_rendered_height (cell);
2586 if (height > data->max)
2587 data->max = height;
2589 return NULL;
2593 * sheet_row_size_fit_pixels:
2594 * @sheet: The sheet
2595 * @row: the row that we want to query
2596 * @scol: starting column.
2597 * @ecol: ending column.
2598 * @ignore_strings: skip cells containing string values.
2600 * This routine computes the ideal size for the row to make all data fit
2601 * properly.
2603 * Returns: Maximum size in pixels INCLUDING margins and grid lines
2604 * or 0 if there are no cells.
2607 sheet_row_size_fit_pixels (Sheet *sheet, int row, int scol, int ecol,
2608 gboolean ignore_strings)
2610 struct cb_fit data;
2611 ColRowInfo const *ri = sheet_row_get (sheet, row);
2612 if (ri == NULL)
2613 return 0;
2615 data.max = -1;
2616 data.ignore_strings = ignore_strings;
2617 sheet_foreach_cell_in_region (sheet,
2618 CELL_ITER_IGNORE_NONEXISTENT |
2619 CELL_ITER_IGNORE_HIDDEN |
2620 CELL_ITER_IGNORE_FILTERED,
2621 scol, row,
2622 ecol, row,
2623 (CellIterFunc)&cb_max_cell_height, &data);
2625 /* Reset to the default width if the column was empty */
2626 if (data.max <= 0)
2627 return 0;
2629 /* GnmCell height does not include margins or bottom grid line */
2630 return data.max + GNM_ROW_MARGIN + GNM_ROW_MARGIN + 1;
2633 struct recalc_span_closure {
2634 Sheet *sheet;
2635 int col;
2638 static gboolean
2639 cb_recalc_spans_in_col (GnmColRowIter const *iter, gpointer user)
2641 struct recalc_span_closure *closure = user;
2642 int const col = closure->col;
2643 int left, right;
2644 CellSpanInfo const *span = row_span_get (iter->cri, col);
2646 if (span) {
2647 /* If there is an existing span see if it changed */
2648 GnmCell const * const cell = span->cell;
2649 cell_calc_span (cell, &left, &right);
2650 if (left != span->left || right != span->right) {
2651 cell_unregister_span (cell);
2652 cell_register_span (cell, left, right);
2654 } else {
2655 /* If there is a cell see if it started to span */
2656 GnmCell const * const cell = sheet_cell_get (closure->sheet, col, iter->pos);
2657 if (cell) {
2658 cell_calc_span (cell, &left, &right);
2659 if (left != right)
2660 cell_register_span (cell, left, right);
2664 return FALSE;
2668 * sheet_recompute_spans_for_col:
2669 * @sheet: the sheet
2670 * @col: The column that changed
2672 * This routine recomputes the column span for the cells that touches
2673 * the column.
2675 void
2676 sheet_recompute_spans_for_col (Sheet *sheet, int col)
2678 struct recalc_span_closure closure;
2679 closure.sheet = sheet;
2680 closure.col = col;
2682 sheet_colrow_foreach (sheet, FALSE, 0, -1,
2683 &cb_recalc_spans_in_col, &closure);
2686 /****************************************************************************/
2687 typedef struct {
2688 GnmValue *val;
2689 GnmExprTop const *texpr;
2690 GnmRange expr_bound;
2691 } closure_set_cell_value;
2693 static GnmValue *
2694 cb_set_cell_content (GnmCellIter const *iter, closure_set_cell_value *info)
2696 GnmExprTop const *texpr = info->texpr;
2697 GnmCell *cell;
2699 cell = iter->cell;
2700 if (!cell)
2701 cell = sheet_cell_create (iter->pp.sheet,
2702 iter->pp.eval.col,
2703 iter->pp.eval.row);
2706 * If we are overwriting an array, we need to clear things here
2707 * or gnm_cell_set_expr/gnm_cell_set_value will complain.
2709 if (cell->base.texpr && gnm_expr_top_is_array (cell->base.texpr))
2710 gnm_cell_cleanout (cell);
2712 if (texpr != NULL) {
2713 if (!range_contains (&info->expr_bound,
2714 iter->pp.eval.col, iter->pp.eval.row)) {
2715 GnmExprRelocateInfo rinfo;
2717 rinfo.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE;
2718 rinfo.pos = iter->pp;
2719 rinfo.origin.start = iter->pp.eval;
2720 rinfo.origin.end = iter->pp.eval;
2721 rinfo.origin_sheet = iter->pp.sheet;
2722 rinfo.target_sheet = iter->pp.sheet;
2723 rinfo.col_offset = 0;
2724 rinfo.row_offset = 0;
2725 texpr = gnm_expr_top_relocate (texpr, &rinfo, FALSE);
2728 gnm_cell_set_expr (cell, texpr);
2729 } else
2730 gnm_cell_set_value (cell, value_dup (info->val));
2731 return NULL;
2734 static GnmValue *
2735 cb_clear_non_corner (GnmCellIter const *iter, GnmRange const *merged)
2737 if (merged->start.col != iter->pp.eval.col ||
2738 merged->start.row != iter->pp.eval.row)
2739 gnm_cell_set_value (iter->cell, value_new_empty ());
2740 return NULL;
2744 * sheet_range_set_expr_cb:
2745 * @sr: #GnmSheetRange
2746 * @texpr: #GnmExprTop
2749 * Does NOT check for array division.
2751 static void
2752 sheet_range_set_expr_cb (GnmSheetRange const *sr, GnmExprTop const *texpr)
2754 closure_set_cell_value closure;
2755 GSList *merged, *ptr;
2757 g_return_if_fail (sr != NULL);
2758 g_return_if_fail (texpr != NULL);
2760 closure.texpr = texpr;
2761 gnm_expr_top_get_boundingbox (closure.texpr,
2762 sr->sheet,
2763 &closure.expr_bound);
2765 sheet_region_queue_recalc (sr->sheet, &sr->range);
2766 /* Store the parsed result creating any cells necessary */
2767 sheet_foreach_cell_in_range
2768 (sr->sheet, CELL_ITER_ALL, &sr->range,
2769 (CellIterFunc)&cb_set_cell_content, &closure);
2771 merged = gnm_sheet_merge_get_overlap (sr->sheet, &sr->range);
2772 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
2773 GnmRange const *tmp = ptr->data;
2774 sheet_foreach_cell_in_range
2775 (sr->sheet, CELL_ITER_IGNORE_BLANK, tmp,
2776 (CellIterFunc)&cb_clear_non_corner,
2777 (gpointer)tmp);
2779 g_slist_free (merged);
2781 sheet_region_queue_recalc (sr->sheet, &sr->range);
2782 sheet_flag_status_update_range (sr->sheet, &sr->range);
2783 sheet_queue_respan (sr->sheet, sr->range.start.row,
2784 sr->range.end.row);
2788 * sheet_range_set_expr_undo:
2789 * @sr: (transfer full): #GnmSheetRange
2790 * @texpr: (transfer none): #GnmExprTop
2792 * Returns: (transfer full): the newly created #GOUndo.
2794 GOUndo *
2795 sheet_range_set_expr_undo (GnmSheetRange *sr, GnmExprTop const *texpr)
2797 gnm_expr_top_ref (texpr);
2798 return go_undo_binary_new
2799 (sr, (gpointer)texpr,
2800 (GOUndoBinaryFunc) sheet_range_set_expr_cb,
2801 (GFreeFunc) gnm_sheet_range_free,
2802 (GFreeFunc) gnm_expr_top_unref);
2807 * sheet_range_set_text:
2808 * @pos: The position from which to parse an expression.
2809 * @r: The range to fill
2810 * @str: The text to be parsed and assigned.
2812 * Does NOT check for array division.
2813 * Does NOT redraw
2814 * Does NOT generate spans.
2816 void
2817 sheet_range_set_text (GnmParsePos const *pos, GnmRange const *r, char const *str)
2819 closure_set_cell_value closure;
2820 GSList *merged, *ptr;
2821 Sheet *sheet;
2823 g_return_if_fail (pos != NULL);
2824 g_return_if_fail (r != NULL);
2825 g_return_if_fail (str != NULL);
2827 sheet = pos->sheet;
2829 parse_text_value_or_expr (pos, str,
2830 &closure.val, &closure.texpr);
2832 if (closure.texpr)
2833 gnm_expr_top_get_boundingbox (closure.texpr,
2834 sheet,
2835 &closure.expr_bound);
2837 /* Store the parsed result creating any cells necessary */
2838 sheet_foreach_cell_in_range (sheet, CELL_ITER_ALL, r,
2839 (CellIterFunc)&cb_set_cell_content, &closure);
2841 merged = gnm_sheet_merge_get_overlap (sheet, r);
2842 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
2843 GnmRange const *tmp = ptr->data;
2844 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_BLANK, tmp,
2845 (CellIterFunc)&cb_clear_non_corner, (gpointer)tmp);
2847 g_slist_free (merged);
2849 sheet_region_queue_recalc (sheet, r);
2851 value_release (closure.val);
2852 if (closure.texpr)
2853 gnm_expr_top_unref (closure.texpr);
2855 sheet_flag_status_update_range (sheet, r);
2858 static void
2859 sheet_range_set_text_cb (GnmSheetRange const *sr, gchar const *text)
2861 GnmParsePos pos;
2863 pos.eval = sr->range.start;
2864 pos.sheet = sr->sheet;
2865 pos.wb = sr->sheet->workbook;
2867 sheet_range_set_text (&pos, &sr->range, text);
2868 sheet_region_queue_recalc (sr->sheet, &sr->range);
2869 sheet_flag_status_update_range (sr->sheet, &sr->range);
2870 sheet_queue_respan (sr->sheet, sr->range.start.row,
2871 sr->range.end.row);
2872 sheet_redraw_range (sr->sheet, &sr->range);
2876 * sheet_range_set_text_undo:
2877 * @sr: (transfer full): #GnmSheetRange
2878 * @text: (transfer none): text for range
2880 * Returns: (transfer full): the newly created #GOUndo.
2882 GOUndo *
2883 sheet_range_set_text_undo (GnmSheetRange *sr,
2884 char const *text)
2886 return go_undo_binary_new
2887 (sr, g_strdup (text),
2888 (GOUndoBinaryFunc) sheet_range_set_text_cb,
2889 (GFreeFunc) gnm_sheet_range_free,
2890 (GFreeFunc) g_free);
2894 static GnmValue *
2895 cb_set_markup (GnmCellIter const *iter, PangoAttrList *markup)
2897 GnmCell *cell;
2899 cell = iter->cell;
2900 if (!cell)
2901 return NULL;
2903 if (VALUE_IS_STRING (cell->value)) {
2904 GOFormat *fmt;
2905 GnmValue *val = value_dup (cell->value);
2907 fmt = go_format_new_markup (markup, TRUE);
2908 value_set_fmt (val, fmt);
2909 go_format_unref (fmt);
2911 gnm_cell_cleanout (cell);
2912 gnm_cell_assign_value (cell, val);
2914 return NULL;
2917 static void
2918 sheet_range_set_markup_cb (GnmSheetRange const *sr, PangoAttrList *markup)
2920 sheet_foreach_cell_in_range
2921 (sr->sheet, CELL_ITER_ALL, &sr->range,
2922 (CellIterFunc)&cb_set_markup, markup);
2924 sheet_region_queue_recalc (sr->sheet, &sr->range);
2925 sheet_flag_status_update_range (sr->sheet, &sr->range);
2926 sheet_queue_respan (sr->sheet, sr->range.start.row,
2927 sr->range.end.row);
2931 * sheet_range_set_markup_undo:
2932 * @sr: (transfer full): #GnmSheetRange
2933 * @markup: (transfer none) (nullable): #PangoAttrList
2935 * Returns: (transfer full) (nullable): the newly created #GOUndo.
2937 GOUndo *
2938 sheet_range_set_markup_undo (GnmSheetRange *sr, PangoAttrList *markup)
2940 if (markup == NULL)
2941 return NULL;
2942 return go_undo_binary_new
2943 (sr, pango_attr_list_ref (markup),
2944 (GOUndoBinaryFunc) sheet_range_set_markup_cb,
2945 (GFreeFunc) gnm_sheet_range_free,
2946 (GFreeFunc) pango_attr_list_unref);
2950 * sheet_cell_get_value:
2951 * @sheet: Sheet
2952 * @col: Source column
2953 * @row: Source row
2955 * Returns: (transfer none) (nullable): the cell's current value. The return
2956 * value will be %NULL only when the cell does not exist.
2958 GnmValue const *
2959 sheet_cell_get_value (Sheet *sheet, int const col, int const row)
2961 GnmCell *cell;
2963 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2965 cell = sheet_cell_get (sheet, col, row);
2967 return cell ? cell->value : NULL;
2971 * sheet_cell_set_text:
2972 * @cell: A cell.
2973 * @str: the text to set.
2974 * @markup: (allow-none): an optional PangoAttrList.
2976 * Marks the sheet as dirty
2977 * Clears old spans.
2978 * Flags status updates
2979 * Queues recalcs
2981 void
2982 sheet_cell_set_text (GnmCell *cell, char const *text, PangoAttrList *markup)
2984 GnmExprTop const *texpr;
2985 GnmValue *val;
2986 GnmParsePos pp;
2988 g_return_if_fail (cell != NULL);
2989 g_return_if_fail (text != NULL);
2990 g_return_if_fail (!gnm_cell_is_nonsingleton_array (cell));
2992 parse_text_value_or_expr (parse_pos_init_cell (&pp, cell),
2993 text, &val, &texpr);
2995 /* Queue a redraw before in case the span changes */
2996 sheet_redraw_cell (cell);
2998 if (texpr != NULL) {
2999 gnm_cell_set_expr (cell, texpr);
3000 gnm_expr_top_unref (texpr);
3003 * Queue recalc before spanning. Otherwise spanning may
3004 * create a bogus rendered value, see #495879.
3006 cell_queue_recalc (cell);
3008 /* Clear spans from _other_ cells */
3009 sheet_cell_calc_span (cell, GNM_SPANCALC_SIMPLE);
3010 } else {
3011 g_return_if_fail (val != NULL);
3013 if (markup != NULL && VALUE_IS_STRING (val)) {
3014 gboolean quoted = (text[0] == '\'');
3015 PangoAttrList *adj_markup;
3016 GOFormat *fmt;
3018 if (quoted) {
3019 /* We ate the quote. Adjust. Ugh. */
3020 adj_markup = pango_attr_list_copy (markup);
3021 go_pango_attr_list_erase (adj_markup, 0, 1);
3022 } else
3023 adj_markup = markup;
3025 fmt = go_format_new_markup (adj_markup, TRUE);
3026 value_set_fmt (val, fmt);
3027 go_format_unref (fmt);
3028 if (quoted)
3029 pango_attr_list_unref (adj_markup);
3032 gnm_cell_set_value (cell, val);
3034 /* Queue recalc before spanning, see above. */
3035 cell_queue_recalc (cell);
3037 sheet_cell_calc_span (cell, GNM_SPANCALC_RESIZE | GNM_SPANCALC_RENDER);
3040 sheet_flag_status_update_cell (cell);
3044 * sheet_cell_set_text_gi: (rename-to sheet_cell_set_text)
3045 * @sheet: #Sheet
3046 * @col: column number
3047 * @row: row number
3048 * @str: the text to set.
3050 * Sets the contents of a cell.
3052 void
3053 sheet_cell_set_text_gi (Sheet *sheet, int col, int row, char const *str)
3055 sheet_cell_set_text (sheet_cell_fetch (sheet, col, row), str, NULL);
3060 * sheet_cell_set_expr:
3061 * @cell: #GnmCell
3062 * @texpr: New expression for @cell.
3064 * Marks the sheet as dirty
3065 * Clears old spans.
3066 * Flags status updates
3067 * Queues recalcs
3069 void
3070 sheet_cell_set_expr (GnmCell *cell, GnmExprTop const *texpr)
3072 gnm_cell_set_expr (cell, texpr);
3074 /* clear spans from _other_ cells */
3075 sheet_cell_calc_span (cell, GNM_SPANCALC_SIMPLE);
3077 cell_queue_recalc (cell);
3078 sheet_flag_status_update_cell (cell);
3082 * sheet_cell_set_value: (skip)
3083 * @cell: #GnmCell
3084 * @v: (transfer full): #GnmValue
3086 * Stores, without copying, the supplied value. It marks the
3087 * sheet as dirty.
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 * NOTE : This DOES check for array partitioning.
3094 void
3095 sheet_cell_set_value (GnmCell *cell, GnmValue *v)
3097 /* TODO : if the value is unchanged do not assign it */
3098 gnm_cell_set_value (cell, v);
3099 sheet_cell_calc_span (cell, GNM_SPANCALC_RESIZE | GNM_SPANCALC_RENDER);
3100 cell_queue_recalc (cell);
3101 sheet_flag_status_update_cell (cell);
3105 * sheet_cell_set_value_gi: (rename-to sheet_cell_set_value)
3106 * @sheet: #Sheet
3107 * @col: column number
3108 * @row: row number
3109 * @v: #GnmValue
3111 * Set the the value of the cell at (@col,@row) to @v.
3113 * The value is rendered and spans are calculated. It queues a redraw
3114 * and checks to see if the edit region or selection content changed.
3116 void
3117 sheet_cell_set_value_gi (Sheet *sheet, int col, int row, GnmValue *v)
3119 // This version exists because not all versions of pygobject
3120 // understand transfer-full parameters
3121 sheet_cell_set_value (sheet_cell_fetch (sheet, col, row),
3122 value_dup (v));
3125 /****************************************************************************/
3128 * This routine is used to queue the redraw regions for the
3129 * cell region specified.
3131 * It is usually called before a change happens to a region,
3132 * and after the change has been done to queue the regions
3133 * for the old contents and the new contents.
3135 * It intelligently handles spans and merged ranges
3137 void
3138 sheet_range_bounding_box (Sheet const *sheet, GnmRange *bound)
3140 GSList *ptr;
3141 int row;
3142 GnmRange r = *bound;
3144 g_return_if_fail (range_is_sane (bound));
3146 /* Check the first and last columns for spans and extend the region to
3147 * include the maximum extent.
3149 for (row = r.start.row; row <= r.end.row; row++){
3150 ColRowInfo const *ri = sheet_row_get (sheet, row);
3152 if (ri != NULL) {
3153 CellSpanInfo const * span0;
3155 if (ri->needs_respan)
3156 row_calc_spans ((ColRowInfo *)ri, row, sheet);
3158 span0 = row_span_get (ri, r.start.col);
3160 if (span0 != NULL) {
3161 if (bound->start.col > span0->left)
3162 bound->start.col = span0->left;
3163 if (bound->end.col < span0->right)
3164 bound->end.col = span0->right;
3166 if (r.start.col != r.end.col) {
3167 CellSpanInfo const * span1 =
3168 row_span_get (ri, r.end.col);
3170 if (span1 != NULL) {
3171 if (bound->start.col > span1->left)
3172 bound->start.col = span1->left;
3173 if (bound->end.col < span1->right)
3174 bound->end.col = span1->right;
3177 /* skip segments with no cells */
3178 } else if (row == COLROW_SEGMENT_START (row)) {
3179 ColRowSegment const * const segment =
3180 COLROW_GET_SEGMENT (&(sheet->rows), row);
3181 if (segment == NULL)
3182 row = COLROW_SEGMENT_END (row);
3186 /* TODO : this may get expensive if there are alot of merged ranges */
3187 /* no need to iterate, one pass is enough */
3188 for (ptr = sheet->list_merged ; ptr != NULL ; ptr = ptr->next) {
3189 GnmRange const * const test = ptr->data;
3190 if (r.start.row <= test->end.row || r.end.row >= test->start.row) {
3191 if (bound->start.col > test->start.col)
3192 bound->start.col = test->start.col;
3193 if (bound->end.col < test->end.col)
3194 bound->end.col = test->end.col;
3195 if (bound->start.row > test->start.row)
3196 bound->start.row = test->start.row;
3197 if (bound->end.row < test->end.row)
3198 bound->end.row = test->end.row;
3203 void
3204 sheet_redraw_region (Sheet const *sheet,
3205 int start_col, int start_row,
3206 int end_col, int end_row)
3208 GnmRange bound;
3210 g_return_if_fail (IS_SHEET (sheet));
3213 * Getting the bounding box causes row respans to be done if
3214 * needed. That can be expensive, so just redraw the whole
3215 * sheet if the row count is too big.
3217 if (end_row - start_row > 500) {
3218 sheet_redraw_all (sheet, FALSE);
3219 return;
3222 /* We potentially do a lot of recalcs as part of this, so make sure
3223 stuff that caches sub-computations see the whole thing instead
3224 of clearing between cells. */
3225 gnm_app_recalc_start ();
3227 sheet_range_bounding_box (sheet,
3228 range_init (&bound, start_col, start_row, end_col, end_row));
3229 SHEET_FOREACH_CONTROL (sheet, view, control,
3230 sc_redraw_range (control, &bound););
3232 gnm_app_recalc_finish ();
3235 void
3236 sheet_redraw_range (Sheet const *sheet, GnmRange const *range)
3238 g_return_if_fail (IS_SHEET (sheet));
3239 g_return_if_fail (range != NULL);
3241 sheet_redraw_region (sheet,
3242 range->start.col, range->start.row,
3243 range->end.col, range->end.row);
3246 /****************************************************************************/
3248 gboolean
3249 sheet_col_is_hidden (Sheet const *sheet, int col)
3251 ColRowInfo const * const res = sheet_col_get (sheet, col);
3252 return (res != NULL && !res->visible);
3255 gboolean
3256 sheet_row_is_hidden (Sheet const *sheet, int row)
3258 ColRowInfo const * const res = sheet_row_get (sheet, row);
3259 return (res != NULL && !res->visible);
3264 * sheet_find_boundary_horizontal:
3265 * @sheet: The Sheet
3266 * @col: The column from which to begin searching.
3267 * @move_row: The row in which to search for the edge of the range.
3268 * @base_row: The height of the area being moved.
3269 * @count: units to extend the selection vertically
3270 * @jump_to_boundaries: Jump to range boundaries.
3272 * Calculate the column index for the column which is @count units
3273 * from @start_col doing bounds checking. If @jump_to_boundaries is
3274 * %TRUE then @count must be 1 and the jump is to the edge of the logical range.
3276 * This routine implements the logic necasary for ctrl-arrow style
3277 * movement. That is more complicated than simply finding the last in a list
3278 * of cells with content. If you are at the end of a range it will find the
3279 * start of the next. Make sure that is the sort of behavior you want before
3280 * calling this.
3282 * Returns: the column index.
3285 sheet_find_boundary_horizontal (Sheet *sheet, int start_col, int move_row,
3286 int base_row, int count,
3287 gboolean jump_to_boundaries)
3289 gboolean find_nonblank = sheet_is_cell_empty (sheet, start_col, move_row);
3290 gboolean keep_looking = FALSE;
3291 int new_col, prev_col, lagged_start_col, max_col = gnm_sheet_get_last_col (sheet);
3292 int iterations = 0;
3293 GnmRange check_merge;
3294 GnmRange const * const bound = &sheet->priv->unhidden_region;
3296 /* Jumping to bounds requires steping cell by cell */
3297 g_return_val_if_fail (count == 1 || count == -1 || !jump_to_boundaries, start_col);
3298 g_return_val_if_fail (IS_SHEET (sheet), start_col);
3300 if (move_row < base_row) {
3301 check_merge.start.row = move_row;
3302 check_merge.end.row = base_row;
3303 } else {
3304 check_merge.end.row = move_row;
3305 check_merge.start.row = base_row;
3308 do {
3309 GSList *merged, *ptr;
3311 lagged_start_col = check_merge.start.col = check_merge.end.col = start_col;
3312 merged = gnm_sheet_merge_get_overlap (sheet, &check_merge);
3313 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3314 GnmRange const * const r = ptr->data;
3315 if (count > 0) {
3316 if (start_col < r->end.col)
3317 start_col = r->end.col;
3318 } else {
3319 if (start_col > r->start.col)
3320 start_col = r->start.col;
3323 g_slist_free (merged);
3324 } while (start_col != lagged_start_col);
3325 new_col = prev_col = start_col;
3327 do {
3328 new_col += count;
3329 ++iterations;
3331 if (new_col < bound->start.col)
3332 return MIN (bound->start.col, max_col);
3333 if (new_col > bound->end.col)
3334 return MIN (bound->end.col, max_col);
3336 keep_looking = sheet_col_is_hidden (sheet, new_col);
3337 if (jump_to_boundaries) {
3338 if (new_col > sheet->cols.max_used) {
3339 if (count > 0)
3340 return (find_nonblank || iterations == 1)?
3341 MIN (bound->end.col, max_col):
3342 MIN (prev_col, max_col);
3343 new_col = sheet->cols.max_used;
3346 keep_looking |= (sheet_is_cell_empty (sheet, new_col, move_row) == find_nonblank);
3347 if (keep_looking)
3348 prev_col = new_col;
3349 else if (!find_nonblank) {
3351 * Handle special case where we are on the last
3352 * non-NULL cell
3354 if (iterations == 1)
3355 keep_looking = find_nonblank = TRUE;
3356 else
3357 new_col = prev_col;
3360 } while (keep_looking);
3362 return MIN (new_col, max_col);
3366 * sheet_find_boundary_vertical:
3367 * @sheet: The Sheet
3368 * @move_col: The col in which to search for the edge of the range.
3369 * @row: The row from which to begin searching.
3370 * @base_col: The width of the area being moved.
3371 * @count: units to extend the selection vertically
3372 * @jump_to_boundaries: Jump to range boundaries.
3374 * Calculate the row index for the row which is @count units
3375 * from @start_row doing bounds checking. If @jump_to_boundaries is
3376 * %TRUE then @count must be 1 and the jump is to the edge of the logical range.
3378 * This routine implements the logic necasary for ctrl-arrow style
3379 * movement. That is more complicated than simply finding the last in a list
3380 * of cells with content. If you are at the end of a range it will find the
3381 * start of the next. Make sure that is the sort of behavior you want before
3382 * calling this.
3384 * Returns: the row index.
3387 sheet_find_boundary_vertical (Sheet *sheet, int move_col, int start_row,
3388 int base_col, int count,
3389 gboolean jump_to_boundaries)
3391 gboolean find_nonblank = sheet_is_cell_empty (sheet, move_col, start_row);
3392 gboolean keep_looking = FALSE;
3393 int new_row, prev_row, lagged_start_row, max_row = gnm_sheet_get_last_row (sheet);
3394 int iterations = 0;
3395 GnmRange check_merge;
3396 GnmRange const * const bound = &sheet->priv->unhidden_region;
3398 /* Jumping to bounds requires steping cell by cell */
3399 g_return_val_if_fail (count == 1 || count == -1 || !jump_to_boundaries, start_row);
3400 g_return_val_if_fail (IS_SHEET (sheet), start_row);
3402 if (move_col < base_col) {
3403 check_merge.start.col = move_col;
3404 check_merge.end.col = base_col;
3405 } else {
3406 check_merge.end.col = move_col;
3407 check_merge.start.col = base_col;
3410 do {
3411 GSList *merged, *ptr;
3413 lagged_start_row = check_merge.start.row = check_merge.end.row = start_row;
3414 merged = gnm_sheet_merge_get_overlap (sheet, &check_merge);
3415 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3416 GnmRange const * const r = ptr->data;
3417 if (count > 0) {
3418 if (start_row < r->end.row)
3419 start_row = r->end.row;
3420 } else {
3421 if (start_row > r->start.row)
3422 start_row = r->start.row;
3425 g_slist_free (merged);
3426 } while (start_row != lagged_start_row);
3427 new_row = prev_row = start_row;
3429 do {
3430 new_row += count;
3431 ++iterations;
3433 if (new_row < bound->start.row)
3434 return MIN (bound->start.row, max_row);
3435 if (new_row > bound->end.row)
3436 return MIN (bound->end.row, max_row);
3438 keep_looking = sheet_row_is_hidden (sheet, new_row);
3439 if (jump_to_boundaries) {
3440 if (new_row > sheet->rows.max_used) {
3441 if (count > 0)
3442 return (find_nonblank || iterations == 1)?
3443 MIN (bound->end.row, max_row):
3444 MIN (prev_row, max_row);
3445 new_row = sheet->rows.max_used;
3448 keep_looking |= (sheet_is_cell_empty (sheet, move_col, new_row) == find_nonblank);
3449 if (keep_looking)
3450 prev_row = new_row;
3451 else if (!find_nonblank) {
3453 * Handle special case where we are on the last
3454 * non-NULL cell
3456 if (iterations == 1)
3457 keep_looking = find_nonblank = TRUE;
3458 else
3459 new_row = prev_row;
3462 } while (keep_looking);
3464 return MIN (new_row, max_row);
3467 typedef enum {
3468 CHECK_AND_LOAD_START = 1,
3469 CHECK_END = 2,
3470 LOAD_END = 4
3471 } ArrayCheckFlags;
3473 typedef struct {
3474 Sheet const *sheet;
3475 int flags;
3476 int start, end;
3477 GnmRange const *ignore;
3479 GnmRange error;
3480 } ArrayCheckData;
3482 static gboolean
3483 cb_check_array_horizontal (GnmColRowIter const *iter, gpointer data_)
3485 ArrayCheckData *data = data_;
3486 gboolean is_array = FALSE;
3488 if (data->flags & CHECK_AND_LOAD_START && /* Top */
3489 (is_array = gnm_cell_array_bound (
3490 sheet_cell_get (data->sheet, iter->pos, data->start),
3491 &data->error)) &&
3492 data->error.start.row < data->start &&
3493 (data->ignore == NULL ||
3494 !range_contained (&data->error, data->ignore)))
3495 return TRUE;
3497 if (data->flags & LOAD_END)
3498 is_array = gnm_cell_array_bound (
3499 sheet_cell_get (data->sheet, iter->pos, data->end),
3500 &data->error);
3502 return (data->flags & CHECK_END &&
3503 is_array &&
3504 data->error.end.row > data->end && /* Bottom */
3505 (data->ignore == NULL ||
3506 !range_contained (&data->error, data->ignore)));
3509 static gboolean
3510 cb_check_array_vertical (GnmColRowIter const *iter, gpointer data_)
3512 ArrayCheckData *data = data_;
3513 gboolean is_array = FALSE;
3515 if (data->flags & CHECK_AND_LOAD_START && /* Left */
3516 (is_array = gnm_cell_array_bound (
3517 sheet_cell_get (data->sheet, data->start, iter->pos),
3518 &data->error)) &&
3519 data->error.start.col < data->start &&
3520 (data->ignore == NULL ||
3521 !range_contained (&data->error, data->ignore)))
3522 return TRUE;
3524 if (data->flags & LOAD_END)
3525 is_array = gnm_cell_array_bound (
3526 sheet_cell_get (data->sheet, data->end, iter->pos),
3527 &data->error);
3529 return (data->flags & CHECK_END &&
3530 is_array &&
3531 data->error.end.col > data->end && /* Right */
3532 (data->ignore == NULL ||
3533 !range_contained (&data->error, data->ignore)));
3537 * sheet_range_splits_array:
3538 * @sheet: The sheet.
3539 * @r: The range to check
3540 * @ignore: (nullable): a range in which it is ok to have an array.
3541 * @cc: (nullable): place to report an error.
3542 * @cmd: (nullable): cmd name used with @cc.
3544 * Check the outer edges of range @sheet!@r to ensure that if an array is
3545 * within it then the entire array is within the range. @ignore is useful when
3546 * src and dest ranges may overlap.
3548 * Returns: %TRUE if an array would be split.
3550 gboolean
3551 sheet_range_splits_array (Sheet const *sheet,
3552 GnmRange const *r, GnmRange const *ignore,
3553 GOCmdContext *cc, char const *cmd)
3555 ArrayCheckData closure;
3557 g_return_val_if_fail (r->start.col <= r->end.col, FALSE);
3558 g_return_val_if_fail (r->start.row <= r->end.row, FALSE);
3560 closure.sheet = sheet;
3561 closure.ignore = ignore;
3563 closure.start = r->start.row;
3564 closure.end = r->end.row;
3565 if (closure.start <= 0) {
3566 closure.flags = (closure.end < sheet->rows.max_used)
3567 ? CHECK_END | LOAD_END
3568 : 0;
3569 } else if (closure.end < sheet->rows.max_used)
3570 closure.flags = (closure.start == closure.end)
3571 ? CHECK_AND_LOAD_START | CHECK_END
3572 : CHECK_AND_LOAD_START | CHECK_END | LOAD_END;
3573 else
3574 closure.flags = CHECK_AND_LOAD_START;
3576 if (closure.flags &&
3577 sheet_colrow_foreach (sheet, TRUE,
3578 r->start.col, r->end.col,
3579 cb_check_array_horizontal, &closure)) {
3580 if (cc)
3581 gnm_cmd_context_error_splits_array (cc,
3582 cmd, &closure.error);
3583 return TRUE;
3586 closure.start = r->start.col;
3587 closure.end = r->end.col;
3588 if (closure.start <= 0) {
3589 closure.flags = (closure.end < sheet->cols.max_used)
3590 ? CHECK_END | LOAD_END
3591 : 0;
3592 } else if (closure.end < sheet->cols.max_used)
3593 closure.flags = (closure.start == closure.end)
3594 ? CHECK_AND_LOAD_START | CHECK_END
3595 : CHECK_AND_LOAD_START | CHECK_END | LOAD_END;
3596 else
3597 closure.flags = CHECK_AND_LOAD_START;
3599 if (closure.flags &&
3600 sheet_colrow_foreach (sheet, FALSE,
3601 r->start.row, r->end.row,
3602 cb_check_array_vertical, &closure)) {
3603 if (cc)
3604 gnm_cmd_context_error_splits_array (cc,
3605 cmd, &closure.error);
3606 return TRUE;
3608 return FALSE;
3612 * sheet_range_splits_region:
3613 * @sheet: the sheet.
3614 * @r: The range whose boundaries are checked
3615 * @ignore: An optional range in which it is ok to have arrays and merges
3616 * @cc: The context that issued the command
3617 * @cmd: The translated command name.
3619 * A utility to see whether moving the range @r will split any arrays
3620 * or merged regions.
3621 * Returns: whether any arrays or merged regions will be split.
3623 gboolean
3624 sheet_range_splits_region (Sheet const *sheet,
3625 GnmRange const *r, GnmRange const *ignore,
3626 GOCmdContext *cc, char const *cmd_name)
3628 GSList *merged;
3630 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
3632 /* Check for array subdivision */
3633 if (sheet_range_splits_array (sheet, r, ignore, cc, cmd_name))
3634 return TRUE;
3636 merged = gnm_sheet_merge_get_overlap (sheet, r);
3637 if (merged) {
3638 GSList *ptr;
3640 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3641 GnmRange const *m = ptr->data;
3642 if (ignore != NULL && range_contained (m, ignore))
3643 continue;
3644 if (!range_contained (m, r))
3645 break;
3647 g_slist_free (merged);
3649 if (cc != NULL && ptr != NULL) {
3650 go_cmd_context_error_invalid (cc, cmd_name,
3651 _("Target region contains merged cells"));
3652 return TRUE;
3655 return FALSE;
3659 * sheet_ranges_split_region:
3660 * @sheet: the sheet.
3661 * @ranges: (element-type GnmRange): A list of ranges to check.
3662 * @cc: The context that issued the command
3663 * @cmd: The translated command name.
3665 * A utility to see whether moving any of the ranges @ranges will split any
3666 * arrays or merged regions.
3667 * Returns: whether any arrays or merged regions will be splitted.
3669 gboolean
3670 sheet_ranges_split_region (Sheet const * sheet, GSList const *ranges,
3671 GOCmdContext *cc, char const *cmd)
3673 GSList const *l;
3675 /* Check for array subdivision */
3676 for (l = ranges; l != NULL; l = l->next) {
3677 GnmRange const *r = l->data;
3678 if (sheet_range_splits_region (sheet, r, NULL, cc, cmd))
3679 return TRUE;
3681 return FALSE;
3684 static GnmValue *
3685 cb_cell_is_array (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
3687 return gnm_cell_is_array (iter->cell) ? VALUE_TERMINATE : NULL;
3691 * sheet_range_contains_merges_or_arrays:
3692 * @sheet: The sheet
3693 * @r: the range to check.
3694 * @cc: an optional place to report errors.
3695 * @cmd:
3696 * @merges: if %TRUE, check for merges.
3697 * @arrays: if %TRUE, check for arrays.
3699 * Check to see if the target region @sheet!@r contains any merged regions or
3700 * arrays. Report an error to the @cc if it is supplied.
3701 * Returns: %TRUE if the target region @sheet!@r contains any merged regions or
3702 * arrays.
3704 gboolean
3705 sheet_range_contains_merges_or_arrays (Sheet const *sheet, GnmRange const *r,
3706 GOCmdContext *cc, char const *cmd,
3707 gboolean merges, gboolean arrays)
3709 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
3711 if (merges) {
3712 GSList *merged = gnm_sheet_merge_get_overlap (sheet, r);
3713 if (merged != NULL) {
3714 if (cc != NULL)
3715 go_cmd_context_error_invalid
3716 (cc, cmd,
3717 _("cannot operate on merged cells"));
3718 g_slist_free (merged);
3719 return TRUE;
3723 if (arrays) {
3724 if (sheet_foreach_cell_in_range (
3725 (Sheet *)sheet, CELL_ITER_IGNORE_NONEXISTENT, r,
3726 cb_cell_is_array, NULL)) {
3727 if (cc != NULL)
3728 go_cmd_context_error_invalid
3729 (cc, cmd,
3730 _("cannot operate on array formul\303\246"));
3731 return TRUE;
3735 return FALSE;
3738 /***************************************************************************/
3741 * sheet_colrow_get_default:
3742 * @sheet:
3743 * @is_cols: %TRUE for columns, %FALSE for rows.
3745 * Returns: (transfer none): the default #ColRowInfo.
3747 ColRowInfo const *
3748 sheet_colrow_get_default (Sheet const *sheet, gboolean is_cols)
3750 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3752 return is_cols ? &sheet->cols.default_style : &sheet->rows.default_style;
3755 static void
3756 sheet_colrow_optimize1 (int max, int max_used, ColRowCollection *collection)
3758 int i;
3759 int first_unused = max_used + 1;
3761 for (i = COLROW_SEGMENT_START (first_unused);
3762 i < max;
3763 i += COLROW_SEGMENT_SIZE) {
3764 ColRowSegment *segment = COLROW_GET_SEGMENT (collection, i);
3765 int j;
3766 gboolean any = FALSE;
3768 if (!segment)
3769 continue;
3770 for (j = 0; j < COLROW_SEGMENT_SIZE; j++) {
3771 ColRowInfo *info = segment->info[j];
3772 if (!info)
3773 continue;
3774 if (i + j >= first_unused &&
3775 col_row_info_equal (&collection->default_style, info)) {
3776 colrow_free (info);
3777 segment->info[j] = NULL;
3778 } else {
3779 any = TRUE;
3780 if (i + j >= first_unused)
3781 max_used = i + j;
3785 if (!any) {
3786 g_free (segment);
3787 COLROW_GET_SEGMENT (collection, i) = NULL;
3791 collection->max_used = max_used;
3794 void
3795 sheet_colrow_optimize (Sheet *sheet)
3797 GnmRange extent;
3799 g_return_if_fail (IS_SHEET (sheet));
3801 extent = sheet_get_cells_extent (sheet);
3803 sheet_colrow_optimize1 (gnm_sheet_get_max_cols (sheet),
3804 extent.end.col,
3805 &sheet->cols);
3806 sheet_colrow_optimize1 (gnm_sheet_get_max_rows (sheet),
3807 extent.end.row,
3808 &sheet->rows);
3812 * sheet_col_get:
3813 * @col: column number
3815 * Returns: (transfer none) (nullable): A #ColRowInfo for the column, or %NULL
3816 * if none has been allocated yet.
3818 ColRowInfo *
3819 sheet_col_get (Sheet const *sheet, int col)
3821 ColRowSegment *segment;
3823 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3824 g_return_val_if_fail (col < gnm_sheet_get_max_cols (sheet), NULL);
3825 g_return_val_if_fail (col >= 0, NULL);
3827 segment = COLROW_GET_SEGMENT (&(sheet->cols), col);
3828 if (segment != NULL)
3829 return segment->info[COLROW_SUB_INDEX (col)];
3830 return NULL;
3834 * sheet_row_get:
3835 * @row: row number
3837 * Returns: (transfer none) (nullable): A #ColRowInfo for the row, or %NULL
3838 * if none has been allocated yet.
3840 ColRowInfo *
3841 sheet_row_get (Sheet const *sheet, int row)
3843 ColRowSegment *segment;
3845 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3846 g_return_val_if_fail (row < gnm_sheet_get_max_rows (sheet), NULL);
3847 g_return_val_if_fail (row >= 0, NULL);
3849 segment = COLROW_GET_SEGMENT (&(sheet->rows), row);
3850 if (segment != NULL)
3851 return segment->info[COLROW_SUB_INDEX (row)];
3852 return NULL;
3855 ColRowInfo *
3856 sheet_colrow_get (Sheet const *sheet, int colrow, gboolean is_cols)
3858 if (is_cols)
3859 return sheet_col_get (sheet, colrow);
3860 return sheet_row_get (sheet, colrow);
3864 * sheet_col_fetch:
3865 * @col: column number
3867 * Returns: (transfer none): The #ColRowInfo for column @col. This result
3868 * will not be the default #ColRowInfo and may be changed.
3870 ColRowInfo *
3871 sheet_col_fetch (Sheet *sheet, int pos)
3873 ColRowInfo *cri = sheet_col_get (sheet, pos);
3874 if (NULL == cri && NULL != (cri = sheet_col_new (sheet)))
3875 sheet_colrow_add (sheet, cri, TRUE, pos);
3876 return cri;
3880 * sheet_row_fetch:
3881 * @row: row number
3883 * Returns: (transfer none): The #ColRowInfo for row @row. This result
3884 * will not be the default #ColRowInfo and may be changed.
3886 ColRowInfo *
3887 sheet_row_fetch (Sheet *sheet, int pos)
3889 ColRowInfo *cri = sheet_row_get (sheet, pos);
3890 if (NULL == cri && NULL != (cri = sheet_row_new (sheet)))
3891 sheet_colrow_add (sheet, cri, FALSE, pos);
3892 return cri;
3895 ColRowInfo *
3896 sheet_colrow_fetch (Sheet *sheet, int colrow, gboolean is_cols)
3898 if (is_cols)
3899 return sheet_col_fetch (sheet, colrow);
3900 return sheet_row_fetch (sheet, colrow);
3904 * sheet_col_get_info:
3905 * @col: column number
3907 * Returns: (transfer none): The #ColRowInfo for column @col. The may be
3908 * the default #ColRowInfo for columns and should not be changed.
3910 ColRowInfo const *
3911 sheet_col_get_info (Sheet const *sheet, int col)
3913 ColRowInfo *ci = sheet_col_get (sheet, col);
3915 if (ci != NULL)
3916 return ci;
3917 return &sheet->cols.default_style;
3921 * sheet_row_get_info:
3922 * @row: column number
3924 * Returns: (transfer none): The #ColRowInfo for row @row. The may be
3925 * the default #ColRowInfo for rows and should not be changed.
3927 ColRowInfo const *
3928 sheet_row_get_info (Sheet const *sheet, int row)
3930 ColRowInfo *ri = sheet_row_get (sheet, row);
3932 if (ri != NULL)
3933 return ri;
3934 return &sheet->rows.default_style;
3937 ColRowInfo const *
3938 sheet_colrow_get_info (Sheet const *sheet, int colrow, gboolean is_cols)
3940 return is_cols
3941 ? sheet_col_get_info (sheet, colrow)
3942 : sheet_row_get_info (sheet, colrow);
3946 * sheet_colrow_foreach:
3947 * @sheet: #Sheet
3948 * @is_cols: %TRUE for columns, %FALSE for rows.
3949 * @first: start position (inclusive)
3950 * @last: stop position (inclusive), -1 meaning end-of-sheet
3951 * @callback: (scope call): A callback function which should return %TRUE
3952 * to stop the iteration.
3953 * @user_data: A baggage pointer.
3955 * Iterates through the existing rows or columns within the range supplied.
3956 * If a callback returns %TRUE, iteration stops.
3958 gboolean
3959 sheet_colrow_foreach (Sheet const *sheet,
3960 gboolean is_cols,
3961 int first, int last,
3962 ColRowHandler callback,
3963 gpointer user_data)
3965 ColRowCollection const *infos;
3966 GnmColRowIter iter;
3967 ColRowSegment const *segment;
3968 int sub, inner_last, i;
3970 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
3972 if (last == -1)
3973 last = colrow_max (is_cols, sheet) - 1;
3974 infos = is_cols ? &sheet->cols : &sheet->rows;
3976 /* clip */
3977 if (last > infos->max_used)
3978 last = infos->max_used;
3980 for (i = first; i <= last ; ) {
3981 segment = COLROW_GET_SEGMENT (infos, i);
3982 sub = COLROW_SUB_INDEX(i);
3983 inner_last = (COLROW_SEGMENT_INDEX (last) == COLROW_SEGMENT_INDEX (i))
3984 ? COLROW_SUB_INDEX (last)+1 : COLROW_SEGMENT_SIZE;
3985 iter.pos = i;
3986 i += COLROW_SEGMENT_SIZE - sub;
3987 if (segment == NULL)
3988 continue;
3990 for (; sub < inner_last; sub++, iter.pos++) {
3991 iter.cri = segment->info[sub];
3992 if (iter.cri != NULL && (*callback)(&iter, user_data))
3993 return TRUE;
3997 return FALSE;
4001 /*****************************************************************************/
4003 static gint
4004 cell_ordering (gconstpointer a_, gconstpointer b_)
4006 GnmCell const *a = *(GnmCell **)a_;
4007 GnmCell const *b = *(GnmCell **)b_;
4009 if (a->pos.row != b->pos.row)
4010 return a->pos.row - b->pos.row;
4012 return a->pos.col - b->pos.col;
4016 * sheet_cells:
4017 * @sheet: a #Sheet
4018 * @r: (nullable): a #GnmRange
4020 * Retrieves an array of all cells inside @r.
4021 * Returns: (element-type GnmCell) (transfer container): the cells array.
4023 GPtrArray *
4024 sheet_cells (Sheet *sheet, const GnmRange *r)
4026 GPtrArray *res = g_ptr_array_new ();
4027 GHashTableIter hiter;
4028 gpointer value;
4030 g_hash_table_iter_init (&hiter, sheet->cell_hash);
4031 while (g_hash_table_iter_next (&hiter, NULL, &value)) {
4032 GnmCell *cell = value;
4033 if (!r || range_contains (r, cell->pos.col, cell->pos.row))
4034 g_ptr_array_add (res, cell);
4036 g_ptr_array_sort (res, cell_ordering);
4038 return res;
4043 #define SWAP_INT(a,b) do { int t; t = a; a = b; b = t; } while (0)
4046 * sheet_foreach_cell_in_range:
4047 * @sheet: #Sheet
4048 * @flags:
4049 * @r: #GnmRange
4050 * @callback: (scope call): #CellFilterFunc
4051 * @closure: user data.
4053 * For each existing cell in the range specified, invoke the
4054 * callback routine. If the only_existing flag is passed, then
4055 * callbacks are only invoked for existing cells.
4057 * Note: this function does not honour the CELL_ITER_IGNORE_SUBTOTAL flag.
4059 * Returns: (transfer none): the value returned by the callback, which can be:
4060 * non-%NULL on error, or VALUE_TERMINATE if some invoked routine requested
4061 * to stop (by returning non-%NULL).
4063 * NOTE: between 0.56 and 0.57, the traversal order changed. The order is now
4065 * 1 2 3
4066 * 4 5 6
4067 * 7 8 9
4069 * (This appears to be the order in which XL looks at the values of ranges.)
4070 * If your code depends on any particular ordering, please add a very visible
4071 * comment near the call.
4073 GnmValue *
4074 sheet_foreach_cell_in_range (Sheet *sheet, CellIterFlags flags,
4075 GnmRange const *r,
4076 CellIterFunc callback,
4077 gpointer closure)
4079 return sheet_foreach_cell_in_region (sheet, flags,
4080 r->start.col, r->start.row,
4081 r->end.col, r->end.row,
4082 callback, closure);
4087 * sheet_foreach_cell_in_region:
4088 * @sheet: #Sheet
4089 * @flags:
4090 * @start_col: Starting column
4091 * @start_row: Starting row
4092 * @end_col: Ending column, -1 meaning last
4093 * @end_row: Ending row, -1 meaning last
4094 * @callback: (scope call): #CellFilterFunc
4095 * @closure: user data.
4097 * For each existing cell in the range specified, invoke the
4098 * callback routine. If the only_existing flag is passed, then
4099 * callbacks are only invoked for existing cells.
4101 * Note: this function does not honour the CELL_ITER_IGNORE_SUBTOTAL flag.
4103 * Returns: (transfer none): the value returned by the callback, which can be:
4104 * non-%NULL on error, or VALUE_TERMINATE if some invoked routine requested
4105 * to stop (by returning non-%NULL).
4107 * NOTE: between 0.56 and 0.57, the traversal order changed. The order is now
4109 * 1 2 3
4110 * 4 5 6
4111 * 7 8 9
4113 * (This appears to be the order in which XL looks at the values of ranges.)
4114 * If your code depends on any particular ordering, please add a very visible
4115 * comment near the call.
4117 GnmValue *
4118 sheet_foreach_cell_in_region (Sheet *sheet, CellIterFlags flags,
4119 int start_col, int start_row,
4120 int end_col, int end_row,
4121 CellIterFunc callback, void *closure)
4123 GnmValue *cont;
4124 GnmCellIter iter;
4125 gboolean const visibility_matters = (flags & CELL_ITER_IGNORE_HIDDEN) != 0;
4126 gboolean const ignore_filtered = (flags & CELL_ITER_IGNORE_FILTERED) != 0;
4127 gboolean const only_existing = (flags & CELL_ITER_IGNORE_NONEXISTENT) != 0;
4128 gboolean const ignore_empty = (flags & CELL_ITER_IGNORE_EMPTY) != 0;
4129 gboolean ignore;
4130 gboolean use_celllist;
4131 guint64 range_size;
4133 g_return_val_if_fail (IS_SHEET (sheet), NULL);
4134 g_return_val_if_fail (callback != NULL, NULL);
4136 // For convenience
4137 if (end_col == -1) end_col = gnm_sheet_get_last_col (sheet);
4138 if (end_row == -1) end_row = gnm_sheet_get_last_row (sheet);
4140 iter.pp.sheet = sheet;
4141 iter.pp.wb = sheet->workbook;
4143 if (start_col > end_col)
4144 SWAP_INT (start_col, end_col);
4145 if (end_col < 0 || start_col > gnm_sheet_get_last_col (sheet))
4146 return NULL;
4147 start_col = MAX (0, start_col);
4148 end_col = MIN (end_col, gnm_sheet_get_last_col (sheet));
4150 if (start_row > end_row)
4151 SWAP_INT (start_row, end_row);
4152 if (end_row < 0 || start_row > gnm_sheet_get_last_row (sheet))
4153 return NULL;
4154 start_row = MAX (0, start_row);
4155 end_row = MIN (end_row, gnm_sheet_get_last_row (sheet));
4157 range_size = (guint64)(end_row - start_row + 1) * (end_col - start_col + 1);
4158 use_celllist =
4159 only_existing &&
4160 range_size > g_hash_table_size (sheet->cell_hash) + 1000;
4161 if (use_celllist) {
4162 GPtrArray *all_cells;
4163 int last_row = -1, last_col = -1;
4164 GnmValue *res = NULL;
4165 unsigned ui;
4166 GnmRange r;
4168 if (gnm_debug_flag ("sheet-foreach"))
4169 g_printerr ("Using celllist for area of size %d\n",
4170 (int)range_size);
4172 range_init (&r, start_col, start_row, end_col, end_row);
4173 all_cells = sheet_cells (sheet, &r);
4175 for (ui = 0; ui < all_cells->len; ui++) {
4176 GnmCell *cell = g_ptr_array_index (all_cells, ui);
4178 iter.cell = cell;
4179 iter.pp.eval.row = cell->pos.row;
4180 iter.pp.eval.col = cell->pos.col;
4182 if (iter.pp.eval.row != last_row) {
4183 last_row = iter.pp.eval.row;
4184 iter.ri = sheet_row_get (iter.pp.sheet, last_row);
4186 if (iter.ri == NULL) {
4187 g_critical ("Cell without row data -- please report");
4188 continue;
4190 if (visibility_matters && !iter.ri->visible)
4191 continue;
4192 if (ignore_filtered && iter.ri->in_filter && !iter.ri->visible)
4193 continue;
4195 if (iter.pp.eval.col != last_col) {
4196 last_col = iter.pp.eval.col;
4197 iter.ci = sheet_col_get (iter.pp.sheet, last_col);
4199 if (iter.ci == NULL) {
4200 g_critical ("Cell without column data -- please report");
4201 continue;
4203 if (visibility_matters && !iter.ci->visible)
4204 continue;
4206 ignore = (ignore_empty &&
4207 VALUE_IS_EMPTY (cell->value) &&
4208 !gnm_cell_needs_recalc (cell));
4209 if (ignore)
4210 continue;
4212 res = (*callback) (&iter, closure);
4213 if (res != NULL)
4214 break;
4217 g_ptr_array_free (all_cells, TRUE);
4218 return res;
4221 for (iter.pp.eval.row = start_row;
4222 iter.pp.eval.row <= end_row;
4223 ++iter.pp.eval.row) {
4224 iter.ri = sheet_row_get (iter.pp.sheet, iter.pp.eval.row);
4226 /* no need to check visibility, that would require a colinfo to exist */
4227 if (iter.ri == NULL) {
4228 if (only_existing) {
4229 /* skip segments with no cells */
4230 if (iter.pp.eval.row == COLROW_SEGMENT_START (iter.pp.eval.row)) {
4231 ColRowSegment const *segment =
4232 COLROW_GET_SEGMENT (&(sheet->rows), iter.pp.eval.row);
4233 if (segment == NULL)
4234 iter.pp.eval.row = COLROW_SEGMENT_END (iter.pp.eval.row);
4236 } else {
4237 iter.cell = NULL;
4238 for (iter.pp.eval.col = start_col; iter.pp.eval.col <= end_col; ++iter.pp.eval.col) {
4239 cont = (*callback) (&iter, closure);
4240 if (cont != NULL)
4241 return cont;
4245 continue;
4248 if (visibility_matters && !iter.ri->visible)
4249 continue;
4250 if (ignore_filtered && iter.ri->in_filter && !iter.ri->visible)
4251 continue;
4253 for (iter.pp.eval.col = start_col; iter.pp.eval.col <= end_col; ++iter.pp.eval.col) {
4254 iter.ci = sheet_col_get (sheet, iter.pp.eval.col);
4255 if (iter.ci != NULL) {
4256 if (visibility_matters && !iter.ci->visible)
4257 continue;
4258 iter.cell = sheet_cell_get (sheet,
4259 iter.pp.eval.col, iter.pp.eval.row);
4260 } else
4261 iter.cell = NULL;
4263 ignore = (iter.cell == NULL)
4264 ? (only_existing || ignore_empty)
4265 : (ignore_empty && VALUE_IS_EMPTY (iter.cell->value) &&
4266 !gnm_cell_needs_recalc (iter.cell));
4268 if (ignore) {
4269 if (iter.pp.eval.col == COLROW_SEGMENT_START (iter.pp.eval.col)) {
4270 ColRowSegment const *segment =
4271 COLROW_GET_SEGMENT (&(sheet->cols), iter.pp.eval.col);
4272 if (segment == NULL)
4273 iter.pp.eval.col = COLROW_SEGMENT_END (iter.pp.eval.col);
4275 continue;
4278 cont = (*callback) (&iter, closure);
4279 if (cont != NULL)
4280 return cont;
4283 return NULL;
4287 * sheet_cell_foreach:
4288 * @sheet: #Sheet
4289 * @callback: (scope call):
4290 * @data:
4292 * Call @callback with an argument of @data for each cell in the sheet
4294 void
4295 sheet_cell_foreach (Sheet const *sheet, GHFunc callback, gpointer data)
4297 g_return_if_fail (IS_SHEET (sheet));
4299 g_hash_table_foreach (sheet->cell_hash, callback, data);
4303 * sheet_cells_count:
4304 * @sheet: #Sheet
4306 * Returns the number of cells with content in the current workbook.
4308 unsigned
4309 sheet_cells_count (Sheet const *sheet)
4311 return g_hash_table_size (sheet->cell_hash);
4314 static void
4315 cb_sheet_cells_collect (G_GNUC_UNUSED gpointer unused,
4316 GnmCell const *cell,
4317 GPtrArray *cells)
4319 GnmEvalPos *ep = eval_pos_init_cell (g_new (GnmEvalPos, 1), cell);
4320 g_ptr_array_add (cells, ep);
4324 * sheet_cell_positions:
4325 * @sheet: The sheet to find cells in.
4326 * @comments: If true, include cells with only comments also.
4328 * Collects a GPtrArray of GnmEvalPos pointers for all cells in a sheet.
4329 * No particular order should be assumed.
4330 * Returns: (element-type GnmEvalPos) (transfer full): the newly created array
4332 GPtrArray *
4333 sheet_cell_positions (Sheet *sheet, gboolean comments)
4335 GPtrArray *cells = g_ptr_array_new ();
4337 g_return_val_if_fail (IS_SHEET (sheet), cells);
4339 sheet_cell_foreach (sheet, (GHFunc)cb_sheet_cells_collect, cells);
4341 if (comments) {
4342 GnmRange r;
4343 GSList *scomments, *ptr;
4345 range_init_full_sheet (&r, sheet);
4346 scomments = sheet_objects_get (sheet, &r, GNM_CELL_COMMENT_TYPE);
4347 for (ptr = scomments; ptr; ptr = ptr->next) {
4348 GnmComment *c = ptr->data;
4349 GnmRange const *loc = sheet_object_get_range (GNM_SO (c));
4350 GnmCell *cell = sheet_cell_get (sheet, loc->start.col, loc->start.row);
4351 if (!cell) {
4352 /* If cell does not exist, we haven't seen it... */
4353 GnmEvalPos *ep = g_new (GnmEvalPos, 1);
4354 ep->sheet = sheet;
4355 ep->eval.col = loc->start.col;
4356 ep->eval.row = loc->start.row;
4357 g_ptr_array_add (cells, ep);
4360 g_slist_free (scomments);
4363 return cells;
4367 static GnmValue *
4368 cb_fail_if_exist (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
4370 return gnm_cell_is_empty (iter->cell) ? NULL : VALUE_TERMINATE;
4374 * sheet_is_region_empty:
4375 * @sheet: sheet to check
4376 * @r: region to check
4378 * Returns: %TRUE if the specified region of the @sheet does not
4379 * contain any cells
4381 gboolean
4382 sheet_is_region_empty (Sheet *sheet, GnmRange const *r)
4384 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
4386 return sheet_foreach_cell_in_range (
4387 sheet, CELL_ITER_IGNORE_BLANK, r,
4388 cb_fail_if_exist, NULL) == NULL;
4391 gboolean
4392 sheet_is_cell_empty (Sheet *sheet, int col, int row)
4394 GnmCell const *cell = sheet_cell_get (sheet, col, row);
4395 return gnm_cell_is_empty (cell);
4399 * sheet_cell_add_to_hash:
4400 * @sheet The sheet where the cell is inserted
4401 * @cell The cell, it should already have col/pos pointers
4402 * initialized pointing to the correct ColRowInfo
4404 * GnmCell::pos must be valid before this is called. The position is used as the
4405 * hash key.
4407 static void
4408 sheet_cell_add_to_hash (Sheet *sheet, GnmCell *cell)
4410 g_return_if_fail (cell->pos.col < gnm_sheet_get_max_cols (sheet));
4411 g_return_if_fail (cell->pos.row < gnm_sheet_get_max_rows (sheet));
4413 cell->base.flags |= GNM_CELL_IN_SHEET_LIST;
4414 /* NOTE:
4415 * fetching the col/row here serve 3 functions
4416 * 1) obsolete: we used to store the pointer in the cell
4417 * 2) Expanding col/row.max_used
4418 * 3) Creating an entry in the COLROW_SEGMENT. Lots and lots of
4419 * things use those to help limit iteration
4421 * For now just call col_fetch even though it is not necessary to
4422 * ensure that 2,3 still happen. Alot will need rewriting to avoid
4423 * these requirements.
4425 (void)sheet_col_fetch (sheet, cell->pos.col);
4426 (void)sheet_row_fetch (sheet, cell->pos.row);
4428 gnm_cell_unrender (cell);
4430 g_hash_table_insert (sheet->cell_hash, cell, cell);
4432 if (gnm_sheet_merge_is_corner (sheet, &cell->pos))
4433 cell->base.flags |= GNM_CELL_IS_MERGED;
4436 #undef USE_CELL_POOL
4438 #ifdef USE_CELL_POOL
4439 /* The pool from which all cells are allocated. */
4440 static GOMemChunk *cell_pool;
4441 #else
4442 static int cell_allocations = 0;
4443 #endif
4445 static GnmCell *
4446 cell_new (void)
4448 GnmCell *cell =
4449 #ifdef USE_CELL_POOL
4450 go_mem_chunk_alloc0 (cell_pool)
4451 #else
4452 (cell_allocations++, g_slice_new0 (GnmCell))
4453 #endif
4456 cell->base.flags = DEPENDENT_CELL;
4457 return cell;
4461 static void
4462 cell_free (GnmCell *cell)
4464 g_return_if_fail (cell != NULL);
4466 gnm_cell_cleanout (cell);
4467 #ifdef USE_CELL_POOL
4468 go_mem_chunk_free (cell_pool, cell);
4469 #else
4470 cell_allocations--, g_slice_free1 (sizeof (*cell), cell);
4471 #endif
4475 * gnm_sheet_cell_init: (skip)
4477 void
4478 gnm_sheet_cell_init (void)
4480 #ifdef USE_CELL_POOL
4481 cell_pool = go_mem_chunk_new ("cell pool",
4482 sizeof (GnmCell),
4483 128 * 1024 - 128);
4484 #endif
4487 #ifdef USE_CELL_POOL
4488 static void
4489 cb_cell_pool_leak (gpointer data, G_GNUC_UNUSED gpointer user)
4491 GnmCell const *cell = data;
4492 g_printerr ("Leaking cell %p at %s\n", (void *)cell, cell_name (cell));
4494 #endif
4497 * gnm_sheet_cell_shutdown: (skip)
4499 void
4500 gnm_sheet_cell_shutdown (void)
4502 #ifdef USE_CELL_POOL
4503 go_mem_chunk_foreach_leak (cell_pool, cb_cell_pool_leak, NULL);
4504 go_mem_chunk_destroy (cell_pool, FALSE);
4505 cell_pool = NULL;
4506 #else
4507 if (cell_allocations)
4508 g_printerr ("Leaking %d cells.\n", cell_allocations);
4509 #endif
4512 /****************************************************************************/
4515 * sheet_cell_create:
4516 * @sheet: #Sheet
4517 * @col:
4518 * @row:
4520 * Creates a new cell and adds it to the sheet hash.
4522 GnmCell *
4523 sheet_cell_create (Sheet *sheet, int col, int row)
4525 GnmCell *cell;
4527 g_return_val_if_fail (IS_SHEET (sheet), NULL);
4528 g_return_val_if_fail (col >= 0, NULL);
4529 g_return_val_if_fail (col < gnm_sheet_get_max_cols (sheet), NULL);
4530 g_return_val_if_fail (row >= 0, NULL);
4531 g_return_val_if_fail (row < gnm_sheet_get_max_rows (sheet), NULL);
4533 cell = cell_new ();
4534 cell->base.sheet = sheet;
4535 cell->pos.col = col;
4536 cell->pos.row = row;
4537 cell->value = value_new_empty ();
4539 sheet_cell_add_to_hash (sheet, cell);
4540 return cell;
4544 * sheet_cell_remove_from_hash:
4545 * @sheet:
4546 * @cell:
4548 * Removes a cell from the sheet hash, clears any spans, and unlinks it from
4549 * the dependent collection.
4551 static void
4552 sheet_cell_remove_from_hash (Sheet *sheet, GnmCell *cell)
4554 cell_unregister_span (cell);
4555 if (gnm_cell_expr_is_linked (cell))
4556 dependent_unlink (GNM_CELL_TO_DEP (cell));
4557 g_hash_table_remove (sheet->cell_hash, cell);
4558 cell->base.flags &= ~(GNM_CELL_IN_SHEET_LIST|GNM_CELL_IS_MERGED);
4562 * sheet_cell_destroy:
4563 * @sheet:
4564 * @cell:
4565 * @queue_recalc:
4567 * Remove the cell from the web of dependencies of a
4568 * sheet. Do NOT redraw.
4570 static void
4571 sheet_cell_destroy (Sheet *sheet, GnmCell *cell, gboolean queue_recalc)
4573 if (gnm_cell_expr_is_linked (cell)) {
4574 /* if it needs recalc then its depends are already queued
4575 * check recalc status before we unlink
4577 queue_recalc &= !gnm_cell_needs_recalc (cell);
4578 dependent_unlink (GNM_CELL_TO_DEP (cell));
4581 if (queue_recalc)
4582 cell_foreach_dep (cell, (GnmDepFunc)dependent_queue_recalc, NULL);
4584 sheet_cell_remove_from_hash (sheet, cell);
4585 cell_free (cell);
4589 * sheet_cell_remove:
4590 * @sheet:
4591 * @cell:
4592 * @redraw:
4593 * @queue_recalc:
4595 * Remove the cell from the web of dependencies of a
4596 * sheet. Do NOT free the cell, optionally redraw it, optionally
4597 * queue it for recalc.
4599 void
4600 sheet_cell_remove (Sheet *sheet, GnmCell *cell,
4601 gboolean redraw, gboolean queue_recalc)
4603 g_return_if_fail (cell != NULL);
4604 g_return_if_fail (IS_SHEET (sheet));
4606 /* Queue a redraw on the region used by the cell being removed */
4607 if (redraw) {
4608 sheet_redraw_region (sheet,
4609 cell->pos.col, cell->pos.row,
4610 cell->pos.col, cell->pos.row);
4611 sheet_flag_status_update_cell (cell);
4614 sheet_cell_destroy (sheet, cell, queue_recalc);
4617 static GnmValue *
4618 cb_free_cell (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
4620 sheet_cell_destroy (iter->pp.sheet, iter->cell, FALSE);
4621 return NULL;
4625 * sheet_col_destroy:
4626 * @sheet:
4627 * @col:
4628 * @free_cells:
4630 * Destroys a ColRowInfo from the Sheet with all of its cells
4632 static void
4633 sheet_col_destroy (Sheet *sheet, int const col, gboolean free_cells)
4635 ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->cols), col);
4636 int const sub = COLROW_SUB_INDEX (col);
4637 ColRowInfo *ci = NULL;
4639 if (*segment == NULL)
4640 return;
4641 ci = (*segment)->info[sub];
4642 if (ci == NULL)
4643 return;
4645 if (sheet->cols.max_outline_level > 0 &&
4646 sheet->cols.max_outline_level == ci->outline_level)
4647 sheet->priv->recompute_max_col_group = TRUE;
4649 if (free_cells)
4650 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4651 col, 0, col, -1,
4652 &cb_free_cell, NULL);
4654 (*segment)->info[sub] = NULL;
4655 colrow_free (ci);
4657 /* Use >= just in case things are screwed up */
4658 if (col >= sheet->cols.max_used) {
4659 int i = col;
4660 while (--i >= 0 && sheet_col_get (sheet, i) == NULL)
4662 sheet->cols.max_used = i;
4667 * Destroys a row ColRowInfo
4669 static void
4670 sheet_row_destroy (Sheet *sheet, int const row, gboolean free_cells)
4672 ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->rows), row);
4673 int const sub = COLROW_SUB_INDEX (row);
4674 ColRowInfo *ri = NULL;
4676 if (*segment == NULL)
4677 return;
4678 ri = (*segment)->info[sub];
4679 if (ri == NULL)
4680 return;
4682 if (sheet->rows.max_outline_level > 0 &&
4683 sheet->rows.max_outline_level == ri->outline_level)
4684 sheet->priv->recompute_max_row_group = TRUE;
4686 if (free_cells)
4687 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4688 0, row, -1, row,
4689 &cb_free_cell, NULL);
4691 /* Rows have span lists, destroy them too */
4692 row_destroy_span (ri);
4694 (*segment)->info[sub] = NULL;
4695 colrow_free (ri);
4697 /* Use >= just in case things are screwed up */
4698 if (row >= sheet->rows.max_used) {
4699 int i = row;
4700 while (--i >= 0 && sheet_row_get (sheet, i) == NULL)
4702 sheet->rows.max_used = i;
4706 static void
4707 cb_remove_allcells (G_GNUC_UNUSED gpointer ignore0, GnmCell *cell, G_GNUC_UNUSED gpointer ignore1)
4709 cell->base.flags &= ~GNM_CELL_IN_SHEET_LIST;
4710 cell_free (cell);
4713 void
4714 sheet_destroy_contents (Sheet *sheet)
4716 GSList *filters;
4717 int i;
4719 /* By the time we reach here dependencies should have been shut down */
4720 g_return_if_fail (sheet->deps == NULL);
4722 /* A simple test to see if this has already been run. */
4723 if (sheet->hash_merged == NULL)
4724 return;
4727 GSList *tmp = sheet->slicers;
4728 sheet->slicers = NULL;
4729 g_slist_free_full (tmp, (GDestroyNotify)gnm_sheet_slicer_clear_sheet);
4732 /* These contain SheetObjects, remove them first */
4733 filters = g_slist_copy (sheet->filters);
4734 g_slist_foreach (filters, (GFunc)gnm_filter_remove, NULL);
4735 g_slist_foreach (filters, (GFunc)gnm_filter_unref, NULL);
4736 g_slist_free (filters);
4738 if (sheet->sheet_objects) {
4739 /* The list is changed as we remove */
4740 GSList *objs = g_slist_copy (sheet->sheet_objects);
4741 GSList *ptr;
4742 for (ptr = objs; ptr != NULL ; ptr = ptr->next) {
4743 SheetObject *so = GNM_SO (ptr->data);
4744 if (so != NULL)
4745 sheet_object_clear_sheet (so);
4747 g_slist_free (objs);
4748 if (sheet->sheet_objects != NULL)
4749 g_warning ("There is a problem with sheet objects");
4752 /* The memory is managed by Sheet::list_merged */
4753 g_hash_table_destroy (sheet->hash_merged);
4754 sheet->hash_merged = NULL;
4756 g_slist_free_full (sheet->list_merged, g_free);
4757 sheet->list_merged = NULL;
4759 /* Clear the row spans 1st */
4760 for (i = sheet->rows.max_used; i >= 0 ; --i)
4761 row_destroy_span (sheet_row_get (sheet, i));
4763 /* Remove all the cells */
4764 sheet_cell_foreach (sheet, (GHFunc) &cb_remove_allcells, NULL);
4765 g_hash_table_destroy (sheet->cell_hash);
4767 /* Delete in ascending order to avoid decrementing max_used each time */
4768 for (i = 0; i <= sheet->cols.max_used; ++i)
4769 sheet_col_destroy (sheet, i, FALSE);
4771 for (i = 0; i <= sheet->rows.max_used; ++i)
4772 sheet_row_destroy (sheet, i, FALSE);
4774 /* Free segments too */
4775 col_row_collection_resize (&sheet->cols, 0);
4776 g_ptr_array_free (sheet->cols.info, TRUE);
4777 sheet->cols.info = NULL;
4779 col_row_collection_resize (&sheet->rows, 0);
4780 g_ptr_array_free (sheet->rows.info, TRUE);
4781 sheet->rows.info = NULL;
4783 g_clear_object (&sheet->solver_parameters);
4787 * sheet_destroy:
4788 * @sheet: the sheet to destroy
4790 * Please note that you need to detach this sheet before
4791 * calling this routine or you will get a warning.
4793 static void
4794 sheet_destroy (Sheet *sheet)
4796 g_return_if_fail (IS_SHEET (sheet));
4798 if (sheet->sheet_views->len > 0)
4799 g_warning ("Unexpected left over views");
4801 if (sheet->print_info) {
4802 gnm_print_info_free (sheet->print_info);
4803 sheet->print_info = NULL;
4806 style_color_unref (sheet->tab_color);
4807 sheet->tab_color = NULL;
4808 style_color_unref (sheet->tab_text_color);
4809 sheet->tab_text_color = NULL;
4811 gnm_app_clipboard_invalidate_sheet (sheet);
4814 static void
4815 gnm_sheet_finalize (GObject *obj)
4817 Sheet *sheet = SHEET (obj);
4818 gboolean debug_FMR = gnm_debug_flag ("sheet-fmr");
4820 sheet_destroy (sheet);
4822 g_clear_object (&sheet->solver_parameters);
4824 gnm_conventions_unref (sheet->convs);
4825 sheet->convs = NULL;
4827 g_list_free_full (sheet->scenarios, g_object_unref);
4828 sheet->scenarios = NULL;
4830 if (sheet->sort_setups != NULL)
4831 g_hash_table_unref (sheet->sort_setups);
4833 dependents_invalidate_sheet (sheet, TRUE);
4835 sheet_destroy_contents (sheet);
4837 if (sheet->slicers != NULL) {
4838 g_warning ("DataSlicer list should be NULL");
4840 if (sheet->filters != NULL) {
4841 g_warning ("Filter list should be NULL");
4843 if (sheet->sheet_objects != NULL) {
4844 g_warning ("Sheet object list should be NULL");
4846 if (sheet->list_merged != NULL) {
4847 g_warning ("Merged list should be NULL");
4849 if (sheet->hash_merged != NULL) {
4850 g_warning ("Merged hash should be NULL");
4853 sheet_style_shutdown (sheet);
4855 (void) g_idle_remove_by_data (sheet);
4857 if (debug_FMR) {
4858 g_printerr ("Sheet %p is %s\n", sheet, sheet->name_quoted);
4860 g_free (sheet->name_quoted);
4861 g_free (sheet->name_unquoted);
4862 g_free (sheet->name_unquoted_collate_key);
4863 g_free (sheet->name_case_insensitive);
4864 /* Poison */
4865 sheet->name_quoted = (char *)0xdeadbeef;
4866 sheet->name_unquoted = (char *)0xdeadbeef;
4867 g_free (sheet->priv);
4868 g_ptr_array_free (sheet->sheet_views, TRUE);
4870 gnm_rvc_free (sheet->rendered_values);
4872 if (debug_FMR) {
4873 /* Keep object around. */
4874 return;
4877 G_OBJECT_CLASS (parent_class)->finalize (obj);
4880 /*****************************************************************************/
4883 * cb_empty_cell: A callback for sheet_foreach_cell_in_region
4884 * removes/clear all of the cells in the specified region.
4885 * Does NOT queue a redraw.
4887 * WARNING : This does NOT regenerate spans that were interrupted by
4888 * this cell and can now continue.
4890 static GnmValue *
4891 cb_empty_cell (GnmCellIter const *iter, gpointer user)
4893 int clear_flags = GPOINTER_TO_INT (user);
4894 #if 0
4895 /* TODO : here and other places flag a need to update the
4896 * row/col maxima.
4898 if (row >= sheet->rows.max_used || col >= sheet->cols.max_used) { }
4899 #endif
4901 sheet_cell_remove (iter->pp.sheet, iter->cell, FALSE,
4902 (clear_flags & CLEAR_RECALC_DEPS) &&
4903 iter->pp.wb->recursive_dirty_enabled);
4905 return NULL;
4909 * sheet_clear_region:
4910 * @sheet:
4911 * @start_col:
4912 * @start_row:
4913 * @end_col:
4914 * @end_row:
4915 * @clear_flags: If this is %TRUE then styles are erased.
4916 * @cc: (nullable):
4918 * Clears are region of cells
4920 * We assemble a list of cells to destroy, since we will be making changes
4921 * to the structure being manipulated by the sheet_foreach_cell_in_region routine
4923 void
4924 sheet_clear_region (Sheet *sheet,
4925 int start_col, int start_row,
4926 int end_col, int end_row,
4927 int clear_flags,
4928 GOCmdContext *cc)
4930 GnmRange r;
4932 g_return_if_fail (IS_SHEET (sheet));
4933 g_return_if_fail (start_col <= end_col);
4934 g_return_if_fail (start_row <= end_row);
4936 r.start.col = start_col;
4937 r.start.row = start_row;
4938 r.end.col = end_col;
4939 r.end.row = end_row;
4941 if (clear_flags & CLEAR_VALUES && !(clear_flags & CLEAR_NOCHECKARRAY) &&
4942 sheet_range_splits_array (sheet, &r, NULL, cc, _("Clear")))
4943 return;
4945 /* Queue a redraw for cells being modified */
4946 if (clear_flags & (CLEAR_VALUES|CLEAR_FORMATS))
4947 sheet_redraw_region (sheet,
4948 start_col, start_row,
4949 end_col, end_row);
4951 /* Clear the style in the region (new_default will ref the style for us). */
4952 if (clear_flags & CLEAR_FORMATS) {
4953 sheet_style_set_range (sheet, &r, sheet_style_default (sheet));
4954 sheet_range_calc_spans (sheet, &r, GNM_SPANCALC_RE_RENDER|GNM_SPANCALC_RESIZE);
4955 rows_height_update (sheet, &r, TRUE);
4958 if (clear_flags & CLEAR_OBJECTS)
4959 sheet_objects_clear (sheet, &r, G_TYPE_NONE, NULL);
4960 else if (clear_flags & CLEAR_COMMENTS)
4961 sheet_objects_clear (sheet, &r, GNM_CELL_COMMENT_TYPE, NULL);
4963 /* TODO : how to handle objects ? */
4964 if (clear_flags & CLEAR_VALUES) {
4965 /* Remove or empty the cells depending on
4966 * whether or not there are comments
4968 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4969 start_col, start_row, end_col, end_row,
4970 &cb_empty_cell, GINT_TO_POINTER (clear_flags));
4972 if (!(clear_flags & CLEAR_NORESPAN)) {
4973 sheet_queue_respan (sheet, start_row, end_row);
4974 sheet_flag_status_update_range (sheet, &r);
4978 if (clear_flags & CLEAR_MERGES) {
4979 GSList *merged, *ptr;
4980 merged = gnm_sheet_merge_get_overlap (sheet, &r);
4981 for (ptr = merged ; ptr != NULL ; ptr = ptr->next)
4982 gnm_sheet_merge_remove (sheet, ptr->data);
4983 g_slist_free (merged);
4986 if (clear_flags & CLEAR_RECALC_DEPS)
4987 sheet_region_queue_recalc (sheet, &r);
4989 /* Always redraw */
4990 sheet_redraw_all (sheet, FALSE);
4993 static void
4994 sheet_clear_region_cb (GnmSheetRange *sr, int *flags)
4996 sheet_clear_region (sr->sheet,
4997 sr->range.start.col, sr->range.start.row,
4998 sr->range.end.col, sr->range.end.row,
4999 *flags | CLEAR_NOCHECKARRAY, NULL);
5004 * sheet_clear_region_undo:
5005 * @sr: #GnmSheetRange
5006 * @clear_flags: flags.
5008 * Returns: (transfer full): the new #GOUndo.
5010 GOUndo *sheet_clear_region_undo (GnmSheetRange *sr, int clear_flags)
5012 int *flags = g_new(int, 1);
5013 *flags = clear_flags;
5014 return go_undo_binary_new
5015 (sr, (gpointer)flags,
5016 (GOUndoBinaryFunc) sheet_clear_region_cb,
5017 (GFreeFunc) gnm_sheet_range_free,
5018 (GFreeFunc) g_free);
5022 /*****************************************************************************/
5024 void
5025 sheet_mark_dirty (Sheet *sheet)
5027 g_return_if_fail (IS_SHEET (sheet));
5029 if (sheet->workbook)
5030 go_doc_set_dirty (GO_DOC (sheet->workbook), TRUE);
5033 /****************************************************************************/
5035 static void
5036 sheet_cells_deps_move (GnmExprRelocateInfo *rinfo)
5038 Sheet *sheet = rinfo->origin_sheet;
5039 GPtrArray *deps = sheet_cells (sheet, &rinfo->origin);
5040 unsigned ui;
5042 /* Phase 1: collect all cells and remove them from hash. */
5043 for (ui = 0; ui < deps->len; ui++) {
5044 GnmCell *cell = g_ptr_array_index (deps, ui);
5045 gboolean needs_recalc = gnm_cell_needs_recalc (cell);
5046 sheet_cell_remove_from_hash (sheet, cell);
5047 if (needs_recalc) /* Do we need this now? */
5048 cell->base.flags |= DEPENDENT_NEEDS_RECALC;
5051 /* Phase 2: add all non-cell deps with positions */
5052 SHEET_FOREACH_DEPENDENT
5053 (sheet, dep, {
5054 GnmCellPos const *pos;
5055 if (!dependent_is_cell (dep) &&
5056 dependent_has_pos (dep) &&
5057 (pos = dependent_pos (dep)) &&
5058 range_contains (&rinfo->origin, pos->col, pos->row)) {
5059 dependent_unlink (dep);
5060 g_ptr_array_add (deps, dep);
5064 /* Phase 3: move everything and add cells to hash. */
5065 for (ui = 0; ui < deps->len; ui++) {
5066 GnmDependent *dep = g_ptr_array_index (deps, ui);
5068 dependent_move (dep, rinfo->col_offset, rinfo->row_offset);
5070 if (dependent_is_cell (dep))
5071 sheet_cell_add_to_hash (sheet, GNM_DEP_TO_CELL (dep));
5073 if (dep->texpr)
5074 dependent_link (dep);
5077 g_ptr_array_free (deps, TRUE);
5080 /* Moves the headers to their new location */
5081 static void
5082 sheet_colrow_move (Sheet *sheet, gboolean is_cols,
5083 int const old_pos, int const new_pos)
5085 ColRowSegment *segment = COLROW_GET_SEGMENT (is_cols ? &sheet->cols : &sheet->rows, old_pos);
5086 ColRowInfo *info = segment
5087 ? segment->info[COLROW_SUB_INDEX (old_pos)]
5088 : NULL;
5090 g_return_if_fail (old_pos >= 0);
5091 g_return_if_fail (new_pos >= 0);
5093 if (info == NULL)
5094 return;
5096 /* Update the position */
5097 segment->info[COLROW_SUB_INDEX (old_pos)] = NULL;
5098 sheet_colrow_add (sheet, info, is_cols, new_pos);
5101 static void
5102 sheet_colrow_set_collapse (Sheet *sheet, gboolean is_cols, int pos)
5104 ColRowInfo *cri;
5105 ColRowInfo const *vs = NULL;
5107 if (pos < 0 || pos >= colrow_max (is_cols, sheet))
5108 return;
5110 /* grab the next or previous col/row */
5111 if ((is_cols ? sheet->outline_symbols_right : sheet->outline_symbols_below)) {
5112 if (pos > 0)
5113 vs = sheet_colrow_get (sheet, pos-1, is_cols);
5114 } else if ((pos+1) < colrow_max (is_cols, sheet))
5115 vs = sheet_colrow_get (sheet, pos+1, is_cols);
5117 /* handle the case where an empty col/row should be marked collapsed */
5118 cri = sheet_colrow_get (sheet, pos, is_cols);
5119 if (cri != NULL)
5120 cri->is_collapsed = (vs != NULL && !vs->visible &&
5121 vs->outline_level > cri->outline_level);
5122 else if (vs != NULL && !vs->visible && vs->outline_level > 0) {
5123 cri = sheet_colrow_fetch (sheet, pos, is_cols);
5124 cri->is_collapsed = TRUE;
5128 static void
5129 combine_undo (GOUndo **pundo, GOUndo *u)
5131 if (pundo)
5132 *pundo = go_undo_combine (*pundo, u);
5133 else
5134 g_object_unref (u);
5137 typedef gboolean (*ColRowInsDelFunc) (Sheet *sheet, int idx, int count,
5138 GOUndo **pundo, GOCmdContext *cc);
5140 typedef struct {
5141 ColRowInsDelFunc func;
5142 Sheet *sheet;
5143 gboolean is_cols;
5144 int pos;
5145 int count;
5146 ColRowStateList *states;
5147 int state_start;
5148 } ColRowInsDelData;
5150 static void
5151 cb_undo_insdel (ColRowInsDelData *data)
5153 data->func (data->sheet, data->pos, data->count, NULL, NULL);
5154 colrow_set_states (data->sheet, data->is_cols,
5155 data->state_start, data->states);
5158 static void
5159 cb_undo_insdel_free (ColRowInsDelData *data)
5161 colrow_state_list_destroy (data->states);
5162 g_free (data);
5165 static gboolean
5166 sheet_insdel_colrow (Sheet *sheet, int pos, int count,
5167 GOUndo **pundo, GOCmdContext *cc,
5168 gboolean is_cols, gboolean is_insert,
5169 const char *description,
5170 ColRowInsDelFunc opposite)
5173 GnmRange kill_zone; /* The range whose contents will be lost. */
5174 GnmRange move_zone; /* The range whose contents will be moved. */
5175 GnmRange change_zone; /* The union of kill_zone and move_zone. */
5176 int i, last_pos, max_used_pos;
5177 int kill_start, kill_end, move_start, move_end;
5178 int scount = is_insert ? count : -count;
5179 ColRowStateList *states = NULL;
5180 GnmExprRelocateInfo reloc_info;
5181 GSList *l;
5182 gboolean sticky_end = TRUE;
5184 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
5185 g_return_val_if_fail (count > 0, TRUE);
5188 * The main undo for an insert col/row is delete col/row and vice versa.
5189 * In addition to that, we collect undo information that the main undo
5190 * operation will not restore -- for example the contents of the kill
5191 * zone.
5193 if (pundo) *pundo = NULL;
5195 last_pos = colrow_max (is_cols, sheet) - 1;
5196 max_used_pos = is_cols ? sheet->cols.max_used : sheet->rows.max_used;
5197 if (is_insert) {
5198 kill_start = last_pos - (count - 1);
5199 kill_end = last_pos;
5200 move_start = pos;
5201 move_end = kill_start - 1;
5202 } else {
5203 int max_count = last_pos + 1 - pos;
5204 if (count > max_count) {
5205 sticky_end = FALSE;
5206 count = max_count;
5208 kill_start = pos;
5209 kill_end = pos + (count - 1);
5210 move_start = kill_end + 1;
5211 move_end = last_pos;
5213 (is_cols ? range_init_cols : range_init_rows)
5214 (&kill_zone, sheet, kill_start, kill_end);
5215 (is_cols ? range_init_cols : range_init_rows)
5216 (&move_zone, sheet, move_start, move_end);
5217 change_zone = range_union (&kill_zone, &move_zone);
5219 /* 0. Check displaced/deleted region and ensure arrays aren't divided. */
5220 if (sheet_range_splits_array (sheet, &kill_zone, NULL, cc, description))
5221 return TRUE;
5222 if (move_start <= move_end &&
5223 sheet_range_splits_array (sheet, &move_zone, NULL, cc, description))
5224 return TRUE;
5227 * At this point we're committed. Anything that can go wrong should
5228 * have been ruled out already.
5231 if (0) {
5232 g_printerr ("Action = %s at %d count %d\n", description, pos, count);
5233 g_printerr ("Kill zone: %s\n", range_as_string (&kill_zone));
5236 /* 1. Delete all columns/rows in the kill zone */
5237 if (pundo) {
5238 combine_undo (pundo, clipboard_copy_range_undo (sheet, &kill_zone));
5239 states = colrow_get_states (sheet, is_cols, kill_start, kill_end);
5241 for (i = MIN (max_used_pos, kill_end); i >= kill_start; --i)
5242 (is_cols ? sheet_col_destroy : sheet_row_destroy)
5243 (sheet, i, TRUE);
5244 /* Brutally discard auto filter objects. Collect the rest for undo. */
5245 sheet_objects_clear (sheet, &kill_zone, GNM_FILTER_COMBO_TYPE, NULL);
5246 sheet_objects_clear (sheet, &kill_zone, G_TYPE_NONE, pundo);
5248 reloc_info.reloc_type = is_cols ? GNM_EXPR_RELOCATE_COLS : GNM_EXPR_RELOCATE_ROWS;
5249 reloc_info.sticky_end = sticky_end;
5250 reloc_info.origin_sheet = reloc_info.target_sheet = sheet;
5251 parse_pos_init_sheet (&reloc_info.pos, sheet);
5253 /* 2. Get rid of style dependents, see #741197. */
5254 sheet_style_clear_style_dependents (sheet, &change_zone);
5256 /* 3. Invalidate references to kill zone. */
5257 if (is_insert) {
5258 /* Done in the next step. */
5259 } else {
5260 reloc_info.origin = kill_zone;
5261 /* Force invalidation: */
5262 reloc_info.col_offset = is_cols ? last_pos + 1 : 0;
5263 reloc_info.row_offset = is_cols ? 0 : last_pos + 1;
5264 combine_undo (pundo, dependents_relocate (&reloc_info));
5267 /* 4. Fix references to the cells which are moving */
5268 reloc_info.origin = is_insert ? change_zone : move_zone;
5269 reloc_info.col_offset = is_cols ? scount : 0;
5270 reloc_info.row_offset = is_cols ? 0 : scount;
5271 combine_undo (pundo, dependents_relocate (&reloc_info));
5273 /* 5. Move the cells */
5274 sheet_cells_deps_move (&reloc_info);
5276 /* 6. Move the columns/rows to their new location. */
5277 if (is_insert) {
5278 /* From right to left */
5279 for (i = max_used_pos; i >= pos ; --i)
5280 sheet_colrow_move (sheet, is_cols, i, i + count);
5281 } else {
5282 /* From left to right */
5283 for (i = pos + count ; i <= max_used_pos; ++i)
5284 sheet_colrow_move (sheet, is_cols, i, i - count);
5286 sheet_colrow_set_collapse (sheet, is_cols, pos);
5287 sheet_colrow_set_collapse (sheet, is_cols,
5288 is_insert ? pos + count : last_pos - (count - 1));
5290 /* 7. Move formatting. */
5291 sheet_style_insdel_colrow (&reloc_info);
5293 /* 8. Move objects. */
5294 sheet_objects_relocate (&reloc_info, FALSE, pundo);
5296 /* 9. Move merges. */
5297 gnm_sheet_merge_relocate (&reloc_info, pundo);
5299 /* 10. Move filters. */
5300 gnm_sheet_filter_insdel_colrow (sheet, is_cols, is_insert, pos, count, pundo);
5302 /* Notify sheet of pending updates */
5303 sheet_mark_dirty (sheet);
5304 sheet->priv->recompute_visibility = TRUE;
5305 sheet_flag_recompute_spans (sheet);
5306 sheet_flag_status_update_range (sheet, &change_zone);
5307 if (is_cols)
5308 sheet->priv->reposition_objects.col = pos;
5309 else
5310 sheet->priv->reposition_objects.row = pos;
5312 /* WARNING WARNING WARNING
5313 * This is bad practice and should not really be here.
5314 * However, we need to ensure that update is run before
5315 * gnm_sheet_view_panes_insdel_colrow plays with frozen panes, updating those can
5316 * trigger redraws before sheet_update has been called. */
5317 sheet_update (sheet);
5319 SHEET_FOREACH_VIEW (sheet, sv,
5320 gnm_sheet_view_panes_insdel_colrow (sv, is_cols, is_insert, pos, count););
5322 /* The main undo is the opposite operation. */
5323 if (pundo) {
5324 ColRowInsDelData *data;
5325 GOUndo *u;
5327 data = g_new (ColRowInsDelData, 1);
5328 data->func = opposite;
5329 data->sheet = sheet;
5330 data->is_cols = is_cols;
5331 data->pos = pos;
5332 data->count = count;
5333 data->states = states;
5334 data->state_start = kill_start;
5336 u = go_undo_unary_new (data, (GOUndoUnaryFunc)cb_undo_insdel,
5337 (GFreeFunc)cb_undo_insdel_free);
5339 combine_undo (pundo, u);
5342 /* Reapply all filters. */
5343 for (l = sheet->filters; l; l = l->next) {
5344 GnmFilter *filter = l->data;
5345 gnm_filter_reapply (filter);
5348 return FALSE;
5352 * sheet_insert_cols:
5353 * @sheet: #Sheet
5354 * @col: At which position we want to insert
5355 * @count: The number of columns to be inserted
5356 * @pundo: (out): (transfer full): (allow-none): undo closure
5357 * @cc:
5359 gboolean
5360 sheet_insert_cols (Sheet *sheet, int col, int count,
5361 GOUndo **pundo, GOCmdContext *cc)
5363 return sheet_insdel_colrow (sheet, col, count, pundo, cc,
5364 TRUE, TRUE,
5365 _("Insert Columns"),
5366 sheet_delete_cols);
5370 * sheet_delete_cols:
5371 * @sheet: The sheet
5372 * @col: At which position we want to start deleting columns
5373 * @count: The number of columns to be deleted
5374 * @pundo: (out): (transfer full): (allow-none): undo closure
5375 * @cc: The command context
5377 gboolean
5378 sheet_delete_cols (Sheet *sheet, int col, int count,
5379 GOUndo **pundo, GOCmdContext *cc)
5381 return sheet_insdel_colrow (sheet, col, count, pundo, cc,
5382 TRUE, FALSE,
5383 _("Delete Columns"),
5384 sheet_insert_cols);
5388 * sheet_insert_rows:
5389 * @sheet: The sheet
5390 * @row: At which position we want to insert
5391 * @count: The number of rows to be inserted
5392 * @pundo: (out): (transfer full): (allow-none): undo closure
5393 * @cc: The command context
5395 gboolean
5396 sheet_insert_rows (Sheet *sheet, int row, int count,
5397 GOUndo **pundo, GOCmdContext *cc)
5399 return sheet_insdel_colrow (sheet, row, count, pundo, cc,
5400 FALSE, TRUE,
5401 _("Insert Rows"),
5402 sheet_delete_rows);
5406 * sheet_delete_rows:
5407 * @sheet: The sheet
5408 * @row: At which position we want to start deleting rows
5409 * @count: The number of rows to be deleted
5410 * @pundo: (out): (transfer full): (allow-none): undo closure
5411 * @cc: The command context
5413 gboolean
5414 sheet_delete_rows (Sheet *sheet, int row, int count,
5415 GOUndo **pundo, GOCmdContext *cc)
5417 return sheet_insdel_colrow (sheet, row, count, pundo, cc,
5418 FALSE, FALSE,
5419 _("Delete Rows"),
5420 sheet_insert_rows);
5424 * Callback for sheet_foreach_cell_in_region to remove a cell from the sheet
5425 * hash, unlink from the dependent collection and put it in a temporary list.
5427 static GnmValue *
5428 cb_collect_cell (GnmCellIter const *iter, gpointer user)
5430 GList ** l = user;
5431 GnmCell *cell = iter->cell;
5432 gboolean needs_recalc = gnm_cell_needs_recalc (cell);
5434 sheet_cell_remove_from_hash (iter->pp.sheet, cell);
5435 *l = g_list_prepend (*l, cell);
5436 if (needs_recalc)
5437 cell->base.flags |= DEPENDENT_NEEDS_RECALC;
5438 return NULL;
5442 * sheet_move_range:
5443 * @cc:
5444 * @rinfo:
5445 * @pundo: (out) (optional) (transfer full): undo object
5447 * Move a range as specified in @rinfo report warnings to @cc.
5448 * if @pundo is non-%NULL, invalidate references to the
5449 * target region that are being cleared, and store the undo information
5450 * in @pundo. If it is %NULL do NOT INVALIDATE.
5452 void
5453 sheet_move_range (GnmExprRelocateInfo const *rinfo,
5454 GOUndo **pundo, GOCmdContext *cc)
5456 GList *cells = NULL;
5457 GnmCell *cell;
5458 GnmRange dst;
5459 gboolean out_of_range;
5461 g_return_if_fail (rinfo != NULL);
5462 g_return_if_fail (IS_SHEET (rinfo->origin_sheet));
5463 g_return_if_fail (IS_SHEET (rinfo->target_sheet));
5464 g_return_if_fail (rinfo->origin_sheet != rinfo->target_sheet ||
5465 rinfo->col_offset != 0 ||
5466 rinfo->row_offset != 0);
5468 dst = rinfo->origin;
5469 out_of_range = range_translate (&dst, rinfo->target_sheet,
5470 rinfo->col_offset, rinfo->row_offset);
5472 /* Redraw the src region in case anything was spanning */
5473 sheet_redraw_range (rinfo->origin_sheet, &rinfo->origin);
5475 /* 1. invalidate references to any cells in the destination range that
5476 * are not shared with the src. This must be done before the references
5477 * to from the src range are adjusted because they will point into
5478 * the destination.
5480 if (pundo != NULL) {
5481 *pundo = NULL;
5482 if (!out_of_range) {
5483 GSList *invalid;
5484 GnmExprRelocateInfo reloc_info;
5486 /* We need to be careful about invalidating references
5487 * to the old content of the destination region. We
5488 * only invalidate references to regions that are
5489 * actually lost. However, this care is only necessary
5490 * if the source and target sheets are the same.
5492 * Handle dst cells being pasted over
5494 if (rinfo->origin_sheet == rinfo->target_sheet &&
5495 range_overlap (&rinfo->origin, &dst))
5496 invalid = range_split_ranges (&rinfo->origin, &dst);
5497 else
5498 invalid = g_slist_append (NULL, gnm_range_dup (&dst));
5500 reloc_info.origin_sheet = reloc_info.target_sheet = rinfo->target_sheet;
5502 /* send to infinity to invalidate, but try to assist
5503 * the relocation heuristics only move in 1
5504 * dimension if possible to give us a chance to be
5505 * smart about partial invalidations */
5506 reloc_info.col_offset = gnm_sheet_get_max_cols (rinfo->target_sheet);
5507 reloc_info.row_offset = gnm_sheet_get_max_rows (rinfo->target_sheet);
5508 reloc_info.sticky_end = TRUE;
5509 if (rinfo->col_offset == 0) {
5510 reloc_info.col_offset = 0;
5511 reloc_info.reloc_type = GNM_EXPR_RELOCATE_ROWS;
5512 } else if (rinfo->row_offset == 0) {
5513 reloc_info.row_offset = 0;
5514 reloc_info.reloc_type = GNM_EXPR_RELOCATE_COLS;
5515 } else
5516 reloc_info.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE;
5518 parse_pos_init_sheet (&reloc_info.pos,
5519 rinfo->origin_sheet);
5521 while (invalid) {
5522 GnmRange *r = invalid->data;
5523 invalid = g_slist_remove (invalid, r);
5524 if (!range_overlap (r, &rinfo->origin)) {
5525 reloc_info.origin = *r;
5526 combine_undo (pundo,
5527 dependents_relocate (&reloc_info));
5529 g_free (r);
5533 * DO NOT handle src cells moving out the bounds.
5534 * that is handled elsewhere.
5538 /* 2. Fix references to and from the cells which are moving */
5539 combine_undo (pundo, dependents_relocate (rinfo));
5542 /* 3. Collect the cells */
5543 sheet_foreach_cell_in_range (rinfo->origin_sheet,
5544 CELL_ITER_IGNORE_NONEXISTENT,
5545 &rinfo->origin,
5546 &cb_collect_cell, &cells);
5548 /* Reverse list so that we start at the top left (simplifies arrays). */
5549 cells = g_list_reverse (cells);
5551 /* 4. Clear the target area and invalidate references to it */
5552 if (!out_of_range)
5553 /* we can clear content but not styles from the destination
5554 * region without worrying if it overlaps with the source,
5555 * because we have already extracted the content. However,
5556 * we do need to queue anything that depends on the region for
5557 * recalc. */
5558 sheet_clear_region (rinfo->target_sheet,
5559 dst.start.col, dst.start.row,
5560 dst.end.col, dst.end.row,
5561 CLEAR_VALUES|CLEAR_RECALC_DEPS, cc);
5563 /* 5. Slide styles BEFORE the cells so that spans get computed properly */
5564 sheet_style_relocate (rinfo);
5566 /* 6. Insert the cells back */
5567 for (; cells != NULL ; cells = g_list_remove (cells, cell)) {
5568 cell = cells->data;
5570 /* check for out of bounds and delete if necessary */
5571 if ((cell->pos.col + rinfo->col_offset) >= gnm_sheet_get_max_cols (rinfo->target_sheet) ||
5572 (cell->pos.row + rinfo->row_offset) >= gnm_sheet_get_max_rows (rinfo->target_sheet)) {
5573 cell_free (cell);
5574 continue;
5577 /* Update the location */
5578 cell->base.sheet = rinfo->target_sheet;
5579 cell->pos.col += rinfo->col_offset;
5580 cell->pos.row += rinfo->row_offset;
5581 sheet_cell_add_to_hash (rinfo->target_sheet, cell);
5582 if (gnm_cell_has_expr (cell))
5583 dependent_link (GNM_CELL_TO_DEP (cell));
5586 /* 7. Move objects in the range */
5587 sheet_objects_relocate (rinfo, TRUE, pundo);
5588 gnm_sheet_merge_relocate (rinfo, pundo);
5590 /* 8. Notify sheet of pending update */
5591 sheet_flag_recompute_spans (rinfo->origin_sheet);
5592 sheet_flag_status_update_range (rinfo->origin_sheet, &rinfo->origin);
5595 static void
5596 sheet_colrow_default_calc (Sheet *sheet, double units,
5597 gboolean is_cols, gboolean is_pts)
5599 ColRowInfo *cri = is_cols
5600 ? &sheet->cols.default_style
5601 : &sheet->rows.default_style;
5603 g_return_if_fail (units > 0.);
5605 cri->is_default = TRUE;
5606 cri->hard_size = FALSE;
5607 cri->visible = TRUE;
5608 cri->spans = NULL;
5610 if (is_pts) {
5611 cri->size_pts = units;
5612 colrow_compute_pixels_from_pts (cri, sheet, is_cols, -1);
5613 } else {
5614 cri->size_pixels = units;
5615 colrow_compute_pts_from_pixels (cri, sheet, is_cols, -1);
5619 /************************************************************************/
5620 /* Col width support routines.
5624 * sheet_col_get_distance_pts:
5626 * Return the number of points between from_col to to_col
5627 * measured from the upper left corner.
5629 double
5630 sheet_col_get_distance_pts (Sheet const *sheet, int from, int to)
5632 ColRowInfo const *ci;
5633 double dflt, pts = 0., sign = 1.;
5634 int i;
5636 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5638 if (from > to) {
5639 int const tmp = to;
5640 to = from;
5641 from = tmp;
5642 sign = -1.;
5645 g_return_val_if_fail (from >= 0, 1.);
5646 g_return_val_if_fail (to <= gnm_sheet_get_max_cols (sheet), 1.);
5648 /* Do not use sheet_colrow_foreach, it ignores empties */
5649 dflt = sheet->cols.default_style.size_pts;
5650 for (i = from ; i < to ; ++i) {
5651 if (NULL == (ci = sheet_col_get (sheet, i)))
5652 pts += dflt;
5653 else if (ci->visible)
5654 pts += ci->size_pts;
5657 if (sheet->display_formulas)
5658 pts *= 2.;
5660 return pts * sign;
5664 * sheet_col_get_distance_pixels:
5666 * Return the number of pixels between from_col to to_col
5667 * measured from the upper left corner.
5670 sheet_col_get_distance_pixels (Sheet const *sheet, int from, int to)
5672 ColRowInfo const *ci;
5673 int dflt, pixels = 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_cols (sheet), 1);
5688 /* Do not use sheet_colrow_foreach, it ignores empties */
5689 dflt = sheet_col_get_default_size_pixels (sheet);
5690 for (i = from ; i < to ; ++i) {
5691 if (NULL == (ci = sheet_col_get (sheet, i)))
5692 pixels += dflt;
5693 else if (ci->visible)
5694 pixels += ci->size_pixels;
5697 return pixels * sign;
5701 * sheet_col_set_size_pts:
5702 * @sheet: The sheet
5703 * @col: The col
5704 * @width_pts: The desired widtht in pts
5705 * @set_by_user: %TRUE if this was done by a user (ie, user manually
5706 * set the width)
5708 * Sets width of a col in pts, INCLUDING left and right margins, and the far
5709 * grid line. This is a low level internal routine. It does NOT redraw,
5710 * or reposition objects.
5712 void
5713 sheet_col_set_size_pts (Sheet *sheet, int col, double width_pts,
5714 gboolean set_by_user)
5716 ColRowInfo *ci;
5718 g_return_if_fail (IS_SHEET (sheet));
5719 g_return_if_fail (width_pts > 0.0);
5721 ci = sheet_col_fetch (sheet, col);
5722 ci->hard_size = set_by_user;
5723 if (ci->size_pts == width_pts)
5724 return;
5726 ci->size_pts = width_pts;
5727 colrow_compute_pixels_from_pts (ci, sheet, TRUE, -1);
5729 sheet->priv->recompute_visibility = TRUE;
5730 sheet_flag_recompute_spans (sheet);
5731 if (sheet->priv->reposition_objects.col > col)
5732 sheet->priv->reposition_objects.col = col;
5735 void
5736 sheet_col_set_size_pixels (Sheet *sheet, int col, int width_pixels,
5737 gboolean set_by_user)
5739 ColRowInfo *ci;
5741 g_return_if_fail (IS_SHEET (sheet));
5742 g_return_if_fail (width_pixels > 0.0);
5744 ci = sheet_col_fetch (sheet, col);
5745 ci->hard_size = set_by_user;
5746 if (ci->size_pixels == width_pixels)
5747 return;
5749 ci->size_pixels = width_pixels;
5750 colrow_compute_pts_from_pixels (ci, sheet, TRUE, -1);
5752 sheet->priv->recompute_visibility = TRUE;
5753 sheet_flag_recompute_spans (sheet);
5754 if (sheet->priv->reposition_objects.col > col)
5755 sheet->priv->reposition_objects.col = col;
5759 * sheet_col_get_default_size_pts:
5761 * Return the default number of pts in a column, including margins.
5762 * This function returns the raw sum, no rounding etc.
5764 double
5765 sheet_col_get_default_size_pts (Sheet const *sheet)
5767 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5768 return sheet->cols.default_style.size_pts;
5772 sheet_col_get_default_size_pixels (Sheet const *sheet)
5774 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5775 return sheet->cols.default_style.size_pixels;
5778 void
5779 sheet_col_set_default_size_pts (Sheet *sheet, double width_pts)
5781 g_return_if_fail (IS_SHEET (sheet));
5782 g_return_if_fail (width_pts > 0.);
5784 sheet_colrow_default_calc (sheet, width_pts, TRUE, TRUE);
5785 sheet->priv->recompute_visibility = TRUE;
5786 sheet_flag_recompute_spans (sheet);
5787 sheet->priv->reposition_objects.col = 0;
5789 void
5790 sheet_col_set_default_size_pixels (Sheet *sheet, int width_pixels)
5792 g_return_if_fail (IS_SHEET (sheet));
5794 sheet_colrow_default_calc (sheet, width_pixels, TRUE, FALSE);
5795 sheet->priv->recompute_visibility = TRUE;
5796 sheet_flag_recompute_spans (sheet);
5797 sheet->priv->reposition_objects.col = 0;
5800 /**************************************************************************/
5801 /* Row height support routines
5805 * sheet_row_get_distance_pts:
5807 * Return the number of points between from_row to to_row
5808 * measured from the upper left corner.
5810 double
5811 sheet_row_get_distance_pts (Sheet const *sheet, int from, int to)
5813 ColRowSegment const *segment;
5814 ColRowInfo const *ri;
5815 double const default_size = sheet->rows.default_style.size_pts;
5816 double pts = 0., sign = 1.;
5817 int i;
5819 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5821 if (from > to) {
5822 int const tmp = to;
5823 to = from;
5824 from = tmp;
5825 sign = -1.;
5828 g_return_val_if_fail (from >= 0, 1.);
5829 g_return_val_if_fail (to <= gnm_sheet_get_max_rows (sheet), 1.);
5831 /* Do not use sheet_colrow_foreach, it ignores empties.
5832 * Optimize this so that long jumps are not quite so horrific
5833 * for performance.
5835 for (i = from ; i < to ; ++i) {
5836 segment = COLROW_GET_SEGMENT (&(sheet->rows), i);
5838 if (segment != NULL) {
5839 ri = segment->info[COLROW_SUB_INDEX (i)];
5840 if (ri == NULL)
5841 pts += default_size;
5842 else if (ri->visible)
5843 pts += ri->size_pts;
5844 } else {
5845 int segment_end = COLROW_SEGMENT_END (i)+1;
5846 if (segment_end > to)
5847 segment_end = to;
5848 pts += default_size * (segment_end - i);
5849 i = segment_end-1;
5853 return pts*sign;
5857 * sheet_row_get_distance_pixels:
5859 * Return the number of pixels between from_row to to_row
5860 * measured from the upper left corner.
5863 sheet_row_get_distance_pixels (Sheet const *sheet, int from, int to)
5865 ColRowInfo const *ci;
5866 int dflt, pixels = 0, sign = 1;
5867 int i;
5869 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5871 if (from > to) {
5872 int const tmp = to;
5873 to = from;
5874 from = tmp;
5875 sign = -1;
5878 g_return_val_if_fail (from >= 0, 1);
5879 g_return_val_if_fail (to <= gnm_sheet_get_max_rows (sheet), 1);
5881 /* Do not use sheet_colrow_foreach, it ignores empties */
5882 dflt = sheet_row_get_default_size_pixels (sheet);
5883 for (i = from ; i < to ; ++i) {
5884 if (NULL == (ci = sheet_row_get (sheet, i)))
5885 pixels += dflt;
5886 else if (ci->visible)
5887 pixels += ci->size_pixels;
5890 return pixels * sign;
5894 * sheet_row_set_size_pts:
5895 * @sheet: The sheet
5896 * @row: The row
5897 * @height_pts: The desired height in pts
5898 * @set_by_user: %TRUE if this was done by a user (ie, user manually
5899 * set the height)
5901 * Sets height of a row in pts, INCLUDING top and bottom margins, and the lower
5902 * grid line. This is a low level internal routine. It does NOT redraw,
5903 * or reposition objects.
5905 void
5906 sheet_row_set_size_pts (Sheet *sheet, int row, double height_pts,
5907 gboolean set_by_user)
5909 ColRowInfo *ri;
5911 g_return_if_fail (IS_SHEET (sheet));
5912 g_return_if_fail (height_pts > 0.0);
5914 ri = sheet_row_fetch (sheet, row);
5915 ri->hard_size = set_by_user;
5916 if (ri->size_pts == height_pts)
5917 return;
5919 ri->size_pts = height_pts;
5920 colrow_compute_pixels_from_pts (ri, sheet, FALSE, -1);
5922 sheet->priv->recompute_visibility = TRUE;
5923 if (sheet->priv->reposition_objects.row > row)
5924 sheet->priv->reposition_objects.row = row;
5928 * sheet_row_set_size_pixels:
5929 * @sheet: The sheet
5930 * @row: The row
5931 * @height_pixels: The desired height
5932 * @set_by_user: %TRUE if this was done by a user (ie, user manually
5933 * set the width)
5935 * Sets height of a row in pixels, INCLUDING top and bottom margins, and the lower
5936 * grid line.
5938 void
5939 sheet_row_set_size_pixels (Sheet *sheet, int row, int height_pixels,
5940 gboolean set_by_user)
5942 ColRowInfo *ri;
5944 g_return_if_fail (IS_SHEET (sheet));
5945 g_return_if_fail (height_pixels > 0);
5947 ri = sheet_row_fetch (sheet, row);
5948 ri->hard_size = set_by_user;
5949 if (ri->size_pixels == height_pixels)
5950 return;
5952 ri->size_pixels = height_pixels;
5953 colrow_compute_pts_from_pixels (ri, sheet, FALSE, -1);
5955 sheet->priv->recompute_visibility = TRUE;
5956 if (sheet->priv->reposition_objects.row > row)
5957 sheet->priv->reposition_objects.row = row;
5961 * sheet_row_get_default_size_pts:
5963 * Return the defaul number of units in a row, including margins.
5964 * This function returns the raw sum, no rounding etc.
5966 double
5967 sheet_row_get_default_size_pts (Sheet const *sheet)
5969 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5970 return sheet->rows.default_style.size_pts;
5974 sheet_row_get_default_size_pixels (Sheet const *sheet)
5976 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5977 return sheet->rows.default_style.size_pixels;
5980 void
5981 sheet_row_set_default_size_pts (Sheet *sheet, double height_pts)
5983 g_return_if_fail (IS_SHEET (sheet));
5985 sheet_colrow_default_calc (sheet, height_pts, FALSE, TRUE);
5986 sheet->priv->recompute_visibility = TRUE;
5987 sheet->priv->reposition_objects.row = 0;
5990 void
5991 sheet_row_set_default_size_pixels (Sheet *sheet, int height_pixels)
5993 g_return_if_fail (IS_SHEET (sheet));
5995 sheet_colrow_default_calc (sheet, height_pixels, FALSE, FALSE);
5996 sheet->priv->recompute_visibility = TRUE;
5997 sheet->priv->reposition_objects.row = 0;
6000 /****************************************************************************/
6002 void
6003 sheet_scrollbar_config (Sheet const *sheet)
6005 g_return_if_fail (IS_SHEET (sheet));
6007 SHEET_FOREACH_CONTROL (sheet, view, control,
6008 sc_scrollbar_config (control););
6011 /*****************************************************************************/
6012 typedef struct
6014 gboolean is_column;
6015 Sheet *sheet;
6016 } closure_clone_colrow;
6018 static gboolean
6019 sheet_clone_colrow_info_item (GnmColRowIter const *iter, void *user_data)
6021 closure_clone_colrow const *closure = user_data;
6022 ColRowInfo *new_colrow = sheet_colrow_fetch (closure->sheet,
6023 iter->pos, closure->is_column);
6024 col_row_info_copy (new_colrow, iter->cri);
6025 return FALSE;
6028 static void
6029 sheet_dup_colrows (Sheet const *src, Sheet *dst)
6031 closure_clone_colrow closure;
6032 int max_col = MIN (gnm_sheet_get_max_cols (src), gnm_sheet_get_max_cols (dst)),
6033 max_row = MIN (gnm_sheet_get_max_rows (src), gnm_sheet_get_max_rows (dst));
6035 closure.sheet = dst;
6036 closure.is_column = TRUE;
6037 sheet_colrow_foreach (src, TRUE, 0, max_col - 1,
6038 &sheet_clone_colrow_info_item, &closure);
6039 closure.is_column = FALSE;
6040 sheet_colrow_foreach (src, FALSE, 0, max_row - 1,
6041 &sheet_clone_colrow_info_item, &closure);
6043 sheet_col_set_default_size_pixels (dst,
6044 sheet_col_get_default_size_pixels (src));
6045 sheet_row_set_default_size_pixels (dst,
6046 sheet_row_get_default_size_pixels (src));
6048 dst->cols.max_outline_level = src->cols.max_outline_level;
6049 dst->rows.max_outline_level = src->rows.max_outline_level;
6052 static void
6053 sheet_dup_styles (Sheet const *src, Sheet *dst)
6055 static GnmCellPos const corner = { 0, 0 };
6056 GnmRange r;
6057 GnmStyleList *styles;
6059 sheet_style_set_auto_pattern_color (
6060 dst, sheet_style_get_auto_pattern_color (src));
6062 styles = sheet_style_get_range (src, range_init_full_sheet (&r, src));
6063 sheet_style_set_list (dst, &corner, styles, NULL, NULL);
6064 style_list_free (styles);
6067 static void
6068 sheet_dup_merged_regions (Sheet const *src, Sheet *dst)
6070 GSList *ptr;
6072 for (ptr = src->list_merged ; ptr != NULL ; ptr = ptr->next)
6073 gnm_sheet_merge_add (dst, ptr->data, FALSE, NULL);
6076 static void
6077 sheet_dup_names (Sheet const *src, Sheet *dst)
6079 GSList *names = gnm_named_expr_collection_list (src->names);
6080 GSList *l;
6081 GnmParsePos dst_pp;
6083 if (names == NULL)
6084 return;
6086 parse_pos_init_sheet (&dst_pp, dst);
6088 /* Pass 1: add placeholders. */
6089 for (l = names; l; l = l->next) {
6090 GnmNamedExpr *src_nexpr = l->data;
6091 char const *name = expr_name_name (src_nexpr);
6092 GnmNamedExpr *dst_nexpr =
6093 gnm_named_expr_collection_lookup (dst->names, name);
6094 GnmExprTop const *texpr;
6096 if (dst_nexpr)
6097 continue;
6099 texpr = gnm_expr_top_new_constant (value_new_empty ());
6100 expr_name_add (&dst_pp, name, texpr , NULL, TRUE, NULL);
6103 /* Pass 2: assign the right expression. */
6104 for (l = names; l; l = l->next) {
6105 GnmNamedExpr *src_nexpr = l->data;
6106 char const *name = expr_name_name (src_nexpr);
6107 GnmNamedExpr *dst_nexpr =
6108 gnm_named_expr_collection_lookup (dst->names, name);
6109 GnmExprTop const *texpr;
6111 if (!dst_nexpr) {
6112 g_warning ("Trouble while duplicating name %s", name);
6113 continue;
6116 if (!dst_nexpr->is_editable)
6117 continue;
6119 texpr = gnm_expr_top_relocate_sheet (src_nexpr->texpr, src, dst);
6120 expr_name_set_expr (dst_nexpr, texpr);
6123 g_slist_free (names);
6126 static void
6127 cb_sheet_cell_copy (G_GNUC_UNUSED gpointer unused, gpointer key, gpointer new_sheet_param)
6129 GnmCell const *cell = key;
6130 Sheet *dst = new_sheet_param;
6131 Sheet *src;
6132 GnmExprTop const *texpr;
6134 g_return_if_fail (dst != NULL);
6135 g_return_if_fail (cell != NULL);
6137 src = cell->base.sheet;
6138 texpr = cell->base.texpr;
6140 if (texpr && gnm_expr_top_is_array_corner (texpr)) {
6141 int cols, rows;
6143 texpr = gnm_expr_top_relocate_sheet (texpr, src, dst);
6144 gnm_expr_top_get_array_size (texpr, &cols, &rows);
6146 gnm_cell_set_array_formula (dst,
6147 cell->pos.col, cell->pos.row,
6148 cell->pos.col + cols - 1,
6149 cell->pos.row + rows - 1,
6150 gnm_expr_top_new (gnm_expr_copy (gnm_expr_top_get_array_expr (texpr))));
6152 gnm_expr_top_unref (texpr);
6153 } else if (texpr && gnm_expr_top_is_array_elem (texpr, NULL, NULL)) {
6154 /* Not a corner -- ignore. */
6155 } else {
6156 GnmCell *new_cell = sheet_cell_create (dst, cell->pos.col, cell->pos.row);
6157 if (gnm_cell_has_expr (cell)) {
6158 texpr = gnm_expr_top_relocate_sheet (texpr, src, dst);
6159 gnm_cell_set_expr_and_value (new_cell, texpr, value_new_empty (), TRUE);
6160 gnm_expr_top_unref (texpr);
6161 } else
6162 gnm_cell_set_value (new_cell, value_dup (cell->value));
6166 static void
6167 sheet_dup_cells (Sheet const *src, Sheet *dst)
6169 sheet_cell_foreach (src, &cb_sheet_cell_copy, dst);
6170 sheet_region_queue_recalc (dst, NULL);
6173 static void
6174 sheet_dup_filters (Sheet const *src, Sheet *dst)
6176 GSList *ptr;
6177 for (ptr = src->filters ; ptr != NULL ; ptr = ptr->next)
6178 gnm_filter_dup (ptr->data, dst);
6179 dst->filters = g_slist_reverse (dst->filters);
6183 * sheet_dup:
6184 * @source_sheet: #Sheet
6186 * Create a new Sheet and return it.
6187 * Returns: (transfer full): the newly allocated #Sheet.
6189 Sheet *
6190 sheet_dup (Sheet const *src)
6192 Workbook *wb;
6193 Sheet *dst;
6194 char *name;
6195 GList *l;
6197 g_return_val_if_fail (IS_SHEET (src), NULL);
6198 g_return_val_if_fail (src->workbook != NULL, NULL);
6200 wb = src->workbook;
6201 name = workbook_sheet_get_free_name (wb, src->name_unquoted,
6202 TRUE, TRUE);
6203 dst = sheet_new_with_type (wb, name, src->sheet_type,
6204 src->size.max_cols, src->size.max_rows);
6205 g_free (name);
6207 dst->protected_allow = src->protected_allow;
6208 g_object_set (dst,
6209 "zoom-factor", src->last_zoom_factor_used,
6210 "text-is-rtl", src->text_is_rtl,
6211 "visibility", src->visibility,
6212 "protected", src->is_protected,
6213 "display-formulas", src->display_formulas,
6214 "display-zeros", !src->hide_zero,
6215 "display-grid", !src->hide_grid,
6216 "display-column-header", !src->hide_col_header,
6217 "display-row-header", !src->hide_row_header,
6218 "display-outlines", src->display_outlines,
6219 "display-outlines-below", src->outline_symbols_below,
6220 "display-outlines-right", src->outline_symbols_right,
6221 "conventions", src->convs,
6222 "tab-foreground", src->tab_text_color,
6223 "tab-background", src->tab_color,
6224 NULL);
6226 gnm_print_info_free (dst->print_info);
6227 dst->print_info = gnm_print_info_dup (src->print_info);
6229 sheet_dup_styles (src, dst);
6230 sheet_dup_merged_regions (src, dst);
6231 sheet_dup_colrows (src, dst);
6232 sheet_dup_names (src, dst);
6233 sheet_dup_cells (src, dst);
6234 sheet_objects_dup (src, dst, NULL);
6235 sheet_dup_filters (src, dst); /* must be after objects */
6237 #warning selection is in view
6238 #warning freeze/thaw is in view
6240 g_object_unref (dst->solver_parameters);
6241 dst->solver_parameters = gnm_solver_param_dup (src->solver_parameters, dst);
6243 for (l = src->scenarios; l; l = l->next) {
6244 GnmScenario *src_sc = l->data;
6245 GnmScenario *dst_sc = gnm_scenario_dup (src_sc, dst);
6246 dst->scenarios = g_list_prepend (dst->scenarios, dst_sc);
6248 dst->scenarios = g_list_reverse (dst->scenarios);
6250 sheet_mark_dirty (dst);
6251 sheet_redraw_all (dst, TRUE);
6253 return dst;
6257 * sheet_set_outline_direction:
6258 * @sheet: the sheet
6259 * @is_cols: %TRUE for columns, %FALSE for rows.
6261 * When changing the placement of outline collapse markers the flags
6262 * need to be recomputed.
6264 void
6265 sheet_set_outline_direction (Sheet *sheet, gboolean is_cols)
6267 unsigned i;
6268 g_return_if_fail (IS_SHEET (sheet));
6270 /* not particularly efficient, but this is not a hot spot */
6271 for (i = colrow_max (is_cols, sheet); i-- > 0 ; )
6272 sheet_colrow_set_collapse (sheet, is_cols, i);
6276 * sheet_get_view:
6277 * @sheet:
6278 * @wbv:
6280 * Find the SheetView corresponding to the supplied @wbv.
6281 * Returns: (transfer none): the view.
6283 SheetView *
6284 sheet_get_view (Sheet const *sheet, WorkbookView const *wbv)
6286 if (sheet == NULL)
6287 return NULL;
6289 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6291 SHEET_FOREACH_VIEW (sheet, view, {
6292 if (sv_wbv (view) == wbv)
6293 return view;
6295 return NULL;
6298 static gboolean
6299 cb_queue_respan (GnmColRowIter const *iter, void *user_data)
6301 ((ColRowInfo *)(iter->cri))->needs_respan = TRUE;
6302 return FALSE;
6306 * sheet_queue_respan:
6307 * @sheet:
6308 * @start_row:
6309 * @end_row:
6311 * queues a span generation for the selected rows.
6312 * the caller is responsible for queuing a redraw
6314 void
6315 sheet_queue_respan (Sheet const *sheet, int start_row, int end_row)
6317 sheet_colrow_foreach (sheet, FALSE, start_row, end_row,
6318 cb_queue_respan, NULL);
6321 void
6322 sheet_cell_queue_respan (GnmCell *cell)
6324 ColRowInfo *ri = sheet_row_get (cell->base.sheet, cell->pos.row);
6325 ri->needs_respan = TRUE;
6330 * sheet_get_comment:
6331 * @sheet: #Sheet const *
6332 * @pos: #GnmCellPos const *
6334 * If there is a cell comment at @pos in @sheet return it.
6336 * Caller does get a reference to the object if it exists.
6337 * Returns: (transfer full): the comment or %NULL.
6339 GnmComment *
6340 sheet_get_comment (Sheet const *sheet, GnmCellPos const *pos)
6342 GnmRange r;
6343 GSList *comments;
6344 GnmComment *res;
6346 GnmRange const *mr;
6348 mr = gnm_sheet_merge_contains_pos (sheet, pos);
6350 if (mr)
6351 comments = sheet_objects_get (sheet, mr, GNM_CELL_COMMENT_TYPE);
6352 else {
6353 r.start = r.end = *pos;
6354 comments = sheet_objects_get (sheet, &r, GNM_CELL_COMMENT_TYPE);
6356 if (!comments)
6357 return NULL;
6359 /* This assumes just one comment per cell. */
6360 res = comments->data;
6361 g_slist_free (comments);
6362 return res;
6365 static GnmValue *
6366 cb_find_extents (GnmCellIter const *iter, GnmCellPos *extent)
6368 if (extent->col < iter->pp.eval.col)
6369 extent->col = iter->pp.eval.col;
6370 if (extent->row < iter->pp.eval.row)
6371 extent->row = iter->pp.eval.row;
6372 return NULL;
6376 * sheet_range_trim:
6377 * @sheet: sheet cells are contained on
6378 * @r: range to trim empty cells from
6379 * @cols: trim from right
6380 * @rows: trim from bottom
6382 * This removes empty rows/cols from the
6383 * right hand or bottom edges of the range
6384 * depending on the value of @cols or @rows.
6386 * Returns: %TRUE if the range was totally empty.
6388 gboolean
6389 sheet_range_trim (Sheet const *sheet, GnmRange *r,
6390 gboolean cols, gboolean rows)
6392 GnmCellPos extent = { -1, -1 };
6394 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
6395 g_return_val_if_fail (r != NULL, TRUE);
6397 sheet_foreach_cell_in_range (
6398 (Sheet *)sheet, CELL_ITER_IGNORE_BLANK, r,
6399 (CellIterFunc) cb_find_extents, &extent);
6401 if (extent.col < 0 || extent.row < 0)
6402 return TRUE;
6403 if (cols)
6404 r->end.col = extent.col;
6405 if (rows)
6406 r->end.row = extent.row;
6407 return FALSE;
6411 * sheet_range_has_heading:
6412 * @sheet: Sheet to check
6413 * @src: GnmRange to check
6414 * @top: Flag
6416 * Checks for a header row in @sheet!@src. If top is true it looks for a
6417 * header row from the top and if false it looks for a header col from the
6418 * left
6420 * Returns: %TRUE if @src seems to have a heading
6422 gboolean
6423 sheet_range_has_heading (Sheet const *sheet, GnmRange const *src,
6424 gboolean top, gboolean ignore_styles)
6426 GnmCell const *a, *b;
6427 int length, i;
6429 /* There is only one row or col */
6430 if (top) {
6431 if (src->end.row <= src->start.row)
6432 return FALSE;
6433 length = src->end.col - src->start.col + 1;
6434 } else {
6435 if (src->end.col <= src->start.col)
6436 return FALSE;
6437 length = src->end.row - src->start.row + 1;
6440 for (i = 0; i < length; i++) {
6441 if (top) {
6442 a = sheet_cell_get (sheet,
6443 src->start.col + i, src->start.row);
6444 b = sheet_cell_get (sheet,
6445 src->start.col + i, src->start.row + 1);
6446 } else {
6447 a = sheet_cell_get (sheet,
6448 src->start.col, src->start.row + i);
6449 b = sheet_cell_get (sheet,
6450 src->start.col + 1, src->start.row + i);
6453 /* be anal */
6454 if (a == NULL || a->value == NULL || b == NULL || b->value == NULL)
6455 continue;
6457 if (VALUE_IS_NUMBER (a->value)) {
6458 if (!VALUE_IS_NUMBER (b->value))
6459 return TRUE;
6460 /* check for style differences */
6461 } else if (a->value->v_any.type != b->value->v_any.type)
6462 return TRUE;
6464 /* Look for style differences */
6465 if (!ignore_styles &&
6466 !gnm_style_equal_header (gnm_cell_get_style (a),
6467 gnm_cell_get_style (b), top))
6468 return TRUE;
6471 return FALSE;
6475 * gnm_sheet_foreach_name:
6476 * @sheet: #Sheet
6477 * @func: (scope call): #GHFunc
6478 * @data: user data.
6480 * Executes @func for each name in @sheet.
6482 void
6483 gnm_sheet_foreach_name (Sheet const *sheet, GHFunc func, gpointer data)
6485 g_return_if_fail (IS_SHEET (sheet));
6487 if (sheet->names)
6488 gnm_named_expr_collection_foreach (sheet->names, func, data);
6492 * gnm_sheet_get_size:
6493 * @sheet: #Sheet
6495 * Returns: (transfer none): the sheet size.
6497 GnmSheetSize const *
6498 gnm_sheet_get_size (Sheet const *sheet)
6500 static const GnmSheetSize default_size = {
6501 GNM_DEFAULT_COLS, GNM_DEFAULT_ROWS
6504 if (G_UNLIKELY (!sheet)) {
6505 g_warning ("NULL sheet in gnm_sheet_get_size!");
6506 /* FIXME: This needs to go. */
6507 return &default_size;
6510 if (G_UNLIKELY (sheet->being_constructed))
6511 g_warning ("Access to sheet size during construction!");
6513 return &sheet->size;
6517 * gnm_sheet_get_size2:
6518 * @sheet: #Sheet, might be %NULL
6519 * @wb: #Workbook, must be non %NULL if @sheet is %NULL
6521 * Returns: (transfer none): the sheet size if @sheet is non %NULL, or the
6522 * default sheet size for @wb.
6524 GnmSheetSize const *
6525 gnm_sheet_get_size2 (Sheet const *sheet, Workbook const *wb)
6527 return sheet
6528 ? gnm_sheet_get_size (sheet)
6529 : workbook_get_sheet_size (wb);
6532 void
6533 gnm_sheet_set_solver_params (Sheet *sheet, GnmSolverParameters *param)
6535 g_return_if_fail (IS_SHEET (sheet));
6536 g_return_if_fail (GNM_IS_SOLVER_PARAMETERS (param));
6538 g_object_ref (param);
6539 g_object_unref (sheet->solver_parameters);
6540 sheet->solver_parameters = param;
6543 /* ------------------------------------------------------------------------- */
6546 * gnm_sheet_scenario_new:
6547 * @sheet:  #Sheet
6548 * @name: the new scenario name.
6550 * Returns: (transfer full): the newly created #GnmScenario.
6552 GnmScenario *
6553 gnm_sheet_scenario_new (Sheet *sheet, const char *name)
6555 GnmScenario *sc;
6556 char *actual_name;
6558 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6559 g_return_val_if_fail (name != NULL, NULL);
6561 /* Check if a scenario having the same name already exists. */
6562 if (gnm_sheet_scenario_find (sheet, name)) {
6563 GString *str = g_string_new (NULL);
6564 gchar *tmp;
6565 int i, j, len;
6567 len = strlen (name);
6568 if (len > 1 && name [len - 1] == ']') {
6569 for (i = len - 2; i > 0; i--) {
6570 if (! g_ascii_isdigit (name [i]))
6571 break;
6574 tmp = g_strdup (name);
6575 if (i > 0 && name [i] == '[')
6576 tmp [i] = '\0';
6577 } else
6578 tmp = g_strdup (name);
6580 for (j = 1; ; j++) {
6581 g_string_printf (str, "%s [%d]", tmp, j);
6582 if (!gnm_sheet_scenario_find (sheet, str->str)) {
6583 actual_name = g_string_free (str, FALSE);
6584 str = NULL;
6585 break;
6588 if (str)
6589 g_string_free (str, TRUE);
6590 g_free (tmp);
6591 } else
6592 actual_name = g_strdup (name);
6594 sc = gnm_scenario_new (actual_name, sheet);
6596 g_free (actual_name);
6598 return sc;
6602 * gnm_sheet_scenario_find:
6603 * @sheet:  #Sheet
6604 * @name: the scenario name.
6606 * Returns: (transfer none): the newly created #GnmScenario.
6608 GnmScenario *
6609 gnm_sheet_scenario_find (Sheet *sheet, const char *name)
6611 GList *l;
6613 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6614 g_return_val_if_fail (name != NULL, NULL);
6616 for (l = sheet->scenarios; l; l = l->next) {
6617 GnmScenario *sc = l->data;
6618 if (strcmp (name, sc->name) == 0)
6619 return sc;
6622 return NULL;
6626 * gnm_sheet_scenario_add:
6627 * @sheet:  #Sheet
6628 * @sc: (transfer full): #GnmScenario
6631 void
6632 gnm_sheet_scenario_add (Sheet *sheet, GnmScenario *sc)
6634 g_return_if_fail (IS_SHEET (sheet));
6635 g_return_if_fail (GNM_IS_SCENARIO (sc));
6637 /* We take ownership of the ref. */
6638 sheet->scenarios = g_list_append (sheet->scenarios, sc);
6641 void
6642 gnm_sheet_scenario_remove (Sheet *sheet, GnmScenario *sc)
6644 g_return_if_fail (IS_SHEET (sheet));
6645 g_return_if_fail (GNM_IS_SCENARIO (sc));
6647 sheet->scenarios = g_list_remove (sheet->scenarios, sc);
6648 g_object_unref (sc);
6651 /* ------------------------------------------------------------------------- */
6654 * gnm_sheet_get_sort_setups:
6655 * @sheet: #Sheet
6657 * Returns: (transfer none): the sort setups for @sheet.
6659 GHashTable *
6660 gnm_sheet_get_sort_setups (Sheet *sheet)
6662 GHashTable *hash = sheet->sort_setups;
6664 if (hash == NULL)
6665 hash = sheet->sort_setups =
6666 g_hash_table_new_full
6667 (g_str_hash, g_str_equal,
6668 g_free, (GDestroyNotify)gnm_sort_data_destroy);
6670 return hash;
6673 void
6674 gnm_sheet_add_sort_setup (Sheet *sheet, char *key, gpointer setup)
6676 GHashTable *hash = gnm_sheet_get_sort_setups (sheet);
6678 g_hash_table_insert (hash, key, setup);
6682 * gnm_sheet_find_sort_setup:
6683 * @sheet: #Sheet
6684 * @key:
6686 * Returns: (transfer none): the found sort setup or %NULL.
6688 gconstpointer
6689 gnm_sheet_find_sort_setup (Sheet *sheet, char const *key)
6691 if (sheet->sort_setups == NULL)
6692 return NULL;
6693 return g_hash_table_lookup (sheet->sort_setups, key);
6697 * sheet_date_conv:
6698 * @sheet: #Sheet
6700 * Returns: (transfer none): the date conventions in effect for the sheet.
6701 * This is purely a convenience function to access the conventions used
6702 * for the workbook. All sheets in a workbook share the same date
6703 * conventions.
6705 GODateConventions const *
6706 sheet_date_conv (Sheet const *sheet)
6708 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6709 return workbook_date_conv (sheet->workbook);