Merging across Plug/Socket
- From: Matthias Clasen <maclas gmx de>
- To: gtk-devel-list gnome org
- Subject: Merging across Plug/Socket
- Date: 22 Sep 2003 00:16:08 +0200
Over the last few days, I played around with getting GtkUIManager ready
for cross-process UI merging. I just delivered the necessary changes to
CVS. The demo code I have for this is currently in the form of a 500
line patch to testmerge.c, which doesn't actually use IPC, but contains
all the other bits (including serializing/deserializing actions to XML).
There are only 4 functions which will have to be replaced by a suitable
IPC mechanism (for GtkPlug/GtkSocket, setting properties and sending
client messages should be sufficient). Since I don't know how long it
will take me to clean this up and rewrite it as proper GtkPlug/GtkSocket
subclasses, I'll just attach the patch here for the benefit of whoever
decides to work on this.
Matthias
Index: tests/testmerge.c
===================================================================
RCS file: /cvs/gnome/gtk+/tests/testmerge.c,v
retrieving revision 1.12
diff -u -p -r1.12 testmerge.c
--- tests/testmerge.c 17 Sep 2003 23:58:25 -0000 1.12
+++ tests/testmerge.c 21 Sep 2003 22:05:43 -0000
@@ -1,4 +1,5 @@
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <gtk/gtk.h>
@@ -442,11 +443,524 @@ activate_path (GtkWidget *button,
g_message ("no action found");
}
+
+typedef struct _Socket Socket;
+typedef struct _Plug Plug;
+
+struct _Socket {
+ GtkUIManager *merge;
+ GtkActionGroup *actions;
+ guint merge_id;
+
+ Plug *plug;
+};
+
+struct _Plug {
+ GtkUIManager *merge;
+
+ Socket *socket;
+};
+
+/*
+ * Plug<->Socket interface.
+ * These have to be replaced by an IPC mechanism to make
+ * cross-process merging work. For X11, I would envision
+ * socket_update_ui and socket_update_actions to be realized
+ * by having the plug set Properties, while
+ * socket_set_action_state and plug_activate_action would
+ * probably be ClientMessages.
+ */
+static void socket_update_ui (Socket *socket,
+ const gchar *ui);
+static void socket_update_actions (Socket *socket,
+ const gchar *actions);
+static void socket_set_action_state (Socket *socket,
+ guint32 id,
+ gboolean active);
+static void plug_activate_action (Plug *plug,
+ guint32 id);
+
+
+
+static GtkAction *
+plug_get_action_by_id (Plug *plug,
+ guint32 id)
+{
+ GList *tmp;
+ const gchar *name = g_quark_to_string ((GQuark)id);
+
+ if (!name)
+ return NULL;
+
+ /* lookup name */
+ for (tmp = gtk_ui_manager_get_action_groups (plug->merge);
+ tmp != NULL;
+ tmp = tmp->next)
+ {
+ GtkActionGroup *actions = tmp->data;
+ GtkAction *action = gtk_action_group_get_action (actions, name);
+
+ if (action)
+ return action;
+ }
+
+ return NULL;
+}
+
+static void
+serialize_action (GtkAction *action,
+ GString *buffer)
+{
+ const gchar *name;
+ const gchar *label;
+ const gchar *short_label;
+ const gchar *tooltip;
+ const gchar *stock_id;
+ gboolean is_important;
+ gboolean sensitive;
+ gboolean visible;
+ gboolean active;
+
+ g_object_get (G_OBJECT (action),
+ "name", &name,
+ "label", &label,
+ "short_label", &short_label,
+ "tooltip", &tooltip,
+ "stock_id", &stock_id,
+ "is_important", &is_important,
+ "sensitive", &sensitive,
+ "visible", &visible,
+ NULL);
+
+ if (GTK_IS_RADIO_ACTION (action))
+ g_string_append (buffer, "<radioaction");
+ else if (GTK_IS_TOGGLE_ACTION (action))
+ g_string_append (buffer, "<toggleaction");
+ else
+ g_string_append (buffer, "<action");
+
+ g_string_append_printf (buffer, " id=\"%#lx\" name=\"%s\"",
+ (glong)g_quark_from_string (name),
+ name);
+
+ if (label)
+ g_string_append_printf (buffer, " label=\"%s\"", label);
+ if (short_label)
+ g_string_append_printf (buffer, " short_label=\"%s\"", short_label);
+ if (tooltip)
+ g_string_append_printf (buffer, " tooltip=\"%s\"", tooltip);
+ if (stock_id)
+ g_string_append_printf (buffer, " stock_id=\"%s\"", stock_id);
+ if (is_important)
+ g_string_append (buffer, " is_important=\"true\"");
+ if (!sensitive)
+ g_string_append (buffer, " sensitive=\"false\"");
+ if (!visible)
+ g_string_append (buffer, " visible=\"false\"");
+
+ if (GTK_IS_TOGGLE_ACTION (action))
+ {
+ active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+ g_string_append_printf (buffer, " active=\"%s\"",
+ active ? "true" : "false");
+ }
+
+ g_string_append (buffer, " />\n");
+}
+
+static void plug_sync_action (GtkAction *action,
+ GParamSpec *pspec,
+ Plug *plug);
+
+static void plug_sync_action_state (GtkAction *action,
+ Plug *plug);
+
+static void
+plug_serialize_and_connect_actions (Plug *plug,
+ GString *buffer)
+{
+ GSList *seen = NULL;
+ GList *tmp, *tmp2;
+ GQuark id;
+ GtkActionGroup *actions;
+ GtkAction *action;
+ const gchar *name;
+
+ g_string_append (buffer, "<actions>\n");
+ for (tmp = gtk_ui_manager_get_action_groups (plug->merge);
+ tmp != NULL;
+ tmp = tmp->next)
+ {
+ actions = tmp->data;
+ for (tmp2 = gtk_action_group_list_actions (actions);
+ tmp2 != NULL;
+ tmp2 = tmp2->next)
+ {
+ action = tmp2->data;
+ name = gtk_action_get_name (action);
+ id = g_quark_from_string (name);
+
+ if (!g_slist_find (seen, GUINT_TO_POINTER (id)))
+ {
+ seen = g_slist_prepend (seen, GUINT_TO_POINTER (id));
+
+ g_signal_connect (action, "notify::label",
+ G_CALLBACK (plug_sync_action), plug);
+ g_signal_connect (action, "notify::stock_id",
+ G_CALLBACK (plug_sync_action), plug);
+ g_signal_connect (action, "notify::short_label",
+ G_CALLBACK (plug_sync_action), plug);
+ g_signal_connect (action, "notify::is_important",
+ G_CALLBACK (plug_sync_action), plug);
+ g_signal_connect (action, "notify::sensitive",
+ G_CALLBACK (plug_sync_action), plug);
+ g_signal_connect (action, "notify::visible",
+ G_CALLBACK (plug_sync_action), plug);
+
+ if (GTK_IS_TOGGLE_ACTION (action))
+ g_signal_connect (action, "activate",
+ G_CALLBACK (plug_sync_action_state), plug);
+
+ serialize_action (action, buffer);
+ }
+ }
+ }
+
+ g_slist_free (seen);
+ g_string_append (buffer, "</actions>\n");
+}
+
+static void
+plug_activate_action (Plug *plug,
+ guint32 id)
+{
+ GtkAction *action;
+
+ action = plug_get_action_by_id (plug, id);
+
+ gtk_action_activate (action);
+}
+
+static void
+plug_update_ui (Plug *plug)
+{
+ gchar *ui = gtk_ui_manager_get_ui (plug->merge);
+
+ g_message ("plug->socket: update_ui: %s", ui);
+
+ socket_update_ui (plug->socket, ui);
+
+ g_free (ui);
+}
+
+static void
+plug_update_actions (Plug *plug)
+{
+ GString *buffer;
+ gchar *actions;
+
+ buffer = g_string_new (NULL);
+ plug_serialize_and_connect_actions (plug, buffer);
+ actions = g_string_free (buffer, FALSE);
+
+ g_message ("plug->socket: update_actions: %s", actions);
+
+ socket_update_actions (plug->socket, actions);
+
+ g_free (actions);
+}
+
+static void
+plug_sync_action (GtkAction *action,
+ GParamSpec *pspec,
+ Plug *plug)
+{
+ GString *buffer;
+ gchar *text;
+
+ buffer = g_string_new (NULL);
+ serialize_action (action, buffer);
+ text = g_string_free (buffer, FALSE);
+
+ g_message ("plug->socket: sync_action: %s", text);
+
+ socket_update_actions (plug->socket, text);
+
+ g_free (text);
+}
+
+static void
+plug_sync_action_state (GtkAction *action,
+ Plug *plug)
+{
+ const gchar *name = gtk_action_get_name (action);
+ gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+ guint32 id = g_quark_from_string (name);
+
+ g_message ("plug->socket: sync_action_state: %#lx (%s) %d",
+ (glong)id, name, active);
+
+ socket_set_action_state (plug->socket, id, active);
+}
+
+
+static void
+socket_activate_proxy (GtkAction *proxy,
+ Socket *socket)
+{
+ guint id;
+ const gchar *name;
+
+ id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (proxy), "action-id"));
+ if (!id)
+ return;
+
+ name = gtk_action_get_name (proxy);
+
+ g_message ("socket->plug: activate_action %#lx (%s)", (glong)id, name);
+
+ plug_activate_action (socket->plug, id);
+}
+
+
+typedef struct _ParseContext ParseContext;
+struct _ParseContext
+{
+ Socket *socket;
+
+ gboolean update_state;
+};
+
+static void
+start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ GType type;
+ const gchar *name = NULL;
+ const gchar *label = NULL;
+ const gchar *short_label = NULL;
+ const gchar *tooltip = NULL;
+ const gchar *stock_id = NULL;
+ gboolean is_important = FALSE;
+ gboolean visible = TRUE;
+ gboolean sensitive = TRUE;
+ gboolean active = FALSE;
+ guint32 id = 0;
+ gint i;
+
+ ParseContext *ctx = user_data;
+ GtkAction *proxy;
+
+ if (!strcmp (element_name, "actions"))
+ {
+ ctx->update_state = TRUE;
+
+ return;
+ }
+ else if (!strcmp (element_name, "action"))
+ type = GTK_TYPE_ACTION;
+ else if (!strcmp (element_name, "toggleaction"))
+ type = GTK_TYPE_TOGGLE_ACTION;
+ else if (!strcmp (element_name, "radioaction"))
+ type = GTK_TYPE_RADIO_ACTION;
+ else
+ {
+ g_warning ("Unexpected element %s", element_name);
+
+ return;
+ }
+
+ for (i = 0; attribute_names[i] != NULL; i++)
+ {
+ if (!strcmp (attribute_names[i], "id"))
+ id = strtoul (attribute_values[i], NULL, 0);
+ else if (!strcmp (attribute_names[i], "name"))
+ name = attribute_values[i];
+ else if (!strcmp (attribute_names[i], "label"))
+ label = attribute_values[i];
+ else if (!strcmp (attribute_names[i], "short_label"))
+ short_label = attribute_values[i];
+ else if (!strcmp (attribute_names[i], "tooltip"))
+ tooltip = attribute_values[i];
+ else if (!strcmp (attribute_names[i], "stock_id"))
+ stock_id = attribute_values[i];
+ else if (!strcmp (attribute_names[i], "is_important"))
+ is_important = !strcmp (attribute_values[i], "true");
+ else if (!strcmp (attribute_names[i], "visible"))
+ visible = !strcmp (attribute_values[i], "true");
+ else if (!strcmp (attribute_names[i], "sensitive"))
+ sensitive = !strcmp (attribute_values[i], "true");
+ else if (!strcmp (attribute_names[i], "active"))
+ active = !strcmp (attribute_values[i], "true");
+ }
+
+ if (!name || !label)
+ {
+ g_warning ("Required attribute missing");
+
+ return;
+ }
+
+ proxy = gtk_action_group_get_action (ctx->socket->actions, name);
+ if (proxy && G_OBJECT_TYPE (proxy) != type)
+ {
+ gtk_action_group_remove_action (ctx->socket->actions, proxy);
+ proxy = NULL;
+ }
+
+ if (proxy == NULL)
+ {
+ if (type == GTK_TYPE_RADIO_ACTION)
+ proxy = g_object_new (GTK_TYPE_TOGGLE_ACTION,
+ "name", name,
+ "draw_as_radio", TRUE,
+ NULL);
+ else
+ proxy = g_object_new (type,
+ "name", name,
+ NULL);
+
+ gtk_action_group_add_action (ctx->socket->actions, proxy);
+ }
+
+ g_object_set (G_OBJECT (proxy),
+ "label", label,
+ "short_label", short_label,
+ "tooltip", tooltip,
+ "stock_id", stock_id,
+ "is_important", is_important,
+ "visible", visible,
+ "sensitive", sensitive,
+ NULL);
+
+ if (ctx->update_state)
+ {
+ if (GTK_IS_TOGGLE_ACTION (proxy))
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (proxy), active);
+
+ g_signal_connect_data (proxy, "activate",
+ G_CALLBACK (socket_activate_proxy),
+ ctx->socket, NULL, 0);
+ }
+
+ g_object_set_data (G_OBJECT (proxy), "action-id", GUINT_TO_POINTER (id));
+}
+
+static GMarkupParser action_parser = {
+ start_element_handler,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static void
+socket_update_actions (Socket *socket,
+ const gchar *actions)
+{
+ ParseContext ctx = { 0 };
+ GMarkupParseContext *context;
+
+ ctx.socket = socket;
+ ctx.update_state = FALSE;
+ if (socket->actions == NULL)
+ {
+ socket->actions = gtk_action_group_new ("proxy-actions");
+ gtk_ui_manager_insert_action_group (socket->merge, socket->actions, 0);
+ }
+
+ context = g_markup_parse_context_new (&action_parser, 0, &ctx, NULL);
+
+ if (!g_markup_parse_context_parse (context, actions, -1, NULL))
+ {
+ g_object_unref (socket->actions);
+ socket->actions = NULL;
+ }
+
+ g_markup_parse_context_free (context);
+}
+
+static void
+socket_update_ui (Socket *socket,
+ const gchar *ui)
+{
+ if (socket->merge_id > 0)
+ gtk_ui_manager_remove_ui (socket->merge, socket->merge_id);
+
+ socket->merge_id =
+ gtk_ui_manager_add_ui_from_string (socket->merge, ui, -1, NULL);
+}
+
+static GtkAction *
+socket_get_proxy_by_id (Socket *socket,
+ guint32 id)
+{
+ GList *tmp, *tmp2;
+
+ for (tmp = gtk_ui_manager_get_action_groups (socket->merge);
+ tmp != NULL;
+ tmp = tmp->next)
+ {
+ GtkActionGroup *actions = tmp->data;
+
+ for (tmp2 = gtk_action_group_list_actions (actions);
+ tmp2 != NULL;
+ tmp2 = tmp2->next)
+ {
+ GtkAction *action = tmp2->data;
+
+ if (id == GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (action),
+ "action-id")))
+ return action;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+socket_set_action_state (Socket *socket,
+ guint32 id,
+ gboolean active)
+{
+ GtkAction *proxy = socket_get_proxy_by_id (socket, id);
+
+ g_object_set_data (G_OBJECT (proxy), "action-id", GINT_TO_POINTER (0));
+
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (proxy), active);
+
+ g_object_set_data (G_OBJECT (proxy), "action-id", GINT_TO_POINTER (id));
+}
+
+static void
+ui_connect (Plug *plug,
+ Socket *socket)
+{
+ gtk_ui_manager_ensure_update (plug->merge);
+
+ plug->socket = socket;
+ socket->plug = plug;
+
+ plug_update_actions (plug);
+ plug_update_ui (plug);
+
+ g_signal_connect_swapped (plug->merge, "actions_changed",
+ G_CALLBACK (plug_update_actions), plug);
+
+ g_signal_connect_swapped (plug->merge, "notify::ui",
+ G_CALLBACK (plug_update_ui), plug);
+}
+
int
main (int argc, char **argv)
{
GtkActionGroup *action_group;
- GtkUIManager *merge;
+ Plug *plug;
+ Socket *socket;
GtkWidget *window, *table, *frame, *menu_box, *vbox, *view;
GtkWidget *button, *area;
gint i;
@@ -474,7 +988,6 @@ main (int argc, char **argv)
gtk_table_set_col_spacings (GTK_TABLE (table), 2);
gtk_container_set_border_width (GTK_CONTAINER (table), 2);
gtk_container_add (GTK_CONTAINER (window), table);
-
frame = gtk_frame_new ("Menus and Toolbars");
gtk_table_attach (GTK_TABLE (table), frame, 0,2, 1,2,
GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
@@ -494,23 +1007,25 @@ main (int argc, char **argv)
gtk_action_connect_proxy (gtk_action_group_get_action (action_group, "AboutAction"),
button);
gtk_widget_show (button);
- merge = gtk_ui_manager_new ();
button = gtk_check_button_new ();
gtk_box_pack_end (GTK_BOX (menu_box), button, FALSE, FALSE, 0);
gtk_action_connect_proxy (gtk_action_group_get_action (action_group, "BoldAction"),
button);
gtk_widget_show (button);
- merge = gtk_ui_manager_new ();
+ plug = g_new0 (Plug, 1);
+ socket = g_new0 (Socket, 1);
+ plug->merge = gtk_ui_manager_new ();
+ socket->merge = gtk_ui_manager_new ();
g_signal_connect (area, "button_press_event",
- G_CALLBACK (area_press), merge);
+ G_CALLBACK (area_press), socket->merge);
- gtk_ui_manager_insert_action_group (merge, action_group, 0);
- g_signal_connect (merge, "add_widget", G_CALLBACK (add_widget), menu_box);
+ gtk_ui_manager_insert_action_group (plug->merge, action_group, 0);
+ g_signal_connect (socket->merge, "add_widget", G_CALLBACK (add_widget), menu_box);
gtk_window_add_accel_group (GTK_WINDOW (window),
- gtk_ui_manager_get_accel_group (merge));
+ gtk_ui_manager_get_accel_group (socket->merge));
frame = gtk_frame_new ("UI Files");
gtk_table_attach (GTK_TABLE (table), frame, 0,1, 0,1,
@@ -524,38 +1039,43 @@ main (int argc, char **argv)
{
button = gtk_check_button_new_with_label (merge_ids[i].filename);
g_object_set_data (G_OBJECT (button), "mergenum", GINT_TO_POINTER (i));
- g_signal_connect (button, "toggled", G_CALLBACK (toggle_merge), merge);
+ g_signal_connect (button, "toggled", G_CALLBACK (toggle_merge), plug->merge);
gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
}
button = gtk_check_button_new_with_label ("Tearoffs");
- g_signal_connect (button, "clicked", G_CALLBACK (toggle_tearoffs), merge);
+ g_signal_connect (button, "clicked", G_CALLBACK (toggle_tearoffs), socket->merge);
gtk_box_pack_end (GTK_BOX (vbox), button, FALSE, FALSE, 0);
button = gtk_check_button_new_with_label ("Dynamic");
- g_signal_connect (button, "clicked", G_CALLBACK (toggle_dynamic), merge);
+ g_signal_connect (button, "clicked", G_CALLBACK (toggle_dynamic), plug->merge);
gtk_box_pack_end (GTK_BOX (vbox), button, FALSE, FALSE, 0);
button = gtk_button_new_with_label ("Activate path");
- g_signal_connect (button, "clicked", G_CALLBACK (activate_path), merge);
+ g_signal_connect (button, "clicked", G_CALLBACK (activate_path), socket->merge);
gtk_box_pack_end (GTK_BOX (vbox), button, FALSE, FALSE, 0);
button = gtk_button_new_with_label ("Dump Tree");
- g_signal_connect (button, "clicked", G_CALLBACK (dump_tree), merge);
+ g_signal_connect (button, "clicked", G_CALLBACK (dump_tree), socket->merge);
gtk_box_pack_end (GTK_BOX (vbox), button, FALSE, FALSE, 0);
button = gtk_button_new_with_label ("Dump Accels");
g_signal_connect (button, "clicked", G_CALLBACK (dump_accels), NULL);
gtk_box_pack_end (GTK_BOX (vbox), button, FALSE, FALSE, 0);
- view = create_tree_view (merge);
+ view = create_tree_view (plug->merge);
gtk_table_attach (GTK_TABLE (table), view, 1,2, 0,1,
GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 0);
+ ui_connect (plug, socket);
+
gtk_widget_show_all (window);
gtk_main ();
+ g_object_unref (action_group);
+ g_object_unref (plug->merge);
+ g_object_unref (socket->merge);
return 0;
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]