sheet-style.c 88.6 KB
Newer Older
1
/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
/*
3 4
 * sheet-style.c: storage mechanism for styles and eventually cells.
 *
5
 * Copyright (C) 2000-2006 Jody Goldberg (jody@gnome.org)
Morten Welinder's avatar
Morten Welinder committed
6
 * Copyright 2013 Morten Welinder (terra@gnome.org)
7
 *
8 9 10 11
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) version 3.
12
 *
13 14 15 16 17 18 19
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * 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
22
 */
23

24
#include <gnumeric-config.h>
Jody Goldberg's avatar
Jody Goldberg committed
25
#include "sheet-style.h"
26
#include "ranges.h"
27
#include "sheet.h"
Jody Goldberg's avatar
Jody Goldberg committed
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"
Morten Welinder's avatar
Morten Welinder committed
35
#include "gutils.h"
36
#include <goffice/goffice.h>
37
#include <glib/gi18n-lib.h>
38
#include <string.h>
39
#include <math.h>
40

41
#define USE_TILE_POOLS 0
Morten Welinder's avatar
Morten Welinder committed
42

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
/* ------------------------------------------------------------------------- */

/*
 * This is, essentially, an std::multiset implementation for the style hash.
 * Note, however, that sh_lookup is based on gnm_style_equal, not gnm_style_eq.
 */
typedef GHashTable GnmStyleHash;

#if 0
/* This is a really crummy hash -- except for forcing collisions.  */
#define gnm_style_hash(st) 0
#endif

static void
sh_remove (GnmStyleHash *h, GnmStyle *st)
{
	guint32 hv = gnm_style_hash (st);
	GSList *l = g_hash_table_lookup (h, GUINT_TO_POINTER (hv));

	g_return_if_fail (l != NULL);

	if (l->data == st) {
		GSList *next = l->next;
		if (next) {
			/* We're removing the first of several elements.  */
			l->next = NULL;
			g_hash_table_replace (h, GUINT_TO_POINTER (hv), next);
		} else {
			/* We're removing the last element.  */
			g_hash_table_remove (h, GUINT_TO_POINTER (hv));
		}
	} else {
		/* We're removing an element that isn't first.  */
		l = g_slist_remove (l, st);
	}
}

static GnmStyle *
sh_lookup (GnmStyleHash *h, GnmStyle *st)
{
	guint32 hv = gnm_style_hash (st);
	GSList *l = g_hash_table_lookup (h, GUINT_TO_POINTER (hv));
	while (l) {
		GnmStyle *st2 = l->data;
		/* NOTE: This uses gnm_style_equal, not gnm_style_eq.  */
		if (gnm_style_equal (st, st2))
			return st2;
		l = l->next;
	}
	return NULL;
}

static void
sh_insert (GnmStyleHash *h, GnmStyle *st)
{
	GSList *s = g_slist_prepend (NULL, st);
	guint32 hv = gnm_style_hash (st);
	GSList *l = g_hash_table_lookup (h, GUINT_TO_POINTER (hv));
	if (l) {
		s->next = l->next;
		l->next = s;
	} else {
		g_hash_table_insert (h, GUINT_TO_POINTER (hv), s);
	}
}

109 110
static GSList *
sh_all_styles (GnmStyleHash *h)
111 112 113
{
	GHashTableIter iter;
	gpointer value;
114
	GSList *res = NULL;
115 116 117 118

	g_hash_table_iter_init (&iter, h);
	while (g_hash_table_iter_next (&iter, NULL, &value)) {
		GSList *l = value;
119 120
		for (; l; l = l->next)
			res = g_slist_prepend (res, l->data);
121
	}
122 123

	return res;
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
}

static GnmStyleHash *
sh_create (void)
{
	return g_hash_table_new_full (g_direct_hash, g_direct_equal,
				      NULL, (GDestroyNotify)g_slist_free);
}

static void
sh_destroy (GnmStyleHash *h)
{
	g_hash_table_destroy (h);
}

/* ------------------------------------------------------------------------- */

141
typedef union _CellTile CellTile;
142
struct _GnmSheetStyleData {
143 144 145 146 147 148
	/*
	 * style_hash is a set of all styles used by this sheet.  These
	 * styles are all linked.
	 *
	 * We always re-use styles from here when we can, but there can
	 * still be duplicates.  This happens when styles are changed
149 150 151
	 * while they are in the hash.  For example, this happens when
	 * an expression used by a validation style changes due to
	 * row/col insert/delete.
152
	 */
153
	GnmStyleHash *style_hash;
154

155
	CellTile   *styles;
156 157
	GnmStyle   *default_style;
	GnmColor   *auto_pattern_color;
158 159
};

160 161 162 163
static gboolean debug_style_optimize;

typedef struct {
	GnmSheetSize const *ss;
164
	gboolean recursion;
165 166 167 168 169 170 171
} CellTileOptimize;

static void
cell_tile_optimize (CellTile **tile, int level, CellTileOptimize *data,
		    int ccol, int crow);


172
/*
173 174 175 176
 * sheet_style_unlink
 * For internal use only
 */
void
Morten Welinder's avatar
Morten Welinder committed
177
sheet_style_unlink (Sheet *sheet, GnmStyle *st)
178
{
179 180
	if (sheet->style_data->style_hash)
		sh_remove (sheet->style_data->style_hash, st);
181 182 183
}

/**
184
 * sheet_style_find:
185
 * @sheet: (transfer full): the sheet
186
 * @st: a style
187
 *
188 189
 * Looks up a style from the sheets collection.  Linking if necessary.
 * ABSORBS the reference and adds a link.
190 191
 *
 * Returns: (transfer full): the new style.
192
 */
