changeset 24396:38a2f78f80a7

Allow plugins to specify custom link types to the GtkIMHtml widget. Currently, the custom link types are added for all GtkIMHtml widgets. If we wanted, it should be possible to add custom links to particular widgets only too. If everything looks OK, I might merge this in before 2.6.0
author Sadrul Habib Chowdhury <imadil@gmail.com>
date Sun, 16 Nov 2008 09:58:48 +0000
parents fae699fece1f
children 16811be2253f
files ChangeLog.API pidgin/gtkimhtml.c pidgin/gtkimhtml.h pidgin/gtkmain.c pidgin/gtkutils.c pidgin/gtkutils.h
diffstat 6 files changed, 193 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog.API	Sun Nov 16 00:10:02 2008 +0000
+++ b/ChangeLog.API	Sun Nov 16 09:58:48 2008 +0000
@@ -1,5 +1,11 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.6.0 (??/??/????):
+	pidgin:
+		Added:
+		* gtk_imhtml_class_register_protocol
+		* pidgin_utils_init, pidgin_utils_uninit
+
 version 2.5.0 (08/18/2008):
 	libpurple:
 		Added:
--- a/pidgin/gtkimhtml.c	Sun Nov 16 00:10:02 2008 +0000
+++ b/pidgin/gtkimhtml.c	Sun Nov 16 09:58:48 2008 +0000
@@ -88,6 +88,15 @@
 	GtkTextMark *mark;
 };
 
+typedef struct _GtkIMHtmlProtocol
+{
+	char *name;
+	int length;
+
+	gboolean (*activate)(GtkIMHtml *imhtml, const char *text);
+	gboolean (*context_menu)(GtkIMHtml *imhtml, const char *text, GtkWidget *menu);
+} GtkIMHtmlProtocol;
+
 static gboolean
 gtk_text_view_drag_motion (GtkWidget        *widget,
                            GdkDragContext   *context,
@@ -115,6 +124,7 @@
 static void imhtml_font_grow(GtkIMHtml *imhtml);
 static void imhtml_font_shrink(GtkIMHtml *imhtml);
 static void imhtml_clear_formatting(GtkIMHtml *imhtml);
+static int gtk_imhtml_is_protocol(const char *text);
 
 /* POINT_SIZE converts from AIM font sizes to a point size scale factor. */
 #define MAX_FONT_SIZE 7
@@ -1391,6 +1401,32 @@
 
 }
 
+static GtkIMHtmlProtocol *
+imhtml_find_protocol(const char *url)
+{
+	GtkIMHtmlClass *klass;
+	GList *iter;
+	GtkIMHtmlProtocol *proto = NULL;
+
+	klass = g_type_class_ref(GTK_TYPE_IMHTML);
+	for (iter = klass->protocols; iter; iter = iter->next) {
+		proto = iter->data;
+		if (g_ascii_strncasecmp(url, proto->name, proto->length) == 0) {
+			return proto;
+		}
+	}
+	return NULL;
+}
+
+static void
+imhtml_url_clicked(GtkIMHtml *imhtml, const char *url)
+{
+	GtkIMHtmlProtocol *proto = imhtml_find_protocol(url);
+	if (!proto)
+		return;
+	proto->activate(imhtml, url);   /* XXX: Do something with the return value? */
+}
+
 /* Boring GTK+ stuff */
 static void gtk_imhtml_class_init (GtkIMHtmlClass *klass)
 {
@@ -1475,6 +1511,7 @@
 	klass->toggle_format = imhtml_toggle_format;
 	klass->message_send = imhtml_message_send;
 	klass->clear_format = imhtml_clear_formatting;
+	klass->url_clicked = imhtml_url_clicked;
 	klass->undo = gtk_imhtml_undo;
 	klass->redo = gtk_imhtml_redo;
 
@@ -1745,6 +1782,7 @@
 			return FALSE;
 		} else if(event_button->button == 3) {
 			GtkWidget *img, *item, *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"));
@@ -1766,7 +1804,7 @@
 			menu = gtk_menu_new();
 			g_object_set_data_full(G_OBJECT(menu), "x-imhtml-url-data", tempdata, url_data_destroy);
 
-			/* buttons and such */
+			proto = imhtml_find_protocol(tempdata->url);
 
 			if (!strncmp(tempdata->url, "mailto:", 7))
 			{
@@ -1780,6 +1818,20 @@
 								 G_CALLBACK(url_copy), tempdata->url + 7);
 				gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
 			}
