diff pidgin/gtkimhtml.c @ 27842:acef4202e147

propagate from branch 'im.pidgin.pidgin' (head 217103c3e954ff2d295a6590ad3477d357894f9c) to branch 'im.pidgin.pidgin.yaz' (head 0a5a324e3974fba2b40dcd1bb82fb1cc8b8fcabf)
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Sun, 25 May 2008 04:30:41 +0000
parents 22f8b5512ef4 88d67b1bb52a
children 30eaeb7cc076
line wrap: on
line diff
--- a/pidgin/gtkimhtml.c	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtkimhtml.c	Sun May 25 04:30:41 2008 +0000
@@ -32,10 +32,14 @@
 #include "internal.h"
 #include "pidgin.h"
 #include "pidginstock.h"
+#include "gtkutils.h"
+#include "smiley.h"
+#include "imgstore.h"
 
 #include "debug.h"
 #include "util.h"
 #include "gtkimhtml.h"
+#include "gtksmiley.h"
 #include "gtksourceiter.h"
 #include "gtksourceundomanager.h"
 #include "gtksourceview-marshal.h"
@@ -397,6 +401,55 @@
 		gtk_imhtml_scroll_to_end(imhtml, FALSE);
 }
 
+#define DEFAULT_SEND_COLOR "#204a87"
+#define DEFAULT_RECV_COLOR "#cc0000"
+#define DEFAULT_HIGHLIGHT_COLOR "#AF7F00"
+#define DEFAULT_ACTION_COLOR "#062585"
+#define DEFAULT_WHISPER_ACTION_COLOR "#6C2585"
+#define DEFAULT_WHISPER_COLOR "#00FF00"
+
+static void (*parent_style_set)(GtkWidget *widget, GtkStyle *prev_style);
+
+static void
+gtk_imhtml_style_set(GtkWidget *widget, GtkStyle *prev_style)
+{
+	int i;
+	struct {
+		const char *tag;
+		const char *color;
+		const char *def;
+	} styles[] = {
+		{"send-name", "send-name-color", DEFAULT_SEND_COLOR},
+		{"receive-name", "receive-name-color", DEFAULT_RECV_COLOR},
+		{"highlight-name", "highlight-name-color", DEFAULT_HIGHLIGHT_COLOR},
+		{"action-name", "action-name-color", DEFAULT_ACTION_COLOR},
+		{"whisper-action-name", "whisper-action-name-color", DEFAULT_WHISPER_ACTION_COLOR},
+		{"whisper-name", "whisper-name-color", DEFAULT_WHISPER_COLOR},
+		{NULL, NULL, NULL}
+	};
+	GtkIMHtml *imhtml = GTK_IMHTML(widget);
+	GtkTextTagTable *table = gtk_text_buffer_get_tag_table(imhtml->text_buffer);
+
+	for (i = 0; styles[i].tag; i++) {
+		GdkColor *color = NULL;
+		GtkTextTag *tag = gtk_text_tag_table_lookup(table, styles[i].tag);
+		if (!tag) {
+			purple_debug_warning("gtkimhtml", "Cannot find tag '%s'. This should never happen. Please file a bug.\n", styles[i].tag);
+			continue;
+		}
+		gtk_widget_style_get(widget, styles[i].color, &color, NULL);
+		if (color) {
+			g_object_set(tag, "foreground-gdk", color, NULL);
+			gdk_color_free(color);
+		} else {
+			GdkColor defcolor;
+			gdk_color_parse(styles[i].def, &defcolor);
+			g_object_set(tag, "foreground-gdk", &defcolor, NULL);
+		}
+	}
+	parent_style_set(widget, prev_style);
+}
+
 static gint
 gtk_imhtml_tip_paint (GtkIMHtml *imhtml)
 {
@@ -1433,6 +1486,8 @@
 	widget_class->expose_event = gtk_imhtml_expose_event;
 	parent_size_allocate = widget_class->size_allocate;
 	widget_class->size_allocate = gtk_imhtml_size_allocate;
+	parent_style_set = widget_class->style_set;
+	widget_class->style_set = gtk_imhtml_style_set;
 
 	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("hyperlink-color",
 	                                        _("Hyperlink color"),
@@ -1458,6 +1513,14 @@
 	                                        _("Action Message Name Color"),
 	                                        _("Color to draw the name of an action message."),
 	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
+	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("whisper-action-name-color",
+	                                        _("Action Message Name Color for Whispered Message"),
+	                                        _("Color to draw the name of an action message."),
+	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
+	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("whisper-name-color",
+	                                        _("Whisper Message Name Color"),
+	                                        _("Color to draw the name of an action message."),
+	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
 
 	/* Customizable typing notification ... sort of. Example:
 	 *   GtkIMHtml::typing-notification-font = "monospace italic light 8.0"
@@ -1519,9 +1582,18 @@
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "SUP", "rise", 5000, NULL);
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "PRE", "family", "Monospace", NULL);
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "search", "background", "#22ff00", "weight", "bold", NULL);
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "comment", "weight", PANGO_WEIGHT_NORMAL,
 #if FALSE && GTK_CHECK_VERSION(2,10,10)
-	gtk_text_buffer_create_tag(imhtml->text_buffer, "comment", "invisible", FALSE, NULL);
+			"invisible", FALSE,
 #endif
+			NULL);
+
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "send-name", "weight", PANGO_WEIGHT_BOLD, NULL);
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "receive-name", "weight", PANGO_WEIGHT_BOLD, NULL);
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "highlight-name", "weight", PANGO_WEIGHT_BOLD, NULL);
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "action-name", "weight", PANGO_WEIGHT_BOLD, NULL);
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "whisper-action-name", "weight", PANGO_WEIGHT_BOLD, NULL);
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "whisper-name", "weight", PANGO_WEIGHT_BOLD, NULL);
 
 	/* When hovering over a link, we show the hand cursor--elsewhere we show the plain ol' pointer cursor */
 	imhtml->hand_cursor = gdk_cursor_new (GDK_HAND2);
@@ -3063,7 +3135,7 @@
 #else
 					if (imhtml->show_comments && !(options & GTK_IMHTML_NO_COMMENTS)) {
 						wpos = g_snprintf (ws, len, "%s", tag);
-						gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
+						gtk_text_buffer_insert_with_tags_by_name(imhtml->text_buffer, iter, ws, wpos, "comment", NULL);
 					}
 #endif
 					ws[0] = '\0'; wpos = 0;
@@ -3631,6 +3703,15 @@
 	gtk_widget_show(image->filesel);
 }
 
+static void
+gtk_imhtml_custom_smiley_save(GtkWidget *w, GtkIMHtmlImage *image)
+{
+	/* Create an add dialog */
+	PidginSmiley *editor = pidgin_smiley_edit(NULL, NULL);
+	pidgin_smiley_editor_set_shortcut(editor, image->filename);
+	pidgin_smiley_editor_set_image(editor, image->pixbuf);
+}
+
 /*
  * So, um, AIM Direct IM lets you send any file, not just images.  You can
  * just insert a sound or a file or whatever in a conversation.  It's
@@ -3655,6 +3736,19 @@
 			g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_image_save), image);
 			gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
 
+			/* Add menu item for adding custom smiley to local smileys */
+			/* we only add the menu if the image is of "custom smiley size"
+			  <= 96x96 pixels */
+			if (image->width <= 96 && image->height <= 96) {
+				text = g_strdup_printf(_("_Add Custom Smiley..."));
+				img = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
+				item = gtk_image_menu_item_new_with_mnemonic(text);
+				gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
+				g_signal_connect(G_OBJECT(item), "activate",
+								 G_CALLBACK(gtk_imhtml_custom_smiley_save), image);
+				gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+			}
+
 			gtk_widget_show_all(menu);
 			gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
 							event_button->button, event_button->time);
@@ -3676,7 +3770,7 @@
 	GdkPixbufAnimation *anim = NULL;
 	GtkIMHtmlScalable *image = NULL;
 	gboolean ret;
-	
+
 	if (event->type != GDK_BUTTON_RELEASE || ((GdkEventButton*)event)->button != 3)
 		return FALSE;
 
@@ -4912,7 +5006,67 @@
 		g_snprintf(buf, sizeof(buf), "<font size=\"%s\">", &name[10]);
 		return buf;
 	} else {
-		return "";
+		char *str = buf;
+		gboolean isset;
+		int ivalue = 0;
+		GdkColor *color = NULL;
+		GObject *obj = G_OBJECT(tag);
+		gboolean empty = TRUE;
+
+		str += g_snprintf(str, sizeof(buf) - (str - buf), "<span style='");
+
+		/* Weight */
+		g_object_get(obj, "weight-set", &isset, "weight", &ivalue, NULL);
+		if (isset) {
+			const char *weight = "";
+			if (ivalue >= PANGO_WEIGHT_ULTRABOLD)
+				weight = "bolder";
+			else if (ivalue >= PANGO_WEIGHT_BOLD)
+				weight = "bold";
+			else if (ivalue >= PANGO_WEIGHT_NORMAL)
+				weight = "normal";
+			else
+				weight = "lighter";
+
+			str += g_snprintf(str, sizeof(buf) - (str - buf), "font-weight: %s;", weight);
+			empty = FALSE;
+		}
+
+		/* Foreground color */
+		g_object_get(obj, "foreground-set", &isset, "foreground-gdk", &color, NULL);
+		if (isset && color) {
+			str += g_snprintf(str, sizeof(buf) - (str - buf),
+					"color: #%02x%02x%02x;",
+					color->red >> 8, color->green >> 8, color->blue >> 8);
+			gdk_color_free(color);
+			empty = FALSE;
+		}
+
+		/* Background color */
+		g_object_get(obj, "background-set", &isset, "background-gdk", &color, NULL);
+		if (isset && color) {
+			str += g_snprintf(str, sizeof(buf) - (str - buf),
+					"background: #%02x%02x%02x;",
+					color->red >> 8, color->green >> 8, color->blue >> 8);
+			gdk_color_free(color);
+			empty = FALSE;
+		}
+
+		/* Underline */
+		g_object_get(obj, "underline-set", &isset, "underline", &ivalue, NULL);
+		if (isset) {
+			switch (ivalue) {
+				case PANGO_UNDERLINE_NONE:
+				case PANGO_UNDERLINE_ERROR:
+					break;
+				default:
+					str += g_snprintf(str, sizeof(buf) - (str - buf), "text-decoration: underline;");
+			}
+		}
+
+		g_snprintf(str, sizeof(buf) - (str - buf), "'>");
+
+		return (empty ? "" : buf);
 	}
 }
 
