Paste: fix undo
[gnumeric.git] / src / colrow.c
blobe7a0283f2f930832271678563d6a5c21b88650d7
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>
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->last_zoom_factor_used *
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->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);
186 * col_row_collection_foreach:
187 * @infos: The Row or Column collection.
188 * @first: start position (inclusive)
189 * @last: stop column (inclusive)
190 * @callback: (scope call): A callback function which should return %TRUE to stop
191 * the iteration.
192 * @user_data: A bagage pointer.
194 * Iterates through the existing rows or columns within the range supplied.
195 * Currently only support left -> right iteration. If a callback returns
196 * %TRUE iteration stops.
198 gboolean
199 col_row_collection_foreach (ColRowCollection const *infos, int first, int last,
200 ColRowHandler callback, gpointer user_data)
202 GnmColRowIter iter;
203 ColRowSegment const *segment;
204 int sub, inner_last, i;
206 /* TODO : Do we need to support right -> left as an option */
208 /* clip */
209 if (last > infos->max_used)
210 last = infos->max_used;
212 for (i = first; i <= last ; ) {
213 segment = COLROW_GET_SEGMENT (infos, i);
214 sub = COLROW_SUB_INDEX(i);
215 inner_last = (COLROW_SEGMENT_INDEX (last) == COLROW_SEGMENT_INDEX (i))
216 ? COLROW_SUB_INDEX (last)+1 : COLROW_SEGMENT_SIZE;
217 iter.pos = i;
218 i += COLROW_SEGMENT_SIZE - sub;
219 if (segment == NULL)
220 continue;
222 for (; sub < inner_last; sub++, iter.pos++) {
223 iter.cri = segment->info[sub];
224 if (iter.cri != NULL && (*callback)(&iter, user_data))
225 return TRUE;
228 return FALSE;
231 /*****************************************************************************/
233 typedef struct _ColRowIndex {
234 int first, last;
235 } ColRowIndex;
238 static void
239 cb_colrow_index_counter (gpointer data, gpointer user_data)
241 ColRowIndex *index = data;
242 gint *count = user_data;
243 if (data != NULL)
244 *count += index->last - index->first + 1;
247 gint
248 colrow_vis_list_length (ColRowVisList *list)
250 gint count = 0;
251 g_slist_foreach (list, cb_colrow_index_counter, &count);
252 return count;
256 * colrow_state_group_destroy:
257 * @set: (transfer full): the group to destroy.
259 * Returns: (transfer none) (nullable): %NULL.
261 ColRowStateGroup *
262 colrow_state_group_destroy (ColRowStateGroup *group)
264 ColRowStateGroup *ptr;
265 for (ptr = group; ptr != NULL ; ptr = ptr->next)
266 colrow_state_list_destroy (ptr->data);
267 g_slist_free (group);
268 return NULL;
271 static gint
272 colrow_index_compare (ColRowIndex const * a, ColRowIndex const * b)
274 return a->first - b->first;
278 * colrow_index_list_to_string: Convert an index list into a string.
279 * The result must be freed by the caller.
280 * It will be something like : A-B, F-G
282 * @list: The list
283 * @is_cols: Column index list or row index list?
284 * @is_single: If non-null this will be set to %TRUE if there's only a single col/row involved.
286 GString *
287 colrow_index_list_to_string (ColRowIndexList *list, gboolean is_cols, gboolean *is_single)
289 ColRowIndexList *ptr;
290 GString *result;
291 gboolean single = TRUE;
293 g_return_val_if_fail (list != NULL, NULL);
295 result = g_string_new (NULL);
296 for (ptr = list; ptr != NULL; ptr = ptr->next) {
297 ColRowIndex *index = ptr->data;
299 if (is_cols)
300 g_string_append (result, cols_name (index->first, index->last));
301 else
302 g_string_append (result, rows_name (index->first, index->last));
304 if (index->last != index->first)
305 single = FALSE;
307 if (ptr->next) {
308 g_string_append (result, ", ");
309 single = FALSE;
313 if (is_single)
314 *is_single = single;
316 return result;
320 * colrow_get_index_list:
321 * @first:
322 * @last:
323 * @list: (transfer full):
325 * Build an ordered list of pairs doing intelligent merging
326 * of overlapping regions.
328 * Returns: (transfer full): @list.
330 ColRowIndexList *
331 colrow_get_index_list (int first, int last, ColRowIndexList *list)
333 ColRowIndex *tmp, *prev;
334 GList *ptr;
336 tmp = g_new (ColRowIndex, 1);
337 tmp->first = first;
338 tmp->last = last;
340 list = g_list_insert_sorted (list, tmp,
341 (GCompareFunc)&colrow_index_compare);
343 prev = list->data;
344 for (ptr = list->next ; ptr != NULL ; ) {
345 tmp = ptr->data;
347 /* at the end of existing segment or contained */
348 if (prev->last+1 >= tmp->first) {
349 GList *next = ptr->next;
350 if (prev->last < tmp->last)
351 prev->last = tmp->last;
352 list = g_list_remove_link (list, ptr);
353 ptr = next;
354 } else {
355 ptr = ptr->next;
356 prev = tmp;
359 return list;
363 * colrow_index_list_copy:
364 * @list: #ColRowIndexList
366 * Returns: (transfer full):
368 ColRowIndexList *
369 colrow_index_list_copy (ColRowIndexList *list)
371 GList *copy = NULL, *ptr;
373 for (ptr = list ; ptr != NULL ; ptr = ptr->next) {
374 ColRowIndex *tmp = g_new (ColRowIndex, 1);
375 ColRowIndex *ex = ptr->data;
376 tmp->first = ex->first;
377 tmp->last = ex->last;
378 copy = g_list_prepend (copy, tmp);
380 return g_list_reverse (copy);
383 static void
384 colrow_set_single_state (ColRowState *state,
385 Sheet *sheet, int i, gboolean is_cols)
387 ColRowInfo const *info = sheet_colrow_get_info (sheet, i, is_cols);
388 state->is_default = col_row_info_is_default (info);
389 state->size_pts = info->size_pts;
390 state->outline_level = info->outline_level;
391 state->is_collapsed = info->is_collapsed;
392 state->hard_size = info->hard_size;
393 state->visible = info->visible;
397 * colrow_state_list_destroy:
398 * @list: (transfer full): the list to destroy.
400 * Returns: (transfer none): %NULL.
402 ColRowStateList *
403 colrow_state_list_destroy (ColRowStateList *list)
405 g_slist_free_full (list, g_free);
406 return NULL;
410 * colrow_get_states:
411 * @sheet: #Sheet
412 * @is_cols: %TRUE if columns.
413 * @first:
414 * @last:
416 * Returns: (transfer full):
418 ColRowStateList *
419 colrow_get_states (Sheet *sheet, gboolean is_cols, int first, int last)
421 ColRowStateList *list = NULL;
422 ColRowRLEState *rles;
423 ColRowState run_state;
424 int i, run_length;
426 g_return_val_if_fail (IS_SHEET (sheet), NULL);
427 g_return_val_if_fail (first <= last, NULL);
429 colrow_set_single_state (&run_state, sheet, first, is_cols);
430 run_length = 1;
432 for (i = first + 1; i <= last; ++i) {
433 ColRowState cur_state;
434 colrow_set_single_state (&cur_state, sheet, i, is_cols);
436 /* If state changed, start a new block */
437 if (cur_state.is_default != run_state.is_default ||
438 cur_state.size_pts != run_state.size_pts ||
439 cur_state.outline_level != run_state.outline_level ||
440 cur_state.is_collapsed != run_state.is_collapsed ||
441 cur_state.hard_size != run_state.hard_size ||
442 cur_state.visible != run_state.visible) {
443 rles = g_new (ColRowRLEState, 1);
444 rles->length = run_length;
445 rles->state = run_state;
446 list = g_slist_prepend (list, rles);
448 run_state = cur_state;
449 run_length = 1;
450 } else
451 ++run_length;
454 /* Store the final run */
455 rles = g_new (ColRowRLEState, 1);
456 rles->length = run_length;
457 rles->state = run_state;
458 list = g_slist_prepend (list, rles);
460 return g_slist_reverse (list);
463 struct resize_closure {
464 Sheet *sheet;
465 int new_size;
466 gboolean is_cols;
469 static gboolean
470 cb_set_colrow_size (GnmColRowIter const *iter, gpointer userdata)
472 if (iter->cri->visible) {
473 struct resize_closure const *c = userdata;
475 if (c->is_cols)
476 sheet_col_set_size_pixels (c->sheet, iter->pos,
477 c->new_size, TRUE);
478 else
479 sheet_row_set_size_pixels (c->sheet, iter->pos,
480 c->new_size, TRUE);
482 return FALSE;
485 static GnmValue *
486 cb_clear_variable_width_content (GnmCellIter const *iter,
487 G_GNUC_UNUSED gpointer user)
489 GnmRenderedValue *rv = gnm_cell_get_rendered_value (iter->cell);
490 if (rv && rv->variable_width) {
491 iter->ri->needs_respan = TRUE;
492 gnm_cell_unrender (iter->cell);
494 return NULL;
498 * colrow_get_sizes:
499 * @sheet: #Sheet
500 * @is_cols: %TRUE if columns.
501 * @src:
502 * @new_size:
504 * Returns: (transfer full):
506 ColRowStateGroup *
507 colrow_get_sizes (Sheet *sheet, gboolean is_cols,
508 ColRowIndexList *src, int new_size)
510 ColRowStateGroup *res = NULL;
511 ColRowIndexList *ptr;
513 for (ptr = src; ptr != NULL ; ptr = ptr->next) {
514 ColRowIndex const *index = ptr->data;
515 res = g_slist_prepend (res, colrow_get_states (sheet, is_cols,
516 index->first, index->last));
518 if (new_size > 0 && index->first == 0 &&
519 (index->last+1) >= colrow_max (is_cols, sheet)) {
520 ColRowRLEState *rles = g_new0 (ColRowRLEState, 1);
522 rles->length = -1; /* Flag as changing the default */
524 if (is_cols)
525 rles->state.size_pts = sheet_col_get_default_size_pts (sheet);
526 else
527 rles->state.size_pts = sheet_row_get_default_size_pts (sheet);
529 /* Result is a magic 'default' record + >= 1 normal */
530 return g_slist_prepend (res, g_slist_append (NULL, rles));
534 return res;
538 * colrow_set_sizes:
539 * @sheet: #Sheet
540 * @is_cols:
541 * @src:
542 * @new_size:
543 * @from:
544 * @to:
546 * Returns: (transfer full):
548 ColRowStateGroup *
549 colrow_set_sizes (Sheet *sheet, gboolean is_cols,
550 ColRowIndexList *src, int new_size, int from, int to)
551 /* from & to are used to restrict fitting to that range. Pass 0, -1 if you want to use the */
552 /* whole row/column */
554 int i;
555 ColRowStateGroup *res = NULL;
556 ColRowIndexList *ptr;
558 for (ptr = src; ptr != NULL ; ptr = ptr->next) {
559 ColRowIndex const *index = ptr->data;
560 res = g_slist_prepend (res, colrow_get_states (sheet, is_cols,
561 index->first, index->last));
563 /* FIXME:
564 * If we are changing the size of more than half of the rows/col to
565 * something specific (not autosize) we should change the default
566 * row/col size instead. However, it is unclear how to handle
567 * hard sizing.
569 * we need better management of rows/cols. Currently if they are all
570 * defined calculation speed grinds to a halt.
572 if (new_size > 0 && index->first == 0 &&
573 (index->last+1) >= colrow_max (is_cols, sheet)) {
574 struct resize_closure closure;
575 ColRowRLEState *rles = g_new0 (ColRowRLEState, 1);
577 rles->length = -1; /* Flag as changing the default */
579 closure.sheet = sheet;
580 closure.new_size = new_size;
581 closure.is_cols = is_cols;
582 if (is_cols) {
583 rles->state.size_pts = sheet_col_get_default_size_pts (sheet);
584 sheet_col_set_default_size_pixels (sheet, new_size);
585 col_row_collection_foreach (&sheet->cols, 0, gnm_sheet_get_last_col (sheet),
586 &cb_set_colrow_size, &closure);
587 } else {
588 rles->state.size_pts = sheet_row_get_default_size_pts (sheet);
589 sheet_row_set_default_size_pixels (sheet, new_size);
590 col_row_collection_foreach (&sheet->rows, 0, gnm_sheet_get_last_row (sheet),
591 &cb_set_colrow_size, &closure);
594 /* force a re-render of cells with expanding formats */
595 if (is_cols)
596 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_BLANK,
597 0, 0, gnm_sheet_get_last_col (sheet), gnm_sheet_get_last_row (sheet),
598 (CellIterFunc) &cb_clear_variable_width_content, NULL);
600 /* Result is a magic 'default' record + >= 1 normal */
601 return g_slist_prepend (res, g_slist_append (NULL, rles));
604 if (is_cols) {
605 /* force a re-render of cells with expanding formats */
606 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_BLANK,
607 index->first, 0, index->last, gnm_sheet_get_last_row (sheet),
608 (CellIterFunc) &cb_clear_variable_width_content, NULL);
610 /* In order to properly reposition cell comments in
611 * merged cells that cross the boundary we need to do
612 * everything. Remove this when comments are handled
613 * properly */
614 sheet->priv->reposition_objects.col = 0;
617 for (i = index->first ; i <= index->last ; ++i) {
618 int tmp = new_size;
619 if (tmp < 0) {
620 int max = is_cols ? gnm_sheet_get_last_row (sheet)
621 : gnm_sheet_get_last_col (sheet);
622 if (from < 0)
623 from = 0;
624 if (to < 0 || to > max)
625 to = max;
626 if (from > max)
627 from = to;
628 /* Fall back to assigning the default if it is empty */
629 tmp = (is_cols)
630 ? sheet_col_size_fit_pixels (sheet, i, from, to, FALSE)
631 : sheet_row_size_fit_pixels (sheet, i, from, to, FALSE);
633 if (tmp > 0) {
634 if (is_cols)
635 sheet_col_set_size_pixels (sheet, i, tmp, new_size > 0);
636 else
637 sheet_row_set_size_pixels (sheet, i, tmp, new_size > 0);
638 } else if (sheet_colrow_get (sheet, i, is_cols) != NULL) {
639 if (is_cols)
640 sheet_col_set_size_pixels (sheet, i,
641 sheet_col_get_default_size_pixels (sheet), FALSE);
642 else
643 sheet_row_set_size_pixels (sheet, i,
644 sheet_row_get_default_size_pixels (sheet), FALSE);
649 return res;
653 * NOTE : this is a low level routine it does not redraw or
654 * reposition objects
656 * NOTE : this does not delete states any longer since it may be used
657 * for several sheets.
660 void
661 colrow_set_states (Sheet *sheet, gboolean is_cols,
662 int first, ColRowStateList *states)
664 GSList *l;
665 int i, max_outline, offset = first;
666 ColRowCollection *infos;
667 double scale;
669 g_return_if_fail (IS_SHEET (sheet));
671 infos = is_cols ? &(sheet->cols) : &(sheet->rows);
672 max_outline = infos->max_outline_level;
673 scale = colrow_compute_pixel_scale (sheet, is_cols);
675 for (l = states; l != NULL; l = l->next) {
676 ColRowRLEState const *rles = l->data;
677 ColRowState const *state = &rles->state;
679 if (max_outline < state->outline_level)
680 max_outline = state->outline_level;
682 for (i = offset; i < offset + rles->length; i++) {
683 if (state->is_default) {
684 ColRowSegment *segment = COLROW_GET_SEGMENT(infos, i);
685 if (segment != NULL) {
686 int const sub = COLROW_SUB_INDEX (i);
687 ColRowInfo *cri = segment->info[sub];
688 if (cri != NULL) {
689 segment->info[sub] = NULL;
690 colrow_free (cri);
693 } else {
694 ColRowInfo *cri = sheet_colrow_fetch (sheet, i, is_cols);
695 cri->hard_size = state->hard_size;
696 cri->size_pts = state->size_pts;
697 colrow_compute_pixels_from_pts (cri, sheet, is_cols, scale);
698 col_row_info_set_outline (cri, state->outline_level,
699 state->is_collapsed);
702 offset += rles->length;
705 /* Notify sheet of pending update */
706 sheet->priv->recompute_visibility = TRUE;
707 if (is_cols) {
708 sheet_flag_recompute_spans (sheet);
710 /* In order to properly reposition cell
711 * comments in merged cells that cross the
712 * boundary we need to do everything. Revert
713 * this when comments are handled properly */
714 #if 0
715 if (sheet->priv->reposition_objects.col > first)
716 sheet->priv->reposition_objects.col = first;
717 #else
718 sheet->priv->reposition_objects.col = 0;
719 #endif
720 } else {
721 if (sheet->priv->reposition_objects.row > first)
722 sheet->priv->reposition_objects.row = first;
724 sheet_colrow_gutter (sheet, is_cols, max_outline);
727 void
728 colrow_restore_state_group (Sheet *sheet, gboolean is_cols,
729 ColRowIndexList *selection,
730 ColRowStateGroup *state_groups)
732 ColRowStateGroup *ptr = state_groups;
734 /* Cycle to end, we have to traverse the selections
735 * in parallel with the state_groups
737 selection = g_list_last (selection);
738 for (; selection != NULL && ptr != NULL ; ptr = ptr->next) {
739 ColRowIndex const *index = selection->data;
740 ColRowStateList *list = ptr->data;
741 ColRowRLEState const *rles = list->data;
743 /* MAGIC : the -1 was set above to flag this */
744 if (rles->length == -1) {
745 if (is_cols)
746 sheet_col_set_default_size_pts (sheet, rles->state.size_pts);
747 else
748 sheet_row_set_default_size_pts (sheet, rles->state.size_pts);
750 /* we are guaranteed to have at least 1 more record */
751 ptr = ptr->next;
754 colrow_set_states (sheet, is_cols, index->first, ptr->data);
755 /* force a re-render of cells with expanding formats */
756 if (is_cols)
757 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_BLANK,
758 index->first, 0, index->last, gnm_sheet_get_last_row (sheet),
759 (CellIterFunc) &cb_clear_variable_width_content, NULL);
760 selection = selection->prev;
765 * rows_height_update:
766 * @sheet: The sheet,
767 * @range: The range whose rows should be resized.
768 * @shrink: If set to FALSE, rows will never shrink!
770 * Use this function having changed the font size to auto
771 * resize the row heights to make the text fit nicely.
773 void
774 rows_height_update (Sheet *sheet, GnmRange const * range, gboolean shrink)
776 /* FIXME : this needs to check font sizes and contents rather than
777 * just contents. Empty cells will cause resize also */
778 colrow_autofit (sheet, range, FALSE, FALSE,
779 FALSE, !shrink,
780 NULL, NULL);
783 /* ------------------------------------------------------------------------- */
785 struct cb_autofit {
786 Sheet *sheet;
787 const GnmRange *range;
788 gboolean ignore_strings;
789 gboolean min_current;
790 gboolean min_default;
793 static gboolean
794 cb_autofit_col (GnmColRowIter const *iter, gpointer data_)
796 struct cb_autofit *data = data_;
797 int size, min, max;
799 if (iter->cri->hard_size)
800 return FALSE;
802 size = sheet_col_size_fit_pixels (data->sheet, iter->pos,
803 data->range->start.row, data->range->end.row,
804 data->ignore_strings);
805 /* FIXME: better idea than this? */
806 max = 50 * sheet_col_get_default_size_pixels (data->sheet);
807 size = MIN (size, max);
809 min = 0;
810 if (data->min_current)
811 min = MAX (min, iter->cri->size_pixels);
812 if (data->min_default)
813 min = MAX (min, sheet_col_get_default_size_pixels (data->sheet));
815 if (size > min)
816 sheet_col_set_size_pixels (data->sheet, iter->pos, size, FALSE);
818 return FALSE;
821 static gboolean
822 cb_autofit_row (GnmColRowIter const *iter, gpointer data_)
824 struct cb_autofit *data = data_;
825 int size, min, max;
827 if (iter->cri->hard_size)
828 return FALSE;
830 size = sheet_row_size_fit_pixels (data->sheet, iter->pos,
831 data->range->start.col, data->range->end.col,
832 data->ignore_strings);
833 max = 20 * sheet_row_get_default_size_pixels (data->sheet);
834 size = MIN (size, max);
836 min = 0;
837 if (data->min_current)
838 min = MAX (min, iter->cri->size_pixels);
839 if (data->min_default)
840 min = MAX (min, sheet_row_get_default_size_pixels (data->sheet));
842 if (size > min)
843 sheet_row_set_size_pixels (data->sheet, iter->pos, size, FALSE);
845 return FALSE;
849 * colrow_autofit:
850 * @sheet: the sheet to change
851 * @range: the range to consider
852 * @is_cols: %TRUE for columns, %FALSE for rows.
853 * @ignore_strings: Don't consider cells with string values.
854 * @min_current: Don't shrink below current size.
855 * @min_default: Don't shrink below default size.
856 * @indices: (nullable): indices appropriate for colrow_restore_state_group.
857 * @sizes: (nullable): old sizes appropriate for colrow_restore_state_group.
859 * This function autofits columns or rows in @range as specified by
860 * @is_cols. Only cells in @range are considered for the sizing
861 * and the size can be bounded below by current size and/or default
862 * size.
864 void
865 colrow_autofit (Sheet *sheet, const GnmRange *range, gboolean is_cols,
866 gboolean ignore_strings,
867 gboolean min_current, gboolean min_default,
868 ColRowIndexList **indices,
869 ColRowStateGroup **sizes)
871 struct cb_autofit data;
872 int a, b;
873 ColRowCollection *crs;
874 ColRowHandler handler;
876 data.sheet = sheet;
877 data.range = range;
878 data.ignore_strings = ignore_strings;
879 data.min_current = min_current;
880 data.min_default = min_default;
882 if (is_cols) {
883 a = range->start.col;
884 b = range->end.col;
885 crs = &sheet->cols;
886 handler = cb_autofit_col;
887 } else {
888 a = range->start.row;
889 b = range->end.row;
890 crs = &sheet->rows;
891 handler = cb_autofit_row;
894 if (indices)
895 *indices = colrow_get_index_list (a, b, NULL);
896 if (sizes)
897 *sizes = g_slist_prepend (NULL, colrow_get_states (sheet, is_cols, a, b));
899 /* We potentially do a lot of recalcs as part of this, so make sure
900 stuff that caches sub-computations see the whole thing instead
901 of clearing between cells. */
902 gnm_app_recalc_start ();
903 col_row_collection_foreach (crs, a, b, handler, &data);
904 gnm_app_recalc_finish ();
907 void
908 colrow_autofit_col (Sheet *sheet, GnmRange *r)
910 colrow_autofit (sheet, r, TRUE, TRUE,
911 TRUE, FALSE, NULL, NULL);
912 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_BLANK,
913 r->start.col, 0,
914 r->end.col, gnm_sheet_get_last_row (sheet),
915 (CellIterFunc) &cb_clear_variable_width_content,
916 NULL);
919 void
920 colrow_autofit_row (Sheet *sheet, GnmRange *r)
922 colrow_autofit (sheet, r, FALSE, FALSE,
923 TRUE, FALSE, NULL, NULL);
926 /*****************************************************************************/
928 typedef struct
930 gboolean is_cols, visible;
931 ColRowVisList *elements;
932 } ColRowVisiblity;
934 static gint
935 colrow_index_cmp (ColRowIndex const *a, ColRowIndex const *b)
937 /* We can be very simplistic here because the ranges never overlap */
938 return b->first - a->first;
941 static void
942 colrow_visibility (Sheet const *sheet, ColRowVisiblity * const dat,
943 int first, int last)
945 int i;
946 gboolean const visible = dat->visible;
947 ColRowInfo * (*get) (Sheet const *sheet, int pos) = (dat->is_cols)
948 ? &sheet_col_get : &sheet_row_get;
950 /* Find the end of a segment that will be toggled */
951 for (i = last; i >= first; --i) {
952 int j;
953 ColRowIndex *res;
954 ColRowInfo const *cri = (*get) (sheet, i);
956 if (cri == NULL) {
957 if (visible != 0)
958 continue;
959 } else if ((visible != 0) == (cri->visible != 0))
960 continue;
962 /* Find the begining */
963 for (j = i; j >= first ; --j) {
964 cri = (*get) (sheet, j);
965 if (cri == NULL) {
966 if (visible != 0)
967 break;
968 } else if ((visible != 0) == (cri->visible != 0))
969 break;
970 else if (cri->is_collapsed) {
971 --j;
972 break;
975 res = g_new (ColRowIndex, 1);
976 res->first = (j >= first) ? j+1 : first;
977 res->last = i;
978 #if 0
979 g_printerr ("%d %d\n", res->index, res->count);
980 #endif
981 dat->elements = g_slist_insert_sorted (dat->elements, res,
982 (GCompareFunc)colrow_index_cmp);
984 if (visible && cri != NULL && cri->is_collapsed) {
985 i = colrow_find_outline_bound (
986 sheet, dat->is_cols, j,
987 cri->outline_level+1, FALSE);
988 } else
989 i = j;
994 * colrow_get_outline_toggle:
995 * @sheet: #Sheet
996 * @is_cols:
997 * @visible:
998 * @first:
999 * @last:
1001 * Returns: (transfer full):
1003 ColRowVisList *
1004 colrow_get_outline_toggle (Sheet const *sheet, gboolean is_cols, gboolean visible,
1005 int first, int last)
1007 ColRowVisiblity closure;
1008 closure.is_cols = is_cols;
1009 closure.visible = visible;
1010 closure.elements = NULL;
1012 colrow_visibility (sheet, &closure, first, last);
1013 return closure.elements;
1016 static void
1017 cb_colrow_visibility (SheetView *sv, GnmRange const *r, gpointer closure)
1019 ColRowVisiblity * const dat = (ColRowVisiblity *)closure;
1020 int first, last;
1022 if (dat->is_cols) {
1023 first = r->start.col;
1024 last = r->end.col;
1025 } else {
1026 first = r->start.row;
1027 last = r->end.row;
1029 colrow_visibility (sv_sheet (sv), dat, first, last);
1033 * colrow_get_visiblity_toggle:
1034 * @sv: The sheet view whose selection we are interested in.
1035 * @is_cols: A flag indicating whether this it is a column or a row.
1036 * @visible: Should we unhide or hide the cols/rows.
1038 * Searches the selection list and generates a list of index,count
1039 * pairs of row/col ranges that need to be hidden or unhiden.
1041 * NOTE : leave sheet non-const until we have a const version of
1042 * sv_selection_apply.
1044 * Returns: (transfer full): the list.
1046 ColRowVisList *
1047 colrow_get_visiblity_toggle (SheetView *sv, gboolean is_cols,
1048 gboolean visible)
1050 ColRowVisiblity closure;
1051 closure.is_cols = is_cols;
1052 closure.visible = visible;
1053 closure.elements = NULL;
1055 sv_selection_apply (sv, &cb_colrow_visibility, FALSE, &closure);
1057 return closure.elements;
1061 * colrow_set_visibility_list:
1063 * This is the high level command that is wrapped by undo and redo.
1064 * It should not be called by other commands.
1066 void
1067 colrow_set_visibility_list (Sheet *sheet, gboolean is_cols,
1068 gboolean visible, ColRowVisList *list)
1070 ColRowVisList *ptr;
1071 ColRowIndex *info;
1073 for (ptr = list; ptr != NULL ; ptr = ptr->next) {
1074 info = ptr->data;
1075 colrow_set_visibility (sheet, is_cols, visible,
1076 info->first, info->last);
1079 if (visible)
1080 sheet_colrow_optimize (sheet);
1082 if (is_cols)
1083 sheet_queue_respan (sheet, 0, gnm_sheet_get_last_row (sheet));
1084 if (list != NULL)
1085 sheet_redraw_all (sheet, TRUE);
1089 * col_row_info_set_outline:
1090 * @cri: #ColRowInfo to tweak
1091 * @outline_level:
1092 * @is_collapsed:
1094 * Adjust the outline state of a col/row
1096 void
1097 col_row_info_set_outline (ColRowInfo *cri, int outline_level, gboolean is_collapsed)
1099 g_return_if_fail (outline_level >= 0);
1101 cri->is_collapsed = !!is_collapsed;
1102 cri->outline_level = outline_level;
1106 * colrow_find_outline_bound:
1108 * find the next/prev col/row at the designated depth starting from the
1109 * supplied @index.
1112 colrow_find_outline_bound (Sheet const *sheet, gboolean is_cols,
1113 int index, int depth, gboolean inc)
1115 ColRowInfo * (*get) (Sheet const *sheet, int pos) = is_cols
1116 ? &sheet_col_get : &sheet_row_get;
1117 int const max = colrow_max (is_cols, sheet);
1118 int const step = inc ? 1 : -1;
1120 while (1) {
1121 ColRowInfo const *cri;
1122 int const next = index + step;
1124 if (next < 0 || next >= max)
1125 return index;
1126 cri = (*get) (sheet, next);
1127 if (cri == NULL || cri->outline_level < depth)
1128 return index;
1129 index = next;
1132 return index;
1136 * colrow_set_visibility:
1137 * @sheet: the sheet
1138 * @is_cols: Are we dealing with rows or columns.
1139 * @visible: Make things visible or invisible.
1140 * @first: The index of the first row/col (inclusive)
1141 * @last: The index of the last row/col (inclusive)
1143 * Change the visibility of the selected range of contiguous cols/rows.
1144 * NOTE : only changes the collapsed state for the LAST+1 element.
1146 void
1147 colrow_set_visibility (Sheet *sheet, gboolean is_cols,
1148 gboolean visible, int first, int last)
1150 int i, step, prev_outline = 0;
1151 gboolean changed = FALSE;
1152 GnmRange * const bound = &sheet->priv->unhidden_region;
1153 gboolean const fwd = is_cols ? sheet->outline_symbols_right : sheet->outline_symbols_below;
1155 g_return_if_fail (IS_SHEET (sheet));
1156 g_return_if_fail (first <= last);
1158 if (visible) { /* expand to include newly visible regions */
1159 if (is_cols) {
1160 if (bound->start.col > first)
1161 bound->start.col = first;
1162 if (bound->end.col < last)
1163 bound->end.col = last;
1164 } else {
1165 if (bound->start.row > first)
1166 bound->start.row = first;
1167 if (bound->end.row < last)
1168 bound->end.row = last;
1170 } else { /* contract to exclude newly hidden regions */
1171 if (is_cols) {
1172 if (bound->start.col >= first && bound->start.col <= last)
1173 bound->start.col = last+1;
1174 if (bound->end.col <= last && bound->end.col >= first)
1175 bound->end.col = first-1;
1176 } else {
1177 if (bound->start.row >= first && bound->start.row <= last)
1178 bound->start.row = last+1;
1179 if (bound->end.row <= last && bound->end.row >= first)
1180 bound->end.row = first-1;
1184 if (fwd) {
1185 i = first;
1186 step = 1;
1187 } else {
1188 i = last;
1189 step = -1;
1192 for (; fwd ? (i <= last) : (i >= first) ; i += step) {
1193 ColRowInfo * const cri = sheet_colrow_fetch (sheet, i, is_cols);
1195 if (changed && prev_outline > cri->outline_level && !visible)
1196 cri->is_collapsed = FALSE;
1198 changed = (visible == 0) != (cri->visible == 0);
1199 if (changed) {
1200 cri->visible = visible;
1201 prev_outline = cri->outline_level;
1202 sheet->priv->recompute_visibility = TRUE;
1204 if (is_cols) {
1205 sheet_flag_recompute_spans (sheet);
1207 /* In order to properly reposition cell
1208 * comments in merged cells that cross the
1209 * boundary we need to do everything. Revert
1210 * this when comments are handled properly */
1211 #if 0
1212 if (sheet->priv->reposition_objects.col > i)
1213 sheet->priv->reposition_objects.col = i;
1214 #else
1215 sheet->priv->reposition_objects.col = 0;
1216 #endif
1217 } else {
1218 if (sheet->priv->reposition_objects.row > i)
1219 sheet->priv->reposition_objects.row = i;
1224 if (changed && 0 <= i && i < colrow_max (is_cols, sheet)) {
1225 ColRowInfo *cri = sheet_colrow_get (sheet, i, is_cols);
1226 if (!cri && !visible && prev_outline > 0)
1227 cri = sheet_colrow_fetch (sheet, i, is_cols);
1229 if (cri && prev_outline > cri->outline_level)
1230 cri->is_collapsed = !visible;
1235 * colrow_get_global_outline:
1236 * @sheet:
1237 * @is_cols:
1238 * @depth:
1239 * @show:
1240 * @hide:
1242 * Collect the set of visiblity changes required to change the visiblity of
1243 * all outlined columns such tach those > @depth are visible.
1245 void
1246 colrow_get_global_outline (Sheet const *sheet, gboolean is_cols, int depth,
1247 ColRowVisList **show, ColRowVisList **hide)
1249 ColRowInfo const *cri;
1250 ColRowIndex *prev = NULL;
1251 gboolean show_prev = FALSE;
1252 unsigned tmp, prev_outline = 0;
1253 int i, max = is_cols ? sheet->cols.max_used : sheet->rows.max_used;
1255 *show = *hide = NULL;
1256 for (i = 0; i <= max ; i++) {
1257 cri = sheet_colrow_get (sheet, i, is_cols);
1259 if (cri == NULL || cri->outline_level == 0) {
1260 prev_outline = 0;
1261 continue;
1263 tmp = prev_outline;
1264 prev_outline = cri->outline_level;
1266 /* see what sort of changes are necessary and do simple run
1267 * length encoding. Do not be too efficent, we need to change
1268 * the visiblity per outline level or the collapse state
1269 * change in colrow_set_visibility is missed. */
1270 if (cri->outline_level < depth) {
1271 if (cri->visible)
1272 continue;
1273 if (show_prev && prev != NULL && prev->last == (i-1) &&
1274 tmp == prev_outline) {
1275 prev->last = i;
1276 continue;
1278 prev = g_new (ColRowIndex, 1);
1279 prev->first = prev->last = i;
1280 *show = g_slist_prepend (*show, prev);
1281 show_prev = TRUE;
1282 } else {
1283 if (!cri->visible)
1284 continue;
1285 if (!show_prev && prev != NULL && prev->last == (i-1) &&
1286 tmp == prev_outline) {
1287 prev->last = i;
1288 continue;
1290 prev = g_new (ColRowIndex, 1);
1291 prev->first = prev->last = i;
1292 *hide = g_slist_prepend (*hide, prev);
1293 show_prev = FALSE;
1297 *show = g_slist_reverse (*show);
1298 *hide = g_slist_reverse (*hide);
1301 void
1302 col_row_collection_resize (ColRowCollection *infos, int size)
1304 int end_idx = COLROW_SEGMENT_INDEX (size);
1305 int i = infos->info->len - 1;
1307 while (i >= end_idx) {
1308 ColRowSegment *segment = g_ptr_array_index (infos->info, i);
1309 if (segment) {
1310 g_free (segment);
1311 g_ptr_array_index (infos->info, i) = NULL;
1313 i--;
1316 g_ptr_array_set_size (infos->info, end_idx);