forked from AuroraMiddleware/gtk
1653 lines
43 KiB
C
1653 lines
43 KiB
C
|
/*
|
||
|
* GTK - The GIMP Toolkit
|
||
|
* Copyright (C) 1998, 1999 Red Hat, Inc.
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* This Library is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU Library General Public License as
|
||
|
* published by the Free Software Foundation; either version 2 of the
|
||
|
* License, or (at your option) any later version.
|
||
|
*
|
||
|
* This Library 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
|
||
|
* Library General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU Library General Public
|
||
|
* License along with the Gnome Library; see the file COPYING.LIB. If not,
|
||
|
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||
|
* Boston, MA 02111-1307, USA.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Author: James Henstridge <james@daa.com.au>
|
||
|
*
|
||
|
* Modified by the GTK+ Team and others 2003. See the AUTHORS
|
||
|
* file for a list of people on the GTK+ Team. See the ChangeLog
|
||
|
* files for a list of changes. These files are distributed with
|
||
|
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
|
||
|
*/
|
||
|
|
||
|
#include <config.h>
|
||
|
|
||
|
#include <string.h>
|
||
|
#include "gtkmenumerge.h"
|
||
|
#include "gtktoolbar.h"
|
||
|
#include "gtkseparatortoolitem.h"
|
||
|
#include "gtkmenushell.h"
|
||
|
#include "gtkmenu.h"
|
||
|
#include "gtkmenubar.h"
|
||
|
#include "gtkseparatormenuitem.h"
|
||
|
#include "gtkintl.h"
|
||
|
|
||
|
#undef DEBUG_MENU_MERGE
|
||
|
|
||
|
typedef enum
|
||
|
{
|
||
|
GTK_MENU_MERGE_UNDECIDED,
|
||
|
GTK_MENU_MERGE_ROOT,
|
||
|
GTK_MENU_MERGE_MENUBAR,
|
||
|
GTK_MENU_MERGE_MENU,
|
||
|
GTK_MENU_MERGE_TOOLBAR,
|
||
|
GTK_MENU_MERGE_MENU_PLACEHOLDER,
|
||
|
GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER,
|
||
|
GTK_MENU_MERGE_POPUPS,
|
||
|
GTK_MENU_MERGE_MENUITEM,
|
||
|
GTK_MENU_MERGE_TOOLITEM,
|
||
|
GTK_MENU_MERGE_SEPARATOR,
|
||
|
} GtkMenuMergeNodeType;
|
||
|
|
||
|
|
||
|
typedef struct _GtkMenuMergeNode GtkMenuMergeNode;
|
||
|
|
||
|
struct _GtkMenuMergeNode {
|
||
|
GtkMenuMergeNodeType type;
|
||
|
|
||
|
const gchar *name;
|
||
|
|
||
|
GQuark action_name;
|
||
|
GtkAction *action;
|
||
|
GtkWidget *proxy;
|
||
|
GtkWidget *extra; /*GtkMenu for submenus, second separator for placeholders*/
|
||
|
|
||
|
GList *uifiles;
|
||
|
|
||
|
guint dirty : 1;
|
||
|
};
|
||
|
|
||
|
#define GTK_MENU_MERGE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_MENU_MERGE, GtkMenuMergePrivate))
|
||
|
|
||
|
struct _GtkMenuMergePrivate
|
||
|
{
|
||
|
GtkAccelGroup *accel_group;
|
||
|
|
||
|
GNode *root_node;
|
||
|
GList *action_groups;
|
||
|
|
||
|
guint last_merge_id;
|
||
|
|
||
|
guint update_tag;
|
||
|
};
|
||
|
|
||
|
#define NODE_INFO(node) ((GtkMenuMergeNode *)node->data)
|
||
|
|
||
|
typedef struct _NodeUIReference NodeUIReference;
|
||
|
|
||
|
struct _NodeUIReference
|
||
|
{
|
||
|
guint merge_id;
|
||
|
GQuark action_quark;
|
||
|
};
|
||
|
|
||
|
static void gtk_menu_merge_class_init (GtkMenuMergeClass *class);
|
||
|
static void gtk_menu_merge_init (GtkMenuMerge *merge);
|
||
|
|
||
|
static void gtk_menu_merge_queue_update (GtkMenuMerge *self);
|
||
|
static void gtk_menu_merge_dirty_all (GtkMenuMerge *self);
|
||
|
|
||
|
static GNode *get_child_node (GtkMenuMerge *self, GNode *parent,
|
||
|
const gchar *childname,
|
||
|
gint childname_length,
|
||
|
GtkMenuMergeNodeType node_type,
|
||
|
gboolean create, gboolean top);
|
||
|
static GNode *gtk_menu_merge_get_node (GtkMenuMerge *self,
|
||
|
const gchar *path,
|
||
|
GtkMenuMergeNodeType node_type,
|
||
|
gboolean create);
|
||
|
static guint gtk_menu_merge_next_merge_id (GtkMenuMerge *self);
|
||
|
|
||
|
static void gtk_menu_merge_node_prepend_ui_reference (GtkMenuMergeNode *node,
|
||
|
guint merge_id,
|
||
|
GQuark action_quark);
|
||
|
static void gtk_menu_merge_node_remove_ui_reference (GtkMenuMergeNode *node,
|
||
|
guint merge_id);
|
||
|
static void gtk_menu_merge_ensure_update (GtkMenuMerge *self);
|
||
|
|
||
|
|
||
|
enum
|
||
|
{
|
||
|
ADD_WIDGET,
|
||
|
REMOVE_WIDGET,
|
||
|
LAST_SIGNAL
|
||
|
};
|
||
|
|
||
|
static guint merge_signals[LAST_SIGNAL] = { 0 };
|
||
|
|
||
|
static GMemChunk *merge_node_chunk = NULL;
|
||
|
|
||
|
GType
|
||
|
gtk_menu_merge_get_type (void)
|
||
|
{
|
||
|
static GtkType type = 0;
|
||
|
|
||
|
if (!type)
|
||
|
{
|
||
|
static const GTypeInfo type_info =
|
||
|
{
|
||
|
sizeof (GtkMenuMergeClass),
|
||
|
(GBaseInitFunc) NULL,
|
||
|
(GBaseFinalizeFunc) NULL,
|
||
|
(GClassInitFunc) gtk_menu_merge_class_init,
|
||
|
(GClassFinalizeFunc) NULL,
|
||
|
NULL,
|
||
|
|
||
|
sizeof (GtkMenuMerge),
|
||
|
0, /* n_preallocs */
|
||
|
(GInstanceInitFunc) gtk_menu_merge_init,
|
||
|
};
|
||
|
|
||
|
type = g_type_register_static (G_TYPE_OBJECT,
|
||
|
"GtkMenuMerge",
|
||
|
&type_info, 0);
|
||
|
}
|
||
|
return type;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gtk_menu_merge_class_init (GtkMenuMergeClass *klass)
|
||
|
{
|
||
|
GObjectClass *gobject_class;
|
||
|
|
||
|
gobject_class = G_OBJECT_CLASS (klass);
|
||
|
|
||
|
if (!merge_node_chunk)
|
||
|
merge_node_chunk = g_mem_chunk_create (GtkMenuMergeNode, 64,
|
||
|
G_ALLOC_AND_FREE);
|
||
|
|
||
|
merge_signals[ADD_WIDGET] =
|
||
|
g_signal_new ("add_widget",
|
||
|
G_OBJECT_CLASS_TYPE (klass),
|
||
|
G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
|
||
|
G_STRUCT_OFFSET (GtkMenuMergeClass, add_widget), NULL, NULL,
|
||
|
g_cclosure_marshal_VOID__OBJECT,
|
||
|
G_TYPE_NONE, 1,
|
||
|
GTK_TYPE_WIDGET);
|
||
|
merge_signals[REMOVE_WIDGET] =
|
||
|
g_signal_new ("remove_widget",
|
||
|
G_OBJECT_CLASS_TYPE (klass),
|
||
|
G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
|
||
|
G_STRUCT_OFFSET (GtkMenuMergeClass, remove_widget), NULL, NULL,
|
||
|
g_cclosure_marshal_VOID__OBJECT,
|
||
|
G_TYPE_NONE, 1,
|
||
|
GTK_TYPE_WIDGET);
|
||
|
|
||
|
g_type_class_add_private (gobject_class, sizeof (GtkMenuMergePrivate));
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
gtk_menu_merge_init (GtkMenuMerge *self)
|
||
|
{
|
||
|
guint merge_id;
|
||
|
GNode *node;
|
||
|
|
||
|
self->private_data = GTK_MENU_MERGE_GET_PRIVATE (self);
|
||
|
|
||
|
self->private_data->accel_group = gtk_accel_group_new ();
|
||
|
|
||
|
self->private_data->root_node = NULL;
|
||
|
self->private_data->action_groups = NULL;
|
||
|
|
||
|
self->private_data->last_merge_id = 0;
|
||
|
|
||
|
|
||
|
merge_id = gtk_menu_merge_next_merge_id (self);
|
||
|
node = get_child_node (self, NULL, "Root", 4,
|
||
|
GTK_MENU_MERGE_ROOT, TRUE, FALSE);
|
||
|
gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (node), merge_id, 0);
|
||
|
node = get_child_node (self, self->private_data->root_node, "popups", 6,
|
||
|
GTK_MENU_MERGE_POPUPS, TRUE, FALSE);
|
||
|
gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (node), merge_id, 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* gtk_menu_merge_new:
|
||
|
*
|
||
|
* Creates a new menu merge object.
|
||
|
*
|
||
|
* Return value: a new menu merge object.
|
||
|
*
|
||
|
* Since: 2.4
|
||
|
**/
|
||
|
GtkMenuMerge*
|
||
|
gtk_menu_merge_new (void)
|
||
|
{
|
||
|
return g_object_new (GTK_TYPE_MENU_MERGE, NULL);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* gtk_menu_merge_insert_action_group:
|
||
|
* @self: a #GtkMenuMerge object
|
||
|
* @action_group: the action group to be inserted
|
||
|
* @pos: the position at which the group will be inserted
|
||
|
*
|
||
|
* Inserts an action group into the list of action groups associated with @self.
|
||
|
*
|
||
|
* Since: 2.4
|
||
|
**/
|
||
|
void
|
||
|
gtk_menu_merge_insert_action_group (GtkMenuMerge *self,
|
||
|
GtkActionGroup *action_group,
|
||
|
gint pos)
|
||
|
{
|
||
|
g_return_if_fail (GTK_IS_MENU_MERGE (self));
|
||
|
g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
|
||
|
g_return_if_fail (g_list_find (self->private_data->action_groups, action_group) == NULL);
|
||
|
|
||
|
g_object_ref (action_group);
|
||
|
self->private_data->action_groups = g_list_insert (self->private_data->action_groups, action_group, pos);
|
||
|
|
||
|
/* dirty all nodes, as action bindings may change */
|
||
|
gtk_menu_merge_dirty_all (self);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gtk_menu_merge_remove_action_group:
|
||
|
* @self: a #GtkMenuMerge object
|
||
|
* @action_group: the action group to be removed
|
||
|
*
|
||
|
* Removes an action group from the list of action groups associated with @self.
|
||
|
*
|
||
|
* Since: 2.4
|
||
|
**/
|
||
|
void
|
||
|
gtk_menu_merge_remove_action_group (GtkMenuMerge *self,
|
||
|
GtkActionGroup *action_group)
|
||
|
{
|
||
|
g_return_if_fail (GTK_IS_MENU_MERGE (self));
|
||
|
g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
|
||
|
g_return_if_fail (g_list_find (self->private_data->action_groups,
|
||
|
action_group) != NULL);
|
||
|
|
||
|
self->private_data->action_groups =
|
||
|
g_list_remove (self->private_data->action_groups, action_group);
|
||
|
g_object_unref (action_group);
|
||
|
|
||
|
/* dirty all nodes, as action bindings may change */
|
||
|
gtk_menu_merge_dirty_all (self);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gtk_menu_merge_get_action_groups:
|
||
|
* @self: a #GtkMenuMerge object
|
||
|
*
|
||
|
* Returns the list of action groups associated with @self.
|
||
|
*
|
||
|
* Return value: a #GList of action groups. The list is owned by GTK+
|
||
|
* and should not be modified.
|
||
|
*
|
||
|
* Since: 2.4
|
||
|
**/
|
||
|
GList *
|
||
|
gtk_menu_merge_get_action_groups (GtkMenuMerge *self)
|
||
|
{
|
||
|
g_return_val_if_fail (GTK_IS_MENU_MERGE (self), NULL);
|
||
|
|
||
|
return self->private_data->action_groups;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gtk_menu_merge_get_accel_group:
|
||
|
* @self: a #GtkMenuMerge object
|
||
|
*
|
||
|
* Returns the #GtkAccelGroup associated with @self.
|
||
|
*
|
||
|
* Return value: the #GtkAccelGroup.
|
||
|
*
|
||
|
* Since: 2.4
|
||
|
**/
|
||
|
GtkAccelGroup *
|
||
|
gtk_menu_merge_get_accel_group (GtkMenuMerge *self)
|
||
|
{
|
||
|
g_return_val_if_fail (GTK_IS_MENU_MERGE (self), NULL);
|
||
|
|
||
|
return self->private_data->accel_group;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gtk_menu_merge_get_widget:
|
||
|
* @self: a #GtkMenuMerge
|
||
|
* @path: a path
|
||
|
*
|
||
|
* Looks up a widget by following a path. The path consists of the names specified
|
||
|
* in the XML description of the UI. separated by '/'. Elements which don't have
|
||
|
* a name attribute in the XML (e.g. <popups>) can be addressed by their
|
||
|
* XML element name (e.g. "popups").
|
||
|
*
|
||
|
* Return value: the widget found by following the path, or %NULL if no widget
|
||
|
* was found.
|
||
|
*
|
||
|
* Since: 2.4
|
||
|
**/
|
||
|
GtkWidget *
|
||
|
gtk_menu_merge_get_widget (GtkMenuMerge *self,
|
||
|
const gchar *path)
|
||
|
{
|
||
|
GNode *node;
|
||
|
|
||
|
/* ensure that there are no pending updates before we get the
|
||
|
* widget */
|
||
|
gtk_menu_merge_ensure_update (self);
|
||
|
|
||
|
node = gtk_menu_merge_get_node (self, path, GTK_MENU_MERGE_UNDECIDED, FALSE);
|
||
|
|
||
|
if (node == NULL)
|
||
|
return NULL;
|
||
|
|
||
|
return NODE_INFO (node)->proxy;
|
||
|
}
|
||
|
|
||
|
static GNode *
|
||
|
get_child_node (GtkMenuMerge *self,
|
||
|
GNode *parent,
|
||
|
const gchar *childname,
|
||
|
gint childname_length,
|
||
|
GtkMenuMergeNodeType node_type,
|
||
|
gboolean create,
|
||
|
gboolean top)
|
||
|
{
|
||
|
GNode *child = NULL;
|
||
|
|
||
|
g_return_val_if_fail (parent == NULL ||
|
||
|
(NODE_INFO (parent)->type != GTK_MENU_MERGE_MENUITEM &&
|
||
|
NODE_INFO (parent)->type != GTK_MENU_MERGE_TOOLITEM),
|
||
|
NULL);
|
||
|
|
||
|
if (parent)
|
||
|
{
|
||
|
if (childname)
|
||
|
{
|
||
|
for (child = parent->children; child != NULL; child = child->next)
|
||
|
{
|
||
|
if (strlen (NODE_INFO (child)->name) == childname_length &&
|
||
|
!strncmp (NODE_INFO (child)->name, childname, childname_length))
|
||
|
{
|
||
|
/* if undecided about node type, set it */
|
||
|
if (NODE_INFO (child)->type == GTK_MENU_MERGE_UNDECIDED)
|
||
|
NODE_INFO (child)->type = node_type;
|
||
|
|
||
|
/* warn about type mismatch */
|
||
|
if (NODE_INFO (child)->type != GTK_MENU_MERGE_UNDECIDED &&
|
||
|
node_type != GTK_MENU_MERGE_UNDECIDED &&
|
||
|
NODE_INFO (child)->type != node_type)
|
||
|
g_warning ("node type doesn't match %d (%s is type %d)",
|
||
|
node_type,
|
||
|
NODE_INFO (child)->name,
|
||
|
NODE_INFO (child)->type);
|
||
|
|
||
|
return child;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (!child && create)
|
||
|
{
|
||
|
GtkMenuMergeNode *mnode;
|
||
|
|
||
|
mnode = g_chunk_new0 (GtkMenuMergeNode, merge_node_chunk);
|
||
|
mnode->type = node_type;
|
||
|
mnode->name = g_strndup (childname, childname_length);
|
||
|
mnode->dirty = TRUE;
|
||
|
|
||
|
if (top)
|
||
|
child = g_node_prepend_data (parent, mnode);
|
||
|
else
|
||
|
child = g_node_append_data (parent, mnode);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* handle root node */
|
||
|
if (self->private_data->root_node)
|
||
|
{
|
||
|
child = self->private_data->root_node;
|
||
|
if (strncmp (NODE_INFO (child)->name, childname, childname_length) != 0)
|
||
|
g_warning ("root node name '%s' doesn't match '%s'",
|
||
|
childname, NODE_INFO (child)->name);
|
||
|
if (NODE_INFO (child)->type != GTK_MENU_MERGE_ROOT)
|
||
|
g_warning ("base element must be of type ROOT");
|
||
|
}
|
||
|
else if (create)
|
||
|
{
|
||
|
GtkMenuMergeNode *mnode;
|
||
|
|
||
|
mnode = g_chunk_new0 (GtkMenuMergeNode, merge_node_chunk);
|
||
|
mnode->type = node_type;
|
||
|
mnode->name = g_strndup (childname, childname_length);
|
||
|
mnode->dirty = TRUE;
|
||
|
|
||
|
child = self->private_data->root_node = g_node_new (mnode);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return child;
|
||
|
}
|
||
|
|
||
|
static GNode *
|
||
|
gtk_menu_merge_get_node (GtkMenuMerge *self,
|
||
|
const gchar *path,
|
||
|
GtkMenuMergeNodeType node_type,
|
||
|
gboolean create)
|
||
|
{
|
||
|
const gchar *pos, *end;
|
||
|
GNode *parent, *node;
|
||
|
|
||
|
end = path + strlen (path);
|
||
|
pos = path;
|
||
|
parent = node = NULL;
|
||
|
while (pos < end)
|
||
|
{
|
||
|
const gchar *slash;
|
||
|
gsize length;
|
||
|
|
||
|
slash = strchr (pos, '/');
|
||
|
if (slash)
|
||
|
length = slash - pos;
|
||
|
else
|
||
|
length = strlen (pos);
|
||
|
|
||
|
node = get_child_node (self, parent, pos, length, GTK_MENU_MERGE_UNDECIDED,
|
||
|
create, FALSE);
|
||
|
if (!node)
|
||
|
return NULL;
|
||
|
|
||
|
pos += length + 1; /* move past the node name and the slash too */
|
||
|
parent = node;
|
||
|
}
|
||
|
|
||
|
if (NODE_INFO (node)->type == GTK_MENU_MERGE_UNDECIDED)
|
||
|
NODE_INFO (node)->type = node_type;
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
static guint
|
||
|
gtk_menu_merge_next_merge_id (GtkMenuMerge *self)
|
||
|
{
|
||
|
self->private_data->last_merge_id++;
|
||
|
|
||
|
return self->private_data->last_merge_id;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gtk_menu_merge_node_prepend_ui_reference (GtkMenuMergeNode *node,
|
||
|
guint merge_id,
|
||
|
GQuark action_quark)
|
||
|
{
|
||
|
NodeUIReference *reference;
|
||
|
|
||
|
reference = g_new (NodeUIReference, 1);
|
||
|
reference->action_quark = action_quark;
|
||
|
reference->merge_id = merge_id;
|
||
|
|
||
|
/* Prepend the reference */
|
||
|
node->uifiles = g_list_prepend (node->uifiles, reference);
|
||
|
|
||
|
node->dirty = TRUE;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gtk_menu_merge_node_remove_ui_reference (GtkMenuMergeNode *node,
|
||
|
guint merge_id)
|
||
|
{
|
||
|
GList *p;
|
||
|
|
||
|
for (p = node->uifiles; p != NULL; p = p->next)
|
||
|
{
|
||
|
NodeUIReference *reference = p->data;
|
||
|
|
||
|
if (reference->merge_id == merge_id)
|
||
|
{
|
||
|
node->uifiles = g_list_remove_link (node->uifiles, p);
|
||
|
node->dirty = TRUE;
|
||
|
g_free (reference);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* -------------------- The UI file parser -------------------- */
|
||
|
|
||
|
typedef enum
|
||
|
{
|
||
|
STATE_START,
|
||
|
STATE_ROOT,
|
||
|
STATE_MENU,
|
||
|
STATE_TOOLBAR,
|
||
|
STATE_POPUPS,
|
||
|
STATE_MENUITEM,
|
||
|
STATE_TOOLITEM,
|
||
|
STATE_END
|
||
|
} ParseState;
|
||
|
|
||
|
typedef struct _ParseContext ParseContext;
|
||
|
struct _ParseContext
|
||
|
{
|
||
|
ParseState state;
|
||
|
ParseState prev_state;
|
||
|
|
||
|
GtkMenuMerge *self;
|
||
|
|
||
|
GNode *current;
|
||
|
|
||
|
guint merge_id;
|
||
|
};
|
||
|
|
||
|
static void
|
||
|
start_element_handler (GMarkupParseContext *context,
|
||
|
const gchar *element_name,
|
||
|
const gchar **attribute_names,
|
||
|
const gchar **attribute_values,
|
||
|
gpointer user_data,
|
||
|
GError **error)
|
||
|
{
|
||
|
ParseContext *ctx = user_data;
|
||
|
GtkMenuMerge *self = ctx->self;
|
||
|
|
||
|
gint i;
|
||
|
const gchar *node_name;
|
||
|
GQuark verb_quark;
|
||
|
gboolean top;
|
||
|
|
||
|
gboolean raise_error = TRUE;
|
||
|
gchar *error_attr = NULL;
|
||
|
|
||
|
/* work out a name for this node. Either the name attribute, or
|
||
|
* element name */
|
||
|
node_name = element_name;
|
||
|
verb_quark = 0;
|
||
|
top = FALSE;
|
||
|
for (i = 0; attribute_names[i] != NULL; i++)
|
||
|
{
|
||
|
if (!strcmp (attribute_names[i], "name"))
|
||
|
{
|
||
|
node_name = attribute_values[i];
|
||
|
}
|
||
|
else if (!strcmp (attribute_names[i], "verb"))
|
||
|
{
|
||
|
verb_quark = g_quark_from_string (attribute_values[i]);
|
||
|
}
|
||
|
else if (!strcmp (attribute_names[i], "pos"))
|
||
|
{
|
||
|
top = !strcmp (attribute_values[i], "top");
|
||
|
}
|
||
|
}
|
||
|
/* if no verb, then set it to the node's name */
|
||
|
if (verb_quark == 0)
|
||
|
verb_quark = g_quark_from_string (node_name);
|
||
|
|
||
|
switch (element_name[0])
|
||
|
{
|
||
|
case 'R':
|
||
|
if (ctx->state == STATE_START && !strcmp (element_name, "Root"))
|
||
|
{
|
||
|
ctx->state = STATE_ROOT;
|
||
|
ctx->current = self->private_data->root_node;
|
||
|
raise_error = FALSE;
|
||
|
|
||
|
gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
|
||
|
ctx->merge_id, verb_quark);
|
||
|
}
|
||
|
break;
|
||
|
case 'm':
|
||
|
if (ctx->state == STATE_ROOT && !strcmp (element_name, "menu"))
|
||
|
{
|
||
|
ctx->state = STATE_MENU;
|
||
|
ctx->current = get_child_node (self, ctx->current,
|
||
|
node_name, strlen (node_name),
|
||
|
GTK_MENU_MERGE_MENUBAR,
|
||
|
TRUE, FALSE);
|
||
|
if (NODE_INFO (ctx->current)->action_name == 0)
|
||
|
NODE_INFO (ctx->current)->action_name = verb_quark;
|
||
|
|
||
|
gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
|
||
|
ctx->merge_id, verb_quark);
|
||
|
NODE_INFO (ctx->current)->dirty = TRUE;
|
||
|
|
||
|
raise_error = FALSE;
|
||
|
}
|
||
|
else if (ctx->state == STATE_MENU && !strcmp (element_name, "menuitem"))
|
||
|
{
|
||
|
GNode *node;
|
||
|
|
||
|
ctx->state = STATE_MENUITEM;
|
||
|
node = get_child_node (self, ctx->current,
|
||
|
node_name, strlen (node_name),
|
||
|
GTK_MENU_MERGE_MENUITEM,
|
||
|
TRUE, top);
|
||
|
if (NODE_INFO (node)->action_name == 0)
|
||
|
NODE_INFO (node)->action_name = verb_quark;
|
||
|
|
||
|
gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (node),
|
||
|
ctx->merge_id, verb_quark);
|
||
|
NODE_INFO (node)->dirty = TRUE;
|
||
|
|
||
|
raise_error = FALSE;
|
||
|
}
|
||
|
break;
|
||
|
case 'd':
|
||
|
if (ctx->state == STATE_ROOT && !strcmp (element_name, "dockitem"))
|
||
|
{
|
||
|
ctx->state = STATE_TOOLBAR;
|
||
|
ctx->current = get_child_node (self, ctx->current,
|
||
|
node_name, strlen (node_name),
|
||
|
GTK_MENU_MERGE_TOOLBAR,
|
||
|
TRUE, FALSE);
|
||
|
if (NODE_INFO (ctx->current)->action_name == 0)
|
||
|
NODE_INFO (ctx->current)->action_name = verb_quark;
|
||
|
|
||
|
gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
|
||
|
ctx->merge_id, verb_quark);
|
||
|
NODE_INFO (ctx->current)->dirty = TRUE;
|
||
|
|
||
|
raise_error = FALSE;
|
||
|
}
|
||
|
break;
|
||
|
case 'p':
|
||
|
if (ctx->state == STATE_ROOT && !strcmp (element_name, "popups"))
|
||
|
{
|
||
|
ctx->state = STATE_POPUPS;
|
||
|
ctx->current = get_child_node (self, ctx->current,
|
||
|
node_name, strlen (node_name),
|
||
|
GTK_MENU_MERGE_POPUPS,
|
||
|
TRUE, FALSE);
|
||
|
|
||
|
gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
|
||
|
ctx->merge_id, verb_quark);
|
||
|
NODE_INFO (ctx->current)->dirty = TRUE;
|
||
|
|
||
|
raise_error = FALSE;
|
||
|
}
|
||
|
else if (ctx->state == STATE_POPUPS && !strcmp (element_name, "popup"))
|
||
|
{
|
||
|
ctx->state = STATE_MENU;
|
||
|
ctx->current = get_child_node (self, ctx->current,
|
||
|
node_name, strlen (node_name),
|
||
|
GTK_MENU_MERGE_MENU,
|
||
|
TRUE, FALSE);
|
||
|
if (NODE_INFO (ctx->current)->action_name == 0)
|
||
|
NODE_INFO (ctx->current)->action_name = verb_quark;
|
||
|
|
||
|
gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
|
||
|
ctx->merge_id, verb_quark);
|
||
|
NODE_INFO (ctx->current)->dirty = TRUE;
|
||
|
|
||
|
raise_error = FALSE;
|
||
|
}
|
||
|
else if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
|
||
|
!strcmp (element_name, "placeholder"))
|
||
|
{
|
||
|
if (ctx->state == STATE_MENU)
|
||
|
ctx->current = get_child_node (self, ctx->current,
|
||
|
node_name, strlen (node_name),
|
||
|
GTK_MENU_MERGE_MENU_PLACEHOLDER,
|
||
|
TRUE, top);
|
||
|
else
|
||
|
ctx->current = get_child_node (self, ctx->current,
|
||
|
node_name, strlen (node_name),
|
||
|
GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER,
|
||
|
TRUE, top);
|
||
|
|
||
|
gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
|
||
|
ctx->merge_id, verb_quark);
|
||
|
NODE_INFO (ctx->current)->dirty = TRUE;
|
||
|
|
||
|
raise_error = FALSE;
|
||
|
}
|
||
|
break;
|
||
|
case 's':
|
||
|
if (ctx->state == STATE_MENU && !strcmp (element_name, "submenu"))
|
||
|
{
|
||
|
ctx->state = STATE_MENU;
|
||
|
ctx->current = get_child_node (self, ctx->current,
|
||
|
node_name, strlen (node_name),
|
||
|
GTK_MENU_MERGE_MENU,
|
||
|
TRUE, top);
|
||
|
if (NODE_INFO (ctx->current)->action_name == 0)
|
||
|
NODE_INFO (ctx->current)->action_name = verb_quark;
|
||
|
|
||
|
gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
|
||
|
ctx->merge_id, verb_quark);
|
||
|
NODE_INFO (ctx->current)->dirty = TRUE;
|
||
|
|
||
|
raise_error = FALSE;
|
||
|
}
|
||
|
else if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
|
||
|
!strcmp (element_name, "separator"))
|
||
|
{
|
||
|
GNode *node;
|
||
|
|
||
|
if (ctx->state == STATE_MENU)
|
||
|
ctx->state = STATE_MENUITEM;
|
||
|
else
|
||
|
ctx->state = STATE_TOOLITEM;
|
||
|
node = get_child_node (self, ctx->current,
|
||
|
node_name, strlen (node_name),
|
||
|
GTK_MENU_MERGE_SEPARATOR,
|
||
|
TRUE, top);
|
||
|
if (NODE_INFO (node)->action_name == 0)
|
||
|
NODE_INFO (node)->action_name = verb_quark;
|
||
|
|
||
|
gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (node),
|
||
|
ctx->merge_id, verb_quark);
|
||
|
NODE_INFO (node)->dirty = TRUE;
|
||
|
|
||
|
raise_error = FALSE;
|
||
|
}
|
||
|
break;
|
||
|
case 't':
|
||
|
if (ctx->state == STATE_TOOLBAR && !strcmp (element_name, "toolitem"))
|
||
|
{
|
||
|
GNode *node;
|
||
|
|
||
|
ctx->state = STATE_TOOLITEM;
|
||
|
node = get_child_node (self, ctx->current,
|
||
|
node_name, strlen (node_name),
|
||
|
GTK_MENU_MERGE_TOOLITEM,
|
||
|
TRUE, top);
|
||
|
if (NODE_INFO (node)->action_name == 0)
|
||
|
NODE_INFO (node)->action_name = verb_quark;
|
||
|
|
||
|
gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (node),
|
||
|
ctx->merge_id, verb_quark);
|
||
|
NODE_INFO (node)->dirty = TRUE;
|
||
|
|
||
|
raise_error = FALSE;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
if (raise_error)
|
||
|
{
|
||
|
gint line_number, char_number;
|
||
|
|
||
|
g_markup_parse_context_get_position (context,
|
||
|
&line_number, &char_number);
|
||
|
if (error_attr)
|
||
|
g_set_error (error,
|
||
|
G_MARKUP_ERROR,
|
||
|
G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
|
||
|
_("Unknown attribute '%s' on line %d char %d"),
|
||
|
error_attr,
|
||
|
line_number, char_number);
|
||
|
else
|
||
|
g_set_error (error,
|
||
|
G_MARKUP_ERROR,
|
||
|
G_MARKUP_ERROR_UNKNOWN_ELEMENT,
|
||
|
_("Unknown tag '%s' on line %d char %d"),
|
||
|
element_name,
|
||
|
line_number, char_number);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
end_element_handler (GMarkupParseContext *context,
|
||
|
const gchar *element_name,
|
||
|
gpointer user_data,
|
||
|
GError **error)
|
||
|
{
|
||
|
ParseContext *ctx = user_data;
|
||
|
GtkMenuMerge *self = ctx->self;
|
||
|
|
||
|
switch (ctx->state)
|
||
|
{
|
||
|
case STATE_START:
|
||
|
g_warning ("shouldn't get any end tags in start state");
|
||
|
/* should we GError here? */
|
||
|
break;
|
||
|
case STATE_ROOT:
|
||
|
if (ctx->current != self->private_data->root_node)
|
||
|
g_warning ("we are in STATE_ROOT, but the current node isn't the root");
|
||
|
ctx->current = NULL;
|
||
|
ctx->state = STATE_END;
|
||
|
break;
|
||
|
case STATE_MENU:
|
||
|
ctx->current = ctx->current->parent;
|
||
|
if (NODE_INFO (ctx->current)->type == GTK_MENU_MERGE_ROOT) /* menubar */
|
||
|
ctx->state = STATE_ROOT;
|
||
|
else if (NODE_INFO (ctx->current)->type == GTK_MENU_MERGE_POPUPS) /* popup */
|
||
|
ctx->state = STATE_POPUPS;
|
||
|
/* else, stay in STATE_MENU state */
|
||
|
break;
|
||
|
case STATE_TOOLBAR:
|
||
|
ctx->current = ctx->current->parent;
|
||
|
/* we conditionalise this test, in case we are closing off a
|
||
|
* placeholder */
|
||
|
if (NODE_INFO (ctx->current)->type == GTK_MENU_MERGE_ROOT)
|
||
|
ctx->state = STATE_ROOT;
|
||
|
/* else, stay in STATE_TOOLBAR state */
|
||
|
break;
|
||
|
case STATE_POPUPS:
|
||
|
ctx->current = ctx->current->parent;
|
||
|
ctx->state = STATE_ROOT;
|
||
|
break;
|
||
|
case STATE_MENUITEM:
|
||
|
ctx->state = STATE_MENU;
|
||
|
break;
|
||
|
case STATE_TOOLITEM:
|
||
|
ctx->state = STATE_TOOLBAR;
|
||
|
break;
|
||
|
case STATE_END:
|
||
|
g_warning ("shouldn't get any end tags at this point");
|
||
|
/* should do an error here */
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cleanup (GMarkupParseContext *context,
|
||
|
GError *error,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
ParseContext *ctx = user_data;
|
||
|
GtkMenuMerge *self = ctx->self;
|
||
|
|
||
|
ctx->current = NULL;
|
||
|
/* should also walk through the tree and get rid of nodes related to
|
||
|
* this UI file's tag */
|
||
|
|
||
|
gtk_menu_merge_remove_ui (self, ctx->merge_id);
|
||
|
}
|
||
|
|
||
|
static GMarkupParser ui_parser = {
|
||
|
start_element_handler,
|
||
|
end_element_handler,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
cleanup
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* gtk_menu_merge_add_ui_from_string:
|
||
|
* @self: a #GtkMenuMerge object
|
||
|
* @buffer: the string to parse
|
||
|
* @length: the length of @buffer (may be -1 if @buffer is nul-terminated)
|
||
|
* @error: return location for an error
|
||
|
*
|
||
|
* Parses a string containing a UI description and merge it with the current
|
||
|
* contents of @self. FIXME: describe the XML format.
|
||
|
*
|
||
|
* Return value: The merge id for the merged UI. The merge id can be used
|
||
|
* to unmerge the UI with gtk_menu_merge_remove_ui(). If an error occurred,
|
||
|
* the return value is 0.
|
||
|
*
|
||
|
* Since: 2.4
|
||
|
**/
|
||
|
guint
|
||
|
gtk_menu_merge_add_ui_from_string (GtkMenuMerge *self,
|
||
|
const gchar *buffer,
|
||
|
gsize length,
|
||
|
GError **error)
|
||
|
{
|
||
|
ParseContext ctx = { 0 };
|
||
|
GMarkupParseContext *context;
|
||
|
gboolean res = TRUE;
|
||
|
|
||
|
g_return_val_if_fail (GTK_IS_MENU_MERGE (self), FALSE);
|
||
|
g_return_val_if_fail (buffer != NULL, FALSE);
|
||
|
|
||
|
ctx.state = STATE_START;
|
||
|
ctx.self = self;
|
||
|
ctx.current = NULL;
|
||
|
ctx.merge_id = gtk_menu_merge_next_merge_id (self);
|
||
|
|
||
|
context = g_markup_parse_context_new (&ui_parser, 0, &ctx, NULL);
|
||
|
if (length < 0)
|
||
|
length = strlen (buffer);
|
||
|
|
||
|
if (g_markup_parse_context_parse (context, buffer, length, error))
|
||
|
{
|
||
|
if (!g_markup_parse_context_end_parse (context, error))
|
||
|
res = FALSE;
|
||
|
}
|
||
|
else
|
||
|
res = FALSE;
|
||
|
|
||
|
g_markup_parse_context_free (context);
|
||
|
|
||
|
gtk_menu_merge_queue_update (self);
|
||
|
|
||
|
if (res)
|
||
|
return ctx.merge_id;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gtk_menu_merge_add_ui_from_file:
|
||
|
* @self: a #GtkMenuMerge object
|
||
|
* @filename: the name of the file to parse
|
||
|
* @error: return location for an error
|
||
|
*
|
||
|
* Parses a file containing a UI description and merge it with the current
|
||
|
* contents of @self. See gtk_menu_merge_add_ui_from_file().
|
||
|
*
|
||
|
* Return value: The merge id for the merged UI. The merge id can be used
|
||
|
* to unmerge the UI with gtk_menu_merge_remove_ui(). If an error occurred,
|
||
|
* the return value is 0.
|
||
|
*
|
||
|
* Since: 2.4
|
||
|
**/
|
||
|
guint
|
||
|
gtk_menu_merge_add_ui_from_file (GtkMenuMerge *self,
|
||
|
const gchar *filename,
|
||
|
GError **error)
|
||
|
{
|
||
|
gchar *buffer;
|
||
|
gint length;
|
||
|
guint res;
|
||
|
|
||
|
if (!g_file_get_contents (filename, &buffer, &length, error))
|
||
|
return 0;
|
||
|
|
||
|
res = gtk_menu_merge_add_ui_from_string (self, buffer, length, error);
|
||
|
g_free (buffer);
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
remove_ui (GNode *node,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
guint merge_id = GPOINTER_TO_UINT (user_data);
|
||
|
|
||
|
gtk_menu_merge_node_remove_ui_reference (NODE_INFO (node), merge_id);
|
||
|
|
||
|
return FALSE; /* continue */
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gtk_menu_merge_remove_ui:
|
||
|
* @self: a #GtkMenuMerge object
|
||
|
* @merge_id: a merge id as returned by gtk_menu_merge_add_ui_from_string()
|
||
|
*
|
||
|
* Unmerges the part of @self<!-- -->s content identified by @merge_id.
|
||
|
**/
|
||
|
void
|
||
|
gtk_menu_merge_remove_ui (GtkMenuMerge *self,
|
||
|
guint merge_id)
|
||
|
{
|
||
|
g_node_traverse (self->private_data->root_node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
|
||
|
remove_ui, GUINT_TO_POINTER (merge_id));
|
||
|
|
||
|
gtk_menu_merge_queue_update (self);
|
||
|
}
|
||
|
|
||
|
/* -------------------- Updates -------------------- */
|
||
|
|
||
|
|
||
|
static GtkAction *
|
||
|
get_action_by_name (GtkMenuMerge *merge,
|
||
|
const char *action_name)
|
||
|
{
|
||
|
GList *tmp;
|
||
|
|
||
|
if (!action_name)
|
||
|
return NULL;
|
||
|
|
||
|
/* lookup name */
|
||
|
for (tmp = merge->private_data->action_groups; tmp != NULL; tmp = tmp->next)
|
||
|
{
|
||
|
GtkActionGroup *action_group = tmp->data;
|
||
|
GtkAction *action;
|
||
|
|
||
|
action = gtk_action_group_get_action (action_group, action_name);
|
||
|
|
||
|
if (action)
|
||
|
return action;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
find_menu_position (GNode *node,
|
||
|
GtkWidget **menushell_p,
|
||
|
gint *pos_p)
|
||
|
{
|
||
|
GtkWidget *menushell;
|
||
|
gint pos;
|
||
|
|
||
|
g_return_val_if_fail (node != NULL, FALSE);
|
||
|
g_return_val_if_fail (NODE_INFO (node)->type == GTK_MENU_MERGE_MENU ||
|
||
|
NODE_INFO (node)->type == GTK_MENU_MERGE_MENU_PLACEHOLDER ||
|
||
|
NODE_INFO (node)->type == GTK_MENU_MERGE_MENUITEM ||
|
||
|
NODE_INFO (node)->type == GTK_MENU_MERGE_SEPARATOR,
|
||
|
FALSE);
|
||
|
|
||
|
/* first sibling -- look at parent */
|
||
|
if (node->prev == NULL)
|
||
|
{
|
||
|
GNode *parent;
|
||
|
|
||
|
parent = node->parent;
|
||
|
switch (NODE_INFO (parent)->type)
|
||
|
{
|
||
|
case GTK_MENU_MERGE_MENUBAR:
|
||
|
menushell = NODE_INFO (parent)->proxy;
|
||
|
pos = 0;
|
||
|
break;
|
||
|
case GTK_MENU_MERGE_MENU:
|
||
|
menushell = NODE_INFO (parent)->proxy;
|
||
|
if (GTK_IS_MENU_ITEM (menushell))
|
||
|
menushell = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menushell));
|
||
|
pos = 0;
|
||
|
break;
|
||
|
case GTK_MENU_MERGE_MENU_PLACEHOLDER:
|
||
|
menushell = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
|
||
|
g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
|
||
|
pos = g_list_index (GTK_MENU_SHELL (menushell)->children,
|
||
|
NODE_INFO (parent)->proxy) + 1;
|
||
|
break;
|
||
|
default:
|
||
|
g_warning("%s: bad parent node type %d", G_STRLOC,
|
||
|
NODE_INFO (parent)->type);
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GtkWidget *prev_child;
|
||
|
GNode *sibling;
|
||
|
|
||
|
sibling = node->prev;
|
||
|
if (NODE_INFO (sibling)->type == GTK_MENU_MERGE_MENU_PLACEHOLDER)
|
||
|
prev_child = NODE_INFO (sibling)->extra; /* second Separator */
|
||
|
else
|
||
|
prev_child = NODE_INFO (sibling)->proxy;
|
||
|
|
||
|
g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE);
|
||
|
menushell = gtk_widget_get_parent (prev_child);
|
||
|
g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
|
||
|
|
||
|
pos = g_list_index (GTK_MENU_SHELL (menushell)->children, prev_child) + 1;
|
||
|
}
|
||
|
|
||
|
if (menushell_p)
|
||
|
*menushell_p = menushell;
|
||
|
if (pos_p)
|
||
|
*pos_p = pos;
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
find_toolbar_position (GNode *node,
|
||
|
GtkWidget **toolbar_p,
|
||
|
gint *pos_p)
|
||
|
{
|
||
|
GtkWidget *toolbar;
|
||
|
gint pos;
|
||
|
|
||
|
g_return_val_if_fail (node != NULL, FALSE);
|
||
|
g_return_val_if_fail (NODE_INFO (node)->type == GTK_MENU_MERGE_TOOLBAR ||
|
||
|
NODE_INFO (node)->type == GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER ||
|
||
|
NODE_INFO (node)->type == GTK_MENU_MERGE_TOOLITEM ||
|
||
|
NODE_INFO (node)->type == GTK_MENU_MERGE_SEPARATOR,
|
||
|
FALSE);
|
||
|
|
||
|
/* first sibling -- look at parent */
|
||
|
if (node->prev == NULL)
|
||
|
{
|
||
|
GNode *parent;
|
||
|
|
||
|
parent = node->parent;
|
||
|
switch (NODE_INFO (parent)->type)
|
||
|
{
|
||
|
case GTK_MENU_MERGE_TOOLBAR:
|
||
|
toolbar = NODE_INFO (parent)->proxy;
|
||
|
pos = 0;
|
||
|
break;
|
||
|
case GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER:
|
||
|
toolbar = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
|
||
|
g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
|
||
|
pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
|
||
|
GTK_TOOL_ITEM (NODE_INFO (parent)->proxy)) + 1;
|
||
|
break;
|
||
|
default:
|
||
|
g_warning ("%s: bad parent node type %d", G_STRLOC,
|
||
|
NODE_INFO (parent)->type);
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GtkWidget *prev_child;
|
||
|
GNode *sibling;
|
||
|
|
||
|
sibling = node->prev;
|
||
|
if (NODE_INFO (sibling)->type == GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER)
|
||
|
prev_child = NODE_INFO (sibling)->extra; /* second Separator */
|
||
|
else
|
||
|
prev_child = NODE_INFO (sibling)->proxy;
|
||
|
|
||
|
g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE);
|
||
|
toolbar = gtk_widget_get_parent (prev_child);
|
||
|
g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
|
||
|
|
||
|
pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
|
||
|
GTK_TOOL_ITEM (prev_child)) + 1;
|
||
|
}
|
||
|
|
||
|
if (toolbar_p)
|
||
|
*toolbar_p = toolbar;
|
||
|
if (pos_p)
|
||
|
*pos_p = pos;
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
update_node (GtkMenuMerge *self,
|
||
|
GNode *node)
|
||
|
{
|
||
|
GtkMenuMergeNode *info;
|
||
|
GNode *child;
|
||
|
GtkAction *action;
|
||
|
#ifdef DEBUG_MENU_MERGE
|
||
|
GList *tmp;
|
||
|
#endif
|
||
|
|
||
|
g_return_if_fail (node != NULL);
|
||
|
g_return_if_fail (NODE_INFO (node) != NULL);
|
||
|
|
||
|
info = NODE_INFO (node);
|
||
|
|
||
|
#ifdef DEBUG_MENU_MERGE
|
||
|
g_print ("update_node name=%s dirty=%d (", info->name, info->dirty);
|
||
|
for (tmp = info->uifiles; tmp != NULL; tmp = tmp->next)
|
||
|
{
|
||
|
NodeUIReference *ref = tmp->data;
|
||
|
g_print("%s:%u", g_quark_to_string (ref->action_quark), ref->merge_id);
|
||
|
if (tmp->next)
|
||
|
g_print (", ");
|
||
|
}
|
||
|
g_print (")\n");
|
||
|
#endif
|
||
|
|
||
|
if (NODE_INFO (node)->dirty)
|
||
|
{
|
||
|
const gchar *action_name;
|
||
|
NodeUIReference *ref;
|
||
|
|
||
|
if (info->uifiles == NULL) {
|
||
|
/* We may need to remove this node.
|
||
|
* This must be done in post order
|
||
|
*/
|
||
|
goto recurse_children;
|
||
|
}
|
||
|
|
||
|
ref = info->uifiles->data;
|
||
|
action_name = g_quark_to_string (ref->action_quark);
|
||
|
action = get_action_by_name (self, action_name);
|
||
|
|
||
|
NODE_INFO (node)->dirty = FALSE;
|
||
|
|
||
|
/* Check if the node doesn't have an action and must have an action */
|
||
|
if (action == NULL &&
|
||
|
info->type != GTK_MENU_MERGE_MENUBAR &&
|
||
|
info->type != GTK_MENU_MERGE_TOOLBAR &&
|
||
|
info->type != GTK_MENU_MERGE_SEPARATOR &&
|
||
|
info->type != GTK_MENU_MERGE_MENU_PLACEHOLDER &&
|
||
|
info->type != GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER)
|
||
|
{
|
||
|
/* FIXME: Should we warn here? */
|
||
|
goto recurse_children;
|
||
|
}
|
||
|
|
||
|
/* If the widget already has a proxy and the action hasn't changed, then
|
||
|
* we don't have to do anything.
|
||
|
*/
|
||
|
if (info->proxy != NULL &&
|
||
|
action == info->action)
|
||
|
{
|
||
|
goto recurse_children;
|
||
|
}
|
||
|
|
||
|
if (info->action)
|
||
|
g_object_unref (info->action);
|
||
|
info->action = action;
|
||
|
if (info->action)
|
||
|
g_object_ref (info->action);
|
||
|
|
||
|
switch (info->type)
|
||
|
{
|
||
|
case GTK_MENU_MERGE_MENUBAR:
|
||
|
if (info->proxy == NULL)
|
||
|
{
|
||
|
info->proxy = gtk_menu_bar_new ();
|
||
|
gtk_widget_show (info->proxy);
|
||
|
g_signal_emit (self, merge_signals[ADD_WIDGET], 0, info->proxy);
|
||
|
}
|
||
|
break;
|
||
|
case GTK_MENU_MERGE_MENU:
|
||
|
if (NODE_INFO (node->parent)->type == GTK_MENU_MERGE_POPUPS)
|
||
|
{
|
||
|
if (info->proxy == NULL)
|
||
|
{
|
||
|
GtkWidget *menu;
|
||
|
menu = gtk_menu_new ();
|
||
|
gtk_menu_set_accel_group (GTK_MENU (menu), self->private_data->accel_group);
|
||
|
info->proxy = menu;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GtkWidget *prev_submenu = NULL;
|
||
|
/* remove the proxy if it is of the wrong type ... */
|
||
|
if (info->proxy && G_OBJECT_TYPE (info->proxy) !=
|
||
|
GTK_ACTION_GET_CLASS (info->action)->menu_item_type)
|
||
|
{
|
||
|
prev_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
|
||
|
if (prev_submenu)
|
||
|
{
|
||
|
g_object_ref (prev_submenu);
|
||
|
gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy),NULL);
|
||
|
}
|
||
|
gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
|
||
|
info->proxy);
|
||
|
info->proxy = NULL;
|
||
|
}
|
||
|
/* create proxy if needed ... */
|
||
|
if (info->proxy == NULL)
|
||
|
{
|
||
|
GtkWidget *menushell;
|
||
|
gint pos;
|
||
|
|
||
|
if (find_menu_position (node, &menushell, &pos))
|
||
|
{
|
||
|
GtkWidget *menu;
|
||
|
info->proxy = gtk_action_create_menu_item (info->action);
|
||
|
menu = gtk_menu_new ();
|
||
|
gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), menu);
|
||
|
gtk_menu_set_accel_group (GTK_MENU (menu), self->private_data->accel_group);
|
||
|
gtk_menu_shell_insert (GTK_MENU_SHELL (menushell), info->proxy, pos);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
gtk_action_connect_proxy (info->action, info->proxy);
|
||
|
}
|
||
|
if (prev_submenu)
|
||
|
{
|
||
|
gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy),
|
||
|
prev_submenu);
|
||
|
g_object_unref (prev_submenu);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case GTK_MENU_MERGE_UNDECIDED:
|
||
|
g_warning ("found 'undecided node!");
|
||
|
break;
|
||
|
case GTK_MENU_MERGE_ROOT:
|
||
|
break;
|
||
|
case GTK_MENU_MERGE_TOOLBAR:
|
||
|
if (info->proxy == NULL)
|
||
|
{
|
||
|
info->proxy = gtk_toolbar_new ();
|
||
|
gtk_widget_show (info->proxy);
|
||
|
g_signal_emit (self, merge_signals[ADD_WIDGET], 0, info->proxy);
|
||
|
}
|
||
|
break;
|
||
|
case GTK_MENU_MERGE_MENU_PLACEHOLDER:
|
||
|
/* create menu items for placeholders if necessary ... */
|
||
|
if (!GTK_IS_SEPARATOR_MENU_ITEM (info->proxy) ||
|
||
|
!GTK_IS_SEPARATOR_MENU_ITEM (info->extra))
|
||
|
{
|
||
|
if (info->proxy)
|
||
|
gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
|
||
|
info->proxy);
|
||
|
if (info->extra)
|
||
|
gtk_container_remove (GTK_CONTAINER (info->extra->parent),
|
||
|
info->extra);
|
||
|
info->proxy = NULL;
|
||
|
info->extra = NULL;
|
||
|
}
|
||
|
if (info->proxy == NULL)
|
||
|
{
|
||
|
GtkWidget *menushell;
|
||
|
gint pos;
|
||
|
|
||
|
if (find_menu_position (node, &menushell, &pos))
|
||
|
{
|
||
|
NODE_INFO (node)->proxy = gtk_separator_menu_item_new ();
|
||
|
gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
|
||
|
NODE_INFO (node)->proxy, pos);
|
||
|
|
||
|
NODE_INFO (node)->extra = gtk_separator_menu_item_new ();
|
||
|
gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
|
||
|
NODE_INFO (node)->extra, pos+1);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER:
|
||
|
/* create toolbar items for placeholders if necessary ... */
|
||
|
if (!GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy) ||
|
||
|
!GTK_IS_SEPARATOR_TOOL_ITEM (info->extra))
|
||
|
{
|
||
|
if (info->proxy)
|
||
|
gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
|
||
|
info->proxy);
|
||
|
if (info->extra)
|
||
|
gtk_container_remove (GTK_CONTAINER (info->extra->parent),
|
||
|
info->extra);
|
||
|
info->proxy = NULL;
|
||
|
info->extra = NULL;
|
||
|
}
|
||
|
if (info->proxy == NULL)
|
||
|
{
|
||
|
GtkWidget *toolbar;
|
||
|
gint pos;
|
||
|
|
||
|
if (find_toolbar_position (node, &toolbar, &pos))
|
||
|
{
|
||
|
GtkToolItem *item;
|
||
|
|
||
|
item = gtk_separator_tool_item_new ();
|
||
|
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
|
||
|
NODE_INFO(node)->proxy = GTK_WIDGET (item);
|
||
|
|
||
|
item = gtk_separator_tool_item_new ();
|
||
|
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos+1);
|
||
|
NODE_INFO (node)->extra = GTK_WIDGET (item);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case GTK_MENU_MERGE_POPUPS:
|
||
|
break;
|
||
|
case GTK_MENU_MERGE_MENUITEM:
|
||
|
/* remove the proxy if it is of the wrong type ... */
|
||
|
if (info->proxy && G_OBJECT_TYPE (info->proxy) !=
|
||
|
GTK_ACTION_GET_CLASS (info->action)->menu_item_type)
|
||
|
{
|
||
|
gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
|
||
|
info->proxy);
|
||
|
info->proxy = NULL;
|
||
|
}
|
||
|
/* create proxy if needed ... */
|
||
|
if (info->proxy == NULL)
|
||
|
{
|
||
|
GtkWidget *menushell;
|
||
|
gint pos;
|
||
|
|
||
|
if (find_menu_position (node, &menushell, &pos))
|
||
|
{
|
||
|
info->proxy = gtk_action_create_menu_item (info->action);
|
||
|
|
||
|
gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
|
||
|
info->proxy, pos);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
|
||
|
gtk_action_connect_proxy (info->action, info->proxy);
|
||
|
}
|
||
|
break;
|
||
|
case GTK_MENU_MERGE_TOOLITEM:
|
||
|
/* remove the proxy if it is of the wrong type ... */
|
||
|
if (info->proxy && G_OBJECT_TYPE (info->proxy) !=
|
||
|
GTK_ACTION_GET_CLASS (info->action)->toolbar_item_type)
|
||
|
{
|
||
|
gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
|
||
|
info->proxy);
|
||
|
info->proxy = NULL;
|
||
|
}
|
||
|
/* create proxy if needed ... */
|
||
|
if (info->proxy == NULL)
|
||
|
{
|
||
|
GtkWidget *toolbar;
|
||
|
gint pos;
|
||
|
|
||
|
if (find_toolbar_position (node, &toolbar, &pos))
|
||
|
{
|
||
|
info->proxy = gtk_action_create_tool_item (info->action);
|
||
|
|
||
|
gtk_toolbar_insert (GTK_TOOLBAR (toolbar),
|
||
|
GTK_TOOL_ITEM (info->proxy), pos);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
gtk_action_connect_proxy (info->action, info->proxy);
|
||
|
}
|
||
|
break;
|
||
|
case GTK_MENU_MERGE_SEPARATOR:
|
||
|
if (NODE_INFO (node->parent)->type == GTK_MENU_MERGE_TOOLBAR ||
|
||
|
NODE_INFO (node->parent)->type == GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER)
|
||
|
{
|
||
|
GtkWidget *toolbar;
|
||
|
gint pos;
|
||
|
|
||
|
if (GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy))
|
||
|
{
|
||
|
gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
|
||
|
info->proxy);
|
||
|
info->proxy = NULL;
|
||
|
}
|
||
|
|
||
|
if (find_toolbar_position (node, &toolbar, &pos))
|
||
|
{
|
||
|
GtkToolItem *item = gtk_separator_tool_item_new ();
|
||
|
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
|
||
|
info->proxy = GTK_WIDGET (item);
|
||
|
gtk_widget_show (info->proxy);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GtkWidget *menushell;
|
||
|
gint pos;
|
||
|
|
||
|
if (GTK_IS_SEPARATOR_MENU_ITEM (info->proxy))
|
||
|
{
|
||
|
gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
|
||
|
info->proxy);
|
||
|
info->proxy = NULL;
|
||
|
}
|
||
|
|
||
|
if (find_menu_position (node, &menushell, &pos))
|
||
|
{
|
||
|
info->proxy = gtk_separator_menu_item_new ();
|
||
|
gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
|
||
|
info->proxy, pos);
|
||
|
gtk_widget_show (info->proxy);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* if this node has a widget, but it is the wrong type, remove it */
|
||
|
}
|
||
|
|
||
|
recurse_children:
|
||
|
/* process children */
|
||
|
child = node->children;
|
||
|
while (child)
|
||
|
{
|
||
|
GNode *current;
|
||
|
|
||
|
current = child;
|
||
|
child = current->next;
|
||
|
update_node (self, current);
|
||
|
}
|
||
|
|
||
|
/* handle cleanup of dead nodes */
|
||
|
if (node->children == NULL && NODE_INFO (node)->uifiles == NULL)
|
||
|
{
|
||
|
if (NODE_INFO (node)->proxy)
|
||
|
gtk_widget_destroy (NODE_INFO (node)->proxy);
|
||
|
if ((NODE_INFO (node)->type == GTK_MENU_MERGE_MENU_PLACEHOLDER ||
|
||
|
NODE_INFO (node)->type == GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER) &&
|
||
|
NODE_INFO (node)->extra)
|
||
|
gtk_widget_destroy (NODE_INFO (node)->extra);
|
||
|
g_chunk_free (NODE_INFO (node), merge_node_chunk);
|
||
|
g_node_destroy (node);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
do_updates (GtkMenuMerge *self)
|
||
|
{
|
||
|
/* this function needs to check through the tree for dirty nodes.
|
||
|
* For such nodes, it needs to do the following:
|
||
|
*
|
||
|
* 1) check if they are referenced by any loaded UI files anymore.
|
||
|
* In which case, the proxy widget should be destroyed, unless
|
||
|
* there are any subnodes.
|
||
|
*
|
||
|
* 2) lookup the action for this node again. If it is different to
|
||
|
* the current one (or if no previous action has been looked up),
|
||
|
* the proxy is reconnected to the new action (or a new proxy widget
|
||
|
* is created and added to the parent container).
|
||
|
*/
|
||
|
|
||
|
update_node (self, self->private_data->root_node);
|
||
|
|
||
|
self->private_data->update_tag = 0;
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gtk_menu_merge_queue_update (GtkMenuMerge *self)
|
||
|
{
|
||
|
if (self->private_data->update_tag != 0)
|
||
|
return;
|
||
|
|
||
|
self->private_data->update_tag = g_idle_add ((GSourceFunc)do_updates, self);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gtk_menu_merge_ensure_update (GtkMenuMerge *self)
|
||
|
{
|
||
|
if (self->private_data->update_tag != 0)
|
||
|
{
|
||
|
g_source_remove (self->private_data->update_tag);
|
||
|
do_updates (self);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
dirty_traverse_func (GNode *node,
|
||
|
gpointer data)
|
||
|
{
|
||
|
NODE_INFO (node)->dirty = TRUE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gtk_menu_merge_dirty_all (GtkMenuMerge *self)
|
||
|
{
|
||
|
g_node_traverse (self->private_data->root_node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
|
||
|
dirty_traverse_func, NULL);
|
||
|
gtk_menu_merge_queue_update (self);
|
||
|
}
|
||
|
|
||
|
static const gchar *open_tag_format[] = {
|
||
|
"%*s<UNDECIDED>\n",
|
||
|
"%*s<Root>\n",
|
||
|
"%*s<menu name=\"%s\">\n",
|
||
|
"%*s<submenu name=\"%s\" verb=\"%s\">\n",
|
||
|
"%*s<dockitem name=\"%s\">\n",
|
||
|
"%*s<placeholder name=\"%s\">\n",
|
||
|
"%*s<placeholder name=\"%s\">\n",
|
||
|
"%*s<popups>\n",
|
||
|
"%*s<menuitem name=\"%s\" verb=\"%s\"/>\n",
|
||
|
"%*s<toolitem name=\"%s\" verb=\"%s\"/>\n",
|
||
|
"%*s<separator/>\n",
|
||
|
"%*s<popup name=\"%s\">\n"
|
||
|
};
|
||
|
|
||
|
static const gchar *close_tag_format[] = {
|
||
|
"%*s</UNDECIDED>\n",
|
||
|
"%*s</Root>\n",
|
||
|
"%*s</menu>\n",
|
||
|
"%*s</submenu>\n",
|
||
|
"%*s</dockitem>\n",
|
||
|
"%*s</placeholder>\n",
|
||
|
"%*s</placeholder>\n",
|
||
|
"%*s</popups>\n",
|
||
|
"",
|
||
|
"",
|
||
|
"",
|
||
|
"%*s</popup>\n"
|
||
|
};
|
||
|
|
||
|
static void
|
||
|
print_node (GtkMenuMerge *self,
|
||
|
GNode *node,
|
||
|
gint indent_level,
|
||
|
GString *buffer)
|
||
|
{
|
||
|
GtkMenuMergeNode *mnode;
|
||
|
GNode *child;
|
||
|
guint type;
|
||
|
|
||
|
mnode = node->data;
|
||
|
if (mnode->type == GTK_MENU_MERGE_MENU &&
|
||
|
NODE_INFO (node->parent)->type == GTK_MENU_MERGE_POPUPS)
|
||
|
type = GTK_MENU_MERGE_SEPARATOR + 1;
|
||
|
else
|
||
|
type = mnode->type;
|
||
|
|
||
|
g_string_append_printf (buffer, open_tag_format[type],
|
||
|
indent_level, "",
|
||
|
mnode->name,
|
||
|
g_quark_to_string (mnode->action_name));
|
||
|
|
||
|
for (child = node->children; child != NULL; child = child->next)
|
||
|
print_node (self, child, indent_level + 2, buffer);
|
||
|
|
||
|
g_string_append_printf (buffer, close_tag_format[type],
|
||
|
indent_level, "");
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gtk_menu_merge_get_ui:
|
||
|
* @self: a #GtkMenuMerge
|
||
|
*
|
||
|
* Creates an XML representation of the merged ui.
|
||
|
*
|
||
|
* Return value: A newly allocated string containing an XML representation of
|
||
|
* the merged ui.
|
||
|
**/
|
||
|
gchar*
|
||
|
gtk_menu_merge_get_ui (GtkMenuMerge *self)
|
||
|
{
|
||
|
GString *buffer;
|
||
|
|
||
|
buffer = g_string_new (NULL);
|
||
|
|
||
|
gtk_menu_merge_ensure_update (self);
|
||
|
|
||
|
print_node (self, self->private_data->root_node, 0, buffer);
|
||
|
|
||
|
return g_string_free (buffer, FALSE);
|
||
|
}
|