193 194
GnmStyle *
sheet_style_find (Sheet const *sheet, GnmStyle *s)
195
{
Morten Welinder's avatar
Morten Welinder committed
196
	GnmStyle *res;
197
	res = sh_lookup (sheet->style_data->style_hash, s);
198
	if (res != NULL) {
199 200
		gnm_style_link (res);
		gnm_style_unref (s);
201
		return res;
202
	}
203

204
	s = gnm_style_link_sheet (s, (Sheet *)sheet);
205 206

	/* Retry the lookup in case "s" changed.  See #585178.  */
207
	res = sh_lookup (sheet->style_data->style_hash, s);
208 209 210 211 212 213 214
	if (res != NULL) {
		gnm_style_link (res);
		/*
		 * We are abandoning the linking here.  We cannot use
		 * gnm_style_unlink as that would call sheet_style_unlink
		 * and thus remove "res" from the hash.
		 */
Morten Welinder's avatar
Morten Welinder committed
215
		gnm_style_abandon_link (s);
216 217 218 219 220
		gnm_style_unref (s);

		return res;
	}

221
	sh_insert (sheet->style_data->style_hash, s);
222 223 224
	return s;
}

225 226
/* Place holder until I merge in the new styles too */
static void
Morten Welinder's avatar
Morten Welinder committed
227
pstyle_set_border (GnmStyle *st, GnmBorder *border,
Morten Welinder's avatar
Morten Welinder committed
228
		   GnmStyleBorderLocation side)
229
{
230
	gnm_style_set_border (st,
Morten Welinder's avatar
Morten Welinder committed
231 232
			      GNM_STYLE_BORDER_LOCATION_TO_STYLE_ELEMENT (side),
			      gnm_style_border_ref (border));
233 234
}

235 236 237 238
/* Amortize the cost of applying a partial style over a large region
 * by caching and rereferencing the merged result for repeated styles.
 */
typedef struct {
239 240
	GnmStyle   *new_style;
	GnmStyle   *pstyle;
241
	GHashTable *cache;
242
	Sheet	   *sheet;
243
} ReplacementStyle;
244

245 246
static void
rstyle_ctor_style (ReplacementStyle *res, GnmStyle *new_style, Sheet *sheet)
247
{
248
	res->sheet = sheet;
249 250 251 252 253 254 255 256 257 258 259 260
	res->new_style = sheet_style_find (sheet, new_style);
	res->pstyle = NULL;
	res->cache = NULL;
}

static void
rstyle_ctor_pstyle (ReplacementStyle *res, GnmStyle *pstyle, Sheet *sheet)
{
	res->sheet = sheet;
	res->new_style = NULL;
	res->pstyle = pstyle;
	res->cache = g_hash_table_new (g_direct_hash, g_direct_equal);
261 262
}

263
static void
264
cb_style_unlink (gpointer key, gpointer value, G_GNUC_UNUSED gpointer user_data)
265
{
266 267
	gnm_style_unlink ((GnmStyle *)key);
	gnm_style_unlink ((GnmStyle *)value);
268 269
}

270
static void
271
rstyle_dtor (ReplacementStyle *rs)
272
{
273
	if (rs->cache != NULL) {
274
		g_hash_table_foreach (rs->cache, cb_style_unlink, NULL);
275 276
		g_hash_table_destroy (rs->cache);
		rs->cache = NULL;
277
	}
278
	if (rs->new_style != NULL) {
279
		gnm_style_unlink (rs->new_style);
280 281 282
		rs->new_style = NULL;
	}
	if (rs->pstyle != NULL) {
283
		gnm_style_unref (rs->pstyle);
284 285
		rs->pstyle = NULL;
	}
286 287
}

288
/*
289
 * rstyle_apply:  Utility routine that is at the core of applying partial
290 291 292
 * styles or storing complete styles.  It will eventually be smarter
 * and will maintain the cache of styles associated with each sheet
 */
293
static void
294
rstyle_apply (GnmStyle **old, ReplacementStyle *rs, GnmRange const *r)
295
{
Morten Welinder's avatar
Morten Welinder committed
296
	GnmStyle *s;
297 298
	g_return_if_fail (old != NULL);
	g_return_if_fail (rs != NULL);
299

300 301 302 303
	if (rs->pstyle != NULL) {
		/* Cache the merged styles keeping a reference to the originals
		 * just in case all instances change.
		 */
304
		s = g_hash_table_lookup (rs->cache, *old);
305
		if (s == NULL) {
Jody Goldberg's avatar
Jody Goldberg committed
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);
310
		}
311 312
	} else
		s = rs->new_style;
313

314
	if (*old != s) {
315
		if (*old) {
316
			gnm_style_unlink_dependents (*old, r);
317
			gnm_style_unlink (*old);
318 319
		}

320
		gnm_style_link_dependents (s, r);
321 322
		gnm_style_link (s);

323 324
		*old = s;
	}
325 326
}

327 328 329 330 331 332
void
sheet_style_clear_style_dependents (Sheet *sheet, GnmRange const *r)
{
	GSList *styles = sh_all_styles (sheet->style_data->style_hash);
	g_slist_foreach (styles,
			 (GFunc)gnm_style_unlink_dependents,
333
			 (gpointer)r);
334 335 336 337
	g_slist_free (styles);
}


338
/****************************************************************************/
339

340
/* If you change this, change the tile_{widths,heights} here
Morten Welinder's avatar
Morten Welinder committed
341
 * and GNM_MAX_COLS and GNM_MAX_ROWS in gnumeric.h */
342
#define TILE_TOP_LEVEL 6
343

344
#define TILE_SIZE_COL 8
Morten Welinder's avatar
Morten Welinder committed
345
#define	TILE_SIZE_ROW 16
346

