/*
* bonobo-ui-util.c: Bonobo UI utility functions
*
* Author:
* Michael Meeks (michael@ximian.com)
*
* Copyright 2000, 2001 Ximian, Inc.
*/
#include <config.h>
#include <string.h>
#include <gtk/gtkstock.h>
#include <gtk/gtkimage.h>
#include <gtk/gtkiconfactory.h>
#include <libgnome/gnome-help.h>
#include <libgnome/gnome-init.h>
#include <libgnome/gnome-program.h>
#include <bonobo/bonobo-ui-xml.h>
#include <bonobo/bonobo-ui-util.h>
#include <bonobo/bonobo-i18n.h>
#include <bonobo/bonobo-ui-private.h>
static gchar *find_pixmap_in_path (const gchar *filename);
static const char write_lut[16] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
static const gint8 read_lut[128] = {
-1, -1, -1, -1, -1, -1, -1, -1, /* 0x00 -> 0x07 */
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, /* 0x10 -> 0x17 */
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, /* 0x20 -> 0x27 */
-1, -1, -1, -1, -1, -1, -1, -1,
0, 1, 2, 3, 4, 5, 6, 7, /* 0x30 -> 0x37 */
8, 9, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1, /* 0x40 -> 0x47 */
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, /* 0x50 -> 0x57 */
-1, -1, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1, /* 0x60 -> 0x67 */
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, /* 0x70 -> 0x77 */
-1, -1, -1, -1, -1, -1, -1, -1,
};
static inline void
write_byte (char *start, guint8 byte)
{
start[0] = write_lut[byte >> 4];
start[1] = write_lut[byte & 15];
}
static inline void
write_four_bytes (char *pos, int value)
{
write_byte (pos + 0, value >> 24);
write_byte (pos + 2, value >> 16);
write_byte (pos + 4, value >> 8);
write_byte (pos + 6, value);
}
static void
read_warning (const char *start)
{
g_warning ("Format error in stream '%c', '%c'", start[0], start[1]);
}
static inline guint8
read_byte (const char *start)
{
guint8 byte1, byte2;
gint8 nibble1, nibble2;
byte1 = start[0];
byte2 = start[1];
if (byte1 >= 128 || byte2 >= 128)
read_warning (start);
nibble1 = read_lut[byte1];
nibble2 = read_lut[byte2];
if (nibble1 < 0 || nibble2 < 0)
read_warning (start);
return (nibble1 << 4) + nibble2;
}
static inline guint32
read_four_bytes (const char *pos)
{
return ((read_byte (pos) << 24) |
(read_byte (pos + 2) << 16) |
(read_byte (pos + 4) << 8) |
(read_byte (pos + 6)));
}
/**
* bonobo_ui_util_pixbuf_to_xml:
* @pixbuf: a GdkPixbuf
*
* Convert a @pixbuf to a string representation suitable
* for passing as a "pixname" attribute with a pixtype
* attribute = "pixbuf".
*
* Return value: the stringified pixbuf.
**/
char *
bonobo_ui_util_pixbuf_to_xml (GdkPixbuf *pixbuf)
{
char *xml, *dst, *src;
int size, width, height, row, row_stride, col, byte_width;
gboolean has_alpha;
g_return_val_if_fail (pixbuf != NULL, NULL);
width = gdk_pixbuf_get_width (pixbuf);
height = gdk_pixbuf_get_height (pixbuf);
has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
byte_width = width * (3 + (has_alpha ? 1 : 0));
size = 4 * 2 * 2 + /* width, height */
1 + 1 + /* alpha, terminator */
height * byte_width * 2;
xml = g_malloc (size);
xml [size - 1] = '\0';
dst = xml;
write_four_bytes (dst, gdk_pixbuf_get_width (pixbuf));
dst+= 4 * 2;
write_four_bytes (dst, gdk_pixbuf_get_height (pixbuf));
dst+= 4 * 2;
if (has_alpha)
*dst = 'A';
else
*dst = 'N';
dst++;
/* Copy over bitmap information */
src = gdk_pixbuf_get_pixels (pixbuf);
row_stride = gdk_pixbuf_get_rowstride (pixbuf);
for (row = 0; row < height; row++) {
for (col = 0; col < byte_width; col++) {
write_byte (dst, src [col]);
dst+= 2;
}
src += row_stride;
}
return xml;
}
/**
* bonobo_ui_util_xml_to_pixbuf:
* @xml: a string
*
* This converts a stringified pixbuf in @xml into a GdkPixbuf
*
* Return value: a handed reference to the created GdkPixbuf.
**/
GdkPixbuf *
bonobo_ui_util_xml_to_pixbuf (const char *xml)
{
GdkPixbuf *pixbuf;
int width, height, byte_width;
int length, row_stride, col, row;
gboolean has_alpha;
guint8 *dst;
g_return_val_if_fail (xml != NULL, NULL);
while (*xml && g_ascii_isspace (*xml))
xml++;
length = strlen (xml);
g_return_val_if_fail (length > 4 * 2 * 2 + 1, NULL);
width = read_four_bytes (xml);
xml += 4 * 2;
height = read_four_bytes (xml);
xml += 4 * 2;
if (*xml == 'A')
has_alpha = TRUE;
else if (*xml == 'N')
has_alpha = FALSE;
else {
g_warning ("Unknown type '%c'", *xml);
return NULL;
}
xml++;
byte_width = width * (3 + (has_alpha ? 1 : 0));
g_return_val_if_fail (length >= (byte_width * height * 2 + 4 * 2 * 2 + 1), NULL);
pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, has_alpha, 8, width, height);
dst = gdk_pixbuf_get_pixels (pixbuf);
row_stride = gdk_pixbuf_get_rowstride (pixbuf);
for (row = 0; row < height; row++) {
for (col = 0; col < byte_width; col++) {
dst [col] = read_byte (xml);
xml += 2;
}
dst += row_stride;
}
return pixbuf;
}
static gchar *
find_pixmap_in_path (const gchar *filename)
{
gchar *file;
if (g_path_is_absolute (filename))
return g_strdup (filename);
file = gnome_program_locate_file (gnome_program_get (),
GNOME_FILE_DOMAIN_PIXMAP,
filename, TRUE, NULL);
return file;
}
/* FIXME: could cut down allocation here a bit */
static char *
lookup_stock_compat (const char *id)
{
char *lower, *new_id;
static GHashTable *compat_hash = NULL;
if (!compat_hash) {
static const char *mapping[][2] = {
{ "Up", "gtk-go-up" },
{ "Down", "gtk-go-down" },
{ "Back", "gtk-go-back" },
{ "Forward", "gtk-go-forward" },
{ "Save As", "gtk-save-as" },
{ "Trash", "gtk-delete" },
{ "Revert", "gtk-revert-to-saved" },
{ "Exit", "gtk-quit" },
{ "Search", "gtk-find" },
{ "Search/Replace", "gtk-find-and-replace" },
{ "Timer Stopped", "gnome-stock-timer-stop" },
{ "Scores", "gnome-stock-scores" },
{ "About", "gnome-stock-about" },
{ NULL, NULL }
};
int i;
compat_hash = g_hash_table_new (g_str_hash, g_str_equal);
for (i = 0; mapping [i][0]; i++)
g_hash_table_insert (compat_hash,
(gpointer) mapping [i] [0],
(gpointer) mapping [i] [1]);
}
if ((new_id = g_hash_table_lookup (compat_hash, id)))
return g_strdup (new_id);
lower = g_ascii_strdown (id, -1);
new_id = g_strconcat ("gtk-", lower, NULL);
if (gtk_icon_factory_lookup_default (new_id)) {
g_free (lower);
return new_id;
}
g_free (new_id);
new_id = g_strconcat ("gnome-stock-", lower, NULL);
if (gtk_icon_factory_lookup_default (new_id)) {
g_free (lower);
return new_id;
}
g_free (lower);
g_free (new_id);
/* FIXME: does this catch them all ? */
return NULL;
}
void
bonobo_ui_image_set_pixbuf (GtkImage *image, GdkPixbuf *pixbuf)
{
if (gtk_image_get_pixbuf (image) != pixbuf)
gtk_image_set_from_pixbuf (image, pixbuf);
else if (pixbuf)
g_object_unref (pixbuf);
}
static void
bonobo_ui_image_set_stock (GtkImage *image,
const char *name,
GtkIconSize icon_size)
{
g_return_if_fail (name != NULL);
if (image->storage_type != GTK_IMAGE_STOCK ||
image->icon_size != icon_size ||
!image->data.stock.stock_id ||
strcmp (image->data.stock.stock_id, name))
gtk_image_set_from_stock (image, name, icon_size);
}
static GtkIconSize
bonobo_ui_util_xml_get_icon_size (BonoboUINode *node,
GtkIconSize default_size)
{
GtkIconSize retval;
const char *size_name;
retval = default_size;
if ((size_name = bonobo_ui_node_peek_attr (node, "icon_size")))
retval = gtk_icon_size_from_name (size_name);
return retval;
}
void
bonobo_ui_util_xml_set_image (GtkImage *image,
BonoboUINode *node,
BonoboUINode *cmd_node,
GtkIconSize icon_size)
{
char *key;
const char *type, *text;
GdkPixbuf *pixbuf = NULL;
static GHashTable *pixbuf_cache = NULL;
g_return_if_fail (node != NULL);
if ((type = bonobo_ui_node_peek_attr (node, "pixtype"))) {
text = bonobo_ui_node_peek_attr (node, "pixname");
icon_size = bonobo_ui_util_xml_get_icon_size (node, icon_size);
} else if (cmd_node && (type = bonobo_ui_node_peek_attr (cmd_node, "pixtype"))) {
text = bonobo_ui_node_peek_attr (cmd_node, "pixname");
icon_size = bonobo_ui_util_xml_get_icon_size (cmd_node, icon_size);
} else
return;
if (!text) {
if (g_getenv ("BONOBO_DEBUG"))
g_warning ("Missing pixname on '%s'",
bonobo_ui_xml_make_path (node));
return;
}
if (!strcmp (type, "stock")) {
if (gtk_icon_factory_lookup_default (text))
bonobo_ui_image_set_stock (image, text, icon_size);
else {
char *mapped;
if ((mapped = lookup_stock_compat (text))) {
bonobo_ui_image_set_stock (image, mapped, icon_size);
g_free (mapped);
}
}
return;
}
key = g_strdup_printf ("%s:%u", text, icon_size);
if (!pixbuf_cache)
pixbuf_cache = g_hash_table_new_full (
g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_object_unref);
else if ((pixbuf = g_hash_table_lookup (pixbuf_cache, key))) {
g_free (key);
g_object_ref (pixbuf);
bonobo_ui_image_set_pixbuf (image, pixbuf);
return;
}
if (!strcmp (type, "filename")) {
char *name = find_pixmap_in_path (text);
if ((name == NULL) || !g_file_test (name, G_FILE_TEST_EXISTS))
g_warning ("Could not find GNOME pixmap file %s", text);
else {
int w, h;
GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (image));
if (gtk_icon_size_lookup_for_settings (settings, icon_size, &w, &h))
pixbuf = gdk_pixbuf_new_from_file_at_size (name, w, h, NULL);
else
pixbuf = gdk_pixbuf_new_from_file (name, NULL);
}
g_free (name);
} else if (!strcmp (type, "pixbuf"))
pixbuf = bonobo_ui_util_xml_to_pixbuf (text);
else
g_warning ("Unknown icon_pixbuf type '%s'", type);
if (pixbuf) {
g_object_ref (pixbuf);
g_hash_table_insert (pixbuf_cache, key, pixbuf);
} else
g_free (key);
bonobo_ui_image_set_pixbuf (image, pixbuf);
}
/**
* bonobo_ui_util_xml_get_icon_widget:
* @node: the node
* @icon_size: the desired size of the icon
*
* This function extracts a pixbuf from the node and returns a GtkWidget
* containing a display of the pixbuf.
*
* Unused internally.
*
* Return value: the widget.
**/
GtkWidget *
bonobo_ui_util_xml_get_icon_widget (BonoboUINode *node, GtkIconSize icon_size)
{
const char *type, *text;
GtkWidget *image = NULL;
g_return_val_if_fail (node != NULL, NULL);
if (!(type = bonobo_ui_node_peek_attr (node, "pixtype")))
return NULL;
if (!(text = bonobo_ui_node_peek_attr (node, "pixname")))
return NULL;
if (!text)
return NULL;
if (!strcmp (type, "stock")) {
if (gtk_icon_factory_lookup_default (text))
image = gtk_image_new_from_stock (text, icon_size);
else {
char *mapped;
if ((mapped = lookup_stock_compat (text))) {
image = gtk_image_new_from_stock (mapped, icon_size);
g_free (mapped);
} else
g_warning ("Unknown stock icon '%s', stock names all changed in Gtk+ 2.0", text);
}
} else if (!strcmp (type, "filename")) {
char *name = find_pixmap_in_path (text);
if ((name == NULL) || !g_file_test (name, G_FILE_TEST_EXISTS))
g_warning ("Could not find GNOME pixmap file %s", text);
else
image = gtk_image_new_from_file (name);
g_free (name);
} else if (!strcmp (type, "pixbuf")) {
GdkPixbuf *icon_pixbuf;
/* Get pointer to GdkPixbuf */
icon_pixbuf = bonobo_ui_util_xml_to_pixbuf (text);
if (icon_pixbuf) {
image = gtk_image_new_from_pixbuf (icon_pixbuf);
g_object_unref (icon_pixbuf);
}
} else
g_warning ("Unknown icon_pixbuf type '%s'", type);
if (image)
gtk_widget_show (image);
return image;
}
/**
* bonobo_ui_util_xml_set_pixbuf:
* @node: the node
* @pixbuf: the pixbuf
*
* Associate @pixbuf with this @node by stringifying it and setting
* the requisite attributes.
**/
void
bonobo_ui_util_xml_set_pixbuf (BonoboUINode *node,
GdkPixbuf *pixbuf)
{
char *data;
g_return_if_fail (node != NULL);
g_return_if_fail (pixbuf != NULL);
bonobo_ui_node_set_attr (node, "pixtype", "pixbuf");
data = bonobo_ui_util_pixbuf_to_xml (pixbuf);
bonobo_ui_node_set_attr (node, "pixname", data);
g_free (data);
}
typedef struct {
char *app_prefix;
char *app_name;
GnomeProgram *program;
} HelpDisplayClosure;
static void
help_display_closure_free (gpointer user_data,
GClosure *closure)
{
HelpDisplayClosure *cl = user_data;
g_free (cl->app_prefix);
g_free (cl->app_name);
if (cl->program)
g_object_unref (cl->program);
g_free (cl);
}
static void
bonobo_help_display_cb (BonoboUIComponent *component,
gpointer user_data,
const char *cname)
{
GError *error = NULL;
const char *doc_id;
HelpDisplayClosure *cl = user_data;
if (cl->app_name)
doc_id = cl->app_name;
else
doc_id = gnome_program_get_app_id (gnome_program_get ());
if (!cl->program) {
int argc = 1;
char *argv[2];
char *prefix;
char *datadir;
argv [0] = (char *) (doc_id ? doc_id : "unknown-lib");
argv [1] = NULL;
if (cl->app_prefix)
prefix = g_strdup (cl->app_prefix);
else
prefix = NULL;
if (prefix)
datadir = g_strdup_printf ("%s/share", prefix);
else {
datadir = NULL;
g_object_get (G_OBJECT (gnome_program_get ()),
GNOME_PARAM_APP_DATADIR, &datadir, NULL);
}
if (!datadir) /* desparate fallback */
datadir = g_strdup (BONOBO_DATADIR);
cl->program = gnome_program_init (
doc_id, "2.1",
LIBGNOME_MODULE,
argc, argv,
GNOME_PARAM_APP_PREFIX, prefix,
GNOME_PARAM_APP_DATADIR, datadir,
NULL);
g_free (datadir);
g_free (prefix);
}
gnome_help_display_with_doc_id (
cl->program, doc_id, doc_id, NULL, &error);
if (error) {
/* FIXME: better error handling ? */
g_warning ("Error: '%s'", error->message);
g_error_free (error);
}
}
/**
* bonobo_ui_util_build_help_menu:
* @listener: associated component
* @app_prefix: application prefix
* @app_name: application name
* @parent: toplevel node
*
* This routine inserts all the help menu items appropriate for this
* application as children of the @parent node.
**/
void
bonobo_ui_util_build_help_menu (BonoboUIComponent *listener,
const char *app_prefix,
const char *app_name,
BonoboUINode *parent)
{
static int unique = 0;
char *id;
BonoboUINode *node;
HelpDisplayClosure *cl;
node = bonobo_ui_node_new ("menuitem");
id = g_strdup_printf ("Help%s%d",
app_name ? app_name : "main",
unique++);
bonobo_ui_node_set_attr (node, "name", id);
bonobo_ui_node_set_attr (node, "verb", "");
bonobo_ui_node_set_attr (node, "label", _("_Contents"));
bonobo_ui_node_set_attr (node, "tip", _("View help for this application"));
bonobo_ui_node_set_attr (node, "pixtype", "stock");
bonobo_ui_node_set_attr (node, "pixname", "gtk-help");
bonobo_ui_node_set_attr (node, "accel", "F1");
cl = g_new0 (HelpDisplayClosure, 1);
cl->app_name = g_strdup (app_name);
cl->app_prefix = g_strdup (app_prefix);
bonobo_ui_component_add_verb_full (
listener, id,
g_cclosure_new (
G_CALLBACK (bonobo_help_display_cb),
cl, help_display_closure_free));
bonobo_ui_node_add_child (parent, node);
g_free (id);
}
/**
* bonobo_ui_util_get_ui_fname:
* @component_datadir: the datadir for the component, e.g. /usr/share
* @file_name: the file name of the xml file.
*
* Builds a path to the xml file that stores the GUI.
*
* Return value: the path to the file that describes the
* UI or NULL if it is not found.
**/
char *
bonobo_ui_util_get_ui_fname (const char *component_datadir,
const char *file_name)
{
char *fname, *name;
if ((g_path_is_absolute (file_name) || file_name [0] == '.') &&
g_file_test (file_name, G_FILE_TEST_EXISTS))
return g_strdup (file_name);
if (component_datadir) {
fname = g_build_filename (component_datadir,
"gnome-2.0",
"ui",
file_name,
NULL);
if (g_file_test (fname, G_FILE_TEST_EXISTS))
return fname;
g_free (fname);
}
name = g_build_filename (BONOBO_UIDIR, file_name, NULL);
if (g_file_test (name, G_FILE_TEST_EXISTS))
return name;
g_free (name);
if (component_datadir) {
name = g_build_filename (component_datadir, file_name, NULL);
if (g_file_test (name, G_FILE_TEST_EXISTS))
return name;
g_free (name);
}
return NULL;
}
/**
* bonobo_ui_util_translate_ui:
* @node: the node to start at.
*
* Quest through a tree looking for translatable properties
* ( those prefixed with an '_' ). Translates the value of the
* property and removes the leading '_'.
**/
void
bonobo_ui_util_translate_ui (BonoboUINode *node)
{
BonoboUINode *l;
int i;
if (!node)
return;
for (i = 0; i < node->attrs->len; i++) {
BonoboUIAttr *a;
const char *str;
a = &g_array_index (node->attrs, BonoboUIAttr, i);
if (!a->id)
continue;
str = g_quark_to_string (a->id);
if (str [0] == '_') {
char *old;
a->id = g_quark_from_static_string (str + 1);
old = a->value;
#ifdef ENABLE_NLS
a->value = xmlStrdup (gettext(a->value));
#else
a->value = xmlStrdup (a->value);
#endif
xmlFree (old);
}
}
for (l = node->children; l; l = l->next)
bonobo_ui_util_translate_ui (l);
}
/**
* bonobo_ui_util_fixup_help:
* @component: the UI component
* @node: the node to search under
* @app_prefix: the application prefix
* @app_name: the application name
*
* This searches for 'BuiltMenuItems' placeholders, and then
* fills them with the application's menu items.
**/
void
bonobo_ui_util_fixup_help (BonoboUIComponent *component,
BonoboUINode *node,
const char *app_prefix,
const char *app_name)
{
BonoboUINode *l;
gboolean build_here = FALSE;
if (!node)
return;
if (bonobo_ui_node_has_name (node, "placeholder")) {
const char *txt;
if ((txt = bonobo_ui_node_peek_attr (node, "name")))
build_here = !strcmp (txt, "BuiltMenuItems");
}
if (build_here) {
bonobo_ui_util_build_help_menu (
component, app_prefix, app_n