changeset 25196:82deaa289d2a

propagate from branch 'im.pidgin.pidgin' (head a9579fef71ff6820f390a986132d30b079f590a0) to branch 'im.pidgin.imhtml.customlinks' (head b42b5daebfa64a4919ddf75ed6a9ce256d795d46)
author Sadrul Habib Chowdhury <imadil@gmail.com>
date Sun, 23 Nov 2008 18:09:21 +0000
parents de3b12b70b5f (current diff) 16811be2253f (diff)
children 284fd17c6020
files
diffstat 6 files changed, 194 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog.API	Mon Nov 17 13:28:32 2008 +0000
+++ b/ChangeLog.API	Sun Nov 23 18:09:21 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	Mon Nov 17 13:28:32 2008 +0000
+++ b/pidgin/gtkimhtml.c	Sun Nov 23 18:09:21 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,13 +1818,27 @@
 								 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 */
 				img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO,
 											   GTK_ICON_SIZE_MENU);
 				item = gtk_image_menu_item_new_with_mnemonic(
-					_("_Open Link in Browser"));
+					_("_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);
@@ -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	Mon Nov 17 13:28:32 2008 +0000
+++ b/pidgin/gtkimhtml.h	Sun Nov 23 18:09:21 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	Mon Nov 17 13:28:32 2008 +0000
+++ b/pidgin/gtkmain.c	Sun Nov 23 18:09:21 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	Mon Nov 17 13:28:32 2008 +0000
+++ b/pidgin/gtkutils.c	Sun Nov 23 18:09:21 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	Mon Nov 17 13:28:32 2008 +0000
+++ b/pidgin/gtkutils.h	Sun Nov 23 18:09:21 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_ */