+			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 */
@@ -1884,10 +1936,7 @@
 
 			links = g_strsplit((char *)sd->data, "\n", 0);
 			while((link = links[i]) != NULL){
-				if(purple_str_has_prefix(link, "http://") ||
-				   purple_str_has_prefix(link, "https://") ||
-				   purple_str_has_prefix(link, "ftp://"))
-				{
+				if (gtk_imhtml_is_protocol(link)) {
 					gchar *label;
 
 					if(links[i + 1])
@@ -1896,7 +1945,7 @@
 					label = links[i];
 
 					gtk_imhtml_insert_link(imhtml, mark, link, label);
-				} else if (link=='\0') {
+				} else if (*link == '\0') {
 					/* Ignore blank lines */
 				} else {
 					/* Special reasons, aka images being put in via other tag, etc. */
@@ -2382,26 +2431,12 @@
 	return g_string_free(ret, FALSE);
 }
 
-static const char *accepted_protocols[] = {
-	"http://",
-	"https://",
-	"ftp://"
-};
-
-static const int accepted_protocols_size = 3;
-
 /* returns if the beginning of the text is a protocol. If it is the protocol, returns the length so
    the caller knows how long the protocol string is. */
 static int gtk_imhtml_is_protocol(const char *text)
 {
-	gint i;
-
-	for(i=0; i<accepted_protocols_size; i++){
-		if( g_ascii_strncasecmp(text, accepted_protocols[i], strlen(accepted_protocols[i])) == 0  ){
-			return strlen(accepted_protocols[i]);
-		}
-	}
-	return 0;
+	GtkIMHtmlProtocol *proto = imhtml_find_protocol(text);
+	return proto ? proto->length : 0;
 }
 
 /*
@@ -3320,6 +3355,11 @@
 			pos++;
 		} else if ((len_protocol = gtk_imhtml_is_protocol(c)) > 0){
 			br = FALSE;
+			if (wpos > 0) {
+				gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
+				ws[0] = '\0';
+				wpos = 0;
+			}
 			while(len_protocol--){
 				/* Skip the next len_protocol characters, but make sure they're
 				   copied into the ws array.
@@ -3327,6 +3367,17 @@
 				 ws [wpos++] = *c++;
 				 pos++;
 			}
+			if (!imhtml->edit.link) {
+				while (*c && *c != ' ') {
+					ws [wpos++] = *c++;
+					pos++;
+				}
+				ws[wpos] = '\0';
+				gtk_imhtml_toggle_link(imhtml, ws);
+				gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
+				ws[0] = '\0'; wpos = 0;
+				gtk_imhtml_toggle_link(imhtml, NULL);
+			}
 		} else if (*c) {
 			br = FALSE;
 			ws [wpos++] = *c++;
@@ -5745,3 +5796,35 @@
 	g_free(smiley);
 }
 
+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))
+{
+	GtkIMHtmlClass *klass;
+	GtkIMHtmlProtocol *proto;
+
+	g_return_val_if_fail(name, FALSE);
+
+	klass = g_type_class_ref(GTK_TYPE_IMHTML);
+	g_return_val_if_fail(klass, FALSE);
+
+	if ((proto = imhtml_find_protocol(name))) {
+		g_return_val_if_fail(!activate, FALSE);
+		g_free(proto->name);
+		g_free(proto);
+		klass->protocols = g_list_remove(klass->protocols, proto);
+		return TRUE;
+	} else {
+		g_return_val_if_fail(activate, FALSE);
+	}
+
+	proto = g_new0(GtkIMHtmlProtocol, 1);
+	proto->name = g_strdup(name);
+	proto->length = strlen(name);
+	proto->activate = activate;
+	proto->context_menu = context_menu;
+	klass->protocols = g_list_prepend(klass->protocols, proto);
+
+	return TRUE;
+}
+
--- a/pidgin/gtkimhtml.h	Sun Nov 16 00:10:02 2008 +0000
+++ b/pidgin/gtkimhtml.h	Sun Nov 16 09:58:48 2008 +0000
@@ -156,6 +156,7 @@
 	gboolean (*message_send)(GtkIMHtml *);
 	void (*undo)(GtkIMHtml *);
 	void (*redo)(GtkIMHtml *);
+	GList *protocols; /* List of GtkIMHtmlProtocol's */
 };
 
 struct _GtkIMHtmlFontDetail {
@@ -885,6 +886,23 @@
  * @since 2.5.0
  */
 void gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley *smiley);
+
+/**
+ * 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.
+ *
+ * @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));
 /*@}*/
 
 #ifdef __cplusplus
--- a/pidgin/gtkmain.c	Sun Nov 16 00:10:02 2008 +0000
+++ b/pidgin/gtkmain.c	Sun Nov 16 09:58:48 2008 +0000
@@ -310,6 +310,7 @@
 	pidgin_log_init();
 	pidgin_docklet_init();
 	pidgin_smileys_init();
+	pidgin_utils_init();
 }
 
 static GHashTable *ui_info = NULL;
