Merging across Plug/Socket



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]