2007-01-27  Tom Tromey  <tromey@redhat.com>

	* src/notification.c (in_menu, menu, menu_list, click_output): New
	globals.
	(struct menu_entry): New type.
	(free_menu_list): New function.
	(menu_item_callback): Likewise.
	(maybe_output_click_string): Likewise.
	(build_menu): Likewise.
	(pop_up_menu): Likewise.
	(zenity_notification_handle_stdin): Added new commands "menu",
	"menuitem", "endmenu", "click", and "blink".

Index: src/notification.c
===================================================================
--- src/notification.c	(revision 1153)
+++ src/notification.c	(working copy)
@@ -41,6 +41,19 @@
 static const gchar *icon_stock;
 static gint icon_size;
 
+static gboolean in_menu;
+static GtkMenu *menu;
+static GSList *menu_list;
+
+static gchar *click_output;
+
+struct menu_entry {
+  /* Title of a menu entry.  */
+  gchar *title;
+  /* String to print when entry is selected.  */
+  gchar *output;
+};
+
 static void
 zenity_notification_icon_update (void)
 {
@@ -95,6 +108,70 @@
   return TRUE;
 }
 
+static void
+free_menu_list (void)
+{
+  GSList *iter;
+  for (iter = menu_list; iter != NULL; iter = g_slist_next (iter)) {
+    struct menu_entry *entry = (struct menu_entry *) iter->data;
+    g_free (entry->title);
+    g_free (entry->output);
+    g_free (entry);
+  }
+  g_slist_free (menu_list);
+  menu_list = NULL;
+}
+
+static void
+menu_item_callback (GtkMenuItem *item, gpointer data)
+{
+  gchar *output = (gchar *) data;
+  g_print ("%s\n", output);
+}
+
+static void
+maybe_output_click_string (GtkWidget *widget, gpointer ignore)
+{
+  if (click_output)
+    g_print ("%s\n", click_output);
+}
+
+static void
+build_menu (void)
+{
+  GSList *iter;
+
+  if (menu_list == NULL) {
+    /* An empty menu means that the mouse button does nothing.  */
+    return;
+  }
+
+  menu = GTK_MENU (gtk_menu_new ());
+  for (iter = menu_list; iter != NULL; iter = g_slist_next (iter)) {
+    struct menu_entry *entry = (struct menu_entry *) iter->data;
+    GtkMenuItem *item;
+
+    item = GTK_MENU_ITEM (gtk_menu_item_new_with_label (entry->title));
+    g_signal_connect (item, "activate",
+		      G_CALLBACK (menu_item_callback), entry->output);
+    gtk_widget_show (GTK_WIDGET (item));
+    gtk_menu_shell_append (GTK_MENU_SHELL (menu), GTK_WIDGET (item));
+  }
+
+  /* Note that we keep the menu list around since it owns the
+     strings.  */
+}
+
+static void
+pop_up_menu (GtkStatusIcon *icon, guint button, guint activate_time,
+	     gpointer user_data)
+{
+  if (menu == NULL)
+    return;
+  gtk_menu_popup (menu, NULL, NULL, gtk_status_icon_position_menu, icon,
+		  button, activate_time);
+}
+
 static gboolean
 zenity_notification_handle_stdin (GIOChannel *channel,
 				  GIOCondition condition,
@@ -189,12 +266,11 @@
 	  }
 
 	  g_object_unref (notif);
-	} else {
-#else
-	{ /* this brace is for balance */
+	} else
 #endif
- 	  g_warning ("Notification framework not available");
-	}
+	  {
+	    g_warning ("Notification framework not available");
+	  }
       } else if (!g_ascii_strcasecmp (command, "tooltip")) {
         if (g_utf8_validate (value, -1, NULL)) {
           gtk_status_icon_set_tooltip (status_icon, value);
@@ -207,6 +283,62 @@
         } else {
           gtk_status_icon_set_visible (status_icon, TRUE);
 	}
+      } else if (!g_ascii_strcasecmp (command, "blink")) {
+        if (!g_ascii_strcasecmp (value, "false")) {
+          gtk_status_icon_set_blinking (status_icon, FALSE);
+        } else {
+          gtk_status_icon_set_blinking (status_icon, TRUE);
+	}
+      } else if (!g_ascii_strcasecmp (command, "menu")) {
+	if (in_menu) {
+	  g_warning ("already constructing menu");
+	  continue;
+	}
+	if (menu != NULL) {
+	  gtk_object_destroy (GTK_OBJECT (menu));
+	  menu = NULL;
+	}
+	if (menu_list != NULL) {
+	  free_menu_list ();
+	}
+	in_menu = TRUE;
+      } else if (!g_ascii_strcasecmp (command, "menuitem")) {
+          GError *error = NULL;
+	  gint argc;
+	  gchar **argv;
+	  struct menu_entry *new_entry;
+
+	  if (!in_menu) {
+	    g_warning ("not building a menu");
+	    continue;
+	  }
+
+	  if (!g_shell_parse_argv (value, &argc, &argv, &error)) {
+	    g_warning ("could not parse argument: %s", error->message);
+	    g_error_free (error);
+	    continue;
+	  }
+	  if (argc != 2) {
+	    g_warning ("invalid menu item: %s", value);
+	    g_strfreev (argv);
+	    continue;
+	  }
+	  new_entry = g_new (struct menu_entry, 1);
+	  new_entry->title = g_strdup (argv[0]);
+	  new_entry->output = g_strdup (argv[1]);
+	  menu_list = g_slist_append (menu_list, new_entry);
+	  g_strfreev (argv);
+      } else if (!g_ascii_strcasecmp (command, "endmenu")) {
+	if (!in_menu) {
+	  g_warning ("no menu being constructed");
+	  continue;
+	}
+	build_menu ();
+	in_menu = FALSE;
+      } else if (!g_ascii_strcasecmp (command, "click")) {
+	if (click_output != NULL)
+	  g_free (click_output);
+	click_output = g_strdup (value);
       } else {
 	g_warning ("Unknown command '%s'", command);
       }
@@ -271,6 +403,13 @@
  
   if (notification_data->listen) {
     zenity_notification_listen_on_stdin (data);
+
+    /* Left-click will output a string if one is specified.  */
+    g_signal_connect (status_icon, "activate",
+		      G_CALLBACK (maybe_output_click_string), NULL);
+    /* Right-click will pull up the menu, if it exists.  */
+    g_signal_connect (status_icon, "popup-menu",
+		      G_CALLBACK (pop_up_menu), NULL);
   } else {
     /* if we aren't listening for changes, then close on activate (left-click) */
     g_signal_connect (status_icon, "activate",
