ssdiff: move comparison engine into its own file.
[gnumeric.git] / src / sheet-style.c
blob3f40ee9c967556359490153e9359eb5de61241a9
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * sheet-style.c: storage mechanism for styles and eventually cells.
5 * Copyright (C) 2000-2006 Jody Goldberg (jody@gnome.org)
6 * Copyright 2013 Morten Welinder (terra@gnome.org)
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) version 3.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
21 * USA
24 #include <gnumeric-config.h>
25 #include "sheet-style.h"
26 #include "gnm-style-impl.h"
27 #include "ranges.h"
28 #include "sheet.h"
29 #include "expr.h"
30 #include "style.h"
31 #include "style-border.h"
32 #include "style-color.h"
33 #include "style-conditions.h"
34 #include "parse-util.h"
35 #include "cell.h"
36 #include "gutils.h"
37 #include <goffice/goffice.h>
38 #include <glib/gi18n-lib.h>
39 #include <string.h>
40 #include <math.h>
42 #define USE_TILE_POOLS 0
44 /* ------------------------------------------------------------------------- */
47 * This is, essentially, an std::multiset implementation for the style hash.
48 * Note, however, that sh_lookup is based on gnm_style_equal, not gnm_style_eq.
50 typedef GHashTable GnmStyleHash;
52 #if 0
53 /* This is a really crummy hash -- except for forcing collisions. */
54 #define gnm_style_hash(st) 0
55 #endif
57 static void
58 sh_remove (GnmStyleHash *h, GnmStyle *st)
60 guint32 hv = gnm_style_hash (st);
61 GSList *l = g_hash_table_lookup (h, GUINT_TO_POINTER (hv));
63 g_return_if_fail (l != NULL);
65 if (l->data == st) {
66 GSList *next = l->next;
67 if (next) {
68 /* We're removing the first of several elements. */
69 l->next = NULL;
70 g_hash_table_replace (h, GUINT_TO_POINTER (hv), next);
71 } else {
72 /* We're removing the last element. */
73 g_hash_table_remove (h, GUINT_TO_POINTER (hv));
75 } else {
76 /* We're removing an element that isn't first. */
77 l = g_slist_remove (l, st);
81 static GnmStyle *
82 sh_lookup (GnmStyleHash *h, GnmStyle *st)
84 guint32 hv = gnm_style_hash (st);
85 GSList *l = g_hash_table_lookup (h, GUINT_TO_POINTER (hv));
86 while (l) {
87 GnmStyle *st2 = l->data;
88 /* NOTE: This uses gnm_style_equal, not gnm_style_eq. */
89 if (gnm_style_equal (st, st2))
90 return st2;
91 l = l->next;
93 return NULL;
96 static void
97 sh_insert (GnmStyleHash *h, GnmStyle *st)
99 GSList *s = g_slist_prepend (NULL, st);
100 guint32 hv = gnm_style_hash (st);
101 GSList *l = g_hash_table_lookup (h, GUINT_TO_POINTER (hv));
102 if (l) {
103 s->next = l->next;
104 l->next = s;
105 } else {
106 g_hash_table_insert (h, GUINT_TO_POINTER (hv), s);
110 static GSList *
111 sh_all_styles (GnmStyleHash *h)
113 GHashTableIter iter;
114 gpointer value;
115 GSList *res = NULL;
117 g_hash_table_iter_init (&iter, h);
118 while (g_hash_table_iter_next (&iter, NULL, &value)) {
119 GSList *l = value;
120 for (; l; l = l->next)
121 res = g_slist_prepend (res, l->data);
124 return res;
127 static GnmStyleHash *
128 sh_create (void)
130 return g_hash_table_new_full (g_direct_hash, g_direct_equal,
131 NULL, (GDestroyNotify)g_slist_free);
134 static void
135 sh_destroy (GnmStyleHash *h)
137 g_hash_table_destroy (h);
140 /* ------------------------------------------------------------------------- */
142 typedef union _CellTile CellTile;
143 struct _GnmSheetStyleData {
145 * style_hash is a set of all styles used by this sheet. These
146 * styles are all linked.
148 * We always re-use styles from here when we can, but there can
149 * still be duplicates. This happens when styles are changed
150 * while they are in the hash. For example, this happens when
151 * an expression used by a validation style changes due to
152 * row/col insert/delete.
154 GnmStyleHash *style_hash;
156 CellTile *styles;
157 GnmStyle *default_style;
158 GnmColor *auto_pattern_color;
161 static gboolean debug_style_optimize;
163 typedef struct {
164 GnmSheetSize const *ss;
165 gboolean recursion;
166 } CellTileOptimize;
168 static void
169 cell_tile_optimize (CellTile **tile, int level, CellTileOptimize *data,
170 int ccol, int crow);
174 * sheet_style_unlink
175 * For internal use only
177 void
178 sheet_style_unlink (Sheet *sheet, GnmStyle *st)
180 if (sheet->style_data->style_hash)
181 sh_remove (sheet->style_data->style_hash, st);
185 * sheet_style_find:
186 * @sheet: (transfer full): the sheet
187 * @st: a style
189 * Looks up a style from the sheets collection. Linking if necessary.
190 * ABSORBS the reference and adds a link.
192 * Returns: (transfer full): the new style.
194 GnmStyle *
195 sheet_style_find (Sheet const *sheet, GnmStyle *s)
197 GnmStyle *res;
198 res = sh_lookup (sheet->style_data->style_hash, s);
199 if (res != NULL) {
200 gnm_style_link (res);
201 gnm_style_unref (s);
202 return res;
205 s = gnm_style_link_sheet (s, (Sheet *)sheet);
207 /* Retry the lookup in case "s" changed. See #585178. */
208 res = sh_lookup (sheet->style_data->style_hash, s);
209 if (res != NULL) {
210 gnm_style_link (res);
212 * We are abandoning the linking here. We cannot use
213 * gnm_style_unlink as that would call sheet_style_unlink
214 * and thus remove "res" from the hash.
216 s->link_count = 0;
217 s->linked_sheet = NULL;
218 gnm_style_unref (s);
220 return res;
223 sh_insert (sheet->style_data->style_hash, s);
224 return s;
227 /* Place holder until I merge in the new styles too */
228 static void
229 pstyle_set_border (GnmStyle *st, GnmBorder *border,
230 GnmStyleBorderLocation side)
232 gnm_style_set_border (st,
233 GNM_STYLE_BORDER_LOCATION_TO_STYLE_ELEMENT (side),
234 gnm_style_border_ref (border));
237 /* Amortize the cost of applying a partial style over a large region
238 * by caching and rereferencing the merged result for repeated styles.
240 typedef struct {
241 GnmStyle *new_style;
242 GnmStyle *pstyle;
243 GHashTable *cache;
244 Sheet *sheet;
245 } ReplacementStyle;
247 static void
248 rstyle_ctor_style (ReplacementStyle *res, GnmStyle *new_style, Sheet *sheet)
250 res->sheet = sheet;
251 res->new_style = sheet_style_find (sheet, new_style);
252 res->pstyle = NULL;
253 res->cache = NULL;
256 static void
257 rstyle_ctor_pstyle (ReplacementStyle *res, GnmStyle *pstyle, Sheet *sheet)
259 res->sheet = sheet;
260 res->new_style = NULL;
261 res->pstyle = pstyle;
262 res->cache = g_hash_table_new (g_direct_hash, g_direct_equal);
265 static void
266 cb_style_unlink (gpointer key, gpointer value, G_GNUC_UNUSED gpointer user_data)
268 gnm_style_unlink ((GnmStyle *)key);
269 gnm_style_unlink ((GnmStyle *)value);
272 static void
273 rstyle_dtor (ReplacementStyle *rs)
275 if (rs->cache != NULL) {
276 g_hash_table_foreach (rs->cache, cb_style_unlink, NULL);
277 g_hash_table_destroy (rs->cache);
278 rs->cache = NULL;
280 if (rs->new_style != NULL) {
281 gnm_style_unlink (rs->new_style);
282 rs->new_style = NULL;
284 if (rs->pstyle != NULL) {
285 gnm_style_unref (rs->pstyle);
286 rs->pstyle = NULL;
291 * rstyle_apply: Utility routine that is at the core of applying partial
292 * styles or storing complete styles. It will eventually be smarter
293 * and will maintain the cache of styles associated with each sheet
295 static void
296 rstyle_apply (GnmStyle **old, ReplacementStyle *rs, GnmRange const *r)
298 GnmStyle *s;
299 g_return_if_fail (old != NULL);
300 g_return_if_fail (rs != NULL);
302 if (rs->pstyle != NULL) {
303 /* Cache the merged styles keeping a reference to the originals
304 * just in case all instances change.
306 s = g_hash_table_lookup (rs->cache, *old);
307 if (s == NULL) {
308 GnmStyle *tmp = gnm_style_new_merged (*old, rs->pstyle);
309 s = sheet_style_find (rs->sheet, tmp);
310 gnm_style_link (*old);
311 g_hash_table_insert (rs->cache, *old, s);
313 } else
314 s = rs->new_style;
316 if (*old != s) {
317 if (*old) {
318 gnm_style_unlink_dependents (*old, r);
319 gnm_style_unlink (*old);
322 gnm_style_link_dependents (s, r);
323 gnm_style_link (s);
325 *old = s;
329 void
330 sheet_style_clear_style_dependents (Sheet *sheet, GnmRange const *r)
332 GSList *styles = sh_all_styles (sheet->style_data->style_hash);
333 g_slist_foreach (styles,
334 (GFunc)gnm_style_unlink_dependents,
335 (gpointer)r);
336 g_slist_free (styles);
340 /****************************************************************************/
342 /* If you change this, change the tile_{widths,heights} here
343 * and GNM_MAX_COLS and GNM_MAX_ROWS in gnumeric.h */
344 #define TILE_TOP_LEVEL 6
346 #define TILE_SIZE_COL 8
347 #define TILE_SIZE_ROW 16
349 typedef enum {
350 TILE_UNDEFINED = -1,
351 TILE_SIMPLE = 0,
352 TILE_COL = 1,
353 TILE_ROW = 2,
354 TILE_MATRIX = 3,
355 TILE_PTR_MATRIX = 4
356 } CellTileType;
357 static int const tile_size[/*type*/] = {
358 1, /* TILE_SIMPLE */
359 TILE_SIZE_COL, /* TILE_COL */
360 TILE_SIZE_ROW, /* TILE_ROW */
361 TILE_SIZE_COL * TILE_SIZE_ROW /* TILE_MATRIX */
363 static int const tile_col_count[/*type*/] = {
364 1, /* TILE_SIMPLE */
365 TILE_SIZE_COL, /* TILE_COL */
366 1, /* TILE_ROW */
367 TILE_SIZE_COL, /* TILE_MATRIX */
368 TILE_SIZE_COL /* TILE_PTR_MATRIX */
370 static int const tile_row_count[/*type*/] = {
371 1, /* TILE_SIMPLE */
372 1, /* TILE_COL */
373 TILE_SIZE_ROW, /* TILE_ROW */
374 TILE_SIZE_ROW, /* TILE_MATRIX */
375 TILE_SIZE_ROW /* TILE_PTR_MATRIX */
377 static const char * const tile_type_str[/*type*/] = {
378 "simple", "col", "row", "matrix", "ptr-matrix"
380 static int const tile_widths[/*level*/] = {
382 TILE_SIZE_COL,
383 TILE_SIZE_COL * TILE_SIZE_COL,
384 TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL,
385 TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL,
386 TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL,
387 TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL,
388 TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL
390 static int const tile_heights[/*level*/] = {
392 TILE_SIZE_ROW,
393 TILE_SIZE_ROW * TILE_SIZE_ROW,
394 TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW,
395 TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW,
396 TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW,
397 TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW,
398 TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW
401 typedef struct {
402 CellTileType const type;
403 GnmStyle *style[1];
404 } CellTileStyleSimple;
405 typedef struct {
406 CellTileType const type;
407 GnmStyle *style[TILE_SIZE_COL];
408 } CellTileStyleCol;
409 typedef struct {
410 CellTileType const type;
411 GnmStyle *style[TILE_SIZE_ROW];
412 } CellTileStyleRow;
413 typedef struct {
414 CellTileType const type;
415 GnmStyle *style[TILE_SIZE_COL * TILE_SIZE_ROW];
416 } CellTileStyleMatrix;
417 typedef struct {
418 CellTileType const type;
419 CellTile *ptr[TILE_SIZE_COL * TILE_SIZE_ROW];
420 } CellTilePtrMatrix;
422 union _CellTile {
423 CellTileType const type;
424 CellTileStyleSimple style_any;
425 CellTileStyleSimple style_simple;
426 CellTileStyleCol style_col;
427 CellTileStyleRow style_row;
428 CellTileStyleMatrix style_matrix;
429 CellTilePtrMatrix ptr_matrix;
432 static int active_sheet_count;
433 #if USE_TILE_POOLS
434 static GOMemChunk *tile_pools[5];
435 #define CHUNK_ALLOC(T,ctt) ((T*)go_mem_chunk_alloc (tile_pools[(ctt)]))
436 #define CHUNK_FREE(ctt,v) go_mem_chunk_free (tile_pools[(ctt)], (v))
437 #else
438 static const size_t tile_type_sizeof[5] = {
439 sizeof (CellTileStyleSimple),
440 sizeof (CellTileStyleCol),
441 sizeof (CellTileStyleRow),
442 sizeof (CellTileStyleMatrix),
443 sizeof (CellTilePtrMatrix)
445 static int tile_allocations = 0;
446 #if 1
447 #define CHUNK_ALLOC(T,ctt) (tile_allocations++, (T*)g_slice_alloc (tile_type_sizeof[(ctt)]))
448 #define CHUNK_FREE(ctt,v) (tile_allocations--, g_slice_free1 (tile_type_sizeof[(ctt)], (v)))
449 #else
450 #define CHUNK_ALLOC(T,ctt) (tile_allocations++, (T*)g_malloc (tile_type_sizeof[(ctt)]))
451 #define CHUNK_FREE(ctt,v) (tile_allocations--, g_free ((v)))
452 #endif
453 #endif
457 * Destroy a CellTile (recursively if needed). This will unlink all the
458 * styles in it. We do _not_ unlink style dependents here. That is done
459 * only in rstyle_apply.
461 static void
462 cell_tile_dtor (CellTile *tile)
464 CellTileType t;
466 g_return_if_fail (tile != NULL);
468 t = tile->type;
469 if (t == TILE_PTR_MATRIX) {
470 int i = TILE_SIZE_COL * TILE_SIZE_ROW;
471 while (--i >= 0) {
472 cell_tile_dtor (tile->ptr_matrix.ptr[i]);
473 tile->ptr_matrix.ptr[i] = NULL;
475 } else if (TILE_SIMPLE <= t && t <= TILE_MATRIX) {
476 int i = tile_size[t];
477 while (--i >= 0) {
478 gnm_style_unlink (tile->style_any.style[i]);
479 tile->style_any.style[i] = NULL;
481 } else {
482 g_return_if_fail (FALSE); /* don't free anything */
485 *((CellTileType *)&(tile->type)) = TILE_UNDEFINED; /* poison it */
486 CHUNK_FREE (t, tile);
489 static CellTile *
490 cell_tile_style_new (GnmStyle *style, CellTileType t)
492 CellTile *res = CHUNK_ALLOC (CellTile, t);
493 *((CellTileType *)&(res->type)) = t;
495 if (style != NULL) {
496 int i = tile_size[t];
497 gnm_style_link_multiple (style, i);
498 while (--i >= 0)
499 res->style_any.style[i] = style;
502 return res;
505 static CellTile *
506 cell_tile_ptr_matrix_new (CellTile *t)
508 CellTilePtrMatrix *res;
510 g_return_val_if_fail (t != NULL, NULL);
511 g_return_val_if_fail (TILE_SIMPLE <= t->type &&
512 TILE_MATRIX >= t->type, NULL);
514 res = CHUNK_ALLOC (CellTilePtrMatrix, TILE_PTR_MATRIX);
515 *((CellTileType *)&(res->type)) = TILE_PTR_MATRIX;
517 /* TODO:
518 * If we wanted to get fancy we could use self similarity to decrease
519 * the number of subtiles. However, this would increase the cost of
520 * applying changes later so I'm not sure it is worth the effort.
522 switch (t->type) {
523 case TILE_SIMPLE: {
524 int i = TILE_SIZE_COL * TILE_SIZE_ROW;
525 while (--i >= 0)
526 res->ptr[i] = cell_tile_style_new (
527 t->style_simple.style[0], TILE_SIMPLE);
528 break;
530 case TILE_COL: {
531 int i, r, c;
532 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r)
533 for (c = 0 ; c < TILE_SIZE_COL ; ++c)
534 res->ptr[i++] = cell_tile_style_new (
535 t->style_col.style[c], TILE_SIMPLE);
536 break;
538 case TILE_ROW: {
539 int i, r, c;
540 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r)
541 for (c = 0 ; c < TILE_SIZE_COL ; ++c)
542 res->ptr[i++] = cell_tile_style_new (
543 t->style_row.style[r], TILE_SIMPLE);
544 break;
546 case TILE_MATRIX: {
547 int i = TILE_SIZE_COL * TILE_SIZE_ROW;
548 while (--i >= 0)
549 res->ptr[i] = cell_tile_style_new (
550 t->style_matrix.style[i], TILE_SIMPLE);
551 break;
553 default: ;
556 return (CellTile *)res;
559 static CellTile *
560 cell_tile_matrix_set (CellTile *t)
562 int r, c;
563 CellTileStyleMatrix *res;
565 g_return_val_if_fail (t != NULL, NULL);
566 g_return_val_if_fail (TILE_SIMPLE <= t->type &&
567 TILE_MATRIX >= t->type, NULL);
569 if (t->type == TILE_MATRIX)
570 return t;
572 res = (CellTileStyleMatrix *)cell_tile_style_new (NULL, TILE_MATRIX);
574 switch (t->type) {
575 case TILE_SIMPLE: {
576 GnmStyle *tmp = t->style_simple.style[0];
577 int i = TILE_SIZE_COL * TILE_SIZE_ROW;
578 gnm_style_link_multiple (tmp, i);
579 while (--i >= 0)
580 res->style[i] = tmp;
581 break;
584 case TILE_COL: {
585 int i = 0;
586 for (r = 0; r < TILE_SIZE_ROW; ++r)
587 for (c = 0; c < TILE_SIZE_COL; ++c)
588 gnm_style_link (res->style[i++] =
589 t->style_col.style[c]);
590 break;
593 case TILE_ROW: {
594 int i = 0;
595 for (r = 0; r < TILE_SIZE_ROW; ++r) {
596 GnmStyle *tmp = t->style_row.style[r];
597 gnm_style_link_multiple (tmp, TILE_SIZE_COL);
598 for (c = 0; c < TILE_SIZE_COL; ++c)
599 res->style[i++] = tmp;
601 break;
604 case TILE_MATRIX:
605 default:
606 g_assert_not_reached();
609 cell_tile_dtor (t);
611 return (CellTile *)res;
614 /****************************************************************************/
616 static void
617 sheet_style_sanity_check (void)
619 unsigned c, r;
620 int i;
622 for (c = 1, i = 0; i <= TILE_TOP_LEVEL; i++) {
623 g_assert (c < G_MAXUINT / TILE_SIZE_COL);
624 c *= TILE_SIZE_COL;
626 g_assert (c >= GNM_MAX_COLS);
628 for (r = 1, i = 0; i <= TILE_TOP_LEVEL; i++) {
629 g_assert (r < G_MAXUINT / TILE_SIZE_COL);
630 r *= TILE_SIZE_ROW;
632 g_assert (r >= GNM_MAX_ROWS);
634 g_assert (G_N_ELEMENTS (tile_heights) > TILE_TOP_LEVEL + 1);
636 g_assert (G_N_ELEMENTS (tile_widths) > TILE_TOP_LEVEL + 1);
639 static void
640 sheet_style_init_size (Sheet *sheet, int cols, int rows)
642 GnmStyle *default_style;
643 int lc = 0, lr = 0, w = TILE_SIZE_COL, h = TILE_SIZE_ROW;
645 while (w < cols) {
646 w *= TILE_SIZE_COL;
647 lc++;
649 while (h < rows) {
650 h *= TILE_SIZE_ROW;
651 lr++;
653 sheet->tile_top_level = MAX (lc, lr);
655 if (active_sheet_count++ == 0) {
656 #if USE_TILE_POOLS
657 tile_pools[TILE_SIMPLE] =
658 go_mem_chunk_new ("simple tile pool",
659 sizeof (CellTileStyleSimple),
660 16 * 1024 - 128);
661 tile_pools[TILE_COL] =
662 go_mem_chunk_new ("column tile pool",
663 sizeof (CellTileStyleCol),
664 16 * 1024 - 128);
665 tile_pools[TILE_ROW] =
666 go_mem_chunk_new ("row tile pool",
667 sizeof (CellTileStyleRow),
668 16 * 1024 - 128);
669 tile_pools[TILE_MATRIX] =
670 go_mem_chunk_new ("matrix tile pool",
671 sizeof (CellTileStyleMatrix),
672 MAX (16 * 1024 - 128,
673 100 * sizeof (CellTileStyleMatrix)));
675 /* If this fails one day, just make two pools. */
676 g_assert (sizeof (CellTileStyleMatrix) == sizeof (CellTilePtrMatrix));
677 tile_pools[TILE_PTR_MATRIX] = tile_pools[TILE_MATRIX];
678 #endif
681 sheet->style_data = g_new (GnmSheetStyleData, 1);
682 sheet->style_data->style_hash = sh_create ();
684 sheet->style_data->auto_pattern_color = style_color_auto_pattern ();
686 default_style = gnm_style_new_default ();
687 #if 0
688 /* We can not do this, XL creates full page charts with background
689 * 'none' by default. Then displays that as white. */
690 if (sheet->sheet_type == GNM_SHEET_OBJECT) {
691 gnm_style_set_back_color (default_style,
692 gnm_color_new_rgb8 (0x50, 0x50, 0x50));
693 gnm_style_set_pattern (default_style, 1);
695 #endif
696 sheet->style_data->default_style =
697 sheet_style_find (sheet, default_style);
698 sheet->style_data->styles =
699 cell_tile_style_new (sheet->style_data->default_style,
700 TILE_SIMPLE);
703 void
704 sheet_style_init (Sheet *sheet)
706 int cols = gnm_sheet_get_max_cols (sheet);
707 int rows = gnm_sheet_get_max_rows (sheet);
709 debug_style_optimize = gnm_debug_flag ("style-optimize");
711 sheet_style_sanity_check ();
713 sheet_style_init_size (sheet, cols, rows);
716 void
717 sheet_style_resize (Sheet *sheet, int cols, int rows)
719 GnmStyleList *styles, *l;
720 int old_cols = gnm_sheet_get_max_cols (sheet);
721 int old_rows = gnm_sheet_get_max_rows (sheet);
722 GnmRange save_range, new_full;
724 /* Save the style for the surviving area. */
725 range_init (&save_range, 0, 0,
726 MIN (cols, old_cols) - 1, MIN (rows, old_rows) - 1);
727 styles = sheet_style_get_range (sheet, &save_range);
729 /* Build new empty structures. */
730 sheet_style_shutdown (sheet);
731 sheet_style_init_size (sheet, cols, rows);
733 /* Reapply styles. */
734 range_init (&new_full, 0, 0, cols - 1, rows - 1);
735 for (l = styles; l; l = l->next) {
736 GnmStyleRegion const *sr = l->data;
737 GnmRange const *r = &sr->range;
738 GnmStyle *style = sr->style;
739 GnmRange newr;
740 if (range_intersection (&newr, r, &new_full)) {
741 gnm_style_ref (style);
742 sheet_style_apply_range (sheet, &newr, style);
746 style_list_free (styles);
749 #if USE_TILE_POOLS
750 static void
751 cb_tile_pool_leak (gpointer data, gpointer user)
753 CellTile *tile = data;
754 g_printerr ("Leaking tile at %p.\n", (void *)tile);
756 #endif
758 void
759 sheet_style_shutdown (Sheet *sheet)
761 GnmStyleHash *table;
762 GnmRange r;
764 g_return_if_fail (IS_SHEET (sheet));
765 g_return_if_fail (sheet->style_data != NULL);
768 * Clear all styles. This is an easy way to clear out all
769 * style dependencies.
771 range_init_full_sheet (&r, sheet);
772 sheet_style_set_range (sheet, &r, sheet_style_default (sheet));
774 cell_tile_dtor (sheet->style_data->styles);
775 sheet->style_data->styles = NULL;
777 sheet->style_data->default_style = NULL;
779 /* Clear the pointer to the hash BEFORE clearing and add a test in
780 * sheet_style_unlink. If we don't then it is possible/probable that
781 * unlinking the styles will attempt to remove them from the hash while
782 * we are walking it.
784 table = sheet->style_data->style_hash;
785 sheet->style_data->style_hash = NULL;
786 g_slist_free_full (sh_all_styles (table),
787 (GDestroyNotify)gnm_style_unlink);
788 sh_destroy (table);
789 style_color_unref (sheet->style_data->auto_pattern_color);
791 g_free (sheet->style_data);
792 sheet->style_data = NULL;
794 if (--active_sheet_count == 0) {
795 #if USE_TILE_POOLS
796 go_mem_chunk_foreach_leak (tile_pools[TILE_SIMPLE],
797 cb_tile_pool_leak, NULL);
798 go_mem_chunk_destroy (tile_pools[TILE_SIMPLE], FALSE);
799 tile_pools[TILE_SIMPLE] = NULL;
801 go_mem_chunk_foreach_leak (tile_pools[TILE_COL],
802 cb_tile_pool_leak, NULL);
803 go_mem_chunk_destroy (tile_pools[TILE_COL], FALSE);
804 tile_pools[TILE_COL] = NULL;
806 go_mem_chunk_foreach_leak (tile_pools[TILE_ROW],
807 cb_tile_pool_leak, NULL);
808 go_mem_chunk_destroy (tile_pools[TILE_ROW], FALSE);
809 tile_pools[TILE_ROW] = NULL;
811 go_mem_chunk_foreach_leak (tile_pools[TILE_MATRIX],
812 cb_tile_pool_leak, NULL);
813 go_mem_chunk_destroy (tile_pools[TILE_MATRIX], FALSE);
814 tile_pools[TILE_MATRIX] = NULL;
816 /* If this fails one day, just make two pools. */
817 g_assert (sizeof (CellTileStyleMatrix) == sizeof (CellTilePtrMatrix));
818 tile_pools[TILE_PTR_MATRIX] = NULL;
819 #else
820 if (tile_allocations)
821 g_printerr ("Leaking %d style tiles.\n", tile_allocations);
822 #endif
827 * sheet_style_set_auto_pattern_color:
828 * @sheet: The sheet
829 * @grid_color: (transfer full): The color
831 * Set the color for rendering auto colored patterns in this sheet.
832 * Absorbs a reference to @pattern_color;
834 void
835 sheet_style_set_auto_pattern_color (Sheet *sheet, GnmColor *pattern_color)
837 g_return_if_fail (IS_SHEET (sheet));
838 g_return_if_fail (sheet->style_data != NULL);
840 style_color_unref (sheet->style_data->auto_pattern_color);
841 sheet->style_data->auto_pattern_color = gnm_color_new_auto (pattern_color->go_color);
842 style_color_unref (pattern_color);
846 * sheet_style_get_auto_pattern_color:
847 * @sheet: the sheet
849 * Returns: (transfer full): the color for rendering auto colored patterns
850 * in this sheet.
852 GnmColor *
853 sheet_style_get_auto_pattern_color (Sheet const *sheet)
855 GnmColor *sc;
856 g_return_val_if_fail (IS_SHEET (sheet), style_color_black ());
857 g_return_val_if_fail (sheet->style_data != NULL, style_color_black ());
858 g_return_val_if_fail (sheet->style_data->auto_pattern_color != NULL,
859 style_color_black ());
861 sc = sheet->style_data->auto_pattern_color;
862 style_color_ref (sc);
864 return sc;
868 * sheet_style_update_grid_color:
870 * This function updates the color of gnm_style_border_none when the sheet to be
871 * rendered is known. gnm_style_border_none tells how to render the
872 * grid. Because the grid color may be different for different sheets, the
873 * functions which render the grid call this function first. The rule for
874 * selecting the grid color, which is the same as in Excel, is: - if the
875 * auto pattern color is default (which is black), the grid color is gray,
876 * as returned by style_color_grid (). - otherwise, the auto pattern color
877 * is used for the grid.
879 void
880 sheet_style_update_grid_color (Sheet const *sheet)
882 GnmColor *default_auto = style_color_auto_pattern ();
883 GnmColor *sheet_auto = sheet_style_get_auto_pattern_color (sheet);
884 GnmColor *grid_color = style_color_grid ();
885 GnmColor *new_color;
887 new_color = (style_color_equal (default_auto, sheet_auto)
888 ? grid_color : sheet_auto);
890 /* Do nothing if we already have the right color */
891 if (gnm_style_border_none()->color != new_color) {
892 style_color_ref (new_color); /* none_set eats the ref */
893 gnm_style_border_none_set_color (new_color);
895 style_color_unref (grid_color);
896 style_color_unref (sheet_auto);
897 style_color_unref (default_auto);
900 /****************************************************************************/
902 static gboolean
903 tile_is_uniform (CellTile const *tile)
905 const int s = tile_size[tile->type];
906 GnmStyle const *st = tile->style_any.style[0];
907 int i;
909 for (i = 1; i < s; i++)
910 if (tile->style_any.style[i] != st)
911 return FALSE;
913 return TRUE;
916 static void
917 vector_apply_pstyle (CellTile *tile, ReplacementStyle *rs,
918 int cc, int cr, int level, GnmRange const *indic)
920 const CellTileType type = tile->type;
921 const int ncols = tile_col_count[type];
922 const int nrows = tile_row_count[type];
923 const int w1 = tile_widths[level + 1] / ncols;
924 const int h1 = tile_heights[level + 1] / nrows;
925 const int fcol = indic->start.col;
926 const int frow = indic->start.row;
927 const int lcol = MIN (ncols - 1, indic->end.col);
928 const int lrow = MIN (nrows - 1, indic->end.row);
929 GnmSheetSize const *ss = gnm_sheet_get_size (rs->sheet);
930 int r, c;
931 GnmRange rng;
933 for (r = frow; r <= lrow; r++) {
934 GnmStyle **st = tile->style_any.style + ncols * r;
935 rng.start.row = cr + h1 * r;
936 rng.end.row = MIN (rng.start.row + (h1 - 1),
937 ss->max_rows - 1);
938 for (c = fcol; c <= lcol; c++) {
939 rng.start.col = cc + w1 * c;
940 rng.end.col = MIN (rng.start.col + (w1 - 1),
941 ss->max_cols - 1);
942 rstyle_apply (st + c, rs, &rng);
948 * Determine whether before applying a style in the area of apply_to
949 * one needs to split the tile column-wise.
951 * If FALSE is returned then the tile need to be split to a TILE_PTR_MATRIX
952 * because the current level is not fine-grained enough.
954 * If TRUE is returned, TILE_SIMPLE needs to be split into TILE_COL and
955 * TILE_ROW needs to be split into TILE_MATRIX. TILE_COL and TILE_MATRIX
956 * should be kept. In indic, the inclusive post-split indicies of the
957 * range will be returned.
959 * If apply_to covers the entire tile, TRUE will be returned and the judgement
960 * on splitting above should be ignored. The indices in indic will be as-if
961 * the split was done.
963 static gboolean
964 col_indicies (int corner_col, int w, GnmRange const *apply_to,
965 GnmRange *indec)
967 int i, tmp;
969 i = apply_to->start.col - corner_col;
970 if (i <= 0)
971 indec->start.col = 0;
972 else {
973 tmp = i / w;
974 if (i != tmp * w)
975 return FALSE;
976 indec->start.col = tmp;
979 i = 1 + apply_to->end.col - corner_col;
980 tmp = i / w;
981 if (tmp >= TILE_SIZE_COL)
982 indec->end.col = TILE_SIZE_COL - 1;
983 else {
984 if (i != tmp * w)
985 return FALSE;
986 indec->end.col = tmp - 1;
989 return TRUE;
992 /* See docs for col_indicies. Swap cols and rows. */
993 static gboolean
994 row_indicies (int corner_row, int h, GnmRange const *apply_to,
995 GnmRange *indic)
997 int i, tmp;
999 i = apply_to->start.row - corner_row;
1000 if (i <= 0)
1001 indic->start.row = 0;
1002 else {
1003 int tmp = i / h;
1004 if (i != tmp * h)
1005 return FALSE;
1006 indic->start.row = tmp;
1009 i = 1 + apply_to->end.row - corner_row;
1010 tmp = i / h;
1011 if (tmp >= TILE_SIZE_ROW)
1012 indic->end.row = TILE_SIZE_ROW - 1;
1013 else {
1014 if (i != tmp * h)
1015 return FALSE;
1016 indic->end.row = tmp - 1;
1019 return TRUE;
1023 * cell_tile_apply: This is the primary logic for making changing areas in the
1024 * tree. It could be further optimised if it becomes a bottle neck.
1026 static void
1027 cell_tile_apply (CellTile **tile, int level,
1028 int corner_col, int corner_row,
1029 GnmRange const *apply_to,
1030 ReplacementStyle *rs)
1032 int const width = tile_widths[level+1];
1033 int const height = tile_heights[level+1];
1034 int const w = tile_widths[level];
1035 int const h = tile_heights[level];
1036 gboolean const full_width = (apply_to->start.col <= corner_col &&
1037 apply_to->end.col >= (corner_col+width-1));
1038 gboolean const full_height = (apply_to->start.row <= corner_row &&
1039 apply_to->end.row >= (corner_row+height-1));
1040 GnmRange indic;
1041 CellTileType type;
1042 int c, r, i;
1044 g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
1045 g_return_if_fail (tile != NULL);
1046 g_return_if_fail (*tile != NULL);
1048 type = (*tile)->type;
1049 g_return_if_fail (TILE_SIMPLE <= type && type <= TILE_PTR_MATRIX);
1051 /* applying the same style to part of a simple-tile is a nop */
1052 if (type == TILE_SIMPLE &&
1053 (*tile)->style_simple.style[0] == rs->new_style)
1054 return;
1057 * Indices for the whole tile assuming a split to matrix.
1058 * We can still use these indices if we don't split either way.
1060 indic.start.col = 0;
1061 indic.start.row = 0;
1062 indic.end.col = TILE_SIZE_COL - 1;
1063 indic.end.row = TILE_SIZE_ROW - 1;
1065 if (type == TILE_PTR_MATRIX)
1066 goto drill_down;
1067 else if (full_width && full_height)
1068 goto apply;
1069 else if (full_height) {
1070 if (!col_indicies (corner_col, w, apply_to, &indic))
1071 goto split_to_ptr_matrix;
1073 switch (type) {
1074 case TILE_SIMPLE: {
1075 CellTile *res;
1076 type = TILE_COL;
1077 res = cell_tile_style_new (
1078 (*tile)->style_simple.style[0],
1079 type);
1080 cell_tile_dtor (*tile);
1081 *tile = res;
1082 /* Fall through */
1084 case TILE_COL:
1085 case TILE_MATRIX:
1086 goto apply;
1087 case TILE_ROW:
1088 goto split_to_matrix;
1089 default:
1090 g_assert_not_reached ();
1092 } else if (full_width) {
1093 if (!row_indicies (corner_row, h, apply_to, &indic))
1094 goto split_to_ptr_matrix;
1095 switch (type) {
1096 case TILE_SIMPLE: {
1097 CellTile *res;
1099 type = TILE_ROW;
1100 res = cell_tile_style_new (
1101 (*tile)->style_simple.style[0],
1102 type);
1103 cell_tile_dtor (*tile);
1104 *tile = res;
1105 /* Fall through */
1107 case TILE_ROW:
1108 case TILE_MATRIX:
1109 goto apply;
1110 case TILE_COL:
1111 goto split_to_matrix;
1112 default:
1113 g_assert_not_reached ();
1115 } else {
1116 if (col_indicies (corner_col, w, apply_to, &indic) &&
1117 row_indicies (corner_row, h, apply_to, &indic))
1118 goto split_to_matrix;
1119 else
1120 goto split_to_ptr_matrix;
1123 g_assert_not_reached ();
1125 split_to_matrix:
1126 *tile = cell_tile_matrix_set (*tile);
1128 apply:
1129 vector_apply_pstyle (*tile, rs, corner_col, corner_row, level, &indic);
1131 try_optimize:
1133 CellTileOptimize cto;
1134 cto.ss = gnm_sheet_get_size (rs->sheet);
1135 cto.recursion = FALSE;
1136 cell_tile_optimize (tile, level, &cto, corner_col, corner_row);
1138 return;
1140 split_to_ptr_matrix:
1142 * We get here when apply_to's corners are not on a TILE_MATRIX grid.
1143 * Split to pointer matrix whose element tiles will have a finer grid.
1145 g_return_if_fail (type != TILE_PTR_MATRIX);
1147 CellTile *res = cell_tile_ptr_matrix_new (*tile);
1148 cell_tile_dtor (*tile);
1149 *tile = res;
1150 type = TILE_PTR_MATRIX;
1153 drill_down:
1154 g_return_if_fail (type == TILE_PTR_MATRIX);
1155 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r, i += TILE_SIZE_COL) {
1156 int const cr = corner_row + h*r;
1157 if (cr > apply_to->end.row)
1158 break;
1159 if ((cr + h) <= apply_to->start.row)
1160 continue;
1162 for (c = 0 ; c < TILE_SIZE_COL ; ++c) {
1163 int const cc = corner_col + w*c;
1164 if (cc > apply_to->end.col)
1165 break;
1166 if ((cc + w) <= apply_to->start.col)
1167 continue;
1169 cell_tile_apply ((*tile)->ptr_matrix.ptr + i + c,
1170 level - 1, cc, cr, apply_to, rs);
1173 goto try_optimize;
1176 /* Handler for foreach_tile.
1178 * "width" and "height" refer to tile size which may extend beyond
1179 * the range supplied to foreach_tile and even beyond the sheet.
1181 typedef void (*ForeachTileFunc) (GnmStyle *style,
1182 int corner_col, int corner_row,
1183 int width, int height,
1184 GnmRange const *apply_to, gpointer user);
1185 static void
1186 foreach_tile_r (CellTile *tile, int level,
1187 int corner_col, int corner_row,
1188 GnmRange const *apply_to,
1189 ForeachTileFunc handler,
1190 gpointer user)
1192 int const width = tile_widths[level+1];
1193 int const height = tile_heights[level+1];
1194 int const w = tile_widths[level];
1195 int const h = tile_heights[level];
1196 int c, r, i, last;
1198 g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
1199 g_return_if_fail (tile != NULL);
1201 switch (tile->type) {
1202 case TILE_SIMPLE:
1203 handler (tile->style_simple.style[0],
1204 corner_col, corner_row, width, height,
1205 apply_to, user);
1206 break;
1208 case TILE_COL:
1209 if (apply_to != NULL) {
1210 c = (apply_to->start.col - corner_col) / w;
1211 if (c < 0)
1212 c = 0;
1213 last = (apply_to->end.col - corner_col) / w + 1;
1214 if (last > TILE_SIZE_COL)
1215 last = TILE_SIZE_COL;
1216 } else {
1217 c = 0;
1218 last = TILE_SIZE_COL;
1220 for (; c < last ; ++c)
1221 handler (tile->style_col.style[c],
1222 corner_col + c*w, corner_row, w, height,
1223 apply_to, user);
1224 break;
1226 case TILE_ROW:
1227 if (apply_to != NULL) {
1228 r = (apply_to->start.row - corner_row) / h;
1229 if (r < 0)
1230 r = 0;
1231 last = (apply_to->end.row - corner_row) / h + 1;
1232 if (last > TILE_SIZE_ROW)
1233 last = TILE_SIZE_ROW;
1234 } else {
1235 r = 0;
1236 last = TILE_SIZE_ROW;
1238 for (; r < last ; ++r)
1239 handler (tile->style_row.style[r],
1240 corner_col, corner_row + r*h, width, h,
1241 apply_to, user);
1242 break;
1244 case TILE_MATRIX:
1245 case TILE_PTR_MATRIX:
1246 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r, i += TILE_SIZE_COL) {
1247 int const cr = corner_row + h*r;
1248 if (apply_to) {
1249 if (cr > apply_to->end.row)
1250 break;
1251 if ((cr + h) <= apply_to->start.row)
1252 continue;
1255 for (c = 0 ; c < TILE_SIZE_COL ; ++c) {
1256 int const cc = corner_col + w*c;
1257 if (apply_to) {
1258 if (cc > apply_to->end.col)
1259 break;
1260 if ((cc + w) <= apply_to->start.col)
1261 continue;
1264 if (tile->type == TILE_MATRIX) {
1265 handler (tile->style_matrix.style[r*TILE_SIZE_COL+c],
1266 corner_col + c * w,
1267 corner_row + r * h,
1268 w, h, apply_to, user);
1269 } else {
1270 foreach_tile_r (
1271 tile->ptr_matrix.ptr[c + r*TILE_SIZE_COL],
1272 level-1, cc, cr, apply_to, handler, user);
1276 break;
1278 default:
1279 g_warning ("Adaptive Quad Tree corruption !");
1283 static void
1284 foreach_tile (Sheet const *sheet, GnmRange const *apply_to,
1285 ForeachTileFunc handler, gpointer user)
1287 foreach_tile_r (sheet->style_data->styles,
1288 sheet->tile_top_level, 0, 0,
1289 apply_to, handler, user);
1293 * cell_tile_apply_pos: This is an simplified version of cell_tile_apply. It
1294 * does not need all the bells and whistles because it operates on single cells.
1296 static void
1297 cell_tile_apply_pos (CellTile **tile, int level,
1298 int col, int row,
1299 ReplacementStyle *rs)
1301 CellTile *tmp;
1302 CellTileType type;
1303 GnmRange rng;
1305 g_return_if_fail (col >= 0);
1306 g_return_if_fail (col < gnm_sheet_get_max_cols (rs->sheet));
1307 g_return_if_fail (row >= 0);
1308 g_return_if_fail (row < gnm_sheet_get_max_rows (rs->sheet));
1310 range_init (&rng, col, row, col, row);
1312 tail_recursion:
1313 g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
1314 g_return_if_fail (tile != NULL);
1315 g_return_if_fail (*tile != NULL);
1317 tmp = *tile;
1318 type = tmp->type;
1319 g_return_if_fail (TILE_SIMPLE <= type && type <= TILE_PTR_MATRIX);
1321 if (level > 0) {
1322 int const w = tile_widths[level];
1323 int const c = col / w;
1324 int const h = tile_heights[level];
1325 int const r = row / h;
1327 if (type != TILE_PTR_MATRIX) {
1328 /* applying the same style to part of a simple-tile is a nop */
1329 if (type == TILE_SIMPLE &&
1330 (*tile)->style_simple.style[0] == rs->new_style)
1331 return;
1333 tmp = cell_tile_ptr_matrix_new (tmp);
1334 cell_tile_dtor (*tile);
1335 *tile = tmp;
1337 tile = tmp->ptr_matrix.ptr + r * TILE_SIZE_COL + c;
1338 level--;
1339 col -= c*w;
1340 row -= r*h;
1341 goto tail_recursion;
1342 } else if (type != TILE_MATRIX)
1343 *tile = tmp = cell_tile_matrix_set (tmp);
1345 g_return_if_fail (tmp->type == TILE_MATRIX);
1346 rstyle_apply (tmp->style_matrix.style + row * TILE_SIZE_COL + col,
1348 &rng);
1352 * sheet_style_set_range:
1353 * @sheet:
1354 * @range:
1355 * @style: #GnmStyle
1357 * Change the complete style for a region.
1358 * This function absorbs a reference to the new @style.
1360 void
1361 sheet_style_set_range (Sheet *sheet, GnmRange const *range,
1362 GnmStyle *style)
1364 ReplacementStyle rs;
1365 GnmRange r;
1367 g_return_if_fail (IS_SHEET (sheet));
1368 g_return_if_fail (range != NULL);
1370 if (range->start.col > range->end.col ||
1371 range->start.row > range->end.row) {
1372 gnm_style_unref (style);
1373 return;
1376 r = *range;
1377 range_ensure_sanity (&r, sheet);
1379 rstyle_ctor_style (&rs, style, sheet);
1380 cell_tile_apply (&sheet->style_data->styles,
1381 sheet->tile_top_level, 0, 0,
1382 &r, &rs);
1383 rstyle_dtor (&rs);
1387 * sheet_style_apply_col:
1388 * @sheet:
1389 * @col:
1390 * @style: #GnmStyle
1392 * NOTE: This is a simple wrapper for now. When we support col/row styles it
1393 * will make life easier.
1395 * Apply a partial style to a full col.
1396 * The routine absorbs a reference to the partial style.
1398 void
1399 sheet_style_apply_col (Sheet *sheet, int col, GnmStyle *pstyle)
1401 GnmRange r;
1402 range_init_cols (&r, sheet, col, col);
1403 sheet_style_apply_range (sheet, &r, pstyle);
1407 * sheet_style_apply_row:
1408 * @sheet:
1409 * @row:
1410 * @style: #GnmStyle
1412 * NOTE: This is a simple wrapper for now. When we support col/row styles it
1413 * will make life easier.
1415 * Apply a partial style to a full col.
1416 * The routine absorbs a reference to the partial style.
1418 void
1419 sheet_style_apply_row (Sheet *sheet, int row, GnmStyle *pstyle)
1421 GnmRange r;
1422 range_init_rows (&r, sheet, row, row);
1423 sheet_style_apply_range (sheet, &r, pstyle);
1427 * sheet_style_apply_pos:
1428 * @sheet:
1429 * @col:
1430 * @row:
1431 * @style: #GnmStyle
1433 * Apply a partial style to a single cell
1434 * This function absorbs a reference to the new @style.
1436 void
1437 sheet_style_apply_pos (Sheet *sheet, int col, int row,
1438 GnmStyle *pstyle)
1440 ReplacementStyle rs;
1442 g_return_if_fail (IS_SHEET (sheet));
1444 rstyle_ctor_pstyle (&rs, pstyle, sheet);
1445 cell_tile_apply_pos (&sheet->style_data->styles,
1446 sheet->tile_top_level, col, row,
1447 &rs);
1448 rstyle_dtor (&rs);
1451 * sheet_style_set_pos:
1452 * @sheet:
1453 * @col:
1454 * @row:
1455 * @style:
1457 * Change the complete style for a single cell.
1458 * This function absorbs a reference to the new @style.
1460 void
1461 sheet_style_set_pos (Sheet *sheet, int col, int row,
1462 GnmStyle *style)
1464 ReplacementStyle rs;
1466 g_return_if_fail (IS_SHEET (sheet));
1468 rstyle_ctor_style (&rs, style, sheet);
1469 cell_tile_apply_pos (&sheet->style_data->styles,
1470 sheet->tile_top_level, col, row,
1471 &rs);
1472 rstyle_dtor (&rs);
1476 * sheet_style_default:
1477 * @sheet:
1479 * Returns a reference to default style for a sheet.
1481 GnmStyle *
1482 sheet_style_default (Sheet const *sheet)
1484 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1485 g_return_val_if_fail (sheet->style_data != NULL, NULL);
1487 gnm_style_ref (sheet->style_data->default_style);
1488 return sheet->style_data->default_style;
1492 * sheet_style_get:
1493 * @sheet: #Sheet
1494 * @col:
1495 * @row:
1497 * Find the fully qualified style applicable to the specified cellpos.
1498 * Does _not_ add a reference.
1500 GnmStyle const *
1501 sheet_style_get (Sheet const *sheet, int col, int row)
1503 int level = sheet->tile_top_level;
1504 CellTile *tile = sheet->style_data->styles;
1506 while (1) {
1507 int width = tile_widths[level];
1508 int height = tile_heights[level];
1509 int c = col / width;
1510 int r = row / height;
1512 g_return_val_if_fail (tile != NULL, NULL);
1513 g_return_val_if_fail (0 <= c && c < TILE_SIZE_COL, NULL);
1514 g_return_val_if_fail (0 <= r && r < TILE_SIZE_ROW, NULL);
1516 switch (tile->type) {
1517 case TILE_SIMPLE:
1518 return tile->style_simple.style[0];
1519 case TILE_COL:
1520 return tile->style_col.style[c];
1521 case TILE_ROW:
1522 return tile->style_row.style[r];
1523 case TILE_MATRIX:
1524 return tile->style_matrix.style[r * TILE_SIZE_COL + c];
1526 case TILE_PTR_MATRIX:
1527 g_return_val_if_fail (level > 0, NULL);
1529 level--;
1530 tile = tile->ptr_matrix.ptr[r * TILE_SIZE_COL + c];
1531 col -= c * width;
1532 row -= r * height;
1533 continue;
1535 default:
1536 g_warning ("Adaptive Quad Tree corruption !");
1537 return NULL;
1542 #define border_null(b) ((b) == none || (b) == NULL)
1544 static void
1545 style_row (GnmStyle *style, int start_col, int end_col, GnmStyleRow *sr, gboolean accept_conditions)
1547 GnmBorder const *top, *bottom, *none = gnm_style_border_none ();
1548 GnmBorder const *left, *right, *v;
1549 int const end = MIN (end_col, sr->end_col);
1550 int i = MAX (start_col, sr->start_col);
1552 if (accept_conditions && style->conditions) {
1553 GnmEvalPos ep;
1554 int res;
1556 for (eval_pos_init (&ep, (Sheet *)sr->sheet, i, sr->row); ep.eval.col <= end ; ep.eval.col++) {
1557 res = gnm_style_conditions_eval (style->conditions, &ep);
1558 style_row (res >= 0 ? g_ptr_array_index (style->cond_styles, res) : style,
1559 ep.eval.col, ep.eval.col, sr, FALSE);
1561 return;
1564 top = gnm_style_get_border (style, MSTYLE_BORDER_TOP);
1565 bottom = gnm_style_get_border (style, MSTYLE_BORDER_BOTTOM);
1566 left = gnm_style_get_border (style, MSTYLE_BORDER_LEFT);
1567 right = gnm_style_get_border (style, MSTYLE_BORDER_RIGHT);
1569 /* Cancel grids if there is a background */
1570 if (sr->hide_grid || gnm_style_get_pattern (style) > 0) {
1571 if (top == none)
1572 top = NULL;
1573 if (bottom == none)
1574 bottom = NULL;
1575 if (left == none)
1576 left = NULL;
1577 if (right == none)
1578 right = NULL;
1581 if (left != none && border_null (sr->vertical[i]))
1582 sr->vertical[i] = left;
1583 v = border_null (right) ? left : right;
1585 while (i <= end) {
1586 sr->styles[i] = style;
1587 if (top != none && border_null (sr->top[i]))
1588 sr->top[i] = top;
1589 sr->bottom[i] = bottom;
1590 sr->vertical[++i] = v;
1592 if (border_null (right))
1593 sr->vertical[i] = right;
1596 static void
1597 get_style_row (CellTile const *tile, int level,
1598 int corner_col, int corner_row,
1599 GnmStyleRow *sr)
1601 int const width = tile_widths[level+1];
1602 int const w = tile_widths[level];
1603 int const h = tile_heights[level];
1604 int r = 0;
1605 CellTileType t;
1607 g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
1608 g_return_if_fail (tile != NULL);
1610 t = tile->type;
1612 if (t != TILE_SIMPLE && t != TILE_COL) {
1613 r = (sr->row > corner_row) ? (sr->row - corner_row)/ h : 0;
1614 g_return_if_fail (r < TILE_SIZE_ROW);
1617 if (t == TILE_ROW || t == TILE_SIMPLE) {
1618 style_row (tile->style_any.style[r],
1619 corner_col, corner_col + width - 1, sr, TRUE);
1620 } else {
1621 /* find the start and end */
1622 int c;
1623 int last_c = (sr->end_col - corner_col) / w;
1624 if (last_c >= TILE_SIZE_COL)
1625 last_c = TILE_SIZE_COL-1;
1626 if (sr->start_col > corner_col) {
1627 c = (sr->start_col - corner_col) / w;
1628 corner_col += c * w;
1629 } else
1630 c = 0;
1632 corner_row += h*r;
1634 if (t != TILE_PTR_MATRIX) {
1635 GnmStyle * const *styles = tile->style_any.style + r*TILE_SIZE_COL;
1637 for ( ; c <= last_c ; c++, corner_col += w)
1638 style_row (styles[c],
1639 corner_col, corner_col + w - 1, sr, TRUE);
1640 } else {
1641 CellTile * const *tiles = tile->ptr_matrix.ptr + r*TILE_SIZE_COL;
1643 g_return_if_fail (level > 0);
1645 for ( level-- ; c <= last_c ; c++, corner_col += w)
1646 get_style_row (tiles[c], level,
1647 corner_col, corner_row, sr);
1653 * sheet_style_get_row:
1654 * @sheet: #Sheet
1655 * @sr: #GnmStyleRow
1657 * A utility routine which efficiently retrieves a range of styles within a row.
1658 * It also merges adjacent borders as necessary.
1660 void
1661 sheet_style_get_row (Sheet const *sheet, GnmStyleRow *sr)
1664 g_return_if_fail (IS_SHEET (sheet));
1665 g_return_if_fail (sr != NULL);
1666 g_return_if_fail (sr->styles != NULL);
1667 g_return_if_fail (sr->vertical != NULL);
1668 g_return_if_fail (sr->top != NULL);
1669 g_return_if_fail (sr->bottom != NULL);
1671 sr->sheet = sheet;
1672 sr->vertical[sr->start_col] = gnm_style_border_none ();
1673 get_style_row (sheet->style_data->styles, sheet->tile_top_level, 0, 0, sr);
1676 static void
1677 cb_get_row (GnmStyle *style,
1678 int corner_col, G_GNUC_UNUSED int corner_row,
1679 int width, G_GNUC_UNUSED int height,
1680 GnmRange const *apply_to, gpointer user_)
1682 GnmStyle **res = user_;
1683 int i;
1685 /* The given dimensions refer to the tile, not the area. */
1686 width = MIN (width, apply_to->end.col - corner_col + 1);
1688 for (i = 0; i < width; i++)
1689 res[corner_col + i] = style;
1692 GnmStyle **
1693 sheet_style_get_row2 (Sheet const *sheet, int row)
1695 GnmRange r;
1696 GnmStyle **res = g_new (GnmStyle *, gnm_sheet_get_max_cols (sheet));
1698 range_init_rows (&r, sheet, row, row);
1700 foreach_tile (sheet, &r, cb_get_row, res);
1702 return res;
1707 * style_row_init:
1709 * A small utility routine to initialize the grid drawing GnmStyleRow data
1710 * structure.
1712 void
1713 style_row_init (GnmBorder const * * *prev_vert,
1714 GnmStyleRow *sr, GnmStyleRow *next_sr,
1715 int start_col, int end_col, gpointer mem, gboolean hide_grid)
1717 int n, col;
1718 GnmBorder const *none = hide_grid ? NULL : gnm_style_border_none ();
1720 /* alias the arrays for easy access so that array[col] is valid
1721 * for all elements start_col-1 .. end_col+1 inclusive.
1722 * Note that this means that in some cases array[-1] is legal.
1724 n = end_col - start_col + 3; /* 1 before, 1 after, 1 fencepost */
1725 sr->vertical = mem;
1726 sr->vertical -= start_col-1;
1727 sr->top = sr->vertical + n;
1728 sr->bottom = sr->top + n;
1729 next_sr->top = sr->bottom; /* yes they should share */
1730 next_sr->bottom = next_sr->top + n;
1731 next_sr->vertical = next_sr->bottom + n;
1732 *prev_vert = next_sr->vertical + n;
1733 sr->styles = ((GnmStyle const **) (*prev_vert + n));
1734 next_sr->styles = sr->styles + n;
1735 sr->start_col = next_sr->start_col = start_col;
1736 sr->end_col = next_sr->end_col = end_col;
1737 sr->hide_grid = next_sr->hide_grid = hide_grid;
1739 /* Init the areas that sheet_style_get_row will not */
1740 for (col = start_col-1 ; col <= end_col+1; ++col)
1741 (*prev_vert)[col] = sr->top[col] = none;
1742 sr->vertical [start_col-1] = sr->vertical [end_col+1] =
1743 next_sr->vertical[start_col-1] = next_sr->vertical[end_col+1] =
1744 next_sr->top [start_col-1] = next_sr->top [end_col+1] =
1745 next_sr->bottom [start_col-1] = next_sr->bottom [end_col+1] = none;
1749 * sheet_style_apply_range:
1750 * @sheet:
1751 * @range:
1752 * @pstyle:
1754 * Apply a partial style to a region.
1755 * The routine absorbs a reference to the partial style.
1757 void
1758 sheet_style_apply_range (Sheet *sheet, GnmRange const *range, GnmStyle *pstyle)
1760 ReplacementStyle rs;
1761 GnmRange r;
1763 g_return_if_fail (IS_SHEET (sheet));
1764 g_return_if_fail (range != NULL);
1766 if (range->start.col > range->end.col ||
1767 range->start.row > range->end.row) {
1768 gnm_style_unref (pstyle);
1769 return;
1772 r = *range;
1773 range_ensure_sanity (&r, sheet);
1775 rstyle_ctor_pstyle (&rs, pstyle, sheet);
1776 cell_tile_apply (&sheet->style_data->styles,
1777 sheet->tile_top_level, 0, 0,
1778 &r, &rs);
1779 rstyle_dtor (&rs);
1782 static void
1783 apply_border (Sheet *sheet, GnmRange const *r,
1784 GnmStyleBorderLocation side,
1785 GnmBorder *border)
1787 GnmStyle *pstyle = gnm_style_new ();
1788 pstyle_set_border (pstyle, border, side);
1789 sheet_style_apply_range (sheet, r, pstyle);
1793 * sheet_style_apply_border:
1794 * @sheet:
1795 * @range:
1796 * @borders:
1798 * When a user applies a border to a region we attempt to remove the border
1799 * from the opposing side to avoid overlapping border specifications.
1800 * eg
1801 * if we apply a top border to a range, we would clear the bottom border
1802 * of the range offset upwards.
1804 void
1805 sheet_style_apply_border (Sheet *sheet,
1806 GnmRange const *range,
1807 GnmBorder **borders)
1809 GnmStyle *pstyle = NULL;
1811 if (borders == NULL)
1812 return;
1814 if (borders[GNM_STYLE_BORDER_TOP]) {
1815 /* 1.1 top inner */
1816 GnmRange r = *range;
1817 r.end.row = r.start.row;
1818 apply_border (sheet, &r, GNM_STYLE_BORDER_TOP,
1819 borders[GNM_STYLE_BORDER_TOP]);
1821 /* 1.2 top outer */
1822 r.start.row--;
1823 if (r.start.row >= 0) {
1824 r.end.row = r.start.row;
1825 apply_border (sheet, &r, GNM_STYLE_BORDER_BOTTOM,
1826 gnm_style_border_none ());
1830 if (borders[GNM_STYLE_BORDER_BOTTOM]) {
1831 /* 2.1 bottom inner */
1832 GnmRange r = *range;
1833 r.start.row = r.end.row;
1834 apply_border (sheet, &r, GNM_STYLE_BORDER_BOTTOM,
1835 borders[GNM_STYLE_BORDER_BOTTOM]);
1837 /* 2.2 bottom outer */
1838 r.end.row++;
1839 if (r.end.row < gnm_sheet_get_last_row (sheet)) {
1840 r.start.row = r.end.row;
1841 apply_border (sheet, &r, GNM_STYLE_BORDER_TOP,
1842 gnm_style_border_none ());
1846 if (borders[GNM_STYLE_BORDER_LEFT]) {
1847 /* 3.1 left inner */
1848 GnmRange r = *range;
1849 r.end.col = r.start.col;
1850 apply_border (sheet, &r, GNM_STYLE_BORDER_LEFT,
1851 borders[GNM_STYLE_BORDER_LEFT]);
1853 /* 3.2 left outer */
1854 r.start.col--;
1855 if (r.start.col >= 0) {
1856 r.end.col = r.start.col;
1857 apply_border (sheet, &r, GNM_STYLE_BORDER_RIGHT,
1858 gnm_style_border_none ());
1862 if (borders[GNM_STYLE_BORDER_RIGHT]) {
1863 /* 4.1 right inner */
1864 GnmRange r = *range;
1865 r.start.col = r.end.col;
1866 apply_border (sheet, &r, GNM_STYLE_BORDER_RIGHT,
1867 borders[GNM_STYLE_BORDER_RIGHT]);
1869 /* 4.2 right outer */
1870 r.end.col++;
1871 if (r.end.col < gnm_sheet_get_last_col (sheet)) {
1872 r.start.col = r.end.col;
1873 apply_border (sheet, &r, GNM_STYLE_BORDER_LEFT,
1874 gnm_style_border_none ());
1878 /* Interiors horizontal : prefer top */
1879 if (borders[GNM_STYLE_BORDER_HORIZ] != NULL) {
1880 /* 5.1 horizontal interior top */
1881 if (range->start.row != range->end.row) {
1882 GnmRange r = *range;
1883 ++r.start.row;
1884 apply_border (sheet, &r, GNM_STYLE_BORDER_TOP,
1885 borders[GNM_STYLE_BORDER_HORIZ]);
1887 /* 5.2 interior bottom */
1888 if (range->start.row != range->end.row) {
1889 GnmRange r = *range;
1890 --r.end.row;
1891 apply_border (sheet, &r, GNM_STYLE_BORDER_BOTTOM,
1892 gnm_style_border_none ());
1896 /* Interiors vertical: prefer left */
1897 if (borders[GNM_STYLE_BORDER_VERT] != NULL) {
1898 /* 6.1 vertical interior left */
1899 if (range->start.col != range->end.col) {
1900 GnmRange r = *range;
1901 ++r.start.col;
1902 apply_border (sheet, &r, GNM_STYLE_BORDER_LEFT,
1903 borders[GNM_STYLE_BORDER_VERT]);
1906 /* 6.2 The vertical interior right */
1907 if (range->start.col != range->end.col) {
1908 GnmRange r = *range;
1909 --r.end.col;
1910 apply_border (sheet, &r, GNM_STYLE_BORDER_RIGHT,
1911 gnm_style_border_none ());
1915 /* 7. Diagonals (apply both in one pass) */
1916 if (borders[GNM_STYLE_BORDER_DIAG] != NULL) {
1917 pstyle = gnm_style_new ();
1918 pstyle_set_border (pstyle, borders[GNM_STYLE_BORDER_DIAG],
1919 GNM_STYLE_BORDER_DIAG);
1921 if (borders[GNM_STYLE_BORDER_REV_DIAG]) {
1922 if (pstyle == NULL)
1923 pstyle = gnm_style_new ();
1924 pstyle_set_border (pstyle, borders[GNM_STYLE_BORDER_REV_DIAG],
1925 GNM_STYLE_BORDER_REV_DIAG);
1927 if (pstyle != NULL)
1928 sheet_style_apply_range (sheet, range, pstyle);
1931 /****************************************************************************/
1933 typedef struct {
1934 GnmStyle *accum;
1935 unsigned int conflicts;
1936 } FindConflicts;
1938 static void
1939 cb_find_conflicts (GnmStyle *style,
1940 G_GNUC_UNUSED int corner_col, G_GNUC_UNUSED int corner_row,
1941 G_GNUC_UNUSED int width, G_GNUC_UNUSED int height,
1942 G_GNUC_UNUSED GnmRange const *apply_to, FindConflicts *ptr)
1944 ptr->conflicts = gnm_style_find_conflicts (ptr->accum, style, ptr->conflicts);
1947 static void
1948 border_mask_internal (gboolean *known, GnmBorder **borders,
1949 GnmBorder const *b, GnmStyleBorderLocation l)
1951 if (!known[l]) {
1952 known[l] = TRUE;
1953 borders[l] = (GnmBorder *)b;
1954 gnm_style_border_ref (borders[l]);
1955 } else if (borders[l] != b && borders[l] != NULL) {
1956 gnm_style_border_unref (borders[l]);
1957 borders[l] = NULL;
1961 static void
1962 border_mask (gboolean *known, GnmBorder **borders,
1963 GnmBorder const *b, GnmStyleBorderLocation l)
1965 if (b == NULL)
1966 b = gnm_style_border_none ();
1967 border_mask_internal (known, borders, b, l);
1970 static void
1971 border_mask_vec (gboolean *known, GnmBorder **borders,
1972 GnmBorder const * const *vec, int first, int last,
1973 GnmStyleBorderLocation l)
1975 GnmBorder const *b = vec[first];
1977 if (b == NULL)
1978 b = gnm_style_border_none ();
1979 while (first++ < last) {
1980 GnmBorder const *tmp = vec[first];
1981 if (tmp == NULL)
1982 tmp = gnm_style_border_none ();
1983 if (b != tmp) {
1984 b = NULL;
1985 break;
1989 border_mask_internal (known, borders, b, l);
1993 * sheet_style_get_uniform:
1994 * @sheet:
1995 * @range:
1996 * @borders:
1998 * Find out what style elements are common to every cell in a range
1999 * Returns a flag of TRUE if there was a conflict a given style element
2001 unsigned int
2002 sheet_style_find_conflicts (Sheet const *sheet, GnmRange const *r,
2003 GnmStyle **style, GnmBorder **borders)
2005 int n, col, row, start_col, end_col;
2006 GnmStyleRow sr;
2007 gpointer *sr_array_data;
2008 GnmStyleBorderLocation i;
2009 gboolean known[GNM_STYLE_BORDER_EDGE_MAX];
2010 GnmBorder const *none = gnm_style_border_none ();
2011 FindConflicts user;
2013 g_return_val_if_fail (IS_SHEET (sheet), 0);
2014 g_return_val_if_fail (r != NULL, 0);
2015 g_return_val_if_fail (style != NULL, 0);
2016 g_return_val_if_fail (borders != NULL, 0);
2018 /* init style set with a copy of the top left corner of the 1st range */
2019 if (*style == NULL) {
2020 GnmStyle const *tmp = sheet_style_get (sheet, r->start.col, r->start.row);
2021 *style = gnm_style_dup (tmp);
2022 for (i = GNM_STYLE_BORDER_TOP ; i < GNM_STYLE_BORDER_EDGE_MAX ; i++) {
2023 known[i] = FALSE;
2024 borders[i] = gnm_style_border_ref ((GnmBorder *)none);
2026 } else {
2027 for (i = GNM_STYLE_BORDER_TOP ; i < GNM_STYLE_BORDER_EDGE_MAX ; i++)
2028 known[i] = TRUE;
2031 user.accum = *style;
2032 user.conflicts = 0; /* no conflicts yet */
2033 foreach_tile (sheet, r, (ForeachTileFunc)cb_find_conflicts, &user);
2035 /* copy over the diagonals */
2036 for (i = GNM_STYLE_BORDER_REV_DIAG ; i <= GNM_STYLE_BORDER_DIAG ; i++) {
2037 GnmStyleElement se = GNM_STYLE_BORDER_LOCATION_TO_STYLE_ELEMENT (i);
2038 if (user.conflicts & (1 << se))
2039 borders[i] = NULL;
2040 else
2041 borders[i] = gnm_style_border_ref (
2042 gnm_style_get_border (*style, se));
2045 start_col = r->start.col;
2046 if (r->start.col > 0)
2047 start_col--;
2048 end_col = r->end.col;
2049 if (r->end.col < gnm_sheet_get_max_cols (sheet))
2050 end_col++;
2052 /* allocate then alias the arrays for easy access */
2053 n = end_col - start_col + 2;
2054 g_assert (sizeof (GnmBorder *) == sizeof (gpointer));
2055 g_assert (sizeof (GnmStyle *) == sizeof (gpointer));
2056 sr_array_data = g_new (gpointer, n * 4);
2057 sr.vertical = (GnmBorder const **)(sr_array_data - start_col);
2058 sr.top = (GnmBorder const **)(sr_array_data + n - start_col);
2059 sr.bottom = (GnmBorder const **)(sr_array_data + 2 * n - start_col);
2060 sr.styles = (GnmStyle const **) (sr_array_data + 3 * n - start_col);
2061 sr.start_col = start_col;
2062 sr.end_col = end_col;
2063 sr.hide_grid = sheet->hide_grid;
2065 /* pretend the previous bottom had no borders */
2066 for (col = start_col ; col <= end_col; ++col)
2067 sr.top[col] = none;
2069 /* merge the bottom of the previous row */
2070 if (r->start.row > 0) {
2071 GnmBorder const ** roller;
2072 sr.row = r->start.row - 1;
2073 sheet_style_get_row (sheet, &sr);
2074 roller = sr.top; sr.top = sr.bottom; sr.bottom = roller;
2078 * TODO: The border handling is tricky and currently VERY slow for
2079 * large ranges. We could easily optimize this. There is no need to
2080 * retrieve the style in every cell just to do a filter for uniformity
2081 * by row. One day we should do a special case version of
2082 * sheet_style_get_row probably style_get_uniform_col (this will be
2083 * faster)
2085 for (row = r->start.row ; row <= r->end.row ; row++) {
2086 GnmBorder const **roller;
2087 sr.row = row;
2088 sheet_style_get_row (sheet, &sr);
2090 border_mask (known, borders, sr.vertical[r->start.col],
2091 GNM_STYLE_BORDER_LEFT);
2092 border_mask (known, borders, sr.vertical[r->end.col+1],
2093 GNM_STYLE_BORDER_RIGHT);
2094 border_mask_vec (known, borders, sr.top,
2095 r->start.col, r->end.col, (row == r->start.row)
2096 ? GNM_STYLE_BORDER_TOP : GNM_STYLE_BORDER_HORIZ);
2097 if (r->start.col != r->end.col)
2098 border_mask_vec (known, borders, sr.vertical,
2099 r->start.col+1, r->end.col,
2100 GNM_STYLE_BORDER_VERT);
2102 roller = sr.top; sr.top = sr.bottom; sr.bottom = roller;
2105 /* merge the top of the next row */
2106 if (r->end.row < gnm_sheet_get_last_row (sheet)) {
2107 sr.row = r->end.row + 1;
2108 sheet_style_get_row (sheet, &sr);
2110 border_mask_vec (known, borders, sr.top, r->start.col, r->end.col,
2111 GNM_STYLE_BORDER_BOTTOM);
2113 g_free (sr_array_data);
2114 return user.conflicts;
2118 * sheet_style_relocate:
2119 * @rinfo:
2121 * Slide the styles from the origin region to the new position.
2123 void
2124 sheet_style_relocate (GnmExprRelocateInfo const *rinfo)
2126 GnmCellPos corner;
2127 GnmStyleList *styles;
2129 g_return_if_fail (rinfo != NULL);
2131 styles = sheet_style_get_range (rinfo->origin_sheet, &rinfo->origin);
2133 sheet_style_set_range (rinfo->origin_sheet, &rinfo->origin,
2134 sheet_style_default (rinfo->origin_sheet));
2135 corner.col = rinfo->origin.start.col + rinfo->col_offset;
2136 corner.row = rinfo->origin.start.row + rinfo->row_offset;
2137 sheet_style_set_list (rinfo->target_sheet, &corner, styles, NULL, NULL);
2138 style_list_free (styles);
2142 * sheet_style_insdel_colrow:
2143 * @rinfo:
2145 * Insert of delete style columns/rows.
2147 * For the insert case, we stretch the preceding column/row into there space
2148 * we open.
2150 void
2151 sheet_style_insdel_colrow (GnmExprRelocateInfo const *rinfo)
2153 GnmStyleList *styles = NULL;
2154 Sheet *sheet;
2155 GnmCellPos corner;
2156 gboolean is_insert;
2158 g_return_if_fail (rinfo != NULL);
2159 g_return_if_fail (rinfo->origin_sheet == rinfo->target_sheet);
2160 g_return_if_fail ((rinfo->col_offset == 0) != (rinfo->row_offset == 0));
2162 is_insert = (rinfo->col_offset + rinfo->row_offset > 0);
2163 sheet = rinfo->origin_sheet;
2165 if (is_insert) {
2166 /* 1) copy col/row to the top/left of the region, and extend it */
2167 corner = rinfo->origin.start;
2168 if (rinfo->col_offset) {
2169 int col = MAX (corner.col - 1, 0);
2170 GnmStyleList *ptr;
2171 GnmRange r;
2173 corner.row = 0;
2174 range_init_cols (&r, sheet, col, col);
2175 styles = sheet_style_get_range (sheet, &r);
2176 for (ptr = styles ; ptr != NULL ; ptr = ptr->next) {
2177 GnmStyleRegion *sr = ptr->data;
2178 sr->range.end.col = rinfo->col_offset - 1;
2180 } else {
2181 int row = MAX (corner.row - 1, 0);
2182 GnmStyleList *ptr;
2183 GnmRange r;
2185 corner.col = 0;
2186 range_init_rows (&r, sheet, row, row);
2187 styles = sheet_style_get_range (sheet, &r);
2188 for (ptr = styles ; ptr != NULL ; ptr = ptr->next) {
2189 GnmStyleRegion *sr = ptr->data;
2190 sr->range.end.row = rinfo->row_offset - 1;
2195 sheet_style_relocate (rinfo);
2197 if (styles) {
2198 sheet_style_set_list (sheet, &corner, styles, NULL, NULL);
2199 style_list_free (styles);
2203 static void
2204 cb_style_extent (GnmStyle *style,
2205 int corner_col, int corner_row, int width, int height,
2206 GnmRange const *apply_to, gpointer user)
2208 GnmRange *res = user;
2209 if (gnm_style_visible_in_blank (style)) {
2210 int tmp;
2212 /* The given dimensions refer to the tile, not the area. */
2213 width = MIN (width, apply_to->end.col - corner_col + 1);
2214 height = MIN (height, apply_to->end.row - corner_row + 1);
2216 tmp = corner_col+width-1;
2217 if (res->end.col < tmp)
2218 res->end.col = tmp;
2219 if (res->start.col > corner_col)
2220 res->start.col = corner_col;
2222 tmp = corner_row+height-1;
2223 if (res->end.row < tmp)
2224 res->end.row = tmp;
2225 if (res->start.row > corner_row)
2226 res->start.row = corner_row;
2231 * sheet_style_get_extent:
2232 * @sheet: sheet to measure
2233 * @r: starting range and resulting range
2235 * A simple implementation that finds the smallest range containing all visible styles
2236 * and containing @res.
2238 void
2239 sheet_style_get_extent (Sheet const *sheet, GnmRange *res)
2241 GnmRange r;
2243 range_init_full_sheet (&r, sheet);
2244 foreach_tile (sheet, &r, cb_style_extent, res);
2247 struct cb_nondefault_extent {
2248 GnmRange *res;
2249 GnmStyle **col_defaults;
2252 static void
2253 cb_nondefault_extent (GnmStyle *style,
2254 int corner_col, int corner_row, int width, int height,
2255 GnmRange const *apply_to, gpointer user_)
2257 struct cb_nondefault_extent *user = user_;
2258 GnmRange *res = user->res;
2259 int i;
2261 for (i = 0; i < width; i++) {
2262 int col = corner_col + i;
2263 if (col >= apply_to->start.col &&
2264 col <= apply_to->end.col &&
2265 style != user->col_defaults[col]) {
2266 int max_row = MIN (corner_row + height - 1,
2267 apply_to->end.row);
2268 int min_row = MAX (corner_row, apply_to->start.row);
2270 res->start.col = MIN (col, res->start.col);
2271 res->start.row = MIN (min_row, res->start.row);
2273 res->end.col = MAX (col, res->end.col);
2274 res->end.row = MAX (max_row, res->end.row);
2279 void
2280 sheet_style_get_nondefault_extent (Sheet const *sheet, GnmRange *extent,
2281 const GnmRange *src, GnmStyle **col_defaults)
2283 struct cb_nondefault_extent user;
2284 user.res = extent;
2285 user.col_defaults = col_defaults;
2286 foreach_tile (sheet, src, cb_nondefault_extent, &user);
2289 struct cb_is_default {
2290 gboolean res;
2291 GnmStyle **col_defaults;
2294 static void
2295 cb_is_default (GnmStyle *style,
2296 int corner_col, G_GNUC_UNUSED int corner_row,
2297 int width, G_GNUC_UNUSED int height,
2298 GnmRange const *apply_to, gpointer user_)
2300 struct cb_is_default *user = user_;
2301 int i;
2303 /* The given "width" refers to the tile, not the area. */
2304 width = MIN (width, apply_to->end.col - corner_col + 1);
2306 for (i = 0; user->res && i < width; i++) {
2307 if (style != user->col_defaults[corner_col + i])
2308 user->res = FALSE;
2312 gboolean
2313 sheet_style_is_default (Sheet const *sheet, const GnmRange *r, GnmStyle **col_defaults)
2315 struct cb_is_default user;
2317 user.res = TRUE;
2318 user.col_defaults = col_defaults;
2320 foreach_tile (sheet, r, cb_is_default, &user);
2322 return user.res;
2325 struct cb_get_nondefault {
2326 guint8 *res;
2327 GnmStyle **col_defaults;
2330 static void
2331 cb_get_nondefault (GnmStyle *style,
2332 int corner_col, G_GNUC_UNUSED int corner_row,
2333 int width, G_GNUC_UNUSED int height,
2334 GnmRange const *apply_to, gpointer user_)
2336 struct cb_get_nondefault *user = user_;
2337 int i;
2339 /* The given dimensions refer to the tile, not the area. */
2340 width = MIN (width, apply_to->end.col - corner_col + 1);
2341 height = MIN (height, apply_to->end.row - corner_row + 1);
2343 for (i = 0; i < width; i++) {
2344 if (style != user->col_defaults[corner_col + i]) {
2345 int j;
2346 for (j = 0; j < height; j++)
2347 user->res[corner_row + j] = 1;
2348 break;
2353 guint8 *
2354 sheet_style_get_nondefault_rows (Sheet const *sheet, GnmStyle **col_defaults)
2356 struct cb_get_nondefault user;
2357 GnmRange r;
2359 range_init_full_sheet (&r, sheet);
2361 user.res = g_new0 (guint8, gnm_sheet_get_max_rows (sheet));
2362 user.col_defaults = col_defaults;
2364 foreach_tile (sheet, &r, cb_get_nondefault, &user);
2366 return user.res;
2369 struct cb_most_common {
2370 GHashTable *h;
2371 int l;
2372 gboolean is_col;
2375 static void
2376 cb_most_common (GnmStyle *style,
2377 int corner_col, int corner_row, int width, int height,
2378 GnmRange const *apply_to, gpointer user)
2380 struct cb_most_common *cmc = user;
2381 int *counts = g_hash_table_lookup (cmc->h, style);
2382 int i;
2383 if (!counts) {
2384 counts = g_new0 (int, cmc->l);
2385 g_hash_table_insert (cmc->h, style, counts);
2388 /* The given dimensions refer to the tile, not the area. */
2389 width = MIN (width, apply_to->end.col - corner_col + 1);
2390 height = MIN (height, apply_to->end.row - corner_row + 1);
2392 if (cmc->is_col)
2393 for (i = 0; i < width; i++)
2394 counts[corner_col + i] += height;
2395 else
2396 for (i = 0; i < height; i++)
2397 counts[corner_row + i] += width;
2401 * sheet_style_most_common:
2402 * @sheet: sheet to inspect
2403 * @is_col: if %TRUE, look for common styles in columns; if FALSE, look in rows.
2405 * Returns: an array of styles describing the most common styles, one per column
2406 * or row.
2408 GnmStyle **
2409 sheet_style_most_common (Sheet const *sheet, gboolean is_col)
2411 GnmRange r;
2412 struct cb_most_common cmc;
2413 int *max;
2414 GnmStyle **res;
2415 GHashTableIter iter;
2416 gpointer key, value;
2418 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2420 range_init_full_sheet (&r, sheet);
2421 cmc.h = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
2422 cmc.l = colrow_max (is_col, sheet);
2423 cmc.is_col = is_col;
2424 foreach_tile (sheet, &r, cb_most_common, &cmc);
2426 max = g_new0 (int, cmc.l);
2427 res = g_new0 (GnmStyle *, cmc.l);
2428 g_hash_table_iter_init (&iter, cmc.h);
2429 while (g_hash_table_iter_next (&iter, &key, &value)) {
2430 int *counts = value;
2431 GnmStyle *style = key;
2432 int j;
2433 for (j = 0; j < cmc.l; j++) {
2434 /* FIXME: we really ought to break ties in a
2435 consistent way that does not depend on hash
2436 order. */
2437 if (counts[j] > max[j]) {
2438 max[j] = counts[j];
2439 res[j] = style;
2443 g_hash_table_destroy (cmc.h);
2444 g_free (max);
2446 return res;
2449 /****************************************************************************/
2452 * gnm_style_region_new:
2453 * @range: #GnmRange
2454 * @style: #GnmStyle
2456 * Returns: (transfer full): the newly allocated #GnmStyleRegion.
2458 GnmStyleRegion *
2459 gnm_style_region_new (GnmRange const *range, GnmStyle *style)
2461 GnmStyleRegion *sr;
2463 sr = g_new (GnmStyleRegion, 1);
2464 sr->range = *range;
2465 sr->style = style;
2466 gnm_style_ref (style);
2468 return sr;
2471 void
2472 gnm_style_region_free (GnmStyleRegion *sr)
2474 g_return_if_fail (sr != NULL);
2476 gnm_style_unref (sr->style);
2477 sr->style = NULL;
2478 g_free (sr);
2481 static GnmStyleRegion *
2482 gnm_style_region_copy (GnmStyleRegion *sr)
2484 GnmStyleRegion *res = g_new (GnmStyleRegion, 1);
2485 *res = *sr;
2486 gnm_style_ref (sr->style);
2487 return res;
2490 GType
2491 gnm_style_region_get_type (void)
2493 static GType t = 0;
2495 if (t == 0) {
2496 t = g_boxed_type_register_static ("GnmStyleRegion",
2497 (GBoxedCopyFunc)gnm_style_region_copy,
2498 (GBoxedFreeFunc)gnm_style_region_free);
2500 return t;
2504 static gboolean
2505 debug_style_list (void)
2507 static int debug = -1;
2508 if (debug < 0)
2509 debug = gnm_debug_flag ("style-list");
2510 return debug;
2513 typedef struct {
2514 GPtrArray *accum;
2515 GHashTable *by_tl, *by_br;
2516 guint64 area;
2517 gboolean (*style_equal) (GnmStyle const *a, GnmStyle const *b);
2518 gboolean (*style_filter) (GnmStyle const *style);
2519 GnmSheetSize const *sheet_size;
2520 } ISL;
2522 static gboolean
2523 merge_ranges (GnmRange *a, GnmRange const *b)
2525 if (a->start.row == b->start.row &&
2526 a->end.row == b->end.row &&
2527 a->end.col + 1 == b->start.col) {
2528 /* "a" is just left of "b". */
2529 a->end.col = b->end.col;
2530 return TRUE;
2533 if (a->start.col == b->start.col &&
2534 a->end.col == b->end.col &&
2535 a->end.row + 1 == b->start.row) {
2536 /* "a" is just on top of "b". */
2537 a->end.row = b->end.row;
2538 return TRUE;
2541 /* Punt. */
2542 return FALSE;
2545 static gboolean
2546 try_merge_pair (ISL *data, unsigned ui1, unsigned ui2)
2548 GnmStyleRegion *a;
2549 GnmStyleRegion *b;
2551 if (ui1 >= data->accum->len || ui2 >= data->accum->len)
2552 return FALSE;
2554 a = g_ptr_array_index (data->accum, ui1);
2555 b = g_ptr_array_index (data->accum, ui2);
2557 if (!data->style_equal (a->style, b->style))
2558 return FALSE;
2560 if (!merge_ranges (&a->range, &b->range))
2561 return FALSE;
2563 gnm_style_region_free (b);
2564 g_ptr_array_remove_index (data->accum, ui2);
2566 return TRUE;
2569 static void
2570 cb_style_list_add_node (GnmStyle *style,
2571 int corner_col, int corner_row, int width, int height,
2572 GnmRange const *apply_to, gpointer user_)
2574 ISL *data = user_;
2575 GnmSheetSize const *ss = data->sheet_size;
2576 GnmStyleRegion *sr;
2577 GnmRange range;
2579 /* Can this even happen? */
2580 if (corner_col >= ss->max_cols || corner_row >= ss->max_rows)
2581 return;
2583 if (data->style_filter && !data->style_filter (style))
2584 return;
2586 range.start.col = corner_col;
2587 range.start.row = corner_row;
2588 range.end.col = MIN (corner_col + width - 1, ss->max_cols - 1);
2589 range.end.row = MIN (corner_row + height - 1, ss->max_rows - 1);
2591 if (apply_to) {
2592 range.start.col -= apply_to->start.col;
2593 if (range.start.col < 0)
2594 range.start.col = 0;
2595 range.start.row -= apply_to->start.row;
2596 if (range.start.row < 0)
2597 range.start.row = 0;
2599 if (range.end.col > apply_to->end.col)
2600 range.end.col = apply_to->end.col;
2601 range.end.col -= apply_to->start.col;
2602 if (range.end.row > apply_to->end.row)
2603 range.end.row = apply_to->end.row;
2604 range.end.row -= apply_to->start.row;
2607 data->area += (guint64)range_width (&range) * range_height (&range);
2609 sr = gnm_style_region_new (&range, style);
2610 g_ptr_array_add (data->accum, sr);
2612 while (try_merge_pair (data, data->accum->len - 2, data->accum->len - 1))
2613 /* Nothing */;
2616 static void
2617 verify_hashes (ISL *data)
2619 GHashTable *by_tl = data->by_tl;
2620 GHashTable *by_br = data->by_br;
2621 unsigned ui;
2622 guint64 area = 0;
2624 g_return_if_fail (g_hash_table_size (by_tl) == data->accum->len);
2625 g_return_if_fail (g_hash_table_size (by_br) == data->accum->len);
2627 for (ui = 0; ui < data->accum->len; ui++) {
2628 GnmStyleRegion *sr = g_ptr_array_index (data->accum, ui);
2629 g_return_if_fail (g_hash_table_lookup (by_tl, &sr->range.start) == sr);
2630 g_return_if_fail (g_hash_table_lookup (by_br, &sr->range.end) == sr);
2631 area += range_height (&sr->range) *
2632 (guint64)range_width (&sr->range);
2635 g_return_if_fail (area == data->area);
2638 static void
2639 merge_vertical_stripes (ISL *data)
2641 unsigned ui;
2642 GHashTable *by_tl = data->by_tl;
2643 GHashTable *by_br = data->by_br;
2644 gboolean debug = debug_style_list ();
2645 gboolean paranoid = debug;
2647 for (ui = 0; ui < data->accum->len; ui++) {
2648 GnmStyleRegion *a = g_ptr_array_index (data->accum, ui);
2649 GnmStyleRegion *c;
2650 GnmCellPos cr;
2651 GSList *Bs = NULL, *l;
2652 gboolean fail = FALSE;
2654 /* We're looking for the setup below and extend Bs down */
2655 /* taking over part of C which is then extended to */
2656 /* include all of A. */
2657 /* */
2658 /* +----+ */
2659 /* | +---------+ */
2660 /* +---------+ B1 | B2 | */
2661 /* | A | | | */
2662 /* +---------+----+---------+ */
2663 /* | C | */
2664 /* +------------------------+ */
2666 cr.col = a->range.start.col;
2667 cr.row = a->range.end.row + 1;
2668 c = g_hash_table_lookup (by_tl, &cr);
2669 if (!c || !data->style_equal (a->style, c->style))
2670 continue;
2672 cr.col = c->range.end.col;
2673 cr.row = a->range.end.row;
2674 while (cr.col > a->range.end.col) {
2675 GnmStyleRegion *b = g_hash_table_lookup (by_br, &cr);
2676 if (!b || !data->style_equal (a->style, b->style)) {
2677 fail = TRUE;
2678 break;
2680 Bs = g_slist_prepend (Bs, b);
2681 cr.col = b->range.start.col - 1;
2683 if (fail || cr.col != a->range.end.col) {
2684 g_slist_free (Bs);
2685 continue;
2688 if (debug) {
2689 g_printerr ("Vertical stripe merge:\n");
2690 g_printerr ("A: %s\n", range_as_string (&a->range));
2691 for (l = Bs; l; l = l-> next) {
2692 GnmStyleRegion *b = l->data;
2693 g_printerr ("B: %s\n", range_as_string (&b->range));
2695 g_printerr ("C: %s\n", range_as_string (&c->range));
2698 g_hash_table_remove (by_tl, &a->range.start);
2699 g_hash_table_remove (by_br, &a->range.end);
2700 g_ptr_array_remove_index_fast (data->accum, ui);
2701 ui--;
2703 g_hash_table_remove (by_tl, &c->range.start);
2704 g_hash_table_remove (by_br, &c->range.end);
2705 c->range.start.row = a->range.start.row;
2706 c->range.end.col = a->range.end.col;
2707 g_hash_table_insert (by_tl, &c->range.start, c);
2708 g_hash_table_insert (by_br, &c->range.end, c);
2709 if (debug)
2710 g_printerr ("New C: %s\n", range_as_string (&c->range));
2712 for (l = Bs; l; l = l-> next) {
2713 GnmStyleRegion *b = l->data;
2714 g_hash_table_remove (by_br, &b->range.end);
2715 b->range.end.row = c->range.end.row;
2716 g_hash_table_insert (by_br, &b->range.end, b);
2717 if (debug)
2718 g_printerr ("New B: %s\n", range_as_string (&b->range));
2720 if (debug)
2721 g_printerr ("\n");
2723 gnm_style_region_free (a);
2724 g_slist_free (Bs);
2726 if (paranoid) verify_hashes (data);
2730 static void
2731 merge_horizontal_stripes (ISL *data)
2733 unsigned ui;
2734 GHashTable *by_tl = data->by_tl;
2735 GHashTable *by_br = data->by_br;
2736 gboolean debug = debug_style_list ();
2737 gboolean paranoid = debug;
2739 for (ui = 0; ui < data->accum->len; ui++) {
2740 GnmStyleRegion *a = g_ptr_array_index (data->accum, ui);
2741 GnmStyleRegion *c;
2742 GnmCellPos cr;
2743 GSList *Bs = NULL, *l;
2744 gboolean fail = FALSE;
2746 /* We're looking for the setup below and extend Bs right */
2747 /* taking over part of C which is then extended to */
2748 /* include all of A. */
2749 /* */
2750 /* +-----+-----+ */
2751 /* | A | | */
2752 /* +----+-----+ | */
2753 /* | B1 | | */
2754 /* +--+-------+ | */
2755 /* | | C | */
2756 /* | | | */
2757 /* | B2 | | */
2758 /* | | | */
2759 /* | | | */
2760 /* +-------+-----+ */
2762 cr.col = a->range.end.col + 1;
2763 cr.row = a->range.start.row;
2764 c = g_hash_table_lookup (by_tl, &cr);
2765 if (!c || !data->style_equal (a->style, c->style))
2766 continue;
2768 cr.col = a->range.end.col;
2769 cr.row = c->range.end.row;
2770 while (cr.row > a->range.end.row) {
2771 GnmStyleRegion *b = g_hash_table_lookup (by_br, &cr);
2772 if (!b || !data->style_equal (a->style, b->style)) {
2773 fail = TRUE;
2774 break;
2776 Bs = g_slist_prepend (Bs, b);
2777 cr.row = b->range.start.row - 1;
2779 if (fail || cr.row != a->range.end.row) {
2780 g_slist_free (Bs);
2781 continue;
2784 if (debug) {
2785 g_printerr ("Horizontal stripe merge:\n");
2786 g_printerr ("A: %s\n", range_as_string (&a->range));
2787 for (l = Bs; l; l = l-> next) {
2788 GnmStyleRegion *b = l->data;
2789 g_printerr ("B: %s\n", range_as_string (&b->range));
2791 g_printerr ("C: %s\n", range_as_string (&c->range));
2794 g_hash_table_remove (by_tl, &a->range.start);
2795 g_hash_table_remove (by_br, &a->range.end);
2796 g_ptr_array_remove_index_fast (data->accum, ui);
2797 ui--;
2799 g_hash_table_remove (by_tl, &c->range.start);
2800 g_hash_table_remove (by_br, &c->range.end);
2801 c->range.start.col = a->range.start.col;
2802 c->range.end.row = a->range.end.row;
2803 g_hash_table_insert (by_tl, &c->range.start, c);
2804 g_hash_table_insert (by_br, &c->range.end, c);
2805 if (debug)
2806 g_printerr ("New C: %s\n", range_as_string (&c->range));
2808 for (l = Bs; l; l = l-> next) {
2809 GnmStyleRegion *b = l->data;
2810 g_hash_table_remove (by_br, &b->range.end);
2811 b->range.end.col = c->range.end.col;
2812 g_hash_table_insert (by_br, &b->range.end, b);
2813 if (debug)
2814 g_printerr ("New B: %s\n", range_as_string (&b->range));
2816 if (debug)
2817 g_printerr ("\n");
2819 gnm_style_region_free (a);
2820 g_slist_free (Bs);
2822 if (paranoid) verify_hashes (data);
2826 static int
2827 by_col_row (GnmStyleRegion **a, GnmStyleRegion **b)
2829 int d;
2831 d = (*a)->range.start.col - (*b)->range.start.col;
2832 if (d)
2833 return d;
2835 d = (*a)->range.start.row - (*b)->range.start.row;
2836 return d;
2839 static GnmStyleList *
2840 internal_style_list (Sheet const *sheet, GnmRange const *r,
2841 gboolean (*style_equal) (GnmStyle const *a, GnmStyle const *b),
2842 gboolean (*style_filter) (GnmStyle const *style))
2844 GnmRange full_sheet;
2845 ISL data;
2846 GnmStyleList *res = NULL;
2847 unsigned ui, prelen;
2848 gboolean paranoid = FALSE;
2849 guint64 sheet_area;
2851 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2853 if (r) {
2854 /* This can happen if the last row or column is deleted. */
2855 if (!range_valid (r))
2856 return NULL;
2857 } else
2858 r = range_init_full_sheet (&full_sheet, sheet);
2860 data.accum = g_ptr_array_new ();
2861 data.by_tl = g_hash_table_new ((GHashFunc)gnm_cellpos_hash,
2862 (GEqualFunc)gnm_cellpos_equal);
2863 data.by_br = g_hash_table_new ((GHashFunc)gnm_cellpos_hash,
2864 (GEqualFunc)gnm_cellpos_equal);
2865 data.area = 0;
2866 data.style_equal = style_equal;
2867 data.style_filter = style_filter;
2868 data.sheet_size = gnm_sheet_get_size (sheet);
2870 foreach_tile (sheet, r, cb_style_list_add_node, &data);
2872 sheet_area = (guint64)range_height (r) * range_width (r);
2873 if (data.style_filter ? (data.area > sheet_area) : (data.area != sheet_area))
2874 g_warning ("Strange size issue in internal_style_list");
2877 * Simple, fast optimization first. For the file underlying
2878 * bug 699045 this brings down 332688 entries to just 86.
2880 if (data.accum->len >= 2) {
2881 g_ptr_array_sort (data.accum, (GCompareFunc)by_col_row);
2882 for (ui = data.accum->len - 1; ui > 0; ui--) {
2883 try_merge_pair (&data, ui - 1, ui);
2887 /* Populate hashes. */
2888 for (ui = 0; ui < data.accum->len; ui++) {
2889 GnmStyleRegion *sr = g_ptr_array_index (data.accum, ui);
2890 g_hash_table_insert (data.by_tl, &sr->range.start, sr);
2891 g_hash_table_insert (data.by_br, &sr->range.end, sr);
2894 if (paranoid) verify_hashes (&data);
2896 do {
2897 prelen = data.accum->len;
2898 merge_vertical_stripes (&data);
2899 merge_horizontal_stripes (&data);
2900 } while (prelen > data.accum->len);
2902 /* Always verify once. */
2903 verify_hashes (&data);
2905 if (debug_style_list ())
2906 g_printerr ("Total of %d ranges:\n", data.accum->len);
2907 for (ui = data.accum->len; ui-- > 0; ) {
2908 GnmStyleRegion *sr = g_ptr_array_index (data.accum, ui);
2909 if (debug_style_list ())
2910 g_printerr (" %s %p\n",
2911 range_as_string (&sr->range),
2912 sr->style);
2913 res = g_slist_prepend (res, sr);
2916 g_ptr_array_free (data.accum, TRUE);
2917 g_hash_table_destroy (data.by_tl);
2918 g_hash_table_destroy (data.by_br);
2919 return res;
2923 * sheet_style_get_range:
2924 * @sheet: the sheet in which to find styles
2925 * @r: optional range to scan
2927 * Get a list of rectangles and their associated styles.
2928 * Caller is responsible for freeing. Note that when a range is given,
2929 * the resulting ranges are relative to the input range.
2931 * Returns: (transfer full):
2933 GnmStyleList *
2934 sheet_style_get_range (Sheet const *sheet, GnmRange const *r)
2936 return internal_style_list (sheet, r,
2937 gnm_style_eq,
2938 NULL);
2941 static gboolean
2942 style_conditions_equal (GnmStyle const *a, GnmStyle const *b)
2944 return gnm_style_get_conditions (a) == gnm_style_get_conditions (b);
2947 static gboolean
2948 style_conditions_filter (GnmStyle const *style)
2950 return gnm_style_get_conditions (style) != NULL;
2954 * sheet_style_collect_conditions:
2955 * @sheet:
2956 * @r:
2958 * Returns: (transfer full): a list of areas with conditionals, Caller is
2959 * responsible for freeing.
2961 GnmStyleList *
2962 sheet_style_collect_conditions (Sheet const *sheet, GnmRange const *r)
2964 return internal_style_list (sheet, r,
2965 style_conditions_equal,
2966 style_conditions_filter);
2970 static gboolean
2971 style_hlink_equal (GnmStyle const *a, GnmStyle const *b)
2973 return gnm_style_get_hlink (a) == gnm_style_get_hlink (b);
2976 static gboolean
2977 style_hlink_filter (GnmStyle const *style)
2979 return gnm_style_get_hlink (style) != NULL;
2983 * sheet_style_collect_hlinks:
2984 * @sheet:
2985 * @r:
2987 * Returns: (transfer full): a list of areas with hyperlinks, Caller is
2988 * responsible for freeing.
2990 GnmStyleList *
2991 sheet_style_collect_hlinks (Sheet const *sheet, GnmRange const *r)
2993 return internal_style_list (sheet, r,
2994 style_hlink_equal,
2995 style_hlink_filter);
2999 static gboolean
3000 style_validation_equal (GnmStyle const *a, GnmStyle const *b)
3002 return gnm_style_get_validation (a) == gnm_style_get_validation (b) &&
3003 gnm_style_get_input_msg (a) == gnm_style_get_input_msg (b);
3006 static gboolean
3007 style_validation_filter (GnmStyle const *style)
3009 return (gnm_style_get_validation (style) != NULL ||
3010 gnm_style_get_input_msg (style) != NULL);
3014 * sheet_style_collect_validations:
3015 * @sheet: the to trawl
3016 * @r: (allow-none): range to restrict to
3018 * Returns: (transfer full): a list of areas with validation or input
3019 * message.
3021 GnmStyleList *
3022 sheet_style_collect_validations (Sheet const *sheet, GnmRange const *r)
3024 return internal_style_list (sheet, r,
3025 style_validation_equal,
3026 style_validation_filter);
3030 * sheet_style_set_list:
3031 * @sheet: #Sheet
3032 * @corner: The top-left corner (in LTR mode)
3033 * @l: #GnmStyleList
3034 * @range_modify: (scope call):
3035 * @data: user data
3037 * Overwrites the styles of the ranges given by @corner with the content of
3038 * @list. Optionally transposing the ranges
3040 GnmSpanCalcFlags
3041 sheet_style_set_list (Sheet *sheet, GnmCellPos const *corner,
3042 GnmStyleList const *list,
3043 sheet_style_set_list_cb_t range_modify,
3044 gpointer data)
3046 GnmSpanCalcFlags spanflags = GNM_SPANCALC_SIMPLE;
3047 GnmStyleList const *l;
3049 g_return_val_if_fail (IS_SHEET (sheet), spanflags);
3051 /* Sluggish but simple implementation for now */
3052 for (l = list; l; l = l->next) {
3053 GnmStyleRegion const *sr = l->data;
3054 GnmRange r = sr->range;
3056 range_translate (&r, sheet, +corner->col, +corner->row);
3057 if (range_modify)
3058 range_modify (&r, sheet, data);
3060 gnm_style_ref (sr->style);
3061 sheet_style_set_range (sheet, &r, sr->style);
3062 spanflags |= gnm_style_required_spanflags (sr->style);
3064 return spanflags;
3068 * style_list_free:
3069 * @l: the list to free
3071 * Free up the ressources in the style list. Including unreferencing the
3072 * styles.
3074 void
3075 style_list_free (GnmStyleList *list)
3077 g_slist_free_full (list, (GDestroyNotify)gnm_style_region_free);
3081 * style_list_get_style:
3082 * @l: A style list.
3083 * @col:
3084 * @row:
3086 * Attempts to find the style associated with the @pos offset within the 0,0
3087 * based style list.
3088 * The resulting style does not have its reference count bumped.
3090 GnmStyle const *
3091 style_list_get_style (GnmStyleList const *list, int col, int row)
3093 GnmStyleList const *l;
3095 for (l = list; l; l = l->next) {
3096 GnmStyleRegion const *sr = l->data;
3097 GnmRange const *r = &sr->range;
3098 if (range_contains (r, col, row))
3099 return sr->style;
3101 return NULL;
3104 static void
3105 cb_find_link (GnmStyle *style,
3106 G_GNUC_UNUSED int corner_col, G_GNUC_UNUSED int corner_row,
3107 G_GNUC_UNUSED int width, G_GNUC_UNUSED int height,
3108 G_GNUC_UNUSED GnmRange const *apply_to, gpointer user)
3110 GnmHLink **plink = user;
3111 if (*plink == NULL)
3112 *plink = gnm_style_get_hlink (style);
3116 * sheet_style_region_contains_link:
3117 * @sheet:
3118 * @r:
3120 * Utility routine that checks to see if a region contains at least 1 hyper link
3121 * and returns the 1st one it finds.
3123 * Returns: (transfer none): the found #GmHLink if any.
3125 GnmHLink *
3126 sheet_style_region_contains_link (Sheet const *sheet, GnmRange const *r)
3128 GnmHLink *res = NULL;
3130 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3131 g_return_val_if_fail (r != NULL, NULL);
3133 foreach_tile (sheet, r, cb_find_link, &res);
3134 return res;
3138 * sheet_style_foreach:
3139 * @sheet: #Sheet
3140 * @func: (scope call): callback
3141 * @user_data: user data.
3143 * Executes @func for each style in the sheet.
3145 void
3146 sheet_style_foreach (Sheet const *sheet, GFunc func, gpointer user_data)
3148 GSList *styles;
3150 g_return_if_fail (IS_SHEET (sheet));
3151 g_return_if_fail (sheet->style_data != NULL);
3153 styles = sh_all_styles (sheet->style_data->style_hash);
3154 styles = g_slist_sort (styles, (GCompareFunc)gnm_style_cmp);
3155 g_slist_foreach (styles, func, user_data);
3156 g_slist_free (styles);
3160 * sheet_style_range_foreach:
3161 * @sheet: #Sheet
3162 * @r: optional range
3163 * @func: (scope call): callback.
3164 * @user_data: user data
3167 void
3168 sheet_style_range_foreach (Sheet const *sheet, GnmRange const *r,
3169 GHFunc func, gpointer user_data)
3171 GnmStyleList *styles, *l;
3173 styles = sheet_style_get_range (sheet, r);
3175 for (l = styles; l; l = l->next) {
3176 GnmStyleRegion *sr = l->data;
3177 if (r) {
3178 sr->range.start.col += r->start.col;
3179 sr->range.start.row += r->start.row;
3180 sr->range.end.col += r->start.col;
3181 sr->range.end.row += r->start.row;
3183 func (NULL, sr, user_data);
3184 gnm_style_region_free (sr);
3187 g_slist_free (styles);
3190 /* ------------------------------------------------------------------------- */
3192 static void
3193 cell_tile_dump (CellTile **tile, int level, CellTileOptimize *data,
3194 int ccol, int crow)
3196 CellTileType type;
3197 int const w = tile_widths[level];
3198 int const h = tile_heights[level];
3199 GnmRange rng;
3200 const char *indent = "";
3202 type = (*tile)->type;
3204 range_init (&rng,
3205 ccol, crow,
3206 MIN (ccol + tile_widths[level + 1] - 1,
3207 data->ss->max_cols - 1),
3208 MIN (crow + tile_heights[level + 1] - 1,
3209 data->ss->max_rows - 1));
3211 g_printerr ("%s%s: type %s\n",
3212 indent,
3213 range_as_string (&rng),
3214 tile_type_str[type]);
3216 if (type == TILE_PTR_MATRIX) {
3217 int i;
3219 for (i = 0; i < TILE_SIZE_COL * TILE_SIZE_ROW; i++) {
3220 CellTile **subtile = (*tile)->ptr_matrix.ptr + i;
3221 int c = i % TILE_SIZE_COL;
3222 int r = i / TILE_SIZE_COL;
3223 cell_tile_dump (subtile, level - 1, data,
3224 ccol + w * c,
3225 crow + h * r);
3227 } else {
3228 #if 0
3229 int i;
3231 for (i = 0; i < tile_size[type]; i++) {
3232 g_printerr ("%s: %d %p\n",
3233 indent,
3235 (*tile)->style_any.style[i]);
3237 #endif
3241 /* ------------------------------------------------------------------------- */
3243 static void
3244 cell_tile_optimize (CellTile **tile, int level, CellTileOptimize *data,
3245 int ccol, int crow)
3247 CellTileType type;
3248 int const w = tile_widths[level];
3249 int const h = tile_heights[level];
3250 CellTile *res;
3251 int i;
3252 GnmRange rng;
3254 type = (*tile)->type;
3255 if (type == TILE_SIMPLE)
3256 return;
3258 range_init (&rng,
3259 ccol, crow,
3260 MIN (ccol + tile_widths[level + 1] - 1,
3261 data->ss->max_cols - 1),
3262 MIN (crow + tile_heights[level + 1] - 1,
3263 data->ss->max_rows - 1));
3265 switch (type) {
3266 case TILE_COL:
3267 case TILE_ROW:
3268 if (!tile_is_uniform (*tile))
3269 return;
3271 type = TILE_SIMPLE;
3272 break;
3274 case TILE_PTR_MATRIX: {
3275 gboolean all_simple = TRUE;
3276 int i;
3278 for (i = 0; i < TILE_SIZE_COL * TILE_SIZE_ROW; i++) {
3279 CellTile **subtile = (*tile)->ptr_matrix.ptr + i;
3280 if (data->recursion) {
3281 int c = i % TILE_SIZE_COL;
3282 int r = i / TILE_SIZE_COL;
3283 cell_tile_optimize (subtile, level - 1, data,
3284 ccol + w * c,
3285 crow + h * r);
3287 if ((*subtile)->type != TILE_SIMPLE)
3288 all_simple = FALSE;
3290 if (!all_simple)
3291 return;
3293 res = cell_tile_style_new (NULL, TILE_MATRIX);
3294 for (i = 0; i < TILE_SIZE_COL * TILE_SIZE_ROW; i++) {
3295 CellTile *subtile = (*tile)->ptr_matrix.ptr[i];
3296 GnmStyle *st = subtile->style_simple.style[0];
3297 gnm_style_link (st);
3298 res->style_matrix.style[i] = st;
3301 if (debug_style_optimize)
3302 g_printerr ("Turning %s (%dx%d) from a %s into a %s\n",
3303 range_as_string (&rng),
3304 range_width (&rng), range_height (&rng),
3305 tile_type_str[(*tile)->type],
3306 tile_type_str[res->type]);
3307 cell_tile_dtor (*tile);
3308 *tile = res;
3310 /* Fall through */
3313 case TILE_MATRIX: {
3314 gboolean csame = TRUE;
3315 gboolean rsame = TRUE;
3316 int c, r, i;
3318 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r, i += TILE_SIZE_COL) {
3319 for (c = 0 ; c < TILE_SIZE_COL ; ++c) {
3320 if (rsame && c &&
3321 !gnm_style_eq ((*tile)->style_matrix.style[i + c],
3322 (*tile)->style_matrix.style[i ])) {
3323 rsame = FALSE;
3324 if (!csame)
3325 return;
3327 if (csame && r &&
3328 !gnm_style_eq ((*tile)->style_matrix.style[i + c],
3329 (*tile)->style_matrix.style[ c])) {
3330 csame = FALSE;
3331 if (!rsame)
3332 return;
3337 if (csame && rsame)
3338 type = TILE_SIMPLE;
3339 else if (csame) {
3340 type = TILE_COL;
3341 } else {
3342 type = TILE_ROW;
3344 break;
3347 default:
3348 g_assert_not_reached ();
3351 if (debug_style_optimize)
3352 g_printerr ("Turning %s (%dx%d) from a %s into a %s\n",
3353 range_as_string (&rng),
3354 range_width (&rng), range_height (&rng),
3355 tile_type_str[(*tile)->type],
3356 tile_type_str[type]);
3358 res = cell_tile_style_new (NULL, type);
3359 switch (type) {
3360 case TILE_SIMPLE:
3361 res->style_simple.style[0] = (*tile)->style_any.style[0];
3362 break;
3363 case TILE_ROW:
3364 for (i = 0; i < TILE_SIZE_ROW; i++)
3365 res->style_row.style[i] =
3366 (*tile)->style_matrix.style[i * TILE_SIZE_COL];
3367 break;
3368 case TILE_COL:
3369 for (i = 0; i < TILE_SIZE_COL; i++)
3370 res->style_col.style[i] =
3371 (*tile)->style_matrix.style[i];
3372 break;
3373 default:
3374 g_assert_not_reached ();
3377 for (i = 0; i < tile_size[type]; i++)
3378 gnm_style_link (res->style_any.style[i]);
3380 cell_tile_dtor (*tile);
3381 *tile = res;
3384 static GSList *
3385 sample_styles (Sheet *sheet)
3387 GnmSheetSize const *ss = gnm_sheet_get_size (sheet);
3388 GSList *res = NULL;
3389 int c = 0, r = 0;
3390 const int SKIP = 1;
3392 while (1) {
3393 GnmStyle const *mstyle = sheet_style_get (sheet, c, r);
3394 if (res == NULL || !gnm_style_eq (mstyle, res->data)) {
3395 gnm_style_ref (mstyle);
3396 res = g_slist_prepend (res, GINT_TO_POINTER (c));
3397 res = g_slist_prepend (res, GINT_TO_POINTER (r));
3398 res = g_slist_prepend (res, (gpointer)mstyle);
3401 c += SKIP;
3402 if (c >= ss->max_cols) {
3403 c -= ss->max_cols;
3404 r++;
3405 if (r >= ss->max_rows)
3406 break;
3410 return g_slist_reverse (res);
3413 static void
3414 verify_styles (GSList *pre, GSList *post)
3416 GSList *lpre, *lpost;
3417 gboolean silent = FALSE, bad = FALSE;
3419 for (lpre = pre, lpost = post;
3420 lpre || lpost;
3421 lpre = (lpre ? lpre->next->next->next : NULL),
3422 lpost = (lpost ? lpost->next->next->next : NULL)) {
3423 int cpre = lpre ? GPOINTER_TO_INT (lpre->data) : -1;
3424 int rpre = lpre ? GPOINTER_TO_INT (lpre->next->data) : -1;
3425 GnmStyle const *spre = lpre ? lpre->next->next->data : NULL;
3426 int cpost = lpost ? GPOINTER_TO_INT (lpost->data) : -1;
3427 int rpost = lpost ? GPOINTER_TO_INT (lpost->next->data) : -1;
3428 GnmStyle const *spost = lpost ? lpost->next->next->data : NULL;
3430 if (!silent) {
3431 if (!spre || !spost) {
3432 bad = TRUE;
3433 g_warning ("Style optimizer failure at end!");
3434 silent = TRUE;
3435 } else if (cpre != cpost || rpre != rpost) {
3436 bad = TRUE;
3437 g_warning ("Style optimizer position conflict at %s!",
3438 cell_coord_name (cpre, rpre));
3439 silent = TRUE;
3440 } else if (!gnm_style_eq (spre, spost)) {
3441 bad = TRUE;
3442 g_warning ("Style optimizer failure at %s!",
3443 cell_coord_name (cpre, rpre));
3447 if (spre) gnm_style_unref (spre);
3448 if (spost) gnm_style_unref (spost);
3451 g_slist_free (pre);
3452 g_slist_free (post);
3454 g_assert (!bad);
3457 void
3458 sheet_style_optimize (Sheet *sheet)
3460 CellTileOptimize data;
3461 GSList *pre;
3462 gboolean verify;
3464 g_return_if_fail (IS_SHEET (sheet));
3466 if (gnm_debug_flag ("no-style-optimize"))
3467 return;
3469 sheet_colrow_optimize (sheet);
3471 data.ss = gnm_sheet_get_size (sheet);
3472 data.recursion = TRUE;
3474 if (debug_style_optimize) {
3475 g_printerr ("Optimizing %s\n", sheet->name_unquoted);
3476 cell_tile_dump (&sheet->style_data->styles,
3477 sheet->tile_top_level, &data,
3478 0, 0);
3481 verify = gnm_debug_flag ("style-optimize-verify");
3482 pre = verify ? sample_styles (sheet) : NULL;
3484 cell_tile_optimize (&sheet->style_data->styles,
3485 sheet->tile_top_level, &data,
3486 0, 0);
3488 if (debug_style_optimize)
3489 g_printerr ("Optimizing %s...done\n", sheet->name_unquoted);
3491 if (verify) {
3492 GSList *post = sample_styles (sheet);
3493 verify_styles (pre, post);