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