347 348 349 350 351 352 353 354
typedef enum {
	TILE_UNDEFINED	= -1,
	TILE_SIMPLE	=  0,
	TILE_COL	=  1,
	TILE_ROW	=  2,
	TILE_MATRIX	=  3,
	TILE_PTR_MATRIX	=  4
} CellTileType;
355
static int const tile_size[/*type*/] = {
356 357 358 359 360
	1,				/* TILE_SIMPLE */
	TILE_SIZE_COL,			/* TILE_COL */
	TILE_SIZE_ROW,			/* TILE_ROW */
	TILE_SIZE_COL * TILE_SIZE_ROW	/* TILE_MATRIX */
};
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
static int const tile_col_count[/*type*/] = {
	1,				/* TILE_SIMPLE */
	TILE_SIZE_COL,			/* TILE_COL */
	1,				/* TILE_ROW */
	TILE_SIZE_COL,			/* TILE_MATRIX */
	TILE_SIZE_COL			/* TILE_PTR_MATRIX */
};
static int const tile_row_count[/*type*/] = {
	1,				/* TILE_SIMPLE */
	1,				/* TILE_COL */
	TILE_SIZE_ROW,			/* TILE_ROW */
	TILE_SIZE_ROW,			/* TILE_MATRIX */
	TILE_SIZE_ROW			/* TILE_PTR_MATRIX */
};
static const char * const tile_type_str[/*type*/] = {
376 377
	"simple", "col", "row", "matrix", "ptr-matrix"
};
378
static int const tile_widths[/*level*/] = {
379 380 381 382
	1,
	TILE_SIZE_COL,
	TILE_SIZE_COL * TILE_SIZE_COL,
	TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL,
383 384
	TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL,
	TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL,
385 386
	TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL,
	TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL * TILE_SIZE_COL
387
};
388
static int const tile_heights[/*level*/] = {
389 390 391 392
	1,
	TILE_SIZE_ROW,
	TILE_SIZE_ROW * TILE_SIZE_ROW,
	TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW,
393 394
	TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW,
	TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW,
395 396
	TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW,
	TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW * TILE_SIZE_ROW
397
};
398

399 400
typedef struct {
	CellTileType const type;
Morten Welinder's avatar
Morten Welinder committed
401
	GnmStyle *style[1];
402 403 404
} CellTileStyleSimple;
typedef struct {
	CellTileType const type;
Morten Welinder's avatar
Morten Welinder committed
405
	GnmStyle *style[TILE_SIZE_COL];
406 407 408
} CellTileStyleCol;
typedef struct {
	CellTileType const type;
Morten Welinder's avatar
Morten Welinder committed
409
	GnmStyle *style[TILE_SIZE_ROW];
410 411 412
} CellTileStyleRow;
typedef struct {
	CellTileType const type;
Morten Welinder's avatar
Morten Welinder committed
413
	GnmStyle *style[TILE_SIZE_COL * TILE_SIZE_ROW];
414 415 416
} CellTileStyleMatrix;
typedef struct {
	CellTileType const type;
Morten Welinder's avatar
Morten Welinder committed
417
	CellTile	*ptr[TILE_SIZE_COL * TILE_SIZE_ROW];
418 419 420 421 422 423 424 425 426 427 428
} CellTilePtrMatrix;

union _CellTile {
	CellTileType const type;
	CellTileStyleSimple	style_any;
	CellTileStyleSimple	style_simple;
	CellTileStyleCol	style_col;
	CellTileStyleRow	style_row;
	CellTileStyleMatrix	style_matrix;
	CellTilePtrMatrix	ptr_matrix;
};
429

430
static int active_sheet_count;
Morten Welinder's avatar
Morten Welinder committed
431
#if USE_TILE_POOLS
432
static GOMemChunk *tile_pools[5];
433 434 435 436 437 438 439 440 441 442 443 444 445 446
#define CHUNK_ALLOC(T,ctt) ((T*)go_mem_chunk_alloc (tile_pools[(ctt)]))
#define CHUNK_FREE(ctt,v) go_mem_chunk_free (tile_pools[(ctt)], (v))
#else
static const size_t tile_type_sizeof[5] = {
	sizeof (CellTileStyleSimple),
	sizeof (CellTileStyleCol),
	sizeof (CellTileStyleRow),
	sizeof (CellTileStyleMatrix),
	sizeof (CellTilePtrMatrix)
};
static int tile_allocations = 0;
#if 1
#define CHUNK_ALLOC(T,ctt) (tile_allocations++, (T*)g_slice_alloc (tile_type_sizeof[(ctt)]))
#define CHUNK_FREE(ctt,v) (tile_allocations--, g_slice_free1 (tile_type_sizeof[(ctt)], (v)))
Morten Welinder's avatar
Morten Welinder committed
447
#else
448 449 450
#define CHUNK_ALLOC(T,ctt) (tile_allocations++, (T*)g_malloc (tile_type_sizeof[(ctt)]))
#define CHUNK_FREE(ctt,v) (tile_allocations--, g_free ((v)))
#endif
Morten Welinder's avatar
Morten Welinder committed
451 452 453
#endif


454 455 456 457 458
/*
 * Destroy a CellTile (recursively if needed).  This will unlink all the
 * styles in it.  We do _not_ unlink style dependents here.  That is done
 * only in rstyle_apply.
 */
459
static void
460
cell_tile_dtor (CellTile *tile)
461
{
462
	CellTileType t;
463

464
	g_return_if_fail (tile != NULL);
465

466 467 468 469
	t = tile->type;
	if (t == TILE_PTR_MATRIX) {
		int i = TILE_SIZE_COL * TILE_SIZE_ROW;
		while (--i >= 0) {
Morten Welinder's avatar
Morten Welinder committed
470 471
			cell_tile_dtor (tile->ptr_matrix.ptr[i]);
			tile->ptr_matrix.ptr[i] = NULL;
472
		}
473
	} else if (TILE_SIMPLE <= t && t <= TILE_MATRIX) {
Morten Welinder's avatar
Morten Welinder committed
474
		int i = tile_size[t];
475
		while (--i >= 0) {
Morten Welinder's avatar
Morten Welinder committed
476 477
			gnm_style_unlink (tile->style_any.style[i]);
			tile->style_any.style[i] = NULL;
478
		}
479 480
	} else {
		g_return_if_fail (FALSE); /* don't free anything */
481 482
	}

483
	*((CellTileType *)&(tile->type)) = TILE_UNDEFINED; /* poison it */
484
	CHUNK_FREE (t, tile);
485 486
}

