changeset 24548:284fd17c6020

Clean up some crufts. I think this is now mergeable with .next.minor.
author Sadrul Habib Chowdhury <imadil@gmail.com>
date Sun, 23 Nov 2008 18:30:31 +0000
parents 82deaa289d2a
children 5b9345469776
files ChangeLog.API pidgin/gtkimhtml.c pidgin/gtkimhtml.h pidgin/gtkutils.c
diffstat 4 files changed, 203 insertions(+), 112 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog.API	Sun Nov 23 18:09:21 2008 +0000
+++ b/ChangeLog.API	Sun Nov 23 18:30:31 2008 +0000
@@ -4,6 +4,9 @@
 	pidgin:
 		Added:
 		* gtk_imhtml_class_register_protocol
+		* gtk_imhtml_link_get_url, gtk_imhtml_link_get_text_tag,
+		  gtk_imhtml_link_activate functions to process GtkIMHtmlLink objects
+		  from GtkIMHtml protocol callbacks.
 		* pidgin_utils_init, pidgin_utils_uninit
 
 version 2.5.0 (08/18/2008):
--- a/pidgin/gtkimhtml.c	Sun Nov 23 18:09:21 2008 +0000
+++ b/pidgin/gtkimhtml.c	Sun Nov 23 18:30:31 2008 +0000
@@ -88,13 +88,20 @@
 	GtkTextMark *mark;
 };
 
+struct _GtkIMHtmlLink
+{
+	GtkIMHtml *imhtml;
+	gchar *url;
+	GtkTextTag *tag;
+};
+
 typedef struct _GtkIMHtmlProtocol
 {
 	char *name;
 	int length;
 
-	gboolean (*activate)(GtkIMHtml *imhtml, const char *text);
-	gboolean (*context_menu)(GtkIMHtml *imhtml, const char *text, GtkWidget *menu);
+	gboolean (*activate)(GtkIMHtml *imhtml, GtkIMHtmlLink *link);
+	gboolean (*context_menu)(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu);
 } GtkIMHtmlProtocol;
 
 static gboolean
@@ -125,6 +132,8 @@
 static void imhtml_font_shrink(GtkIMHtml *imhtml);
 static void imhtml_clear_formatting(GtkIMHtml *imhtml);
 static int gtk_imhtml_is_protocol(const char *text);
+static void gtk_imhtml_activate_tag(GtkIMHtml *imhtml, GtkTextTag *tag);
+static void gtk_imhtml_link_destroy(GtkIMHtmlLink *link);
 
 /* POINT_SIZE converts from AIM font sizes to a point size scale factor. */
 #define MAX_FONT_SIZE 7
@@ -1422,9 +1431,14 @@
 imhtml_url_clicked(GtkIMHtml *imhtml, const char *url)
 {
 	GtkIMHtmlProtocol *proto = imhtml_find_protocol(url);
+	GtkIMHtmlLink *link;
 	if (!proto)
 		return;
-	proto->activate(imhtml, url);   /* XXX: Do something with the return value? */
+	link = g_new0(GtkIMHtmlLink, 1);
+	link->imhtml = g_object_ref(imhtml);
+	link->url = g_strdup(url);
+	proto->activate(imhtml, link);   /* XXX: Do something with the return value? */
+	gtk_imhtml_link_destroy(link);
 }
 
 /* Boring GTK+ stuff */
@@ -1725,37 +1739,14 @@
 	return imhtml_type;
 }
 
