Use application properties instead of ugly dual-use global variable
[gnumeric.git] / src / colrow.c
blob080ec37c73d856a23006ef0b0d8a5c1b952ce02a
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * colrow.c: Utilities for Rows and Columns
5 * Copyright (C) 1999-2007 Jody Goldberg (jody@gnome.org)
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20 * USA
22 #include <gnumeric-config.h>
23 #include "gnumeric.h"
24 #include "colrow.h"
26 #include "sheet.h"
27 #include "sheet-view.h"
28 #include "sheet-private.h"
29 #include "application.h"
30 #include "parse-util.h"
31 #include "selection.h"
32 #include "ranges.h"
33 #include "sheet-merge.h"
34 #include "cell.h"
35 #include "cellspan.h"
36 #include "rendered-value.h"
37 #include <goffice/goffice.h>
38 #include <string.h>
40 /* Making ColRowInfo a boxed type to make introspection happy. using no-op
41 * functions for copy and free, and crossing fingers.
43 static ColRowInfo *
44 col_row_info_fake_copy (ColRowInfo *cri)
46 return cri;
49 GType
50 col_row_info_get_type (void)
52 static GType t = 0;
54 if (t == 0) {
55 t = g_boxed_type_register_static ("ColRowInfo",
56 (GBoxedCopyFunc)col_row_info_fake_copy,
57 (GBoxedFreeFunc)col_row_info_fake_copy);
59 return t;
62 double
63 colrow_compute_pixel_scale (Sheet const *sheet, gboolean horizontal)
65 return (sheet ? sheet->last_zoom_factor_used : 1.0) *
66 gnm_app_display_dpi_get (horizontal) / 72.;
69 void
70 colrow_compute_pixels_from_pts (ColRowInfo *cri, Sheet const *sheet,
71 gboolean horizontal, double scale)
73 int const margin = horizontal ? 2*GNM_COL_MARGIN : 2*GNM_ROW_MARGIN;
75 if (scale == -1)
76 scale = colrow_compute_pixel_scale (sheet, horizontal);
78 if (horizontal && sheet && sheet->display_formulas)
79 scale *= 2;
81 cri->size_pixels = (int)(cri->size_pts * scale + 0.5);
83 if (cri->size_pixels <= margin)
84 cri->size_pixels = margin + 1;
87 void
88 colrow_compute_pts_from_pixels (ColRowInfo *cri, Sheet const *sheet,
89 gboolean horizontal, double scale)
91 if (scale <= 0.)
92 scale = colrow_compute_pixel_scale (sheet, horizontal);
94 if (horizontal && sheet->display_formulas)
95 scale *= 2;
97 cri->size_pts = cri->size_pixels / scale;
98 #if 0
99 /* Disable this until we decide how to deal with scaling */
100 g_return_if_fail (cri->size_pts >= cri->margin_a + cri->margin_b);
101 #endif
105 * col_row_info_is_default:
106 * @cri: #ColRowInfo
108 * %TRUE if @cri is the default style for columns or rows.
110 gboolean
111 col_row_info_is_default (ColRowInfo const *cri)
113 g_return_val_if_fail (cri != NULL, FALSE);
114 return cri->is_default;
118 * col_row_info_is_empty:
119 * @cri: #ColRowInfo
121 * %TRUE if there is no information in col/row @cri.
123 gboolean
124 col_row_info_is_empty (ColRowInfo const *cri)
126 if (cri == NULL)
127 return TRUE;
128 return cri->is_default &&
129 cri->outline_level == 0 &&
130 !cri->is_collapsed &&
131 !cri->hard_size;
135 * col_row_info_equal:
136 * @a: First #ColRowInfo
137 * @b: Second #ColRowInfo
139 * Returns %TRUE if the infos are equivalent.
141 gboolean
142 col_row_info_equal (ColRowInfo const *a, ColRowInfo const *b)
144 if (a == NULL)
145 return b == NULL;
146 if (b == NULL)
147 return FALSE;
149 return fabs (a->size_pts - b->size_pts) < 1e-5 &&
150 a->outline_level == b->outline_level &&
151 a->is_collapsed == b->is_collapsed &&
152 a->hard_size == b->hard_size &&
153 a->visible == b->visible;
157 * col_row_info_copy:
158 * @dst: Destination #ColRowInfo
159 * @src: Source #ColRowInfo
161 * Copy all content, except the position of @src to @dst.
163 void
164 col_row_info_copy (ColRowInfo *dst, ColRowInfo const *src)
166 dst->size_pts = src->size_pts;
167 dst->size_pixels = src->size_pixels;
168 dst->outline_level = src->outline_level;
169 dst->is_collapsed = src->is_collapsed;
170 dst->hard_size = src->hard_size;
171 dst->visible = src->visible;
174 ColRowInfo *
175 col_row_info_new (void)
177 return g_slice_new (ColRowInfo);
180 void
181 colrow_free (ColRowInfo *cri)
183 g_slice_free1 (sizeof (*cri), cri);
188 * colrow_state_list_foreach:
189 * @list: The #ColRowStateList to iterate.
190 * @sheet: (nullable): Origin #Sheet.
191 * @is_cols: %TRUE for columns, %FALSE for rows.
192 * @base: index of first column or row.
193 * @callback: (scope call): A callback function which should
194 * return %TRUE to stop the iteration.
195 * @user_data: A baggage pointer.
197 * Iterates through the existing rows or columns within the range supplied.
198 * Currently only support left -> right iteration. If a callback returns
199 * %TRUE iteration stops.
201 gboolean
202 colrow_state_list_foreach (ColRowStateList *list,
203 Sheet const *sheet, gboolean is_cols,
204 int base,
205 ColRowHandler callback,
206 gpointer user_data)
208 GnmColRowIter iter;
209 int i = base;
210 ColRowStateList *l;
211 ColRowInfo cri;
212 double scale = colrow_compute_pixel_scale (sheet, is_cols);
214 // This sets various fields we do not have
215 memset (&cri, 0, sizeof (cri));
217 iter.cri = &cri;
218 for (l = list; l; l = l->next) {
219 ColRowRLEState *rle = l->data;
220 ColRowState const *state = &rle->state;
221 int l;
223 cri.size_pts = state->size_pts;
224 cri.outline_level = state->outline_level;
225 cri.is_collapsed = state->is_collapsed;
226 cri.hard_size = state->hard_size;
227 cri.visible = state->visible;
228 colrow_compute_pixels_from_pts (&cri, sheet, is_cols, scale);
230 for (l = 0; l < rle->length; l++) {
231 iter.pos = i++;
232 if (iter.cri && (*callback)(&iter, user_data))
233 return TRUE;
236 return FALSE;
240 /*****************************************************************************/
242 typedef struct _ColRowIndex {
243 int first, last;
244 } ColRowIndex;
247 static void
248 cb_colrow_index_counter (gpointer data, gpointer user_data)
250 ColRowIndex *index = data;
251 gint *count = user_data;
252 if (data != NULL)
253 *count += index->last - index->first + 1;
256 gint
257 colrow_vis_list_length (ColRowVisList *list)
259 gint count = 0;
260 g_slist_foreach (list, cb_colrow_index_counter, &count);
261 return count;
265 * colrow_state_group_destroy:
266 * @set: (transfer full): the group to destroy.
268 * Returns: (transfer none) (nullable): %NULL.
270 ColRowStateGroup *
271 colrow_state_group_destroy (ColRowStateGroup *group)
273 ColRowStateGroup *ptr;
274 for (ptr = group; ptr != NULL ; ptr = ptr->next)
275 colrow_state_list_destroy (ptr->data);
276 g_slist_free (group);
277 return NULL;
280 static gint
281 colrow_index_compare (ColRowIndex const * a, ColRowIndex const * b)
283 return a->first - b->first;
287 * colrow_index_list_to_string: Convert an index list into a string.
288 * The result must be freed by the caller.
289 * It will be something like : A-B, F-G
291 * @list: The list
292 * @is_cols: %TRUE for columns, %FALSE for rows.
293 * @is_single: If non-null this will be set to %TRUE if there's only a single col/row involved.
295 GString *
296 colrow_index_list_to_string (ColRowIndexList *list, gboolean is_cols, gboolean *is_single)
298 ColRowIndexList *ptr;
299 GString *result;
300 gboolean single = TRUE;
302 g_return_val_if_fail (list != NULL, NULL);
304 result = g_string_new (NULL);
305 for (ptr = list; ptr != NULL; ptr = ptr->next) {
306 ColRowIndex *index = ptr->data;
308 if (is_cols)
309 g_string_append (result, cols_name (index->first, index->last));
310 else
311 g_string_append (result, rows_name (index->first, index->last));
313 if (index->last != index->first)
314 single = FALSE;
316 if (ptr->next) {
317 g_string_append (result, ", ");
318 single = FALSE;
322 if (is_single)
323 *is_single = single;
325 return result;
329 * colrow_get_index_list:
330 * @first:
331 * @last:
332 * @list: (transfer full):
334 * Build an ordered list of pairs doing intelligent merging
335 * of overlapping regions.
337 * Returns: (transfer full): @list.
339 ColRowIndexList *
340 colrow_get_index_list (int first, int last, ColRowIndexList *list)
342 ColRowIndex *tmp, *prev;
343 GList *ptr;
345 tmp = g_new (ColRowIndex, 1);
346 tmp->first = first;
347 tmp->last = last;
349 list = g_list_insert_sorted (list, tmp,
350 (GCompareFunc)&colrow_index_compare);
352 prev = list->data;
353 for (ptr = list->next ; ptr != NULL ; ) {
354 tmp = ptr->data;
356 /* at the end of existing segment or contained */
357 if (prev->last+1 >= tmp->first) {
358 GList *next = ptr->next;
359 if (prev->last < tmp->last)
360 prev->last = tmp->last;
361 list = g_list_remove_link (list, ptr);
362 ptr = next;
363 } else {
364 ptr = ptr->next;
365 prev = tmp;
368 return list;
372 * colrow_index_list_copy:
373 * @list: #ColRowIndexList
375 * Returns: (transfer full):
377 ColRowIndexList *
378 colrow_index_list_copy (ColRowIndexList *list)
380 GList *copy = NULL, *ptr;
382 for (ptr = list ; ptr != NULL ; ptr = ptr->next) {
383 ColRowIndex *tmp = g_new (ColRowIndex, 1);
384 ColRowIndex *ex = ptr->data;
385 tmp->first = ex->first;
386 tmp->last = ex->last;
387 copy = g_list_prepend (copy, tmp);
389 return g_list_reverse (copy);
392 static void
393 colrow_set_single_state (ColRowState *state,
394 Sheet *sheet, int i, gboolean is_cols)
396 ColRowInfo const *info = sheet_colrow_get_info (sheet, i, is_cols);
397 state->is_default = col_row_info_is_default (info);
398 state->size_pts = info->size_pts;
399 state->outline_level = info->outline_level;
400 state->is_collapsed = info->is_collapsed;
401 state->hard_size = info->hard_size;
402 state->visible = info->visible;
406 * colrow_state_list_destroy:
407 * @list: (transfer full): the list to destroy.
409 * Returns: (transfer none): %NULL.
411 ColRowStateList *
412 colrow_state_list_destroy (ColRowStateList *list)
414 g_slist_free_full (list, g_free);
415 return NULL;
419 * colrow_get_states: (skip)
420 * @sheet: #Sheet
421 * @is_cols: %TRUE for columns, %FALSE for rows.
422 * @first: first column or row.
423 * @last: last column or row.
425 * Returns: (transfer full):
427 ColRowStateList *
428 colrow_get_states (Sheet *sheet, gboolean is_cols, int first, int last)
430 ColRowStateList *list = NULL;
431 ColRowRLEState *rles;
432 ColRowState run_state;
433 int i, run_length;
435 g_return_val_if_fail (IS_SHEET (sheet), NULL);
436 g_return_val_if_fail (first <= last, NULL);
438 colrow_set_single_state (&run_state, sheet, first, is_cols);
439 run_length = 1;
441 for (i = first + 1; i <= last; ++i) {
442 ColRowState cur_state;
443 colrow_set_single_state (&cur_state, sheet, i, is_cols);
445 /* If state changed, start a new block */
446 if (cur_state.is_default != run_state.is_default ||
447 cur_state.size_pts != run_state.size_pts ||
448 cur_state.outline_level != run_state.outline_level ||
449 cur_state.is_collapsed != run_state.is_collapsed ||
450 cur_state.hard_size != run_state.hard_size ||
451 cur_state.visible != run_state.visible) {
452 rles = g_new (ColRowRLEState, 1);
453 rles->length = run_length;
454 rles->state = run_state;
455 list = g_slist_prepend (list, rles);
457 run_state = cur_state;
458 run_length = 1;
459 } else
460 ++run_length;
463 /* Store the final run */
464 rles = g_new (ColRowRLEState, 1);
465 rles->length = run_length;
466 rles->state = run_state;
467 list = g_slist_prepend (list, rles);
469 return g_slist_reverse (list);
472 struct resize_closure {
473 Sheet *sheet;
474 int new_size;
475 gboolean is_cols;
478 static gboolean
479 cb_set_colrow_size (GnmColRowIter const *iter, gpointer userdata)
481 if (iter->cri->visible) {
482 struct resize_closure const *c = userdata;
484 if (c->is_cols)
485 sheet_col_set_size_pixels (c->sheet, iter->pos,
486 c->new_size, TRUE);
487 else
488 sheet_row_set_size_pixels (c->sheet, iter->pos,
489 c->new_size, TRUE);
491 return FALSE;
494 static GnmValue *
495 cb_clear_variable_width_content (GnmCellIter const *iter,
496 G_GNUC_UNUSED gpointer user)
498 GnmRenderedValue *rv = gnm_cell_get_rendered_value (iter->cell);
499 if (rv && rv->variable_width) {
500 iter->ri->needs_respan = TRUE;
501 gnm_cell_unrender (iter->cell);
503 return NULL;
507 * colrow_get_sizes: (skip)
508 * @sheet: #Sheet
509 * @is_cols: %TRUE for columns, %FALSE for rows.
510 * @src:
511 * @new_size:
513 * Returns: (transfer full):
515 ColRowStateGroup *
516 colrow_get_sizes (Sheet *sheet, gboolean is_cols,
517 ColRowIndexList *src, int new_size)
519 ColRowStateGroup *res = NULL;
520 ColRowIndexList *ptr;
522 for (ptr = src; ptr != NULL ; ptr = ptr->next) {
523 ColRowIndex const *index = ptr->data;
524 res = g_slist_prepend (res, colrow_get_states (sheet, is_cols,
525 index->first, index->last));
527 if (new_size > 0 && index->first == 0 &&
528 (index->last+1) >= colrow_max (is_cols, sheet)) {
529 ColRowRLEState *rles = g_new0 (ColRowRLEState, 1);
531 rles->length = -1; /* Flag as changing the default */
533 if (is_cols)
534 rles->state.size_pts = sheet_col_get_default_size_pts (sheet);
535 else
536 rles->state.size_pts = sheet_row_get_default_size_pts (sheet);
538 /* Result is a magic 'default' record + >= 1 normal */
539 return g_slist_prepend (res, g_slist_append (NULL, rles));
543 return res;
547 * colrow_set_sizes: (skip)
548 * @sheet: #Sheet
549 * @is_cols: %TRUE for columns, %FALSE for rows.
550 * @src:
551 * @new_size:
552 * @from:
553 * @to:
555 * Returns: (transfer full):
557 ColRowStateGroup *
558 colrow_set_sizes (Sheet *sheet, gboolean is_cols,
559 ColRowIndexList *src, int new_size, int from, int to)
560 /* from & to are used to restrict fitting to that range. Pass 0, -1 if you want to use the */
561 /* whole row/column */
563 int i;
564 ColRowStateGroup *res = NULL;
565 ColRowIndexList *ptr;
567 for (ptr = src; ptr != NULL ; ptr = ptr->next) {
568 ColRowIndex const *index = ptr->data;
569 res = g_slist_prepend (res, colrow_get_states (sheet, is_cols,
570 index->first, index->last));
572 /* FIXME:
573 * If we are changing the size of more than half of the rows/col to
574 * something specific (not autosize) we should change the default
575 * row/col size instead. However, it is unclear how to handle
576 * hard sizing.
578 * we need better management of rows/cols. Currently if they are all
579 * defined calculation speed grinds to a halt.
581 if (new_size > 0 && index->first == 0 &&
582 (index->last+1) >= colrow_max (is_cols, sheet)) {
583 struct resize_closure closure;
584 ColRowRLEState *rles = g_new0 (ColRowRLEState, 1);
586 rles->length = -1; /* Flag as changing the default */
588 closure.sheet = sheet;
589 closure.new_size = new_size;
590 closure.is_cols = is_cols;
591 if (is_cols) {
592 rles->state.size_pts = sheet_col_get_default_size_pts (sheet);
593 sheet_col_set_default_size_pixels (sheet, new_size);
594 sheet_colrow_foreach (sheet, TRUE, 0, -1,
595 &cb_set_colrow_size, &closure);
596 } else {
597 rles->state.size_pts = sheet_row_get_default_size_pts (sheet);
598 sheet_row_set_default_size_pixels (sheet, new_size);
599 sheet_colrow_foreach (sheet, FALSE, 0, -1,
600 &cb_set_colrow_size, &closure);
603 /* force a re-render of cells with expanding formats */
604 if (is_cols)
605 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_BLANK,
606 0, 0, -1, -1,
607 (CellIterFunc) &cb_clear_variable_width_content, NULL);
609 /* Result is a magic 'default' record + >= 1 normal */
610 return g_slist_prepend (res, g_slist_append (NULL, rles));
613 if (is_cols) {
614 /* force a re-render of cells with expanding formats */
615 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_BLANK,
616 index->first, 0, index->last, -1,
617 (CellIterFunc) &cb_clear_variable_width_content, NULL);
619 /* In order to properly reposition cell comments in
620 * merged cells that cross the boundary we need to do
621 * everything. Remove this when comments are handled
622 * properly */
623 sheet->priv->reposition_objects.col = 0;
626 for (i = index->first ; i <= index->last ; ++i) {
627 int tmp = new_size;
628 if (tmp < 0) {
629 int max = is_cols ? gnm_sheet_get_last_row (sheet)
630 : gnm_sheet_get_last_col (sheet);
631 if (from < 0)
632 from = 0;
633 if (to < 0 || to > max)
634 to = max;
635 if (from > max)
636 from = to;
637 /* Fall back to assigning the default if it is empty */
638 tmp = (is_cols)
639 ? sheet_col_size_fit_pixels (sheet, i, from, to, FALSE)
640 : sheet_row_size_fit_pixels (sheet, i, from, to, FALSE);
642 if (tmp > 0) {
643 if (is_cols)
644 sheet_col_set_size_pixels (sheet, i, tmp, new_size > 0);
645 else
646 sheet_row_set_size_pixels (sheet, i, tmp, new_size > 0);
647 } else if (sheet_colrow_get (sheet, i, is_cols) != NULL) {
648 if (is_cols)
649 sheet_col_set_size_pixels (sheet, i,
650 sheet_col_get_default_size_pixels (sheet), FALSE);
651 else
652 sheet_row_set_size_pixels (sheet, i,
653 sheet_row_get_default_size_pixels (sheet), FALSE);
658 return res;
662 * colrow_set_states: (skip)
663 * @sheet: #Sheet
664 * @is_cols: %TRUE for columns, %FALSE for rows.
665 * @first: first column or row
666 * @states: saved state to restore.
668 * This is a low level routine it does not redraw or reposition objects
670 void
671 colrow_set_states (Sheet *sheet, gboolean is_cols,
672 int first, ColRowStateList *states)
674 GSList *l;
675 int i, max_outline, offset = first;
676 ColRowCollection *infos;
677 double scale;
679 g_return_if_fail (IS_SHEET (sheet));
681 infos = is_cols ? &(sheet->cols) : &(sheet->rows);
682 max_outline = infos->max_outline_level;
683 scale = colrow_compute_pixel_scale (sheet, is_cols);
685 for (l = states; l != NULL; l = l->next) {
686 ColRowRLEState const *rles = l->data;
687 ColRowState const *state = &rles->state;
689 if (max_outline < state->outline_level)
690 max_outline = state->outline_level;
692 for (i = offset; i < offset + rles->length; i++) {
693 if (state->is_default) {
694 ColRowSegment *segment = COLROW_GET_SEGMENT(infos, i);
695 if (segment != NULL) {
696 int const sub = COLROW_SUB_INDEX (i);
697 ColRowInfo *cri = segment->info[sub];
698 if (cri != NULL) {
699 segment->info[sub] = NULL;
700 colrow_free (cri);
703 } else {
704 ColRowInfo *cri = sheet_colrow_fetch (sheet, i, is_cols);
705 cri->hard_size = state->hard_size;
706 cri->size_pts = state->size_pts;
707 colrow_compute_pixels_from_pts (cri, sheet, is_cols, scale);
708 col_row_info_set_outline (cri, state->outline_level,
709 state->is_collapsed);
712 offset += rles->length;
715 /* Notify sheet of pending update */
716 sheet->priv->recompute_visibility = TRUE;
717 if (is_cols) {
718 sheet_flag_recompute_spans (sheet);
720 /* In order to properly reposition cell
721 * comments in merged cells that cross the
722 * boundary we need to do everything. Revert
723 * this when comments are handled properly */
724 #if 0
725 if (sheet->priv->reposition_objects.col > first)
726 sheet->priv->reposition_objects.col = first;
727 #else
728 sheet->priv->reposition_objects.col = 0;
729 #endif
730 } else {
731 if (sheet->priv->reposition_objects.row > first)
732 sheet->priv->reposition_objects.row = first;
734 sheet_colrow_gutter (sheet, is_cols, max_outline);
738 * colrow_restore_state_group: (skip)
739 * @sheet: #Sheet
740 * @is_cols: %TRUE for columns, %FALSE for rows.
741 * @selection:
742 * @state_groups:
745 void
746 colrow_restore_state_group (Sheet *sheet, gboolean is_cols,
747 ColRowIndexList *selection,
748 ColRowStateGroup *state_groups)
750 ColRowStateGroup *ptr = state_groups;
752 /* Cycle to end, we have to traverse the selections
753 * in parallel with the state_groups
755 selection = g_list_last (selection);
756 for (; selection != NULL && ptr != NULL ; ptr = ptr->next) {
757 ColRowIndex const *index = selection->data;
758 ColRowStateList *list = ptr->data;
759 ColRowRLEState const *rles = list->data;
761 /* MAGIC : the -1 was set above to flag this */
762 if (rles->length == -1) {
763 if (is_cols)
764 sheet_col_set_default_size_pts (sheet, rles->state.size_pts);
765 else
766 sheet_row_set_default_size_pts (sheet, rles->state.size_pts);
768 /* we are guaranteed to have at least 1 more record */
769 ptr = ptr->next;
772 colrow_set_states (sheet, is_cols, index->first, ptr->data);
773 /* force a re-render of cells with expanding formats */
774 if (is_cols)
775 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_BLANK,
776 index->first, 0, index->last, -1,
777 (CellIterFunc) &cb_clear_variable_width_content, NULL);
778 selection = selection->prev;
783 * rows_height_update:
784 * @sheet: The sheet,
785 * @range: The range whose rows should be resized.
786 * @shrink: If set to %FALSE, rows will never shrink!
788 * Use this function having changed the font size to auto-resize the row
789 * heights to make the text fit nicely.
791 void
792 rows_height_update (Sheet *sheet, GnmRange const *range, gboolean shrink)
794 /* FIXME: this needs to check font sizes and contents rather than
795 * just contents. Empty cells will cause resize also */
796 colrow_autofit (sheet, range, FALSE, FALSE,
797 FALSE, !shrink,
798 NULL, NULL);
801 /* ------------------------------------------------------------------------- */
803 struct cb_autofit {
804 Sheet *sheet;
805 const GnmRange *range;
806 gboolean ignore_strings;
807 gboolean min_current;
808 gboolean min_default;
811 static gboolean
812 cb_autofit_col (GnmColRowIter const *iter, gpointer data_)
814 struct cb_autofit *data = data_;
815 int size, min, max;
817 if (iter->cri->hard_size)
818 return FALSE;
820 size = sheet_col_size_fit_pixels (data->sheet, iter->pos,
821 data->range->start.row, data->range->end.row,
822 data->ignore_strings);
823 /* FIXME: better idea than this? */
824 max = 50 * sheet_col_get_default_size_pixels (data->sheet);
825 size = MIN (size, max);
827 min = 0;
828 if (data->min_current)
829 min = MAX (min, iter->cri->size_pixels);
830 if (data->min_default)
831 min = MAX (min, sheet_col_get_default_size_pixels (data->sheet));
833 if (size > min)
834 sheet_col_set_size_pixels (data->sheet, iter->pos, size, FALSE);
836 return FALSE;
839 static gboolean
840 cb_autofit_row (GnmColRowIter const *iter, gpointer data_)
842 struct cb_autofit *data = data_;
843 int size, min, max;
845 if (iter->cri->hard_size)
846 return FALSE;
848 size = sheet_row_size_fit_pixels (data->sheet, iter->pos,
849 data->range->start.col, data->range->end.col,
850 data->ignore_strings);
851 max = 20 * sheet_row_get_default_size_pixels (data->sheet);
852 size = MIN (size, max);
854 min = 0;
855 if (data->min_current)
856 min = MAX (min, iter->cri->size_pixels);
857 if (data->min_default)
858 min = MAX (min, sheet_row_get_default_size_pixels (data->sheet));
860 if (size > min)
861 sheet_row_set_size_pixels (data->sheet, iter->pos, size, FALSE);
863 return FALSE;
867 * colrow_autofit:
868 * @sheet: the #Sheet to change
869 * @range: the range to consider
870 * @is_cols: %TRUE for columns, %FALSE for rows.
871 * @ignore_strings: Don't consider cells with string values.
872 * @min_current: Don't shrink below current size.
873 * @min_default: Don't shrink below default size.
874 * @indices: (out) (optional): indices appropriate for
875 * colrow_restore_state_group.
876 * @sizes: (out) (optional): old sizes appropriate for
877 * colrow_restore_state_group.
879 * This function autofits columns or rows in @range as specified by
880 * @is_cols. Only cells in @range are considered for the sizing
881 * and the size can be bounded below by current size and/or default
882 * size.
884 void
885 colrow_autofit (Sheet *sheet, const GnmRange *range, gboolean is_cols,
886 gboolean ignore_strings,
887 gboolean min_current, gboolean min_default,
888 ColRowIndexList **indices,
889 ColRowStateGroup **sizes)
891 struct cb_autofit data;
892 int a, b;
893 ColRowHandler handler;
895 data.sheet = sheet;
896 data.range = range;
897 data.ignore_strings = ignore_strings;
898 data.min_current = min_current;
899 data.min_default = min_default;
901 if (is_cols) {
902 a = range->start.col;
903 b = range->end.col;
904 handler = cb_autofit_col;
905 } else {
906 a = range->start.row;
907 b = range->end.row;
908 handler = cb_autofit_row;
911 if (indices)
912 *indices = colrow_get_index_list (a, b, NULL);
913 if (sizes)
914 *sizes = g_slist_prepend (NULL, colrow_get_states (sheet, is_cols, a, b));
916 /* We potentially do a lot of recalcs as part of this, so make sure
917 stuff that caches sub-computations see the whole thing instead
918 of clearing between cells. */
919 gnm_app_recalc_start ();
920 sheet_colrow_foreach (sheet, is_cols, a, b, handler, &data);
921 gnm_app_recalc_finish ();
925 * colrow_autofit_col:
926 * @sheet: the #Sheet to change
927 * @r: the range to consider
929 void
930 colrow_autofit_col (Sheet *sheet, GnmRange *r)
932 colrow_autofit (sheet, r, TRUE, TRUE,
933 TRUE, FALSE, NULL, NULL);
934 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_BLANK,
935 r->start.col, 0,
936 r->end.col, -1,
937 (CellIterFunc) &cb_clear_variable_width_content,
938 NULL);
942 * colrow_autofit_row:
943 * @sheet: the #Sheet to change
944 * @r: the range to consider
946 void
947 colrow_autofit_row (Sheet *sheet, GnmRange *r)
949 colrow_autofit (sheet, r, FALSE, FALSE,
950 TRUE, FALSE, NULL, NULL);
953 /*****************************************************************************/
955 typedef struct
957 gboolean is_cols, visible;
958 ColRowVisList *elements;
959 } ColRowVisibility;
961 static gint
962 colrow_index_cmp (ColRowIndex const *a, ColRowIndex const *b)
964 /* We can be very simplistic here because the ranges never overlap */
965 return b->first - a->first;
968 static void
969 colrow_visibility (Sheet const *sheet, ColRowVisibility * const dat,
970 int first, int last)
972 int i;
973 gboolean const visible = dat->visible;
974 ColRowInfo * (*get) (Sheet const *sheet, int pos) = (dat->is_cols)
975 ? &sheet_col_get : &sheet_row_get;
977 /* Find the end of a segment that will be toggled */
978 for (i = last; i >= first; --i) {
979 int j;
980 ColRowIndex *res;
981 ColRowInfo const *cri = (*get) (sheet, i);
983 if (cri == NULL) {
984 if (visible != 0)
985 continue;
986 } else if ((visible != 0) == (cri->visible != 0))
987 continue;
989 /* Find the begining */
990 for (j = i; j >= first ; --j) {
991 cri = (*get) (sheet, j);
992 if (cri == NULL) {
993 if (visible != 0)
994 break;
995 } else if ((visible != 0) == (cri->visible != 0))
996 break;
997 else if (cri->is_collapsed) {
998 --j;
999 break;
1002 res = g_new (ColRowIndex, 1);
1003 res->first = (j >= first) ? j+1 : first;
1004 res->last = i;
1005 #if 0
1006 g_printerr ("%d %d\n", res->index, res->count);
1007 #endif
1008 dat->elements = g_slist_insert_sorted (dat->elements, res,
1009 (GCompareFunc)colrow_index_cmp);
1011 if (visible && cri != NULL && cri->is_collapsed) {
1012 i = colrow_find_outline_bound (
1013 sheet, dat->is_cols, j,
1014 cri->outline_level+1, FALSE);
1015 } else
1016 i = j;
1021 * colrow_get_outline_toggle: (skip)
1022 * @sheet: #Sheet
1023 * @is_cols: %TRUE for columns, %FALSE for rows.
1024 * @visible:
1025 * @first:
1026 * @last:
1028 * Returns: (transfer full):
1030 ColRowVisList *
1031 colrow_get_outline_toggle (Sheet const *sheet, gboolean is_cols, gboolean visible,
1032 int first, int last)
1034 ColRowVisibility closure;
1035 closure.is_cols = is_cols;
1036 closure.visible = visible;
1037 closure.elements = NULL;
1039 colrow_visibility (sheet, &closure, first, last);
1040 return closure.elements;
1043 static void
1044 cb_colrow_visibility (SheetView *sv, GnmRange const *r, gpointer closure)
1046 ColRowVisibility * const dat = (ColRowVisibility *)closure;
1047 int first, last;
1049 if (dat->is_cols) {
1050 first = r->start.col;
1051 last = r->end.col;
1052 } else {
1053 first = r->start.row;
1054 last = r->end.row;
1056 colrow_visibility (sv_sheet (sv), dat, first, last);
1060 * colrow_get_visibility_toggle: (skip)
1061 * @sv: The sheet view whose selection we are interested in.
1062 * @is_cols: %TRUE for columns, %FALSE for rows.
1063 * @visible: Should we unhide or hide the cols/rows.
1065 * Searches the selection list and generates a list of index,count
1066 * pairs of row/col ranges that need to be hidden or unhiden.
1068 * Returns: (transfer full): the list.
1070 ColRowVisList *
1071 colrow_get_visibility_toggle (SheetView *sv, gboolean is_cols,
1072 gboolean visible)
1074 ColRowVisibility closure;
1075 closure.is_cols = is_cols;
1076 closure.visible = visible;
1077 closure.elements = NULL;
1079 sv_selection_apply (sv, &cb_colrow_visibility, FALSE, &closure);
1081 return closure.elements;
1085 * colrow_set_visibility_list:
1086 * @sheet: The #Sheet to change
1087 * @is_cols: %TRUE for columns, %FALSE for rows.
1088 * @visible: Should we unhide or hide the cols/rows.
1090 * This is the high level command that is wrapped by undo and redo.
1091 * It should not be called by other commands.
1093 void
1094 colrow_set_visibility_list (Sheet *sheet, gboolean is_cols,
1095 gboolean visible, ColRowVisList *list)
1097 ColRowVisList *ptr;
1098 ColRowIndex *info;
1100 for (ptr = list; ptr != NULL ; ptr = ptr->next) {
1101 info = ptr->data;
1102 colrow_set_visibility (sheet, is_cols, visible,
1103 info->first, info->last);
1106 if (visible)
1107 sheet_colrow_optimize (sheet);
1109 if (is_cols)
1110 sheet_queue_respan (sheet, 0, gnm_sheet_get_last_row (sheet));
1111 if (list != NULL)
1112 sheet_redraw_all (sheet, TRUE);
1116 * col_row_info_set_outline:
1117 * @cri: #ColRowInfo to tweak
1118 * @outline_level:
1119 * @is_collapsed:
1121 * Adjust the outline state of a col/row
1123 void
1124 col_row_info_set_outline (ColRowInfo *cri, int outline_level, gboolean is_collapsed)
1126 g_return_if_fail (outline_level >= 0);
1128 cri->is_collapsed = !!is_collapsed;
1129 cri->outline_level = outline_level;
1133 * colrow_find_outline_bound:
1135 * find the next/prev col/row at the designated depth starting from the
1136 * supplied @index.
1139 colrow_find_outline_bound (Sheet const *sheet, gboolean is_cols,
1140 int index, int depth, gboolean inc)
1142 ColRowInfo * (*get) (Sheet const *sheet, int pos) = is_cols
1143 ? &sheet_col_get : &sheet_row_get;
1144 int const max = colrow_max (is_cols, sheet);
1145 int const step = inc ? 1 : -1;
1147 while (1) {
1148 ColRowInfo const *cri;
1149 int const next = index + step;
1151 if (next < 0 || next >= max)
1152 return index;
1153 cri = (*get) (sheet, next);
1154 if (cri == NULL || cri->outline_level < depth)
1155 return index;
1156 index = next;
1159 return index;
1163 * colrow_set_visibility:
1164 * @sheet: the #Sheet
1165 * @is_cols: %TRUE for columns, %FALSE for rows.
1166 * @visible: Make things visible or invisible.
1167 * @first: The index of the first row/col (inclusive)
1168 * @last: The index of the last row/col (inclusive)
1170 * Change the visibility of the selected range of contiguous cols/rows.
1171 * NOTE : only changes the collapsed state for the LAST+1 element.
1173 void
1174 colrow_set_visibility (Sheet *sheet, gboolean is_cols,
1175 gboolean visible, int first, int last)
1177 int i, step, prev_outline = 0;
1178 gboolean changed = FALSE;
1179 GnmRange * const bound = &sheet->priv->unhidden_region;
1180 gboolean const fwd = is_cols ? sheet->outline_symbols_right : sheet->outline_symbols_below;
1182 g_return_if_fail (IS_SHEET (sheet));
1183 g_return_if_fail (first <= last);
1185 if (visible) { /* expand to include newly visible regions */
1186 if (is_cols) {
1187 if (bound->start.col > first)
1188 bound->start.col = first;
1189 if (bound->end.col < last)
1190 bound->end.col = last;
1191 } else {
1192 if (bound->start.row > first)
1193 bound->start.row = first;
1194 if (bound->end.row < last)
1195 bound->end.row = last;
1197 } else { /* contract to exclude newly hidden regions */
1198 if (is_cols) {
1199 if (bound->start.col >= first && bound->start.col <= last)
1200 bound->start.col = last+1;
1201 if (bound->end.col <= last && bound->end.col >= first)
1202 bound->end.col = first-1;
1203 } else {
1204 if (bound->start.row >= first && bound->start.row <= last)
1205 bound->start.row = last+1;
1206 if (bound->end.row <= last && bound->end.row >= first)
1207 bound->end.row = first-1;
1211 if (fwd) {
1212 i = first;
1213 step = 1;
1214 } else {
1215 i = last;
1216 step = -1;
1219 for (; fwd ? (i <= last) : (i >= first) ; i += step) {
1220 ColRowInfo * const cri = sheet_colrow_fetch (sheet, i, is_cols);
1222 if (changed && prev_outline > cri->outline_level && !visible)
1223 cri->is_collapsed = FALSE;
1225 changed = (visible == 0) != (cri->visible == 0);
1226 if (changed) {
1227 cri->visible = visible;
1228 prev_outline = cri->outline_level;
1229 sheet->priv->recompute_visibility = TRUE;
1231 if (is_cols) {
1232 sheet_flag_recompute_spans (sheet);
1234 /* In order to properly reposition cell
1235 * comments in merged cells that cross the
1236 * boundary we need to do everything. Revert
1237 * this when comments are handled properly */
1238 #if 0
1239 if (sheet->priv->reposition_objects.col > i)
1240 sheet->priv->reposition_objects.col = i;
1241 #else
1242 sheet->priv->reposition_objects.col = 0;
1243 #endif
1244 } else {
1245 if (sheet->priv->reposition_objects.row > i)
1246 sheet->priv->reposition_objects.row = i;
1251 if (changed && 0 <= i && i < colrow_max (is_cols, sheet)) {
1252 ColRowInfo *cri = sheet_colrow_get (sheet, i, is_cols);
1253 if (!cri && !visible && prev_outline > 0)
1254 cri = sheet_colrow_fetch (sheet, i, is_cols);
1256 if (cri && prev_outline > cri->outline_level)
1257 cri->is_collapsed = !visible;
1261 // Introspection scanner crashes if the following "hide" is removed.
1263 /*hide*
1264 * colrow_get_global_outline: (skip)
1265 * @sheet:
1266 * @is_cols: %TRUE for columns, %FALSE for rows.
1267 * @depth:
1268 * @show: (out):
1269 * @hide: (out):
1271 * Collect the set of visibility changes required to change the visibility of
1272 * all outlined columns such that those > @depth are visible.
1274 void
1275 colrow_get_global_outline (Sheet const *sheet, gboolean is_cols, int depth,
1276 ColRowVisList **show, ColRowVisList **hide)
1278 ColRowInfo const *cri;
1279 ColRowIndex *prev = NULL;
1280 gboolean show_prev = FALSE;
1281 unsigned tmp, prev_outline = 0;
1282 int i, max = is_cols ? sheet->cols.max_used : sheet->rows.max_used;
1284 *show = *hide = NULL;
1285 for (i = 0; i <= max ; i++) {
1286 cri = sheet_colrow_get (sheet, i, is_cols);
1288 if (cri == NULL || cri->outline_level == 0) {
1289 prev_outline = 0;
1290 continue;
1292 tmp = prev_outline;
1293 prev_outline = cri->outline_level;
1295 /* see what sort of changes are necessary and do simple run
1296 * length encoding. Do not be too efficent, we need to change
1297 * the visibility per outline level or the collapse state
1298 * change in colrow_set_visibility is missed. */
1299 if (cri->outline_level < depth) {
1300 if (cri->visible)
1301 continue;
1302 if (show_prev && prev != NULL && prev->last == (i-1) &&
1303 tmp == prev_outline) {
1304 prev->last = i;
1305 continue;
1307 prev = g_new (ColRowIndex, 1);
1308 prev->first = prev->last = i;
1309 *show = g_slist_prepend (*show, prev);
1310 show_prev = TRUE;
1311 } else {
1312 if (!cri->visible)
1313 continue;
1314 if (!show_prev && prev != NULL && prev->last == (i-1) &&
1315 tmp == prev_outline) {
1316 prev->last = i;
1317 continue;
1319 prev = g_new (ColRowIndex, 1);
1320 prev->first = prev->last = i;
1321 *hide = g_slist_prepend (*hide, prev);
1322 show_prev = FALSE;
1326 *show = g_slist_reverse (*show);
1327 *hide = g_slist_reverse (*hide);