487
static CellTile *
Morten Welinder's avatar
Morten Welinder committed
488
cell_tile_style_new (GnmStyle *style, CellTileType t)
489
{
490
	CellTile *res = CHUNK_ALLOC (CellTile, t);
491 492 493
	*((CellTileType *)&(res->type)) = t;

	if (style != NULL) {
Morten Welinder's avatar
Morten Welinder committed
494
		int i = tile_size[t];
495
		gnm_style_link_multiple (style, i);
496
		while (--i >= 0)
Morten Welinder's avatar
Morten Welinder committed
497
			res->style_any.style[i] = style;
498
	}
499

500
	return res;
501 502
}

503 504
static CellTile *
cell_tile_ptr_matrix_new (CellTile *t)
505
{
506
	CellTilePtrMatrix *res;
507

508 509 510
	g_return_val_if_fail (t != NULL, NULL);
	g_return_val_if_fail (TILE_SIMPLE <= t->type &&
			      TILE_MATRIX >= t->type, NULL);
511

512
	res = CHUNK_ALLOC (CellTilePtrMatrix, TILE_PTR_MATRIX);
513
	*((CellTileType *)&(res->type)) = TILE_PTR_MATRIX;
514

515
	/* TODO:
Jody Goldberg's avatar
Jody Goldberg committed
516 517 518
	 * If we wanted to get fancy we could use self similarity to decrease
	 * the number of subtiles.  However, this would increase the cost of
	 * applying changes later so I'm not sure it is worth the effort.
519 520
	 */
	switch (t->type) {
521
	case TILE_SIMPLE: {
522 523
		int i = TILE_SIZE_COL * TILE_SIZE_ROW;
		while (--i >= 0)
Morten Welinder's avatar
Morten Welinder committed
524 525
			res->ptr[i] = cell_tile_style_new (
				t->style_simple.style[0], TILE_SIMPLE);
526
		break;
527
	}
528
	case TILE_COL: {
529 530 531
		int i, r, c;
		for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r)
			for (c = 0 ; c < TILE_SIZE_COL ; ++c)
Morten Welinder's avatar
Morten Welinder committed
532 533
				res->ptr[i++] = cell_tile_style_new (
					t->style_col.style[c], TILE_SIMPLE);
534
		break;
535
	}
536
	case TILE_ROW: {
537 538 539
		int i, r, c;
		for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r)
			for (c = 0 ; c < TILE_SIZE_COL ; ++c)
Morten Welinder's avatar
Morten Welinder committed
540 541
				res->ptr[i++] = cell_tile_style_new (
					t->style_row.style[r], TILE_SIMPLE);
542
		break;
543
	}
544
	case TILE_MATRIX: {
545 546
		int i = TILE_SIZE_COL * TILE_SIZE_ROW;
		while (--i >= 0)
Morten Welinder's avatar
Morten Welinder committed
547 548
			res->ptr[i] = cell_tile_style_new (
				t->style_matrix.style[i], TILE_SIMPLE);
549 550
		break;
	}
551
	default: ;
552
	}
553

554
	return (CellTile *)res;
555 556
}

557
static CellTile *
558
cell_tile_matrix_set (CellTile *t)
559
{
560
	int r, c;
561 562 563 564 565 566
	CellTileStyleMatrix *res;

	g_return_val_if_fail (t != NULL, NULL);
	g_return_val_if_fail (TILE_SIMPLE <= t->type &&
			      TILE_MATRIX >= t->type, NULL);

567 568 569 570
	if (t->type == TILE_MATRIX)
		return t;

	res = (CellTileStyleMatrix *)cell_tile_style_new (NULL, TILE_MATRIX);
571 572

	switch (t->type) {
573 574 575 576
	case TILE_SIMPLE: {
		GnmStyle *tmp = t->style_simple.style[0];
		int i = TILE_SIZE_COL * TILE_SIZE_ROW;
		gnm_style_link_multiple (tmp, i);
577
		while (--i >= 0)
Morten Welinder's avatar
Morten Welinder committed
578
			res->style[i] = tmp;
579
		break;
580
	}
581

582 583 584 585
	case TILE_COL: {
		int i = 0;
		for (r = 0; r < TILE_SIZE_ROW; ++r)
			for (c = 0; c < TILE_SIZE_COL; ++c)
Morten Welinder's avatar
Morten Welinder committed
586
				gnm_style_link (res->style[i++] =
587
						t->style_col.style[c]);
588
		break;
589 590 591 592 593 594 595 596
	}

	case TILE_ROW: {
		int i = 0;
		for (r = 0; r < TILE_SIZE_ROW; ++r) {
			GnmStyle *tmp = t->style_row.style[r];
			gnm_style_link_multiple (tmp, TILE_SIZE_COL);
			for (c = 0; c < TILE_SIZE_COL; ++c)
Morten Welinder's avatar
Morten Welinder committed
597
				res->style[i++] = tmp;
598
		}
599
		break;
600 601 602 603
	}

	case TILE_MATRIX:
	default:
604
		g_assert_not_reached();
605
	}
606

607
	cell_tile_dtor (t);
608

609
	return (CellTile *)res;
610 611
}

612
/****************************************************************************/
613