-struct url_data {
-	GObject *object;
-	gchar *url;
-	GtkTextTag *tag;
-};
-
-static void url_data_destroy(gpointer mydata)
-{
-	struct url_data *data = mydata;
-	g_object_unref(data->object);
-	g_object_unref(data->tag);
-	g_free(data->url);
-	g_free(data);
-}
-
-static void url_open(GtkWidget *w, struct url_data *data)
-{
-	if(!data) return;
-	g_signal_emit(data->object, signals[URL_CLICKED], 0, data->url);
-	g_object_set_data(G_OBJECT(data->tag), "visited", GINT_TO_POINTER(TRUE));
-	gtk_imhtml_set_link_color(GTK_IMHTML(data->object), data->tag);
-}
-
-static void url_copy(GtkWidget *w, gchar *url) {
-	GtkClipboard *clipboard;
-
-	clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_PRIMARY);
-	gtk_clipboard_set_text(clipboard, url, -1);
-
-	clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_CLIPBOARD);
-	gtk_clipboard_set_text(clipboard, url, -1);
+static void gtk_imhtml_link_destroy(GtkIMHtmlLink *link)
+{
+	if (link->imhtml)
+		g_object_unref(link->imhtml);
+	if (link->tag)
+		g_object_unref(link->tag);
+	g_free(link->url);
+	g_free(link);
 }
 
 /* The callback for an event on a link tag. */
@@ -1771,22 +1762,16 @@
 			if (gtk_text_buffer_get_selection_bounds(
 						gtk_text_iter_get_buffer(arg2),	&start, &end))
 				return FALSE;
