Update Spanish translation
[gnumeric.git] / src / format-template.c
blob4fd2bec72173c9845b6aaa0f0bcb59753a94f008
1 /*
2 * format-template.c : implementation of the template handling system.
4 * Copyright (C) Almer S. Tigelaar <almer@gnome.org>
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.
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.
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/>.
20 #include <gnumeric-config.h>
21 #include <glib/gi18n-lib.h>
22 #include <gnumeric.h>
23 #include <format-template.h>
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>
35 #define CC2XML(s) ((xmlChar const *)(s))
36 #define CXML2C(s) ((char const *)(s))
38 static inline gboolean
39 attr_eq (const xmlChar *a, const char *s)
41 return !strcmp (CXML2C (a), s);
44 /******************************************************************************
45 * FormatTemplateMember - Getters/setters and creation
46 ******************************************************************************/
48 /**
49 * gnm_ft_member_new:
51 * Create a new GnmFTMember
53 * Return value: the new GnmFTMember
54 **/
55 static GnmFTMember *
56 gnm_ft_member_new (void)
58 GnmFTMember *member;
60 member = g_new (GnmFTMember, 1);
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;
71 return member;
74 /**
75 * gnm_ft_member_clone:
77 * Clone a template member
79 * Return value: a copy of @member
80 **/
81 static GnmFTMember *
82 gnm_ft_member_clone (GnmFTMember *member)
84 GnmFTMember *clone = gnm_ft_member_new ();
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);
95 return clone;
98 /**
99 * gnm_ft_member_free:
100 * @member: GnmFTMember
102 * Frees an existing template member
104 static void
105 gnm_ft_member_free (GnmFTMember *member)
107 g_return_if_fail (member != NULL);
109 if (member->mstyle) {
110 gnm_style_unref (member->mstyle);
111 member->mstyle = NULL;
114 g_free (member);
119 * gnm_ft_member_get_rect:
120 * @member:
121 * @r:
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 :-)
128 * Return value: a GnmRange containing the effective rectangle of @member
130 static GnmRange
131 gnm_ft_member_get_rect (GnmFTMember const *member, GnmRange const *r)
133 GnmRange res;
135 res.start.row = res.end.row = 0;
136 res.start.col = res.end.col = 0;
138 g_return_val_if_fail (member != NULL, res);
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;
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;
152 * Now that we know these coordinates we'll calculate the
153 * bottom right coordinates
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;
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;
179 return res;
182 /****************************************************************************/
184 static gboolean
185 gnm_ft_member_valid (GnmFTMember const *member)
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);
196 /******************************************************************************
197 * GnmFT - Creation/Destruction
198 ******************************************************************************/
201 * gnm_ft_new:
203 * Create a new 'empty' GnmFT
205 * Return value: the new GnmFT
207 static GnmFT *
208 gnm_ft_new (void)
210 GnmFT *ft;
212 ft = g_new0 (GnmFT, 1);
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 ("");
219 ft->category = NULL;
221 ft->members = NULL;
222 ft->number = TRUE;
223 ft->border = TRUE;
224 ft->font = TRUE;
225 ft->patterns = TRUE;
226 ft->alignment = TRUE;
228 ft->edges.left = TRUE;
229 ft->edges.right = TRUE;
230 ft->edges.top = TRUE;
231 ft->edges.bottom = TRUE;
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;
239 range_init (&ft->dimension, 0,0,0,0);
241 return ft;
245 * gnm_ft_free:
247 void
248 gnm_ft_free (GnmFT *ft)
250 g_return_if_fail (ft != NULL);
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);
259 g_free (ft);
263 static void
264 gnm_ft_set_name (GnmFT *ft, char const *name)
266 g_return_if_fail (ft != NULL);
267 g_return_if_fail (name != NULL);
269 g_free (ft->name);
270 ft->name = g_strdup (name);
273 static void
274 gnm_ft_set_author (GnmFT *ft, char const *author)
276 g_return_if_fail (ft != NULL);
277 g_return_if_fail (author != NULL);
279 g_free (ft->author);
280 ft->author = g_strdup (author);
283 static void
284 gnm_ft_set_description (GnmFT *ft, char const *description)
286 g_return_if_fail (ft != NULL);
287 g_return_if_fail (description != NULL);
289 g_free (ft->description);
290 ft->description = g_strdup (description);
294 * gnm_ft_clone:
295 * @ft: GnmFT
297 * Make a copy of @ft.
299 * Returns: transfer full): a copy of @ft
301 GnmFT *
302 gnm_ft_clone (GnmFT const *ft)
304 GnmFT *clone;
306 g_return_val_if_fail (ft != NULL, NULL);
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);
314 clone->category = ft->category;
316 clone->members =
317 g_slist_copy_deep (ft->members,
318 (GCopyFunc)gnm_ft_member_clone, NULL);
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;
328 clone->invalidate_hash = TRUE;
330 return clone;
333 GType
334 gnm_ft_get_type (void)
336 static GType t = 0;
338 if (t == 0) {
339 t = g_boxed_type_register_static ("GnmFT",
340 (GBoxedCopyFunc)gnm_ft_clone,
341 (GBoxedFreeFunc)gnm_ft_free);
343 return t;
346 #define GNM 100
347 #define GMR 200
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
355 static void
356 sax_information (GsfXMLIn *xin, xmlChar const **attrs)
358 GnmFT *ft = (GnmFT *)xin->user_state;
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]));
370 static void
371 sax_members_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *blob)
373 GnmFT *ft = (GnmFT *)xin->user_state;
374 ft->members = g_slist_reverse (ft->members);
377 static void
378 sax_member (GsfXMLIn *xin, xmlChar const **attrs)
380 GnmFT *ft = (GnmFT *)xin->user_state;
381 GnmFTMember *member = gnm_ft_member_new ();
383 /* Order reversed in sax_members_end. */
384 ft->members = g_slist_prepend (ft->members, member);
387 static void
388 sax_member_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *blob)
390 GnmFT *ft = (GnmFT *)xin->user_state;
391 GnmFTMember *member = ft->members->data;
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);
400 static void
401 sax_placement (GnmFTColRowInfo *info, xmlChar const **attrs)
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 */
410 static void
411 sax_row_placement (GsfXMLIn *xin, xmlChar const **attrs)
413 GnmFT *ft = (GnmFT *)xin->user_state;
414 GnmFTMember *member = ft->members->data;
415 sax_placement (&member->row, attrs);
418 static void
419 sax_col_placement (GsfXMLIn *xin, xmlChar const **attrs)
421 GnmFT *ft = (GnmFT *)xin->user_state;
422 GnmFTMember *member = ft->members->data;
423 sax_placement (&member->col, attrs);
426 static void
427 sax_dimensions (GnmFTColRowInfo *info, xmlChar const **attrs)
429 for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2) {
430 if (gnm_xml_attr_int (attrs, "size", &info->size))
431 ; /* Nothing */
435 static void
436 sax_row_dimensions (GsfXMLIn *xin, xmlChar const **attrs)
438 GnmFT *ft = (GnmFT *)xin->user_state;
439 GnmFTMember *member = ft->members->data;
440 sax_dimensions (&member->row, attrs);
443 static void
444 sax_col_dimensions (GsfXMLIn *xin, xmlChar const **attrs)
446 GnmFT *ft = (GnmFT *)xin->user_state;
447 GnmFTMember *member = ft->members->data;
448 sax_dimensions (&member->col, attrs);
451 static void
452 sax_frequency (GsfXMLIn *xin, xmlChar const **attrs)
454 GnmFT *ft = (GnmFT *)xin->user_state;
455 GnmFTMember *member = ft->members->data;
457 for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2) {
458 int i;
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 */
469 static void
470 sax_style_handler (GsfXMLIn *xin, GnmStyle *style, gpointer user)
472 GnmFT *ft = (GnmFT *)xin->user_state;
473 GnmFTMember *member = ft->members->data;
474 gnm_style_ref (style);
475 member->mstyle = style;
478 static gboolean
479 template_sax_unknown (GsfXMLIn *xin, xmlChar const *elem, xmlChar const **attrs)
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);
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;
495 return FALSE;
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
515 * gnm_ft_new_from_file:
516 * @context:
517 * @filename: The filename to load from
519 * Create a new GnmFT and load a template file
520 * into it.
522 * Return value: (transfer full): a new GnmFT (or %NULL on error)
524 GnmFT *
525 gnm_ft_new_from_file (char const *filename, GOCmdContext *cc)
527 GnmFT *ft = NULL;
528 GsfXMLInDoc *doc = NULL;
529 GnmLocale *locale;
530 gboolean ok = FALSE;
531 GsfInput *input = NULL;
533 g_return_val_if_fail (filename != NULL, NULL);
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;
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);
548 ft = gnm_ft_new ();
549 ft->filename = g_strdup (filename);
551 locale = gnm_push_C_locale ();
552 ok = gsf_xml_in_doc_parse (doc, input, ft);
553 gnm_pop_C_locale (locale);
555 done:
556 if (input) g_object_unref (input);
557 if (doc) gsf_xml_in_doc_free (doc);
559 if (ft && !ok) {
560 gnm_ft_free (ft);
561 ft = NULL;
564 return ft;
569 * gnm_ft_compare_name:
570 * @a: First GnmFT
571 * @b: Second GnmFT
574 gint
575 gnm_ft_compare_name (gconstpointer a, gconstpointer b)
577 GnmFT const *ft_a = (GnmFT const *) a;
578 GnmFT const *ft_b = (GnmFT const *) b;
580 return g_utf8_collate (_(ft_a->name), _(ft_b->name));
583 /******************************************************************************
584 * GnmFT - Actual implementation (Filtering and calculating)
585 ******************************************************************************/
588 * format_template_filter_style:
589 * @ft:
590 * @mstyle:
591 * @fill_defaults: If set fill in the gaps with the "default" mstyle.
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'
599 * Return value: The same mstyle as @mstyle with most likely some modifications
601 static GnmStyle *
602 format_template_filter_style (GnmFT *ft, GnmStyle *mstyle, gboolean fill_defaults)
604 g_return_val_if_fail (ft != NULL, NULL);
605 g_return_val_if_fail (mstyle != NULL, NULL);
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
612 if (!fill_defaults) {
613 if (!ft->number) {
614 gnm_style_unset_element (mstyle, MSTYLE_FORMAT);
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);
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);
632 gnm_style_unset_element (mstyle, MSTYLE_FONT_COLOR);
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);
639 if (!ft->alignment) {
640 gnm_style_unset_element (mstyle, MSTYLE_ALIGN_V);
641 gnm_style_unset_element (mstyle, MSTYLE_ALIGN_H);
643 } else {
644 GnmStyle *gnm_style_default = gnm_style_new_default ();
647 * We fill in the gaps with the default mstyle
650 if (!ft->number) {
651 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_FORMAT);
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);
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);
669 gnm_style_merge_element (mstyle, gnm_style_default, MSTYLE_FONT_COLOR);
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);
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);
681 gnm_style_unref (gnm_style_default);
684 return mstyle;
688 * Callback used for calculating the styles
690 typedef void (* PCalcCallback) (GnmFT *ft, GnmRange *r, GnmStyle *mstyle, gpointer data);
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.
699 * Check whether range @r is big enough to apply format template @ft to it.
701 * Returns: %TRUE if @s is big enough, %FALSE if not.
703 static gboolean
704 format_template_range_check (GnmFT *ft, GnmRange const *r,
705 GOCmdContext *optional_cc)
707 GSList *ptr;
708 int diff_col_high = -1;
709 int diff_row_high = -1;
710 gboolean invalid_range_seen = FALSE;
712 g_return_val_if_fail (ft != NULL, FALSE);
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);
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);
722 if (diff_col > diff_col_high)
723 diff_col_high = diff_col;
725 if (diff_row > diff_row_high)
726 diff_row_high = diff_row;
728 invalid_range_seen = TRUE;
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;
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!)");
763 if (errmsg != NULL) {
764 go_cmd_context_error_system (optional_cc, errmsg);
765 g_free (errmsg);
768 return !invalid_range_seen;
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 *
774 gnm_auto_fmt_filter_edges (GnmFT const *origft)
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;
781 for (ptr = ft->members; ptr != NULL ; ) {
782 member = ptr->data;
783 ptr = ptr->next;
784 if (!member->direction == FREQ_DIRECTION_NONE)
785 continue;
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);
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);
800 if (is_edge) {
801 gnm_ft_member_free (member);
802 ft->members = g_slist_remove (ft->members, member);
806 if (!l && !r && !t && !b)
807 return ft;
808 for (ptr = ft->members; ptr != NULL ; ptr = ptr->next) {
809 GnmFTMember *submember = ptr->data;
811 if (l && submember->col.offset_gravity > 0) {
812 if (submember->col.offset >= 1)
813 submember->col.offset--;
814 submember->edge = 0;
817 if (r && submember->col.offset_gravity < 0) {
818 if (submember->col.offset >= 1)
819 submember->col.offset--;
820 submember->edge = 0;
823 if (t && submember->row.offset_gravity > 0) {
824 if (submember->row.offset >= 1)
825 submember->row.offset--;
826 submember->edge = 0;
829 if (b && submember->row.offset_gravity < 0) {
830 if (submember->row.offset >= 1)
831 submember->row.offset--;
832 submember->edge = 0;
835 return ft;
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
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!
850 static void
851 gnm_ft_calculate (GnmFT *origft, GnmRange const *r,
852 PCalcCallback pc, gpointer cb_data)
854 GnmFT *ft = origft;
855 GSList *ptr;
857 g_return_if_fail (origft != NULL);
859 if (!ft->edges.left || !ft->edges.right || !ft->edges.top || !ft->edges.bottom)
860 ft = gnm_auto_fmt_filter_edges (origft);
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);
867 g_return_if_fail (range_valid (&range));
869 if (member->direction == FREQ_DIRECTION_NONE)
870 pc (ft, &range, gnm_style_dup (mstyle), cb_data);
872 else if (member->direction == FREQ_DIRECTION_HORIZONTAL) {
873 int col_repeat = member->repeat;
874 GnmRange hr = range;
876 while (col_repeat != 0) {
877 pc (ft, &hr, gnm_style_dup (mstyle), cb_data);
879 hr.start.col += member->skip + member->col.size;
880 hr.end.col += member->skip + member->col.size;
882 if (member->repeat != -1)
883 col_repeat--;
884 else {
885 if (hr.start.row > r->end.row)
886 break;
889 if (hr.start.row > r->end.row - member->edge)
890 break;
892 } else if (member->direction == FREQ_DIRECTION_VERTICAL) {
893 int row_repeat = member->repeat;
894 GnmRange vr = range;
896 while (row_repeat != 0) {
897 pc (ft, &vr, gnm_style_dup (mstyle), cb_data);
899 vr.start.row += member->skip + member->row.size;
900 vr.end.row += member->skip + member->row.size;
902 if (member->repeat != -1)
903 row_repeat--;
904 else {
905 if (vr.start.row > r->end.row)
906 break;
909 if (vr.start.row > r->end.row - member->edge)
910 break;
915 if (ft != origft)
916 gnm_ft_free (ft);
919 /******************************************************************************
920 * GnmFT - Application for the hashtable (previews)
921 ******************************************************************************/
923 static void
924 cb_format_hash_style (GnmFT *ft, GnmRange *r, GnmStyle *mstyle, gpointer user)
926 GHashTable *table = user;
927 int row, col;
930 * Filter out undesired elements
932 mstyle = format_template_filter_style (ft, mstyle, TRUE);
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));
945 * Unref here, the hashtable will take care of its own
946 * resources
948 gnm_style_unref (mstyle);
952 * format_template_recalc_hash:
953 * @ft: GnmFT
955 * Refills the hashtable based on new dimensions
957 static void
958 format_template_recalc_hash (GnmFT *ft)
960 GnmRange r;
962 g_return_if_fail (ft != NULL);
964 g_hash_table_remove_all (ft->table);
966 r = ft->dimension;
968 /* If the range check fails then the template it simply too *huge*
969 * so we don't display an error dialog.
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;
976 gnm_ft_calculate (ft, &r, cb_format_hash_style, ft->table);
980 * gnm_ft_get_style:
981 * @ft:
982 * @row:
983 * @col:
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)
991 * Return value: an GnmStyle
993 GnmStyle *
994 gnm_ft_get_style (GnmFT *ft, int row, int col)
996 GnmCellPos key;
998 g_return_val_if_fail (ft != NULL, NULL);
999 g_return_val_if_fail (ft->table != NULL, NULL);
1002 * If the hash isn't filled (as result of resizing) or whatever,
1003 * then refill it
1005 if (ft->invalidate_hash) {
1006 ft->invalidate_hash = FALSE;
1007 format_template_recalc_hash (ft);
1010 key.col = col;
1011 key.row = row;
1012 return g_hash_table_lookup (ft->table, &key);
1017 /******************************************************************************
1018 * GnmFT - Application to Sheet
1019 ******************************************************************************/
1021 static void
1022 cb_format_sheet_style (GnmFT *ft, GnmRange *r, GnmStyle *mstyle, gpointer user)
1024 Sheet *sheet = user;
1026 g_return_if_fail (ft != NULL);
1027 g_return_if_fail (r != NULL);
1028 g_return_if_fail (mstyle != NULL);
1030 mstyle = format_template_filter_style (ft, mstyle, FALSE);
1033 * We need not unref the mstyle, sheet will
1034 * take care of the mstyle
1036 sheet_apply_style (sheet, r, mstyle);
1040 * gnm_ft_check_valid:
1041 * @ft:
1042 * @regions: (element-type GnmRange):
1043 * @cc: (nullable): where to report errors
1045 * check to see if the @regions are able to contain the support template @ft.
1047 * Returns: %TRUE if ok, else %FALSE. Will report an error to @cc if it is
1048 * supplied.
1050 gboolean
1051 gnm_ft_check_valid (GnmFT *ft, GSList *regions, GOCmdContext *cc)
1053 g_return_val_if_fail (cc != NULL, FALSE);
1055 for (; regions != NULL ; regions = regions->next)
1056 if (!format_template_range_check (ft, regions->data, cc))
1057 return FALSE;
1059 return TRUE;
1063 * gnm_ft_apply_to_sheet_regions:
1064 * @ft: GnmFT
1065 * @sheet: the Target sheet
1066 * @regions: (element-type GnmRange): Region list
1068 * Apply the template to all selected regions in @sheet.
1070 void
1071 gnm_ft_apply_to_sheet_regions (GnmFT *ft, Sheet *sheet, GSList *regions)
1073 for (; regions != NULL ; regions = regions->next)
1074 gnm_ft_calculate (ft, regions->data,
1075 cb_format_sheet_style, sheet);