614 615 616 617 618 619
static void
sheet_style_sanity_check (void)
{
	unsigned c, r;
	int i;

620
	for (c = 1, i = 0; i <= TILE_TOP_LEVEL; i++) {
621 622 623 624 625
		g_assert (c < G_MAXUINT / TILE_SIZE_COL);
		c *= TILE_SIZE_COL;
	}
	g_assert (c >= GNM_MAX_COLS);

626
	for (r = 1, i = 0; i <= TILE_TOP_LEVEL; i++) {
627 628 629 630 631
		g_assert (r < G_MAXUINT / TILE_SIZE_COL);
		r *= TILE_SIZE_ROW;
	}
	g_assert (r >= GNM_MAX_ROWS);

632
	g_assert (G_N_ELEMENTS (tile_heights) > TILE_TOP_LEVEL + 1);
633

634
	g_assert (G_N_ELEMENTS (tile_widths) > TILE_TOP_LEVEL + 1);
635 636
}

637 638
static void
sheet_style_init_size (Sheet *sheet, int cols, int rows)
639
{
640
	GnmStyle *default_style;
641
	int lc = 0, lr = 0, w = TILE_SIZE_COL, h = TILE_SIZE_ROW;
642

643
	while (w < cols) {
644
		w *= TILE_SIZE_COL;
645
		lc++;
646
	}
647
	while (h < rows) {
648
		h *= TILE_SIZE_ROW;
649
		lr++;
650
	}
651 652
	sheet->tile_top_level = MAX (lc, lr);

653
	if (active_sheet_count++ == 0) {
Morten Welinder's avatar
Morten Welinder committed
654 655
#if USE_TILE_POOLS
		tile_pools[TILE_SIMPLE] =
656
			go_mem_chunk_new ("simple tile pool",
Morten Welinder's avatar
Morten Welinder committed
657 658 659
					   sizeof (CellTileStyleSimple),
					   16 * 1024 - 128);
		tile_pools[TILE_COL] =
660
			go_mem_chunk_new ("column tile pool",
Morten Welinder's avatar
Morten Welinder committed
661 662 663
					   sizeof (CellTileStyleCol),
					   16 * 1024 - 128);
		tile_pools[TILE_ROW] =
664
			go_mem_chunk_new ("row tile pool",
Morten Welinder's avatar
Morten Welinder committed
665 666 667
					   sizeof (CellTileStyleRow),
					   16 * 1024 - 128);
		tile_pools[TILE_MATRIX] =
668
			go_mem_chunk_new ("matrix tile pool",
Morten Welinder's avatar
Morten Welinder committed
669 670 671 672 673 674 675 676
					   sizeof (CellTileStyleMatrix),
					   MAX (16 * 1024 - 128,
						100 * sizeof (CellTileStyleMatrix)));

		/* If this fails one day, just make two pools.  */
		g_assert (sizeof (CellTileStyleMatrix) == sizeof (CellTilePtrMatrix));
		tile_pools[TILE_PTR_MATRIX] = tile_pools[TILE_MATRIX];
#endif
677
	}
Morten Welinder's avatar
Morten Welinder committed
678

679
	sheet->style_data = g_new (GnmSheetStyleData, 1);
680
	sheet->style_data->style_hash = sh_create ();
Morten Welinder's avatar
Morten Welinder committed
681

682
	sheet->style_data->auto_pattern_color = style_color_auto_pattern ();
683

684
	default_style =  gnm_style_new_default ();
685 686 687 688
#if 0
	/* We can not do this, XL creates full page charts with background
	 * 'none' by default.  Then displays that as white. */
	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);
692 693
	}
#endif
694
	sheet->style_data->default_style =
695
		sheet_style_find (sheet, default_style);
696 697 698
	sheet->style_data->styles =
		cell_tile_style_new (sheet->style_data->default_style,
				     TILE_SIMPLE);
699 700
}

701 702 703 704 705
void
sheet_style_init (Sheet *sheet)
{
	int cols = gnm_sheet_get_max_cols (sheet);
	int rows = gnm_sheet_get_max_rows (sheet);
706

707 708
	debug_style_optimize = gnm_debug_flag ("style-optimize");

709 710
	sheet_style_sanity_check ();

711 712 713 714 715 716 717 718 719 720 721 722 723
	sheet_style_init_size (sheet, cols, rows);
}

void
sheet_style_resize (Sheet *sheet, int cols, int rows)
{
	GnmStyleList *styles, *l;
	int old_cols = gnm_sheet_get_max_cols (sheet);
	int old_rows = gnm_sheet_get_max_rows (sheet);
	GnmRange save_range, new_full;

	/* Save the style for the surviving area.  */
	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);
726 727 728 729 730 731

	/* Build new empty structures.  */
	sheet_style_shutdown (sheet);
	sheet_style_init_size (sheet, cols, rows);

	/* Reapply styles.  */
Morten Welinder's avatar
Morten Welinder committed
732
	range_init (&new_full, 0, 0, cols - 1, rows - 1);
733 734 735 736 737
	for (l = styles; l; l = l->next) {
		GnmStyleRegion const *sr = l->data;
		GnmRange const *r = &sr->range;
		GnmStyle *style = sr->style;
		GnmRange newr;
Morten Welinder's avatar
Morten Welinder committed
738 739
		if (range_intersection (&newr, r, &new_full))
			sheet_style_apply_range2 (sheet, &newr, style);
740 741 742 743 744
	}

	style_list_free	(styles);
}

Morten Welinder's avatar
Morten Welinder committed
745 746 747 748 749
#if USE_TILE_POOLS
static void
cb_tile_pool_leak (gpointer data, gpointer user)
{
	CellTile *tile = data;
750
	g_printerr ("Leaking tile at %p.\n", (void *)tile);
Morten Welinder's avatar
Morten Welinder committed
751 752 753
}
#endif

