Move plugin init code
[gnumeric.git] / src / sheet.c
blob2ed8c0bb20ebed822e72dcaa39a0dab0aa2b83ba
2 /*
3 * sheet.c: Implements the sheet management and per-sheet storage
5 * Copyright (C) 2000-2007 Jody Goldberg (jody@gnome.org)
6 * Copyright (C) 1997-1999 Miguel de Icaza (miguel@kernel.org)
7 * Copyright (C) 1999-2009 Morten Welinder (terra@gnome.org)
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of the
12 * License, or (at your option) version 3.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
22 * USA
24 #include <gnumeric-config.h>
25 #include <gnumeric.h>
26 #include <sheet.h>
28 #include <sheet-view.h>
29 #include <command-context.h>
30 #include <sheet-control.h>
31 #include <sheet-style.h>
32 #include <workbook-priv.h>
33 #include <workbook-control.h>
34 #include <workbook-view.h>
35 #include <parse-util.h>
36 #include <dependent.h>
37 #include <value.h>
38 #include <number-match.h>
39 #include <clipboard.h>
40 #include <selection.h>
41 #include <ranges.h>
42 #include <print-info.h>
43 #include <mstyle.h>
44 #include <style-color.h>
45 #include <style-font.h>
46 #include <application.h>
47 #include <commands.h>
48 #include <cellspan.h>
49 #include <cell.h>
50 #include <sheet-merge.h>
51 #include <sheet-private.h>
52 #include <expr-name.h>
53 #include <expr.h>
54 #include <rendered-value.h>
55 #include <gnumeric-conf.h>
56 #include <sheet-object-impl.h>
57 #include <sheet-object-cell-comment.h>
58 #include <tools/gnm-solver.h>
59 #include <hlink.h>
60 #include <sheet-filter.h>
61 #include <sheet-filter-combo.h>
62 #include <gnm-sheet-slicer.h>
63 #include <tools/scenarios.h>
64 #include <cell-draw.h>
65 #include <sort.h>
66 #include <gutils.h>
67 #include <goffice/goffice.h>
69 #include <gnm-i18n.h>
70 #include <gsf/gsf-impl-utils.h>
71 #include <stdlib.h>
72 #include <string.h>
74 static GnmSheetSize *
75 gnm_sheet_size_copy (GnmSheetSize *size)
77 GnmSheetSize *res = g_new (GnmSheetSize, 1);
78 *res = *size;
79 return res;
82 GType
83 gnm_sheet_size_get_type (void)
85 static GType t = 0;
87 if (t == 0) {
88 t = g_boxed_type_register_static ("GnmSheetSize",
89 (GBoxedCopyFunc)gnm_sheet_size_copy,
90 (GBoxedFreeFunc)g_free);
92 return t;
95 enum {
96 DETACHED_FROM_WORKBOOK,
97 LAST_SIGNAL
100 static guint signals [LAST_SIGNAL] = { 0 };
102 typedef struct {
103 GObjectClass parent;
105 void (*detached_from_workbook) (Sheet *, Workbook *wb);
106 } GnmSheetClass;
107 typedef Sheet GnmSheet;
109 enum {
110 PROP_0,
111 PROP_SHEET_TYPE,
112 PROP_WORKBOOK,
113 PROP_NAME,
114 PROP_RTL,
115 PROP_VISIBILITY,
116 PROP_DISPLAY_FORMULAS,
117 PROP_DISPLAY_ZEROS,
118 PROP_DISPLAY_GRID,
119 PROP_DISPLAY_COLUMN_HEADER,
120 PROP_DISPLAY_ROW_HEADER,
121 PROP_DISPLAY_OUTLINES,
122 PROP_DISPLAY_OUTLINES_BELOW,
123 PROP_DISPLAY_OUTLINES_RIGHT,
125 PROP_PROTECTED,
126 PROP_PROTECTED_ALLOW_EDIT_OBJECTS,
127 PROP_PROTECTED_ALLOW_EDIT_SCENARIOS,
128 PROP_PROTECTED_ALLOW_CELL_FORMATTING,
129 PROP_PROTECTED_ALLOW_COLUMN_FORMATTING,
130 PROP_PROTECTED_ALLOW_ROW_FORMATTING,
131 PROP_PROTECTED_ALLOW_INSERT_COLUMNS,
132 PROP_PROTECTED_ALLOW_INSERT_ROWS,
133 PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS,
134 PROP_PROTECTED_ALLOW_DELETE_COLUMNS,
135 PROP_PROTECTED_ALLOW_DELETE_ROWS,
136 PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS,
137 PROP_PROTECTED_ALLOW_SORT_RANGES,
138 PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS,
139 PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE,
140 PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS,
142 PROP_CONVENTIONS,
143 PROP_USE_R1C1,
145 PROP_TAB_FOREGROUND,
146 PROP_TAB_BACKGROUND,
147 PROP_ZOOM_FACTOR,
149 PROP_COLUMNS,
150 PROP_ROWS
153 static void gnm_sheet_finalize (GObject *obj);
155 static GObjectClass *parent_class;
157 static void
158 col_row_collection_resize (ColRowCollection *infos, int size)
160 int end_idx = COLROW_SEGMENT_INDEX (size);
161 int i = infos->info->len - 1;
163 while (i >= end_idx) {
164 ColRowSegment *segment = g_ptr_array_index (infos->info, i);
165 if (segment) {
166 g_free (segment);
167 g_ptr_array_index (infos->info, i) = NULL;
169 i--;
172 g_ptr_array_set_size (infos->info, end_idx);
175 static void
176 sheet_set_direction (Sheet *sheet, gboolean text_is_rtl)
178 GnmRange r;
180 text_is_rtl = !!text_is_rtl;
181 if (text_is_rtl == sheet->text_is_rtl)
182 return;
184 sheet_mark_dirty (sheet);
186 sheet->text_is_rtl = text_is_rtl;
187 sheet->priv->reposition_objects.col = 0;
188 sheet_range_calc_spans (sheet,
189 range_init_full_sheet (&r, sheet),
190 GNM_SPANCALC_RE_RENDER);
193 static void
194 sheet_set_visibility (Sheet *sheet, GnmSheetVisibility visibility)
196 if (sheet->visibility == visibility)
197 return;
199 sheet->visibility = visibility;
200 sheet_mark_dirty (sheet);
203 static void
204 cb_re_render_formulas (G_GNUC_UNUSED gpointer unused,
205 GnmCell *cell,
206 G_GNUC_UNUSED gpointer user)
208 if (gnm_cell_has_expr (cell)) {
209 gnm_cell_unrender (cell);
210 sheet_cell_queue_respan (cell);
214 static void
215 re_render_formulas (Sheet const *sheet)
217 sheet_cell_foreach (sheet, (GHFunc)cb_re_render_formulas, NULL);
220 static void
221 sheet_set_conventions (Sheet *sheet, GnmConventions const *convs)
223 if (sheet->convs == convs)
224 return;
225 gnm_conventions_unref (sheet->convs);
226 sheet->convs = gnm_conventions_ref (convs);
227 if (sheet->display_formulas)
228 re_render_formulas (sheet);
229 SHEET_FOREACH_VIEW (sheet, sv,
230 sv->edit_pos_changed.content = TRUE;);
231 sheet_mark_dirty (sheet);
234 GnmConventions const *
235 sheet_get_conventions (Sheet const *sheet)
237 g_return_val_if_fail (IS_SHEET (sheet), gnm_conventions_default);
239 return sheet->convs;
242 static void
243 cb_sheet_set_hide_zeros (G_GNUC_UNUSED gpointer unused,
244 GnmCell *cell,
245 G_GNUC_UNUSED gpointer user)
247 if (gnm_cell_is_zero (cell))
248 gnm_cell_unrender (cell);
251 static void
252 sheet_set_hide_zeros (Sheet *sheet, gboolean hide)
254 hide = !!hide;
255 if (sheet->hide_zero == hide)
256 return;
258 sheet->hide_zero = hide;
259 sheet_mark_dirty (sheet);
261 sheet_cell_foreach (sheet, (GHFunc)cb_sheet_set_hide_zeros, NULL);
264 static void
265 sheet_set_name (Sheet *sheet, char const *new_name)
267 Workbook *wb = sheet->workbook;
268 gboolean attached;
269 Sheet *sucker;
270 char *new_name_unquoted;
272 g_return_if_fail (new_name != NULL);
274 /* No change whatsoever. */
275 if (go_str_compare (sheet->name_unquoted, new_name) == 0)
276 return;
278 /* Mark the sheet dirty unless this is the initial name. */
279 if (sheet->name_unquoted)
280 sheet_mark_dirty (sheet);
282 sucker = wb ? workbook_sheet_by_name (wb, new_name) : NULL;
283 if (sucker && sucker != sheet) {
285 * Prevent a name clash. With this you can swap names by
286 * setting just the two names.
288 char *sucker_name = workbook_sheet_get_free_name (wb, new_name, TRUE, FALSE);
289 #if 0
290 g_warning ("Renaming %s to %s to avoid clash.\n", sucker->name_unquoted, sucker_name);
291 #endif
292 g_object_set (sucker, "name", sucker_name, NULL);
293 g_free (sucker_name);
296 attached = wb != NULL &&
297 sheet->index_in_wb != -1 &&
298 sheet->name_case_insensitive;
300 /* FIXME: maybe have workbook_sheet_detach_internal for this. */
301 if (attached)
302 g_hash_table_remove (wb->sheet_hash_private,
303 sheet->name_case_insensitive);
305 /* Copy before free. */
306 new_name_unquoted = g_strdup (new_name);
308 g_free (sheet->name_unquoted);
309 g_free (sheet->name_quoted);
310 g_free (sheet->name_unquoted_collate_key);
311 g_free (sheet->name_case_insensitive);
312 sheet->name_unquoted = new_name_unquoted;
313 sheet->name_quoted = g_string_free
314 (gnm_expr_conv_quote (sheet->convs, new_name_unquoted),
315 FALSE);
316 sheet->name_unquoted_collate_key =
317 g_utf8_collate_key (new_name_unquoted, -1);
318 sheet->name_case_insensitive =
319 g_utf8_casefold (new_name_unquoted, -1);
321 /* FIXME: maybe have workbook_sheet_attach_internal for this. */
322 if (attached)
323 g_hash_table_insert (wb->sheet_hash_private,
324 sheet->name_case_insensitive,
325 sheet);
327 if (!sheet->being_constructed &&
328 sheet->sheet_type == GNM_SHEET_DATA) {
329 /* We have to fix the Sheet_Title name */
330 GnmNamedExpr *nexpr;
331 GnmParsePos pp;
333 parse_pos_init_sheet (&pp, sheet);
334 nexpr = expr_name_lookup (&pp, "Sheet_Title");
335 if (nexpr) {
336 GnmExprTop const *texpr =
337 gnm_expr_top_new_constant
338 (value_new_string (sheet->name_unquoted));
339 expr_name_set_expr (nexpr, texpr);
344 struct resize_colrow {
345 Sheet *sheet;
346 gboolean is_cols;
347 double scale;
350 static gboolean
351 cb_colrow_compute_pixels_from_pts (GnmColRowIter const *iter,
352 gpointer data_)
354 struct resize_colrow *data = data_;
355 colrow_compute_pixels_from_pts ((ColRowInfo *)iter->cri,
356 data->sheet, data->is_cols,
357 data->scale);
358 return FALSE;
361 static void
362 cb_clear_rendered_cells (G_GNUC_UNUSED gpointer ignored, GnmCell *cell)
364 if (gnm_cell_get_rendered_value (cell) != NULL) {
365 sheet_cell_queue_respan (cell);
366 gnm_cell_unrender (cell);
370 static void
371 sheet_scale_changed (Sheet *sheet, gboolean cols_rescaled, gboolean rows_rescaled)
373 g_return_if_fail (cols_rescaled || rows_rescaled);
375 /* Then every column and row */
376 if (cols_rescaled) {
377 struct resize_colrow closure;
379 closure.sheet = sheet;
380 closure.is_cols = TRUE;
381 closure.scale = colrow_compute_pixel_scale (sheet, TRUE);
383 colrow_compute_pixels_from_pts (&sheet->cols.default_style,
384 sheet, TRUE, closure.scale);
385 sheet_colrow_foreach (sheet, TRUE, 0, -1,
386 cb_colrow_compute_pixels_from_pts,
387 &closure);
389 if (rows_rescaled) {
390 struct resize_colrow closure;
392 closure.sheet = sheet;
393 closure.is_cols = FALSE;
394 closure.scale = colrow_compute_pixel_scale (sheet, FALSE);
396 colrow_compute_pixels_from_pts (&sheet->rows.default_style,
397 sheet, FALSE, closure.scale);
398 sheet_colrow_foreach (sheet, FALSE, 0, -1,
399 cb_colrow_compute_pixels_from_pts,
400 &closure);
403 sheet_cell_foreach (sheet, (GHFunc)&cb_clear_rendered_cells, NULL);
404 SHEET_FOREACH_CONTROL (sheet, view, control, sc_scale_changed (control););
407 static void
408 sheet_set_display_formulas (Sheet *sheet, gboolean display)
410 display = !!display;
411 if (sheet->display_formulas == display)
412 return;
414 sheet->display_formulas = display;
415 sheet_mark_dirty (sheet);
416 if (!sheet->being_constructed)
417 sheet_scale_changed (sheet, TRUE, FALSE);
420 static void
421 sheet_set_zoom_factor (Sheet *sheet, double factor)
423 if (fabs (factor - sheet->last_zoom_factor_used) < 1e-6)
424 return;
425 sheet->last_zoom_factor_used = factor;
426 if (!sheet->being_constructed)
427 sheet_scale_changed (sheet, TRUE, TRUE);
430 static void
431 gnm_sheet_set_property (GObject *object, guint property_id,
432 GValue const *value, GParamSpec *pspec)
434 Sheet *sheet = (Sheet *)object;
436 switch (property_id) {
437 case PROP_SHEET_TYPE:
438 /* Construction-time only */
439 sheet->sheet_type = g_value_get_enum (value);
440 break;
441 case PROP_WORKBOOK:
442 /* Construction-time only */
443 sheet->workbook = g_value_get_object (value);
444 break;
445 case PROP_NAME:
446 sheet_set_name (sheet, g_value_get_string (value));
447 break;
448 case PROP_RTL:
449 sheet_set_direction (sheet, g_value_get_boolean (value));
450 break;
451 case PROP_VISIBILITY:
452 sheet_set_visibility (sheet, g_value_get_enum (value));
453 break;
454 case PROP_DISPLAY_FORMULAS:
455 sheet_set_display_formulas (sheet, g_value_get_boolean (value));
456 break;
457 case PROP_DISPLAY_ZEROS:
458 sheet_set_hide_zeros (sheet, !g_value_get_boolean (value));
459 break;
460 case PROP_DISPLAY_GRID:
461 sheet->hide_grid = !g_value_get_boolean (value);
462 break;
463 case PROP_DISPLAY_COLUMN_HEADER:
464 sheet->hide_col_header = !g_value_get_boolean (value);
465 break;
466 case PROP_DISPLAY_ROW_HEADER:
467 sheet->hide_row_header = !g_value_get_boolean (value);
468 break;
469 case PROP_DISPLAY_OUTLINES:
470 sheet->display_outlines = !!g_value_get_boolean (value);
471 break;
472 case PROP_DISPLAY_OUTLINES_BELOW:
473 sheet->outline_symbols_below = !!g_value_get_boolean (value);
474 break;
475 case PROP_DISPLAY_OUTLINES_RIGHT:
476 sheet->outline_symbols_right = !!g_value_get_boolean (value);
477 break;
479 case PROP_PROTECTED:
480 sheet->is_protected = !!g_value_get_boolean (value);
481 break;
482 case PROP_PROTECTED_ALLOW_EDIT_OBJECTS:
483 sheet->protected_allow.edit_objects = !!g_value_get_boolean (value);
484 break;
485 case PROP_PROTECTED_ALLOW_EDIT_SCENARIOS:
486 sheet->protected_allow.edit_scenarios = !!g_value_get_boolean (value);
487 break;
488 case PROP_PROTECTED_ALLOW_CELL_FORMATTING:
489 sheet->protected_allow.cell_formatting = !!g_value_get_boolean (value);
490 break;
491 case PROP_PROTECTED_ALLOW_COLUMN_FORMATTING:
492 sheet->protected_allow.column_formatting = !!g_value_get_boolean (value);
493 break;
494 case PROP_PROTECTED_ALLOW_ROW_FORMATTING:
495 sheet->protected_allow.row_formatting = !!g_value_get_boolean (value);
496 break;
497 case PROP_PROTECTED_ALLOW_INSERT_COLUMNS:
498 sheet->protected_allow.insert_columns = !!g_value_get_boolean (value);
499 break;
500 case PROP_PROTECTED_ALLOW_INSERT_ROWS:
501 sheet->protected_allow.insert_rows = !!g_value_get_boolean (value);
502 break;
503 case PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS:
504 sheet->protected_allow.insert_hyperlinks = !!g_value_get_boolean (value);
505 break;
506 case PROP_PROTECTED_ALLOW_DELETE_COLUMNS:
507 sheet->protected_allow.delete_columns = !!g_value_get_boolean (value);
508 break;
509 case PROP_PROTECTED_ALLOW_DELETE_ROWS:
510 sheet->protected_allow.delete_rows = !!g_value_get_boolean (value);
511 break;
512 case PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS:
513 sheet->protected_allow.select_locked_cells = !!g_value_get_boolean (value);
514 break;
515 case PROP_PROTECTED_ALLOW_SORT_RANGES:
516 sheet->protected_allow.sort_ranges = !!g_value_get_boolean (value);
517 break;
518 case PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS:
519 sheet->protected_allow.edit_auto_filters = !!g_value_get_boolean (value);
520 break;
521 case PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE:
522 sheet->protected_allow.edit_pivottable = !!g_value_get_boolean (value);
523 break;
524 case PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS:
525 sheet->protected_allow.select_unlocked_cells = !!g_value_get_boolean (value);
526 break;
528 case PROP_CONVENTIONS:
529 sheet_set_conventions (sheet, g_value_get_boxed (value));
530 break;
531 case PROP_USE_R1C1: /* convenience api */
532 sheet_set_conventions (sheet, !!g_value_get_boolean (value)
533 ? gnm_conventions_xls_r1c1 : gnm_conventions_default);
534 break;
536 case PROP_TAB_FOREGROUND: {
537 GnmColor *color = g_value_dup_boxed (value);
538 style_color_unref (sheet->tab_text_color);
539 sheet->tab_text_color = color;
540 sheet_mark_dirty (sheet);
541 break;
543 case PROP_TAB_BACKGROUND: {
544 GnmColor *color = g_value_dup_boxed (value);
545 style_color_unref (sheet->tab_color);
546 sheet->tab_color = color;
547 sheet_mark_dirty (sheet);
548 break;
550 case PROP_ZOOM_FACTOR:
551 sheet_set_zoom_factor (sheet, g_value_get_double (value));
552 break;
553 case PROP_COLUMNS:
554 /* Construction-time only */
555 sheet->size.max_cols = g_value_get_int (value);
556 break;
557 case PROP_ROWS:
558 /* Construction-time only */
559 sheet->size.max_rows = g_value_get_int (value);
560 break;
561 default:
562 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
563 break;
567 static void
568 gnm_sheet_get_property (GObject *object, guint property_id,
569 GValue *value, GParamSpec *pspec)
571 Sheet *sheet = (Sheet *)object;
573 switch (property_id) {
574 case PROP_SHEET_TYPE:
575 g_value_set_enum (value, sheet->sheet_type);
576 break;
577 case PROP_WORKBOOK:
578 g_value_set_object (value, sheet->workbook);
579 break;
580 case PROP_NAME:
581 g_value_set_string (value, sheet->name_unquoted);
582 break;
583 case PROP_RTL:
584 g_value_set_boolean (value, sheet->text_is_rtl);
585 break;
586 case PROP_VISIBILITY:
587 g_value_set_enum (value, sheet->visibility);
588 break;
589 case PROP_DISPLAY_FORMULAS:
590 g_value_set_boolean (value, sheet->display_formulas);
591 break;
592 case PROP_DISPLAY_ZEROS:
593 g_value_set_boolean (value, !sheet->hide_zero);
594 break;
595 case PROP_DISPLAY_GRID:
596 g_value_set_boolean (value, !sheet->hide_grid);
597 break;
598 case PROP_DISPLAY_COLUMN_HEADER:
599 g_value_set_boolean (value, !sheet->hide_col_header);
600 break;
601 case PROP_DISPLAY_ROW_HEADER:
602 g_value_set_boolean (value, !sheet->hide_row_header);
603 break;
604 case PROP_DISPLAY_OUTLINES:
605 g_value_set_boolean (value, sheet->display_outlines);
606 break;
607 case PROP_DISPLAY_OUTLINES_BELOW:
608 g_value_set_boolean (value, sheet->outline_symbols_below);
609 break;
610 case PROP_DISPLAY_OUTLINES_RIGHT:
611 g_value_set_boolean (value, sheet->outline_symbols_right);
612 break;
614 case PROP_PROTECTED:
615 g_value_set_boolean (value, sheet->is_protected);
616 break;
617 case PROP_PROTECTED_ALLOW_EDIT_OBJECTS:
618 g_value_set_boolean (value, sheet->protected_allow.edit_objects);
619 break;
620 case PROP_PROTECTED_ALLOW_EDIT_SCENARIOS:
621 g_value_set_boolean (value, sheet->protected_allow.edit_scenarios);
622 break;
623 case PROP_PROTECTED_ALLOW_CELL_FORMATTING:
624 g_value_set_boolean (value, sheet->protected_allow.cell_formatting);
625 break;
626 case PROP_PROTECTED_ALLOW_COLUMN_FORMATTING:
627 g_value_set_boolean (value, sheet->protected_allow.column_formatting);
628 break;
629 case PROP_PROTECTED_ALLOW_ROW_FORMATTING:
630 g_value_set_boolean (value, sheet->protected_allow.row_formatting);
631 break;
632 case PROP_PROTECTED_ALLOW_INSERT_COLUMNS:
633 g_value_set_boolean (value, sheet->protected_allow.insert_columns);
634 break;
635 case PROP_PROTECTED_ALLOW_INSERT_ROWS:
636 g_value_set_boolean (value, sheet->protected_allow.insert_rows);
637 break;
638 case PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS:
639 g_value_set_boolean (value, sheet->protected_allow.insert_hyperlinks);
640 break;
641 case PROP_PROTECTED_ALLOW_DELETE_COLUMNS:
642 g_value_set_boolean (value, sheet->protected_allow.delete_columns);
643 break;
644 case PROP_PROTECTED_ALLOW_DELETE_ROWS:
645 g_value_set_boolean (value, sheet->protected_allow.delete_rows);
646 break;
647 case PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS:
648 g_value_set_boolean (value, sheet->protected_allow.select_locked_cells);
649 break;
650 case PROP_PROTECTED_ALLOW_SORT_RANGES:
651 g_value_set_boolean (value, sheet->protected_allow.sort_ranges);
652 break;
653 case PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS:
654 g_value_set_boolean (value, sheet->protected_allow.edit_auto_filters);
655 break;
656 case PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE:
657 g_value_set_boolean (value, sheet->protected_allow.edit_pivottable);
658 break;
659 case PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS:
660 g_value_set_boolean (value, sheet->protected_allow.select_unlocked_cells);
661 break;
663 case PROP_CONVENTIONS:
664 g_value_set_boxed (value, sheet->convs);
665 break;
666 case PROP_USE_R1C1: /* convenience api */
667 g_value_set_boolean (value, sheet->convs->r1c1_addresses);
668 break;
670 case PROP_TAB_FOREGROUND:
671 g_value_set_boxed (value, sheet->tab_text_color);
672 break;
673 case PROP_TAB_BACKGROUND:
674 g_value_set_boxed (value, sheet->tab_color);
675 break;
676 case PROP_ZOOM_FACTOR:
677 g_value_set_double (value, sheet->last_zoom_factor_used);
678 break;
679 case PROP_COLUMNS:
680 g_value_set_int (value, sheet->size.max_cols);
681 break;
682 case PROP_ROWS:
683 g_value_set_int (value, sheet->size.max_rows);
684 break;
685 default:
686 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
687 break;
691 static void
692 gnm_sheet_constructed (GObject *obj)
694 Sheet *sheet = SHEET (obj);
696 if (parent_class->constructed)
697 parent_class->constructed (obj);
699 /* Now sheet_type, max_cols, and max_rows have been set. */
700 sheet->being_constructed = FALSE;
702 col_row_collection_resize (&sheet->cols, sheet->size.max_cols);
703 col_row_collection_resize (&sheet->rows, sheet->size.max_rows);
705 sheet->priv->reposition_objects.col = sheet->size.max_cols;
706 sheet->priv->reposition_objects.row = sheet->size.max_rows;
708 range_init_full_sheet (&sheet->priv->unhidden_region, sheet);
709 sheet_style_init (sheet);
711 sheet->deps = gnm_dep_container_new (sheet);
713 switch (sheet->sheet_type) {
714 case GNM_SHEET_XLM:
715 sheet->display_formulas = TRUE;
716 break;
717 case GNM_SHEET_OBJECT:
718 sheet->hide_grid = TRUE;
719 sheet->hide_col_header = sheet->hide_row_header = TRUE;
720 colrow_compute_pixels_from_pts (&sheet->rows.default_style,
721 sheet, FALSE, -1);
722 colrow_compute_pixels_from_pts (&sheet->cols.default_style,
723 sheet, TRUE, -1);
724 break;
725 case GNM_SHEET_DATA: {
726 /* We have to add permanent names */
727 GnmExprTop const *texpr;
729 if (sheet->name_unquoted)
730 texpr = gnm_expr_top_new_constant
731 (value_new_string (sheet->name_unquoted));
732 else
733 texpr = gnm_expr_top_new_constant
734 (value_new_error_REF (NULL));
735 expr_name_perm_add (sheet, "Sheet_Title",
736 texpr, FALSE);
738 texpr = gnm_expr_top_new_constant
739 (value_new_error_REF (NULL));
740 expr_name_perm_add (sheet, "Print_Area",
741 texpr, FALSE);
742 break;
744 default:
745 g_assert_not_reached ();
748 sheet_scale_changed (sheet, TRUE, TRUE);
751 static guint
752 cell_set_hash (GnmCell const *key)
754 guint32 r = key->pos.row;
755 guint32 c = key->pos.col;
756 guint32 h;
758 h = r;
759 h *= (guint32)123456789;
760 h ^= c;
761 h *= (guint32)123456789;
763 return h;
766 static gint
767 cell_set_equal (GnmCell const *a, GnmCell const *b)
769 return (a->pos.row == b->pos.row && a->pos.col == b->pos.col);
772 static void
773 gnm_sheet_init (Sheet *sheet)
775 PangoContext *context;
777 sheet->priv = g_new0 (SheetPrivate, 1);
778 sheet->being_constructed = TRUE;
780 sheet->sheet_views = g_ptr_array_new ();
782 /* Init, focus, and load handle setting these if/when necessary */
783 sheet->priv->recompute_visibility = TRUE;
784 sheet->priv->recompute_spans = TRUE;
786 sheet->is_protected = FALSE;
787 sheet->protected_allow.edit_scenarios = FALSE;
788 sheet->protected_allow.cell_formatting = FALSE;
789 sheet->protected_allow.column_formatting = FALSE;
790 sheet->protected_allow.row_formatting = FALSE;
791 sheet->protected_allow.insert_columns = FALSE;
792 sheet->protected_allow.insert_rows = FALSE;
793 sheet->protected_allow.insert_hyperlinks = FALSE;
794 sheet->protected_allow.delete_columns = FALSE;
795 sheet->protected_allow.delete_rows = FALSE;
796 sheet->protected_allow.select_locked_cells = TRUE;
797 sheet->protected_allow.sort_ranges = FALSE;
798 sheet->protected_allow.edit_auto_filters = FALSE;
799 sheet->protected_allow.edit_pivottable = FALSE;
800 sheet->protected_allow.select_unlocked_cells = TRUE;
802 sheet->hide_zero = FALSE;
803 sheet->display_outlines = TRUE;
804 sheet->outline_symbols_below = TRUE;
805 sheet->outline_symbols_right = TRUE;
806 sheet->tab_color = NULL;
807 sheet->tab_text_color = NULL;
808 sheet->visibility = GNM_SHEET_VISIBILITY_VISIBLE;
809 #ifdef GNM_WITH_GTK
810 sheet->text_is_rtl = (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL);
811 #else
812 sheet->text_is_rtl = FALSE;
813 #endif
815 sheet->sheet_objects = NULL;
816 sheet->max_object_extent.col = sheet->max_object_extent.row = 0;
818 sheet->solver_parameters = gnm_solver_param_new (sheet);
820 sheet->cols.max_used = -1;
821 sheet->cols.info = g_ptr_array_new ();
822 sheet_col_set_default_size_pts (sheet, 48);
824 sheet->rows.max_used = -1;
825 sheet->rows.info = g_ptr_array_new ();
826 sheet_row_set_default_size_pts (sheet, 12.75);
828 sheet->print_info = gnm_print_information_new (FALSE);
830 sheet->filters = NULL;
831 sheet->scenarios = NULL;
832 sheet->sort_setups = NULL;
833 sheet->list_merged = NULL;
834 sheet->hash_merged = g_hash_table_new ((GHashFunc)&gnm_cellpos_hash,
835 (GCompareFunc)&gnm_cellpos_equal);
837 sheet->cell_hash = g_hash_table_new ((GHashFunc)&cell_set_hash,
838 (GCompareFunc)&cell_set_equal);
840 /* Init preferences */
841 sheet->convs = gnm_conventions_ref (gnm_conventions_default);
843 /* FIXME: probably not here. */
844 /* See also gtk_widget_create_pango_context (). */
845 sheet->last_zoom_factor_used = -1; /* Overridden later */
846 context = gnm_pango_context_get ();
847 sheet->rendered_values = gnm_rvc_new (context, 5000);
848 g_object_unref (context);
850 /* Init menu states */
851 sheet->priv->enable_showhide_detail = TRUE;
853 sheet->names = gnm_named_expr_collection_new ();
854 sheet->style_data = NULL;
856 sheet->index_in_wb = -1;
859 static Sheet the_invalid_sheet;
860 Sheet *invalid_sheet = &the_invalid_sheet;
862 static void
863 gnm_sheet_class_init (GObjectClass *gobject_class)
865 if (GNM_MAX_COLS > 364238) {
866 /* Oh, yeah? */
867 g_warning (_("This is a special version of Gnumeric. It has been compiled\n"
868 "with support for a very large number of columns. Access to the\n"
869 "column named TRUE may conflict with the constant of the same\n"
870 "name. Expect weirdness."));
873 parent_class = g_type_class_peek_parent (gobject_class);
875 gobject_class->set_property = gnm_sheet_set_property;
876 gobject_class->get_property = gnm_sheet_get_property;
877 gobject_class->finalize = gnm_sheet_finalize;
878 gobject_class->constructed = gnm_sheet_constructed;
880 g_object_class_install_property (gobject_class, PROP_SHEET_TYPE,
881 g_param_spec_enum ("sheet-type",
882 P_("Sheet Type"),
883 P_("Which type of sheet this is."),
884 GNM_SHEET_TYPE_TYPE,
885 GNM_SHEET_DATA,
886 GSF_PARAM_STATIC |
887 G_PARAM_READWRITE |
888 G_PARAM_CONSTRUCT_ONLY));
889 g_object_class_install_property (gobject_class, PROP_WORKBOOK,
890 g_param_spec_object ("workbook",
891 P_("Parent workbook"),
892 P_("The workbook in which this sheet lives"),
893 GNM_WORKBOOK_TYPE,
894 GSF_PARAM_STATIC |
895 G_PARAM_READWRITE |
896 G_PARAM_CONSTRUCT_ONLY));
897 g_object_class_install_property (gobject_class, PROP_NAME,
898 g_param_spec_string ("name",
899 P_("Name"),
900 P_("The name of the sheet."),
901 NULL,
902 GSF_PARAM_STATIC |
903 G_PARAM_READWRITE));
904 g_object_class_install_property (gobject_class, PROP_RTL,
905 g_param_spec_boolean ("text-is-rtl",
906 P_("text-is-rtl"),
907 P_("Text goes from right to left."),
908 FALSE,
909 GSF_PARAM_STATIC |
910 G_PARAM_READWRITE));
911 g_object_class_install_property (gobject_class, PROP_VISIBILITY,
912 g_param_spec_enum ("visibility",
913 P_("Visibility"),
914 P_("How visible the sheet is."),
915 GNM_SHEET_VISIBILITY_TYPE,
916 GNM_SHEET_VISIBILITY_VISIBLE,
917 GSF_PARAM_STATIC |
918 G_PARAM_READWRITE));
919 g_object_class_install_property (gobject_class, PROP_DISPLAY_FORMULAS,
920 g_param_spec_boolean ("display-formulas",
921 P_("Display Formul\303\246"),
922 P_("Control whether formul\303\246 are shown instead of values."),
923 FALSE,
924 GSF_PARAM_STATIC |
925 G_PARAM_READWRITE));
926 g_object_class_install_property (gobject_class, PROP_DISPLAY_ZEROS,
927 g_param_spec_boolean ("display-zeros", _("Display Zeros"),
928 _("Control whether zeros are shown are blanked out."),
929 TRUE,
930 GSF_PARAM_STATIC |
931 G_PARAM_READWRITE));
932 g_object_class_install_property (gobject_class, PROP_DISPLAY_GRID,
933 g_param_spec_boolean ("display-grid", _("Display Grid"),
934 _("Control whether the grid is shown."),
935 TRUE,
936 GSF_PARAM_STATIC |
937 G_PARAM_READWRITE));
938 g_object_class_install_property (gobject_class, PROP_DISPLAY_COLUMN_HEADER,
939 g_param_spec_boolean ("display-column-header",
940 P_("Display Column Headers"),
941 P_("Control whether column headers are shown."),
942 FALSE,
943 GSF_PARAM_STATIC |
944 G_PARAM_READWRITE));
945 g_object_class_install_property (gobject_class, PROP_DISPLAY_ROW_HEADER,
946 g_param_spec_boolean ("display-row-header",
947 P_("Display Row Headers"),
948 P_("Control whether row headers are shown."),
949 FALSE,
950 GSF_PARAM_STATIC |
951 G_PARAM_READWRITE));
952 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES,
953 g_param_spec_boolean ("display-outlines",
954 P_("Display Outlines"),
955 P_("Control whether outlines are shown."),
956 TRUE,
957 GSF_PARAM_STATIC |
958 G_PARAM_READWRITE));
959 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES_BELOW,
960 g_param_spec_boolean ("display-outlines-below",
961 P_("Display Outlines Below"),
962 P_("Control whether outline symbols are shown below."),
963 TRUE,
964 GSF_PARAM_STATIC |
965 G_PARAM_READWRITE));
966 g_object_class_install_property (gobject_class, PROP_DISPLAY_OUTLINES_RIGHT,
967 g_param_spec_boolean ("display-outlines-right",
968 P_("Display Outlines Right"),
969 P_("Control whether outline symbols are shown to the right."),
970 TRUE,
971 GSF_PARAM_STATIC |
972 G_PARAM_READWRITE));
974 g_object_class_install_property (gobject_class, PROP_PROTECTED,
975 g_param_spec_boolean ("protected",
976 P_("Protected"),
977 P_("Sheet is protected."),
978 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
979 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_OBJECTS,
980 g_param_spec_boolean ("protected-allow-edit-objects",
981 P_("Protected Allow Edit objects"),
982 P_("Allow objects to be edited while a sheet is protected"),
983 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
984 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_SCENARIOS,
985 g_param_spec_boolean ("protected-allow-edit-scenarios",
986 P_("Protected allow edit scenarios"),
987 P_("Allow scenarios to be edited while a sheet is protected"),
988 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
989 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_CELL_FORMATTING,
990 g_param_spec_boolean ("protected-allow-cell-formatting",
991 P_("Protected allow cell formatting"),
992 P_("Allow cell format changes while a sheet is protected"),
993 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
994 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_COLUMN_FORMATTING,
995 g_param_spec_boolean ("protected-allow-column-formatting",
996 P_("Protected allow column formatting"),
997 P_("Allow column formatting while a sheet is protected"),
998 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
999 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_ROW_FORMATTING,
1000 g_param_spec_boolean ("protected-allow-row-formatting",
1001 P_("Protected allow row formatting"),
1002 P_("Allow row formatting while a sheet is protected"),
1003 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1004 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_COLUMNS,
1005 g_param_spec_boolean ("protected-allow-insert-columns",
1006 P_("Protected allow insert columns"),
1007 P_("Allow columns to be inserted while a sheet is protected"),
1008 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1009 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_ROWS,
1010 g_param_spec_boolean ("protected-allow-insert-rows",
1011 P_("Protected allow insert rows"),
1012 P_("Allow rows to be inserted while a sheet is protected"),
1013 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1014 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_INSERT_HYPERLINKS,
1015 g_param_spec_boolean ("protected-allow-insert-hyperlinks",
1016 P_("Protected allow insert hyperlinks"),
1017 P_("Allow hyperlinks to be inserted while a sheet is protected"),
1018 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1019 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_DELETE_COLUMNS,
1020 g_param_spec_boolean ("protected-allow-delete-columns",
1021 P_("Protected allow delete columns"),
1022 P_("Allow columns to be deleted while a sheet is protected"),
1023 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1024 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_DELETE_ROWS,
1025 g_param_spec_boolean ("protected-allow-delete-rows",
1026 P_("Protected allow delete rows"),
1027 P_("Allow rows to be deleted while a sheet is protected"),
1028 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1029 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SELECT_LOCKED_CELLS,
1030 g_param_spec_boolean ("protected-allow-select-locked-cells",
1031 P_("Protected allow select locked cells"),
1032 P_("Allow the user to select locked cells while a sheet is protected"),
1033 TRUE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1034 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SORT_RANGES,
1035 g_param_spec_boolean ("protected-allow-sort-ranges",
1036 P_("Protected allow sort ranges"),
1037 P_("Allow ranges to be sorted while a sheet is protected"),
1038 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1039 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_AUTO_FILTERS,
1040 g_param_spec_boolean ("protected-allow-edit-auto-filters",
1041 P_("Protected allow edit auto filters"),
1042 P_("Allow auto filters to be edited while a sheet is protected"),
1043 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1044 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_EDIT_PIVOTTABLE,
1045 g_param_spec_boolean ("protected-allow-edit-pivottable",
1046 P_("Protected allow edit pivottable"),
1047 P_("Allow pivottable to be edited while a sheet is protected"),
1048 FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1049 g_object_class_install_property (gobject_class, PROP_PROTECTED_ALLOW_SELECT_UNLOCKED_CELLS,
1050 g_param_spec_boolean ("protected-allow-select-unlocked-cells",
1051 P_("Protected allow select unlocked cells"),
1052 P_("Allow the user to select unlocked cells while a sheet is protected"),
1053 TRUE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
1055 g_object_class_install_property
1056 (gobject_class, PROP_CONVENTIONS,
1057 g_param_spec_boxed ("conventions",
1058 P_("Display convention for expressions (default Gnumeric A1)"),
1059 P_("How to format displayed expressions, (A1 vs R1C1, function names, ...)"),
1060 gnm_conventions_get_type (),
1061 GSF_PARAM_STATIC |
1062 G_PARAM_READWRITE));
1063 g_object_class_install_property (gobject_class, PROP_USE_R1C1, /* convenience wrapper to CONVENTIONS */
1064 g_param_spec_boolean ("use-r1c1",
1065 P_("Display convention for expressions as XLS_R1C1 vs default"),
1066 P_("How to format displayed expressions, (a convenience api)"),
1067 FALSE,
1068 GSF_PARAM_STATIC |
1069 G_PARAM_READWRITE));
1071 g_object_class_install_property (gobject_class, PROP_TAB_FOREGROUND,
1072 g_param_spec_boxed ("tab-foreground",
1073 P_("Tab Foreground"),
1074 P_("The foreground color of the tab."),
1075 GNM_COLOR_TYPE,
1076 GSF_PARAM_STATIC |
1077 G_PARAM_READWRITE));
1078 g_object_class_install_property (gobject_class, PROP_TAB_BACKGROUND,
1079 g_param_spec_boxed ("tab-background",
1080 P_("Tab Background"),
1081 P_("The background color of the tab."),
1082 GNM_COLOR_TYPE,
1083 GSF_PARAM_STATIC |
1084 G_PARAM_READWRITE));
1086 /* What is this doing in sheet? */
1087 g_object_class_install_property (gobject_class, PROP_ZOOM_FACTOR,
1088 g_param_spec_double ("zoom-factor",
1089 P_("Zoom Factor"),
1090 P_("The level of zoom used for this sheet."),
1091 0.1, 5.0,
1092 1.0,
1093 GSF_PARAM_STATIC |
1094 G_PARAM_CONSTRUCT |
1095 G_PARAM_READWRITE));
1097 g_object_class_install_property (gobject_class, PROP_COLUMNS,
1098 g_param_spec_int ("columns",
1099 P_("Columns"),
1100 P_("Columns number in the sheet"),
1101 0, GNM_MAX_COLS, GNM_DEFAULT_COLS,
1102 GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1104 g_object_class_install_property (gobject_class, PROP_ROWS,
1105 g_param_spec_int ("rows",
1106 P_("Rows"),
1107 P_("Rows number in the sheet"),
1108 0, GNM_MAX_ROWS, GNM_DEFAULT_ROWS,
1109 GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1111 signals[DETACHED_FROM_WORKBOOK] = g_signal_new
1112 ("detached_from_workbook",
1113 GNM_SHEET_TYPE,
1114 G_SIGNAL_RUN_LAST,
1115 G_STRUCT_OFFSET (GnmSheetClass, detached_from_workbook),
1116 NULL, NULL,
1117 g_cclosure_marshal_VOID__OBJECT,
1118 G_TYPE_NONE, 1, GNM_WORKBOOK_TYPE);
1122 GSF_CLASS (GnmSheet, gnm_sheet,
1123 gnm_sheet_class_init, gnm_sheet_init, G_TYPE_OBJECT)
1125 /* ------------------------------------------------------------------------- */
1127 GType
1128 gnm_sheet_type_get_type (void)
1130 static GType etype = 0;
1131 if (etype == 0) {
1132 static const GEnumValue values[] = {
1133 { GNM_SHEET_DATA, "GNM_SHEET_DATA", "data" },
1134 { GNM_SHEET_OBJECT, "GNM_SHEET_OBJECT", "object" },
1135 { GNM_SHEET_XLM, "GNM_SHEET_XLM", "xlm" },
1136 { 0, NULL, NULL }
1138 etype = g_enum_register_static ("GnmSheetType", values);
1140 return etype;
1143 GType
1144 gnm_sheet_visibility_get_type (void)
1146 static GType etype = 0;
1147 if (etype == 0) {
1148 static GEnumValue const values[] = {
1149 { GNM_SHEET_VISIBILITY_VISIBLE, "GNM_SHEET_VISIBILITY_VISIBLE", "visible" },
1150 { GNM_SHEET_VISIBILITY_HIDDEN, "GNM_SHEET_VISIBILITY_HIDDEN", "hidden" },
1151 { GNM_SHEET_VISIBILITY_VERY_HIDDEN, "GNM_SHEET_VISIBILITY_VERY_HIDDEN", "very-hidden" },
1152 { 0, NULL, NULL }
1154 etype = g_enum_register_static ("GnmSheetVisibility", values);
1156 return etype;
1159 /* ------------------------------------------------------------------------- */
1161 static gboolean
1162 powerof_2 (int i)
1164 return i > 0 && (i & (i - 1)) == 0;
1167 gboolean
1168 gnm_sheet_valid_size (int cols, int rows)
1170 return (cols >= GNM_MIN_COLS &&
1171 cols <= GNM_MAX_COLS &&
1172 powerof_2 (cols) &&
1173 rows >= GNM_MIN_ROWS &&
1174 rows <= GNM_MAX_ROWS &&
1175 powerof_2 (rows)
1176 #if 0
1177 && 0x80000000u / (unsigned)(cols / 2) >= (unsigned)rows
1178 #endif
1182 void
1183 gnm_sheet_suggest_size (int *cols, int *rows)
1185 int c = GNM_DEFAULT_COLS;
1186 int r = GNM_DEFAULT_ROWS;
1188 while (c < *cols && c < GNM_MAX_COLS)
1189 c *= 2;
1191 while (r < *rows && r < GNM_MAX_ROWS)
1192 r *= 2;
1194 while (!gnm_sheet_valid_size (c, r)) {
1195 /* Darn! Too large. */
1196 if (*cols >= GNM_MIN_COLS && c > GNM_MIN_COLS)
1197 c /= 2;
1198 else if (*rows >= GNM_MIN_ROWS && r > GNM_MIN_ROWS)
1199 r /= 2;
1200 else if (c > GNM_MIN_COLS)
1201 c /= 2;
1202 else
1203 r /= 2;
1206 *cols = c;
1207 *rows = r;
1210 static void gnm_sheet_resize_main (Sheet *sheet, int cols, int rows,
1211 GOCmdContext *cc, GOUndo **pundo);
1213 static void
1214 cb_sheet_resize (Sheet *sheet, const GnmSheetSize *data, GOCmdContext *cc)
1216 gnm_sheet_resize_main (sheet, data->max_cols, data->max_rows,
1217 cc, NULL);
1220 static void
1221 gnm_sheet_resize_main (Sheet *sheet, int cols, int rows,
1222 GOCmdContext *cc, GOUndo **pundo)
1224 int old_cols, old_rows;
1225 GnmStyle **common_col_styles = NULL;
1226 GnmStyle **common_row_styles = NULL;
1228 if (pundo) *pundo = NULL;
1230 old_cols = gnm_sheet_get_max_cols (sheet);
1231 old_rows = gnm_sheet_get_max_rows (sheet);
1232 if (old_cols == cols && old_rows == rows)
1233 return;
1235 /* ---------------------------------------- */
1236 /* Gather styles we want to copy into new areas. */
1238 if (cols > old_cols) {
1239 int r;
1240 common_row_styles = sheet_style_most_common (sheet, FALSE);
1241 for (r = 0; r < old_rows; r++)
1242 gnm_style_ref (common_row_styles[r]);
1244 if (rows > old_rows) {
1245 int c;
1246 common_col_styles = sheet_style_most_common (sheet, TRUE);
1247 for (c = 0; c < old_cols; c++)
1248 gnm_style_ref (common_col_styles[c]);
1251 /* ---------------------------------------- */
1252 /* Remove the columns and rows that will disappear. */
1254 if (cols < old_cols) {
1255 GOUndo *u = NULL;
1256 gboolean err;
1258 err = sheet_delete_cols (sheet, cols, G_MAXINT,
1259 pundo ? &u : NULL, cc);
1260 if (pundo)
1261 *pundo = go_undo_combine (*pundo, u);
1262 if (err)
1263 goto handle_error;
1266 if (rows < old_rows) {
1267 GOUndo *u = NULL;
1268 gboolean err;
1270 err = sheet_delete_rows (sheet, rows, G_MAXINT,
1271 pundo ? &u : NULL, cc);
1272 if (pundo)
1273 *pundo = go_undo_combine (*pundo, u);
1274 if (err)
1275 goto handle_error;
1278 /* ---------------------------------------- */
1279 /* Restrict selection. (Not undone.) */
1281 SHEET_FOREACH_VIEW (sheet, sv,
1283 GnmRange new_full;
1284 GSList *l;
1285 GSList *sel = selection_get_ranges (sv, TRUE);
1286 gboolean any = FALSE;
1287 GnmCellPos vis;
1288 sv_selection_reset (sv);
1289 range_init (&new_full, 0, 0, cols - 1, rows - 1);
1290 vis = new_full.start;
1291 for (l = sel; l; l = l->next) {
1292 GnmRange *r = l->data;
1293 GnmRange newr;
1294 if (range_intersection (&newr, r, &new_full)) {
1295 sv_selection_add_range (sv, &newr);
1296 vis = newr.start;
1297 any = TRUE;
1299 g_free (r);
1301 g_slist_free (sel);
1302 if (!any)
1303 sv_selection_add_pos (sv, 0, 0,
1304 GNM_SELECTION_MODE_ADD);
1305 gnm_sheet_view_make_cell_visible (sv, vis.col, vis.row, FALSE);
1308 /* ---------------------------------------- */
1309 /* Resize column and row containers. */
1311 col_row_collection_resize (&sheet->cols, cols);
1312 col_row_collection_resize (&sheet->rows, rows);
1314 /* ---------------------------------------- */
1315 /* Resize the dependency containers. */
1318 GSList *l, *linked = NULL;
1319 /* FIXME: what about dependents in other workbooks? */
1320 WORKBOOK_FOREACH_DEPENDENT
1321 (sheet->workbook, dep,
1323 if (dependent_is_linked (dep)) {
1324 dependent_unlink (dep);
1325 linked = g_slist_prepend (linked, dep);
1328 gnm_dep_container_resize (sheet->deps, rows);
1330 for (l = linked; l; l = l->next) {
1331 GnmDependent *dep = l->data;
1332 dependent_link (dep);
1335 g_slist_free (linked);
1337 workbook_queue_all_recalc (sheet->workbook);
1340 /* ---------------------------------------- */
1341 /* Resize the styles. */
1343 sheet_style_resize (sheet, cols, rows);
1345 /* ---------------------------------------- */
1346 /* Actually change the properties. */
1348 sheet->size.max_cols = cols;
1349 sheet->cols.max_used = MIN (sheet->cols.max_used, cols - 1);
1350 sheet->size.max_rows = rows;
1351 sheet->rows.max_used = MIN (sheet->rows.max_used, rows - 1);
1353 if (old_cols != cols)
1354 g_object_notify (G_OBJECT (sheet), "columns");
1355 if (old_rows != rows)
1356 g_object_notify (G_OBJECT (sheet), "rows");
1358 if (pundo) {
1359 GnmSheetSize *data = g_new (GnmSheetSize, 1);
1360 GOUndo *u;
1362 data->max_cols = old_cols;
1363 data->max_rows = old_rows;
1364 u = go_undo_binary_new (sheet, data,
1365 (GOUndoBinaryFunc)cb_sheet_resize,
1366 NULL, g_free);
1367 *pundo = go_undo_combine (*pundo, u);
1370 range_init_full_sheet (&sheet->priv->unhidden_region, sheet);
1372 /* ---------------------------------------- */
1373 /* Apply styles to new areas. */
1375 if (cols > old_cols) {
1376 int r = 0;
1377 while (r < old_rows) {
1378 int r2 = r;
1379 GnmStyle *mstyle = common_row_styles[r];
1380 GnmRange rng;
1381 while (r2 + 1 < old_rows &&
1382 mstyle == common_row_styles[r2 + 1])
1383 r2++;
1384 range_init (&rng, old_cols, r, cols - 1, r2);
1385 gnm_style_ref (mstyle);
1386 sheet_apply_style (sheet, &rng, mstyle);
1387 r = r2 + 1;
1390 for (r = 0; r < old_rows; r++)
1391 gnm_style_unref (common_row_styles[r]);
1393 g_free (common_row_styles);
1396 if (rows > old_rows) {
1397 int c = 0;
1399 while (c < old_cols) {
1400 int c2 = c;
1401 GnmStyle *mstyle = common_col_styles[c];
1402 GnmRange rng;
1403 while (c2 + 1 < old_cols &&
1404 mstyle == common_col_styles[c2 + 1])
1405 c2++;
1406 range_init (&rng, c, old_rows, c2, rows - 1);
1407 gnm_style_ref (mstyle);
1408 sheet_apply_style (sheet, &rng, mstyle);
1409 c = c2 + 1;
1412 if (cols > old_cols) {
1414 * Expanded in both directions. One could argue about
1415 * what style to use down here, but we choose the
1416 * last column style.
1418 GnmStyle *mstyle = common_col_styles[old_cols - 1];
1419 GnmRange rng;
1421 range_init (&rng,
1422 old_cols, old_rows,
1423 cols - 1, rows - 1);
1424 gnm_style_ref (mstyle);
1425 sheet_apply_style (sheet, &rng, mstyle);
1428 for (c = 0; c < old_cols; c++)
1429 gnm_style_unref (common_col_styles[c]);
1430 g_free (common_col_styles);
1433 /* ---------------------------------------- */
1435 sheet_redraw_all (sheet, TRUE);
1436 return;
1438 handle_error:
1439 if (pundo) {
1440 go_undo_undo_with_data (*pundo, cc);
1441 g_object_unref (*pundo);
1442 *pundo = NULL;
1447 * gnm_sheet_resize:
1448 * @sheet: #Sheet
1449 * @cols: the new columns number.
1450 * @rows: the new rows number.
1451 * @cc: #GOCmdContext.
1452 * @perr: will be %TRUE on error.
1454 * Returns: (transfer full): the newly allocated #GOUndo.
1456 GOUndo *
1457 gnm_sheet_resize (Sheet *sheet, int cols, int rows,
1458 GOCmdContext *cc, gboolean *perr)
1460 GOUndo *undo = NULL;
1462 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1463 g_return_val_if_fail (gnm_sheet_valid_size (cols, rows), NULL);
1465 if (cols < sheet->size.max_cols || rows < sheet->size.max_rows) {
1466 GSList *overlap, *l;
1467 gboolean bad = FALSE;
1468 GnmRange r;
1470 r.start.col = r.start.row = 0;
1471 r.end.col = MIN (cols, sheet->size.max_cols) - 1;
1472 r.end.row = MIN (rows, sheet->size.max_rows) - 1;
1474 overlap = gnm_sheet_merge_get_overlap (sheet, &r);
1475 for (l = overlap; l && !bad; l = l->next) {
1476 GnmRange const *m = l->data;
1477 if (!range_contained (m, &r)) {
1478 bad = TRUE;
1479 gnm_cmd_context_error_splits_merge (cc, m);
1482 g_slist_free (overlap);
1483 if (bad) {
1484 *perr = TRUE;
1485 return NULL;
1489 gnm_sheet_resize_main (sheet, cols, rows, cc, &undo);
1491 *perr = FALSE;
1492 return undo;
1497 * sheet_new_with_type:
1498 * @wb: #Workbook
1499 * @name: An unquoted name
1500 * @type: @GnmSheetType
1501 * @columns: The number of columns for the sheet
1502 * @rows: The number of rows for the sheet
1504 * Create a new Sheet of type @type, and associate it with @wb.
1505 * The type cannot be changed later.
1506 * Returns: (transfer full): the newly allocated sheet.
1508 Sheet *
1509 sheet_new_with_type (Workbook *wb, char const *name, GnmSheetType type,
1510 int columns, int rows)
1512 Sheet *sheet;
1514 g_return_val_if_fail (wb != NULL, NULL);
1515 g_return_val_if_fail (name != NULL, NULL);
1516 g_return_val_if_fail (gnm_sheet_valid_size (columns, rows), NULL);
1518 sheet = g_object_new (GNM_SHEET_TYPE,
1519 "workbook", wb,
1520 "sheet-type", type,
1521 "columns", columns,
1522 "rows", rows,
1523 "name", name,
1524 "zoom-factor", gnm_conf_get_core_gui_window_zoom (),
1525 NULL);
1527 if (type == GNM_SHEET_OBJECT)
1528 print_info_set_paper_orientation (sheet->print_info, GTK_PAGE_ORIENTATION_LANDSCAPE);
1530 return sheet;
1534 * sheet_new:
1535 * @wb: #Workbook
1536 * @name: The name for the sheet (unquoted).
1537 * @columns: The requested columns number.
1538 * @rows: The requested rows number.
1540 * Create a new Sheet of type SHEET_DATA, and associate it with @wb.
1541 * The type can not be changed later
1542 * Returns: (transfer full): the newly allocated sheet.
1544 Sheet *
1545 sheet_new (Workbook *wb, char const *name, int columns, int rows)
1547 return sheet_new_with_type (wb, name, GNM_SHEET_DATA, columns, rows);
1550 /****************************************************************************/
1552 void
1553 sheet_redraw_all (Sheet const *sheet, gboolean headers)
1555 /* We potentially do a lot of recalcs as part of this, so make sure
1556 stuff that caches sub-computations see the whole thing instead
1557 of clearing between cells. */
1558 gnm_app_recalc_start ();
1559 SHEET_FOREACH_CONTROL (sheet, view, control,
1560 sc_redraw_all (control, headers););
1561 gnm_app_recalc_finish ();
1564 static GnmValue *
1565 cb_clear_rendered_values (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
1567 gnm_cell_unrender (iter->cell);
1568 return NULL;
1572 * sheet_range_calc_spans:
1573 * @sheet: The sheet,
1574 * @r: the region to update.
1575 * @flags:
1577 * This is used to re-calculate cell dimensions and re-render
1578 * a cell's text. eg. if a format has changed we need to re-render
1579 * the cached version of the rendered text in the cell.
1581 void
1582 sheet_range_calc_spans (Sheet *sheet, GnmRange const *r, GnmSpanCalcFlags flags)
1584 if (flags & GNM_SPANCALC_RE_RENDER)
1585 sheet_foreach_cell_in_range
1586 (sheet, CELL_ITER_IGNORE_NONEXISTENT, r,
1587 cb_clear_rendered_values, NULL);
1588 sheet_queue_respan (sheet, r->start.row, r->end.row);
1590 /* Redraw the new region in case the span changes */
1591 sheet_redraw_range (sheet, r);
1594 static void
1595 sheet_redraw_partial_row (Sheet const *sheet, int const row,
1596 int const start_col, int const end_col)
1598 GnmRange r;
1599 range_init (&r, start_col, row, end_col, row);
1600 SHEET_FOREACH_CONTROL (sheet, view, control,
1601 sc_redraw_range (control, &r););
1604 static void
1605 sheet_redraw_cell (GnmCell const *cell)
1607 CellSpanInfo const * span;
1608 int start_col, end_col, row;
1609 GnmRange const *merged;
1610 Sheet *sheet;
1611 ColRowInfo *ri;
1613 g_return_if_fail (cell != NULL);
1615 sheet = cell->base.sheet;
1616 merged = gnm_sheet_merge_is_corner (sheet, &cell->pos);
1617 if (merged != NULL) {
1618 SHEET_FOREACH_CONTROL (sheet, view, control,
1619 sc_redraw_range (control, merged););
1620 return;
1623 row = cell->pos.row;
1624 start_col = end_col = cell->pos.col;
1625 ri = sheet_row_get (sheet, row);
1626 span = row_span_get (ri, start_col);
1628 if (span) {
1629 start_col = span->left;
1630 end_col = span->right;
1633 sheet_redraw_partial_row (sheet, row, start_col, end_col);
1636 static void
1637 sheet_cell_calc_span (GnmCell *cell, GnmSpanCalcFlags flags)
1639 CellSpanInfo const * span;
1640 int left, right;
1641 int min_col, max_col, row;
1642 gboolean render = (flags & GNM_SPANCALC_RE_RENDER) != 0;
1643 gboolean const resize = (flags & GNM_SPANCALC_RESIZE) != 0;
1644 gboolean existing = FALSE;
1645 GnmRange const *merged;
1646 Sheet *sheet;
1647 ColRowInfo *ri;
1649 g_return_if_fail (cell != NULL);
1651 sheet = cell->base.sheet;
1652 row = cell->pos.row;
1654 /* Render & Size any unrendered cells */
1655 if ((flags & GNM_SPANCALC_RENDER) && gnm_cell_get_rendered_value (cell) == NULL)
1656 render = TRUE;
1658 if (render) {
1659 if (!gnm_cell_has_expr (cell))
1660 gnm_cell_render_value ((GnmCell *)cell, TRUE);
1661 else
1662 gnm_cell_unrender (cell);
1663 } else if (resize) {
1664 /* FIXME: what was wanted here? */
1665 /* rendered_value_calc_size (cell); */
1668 /* Is there an existing span ? clear it BEFORE calculating new one */
1669 ri = sheet_row_get (sheet, row);
1670 span = row_span_get (ri, cell->pos.col);
1671 if (span != NULL) {
1672 GnmCell const * const other = span->cell;
1674 min_col = span->left;
1675 max_col = span->right;
1677 /* A different cell used to span into this cell, respan that */
1678 if (cell != other) {
1679 int other_left, other_right;
1681 cell_unregister_span (other);
1682 cell_calc_span (other, &other_left, &other_right);
1683 if (min_col > other_left)
1684 min_col = other_left;
1685 if (max_col < other_right)
1686 max_col = other_right;
1688 if (other_left != other_right)
1689 cell_register_span (other, other_left, other_right);
1690 } else
1691 existing = TRUE;
1692 } else
1693 min_col = max_col = cell->pos.col;
1695 merged = gnm_sheet_merge_is_corner (sheet, &cell->pos);
1696 if (NULL != merged) {
1697 if (existing) {
1698 if (min_col > merged->start.col)
1699 min_col = merged->start.col;
1700 if (max_col < merged->end.col)
1701 max_col = merged->end.col;
1702 } else {
1703 sheet_redraw_cell (cell);
1704 return;
1706 } else {
1707 /* Calculate the span of the cell */
1708 cell_calc_span (cell, &left, &right);
1709 if (min_col > left)
1710 min_col = left;
1711 if (max_col < right)
1712 max_col = right;
1714 /* This cell already had an existing span */
1715 if (existing) {
1716 /* If it changed, remove the old one */
1717 if (left != span->left || right != span->right)
1718 cell_unregister_span (cell);
1719 else
1720 /* unchanged, short curcuit adding the span again */
1721 left = right;
1724 if (left != right)
1725 cell_register_span (cell, left, right);
1728 sheet_redraw_partial_row (sheet, row, min_col, max_col);
1732 * sheet_apply_style: (skip)
1733 * @sheet: the sheet in which can be found
1734 * @range: the range to which should be applied
1735 * @style: (transfer full): A #GnmStyle partial style
1737 * A mid level routine that applies the supplied partial style @style to the
1738 * target @range and performs the necessary respanning and redrawing.
1740 void
1741 sheet_apply_style (Sheet *sheet,
1742 GnmRange const *range,
1743 GnmStyle *style)
1745 GnmSpanCalcFlags spanflags = gnm_style_required_spanflags (style);
1746 sheet_style_apply_range (sheet, range, style);
1747 /* This also redraws the range: */
1748 sheet_range_calc_spans (sheet, range, spanflags);
1752 * sheet_apply_style_gi: (rename-to sheet_apply_style)
1753 * @sheet: the sheet in which can be found
1754 * @range: the range to which should be applied
1755 * @style: A #GnmStyle partial style
1757 * A mid level routine that applies the supplied partial style @style to the
1758 * target @range and performs the necessary respanning and redrawing.
1760 void
1761 sheet_apply_style_gi (Sheet *sheet, GnmRange const *range, GnmStyle *style)
1763 GnmSpanCalcFlags spanflags = gnm_style_required_spanflags (style);
1764 gnm_style_ref (style);
1765 sheet_style_apply_range (sheet, range, style);
1766 /* This also redraws the range: */
1767 sheet_range_calc_spans (sheet, range, spanflags);
1770 static void
1771 sheet_apply_style_cb (GnmSheetRange *sr,
1772 GnmStyle *style)
1774 gnm_style_ref (style);
1775 sheet_apply_style (sr->sheet, &sr->range, style);
1776 sheet_flag_style_update_range (sr->sheet, &sr->range);
1780 * sheet_apply_style_undo:
1781 * @sr: #GnmSheetRange
1782 * @style: #GnmStyle
1784 * Returns: (transfer full): the new #GOUndo.
1786 GOUndo *
1787 sheet_apply_style_undo (GnmSheetRange *sr,
1788 GnmStyle *style)
1790 gnm_style_ref (style);
1791 return go_undo_binary_new
1792 (sr, (gpointer)style,
1793 (GOUndoBinaryFunc) sheet_apply_style_cb,
1794 (GFreeFunc) gnm_sheet_range_free,
1795 (GFreeFunc) gnm_style_unref);
1800 void
1801 sheet_apply_border (Sheet *sheet,
1802 GnmRange const *range,
1803 GnmBorder **borders)
1805 GnmSpanCalcFlags spanflags = GNM_SPANCALC_RE_RENDER | GNM_SPANCALC_RESIZE;
1806 sheet_style_apply_border (sheet, range, borders);
1807 /* This also redraws the range: */
1808 sheet_range_calc_spans (sheet, range, spanflags);
1811 /****************************************************************************/
1813 static ColRowInfo *
1814 sheet_row_new (Sheet *sheet)
1816 ColRowInfo *ri;
1818 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1820 ri = col_row_info_new ();
1821 *ri = sheet->rows.default_style;
1822 ri->is_default = FALSE;
1823 ri->needs_respan = TRUE;
1825 return ri;
1828 static ColRowInfo *
1829 sheet_col_new (Sheet *sheet)
1831 ColRowInfo *ci;
1833 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1835 ci = col_row_info_new ();
1836 *ci = sheet->cols.default_style;
1837 ci->is_default = FALSE;
1839 return ci;
1842 static void
1843 sheet_colrow_add (Sheet *sheet, ColRowInfo *cp, gboolean is_cols, int n)
1845 ColRowCollection *info = is_cols ? &sheet->cols : &sheet->rows;
1846 ColRowSegment **psegment = (ColRowSegment **)&COLROW_GET_SEGMENT (info, n);
1848 g_return_if_fail (n >= 0);
1849 g_return_if_fail (n < colrow_max (is_cols, sheet));
1851 if (*psegment == NULL)
1852 *psegment = g_new0 (ColRowSegment, 1);
1853 colrow_free ((*psegment)->info[COLROW_SUB_INDEX (n)]);
1854 (*psegment)->info[COLROW_SUB_INDEX (n)] = cp;
1856 if (cp->outline_level > info->max_outline_level)
1857 info->max_outline_level = cp->outline_level;
1858 if (n > info->max_used) {
1859 info->max_used = n;
1860 sheet->priv->resize_scrollbar = TRUE;
1864 static void
1865 sheet_reposition_objects (Sheet const *sheet, GnmCellPos const *pos)
1867 GSList *ptr;
1868 for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = ptr->next )
1869 sheet_object_update_bounds (GNM_SO (ptr->data), pos);
1873 * sheet_flag_status_update_cell:
1874 * @cell: The cell that has changed.
1876 * flag the sheet as requiring an update to the status display
1877 * if the supplied cell location is the edit cursor, or part of the
1878 * selected region.
1880 * Will cause the format toolbar, the edit area, and the auto expressions to be
1881 * updated if appropriate.
1883 void
1884 sheet_flag_status_update_cell (GnmCell const *cell)
1886 SHEET_FOREACH_VIEW (cell->base.sheet, sv,
1887 gnm_sheet_view_flag_status_update_pos (sv, &cell->pos););
1891 * sheet_flag_status_update_range:
1892 * @sheet:
1893 * @range: If NULL then force an update.
1895 * flag the sheet as requiring an update to the status display
1896 * if the supplied cell location contains the edit cursor, or intersects of
1897 * the selected region.
1899 * Will cause the format toolbar, the edit area, and the auto expressions to be
1900 * updated if appropriate.
1902 void
1903 sheet_flag_status_update_range (Sheet const *sheet, GnmRange const *range)
1905 SHEET_FOREACH_VIEW (sheet, sv,
1906 gnm_sheet_view_flag_status_update_range (sv, range););
1910 * sheet_flag_style_update_range:
1911 * @sheet: The sheet being changed
1912 * @range: the range that is changing.
1914 * Flag format changes that will require updating the format indicators.
1916 void
1917 sheet_flag_style_update_range (Sheet const *sheet, GnmRange const *range)
1919 SHEET_FOREACH_VIEW (sheet, sv,
1920 gnm_sheet_view_flag_style_update_range (sv, range););
1924 * sheet_flag_recompute_spans:
1925 * @sheet:
1927 * Flag the sheet as requiring a full span recomputation the next time
1928 * sheet_update is called.
1930 void
1931 sheet_flag_recompute_spans (Sheet const *sheet)
1933 sheet->priv->recompute_spans = TRUE;
1936 static gboolean
1937 cb_outline_level (GnmColRowIter const *iter, gpointer data)
1939 int *outline_level = data;
1940 if (*outline_level < iter->cri->outline_level)
1941 *outline_level = iter->cri->outline_level;
1942 return FALSE;
1946 * sheet_colrow_fit_gutter:
1947 * @sheet: Sheet to change for.
1948 * @is_cols: Column gutter or row gutter?
1950 * Find the current max outline level.
1952 static int
1953 sheet_colrow_fit_gutter (Sheet const *sheet, gboolean is_cols)
1955 int outline_level = 0;
1956 sheet_colrow_foreach (sheet, is_cols, 0, -1,
1957 cb_outline_level, &outline_level);
1958 return outline_level;
1962 * sheet_update_only_grid:
1963 * @sheet: #Sheet
1965 * Should be called after a logical command has finished processing
1966 * to request redraws for any pending events
1968 void
1969 sheet_update_only_grid (Sheet const *sheet)
1971 SheetPrivate *p;
1973 g_return_if_fail (IS_SHEET (sheet));
1975 p = sheet->priv;
1977 /* be careful these can toggle flags */
1978 if (p->recompute_max_col_group) {
1979 sheet_colrow_gutter ((Sheet *)sheet, TRUE,
1980 sheet_colrow_fit_gutter (sheet, TRUE));
1981 sheet->priv->recompute_max_col_group = FALSE;
1983 if (p->recompute_max_row_group) {
1984 sheet_colrow_gutter ((Sheet *)sheet, FALSE,
1985 sheet_colrow_fit_gutter (sheet, FALSE));
1986 sheet->priv->recompute_max_row_group = FALSE;
1989 SHEET_FOREACH_VIEW (sheet, sv, {
1990 if (sv->reposition_selection) {
1991 sv->reposition_selection = FALSE;
1993 /* when moving we cleared the selection before
1994 * arriving in here.
1996 if (sv->selections != NULL)
1997 sv_selection_set (sv, &sv->edit_pos_real,
1998 sv->cursor.base_corner.col,
1999 sv->cursor.base_corner.row,
2000 sv->cursor.move_corner.col,
2001 sv->cursor.move_corner.row);
2005 if (p->recompute_spans) {
2006 p->recompute_spans = FALSE;
2007 /* FIXME : I would prefer to use GNM_SPANCALC_RENDER rather than
2008 * RE_RENDER. It only renders those cells which are not
2009 * rendered. The trouble is that when a col changes size we
2010 * need to rerender, but currently nothing marks that.
2012 * hmm, that suggests an approach. maybe I can install a per
2013 * col flag. Then add a flag clearing loop after the
2014 * sheet_calc_span.
2016 #if 0
2017 sheet_calc_spans (sheet, GNM_SPANCALC_RESIZE|GNM_SPANCALC_RE_RENDER |
2018 (p->recompute_visibility ?
2019 SPANCALC_NO_DRAW : GNM_SPANCALC_SIMPLE));
2020 #endif
2021 sheet_queue_respan (sheet, 0, gnm_sheet_get_last_row (sheet));
2024 if (p->reposition_objects.row < gnm_sheet_get_max_rows (sheet) ||
2025 p->reposition_objects.col < gnm_sheet_get_max_cols (sheet)) {
2026 SHEET_FOREACH_VIEW (sheet, sv, {
2027 if (!p->resize && gnm_sheet_view_is_frozen (sv)) {
2028 if (p->reposition_objects.col < sv->unfrozen_top_left.col ||
2029 p->reposition_objects.row < sv->unfrozen_top_left.row) {
2030 gnm_sheet_view_resize (sv, FALSE);
2034 sheet_reposition_objects (sheet, &p->reposition_objects);
2035 p->reposition_objects.row = gnm_sheet_get_max_rows (sheet);
2036 p->reposition_objects.col = gnm_sheet_get_max_cols (sheet);
2039 if (p->resize) {
2040 p->resize = FALSE;
2041 SHEET_FOREACH_VIEW (sheet, sv, { gnm_sheet_view_resize (sv, FALSE); });
2044 if (p->recompute_visibility) {
2045 /* TODO : There is room for some opimization
2046 * We only need to force complete visibility recalculation
2047 * (which we do in sheet_compute_visible_region)
2048 * if a row or col before the start of the visible region.
2049 * If we are REALLY smart we could even accumulate the size differential
2050 * and use that.
2052 p->recompute_visibility = FALSE;
2053 p->resize_scrollbar = FALSE; /* compute_visible_region does this */
2054 SHEET_FOREACH_CONTROL(sheet, view, control,
2055 sc_recompute_visible_region (control, TRUE););
2056 sheet_redraw_all (sheet, TRUE);
2059 if (p->resize_scrollbar) {
2060 sheet_scrollbar_config (sheet);
2061 p->resize_scrollbar = FALSE;
2063 if (p->filters_changed) {
2064 p->filters_changed = FALSE;
2065 SHEET_FOREACH_CONTROL (sheet, sv, sc,
2066 wb_control_menu_state_update (sc_wbc (sc), MS_ADD_VS_REMOVE_FILTER););
2071 * sheet_update:
2072 * @sheet: #Sheet
2074 * Should be called after a logical command has finished processing to request
2075 * redraws for any pending events, and to update the various status regions
2077 void
2078 sheet_update (Sheet const *sheet)
2080 g_return_if_fail (IS_SHEET (sheet));
2082 sheet_update_only_grid (sheet);
2084 SHEET_FOREACH_VIEW (sheet, sv, gnm_sheet_view_update (sv););
2088 * sheet_cell_get:
2089 * @sheet: The sheet where we want to locate the cell
2090 * @col: the cell column
2091 * @row: the cell row
2093 * Return value: (nullable): a #GnmCell, or %NULL if the cell does not exist
2095 GnmCell *
2096 sheet_cell_get (Sheet const *sheet, int col, int row)
2098 GnmCell *cell;
2099 GnmCell key;
2101 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2103 key.pos.col = col;
2104 key.pos.row = row;
2105 cell = g_hash_table_lookup (sheet->cell_hash, &key);
2107 return cell;
2111 * sheet_cell_fetch:
2112 * @sheet: The sheet where we want to locate the cell
2113 * @col: the cell column
2114 * @row: the cell row
2116 * Return value: a #GnmCell containing at (@col,@row).
2117 * If no cell existed at that location before, it is created.
2119 GnmCell *
2120 sheet_cell_fetch (Sheet *sheet, int col, int row)
2122 GnmCell *cell;
2124 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2126 cell = sheet_cell_get (sheet, col, row);
2127 if (!cell)
2128 cell = sheet_cell_create (sheet, col, row);
2130 return cell;
2134 * sheet_colrow_can_group:
2135 * @sheet: #Sheet
2136 * @r: A #GnmRange
2137 * @is_cols: boolean
2139 * Returns TRUE if the cols/rows in @r.start -> @r.end can be grouped, return
2140 * FALSE otherwise. You can invert the result if you need to find out if a
2141 * group can be ungrouped.
2143 gboolean
2144 sheet_colrow_can_group (Sheet *sheet, GnmRange const *r, gboolean is_cols)
2146 ColRowInfo const *start_cri, *end_cri;
2147 int start, end;
2149 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
2151 if (is_cols) {
2152 start = r->start.col;
2153 end = r->end.col;
2154 } else {
2155 start = r->start.row;
2156 end = r->end.row;
2158 start_cri = sheet_colrow_fetch (sheet, start, is_cols);
2159 end_cri = sheet_colrow_fetch (sheet, end, is_cols);
2161 /* Groups on outline level 0 (no outline) may always be formed */
2162 if (start_cri->outline_level == 0 || end_cri->outline_level == 0)
2163 return TRUE;
2165 /* We just won't group a group that already exists (or doesn't), it's useless */
2166 return (colrow_find_outline_bound (sheet, is_cols, start, start_cri->outline_level, FALSE) != start ||
2167 colrow_find_outline_bound (sheet, is_cols, end, end_cri->outline_level, TRUE) != end);
2170 gboolean
2171 sheet_colrow_group_ungroup (Sheet *sheet, GnmRange const *r,
2172 gboolean is_cols, gboolean group)
2174 int i, new_max, start, end;
2175 int const step = group ? 1 : -1;
2177 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
2179 /* Can we group/ungroup ? */
2180 if (group != sheet_colrow_can_group (sheet, r, is_cols))
2181 return FALSE;
2183 if (is_cols) {
2184 start = r->start.col;
2185 end = r->end.col;
2186 } else {
2187 start = r->start.row;
2188 end = r->end.row;
2191 /* Set new outline for each col/row and find highest outline level */
2192 new_max = (is_cols ? &sheet->cols : &sheet->rows)->max_outline_level;
2193 for (i = start; i <= end; i++) {
2194 ColRowInfo *cri = sheet_colrow_fetch (sheet, i, is_cols);
2195 int const new_level = cri->outline_level + step;
2197 if (new_level >= 0) {
2198 col_row_info_set_outline (cri, new_level, FALSE);
2199 if (new_max < new_level)
2200 new_max = new_level;
2204 if (!group)
2205 new_max = sheet_colrow_fit_gutter (sheet, is_cols);
2207 sheet_colrow_gutter (sheet, is_cols, new_max);
2208 SHEET_FOREACH_VIEW (sheet, sv,
2209 gnm_sheet_view_redraw_headers (sv, is_cols, !is_cols, NULL););
2211 return TRUE;
2215 * sheet_colrow_gutter:
2216 * @sheet:
2217 * @is_cols:
2218 * @max_outline:
2220 * Set the maximum outline levels for cols or rows.
2222 void
2223 sheet_colrow_gutter (Sheet *sheet, gboolean is_cols, int max_outline)
2225 ColRowCollection *infos;
2227 g_return_if_fail (IS_SHEET (sheet));
2229 infos = is_cols ? &(sheet->cols) : &(sheet->rows);
2230 if (infos->max_outline_level != max_outline) {
2231 sheet->priv->resize = TRUE;
2232 infos->max_outline_level = max_outline;
2236 struct sheet_extent_data {
2237 GnmRange range;
2238 gboolean spans_and_merges_extend;
2239 gboolean ignore_empties;
2240 gboolean include_hidden;
2243 static void
2244 cb_sheet_get_extent (G_GNUC_UNUSED gpointer ignored, gpointer value, gpointer data)
2246 GnmCell const *cell = (GnmCell const *) value;
2247 struct sheet_extent_data *res = data;
2248 Sheet *sheet = cell->base.sheet;
2249 ColRowInfo *ri = NULL;
2251 if (res->ignore_empties && gnm_cell_is_empty (cell))
2252 return;
2253 if (!res->include_hidden) {
2254 ri = sheet_col_get (sheet, cell->pos.col);
2255 if (!ri->visible)
2256 return;
2257 ri = sheet_row_get (sheet, cell->pos.row);
2258 if (!ri->visible)
2259 return;
2262 /* Remember the first cell is the min & max */
2263 if (res->range.start.col > cell->pos.col)
2264 res->range.start.col = cell->pos.col;
2265 if (res->range.end.col < cell->pos.col)
2266 res->range.end.col = cell->pos.col;
2267 if (res->range.start.row > cell->pos.row)
2268 res->range.start.row = cell->pos.row;
2269 if (res->range.end.row < cell->pos.row)
2270 res->range.end.row = cell->pos.row;
2272 if (!res->spans_and_merges_extend)
2273 return;
2275 /* Cannot span AND merge */
2276 if (gnm_cell_is_merged (cell)) {
2277 GnmRange const *merged =
2278 gnm_sheet_merge_is_corner (sheet, &cell->pos);
2279 res->range = range_union (&res->range, merged);
2280 } else {
2281 CellSpanInfo const *span;
2282 if (ri == NULL)
2283 ri = sheet_row_get (sheet, cell->pos.row);
2284 if (ri->needs_respan)
2285 row_calc_spans (ri, cell->pos.row, sheet);
2286 span = row_span_get (ri, cell->pos.col);
2287 if (NULL != span) {
2288 if (res->range.start.col > span->left)
2289 res->range.start.col = span->left;
2290 if (res->range.end.col < span->right)
2291 res->range.end.col = span->right;
2297 * sheet_get_extent:
2298 * @sheet: the sheet
2299 * @spans_and_merges_extend: optionally extend region for spans and merges.
2300 * @include_hidden: whether to include the content of hidden cells.
2302 * calculates the area occupied by cell data.
2304 * NOTE: When spans_and_merges_extend is TRUE, this function will calculate
2305 * all spans. That might be expensive.
2307 * NOTE: This refers to *visible* contents. Cells with empty values, including
2308 * formulas with such values, are *ignored.
2310 * Return value: the range.
2312 GnmRange
2313 sheet_get_extent (Sheet const *sheet, gboolean spans_and_merges_extend, gboolean include_hidden)
2315 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2316 struct sheet_extent_data closure;
2317 GSList *ptr;
2319 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2321 closure.range.start.col = gnm_sheet_get_last_col (sheet) + 1;
2322 closure.range.start.row = gnm_sheet_get_last_row (sheet) + 1;
2323 closure.range.end.col = 0;
2324 closure.range.end.row = 0;
2325 closure.spans_and_merges_extend = spans_and_merges_extend;
2326 closure.include_hidden = include_hidden;
2327 closure.ignore_empties = TRUE;
2329 sheet_cell_foreach (sheet, &cb_sheet_get_extent, &closure);
2331 for (ptr = sheet->sheet_objects; ptr; ptr = ptr->next) {
2332 SheetObject *so = GNM_SO (ptr->data);
2334 closure.range.start.col = MIN (so->anchor.cell_bound.start.col,
2335 closure.range.start.col);
2336 closure.range.start.row = MIN (so->anchor.cell_bound.start.row,
2337 closure.range.start.row);
2338 closure.range.end.col = MAX (so->anchor.cell_bound.end.col,
2339 closure.range.end.col);
2340 closure.range.end.row = MAX (so->anchor.cell_bound.end.row,
2341 closure.range.end.row);
2344 if (closure.range.start.col > gnm_sheet_get_last_col (sheet))
2345 closure.range.start.col = 0;
2346 if (closure.range.start.row > gnm_sheet_get_last_row (sheet))
2347 closure.range.start.row = 0;
2348 if (closure.range.end.col < 0)
2349 closure.range.end.col = 0;
2350 if (closure.range.end.row < 0)
2351 closure.range.end.row = 0;
2353 return closure.range;
2357 * sheet_get_cells_extent:
2358 * @sheet: the sheet
2360 * calculates the area occupied by cells, including empty cells.
2362 * Return value: the range.
2364 GnmRange
2365 sheet_get_cells_extent (Sheet const *sheet)
2367 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2368 struct sheet_extent_data closure;
2370 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2372 closure.range.start.col = gnm_sheet_get_last_col (sheet);
2373 closure.range.start.row = gnm_sheet_get_last_row (sheet);
2374 closure.range.end.col = 0;
2375 closure.range.end.row = 0;
2376 closure.spans_and_merges_extend = FALSE;
2377 closure.include_hidden = TRUE;
2378 closure.ignore_empties = FALSE;
2380 sheet_cell_foreach (sheet, &cb_sheet_get_extent, &closure);
2382 return closure.range;
2386 GnmRange *
2387 sheet_get_nominal_printarea (Sheet const *sheet)
2389 GnmNamedExpr *nexpr;
2390 GnmValue *val;
2391 GnmParsePos pos;
2392 GnmRange *r;
2393 GnmRangeRef const *r_ref;
2394 gint max_rows;
2395 gint max_cols;
2397 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2399 parse_pos_init_sheet (&pos, sheet);
2400 nexpr = expr_name_lookup (&pos, "Print_Area");
2401 if (nexpr == NULL)
2402 return NULL;
2404 val = gnm_expr_top_get_range (nexpr->texpr);
2405 r_ref = val ? value_get_rangeref (val) : NULL;
2406 if (r_ref == NULL) {
2407 value_release (val);
2408 return NULL;
2411 r = g_new0 (GnmRange, 1);
2412 range_init_rangeref (r, r_ref);
2413 value_release (val);
2415 if (r->end.col >= (max_cols = gnm_sheet_get_max_cols (sheet)))
2416 r->end.col = max_cols - 1;
2417 if (r->end.row >= (max_rows = gnm_sheet_get_max_rows (sheet)))
2418 r->end.row = max_rows - 1;
2419 if (r->start.col < 0)
2420 r->start.col = 0;
2421 if (r->start.row < 0)
2422 r->start.row = 0;
2424 return r;
2427 GnmRange
2428 sheet_get_printarea (Sheet const *sheet,
2429 gboolean include_styles,
2430 gboolean ignore_printarea)
2432 static GnmRange const dummy = { { 0,0 }, { 0,0 } };
2433 GnmRange print_area;
2435 g_return_val_if_fail (IS_SHEET (sheet), dummy);
2437 if (!ignore_printarea) {
2438 GnmRange *r = sheet_get_nominal_printarea (sheet);
2439 if (r != NULL) {
2440 print_area = *r;
2441 g_free (r);
2442 return print_area;
2446 print_area = sheet_get_extent (sheet, TRUE, FALSE);
2447 if (include_styles)
2448 sheet_style_get_extent (sheet, &print_area);
2450 return print_area;
2453 struct cb_fit {
2454 int max;
2455 gboolean ignore_strings;
2458 /* find the maximum width in a range. */
2459 static GnmValue *
2460 cb_max_cell_width (GnmCellIter const *iter, struct cb_fit *data)
2462 int width;
2463 GnmCell *cell = iter->cell;
2464 GnmRenderedValue *rv;
2466 if (gnm_cell_is_merged (cell))
2467 return NULL;
2470 * Special handling for manual recalc. We need to eval newly
2471 * entered expressions. gnm_cell_render_value will do that for us,
2472 * but we want to short-circuit some strings early.
2474 if (cell->base.flags & GNM_CELL_HAS_NEW_EXPR)
2475 gnm_cell_eval (cell);
2477 if (data->ignore_strings && VALUE_IS_STRING (cell->value))
2478 return NULL;
2480 /* Variable width cell must be re-rendered */
2481 rv = gnm_cell_get_rendered_value (cell);
2482 if (rv == NULL || rv->variable_width)
2483 gnm_cell_render_value (cell, FALSE);
2485 /* Make sure things are as-if drawn. */
2486 cell_finish_layout (cell, NULL, iter->ci->size_pixels, TRUE);
2488 width = gnm_cell_rendered_width (cell) + gnm_cell_rendered_offset (cell);
2489 if (width > data->max)
2490 data->max = width;
2492 return NULL;
2496 * sheet_col_size_fit_pixels:
2497 * @sheet: The sheet
2498 * @col: the column that we want to query
2499 * @srow: starting row.
2500 * @erow: ending row.
2501 * @ignore_strings: skip cells containing string values.
2503 * This routine computes the ideal size for the column to make the contents all
2504 * cells in the column visible.
2506 * Returns: Maximum size in pixels INCLUDING margins and grid lines
2507 * or 0 if there are no cells.
2510 sheet_col_size_fit_pixels (Sheet *sheet, int col, int srow, int erow,
2511 gboolean ignore_strings)
2513 struct cb_fit data;
2514 ColRowInfo *ci = sheet_col_get (sheet, col);
2515 if (ci == NULL)
2516 return 0;
2518 data.max = -1;
2519 data.ignore_strings = ignore_strings;
2520 sheet_foreach_cell_in_region (sheet,
2521 CELL_ITER_IGNORE_NONEXISTENT |
2522 CELL_ITER_IGNORE_HIDDEN |
2523 CELL_ITER_IGNORE_FILTERED,
2524 col, srow, col, erow,
2525 (CellIterFunc)&cb_max_cell_width, &data);
2527 /* Reset to the default width if the column was empty */
2528 if (data.max <= 0)
2529 return 0;
2531 /* GnmCell width does not include margins or far grid line*/
2532 return data.max + GNM_COL_MARGIN + GNM_COL_MARGIN + 1;
2535 /* find the maximum height in a range. */
2536 static GnmValue *
2537 cb_max_cell_height (GnmCellIter const *iter, struct cb_fit *data)
2539 int height;
2540 GnmCell *cell = iter->cell;
2542 if (gnm_cell_is_merged (cell))
2543 return NULL;
2546 * Special handling for manual recalc. We need to eval newly
2547 * entered expressions. gnm_cell_render_value will do that for us,
2548 * but we want to short-circuit some strings early.
2550 if (cell->base.flags & GNM_CELL_HAS_NEW_EXPR)
2551 gnm_cell_eval (cell);
2553 if (data->ignore_strings && VALUE_IS_STRING (cell->value))
2554 return NULL;
2556 if (!VALUE_IS_STRING (cell->value)) {
2558 * Mildly cheating to avoid performance problems, See bug
2559 * 359392. This assumes that non-strings do not wrap and
2560 * that they are all the same height, more or less.
2562 Sheet const *sheet = cell->base.sheet;
2563 height = gnm_style_get_pango_height (gnm_cell_get_style (cell),
2564 sheet->rendered_values->context,
2565 sheet->last_zoom_factor_used);
2566 } else {
2567 (void)gnm_cell_fetch_rendered_value (cell, TRUE);
2569 /* Make sure things are as-if drawn. Inhibit #####s. */
2570 cell_finish_layout (cell, NULL, iter->ci->size_pixels, FALSE);
2572 height = gnm_cell_rendered_height (cell);
2575 if (height > data->max)
2576 data->max = height;
2578 return NULL;
2582 * sheet_row_size_fit_pixels:
2583 * @sheet: The sheet
2584 * @row: the row that we want to query
2585 * @scol: starting column.
2586 * @ecol: ending column.
2587 * @ignore_strings: skip cells containing string values.
2589 * This routine computes the ideal size for the row to make all data fit
2590 * properly.
2592 * Returns: Maximum size in pixels INCLUDING margins and grid lines
2593 * or 0 if there are no cells.
2596 sheet_row_size_fit_pixels (Sheet *sheet, int row, int scol, int ecol,
2597 gboolean ignore_strings)
2599 struct cb_fit data;
2600 ColRowInfo const *ri = sheet_row_get (sheet, row);
2601 if (ri == NULL)
2602 return 0;
2604 data.max = -1;
2605 data.ignore_strings = ignore_strings;
2606 sheet_foreach_cell_in_region (sheet,
2607 CELL_ITER_IGNORE_NONEXISTENT |
2608 CELL_ITER_IGNORE_HIDDEN |
2609 CELL_ITER_IGNORE_FILTERED,
2610 scol, row,
2611 ecol, row,
2612 (CellIterFunc)&cb_max_cell_height, &data);
2614 /* Reset to the default width if the column was empty */
2615 if (data.max <= 0)
2616 return 0;
2618 /* GnmCell height does not include margins or bottom grid line */
2619 return data.max + GNM_ROW_MARGIN + GNM_ROW_MARGIN + 1;
2622 struct recalc_span_closure {
2623 Sheet *sheet;
2624 int col;
2627 static gboolean
2628 cb_recalc_spans_in_col (GnmColRowIter const *iter, gpointer user)
2630 struct recalc_span_closure *closure = user;
2631 int const col = closure->col;
2632 int left, right;
2633 CellSpanInfo const *span = row_span_get (iter->cri, col);
2635 if (span) {
2636 /* If there is an existing span see if it changed */
2637 GnmCell const * const cell = span->cell;
2638 cell_calc_span (cell, &left, &right);
2639 if (left != span->left || right != span->right) {
2640 cell_unregister_span (cell);
2641 cell_register_span (cell, left, right);
2643 } else {
2644 /* If there is a cell see if it started to span */
2645 GnmCell const * const cell = sheet_cell_get (closure->sheet, col, iter->pos);
2646 if (cell) {
2647 cell_calc_span (cell, &left, &right);
2648 if (left != right)
2649 cell_register_span (cell, left, right);
2653 return FALSE;
2657 * sheet_recompute_spans_for_col:
2658 * @sheet: the sheet
2659 * @col: The column that changed
2661 * This routine recomputes the column span for the cells that touches
2662 * the column.
2664 void
2665 sheet_recompute_spans_for_col (Sheet *sheet, int col)
2667 struct recalc_span_closure closure;
2668 closure.sheet = sheet;
2669 closure.col = col;
2671 sheet_colrow_foreach (sheet, FALSE, 0, -1,
2672 &cb_recalc_spans_in_col, &closure);
2675 /****************************************************************************/
2676 typedef struct {
2677 GnmValue *val;
2678 GnmExprTop const *texpr;
2679 GnmRange expr_bound;
2680 } closure_set_cell_value;
2682 static GnmValue *
2683 cb_set_cell_content (GnmCellIter const *iter, closure_set_cell_value *info)
2685 GnmExprTop const *texpr = info->texpr;
2686 GnmCell *cell;
2688 cell = iter->cell;
2689 if (!cell)
2690 cell = sheet_cell_create (iter->pp.sheet,
2691 iter->pp.eval.col,
2692 iter->pp.eval.row);
2695 * If we are overwriting an array, we need to clear things here
2696 * or gnm_cell_set_expr/gnm_cell_set_value will complain.
2698 if (cell->base.texpr && gnm_expr_top_is_array (cell->base.texpr))
2699 gnm_cell_cleanout (cell);
2701 if (texpr != NULL) {
2702 if (!range_contains (&info->expr_bound,
2703 iter->pp.eval.col, iter->pp.eval.row)) {
2704 GnmExprRelocateInfo rinfo;
2706 rinfo.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE;
2707 rinfo.pos = iter->pp;
2708 rinfo.origin.start = iter->pp.eval;
2709 rinfo.origin.end = iter->pp.eval;
2710 rinfo.origin_sheet = iter->pp.sheet;
2711 rinfo.target_sheet = iter->pp.sheet;
2712 rinfo.col_offset = 0;
2713 rinfo.row_offset = 0;
2714 texpr = gnm_expr_top_relocate (texpr, &rinfo, FALSE);
2717 gnm_cell_set_expr (cell, texpr);
2718 } else
2719 gnm_cell_set_value (cell, value_dup (info->val));
2720 return NULL;
2723 static GnmValue *
2724 cb_clear_non_corner (GnmCellIter const *iter, GnmRange const *merged)
2726 if (merged->start.col != iter->pp.eval.col ||
2727 merged->start.row != iter->pp.eval.row)
2728 gnm_cell_set_value (iter->cell, value_new_empty ());
2729 return NULL;
2733 * sheet_range_set_expr_cb:
2734 * @sr: #GnmSheetRange
2735 * @texpr: #GnmExprTop
2738 * Does NOT check for array division.
2740 static void
2741 sheet_range_set_expr_cb (GnmSheetRange const *sr, GnmExprTop const *texpr)
2743 closure_set_cell_value closure;
2744 GSList *merged, *ptr;
2746 g_return_if_fail (sr != NULL);
2747 g_return_if_fail (texpr != NULL);
2749 closure.texpr = texpr;
2750 gnm_expr_top_get_boundingbox (closure.texpr,
2751 sr->sheet,
2752 &closure.expr_bound);
2754 sheet_region_queue_recalc (sr->sheet, &sr->range);
2755 /* Store the parsed result creating any cells necessary */
2756 sheet_foreach_cell_in_range
2757 (sr->sheet, CELL_ITER_ALL, &sr->range,
2758 (CellIterFunc)&cb_set_cell_content, &closure);
2760 merged = gnm_sheet_merge_get_overlap (sr->sheet, &sr->range);
2761 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
2762 GnmRange const *tmp = ptr->data;
2763 sheet_foreach_cell_in_range
2764 (sr->sheet, CELL_ITER_IGNORE_BLANK, tmp,
2765 (CellIterFunc)&cb_clear_non_corner,
2766 (gpointer)tmp);
2768 g_slist_free (merged);
2770 sheet_region_queue_recalc (sr->sheet, &sr->range);
2771 sheet_flag_status_update_range (sr->sheet, &sr->range);
2772 sheet_queue_respan (sr->sheet, sr->range.start.row,
2773 sr->range.end.row);
2777 * sheet_range_set_expr_undo:
2778 * @sr: #GnmSheetRange
2779 * @texpr: #GnmExprTop
2781 * Returns: (transfer full): the newly created #GOUndo.
2783 GOUndo *
2784 sheet_range_set_expr_undo (GnmSheetRange *sr, GnmExprTop const *texpr)
2786 gnm_expr_top_ref (texpr);
2787 return go_undo_binary_new
2788 (sr, (gpointer)texpr,
2789 (GOUndoBinaryFunc) sheet_range_set_expr_cb,
2790 (GFreeFunc) gnm_sheet_range_free,
2791 (GFreeFunc) gnm_expr_top_unref);
2796 * sheet_range_set_text:
2797 * @pos: The position from which to parse an expression.
2798 * @r: The range to fill
2799 * @str: The text to be parsed and assigned.
2801 * Does NOT check for array division.
2802 * Does NOT redraw
2803 * Does NOT generate spans.
2805 void
2806 sheet_range_set_text (GnmParsePos const *pos, GnmRange const *r, char const *str)
2808 closure_set_cell_value closure;
2809 GSList *merged, *ptr;
2810 Sheet *sheet;
2812 g_return_if_fail (pos != NULL);
2813 g_return_if_fail (r != NULL);
2814 g_return_if_fail (str != NULL);
2816 sheet = pos->sheet;
2818 parse_text_value_or_expr (pos, str,
2819 &closure.val, &closure.texpr);
2821 if (closure.texpr)
2822 gnm_expr_top_get_boundingbox (closure.texpr,
2823 sheet,
2824 &closure.expr_bound);
2826 /* Store the parsed result creating any cells necessary */
2827 sheet_foreach_cell_in_range (sheet, CELL_ITER_ALL, r,
2828 (CellIterFunc)&cb_set_cell_content, &closure);
2830 merged = gnm_sheet_merge_get_overlap (sheet, r);
2831 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
2832 GnmRange const *tmp = ptr->data;
2833 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_BLANK, tmp,
2834 (CellIterFunc)&cb_clear_non_corner, (gpointer)tmp);
2836 g_slist_free (merged);
2838 sheet_region_queue_recalc (sheet, r);
2840 value_release (closure.val);
2841 if (closure.texpr)
2842 gnm_expr_top_unref (closure.texpr);
2844 sheet_flag_status_update_range (sheet, r);
2847 static void
2848 sheet_range_set_text_cb (GnmSheetRange const *sr, gchar const *text)
2850 GnmParsePos pos;
2852 pos.eval = sr->range.start;
2853 pos.sheet = sr->sheet;
2854 pos.wb = sr->sheet->workbook;
2856 sheet_range_set_text (&pos, &sr->range, text);
2857 sheet_region_queue_recalc (sr->sheet, &sr->range);
2858 sheet_flag_status_update_range (sr->sheet, &sr->range);
2859 sheet_queue_respan (sr->sheet, sr->range.start.row,
2860 sr->range.end.row);
2861 sheet_redraw_range (sr->sheet, &sr->range);
2865 * sheet_range_set_text_undo:
2866 * @sr: #GnmSheetRange
2867 * @text:
2869 * Returns: (transfer full): the newly created #GOUndo.
2871 GOUndo *
2872 sheet_range_set_text_undo (GnmSheetRange *sr,
2873 char const *text)
2875 return go_undo_binary_new
2876 (sr, g_strdup (text),
2877 (GOUndoBinaryFunc) sheet_range_set_text_cb,
2878 (GFreeFunc) gnm_sheet_range_free,
2879 (GFreeFunc) g_free);
2883 static GnmValue *
2884 cb_set_markup (GnmCellIter const *iter, PangoAttrList *markup)
2886 GnmCell *cell;
2888 cell = iter->cell;
2889 if (!cell)
2890 return NULL;
2892 if (VALUE_IS_STRING (cell->value)) {
2893 GOFormat *fmt;
2894 GnmValue *val = value_dup (cell->value);
2896 fmt = go_format_new_markup (markup, TRUE);
2897 value_set_fmt (val, fmt);
2898 go_format_unref (fmt);
2900 gnm_cell_cleanout (cell);
2901 gnm_cell_assign_value (cell, val);
2903 return NULL;
2906 static void
2907 sheet_range_set_markup_cb (GnmSheetRange const *sr, PangoAttrList *markup)
2909 sheet_foreach_cell_in_range
2910 (sr->sheet, CELL_ITER_ALL, &sr->range,
2911 (CellIterFunc)&cb_set_markup, markup);
2913 sheet_region_queue_recalc (sr->sheet, &sr->range);
2914 sheet_flag_status_update_range (sr->sheet, &sr->range);
2915 sheet_queue_respan (sr->sheet, sr->range.start.row,
2916 sr->range.end.row);
2920 * sheet_range_set_markup_undo:
2921 * @sr: #GnmSheetRange
2922 * @markup: #PangoAttrList
2924 * Returns: (transfer full): the newly created #GOUndo.
2926 GOUndo *
2927 sheet_range_set_markup_undo (GnmSheetRange *sr, PangoAttrList *markup)
2929 if (markup == NULL)
2930 return NULL;
2931 return go_undo_binary_new
2932 (sr, pango_attr_list_ref (markup),
2933 (GOUndoBinaryFunc) sheet_range_set_markup_cb,
2934 (GFreeFunc) gnm_sheet_range_free,
2935 (GFreeFunc) pango_attr_list_unref);
2939 * sheet_cell_get_value:
2940 * @sheet: Sheet
2941 * @col: Source column
2942 * @row: Source row
2944 * Returns: (transfer none) (nullable): the cell's current value.
2946 GnmValue const *
2947 sheet_cell_get_value (Sheet *sheet, int const col, int const row)
2949 GnmCell *cell;
2951 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2953 cell = sheet_cell_get (sheet, col, row);
2955 return cell ? cell->value : NULL;
2959 * sheet_cell_set_text:
2960 * @cell: A cell.
2961 * @str: the text to set.
2962 * @markup: (allow-none): an optional PangoAttrList.
2964 * Marks the sheet as dirty
2965 * Clears old spans.
2966 * Flags status updates
2967 * Queues recalcs
2969 void
2970 sheet_cell_set_text (GnmCell *cell, char const *text, PangoAttrList *markup)
2972 GnmExprTop const *texpr;
2973 GnmValue *val;
2974 GnmParsePos pp;
2976 g_return_if_fail (cell != NULL);
2977 g_return_if_fail (text != NULL);
2978 g_return_if_fail (!gnm_cell_is_nonsingleton_array (cell));
2980 parse_text_value_or_expr (parse_pos_init_cell (&pp, cell),
2981 text, &val, &texpr);
2983 /* Queue a redraw before in case the span changes */
2984 sheet_redraw_cell (cell);
2986 if (texpr != NULL) {
2987 gnm_cell_set_expr (cell, texpr);
2988 gnm_expr_top_unref (texpr);
2991 * Queue recalc before spanning. Otherwise spanning may
2992 * create a bogus rendered value, see #495879.
2994 cell_queue_recalc (cell);
2996 /* Clear spans from _other_ cells */
2997 sheet_cell_calc_span (cell, GNM_SPANCALC_SIMPLE);
2998 } else {
2999 g_return_if_fail (val != NULL);
3001 if (markup != NULL && VALUE_IS_STRING (val)) {
3002 gboolean quoted = (text[0] == '\'');
3003 PangoAttrList *adj_markup;
3004 GOFormat *fmt;
3006 if (quoted) {
3007 /* We ate the quote. Adjust. Ugh. */
3008 adj_markup = pango_attr_list_copy (markup);
3009 go_pango_attr_list_erase (adj_markup, 0, 1);
3010 } else
3011 adj_markup = markup;
3013 fmt = go_format_new_markup (adj_markup, TRUE);
3014 value_set_fmt (val, fmt);
3015 go_format_unref (fmt);
3016 if (quoted)
3017 pango_attr_list_unref (adj_markup);
3020 gnm_cell_set_value (cell, val);
3022 /* Queue recalc before spanning, see above. */
3023 cell_queue_recalc (cell);
3025 sheet_cell_calc_span (cell, GNM_SPANCALC_RESIZE | GNM_SPANCALC_RENDER);
3028 sheet_flag_status_update_cell (cell);
3032 * sheet_cell_set_text_gi: (rename-to sheet_cell_set_text)
3033 * @sheet: #Sheet
3034 * @col: column number
3035 * @row: row number
3036 * @str: the text to set.
3038 * Sets the contents of a cell.
3040 void
3041 sheet_cell_set_text_gi (Sheet *sheet, int col, int row, char const *str)
3043 sheet_cell_set_text (sheet_cell_fetch (sheet, col, row), str, NULL);
3048 * sheet_cell_set_expr:
3050 * Marks the sheet as dirty
3051 * Clears old spans.
3052 * Flags status updates
3053 * Queues recalcs
3055 void
3056 sheet_cell_set_expr (GnmCell *cell, GnmExprTop const *texpr)
3058 gnm_cell_set_expr (cell, texpr);
3060 /* clear spans from _other_ cells */
3061 sheet_cell_calc_span (cell, GNM_SPANCALC_SIMPLE);
3063 cell_queue_recalc (cell);
3064 sheet_flag_status_update_cell (cell);
3068 * sheet_cell_set_value: (skip)
3069 * @cell: #GnmCell
3070 * @v: (transfer full): #GnmValue
3072 * Stores, without copying, the supplied value. It marks the
3073 * sheet as dirty.
3075 * The value is rendered and spans are calculated. It queues a redraw
3076 * and checks to see if the edit region or selection content changed.
3078 * NOTE : This DOES check for array partitioning.
3080 void
3081 sheet_cell_set_value (GnmCell *cell, GnmValue *v)
3083 /* TODO : if the value is unchanged do not assign it */
3084 gnm_cell_set_value (cell, v);
3085 sheet_cell_calc_span (cell, GNM_SPANCALC_RESIZE | GNM_SPANCALC_RENDER);
3086 cell_queue_recalc (cell);
3087 sheet_flag_status_update_cell (cell);
3091 * sheet_cell_set_value_gi: (rename-to sheet_cell_set_value)
3092 * @sheet: #Sheet
3093 * @col: column number
3094 * @row: row number
3095 * @v: #GnmValue
3097 * Set the the value of the cell at (@col,@row) to @v.
3099 * The value is rendered and spans are calculated. It queues a redraw
3100 * and checks to see if the edit region or selection content changed.
3102 void
3103 sheet_cell_set_value_gi (Sheet *sheet, int col, int row, GnmValue *v)
3105 // This version exists because not all versions of pygobject
3106 // understand transfer-full parameters
3107 sheet_cell_set_value (sheet_cell_fetch (sheet, col, row),
3108 value_dup (v));
3111 /****************************************************************************/
3114 * This routine is used to queue the redraw regions for the
3115 * cell region specified.
3117 * It is usually called before a change happens to a region,
3118 * and after the change has been done to queue the regions
3119 * for the old contents and the new contents.
3121 * It intelligently handles spans and merged ranges
3123 void
3124 sheet_range_bounding_box (Sheet const *sheet, GnmRange *bound)
3126 GSList *ptr;
3127 int row;
3128 GnmRange r = *bound;
3130 g_return_if_fail (range_is_sane (bound));
3132 /* Check the first and last columns for spans and extend the region to
3133 * include the maximum extent.
3135 for (row = r.start.row; row <= r.end.row; row++){
3136 ColRowInfo const *ri = sheet_row_get (sheet, row);
3138 if (ri != NULL) {
3139 CellSpanInfo const * span0;
3141 if (ri->needs_respan)
3142 row_calc_spans ((ColRowInfo *)ri, row, sheet);
3144 span0 = row_span_get (ri, r.start.col);
3146 if (span0 != NULL) {
3147 if (bound->start.col > span0->left)
3148 bound->start.col = span0->left;
3149 if (bound->end.col < span0->right)
3150 bound->end.col = span0->right;
3152 if (r.start.col != r.end.col) {
3153 CellSpanInfo const * span1 =
3154 row_span_get (ri, r.end.col);
3156 if (span1 != NULL) {
3157 if (bound->start.col > span1->left)
3158 bound->start.col = span1->left;
3159 if (bound->end.col < span1->right)
3160 bound->end.col = span1->right;
3163 /* skip segments with no cells */
3164 } else if (row == COLROW_SEGMENT_START (row)) {
3165 ColRowSegment const * const segment =
3166 COLROW_GET_SEGMENT (&(sheet->rows), row);
3167 if (segment == NULL)
3168 row = COLROW_SEGMENT_END (row);
3172 /* TODO : this may get expensive if there are alot of merged ranges */
3173 /* no need to iterate, one pass is enough */
3174 for (ptr = sheet->list_merged ; ptr != NULL ; ptr = ptr->next) {
3175 GnmRange const * const test = ptr->data;
3176 if (r.start.row <= test->end.row || r.end.row >= test->start.row) {
3177 if (bound->start.col > test->start.col)
3178 bound->start.col = test->start.col;
3179 if (bound->end.col < test->end.col)
3180 bound->end.col = test->end.col;
3181 if (bound->start.row > test->start.row)
3182 bound->start.row = test->start.row;
3183 if (bound->end.row < test->end.row)
3184 bound->end.row = test->end.row;
3189 void
3190 sheet_redraw_region (Sheet const *sheet,
3191 int start_col, int start_row,
3192 int end_col, int end_row)
3194 GnmRange bound;
3196 g_return_if_fail (IS_SHEET (sheet));
3199 * Getting the bounding box causes row respans to be done if
3200 * needed. That can be expensive, so just redraw the whole
3201 * sheet if the row count is too big.
3203 if (end_row - start_row > 500) {
3204 sheet_redraw_all (sheet, FALSE);
3205 return;
3208 /* We potentially do a lot of recalcs as part of this, so make sure
3209 stuff that caches sub-computations see the whole thing instead
3210 of clearing between cells. */
3211 gnm_app_recalc_start ();
3213 sheet_range_bounding_box (sheet,
3214 range_init (&bound, start_col, start_row, end_col, end_row));
3215 SHEET_FOREACH_CONTROL (sheet, view, control,
3216 sc_redraw_range (control, &bound););
3218 gnm_app_recalc_finish ();
3221 void
3222 sheet_redraw_range (Sheet const *sheet, GnmRange const *range)
3224 g_return_if_fail (IS_SHEET (sheet));
3225 g_return_if_fail (range != NULL);
3227 sheet_redraw_region (sheet,
3228 range->start.col, range->start.row,
3229 range->end.col, range->end.row);
3232 /****************************************************************************/
3234 gboolean
3235 sheet_col_is_hidden (Sheet const *sheet, int col)
3237 ColRowInfo const * const res = sheet_col_get (sheet, col);
3238 return (res != NULL && !res->visible);
3241 gboolean
3242 sheet_row_is_hidden (Sheet const *sheet, int row)
3244 ColRowInfo const * const res = sheet_row_get (sheet, row);
3245 return (res != NULL && !res->visible);
3250 * sheet_find_boundary_horizontal:
3251 * @sheet: The Sheet
3252 * @col: The column from which to begin searching.
3253 * @move_row: The row in which to search for the edge of the range.
3254 * @base_row: The height of the area being moved.
3255 * @count: units to extend the selection vertically
3256 * @jump_to_boundaries: Jump to range boundaries.
3258 * Calculate the column index for the column which is @n units
3259 * from @start_col doing bounds checking. If @jump_to_boundaries is
3260 * TRUE @n must be 1 and the jump is to the edge of the logical range.
3262 * NOTE : This routine implements the logic necasary for ctrl-arrow style
3263 * movement. That is more compilcated than simply finding the last in a list
3264 * of cells with content. If you are at the end of a range it will find the
3265 * start of the next. Make sure that is the sort of behavior you want before
3266 * calling this.
3267 * Returns: the column inex.
3270 sheet_find_boundary_horizontal (Sheet *sheet, int start_col, int move_row,
3271 int base_row, int count,
3272 gboolean jump_to_boundaries)
3274 gboolean find_nonblank = sheet_is_cell_empty (sheet, start_col, move_row);
3275 gboolean keep_looking = FALSE;
3276 int new_col, prev_col, lagged_start_col, max_col = gnm_sheet_get_last_col (sheet);
3277 int iterations = 0;
3278 GnmRange check_merge;
3279 GnmRange const * const bound = &sheet->priv->unhidden_region;
3281 /* Jumping to bounds requires steping cell by cell */
3282 g_return_val_if_fail (count == 1 || count == -1 || !jump_to_boundaries, start_col);
3283 g_return_val_if_fail (IS_SHEET (sheet), start_col);
3285 if (move_row < base_row) {
3286 check_merge.start.row = move_row;
3287 check_merge.end.row = base_row;
3288 } else {
3289 check_merge.end.row = move_row;
3290 check_merge.start.row = base_row;
3293 do {
3294 GSList *merged, *ptr;
3296 lagged_start_col = check_merge.start.col = check_merge.end.col = start_col;
3297 merged = gnm_sheet_merge_get_overlap (sheet, &check_merge);
3298 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3299 GnmRange const * const r = ptr->data;
3300 if (count > 0) {
3301 if (start_col < r->end.col)
3302 start_col = r->end.col;
3303 } else {
3304 if (start_col > r->start.col)
3305 start_col = r->start.col;
3308 g_slist_free (merged);
3309 } while (start_col != lagged_start_col);
3310 new_col = prev_col = start_col;
3312 do {
3313 new_col += count;
3314 ++iterations;
3316 if (new_col < bound->start.col)
3317 return MIN (bound->start.col, max_col);
3318 if (new_col > bound->end.col)
3319 return MIN (bound->end.col, max_col);
3321 keep_looking = sheet_col_is_hidden (sheet, new_col);
3322 if (jump_to_boundaries) {
3323 if (new_col > sheet->cols.max_used) {
3324 if (count > 0)
3325 return (find_nonblank || iterations == 1)?
3326 MIN (bound->end.col, max_col):
3327 MIN (prev_col, max_col);
3328 new_col = sheet->cols.max_used;
3331 keep_looking |= (sheet_is_cell_empty (sheet, new_col, move_row) == find_nonblank);
3332 if (keep_looking)
3333 prev_col = new_col;
3334 else if (!find_nonblank) {
3336 * Handle special case where we are on the last
3337 * non-null cell
3339 if (iterations == 1)
3340 keep_looking = find_nonblank = TRUE;
3341 else
3342 new_col = prev_col;
3345 } while (keep_looking);
3347 return MIN (new_col, max_col);
3351 * sheet_find_boundary_vertical:
3352 * @sheet: The Sheet *
3353 * @move_col: The col in which to search for the edge of the range.
3354 * @row: The row from which to begin searching.
3355 * @base_col: The width of the area being moved.
3356 * @count: units to extend the selection vertically
3357 * @jump_to_boundaries: Jump to range boundaries.
3359 * Calculate the row index for the row which is @n units
3360 * from @start_row doing bounds checking. If @jump_to_boundaries is
3361 * TRUE @n must be 1 and the jump is to the edge of the logical range.
3363 * NOTE : This routine implements the logic necasary for ctrl-arrow style
3364 * movement. That is more compilcated than simply finding the last in a list
3365 * of cells with content. If you are at the end of a range it will find the
3366 * start of the next. Make sure that is the sort of behavior you want before
3367 * calling this.
3368 * Returns: the row index.
3371 sheet_find_boundary_vertical (Sheet *sheet, int move_col, int start_row,
3372 int base_col, int count,
3373 gboolean jump_to_boundaries)
3375 gboolean find_nonblank = sheet_is_cell_empty (sheet, move_col, start_row);
3376 gboolean keep_looking = FALSE;
3377 int new_row, prev_row, lagged_start_row, max_row = gnm_sheet_get_last_row (sheet);
3378 int iterations = 0;
3379 GnmRange check_merge;
3380 GnmRange const * const bound = &sheet->priv->unhidden_region;
3382 /* Jumping to bounds requires steping cell by cell */
3383 g_return_val_if_fail (count == 1 || count == -1 || !jump_to_boundaries, start_row);
3384 g_return_val_if_fail (IS_SHEET (sheet), start_row);
3386 if (move_col < base_col) {
3387 check_merge.start.col = move_col;
3388 check_merge.end.col = base_col;
3389 } else {
3390 check_merge.end.col = move_col;
3391 check_merge.start.col = base_col;
3394 do {
3395 GSList *merged, *ptr;
3397 lagged_start_row = check_merge.start.row = check_merge.end.row = start_row;
3398 merged = gnm_sheet_merge_get_overlap (sheet, &check_merge);
3399 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3400 GnmRange const * const r = ptr->data;
3401 if (count > 0) {
3402 if (start_row < r->end.row)
3403 start_row = r->end.row;
3404 } else {
3405 if (start_row > r->start.row)
3406 start_row = r->start.row;
3409 g_slist_free (merged);
3410 } while (start_row != lagged_start_row);
3411 new_row = prev_row = start_row;
3413 do {
3414 new_row += count;
3415 ++iterations;
3417 if (new_row < bound->start.row)
3418 return MIN (bound->start.row, max_row);
3419 if (new_row > bound->end.row)
3420 return MIN (bound->end.row, max_row);
3422 keep_looking = sheet_row_is_hidden (sheet, new_row);
3423 if (jump_to_boundaries) {
3424 if (new_row > sheet->rows.max_used) {
3425 if (count > 0)
3426 return (find_nonblank || iterations == 1)?
3427 MIN (bound->end.row, max_row):
3428 MIN (prev_row, max_row);
3429 new_row = sheet->rows.max_used;
3432 keep_looking |= (sheet_is_cell_empty (sheet, move_col, new_row) == find_nonblank);
3433 if (keep_looking)
3434 prev_row = new_row;
3435 else if (!find_nonblank) {
3437 * Handle special case where we are on the last
3438 * non-null cell
3440 if (iterations == 1)
3441 keep_looking = find_nonblank = TRUE;
3442 else
3443 new_row = prev_row;
3446 } while (keep_looking);
3448 return MIN (new_row, max_row);
3451 typedef enum {
3452 CHECK_AND_LOAD_START = 1,
3453 CHECK_END = 2,
3454 LOAD_END = 4
3455 } ArrayCheckFlags;
3457 typedef struct {
3458 Sheet const *sheet;
3459 int flags;
3460 int start, end;
3461 GnmRange const *ignore;
3463 GnmRange error;
3464 } ArrayCheckData;
3466 static gboolean
3467 cb_check_array_horizontal (GnmColRowIter const *iter, gpointer data_)
3469 ArrayCheckData *data = data_;
3470 gboolean is_array = FALSE;
3472 if (data->flags & CHECK_AND_LOAD_START && /* Top */
3473 (is_array = gnm_cell_array_bound (
3474 sheet_cell_get (data->sheet, iter->pos, data->start),
3475 &data->error)) &&
3476 data->error.start.row < data->start &&
3477 (data->ignore == NULL ||
3478 !range_contained (&data->error, data->ignore)))
3479 return TRUE;
3481 if (data->flags & LOAD_END)
3482 is_array = gnm_cell_array_bound (
3483 sheet_cell_get (data->sheet, iter->pos, data->end),
3484 &data->error);
3486 return (data->flags & CHECK_END &&
3487 is_array &&
3488 data->error.end.row > data->end && /* Bottom */
3489 (data->ignore == NULL ||
3490 !range_contained (&data->error, data->ignore)));
3493 static gboolean
3494 cb_check_array_vertical (GnmColRowIter const *iter, gpointer data_)
3496 ArrayCheckData *data = data_;
3497 gboolean is_array = FALSE;
3499 if (data->flags & CHECK_AND_LOAD_START && /* Left */
3500 (is_array = gnm_cell_array_bound (
3501 sheet_cell_get (data->sheet, data->start, iter->pos),
3502 &data->error)) &&
3503 data->error.start.col < data->start &&
3504 (data->ignore == NULL ||
3505 !range_contained (&data->error, data->ignore)))
3506 return TRUE;
3508 if (data->flags & LOAD_END)
3509 is_array = gnm_cell_array_bound (
3510 sheet_cell_get (data->sheet, data->end, iter->pos),
3511 &data->error);
3513 return (data->flags & CHECK_END &&
3514 is_array &&
3515 data->error.end.col > data->end && /* Right */
3516 (data->ignore == NULL ||
3517 !range_contained (&data->error, data->ignore)));
3521 * sheet_range_splits_array:
3522 * @sheet: The sheet.
3523 * @r: The range to check
3524 * @ignore: (nullable): a range in which it is ok to have an array.
3525 * @cc: (nullable): place to report an error.
3526 * @cmd: (nullable): cmd name used with @cc.
3528 * Check the outer edges of range @sheet!@r to ensure that if an array is
3529 * within it then the entire array is within the range. @ignore is useful when
3530 * src & dest ranges may overlap.
3532 * Returns: TRUE if an array would be split.
3534 gboolean
3535 sheet_range_splits_array (Sheet const *sheet,
3536 GnmRange const *r, GnmRange const *ignore,
3537 GOCmdContext *cc, char const *cmd)
3539 ArrayCheckData closure;
3541 g_return_val_if_fail (r->start.col <= r->end.col, FALSE);
3542 g_return_val_if_fail (r->start.row <= r->end.row, FALSE);
3544 closure.sheet = sheet;
3545 closure.ignore = ignore;
3547 closure.start = r->start.row;
3548 closure.end = r->end.row;
3549 if (closure.start <= 0) {
3550 closure.flags = (closure.end < sheet->rows.max_used)
3551 ? CHECK_END | LOAD_END
3552 : 0;
3553 } else if (closure.end < sheet->rows.max_used)
3554 closure.flags = (closure.start == closure.end)
3555 ? CHECK_AND_LOAD_START | CHECK_END
3556 : CHECK_AND_LOAD_START | CHECK_END | LOAD_END;
3557 else
3558 closure.flags = CHECK_AND_LOAD_START;
3560 if (closure.flags &&
3561 sheet_colrow_foreach (sheet, TRUE,
3562 r->start.col, r->end.col,
3563 cb_check_array_horizontal, &closure)) {
3564 if (cc)
3565 gnm_cmd_context_error_splits_array (cc,
3566 cmd, &closure.error);
3567 return TRUE;
3570 closure.start = r->start.col;
3571 closure.end = r->end.col;
3572 if (closure.start <= 0) {
3573 closure.flags = (closure.end < sheet->cols.max_used)
3574 ? CHECK_END | LOAD_END
3575 : 0;
3576 } else if (closure.end < sheet->cols.max_used)
3577 closure.flags = (closure.start == closure.end)
3578 ? CHECK_AND_LOAD_START | CHECK_END
3579 : CHECK_AND_LOAD_START | CHECK_END | LOAD_END;
3580 else
3581 closure.flags = CHECK_AND_LOAD_START;
3583 if (closure.flags &&
3584 sheet_colrow_foreach (sheet, FALSE,
3585 r->start.row, r->end.row,
3586 cb_check_array_vertical, &closure)) {
3587 if (cc)
3588 gnm_cmd_context_error_splits_array (cc,
3589 cmd, &closure.error);
3590 return TRUE;
3592 return FALSE;
3596 * sheet_range_splits_region:
3597 * @sheet: the sheet.
3598 * @r: The range whose boundaries are checked
3599 * @ignore: An optional range in which it is ok to have arrays and merges
3600 * @cc: The context that issued the command
3601 * @cmd: The translated command name.
3603 * A utility to see whether moving the range @r will split any arrays
3604 * or merged regions.
3605 * Returns: whether any arrays or merged regions will be split.
3607 gboolean
3608 sheet_range_splits_region (Sheet const *sheet,
3609 GnmRange const *r, GnmRange const *ignore,
3610 GOCmdContext *cc, char const *cmd_name)
3612 GSList *merged;
3614 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
3616 /* Check for array subdivision */
3617 if (sheet_range_splits_array (sheet, r, ignore, cc, cmd_name))
3618 return TRUE;
3620 merged = gnm_sheet_merge_get_overlap (sheet, r);
3621 if (merged) {
3622 GSList *ptr;
3624 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
3625 GnmRange const *m = ptr->data;
3626 if (ignore != NULL && range_contained (m, ignore))
3627 continue;
3628 if (!range_contained (m, r))
3629 break;
3631 g_slist_free (merged);
3633 if (cc != NULL && ptr != NULL) {
3634 go_cmd_context_error_invalid (cc, cmd_name,
3635 _("Target region contains merged cells"));
3636 return TRUE;
3639 return FALSE;
3643 * sheet_ranges_split_region:
3644 * @sheet: the sheet.
3645 * @ranges: (element-type GnmRange): A list of ranges to check.
3646 * @cc: The context that issued the command
3647 * @cmd: The translated command name.
3649 * A utility to see whether moving any of the ranges @ranges will split any
3650 * arrays or merged regions.
3651 * Returns: whether any arrays or merged regions will be splitted.
3653 gboolean
3654 sheet_ranges_split_region (Sheet const * sheet, GSList const *ranges,
3655 GOCmdContext *cc, char const *cmd)
3657 GSList const *l;
3659 /* Check for array subdivision */
3660 for (l = ranges; l != NULL; l = l->next) {
3661 GnmRange const *r = l->data;
3662 if (sheet_range_splits_region (sheet, r, NULL, cc, cmd))
3663 return TRUE;
3665 return FALSE;
3668 static GnmValue *
3669 cb_cell_is_array (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
3671 return gnm_cell_is_array (iter->cell) ? VALUE_TERMINATE : NULL;
3675 * sheet_range_contains_merges_or_arrays:
3676 * @sheet: The sheet
3677 * @r: the range to check.
3678 * @cc: an optional place to report errors.
3679 * @cmd:
3680 * @merges: if %TRUE, check for merges.
3681 * @arrays: if %TRUE, check for arrays.
3683 * Check to see if the target region @sheet!@r contains any merged regions or
3684 * arrays. Report an error to the @cc if it is supplied.
3685 * Returns: %TRUE if the target region @sheet!@r contains any merged regions or
3686 * arrays.
3688 gboolean
3689 sheet_range_contains_merges_or_arrays (Sheet const *sheet, GnmRange const *r,
3690 GOCmdContext *cc, char const *cmd,
3691 gboolean merges, gboolean arrays)
3693 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
3695 if (merges) {
3696 GSList *merged = gnm_sheet_merge_get_overlap (sheet, r);
3697 if (merged != NULL) {
3698 if (cc != NULL)
3699 go_cmd_context_error_invalid
3700 (cc, cmd,
3701 _("cannot operate on merged cells"));
3702 g_slist_free (merged);
3703 return TRUE;
3707 if (arrays) {
3708 if (sheet_foreach_cell_in_range (
3709 (Sheet *)sheet, CELL_ITER_IGNORE_NONEXISTENT, r,
3710 cb_cell_is_array, NULL)) {
3711 if (cc != NULL)
3712 go_cmd_context_error_invalid
3713 (cc, cmd,
3714 _("cannot operate on array formul\303\246"));
3715 return TRUE;
3719 return FALSE;
3722 /***************************************************************************/
3725 * sheet_colrow_get_default:
3726 * @sheet:
3727 * @is_cols:
3729 * Returns: (transfer none): the default #ColRowInfo.
3731 ColRowInfo const *
3732 sheet_colrow_get_default (Sheet const *sheet, gboolean is_cols)
3734 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3736 return is_cols ? &sheet->cols.default_style : &sheet->rows.default_style;
3739 static void
3740 sheet_colrow_optimize1 (int max, int max_used, ColRowCollection *collection)
3742 int i;
3743 int first_unused = max_used + 1;
3745 for (i = COLROW_SEGMENT_START (first_unused);
3746 i < max;
3747 i += COLROW_SEGMENT_SIZE) {
3748 ColRowSegment *segment = COLROW_GET_SEGMENT (collection, i);
3749 int j;
3750 gboolean any = FALSE;
3752 if (!segment)
3753 continue;
3754 for (j = 0; j < COLROW_SEGMENT_SIZE; j++) {
3755 ColRowInfo *info = segment->info[j];
3756 if (!info)
3757 continue;
3758 if (i + j >= first_unused &&
3759 col_row_info_equal (&collection->default_style, info)) {
3760 colrow_free (info);
3761 segment->info[j] = NULL;
3762 } else {
3763 any = TRUE;
3764 if (i + j >= first_unused)
3765 max_used = i + j;
3769 if (!any) {
3770 g_free (segment);
3771 COLROW_GET_SEGMENT (collection, i) = NULL;
3775 collection->max_used = max_used;
3778 void
3779 sheet_colrow_optimize (Sheet *sheet)
3781 GnmRange extent;
3783 g_return_if_fail (IS_SHEET (sheet));
3785 extent = sheet_get_cells_extent (sheet);
3787 sheet_colrow_optimize1 (gnm_sheet_get_max_cols (sheet),
3788 extent.end.col,
3789 &sheet->cols);
3790 sheet_colrow_optimize1 (gnm_sheet_get_max_rows (sheet),
3791 extent.end.row,
3792 &sheet->rows);
3796 * sheet_col_get:
3797 * @col: column number
3799 * Returns: (transfer none) (nullable): A #ColRowInfo for the column, or %NULL
3800 * if none has been allocated yet.
3802 ColRowInfo *
3803 sheet_col_get (Sheet const *sheet, int col)
3805 ColRowSegment *segment;
3807 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3808 g_return_val_if_fail (col < gnm_sheet_get_max_cols (sheet), NULL);
3809 g_return_val_if_fail (col >= 0, NULL);
3811 segment = COLROW_GET_SEGMENT (&(sheet->cols), col);
3812 if (segment != NULL)
3813 return segment->info[COLROW_SUB_INDEX (col)];
3814 return NULL;
3818 * sheet_row_get:
3819 * @row: row number
3821 * Returns: (transfer none) (nullable): A #ColRowInfo for the row, or %NULL
3822 * if none has been allocated yet.
3824 ColRowInfo *
3825 sheet_row_get (Sheet const *sheet, int row)
3827 ColRowSegment *segment;
3829 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3830 g_return_val_if_fail (row < gnm_sheet_get_max_rows (sheet), NULL);
3831 g_return_val_if_fail (row >= 0, NULL);
3833 segment = COLROW_GET_SEGMENT (&(sheet->rows), row);
3834 if (segment != NULL)
3835 return segment->info[COLROW_SUB_INDEX (row)];
3836 return NULL;
3839 ColRowInfo *
3840 sheet_colrow_get (Sheet const *sheet, int colrow, gboolean is_cols)
3842 if (is_cols)
3843 return sheet_col_get (sheet, colrow);
3844 return sheet_row_get (sheet, colrow);
3848 * sheet_col_fetch:
3849 * @col: column number
3851 * Returns: (transfer none): The #ColRowInfo for column @col. This result
3852 * will not be the default #ColRowInfo and may be changed.
3854 ColRowInfo *
3855 sheet_col_fetch (Sheet *sheet, int pos)
3857 ColRowInfo *cri = sheet_col_get (sheet, pos);
3858 if (NULL == cri && NULL != (cri = sheet_col_new (sheet)))
3859 sheet_colrow_add (sheet, cri, TRUE, pos);
3860 return cri;
3864 * sheet_row_fetch:
3865 * @row: row number
3867 * Returns: (transfer none): The #ColRowInfo for row @row. This result
3868 * will not be the default #ColRowInfo and may be changed.
3870 ColRowInfo *
3871 sheet_row_fetch (Sheet *sheet, int pos)
3873 ColRowInfo *cri = sheet_row_get (sheet, pos);
3874 if (NULL == cri && NULL != (cri = sheet_row_new (sheet)))
3875 sheet_colrow_add (sheet, cri, FALSE, pos);
3876 return cri;
3879 ColRowInfo *
3880 sheet_colrow_fetch (Sheet *sheet, int colrow, gboolean is_cols)
3882 if (is_cols)
3883 return sheet_col_fetch (sheet, colrow);
3884 return sheet_row_fetch (sheet, colrow);
3888 * sheet_col_get_info:
3889 * @col: column number
3891 * Returns: (transfer none): The #ColRowInfo for column @col. The may be
3892 * the default #ColRowInfo for columns and should not be changed.
3894 ColRowInfo const *
3895 sheet_col_get_info (Sheet const *sheet, int col)
3897 ColRowInfo *ci = sheet_col_get (sheet, col);
3899 if (ci != NULL)
3900 return ci;
3901 return &sheet->cols.default_style;
3905 * sheet_row_get_info:
3906 * @row: column number
3908 * Returns: (transfer none): The #ColRowInfo for row @row. The may be
3909 * the default #ColRowInfo for rows and should not be changed.
3911 ColRowInfo const *
3912 sheet_row_get_info (Sheet const *sheet, int row)
3914 ColRowInfo *ri = sheet_row_get (sheet, row);
3916 if (ri != NULL)
3917 return ri;
3918 return &sheet->rows.default_style;
3921 ColRowInfo const *
3922 sheet_colrow_get_info (Sheet const *sheet, int colrow, gboolean is_cols)
3924 return is_cols
3925 ? sheet_col_get_info (sheet, colrow)
3926 : sheet_row_get_info (sheet, colrow);
3930 * sheet_colrow_foreach:
3931 * @sheet: #Sheet
3932 * @is_cols: %TRUE for columns, %FALSE for rows.
3933 * @first: start position (inclusive)
3934 * @last: stop position (inclusive), -1 meaning end-of-sheet
3935 * @callback: (scope call): A callback function which should return %TRUE
3936 * to stop the iteration.
3937 * @user_data: A baggage pointer.
3939 * Iterates through the existing rows or columns within the range supplied.
3940 * If a callback returns %TRUE, iteration stops.
3942 gboolean
3943 sheet_colrow_foreach (Sheet const *sheet,
3944 gboolean is_cols,
3945 int first, int last,
3946 ColRowHandler callback,
3947 gpointer user_data)
3949 ColRowCollection const *infos;
3950 GnmColRowIter iter;
3951 ColRowSegment const *segment;
3952 int sub, inner_last, i;
3954 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
3956 if (last == -1)
3957 last = colrow_max (is_cols, sheet) - 1;
3958 infos = is_cols ? &sheet->cols : &sheet->rows;
3960 /* clip */
3961 if (last > infos->max_used)
3962 last = infos->max_used;
3964 for (i = first; i <= last ; ) {
3965 segment = COLROW_GET_SEGMENT (infos, i);
3966 sub = COLROW_SUB_INDEX(i);
3967 inner_last = (COLROW_SEGMENT_INDEX (last) == COLROW_SEGMENT_INDEX (i))
3968 ? COLROW_SUB_INDEX (last)+1 : COLROW_SEGMENT_SIZE;
3969 iter.pos = i;
3970 i += COLROW_SEGMENT_SIZE - sub;
3971 if (segment == NULL)
3972 continue;
3974 for (; sub < inner_last; sub++, iter.pos++) {
3975 iter.cri = segment->info[sub];
3976 if (iter.cri != NULL && (*callback)(&iter, user_data))
3977 return TRUE;
3981 return FALSE;
3985 /*****************************************************************************/
3987 static gint
3988 cell_ordering (gconstpointer a_, gconstpointer b_)
3990 GnmCell const *a = *(GnmCell **)a_;
3991 GnmCell const *b = *(GnmCell **)b_;
3993 if (a->pos.row != b->pos.row)
3994 return a->pos.row - b->pos.row;
3996 return a->pos.col - b->pos.col;
4000 * sheet_cells:
4001 * @sheet: a #Sheet
4002 * @r: (nullable): a #GnmRange
4004 * Retrieves an array of all cells inside @r.
4005 * Returns: (element-type GnmCell) (transfer container): the cells array.
4007 GPtrArray *
4008 sheet_cells (Sheet *sheet, const GnmRange *r)
4010 GPtrArray *res = g_ptr_array_new ();
4011 GHashTableIter hiter;
4012 gpointer value;
4014 g_hash_table_iter_init (&hiter, sheet->cell_hash);
4015 while (g_hash_table_iter_next (&hiter, NULL, &value)) {
4016 GnmCell *cell = value;
4017 if (!r || range_contains (r, cell->pos.col, cell->pos.row))
4018 g_ptr_array_add (res, cell);
4020 g_ptr_array_sort (res, cell_ordering);
4022 return res;
4027 #define SWAP_INT(a,b) do { int t; t = a; a = b; b = t; } while (0)
4030 * sheet_foreach_cell_in_range:
4031 * @sheet: #Sheet
4032 * @flags:
4033 * @r: #GnmRange
4034 * @callback: (scope call): #CellFilterFunc
4035 * @closure: user data.
4037 * For each existing cell in the range specified, invoke the
4038 * callback routine. If the only_existing flag is passed, then
4039 * callbacks are only invoked for existing cells.
4041 * Note: this function does not honour the CELL_ITER_IGNORE_SUBTOTAL flag.
4043 * Returns: (transfer none): the value returned by the callback, which can be:
4044 * non-NULL on error, or VALUE_TERMINATE if some invoked routine requested
4045 * to stop (by returning non-NULL).
4047 * NOTE: between 0.56 and 0.57, the traversal order changed. The order is now
4049 * 1 2 3
4050 * 4 5 6
4051 * 7 8 9
4053 * (This appears to be the order in which XL looks at the values of ranges.)
4054 * If your code depends on any particular ordering, please add a very visible
4055 * comment near the call.
4057 GnmValue *
4058 sheet_foreach_cell_in_range (Sheet *sheet, CellIterFlags flags,
4059 GnmRange const *r,
4060 CellIterFunc callback,
4061 gpointer closure)
4063 return sheet_foreach_cell_in_region (sheet, flags,
4064 r->start.col, r->start.row,
4065 r->end.col, r->end.row,
4066 callback, closure);
4071 * sheet_foreach_cell_in_region:
4072 * @sheet: #Sheet
4073 * @flags:
4074 * @start_col: Starting column
4075 * @start_row: Starting row
4076 * @end_col: Ending column, -1 meaning last
4077 * @end_row: Ending row, -1 meaning last
4078 * @callback: (scope call): #CellFilterFunc
4079 * @closure: user data.
4081 * For each existing cell in the range specified, invoke the
4082 * callback routine. If the only_existing flag is passed, then
4083 * callbacks are only invoked for existing cells.
4085 * Note: this function does not honour the CELL_ITER_IGNORE_SUBTOTAL flag.
4087 * Returns: (transfer none): the value returned by the callback, which can be:
4088 * non-NULL on error, or VALUE_TERMINATE if some invoked routine requested
4089 * to stop (by returning non-NULL).
4091 * NOTE: between 0.56 and 0.57, the traversal order changed. The order is now
4093 * 1 2 3
4094 * 4 5 6
4095 * 7 8 9
4097 * (This appears to be the order in which XL looks at the values of ranges.)
4098 * If your code depends on any particular ordering, please add a very visible
4099 * comment near the call.
4101 GnmValue *
4102 sheet_foreach_cell_in_region (Sheet *sheet, CellIterFlags flags,
4103 int start_col, int start_row,
4104 int end_col, int end_row,
4105 CellIterFunc callback, void *closure)
4107 GnmValue *cont;
4108 GnmCellIter iter;
4109 gboolean const visibility_matters = (flags & CELL_ITER_IGNORE_HIDDEN) != 0;
4110 gboolean const ignore_filtered = (flags & CELL_ITER_IGNORE_FILTERED) != 0;
4111 gboolean const only_existing = (flags & CELL_ITER_IGNORE_NONEXISTENT) != 0;
4112 gboolean const ignore_empty = (flags & CELL_ITER_IGNORE_EMPTY) != 0;
4113 gboolean ignore;
4114 gboolean use_celllist;
4115 guint64 range_size;
4117 g_return_val_if_fail (IS_SHEET (sheet), NULL);
4118 g_return_val_if_fail (callback != NULL, NULL);
4120 // For convenience
4121 if (end_col == -1) end_col = gnm_sheet_get_last_col (sheet);
4122 if (end_row == -1) end_row = gnm_sheet_get_last_row (sheet);
4124 iter.pp.sheet = sheet;
4125 iter.pp.wb = sheet->workbook;
4127 if (start_col > end_col)
4128 SWAP_INT (start_col, end_col);
4129 if (end_col < 0 || start_col > gnm_sheet_get_last_col (sheet))
4130 return NULL;
4131 start_col = MAX (0, start_col);
4132 end_col = MIN (end_col, gnm_sheet_get_last_col (sheet));
4134 if (start_row > end_row)
4135 SWAP_INT (start_row, end_row);
4136 if (end_row < 0 || start_row > gnm_sheet_get_last_row (sheet))
4137 return NULL;
4138 start_row = MAX (0, start_row);
4139 end_row = MIN (end_row, gnm_sheet_get_last_row (sheet));
4141 range_size = (guint64)(end_row - start_row + 1) * (end_col - start_col + 1);
4142 use_celllist =
4143 only_existing &&
4144 range_size > g_hash_table_size (sheet->cell_hash) + 1000;
4145 if (use_celllist) {
4146 GPtrArray *all_cells;
4147 int last_row = -1, last_col = -1;
4148 GnmValue *res = NULL;
4149 unsigned ui;
4150 GnmRange r;
4152 if (gnm_debug_flag ("sheet-foreach"))
4153 g_printerr ("Using celllist for area of size %d\n",
4154 (int)range_size);
4156 range_init (&r, start_col, start_row, end_col, end_row);
4157 all_cells = sheet_cells (sheet, &r);
4159 for (ui = 0; ui < all_cells->len; ui++) {
4160 GnmCell *cell = g_ptr_array_index (all_cells, ui);
4162 iter.cell = cell;
4163 iter.pp.eval.row = cell->pos.row;
4164 iter.pp.eval.col = cell->pos.col;
4166 if (iter.pp.eval.row != last_row) {
4167 last_row = iter.pp.eval.row;
4168 iter.ri = sheet_row_get (iter.pp.sheet, last_row);
4170 if (iter.ri == NULL) {
4171 g_critical ("Cell without row data -- please report");
4172 continue;
4174 if (visibility_matters && !iter.ri->visible)
4175 continue;
4176 if (ignore_filtered && iter.ri->in_filter && !iter.ri->visible)
4177 continue;
4179 if (iter.pp.eval.col != last_col) {
4180 last_col = iter.pp.eval.col;
4181 iter.ci = sheet_col_get (iter.pp.sheet, last_col);
4183 if (iter.ci == NULL) {
4184 g_critical ("Cell without column data -- please report");
4185 continue;
4187 if (visibility_matters && !iter.ci->visible)
4188 continue;
4190 ignore = (ignore_empty &&
4191 VALUE_IS_EMPTY (cell->value) &&
4192 !gnm_cell_needs_recalc (cell));
4193 if (ignore)
4194 continue;
4196 res = (*callback) (&iter, closure);
4197 if (res != NULL)
4198 break;
4201 g_ptr_array_free (all_cells, TRUE);
4202 return res;
4205 for (iter.pp.eval.row = start_row;
4206 iter.pp.eval.row <= end_row;
4207 ++iter.pp.eval.row) {
4208 iter.ri = sheet_row_get (iter.pp.sheet, iter.pp.eval.row);
4210 /* no need to check visibility, that would require a colinfo to exist */
4211 if (iter.ri == NULL) {
4212 if (only_existing) {
4213 /* skip segments with no cells */
4214 if (iter.pp.eval.row == COLROW_SEGMENT_START (iter.pp.eval.row)) {
4215 ColRowSegment const *segment =
4216 COLROW_GET_SEGMENT (&(sheet->rows), iter.pp.eval.row);
4217 if (segment == NULL)
4218 iter.pp.eval.row = COLROW_SEGMENT_END (iter.pp.eval.row);
4220 } else {
4221 iter.cell = NULL;
4222 for (iter.pp.eval.col = start_col; iter.pp.eval.col <= end_col; ++iter.pp.eval.col) {
4223 cont = (*callback) (&iter, closure);
4224 if (cont != NULL)
4225 return cont;
4229 continue;
4232 if (visibility_matters && !iter.ri->visible)
4233 continue;
4234 if (ignore_filtered && iter.ri->in_filter && !iter.ri->visible)
4235 continue;
4237 for (iter.pp.eval.col = start_col; iter.pp.eval.col <= end_col; ++iter.pp.eval.col) {
4238 iter.ci = sheet_col_get (sheet, iter.pp.eval.col);
4239 if (iter.ci != NULL) {
4240 if (visibility_matters && !iter.ci->visible)
4241 continue;
4242 iter.cell = sheet_cell_get (sheet,
4243 iter.pp.eval.col, iter.pp.eval.row);
4244 } else
4245 iter.cell = NULL;
4247 ignore = (iter.cell == NULL)
4248 ? (only_existing || ignore_empty)
4249 : (ignore_empty && VALUE_IS_EMPTY (iter.cell->value) &&
4250 !gnm_cell_needs_recalc (iter.cell));
4252 if (ignore) {
4253 if (iter.pp.eval.col == COLROW_SEGMENT_START (iter.pp.eval.col)) {
4254 ColRowSegment const *segment =
4255 COLROW_GET_SEGMENT (&(sheet->cols), iter.pp.eval.col);
4256 if (segment == NULL)
4257 iter.pp.eval.col = COLROW_SEGMENT_END (iter.pp.eval.col);
4259 continue;
4262 cont = (*callback) (&iter, closure);
4263 if (cont != NULL)
4264 return cont;
4267 return NULL;
4271 * sheet_cell_foreach:
4272 * @sheet: #Sheet
4273 * @callback: (scope call):
4274 * @data:
4276 * Call @callback with an argument of @data for each cell in the sheet
4278 void
4279 sheet_cell_foreach (Sheet const *sheet, GHFunc callback, gpointer data)
4281 g_return_if_fail (IS_SHEET (sheet));
4283 g_hash_table_foreach (sheet->cell_hash, callback, data);
4287 * sheet_cells_count:
4288 * @sheet: #Sheet
4290 * Returns the number of cells with content in the current workbook.
4292 unsigned
4293 sheet_cells_count (Sheet const *sheet)
4295 return g_hash_table_size (sheet->cell_hash);
4298 static void
4299 cb_sheet_cells_collect (G_GNUC_UNUSED gpointer unused,
4300 GnmCell const *cell,
4301 GPtrArray *cells)
4303 GnmEvalPos *ep = eval_pos_init_cell (g_new (GnmEvalPos, 1), cell);
4304 g_ptr_array_add (cells, ep);
4308 * sheet_cell_positions:
4309 * @sheet: The sheet to find cells in.
4310 * @comments: If true, include cells with only comments also.
4312 * Collects a GPtrArray of GnmEvalPos pointers for all cells in a sheet.
4313 * No particular order should be assumed.
4314 * Returns: (element-type GnmEvalPos) (transfer full): the newly created array
4316 GPtrArray *
4317 sheet_cell_positions (Sheet *sheet, gboolean comments)
4319 GPtrArray *cells = g_ptr_array_new ();
4321 g_return_val_if_fail (IS_SHEET (sheet), cells);
4323 sheet_cell_foreach (sheet, (GHFunc)cb_sheet_cells_collect, cells);
4325 if (comments) {
4326 GnmRange r;
4327 GSList *scomments, *ptr;
4329 range_init_full_sheet (&r, sheet);
4330 scomments = sheet_objects_get (sheet, &r, GNM_CELL_COMMENT_TYPE);
4331 for (ptr = scomments; ptr; ptr = ptr->next) {
4332 GnmComment *c = ptr->data;
4333 GnmRange const *loc = sheet_object_get_range (GNM_SO (c));
4334 GnmCell *cell = sheet_cell_get (sheet, loc->start.col, loc->start.row);
4335 if (!cell) {
4336 /* If cell does not exist, we haven't seen it... */
4337 GnmEvalPos *ep = g_new (GnmEvalPos, 1);
4338 ep->sheet = sheet;
4339 ep->eval.col = loc->start.col;
4340 ep->eval.row = loc->start.row;
4341 g_ptr_array_add (cells, ep);
4344 g_slist_free (scomments);
4347 return cells;
4351 static GnmValue *
4352 cb_fail_if_exist (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
4354 return gnm_cell_is_empty (iter->cell) ? NULL : VALUE_TERMINATE;
4358 * sheet_is_region_empty:
4359 * @sheet: sheet to check
4360 * @r: region to check
4362 * Returns TRUE if the specified region of the @sheet does not
4363 * contain any cells
4365 gboolean
4366 sheet_is_region_empty (Sheet *sheet, GnmRange const *r)
4368 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
4370 return sheet_foreach_cell_in_range (
4371 sheet, CELL_ITER_IGNORE_BLANK, r,
4372 cb_fail_if_exist, NULL) == NULL;
4375 gboolean
4376 sheet_is_cell_empty (Sheet *sheet, int col, int row)
4378 GnmCell const *cell = sheet_cell_get (sheet, col, row);
4379 return gnm_cell_is_empty (cell);
4383 * sheet_cell_add_to_hash:
4384 * @sheet The sheet where the cell is inserted
4385 * @cell The cell, it should already have col/pos pointers
4386 * initialized pointing to the correct ColRowInfo
4388 * GnmCell::pos must be valid before this is called. The position is used as the
4389 * hash key.
4391 static void
4392 sheet_cell_add_to_hash (Sheet *sheet, GnmCell *cell)
4394 g_return_if_fail (cell->pos.col < gnm_sheet_get_max_cols (sheet));
4395 g_return_if_fail (cell->pos.row < gnm_sheet_get_max_rows (sheet));
4397 cell->base.flags |= GNM_CELL_IN_SHEET_LIST;
4398 /* NOTE:
4399 * fetching the col/row here serve 3 functions
4400 * 1) obsolete: we used to store the pointer in the cell
4401 * 2) Expanding col/row.max_used
4402 * 3) Creating an entry in the COLROW_SEGMENT. Lots and lots of
4403 * things use those to help limit iteration
4405 * For now just call col_fetch even though it is not necessary to
4406 * ensure that 2,3 still happen. Alot will need rewriting to avoid
4407 * these requirements.
4409 (void)sheet_col_fetch (sheet, cell->pos.col);
4410 (void)sheet_row_fetch (sheet, cell->pos.row);
4412 gnm_cell_unrender (cell);
4414 g_hash_table_insert (sheet->cell_hash, cell, cell);
4416 if (gnm_sheet_merge_is_corner (sheet, &cell->pos))
4417 cell->base.flags |= GNM_CELL_IS_MERGED;
4420 #undef USE_CELL_POOL
4422 #ifdef USE_CELL_POOL
4423 /* The pool from which all cells are allocated. */
4424 static GOMemChunk *cell_pool;
4425 #else
4426 static int cell_allocations = 0;
4427 #endif
4429 static GnmCell *
4430 cell_new (void)
4432 GnmCell *cell =
4433 #ifdef USE_CELL_POOL
4434 go_mem_chunk_alloc0 (cell_pool)
4435 #else
4436 (cell_allocations++, g_slice_new0 (GnmCell))
4437 #endif
4440 cell->base.flags = DEPENDENT_CELL;
4441 return cell;
4445 static void
4446 cell_free (GnmCell *cell)
4448 g_return_if_fail (cell != NULL);
4450 gnm_cell_cleanout (cell);
4451 #ifdef USE_CELL_POOL
4452 go_mem_chunk_free (cell_pool, cell);
4453 #else
4454 cell_allocations--, g_slice_free1 (sizeof (*cell), cell);
4455 #endif
4459 * gnm_sheet_cell_init: (skip)
4461 void
4462 gnm_sheet_cell_init (void)
4464 #ifdef USE_CELL_POOL
4465 cell_pool = go_mem_chunk_new ("cell pool",
4466 sizeof (GnmCell),
4467 128 * 1024 - 128);
4468 #endif
4471 #ifdef USE_CELL_POOL
4472 static void
4473 cb_cell_pool_leak (gpointer data, G_GNUC_UNUSED gpointer user)
4475 GnmCell const *cell = data;
4476 g_printerr ("Leaking cell %p at %s\n", (void *)cell, cell_name (cell));
4478 #endif
4481 * gnm_sheet_cell_shutdown: (skip)
4483 void
4484 gnm_sheet_cell_shutdown (void)
4486 #ifdef USE_CELL_POOL
4487 go_mem_chunk_foreach_leak (cell_pool, cb_cell_pool_leak, NULL);
4488 go_mem_chunk_destroy (cell_pool, FALSE);
4489 cell_pool = NULL;
4490 #else
4491 if (cell_allocations)
4492 g_printerr ("Leaking %d cells.\n", cell_allocations);
4493 #endif
4496 /****************************************************************************/
4499 * sheet_cell_create:
4500 * @sheet: #Sheet
4501 * @col:
4502 * @row:
4504 * Creates a new cell and adds it to the sheet hash.
4506 GnmCell *
4507 sheet_cell_create (Sheet *sheet, int col, int row)
4509 GnmCell *cell;
4511 g_return_val_if_fail (IS_SHEET (sheet), NULL);
4512 g_return_val_if_fail (col >= 0, NULL);
4513 g_return_val_if_fail (col < gnm_sheet_get_max_cols (sheet), NULL);
4514 g_return_val_if_fail (row >= 0, NULL);
4515 g_return_val_if_fail (row < gnm_sheet_get_max_rows (sheet), NULL);
4517 cell = cell_new ();
4518 cell->base.sheet = sheet;
4519 cell->pos.col = col;
4520 cell->pos.row = row;
4521 cell->value = value_new_empty ();
4523 sheet_cell_add_to_hash (sheet, cell);
4524 return cell;
4528 * sheet_cell_remove_from_hash:
4529 * @sheet:
4530 * @cell:
4532 * Removes a cell from the sheet hash, clears any spans, and unlinks it from
4533 * the dependent collection.
4535 static void
4536 sheet_cell_remove_from_hash (Sheet *sheet, GnmCell *cell)
4538 cell_unregister_span (cell);
4539 if (gnm_cell_expr_is_linked (cell))
4540 dependent_unlink (GNM_CELL_TO_DEP (cell));
4541 g_hash_table_remove (sheet->cell_hash, cell);
4542 cell->base.flags &= ~(GNM_CELL_IN_SHEET_LIST|GNM_CELL_IS_MERGED);
4546 * sheet_cell_destroy:
4547 * @sheet:
4548 * @cell:
4549 * @queue_recalc:
4551 * Remove the cell from the web of dependencies of a
4552 * sheet. Do NOT redraw.
4554 static void
4555 sheet_cell_destroy (Sheet *sheet, GnmCell *cell, gboolean queue_recalc)
4557 if (gnm_cell_expr_is_linked (cell)) {
4558 /* if it needs recalc then its depends are already queued
4559 * check recalc status before we unlink
4561 queue_recalc &= !gnm_cell_needs_recalc (cell);
4562 dependent_unlink (GNM_CELL_TO_DEP (cell));
4565 if (queue_recalc)
4566 cell_foreach_dep (cell, (GnmDepFunc)dependent_queue_recalc, NULL);
4568 sheet_cell_remove_from_hash (sheet, cell);
4569 cell_free (cell);
4573 * sheet_cell_remove:
4574 * @sheet:
4575 * @cell:
4576 * @redraw:
4577 * @queue_recalc:
4579 * Remove the cell from the web of dependencies of a
4580 * sheet. Do NOT free the cell, optionally redraw it, optionally
4581 * queue it for recalc.
4583 void
4584 sheet_cell_remove (Sheet *sheet, GnmCell *cell,
4585 gboolean redraw, gboolean queue_recalc)
4587 g_return_if_fail (cell != NULL);
4588 g_return_if_fail (IS_SHEET (sheet));
4590 /* Queue a redraw on the region used by the cell being removed */
4591 if (redraw) {
4592 sheet_redraw_region (sheet,
4593 cell->pos.col, cell->pos.row,
4594 cell->pos.col, cell->pos.row);
4595 sheet_flag_status_update_cell (cell);
4598 sheet_cell_destroy (sheet, cell, queue_recalc);
4601 static GnmValue *
4602 cb_free_cell (GnmCellIter const *iter, G_GNUC_UNUSED gpointer user)
4604 sheet_cell_destroy (iter->pp.sheet, iter->cell, FALSE);
4605 return NULL;
4609 * sheet_col_destroy:
4610 * @sheet:
4611 * @col:
4612 * @free_cells:
4614 * Destroys a ColRowInfo from the Sheet with all of its cells
4616 static void
4617 sheet_col_destroy (Sheet *sheet, int const col, gboolean free_cells)
4619 ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->cols), col);
4620 int const sub = COLROW_SUB_INDEX (col);
4621 ColRowInfo *ci = NULL;
4623 if (*segment == NULL)
4624 return;
4625 ci = (*segment)->info[sub];
4626 if (ci == NULL)
4627 return;
4629 if (sheet->cols.max_outline_level > 0 &&
4630 sheet->cols.max_outline_level == ci->outline_level)
4631 sheet->priv->recompute_max_col_group = TRUE;
4633 if (free_cells)
4634 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4635 col, 0, col, -1,
4636 &cb_free_cell, NULL);
4638 (*segment)->info[sub] = NULL;
4639 colrow_free (ci);
4641 /* Use >= just in case things are screwed up */
4642 if (col >= sheet->cols.max_used) {
4643 int i = col;
4644 while (--i >= 0 && sheet_col_get (sheet, i) == NULL)
4646 sheet->cols.max_used = i;
4651 * Destroys a row ColRowInfo
4653 static void
4654 sheet_row_destroy (Sheet *sheet, int const row, gboolean free_cells)
4656 ColRowSegment **segment = (ColRowSegment **)&COLROW_GET_SEGMENT (&(sheet->rows), row);
4657 int const sub = COLROW_SUB_INDEX (row);
4658 ColRowInfo *ri = NULL;
4660 if (*segment == NULL)
4661 return;
4662 ri = (*segment)->info[sub];
4663 if (ri == NULL)
4664 return;
4666 if (sheet->rows.max_outline_level > 0 &&
4667 sheet->rows.max_outline_level == ri->outline_level)
4668 sheet->priv->recompute_max_row_group = TRUE;
4670 if (free_cells)
4671 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4672 0, row, -1, row,
4673 &cb_free_cell, NULL);
4675 /* Rows have span lists, destroy them too */
4676 row_destroy_span (ri);
4678 (*segment)->info[sub] = NULL;
4679 colrow_free (ri);
4681 /* Use >= just in case things are screwed up */
4682 if (row >= sheet->rows.max_used) {
4683 int i = row;
4684 while (--i >= 0 && sheet_row_get (sheet, i) == NULL)
4686 sheet->rows.max_used = i;
4690 static void
4691 cb_remove_allcells (G_GNUC_UNUSED gpointer ignore0, GnmCell *cell, G_GNUC_UNUSED gpointer ignore1)
4693 cell->base.flags &= ~GNM_CELL_IN_SHEET_LIST;
4694 cell_free (cell);
4697 void
4698 sheet_destroy_contents (Sheet *sheet)
4700 GSList *filters;
4701 int i;
4703 /* By the time we reach here dependencies should have been shut down */
4704 g_return_if_fail (sheet->deps == NULL);
4706 /* A simple test to see if this has already been run. */
4707 if (sheet->hash_merged == NULL)
4708 return;
4711 GSList *tmp = sheet->slicers;
4712 sheet->slicers = NULL;
4713 g_slist_free_full (tmp, (GDestroyNotify)gnm_sheet_slicer_clear_sheet);
4716 /* These contain SheetObjects, remove them first */
4717 filters = g_slist_copy (sheet->filters);
4718 g_slist_foreach (filters, (GFunc)gnm_filter_remove, NULL);
4719 g_slist_foreach (filters, (GFunc)gnm_filter_unref, NULL);
4720 g_slist_free (filters);
4722 if (sheet->sheet_objects) {
4723 /* The list is changed as we remove */
4724 GSList *objs = g_slist_copy (sheet->sheet_objects);
4725 GSList *ptr;
4726 for (ptr = objs; ptr != NULL ; ptr = ptr->next) {
4727 SheetObject *so = GNM_SO (ptr->data);
4728 if (so != NULL)
4729 sheet_object_clear_sheet (so);
4731 g_slist_free (objs);
4732 if (sheet->sheet_objects != NULL)
4733 g_warning ("There is a problem with sheet objects");
4736 /* The memory is managed by Sheet::list_merged */
4737 g_hash_table_destroy (sheet->hash_merged);
4738 sheet->hash_merged = NULL;
4740 g_slist_free_full (sheet->list_merged, g_free);
4741 sheet->list_merged = NULL;
4743 /* Clear the row spans 1st */
4744 for (i = sheet->rows.max_used; i >= 0 ; --i)
4745 row_destroy_span (sheet_row_get (sheet, i));
4747 /* Remove all the cells */
4748 sheet_cell_foreach (sheet, (GHFunc) &cb_remove_allcells, NULL);
4749 g_hash_table_destroy (sheet->cell_hash);
4751 /* Delete in ascending order to avoid decrementing max_used each time */
4752 for (i = 0; i <= sheet->cols.max_used; ++i)
4753 sheet_col_destroy (sheet, i, FALSE);
4755 for (i = 0; i <= sheet->rows.max_used; ++i)
4756 sheet_row_destroy (sheet, i, FALSE);
4758 /* Free segments too */
4759 col_row_collection_resize (&sheet->cols, 0);
4760 g_ptr_array_free (sheet->cols.info, TRUE);
4761 sheet->cols.info = NULL;
4763 col_row_collection_resize (&sheet->rows, 0);
4764 g_ptr_array_free (sheet->rows.info, TRUE);
4765 sheet->rows.info = NULL;
4767 g_clear_object (&sheet->solver_parameters);
4771 * sheet_destroy:
4772 * @sheet: the sheet to destroy
4774 * Please note that you need to detach this sheet before
4775 * calling this routine or you will get a warning.
4777 static void
4778 sheet_destroy (Sheet *sheet)
4780 g_return_if_fail (IS_SHEET (sheet));
4782 if (sheet->sheet_views->len > 0)
4783 g_warning ("Unexpected left over views");
4785 if (sheet->print_info) {
4786 gnm_print_info_free (sheet->print_info);
4787 sheet->print_info = NULL;
4790 style_color_unref (sheet->tab_color);
4791 sheet->tab_color = NULL;
4792 style_color_unref (sheet->tab_text_color);
4793 sheet->tab_text_color = NULL;
4795 gnm_app_clipboard_invalidate_sheet (sheet);
4798 static void
4799 gnm_sheet_finalize (GObject *obj)
4801 Sheet *sheet = SHEET (obj);
4802 gboolean debug_FMR = gnm_debug_flag ("sheet-fmr");
4804 sheet_destroy (sheet);
4806 g_clear_object (&sheet->solver_parameters);
4808 gnm_conventions_unref (sheet->convs);
4809 sheet->convs = NULL;
4811 g_list_free_full (sheet->scenarios, g_object_unref);
4812 sheet->scenarios = NULL;
4814 if (sheet->sort_setups != NULL)
4815 g_hash_table_unref (sheet->sort_setups);
4817 dependents_invalidate_sheet (sheet, TRUE);
4819 sheet_destroy_contents (sheet);
4821 if (sheet->slicers != NULL) {
4822 g_warning ("DataSlicer list should be NULL");
4824 if (sheet->filters != NULL) {
4825 g_warning ("Filter list should be NULL");
4827 if (sheet->sheet_objects != NULL) {
4828 g_warning ("Sheet object list should be NULL");
4830 if (sheet->list_merged != NULL) {
4831 g_warning ("Merged list should be NULL");
4833 if (sheet->hash_merged != NULL) {
4834 g_warning ("Merged hash should be NULL");
4837 sheet_style_shutdown (sheet);
4839 (void) g_idle_remove_by_data (sheet);
4841 if (debug_FMR) {
4842 g_printerr ("Sheet %p is %s\n", sheet, sheet->name_quoted);
4844 g_free (sheet->name_quoted);
4845 g_free (sheet->name_unquoted);
4846 g_free (sheet->name_unquoted_collate_key);
4847 g_free (sheet->name_case_insensitive);
4848 /* Poison */
4849 sheet->name_quoted = (char *)0xdeadbeef;
4850 sheet->name_unquoted = (char *)0xdeadbeef;
4851 g_free (sheet->priv);
4852 g_ptr_array_free (sheet->sheet_views, TRUE);
4854 gnm_rvc_free (sheet->rendered_values);
4856 if (debug_FMR) {
4857 /* Keep object around. */
4858 return;
4861 G_OBJECT_CLASS (parent_class)->finalize (obj);
4864 /*****************************************************************************/
4867 * cb_empty_cell: A callback for sheet_foreach_cell_in_region
4868 * removes/clear all of the cells in the specified region.
4869 * Does NOT queue a redraw.
4871 * WARNING : This does NOT regenerate spans that were interrupted by
4872 * this cell and can now continue.
4874 static GnmValue *
4875 cb_empty_cell (GnmCellIter const *iter, gpointer user)
4877 int clear_flags = GPOINTER_TO_INT (user);
4878 #if 0
4879 /* TODO : here and other places flag a need to update the
4880 * row/col maxima.
4882 if (row >= sheet->rows.max_used || col >= sheet->cols.max_used) { }
4883 #endif
4885 sheet_cell_remove (iter->pp.sheet, iter->cell, FALSE,
4886 (clear_flags & CLEAR_RECALC_DEPS) &&
4887 iter->pp.wb->recursive_dirty_enabled);
4889 return NULL;
4893 * sheet_clear_region:
4894 * @sheet:
4895 * @start_col:
4896 * @start_row:
4897 * @end_col:
4898 * @end_row:
4899 * @clear_flags: If this is TRUE then styles are erased.
4900 * @cc: (nullable):
4902 * Clears are region of cells
4904 * We assemble a list of cells to destroy, since we will be making changes
4905 * to the structure being manipulated by the sheet_foreach_cell_in_region routine
4907 void
4908 sheet_clear_region (Sheet *sheet,
4909 int start_col, int start_row,
4910 int end_col, int end_row,
4911 int clear_flags,
4912 GOCmdContext *cc)
4914 GnmRange r;
4916 g_return_if_fail (IS_SHEET (sheet));
4917 g_return_if_fail (start_col <= end_col);
4918 g_return_if_fail (start_row <= end_row);
4920 r.start.col = start_col;
4921 r.start.row = start_row;
4922 r.end.col = end_col;
4923 r.end.row = end_row;
4925 if (clear_flags & CLEAR_VALUES && !(clear_flags & CLEAR_NOCHECKARRAY) &&
4926 sheet_range_splits_array (sheet, &r, NULL, cc, _("Clear")))
4927 return;
4929 /* Queue a redraw for cells being modified */
4930 if (clear_flags & (CLEAR_VALUES|CLEAR_FORMATS))
4931 sheet_redraw_region (sheet,
4932 start_col, start_row,
4933 end_col, end_row);
4935 /* Clear the style in the region (new_default will ref the style for us). */
4936 if (clear_flags & CLEAR_FORMATS) {
4937 sheet_style_set_range (sheet, &r, sheet_style_default (sheet));
4938 sheet_range_calc_spans (sheet, &r, GNM_SPANCALC_RE_RENDER|GNM_SPANCALC_RESIZE);
4939 rows_height_update (sheet, &r, TRUE);
4942 if (clear_flags & CLEAR_OBJECTS)
4943 sheet_objects_clear (sheet, &r, G_TYPE_NONE, NULL);
4944 else if (clear_flags & CLEAR_COMMENTS)
4945 sheet_objects_clear (sheet, &r, GNM_CELL_COMMENT_TYPE, NULL);
4947 /* TODO : how to handle objects ? */
4948 if (clear_flags & CLEAR_VALUES) {
4949 /* Remove or empty the cells depending on
4950 * whether or not there are comments
4952 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_NONEXISTENT,
4953 start_col, start_row, end_col, end_row,
4954 &cb_empty_cell, GINT_TO_POINTER (clear_flags));
4956 if (!(clear_flags & CLEAR_NORESPAN)) {
4957 sheet_queue_respan (sheet, start_row, end_row);
4958 sheet_flag_status_update_range (sheet, &r);
4962 if (clear_flags & CLEAR_MERGES) {
4963 GSList *merged, *ptr;
4964 merged = gnm_sheet_merge_get_overlap (sheet, &r);
4965 for (ptr = merged ; ptr != NULL ; ptr = ptr->next)
4966 gnm_sheet_merge_remove (sheet, ptr->data);
4967 g_slist_free (merged);
4970 if (clear_flags & CLEAR_RECALC_DEPS)
4971 sheet_region_queue_recalc (sheet, &r);
4973 /* Always redraw */
4974 sheet_redraw_all (sheet, FALSE);
4977 static void
4978 sheet_clear_region_cb (GnmSheetRange *sr, int *flags)
4980 sheet_clear_region (sr->sheet,
4981 sr->range.start.col, sr->range.start.row,
4982 sr->range.end.col, sr->range.end.row,
4983 *flags | CLEAR_NOCHECKARRAY, NULL);
4988 * sheet_clear_region_undo:
4989 * @sr: #GnmSheetRange
4990 * @clear_flags: flags.
4992 * Returns: (transfer full): the new #GOUndo.
4994 GOUndo *sheet_clear_region_undo (GnmSheetRange *sr, int clear_flags)
4996 int *flags = g_new(int, 1);
4997 *flags = clear_flags;
4998 return go_undo_binary_new
4999 (sr, (gpointer)flags,
5000 (GOUndoBinaryFunc) sheet_clear_region_cb,
5001 (GFreeFunc) gnm_sheet_range_free,
5002 (GFreeFunc) g_free);
5006 /*****************************************************************************/
5008 void
5009 sheet_mark_dirty (Sheet *sheet)
5011 g_return_if_fail (IS_SHEET (sheet));
5013 if (sheet->workbook)
5014 go_doc_set_dirty (GO_DOC (sheet->workbook), TRUE);
5017 /****************************************************************************/
5019 static void
5020 sheet_cells_deps_move (GnmExprRelocateInfo *rinfo)
5022 Sheet *sheet = rinfo->origin_sheet;
5023 GPtrArray *deps = sheet_cells (sheet, &rinfo->origin);
5024 unsigned ui;
5026 /* Phase 1: collect all cells and remove them from hash. */
5027 for (ui = 0; ui < deps->len; ui++) {
5028 GnmCell *cell = g_ptr_array_index (deps, ui);
5029 gboolean needs_recalc = gnm_cell_needs_recalc (cell);
5030 sheet_cell_remove_from_hash (sheet, cell);
5031 if (needs_recalc) /* Do we need this now? */
5032 cell->base.flags |= DEPENDENT_NEEDS_RECALC;
5035 /* Phase 2: add all non-cell deps with positions */
5036 SHEET_FOREACH_DEPENDENT
5037 (sheet, dep, {
5038 GnmCellPos const *pos;
5039 if (!dependent_is_cell (dep) &&
5040 dependent_has_pos (dep) &&
5041 (pos = dependent_pos (dep)) &&
5042 range_contains (&rinfo->origin, pos->col, pos->row)) {
5043 dependent_unlink (dep);
5044 g_ptr_array_add (deps, dep);
5048 /* Phase 3: move everything and add cells to hash. */
5049 for (ui = 0; ui < deps->len; ui++) {
5050 GnmDependent *dep = g_ptr_array_index (deps, ui);
5052 dependent_move (dep, rinfo->col_offset, rinfo->row_offset);
5054 if (dependent_is_cell (dep))
5055 sheet_cell_add_to_hash (sheet, GNM_DEP_TO_CELL (dep));
5057 if (dep->texpr)
5058 dependent_link (dep);
5061 g_ptr_array_free (deps, TRUE);
5064 /* Moves the headers to their new location */
5065 static void
5066 sheet_colrow_move (Sheet *sheet, gboolean is_cols,
5067 int const old_pos, int const new_pos)
5069 ColRowSegment *segment = COLROW_GET_SEGMENT (is_cols ? &sheet->cols : &sheet->rows, old_pos);
5070 ColRowInfo *info = segment
5071 ? segment->info[COLROW_SUB_INDEX (old_pos)]
5072 : NULL;
5074 g_return_if_fail (old_pos >= 0);
5075 g_return_if_fail (new_pos >= 0);
5077 if (info == NULL)
5078 return;
5080 /* Update the position */
5081 segment->info[COLROW_SUB_INDEX (old_pos)] = NULL;
5082 sheet_colrow_add (sheet, info, is_cols, new_pos);
5085 static void
5086 sheet_colrow_set_collapse (Sheet *sheet, gboolean is_cols, int pos)
5088 ColRowInfo *cri;
5089 ColRowInfo const *vs = NULL;
5091 if (pos < 0 || pos >= colrow_max (is_cols, sheet))
5092 return;
5094 /* grab the next or previous col/row */
5095 if ((is_cols ? sheet->outline_symbols_right : sheet->outline_symbols_below)) {
5096 if (pos > 0)
5097 vs = sheet_colrow_get (sheet, pos-1, is_cols);
5098 } else if ((pos+1) < colrow_max (is_cols, sheet))
5099 vs = sheet_colrow_get (sheet, pos+1, is_cols);
5101 /* handle the case where an empty col/row should be marked collapsed */
5102 cri = sheet_colrow_get (sheet, pos, is_cols);
5103 if (cri != NULL)
5104 cri->is_collapsed = (vs != NULL && !vs->visible &&
5105 vs->outline_level > cri->outline_level);
5106 else if (vs != NULL && !vs->visible && vs->outline_level > 0) {
5107 cri = sheet_colrow_fetch (sheet, pos, is_cols);
5108 cri->is_collapsed = TRUE;
5112 static void
5113 combine_undo (GOUndo **pundo, GOUndo *u)
5115 if (pundo)
5116 *pundo = go_undo_combine (*pundo, u);
5117 else
5118 g_object_unref (u);
5121 typedef gboolean (*ColRowInsDelFunc) (Sheet *sheet, int idx, int count,
5122 GOUndo **pundo, GOCmdContext *cc);
5124 typedef struct {
5125 ColRowInsDelFunc func;
5126 Sheet *sheet;
5127 gboolean is_cols;
5128 int pos;
5129 int count;
5130 ColRowStateList *states;
5131 int state_start;
5132 } ColRowInsDelData;
5134 static void
5135 cb_undo_insdel (ColRowInsDelData *data)
5137 data->func (data->sheet, data->pos, data->count, NULL, NULL);
5138 colrow_set_states (data->sheet, data->is_cols,
5139 data->state_start, data->states);
5142 static void
5143 cb_undo_insdel_free (ColRowInsDelData *data)
5145 colrow_state_list_destroy (data->states);
5146 g_free (data);
5149 static gboolean
5150 sheet_insdel_colrow (Sheet *sheet, int pos, int count,
5151 GOUndo **pundo, GOCmdContext *cc,
5152 gboolean is_cols, gboolean is_insert,
5153 const char *description,
5154 ColRowInsDelFunc opposite)
5157 GnmRange kill_zone; /* The range whose contents will be lost. */
5158 GnmRange move_zone; /* The range whose contents will be moved. */
5159 GnmRange change_zone; /* The union of kill_zone and move_zone. */
5160 int i, last_pos, max_used_pos;
5161 int kill_start, kill_end, move_start, move_end;
5162 int scount = is_insert ? count : -count;
5163 ColRowStateList *states = NULL;
5164 GnmExprRelocateInfo reloc_info;
5165 GSList *l;
5166 gboolean sticky_end = TRUE;
5168 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
5169 g_return_val_if_fail (count > 0, TRUE);
5172 * The main undo for an insert col/row is delete col/row and vice versa.
5173 * In addition to that, we collect undo information that the main undo
5174 * operation will not restore -- for example the contents of the kill
5175 * zone.
5177 if (pundo) *pundo = NULL;
5179 last_pos = colrow_max (is_cols, sheet) - 1;
5180 max_used_pos = is_cols ? sheet->cols.max_used : sheet->rows.max_used;
5181 if (is_insert) {
5182 kill_start = last_pos - (count - 1);
5183 kill_end = last_pos;
5184 move_start = pos;
5185 move_end = kill_start - 1;
5186 } else {
5187 int max_count = last_pos + 1 - pos;
5188 if (count > max_count) {
5189 sticky_end = FALSE;
5190 count = max_count;
5192 kill_start = pos;
5193 kill_end = pos + (count - 1);
5194 move_start = kill_end + 1;
5195 move_end = last_pos;
5197 (is_cols ? range_init_cols : range_init_rows)
5198 (&kill_zone, sheet, kill_start, kill_end);
5199 (is_cols ? range_init_cols : range_init_rows)
5200 (&move_zone, sheet, move_start, move_end);
5201 change_zone = range_union (&kill_zone, &move_zone);
5203 /* 0. Check displaced/deleted region and ensure arrays aren't divided. */
5204 if (sheet_range_splits_array (sheet, &kill_zone, NULL, cc, description))
5205 return TRUE;
5206 if (move_start <= move_end &&
5207 sheet_range_splits_array (sheet, &move_zone, NULL, cc, description))
5208 return TRUE;
5211 * At this point we're committed. Anything that can go wrong should
5212 * have been ruled out already.
5215 if (0) {
5216 g_printerr ("Action = %s at %d count %d\n", description, pos, count);
5217 g_printerr ("Kill zone: %s\n", range_as_string (&kill_zone));
5220 /* 1. Delete all columns/rows in the kill zone */
5221 if (pundo) {
5222 combine_undo (pundo, clipboard_copy_range_undo (sheet, &kill_zone));
5223 states = colrow_get_states (sheet, is_cols, kill_start, kill_end);
5225 for (i = MIN (max_used_pos, kill_end); i >= kill_start; --i)
5226 (is_cols ? sheet_col_destroy : sheet_row_destroy)
5227 (sheet, i, TRUE);
5228 /* Brutally discard auto filter objects. Collect the rest for undo. */
5229 sheet_objects_clear (sheet, &kill_zone, GNM_FILTER_COMBO_TYPE, NULL);
5230 sheet_objects_clear (sheet, &kill_zone, G_TYPE_NONE, pundo);
5232 reloc_info.reloc_type = is_cols ? GNM_EXPR_RELOCATE_COLS : GNM_EXPR_RELOCATE_ROWS;
5233 reloc_info.sticky_end = sticky_end;
5234 reloc_info.origin_sheet = reloc_info.target_sheet = sheet;
5235 parse_pos_init_sheet (&reloc_info.pos, sheet);
5237 /* 2. Get rid of style dependents, see #741197. */
5238 sheet_style_clear_style_dependents (sheet, &change_zone);
5240 /* 3. Invalidate references to kill zone. */
5241 if (is_insert) {
5242 /* Done in the next step. */
5243 } else {
5244 reloc_info.origin = kill_zone;
5245 /* Force invalidation: */
5246 reloc_info.col_offset = is_cols ? last_pos + 1 : 0;
5247 reloc_info.row_offset = is_cols ? 0 : last_pos + 1;
5248 combine_undo (pundo, dependents_relocate (&reloc_info));
5251 /* 4. Fix references to the cells which are moving */
5252 reloc_info.origin = is_insert ? change_zone : move_zone;
5253 reloc_info.col_offset = is_cols ? scount : 0;
5254 reloc_info.row_offset = is_cols ? 0 : scount;
5255 combine_undo (pundo, dependents_relocate (&reloc_info));
5257 /* 5. Move the cells */
5258 sheet_cells_deps_move (&reloc_info);
5260 /* 6. Move the columns/rows to their new location. */
5261 if (is_insert) {
5262 /* From right to left */
5263 for (i = max_used_pos; i >= pos ; --i)
5264 sheet_colrow_move (sheet, is_cols, i, i + count);
5265 } else {
5266 /* From left to right */
5267 for (i = pos + count ; i <= max_used_pos; ++i)
5268 sheet_colrow_move (sheet, is_cols, i, i - count);
5270 sheet_colrow_set_collapse (sheet, is_cols, pos);
5271 sheet_colrow_set_collapse (sheet, is_cols,
5272 is_insert ? pos + count : last_pos - (count - 1));
5274 /* 7. Move formatting. */
5275 sheet_style_insdel_colrow (&reloc_info);
5277 /* 8. Move objects. */
5278 sheet_objects_relocate (&reloc_info, FALSE, pundo);
5280 /* 9. Move merges. */
5281 gnm_sheet_merge_relocate (&reloc_info, pundo);
5283 /* 10. Move filters. */
5284 gnm_sheet_filter_insdel_colrow (sheet, is_cols, is_insert, pos, count, pundo);
5286 /* Notify sheet of pending updates */
5287 sheet_mark_dirty (sheet);
5288 sheet->priv->recompute_visibility = TRUE;
5289 sheet_flag_recompute_spans (sheet);
5290 sheet_flag_status_update_range (sheet, &change_zone);
5291 if (is_cols)
5292 sheet->priv->reposition_objects.col = pos;
5293 else
5294 sheet->priv->reposition_objects.row = pos;
5296 /* WARNING WARNING WARNING
5297 * This is bad practice and should not really be here.
5298 * However, we need to ensure that update is run before
5299 * gnm_sheet_view_panes_insdel_colrow plays with frozen panes, updating those can
5300 * trigger redraws before sheet_update has been called. */
5301 sheet_update (sheet);
5303 SHEET_FOREACH_VIEW (sheet, sv,
5304 gnm_sheet_view_panes_insdel_colrow (sv, is_cols, is_insert, pos, count););
5306 /* The main undo is the opposite operation. */
5307 if (pundo) {
5308 ColRowInsDelData *data;
5309 GOUndo *u;
5311 data = g_new (ColRowInsDelData, 1);
5312 data->func = opposite;
5313 data->sheet = sheet;
5314 data->is_cols = is_cols;
5315 data->pos = pos;
5316 data->count = count;
5317 data->states = states;
5318 data->state_start = kill_start;
5320 u = go_undo_unary_new (data, (GOUndoUnaryFunc)cb_undo_insdel,
5321 (GFreeFunc)cb_undo_insdel_free);
5323 combine_undo (pundo, u);
5326 /* Reapply all filters. */
5327 for (l = sheet->filters; l; l = l->next) {
5328 GnmFilter *filter = l->data;
5329 gnm_filter_reapply (filter);
5332 return FALSE;
5336 * sheet_insert_cols:
5337 * @sheet: #Sheet
5338 * @col: At which position we want to insert
5339 * @count: The number of columns to be inserted
5340 * @pundo: (out): (transfer full): (allow-none): undo closure
5341 * @cc:
5343 gboolean
5344 sheet_insert_cols (Sheet *sheet, int col, int count,
5345 GOUndo **pundo, GOCmdContext *cc)
5347 return sheet_insdel_colrow (sheet, col, count, pundo, cc,
5348 TRUE, TRUE,
5349 _("Insert Columns"),
5350 sheet_delete_cols);
5354 * sheet_delete_cols:
5355 * @sheet: The sheet
5356 * @col: At which position we want to start deleting columns
5357 * @count: The number of columns to be deleted
5358 * @pundo: (out): (transfer full): (allow-none): undo closure
5359 * @cc: The command context
5361 gboolean
5362 sheet_delete_cols (Sheet *sheet, int col, int count,
5363 GOUndo **pundo, GOCmdContext *cc)
5365 return sheet_insdel_colrow (sheet, col, count, pundo, cc,
5366 TRUE, FALSE,
5367 _("Delete Columns"),
5368 sheet_insert_cols);
5372 * sheet_insert_rows:
5373 * @sheet: The sheet
5374 * @row: At which position we want to insert
5375 * @count: The number of rows to be inserted
5376 * @pundo: (out): (transfer full): (allow-none): undo closure
5377 * @cc: The command context
5379 gboolean
5380 sheet_insert_rows (Sheet *sheet, int row, int count,
5381 GOUndo **pundo, GOCmdContext *cc)
5383 return sheet_insdel_colrow (sheet, row, count, pundo, cc,
5384 FALSE, TRUE,
5385 _("Insert Rows"),
5386 sheet_delete_rows);
5390 * sheet_delete_rows:
5391 * @sheet: The sheet
5392 * @row: At which position we want to start deleting rows
5393 * @count: The number of rows to be deleted
5394 * @pundo: (out): (transfer full): (allow-none): undo closure
5395 * @cc: The command context
5397 gboolean
5398 sheet_delete_rows (Sheet *sheet, int row, int count,
5399 GOUndo **pundo, GOCmdContext *cc)
5401 return sheet_insdel_colrow (sheet, row, count, pundo, cc,
5402 FALSE, FALSE,
5403 _("Delete Rows"),
5404 sheet_insert_rows);
5408 * Callback for sheet_foreach_cell_in_region to remove a cell from the sheet
5409 * hash, unlink from the dependent collection and put it in a temporary list.
5411 static GnmValue *
5412 cb_collect_cell (GnmCellIter const *iter, gpointer user)
5414 GList ** l = user;
5415 GnmCell *cell = iter->cell;
5416 gboolean needs_recalc = gnm_cell_needs_recalc (cell);
5418 sheet_cell_remove_from_hash (iter->pp.sheet, cell);
5419 *l = g_list_prepend (*l, cell);
5420 if (needs_recalc)
5421 cell->base.flags |= DEPENDENT_NEEDS_RECALC;
5422 return NULL;
5426 * sheet_move_range:
5427 * @cc:
5428 * @rinfo:
5429 * @pundo: optionally NULL, caller releases result
5431 * Move a range as specified in @rinfo report warnings to @cc.
5432 * if @pundo is non NULL, invalidate references to the
5433 * target region that are being cleared, and store the undo information
5434 * in @pundo. If it is NULL do NOT INVALIDATE.
5436 void
5437 sheet_move_range (GnmExprRelocateInfo const *rinfo,
5438 GOUndo **pundo, GOCmdContext *cc)
5440 GList *cells = NULL;
5441 GnmCell *cell;
5442 GnmRange dst;
5443 gboolean out_of_range;
5445 g_return_if_fail (rinfo != NULL);
5446 g_return_if_fail (IS_SHEET (rinfo->origin_sheet));
5447 g_return_if_fail (IS_SHEET (rinfo->target_sheet));
5448 g_return_if_fail (rinfo->origin_sheet != rinfo->target_sheet ||
5449 rinfo->col_offset != 0 ||
5450 rinfo->row_offset != 0);
5452 dst = rinfo->origin;
5453 out_of_range = range_translate (&dst, rinfo->target_sheet,
5454 rinfo->col_offset, rinfo->row_offset);
5456 /* Redraw the src region in case anything was spanning */
5457 sheet_redraw_range (rinfo->origin_sheet, &rinfo->origin);
5459 /* 1. invalidate references to any cells in the destination range that
5460 * are not shared with the src. This must be done before the references
5461 * to from the src range are adjusted because they will point into
5462 * the destination.
5464 if (pundo != NULL) {
5465 *pundo = NULL;
5466 if (!out_of_range) {
5467 GSList *invalid;
5468 GnmExprRelocateInfo reloc_info;
5470 /* We need to be careful about invalidating references
5471 * to the old content of the destination region. We
5472 * only invalidate references to regions that are
5473 * actually lost. However, this care is only necessary
5474 * if the source and target sheets are the same.
5476 * Handle dst cells being pasted over
5478 if (rinfo->origin_sheet == rinfo->target_sheet &&
5479 range_overlap (&rinfo->origin, &dst))
5480 invalid = range_split_ranges (&rinfo->origin, &dst);
5481 else
5482 invalid = g_slist_append (NULL, gnm_range_dup (&dst));
5484 reloc_info.origin_sheet = reloc_info.target_sheet = rinfo->target_sheet;
5486 /* send to infinity to invalidate, but try to assist
5487 * the relocation heuristics only move in 1
5488 * dimension if possible to give us a chance to be
5489 * smart about partial invalidations */
5490 reloc_info.col_offset = gnm_sheet_get_max_cols (rinfo->target_sheet);
5491 reloc_info.row_offset = gnm_sheet_get_max_rows (rinfo->target_sheet);
5492 reloc_info.sticky_end = TRUE;
5493 if (rinfo->col_offset == 0) {
5494 reloc_info.col_offset = 0;
5495 reloc_info.reloc_type = GNM_EXPR_RELOCATE_ROWS;
5496 } else if (rinfo->row_offset == 0) {
5497 reloc_info.row_offset = 0;
5498 reloc_info.reloc_type = GNM_EXPR_RELOCATE_COLS;
5499 } else
5500 reloc_info.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE;
5502 parse_pos_init_sheet (&reloc_info.pos,
5503 rinfo->origin_sheet);
5505 while (invalid) {
5506 GnmRange *r = invalid->data;
5507 invalid = g_slist_remove (invalid, r);
5508 if (!range_overlap (r, &rinfo->origin)) {
5509 reloc_info.origin = *r;
5510 combine_undo (pundo,
5511 dependents_relocate (&reloc_info));
5513 g_free (r);
5517 * DO NOT handle src cells moving out the bounds.
5518 * that is handled elsewhere.
5522 /* 2. Fix references to and from the cells which are moving */
5523 combine_undo (pundo, dependents_relocate (rinfo));
5526 /* 3. Collect the cells */
5527 sheet_foreach_cell_in_range (rinfo->origin_sheet,
5528 CELL_ITER_IGNORE_NONEXISTENT,
5529 &rinfo->origin,
5530 &cb_collect_cell, &cells);
5532 /* Reverse list so that we start at the top left (simplifies arrays). */
5533 cells = g_list_reverse (cells);
5535 /* 4. Clear the target area & invalidate references to it */
5536 if (!out_of_range)
5537 /* we can clear content but not styles from the destination
5538 * region without worrying if it overlaps with the source,
5539 * because we have already extracted the content. However,
5540 * we do need to queue anything that depends on the region for
5541 * recalc. */
5542 sheet_clear_region (rinfo->target_sheet,
5543 dst.start.col, dst.start.row,
5544 dst.end.col, dst.end.row,
5545 CLEAR_VALUES|CLEAR_RECALC_DEPS, cc);
5547 /* 5. Slide styles BEFORE the cells so that spans get computed properly */
5548 sheet_style_relocate (rinfo);
5550 /* 6. Insert the cells back */
5551 for (; cells != NULL ; cells = g_list_remove (cells, cell)) {
5552 cell = cells->data;
5554 /* check for out of bounds and delete if necessary */
5555 if ((cell->pos.col + rinfo->col_offset) >= gnm_sheet_get_max_cols (rinfo->target_sheet) ||
5556 (cell->pos.row + rinfo->row_offset) >= gnm_sheet_get_max_rows (rinfo->target_sheet)) {
5557 cell_free (cell);
5558 continue;
5561 /* Update the location */
5562 cell->base.sheet = rinfo->target_sheet;
5563 cell->pos.col += rinfo->col_offset;
5564 cell->pos.row += rinfo->row_offset;
5565 sheet_cell_add_to_hash (rinfo->target_sheet, cell);
5566 if (gnm_cell_has_expr (cell))
5567 dependent_link (GNM_CELL_TO_DEP (cell));
5570 /* 7. Move objects in the range */
5571 sheet_objects_relocate (rinfo, TRUE, pundo);
5572 gnm_sheet_merge_relocate (rinfo, pundo);
5574 /* 8. Notify sheet of pending update */
5575 sheet_flag_recompute_spans (rinfo->origin_sheet);
5576 sheet_flag_status_update_range (rinfo->origin_sheet, &rinfo->origin);
5579 static void
5580 sheet_colrow_default_calc (Sheet *sheet, double units,
5581 gboolean is_cols, gboolean is_pts)
5583 ColRowInfo *cri = is_cols
5584 ? &sheet->cols.default_style
5585 : &sheet->rows.default_style;
5587 g_return_if_fail (units > 0.);
5589 cri->is_default = TRUE;
5590 cri->hard_size = FALSE;
5591 cri->visible = TRUE;
5592 cri->spans = NULL;
5594 if (is_pts) {
5595 cri->size_pts = units;
5596 colrow_compute_pixels_from_pts (cri, sheet, is_cols, -1);
5597 } else {
5598 cri->size_pixels = units;
5599 colrow_compute_pts_from_pixels (cri, sheet, is_cols, -1);
5603 /************************************************************************/
5604 /* Col width support routines.
5608 * sheet_col_get_distance_pts:
5610 * Return the number of points between from_col to to_col
5611 * measured from the upper left corner.
5613 double
5614 sheet_col_get_distance_pts (Sheet const *sheet, int from, int to)
5616 ColRowInfo const *ci;
5617 double dflt, pts = 0., sign = 1.;
5618 int i;
5620 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5622 if (from > to) {
5623 int const tmp = to;
5624 to = from;
5625 from = tmp;
5626 sign = -1.;
5629 g_return_val_if_fail (from >= 0, 1.);
5630 g_return_val_if_fail (to <= gnm_sheet_get_max_cols (sheet), 1.);
5632 /* Do not use sheet_colrow_foreach, it ignores empties */
5633 dflt = sheet->cols.default_style.size_pts;
5634 for (i = from ; i < to ; ++i) {
5635 if (NULL == (ci = sheet_col_get (sheet, i)))
5636 pts += dflt;
5637 else if (ci->visible)
5638 pts += ci->size_pts;
5641 if (sheet->display_formulas)
5642 pts *= 2.;
5644 return pts * sign;
5648 * sheet_col_get_distance_pixels:
5650 * Return the number of pixels between from_col to to_col
5651 * measured from the upper left corner.
5654 sheet_col_get_distance_pixels (Sheet const *sheet, int from, int to)
5656 ColRowInfo const *ci;
5657 int dflt, pixels = 0, sign = 1;
5658 int i;
5660 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5662 if (from > to) {
5663 int const tmp = to;
5664 to = from;
5665 from = tmp;
5666 sign = -1;
5669 g_return_val_if_fail (from >= 0, 1);
5670 g_return_val_if_fail (to <= gnm_sheet_get_max_cols (sheet), 1);
5672 /* Do not use sheet_colrow_foreach, it ignores empties */
5673 dflt = sheet_col_get_default_size_pixels (sheet);
5674 for (i = from ; i < to ; ++i) {
5675 if (NULL == (ci = sheet_col_get (sheet, i)))
5676 pixels += dflt;
5677 else if (ci->visible)
5678 pixels += ci->size_pixels;
5681 return pixels * sign;
5685 * sheet_col_set_size_pts:
5686 * @sheet: The sheet
5687 * @col: The col
5688 * @width_pts: The desired widtht in pts
5689 * @set_by_user: TRUE if this was done by a user (ie, user manually
5690 * set the width)
5692 * Sets width of a col in pts, INCLUDING left and right margins, and the far
5693 * grid line. This is a low level internal routine. It does NOT redraw,
5694 * or reposition objects.
5696 void
5697 sheet_col_set_size_pts (Sheet *sheet, int col, double width_pts,
5698 gboolean set_by_user)
5700 ColRowInfo *ci;
5702 g_return_if_fail (IS_SHEET (sheet));
5703 g_return_if_fail (width_pts > 0.0);
5705 ci = sheet_col_fetch (sheet, col);
5706 ci->hard_size = set_by_user;
5707 if (ci->size_pts == width_pts)
5708 return;
5710 ci->size_pts = width_pts;
5711 colrow_compute_pixels_from_pts (ci, sheet, TRUE, -1);
5713 sheet->priv->recompute_visibility = TRUE;
5714 sheet_flag_recompute_spans (sheet);
5715 if (sheet->priv->reposition_objects.col > col)
5716 sheet->priv->reposition_objects.col = col;
5719 void
5720 sheet_col_set_size_pixels (Sheet *sheet, int col, int width_pixels,
5721 gboolean set_by_user)
5723 ColRowInfo *ci;
5725 g_return_if_fail (IS_SHEET (sheet));
5726 g_return_if_fail (width_pixels > 0.0);
5728 ci = sheet_col_fetch (sheet, col);
5729 ci->hard_size = set_by_user;
5730 if (ci->size_pixels == width_pixels)
5731 return;
5733 ci->size_pixels = width_pixels;
5734 colrow_compute_pts_from_pixels (ci, sheet, TRUE, -1);
5736 sheet->priv->recompute_visibility = TRUE;
5737 sheet_flag_recompute_spans (sheet);
5738 if (sheet->priv->reposition_objects.col > col)
5739 sheet->priv->reposition_objects.col = col;
5743 * sheet_col_get_default_size_pts:
5745 * Return the default number of pts in a column, including margins.
5746 * This function returns the raw sum, no rounding etc.
5748 double
5749 sheet_col_get_default_size_pts (Sheet const *sheet)
5751 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5752 return sheet->cols.default_style.size_pts;
5756 sheet_col_get_default_size_pixels (Sheet const *sheet)
5758 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5759 return sheet->cols.default_style.size_pixels;
5762 void
5763 sheet_col_set_default_size_pts (Sheet *sheet, double width_pts)
5765 g_return_if_fail (IS_SHEET (sheet));
5766 g_return_if_fail (width_pts > 0.);
5768 sheet_colrow_default_calc (sheet, width_pts, TRUE, TRUE);
5769 sheet->priv->recompute_visibility = TRUE;
5770 sheet_flag_recompute_spans (sheet);
5771 sheet->priv->reposition_objects.col = 0;
5773 void
5774 sheet_col_set_default_size_pixels (Sheet *sheet, int width_pixels)
5776 g_return_if_fail (IS_SHEET (sheet));
5778 sheet_colrow_default_calc (sheet, width_pixels, TRUE, FALSE);
5779 sheet->priv->recompute_visibility = TRUE;
5780 sheet_flag_recompute_spans (sheet);
5781 sheet->priv->reposition_objects.col = 0;
5784 /**************************************************************************/
5785 /* Row height support routines
5789 * sheet_row_get_distance_pts:
5791 * Return the number of points between from_row to to_row
5792 * measured from the upper left corner.
5794 double
5795 sheet_row_get_distance_pts (Sheet const *sheet, int from, int to)
5797 ColRowSegment const *segment;
5798 ColRowInfo const *ri;
5799 double const default_size = sheet->rows.default_style.size_pts;
5800 double pts = 0., sign = 1.;
5801 int i;
5803 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5805 if (from > to) {
5806 int const tmp = to;
5807 to = from;
5808 from = tmp;
5809 sign = -1.;
5812 g_return_val_if_fail (from >= 0, 1.);
5813 g_return_val_if_fail (to <= gnm_sheet_get_max_rows (sheet), 1.);
5815 /* Do not use sheet_colrow_foreach, it ignores empties.
5816 * Optimize this so that long jumps are not quite so horrific
5817 * for performance.
5819 for (i = from ; i < to ; ++i) {
5820 segment = COLROW_GET_SEGMENT (&(sheet->rows), i);
5822 if (segment != NULL) {
5823 ri = segment->info[COLROW_SUB_INDEX (i)];
5824 if (ri == NULL)
5825 pts += default_size;
5826 else if (ri->visible)
5827 pts += ri->size_pts;
5828 } else {
5829 int segment_end = COLROW_SEGMENT_END (i)+1;
5830 if (segment_end > to)
5831 segment_end = to;
5832 pts += default_size * (segment_end - i);
5833 i = segment_end-1;
5837 return pts*sign;
5841 * sheet_row_get_distance_pixels:
5843 * Return the number of pixels between from_row to to_row
5844 * measured from the upper left corner.
5847 sheet_row_get_distance_pixels (Sheet const *sheet, int from, int to)
5849 ColRowInfo const *ci;
5850 int dflt, pixels = 0, sign = 1;
5851 int i;
5853 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5855 if (from > to) {
5856 int const tmp = to;
5857 to = from;
5858 from = tmp;
5859 sign = -1;
5862 g_return_val_if_fail (from >= 0, 1);
5863 g_return_val_if_fail (to <= gnm_sheet_get_max_rows (sheet), 1);
5865 /* Do not use sheet_colrow_foreach, it ignores empties */
5866 dflt = sheet_row_get_default_size_pixels (sheet);
5867 for (i = from ; i < to ; ++i) {
5868 if (NULL == (ci = sheet_row_get (sheet, i)))
5869 pixels += dflt;
5870 else if (ci->visible)
5871 pixels += ci->size_pixels;
5874 return pixels * sign;
5878 * sheet_row_set_size_pts:
5879 * @sheet: The sheet
5880 * @row: The row
5881 * @height_pts: The desired height in pts
5882 * @set_by_user: TRUE if this was done by a user (ie, user manually
5883 * set the height)
5885 * Sets height of a row in pts, INCLUDING top and bottom margins, and the lower
5886 * grid line. This is a low level internal routine. It does NOT redraw,
5887 * or reposition objects.
5889 void
5890 sheet_row_set_size_pts (Sheet *sheet, int row, double height_pts,
5891 gboolean set_by_user)
5893 ColRowInfo *ri;
5895 g_return_if_fail (IS_SHEET (sheet));
5896 g_return_if_fail (height_pts > 0.0);
5898 ri = sheet_row_fetch (sheet, row);
5899 ri->hard_size = set_by_user;
5900 if (ri->size_pts == height_pts)
5901 return;
5903 ri->size_pts = height_pts;
5904 colrow_compute_pixels_from_pts (ri, sheet, FALSE, -1);
5906 sheet->priv->recompute_visibility = TRUE;
5907 if (sheet->priv->reposition_objects.row > row)
5908 sheet->priv->reposition_objects.row = row;
5912 * sheet_row_set_size_pixels:
5913 * @sheet: The sheet
5914 * @row: The row
5915 * @height_pixels: The desired height
5916 * @set_by_user: TRUE if this was done by a user (ie, user manually
5917 * set the width)
5919 * Sets height of a row in pixels, INCLUDING top and bottom margins, and the lower
5920 * grid line.
5922 void
5923 sheet_row_set_size_pixels (Sheet *sheet, int row, int height_pixels,
5924 gboolean set_by_user)
5926 ColRowInfo *ri;
5928 g_return_if_fail (IS_SHEET (sheet));
5929 g_return_if_fail (height_pixels > 0);
5931 ri = sheet_row_fetch (sheet, row);
5932 ri->hard_size = set_by_user;
5933 if (ri->size_pixels == height_pixels)
5934 return;
5936 ri->size_pixels = height_pixels;
5937 colrow_compute_pts_from_pixels (ri, sheet, FALSE, -1);
5939 sheet->priv->recompute_visibility = TRUE;
5940 if (sheet->priv->reposition_objects.row > row)
5941 sheet->priv->reposition_objects.row = row;
5945 * sheet_row_get_default_size_pts:
5947 * Return the defaul number of units in a row, including margins.
5948 * This function returns the raw sum, no rounding etc.
5950 double
5951 sheet_row_get_default_size_pts (Sheet const *sheet)
5953 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5954 return sheet->rows.default_style.size_pts;
5958 sheet_row_get_default_size_pixels (Sheet const *sheet)
5960 g_return_val_if_fail (IS_SHEET (sheet), 1.);
5961 return sheet->rows.default_style.size_pixels;
5964 void
5965 sheet_row_set_default_size_pts (Sheet *sheet, double height_pts)
5967 g_return_if_fail (IS_SHEET (sheet));
5969 sheet_colrow_default_calc (sheet, height_pts, FALSE, TRUE);
5970 sheet->priv->recompute_visibility = TRUE;
5971 sheet->priv->reposition_objects.row = 0;
5974 void
5975 sheet_row_set_default_size_pixels (Sheet *sheet, int height_pixels)
5977 g_return_if_fail (IS_SHEET (sheet));
5979 sheet_colrow_default_calc (sheet, height_pixels, FALSE, FALSE);
5980 sheet->priv->recompute_visibility = TRUE;
5981 sheet->priv->reposition_objects.row = 0;
5984 /****************************************************************************/
5986 void
5987 sheet_scrollbar_config (Sheet const *sheet)
5989 g_return_if_fail (IS_SHEET (sheet));
5991 SHEET_FOREACH_CONTROL (sheet, view, control,
5992 sc_scrollbar_config (control););
5995 /*****************************************************************************/
5996 typedef struct
5998 gboolean is_column;
5999 Sheet *sheet;
6000 } closure_clone_colrow;
6002 static gboolean
6003 sheet_clone_colrow_info_item (GnmColRowIter const *iter, void *user_data)
6005 closure_clone_colrow const *closure = user_data;
6006 ColRowInfo *new_colrow = sheet_colrow_fetch (closure->sheet,
6007 iter->pos, closure->is_column);
6008 col_row_info_copy (new_colrow, iter->cri);
6009 return FALSE;
6012 static void
6013 sheet_dup_colrows (Sheet const *src, Sheet *dst)
6015 closure_clone_colrow closure;
6016 int max_col = MIN (gnm_sheet_get_max_cols (src), gnm_sheet_get_max_cols (dst)),
6017 max_row = MIN (gnm_sheet_get_max_rows (src), gnm_sheet_get_max_rows (dst));
6019 closure.sheet = dst;
6020 closure.is_column = TRUE;
6021 sheet_colrow_foreach (src, TRUE, 0, max_col - 1,
6022 &sheet_clone_colrow_info_item, &closure);
6023 closure.is_column = FALSE;
6024 sheet_colrow_foreach (src, FALSE, 0, max_row - 1,
6025 &sheet_clone_colrow_info_item, &closure);
6027 sheet_col_set_default_size_pixels (dst,
6028 sheet_col_get_default_size_pixels (src));
6029 sheet_row_set_default_size_pixels (dst,
6030 sheet_row_get_default_size_pixels (src));
6032 dst->cols.max_outline_level = src->cols.max_outline_level;
6033 dst->rows.max_outline_level = src->rows.max_outline_level;
6036 static void
6037 sheet_dup_styles (Sheet const *src, Sheet *dst)
6039 static GnmCellPos const corner = { 0, 0 };
6040 GnmRange r;
6041 GnmStyleList *styles;
6043 sheet_style_set_auto_pattern_color (
6044 dst, sheet_style_get_auto_pattern_color (src));
6046 styles = sheet_style_get_range (src, range_init_full_sheet (&r, src));
6047 sheet_style_set_list (dst, &corner, styles, NULL, NULL);
6048 style_list_free (styles);
6051 static void
6052 sheet_dup_merged_regions (Sheet const *src, Sheet *dst)
6054 GSList *ptr;
6056 for (ptr = src->list_merged ; ptr != NULL ; ptr = ptr->next)
6057 gnm_sheet_merge_add (dst, ptr->data, FALSE, NULL);
6060 static void
6061 sheet_dup_names (Sheet const *src, Sheet *dst)
6063 GSList *names = gnm_named_expr_collection_list (src->names);
6064 GSList *l;
6065 GnmParsePos dst_pp;
6067 if (names == NULL)
6068 return;
6070 parse_pos_init_sheet (&dst_pp, dst);
6072 /* Pass 1: add placeholders. */
6073 for (l = names; l; l = l->next) {
6074 GnmNamedExpr *src_nexpr = l->data;
6075 char const *name = expr_name_name (src_nexpr);
6076 GnmNamedExpr *dst_nexpr =
6077 gnm_named_expr_collection_lookup (dst->names, name);
6078 GnmExprTop const *texpr;
6080 if (dst_nexpr)
6081 continue;
6083 texpr = gnm_expr_top_new_constant (value_new_empty ());
6084 expr_name_add (&dst_pp, name, texpr , NULL, TRUE, NULL);
6087 /* Pass 2: assign the right expression. */
6088 for (l = names; l; l = l->next) {
6089 GnmNamedExpr *src_nexpr = l->data;
6090 char const *name = expr_name_name (src_nexpr);
6091 GnmNamedExpr *dst_nexpr =
6092 gnm_named_expr_collection_lookup (dst->names, name);
6093 GnmExprTop const *texpr;
6095 if (!dst_nexpr) {
6096 g_warning ("Trouble while duplicating name %s", name);
6097 continue;
6100 if (!dst_nexpr->is_editable)
6101 continue;
6103 texpr = gnm_expr_top_relocate_sheet (src_nexpr->texpr, src, dst);
6104 expr_name_set_expr (dst_nexpr, texpr);
6107 g_slist_free (names);
6110 static void
6111 cb_sheet_cell_copy (G_GNUC_UNUSED gpointer unused, gpointer key, gpointer new_sheet_param)
6113 GnmCell const *cell = key;
6114 Sheet *dst = new_sheet_param;
6115 Sheet *src;
6116 GnmExprTop const *texpr;
6118 g_return_if_fail (dst != NULL);
6119 g_return_if_fail (cell != NULL);
6121 src = cell->base.sheet;
6122 texpr = cell->base.texpr;
6124 if (texpr && gnm_expr_top_is_array_corner (texpr)) {
6125 int cols, rows;
6127 texpr = gnm_expr_top_relocate_sheet (texpr, src, dst);
6128 gnm_expr_top_get_array_size (texpr, &cols, &rows);
6130 gnm_cell_set_array_formula (dst,
6131 cell->pos.col, cell->pos.row,
6132 cell->pos.col + cols - 1,
6133 cell->pos.row + rows - 1,
6134 gnm_expr_top_new (gnm_expr_copy (gnm_expr_top_get_array_expr (texpr))));
6136 gnm_expr_top_unref (texpr);
6137 } else if (texpr && gnm_expr_top_is_array_elem (texpr, NULL, NULL)) {
6138 /* Not a corner -- ignore. */
6139 } else {
6140 GnmCell *new_cell = sheet_cell_create (dst, cell->pos.col, cell->pos.row);
6141 if (gnm_cell_has_expr (cell)) {
6142 texpr = gnm_expr_top_relocate_sheet (texpr, src, dst);
6143 gnm_cell_set_expr_and_value (new_cell, texpr, value_new_empty (), TRUE);
6144 gnm_expr_top_unref (texpr);
6145 } else
6146 gnm_cell_set_value (new_cell, value_dup (cell->value));
6150 static void
6151 sheet_dup_cells (Sheet const *src, Sheet *dst)
6153 sheet_cell_foreach (src, &cb_sheet_cell_copy, dst);
6154 sheet_region_queue_recalc (dst, NULL);
6157 static void
6158 sheet_dup_filters (Sheet const *src, Sheet *dst)
6160 GSList *ptr;
6161 for (ptr = src->filters ; ptr != NULL ; ptr = ptr->next)
6162 gnm_filter_dup (ptr->data, dst);
6163 dst->filters = g_slist_reverse (dst->filters);
6167 * sheet_dup:
6168 * @source_sheet: #Sheet
6170 * Create a new Sheet and return it.
6171 * Returns: (transfer full): the newly allocated #Sheet.
6173 Sheet *
6174 sheet_dup (Sheet const *src)
6176 Workbook *wb;
6177 Sheet *dst;
6178 char *name;
6179 GList *l;
6181 g_return_val_if_fail (IS_SHEET (src), NULL);
6182 g_return_val_if_fail (src->workbook != NULL, NULL);
6184 wb = src->workbook;
6185 name = workbook_sheet_get_free_name (wb, src->name_unquoted,
6186 TRUE, TRUE);
6187 dst = sheet_new_with_type (wb, name, src->sheet_type,
6188 src->size.max_cols, src->size.max_rows);
6189 g_free (name);
6191 dst->protected_allow = src->protected_allow;
6192 g_object_set (dst,
6193 "zoom-factor", src->last_zoom_factor_used,
6194 "text-is-rtl", src->text_is_rtl,
6195 "visibility", src->visibility,
6196 "protected", src->is_protected,
6197 "display-formulas", src->display_formulas,
6198 "display-zeros", !src->hide_zero,
6199 "display-grid", !src->hide_grid,
6200 "display-column-header", !src->hide_col_header,
6201 "display-row-header", !src->hide_row_header,
6202 "display-outlines", src->display_outlines,
6203 "display-outlines-below", src->outline_symbols_below,
6204 "display-outlines-right", src->outline_symbols_right,
6205 "conventions", src->convs,
6206 "tab-foreground", src->tab_text_color,
6207 "tab-background", src->tab_color,
6208 NULL);
6210 gnm_print_info_free (dst->print_info);
6211 dst->print_info = gnm_print_info_dup (src->print_info);
6213 sheet_dup_styles (src, dst);
6214 sheet_dup_merged_regions (src, dst);
6215 sheet_dup_colrows (src, dst);
6216 sheet_dup_names (src, dst);
6217 sheet_dup_cells (src, dst);
6218 sheet_objects_dup (src, dst, NULL);
6219 sheet_dup_filters (src, dst); /* must be after objects */
6221 #warning selection is in view
6222 #warning freeze/thaw is in view
6224 g_object_unref (dst->solver_parameters);
6225 dst->solver_parameters = gnm_solver_param_dup (src->solver_parameters, dst);
6227 for (l = src->scenarios; l; l = l->next) {
6228 GnmScenario *src_sc = l->data;
6229 GnmScenario *dst_sc = gnm_scenario_dup (src_sc, dst);
6230 dst->scenarios = g_list_prepend (dst->scenarios, dst_sc);
6232 dst->scenarios = g_list_reverse (dst->scenarios);
6234 sheet_mark_dirty (dst);
6235 sheet_redraw_all (dst, TRUE);
6237 return dst;
6241 * sheet_set_outline_direction:
6242 * @sheet: the sheet
6243 * @is_cols: use cols or rows
6245 * When changing the placement of outline collapse markers the flags
6246 * need to be recomputed.
6248 void
6249 sheet_set_outline_direction (Sheet *sheet, gboolean is_cols)
6251 unsigned i;
6252 g_return_if_fail (IS_SHEET (sheet));
6254 /* not particularly efficient, but this is not a hot spot */
6255 for (i = colrow_max (is_cols, sheet); i-- > 0 ; )
6256 sheet_colrow_set_collapse (sheet, is_cols, i);
6260 * sheet_get_view:
6261 * @sheet:
6262 * @wbv:
6264 * Find the SheetView corresponding to the supplied @wbv.
6265 * Returns: (transfer none): the view.
6267 SheetView *
6268 sheet_get_view (Sheet const *sheet, WorkbookView const *wbv)
6270 if (sheet == NULL)
6271 return NULL;
6273 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6275 SHEET_FOREACH_VIEW (sheet, view, {
6276 if (sv_wbv (view) == wbv)
6277 return view;
6279 return NULL;
6282 static gboolean
6283 cb_queue_respan (GnmColRowIter const *iter, void *user_data)
6285 ((ColRowInfo *)(iter->cri))->needs_respan = TRUE;
6286 return FALSE;
6290 * sheet_queue_respan:
6291 * @sheet:
6292 * @start_row:
6293 * @end_row:
6295 * queues a span generation for the selected rows.
6296 * the caller is responsible for queuing a redraw
6298 void
6299 sheet_queue_respan (Sheet const *sheet, int start_row, int end_row)
6301 sheet_colrow_foreach (sheet, FALSE, start_row, end_row,
6302 cb_queue_respan, NULL);
6305 void
6306 sheet_cell_queue_respan (GnmCell *cell)
6308 ColRowInfo *ri = sheet_row_get (cell->base.sheet, cell->pos.row);
6309 ri->needs_respan = TRUE;
6314 * sheet_get_comment:
6315 * @sheet: #Sheet const *
6316 * @pos: #GnmCellPos const *
6318 * If there is a cell comment at @pos in @sheet return it.
6320 * Caller does get a reference to the object if it exists.
6321 * Returns: (transfer full): the comment or %NULL.
6323 GnmComment *
6324 sheet_get_comment (Sheet const *sheet, GnmCellPos const *pos)
6326 GnmRange r;
6327 GSList *comments;
6328 GnmComment *res;
6330 GnmRange const *mr;
6332 mr = gnm_sheet_merge_contains_pos (sheet, pos);
6334 if (mr)
6335 comments = sheet_objects_get (sheet, mr, GNM_CELL_COMMENT_TYPE);
6336 else {
6337 r.start = r.end = *pos;
6338 comments = sheet_objects_get (sheet, &r, GNM_CELL_COMMENT_TYPE);
6340 if (!comments)
6341 return NULL;
6343 /* This assumes just one comment per cell. */
6344 res = comments->data;
6345 g_slist_free (comments);
6346 return res;
6349 static GnmValue *
6350 cb_find_extents (GnmCellIter const *iter, GnmCellPos *extent)
6352 if (extent->col < iter->pp.eval.col)
6353 extent->col = iter->pp.eval.col;
6354 if (extent->row < iter->pp.eval.row)
6355 extent->row = iter->pp.eval.row;
6356 return NULL;
6360 * sheet_range_trim:
6361 * @sheet: sheet cells are contained on
6362 * @r: range to trim empty cells from
6363 * @cols: trim from right
6364 * @rows: trim from bottom
6366 * This removes empty rows/cols from the
6367 * right hand or bottom edges of the range
6368 * depending on the value of @cols or @rows.
6370 * Return value: TRUE if the range was totally empty.
6372 gboolean
6373 sheet_range_trim (Sheet const *sheet, GnmRange *r,
6374 gboolean cols, gboolean rows)
6376 GnmCellPos extent = { -1, -1 };
6378 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
6379 g_return_val_if_fail (r != NULL, TRUE);
6381 sheet_foreach_cell_in_range (
6382 (Sheet *)sheet, CELL_ITER_IGNORE_BLANK, r,
6383 (CellIterFunc) cb_find_extents, &extent);
6385 if (extent.col < 0 || extent.row < 0)
6386 return TRUE;
6387 if (cols)
6388 r->end.col = extent.col;
6389 if (rows)
6390 r->end.row = extent.row;
6391 return FALSE;
6395 * sheet_range_has_heading:
6396 * @sheet: Sheet to check
6397 * @src: GnmRange to check
6398 * @top: Flag
6400 * Checks for a header row in @sheet!@src. If top is true it looks for a
6401 * header row from the top and if false it looks for a header col from the
6402 * left
6404 * Returns: TRUE if @src seems to have a heading
6406 gboolean
6407 sheet_range_has_heading (Sheet const *sheet, GnmRange const *src,
6408 gboolean top, gboolean ignore_styles)
6410 GnmCell const *a, *b;
6411 int length, i;
6413 /* There is only one row or col */
6414 if (top) {
6415 if (src->end.row <= src->start.row)
6416 return FALSE;
6417 length = src->end.col - src->start.col + 1;
6418 } else {
6419 if (src->end.col <= src->start.col)
6420 return FALSE;
6421 length = src->end.row - src->start.row + 1;
6424 for (i = 0; i < length; i++) {
6425 if (top) {
6426 a = sheet_cell_get (sheet,
6427 src->start.col + i, src->start.row);
6428 b = sheet_cell_get (sheet,
6429 src->start.col + i, src->start.row + 1);
6430 } else {
6431 a = sheet_cell_get (sheet,
6432 src->start.col, src->start.row + i);
6433 b = sheet_cell_get (sheet,
6434 src->start.col + 1, src->start.row + i);
6437 /* be anal */
6438 if (a == NULL || a->value == NULL || b == NULL || b->value == NULL)
6439 continue;
6441 if (VALUE_IS_NUMBER (a->value)) {
6442 if (!VALUE_IS_NUMBER (b->value))
6443 return TRUE;
6444 /* check for style differences */
6445 } else if (a->value->v_any.type != b->value->v_any.type)
6446 return TRUE;
6448 /* Look for style differences */
6449 if (!ignore_styles &&
6450 !gnm_style_equal_header (gnm_cell_get_style (a),
6451 gnm_cell_get_style (b), top))
6452 return TRUE;
6455 return FALSE;
6459 * gnm_sheet_foreach_name:
6460 * @sheet: #Sheet
6461 * @func: (scope call): #GHFunc
6462 * @data: user data.
6464 * Executes @func for each name in @sheet.
6466 void
6467 gnm_sheet_foreach_name (Sheet const *sheet, GHFunc func, gpointer data)
6469 g_return_if_fail (IS_SHEET (sheet));
6471 if (sheet->names)
6472 gnm_named_expr_collection_foreach (sheet->names, func, data);
6476 * gnm_sheet_get_size:
6477 * @sheet: #Sheet
6479 * Returns: (transfer none): the sheet size.
6481 GnmSheetSize const *
6482 gnm_sheet_get_size (Sheet const *sheet)
6484 static const GnmSheetSize default_size = {
6485 GNM_DEFAULT_COLS, GNM_DEFAULT_ROWS
6488 if (G_UNLIKELY (!sheet)) {
6489 g_warning ("NULL sheet in gnm_sheet_get_size!");
6490 /* FIXME: This needs to go. */
6491 return &default_size;
6494 if (G_UNLIKELY (sheet->being_constructed))
6495 g_warning ("Access to sheet size during construction!");
6497 return &sheet->size;
6501 * gnm_sheet_get_size2:
6502 * @sheet: #Sheet, might be %NULL
6503 * @wb: #Workbook, must be non %NULL if @sheet is %NULL
6505 * Returns: (transfer none): the sheet size if @sheet is non %NULL, or the
6506 * default sheet size for @wb.
6508 GnmSheetSize const *
6509 gnm_sheet_get_size2 (Sheet const *sheet, Workbook const *wb)
6511 return sheet
6512 ? gnm_sheet_get_size (sheet)
6513 : workbook_get_sheet_size (wb);
6516 void
6517 gnm_sheet_set_solver_params (Sheet *sheet, GnmSolverParameters *param)
6519 g_return_if_fail (IS_SHEET (sheet));
6520 g_return_if_fail (GNM_IS_SOLVER_PARAMETERS (param));
6522 g_object_ref (param);
6523 g_object_unref (sheet->solver_parameters);
6524 sheet->solver_parameters = param;
6527 /* ------------------------------------------------------------------------- */
6530 * gnm_sheet_scenario_new:
6531 * @sheet:  #Sheet
6532 * @name: the new scenario name.
6534 * Returns: (transfer full): the newly created #GnmScenario.
6536 GnmScenario *
6537 gnm_sheet_scenario_new (Sheet *sheet, const char *name)
6539 GnmScenario *sc;
6540 char *actual_name;
6542 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6543 g_return_val_if_fail (name != NULL, NULL);
6545 /* Check if a scenario having the same name already exists. */
6546 if (gnm_sheet_scenario_find (sheet, name)) {
6547 GString *str = g_string_new (NULL);
6548 gchar *tmp;
6549 int i, j, len;
6551 len = strlen (name);
6552 if (len > 1 && name [len - 1] == ']') {
6553 for (i = len - 2; i > 0; i--) {
6554 if (! g_ascii_isdigit (name [i]))
6555 break;
6558 tmp = g_strdup (name);
6559 if (i > 0 && name [i] == '[')
6560 tmp [i] = '\0';
6561 } else
6562 tmp = g_strdup (name);
6564 for (j = 1; ; j++) {
6565 g_string_printf (str, "%s [%d]", tmp, j);
6566 if (!gnm_sheet_scenario_find (sheet, str->str)) {
6567 actual_name = g_string_free (str, FALSE);
6568 str = NULL;
6569 break;
6572 if (str)
6573 g_string_free (str, TRUE);
6574 g_free (tmp);
6575 } else
6576 actual_name = g_strdup (name);
6578 sc = gnm_scenario_new (actual_name, sheet);
6580 g_free (actual_name);
6582 return sc;
6586 * gnm_sheet_scenario_find:
6587 * @sheet:  #Sheet
6588 * @name: the scenario name.
6590 * Returns: (transfer none): the newly created #GnmScenario.
6592 GnmScenario *
6593 gnm_sheet_scenario_find (Sheet *sheet, const char *name)
6595 GList *l;
6597 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6598 g_return_val_if_fail (name != NULL, NULL);
6600 for (l = sheet->scenarios; l; l = l->next) {
6601 GnmScenario *sc = l->data;
6602 if (strcmp (name, sc->name) == 0)
6603 return sc;
6606 return NULL;
6610 * gnm_sheet_scenario_add:
6611 * @sheet:  #Sheet
6612 * @sc: (transfer full): #GnmScenario
6615 void
6616 gnm_sheet_scenario_add (Sheet *sheet, GnmScenario *sc)
6618 g_return_if_fail (IS_SHEET (sheet));
6619 g_return_if_fail (GNM_IS_SCENARIO (sc));
6621 /* We take ownership of the ref. */
6622 sheet->scenarios = g_list_append (sheet->scenarios, sc);
6625 void
6626 gnm_sheet_scenario_remove (Sheet *sheet, GnmScenario *sc)
6628 g_return_if_fail (IS_SHEET (sheet));
6629 g_return_if_fail (GNM_IS_SCENARIO (sc));
6631 sheet->scenarios = g_list_remove (sheet->scenarios, sc);
6632 g_object_unref (sc);
6635 /* ------------------------------------------------------------------------- */
6638 * gnm_sheet_get_sort_setups:
6639 * @sheet: #Sheet
6641 * Returns: (transfer none): the sort setups for @sheet.
6643 GHashTable *
6644 gnm_sheet_get_sort_setups (Sheet *sheet)
6646 GHashTable *hash = sheet->sort_setups;
6648 if (hash == NULL)
6649 hash = sheet->sort_setups =
6650 g_hash_table_new_full
6651 (g_str_hash, g_str_equal,
6652 g_free, (GDestroyNotify)gnm_sort_data_destroy);
6654 return hash;
6657 void
6658 gnm_sheet_add_sort_setup (Sheet *sheet, char *key, gpointer setup)
6660 GHashTable *hash = gnm_sheet_get_sort_setups (sheet);
6662 g_hash_table_insert (hash, key, setup);
6666 * gnm_sheet_find_sort_setup:
6667 * @sheet: #Sheet
6668 * @key:
6670 * Returns: (transfer none): the found sort setup or %NULL.
6672 gconstpointer
6673 gnm_sheet_find_sort_setup (Sheet *sheet, char const *key)
6675 if (sheet->sort_setups == NULL)
6676 return NULL;
6677 return g_hash_table_lookup (sheet->sort_setups, key);
6681 * sheet_date_conv:
6682 * @sheet: #Sheet
6684 * Returns: (transfer none): the date conventions in effect for the sheet.
6685 * This is purely a convenience function to access the conventions used
6686 * for the workbook. All sheets in a workbook share the same date
6687 * conventions.
6689 GODateConventions const *
6690 sheet_date_conv (Sheet const *sheet)
6692 g_return_val_if_fail (IS_SHEET (sheet), NULL);
6693 return workbook_date_conv (sheet->workbook);