1 /*
2  * mstyle.c: Storing a style
3  *
4  * Authors:
5  *   Michael Meeks <mmeeks@gnu.org>
6  *   Almer S. Tigelaar <almer@gnome.org>
7  *   Jody Goldberg <jody@gnome.org>
8  *   Morten Welinder <terra@gnome.org>
9  */
10 #include <gnumeric-config.h>
11 #include <gnumeric.h>
12 #include <style.h>
13 
14 #include <sheet-style.h>
15 #include <style-border.h>
16 #include <style-font.h>
17 #include <style-color.h>
18 #include <style-conditions.h>
19 #include <sheet-conditions.h>
20 #include <validation.h>
21 #include <pattern.h>
22 #include <hlink.h>
23 #include <input-msg.h>
24 #include <application.h>
25 #include <parse-util.h>
26 #include <expr.h>
27 #include <value.h>
28 #include <gutils.h>
29 #include <ranges.h>
30 #include <sheet.h>
31 #include <gnumeric-conf.h>
32 #include <goffice/goffice.h>
33 #include <string.h>
34 
35 static gboolean debug_style_deps;
36 
37 #define DEBUG_STYLES
38 #ifndef USE_MSTYLE_POOL
39 #define USE_MSTYLE_POOL 1
40 #endif
41 
42 #if USE_MSTYLE_POOL
43 /* Memory pool for GnmStyles.  */
44 static GOMemChunk *gnm_style_pool;
45 #define CHUNK_ALLOC(T,p) ((T*)go_mem_chunk_alloc (p))
46 #define CHUNK_ALLOC0(T,p) ((T*)go_mem_chunk_alloc0 (p))
47 #define CHUNK_FREE(p,v) go_mem_chunk_free ((p), (v))
48 #else
49 #define CHUNK_ALLOC(T,c) g_new (T,1)
50 #define CHUNK_ALLOC0(T,c) g_new0 (T,1)
51 #define CHUNK_FREE(p,v) g_free ((v))
52 #endif
53 
54 
55 struct _GnmStyle {
56 	unsigned int	changed;
57 	unsigned int	set;
58 
59 	unsigned int    hash_key;
60 	unsigned int    hash_key_xl;
61 	unsigned int    ref_count;
62 	unsigned int    link_count;
63 	Sheet	       *linked_sheet;
64 
65 	PangoAttrList *pango_attrs;
66 	double         pango_attrs_zoom;
67 	int            pango_attrs_height;
68 
69 	GnmFont       *font;
70 	PangoContext  *font_context;
71 
72 /* public */
73 	struct _GnmStyleColor {
74 		GnmColor *font;
75 		GnmColor *back;
76 		GnmColor *pattern;
77 	} color;
78 	GnmBorder	*borders[MSTYLE_BORDER_DIAGONAL - MSTYLE_BORDER_TOP + 1];
79 	guint32          pattern;
80 
81 	/* FIXME: TODO use GOFont */
82 	struct _GnmStyleFontDetails {
83 		GOString	*name;
84 		gboolean	bold;
85 		gboolean	italic;
86 		GnmUnderline	underline;
87 		gboolean	strikethrough;
88 		GOFontScript	script;
89 		double		size;
90 	} font_detail;
91 
92 	GOFormat const *format;
93 	GnmHAlign	 h_align;
94 	GnmVAlign	 v_align;
95 	int		 indent;
96 	int		 rotation;
97 	int		 text_dir;
98 	gboolean         wrap_text;
99 	gboolean         shrink_to_fit;
100 	gboolean         contents_locked;
101 	gboolean         contents_hidden;
102 
103 	GnmValidation		*validation;
104 	GnmHLink		*hlink;
105 	GnmInputMsg		*input_msg;
106 	GnmStyleConditions	*conditions;
107 	GPtrArray		*cond_styles;
108 };
109 
110 #define elem_changed(style, elem) do { (style)->changed |= (1u << (elem)); } while(0)
111 #define elem_set(style, elem)	  do { (style)->set |=  (1u << (elem)); } while(0)
112 #define elem_unset(style, elem)	  do { (style)->set &= ~(1u << (elem)); } while(0)
113 #define elem_is_set(style, elem)  (((style)->set & (1u << (elem))) != 0)
114 
115 #define MSTYLE_ANY_BORDER            MSTYLE_BORDER_TOP: \
116 				case MSTYLE_BORDER_BOTTOM: \
117 				case MSTYLE_BORDER_LEFT: \
118 				case MSTYLE_BORDER_RIGHT: \
119 				case MSTYLE_BORDER_DIAGONAL: \
120 				case MSTYLE_BORDER_REV_DIAGONAL
121 
122 
123 #define UNROLLED_FOR(init_,cond_,step_,code_)			\
124 do {								\
125 	init_;							\
126 	if (cond_) { code_; step_;				\
127 	if (cond_) { code_; step_;				\
128 	if (cond_) { code_; step_;				\
129 	if (cond_) { code_; step_;				\
130 	if (cond_) { code_; step_;				\
131 	if (cond_) { code_; step_;				\
132 	if (cond_) { code_; step_;				\
133 	if (cond_) { code_; step_;				\
134 	if (cond_) { code_; step_;				\
135 	if (cond_) { code_; step_;				\
136 	if (cond_) { code_; step_;				\
137 	if (cond_) { code_; step_;				\
138 	if (cond_) { code_; step_;				\
139 	if (cond_) { code_; step_;				\
140 	if (cond_) { code_; step_;				\
141 	if (cond_) { code_; step_;				\
142 	if (cond_) { code_; step_;				\
143 	if (cond_) { code_; step_;				\
144 	if (cond_) { code_; step_;				\
145 	if (cond_) { code_; step_;				\
146 	if (cond_) { code_; step_;				\
147 	if (cond_) { code_; step_;				\
148 	if (cond_) { code_; step_;				\
149 	if (cond_) { code_; step_;				\
150 	if (cond_) { code_; step_;				\
151 	if (cond_) { code_; step_;				\
152 	if (cond_) { code_; step_;				\
153 	if (cond_) { code_; step_;				\
154 	if (cond_) { code_; step_;				\
155 	if (cond_) { code_; step_;				\
156 	if (cond_) { code_; step_;				\
157 	if (cond_) { code_; step_;				\
158 	if (cond_) { code_; step_;				\
159 	if (cond_) { code_; step_;				\
160 	if (cond_) { code_; step_;				\
161 	if (cond_) { code_; step_;				\
162 	if (cond_) { code_; step_;				\
163 	if (cond_) { code_; step_;				\
164 	if (cond_) { code_; step_;				\
165 	if (cond_) { code_; step_;				\
166 	if (cond_) { code_; step_;				\
167 	if (cond_) { code_; step_;				\
168 	if (cond_) { code_; step_;				\
169 	if (cond_) { code_; step_;				\
170 	if (cond_) { code_; step_;				\
171 	if (cond_) { code_; step_;				\
172 	if (cond_) { code_; step_;				\
173 	if (cond_) { code_; step_;				\
174 	if (cond_) { code_; step_;				\
175 	if (cond_) { code_; step_;				\
176 	g_assert_not_reached ();				\
177 	}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}	\
178 } while (0)
179 
180 
181 
182 static char const * const
183 gnm_style_element_name[MSTYLE_ELEMENT_MAX] = {
184 	"Color.Back",
185 	"Color.Pattern",
186 	"Border.Top",
187 	"Border.Bottom",
188 	"Border.Left",
189 	"Border.Right",
190 	"Border.RevDiagonal",
191 	"Border.Diagonal",
192 	"Pattern",
193 	"Color.Fore",
194 	"Font.Name",
195 	"Font.Bold",
196 	"Font.Italic",
197 	"Font.Underline",
198 	"Font.Strikethrough",
199 	"Font.Script",
200 	"Font.Size",
201 	"Format",
202 	"Align.v",
203 	"Align.h",
204 	"Indent",
205 	"Rotation",
206 	"WrapText",
207 	"ShrinkToFit",
208 	"Contents.Locked",
209 	"Contents.Hidden",
210 	"Validation",
211 	"Hyper Link",
212 	"Input Msg"
213 };
214 
215 /* Some ref/link count debugging */
216 #if 0
217 #define d(arg)	g_printerr arg
218 #else
219 #define d(arg)	do { } while (0)
220 #endif
221 
222 static void
clear_conditional_merges(GnmStyle * style)223 clear_conditional_merges (GnmStyle *style)
224 {
225 	if (style->cond_styles) {
226 		unsigned i = style->cond_styles->len;
227 		while (i-- > 0)
228 			gnm_style_unref (g_ptr_array_index (style->cond_styles, i));
229 		g_ptr_array_free (style->cond_styles, TRUE);
230 		style->cond_styles = NULL;
231 	}
232 }
233 
234 #define MIX(H) do {				\
235   H *= G_GUINT64_CONSTANT(123456789012345);	\
236   H ^= (H >> 31);				\
237 } while (0)
238 
239 static void
gnm_style_update(GnmStyle * style)240 gnm_style_update (GnmStyle *style)
241 {
242 	guint64 hash = 0;
243 	int i;
244 
245 	g_return_if_fail (style->changed);
246 
247 	style->changed = 0;
248 
249 	clear_conditional_merges (style);
250 	if (elem_is_set (style, MSTYLE_CONDITIONS) && style->conditions)
251 		style->cond_styles = gnm_style_conditions_overlay (style->conditions, style);
252 
253 	/* ---------------------------------------- */
254 
255 	if (elem_is_set (style, MSTYLE_COLOR_BACK)) {
256 		if (!style->color.back->is_auto)
257 			hash ^= GPOINTER_TO_UINT (style->color.back);
258 		else
259 			hash++;
260 	}
261 	MIX (hash);
262 
263 	if (elem_is_set (style, MSTYLE_COLOR_PATTERN)) {
264 		if (!style->color.pattern->is_auto)
265 			hash ^= GPOINTER_TO_UINT (style->color.pattern);
266 		else
267 			hash++;
268 	}
269 	MIX (hash);
270 
271 	if (elem_is_set (style, MSTYLE_FONT_COLOR)) {
272 		if (!style->color.font->is_auto)
273 			hash ^= GPOINTER_TO_UINT (style->color.font);
274 		else
275 			hash++;
276 	}
277 	MIX (hash);
278 
279 	for (i = MSTYLE_BORDER_TOP; i <= MSTYLE_BORDER_DIAGONAL; i++) {
280 		if (elem_is_set (style, i))
281 			hash ^= GPOINTER_TO_UINT (style->borders[i - MSTYLE_BORDER_TOP]);
282 		else
283 			hash++;
284 		MIX (hash);
285 	}
286 
287 	if (elem_is_set (style, MSTYLE_PATTERN))
288 		hash ^= style->pattern;
289 	MIX (hash);
290 
291 	if (elem_is_set (style, MSTYLE_FONT_NAME))
292 		hash ^= GPOINTER_TO_UINT (style->font_detail.name);
293 	MIX (hash);
294 
295 	if (elem_is_set (style, MSTYLE_FONT_BOLD))
296 		hash ^= (style->font_detail.bold ? 1 : 2);
297 	MIX (hash);
298 
299 	if (elem_is_set (style, MSTYLE_FONT_ITALIC))
300 		hash ^= (style->font_detail.italic ? 1 : 2);
301 	MIX (hash);
302 
303 	if (elem_is_set (style, MSTYLE_FONT_UNDERLINE))
304 		hash ^= (style->font_detail.underline ? 1 : 2);
305 	MIX (hash);
306 
307 	if (elem_is_set (style, MSTYLE_FONT_STRIKETHROUGH))
308 		hash ^= (style->font_detail.strikethrough ? 1 : 2);
309 	MIX (hash);
310 
311 	if (elem_is_set (style, MSTYLE_FONT_SCRIPT))
312 		hash ^= (style->font_detail.script + 0x100);
313 	MIX (hash);
314 
315 	if (elem_is_set (style, MSTYLE_FONT_SIZE))
316 		hash ^= ((int)(style->font_detail.size * 97));
317 	MIX (hash);
318 
319 	if (elem_is_set (style, MSTYLE_FORMAT))
320 		hash ^= GPOINTER_TO_UINT (style->format);
321 	MIX (hash);
322 
323 	if (elem_is_set (style, MSTYLE_ALIGN_H))
324 		hash ^= (style->h_align + 0x100);
325 	MIX (hash);
326 
327 	if (elem_is_set (style, MSTYLE_ALIGN_V))
328 		hash ^= (style->v_align + 0x100);
329 	MIX (hash);
330 
331 	if (elem_is_set (style, MSTYLE_INDENT))
332 		hash ^= style->indent;
333 	MIX (hash);
334 
335 	if (elem_is_set (style, MSTYLE_ROTATION))
336 		hash ^= style->rotation;
337 	MIX (hash);
338 
339 	if (elem_is_set (style, MSTYLE_TEXT_DIR))
340 		hash ^= (style->text_dir + 0x100);
341 	MIX (hash);
342 
343 	if (elem_is_set (style, MSTYLE_WRAP_TEXT))
344 		hash ^= (style->wrap_text ? 1 : 2);
345 	MIX (hash);
346 
347 	if (elem_is_set (style, MSTYLE_SHRINK_TO_FIT))
348 		hash ^= (style->shrink_to_fit ? 1 : 2);
349 	MIX (hash);
350 
351 	if (elem_is_set (style, MSTYLE_CONTENTS_LOCKED))
352 		hash ^= (style->contents_locked ? 1 : 2);
353 	MIX (hash);
354 
355 	if (elem_is_set (style, MSTYLE_CONTENTS_HIDDEN))
356 		hash ^= (style->contents_hidden ? 1 : 2);
357 	MIX (hash);
358 
359 	style->hash_key_xl = (guint32)hash;
360 
361 	/* From here on, fields are not in MS XL */
362 
363 	if (elem_is_set (style, MSTYLE_VALIDATION)) {
364 		/*
365 		 * The hash used must not depend on the expressions inside
366 		 * the validation.
367 		 */
368 		hash ^= (style->validation != NULL ? 1 : 2);
369 	}
370 	MIX (hash);
371 
372 	if (elem_is_set (style, MSTYLE_HLINK))
373 		hash ^= GPOINTER_TO_UINT (style->hlink);
374 	MIX (hash);
375 
376 	if (elem_is_set (style, MSTYLE_INPUT_MSG))
377 		hash ^= GPOINTER_TO_UINT (style->input_msg);
378 	MIX (hash);
379 
380 	if (elem_is_set (style, MSTYLE_CONDITIONS)) {
381 		/*
382 		 * The hash used must not depend on the expressions inside
383 		 * the conditions.
384 		 */
385 		hash ^= style->conditions
386 			? gnm_style_conditions_hash (style->conditions)
387 			: 1u;
388 	}
389 	MIX (hash);
390 
391 	style->hash_key = (guint32)hash;
392 
393 	if (G_UNLIKELY (style->set == 0)) {
394 		/*
395 		 * gnm_style_new and gnm_style_dup both assume that the
396 		 * correct hash values (both of them) for the empty style
397 		 * is zero.
398 		 */
399 		g_assert (style->hash_key == 0);
400 		g_assert (style->hash_key_xl == 0);
401 	}
402 }
403 
404 #undef MIX
405 
406 guint
gnm_style_hash_XL(gconstpointer style)407 gnm_style_hash_XL (gconstpointer style)
408 {
409 	if (((GnmStyle const *)style)->changed)
410 		gnm_style_update ((GnmStyle *)style);
411 	return ((GnmStyle const *)style)->hash_key_xl;
412 }
413 
414 guint
gnm_style_hash(gconstpointer style)415 gnm_style_hash (gconstpointer style)
416 {
417 	if (((GnmStyle const *)style)->changed)
418 		gnm_style_update ((GnmStyle *)style);
419 	return ((GnmStyle const *)style)->hash_key;
420 }
421 
422 #define ELEM_IS_EQ(a,b,elem)						\
423     (elem == MSTYLE_COLOR_BACK						\
424     ? a->color.back == b->color.back ||	(a->color.back->is_auto && b->color.back->is_auto) \
425     : (elem == MSTYLE_COLOR_PATTERN					\
426     ? a->color.pattern == b->color.pattern || (a->color.pattern->is_auto && b->color.pattern->is_auto) \
427     : (elem >= MSTYLE_BORDER_TOP && elem <= MSTYLE_BORDER_DIAGONAL)	\
428     ? a->borders[elem - MSTYLE_BORDER_TOP] == b->borders[elem - MSTYLE_BORDER_TOP] \
429     : (elem == MSTYLE_PATTERN						\
430     ? a->pattern == b->pattern						\
431     : (elem == MSTYLE_FONT_COLOR					\
432     ? a->color.font == b->color.font || (a->color.font->is_auto && b->color.font->is_auto) \
433     : (elem == MSTYLE_FONT_NAME						\
434     ? a->font_detail.name == b->font_detail.name			\
435     : (elem == MSTYLE_FONT_BOLD						\
436     ? a->font_detail.bold == b->font_detail.bold			\
437     : (elem == MSTYLE_FONT_ITALIC					\
438     ? a->font_detail.italic == b->font_detail.italic			\
439     : (elem == MSTYLE_FONT_UNDERLINE					\
440     ? a->font_detail.underline == b->font_detail.underline		\
441     : (elem == MSTYLE_FONT_STRIKETHROUGH				\
442     ? a->font_detail.strikethrough == b->font_detail.strikethrough	\
443     : (elem == MSTYLE_FONT_SCRIPT					\
444     ? a->font_detail.script == b->font_detail.script			\
445     : (elem == MSTYLE_FONT_SIZE						\
446     ? a->font_detail.size == b->font_detail.size			\
447     : (elem == MSTYLE_FORMAT						\
448     ? a->format == b->format						\
449     : (elem == MSTYLE_ALIGN_V						\
450     ? a->v_align == b->v_align						\
451     : (elem == MSTYLE_ALIGN_H						\
452     ? a->h_align == b->h_align						\
453     : (elem == MSTYLE_INDENT						\
454     ? a->indent == b->indent						\
455     : (elem == MSTYLE_ROTATION						\
456     ? a->rotation == b->rotation					\
457     : (elem == MSTYLE_TEXT_DIR						\
458     ? a->text_dir == b->text_dir					\
459     : (elem == MSTYLE_WRAP_TEXT						\
460     ? a->wrap_text == b->wrap_text					\
461     : (elem == MSTYLE_SHRINK_TO_FIT					\
462     ? a->shrink_to_fit == b->shrink_to_fit				\
463     : (elem == MSTYLE_CONTENTS_LOCKED					\
464     ? a->contents_locked == b->contents_locked				\
465     : (elem == MSTYLE_CONTENTS_HIDDEN					\
466     ? a->contents_hidden == b->contents_hidden				\
467     : (elem == MSTYLE_VALIDATION					\
468     ? a->validation == b->validation					\
469     : (elem == MSTYLE_HLINK						\
470     ? a->hlink == b->hlink						\
471     : (elem == MSTYLE_INPUT_MSG						\
472     ? a->input_msg == b->input_msg					\
473     : (elem == MSTYLE_CONDITIONS					\
474     ? (a->conditions == b->conditions ||				\
475        (a->conditions && b->conditions &&				\
476 	gnm_style_conditions_equal (a->conditions, b->conditions, FALSE)))	\
477     : FALSE)))))))))))))))))))))))))
478 
479 /*
480  * Note: the above is suboptimal for validation, hlink, input_msg.
481  *
482  * We are comparing pointers (which at least safely matches what we do
483  * with the hash), but I think we want proper equality.
484  */
485 
486 static gboolean
elem_is_eq(GnmStyle const * a,GnmStyle const * b,GnmStyleElement elem)487 elem_is_eq (GnmStyle const *a, GnmStyle const *b, GnmStyleElement elem)
488 {
489 	return ELEM_IS_EQ (a, b, elem);
490 }
491 
492 static void
elem_assign_contents(GnmStyle * dst,GnmStyle const * src,GnmStyleElement elem)493 elem_assign_contents (GnmStyle *dst, GnmStyle const *src, GnmStyleElement elem)
494 {
495 #ifdef DEBUG_STYLES
496 	g_return_if_fail (src != dst);
497 	g_return_if_fail (elem_is_set (src, elem));
498 #endif
499 	switch (elem) {
500 	case MSTYLE_COLOR_BACK :	style_color_ref (dst->color.back = src->color.back); return;
501 	case MSTYLE_COLOR_PATTERN :	style_color_ref (dst->color.pattern = src->color.pattern); return;
502 	case MSTYLE_ANY_BORDER:
503 		elem -= MSTYLE_BORDER_TOP;
504 		gnm_style_border_ref (dst->borders[elem] = src->borders[elem]);
505 		return;
506 	case MSTYLE_PATTERN:		dst->pattern = src->pattern; return;
507 	case MSTYLE_FONT_COLOR :	style_color_ref (dst->color.font = src->color.font); return;
508 	case MSTYLE_FONT_NAME:		go_string_ref (dst->font_detail.name = src->font_detail.name); return;
509 	case MSTYLE_FONT_BOLD:		dst->font_detail.bold = src->font_detail.bold; return;
510 	case MSTYLE_FONT_ITALIC:	dst->font_detail.italic = src->font_detail.italic; return;
511 	case MSTYLE_FONT_UNDERLINE:	dst->font_detail.underline = src->font_detail.underline; return;
512 	case MSTYLE_FONT_STRIKETHROUGH: dst->font_detail.strikethrough = src->font_detail.strikethrough; return;
513 	case MSTYLE_FONT_SCRIPT:	dst->font_detail.script = src->font_detail.script; return;
514 	case MSTYLE_FONT_SIZE:		dst->font_detail.size = src->font_detail.size; return;
515 	case MSTYLE_FORMAT:		go_format_ref (dst->format = src->format); return;
516 	case MSTYLE_ALIGN_V:		dst->v_align = src->v_align; return;
517 	case MSTYLE_ALIGN_H:		dst->h_align = src->h_align; return;
518 	case MSTYLE_INDENT:		dst->indent = src->indent; return;
519 	case MSTYLE_ROTATION:		dst->rotation = src->rotation; return;
520 	case MSTYLE_TEXT_DIR:		dst->text_dir = src->text_dir; return;
521 	case MSTYLE_WRAP_TEXT:		dst->wrap_text = src->wrap_text; return;
522 	case MSTYLE_SHRINK_TO_FIT:	dst->shrink_to_fit = src->shrink_to_fit; return;
523 	case MSTYLE_CONTENTS_LOCKED:	dst->contents_locked = src->contents_locked; return;
524 	case MSTYLE_CONTENTS_HIDDEN:	dst->contents_hidden = src->contents_hidden; return;
525 	case MSTYLE_VALIDATION:
526 		if ((dst->validation = src->validation))
527 			gnm_validation_ref (dst->validation);
528 		return;
529 	case MSTYLE_HLINK:
530 		if ((dst->hlink = src->hlink))
531 			g_object_ref (dst->hlink);
532 		return;
533 	case MSTYLE_INPUT_MSG:
534 		if ((dst->input_msg = src->input_msg))
535 			g_object_ref (dst->input_msg);
536 		return;
537 	case MSTYLE_CONDITIONS:
538 		if ((dst->conditions = src->conditions))
539 			g_object_ref (dst->conditions);
540 		return;
541 	default:
542 		;
543 	}
544 }
545 
546 static void
elem_clear_contents(GnmStyle * style,GnmStyleElement elem)547 elem_clear_contents (GnmStyle *style, GnmStyleElement elem)
548 {
549 #ifdef DEBUG_STYLES
550 	g_return_if_fail (style != NULL);
551 #endif
552 	if (!elem_is_set (style, elem))
553 		return;
554 
555 	switch (elem) {
556 	case MSTYLE_COLOR_BACK :	style_color_unref (style->color.back); return;
557 	case MSTYLE_COLOR_PATTERN :	style_color_unref (style->color.pattern); return;
558 	case MSTYLE_ANY_BORDER:
559 		gnm_style_border_unref (style->borders[elem - MSTYLE_BORDER_TOP]);
560 		return;
561 	case MSTYLE_FONT_COLOR :	style_color_unref (style->color.font); return;
562 	case MSTYLE_FONT_NAME:		go_string_unref (style->font_detail.name); return;
563 	case MSTYLE_FORMAT:		go_format_unref (style->format); return;
564 	case MSTYLE_VALIDATION:
565 		if (style->validation)
566 			gnm_validation_unref (style->validation);
567 		return;
568 	case MSTYLE_HLINK:
569 		if (style->hlink)
570 			g_object_unref (style->hlink);
571 		return;
572 	case MSTYLE_INPUT_MSG:
573 		if (style->input_msg)
574 			g_object_unref (style->input_msg);
575 		return;
576 	case MSTYLE_CONDITIONS:
577 		if (style->conditions) {
578 			clear_conditional_merges (style);
579 			g_object_unref (style->conditions);
580 		}
581 		return;
582 	default:
583 		;
584 	}
585 }
586 
587 /**
588  * gnm_style_find_conflicts:
589  * @accum: accumulator #GnmStyle
590  * @overlay: #GnmStyle
591  * @conflicts: flags
592  *
593  * Copy any items from @overlay that do not conflict with the values in @accum.
594  * If an element had a previous conflict (flagged via @conflicts) it is ignored.
595  *
596  * Returns @conflicts with any new conflicts added.
597  **/
598 unsigned int
gnm_style_find_conflicts(GnmStyle * accum,GnmStyle const * overlay,unsigned int conflicts)599 gnm_style_find_conflicts (GnmStyle *accum, GnmStyle const *overlay,
600 			  unsigned int conflicts)
601 {
602 	int i;
603 
604 	g_assert (MSTYLE_ELEMENT_MAX <= CHAR_BIT * sizeof (conflicts));
605 
606 	for (i = 0; i < MSTYLE_ELEMENT_MAX; i++) {
607 		if (conflicts & (1u << i) || !elem_is_set (overlay, i)) {
608 			/* Nothing */
609 		} else if (!elem_is_set (accum, i)) {
610 			elem_assign_contents (accum, overlay, i);
611 			elem_set (accum, i);
612 			elem_changed (accum, i);
613 		} else if (!elem_is_eq (accum, overlay, i))
614 			conflicts |= (1u << i);
615 	}
616 
617 	return conflicts;
618 }
619 
620 #define GNM_INPUT_MSG_EQUAL3(a,b,r) (gnm_input_msg_equal (a,b))
621 
622 #define RELAX_CHECK(op_,field_,checker_) do {			\
623 	if (diffs & (1u << (op_)) &&				\
624 	    elem_is_set (a, (op_)) &&				\
625 	    elem_is_set (b, (op_)) &&				\
626 	    ((a->field_ == NULL) != (b->field_ == NULL) ||	\
627 	     checker_ (a->field_, b->field_, relax_sheet)))	\
628 		diffs &= ~(1u << (op_));			\
629 	} while (0)
630 
631 /**
632  * gnm_style_find_differences:
633  * @a: A #GnmStyle
634  * @b: A #GnmStyle
635  * @relax_sheet: if %TRUE, ignore differences solely caused by being linked into different sheets.
636  *
637  * Determine how two fully-qualified styles differ.
638  *
639  * Returns differences as a bitset of #GnmStyleElement.
640  **/
641 unsigned int
gnm_style_find_differences(GnmStyle const * a,GnmStyle const * b,gboolean relax_sheet)642 gnm_style_find_differences (GnmStyle const *a, GnmStyle const *b,
643 			    gboolean relax_sheet)
644 {
645 	int i;
646 	unsigned int diffs = 0;
647 
648 	g_assert (MSTYLE_ELEMENT_MAX <= CHAR_BIT * sizeof (diffs));
649 
650 	for (i = 0; i < MSTYLE_ELEMENT_MAX; i++) {
651 		if (elem_is_set (a, i) != elem_is_set (b, i) ||
652 		    (elem_is_set (a, i) && !elem_is_eq (a, b, i)))
653 			diffs |= (1u << i);
654 	}
655 
656 	if (relax_sheet) {
657 		RELAX_CHECK (MSTYLE_HLINK, hlink, gnm_hlink_equal);
658 		RELAX_CHECK (MSTYLE_VALIDATION, validation, gnm_validation_equal);
659 		RELAX_CHECK (MSTYLE_INPUT_MSG, input_msg, GNM_INPUT_MSG_EQUAL3);
660 		RELAX_CHECK (MSTYLE_CONDITIONS, conditions, gnm_style_conditions_equal);
661 	}
662 
663 	return diffs;
664 }
665 
666 #undef RELAX_CHECK
667 #undef GNM_INPUT_MSG_EQUAL3
668 
669 static inline void
gnm_style_clear_pango(GnmStyle * style)670 gnm_style_clear_pango (GnmStyle *style)
671 {
672 	if (style->pango_attrs) {
673 		pango_attr_list_unref (style->pango_attrs);
674 		style->pango_attrs = NULL;
675 	}
676 }
677 
678 
679 static inline void
gnm_style_clear_font(GnmStyle * style)680 gnm_style_clear_font (GnmStyle *style)
681 {
682 	if (style->font) {
683 		gnm_font_unref (style->font);
684 		style->font = NULL;
685 	}
686 	g_clear_object (&style->font_context);
687 }
688 
689 /**
690  * gnm_style_new:
691  *
692  * Returns: (transfer full): a new style with _no_ elements set.
693  **/
694 GnmStyle *
gnm_style_new(void)695 gnm_style_new (void)
696 {
697 	GnmStyle *style = CHUNK_ALLOC0 (GnmStyle, gnm_style_pool);
698 
699 	style->ref_count = 1;
700 	style->link_count = 0;
701 	style->linked_sheet = NULL;
702 	style->pango_attrs = NULL;
703 	style->font = NULL;
704 	style->validation = NULL;
705 
706 	style->set = style->changed = 0;
707 	style->validation = NULL;
708 	style->hlink = NULL;
709 	style->input_msg = NULL;
710 	style->conditions = NULL;
711 
712 	d(("new %p\n", style));
713 
714 	return style;
715 }
716 
717 /**
718  * gnm_style_new_default:
719  *
720  * Returns: (transfer full): a new style initialized to the default state.
721  **/
722 GnmStyle *
gnm_style_new_default(void)723 gnm_style_new_default (void)
724 {
725 	GnmStyle *new_style = gnm_style_new ();
726 	int i;
727 
728 	gnm_style_set_font_name	  (new_style, gnm_conf_get_core_defaultfont_name ());
729 	gnm_style_set_font_size	  (new_style, gnm_conf_get_core_defaultfont_size ());
730 	gnm_style_set_font_bold	  (new_style, gnm_conf_get_core_defaultfont_bold ());
731 	gnm_style_set_font_italic (new_style, gnm_conf_get_core_defaultfont_italic ());
732 
733 	gnm_style_set_format      (new_style, go_format_general ());
734 	gnm_style_set_align_v     (new_style, GNM_VALIGN_BOTTOM);
735 	gnm_style_set_align_h     (new_style, GNM_HALIGN_GENERAL);
736 	gnm_style_set_indent      (new_style, 0);
737 	gnm_style_set_rotation    (new_style, 0);
738 	gnm_style_set_text_dir    (new_style, GNM_TEXT_DIR_CONTEXT);
739 	gnm_style_set_wrap_text   (new_style, FALSE);
740 	gnm_style_set_shrink_to_fit (new_style, FALSE);
741 	gnm_style_set_contents_locked (new_style, TRUE);
742 	gnm_style_set_contents_hidden (new_style, FALSE);
743 	gnm_style_set_font_uline  (new_style, UNDERLINE_NONE);
744 	gnm_style_set_font_strike (new_style, FALSE);
745 	gnm_style_set_font_script (new_style, GO_FONT_SCRIPT_STANDARD);
746 
747 	gnm_style_set_validation (new_style, NULL);
748 	gnm_style_set_hlink      (new_style, NULL);
749 	gnm_style_set_input_msg  (new_style, NULL);
750 	gnm_style_set_conditions (new_style, NULL);
751 
752 	gnm_style_set_font_color (new_style, style_color_black ());
753 	gnm_style_set_back_color (new_style, style_color_auto_back ());
754 	gnm_style_set_pattern_color (new_style, style_color_black ());
755 
756 	for (i = MSTYLE_BORDER_TOP; i <= MSTYLE_BORDER_DIAGONAL; ++i)
757 		gnm_style_set_border (new_style, i,
758 			gnm_style_border_ref (gnm_style_border_none ()));
759 	gnm_style_set_pattern (new_style, 0);
760 
761 	return new_style;
762 }
763 
764 GnmStyle *
gnm_style_dup(GnmStyle const * src)765 gnm_style_dup (GnmStyle const *src)
766 {
767 	GnmStyle *new_style = CHUNK_ALLOC0 (GnmStyle, gnm_style_pool);
768 	int i;
769 
770 	new_style->ref_count = 1;
771 	for (i = 0; i < MSTYLE_ELEMENT_MAX; i++)
772 		if (elem_is_set (src, i)) {
773 			elem_assign_contents (new_style, src, i);
774 			elem_set (new_style, i);
775 			elem_changed (new_style, i);
776 		}
777 
778 	if ((new_style->pango_attrs = src->pango_attrs)) {
779 		pango_attr_list_ref (new_style->pango_attrs);
780 		new_style->pango_attrs_zoom = src->pango_attrs_zoom;
781 	}
782 
783 	if ((new_style->font = src->font)) {
784 		gnm_font_ref (new_style->font);
785 		new_style->font_context = g_object_ref (src->font_context);
786 	}
787 
788 	d(("dup %p\n", new_style));
789 	return new_style;
790 }
791 
792 /**
793  * gnm_style_new_merged:
794  * @base: #GnmStyle
795  * @overlay: #GnmStyle
796  *
797  * A new GnmStyle that contains any elements of @overlay that are set, and uses
798  * @base for anything that is not set in @overlay.
799  *
800  * Returns: (transfer full): A ref to a new GnmStyle.
801  **/
802 GnmStyle *
gnm_style_new_merged(GnmStyle const * base,GnmStyle const * overlay)803 gnm_style_new_merged (GnmStyle const *base, GnmStyle const *overlay)
804 {
805 	GnmStyle *new_style = CHUNK_ALLOC0 (GnmStyle, gnm_style_pool);
806 	int i;
807 
808 	new_style->ref_count = 1;
809 	for (i = 0; i < MSTYLE_ELEMENT_MAX; i++) {
810 		if (elem_is_set (overlay, i))
811 			elem_assign_contents (new_style, overlay, i);
812 		else if (elem_is_set (base, i))
813 			elem_assign_contents (new_style, base, i);
814 		else
815 			continue;
816 		elem_set (new_style, i);
817 		elem_changed (new_style, i);
818 	}
819 	d(("copy merge %p\n", new_style));
820 	return new_style;
821 }
822 
823 /**
824  * gnm_style_ref: (skip)
825  * @style: #GnmStyle
826  *
827  * Returns: (transfer full): A new reference to @style.
828  */
829 GnmStyle *
gnm_style_ref(GnmStyle const * style)830 gnm_style_ref (GnmStyle const *style)
831 {
832 	g_return_val_if_fail (style != NULL, NULL);
833 	g_return_val_if_fail (style->ref_count > 0, NULL);
834 
835 	((GnmStyle *)style)->ref_count++;
836 	d(("ref %p = %d\n", style, style->ref_count));
837 
838 	return ((GnmStyle *)style);
839 }
840 
841 /**
842  * gnm_style_unref: (skip)
843  * @style: #GnmStyle const
844  *
845  * Unrefs and _potentially frees_ @style.
846  * Takes a _const_ pointer to facilitate life cycles.  The const indicates that
847  * the content cannot be changed, mainly when handling styles that are in the
848  * style hash.
849  **/
850 void
gnm_style_unref(GnmStyle const * style)851 gnm_style_unref (GnmStyle const *style)
852 {
853 	g_return_if_fail (style != NULL);
854 	g_return_if_fail (style->ref_count > 0);
855 
856 	d(("unref %p = %d\n", style, style->ref_count-1));
857 	if (((GnmStyle *)style)->ref_count-- <= 1) {
858 		GnmStyle *unconst = (GnmStyle *)style;
859 		int i;
860 
861 		g_return_if_fail (style->link_count == 0);
862 		g_return_if_fail (style->linked_sheet == NULL);
863 
864 		for (i = 0; i < MSTYLE_ELEMENT_MAX; i++)
865 			elem_clear_contents (unconst, i);
866 		unconst->set = 0;
867 		clear_conditional_merges (unconst);
868 		gnm_style_clear_pango (unconst);
869 		gnm_style_clear_font (unconst);
870 
871 		CHUNK_FREE (gnm_style_pool, unconst);
872 	}
873 }
874 
875 GType
gnm_style_get_type(void)876 gnm_style_get_type (void)
877 {
878 	static GType t = 0;
879 
880 	if (t == 0) {
881 		t = g_boxed_type_register_static ("GnmStyle",
882 			 (GBoxedCopyFunc)gnm_style_ref,
883 			 (GBoxedFreeFunc)gnm_style_unref);
884 	}
885 	return t;
886 }
887 
888 /*
889  * Replace auto pattern color in style with sheet's auto pattern color.
890  * make_copy tells if we are allowed to modify the style in place or we must
891  * make a copy first.
892  */
893 static GnmStyle *
link_pattern_color(GnmStyle * style,GnmColor * auto_color,gboolean make_copy)894 link_pattern_color (GnmStyle *style, GnmColor *auto_color, gboolean make_copy)
895 {
896 	GnmColor *pattern_color = style->color.pattern;
897 
898 	if (pattern_color->is_auto && auto_color != pattern_color) {
899 		style_color_ref (auto_color);
900 		if (make_copy) {
901 			GnmStyle *orig = style;
902 			style = gnm_style_dup (style);
903 			gnm_style_unref (orig);
904 		}
905 		gnm_style_set_pattern_color (style, auto_color);
906 	}
907 	return style;
908 }
909 
910 /*
911  * Replace auto border colors in style with sheet's auto pattern
912  * color. (pattern is *not* a typo.)
913  * make_copy tells if we are allowed to modify the style in place or we must
914  * make a copy first.
915  *
916  * FIXME: We conjecture that XL color 64 in border should change with the
917  * pattern, but not color 127. That distinction is not yet represented in
918  * our data structures.
919  */
920 static GnmStyle *
link_border_colors(GnmStyle * style,GnmColor * auto_color,gboolean make_copy)921 link_border_colors (GnmStyle *style, GnmColor *auto_color, gboolean make_copy)
922 {
923 	int i;
924 
925 	for (i = MSTYLE_BORDER_TOP; i <= MSTYLE_BORDER_DIAGONAL; ++i) {
926 		if (elem_is_set (style, i)) {
927 			GnmBorder *border =
928 				style->borders[i- MSTYLE_BORDER_TOP];
929 			GnmColor *color;
930 
931 			if (!border)
932 				continue;
933 
934 			color = border->color;
935 			if (color->is_auto && auto_color != color) {
936 				GnmBorder *new_border;
937 				GnmStyleBorderOrientation orientation;
938 
939 				switch (i) {
940 				case MSTYLE_BORDER_LEFT:
941 				case MSTYLE_BORDER_RIGHT:
942 					orientation = GNM_STYLE_BORDER_VERTICAL;
943 					break;
944 				case MSTYLE_BORDER_REV_DIAGONAL:
945 				case MSTYLE_BORDER_DIAGONAL:
946 					orientation = GNM_STYLE_BORDER_DIAGONAL;
947 					break;
948 				case MSTYLE_BORDER_TOP:
949 				case MSTYLE_BORDER_BOTTOM:
950 				default:
951 					orientation = GNM_STYLE_BORDER_HORIZONTAL;
952 					break;
953 				}
954 				style_color_ref (auto_color);
955 				new_border = gnm_style_border_fetch (
956 					border->line_type, auto_color,
957 					orientation);
958 
959 				if (make_copy) {
960 					GnmStyle *orig = style;
961 					style = gnm_style_dup (style);
962 					gnm_style_unref (orig);
963 					make_copy = FALSE;
964 				}
965 				gnm_style_set_border (style, i, new_border);
966 			}
967 		}
968 	}
969 	return style;
970 }
971 
972 static void
gnm_style_linked_sheet_changed(GnmStyle * style)973 gnm_style_linked_sheet_changed (GnmStyle *style)
974 {
975 	Sheet *sheet = style->linked_sheet;
976 
977 	if (elem_is_set (style, MSTYLE_VALIDATION) &&
978 	    style->validation &&
979 	    gnm_validation_get_sheet (style->validation) != sheet) {
980 		GnmValidation *new_v = gnm_validation_dup_to (style->validation, sheet);
981 		gnm_style_set_validation (style, new_v);
982 	}
983 
984 	if (elem_is_set (style, MSTYLE_HLINK) &&
985 	    style->hlink &&
986 	    gnm_hlink_get_sheet (style->hlink) != sheet) {
987 		GnmHLink *new_l = gnm_hlink_dup_to (style->hlink, sheet);
988 		gnm_style_set_hlink (style, new_l);
989 	}
990 
991 	if (elem_is_set (style, MSTYLE_CONDITIONS) &&
992 	    style->conditions &&
993 	    gnm_style_conditions_get_sheet (style->conditions) != sheet) {
994 		GnmStyleConditions *new_c, *new_sc;
995 
996 		sheet_conditions_share_conditions_remove (style->conditions);
997 		new_c = gnm_style_conditions_dup_to (style->conditions, sheet);
998 		new_sc = sheet_conditions_share_conditions_add (new_c);
999 		if (new_sc) {
1000 			g_object_unref (new_c);
1001 			new_c = new_sc;
1002 		}
1003 		gnm_style_set_conditions (style, new_c);
1004 	}
1005 }
1006 
1007 /**
1008  * gnm_style_link_sheet:
1009  * @style: (transfer full):
1010  * @sheet:
1011  *
1012  * ABSORBS a reference to the style and sets the link count to 1.
1013  *
1014  * Where auto pattern color occurs in the style (it may for pattern and
1015  * borders), it is replaced with the sheet's auto pattern color. We make
1016  * sure that we do not modify the style which was passed in to us, but also
1017  * that we don't copy more than once. The final argument to the
1018  * link_xxxxx_color functions tell whether or not to copy.
1019  */
1020 GnmStyle *
gnm_style_link_sheet(GnmStyle * style,Sheet * sheet)1021 gnm_style_link_sheet (GnmStyle *style, Sheet *sheet)
1022 {
1023 	GnmColor *auto_color;
1024 	gboolean style_is_orig = TRUE;
1025 
1026 	if (style->linked_sheet != NULL) {
1027 		GnmStyle *orig = style;
1028 		style = gnm_style_dup (style);
1029 		gnm_style_unref (orig);
1030 		style_is_orig = FALSE;
1031 
1032 		/* safety test */
1033 		g_return_val_if_fail (style->linked_sheet != sheet, style);
1034 	}
1035 
1036 	g_return_val_if_fail (style->link_count == 0, style);
1037 	g_return_val_if_fail (style->linked_sheet == NULL, style);
1038 
1039 	auto_color = sheet_style_get_auto_pattern_color (sheet);
1040 	if (elem_is_set (style, MSTYLE_COLOR_PATTERN))
1041 		style = link_pattern_color (style, auto_color, style_is_orig);
1042 	style = link_border_colors (style, auto_color, style_is_orig);
1043 	style_color_unref (auto_color);
1044 
1045 	if (elem_is_set (style, MSTYLE_CONDITIONS) && style->conditions) {
1046 		// We actually change the style here, but the resulting
1047 		// ->conditions should be equivalent.
1048 		GnmStyleConditions *sc_new = sheet_conditions_share_conditions_add (style->conditions);
1049 		if (sc_new)
1050 			gnm_style_set_conditions (style, g_object_ref (sc_new));
1051 	}
1052 
1053 	style->linked_sheet = sheet;
1054 	style->link_count = 1;
1055 
1056 	gnm_style_linked_sheet_changed (style);
1057 
1058 	d(("link sheet %p = 1\n", style));
1059 	return style;
1060 }
1061 
1062 void
gnm_style_link(GnmStyle * style)1063 gnm_style_link (GnmStyle *style)
1064 {
1065 	g_return_if_fail (style->link_count > 0);
1066 
1067 	style->link_count++;
1068 	d(("link %p = %d\n", style, style->link_count));
1069 }
1070 
1071 void
gnm_style_unlink(GnmStyle * style)1072 gnm_style_unlink (GnmStyle *style)
1073 {
1074 	g_return_if_fail (style->link_count > 0);
1075 
1076 	d(("unlink %p = %d\n", style, style->link_count-1));
1077 	if (style->link_count-- == 1) {
1078 		if (elem_is_set (style, MSTYLE_CONDITIONS) && style->conditions)
1079 			sheet_conditions_share_conditions_remove (style->conditions);
1080 		sheet_style_unlink (style->linked_sheet, style);
1081 		style->linked_sheet = NULL;
1082 		gnm_style_unref (style);
1083 	}
1084 }
1085 
1086 // Internal function for sheet-style.c use only
1087 void
gnm_style_abandon_link(GnmStyle * style)1088 gnm_style_abandon_link (GnmStyle *style)
1089 {
1090 	style->link_count = 0;
1091 	style->linked_sheet = NULL;
1092 }
1093 
1094 gboolean
gnm_style_eq(GnmStyle const * a,GnmStyle const * b)1095 gnm_style_eq (GnmStyle const *a, GnmStyle const *b)
1096 {
1097 	return a == b;
1098 }
1099 
1100 gboolean
gnm_style_equal(GnmStyle const * a,GnmStyle const * b)1101 gnm_style_equal (GnmStyle const *a, GnmStyle const *b)
1102 {
1103 	int i;
1104 
1105 	if (a == b)
1106 		return TRUE;
1107 	if (a->set != b->set || !gnm_style_equal_XL (a, b))
1108 		return FALSE;
1109 	UNROLLED_FOR (i = MSTYLE_VALIDATION, i < MSTYLE_ELEMENT_MAX, i++, {
1110 		if (elem_is_set (a, i) && !ELEM_IS_EQ (a, b, i))
1111 			return FALSE;
1112 	});
1113 
1114 	return TRUE;
1115 }
1116 
1117 gboolean
gnm_style_equal_XL(GnmStyle const * a,GnmStyle const * b)1118 gnm_style_equal_XL (GnmStyle const *a, GnmStyle const *b)
1119 {
1120 	int i;
1121 
1122 	g_return_val_if_fail (a != NULL, FALSE);
1123 	g_return_val_if_fail (b != NULL, FALSE);
1124 
1125 	if (a == b)
1126 		return TRUE;
1127 
1128 	if ((a->set ^ b->set) & ((1u << MSTYLE_VALIDATION) - 1))
1129 		return FALSE;
1130 
1131 	UNROLLED_FOR (i = MSTYLE_COLOR_BACK, i < MSTYLE_VALIDATION, i++, {
1132 		if (elem_is_set (a, i) && !ELEM_IS_EQ (a, b, i))
1133 			return FALSE;
1134 	});
1135 	return TRUE;
1136 }
1137 
1138 /**
1139  * gnm_style_equal_elem:
1140  * @a: first style
1141  * @b: second style
1142  * @e: style element
1143  *
1144  * Returns: %TRUE, if the two styles have the same contents for the
1145  * given element, either because neither have it set, or because both
1146  * have it set and to the same value.
1147  */
1148 gboolean
gnm_style_equal_elem(GnmStyle const * a,GnmStyle const * b,GnmStyleElement e)1149 gnm_style_equal_elem (GnmStyle const *a, GnmStyle const *b, GnmStyleElement e)
1150 {
1151 	if (elem_is_set (a, e))
1152 		return elem_is_set (b, e) && elem_is_eq (a, b, e);
1153 	else
1154 		return !elem_is_set (b, e);
1155 }
1156 
1157 
1158 
1159 #define CMP_TRY_NUMBER_RAW(a_,b_)		\
1160   do {						\
1161     if ((a_) < (b_)) return -1;			\
1162     if ((a_) > (b_)) return -1;			\
1163   } while (0)
1164 
1165 #define CMP_TRY_NUMBER(e_,f_)			\
1166   do {						\
1167     if (elem_is_set (a, (e_)))			\
1168       CMP_TRY_NUMBER_RAW(a->f_, b->f_);		\
1169   } while (0)
1170 
1171 #define CMP_TRY_COLOR(e_,f_)					\
1172   do {								\
1173     if (elem_is_set (a, (e_))) {				\
1174       CMP_TRY_NUMBER_RAW(a->f_->is_auto, b->f_->is_auto);	\
1175       CMP_TRY_NUMBER_RAW(a->f_->go_color, b->f_->go_color);	\
1176     }								\
1177   } while (0)
1178 
1179 /*
1180  * Ordering of GnmStyles.  Apart from FIXMEs, this shouldn't change
1181  * from one run to the next.
1182  */
1183 int
gnm_style_cmp(GnmStyle const * a,GnmStyle const * b)1184 gnm_style_cmp (GnmStyle const *a, GnmStyle const *b)
1185 {
1186 	GnmStyleElement e;
1187 
1188 	if (a == b)
1189 		return 0;
1190 
1191 	/*
1192 	 * Very quick comparison based on what is set.  This also allows
1193 	 * us to check on one elem_is_set below.
1194 	 */
1195 	CMP_TRY_NUMBER_RAW (a->set, b->set);
1196 
1197 	CMP_TRY_COLOR (MSTYLE_FONT_COLOR, color.font);
1198 	CMP_TRY_COLOR (MSTYLE_COLOR_BACK, color.back);
1199 	CMP_TRY_COLOR (MSTYLE_COLOR_PATTERN, color.pattern);
1200 	for (e = MSTYLE_BORDER_TOP; e <= MSTYLE_BORDER_DIAGONAL; e++) {
1201 		GnmBorder const *ba, *bb;
1202 		if (!elem_is_set (a, e))
1203 			continue;
1204 		ba = a->borders[e - MSTYLE_BORDER_TOP];
1205 		bb = b->borders[e - MSTYLE_BORDER_TOP];
1206 		if (ba == bb)
1207 			continue;  /* Handles both being NULL */
1208 		CMP_TRY_NUMBER_RAW(!!ba, !!bb);
1209 		CMP_TRY_NUMBER_RAW(ba->line_type, bb->line_type);
1210 		CMP_TRY_NUMBER_RAW(ba->color->go_color, bb->color->go_color);
1211 		CMP_TRY_NUMBER_RAW(ba->begin_margin, bb->begin_margin);
1212 		CMP_TRY_NUMBER_RAW(ba->end_margin, bb->end_margin);
1213 		CMP_TRY_NUMBER_RAW(ba->width, bb->width);
1214 	}
1215 	CMP_TRY_NUMBER (MSTYLE_PATTERN, pattern);
1216 	if (elem_is_set (a, MSTYLE_FONT_NAME)) {
1217 		/* Plain strcmp, not utf-8.  We need to see diffs.  */
1218 		int tmp = strcmp (a->font_detail.name->str,
1219 				  b->font_detail.name->str);
1220 		if (tmp)
1221 			return tmp;
1222 	}
1223 	CMP_TRY_NUMBER (MSTYLE_FONT_BOLD, font_detail.bold);
1224 	CMP_TRY_NUMBER (MSTYLE_FONT_ITALIC, font_detail.italic);
1225 	CMP_TRY_NUMBER (MSTYLE_FONT_UNDERLINE, font_detail.underline);
1226 	CMP_TRY_NUMBER (MSTYLE_FONT_STRIKETHROUGH, font_detail.strikethrough);
1227 	CMP_TRY_NUMBER (MSTYLE_FONT_SCRIPT, font_detail.script);
1228 	CMP_TRY_NUMBER (MSTYLE_FONT_SIZE, font_detail.size);
1229 	if (elem_is_set (a, MSTYLE_FORMAT)) {
1230 		/* Plain strcmp, not utf-8.  We need to see diffs.  */
1231 		int tmp = strcmp (go_format_as_XL (a->format),
1232 				  go_format_as_XL (b->format));
1233 		if (tmp)
1234 			return tmp;
1235 	}
1236 	CMP_TRY_NUMBER (MSTYLE_ALIGN_H, h_align);
1237 	CMP_TRY_NUMBER (MSTYLE_ALIGN_V, v_align);
1238 	CMP_TRY_NUMBER (MSTYLE_INDENT, indent);
1239 	CMP_TRY_NUMBER (MSTYLE_ROTATION, rotation);
1240 	CMP_TRY_NUMBER (MSTYLE_TEXT_DIR, text_dir);
1241 	CMP_TRY_NUMBER (MSTYLE_WRAP_TEXT, wrap_text);
1242 	CMP_TRY_NUMBER (MSTYLE_SHRINK_TO_FIT, shrink_to_fit);
1243 	CMP_TRY_NUMBER (MSTYLE_CONTENTS_LOCKED, contents_locked);
1244 	CMP_TRY_NUMBER (MSTYLE_CONTENTS_HIDDEN, contents_hidden);
1245 	/* FIXME: validation */
1246 	/* FIXME: hlink */
1247 	/* FIXME: input_msg */
1248 	/* FIXME: conditions */
1249 	/* FIXME: cond_styles */
1250 
1251 	/* Last resort: pointer comparison.  */
1252 	return a < b ? -1 : +1;
1253 }
1254 
1255 #undef CMP_TRY_NUMBER_RAW
1256 #undef CMP_TRY_NUMBER
1257 #undef CMP_TRY_COLOR
1258 
1259 
1260 /**
1261  * gnm_style_equal_header:
1262  * @a: #GnmStyle
1263  * @b: #GnmStyle
1264  * @top: is this a header vertically or horizontally
1265  *
1266  * Check to see if @a is different enough from @b to make us think that @a is
1267  * from a header.
1268  **/
1269 gboolean
gnm_style_equal_header(GnmStyle const * a,GnmStyle const * b,gboolean top)1270 gnm_style_equal_header (GnmStyle const *a, GnmStyle const *b, gboolean top)
1271 {
1272 	int i = top ? MSTYLE_BORDER_BOTTOM : MSTYLE_BORDER_RIGHT;
1273 
1274 	if (!elem_is_eq (a, b, i))
1275 		return FALSE;
1276 	for (i = MSTYLE_COLOR_BACK; i <= MSTYLE_COLOR_PATTERN ; i++)
1277 		if (!elem_is_eq (a, b, i))
1278 			return FALSE;
1279 	for (i = MSTYLE_FONT_COLOR; i <= MSTYLE_SHRINK_TO_FIT ; i++)
1280 		if (!elem_is_eq (a, b, i))
1281 			return FALSE;
1282 	return TRUE;
1283 }
1284 
1285 
1286 gboolean
gnm_style_is_element_set(GnmStyle const * style,GnmStyleElement elem)1287 gnm_style_is_element_set (GnmStyle const *style, GnmStyleElement elem)
1288 {
1289 	g_return_val_if_fail (style != NULL, FALSE);
1290 	g_return_val_if_fail (MSTYLE_COLOR_BACK <= elem && elem < MSTYLE_ELEMENT_MAX, FALSE);
1291 	return elem_is_set (style, elem);
1292 }
1293 
1294 /**
1295  * gnm_style_is_complete:
1296  * @style: #GnmStyle to query
1297  *
1298  * Returns: %TRUE if all elements are set.
1299  **/
1300 gboolean
gnm_style_is_complete(GnmStyle const * style)1301 gnm_style_is_complete (GnmStyle const *style)
1302 {
1303 	g_return_val_if_fail (style != NULL, FALSE);
1304 
1305 	return style->set == ((1u << MSTYLE_ELEMENT_MAX) - 1);
1306 }
1307 
1308 void
gnm_style_unset_element(GnmStyle * style,GnmStyleElement elem)1309 gnm_style_unset_element (GnmStyle *style, GnmStyleElement elem)
1310 {
1311 	g_return_if_fail (style != NULL);
1312 	g_return_if_fail (MSTYLE_COLOR_BACK <= elem && elem < MSTYLE_ELEMENT_MAX);
1313 
1314 	if (elem_is_set (style, elem)) {
1315 		elem_clear_contents (style, elem);
1316 		elem_unset (style, elem);
1317 	}
1318 }
1319 
1320 /**
1321  * gnm_style_merge:
1322  * @base: #GnmStyle
1323  * @overlay: #GnmStyle
1324  *
1325  * Applies all active elements of @overlay onto @base.
1326  **/
1327 void
gnm_style_merge(GnmStyle * base,GnmStyle const * overlay)1328 gnm_style_merge (GnmStyle *base, GnmStyle const *overlay)
1329 {
1330 	unsigned i;
1331 	if (base == overlay)
1332 		return;
1333 	for (i = 0; i < MSTYLE_ELEMENT_MAX; i++)
1334 		if (elem_is_set (overlay, i)) {
1335 			elem_clear_contents (base, i);
1336 			elem_assign_contents (base, overlay, i);
1337 			elem_changed (base, i);
1338 		}
1339 }
1340 
1341 /**
1342  * gnm_style_merge_element:
1343  * @dst: Destination style
1344  * @src: Source style
1345  * @elem: Element to replace
1346  *
1347  * This function replaces element @elem in style @dst with element @elem
1348  * in style @src. (If element @elem was already set in style @dst then
1349  * the element will first be unset)
1350  **/
1351 void
gnm_style_merge_element(GnmStyle * dst,GnmStyle const * src,GnmStyleElement elem)1352 gnm_style_merge_element (GnmStyle *dst, GnmStyle const *src, GnmStyleElement elem)
1353 {
1354 	g_return_if_fail (src != NULL);
1355 	g_return_if_fail (dst != NULL);
1356 	g_return_if_fail (src != dst);
1357 
1358 	if (elem_is_set (src, elem)) {
1359 		elem_clear_contents (dst, elem);
1360 		elem_assign_contents (dst, src, elem);
1361 		elem_set (dst, elem);
1362 		elem_changed (dst, elem);
1363 	}
1364 }
1365 
1366 /**
1367  * gnm_style_set_font_color:
1368  * @style: #GnmStyle to change
1369  * @col: (transfer full): #GnmColor
1370  *
1371  * Set the color used for fonts.
1372  */
1373 void
gnm_style_set_font_color(GnmStyle * style,GnmColor * col)1374 gnm_style_set_font_color (GnmStyle *style, GnmColor *col)
1375 {
1376 	g_return_if_fail (style != NULL);
1377 	g_return_if_fail (col != NULL);
1378 
1379 	elem_changed (style, MSTYLE_FONT_COLOR);
1380 	if (elem_is_set (style, MSTYLE_FONT_COLOR))
1381 		style_color_unref (style->color.font);
1382 	else
1383 		elem_set (style, MSTYLE_FONT_COLOR);
1384 	elem_changed (style, MSTYLE_FONT_COLOR);
1385 	style->color.font = col;
1386 	gnm_style_clear_pango (style);
1387 }
1388 
1389 /**
1390  * gnm_style_set_back_color:
1391  * @style: #GnmStyle to change
1392  * @col: (transfer full): #GnmColor
1393  *
1394  * Assigns @col as the background of @style.
1395  *
1396  * NOTE: the background colour is only visible if GnmStyle::pattern > 0
1397  **/
1398 void
gnm_style_set_back_color(GnmStyle * style,GnmColor * col)1399 gnm_style_set_back_color (GnmStyle *style, GnmColor *col)
1400 {
1401 	g_return_if_fail (style != NULL);
1402 	g_return_if_fail (col != NULL);
1403 
1404 	elem_changed (style, MSTYLE_COLOR_BACK);
1405 	if (elem_is_set (style, MSTYLE_COLOR_BACK))
1406 		style_color_unref (style->color.back);
1407 	else
1408 		elem_set (style, MSTYLE_COLOR_BACK);
1409 	style->color.back = col;
1410 	gnm_style_clear_pango (style);
1411 }
1412 
1413 /**
1414  * gnm_style_set_pattern_color:
1415  * @style: #GnmStyle to change
1416  * @col: (transfer full): #GnmColor
1417  *
1418  * Set the color used for pattern.
1419  */
1420 void
gnm_style_set_pattern_color(GnmStyle * style,GnmColor * col)1421 gnm_style_set_pattern_color (GnmStyle *style, GnmColor *col)
1422 {
1423 	g_return_if_fail (style != NULL);
1424 	g_return_if_fail (col != NULL);
1425 
1426 	elem_changed (style, MSTYLE_COLOR_PATTERN);
1427 	if (elem_is_set (style, MSTYLE_COLOR_PATTERN))
1428 		style_color_unref (style->color.pattern);
1429 	else
1430 		elem_set (style, MSTYLE_COLOR_PATTERN);
1431 	style->color.pattern = col;
1432 	gnm_style_clear_pango (style);
1433 }
1434 
1435 /**
1436  * gnm_style_get_font_color:
1437  * @style: #GnmStyle to query
1438  *
1439  * Returns: (transfer none) (nullable): #GnmColor used for font.
1440  */
1441 GnmColor *
gnm_style_get_font_color(GnmStyle const * style)1442 gnm_style_get_font_color (GnmStyle const *style)
1443 {
1444 	g_return_val_if_fail (style != NULL, NULL);
1445 	g_return_val_if_fail (elem_is_set (style, MSTYLE_FONT_COLOR), NULL);
1446 	return style->color.font;
1447 }
1448 
1449 /**
1450  * gnm_style_get_back_color:
1451  * @style: #GnmStyle to query
1452  *
1453  * Returns: (transfer none) (nullable): #GnmColor used for background.
1454  */
1455 GnmColor *
gnm_style_get_back_color(GnmStyle const * style)1456 gnm_style_get_back_color (GnmStyle const *style)
1457 {
1458 	g_return_val_if_fail (style != NULL, NULL);
1459 	g_return_val_if_fail (elem_is_set (style, MSTYLE_COLOR_BACK), NULL);
1460 	return style->color.back;
1461 }
1462 
1463 /**
1464  * gnm_style_get_pattern_color:
1465  * @style: #GnmStyle to query
1466  *
1467  * Returns: (transfer none) (nullable): #GnmColor used for pattern.
1468  */
1469 GnmColor *
gnm_style_get_pattern_color(GnmStyle const * style)1470 gnm_style_get_pattern_color (GnmStyle const *style)
1471 {
1472 	g_return_val_if_fail (style != NULL, NULL);
1473 	g_return_val_if_fail (elem_is_set (style, MSTYLE_COLOR_PATTERN), NULL);
1474 	return style->color.pattern;
1475 }
1476 
1477 /**
1478  * gnm_style_set_border:
1479  * @style: #GnmStyle to change
1480  * @elem: Border element
1481  * @border: (transfer full) (nullable): new #GnmBorder for @style.
1482  */
1483 void
gnm_style_set_border(GnmStyle * style,GnmStyleElement elem,GnmBorder * border)1484 gnm_style_set_border (GnmStyle *style, GnmStyleElement elem,
1485 		      GnmBorder *border)
1486 {
1487 	g_return_if_fail (style != NULL);
1488 
1489 	/* NOTE : It is legal for border to be NULL */
1490 	switch (elem) {
1491 	case MSTYLE_ANY_BORDER:
1492 		elem_changed (style, elem);
1493 		elem_set (style, elem);
1494 		elem -= MSTYLE_BORDER_TOP;
1495 		gnm_style_border_unref (style->borders[elem]);
1496 		style->borders[elem] = border;
1497 		break;
1498 	default:
1499 		g_warning ("Not a border element");
1500 		break;
1501 	}
1502 }
1503 
1504 /**
1505  * gnm_style_get_border:
1506  * @style: #GnmStyle to query
1507  * @elem: Border element
1508  *
1509  * Returns: (transfer none) (nullable): The #GnmBorder for a single
1510  * border element.
1511  */
1512 GnmBorder *
gnm_style_get_border(GnmStyle const * style,GnmStyleElement elem)1513 gnm_style_get_border (GnmStyle const *style, GnmStyleElement elem)
1514 {
1515 	g_return_val_if_fail (style != NULL, NULL);
1516 
1517 	switch (elem) {
1518 	case MSTYLE_ANY_BORDER:
1519 		return style->borders[elem - MSTYLE_BORDER_TOP ];
1520 
1521 	default:
1522 		g_warning ("Not a border element");
1523 		return NULL;
1524 	}
1525 }
1526 
1527 /**
1528  * gnm_style_set_pattern:
1529  * @style: #GnmStyle to change
1530  * @pattern: pattern code
1531  **/
1532 void
gnm_style_set_pattern(GnmStyle * style,int pattern)1533 gnm_style_set_pattern (GnmStyle *style, int pattern)
1534 {
1535 	g_return_if_fail (style != NULL);
1536 	g_return_if_fail (pattern >= 0);
1537 	g_return_if_fail (pattern < GNM_PATTERNS_MAX);
1538 
1539 	elem_changed (style, MSTYLE_PATTERN);
1540 	elem_set (style, MSTYLE_PATTERN);
1541 	style->pattern = pattern;
1542 }
1543 
1544 int
gnm_style_get_pattern(GnmStyle const * style)1545 gnm_style_get_pattern (GnmStyle const *style)
1546 {
1547 	g_return_val_if_fail (style != NULL, 0);
1548 	g_return_val_if_fail (elem_is_set (style, MSTYLE_PATTERN), 0);
1549 
1550 	return style->pattern;
1551 }
1552 
1553 /**
1554  * gnm_style_get_font:
1555  * @style: #GnmStyle to query
1556  * @context: #PangoContext
1557  *
1558  * Returns: (transfer none): GnmFont implied by @style.
1559  **/
1560 GnmFont *
gnm_style_get_font(GnmStyle const * style,PangoContext * context)1561 gnm_style_get_font (GnmStyle const *style, PangoContext *context)
1562 {
1563 	g_return_val_if_fail (style != NULL, NULL);
1564 
1565 	if (!style->font || style->font_context != context) {
1566 		char const *name;
1567 		gboolean bold, italic;
1568 		double size;
1569 
1570 		gnm_style_clear_font ((GnmStyle *)style);
1571 
1572 		if (elem_is_set (style, MSTYLE_FONT_NAME))
1573 			name = gnm_style_get_font_name (style);
1574 		else
1575 			name = DEFAULT_FONT;
1576 
1577 		if (elem_is_set (style, MSTYLE_FONT_BOLD))
1578 			bold = gnm_style_get_font_bold (style);
1579 		else
1580 			bold = FALSE;
1581 
1582 		if (elem_is_set (style, MSTYLE_FONT_ITALIC))
1583 			italic = gnm_style_get_font_italic (style);
1584 		else
1585 			italic = FALSE;
1586 
1587 		if (elem_is_set (style, MSTYLE_FONT_SIZE))
1588 			size = gnm_style_get_font_size (style);
1589 		else
1590 			size = DEFAULT_SIZE;
1591 
1592 		((GnmStyle *)style)->font =
1593 			gnm_font_new (context, name, size, bold, italic);
1594 		((GnmStyle *)style)->font_context = g_object_ref (context);
1595 	}
1596 
1597 	return style->font;
1598 }
1599 
1600 /**
1601  * gnm_style_set_font_name:
1602  * @style: #GnmStyle to change
1603  * @name: the font name as a string
1604  */
1605 void
gnm_style_set_font_name(GnmStyle * style,char const * name)1606 gnm_style_set_font_name (GnmStyle *style, char const *name)
1607 {
1608 	g_return_if_fail (name != NULL);
1609 	g_return_if_fail (style != NULL);
1610 
1611 	elem_changed (style, MSTYLE_FONT_NAME);
1612 	if (elem_is_set (style, MSTYLE_FONT_NAME))
1613 		go_string_unref (style->font_detail.name);
1614 	else
1615 		elem_set (style, MSTYLE_FONT_NAME);
1616 	style->font_detail.name = go_string_new (name);
1617 	gnm_style_clear_font (style);
1618 	gnm_style_clear_pango (style);
1619 }
1620 
1621 /**
1622  * gnm_style_get_font_name:
1623  * @style: the style to query
1624  *
1625  * Returns: (transfer none): the currently set font name
1626  */
1627 char const *
gnm_style_get_font_name(GnmStyle const * style)1628 gnm_style_get_font_name (GnmStyle const *style)
1629 {
1630 	g_return_val_if_fail (style != NULL, NULL);
1631 	g_return_val_if_fail (elem_is_set (style, MSTYLE_FONT_NAME), NULL);
1632 
1633 	return style->font_detail.name->str;
1634 }
1635 
1636 /**
1637  * gnm_style_set_font_bold:
1638  * @style: #GnmStyle to change
1639  * @bold: %TRUE for bold, %FALSE for regular
1640  */
1641 void
gnm_style_set_font_bold(GnmStyle * style,gboolean bold)1642 gnm_style_set_font_bold (GnmStyle *style, gboolean bold)
1643 {
1644 	g_return_if_fail (style != NULL);
1645 
1646 	elem_changed (style, MSTYLE_FONT_BOLD);
1647 	elem_set (style, MSTYLE_FONT_BOLD);
1648 	style->font_detail.bold = !!bold;
1649 	gnm_style_clear_font (style);
1650 	gnm_style_clear_pango (style);
1651 }
1652 
1653 /**
1654  * gnm_style_get_font_bold:
1655  * @style: #GnmStyle to query
1656  *
1657  * Returns: %TRUE if the style has a bold font.
1658  */
1659 gboolean
gnm_style_get_font_bold(GnmStyle const * style)1660 gnm_style_get_font_bold (GnmStyle const *style)
1661 {
1662 	g_return_val_if_fail (style != NULL, FALSE);
1663 	g_return_val_if_fail (elem_is_set (style, MSTYLE_FONT_BOLD), FALSE);
1664 
1665 	return style->font_detail.bold;
1666 }
1667 
1668 /**
1669  * gnm_style_set_font_italic:
1670  * @style: #GnmStyle to change
1671  * @italic: %TRUE for italic, %FALSE for regular
1672  */
1673 void
gnm_style_set_font_italic(GnmStyle * style,gboolean italic)1674 gnm_style_set_font_italic (GnmStyle *style, gboolean italic)
1675 {
1676 	g_return_if_fail (style != NULL);
1677 
1678 	elem_changed (style, MSTYLE_FONT_ITALIC);
1679 	elem_set (style, MSTYLE_FONT_ITALIC);
1680 	style->font_detail.italic = !!italic;
1681 	gnm_style_clear_font (style);
1682 	gnm_style_clear_pango (style);
1683 }
1684 
1685 /**
1686  * gnm_style_get_font_italic:
1687  * @style: #GnmStyle to query
1688  *
1689  * Returns: %TRUE if the style has an italic font.
1690  */
1691 gboolean
gnm_style_get_font_italic(GnmStyle const * style)1692 gnm_style_get_font_italic (GnmStyle const *style)
1693 {
1694 	g_return_val_if_fail (style != NULL, FALSE);
1695 	g_return_val_if_fail (elem_is_set (style, MSTYLE_FONT_ITALIC), FALSE);
1696 
1697 	return style->font_detail.italic;
1698 }
1699 
1700 /**
1701  * gnm_style_set_font_uline:
1702  * @style: #GnmStyle to change
1703  * @ul: #GnmUnderline specifying type of underlining
1704  **/
1705 void
gnm_style_set_font_uline(GnmStyle * style,GnmUnderline const underline)1706 gnm_style_set_font_uline (GnmStyle *style, GnmUnderline const underline)
1707 {
1708 	g_return_if_fail (style != NULL);
1709 	g_return_if_fail (underline >= UNDERLINE_NONE && underline <= UNDERLINE_DOUBLE_LOW);
1710 
1711 	elem_changed (style, MSTYLE_FONT_UNDERLINE);
1712 	elem_set (style, MSTYLE_FONT_UNDERLINE);
1713 	style->font_detail.underline = underline;
1714 	gnm_style_clear_pango (style);
1715 }
1716 
1717 /**
1718  * gnm_style_get_font_uline:
1719  * @style: #GnmStyle to query
1720  *
1721  * Returns: #GnmUnderline specifying type of underlining
1722  **/
1723 GnmUnderline
gnm_style_get_font_uline(GnmStyle const * style)1724 gnm_style_get_font_uline (GnmStyle const *style)
1725 {
1726 	g_return_val_if_fail (style != NULL, UNDERLINE_NONE);
1727 	g_return_val_if_fail (elem_is_set (style, MSTYLE_FONT_UNDERLINE), UNDERLINE_NONE);
1728 
1729 	return style->font_detail.underline;
1730 }
1731 
1732 /**
1733  * gnm_style_set_font_strike:
1734  * @style: #GnmStyle to change
1735  * @strike: %TRUE for strikethrough, %FALSE for regular
1736  */
1737 void
gnm_style_set_font_strike(GnmStyle * style,gboolean strikethrough)1738 gnm_style_set_font_strike (GnmStyle *style, gboolean strikethrough)
1739 {
1740 	g_return_if_fail (style != NULL);
1741 
1742 	elem_changed (style, MSTYLE_FONT_STRIKETHROUGH);
1743 	elem_set (style, MSTYLE_FONT_STRIKETHROUGH);
1744 	style->font_detail.strikethrough = !!strikethrough;
1745 	gnm_style_clear_pango (style);
1746 }
1747 
1748 /**
1749  * gnm_style_get_font_strike:
1750  * @style: #GnmStyle to query
1751  *
1752  * Returns: %TRUE for strikethrough, %FALSE for regular
1753  */
1754 gboolean
gnm_style_get_font_strike(GnmStyle const * style)1755 gnm_style_get_font_strike (GnmStyle const *style)
1756 {
1757 	g_return_val_if_fail (elem_is_set (style, MSTYLE_FONT_STRIKETHROUGH), FALSE);
1758 
1759 	return style->font_detail.strikethrough;
1760 }
1761 
1762 /**
1763  * gnm_style_set_font_script:
1764  * @style: #GnmStyle to change
1765  * @script: #GOFontScript specifying super or subscript
1766  **/
1767 void
gnm_style_set_font_script(GnmStyle * style,GOFontScript script)1768 gnm_style_set_font_script (GnmStyle *style, GOFontScript script)
1769 {
1770 	g_return_if_fail (style != NULL);
1771 	elem_changed (style, MSTYLE_FONT_SCRIPT);
1772 	elem_set (style, MSTYLE_FONT_SCRIPT);
1773 	style->font_detail.script = script;
1774 	gnm_style_clear_pango (style);
1775 }
1776 
1777 /**
1778  * gnm_style_get_font_script:
1779  * @style: #GnmStyle to query
1780  *
1781  * Returns: #GOFontScript specifying super or subscript
1782  **/
1783 GOFontScript
gnm_style_get_font_script(GnmStyle const * style)1784 gnm_style_get_font_script (GnmStyle const *style)
1785 {
1786 	g_return_val_if_fail (style != NULL, GO_FONT_SCRIPT_STANDARD);
1787 	g_return_val_if_fail (elem_is_set (style, MSTYLE_FONT_SCRIPT), GO_FONT_SCRIPT_STANDARD);
1788 
1789 	return style->font_detail.script;
1790 }
1791 
1792 /**
1793  * gnm_style_set_font_size:
1794  * @style: #GnmStyle to change
1795  * @size: Font size in points
1796  **/
1797 void
gnm_style_set_font_size(GnmStyle * style,double size)1798 gnm_style_set_font_size (GnmStyle *style, double size)
1799 {
1800 	g_return_if_fail (style != NULL);
1801 	g_return_if_fail (size >= 1.);
1802 	elem_changed (style, MSTYLE_FONT_SIZE);
1803 	elem_set (style, MSTYLE_FONT_SIZE);
1804 	style->font_detail.size = size;
1805 	gnm_style_clear_font (style);
1806 	gnm_style_clear_pango (style);
1807 }
1808 
1809 /**
1810  * gnm_style_get_font_size:
1811  * @style: #GnmStyle to query
1812  *
1813  * Returns: Font size in points
1814  **/
1815 double
gnm_style_get_font_size(GnmStyle const * style)1816 gnm_style_get_font_size (GnmStyle const *style)
1817 {
1818 	g_return_val_if_fail (style != NULL, 12.0);
1819 	g_return_val_if_fail (elem_is_set (style, MSTYLE_FONT_SIZE), 12.0);
1820 
1821 	return style->font_detail.size;
1822 }
1823 
1824 /**
1825  * gnm_style_set_format:
1826  * @style: #GnmStyle to change
1827  * @fmt: #GOFormat
1828  */
1829 void
gnm_style_set_format(GnmStyle * style,GOFormat const * fmt)1830 gnm_style_set_format (GnmStyle *style, GOFormat const *fmt)
1831 {
1832 	g_return_if_fail (style != NULL);
1833 	g_return_if_fail (fmt != NULL);
1834 
1835 	elem_changed (style, MSTYLE_FORMAT);
1836 	go_format_ref (fmt);
1837 	elem_clear_contents (style, MSTYLE_FORMAT);
1838 	elem_set (style, MSTYLE_FORMAT);
1839 	style->format = fmt;
1840 }
1841 
1842 /*
1843  * gnm_style_set_format_text:
1844  * @style: mstyle to change.
1845  * @format: An *untranslated* format string.
1846  */
1847 void
gnm_style_set_format_text(GnmStyle * style,char const * format)1848 gnm_style_set_format_text (GnmStyle *style, char const *format)
1849 {
1850 	GOFormat *sf;
1851 
1852 	g_return_if_fail (style != NULL);
1853 	g_return_if_fail (format != NULL);
1854 
1855 	sf = go_format_new_from_XL (format);
1856 	gnm_style_set_format (style, sf);
1857 	go_format_unref (sf);
1858 }
1859 
1860 /**
1861  * gnm_style_get_format:
1862  * @style: #GnmStyle to query
1863  *
1864  * Returns: (transfer none): #GOFormat
1865  */
1866 const GOFormat *
gnm_style_get_format(GnmStyle const * style)1867 gnm_style_get_format (GnmStyle const *style)
1868 {
1869 	g_return_val_if_fail (style != NULL, NULL);
1870 	g_return_val_if_fail (elem_is_set (style, MSTYLE_FORMAT), NULL);
1871 
1872 	return style->format;
1873 }
1874 
1875 /**
1876  * gnm_style_set_align_h:
1877  * @style: #GnmStyle to change
1878  * @a: A #GnmHAlign
1879  **/
1880 void
gnm_style_set_align_h(GnmStyle * style,GnmHAlign a)1881 gnm_style_set_align_h (GnmStyle *style, GnmHAlign a)
1882 {
1883 	g_return_if_fail (style != NULL);
1884 
1885 	elem_changed (style, MSTYLE_ALIGN_H);
1886 	elem_set (style, MSTYLE_ALIGN_H);
1887 	style->h_align = a;
1888 }
1889 
1890 /**
1891  * gnm_style_get_align_h:
1892  * @style: #GnmStyle to query
1893  *
1894  * Returns: A #GnmHAlign
1895  **/
1896 GnmHAlign
gnm_style_get_align_h(GnmStyle const * style)1897 gnm_style_get_align_h (GnmStyle const *style)
1898 {
1899 	g_return_val_if_fail (style != NULL, GNM_HALIGN_LEFT);
1900 	g_return_val_if_fail (elem_is_set (style, MSTYLE_ALIGN_H), GNM_HALIGN_LEFT);
1901 
1902 	return style->h_align;
1903 }
1904 
1905 /**
1906  * gnm_style_set_align_v:
1907  * @style: #GnmStyle to change
1908  * @a: A #GnmVAlign
1909  **/
1910 void
gnm_style_set_align_v(GnmStyle * style,GnmVAlign a)1911 gnm_style_set_align_v (GnmStyle *style, GnmVAlign a)
1912 {
1913 	g_return_if_fail (style != NULL);
1914 
1915 	elem_changed (style, MSTYLE_ALIGN_V);
1916 	elem_set (style, MSTYLE_ALIGN_V);
1917 	style->v_align = a;
1918 }
1919 
1920 /**
1921  * gnm_style_get_align_v:
1922  * @style: #GnmStyle to query
1923  *
1924  * Returns: A #GnmVAlign
1925  **/
1926 GnmVAlign
gnm_style_get_align_v(GnmStyle const * style)1927 gnm_style_get_align_v (GnmStyle const *style)
1928 {
1929 	g_return_val_if_fail (style != NULL, GNM_VALIGN_TOP);
1930 	g_return_val_if_fail (elem_is_set (style, MSTYLE_ALIGN_V), GNM_VALIGN_TOP);
1931 
1932 	return style->v_align;
1933 }
1934 
1935 /**
1936  * gnm_style_set_indent:
1937  * @style: #GnmStyle to change
1938  * @i: Indentation amount
1939  **/
1940 void
gnm_style_set_indent(GnmStyle * style,int i)1941 gnm_style_set_indent (GnmStyle *style, int i)
1942 {
1943 	g_return_if_fail (style != NULL);
1944 
1945 	elem_changed (style, MSTYLE_INDENT);
1946 	elem_set (style, MSTYLE_INDENT);
1947 	style->indent = i;
1948 }
1949 
1950 /**
1951  * gnm_style_get_indent:
1952  * @style: #GnmStyle to query
1953  *
1954  * Returns: Indentation amount
1955  **/
1956 int
gnm_style_get_indent(GnmStyle const * style)1957 gnm_style_get_indent (GnmStyle const *style)
1958 {
1959 	g_return_val_if_fail (style != NULL, 0);
1960 	g_return_val_if_fail (elem_is_set (style, MSTYLE_INDENT), 0);
1961 
1962 	return style->indent;
1963 }
1964 
1965 /**
1966  * gnm_style_set_rotation:
1967  * @style: #GnmStyle to change
1968  * @r: Rotation in degrees relative to horizontal
1969  **/
1970 void
gnm_style_set_rotation(GnmStyle * style,int rot_deg)1971 gnm_style_set_rotation (GnmStyle *style, int rot_deg)
1972 {
1973 	g_return_if_fail (style != NULL);
1974 
1975 	elem_changed (style, MSTYLE_ROTATION);
1976 	elem_set (style, MSTYLE_ROTATION);
1977 	style->rotation = rot_deg;
1978 }
1979 
1980 /**
1981  * gnm_style_get_rotation:
1982  * @style: #GnmStyle to query
1983  *
1984  * Returns: Rotation in degrees relative to horizontal
1985  **/
1986 int
gnm_style_get_rotation(GnmStyle const * style)1987 gnm_style_get_rotation (GnmStyle const *style)
1988 {
1989 	g_return_val_if_fail (style != NULL, 0);
1990 	g_return_val_if_fail (elem_is_set (style, MSTYLE_ROTATION), 0);
1991 
1992 	return style->rotation;
1993 }
1994 
1995 /**
1996  * gnm_style_set_text_dir:
1997  * @style: #GnmStyle to change
1998  * @text_dir: A #GnmTextDir
1999  **/
2000 void
gnm_style_set_text_dir(GnmStyle * style,GnmTextDir text_dir)2001 gnm_style_set_text_dir (GnmStyle *style, GnmTextDir text_dir)
2002 {
2003 	g_return_if_fail (style != NULL);
2004 
2005 	elem_changed (style, MSTYLE_TEXT_DIR);
2006 	elem_set (style, MSTYLE_TEXT_DIR);
2007 	style->text_dir = text_dir;
2008 }
2009 
2010 /**
2011  * gnm_style_get_text_dir:
2012  * @style: #GnmStyle to query
2013  *
2014  * Returns: A #GnmTextDir
2015  **/
2016 GnmTextDir
gnm_style_get_text_dir(GnmStyle const * style)2017 gnm_style_get_text_dir (GnmStyle const *style)
2018 {
2019 	g_return_val_if_fail (style != NULL, GNM_TEXT_DIR_CONTEXT);
2020 	g_return_val_if_fail (elem_is_set (style, MSTYLE_TEXT_DIR), GNM_TEXT_DIR_CONTEXT);
2021 
2022 	return style->text_dir;
2023 }
2024 
2025 /**
2026  * gnm_style_set_wrap_text:
2027  * @style: #GnmStyle to change
2028  * @f: %TRUE for wrapping, %FALSE for not
2029  **/
2030 void
gnm_style_set_wrap_text(GnmStyle * style,gboolean f)2031 gnm_style_set_wrap_text (GnmStyle *style, gboolean f)
2032 {
2033 	g_return_if_fail (style != NULL);
2034 
2035 	elem_changed (style, MSTYLE_WRAP_TEXT);
2036 	elem_set (style, MSTYLE_WRAP_TEXT);
2037 	style->wrap_text = !!f;
2038 }
2039 
2040 /**
2041  * gnm_style_get_wrap_text:
2042  * @style: #GnmStyle to query
2043  *
2044  * Returns: %TRUE for wrapping, %FALSE for not.  See also
2045  * gnm_style_get_effective_wrap_text.
2046  **/
2047 gboolean
gnm_style_get_wrap_text(GnmStyle const * style)2048 gnm_style_get_wrap_text (GnmStyle const *style)
2049 {
2050 	g_return_val_if_fail (elem_is_set (style, MSTYLE_WRAP_TEXT), FALSE);
2051 
2052 	return style->wrap_text;
2053 }
2054 
2055 /**
2056  * gnm_style_get_effective_wrap_text:
2057  * @style: #GnmStyle to query
2058  *
2059  * Returns: %TRUE for wrapping, %FALSE for not.  This will be %TRUE also
2060  * when either alignment is JUSTIFY.
2061  **/
2062 gboolean
gnm_style_get_effective_wrap_text(GnmStyle const * style)2063 gnm_style_get_effective_wrap_text (GnmStyle const *style)
2064 {
2065 	g_return_val_if_fail (style != NULL, FALSE);
2066 	g_return_val_if_fail (elem_is_set (style, MSTYLE_WRAP_TEXT), FALSE);
2067 	g_return_val_if_fail (elem_is_set (style, MSTYLE_ALIGN_V), FALSE);
2068 	g_return_val_if_fail (elem_is_set (style, MSTYLE_ALIGN_H), FALSE);
2069 
2070 	/* Note: GNM_HALIGN_GENERAL never expands to GNM_HALIGN_JUSTIFY.  */
2071 	return (style->wrap_text ||
2072 		style->v_align == GNM_VALIGN_JUSTIFY ||
2073 		style->v_align == GNM_VALIGN_DISTRIBUTED ||
2074 		style->h_align == GNM_HALIGN_JUSTIFY);
2075 }
2076 
2077 /**
2078  * gnm_style_set_shrink_to_fit:
2079  * @style: #GnmStyle to change
2080  * @f: %TRUE for shrink-to-fit, %FALSE for not
2081  **/
2082 void
gnm_style_set_shrink_to_fit(GnmStyle * style,gboolean f)2083 gnm_style_set_shrink_to_fit (GnmStyle *style, gboolean f)
2084 {
2085 	g_return_if_fail (style != NULL);
2086 
2087 	elem_changed (style, MSTYLE_SHRINK_TO_FIT);
2088 	elem_set (style, MSTYLE_SHRINK_TO_FIT);
2089 	style->shrink_to_fit = !!f;
2090 }
2091 
2092 /**
2093  * gnm_style_get_shrink_to_fit:
2094  * @style: #GnmStyle to query
2095  *
2096  * Returns: %TRUE for shrink-to-fit, %FALSE for not
2097  **/
2098 gboolean
gnm_style_get_shrink_to_fit(GnmStyle const * style)2099 gnm_style_get_shrink_to_fit (GnmStyle const *style)
2100 {
2101 	g_return_val_if_fail (style != NULL, FALSE);
2102 	g_return_val_if_fail (elem_is_set (style, MSTYLE_SHRINK_TO_FIT), FALSE);
2103 
2104 	return style->shrink_to_fit;
2105 }
2106 
2107 /**
2108  * gnm_style_set_contents_locked:
2109  * @style: #GnmStyle to change
2110  * @f: %TRUE for locked, %FALSE for not
2111  **/
2112 void
gnm_style_set_contents_locked(GnmStyle * style,gboolean f)2113 gnm_style_set_contents_locked (GnmStyle *style, gboolean f)
2114 {
2115 	g_return_if_fail (style != NULL);
2116 
2117 	elem_changed (style, MSTYLE_CONTENTS_LOCKED);
2118 	elem_set (style, MSTYLE_CONTENTS_LOCKED);
2119 	style->contents_locked = !!f;
2120 }
2121 
2122 /**
2123  * gnm_style_get_contents_locked:
2124  * @style: #GnmStyle to query
2125  *
2126  * Returns: %TRUE for locked, %FALSE for not
2127  **/
2128 gboolean
gnm_style_get_contents_locked(GnmStyle const * style)2129 gnm_style_get_contents_locked (GnmStyle const *style)
2130 {
2131 	g_return_val_if_fail (style != NULL, FALSE);
2132 	g_return_val_if_fail (elem_is_set (style, MSTYLE_CONTENTS_LOCKED), FALSE);
2133 
2134 	return style->contents_locked;
2135 }
2136 
2137 /**
2138  * gnm_style_set_contents_hidden:
2139  * @style: #GnmStyle to change
2140  * @f: %TRUE for hidden, %FALSE for not
2141  **/
2142 void
gnm_style_set_contents_hidden(GnmStyle * style,gboolean f)2143 gnm_style_set_contents_hidden (GnmStyle *style, gboolean f)
2144 {
2145 	g_return_if_fail (style != NULL);
2146 
2147 	elem_changed (style, MSTYLE_CONTENTS_HIDDEN);
2148 	elem_set (style, MSTYLE_CONTENTS_HIDDEN);
2149 	style->contents_hidden = !!f;
2150 }
2151 
2152 /**
2153  * gnm_style_get_contents_hidden:
2154  * @style: #GnmStyle to query
2155  *
2156  * Return: %TRUE for hidden, %FALSE for not
2157  **/
2158 gboolean
gnm_style_get_contents_hidden(GnmStyle const * style)2159 gnm_style_get_contents_hidden (GnmStyle const *style)
2160 {
2161 	g_return_val_if_fail (style != NULL, FALSE);
2162 	g_return_val_if_fail (elem_is_set (style, MSTYLE_CONTENTS_HIDDEN), FALSE);
2163 
2164 	return style->contents_hidden;
2165 }
2166 
2167 /**
2168  * gnm_style_set_validation:
2169  * @style: #GnmStyle to change
2170  * @v: (transfer full) (nullable): #GnmValidation
2171  **/
2172 void
gnm_style_set_validation(GnmStyle * style,GnmValidation * v)2173 gnm_style_set_validation (GnmStyle *style, GnmValidation *v)
2174 {
2175 	g_return_if_fail (style != NULL);
2176 
2177 	elem_clear_contents (style, MSTYLE_VALIDATION);
2178 	elem_changed (style, MSTYLE_VALIDATION);
2179 	elem_set (style, MSTYLE_VALIDATION);
2180 	style->validation = v;
2181 }
2182 
2183 /**
2184  * gnm_style_get_validation:
2185  * @style: #GnmStyle to query
2186  *
2187  * Returns: (transfer none) (nullable):
2188  **/
2189 GnmValidation const *
gnm_style_get_validation(GnmStyle const * style)2190 gnm_style_get_validation (GnmStyle const *style)
2191 {
2192 	g_return_val_if_fail (style != NULL, NULL);
2193 	g_return_val_if_fail (elem_is_set (style, MSTYLE_VALIDATION), NULL);
2194 
2195 	return style->validation;
2196 }
2197 
2198 /**
2199  * gnm_style_set_hlink:
2200  * @style: #GnmStyle to change
2201  * @lnk: (transfer full) (nullable): #GnmHLink
2202  *
2203  * This sets a link for @style.
2204  **/
2205 void
gnm_style_set_hlink(GnmStyle * style,GnmHLink * lnk)2206 gnm_style_set_hlink (GnmStyle *style, GnmHLink *lnk)
2207 {
2208 	g_return_if_fail (style != NULL);
2209 
2210 	elem_clear_contents (style, MSTYLE_HLINK);
2211 	elem_changed (style, MSTYLE_HLINK);
2212 	elem_set (style, MSTYLE_HLINK);
2213 	style->hlink = lnk;
2214 }
2215 
2216 /**
2217  * gnm_style_get_hlink:
2218  * @style: #GnmStyle to query
2219  *
2220  * Returns: (transfer none) (nullable): the associated #GnmHLink.
2221  **/
2222 GnmHLink *
gnm_style_get_hlink(GnmStyle const * style)2223 gnm_style_get_hlink (GnmStyle const *style)
2224 {
2225 	g_return_val_if_fail (style != NULL, NULL);
2226 	g_return_val_if_fail (elem_is_set (style, MSTYLE_HLINK), NULL);
2227 
2228 	return style->hlink;
2229 }
2230 
2231 /**
2232  * gnm_style_set_input_msg:
2233  * @style: #GnmStyle to change
2234  * @msg: (transfer full) (nullable): #GnmInputMsg
2235  *
2236  * This sets an input message for @style.
2237  **/
2238 void
gnm_style_set_input_msg(GnmStyle * style,GnmInputMsg * msg)2239 gnm_style_set_input_msg (GnmStyle *style, GnmInputMsg *msg)
2240 {
2241 	g_return_if_fail (style != NULL);
2242 
2243 	elem_clear_contents (style, MSTYLE_INPUT_MSG);
2244 	elem_changed (style, MSTYLE_INPUT_MSG);
2245 	elem_set (style, MSTYLE_INPUT_MSG);
2246 	style->input_msg = msg;
2247 }
2248 
2249 /**
2250  * gnm_style_get_input_msg:
2251  * @style: #GnmStyle to query
2252  *
2253  * Returns: (transfer none) (nullable): the currently set input message.
2254  **/
2255 GnmInputMsg *
gnm_style_get_input_msg(GnmStyle const * style)2256 gnm_style_get_input_msg (GnmStyle const *style)
2257 {
2258 	g_return_val_if_fail (style != NULL, NULL);
2259 	g_return_val_if_fail (elem_is_set (style, MSTYLE_INPUT_MSG), NULL);
2260 
2261 	return style->input_msg;
2262 }
2263 
2264 /**
2265  * gnm_style_set_conditions:
2266  * @style: #GnmStyle to change
2267  * @sc: (transfer full) (nullable): #GnmStyleConditions
2268  *
2269  * This sets conditional style for @style.
2270  **/
2271 void
gnm_style_set_conditions(GnmStyle * style,GnmStyleConditions * sc)2272 gnm_style_set_conditions (GnmStyle *style, GnmStyleConditions *sc)
2273 {
2274 	g_return_if_fail (style != NULL);
2275 
2276 	elem_clear_contents (style, MSTYLE_CONDITIONS);
2277 	elem_changed (style, MSTYLE_CONDITIONS);
2278 	elem_set (style, MSTYLE_CONDITIONS);
2279 	style->conditions = sc;
2280 }
2281 
2282 /**
2283  * gnm_style_get_conditions:
2284  * @style: #GnmStyle to query
2285  *
2286  * Returns: (transfer none) (nullable): the currently set conditional style.
2287  **/
2288 GnmStyleConditions *
gnm_style_get_conditions(GnmStyle const * style)2289 gnm_style_get_conditions (GnmStyle const *style)
2290 {
2291 	g_return_val_if_fail (style != NULL, NULL);
2292 	g_return_val_if_fail (elem_is_set (style, MSTYLE_CONDITIONS), NULL);
2293 	return style->conditions;
2294 }
2295 
2296 /**
2297  * gnm_style_get_cond_style:
2298  * @style: #GnmStyle to query
2299  * @ix: The index of the condition for which style is desired
2300  *
2301  * Returns: (transfer none): the resulting style from applying the condition's
2302  * style overlay onto @style.
2303  **/
2304 GnmStyle const *
gnm_style_get_cond_style(GnmStyle const * style,int ix)2305 gnm_style_get_cond_style (GnmStyle const *style, int ix)
2306 {
2307 	g_return_val_if_fail (style != NULL, NULL);
2308 	g_return_val_if_fail (elem_is_set (style, MSTYLE_CONDITIONS), NULL);
2309 	g_return_val_if_fail (style->conditions != NULL, NULL);
2310 	g_return_val_if_fail (ix >= 0 && (unsigned)ix < gnm_style_conditions_details (style->conditions)->len, NULL);
2311 
2312 	if (style->changed)
2313 		gnm_style_update ((GnmStyle *)style);
2314 
2315 	return g_ptr_array_index (style->cond_styles, ix);
2316 }
2317 
2318 void
gnm_style_link_dependents(GnmStyle * style,GnmRange const * r)2319 gnm_style_link_dependents (GnmStyle *style, GnmRange const *r)
2320 {
2321 	GnmStyleConditions *sc;
2322 	Sheet *sheet;
2323 
2324 	g_return_if_fail (style != NULL);
2325 	g_return_if_fail (r != NULL);
2326 
2327 	sheet = style->linked_sheet;
2328 
2329 	// ----------------------------------------
2330 
2331 	// Conditional formatting.
2332 	//
2333 	// We need to trigger a reformatting of the cell if a cell referenced
2334 	// by the condition changes.
2335 	sc = elem_is_set (style, MSTYLE_CONDITIONS)
2336 		? gnm_style_get_conditions (style)
2337 		: NULL;
2338 	if (sc) {
2339 		sheet_conditions_add (sheet, r, style);
2340 	}
2341 
2342 	// ----------------------------------------
2343 	// Validations.
2344 	//
2345 	// We can probably ignore those.  If a dependent cell changes such
2346 	// that a validation condition is no longer satisfied, it is
2347 	// grandfathered in as valid.
2348 }
2349 
2350 void
gnm_style_unlink_dependents(GnmStyle * style,GnmRange const * r)2351 gnm_style_unlink_dependents (GnmStyle *style, GnmRange const *r)
2352 {
2353 	GnmStyleConditions *sc;
2354 	Sheet *sheet;
2355 
2356 	g_return_if_fail (style != NULL);
2357 	g_return_if_fail (r != NULL);
2358 
2359 	sheet = style->linked_sheet;
2360 
2361 	sc = elem_is_set (style, MSTYLE_CONDITIONS)
2362 		? gnm_style_get_conditions (style)
2363 		: NULL;
2364 	if (sc) {
2365 		sheet_conditions_remove (sheet, r, style);
2366 	}
2367 
2368 	// Validation -- see gnm_style_link_dependents
2369 }
2370 
2371 
2372 /**
2373  * gnm_style_visible_in_blank:
2374  * @style: style to query
2375  *
2376  * Returns: %TRUE if the style is visible, i.e., not transparent.  Specifically
2377  * that means if it has a background or a visible border.
2378  */
2379 gboolean
gnm_style_visible_in_blank(GnmStyle const * style)2380 gnm_style_visible_in_blank (GnmStyle const *style)
2381 {
2382 	GnmStyleElement i;
2383 
2384 	g_return_val_if_fail (style != NULL, FALSE);
2385 
2386 	if (elem_is_set (style, MSTYLE_PATTERN) &&
2387 	    gnm_style_get_pattern (style) > 0)
2388 		return TRUE;
2389 
2390 	for (i = MSTYLE_BORDER_TOP; i <= MSTYLE_BORDER_DIAGONAL; ++i)
2391 		if (elem_is_set (style, i) &&
2392 		    gnm_style_border_visible_in_blank (gnm_style_get_border (style, i)))
2393 			return TRUE;
2394 
2395 	return FALSE;
2396 }
2397 
2398 static void
add_attr(PangoAttrList * attrs,PangoAttribute * attr)2399 add_attr (PangoAttrList *attrs, PangoAttribute *attr)
2400 {
2401 	attr->start_index = 0;
2402 	attr->end_index = G_MAXINT;
2403 	pango_attr_list_insert (attrs, attr);
2404 }
2405 
2406 /**
2407  * gnm_style_generate_attrs:
2408  * @style: style to query
2409  * @context: the context for the attributes
2410  * @zoom: zoom level
2411  *
2412  * Returns: (transfer full): a #PangoAttrList with attributes matching
2413  * @style.  Attributes where the default will serve are not included.
2414  * The foreground color is not included.
2415  */
2416 PangoAttrList *
gnm_style_get_pango_attrs(GnmStyle const * style,PangoContext * context,double zoom)2417 gnm_style_get_pango_attrs (GnmStyle const *style,
2418 			   PangoContext *context,
2419 			   double zoom)
2420 {
2421 	PangoAttrList *l;
2422 	GnmUnderline ul;
2423 	GnmFont *font = gnm_style_get_font (style, context);
2424 
2425 	if (style->pango_attrs) {
2426 		if (zoom == style->pango_attrs_zoom) {
2427 			pango_attr_list_ref (style->pango_attrs);
2428 			return style->pango_attrs;
2429 		}
2430 		pango_attr_list_unref (((GnmStyle *)style)->pango_attrs);
2431 	}
2432 
2433 	((GnmStyle *)style)->pango_attrs = l = pango_attr_list_new ();
2434 	((GnmStyle *)style)->pango_attrs_zoom = zoom;
2435 	((GnmStyle *)style)->pango_attrs_height = -1;
2436 
2437 	/* Foreground colour.  */
2438 	/* See http://bugzilla.gnome.org/show_bug.cgi?id=105322 */
2439 	if (0) {
2440 		GnmColor const *fore = style->color.font;
2441 		add_attr (l, go_color_to_pango (fore->go_color, TRUE));
2442 	}
2443 
2444 	/* Handle underlining.  */
2445 	ul = gnm_style_get_font_uline (style);
2446 	if (ul != UNDERLINE_NONE)
2447 		add_attr (l,
2448 			  pango_attr_underline_new (gnm_translate_underline_to_pango (ul)));
2449 
2450 	/* Handle strikethrough. */
2451 	if (gnm_style_get_font_strike (style))
2452 		add_attr (l, pango_attr_strikethrough_new (TRUE));
2453 
2454 	/* Handle script. */
2455 	switch (gnm_style_get_font_script (style)) {
2456 	default:
2457 	case GO_FONT_SCRIPT_STANDARD:
2458 		break;
2459 	case GO_FONT_SCRIPT_SUB:
2460 		add_attr (l, go_pango_attr_subscript_new (TRUE));
2461 		break;
2462 	case GO_FONT_SCRIPT_SUPER:
2463 		add_attr (l, go_pango_attr_superscript_new (TRUE));
2464 		break;
2465 	}
2466 
2467 	add_attr (l, pango_attr_font_desc_new (font->go.font->desc));
2468 
2469 	if (zoom != 1)
2470 		add_attr (l, pango_attr_scale_new (zoom));
2471 
2472 	pango_attr_list_ref (l);
2473 	return l;
2474 }
2475 
2476 /**
2477  * gnm_style_generate_attrs_full:
2478  * @style: style to query
2479  *
2480  * Returns: (transfer full): a #PangoAttrList with attributes matching
2481  * @style, even attributes where the default would have served.
2482  */
2483 PangoAttrList *
gnm_style_generate_attrs_full(GnmStyle const * style)2484 gnm_style_generate_attrs_full (GnmStyle const *style)
2485 {
2486 	GnmColor const *fore = style->color.font;
2487 	PangoAttrList *l = pango_attr_list_new ();
2488 
2489 	add_attr (l, pango_attr_family_new (gnm_style_get_font_name (style)));
2490 	add_attr (l, pango_attr_size_new (gnm_style_get_font_size (style) * PANGO_SCALE));
2491 	add_attr (l, pango_attr_style_new (gnm_style_get_font_italic (style)
2492 		? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL));
2493 	add_attr (l, pango_attr_weight_new (gnm_style_get_font_bold (style)
2494 		? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL));
2495 	add_attr (l, go_color_to_pango (fore->go_color, TRUE));
2496 	add_attr (l, pango_attr_strikethrough_new
2497 		  (gnm_style_get_font_strike (style)));
2498 	add_attr (l, pango_attr_underline_new
2499 		  (gnm_translate_underline_to_pango
2500 		   (gnm_style_get_font_uline (style))));
2501 	return l;
2502 }
2503 
2504 int
gnm_style_get_pango_height(GnmStyle const * style,PangoContext * context,double zoom)2505 gnm_style_get_pango_height (GnmStyle const *style,
2506 			    PangoContext *context,
2507 			    double zoom)
2508 {
2509 	PangoAttrList *attrs = gnm_style_get_pango_attrs (style, context, zoom);
2510 
2511 	if (style->pango_attrs_height == -1) {
2512 		int h;
2513 		PangoLayout *layout = pango_layout_new (context);
2514 		GOFormat const *fmt;
2515 		gboolean requires_translation = FALSE;
2516 
2517 		fmt = gnm_style_get_format (style);
2518 		if (!go_format_is_general (fmt)) {
2519 			GOFormatDetails details;
2520 			go_format_get_details (fmt, &details, NULL);
2521 			if (details.family == GO_FORMAT_SCIENTIFIC &&
2522 			    details.use_markup) {
2523 				PangoAttribute *a
2524 					= go_pango_attr_superscript_new (TRUE);
2525 				/* We want to superscript the "-01" in the */
2526 				/* string "+1.23456789E-01" */
2527 				a->start_index = 12;
2528 				a->end_index = 15;
2529 				pango_attr_list_insert (attrs, a);
2530 				requires_translation = TRUE;
2531 			}
2532 		}
2533 		pango_layout_set_attributes (layout, attrs);
2534 		pango_layout_set_text (layout, "+1.23456789E-01", -1);
2535 		if (requires_translation)
2536 			go_pango_translate_layout (layout);
2537 		pango_layout_get_pixel_size (layout, NULL, &h);
2538 		g_object_unref (layout);
2539 		((GnmStyle *)style)->pango_attrs_height = h;
2540 	}
2541 
2542 	pango_attr_list_unref (attrs);
2543 	return style->pango_attrs_height;
2544 }
2545 
2546 
2547 void
gnm_style_set_from_pango_attribute(GnmStyle * style,PangoAttribute const * attr)2548 gnm_style_set_from_pango_attribute (GnmStyle *style, PangoAttribute const *attr)
2549 {
2550 	switch (attr->klass->type) {
2551 	case PANGO_ATTR_FAMILY:
2552 		gnm_style_set_font_name (style, ((PangoAttrString *)attr)->value);
2553 		break;
2554 	case PANGO_ATTR_SIZE:
2555 		gnm_style_set_font_size (style,
2556 					 ((PangoAttrInt *)attr)->value / (double)PANGO_SCALE);
2557 		break;
2558 	case PANGO_ATTR_STYLE:
2559 		gnm_style_set_font_italic (style,
2560 			((PangoAttrInt *)attr)->value == PANGO_STYLE_ITALIC);
2561 		break;
2562 	case PANGO_ATTR_WEIGHT:
2563 		gnm_style_set_font_bold (style,
2564 			((PangoAttrInt *)attr)->value >= PANGO_WEIGHT_BOLD);
2565 		break;
2566 	case PANGO_ATTR_FOREGROUND:
2567 		gnm_style_set_font_color (style, gnm_color_new_pango (
2568 			&((PangoAttrColor *)attr)->color));
2569 		break;
2570 	case PANGO_ATTR_UNDERLINE:
2571 		gnm_style_set_font_uline
2572 			(style, gnm_translate_underline_from_pango
2573 			 (((PangoAttrInt *)attr)->value));
2574 		break;
2575 	case PANGO_ATTR_STRIKETHROUGH:
2576 		gnm_style_set_font_strike (style,
2577 			((PangoAttrInt *)attr)->value != 0);
2578 		break;
2579 	default : {
2580 		gboolean script_seen = FALSE, script_set = FALSE;
2581 		if (attr->klass->type == go_pango_attr_superscript_get_attr_type ()) {
2582 			script_seen = TRUE;
2583 			if (((GOPangoAttrSuperscript *)attr)->val == 1) {
2584 				script_set = TRUE;
2585 				gnm_style_set_font_script
2586 					(style, GO_FONT_SCRIPT_SUPER);
2587 			}
2588 		} else if (attr->klass->type == go_pango_attr_subscript_get_attr_type ()) {
2589 			script_seen = TRUE;
2590 			if (((GOPangoAttrSubscript *)attr)->val == 1) {
2591 				script_set = TRUE;
2592 				gnm_style_set_font_script
2593 					(style, GO_FONT_SCRIPT_SUB);
2594 			}
2595 		}
2596 		if (script_seen && !script_set)
2597 			gnm_style_set_font_script
2598 				(style, GO_FONT_SCRIPT_STANDARD);
2599 		break; /* ignored */
2600 	}
2601 	}
2602 }
2603 
2604 /* ------------------------------------------------------------------------- */
2605 
2606 static void
gnm_style_dump_color(GnmColor * color,GnmStyleElement elem)2607 gnm_style_dump_color (GnmColor *color, GnmStyleElement elem)
2608 {
2609 	if (color)
2610 		g_printerr ("\t%s: %x:%x:%x%s\n",
2611 			    gnm_style_element_name [elem],
2612 			    GO_COLOR_UINT_R (color->go_color),
2613 			    GO_COLOR_UINT_G (color->go_color),
2614 			    GO_COLOR_UINT_B (color->go_color),
2615 			    color->is_auto ? " auto" : "");
2616 	else
2617 		g_printerr ("\t%s: (NULL)\n", gnm_style_element_name [elem]);
2618 }
2619 
2620 static void
gnm_style_dump_border(GnmBorder * border,GnmStyleElement elem)2621 gnm_style_dump_border (GnmBorder *border, GnmStyleElement elem)
2622 {
2623 	g_printerr ("\t%s: ", gnm_style_element_name[elem]);
2624 	if (border)
2625 		g_printerr ("%d\n", border->line_type);
2626 	else
2627 		g_printerr ("blank\n");
2628 }
2629 
2630 /**
2631  * gnm_style_dump:
2632  * @style: style to dump
2633  *
2634  * This function dumps the given style's contents to stderr.  This is meant
2635  * for debug purposes only and doesn't do a very good job for, for example,
2636  * conditional style settings.
2637  */
2638 void
gnm_style_dump(GnmStyle const * style)2639 gnm_style_dump (GnmStyle const *style)
2640 {
2641 	int i;
2642 
2643 	g_printerr ("Style Refs %d\n", style->ref_count);
2644 	if (elem_is_set (style, MSTYLE_COLOR_BACK))
2645 		gnm_style_dump_color (style->color.back, MSTYLE_COLOR_BACK);
2646 	if (elem_is_set (style, MSTYLE_COLOR_PATTERN))
2647 		gnm_style_dump_color (style->color.pattern, MSTYLE_COLOR_PATTERN);
2648 
2649 	for (i = MSTYLE_BORDER_TOP; i <= MSTYLE_BORDER_DIAGONAL; ++i)
2650 		if (elem_is_set (style, i))
2651 			gnm_style_dump_border (style->borders[i-MSTYLE_BORDER_TOP], i);
2652 
2653 	if (elem_is_set (style, MSTYLE_PATTERN))
2654 		g_printerr ("\tpattern %d\n", style->pattern);
2655 	if (elem_is_set (style, MSTYLE_FONT_COLOR))
2656 		gnm_style_dump_color (style->color.font, MSTYLE_FONT_COLOR);
2657 	if (elem_is_set (style, MSTYLE_FONT_NAME))
2658 		g_printerr ("\tname '%s'\n", style->font_detail.name->str);
2659 	if (elem_is_set (style, MSTYLE_FONT_BOLD))
2660 		g_printerr (style->font_detail.bold ? "\tbold\n" : "\tnot bold\n");
2661 	if (elem_is_set (style, MSTYLE_FONT_ITALIC))
2662 		g_printerr (style->font_detail.italic ? "\titalic\n" : "\tnot italic\n");
2663 	if (elem_is_set (style, MSTYLE_FONT_UNDERLINE))
2664 		switch (style->font_detail.underline) {
2665 		default:
2666 		case UNDERLINE_NONE:
2667 			g_printerr ("\tno underline\n"); break;
2668 		case UNDERLINE_SINGLE:
2669 			g_printerr ("\tsingle underline\n"); break;
2670 		case UNDERLINE_DOUBLE:
2671 			g_printerr ("\tdouble underline\n"); break;
2672 		}
2673 	if (elem_is_set (style, MSTYLE_FONT_STRIKETHROUGH))
2674 		g_printerr (style->font_detail.strikethrough ? "\tstrikethrough\n" : "\tno strikethrough\n");
2675 	if (elem_is_set (style, MSTYLE_FONT_SCRIPT))
2676 		switch (style->font_detail.script) {
2677 		case GO_FONT_SCRIPT_SUB:
2678 			g_printerr ("\tsubscript\n"); break;
2679 		default:
2680 		case GO_FONT_SCRIPT_STANDARD:
2681 			g_printerr ("\tno super or sub\n"); break;
2682 		case GO_FONT_SCRIPT_SUPER:
2683 			g_printerr ("\tsuperscript\n"); break;
2684 		}
2685 	if (elem_is_set (style, MSTYLE_FONT_SIZE))
2686 		g_printerr ("\tsize %f\n", style->font_detail.size);
2687 	if (elem_is_set (style, MSTYLE_FORMAT)) {
2688 		const char *fmt = go_format_as_XL (style->format);
2689 		g_printerr ("\tformat '%s'\n", fmt);
2690 	}
2691 	if (elem_is_set (style, MSTYLE_ALIGN_V))
2692 		g_printerr ("\tvalign %hd\n", (short)style->v_align);
2693 	if (elem_is_set (style, MSTYLE_ALIGN_H))
2694 		g_printerr ("\thalign %hd\n", (short)style->h_align);
2695 	if (elem_is_set (style, MSTYLE_INDENT))
2696 		g_printerr ("\tindent %d\n", style->indent);
2697 	if (elem_is_set (style, MSTYLE_ROTATION))
2698 		g_printerr ("\trotation %d\n", style->rotation);
2699 	if (elem_is_set (style, MSTYLE_TEXT_DIR))
2700 		g_printerr ("\ttext dir %d\n", style->text_dir);
2701 	if (elem_is_set (style, MSTYLE_WRAP_TEXT))
2702 		g_printerr ("\twrap text %d\n", style->wrap_text);
2703 	if (elem_is_set (style, MSTYLE_SHRINK_TO_FIT))
2704 		g_printerr ("\tshrink to fit %d\n", style->shrink_to_fit);
2705 	if (elem_is_set (style, MSTYLE_CONTENTS_LOCKED))
2706 		g_printerr ("\tlocked %d\n", style->contents_locked);
2707 	if (elem_is_set (style, MSTYLE_CONTENTS_HIDDEN))
2708 		g_printerr ("\thidden %d\n", style->contents_hidden);
2709 	if (elem_is_set (style, MSTYLE_VALIDATION))
2710 		g_printerr ("\tvalidation %p\n", (void *)style->validation);
2711 	if (elem_is_set (style, MSTYLE_HLINK))
2712 		g_printerr ("\thlink %p\n", (void *)style->hlink);
2713 	if (elem_is_set (style, MSTYLE_INPUT_MSG))
2714 		g_printerr ("\tinput msg %p\n", (void *)style->input_msg);
2715 	if (elem_is_set (style, MSTYLE_CONDITIONS))
2716 		g_printerr ("\tconditions %p\n", (void *)style->conditions);
2717 }
2718 
2719 /* ------------------------------------------------------------------------- */
2720 
2721 /**
2722  * gnm_style_init: (skip)
2723  */
2724 void
gnm_style_init(void)2725 gnm_style_init (void)
2726 {
2727 	debug_style_deps = gnm_debug_flag ("style-deps");
2728 
2729 #if USE_MSTYLE_POOL
2730 	gnm_style_pool =
2731 		go_mem_chunk_new ("style pool",
2732 				   sizeof (GnmStyle),
2733 				   16 * 1024 - 128);
2734 #endif
2735 }
2736 
2737 #if USE_MSTYLE_POOL
2738 static void
cb_gnm_style_pool_leak(gpointer data,G_GNUC_UNUSED gpointer user)2739 cb_gnm_style_pool_leak (gpointer data, G_GNUC_UNUSED gpointer user)
2740 {
2741 	GnmStyle *style = data;
2742 	g_printerr ("Leaking style at %p.\n", (void *)style);
2743 	gnm_style_dump (style);
2744 }
2745 #endif
2746 
2747 /**
2748  * gnm_style_shutdown: (skip)
2749  */
2750 void
gnm_style_shutdown(void)2751 gnm_style_shutdown (void)
2752 {
2753 #if USE_MSTYLE_POOL
2754 	go_mem_chunk_foreach_leak (gnm_style_pool, cb_gnm_style_pool_leak, NULL);
2755 	go_mem_chunk_destroy (gnm_style_pool, FALSE);
2756 	gnm_style_pool = NULL;
2757 #endif
2758 }
2759