@@ -4944,6 +5098,16 @@
 	} else if (strncmp(name, "FONT SIZE ", 10) == 0) {
 		return "</font>";
 	} else {
+		const char *props[] = {"weight-set", "foreground-set", "background-set",
+			"size-set", "underline-set", NULL};
+		int i;
+		for (i = 0; props[i]; i++) {
+			gboolean set = FALSE;
+			g_object_get(G_OBJECT(tag), props[i], &set, NULL);
+			if (set)
+				return "</span>";
+		}
+
 		return "";
 	}
 }
@@ -5273,7 +5437,150 @@
 	if (flags & PURPLE_CONNECTION_NO_IMAGES)
 		buttons &= ~GTK_IMHTML_IMAGE;
 
+	if (flags & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY)
+		buttons |= GTK_IMHTML_CUSTOM_SMILEY;
+	else
+		buttons &= ~GTK_IMHTML_CUSTOM_SMILEY;
+
 	gtk_imhtml_set_format_functions(imhtml, buttons);
 }
 
-
+/*******
+ * GtkIMHtmlSmiley functions
+ *******/
+static void gtk_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data)
+{
+	GtkIMHtmlSmiley *smiley;
+
+	smiley = (GtkIMHtmlSmiley *)user_data;
+	smiley->icon = gdk_pixbuf_loader_get_animation(loader);
+
+	if (smiley->icon)
+		g_object_ref(G_OBJECT(smiley->icon));
+#ifdef DEBUG_CUSTOM_SMILEY
+	purple_debug_info("custom-smiley", "gtk_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s'\n", smiley->icon, smiley->smile);
+#endif
+}
+
+static void gtk_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data)
+{
+	GtkIMHtmlSmiley *smiley;
+	GtkWidget *icon = NULL;
+	GtkTextChildAnchor *anchor = NULL;
+	GSList *current = NULL;
+
+	smiley = (GtkIMHtmlSmiley *)user_data;
+	if (!smiley->imhtml) {
+#ifdef DEBUG_CUSTOM_SMILEY
+		purple_debug_error("custom-smiley", "gtk_custom_smiley_closed(): orphan smiley found: %p\n", smiley);
+#endif
+		g_object_unref(G_OBJECT(loader));
+		smiley->loader = NULL;
+		return;
+	}
+
+	for (current = smiley->anchors; current; current = g_slist_next(current)) {
+
+		icon = gtk_image_new_from_animation(smiley->icon);
+
+#ifdef DEBUG_CUSTOM_SMILEY
+		purple_debug_info("custom-smiley", "gtk_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s'\n",
+				icon, smiley->icon, smiley->smile);
+#endif
+		if (icon) {
+			GList *wids;
+			gtk_widget_show(icon);
+
+			anchor = GTK_TEXT_CHILD_ANCHOR(current->data);
+			wids = gtk_text_child_anchor_get_widgets(anchor);
+
+			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", purple_unescape_html(smiley->smile), g_free);
+			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley->smile), g_free);
+
+			if (smiley->imhtml) {
+				if (wids) {
+					GList *children = gtk_container_get_children(GTK_CONTAINER(wids->data));
+					g_list_foreach(children, (GFunc)gtk_widget_destroy, NULL);
+					g_list_free(children);
+					gtk_container_add(GTK_CONTAINER(wids->data), icon);
+				} else
+					gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley->imhtml), icon, anchor);
+			}
+			g_list_free(wids);
+		}
+
+	}
+
+	g_slist_free(smiley->anchors);
+	smiley->anchors = NULL;
+
+	g_object_unref(G_OBJECT(loader));
+	smiley->loader = NULL;
+}
+
+static void
+gtk_custom_smiley_size_prepared(GdkPixbufLoader *loader, gint width, gint height, gpointer data)
+{
+#define CUSTOM_SMILEY_SIZE 96	/* XXX: Should this be a theme setting? */
+	if (width <= CUSTOM_SMILEY_SIZE && height <= CUSTOM_SMILEY_SIZE)
+		return;
+
+	if (width >= height) {
+		height = height * CUSTOM_SMILEY_SIZE / width;
+		width = CUSTOM_SMILEY_SIZE;
+	} else {
+		width = width * CUSTOM_SMILEY_SIZE / height;
+		height = CUSTOM_SMILEY_SIZE;
+	}
+
+	gdk_pixbuf_loader_set_size(loader, width, height);
+}
+
+void
+gtk_imhtml_smiley_reload(GtkIMHtmlSmiley *smiley)
+{
+	if (smiley->icon)
+		g_object_unref(smiley->icon);
+	if (smiley->loader)
+		g_object_unref(smiley->loader);  /* XXX: does this crash? */
+
+	smiley->icon = NULL;
+	smiley->loader = NULL;
+
+	if (smiley->file) {
+		/* We do not use the pixbuf loader for a smiley that can be loaded
+		 * from a file. (e.g., local custom smileys)
+		 */
+		return;
+	}
+
+	smiley->loader = gdk_pixbuf_loader_new();
+
+	g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(gtk_custom_smiley_allocated), smiley);
+	g_signal_connect(smiley->loader, "closed", G_CALLBACK(gtk_custom_smiley_closed), smiley);
+	g_signal_connect(smiley->loader, "size_prepared", G_CALLBACK(gtk_custom_smiley_size_prepared), smiley);
+}
+
+GtkIMHtmlSmiley *gtk_imhtml_smiley_create(const char *file, const char *shortcut, gboolean hide,
+		GtkIMHtmlSmileyFlags flags)
+{
+	GtkIMHtmlSmiley *smiley = g_new0(GtkIMHtmlSmiley, 1);
+	smiley->file = g_strdup(file);
+	smiley->smile = g_strdup(shortcut);
+	smiley->hidden = hide;
+	smiley->flags = flags;
+	gtk_imhtml_smiley_reload(smiley);
+	return smiley;
+}
+
+void gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley *smiley)
+{
+	g_free(smiley->smile);
+	g_free(smiley->file);
+	if (smiley->icon)
+		g_object_unref(smiley->icon);
+	if (smiley->loader)
+		g_object_unref(smiley->loader);
+	g_free(smiley);
+}
+