@@ -326,6 +327,7 @@
 	pidgin_plugins_save();
 
 	/* Uninit */
+	pidgin_utils_uninit();
 	pidgin_smileys_uninit();
 	pidgin_conversations_uninit();
 	pidgin_status_uninit();
--- a/pidgin/gtkutils.c	Sun Nov 16 00:10:02 2008 +0000
+++ b/pidgin/gtkutils.c	Sun Nov 16 09:58:48 2008 +0000
@@ -56,6 +56,9 @@
 #include "signals.h"
 #include "util.h"
 
+#include "gtkaccount.h"
+#include "gtkprefs.h"
+
 #include "gtkconv.h"
 #include "gtkdialogs.h"
 #include "gtkimhtml.h"
@@ -80,10 +83,11 @@
 	return FALSE;
 }
 
-static void
-url_clicked_cb(GtkWidget *w, const char *uri)
+static gboolean
+url_clicked_cb(GtkIMHtml *imhtml, const char *uri)
 {
 	g_idle_add(url_clicked_idle_cb, g_strdup(uri));
+	return TRUE;
 }
 
 static GtkIMHtmlFuncs gtkimhtml_cbs = {
@@ -102,9 +106,6 @@
 	g_return_if_fail(imhtml != NULL);
 	g_return_if_fail(GTK_IS_IMHTML(imhtml));
 
-	g_signal_connect(G_OBJECT(imhtml), "url_clicked",
-					 G_CALLBACK(url_clicked_cb), NULL);
-
 	pidgin_themes_smiley_themeize(imhtml);
 
 	gtk_imhtml_set_funcs(GTK_IMHTML(imhtml), &gtkimhtml_cbs);
@@ -3480,3 +3481,47 @@
 	return pixbuf;
 }
 
+/* XXX: The following two functions are for demonstration purposes only! */
+static gboolean
+open_dialog(GtkIMHtml *imhtml, const char *url)
+{
+	const char *str;
+
+	if (strlen(url) < sizeof("open://"))
+		return FALSE;
+
+	str = url + sizeof("open://") - 1;
+
+	if (strcmp(str, "accounts") == 0)
+		pidgin_accounts_window_show();
+	else if (strcmp(str, "prefs") == 0)
+		pidgin_prefs_show();
+	else
+		return FALSE;
+	return TRUE;
+}
+
+static gboolean
+dummy(GtkIMHtml *imhtml, const char *text, 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("open://", open_dialog, dummy);
+}
+
+void pidgin_utils_uninit(void)
+{
+	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("open://", NULL, NULL);
+}
+
--- a/pidgin/gtkutils.h	Sun Nov 16 00:10:02 2008 +0000
+++ b/pidgin/gtkutils.h	Sun Nov 16 09:58:48 2008 +0000
@@ -822,5 +822,17 @@
  */
 GdkPixbuf * pidgin_pixbuf_from_imgstore(PurpleStoredImage *image);
 
+/**
+ * Initialize some utility functions.
+ * @since 2.6.0
+ */
+void pidgin_utils_init(void);
+
+/**
+ * Uninitialize some utility functions.
+ * @since 2.6.0
+ */
+void pidgin_utils_uninit(void);
+
 #endif /* _PIDGINUTILS_H_ */