Whitespace.
[gnumeric.git] / src / sheet-style.c
blob71edcbe5211528ceb8f9b928f668d39e115cd878
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * sheet-style.c: storage mechanism for styles and eventually cells.
5 * Copyright (C) 2000-2006 Jody Goldberg (jody@gnome.org)
6 * Copyright 2013 Morten Welinder (terra@gnome.org)
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) version 3.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
21 * USA
24 #include <gnumeric-config.h>
25 #include "sheet-style.h"
26 #include "ranges.h"
27 #include "sheet.h"
28 #include "expr.h"
29 #include "style.h"
30 #include "style-border.h"
31 #include "style-color.h"
32 #include "style-conditions.h"
33 #include "parse-util.h"
34 #include "cell.h"
35 #include "gutils.h"
36 #include <goffice/goffice.h>
37 #include <glib/gi18n-lib.h>
38 #include <string.h>
39 #include <math.h>
41 #define USE_TILE_POOLS 0
43 /* ------------------------------------------------------------------------- */
46 * This is, essentially, an std::multiset implementation for the style hash.
47 * Note, however, that sh_lookup is based on gnm_style_equal, not gnm_style_eq.
49 typedef GHashTable GnmStyleHash;
51 #if 0
52 /* This is a really crummy hash -- except for forcing collisions. */
53 #define gnm_style_hash(st) 0
54 #endif
56 static void
57 sh_remove (GnmStyleHash *h, GnmStyle *st)
59 guint32 hv = gnm_style_hash (st);
60 GSList *l = g_hash_table_lookup (h, GUINT_TO_POINTER (hv));
62 g_return_if_fail (l != NULL);
64 if (l->data == st) {
65 GSList *next = l->next;
66 if (next) {
67 /* We're removing the first of several elements. */
68 l->next = NULL;
69 g_hash_table_replace (h, GUINT_TO_POINTER (hv), next);
70 } else {
71 /* We're removing the last element. */
72 g_hash_table_remove (h, GUINT_TO_POINTER (hv));
74 } else {
75 /* We're removing an element that isn't first. */
76 l = g_slist_remove (l, st);
80 static GnmStyle *
81 sh_lookup (GnmStyleHash *h, GnmStyle *st)
83 guint32 hv = gnm_style_hash (st);
84 GSList *l = g_hash_table_lookup (h, GUINT_TO_POINTER (hv));
85 while (l) {
86 GnmStyle *st2 = l->data;
87 /* NOTE: This uses gnm_style_equal, not gnm_style_eq. */
88 if (gnm_style_equal (st, st2))
89 return st2;
90 l = l->next;
92 return NULL;
95 static void
96 sh_insert (GnmStyleHash *h, GnmStyle *st)
98 GSList *s = g_slist_prepend (NULL, st);
99 guint32 hv = gnm_style_hash (st);
100 GSList *l = g_hash_table_lookup (h, GUINT_TO_POINTER (hv));
101 if (l) {
102 s->next = l->next;
103 l->next = s;
104 } else {
105 g_hash_table_insert (h, GUINT_TO_POINTER (hv), s);
109 static GSList *
110 sh_all_styles (GnmStyleHash *h)
112 GHashTableIter iter;
113 gpointer value;
114 GSList *res = NULL;
116 g_hash_table_iter_init (&iter, h);
117 while (g_hash_table_iter_next (&iter, NULL, &value)) {
118 GSList *l = value;
119 for (; l; l = l->next)
120 res = g_slist_prepend (res, l->data);
123 return res;
126 static GnmStyleHash *
127 sh_create (void)
129 return g_hash_table_new_full (g_direct_hash, g_direct_equal,
130 NULL, (GDestroyNotify)g_slist_free);
133 static void
134 sh_destroy (GnmStyleHash *h)
136 g_hash_table_destroy (h);
139 /* ------------------------------------------------------------------------- */
141 typedef union _CellTile CellTile;
142 struct _GnmSheetStyleData {
144 * style_hash is a set of all styles used by this sheet. These
145 * styles are all linked.
147 * We always re-use styles from here when we can, but there can
148 * still be duplicates. This happens when styles are changed
149 * while they are in the hash. For example, this happens when
150 * an expression used by a validation style changes due to
151 * row/col insert/delete.
153 GnmStyleHash *style_hash;
155 CellTile *styles;
156 GnmStyle *default_style;
157 GnmColor *auto_pattern_color;
160 static gboolean debug_style_optimize;
162 typedef struct {
163 GnmSheetSize const *ss;
164 gboolean recursion;
165 } CellTileOptimize;
167 static void
168 cell_tile_optimize (CellTile **tile, int level, CellTileOptimize *data,
169 int ccol, int crow);
173 * sheet_style_unlink
174 * For internal use only
176 void
177 sheet_style_unlink (Sheet *sheet, GnmStyle *st)
179 if (sheet->style_data->style_hash)
180 sh_remove (sheet->style_data->style_hash, st);
184 * sheet_style_find:
185 * @sheet: (transfer full): the sheet
186 * @st: a style
188 * Looks up a style from the sheets collection. Linking if necessary.
189 * ABSORBS the reference and adds a link.
191 * Returns: (transfer full): the new style.
193 GnmStyle *
194 sheet_style_find (Sheet const *sheet, GnmStyle *s)
196 GnmStyle *res;
197 res = sh_lookup (sheet->style_data->style_hash, s);
198 if (res != NULL) {
199 gnm_style_link (res);
200 gnm_style_unref (s);
201 return res;
204 s = gnm_style_link_sheet (s, (Sheet *)sheet);
206 /* Retry the lookup in case "s" changed. See #585178. */
207 res = sh_lookup (sheet->style_data->style_hash, s);
208 if (res != NULL) {
209 gnm_style_link (res);
211 * We are abandoning the linking here. We cannot use
212 * gnm_style_unlink as that would call sheet_style_unlink
213 * and thus remove "res" from the hash.
215 gnm_style_abandon_link (s);
216 gnm_style_unref (s);
218 return res;
221 sh_insert (sheet->style_data->style_hash, s);
222 return s;
225 /* Place holder until I merge in the new styles too */
226 static void
227 pstyle_set_border (GnmStyle *st, GnmBorder *border,
228 GnmStyleBorderLocation side)
230 gnm_style_set_border (st,
231 GNM_STYLE_BORDER_LOCATION_TO_STYLE_ELEMENT (side),
232 gnm_style_border_ref (border));
235 /* Amortize the cost of applying a partial style over a large region
236 * by caching and rereferencing the merged result for repeated styles.
238 typedef struct {
239 GnmStyle *new_style;
240 GnmStyle *pstyle;
241 GHashTable *cache;
242 Sheet *sheet;
243 } ReplacementStyle;
245 static void
246 rstyle_ctor_style (ReplacementStyle *res, GnmStyle *new_style, Sheet *sheet)
248 res->sheet = sheet;
249 res->new_style = sheet_style_find (sheet, new_style);
250 res->pstyle = NULL;
251 res->cache = NULL;
254 static void
255 rstyle_ctor_pstyle (ReplacementStyle *res, GnmStyle *pstyle, Sheet *sheet)
257 res->sheet = sheet;
258 res->new_style = NULL;
259 res->pstyle = pstyle;
260 res->cache = g_hash_table_new (g_direct_hash, g_direct_equal);
263 static void
264 cb_style_unlink (gpointer key, gpointer value, G_GNUC_UNUSED gpointer user_data)
266 gnm_style_unlink ((GnmStyle *)key);
267 gnm_style_unlink ((GnmStyle *)value);
270 static void
271 rstyle_dtor (ReplacementStyle *rs)
273 if (rs->cache != NULL) {
274 g_hash_table_foreach (rs->cache, cb_style_unlink, NULL);
275 g_hash_table_destroy (rs->cache);
276 rs->cache = NULL;
278 if (rs->new_style != NULL) {
279 gnm_style_unlink (rs->new_style);
280 rs->new_style = NULL;
282 if (rs->pstyle != NULL) {
283 gnm_style_unref (rs->pstyle);
284 rs->pstyle = NULL;
289 * rstyle_apply: Utility routine that is at the core of applying partial
290 * styles or storing complete styles. It will eventually be smarter
291 * and will maintain the cache of styles associated with each sheet
293 static void
294 rstyle_apply (GnmStyle **old, ReplacementStyle *rs, GnmRange const *r)
296 GnmStyle *s;
297 g_return_if_fail (old != NULL);
298 g_return_if_fail (rs != NULL);
300 if (rs->pstyle != NULL) {
301 /* Cache the merged styles keeping a reference to the originals
302 * just in case all instances change.
304 s = g_hash_table_lookup (rs->cache, *old);
305 if (s == NULL) {
306 GnmStyle *tmp = gnm_style_new_merged (*old, rs->pstyle);
307 s = sheet_style_find (rs->sheet, tmp);
308 gnm_style_link (*old);
309 g_hash_table_insert (rs->cache, *old, s);
311 } else
312 s = rs->new_style;
314 if (*old != s) {
315 if (*old) {
316 gnm_style_unlink_dependents (*old, r);
317 gnm_style_unlink (*old);
320 gnm_style_link_dependents (s, r);
321 gnm_style_link (s);
323 *old = s;
327 void
328 sheet_style_clear_style_dependents (Sheet *sheet, GnmRange const *r)
330 GSList *styles = sh_all_styles (sheet->style_data->style_hash);
331 g_slist_foreach (styles,
332 (GFunc)gnm_style_unlink_dependents,
333 (gpointer)r);
334 g_slist_free (styles);
338 /****************************************************************************/
340 /* If you change this, change the tile_{widths,heights} here
341 * and GNM_MAX_COLS and GNM_MAX_ROWS in gnumeric.h */
342 #define TILE_TOP_LEVEL 6
344 #define TILE_SIZE_COL 8
345 #define TILE_SIZE_ROW 16
347 typedef enum {
348 TILE_UNDEFINED = -1,
349 TILE_SIMPLE = 0,
350 TILE_COL = 1,
351 TILE_ROW = 2,
352 TILE_MATRIX = 3,
353 TILE_PTR_MATRIX = 4
354 } CellTileType;
355 static int const tile_size[/*type*/] = {
356 1, /* TILE_SIMPLE */
357 TILE_SIZE_COL, /* TILE_COL */
358 TILE_SIZE_ROW, /* TILE_ROW */
359 TILE_SIZE_COL * TILE_SIZE_ROW /* TILE_MATRIX */
361 static int const tile_col_count[/*type*/] = {
362 1, /* TILE_SIMPLE */
363 TILE_SIZE_COL, /* TILE_COL */
364 1, /* TILE_ROW */
365 TILE_SIZE_COL, /* TILE_MATRIX */
366 TILE_SIZE_COL /* TILE_PTR_MATRIX */
368 static int const tile_row_count[/*type*/] = {
369 1, /* TILE_SIMPLE */
370 1, /* TILE_COL */
371 TILE_SIZE_ROW, /* TILE_ROW */
372 TILE_SIZE_ROW, /* TILE_MATRIX */
373 TILE_SIZE_ROW /* TILE_PTR_MATRIX */
375 static const char * const tile_type_str[/*type*/] = {
376 "simple", "col", "row", "matrix", "ptr-matrix"
378 static int const tile_widths[/*level*/] = {
380 TILE_SIZE_COL,
381 TILE_SIZE_COL * TILE_SIZE_COL,
382 TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL,
383 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,
385 TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL,
386 TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL
388 static int const tile_heights[/*level*/] = {
390 TILE_SIZE_ROW,
391 TILE_SIZE_ROW * TILE_SIZE_ROW,
392 TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW,
393 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,
395 TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW,
396 TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW
399 typedef struct {
400 CellTileType const type;
401 GnmStyle *style[1];
402 } CellTileStyleSimple;
403 typedef struct {
404 CellTileType const type;
405 GnmStyle *style[TILE_SIZE_COL];
406 } CellTileStyleCol;
407 typedef struct {
408 CellTileType const type;
409 GnmStyle *style[TILE_SIZE_ROW];
410 } CellTileStyleRow;
411 typedef struct {
412 CellTileType const type;
413 GnmStyle *style[TILE_SIZE_COL * TILE_SIZE_ROW];
414 } CellTileStyleMatrix;
415 typedef struct {
416 CellTileType const type;
417 CellTile *ptr[TILE_SIZE_COL * TILE_SIZE_ROW];
418 } CellTilePtrMatrix;
420 union _CellTile {
421 CellTileType const type;
422 CellTileStyleSimple style_any;
423 CellTileStyleSimple style_simple;
424 CellTileStyleCol style_col;
425 CellTileStyleRow style_row;
426 CellTileStyleMatrix style_matrix;
427 CellTilePtrMatrix ptr_matrix;
430 static int active_sheet_count;
431 #if USE_TILE_POOLS
432 static GOMemChunk *tile_pools[5];
433 #define CHUNK_ALLOC(T,ctt) ((T*)go_mem_chunk_alloc (tile_pools[(ctt)]))
434 #define CHUNK_FREE(ctt,v) go_mem_chunk_free (tile_pools[(ctt)], (v))
435 #else
436 static const size_t tile_type_sizeof[5] = {
437 sizeof (CellTileStyleSimple),
438 sizeof (CellTileStyleCol),
439 sizeof (CellTileStyleRow),
440 sizeof (CellTileStyleMatrix),
441 sizeof (CellTilePtrMatrix)
443 static int tile_allocations = 0;
444 #if 1
445 #define CHUNK_ALLOC(T,ctt) (tile_allocations++, (T*)g_slice_alloc (tile_type_sizeof[(ctt)]))
446 #define CHUNK_FREE(ctt,v) (tile_allocations--, g_slice_free1 (tile_type_sizeof[(ctt)], (v)))
447 #else
448 #define CHUNK_ALLOC(T,ctt) (tile_allocations++, (T*)g_malloc (tile_type_sizeof[(ctt)]))
449 #define CHUNK_FREE(ctt,v) (tile_allocations--, g_free ((v)))
450 #endif
451 #endif
455 * Destroy a CellTile (recursively if needed). This will unlink all the
456 * styles in it. We do _not_ unlink style dependents here. That is done
457 * only in rstyle_apply.
459 static void
460 cell_tile_dtor (CellTile *tile)
462 CellTileType t;
464 g_return_if_fail (tile != NULL);
466 t = tile->type;
467 if (t == TILE_PTR_MATRIX) {
468 int i = TILE_SIZE_COL * TILE_SIZE_ROW;
469 while (--i >= 0) {
470 cell_tile_dtor (tile->ptr_matrix.ptr[i]);
471 tile->ptr_matrix.ptr[i] = NULL;
473 } else if (TILE_SIMPLE <= t && t <= TILE_MATRIX) {
474 int i = tile_size[t];
475 while (--i >= 0) {
476 gnm_style_unlink (tile->style_any.style[i]);
477 tile->style_any.style[i] = NULL;
479 } else {
480 g_return_if_fail (FALSE); /* don't free anything */
483 *((CellTileType *)&(tile->type)) = TILE_UNDEFINED; /* poison it */
484 CHUNK_FREE (t, tile);
487 static CellTile *
488 cell_tile_style_new (GnmStyle *style, CellTileType t)
490 CellTile *res = CHUNK_ALLOC (CellTile, t);
491 *((CellTileType *)&(res->type)) = t;
493 if (style != NULL) {
494 int i = tile_size[t];
495 gnm_style_link_multiple (style, i);
496 while (--i >= 0)
497 res->style_any.style[i] = style;
500 return res;
503 static CellTile *
504 cell_tile_ptr_matrix_new (CellTile *t)
506 CellTilePtrMatrix *res;
508 g_return_val_if_fail (t != NULL, NULL);
509 g_return_val_if_fail (TILE_SIMPLE <= t->type &&
510 TILE_MATRIX >= t->type, NULL);
512 res = CHUNK_ALLOC (CellTilePtrMatrix, TILE_PTR_MATRIX);
513 *((CellTileType *)&(res->type)) = TILE_PTR_MATRIX;
515 /* TODO:
516 * If we wanted to get fancy we could use self similarity to decrease
517 * the number of subtiles. However, this would increase the cost of
518 * applying changes later so I'm not sure it is worth the effort.
520 switch (t->type) {
521 case TILE_SIMPLE: {
522 int i = TILE_SIZE_COL * TILE_SIZE_ROW;
523 while (--i >= 0)
524 res->ptr[i] = cell_tile_style_new (
525 t->style_simple.style[0], TILE_SIMPLE);
526 break;
528 case TILE_COL: {
529 int i, r, c;
530 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r)
531 for (c = 0 ; c < TILE_SIZE_COL ; ++c)
532 res->ptr[i++] = cell_tile_style_new (
533 t->style_col.style[c], TILE_SIMPLE);
534 break;
536 case TILE_ROW: {
537 int i, r, c;
538 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r)
539 for (c = 0 ; c < TILE_SIZE_COL ; ++c)
540 res->ptr[i++] = cell_tile_style_new (
541 t->style_row.style[r], TILE_SIMPLE);
542 break;
544 case TILE_MATRIX: {
545 int i = TILE_SIZE_COL * TILE_SIZE_ROW;
546 while (--i >= 0)
547 res->ptr[i] = cell_tile_style_new (
548 t->style_matrix.style[i], TILE_SIMPLE);
549 break;
551 default: ;
554 return (CellTile *)res;
557 static CellTile *
558 cell_tile_matrix_set (CellTile *t)
560 int r, c;
561 CellTileStyleMatrix *res;
563 g_return_val_if_fail (t != NULL, NULL);
564 g_return_val_if_fail (TILE_SIMPLE <= t->type &&
565 TILE_MATRIX >= t->type, NULL);
567 if (t->type == TILE_MATRIX)
568 return t;
570 res = (CellTileStyleMatrix *)cell_tile_style_new (NULL, TILE_MATRIX);
572 switch (t->type) {
573 case TILE_SIMPLE: {
574 GnmStyle *tmp = t->style_simple.style[0];
575 int i = TILE_SIZE_COL * TILE_SIZE_ROW;
576 gnm_style_link_multiple (tmp, i);
577 while (--i >= 0)
578 res->style[i] = tmp;
579 break;
582 case TILE_COL: {
583 int i = 0;
584 for (r = 0; r < TILE_SIZE_ROW; ++r)
585 for (c = 0; c < TILE_SIZE_COL; ++c)
586 gnm_style_link (res->style[i++] =
587 t->style_col.style[c]);
588 break;
591 case TILE_ROW: {
592 int i = 0;
593 for (r = 0; r < TILE_SIZE_ROW; ++r) {
594 GnmStyle *tmp = t->style_row.style[r];
595 gnm_style_link_multiple (tmp, TILE_SIZE_COL);
596 for (c = 0; c < TILE_SIZE_COL; ++c)
597 res->style[i++] = tmp;
599 break;
602 case TILE_MATRIX:
603 default:
604 g_assert_not_reached();
607 cell_tile_dtor (t);
609 return (CellTile *)res;
612 /****************************************************************************/
614 static void
615 sheet_style_sanity_check (void)
617 unsigned c, r;
618 int i;
620 for (c = 1, i = 0; i <= TILE_TOP_LEVEL; i++) {
621 g_assert (c < G_MAXUINT / TILE_SIZE_COL);
622 c *= TILE_SIZE_COL;
624 g_assert (c >= GNM_MAX_COLS);
626 for (r = 1, i = 0; i <= TILE_TOP_LEVEL; i++) {
627 g_assert (r < G_MAXUINT / TILE_SIZE_COL);
628 r *= TILE_SIZE_ROW;
630 g_assert (r >= GNM_MAX_ROWS);
632 g_assert (G_N_ELEMENTS (tile_heights) > TILE_TOP_LEVEL + 1);
634 g_assert (G_N_ELEMENTS (tile_widths) > TILE_TOP_LEVEL + 1);
637 static void
638 sheet_style_init_size (Sheet *sheet, int cols, int rows)
640 GnmStyle *default_style;
641 int lc = 0, lr = 0, w = TILE_SIZE_COL, h = TILE_SIZE_ROW;
643 while (w < cols) {
644 w *= TILE_SIZE_COL;
645 lc++;
647 while (h < rows) {
648 h *= TILE_SIZE_ROW;
649 lr++;
651 sheet->tile_top_level = MAX (lc, lr);
653 if (active_sheet_count++ == 0) {
654 #if USE_TILE_POOLS
655 tile_pools[TILE_SIMPLE] =
656 go_mem_chunk_new ("simple tile pool",
657 sizeof (CellTileStyleSimple),
658 16 * 1024 - 128);
659 tile_pools[TILE_COL] =
660 go_mem_chunk_new ("column tile pool",
661 sizeof (CellTileStyleCol),
662 16 * 1024 - 128);
663 tile_pools[TILE_ROW] =
664 go_mem_chunk_new ("row tile pool",
665 sizeof (CellTileStyleRow),
666 16 * 1024 - 128);
667 tile_pools[TILE_MATRIX] =
668 go_mem_chunk_new ("matrix tile pool",
669 sizeof (CellTileStyleMatrix),
670 MAX (16 * 1024 - 128,
671 100 * sizeof (CellTileStyleMatrix)));
673 /* If this fails one day, just make two pools. */
674 g_assert (sizeof (CellTileStyleMatrix) == sizeof (CellTilePtrMatrix));
675 tile_pools[TILE_PTR_MATRIX] = tile_pools[TILE_MATRIX];
676 #endif
679 sheet->style_data = g_new (GnmSheetStyleData, 1);
680 sheet->style_data->style_hash = sh_create ();
682 sheet->style_data->auto_pattern_color = style_color_auto_pattern ();
684 default_style = gnm_style_new_default ();
685 #if 0
686 /* We can not do this, XL creates full page charts with background
687 * 'none' by default. Then displays that as white. */
688 if (sheet->sheet_type == GNM_SHEET_OBJECT) {
689 gnm_style_set_back_color (default_style,
690 gnm_color_new_rgb8 (0x50, 0x50, 0x50));
691 gnm_style_set_pattern (default_style, 1);
693 #endif
694 sheet->style_data->default_style =
695 sheet_style_find (sheet, default_style);
696 sheet->style_data->styles =
697 cell_tile_style_new (sheet->style_data->default_style,
698 TILE_SIMPLE);
701 void
702 sheet_style_init (Sheet *sheet)
704 int cols = gnm_sheet_get_max_cols (sheet);
705 int rows = gnm_sheet_get_max_rows (sheet);
707 debug_style_optimize = gnm_debug_flag ("style-optimize");
709 sheet_style_sanity_check ();
711 sheet_style_init_size (sheet, cols, rows);
714 void
715 sheet_style_resize (Sheet *sheet, int cols, int rows)
717 GnmStyleList *styles, *l;
718 int old_cols = gnm_sheet_get_max_cols (sheet);
719 int old_rows = gnm_sheet_get_max_rows (sheet);
720 GnmRange save_range, new_full;
722 /* Save the style for the surviving area. */
723 range_init (&save_range, 0, 0,
724 MIN (cols, old_cols) - 1, MIN (rows, old_rows) - 1);
725 styles = sheet_style_get_range (sheet, &save_range);
727 /* Build new empty structures. */
728 sheet_style_shutdown (sheet);
729 sheet_style_init_size (sheet, cols, rows);
731 /* Reapply styles. */
732 range_init (&new_full, 0, 0, cols - 1, rows - 1);
733 for (l = styles; l; l = l->next) {
734 GnmStyleRegion const *sr = l->data;
735 GnmRange const *r = &sr->range;
736 GnmStyle *style = sr->style;
737 GnmRange newr;
738 if (range_intersection (&newr, r, &new_full))
739 sheet_style_apply_range2 (sheet, &newr, style);
742 style_list_free (styles);
745 #if USE_TILE_POOLS
746 static void
747 cb_tile_pool_leak (gpointer data, gpointer user)
749 CellTile *tile = data;
750 g_printerr ("Leaking tile at %p.\n", (void *)tile);
752 #endif
754 void
755 sheet_style_shutdown (Sheet *sheet)
757 GnmStyleHash *table;
758 GnmRange r;
760 g_return_if_fail (IS_SHEET (sheet));
761 g_return_if_fail (sheet->style_data != NULL);
764 * Clear all styles. This is an easy way to clear out all
765 * style dependencies.
767 range_init_full_sheet (&r, sheet);
768 sheet_style_set_range (sheet, &r, sheet_style_default (sheet));
770 cell_tile_dtor (sheet->style_data->styles);
771 sheet->style_data->styles = NULL;
773 sheet->style_data->default_style = NULL;
775 /* Clear the pointer to the hash BEFORE clearing and add a test in
776 * sheet_style_unlink. If we don't then it is possible/probable that
777 * unlinking the styles will attempt to remove them from the hash while
778 * we are walking it.
780 table = sheet->style_data->style_hash;
781 sheet->style_data->style_hash = NULL;
782 g_slist_free_full (sh_all_styles (table),
783 (GDestroyNotify)gnm_style_unlink);
784 sh_destroy (table);
785 style_color_unref (sheet->style_data->auto_pattern_color);
787 g_free (sheet->style_data);
788 sheet->style_data = NULL;
790 if (--active_sheet_count == 0) {
791 #if USE_TILE_POOLS
792 go_mem_chunk_foreach_leak (tile_pools[TILE_SIMPLE],
793 cb_tile_pool_leak, NULL);
794 go_mem_chunk_destroy (tile_pools[TILE_SIMPLE], FALSE);
795 tile_pools[TILE_SIMPLE] = NULL;
797 go_mem_chunk_foreach_leak (tile_pools[TILE_COL],
798 cb_tile_pool_leak, NULL);
799 go_mem_chunk_destroy (tile_pools[TILE_COL], FALSE);
800 tile_pools[TILE_COL] = NULL;
802 go_mem_chunk_foreach_leak (tile_pools[TILE_ROW],
803 cb_tile_pool_leak, NULL);
804 go_mem_chunk_destroy (tile_pools[TILE_ROW], FALSE);
805 tile_pools[TILE_ROW] = NULL;
807 go_mem_chunk_foreach_leak (tile_pools[TILE_MATRIX],
808 cb_tile_pool_leak, NULL);
809 go_mem_chunk_destroy (tile_pools[TILE_MATRIX], FALSE);
810 tile_pools[TILE_MATRIX] = NULL;
812 /* If this fails one day, just make two pools. */
813 g_assert (sizeof (CellTileStyleMatrix) == sizeof (CellTilePtrMatrix));
814 tile_pools[TILE_PTR_MATRIX] = NULL;
815 #else
816 if (tile_allocations)
817 g_printerr ("Leaking %d style tiles.\n", tile_allocations);
818 #endif
823 * sheet_style_set_auto_pattern_color:
824 * @sheet: The sheet
825 * @grid_color: (transfer full): The color
827 * Set the color for rendering auto colored patterns in this sheet.
828 * Absorbs a reference to @pattern_color;
830 void
831 sheet_style_set_auto_pattern_color (Sheet *sheet, GnmColor *pattern_color)
833 g_return_if_fail (IS_SHEET (sheet));
834 g_return_if_fail (sheet->style_data != NULL);
836 style_color_unref (sheet->style_data->auto_pattern_color);
837 sheet->style_data->auto_pattern_color = gnm_color_new_auto (pattern_color->go_color);
838 style_color_unref (pattern_color);
842 * sheet_style_get_auto_pattern_color:
843 * @sheet: the sheet
845 * Returns: (transfer full): the color for rendering auto colored patterns
846 * in this sheet.
848 GnmColor *
849 sheet_style_get_auto_pattern_color (Sheet const *sheet)
851 GnmColor *sc;
852 g_return_val_if_fail (IS_SHEET (sheet), style_color_black ());
853 g_return_val_if_fail (sheet->style_data != NULL, style_color_black ());
854 g_return_val_if_fail (sheet->style_data->auto_pattern_color != NULL,
855 style_color_black ());
857 sc = sheet->style_data->auto_pattern_color;
858 style_color_ref (sc);
860 return sc;
864 * sheet_style_update_grid_color:
866 * This function updates the color of gnm_style_border_none when the sheet to be
867 * rendered is known. gnm_style_border_none tells how to render the
868 * grid. Because the grid color may be different for different sheets, the
869 * functions which render the grid call this function first. The rule for
870 * selecting the grid color, which is the same as in Excel, is: - if the
871 * auto pattern color is default (which is black), the grid color is gray,
872 * as returned by style_color_grid (). - otherwise, the auto pattern color
873 * is used for the grid.
875 void
876 sheet_style_update_grid_color (Sheet const *sheet)
878 GnmColor *default_auto = style_color_auto_pattern ();
879 GnmColor *sheet_auto = sheet_style_get_auto_pattern_color (sheet);
880 GnmColor *grid_color = style_color_grid ();
881 GnmColor *new_color;
883 new_color = (style_color_equal (default_auto, sheet_auto)
884 ? grid_color : sheet_auto);
886 /* Do nothing if we already have the right color */
887 if (gnm_style_border_none()->color != new_color) {
888 style_color_ref (new_color); /* none_set eats the ref */
889 gnm_style_border_none_set_color (new_color);
891 style_color_unref (grid_color);
892 style_color_unref (sheet_auto);
893 style_color_unref (default_auto);
896 /****************************************************************************/
898 static gboolean
899 tile_is_uniform (CellTile const *tile)
901 const int s = tile_size[tile->type];
902 GnmStyle const *st = tile->style_any.style[0];
903 int i;
905 for (i = 1; i < s; i++)
906 if (tile->style_any.style[i] != st)
907 return FALSE;
909 return TRUE;
912 static void
913 vector_apply_pstyle (CellTile *tile, ReplacementStyle *rs,
914 int cc, int cr, int level, GnmRange const *indic)
916 const CellTileType type = tile->type;
917 const int ncols = tile_col_count[type];
918 const int nrows = tile_row_count[type];
919 const int w1 = tile_widths[level + 1] / ncols;
920 const int h1 = tile_heights[level + 1] / nrows;
921 const int fcol = indic->start.col;
922 const int frow = indic->start.row;
923 const int lcol = MIN (ncols - 1, indic->end.col);
924 const int lrow = MIN (nrows - 1, indic->end.row);
925 GnmSheetSize const *ss = gnm_sheet_get_size (rs->sheet);
926 int r, c;
927 GnmRange rng;
929 for (r = frow; r <= lrow; r++) {
930 GnmStyle **st = tile->style_any.style + ncols * r;
931 rng.start.row = cr + h1 * r;
932 rng.end.row = MIN (rng.start.row + (h1 - 1),
933 ss->max_rows - 1);
934 for (c = fcol; c <= lcol; c++) {
935 rng.start.col = cc + w1 * c;
936 rng.end.col = MIN (rng.start.col + (w1 - 1),
937 ss->max_cols - 1);
938 rstyle_apply (st + c, rs, &rng);
944 * Determine whether before applying a style in the area of apply_to
945 * one needs to split the tile column-wise.
947 * If FALSE is returned then the tile need to be split to a TILE_PTR_MATRIX
948 * because the current level is not fine-grained enough.
950 * If TRUE is returned, TILE_SIMPLE needs to be split into TILE_COL and
951 * TILE_ROW needs to be split into TILE_MATRIX. TILE_COL and TILE_MATRIX
952 * should be kept. In indic, the inclusive post-split indicies of the
953 * range will be returned.
955 * If apply_to covers the entire tile, TRUE will be returned and the judgement
956 * on splitting above should be ignored. The indices in indic will be as-if
957 * the split was done.
959 static gboolean
960 col_indicies (int corner_col, int w, GnmRange const *apply_to,
961 GnmRange *indec)
963 int i, tmp;
965 i = apply_to->start.col - corner_col;
966 if (i <= 0)
967 indec->start.col = 0;
968 else {
969 tmp = i / w;
970 if (i != tmp * w)
971 return FALSE;
972 indec->start.col = tmp;
975 i = 1 + apply_to->end.col - corner_col;
976 tmp = i / w;
977 if (tmp >= TILE_SIZE_COL)
978 indec->end.col = TILE_SIZE_COL - 1;
979 else {
980 if (i != tmp * w)
981 return FALSE;
982 indec->end.col = tmp - 1;
985 return TRUE;
988 /* See docs for col_indicies. Swap cols and rows. */
989 static gboolean
990 row_indicies (int corner_row, int h, GnmRange const *apply_to,
991 GnmRange *indic)
993 int i, tmp;
995 i = apply_to->start.row - corner_row;
996 if (i <= 0)
997 indic->start.row = 0;
998 else {
999 int tmp = i / h;
1000 if (i != tmp * h)
1001 return FALSE;
1002 indic->start.row = tmp;
1005 i = 1 + apply_to->end.row - corner_row;
1006 tmp = i / h;
1007 if (tmp >= TILE_SIZE_ROW)
1008 indic->end.row = TILE_SIZE_ROW - 1;
1009 else {
1010 if (i != tmp * h)
1011 return FALSE;
1012 indic->end.row = tmp - 1;
1015 return TRUE;
1019 * cell_tile_apply: This is the primary logic for making changing areas in the
1020 * tree. It could be further optimised if it becomes a bottle neck.
1022 static void
1023 cell_tile_apply (CellTile **tile, int level,
1024 int corner_col, int corner_row,
1025 GnmRange const *apply_to,
1026 ReplacementStyle *rs)
1028 int const width = tile_widths[level+1];
1029 int const height = tile_heights[level+1];
1030 int const w = tile_widths[level];
1031 int const h = tile_heights[level];
1032 gboolean const full_width = (apply_to->start.col <= corner_col &&
1033 apply_to->end.col >= (corner_col+width-1));
1034 gboolean const full_height = (apply_to->start.row <= corner_row &&
1035 apply_to->end.row >= (corner_row+height-1));
1036 GnmRange indic;
1037 CellTileType type;
1038 int c, r, i;
1040 g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
1041 g_return_if_fail (tile != NULL);
1042 g_return_if_fail (*tile != NULL);
1044 type = (*tile)->type;
1045 g_return_if_fail (TILE_SIMPLE <= type && type <= TILE_PTR_MATRIX);
1047 /* applying the same style to part of a simple-tile is a nop */
1048 if (type == TILE_SIMPLE &&
1049 (*tile)->style_simple.style[0] == rs->new_style)
1050 return;
1053 * Indices for the whole tile assuming a split to matrix.
1054 * We can still use these indices if we don't split either way.
1056 indic.start.col = 0;
1057 indic.start.row = 0;
1058 indic.end.col = TILE_SIZE_COL - 1;
1059 indic.end.row = TILE_SIZE_ROW - 1;
1061 if (type == TILE_PTR_MATRIX)
1062 goto drill_down;
1063 else if (full_width && full_height)
1064 goto apply;
1065 else if (full_height) {
1066 if (!col_indicies (corner_col, w, apply_to, &indic))
1067 goto split_to_ptr_matrix;
1069 switch (type) {
1070 case TILE_SIMPLE: {
1071 CellTile *res;
1072 type = TILE_COL;
1073 res = cell_tile_style_new (
1074 (*tile)->style_simple.style[0],
1075 type);
1076 cell_tile_dtor (*tile);
1077 *tile = res;
1078 /* Fall through */
1080 case TILE_COL:
1081 case TILE_MATRIX:
1082 goto apply;
1083 case TILE_ROW:
1084 goto split_to_matrix;
1085 default:
1086 g_assert_not_reached ();
1088 } else if (full_width) {
1089 if (!row_indicies (corner_row, h, apply_to, &indic))
1090 goto split_to_ptr_matrix;
1091 switch (type) {
1092 case TILE_SIMPLE: {
1093 CellTile *res;
1095 type = TILE_ROW;
1096 res = cell_tile_style_new (
1097 (*tile)->style_simple.style[0],
1098 type);
1099 cell_tile_dtor (*tile);
1100 *tile = res;
1101 /* Fall through */
1103 case TILE_ROW:
1104 case TILE_MATRIX:
1105 goto apply;
1106 case TILE_COL:
1107 goto split_to_matrix;
1108 default:
1109 g_assert_not_reached ();
1111 } else {
1112 if (col_indicies (corner_col, w, apply_to, &indic) &&
1113 row_indicies (corner_row, h, apply_to, &indic))
1114 goto split_to_matrix;
1115 else
1116 goto split_to_ptr_matrix;
1119 g_assert_not_reached ();
1121 split_to_matrix:
1122 *tile = cell_tile_matrix_set (*tile);
1124 apply:
1125 vector_apply_pstyle (*tile, rs, corner_col, corner_row, level, &indic);
1127 try_optimize:
1129 CellTileOptimize cto;
1130 cto.ss = gnm_sheet_get_size (rs->sheet);
1131 cto.recursion = FALSE;
1132 cell_tile_optimize (tile, level, &cto, corner_col, corner_row);
1134 return;
1136 split_to_ptr_matrix:
1138 * We get here when apply_to's corners are not on a TILE_MATRIX grid.
1139 * Split to pointer matrix whose element tiles will have a finer grid.
1141 g_return_if_fail (type != TILE_PTR_MATRIX);
1143 CellTile *res = cell_tile_ptr_matrix_new (*tile);
1144 cell_tile_dtor (*tile);
1145 *tile = res;
1146 type = TILE_PTR_MATRIX;
1149 drill_down:
1150 g_return_if_fail (type == TILE_PTR_MATRIX);
1151 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r, i += TILE_SIZE_COL) {
1152 int const cr = corner_row + h*r;
1153 if (cr > apply_to->end.row)
1154 break;
1155 if ((cr + h) <= apply_to->start.row)
1156 continue;
1158 for (c = 0 ; c < TILE_SIZE_COL ; ++c) {
1159 int const cc = corner_col + w*c;
1160 if (cc > apply_to->end.col)
1161 break;
1162 if ((cc + w) <= apply_to->start.col)
1163 continue;
1165 cell_tile_apply ((*tile)->ptr_matrix.ptr + i + c,
1166 level - 1, cc, cr, apply_to, rs);
1169 goto try_optimize;
1172 /* Handler for foreach_tile.
1174 * "width" and "height" refer to tile size which may extend beyond
1175 * the range supplied to foreach_tile and even beyond the sheet.
1177 typedef void (*ForeachTileFunc) (GnmStyle *style,
1178 int corner_col, int corner_row,
1179 int width, int height,
1180 GnmRange const *apply_to, gpointer user);
1181 static void
1182 foreach_tile_r (CellTile *tile, int level,
1183 int corner_col, int corner_row,
1184 GnmRange const *apply_to,
1185 ForeachTileFunc handler,
1186 gpointer user)
1188 int const width = tile_widths[level+1];
1189 int const height = tile_heights[level+1];
1190 int const w = tile_widths[level];
1191 int const h = tile_heights[level];
1192 int c, r, i, last;
1194 g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
1195 g_return_if_fail (tile != NULL);
1197 switch (tile->type) {
1198 case TILE_SIMPLE:
1199 handler (tile->style_simple.style[0],
1200 corner_col, corner_row, width, height,
1201 apply_to, user);
1202 break;
1204 case TILE_COL:
1205 if (apply_to != NULL) {
1206 c = (apply_to->start.col - corner_col) / w;
1207 if (c < 0)
1208 c = 0;
1209 last = (apply_to->end.col - corner_col) / w + 1;
1210 if (last > TILE_SIZE_COL)
1211 last = TILE_SIZE_COL;
1212 } else {
1213 c = 0;
1214 last = TILE_SIZE_COL;
1216 for (; c < last ; ++c)
1217 handler (tile->style_col.style[c],
1218 corner_col + c*w, corner_row, w, height,
1219 apply_to, user);
1220 break;
1222 case TILE_ROW:
1223 if (apply_to != NULL) {
1224 r = (apply_to->start.row - corner_row) / h;
1225 if (r < 0)
1226 r = 0;
1227 last = (apply_to->end.row - corner_row) / h + 1;
1228 if (last > TILE_SIZE_ROW)
1229 last = TILE_SIZE_ROW;
1230 } else {
1231 r = 0;
1232 last = TILE_SIZE_ROW;
1234 for (; r < last ; ++r)
1235 handler (tile->style_row.style[r],
1236 corner_col, corner_row + r*h, width, h,
1237 apply_to, user);
1238 break;
1240 case TILE_MATRIX:
1241 case TILE_PTR_MATRIX:
1242 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r, i += TILE_SIZE_COL) {
1243 int const cr = corner_row + h*r;
1244 if (apply_to) {
1245 if (cr > apply_to->end.row)
1246 break;
1247 if ((cr + h) <= apply_to->start.row)
1248 continue;
1251 for (c = 0 ; c < TILE_SIZE_COL ; ++c) {
1252 int const cc = corner_col + w*c;
1253 if (apply_to) {
1254 if (cc > apply_to->end.col)
1255 break;
1256 if ((cc + w) <= apply_to->start.col)
1257 continue;
1260 if (tile->type == TILE_MATRIX) {
1261 handler (tile->style_matrix.style[r*TILE_SIZE_COL+c],
1262 corner_col + c * w,
1263 corner_row + r * h,
1264 w, h, apply_to, user);
1265 } else {
1266 foreach_tile_r (
1267 tile->ptr_matrix.ptr[c + r*TILE_SIZE_COL],
1268 level-1, cc, cr, apply_to, handler, user);
1272 break;
1274 default:
1275 g_warning ("Adaptive Quad Tree corruption !");
1279 static void
1280 foreach_tile (Sheet const *sheet, GnmRange const *apply_to,
1281 ForeachTileFunc handler, gpointer user)
1283 foreach_tile_r (sheet->style_data->styles,
1284 sheet->tile_top_level, 0, 0,
1285 apply_to, handler, user);
1289 * cell_tile_apply_pos: This is an simplified version of cell_tile_apply. It
1290 * does not need all the bells and whistles because it operates on single cells.
1292 static void
1293 cell_tile_apply_pos (CellTile **tile, int level,
1294 int col, int row,
1295 ReplacementStyle *rs)
1297 CellTile *tmp;
1298 CellTileType type;
1299 GnmRange rng;
1301 g_return_if_fail (col >= 0);
1302 g_return_if_fail (col < gnm_sheet_get_max_cols (rs->sheet));
1303 g_return_if_fail (row >= 0);
1304 g_return_if_fail (row < gnm_sheet_get_max_rows (rs->sheet));
1306 range_init (&rng, col, row, col, row);
1308 tail_recursion:
1309 g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
1310 g_return_if_fail (tile != NULL);
1311 g_return_if_fail (*tile != NULL);
1313 tmp = *tile;
1314 type = tmp->type;
1315 g_return_if_fail (TILE_SIMPLE <= type && type <= TILE_PTR_MATRIX);
1317 if (level > 0) {
1318 int const w = tile_widths[level];
1319 int const c = col / w;
1320 int const h = tile_heights[level];
1321 int const r = row / h;
1323 if (type != TILE_PTR_MATRIX) {
1324 /* applying the same style to part of a simple-tile is a nop */
1325 if (type == TILE_SIMPLE &&
1326 (*tile)->style_simple.style[0] == rs->new_style)
1327 return;
1329 tmp = cell_tile_ptr_matrix_new (tmp);
1330 cell_tile_dtor (*tile);
1331 *tile = tmp;
1333 tile = tmp->ptr_matrix.ptr + r * TILE_SIZE_COL + c;
1334 level--;
1335 col -= c*w;
1336 row -= r*h;
1337 goto tail_recursion;
1338 } else if (type != TILE_MATRIX)
1339 *tile = tmp = cell_tile_matrix_set (tmp);
1341 g_return_if_fail (tmp->type == TILE_MATRIX);
1342 rstyle_apply (tmp->style_matrix.style + row * TILE_SIZE_COL + col,
1344 &rng);
1348 * sheet_style_set_range:
1349 * @sheet:
1350 * @range:
1351 * @style: #GnmStyle
1353 * Change the complete style for a region.
1354 * This function absorbs a reference to the new @style.
1356 void
1357 sheet_style_set_range (Sheet *sheet, GnmRange const *range,
1358 GnmStyle *style)
1360 ReplacementStyle rs;
1361 GnmRange r;
1363 g_return_if_fail (IS_SHEET (sheet));
1364 g_return_if_fail (range != NULL);
1366 if (range->start.col > range->end.col ||
1367 range->start.row > range->end.row) {
1368 gnm_style_unref (style);
1369 return;
1372 r = *range;
1373 range_ensure_sanity (&r, sheet);
1375 rstyle_ctor_style (&rs, style, sheet);
1376 cell_tile_apply (&sheet->style_data->styles,
1377 sheet->tile_top_level, 0, 0,
1378 &r, &rs);
1379 rstyle_dtor (&rs);
1383 * sheet_style_apply_col:
1384 * @sheet:
1385 * @col:
1386 * @style: #GnmStyle
1388 * NOTE: This is a simple wrapper for now. When we support col/row styles it
1389 * will make life easier.
1391 * Apply a partial style to a full col.
1392 * The routine absorbs a reference to the partial style.
1394 void
1395 sheet_style_apply_col (Sheet *sheet, int col, GnmStyle *pstyle)
1397 GnmRange r;
1398 range_init_cols (&r, sheet, col, col);
1399 sheet_style_apply_range (sheet, &r, pstyle);
1403 * sheet_style_apply_row:
1404 * @sheet:
1405 * @row:
1406 * @style: #GnmStyle
1408 * NOTE: This is a simple wrapper for now. When we support col/row styles it
1409 * will make life easier.
1411 * Apply a partial style to a full col.
1412 * The routine absorbs a reference to the partial style.
1414 void
1415 sheet_style_apply_row (Sheet *sheet, int row, GnmStyle *pstyle)
1417 GnmRange r;
1418 range_init_rows (&r, sheet, row, row);
1419 sheet_style_apply_range (sheet, &r, pstyle);
1423 * sheet_style_apply_pos:
1424 * @sheet:
1425 * @col:
1426 * @row:
1427 * @style: #GnmStyle
1429 * Apply a partial style to a single cell
1430 * This function absorbs a reference to the new @style.
1432 void
1433 sheet_style_apply_pos (Sheet *sheet, int col, int row,
1434 GnmStyle *pstyle)
1436 ReplacementStyle rs;
1438 g_return_if_fail (IS_SHEET (sheet));
1440 rstyle_ctor_pstyle (&rs, pstyle, sheet);
1441 cell_tile_apply_pos (&sheet->style_data->styles,
1442 sheet->tile_top_level, col, row,
1443 &rs);
1444 rstyle_dtor (&rs);
1447 * sheet_style_set_pos:
1448 * @sheet:
1449 * @col:
1450 * @row:
1451 * @style:
1453 * Change the complete style for a single cell.
1454 * This function absorbs a reference to the new @style.
1456 void
1457 sheet_style_set_pos (Sheet *sheet, int col, int row,
1458 GnmStyle *style)
1460 ReplacementStyle rs;
1462 g_return_if_fail (IS_SHEET (sheet));
1464 rstyle_ctor_style (&rs, style, sheet);
1465 cell_tile_apply_pos (&sheet->style_data->styles,
1466 sheet->tile_top_level, col, row,
1467 &rs);
1468 rstyle_dtor (&rs);
1472 * sheet_style_default:
1473 * @sheet:
1475 * Returns a reference to default style for a sheet.
1477 GnmStyle *
1478 sheet_style_default (Sheet const *sheet)
1480 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1481 g_return_val_if_fail (sheet->style_data != NULL, NULL);
1483 gnm_style_ref (sheet->style_data->default_style);
1484 return sheet->style_data->default_style;
1488 * sheet_style_get:
1489 * @sheet: #Sheet
1490 * @col: column number
1491 * @row: row number
1493 * Returns: (transfer none): find the fully qualified style applicable to
1494 * the specified cell position
1496 GnmStyle const *
1497 sheet_style_get (Sheet const *sheet, int col, int row)
1499 int level = sheet->tile_top_level;
1500 CellTile *tile = sheet->style_data->styles;
1502 while (1) {
1503 int width = tile_widths[level];
1504 int height = tile_heights[level];
1505 int c = col / width;
1506 int r = row / height;
1508 g_return_val_if_fail (tile != NULL, NULL);
1509 g_return_val_if_fail (0 <= c && c < TILE_SIZE_COL, NULL);
1510 g_return_val_if_fail (0 <= r && r < TILE_SIZE_ROW, NULL);
1512 switch (tile->type) {
1513 case TILE_SIMPLE:
1514 return tile->style_simple.style[0];
1515 case TILE_COL:
1516 return tile->style_col.style[c];
1517 case TILE_ROW:
1518 return tile->style_row.style[r];
1519 case TILE_MATRIX:
1520 return tile->style_matrix.style[r * TILE_SIZE_COL + c];
1522 case TILE_PTR_MATRIX:
1523 g_return_val_if_fail (level > 0, NULL);
1525 level--;
1526 tile = tile->ptr_matrix.ptr[r * TILE_SIZE_COL + c];
1527 col -= c * width;
1528 row -= r * height;
1529 continue;
1531 default:
1532 g_warning ("Adaptive Quad Tree corruption !");
1533 return NULL;
1538 #define border_null(b) ((b) == none || (b) == NULL)
1540 static void
1541 style_row (GnmStyle const *style, int start_col, int end_col,
1542 GnmStyleRow *sr, gboolean accept_conditions)
1544 GnmBorder const *top, *bottom, *none = gnm_style_border_none ();
1545 GnmBorder const *left, *right, *v;
1546 int const end = MIN (end_col, sr->end_col);
1547 int i = MAX (start_col, sr->start_col);
1548 GnmStyleConditions *conds;
1550 conds = accept_conditions
1551 ? gnm_style_get_conditions (style)
1552 : NULL;
1553 if (conds) {
1554 GnmEvalPos ep;
1555 int res;
1557 for (eval_pos_init (&ep, (Sheet *)sr->sheet, i, sr->row); ep.eval.col <= end ; ep.eval.col++) {
1558 res = gnm_style_conditions_eval (conds, &ep);
1559 style_row (res >= 0
1560 ? gnm_style_get_cond_style (style, res)
1561 : style,
1562 ep.eval.col, ep.eval.col, sr, FALSE);
1564 return;
1567 top = gnm_style_get_border (style, MSTYLE_BORDER_TOP);
1568 bottom = gnm_style_get_border (style, MSTYLE_BORDER_BOTTOM);
1569 left = gnm_style_get_border (style, MSTYLE_BORDER_LEFT);
1570 right = gnm_style_get_border (style, MSTYLE_BORDER_RIGHT);
1572 /* Cancel grids if there is a background */
1573 if (sr->hide_grid || gnm_style_get_pattern (style) > 0) {
1574 if (top == none)
1575 top = NULL;
1576 if (bottom == none)
1577 bottom = NULL;
1578 if (left == none)
1579 left = NULL;
1580 if (right == none)
1581 right = NULL;
1584 if (left != none && border_null (sr->vertical[i]))
1585 sr->vertical[i] = left;
1586 v = border_null (right) ? left : right;
1588 while (i <= end) {
1589 sr->styles[i] = style;
1590 if (top != none && border_null (sr->top[i]))
1591 sr->top[i] = top;
1592 sr->bottom[i] = bottom;
1593 sr->vertical[++i] = v;
1595 if (border_null (right))
1596 sr->vertical[i] = right;
1599 static void
1600 get_style_row (CellTile const *tile, int level,
1601 int corner_col, int corner_row,
1602 GnmStyleRow *sr)
1604 int const width = tile_widths[level+1];
1605 int const w = tile_widths[level];
1606 int const h = tile_heights[level];
1607 int r = 0;
1608 CellTileType t;
1610 g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
1611 g_return_if_fail (tile != NULL);
1613 t = tile->type;
1615 if (t != TILE_SIMPLE && t != TILE_COL) {
1616 r = (sr->row > corner_row) ? (sr->row - corner_row)/ h : 0;
1617 g_return_if_fail (r < TILE_SIZE_ROW);
1620 if (t == TILE_ROW || t == TILE_SIMPLE) {
1621 style_row (tile->style_any.style[r],
1622 corner_col, corner_col + width - 1, sr, TRUE);
1623 } else {
1624 /* find the start and end */
1625 int c;
1626 int last_c = (sr->end_col - corner_col) / w;
1627 if (last_c >= TILE_SIZE_COL)
1628 last_c = TILE_SIZE_COL-1;
1629 if (sr->start_col > corner_col) {
1630 c = (sr->start_col - corner_col) / w;
1631 corner_col += c * w;
1632 } else
1633 c = 0;
1635 corner_row += h*r;
1637 if (t != TILE_PTR_MATRIX) {
1638 GnmStyle * const *styles = tile->style_any.style + r*TILE_SIZE_COL;
1640 for ( ; c <= last_c ; c++, corner_col += w)
1641 style_row (styles[c],
1642 corner_col, corner_col + w - 1, sr, TRUE);
1643 } else {
1644 CellTile * const *tiles = tile->ptr_matrix.ptr + r*TILE_SIZE_COL;
1646 g_return_if_fail (level > 0);
1648 for ( level-- ; c <= last_c ; c++, corner_col += w)
1649 get_style_row (tiles[c], level,
1650 corner_col, corner_row, sr);
1656 * sheet_style_get_row:
1657 * @sheet: #Sheet
1658 * @sr: #GnmStyleRow
1660 * A utility routine which efficiently retrieves a range of styles within a row.
1661 * It also merges adjacent borders as necessary.
1663 void
1664 sheet_style_get_row (Sheet const *sheet, GnmStyleRow *sr)
1667 g_return_if_fail (IS_SHEET (sheet));
1668 g_return_if_fail (sr != NULL);
1669 g_return_if_fail (sr->styles != NULL);
1670 g_return_if_fail (sr->vertical != NULL);
1671 g_return_if_fail (sr->top != NULL);
1672 g_return_if_fail (sr->bottom != NULL);
1674 sr->sheet = sheet;
1675 sr->vertical[sr->start_col] = gnm_style_border_none ();
1676 get_style_row (sheet->style_data->styles, sheet->tile_top_level, 0, 0, sr);
1679 static void
1680 cb_get_row (GnmStyle *style,
1681 int corner_col, G_GNUC_UNUSED int corner_row,
1682 int width, G_GNUC_UNUSED int height,
1683 GnmRange const *apply_to, gpointer user_)
1685 GnmStyle **res = user_;
1686 int i;
1688 /* The given dimensions refer to the tile, not the area. */
1689 width = MIN (width, apply_to->end.col - corner_col + 1);
1691 for (i = 0; i < width; i++)
1692 res[corner_col + i] = style;
1695 GnmStyle **
1696 sheet_style_get_row2 (Sheet const *sheet, int row)
1698 GnmRange r;
1699 GnmStyle **res = g_new (GnmStyle *, gnm_sheet_get_max_cols (sheet));
1701 range_init_rows (&r, sheet, row, row);
1703 foreach_tile (sheet, &r, cb_get_row, res);
1705 return res;
1710 * style_row_init:
1712 * A small utility routine to initialize the grid drawing GnmStyleRow data
1713 * structure.
1715 void
1716 style_row_init (GnmBorder const * * *prev_vert,
1717 GnmStyleRow *sr, GnmStyleRow *next_sr,
1718 int start_col, int end_col, gpointer mem, gboolean hide_grid)
1720 int n, col;
1721 GnmBorder const *none = hide_grid ? NULL : gnm_style_border_none ();
1723 /* alias the arrays for easy access so that array[col] is valid
1724 * for all elements start_col-1 .. end_col+1 inclusive.
1725 * Note that this means that in some cases array[-1] is legal.
1727 n = end_col - start_col + 3; /* 1 before, 1 after, 1 fencepost */
1728 sr->vertical = mem;
1729 sr->vertical -= start_col-1;
1730 sr->top = sr->vertical + n;
1731 sr->bottom = sr->top + n;
1732 next_sr->top = sr->bottom; /* yes they should share */
1733 next_sr->bottom = next_sr->top + n;
1734 next_sr->vertical = next_sr->bottom + n;
1735 *prev_vert = next_sr->vertical + n;
1736 sr->styles = ((GnmStyle const **) (*prev_vert + n));
1737 next_sr->styles = sr->styles + n;
1738 sr->start_col = next_sr->start_col = start_col;
1739 sr->end_col = next_sr->end_col = end_col;
1740 sr->hide_grid = next_sr->hide_grid = hide_grid;
1742 /* Init the areas that sheet_style_get_row will not */
1743 for (col = start_col-1 ; col <= end_col+1; ++col)
1744 (*prev_vert)[col] = sr->top[col] = none;
1745 sr->vertical [start_col-1] = sr->vertical [end_col+1] =
1746 next_sr->vertical[start_col-1] = next_sr->vertical[end_col+1] =
1747 next_sr->top [start_col-1] = next_sr->top [end_col+1] =
1748 next_sr->bottom [start_col-1] = next_sr->bottom [end_col+1] = none;
1752 * sheet_style_apply_range: (skip)
1753 * @sheet: #Sheet
1754 * @range: #GnmRange to apply over
1755 * @pstyle: (transfer full): A partial style to apply
1757 * Apply a partial style to a region.
1758 * The routine absorbs a reference to the partial style.
1760 void
1761 sheet_style_apply_range (Sheet *sheet, GnmRange const *range, GnmStyle *pstyle)
1763 ReplacementStyle rs;
1764 GnmRange r;
1766 g_return_if_fail (IS_SHEET (sheet));
1767 g_return_if_fail (range != NULL);
1769 if (range->start.col > range->end.col ||
1770 range->start.row > range->end.row) {
1771 gnm_style_unref (pstyle);
1772 return;
1775 r = *range;
1776 range_ensure_sanity (&r, sheet);
1778 rstyle_ctor_pstyle (&rs, pstyle, sheet);
1779 cell_tile_apply (&sheet->style_data->styles,
1780 sheet->tile_top_level, 0, 0,
1781 &r, &rs);
1782 rstyle_dtor (&rs);
1786 * sheet_style_apply_range2: (skip)
1787 * @sheet: #Sheet
1788 * @range: #GnmRange to apply over
1789 * @pstyle: A partial style to apply
1791 * Apply a partial style to a region.
1793 void
1794 sheet_style_apply_range2 (Sheet *sheet, GnmRange const *range, GnmStyle *pstyle)
1796 gnm_style_ref (pstyle);
1797 sheet_style_apply_range (sheet, range, pstyle);
1801 static void
1802 apply_border (Sheet *sheet, GnmRange const *r,
1803 GnmStyleBorderLocation side,
1804 GnmBorder *border)
1806 GnmStyle *pstyle = gnm_style_new ();
1807 pstyle_set_border (pstyle, border, side);
1808 sheet_style_apply_range (sheet, r, pstyle);
1812 * sheet_style_apply_border:
1813 * @sheet:
1814 * @range:
1815 * @borders:
1817 * When a user applies a border to a region we attempt to remove the border
1818 * from the opposing side to avoid overlapping border specifications.
1819 * eg
1820 * if we apply a top border to a range, we would clear the bottom border
1821 * of the range offset upwards.
1823 void
1824 sheet_style_apply_border (Sheet *sheet,
1825 GnmRange const *range,
1826 GnmBorder **borders)
1828 GnmStyle *pstyle = NULL;
1830 if (borders == NULL)
1831 return;
1833 if (borders[GNM_STYLE_BORDER_TOP]) {
1834 /* 1.1 top inner */
1835 GnmRange r = *range;
1836 r.end.row = r.start.row;
1837 apply_border (sheet, &r, GNM_STYLE_BORDER_TOP,
1838 borders[GNM_STYLE_BORDER_TOP]);
1840 /* 1.2 top outer */
1841 r.start.row--;
1842 if (r.start.row >= 0) {
1843 r.end.row = r.start.row;
1844 apply_border (sheet, &r, GNM_STYLE_BORDER_BOTTOM,
1845 gnm_style_border_none ());
1849 if (borders[GNM_STYLE_BORDER_BOTTOM]) {
1850 /* 2.1 bottom inner */
1851 GnmRange r = *range;
1852 r.start.row = r.end.row;
1853 apply_border (sheet, &r, GNM_STYLE_BORDER_BOTTOM,
1854 borders[GNM_STYLE_BORDER_BOTTOM]);
1856 /* 2.2 bottom outer */
1857 r.end.row++;
1858 if (r.end.row < gnm_sheet_get_last_row (sheet)) {
1859 r.start.row = r.end.row;
1860 apply_border (sheet, &r, GNM_STYLE_BORDER_TOP,
1861 gnm_style_border_none ());
1865 if (borders[GNM_STYLE_BORDER_LEFT]) {
1866 /* 3.1 left inner */
1867 GnmRange r = *range;
1868 r.end.col = r.start.col;
1869 apply_border (sheet, &r, GNM_STYLE_BORDER_LEFT,
1870 borders[GNM_STYLE_BORDER_LEFT]);
1872 /* 3.2 left outer */
1873 r.start.col--;
1874 if (r.start.col >= 0) {
1875 r.end.col = r.start.col;
1876 apply_border (sheet, &r, GNM_STYLE_BORDER_RIGHT,
1877 gnm_style_border_none ());
1881 if (borders[GNM_STYLE_BORDER_RIGHT]) {
1882 /* 4.1 right inner */
1883 GnmRange r = *range;
1884 r.start.col = r.end.col;
1885 apply_border (sheet, &r, GNM_STYLE_BORDER_RIGHT,
1886 borders[GNM_STYLE_BORDER_RIGHT]);
1888 /* 4.2 right outer */
1889 r.end.col++;
1890 if (r.end.col < gnm_sheet_get_last_col (sheet)) {
1891 r.start.col = r.end.col;
1892 apply_border (sheet, &r, GNM_STYLE_BORDER_LEFT,
1893 gnm_style_border_none ());
1897 /* Interiors horizontal : prefer top */
1898 if (borders[GNM_STYLE_BORDER_HORIZ] != NULL) {
1899 /* 5.1 horizontal interior top */
1900 if (range->start.row != range->end.row) {
1901 GnmRange r = *range;
1902 ++r.start.row;
1903 apply_border (sheet, &r, GNM_STYLE_BORDER_TOP,
1904 borders[GNM_STYLE_BORDER_HORIZ]);
1906 /* 5.2 interior bottom */
1907 if (range->start.row != range->end.row) {
1908 GnmRange r = *range;
1909 --r.end.row;
1910 apply_border (sheet, &r, GNM_STYLE_BORDER_BOTTOM,
1911 gnm_style_border_none ());
1915 /* Interiors vertical: prefer left */
1916 if (borders[GNM_STYLE_BORDER_VERT] != NULL) {
1917 /* 6.1 vertical interior left */
1918 if (range->start.col != range->end.col) {
1919 GnmRange r = *range;
1920 ++r.start.col;
1921 apply_border (sheet, &r, GNM_STYLE_BORDER_LEFT,
1922 borders[GNM_STYLE_BORDER_VERT]);
1925 /* 6.2 The vertical interior right */
1926 if (range->start.col != range->end.col) {
1927 GnmRange r = *range;
1928 --r.end.col;
1929 apply_border (sheet, &r, GNM_STYLE_BORDER_RIGHT,
1930 gnm_style_border_none ());
1934 /* 7. Diagonals (apply both in one pass) */
1935 if (borders[GNM_STYLE_BORDER_DIAG] != NULL) {
1936 pstyle = gnm_style_new ();
1937 pstyle_set_border (pstyle, borders[GNM_STYLE_BORDER_DIAG],
1938 GNM_STYLE_BORDER_DIAG);
1940 if (borders[GNM_STYLE_BORDER_REV_DIAG]) {
1941 if (pstyle == NULL)
1942 pstyle = gnm_style_new ();
1943 pstyle_set_border (pstyle, borders[GNM_STYLE_BORDER_REV_DIAG],
1944 GNM_STYLE_BORDER_REV_DIAG);
1946 if (pstyle != NULL)
1947 sheet_style_apply_range (sheet, range, pstyle);
1950 /****************************************************************************/
1952 typedef struct {
1953 GnmStyle *accum;
1954 unsigned int conflicts;
1955 } FindConflicts;
1957 static void
1958 cb_find_conflicts (GnmStyle *style,
1959 G_GNUC_UNUSED int corner_col, G_GNUC_UNUSED int corner_row,
1960 G_GNUC_UNUSED int width, G_GNUC_UNUSED int height,
1961 G_GNUC_UNUSED GnmRange const *apply_to, FindConflicts *ptr)
1963 ptr->conflicts = gnm_style_find_conflicts (ptr->accum, style, ptr->conflicts);
1966 static void
1967 border_mask_internal (gboolean *known, GnmBorder **borders,
1968 GnmBorder const *b, GnmStyleBorderLocation l)
1970 if (!known[l]) {
1971 known[l] = TRUE;
1972 borders[l] = (GnmBorder *)b;
1973 gnm_style_border_ref (borders[l]);
1974 } else if (borders[l] != b && borders[l] != NULL) {
1975 gnm_style_border_unref (borders[l]);
1976 borders[l] = NULL;
1980 static void
1981 border_mask (gboolean *known, GnmBorder **borders,
1982 GnmBorder const *b, GnmStyleBorderLocation l)
1984 if (b == NULL)
1985 b = gnm_style_border_none ();
1986 border_mask_internal (known, borders, b, l);
1989 static void
1990 border_mask_vec (gboolean *known, GnmBorder **borders,
1991 GnmBorder const * const *vec, int first, int last,
1992 GnmStyleBorderLocation l)
1994 GnmBorder const *b = vec[first];
1996 if (b == NULL)
1997 b = gnm_style_border_none ();
1998 while (first++ < last) {
1999 GnmBorder const *tmp = vec[first];
2000 if (tmp == NULL)
2001 tmp = gnm_style_border_none ();
2002 if (b != tmp) {
2003 b = NULL;
2004 break;
2008 border_mask_internal (known, borders, b, l);
2012 * sheet_style_get_uniform:
2013 * @sheet:
2014 * @range:
2015 * @borders:
2017 * Find out what style elements are common to every cell in a range
2018 * Returns a flag of TRUE if there was a conflict a given style element
2020 unsigned int
2021 sheet_style_find_conflicts (Sheet const *sheet, GnmRange const *r,
2022 GnmStyle **style, GnmBorder **borders)
2024 int n, col, row, start_col, end_col;
2025 GnmStyleRow sr;
2026 gpointer *sr_array_data;
2027 GnmStyleBorderLocation i;
2028 gboolean known[GNM_STYLE_BORDER_EDGE_MAX];
2029 GnmBorder const *none = gnm_style_border_none ();
2030 FindConflicts user;
2032 g_return_val_if_fail (IS_SHEET (sheet), 0);
2033 g_return_val_if_fail (r != NULL, 0);
2034 g_return_val_if_fail (style != NULL, 0);
2035 g_return_val_if_fail (borders != NULL, 0);
2037 /* init style set with a copy of the top left corner of the 1st range */
2038 if (*style == NULL) {
2039 GnmStyle const *tmp = sheet_style_get (sheet, r->start.col, r->start.row);
2040 *style = gnm_style_dup (tmp);
2041 for (i = GNM_STYLE_BORDER_TOP ; i < GNM_STYLE_BORDER_EDGE_MAX ; i++) {
2042 known[i] = FALSE;
2043 borders[i] = gnm_style_border_ref ((GnmBorder *)none);
2045 } else {
2046 for (i = GNM_STYLE_BORDER_TOP ; i < GNM_STYLE_BORDER_EDGE_MAX ; i++)
2047 known[i] = TRUE;
2050 user.accum = *style;
2051 user.conflicts = 0; /* no conflicts yet */
2052 foreach_tile (sheet, r, (ForeachTileFunc)cb_find_conflicts, &user);
2054 /* copy over the diagonals */
2055 for (i = GNM_STYLE_BORDER_REV_DIAG ; i <= GNM_STYLE_BORDER_DIAG ; i++) {
2056 GnmStyleElement se = GNM_STYLE_BORDER_LOCATION_TO_STYLE_ELEMENT (i);
2057 if (user.conflicts & (1 << se))
2058 borders[i] = NULL;
2059 else
2060 borders[i] = gnm_style_border_ref (
2061 gnm_style_get_border (*style, se));
2064 start_col = r->start.col;
2065 if (r->start.col > 0)
2066 start_col--;
2067 end_col = r->end.col;
2068 if (r->end.col < gnm_sheet_get_max_cols (sheet))
2069 end_col++;
2071 /* allocate then alias the arrays for easy access */
2072 n = end_col - start_col + 2;
2073 g_assert (sizeof (GnmBorder *) == sizeof (gpointer));
2074 g_assert (sizeof (GnmStyle *) == sizeof (gpointer));
2075 sr_array_data = g_new (gpointer, n * 4);
2076 sr.vertical = (GnmBorder const **)(sr_array_data - start_col);
2077 sr.top = (GnmBorder const **)(sr_array_data + n - start_col);
2078 sr.bottom = (GnmBorder const **)(sr_array_data + 2 * n - start_col);
2079 sr.styles = (GnmStyle const **) (sr_array_data + 3 * n - start_col);
2080 sr.start_col = start_col;
2081 sr.end_col = end_col;
2082 sr.hide_grid = sheet->hide_grid;
2084 /* pretend the previous bottom had no borders */
2085 for (col = start_col ; col <= end_col; ++col)
2086 sr.top[col] = none;
2088 /* merge the bottom of the previous row */
2089 if (r->start.row > 0) {
2090 GnmBorder const ** roller;
2091 sr.row = r->start.row - 1;
2092 sheet_style_get_row (sheet, &sr);
2093 roller = sr.top; sr.top = sr.bottom; sr.bottom = roller;
2097 * TODO: The border handling is tricky and currently VERY slow for
2098 * large ranges. We could easily optimize this. There is no need to
2099 * retrieve the style in every cell just to do a filter for uniformity
2100 * by row. One day we should do a special case version of
2101 * sheet_style_get_row probably style_get_uniform_col (this will be
2102 * faster)
2104 for (row = r->start.row ; row <= r->end.row ; row++) {
2105 GnmBorder const **roller;
2106 sr.row = row;
2107 sheet_style_get_row (sheet, &sr);
2109 border_mask (known, borders, sr.vertical[r->start.col],
2110 GNM_STYLE_BORDER_LEFT);
2111 border_mask (known, borders, sr.vertical[r->end.col+1],
2112 GNM_STYLE_BORDER_RIGHT);
2113 border_mask_vec (known, borders, sr.top,
2114 r->start.col, r->end.col, (row == r->start.row)
2115 ? GNM_STYLE_BORDER_TOP : GNM_STYLE_BORDER_HORIZ);
2116 if (r->start.col != r->end.col)
2117 border_mask_vec (known, borders, sr.vertical,
2118 r->start.col+1, r->end.col,
2119 GNM_STYLE_BORDER_VERT);
2121 roller = sr.top; sr.top = sr.bottom; sr.bottom = roller;
2124 /* merge the top of the next row */
2125 if (r->end.row < gnm_sheet_get_last_row (sheet)) {
2126 sr.row = r->end.row + 1;
2127 sheet_style_get_row (sheet, &sr);
2129 border_mask_vec (known, borders, sr.top, r->start.col, r->end.col,
2130 GNM_STYLE_BORDER_BOTTOM);
2132 g_free (sr_array_data);
2133 return user.conflicts;
2137 * sheet_style_relocate:
2138 * @rinfo:
2140 * Slide the styles from the origin region to the new position.
2142 void
2143 sheet_style_relocate (GnmExprRelocateInfo const *rinfo)
2145 GnmCellPos corner;
2146 GnmStyleList *styles;
2148 g_return_if_fail (rinfo != NULL);
2150 styles = sheet_style_get_range (rinfo->origin_sheet, &rinfo->origin);
2152 sheet_style_set_range (rinfo->origin_sheet, &rinfo->origin,
2153 sheet_style_default (rinfo->origin_sheet));
2154 corner.col = rinfo->origin.start.col + rinfo->col_offset;
2155 corner.row = rinfo->origin.start.row + rinfo->row_offset;
2156 sheet_style_set_list (rinfo->target_sheet, &corner, styles, NULL, NULL);
2157 style_list_free (styles);
2161 * sheet_style_insdel_colrow:
2162 * @rinfo:
2164 * Insert of delete style columns/rows.
2166 * For the insert case, we stretch the preceding column/row into there space
2167 * we open.
2169 void
2170 sheet_style_insdel_colrow (GnmExprRelocateInfo const *rinfo)
2172 GnmStyleList *styles = NULL;
2173 Sheet *sheet;
2174 GnmCellPos corner;
2175 gboolean is_insert;
2177 g_return_if_fail (rinfo != NULL);
2178 g_return_if_fail (rinfo->origin_sheet == rinfo->target_sheet);
2179 g_return_if_fail ((rinfo->col_offset == 0) != (rinfo->row_offset == 0));
2181 is_insert = (rinfo->col_offset + rinfo->row_offset > 0);
2182 sheet = rinfo->origin_sheet;
2184 if (is_insert) {
2185 /* 1) copy col/row to the top/left of the region, and extend it */
2186 corner = rinfo->origin.start;
2187 if (rinfo->col_offset) {
2188 int col = MAX (corner.col - 1, 0);
2189 GnmStyleList *ptr;
2190 GnmRange r;
2192 corner.row = 0;
2193 range_init_cols (&r, sheet, col, col);
2194 styles = sheet_style_get_range (sheet, &r);
2195 for (ptr = styles ; ptr != NULL ; ptr = ptr->next) {
2196 GnmStyleRegion *sr = ptr->data;
2197 sr->range.end.col = rinfo->col_offset - 1;
2199 } else {
2200 int row = MAX (corner.row - 1, 0);
2201 GnmStyleList *ptr;
2202 GnmRange r;
2204 corner.col = 0;
2205 range_init_rows (&r, sheet, row, row);
2206 styles = sheet_style_get_range (sheet, &r);
2207 for (ptr = styles ; ptr != NULL ; ptr = ptr->next) {
2208 GnmStyleRegion *sr = ptr->data;
2209 sr->range.end.row = rinfo->row_offset - 1;
2214 sheet_style_relocate (rinfo);
2216 if (styles) {
2217 sheet_style_set_list (sheet, &corner, styles, NULL, NULL);
2218 style_list_free (styles);
2222 static void
2223 cb_style_extent (GnmStyle *style,
2224 int corner_col, int corner_row, int width, int height,
2225 GnmRange const *apply_to, gpointer user)
2227 GnmRange *res = user;
2228 if (gnm_style_visible_in_blank (style)) {
2229 int tmp;
2231 /* The given dimensions refer to the tile, not the area. */
2232 width = MIN (width, apply_to->end.col - corner_col + 1);
2233 height = MIN (height, apply_to->end.row - corner_row + 1);
2235 tmp = corner_col+width-1;
2236 if (res->end.col < tmp)
2237 res->end.col = tmp;
2238 if (res->start.col > corner_col)
2239 res->start.col = corner_col;
2241 tmp = corner_row+height-1;
2242 if (res->end.row < tmp)
2243 res->end.row = tmp;
2244 if (res->start.row > corner_row)
2245 res->start.row = corner_row;
2250 * sheet_style_get_extent:
2251 * @sheet: sheet to measure
2252 * @r: starting range and resulting range
2254 * A simple implementation that finds the smallest range containing all visible styles
2255 * and containing @res.
2257 void
2258 sheet_style_get_extent (Sheet const *sheet, GnmRange *res)
2260 GnmRange r;
2262 range_init_full_sheet (&r, sheet);
2263 foreach_tile (sheet, &r, cb_style_extent, res);
2266 struct cb_nondefault_extent {
2267 GnmRange *res;
2268 GnmStyle **col_defaults;
2271 static void
2272 cb_nondefault_extent (GnmStyle *style,
2273 int corner_col, int corner_row, int width, int height,
2274 GnmRange const *apply_to, gpointer user_)
2276 struct cb_nondefault_extent *user = user_;
2277 GnmRange *res = user->res;
2278 int i;
2280 for (i = 0; i < width; i++) {
2281 int col = corner_col + i;
2282 if (col >= apply_to->start.col &&
2283 col <= apply_to->end.col &&
2284 style != user->col_defaults[col]) {
2285 int max_row = MIN (corner_row + height - 1,
2286 apply_to->end.row);
2287 int min_row = MAX (corner_row, apply_to->start.row);
2289 res->start.col = MIN (col, res->start.col);
2290 res->start.row = MIN (min_row, res->start.row);
2292 res->end.col = MAX (col, res->end.col);
2293 res->end.row = MAX (max_row, res->end.row);
2298 void
2299 sheet_style_get_nondefault_extent (Sheet const *sheet, GnmRange *extent,
2300 const GnmRange *src, GnmStyle **col_defaults)
2302 struct cb_nondefault_extent user;
2303 user.res = extent;
2304 user.col_defaults = col_defaults;
2305 foreach_tile (sheet, src, cb_nondefault_extent, &user);
2308 struct cb_is_default {
2309 gboolean res;
2310 GnmStyle **col_defaults;
2313 static void
2314 cb_is_default (GnmStyle *style,
2315 int corner_col, G_GNUC_UNUSED int corner_row,
2316 int width, G_GNUC_UNUSED int height,
2317 GnmRange const *apply_to, gpointer user_)
2319 struct cb_is_default *user = user_;
2320 int i;
2322 /* The given "width" refers to the tile, not the area. */
2323 width = MIN (width, apply_to->end.col - corner_col + 1);
2325 for (i = 0; user->res && i < width; i++) {
2326 if (style != user->col_defaults[corner_col + i])
2327 user->res = FALSE;
2331 gboolean
2332 sheet_style_is_default (Sheet const *sheet, const GnmRange *r, GnmStyle **col_defaults)
2334 struct cb_is_default user;
2336 user.res = TRUE;
2337 user.col_defaults = col_defaults;
2339 foreach_tile (sheet, r, cb_is_default, &user);
2341 return user.res;
2344 struct cb_get_nondefault {
2345 guint8 *res;
2346 GnmStyle **col_defaults;
2349 static void
2350 cb_get_nondefault (GnmStyle *style,
2351 int corner_col, G_GNUC_UNUSED int corner_row,
2352 int width, G_GNUC_UNUSED int height,
2353 GnmRange const *apply_to, gpointer user_)
2355 struct cb_get_nondefault *user = user_;
2356 int i;
2358 /* The given dimensions refer to the tile, not the area. */
2359 width = MIN (width, apply_to->end.col - corner_col + 1);
2360 height = MIN (height, apply_to->end.row - corner_row + 1);
2362 for (i = 0; i < width; i++) {
2363 if (style != user->col_defaults[corner_col + i]) {
2364 int j;
2365 for (j = 0; j < height; j++)
2366 user->res[corner_row + j] = 1;
2367 break;
2372 guint8 *
2373 sheet_style_get_nondefault_rows (Sheet const *sheet, GnmStyle **col_defaults)
2375 struct cb_get_nondefault user;
2376 GnmRange r;
2378 range_init_full_sheet (&r, sheet);
2380 user.res = g_new0 (guint8, gnm_sheet_get_max_rows (sheet));
2381 user.col_defaults = col_defaults;
2383 foreach_tile (sheet, &r, cb_get_nondefault, &user);
2385 return user.res;
2388 struct cb_most_common {
2389 GHashTable *h;
2390 int l;
2391 gboolean is_col;
2394 static void
2395 cb_most_common (GnmStyle *style,
2396 int corner_col, int corner_row, int width, int height,
2397 GnmRange const *apply_to, gpointer user)
2399 struct cb_most_common *cmc = user;
2400 int *counts = g_hash_table_lookup (cmc->h, style);
2401 int i;
2402 if (!counts) {
2403 counts = g_new0 (int, cmc->l);
2404 g_hash_table_insert (cmc->h, style, counts);
2407 /* The given dimensions refer to the tile, not the area. */
2408 width = MIN (width, apply_to->end.col - corner_col + 1);
2409 height = MIN (height, apply_to->end.row - corner_row + 1);
2411 if (cmc->is_col)
2412 for (i = 0; i < width; i++)
2413 counts[corner_col + i] += height;
2414 else
2415 for (i = 0; i < height; i++)
2416 counts[corner_row + i] += width;
2420 * sheet_style_most_common:
2421 * @sheet: sheet to inspect
2422 * @is_col: if %TRUE, look for common styles in columns; if FALSE, look in rows.
2424 * Returns: an array of styles describing the most common styles, one per column
2425 * or row.
2427 GnmStyle **
2428 sheet_style_most_common (Sheet const *sheet, gboolean is_col)
2430 GnmRange r;
2431 struct cb_most_common cmc;
2432 int *max;
2433 GnmStyle **res;
2434 GHashTableIter iter;
2435 gpointer key, value;
2437 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2439 range_init_full_sheet (&r, sheet);
2440 cmc.h = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
2441 cmc.l = colrow_max (is_col, sheet);
2442 cmc.is_col = is_col;
2443 foreach_tile (sheet, &r, cb_most_common, &cmc);
2445 max = g_new0 (int, cmc.l);
2446 res = g_new0 (GnmStyle *, cmc.l);
2447 g_hash_table_iter_init (&iter, cmc.h);
2448 while (g_hash_table_iter_next (&iter, &key, &value)) {
2449 int *counts = value;
2450 GnmStyle *style = key;
2451 int j;
2452 for (j = 0; j < cmc.l; j++) {
2453 /* FIXME: we really ought to break ties in a
2454 consistent way that does not depend on hash
2455 order. */
2456 if (counts[j] > max[j]) {
2457 max[j] = counts[j];
2458 res[j] = style;
2462 g_hash_table_destroy (cmc.h);
2463 g_free (max);
2465 return res;
2468 /****************************************************************************/
2471 * gnm_style_region_new:
2472 * @range: #GnmRange
2473 * @style: #GnmStyle
2475 * Returns: (transfer full): the newly allocated #GnmStyleRegion.
2477 GnmStyleRegion *
2478 gnm_style_region_new (GnmRange const *range, GnmStyle *style)
2480 GnmStyleRegion *sr;
2482 sr = g_new (GnmStyleRegion, 1);
2483 sr->range = *range;
2484 sr->style = style;
2485 gnm_style_ref (style);
2487 return sr;
2490 void
2491 gnm_style_region_free (GnmStyleRegion *sr)
2493 g_return_if_fail (sr != NULL);
2495 gnm_style_unref (sr->style);
2496 sr->style = NULL;
2497 g_free (sr);
2500 static GnmStyleRegion *
2501 gnm_style_region_copy (GnmStyleRegion *sr)
2503 GnmStyleRegion *res = g_new (GnmStyleRegion, 1);
2504 *res = *sr;
2505 gnm_style_ref (sr->style);
2506 return res;
2509 GType
2510 gnm_style_region_get_type (void)
2512 static GType t = 0;
2514 if (t == 0) {
2515 t = g_boxed_type_register_static ("GnmStyleRegion",
2516 (GBoxedCopyFunc)gnm_style_region_copy,
2517 (GBoxedFreeFunc)gnm_style_region_free);
2519 return t;
2523 static gboolean
2524 debug_style_list (void)
2526 static int debug = -1;
2527 if (debug < 0)
2528 debug = gnm_debug_flag ("style-list");
2529 return debug;
2532 typedef struct {
2533 GPtrArray *accum;
2534 GHashTable *by_tl, *by_br;
2535 guint64 area;
2536 gboolean (*style_equal) (GnmStyle const *a, GnmStyle const *b);
2537 gboolean (*style_filter) (GnmStyle const *style);
2538 GnmSheetSize const *sheet_size;
2539 } ISL;
2541 static gboolean
2542 merge_ranges (GnmRange *a, GnmRange const *b)
2544 if (a->start.row == b->start.row &&
2545 a->end.row == b->end.row &&
2546 a->end.col + 1 == b->start.col) {
2547 /* "a" is just left of "b". */
2548 a->end.col = b->end.col;
2549 return TRUE;
2552 if (a->start.col == b->start.col &&
2553 a->end.col == b->end.col &&
2554 a->end.row + 1 == b->start.row) {
2555 /* "a" is just on top of "b". */
2556 a->end.row = b->end.row;
2557 return TRUE;
2560 /* Punt. */
2561 return FALSE;
2564 static gboolean
2565 try_merge_pair (ISL *data, unsigned ui1, unsigned ui2)
2567 GnmStyleRegion *a;
2568 GnmStyleRegion *b;
2570 if (ui1 >= data->accum->len || ui2 >= data->accum->len)
2571 return FALSE;
2573 a = g_ptr_array_index (data->accum, ui1);
2574 b = g_ptr_array_index (data->accum, ui2);
2576 if (!data->style_equal (a->style, b->style))
2577 return FALSE;
2579 if (!merge_ranges (&a->range, &b->range))
2580 return FALSE;
2582 gnm_style_region_free (b);
2583 g_ptr_array_remove_index (data->accum, ui2);
2585 return TRUE;
2588 static void
2589 cb_style_list_add_node (GnmStyle *style,
2590 int corner_col, int corner_row, int width, int height,
2591 GnmRange const *apply_to, gpointer user_)
2593 ISL *data = user_;
2594 GnmSheetSize const *ss = data->sheet_size;
2595 GnmStyleRegion *sr;
2596 GnmRange range;
2598 /* Can this even happen? */
2599 if (corner_col >= ss->max_cols || corner_row >= ss->max_rows)
2600 return;
2602 if (data->style_filter && !data->style_filter (style))
2603 return;
2605 range.start.col = corner_col;
2606 range.start.row = corner_row;
2607 range.end.col = MIN (corner_col + width - 1, ss->max_cols - 1);
2608 range.end.row = MIN (corner_row + height - 1, ss->max_rows - 1);
2610 if (apply_to) {
2611 range.start.col -= apply_to->start.col;
2612 if (range.start.col < 0)
2613 range.start.col = 0;
2614 range.start.row -= apply_to->start.row;
2615 if (range.start.row < 0)
2616 range.start.row = 0;
2618 if (range.end.col > apply_to->end.col)
2619 range.end.col = apply_to->end.col;
2620 range.end.col -= apply_to->start.col;
2621 if (range.end.row > apply_to->end.row)
2622 range.end.row = apply_to->end.row;
2623 range.end.row -= apply_to->start.row;
2626 data->area += (guint64)range_width (&range) * range_height (&range);
2628 sr = gnm_style_region_new (&range, style);
2629 g_ptr_array_add (data->accum, sr);
2631 while (try_merge_pair (data, data->accum->len - 2, data->accum->len - 1))
2632 /* Nothing */;
2635 static void
2636 verify_hashes (ISL *data)
2638 GHashTable *by_tl = data->by_tl;
2639 GHashTable *by_br = data->by_br;
2640 unsigned ui;
2641 guint64 area = 0;
2643 g_return_if_fail (g_hash_table_size (by_tl) == data->accum->len);
2644 g_return_if_fail (g_hash_table_size (by_br) == data->accum->len);
2646 for (ui = 0; ui < data->accum->len; ui++) {
2647 GnmStyleRegion *sr = g_ptr_array_index (data->accum, ui);
2648 g_return_if_fail (g_hash_table_lookup (by_tl, &sr->range.start) == sr);
2649 g_return_if_fail (g_hash_table_lookup (by_br, &sr->range.end) == sr);
2650 area += range_height (&sr->range) *
2651 (guint64)range_width (&sr->range);
2654 g_return_if_fail (area == data->area);
2657 static void
2658 merge_vertical_stripes (ISL *data)
2660 unsigned ui;
2661 GHashTable *by_tl = data->by_tl;
2662 GHashTable *by_br = data->by_br;
2663 gboolean debug = debug_style_list ();
2664 gboolean paranoid = debug;
2666 for (ui = 0; ui < data->accum->len; ui++) {
2667 GnmStyleRegion *a = g_ptr_array_index (data->accum, ui);
2668 GnmStyleRegion *c;
2669 GnmCellPos cr;
2670 GSList *Bs = NULL, *l;
2671 gboolean fail = FALSE;
2673 /* We're looking for the setup below and extend Bs down */
2674 /* taking over part of C which is then extended to */
2675 /* include all of A. */
2676 /* */
2677 /* +----+ */
2678 /* | +---------+ */
2679 /* +---------+ B1 | B2 | */
2680 /* | A | | | */
2681 /* +---------+----+---------+ */
2682 /* | C | */
2683 /* +------------------------+ */
2685 cr.col = a->range.start.col;
2686 cr.row = a->range.end.row + 1;
2687 c = g_hash_table_lookup (by_tl, &cr);
2688 if (!c || !data->style_equal (a->style, c->style))
2689 continue;
2691 cr.col = c->range.end.col;
2692 cr.row = a->range.end.row;
2693 while (cr.col > a->range.end.col) {
2694 GnmStyleRegion *b = g_hash_table_lookup (by_br, &cr);
2695 if (!b || !data->style_equal (a->style, b->style)) {
2696 fail = TRUE;
2697 break;
2699 Bs = g_slist_prepend (Bs, b);
2700 cr.col = b->range.start.col - 1;
2702 if (fail || cr.col != a->range.end.col) {
2703 g_slist_free (Bs);
2704 continue;
2707 if (debug) {
2708 g_printerr ("Vertical stripe merge:\n");
2709 g_printerr ("A: %s\n", range_as_string (&a->range));
2710 for (l = Bs; l; l = l-> next) {
2711 GnmStyleRegion *b = l->data;
2712 g_printerr ("B: %s\n", range_as_string (&b->range));
2714 g_printerr ("C: %s\n", range_as_string (&c->range));
2717 g_hash_table_remove (by_tl, &a->range.start);
2718 g_hash_table_remove (by_br, &a->range.end);
2719 g_ptr_array_remove_index_fast (data->accum, ui);
2720 ui--;
2722 g_hash_table_remove (by_tl, &c->range.start);
2723 g_hash_table_remove (by_br, &c->range.end);
2724 c->range.start.row = a->range.start.row;
2725 c->range.end.col = a->range.end.col;
2726 g_hash_table_insert (by_tl, &c->range.start, c);
2727 g_hash_table_insert (by_br, &c->range.end, c);
2728 if (debug)
2729 g_printerr ("New C: %s\n", range_as_string (&c->range));
2731 for (l = Bs; l; l = l-> next) {
2732 GnmStyleRegion *b = l->data;
2733 g_hash_table_remove (by_br, &b->range.end);
2734 b->range.end.row = c->range.end.row;
2735 g_hash_table_insert (by_br, &b->range.end, b);
2736 if (debug)
2737 g_printerr ("New B: %s\n", range_as_string (&b->range));
2739 if (debug)
2740 g_printerr ("\n");
2742 gnm_style_region_free (a);
2743 g_slist_free (Bs);
2745 if (paranoid) verify_hashes (data);
2749 static void
2750 merge_horizontal_stripes (ISL *data)
2752 unsigned ui;
2753 GHashTable *by_tl = data->by_tl;
2754 GHashTable *by_br = data->by_br;
2755 gboolean debug = debug_style_list ();
2756 gboolean paranoid = debug;
2758 for (ui = 0; ui < data->accum->len; ui++) {
2759 GnmStyleRegion *a = g_ptr_array_index (data->accum, ui);
2760 GnmStyleRegion *c;
2761 GnmCellPos cr;
2762 GSList *Bs = NULL, *l;
2763 gboolean fail = FALSE;
2765 /* We're looking for the setup below and extend Bs right */
2766 /* taking over part of C which is then extended to */
2767 /* include all of A. */
2768 /* */
2769 /* +-----+-----+ */
2770 /* | A | | */
2771 /* +----+-----+ | */
2772 /* | B1 | | */
2773 /* +--+-------+ | */
2774 /* | | C | */
2775 /* | | | */
2776 /* | B2 | | */
2777 /* | | | */
2778 /* | | | */
2779 /* +-------+-----+ */
2781 cr.col = a->range.end.col + 1;
2782 cr.row = a->range.start.row;
2783 c = g_hash_table_lookup (by_tl, &cr);
2784 if (!c || !data->style_equal (a->style, c->style))
2785 continue;
2787 cr.col = a->range.end.col;
2788 cr.row = c->range.end.row;
2789 while (cr.row > a->range.end.row) {
2790 GnmStyleRegion *b = g_hash_table_lookup (by_br, &cr);
2791 if (!b || !data->style_equal (a->style, b->style)) {
2792 fail = TRUE;
2793 break;
2795 Bs = g_slist_prepend (Bs, b);
2796 cr.row = b->range.start.row - 1;
2798 if (fail || cr.row != a->range.end.row) {
2799 g_slist_free (Bs);
2800 continue;
2803 if (debug) {
2804 g_printerr ("Horizontal stripe merge:\n");
2805 g_printerr ("A: %s\n", range_as_string (&a->range));
2806 for (l = Bs; l; l = l-> next) {
2807 GnmStyleRegion *b = l->data;
2808 g_printerr ("B: %s\n", range_as_string (&b->range));
2810 g_printerr ("C: %s\n", range_as_string (&c->range));
2813 g_hash_table_remove (by_tl, &a->range.start);
2814 g_hash_table_remove (by_br, &a->range.end);
2815 g_ptr_array_remove_index_fast (data->accum, ui);
2816 ui--;
2818 g_hash_table_remove (by_tl, &c->range.start);
2819 g_hash_table_remove (by_br, &c->range.end);
2820 c->range.start.col = a->range.start.col;
2821 c->range.end.row = a->range.end.row;
2822 g_hash_table_insert (by_tl, &c->range.start, c);
2823 g_hash_table_insert (by_br, &c->range.end, c);
2824 if (debug)
2825 g_printerr ("New C: %s\n", range_as_string (&c->range));
2827 for (l = Bs; l; l = l-> next) {
2828 GnmStyleRegion *b = l->data;
2829 g_hash_table_remove (by_br, &b->range.end);
2830 b->range.end.col = c->range.end.col;
2831 g_hash_table_insert (by_br, &b->range.end, b);
2832 if (debug)
2833 g_printerr ("New B: %s\n", range_as_string (&b->range));
2835 if (debug)
2836 g_printerr ("\n");
2838 gnm_style_region_free (a);
2839 g_slist_free (Bs);
2841 if (paranoid) verify_hashes (data);
2845 static int
2846 by_col_row (GnmStyleRegion **a, GnmStyleRegion **b)
2848 int d;
2850 d = (*a)->range.start.col - (*b)->range.start.col;
2851 if (d)
2852 return d;
2854 d = (*a)->range.start.row - (*b)->range.start.row;
2855 return d;
2858 static GnmStyleList *
2859 internal_style_list (Sheet const *sheet, GnmRange const *r,
2860 gboolean (*style_equal) (GnmStyle const *a, GnmStyle const *b),
2861 gboolean (*style_filter) (GnmStyle const *style))
2863 GnmRange full_sheet;
2864 ISL data;
2865 GnmStyleList *res = NULL;
2866 unsigned ui, prelen;
2867 gboolean paranoid = FALSE;
2868 guint64 sheet_area;
2870 g_return_val_if_fail (IS_SHEET (sheet), NULL);
2872 if (r) {
2873 /* This can happen if the last row or column is deleted. */
2874 if (!range_valid (r))
2875 return NULL;
2876 } else
2877 r = range_init_full_sheet (&full_sheet, sheet);
2879 data.accum = g_ptr_array_new ();
2880 data.by_tl = g_hash_table_new ((GHashFunc)gnm_cellpos_hash,
2881 (GEqualFunc)gnm_cellpos_equal);
2882 data.by_br = g_hash_table_new ((GHashFunc)gnm_cellpos_hash,
2883 (GEqualFunc)gnm_cellpos_equal);
2884 data.area = 0;
2885 data.style_equal = style_equal;
2886 data.style_filter = style_filter;
2887 data.sheet_size = gnm_sheet_get_size (sheet);
2889 foreach_tile (sheet, r, cb_style_list_add_node, &data);
2891 sheet_area = (guint64)range_height (r) * range_width (r);
2892 if (data.style_filter ? (data.area > sheet_area) : (data.area != sheet_area))
2893 g_warning ("Strange size issue in internal_style_list");
2896 * Simple, fast optimization first. For the file underlying
2897 * bug 699045 this brings down 332688 entries to just 86.
2899 if (data.accum->len >= 2) {
2900 g_ptr_array_sort (data.accum, (GCompareFunc)by_col_row);
2901 for (ui = data.accum->len - 1; ui > 0; ui--) {
2902 try_merge_pair (&data, ui - 1, ui);
2906 /* Populate hashes. */
2907 for (ui = 0; ui < data.accum->len; ui++) {
2908 GnmStyleRegion *sr = g_ptr_array_index (data.accum, ui);
2909 g_hash_table_insert (data.by_tl, &sr->range.start, sr);
2910 g_hash_table_insert (data.by_br, &sr->range.end, sr);
2913 if (paranoid) verify_hashes (&data);
2915 do {
2916 prelen = data.accum->len;
2917 merge_vertical_stripes (&data);
2918 merge_horizontal_stripes (&data);
2919 } while (prelen > data.accum->len);
2921 /* Always verify once. */
2922 verify_hashes (&data);
2924 if (debug_style_list ())
2925 g_printerr ("Total of %d ranges:\n", data.accum->len);
2926 for (ui = data.accum->len; ui-- > 0; ) {
2927 GnmStyleRegion *sr = g_ptr_array_index (data.accum, ui);
2928 if (debug_style_list ())
2929 g_printerr (" %s %p\n",
2930 range_as_string (&sr->range),
2931 sr->style);
2932 res = g_slist_prepend (res, sr);
2935 g_ptr_array_free (data.accum, TRUE);
2936 g_hash_table_destroy (data.by_tl);
2937 g_hash_table_destroy (data.by_br);
2938 return res;
2942 * sheet_style_get_range:
2943 * @sheet: the sheet in which to find styles
2944 * @r: optional range to scan
2946 * Get a list of rectangles and their associated styles.
2947 * Caller is responsible for freeing. Note that when a range is given,
2948 * the resulting ranges are relative to the input range.
2950 * Returns: (transfer full):
2952 GnmStyleList *
2953 sheet_style_get_range (Sheet const *sheet, GnmRange const *r)
2955 return internal_style_list (sheet, r,
2956 gnm_style_eq,
2957 NULL);
2960 static gboolean
2961 style_conditions_equal (GnmStyle const *a, GnmStyle const *b)
2963 return gnm_style_get_conditions (a) == gnm_style_get_conditions (b);
2966 static gboolean
2967 style_conditions_filter (GnmStyle const *style)
2969 return gnm_style_get_conditions (style) != NULL;
2973 * sheet_style_collect_conditions:
2974 * @sheet:
2975 * @r:
2977 * Returns: (transfer full): a list of areas with conditionals, Caller is
2978 * responsible for freeing.
2980 GnmStyleList *
2981 sheet_style_collect_conditions (Sheet const *sheet, GnmRange const *r)
2983 return internal_style_list (sheet, r,
2984 style_conditions_equal,
2985 style_conditions_filter);
2989 static gboolean
2990 style_hlink_equal (GnmStyle const *a, GnmStyle const *b)
2992 return gnm_style_get_hlink (a) == gnm_style_get_hlink (b);
2995 static gboolean
2996 style_hlink_filter (GnmStyle const *style)
2998 return gnm_style_get_hlink (style) != NULL;
3002 * sheet_style_collect_hlinks:
3003 * @sheet:
3004 * @r:
3006 * Returns: (transfer full): a list of areas with hyperlinks, Caller is
3007 * responsible for freeing.
3009 GnmStyleList *
3010 sheet_style_collect_hlinks (Sheet const *sheet, GnmRange const *r)
3012 return internal_style_list (sheet, r,
3013 style_hlink_equal,
3014 style_hlink_filter);
3018 static gboolean
3019 style_validation_equal (GnmStyle const *a, GnmStyle const *b)
3021 return gnm_style_get_validation (a) == gnm_style_get_validation (b) &&
3022 gnm_style_get_input_msg (a) == gnm_style_get_input_msg (b);
3025 static gboolean
3026 style_validation_filter (GnmStyle const *style)
3028 return (gnm_style_get_validation (style) != NULL ||
3029 gnm_style_get_input_msg (style) != NULL);
3033 * sheet_style_collect_validations:
3034 * @sheet: the to trawl
3035 * @r: (allow-none): range to restrict to
3037 * Returns: (transfer full): a list of areas with validation or input
3038 * message.
3040 GnmStyleList *
3041 sheet_style_collect_validations (Sheet const *sheet, GnmRange const *r)
3043 return internal_style_list (sheet, r,
3044 style_validation_equal,
3045 style_validation_filter);
3049 * sheet_style_set_list:
3050 * @sheet: #Sheet
3051 * @corner: The top-left corner (in LTR mode)
3052 * @l: #GnmStyleList
3053 * @range_modify: (scope call):
3054 * @data: user data
3056 * Overwrites the styles of the ranges given by @corner with the content of
3057 * @list. Optionally transposing the ranges
3059 GnmSpanCalcFlags
3060 sheet_style_set_list (Sheet *sheet, GnmCellPos const *corner,
3061 GnmStyleList const *list,
3062 sheet_style_set_list_cb_t range_modify,
3063 gpointer data)
3065 GnmSpanCalcFlags spanflags = GNM_SPANCALC_SIMPLE;
3066 GnmStyleList const *l;
3068 g_return_val_if_fail (IS_SHEET (sheet), spanflags);
3070 /* Sluggish but simple implementation for now */
3071 for (l = list; l; l = l->next) {
3072 GnmStyleRegion const *sr = l->data;
3073 GnmRange r = sr->range;
3075 range_translate (&r, sheet, +corner->col, +corner->row);
3076 if (range_modify)
3077 range_modify (&r, sheet, data);
3079 gnm_style_ref (sr->style);
3080 sheet_style_set_range (sheet, &r, sr->style);
3081 spanflags |= gnm_style_required_spanflags (sr->style);
3083 return spanflags;
3087 * style_list_free:
3088 * @l: the list to free
3090 * Free up the ressources in the style list. Including unreferencing the
3091 * styles.
3093 void
3094 style_list_free (GnmStyleList *list)
3096 g_slist_free_full (list, (GDestroyNotify)gnm_style_region_free);
3100 * style_list_get_style:
3101 * @l: A style list.
3102 * @col:
3103 * @row:
3105 * Attempts to find the style associated with the @pos offset within the 0,0
3106 * based style list.
3107 * The resulting style does not have its reference count bumped.
3109 GnmStyle const *
3110 style_list_get_style (GnmStyleList const *list, int col, int row)
3112 GnmStyleList const *l;
3114 for (l = list; l; l = l->next) {
3115 GnmStyleRegion const *sr = l->data;
3116 GnmRange const *r = &sr->range;
3117 if (range_contains (r, col, row))
3118 return sr->style;
3120 return NULL;
3123 static void
3124 cb_find_link (GnmStyle *style,
3125 G_GNUC_UNUSED int corner_col, G_GNUC_UNUSED int corner_row,
3126 G_GNUC_UNUSED int width, G_GNUC_UNUSED int height,
3127 G_GNUC_UNUSED GnmRange const *apply_to, gpointer user)
3129 GnmHLink **plink = user;
3130 if (*plink == NULL)
3131 *plink = gnm_style_get_hlink (style);
3135 * sheet_style_region_contains_link:
3136 * @sheet:
3137 * @r:
3139 * Utility routine that checks to see if a region contains at least 1 hyper link
3140 * and returns the 1st one it finds.
3142 * Returns: (transfer none): the found #GmHLink if any.
3144 GnmHLink *
3145 sheet_style_region_contains_link (Sheet const *sheet, GnmRange const *r)
3147 GnmHLink *res = NULL;
3149 g_return_val_if_fail (IS_SHEET (sheet), NULL);
3150 g_return_val_if_fail (r != NULL, NULL);
3152 foreach_tile (sheet, r, cb_find_link, &res);
3153 return res;
3157 * sheet_style_foreach:
3158 * @sheet: #Sheet
3159 * @func: (scope call): callback
3160 * @user_data: user data.
3162 * Executes @func for each style in the sheet.
3164 void
3165 sheet_style_foreach (Sheet const *sheet, GFunc func, gpointer user_data)
3167 GSList *styles;
3169 g_return_if_fail (IS_SHEET (sheet));
3170 g_return_if_fail (sheet->style_data != NULL);
3172 styles = sh_all_styles (sheet->style_data->style_hash);
3173 styles = g_slist_sort (styles, (GCompareFunc)gnm_style_cmp);
3174 g_slist_foreach (styles, func, user_data);
3175 g_slist_free (styles);
3179 * sheet_style_range_foreach:
3180 * @sheet: #Sheet
3181 * @r: optional range
3182 * @func: (scope call): callback.
3183 * @user_data: user data
3186 void
3187 sheet_style_range_foreach (Sheet const *sheet, GnmRange const *r,
3188 GHFunc func, gpointer user_data)
3190 GnmStyleList *styles, *l;
3192 styles = sheet_style_get_range (sheet, r);
3194 for (l = styles; l; l = l->next) {
3195 GnmStyleRegion *sr = l->data;
3196 if (r) {
3197 sr->range.start.col += r->start.col;
3198 sr->range.start.row += r->start.row;
3199 sr->range.end.col += r->start.col;
3200 sr->range.end.row += r->start.row;
3202 func (NULL, sr, user_data);
3203 gnm_style_region_free (sr);
3206 g_slist_free (styles);
3209 /* ------------------------------------------------------------------------- */
3211 static void
3212 cell_tile_dump (CellTile **tile, int level, CellTileOptimize *data,
3213 int ccol, int crow)
3215 CellTileType type;
3216 int const w = tile_widths[level];
3217 int const h = tile_heights[level];
3218 GnmRange rng;
3219 const char *indent = "";
3221 type = (*tile)->type;
3223 range_init (&rng,
3224 ccol, crow,
3225 MIN (ccol + tile_widths[level + 1] - 1,
3226 data->ss->max_cols - 1),
3227 MIN (crow + tile_heights[level + 1] - 1,
3228 data->ss->max_rows - 1));
3230 g_printerr ("%s%s: type %s\n",
3231 indent,
3232 range_as_string (&rng),
3233 tile_type_str[type]);
3235 if (type == TILE_PTR_MATRIX) {
3236 int i;
3238 for (i = 0; i < TILE_SIZE_COL * TILE_SIZE_ROW; i++) {
3239 CellTile **subtile = (*tile)->ptr_matrix.ptr + i;
3240 int c = i % TILE_SIZE_COL;
3241 int r = i / TILE_SIZE_COL;
3242 cell_tile_dump (subtile, level - 1, data,
3243 ccol + w * c,
3244 crow + h * r);
3246 } else {
3247 #if 0
3248 int i;
3250 for (i = 0; i < tile_size[type]; i++) {
3251 g_printerr ("%s: %d %p\n",
3252 indent,
3254 (*tile)->style_any.style[i]);
3256 #endif
3260 /* ------------------------------------------------------------------------- */
3262 static void
3263 cell_tile_optimize (CellTile **tile, int level, CellTileOptimize *data,
3264 int ccol, int crow)
3266 CellTileType type;
3267 int const w = tile_widths[level];
3268 int const h = tile_heights[level];
3269 CellTile *res;
3270 int i;
3271 GnmRange rng;
3273 type = (*tile)->type;
3274 if (type == TILE_SIMPLE)
3275 return;
3277 range_init (&rng,
3278 ccol, crow,
3279 MIN (ccol + tile_widths[level + 1] - 1,
3280 data->ss->max_cols - 1),
3281 MIN (crow + tile_heights[level + 1] - 1,
3282 data->ss->max_rows - 1));
3284 switch (type) {
3285 case TILE_COL:
3286 case TILE_ROW:
3287 if (!tile_is_uniform (*tile))
3288 return;
3290 type = TILE_SIMPLE;
3291 break;
3293 case TILE_PTR_MATRIX: {
3294 gboolean all_simple = TRUE;
3295 int i;
3297 for (i = 0; i < TILE_SIZE_COL * TILE_SIZE_ROW; i++) {
3298 CellTile **subtile = (*tile)->ptr_matrix.ptr + i;
3299 if (data->recursion) {
3300 int c = i % TILE_SIZE_COL;
3301 int r = i / TILE_SIZE_COL;
3302 cell_tile_optimize (subtile, level - 1, data,
3303 ccol + w * c,
3304 crow + h * r);
3306 if ((*subtile)->type != TILE_SIMPLE)
3307 all_simple = FALSE;
3309 if (!all_simple)
3310 return;
3312 res = cell_tile_style_new (NULL, TILE_MATRIX);
3313 for (i = 0; i < TILE_SIZE_COL * TILE_SIZE_ROW; i++) {
3314 CellTile *subtile = (*tile)->ptr_matrix.ptr[i];
3315 GnmStyle *st = subtile->style_simple.style[0];
3316 gnm_style_link (st);
3317 res->style_matrix.style[i] = st;
3320 if (debug_style_optimize)
3321 g_printerr ("Turning %s (%dx%d) from a %s into a %s\n",
3322 range_as_string (&rng),
3323 range_width (&rng), range_height (&rng),
3324 tile_type_str[(*tile)->type],
3325 tile_type_str[res->type]);
3326 cell_tile_dtor (*tile);
3327 *tile = res;
3329 /* Fall through */
3332 case TILE_MATRIX: {
3333 gboolean csame = TRUE;
3334 gboolean rsame = TRUE;
3335 int c, r, i;
3337 for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r, i += TILE_SIZE_COL) {
3338 for (c = 0 ; c < TILE_SIZE_COL ; ++c) {
3339 if (rsame && c &&
3340 !gnm_style_eq ((*tile)->style_matrix.style[i + c],
3341 (*tile)->style_matrix.style[i ])) {
3342 rsame = FALSE;
3343 if (!csame)
3344 return;
3346 if (csame && r &&
3347 !gnm_style_eq ((*tile)->style_matrix.style[i + c],
3348 (*tile)->style_matrix.style[ c])) {
3349 csame = FALSE;
3350 if (!rsame)
3351 return;
3356 if (csame && rsame)
3357 type = TILE_SIMPLE;
3358 else if (csame) {
3359 type = TILE_COL;
3360 } else {
3361 type = TILE_ROW;
3363 break;
3366 default:
3367 g_assert_not_reached ();
3370 if (debug_style_optimize)
3371 g_printerr ("Turning %s (%dx%d) from a %s into a %s\n",
3372 range_as_string (&rng),
3373 range_width (&rng), range_height (&rng),
3374 tile_type_str[(*tile)->type],
3375 tile_type_str[type]);
3377 res = cell_tile_style_new (NULL, type);
3378 switch (type) {
3379 case TILE_SIMPLE:
3380 res->style_simple.style[0] = (*tile)->style_any.style[0];
3381 break;
3382 case TILE_ROW:
3383 for (i = 0; i < TILE_SIZE_ROW; i++)
3384 res->style_row.style[i] =
3385 (*tile)->style_matrix.style[i * TILE_SIZE_COL];
3386 break;
3387 case TILE_COL:
3388 for (i = 0; i < TILE_SIZE_COL; i++)
3389 res->style_col.style[i] =
3390 (*tile)->style_matrix.style[i];
3391 break;
3392 default:
3393 g_assert_not_reached ();
3396 for (i = 0; i < tile_size[type]; i++)
3397 gnm_style_link (res->style_any.style[i]);
3399 cell_tile_dtor (*tile);
3400 *tile = res;
3403 static GSList *
3404 sample_styles (Sheet *sheet)
3406 GnmSheetSize const *ss = gnm_sheet_get_size (sheet);
3407 GSList *res = NULL;
3408 int c = 0, r = 0;
3409 const int SKIP = 1;
3411 while (1) {
3412 GnmStyle const *mstyle = sheet_style_get (sheet, c, r);
3413 if (res == NULL || !gnm_style_eq (mstyle, res->data)) {
3414 gnm_style_ref (mstyle);
3415 res = g_slist_prepend (res, GINT_TO_POINTER (c));
3416 res = g_slist_prepend (res, GINT_TO_POINTER (r));
3417 res = g_slist_prepend (res, (gpointer)mstyle);
3420 c += SKIP;
3421 if (c >= ss->max_cols) {
3422 c -= ss->max_cols;
3423 r++;
3424 if (r >= ss->max_rows)
3425 break;
3429 return g_slist_reverse (res);
3432 static void
3433 verify_styles (GSList *pre, GSList *post)
3435 GSList *lpre, *lpost;
3436 gboolean silent = FALSE, bad = FALSE;
3438 for (lpre = pre, lpost = post;
3439 lpre || lpost;
3440 lpre = (lpre ? lpre->next->next->next : NULL),
3441 lpost = (lpost ? lpost->next->next->next : NULL)) {
3442 int cpre = lpre ? GPOINTER_TO_INT (lpre->data) : -1;
3443 int rpre = lpre ? GPOINTER_TO_INT (lpre->next->data) : -1;
3444 GnmStyle const *spre = lpre ? lpre->next->next->data : NULL;
3445 int cpost = lpost ? GPOINTER_TO_INT (lpost->data) : -1;
3446 int rpost = lpost ? GPOINTER_TO_INT (lpost->next->data) : -1;
3447 GnmStyle const *spost = lpost ? lpost->next->next->data : NULL;
3449 if (!silent) {
3450 if (!spre || !spost) {
3451 bad = TRUE;
3452 g_warning ("Style optimizer failure at end!");
3453 silent = TRUE;
3454 } else if (cpre != cpost || rpre != rpost) {
3455 bad = TRUE;
3456 g_warning ("Style optimizer position conflict at %s!",
3457 cell_coord_name (cpre, rpre));
3458 silent = TRUE;
3459 } else if (!gnm_style_eq (spre, spost)) {
3460 bad = TRUE;
3461 g_warning ("Style optimizer failure at %s!",
3462 cell_coord_name (cpre, rpre));
3466 if (spre) gnm_style_unref (spre);
3467 if (spost) gnm_style_unref (spost);
3470 g_slist_free (pre);
3471 g_slist_free (post);
3473 g_assert (!bad);
3476 void
3477 sheet_style_optimize (Sheet *sheet)
3479 CellTileOptimize data;
3480 GSList *pre;
3481 gboolean verify;
3483 g_return_if_fail (IS_SHEET (sheet));
3485 if (gnm_debug_flag ("no-style-optimize"))
3486 return;
3488 sheet_colrow_optimize (sheet);
3490 data.ss = gnm_sheet_get_size (sheet);
3491 data.recursion = TRUE;
3493 if (debug_style_optimize) {
3494 g_printerr ("Optimizing %s\n", sheet->name_unquoted);
3495 cell_tile_dump (&sheet->style_data->styles,
3496 sheet->tile_top_level, &data,
3497 0, 0);
3500 verify = gnm_debug_flag ("style-optimize-verify");
3501 pre = verify ? sample_styles (sheet) : NULL;
3503 cell_tile_optimize (&sheet->style_data->styles,
3504 sheet->tile_top_level, &data,
3505 0, 0);
3507 if (debug_style_optimize)
3508 g_printerr ("Optimizing %s...done\n", sheet->name_unquoted);
3510 if (verify) {
3511 GSList *post = sample_styles (sheet);
3512 verify_styles (pre, post);