Update Spanish translation
[gnumeric.git] / src / colrow.c
blob237df535a0cb5def38a467c814bc33cad4024e84
1 /*
2 * colrow.c: Utilities for Rows and Columns
4 * Copyright (C) 1999-2007 Jody Goldberg (jody@gnome.org)
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
19 * USA
21 #include <gnumeric-config.h>
22 #include <gnumeric.h>
23 #include <colrow.h>
25 #include <sheet.h>
26 #include <sheet-view.h>
27 #include <sheet-private.h>
28 #include <application.h>
29 #include <parse-util.h>
30 #include <selection.h>
31 #include <ranges.h>
32 #include <sheet-merge.h>
33 #include <cell.h>
34 #include <cellspan.h>
35 #include <rendered-value.h>
36 #include <goffice/goffice.h>
37 #include <string.h>
39 /* Making ColRowInfo a boxed type to make introspection happy. using no-op
40 * functions for copy and free, and crossing fingers.
42 static ColRowInfo *
43 col_row_info_fake_copy (ColRowInfo *cri)
45 return cri;
48 GType
49 col_row_info_get_type (void)
51 static GType t = 0;
53 if (t == 0) {
54 t = g_boxed_type_register_static ("ColRowInfo",
55 (GBoxedCopyFunc)col_row_info_fake_copy,
56 (GBoxedFreeFunc)col_row_info_fake_copy);
58 return t;
61 double
62 colrow_compute_pixel_scale (Sheet const *sheet, gboolean horizontal)
64 return (sheet ? sheet->last_zoom_factor_used : 1.0) *
65 gnm_app_display_dpi_get (horizontal) / 72.;
68 void
69 colrow_compute_pixels_from_pts (ColRowInfo *cri, Sheet const *sheet,
70 gboolean horizontal, double scale)
72 int const margin = horizontal ? 2*GNM_COL_MARGIN : 2*GNM_ROW_MARGIN;
74 if (scale == -1)
75 scale = colrow_compute_pixel_scale (sheet, horizontal);
77 if (horizontal && sheet && sheet->display_formulas)
78 scale *= 2;
80 cri->size_pixels = (int)(cri->size_pts * scale + 0.5);
82 if (cri->size_pixels <= margin)
83 cri->size_pixels = margin + 1;
86 void
87 colrow_compute_pts_from_pixels (ColRowInfo *cri, Sheet const *sheet,
88 gboolean horizontal, double scale)
90 if (scale <= 0.)
91 scale = colrow_compute_pixel_scale (sheet, horizontal);
93 if (horizontal && sheet->display_formulas)
94 scale *= 2;
96 cri->size_pts = cri->size_pixels / scale;
97 #if 0
98 /* Disable this until we decide how to deal with scaling */
99 g_return_if_fail (cri->size_pts >= cri->margin_a + cri->margin_b);
100 #endif
104 * col_row_info_is_default:
105 * @cri: #ColRowInfo
107 * %TRUE if @cri is the default style for columns or rows.
109 gboolean
110 col_row_info_is_default (ColRowInfo const *cri)
112 g_return_val_if_fail (cri != NULL, FALSE);
113 return cri->is_default;
117 * col_row_info_is_empty:
118 * @cri: #ColRowInfo
120 * %TRUE if there is no information in col/row @cri.
122 gboolean
123 col_row_info_is_empty (ColRowInfo const *cri)
125 if (cri == NULL)
126 return TRUE;
127 return cri->is_default &&
128 cri->outline_level == 0 &&
129 !cri->is_collapsed &&
130 !cri->hard_size;
134 * col_row_info_equal:
135 * @a: First #ColRowInfo
136 * @b: Second #ColRowInfo
138 * Returns %TRUE if the infos are equivalent.
140 gboolean
141 col_row_info_equal (ColRowInfo const *a, ColRowInfo const *b)
143 if (a == NULL)
144 return b == NULL;
145 if (b == NULL)
146 return FALSE;
148 return fabs (a->size_pts - b->size_pts) < 1e-5 &&
149 a->outline_level == b->outline_level &&
150 a->is_collapsed == b->is_collapsed &&
151 a->hard_size == b->hard_size &&
152 a->visible == b->visible;
156 * col_row_info_copy:
157 * @dst: Destination #ColRowInfo
158 * @src: Source #ColRowInfo
160 * Copy all content, except the position of @src to @dst.
162 void
163 col_row_info_copy (ColRowInfo *dst, ColRowInfo const *src)
165 dst->size_pts = src->size_pts;
166 dst->size_pixels = src->size_pixels;
167 dst->outline_level = src->outline_level;
168 dst->is_collapsed = src->is_collapsed;
169 dst->hard_size = src->hard_size;
170 dst->visible = src->visible;
173 ColRowInfo *
174 col_row_info_new (void)
176 return g_slice_new (ColRowInfo);
179 void
180 colrow_free (ColRowInfo *cri)
182 g_slice_free1 (sizeof (*cri), cri);
187 * colrow_state_list_foreach:
188 * @list: The #ColRowStateList to iterate.
189 * @sheet: (nullable): Origin #Sheet.
190 * @is_cols: %TRUE for columns, %FALSE for rows.
191 * @base: index of first column or row.
192 * @callback: (scope call): A callback function which should
193 * return %TRUE to stop the iteration.
194 * @user_data: A baggage pointer.
196 * Iterates through the existing rows or columns within the range supplied.
197 * Currently only support left -> right iteration. If a callback returns
198 * %TRUE iteration stops.
200 gboolean
201 colrow_state_list_foreach (ColRowStateList *list,
202 Sheet const *sheet, gboolean is_cols,
203 int base,
204 ColRowHandler callback,
205 gpointer user_data)
207 GnmColRowIter iter;
208 int i = base;
209 ColRowStateList *l;
210 ColRowInfo cri;
211 double scale = colrow_compute_pixel_scale (sheet, is_cols);
213 // This sets various fields we do not have
214 memset (&cri, 0, sizeof (cri));
216 iter.cri = &cri;
217 for (l = list; l; l = l->next) {
218 ColRowRLEState *rle = l->data;
219 ColRowState const *state = &rle->state;
220 int l;
222 cri.size_pts = state->size_pts;
223 cri.outline_level = state->outline_level;
224 cri.is_collapsed = state->is_collapsed;
225 cri.hard_size = state->hard_size;
226 cri.visible = state->visible;
227 colrow_compute_pixels_from_pts (&cri, sheet, is_cols, scale);
229 for (l = 0; l < rle->length; l++) {
230 iter.pos = i++;
231 if (iter.cri && (*callback)(&iter, user_data))
232 return TRUE;
235 return FALSE;
239 /*****************************************************************************/
241 typedef struct _ColRowIndex {
242 int first, last;
243 } ColRowIndex;
246 static void
247 cb_colrow_index_counter (gpointer data, gpointer user_data)
249 ColRowIndex *index = data;
250 gint *count = user_data;
251 if (data != NULL)
252 *count += index->last - index->first + 1;
255 gint
256 colrow_vis_list_length (ColRowVisList *list)
258 gint count = 0;
259 g_slist_foreach (list, cb_colrow_index_counter, &count);
260 return count;
264 * colrow_state_group_destroy:
265 * @set: (transfer full): the group to destroy.
267 * Returns: (transfer none) (nullable): %NULL.
269 ColRowStateGroup *
270 colrow_state_group_destroy (ColRowStateGroup *group)
272 ColRowStateGroup *ptr;
273 for (ptr = group; ptr != NULL ; ptr = ptr->next)
274 colrow_state_list_destroy (ptr->data);
275 g_slist_free (group);
276 return NULL;
279 static gint
280 colrow_index_compare (ColRowIndex const * a, ColRowIndex const * b)
282 return a->first - b->first;
286 * colrow_index_list_to_string: Convert an index list into a string.
287 * The result must be freed by the caller.
288 * It will be something like : A-B, F-G
290 * @list: The list
291 * @is_cols: %TRUE for columns, %FALSE for rows.
292 * @is_single: If non-null this will be set to %TRUE if there's only a single col/row involved.
294 GString *
295 colrow_index_list_to_string (ColRowIndexList *list, gboolean is_cols, gboolean *is_single)
297 ColRowIndexList *ptr;
298 GString *result;
299 gboolean single = TRUE;
301 g_return_val_if_fail (list != NULL, NULL);
303 result = g_string_new (NULL);
304 for (ptr = list; ptr != NULL; ptr = ptr->next) {
305 ColRowIndex *index = ptr->data;
307 if (is_cols)
308 g_string_append (result, cols_name (index->first, index->last));
309 else
310 g_string_append (result, rows_name (index->first, index->last));
312 if (index->last != index->first)
313 single = FALSE;
315 if (ptr->next) {
316 g_string_append (result, ", ");
317 single = FALSE;
321 if (is_single)
322 *is_single = single;
324 return result;
328 * colrow_get_index_list:
329 * @first:
330 * @last:
331 * @list: (transfer full):
333 * Build an ordered list of pairs doing intelligent merging
334 * of overlapping regions.
336 * Returns: (transfer full): @list.
338 ColRowIndexList *
339 colrow_get_index_list (int first, int last, ColRowIndexList *list)
341 ColRowIndex *tmp, *prev;
342 GList *ptr;
344 tmp = g_new (ColRowIndex, 1);
345 tmp->first = first;
346 tmp->last = last;
348 list = g_list_insert_sorted (list, tmp,
349 (GCompareFunc)&colrow_index_compare);
351 prev = list->data;
352 for (ptr = list->next ; ptr != NULL ; ) {
353 tmp = ptr->data;
355 /* at the end of existing segment or contained */
356 if (prev->last+1 >= tmp->first) {
357 GList *next = ptr->next;
358 if (prev->last < tmp->last)
359 prev->last = tmp->last;
360 list = g_list_remove_link (list, ptr);
361 ptr = next;
362 } else {
363 ptr = ptr->next;
364 prev = tmp;
367 return list;
371 * colrow_index_list_copy:
372 * @list: #ColRowIndexList
374 * Returns: (transfer full):
376 ColRowIndexList *
377 colrow_index_list_copy (ColRowIndexList *list)
379 GList *copy = NULL, *ptr;
381 for (ptr = list ; ptr != NULL ; ptr = ptr->next) {
382 ColRowIndex *tmp = g_new (ColRowIndex, 1);
383 ColRowIndex *ex = ptr->data;
384 tmp->first = ex->first;
385 tmp->last = ex->last;
386 copy = g_list_prepend (copy, tmp);
388 return g_list_reverse (copy);
391 static void
392 colrow_set_single_state (ColRowState *state,
393 Sheet *sheet, int i, gboolean is_cols)
395 ColRowInfo const *info = sheet_colrow_get_info (sheet, i, is_cols);
396 state->is_default = col_row_info_is_default (info);
397 state->size_pts = info->size_pts;
398 state->outline_level = info->outline_level;
399 state->is_collapsed = info->is_collapsed;
400 state->hard_size = info->hard_size;
401 state->visible = info->visible;
405 * colrow_state_list_destroy:
406 * @list: (transfer full): the list to destroy.
408 * Returns: (transfer none): %NULL.
410 ColRowStateList *
411 colrow_state_list_destroy (ColRowStateList *list)
413 g_slist_free_full (list, g_free);
414 return NULL;
418 * colrow_get_states: (skip)
419 * @sheet: #Sheet
420 * @is_cols: %TRUE for columns, %FALSE for rows.
421 * @first: first column or row.
422 * @last: last column or row.
424 * Returns: (transfer full):
426 ColRowStateList *
427 colrow_get_states (Sheet *sheet, gboolean is_cols, int first, int last)
429 ColRowStateList *list = NULL;
430 ColRowRLEState *rles;
431 ColRowState run_state;
432 int i, run_length;
434 g_return_val_if_fail (IS_SHEET (sheet), NULL);
435 g_return_val_if_fail (first <= last, NULL);
437 colrow_set_single_state (&run_state, sheet, first, is_cols);
438 run_length = 1;
440 for (i = first + 1; i <= last; ++i) {
441 ColRowState cur_state;
442 colrow_set_single_state (&cur_state, sheet, i, is_cols);
444 /* If state changed, start a new block */
445 if (cur_state.is_default != run_state.is_default ||
446 cur_state.size_pts != run_state.size_pts ||
447 cur_state.outline_level != run_state.outline_level ||
448 cur_state.is_collapsed != run_state.is_collapsed ||
449 cur_state.hard_size != run_state.hard_size ||
450 cur_state.visible != run_state.visible) {
451 rles = g_new (ColRowRLEState, 1);
452 rles->length = run_length;
453 rles->state = run_state;
454 list = g_slist_prepend (list, rles);
456 run_state = cur_state;
457 run_length = 1;
458 } else
459 ++run_length;
462 /* Store the final run */
463 rles = g_new (ColRowRLEState, 1);
464 rles->length = run_length;
465 rles->state = run_state;
466 list = g_slist_prepend (list, rles);
468 return g_slist_reverse (list);
471 struct resize_closure {
472 Sheet *sheet;
473 int new_size;
474 gboolean is_cols;
477 static gboolean
478 cb_set_colrow_size (GnmColRowIter const *iter, gpointer userdata)
480 if (iter->cri->visible) {
481 struct resize_closure const *c = userdata;
483 if (c->is_cols)
484 sheet_col_set_size_pixels (c->sheet, iter->pos,
485 c->new_size, TRUE);
486 else
487 sheet_row_set_size_pixels (c->sheet, iter->pos,
488 c->new_size, TRUE);
490 return FALSE;
493 static GnmValue *
494 cb_clear_variable_width_content (GnmCellIter const *iter,
495 G_GNUC_UNUSED gpointer user)
497 GnmRenderedValue *rv = gnm_cell_get_rendered_value (iter->cell);
498 if (rv && rv->variable_width) {
499 iter->ri->needs_respan = TRUE;
500 gnm_cell_unrender (iter->cell);
502 return NULL;
506 * colrow_get_sizes: (skip)
507 * @sheet: #Sheet
508 * @is_cols: %TRUE for columns, %FALSE for rows.
509 * @src:
510 * @new_size:
512 * Returns: (transfer full):
514 ColRowStateGroup *
515 colrow_get_sizes (Sheet *sheet, gboolean is_cols,
516 ColRowIndexList *src, int new_size)
518 ColRowStateGroup *res = NULL;
519 ColRowIndexList *ptr;
521 for (ptr = src; ptr != NULL ; ptr = ptr->next) {
522 ColRowIndex const *index = ptr->data;
523 res = g_slist_prepend (res, colrow_get_states (sheet, is_cols,
524 index->first, index->last));
526 if (new_size > 0 && index->first == 0 &&
527 (index->last+1) >= colrow_max (is_cols, sheet)) {
528 ColRowRLEState *rles = g_new0 (ColRowRLEState, 1);
530 rles->length = -1; /* Flag as changing the default */
532 if (is_cols)
533 rles->state.size_pts = sheet_col_get_default_size_pts (sheet);
534 else
535 rles->state.size_pts = sheet_row_get_default_size_pts (sheet);
537 /* Result is a magic 'default' record + >= 1 normal */
538 return g_slist_prepend (res, g_slist_append (NULL, rles));
542 return res;
546 * colrow_set_sizes: (skip)
547 * @sheet: #Sheet
548 * @is_cols: %TRUE for columns, %FALSE for rows.
549 * @src:
550 * @new_size:
551 * @from:
552 * @to:
554 * Returns: (transfer full):
556 ColRowStateGroup *
557 colrow_set_sizes (Sheet *sheet, gboolean is_cols,
558 ColRowIndexList *src, int new_size, int from, int to)
559 /* from & to are used to restrict fitting to that range. Pass 0, -1 if you want to use the */
560 /* whole row/column */
562 int i;
563 ColRowStateGroup *res = NULL;
564 ColRowIndexList *ptr;
566 for (ptr = src; ptr != NULL ; ptr = ptr->next) {
567 ColRowIndex const *index = ptr->data;
568 res = g_slist_prepend (res, colrow_get_states (sheet, is_cols,
569 index->first, index->last));
571 /* FIXME:
572 * If we are changing the size of more than half of the rows/col to
573 * something specific (not autosize) we should change the default
574 * row/col size instead. However, it is unclear how to handle
575 * hard sizing.
577 * we need better management of rows/cols. Currently if they are all
578 * defined calculation speed grinds to a halt.
580 if (new_size > 0 && index->first == 0 &&
581 (index->last+1) >= colrow_max (is_cols, sheet)) {
582 struct resize_closure closure;
583 ColRowRLEState *rles = g_new0 (ColRowRLEState, 1);
585 rles->length = -1; /* Flag as changing the default */
587 closure.sheet = sheet;
588 closure.new_size = new_size;
589 closure.is_cols = is_cols;
590 if (is_cols) {
591 rles->state.size_pts = sheet_col_get_default_size_pts (sheet);
592 sheet_col_set_default_size_pixels (sheet, new_size);
593 sheet_colrow_foreach (sheet, TRUE, 0, -1,
594 &cb_set_colrow_size, &closure);
595 } else {
596 rles->state.size_pts = sheet_row_get_default_size_pts (sheet);
597 sheet_row_set_default_size_pixels (sheet, new_size);
598 sheet_colrow_foreach (sheet, FALSE, 0, -1,
599 &cb_set_colrow_size, &closure);
602 /* force a re-render of cells with expanding formats */
603 if (is_cols)
604 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_BLANK,
605 0, 0, -1, -1,
606 (CellIterFunc) &cb_clear_variable_width_content, NULL);
608 /* Result is a magic 'default' record + >= 1 normal */
609 return g_slist_prepend (res, g_slist_append (NULL, rles));
612 if (is_cols) {
613 /* force a re-render of cells with expanding formats */
614 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_BLANK,
615 index->first, 0, index->last, -1,
616 (CellIterFunc) &cb_clear_variable_width_content, NULL);
618 /* In order to properly reposition cell comments in
619 * merged cells that cross the boundary we need to do
620 * everything. Remove this when comments are handled
621 * properly */
622 sheet->priv->reposition_objects.col = 0;
625 for (i = index->first ; i <= index->last ; ++i) {
626 int tmp = new_size;
627 if (tmp < 0) {
628 int max = is_cols ? gnm_sheet_get_last_row (sheet)
629 : gnm_sheet_get_last_col (sheet);
630 if (from < 0)
631 from = 0;
632 if (to < 0 || to > max)
633 to = max;
634 if (from > max)
635 from = to;
636 /* Fall back to assigning the default if it is empty */
637 tmp = (is_cols)
638 ? sheet_col_size_fit_pixels (sheet, i, from, to, FALSE)
639 : sheet_row_size_fit_pixels (sheet, i, from, to, FALSE);
641 if (tmp > 0) {
642 if (is_cols)
643 sheet_col_set_size_pixels (sheet, i, tmp, new_size > 0);
644 else
645 sheet_row_set_size_pixels (sheet, i, tmp, new_size > 0);
646 } else if (sheet_colrow_get (sheet, i, is_cols) != NULL) {
647 if (is_cols)
648 sheet_col_set_size_pixels (sheet, i,
649 sheet_col_get_default_size_pixels (sheet), FALSE);
650 else
651 sheet_row_set_size_pixels (sheet, i,
652 sheet_row_get_default_size_pixels (sheet), FALSE);
657 return res;
661 * colrow_set_states: (skip)
662 * @sheet: #Sheet
663 * @is_cols: %TRUE for columns, %FALSE for rows.
664 * @first: first column or row
665 * @states: saved state to restore.
667 * This is a low level routine it does not redraw or reposition objects
669 void
670 colrow_set_states (Sheet *sheet, gboolean is_cols,
671 int first, ColRowStateList *states)
673 GSList *l;
674 int i, max_outline, offset = first;
675 ColRowCollection *infos;
676 double scale;
678 g_return_if_fail (IS_SHEET (sheet));
680 infos = is_cols ? &(sheet->cols) : &(sheet->rows);
681 max_outline = infos->max_outline_level;
682 scale = colrow_compute_pixel_scale (sheet, is_cols);
684 for (l = states; l != NULL; l = l->next) {
685 ColRowRLEState const *rles = l->data;
686 ColRowState const *state = &rles->state;
688 if (max_outline < state->outline_level)
689 max_outline = state->outline_level;
691 for (i = offset; i < offset + rles->length; i++) {
692 if (state->is_default) {
693 ColRowSegment *segment = COLROW_GET_SEGMENT(infos, i);
694 if (segment != NULL) {
695 int const sub = COLROW_SUB_INDEX (i);
696 ColRowInfo *cri = segment->info[sub];
697 if (cri != NULL) {
698 segment->info[sub] = NULL;
699 colrow_free (cri);
702 } else {
703 ColRowInfo *cri = sheet_colrow_fetch (sheet, i, is_cols);
704 cri->hard_size = state->hard_size;
705 cri->size_pts = state->size_pts;
706 colrow_compute_pixels_from_pts (cri, sheet, is_cols, scale);
707 col_row_info_set_outline (cri, state->outline_level,
708 state->is_collapsed);
711 offset += rles->length;
714 /* Notify sheet of pending update */
715 sheet->priv->recompute_visibility = TRUE;
716 if (is_cols) {
717 sheet_flag_recompute_spans (sheet);
719 /* In order to properly reposition cell
720 * comments in merged cells that cross the
721 * boundary we need to do everything. Revert
722 * this when comments are handled properly */
723 #if 0
724 if (sheet->priv->reposition_objects.col > first)
725 sheet->priv->reposition_objects.col = first;
726 #else
727 sheet->priv->reposition_objects.col = 0;
728 #endif
729 } else {
730 if (sheet->priv->reposition_objects.row > first)
731 sheet->priv->reposition_objects.row = first;
733 sheet_colrow_gutter (sheet, is_cols, max_outline);
737 * colrow_restore_state_group: (skip)
738 * @sheet: #Sheet
739 * @is_cols: %TRUE for columns, %FALSE for rows.
740 * @selection:
741 * @state_groups:
744 void
745 colrow_restore_state_group (Sheet *sheet, gboolean is_cols,
746 ColRowIndexList *selection,
747 ColRowStateGroup *state_groups)
749 ColRowStateGroup *ptr = state_groups;
751 /* Cycle to end, we have to traverse the selections
752 * in parallel with the state_groups
754 selection = g_list_last (selection);
755 for (; selection != NULL && ptr != NULL ; ptr = ptr->next) {
756 ColRowIndex const *index = selection->data;
757 ColRowStateList *list = ptr->data;
758 ColRowRLEState const *rles = list->data;
760 /* MAGIC : the -1 was set above to flag this */
761 if (rles->length == -1) {
762 if (is_cols)
763 sheet_col_set_default_size_pts (sheet, rles->state.size_pts);
764 else
765 sheet_row_set_default_size_pts (sheet, rles->state.size_pts);
767 /* we are guaranteed to have at least 1 more record */
768 ptr = ptr->next;
771 colrow_set_states (sheet, is_cols, index->first, ptr->data);
772 /* force a re-render of cells with expanding formats */
773 if (is_cols)
774 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_BLANK,
775 index->first, 0, index->last, -1,
776 (CellIterFunc) &cb_clear_variable_width_content, NULL);
777 selection = selection->prev;
782 * rows_height_update:
783 * @sheet: The sheet,
784 * @range: The range whose rows should be resized.
785 * @shrink: If set to %FALSE, rows will never shrink!
787 * Use this function having changed the font size to auto-resize the row
788 * heights to make the text fit nicely.
790 void
791 rows_height_update (Sheet *sheet, GnmRange const *range, gboolean shrink)
793 /* FIXME: this needs to check font sizes and contents rather than
794 * just contents. Empty cells will cause resize also */
795 colrow_autofit (sheet, range, FALSE, FALSE,
796 FALSE, !shrink,
797 NULL, NULL);
800 /* ------------------------------------------------------------------------- */
802 struct cb_autofit {
803 Sheet *sheet;
804 const GnmRange *range;
805 gboolean ignore_strings;
806 gboolean min_current;
807 gboolean min_default;
810 static gboolean
811 cb_autofit_col (GnmColRowIter const *iter, gpointer data_)
813 struct cb_autofit *data = data_;
814 int size, min, max;
816 if (iter->cri->hard_size)
817 return FALSE;
819 size = sheet_col_size_fit_pixels (data->sheet, iter->pos,
820 data->range->start.row, data->range->end.row,
821 data->ignore_strings);
822 /* FIXME: better idea than this? */
823 max = 50 * sheet_col_get_default_size_pixels (data->sheet);
824 size = MIN (size, max);
826 min = 0;
827 if (data->min_current)
828 min = MAX (min, iter->cri->size_pixels);
829 if (data->min_default)
830 min = MAX (min, sheet_col_get_default_size_pixels (data->sheet));
832 if (size > min)
833 sheet_col_set_size_pixels (data->sheet, iter->pos, size, FALSE);
835 return FALSE;
838 static gboolean
839 cb_autofit_row (GnmColRowIter const *iter, gpointer data_)
841 struct cb_autofit *data = data_;
842 int size, min, max;
844 if (iter->cri->hard_size)
845 return FALSE;
847 size = sheet_row_size_fit_pixels (data->sheet, iter->pos,
848 data->range->start.col, data->range->end.col,
849 data->ignore_strings);
850 max = 20 * sheet_row_get_default_size_pixels (data->sheet);
851 size = MIN (size, max);
853 min = 0;
854 if (data->min_current)
855 min = MAX (min, iter->cri->size_pixels);
856 if (data->min_default)
857 min = MAX (min, sheet_row_get_default_size_pixels (data->sheet));
859 if (size > min)
860 sheet_row_set_size_pixels (data->sheet, iter->pos, size, FALSE);
862 return FALSE;
866 * colrow_autofit:
867 * @sheet: the #Sheet to change
868 * @range: the range to consider
869 * @is_cols: %TRUE for columns, %FALSE for rows.
870 * @ignore_strings: Don't consider cells with string values.
871 * @min_current: Don't shrink below current size.
872 * @min_default: Don't shrink below default size.
873 * @indices: (out) (optional): indices appropriate for
874 * colrow_restore_state_group.
875 * @sizes: (out) (optional): old sizes appropriate for
876 * colrow_restore_state_group.
878 * This function autofits columns or rows in @range as specified by
879 * @is_cols. Only cells in @range are considered for the sizing
880 * and the size can be bounded below by current size and/or default
881 * size.
883 void
884 colrow_autofit (Sheet *sheet, const GnmRange *range, gboolean is_cols,
885 gboolean ignore_strings,
886 gboolean min_current, gboolean min_default,
887 ColRowIndexList **indices,
888 ColRowStateGroup **sizes)
890 struct cb_autofit data;
891 int a, b;
892 ColRowHandler handler;
894 data.sheet = sheet;
895 data.range = range;
896 data.ignore_strings = ignore_strings;
897 data.min_current = min_current;
898 data.min_default = min_default;
900 if (is_cols) {
901 a = range->start.col;
902 b = range->end.col;
903 handler = cb_autofit_col;
904 } else {
905 a = range->start.row;
906 b = range->end.row;
907 handler = cb_autofit_row;
910 if (indices)
911 *indices = colrow_get_index_list (a, b, NULL);
912 if (sizes)
913 *sizes = g_slist_prepend (NULL, colrow_get_states (sheet, is_cols, a, b));
915 /* We potentially do a lot of recalcs as part of this, so make sure
916 stuff that caches sub-computations see the whole thing instead
917 of clearing between cells. */
918 gnm_app_recalc_start ();
919 sheet_colrow_foreach (sheet, is_cols, a, b, handler, &data);
920 gnm_app_recalc_finish ();
924 * colrow_autofit_col:
925 * @sheet: the #Sheet to change
926 * @r: the range to consider
928 void
929 colrow_autofit_col (Sheet *sheet, GnmRange *r)
931 colrow_autofit (sheet, r, TRUE, TRUE,
932 TRUE, FALSE, NULL, NULL);
933 sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_BLANK,
934 r->start.col, 0,
935 r->end.col, -1,
936 (CellIterFunc) &cb_clear_variable_width_content,
937 NULL);
941 * colrow_autofit_row:
942 * @sheet: the #Sheet to change
943 * @r: the range to consider
945 void
946 colrow_autofit_row (Sheet *sheet, GnmRange *r)
948 colrow_autofit (sheet, r, FALSE, FALSE,
949 TRUE, FALSE, NULL, NULL);
952 /*****************************************************************************/
954 typedef struct
956 gboolean is_cols, visible;
957 ColRowVisList *elements;
958 } ColRowVisibility;
960 static gint
961 colrow_index_cmp (ColRowIndex const *a, ColRowIndex const *b)
963 /* We can be very simplistic here because the ranges never overlap */
964 return b->first - a->first;
967 static void
968 colrow_visibility (Sheet const *sheet, ColRowVisibility * const dat,
969 int first, int last)
971 int i;
972 gboolean const visible = dat->visible;
973 ColRowInfo * (*get) (Sheet const *sheet, int pos) = (dat->is_cols)
974 ? &sheet_col_get : &sheet_row_get;
976 /* Find the end of a segment that will be toggled */
977 for (i = last; i >= first; --i) {
978 int j;
979 ColRowIndex *res;
980 ColRowInfo const *cri = (*get) (sheet, i);
982 if (cri == NULL) {
983 if (visible != 0)
984 continue;
985 } else if ((visible != 0) == (cri->visible != 0))
986 continue;
988 /* Find the begining */
989 for (j = i; j >= first ; --j) {
990 cri = (*get) (sheet, j);
991 if (cri == NULL) {
992 if (visible != 0)
993 break;
994 } else if ((visible != 0) == (cri->visible != 0))
995 break;
996 else if (cri->is_collapsed) {
997 --j;
998 break;
1001 res = g_new (ColRowIndex, 1);
1002 res->first = (j >= first) ? j+1 : first;
1003 res->last = i;
1004 #if 0
1005 g_printerr ("%d %d\n", res->index, res->count);
1006 #endif
1007 dat->elements = g_slist_insert_sorted (dat->elements, res,
1008 (GCompareFunc)colrow_index_cmp);
1010 if (visible && cri != NULL && cri->is_collapsed) {
1011 i = colrow_find_outline_bound (
1012 sheet, dat->is_cols, j,
1013 cri->outline_level+1, FALSE);
1014 } else
1015 i = j;
1020 * colrow_get_outline_toggle: (skip)
1021 * @sheet: #Sheet
1022 * @is_cols: %TRUE for columns, %FALSE for rows.
1023 * @visible:
1024 * @first:
1025 * @last:
1027 * Returns: (transfer full):
1029 ColRowVisList *
1030 colrow_get_outline_toggle (Sheet const *sheet, gboolean is_cols, gboolean visible,
1031 int first, int last)
1033 ColRowVisibility closure;
1034 closure.is_cols = is_cols;
1035 closure.visible = visible;
1036 closure.elements = NULL;
1038 colrow_visibility (sheet, &closure, first, last);
1039 return closure.elements;
1042 static void
1043 cb_colrow_visibility (SheetView *sv, GnmRange const *r, gpointer closure)
1045 ColRowVisibility * const dat = (ColRowVisibility *)closure;
1046 int first, last;
1048 if (dat->is_cols) {
1049 first = r->start.col;
1050 last = r->end.col;
1051 } else {
1052 first = r->start.row;
1053 last = r->end.row;
1055 colrow_visibility (sv_sheet (sv), dat, first, last);
1059 * colrow_get_visibility_toggle: (skip)
1060 * @sv: The sheet view whose selection we are interested in.
1061 * @is_cols: %TRUE for columns, %FALSE for rows.
1062 * @visible: Should we unhide or hide the cols/rows.
1064 * Searches the selection list and generates a list of index,count
1065 * pairs of row/col ranges that need to be hidden or unhiden.
1067 * Returns: (transfer full): the list.
1069 ColRowVisList *
1070 colrow_get_visibility_toggle (SheetView *sv, gboolean is_cols,
1071 gboolean visible)
1073 ColRowVisibility closure;
1074 closure.is_cols = is_cols;
1075 closure.visible = visible;
1076 closure.elements = NULL;
1078 sv_selection_apply (sv, &cb_colrow_visibility, FALSE, &closure);
1080 return closure.elements;
1084 * colrow_set_visibility_list:
1085 * @sheet: The #Sheet to change
1086 * @is_cols: %TRUE for columns, %FALSE for rows.
1087 * @visible: Should we unhide or hide the cols/rows.
1089 * This is the high level command that is wrapped by undo and redo.
1090 * It should not be called by other commands.
1092 void
1093 colrow_set_visibility_list (Sheet *sheet, gboolean is_cols,
1094 gboolean visible, ColRowVisList *list)
1096 ColRowVisList *ptr;
1097 ColRowIndex *info;
1099 for (ptr = list; ptr != NULL ; ptr = ptr->next) {
1100 info = ptr->data;
1101 colrow_set_visibility (sheet, is_cols, visible,
1102 info->first, info->last);
1105 if (visible)
1106 sheet_colrow_optimize (sheet);
1108 if (is_cols)
1109 sheet_queue_respan (sheet, 0, gnm_sheet_get_last_row (sheet));
1110 if (list != NULL)
1111 sheet_redraw_all (sheet, TRUE);
1115 * col_row_info_set_outline:
1116 * @cri: #ColRowInfo to tweak
1117 * @outline_level:
1118 * @is_collapsed:
1120 * Adjust the outline state of a col/row
1122 void
1123 col_row_info_set_outline (ColRowInfo *cri, int outline_level, gboolean is_collapsed)
1125 g_return_if_fail (outline_level >= 0);
1127 cri->is_collapsed = !!is_collapsed;
1128 cri->outline_level = outline_level;
1132 * colrow_find_outline_bound:
1134 * find the next/prev col/row at the designated depth starting from the
1135 * supplied @index.
1138 colrow_find_outline_bound (Sheet const *sheet, gboolean is_cols,
1139 int index, int depth, gboolean inc)
1141 ColRowInfo * (*get) (Sheet const *sheet, int pos) = is_cols
1142 ? &sheet_col_get : &sheet_row_get;
1143 int const max = colrow_max (is_cols, sheet);
1144 int const step = inc ? 1 : -1;
1146 while (1) {
1147 ColRowInfo const *cri;
1148 int const next = index + step;
1150 if (next < 0 || next >= max)
1151 return index;
1152 cri = (*get) (sheet, next);
1153 if (cri == NULL || cri->outline_level < depth)
1154 return index;
1155 index = next;
1158 return index;
1162 * colrow_set_visibility:
1163 * @sheet: the #Sheet
1164 * @is_cols: %TRUE for columns, %FALSE for rows.
1165 * @visible: Make things visible or invisible.
1166 * @first: The index of the first row/col (inclusive)
1167 * @last: The index of the last row/col (inclusive)
1169 * Change the visibility of the selected range of contiguous cols/rows.
1170 * NOTE : only changes the collapsed state for the LAST+1 element.
1172 void
1173 colrow_set_visibility (Sheet *sheet, gboolean is_cols,
1174 gboolean visible, int first, int last)
1176 int i, step, prev_outline = 0;
1177 gboolean changed = FALSE;
1178 GnmRange * const bound = &sheet->priv->unhidden_region;
1179 gboolean const fwd = is_cols ? sheet->outline_symbols_right : sheet->outline_symbols_below;
1181 g_return_if_fail (IS_SHEET (sheet));
1182 g_return_if_fail (first <= last);
1184 if (visible) { /* expand to include newly visible regions */
1185 if (is_cols) {
1186 if (bound->start.col > first)
1187 bound->start.col = first;
1188 if (bound->end.col < last)
1189 bound->end.col = last;
1190 } else {
1191 if (bound->start.row > first)
1192 bound->start.row = first;
1193 if (bound->end.row < last)
1194 bound->end.row = last;
1196 } else { /* contract to exclude newly hidden regions */
1197 if (is_cols) {
1198 if (bound->start.col >= first && bound->start.col <= last)
1199 bound->start.col = last+1;
1200 if (bound->end.col <= last && bound->end.col >= first)
1201 bound->end.col = first-1;
1202 } else {
1203 if (bound->start.row >= first && bound->start.row <= last)
1204 bound->start.row = last+1;
1205 if (bound->end.row <= last && bound->end.row >= first)
1206 bound->end.row = first-1;
1210 if (fwd) {
1211 i = first;
1212 step = 1;
1213 } else {
1214 i = last;
1215 step = -1;
1218 for (; fwd ? (i <= last) : (i >= first) ; i += step) {
1219 ColRowInfo * const cri = sheet_colrow_fetch (sheet, i, is_cols);
1221 if (changed && prev_outline > cri->outline_level && !visible)
1222 cri->is_collapsed = FALSE;
1224 changed = (visible == 0) != (cri->visible == 0);
1225 if (changed) {
1226 cri->visible = visible;
1227 prev_outline = cri->outline_level;
1228 sheet->priv->recompute_visibility = TRUE;
1230 if (is_cols) {
1231 sheet_flag_recompute_spans (sheet);
1233 /* In order to properly reposition cell
1234 * comments in merged cells that cross the
1235 * boundary we need to do everything. Revert
1236 * this when comments are handled properly */
1237 #if 0
1238 if (sheet->priv->reposition_objects.col > i)
1239 sheet->priv->reposition_objects.col = i;
1240 #else
1241 sheet->priv->reposition_objects.col = 0;
1242 #endif
1243 } else {
1244 if (sheet->priv->reposition_objects.row > i)
1245 sheet->priv->reposition_objects.row = i;
1250 if (changed && 0 <= i && i < colrow_max (is_cols, sheet)) {
1251 ColRowInfo *cri = sheet_colrow_get (sheet, i, is_cols);
1252 if (!cri && !visible && prev_outline > 0)
1253 cri = sheet_colrow_fetch (sheet, i, is_cols);
1255 if (cri && prev_outline > cri->outline_level)
1256 cri->is_collapsed = !visible;
1260 // Introspection scanner crashes if the following "hide" is removed.
1262 /*hide*
1263 * colrow_get_global_outline: (skip)
1264 * @sheet:
1265 * @is_cols: %TRUE for columns, %FALSE for rows.
1266 * @depth:
1267 * @show: (out):
1268 * @hide: (out):
1270 * Collect the set of visibility changes required to change the visibility of
1271 * all outlined columns such that those > @depth are visible.
1273 void
1274 colrow_get_global_outline (Sheet const *sheet, gboolean is_cols, int depth,
1275 ColRowVisList **show, ColRowVisList **hide)
1277 ColRowInfo const *cri;
1278 ColRowIndex *prev = NULL;
1279 gboolean show_prev = FALSE;
1280 unsigned tmp, prev_outline = 0;
1281 int i, max = is_cols ? sheet->cols.max_used : sheet->rows.max_used;
1283 *show = *hide = NULL;
1284 for (i = 0; i <= max ; i++) {
1285 cri = sheet_colrow_get (sheet, i, is_cols);
1287 if (cri == NULL || cri->outline_level == 0) {
1288 prev_outline = 0;
1289 continue;
1291 tmp = prev_outline;
1292 prev_outline = cri->outline_level;
1294 /* see what sort of changes are necessary and do simple run
1295 * length encoding. Do not be too efficent, we need to change
1296 * the visibility per outline level or the collapse state
1297 * change in colrow_set_visibility is missed. */
1298 if (cri->outline_level < depth) {
1299 if (cri->visible)
1300 continue;
1301 if (show_prev && prev != NULL && prev->last == (i-1) &&
1302 tmp == prev_outline) {
1303 prev->last = i;
1304 continue;
1306 prev = g_new (ColRowIndex, 1);
1307 prev->first = prev->last = i;
1308 *show = g_slist_prepend (*show, prev);
1309 show_prev = TRUE;
1310 } else {
1311 if (!cri->visible)
1312 continue;
1313 if (!show_prev && prev != NULL && prev->last == (i-1) &&
1314 tmp == prev_outline) {
1315 prev->last = i;
1316 continue;
1318 prev = g_new (ColRowIndex, 1);
1319 prev->first = prev->last = i;
1320 *hide = g_slist_prepend (*hide, prev);
1321 show_prev = FALSE;
1325 *show = g_slist_reverse (*show);
1326 *hide = g_slist_reverse (*hide);