1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
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
22 #include <gnumeric-config.h>
27 #include "sheet-view.h"
28 #include "sheet-private.h"
29 #include "application.h"
30 #include "parse-util.h"
31 #include "selection.h"
33 #include "sheet-merge.h"
36 #include "rendered-value.h"
37 #include <goffice/goffice.h>
40 /* Making ColRowInfo a boxed type to make introspection happy. using no-op
41 * functions for copy and free, and crossing fingers.
44 col_row_info_fake_copy (ColRowInfo
*cri
)
50 col_row_info_get_type (void)
55 t
= g_boxed_type_register_static ("ColRowInfo",
56 (GBoxedCopyFunc
)col_row_info_fake_copy
,
57 (GBoxedFreeFunc
)col_row_info_fake_copy
);
63 colrow_compute_pixel_scale (Sheet
const *sheet
, gboolean horizontal
)
65 return (sheet
? sheet
->last_zoom_factor_used
: 1.0) *
66 gnm_app_display_dpi_get (horizontal
) / 72.;
70 colrow_compute_pixels_from_pts (ColRowInfo
*cri
, Sheet
const *sheet
,
71 gboolean horizontal
, double scale
)
73 int const margin
= horizontal
? 2*GNM_COL_MARGIN
: 2*GNM_ROW_MARGIN
;
76 scale
= colrow_compute_pixel_scale (sheet
, horizontal
);
78 if (horizontal
&& sheet
&& sheet
->display_formulas
)
81 cri
->size_pixels
= (int)(cri
->size_pts
* scale
+ 0.5);
83 if (cri
->size_pixels
<= margin
)
84 cri
->size_pixels
= margin
+ 1;
88 colrow_compute_pts_from_pixels (ColRowInfo
*cri
, Sheet
const *sheet
,
89 gboolean horizontal
, double scale
)
92 scale
= colrow_compute_pixel_scale (sheet
, horizontal
);
94 if (horizontal
&& sheet
->display_formulas
)
97 cri
->size_pts
= cri
->size_pixels
/ scale
;
99 /* Disable this until we decide how to deal with scaling */
100 g_return_if_fail (cri
->size_pts
>= cri
->margin_a
+ cri
->margin_b
);
105 * col_row_info_is_default:
108 * %TRUE if @cri is the default style for columns or rows.
111 col_row_info_is_default (ColRowInfo
const *cri
)
113 g_return_val_if_fail (cri
!= NULL
, FALSE
);
114 return cri
->is_default
;
118 * col_row_info_is_empty:
121 * %TRUE if there is no information in col/row @cri.
124 col_row_info_is_empty (ColRowInfo
const *cri
)
128 return cri
->is_default
&&
129 cri
->outline_level
== 0 &&
130 !cri
->is_collapsed
&&
135 * col_row_info_equal:
136 * @a: First #ColRowInfo
137 * @b: Second #ColRowInfo
139 * Returns %TRUE if the infos are equivalent.
142 col_row_info_equal (ColRowInfo
const *a
, ColRowInfo
const *b
)
149 return fabs (a
->size_pts
- b
->size_pts
) < 1e-5 &&
150 a
->outline_level
== b
->outline_level
&&
151 a
->is_collapsed
== b
->is_collapsed
&&
152 a
->hard_size
== b
->hard_size
&&
153 a
->visible
== b
->visible
;
158 * @dst: Destination #ColRowInfo
159 * @src: Source #ColRowInfo
161 * Copy all content, except the position of @src to @dst.
164 col_row_info_copy (ColRowInfo
*dst
, ColRowInfo
const *src
)
166 dst
->size_pts
= src
->size_pts
;
167 dst
->size_pixels
= src
->size_pixels
;
168 dst
->outline_level
= src
->outline_level
;
169 dst
->is_collapsed
= src
->is_collapsed
;
170 dst
->hard_size
= src
->hard_size
;
171 dst
->visible
= src
->visible
;
175 col_row_info_new (void)
177 return g_slice_new (ColRowInfo
);
181 colrow_free (ColRowInfo
*cri
)
183 g_slice_free1 (sizeof (*cri
), cri
);
188 * colrow_state_list_foreach:
189 * @list: The #ColRowStateList to iterate.
190 * @sheet: (nullable): Origin #Sheet.
191 * @is_cols: %TRUE for columns, %FALSE for rows.
192 * @base: index of first column or row.
193 * @callback: (scope call): A callback function which should
194 * return %TRUE to stop the iteration.
195 * @user_data: A baggage pointer.
197 * Iterates through the existing rows or columns within the range supplied.
198 * Currently only support left -> right iteration. If a callback returns
199 * %TRUE iteration stops.
202 colrow_state_list_foreach (ColRowStateList
*list
,
203 Sheet
const *sheet
, gboolean is_cols
,
205 ColRowHandler callback
,
212 double scale
= colrow_compute_pixel_scale (sheet
, is_cols
);
214 // This sets various fields we do not have
215 memset (&cri
, 0, sizeof (cri
));
218 for (l
= list
; l
; l
= l
->next
) {
219 ColRowRLEState
*rle
= l
->data
;
220 ColRowState
const *state
= &rle
->state
;
223 cri
.size_pts
= state
->size_pts
;
224 cri
.outline_level
= state
->outline_level
;
225 cri
.is_collapsed
= state
->is_collapsed
;
226 cri
.hard_size
= state
->hard_size
;
227 cri
.visible
= state
->visible
;
228 colrow_compute_pixels_from_pts (&cri
, sheet
, is_cols
, scale
);
230 for (l
= 0; l
< rle
->length
; l
++) {
232 if (iter
.cri
&& (*callback
)(&iter
, user_data
))
240 /*****************************************************************************/
242 typedef struct _ColRowIndex
{
248 cb_colrow_index_counter (gpointer data
, gpointer user_data
)
250 ColRowIndex
*index
= data
;
251 gint
*count
= user_data
;
253 *count
+= index
->last
- index
->first
+ 1;
257 colrow_vis_list_length (ColRowVisList
*list
)
260 g_slist_foreach (list
, cb_colrow_index_counter
, &count
);
265 * colrow_state_group_destroy:
266 * @set: (transfer full): the group to destroy.
268 * Returns: (transfer none) (nullable): %NULL.
271 colrow_state_group_destroy (ColRowStateGroup
*group
)
273 ColRowStateGroup
*ptr
;
274 for (ptr
= group
; ptr
!= NULL
; ptr
= ptr
->next
)
275 colrow_state_list_destroy (ptr
->data
);
276 g_slist_free (group
);
281 colrow_index_compare (ColRowIndex
const * a
, ColRowIndex
const * b
)
283 return a
->first
- b
->first
;
287 * colrow_index_list_to_string: Convert an index list into a string.
288 * The result must be freed by the caller.
289 * It will be something like : A-B, F-G
292 * @is_cols: %TRUE for columns, %FALSE for rows.
293 * @is_single: If non-null this will be set to %TRUE if there's only a single col/row involved.
296 colrow_index_list_to_string (ColRowIndexList
*list
, gboolean is_cols
, gboolean
*is_single
)
298 ColRowIndexList
*ptr
;
300 gboolean single
= TRUE
;
302 g_return_val_if_fail (list
!= NULL
, NULL
);
304 result
= g_string_new (NULL
);
305 for (ptr
= list
; ptr
!= NULL
; ptr
= ptr
->next
) {
306 ColRowIndex
*index
= ptr
->data
;
309 g_string_append (result
, cols_name (index
->first
, index
->last
));
311 g_string_append (result
, rows_name (index
->first
, index
->last
));
313 if (index
->last
!= index
->first
)
317 g_string_append (result
, ", ");
329 * colrow_get_index_list:
332 * @list: (transfer full):
334 * Build an ordered list of pairs doing intelligent merging
335 * of overlapping regions.
337 * Returns: (transfer full): @list.
340 colrow_get_index_list (int first
, int last
, ColRowIndexList
*list
)
342 ColRowIndex
*tmp
, *prev
;
345 tmp
= g_new (ColRowIndex
, 1);
349 list
= g_list_insert_sorted (list
, tmp
,
350 (GCompareFunc
)&colrow_index_compare
);
353 for (ptr
= list
->next
; ptr
!= NULL
; ) {
356 /* at the end of existing segment or contained */
357 if (prev
->last
+1 >= tmp
->first
) {
358 GList
*next
= ptr
->next
;
359 if (prev
->last
< tmp
->last
)
360 prev
->last
= tmp
->last
;
361 list
= g_list_remove_link (list
, ptr
);
372 * colrow_index_list_copy:
373 * @list: #ColRowIndexList
375 * Returns: (transfer full):
378 colrow_index_list_copy (ColRowIndexList
*list
)
380 GList
*copy
= NULL
, *ptr
;
382 for (ptr
= list
; ptr
!= NULL
; ptr
= ptr
->next
) {
383 ColRowIndex
*tmp
= g_new (ColRowIndex
, 1);
384 ColRowIndex
*ex
= ptr
->data
;
385 tmp
->first
= ex
->first
;
386 tmp
->last
= ex
->last
;
387 copy
= g_list_prepend (copy
, tmp
);
389 return g_list_reverse (copy
);
393 colrow_set_single_state (ColRowState
*state
,
394 Sheet
*sheet
, int i
, gboolean is_cols
)
396 ColRowInfo
const *info
= sheet_colrow_get_info (sheet
, i
, is_cols
);
397 state
->is_default
= col_row_info_is_default (info
);
398 state
->size_pts
= info
->size_pts
;
399 state
->outline_level
= info
->outline_level
;
400 state
->is_collapsed
= info
->is_collapsed
;
401 state
->hard_size
= info
->hard_size
;
402 state
->visible
= info
->visible
;
406 * colrow_state_list_destroy:
407 * @list: (transfer full): the list to destroy.
409 * Returns: (transfer none): %NULL.
412 colrow_state_list_destroy (ColRowStateList
*list
)
414 g_slist_free_full (list
, g_free
);
419 * colrow_get_states: (skip)
421 * @is_cols: %TRUE for columns, %FALSE for rows.
422 * @first: first column or row.
423 * @last: last column or row.
425 * Returns: (transfer full):
428 colrow_get_states (Sheet
*sheet
, gboolean is_cols
, int first
, int last
)
430 ColRowStateList
*list
= NULL
;
431 ColRowRLEState
*rles
;
432 ColRowState run_state
;
435 g_return_val_if_fail (IS_SHEET (sheet
), NULL
);
436 g_return_val_if_fail (first
<= last
, NULL
);
438 colrow_set_single_state (&run_state
, sheet
, first
, is_cols
);
441 for (i
= first
+ 1; i
<= last
; ++i
) {
442 ColRowState cur_state
;
443 colrow_set_single_state (&cur_state
, sheet
, i
, is_cols
);
445 /* If state changed, start a new block */
446 if (cur_state
.is_default
!= run_state
.is_default
||
447 cur_state
.size_pts
!= run_state
.size_pts
||
448 cur_state
.outline_level
!= run_state
.outline_level
||
449 cur_state
.is_collapsed
!= run_state
.is_collapsed
||
450 cur_state
.hard_size
!= run_state
.hard_size
||
451 cur_state
.visible
!= run_state
.visible
) {
452 rles
= g_new (ColRowRLEState
, 1);
453 rles
->length
= run_length
;
454 rles
->state
= run_state
;
455 list
= g_slist_prepend (list
, rles
);
457 run_state
= cur_state
;
463 /* Store the final run */
464 rles
= g_new (ColRowRLEState
, 1);
465 rles
->length
= run_length
;
466 rles
->state
= run_state
;
467 list
= g_slist_prepend (list
, rles
);
469 return g_slist_reverse (list
);
472 struct resize_closure
{
479 cb_set_colrow_size (GnmColRowIter
const *iter
, gpointer userdata
)
481 if (iter
->cri
->visible
) {
482 struct resize_closure
const *c
= userdata
;
485 sheet_col_set_size_pixels (c
->sheet
, iter
->pos
,
488 sheet_row_set_size_pixels (c
->sheet
, iter
->pos
,
495 cb_clear_variable_width_content (GnmCellIter
const *iter
,
496 G_GNUC_UNUSED gpointer user
)
498 GnmRenderedValue
*rv
= gnm_cell_get_rendered_value (iter
->cell
);
499 if (rv
&& rv
->variable_width
) {
500 iter
->ri
->needs_respan
= TRUE
;
501 gnm_cell_unrender (iter
->cell
);
507 * colrow_get_sizes: (skip)
509 * @is_cols: %TRUE for columns, %FALSE for rows.
513 * Returns: (transfer full):
516 colrow_get_sizes (Sheet
*sheet
, gboolean is_cols
,
517 ColRowIndexList
*src
, int new_size
)
519 ColRowStateGroup
*res
= NULL
;
520 ColRowIndexList
*ptr
;
522 for (ptr
= src
; ptr
!= NULL
; ptr
= ptr
->next
) {
523 ColRowIndex
const *index
= ptr
->data
;
524 res
= g_slist_prepend (res
, colrow_get_states (sheet
, is_cols
,
525 index
->first
, index
->last
));
527 if (new_size
> 0 && index
->first
== 0 &&
528 (index
->last
+1) >= colrow_max (is_cols
, sheet
)) {
529 ColRowRLEState
*rles
= g_new0 (ColRowRLEState
, 1);
531 rles
->length
= -1; /* Flag as changing the default */
534 rles
->state
.size_pts
= sheet_col_get_default_size_pts (sheet
);
536 rles
->state
.size_pts
= sheet_row_get_default_size_pts (sheet
);
538 /* Result is a magic 'default' record + >= 1 normal */
539 return g_slist_prepend (res
, g_slist_append (NULL
, rles
));
547 * colrow_set_sizes: (skip)
549 * @is_cols: %TRUE for columns, %FALSE for rows.
555 * Returns: (transfer full):
558 colrow_set_sizes (Sheet
*sheet
, gboolean is_cols
,
559 ColRowIndexList
*src
, int new_size
, int from
, int to
)
560 /* from & to are used to restrict fitting to that range. Pass 0, -1 if you want to use the */
561 /* whole row/column */
564 ColRowStateGroup
*res
= NULL
;
565 ColRowIndexList
*ptr
;
567 for (ptr
= src
; ptr
!= NULL
; ptr
= ptr
->next
) {
568 ColRowIndex
const *index
= ptr
->data
;
569 res
= g_slist_prepend (res
, colrow_get_states (sheet
, is_cols
,
570 index
->first
, index
->last
));
573 * If we are changing the size of more than half of the rows/col to
574 * something specific (not autosize) we should change the default
575 * row/col size instead. However, it is unclear how to handle
578 * we need better management of rows/cols. Currently if they are all
579 * defined calculation speed grinds to a halt.
581 if (new_size
> 0 && index
->first
== 0 &&
582 (index
->last
+1) >= colrow_max (is_cols
, sheet
)) {
583 struct resize_closure closure
;
584 ColRowRLEState
*rles
= g_new0 (ColRowRLEState
, 1);
586 rles
->length
= -1; /* Flag as changing the default */
588 closure
.sheet
= sheet
;
589 closure
.new_size
= new_size
;
590 closure
.is_cols
= is_cols
;
592 rles
->state
.size_pts
= sheet_col_get_default_size_pts (sheet
);
593 sheet_col_set_default_size_pixels (sheet
, new_size
);
594 sheet_colrow_foreach (sheet
, TRUE
, 0, -1,
595 &cb_set_colrow_size
, &closure
);
597 rles
->state
.size_pts
= sheet_row_get_default_size_pts (sheet
);
598 sheet_row_set_default_size_pixels (sheet
, new_size
);
599 sheet_colrow_foreach (sheet
, FALSE
, 0, -1,
600 &cb_set_colrow_size
, &closure
);
603 /* force a re-render of cells with expanding formats */
605 sheet_foreach_cell_in_region (sheet
, CELL_ITER_IGNORE_BLANK
,
607 (CellIterFunc
) &cb_clear_variable_width_content
, NULL
);
609 /* Result is a magic 'default' record + >= 1 normal */
610 return g_slist_prepend (res
, g_slist_append (NULL
, rles
));
614 /* force a re-render of cells with expanding formats */
615 sheet_foreach_cell_in_region (sheet
, CELL_ITER_IGNORE_BLANK
,
616 index
->first
, 0, index
->last
, -1,
617 (CellIterFunc
) &cb_clear_variable_width_content
, NULL
);
619 /* In order to properly reposition cell comments in
620 * merged cells that cross the boundary we need to do
621 * everything. Remove this when comments are handled
623 sheet
->priv
->reposition_objects
.col
= 0;
626 for (i
= index
->first
; i
<= index
->last
; ++i
) {
629 int max
= is_cols
? gnm_sheet_get_last_row (sheet
)
630 : gnm_sheet_get_last_col (sheet
);
633 if (to
< 0 || to
> max
)
637 /* Fall back to assigning the default if it is empty */
639 ? sheet_col_size_fit_pixels (sheet
, i
, from
, to
, FALSE
)
640 : sheet_row_size_fit_pixels (sheet
, i
, from
, to
, FALSE
);
644 sheet_col_set_size_pixels (sheet
, i
, tmp
, new_size
> 0);
646 sheet_row_set_size_pixels (sheet
, i
, tmp
, new_size
> 0);
647 } else if (sheet_colrow_get (sheet
, i
, is_cols
) != NULL
) {
649 sheet_col_set_size_pixels (sheet
, i
,
650 sheet_col_get_default_size_pixels (sheet
), FALSE
);
652 sheet_row_set_size_pixels (sheet
, i
,
653 sheet_row_get_default_size_pixels (sheet
), FALSE
);
662 * colrow_set_states: (skip)
664 * @is_cols: %TRUE for columns, %FALSE for rows.
665 * @first: first column or row
666 * @states: saved state to restore.
668 * This is a low level routine it does not redraw or reposition objects
671 colrow_set_states (Sheet
*sheet
, gboolean is_cols
,
672 int first
, ColRowStateList
*states
)
675 int i
, max_outline
, offset
= first
;
676 ColRowCollection
*infos
;
679 g_return_if_fail (IS_SHEET (sheet
));
681 infos
= is_cols
? &(sheet
->cols
) : &(sheet
->rows
);
682 max_outline
= infos
->max_outline_level
;
683 scale
= colrow_compute_pixel_scale (sheet
, is_cols
);
685 for (l
= states
; l
!= NULL
; l
= l
->next
) {
686 ColRowRLEState
const *rles
= l
->data
;
687 ColRowState
const *state
= &rles
->state
;
689 if (max_outline
< state
->outline_level
)
690 max_outline
= state
->outline_level
;
692 for (i
= offset
; i
< offset
+ rles
->length
; i
++) {
693 if (state
->is_default
) {
694 ColRowSegment
*segment
= COLROW_GET_SEGMENT(infos
, i
);
695 if (segment
!= NULL
) {
696 int const sub
= COLROW_SUB_INDEX (i
);
697 ColRowInfo
*cri
= segment
->info
[sub
];
699 segment
->info
[sub
] = NULL
;
704 ColRowInfo
*cri
= sheet_colrow_fetch (sheet
, i
, is_cols
);
705 cri
->hard_size
= state
->hard_size
;
706 cri
->size_pts
= state
->size_pts
;
707 colrow_compute_pixels_from_pts (cri
, sheet
, is_cols
, scale
);
708 col_row_info_set_outline (cri
, state
->outline_level
,
709 state
->is_collapsed
);
712 offset
+= rles
->length
;
715 /* Notify sheet of pending update */
716 sheet
->priv
->recompute_visibility
= TRUE
;
718 sheet_flag_recompute_spans (sheet
);
720 /* In order to properly reposition cell
721 * comments in merged cells that cross the
722 * boundary we need to do everything. Revert
723 * this when comments are handled properly */
725 if (sheet
->priv
->reposition_objects
.col
> first
)
726 sheet
->priv
->reposition_objects
.col
= first
;
728 sheet
->priv
->reposition_objects
.col
= 0;
731 if (sheet
->priv
->reposition_objects
.row
> first
)
732 sheet
->priv
->reposition_objects
.row
= first
;
734 sheet_colrow_gutter (sheet
, is_cols
, max_outline
);
738 * colrow_restore_state_group: (skip)
740 * @is_cols: %TRUE for columns, %FALSE for rows.
746 colrow_restore_state_group (Sheet
*sheet
, gboolean is_cols
,
747 ColRowIndexList
*selection
,
748 ColRowStateGroup
*state_groups
)
750 ColRowStateGroup
*ptr
= state_groups
;
752 /* Cycle to end, we have to traverse the selections
753 * in parallel with the state_groups
755 selection
= g_list_last (selection
);
756 for (; selection
!= NULL
&& ptr
!= NULL
; ptr
= ptr
->next
) {
757 ColRowIndex
const *index
= selection
->data
;
758 ColRowStateList
*list
= ptr
->data
;
759 ColRowRLEState
const *rles
= list
->data
;
761 /* MAGIC : the -1 was set above to flag this */
762 if (rles
->length
== -1) {
764 sheet_col_set_default_size_pts (sheet
, rles
->state
.size_pts
);
766 sheet_row_set_default_size_pts (sheet
, rles
->state
.size_pts
);
768 /* we are guaranteed to have at least 1 more record */
772 colrow_set_states (sheet
, is_cols
, index
->first
, ptr
->data
);
773 /* force a re-render of cells with expanding formats */
775 sheet_foreach_cell_in_region (sheet
, CELL_ITER_IGNORE_BLANK
,
776 index
->first
, 0, index
->last
, -1,
777 (CellIterFunc
) &cb_clear_variable_width_content
, NULL
);
778 selection
= selection
->prev
;
783 * rows_height_update:
785 * @range: The range whose rows should be resized.
786 * @shrink: If set to %FALSE, rows will never shrink!
788 * Use this function having changed the font size to auto-resize the row
789 * heights to make the text fit nicely.
792 rows_height_update (Sheet
*sheet
, GnmRange
const *range
, gboolean shrink
)
794 /* FIXME: this needs to check font sizes and contents rather than
795 * just contents. Empty cells will cause resize also */
796 colrow_autofit (sheet
, range
, FALSE
, FALSE
,
801 /* ------------------------------------------------------------------------- */
805 const GnmRange
*range
;
806 gboolean ignore_strings
;
807 gboolean min_current
;
808 gboolean min_default
;
812 cb_autofit_col (GnmColRowIter
const *iter
, gpointer data_
)
814 struct cb_autofit
*data
= data_
;
817 if (iter
->cri
->hard_size
)
820 size
= sheet_col_size_fit_pixels (data
->sheet
, iter
->pos
,
821 data
->range
->start
.row
, data
->range
->end
.row
,
822 data
->ignore_strings
);
823 /* FIXME: better idea than this? */
824 max
= 50 * sheet_col_get_default_size_pixels (data
->sheet
);
825 size
= MIN (size
, max
);
828 if (data
->min_current
)
829 min
= MAX (min
, iter
->cri
->size_pixels
);
830 if (data
->min_default
)
831 min
= MAX (min
, sheet_col_get_default_size_pixels (data
->sheet
));
834 sheet_col_set_size_pixels (data
->sheet
, iter
->pos
, size
, FALSE
);
840 cb_autofit_row (GnmColRowIter
const *iter
, gpointer data_
)
842 struct cb_autofit
*data
= data_
;
845 if (iter
->cri
->hard_size
)
848 size
= sheet_row_size_fit_pixels (data
->sheet
, iter
->pos
,
849 data
->range
->start
.col
, data
->range
->end
.col
,
850 data
->ignore_strings
);
851 max
= 20 * sheet_row_get_default_size_pixels (data
->sheet
);
852 size
= MIN (size
, max
);
855 if (data
->min_current
)
856 min
= MAX (min
, iter
->cri
->size_pixels
);
857 if (data
->min_default
)
858 min
= MAX (min
, sheet_row_get_default_size_pixels (data
->sheet
));
861 sheet_row_set_size_pixels (data
->sheet
, iter
->pos
, size
, FALSE
);
868 * @sheet: the #Sheet to change
869 * @range: the range to consider
870 * @is_cols: %TRUE for columns, %FALSE for rows.
871 * @ignore_strings: Don't consider cells with string values.
872 * @min_current: Don't shrink below current size.
873 * @min_default: Don't shrink below default size.
874 * @indices: (out) (optional): indices appropriate for
875 * colrow_restore_state_group.
876 * @sizes: (out) (optional): old sizes appropriate for
877 * colrow_restore_state_group.
879 * This function autofits columns or rows in @range as specified by
880 * @is_cols. Only cells in @range are considered for the sizing
881 * and the size can be bounded below by current size and/or default
885 colrow_autofit (Sheet
*sheet
, const GnmRange
*range
, gboolean is_cols
,
886 gboolean ignore_strings
,
887 gboolean min_current
, gboolean min_default
,
888 ColRowIndexList
**indices
,
889 ColRowStateGroup
**sizes
)
891 struct cb_autofit data
;
893 ColRowHandler handler
;
897 data
.ignore_strings
= ignore_strings
;
898 data
.min_current
= min_current
;
899 data
.min_default
= min_default
;
902 a
= range
->start
.col
;
904 handler
= cb_autofit_col
;
906 a
= range
->start
.row
;
908 handler
= cb_autofit_row
;
912 *indices
= colrow_get_index_list (a
, b
, NULL
);
914 *sizes
= g_slist_prepend (NULL
, colrow_get_states (sheet
, is_cols
, a
, b
));
916 /* We potentially do a lot of recalcs as part of this, so make sure
917 stuff that caches sub-computations see the whole thing instead
918 of clearing between cells. */
919 gnm_app_recalc_start ();
920 sheet_colrow_foreach (sheet
, is_cols
, a
, b
, handler
, &data
);
921 gnm_app_recalc_finish ();
925 * colrow_autofit_col:
926 * @sheet: the #Sheet to change
927 * @r: the range to consider
930 colrow_autofit_col (Sheet
*sheet
, GnmRange
*r
)
932 colrow_autofit (sheet
, r
, TRUE
, TRUE
,
933 TRUE
, FALSE
, NULL
, NULL
);
934 sheet_foreach_cell_in_region (sheet
, CELL_ITER_IGNORE_BLANK
,
937 (CellIterFunc
) &cb_clear_variable_width_content
,
942 * colrow_autofit_row:
943 * @sheet: the #Sheet to change
944 * @r: the range to consider
947 colrow_autofit_row (Sheet
*sheet
, GnmRange
*r
)
949 colrow_autofit (sheet
, r
, FALSE
, FALSE
,
950 TRUE
, FALSE
, NULL
, NULL
);
953 /*****************************************************************************/
957 gboolean is_cols
, visible
;
958 ColRowVisList
*elements
;
962 colrow_index_cmp (ColRowIndex
const *a
, ColRowIndex
const *b
)
964 /* We can be very simplistic here because the ranges never overlap */
965 return b
->first
- a
->first
;
969 colrow_visibility (Sheet
const *sheet
, ColRowVisibility
* const dat
,
973 gboolean
const visible
= dat
->visible
;
974 ColRowInfo
* (*get
) (Sheet
const *sheet
, int pos
) = (dat
->is_cols
)
975 ? &sheet_col_get
: &sheet_row_get
;
977 /* Find the end of a segment that will be toggled */
978 for (i
= last
; i
>= first
; --i
) {
981 ColRowInfo
const *cri
= (*get
) (sheet
, i
);
986 } else if ((visible
!= 0) == (cri
->visible
!= 0))
989 /* Find the begining */
990 for (j
= i
; j
>= first
; --j
) {
991 cri
= (*get
) (sheet
, j
);
995 } else if ((visible
!= 0) == (cri
->visible
!= 0))
997 else if (cri
->is_collapsed
) {
1002 res
= g_new (ColRowIndex
, 1);
1003 res
->first
= (j
>= first
) ? j
+1 : first
;
1006 g_printerr ("%d %d\n", res
->index
, res
->count
);
1008 dat
->elements
= g_slist_insert_sorted (dat
->elements
, res
,
1009 (GCompareFunc
)colrow_index_cmp
);
1011 if (visible
&& cri
!= NULL
&& cri
->is_collapsed
) {
1012 i
= colrow_find_outline_bound (
1013 sheet
, dat
->is_cols
, j
,
1014 cri
->outline_level
+1, FALSE
);
1021 * colrow_get_outline_toggle: (skip)
1023 * @is_cols: %TRUE for columns, %FALSE for rows.
1028 * Returns: (transfer full):
1031 colrow_get_outline_toggle (Sheet
const *sheet
, gboolean is_cols
, gboolean visible
,
1032 int first
, int last
)
1034 ColRowVisibility closure
;
1035 closure
.is_cols
= is_cols
;
1036 closure
.visible
= visible
;
1037 closure
.elements
= NULL
;
1039 colrow_visibility (sheet
, &closure
, first
, last
);
1040 return closure
.elements
;
1044 cb_colrow_visibility (SheetView
*sv
, GnmRange
const *r
, gpointer closure
)
1046 ColRowVisibility
* const dat
= (ColRowVisibility
*)closure
;
1050 first
= r
->start
.col
;
1053 first
= r
->start
.row
;
1056 colrow_visibility (sv_sheet (sv
), dat
, first
, last
);
1060 * colrow_get_visibility_toggle: (skip)
1061 * @sv: The sheet view whose selection we are interested in.
1062 * @is_cols: %TRUE for columns, %FALSE for rows.
1063 * @visible: Should we unhide or hide the cols/rows.
1065 * Searches the selection list and generates a list of index,count
1066 * pairs of row/col ranges that need to be hidden or unhiden.
1068 * Returns: (transfer full): the list.
1071 colrow_get_visibility_toggle (SheetView
*sv
, gboolean is_cols
,
1074 ColRowVisibility closure
;
1075 closure
.is_cols
= is_cols
;
1076 closure
.visible
= visible
;
1077 closure
.elements
= NULL
;
1079 sv_selection_apply (sv
, &cb_colrow_visibility
, FALSE
, &closure
);
1081 return closure
.elements
;
1085 * colrow_set_visibility_list:
1086 * @sheet: The #Sheet to change
1087 * @is_cols: %TRUE for columns, %FALSE for rows.
1088 * @visible: Should we unhide or hide the cols/rows.
1090 * This is the high level command that is wrapped by undo and redo.
1091 * It should not be called by other commands.
1094 colrow_set_visibility_list (Sheet
*sheet
, gboolean is_cols
,
1095 gboolean visible
, ColRowVisList
*list
)
1100 for (ptr
= list
; ptr
!= NULL
; ptr
= ptr
->next
) {
1102 colrow_set_visibility (sheet
, is_cols
, visible
,
1103 info
->first
, info
->last
);
1107 sheet_colrow_optimize (sheet
);
1110 sheet_queue_respan (sheet
, 0, gnm_sheet_get_last_row (sheet
));
1112 sheet_redraw_all (sheet
, TRUE
);
1116 * col_row_info_set_outline:
1117 * @cri: #ColRowInfo to tweak
1121 * Adjust the outline state of a col/row
1124 col_row_info_set_outline (ColRowInfo
*cri
, int outline_level
, gboolean is_collapsed
)
1126 g_return_if_fail (outline_level
>= 0);
1128 cri
->is_collapsed
= !!is_collapsed
;
1129 cri
->outline_level
= outline_level
;
1133 * colrow_find_outline_bound:
1135 * find the next/prev col/row at the designated depth starting from the
1139 colrow_find_outline_bound (Sheet
const *sheet
, gboolean is_cols
,
1140 int index
, int depth
, gboolean inc
)
1142 ColRowInfo
* (*get
) (Sheet
const *sheet
, int pos
) = is_cols
1143 ? &sheet_col_get
: &sheet_row_get
;
1144 int const max
= colrow_max (is_cols
, sheet
);
1145 int const step
= inc
? 1 : -1;
1148 ColRowInfo
const *cri
;
1149 int const next
= index
+ step
;
1151 if (next
< 0 || next
>= max
)
1153 cri
= (*get
) (sheet
, next
);
1154 if (cri
== NULL
|| cri
->outline_level
< depth
)
1163 * colrow_set_visibility:
1164 * @sheet: the #Sheet
1165 * @is_cols: %TRUE for columns, %FALSE for rows.
1166 * @visible: Make things visible or invisible.
1167 * @first: The index of the first row/col (inclusive)
1168 * @last: The index of the last row/col (inclusive)
1170 * Change the visibility of the selected range of contiguous cols/rows.
1171 * NOTE : only changes the collapsed state for the LAST+1 element.
1174 colrow_set_visibility (Sheet
*sheet
, gboolean is_cols
,
1175 gboolean visible
, int first
, int last
)
1177 int i
, step
, prev_outline
= 0;
1178 gboolean changed
= FALSE
;
1179 GnmRange
* const bound
= &sheet
->priv
->unhidden_region
;
1180 gboolean
const fwd
= is_cols
? sheet
->outline_symbols_right
: sheet
->outline_symbols_below
;
1182 g_return_if_fail (IS_SHEET (sheet
));
1183 g_return_if_fail (first
<= last
);
1185 if (visible
) { /* expand to include newly visible regions */
1187 if (bound
->start
.col
> first
)
1188 bound
->start
.col
= first
;
1189 if (bound
->end
.col
< last
)
1190 bound
->end
.col
= last
;
1192 if (bound
->start
.row
> first
)
1193 bound
->start
.row
= first
;
1194 if (bound
->end
.row
< last
)
1195 bound
->end
.row
= last
;
1197 } else { /* contract to exclude newly hidden regions */
1199 if (bound
->start
.col
>= first
&& bound
->start
.col
<= last
)
1200 bound
->start
.col
= last
+1;
1201 if (bound
->end
.col
<= last
&& bound
->end
.col
>= first
)
1202 bound
->end
.col
= first
-1;
1204 if (bound
->start
.row
>= first
&& bound
->start
.row
<= last
)
1205 bound
->start
.row
= last
+1;
1206 if (bound
->end
.row
<= last
&& bound
->end
.row
>= first
)
1207 bound
->end
.row
= first
-1;
1219 for (; fwd
? (i
<= last
) : (i
>= first
) ; i
+= step
) {
1220 ColRowInfo
* const cri
= sheet_colrow_fetch (sheet
, i
, is_cols
);
1222 if (changed
&& prev_outline
> cri
->outline_level
&& !visible
)
1223 cri
->is_collapsed
= FALSE
;
1225 changed
= (visible
== 0) != (cri
->visible
== 0);
1227 cri
->visible
= visible
;
1228 prev_outline
= cri
->outline_level
;
1229 sheet
->priv
->recompute_visibility
= TRUE
;
1232 sheet_flag_recompute_spans (sheet
);
1234 /* In order to properly reposition cell
1235 * comments in merged cells that cross the
1236 * boundary we need to do everything. Revert
1237 * this when comments are handled properly */
1239 if (sheet
->priv
->reposition_objects
.col
> i
)
1240 sheet
->priv
->reposition_objects
.col
= i
;
1242 sheet
->priv
->reposition_objects
.col
= 0;
1245 if (sheet
->priv
->reposition_objects
.row
> i
)
1246 sheet
->priv
->reposition_objects
.row
= i
;
1251 if (changed
&& 0 <= i
&& i
< colrow_max (is_cols
, sheet
)) {
1252 ColRowInfo
*cri
= sheet_colrow_get (sheet
, i
, is_cols
);
1253 if (!cri
&& !visible
&& prev_outline
> 0)
1254 cri
= sheet_colrow_fetch (sheet
, i
, is_cols
);
1256 if (cri
&& prev_outline
> cri
->outline_level
)
1257 cri
->is_collapsed
= !visible
;
1261 // Introspection scanner crashes if the following "hide" is removed.
1264 * colrow_get_global_outline: (skip)
1266 * @is_cols: %TRUE for columns, %FALSE for rows.
1271 * Collect the set of visibility changes required to change the visibility of
1272 * all outlined columns such that those > @depth are visible.
1275 colrow_get_global_outline (Sheet
const *sheet
, gboolean is_cols
, int depth
,
1276 ColRowVisList
**show
, ColRowVisList
**hide
)
1278 ColRowInfo
const *cri
;
1279 ColRowIndex
*prev
= NULL
;
1280 gboolean show_prev
= FALSE
;
1281 unsigned tmp
, prev_outline
= 0;
1282 int i
, max
= is_cols
? sheet
->cols
.max_used
: sheet
->rows
.max_used
;
1284 *show
= *hide
= NULL
;
1285 for (i
= 0; i
<= max
; i
++) {
1286 cri
= sheet_colrow_get (sheet
, i
, is_cols
);
1288 if (cri
== NULL
|| cri
->outline_level
== 0) {
1293 prev_outline
= cri
->outline_level
;
1295 /* see what sort of changes are necessary and do simple run
1296 * length encoding. Do not be too efficent, we need to change
1297 * the visibility per outline level or the collapse state
1298 * change in colrow_set_visibility is missed. */
1299 if (cri
->outline_level
< depth
) {
1302 if (show_prev
&& prev
!= NULL
&& prev
->last
== (i
-1) &&
1303 tmp
== prev_outline
) {
1307 prev
= g_new (ColRowIndex
, 1);
1308 prev
->first
= prev
->last
= i
;
1309 *show
= g_slist_prepend (*show
, prev
);
1314 if (!show_prev
&& prev
!= NULL
&& prev
->last
== (i
-1) &&
1315 tmp
== prev_outline
) {
1319 prev
= g_new (ColRowIndex
, 1);
1320 prev
->first
= prev
->last
= i
;
1321 *hide
= g_slist_prepend (*hide
, prev
);
1326 *show
= g_slist_reverse (*show
);
1327 *hide
= g_slist_reverse (*hide
);