/* * gog-theme.c : * * Copyright (C) 2003-2004 Jody Goldberg (jody@gnome.org) * Copyright (C) 2010 Jean Brefort (jean.brefort@normalesup.org) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 * USA */ #include #include #include #include #include #include #include #include /** * SECTION:gog-theme * @short_description: a list of default styles to apply to appropriate graph elements. * * The library provides two hard coded themes, "Default", and "Guppi". Other themes * are described in files, some of which might be distributed with the library. * * A file defining a theme is an xml file with a <GogTheme> root node. * The root element needs to have an Id which should be unique. The uuid command * provides such Ids. * * The contents must be: _name|name+, _description?|description*, GOStyle+. * * _name and name nodes: * * The _name node should be used for themes distributed with goffice, localized * names will be in *.po files and only the default name for "C" locale needs to * be there. Other files need at least one name node for the default name, and * might have some translated names with an appropriate "xml:lang" attribute. * * _description and description nodes: * * These work just like name nodes. The difference is that no description node is * mandatory. A theme can work perfectly without a description. * * GOStyle nodes: * * These nodes actually define the theme. Attributes and contents are: * node attributes: class, role. * node contents: (line|outline)?, fill?, marker?, font?, text_layout? * * The attributes define the target for the style. You might have a class and * a role attribute, or just one of them. If the role attribute is given, the class attribute, * if given, represents the class of the parent object. * A GOStyle node with no class or role will be used * as default when another style is missing. If several such nodes exist, the * last one will be used. If no default style exists, the "Default" theme is applied for * missing nodes. * The list of GOStyle nodes might be: * * * * * * * * * * * * * * * * * * * * * *
classrolecontentscomments
GogGraphoutline, fill
GogGraphTitleoutline, fill, font, text_layoutThe graph title
GogChartoutline, fill
GogChartTitleoutline, fill, font, text_layoutThe chart title
GogLegendoutline, fill, font
GogAxisline, font, text_layout
GogAxisLineline, font
GogGridoutline, fillGogGrid is actually the back plane
MajorGridline, fill
MinorGridline, fill
GogLabeloutline, fill, font, text_layout
GogSeriesline, fill, markerOne is needed for each entry in the palette
GogTrendLineline
GogRegEqnoutline, fill, font, text_layout
GogSeriesLabelsoutline, fill, font, text_layout
GogRegEqnoutline, fill, font, text_layout
GogSeriesLabeloutline, fill, font, text_layout
GogDataLabeloutline, fill, font, text_layout
GogEquationoutline, fill, font, text_layout
* * The line and outline nodes are actually the same so using line in place of outline is * not an issue. A color is specified either using the format RR:GG::BB:AA or a string as defined * in rgb.txt, so black can be specified as "black" or "00:00:00:FF". * * "line" or "outline" node: * * * * * * *
attributevalue
comments
dashone of "none", "solid", "s-dot", "s-dash-dot", "s-dash-dot-dot", "dash-dot-dot-dot", "dot", * "s-dash", "dash", "l-dash", "dash-dot", or "dash-dot-dot"
colorany color as described above
widthfloatnot always taken into account
* * "fill" node * * contents: (pattern|gradient)? * * * *
attributevaluecomments
typeone of "none", "pattern", or "gradient"
* * "pattern" node * * Should be included in the fill node if type is pattern. * * * * * *
attributevalue
typeone of "solid", "grey75", "grey50", "grey25", "grey12.5", * "grey6.25", "horiz", "vert", "rev-diag", "diag", "diag-cross", "thick-diag-cross", * "thin-horiz", "thin-vert", "thin-rev-diag", "thin-diag", "thin-horiz-cross", * "thin-diag-cross", "foreground-solid", "small-circles","semi-circles", "thatch", * "large-circles", or "bricks"
foreany color as described above
backany color as described above
* * "gradient" node * * Should be included in the fill node if type is gradient. * * * * * * *
attributevaluecomments
directionone of "n-s", "s-n", "n-s-mirrored", "s-n-mirrored", * "w-e", "e-w", "w-e-mirrored", "e-w-mirrored", "nw-se", "se-nw", "nw-se-mirrored", * "se-nw-mirrored", "ne-sw", "sw-ne", "sw-ne-mirrored", or "ne-sw-mirrored"
start_colorany color as described above
brightnessfloatmeaningful only for monocolor gradients
end_colorany color as described abovemeaningful only for bicolor gradients
* * "marker" node * * * * * * * *
attributevaluecomments
shapeone of "none", "square", "diamond", "triangle-down", * "triangle-up", "triangle-right", "triangle-left", "circle", "x", "cross", * "asterisk", "bar", "half-bar", "butterfly", "hourglass", or "lefthalf-bar"
fill-colorany color as described above
outline-colorany color as described above
sizefloatnot always taken into account
* * "font" node * * * * *
attributevalue
colorany color as described above
fonta string describing the font such as "Sans 10"
* * "text_layout" node * * * *
attributevaluecomments
anglefloatexpressed in degrees
**/ typedef void (*GogThemeStyleMap) (GOStyle *style, unsigned ind, GogTheme const *theme); typedef struct { /* If role is non-null, klass_name specifies the container class, * If role is null, klass_name specifies object type */ char *klass_name; char *role_id; GOStyle *style; GogThemeStyleMap map; } GogThemeElement; struct _GogTheme { GObject base; char *id; char *name; char *description; char *uri; GoResourceType type; GHashTable *names; GHashTable *descs; GHashTable *elem_hash_by_role; GHashTable *elem_hash_by_class; GHashTable *class_aliases; GOStyle *default_style; GPtrArray *palette; GogAxisColorMap *cm, *dcm; /* cm: color map for color axis, dcm stands for discrete color map */ gboolean built_color_map; /* TRUE if color map is built from the palette */ gboolean writeable; /* TRUE if theme can be edited */ char *path; /* file path if any */ }; typedef GObjectClass GogThemeClass; static GObjectClass *parent_klass; static GSList *themes; static GogTheme *default_theme = NULL; static GHashTable *global_class_aliases = NULL; enum { GOG_THEME_PROP_0, GOG_THEME_PROP_TYPE }; static void gog_theme_set_property (GObject *gobject, guint param_id, GValue const *value, GParamSpec *pspec) { GogTheme *theme = GOG_THEME (gobject); switch (param_id) { case GOG_THEME_PROP_TYPE: theme->type = g_value_get_enum (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec); return; /* NOTE : RETURN */ } } static void gog_theme_get_property (GObject *gobject, guint param_id, GValue *value, GParamSpec *pspec) { GogTheme *theme = GOG_THEME (gobject); switch (param_id) { case GOG_THEME_PROP_TYPE: g_value_set_enum (value, theme->type); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec); return; /* NOTE : RETURN */ } } static void gog_theme_element_free (GogThemeElement *elem) { g_object_unref (elem->style); g_free (elem->klass_name); g_free (elem->role_id); g_free (elem); } static guint gog_theme_element_hash (GogThemeElement const *elem) { return g_str_hash (elem->role_id); } static gboolean gog_theme_element_eq (GogThemeElement const *a, GogThemeElement const *b) { if (!g_str_equal (a->role_id, b->role_id)) return FALSE; if (a->klass_name == NULL) return b->klass_name == NULL; if (b->klass_name == NULL) return FALSE; return g_str_equal (a->klass_name, b->klass_name); } static void gog_theme_finalize (GObject *obj) { GogTheme *theme = GOG_THEME (obj); themes = g_slist_remove (themes, theme); g_free (theme->id); g_free (theme->name); g_free (theme->uri); g_free (theme->description); if (theme->names) g_hash_table_destroy (theme->names); if (theme->descs) g_hash_table_destroy (theme->descs); if (theme->elem_hash_by_role) g_hash_table_destroy (theme->elem_hash_by_role); if (theme->elem_hash_by_class) g_hash_table_destroy (theme->elem_hash_by_class); if (theme->class_aliases) g_hash_table_destroy (theme->class_aliases); if (theme->palette) { unsigned i; for (i = 0; i < theme->palette->len; i++) g_object_unref (g_ptr_array_index (theme->palette, i)); g_ptr_array_free (theme->palette, TRUE); } if (theme->cm) g_object_unref (theme->cm); if (theme->dcm) g_object_unref (theme->dcm); (parent_klass->finalize) (obj); } static void gog_theme_class_init (GogThemeClass *klass) { GObjectClass *gobject_klass = (GObjectClass *) klass; parent_klass = g_type_class_peek_parent (klass); gobject_klass->finalize = gog_theme_finalize; gobject_klass->set_property = gog_theme_set_property; gobject_klass->get_property = gog_theme_get_property; g_object_class_install_property (gobject_klass, GOG_THEME_PROP_TYPE, g_param_spec_enum ("resource-type", _("Resource type"), _("The resource type for the theme"), go_resource_type_get_type (), GO_RESOURCE_INVALID, GSF_PARAM_STATIC | G_PARAM_READWRITE |G_PARAM_CONSTRUCT_ONLY)); } static void gog_theme_init (GogTheme *theme) { theme->names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); theme->descs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); theme->elem_hash_by_role = g_hash_table_new_full ((GHashFunc) gog_theme_element_hash, (GCompareFunc) gog_theme_element_eq, NULL, (GDestroyNotify) gog_theme_element_free); theme->elem_hash_by_class = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) gog_theme_element_free); theme->class_aliases = g_hash_table_new (g_str_hash, g_str_equal); } /*************** * Output code * ***************/ static void save_name_cb (char const *lang, char const *name, GsfXMLOut *output) { gsf_xml_out_start_element (output, "name"); if (strcmp (lang, "C")) gsf_xml_out_add_cstr_unchecked (output, "xml:lang", lang); gsf_xml_out_add_cstr_unchecked (output, NULL, name); gsf_xml_out_end_element (output); } static void save_desc_cb (char const *lang, char const *name, GsfXMLOut *output) { gsf_xml_out_start_element (output, "description"); if (strcmp (lang, "C")) gsf_xml_out_add_cstr_unchecked (output, "xml:lang", lang); gsf_xml_out_add_cstr_unchecked (output, NULL, name); gsf_xml_out_end_element (output); } static void save_elem_cb (G_GNUC_UNUSED char const *key, GogThemeElement *elt, GsfXMLOut *output) { if (elt->klass_name && !strcmp (elt->klass_name, "GogSeries")) return; gsf_xml_out_start_element (output, "GOStyle"); if (elt->klass_name) gsf_xml_out_add_cstr_unchecked (output, "class", elt->klass_name); if (elt->role_id) gsf_xml_out_add_cstr_unchecked (output, "role", elt->role_id); go_persist_sax_save (GO_PERSIST (elt->style), output); gsf_xml_out_end_element (output); } static void save_series_style_cb (GOPersist *gp, GsfXMLOut *output) { gsf_xml_out_start_element (output, "GOStyle"); gsf_xml_out_add_cstr_unchecked (output, "class", "GogSeries"); go_persist_sax_save (gp, output); gsf_xml_out_end_element (output); } static void gog_theme_save (GogTheme const *theme); static void gog_theme_build_uri (GogTheme *theme) { char *filename, *full_name; filename = g_strconcat (theme->id, ".theme", NULL); full_name = g_build_filename (g_get_home_dir (), ".goffice", "themes", filename, NULL); theme->uri = go_filename_to_uri (full_name); g_free (filename); g_free (full_name); } static void gog_theme_sax_save (GOPersist const *gp, GsfXMLOut *output) { GogTheme *theme; g_return_if_fail (GOG_IS_THEME (gp)); theme = GOG_THEME (gp); if (output == NULL) { g_return_if_fail (theme->uri == NULL); gog_theme_build_uri (theme); theme->type = GO_RESOURCE_RW; gog_theme_save (theme); return; } gsf_xml_out_add_cstr_unchecked (output, "id", theme->id); g_hash_table_foreach (theme->names, (GHFunc) save_name_cb, output); g_hash_table_foreach (theme->descs, (GHFunc) save_desc_cb, output); g_hash_table_foreach (theme->elem_hash_by_class, (GHFunc) save_elem_cb, output); g_hash_table_foreach (theme->elem_hash_by_role, (GHFunc) save_elem_cb, output); if (theme->palette) g_ptr_array_foreach (theme->palette, (GFunc) save_series_style_cb, output); if (theme->cm && gog_axis_color_map_get_resource_type (theme->cm) == GO_RESOURCE_CHILD) { gsf_xml_out_start_element (output, "GogAxisColorMap"); gsf_xml_out_add_cstr_unchecked (output, "type", (theme->cm == theme->dcm)? "both": "gradient"); go_persist_sax_save (GO_PERSIST (theme->cm), output); gsf_xml_out_end_element (output); } if (theme->dcm && theme->dcm != theme->cm && gog_axis_color_map_get_resource_type (theme->dcm) == GO_RESOURCE_CHILD) { gsf_xml_out_start_element (output, "GogAxisColorMap"); gsf_xml_out_add_cstr_unchecked (output, "type", "discrete"); go_persist_sax_save (GO_PERSIST (theme->dcm), output); gsf_xml_out_end_element (output); } } static void gog_theme_save (GogTheme const *theme) { GsfOutput *output = gsf_output_gio_new_for_uri (theme->uri, NULL); GsfXMLOut *xml = gsf_xml_out_new (output); gsf_xml_out_start_element (xml, "GogTheme"); gog_theme_sax_save (GO_PERSIST (theme), xml); gsf_xml_out_end_element (xml); g_object_unref (xml); g_object_unref (output); } /** * gog_theme_save_to_home_dir: * @theme: the #GogTheme to save * * Writes the theme to the user directory so that it becomes permanently * available. **/ void gog_theme_save_to_home_dir (GogTheme *theme) { g_return_if_fail (GOG_IS_THEME (theme) && theme->type == GO_RESOURCE_EXTERNAL && theme->uri == NULL); gog_theme_build_uri (theme); gog_theme_save (theme); theme->type = GO_RESOURCE_RW; } /************** * Input code * **************/ struct theme_load_state { GogTheme *theme; char *name, *desc, *lang; unsigned name_lang_score; unsigned desc_lang_score; char const * const *langs; GSList *garbage; }; static void theme_start (GsfXMLIn *xin, xmlChar const **attrs) { struct theme_load_state *state = (struct theme_load_state *) xin->user_state; if (state->theme == NULL) { state->theme = g_object_new (GOG_TYPE_THEME, NULL); for (; attrs && *attrs; attrs +=2) if (!strcmp ((char const *) *attrs, "id")) { state->theme->id = g_strdup ((char const *) attrs[1]); break; } } } static void name_start (GsfXMLIn *xin, xmlChar const **attrs) { struct theme_load_state *state = (struct theme_load_state *) xin->user_state; unsigned i; if (state->theme->name) /* the theme has already been loaded from elsewhere */ return; for (i = 0; attrs != NULL && attrs[i] && attrs[i+1] ; i += 2) if (0 == strcmp (attrs[i], "xml:lang")) state->lang = g_strdup (attrs[i+1]); } static void name_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *blob) { struct theme_load_state *state = (struct theme_load_state *) xin->user_state; char *name = NULL; if (state->theme->name) /* the theme has already been loaded from elsewhere */ return; if (xin->content->str == NULL) return; name = g_strdup (xin->content->str); if (state->lang == NULL) state->lang = g_strdup ("C"); if (state->name_lang_score > 0 && state->langs[0] != NULL) { unsigned i; for (i = 0; i < state->name_lang_score && state->langs[i] != NULL; i++) { if (strcmp (state->langs[i], state->lang) == 0) { g_free (state->name); state->name = g_strdup (name); state->name_lang_score = i; } } } g_hash_table_replace (state->theme->names, state->lang, name); state->lang = NULL; } static void desc_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *blob) { struct theme_load_state *state = (struct theme_load_state *) xin->user_state; char *desc = NULL; if (state->theme->name) /* the theme has already been loaded from elsewhere */ return; if (xin->content->str == NULL) return; desc = g_strdup (xin->content->str); if (state->lang == NULL) state->lang = g_strdup ("C"); if (state->desc_lang_score > 0 && state->langs[0] != NULL) { unsigned i; for (i = 0; i < state->desc_lang_score && state->langs[i] != NULL; i++) { if (strcmp (state->langs[i], state->lang) == 0) { g_free (state->desc); state->desc = g_strdup (desc); state->desc_lang_score = i; } } } g_hash_table_replace (state->theme->descs, state->lang, desc); state->lang = NULL; } enum { /* used in theme editor to display the right snapshot */ SNAPSHOT_GRAPH, SNAPSHOT_TRENDLINE, SNAPSHOT_SERIES, SNAPSHOT_SERIESLABELS, SNAPSHOT_SERIESLINES, #ifdef GOFFICE_WITH_LASEM SNAPSHOT_EQUATION, #endif }; typedef struct { char const *klass_name; char const *role_id; char const *label; GOStyleFlag flags; unsigned snapshot; } GogThemeRoles; static GogThemeRoles roles[] = { {"GogGraph", NULL, N_("Graph"), GO_STYLE_FILL | GO_STYLE_OUTLINE, SNAPSHOT_GRAPH}, {"GogGraph", "Title", N_("Graph title"), GO_STYLE_OUTLINE | GO_STYLE_FILL | GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT, SNAPSHOT_GRAPH}, {"GogChart", NULL, N_("Chart"), GO_STYLE_FILL | GO_STYLE_OUTLINE, SNAPSHOT_GRAPH}, {"GogChart", "Title", N_("Chart title"), GO_STYLE_OUTLINE | GO_STYLE_FILL | GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT, SNAPSHOT_GRAPH}, {"GogLegend", NULL, N_("Legend"), GO_STYLE_OUTLINE | GO_STYLE_FILL | GO_STYLE_FONT, SNAPSHOT_GRAPH}, {"GogAxis", NULL, N_("Axis"), GO_STYLE_LINE | GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT, SNAPSHOT_GRAPH}, {"GogAxisLine", NULL, N_("Axis line"), GO_STYLE_LINE | GO_STYLE_FONT, SNAPSHOT_GRAPH}, {"GogGrid", NULL, N_("Backplane"), GO_STYLE_FILL | GO_STYLE_OUTLINE, SNAPSHOT_GRAPH}, {"GogGrid", "MajorGrid", N_("Major grid"), GO_STYLE_LINE | GO_STYLE_FILL, SNAPSHOT_GRAPH}, {"GogGrid", "MinorGrid", N_("Minor grid"), GO_STYLE_LINE | GO_STYLE_FILL, SNAPSHOT_GRAPH}, {"GogLabel", NULL, N_("Label"), GO_STYLE_OUTLINE | GO_STYLE_FILL | GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT, SNAPSHOT_GRAPH}, {"GogTrendLine", NULL, N_("Trend line"), GO_STYLE_LINE, SNAPSHOT_TRENDLINE}, {"GogRegEqn", NULL, N_("Regression equation"), GO_STYLE_OUTLINE | GO_STYLE_FILL | GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT, SNAPSHOT_TRENDLINE}, #ifdef GOFFICE_WITH_LASEM {"GogEquation", NULL, N_("Equation"), GO_STYLE_OUTLINE | GO_STYLE_FILL | GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT, SNAPSHOT_EQUATION}, #endif {"GogSeriesLine", NULL, N_("Series lines"), GO_STYLE_LINE, SNAPSHOT_SERIESLINES}, {"GogSeries", NULL, N_("Series"), GO_STYLE_LINE | GO_STYLE_FILL | GO_STYLE_MARKER, SNAPSHOT_SERIES}, {"GogSeriesLabels", NULL, N_("Series labels"), GO_STYLE_OUTLINE | GO_STYLE_FILL | GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT, SNAPSHOT_SERIESLABELS}, {"GogDataLabel", NULL, N_("Data label"), GO_STYLE_OUTLINE | GO_STYLE_FILL | GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT, SNAPSHOT_SERIESLABELS}, {"GogColorScale", NULL, N_("Color scale"), GO_STYLE_OUTLINE, SNAPSHOT_GRAPH} }; static void gog_theme_add_element (GogTheme *theme, GOStyle *style, GogThemeStyleMap map, char *klass_name, char *role_id) { GogThemeElement *elem; unsigned i; g_return_if_fail (theme != NULL); elem = g_new0 (GogThemeElement, 1); elem->klass_name = klass_name; elem->role_id = role_id; elem->style = style; elem->map = map; /* sets the needed insteresting_fields in style */ for (i = 0; i < G_N_ELEMENTS (roles); i++) { if ((klass_name && roles[i].klass_name && strcmp (klass_name, roles[i].klass_name)) || (klass_name != NULL && roles[i].klass_name == NULL)) continue; if ((role_id && roles[i].role_id && strcmp (role_id, roles[i].role_id)) || (role_id == NULL && roles[i].role_id != NULL) || (role_id != NULL && roles[i].role_id == NULL)) continue; style->interesting_fields = roles[i].flags; break; } if (i == G_N_ELEMENTS (roles)) g_warning ("[GogTheme]: unregistered style class=%s role=%s\n",klass_name, role_id); /* Never put an element into both by_role_id & by_class_name */ if (role_id != NULL) { if (g_hash_table_lookup (theme->elem_hash_by_role, (gpointer)elem) == NULL) g_hash_table_insert (theme->elem_hash_by_role, (gpointer)elem, elem); else { g_object_unref (style); g_free (elem); } } else if (klass_name != NULL) { if (g_hash_table_lookup (theme->elem_hash_by_class, (gpointer)klass_name) == NULL) g_hash_table_insert (theme->elem_hash_by_class, (gpointer)klass_name, elem); else { g_object_unref (style); g_free (elem); } } else { if (theme->default_style) g_object_unref (theme->default_style); theme->default_style = style; g_free (elem); } } static void elem_start (GsfXMLIn *xin, G_GNUC_UNUSED xmlChar const **attrs) { struct theme_load_state *state = (struct theme_load_state *) xin->user_state; char *role = NULL, *class_name = NULL; GOStyle *style; unsigned i; if (state->theme == NULL) return; if (state->theme->name) { /* the theme has already been loaded from elsewhere */ style = go_style_new (); /* still load the style to avoid warnings */ go_persist_prep_sax (GO_PERSIST (style), xin, attrs); state->garbage = g_slist_prepend (state->garbage, style); return; } for (i = 0; attrs != NULL && attrs[i] && attrs[i+1] ; i += 2) if (0 == strcmp (attrs[i], "class")) class_name = g_strdup (attrs[i+1]); else if (0 == strcmp (attrs[i], "role")) role = g_strdup (attrs[i+1]); style = go_style_new (); go_style_clear_auto (style); go_persist_prep_sax (GO_PERSIST (style), xin, attrs); if (class_name && !strcmp (class_name, "GogSeries")) { if (state->theme->palette == NULL) state->theme->palette = g_ptr_array_new (); g_ptr_array_add (state->theme->palette, style); g_free (class_name); } else gog_theme_add_element (state->theme, style, NULL, class_name, role); } static void color_map_start (GsfXMLIn *xin, G_GNUC_UNUSED xmlChar const **attrs) { struct theme_load_state *state = (struct theme_load_state *) xin->user_state; GogAxisColorMap *map; if (state->theme == NULL) return; if (state->theme->name) /* the theme has already been loaded from elsewhere */ return; map = g_object_new (GOG_TYPE_AXIS_COLOR_MAP, "resource-type", GO_RESOURCE_CHILD, NULL); for (; attrs && *attrs; attrs += 2) if (!strcmp ((char const *) *attrs, "type")) { if (strcmp ((char const *) attrs[1], "discrete")) { if (state->theme->cm != NULL) { g_warning ("[GogTheme]: extra GogAxisColorMap found."); g_object_unref (map); return; } state->theme->cm = map; if (strcmp ((char const *) attrs[1], "both")) return; g_object_ref (map); } if (state->theme->dcm != NULL) { g_warning ("[GogTheme]: extra GogAxisColorMap found."); g_object_unref (map); return; } state->theme->dcm = map; } } static GsfXMLInNode const theme_dtd[] = { GSF_XML_IN_NODE (THEME, THEME, -1, "GogTheme", GSF_XML_NO_CONTENT, theme_start, NULL), GSF_XML_IN_NODE (THEME, NAME, -1, "name", GSF_XML_CONTENT, name_start, name_end), GSF_XML_IN_NODE (THEME, UNAME, -1, "_name", GSF_XML_CONTENT, name_start, name_end), GSF_XML_IN_NODE (THEME, DESCRIPTION, -1, "description", GSF_XML_CONTENT, name_start, desc_end), GSF_XML_IN_NODE (THEME, UDESCRIPTION, -1, "_description", GSF_XML_CONTENT, name_start, desc_end), GSF_XML_IN_NODE (THEME, STYLE, -1, "GOStyle", GSF_XML_NO_CONTENT, elem_start, NULL), GSF_XML_IN_NODE (THEME, COLORMAP, -1, "GogAxisColormap", GSF_XML_NO_CONTENT, color_map_start, NULL), GSF_XML_IN_NODE_END }; static GsfXMLInDoc *xml = NULL; static void map_area_series_solid_palette (GOStyle *, unsigned , GogTheme const *); static void map_area_series_solid_default (GOStyle *, unsigned , GogTheme const *); static void theme_loaded (struct theme_load_state *state) { /* initialize a dummy GogSeries style */ GOStyle *style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0; /* hairline */ style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_PATTERN; if (state->theme->palette && state->theme->palette->len > 0) gog_theme_add_element (state->theme, style, map_area_series_solid_palette, g_strdup ("GogSeries"), NULL); else gog_theme_add_element (state->theme, style, map_area_series_solid_default, g_strdup ("GogSeries"), NULL); if (state->theme->dcm == NULL) { if (state->theme->palette) { GOColor *colors = g_new (GOColor, state->theme->palette->len); unsigned i; for (i = 0; i < state->theme->palette->len; i++) colors[i] = GO_STYLE (g_ptr_array_index (state->theme->palette, i))->fill.pattern.back; state->theme->dcm = gog_axis_color_map_from_colors ("Default", state->theme->palette->len, colors, GO_RESOURCE_GENERATED); g_free (colors); } else { GogTheme *theme = gog_theme_registry_lookup ("Default"); state->theme->dcm = g_object_ref (theme->dcm); } } state->theme->name = state->name; state->theme->description = state->desc; } static void parse_done_cb (GsfXMLIn *xin, struct theme_load_state *state) { if (state->theme->name == NULL) theme_loaded (state); else { g_free (state->name); g_free (state->desc); } g_free (state->lang); g_slist_free_full (state->garbage, g_object_unref); g_free (state); } static void gog_theme_prep_sax (GOPersist *gp, GsfXMLIn *xin, xmlChar const **attrs) { struct theme_load_state *state; g_return_if_fail (GOG_IS_THEME (gp)); state = g_new (struct theme_load_state, 1); state->theme = GOG_THEME (gp); state->name = NULL; state->desc = NULL; state->lang = NULL; state->langs = g_get_language_names (); state->name_lang_score = G_MAXINT; state->desc_lang_score = G_MAXINT; state->garbage = NULL; if (!xml) xml = gsf_xml_in_doc_new (theme_dtd, NULL); gsf_xml_in_push_state (xin, xml, state, (GsfXMLInExtDtor) parse_done_cb, attrs); } static void gog_theme_persist_init (GOPersistClass *iface) { iface->prep_sax = gog_theme_prep_sax; iface->sax_save = gog_theme_sax_save; } GSF_CLASS_FULL (GogTheme, gog_theme, NULL, NULL, gog_theme_class_init, NULL, gog_theme_init, G_TYPE_OBJECT, 0, GSF_INTERFACE (gog_theme_persist_init, GO_TYPE_PERSIST)) static GogThemeElement * gog_theme_find_element (GogTheme const *theme, GogObject const *obj) { GogThemeElement *elem = NULL; GObjectClass *klass = NULL; /* make gcc happy */ char const *name; if (theme == NULL) theme = default_theme; g_return_val_if_fail (theme != NULL, NULL); /* FIXME: Restore hash search caching. */ /* Search by role */ if (elem == NULL && obj->role != NULL && obj->parent != NULL) { GogThemeElement key; /* Search by specific role */ key.role_id = (char *) obj->role->id; key.klass_name = (char *) G_OBJECT_TYPE_NAME (obj->parent); elem = g_hash_table_lookup (theme->elem_hash_by_role, &key); } if (elem == NULL && obj->role != NULL) { GogThemeElement key; /* Search by generic role */ key.role_id = (char *) obj->role->id; key.klass_name = NULL; elem = g_hash_table_lookup (theme->elem_hash_by_role, &key); } /* Still not found ? Search by object type */ if (elem == NULL) { klass = G_OBJECT_GET_CLASS (obj); do { name = G_OBJECT_CLASS_NAME (klass); elem = g_hash_table_lookup ( /* Is the type known ? */ theme->elem_hash_by_class, name); if (elem == NULL) { /* Is this a local alias ? */ name = g_hash_table_lookup ( theme->class_aliases, name); if (name != NULL) elem = g_hash_table_lookup ( theme->elem_hash_by_class, name); } if (elem == NULL && global_class_aliases != NULL) { /* Is this a global alias */ name = g_hash_table_lookup ( global_class_aliases, G_OBJECT_CLASS_NAME (klass)); if (name != NULL) elem = g_hash_table_lookup ( theme->elem_hash_by_class, name); } } while (elem == NULL && (klass = g_type_class_peek_parent (klass)) != NULL); } /* still not found, use default theme */ if (elem == NULL && theme != default_theme) elem = gog_theme_find_element (default_theme, obj); return elem; } /** * gog_theme_fillin_style : * @theme: #GogTheme * @style: #GOStyle to initialize * @obj: #GogObject The object associated with @style * @ind: an optional index * @relevant_fields: GOStyleFlag * * Fill in the auto aspects of @style based on @theme 's element for objects of * type/role similar to @obj with index @ind. If @relevant_fields is GO_STYLE_ALL, * fillin the entire style, not just the auto portions included in @relevant_fields. **/ void gog_theme_fillin_style (GogTheme const *theme, GOStyle *style, GogObject const *obj, int ind, GOStyleFlag relevant_fields) { GogThemeElement *elem = gog_theme_find_element (theme, obj); g_return_if_fail (elem != NULL); if (relevant_fields == GO_STYLE_ALL) { go_style_assign (style, elem->style); go_style_force_auto (style); } else go_style_apply_theme (style, elem->style, relevant_fields); /* FIXME FIXME FIXME we should handle the applicability here not in the map */ /* ensure only relevant fields are changed */ if (ind >= 0 && elem->map) { /* ensure only relevant fields are changed */ GOStyleFlag flags = style->disable_theming; style->disable_theming = GO_STYLE_ALL ^ relevant_fields; (elem->map) (style, ind, theme); style->disable_theming = flags; } } static GogTheme * gog_theme_new (char const *name, GoResourceType type) { GogTheme *theme = g_object_new (GOG_TYPE_THEME, NULL); theme->name = g_strdup (_(name)); if (type == GO_RESOURCE_NATIVE) theme->id = g_strdup (name); theme->type = type; return theme; } #if 0 void gog_theme_register_file (char const *name, char const *file) { GogTheme *theme = gog_theme_new (name); theme->load_from_file = g_strdup (file); } static void gog_theme_add_alias (GogTheme *theme, char const *from, char const *to) { g_hash_table_insert (theme->class_aliases, (gpointer)from, (gpointer)to); } #endif /** * gog_theme_get_id: * @theme: a #GogTheme * * Retrieves the theme Id. * Returns: the GogTheme Id. **/ char const * gog_theme_get_id (GogTheme const *theme) { g_return_val_if_fail (GOG_IS_THEME (theme), ""); return theme->id; } /** * gog_theme_get_name: * @theme: a #GogTheme * * Returns: the GogTheme name. **/ char const * gog_theme_get_name (GogTheme const *theme) { g_return_val_if_fail (GOG_IS_THEME (theme), ""); return theme->name; } /** * gog_theme_get_descrition: * @theme: a #GogTheme * * Returns: the localized GogTheme decription. **/ char const * gog_theme_get_description (GogTheme const *theme) { g_return_val_if_fail (GOG_IS_THEME (theme), ""); return theme->description; } /** * gog_themep_get_resource_type: * @theme: a #GogTheme * * Retrieves the resource type for @theme. * Returns: the resource type. **/ GoResourceType gog_theme_get_resource_type (GogTheme const *theme) { g_return_val_if_fail (GOG_IS_THEME (theme), GO_RESOURCE_INVALID); return theme->type; } /**************************************************************************/ static void map_marker (GOStyleMark *mark, unsigned shape, unsigned palette_index, GOColor const *palette) { static GOMarkerShape const shape_palette [] = { GO_MARKER_DIAMOND, GO_MARKER_SQUARE, GO_MARKER_TRIANGLE_UP, GO_MARKER_X, GO_MARKER_ASTERISK, GO_MARKER_CIRCLE, GO_MARKER_CROSS, GO_MARKER_HALF_BAR, GO_MARKER_BAR }; if (shape >= G_N_ELEMENTS (shape_palette)) shape %= G_N_ELEMENTS (shape_palette); if (mark->auto_shape) go_marker_set_shape (mark->mark, shape_palette [shape]); if (mark->auto_outline_color) go_marker_set_outline_color (mark->mark, palette [palette_index]); if (mark->auto_fill_color) go_marker_set_fill_color (mark->mark, palette [palette_index]); } static GOColor const default_palette [] = { 0x9c9cffff, 0x9c3163ff, 0xffffceff, 0xceffffff, 0x630063ff, 0xff8080ff, 0x0063ceff, 0xceceffff, 0x000080ff, 0xff00ffff, 0xffff00ff, 0x00ffffff, 0x800080ff, 0x800000ff, 0x008080ff, 0x0000ffff, 0x00ceffff, 0xceffffff, 0xceffceff, 0xffff9cff, 0x9cceffff, 0xff9cceff, 0xce9cffff, 0xffce9cff, 0x3163ffff, 0x31ceceff, 0x9cce00ff, 0xffce00ff, 0xff9c00ff, 0xff6300ff, 0x63639cff, 0x949494ff, 0x003163ff, 0x319c63ff, 0x003100ff, 0x313100ff, 0x9c3100ff, 0x9c3163ff, 0x31319cff, 0x313131ff, 0xffffffff, 0xff0000ff, 0x00ff00ff, 0x0000ffff, 0xffff00ff, 0xff00ffff, 0x00ffffff, 0x800000ff, 0x008000ff, 0x000080ff, 0x808000ff, 0x800080ff, 0x008080ff, 0xc6c6c6ff, 0x808080ff }; static void map_area_series_solid_default (GOStyle *style, unsigned ind, G_GNUC_UNUSED GogTheme const *theme) { unsigned palette_index = ind; if (palette_index >= G_N_ELEMENTS (default_palette)) palette_index %= G_N_ELEMENTS (default_palette); if (style->fill.auto_back) { style->fill.pattern.back = default_palette [palette_index]; /* force the brightness to reinterpolate */ if (style->fill.type == GO_STYLE_FILL_GRADIENT && style->fill.gradient.brightness >= 0) go_style_set_fill_brightness (style, style->fill.gradient.brightness); } palette_index += 8; if (palette_index >= G_N_ELEMENTS (default_palette)) palette_index -= G_N_ELEMENTS (default_palette); if (style->line.auto_color && !(style->disable_theming & GO_STYLE_LINE)) style->line.color = default_palette [palette_index]; if (!(style->disable_theming & GO_STYLE_MARKER)) map_marker (&style->marker, ind, palette_index, default_palette); } static GOColor const guppi_palette[] = { 0xff3000ff, 0x80ff00ff, 0x00ffcfff, 0x2000ffff, 0xff008fff, 0xffbf00ff, 0x00ff10ff, 0x009fffff, 0xaf00ffff, 0xff0000ff, 0xafff00ff, 0x00ff9fff, 0x0010ffff, 0xff00bfff, 0xff8f00ff, 0x20ff00ff, 0x00cfffff, 0x8000ffff, 0xff0030ff, 0xdfff00ff, 0x00ff70ff, 0x0040ffff, 0xff00efff, 0xff6000ff, 0x50ff00ff, 0x00ffffff, 0x5000ffff, 0xff0060ff, 0xffef00ff, 0x00ff40ff, 0x0070ffff, 0xdf00ffff }; static void map_area_series_solid_guppi (GOStyle *style, unsigned ind, G_GNUC_UNUSED GogTheme const *theme) { unsigned palette_index = ind; if (palette_index >= G_N_ELEMENTS (guppi_palette)) palette_index %= G_N_ELEMENTS (guppi_palette); if (style->fill.auto_back) { style->fill.pattern.back = guppi_palette [palette_index]; if (style->fill.type == GO_STYLE_FILL_GRADIENT && style->fill.gradient.brightness >= 0) /* force the brightness to reinterpolate */ go_style_set_fill_brightness (style, style->fill.gradient.brightness); } if (style->line.auto_color && !(style->disable_theming & GO_STYLE_LINE)) style->line.color = guppi_palette [palette_index]; if (!(style->disable_theming & GO_STYLE_MARKER)) map_marker (&style->marker, ind, palette_index, guppi_palette); } static void map_area_series_solid_palette (GOStyle *style, unsigned ind, GogTheme const *theme) { GOStyle *src; if (theme->palette->len == 0) src = theme->default_style; else { ind %= theme->palette->len; src = g_ptr_array_index (theme->palette, ind); } if (src) go_style_apply_theme (style, src, style->interesting_fields); } /**************************************************************************/ /** * gog_theme_registry_add: * @theme: a #GogTheme * @is_default: bool * * Keep a pointer to @theme in graph theme registry. * This function does not add a reference to @theme. **/ static void gog_theme_registry_add (GogTheme *theme, gboolean is_default) { g_return_if_fail (GOG_IS_THEME (theme)); /* TODO: Check for duplicated names and for already * registered themes */ if (is_default) { g_object_ref (theme); if (default_theme != NULL) g_object_unref (default_theme); default_theme = theme; } themes = g_slist_append (themes, theme); } /** * gog_theme_registry_lookup: * @name: a theme name * * Returns: (transfer none): a #GogTheme from theme registry. **/ GogTheme * gog_theme_registry_lookup (char const *name) { GSList *ptr; GogTheme *theme = default_theme; if (name != NULL) { for (ptr = themes ; ptr != NULL ; ptr = ptr->next) { theme = ptr->data; if (!strcmp (theme->id, name)) return theme; } if (strlen (name) != 36 || name[8] != '-' || name[13] != '-' || name[18] !='-' || name[23] != '-') { /* name does not seem to be an uuid, migth be the theme name (needed for compatibility) */ char const *found_name; for (ptr = themes ; ptr != NULL ; ptr = ptr->next) { theme = ptr->data; found_name = g_hash_table_lookup (theme->names, "C"); if (found_name && !strcmp (found_name, name)) return theme; } } /* create an empty theme */ theme = g_object_new (GOG_TYPE_THEME, "resource-type", GO_RESOURCE_EXTERNAL, NULL); theme->id = g_strdup (name); gog_theme_registry_add (theme, FALSE); } return theme; } /** * gog_theme_registry_get_theme_names: * * Returns: (element-type utf8) (transfer container): a newly allocated theme name list from theme registry. **/ GSList * gog_theme_registry_get_theme_names (void) { GogTheme *theme; GSList *names = NULL; GSList *ptr; for (ptr = themes; ptr != NULL; ptr = ptr->next) { theme = ptr->data; names = g_slist_append (names, theme->id); } return names; } /**************************************************************************/ static void build_predefined_themes (void) { GogTheme *theme; GOStyle *style; if (NULL == global_class_aliases) { global_class_aliases = g_hash_table_new ( g_str_hash, g_str_equal); g_hash_table_insert (global_class_aliases, (gpointer)"GogSeriesElement", (gpointer)"GogSeries"); g_hash_table_insert (global_class_aliases, (gpointer)"GogSeriesLines", (gpointer)"GogSeries"); g_hash_table_insert (global_class_aliases, (gpointer)"GogSeriesLabels", (gpointer)"GogLabel"); g_hash_table_insert (global_class_aliases, (gpointer)"GogDataLabel", (gpointer)"GogLabel"); #ifdef GOFFICE_WITH_LASEM g_hash_table_insert (global_class_aliases, (gpointer)"GogEquation", (gpointer)"GogLabel"); #endif } /* An MS Excel-ish theme */ theme = gog_theme_new (N_("Default"), GO_RESOURCE_NATIVE); theme->description = g_strdup (_("An MS Excel like theme")); gog_theme_registry_add (theme, TRUE); /* graph */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_NONE; style->line.width = 0; style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_NONE; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE); gog_theme_add_element (theme, style, NULL, g_strdup ("GogGraph"), NULL); /* chart */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0; /* hairline */ style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_PATTERN; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE); gog_theme_add_element (theme, style, NULL, g_strdup ("GogChart"), NULL); /* Legend */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0; /* hairline */ style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_PATTERN; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE); gog_theme_add_element (theme, style, NULL, g_strdup ("GogLegend"), NULL); /* Axis */ style = go_style_new (); go_style_clear_auto (style); style->line.width = 0; /* hairline */ style->line.color = GO_COLOR_BLACK; gog_theme_add_element (theme, style, NULL, g_strdup ("GogAxis"), NULL); /* AxisLine */ style = go_style_new (); go_style_clear_auto (style); style->line.width = 0; /* hairline */ style->line.color = GO_COLOR_BLACK; gog_theme_add_element (theme, style, NULL, g_strdup ("GogAxisLine"), NULL); /* Grid */ style = go_style_new (); go_style_clear_auto (style); style->fill.type = GO_STYLE_FILL_PATTERN; style->line.dash_type = GO_LINE_SOLID; style->line.width = 1.; style->line.color = 0X848284ff; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_GREY (0xd0)); gog_theme_add_element (theme, style, NULL, g_strdup ("GogGrid"), NULL); /* GridLine */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0.4; style->line.color = GO_COLOR_BLACK; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_FROM_RGBA (0xE0, 0xE0, 0xE0, 0xE0)); style->fill.type = GO_STYLE_FILL_NONE; gog_theme_add_element (theme, style, NULL, NULL, g_strdup ("MajorGrid")); style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0.2; style->line.color = GO_COLOR_BLACK; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_FROM_RGBA (0xE0, 0xE0, 0xE0, 0xE0)); style->fill.type = GO_STYLE_FILL_NONE; gog_theme_add_element (theme, style, NULL, NULL, g_strdup ("MinorGrid")); /* Series */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0; /* hairline */ style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_PATTERN; /* FIXME : not really true, will want to split area from line */ gog_theme_add_element (theme, style, map_area_series_solid_default, g_strdup ("GogSeries"), NULL); /* Chart titles */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_NONE; style->line.width = 0.; style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_NONE; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE); go_style_set_font_desc (style, pango_font_description_from_string ("Sans Bold 12")); gog_theme_add_element (theme, style, NULL, g_strdup ("GogChart"), g_strdup ("Title")); /* labels */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_NONE; style->line.width = 0.; style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_NONE; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE); go_style_set_font_desc (style, pango_font_description_from_string ("Sans 10")); gog_theme_add_element (theme, style, NULL, g_strdup ("GogLabel"), NULL); /* regression curves */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 1; style->line.color = GO_COLOR_BLACK; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_FROM_RGBA (0x00, 0x00, 0x00, 0x20)); style->fill.type = GO_STYLE_FILL_NONE; gog_theme_add_element (theme, style, NULL, g_strdup ("GogTrendLine"), NULL); /* regression equations */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0; /* hairline */ style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_PATTERN; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE); gog_theme_add_element (theme, style, NULL, g_strdup ("GogRegEqn"), NULL); /* series labels */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_NONE; style->line.width = 0; /* none */ style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_NONE; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE); go_style_set_font_desc (style, pango_font_description_from_string ("Sans 6")); gog_theme_add_element (theme, style, NULL, g_strdup ("GogSeriesLabels"), NULL); /* data label */ style = go_style_new (); style->line.dash_type = GO_LINE_NONE; style->line.width = 0; /* none */ style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_NONE; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE); go_style_set_font_desc (style, pango_font_description_from_string ("Sans 6")); gog_theme_add_element (theme, style, NULL, g_strdup ("GogDataLabel"), NULL); /* Color scale */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0; /* hairline */ style->line.color = GO_COLOR_BLACK; gog_theme_add_element (theme, style, NULL, g_strdup ("GogColorScale"), NULL); #ifdef GOFFICE_WITH_LASEM /* Equations */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_NONE; style->line.width = 0; /* hairline */ style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_NONE; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE); go_style_set_font_desc (style, pango_font_description_from_string ("Sans 10")); gog_theme_add_element (theme, style, NULL, g_strdup ("GogEquation"), NULL); #endif /* builds the default discrete color map */ theme->dcm = gog_axis_color_map_from_colors (N_("Theme"), G_N_ELEMENTS (default_palette), default_palette, GO_RESOURCE_GENERATED); /* Guppi */ theme = gog_theme_new (N_("Guppi"), GO_RESOURCE_NATIVE); theme->description = g_strdup (_("Guppi theme")); gog_theme_registry_add (theme, FALSE); /* graph */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_NONE; style->line.width = 0; style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_GRADIENT; style->fill.gradient.dir = GO_GRADIENT_N_TO_S; style->fill.pattern.fore = GO_COLOR_BLUE; style->fill.pattern.back = GO_COLOR_BLACK; gog_theme_add_element (theme, style, NULL, g_strdup ("GogGraph"), NULL); /* chart */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0; /* hairline */ style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_PATTERN; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE); gog_theme_add_element (theme, style, NULL, g_strdup ("GogChart"), NULL); /* legend */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0; /* hairline */ style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_PATTERN; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE); gog_theme_add_element (theme, style, NULL, g_strdup ("GogLegend"), NULL); /* Axis */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0.; /* hairline */ style->line.color = GO_COLOR_GREY (0x20); gog_theme_add_element (theme, style, NULL, g_strdup ("GogAxis"), NULL); /* AxisLine */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0.; /* hairline */ style->line.color = GO_COLOR_GREY (0x20); gog_theme_add_element (theme, style, NULL, g_strdup ("GogAxisLine"), NULL); /* Grid */ style = go_style_new (); go_style_clear_auto (style); style->fill.type = GO_STYLE_FILL_PATTERN; style->line.dash_type = GO_LINE_NONE; style->line.color = GO_COLOR_BLACK; style->line.width = 0; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_GREY (0xd0)); gog_theme_add_element (theme, style, NULL, g_strdup ("GogGrid"), NULL); /* GridLine */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0.; /* hairline */ style->line.color = GO_COLOR_GREY (0x96); go_pattern_set_solid (&style->fill.pattern, GO_COLOR_FROM_RGBA (0xE0, 0xE0, 0xE0, 0xE0)); style->fill.type = GO_STYLE_FILL_NONE; gog_theme_add_element (theme, style, NULL, NULL, g_strdup ("MajorGrid")); style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0.; /* hairline */ style->line.color = GO_COLOR_GREY (0xC0); go_pattern_set_solid (&style->fill.pattern, GO_COLOR_FROM_RGBA (0xE0, 0xE0, 0xE0, 0xE0)); style->fill.type = GO_STYLE_FILL_NONE; gog_theme_add_element (theme, style, NULL, NULL, g_strdup ("MinorGrid")); /* series */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0.; /* hairline */ style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_PATTERN; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_GREY (0x20)); /* FIXME : not really true, will want to split area from line */ gog_theme_add_element (theme, style, map_area_series_solid_guppi, g_strdup ("GogSeries"), NULL); /* labels */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_NONE; style->line.width = 0; /* none */ style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_NONE; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE); gog_theme_add_element (theme, style, NULL, g_strdup ("GogLabel"), NULL); /* trend lines */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 1; style->line.color = GO_COLOR_BLACK; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_FROM_RGBA (0x00, 0x00, 0x00, 0x20)); style->fill.type = GO_STYLE_FILL_NONE; gog_theme_add_element (theme, style, NULL, g_strdup ("GogTrendLine"), NULL); /* regression equations */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0; /* hairline */ style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_PATTERN; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE); gog_theme_add_element (theme, style, NULL, g_strdup ("GogRegEqn"), NULL); /* series labels */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_NONE; style->line.width = 0; /* none */ style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_NONE; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE); go_style_set_font_desc (style, pango_font_description_from_string ("Sans 6")); gog_theme_add_element (theme, style, NULL, g_strdup ("GogSeriesLabels"), NULL); /* data label */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_NONE; style->line.width = 0; /* none */ style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_NONE; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE); go_style_set_font_desc (style, pango_font_description_from_string ("Sans 6")); gog_theme_add_element (theme, style, NULL, g_strdup ("GogDataLabel"), NULL); /* Color scale */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_SOLID; style->line.width = 0; /* hairline */ style->line.color = GO_COLOR_BLACK; gog_theme_add_element (theme, style, NULL, g_strdup ("GogColorScale"), NULL); #ifdef GOFFICE_WITH_LASEM /* Equations */ style = go_style_new (); go_style_clear_auto (style); style->line.dash_type = GO_LINE_NONE; style->line.width = 0; /* hairline */ style->line.color = GO_COLOR_BLACK; style->fill.type = GO_STYLE_FILL_NONE; go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE); go_style_set_font_desc (style, pango_font_description_from_string ("Sans 10")); gog_theme_add_element (theme, style, NULL, g_strdup ("GogEquation"), NULL); #endif theme->dcm = gog_axis_color_map_from_colors (N_("Theme"), G_N_ELEMENTS (guppi_palette), guppi_palette, GO_RESOURCE_GENERATED); } static void theme_load_from_uri (char const *uri) { struct theme_load_state state; GsfInput *input = go_file_open (uri, NULL); if (input == NULL) { g_warning ("[GogTheme]: Could not open %s", uri); return; } state.theme = NULL; state.desc = state.lang = state.name = NULL; state.langs = g_get_language_names (); state.name_lang_score = state.desc_lang_score = G_MAXINT; if (!xml) xml = gsf_xml_in_doc_new (theme_dtd, NULL); if (!gsf_xml_in_doc_parse (xml, input, &state)) g_warning ("[GogTheme]: Could not parse %s", uri); if (state.theme != NULL) { if (!go_file_access (uri, GO_W_OK)) { state.theme->uri = g_strdup (uri); if (state.theme->id == NULL) { state.theme->id = go_uuid (); gog_theme_save (state.theme); } state.theme->type = GO_RESOURCE_RW; } else { if (state.theme->id == NULL) { /* just duplicate the name, anyway, this should never occur */ char *name = g_hash_table_lookup (state.theme->names, "C"); if (name) state.theme->id = g_strdup (name); else g_warning ("[GogTheme]: Theme with no Id in %s", uri); } state.theme->type = GO_RESOURCE_RO; } theme_loaded (&state); gog_theme_registry_add (state.theme, FALSE); } else { g_free (state.name); g_free (state.desc); } g_free (state.lang); g_object_unref (input); } static void themes_load_from_dir (char const *path) { GDir *dir = g_dir_open (path, 0, NULL); char const *d_name; if (dir == NULL) return; while ((d_name = g_dir_read_name (dir)) != NULL) { char *fullname = g_build_filename (path, d_name, NULL); char *uri = go_filename_to_uri (fullname); char *mime_type = go_get_mime_type (uri); if (!strcmp (mime_type, "application/x-theme")) theme_load_from_uri (uri); g_free (mime_type); g_free (uri); g_free (fullname); } g_dir_close (dir); } void _gog_themes_init (void) { char *path; _gog_axis_color_maps_init (); build_predefined_themes (); /* Load themes from file */ path = g_build_filename (go_sys_data_dir (), "themes", NULL); themes_load_from_dir (path); g_free (path); path = g_build_filename (g_get_home_dir (), ".goffice", "themes", NULL); themes_load_from_dir (path); g_free (path); } void _gog_themes_shutdown (void) { if (default_theme != NULL) { g_object_unref (default_theme); default_theme = NULL; } g_slist_free_full (g_slist_copy (themes), g_object_unref); g_slist_free (themes); g_hash_table_destroy (global_class_aliases); _gog_axis_color_maps_shutdown (); themes = NULL; if (xml) gsf_xml_in_doc_free (xml); } /** * gog_theme_get_color_map: * @theme: #GogTheme * @discrete: whether the map is for a discrete axis. * * Retrieves the themed color map. Each theme has a discrete color map and a * continuous one. * Returns: (transfer none): the requested color map. **/ GogAxisColorMap const * gog_theme_get_color_map (GogTheme const *theme, gboolean discrete) { g_return_val_if_fail (GOG_IS_THEME (theme), NULL); if (discrete) return theme->dcm; else return (theme->cm)? theme->cm: _gog_axis_color_map_get_default (); return NULL; } /** * gog_theme_delete: * @theme: a #GogTheme * * Destroys the theme and remove it from the user directory and from the * database. * Returns: %TRUE on success. **/ gboolean gog_theme_delete (GogTheme *theme) { GFile *file = g_file_new_for_uri (theme->uri); gboolean res; if ((res = g_file_delete (file, NULL, NULL))) { themes = g_slist_remove (themes, theme); g_object_unref (theme); } g_object_unref (file); return res; } /***************** * Theme edition * *****************/ /** * gog_theme_foreach: * @handler: (scope call): a #GFunc using a theme as first argument * @user_data: data to pass to @handler * * Executes @handler to each theme installed on the system, including built-in * themes. **/ void gog_theme_foreach (GFunc handler, gpointer user_data) { g_slist_foreach (themes, handler, user_data); } static void gog_theme_set_name (GogTheme *theme, char const *name) { g_return_if_fail (GOG_IS_THEME (theme)); g_free (theme->name); g_hash_table_remove_all (theme->names); theme->name = g_strdup (name); g_hash_table_insert (theme->names, g_strdup ("C"), g_strdup (name)); } static void gog_theme_set_description (GogTheme *theme, char const *desc) { g_return_if_fail (GOG_IS_THEME (theme)); g_free (theme->description); g_hash_table_remove_all (theme->descs); theme->description = g_strdup (desc); g_hash_table_insert (theme->descs, g_strdup ("C"), g_strdup (desc)); } /** * gog_theme_dup: * @theme: a #GogTheme * * Duplicates @theme with a new Id. * Returns: (transfer full): the new theme. **/ GogTheme* gog_theme_dup (GogTheme *theme) { GogTheme *new_theme; char *desc, *name; g_return_val_if_fail (GOG_IS_THEME (theme), NULL); new_theme = g_object_new (GOG_TYPE_THEME, "resource-type", GO_RESOURCE_RW, NULL); new_theme->id = go_uuid (); gog_theme_build_uri (new_theme); gog_theme_set_name (new_theme, "New theme"); name = g_hash_table_lookup (theme->names, "C"); desc = g_strdup_printf ("New theme base on %s", name); gog_theme_set_description (new_theme, desc); g_free (desc); /* duplicate the styles */ /* duplicate the color maps */ if (theme->cm) { new_theme->cm = gog_axis_color_map_dup (theme->cm); g_object_set (G_OBJECT (new_theme->cm), "resource-type", GO_RESOURCE_CHILD, NULL); } if (theme->dcm && gog_axis_color_map_get_resource_type (theme->dcm) == GO_RESOURCE_CHILD) { new_theme->dcm = gog_axis_color_map_dup (theme->dcm); g_object_set (G_OBJECT (new_theme->dcm), "resource-type", GO_RESOURCE_CHILD, NULL); } return new_theme; } #ifdef GOFFICE_WITH_GTK struct theme_edit_state { GtkBuilder *gui; GogTheme *theme; }; static void create_toggled_cb (GtkListStore *list, char const *path) { GtkTreeIter iter; gboolean set; gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (list), &iter, path); gtk_tree_model_get (GTK_TREE_MODEL (list), &iter, 1, &set, -1); gtk_list_store_set (list, &iter, 1, !set, -1); } /** * gog_theme_edit: * @theme: the #GogTheme to edit or %NULL to create a new one. * @cc: a #GOCmdContext or %NULL. * * Displays a dialog box to edit the theme. This can be done only for * locally installed themes that are writeable. * Returns: (transfer none): The edited theme or %NULL if the edition has * been cancelled. **/ GogTheme * gog_theme_edit (GogTheme *theme, GOCmdContext *cc) { struct theme_edit_state state; GtkWidget *w; if (!GOG_IS_THEME (theme)) { /* display a dialog box to select used roles and series number */ GtkBuilder *gui = go_gtk_builder_load_internal ("res:go:graph/new-theme-prefs.ui", GETTEXT_PACKAGE, cc); int response; GtkWidget *w; GtkListStore *l = GTK_LIST_STORE (gtk_builder_get_object (gui, "classes-list")); GtkTreeView *tv = GTK_TREE_VIEW (gtk_builder_get_object (gui, "classes-tree")); GtkCellRenderer *renderer; GtkTreeViewColumn *column; GtkTreeIter iter; unsigned i; renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes (_("Class"), renderer, "text", 0, NULL); gtk_tree_view_append_column (tv, column); renderer = gtk_cell_renderer_toggle_new (); column = gtk_tree_view_column_new_with_attributes (_("Create"), renderer, "active", 1, NULL); gtk_tree_view_append_column (tv, column); for (i = 0; i < G_N_ELEMENTS (roles); i++) { if (!strcmp (roles[i].klass_name, "Series")) continue; gtk_list_store_append (l, &iter); gtk_list_store_set (l, &iter, 0, _(roles[i].label), 1, FALSE, 2, i, -1); } g_signal_connect_swapped (renderer, "toggled", G_CALLBACK (create_toggled_cb), l); w = go_gtk_builder_get_widget (gui, "new-theme-prefs"); response = gtk_dialog_run (GTK_DIALOG (w)); gtk_widget_destroy (w); g_object_unref (gui); if (response == 1) { theme = gog_theme_new (_("New theme"), FALSE); theme->id = go_uuid (); theme->type = GO_RESOURCE_RW; } else return NULL; } state.theme = theme; state.gui = go_gtk_builder_load_internal ("res:go:graph/gog-theme-editor.ui", GETTEXT_PACKAGE, cc); w = go_gtk_builder_get_widget (state.gui, "gog-theme-editor"); if (gtk_dialog_run (GTK_DIALOG (w))) { } gtk_widget_destroy (w); g_object_unref (state.gui); return NULL; } #endif