/*
* Copyright (C) 2001 Ximian, Inc.
* Copyright (C) 2004 Imendio AB
*
* 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) any later version.
*
* 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Authors:
* Chema Celorio <chema@celorio.com>
*/
#include <config.h>
#include "glade.h"
#include "glade-catalog.h"
#include "glade-widget-adaptor.h"
#include "glade-private.h"
#include "glade-tsort.h"
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <glib.h>
#include <glib/gi18n-lib.h>
struct _GladeCatalog
{
guint16 major_version; /* The catalog version */
guint16 minor_version;
GList *targetable_versions; /* list of suitable version targets */
gchar *library; /* Library name for backend support */
gchar *name; /* Symbolic catalog name */
gchar *dep_catalog; /* Symbolic name of the catalog that
* this catalog depends on */
gchar *domain; /* The domain to be used to translate
* strings from this catalog (otherwise this
* defaults to the library name)
*/
gchar *book; /* Devhelp search domain */
gchar *icon_prefix; /* the prefix for icons */
GList *widget_groups; /* List of widget groups (palette) */
GList *adaptors; /* List of widget class adaptors (all) */
GladeXmlContext *context; /* Xml context is stored after open
* before classes are loaded */
GModule *module;
gchar *init_function_name; /* Catalog's init function name */
GladeCatalogInitFunc init_function;
};
struct _GladeWidgetGroup
{
gchar *name; /* Group name */
gchar *title; /* Group name in the palette */
gboolean expanded; /* Whether group is expanded in the palette */
GList *adaptors; /* List of class adaptors in the palette */
};
/* List of catalogs successfully loaded. */
static GList *loaded_catalogs = NULL;
/* Extra paths to load catalogs from */
static GList *catalog_paths = NULL;
static gboolean
catalog_get_function (GladeCatalog *catalog,
const gchar *symbol_name,
gpointer *symbol_ptr)
{
if (catalog->module == NULL)
catalog->module = glade_util_load_library (catalog->library);
if (catalog->module)
return g_module_symbol (catalog->module, symbol_name, symbol_ptr);
return FALSE;
}
static GladeCatalog *
catalog_allocate (void)
{
GladeCatalog *catalog;
catalog = g_slice_new0 (GladeCatalog);
catalog->library = NULL;
catalog->name = NULL;
catalog->dep_catalog = NULL;
catalog->domain = NULL;
catalog->book = NULL;
catalog->icon_prefix = NULL;
catalog->init_function_name = NULL;
catalog->module = NULL;
catalog->context = NULL;
catalog->adaptors = NULL;
catalog->widget_groups = NULL;
return catalog;
}
static void
widget_group_destroy (GladeWidgetGroup *group)
{
g_return_if_fail (GLADE_IS_WIDGET_GROUP (group));
g_free (group->name);
g_free (group->title);
g_list_free (group->adaptors);
g_slice_free (GladeWidgetGroup, group);
}
static void
catalog_destroy (GladeCatalog *catalog)
{
g_return_if_fail (GLADE_IS_CATALOG (catalog));
g_free (catalog->name);
g_free (catalog->library);
g_free (catalog->dep_catalog);
g_free (catalog->domain);
g_free (catalog->book);
g_free (catalog->icon_prefix);
g_free (catalog->init_function_name);
if (catalog->adaptors)
{
g_list_free (catalog->adaptors);
}
if (catalog->widget_groups)
{
g_list_foreach (catalog->widget_groups, (GFunc) widget_group_destroy,
NULL);
g_list_free (catalog->widget_groups);
}
if (catalog->context)
glade_xml_context_free (catalog->context);
g_slice_free (GladeCatalog, catalog);
}
static GladeCatalog *
catalog_open (const gchar *filename)
{
GladeTargetableVersion *version;
GladeCatalog *catalog;
GladeXmlContext *context;
GladeXmlDoc *doc;
GladeXmlNode *root;
gchar *name;
/* get the context & root node of the catalog file */
context = glade_xml_context_new_from_path (filename,
NULL, GLADE_TAG_GLADE_CATALOG);
if (!context)
{
g_warning ("Couldn't open catalog [%s].", filename);
return NULL;
}
doc = glade_xml_context_get_doc (context);
root = glade_xml_doc_get_root (doc);
if (!glade_xml_node_verify (root, GLADE_TAG_GLADE_CATALOG))
{
g_warning ("Catalog root node is not '%s', skipping %s",
GLADE_TAG_GLADE_CATALOG, filename);
glade_xml_context_free (context);
return NULL;
}
if (!(name = glade_xml_get_property_string_required (root, GLADE_TAG_NAME, NULL)))
return NULL;
catalog = catalog_allocate ();
catalog->context = context;
catalog->name = name;
glade_xml_get_property_version (root, GLADE_TAG_VERSION,
&catalog->major_version,
&catalog->minor_version);
/* Make one default suitable target */
version = g_new (GladeTargetableVersion, 1);
version->major = catalog->major_version;
version->minor = catalog->minor_version;
catalog->targetable_versions =
glade_xml_get_property_targetable_versions (root, GLADE_TAG_TARGETABLE);
catalog->targetable_versions =
g_list_prepend (catalog->targetable_versions, version);
catalog->library = glade_xml_get_property_string (root, GLADE_TAG_LIBRARY);
catalog->dep_catalog =
glade_xml_get_property_string (root, GLADE_TAG_DEPENDS);
catalog->domain = glade_xml_get_property_string (root, GLADE_TAG_DOMAIN);
catalog->book = glade_xml_get_property_string (root, GLADE_TAG_BOOK);
catalog->icon_prefix =
glade_xml_get_property_string (root, GLADE_TAG_ICON_PREFIX);
catalog->init_function_name =
glade_xml_get_value_string (root, GLADE_TAG_INIT_FUNCTION);
if (!catalog->domain)
catalog->domain = g_strdup (catalog->library);
/* catalog->icon_prefix defaults to catalog->name */
if (!catalog->icon_prefix)
catalog->icon_prefix = g_strdup (catalog->name);
if (catalog->init_function_name)
{
if (!catalog_get_function (catalog, catalog->init_function_name,
(gpointer) & catalog->init_function))
g_warning ("Failed to find and execute catalog '%s' init function '%s'",
glade_catalog_get_name (catalog),
catalog->init_function_name);
}
return catalog;
}
static GHashTable *modules = NULL;
static GModule *
catalog_load_library (GladeCatalog *catalog)
{
GModule *module;
if (modules == NULL)
modules = g_hash_table_new_full (g_str_hash,
g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_module_close);
if (catalog->library == NULL)
return NULL;
if ((module = g_hash_table_lookup (modules, catalog->library)))
return module;
if ((module = glade_util_load_library (catalog->library)))
g_hash_table_insert (modules, g_strdup (catalog->library), module);
else
g_warning ("Failed to load external library '%s' for catalog '%s'",
catalog->library,
glade_catalog_get_name (catalog));
return module;
}
static gboolean
catalog_load_classes (GladeCatalog *catalog, GladeXmlNode *widgets_node)
{
GladeXmlNode *node;
GModule *module = catalog_load_library (catalog);
node = glade_xml_node_get_children (widgets_node);
for (; node; node = glade_xml_node_next (node))
{
const gchar *node_name;
GladeWidgetAdaptor *adaptor;
node_name = glade_xml_node_get_name (node);
if (strcmp (node_name, GLADE_TAG_GLADE_WIDGET_CLASS) != 0)
continue;
adaptor = glade_widget_adaptor_from_catalog (catalog, node, module);
catalog->adaptors = g_list_prepend (catalog->adaptors, adaptor);
}
return TRUE;
}
static gboolean
catalog_load_group (GladeCatalog *catalog, GladeXmlNode *group_node)
{
GladeWidgetGroup *group;
GladeXmlNode *node;
char *title, *translated_title;
group = g_slice_new0 (GladeWidgetGroup);
group->name = glade_xml_get_property_string (group_node, GLADE_TAG_NAME);
if (!group->name)
{
g_warning ("Required property 'name' not found in group node");
widget_group_destroy (group);
return FALSE;
}
title = glade_xml_get_property_string (group_node, GLADE_TAG_TITLE);
if (!title)
{
g_warning ("Required property 'title' not found in group node");
widget_group_destroy (group);
return FALSE;
}
/* by default, group is expanded in palette */
group->expanded = TRUE;
/* Translate it */
translated_title = dgettext (catalog->domain, title);
if (translated_title != title)
{
group->title = g_strdup (translated_title);
g_free (title);
}
else
{
group->title = title;
}
group->adaptors = NULL;
node = glade_xml_node_get_children (group_node);
for (; node; node = glade_xml_node_next (node))
{
const gchar *node_name;
GladeWidgetAdaptor *adaptor;
gchar *name;
node_name = glade_xml_node_get_name (node);
if (strcmp (node_name, GLADE_TAG_GLADE_WIDGET_CLASS_REF) == 0)
{
if ((name =
glade_xml_get_property_string (node, GLADE_TAG_NAME)) == NULL)
{
g_warning ("Couldn't find required property on %s",
GLADE_TAG_GLADE_WIDGET_CLASS);
continue;
}
if ((adaptor = glade_widget_adaptor_get_by_name (name)) == NULL)
{
g_warning ("Tried to include undefined widget "
"class '%s' in a widget group", name);
g_free (name);
continue;
}
g_free (name);
group->adaptors = g_list_prepend (group->adaptors, adaptor);
}
else if (strcmp (node_name, GLADE_TAG_DEFAULT_PALETTE_STATE) == 0)
{
group->expanded =
glade_xml_get_property_boolean
(node, GLADE_TAG_EXPANDED, group->expanded);
}
}
group->adaptors = g_list_reverse (group->adaptors);
catalog->widget_groups = g_list_prepend (catalog->widget_groups, group);
return TRUE;
}
static void
catalog_load (GladeCatalog *catalog)
{
GladeXmlDoc *doc;
GladeXmlNode *root;
GladeXmlNode *node;
g_return_if_fail (catalog->context != NULL);
doc = glade_xml_context_get_doc (catalog->context);
root = glade_xml_doc_get_root (doc);
node = glade_xml_node_get_children (root);
for (; node; node = glade_xml_node_next (node))
{
const gchar *node_name;
node_name = glade_xml_node_get_name (node);
if (strcmp (node_name, GLADE_TAG_GLADE_WIDGET_CLASSES) == 0)
{
catalog_load_classes (catalog, node);
}
else if (strcmp (node_name, GLADE_TAG_GLADE_WIDGET_GROUP) == 0)
{
catalog_load_group (catalog, node);
}
else
continue;
}
catalog->widget_groups = g_list_reverse (catalog->widget_groups);
catalog->context = (glade_xml_context_free (catalog->context), NULL);
return;
}
static GladeCatalog *
catalog_find_by_name (GList *catalogs, const gchar *name)
{
if (name)
{
GList *l;
for (l = catalogs; l; l = g_list_next (l))
{
GladeCatalog *catalog = l->data;
if (g_strcmp0 (catalog->name, name) == 0)
return catalog;
}
}
return NULL;
}
static gint
catalog_name_cmp (gconstpointer a, gconstpointer b)
{
return (a && b) ? g_strcmp0 (GLADE_CATALOG(a)->name, GLADE_CATALOG(b)->name) : 0;
}
static GList *
glade_catalog_tsort (GList *catalogs, gboolean loading)
{
GList *l, *sorted = NULL;
GList *deps = NULL;
/* Sort alphabetically first */
catalogs = g_list_sort (catalogs, catalog_name_cmp);
/* Generate dependency graph edges */
for (l = catalogs; l; l = g_list_next (l))
{
GladeCatalog *catalog = l->data, *dep;
if (!catalog->dep_catalog)
continue;
if ((dep = catalog_find_by_name ((loading) ? catalogs : loaded_catalogs,
catalog->dep_catalog)))
deps = _node_edge_prepend (deps, dep, catalog);
else
g_critical ("Catalog %s depends on catalog %s, not found",
catalog->name, catalog->dep_catalog);
}
sorted = _glade_tsort (&catalogs, &deps);
if (deps)
{
GList *l, *cycles = NULL;
g_warning ("Circular dependency detected loading catalogs, they will be ignored");
for (l = deps; l; l = g_list_next (l))
{
_NodeEdge *edge = l->data;
g_message ("\t%s depends on %s",
GLADE_CATALOG (edge->successor)->name,
GLADE_CATALOG (edge->successor)->dep_catalog);
if (loading && !g_list_find (cycles, edge->successor))
cycles = g_list_prepend (cycles, edge->successor);
}
if (cycles)
g_list_free_full (cycles, (GDestroyNotify) catalog_destroy);
_node_edge_list_free (deps);
}
return sorted;
}
static GList *
catalogs_from_path (GList *catalogs, const gchar *path)
{
GladeCatalog *catalog;
GDir *dir;
GError *error = NULL;
const gchar *filename;
/* Silent return if the directory didn't exist */
if (!g_file_test (path, G_FILE_TEST_IS_DIR))
return catalogs;
if ((dir = g_dir_open (path, 0, &error)) != NULL)
{
while ((filename = g_dir_read_name (dir)))
{
gchar *catalog_filename;
if (!g_str_has_suffix (filename, ".xml"))
continue;
/* Special case, ignore gresource files (which are present
* while running tests)
*/
if (g_str_has_suffix (filename, ".gresource.xml"))
continue;
/* If we're running in the bundle, don't ever try to load
* anything except the GTK+ catalog
*/
if (g_getenv (GLADE_ENV_BUNDLED) != NULL &&
strcmp (filename, "gtk+.xml") != 0)
continue;
catalog_filename = g_build_filename (path, filename, NULL);
catalog = catalog_open (catalog_filename);
g_free (catalog_filename);
if (catalog)
{
/* Verify that we are not loading the same catalog twice !
*/
if (catalog_find_by_name (catalogs, catalog->name))
catalog_destroy (catalog);
else
catalogs = g_list_prepend (catalogs, catalog);
}
else
g_warning ("Unable to open the catalog file %s.\n", filename);
}
g_dir_close (dir);
}
else
g_warning ("Failed to open catalog directory '%s': %s", path,
error->message);
return catalogs;
}
/**
* glade_catalog_add_path:
* @path:
*
* Adds a new path to the list of path to look catalogs for.
*/
void
glade_catalog_add_path (const gchar *path)
{
g_return_if_fail (path != NULL);
if (g_list_find_custom (catalog_paths, path, (GCompareFunc) g_strcmp0) == NULL)
catalog_paths = g_list_append (catalog_paths, g_strdup (path));
}
/**
* glade_catalog_remove_path:
* @path:
*
* Remove path from the list of path to look catalogs for.
* NULL to remove all paths.
*/
void
glade_catalog_remove_path (const gchar *path)
{
GList *l;
if (path == NULL)
{
g_list_free_full (catalog_paths, g_free);
catalog_paths = NULL;
}
else if ((l = g_list_find_custom (catalog_paths, path, (GCompareFunc) g_strcmp0)))
{
catalog_paths = g_list_remove_link (catalog_paths, l);
}
}
/**
* glade_catalog_get_extra_paths:
*
* Returns a list paths added by glade_catalog_add_path()
*/
const GList *
glade_catalog_get_extra_paths (void)
{
return catalog_paths;
}
/**
* glade_catalog_load_all:
*
* Loads all available catalogs in the system.
* First loads catalogs from GLADE_ENV_CATALOG_PATH,
* then from glade_app_get_catalogs_dir() and finally from paths specified with
* glade_catalog_add_path()
*
* Returns: the list of loaded GladeCatalog *
*/
const GList *
glade_catalog_load_all (void)
{
GList *catalogs = NULL, *l, *adaptors;
GladeCatalog *catalog;
const gchar *search_path;
gchar **split;
GString *icon_warning = NULL;
gint i;
/* Make sure we don't init the catalogs twice */
if (loaded_catalogs)
return loaded_catalogs;
/* First load catalogs from user specified directories ... */
if ((search_path = g_getenv (GLADE_ENV_CATALOG_PATH)) != NULL)
{
if ((split = g_strsplit (search_path, ":", 0)) != NULL)
{
for (i = 0; split[i] != NULL; i++)
catalogs = catalogs_from_path (catalogs, split[i]);
g_strfreev (split);
}
}
/* ... Then load catalogs from standard install directory */
if (g_getenv (GLADE_ENV_TESTING) == NULL)
catalogs = catalogs_from_path (catalogs, glade_app_get_catalogs_dir ());
/* And then load catalogs from extra paths */
for (l = catalog_paths; l; l = g_list_next (l))
catalogs = catalogs_from_path (catalogs, l->data);
/* Catalogs need dependancies, most catalogs depend on
* the gtk+ catalog, but some custom toolkits may depend
* on the gnome catalog for instance.
*/
catalogs = glade_catalog_tsort (catalogs, TRUE);
/* After sorting, execute init function and then load */
for (l = catalogs; l; l = g_list_next (l))
{
catalog = l->data;
if (catalog->init_function)
catalog->init_function (catalog->name);
}
for (l = catalogs; l; l = g_list_next (l))
{
catalog = l->data;
catalog_load (catalog);
}
/* Print a summery of widget adaptors missing icons.
*/
adaptors = glade_widget_adaptor_list_adaptors ();
for (l = adaptors; l; l = g_list_next (l))
{
GladeWidgetAdaptor *adaptor = l->data;
/* Dont print missing icons in unit tests */
if (glade_widget_adaptor_get_missing_icon (adaptor) &&
g_getenv (GLADE_ENV_TESTING) == NULL)
{
if (!icon_warning)
icon_warning = g_string_new ("Glade needs artwork; "
"a default icon will be used for "
"the following classes:");
g_string_append_printf (icon_warning,
"\n\t%s\tneeds an icon named '%s'",
glade_widget_adaptor_get_name (adaptor),
glade_widget_adaptor_get_missing_icon (adaptor));
}
}
g_list_free (adaptors);
if (icon_warning)
{
g_message ("%s", icon_warning->str);
g_string_free (icon_warning, TRUE);
}
loaded_catalogs = catalogs;
return loaded_catalogs;
}
/**
* glade_catalog_get_name:
* @catalog: a catalog object
*
* Returns: The symbolic catalog name.
*/
G_CONST_RETURN gchar *
glade_catalog_get_name (GladeCatalog *catalog)
{
g_return_val_if_fail (GLADE_IS_CATALOG (catalog), NULL);
return catalog->name;
}
/**
* glade_catalog_get_book:
* @catalog: a catalog object
*
* Returns: The Devhelp search domain.
*/
G_CONST_RETURN gchar *
glade_catalog_get_book (GladeCatalog *catalog)
{
g_return_val_if_fail (GLADE_IS_CATALOG (catalog), NULL);
return catalog->book;
}
/**
* glade_catalog_get_domain:
* @catalog: a catalog object
*
* Returns: The domain to be used to translate strings from this catalog
*/
G_CONST_RETURN gchar *
glade_catalog_get_domain (GladeCatalog *catalog)
{
g_return_val_if_fail (GLADE_IS_CATALOG (catalog), NULL);
return catalog->domain;
}
/**
* glade_catalog_get_icon_prefix:
* @catalog: a catalog object
*
* Returns: The prefix for icons.
*/
G_CONST_RETURN gchar *
glade_catalog_get_icon_prefix (GladeCatalog *catalog)
{
g_return_val_if_fail (GLADE_IS_CATALOG (catalog), NULL);
return catalog->icon_prefix;
}
/**
* glade_catalog_get_major_version:
* @catalog: a catalog object
*
* Returns: The catalog version
*/
guint16
glade_catalog_get_major_version (GladeCatalog *catalog)
{
g_return_val_if_fail (GLADE_IS_CATALOG (catalog), 0);
return catalog->major_version;
}
/**
* glade_catalog_get_minor_version:
* @catalog: a catalog object
*
* Returns: The catalog minor version
*/
guint16
glade_catalog_get_minor_version (GladeCatalog *catalog)
{
g_return_val_if_fail (GLADE_IS_CATALOG (catalog), 0);
return catalog->minor_version;
}
/**
* glade_catalog_get_targets:
* @catalog: a catalog object
*
* Returns: the list of suitable version targets.
*/
GList *
glade_catalog_get_targets (GladeCatalog *catalog)
{
g_return_val_if_fail (GLADE_IS_CATALOG (catalog), NULL);
return catalog->targetable_versions;
}
/**
* glade_catalog_get_widget_groups:
* @catalog: a catalog object
*
* Returns: the list of widget groups (palette)
*/
GList *
glade_catalog_get_widget_groups (GladeCatalog *catalog)
{
g_return_val_if_fail (GLADE_IS_CATALOG (catalog), NULL);
return catalog->widget_groups;
}
/**
* glade_catalog_get_adaptors:
* @catalog: a catalog object
*
* Returns: the list of widget class adaptors
*/
GList *
glade_catalog_get_adaptors (GladeCatalog *catalog)
{
g_return_val_if_fail (GLADE_IS_CATALOG (catalog), NULL);
return catalog->adaptors;
}
/**
* glade_catalog_is_loaded:
* @name: a catalog object
*
* Returns: Whether @name is loaded or not
*/
gboolean
glade_catalog_is_loaded (const gchar *name)
{
g_return_val_if_fail (name != NULL, FALSE);
g_assert (loaded_catalogs != NULL);
return catalog_find_by_name (loaded_catalogs, name) != NULL;
}
/**
* glade_catalog_destroy_all:
*
* Destroy and free all resources related with every loaded catalog.
*/
void
glade_catalog_destroy_all (void)
{
/* close catalogs */
if (loaded_catalogs)
{
GList *l;
for (l = loaded_catalogs; l; l = l->next)
catalog_destroy (GLADE_CATALOG (l->data));
g_list_free (loaded_catalogs);
loaded_catalogs = NULL;
}
/* close plugin modules */
if (modules)
{
g_hash_table_destroy (modules);
modules = NULL;
}
}
/**
* glade_widget_group_get_name:
* @group: a widget group
*
* Returns: the widget group name
*/
const gchar *
glade_widget_group_get_name (GladeWidgetGroup *group)
{
g_return_val_if_fail (group != NULL, NULL);
return group->name;
}
/**
* glade_widget_group_get_title:
* @group: a widget group
*
* Returns: the widget group name used in the palette
*/
const gchar *
glade_widget_group_get_title (GladeWidgetGroup *group)
{
g_return_val_if_fail (group != NULL, NULL);
return group->title;
}
/**
* glade_widget_group_get_expanded:
* @group: a widget group
*
* Returns: Whether group is expanded in the palette
*/
gboolean
glade_widget_group_get_expanded (GladeWidgetGroup *group)
{
g_return_val_if_fail (group != NULL, FALSE);
return group->expanded;
}
/**
* glade_widget_group_get_adaptors:
* @group: a widget group
*
* Returns: a list of class adaptors in the palette
*/
const GList *
glade_widget_group_get_adaptors (GladeWidgetGroup *group)
{
g_return_val_if_fail (group != NULL, NULL);
return group->adaptors;
}
/* Private API */
GladeCatalog *
_glade_catalog_get_catalog (const gchar *name)
{
g_return_val_if_fail (name != NULL, NULL);
g_assert (loaded_catalogs != NULL);
return catalog_find_by_name (loaded_catalogs, name);
}
GList *
_glade_catalog_tsort (GList *catalogs)
{
return glade_catalog_tsort (catalogs, FALSE);
}