754
void
755
sheet_style_shutdown (Sheet *sheet)
756
{
757
	GnmStyleHash *table;
758
	GnmRange r;
759

760
	g_return_if_fail (IS_SHEET (sheet));
761
	g_return_if_fail (sheet->style_data != NULL);
762

763 764 765 766 767 768 769
	/*
	 * Clear all styles.  This is an easy way to clear out all
	 * style dependencies.
	 */
	range_init_full_sheet (&r, sheet);
	sheet_style_set_range (sheet, &r, sheet_style_default (sheet));

770 771
	cell_tile_dtor (sheet->style_data->styles);
	sheet->style_data->styles = NULL;
772

773 774 775 776 777 778 779 780
	sheet->style_data->default_style = NULL;

	/* Clear the pointer to the hash BEFORE clearing and add a test in
	 * sheet_style_unlink.  If we don't then it is possible/probable that
	 * unlinking the styles will attempt to remove them from the hash while
	 * we are walking it.
	 */
	table = sheet->style_data->style_hash;
781
	sheet->style_data->style_hash = NULL;
782 783
	g_slist_free_full (sh_all_styles (table),
			   (GDestroyNotify)gnm_style_unlink);
784
	sh_destroy (table);
785
	style_color_unref (sheet->style_data->auto_pattern_color);
786

787 788
	g_free (sheet->style_data);
	sheet->style_data = NULL;
Morten Welinder's avatar
Morten Welinder committed
789

790
	if (--active_sheet_count == 0) {
Morten Welinder's avatar
Morten Welinder committed
791
#if USE_TILE_POOLS
792
		go_mem_chunk_foreach_leak (tile_pools[TILE_SIMPLE],
Morten Welinder's avatar
Morten Welinder committed
793
					    cb_tile_pool_leak, NULL);
794
		go_mem_chunk_destroy (tile_pools[TILE_SIMPLE], FALSE);
Morten Welinder's avatar
Morten Welinder committed
795 796
		tile_pools[TILE_SIMPLE] = NULL;

797
		go_mem_chunk_foreach_leak (tile_pools[TILE_COL],
Morten Welinder's avatar
Morten Welinder committed
798
					    cb_tile_pool_leak, NULL);
799
		go_mem_chunk_destroy (tile_pools[TILE_COL], FALSE);
Morten Welinder's avatar
Morten Welinder committed
800 801
		tile_pools[TILE_COL] = NULL;

802
		go_mem_chunk_foreach_leak (tile_pools[TILE_ROW],
Morten Welinder's avatar
Morten Welinder committed
803
					    cb_tile_pool_leak, NULL);
804
		go_mem_chunk_destroy (tile_pools[TILE_ROW], FALSE);
Morten Welinder's avatar
Morten Welinder committed
805 806
		tile_pools[TILE_ROW] = NULL;

807
		go_mem_chunk_foreach_leak (tile_pools[TILE_MATRIX],
Morten Welinder's avatar
Morten Welinder committed
808
					    cb_tile_pool_leak, NULL);
809
		go_mem_chunk_destroy (tile_pools[TILE_MATRIX], FALSE);
Morten Welinder's avatar
Morten Welinder committed
810 811 812 813 814
		tile_pools[TILE_MATRIX] = NULL;

		/* If this fails one day, just make two pools.  */
		g_assert (sizeof (CellTileStyleMatrix) == sizeof (CellTilePtrMatrix));
		tile_pools[TILE_PTR_MATRIX] = NULL;
815 816 817
#else
		if (tile_allocations)
			g_printerr ("Leaking %d style tiles.\n", tile_allocations);
Morten Welinder's avatar
Morten Welinder committed
818
#endif
819
	}
820
}
821

822
/**
823
 * sheet_style_set_auto_pattern_color:
Morten Welinder's avatar
Morten Welinder committed
824
 * @sheet: The sheet
825
 * @grid_color: (transfer full): The color
826 827
 *
 * Set the color for rendering auto colored patterns in this sheet.
828 829
 * Absorbs a reference to @pattern_color;
 **/
830
void
831
sheet_style_set_auto_pattern_color (Sheet *sheet, GnmColor *pattern_color)
832 833 834 835
{
	g_return_if_fail (IS_SHEET (sheet));
	g_return_if_fail (sheet->style_data != NULL);

836 837
	style_color_unref (sheet->style_data->auto_pattern_color);
	sheet->style_data->auto_pattern_color = gnm_color_new_auto (pattern_color->go_color);
838
	style_color_unref (pattern_color);
839 840 841 842 843
}

/**
 * sheet_style_get_auto_pattern_color:
 * @sheet: the sheet
844
 *
845 846
 * Returns: (transfer full): the color for rendering auto colored patterns
 * in this sheet.
847
 **/
Morten Welinder's avatar
Morten Welinder committed
848
GnmColor *
849
sheet_style_get_auto_pattern_color (Sheet const *sheet)
850
{
Morten Welinder's avatar
Morten Welinder committed
851
	GnmColor *sc;
852 853 854 855 856 857 858 859 860 861 862
	g_return_val_if_fail (IS_SHEET (sheet), style_color_black ());
	g_return_val_if_fail (sheet->style_data != NULL, style_color_black ());
	g_return_val_if_fail (sheet->style_data->auto_pattern_color != NULL,
			      style_color_black ());

	sc = sheet->style_data->auto_pattern_color;
	style_color_ref (sc);

	return sc;
}

863
/**
864 865
 * sheet_style_update_grid_color:
 *
Morten Welinder's avatar
Morten Welinder committed
866 867
 * This function updates the color of gnm_style_border_none when the sheet to be
 * rendered is known. gnm_style_border_none tells how to render the
868 869 870 871 872 873 874 875 876 877
 * grid. Because the grid color may be different for different sheets, the
 * functions which render the grid call this function first.  The rule for
 * selecting the grid color, which is the same as in Excel, is: - if the
 * auto pattern color is default (which is black), the grid color is gray,
 * as returned by style_color_grid ().  - otherwise, the auto pattern color
 * is used for the grid.
 */