-
-			/* A link was clicked--we emit the "url_clicked" signal
-			 * with the URL as the argument */
-			g_object_ref(G_OBJECT(tag));
-			g_signal_emit(imhtml, signals[URL_CLICKED], 0, g_object_get_data(G_OBJECT(tag), "link_url"));
-			g_object_unref(G_OBJECT(tag));
-			g_object_set_data(G_OBJECT(tag), "visited", GINT_TO_POINTER(TRUE));
-			gtk_imhtml_set_link_color(GTK_IMHTML(imhtml), tag);
+			gtk_imhtml_activate_tag(GTK_IMHTML(imhtml), tag);
 			return FALSE;
 		} else if(event_button->button == 3) {
-			GtkWidget *img, *item, *menu;
+			GList *children;
+			GtkWidget *menu;
 			GtkIMHtmlProtocol *proto;
-			struct url_data *tempdata = g_new(struct url_data, 1);
-			tempdata->object = g_object_ref(imhtml);
-			tempdata->url = g_strdup(g_object_get_data(G_OBJECT(tag), "link_url"));
-			tempdata->tag = g_object_ref(tag);
+			GtkIMHtmlLink *link = g_new(GtkIMHtmlLink, 1);
+			link->imhtml = g_object_ref(imhtml);
+			link->url = g_strdup(g_object_get_data(G_OBJECT(tag), "link_url"));
+			link->tag = g_object_ref(tag);
 
 			/* Don't want the tooltip around if user right-clicked on link */
 			if (GTK_IMHTML(imhtml)->tip_window) {
@@ -1802,57 +1787,23 @@
 			else
 				gdk_window_set_cursor(event_button->window, GTK_IMHTML(imhtml)->arrow_cursor);
 			menu = gtk_menu_new();
-			g_object_set_data_full(G_OBJECT(menu), "x-imhtml-url-data", tempdata, url_data_destroy);
-
-			proto = imhtml_find_protocol(tempdata->url);
-
-			if (!strncmp(tempdata->url, "mailto:", 7))
-			{
-				/* Copy Email Address */
-				img = gtk_image_new_from_stock(GTK_STOCK_COPY,
-											   GTK_ICON_SIZE_MENU);
-				item = gtk_image_menu_item_new_with_mnemonic(
-					_("_Copy Email Address"));
-				gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
-				g_signal_connect(G_OBJECT(item), "activate",
-								 G_CALLBACK(url_copy), tempdata->url + 7);
-				gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+			g_object_set_data_full(G_OBJECT(menu), "x-imhtml-url-data", link,
+					(GDestroyNotify)gtk_imhtml_link_destroy);
+
+			proto = imhtml_find_protocol(link->url);
+
+			if (proto && proto->context_menu) {
+				proto->context_menu(GTK_IMHTML(link->imhtml), link, menu);
 			}
-			else if (proto && proto->context_menu)
-			{
-				GList *children;
-				proto->context_menu(GTK_IMHTML(tempdata->object), tempdata->url, menu);
-				children = gtk_container_get_children(GTK_CONTAINER(menu));
-				if (!children) {
-					item = gtk_menu_item_new_with_label(_("No actions available"));
-					gtk_widget_show(item);
-					gtk_widget_set_sensitive(item, FALSE);
-					gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
-				} else {
-					g_list_free(children);
-				}
-			}
-			else
-			{
-				/* Open Link in Browser */
-				img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO,
-											   GTK_ICON_SIZE_MENU);
-				item = gtk_image_menu_item_new_with_mnemonic(
-					_("_Open Link"));
-				gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
-				g_signal_connect(G_OBJECT(item), "activate",
-								 G_CALLBACK(url_open), tempdata);
+
+			children = gtk_container_get_children(GTK_CONTAINER(menu));
+			if (!children) {
+				GtkWidget *item = gtk_menu_item_new_with_label(_("No actions available"));
+				gtk_widget_show(item);
+				gtk_widget_set_sensitive(item, FALSE);
 				gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
-
-				/* Copy Link Location */
-				img = gtk_image_new_from_stock(GTK_STOCK_COPY,
-											   GTK_ICON_SIZE_MENU);
-				item = gtk_image_menu_item_new_with_mnemonic(
-					_("_Copy Link Location"));
-				gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
-				g_signal_connect(G_OBJECT(item), "activate",
-								 G_CALLBACK(url_copy), tempdata->url);
-				gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+			} else {
+				g_list_free(children);
 			}
 
 
@@ -5797,8 +5748,8 @@
 }
 
 gboolean gtk_imhtml_class_register_protocol(const char *name,
-		gboolean (*activate)(GtkIMHtml *imhtml, const char *text),
-		gboolean (*context_menu)(GtkIMHtml *imhtml, const char *text, GtkWidget *menu))
+		gboolean (*activate)(GtkIMHtml *imhtml, GtkIMHtmlLink *link),
+		gboolean (*context_menu)(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu))
 {
 	GtkIMHtmlClass *klass;
 	GtkIMHtmlProtocol *proto;
@@ -5828,3 +5779,38 @@
 	return TRUE;
 }
 
+static void
+gtk_imhtml_activate_tag(GtkIMHtml *imhtml, GtkTextTag *tag)
+{
+	/* A link was clicked--we emit the "url_clicked" signal
+	 * with the URL as the argument */
+	g_object_ref(G_OBJECT(tag));
+	g_signal_emit(imhtml, signals[URL_CLICKED], 0, g_object_get_data(G_OBJECT(tag), "link_url"));
+	g_object_unref(G_OBJECT(tag));
+	g_object_set_data(G_OBJECT(tag), "visited", GINT_TO_POINTER(TRUE));
+	gtk_imhtml_set_link_color(GTK_IMHTML(imhtml), tag);
+}
+
+gboolean gtk_imhtml_link_activate(GtkIMHtmlLink *link)
+{
+	g_return_val_if_fail(link, FALSE);
+
+	if (link->tag) {
+		gtk_imhtml_activate_tag(link->imhtml, link->tag);
+	} else if (link->url) {
+		g_signal_emit(link->imhtml, signals[URL_CLICKED], 0, link->url);
+	} else
+		return FALSE;
+	return TRUE;
+}
+
+const char *gtk_imhtml_link_get_url(GtkIMHtmlLink *link)
+{
+	return link->url;
+}
+
+const GtkTextTag * gtk_imhtml_link_get_text_tag(GtkIMHtmlLink *link)
+{
+	return link->tag;
+}
+
--- a/pidgin/gtkimhtml.h	Sun Nov 23 18:09:21 2008 +0000
+++ b/pidgin/gtkimhtml.h	Sun Nov 23 18:30:31 2008 +0000
@@ -60,6 +60,7 @@
 typedef struct _GtkIMHtmlAnimation	GtkIMHtmlAnimation;
 typedef struct _GtkIMHtmlHr			GtkIMHtmlHr;
 typedef struct _GtkIMHtmlFuncs		GtkIMHtmlFuncs;
+typedef struct _GtkIMHtmlLink       GtkIMHtmlLink;
 
 typedef enum {
 	GTK_IMHTML_BOLD =       1 << 0,
@@ -888,21 +889,57 @@
 void gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley *smiley);
 
 /**
- * Register a protocol with the GtkIMHtml widget. Registering a protocol would allow
- * certain text to be clickable.
+ * Register a protocol with the GtkIMHtml widget. Registering a protocol would
+ * allow certain text to be clickable.
  *
  * @param name      The name of the protocol (e.g. http://)
  * @param activate  The callback to trigger when the protocol text is clicked.
- *                  Removes any current protocol definition if @c NULL.
- * @param context_menu  The callback totrigger when the context menu is popped up on
- *                      the protocol text.
+ *                  Removes any current protocol definition if @c NULL. The
+ *                  callback should return @c TRUE if the link was activated
+ *                  properly, @c FALSE otherwise.
+ * @param context_menu  The callback to trigger when the context menu is popped
+ *                      up on the protocol text. The callback should return
+ *                      @c TRUE if the request for context menu was processed
+ *                      successfully, @c FALSE otherwise.
  *
  * @return  @c TRUE if the protocol was successfully registered (or unregistered, when #activate is @c NULL)
  * @since 2.6.0
  */
 gboolean gtk_imhtml_class_register_protocol(const char *name,
-		gboolean (*activate)(GtkIMHtml *imhtml, const char *text),
-		gboolean (*context_menu)(GtkIMHtml *imhtml, const char *text, GtkWidget *menu));
+		gboolean (*activate)(GtkIMHtml *imhtml, GtkIMHtmlLink *link),
+		gboolean (*context_menu)(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu));
+
+/**
+ * Get the URL associated with a link. This should be used by the IMHtml protocol-callbacks.
+ *
+ * @param link   The GtkIMHtmlLink object sent to the callback functions
+ *
+ * @return  The URL
+ * @since 2.6.0
+ */
+const char *gtk_imhtml_link_get_url(GtkIMHtmlLink *link);
+
+/**
+ * Get the GtkTextTag object (if any) associated with a particular link.
+ *
+ * @param link   The GtkIMHtmlLink object sent to the callback functions
+ *
+ * @return  The GtkTextTag object, or @c NULL
+ * @since 2.6.0
+ */
+const GtkTextTag * gtk_imhtml_link_get_text_tag(GtkIMHtmlLink *link);
+
+/**
+ * Activates a GtkIMHtmlLink object. This triggers the 'url-clicked' signal, marks the
+ * link as visited (when possible).
+ *
+ * @param link   The GtkIMHtmlLink object sent to the callback functions
+ *
+ * @return  @c TRUE if 'url-clicked' signal was emitted, @c FALSE otherwise.
+ * @since 2.6.0
+ */
+gboolean gtk_imhtml_link_activate(GtkIMHtmlLink *link);
+
 /*@}*/
 
 #ifdef __cplusplus
