mirror of https://github.com/Gnucash/gnucash
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
454 lines
13 KiB
454 lines
13 KiB
/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
|
|
/*
|
|
* go-plot-engine.c :
|
|
*
|
|
* Copyright (C) 2003-2004 Jody Goldberg (jody@gnome.org)
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of version 2 of the GNU General Public
|
|
* License as published by the Free Software Foundation.
|
|
*
|
|
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
|
* USA
|
|
*/
|
|
|
|
#include <goffice/goffice-config.h>
|
|
#include <goffice/graph/gog-plot-engine.h>
|
|
#include <goffice/graph/gog-plot-impl.h>
|
|
#include <goffice/graph/gog-theme.h>
|
|
#include <glib/gi18n.h>
|
|
#include <xml-io.h>
|
|
|
|
#include <gsf/gsf-impl-utils.h>
|
|
|
|
#include <string.h>
|
|
|
|
static GSList *refd_plugins;
|
|
|
|
/***************************************************************************/
|
|
/* Support plot engines in plugins */
|
|
|
|
#include <plugin-service-impl.h>
|
|
|
|
#define GOG_PLOT_ENGINE_SERVICE_TYPE (gog_plot_engine_service_get_type ())
|
|
#define GOG_PLOT_ENGINE_SERVICE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOG_PLOT_ENGINE_SERVICE_TYPE, GogPlotEngineService))
|
|
#define IS_GOG_PLOT_ENGINE_SERVICE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOG_PLOT_ENGINE_SERVICE_TYPE))
|
|
|
|
static GType gog_plot_engine_service_get_type (void);
|
|
|
|
typedef PluginServiceGObjectLoader GogPlotEngineService;
|
|
typedef PluginServiceGObjectLoaderClass GogPlotEngineServiceClass;
|
|
|
|
static GHashTable *pending_engines = NULL;
|
|
|
|
static char *
|
|
gog_plot_engine_service_get_description (GnmPluginService *service)
|
|
{
|
|
return g_strdup (_("Plot Engine"));
|
|
}
|
|
|
|
static void
|
|
gog_plot_engine_service_class_init (PluginServiceGObjectLoaderClass *gobj_loader_class)
|
|
{
|
|
GnmPluginServiceClass *ps_class = GPS_CLASS (gobj_loader_class);
|
|
|
|
ps_class->get_description = gog_plot_engine_service_get_description;
|
|
|
|
gobj_loader_class->pending =
|
|
pending_engines = g_hash_table_new (g_str_hash, g_str_equal);
|
|
}
|
|
|
|
GSF_CLASS (GogPlotEngineService, gog_plot_engine_service,
|
|
gog_plot_engine_service_class_init, NULL,
|
|
GNM_PLUGIN_SERVICE_GOBJECT_LOADER_TYPE)
|
|
|
|
GogPlot *
|
|
gog_plot_new_by_name (char const *id)
|
|
{
|
|
GType type = g_type_from_name (id);
|
|
|
|
if (type == 0) {
|
|
ErrorInfo *err = NULL;
|
|
GnmPluginService *service =
|
|
pending_engines
|
|
? g_hash_table_lookup (pending_engines, id)
|
|
: NULL;
|
|
GnmPlugin *plugin;
|
|
|
|
if (!service || !service->is_active)
|
|
return NULL;
|
|
|
|
g_return_val_if_fail (!service->is_loaded, NULL);
|
|
|
|
plugin_service_load (service, &err);
|
|
type = g_type_from_name (id);
|
|
|
|
if (err != NULL) {
|
|
error_info_print (err);
|
|
error_info_free (err);
|
|
}
|
|
|
|
g_return_val_if_fail (type != 0, NULL);
|
|
|
|
/*
|
|
* The plugin defined a gtype so it must not be unloaded.
|
|
*/
|
|
plugin = plugin_service_get_plugin (service);
|
|
refd_plugins = g_slist_prepend (refd_plugins, plugin);
|
|
g_object_ref (plugin);
|
|
gnm_plugin_use_ref (plugin);
|
|
}
|
|
|
|
return g_object_new (type, NULL);
|
|
}
|
|
|
|
/***************************************************************************/
|
|
/* Use a plugin service to define where to find plot types */
|
|
|
|
#define GOG_PLOT_TYPE_SERVICE_TYPE (gog_plot_type_service_get_type ())
|
|
#define GOG_PLOT_TYPE_SERVICE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOG_PLOT_TYPE_SERVICE_TYPE, GogPlotTypeService))
|
|
#define IS_GOG_PLOT_TYPE_SERVICE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOG_PLOT_TYPE_SERVICE_TYPE))
|
|
|
|
GType gog_plot_type_service_get_type (void);
|
|
|
|
typedef struct {
|
|
PluginServiceSimple base;
|
|
|
|
GSList *families, *types;
|
|
} GogPlotTypeService;
|
|
|
|
typedef struct{
|
|
PluginServiceSimpleClass base;
|
|
} GogPlotTypeServiceClass;
|
|
|
|
static GHashTable *pending_plot_type_files = NULL;
|
|
static GObjectClass *plot_type_parent_klass;
|
|
|
|
static void
|
|
cb_pending_plot_types_load (char const *path,
|
|
GogPlotTypeService *service,
|
|
G_GNUC_UNUSED gpointer ignored)
|
|
{
|
|
xmlNode *ptr, *prop;
|
|
xmlDoc *doc = xmlParseFile (path);
|
|
GogPlotFamily *family = NULL;
|
|
GogPlotType *type;
|
|
int col, row;
|
|
xmlChar *name, *image_file, *description, *engine;
|
|
|
|
g_return_if_fail (doc != NULL && doc->xmlRootNode != NULL);
|
|
|
|
/* do the families before the types so that they are available */
|
|
for (ptr = doc->xmlRootNode->xmlChildrenNode; ptr ; ptr = ptr->next)
|
|
if (!xmlIsBlankNode (ptr) && ptr->name && !strcmp (ptr->name, "Family")) {
|
|
name = xmlGetProp (ptr, "_name");
|
|
image_file = xmlGetProp (ptr, "sample_image_file");
|
|
family = gog_plot_family_register (name, image_file);
|
|
if (family != NULL)
|
|
service->families = g_slist_prepend (service->families, family);
|
|
if (name != NULL) xmlFree (name);
|
|
if (image_file != NULL) xmlFree (image_file);
|
|
}
|
|
|
|
for (ptr = doc->xmlRootNode->xmlChildrenNode; ptr ; ptr = ptr->next)
|
|
if (!xmlIsBlankNode (ptr) && ptr->name && !strcmp (ptr->name, "Type")) {
|
|
name = xmlGetProp (ptr, "family");
|
|
if (name != NULL) {
|
|
family = gog_plot_family_by_name (name);
|
|
xmlFree (name);
|
|
if (family == NULL)
|
|
continue;
|
|
}
|
|
|
|
name = xmlGetProp (ptr, "_name");
|
|
image_file = xmlGetProp (ptr, "sample_image_file");
|
|
description = xmlGetProp (ptr, "_description");
|
|
engine = xmlGetProp (ptr, "engine");
|
|
if (xml_node_get_int (ptr, "col", &col) &&
|
|
xml_node_get_int (ptr, "row", &row)) {
|
|
type = gog_plot_type_register (family, col, row,
|
|
name, image_file, description, engine);
|
|
if (type != NULL) {
|
|
service->types = g_slist_prepend (service->types, type);
|
|
for (prop = ptr->xmlChildrenNode ; prop != NULL ; prop = prop->next)
|
|
if (!xmlIsBlankNode (prop) &&
|
|
prop->name && !strcmp (prop->name, "property")) {
|
|
xmlChar *prop_name = xmlGetProp (prop, "name");
|
|
|
|
if (prop_name == NULL) {
|
|
g_warning ("missing name for property entry");
|
|
continue;
|
|
}
|
|
|
|
if (type->properties == NULL)
|
|
type->properties = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
xmlFree, xmlFree);
|
|
g_hash_table_replace (type->properties,
|
|
prop_name, xmlNodeGetContent (prop));
|
|
}
|
|
}
|
|
}
|
|
if (name != NULL) xmlFree (name);
|
|
if (image_file != NULL) xmlFree (image_file);
|
|
if (description != NULL) xmlFree (description);
|
|
if (engine != NULL) xmlFree (engine);
|
|
}
|
|
|
|
xmlFreeDoc (doc);
|
|
}
|
|
|
|
static void
|
|
pending_plot_types_load (void)
|
|
{
|
|
if (pending_plot_type_files != NULL) {
|
|
GHashTable *tmp = pending_plot_type_files;
|
|
pending_plot_type_files = NULL;
|
|
g_hash_table_foreach (tmp,
|
|
(GHFunc) cb_pending_plot_types_load, NULL);
|
|
g_hash_table_destroy (tmp);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gog_plot_type_service_read_xml (GnmPluginService *service, xmlNode *tree, ErrorInfo **ret_error)
|
|
{
|
|
char *path;
|
|
xmlNode *ptr;
|
|
|
|
for (ptr = tree->xmlChildrenNode; ptr != NULL; ptr = ptr->next)
|
|
if (0 == xmlStrcmp (ptr->name, "file") &&
|
|
NULL != (path = xmlNodeGetContent (ptr))) {
|
|
if (!g_path_is_absolute (path)) {
|
|
char const *dir = gnm_plugin_get_dir_name (
|
|
plugin_service_get_plugin (service));
|
|
char *tmp = g_build_filename (dir, path, NULL);
|
|
g_free (path);
|
|
path = tmp;
|
|
}
|
|
if (pending_plot_type_files == NULL)
|
|
pending_plot_type_files = g_hash_table_new_full (
|
|
g_str_hash, g_str_equal, g_free, g_object_unref);
|
|
g_object_ref (service);
|
|
g_hash_table_replace (pending_plot_type_files, path, service);
|
|
}
|
|
}
|
|
|
|
static char *
|
|
gog_plot_type_service_get_description (GnmPluginService *service)
|
|
{
|
|
return g_strdup (_("Plot Type"));
|
|
}
|
|
|
|
static void
|
|
gog_plot_type_service_finalize (GObject *obj)
|
|
{
|
|
GogPlotTypeService *service = GOG_PLOT_TYPE_SERVICE (obj);
|
|
GSList *ptr;
|
|
|
|
for (ptr = service->families ; ptr != NULL ; ptr = ptr->next) {
|
|
}
|
|
g_slist_free (service->families);
|
|
service->families = NULL;
|
|
|
|
for (ptr = service->types ; ptr != NULL ; ptr = ptr->next) {
|
|
}
|
|
g_slist_free (service->types);
|
|
service->types = NULL;
|
|
|
|
(plot_type_parent_klass->finalize) (obj);
|
|
}
|
|
|
|
static void
|
|
gog_plot_type_service_init (GObject *obj)
|
|
{
|
|
GogPlotTypeService *service = GOG_PLOT_TYPE_SERVICE (obj);
|
|
|
|
service->families = NULL;
|
|
service->types = NULL;
|
|
}
|
|
|
|
static void
|
|
gog_plot_type_service_class_init (GObjectClass *gobject_klass)
|
|
{
|
|
GnmPluginServiceClass *ps_class = GPS_CLASS (gobject_klass);
|
|
|
|
plot_type_parent_klass = g_type_class_peek_parent (gobject_klass);
|
|
gobject_klass->finalize = gog_plot_type_service_finalize;
|
|
ps_class->read_xml = gog_plot_type_service_read_xml;
|
|
ps_class->get_description = gog_plot_type_service_get_description;
|
|
}
|
|
|
|
GSF_CLASS (GogPlotTypeService, gog_plot_type_service,
|
|
gog_plot_type_service_class_init, gog_plot_type_service_init,
|
|
GNM_PLUGIN_SERVICE_SIMPLE_TYPE)
|
|
|
|
/***************************************************************************/
|
|
/* Use a plugin service to define themes */
|
|
|
|
#define GOG_THEME_SERVICE_TYPE (gog_theme_service_get_type ())
|
|
#define GOG_THEME_SERVICE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOG_THEME_SERVICE_TYPE, GogThemeService))
|
|
#define IS_GOG_THEME_SERVICE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOG_THEME_SERVICE_TYPE))
|
|
|
|
GType gog_theme_service_get_type (void);
|
|
|
|
typedef PluginServiceSimple GogThemeService;
|
|
typedef PluginServiceSimpleClass GogThemeServiceClass;
|
|
|
|
static void
|
|
gog_theme_service_read_xml (GnmPluginService *service, xmlNode *tree, ErrorInfo **ret_error)
|
|
{
|
|
char *path;
|
|
xmlNode *ptr;
|
|
|
|
for (ptr = tree->xmlChildrenNode; ptr != NULL; ptr = ptr->next)
|
|
if (0 == xmlStrcmp (ptr->name, "file") &&
|
|
NULL != (path = xmlNodeGetContent (ptr))) {
|
|
if (!g_path_is_absolute (path)) {
|
|
char const *dir = gnm_plugin_get_dir_name (
|
|
plugin_service_get_plugin (service));
|
|
char *tmp = g_build_filename (dir, path, NULL);
|
|
g_free (path);
|
|
path = tmp;
|
|
}
|
|
gog_theme_register_file (
|
|
plugin_service_get_description (service), path);
|
|
}
|
|
}
|
|
|
|
static char *
|
|
gog_theme_service_get_description (GnmPluginService *service)
|
|
{
|
|
return g_strdup (_("Chart Theme"));
|
|
}
|
|
|
|
static void
|
|
gog_theme_service_class_init (GnmPluginServiceClass *ps_class)
|
|
{
|
|
ps_class->read_xml = gog_theme_service_read_xml;
|
|
ps_class->get_description = gog_theme_service_get_description;
|
|
}
|
|
|
|
GSF_CLASS (GogThemeService, gog_theme_service,
|
|
gog_theme_service_class_init, NULL,
|
|
GNM_PLUGIN_SERVICE_SIMPLE_TYPE)
|
|
|
|
/***************************************************************************/
|
|
|
|
void
|
|
gog_plugin_services_init (void)
|
|
{
|
|
plugin_service_define ("plot_engine", &gog_plot_engine_service_get_type);
|
|
plugin_service_define ("plot_type", &gog_plot_type_service_get_type);
|
|
plugin_service_define ("chart_theme", &gog_theme_service_get_type);
|
|
}
|
|
|
|
void
|
|
gog_plugin_services_shutdown (void)
|
|
{
|
|
g_slist_foreach (refd_plugins, (GFunc)gnm_plugin_use_unref, NULL);
|
|
g_slist_foreach (refd_plugins, (GFunc)g_object_unref, NULL);
|
|
g_slist_free (refd_plugins);
|
|
}
|
|
|
|
/***************************************************************************/
|
|
|
|
static GHashTable *plot_families = NULL;
|
|
|
|
static void
|
|
gog_plot_family_free (GogPlotFamily *family)
|
|
{
|
|
g_free (family->name); family->name = NULL;
|
|
g_free (family->sample_image_file); family->sample_image_file = NULL;
|
|
if (family->types) {
|
|
g_hash_table_destroy (family->types);
|
|
family->types = NULL;
|
|
}
|
|
g_free (family);
|
|
}
|
|
|
|
static void
|
|
gog_plot_type_free (GogPlotType *type)
|
|
{
|
|
g_free (type->name);
|
|
g_free (type->sample_image_file);
|
|
g_free (type->description);
|
|
g_free (type->engine);
|
|
g_free (type);
|
|
}
|
|
|
|
static void
|
|
create_plot_families (void)
|
|
{
|
|
if (!plot_families)
|
|
plot_families = g_hash_table_new_full
|
|
(g_str_hash, g_str_equal,
|
|
NULL, (GDestroyNotify) gog_plot_family_free);
|
|
}
|
|
|
|
GHashTable const *
|
|
gog_plot_families (void)
|
|
{
|
|
create_plot_families ();
|
|
pending_plot_types_load ();
|
|
return plot_families;
|
|
}
|
|
GogPlotFamily *
|
|
gog_plot_family_by_name (char const *name)
|
|
{
|
|
create_plot_families ();
|
|
pending_plot_types_load ();
|
|
return g_hash_table_lookup (plot_families, name);
|
|
}
|
|
|
|
GogPlotFamily *
|
|
gog_plot_family_register (char const *name, char const *sample_image_file)
|
|
{
|
|
GogPlotFamily *res;
|
|
|
|
g_return_val_if_fail (name != NULL, NULL);
|
|
g_return_val_if_fail (sample_image_file != NULL, NULL);
|
|
|
|
create_plot_families ();
|
|
g_return_val_if_fail (g_hash_table_lookup (plot_families, name) == NULL, NULL);
|
|
|
|
res = g_new0 (GogPlotFamily, 1);
|
|
res->name = g_strdup (name);
|
|
res->sample_image_file = g_strdup (sample_image_file);
|
|
res->types = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
NULL, (GDestroyNotify) gog_plot_type_free);
|
|
g_hash_table_insert (plot_families, res->name, res);
|
|
|
|
return res;
|
|
}
|
|
|
|
GogPlotType *
|
|
gog_plot_type_register (GogPlotFamily *family, int col, int row,
|
|
char const *name, char const *sample_image_file,
|
|
char const *description, char const *engine)
|
|
{
|
|
GogPlotType *res;
|
|
|
|
g_return_val_if_fail (family != NULL, NULL);
|
|
|
|
res = g_new0 (GogPlotType, 1);
|
|
res->name = g_strdup (name);
|
|
res->sample_image_file = g_strdup (sample_image_file);
|
|
res->description = g_strdup (description);
|
|
res->engine = g_strdup (engine);
|
|
|
|
res->col = col;
|
|
res->row = row;
|
|
res->family = family;
|
|
g_hash_table_replace (family->types, res->name, res);
|
|
|
|
return res;
|
|
}
|
|
|