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>
23 #include <format-template.h>
28 #include <command-context.h>
31 #include <goffice/goffice.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 ******************************************************************************/
51 * Create a new GnmFTMember
53 * Return value: the new GnmFTMember
56 gnm_ft_member_new (void)
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
;
69 member
->mstyle
= NULL
;
75 * gnm_ft_member_clone:
77 * Clone a template member
79 * Return value: a copy of @member
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
);
100 * @member: GnmFTMember
102 * Frees an existing template member
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
;
119 * gnm_ft_member_get_rect:
123 * Get the rectangular area covered by the GnmFTMember @member in the parent
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
131 gnm_ft_member_get_rect (GnmFTMember
const *member
, GnmRange
const *r
)
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
;
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
;
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;
159 res
.end
.row
= r
->end
.row
+ member
->row
.size
;
161 if (member
->row
.size
> 0)
162 res
.start
.row
= res
.end
.row
- member
->row
.size
+ 1;
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;
171 res
.end
.col
= r
->end
.col
+ member
->col
.size
;
173 if (member
->col
.size
> 0)
174 res
.start
.col
= res
.end
.col
- member
->col
.size
+ 1;
176 res
.start
.col
= r
->start
.col
- member
->col
.size
;
182 /****************************************************************************/
185 gnm_ft_member_valid (GnmFTMember
const *member
)
189 member
->direction
>= FREQ_DIRECTION_NONE
&&
190 member
->direction
<= FREQ_DIRECTION_VERTICAL
&&
191 member
->repeat
>= -1 &&
196 /******************************************************************************
197 * GnmFT - Creation/Destruction
198 ******************************************************************************/
203 * Create a new 'empty' GnmFT
205 * Return value: the new GnmFT
212 ft
= g_new0 (GnmFT
, 1);
215 ft
->author
= g_strdup (go_get_real_name ());
216 ft
->name
= g_strdup (N_("Name"));
217 ft
->description
= g_strdup ("");
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);
248 gnm_ft_free (GnmFT
*ft
)
250 g_return_if_fail (ft
!= NULL
);
252 g_free (ft
->filename
);
255 g_free (ft
->description
);
256 g_slist_free_full (ft
->members
, (GDestroyNotify
)gnm_ft_member_free
);
257 g_hash_table_destroy (ft
->table
);
264 gnm_ft_set_name (GnmFT
*ft
, char const *name
)
266 g_return_if_fail (ft
!= NULL
);
267 g_return_if_fail (name
!= NULL
);
270 ft
->name
= g_strdup (name
);
274 gnm_ft_set_author (GnmFT
*ft
, char const *author
)
276 g_return_if_fail (ft
!= NULL
);
277 g_return_if_fail (author
!= NULL
);
280 ft
->author
= g_strdup (author
);
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
);
297 * Make a copy of @ft.
299 * Returns: transfer full): a copy of @ft
302 gnm_ft_clone (GnmFT
const *ft
)
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
;
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
;
334 gnm_ft_get_type (void)
339 t
= g_boxed_type_register_static ("GnmFT",
340 (GBoxedCopyFunc
)gnm_ft_clone
,
341 (GBoxedFreeFunc
)gnm_ft_free
);
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"),
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]));
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
);
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
);
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
);
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
))
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
);
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
);
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
))
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
);
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
);
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) {
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
))
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
;
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
,
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
),
515 * gnm_ft_new_from_file:
517 * @filename: The filename to load from
519 * Create a new GnmFT and load a template file
522 * Return value: (transfer full): a new GnmFT (or %NULL on error)
525 gnm_ft_new_from_file (char const *filename
, GOCmdContext
*cc
)
528 GsfXMLInDoc
*doc
= NULL
;
531 GsfInput
*input
= NULL
;
533 g_return_val_if_fail (filename
!= NULL
, NULL
);
535 input
= gsf_input_stdio_new (filename
, NULL
);
537 go_cmd_context_error_import
539 _("Error while opening autoformat template"));
543 doc
= gsf_xml_in_doc_new (template_dtd
, template_ns
);
546 gsf_xml_in_doc_set_unknown_handler (doc
, &template_sax_unknown
);
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
);
556 if (input
) g_object_unref (input
);
557 if (doc
) gsf_xml_in_doc_free (doc
);
569 * gnm_ft_compare_name:
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:
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
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
) {
614 gnm_style_unset_element (mstyle
, MSTYLE_FORMAT
);
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
);
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
);
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
);
644 GnmStyle
*gnm_style_default
= gnm_style_new_default ();
647 * We fill in the gaps with the default mstyle
651 gnm_style_merge_element (mstyle
, gnm_style_default
, MSTYLE_FORMAT
);
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
);
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
);
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
);
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
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.
704 format_template_range_check (GnmFT
*ft
, GnmRange
const *r
,
705 GOCmdContext
*optional_cc
)
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
);
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"),
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",
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",
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
);
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. */
774 gnm_auto_fmt_filter_edges (GnmFT
const *origft
)
777 GnmFT
*ft
= gnm_ft_clone (origft
);
779 gboolean is_edge
, l
= FALSE
, r
= FALSE
, t
= FALSE
, b
= FALSE
;
781 for (ptr
= ft
->members
; ptr
!= NULL
; ) {
784 if (!member
->direction
== FREQ_DIRECTION_NONE
)
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
);
801 gnm_ft_member_free (member
);
802 ft
->members
= g_slist_remove (ft
->members
, member
);
806 if (!l
&& !r
&& !t
&& !b
)
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
--;
817 if (r
&& submember
->col
.offset_gravity
< 0) {
818 if (submember
->col
.offset
>= 1)
819 submember
->col
.offset
--;
823 if (t
&& submember
->row
.offset_gravity
> 0) {
824 if (submember
->row
.offset
>= 1)
825 submember
->row
.offset
--;
829 if (b
&& submember
->row
.offset_gravity
< 0) {
830 if (submember
->row
.offset
>= 1)
831 submember
->row
.offset
--;
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!
851 gnm_ft_calculate (GnmFT
*origft
, GnmRange
const *r
,
852 PCalcCallback pc
, gpointer cb_data
)
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
;
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)
885 if (hr
.start
.row
> r
->end
.row
)
889 if (hr
.start
.row
> r
->end
.row
- member
->edge
)
892 } else if (member
->direction
== FREQ_DIRECTION_VERTICAL
) {
893 int row_repeat
= member
->repeat
;
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)
905 if (vr
.start
.row
> r
->end
.row
)
909 if (vr
.start
.row
> r
->end
.row
- member
->edge
)
919 /******************************************************************************
920 * GnmFT - Application for the hashtable (previews)
921 ******************************************************************************/
924 cb_format_hash_style (GnmFT
*ft
, GnmRange
*r
, GnmStyle
*mstyle
, gpointer user
)
926 GHashTable
*table
= user
;
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
++) {
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
948 gnm_style_unref (mstyle
);
952 * format_template_recalc_hash:
955 * Refills the hashtable based on new dimensions
958 format_template_recalc_hash (GnmFT
*ft
)
962 g_return_if_fail (ft
!= NULL
);
964 g_hash_table_remove_all (ft
->table
);
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
);
976 gnm_ft_calculate (ft
, &r
, cb_format_hash_style
, ft
->table
);
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
994 gnm_ft_get_style (GnmFT
*ft
, int row
, int col
)
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,
1005 if (ft
->invalidate_hash
) {
1006 ft
->invalidate_hash
= FALSE
;
1007 format_template_recalc_hash (ft
);
1012 return g_hash_table_lookup (ft
->table
, &key
);
1017 /******************************************************************************
1018 * GnmFT - Application to Sheet
1019 ******************************************************************************/
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:
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
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
))
1063 * gnm_ft_apply_to_sheet_regions:
1065 * @sheet: the Target sheet
1066 * @regions: (element-type GnmRange): Region list
1068 * Apply the template to all selected regions in @sheet.
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
);