--- a/pidgin/gtkutils.c	Sun Nov 23 18:09:21 2008 +0000
+++ b/pidgin/gtkutils.c	Sun Nov 23 18:30:31 2008 +0000
@@ -84,8 +84,9 @@
 }
 
 static gboolean
-url_clicked_cb(GtkIMHtml *imhtml, const char *uri)
+url_clicked_cb(GtkIMHtml *unused, GtkIMHtmlLink *link)
 {
+	const char *uri = gtk_imhtml_link_get_url(link);
 	g_idle_add(url_clicked_idle_cb, g_strdup(uri));
 	return TRUE;
 }
@@ -3481,13 +3482,73 @@
 	return pixbuf;
 }
 
+static void url_copy(GtkWidget *w, gchar *url)
+{
+	GtkClipboard *clipboard;
+
+	clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_PRIMARY);
+	gtk_clipboard_set_text(clipboard, url, -1);
+
+	clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_CLIPBOARD);
+	gtk_clipboard_set_text(clipboard, url, -1);
+}
+
+static gboolean
+link_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
+{
+	GtkWidget *img, *item;
+	const char *url;
+
+	url = gtk_imhtml_link_get_url(link);
+
+	/* Open Link in Browser */
+	img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU);
+	item = gtk_image_menu_item_new_with_mnemonic(_("_Open Link"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
+	g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link);
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+	/* Copy Link Location */
+	img = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
+	item = gtk_image_menu_item_new_with_mnemonic(_("_Copy Link Location"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
+	g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(url_copy), (gpointer)url);
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+	return TRUE;
+}
+
+static gboolean
+copy_email_address(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
+{
+	GtkWidget *img, *item;
+	const char *text;
+	char *address;
+#define MAILTOSIZE  (sizeof("mailto:") - 1)
+
+	text = gtk_imhtml_link_get_url(link);
+	g_return_val_if_fail(text && strlen(text) > MAILTOSIZE, FALSE);
+	address = (char*)text + MAILTOSIZE;
+
+	/* Copy Email Address */
+	img = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
+	item = gtk_image_menu_item_new_with_mnemonic(_("_Copy Email Address"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
+	g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(url_copy), address);
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+	return TRUE;
+}
+
 /* XXX: The following two functions are for demonstration purposes only! */
 static gboolean
-open_dialog(GtkIMHtml *imhtml, const char *url)
+open_dialog(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
 {
+	const char *url;
 	const char *str;
 
-	if (strlen(url) < sizeof("open://"))
+	url = gtk_imhtml_link_get_url(link);
+	if (!url || strlen(url) < sizeof("open://"))
 		return FALSE;
 
 	str = url + sizeof("open://") - 1;
@@ -3502,16 +3563,18 @@
 }
 
 static gboolean
-dummy(GtkIMHtml *imhtml, const char *text, GtkWidget *menu)
+dummy(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
 {
 	return TRUE;
 }
 
 void pidgin_utils_init(void)
 {
-	gtk_imhtml_class_register_protocol("http://", url_clicked_cb, NULL);
-	gtk_imhtml_class_register_protocol("https://", url_clicked_cb, NULL);
-	gtk_imhtml_class_register_protocol("ftp://", url_clicked_cb, NULL);
+	gtk_imhtml_class_register_protocol("http://", url_clicked_cb, link_context_menu);
+	gtk_imhtml_class_register_protocol("https://", url_clicked_cb, link_context_menu);
+	gtk_imhtml_class_register_protocol("ftp://", url_clicked_cb, link_context_menu);
+	gtk_imhtml_class_register_protocol("gopher://", url_clicked_cb, link_context_menu);
+	gtk_imhtml_class_register_protocol("mailto:", url_clicked_cb, copy_email_address);
 
 	gtk_imhtml_class_register_protocol("open://", open_dialog, dummy);
 }
@@ -3521,6 +3584,8 @@
 	gtk_imhtml_class_register_protocol("http://", NULL, NULL);
 	gtk_imhtml_class_register_protocol("https://", NULL, NULL);
 	gtk_imhtml_class_register_protocol("ftp://", NULL, NULL);
+	gtk_imhtml_class_register_protocol("mailto:", NULL, NULL);
+	gtk_imhtml_class_register_protocol("gopher://", NULL, NULL);
 
 	gtk_imhtml_class_register_protocol("open://", NULL, NULL);
 }