Update Spanish translation
[gnumeric.git] / src / sheet-style.c
blob7d3fdf2e53fc500b93bba1d8d976e7709829bec5
1 /*
2 * sheet-style.c: storage mechanism for styles and eventually cells.
4 * Copyright (C) 2000-2006 Jody Goldberg (jody@gnome.org)
5 * Copyright 2013 Morten Welinder (terra@gnome.org)
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) version 3.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20 * USA
23 #include <gnumeric-config.h>
24 #include <sheet-style.h>
25 #include <ranges.h>
26 #include <sheet.h>
27 #include <expr.h>
28 #include <style.h>
29 #include <style-border.h>
30 #include <style-color.h>
31 #include <style-conditions.h>
32 #include <parse-util.h>
33 #include <cell.h>
34 #include <gutils.h>
35 #include <goffice/goffice.h>
36 #include <glib/gi18n-lib.h>
37 #include <string.h>
38 #include <math.h>
40 #define USE_TILE_POOLS 0
42 /* ------------------------------------------------------------------------- */
45 * This is, essentially, an std::multiset implementation for the style hash.
46 * Note, however, that sh_lookup is based on gnm_style_equal, not gnm_style_eq.
48 typedef GHashTable GnmStyleHash;
50 #if 0
51 /* This is a really crummy hash -- except for forcing collisions. */
52 #define gnm_style_hash(st) 0
53 #endif
55 static void
56 sh_remove (GnmStyleHash *h, GnmStyle *st)
58 guint32 hv = gnm_style_hash (st);
59 GSList *l = g_hash_table_lookup (h, GUINT_TO_POINTER (hv));
61 g_return_if_fail (l != NULL);
63 if (l->data == st) {
64 GSList *next = l->next;
65 if (next) {
66 /* We're removing the first of several elements. */
67 l->next = NULL;
68 g_hash_table_replace (h, GUINT_TO_POINTER (hv), next);
69 } else {
70 /* We're removing the last element. */
71 g_hash_table_remove (h, GUINT_TO_POINTER (hv));
73 } else {
74 /* We're removing an element that isn't first. */
75 l = g_slist_remove (l, st);
79 static GnmStyle *
80 sh_lookup (GnmStyleHash *h, GnmStyle *st)
82 guint32 hv = gnm_style_hash (st);
83 GSList *l = g_hash_table_lookup (h, GUINT_TO_POINTER (hv));
84 while (l) {
85 GnmStyle *st2 = l->data;
86 /* NOTE: This uses gnm_style_equal, not gnm_style_eq. */
87 if (gnm_style_equal (st, st2))
88 return st2;
89 l = l->next;
91 return NULL;
94 static void
95 sh_insert (GnmStyleHash *h, GnmStyle *st)
97 GSList *s = g_slist_prepend (NULL, st);
98 guint32 hv = gnm_style_hash (st);
99 GSList *l = g_hash_table_lookup (h, GUINT_TO_POINTER (hv));
100 if (l) {
101 s->next = l->next;
102 l->next = s;
103 } else {
104 g_hash_table_insert (h, GUINT_TO_POINTER (hv), s);
108 static GSList *
109 sh_all_styles (GnmStyleHash *h)
111 GHashTableIter iter;
112 gpointer value;
113 GSList *res = NULL;
115 g_hash_table_iter_init (&iter, h);
116 while (g_hash_table_iter_next (&iter, NULL, &value)) {
117 GSList *l = value;
118 for (; l; l = l->next)
119 res = g_slist_prepend (res, l->data);
122 return res;
125 static GnmStyleHash *
126 sh_create (void)
128 return g_hash_table_new_full (g_direct_hash, g_direct_equal,
129 NULL, (GDestroyNotify)g_slist_free);
132 static void
133 sh_destroy (GnmStyleHash *h)
135 g_hash_table_destroy (h);
138 /* ------------------------------------------------------------------------- */
140 typedef union _CellTile CellTile;
141 struct _GnmSheetStyleData {
143 * style_hash is a set of all styles used by this sheet. These
144 * styles are all linked.
146 * We always re-use styles from here when we can, but there can
147 * still be duplicates. This happens when styles are changed
148 * while they are in the hash. For example, this happens when
149 * an expression used by a validation style changes due to
150 * row/col insert/delete.
152 GnmStyleHash *style_hash;
154 CellTile *styles;
155 GnmStyle *default_style;
156 GnmColor *auto_pattern_color;
159 static gboolean debug_style_optimize;
161 typedef struct {
162 GnmSheetSize const *ss;
163 gboolean recursion;
164 } CellTileOptimize;
166 static void
167 cell_tile_optimize (CellTile **tile, int level, CellTileOptimize *data,
168 int ccol, int crow);
172 * sheet_style_unlink
173 * For internal use only
175 void
176 sheet_style_unlink (Sheet *sheet, GnmStyle *st)
178 if (sheet->style_data->style_hash)
179 sh_remove (sheet->style_data->style_hash, st);
183 * sheet_style_find:
184 * @sheet: (transfer full): the sheet
185 * @st: (transfer full): a style
187 * Looks up a style from the sheets collection. Linking if necessary.
189 * Returns: (transfer full): the new style.
191 GnmStyle *
192 sheet_style_find (Sheet const *sheet, GnmStyle *s)
194 GnmStyle *res;
195 res = sh_lookup (sheet->style_data->style_hash, s);
196 if (res != NULL) {
197 gnm_style_link (res);
198 gnm_style_unref (s);
199 return res;
202 s = gnm_style_link_sheet (s, (Sheet *)sheet);
204 /* Retry the lookup in case "s" changed. See #585178. */
205 res = sh_lookup (sheet->style_data->style_hash, s);
206 if (res != NULL) {
207 gnm_style_link (res);
209 * We are abandoning the linking here. We cannot use
210 * gnm_style_unlink as that would call sheet_style_unlink
211 * and thus remove "res" from the hash.
213 gnm_style_abandon_link (s);
214 gnm_style_unref (s);
216 return res;
219 sh_insert (sheet->style_data->style_hash, s);
220 return s;
223 /* Place holder until I merge in the new styles too */
224 static void
225 pstyle_set_border (GnmStyle *st, GnmBorder *border,
226 GnmStyleBorderLocation side)
228 gnm_style_set_border (st,
229 GNM_STYLE_BORDER_LOCATION_TO_STYLE_ELEMENT (side),
230 gnm_style_border_ref (border));
233 /* Amortize the cost of applying a partial style over a large region
234 * by caching and rereferencing the merged result for repeated styles.
236 typedef struct {
237 GnmStyle *new_style;
238 GnmStyle *pstyle;
239 GHashTable *cache;
240 Sheet *sheet;
241 } ReplacementStyle;
243 static void
244 rstyle_ctor_style (ReplacementStyle *res, GnmStyle *new_style, Sheet *sheet)
246 res->sheet = sheet;
247 res->new_style = sheet_style_find (sheet, new_style);
248 res->pstyle = NULL;
249 res->cache = NULL;
252 static void
253 rstyle_ctor_pstyle (ReplacementStyle *res, GnmStyle *pstyle, Sheet *sheet)
255 res->sheet = sheet;
256 res->new_style = NULL;
257 res->pstyle = pstyle;
258 res->cache = g_hash_table_new (g_direct_hash, g_direct_equal);
261 static void
262 cb_style_unlink (gpointer key, gpointer value, G_GNUC_UNUSED gpointer user_data)
264 gnm_style_unlink ((GnmStyle *)key);
265 gnm_style_unlink ((GnmStyle *)value);
268 static void
269 rstyle_dtor (ReplacementStyle *rs)
271 if (rs->cache != NULL) {
272 g_hash_table_foreach (rs->cache, cb_style_unlink, NULL);
273 g_hash_table_destroy (rs->cache);
274 rs->cache = NULL;
276 if (rs->new_style != NULL) {
277 gnm_style_unlink (rs->new_style);
278 rs->new_style = NULL;
280 if (rs->pstyle != NULL) {
281 gnm_style_unref (rs->pstyle);
282 rs->pstyle = NULL;
287 * rstyle_apply: Utility routine that is at the core of applying partial
288 * styles or storing complete styles. It will eventually be smarter
289 * and will maintain the cache of styles associated with each sheet
291 static void
292 rstyle_apply (GnmStyle **old, ReplacementStyle *rs, GnmRange const *r)
294 GnmStyle *s;
295 g_return_if_fail (old != NULL);
296 g_return_if_fail (rs != NULL);
298 if (rs->pstyle != NULL) {
299 /* Cache the merged styles keeping a reference to the originals
300 * just in case all instances change.
302 s = g_hash_table_lookup (rs->cache, *old);
303 if (s == NULL) {
304 GnmStyle *tmp = gnm_style_new_merged (*old, rs->pstyle);
305 s = sheet_style_find (rs->sheet, tmp);
306 gnm_style_link (*old);
307 g_hash_table_insert (rs->cache, *old, s);
309 } else
310 s = rs->new_style;
312 if (*old != s) {
313 if (*old) {
314 gnm_style_unlink_dependents (*old, r);
315 gnm_style_unlink (*old);
318 gnm_style_link_dependents (s, r);
319 gnm_style_link (s);
321 *old = s;
325 void
326 sheet_style_clear_style_dependents (Sheet *sheet, GnmRange const *r)
328 GSList *styles = sh_all_styles (sheet->style_data->style_hash);
329 g_slist_foreach (styles,
330 (GFunc)gnm_style_unlink_dependents,
331 (gpointer)r);
332 g_slist_free (styles);
336 /****************************************************************************/
338 /* If you change this, change the tile_{widths,heights} here
339 * and GNM_MAX_COLS and GNM_MAX_ROWS in gnumeric.h */
340 #define TILE_TOP_LEVEL 6
342 #define TILE_SIZE_COL 8
343 #define TILE_SIZE_ROW 16
345 typedef enum {
346 TILE_UNDEFINED = -1,
347 TILE_SIMPLE = 0,
348 TILE_COL = 1,
349 TILE_ROW = 2,
350 TILE_MATRIX = 3,
351 TILE_PTR_MATRIX = 4
352 } CellTileType;
353 static int const tile_size[/*type*/] = {
354 1, /* TILE_SIMPLE */
355 TILE_SIZE_COL, /* TILE_COL */
356 TILE_SIZE_ROW, /* TILE_ROW */
357 TILE_SIZE_COL * TILE_SIZE_ROW /* TILE_MATRIX */
359 static int const tile_col_count[/*type*/] = {
360 1, /* TILE_SIMPLE */
361 TILE_SIZE_COL, /* TILE_COL */
362 1, /* TILE_ROW */
363 TILE_SIZE_COL, /* TILE_MATRIX */
364 TILE_SIZE_COL /* TILE_PTR_MATRIX */
366 static int const tile_row_count[/*type*/] = {
367 1, /* TILE_SIMPLE */
368 1, /* TILE_COL */
369 TILE_SIZE_ROW, /* TILE_ROW */
370 TILE_SIZE_ROW, /* TILE_MATRIX */
371 TILE_SIZE_ROW /* TILE_PTR_MATRIX */
373 static const char * const tile_type_str[/*type*/] = {
374 "simple", "col", "row", "matrix", "ptr-matrix"
376 static int const tile_widths[/*level*/] = {
378 TILE_SIZE_COL,
379 TILE_SIZE_COL * TILE_SIZE_COL,
380 TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL,
381 TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL,
382 TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL,
383 TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL,
384 TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL
386 static int const tile_heights[/*level*/] = {
388 TILE_SIZE_ROW,
389 TILE_SIZE_ROW * TILE_SIZE_ROW,
390 TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW,
391 TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW,
392 TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW,
393 TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW,
394 TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW
397 typedef struct {
398 CellTileType const type;
399 GnmStyle *style[1];
400 } CellTileStyleSimple;
401 typedef struct {
402 CellTileType const type;
403 GnmStyle *style[TILE_SIZE_COL];
404 } CellTileStyleCol;
405 typedef struct {
406 CellTileType const type;
407 GnmStyle *style[TILE_SIZE_ROW];
408 } CellTileStyleRow;
409 typedef struct {
410 CellTileType const type;
411 GnmStyle *style[TILE_SIZE_COL * TILE_SIZE_ROW];
412 } CellTileStyleMatrix;
413 typedef struct {
414 CellTileType const type;
415 CellTile *ptr[TILE_SIZE_COL * TILE_SIZE_ROW];
416 } CellTilePtrMatrix;
418 union _CellTile {
419 CellTileType const type;
420 CellTileStyleSimple style_any;
421 CellTileStyleSimple style_simple;
422 CellTileStyleCol style_col;
423 CellTileStyleRow style_row;
424 CellTileStyleMatrix style_matrix;
425 CellTilePtrMatrix ptr_matrix;
428 static int active_sheet_count;
429 #if USE_TILE_POOLS
430 static GOMemChunk *tile_pools[5];
431 #define CHUNK_ALLOC(T,ctt) ((T*)go_mem_chunk_alloc (tile_pools[(ctt)]))
432 #define CHUNK_FREE(ctt,v) go_mem_chunk_free (tile_pools[(ctt)], (v))
433 #else
434 static const size_t tile_type_sizeof[5] = {
435 sizeof (CellTileStyleSimple),
436 sizeof (CellTileStyleCol),
437 sizeof (CellTileStyleRow),
438 sizeof (CellTileStyleMatrix),
439 sizeof (CellTilePtrMatrix)
441 static int tile_allocations = 0;
442 #if 1
443 #define CHUNK_ALLOC(T,ctt) (tile_allocations++, (T*)g_slice_alloc (tile_type_sizeof[(ctt)]))
444 #define CHUNK_FREE(ctt,v) (tile_allocations--, g_slice_free1 (tile_type_sizeof[(ctt)], (v)))
445 #else
446 #define CHUNK_ALLOC(T,ctt) (tile_allocations++, (T*)g_malloc (tile_type_sizeof[(ctt)]))
447 #define CHUNK_FREE(ctt,v) (tile_allocations--, g_free ((v)))
448 #endif
449 #endif
453 * Destroy a CellTile (recursively if needed). This will unlink all the
454 * styles in it. We do _not_ unlink style dependents here. That is done
455 * only in rstyle_apply.
457 static void
458 cell_tile_dtor (CellTile *tile)
460 CellTileType t;
462 g_return_if_fail (tile != NULL);
464 t = tile->type;
465 if (t == TILE_PTR_MATRIX) {
466 int i = TILE_SIZE_COL * TILE_SIZE_ROW;
467 while (--i >= 0) {
468 cell_tile_dtor (tile->ptr_matrix.ptr[i]);
469 tile->ptr_matrix.ptr[i] = NULL;
471 } else if (TILE_SIMPLE <= t && t <= TILE_MATRIX) {
472 int i = tile_size[t];
473 while (--i >= 0) {
474 gnm_style_unlink (tile->style_any.style[i]);
475 tile->style_any.style[i] = NULL;
477 } else {
478 g_return_if_fail (FALSE); /* don't free anything */
481 *((CellTileType *)&(tile->type)) = TILE_UNDEFINED; /* poison it */
482 CHUNK_FREE (t, tile);
485 static CellTile *
486 cell_tile_style_new (GnmStyle *style, CellTileType t)
488 CellTile *res = CHUNK_ALLOC (CellTile, t);
489 *((CellTileType *)&(res->type)) = t;
491 if (style != NULL) {
492 int i = tile_size[t];
493 gnm_style_link_multiple (style, i);
494 while (--i >= 0)
495 res->style_any.style[i] = style;
498 return res;
501 static CellTile *
502 cell_tile_ptr_matrix_new (CellTile *t)
504 CellTilePtrMatrix *res;
506 g_return_val_if_fail (t != NULL, NULL);
507 g_return_val_if_fail (TILE_SIMPLE <= t->type &&
508 TILE_MATRIX >= t->type, NULL);
510 res = CHUNK_ALLOC (CellTilePtrMatrix, TILE_PTR_MATRIX);
511 *((CellTileType *)&(res->type)) = TILE_PTR_MATRIX;
513 /* TODO:
514 * If we wanted to get fancy we could use self similarity to decrease
515 * the number of subtiles. However, this would increase the cost of
516 * applying changes later so I'm not sure it is worth the effort.
518 switch (t->type) {
519 case TILE_SIMPLE: {
520 int i = TILE_SIZE_COL * TILE_SIZE_ROW;
521 while (--i >= 0)
522 res->ptr[i] = cell_tile_style_new (
523 t->style_simple.style[0], TILE_SIMPLE);
524 break;
526 case TILE_COL: {
527 int i, r, c;
528 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r)
529 for (c = 0 ; c < TILE_SIZE_COL ; ++c)
530 res->ptr[i++] = cell_tile_style_new (
531 t->style_col.style[c], TILE_SIMPLE);
532 break;
534 case TILE_ROW: {
535 int i, r, c;
536 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r)
537 for (c = 0 ; c < TILE_SIZE_COL ; ++c)
538 res->ptr[i++] = cell_tile_style_new (
539 t->style_row.style[r], TILE_SIMPLE);
540 break;
542 case TILE_MATRIX: {
543 int i = TILE_SIZE_COL * TILE_SIZE_ROW;
544 while (--i >= 0)
545 res->ptr[i] = cell_tile_style_new (
546 t->style_matrix.style[i], TILE_SIMPLE);
547 break;
549 default: ;
552 return (CellTile *)res;
555 static CellTile *
556 cell_tile_matrix_set (CellTile *t)
558 int r, c;
559 CellTileStyleMatrix *res;
561 g_return_val_if_fail (t != NULL, NULL);
562 g_return_val_if_fail (TILE_SIMPLE <= t->type &&
563 TILE_MATRIX >= t->type, NULL);
565 if (t->type == TILE_MATRIX)
566 return t;
568 res = (CellTileStyleMatrix *)cell_tile_style_new (NULL, TILE_MATRIX);
570 switch (t->type) {
571 case TILE_SIMPLE: {
572 GnmStyle *tmp = t->style_simple.style[0];
573 int i = TILE_SIZE_COL * TILE_SIZE_ROW;
574 gnm_style_link_multiple (tmp, i);
575 while (--i >= 0)
576 res->style[i] = tmp;
577 break;
580 case TILE_COL: {
581 int i = 0;
582 for (r = 0; r < TILE_SIZE_ROW; ++r)
583 for (c = 0; c < TILE_SIZE_COL; ++c)
584 gnm_style_link (res->style[i++] =
585 t->style_col.style[c]);
586 break;
589 case TILE_ROW: {
590 int i = 0;
591 for (r = 0; r < TILE_SIZE_ROW; ++r) {
592 GnmStyle *tmp = t->style_row.style[r];
593 gnm_style_link_multiple (tmp, TILE_SIZE_COL);
594 for (c = 0; c < TILE_SIZE_COL; ++c)
595 res->style[i++] = tmp;
597 break;
600 case TILE_MATRIX:
601 default:
602 g_assert_not_reached();
605 cell_tile_dtor (t);
607 return (CellTile *)res;
610 /****************************************************************************/
612 static void
613 sheet_style_sanity_check (void)
615 unsigned c, r;
616 int i;
618 for (c = 1, i = 0; i <= TILE_TOP_LEVEL; i++) {
619 g_assert (c < G_MAXUINT / TILE_SIZE_COL);
620 c *= TILE_SIZE_COL;
622 g_assert (c >= GNM_MAX_COLS);
624 for (r = 1, i = 0; i <= TILE_TOP_LEVEL; i++) {
625 g_assert (r < G_MAXUINT / TILE_SIZE_COL);
626 r *= TILE_SIZE_ROW;
628 g_assert (r >= GNM_MAX_ROWS);
630 g_assert (G_N_ELEMENTS (tile_heights) > TILE_TOP_LEVEL + 1);
632 g_assert (G_N_ELEMENTS (tile_widths) > TILE_TOP_LEVEL + 1);
635 static void
636 sheet_style_init_size (Sheet *sheet, int cols, int rows)
638 GnmStyle *default_style;
639 int lc = 0, lr = 0, w = TILE_SIZE_COL, h = TILE_SIZE_ROW;
641 while (w < cols) {
642 w *= TILE_SIZE_COL;
643 lc++;
645 while (h < rows) {
646 h *= TILE_SIZE_ROW;
647 lr++;
649 sheet->tile_top_level = MAX (lc, lr);
651 if (active_sheet_count++ == 0) {
652 #if USE_TILE_POOLS
653 tile_pools[TILE_SIMPLE] =
654 go_mem_chunk_new ("simple tile pool",
655 sizeof (CellTileStyleSimple),
656 16 * 1024 - 128);
657 tile_pools[TILE_COL] =
658 go_mem_chunk_new ("column tile pool",
659 sizeof (CellTileStyleCol),
660 16 * 1024 - 128);
661 tile_pools[TILE_ROW] =
662 go_mem_chunk_new ("row tile pool",
663 sizeof (CellTileStyleRow),
664 16 * 1024 - 128);
665 tile_pools[TILE_MATRIX] =
666 go_mem_chunk_new ("matrix tile pool",
667 sizeof (CellTileStyleMatrix),
668 MAX (16 * 1024 - 128,
669 100 * sizeof (CellTileStyleMatrix)));
671 /* If this fails one day, just make two pools. */
672 g_assert (sizeof (CellTileStyleMatrix) == sizeof (CellTilePtrMatrix));
673 tile_pools[TILE_PTR_MATRIX] = tile_pools[TILE_MATRIX];
674 #endif
677 sheet->style_data = g_new (GnmSheetStyleData, 1);
678 sheet->style_data->style_hash = sh_create ();
680 sheet->style_data->auto_pattern_color = style_color_auto_pattern ();
682 default_style = gnm_style_new_default ();
683 #if 0
684 /* We can not do this, XL creates full page charts with background
685 * 'none' by default. Then displays that as white. */
686 if (sheet->sheet_type == GNM_SHEET_OBJECT) {
687 gnm_style_set_back_color (default_style,
688 gnm_color_new_rgb8 (0x50, 0x50, 0x50));
689 gnm_style_set_pattern (default_style, 1);
691 #endif
692 sheet->style_data->default_style =
693 sheet_style_find (sheet, default_style);
694 sheet->style_data->styles =
695 cell_tile_style_new (sheet->style_data->default_style,
696 TILE_SIMPLE);
699 void
700 sheet_style_init (Sheet *sheet)
702 int cols = gnm_sheet_get_max_cols (sheet);
703 int rows = gnm_sheet_get_max_rows (sheet);
705 debug_style_optimize = gnm_debug_flag ("style-optimize");
707 sheet_style_sanity_check ();
709 sheet_style_init_size (sheet, cols, rows);
712 void
713 sheet_style_resize (Sheet *sheet, int cols, int rows)
715 GnmStyleList *styles, *l;
716 int old_cols = gnm_sheet_get_max_cols (sheet);
717 int old_rows = gnm_sheet_get_max_rows (sheet);
718 GnmRange save_range, new_full;
720 /* Save the style for the surviving area. */
721 range_init (&save_range, 0, 0,
722 MIN (cols, old_cols) - 1, MIN (rows, old_rows) - 1);
723 styles = sheet_style_get_range (sheet, &save_range);
725 /* Build new empty structures. */
726 sheet_style_shutdown (sheet);
727 sheet_style_init_size (sheet, cols, rows);
729 /* Reapply styles. */
730 range_init (&new_full, 0, 0, cols - 1, rows - 1);
731 for (l = styles; l; l = l->next) {
732 GnmStyleRegion const *sr = l->data;
733 GnmRange const *r = &sr->range;
734 GnmStyle *style = sr->style;
735 GnmRange newr;
736 if (range_intersection (&newr, r, &new_full))
737 sheet_style_apply_range2 (sheet, &newr, style);
740 style_list_free (styles);
743 #if USE_TILE_POOLS
744 static void
745 cb_tile_pool_leak (gpointer data, gpointer user)
747 CellTile *tile = data;
748 g_printerr ("Leaking tile at %p.\n", (void *)tile);
750 #endif
752 void
753 sheet_style_shutdown (Sheet *sheet)
755 GnmStyleHash *table;
756 GnmRange r;
758 g_return_if_fail (IS_SHEET (sheet));
759 g_return_if_fail (sheet->style_data != NULL);
762 * Clear all styles. This is an easy way to clear out all
763 * style dependencies.
765 range_init_full_sheet (&r, sheet);
766 sheet_style_set_range (sheet, &r, sheet_style_default (sheet));
768 cell_tile_dtor (sheet->style_data->styles);
769 sheet->style_data->styles = NULL;
771 sheet->style_data->default_style = NULL;
773 /* Clear the pointer to the hash BEFORE clearing and add a test in
774 * sheet_style_unlink. If we don't then it is possible/probable that
775 * unlinking the styles will attempt to remove them from the hash while
776 * we are walking it.
778 table = sheet->style_data->style_hash;
779 sheet->style_data->style_hash = NULL;
780 g_slist_free_full (sh_all_styles (table),
781 (GDestroyNotify)gnm_style_unlink);
782 sh_destroy (table);
783 style_color_unref (sheet->style_data->auto_pattern_color);
785 g_free (sheet->style_data);
786 sheet->style_data = NULL;
788 if (--active_sheet_count == 0) {
789 #if USE_TILE_POOLS
790 go_mem_chunk_foreach_leak (tile_pools[TILE_SIMPLE],
791 cb_tile_pool_leak, NULL);
792 go_mem_chunk_destroy (tile_pools[TILE_SIMPLE], FALSE);
793 tile_pools[TILE_SIMPLE] = NULL;
795 go_mem_chunk_foreach_leak (tile_pools[TILE_COL],
796 cb_tile_pool_leak, NULL);
797 go_mem_chunk_destroy (tile_pools[TILE_COL], FALSE);
798 tile_pools[TILE_COL] = NULL;
800 go_mem_chunk_foreach_leak (tile_pools[TILE_ROW],
801 cb_tile_pool_leak, NULL);
802 go_mem_chunk_destroy (tile_pools[TILE_ROW], FALSE);
803 tile_pools[TILE_ROW] = NULL;
805 go_mem_chunk_foreach_leak (tile_pools[TILE_MATRIX],
806 cb_tile_pool_leak, NULL);
807 go_mem_chunk_destroy (tile_pools[TILE_MATRIX], FALSE);
808 tile_pools[TILE_MATRIX] = NULL;
810 /* If this fails one day, just make two pools. */
811 g_assert (sizeof (CellTileStyleMatrix) == sizeof (CellTilePtrMatrix));
812 tile_pools[TILE_PTR_MATRIX] = NULL;
813 #else
814 if (tile_allocations)
815 g_printerr ("Leaking %d style tiles.\n", tile_allocations);
816 #endif
821 * sheet_style_set_auto_pattern_color:
822 * @sheet: The sheet
823 * @grid_color: (transfer full): The color
825 * Set the color for rendering auto colored patterns in this sheet.
827 void
828 sheet_style_set_auto_pattern_color (Sheet *sheet, GnmColor *pattern_color)
830 g_return_if_fail (IS_SHEET (sheet));
831 g_return_if_fail (sheet->style_data != NULL);
833 style_color_unref (sheet->style_data->auto_pattern_color);
834 sheet->style_data->auto_pattern_color = gnm_color_new_auto (pattern_color->go_color);
835 style_color_unref (pattern_color);
839 * sheet_style_get_auto_pattern_color:
840 * @sheet: the sheet
842 * Returns: (transfer full): the color for rendering auto colored patterns
843 * in this sheet.
845 GnmColor *
846 sheet_style_get_auto_pattern_color (Sheet const *sheet)
848 GnmColor *sc;
849 g_return_val_if_fail (IS_SHEET (sheet), style_color_black ());
850 g_return_val_if_fail (sheet->style_data != NULL, style_color_black ());
851 g_return_val_if_fail (sheet->style_data->auto_pattern_color != NULL,
852 style_color_black ());
854 sc = sheet->style_data->auto_pattern_color;
855 style_color_ref (sc);
857 return sc;
861 * sheet_style_update_grid_color:
863 * This function updates the color of gnm_style_border_none when the sheet to be
864 * rendered is known. gnm_style_border_none tells how to render the
865 * grid. Because the grid color may be different for different sheets, the
866 * functions which render the grid call this function first. The rule for
867 * selecting the grid color, which is the same as in Excel, is: - if the
868 * auto pattern color is default (which is black), the grid color is gray,
869 * as returned by style_color_grid (). - otherwise, the auto pattern color
870 * is used for the grid.
872 void
873 sheet_style_update_grid_color (Sheet const *sheet)
875 GnmColor *default_auto = style_color_auto_pattern ();
876 GnmColor *sheet_auto = sheet_style_get_auto_pattern_color (sheet);
877 GnmColor *grid_color = style_color_grid ();
878 GnmColor *new_color;
880 new_color = (style_color_equal (default_auto, sheet_auto)
881 ? grid_color : sheet_auto);
883 /* Do nothing if we already have the right color */
884 if (gnm_style_border_none()->color != new_color) {
885 style_color_ref (new_color); /* none_set eats the ref */
886 gnm_style_border_none_set_color (new_color);
888 style_color_unref (grid_color);
889 style_color_unref (sheet_auto);
890 style_color_unref (default_auto);
893 /****************************************************************************/
895 static gboolean
896 tile_is_uniform (CellTile const *tile)
898 const int s = tile_size[tile->type];
899 GnmStyle const *st = tile->style_any.style[0];
900 int i;
902 for (i = 1; i < s; i++)
903 if (tile->style_any.style[i] != st)
904 return FALSE;
906 return TRUE;
909 static void
910 vector_apply_pstyle (CellTile *tile, ReplacementStyle *rs,
911 int cc, int cr, int level, GnmRange const *indic)
913 const CellTileType type = tile->type;
914 const int ncols = tile_col_count[type];
915 const int nrows = tile_row_count[type];
916 const int w1 = tile_widths[level + 1] / ncols;
917 const int h1 = tile_heights[level + 1] / nrows;
918 const int fcol = indic->start.col;
919 const int frow = indic->start.row;
920 const int lcol = MIN (ncols - 1, indic->end.col);
921 const int lrow = MIN (nrows - 1, indic->end.row);
922 GnmSheetSize const *ss = gnm_sheet_get_size (rs->sheet);
923 int r, c;
924 GnmRange rng;
926 for (r = frow; r <= lrow; r++) {
927 GnmStyle **st = tile->style_any.style + ncols * r;
928 rng.start.row = cr + h1 * r;
929 rng.end.row = MIN (rng.start.row + (h1 - 1),
930 ss->max_rows - 1);
931 for (c = fcol; c <= lcol; c++) {
932 rng.start.col = cc + w1 * c;
933 rng.end.col = MIN (rng.start.col + (w1 - 1),
934 ss->max_cols - 1);
935 rstyle_apply (st + c, rs, &rng);
941 * Determine whether before applying a style in the area of apply_to
942 * one needs to split the tile column-wise.
944 * If FALSE is returned then the tile need to be split to a TILE_PTR_MATRIX
945 * because the current level is not fine-grained enough.
947 * If TRUE is returned, TILE_SIMPLE needs to be split into TILE_COL and
948 * TILE_ROW needs to be split into TILE_MATRIX. TILE_COL and TILE_MATRIX
949 * should be kept. In indic, the inclusive post-split indicies of the
950 * range will be returned.
952 * If apply_to covers the entire tile, TRUE will be returned and the judgement
953 * on splitting above should be ignored. The indices in indic will be as-if
954 * the split was done.
956 static gboolean
957 col_indicies (int corner_col, int w, GnmRange const *apply_to,
958 GnmRange *indec)
960 int i, tmp;
962 i = apply_to->start.col - corner_col;
963 if (i <= 0)
964 indec->start.col = 0;
965 else {
966 tmp = i / w;
967 if (i != tmp * w)
968 return FALSE;
969 indec->start.col = tmp;
972 i = 1 + apply_to->end.col - corner_col;
973 tmp = i / w;
974 if (tmp >= TILE_SIZE_COL)
975 indec->end.col = TILE_SIZE_COL - 1;
976 else {
977 if (i != tmp * w)
978 return FALSE;
979 indec->end.col = tmp - 1;
982 return TRUE;
985 /* See docs for col_indicies. Swap cols and rows. */
986 static gboolean
987 row_indicies (int corner_row, int h, GnmRange const *apply_to,
988 GnmRange *indic)
990 int i, tmp;
992 i = apply_to->start.row - corner_row;
993 if (i <= 0)
994 indic->start.row = 0;
995 else {
996 int tmp = i / h;
997 if (i != tmp * h)
998 return FALSE;
999 indic->start.row = tmp;
1002 i = 1 + apply_to->end.row - corner_row;
1003 tmp = i / h;
1004 if (tmp >= TILE_SIZE_ROW)
1005 indic->end.row = TILE_SIZE_ROW - 1;
1006 else {
1007 if (i != tmp * h)
1008 return FALSE;
1009 indic->end.row = tmp - 1;
1012 return TRUE;
1016 * cell_tile_apply: This is the primary logic for making changing areas in the
1017 * tree. It could be further optimised if it becomes a bottle neck.
1019 static void
1020 cell_tile_apply (CellTile **tile, int level,
1021 int corner_col, int corner_row,
1022 GnmRange const *apply_to,
1023 ReplacementStyle *rs)
1025 int const width = tile_widths[level+1];
1026 int const height = tile_heights[level+1];
1027 int const w = tile_widths[level];
1028 int const h = tile_heights[level];
1029 gboolean const full_width = (apply_to->start.col <= corner_col &&
1030 apply_to->end.col >= (corner_col+width-1));
1031 gboolean const full_height = (apply_to->start.row <= corner_row &&
1032 apply_to->end.row >= (corner_row+height-1));
1033 GnmRange indic;
1034 CellTileType type;
1035 int c, r, i;
1037 g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
1038 g_return_if_fail (tile != NULL);
1039 g_return_if_fail (*tile != NULL);
1041 type = (*tile)->type;
1042 g_return_if_fail (TILE_SIMPLE <= type && type <= TILE_PTR_MATRIX);
1044 /* applying the same style to part of a simple-tile is a nop */
1045 if (type == TILE_SIMPLE &&
1046 (*tile)->style_simple.style[0] == rs->new_style)
1047 return;
1050 * Indices for the whole tile assuming a split to matrix.
1051 * We can still use these indices if we don't split either way.
1053 indic.start.col = 0;
1054 indic.start.row = 0;
1055 indic.end.col = TILE_SIZE_COL - 1;
1056 indic.end.row = TILE_SIZE_ROW - 1;
1058 if (type == TILE_PTR_MATRIX)
1059 goto drill_down;
1060 else if (full_width && full_height)
1061 goto apply;
1062 else if (full_height) {
1063 if (!col_indicies (corner_col, w, apply_to, &indic))
1064 goto split_to_ptr_matrix;
1066 switch (type) {
1067 case TILE_SIMPLE: {
1068 CellTile *res;
1069 type = TILE_COL;
1070 res = cell_tile_style_new (
1071 (*tile)->style_simple.style[0],
1072 type);
1073 cell_tile_dtor (*tile);
1074 *tile = res;
1075 /* Fall through */
1077 case TILE_COL:
1078 case TILE_MATRIX:
1079 goto apply;
1080 case TILE_ROW:
1081 goto split_to_matrix;
1082 default:
1083 g_assert_not_reached ();
1085 } else if (full_width) {
1086 if (!row_indicies (corner_row, h, apply_to, &indic))
1087 goto split_to_ptr_matrix;
1088 switch (type) {
1089 case TILE_SIMPLE: {
1090 CellTile *res;
1092 type = TILE_ROW;
1093 res = cell_tile_style_new (
1094 (*tile)->style_simple.style[0],
1095 type);
1096 cell_tile_dtor (*tile);
1097 *tile = res;
1098 /* Fall through */
1100 case TILE_ROW:
1101 case TILE_MATRIX:
1102 goto apply;
1103 case TILE_COL:
1104 goto split_to_matrix;
1105 default:
1106 g_assert_not_reached ();
1108 } else {
1109 if (col_indicies (corner_col, w, apply_to, &indic) &&
1110 row_indicies (corner_row, h, apply_to, &indic))
1111 goto split_to_matrix;
1112 else
1113 goto split_to_ptr_matrix;
1116 g_assert_not_reached ();
1118 split_to_matrix:
1119 *tile = cell_tile_matrix_set (*tile);
1121 apply:
1122 vector_apply_pstyle (*tile, rs, corner_col, corner_row, level, &indic);
1124 try_optimize:
1126 CellTileOptimize cto;
1127 cto.ss = gnm_sheet_get_size (rs->sheet);
1128 cto.recursion = FALSE;
1129 cell_tile_optimize (tile, level, &cto, corner_col, corner_row);
1131 return;
1133 split_to_ptr_matrix:
1135 * We get here when apply_to's corners are not on a TILE_MATRIX grid.
1136 * Split to pointer matrix whose element tiles will have a finer grid.
1138 g_return_if_fail (type != TILE_PTR_MATRIX);
1140 CellTile *res = cell_tile_ptr_matrix_new (*tile);
1141 cell_tile_dtor (*tile);
1142 *tile = res;
1143 type = TILE_PTR_MATRIX;
1146 drill_down:
1147 g_return_if_fail (type == TILE_PTR_MATRIX);
1148 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r, i += TILE_SIZE_COL) {
1149 int const cr = corner_row + h*r;
1150 if (cr > apply_to->end.row)
1151 break;
1152 if ((cr + h) <= apply_to->start.row)
1153 continue;
1155 for (c = 0 ; c < TILE_SIZE_COL ; ++c) {
1156 int const cc = corner_col + w*c;
1157 if (cc > apply_to->end.col)
1158 break;
1159 if ((cc + w) <= apply_to->start.col)
1160 continue;
1162 cell_tile_apply ((*tile)->ptr_matrix.ptr + i + c,
1163 level - 1, cc, cr, apply_to, rs);
1166 goto try_optimize;
1169 /* Handler for foreach_tile.
1171 * "width" and "height" refer to tile size which may extend beyond
1172 * the range supplied to foreach_tile and even beyond the sheet.
1174 typedef void (*ForeachTileFunc) (GnmStyle *style,
1175 int corner_col, int corner_row,
1176 int width, int height,
1177 GnmRange const *apply_to, gpointer user);
1178 static void
1179 foreach_tile_r (CellTile *tile, int level,
1180 int corner_col, int corner_row,
1181 GnmRange const *apply_to,
1182 ForeachTileFunc handler,
1183 gpointer user)
1185 int const width = tile_widths[level+1];
1186 int const height = tile_heights[level+1];
1187 int const w = tile_widths[level];
1188 int const h = tile_heights[level];
1189 int c, r, i, last;
1191 g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
1192 g_return_if_fail (tile != NULL);
1194 switch (tile->type) {
1195 case TILE_SIMPLE:
1196 handler (tile->style_simple.style[0],
1197 corner_col, corner_row, width, height,
1198 apply_to, user);
1199 break;
1201 case TILE_COL:
1202 if (apply_to != NULL) {
1203 c = (apply_to->start.col - corner_col) / w;
1204 if (c < 0)
1205 c = 0;
1206 last = (apply_to->end.col - corner_col) / w + 1;
1207 if (last > TILE_SIZE_COL)
1208 last = TILE_SIZE_COL;
1209 } else {
1210 c = 0;
1211 last = TILE_SIZE_COL;
1213 for (; c < last ; ++c)
1214 handler (tile->style_col.style[c],
1215 corner_col + c*w, corner_row, w, height,
1216 apply_to, user);
1217 break;
1219 case TILE_ROW:
1220 if (apply_to != NULL) {
1221 r = (apply_to->start.row - corner_row) / h;
1222 if (r < 0)
1223 r = 0;
1224 last = (apply_to->end.row - corner_row) / h + 1;
1225 if (last > TILE_SIZE_ROW)
1226 last = TILE_SIZE_ROW;
1227 } else {
1228 r = 0;
1229 last = TILE_SIZE_ROW;
1231 for (; r < last ; ++r)
1232 handler (tile->style_row.style[r],
1233 corner_col, corner_row + r*h, width, h,
1234 apply_to, user);
1235 break;
1237 case TILE_MATRIX:
1238 case TILE_PTR_MATRIX:
1239 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r, i += TILE_SIZE_COL) {
1240 int const cr = corner_row + h*r;
1241 if (apply_to) {
1242 if (cr > apply_to->end.row)
1243 break;
1244 if ((cr + h) <= apply_to->start.row)
1245 continue;
1248 for (c = 0 ; c < TILE_SIZE_COL ; ++c) {
1249 int const cc = corner_col + w*c;
1250 if (apply_to) {
1251 if (cc > apply_to->end.col)
1252 break;
1253 if ((cc + w) <= apply_to->start.col)
1254 continue;
1257 if (tile->type == TILE_MATRIX) {
1258 handler (tile->style_matrix.style[r*TILE_SIZE_COL+c],
1259 corner_col + c * w,
1260 corner_row + r * h,
1261 w, h, apply_to, user);
1262 } else {
1263 foreach_tile_r (
1264 tile->ptr_matrix.ptr[c + r*TILE_SIZE_COL],
1265 level-1, cc, cr, apply_to, handler, user);
1269 break;
1271 default:
1272 g_warning ("Adaptive Quad Tree corruption !");
1276 static void
1277 foreach_tile (Sheet const *sheet, GnmRange const *apply_to,
1278 ForeachTileFunc handler, gpointer user)
1280 foreach_tile_r (sheet->style_data->styles,
1281 sheet->tile_top_level, 0, 0,
1282 apply_to, handler, user);
1286 * cell_tile_apply_pos: This is an simplified version of cell_tile_apply. It
1287 * does not need all the bells and whistles because it operates on single cells.
1289 static void
1290 cell_tile_apply_pos (CellTile **tile, int level,
1291 int col, int row,
1292 ReplacementStyle *rs)
1294 CellTile *tmp;
1295 CellTileType type;
1296 GnmRange rng;
1298 g_return_if_fail (col >= 0);
1299 g_return_if_fail (col < gnm_sheet_get_max_cols (rs->sheet));
1300 g_return_if_fail (row >= 0);
1301 g_return_if_fail (row < gnm_sheet_get_max_rows (rs->sheet));
1303 range_init (&rng, col, row, col, row);
1305 tail_recursion:
1306 g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
1307 g_return_if_fail (tile != NULL);
1308 g_return_if_fail (*tile != NULL);
1310 tmp = *tile;
1311 type = tmp->type;
1312 g_return_if_fail (TILE_SIMPLE <= type && type <= TILE_PTR_MATRIX);
1314 if (level > 0) {
1315 int const w = tile_widths[level];
1316 int const c = col / w;
1317 int const h = tile_heights[level];
1318 int const r = row / h;
1320 if (type != TILE_PTR_MATRIX) {
1321 /* applying the same style to part of a simple-tile is a nop */
1322 if (type == TILE_SIMPLE &&
1323 (*tile)->style_simple.style[0] == rs->new_style)
1324 return;
1326 tmp = cell_tile_ptr_matrix_new (tmp);
1327 cell_tile_dtor (*tile);
1328 *tile = tmp;
1330 tile = tmp->ptr_matrix.ptr + r * TILE_SIZE_COL + c;
1331 level--;
1332 col -= c*w;
1333 row -= r*h;
1334 goto tail_recursion;
1335 } else if (type != TILE_MATRIX)
1336 *tile = tmp = cell_tile_matrix_set (tmp);
1338 g_return_if_fail (tmp->type == TILE_MATRIX);
1339 rstyle_apply (tmp->style_matrix.style + row * TILE_SIZE_COL + col,
1341 &rng);
1345 * sheet_style_set_range:
1346 * @sheet: #Sheet being changed
1347 * @range: #GnmRange being changed
1348 * @style: (transfer full): New #GnmStyle
1350 * Change the complete style for a region.
1352 void
1353 sheet_style_set_range (Sheet *sheet, GnmRange const *range,
1354 GnmStyle *style)
1356 ReplacementStyle rs;
1357 GnmRange r;
1359 g_return_if_fail (IS_SHEET (sheet));
1360 g_return_if_fail (range != NULL);
1362 if (range->start.col > range->end.col ||
1363 range->start.row > range->end.row) {
1364 gnm_style_unref (style);
1365 return;
1368 r = *range;
1369 range_ensure_sanity (&r, sheet);
1371 rstyle_ctor_style (&rs, style, sheet);
1372 cell_tile_apply (&sheet->style_data->styles,
1373 sheet->tile_top_level, 0, 0,
1374 &r, &rs);
1375 rstyle_dtor (&rs);
1379 * sheet_style_apply_col:
1380 * @sheet: #Sheet being changed
1381 * @col: Column
1382 * @style: (transfer full): #GnmStyle
1384 * NOTE: This is a simple wrapper for now. When we support col/row styles it
1385 * will make life easier.
1387 * Apply a partial style to a full col.
1389 void
1390 sheet_style_apply_col (Sheet *sheet, int col, GnmStyle *pstyle)
1392 GnmRange r;
1393 range_init_cols (&r, sheet, col, col);
1394 sheet_style_apply_range (sheet, &r, pstyle);
1398 * sheet_style_apply_row:
1399 * @sheet:
1400 * @row:
1401 * @style: (transfer full): #GnmStyle
1403 * NOTE: This is a simple wrapper for now. When we support col/row styles it
1404 * will make life easier.
1406 * Apply a partial style to a full col.
1408 void
1409 sheet_style_apply_row (Sheet *sheet, int row, GnmStyle *pstyle)
1411 GnmRange r;
1412 range_init_rows (&r, sheet, row, row);
1413 sheet_style_apply_range (sheet, &r, pstyle);
1417 * sheet_style_apply_pos:
1418 * @sheet:
1419 * @col:
1420 * @row:
1421 * @style: (transfer full): #GnmStyle
1423 * Apply a partial style to a single cell
1425 void
1426 sheet_style_apply_pos (Sheet *sheet, int col, int row, GnmStyle *pstyle)
1428 ReplacementStyle rs;
1430 g_return_if_fail (IS_SHEET (sheet));
1432 rstyle_ctor_pstyle (&rs, pstyle, sheet);
1433 cell_tile_apply_pos (&sheet->style_data->styles,
1434 sheet->tile_top_level, col, row,
1435 &rs);
1436 rstyle_dtor (&rs);
1439 * sheet_style_set_pos:
1440 * @sheet:
1441 * @col:
1442 * @row:
1443 * @style: (transfer full):
1445 * Change the complete style for a single cell.
1447 void
1448 sheet_style_set_pos (Sheet *sheet, int col, int row,
1449 GnmStyle *style)
1451 ReplacementStyle rs;
1453 g_return_if_fail (IS_SHEET (sheet));
1455 rstyle_ctor_style (&rs, style, sheet);
1456 cell_tile_apply_pos (&sheet->style_data->styles,
1457 sheet->tile_top_level, col, row,
1458 &rs);
1459 rstyle_dtor (&rs);
1463 * sheet_style_default:
1464 * @sheet:
1466 * Returns a reference to default style for a sheet.
1468 GnmStyle *
1469 sheet_style_default (Sheet const *sheet)
1471 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1472 g_return_val_if_fail (sheet->style_data != NULL, NULL);
1474 gnm_style_ref (sheet->style_data->default_style);
1475 return sheet->style_data->default_style;
1479 * sheet_style_get:
1480 * @sheet: #Sheet
1481 * @col: column number
1482 * @row: row number
1484 * Returns: (transfer none): find the fully qualified style applicable to
1485 * the specified cell position
1487 GnmStyle const *
1488 sheet_style_get (Sheet const *sheet, int col, int row)
1490 int level = sheet->tile_top_level;
1491 CellTile *tile = sheet->style_data->styles;
1493 while (1) {
1494 int width = tile_widths[level];
1495 int height = tile_heights[level];
1496 int c = col / width;
1497 int r = row / height;
1499 g_return_val_if_fail (tile != NULL, NULL);
1500 g_return_val_if_fail (0 <= c && c < TILE_SIZE_COL, NULL);
1501 g_return_val_if_fail (0 <= r && r < TILE_SIZE_ROW, NULL);
1503 switch (tile->type) {
1504 case TILE_SIMPLE:
1505 return tile->style_simple.style[0];
1506 case TILE_COL:
1507 return tile->style_col.style[c];
1508 case TILE_ROW:
1509 return tile->style_row.style[r];
1510 case TILE_MATRIX:
1511 return tile->style_matrix.style[r * TILE_SIZE_COL + c];
1513 case TILE_PTR_MATRIX:
1514 g_return_val_if_fail (level > 0, NULL);
1516 level--;
1517 tile = tile->ptr_matrix.ptr[r * TILE_SIZE_COL + c];
1518 col -= c * width;
1519 row -= r * height;
1520 continue;
1522 default:
1523 g_warning ("Adaptive Quad Tree corruption !");
1524 return NULL;
1529 #define border_null(b) ((b) == none || (b) == NULL)
1531 static void
1532 style_row (GnmStyle const *style, int start_col, int end_col,
1533 GnmStyleRow *sr, gboolean accept_conditions)
1535 GnmBorder const *top, *bottom, *none = gnm_style_border_none ();
1536 GnmBorder const *left, *right, *v;
1537 int const end = MIN (end_col, sr->end_col);
1538 int i = MAX (start_col, sr->start_col);
1539 GnmStyleConditions *conds;
1541 conds = accept_conditions
1542 ? gnm_style_get_conditions (style)
1543 : NULL;
1544 if (conds) {
1545 GnmEvalPos ep;
1546 int res;
1548 for (eval_pos_init (&ep, (Sheet *)sr->sheet, i, sr->row); ep.eval.col <= end ; ep.eval.col++) {
1549 res = gnm_style_conditions_eval (conds, &ep);
1550 style_row (res >= 0
1551 ? gnm_style_get_cond_style (style, res)
1552 : style,
1553 ep.eval.col, ep.eval.col, sr, FALSE);
1555 return;
1558 top = gnm_style_get_border (style, MSTYLE_BORDER_TOP);
1559 bottom = gnm_style_get_border (style, MSTYLE_BORDER_BOTTOM);
1560 left = gnm_style_get_border (style, MSTYLE_BORDER_LEFT);
1561 right = gnm_style_get_border (style, MSTYLE_BORDER_RIGHT);
1563 /* Cancel grids if there is a background */
1564 if (sr->hide_grid || gnm_style_get_pattern (style) > 0) {
1565 if (top == none)
1566 top = NULL;
1567 if (bottom == none)
1568 bottom = NULL;
1569 if (left == none)
1570 left = NULL;
1571 if (right == none)
1572 right = NULL;
1575 if (left != none && border_null (sr->vertical[i]))
1576 sr->vertical[i] = left;
1577 v = border_null (right) ? left : right;
1579 while (i <= end) {
1580 sr->styles[i] = style;
1581 if (top != none && border_null (sr->top[i]))
1582 sr->top[i] = top;
1583 sr->bottom[i] = bottom;
1584 sr->vertical[++i] = v;
1586 if (border_null (right))
1587 sr->vertical[i] = right;
1590 static void
1591 get_style_row (CellTile const *tile, int level,
1592 int corner_col, int corner_row,
1593 GnmStyleRow *sr)
1595 int const width = tile_widths[level+1];
1596 int const w = tile_widths[level];
1597 int const h = tile_heights[level];
1598 int r = 0;
1599 CellTileType t;
1601 g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
1602 g_return_if_fail (tile != NULL);
1604 t = tile->type;
1606 if (t != TILE_SIMPLE && t != TILE_COL) {
1607 r = (sr->row > corner_row) ? (sr->row - corner_row)/ h : 0;
1608 g_return_if_fail (r < TILE_SIZE_ROW);
1611 if (t == TILE_ROW || t == TILE_SIMPLE) {
1612 style_row (tile->style_any.style[r],
1613 corner_col, corner_col + width - 1, sr, TRUE);
1614 } else {
1615 /* find the start and end */
1616 int c;
1617 int last_c = (sr->end_col - corner_col) / w;
1618 if (last_c >= TILE_SIZE_COL)
1619 last_c = TILE_SIZE_COL-1;
1620 if (sr->start_col > corner_col) {
1621 c = (sr->start_col - corner_col) / w;
1622 corner_col += c * w;
1623 } else
1624 c = 0;
1626 corner_row += h*r;
1628 if (t != TILE_PTR_MATRIX) {
1629 GnmStyle * const *styles = tile->style_any.style + r*TILE_SIZE_COL;
1631 for ( ; c <= last_c ; c++, corner_col += w)
1632 style_row (styles[c],
1633 corner_col, corner_col + w - 1, sr, TRUE);
1634 } else {
1635 CellTile * const *tiles = tile->ptr_matrix.ptr + r*TILE_SIZE_COL;
1637 g_return_if_fail (level > 0);
1639 for ( level-- ; c <= last_c ; c++, corner_col += w)
1640 get_style_row (tiles[c], level,
1641 corner_col, corner_row, sr);
1647 * sheet_style_get_row:
1648 * @sheet: #Sheet
1649 * @sr: #GnmStyleRow
1651 * A utility routine which efficiently retrieves a range of styles within a row.
1652 * It also merges adjacent borders as necessary.
1654 void
1655 sheet_style_get_row (Sheet const *sheet, GnmStyleRow *sr)
1658 g_return_if_fail (IS_SHEET (sheet));
1659 g_return_if_fail (sr != NULL);
1660 g_return_if_fail (sr->styles != NULL);
1661 g_return_if_fail (sr->vertical != NULL);
1662 g_return_if_fail (sr->top != NULL);
1663 g_return_if_fail (sr->bottom != NULL);
1665 sr->sheet = sheet;
1666 sr->vertical[sr->start_col] = gnm_style_border_none ();
1667 get_style_row (sheet->style_data->styles, sheet->tile_top_level, 0, 0, sr);
1670 static void
1671 cb_get_row (GnmStyle *style,
1672 int corner_col, G_GNUC_UNUSED int corner_row,
1673 int width, G_GNUC_UNUSED int height,
1674 GnmRange const *apply_to, gpointer user_)
1676 GnmStyle **res = user_;
1677 int i;
1679 /* The given dimensions refer to the tile, not the area. */
1680 width = MIN (width, apply_to->end.col - corner_col + 1);
1682 for (i = 0; i < width; i++)
1683 res[corner_col + i] = style;
1686 GnmStyle **
1687 sheet_style_get_row2 (Sheet const *sheet, int row)
1689 GnmRange r;
1690 GnmStyle **res = g_new (GnmStyle *, gnm_sheet_get_max_cols (sheet));
1692 range_init_rows (&r, sheet, row, row);
1694 foreach_tile (sheet, &r, cb_get_row, res);
1696 return res;
1701 * style_row_init:
1703 * A small utility routine to initialize the grid drawing GnmStyleRow data
1704 * structure.
1706 void
1707 style_row_init (GnmBorder const * * *prev_vert,
1708 GnmStyleRow *sr, GnmStyleRow *next_sr,
1709 int start_col, int end_col, gpointer mem, gboolean hide_grid)
1711 int n, col;
1712 GnmBorder const *none = hide_grid ? NULL : gnm_style_border_none ();
1714 /* alias the arrays for easy access so that array[col] is valid
1715 * for all elements start_col-1 .. end_col+1 inclusive.
1716 * Note that this means that in some cases array[-1] is legal.
1718 n = end_col - start_col + 3; /* 1 before, 1 after, 1 fencepost */
1719 sr->vertical = mem;
1720 sr->vertical -= start_col-1;
1721 sr->top = sr->vertical + n;
1722 sr->bottom = sr->top + n;
1723 next_sr->top = sr->bottom; /* yes they should share */
1724 next_sr->bottom = next_sr->top + n;
1725 next_sr->vertical = next_sr->bottom + n;
1726 *prev_vert = next_sr->vertical + n;
1727 sr->styles = ((GnmStyle const **) (*prev_vert + n));
1728 next_sr->styles = sr->styles + n;
1729 sr->start_col = next_sr->start_col = start_col;
1730 sr->end_col = next_sr->end_col = end_col;
1731 sr->hide_grid = next_sr->hide_grid = hide_grid;
1733 /* Init the areas that sheet_style_get_row will not */
1734 for (col = start_col-1 ; col <= end_col+1; ++col)
1735 (*prev_vert)[col] = sr->top[col] = none;
1736 sr->vertical [start_col-1] = sr->vertical [end_col+1] =
1737 next_sr->vertical[start_col-1] = next_sr->vertical[end_col+1] =
1738 next_sr->top [start_col-1] = next_sr->top [end_col+1] =
1739 next_sr->bottom [start_col-1] = next_sr->bottom [end_col+1] = none;
1743 * sheet_style_apply_range: (skip)
1744 * @sheet: #Sheet
1745 * @range: #GnmRange to apply over
1746 * @pstyle: (transfer full): A partial style to apply
1748 * Apply a partial style to a region.
1750 void
1751 sheet_style_apply_range (Sheet *sheet, GnmRange const *range, GnmStyle *pstyle)
1753 ReplacementStyle rs;
1754 GnmRange r;
1756 g_return_if_fail (IS_SHEET (sheet));
1757 g_return_if_fail (range != NULL);
1759 if (range->start.col > range->end.col ||
1760 range->start.row > range->end.row) {
1761 gnm_style_unref (pstyle);
1762 return;
1765 r = *range;
1766 range_ensure_sanity (&r, sheet);
1768 rstyle_ctor_pstyle (&rs, pstyle, sheet);
1769 cell_tile_apply (&sheet->style_data->styles,
1770 sheet->tile_top_level, 0, 0,
1771 &r, &rs);
1772 rstyle_dtor (&rs);
1776 * sheet_style_apply_range2: (skip)
1777 * @sheet: #Sheet
1778 * @range: #GnmRange to apply over
1779 * @pstyle: (transfer none): A partial style to apply
1781 * Apply a partial style to a region.
1783 void
1784 sheet_style_apply_range2 (Sheet *sheet, GnmRange const *range, GnmStyle *pstyle)
1786 gnm_style_ref (pstyle);
1787 sheet_style_apply_range (sheet, range, pstyle);
1791 static void
1792 apply_border (Sheet *sheet, GnmRange const *r,
1793 GnmStyleBorderLocation side,
1794 GnmBorder *border)
1796 GnmStyle *pstyle = gnm_style_new ();
1797 pstyle_set_border (pstyle, border, side);
1798 sheet_style_apply_range (sheet, r, pstyle);
1802 * sheet_style_apply_border:
1803 * @sheet:
1804 * @range:
1805 * @borders:
1807 * When a user applies a border to a region we attempt to remove the border
1808 * from the opposing side to avoid overlapping border specifications.
1809 * eg
1810 * if we apply a top border to a range, we would clear the bottom border
1811 * of the range offset upwards.
1813 void
1814 sheet_style_apply_border (Sheet *sheet,
1815 GnmRange const *range,
1816 GnmBorder **borders)
1818 GnmStyle *pstyle = NULL;
1820 if (borders == NULL)
1821 return;
1823 if (borders[GNM_STYLE_BORDER_TOP]) {
1824 /* 1.1 top inner */
1825 GnmRange r = *range;
1826 r.end.row = r.start.row;
1827 apply_border (sheet, &r, GNM_STYLE_BORDER_TOP,
1828 borders[GNM_STYLE_BORDER_TOP]);
1830 /* 1.2 top outer */
1831 r.start.row--;
1832 if (r.start.row >= 0) {
1833 r.end.row = r.start.row;
1834 apply_border (sheet, &r, GNM_STYLE_BORDER_BOTTOM,
1835 gnm_style_border_none ());
1839 if (borders[GNM_STYLE_BORDER_BOTTOM]) {
1840 /* 2.1 bottom inner */
1841 GnmRange r = *range;
1842 r.start.row = r.end.row;
1843 apply_border (sheet, &r, GNM_STYLE_BORDER_BOTTOM,
1844 borders[GNM_STYLE_BORDER_BOTTOM]);
1846 /* 2.2 bottom outer */
1847 r.end.row++;
1848 if (r.end.row < gnm_sheet_get_last_row (sheet)) {
1849 r.start.row = r.end.row;
1850 apply_border (sheet, &r, GNM_STYLE_BORDER_TOP,
1851 gnm_style_border_none ());
1855 if (borders[GNM_STYLE_BORDER_LEFT]) {
1856 /* 3.1 left inner */
1857 GnmRange r = *range;
1858 r.end.col = r.start.col;
1859 apply_border (sheet, &r, GNM_STYLE_BORDER_LEFT,
1860 borders[GNM_STYLE_BORDER_LEFT]);
1862 /* 3.2 left outer */
1863 r.start.col--;
1864 if (r.start.col >= 0) {
1865 r.end.col = r.start.col;
1866 apply_border (sheet, &r, GNM_STYLE_BORDER_RIGHT,
1867 gnm_style_border_none ());
1871 if (borders[GNM_STYLE_BORDER_RIGHT]) {
1872 /* 4.1 right inner */
1873 GnmRange r = *range;
1874 r.start.col = r.end.col;
1875 apply_border (sheet, &r, GNM_STYLE_BORDER_RIGHT,
1876 borders[GNM_STYLE_BORDER_RIGHT]);
1878 /* 4.2 right outer */
1879 r.end.col++;
1880 if (r.end.col < gnm_sheet_get_last_col (sheet)) {
1881 r.start.col = r.end.col;
1882 apply_border (sheet, &r, GNM_STYLE_BORDER_LEFT,
1883 gnm_style_border_none ());
1887 /* Interiors horizontal : prefer top */
1888 if (borders[GNM_STYLE_BORDER_HORIZ] != NULL) {
1889 /* 5.1 horizontal interior top */
1890 if (range->start.row != range->end.row) {
1891 GnmRange r = *range;
1892 ++r.start.row;
1893 apply_border (sheet, &r, GNM_STYLE_BORDER_TOP,
1894 borders[GNM_STYLE_BORDER_HORIZ]);
1896 /* 5.2 interior bottom */
1897 if (range->start.row != range->end.row) {
1898 GnmRange r = *range;
1899 --r.end.row;
1900 apply_border (sheet, &r, GNM_STYLE_BORDER_BOTTOM,
1901 gnm_style_border_none ());
1905 /* Interiors vertical: prefer left */
1906 if (borders[GNM_STYLE_BORDER_VERT] != NULL) {
1907 /* 6.1 vertical interior left */
1908 if (range->start.col != range->end.col) {
1909 GnmRange r = *range;
1910 ++r.start.col;
1911 apply_border (sheet, &r, GNM_STYLE_BORDER_LEFT,
1912 borders[GNM_STYLE_BORDER_VERT]);
1915 /* 6.2 The vertical interior right */
1916 if (range->start.col != range->end.col) {
1917 GnmRange r = *range;
1918 --r.end.col;
1919 apply_border (sheet, &r, GNM_STYLE_BORDER_RIGHT,
1920 gnm_style_border_none ());
1924 /* 7. Diagonals (apply both in one pass) */
1925 if (borders[GNM_STYLE_BORDER_DIAG] != NULL) {
1926 pstyle = gnm_style_new ();
1927 pstyle_set_border (pstyle, borders[GNM_STYLE_BORDER_DIAG],
1928 GNM_STYLE_BORDER_DIAG);
1930 if (borders[GNM_STYLE_BORDER_REV_DIAG]) {
1931 if (pstyle == NULL)
1932 pstyle = gnm_style_new ();
1933 pstyle_set_border (pstyle, borders[GNM_STYLE_BORDER_REV_DIAG],
1934 GNM_STYLE_BORDER_REV_DIAG);
1936 if (pstyle != NULL)
1937 sheet_style_apply_range (sheet, range, pstyle);
1940 /****************************************************************************/
1942 typedef struct {
1943 GnmStyle *accum;
1944 unsigned int conflicts;
1945 } FindConflicts;
1947 static void
1948 cb_find_conflicts (GnmStyle *style,
1949 G_GNUC_UNUSED int corner_col, G_GNUC_UNUSED int corner_row,
1950 G_GNUC_UNUSED int width, G_GNUC_UNUSED int height,
1951 G_GNUC_UNUSED GnmRange const *apply_to, FindConflicts *ptr)
1953 ptr->conflicts = gnm_style_find_conflicts (ptr->accum, style, ptr->conflicts);
1956 static void
1957 border_mask_internal (gboolean known[GNM_STYLE_BORDER_EDGE_MAX],
1958 GnmBorder *borders[GNM_STYLE_BORDER_EDGE_MAX],
1959 GnmBorder const *b, GnmStyleBorderLocation l)
1961 if (!known[l]) {
1962 known[l] = TRUE;
1963 gnm_style_border_unref (borders[l]);
1964 borders[l] = (GnmBorder *)b;
1965 gnm_style_border_ref (borders[l]);
1966 } else if (borders[l] != b && borders[l] != NULL) {
1967 gnm_style_border_unref (borders[l]);
1968 borders[l] = NULL;
1972 static void
1973 border_mask (gboolean known[GNM_STYLE_BORDER_EDGE_MAX],
1974 GnmBorder *borders[GNM_STYLE_BORDER_EDGE_MAX],
1975 GnmBorder const *b, GnmStyleBorderLocation l)
1977 if (b == NULL)
1978 b = gnm_style_border_none ();
1979 border_mask_internal (known, borders, b, l);
1982 static void
1983 border_mask_vec (gboolean known[GNM_STYLE_BORDER_EDGE_MAX],
1984 GnmBorder *borders[GNM_STYLE_BORDER_EDGE_MAX],
1985 GnmBorder const * const *vec, int first, int last,
1986 GnmStyleBorderLocation l)
1988 GnmBorder const *b = vec[first];
1990 if (b == NULL)
1991 b = gnm_style_border_none ();
1992 while (first++ < last) {
1993 GnmBorder const *tmp = vec[first];
1994 if (tmp == NULL)
1995 tmp = gnm_style_border_none ();
1996 if (b != tmp) {
1997 b = NULL;
1998 break;
2002 border_mask_internal (known, borders, b, l);
2006 * sheet_style_find_conflicts:
2007 * @sheet: #Sheet to query
2008 * @r: #GnmRange to query
2009 * @style: (inout):
2010 * @borders: (out) (array fixed-size=8):
2012 * Returns: bitmask of conflicts
2014 unsigned int
2015 sheet_style_find_conflicts (Sheet const *sheet, GnmRange const *r,
2016 GnmStyle **style,
2017 GnmBorder *borders[GNM_STYLE_BORDER_EDGE_MAX])
2019 int n, col, row, start_col, end_col;
2020 GnmStyleRow sr;
2021 gpointer *sr_array_data;
2022 GnmStyleBorderLocation i;
2023 gboolean known[GNM_STYLE_BORDER_EDGE_MAX];
2024 GnmBorder const *none = gnm_style_border_none ();
2025 FindConflicts user;
2027 g_return_val_if_fail (IS_SHEET (sheet), 0);
2028 g_return_val_if_fail (r != NULL, 0);
2029 g_return_val_if_fail (style != NULL, 0);
2030 g_return_val_if_fail (borders != NULL, 0);
2032 /* init style set with a copy of the top left corner of the 1st range */
2033 if (*style == NULL) {
2034 GnmStyle const *tmp = sheet_style_get (sheet, r->start.col, r->start.row);
2035 *style = gnm_style_dup (tmp);
2036 for (i = GNM_STYLE_BORDER_TOP ; i < GNM_STYLE_BORDER_EDGE_MAX ; i++) {
2037 known[i] = FALSE;
2038 borders[i] = gnm_style_border_ref ((GnmBorder *)none);
2040 } else {
2041 for (i = GNM_STYLE_BORDER_TOP ; i < GNM_STYLE_BORDER_EDGE_MAX ; i++) {
2042 known[i] = TRUE;
2043 borders[i] = NULL;
2047 user.accum = *style;
2048 user.conflicts = 0; /* no conflicts yet */
2049 foreach_tile (sheet, r, (ForeachTileFunc)cb_find_conflicts, &user);
2051 /* copy over the diagonals */
2052 for (i = GNM_STYLE_BORDER_REV_DIAG ; i <= GNM_STYLE_BORDER_DIAG ; i++) {
2053 GnmStyleElement se = GNM_STYLE_BORDER_LOCATION_TO_STYLE_ELEMENT (i);
2054 gnm_style_border_unref (borders[i]);
2055 if (user.conflicts & (1 << se))
2056 borders[i] = NULL;
2057 else
2058 borders[i] = gnm_style_border_ref (
2059 gnm_style_get_border (*style, se));
2062 start_col = r->start.col;
2063 if (r->start.col > 0)
2064 start_col--;
2065 end_col = r->end.col;
2066 if (r->end.col < gnm_sheet_get_max_cols (sheet))
2067 end_col++;
2069 /* allocate then alias the arrays for easy access */
2070 n = end_col - start_col + 2;
2071 g_assert (sizeof (GnmBorder *) == sizeof (gpointer));
2072 g_assert (sizeof (GnmStyle *) == sizeof (gpointer));
2073 sr_array_data = g_new (gpointer, n * 4);
2074 sr.vertical = (GnmBorder const **)(sr_array_data - start_col);
2075 sr.top = (GnmBorder const **)(sr_array_data + n - start_col);
2076 sr.bottom = (GnmBorder const **)(sr_array_data + 2 * n - start_col);
2077 sr.styles = (GnmStyle const **) (sr_array_data + 3 * n - start_col);
2078 sr.start_col = start_col;
2079 sr.end_col = end_col;
2080 sr.hide_grid = sheet->hide_grid;
2082 /* pretend the previous bottom had no borders */
2083 for (col = start_col ; col <= end_col; ++col)
2084 sr.top[col] = none;
2086 /* merge the bottom of the previous row */
2087 if (r->start.row > 0) {
2088 GnmBorder const ** roller;
2089 sr.row = r->start.row - 1;
2090 sheet_style_get_row (sheet, &sr);
2091 roller = sr.top; sr.top = sr.bottom; sr.bottom = roller;
2095 * TODO: The border handling is tricky and currently VERY slow for
2096 * large ranges. We could easily optimize this. There is no need to
2097 * retrieve the style in every cell just to do a filter for uniformity
2098 * by row. One day we should do a special case version of
2099 * sheet_style_get_row probably style_get_uniform_col (this will be
2100 * faster)
2102 for (row = r->start.row ; row <= r->end.row ; row++) {
2103 GnmBorder const **roller;
2104 sr.row = row;
2105 sheet_style_get_row (sheet, &sr);
2107 border_mask (known, borders, sr.vertical[r->start.col],
2108 GNM_STYLE_BORDER_LEFT);
2109 border_mask (known, borders, sr.vertical[r->end.col+1],
2110 GNM_STYLE_BORDER_RIGHT);
2111 border_mask_vec (known, borders, sr.top,
2112 r->start.col, r->end.col, (row == r->start.row)
2113 ? GNM_STYLE_BORDER_TOP : GNM_STYLE_BORDER_HORIZ);
2114 if (r->start.col != r->end.col)
2115 border_mask_vec (known, borders, sr.vertical,
2116 r->start.col+1, r->end.col,
2117 GNM_STYLE_BORDER_VERT);
2119 roller = sr.top; sr.top = sr.bottom; sr.bottom = roller;
2122 /* merge the top of the next row */
2123 if (r->end.row < gnm_sheet_get_last_row (sheet)) {
2124 sr.row = r->end.row + 1;
2125 sheet_style_get_row (sheet, &sr);
2127 border_mask_vec (known, borders, sr.top, r->start.col, r->end.col,
2128 GNM_STYLE_BORDER_BOTTOM);
2130 g_free (sr_array_data);
2131 return user.conflicts;
2135 * sheet_style_relocate:
2136 * @rinfo:
2138 * Slide the styles from the origin region to the new position.
2140 void
2141 sheet_style_relocate (GnmExprRelocateInfo const *rinfo)
2143 GnmCellPos corner;
2144 GnmStyleList *styles;
2146 g_return_if_fail (rinfo != NULL);
2148 styles = sheet_style_get_range (rinfo->origin_sheet, &rinfo->origin);
2150 sheet_style_set_range (rinfo->origin_sheet, &rinfo->origin,
2151 sheet_style_default (rinfo->origin_sheet));
2152 corner.col = rinfo->origin.start.col + rinfo->col_offset;
2153 corner.row = rinfo->origin.start.row + rinfo->row_offset;
2154 sheet_style_set_list (rinfo->target_sheet, &corner, styles, NULL, NULL);
2155 style_list_free (styles);
2159 * sheet_style_insdel_colrow:
2160 * @rinfo:
2162 * Insert of delete style columns/rows.
2164 * For the insert case, we stretch the preceding column/row into there space
2165 * we open.
2167 void
2168 sheet_style_insdel_colrow (GnmExprRelocateInfo const *rinfo)
2170 GnmStyleList *styles = NULL;
2171 Sheet *sheet;
2172 GnmCellPos corner;
2173 gboolean is_insert;
2175 g_return_if_fail (rinfo != NULL);
2176 g_return_if_fail (rinfo->origin_sheet == rinfo->target_sheet);
2177 g_return_if_fail ((rinfo->col_offset == 0) != (rinfo->row_offset == 0));
2179 is_insert = (rinfo->col_offset + rinfo->row_offset > 0);
2180 sheet = rinfo->origin_sheet;
2182 if (is_insert) {
2183 /* 1) copy col/row to the top/left of the region, and extend it */
2184 corner = rinfo->origin.start;
2185 if (rinfo->col_offset) {
2186 int col = MAX (corner.col - 1, 0);
2187 GnmStyleList *ptr;
2188 GnmRange r;
2190 corner.row = 0;
2191 range_init_cols (&r, sheet, col, col);
2192 styles = sheet_style_get_range (sheet, &r);
2193 for (ptr = styles ; ptr != NULL ; ptr = ptr->next) {
2194 GnmStyleRegion *sr = ptr->data;
2195 sr->range.end.col = rinfo->col_offset - 1;
2197 } else {
2198 int row = MAX (corner.row - 1, 0);
2199 GnmStyleList *ptr;
2200 GnmRange r;
2202 corner.col = 0;
2203 range_init_rows (&r, sheet, row, row);
2204 styles = sheet_style_get_range (sheet, &r);
2205 for (ptr = styles ; ptr != NULL ; ptr = ptr->next) {
2206 GnmStyleRegion *sr = ptr->data;
2207 sr->range.end.row = rinfo->row_offset - 1;
2212 sheet_style_relocate (rinfo);
2214 if (styles) {
2215 sheet_style_set_list (sheet, &corner, styles, NULL, NULL);
2216 style_list_free (styles);
2220 static void
2221 cb_style_extent (GnmStyle *style,
2222 int corner_col, int corner_row, int width, int height,
2223 GnmRange const *apply_to, gpointer user)
2225 GnmRange *res = user;
2226 if (gnm_style_visible_in_blank (style)) {
2227 int tmp;
2229 /* The given dimensions refer to the tile, not the area. */
2230 width = MIN (width, apply_to->end.col - corner_col + 1);
2231 height = MIN (height, apply_to->end.row - corner_row + 1);
2233 tmp = corner_col+width-1;
2234 if (res->end.col < tmp)
2235 res->end.col = tmp;
2236 if (res->start.col > corner_col)
2237 res->start.col = corner_col;
2239 tmp = corner_row+height-1;
2240 if (res->end.row < tmp)
2241 res->end.row = tmp;
2242 if (res->start.row > corner_row)
2243 res->start.row = corner_row;
2248 * sheet_style_get_extent:
2249 * @sheet: sheet to measure
2250 * @r: starting range and resulting range
2252 * A simple implementation that finds the smallest range containing all visible styles
2253 * and containing @res.
2255 void
2256 sheet_style_get_extent (Sheet const *sheet, GnmRange *res)
2258 GnmRange r;
2260 range_init_full_sheet (&r, sheet);
2261 foreach_tile (sheet, &r, cb_style_extent, res);
2264 struct cb_nondefault_extent {
2265 GnmRange *res;
2266 GnmStyle **col_defaults;
2269 static void
2270 cb_nondefault_extent (GnmStyle *style,
2271 int corner_col, int corner_row, int width, int height,
2272 GnmRange const *apply_to, gpointer user_)
2274 struct cb_nondefault_extent *user = user_;
2275 GnmRange *res = user->res;
2276 int i;
2278 for (i = 0; i < width; i++) {
2279 int col = corner_col + i;
2280 if (col >= apply_to->start.col &&
2281 col <= apply_to->end.col &&
2282 style != user->col_defaults[col]) {
2283 int max_row = MIN (corner_row + height - 1,
2284 apply_to->end.row);
2285 int min_row = MAX (corner_row, apply_to->start.row);
2287 res->start.col = MIN (col, res->start.col);
2288 res->start.row = MIN (min_row, res->start.row);
2290 res->end.col = MAX (col, res->end.col);
2291 res->end.row = MAX (max_row, res->end.row);
2296 void
2297 sheet_style_get_nondefault_extent (Sheet const *sheet, GnmRange *extent,
2298 const GnmRange *src, GnmStyle **col_defaults)
2300 struct cb_nondefault_extent user;
2301 user.res = extent;
2302 user.col_defaults = col_defaults;
2303 foreach_tile (sheet, src, cb_nondefault_extent, &user);
2306 struct cb_is_default {
2307 gboolean res;
2308 GnmStyle **col_defaults;
2311 static void
2312 cb_is_default (GnmStyle *style,
2313 int corner_col, G_GNUC_UNUSED int corner_row,
2314 int width, G_GNUC_UNUSED int height,
2315 GnmRange const *apply_to, gpointer user_)
2317 struct cb_is_default *user = user_;
2318 int i;
2320 /* The given "width" refers to the tile, not the area. */
2321 width = MIN (width, apply_to->end.col - corner_col + 1);
2323 for (i = 0; user->res && i < width; i++) {
2324 if (style != user->col_defaults[corner_col + i])
2325 user->res = FALSE;
2329 gboolean
2330 sheet_style_is_default (Sheet const *sheet, const GnmRange *r, GnmStyle **col_defaults)
2332 struct cb_is_default user;
2334 user.res = TRUE;
2335 user.col_defaults = col_defaults;
2337 foreach_tile (sheet, r, cb_is_default, &user);
2339 return user.res;
2342 struct cb_get_nondefault {
2343 guint8 *res;
2344 GnmStyle **col_defaults;
2347 static void
2348 cb_get_nondefault (GnmStyle *style,
2349 int corner_col, G_GNUC_UNUSED int corner_row,
2350 int width, G_GNUC_UNUSED int height,
2351 GnmRange const *apply_to, gpointer user_)
2353 struct cb_get_nondefault *user = user_;
2354 int i;
2356 /* The given dimensions refer to the tile, not the area. */
2357 width = MIN (width, apply_to->end.col - corner_col + 1);
2358 height = MIN (height, apply_to->end.row - corner_row + 1);
2360 for (i = 0; i < width; i++) {
2361 if (style != user->col_defaults[corner_col + i]) {
2362 int j;
2363 for (j = 0; j < height; j++)
2364 user->res[corner_row + j] = 1;
2365 break;
2370 guint8 *
2371 sheet_style_get_nondefault_rows (Sheet const *sheet, GnmStyle **col_defaults)
2373 struct cb_get_nondefault user;
2374 GnmRange r;
2376 range_init_full_sheet (&r, sheet);
2378 user.res = g_new0 (guint8, gnm_sheet_get_max_rows (sheet));
2379 user.col_defaults = col_defaults;
2381 foreach_tile (sheet, &r, cb_get_nondefault, &user);
2383 return user.res;
2386 struct cb_most_common {
2387 GHashTable *h;
2388 int l;
2389 gboolean is_col;
2392 static void
2393 cb_most_common (GnmStyle *style,
2394 int corner_col, int corner_row, int width, int height,
2395 GnmRange const *apply_to, gpointer user)
2397 struct cb_most_common *cmc = user;
2398 int *counts = g_hash_table_lookup (cmc->h, style);
2399 int i;
2400 if (!counts) {
2401 counts = g_new0 (int, cmc->l);
2402 g_hash_table_insert (cmc->h, style, counts);
2405 /* The given dimensions refer to the tile, not the area. */
2406 width = MIN (width, apply_to->end.col - corner_col + 1);
2407 height = MIN (height, apply_to->end.row - corner_row + 1);
2409 if (cmc->is_col)
2410 for (i = 0; i < width; i++)
2411 counts[corner_col + i] += height;
2412 else
2413 for (i = 0; i < height; i++)
2414 counts[corner_row + i] += width;
2418 * sheet_style_most_common:
2419 * @sheet: sheet to inspect
2420 * @is_col: if %TRUE, look for common styles in columns; if FALSE, look in rows.
2422 * Returns: an array of styles describing the most common styles, one per column
2423 * or row.
2425 GnmStyle **
2426 sheet_style_most_common (Sheet const *sheet, gboolean is_col)
2428 GnmRange r;
2429 struct cb_most_common cmc;
2430 int *max;
2431 GnmStyle **res;
2432 GHashTableIter iter;
2433 gpointer key, value;
2435 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2437 range_init_full_sheet (&r, sheet);
2438 cmc.h = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
2439 cmc.l = colrow_max (is_col, sheet);
2440 cmc.is_col = is_col;
2441 foreach_tile (sheet, &r, cb_most_common, &cmc);
2443 max = g_new0 (int, cmc.l);
2444 res = g_new0 (GnmStyle *, cmc.l);
2445 g_hash_table_iter_init (&iter, cmc.h);
2446 while (g_hash_table_iter_next (&iter, &key, &value)) {
2447 int *counts = value;
2448 GnmStyle *style = key;
2449 int j;
2450 for (j = 0; j < cmc.l; j++) {
2451 /* FIXME: we really ought to break ties in a
2452 consistent way that does not depend on hash
2453 order. */
2454 if (counts[j] > max[j]) {
2455 max[j] = counts[j];
2456 res[j] = style;
2460 g_hash_table_destroy (cmc.h);
2461 g_free (max);
2463 return res;
2466 /****************************************************************************/
2469 * gnm_style_region_new:
2470 * @range: #GnmRange
2471 * @style: #GnmStyle
2473 * Returns: (transfer full): the newly allocated #GnmStyleRegion.
2475 GnmStyleRegion *
2476 gnm_style_region_new (GnmRange const *range, GnmStyle *style)
2478 GnmStyleRegion *sr;
2480 sr = g_new (GnmStyleRegion, 1);
2481 sr->range = *range;
2482 sr->style = style;
2483 gnm_style_ref (style);
2485 return sr;
2488 void
2489 gnm_style_region_free (GnmStyleRegion *sr)
2491 g_return_if_fail (sr != NULL);
2493 gnm_style_unref (sr->style);
2494 sr->style = NULL;
2495 g_free (sr);
2498 static GnmStyleRegion *
2499 gnm_style_region_copy (GnmStyleRegion *sr)
2501 GnmStyleRegion *res = g_new (GnmStyleRegion, 1);
2502 *res = *sr;
2503 gnm_style_ref (sr->style);
2504 return res;
2507 GType
2508 gnm_style_region_get_type (void)
2510 static GType t = 0;
2512 if (t == 0) {
2513 t = g_boxed_type_register_static ("GnmStyleRegion",
2514 (GBoxedCopyFunc)gnm_style_region_copy,
2515 (GBoxedFreeFunc)gnm_style_region_free);
2517 return t;
2521 static gboolean
2522 debug_style_list (void)
2524 static int debug = -1;
2525 if (debug < 0)
2526 debug = gnm_debug_flag ("style-list");
2527 return debug;
2530 typedef struct {
2531 GPtrArray *accum;
2532 GHashTable *by_tl, *by_br;
2533 guint64 area;
2534 gboolean (*style_equal) (GnmStyle const *a, GnmStyle const *b);
2535 gboolean (*style_filter) (GnmStyle const *style);
2536 GnmSheetSize const *sheet_size;
2537 } ISL;
2539 static gboolean
2540 merge_ranges (GnmRange *a, GnmRange const *b)
2542 if (a->start.row == b->start.row &&
2543 a->end.row == b->end.row &&
2544 a->end.col + 1 == b->start.col) {
2545 /* "a" is just left of "b". */
2546 a->end.col = b->end.col;
2547 return TRUE;
2550 if (a->start.col == b->start.col &&
2551 a->end.col == b->end.col &&
2552 a->end.row + 1 == b->start.row) {
2553 /* "a" is just on top of "b". */
2554 a->end.row = b->end.row;
2555 return TRUE;
2558 /* Punt. */
2559 return FALSE;
2562 static gboolean
2563 try_merge_pair (ISL *data, unsigned ui1, unsigned ui2)
2565 GnmStyleRegion *a;
2566 GnmStyleRegion *b;
2568 if (ui1 >= data->accum->len || ui2 >= data->accum->len)
2569 return FALSE;
2571 a = g_ptr_array_index (data->accum, ui1);
2572 b = g_ptr_array_index (data->accum, ui2);
2574 if (!data->style_equal (a->style, b->style))
2575 return FALSE;
2577 if (!merge_ranges (&a->range, &b->range))
2578 return FALSE;
2580 gnm_style_region_free (b);
2581 g_ptr_array_remove_index (data->accum, ui2);
2583 return TRUE;
2586 static void
2587 cb_style_list_add_node (GnmStyle *style,
2588 int corner_col, int corner_row, int width, int height,
2589 GnmRange const *apply_to, gpointer user_)
2591 ISL *data = user_;
2592 GnmSheetSize const *ss = data->sheet_size;
2593 GnmStyleRegion *sr;
2594 GnmRange range;
2596 /* Can this even happen? */
2597 if (corner_col >= ss->max_cols || corner_row >= ss->max_rows)
2598 return;
2600 if (data->style_filter && !data->style_filter (style))
2601 return;
2603 range.start.col = corner_col;
2604 range.start.row = corner_row;
2605 range.end.col = MIN (corner_col + width - 1, ss->max_cols - 1);
2606 range.end.row = MIN (corner_row + height - 1, ss->max_rows - 1);
2608 if (apply_to) {
2609 range.start.col -= apply_to->start.col;
2610 if (range.start.col < 0)
2611 range.start.col = 0;
2612 range.start.row -= apply_to->start.row;
2613 if (range.start.row < 0)
2614 range.start.row = 0;
2616 if (range.end.col > apply_to->end.col)
2617 range.end.col = apply_to->end.col;
2618 range.end.col -= apply_to->start.col;
2619 if (range.end.row > apply_to->end.row)
2620 range.end.row = apply_to->end.row;
2621 range.end.row -= apply_to->start.row;
2624 data->area += (guint64)range_width (&range) * range_height (&range);
2626 sr = gnm_style_region_new (&range, style);
2627 g_ptr_array_add (data->accum, sr);
2629 while (try_merge_pair (data, data->accum->len - 2, data->accum->len - 1))
2630 /* Nothing */;
2633 static void
2634 verify_hashes (ISL *data)
2636 GHashTable *by_tl = data->by_tl;
2637 GHashTable *by_br = data->by_br;
2638 unsigned ui;
2639 guint64 area = 0;
2641 g_return_if_fail (g_hash_table_size (by_tl) == data->accum->len);
2642 g_return_if_fail (g_hash_table_size (by_br) == data->accum->len);
2644 for (ui = 0; ui < data->accum->len; ui++) {
2645 GnmStyleRegion *sr = g_ptr_array_index (data->accum, ui);
2646 g_return_if_fail (g_hash_table_lookup (by_tl, &sr->range.start) == sr);
2647 g_return_if_fail (g_hash_table_lookup (by_br, &sr->range.end) == sr);
2648 area += range_height (&sr->range) *
2649 (guint64)range_width (&sr->range);
2652 g_return_if_fail (area == data->area);
2655 static void
2656 merge_vertical_stripes (ISL *data)
2658 unsigned ui;
2659 GHashTable *by_tl = data->by_tl;
2660 GHashTable *by_br = data->by_br;
2661 gboolean debug = debug_style_list ();
2662 gboolean paranoid = debug;
2664 for (ui = 0; ui < data->accum->len; ui++) {
2665 GnmStyleRegion *a = g_ptr_array_index (data->accum, ui);
2666 GnmStyleRegion *c;
2667 GnmCellPos cr;
2668 GSList *Bs = NULL, *l;
2669 gboolean fail = FALSE;
2671 /* We're looking for the setup below and extend Bs down */
2672 /* taking over part of C which is then extended to */
2673 /* include all of A. */
2674 /* */
2675 /* +----+ */
2676 /* | +---------+ */
2677 /* +---------+ B1 | B2 | */
2678 /* | A | | | */
2679 /* +---------+----+---------+ */
2680 /* | C | */
2681 /* +------------------------+ */
2683 cr.col = a->range.start.col;
2684 cr.row = a->range.end.row + 1;
2685 c = g_hash_table_lookup (by_tl, &cr);
2686 if (!c || !data->style_equal (a->style, c->style))
2687 continue;
2689 cr.col = c->range.end.col;
2690 cr.row = a->range.end.row;
2691 while (cr.col > a->range.end.col) {
2692 GnmStyleRegion *b = g_hash_table_lookup (by_br, &cr);
2693 if (!b || !data->style_equal (a->style, b->style)) {
2694 fail = TRUE;
2695 break;
2697 Bs = g_slist_prepend (Bs, b);
2698 cr.col = b->range.start.col - 1;
2700 if (fail || cr.col != a->range.end.col) {
2701 g_slist_free (Bs);
2702 continue;
2705 if (debug) {
2706 g_printerr ("Vertical stripe merge:\n");
2707 g_printerr ("A: %s\n", range_as_string (&a->range));
2708 for (l = Bs; l; l = l-> next) {
2709 GnmStyleRegion *b = l->data;
2710 g_printerr ("B: %s\n", range_as_string (&b->range));
2712 g_printerr ("C: %s\n", range_as_string (&c->range));
2715 g_hash_table_remove (by_tl, &a->range.start);
2716 g_hash_table_remove (by_br, &a->range.end);
2717 g_ptr_array_remove_index_fast (data->accum, ui);
2718 ui--;
2720 g_hash_table_remove (by_tl, &c->range.start);
2721 g_hash_table_remove (by_br, &c->range.end);
2722 c->range.start.row = a->range.start.row;
2723 c->range.end.col = a->range.end.col;
2724 g_hash_table_insert (by_tl, &c->range.start, c);
2725 g_hash_table_insert (by_br, &c->range.end, c);
2726 if (debug)
2727 g_printerr ("New C: %s\n", range_as_string (&c->range));
2729 for (l = Bs; l; l = l-> next) {
2730 GnmStyleRegion *b = l->data;
2731 g_hash_table_remove (by_br, &b->range.end);
2732 b->range.end.row = c->range.end.row;
2733 g_hash_table_insert (by_br, &b->range.end, b);
2734 if (debug)
2735 g_printerr ("New B: %s\n", range_as_string (&b->range));
2737 if (debug)
2738 g_printerr ("\n");
2740 gnm_style_region_free (a);
2741 g_slist_free (Bs);
2743 if (paranoid) verify_hashes (data);
2747 static void
2748 merge_horizontal_stripes (ISL *data)
2750 unsigned ui;
2751 GHashTable *by_tl = data->by_tl;
2752 GHashTable *by_br = data->by_br;
2753 gboolean debug = debug_style_list ();
2754 gboolean paranoid = debug;
2756 for (ui = 0; ui < data->accum->len; ui++) {
2757 GnmStyleRegion *a = g_ptr_array_index (data->accum, ui);
2758 GnmStyleRegion *c;
2759 GnmCellPos cr;
2760 GSList *Bs = NULL, *l;
2761 gboolean fail = FALSE;
2763 /* We're looking for the setup below and extend Bs right */
2764 /* taking over part of C which is then extended to */
2765 /* include all of A. */
2766 /* */
2767 /* +-----+-----+ */
2768 /* | A | | */
2769 /* +----+-----+ | */
2770 /* | B1 | | */
2771 /* +--+-------+ | */
2772 /* | | C | */
2773 /* | | | */
2774 /* | B2 | | */
2775 /* | | | */
2776 /* | | | */
2777 /* +-------+-----+ */
2779 cr.col = a->range.end.col + 1;
2780 cr.row = a->range.start.row;
2781 c = g_hash_table_lookup (by_tl, &cr);
2782 if (!c || !data->style_equal (a->style, c->style))
2783 continue;
2785 cr.col = a->range.end.col;
2786 cr.row = c->range.end.row;
2787 while (cr.row > a->range.end.row) {
2788 GnmStyleRegion *b = g_hash_table_lookup (by_br, &cr);
2789 if (!b || !data->style_equal (a->style, b->style)) {
2790 fail = TRUE;
2791 break;
2793 Bs = g_slist_prepend (Bs, b);
2794 cr.row = b->range.start.row - 1;
2796 if (fail || cr.row != a->range.end.row) {
2797 g_slist_free (Bs);
2798 continue;
2801 if (debug) {
2802 g_printerr ("Horizontal stripe merge:\n");
2803 g_printerr ("A: %s\n", range_as_string (&a->range));
2804 for (l = Bs; l; l = l-> next) {
2805 GnmStyleRegion *b = l->data;
2806 g_printerr ("B: %s\n", range_as_string (&b->range));
2808 g_printerr ("C: %s\n", range_as_string (&c->range));
2811 g_hash_table_remove (by_tl, &a->range.start);
2812 g_hash_table_remove (by_br, &a->range.end);
2813 g_ptr_array_remove_index_fast (data->accum, ui);
2814 ui--;
2816 g_hash_table_remove (by_tl, &c->range.start);
2817 g_hash_table_remove (by_br, &c->range.end);
2818 c->range.start.col = a->range.start.col;
2819 c->range.end.row = a->range.end.row;
2820 g_hash_table_insert (by_tl, &c->range.start, c);
2821 g_hash_table_insert (by_br, &c->range.end, c);
2822 if (debug)
2823 g_printerr ("New C: %s\n", range_as_string (&c->range));
2825 for (l = Bs; l; l = l-> next) {
2826 GnmStyleRegion *b = l->data;
2827 g_hash_table_remove (by_br, &b->range.end);
2828 b->range.end.col = c->range.end.col;
2829 g_hash_table_insert (by_br, &b->range.end, b);
2830 if (debug)
2831 g_printerr ("New B: %s\n", range_as_string (&b->range));
2833 if (debug)
2834 g_printerr ("\n");
2836 gnm_style_region_free (a);
2837 g_slist_free (Bs);
2839 if (paranoid) verify_hashes (data);
2843 static int
2844 by_col_row (GnmStyleRegion **a, GnmStyleRegion **b)
2846 int d;
2848 d = (*a)->range.start.col - (*b)->range.start.col;
2849 if (d)
2850 return d;
2852 d = (*a)->range.start.row - (*b)->range.start.row;
2853 return d;
2856 static GnmStyleList *
2857 internal_style_list (Sheet const *sheet, GnmRange const *r,
2858 gboolean (*style_equal) (GnmStyle const *a, GnmStyle const *b),
2859 gboolean (*style_filter) (GnmStyle const *style))
2861 GnmRange full_sheet;
2862 ISL data;
2863 GnmStyleList *res = NULL;
2864 unsigned ui, prelen;
2865 gboolean paranoid = FALSE;
2866 guint64 sheet_area;
2868 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2870 if (r) {
2871 /* This can happen if the last row or column is deleted. */
2872 if (!range_valid (r))
2873 return NULL;
2874 } else
2875 r = range_init_full_sheet (&full_sheet, sheet);
2877 data.accum = g_ptr_array_new ();
2878 data.by_tl = g_hash_table_new ((GHashFunc)gnm_cellpos_hash,
2879 (GEqualFunc)gnm_cellpos_equal);
2880 data.by_br = g_hash_table_new ((GHashFunc)gnm_cellpos_hash,
2881 (GEqualFunc)gnm_cellpos_equal);
2882 data.area = 0;
2883 data.style_equal = style_equal;
2884 data.style_filter = style_filter;
2885 data.sheet_size = gnm_sheet_get_size (sheet);
2887 foreach_tile (sheet, r, cb_style_list_add_node, &data);
2889 sheet_area = (guint64)range_height (r) * range_width (r);
2890 if (data.style_filter ? (data.area > sheet_area) : (data.area != sheet_area))
2891 g_warning ("Strange size issue in internal_style_list");
2894 * Simple, fast optimization first. For the file underlying
2895 * bug 699045 this brings down 332688 entries to just 86.
2897 if (data.accum->len >= 2) {
2898 g_ptr_array_sort (data.accum, (GCompareFunc)by_col_row);
2899 for (ui = data.accum->len - 1; ui > 0; ui--) {
2900 try_merge_pair (&data, ui - 1, ui);
2904 /* Populate hashes. */
2905 for (ui = 0; ui < data.accum->len; ui++) {
2906 GnmStyleRegion *sr = g_ptr_array_index (data.accum, ui);
2907 g_hash_table_insert (data.by_tl, &sr->range.start, sr);
2908 g_hash_table_insert (data.by_br, &sr->range.end, sr);
2911 if (paranoid) verify_hashes (&data);
2913 do {
2914 prelen = data.accum->len;
2915 merge_vertical_stripes (&data);
2916 merge_horizontal_stripes (&data);
2917 } while (prelen > data.accum->len);
2919 /* Always verify once. */
2920 verify_hashes (&data);
2922 if (debug_style_list ())
2923 g_printerr ("Total of %d ranges:\n", data.accum->len);
2924 for (ui = data.accum->len; ui-- > 0; ) {
2925 GnmStyleRegion *sr = g_ptr_array_index (data.accum, ui);
2926 if (debug_style_list ())
2927 g_printerr (" %s %p\n",
2928 range_as_string (&sr->range),
2929 sr->style);
2930 res = g_slist_prepend (res, sr);
2933 g_ptr_array_free (data.accum, TRUE);
2934 g_hash_table_destroy (data.by_tl);
2935 g_hash_table_destroy (data.by_br);
2936 return res;
2940 * sheet_style_get_range:
2941 * @sheet: the sheet in which to find styles
2942 * @r: optional range to scan
2944 * Get a list of rectangles and their associated styles.
2945 * Caller is responsible for freeing. Note that when a range is given,
2946 * the resulting ranges are relative to the input range.
2948 * Returns: (transfer full):
2950 GnmStyleList *
2951 sheet_style_get_range (Sheet const *sheet, GnmRange const *r)
2953 return internal_style_list (sheet, r,
2954 gnm_style_eq,
2955 NULL);
2958 static gboolean
2959 style_conditions_equal (GnmStyle const *a, GnmStyle const *b)
2961 return gnm_style_get_conditions (a) == gnm_style_get_conditions (b);
2964 static gboolean
2965 style_conditions_filter (GnmStyle const *style)
2967 return gnm_style_get_conditions (style) != NULL;
2971 * sheet_style_collect_conditions:
2972 * @sheet:
2973 * @r:
2975 * Returns: (transfer full): a list of areas with conditionals, Caller is
2976 * responsible for freeing.
2978 GnmStyleList *
2979 sheet_style_collect_conditions (Sheet const *sheet, GnmRange const *r)
2981 return internal_style_list (sheet, r,
2982 style_conditions_equal,
2983 style_conditions_filter);
2987 static gboolean
2988 style_hlink_equal (GnmStyle const *a, GnmStyle const *b)
2990 return gnm_style_get_hlink (a) == gnm_style_get_hlink (b);
2993 static gboolean
2994 style_hlink_filter (GnmStyle const *style)
2996 return gnm_style_get_hlink (style) != NULL;
3000 * sheet_style_collect_hlinks:
3001 * @sheet:
3002 * @r:
3004 * Returns: (transfer full): a list of areas with hyperlinks, Caller is
3005 * responsible for freeing.
3007 GnmStyleList *
3008 sheet_style_collect_hlinks (Sheet const *sheet, GnmRange const *r)
3010 return internal_style_list (sheet, r,
3011 style_hlink_equal,
3012 style_hlink_filter);
3016 static gboolean
3017 style_validation_equal (GnmStyle const *a, GnmStyle const *b)
3019 return gnm_style_get_validation (a) == gnm_style_get_validation (b) &&
3020 gnm_style_get_input_msg (a) == gnm_style_get_input_msg (b);
3023 static gboolean
3024 style_validation_filter (GnmStyle const *style)
3026 return (gnm_style_get_validation (style) != NULL ||
3027 gnm_style_get_input_msg (style) != NULL);
3031 * sheet_style_collect_validations:
3032 * @sheet: the to trawl
3033 * @r: (allow-none): range to restrict to
3035 * Returns: (transfer full): a list of areas with validation or input
3036 * message.
3038 GnmStyleList *
3039 sheet_style_collect_validations (Sheet const *sheet, GnmRange const *r)
3041 return internal_style_list (sheet, r,
3042 style_validation_equal,
3043 style_validation_filter);
3047 * sheet_style_set_list:
3048 * @sheet: #Sheet
3049 * @corner: The top-left corner (in LTR mode)
3050 * @l: #GnmStyleList
3051 * @range_modify: (scope call):
3052 * @data: user data
3054 * Overwrites the styles of the ranges given by @corner with the content of
3055 * @list. Optionally transposing the ranges
3057 GnmSpanCalcFlags
3058 sheet_style_set_list (Sheet *sheet, GnmCellPos const *corner,
3059 GnmStyleList const *list,
3060 sheet_style_set_list_cb_t range_modify,
3061 gpointer data)
3063 GnmSpanCalcFlags spanflags = GNM_SPANCALC_SIMPLE;
3064 GnmStyleList const *l;
3066 g_return_val_if_fail (IS_SHEET (sheet), spanflags);
3068 /* Sluggish but simple implementation for now */
3069 for (l = list; l; l = l->next) {
3070 GnmStyleRegion const *sr = l->data;
3071 GnmRange r = sr->range;
3073 range_translate (&r, sheet, +corner->col, +corner->row);
3074 if (range_modify)
3075 range_modify (&r, sheet, data);
3077 gnm_style_ref (sr->style);
3078 sheet_style_set_range (sheet, &r, sr->style);
3079 spanflags |= gnm_style_required_spanflags (sr->style);
3081 return spanflags;
3085 * style_list_free:
3086 * @l: the list to free
3088 * Free up the ressources in the style list. Including unreferencing the
3089 * styles.
3091 void
3092 style_list_free (GnmStyleList *list)
3094 g_slist_free_full (list, (GDestroyNotify)gnm_style_region_free);
3098 * style_list_get_style:
3099 * @l: A style list.
3100 * @col:
3101 * @row:
3103 * Attempts to find the style associated with the @pos offset within the 0,0
3104 * based style list.
3105 * The resulting style does not have its reference count bumped.
3107 GnmStyle const *
3108 style_list_get_style (GnmStyleList const *list, int col, int row)
3110 GnmStyleList const *l;
3112 for (l = list; l; l = l->next) {
3113 GnmStyleRegion const *sr = l->data;
3114 GnmRange const *r = &sr->range;
3115 if (range_contains (r, col, row))
3116 return sr->style;
3118 return NULL;
3121 static void
3122 cb_find_link (GnmStyle *style,
3123 G_GNUC_UNUSED int corner_col, G_GNUC_UNUSED int corner_row,
3124 G_GNUC_UNUSED int width, G_GNUC_UNUSED int height,
3125 G_GNUC_UNUSED GnmRange const *apply_to, gpointer user)
3127 GnmHLink **plink = user;
3128 if (*plink == NULL)
3129 *plink = gnm_style_get_hlink (style);
3133 * sheet_style_region_contains_link:
3134 * @sheet:
3135 * @r:
3137 * Utility routine that checks to see if a region contains at least 1 hyper link
3138 * and returns the 1st one it finds.
3140 * Returns: (transfer none): the found #GmHLink if any.
3142 GnmHLink *
3143 sheet_style_region_contains_link (Sheet const *sheet, GnmRange const *r)
3145 GnmHLink *res = NULL;
3147 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3148 g_return_val_if_fail (r != NULL, NULL);
3150 foreach_tile (sheet, r, cb_find_link, &res);
3151 return res;
3155 * sheet_style_foreach:
3156 * @sheet: #Sheet
3157 * @func: (scope call): callback
3158 * @user_data: user data.
3160 * Executes @func for each style in the sheet.
3162 void
3163 sheet_style_foreach (Sheet const *sheet, GFunc func, gpointer user_data)
3165 GSList *styles;
3167 g_return_if_fail (IS_SHEET (sheet));
3168 g_return_if_fail (sheet->style_data != NULL);
3170 styles = sh_all_styles (sheet->style_data->style_hash);
3171 styles = g_slist_sort (styles, (GCompareFunc)gnm_style_cmp);
3172 g_slist_foreach (styles, func, user_data);
3173 g_slist_free (styles);
3177 * sheet_style_range_foreach:
3178 * @sheet: #Sheet
3179 * @r: optional range
3180 * @func: (scope call): callback.
3181 * @user_data: user data
3184 void
3185 sheet_style_range_foreach (Sheet const *sheet, GnmRange const *r,
3186 GHFunc func, gpointer user_data)
3188 GnmStyleList *styles, *l;
3190 styles = sheet_style_get_range (sheet, r);
3192 for (l = styles; l; l = l->next) {
3193 GnmStyleRegion *sr = l->data;
3194 if (r) {
3195 sr->range.start.col += r->start.col;
3196 sr->range.start.row += r->start.row;
3197 sr->range.end.col += r->start.col;
3198 sr->range.end.row += r->start.row;
3200 func (NULL, sr, user_data);
3201 gnm_style_region_free (sr);
3204 g_slist_free (styles);
3207 /* ------------------------------------------------------------------------- */
3209 static void
3210 cell_tile_dump (CellTile **tile, int level, CellTileOptimize *data,
3211 int ccol, int crow)
3213 CellTileType type;
3214 int const w = tile_widths[level];
3215 int const h = tile_heights[level];
3216 GnmRange rng;
3217 const char *indent = "";
3219 type = (*tile)->type;
3221 range_init (&rng,
3222 ccol, crow,
3223 MIN (ccol + tile_widths[level + 1] - 1,
3224 data->ss->max_cols - 1),
3225 MIN (crow + tile_heights[level + 1] - 1,
3226 data->ss->max_rows - 1));
3228 g_printerr ("%s%s: type %s\n",
3229 indent,
3230 range_as_string (&rng),
3231 tile_type_str[type]);
3233 if (type == TILE_PTR_MATRIX) {
3234 int i;
3236 for (i = 0; i < TILE_SIZE_COL * TILE_SIZE_ROW; i++) {
3237 CellTile **subtile = (*tile)->ptr_matrix.ptr + i;
3238 int c = i % TILE_SIZE_COL;
3239 int r = i / TILE_SIZE_COL;
3240 cell_tile_dump (subtile, level - 1, data,
3241 ccol + w * c,
3242 crow + h * r);
3244 } else {
3245 #if 0
3246 int i;
3248 for (i = 0; i < tile_size[type]; i++) {
3249 g_printerr ("%s: %d %p\n",
3250 indent,
3252 (*tile)->style_any.style[i]);
3254 #endif
3258 /* ------------------------------------------------------------------------- */
3260 static void
3261 cell_tile_optimize (CellTile **tile, int level, CellTileOptimize *data,
3262 int ccol, int crow)
3264 CellTileType type;
3265 int const w = tile_widths[level];
3266 int const h = tile_heights[level];
3267 CellTile *res;
3268 int i;
3269 GnmRange rng;
3271 type = (*tile)->type;
3272 if (type == TILE_SIMPLE)
3273 return;
3275 range_init (&rng,
3276 ccol, crow,
3277 MIN (ccol + tile_widths[level + 1] - 1,
3278 data->ss->max_cols - 1),
3279 MIN (crow + tile_heights[level + 1] - 1,
3280 data->ss->max_rows - 1));
3282 switch (type) {
3283 case TILE_COL:
3284 case TILE_ROW:
3285 if (!tile_is_uniform (*tile))
3286 return;
3288 type = TILE_SIMPLE;
3289 break;
3291 case TILE_PTR_MATRIX: {
3292 gboolean all_simple = TRUE;
3293 int i;
3295 for (i = 0; i < TILE_SIZE_COL * TILE_SIZE_ROW; i++) {
3296 CellTile **subtile = (*tile)->ptr_matrix.ptr + i;
3297 if (data->recursion) {
3298 int c = i % TILE_SIZE_COL;
3299 int r = i / TILE_SIZE_COL;
3300 cell_tile_optimize (subtile, level - 1, data,
3301 ccol + w * c,
3302 crow + h * r);
3304 if ((*subtile)->type != TILE_SIMPLE)
3305 all_simple = FALSE;
3307 if (!all_simple)
3308 return;
3310 res = cell_tile_style_new (NULL, TILE_MATRIX);
3311 for (i = 0; i < TILE_SIZE_COL * TILE_SIZE_ROW; i++) {
3312 CellTile *subtile = (*tile)->ptr_matrix.ptr[i];
3313 GnmStyle *st = subtile->style_simple.style[0];
3314 gnm_style_link (st);
3315 res->style_matrix.style[i] = st;
3318 if (debug_style_optimize)
3319 g_printerr ("Turning %s (%dx%d) from a %s into a %s\n",
3320 range_as_string (&rng),
3321 range_width (&rng), range_height (&rng),
3322 tile_type_str[(*tile)->type],
3323 tile_type_str[res->type]);
3324 cell_tile_dtor (*tile);
3325 *tile = res;
3327 /* Fall through */
3330 case TILE_MATRIX: {
3331 gboolean csame = TRUE;
3332 gboolean rsame = TRUE;
3333 int c, r, i;
3335 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r, i += TILE_SIZE_COL) {
3336 for (c = 0 ; c < TILE_SIZE_COL ; ++c) {
3337 if (rsame && c &&
3338 !gnm_style_eq ((*tile)->style_matrix.style[i + c],
3339 (*tile)->style_matrix.style[i ])) {
3340 rsame = FALSE;
3341 if (!csame)
3342 return;
3344 if (csame && r &&
3345 !gnm_style_eq ((*tile)->style_matrix.style[i + c],
3346 (*tile)->style_matrix.style[ c])) {
3347 csame = FALSE;
3348 if (!rsame)
3349 return;
3354 if (csame && rsame)
3355 type = TILE_SIMPLE;
3356 else if (csame) {
3357 type = TILE_COL;
3358 } else {
3359 type = TILE_ROW;
3361 break;
3364 default:
3365 g_assert_not_reached ();
3368 if (debug_style_optimize)
3369 g_printerr ("Turning %s (%dx%d) from a %s into a %s\n",
3370 range_as_string (&rng),
3371 range_width (&rng), range_height (&rng),
3372 tile_type_str[(*tile)->type],
3373 tile_type_str[type]);
3375 res = cell_tile_style_new (NULL, type);
3376 switch (type) {
3377 case TILE_SIMPLE:
3378 res->style_simple.style[0] = (*tile)->style_any.style[0];
3379 break;
3380 case TILE_ROW:
3381 for (i = 0; i < TILE_SIZE_ROW; i++)
3382 res->style_row.style[i] =
3383 (*tile)->style_matrix.style[i * TILE_SIZE_COL];
3384 break;
3385 case TILE_COL:
3386 for (i = 0; i < TILE_SIZE_COL; i++)
3387 res->style_col.style[i] =
3388 (*tile)->style_matrix.style[i];
3389 break;
3390 default:
3391 g_assert_not_reached ();
3394 for (i = 0; i < tile_size[type]; i++)
3395 gnm_style_link (res->style_any.style[i]);
3397 cell_tile_dtor (*tile);
3398 *tile = res;
3401 static GSList *
3402 sample_styles (Sheet *sheet)
3404 GnmSheetSize const *ss = gnm_sheet_get_size (sheet);
3405 GSList *res = NULL;
3406 int c = 0, r = 0;
3407 const int SKIP = 1;
3409 while (1) {
3410 GnmStyle const *mstyle = sheet_style_get (sheet, c, r);
3411 if (res == NULL || !gnm_style_eq (mstyle, res->data)) {
3412 gnm_style_ref (mstyle);
3413 res = g_slist_prepend (res, GINT_TO_POINTER (c));
3414 res = g_slist_prepend (res, GINT_TO_POINTER (r));
3415 res = g_slist_prepend (res, (gpointer)mstyle);
3418 c += SKIP;
3419 if (c >= ss->max_cols) {
3420 c -= ss->max_cols;
3421 r++;
3422 if (r >= ss->max_rows)
3423 break;
3427 return g_slist_reverse (res);
3430 static void
3431 verify_styles (GSList *pre, GSList *post)
3433 GSList *lpre, *lpost;
3434 gboolean silent = FALSE, bad = FALSE;
3436 for (lpre = pre, lpost = post;
3437 lpre || lpost;
3438 lpre = (lpre ? lpre->next->next->next : NULL),
3439 lpost = (lpost ? lpost->next->next->next : NULL)) {
3440 int cpre = lpre ? GPOINTER_TO_INT (lpre->data) : -1;
3441 int rpre = lpre ? GPOINTER_TO_INT (lpre->next->data) : -1;
3442 GnmStyle const *spre = lpre ? lpre->next->next->data : NULL;
3443 int cpost = lpost ? GPOINTER_TO_INT (lpost->data) : -1;
3444 int rpost = lpost ? GPOINTER_TO_INT (lpost->next->data) : -1;
3445 GnmStyle const *spost = lpost ? lpost->next->next->data : NULL;
3447 if (!silent) {
3448 if (!spre || !spost) {
3449 bad = TRUE;
3450 g_warning ("Style optimizer failure at end!");
3451 silent = TRUE;
3452 } else if (cpre != cpost || rpre != rpost) {
3453 bad = TRUE;
3454 g_warning ("Style optimizer position conflict at %s!",
3455 cell_coord_name (cpre, rpre));
3456 silent = TRUE;
3457 } else if (!gnm_style_eq (spre, spost)) {
3458 bad = TRUE;
3459 g_warning ("Style optimizer failure at %s!",
3460 cell_coord_name (cpre, rpre));
3464 if (spre) gnm_style_unref (spre);
3465 if (spost) gnm_style_unref (spost);
3468 g_slist_free (pre);
3469 g_slist_free (post);
3471 g_assert (!bad);
3474 void
3475 sheet_style_optimize (Sheet *sheet)
3477 CellTileOptimize data;
3478 GSList *pre;
3479 gboolean verify;
3481 g_return_if_fail (IS_SHEET (sheet));
3483 if (gnm_debug_flag ("no-style-optimize"))
3484 return;
3486 sheet_colrow_optimize (sheet);
3488 data.ss = gnm_sheet_get_size (sheet);
3489 data.recursion = TRUE;
3491 if (debug_style_optimize) {
3492 g_printerr ("Optimizing %s\n", sheet->name_unquoted);
3493 cell_tile_dump (&sheet->style_data->styles,
3494 sheet->tile_top_level, &data,
3495 0, 0);
3498 verify = gnm_debug_flag ("style-optimize-verify");
3499 pre = verify ? sample_styles (sheet) : NULL;
3501 cell_tile_optimize (&sheet->style_data->styles,
3502 sheet->tile_top_level, &data,
3503 0, 0);
3505 if (debug_style_optimize)
3506 g_printerr ("Optimizing %s...done\n", sheet->name_unquoted);
3508 if (verify) {
3509 GSList *post = sample_styles (sheet);
3510 verify_styles (pre, post);