Move plugin init code
[gnumeric.git] / src / sheet-style.c
blobf61eb5e9ba3b63897170221b69a6effc4cd7f355
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: a style
187 * Looks up a style from the sheets collection. Linking if necessary.
188 * ABSORBS the reference and adds a link.
190 * Returns: (transfer full): the new style.
192 GnmStyle *
193 sheet_style_find (Sheet const *sheet, GnmStyle *s)
195 GnmStyle *res;
196 res = sh_lookup (sheet->style_data->style_hash, s);
197 if (res != NULL) {
198 gnm_style_link (res);
199 gnm_style_unref (s);
200 return res;
203 s = gnm_style_link_sheet (s, (Sheet *)sheet);
205 /* Retry the lookup in case "s" changed. See #585178. */
206 res = sh_lookup (sheet->style_data->style_hash, s);
207 if (res != NULL) {
208 gnm_style_link (res);
210 * We are abandoning the linking here. We cannot use
211 * gnm_style_unlink as that would call sheet_style_unlink
212 * and thus remove "res" from the hash.
214 gnm_style_abandon_link (s);
215 gnm_style_unref (s);
217 return res;
220 sh_insert (sheet->style_data->style_hash, s);
221 return s;
224 /* Place holder until I merge in the new styles too */
225 static void
226 pstyle_set_border (GnmStyle *st, GnmBorder *border,
227 GnmStyleBorderLocation side)
229 gnm_style_set_border (st,
230 GNM_STYLE_BORDER_LOCATION_TO_STYLE_ELEMENT (side),
231 gnm_style_border_ref (border));
234 /* Amortize the cost of applying a partial style over a large region
235 * by caching and rereferencing the merged result for repeated styles.
237 typedef struct {
238 GnmStyle *new_style;
239 GnmStyle *pstyle;
240 GHashTable *cache;
241 Sheet *sheet;
242 } ReplacementStyle;
244 static void
245 rstyle_ctor_style (ReplacementStyle *res, GnmStyle *new_style, Sheet *sheet)
247 res->sheet = sheet;
248 res->new_style = sheet_style_find (sheet, new_style);
249 res->pstyle = NULL;
250 res->cache = NULL;
253 static void
254 rstyle_ctor_pstyle (ReplacementStyle *res, GnmStyle *pstyle, Sheet *sheet)
256 res->sheet = sheet;
257 res->new_style = NULL;
258 res->pstyle = pstyle;
259 res->cache = g_hash_table_new (g_direct_hash, g_direct_equal);
262 static void
263 cb_style_unlink (gpointer key, gpointer value, G_GNUC_UNUSED gpointer user_data)
265 gnm_style_unlink ((GnmStyle *)key);
266 gnm_style_unlink ((GnmStyle *)value);
269 static void
270 rstyle_dtor (ReplacementStyle *rs)
272 if (rs->cache != NULL) {
273 g_hash_table_foreach (rs->cache, cb_style_unlink, NULL);
274 g_hash_table_destroy (rs->cache);
275 rs->cache = NULL;
277 if (rs->new_style != NULL) {
278 gnm_style_unlink (rs->new_style);
279 rs->new_style = NULL;
281 if (rs->pstyle != NULL) {
282 gnm_style_unref (rs->pstyle);
283 rs->pstyle = NULL;
288 * rstyle_apply: Utility routine that is at the core of applying partial
289 * styles or storing complete styles. It will eventually be smarter
290 * and will maintain the cache of styles associated with each sheet
292 static void
293 rstyle_apply (GnmStyle **old, ReplacementStyle *rs, GnmRange const *r)
295 GnmStyle *s;
296 g_return_if_fail (old != NULL);
297 g_return_if_fail (rs != NULL);
299 if (rs->pstyle != NULL) {
300 /* Cache the merged styles keeping a reference to the originals
301 * just in case all instances change.
303 s = g_hash_table_lookup (rs->cache, *old);
304 if (s == NULL) {
305 GnmStyle *tmp = gnm_style_new_merged (*old, rs->pstyle);
306 s = sheet_style_find (rs->sheet, tmp);
307 gnm_style_link (*old);
308 g_hash_table_insert (rs->cache, *old, s);
310 } else
311 s = rs->new_style;
313 if (*old != s) {
314 if (*old) {
315 gnm_style_unlink_dependents (*old, r);
316 gnm_style_unlink (*old);
319 gnm_style_link_dependents (s, r);
320 gnm_style_link (s);
322 *old = s;
326 void
327 sheet_style_clear_style_dependents (Sheet *sheet, GnmRange const *r)
329 GSList *styles = sh_all_styles (sheet->style_data->style_hash);
330 g_slist_foreach (styles,
331 (GFunc)gnm_style_unlink_dependents,
332 (gpointer)r);
333 g_slist_free (styles);
337 /****************************************************************************/
339 /* If you change this, change the tile_{widths,heights} here
340 * and GNM_MAX_COLS and GNM_MAX_ROWS in gnumeric.h */
341 #define TILE_TOP_LEVEL 6
343 #define TILE_SIZE_COL 8
344 #define TILE_SIZE_ROW 16
346 typedef enum {
347 TILE_UNDEFINED = -1,
348 TILE_SIMPLE = 0,
349 TILE_COL = 1,
350 TILE_ROW = 2,
351 TILE_MATRIX = 3,
352 TILE_PTR_MATRIX = 4
353 } CellTileType;
354 static int const tile_size[/*type*/] = {
355 1, /* TILE_SIMPLE */
356 TILE_SIZE_COL, /* TILE_COL */
357 TILE_SIZE_ROW, /* TILE_ROW */
358 TILE_SIZE_COL * TILE_SIZE_ROW /* TILE_MATRIX */
360 static int const tile_col_count[/*type*/] = {
361 1, /* TILE_SIMPLE */
362 TILE_SIZE_COL, /* TILE_COL */
363 1, /* TILE_ROW */
364 TILE_SIZE_COL, /* TILE_MATRIX */
365 TILE_SIZE_COL /* TILE_PTR_MATRIX */
367 static int const tile_row_count[/*type*/] = {
368 1, /* TILE_SIMPLE */
369 1, /* TILE_COL */
370 TILE_SIZE_ROW, /* TILE_ROW */
371 TILE_SIZE_ROW, /* TILE_MATRIX */
372 TILE_SIZE_ROW /* TILE_PTR_MATRIX */
374 static const char * const tile_type_str[/*type*/] = {
375 "simple", "col", "row", "matrix", "ptr-matrix"
377 static int const tile_widths[/*level*/] = {
379 TILE_SIZE_COL,
380 TILE_SIZE_COL * TILE_SIZE_COL,
381 TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL,
382 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,
384 TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL,
385 TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL
387 static int const tile_heights[/*level*/] = {
389 TILE_SIZE_ROW,
390 TILE_SIZE_ROW * TILE_SIZE_ROW,
391 TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW,
392 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,
394 TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW,
395 TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW
398 typedef struct {
399 CellTileType const type;
400 GnmStyle *style[1];
401 } CellTileStyleSimple;
402 typedef struct {
403 CellTileType const type;
404 GnmStyle *style[TILE_SIZE_COL];
405 } CellTileStyleCol;
406 typedef struct {
407 CellTileType const type;
408 GnmStyle *style[TILE_SIZE_ROW];
409 } CellTileStyleRow;
410 typedef struct {
411 CellTileType const type;
412 GnmStyle *style[TILE_SIZE_COL * TILE_SIZE_ROW];
413 } CellTileStyleMatrix;
414 typedef struct {
415 CellTileType const type;
416 CellTile *ptr[TILE_SIZE_COL * TILE_SIZE_ROW];
417 } CellTilePtrMatrix;
419 union _CellTile {
420 CellTileType const type;
421 CellTileStyleSimple style_any;
422 CellTileStyleSimple style_simple;
423 CellTileStyleCol style_col;
424 CellTileStyleRow style_row;
425 CellTileStyleMatrix style_matrix;
426 CellTilePtrMatrix ptr_matrix;
429 static int active_sheet_count;
430 #if USE_TILE_POOLS
431 static GOMemChunk *tile_pools[5];
432 #define CHUNK_ALLOC(T,ctt) ((T*)go_mem_chunk_alloc (tile_pools[(ctt)]))
433 #define CHUNK_FREE(ctt,v) go_mem_chunk_free (tile_pools[(ctt)], (v))
434 #else
435 static const size_t tile_type_sizeof[5] = {
436 sizeof (CellTileStyleSimple),
437 sizeof (CellTileStyleCol),
438 sizeof (CellTileStyleRow),
439 sizeof (CellTileStyleMatrix),
440 sizeof (CellTilePtrMatrix)
442 static int tile_allocations = 0;
443 #if 1
444 #define CHUNK_ALLOC(T,ctt) (tile_allocations++, (T*)g_slice_alloc (tile_type_sizeof[(ctt)]))
445 #define CHUNK_FREE(ctt,v) (tile_allocations--, g_slice_free1 (tile_type_sizeof[(ctt)], (v)))
446 #else
447 #define CHUNK_ALLOC(T,ctt) (tile_allocations++, (T*)g_malloc (tile_type_sizeof[(ctt)]))
448 #define CHUNK_FREE(ctt,v) (tile_allocations--, g_free ((v)))
449 #endif
450 #endif
454 * Destroy a CellTile (recursively if needed). This will unlink all the
455 * styles in it. We do _not_ unlink style dependents here. That is done
456 * only in rstyle_apply.
458 static void
459 cell_tile_dtor (CellTile *tile)
461 CellTileType t;
463 g_return_if_fail (tile != NULL);
465 t = tile->type;
466 if (t == TILE_PTR_MATRIX) {
467 int i = TILE_SIZE_COL * TILE_SIZE_ROW;
468 while (--i >= 0) {
469 cell_tile_dtor (tile->ptr_matrix.ptr[i]);
470 tile->ptr_matrix.ptr[i] = NULL;
472 } else if (TILE_SIMPLE <= t && t <= TILE_MATRIX) {
473 int i = tile_size[t];
474 while (--i >= 0) {
475 gnm_style_unlink (tile->style_any.style[i]);
476 tile->style_any.style[i] = NULL;
478 } else {
479 g_return_if_fail (FALSE); /* don't free anything */
482 *((CellTileType *)&(tile->type)) = TILE_UNDEFINED; /* poison it */
483 CHUNK_FREE (t, tile);
486 static CellTile *
487 cell_tile_style_new (GnmStyle *style, CellTileType t)
489 CellTile *res = CHUNK_ALLOC (CellTile, t);
490 *((CellTileType *)&(res->type)) = t;
492 if (style != NULL) {
493 int i = tile_size[t];
494 gnm_style_link_multiple (style, i);
495 while (--i >= 0)
496 res->style_any.style[i] = style;
499 return res;
502 static CellTile *
503 cell_tile_ptr_matrix_new (CellTile *t)
505 CellTilePtrMatrix *res;
507 g_return_val_if_fail (t != NULL, NULL);
508 g_return_val_if_fail (TILE_SIMPLE <= t->type &&
509 TILE_MATRIX >= t->type, NULL);
511 res = CHUNK_ALLOC (CellTilePtrMatrix, TILE_PTR_MATRIX);
512 *((CellTileType *)&(res->type)) = TILE_PTR_MATRIX;
514 /* TODO:
515 * If we wanted to get fancy we could use self similarity to decrease
516 * the number of subtiles. However, this would increase the cost of
517 * applying changes later so I'm not sure it is worth the effort.
519 switch (t->type) {
520 case TILE_SIMPLE: {
521 int i = TILE_SIZE_COL * TILE_SIZE_ROW;
522 while (--i >= 0)
523 res->ptr[i] = cell_tile_style_new (
524 t->style_simple.style[0], TILE_SIMPLE);
525 break;
527 case TILE_COL: {
528 int i, r, c;
529 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r)
530 for (c = 0 ; c < TILE_SIZE_COL ; ++c)
531 res->ptr[i++] = cell_tile_style_new (
532 t->style_col.style[c], TILE_SIMPLE);
533 break;
535 case TILE_ROW: {
536 int i, r, c;
537 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r)
538 for (c = 0 ; c < TILE_SIZE_COL ; ++c)
539 res->ptr[i++] = cell_tile_style_new (
540 t->style_row.style[r], TILE_SIMPLE);
541 break;
543 case TILE_MATRIX: {
544 int i = TILE_SIZE_COL * TILE_SIZE_ROW;
545 while (--i >= 0)
546 res->ptr[i] = cell_tile_style_new (
547 t->style_matrix.style[i], TILE_SIMPLE);
548 break;
550 default: ;
553 return (CellTile *)res;
556 static CellTile *
557 cell_tile_matrix_set (CellTile *t)
559 int r, c;
560 CellTileStyleMatrix *res;
562 g_return_val_if_fail (t != NULL, NULL);
563 g_return_val_if_fail (TILE_SIMPLE <= t->type &&
564 TILE_MATRIX >= t->type, NULL);
566 if (t->type == TILE_MATRIX)
567 return t;
569 res = (CellTileStyleMatrix *)cell_tile_style_new (NULL, TILE_MATRIX);
571 switch (t->type) {
572 case TILE_SIMPLE: {
573 GnmStyle *tmp = t->style_simple.style[0];
574 int i = TILE_SIZE_COL * TILE_SIZE_ROW;
575 gnm_style_link_multiple (tmp, i);
576 while (--i >= 0)
577 res->style[i] = tmp;
578 break;
581 case TILE_COL: {
582 int i = 0;
583 for (r = 0; r < TILE_SIZE_ROW; ++r)
584 for (c = 0; c < TILE_SIZE_COL; ++c)
585 gnm_style_link (res->style[i++] =
586 t->style_col.style[c]);
587 break;
590 case TILE_ROW: {
591 int i = 0;
592 for (r = 0; r < TILE_SIZE_ROW; ++r) {
593 GnmStyle *tmp = t->style_row.style[r];
594 gnm_style_link_multiple (tmp, TILE_SIZE_COL);
595 for (c = 0; c < TILE_SIZE_COL; ++c)
596 res->style[i++] = tmp;
598 break;
601 case TILE_MATRIX:
602 default:
603 g_assert_not_reached();
606 cell_tile_dtor (t);
608 return (CellTile *)res;
611 /****************************************************************************/
613 static void
614 sheet_style_sanity_check (void)
616 unsigned c, r;
617 int i;
619 for (c = 1, i = 0; i <= TILE_TOP_LEVEL; i++) {
620 g_assert (c < G_MAXUINT / TILE_SIZE_COL);
621 c *= TILE_SIZE_COL;
623 g_assert (c >= GNM_MAX_COLS);
625 for (r = 1, i = 0; i <= TILE_TOP_LEVEL; i++) {
626 g_assert (r < G_MAXUINT / TILE_SIZE_COL);
627 r *= TILE_SIZE_ROW;
629 g_assert (r >= GNM_MAX_ROWS);
631 g_assert (G_N_ELEMENTS (tile_heights) > TILE_TOP_LEVEL + 1);
633 g_assert (G_N_ELEMENTS (tile_widths) > TILE_TOP_LEVEL + 1);
636 static void
637 sheet_style_init_size (Sheet *sheet, int cols, int rows)
639 GnmStyle *default_style;
640 int lc = 0, lr = 0, w = TILE_SIZE_COL, h = TILE_SIZE_ROW;
642 while (w < cols) {
643 w *= TILE_SIZE_COL;
644 lc++;
646 while (h < rows) {
647 h *= TILE_SIZE_ROW;
648 lr++;
650 sheet->tile_top_level = MAX (lc, lr);
652 if (active_sheet_count++ == 0) {
653 #if USE_TILE_POOLS
654 tile_pools[TILE_SIMPLE] =
655 go_mem_chunk_new ("simple tile pool",
656 sizeof (CellTileStyleSimple),
657 16 * 1024 - 128);
658 tile_pools[TILE_COL] =
659 go_mem_chunk_new ("column tile pool",
660 sizeof (CellTileStyleCol),
661 16 * 1024 - 128);
662 tile_pools[TILE_ROW] =
663 go_mem_chunk_new ("row tile pool",
664 sizeof (CellTileStyleRow),
665 16 * 1024 - 128);
666 tile_pools[TILE_MATRIX] =
667 go_mem_chunk_new ("matrix tile pool",
668 sizeof (CellTileStyleMatrix),
669 MAX (16 * 1024 - 128,
670 100 * sizeof (CellTileStyleMatrix)));
672 /* If this fails one day, just make two pools. */
673 g_assert (sizeof (CellTileStyleMatrix) == sizeof (CellTilePtrMatrix));
674 tile_pools[TILE_PTR_MATRIX] = tile_pools[TILE_MATRIX];
675 #endif
678 sheet->style_data = g_new (GnmSheetStyleData, 1);
679 sheet->style_data->style_hash = sh_create ();
681 sheet->style_data->auto_pattern_color = style_color_auto_pattern ();
683 default_style = gnm_style_new_default ();
684 #if 0
685 /* We can not do this, XL creates full page charts with background
686 * 'none' by default. Then displays that as white. */
687 if (sheet->sheet_type == GNM_SHEET_OBJECT) {
688 gnm_style_set_back_color (default_style,
689 gnm_color_new_rgb8 (0x50, 0x50, 0x50));
690 gnm_style_set_pattern (default_style, 1);
692 #endif
693 sheet->style_data->default_style =
694 sheet_style_find (sheet, default_style);
695 sheet->style_data->styles =
696 cell_tile_style_new (sheet->style_data->default_style,
697 TILE_SIMPLE);
700 void
701 sheet_style_init (Sheet *sheet)
703 int cols = gnm_sheet_get_max_cols (sheet);
704 int rows = gnm_sheet_get_max_rows (sheet);
706 debug_style_optimize = gnm_debug_flag ("style-optimize");
708 sheet_style_sanity_check ();
710 sheet_style_init_size (sheet, cols, rows);
713 void
714 sheet_style_resize (Sheet *sheet, int cols, int rows)
716 GnmStyleList *styles, *l;
717 int old_cols = gnm_sheet_get_max_cols (sheet);
718 int old_rows = gnm_sheet_get_max_rows (sheet);
719 GnmRange save_range, new_full;
721 /* Save the style for the surviving area. */
722 range_init (&save_range, 0, 0,
723 MIN (cols, old_cols) - 1, MIN (rows, old_rows) - 1);
724 styles = sheet_style_get_range (sheet, &save_range);
726 /* Build new empty structures. */
727 sheet_style_shutdown (sheet);
728 sheet_style_init_size (sheet, cols, rows);
730 /* Reapply styles. */
731 range_init (&new_full, 0, 0, cols - 1, rows - 1);
732 for (l = styles; l; l = l->next) {
733 GnmStyleRegion const *sr = l->data;
734 GnmRange const *r = &sr->range;
735 GnmStyle *style = sr->style;
736 GnmRange newr;
737 if (range_intersection (&newr, r, &new_full))
738 sheet_style_apply_range2 (sheet, &newr, style);
741 style_list_free (styles);
744 #if USE_TILE_POOLS
745 static void
746 cb_tile_pool_leak (gpointer data, gpointer user)
748 CellTile *tile = data;
749 g_printerr ("Leaking tile at %p.\n", (void *)tile);
751 #endif
753 void
754 sheet_style_shutdown (Sheet *sheet)
756 GnmStyleHash *table;
757 GnmRange r;
759 g_return_if_fail (IS_SHEET (sheet));
760 g_return_if_fail (sheet->style_data != NULL);
763 * Clear all styles. This is an easy way to clear out all
764 * style dependencies.
766 range_init_full_sheet (&r, sheet);
767 sheet_style_set_range (sheet, &r, sheet_style_default (sheet));
769 cell_tile_dtor (sheet->style_data->styles);
770 sheet->style_data->styles = NULL;
772 sheet->style_data->default_style = NULL;
774 /* Clear the pointer to the hash BEFORE clearing and add a test in
775 * sheet_style_unlink. If we don't then it is possible/probable that
776 * unlinking the styles will attempt to remove them from the hash while
777 * we are walking it.
779 table = sheet->style_data->style_hash;
780 sheet->style_data->style_hash = NULL;
781 g_slist_free_full (sh_all_styles (table),
782 (GDestroyNotify)gnm_style_unlink);
783 sh_destroy (table);
784 style_color_unref (sheet->style_data->auto_pattern_color);
786 g_free (sheet->style_data);
787 sheet->style_data = NULL;
789 if (--active_sheet_count == 0) {
790 #if USE_TILE_POOLS
791 go_mem_chunk_foreach_leak (tile_pools[TILE_SIMPLE],
792 cb_tile_pool_leak, NULL);
793 go_mem_chunk_destroy (tile_pools[TILE_SIMPLE], FALSE);
794 tile_pools[TILE_SIMPLE] = NULL;
796 go_mem_chunk_foreach_leak (tile_pools[TILE_COL],
797 cb_tile_pool_leak, NULL);
798 go_mem_chunk_destroy (tile_pools[TILE_COL], FALSE);
799 tile_pools[TILE_COL] = NULL;
801 go_mem_chunk_foreach_leak (tile_pools[TILE_ROW],
802 cb_tile_pool_leak, NULL);
803 go_mem_chunk_destroy (tile_pools[TILE_ROW], FALSE);
804 tile_pools[TILE_ROW] = NULL;
806 go_mem_chunk_foreach_leak (tile_pools[TILE_MATRIX],
807 cb_tile_pool_leak, NULL);
808 go_mem_chunk_destroy (tile_pools[TILE_MATRIX], FALSE);
809 tile_pools[TILE_MATRIX] = NULL;
811 /* If this fails one day, just make two pools. */
812 g_assert (sizeof (CellTileStyleMatrix) == sizeof (CellTilePtrMatrix));
813 tile_pools[TILE_PTR_MATRIX] = NULL;
814 #else
815 if (tile_allocations)
816 g_printerr ("Leaking %d style tiles.\n", tile_allocations);
817 #endif
822 * sheet_style_set_auto_pattern_color:
823 * @sheet: The sheet
824 * @grid_color: (transfer full): The color
826 * Set the color for rendering auto colored patterns in this sheet.
827 * Absorbs a reference to @pattern_color;
829 void
830 sheet_style_set_auto_pattern_color (Sheet *sheet, GnmColor *pattern_color)
832 g_return_if_fail (IS_SHEET (sheet));
833 g_return_if_fail (sheet->style_data != NULL);
835 style_color_unref (sheet->style_data->auto_pattern_color);
836 sheet->style_data->auto_pattern_color = gnm_color_new_auto (pattern_color->go_color);
837 style_color_unref (pattern_color);
841 * sheet_style_get_auto_pattern_color:
842 * @sheet: the sheet
844 * Returns: (transfer full): the color for rendering auto colored patterns
845 * in this sheet.
847 GnmColor *
848 sheet_style_get_auto_pattern_color (Sheet const *sheet)
850 GnmColor *sc;
851 g_return_val_if_fail (IS_SHEET (sheet), style_color_black ());
852 g_return_val_if_fail (sheet->style_data != NULL, style_color_black ());
853 g_return_val_if_fail (sheet->style_data->auto_pattern_color != NULL,
854 style_color_black ());
856 sc = sheet->style_data->auto_pattern_color;
857 style_color_ref (sc);
859 return sc;
863 * sheet_style_update_grid_color:
865 * This function updates the color of gnm_style_border_none when the sheet to be
866 * rendered is known. gnm_style_border_none tells how to render the
867 * grid. Because the grid color may be different for different sheets, the
868 * functions which render the grid call this function first. The rule for
869 * selecting the grid color, which is the same as in Excel, is: - if the
870 * auto pattern color is default (which is black), the grid color is gray,
871 * as returned by style_color_grid (). - otherwise, the auto pattern color
872 * is used for the grid.
874 void
875 sheet_style_update_grid_color (Sheet const *sheet)
877 GnmColor *default_auto = style_color_auto_pattern ();
878 GnmColor *sheet_auto = sheet_style_get_auto_pattern_color (sheet);
879 GnmColor *grid_color = style_color_grid ();
880 GnmColor *new_color;
882 new_color = (style_color_equal (default_auto, sheet_auto)
883 ? grid_color : sheet_auto);
885 /* Do nothing if we already have the right color */
886 if (gnm_style_border_none()->color != new_color) {
887 style_color_ref (new_color); /* none_set eats the ref */
888 gnm_style_border_none_set_color (new_color);
890 style_color_unref (grid_color);
891 style_color_unref (sheet_auto);
892 style_color_unref (default_auto);
895 /****************************************************************************/
897 static gboolean
898 tile_is_uniform (CellTile const *tile)
900 const int s = tile_size[tile->type];
901 GnmStyle const *st = tile->style_any.style[0];
902 int i;
904 for (i = 1; i < s; i++)
905 if (tile->style_any.style[i] != st)
906 return FALSE;
908 return TRUE;
911 static void
912 vector_apply_pstyle (CellTile *tile, ReplacementStyle *rs,
913 int cc, int cr, int level, GnmRange const *indic)
915 const CellTileType type = tile->type;
916 const int ncols = tile_col_count[type];
917 const int nrows = tile_row_count[type];
918 const int w1 = tile_widths[level + 1] / ncols;
919 const int h1 = tile_heights[level + 1] / nrows;
920 const int fcol = indic->start.col;
921 const int frow = indic->start.row;
922 const int lcol = MIN (ncols - 1, indic->end.col);
923 const int lrow = MIN (nrows - 1, indic->end.row);
924 GnmSheetSize const *ss = gnm_sheet_get_size (rs->sheet);
925 int r, c;
926 GnmRange rng;
928 for (r = frow; r <= lrow; r++) {
929 GnmStyle **st = tile->style_any.style + ncols * r;
930 rng.start.row = cr + h1 * r;
931 rng.end.row = MIN (rng.start.row + (h1 - 1),
932 ss->max_rows - 1);
933 for (c = fcol; c <= lcol; c++) {
934 rng.start.col = cc + w1 * c;
935 rng.end.col = MIN (rng.start.col + (w1 - 1),
936 ss->max_cols - 1);
937 rstyle_apply (st + c, rs, &rng);
943 * Determine whether before applying a style in the area of apply_to
944 * one needs to split the tile column-wise.
946 * If FALSE is returned then the tile need to be split to a TILE_PTR_MATRIX
947 * because the current level is not fine-grained enough.
949 * If TRUE is returned, TILE_SIMPLE needs to be split into TILE_COL and
950 * TILE_ROW needs to be split into TILE_MATRIX. TILE_COL and TILE_MATRIX
951 * should be kept. In indic, the inclusive post-split indicies of the
952 * range will be returned.
954 * If apply_to covers the entire tile, TRUE will be returned and the judgement
955 * on splitting above should be ignored. The indices in indic will be as-if
956 * the split was done.
958 static gboolean
959 col_indicies (int corner_col, int w, GnmRange const *apply_to,
960 GnmRange *indec)
962 int i, tmp;
964 i = apply_to->start.col - corner_col;
965 if (i <= 0)
966 indec->start.col = 0;
967 else {
968 tmp = i / w;
969 if (i != tmp * w)
970 return FALSE;
971 indec->start.col = tmp;
974 i = 1 + apply_to->end.col - corner_col;
975 tmp = i / w;
976 if (tmp >= TILE_SIZE_COL)
977 indec->end.col = TILE_SIZE_COL - 1;
978 else {
979 if (i != tmp * w)
980 return FALSE;
981 indec->end.col = tmp - 1;
984 return TRUE;
987 /* See docs for col_indicies. Swap cols and rows. */
988 static gboolean
989 row_indicies (int corner_row, int h, GnmRange const *apply_to,
990 GnmRange *indic)
992 int i, tmp;
994 i = apply_to->start.row - corner_row;
995 if (i <= 0)
996 indic->start.row = 0;
997 else {
998 int tmp = i / h;
999 if (i != tmp * h)
1000 return FALSE;
1001 indic->start.row = tmp;
1004 i = 1 + apply_to->end.row - corner_row;
1005 tmp = i / h;
1006 if (tmp >= TILE_SIZE_ROW)
1007 indic->end.row = TILE_SIZE_ROW - 1;
1008 else {
1009 if (i != tmp * h)
1010 return FALSE;
1011 indic->end.row = tmp - 1;
1014 return TRUE;
1018 * cell_tile_apply: This is the primary logic for making changing areas in the
1019 * tree. It could be further optimised if it becomes a bottle neck.
1021 static void
1022 cell_tile_apply (CellTile **tile, int level,
1023 int corner_col, int corner_row,
1024 GnmRange const *apply_to,
1025 ReplacementStyle *rs)
1027 int const width = tile_widths[level+1];
1028 int const height = tile_heights[level+1];
1029 int const w = tile_widths[level];
1030 int const h = tile_heights[level];
1031 gboolean const full_width = (apply_to->start.col <= corner_col &&
1032 apply_to->end.col >= (corner_col+width-1));
1033 gboolean const full_height = (apply_to->start.row <= corner_row &&
1034 apply_to->end.row >= (corner_row+height-1));
1035 GnmRange indic;
1036 CellTileType type;
1037 int c, r, i;
1039 g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
1040 g_return_if_fail (tile != NULL);
1041 g_return_if_fail (*tile != NULL);
1043 type = (*tile)->type;
1044 g_return_if_fail (TILE_SIMPLE <= type && type <= TILE_PTR_MATRIX);
1046 /* applying the same style to part of a simple-tile is a nop */
1047 if (type == TILE_SIMPLE &&
1048 (*tile)->style_simple.style[0] == rs->new_style)
1049 return;
1052 * Indices for the whole tile assuming a split to matrix.
1053 * We can still use these indices if we don't split either way.
1055 indic.start.col = 0;
1056 indic.start.row = 0;
1057 indic.end.col = TILE_SIZE_COL - 1;
1058 indic.end.row = TILE_SIZE_ROW - 1;
1060 if (type == TILE_PTR_MATRIX)
1061 goto drill_down;
1062 else if (full_width && full_height)
1063 goto apply;
1064 else if (full_height) {
1065 if (!col_indicies (corner_col, w, apply_to, &indic))
1066 goto split_to_ptr_matrix;
1068 switch (type) {
1069 case TILE_SIMPLE: {
1070 CellTile *res;
1071 type = TILE_COL;
1072 res = cell_tile_style_new (
1073 (*tile)->style_simple.style[0],
1074 type);
1075 cell_tile_dtor (*tile);
1076 *tile = res;
1077 /* Fall through */
1079 case TILE_COL:
1080 case TILE_MATRIX:
1081 goto apply;
1082 case TILE_ROW:
1083 goto split_to_matrix;
1084 default:
1085 g_assert_not_reached ();
1087 } else if (full_width) {
1088 if (!row_indicies (corner_row, h, apply_to, &indic))
1089 goto split_to_ptr_matrix;
1090 switch (type) {
1091 case TILE_SIMPLE: {
1092 CellTile *res;
1094 type = TILE_ROW;
1095 res = cell_tile_style_new (
1096 (*tile)->style_simple.style[0],
1097 type);
1098 cell_tile_dtor (*tile);
1099 *tile = res;
1100 /* Fall through */
1102 case TILE_ROW:
1103 case TILE_MATRIX:
1104 goto apply;
1105 case TILE_COL:
1106 goto split_to_matrix;
1107 default:
1108 g_assert_not_reached ();
1110 } else {
1111 if (col_indicies (corner_col, w, apply_to, &indic) &&
1112 row_indicies (corner_row, h, apply_to, &indic))
1113 goto split_to_matrix;
1114 else
1115 goto split_to_ptr_matrix;
1118 g_assert_not_reached ();
1120 split_to_matrix:
1121 *tile = cell_tile_matrix_set (*tile);
1123 apply:
1124 vector_apply_pstyle (*tile, rs, corner_col, corner_row, level, &indic);
1126 try_optimize:
1128 CellTileOptimize cto;
1129 cto.ss = gnm_sheet_get_size (rs->sheet);
1130 cto.recursion = FALSE;
1131 cell_tile_optimize (tile, level, &cto, corner_col, corner_row);
1133 return;
1135 split_to_ptr_matrix:
1137 * We get here when apply_to's corners are not on a TILE_MATRIX grid.
1138 * Split to pointer matrix whose element tiles will have a finer grid.
1140 g_return_if_fail (type != TILE_PTR_MATRIX);
1142 CellTile *res = cell_tile_ptr_matrix_new (*tile);
1143 cell_tile_dtor (*tile);
1144 *tile = res;
1145 type = TILE_PTR_MATRIX;
1148 drill_down:
1149 g_return_if_fail (type == TILE_PTR_MATRIX);
1150 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r, i += TILE_SIZE_COL) {
1151 int const cr = corner_row + h*r;
1152 if (cr > apply_to->end.row)
1153 break;
1154 if ((cr + h) <= apply_to->start.row)
1155 continue;
1157 for (c = 0 ; c < TILE_SIZE_COL ; ++c) {
1158 int const cc = corner_col + w*c;
1159 if (cc > apply_to->end.col)
1160 break;
1161 if ((cc + w) <= apply_to->start.col)
1162 continue;
1164 cell_tile_apply ((*tile)->ptr_matrix.ptr + i + c,
1165 level - 1, cc, cr, apply_to, rs);
1168 goto try_optimize;
1171 /* Handler for foreach_tile.
1173 * "width" and "height" refer to tile size which may extend beyond
1174 * the range supplied to foreach_tile and even beyond the sheet.
1176 typedef void (*ForeachTileFunc) (GnmStyle *style,
1177 int corner_col, int corner_row,
1178 int width, int height,
1179 GnmRange const *apply_to, gpointer user);
1180 static void
1181 foreach_tile_r (CellTile *tile, int level,
1182 int corner_col, int corner_row,
1183 GnmRange const *apply_to,
1184 ForeachTileFunc handler,
1185 gpointer user)
1187 int const width = tile_widths[level+1];
1188 int const height = tile_heights[level+1];
1189 int const w = tile_widths[level];
1190 int const h = tile_heights[level];
1191 int c, r, i, last;
1193 g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
1194 g_return_if_fail (tile != NULL);
1196 switch (tile->type) {
1197 case TILE_SIMPLE:
1198 handler (tile->style_simple.style[0],
1199 corner_col, corner_row, width, height,
1200 apply_to, user);
1201 break;
1203 case TILE_COL:
1204 if (apply_to != NULL) {
1205 c = (apply_to->start.col - corner_col) / w;
1206 if (c < 0)
1207 c = 0;
1208 last = (apply_to->end.col - corner_col) / w + 1;
1209 if (last > TILE_SIZE_COL)
1210 last = TILE_SIZE_COL;
1211 } else {
1212 c = 0;
1213 last = TILE_SIZE_COL;
1215 for (; c < last ; ++c)
1216 handler (tile->style_col.style[c],
1217 corner_col + c*w, corner_row, w, height,
1218 apply_to, user);
1219 break;
1221 case TILE_ROW:
1222 if (apply_to != NULL) {
1223 r = (apply_to->start.row - corner_row) / h;
1224 if (r < 0)
1225 r = 0;
1226 last = (apply_to->end.row - corner_row) / h + 1;
1227 if (last > TILE_SIZE_ROW)
1228 last = TILE_SIZE_ROW;
1229 } else {
1230 r = 0;
1231 last = TILE_SIZE_ROW;
1233 for (; r < last ; ++r)
1234 handler (tile->style_row.style[r],
1235 corner_col, corner_row + r*h, width, h,
1236 apply_to, user);
1237 break;
1239 case TILE_MATRIX:
1240 case TILE_PTR_MATRIX:
1241 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r, i += TILE_SIZE_COL) {
1242 int const cr = corner_row + h*r;
1243 if (apply_to) {
1244 if (cr > apply_to->end.row)
1245 break;
1246 if ((cr + h) <= apply_to->start.row)
1247 continue;
1250 for (c = 0 ; c < TILE_SIZE_COL ; ++c) {
1251 int const cc = corner_col + w*c;
1252 if (apply_to) {
1253 if (cc > apply_to->end.col)
1254 break;
1255 if ((cc + w) <= apply_to->start.col)
1256 continue;
1259 if (tile->type == TILE_MATRIX) {
1260 handler (tile->style_matrix.style[r*TILE_SIZE_COL+c],
1261 corner_col + c * w,
1262 corner_row + r * h,
1263 w, h, apply_to, user);
1264 } else {
1265 foreach_tile_r (
1266 tile->ptr_matrix.ptr[c + r*TILE_SIZE_COL],
1267 level-1, cc, cr, apply_to, handler, user);
1271 break;
1273 default:
1274 g_warning ("Adaptive Quad Tree corruption !");
1278 static void
1279 foreach_tile (Sheet const *sheet, GnmRange const *apply_to,
1280 ForeachTileFunc handler, gpointer user)
1282 foreach_tile_r (sheet->style_data->styles,
1283 sheet->tile_top_level, 0, 0,
1284 apply_to, handler, user);
1288 * cell_tile_apply_pos: This is an simplified version of cell_tile_apply. It
1289 * does not need all the bells and whistles because it operates on single cells.
1291 static void
1292 cell_tile_apply_pos (CellTile **tile, int level,
1293 int col, int row,
1294 ReplacementStyle *rs)
1296 CellTile *tmp;
1297 CellTileType type;
1298 GnmRange rng;
1300 g_return_if_fail (col >= 0);
1301 g_return_if_fail (col < gnm_sheet_get_max_cols (rs->sheet));
1302 g_return_if_fail (row >= 0);
1303 g_return_if_fail (row < gnm_sheet_get_max_rows (rs->sheet));
1305 range_init (&rng, col, row, col, row);
1307 tail_recursion:
1308 g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
1309 g_return_if_fail (tile != NULL);
1310 g_return_if_fail (*tile != NULL);
1312 tmp = *tile;
1313 type = tmp->type;
1314 g_return_if_fail (TILE_SIMPLE <= type && type <= TILE_PTR_MATRIX);
1316 if (level > 0) {
1317 int const w = tile_widths[level];
1318 int const c = col / w;
1319 int const h = tile_heights[level];
1320 int const r = row / h;
1322 if (type != TILE_PTR_MATRIX) {
1323 /* applying the same style to part of a simple-tile is a nop */
1324 if (type == TILE_SIMPLE &&
1325 (*tile)->style_simple.style[0] == rs->new_style)
1326 return;
1328 tmp = cell_tile_ptr_matrix_new (tmp);
1329 cell_tile_dtor (*tile);
1330 *tile = tmp;
1332 tile = tmp->ptr_matrix.ptr + r * TILE_SIZE_COL + c;
1333 level--;
1334 col -= c*w;
1335 row -= r*h;
1336 goto tail_recursion;
1337 } else if (type != TILE_MATRIX)
1338 *tile = tmp = cell_tile_matrix_set (tmp);
1340 g_return_if_fail (tmp->type == TILE_MATRIX);
1341 rstyle_apply (tmp->style_matrix.style + row * TILE_SIZE_COL + col,
1343 &rng);
1347 * sheet_style_set_range:
1348 * @sheet:
1349 * @range:
1350 * @style: #GnmStyle
1352 * Change the complete style for a region.
1353 * This function absorbs a reference to the new @style.
1355 void
1356 sheet_style_set_range (Sheet *sheet, GnmRange const *range,
1357 GnmStyle *style)
1359 ReplacementStyle rs;
1360 GnmRange r;
1362 g_return_if_fail (IS_SHEET (sheet));
1363 g_return_if_fail (range != NULL);
1365 if (range->start.col > range->end.col ||
1366 range->start.row > range->end.row) {
1367 gnm_style_unref (style);
1368 return;
1371 r = *range;
1372 range_ensure_sanity (&r, sheet);
1374 rstyle_ctor_style (&rs, style, sheet);
1375 cell_tile_apply (&sheet->style_data->styles,
1376 sheet->tile_top_level, 0, 0,
1377 &r, &rs);
1378 rstyle_dtor (&rs);
1382 * sheet_style_apply_col:
1383 * @sheet:
1384 * @col:
1385 * @style: #GnmStyle
1387 * NOTE: This is a simple wrapper for now. When we support col/row styles it
1388 * will make life easier.
1390 * Apply a partial style to a full col.
1391 * The routine absorbs a reference to the partial style.
1393 void
1394 sheet_style_apply_col (Sheet *sheet, int col, GnmStyle *pstyle)
1396 GnmRange r;
1397 range_init_cols (&r, sheet, col, col);
1398 sheet_style_apply_range (sheet, &r, pstyle);
1402 * sheet_style_apply_row:
1403 * @sheet:
1404 * @row:
1405 * @style: #GnmStyle
1407 * NOTE: This is a simple wrapper for now. When we support col/row styles it
1408 * will make life easier.
1410 * Apply a partial style to a full col.
1411 * The routine absorbs a reference to the partial style.
1413 void
1414 sheet_style_apply_row (Sheet *sheet, int row, GnmStyle *pstyle)
1416 GnmRange r;
1417 range_init_rows (&r, sheet, row, row);
1418 sheet_style_apply_range (sheet, &r, pstyle);
1422 * sheet_style_apply_pos:
1423 * @sheet:
1424 * @col:
1425 * @row:
1426 * @style: #GnmStyle
1428 * Apply a partial style to a single cell
1429 * This function absorbs a reference to the new @style.
1431 void
1432 sheet_style_apply_pos (Sheet *sheet, int col, int row,
1433 GnmStyle *pstyle)
1435 ReplacementStyle rs;
1437 g_return_if_fail (IS_SHEET (sheet));
1439 rstyle_ctor_pstyle (&rs, pstyle, sheet);
1440 cell_tile_apply_pos (&sheet->style_data->styles,
1441 sheet->tile_top_level, col, row,
1442 &rs);
1443 rstyle_dtor (&rs);
1446 * sheet_style_set_pos:
1447 * @sheet:
1448 * @col:
1449 * @row:
1450 * @style:
1452 * Change the complete style for a single cell.
1453 * This function absorbs a reference to the new @style.
1455 void
1456 sheet_style_set_pos (Sheet *sheet, int col, int row,
1457 GnmStyle *style)
1459 ReplacementStyle rs;
1461 g_return_if_fail (IS_SHEET (sheet));
1463 rstyle_ctor_style (&rs, style, sheet);
1464 cell_tile_apply_pos (&sheet->style_data->styles,
1465 sheet->tile_top_level, col, row,
1466 &rs);
1467 rstyle_dtor (&rs);
1471 * sheet_style_default:
1472 * @sheet:
1474 * Returns a reference to default style for a sheet.
1476 GnmStyle *
1477 sheet_style_default (Sheet const *sheet)
1479 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1480 g_return_val_if_fail (sheet->style_data != NULL, NULL);
1482 gnm_style_ref (sheet->style_data->default_style);
1483 return sheet->style_data->default_style;
1487 * sheet_style_get:
1488 * @sheet: #Sheet
1489 * @col: column number
1490 * @row: row number
1492 * Returns: (transfer none): find the fully qualified style applicable to
1493 * the specified cell position
1495 GnmStyle const *
1496 sheet_style_get (Sheet const *sheet, int col, int row)
1498 int level = sheet->tile_top_level;
1499 CellTile *tile = sheet->style_data->styles;
1501 while (1) {
1502 int width = tile_widths[level];
1503 int height = tile_heights[level];
1504 int c = col / width;
1505 int r = row / height;
1507 g_return_val_if_fail (tile != NULL, NULL);
1508 g_return_val_if_fail (0 <= c && c < TILE_SIZE_COL, NULL);
1509 g_return_val_if_fail (0 <= r && r < TILE_SIZE_ROW, NULL);
1511 switch (tile->type) {
1512 case TILE_SIMPLE:
1513 return tile->style_simple.style[0];
1514 case TILE_COL:
1515 return tile->style_col.style[c];
1516 case TILE_ROW:
1517 return tile->style_row.style[r];
1518 case TILE_MATRIX:
1519 return tile->style_matrix.style[r * TILE_SIZE_COL + c];
1521 case TILE_PTR_MATRIX:
1522 g_return_val_if_fail (level > 0, NULL);
1524 level--;
1525 tile = tile->ptr_matrix.ptr[r * TILE_SIZE_COL + c];
1526 col -= c * width;
1527 row -= r * height;
1528 continue;
1530 default:
1531 g_warning ("Adaptive Quad Tree corruption !");
1532 return NULL;
1537 #define border_null(b) ((b) == none || (b) == NULL)
1539 static void
1540 style_row (GnmStyle const *style, int start_col, int end_col,
1541 GnmStyleRow *sr, gboolean accept_conditions)
1543 GnmBorder const *top, *bottom, *none = gnm_style_border_none ();
1544 GnmBorder const *left, *right, *v;
1545 int const end = MIN (end_col, sr->end_col);
1546 int i = MAX (start_col, sr->start_col);
1547 GnmStyleConditions *conds;
1549 conds = accept_conditions
1550 ? gnm_style_get_conditions (style)
1551 : NULL;
1552 if (conds) {
1553 GnmEvalPos ep;
1554 int res;
1556 for (eval_pos_init (&ep, (Sheet *)sr->sheet, i, sr->row); ep.eval.col <= end ; ep.eval.col++) {
1557 res = gnm_style_conditions_eval (conds, &ep);
1558 style_row (res >= 0
1559 ? gnm_style_get_cond_style (style, res)
1560 : style,
1561 ep.eval.col, ep.eval.col, sr, FALSE);
1563 return;
1566 top = gnm_style_get_border (style, MSTYLE_BORDER_TOP);
1567 bottom = gnm_style_get_border (style, MSTYLE_BORDER_BOTTOM);
1568 left = gnm_style_get_border (style, MSTYLE_BORDER_LEFT);
1569 right = gnm_style_get_border (style, MSTYLE_BORDER_RIGHT);
1571 /* Cancel grids if there is a background */
1572 if (sr->hide_grid || gnm_style_get_pattern (style) > 0) {
1573 if (top == none)
1574 top = NULL;
1575 if (bottom == none)
1576 bottom = NULL;
1577 if (left == none)
1578 left = NULL;
1579 if (right == none)
1580 right = NULL;
1583 if (left != none && border_null (sr->vertical[i]))
1584 sr->vertical[i] = left;
1585 v = border_null (right) ? left : right;
1587 while (i <= end) {
1588 sr->styles[i] = style;
1589 if (top != none && border_null (sr->top[i]))
1590 sr->top[i] = top;
1591 sr->bottom[i] = bottom;
1592 sr->vertical[++i] = v;
1594 if (border_null (right))
1595 sr->vertical[i] = right;
1598 static void
1599 get_style_row (CellTile const *tile, int level,
1600 int corner_col, int corner_row,
1601 GnmStyleRow *sr)
1603 int const width = tile_widths[level+1];
1604 int const w = tile_widths[level];
1605 int const h = tile_heights[level];
1606 int r = 0;
1607 CellTileType t;
1609 g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
1610 g_return_if_fail (tile != NULL);
1612 t = tile->type;
1614 if (t != TILE_SIMPLE && t != TILE_COL) {
1615 r = (sr->row > corner_row) ? (sr->row - corner_row)/ h : 0;
1616 g_return_if_fail (r < TILE_SIZE_ROW);
1619 if (t == TILE_ROW || t == TILE_SIMPLE) {
1620 style_row (tile->style_any.style[r],
1621 corner_col, corner_col + width - 1, sr, TRUE);
1622 } else {
1623 /* find the start and end */
1624 int c;
1625 int last_c = (sr->end_col - corner_col) / w;
1626 if (last_c >= TILE_SIZE_COL)
1627 last_c = TILE_SIZE_COL-1;
1628 if (sr->start_col > corner_col) {
1629 c = (sr->start_col - corner_col) / w;
1630 corner_col += c * w;
1631 } else
1632 c = 0;
1634 corner_row += h*r;
1636 if (t != TILE_PTR_MATRIX) {
1637 GnmStyle * const *styles = tile->style_any.style + r*TILE_SIZE_COL;
1639 for ( ; c <= last_c ; c++, corner_col += w)
1640 style_row (styles[c],
1641 corner_col, corner_col + w - 1, sr, TRUE);
1642 } else {
1643 CellTile * const *tiles = tile->ptr_matrix.ptr + r*TILE_SIZE_COL;
1645 g_return_if_fail (level > 0);
1647 for ( level-- ; c <= last_c ; c++, corner_col += w)
1648 get_style_row (tiles[c], level,
1649 corner_col, corner_row, sr);
1655 * sheet_style_get_row:
1656 * @sheet: #Sheet
1657 * @sr: #GnmStyleRow
1659 * A utility routine which efficiently retrieves a range of styles within a row.
1660 * It also merges adjacent borders as necessary.
1662 void
1663 sheet_style_get_row (Sheet const *sheet, GnmStyleRow *sr)
1666 g_return_if_fail (IS_SHEET (sheet));
1667 g_return_if_fail (sr != NULL);
1668 g_return_if_fail (sr->styles != NULL);
1669 g_return_if_fail (sr->vertical != NULL);
1670 g_return_if_fail (sr->top != NULL);
1671 g_return_if_fail (sr->bottom != NULL);
1673 sr->sheet = sheet;
1674 sr->vertical[sr->start_col] = gnm_style_border_none ();
1675 get_style_row (sheet->style_data->styles, sheet->tile_top_level, 0, 0, sr);
1678 static void
1679 cb_get_row (GnmStyle *style,
1680 int corner_col, G_GNUC_UNUSED int corner_row,
1681 int width, G_GNUC_UNUSED int height,
1682 GnmRange const *apply_to, gpointer user_)
1684 GnmStyle **res = user_;
1685 int i;
1687 /* The given dimensions refer to the tile, not the area. */
1688 width = MIN (width, apply_to->end.col - corner_col + 1);
1690 for (i = 0; i < width; i++)
1691 res[corner_col + i] = style;
1694 GnmStyle **
1695 sheet_style_get_row2 (Sheet const *sheet, int row)
1697 GnmRange r;
1698 GnmStyle **res = g_new (GnmStyle *, gnm_sheet_get_max_cols (sheet));
1700 range_init_rows (&r, sheet, row, row);
1702 foreach_tile (sheet, &r, cb_get_row, res);
1704 return res;
1709 * style_row_init:
1711 * A small utility routine to initialize the grid drawing GnmStyleRow data
1712 * structure.
1714 void
1715 style_row_init (GnmBorder const * * *prev_vert,
1716 GnmStyleRow *sr, GnmStyleRow *next_sr,
1717 int start_col, int end_col, gpointer mem, gboolean hide_grid)
1719 int n, col;
1720 GnmBorder const *none = hide_grid ? NULL : gnm_style_border_none ();
1722 /* alias the arrays for easy access so that array[col] is valid
1723 * for all elements start_col-1 .. end_col+1 inclusive.
1724 * Note that this means that in some cases array[-1] is legal.
1726 n = end_col - start_col + 3; /* 1 before, 1 after, 1 fencepost */
1727 sr->vertical = mem;
1728 sr->vertical -= start_col-1;
1729 sr->top = sr->vertical + n;
1730 sr->bottom = sr->top + n;
1731 next_sr->top = sr->bottom; /* yes they should share */
1732 next_sr->bottom = next_sr->top + n;
1733 next_sr->vertical = next_sr->bottom + n;
1734 *prev_vert = next_sr->vertical + n;
1735 sr->styles = ((GnmStyle const **) (*prev_vert + n));
1736 next_sr->styles = sr->styles + n;
1737 sr->start_col = next_sr->start_col = start_col;
1738 sr->end_col = next_sr->end_col = end_col;
1739 sr->hide_grid = next_sr->hide_grid = hide_grid;
1741 /* Init the areas that sheet_style_get_row will not */
1742 for (col = start_col-1 ; col <= end_col+1; ++col)
1743 (*prev_vert)[col] = sr->top[col] = none;
1744 sr->vertical [start_col-1] = sr->vertical [end_col+1] =
1745 next_sr->vertical[start_col-1] = next_sr->vertical[end_col+1] =
1746 next_sr->top [start_col-1] = next_sr->top [end_col+1] =
1747 next_sr->bottom [start_col-1] = next_sr->bottom [end_col+1] = none;
1751 * sheet_style_apply_range: (skip)
1752 * @sheet: #Sheet
1753 * @range: #GnmRange to apply over
1754 * @pstyle: (transfer full): A partial style to apply
1756 * Apply a partial style to a region.
1757 * The routine absorbs a reference to the partial style.
1759 void
1760 sheet_style_apply_range (Sheet *sheet, GnmRange const *range, GnmStyle *pstyle)
1762 ReplacementStyle rs;
1763 GnmRange r;
1765 g_return_if_fail (IS_SHEET (sheet));
1766 g_return_if_fail (range != NULL);
1768 if (range->start.col > range->end.col ||
1769 range->start.row > range->end.row) {
1770 gnm_style_unref (pstyle);
1771 return;
1774 r = *range;
1775 range_ensure_sanity (&r, sheet);
1777 rstyle_ctor_pstyle (&rs, pstyle, sheet);
1778 cell_tile_apply (&sheet->style_data->styles,
1779 sheet->tile_top_level, 0, 0,
1780 &r, &rs);
1781 rstyle_dtor (&rs);
1785 * sheet_style_apply_range2: (skip)
1786 * @sheet: #Sheet
1787 * @range: #GnmRange to apply over
1788 * @pstyle: A partial style to apply
1790 * Apply a partial style to a region.
1792 void
1793 sheet_style_apply_range2 (Sheet *sheet, GnmRange const *range, GnmStyle *pstyle)
1795 gnm_style_ref (pstyle);
1796 sheet_style_apply_range (sheet, range, pstyle);
1800 static void
1801 apply_border (Sheet *sheet, GnmRange const *r,
1802 GnmStyleBorderLocation side,
1803 GnmBorder *border)
1805 GnmStyle *pstyle = gnm_style_new ();
1806 pstyle_set_border (pstyle, border, side);
1807 sheet_style_apply_range (sheet, r, pstyle);
1811 * sheet_style_apply_border:
1812 * @sheet:
1813 * @range:
1814 * @borders:
1816 * When a user applies a border to a region we attempt to remove the border
1817 * from the opposing side to avoid overlapping border specifications.
1818 * eg
1819 * if we apply a top border to a range, we would clear the bottom border
1820 * of the range offset upwards.
1822 void
1823 sheet_style_apply_border (Sheet *sheet,
1824 GnmRange const *range,
1825 GnmBorder **borders)
1827 GnmStyle *pstyle = NULL;
1829 if (borders == NULL)
1830 return;
1832 if (borders[GNM_STYLE_BORDER_TOP]) {
1833 /* 1.1 top inner */
1834 GnmRange r = *range;
1835 r.end.row = r.start.row;
1836 apply_border (sheet, &r, GNM_STYLE_BORDER_TOP,
1837 borders[GNM_STYLE_BORDER_TOP]);
1839 /* 1.2 top outer */
1840 r.start.row--;
1841 if (r.start.row >= 0) {
1842 r.end.row = r.start.row;
1843 apply_border (sheet, &r, GNM_STYLE_BORDER_BOTTOM,
1844 gnm_style_border_none ());
1848 if (borders[GNM_STYLE_BORDER_BOTTOM]) {
1849 /* 2.1 bottom inner */
1850 GnmRange r = *range;
1851 r.start.row = r.end.row;
1852 apply_border (sheet, &r, GNM_STYLE_BORDER_BOTTOM,
1853 borders[GNM_STYLE_BORDER_BOTTOM]);
1855 /* 2.2 bottom outer */
1856 r.end.row++;
1857 if (r.end.row < gnm_sheet_get_last_row (sheet)) {
1858 r.start.row = r.end.row;
1859 apply_border (sheet, &r, GNM_STYLE_BORDER_TOP,
1860 gnm_style_border_none ());
1864 if (borders[GNM_STYLE_BORDER_LEFT]) {
1865 /* 3.1 left inner */
1866 GnmRange r = *range;
1867 r.end.col = r.start.col;
1868 apply_border (sheet, &r, GNM_STYLE_BORDER_LEFT,
1869 borders[GNM_STYLE_BORDER_LEFT]);
1871 /* 3.2 left outer */
1872 r.start.col--;
1873 if (r.start.col >= 0) {
1874 r.end.col = r.start.col;
1875 apply_border (sheet, &r, GNM_STYLE_BORDER_RIGHT,
1876 gnm_style_border_none ());
1880 if (borders[GNM_STYLE_BORDER_RIGHT]) {
1881 /* 4.1 right inner */
1882 GnmRange r = *range;
1883 r.start.col = r.end.col;
1884 apply_border (sheet, &r, GNM_STYLE_BORDER_RIGHT,
1885 borders[GNM_STYLE_BORDER_RIGHT]);
1887 /* 4.2 right outer */
1888 r.end.col++;
1889 if (r.end.col < gnm_sheet_get_last_col (sheet)) {
1890 r.start.col = r.end.col;
1891 apply_border (sheet, &r, GNM_STYLE_BORDER_LEFT,
1892 gnm_style_border_none ());
1896 /* Interiors horizontal : prefer top */
1897 if (borders[GNM_STYLE_BORDER_HORIZ] != NULL) {
1898 /* 5.1 horizontal interior top */
1899 if (range->start.row != range->end.row) {
1900 GnmRange r = *range;
1901 ++r.start.row;
1902 apply_border (sheet, &r, GNM_STYLE_BORDER_TOP,
1903 borders[GNM_STYLE_BORDER_HORIZ]);
1905 /* 5.2 interior bottom */
1906 if (range->start.row != range->end.row) {
1907 GnmRange r = *range;
1908 --r.end.row;
1909 apply_border (sheet, &r, GNM_STYLE_BORDER_BOTTOM,
1910 gnm_style_border_none ());
1914 /* Interiors vertical: prefer left */
1915 if (borders[GNM_STYLE_BORDER_VERT] != NULL) {
1916 /* 6.1 vertical interior left */
1917 if (range->start.col != range->end.col) {
1918 GnmRange r = *range;
1919 ++r.start.col;
1920 apply_border (sheet, &r, GNM_STYLE_BORDER_LEFT,
1921 borders[GNM_STYLE_BORDER_VERT]);
1924 /* 6.2 The vertical interior right */
1925 if (range->start.col != range->end.col) {
1926 GnmRange r = *range;
1927 --r.end.col;
1928 apply_border (sheet, &r, GNM_STYLE_BORDER_RIGHT,
1929 gnm_style_border_none ());
1933 /* 7. Diagonals (apply both in one pass) */
1934 if (borders[GNM_STYLE_BORDER_DIAG] != NULL) {
1935 pstyle = gnm_style_new ();
1936 pstyle_set_border (pstyle, borders[GNM_STYLE_BORDER_DIAG],
1937 GNM_STYLE_BORDER_DIAG);
1939 if (borders[GNM_STYLE_BORDER_REV_DIAG]) {
1940 if (pstyle == NULL)
1941 pstyle = gnm_style_new ();
1942 pstyle_set_border (pstyle, borders[GNM_STYLE_BORDER_REV_DIAG],
1943 GNM_STYLE_BORDER_REV_DIAG);
1945 if (pstyle != NULL)
1946 sheet_style_apply_range (sheet, range, pstyle);
1949 /****************************************************************************/
1951 typedef struct {
1952 GnmStyle *accum;
1953 unsigned int conflicts;
1954 } FindConflicts;
1956 static void
1957 cb_find_conflicts (GnmStyle *style,
1958 G_GNUC_UNUSED int corner_col, G_GNUC_UNUSED int corner_row,
1959 G_GNUC_UNUSED int width, G_GNUC_UNUSED int height,
1960 G_GNUC_UNUSED GnmRange const *apply_to, FindConflicts *ptr)
1962 ptr->conflicts = gnm_style_find_conflicts (ptr->accum, style, ptr->conflicts);
1965 static void
1966 border_mask_internal (gboolean known[GNM_STYLE_BORDER_EDGE_MAX],
1967 GnmBorder *borders[GNM_STYLE_BORDER_EDGE_MAX],
1968 GnmBorder const *b, GnmStyleBorderLocation l)
1970 if (!known[l]) {
1971 known[l] = TRUE;
1972 gnm_style_border_unref (borders[l]);
1973 borders[l] = (GnmBorder *)b;
1974 gnm_style_border_ref (borders[l]);
1975 } else if (borders[l] != b && borders[l] != NULL) {
1976 gnm_style_border_unref (borders[l]);
1977 borders[l] = NULL;
1981 static void
1982 border_mask (gboolean known[GNM_STYLE_BORDER_EDGE_MAX],
1983 GnmBorder *borders[GNM_STYLE_BORDER_EDGE_MAX],
1984 GnmBorder const *b, GnmStyleBorderLocation l)
1986 if (b == NULL)
1987 b = gnm_style_border_none ();
1988 border_mask_internal (known, borders, b, l);
1991 static void
1992 border_mask_vec (gboolean known[GNM_STYLE_BORDER_EDGE_MAX],
1993 GnmBorder *borders[GNM_STYLE_BORDER_EDGE_MAX],
1994 GnmBorder const * const *vec, int first, int last,
1995 GnmStyleBorderLocation l)
1997 GnmBorder const *b = vec[first];
1999 if (b == NULL)
2000 b = gnm_style_border_none ();
2001 while (first++ < last) {
2002 GnmBorder const *tmp = vec[first];
2003 if (tmp == NULL)
2004 tmp = gnm_style_border_none ();
2005 if (b != tmp) {
2006 b = NULL;
2007 break;
2011 border_mask_internal (known, borders, b, l);
2015 * sheet_style_find_conflicts:
2016 * @sheet: #Sheet to query
2017 * @r: #GnmRange to query
2018 * @style: (inout):
2019 * @borders: (out) (array fixed-size=8):
2021 * Returns: bitmask of conflicts
2023 unsigned int
2024 sheet_style_find_conflicts (Sheet const *sheet, GnmRange const *r,
2025 GnmStyle **style,
2026 GnmBorder *borders[GNM_STYLE_BORDER_EDGE_MAX])
2028 int n, col, row, start_col, end_col;
2029 GnmStyleRow sr;
2030 gpointer *sr_array_data;
2031 GnmStyleBorderLocation i;
2032 gboolean known[GNM_STYLE_BORDER_EDGE_MAX];
2033 GnmBorder const *none = gnm_style_border_none ();
2034 FindConflicts user;
2036 g_return_val_if_fail (IS_SHEET (sheet), 0);
2037 g_return_val_if_fail (r != NULL, 0);
2038 g_return_val_if_fail (style != NULL, 0);
2039 g_return_val_if_fail (borders != NULL, 0);
2041 /* init style set with a copy of the top left corner of the 1st range */
2042 if (*style == NULL) {
2043 GnmStyle const *tmp = sheet_style_get (sheet, r->start.col, r->start.row);
2044 *style = gnm_style_dup (tmp);
2045 for (i = GNM_STYLE_BORDER_TOP ; i < GNM_STYLE_BORDER_EDGE_MAX ; i++) {
2046 known[i] = FALSE;
2047 borders[i] = gnm_style_border_ref ((GnmBorder *)none);
2049 } else {
2050 for (i = GNM_STYLE_BORDER_TOP ; i < GNM_STYLE_BORDER_EDGE_MAX ; i++) {
2051 known[i] = TRUE;
2052 borders[i] = NULL;
2056 user.accum = *style;
2057 user.conflicts = 0; /* no conflicts yet */
2058 foreach_tile (sheet, r, (ForeachTileFunc)cb_find_conflicts, &user);
2060 /* copy over the diagonals */
2061 for (i = GNM_STYLE_BORDER_REV_DIAG ; i <= GNM_STYLE_BORDER_DIAG ; i++) {
2062 GnmStyleElement se = GNM_STYLE_BORDER_LOCATION_TO_STYLE_ELEMENT (i);
2063 gnm_style_border_unref (borders[i]);
2064 if (user.conflicts & (1 << se))
2065 borders[i] = NULL;
2066 else
2067 borders[i] = gnm_style_border_ref (
2068 gnm_style_get_border (*style, se));
2071 start_col = r->start.col;
2072 if (r->start.col > 0)
2073 start_col--;
2074 end_col = r->end.col;
2075 if (r->end.col < gnm_sheet_get_max_cols (sheet))
2076 end_col++;
2078 /* allocate then alias the arrays for easy access */
2079 n = end_col - start_col + 2;
2080 g_assert (sizeof (GnmBorder *) == sizeof (gpointer));
2081 g_assert (sizeof (GnmStyle *) == sizeof (gpointer));
2082 sr_array_data = g_new (gpointer, n * 4);
2083 sr.vertical = (GnmBorder const **)(sr_array_data - start_col);
2084 sr.top = (GnmBorder const **)(sr_array_data + n - start_col);
2085 sr.bottom = (GnmBorder const **)(sr_array_data + 2 * n - start_col);
2086 sr.styles = (GnmStyle const **) (sr_array_data + 3 * n - start_col);
2087 sr.start_col = start_col;
2088 sr.end_col = end_col;
2089 sr.hide_grid = sheet->hide_grid;
2091 /* pretend the previous bottom had no borders */
2092 for (col = start_col ; col <= end_col; ++col)
2093 sr.top[col] = none;
2095 /* merge the bottom of the previous row */
2096 if (r->start.row > 0) {
2097 GnmBorder const ** roller;
2098 sr.row = r->start.row - 1;
2099 sheet_style_get_row (sheet, &sr);
2100 roller = sr.top; sr.top = sr.bottom; sr.bottom = roller;
2104 * TODO: The border handling is tricky and currently VERY slow for
2105 * large ranges. We could easily optimize this. There is no need to
2106 * retrieve the style in every cell just to do a filter for uniformity
2107 * by row. One day we should do a special case version of
2108 * sheet_style_get_row probably style_get_uniform_col (this will be
2109 * faster)
2111 for (row = r->start.row ; row <= r->end.row ; row++) {
2112 GnmBorder const **roller;
2113 sr.row = row;
2114 sheet_style_get_row (sheet, &sr);
2116 border_mask (known, borders, sr.vertical[r->start.col],
2117 GNM_STYLE_BORDER_LEFT);
2118 border_mask (known, borders, sr.vertical[r->end.col+1],
2119 GNM_STYLE_BORDER_RIGHT);
2120 border_mask_vec (known, borders, sr.top,
2121 r->start.col, r->end.col, (row == r->start.row)
2122 ? GNM_STYLE_BORDER_TOP : GNM_STYLE_BORDER_HORIZ);
2123 if (r->start.col != r->end.col)
2124 border_mask_vec (known, borders, sr.vertical,
2125 r->start.col+1, r->end.col,
2126 GNM_STYLE_BORDER_VERT);
2128 roller = sr.top; sr.top = sr.bottom; sr.bottom = roller;
2131 /* merge the top of the next row */
2132 if (r->end.row < gnm_sheet_get_last_row (sheet)) {
2133 sr.row = r->end.row + 1;
2134 sheet_style_get_row (sheet, &sr);
2136 border_mask_vec (known, borders, sr.top, r->start.col, r->end.col,
2137 GNM_STYLE_BORDER_BOTTOM);
2139 g_free (sr_array_data);
2140 return user.conflicts;
2144 * sheet_style_relocate:
2145 * @rinfo:
2147 * Slide the styles from the origin region to the new position.
2149 void
2150 sheet_style_relocate (GnmExprRelocateInfo const *rinfo)
2152 GnmCellPos corner;
2153 GnmStyleList *styles;
2155 g_return_if_fail (rinfo != NULL);
2157 styles = sheet_style_get_range (rinfo->origin_sheet, &rinfo->origin);
2159 sheet_style_set_range (rinfo->origin_sheet, &rinfo->origin,
2160 sheet_style_default (rinfo->origin_sheet));
2161 corner.col = rinfo->origin.start.col + rinfo->col_offset;
2162 corner.row = rinfo->origin.start.row + rinfo->row_offset;
2163 sheet_style_set_list (rinfo->target_sheet, &corner, styles, NULL, NULL);
2164 style_list_free (styles);
2168 * sheet_style_insdel_colrow:
2169 * @rinfo:
2171 * Insert of delete style columns/rows.
2173 * For the insert case, we stretch the preceding column/row into there space
2174 * we open.
2176 void
2177 sheet_style_insdel_colrow (GnmExprRelocateInfo const *rinfo)
2179 GnmStyleList *styles = NULL;
2180 Sheet *sheet;
2181 GnmCellPos corner;
2182 gboolean is_insert;
2184 g_return_if_fail (rinfo != NULL);
2185 g_return_if_fail (rinfo->origin_sheet == rinfo->target_sheet);
2186 g_return_if_fail ((rinfo->col_offset == 0) != (rinfo->row_offset == 0));
2188 is_insert = (rinfo->col_offset + rinfo->row_offset > 0);
2189 sheet = rinfo->origin_sheet;
2191 if (is_insert) {
2192 /* 1) copy col/row to the top/left of the region, and extend it */
2193 corner = rinfo->origin.start;
2194 if (rinfo->col_offset) {
2195 int col = MAX (corner.col - 1, 0);
2196 GnmStyleList *ptr;
2197 GnmRange r;
2199 corner.row = 0;
2200 range_init_cols (&r, sheet, col, col);
2201 styles = sheet_style_get_range (sheet, &r);
2202 for (ptr = styles ; ptr != NULL ; ptr = ptr->next) {
2203 GnmStyleRegion *sr = ptr->data;
2204 sr->range.end.col = rinfo->col_offset - 1;
2206 } else {
2207 int row = MAX (corner.row - 1, 0);
2208 GnmStyleList *ptr;
2209 GnmRange r;
2211 corner.col = 0;
2212 range_init_rows (&r, sheet, row, row);
2213 styles = sheet_style_get_range (sheet, &r);
2214 for (ptr = styles ; ptr != NULL ; ptr = ptr->next) {
2215 GnmStyleRegion *sr = ptr->data;
2216 sr->range.end.row = rinfo->row_offset - 1;
2221 sheet_style_relocate (rinfo);
2223 if (styles) {
2224 sheet_style_set_list (sheet, &corner, styles, NULL, NULL);
2225 style_list_free (styles);
2229 static void
2230 cb_style_extent (GnmStyle *style,
2231 int corner_col, int corner_row, int width, int height,
2232 GnmRange const *apply_to, gpointer user)
2234 GnmRange *res = user;
2235 if (gnm_style_visible_in_blank (style)) {
2236 int tmp;
2238 /* The given dimensions refer to the tile, not the area. */
2239 width = MIN (width, apply_to->end.col - corner_col + 1);
2240 height = MIN (height, apply_to->end.row - corner_row + 1);
2242 tmp = corner_col+width-1;
2243 if (res->end.col < tmp)
2244 res->end.col = tmp;
2245 if (res->start.col > corner_col)
2246 res->start.col = corner_col;
2248 tmp = corner_row+height-1;
2249 if (res->end.row < tmp)
2250 res->end.row = tmp;
2251 if (res->start.row > corner_row)
2252 res->start.row = corner_row;
2257 * sheet_style_get_extent:
2258 * @sheet: sheet to measure
2259 * @r: starting range and resulting range
2261 * A simple implementation that finds the smallest range containing all visible styles
2262 * and containing @res.
2264 void
2265 sheet_style_get_extent (Sheet const *sheet, GnmRange *res)
2267 GnmRange r;
2269 range_init_full_sheet (&r, sheet);
2270 foreach_tile (sheet, &r, cb_style_extent, res);
2273 struct cb_nondefault_extent {
2274 GnmRange *res;
2275 GnmStyle **col_defaults;
2278 static void
2279 cb_nondefault_extent (GnmStyle *style,
2280 int corner_col, int corner_row, int width, int height,
2281 GnmRange const *apply_to, gpointer user_)
2283 struct cb_nondefault_extent *user = user_;
2284 GnmRange *res = user->res;
2285 int i;
2287 for (i = 0; i < width; i++) {
2288 int col = corner_col + i;
2289 if (col >= apply_to->start.col &&
2290 col <= apply_to->end.col &&
2291 style != user->col_defaults[col]) {
2292 int max_row = MIN (corner_row + height - 1,
2293 apply_to->end.row);
2294 int min_row = MAX (corner_row, apply_to->start.row);
2296 res->start.col = MIN (col, res->start.col);
2297 res->start.row = MIN (min_row, res->start.row);
2299 res->end.col = MAX (col, res->end.col);
2300 res->end.row = MAX (max_row, res->end.row);
2305 void
2306 sheet_style_get_nondefault_extent (Sheet const *sheet, GnmRange *extent,
2307 const GnmRange *src, GnmStyle **col_defaults)
2309 struct cb_nondefault_extent user;
2310 user.res = extent;
2311 user.col_defaults = col_defaults;
2312 foreach_tile (sheet, src, cb_nondefault_extent, &user);
2315 struct cb_is_default {
2316 gboolean res;
2317 GnmStyle **col_defaults;
2320 static void
2321 cb_is_default (GnmStyle *style,
2322 int corner_col, G_GNUC_UNUSED int corner_row,
2323 int width, G_GNUC_UNUSED int height,
2324 GnmRange const *apply_to, gpointer user_)
2326 struct cb_is_default *user = user_;
2327 int i;
2329 /* The given "width" refers to the tile, not the area. */
2330 width = MIN (width, apply_to->end.col - corner_col + 1);
2332 for (i = 0; user->res && i < width; i++) {
2333 if (style != user->col_defaults[corner_col + i])
2334 user->res = FALSE;
2338 gboolean
2339 sheet_style_is_default (Sheet const *sheet, const GnmRange *r, GnmStyle **col_defaults)
2341 struct cb_is_default user;
2343 user.res = TRUE;
2344 user.col_defaults = col_defaults;
2346 foreach_tile (sheet, r, cb_is_default, &user);
2348 return user.res;
2351 struct cb_get_nondefault {
2352 guint8 *res;
2353 GnmStyle **col_defaults;
2356 static void
2357 cb_get_nondefault (GnmStyle *style,
2358 int corner_col, G_GNUC_UNUSED int corner_row,
2359 int width, G_GNUC_UNUSED int height,
2360 GnmRange const *apply_to, gpointer user_)
2362 struct cb_get_nondefault *user = user_;
2363 int i;
2365 /* The given dimensions refer to the tile, not the area. */
2366 width = MIN (width, apply_to->end.col - corner_col + 1);
2367 height = MIN (height, apply_to->end.row - corner_row + 1);
2369 for (i = 0; i < width; i++) {
2370 if (style != user->col_defaults[corner_col + i]) {
2371 int j;
2372 for (j = 0; j < height; j++)
2373 user->res[corner_row + j] = 1;
2374 break;
2379 guint8 *
2380 sheet_style_get_nondefault_rows (Sheet const *sheet, GnmStyle **col_defaults)
2382 struct cb_get_nondefault user;
2383 GnmRange r;
2385 range_init_full_sheet (&r, sheet);
2387 user.res = g_new0 (guint8, gnm_sheet_get_max_rows (sheet));
2388 user.col_defaults = col_defaults;
2390 foreach_tile (sheet, &r, cb_get_nondefault, &user);
2392 return user.res;
2395 struct cb_most_common {
2396 GHashTable *h;
2397 int l;
2398 gboolean is_col;
2401 static void
2402 cb_most_common (GnmStyle *style,
2403 int corner_col, int corner_row, int width, int height,
2404 GnmRange const *apply_to, gpointer user)
2406 struct cb_most_common *cmc = user;
2407 int *counts = g_hash_table_lookup (cmc->h, style);
2408 int i;
2409 if (!counts) {
2410 counts = g_new0 (int, cmc->l);
2411 g_hash_table_insert (cmc->h, style, counts);
2414 /* The given dimensions refer to the tile, not the area. */
2415 width = MIN (width, apply_to->end.col - corner_col + 1);
2416 height = MIN (height, apply_to->end.row - corner_row + 1);
2418 if (cmc->is_col)
2419 for (i = 0; i < width; i++)
2420 counts[corner_col + i] += height;
2421 else
2422 for (i = 0; i < height; i++)
2423 counts[corner_row + i] += width;
2427 * sheet_style_most_common:
2428 * @sheet: sheet to inspect
2429 * @is_col: if %TRUE, look for common styles in columns; if FALSE, look in rows.
2431 * Returns: an array of styles describing the most common styles, one per column
2432 * or row.
2434 GnmStyle **
2435 sheet_style_most_common (Sheet const *sheet, gboolean is_col)
2437 GnmRange r;
2438 struct cb_most_common cmc;
2439 int *max;
2440 GnmStyle **res;
2441 GHashTableIter iter;
2442 gpointer key, value;
2444 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2446 range_init_full_sheet (&r, sheet);
2447 cmc.h = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
2448 cmc.l = colrow_max (is_col, sheet);
2449 cmc.is_col = is_col;
2450 foreach_tile (sheet, &r, cb_most_common, &cmc);
2452 max = g_new0 (int, cmc.l);
2453 res = g_new0 (GnmStyle *, cmc.l);
2454 g_hash_table_iter_init (&iter, cmc.h);
2455 while (g_hash_table_iter_next (&iter, &key, &value)) {
2456 int *counts = value;
2457 GnmStyle *style = key;
2458 int j;
2459 for (j = 0; j < cmc.l; j++) {
2460 /* FIXME: we really ought to break ties in a
2461 consistent way that does not depend on hash
2462 order. */
2463 if (counts[j] > max[j]) {
2464 max[j] = counts[j];
2465 res[j] = style;
2469 g_hash_table_destroy (cmc.h);
2470 g_free (max);
2472 return res;
2475 /****************************************************************************/
2478 * gnm_style_region_new:
2479 * @range: #GnmRange
2480 * @style: #GnmStyle
2482 * Returns: (transfer full): the newly allocated #GnmStyleRegion.
2484 GnmStyleRegion *
2485 gnm_style_region_new (GnmRange const *range, GnmStyle *style)
2487 GnmStyleRegion *sr;
2489 sr = g_new (GnmStyleRegion, 1);
2490 sr->range = *range;
2491 sr->style = style;
2492 gnm_style_ref (style);
2494 return sr;
2497 void
2498 gnm_style_region_free (GnmStyleRegion *sr)
2500 g_return_if_fail (sr != NULL);
2502 gnm_style_unref (sr->style);
2503 sr->style = NULL;
2504 g_free (sr);
2507 static GnmStyleRegion *
2508 gnm_style_region_copy (GnmStyleRegion *sr)
2510 GnmStyleRegion *res = g_new (GnmStyleRegion, 1);
2511 *res = *sr;
2512 gnm_style_ref (sr->style);
2513 return res;
2516 GType
2517 gnm_style_region_get_type (void)
2519 static GType t = 0;
2521 if (t == 0) {
2522 t = g_boxed_type_register_static ("GnmStyleRegion",
2523 (GBoxedCopyFunc)gnm_style_region_copy,
2524 (GBoxedFreeFunc)gnm_style_region_free);
2526 return t;
2530 static gboolean
2531 debug_style_list (void)
2533 static int debug = -1;
2534 if (debug < 0)
2535 debug = gnm_debug_flag ("style-list");
2536 return debug;
2539 typedef struct {
2540 GPtrArray *accum;
2541 GHashTable *by_tl, *by_br;
2542 guint64 area;
2543 gboolean (*style_equal) (GnmStyle const *a, GnmStyle const *b);
2544 gboolean (*style_filter) (GnmStyle const *style);
2545 GnmSheetSize const *sheet_size;
2546 } ISL;
2548 static gboolean
2549 merge_ranges (GnmRange *a, GnmRange const *b)
2551 if (a->start.row == b->start.row &&
2552 a->end.row == b->end.row &&
2553 a->end.col + 1 == b->start.col) {
2554 /* "a" is just left of "b". */
2555 a->end.col = b->end.col;
2556 return TRUE;
2559 if (a->start.col == b->start.col &&
2560 a->end.col == b->end.col &&
2561 a->end.row + 1 == b->start.row) {
2562 /* "a" is just on top of "b". */
2563 a->end.row = b->end.row;
2564 return TRUE;
2567 /* Punt. */
2568 return FALSE;
2571 static gboolean
2572 try_merge_pair (ISL *data, unsigned ui1, unsigned ui2)
2574 GnmStyleRegion *a;
2575 GnmStyleRegion *b;
2577 if (ui1 >= data->accum->len || ui2 >= data->accum->len)
2578 return FALSE;
2580 a = g_ptr_array_index (data->accum, ui1);
2581 b = g_ptr_array_index (data->accum, ui2);
2583 if (!data->style_equal (a->style, b->style))
2584 return FALSE;
2586 if (!merge_ranges (&a->range, &b->range))
2587 return FALSE;
2589 gnm_style_region_free (b);
2590 g_ptr_array_remove_index (data->accum, ui2);
2592 return TRUE;
2595 static void
2596 cb_style_list_add_node (GnmStyle *style,
2597 int corner_col, int corner_row, int width, int height,
2598 GnmRange const *apply_to, gpointer user_)
2600 ISL *data = user_;
2601 GnmSheetSize const *ss = data->sheet_size;
2602 GnmStyleRegion *sr;
2603 GnmRange range;
2605 /* Can this even happen? */
2606 if (corner_col >= ss->max_cols || corner_row >= ss->max_rows)
2607 return;
2609 if (data->style_filter && !data->style_filter (style))
2610 return;
2612 range.start.col = corner_col;
2613 range.start.row = corner_row;
2614 range.end.col = MIN (corner_col + width - 1, ss->max_cols - 1);
2615 range.end.row = MIN (corner_row + height - 1, ss->max_rows - 1);
2617 if (apply_to) {
2618 range.start.col -= apply_to->start.col;
2619 if (range.start.col < 0)
2620 range.start.col = 0;
2621 range.start.row -= apply_to->start.row;
2622 if (range.start.row < 0)
2623 range.start.row = 0;
2625 if (range.end.col > apply_to->end.col)
2626 range.end.col = apply_to->end.col;
2627 range.end.col -= apply_to->start.col;
2628 if (range.end.row > apply_to->end.row)
2629 range.end.row = apply_to->end.row;
2630 range.end.row -= apply_to->start.row;
2633 data->area += (guint64)range_width (&range) * range_height (&range);
2635 sr = gnm_style_region_new (&range, style);
2636 g_ptr_array_add (data->accum, sr);
2638 while (try_merge_pair (data, data->accum->len - 2, data->accum->len - 1))
2639 /* Nothing */;
2642 static void
2643 verify_hashes (ISL *data)
2645 GHashTable *by_tl = data->by_tl;
2646 GHashTable *by_br = data->by_br;
2647 unsigned ui;
2648 guint64 area = 0;
2650 g_return_if_fail (g_hash_table_size (by_tl) == data->accum->len);
2651 g_return_if_fail (g_hash_table_size (by_br) == data->accum->len);
2653 for (ui = 0; ui < data->accum->len; ui++) {
2654 GnmStyleRegion *sr = g_ptr_array_index (data->accum, ui);
2655 g_return_if_fail (g_hash_table_lookup (by_tl, &sr->range.start) == sr);
2656 g_return_if_fail (g_hash_table_lookup (by_br, &sr->range.end) == sr);
2657 area += range_height (&sr->range) *
2658 (guint64)range_width (&sr->range);
2661 g_return_if_fail (area == data->area);
2664 static void
2665 merge_vertical_stripes (ISL *data)
2667 unsigned ui;
2668 GHashTable *by_tl = data->by_tl;
2669 GHashTable *by_br = data->by_br;
2670 gboolean debug = debug_style_list ();
2671 gboolean paranoid = debug;
2673 for (ui = 0; ui < data->accum->len; ui++) {
2674 GnmStyleRegion *a = g_ptr_array_index (data->accum, ui);
2675 GnmStyleRegion *c;
2676 GnmCellPos cr;
2677 GSList *Bs = NULL, *l;
2678 gboolean fail = FALSE;
2680 /* We're looking for the setup below and extend Bs down */
2681 /* taking over part of C which is then extended to */
2682 /* include all of A. */
2683 /* */
2684 /* +----+ */
2685 /* | +---------+ */
2686 /* +---------+ B1 | B2 | */
2687 /* | A | | | */
2688 /* +---------+----+---------+ */
2689 /* | C | */
2690 /* +------------------------+ */
2692 cr.col = a->range.start.col;
2693 cr.row = a->range.end.row + 1;
2694 c = g_hash_table_lookup (by_tl, &cr);
2695 if (!c || !data->style_equal (a->style, c->style))
2696 continue;
2698 cr.col = c->range.end.col;
2699 cr.row = a->range.end.row;
2700 while (cr.col > a->range.end.col) {
2701 GnmStyleRegion *b = g_hash_table_lookup (by_br, &cr);
2702 if (!b || !data->style_equal (a->style, b->style)) {
2703 fail = TRUE;
2704 break;
2706 Bs = g_slist_prepend (Bs, b);
2707 cr.col = b->range.start.col - 1;
2709 if (fail || cr.col != a->range.end.col) {
2710 g_slist_free (Bs);
2711 continue;
2714 if (debug) {
2715 g_printerr ("Vertical stripe merge:\n");
2716 g_printerr ("A: %s\n", range_as_string (&a->range));
2717 for (l = Bs; l; l = l-> next) {
2718 GnmStyleRegion *b = l->data;
2719 g_printerr ("B: %s\n", range_as_string (&b->range));
2721 g_printerr ("C: %s\n", range_as_string (&c->range));
2724 g_hash_table_remove (by_tl, &a->range.start);
2725 g_hash_table_remove (by_br, &a->range.end);
2726 g_ptr_array_remove_index_fast (data->accum, ui);
2727 ui--;
2729 g_hash_table_remove (by_tl, &c->range.start);
2730 g_hash_table_remove (by_br, &c->range.end);
2731 c->range.start.row = a->range.start.row;
2732 c->range.end.col = a->range.end.col;
2733 g_hash_table_insert (by_tl, &c->range.start, c);
2734 g_hash_table_insert (by_br, &c->range.end, c);
2735 if (debug)
2736 g_printerr ("New C: %s\n", range_as_string (&c->range));
2738 for (l = Bs; l; l = l-> next) {
2739 GnmStyleRegion *b = l->data;
2740 g_hash_table_remove (by_br, &b->range.end);
2741 b->range.end.row = c->range.end.row;
2742 g_hash_table_insert (by_br, &b->range.end, b);
2743 if (debug)
2744 g_printerr ("New B: %s\n", range_as_string (&b->range));
2746 if (debug)
2747 g_printerr ("\n");
2749 gnm_style_region_free (a);
2750 g_slist_free (Bs);
2752 if (paranoid) verify_hashes (data);
2756 static void
2757 merge_horizontal_stripes (ISL *data)
2759 unsigned ui;
2760 GHashTable *by_tl = data->by_tl;
2761 GHashTable *by_br = data->by_br;
2762 gboolean debug = debug_style_list ();
2763 gboolean paranoid = debug;
2765 for (ui = 0; ui < data->accum->len; ui++) {
2766 GnmStyleRegion *a = g_ptr_array_index (data->accum, ui);
2767 GnmStyleRegion *c;
2768 GnmCellPos cr;
2769 GSList *Bs = NULL, *l;
2770 gboolean fail = FALSE;
2772 /* We're looking for the setup below and extend Bs right */
2773 /* taking over part of C which is then extended to */
2774 /* include all of A. */
2775 /* */
2776 /* +-----+-----+ */
2777 /* | A | | */
2778 /* +----+-----+ | */
2779 /* | B1 | | */
2780 /* +--+-------+ | */
2781 /* | | C | */
2782 /* | | | */
2783 /* | B2 | | */
2784 /* | | | */
2785 /* | | | */
2786 /* +-------+-----+ */
2788 cr.col = a->range.end.col + 1;
2789 cr.row = a->range.start.row;
2790 c = g_hash_table_lookup (by_tl, &cr);
2791 if (!c || !data->style_equal (a->style, c->style))
2792 continue;
2794 cr.col = a->range.end.col;
2795 cr.row = c->range.end.row;
2796 while (cr.row > a->range.end.row) {
2797 GnmStyleRegion *b = g_hash_table_lookup (by_br, &cr);
2798 if (!b || !data->style_equal (a->style, b->style)) {
2799 fail = TRUE;
2800 break;
2802 Bs = g_slist_prepend (Bs, b);
2803 cr.row = b->range.start.row - 1;
2805 if (fail || cr.row != a->range.end.row) {
2806 g_slist_free (Bs);
2807 continue;
2810 if (debug) {
2811 g_printerr ("Horizontal stripe merge:\n");
2812 g_printerr ("A: %s\n", range_as_string (&a->range));
2813 for (l = Bs; l; l = l-> next) {
2814 GnmStyleRegion *b = l->data;
2815 g_printerr ("B: %s\n", range_as_string (&b->range));
2817 g_printerr ("C: %s\n", range_as_string (&c->range));
2820 g_hash_table_remove (by_tl, &a->range.start);
2821 g_hash_table_remove (by_br, &a->range.end);
2822 g_ptr_array_remove_index_fast (data->accum, ui);
2823 ui--;
2825 g_hash_table_remove (by_tl, &c->range.start);
2826 g_hash_table_remove (by_br, &c->range.end);
2827 c->range.start.col = a->range.start.col;
2828 c->range.end.row = a->range.end.row;
2829 g_hash_table_insert (by_tl, &c->range.start, c);
2830 g_hash_table_insert (by_br, &c->range.end, c);
2831 if (debug)
2832 g_printerr ("New C: %s\n", range_as_string (&c->range));
2834 for (l = Bs; l; l = l-> next) {
2835 GnmStyleRegion *b = l->data;
2836 g_hash_table_remove (by_br, &b->range.end);
2837 b->range.end.col = c->range.end.col;
2838 g_hash_table_insert (by_br, &b->range.end, b);
2839 if (debug)
2840 g_printerr ("New B: %s\n", range_as_string (&b->range));
2842 if (debug)
2843 g_printerr ("\n");
2845 gnm_style_region_free (a);
2846 g_slist_free (Bs);
2848 if (paranoid) verify_hashes (data);
2852 static int
2853 by_col_row (GnmStyleRegion **a, GnmStyleRegion **b)
2855 int d;
2857 d = (*a)->range.start.col - (*b)->range.start.col;
2858 if (d)
2859 return d;
2861 d = (*a)->range.start.row - (*b)->range.start.row;
2862 return d;
2865 static GnmStyleList *
2866 internal_style_list (Sheet const *sheet, GnmRange const *r,
2867 gboolean (*style_equal) (GnmStyle const *a, GnmStyle const *b),
2868 gboolean (*style_filter) (GnmStyle const *style))
2870 GnmRange full_sheet;
2871 ISL data;
2872 GnmStyleList *res = NULL;
2873 unsigned ui, prelen;
2874 gboolean paranoid = FALSE;
2875 guint64 sheet_area;
2877 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2879 if (r) {
2880 /* This can happen if the last row or column is deleted. */
2881 if (!range_valid (r))
2882 return NULL;
2883 } else
2884 r = range_init_full_sheet (&full_sheet, sheet);
2886 data.accum = g_ptr_array_new ();
2887 data.by_tl = g_hash_table_new ((GHashFunc)gnm_cellpos_hash,
2888 (GEqualFunc)gnm_cellpos_equal);
2889 data.by_br = g_hash_table_new ((GHashFunc)gnm_cellpos_hash,
2890 (GEqualFunc)gnm_cellpos_equal);
2891 data.area = 0;
2892 data.style_equal = style_equal;
2893 data.style_filter = style_filter;
2894 data.sheet_size = gnm_sheet_get_size (sheet);
2896 foreach_tile (sheet, r, cb_style_list_add_node, &data);
2898 sheet_area = (guint64)range_height (r) * range_width (r);
2899 if (data.style_filter ? (data.area > sheet_area) : (data.area != sheet_area))
2900 g_warning ("Strange size issue in internal_style_list");
2903 * Simple, fast optimization first. For the file underlying
2904 * bug 699045 this brings down 332688 entries to just 86.
2906 if (data.accum->len >= 2) {
2907 g_ptr_array_sort (data.accum, (GCompareFunc)by_col_row);
2908 for (ui = data.accum->len - 1; ui > 0; ui--) {
2909 try_merge_pair (&data, ui - 1, ui);
2913 /* Populate hashes. */
2914 for (ui = 0; ui < data.accum->len; ui++) {
2915 GnmStyleRegion *sr = g_ptr_array_index (data.accum, ui);
2916 g_hash_table_insert (data.by_tl, &sr->range.start, sr);
2917 g_hash_table_insert (data.by_br, &sr->range.end, sr);
2920 if (paranoid) verify_hashes (&data);
2922 do {
2923 prelen = data.accum->len;
2924 merge_vertical_stripes (&data);
2925 merge_horizontal_stripes (&data);
2926 } while (prelen > data.accum->len);
2928 /* Always verify once. */
2929 verify_hashes (&data);
2931 if (debug_style_list ())
2932 g_printerr ("Total of %d ranges:\n", data.accum->len);
2933 for (ui = data.accum->len; ui-- > 0; ) {
2934 GnmStyleRegion *sr = g_ptr_array_index (data.accum, ui);
2935 if (debug_style_list ())
2936 g_printerr (" %s %p\n",
2937 range_as_string (&sr->range),
2938 sr->style);
2939 res = g_slist_prepend (res, sr);
2942 g_ptr_array_free (data.accum, TRUE);
2943 g_hash_table_destroy (data.by_tl);
2944 g_hash_table_destroy (data.by_br);
2945 return res;
2949 * sheet_style_get_range:
2950 * @sheet: the sheet in which to find styles
2951 * @r: optional range to scan
2953 * Get a list of rectangles and their associated styles.
2954 * Caller is responsible for freeing. Note that when a range is given,
2955 * the resulting ranges are relative to the input range.
2957 * Returns: (transfer full):
2959 GnmStyleList *
2960 sheet_style_get_range (Sheet const *sheet, GnmRange const *r)
2962 return internal_style_list (sheet, r,
2963 gnm_style_eq,
2964 NULL);
2967 static gboolean
2968 style_conditions_equal (GnmStyle const *a, GnmStyle const *b)
2970 return gnm_style_get_conditions (a) == gnm_style_get_conditions (b);
2973 static gboolean
2974 style_conditions_filter (GnmStyle const *style)
2976 return gnm_style_get_conditions (style) != NULL;
2980 * sheet_style_collect_conditions:
2981 * @sheet:
2982 * @r:
2984 * Returns: (transfer full): a list of areas with conditionals, Caller is
2985 * responsible for freeing.
2987 GnmStyleList *
2988 sheet_style_collect_conditions (Sheet const *sheet, GnmRange const *r)
2990 return internal_style_list (sheet, r,
2991 style_conditions_equal,
2992 style_conditions_filter);
2996 static gboolean
2997 style_hlink_equal (GnmStyle const *a, GnmStyle const *b)
2999 return gnm_style_get_hlink (a) == gnm_style_get_hlink (b);
3002 static gboolean
3003 style_hlink_filter (GnmStyle const *style)
3005 return gnm_style_get_hlink (style) != NULL;
3009 * sheet_style_collect_hlinks:
3010 * @sheet:
3011 * @r:
3013 * Returns: (transfer full): a list of areas with hyperlinks, Caller is
3014 * responsible for freeing.
3016 GnmStyleList *
3017 sheet_style_collect_hlinks (Sheet const *sheet, GnmRange const *r)
3019 return internal_style_list (sheet, r,
3020 style_hlink_equal,
3021 style_hlink_filter);
3025 static gboolean
3026 style_validation_equal (GnmStyle const *a, GnmStyle const *b)
3028 return gnm_style_get_validation (a) == gnm_style_get_validation (b) &&
3029 gnm_style_get_input_msg (a) == gnm_style_get_input_msg (b);
3032 static gboolean
3033 style_validation_filter (GnmStyle const *style)
3035 return (gnm_style_get_validation (style) != NULL ||
3036 gnm_style_get_input_msg (style) != NULL);
3040 * sheet_style_collect_validations:
3041 * @sheet: the to trawl
3042 * @r: (allow-none): range to restrict to
3044 * Returns: (transfer full): a list of areas with validation or input
3045 * message.
3047 GnmStyleList *
3048 sheet_style_collect_validations (Sheet const *sheet, GnmRange const *r)
3050 return internal_style_list (sheet, r,
3051 style_validation_equal,
3052 style_validation_filter);
3056 * sheet_style_set_list:
3057 * @sheet: #Sheet
3058 * @corner: The top-left corner (in LTR mode)
3059 * @l: #GnmStyleList
3060 * @range_modify: (scope call):
3061 * @data: user data
3063 * Overwrites the styles of the ranges given by @corner with the content of
3064 * @list. Optionally transposing the ranges
3066 GnmSpanCalcFlags
3067 sheet_style_set_list (Sheet *sheet, GnmCellPos const *corner,
3068 GnmStyleList const *list,
3069 sheet_style_set_list_cb_t range_modify,
3070 gpointer data)
3072 GnmSpanCalcFlags spanflags = GNM_SPANCALC_SIMPLE;
3073 GnmStyleList const *l;
3075 g_return_val_if_fail (IS_SHEET (sheet), spanflags);
3077 /* Sluggish but simple implementation for now */
3078 for (l = list; l; l = l->next) {
3079 GnmStyleRegion const *sr = l->data;
3080 GnmRange r = sr->range;
3082 range_translate (&r, sheet, +corner->col, +corner->row);
3083 if (range_modify)
3084 range_modify (&r, sheet, data);
3086 gnm_style_ref (sr->style);
3087 sheet_style_set_range (sheet, &r, sr->style);
3088 spanflags |= gnm_style_required_spanflags (sr->style);
3090 return spanflags;
3094 * style_list_free:
3095 * @l: the list to free
3097 * Free up the ressources in the style list. Including unreferencing the
3098 * styles.
3100 void
3101 style_list_free (GnmStyleList *list)
3103 g_slist_free_full (list, (GDestroyNotify)gnm_style_region_free);
3107 * style_list_get_style:
3108 * @l: A style list.
3109 * @col:
3110 * @row:
3112 * Attempts to find the style associated with the @pos offset within the 0,0
3113 * based style list.
3114 * The resulting style does not have its reference count bumped.
3116 GnmStyle const *
3117 style_list_get_style (GnmStyleList const *list, int col, int row)
3119 GnmStyleList const *l;
3121 for (l = list; l; l = l->next) {
3122 GnmStyleRegion const *sr = l->data;
3123 GnmRange const *r = &sr->range;
3124 if (range_contains (r, col, row))
3125 return sr->style;
3127 return NULL;
3130 static void
3131 cb_find_link (GnmStyle *style,
3132 G_GNUC_UNUSED int corner_col, G_GNUC_UNUSED int corner_row,
3133 G_GNUC_UNUSED int width, G_GNUC_UNUSED int height,
3134 G_GNUC_UNUSED GnmRange const *apply_to, gpointer user)
3136 GnmHLink **plink = user;
3137 if (*plink == NULL)
3138 *plink = gnm_style_get_hlink (style);
3142 * sheet_style_region_contains_link:
3143 * @sheet:
3144 * @r:
3146 * Utility routine that checks to see if a region contains at least 1 hyper link
3147 * and returns the 1st one it finds.
3149 * Returns: (transfer none): the found #GmHLink if any.
3151 GnmHLink *
3152 sheet_style_region_contains_link (Sheet const *sheet, GnmRange const *r)
3154 GnmHLink *res = NULL;
3156 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3157 g_return_val_if_fail (r != NULL, NULL);
3159 foreach_tile (sheet, r, cb_find_link, &res);
3160 return res;
3164 * sheet_style_foreach:
3165 * @sheet: #Sheet
3166 * @func: (scope call): callback
3167 * @user_data: user data.
3169 * Executes @func for each style in the sheet.
3171 void
3172 sheet_style_foreach (Sheet const *sheet, GFunc func, gpointer user_data)
3174 GSList *styles;
3176 g_return_if_fail (IS_SHEET (sheet));
3177 g_return_if_fail (sheet->style_data != NULL);
3179 styles = sh_all_styles (sheet->style_data->style_hash);
3180 styles = g_slist_sort (styles, (GCompareFunc)gnm_style_cmp);
3181 g_slist_foreach (styles, func, user_data);
3182 g_slist_free (styles);
3186 * sheet_style_range_foreach:
3187 * @sheet: #Sheet
3188 * @r: optional range
3189 * @func: (scope call): callback.
3190 * @user_data: user data
3193 void
3194 sheet_style_range_foreach (Sheet const *sheet, GnmRange const *r,
3195 GHFunc func, gpointer user_data)
3197 GnmStyleList *styles, *l;
3199 styles = sheet_style_get_range (sheet, r);
3201 for (l = styles; l; l = l->next) {
3202 GnmStyleRegion *sr = l->data;
3203 if (r) {
3204 sr->range.start.col += r->start.col;
3205 sr->range.start.row += r->start.row;
3206 sr->range.end.col += r->start.col;
3207 sr->range.end.row += r->start.row;
3209 func (NULL, sr, user_data);
3210 gnm_style_region_free (sr);
3213 g_slist_free (styles);
3216 /* ------------------------------------------------------------------------- */
3218 static void
3219 cell_tile_dump (CellTile **tile, int level, CellTileOptimize *data,
3220 int ccol, int crow)
3222 CellTileType type;
3223 int const w = tile_widths[level];
3224 int const h = tile_heights[level];
3225 GnmRange rng;
3226 const char *indent = "";
3228 type = (*tile)->type;
3230 range_init (&rng,
3231 ccol, crow,
3232 MIN (ccol + tile_widths[level + 1] - 1,
3233 data->ss->max_cols - 1),
3234 MIN (crow + tile_heights[level + 1] - 1,
3235 data->ss->max_rows - 1));
3237 g_printerr ("%s%s: type %s\n",
3238 indent,
3239 range_as_string (&rng),
3240 tile_type_str[type]);
3242 if (type == TILE_PTR_MATRIX) {
3243 int i;
3245 for (i = 0; i < TILE_SIZE_COL * TILE_SIZE_ROW; i++) {
3246 CellTile **subtile = (*tile)->ptr_matrix.ptr + i;
3247 int c = i % TILE_SIZE_COL;
3248 int r = i / TILE_SIZE_COL;
3249 cell_tile_dump (subtile, level - 1, data,
3250 ccol + w * c,
3251 crow + h * r);
3253 } else {
3254 #if 0
3255 int i;
3257 for (i = 0; i < tile_size[type]; i++) {
3258 g_printerr ("%s: %d %p\n",
3259 indent,
3261 (*tile)->style_any.style[i]);
3263 #endif
3267 /* ------------------------------------------------------------------------- */
3269 static void
3270 cell_tile_optimize (CellTile **tile, int level, CellTileOptimize *data,
3271 int ccol, int crow)
3273 CellTileType type;
3274 int const w = tile_widths[level];
3275 int const h = tile_heights[level];
3276 CellTile *res;
3277 int i;
3278 GnmRange rng;
3280 type = (*tile)->type;
3281 if (type == TILE_SIMPLE)
3282 return;
3284 range_init (&rng,
3285 ccol, crow,
3286 MIN (ccol + tile_widths[level + 1] - 1,
3287 data->ss->max_cols - 1),
3288 MIN (crow + tile_heights[level + 1] - 1,
3289 data->ss->max_rows - 1));
3291 switch (type) {
3292 case TILE_COL:
3293 case TILE_ROW:
3294 if (!tile_is_uniform (*tile))
3295 return;
3297 type = TILE_SIMPLE;
3298 break;
3300 case TILE_PTR_MATRIX: {
3301 gboolean all_simple = TRUE;
3302 int i;
3304 for (i = 0; i < TILE_SIZE_COL * TILE_SIZE_ROW; i++) {
3305 CellTile **subtile = (*tile)->ptr_matrix.ptr + i;
3306 if (data->recursion) {
3307 int c = i % TILE_SIZE_COL;
3308 int r = i / TILE_SIZE_COL;
3309 cell_tile_optimize (subtile, level - 1, data,
3310 ccol + w * c,
3311 crow + h * r);
3313 if ((*subtile)->type != TILE_SIMPLE)
3314 all_simple = FALSE;
3316 if (!all_simple)
3317 return;
3319 res = cell_tile_style_new (NULL, TILE_MATRIX);
3320 for (i = 0; i < TILE_SIZE_COL * TILE_SIZE_ROW; i++) {
3321 CellTile *subtile = (*tile)->ptr_matrix.ptr[i];
3322 GnmStyle *st = subtile->style_simple.style[0];
3323 gnm_style_link (st);
3324 res->style_matrix.style[i] = st;
3327 if (debug_style_optimize)
3328 g_printerr ("Turning %s (%dx%d) from a %s into a %s\n",
3329 range_as_string (&rng),
3330 range_width (&rng), range_height (&rng),
3331 tile_type_str[(*tile)->type],
3332 tile_type_str[res->type]);
3333 cell_tile_dtor (*tile);
3334 *tile = res;
3336 /* Fall through */
3339 case TILE_MATRIX: {
3340 gboolean csame = TRUE;
3341 gboolean rsame = TRUE;
3342 int c, r, i;
3344 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r, i += TILE_SIZE_COL) {
3345 for (c = 0 ; c < TILE_SIZE_COL ; ++c) {
3346 if (rsame && c &&
3347 !gnm_style_eq ((*tile)->style_matrix.style[i + c],
3348 (*tile)->style_matrix.style[i ])) {
3349 rsame = FALSE;
3350 if (!csame)
3351 return;
3353 if (csame && r &&
3354 !gnm_style_eq ((*tile)->style_matrix.style[i + c],
3355 (*tile)->style_matrix.style[ c])) {
3356 csame = FALSE;
3357 if (!rsame)
3358 return;
3363 if (csame && rsame)
3364 type = TILE_SIMPLE;
3365 else if (csame) {
3366 type = TILE_COL;
3367 } else {
3368 type = TILE_ROW;
3370 break;
3373 default:
3374 g_assert_not_reached ();
3377 if (debug_style_optimize)
3378 g_printerr ("Turning %s (%dx%d) from a %s into a %s\n",
3379 range_as_string (&rng),
3380 range_width (&rng), range_height (&rng),
3381 tile_type_str[(*tile)->type],
3382 tile_type_str[type]);
3384 res = cell_tile_style_new (NULL, type);
3385 switch (type) {
3386 case TILE_SIMPLE:
3387 res->style_simple.style[0] = (*tile)->style_any.style[0];
3388 break;
3389 case TILE_ROW:
3390 for (i = 0; i < TILE_SIZE_ROW; i++)
3391 res->style_row.style[i] =
3392 (*tile)->style_matrix.style[i * TILE_SIZE_COL];
3393 break;
3394 case TILE_COL:
3395 for (i = 0; i < TILE_SIZE_COL; i++)
3396 res->style_col.style[i] =
3397 (*tile)->style_matrix.style[i];
3398 break;
3399 default:
3400 g_assert_not_reached ();
3403 for (i = 0; i < tile_size[type]; i++)
3404 gnm_style_link (res->style_any.style[i]);
3406 cell_tile_dtor (*tile);
3407 *tile = res;
3410 static GSList *
3411 sample_styles (Sheet *sheet)
3413 GnmSheetSize const *ss = gnm_sheet_get_size (sheet);
3414 GSList *res = NULL;
3415 int c = 0, r = 0;
3416 const int SKIP = 1;
3418 while (1) {
3419 GnmStyle const *mstyle = sheet_style_get (sheet, c, r);
3420 if (res == NULL || !gnm_style_eq (mstyle, res->data)) {
3421 gnm_style_ref (mstyle);
3422 res = g_slist_prepend (res, GINT_TO_POINTER (c));
3423 res = g_slist_prepend (res, GINT_TO_POINTER (r));
3424 res = g_slist_prepend (res, (gpointer)mstyle);
3427 c += SKIP;
3428 if (c >= ss->max_cols) {
3429 c -= ss->max_cols;
3430 r++;
3431 if (r >= ss->max_rows)
3432 break;
3436 return g_slist_reverse (res);
3439 static void
3440 verify_styles (GSList *pre, GSList *post)
3442 GSList *lpre, *lpost;
3443 gboolean silent = FALSE, bad = FALSE;
3445 for (lpre = pre, lpost = post;
3446 lpre || lpost;
3447 lpre = (lpre ? lpre->next->next->next : NULL),
3448 lpost = (lpost ? lpost->next->next->next : NULL)) {
3449 int cpre = lpre ? GPOINTER_TO_INT (lpre->data) : -1;
3450 int rpre = lpre ? GPOINTER_TO_INT (lpre->next->data) : -1;
3451 GnmStyle const *spre = lpre ? lpre->next->next->data : NULL;
3452 int cpost = lpost ? GPOINTER_TO_INT (lpost->data) : -1;
3453 int rpost = lpost ? GPOINTER_TO_INT (lpost->next->data) : -1;
3454 GnmStyle const *spost = lpost ? lpost->next->next->data : NULL;
3456 if (!silent) {
3457 if (!spre || !spost) {
3458 bad = TRUE;
3459 g_warning ("Style optimizer failure at end!");
3460 silent = TRUE;
3461 } else if (cpre != cpost || rpre != rpost) {
3462 bad = TRUE;
3463 g_warning ("Style optimizer position conflict at %s!",
3464 cell_coord_name (cpre, rpre));
3465 silent = TRUE;
3466 } else if (!gnm_style_eq (spre, spost)) {
3467 bad = TRUE;
3468 g_warning ("Style optimizer failure at %s!",
3469 cell_coord_name (cpre, rpre));
3473 if (spre) gnm_style_unref (spre);
3474 if (spost) gnm_style_unref (spost);
3477 g_slist_free (pre);
3478 g_slist_free (post);
3480 g_assert (!bad);
3483 void
3484 sheet_style_optimize (Sheet *sheet)
3486 CellTileOptimize data;
3487 GSList *pre;
3488 gboolean verify;
3490 g_return_if_fail (IS_SHEET (sheet));
3492 if (gnm_debug_flag ("no-style-optimize"))
3493 return;
3495 sheet_colrow_optimize (sheet);
3497 data.ss = gnm_sheet_get_size (sheet);
3498 data.recursion = TRUE;
3500 if (debug_style_optimize) {
3501 g_printerr ("Optimizing %s\n", sheet->name_unquoted);
3502 cell_tile_dump (&sheet->style_data->styles,
3503 sheet->tile_top_level, &data,
3504 0, 0);
3507 verify = gnm_debug_flag ("style-optimize-verify");
3508 pre = verify ? sample_styles (sheet) : NULL;
3510 cell_tile_optimize (&sheet->style_data->styles,
3511 sheet->tile_top_level, &data,
3512 0, 0);
3514 if (debug_style_optimize)
3515 g_printerr ("Optimizing %s...done\n", sheet->name_unquoted);
3517 if (verify) {
3518 GSList *post = sample_styles (sheet);
3519 verify_styles (pre, post);