1 /*
2  * format-template.c : implementation of the template handling system.
3  *
4  * Copyright (C) Almer S. Tigelaar <almer@gnome.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include <gnumeric-config.h>
21 #include <glib/gi18n-lib.h>
22 #include <gnumeric.h>
23 #include <format-template.h>
24 
25 #include <mstyle.h>
26 #include <gutils.h>
27 #include <sheet.h>
28 #include <command-context.h>
29 #include <ranges.h>
30 #include <xml-sax.h>
31 #include <goffice/goffice.h>
32 #include <string.h>
33 #include <gsf/gsf-input-stdio.h>
34 
35 #define CC2XML(s) ((xmlChar const *)(s))
36 #define CXML2C(s) ((char const *)(s))
37 
38 static inline gboolean
attr_eq(const xmlChar * a,const char * s)39 attr_eq (const xmlChar *a, const char *s)
40 {
41 	return !strcmp (CXML2C (a), s);
42 }
43 
44 /******************************************************************************
45  * FormatTemplateMember - Getters/setters and creation
46  ******************************************************************************/
47 
48 /**
49  * gnm_ft_member_new:
50  *
51  * Create a new GnmFTMember
52  *
53  * Return value: the new GnmFTMember
54  **/
55 static GnmFTMember *
gnm_ft_member_new(void)56 gnm_ft_member_new (void)
57 {
58 	GnmFTMember *member;
59 
60 	member = g_new (GnmFTMember, 1);
61 
62 	member->col.offset	   = member->row.offset = 0;
63 	member->col.offset_gravity = member->row.offset_gravity = 1;
64 	member->col.size	   = member->row.size = 1;
65 	member->direction = FREQ_DIRECTION_NONE;
66 	member->repeat    = 0;
67 	member->skip      = 0;
68 	member->edge      = 0;
69 	member->mstyle    = NULL;
70 
71 	return member;
72 }
73 
74 /**
75  * gnm_ft_member_clone:
76  *
77  * Clone a template member
78  *
79  * Return value: a copy of @member
80  **/
81 static GnmFTMember *
gnm_ft_member_clone(GnmFTMember * member)82 gnm_ft_member_clone (GnmFTMember *member)
83 {
84 	GnmFTMember *clone = gnm_ft_member_new ();
85 
86 	clone->row = member->row;
87 	clone->col = member->col;
88 	clone->direction = member->direction;
89 	clone->repeat    = member->repeat;
90 	clone->skip      = member->skip;
91 	clone->edge      = member->edge;
92 	clone->mstyle    = member->mstyle;
93 	gnm_style_ref (member->mstyle);
94 
95 	return clone;
96 }
97 
98 /**
99  * gnm_ft_member_free:
100  * @member: GnmFTMember
101  *
102  * Frees an existing template member
103  **/
104 static void
gnm_ft_member_free(GnmFTMember * member)105 gnm_ft_member_free (GnmFTMember *member)
106 {
107 	g_return_if_fail (member != NULL);
108 
109 	if (member->mstyle) {
110 		gnm_style_unref (member->mstyle);
111 		member->mstyle = NULL;
112 	}
113 
114 	g_free (member);
115 }
116 
117 
118 /**
119  * gnm_ft_member_get_rect:
120  * @member:
121  * @r:
122  *
123  * Get the rectangular area covered by the GnmFTMember @member in the parent
124  * rectangle @r.
125  * NOTE : This simply calculates the rectangle, it does not calculate repetitions
126  *        or anything. That you'll have to do yourself :-)
127  *
128  * Return value: a GnmRange containing the effective rectangle of @member
129  **/
130 static GnmRange
gnm_ft_member_get_rect(GnmFTMember const * member,GnmRange const * r)131 gnm_ft_member_get_rect (GnmFTMember const *member, GnmRange const *r)
132 {
133 	GnmRange res;
134 
135 	res.start.row = res.end.row = 0;
136 	res.start.col = res.end.col = 0;
137 
138 	g_return_val_if_fail (member != NULL, res);
139 
140 	/* Calculate where the top left of the rectangle will come */
141 	if (member->row.offset_gravity > 0)
142 		res.start.row = r->start.row + member->row.offset;
143 	else
144 		res.end.row = r->end.row - member->row.offset;
145 
146 	if (member->col.offset_gravity > 0)
147 		res.start.col = r->start.col + member->col.offset;
148 	else
149 		res.end.col = r->end.col - member->col.offset;
150 
151 	/*
152 	 * Now that we know these coordinates we'll calculate the
153 	 * bottom right coordinates
154 	 */
155 	if (member->row.offset_gravity > 0) {
156 		if (member->row.size > 0)
157 			res.end.row = res.start.row + member->row.size - 1;
158 		else
159 			res.end.row = r->end.row + member->row.size;
160 	} else {
161 		if (member->row.size > 0)
162 			res.start.row = res.end.row - member->row.size + 1;
163 		else
164 			res.start.row = r->start.row - member->row.size;
165 	}
166 
167 	if (member->col.offset_gravity > 0) {
168 		if (member->col.size > 0)
169 			res.end.col = res.start.col + member->col.size - 1;
170 		else
171 			res.end.col = r->end.col + member->col.size;
172 	} else {
173 		if (member->col.size > 0)
174 			res.start.col = res.end.col - member->col.size + 1;
175 		else
176 			res.start.col = r->start.col - member->col.size;
177 	}
178 
179 	return res;
180 }
181 
182 /****************************************************************************/
183 
184 static gboolean
gnm_ft_member_valid(GnmFTMember const * member)185 gnm_ft_member_valid (GnmFTMember const *member)
186 {
187 	return (member &&
188 		member->mstyle &&
189 		member->direction >= FREQ_DIRECTION_NONE &&
190 		member->direction <= FREQ_DIRECTION_VERTICAL &&
191 		member->repeat >= -1 &&
192 		member->skip >= 0 &&
193 		member->edge >= 0);
194 }
195 
196 /******************************************************************************
197  * GnmFT - Creation/Destruction
198  ******************************************************************************/
199 
200 /**
201  * gnm_ft_new:
202  *
203  * Create a new 'empty' GnmFT
204  *
205  * Return value: the new GnmFT
206  **/
207 static GnmFT *
gnm_ft_new(void)208 gnm_ft_new (void)
209 {
210 	GnmFT *ft;
211 
212 	ft = g_new0 (GnmFT, 1);
213 
214 	ft->filename    = NULL;
215 	ft->author      = g_strdup (go_get_real_name ());
216 	ft->name        = g_strdup (N_("Name"));
217 	ft->description = g_strdup ("");
218 
219 	ft->category = NULL;
220 
221 	ft->members = NULL;
222 	ft->number    = TRUE;
223 	ft->border    = TRUE;
224 	ft->font      = TRUE;
225 	ft->patterns  = TRUE;
226 	ft->alignment = TRUE;
227 
228 	ft->edges.left   = TRUE;
229 	ft->edges.right  = TRUE;
230 	ft->edges.top    = TRUE;
231 	ft->edges.bottom = TRUE;
232 
233 	ft->table = g_hash_table_new_full ((GHashFunc)gnm_cellpos_hash,
234 					   (GEqualFunc)gnm_cellpos_equal,
235 					   (GDestroyNotify)g_free,
236 					   (GDestroyNotify)gnm_style_unref);
237 	ft->invalidate_hash = TRUE;
238 
239 	range_init (&ft->dimension, 0,0,0,0);
240 
241 	return ft;
242 }
243 
244 /**
245  * gnm_ft_free:
246  **/
247 void
gnm_ft_free(GnmFT * ft)248 gnm_ft_free (GnmFT *ft)
249 {
250 	g_return_if_fail (ft != NULL);
251 
252 	g_free (ft->filename);
253 	g_free (ft->author);
254 	g_free (ft->name);
255 	g_free (ft->description);
256 	g_slist_free_full (ft->members, (GDestroyNotify)gnm_ft_member_free);
257 	g_hash_table_destroy (ft->table);
258 
259 	g_free (ft);
260 }
261 
262 
263 static void
gnm_ft_set_name(GnmFT * ft,char const * name)264 gnm_ft_set_name (GnmFT *ft, char const *name)
265 {
266 	g_return_if_fail (ft != NULL);
267 	g_return_if_fail (name != NULL);
268 
269 	g_free (ft->name);
270 	ft->name = g_strdup (name);
271 }
272 
273 static void
gnm_ft_set_author(GnmFT * ft,char const * author)274 gnm_ft_set_author (GnmFT *ft, char const *author)
275 {
276 	g_return_if_fail (ft != NULL);
277 	g_return_if_fail (author != NULL);
278 
279 	g_free (ft->author);
280 	ft->author = g_strdup (author);
281 }
282 
283 static void
gnm_ft_set_description(GnmFT * ft,char const * description)284 gnm_ft_set_description (GnmFT *ft, char const *description)
285 {
286 	g_return_if_fail (ft != NULL);
287 	g_return_if_fail (description != NULL);
288 
289 	g_free (ft->description);
290 	ft->description = g_strdup (description);
291 }
292 
293 /**
294  * gnm_ft_clone:
295  * @ft: GnmFT
296  *
297  * Make a copy of @ft.
298  *
299  * Returns: transfer full): a copy of @ft
300  **/
301 GnmFT *
gnm_ft_clone(GnmFT const * ft)302 gnm_ft_clone (GnmFT const *ft)
303 {
304 	GnmFT *clone;
305 
306 	g_return_val_if_fail (ft != NULL, NULL);
307 
308 	clone = gnm_ft_new ();
309 	gnm_ft_set_author (clone, ft->author);
310 	gnm_ft_set_name (clone, ft->name);
311 	gnm_ft_set_description (clone, ft->description);
312 	g_free (clone->filename); clone->filename = g_strdup (ft->filename);
313 
314 	clone->category    = ft->category;
315 
316 	clone->members =
317 		g_slist_copy_deep (ft->members,
318 				   (GCopyFunc)gnm_ft_member_clone, NULL);
319 
320 	clone->number    = ft->number;
321 	clone->border    = ft->border;
322 	clone->font      = ft->font;
323 	clone->patterns  = ft->patterns;
324 	clone->alignment = ft->alignment;
325 	clone->edges     = ft->edges;
326 	clone->dimension = ft->dimension;
327 
328 	clone->invalidate_hash = TRUE;
329 
330 	return clone;
331 }
332 
333 GType
gnm_ft_get_type(void)334 gnm_ft_get_type (void)
335 {
336 	static GType t = 0;
337 
338 	if (t == 0) {
339 		t = g_boxed_type_register_static ("GnmFT",
340 			 (GBoxedCopyFunc)gnm_ft_clone,
341 			 (GBoxedFreeFunc)gnm_ft_free);
342 	}
343 	return t;
344 }
345 
346 #define GNM 100
347 #define GMR 200
348 
349 static GsfXMLInNS const template_ns[] = {
350 	GSF_XML_IN_NS (GMR, "http://www.gnome.org/gnumeric/format-template/v1"),
351 	GSF_XML_IN_NS (GNM, "http://www.gnumeric.org/v10.dtd"),
352 	GSF_XML_IN_NS_END
353 };
354 
355 static void
sax_information(GsfXMLIn * xin,xmlChar const ** attrs)356 sax_information (GsfXMLIn *xin, xmlChar const **attrs)
357 {
358 	GnmFT *ft = (GnmFT *)xin->user_state;
359 
360 	for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2) {
361 		if (attr_eq (attrs[0], "author"))
362 			gnm_ft_set_author (ft, CXML2C (attrs[1]));
363 		else if (attr_eq (attrs[0], "name"))
364 			gnm_ft_set_name (ft, CXML2C (attrs[1]));
365 		else if (attr_eq (attrs[0], "description"))
366 			gnm_ft_set_description (ft, CXML2C (attrs[1]));
367 	}
368 }
369 
370 static void
sax_members_end(GsfXMLIn * xin,G_GNUC_UNUSED GsfXMLBlob * blob)371 sax_members_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *blob)
372 {
373 	GnmFT *ft = (GnmFT *)xin->user_state;
374 	ft->members = g_slist_reverse (ft->members);
375 }
376 
377 static void
sax_member(GsfXMLIn * xin,xmlChar const ** attrs)378 sax_member (GsfXMLIn *xin, xmlChar const **attrs)
379 {
380 	GnmFT *ft = (GnmFT *)xin->user_state;
381 	GnmFTMember *member = gnm_ft_member_new ();
382 
383 	/* Order reversed in sax_members_end.  */
384 	ft->members = g_slist_prepend (ft->members, member);
385 }
386 
387 static void
sax_member_end(GsfXMLIn * xin,G_GNUC_UNUSED GsfXMLBlob * blob)388 sax_member_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *blob)
389 {
390 	GnmFT *ft = (GnmFT *)xin->user_state;
391 	GnmFTMember *member = ft->members->data;
392 
393 	if (!gnm_ft_member_valid (member)) {
394 		g_warning ("Invalid template member in %s\n", ft->filename);
395 		ft->members = g_slist_remove (ft->members, member);
396 		gnm_ft_member_free (member);
397 	}
398 }
399 
400 static void
sax_placement(GnmFTColRowInfo * info,xmlChar const ** attrs)401 sax_placement (GnmFTColRowInfo *info, xmlChar const **attrs)
402 {
403 	for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2) {
404 		if (gnm_xml_attr_int (attrs, "offset", &info->offset) ||
405 		    gnm_xml_attr_int (attrs, "offset_gravity", &info->offset_gravity))
406 			; /* Nothing */
407 	}
408 }
409 
410 static void
sax_row_placement(GsfXMLIn * xin,xmlChar const ** attrs)411 sax_row_placement (GsfXMLIn *xin, xmlChar const **attrs)
412 {
413 	GnmFT *ft = (GnmFT *)xin->user_state;
414 	GnmFTMember *member = ft->members->data;
415 	sax_placement (&member->row, attrs);
416 }
417 
418 static void
sax_col_placement(GsfXMLIn * xin,xmlChar const ** attrs)419 sax_col_placement (GsfXMLIn *xin, xmlChar const **attrs)
420 {
421 	GnmFT *ft = (GnmFT *)xin->user_state;
422 	GnmFTMember *member = ft->members->data;
423 	sax_placement (&member->col, attrs);
424 }
425 
426 static void
sax_dimensions(GnmFTColRowInfo * info,xmlChar const ** attrs)427 sax_dimensions (GnmFTColRowInfo *info, xmlChar const **attrs)
428 {
429 	for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2) {
430 		if (gnm_xml_attr_int (attrs, "size", &info->size))
431 			; /* Nothing */
432 	}
433 }
434 
435 static void
sax_row_dimensions(GsfXMLIn * xin,xmlChar const ** attrs)436 sax_row_dimensions (GsfXMLIn *xin, xmlChar const **attrs)
437 {
438 	GnmFT *ft = (GnmFT *)xin->user_state;
439 	GnmFTMember *member = ft->members->data;
440 	sax_dimensions (&member->row, attrs);
441 }
442 
443 static void
sax_col_dimensions(GsfXMLIn * xin,xmlChar const ** attrs)444 sax_col_dimensions (GsfXMLIn *xin, xmlChar const **attrs)
445 {
446 	GnmFT *ft = (GnmFT *)xin->user_state;
447 	GnmFTMember *member = ft->members->data;
448 	sax_dimensions (&member->col, attrs);
449 }
450 
451 static void
sax_frequency(GsfXMLIn * xin,xmlChar const ** attrs)452 sax_frequency (GsfXMLIn *xin, xmlChar const **attrs)
453 {
454 	GnmFT *ft = (GnmFT *)xin->user_state;
455 	GnmFTMember *member = ft->members->data;
456 
457 	for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2) {
458 		int i;
459 
460 		if (gnm_xml_attr_int (attrs, "direction", &i))
461 			member->direction = i;
462 		else if (gnm_xml_attr_int (attrs, "repeat", &member->repeat) ||
463 			 gnm_xml_attr_int (attrs, "skip", &member->skip) ||
464 			 gnm_xml_attr_int (attrs, "edge", &member->edge))
465 			; /* Nothing */
466 	}
467 }
468 
469 static void
sax_style_handler(GsfXMLIn * xin,GnmStyle * style,gpointer user)470 sax_style_handler (GsfXMLIn *xin, GnmStyle *style, gpointer user)
471 {
472 	GnmFT *ft = (GnmFT *)xin->user_state;
473 	GnmFTMember *member = ft->members->data;
474 	gnm_style_ref (style);
475 	member->mstyle = style;
476 }
477 
478 static gboolean
template_sax_unknown(GsfXMLIn * xin,xmlChar const * elem,xmlChar const ** attrs)479 template_sax_unknown (GsfXMLIn *xin, xmlChar const *elem, xmlChar const **attrs)
480 {
481 	g_return_val_if_fail (xin != NULL, FALSE);
482 	g_return_val_if_fail (xin->doc != NULL, FALSE);
483 	g_return_val_if_fail (xin->node != NULL, FALSE);
484 
485 	if (GMR == xin->node->ns_id &&
486 	    0 == strcmp (xin->node->id, "MEMBERS_MEMBER")) {
487 		char const *type_name = gsf_xml_in_check_ns (xin, CXML2C (elem), GNM);
488 		if (type_name && strcmp (type_name, "Style") == 0) {
489 			gnm_xml_prep_style_parser (xin, attrs,
490 						   sax_style_handler,
491 						   NULL);
492 			return TRUE;
493 		}
494 	}
495 	return FALSE;
496 }
497 
498 static GsfXMLInNode template_dtd[] = {
499 GSF_XML_IN_NODE_FULL (START, START, -1, NULL, GSF_XML_NO_CONTENT, FALSE, TRUE, NULL, NULL, 0),
500 GSF_XML_IN_NODE (START, TEMPLATE, GMR, "FormatTemplate", GSF_XML_NO_CONTENT, NULL, NULL),
501   GSF_XML_IN_NODE (TEMPLATE, TEMPLATE_INFORMATION, GMR, "Information", GSF_XML_NO_CONTENT, sax_information, NULL),
502   GSF_XML_IN_NODE (TEMPLATE, TEMPLATE_MEMBERS, GMR, "Members", GSF_XML_NO_CONTENT, NULL, sax_members_end),
503     GSF_XML_IN_NODE (TEMPLATE_MEMBERS, MEMBERS_MEMBER, GMR, "Member", GSF_XML_NO_CONTENT, sax_member, sax_member_end),
504       GSF_XML_IN_NODE (MEMBERS_MEMBER, MEMBER_ROW, GMR, "Row", GSF_XML_NO_CONTENT, NULL, NULL),
505         GSF_XML_IN_NODE (MEMBER_ROW, ROW_PLACEMENT, GMR, "Placement", GSF_XML_NO_CONTENT, sax_row_placement, NULL),
506         GSF_XML_IN_NODE (MEMBER_ROW, ROW_DIMENSIONS, GMR, "Dimensions", GSF_XML_NO_CONTENT, sax_row_dimensions, NULL),
507       GSF_XML_IN_NODE (MEMBERS_MEMBER, MEMBER_COL, GMR, "Col", GSF_XML_NO_CONTENT, NULL, NULL),
508         GSF_XML_IN_NODE (MEMBER_COL, COL_PLACEMENT, GMR, "Placement", GSF_XML_NO_CONTENT, sax_col_placement, NULL),
509         GSF_XML_IN_NODE (MEMBER_COL, COL_DIMENSIONS, GMR, "Dimensions", GSF_XML_NO_CONTENT, sax_col_dimensions, NULL),
510       GSF_XML_IN_NODE (MEMBERS_MEMBER, MEMBER_FREQUENCY, GMR, "Frequency", GSF_XML_NO_CONTENT, sax_frequency, NULL),
511   GSF_XML_IN_NODE_END
512 };
513 
514 /**
515  * gnm_ft_new_from_file:
516  * @context:
517  * @filename: The filename to load from
518  *
519  * Create a new GnmFT and load a template file
520  * into it.
521  *
522  * Return value: (transfer full): a new GnmFT (or %NULL on error)
523  **/
524 GnmFT *
gnm_ft_new_from_file(char const * filename,GOCmdContext * cc)525 gnm_ft_new_from_file (char const *filename, GOCmdContext *cc)
526 {
527 	GnmFT *ft = NULL;
528 	GsfXMLInDoc *doc = NULL;
529 	GnmLocale *locale;
530 	gboolean ok = FALSE;
531 	GsfInput *input = NULL;
532 
533 	g_return_val_if_fail (filename != NULL, NULL);
534 
535 	input = gsf_input_stdio_new (filename, NULL);
536 	if (!input) {
537 		go_cmd_context_error_import
538 			(cc,
539 			 _("Error while opening autoformat template"));
540 		goto done;
541 	}
542 
543 	doc = gsf_xml_in_doc_new (template_dtd, template_ns);
544 	if (doc == NULL)
545 		goto done;
546 	gsf_xml_in_doc_set_unknown_handler (doc, &template_sax_unknown);
547 
548 	ft = gnm_ft_new ();
549 	ft->filename = g_strdup (filename);
550 
551 	locale = gnm_push_C_locale ();
552 	ok = gsf_xml_in_doc_parse (doc, input, ft);
553 	gnm_pop_C_locale (locale);
554 
555  done:
556 	if (input) g_object_unref (input);
557 	if (doc) gsf_xml_in_doc_free (doc);
558 
559 	if (ft && !ok) {
560 		gnm_ft_free (ft);
561 		ft = NULL;
562 	}
563 
564 	return ft;
565 }
566 
567 
568 /**
569  * gnm_ft_compare_name:
570  * @a: First GnmFT
571  * @b: Second GnmFT
572  *
573  **/
574 gint
gnm_ft_compare_name(gconstpointer a,gconstpointer b)575 gnm_ft_compare_name (gconstpointer a, gconstpointer b)
576 {
577 	GnmFT const *ft_a = (GnmFT const *) a;
578 	GnmFT const *ft_b = (GnmFT const *) b;
579 
580 	return g_utf8_collate (_(ft_a->name), _(ft_b->name));
581 }
582 
583 /******************************************************************************
584  * GnmFT - Actual implementation (Filtering and calculating)
585  ******************************************************************************/
586 
587 /**
588  * format_template_filter_style:
589  * @ft:
590  * @mstyle:
591  * @fill_defaults: If set fill in the gaps with the "default" mstyle.
592  *
593  * Filter an mstyle and strip and replace certain elements
594  * based on what the user wants to apply.
595  * Basically you should pass FALSE as @fill_defaults, unless you want to have
596  * a completely filled style to be returned. If you set @fill_default to TRUE
597  * the returned mstyle might have some of its elements 'not set'
598  *
599  * Return value: The same mstyle as @mstyle with most likely some modifications
600  **/
601 static GnmStyle *
format_template_filter_style(GnmFT * ft,GnmStyle * mstyle,gboolean fill_defaults)602 format_template_filter_style (GnmFT *ft, GnmStyle *mstyle, gboolean fill_defaults)
603 {
604 	g_return_val_if_fail (ft != NULL, NULL);
605 	g_return_val_if_fail (mstyle != NULL, NULL);
606 
607 	/*
608 	 * Don't fill with defaults, this is perfect for when the
609 	 * mstyles are going to be 'merged' with other mstyles which
610 	 * have all their elements set
611 	 */
612 	if (!fill_defaults) {
613 		if (!ft->number) {
614 			gnm_style_unset_element (mstyle, MSTYLE_FORMAT);
615 		}
616 		if (!ft->border) {
617 			gnm_style_unset_element (mstyle, MSTYLE_BORDER_TOP);
618 			gnm_style_unset_element (mstyle, MSTYLE_BORDER_BOTTOM);
619 			gnm_style_unset_element (mstyle, MSTYLE_BORDER_LEFT);
620 			gnm_style_unset_element (mstyle, MSTYLE_BORDER_RIGHT);
621 			gnm_style_unset_element (mstyle, MSTYLE_BORDER_DIAGONAL);
622 			gnm_style_unset_element (mstyle, MSTYLE_BORDER_REV_DIAGONAL);
623 		}
624 		if (!ft->font) {
625 			gnm_style_unset_element (mstyle, MSTYLE_FONT_NAME);
626 			gnm_style_unset_element (mstyle, MSTYLE_FONT_BOLD);
627 			gnm_style_unset_element (mstyle, MSTYLE_FONT_ITALIC);
628 			gnm_style_unset_element (mstyle, MSTYLE_FONT_UNDERLINE);
629 			gnm_style_unset_element (mstyle, MSTYLE_FONT_STRIKETHROUGH);
630 			gnm_style_unset_element (mstyle, MSTYLE_FONT_SIZE);
631 
632 			gnm_style_unset_element (mstyle, MSTYLE_FONT_COLOR);
633 		}
634 		if (!ft->patterns) {
635 			gnm_style_unset_element (mstyle, MSTYLE_COLOR_BACK);
636 			gnm_style_unset_element (mstyle, MSTYLE_COLOR_PATTERN);
637 			gnm_style_unset_element (mstyle, MSTYLE_PATTERN);
638 		}
639 		if (!ft->alignment) {
640 			gnm_style_unset_element (mstyle, MSTYLE_ALIGN_V);
641 			gnm_style_unset_element (mstyle, MSTYLE_ALIGN_H);
642 		}
643 	} else {
644 		GnmStyle *gnm_style_default = gnm_style_new_default ();
645 
646 		/*
647 		 * We fill in the gaps with the default mstyle
648 		 */
649 
650 		 if (!ft->number) {
651 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_FORMAT);
652 		 }
653 		 if (!ft->border) {
654 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_BORDER_TOP);
655 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_BORDER_BOTTOM);
656 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_BORDER_LEFT);
657 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_BORDER_RIGHT);
658 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_BORDER_DIAGONAL);
659 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_BORDER_REV_DIAGONAL);
660 		 }
661 		 if (!ft->font) {
662 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_FONT_NAME);
663 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_FONT_BOLD);
664 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_FONT_ITALIC);
665 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_FONT_UNDERLINE);
666 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_FONT_STRIKETHROUGH);
667 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_FONT_SIZE);
668 
669 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_FONT_COLOR);
670 		 }
671 		 if (!ft->patterns) {
672 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_COLOR_BACK);
673 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_COLOR_PATTERN);
674 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_PATTERN);
675 		 }
676 		 if (!ft->alignment) {
677 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_ALIGN_V);
678 			 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_ALIGN_H);
679 		 }
680 
681 		 gnm_style_unref (gnm_style_default);
682 	}
683 
684 	return mstyle;
685 }
686 
687 /*
688  * Callback used for calculating the styles
689  */
690 typedef void (* PCalcCallback) (GnmFT *ft, GnmRange *r, GnmStyle *mstyle, gpointer data);
691 
692 /**
693  * format_template_range_check:
694  * @ft: Format template
695  * @r: Target range
696  * @optional_cc: (nullable): if non-%NULL display an error message if @r is not
697  *			appropriate for @ft.
698  *
699  * Check whether range @r is big enough to apply format template @ft to it.
700  *
701  * Returns: %TRUE if @s is big enough, %FALSE if not.
702  **/
703 static gboolean
format_template_range_check(GnmFT * ft,GnmRange const * r,GOCmdContext * optional_cc)704 format_template_range_check (GnmFT *ft, GnmRange const *r,
705 			     GOCmdContext *optional_cc)
706 {
707 	GSList *ptr;
708 	int diff_col_high = -1;
709 	int diff_row_high = -1;
710 	gboolean invalid_range_seen = FALSE;
711 
712 	g_return_val_if_fail (ft != NULL, FALSE);
713 
714 	for (ptr = ft->members; NULL != ptr ; ptr = ptr->next) {
715 		GnmFTMember *member = ptr->data;
716 		GnmRange range = gnm_ft_member_get_rect (member, r);
717 
718 		if (!range_valid (&range)) {
719 			int diff_col = (range.start.col - range.end.col);
720 			int diff_row = (range.start.row - range.end.row);
721 
722 			if (diff_col > diff_col_high)
723 				diff_col_high = diff_col;
724 
725 			if (diff_row > diff_row_high)
726 				diff_row_high = diff_row;
727 
728 			invalid_range_seen = TRUE;
729 		}
730 	}
731 
732 	if (invalid_range_seen && optional_cc != NULL) {
733 		int diff_row_high_ft = diff_row_high + range_height (r);
734 		int diff_col_high_ft = diff_col_high + range_width (r);
735 		char *errmsg;
736 		char *rows, *cols;
737 
738 		if (diff_col_high > 0 && diff_row_high > 0) {
739 			rows = g_strdup_printf (ngettext ("%d row", "%d rows", diff_row_high_ft), diff_row_high_ft);
740 			cols = g_strdup_printf (ngettext ("%d col", "%d cols", diff_col_high_ft), diff_col_high_ft);
741 			errmsg = g_strdup_printf (
742 				_("The target region is too small.  It should be at least %s by %s"),
743 				rows, cols);
744 			g_free (rows);
745 			g_free (cols);
746 		} else if (diff_col_high > 0)
747 			errmsg = g_strdup_printf (
748 				ngettext ("The target region is too small.  It should be at least %d column wide",
749 					"The target region is too small.  It should be at least %d columns wide",
750 					diff_col_high_ft),
751 				diff_col_high_ft);
752 		else if (diff_row_high > 0)
753 			errmsg = g_strdup_printf (
754 				ngettext ("The target region is too small.  It should be at least %d row high",
755 					"The target region is too small.  It should be at least %d rows high",
756 					diff_row_high_ft),
757 				diff_row_high_ft);
758 		else {
759 			errmsg = NULL;
760 			g_warning ("Internal error while verifying ranges! (this should not happen!)");
761 		}
762 
763 		if (errmsg != NULL) {
764 			go_cmd_context_error_system (optional_cc, errmsg);
765 			g_free (errmsg);
766 		}
767 	}
768 	return !invalid_range_seen;
769 }
770 
771 /* Remove edge styles from a template and shift items that anchor on a filtered
772  * edge.  Returns a filtered copy of @origft. */
773 static GnmFT *
gnm_auto_fmt_filter_edges(GnmFT const * origft)774 gnm_auto_fmt_filter_edges (GnmFT const *origft)
775 {
776 	GSList *ptr;
777 	GnmFT *ft = gnm_ft_clone (origft);
778 	GnmFTMember *member;
779 	gboolean is_edge, l = FALSE, r = FALSE, t = FALSE, b = FALSE;
780 
781 	for (ptr = ft->members; ptr != NULL ; ) {
782 		member = ptr->data;
783 		ptr = ptr->next;
784 		if (!member->direction == FREQ_DIRECTION_NONE)
785 			continue;
786 
787 		is_edge = FALSE;
788 		if (member->col.size == 1) {
789 			if (!ft->edges.left && member->col.offset_gravity > 0)
790 				l |= (is_edge = TRUE);
791 			if (!ft->edges.right && member->col.offset_gravity < 0)
792 				r |= (is_edge = TRUE);
793 		}
794 		if (member->row.size == 1) {
795 			if (!ft->edges.top && member->row.offset_gravity > 0)
796 				t |= (is_edge = TRUE);
797 			if (!ft->edges.bottom && member->row.offset_gravity < 0)
798 				b |= (is_edge = TRUE);
799 		}
800 		if (is_edge) {
801 			gnm_ft_member_free (member);
802 			ft->members = g_slist_remove (ft->members, member);
803 		}
804 	}
805 
806 	if (!l && !r && !t && !b)
807 		return ft;
808 	for (ptr = ft->members; ptr != NULL ; ptr = ptr->next) {
809 		GnmFTMember *submember = ptr->data;
810 
811 		if (l && submember->col.offset_gravity > 0) {
812 			if (submember->col.offset >= 1)
813 				submember->col.offset--;
814 			submember->edge = 0;
815 		}
816 
817 		if (r && submember->col.offset_gravity < 0) {
818 			if (submember->col.offset >= 1)
819 				submember->col.offset--;
820 			submember->edge = 0;
821 		}
822 
823 		if (t && submember->row.offset_gravity > 0) {
824 			if (submember->row.offset >= 1)
825 				submember->row.offset--;
826 			submember->edge = 0;
827 		}
828 
829 		if (b && submember->row.offset_gravity < 0) {
830 			if (submember->row.offset >= 1)
831 				submember->row.offset--;
832 			submember->edge = 0;
833 		}
834 	}
835 	return ft;
836 }
837 
838 /**
839  * gnm_ft_calculate:
840  * @origft: GnmFT
841  * @s: Target range
842  * @pc: Callback function
843  * @cb_data: Data to pass to the callback function
844  *
845  * Calculate all styles for a range of @s. This routine will invoke the callback function
846  * and pass all styles and ranges for those styles to the callback function.
847  * The callback function should UNREF the mstyle passed!
848  *
849  **/
850 static void
gnm_ft_calculate(GnmFT * origft,GnmRange const * r,PCalcCallback pc,gpointer cb_data)851 gnm_ft_calculate (GnmFT *origft, GnmRange const *r,
852 			   PCalcCallback pc, gpointer cb_data)
853 {
854 	GnmFT *ft = origft;
855 	GSList *ptr;
856 
857 	g_return_if_fail (origft != NULL);
858 
859 	if (!ft->edges.left || !ft->edges.right || !ft->edges.top || !ft->edges.bottom)
860 		ft = gnm_auto_fmt_filter_edges (origft);
861 
862 	for (ptr = ft->members; NULL != ptr ; ptr = ptr->next) {
863 		GnmFTMember const *member = ptr->data;
864 		GnmStyle const *mstyle = member->mstyle;
865 		GnmRange range = gnm_ft_member_get_rect (member, r);
866 
867 		g_return_if_fail (range_valid (&range));
868 
869 		if (member->direction == FREQ_DIRECTION_NONE)
870 			pc (ft, &range, gnm_style_dup (mstyle), cb_data);
871 
872 		else if (member->direction == FREQ_DIRECTION_HORIZONTAL) {
873 			int col_repeat = member->repeat;
874 			GnmRange hr = range;
875 
876 			while (col_repeat != 0) {
877 				pc (ft, &hr, gnm_style_dup (mstyle), cb_data);
878 
879 				hr.start.col += member->skip + member->col.size;
880 				hr.end.col   += member->skip + member->col.size;
881 
882 				if (member->repeat != -1)
883 					col_repeat--;
884 				else {
885 					if (hr.start.row > r->end.row)
886 						break;
887 				}
888 
889 				if (hr.start.row > r->end.row - member->edge)
890 					break;
891 			}
892 		} else if (member->direction == FREQ_DIRECTION_VERTICAL) {
893 			int row_repeat = member->repeat;
894 			GnmRange vr = range;
895 
896 			while (row_repeat != 0) {
897 				pc (ft, &vr, gnm_style_dup (mstyle), cb_data);
898 
899 				vr.start.row += member->skip + member->row.size;
900 				vr.end.row   += member->skip + member->row.size;
901 
902 				if (member->repeat != -1)
903 					row_repeat--;
904 				else {
905 					if (vr.start.row > r->end.row)
906 						break;
907 				}
908 
909 				if (vr.start.row > r->end.row - member->edge)
910 					break;
911 			}
912 		}
913 	}
914 
915 	if (ft != origft)
916 		gnm_ft_free (ft);
917 }
918 
919 /******************************************************************************
920  * GnmFT - Application for the hashtable (previews)
921  ******************************************************************************/
922 
923 static void
cb_format_hash_style(GnmFT * ft,GnmRange * r,GnmStyle * mstyle,gpointer user)924 cb_format_hash_style (GnmFT *ft, GnmRange *r, GnmStyle *mstyle, gpointer user)
925 {
926 	GHashTable *table = user;
927 	int row, col;
928 
929 	/*
930 	 * Filter out undesired elements
931 	 */
932 	mstyle = format_template_filter_style (ft, mstyle, TRUE);
933 
934 	for (row = r->start.row; row <= r->end.row; row++)
935 		for (col = r->start.col; col <= r->end.col; col++) {
936 			GnmCellPos key;
937 			key.col = col;
938 			key.row = row;
939 			g_hash_table_insert (table,
940 					     g_memdup (&key, sizeof (key)),
941 					     gnm_style_dup (mstyle));
942 		}
943 
944 	/*
945 	 * Unref here, the hashtable will take care of its own
946 	 * resources
947 	 */
948 	gnm_style_unref (mstyle);
949 }
950 
951 /**
952  * format_template_recalc_hash:
953  * @ft: GnmFT
954  *
955  * Refills the hashtable based on new dimensions
956  **/
957 static void
format_template_recalc_hash(GnmFT * ft)958 format_template_recalc_hash (GnmFT *ft)
959 {
960 	GnmRange r;
961 
962 	g_return_if_fail (ft != NULL);
963 
964 	g_hash_table_remove_all (ft->table);
965 
966 	r = ft->dimension;
967 
968 	/* If the range check fails then the template it simply too *huge*
969 	 * so we don't display an error dialog.
970 	 */
971 	if (!format_template_range_check (ft, &r, NULL)) {
972 		g_warning ("Template %s is too large, hash can't be calculated", ft->name);
973 		return;
974 	}
975 
976 	gnm_ft_calculate (ft, &r, cb_format_hash_style, ft->table);
977 }
978 
979 /**
980  * gnm_ft_get_style:
981  * @ft:
982  * @row:
983  * @col:
984  *
985  * Returns the GnmStyle associated with coordinates row, col.
986  * This routine uses the hash to do this.
987  * NOTE : You MAY NOT free the result of this operation,
988  *        you may also NOT MODIFY the GnmStyle returned.
989  *        (make a copy first)
990  *
991  * Return value: an GnmStyle
992  **/
993 GnmStyle *
gnm_ft_get_style(GnmFT * ft,int row,int col)994 gnm_ft_get_style (GnmFT *ft, int row, int col)
995 {
996 	GnmCellPos key;
997 
998 	g_return_val_if_fail (ft != NULL, NULL);
999 	g_return_val_if_fail (ft->table != NULL, NULL);
1000 
1001 	/*
1002 	 * If the hash isn't filled (as result of resizing) or whatever,
1003 	 * then refill it
1004 	 */
1005 	if (ft->invalidate_hash) {
1006 		ft->invalidate_hash = FALSE;
1007 		format_template_recalc_hash (ft);
1008 	}
1009 
1010 	key.col = col;
1011 	key.row = row;
1012 	return g_hash_table_lookup (ft->table, &key);
1013 }
1014 
1015 
1016 
1017 /******************************************************************************
1018  * GnmFT - Application to Sheet
1019  ******************************************************************************/
1020 
1021 static void
cb_format_sheet_style(GnmFT * ft,GnmRange * r,GnmStyle * mstyle,gpointer user)1022 cb_format_sheet_style (GnmFT *ft, GnmRange *r, GnmStyle *mstyle, gpointer user)
1023 {
1024 	Sheet *sheet = user;
1025 
1026 	g_return_if_fail (ft != NULL);
1027 	g_return_if_fail (r != NULL);
1028 	g_return_if_fail (mstyle != NULL);
1029 
1030 	mstyle = format_template_filter_style (ft, mstyle, FALSE);
1031 
1032 	/*
1033 	 * We need not unref the mstyle, sheet will
1034 	 * take care of the mstyle
1035 	 */
1036 	sheet_apply_style (sheet, r, mstyle);
1037 }
1038 
1039 /**
1040  * gnm_ft_check_valid:
1041  * @ft:
1042  * @regions: (element-type GnmRange):
1043  * @cc: (nullable): where to report errors
1044  *
1045  * check to see if the @regions are able to contain the support template @ft.
1046  *
1047  * Returns: %TRUE if ok, else %FALSE.  Will report an error to @cc if it is
1048  * supplied.
1049  */
1050 gboolean
gnm_ft_check_valid(GnmFT * ft,GSList * regions,GOCmdContext * cc)1051 gnm_ft_check_valid (GnmFT *ft, GSList *regions, GOCmdContext *cc)
1052 {
1053 	g_return_val_if_fail (cc != NULL, FALSE);
1054 
1055 	for (; regions != NULL ; regions = regions->next)
1056 		if (!format_template_range_check (ft, regions->data, cc))
1057 			return FALSE;
1058 
1059 	return TRUE;
1060 }
1061 
1062 /**
1063  * gnm_ft_apply_to_sheet_regions:
1064  * @ft: GnmFT
1065  * @sheet: the Target sheet
1066  * @regions: (element-type GnmRange): Region list
1067  *
1068  * Apply the template to all selected regions in @sheet.
1069  **/
1070 void
gnm_ft_apply_to_sheet_regions(GnmFT * ft,Sheet * sheet,GSList * regions)1071 gnm_ft_apply_to_sheet_regions (GnmFT *ft, Sheet *sheet, GSList *regions)
1072 {
1073 	for (; regions != NULL ; regions = regions->next)
1074 		gnm_ft_calculate (ft, regions->data,
1075 					   cb_format_sheet_style, sheet);
1076 }
1077