imhtml.customlinks: d4a6758e: Allow plugins to specify custom link typ...

sadrul at pidgin.im sadrul at pidgin.im
Sun Nov 16 05:00:30 EST 2008


-----------------------------------------------------------------
Revision: d4a6758e4166410f1a45b94a131638d2be8f4882
Ancestor: f3891f3348abfe90fbe60a054833c12075aa8df4
Author: sadrul at pidgin.im
Date: 2008-11-16T09:58:48
Branch: im.pidgin.imhtml.customlinks
URL: http://d.pidgin.im/viewmtn/revision/info/d4a6758e4166410f1a45b94a131638d2be8f4882

Modified files:
        ChangeLog.API pidgin/gtkimhtml.c pidgin/gtkimhtml.h
        pidgin/gtkmain.c pidgin/gtkutils.c pidgin/gtkutils.h

ChangeLog: 

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

-------------- next part --------------
============================================================
--- ChangeLog.API	09bd1c73e92aa9e2e48a51c0e677ecc7d41fc100
+++ ChangeLog.API	5ad60f40efb455f63c50227fd85363d01c828fab
@@ -1,5 +1,11 @@ Pidgin and Finch: The Pimpin' Penguin IM
 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:
============================================================
--- pidgin/gtkimhtml.c	e6d9c36f8a08f24eaac37d6d3f21885b40cc432a
+++ pidgin/gtkimhtml.c	48137ddc71997c81ca27470b8da2a85201492f17
@@ -88,6 +88,15 @@ struct im_image_data {
 	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_clear_formatting(GtkI
 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 @@ gtk_imhtml_finalize (GObject *object)
 
 }
 
+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 @@ static void gtk_imhtml_class_init (GtkIM
 	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 @@ static gboolean tag_event(GtkTextTag *ta
 			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 @@ static gboolean tag_event(GtkTextTag *ta
 			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 @@ static gboolean tag_event(GtkTextTag *ta
 								 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 @@ gtk_imhtml_link_drag_rcv_cb(GtkWidget *w
 
 			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 @@ gtk_imhtml_link_drag_rcv_cb(GtkWidget *w
 					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 @@ gtk_imhtml_get_html_opt (gchar       *ta
 	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 @@ void gtk_imhtml_insert_html_at_iter(GtkI
 			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 @@ void gtk_imhtml_insert_html_at_iter(GtkI
 				 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 @@ void gtk_imhtml_smiley_destroy(GtkIMHtml
 	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;
+}
+
============================================================
--- pidgin/gtkimhtml.h	622a9f59b9e16f4ecab6b0d171594686819ba150
+++ pidgin/gtkimhtml.h	9fff94b5cc2aaa4d6d49f5e7ef62b467870f3796
@@ -156,6 +156,7 @@ struct _GtkIMHtmlClass {
 	gboolean (*message_send)(GtkIMHtml *);
 	void (*undo)(GtkIMHtml *);
 	void (*redo)(GtkIMHtml *);
+	GList *protocols; /* List of GtkIMHtmlProtocol's */
 };
 
 struct _GtkIMHtmlFontDetail {
@@ -885,6 +886,23 @@ void gtk_imhtml_smiley_destroy(GtkIMHtml
  * @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
============================================================
--- pidgin/gtkmain.c	2668e844ffc992c38cdf4bfc848fb870cd56328c
+++ pidgin/gtkmain.c	2e99f2ab7b6bd71f0713b8296358ad86cbe0ad7f
@@ -310,6 +310,7 @@ pidgin_ui_init(void)
 	pidgin_log_init();
 	pidgin_docklet_init();
 	pidgin_smileys_init();
+	pidgin_utils_init();
 }
 
 static GHashTable *ui_info = NULL;
@@ -326,6 +327,7 @@ pidgin_quit(void)
 	pidgin_plugins_save();
 
 	/* Uninit */
+	pidgin_utils_uninit();
 	pidgin_smileys_uninit();
 	pidgin_conversations_uninit();
 	pidgin_status_uninit();
============================================================
--- pidgin/gtkutils.c	5d3e217fe4eb4b27259aaceafb882cf2a2adcb29
+++ pidgin/gtkutils.c	5250bd88a265bb129ad94b5b3fec020681062c2b
@@ -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 @@ url_clicked_idle_cb(gpointer data)
 	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 @@ pidgin_setup_imhtml(GtkWidget *imhtml)
 	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 @@ GdkPixbuf * pidgin_pixbuf_from_imgstore(
 	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);
+}
+
============================================================
--- pidgin/gtkutils.h	de3c539472dcb9a855742e68818d5cd5f05ac1a4
+++ pidgin/gtkutils.h	aaf8f8709bb13907d529a5cc6c1847c23d3b442b
@@ -822,5 +822,17 @@ GdkPixbuf * pidgin_pixbuf_from_imgstore(
  */
 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_ */
 


More information about the Commits mailing list