void
sheet_style_update_grid_color (Sheet const *sheet)
{
Morten Welinder's avatar
Morten Welinder committed
878 879 880 881
	GnmColor *default_auto = style_color_auto_pattern ();
	GnmColor *sheet_auto = sheet_style_get_auto_pattern_color (sheet);
	GnmColor *grid_color = style_color_grid ();
	GnmColor *new_color;
882 883

	new_color = (style_color_equal (default_auto, sheet_auto)
884
		     ? grid_color : sheet_auto);
885 886

	/* Do nothing if we already have the right color */
Morten Welinder's avatar
Morten Welinder committed
887
	if (gnm_style_border_none()->color != new_color) {
888
		style_color_ref (new_color); /* none_set eats the ref */
Morten Welinder's avatar
Morten Welinder committed
889
		gnm_style_border_none_set_color (new_color);
890
	}
Jody Goldberg's avatar
Jody Goldberg committed
891
	style_color_unref (grid_color);
892 893 894 895
	style_color_unref (sheet_auto);
	style_color_unref (default_auto);
}

896
/****************************************************************************/
897

898 899
static gboolean
tile_is_uniform (CellTile const *tile)
900
{
901 902
	const int s = tile_size[tile->type];
	GnmStyle const *st = tile->style_any.style[0];
903 904
	int i;

905 906 907
	for (i = 1; i < s; i++)
		if (tile->style_any.style[i] != st)
			return FALSE;
908

909 910 911 912
	return TRUE;
}

static void
913
vector_apply_pstyle (CellTile *tile, ReplacementStyle *rs,
914
		     int cc, int cr, int level, GnmRange const *indic)
915
{
916 917 918
	const CellTileType type = tile->type;
	const int ncols = tile_col_count[type];
	const int nrows = tile_row_count[type];
919 920
	const int w1 = tile_widths[level + 1] / ncols;
	const int h1 = tile_heights[level + 1] / nrows;
921 922 923 924
	const int fcol = indic->start.col;
	const int frow = indic->start.row;
	const int lcol = MIN (ncols - 1, indic->end.col);
	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;
928

929 930
	for (r = frow; r <= lrow; r++) {
		GnmStyle **st = tile->style_any.style + ncols * r;
931 932 933
		rng.start.row = cr + h1 * r;
		rng.end.row = MIN (rng.start.row + (h1 - 1),
				   ss->max_rows - 1);
934
		for (c = fcol; c <= lcol; c++) {
935 936 937 938
			rng.start.col = cc + w1 * c;
			rng.end.col = MIN (rng.start.col + (w1 - 1),
					   ss->max_cols - 1);
			rstyle_apply (st + c, rs, &rng);
939 940
		}
	}
941 942
}

943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958
/*
 * Determine whether before applying a style in the area of apply_to
 * one needs to split the tile column-wise.
 *
 * If FALSE is returned then the tile need to be split to a TILE_PTR_MATRIX
 * because the current level is not fine-grained enough.
 *
 * If TRUE is returned, TILE_SIMPLE needs to be split into TILE_COL and
 * TILE_ROW needs to be split into TILE_MATRIX.  TILE_COL and TILE_MATRIX
 * should be kept.  In indic, the inclusive post-split indicies of the
 * range will be returned.
 *
 * If apply_to covers the entire tile, TRUE will be returned and the judgement
 * on splitting above should be ignored.  The indices in indic will be as-if
 * the split was done.
 */
959
static gboolean
Jody Goldberg's avatar
Jody Goldberg committed
960
col_indicies (int corner_col, int w, GnmRange const *apply_to,
961
	      GnmRange *indec)
962
{
963
	int i, tmp;
964

965
	i = apply_to->start.col - corner_col;
966
	if (i <= 0)
967
		indec->start.col = 0;
968 969 970 971
	else {
		tmp = i / w;
		if (i != tmp * w)
			return FALSE;
972
		indec->start.col = tmp;
973
	}
974

975 976
	i = 1 + apply_to->end.col - corner_col;
	tmp = i / w;
977
	if (tmp >= TILE_SIZE_COL)
978
		indec->end.col = TILE_SIZE_COL - 1;
979 980 981
	else {
		if (i != tmp * w)
			return FALSE;
982
		indec->end.col = tmp - 1;
983
	}
984

985
	return TRUE;
986
}
987

988
/* See docs for col_indicies.  Swap cols and rows.  */
989
static gboolean
Jody Goldberg's avatar
Jody Goldberg committed
990
row_indicies (int corner_row, int h, GnmRange const *apply_to,
991
	      GnmRange *indic)
992
{
993
	int i, tmp;
994

995
	i = apply_to->start.row - corner_row;
996
	if (i <= 0)
997
		indic->start.row = 0;
998 999 1000 1001
	else {
		int tmp = i / h;
		if (i != tmp * h)
			return FALSE;
1002
		indic->start.row = tmp;
1003
	}
1004

1005 1006
	i = 1 + apply_to->end.row - corner_row;
	tmp = i / h;
1007
	if (tmp >= TILE_SIZE_ROW)
1008
		indic->end.row = TILE_SIZE_ROW - 1;
1009 1010 1011
	else {
		if (i != tmp * h)
			return FALSE;
1012
		indic->end.row = tmp - 1;
1013
	}
1014

1015
	return TRUE;
1016
}
1017

1018
/*
1019
 * cell_tile_apply: This is the primary logic for making changing areas in the
1020 1021
 * tree.  It could be further optimised if it becomes a bottle neck.
 */
1022
static void
1023 1024
cell_tile_apply (CellTile **tile, int level,
		 int corner_col, int corner_row,
Jody Goldberg's avatar
Jody Goldberg committed
1025
		 GnmRange const *apply_to,
1026
		 ReplacementStyle *rs)
