1 /*
2  * colrow.c: Utilities for Rows and Columns
3  *
4  * Copyright (C) 1999-2007 Jody Goldberg (jody@gnome.org)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
19  * USA
20  */
21 #include <gnumeric-config.h>
22 #include <gnumeric.h>
23 #include <colrow.h>
24 
25 #include <sheet.h>
26 #include <sheet-view.h>
27 #include <sheet-private.h>
28 #include <application.h>
29 #include <parse-util.h>
30 #include <selection.h>
31 #include <ranges.h>
32 #include <sheet-merge.h>
33 #include <cell.h>
34 #include <cellspan.h>
35 #include <rendered-value.h>
36 #include <goffice/goffice.h>
37 #include <string.h>
38 
39 /* Making ColRowInfo a boxed type to make introspection happy. using no-op
40  * functions for copy and free, and crossing fingers.
41  */
42 static ColRowInfo *
col_row_info_fake_copy(ColRowInfo * cri)43 col_row_info_fake_copy (ColRowInfo *cri)
44 {
45 	return cri;
46 }
47 
48 GType
col_row_info_get_type(void)49 col_row_info_get_type (void)
50 {
51 	static GType t = 0;
52 
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);
57 	}
58 	return t;
59 }
60 
61 double
colrow_compute_pixel_scale(Sheet const * sheet,gboolean horizontal)62 colrow_compute_pixel_scale (Sheet const *sheet, gboolean horizontal)
63 {
64 	return (sheet ? sheet->last_zoom_factor_used : 1.0) *
65 		gnm_app_display_dpi_get (horizontal) / 72.;
66 }
67 
68 void
colrow_compute_pixels_from_pts(ColRowInfo * cri,Sheet const * sheet,gboolean horizontal,double scale)69 colrow_compute_pixels_from_pts (ColRowInfo *cri, Sheet const *sheet,
70 				gboolean horizontal, double scale)
71 {
72 	int const margin = horizontal ? 2*GNM_COL_MARGIN : 2*GNM_ROW_MARGIN;
73 
74 	if (scale == -1)
75 		scale = colrow_compute_pixel_scale (sheet, horizontal);
76 
77 	if (horizontal && sheet && sheet->display_formulas)
78 		scale *= 2;
79 
80 	cri->size_pixels = (int)(cri->size_pts * scale + 0.5);
81 
82 	if (cri->size_pixels <= margin)
83 		cri->size_pixels = margin + 1;
84 }
85 
86 void
colrow_compute_pts_from_pixels(ColRowInfo * cri,Sheet const * sheet,gboolean horizontal,double scale)87 colrow_compute_pts_from_pixels (ColRowInfo *cri, Sheet const *sheet,
88 				gboolean horizontal, double scale)
89 {
90 	if (scale <= 0.)
91 		scale = colrow_compute_pixel_scale (sheet, horizontal);
92 
93 	if (horizontal && sheet->display_formulas)
94 		scale *= 2;
95 
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
101 }
102 
103 /**
104  * col_row_info_is_default:
105  * @cri: #ColRowInfo
106  *
107  * %TRUE if @cri is the default style for columns or rows.
108  **/
109 gboolean
col_row_info_is_default(ColRowInfo const * cri)110 col_row_info_is_default (ColRowInfo const *cri)
111 {
112 	g_return_val_if_fail (cri != NULL, FALSE);
113 	return cri->is_default;
114 }
115 
116 /**
117  * col_row_info_is_empty:
118  * @cri: #ColRowInfo
119  *
120  * %TRUE if there is no information in col/row @cri.
121  **/
122 gboolean
col_row_info_is_empty(ColRowInfo const * cri)123 col_row_info_is_empty (ColRowInfo const *cri)
124 {
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;
131 }
132 
133 /**
134  * col_row_info_equal:
135  * @a: First #ColRowInfo
136  * @b: Second #ColRowInfo
137  *
138  * Returns %TRUE if the infos are equivalent.
139  **/
140 gboolean
col_row_info_equal(ColRowInfo const * a,ColRowInfo const * b)141 col_row_info_equal (ColRowInfo const *a, ColRowInfo const *b)
142 {
143 	if (a == NULL)
144 		return b == NULL;
145 	if (b == NULL)
146 		return FALSE;
147 
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;
153 }
154 
155 /**
156  * col_row_info_copy:
157  * @dst: Destination #ColRowInfo
158  * @src: Source #ColRowInfo
159  *
160  * Copy all content, except the position of @src to @dst.
161  */
162 void
col_row_info_copy(ColRowInfo * dst,ColRowInfo const * src)163 col_row_info_copy (ColRowInfo *dst, ColRowInfo const *src)
164 {
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;
171 }
172 
173 ColRowInfo *
col_row_info_new(void)174 col_row_info_new (void)
175 {
176 	return g_slice_new (ColRowInfo);
177 }
178 
179 void
colrow_free(ColRowInfo * cri)180 colrow_free (ColRowInfo *cri)
181 {
182 	g_slice_free1 (sizeof (*cri), cri);
183 }
184 
185 
186 /**
187  * colrow_state_list_foreach:
188  * @list: The #ColRowStateList to iterate.
189  * @sheet: (nullable): Origin #Sheet.
190  * @is_cols: %TRUE for columns, %FALSE for rows.
191  * @base: index of first column or row.
192  * @callback: (scope call): A callback function which should
193  *   return %TRUE to stop the iteration.
194  * @user_data:	A baggage pointer.
195  *
196  * Iterates through the existing rows or columns within the range supplied.
197  * Currently only support left -> right iteration.  If a callback returns
198  * %TRUE iteration stops.
199  **/
200 gboolean
colrow_state_list_foreach(ColRowStateList * list,Sheet const * sheet,gboolean is_cols,int base,ColRowHandler callback,gpointer user_data)201 colrow_state_list_foreach (ColRowStateList *list,
202 			   Sheet const *sheet, gboolean is_cols,
203 			   int base,
204 			   ColRowHandler callback,
205 			   gpointer user_data)
206 {
207 	GnmColRowIter iter;
208 	int i = base;
209 	ColRowStateList *l;
210 	ColRowInfo cri;
211 	double scale = colrow_compute_pixel_scale (sheet, is_cols);
212 
213 	// This sets various fields we do not have
214 	memset (&cri, 0, sizeof (cri));
215 
216 	iter.cri = &cri;
217 	for (l = list; l; l = l->next) {
218 		ColRowRLEState *rle = l->data;
219 		ColRowState const *state = &rle->state;
220 		int l;
221 
222 		cri.size_pts = state->size_pts;
223 		cri.outline_level = state->outline_level;
224 		cri.is_collapsed = state->is_collapsed;
225 		cri.hard_size = state->hard_size;
226 		cri.visible = state->visible;
227 		colrow_compute_pixels_from_pts (&cri, sheet, is_cols, scale);
228 
229 		for (l = 0; l < rle->length; l++) {
230 			iter.pos = i++;
231 			if (iter.cri && (*callback)(&iter, user_data))
232 				return TRUE;
233 		}
234 	}
235 	return FALSE;
236 }
237 
238 
239 /*****************************************************************************/
240 
241 typedef struct _ColRowIndex {
242 	int first, last;
243 } ColRowIndex;
244 
245 
246 static void
cb_colrow_index_counter(gpointer data,gpointer user_data)247 cb_colrow_index_counter (gpointer data, gpointer user_data)
248 {
249 	ColRowIndex *index = data;
250 	gint *count = user_data;
251 	if (data != NULL)
252 		*count += index->last - index->first + 1;
253 }
254 
255 gint
colrow_vis_list_length(ColRowVisList * list)256 colrow_vis_list_length (ColRowVisList *list)
257 {
258 	gint count = 0;
259 	g_slist_foreach (list, cb_colrow_index_counter, &count);
260 	return count;
261 }
262 
263 /**
264  * colrow_state_group_destroy:
265  * @set: (transfer full): the group to destroy.
266  *
267  * Returns: (transfer none) (nullable): %NULL.
268  **/
269 ColRowStateGroup *
colrow_state_group_destroy(ColRowStateGroup * group)270 colrow_state_group_destroy (ColRowStateGroup *group)
271 {
272 	ColRowStateGroup *ptr;
273 	for (ptr = group; ptr != NULL ; ptr = ptr->next)
274 		colrow_state_list_destroy (ptr->data);
275 	g_slist_free (group);
276 	return NULL;
277 }
278 
279 static gint
colrow_index_compare(ColRowIndex const * a,ColRowIndex const * b)280 colrow_index_compare (ColRowIndex const * a, ColRowIndex const * b)
281 {
282 	return a->first - b->first;
283 }
284 
285 /*
286  * colrow_index_list_to_string: Convert an index list into a string.
287  *                              The result must be freed by the caller.
288  *                              It will be something like : A-B, F-G
289  *
290  * @list: The list
291  * @is_cols: %TRUE for columns, %FALSE for rows.
292  * @is_single: If non-null this will be set to %TRUE if there's only a single col/row involved.
293  */
294 GString *
colrow_index_list_to_string(ColRowIndexList * list,gboolean is_cols,gboolean * is_single)295 colrow_index_list_to_string (ColRowIndexList *list, gboolean is_cols, gboolean *is_single)
296 {
297 	ColRowIndexList *ptr;
298 	GString *result;
299 	gboolean single = TRUE;
300 
301 	g_return_val_if_fail (list != NULL, NULL);
302 
303 	result = g_string_new (NULL);
304 	for (ptr = list; ptr != NULL; ptr = ptr->next) {
305 		ColRowIndex *index = ptr->data;
306 
307 		if (is_cols)
308 			g_string_append (result, cols_name (index->first, index->last));
309 		else
310 			g_string_append (result, rows_name (index->first, index->last));
311 
312 		if (index->last != index->first)
313 			single = FALSE;
314 
315 		if (ptr->next) {
316 			g_string_append (result, ", ");
317 			single = FALSE;
318 		}
319 	}
320 
321 	if (is_single)
322 		*is_single = single;
323 
324 	return result;
325 }
326 
327 /**
328  * colrow_get_index_list:
329  * @first:
330  * @last:
331  * @list: (transfer full):
332  *
333  * Build an ordered list of pairs doing intelligent merging
334  * of overlapping regions.
335  *
336  * Returns: (transfer full): @list.
337  */
338 ColRowIndexList *
colrow_get_index_list(int first,int last,ColRowIndexList * list)339 colrow_get_index_list (int first, int last, ColRowIndexList *list)
340 {
341 	ColRowIndex *tmp, *prev;
342 	GList *ptr;
343 
344 	tmp = g_new (ColRowIndex, 1);
345 	tmp->first = first;
346 	tmp->last = last;
347 
348 	list = g_list_insert_sorted (list, tmp,
349 				     (GCompareFunc)&colrow_index_compare);
350 
351 	prev = list->data;
352 	for (ptr = list->next ; ptr != NULL ; ) {
353 		tmp = ptr->data;
354 
355 		/* at the end of existing segment or contained */
356 		if (prev->last+1 >= tmp->first) {
357 			GList *next = ptr->next;
358 			if (prev->last < tmp->last)
359 				prev->last = tmp->last;
360 			list = g_list_remove_link (list, ptr);
361 			ptr = next;
362 		} else {
363 			ptr = ptr->next;
364 			prev = tmp;
365 		}
366 	}
367 	return list;
368 }
369 
370 /**
371  * colrow_index_list_copy:
372  * @list: #ColRowIndexList
373  *
374  * Returns: (transfer full):
375  **/
376 ColRowIndexList *
colrow_index_list_copy(ColRowIndexList * list)377 colrow_index_list_copy (ColRowIndexList *list)
378 {
379 	GList *copy = NULL, *ptr;
380 
381 	for (ptr = list ; ptr != NULL ; ptr = ptr->next) {
382 		ColRowIndex *tmp = g_new (ColRowIndex, 1);
383 		ColRowIndex *ex = ptr->data;
384 		tmp->first = ex->first;
385 		tmp->last = ex->last;
386 		copy = g_list_prepend (copy, tmp);
387 	}
388 	return g_list_reverse (copy);
389 }
390 
391 static void
colrow_set_single_state(ColRowState * state,Sheet * sheet,int i,gboolean is_cols)392 colrow_set_single_state (ColRowState *state,
393 			 Sheet *sheet, int i, gboolean is_cols)
394 {
395 	ColRowInfo const *info = sheet_colrow_get_info (sheet, i, is_cols);
396 	state->is_default = col_row_info_is_default (info);
397 	state->size_pts	= info->size_pts;
398 	state->outline_level = info->outline_level;
399 	state->is_collapsed = info->is_collapsed;
400 	state->hard_size = info->hard_size;
401 	state->visible = info->visible;
402 }
403 
404 /**
405  * colrow_state_list_destroy:
406  * @list: (transfer full): the list to destroy.
407  *
408  * Returns: (transfer none): %NULL.
409  **/
410 ColRowStateList *
colrow_state_list_destroy(ColRowStateList * list)411 colrow_state_list_destroy (ColRowStateList *list)
412 {
413 	g_slist_free_full (list, g_free);
414 	return NULL;
415 }
416 
417 /**
418  * colrow_get_states: (skip)
419  * @sheet: #Sheet
420  * @is_cols: %TRUE for columns, %FALSE for rows.
421  * @first: first column or row.
422  * @last: last column or row.
423  *
424  * Returns: (transfer full):
425  **/
426 ColRowStateList *
colrow_get_states(Sheet * sheet,gboolean is_cols,int first,int last)427 colrow_get_states (Sheet *sheet, gboolean is_cols, int first, int last)
428 {
429 	ColRowStateList *list = NULL;
430 	ColRowRLEState  *rles;
431 	ColRowState	 run_state;
432 	int              i, run_length;
433 
434 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
435 	g_return_val_if_fail (first <= last, NULL);
436 
437 	colrow_set_single_state (&run_state, sheet, first, is_cols);
438 	run_length = 1;
439 
440 	for (i = first + 1; i <= last; ++i) {
441 		ColRowState cur_state;
442 		colrow_set_single_state (&cur_state, sheet, i, is_cols);
443 
444 		/* If state changed, start a new block */
445 		if (cur_state.is_default    != run_state.is_default ||
446 		    cur_state.size_pts	    != run_state.size_pts ||
447 		    cur_state.outline_level != run_state.outline_level ||
448 		    cur_state.is_collapsed  != run_state.is_collapsed ||
449 		    cur_state.hard_size	    != run_state.hard_size ||
450 		    cur_state.visible	    != run_state.visible) {
451 			rles = g_new (ColRowRLEState, 1);
452 			rles->length = run_length;
453 			rles->state  = run_state;
454 			list = g_slist_prepend (list, rles);
455 
456 			run_state = cur_state;
457 			run_length = 1;
458 		} else
459 			++run_length;
460 	}
461 
462 	/* Store the final run */
463 	rles = g_new (ColRowRLEState, 1);
464 	rles->length = run_length;
465 	rles->state = run_state;
466 	list = g_slist_prepend (list, rles);
467 
468 	return g_slist_reverse (list);
469 }
470 
471 struct resize_closure {
472 	Sheet *sheet;
473 	int	 new_size;
474 	gboolean is_cols;
475 };
476 
477 static gboolean
cb_set_colrow_size(GnmColRowIter const * iter,gpointer userdata)478 cb_set_colrow_size (GnmColRowIter const *iter, gpointer userdata)
479 {
480 	if (iter->cri->visible) {
481 		struct resize_closure const *c = userdata;
482 
483 		if (c->is_cols)
484 			sheet_col_set_size_pixels (c->sheet, iter->pos,
485 						   c->new_size, TRUE);
486 		else
487 			sheet_row_set_size_pixels (c->sheet, iter->pos,
488 						   c->new_size, TRUE);
489 	}
490 	return FALSE;
491 }
492 
493 static GnmValue *
cb_clear_variable_width_content(GnmCellIter const * iter,G_GNUC_UNUSED gpointer user)494 cb_clear_variable_width_content (GnmCellIter const *iter,
495 				 G_GNUC_UNUSED gpointer user)
496 {
497 	GnmRenderedValue *rv = gnm_cell_get_rendered_value (iter->cell);
498 	if (rv && rv->variable_width) {
499 		iter->ri->needs_respan = TRUE;
500 		gnm_cell_unrender (iter->cell);
501 	}
502 	return NULL;
503 }
504 
505 /**
506  * colrow_get_sizes: (skip)
507  * @sheet: #Sheet
508  * @is_cols: %TRUE for columns, %FALSE for rows.
509  * @src:
510  * @new_size:
511  *
512  * Returns: (transfer full):
513  **/
514 ColRowStateGroup *
colrow_get_sizes(Sheet * sheet,gboolean is_cols,ColRowIndexList * src,int new_size)515 colrow_get_sizes (Sheet *sheet, gboolean is_cols,
516 		  ColRowIndexList *src, int new_size)
517 {
518 	ColRowStateGroup *res = NULL;
519 	ColRowIndexList *ptr;
520 
521 	for (ptr = src; ptr != NULL ; ptr = ptr->next) {
522 		ColRowIndex const *index = ptr->data;
523 		res = g_slist_prepend (res, colrow_get_states (sheet, is_cols,
524 			index->first, index->last));
525 
526 		if (new_size > 0 && index->first == 0 &&
527 		    (index->last+1) >= colrow_max (is_cols, sheet)) {
528 			ColRowRLEState *rles = g_new0 (ColRowRLEState, 1);
529 
530 			rles->length = -1; /* Flag as changing the default */
531 
532 			if (is_cols)
533 				rles->state.size_pts = sheet_col_get_default_size_pts (sheet);
534 			else
535 				rles->state.size_pts = sheet_row_get_default_size_pts (sheet);
536 
537 			/* Result is a magic 'default' record + >= 1 normal */
538 			return g_slist_prepend (res, g_slist_append (NULL, rles));
539 		}
540 	}
541 
542 	return res;
543 }
544 
545 /**
546  * colrow_set_sizes: (skip)
547  * @sheet: #Sheet
548  * @is_cols: %TRUE for columns, %FALSE for rows.
549  * @src:
550  * @new_size:
551  * @from:
552  * @to:
553  *
554  * Returns: (transfer full):
555  **/
556 ColRowStateGroup *
colrow_set_sizes(Sheet * sheet,gboolean is_cols,ColRowIndexList * src,int new_size,int from,int to)557 colrow_set_sizes (Sheet *sheet, gboolean is_cols,
558 		  ColRowIndexList *src, int new_size, int from, int to)
559 /* from & to are used to restrict fitting to that range. Pass 0, -1 if you want to use the */
560 /* whole row/column */
561 {
562 	int i;
563 	ColRowStateGroup *res = NULL;
564 	ColRowIndexList *ptr;
565 
566 	for (ptr = src; ptr != NULL ; ptr = ptr->next) {
567 		ColRowIndex const *index = ptr->data;
568 		res = g_slist_prepend (res, colrow_get_states (sheet, is_cols,
569 			index->first, index->last));
570 
571 		/* FIXME:
572 		 * If we are changing the size of more than half of the rows/col to
573 		 * something specific (not autosize) we should change the default
574 		 * row/col size instead.  However, it is unclear how to handle
575 		 * hard sizing.
576 		 *
577 		 * we need better management of rows/cols.  Currently if they are all
578 		 * defined calculation speed grinds to a halt.
579 		 */
580 		if (new_size > 0 && index->first == 0 &&
581 		    (index->last+1) >= colrow_max (is_cols, sheet)) {
582 			struct resize_closure closure;
583 			ColRowRLEState *rles = g_new0 (ColRowRLEState, 1);
584 
585 			rles->length = -1; /* Flag as changing the default */
586 
587 			closure.sheet	 = sheet;
588 			closure.new_size = new_size;
589 			closure.is_cols  = is_cols;
590 			if (is_cols) {
591 				rles->state.size_pts = sheet_col_get_default_size_pts (sheet);
592 				sheet_col_set_default_size_pixels (sheet, new_size);
593 				sheet_colrow_foreach (sheet, TRUE, 0, -1,
594 					&cb_set_colrow_size, &closure);
595 			} else {
596 				rles->state.size_pts = sheet_row_get_default_size_pts (sheet);
597 				sheet_row_set_default_size_pixels (sheet, new_size);
598 				sheet_colrow_foreach (sheet, FALSE, 0, -1,
599 					&cb_set_colrow_size, &closure);
600 			}
601 
602 			/* force a re-render of cells with expanding formats */
603 			if (is_cols)
604 				sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_BLANK,
605 							      0, 0, -1, -1,
606 					(CellIterFunc) &cb_clear_variable_width_content, NULL);
607 
608 			/* Result is a magic 'default' record + >= 1 normal */
609 			return g_slist_prepend (res, g_slist_append (NULL, rles));
610 		}
611 
612 		if (is_cols) {
613 			/* force a re-render of cells with expanding formats */
614 			sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_BLANK,
615 				index->first, 0, index->last, -1,
616 				(CellIterFunc) &cb_clear_variable_width_content, NULL);
617 
618 			/* In order to properly reposition cell comments in
619 			 * merged cells that cross the boundary we need to do
620 			 * everything.  Remove this when comments are handled
621 			 * properly */
622 			sheet->priv->reposition_objects.col = 0;
623 		}
624 
625 		for (i = index->first ; i <= index->last ; ++i) {
626 			int tmp = new_size;
627 			if (tmp < 0) {
628 				int max = is_cols ? gnm_sheet_get_last_row (sheet)
629 					: gnm_sheet_get_last_col (sheet);
630 				if (from < 0)
631 					from = 0;
632 				if (to < 0 || to > max)
633 					to = max;
634 				if (from > max)
635 					from = to;
636 				/* Fall back to assigning the default if it is empty */
637 				tmp = (is_cols)
638 					? sheet_col_size_fit_pixels (sheet, i, from, to, FALSE)
639 					: sheet_row_size_fit_pixels (sheet, i, from, to, FALSE);
640 			}
641 			if (tmp > 0) {
642 				if (is_cols)
643 					sheet_col_set_size_pixels (sheet, i, tmp, new_size > 0);
644 				else
645 					sheet_row_set_size_pixels (sheet, i, tmp, new_size > 0);
646 			} else if (sheet_colrow_get (sheet, i, is_cols) != NULL) {
647 				if (is_cols)
648 					sheet_col_set_size_pixels (sheet, i,
649 						sheet_col_get_default_size_pixels (sheet), FALSE);
650 				else
651 					sheet_row_set_size_pixels (sheet, i,
652 						sheet_row_get_default_size_pixels (sheet), FALSE);
653 			}
654 		}
655 	}
656 
657 	return res;
658 }
659 
660 /**
661  * colrow_set_states: (skip)
662  * @sheet: #Sheet
663  * @is_cols: %TRUE for columns, %FALSE for rows.
664  * @first: first column or row
665  * @states: saved state to restore.
666  *
667  * This is a low level routine it does not redraw or reposition objects
668  */
669 void
colrow_set_states(Sheet * sheet,gboolean is_cols,int first,ColRowStateList * states)670 colrow_set_states (Sheet *sheet, gboolean is_cols,
671 		   int first, ColRowStateList *states)
672 {
673 	GSList *l;
674 	int i, max_outline, offset = first;
675 	ColRowCollection *infos;
676 	double scale;
677 
678 	g_return_if_fail (IS_SHEET (sheet));
679 
680 	infos = is_cols ? &(sheet->cols) : &(sheet->rows);
681 	max_outline = infos->max_outline_level;
682 	scale = colrow_compute_pixel_scale (sheet, is_cols);
683 
684 	for (l = states; l != NULL; l = l->next) {
685 		ColRowRLEState const *rles = l->data;
686 		ColRowState const *state = &rles->state;
687 
688 		if (max_outline < state->outline_level)
689 			max_outline = state->outline_level;
690 
691 		for (i = offset; i < offset + rles->length; i++) {
692 			if (state->is_default) {
693 				ColRowSegment *segment = COLROW_GET_SEGMENT(infos, i);
694 				if (segment != NULL) {
695 					int const sub = COLROW_SUB_INDEX (i);
696 					ColRowInfo *cri = segment->info[sub];
697 					if (cri != NULL) {
698 						segment->info[sub] = NULL;
699 						colrow_free (cri);
700 					}
701 				}
702 			} else {
703 				ColRowInfo *cri = sheet_colrow_fetch (sheet, i, is_cols);
704 				cri->hard_size = state->hard_size;
705 				cri->size_pts = state->size_pts;
706 				colrow_compute_pixels_from_pts (cri, sheet, is_cols, scale);
707 				col_row_info_set_outline (cri, state->outline_level,
708 					state->is_collapsed);
709 			}
710 		}
711 		offset += rles->length;
712 	}
713 
714 	/* Notify sheet of pending update */
715 	sheet->priv->recompute_visibility = TRUE;
716 	if (is_cols) {
717 		sheet_flag_recompute_spans (sheet);
718 
719 		/* In order to properly reposition cell
720 		 * comments in merged cells that cross the
721 		 * boundary we need to do everything.  Revert
722 		 * this when comments are handled properly */
723 #if 0
724 		if (sheet->priv->reposition_objects.col > first)
725 			sheet->priv->reposition_objects.col = first;
726 #else
727 		sheet->priv->reposition_objects.col = 0;
728 #endif
729 	} else {
730 		if (sheet->priv->reposition_objects.row > first)
731 			sheet->priv->reposition_objects.row = first;
732 	}
733 	sheet_colrow_gutter (sheet, is_cols, max_outline);
734 }
735 
736 /**
737  * colrow_restore_state_group: (skip)
738  * @sheet: #Sheet
739  * @is_cols: %TRUE for columns, %FALSE for rows.
740  * @selection:
741  * @state_groups:
742  *
743  **/
744 void
colrow_restore_state_group(Sheet * sheet,gboolean is_cols,ColRowIndexList * selection,ColRowStateGroup * state_groups)745 colrow_restore_state_group (Sheet *sheet, gboolean is_cols,
746 			    ColRowIndexList *selection,
747 			    ColRowStateGroup *state_groups)
748 {
749 	ColRowStateGroup *ptr = state_groups;
750 
751 	/* Cycle to end, we have to traverse the selections
752 	 * in parallel with the state_groups
753 	 */
754 	selection = g_list_last (selection);
755 	for (; selection != NULL && ptr != NULL ; ptr = ptr->next) {
756 		ColRowIndex const *index = selection->data;
757 		ColRowStateList *list = ptr->data;
758 		ColRowRLEState const *rles = list->data;
759 
760 		/* MAGIC : the -1 was set above to flag this */
761 		if (rles->length == -1) {
762 			if (is_cols)
763 				sheet_col_set_default_size_pts (sheet, rles->state.size_pts);
764 			else
765 				sheet_row_set_default_size_pts (sheet, rles->state.size_pts);
766 
767 			/* we are guaranteed to have at least 1 more record */
768 			ptr = ptr->next;
769 		}
770 
771 		colrow_set_states (sheet, is_cols, index->first, ptr->data);
772 		/* force a re-render of cells with expanding formats */
773 		if (is_cols)
774 			sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_BLANK,
775 				index->first, 0, index->last, -1,
776 				(CellIterFunc) &cb_clear_variable_width_content, NULL);
777 		selection = selection->prev;
778 	}
779 }
780 
781 /**
782  * rows_height_update:
783  * @sheet:  The sheet,
784  * @range:  The range whose rows should be resized.
785  * @shrink: If set to %FALSE, rows will never shrink!
786  *
787  * Use this function having changed the font size to auto-resize the row
788  * heights to make the text fit nicely.
789  **/
790 void
rows_height_update(Sheet * sheet,GnmRange const * range,gboolean shrink)791 rows_height_update (Sheet *sheet, GnmRange const *range, gboolean shrink)
792 {
793 	/* FIXME: this needs to check font sizes and contents rather than
794 	 * just contents.  Empty cells will cause resize also */
795 	colrow_autofit (sheet, range, FALSE, FALSE,
796 			FALSE, !shrink,
797 			NULL, NULL);
798 }
799 
800 /* ------------------------------------------------------------------------- */
801 
802 struct cb_autofit {
803 	Sheet *sheet;
804 	const GnmRange *range;
805 	gboolean ignore_strings;
806 	gboolean min_current;
807 	gboolean min_default;
808 };
809 
810 static gboolean
cb_autofit_col(GnmColRowIter const * iter,gpointer data_)811 cb_autofit_col (GnmColRowIter const *iter, gpointer data_)
812 {
813 	struct cb_autofit *data = data_;
814 	int size, min, max;
815 
816 	if (iter->cri->hard_size)
817 		return FALSE;
818 
819 	size = sheet_col_size_fit_pixels (data->sheet, iter->pos,
820 		 data->range->start.row, data->range->end.row,
821 		 data->ignore_strings);
822 	/* FIXME: better idea than this?  */
823 	max = 50 * sheet_col_get_default_size_pixels (data->sheet);
824 	size = MIN (size, max);
825 
826 	min = 0;
827 	if (data->min_current)
828 		min = MAX (min, iter->cri->size_pixels);
829 	if (data->min_default)
830 		min = MAX (min, sheet_col_get_default_size_pixels (data->sheet));
831 
832 	if (size > min)
833 		sheet_col_set_size_pixels (data->sheet, iter->pos, size, FALSE);
834 
835 	return FALSE;
836 }
837 
838 static gboolean
cb_autofit_row(GnmColRowIter const * iter,gpointer data_)839 cb_autofit_row (GnmColRowIter const *iter, gpointer data_)
840 {
841 	struct cb_autofit *data = data_;
842 	int size, min, max;
843 
844 	if (iter->cri->hard_size)
845 		return FALSE;
846 
847 	size = sheet_row_size_fit_pixels (data->sheet, iter->pos,
848 		 data->range->start.col, data->range->end.col,
849 		 data->ignore_strings);
850 	max = 20 * sheet_row_get_default_size_pixels (data->sheet);
851 	size = MIN (size, max);
852 
853 	min = 0;
854 	if (data->min_current)
855 		min = MAX (min, iter->cri->size_pixels);
856 	if (data->min_default)
857 		min = MAX (min, sheet_row_get_default_size_pixels (data->sheet));
858 
859 	if (size > min)
860 		sheet_row_set_size_pixels (data->sheet, iter->pos, size, FALSE);
861 
862 	return FALSE;
863 }
864 
865 /*
866  * colrow_autofit:
867  * @sheet: the #Sheet to change
868  * @range: the range to consider
869  * @is_cols: %TRUE for columns, %FALSE for rows.
870  * @ignore_strings: Don't consider cells with string values.
871  * @min_current: Don't shrink below current size.
872  * @min_default: Don't shrink below default size.
873  * @indices: (out) (optional): indices appropriate for
874  *     colrow_restore_state_group.
875  * @sizes: (out) (optional): old sizes appropriate for
876  *     colrow_restore_state_group.
877  *
878  * This function autofits columns or rows in @range as specified by
879  * @is_cols.  Only cells in @range are considered for the sizing
880  * and the size can be bounded below by current size and/or default
881  * size.
882  */
883 void
colrow_autofit(Sheet * sheet,const GnmRange * range,gboolean is_cols,gboolean ignore_strings,gboolean min_current,gboolean min_default,ColRowIndexList ** indices,ColRowStateGroup ** sizes)884 colrow_autofit (Sheet *sheet, const GnmRange *range, gboolean is_cols,
885 		gboolean ignore_strings,
886 		gboolean min_current, gboolean min_default,
887 		ColRowIndexList **indices,
888 		ColRowStateGroup **sizes)
889 {
890 	struct cb_autofit data;
891 	int a, b;
892 	ColRowHandler handler;
893 
894 	data.sheet = sheet;
895 	data.range = range;
896 	data.ignore_strings = ignore_strings;
897 	data.min_current = min_current;
898 	data.min_default = min_default;
899 
900 	if (is_cols) {
901 		a = range->start.col;
902 		b = range->end.col;
903 		handler = cb_autofit_col;
904 	} else {
905 		a = range->start.row;
906 		b = range->end.row;
907 		handler = cb_autofit_row;
908 	}
909 
910 	if (indices)
911 		*indices = colrow_get_index_list (a, b, NULL);
912 	if (sizes)
913 		*sizes = g_slist_prepend (NULL, colrow_get_states (sheet, is_cols, a, b));
914 
915 	/* We potentially do a lot of recalcs as part of this, so make sure
916 	   stuff that caches sub-computations see the whole thing instead
917 	   of clearing between cells.  */
918 	gnm_app_recalc_start ();
919 	sheet_colrow_foreach (sheet, is_cols, a, b, handler, &data);
920 	gnm_app_recalc_finish ();
921 }
922 
923 /**
924  * colrow_autofit_col:
925  * @sheet: the #Sheet to change
926  * @r: the range to consider
927  */
928 void
colrow_autofit_col(Sheet * sheet,GnmRange * r)929 colrow_autofit_col (Sheet *sheet, GnmRange *r)
930 {
931 	colrow_autofit (sheet, r, TRUE, TRUE,
932 			TRUE, FALSE, NULL, NULL);
933 	sheet_foreach_cell_in_region (sheet, CELL_ITER_IGNORE_BLANK,
934 				      r->start.col, 0,
935 				      r->end.col, -1,
936 				      (CellIterFunc) &cb_clear_variable_width_content,
937 				      NULL);
938 }
939 
940 /**
941  * colrow_autofit_row:
942  * @sheet: the #Sheet to change
943  * @r: the range to consider
944  */
945 void
colrow_autofit_row(Sheet * sheet,GnmRange * r)946 colrow_autofit_row (Sheet *sheet, GnmRange *r)
947 {
948 	colrow_autofit (sheet, r, FALSE, FALSE,
949 			TRUE, FALSE, NULL, NULL);
950 }
951 
952 /*****************************************************************************/
953 
954 typedef struct
955 {
956 	gboolean is_cols, visible;
957 	ColRowVisList *elements;
958 } ColRowVisibility;
959 
960 static gint
colrow_index_cmp(ColRowIndex const * a,ColRowIndex const * b)961 colrow_index_cmp (ColRowIndex const *a, ColRowIndex const *b)
962 {
963 	/* We can be very simplistic here because the ranges never overlap */
964 	return b->first - a->first;
965 }
966 
967 static void
colrow_visibility(Sheet const * sheet,ColRowVisibility * const dat,int first,int last)968 colrow_visibility (Sheet const *sheet, ColRowVisibility * const dat,
969 		   int first, int last)
970 {
971 	int i;
972 	gboolean const visible = dat->visible;
973 	ColRowInfo * (*get) (Sheet const *sheet, int pos) = (dat->is_cols)
974 		? &sheet_col_get : &sheet_row_get;
975 
976 	/* Find the end of a segment that will be toggled */
977 	for (i = last; i >= first; --i) {
978 		int j;
979 		ColRowIndex *res;
980 		ColRowInfo const *cri = (*get) (sheet, i);
981 
982 		if (cri == NULL) {
983 			if (visible != 0)
984 				continue;
985 		} else if ((visible != 0) == (cri->visible != 0))
986 			continue;
987 
988 		/* Find the begining */
989 		for (j = i; j >= first ; --j) {
990 			cri = (*get) (sheet, j);
991 			if (cri == NULL) {
992 				if (visible != 0)
993 					break;
994 			} else if ((visible != 0) == (cri->visible != 0))
995 				break;
996 			else if (cri->is_collapsed) {
997 				--j;
998 				break;
999 			}
1000 		}
1001 		res = g_new (ColRowIndex, 1);
1002 		res->first = (j >= first) ? j+1 : first;
1003 		res->last = i;
1004 #if 0
1005 		g_printerr ("%d %d\n", res->index, res->count);
1006 #endif
1007 		dat->elements = g_slist_insert_sorted (dat->elements, res,
1008 					(GCompareFunc)colrow_index_cmp);
1009 
1010 		if (visible && cri != NULL && cri->is_collapsed) {
1011 			i = colrow_find_outline_bound (
1012 				sheet, dat->is_cols, j,
1013 				cri->outline_level+1, FALSE);
1014 		} else
1015 			i = j;
1016 	}
1017 }
1018 
1019 /**
1020  * colrow_get_outline_toggle: (skip)
1021  * @sheet: #Sheet
1022  * @is_cols: %TRUE for columns, %FALSE for rows.
1023  * @visible:
1024  * @first:
1025  * @last:
1026  *
1027  * Returns: (transfer full):
1028  **/
1029 ColRowVisList *
colrow_get_outline_toggle(Sheet const * sheet,gboolean is_cols,gboolean visible,int first,int last)1030 colrow_get_outline_toggle (Sheet const *sheet, gboolean is_cols, gboolean visible,
1031 			   int first, int last)
1032 {
1033 	ColRowVisibility closure;
1034 	closure.is_cols = is_cols;
1035 	closure.visible = visible;
1036 	closure.elements = NULL;
1037 
1038 	colrow_visibility (sheet, &closure, first, last);
1039 	return closure.elements;
1040 }
1041 
1042 static void
cb_colrow_visibility(SheetView * sv,GnmRange const * r,gpointer closure)1043 cb_colrow_visibility (SheetView *sv, GnmRange const *r, gpointer closure)
1044 {
1045 	ColRowVisibility * const dat = (ColRowVisibility *)closure;
1046 	int first, last;
1047 
1048 	if (dat->is_cols) {
1049 		first = r->start.col;
1050 		last = r->end.col;
1051 	} else {
1052 		first = r->start.row;
1053 		last = r->end.row;
1054 	}
1055 	colrow_visibility (sv_sheet (sv), dat, first, last);
1056 }
1057 
1058 /**
1059  * colrow_get_visibility_toggle: (skip)
1060  * @sv: The sheet view whose selection we are interested in.
1061  * @is_cols: %TRUE for columns, %FALSE for rows.
1062  * @visible: Should we unhide or hide the cols/rows.
1063  *
1064  * Searches the selection list and generates a list of index,count
1065  * pairs of row/col ranges that need to be hidden or unhidden.
1066  *
1067  * Returns: (transfer full): the list.
1068  */
1069 ColRowVisList *
colrow_get_visibility_toggle(SheetView * sv,gboolean is_cols,gboolean visible)1070 colrow_get_visibility_toggle (SheetView *sv, gboolean is_cols,
1071 			     gboolean visible)
1072 {
1073 	ColRowVisibility closure;
1074 	closure.is_cols = is_cols;
1075 	closure.visible = visible;
1076 	closure.elements = NULL;
1077 
1078 	sv_selection_apply (sv, &cb_colrow_visibility, FALSE, &closure);
1079 
1080 	return closure.elements;
1081 }
1082 
1083 /**
1084  * colrow_set_visibility_list:
1085  * @sheet: The #Sheet to change
1086  * @is_cols: %TRUE for columns, %FALSE for rows.
1087  * @visible: Should we unhide or hide the cols/rows.
1088  *
1089  * This is the high level command that is wrapped by undo and redo.
1090  * It should not be called by other commands.
1091  */
1092 void
colrow_set_visibility_list(Sheet * sheet,gboolean is_cols,gboolean visible,ColRowVisList * list)1093 colrow_set_visibility_list (Sheet *sheet, gboolean is_cols,
1094 			    gboolean visible, ColRowVisList *list)
1095 {
1096 	ColRowVisList *ptr;
1097 	ColRowIndex *info;
1098 
1099 	for (ptr = list; ptr != NULL ; ptr = ptr->next) {
1100 		info = ptr->data;
1101 		colrow_set_visibility (sheet, is_cols, visible,
1102 				       info->first, info->last);
1103 	}
1104 
1105 	if (visible)
1106 		sheet_colrow_optimize (sheet);
1107 
1108 	if (is_cols)
1109 		sheet_queue_respan (sheet, 0, gnm_sheet_get_last_row (sheet));
1110 	if (list != NULL)
1111 		sheet_redraw_all (sheet, TRUE);
1112 }
1113 
1114 /**
1115  * col_row_info_set_outline:
1116  * @cri: #ColRowInfo to tweak
1117  * @outline_level:
1118  * @is_collapsed:
1119  *
1120  * Adjust the outline state of a col/row
1121  */
1122 void
col_row_info_set_outline(ColRowInfo * cri,int outline_level,gboolean is_collapsed)1123 col_row_info_set_outline (ColRowInfo *cri, int outline_level, gboolean is_collapsed)
1124 {
1125 	g_return_if_fail (outline_level >= 0);
1126 
1127 	cri->is_collapsed = !!is_collapsed;
1128 	cri->outline_level = outline_level;
1129 }
1130 
1131 /**
1132  * colrow_find_outline_bound:
1133  *
1134  * find the next/prev col/row at the designated depth starting from the
1135  * supplied @index.
1136  */
1137 int
colrow_find_outline_bound(Sheet const * sheet,gboolean is_cols,int index,int depth,gboolean inc)1138 colrow_find_outline_bound (Sheet const *sheet, gboolean is_cols,
1139 			   int index, int depth, gboolean inc)
1140 {
1141 	ColRowInfo * (*get) (Sheet const *sheet, int pos) = is_cols
1142 		? &sheet_col_get : &sheet_row_get;
1143 	int const max = colrow_max (is_cols, sheet);
1144 	int const step = inc ? 1 : -1;
1145 
1146 	while (1) {
1147 		ColRowInfo const *cri;
1148 		int const next = index + step;
1149 
1150 		if (next < 0 || next >= max)
1151 			return index;
1152 		cri = (*get) (sheet, next);
1153 		if (cri == NULL || cri->outline_level < depth)
1154 			return index;
1155 		index = next;
1156 	}
1157 
1158 	return index;
1159 }
1160 
1161 /**
1162  * colrow_set_visibility:
1163  * @sheet: the #Sheet
1164  * @is_cols: %TRUE for columns, %FALSE for rows.
1165  * @visible: Make things visible or invisible.
1166  * @first: The index of the first row/col (inclusive)
1167  * @last: The index of the last row/col (inclusive)
1168  *
1169  * Change the visibility of the selected range of contiguous cols/rows.
1170  * NOTE : only changes the collapsed state for the LAST+1 element.
1171  */
1172 void
colrow_set_visibility(Sheet * sheet,gboolean is_cols,gboolean visible,int first,int last)1173 colrow_set_visibility (Sheet *sheet, gboolean is_cols,
1174 		       gboolean visible, int first, int last)
1175 {
1176 	int i, step, prev_outline   = 0;
1177 	gboolean changed = FALSE;
1178 	GnmRange * const bound   = &sheet->priv->unhidden_region;
1179 	gboolean const fwd = is_cols ? sheet->outline_symbols_right : sheet->outline_symbols_below;
1180 
1181 	g_return_if_fail (IS_SHEET (sheet));
1182 	g_return_if_fail (first <= last);
1183 
1184 	if (visible) { /* expand to include newly visible regions */
1185 		if (is_cols) {
1186 			if (bound->start.col > first)
1187 				bound->start.col = first;
1188 			if (bound->end.col < last)
1189 				bound->end.col = last;
1190 		} else {
1191 			if (bound->start.row > first)
1192 				bound->start.row = first;
1193 			if (bound->end.row < last)
1194 				bound->end.row = last;
1195 		}
1196 	} else { /* contract to exclude newly hidden regions */
1197 		if (is_cols) {
1198 			if (bound->start.col >= first && bound->start.col <= last)
1199 				bound->start.col = last+1;
1200 			if (bound->end.col <= last && bound->end.col >= first)
1201 				bound->end.col = first-1;
1202 		} else {
1203 			if (bound->start.row >= first && bound->start.row <= last)
1204 				bound->start.row = last+1;
1205 			if (bound->end.row <= last && bound->end.row >= first)
1206 				bound->end.row = first-1;
1207 		}
1208 	}
1209 
1210 	if (fwd) {
1211 		i = first;
1212 		step = 1;
1213 	} else {
1214 		i = last;
1215 		step = -1;
1216 	}
1217 
1218 	for (; fwd ? (i <= last) : (i >= first) ; i += step) {
1219 		ColRowInfo * const cri = sheet_colrow_fetch (sheet, i, is_cols);
1220 
1221 		if (changed && prev_outline > cri->outline_level && !visible)
1222 			cri->is_collapsed = FALSE;
1223 
1224 		changed = (visible == 0) != (cri->visible == 0);
1225 		if (changed) {
1226 			cri->visible = visible;
1227 			prev_outline = cri->outline_level;
1228 			sheet->priv->recompute_visibility = TRUE;
1229 
1230 			if (is_cols) {
1231 				sheet_flag_recompute_spans (sheet);
1232 
1233 				/* In order to properly reposition cell
1234 				 * comments in merged cells that cross the
1235 				 * boundary we need to do everything.  Revert
1236 				 * this when comments are handled properly */
1237 #if 0
1238 				if (sheet->priv->reposition_objects.col > i)
1239 					sheet->priv->reposition_objects.col = i;
1240 #else
1241 				sheet->priv->reposition_objects.col = 0;
1242 #endif
1243 			} else {
1244 				if (sheet->priv->reposition_objects.row > i)
1245 					sheet->priv->reposition_objects.row = i;
1246 			}
1247 		}
1248 	}
1249 
1250 	if (changed && 0 <= i && i < colrow_max (is_cols, sheet)) {
1251 		ColRowInfo *cri = sheet_colrow_get (sheet, i, is_cols);
1252 		if (!cri && !visible && prev_outline > 0)
1253 			cri = sheet_colrow_fetch (sheet, i, is_cols);
1254 
1255 		if (cri && prev_outline > cri->outline_level)
1256 			cri->is_collapsed = !visible;
1257 	}
1258 }
1259 
1260 // Introspection scanner crashes if the following "hide" is removed.
1261 
1262 /*hide*
1263  * colrow_get_global_outline: (skip)
1264  * @sheet:
1265  * @is_cols: %TRUE for columns, %FALSE for rows.
1266  * @depth:
1267  * @show: (out):
1268  * @hide: (out):
1269  *
1270  * Collect the set of visibility changes required to change the visibility of
1271  * all outlined columns such that those > @depth are visible.
1272  **/
1273 void
colrow_get_global_outline(Sheet const * sheet,gboolean is_cols,int depth,ColRowVisList ** show,ColRowVisList ** hide)1274 colrow_get_global_outline (Sheet const *sheet, gboolean is_cols, int depth,
1275 			   ColRowVisList **show, ColRowVisList **hide)
1276 {
1277 	ColRowInfo const *cri;
1278 	ColRowIndex *prev = NULL;
1279 	gboolean show_prev = FALSE;
1280 	unsigned tmp, prev_outline = 0;
1281 	int i, max = is_cols ? sheet->cols.max_used : sheet->rows.max_used;
1282 
1283 	*show = *hide = NULL;
1284 	for (i = 0; i <= max ; i++) {
1285 		cri = sheet_colrow_get (sheet, i, is_cols);
1286 
1287 		if (cri == NULL || cri->outline_level == 0) {
1288 			prev_outline = 0;
1289 			continue;
1290 		}
1291 		tmp = prev_outline;
1292 		prev_outline = cri->outline_level;
1293 
1294 		/* see what sort of changes are necessary and do simple run
1295 		 * length encoding.  Do not be too efficent, we need to change
1296 		 * the visibility per outline level or the collapse state
1297 		 * change in colrow_set_visibility is missed. */
1298 		if (cri->outline_level < depth) {
1299 			if (cri->visible)
1300 				continue;
1301 			if (show_prev && prev != NULL && prev->last == (i-1) &&
1302 			    tmp == prev_outline) {
1303 				prev->last = i;
1304 				continue;
1305 			}
1306 			prev = g_new (ColRowIndex, 1);
1307 			prev->first = prev->last = i;
1308 			*show = g_slist_prepend (*show, prev);
1309 			show_prev = TRUE;
1310 		} else {
1311 			if (!cri->visible)
1312 				continue;
1313 			if (!show_prev && prev != NULL && prev->last == (i-1) &&
1314 			    tmp == prev_outline) {
1315 				prev->last = i;
1316 				continue;
1317 			}
1318 			prev = g_new (ColRowIndex, 1);
1319 			prev->first = prev->last = i;
1320 			*hide = g_slist_prepend (*hide, prev);
1321 			show_prev = FALSE;
1322 		}
1323 	}
1324 
1325 	*show = g_slist_reverse (*show);
1326 	*hide = g_slist_reverse (*hide);
1327 }
1328