medit/moo/mooutils/moouixml.c

2564 lines
65 KiB
C

/*
* mooui/moouixml.c
*
* Copyright (C) 2004-2006 by Yevgen Muntyan <muntyan@math.tamu.edu>
*
* 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.
*
* See COPYING file that comes with this distribution.
*/
#include "mooutils/moouixml.h"
#include "mooutils/moocompat.h"
#include "mooutils/moomarshals.h"
#include "mooutils/moomenutoolbutton.h"
#include <gtk/gtk.h>
#include <string.h>
#define REPORT_UNKNOWN_ACTIONS 0
/* XXX weak ref actions */
typedef MooUINodeType NodeType;
typedef MooUINode Node;
typedef MooUIWidgetNode Widget;
typedef MooUIItemNode Item;
typedef MooUISeparatorNode Separator;
typedef MooUIPlaceholderNode Placeholder;
#define CONTAINER MOO_UI_NODE_CONTAINER
#define WIDGET MOO_UI_NODE_WIDGET
#define TOOLBAR MOO_UI_NODE_TOOLBAR
#define ITEM MOO_UI_NODE_ITEM
#define SEPARATOR MOO_UI_NODE_SEPARATOR
#define PLACEHOLDER MOO_UI_NODE_PLACEHOLDER
static const char *NODE_TYPE_NAME[] = {
NULL,
"MOO_UI_NODE_CONTAINER",
"MOO_UI_NODE_WIDGET",
"MOO_UI_NODE_ITEM",
"MOO_UI_NODE_SEPARATOR",
"MOO_UI_NODE_PLACEHOLDER"
};
struct _MooUIXMLPrivate {
Node *ui;
GSList *toplevels; /* Toplevel* */
guint last_merge_id;
GSList *merged_ui; /* Merge* */
GtkTooltips *tooltips;
};
typedef struct {
Node *node;
GtkWidget *widget;
GHashTable *children; /* Node* -> GtkWidget* */
MooActionGroup *actions;
GtkAccelGroup *accel_group;
gboolean in_creation;
} Toplevel;
typedef struct {
guint id;
GSList *nodes;
} Merge;
typedef enum {
UPDATE_ADD_NODE,
UPDATE_REMOVE_NODE,
UPDATE_CHANGE_NODE
} UpdateType;
#define TOPLEVEL_QUARK (toplevel_quark ())
#define NODE_QUARK (node_quark ())
#define SLIST_FOREACH(list,var) \
G_STMT_START { \
GSList *var; \
for (var = list; var != NULL; var = var->next) \
#define SLIST_FOREACH_END \
} G_STMT_END
/* walking nodes stops when func returns TRUE */
typedef gboolean (*NodeForeachFunc) (Node *node,
gpointer data);
static void moo_ui_xml_finalize (GObject *object);
static void xml_add_markup (MooUIXML *xml,
MooMarkupNode *mnode);
static void update_widgets (MooUIXML *xml,
UpdateType type,
Node *node);
static Node *parse_markup (MooMarkupNode *mnode);
static Node *parse_object (MooMarkupNode *mnode);
static Node *parse_widget (MooMarkupNode *mnode);
static Node *parse_placeholder (MooMarkupNode *mnode);
static Node *parse_item (MooMarkupNode *mnode);
static Node *parse_separator (MooMarkupNode *mnode);
static gboolean node_check (Node *node);
static gboolean placeholder_check (Node *node);
static gboolean item_check (Node *node);
static gboolean widget_check (Node *node);
static gboolean container_check (Node *node);
static Merge *lookup_merge (MooUIXML *xml,
guint id);
static Node *node_new (NodeType type,
const char *name);
static Item *item_new (const char *name,
const char *action,
const char *stock_id,
const char *label,
const char *tooltip,
const char *icon_stock_id);
static gboolean node_is_ancestor (Node *node,
Node *ancestor);
static gboolean node_is_empty (Node *node);
static GSList *node_list_children (Node *ndoe);
static GSList *node_list_all_children (Node *ndoe);
static void node_free (Node *node);
static void node_foreach (Node *node,
NodeForeachFunc func,
gpointer data);
static void merge_add_node (Merge *merge,
Node *node);
static void merge_remove_node (Merge *merge,
Node *node);
static Toplevel *toplevel_new (Node *node,
MooActionGroup *actions,
GtkAccelGroup *accel_group);
static void toplevel_free (Toplevel *toplevel);
static GtkWidget *toplevel_get_widget (Toplevel *toplevel,
Node *node);
static GQuark toplevel_quark (void);
static GQuark node_quark (void);
static void xml_add_item_widget (MooUIXML *xml,
GtkWidget *widget);
static void xml_add_widget (MooUIXML *xml,
GtkWidget *widget,
Toplevel *toplevel,
Node *node);
static void xml_remove_widget (MooUIXML *xml,
GtkWidget *widget);
static void xml_delete_toplevel (MooUIXML *xml,
Toplevel *toplevel);
static void xml_connect_toplevel (MooUIXML *xml,
Toplevel *toplevel);
static gboolean create_menu_separator (MooUIXML *xml,
Toplevel *toplevel,
GtkMenuShell *menu,
Node *node,
int index);
static void create_menu_item (MooUIXML *xml,
Toplevel *toplevel,
GtkMenuShell *menu,
Node *node,
int index);
static gboolean create_menu_shell (MooUIXML *xml,
Toplevel *toplevel,
MooUIWidgetType type);
static gboolean fill_menu_shell (MooUIXML *xml,
Toplevel *toplevel,
Node *menu_node,
GtkMenuShell *menu);
static void check_separators (Node *parent,
Toplevel *toplevel);
static void check_empty (Node *parent,
GtkWidget *widget,
Toplevel *toplevel);
static gboolean create_tool_separator (MooUIXML *xml,
Toplevel *toplevel,
GtkToolbar *toolbar,
Node *node,
int index);
static gboolean create_tool_item (MooUIXML *xml,
Toplevel *toplevel,
GtkToolbar *toolbar,
Node *node,
int index);
static gboolean fill_toolbar (MooUIXML *xml,
Toplevel *toplevel,
Node *toolbar_node,
GtkToolbar *toolbar);
static gboolean create_toolbar (MooUIXML *xml,
Toplevel *toplevel);
/* MOO_TYPE_UI_XML */
G_DEFINE_TYPE (MooUIXML, moo_ui_xml, G_TYPE_OBJECT)
static void
moo_ui_xml_class_init (MooUIXMLClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = moo_ui_xml_finalize;
}
static Node*
node_new (NodeType type, const char *name)
{
Node *node;
gsize size = 0;
switch (type)
{
case MOO_UI_NODE_CONTAINER:
size = sizeof (MooUINode);
break;
case MOO_UI_NODE_WIDGET:
size = sizeof (MooUIWidgetNode);
break;
case MOO_UI_NODE_ITEM:
size = sizeof (MooUIItemNode);
break;
case MOO_UI_NODE_SEPARATOR:
size = sizeof (MooUISeparatorNode);
break;
case MOO_UI_NODE_PLACEHOLDER:
size = sizeof (MooUIPlaceholderNode);
break;
}
node = g_malloc0 (size);
node->type = type;
node->name = name ? g_strdup (name) : g_strdup ("");
return node;
}
static void
moo_ui_xml_init (MooUIXML *xml)
{
xml->priv = g_new0 (MooUIXMLPrivate, 1);
xml->priv->ui = node_new (CONTAINER, "ui");
}
MooUIXML*
moo_ui_xml_new (void)
{
return g_object_new (MOO_TYPE_UI_XML, NULL);
}
static GtkTooltips *
moo_ui_xml_get_tooltips (MooUIXML *xml)
{
g_return_val_if_fail (MOO_IS_UI_XML (xml), NULL);
if (!xml->priv->tooltips)
{
xml->priv->tooltips = gtk_tooltips_new ();
gtk_object_sink (g_object_ref (xml->priv->tooltips));
}
return xml->priv->tooltips;
}
static Node*
parse_object (MooMarkupNode *mnode)
{
Node *node;
const char *name;
MooMarkupNode *mchild;
name = moo_markup_get_prop (mnode, "name");
if (!name || !name[0])
{
g_warning ("%s: object name missing", G_STRLOC);
return NULL;
}
node = node_new (CONTAINER, name);
for (mchild = mnode->children; mchild != NULL; mchild = mchild->next)
{
if (MOO_MARKUP_IS_ELEMENT (mchild))
{
Node *child = parse_markup (mchild);
if (!child)
{
node_free (node);
return NULL;
}
else
{
child->parent = node;
node->children = g_slist_append (node->children, child);
}
}
}
return node;
}
static Node*
parse_widget (MooMarkupNode *mnode)
{
Node *node;
const char *name;
MooMarkupNode *mchild;
name = moo_markup_get_prop (mnode, "name");
if (!name || !name[0])
{
g_warning ("%s: widget name missing", G_STRLOC);
return NULL;
}
node = node_new (WIDGET, name);
for (mchild = mnode->children; mchild != NULL; mchild = mchild->next)
{
if (MOO_MARKUP_IS_ELEMENT (mchild))
{
Node *child = parse_markup (mchild);
if (!child)
{
node_free (node);
return NULL;
}
else
{
child->parent = node;
node->children = g_slist_append (node->children, child);
}
}
}
return node;
}
static Node*
parse_placeholder (MooMarkupNode *mnode)
{
Node *node;
const char *name;
MooMarkupNode *mchild;
name = moo_markup_get_prop (mnode, "name");
if (!name || !name[0])
{
g_warning ("%s: placeholder name missing", G_STRLOC);
return NULL;
}
node = node_new (PLACEHOLDER, name);
for (mchild = mnode->children; mchild != NULL; mchild = mchild->next)
{
if (MOO_MARKUP_IS_ELEMENT (mchild))
{
Node *child = parse_markup (mchild);
if (!child)
{
node_free (node);
return NULL;
}
else
{
child->parent = node;
node->children = g_slist_append (node->children, child);
}
}
}
return node;
}
static Item*
item_new (const char *name,
const char *action,
const char *stock_id,
const char *label,
const char *tooltip,
const char *icon_stock_id)
{
Item *item;
item = (Item*) node_new (ITEM, name);
item->action = g_strdup (action);
item->stock_id = g_strdup (stock_id);
item->label = g_strdup (label);
item->tooltip = g_strdup (tooltip);
item->icon_stock_id = g_strdup (icon_stock_id);
return item;
}
static Node*
parse_item (MooMarkupNode *mnode)
{
Item *item;
const char *name;
MooMarkupNode *mchild;
name = moo_markup_get_prop (mnode, "name");
if (!name)
name = moo_markup_get_prop (mnode, "action");
item = item_new (name,
moo_markup_get_prop (mnode, "action"),
moo_markup_get_prop (mnode, "stock-id"),
moo_markup_get_prop (mnode, "label"),
moo_markup_get_prop (mnode, "tooltip"),
moo_markup_get_prop (mnode, "icon-stock-id"));
for (mchild = mnode->children; mchild != NULL; mchild = mchild->next)
{
if (MOO_MARKUP_IS_ELEMENT (mchild))
{
Node *child = parse_markup (mchild);
if (!child)
{
node_free ((Node*) item);
return NULL;
}
else
{
child->parent = (Node*) item;
item->children = g_slist_append (item->children, child);
}
}
}
return (Node*) item;
}
static Node*
parse_separator (G_GNUC_UNUSED MooMarkupNode *mnode)
{
return node_new (SEPARATOR, NULL);
}
static Node*
parse_markup (MooMarkupNode *mnode)
{
g_return_val_if_fail (MOO_MARKUP_IS_ELEMENT (mnode), NULL);
g_return_val_if_fail (mnode->name != NULL, NULL);
if (!strcmp (mnode->name, "object"))
return parse_object (mnode);
else if (!strcmp (mnode->name, "widget"))
return parse_widget (mnode);
else if (!strcmp (mnode->name, "item"))
return parse_item (mnode);
else if (!strcmp (mnode->name, "separator"))
return parse_separator (mnode);
else if (!strcmp (mnode->name, "placeholder"))
return parse_placeholder (mnode);
g_warning ("%s: unknown element '%s'", G_STRLOC, mnode->name);
return NULL;
}
static void
container_free (G_GNUC_UNUSED Node *node)
{
}
static void
widget_free (G_GNUC_UNUSED Node *node)
{
}
static void
separator_free (G_GNUC_UNUSED Node *node)
{
}
static void
placeholder_free (G_GNUC_UNUSED Node *node)
{
}
static void
item_free (Item *item)
{
g_free (item->action);
g_free (item->stock_id);
g_free (item->label);
g_free (item->tooltip);
g_free (item->icon_stock_id);
}
static void
node_free (Node *node)
{
if (node)
{
g_slist_foreach (node->children, (GFunc) node_free, NULL);
g_slist_free (node->children);
node->children = NULL;
switch (node->type)
{
case CONTAINER:
container_free (node);
break;
case WIDGET:
widget_free (node);
break;
case ITEM:
item_free ((Item*) node);
break;
case SEPARATOR:
separator_free (node);
break;
case PLACEHOLDER:
placeholder_free (node);
break;
}
g_free (node->name);
g_free (node);
}
}
void
moo_ui_xml_add_ui_from_string (MooUIXML *xml,
const char *buffer,
gssize length)
{
MooMarkupDoc *doc;
MooMarkupNode *ui_node, *child;
GError *error = NULL;
g_return_if_fail (MOO_IS_UI_XML (xml));
g_return_if_fail (buffer != NULL);
doc = moo_markup_parse_memory (buffer, length, &error);
if (!doc)
{
g_critical ("%s: could not parse markup", G_STRLOC);
if (error)
{
g_critical ("%s: %s", G_STRLOC, error->message);
g_error_free (error);
}
return;
}
ui_node = moo_markup_get_root_element (doc, "ui");
if (!ui_node)
ui_node = MOO_MARKUP_NODE (doc);
for (child = ui_node->children; child != NULL; child = child->next)
if (MOO_MARKUP_IS_ELEMENT (child))
xml_add_markup (xml, child);
moo_markup_doc_unref (doc);
}
static gboolean
node_check (Node *node)
{
g_return_val_if_fail (node != NULL, FALSE);
switch (node->type)
{
case CONTAINER:
return container_check (node);
case WIDGET:
return widget_check (node);
case ITEM:
return item_check (node);
case SEPARATOR:
return TRUE;
case PLACEHOLDER:
return placeholder_check (node);
}
g_return_val_if_reached (FALSE);
}
static gboolean
placeholder_check (Node *node)
{
g_return_val_if_fail (node != NULL, FALSE);
g_return_val_if_fail (node->type == PLACEHOLDER, FALSE);
g_return_val_if_fail (node->name && node->name[0], FALSE);
SLIST_FOREACH (node->children, l)
{
Node *child = l->data;
switch (child->type)
{
case SEPARATOR:
case ITEM:
case PLACEHOLDER:
if (!node_check (child))
return FALSE;
break;
default:
g_warning ("%s: invalid placeholder child type %s",
G_STRLOC, NODE_TYPE_NAME[child->type]);
return FALSE;
}
}
SLIST_FOREACH_END;
return TRUE;
}
static gboolean
item_check (Node *node)
{
g_return_val_if_fail (node != NULL, FALSE);
g_return_val_if_fail (node->type == ITEM, FALSE);
g_return_val_if_fail (node->name != NULL, FALSE);
SLIST_FOREACH (node->children, l)
{
Node *child = l->data;
switch (child->type)
{
case SEPARATOR:
case ITEM:
case PLACEHOLDER:
if (!node_check (child))
return FALSE;
break;
default:
g_warning ("%s: invalid item child type %s",
G_STRLOC, NODE_TYPE_NAME[child->type]);
return FALSE;
}
}
SLIST_FOREACH_END;
return TRUE;
}
static gboolean
widget_check (Node *node)
{
g_return_val_if_fail (node != NULL, FALSE);
g_return_val_if_fail (node->type == WIDGET, FALSE);
g_return_val_if_fail (node->name && node->name[0], FALSE);
SLIST_FOREACH (node->children, l)
{
Node *child = l->data;
switch (child->type)
{
case SEPARATOR:
case ITEM:
case PLACEHOLDER:
if (!node_check (child))
return FALSE;
break;
default:
g_warning ("%s: invalid widget child type %s",
G_STRLOC, NODE_TYPE_NAME[child->type]);
return FALSE;
}
}
SLIST_FOREACH_END;
return TRUE;
}
static gboolean
container_check (Node *node)
{
g_return_val_if_fail (node != NULL, FALSE);
g_return_val_if_fail (node->type == CONTAINER, FALSE);
g_return_val_if_fail (node->name && node->name[0], FALSE);
SLIST_FOREACH (node->children, l)
{
Node *child = l->data;
switch (child->type)
{
case CONTAINER:
case WIDGET:
if (!node_check (child))
return FALSE;
break;
default:
g_warning ("%s: invalid widget child type %s",
G_STRLOC, NODE_TYPE_NAME[child->type]);
return FALSE;
}
}
SLIST_FOREACH_END;
return TRUE;
}
static void
xml_add_markup (MooUIXML *xml,
MooMarkupNode *mnode)
{
Node *node = parse_markup (mnode);
if (!node)
return;
switch (node->type)
{
case CONTAINER:
if (!container_check (node))
{
node_free (node);
return;
}
break;
case WIDGET:
if (!widget_check (node))
{
node_free (node);
return;
}
break;
case ITEM:
case SEPARATOR:
case PLACEHOLDER:
g_warning ("%s: invalid toplevel type %s",
G_STRLOC, NODE_TYPE_NAME[node->type]);
node_free (node);
return;
}
if (moo_ui_xml_get_node (xml, node->name))
{
g_warning ("%s: implement me?", G_STRLOC);
node_free (node);
return;
}
node->parent = xml->priv->ui;
xml->priv->ui->children = g_slist_append (xml->priv->ui->children, node);
}
guint
moo_ui_xml_new_merge_id (MooUIXML *xml)
{
Merge *merge;
g_return_val_if_fail (MOO_IS_UI_XML (xml), 0);
xml->priv->last_merge_id++;
merge = g_new0 (Merge, 1);
merge->id = xml->priv->last_merge_id;
merge->nodes = NULL;
xml->priv->merged_ui = g_slist_prepend (xml->priv->merged_ui, merge);
return merge->id;
}
static Merge*
lookup_merge (MooUIXML *xml,
guint merge_id)
{
GSList *l;
for (l = xml->priv->merged_ui; l != NULL; l = l->next)
{
Merge *merge = l->data;
if (merge->id == merge_id)
return merge;
}
return NULL;
}
MooUINode*
moo_ui_xml_add_item (MooUIXML *xml,
guint merge_id,
const char *parent_path,
const char *name,
const char *action,
int position)
{
Merge *merge;
MooUINode *parent;
Item *item;
g_return_val_if_fail (MOO_IS_UI_XML (xml), NULL);
g_return_val_if_fail (parent_path != NULL, NULL);
if (!name || !name[0])
name = action;
merge = lookup_merge (xml, merge_id);
g_return_val_if_fail (merge != NULL, NULL);
parent = moo_ui_xml_find_node (xml, parent_path);
g_return_val_if_fail (parent != NULL, NULL);
switch (parent->type)
{
case MOO_UI_NODE_WIDGET:
case MOO_UI_NODE_ITEM:
case MOO_UI_NODE_PLACEHOLDER:
break;
case MOO_UI_NODE_CONTAINER:
case MOO_UI_NODE_SEPARATOR:
g_warning ("%s: can't add item to node of type %s",
G_STRLOC, NODE_TYPE_NAME[parent->type]);
}
item = item_new (name, action, NULL, NULL, NULL, NULL);
item->parent = parent;
parent->children = g_slist_insert (parent->children, item, position);
merge_add_node (merge, (Node*) item);
update_widgets (xml, UPDATE_ADD_NODE, (Node*) item);
return (Node*) item;
}
void
moo_ui_xml_insert (MooUIXML *xml,
guint merge_id,
MooUINode *parent,
int position,
const char *markup)
{
Merge *merge;
GError *error = NULL;
MooMarkupDoc *doc;
MooMarkupNode *mchild;
int children_length;
g_return_if_fail (MOO_IS_UI_XML (xml));
g_return_if_fail (markup != NULL);
g_return_if_fail (!parent || node_is_ancestor (parent, xml->priv->ui));
merge = lookup_merge (xml, merge_id);
g_return_if_fail (merge != NULL);
if (!parent)
parent = xml->priv->ui;
if (parent->type == MOO_UI_NODE_SEPARATOR)
{
g_warning ("%s: can't add stuff to node of type %s",
G_STRLOC, NODE_TYPE_NAME[parent->type]);
return;
}
doc = moo_markup_parse_memory (markup, -1, &error);
if (!doc)
{
g_warning ("%s: could not parse markup", G_STRLOC);
if (error)
{
g_warning ("%s: %s", G_STRLOC, error->message);
g_error_free (error);
}
return;
}
children_length = g_slist_length (parent->children);
if (position < 0 || position > children_length)
position = children_length;
for (mchild = doc->last; mchild != NULL; mchild = mchild->prev)
{
Node *node;
if (!MOO_MARKUP_IS_ELEMENT (mchild))
continue;
node = parse_markup (mchild);
if (!node)
continue;
if (!node_check (node))
{
node_free (node);
continue;
}
switch (node->type)
{
case WIDGET:
case CONTAINER:
if (parent->type != CONTAINER)
{
g_warning ("%s: can not add node of type %s to node of type %s",
G_STRLOC, NODE_TYPE_NAME[node->type],
NODE_TYPE_NAME[parent->type]);
node_free (node);
continue;
}
break;
case ITEM:
case PLACEHOLDER:
case SEPARATOR:
if (parent->type == SEPARATOR || parent->type == CONTAINER)
{
g_warning ("%s: can not add node of type %s to node of type %s",
G_STRLOC, NODE_TYPE_NAME[node->type],
NODE_TYPE_NAME[parent->type]);
node_free (node);
continue;
}
break;
}
/* XXX check names? */
node->parent = parent;
parent->children = g_slist_insert (parent->children, node, position);
merge_add_node (merge, node);
update_widgets (xml, UPDATE_ADD_NODE, node);
}
moo_markup_doc_unref (doc);
}
void
moo_ui_xml_insert_after (MooUIXML *xml,
guint merge_id,
MooUINode *parent,
MooUINode *after,
const char *markup)
{
int position;
g_return_if_fail (MOO_IS_UI_XML (xml));
if (!parent)
parent = xml->priv->ui;
g_return_if_fail (!after || after->parent == parent);
if (!after)
position = 0;
else
position = g_slist_index (parent->children, after) + 1;
moo_ui_xml_insert (xml, merge_id, parent, position, markup);
}
void
moo_ui_xml_insert_before (MooUIXML *xml,
guint merge_id,
MooUINode *parent,
MooUINode *before,
const char *markup)
{
int position;
g_return_if_fail (MOO_IS_UI_XML (xml));
if (!parent)
parent = xml->priv->ui;
g_return_if_fail (!before || before->parent == parent);
if (!before)
position = g_slist_length (parent->children);
else
position = g_slist_index (parent->children, before);
moo_ui_xml_insert (xml, merge_id, parent, position, markup);
}
void
moo_ui_xml_insert_markup_after (MooUIXML *xml,
guint merge_id,
const char *parent_path,
const char *after_name,
const char *markup)
{
Node *parent = NULL, *after = NULL;
g_return_if_fail (MOO_IS_UI_XML (xml));
g_return_if_fail (markup != NULL);
if (parent_path)
{
parent = moo_ui_xml_find_node (xml, parent_path);
g_return_if_fail (parent != NULL);
}
else
{
parent = xml->priv->ui;
}
if (after_name)
{
after = moo_ui_node_get_child (parent, after_name);
g_return_if_fail (after != NULL);
}
moo_ui_xml_insert_after (xml, merge_id, parent, after, markup);
}
void
moo_ui_xml_insert_markup_before (MooUIXML *xml,
guint merge_id,
const char *parent_path,
const char *before_name,
const char *markup)
{
Node *parent = NULL, *before = NULL;
g_return_if_fail (MOO_IS_UI_XML (xml));
g_return_if_fail (markup != NULL);
if (parent_path)
{
parent = moo_ui_xml_find_node (xml, parent_path);
g_return_if_fail (parent != NULL);
}
else
{
parent = xml->priv->ui;
}
if (before_name)
{
before = moo_ui_node_get_child (parent, before_name);
g_return_if_fail (before != NULL);
}
moo_ui_xml_insert_before (xml, merge_id, parent, before, markup);
}
void
moo_ui_xml_insert_markup (MooUIXML *xml,
guint merge_id,
const char *parent_path,
int position,
const char *markup)
{
Node *parent = NULL;
g_return_if_fail (MOO_IS_UI_XML (xml));
g_return_if_fail (markup != NULL);
if (parent_path)
{
parent = moo_ui_xml_find_node (xml, parent_path);
g_return_if_fail (parent != NULL);
}
else
{
parent = xml->priv->ui;
}
moo_ui_xml_insert (xml, merge_id, parent, position, markup);
}
static gboolean
node_is_ancestor (Node *node,
Node *ancestor)
{
Node *n;
g_return_val_if_fail (node != NULL, FALSE);
g_return_val_if_fail (ancestor != NULL, FALSE);
for (n = node; n != NULL; n = n->parent)
if (n == ancestor)
return TRUE;
return FALSE;
}
void
moo_ui_xml_remove_ui (MooUIXML *xml,
guint merge_id)
{
Merge *merge;
GSList *nodes;
g_return_if_fail (MOO_IS_UI_XML (xml));
merge = lookup_merge (xml, merge_id);
g_return_if_fail (merge != NULL);
nodes = g_slist_copy (merge->nodes);
SLIST_FOREACH (nodes, l)
{
moo_ui_xml_remove_node (xml, l->data);
}
SLIST_FOREACH_END;
g_return_if_fail (merge->nodes == NULL);
xml->priv->merged_ui = g_slist_remove (xml->priv->merged_ui, merge);
g_free (merge);
}
void
moo_ui_xml_remove_node (MooUIXML *xml,
MooUINode *node)
{
Node *parent;
g_return_if_fail (MOO_IS_UI_XML (xml));
g_return_if_fail (node != NULL);
g_return_if_fail (node_is_ancestor (node, xml->priv->ui));
SLIST_FOREACH (xml->priv->merged_ui, l)
{
Merge *merge = l->data;
GSList *merge_nodes = g_slist_copy (merge->nodes);
SLIST_FOREACH (merge_nodes, n)
{
Node *merge_node = n->data;
if (node_is_ancestor (merge_node, node))
merge_remove_node (merge, merge_node);
}
SLIST_FOREACH_END;
g_slist_free (merge_nodes);
}
SLIST_FOREACH_END;
update_widgets (xml, UPDATE_REMOVE_NODE, node);
parent = node->parent;
parent->children = g_slist_remove (parent->children, node);
node->parent = NULL;
while (parent && parent->type == MOO_UI_NODE_PLACEHOLDER)
parent = parent->parent;
SLIST_FOREACH (xml->priv->toplevels, l)
{
Toplevel *toplevel = l->data;
if (node_is_ancestor (parent, toplevel->node))
check_separators (parent, toplevel);
}
SLIST_FOREACH_END;
node_free (node);
}
static void
merge_add_node (Merge *merge,
Node *added)
{
g_return_if_fail (merge != NULL);
g_return_if_fail (added != NULL);
SLIST_FOREACH (merge->nodes, l)
{
Node *node = l->data;
if (node_is_ancestor (added, node))
return;
}
SLIST_FOREACH_END;
merge->nodes = g_slist_prepend (merge->nodes, added);
}
static void
merge_remove_node (Merge *merge,
Node *removed)
{
g_return_if_fail (merge != NULL);
g_return_if_fail (removed != NULL);
merge->nodes = g_slist_remove (merge->nodes, removed);
}
MooUINode*
moo_ui_xml_get_node (MooUIXML *xml,
const char *path)
{
g_return_val_if_fail (MOO_IS_UI_XML (xml), NULL);
g_return_val_if_fail (path != NULL, NULL);
return moo_ui_node_get_child (xml->priv->ui, path);
}
MooUINode*
moo_ui_xml_find_node (MooUIXML *xml,
const char *path)
{
MooUINode *node;
g_return_val_if_fail (MOO_IS_UI_XML (xml), NULL);
g_return_val_if_fail (path != NULL, NULL);
node = moo_ui_xml_get_node (xml, path);
if (!node)
node = moo_ui_xml_find_placeholder (xml, path);
return node;
}
static gboolean
find_placeholder_func (Node *node,
gpointer user_data)
{
struct {
Node *found;
const char *name;
} *data = user_data;
if (node->type != PLACEHOLDER)
return FALSE;
if (!strcmp (node->name, data->name))
{
data->found = node;
return TRUE;
}
return FALSE;
}
MooUINode*
moo_ui_xml_find_placeholder (MooUIXML *xml,
const char *name)
{
struct {
Node *found;
const char *name;
} data;
g_return_val_if_fail (MOO_IS_UI_XML (xml), NULL);
g_return_val_if_fail (name != NULL, NULL);
data.found = NULL;
data.name = name;
node_foreach (xml->priv->ui, find_placeholder_func, &data);
return data.found;
}
char*
moo_ui_node_get_path (MooUINode *node)
{
GString *path;
g_return_val_if_fail (node != NULL, NULL);
path = g_string_new (node->name);
while (node->parent && node->parent->parent)
{
node = node->parent;
g_string_prepend_c (path, '/');
g_string_prepend (path, node->name);
}
g_print ("path: %s\n", path->str);
return g_string_free (path, FALSE);
}
MooUINode*
moo_ui_node_get_child (MooUINode *node,
const char *path)
{
char **pieces, **p;
g_return_val_if_fail (node != NULL, NULL);
g_return_val_if_fail (path != NULL, NULL);
pieces = g_strsplit (path, "/", 0);
if (!pieces)
return node;
for (p = pieces; *p != NULL; ++p)
{
Node *child = NULL;
if (!**p)
continue;
SLIST_FOREACH (node->children, l)
{
child = l->data;
if (!strcmp (child->name, *p))
break;
else
child = NULL;
}
SLIST_FOREACH_END;
if (child)
{
node = child;
}
else
{
node = NULL;
goto out;
}
}
out:
g_strfreev (pieces);
return node;
}
static Toplevel*
toplevel_new (Node *node,
MooActionGroup *actions,
GtkAccelGroup *accel_group)
{
Toplevel *top;
g_return_val_if_fail (node != NULL, NULL);
top = g_new0 (Toplevel, 1);
top->node = node;
top->widget = NULL;
top->children = g_hash_table_new (g_direct_hash, g_direct_equal);
top->actions = actions;
top->accel_group = accel_group;
return top;
}
static void
toplevel_free (Toplevel *toplevel)
{
if (toplevel)
{
g_hash_table_destroy (toplevel->children);
g_free (toplevel);
}
}
static GQuark toplevel_quark (void)
{
static GQuark q = 0;
if (!q)
q = g_quark_from_static_string ("moo-ui-xml-toplevel");
return q;
}
static GQuark node_quark (void)
{
static GQuark q = 0;
if (!q)
q = g_quark_from_static_string ("moo-ui-xml-node");
return q;
}
static Node *
get_effective_parent (Node *node)
{
Node *parent;
g_return_val_if_fail (node != NULL && node->parent != NULL, NULL);
for (parent = node->parent;
parent && parent->type == PLACEHOLDER;
parent = parent->parent) ;
return parent;
}
static void
visibility_notify (GtkWidget *widget,
G_GNUC_UNUSED gpointer whatever,
MooUIXML *xml)
{
Toplevel *toplevel;
Node *node;
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (MOO_IS_UI_XML (xml));
toplevel = g_object_get_qdata (G_OBJECT (widget), TOPLEVEL_QUARK);
g_return_if_fail (toplevel != NULL);
if (toplevel->in_creation)
return;
node = g_object_get_qdata (G_OBJECT (widget), NODE_QUARK);
g_return_if_fail (node != NULL && node->parent != NULL);
g_return_if_fail (node->type == ITEM);
check_separators (get_effective_parent (node), toplevel);
}
static void
xml_add_item_widget (MooUIXML *xml,
GtkWidget *widget)
{
g_signal_connect (widget, "notify::visible",
G_CALLBACK (visibility_notify), xml);
}
static void
widget_destroyed (GtkWidget *widget,
MooUIXML *xml)
{
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (MOO_IS_UI_XML (xml));
xml_remove_widget (xml, widget);
}
static void
xml_remove_widget (MooUIXML *xml,
GtkWidget *widget)
{
Toplevel *toplevel;
Node *node;
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (MOO_IS_UI_XML (xml));
toplevel = g_object_get_qdata (G_OBJECT (widget), TOPLEVEL_QUARK);
g_return_if_fail (toplevel != NULL);
g_return_if_fail (toplevel->widget != widget);
node = g_object_get_qdata (G_OBJECT (widget), NODE_QUARK);
g_hash_table_remove (toplevel->children, node);
g_object_set_qdata (G_OBJECT (widget), NODE_QUARK, NULL);
g_object_set_qdata (G_OBJECT (widget), TOPLEVEL_QUARK, NULL);
g_signal_handlers_disconnect_by_func (widget,
(gpointer) widget_destroyed,
xml);
g_signal_handlers_disconnect_by_func (widget,
(gpointer) visibility_notify,
xml);
}
static void
xml_add_widget (MooUIXML *xml,
GtkWidget *widget,
Toplevel *toplevel,
Node *node)
{
g_signal_connect (widget, "destroy",
G_CALLBACK (widget_destroyed), xml);
g_object_set_qdata (G_OBJECT (widget), TOPLEVEL_QUARK, toplevel);
g_object_set_qdata (G_OBJECT (widget), NODE_QUARK, node);
g_hash_table_insert (toplevel->children, node, widget);
}
static void
prepend_value (G_GNUC_UNUSED gpointer key,
gpointer value,
GSList **list)
{
*list = g_slist_prepend (*list, value);
}
static GSList*
hash_table_list_values (GHashTable *hash_table)
{
GSList *list = NULL;
g_hash_table_foreach (hash_table, (GHFunc) prepend_value, &list);
return list;
}
static void
toplevel_destroyed (GtkWidget *widget,
MooUIXML *xml)
{
Toplevel *toplevel;
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (MOO_IS_UI_XML (xml));
toplevel = g_object_get_qdata (G_OBJECT (widget), TOPLEVEL_QUARK);
g_return_if_fail (toplevel != NULL);
g_return_if_fail (toplevel->widget == widget);
xml_delete_toplevel (xml, toplevel);
}
static void
xml_delete_toplevel (MooUIXML *xml,
Toplevel *toplevel)
{
GSList *children, *l;
children = hash_table_list_values (toplevel->children);
for (l = children; l != NULL; l = l->next)
{
GtkWidget *child = GTK_WIDGET (l->data);
if (child != toplevel->widget)
xml_remove_widget (xml, child);
}
g_signal_handlers_disconnect_by_func (toplevel->widget,
(gpointer) toplevel_destroyed,
xml);
g_object_set_qdata (G_OBJECT (toplevel->widget), TOPLEVEL_QUARK, NULL);
g_object_set_qdata (G_OBJECT (toplevel->widget), NODE_QUARK, NULL);
xml->priv->toplevels = g_slist_remove (xml->priv->toplevels, toplevel);
g_slist_free (children);
toplevel_free (toplevel);
}
static void
xml_connect_toplevel (MooUIXML *xml,
Toplevel *toplevel)
{
g_signal_connect (toplevel->widget, "destroy",
G_CALLBACK (toplevel_destroyed), xml);
g_object_set_qdata (G_OBJECT (toplevel->widget), TOPLEVEL_QUARK, toplevel);
g_object_set_qdata (G_OBJECT (toplevel->widget), NODE_QUARK, toplevel->node);
g_hash_table_insert (toplevel->children, toplevel->node, toplevel->widget);
}
static gboolean
create_menu_separator (MooUIXML *xml,
Toplevel *toplevel,
GtkMenuShell *menu,
Node *node,
int index)
{
GtkWidget *item = gtk_separator_menu_item_new ();
gtk_menu_shell_insert (menu, item, index);
xml_add_widget (xml, item, toplevel, node);
return TRUE;
}
static gboolean node_is_empty (Node *node)
{
SLIST_FOREACH (node->children, l)
{
Node *child = l->data;
if (child->type == SEPARATOR)
continue;
if (child->type == PLACEHOLDER)
{
if (!node_is_empty (child))
return FALSE;
}
else
{
return FALSE;
}
}
SLIST_FOREACH_END;
return TRUE;
}
static void
create_menu_item (MooUIXML *xml,
Toplevel *toplevel,
GtkMenuShell *menu,
Node *node,
int index)
{
GtkWidget *menu_item = NULL;
Item *item;
g_return_if_fail (node != NULL && node->type == ITEM);
item = (Item*) node;
if (item->action)
{
MooAction *action;
g_return_if_fail (toplevel->actions != NULL);
action = moo_action_group_get_action (toplevel->actions, item->action);
if (!action)
{
#if REPORT_UNKNOWN_ACTIONS
g_critical ("%s: could not find action '%s'",
G_STRLOC, item->action);
#endif
return;
}
if (action->dead)
return;
menu_item = moo_action_create_menu_item (action);
}
else
{
if (item->stock_id)
{
menu_item = gtk_image_menu_item_new_from_stock (item->stock_id, NULL);
}
else if (item->label)
{
if (item->icon_stock_id)
{
GtkWidget *icon = gtk_image_new_from_stock (item->icon_stock_id,
GTK_ICON_SIZE_MENU);
menu_item = gtk_image_menu_item_new_with_mnemonic (item->label);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), icon);
}
else
{
menu_item = gtk_menu_item_new_with_mnemonic (item->label);
}
}
if (menu_item)
gtk_widget_show (menu_item);
}
g_return_if_fail (menu_item != NULL);
gtk_menu_shell_insert (menu, menu_item, index);
xml_add_widget (xml, menu_item, toplevel, node);
xml_add_item_widget (xml, menu_item);
if (!node_is_empty (node))
{
GtkWidget *submenu = gtk_menu_new ();
gtk_widget_show (submenu);
gtk_menu_set_accel_group (GTK_MENU (submenu), toplevel->accel_group);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), submenu);
fill_menu_shell (xml, toplevel, node,
GTK_MENU_SHELL (submenu));
}
/* XXX completely empty menus? */
if (node->children)
check_empty (node, menu_item, toplevel);
}
static GSList*
node_list_children (Node *parent)
{
GSList *list = NULL, *l;
for (l = parent->children; l != NULL; l = l->next)
{
GSList *tmp, *t;
Node *node = l->data;
switch (node->type)
{
case MOO_UI_NODE_ITEM:
case MOO_UI_NODE_SEPARATOR:
list = g_slist_prepend (list, node);
break;
case MOO_UI_NODE_PLACEHOLDER:
tmp = node_list_children (node);
for (t = tmp; t != NULL; t = t->next)
list = g_slist_prepend (list, t->data);
g_slist_free (tmp);
break;
default:
g_return_val_if_reached (g_slist_reverse (list));
}
}
return g_slist_reverse (list);
}
static void
real_foreach (Node *node,
gpointer data)
{
GSList *l;
struct {
NodeForeachFunc func;
gpointer func_data;
gboolean stop;
} *foreach_data = data;
if (foreach_data->stop)
return;
if (foreach_data->func (node, foreach_data->func_data))
{
foreach_data->stop = TRUE;
return;
}
for (l = node->children; l != NULL; l = l->next)
{
Node *child = l->data;
real_foreach (child, data);
if (foreach_data->stop)
return;
}
}
static void
node_foreach (Node *node,
NodeForeachFunc func,
gpointer data)
{
struct {
NodeForeachFunc func;
gpointer func_data;
gboolean stop;
} foreach_data = {func, data, FALSE};
g_return_if_fail (node != NULL);
g_return_if_fail (func != NULL);
real_foreach (node, &foreach_data);
}
static GtkWidget*
toplevel_get_widget (Toplevel *toplevel,
Node *node)
{
return g_hash_table_lookup (toplevel->children, node);
}
static void
check_empty (Node *parent,
GtkWidget *widget,
Toplevel *toplevel)
{
GSList *children, *l;
gboolean has_children = FALSE;
children = node_list_children (parent);
for (l = children; l != NULL; l = l->next)
{
Node *node = l->data;
if (node->type == MOO_UI_NODE_ITEM)
{
GtkWidget *nw = toplevel_get_widget (toplevel, node);
if (nw && GTK_WIDGET_VISIBLE (nw))
{
has_children = TRUE;
break;
}
}
}
/* XXX decide something on this stuff */
// if (!(parent->flags & MOO_UI_NODE_ENABLE_EMPTY))
// gtk_widget_set_sensitive (widget, has_children);
g_object_set (widget, "visible", has_children, NULL);
g_slist_free (children);
}
static void
check_separators (Node *parent,
Toplevel *toplevel)
{
GSList *children, *l;
Node *separator = NULL;
gboolean first = TRUE;
gboolean has_children = FALSE;
GtkWidget *widget;
if (!toplevel_get_widget (toplevel, parent))
return;
children = node_list_children (parent);
for (l = children; l != NULL; l = l->next)
{
Node *node = l->data;
switch (node->type)
{
case MOO_UI_NODE_ITEM:
widget = toplevel_get_widget (toplevel, node);
if (!widget || !GTK_WIDGET_VISIBLE (widget))
continue;
has_children = TRUE;
if (!first)
{
if (separator)
{
GtkWidget *sep_widget = toplevel_get_widget (toplevel, separator);
g_return_if_fail (sep_widget != NULL);
gtk_widget_show (sep_widget);
}
}
else
{
first = FALSE;
separator = NULL;
}
break;
case MOO_UI_NODE_SEPARATOR:
widget = toplevel_get_widget (toplevel, node);
g_return_if_fail (widget != NULL);
gtk_widget_hide (widget);
if (!first)
separator = node;
break;
default:
g_return_if_reached ();
}
}
widget = toplevel_get_widget (toplevel, parent);
if (widget)
check_empty (parent, widget, toplevel);
g_slist_free (children);
}
static gboolean
fill_menu_shell (MooUIXML *xml,
Toplevel *toplevel,
Node *menu_node,
GtkMenuShell *menu)
{
GSList *children;
children = node_list_children (menu_node);
SLIST_FOREACH (children, l)
{
Node *node = l->data;
switch (node->type)
{
case MOO_UI_NODE_ITEM:
create_menu_item (xml, toplevel, menu, node, -1);
break;
case MOO_UI_NODE_SEPARATOR:
create_menu_separator (xml, toplevel, menu, node, -1);
break;
default:
g_warning ("%s: invalid menu item type %s",
G_STRLOC, NODE_TYPE_NAME[node->type]);
return FALSE;
}
}
SLIST_FOREACH_END;
check_separators (menu_node, toplevel);
return TRUE;
}
static gboolean
create_menu_shell (MooUIXML *xml,
Toplevel *toplevel,
MooUIWidgetType type)
{
g_return_val_if_fail (toplevel != NULL, FALSE);
g_return_val_if_fail (toplevel->widget == NULL, FALSE);
g_return_val_if_fail (toplevel->node != NULL, FALSE);
if (type == MOO_UI_MENUBAR)
{
toplevel->widget = gtk_menu_bar_new ();
}
else
{
toplevel->widget = gtk_menu_new ();
gtk_menu_set_accel_group (GTK_MENU (toplevel->widget),
toplevel->accel_group);
}
xml_connect_toplevel (xml, toplevel);
return fill_menu_shell (xml, toplevel,
toplevel->node,
GTK_MENU_SHELL (toplevel->widget));
}
static gboolean
create_tool_separator (MooUIXML *xml,
Toplevel *toplevel,
GtkToolbar *toolbar,
Node *node,
int index)
{
GtkToolItem *item = gtk_separator_tool_item_new ();
gtk_toolbar_insert (toolbar, item, index);
xml_add_widget (xml, GTK_WIDGET (item), toplevel, node);
return TRUE;
}
#if GTK_CHECK_VERSION(2,6,0)
#define IS_MENU_TOOL_BUTTON(wid) (GTK_IS_MENU_TOOL_BUTTON (wid) || \
MOO_IS_MENU_TOOL_BUTTON (wid))
#else
#define IS_MENU_TOOL_BUTTON MOO_IS_MENU_TOOL_BUTTON
#endif
static void
menu_tool_button_set_menu (GtkWidget *button,
GtkWidget *menu)
{
if (MOO_IS_MENU_TOOL_BUTTON (button))
moo_menu_tool_button_set_menu (MOO_MENU_TOOL_BUTTON (button), menu);
#if GTK_CHECK_VERSION(2,6,0)
else if (GTK_IS_MENU_TOOL_BUTTON (button))
gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (button), menu);
#endif
else
g_return_if_reached ();
}
static GtkWidget*
menu_tool_button_get_menu (GtkWidget *button)
{
if (MOO_IS_MENU_TOOL_BUTTON (button))
return moo_menu_tool_button_get_menu (MOO_MENU_TOOL_BUTTON (button));
#if GTK_CHECK_VERSION(2,6,0)
else if (GTK_IS_MENU_TOOL_BUTTON (button))
return gtk_menu_tool_button_get_menu (GTK_MENU_TOOL_BUTTON (button));
#endif
else
g_return_val_if_reached (NULL);
}
static gboolean
create_tool_item (MooUIXML *xml,
Toplevel *toplevel,
GtkToolbar *toolbar,
Node *node,
int index)
{
GtkWidget *tool_item = NULL;
Item *item;
g_return_val_if_fail (node != NULL && node->type == ITEM, FALSE);
item = (Item*) node;
if (item->action)
{
MooAction *action;
g_return_val_if_fail (toplevel->actions != NULL, FALSE);
action = moo_action_group_get_action (toplevel->actions, item->action);
g_return_val_if_fail (action != NULL, FALSE);
if (action->dead)
return TRUE;
tool_item = moo_action_create_tool_item (action, GTK_WIDGET (toolbar), index,
node->children ? MOO_TOOL_ITEM_MENU : 0);
if (node->children)
{
if (!IS_MENU_TOOL_BUTTON (tool_item))
{
g_critical ("%s: oops", G_STRLOC);
}
else
{
GtkWidget *menu = gtk_menu_new ();
/* XXX empty menu */
gtk_widget_show (menu);
menu_tool_button_set_menu (tool_item, menu);
fill_menu_shell (xml, toplevel, node, GTK_MENU_SHELL (menu));
}
}
}
else
{
GtkWidget *menu;
tool_item = moo_menu_tool_button_new ();
gtk_widget_show (tool_item);
if (item->tooltip)
gtk_tool_item_set_tooltip (GTK_TOOL_ITEM (tool_item),
moo_ui_xml_get_tooltips (xml),
item->tooltip, item->tooltip);
if (item->icon_stock_id)
gtk_tool_button_set_stock_id (GTK_TOOL_BUTTON (tool_item),
item->icon_stock_id);
if (item->stock_id)
gtk_tool_button_set_stock_id (GTK_TOOL_BUTTON (tool_item),
item->stock_id);
if (item->label)
gtk_tool_button_set_label (GTK_TOOL_BUTTON (tool_item),
item->label);
gtk_toolbar_insert (toolbar, GTK_TOOL_ITEM (tool_item), index);
menu = gtk_menu_new ();
/* XXX empty menu */
gtk_widget_show (menu);
menu_tool_button_set_menu (tool_item, menu);
fill_menu_shell (xml, toplevel, node, GTK_MENU_SHELL (menu));
}
g_return_val_if_fail (tool_item != NULL, FALSE);
xml_add_widget (xml, tool_item, toplevel, node);
xml_add_item_widget (xml, tool_item);
return TRUE;
}
static gboolean
fill_toolbar (MooUIXML *xml,
Toplevel *toplevel,
Node *toolbar_node,
GtkToolbar *toolbar)
{
gboolean result = TRUE;
GSList *children;
children = node_list_children (toolbar_node);
SLIST_FOREACH (children, l)
{
Node *node = l->data;
switch (node->type)
{
case MOO_UI_NODE_ITEM:
create_tool_item (xml, toplevel, toolbar, node, -1);
break;
case MOO_UI_NODE_SEPARATOR:
create_tool_separator (xml, toplevel, toolbar, node, -1);
break;
default:
g_warning ("%s: invalid tool item type %s",
G_STRLOC, NODE_TYPE_NAME[node->type]);
return FALSE;
}
if (!result)
return FALSE;
}
SLIST_FOREACH_END;
check_separators (toolbar_node, toplevel);
return TRUE;
}
static gboolean
create_toolbar (MooUIXML *xml,
Toplevel *toplevel)
{
g_return_val_if_fail (toplevel != NULL, FALSE);
g_return_val_if_fail (toplevel->widget == NULL, FALSE);
g_return_val_if_fail (toplevel->node != NULL, FALSE);
toplevel->widget = gtk_toolbar_new ();
xml_connect_toplevel (xml, toplevel);
return fill_toolbar (xml, toplevel,
toplevel->node,
GTK_TOOLBAR (toplevel->widget));
}
gpointer
moo_ui_xml_create_widget (MooUIXML *xml,
MooUIWidgetType type,
const char *path,
MooActionGroup *actions,
GtkAccelGroup *accel_group)
{
Node *node;
Toplevel *toplevel;
gboolean result = FALSE;
g_return_val_if_fail (MOO_IS_UI_XML (xml), NULL);
g_return_val_if_fail (path != NULL, NULL);
g_return_val_if_fail (!actions || MOO_IS_ACTION_GROUP (actions), NULL);
node = moo_ui_xml_get_node (xml, path);
g_return_val_if_fail (node != NULL, NULL);
if (node->type != WIDGET)
{
g_warning ("%s: can create widgets only for nodes of type %s",
G_STRLOC, NODE_TYPE_NAME[WIDGET]);
return NULL;
}
if (type < 1 || type > 3)
{
g_warning ("%s: invalid widget type %d", G_STRLOC, type);
return NULL;
}
toplevel = toplevel_new (node, actions, accel_group);
toplevel->in_creation = TRUE;
xml->priv->toplevels = g_slist_append (xml->priv->toplevels, toplevel);
switch (type)
{
case MOO_UI_MENUBAR:
result = create_menu_shell (xml, toplevel, MOO_UI_MENUBAR);
break;
case MOO_UI_MENU:
result = create_menu_shell (xml, toplevel, MOO_UI_MENU);
break;
case MOO_UI_TOOLBAR:
result = create_toolbar (xml, toplevel);
break;
}
toplevel->in_creation = FALSE;
if (!result)
{
xml_delete_toplevel (xml, toplevel);
return NULL;
}
return toplevel->widget;
}
GtkWidget*
moo_ui_xml_get_widget (MooUIXML *xml,
GtkWidget *widget,
const char *path)
{
Toplevel *toplevel;
MooUINode *node;
g_return_val_if_fail (MOO_IS_UI_XML (xml), NULL);
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
g_return_val_if_fail (path != NULL, NULL);
node = moo_ui_xml_get_node (xml, path);
g_return_val_if_fail (node != NULL, NULL);
toplevel = g_object_get_qdata (G_OBJECT (widget), TOPLEVEL_QUARK);
g_return_val_if_fail (toplevel != NULL, NULL);
return toplevel_get_widget (toplevel, node);
}
static Node*
effective_parent (Node *node)
{
Node *parent;
g_return_val_if_fail (node != NULL, NULL);
parent = node->parent;
while (parent && parent->type == PLACEHOLDER)
parent = parent->parent;
return parent;
}
static int
effective_index (Node *parent,
Node *node)
{
GSList *children;
int index;
g_return_val_if_fail (effective_parent (node) == parent, -1);
children = node_list_children (parent);
index = g_slist_index (children, node);
g_slist_free (children);
g_return_val_if_fail (index >= 0, -1);
return index;
}
static void
toplevel_add_node (MooUIXML *xml,
Toplevel *toplevel,
Node *node)
{
g_return_if_fail (GTK_IS_WIDGET (toplevel->widget));
g_return_if_fail (node->type == ITEM || node->type == SEPARATOR);
g_return_if_fail (node_is_ancestor (node, toplevel->node));
if (GTK_IS_TOOLBAR (toplevel->widget))
{
GtkWidget *parent_widget;
Node *parent = effective_parent (node);
g_return_if_fail (parent != NULL);
parent_widget = toplevel_get_widget (toplevel, parent);
g_return_if_fail (parent_widget != NULL);
if (GTK_IS_TOOLBAR (parent_widget))
{
switch (node->type)
{
case ITEM:
create_tool_item (xml, toplevel,
GTK_TOOLBAR (parent_widget),
node, effective_index (parent, node));
break;
case SEPARATOR:
create_tool_separator (xml, toplevel,
GTK_TOOLBAR (parent_widget),
node, effective_index (parent, node));
break;
default:
g_return_if_reached ();
}
check_separators (parent, toplevel);
}
else if (IS_MENU_TOOL_BUTTON (parent_widget))
{
GtkWidget *menu;
menu = menu_tool_button_get_menu (parent_widget);
if (!menu)
{
menu = gtk_menu_new ();
gtk_widget_show (menu);
menu_tool_button_set_menu (parent_widget, menu);
}
switch (node->type)
{
case ITEM:
create_menu_item (xml, toplevel, GTK_MENU_SHELL (menu),
node, effective_index (parent, node));
break;
case SEPARATOR:
create_menu_separator (xml, toplevel, GTK_MENU_SHELL (menu),
node, effective_index (parent, node));
break;
default:
g_return_if_reached ();
}
check_separators (parent, toplevel);
}
else
{
g_return_if_reached ();
}
}
else if (GTK_IS_MENU_SHELL (toplevel->widget))
{
GtkWidget *parent_widget, *menu_shell;
Node *parent = effective_parent (node);
g_return_if_fail (parent != NULL);
parent_widget = toplevel_get_widget (toplevel, parent);
g_return_if_fail (parent_widget != NULL);
if (GTK_IS_MENU_SHELL (parent_widget))
{
menu_shell = parent_widget;
}
else
{
g_return_if_fail (GTK_IS_MENU_ITEM (parent_widget));
menu_shell = gtk_menu_item_get_submenu (GTK_MENU_ITEM (parent_widget));
if (!menu_shell)
{
menu_shell = gtk_menu_new ();
gtk_widget_show (menu_shell);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (parent_widget), menu_shell);
}
}
switch (node->type)
{
case ITEM:
create_menu_item (xml, toplevel, GTK_MENU_SHELL (menu_shell),
node, effective_index (parent, node));
break;
case SEPARATOR:
create_menu_separator (xml, toplevel, GTK_MENU_SHELL (menu_shell),
node, effective_index (parent, node));
break;
default:
g_return_if_reached ();
}
check_separators (parent, toplevel);
}
else
{
g_return_if_reached ();
}
}
static GSList*
node_list_all_children (Node *node)
{
GSList *list, *l;
g_return_val_if_fail (node != NULL, NULL);
list = g_slist_append (NULL, node);
for (l = node->children; l != NULL; l = l->next)
list = g_slist_append (list, node_list_all_children (l->data));
return list;
}
static void
toplevel_remove_node (G_GNUC_UNUSED MooUIXML *xml,
Toplevel *toplevel,
Node *node)
{
GSList *children, *l;
g_return_if_fail (node != toplevel->node);
g_return_if_fail (node_is_ancestor (node, toplevel->node));
children = node_list_all_children (node);
for (l = children; l != NULL; l = l->next)
{
GtkWidget *widget = g_hash_table_lookup (toplevel->children, node);
if (widget)
gtk_widget_destroy (widget);
}
g_slist_free (children);
}
static void
update_widgets (MooUIXML *xml,
UpdateType type,
Node *node)
{
switch (type)
{
case UPDATE_ADD_NODE:
SLIST_FOREACH (xml->priv->toplevels, l)
{
Toplevel *toplevel = l->data;
if (node_is_ancestor (node, toplevel->node))
toplevel_add_node (xml, toplevel, node);
}
SLIST_FOREACH_END;
break;
case UPDATE_REMOVE_NODE:
SLIST_FOREACH (xml->priv->toplevels, l)
{
Toplevel *toplevel = l->data;
if (node_is_ancestor (toplevel->node, node))
xml_delete_toplevel (xml, toplevel);
else if (node_is_ancestor (node, toplevel->node))
toplevel_remove_node (xml, toplevel, node);
}
SLIST_FOREACH_END;
break;
case UPDATE_CHANGE_NODE:
g_warning ("%s: implement me", G_STRLOC);
break;
default:
g_return_if_reached ();
}
}
static void
moo_ui_xml_finalize (GObject *object)
{
MooUIXML *xml = MOO_UI_XML (object);
SLIST_FOREACH (xml->priv->toplevels, t)
{
Toplevel *toplevel = t->data;
GSList *widgets = hash_table_list_values (toplevel->children);
SLIST_FOREACH (widgets, w)
{
GObject *widget = G_OBJECT (w->data);
g_object_set_qdata (widget, NODE_QUARK, NULL);
g_object_set_qdata (widget, TOPLEVEL_QUARK, NULL);
g_signal_handlers_disconnect_by_func (widget, (gpointer) widget_destroyed, xml);
g_signal_handlers_disconnect_by_func (widget, (gpointer) visibility_notify, xml);
}
SLIST_FOREACH_END;
g_slist_free (widgets);
toplevel_free (toplevel);
}
SLIST_FOREACH_END;
SLIST_FOREACH (xml->priv->merged_ui, m)
{
Merge *merge = m->data;
g_slist_free (merge->nodes);
g_free (merge);
}
SLIST_FOREACH_END;
g_slist_free (xml->priv->toplevels);
g_slist_free (xml->priv->merged_ui);
node_free (xml->priv->ui);
if (xml->priv->tooltips)
g_object_unref (xml->priv->tooltips);
g_free (xml->priv);
xml->priv = NULL;
G_OBJECT_CLASS(moo_ui_xml_parent_class)->finalize (object);
}
GType
moo_ui_node_get_type (void)
{
static GType type = 0;
if (!type)
type = g_pointer_type_register_static ("MooUINode");
return type;
}
GType
moo_ui_widget_type_get_type (void)
{
static GType type = 0;
if (!type)
{
static const GEnumValue values[] = {
{ MOO_UI_MENUBAR, (char*) "MOO_UI_MENUBAR", (char*) "menubar" },
{ MOO_UI_MENU, (char*) "MOO_UI_MENU", (char*) "menu" },
{ MOO_UI_TOOLBAR, (char*) "MOO_UI_TOOLBAR", (char*) "toolbar" },
{ 0, NULL, NULL }
};
type = g_enum_register_static ("MooUIWidgetType", values);
}
return type;
}