Compilation: fix warning.
[gnumeric.git] / src / colrow.c
blob50ab5ea1daa5225806940f7d3b93fd2f05b6d1c4
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_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_copy,
56 (GBoxedFreeFunc)col_row_info_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
103 gboolean
104 colrow_is_default (ColRowInfo const *cri)
106 g_return_val_if_fail (cri != NULL, FALSE);
107 return cri->is_default;
111 * colrow_is_empty :
112 * @cri: #ColRowInfo
114 * TRUE if there is no information in col/row @cri.
116 gboolean
117 colrow_is_empty (ColRowInfo const *cri)
119 if (cri == NULL)
120 return TRUE;
121 return cri->is_default &&
122 cri->outline_level == 0 &&
123 !cri->is_collapsed &&
124 !cri->hard_size;
128 * colrow_equal :
129 * @a: ColRowInfo #1
130 * @b: ColRowInfo #2
132 * Returns true if the infos are equivalent.
134 gboolean
135 colrow_equal (ColRowInfo const *a, ColRowInfo const *b)
137 if (a == NULL)
138 return b == NULL;
139 if (b == NULL)
140 return FALSE;
142 return fabs (a->size_pts - b->size_pts) < 1e-5 &&
143 a->outline_level == b->outline_level &&
144 a->is_collapsed == b->is_collapsed &&
145 a->hard_size == b->hard_size &&
146 a->visible == b->visible;
150 * colrow_copy :
151 * @dst:
152 * @src:
154 * Assign all content, except the position of @src to @dst
156 void
157 colrow_copy (ColRowInfo *dst, ColRowInfo const *src)
159 dst->size_pts = src->size_pts;
160 dst->size_pixels = src->size_pixels;
161 dst->outline_level = src->outline_level;
162 dst->is_collapsed = src->is_collapsed;
163 dst->hard_size = src->hard_size;
164 dst->visible = src->visible;
167 ColRowInfo *
168 col_row_info_new (void)
170 return g_slice_new (ColRowInfo);
173 void
174 colrow_free (ColRowInfo *cri)
176 g_slice_free1 (sizeof (*cri), cri);
180 * colrow_foreach:
181 * @infos: The Row or Column collection.
182 * @first: start position (inclusive)
183 * @last: stop column (inclusive)
184 * @callback: (scope call): A callback function which should return TRUE to stop
185 * the iteration.
186 * @user_data: A bagage pointer.
188 * Iterates through the existing rows or columns within the range supplied.
189 * Currently only support left -> right iteration. If a callback returns
190 * TRUE iteration stops.
192 gboolean
193 colrow_foreach (ColRowCollection const *infos, int first, int last,
194 ColRowHandler callback, gpointer user_data)
196 GnmColRowIter iter;
197 ColRowSegment const *segment;
198 int sub, inner_last, i;
200 /* TODO : Do we need to support right -> left as an option */
202 /* clip */
203 if (last > infos->max_used)
204 last = infos->max_used;
206 for (i = first; i <= last ; ) {
207 segment = COLROW_GET_SEGMENT (infos, i);
208 sub = COLROW_SUB_INDEX(i);
209 inner_last = (COLROW_SEGMENT_INDEX (last) == COLROW_SEGMENT_INDEX (i))
210 ? COLROW_SUB_INDEX (last)+1 : COLROW_SEGMENT_SIZE;
211 iter.pos = i;
212 i += COLROW_SEGMENT_SIZE - sub;
213 if (segment == NULL)
214 continue;
216 for (; sub < inner_last; sub++, iter.pos++) {
217 iter.cri = segment->info[sub];
218 if (iter.cri != NULL && (*callback)(&iter, user_data))
219 return TRUE;
222 return FALSE;
225 /*****************************************************************************/
227 typedef struct _ColRowIndex {
228 int first, last;
229 } ColRowIndex;
232 static void
233 cb_colrow_index_counter (gpointer data, gpointer user_data)
235 ColRowIndex *index = data;
236 gint *count = user_data;
237 if (data != NULL)
238 *count += index->last - index->first + 1;
241 gint
242 colrow_vis_list_length (ColRowVisList *list)
244 gint count = 0;
245 g_slist_foreach (list, cb_colrow_index_counter, &count);
246 return count;
250 * colrow_state_group_destroy:
251 * @set: (transfer full): the group to destroy.
253 * Returns: (transfer none): %NULL.
255 ColRowStateGroup *
256 colrow_state_group_destroy (ColRowStateGroup *group)
258 ColRowStateGroup *ptr;
259 for (ptr = group; ptr != NULL ; ptr = ptr->next)
260 colrow_state_list_destroy (ptr->data);
261 g_slist_free (group);
262 return NULL;
265 static gint
266 colrow_index_compare (ColRowIndex const * a, ColRowIndex const * b)
268 return a->first - b->first;
272 * colrow_index_list_to_string: Convert an index list into a string.
273 * The result must be freed by the caller.
274 * It will be something like : A-B, F-G
276 * @list: The list
277 * @is_cols: Column index list or row index list?
278 * @is_single: If non-null this will be set to TRUE if there's only a single col/row involved.
280 GString *
281 colrow_index_list_to_string (ColRowIndexList *list, gboolean is_cols, gboolean *is_single)
283 ColRowIndexList *ptr;
284 GString *result;
285 gboolean single = TRUE;
287 g_return_val_if_fail (list != NULL, NULL);
289 result = g_string_new (NULL);
290 for (ptr = list; ptr != NULL; ptr = ptr->next) {
291 ColRowIndex *index = ptr->data;
293 if (is_cols)
294 g_string_append (result, cols_name (index->first, index->last));
295 else
296 g_string_append (result, rows_name (index->first, index->last));
298 if (index->last != index->first)
299 single = FALSE;
301 if (ptr->next) {
302 g_string_append (result, ", ");
303 single = FALSE;
307 if (is_single)
308 *is_single = single;
310 return result;
314 * colrow_get_index_list:
315 * @first:
316 * @last:
317 * @list: (transfer full):
319 * Build an ordered list of pairs doing intelligent merging
320 * of overlapping regions.
322 * Returns: (transfer full): @list.
324 ColRowIndexList *
325 colrow_get_index_list (int first, int last, ColRowIndexList *list)
327 ColRowIndex *tmp, *prev;
328 GList *ptr;
330 tmp = g_new (ColRowIndex, 1);
331 tmp->first = first;
332 tmp->last = last;
334 list = g_list_insert_sorted (list, tmp,
335 (GCompareFunc)&colrow_index_compare);
337 prev = list->data;
338 for (ptr = list->next ; ptr != NULL ; ) {
339 tmp = ptr->data;
341 /* at the end of existing segment or contained */
342 if (prev->last+1 >= tmp->first) {
343 GList *next = ptr->next;
344 if (prev->last < tmp->last)
345 prev->last = tmp->last;
346 list = g_list_remove_link (list, ptr);
347 ptr = next;
348 } else {
349 ptr = ptr->next;
350 prev = tmp;
353 return list;
357 * colrow_index_list_copy:
358 * @list: #ColRowIndexList
360 * Returns: (transfer full):
362 ColRowIndexList *
363 colrow_index_list_copy (ColRowIndexList *list)
365 GList *copy = NULL, *ptr;
367 for (ptr = list ; ptr != NULL ; ptr = ptr->next) {
368 ColRowIndex *tmp = g_new (ColRowIndex, 1);
369 ColRowIndex *ex = ptr->data;
370 tmp->first = ex->first;
371 tmp->last = ex->last;
372 copy = g_list_prepend (copy, tmp);
374 return g_list_reverse (copy);
377 static void
378 colrow_set_single_state (ColRowState *state,
379 Sheet *sheet, int i, gboolean is_cols)
381 ColRowInfo const *info = sheet_colrow_get_info (sheet, i, is_cols);
382 state->is_default = colrow_is_default (info);
383 state->size_pts = info->size_pts;
384 state->outline_level = info->outline_level;
385 state->is_collapsed = info->is_collapsed;
386 state->hard_size = info->hard_size;
387 state->visible = info->visible;
391 * colrow_state_list_destroy:
392 * @list: (transfer full): the list to destroy.
394 * Returns: (transfer none): %NULL.
396 ColRowStateList *
397 colrow_state_list_destroy (ColRowStateList *list)
399 g_slist_free_full (list, g_free);
400 return NULL;
404 * colrow_get_states:
405 * @sheet: #Sheet
406 * @is_cols: %TRUE if columns.
407 * @first:
408 * @last:
410 * Returns: (transfer full):
412 ColRowStateList *
413 colrow_get_states (Sheet *sheet, gboolean is_cols, int first, int last)
415 ColRowStateList *list = NULL;
416 ColRowRLEState *rles;
417 ColRowState run_state;
418 int i, run_length;
420 g_return_val_if_fail (IS_SHEET (sheet), NULL);
421 g_return_val_if_fail (first <= last, NULL);
423 colrow_set_single_state (&run_state, sheet, first, is_cols);
424 run_length = 1;
426 for (i = first + 1; i <= last; ++i) {
427 ColRowState cur_state;
428 colrow_set_single_state (&cur_state, sheet, i, is_cols);
430 /* If state changed, start a new block */
431 if (cur_state.is_default != run_state.is_default ||
432 cur_state.size_pts != run_state.size_pts ||
433 cur_state.outline_level != run_state.outline_level ||
434 cur_state.is_collapsed != run_state.is_collapsed ||
435 cur_state.hard_size != run_state.hard_size ||
436 cur_state.visible != run_state.visible) {
437 rles = g_new (ColRowRLEState, 1);
438 rles->length = run_length;
439 rles->state = run_state;
440 list = g_slist_prepend (list, rles);
442 run_state = cur_state;
443 run_length = 1;
444 } else
445 ++run_length;
448 /* Store the final run */
449 rles = g_new (ColRowRLEState, 1);
450 rles->length = run_length;
451 rles->state = run_state;
452 list = g_slist_prepend (list, rles);
454 return g_slist_reverse (list);
457 struct resize_closure {
458 Sheet *sheet;
459 int new_size;
460 gboolean is_cols;
463 static gboolean
464 cb_set_colrow_size (GnmColRowIter const *iter, gpointer userdata)
466 if (iter->cri->visible) {
467 struct resize_closure const *c = userdata;
469 if (c->is_cols)
470 sheet_col_set_size_pixels (c->sheet, iter->pos,
471 c->new_size, TRUE);
472 else
473 sheet_row_set_size_pixels (c->sheet, iter->pos,
474 c->new_size, TRUE);
476 return FALSE;
479 static GnmValue *
480 cb_clear_variable_width_content (GnmCellIter const *iter,
481 G_GNUC_UNUSED gpointer user)
483 GnmRenderedValue *rv = gnm_cell_get_rendered_value (iter->cell);
484 if (rv && rv->variable_width) {
485 iter->ri->needs_respan = TRUE;
486 gnm_cell_unrender (iter->cell);
488 return NULL;
492 * colrow_get_sizes:
493 * @sheet: #Sheet
494 * @is_cols: %TRUE if columns.
495 * @src:
496 * @new_size:
498 * Returns: (transfer full):
500 ColRowStateGroup *
501 colrow_get_sizes (Sheet *sheet, gboolean is_cols,
502 ColRowIndexList *src, int new_size)
504 ColRowStateGroup *res = NULL;
505 ColRowIndexList *ptr;
507 for (ptr = src; ptr != NULL ; ptr = ptr->next) {
508 ColRowIndex const *index = ptr->data;
509 res = g_slist_prepend (res, colrow_get_states (sheet, is_cols,
510 index->first, index->last));
512 if (new_size > 0 && index->first == 0 &&
513 (index->last+1) >= colrow_max (is_cols, sheet)) {
514 ColRowRLEState *rles = g_new0 (ColRowRLEState, 1);
516 rles->length = -1; /* Flag as changing the default */
518 if (is_cols)
519 rles->state.size_pts = sheet_col_get_default_size_pts (sheet);
520 else
521 rles->state.size_pts = sheet_row_get_default_size_pts (sheet);
523 /* Result is a magic 'default' record + >= 1 normal */
524 return g_slist_prepend (res, g_slist_append (NULL, rles));
528 return res;
532 * colrow_set_sizes:
533 * @sheet: #Sheet
534 * @is_cols:
535 * @src:
536 * @new_size:
537 * @from:
538 * @to:
540 * Returns: (transfer full):
542 ColRowStateGroup *
543 colrow_set_sizes (Sheet *sheet, gboolean is_cols,
544 ColRowIndexList *src, int new_size, int from, int to)
545 /* from & to are used to restrict fitting to that range. Pass 0, -1 if you want to use the */
546 /* whole row/column */
548 int i;
549 ColRowStateGroup *res = NULL;
550 ColRowIndexList *ptr;
552 for (ptr = src; ptr != NULL ; ptr = ptr->next) {
553 ColRowIndex const *index = ptr->data;
554 res = g_slist_prepend (res, colrow_get_states (sheet, is_cols,
555 index->first, index->last));
557 /* FIXME :
558 * If we are changing the size of more than half of the rows/col to
559 * something specific (not autosize) we should change the default
560 * row/col size instead. However, it is unclear how to handle
561 * hard sizing.
563 * we need better management of rows/cols. Currently if they are all
564 * defined calculation speed grinds to a halt.
566 if (new_size > 0 && index->first == 0 &&
567 (index->last+1) >= colrow_max (is_cols, sheet)) {
568 struct resize_closure closure;
569 ColRowRLEState *rles = g_new0 (ColRowRLEState, 1);
571 rles->length = -1; /* Flag as changing the default */
573 closure.sheet = sheet;
574 closure.new_size = new_size;
575 closure.is_cols = is_cols;
576 if (is_cols) {
577 rles->state.size_pts = sheet_col_get_default_size_pts (sheet);
578 sheet_col_set_default_size_pixels (sheet, new_size);
579 colrow_foreach (&sheet->cols, 0, gnm_sheet_get_last_col (sheet),
580 &cb_set_colrow_size, &closure);
581 } else {
582 rles->state.size_pts = sheet_row_get_default_size_pts (sheet);
583 sheet_row_set_default_size_pixels (sheet, new_size);
584 colrow_foreach (&sheet->rows, 0, gnm_sheet_get_last_row (sheet),
585 &cb_set_colrow_size, &closure);
588 /* force a re-render of cells with expanding formats */
589 if (is_cols)
590 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_BLANK,
591 0, 0, gnm_sheet_get_last_col (sheet), gnm_sheet_get_last_row (sheet),
592 (CellIterFunc) &cb_clear_variable_width_content, NULL);
594 /* Result is a magic 'default' record + >= 1 normal */
595 return g_slist_prepend (res, g_slist_append (NULL, rles));
598 if (is_cols) {
599 /* force a re-render of cells with expanding formats */
600 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_BLANK,
601 index->first, 0, index->last, gnm_sheet_get_last_row (sheet),
602 (CellIterFunc) &cb_clear_variable_width_content, NULL);
604 /* In order to properly reposition cell comments in
605 * merged cells that cross the boundary we need to do
606 * everything. Remove this when comments are handled
607 * properly */
608 sheet->priv->reposition_objects.col = 0;
611 for (i = index->first ; i <= index->last ; ++i) {
612 int tmp = new_size;
613 if (tmp < 0) {
614 int max = is_cols ? gnm_sheet_get_last_row (sheet)
615 : gnm_sheet_get_last_col (sheet);
616 if (from < 0)
617 from = 0;
618 if (to < 0 || to > max)
619 to = max;
620 if (from > max)
621 from = to;
622 /* Fall back to assigning the default if it is empty */
623 tmp = (is_cols)
624 ? sheet_col_size_fit_pixels (sheet, i, from, to, FALSE)
625 : sheet_row_size_fit_pixels (sheet, i, from, to, FALSE);
627 if (tmp > 0) {
628 if (is_cols)
629 sheet_col_set_size_pixels (sheet, i, tmp, new_size > 0);
630 else
631 sheet_row_set_size_pixels (sheet, i, tmp, new_size > 0);
632 } else if (sheet_colrow_get (sheet, i, is_cols) != NULL) {
633 if (is_cols)
634 sheet_col_set_size_pixels (sheet, i,
635 sheet_col_get_default_size_pixels (sheet), FALSE);
636 else
637 sheet_row_set_size_pixels (sheet, i,
638 sheet_row_get_default_size_pixels (sheet), FALSE);
643 return res;
647 * NOTE : this is a low level routine it does not redraw or
648 * reposition objects
650 * NOTE : this does not delete states any longer since it may be used
651 * for several sheets.
654 void
655 colrow_set_states (Sheet *sheet, gboolean is_cols,
656 int first, ColRowStateList *states)
658 GSList *l;
659 int i, max_outline, offset = first;
660 ColRowCollection *infos;
661 double scale;
663 g_return_if_fail (IS_SHEET (sheet));
665 infos = is_cols ? &(sheet->cols) : &(sheet->rows);
666 max_outline = infos->max_outline_level;
667 scale = colrow_compute_pixel_scale (sheet, is_cols);
669 for (l = states; l != NULL; l = l->next) {
670 ColRowRLEState const *rles = l->data;
671 ColRowState const *state = &rles->state;
673 if (max_outline < state->outline_level)
674 max_outline = state->outline_level;
676 for (i = offset; i < offset + rles->length; i++) {
677 if (state->is_default) {
678 ColRowSegment *segment = COLROW_GET_SEGMENT(infos, i);
679 if (segment != NULL) {
680 int const sub = COLROW_SUB_INDEX (i);
681 ColRowInfo *cri = segment->info[sub];
682 if (cri != NULL) {
683 segment->info[sub] = NULL;
684 colrow_free (cri);
687 } else {
688 ColRowInfo *cri = sheet_colrow_fetch (sheet, i, is_cols);
689 cri->hard_size = state->hard_size;
690 cri->size_pts = state->size_pts;
691 colrow_compute_pixels_from_pts (cri, sheet, is_cols, scale);
692 colrow_set_outline (cri, state->outline_level,
693 state->is_collapsed);
696 offset += rles->length;
699 /* Notify sheet of pending update */
700 sheet->priv->recompute_visibility = TRUE;
701 if (is_cols) {
702 sheet_flag_recompute_spans (sheet);
704 /* In order to properly reposition cell
705 * comments in merged cells that cross the
706 * boundary we need to do everything. Revert
707 * this when comments are handled properly */
708 #if 0
709 if (sheet->priv->reposition_objects.col > first)
710 sheet->priv->reposition_objects.col = first;
711 #else
712 sheet->priv->reposition_objects.col = 0;
713 #endif
714 } else {
715 if (sheet->priv->reposition_objects.row > first)
716 sheet->priv->reposition_objects.row = first;
718 sheet_colrow_gutter (sheet, is_cols, max_outline);
721 void
722 colrow_restore_state_group (Sheet *sheet, gboolean is_cols,
723 ColRowIndexList *selection,
724 ColRowStateGroup *state_groups)
726 ColRowStateGroup *ptr = state_groups;
728 /* Cycle to end, we have to traverse the selections
729 * in parallel with the state_groups
731 selection = g_list_last (selection);
732 for (; selection != NULL && ptr != NULL ; ptr = ptr->next) {
733 ColRowIndex const *index = selection->data;
734 ColRowStateList *list = ptr->data;
735 ColRowRLEState const *rles = list->data;
737 /* MAGIC : the -1 was set above to flag this */
738 if (rles->length == -1) {
739 if (is_cols)
740 sheet_col_set_default_size_pts (sheet, rles->state.size_pts);
741 else
742 sheet_row_set_default_size_pts (sheet, rles->state.size_pts);
744 /* we are guaranteed to have at least 1 more record */
745 ptr = ptr->next;
748 colrow_set_states (sheet, is_cols, index->first, ptr->data);
749 /* force a re-render of cells with expanding formats */
750 if (is_cols)
751 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_BLANK,
752 index->first, 0, index->last, gnm_sheet_get_last_row (sheet),
753 (CellIterFunc) &cb_clear_variable_width_content, NULL);
754 selection = selection->prev;
759 * rows_height_update:
760 * @sheet: The sheet,
761 * @range: The range whose rows should be resized.
762 * @shrink: If set to FALSE, rows will never shrink!
764 * Use this function having changed the font size to auto
765 * resize the row heights to make the text fit nicely.
767 void
768 rows_height_update (Sheet *sheet, GnmRange const * range, gboolean shrink)
770 /* FIXME : this needs to check font sizes and contents rather than
771 * just contents. Empty cells will cause resize also */
772 colrow_autofit (sheet, range, FALSE, FALSE,
773 FALSE, !shrink,
774 NULL, NULL);
777 /* ------------------------------------------------------------------------- */
779 struct cb_autofit {
780 Sheet *sheet;
781 const GnmRange *range;
782 gboolean ignore_strings;
783 gboolean min_current;
784 gboolean min_default;
787 static gboolean
788 cb_autofit_col (GnmColRowIter const *iter, gpointer data_)
790 struct cb_autofit *data = data_;
791 int size, min, max;
793 if (iter->cri->hard_size)
794 return FALSE;
796 size = sheet_col_size_fit_pixels (data->sheet, iter->pos,
797 data->range->start.row, data->range->end.row,
798 data->ignore_strings);
799 /* FIXME: better idea than this? */
800 max = 50 * sheet_col_get_default_size_pixels (data->sheet);
801 size = MIN (size, max);
803 min = 0;
804 if (data->min_current)
805 min = MAX (min, iter->cri->size_pixels);
806 if (data->min_default)
807 min = MAX (min, sheet_col_get_default_size_pixels (data->sheet));
809 if (size > min)
810 sheet_col_set_size_pixels (data->sheet, iter->pos, size, FALSE);
812 return FALSE;
815 static gboolean
816 cb_autofit_row (GnmColRowIter const *iter, gpointer data_)
818 struct cb_autofit *data = data_;
819 int size, min, max;
821 if (iter->cri->hard_size)
822 return FALSE;
824 size = sheet_row_size_fit_pixels (data->sheet, iter->pos,
825 data->range->start.col, data->range->end.col,
826 data->ignore_strings);
827 max = 20 * sheet_row_get_default_size_pixels (data->sheet);
828 size = MIN (size, max);
830 min = 0;
831 if (data->min_current)
832 min = MAX (min, iter->cri->size_pixels);
833 if (data->min_default)
834 min = MAX (min, sheet_row_get_default_size_pixels (data->sheet));
836 if (size > min)
837 sheet_row_set_size_pixels (data->sheet, iter->pos, size, FALSE);
839 return FALSE;
843 * colrow_autofit:
844 * @sheet: the sheet to change
845 * @range: the range to consider
846 * @is_cols: TRUE for columns, FALSE for rows.
847 * @ignore_strings: Don't consider cells with string values.
848 * @min_current: Don't shrink below current size.
849 * @min_default: Don't shrink below default size.
850 * @indices: indices appropriate for colrow_restore_state_group.
851 * @sizes: old sizes appropriate for colrow_restore_state_group.
853 * This function autofits columns or rows in @range as specified by
854 * @is_cols. Only cells in @range are considered for the sizing
855 * and the size can be bounded below by current size and/or default
856 * size.
858 void
859 colrow_autofit (Sheet *sheet, const GnmRange *range, gboolean is_cols,
860 gboolean ignore_strings,
861 gboolean min_current, gboolean min_default,
862 ColRowIndexList **indices,
863 ColRowStateGroup **sizes)
865 struct cb_autofit data;
866 int a, b;
867 ColRowCollection *crs;
868 ColRowHandler handler;
870 data.sheet = sheet;
871 data.range = range;
872 data.ignore_strings = ignore_strings;
873 data.min_current = min_current;
874 data.min_default = min_default;
876 if (is_cols) {
877 a = range->start.col;
878 b = range->end.col;
879 crs = &sheet->cols;
880 handler = cb_autofit_col;
881 } else {
882 a = range->start.row;
883 b = range->end.row;
884 crs = &sheet->rows;
885 handler = cb_autofit_row;
888 if (indices)
889 *indices = colrow_get_index_list (a, b, NULL);
890 if (sizes)
891 *sizes = g_slist_prepend (NULL, colrow_get_states (sheet, is_cols, a, b));
893 /* We potentially do a lot of recalcs as part of this, so make sure
894 stuff that caches sub-computations see the whole thing instead
895 of clearing between cells. */
896 gnm_app_recalc_start ();
897 colrow_foreach (crs, a, b, handler, &data);
898 gnm_app_recalc_finish ();
901 void
902 colrow_autofit_col (Sheet *sheet, GnmRange *r)
904 colrow_autofit (sheet, r, TRUE, TRUE,
905 TRUE, FALSE, NULL, NULL);
906 sheet_foreach_cell_in_range (sheet, CELL_ITER_IGNORE_BLANK,
907 r->start.col, 0,
908 r->end.col, gnm_sheet_get_last_row (sheet),
909 (CellIterFunc) &cb_clear_variable_width_content,
910 NULL);
913 void
914 colrow_autofit_row (Sheet *sheet, GnmRange *r)
916 colrow_autofit (sheet, r, FALSE, FALSE,
917 TRUE, FALSE, NULL, NULL);
920 /*****************************************************************************/
922 typedef struct
924 gboolean is_cols, visible;
925 ColRowVisList *elements;
926 } ColRowVisiblity;
928 static gint
929 colrow_index_cmp (ColRowIndex const *a, ColRowIndex const *b)
931 /* We can be very simplistic here because the ranges never overlap */
932 return b->first - a->first;
935 static void
936 colrow_visibility (Sheet const *sheet, ColRowVisiblity * const dat,
937 int first, int last)
939 int i;
940 gboolean const visible = dat->visible;
941 ColRowInfo * (*get) (Sheet const *sheet, int pos) = (dat->is_cols)
942 ? &sheet_col_get : &sheet_row_get;
944 /* Find the end of a segment that will be toggled */
945 for (i = last; i >= first; --i) {
946 int j;
947 ColRowIndex *res;
948 ColRowInfo const *cri = (*get) (sheet, i);
950 if (cri == NULL) {
951 if (visible != 0)
952 continue;
953 } else if ((visible != 0) == (cri->visible != 0))
954 continue;
956 /* Find the begining */
957 for (j = i; j >= first ; --j) {
958 cri = (*get) (sheet, j);
959 if (cri == NULL) {
960 if (visible != 0)
961 break;
962 } else if ((visible != 0) == (cri->visible != 0))
963 break;
964 else if (cri->is_collapsed) {
965 --j;
966 break;
969 res = g_new (ColRowIndex, 1);
970 res->first = (j >= first) ? j+1 : first;
971 res->last = i;
972 #if 0
973 g_printerr ("%d %d\n", res->index, res->count);
974 #endif
975 dat->elements = g_slist_insert_sorted (dat->elements, res,
976 (GCompareFunc)colrow_index_cmp);
978 if (visible && cri != NULL && cri->is_collapsed) {
979 i = colrow_find_outline_bound (
980 sheet, dat->is_cols, j,
981 cri->outline_level+1, FALSE);
982 } else
983 i = j;
988 * colrow_get_outline_toggle:
989 * @sheet: #Sheet
990 * @is_cols:
991 * @visible:
992 * @first:
993 * @last:
995 * Returns: (transfer full):
997 ColRowVisList *
998 colrow_get_outline_toggle (Sheet const *sheet, gboolean is_cols, gboolean visible,
999 int first, int last)
1001 ColRowVisiblity closure;
1002 closure.is_cols = is_cols;
1003 closure.visible = visible;
1004 closure.elements = NULL;
1006 colrow_visibility (sheet, &closure, first, last);
1007 return closure.elements;
1010 static void
1011 cb_colrow_visibility (SheetView *sv, GnmRange const *r, gpointer closure)
1013 ColRowVisiblity * const dat = (ColRowVisiblity *)closure;
1014 int first, last;
1016 if (dat->is_cols) {
1017 first = r->start.col;
1018 last = r->end.col;
1019 } else {
1020 first = r->start.row;
1021 last = r->end.row;
1023 colrow_visibility (sv_sheet (sv), dat, first, last);
1027 * colrow_get_visiblity_toggle:
1028 * @sv: The sheet view whose selection we are interested in.
1029 * @is_cols: A flag indicating whether this it is a column or a row.
1030 * @visible: Should we unhide or hide the cols/rows.
1032 * Searches the selection list and generates a list of index,count
1033 * pairs of row/col ranges that need to be hidden or unhiden.
1035 * NOTE : leave sheet non-const until we have a const version of
1036 * sv_selection_apply.
1038 * Returns: (transfer full): the list.
1040 ColRowVisList *
1041 colrow_get_visiblity_toggle (SheetView *sv, gboolean is_cols,
1042 gboolean visible)
1044 ColRowVisiblity closure;
1045 closure.is_cols = is_cols;
1046 closure.visible = visible;
1047 closure.elements = NULL;
1049 sv_selection_apply (sv, &cb_colrow_visibility, FALSE, &closure);
1051 return closure.elements;
1055 * colrow_set_visibility_list :
1057 * This is the high level command that is wrapped by undo and redo.
1058 * It should not be called by other commands.
1060 void
1061 colrow_set_visibility_list (Sheet *sheet, gboolean is_cols,
1062 gboolean visible, ColRowVisList *list)
1064 ColRowVisList *ptr;
1065 ColRowIndex *info;
1067 for (ptr = list; ptr != NULL ; ptr = ptr->next) {
1068 info = ptr->data;
1069 colrow_set_visibility (sheet, is_cols, visible,
1070 info->first, info->last);
1073 if (visible)
1074 sheet_colrow_optimize (sheet);
1076 if (is_cols)
1077 sheet_queue_respan (sheet, 0, gnm_sheet_get_last_row (sheet));
1078 if (list != NULL)
1079 sheet_redraw_all (sheet, TRUE);
1083 * colrow_set_outline :
1084 * @cri: the col/row to tweak
1085 * @outline_level:
1086 * @is_collapsed:
1088 * Adjust the outline state of a col/row
1090 void
1091 colrow_set_outline (ColRowInfo *cri, int outline_level, gboolean is_collapsed)
1093 g_return_if_fail (outline_level >= 0);
1095 cri->is_collapsed = (is_collapsed != 0); /* be anal */
1096 cri->outline_level = outline_level;
1100 * colrow_find_outline_bound :
1102 * find the next/prev col/row at the designated depth starting from the
1103 * supplied @index.
1106 colrow_find_outline_bound (Sheet const *sheet, gboolean is_cols,
1107 int index, int depth, gboolean inc)
1109 ColRowInfo * (*get) (Sheet const *sheet, int pos) = is_cols
1110 ? &sheet_col_get : &sheet_row_get;
1111 int const max = colrow_max (is_cols, sheet);
1112 int const step = inc ? 1 : -1;
1114 while (1) {
1115 ColRowInfo const *cri;
1116 int const next = index + step;
1118 if (next < 0 || next >= max)
1119 return index;
1120 cri = (*get) (sheet, next);
1121 if (cri == NULL || cri->outline_level < depth)
1122 return index;
1123 index = next;
1126 return index;
1130 * colrow_set_visibility:
1131 * @sheet: the sheet
1132 * @is_cols: Are we dealing with rows or columns.
1133 * @visible: Make things visible or invisible.
1134 * @first: The index of the first row/col (inclusive)
1135 * @last: The index of the last row/col (inclusive)
1137 * Change the visibility of the selected range of contiguous cols/rows.
1138 * NOTE : only changes the collapsed state for the LAST+1 element.
1140 void
1141 colrow_set_visibility (Sheet *sheet, gboolean is_cols,
1142 gboolean visible, int first, int last)
1144 int i, step, prev_outline = 0;
1145 gboolean changed = FALSE;
1146 GnmRange * const bound = &sheet->priv->unhidden_region;
1147 gboolean const fwd = is_cols ? sheet->outline_symbols_right : sheet->outline_symbols_below;
1149 g_return_if_fail (IS_SHEET (sheet));
1150 g_return_if_fail (first <= last);
1152 if (visible) { /* expand to include newly visible regions */
1153 if (is_cols) {
1154 if (bound->start.col > first)
1155 bound->start.col = first;
1156 if (bound->end.col < last)
1157 bound->end.col = last;
1158 } else {
1159 if (bound->start.row > first)
1160 bound->start.row = first;
1161 if (bound->end.row < last)
1162 bound->end.row = last;
1164 } else { /* contract to exclude newly hidden regions */
1165 if (is_cols) {
1166 if (bound->start.col >= first && bound->start.col <= last)
1167 bound->start.col = last+1;
1168 if (bound->end.col <= last && bound->end.col >= first)
1169 bound->end.col = first-1;
1170 } else {
1171 if (bound->start.row >= first && bound->start.row <= last)
1172 bound->start.row = last+1;
1173 if (bound->end.row <= last && bound->end.row >= first)
1174 bound->end.row = first-1;
1178 if (fwd) {
1179 i = first;
1180 step = 1;
1181 } else {
1182 i = last;
1183 step = -1;
1186 for (; fwd ? (i <= last) : (i >= first) ; i += step) {
1187 ColRowInfo * const cri = sheet_colrow_fetch (sheet, i, is_cols);
1189 if (changed && prev_outline > cri->outline_level && !visible)
1190 cri->is_collapsed = FALSE;
1192 changed = (visible == 0) != (cri->visible == 0);
1193 if (changed) {
1194 cri->visible = visible;
1195 prev_outline = cri->outline_level;
1196 sheet->priv->recompute_visibility = TRUE;
1198 if (is_cols) {
1199 sheet_flag_recompute_spans (sheet);
1201 /* In order to properly reposition cell
1202 * comments in merged cells that cross the
1203 * boundary we need to do everything. Revert
1204 * this when comments are handled properly */
1205 #if 0
1206 if (sheet->priv->reposition_objects.col > i)
1207 sheet->priv->reposition_objects.col = i;
1208 #else
1209 sheet->priv->reposition_objects.col = 0;
1210 #endif
1211 } else {
1212 if (sheet->priv->reposition_objects.row > i)
1213 sheet->priv->reposition_objects.row = i;
1218 if (changed && 0 <= i && i < colrow_max (is_cols, sheet)) {
1219 ColRowInfo *cri = sheet_colrow_get (sheet, i, is_cols);
1220 if (!cri && !visible && prev_outline > 0)
1221 cri = sheet_colrow_fetch (sheet, i, is_cols);
1223 if (cri && prev_outline > cri->outline_level)
1224 cri->is_collapsed = !visible;
1229 * colrow_get_global_outline :
1230 * @sheet:
1231 * @is_cols:
1232 * @depth:
1233 * @show:
1234 * @hide:
1236 * Collect the set of visiblity changes required to change the visiblity of
1237 * all outlined columns such tach those > @depth are visible.
1239 void
1240 colrow_get_global_outline (Sheet const *sheet, gboolean is_cols, int depth,
1241 ColRowVisList **show, ColRowVisList **hide)
1243 ColRowInfo const *cri;
1244 ColRowIndex *prev = NULL;
1245 gboolean show_prev = FALSE;
1246 unsigned tmp, prev_outline = 0;
1247 int i, max = is_cols ? sheet->cols.max_used : sheet->rows.max_used;
1249 *show = *hide = NULL;
1250 for (i = 0; i <= max ; i++) {
1251 cri = sheet_colrow_get (sheet, i, is_cols);
1253 if (cri == NULL || cri->outline_level == 0) {
1254 prev_outline = 0;
1255 continue;
1257 tmp = prev_outline;
1258 prev_outline = cri->outline_level;
1260 /* see what sort of changes are necessary and do simple run
1261 * length encoding. Do not be too efficent, we need to change
1262 * the visiblity per outline level or the collapse state
1263 * change in colrow_set_visibility is missed. */
1264 if (cri->outline_level < depth) {
1265 if (cri->visible)
1266 continue;
1267 if (show_prev && prev != NULL && prev->last == (i-1) &&
1268 tmp == prev_outline) {
1269 prev->last = i;
1270 continue;
1272 prev = g_new (ColRowIndex, 1);
1273 prev->first = prev->last = i;
1274 *show = g_slist_prepend (*show, prev);
1275 show_prev = TRUE;
1276 } else {
1277 if (!cri->visible)
1278 continue;
1279 if (!show_prev && prev != NULL && prev->last == (i-1) &&
1280 tmp == prev_outline) {
1281 prev->last = i;
1282 continue;
1284 prev = g_new (ColRowIndex, 1);
1285 prev->first = prev->last = i;
1286 *hide = g_slist_prepend (*hide, prev);
1287 show_prev = FALSE;
1291 *show = g_slist_reverse (*show);
1292 *hide = g_slist_reverse (*hide);
1295 void
1296 colrow_resize (ColRowCollection *infos, int size)
1298 int end_idx = COLROW_SEGMENT_INDEX (size);
1299 int i = infos->info->len - 1;
1301 while (i >= end_idx) {
1302 ColRowSegment *segment = g_ptr_array_index (infos->info, i);
1303 if (segment) {
1304 g_free (segment);
1305 g_ptr_array_index (infos->info, i) = NULL;
1307 i--;
1310 g_ptr_array_set_size (infos->info, end_idx);