1027
{
Morten Welinder's avatar
Morten Welinder committed
1028 1029 1030 1031
	int const width = tile_widths[level+1];
	int const height = tile_heights[level+1];
	int const w = tile_widths[level];
	int const h = tile_heights[level];
1032 1033 1034 1035
	gboolean const full_width = (apply_to->start.col <= corner_col &&
				     apply_to->end.col >= (corner_col+width-1));
	gboolean const full_height = (apply_to->start.row <= corner_row &&
				      apply_to->end.row >= (corner_row+height-1));
Jody Goldberg's avatar
Jody Goldberg committed
1036
	GnmRange indic;
1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047
	CellTileType type;
	int c, r, i;

	g_return_if_fail (TILE_TOP_LEVEL >= level && level >= 0);
	g_return_if_fail (tile != NULL);
	g_return_if_fail (*tile != NULL);

	type = (*tile)->type;
	g_return_if_fail (TILE_SIMPLE <= type && type <= TILE_PTR_MATRIX);

	/* applying the same style to part of a simple-tile is a nop */
1048
	if (type == TILE_SIMPLE &&
Morten Welinder's avatar
Morten Welinder committed
1049
	    (*tile)->style_simple.style[0] == rs->new_style)
1050
		return;
1051

1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078
	/*
	 * Indices for the whole tile assuming a split to matrix.
	 * We can still use these indices if we don't split either way.
	 */
	indic.start.col = 0;
	indic.start.row = 0;
	indic.end.col = TILE_SIZE_COL - 1;
	indic.end.row = TILE_SIZE_ROW - 1;

	if (type == TILE_PTR_MATRIX)
		goto drill_down;
	else if (full_width && full_height)
		goto apply;
	else if (full_height) {
		if (!col_indicies (corner_col, w, apply_to, &indic))
			goto split_to_ptr_matrix;

		switch (type) {
		case TILE_SIMPLE: {
			CellTile *res;
			type = TILE_COL;
			res = cell_tile_style_new (
				(*tile)->style_simple.style[0],
				type);
			cell_tile_dtor (*tile);
			*tile = res;
			/* Fall through */
1079
		}
1080 1081 1082 1083 1084 1085 1086
		case TILE_COL:
		case TILE_MATRIX:
			goto apply;
		case TILE_ROW:
			goto split_to_matrix;
		default:
			g_assert_not_reached ();
1087 1088
		}
	} else if (full_width) {
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101
		if (!row_indicies (corner_row, h, apply_to, &indic))
			goto split_to_ptr_matrix;
		switch (type) {
		case TILE_SIMPLE: {
			CellTile *res;

			type = TILE_ROW;
			res = cell_tile_style_new (
				(*tile)->style_simple.style[0],
				type);
			cell_tile_dtor (*tile);
			*tile = res;
			/* Fall through */
1102
		}
1103 1104 1105 1106 1107 1108 1109
		case TILE_ROW:
		case TILE_MATRIX:
			goto apply;
		case TILE_COL:
			goto split_to_matrix;
		default:
			g_assert_not_reached ();
1110
		}
1111 1112 1113 1114 1115 1116
	} else {
		if (col_indicies (corner_col, w, apply_to, &indic) &&
		    row_indicies (corner_row, h, apply_to, &indic))
			goto split_to_matrix;
		else
			goto split_to_ptr_matrix;
1117
	}
1118

1119 1120 1121 1122 1123 1124
	g_assert_not_reached ();

split_to_matrix:
	*tile = cell_tile_matrix_set (*tile);

apply:
1125
	vector_apply_pstyle (*tile, rs, corner_col, corner_row, level, &indic);
1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143

try_optimize:
	{
		CellTileOptimize cto;
		cto.ss = gnm_sheet_get_size (rs->sheet);
		cto.recursion = FALSE;
		cell_tile_optimize (tile, level, &cto, corner_col, corner_row);
	}
	return;

split_to_ptr_matrix:
	/*
	 * We get here when apply_to's corners are not on a TILE_MATRIX grid.
	 * Split to pointer matrix whose element tiles will have a finer grid.
	 */
	g_return_if_fail (type != TILE_PTR_MATRIX);
	{
		CellTile *res = cell_tile_ptr_matrix_new (*tile);
1144 1145
		cell_tile_dtor (*tile);
		*tile = res;
1146
		type = TILE_PTR_MATRIX;
1147
	}
1148

1149
drill_down:
1150 1151 1152 1153 1154 1155 1156
	g_return_if_fail (type == TILE_PTR_MATRIX);
	for (i = r = 0 ; r < TILE_SIZE_ROW ; ++r, i += TILE_SIZE_COL) {
		int const cr = corner_row + h*r;
		if (cr > apply_to->end.row)
			break;
		if ((cr + h) <= apply_to->start.row)
			continue;
1157

1158 1159 1160 1161 1162 1163
		for (c = 0 ; c < TILE_SIZE_COL ; ++c) {
			int const cc = corner_col + w*c;
			if (cc > apply_to->end.col)
				break;
			if ((cc + w) <= apply_to->start.col)
				continue;
1164

1165
			cell_tile_apply ((*tile)->ptr_matrix.ptr + i + c,
1166
					 level - 1, cc, cr, apply_to, rs);
1167
		}
1168
	}
1169
	goto try_optimize;
1170 1171
}

1172 1173 1174 1175 1176
/* Handler for foreach_tile.
 *
 * "width" and "height" refer to tile size which may extend beyond
 * the range supplied to foreach_tile and even beyond the sheet.
 */
1177
typedef void (*ForeachTileFunc) (GnmStyle *style,
Morten Welinder's avatar
Morten Welinder committed
1178 1179
				 int corner_col, int corner_row,
				 int<