1 /*
2  * @file gtkimhtml.c GTK+ IMHtml
3  * @ingroup pidgin
4  */
5 
6 /* pidgin
7  *
8  * Pidgin is the legal property of its developers, whose names are too numerous
9  * to list here.  Please refer to the COPYRIGHT file distributed with this
10  * source distribution.
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
25  *
26  */
27 #define _PIDGIN_GTKIMHTML_C_
28 
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif
32 
33 #include "internal.h"
34 #include "glibcompat.h"
35 #include "pidgin.h"
36 #include "pidginstock.h"
37 #include "gtkutils.h"
38 #include "smiley.h"
39 #include "imgstore.h"
40 
41 #include "debug.h"
42 #include "util.h"
43 #include "gtkimhtml.h"
44 #include "gtksmiley.h"
45 #include "gtksourceiter.h"
46 #include "gtksourceundomanager.h"
47 #include "gtksourceview-marshal.h"
48 #include "gtkstyle.h"
49 #include <gtk/gtk.h>
50 #include <glib.h>
51 #include <gdk/gdkkeysyms.h>
52 #include <string.h>
53 #include <ctype.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <math.h>
57 #ifdef HAVE_LANGINFO_CODESET
58 #include <langinfo.h>
59 #include <locale.h>
60 #endif
61 #ifdef _WIN32
62 #include <gdk/gdkwin32.h>
63 #include <windows.h>
64 #endif
65 
66 #include <pango/pango-font.h>
67 
68 #define TOOLTIP_TIMEOUT 500
69 
70 static GtkTextViewClass *parent_class = NULL;
71 
72 struct scalable_data {
73 	GtkIMHtmlScalable *scalable;
74 	GtkTextMark *mark;
75 };
76 
77 typedef struct {
78 	GtkIMHtmlScalable *image;
79 	gpointer data;
80 	gsize datasize;
81 } GtkIMHtmlImageSave;
82 
83 struct im_image_data {
84 	int id;
85 	GtkTextMark *mark;
86 };
87 
88 struct _GtkIMHtmlLink
89 {
90 	GtkIMHtml *imhtml;
91 	gchar *url;
92 	GtkTextTag *tag;
93 };
94 
95 typedef struct _GtkIMHtmlProtocol
96 {
97 	char *name;
98 	int length;
99 
100 	gboolean (*activate)(GtkIMHtml *imhtml, GtkIMHtmlLink *link);
101 	gboolean (*context_menu)(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu);
102 } GtkIMHtmlProtocol;
103 
104 typedef struct _GtkIMHtmlFontDetail {
105 	gushort size;
106 	gchar *face;
107 	gchar *fore;
108 	gchar *back;
109 	gchar *bg;
110 	gchar *sml;
111 	gboolean underline;
112 	gboolean strike;
113 	gshort bold;
114 } GtkIMHtmlFontDetail;
115 
116 static gboolean
117 gtk_text_view_drag_motion (GtkWidget        *widget,
118                            GdkDragContext   *context,
119                            gint              x,
120                            gint              y,
121                            guint             time);
122 
123 static void preinsert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml);
124 static void insert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml);
125 static void delete_cb(GtkTextBuffer *buffer, GtkTextIter *iter, GtkTextIter *end, GtkIMHtml *imhtml);
126 static void insert_ca_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextChildAnchor *arg2, gpointer user_data);
127 static void gtk_imhtml_apply_tags_on_insert(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end);
128 void gtk_imhtml_close_tags(GtkIMHtml *imhtml, GtkTextIter *iter);
129 static void gtk_imhtml_link_drop_cb(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer user_data);
130 static void gtk_imhtml_link_drag_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y, GtkSelectionData *sd, guint info, guint t, GtkIMHtml *imhtml);
131 static void mark_set_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextMark *mark, GtkIMHtml *imhtml);
132 static void hijack_menu_cb(GtkIMHtml *imhtml, GtkMenu *menu, gpointer data);
133 static void paste_received_cb (GtkClipboard *clipboard, GtkSelectionData *selection_data, gpointer data);
134 static void paste_plaintext_received_cb (GtkClipboard *clipboard, const gchar *text, gpointer data);
135 static void imhtml_paste_insert(GtkIMHtml *imhtml, const char *text, gboolean plaintext);
136 static void imhtml_toggle_bold(GtkIMHtml *imhtml);
137 static void imhtml_toggle_italic(GtkIMHtml *imhtml);
138 static void imhtml_toggle_strike(GtkIMHtml *imhtml);
139 static void imhtml_toggle_underline(GtkIMHtml *imhtml);
140 static void imhtml_font_grow(GtkIMHtml *imhtml);
141 static void imhtml_font_shrink(GtkIMHtml *imhtml);
142 static void imhtml_clear_formatting(GtkIMHtml *imhtml);
143 static int gtk_imhtml_is_protocol(const char *text);
144 static void gtk_imhtml_activate_tag(GtkIMHtml *imhtml, GtkTextTag *tag);
145 static void gtk_imhtml_link_destroy(GtkIMHtmlLink *link);
146 
147 /* POINT_SIZE converts from AIM font sizes to a point size scale factor. */
148 #define MAX_FONT_SIZE 7
149 #define POINT_SIZE(x) (_point_sizes [MIN ((x > 0 ? x : 1), MAX_FONT_SIZE) - 1])
150 static const gdouble _point_sizes [] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736};
151 
152 enum {
153 	TARGET_HTML,
154 	TARGET_UTF8_STRING,
155 	TARGET_COMPOUND_TEXT,
156 	TARGET_STRING,
157 	TARGET_TEXT
158 };
159 
160 enum {
161 	URL_CLICKED,
162 	BUTTONS_UPDATE,
163 	TOGGLE_FORMAT,
164 	CLEAR_FORMAT,
165 	UPDATE_FORMAT,
166 	MESSAGE_SEND,
167 	UNDO,
168 	REDO,
169 	PASTE,
170 	LAST_SIGNAL
171 };
172 static guint signals [LAST_SIGNAL] = { 0 };
173 
174 static char *html_clipboard = NULL;
175 static char *text_clipboard = NULL;
176 static GtkClipboard *clipboard_selection = NULL;
177 
178 static const GtkTargetEntry selection_targets[] = {
179 #ifndef _WIN32
180 	{ "text/html", 0, TARGET_HTML },
181 #else
182 	{ "HTML Format", 0, TARGET_HTML },
183 #endif
184 	{ "UTF8_STRING", 0, TARGET_UTF8_STRING },
185 	{ "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT },
186 	{ "STRING", 0, TARGET_STRING },
187 	{ "TEXT", 0, TARGET_TEXT}};
188 
189 static const GtkTargetEntry link_drag_drop_targets[] = {
190 	GTK_IMHTML_DND_TARGETS
191 };
192 
193 #ifdef _WIN32
194 static gchar *
clipboard_win32_to_html(char * clipboard)195 clipboard_win32_to_html(char *clipboard) {
196 	const char *header;
197 	const char *begin, *end;
198 	gint start = 0;
199 	gint finish = 0;
200 	gchar *html;
201 	gchar **split;
202 	int clipboard_length = 0;
203 
204 #if 0 /* Debugging for Windows clipboard */
205 	FILE *fd;
206 
207 	purple_debug_info("imhtml clipboard", "from clipboard: %s\n", clipboard);
208 
209 	fd = g_fopen("c:\\purplecb.txt", "wb");
210 	fprintf(fd, "%s", clipboard);
211 	fclose(fd);
212 #endif
213 
214 	clipboard_length = strlen(clipboard);
215 
216 	if (!(header = strstr(clipboard, "StartFragment:")) || (header - clipboard) >= clipboard_length)
217 		return NULL;
218 	sscanf(header, "StartFragment:%d", &start);
219 
220 	if (!(header = strstr(clipboard, "EndFragment:")) || (header - clipboard) >= clipboard_length)
221 		return NULL;
222 	sscanf(header, "EndFragment:%d", &finish);
223 
224 	if (finish > clipboard_length)
225 		finish = clipboard_length;
226 
227 	if (start > finish)
228 		start = finish;
229 
230 	begin = clipboard + start;
231 
232 	end = clipboard + finish;
233 
234 	html = g_strndup(begin, end - begin);
235 
236 	/* any newlines in the string will now be \r\n, so we need to strip out the \r */
237 	split = g_strsplit(html, "\r\n", 0);
238 	g_free(html);
239 	html = g_strjoinv("\n", split);
240 	g_strfreev(split);
241 
242 #if 0 /* Debugging for Windows clipboard */
243 	purple_debug_info("imhtml clipboard", "HTML fragment: '%s'\n", html);
244 #endif
245 
246 	return html;
247 }
248 
249 static gchar *
clipboard_html_to_win32(char * html)250 clipboard_html_to_win32(char *html) {
251 	int length;
252 	GString *clipboard;
253 
254 	if (html == NULL)
255 		return NULL;
256 
257 	length = strlen(html);
258 	clipboard = g_string_new ("Version:1.0\r\n");
259 	g_string_append(clipboard, "StartHTML:0000000105\r\n");
260 	g_string_append_printf(clipboard, "EndHTML:%010d\r\n", 147 + length);
261 	g_string_append(clipboard, "StartFragment:0000000127\r\n");
262 	g_string_append_printf(clipboard, "EndFragment:%010d\r\n", 127 + length);
263 	g_string_append(clipboard, "<!--StartFragment-->\r\n");
264 	g_string_append(clipboard, html);
265 	g_string_append(clipboard, "\r\n<!--EndFragment-->");
266 
267 	return g_string_free(clipboard, FALSE);
268 }
269 
clipboard_paste_html_win32(GtkIMHtml * imhtml)270 static gboolean clipboard_paste_html_win32(GtkIMHtml *imhtml) {
271 	gboolean pasted = FALSE;
272 
273 	/* Win32 clipboard format value, and functions to convert back and
274 	 * forth between HTML and the clipboard format.
275 	 */
276 	static UINT win_html_fmt = 0;
277 
278 	/* Register HTML Format as desired clipboard format */
279 	if (!win_html_fmt)
280 		win_html_fmt = RegisterClipboardFormat("HTML Format");
281 
282 	if (gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml))
283 				&& IsClipboardFormatAvailable(win_html_fmt)) {
284 		gboolean error_reading_clipboard = FALSE;
285 		HWND hwnd = GDK_WINDOW_HWND(GTK_WIDGET(imhtml)->window);
286 
287 		if (OpenClipboard(hwnd)) {
288 			HGLOBAL hdata = GetClipboardData(win_html_fmt);
289 			if (hdata == NULL) {
290 				if (GetLastError() != ERROR_SUCCESS)
291 					error_reading_clipboard = TRUE;
292 			} else {
293 				char *buffer = GlobalLock(hdata);
294 				if (buffer == NULL) {
295 					error_reading_clipboard = TRUE;
296 				} else {
297 					char *text = clipboard_win32_to_html(
298 							buffer);
299 					imhtml_paste_insert(imhtml, text,
300 							FALSE);
301 					g_free(text);
302 					pasted = TRUE;
303 				}
304 				GlobalUnlock(hdata);
305 			}
306 
307 			CloseClipboard();
308 		} else {
309 			error_reading_clipboard = TRUE;
310 		}
311 
312 		if (error_reading_clipboard) {
313 			gchar *err_msg = g_win32_error_message(GetLastError());
314 			purple_debug_info("html clipboard",
315 					"Unable to read clipboard data: %s\n",
316 					err_msg ? err_msg : "Unknown Error");
317 			g_free(err_msg);
318 		}
319 	}
320 
321 	return pasted;
322 }
323 #endif
324 
325 static GtkSmileyTree*
gtk_smiley_tree_new(void)326 gtk_smiley_tree_new (void)
327 {
328 	return g_new0 (GtkSmileyTree, 1);
329 }
330 
331 static void
gtk_smiley_tree_insert(GtkSmileyTree * tree,GtkIMHtmlSmiley * smiley)332 gtk_smiley_tree_insert (GtkSmileyTree *tree,
333 			GtkIMHtmlSmiley *smiley)
334 {
335 	GtkSmileyTree *t = tree;
336 	const gchar *x = smiley->smile;
337 
338 	if (!(*x))
339 		return;
340 
341 	do {
342 		gchar *pos;
343 		gint index;
344 
345 		if (!t->values)
346 			t->values = g_string_new ("");
347 
348 		pos = strchr (t->values->str, *x);
349 		if (!pos) {
350 			t->values = g_string_append_c (t->values, *x);
351 			index = t->values->len - 1;
352 			t->children = g_realloc (t->children, t->values->len * sizeof (GtkSmileyTree *));
353 			t->children [index] = g_new0 (GtkSmileyTree, 1);
354 		} else
355 			index = GPOINTER_TO_INT(pos) - GPOINTER_TO_INT(t->values->str);
356 
357 		if(t->children != NULL) {
358 			t = t->children [index];
359 		}
360 
361 		x++;
362 	} while (*x);
363 
364 	t->image = smiley;
365 }
366 
367 
368 static void
gtk_smiley_tree_destroy(GtkSmileyTree * tree)369 gtk_smiley_tree_destroy (GtkSmileyTree *tree)
370 {
371 	GSList *list = g_slist_prepend (NULL, tree);
372 
373 	while (list) {
374 		GtkSmileyTree *t = list->data;
375 		gsize i;
376 		list = g_slist_remove(list, t);
377 		if (t && t->values) {
378 			for (i = 0; i < t->values->len; i++)
379 				list = g_slist_prepend (list, t->children [i]);
380 			g_string_free (t->values, TRUE);
381 			g_free (t->children);
382 		}
383 
384 		g_free (t);
385 	}
386 }
387 
388 static void (*parent_size_allocate)(GtkWidget *widget, GtkAllocation *alloc);
389 
gtk_imhtml_size_allocate(GtkWidget * widget,GtkAllocation * alloc)390 static void gtk_imhtml_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
391 {
392 	GtkIMHtml *imhtml = GTK_IMHTML(widget);
393 	GdkRectangle rect;
394 	int xminus;
395 	int height = 0, y = 0;
396 	GtkTextIter iter;
397 	gboolean scroll = TRUE;
398 
399 	gtk_text_buffer_get_end_iter(imhtml->text_buffer, &iter);
400 
401 	gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget), &rect);
402 	gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(imhtml), &iter, &y, &height);
403 
404 	if (((y + height) - (rect.y + rect.height)) > height &&
405 			gtk_text_buffer_get_char_count(imhtml->text_buffer)) {
406 		scroll = FALSE;
407 	}
408 
409 	if(imhtml->old_rect.width != rect.width || imhtml->old_rect.height != rect.height) {
410 		GList *iter = GTK_IMHTML(widget)->scalables;
411 
412 		xminus = gtk_text_view_get_left_margin(GTK_TEXT_VIEW(widget)) +
413 		         gtk_text_view_get_right_margin(GTK_TEXT_VIEW(widget));
414 
415 		while(iter){
416 			struct scalable_data *sd = iter->data;
417 			GtkIMHtmlScalable *scale = GTK_IMHTML_SCALABLE(sd->scalable);
418 			scale->scale(scale, rect.width - xminus, rect.height);
419 
420 			iter = iter->next;
421 		}
422 	}
423 
424 	imhtml->old_rect = rect;
425 	parent_size_allocate(widget, alloc);
426 
427 	/* Don't scroll here if we're in the middle of a smooth scroll */
428 	if (scroll && imhtml->scroll_time == NULL &&
429 	    GTK_WIDGET_REALIZED(imhtml))
430 		gtk_imhtml_scroll_to_end(imhtml, FALSE);
431 }
432 
433 #define DEFAULT_SEND_COLOR "#204a87"
434 #define DEFAULT_RECV_COLOR "#cc0000"
435 #define DEFAULT_HIGHLIGHT_COLOR "#AF7F00"
436 #define DEFAULT_ACTION_COLOR "#062585"
437 #define DEFAULT_WHISPER_ACTION_COLOR "#6C2585"
438 #define DEFAULT_WHISPER_COLOR "#00FF00"
439 
440 static void (*parent_style_set)(GtkWidget *widget, GtkStyle *prev_style);
441 
442 static void
gtk_imhtml_style_set(GtkWidget * widget,GtkStyle * prev_style)443 gtk_imhtml_style_set(GtkWidget *widget, GtkStyle *prev_style)
444 {
445 	int i;
446 	struct {
447 		const char *tag;
448 		const char *color;
449 		const char *def;
450 	} styles[] = {
451 		{"send-name", "send-name-color", DEFAULT_SEND_COLOR},
452 		{"receive-name", "receive-name-color", DEFAULT_RECV_COLOR},
453 		{"highlight-name", "highlight-name-color", DEFAULT_HIGHLIGHT_COLOR},
454 		{"action-name", "action-name-color", DEFAULT_ACTION_COLOR},
455 		{"whisper-action-name", "whisper-action-name-color", DEFAULT_WHISPER_ACTION_COLOR},
456 		{"whisper-name", "whisper-name-color", DEFAULT_WHISPER_COLOR},
457 		{NULL, NULL, NULL}
458 	};
459 	GtkIMHtml *imhtml = GTK_IMHTML(widget);
460 	GtkTextTagTable *table = gtk_text_buffer_get_tag_table(imhtml->text_buffer);
461 
462 	for (i = 0; styles[i].tag; i++) {
463 		GdkColor *color = NULL;
464 		GtkTextTag *tag = gtk_text_tag_table_lookup(table, styles[i].tag);
465 		if (!tag) {
466 			purple_debug_warning("gtkimhtml", "Cannot find tag '%s'. This should never happen. Please file a bug.\n", styles[i].tag);
467 			continue;
468 		}
469 		gtk_widget_style_get(widget, styles[i].color, &color, NULL);
470 		if (color) {
471 			g_object_set(tag, "foreground-gdk", color, NULL);
472 			gdk_color_free(color);
473 		} else {
474 			GdkColor defcolor;
475 			gdk_color_parse(styles[i].def, &defcolor);
476 			pidgin_style_adjust_contrast(gtk_widget_get_style(widget), &defcolor);
477 			g_object_set(tag, "foreground-gdk", &defcolor, NULL);
478 		}
479 	}
480 	parent_style_set(widget, prev_style);
481 }
482 
483 static gboolean
imhtml_get_iter_bounds(GtkIMHtml * imhtml,GtkTextIter * start,GtkTextIter * end)484 imhtml_get_iter_bounds(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end)
485 {
486 	if (imhtml->wbfo) {
487 		gtk_text_buffer_get_bounds(imhtml->text_buffer, start, end);
488 		return TRUE;
489 	} else if (imhtml->editable) {
490 		if (!gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, start, end)) {
491 			GtkTextMark *mark = gtk_text_buffer_get_insert(imhtml->text_buffer);
492 			gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, start, mark);
493 			*end = *start;
494 		}
495 		return TRUE;
496 	}
497 
498 	return FALSE;
499 }
500 
501 static void
gtk_imhtml_set_link_color(GtkIMHtml * imhtml,GtkTextTag * tag)502 gtk_imhtml_set_link_color(GtkIMHtml *imhtml, GtkTextTag *tag)
503 {
504 	GdkColor *color = NULL;
505 	gboolean visited = !!g_object_get_data(G_OBJECT(tag), "visited");
506 	gtk_widget_style_get(GTK_WIDGET(imhtml), visited ? "hyperlink-visited-color" : "hyperlink-color", &color, NULL);
507 	if (color) {
508 		g_object_set(G_OBJECT(tag), "foreground-gdk", color, NULL);
509 		gdk_color_free(color);
510 	} else {
511 		g_object_set(G_OBJECT(tag), "foreground", visited ? "#800000" : "blue", NULL);
512 	}
513 }
514 
515 static gint
gtk_imhtml_tip_paint(GtkIMHtml * imhtml)516 gtk_imhtml_tip_paint (GtkIMHtml *imhtml)
517 {
518 	PangoLayout *layout;
519 
520 	g_return_val_if_fail(GTK_IS_IMHTML(imhtml), FALSE);
521 
522 	/* We set the text in a separate function call so we can specify a
523 	   max length.  This is important so the tooltip isn't too wide for
524 	   the screen, and also because some X library function exits the
525 	   process when it can't allocate enough memory for a super wide
526 	   tooltip. */
527 	layout = gtk_widget_create_pango_layout(imhtml->tip_window, NULL);
528 	pango_layout_set_text(layout, imhtml->tip, 200);
529 
530 	gtk_paint_flat_box (imhtml->tip_window->style, imhtml->tip_window->window,
531 						GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL, imhtml->tip_window,
532 						"tooltip", 0, 0, -1, -1);
533 
534 	gtk_paint_layout (imhtml->tip_window->style, imhtml->tip_window->window, GTK_STATE_NORMAL,
535 					  FALSE, NULL, imhtml->tip_window, NULL, 4, 4, layout);
536 
537 	g_object_unref(layout);
538 	return FALSE;
539 }
540 
541 static gint
gtk_imhtml_tip(gpointer data)542 gtk_imhtml_tip (gpointer data)
543 {
544 	GtkIMHtml *imhtml = data;
545 	PangoFontMetrics *font_metrics;
546 	PangoLayout *layout;
547 	PangoFont *font;
548 
549 	gint x, y, h, w, scr_w, baseline_skip;
550 
551 	g_return_val_if_fail(GTK_IS_IMHTML(imhtml), FALSE);
552 
553 	if (!imhtml->tip || !GTK_WIDGET_DRAWABLE (GTK_WIDGET(imhtml))) {
554 		imhtml->tip_timer = 0;
555 		return FALSE;
556 	}
557 
558 	if (imhtml->tip_window){
559 		gtk_widget_destroy (imhtml->tip_window);
560 		imhtml->tip_window = NULL;
561 	}
562 
563 	imhtml->tip_timer = 0;
564 	imhtml->tip_window = gtk_window_new (GTK_WINDOW_POPUP);
565 	gtk_widget_set_app_paintable (imhtml->tip_window, TRUE);
566 	gtk_window_set_title(GTK_WINDOW(imhtml->tip_window), "GtkIMHtml");
567 	gtk_window_set_resizable (GTK_WINDOW (imhtml->tip_window), FALSE);
568 	gtk_widget_set_name (imhtml->tip_window, "gtk-tooltips");
569 	gtk_window_set_type_hint (GTK_WINDOW (imhtml->tip_window),
570 		GDK_WINDOW_TYPE_HINT_TOOLTIP);
571 	g_signal_connect_swapped (G_OBJECT (imhtml->tip_window), "expose_event",
572 							  G_CALLBACK (gtk_imhtml_tip_paint), imhtml);
573 
574 	gtk_widget_ensure_style (imhtml->tip_window);
575 
576 	/* We set the text in a separate function call so we can specify a
577 	   max length.  This is important so the tooltip isn't too wide for
578 	   the screen, and also because some X library function exits the
579 	   process when it can't allocate enough memory for a super wide
580 	   tooltip. */
581 	layout = gtk_widget_create_pango_layout(imhtml->tip_window, NULL);
582 	pango_layout_set_text(layout, imhtml->tip, 200);
583 
584 	font = pango_context_load_font(pango_layout_get_context(layout),
585 			      imhtml->tip_window->style->font_desc);
586 
587 	if (font == NULL) {
588 		char *tmp = pango_font_description_to_string(
589 					imhtml->tip_window->style->font_desc);
590 
591 		purple_debug(PURPLE_DEBUG_ERROR, "gtk_imhtml_tip",
592 			"pango_context_load_font() couldn't load font: '%s'\n",
593 			tmp);
594 		g_free(tmp);
595 
596 		g_object_unref(layout);
597 		return FALSE;
598 	}
599 
600 	font_metrics = pango_font_get_metrics(font, NULL);
601 
602 	pango_layout_get_pixel_size(layout, &scr_w, NULL);
603 
604 	baseline_skip = PANGO_PIXELS(pango_font_metrics_get_ascent(font_metrics) +
605 								pango_font_metrics_get_descent(font_metrics));
606 	w = 8 + scr_w;
607 	h = 8 + baseline_skip;
608 
609 	gdk_window_get_pointer (NULL, &x, &y, NULL);
610 	if (GTK_WIDGET_NO_WINDOW (GTK_WIDGET(imhtml)))
611 		y += GTK_WIDGET(imhtml)->allocation.y;
612 
613 	scr_w = gdk_screen_width();
614 
615 	x -= ((w >> 1) + 4);
616 
617 	if ((x + w) > scr_w)
618 		x -= (x + w) - scr_w;
619 	else if (x < 0)
620 		x = 0;
621 
622 	y = y + PANGO_PIXELS(pango_font_metrics_get_ascent(font_metrics) +
623 						pango_font_metrics_get_descent(font_metrics));
624 
625 	gtk_widget_set_size_request (imhtml->tip_window, w, h);
626 	gtk_window_move (GTK_WINDOW(imhtml->tip_window), x, y);
627 	gtk_widget_show (imhtml->tip_window);
628 
629 	pango_font_metrics_unref(font_metrics);
630 	g_object_unref(font);
631 	g_object_unref(layout);
632 
633 	return FALSE;
634 }
635 
636 static gboolean
gtk_motion_event_notify(GtkWidget * imhtml,GdkEventMotion * event,gpointer data)637 gtk_motion_event_notify(GtkWidget *imhtml, GdkEventMotion *event, gpointer data)
638 {
639 	GtkTextIter iter;
640 	GdkWindow *win = event->window;
641 	int x, y;
642 	char *tip = NULL;
643 	GSList *tags = NULL, *templist = NULL;
644 	GtkTextTag *tag = NULL, *oldprelit_tag;
645 	GtkTextChildAnchor* anchor;
646 	gboolean hand = TRUE;
647 	GdkCursor *cursor = NULL;
648 
649 	oldprelit_tag = GTK_IMHTML(imhtml)->prelit_tag;
650 
651 	gdk_window_get_pointer(GTK_WIDGET(imhtml)->window, NULL, NULL, NULL);
652 	gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(imhtml), GTK_TEXT_WINDOW_WIDGET,
653 	                                      event->x, event->y, &x, &y);
654 	gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml), &iter, x, y);
655 	tags = gtk_text_iter_get_tags(&iter);
656 
657 	templist = tags;
658 	while (templist) {
659 		tag = templist->data;
660 		tip = g_object_get_data(G_OBJECT(tag), "link_url");
661 		if (tip)
662 			break;
663 		templist = templist->next;
664 	}
665 
666 	if (tip && (!tag || !g_object_get_data(G_OBJECT(tag), "visited"))) {
667 		GTK_IMHTML(imhtml)->prelit_tag = tag;
668 		if (tag != oldprelit_tag) {
669 			GdkColor *pre = NULL;
670 			gtk_widget_style_get(GTK_WIDGET(imhtml), "hyperlink-prelight-color", &pre, NULL);
671 			if (pre) {
672 				g_object_set(G_OBJECT(tag), "foreground-gdk", pre, NULL);
673 				gdk_color_free(pre);
674 			} else
675 				g_object_set(G_OBJECT(tag), "foreground", "#70a0ff", NULL);
676 		}
677 	} else {
678 		GTK_IMHTML(imhtml)->prelit_tag = NULL;
679 	}
680 
681 	if ((oldprelit_tag != NULL) && (GTK_IMHTML(imhtml)->prelit_tag != oldprelit_tag)) {
682 		gtk_imhtml_set_link_color(GTK_IMHTML(imhtml), oldprelit_tag);
683 	}
684 
685 	if (GTK_IMHTML(imhtml)->tip) {
686 		if (tip == GTK_IMHTML(imhtml)->tip) {
687 			g_slist_free(tags);
688 			return FALSE;
689 		}
690 		/* We've left the cell.  Remove the timeout and create a new one below */
691 		if (GTK_IMHTML(imhtml)->tip_window) {
692 			gtk_widget_destroy(GTK_IMHTML(imhtml)->tip_window);
693 			GTK_IMHTML(imhtml)->tip_window = NULL;
694 		}
695 		if (GTK_IMHTML(imhtml)->editable)
696 			cursor = GTK_IMHTML(imhtml)->text_cursor;
697 		else
698 			cursor = GTK_IMHTML(imhtml)->arrow_cursor;
699 		if (GTK_IMHTML(imhtml)->tip_timer)
700 			g_source_remove(GTK_IMHTML(imhtml)->tip_timer);
701 		GTK_IMHTML(imhtml)->tip_timer = 0;
702 	}
703 
704 	/* If we don't have a tip from a URL, let's see if we have a tip from a smiley */
705 	anchor = gtk_text_iter_get_child_anchor(&iter);
706 	if (anchor) {
707 		tip = g_object_get_data(G_OBJECT(anchor), "gtkimhtml_tiptext");
708 		hand = FALSE;
709 	}
710 
711 	if (tip && *tip) {
712 		GTK_IMHTML(imhtml)->tip_timer = g_timeout_add (TOOLTIP_TIMEOUT,
713 							       gtk_imhtml_tip, imhtml);
714 	} else if (!tip) {
715 		hand = FALSE;
716 		for (templist = tags; templist; templist = templist->next) {
717 			tag = templist->data;
718 			if ((tip = g_object_get_data(G_OBJECT(tag), "cursor"))) {
719 				hand = TRUE;
720 				break;
721 			}
722 		}
723 	}
724 
725 	if (hand && !(GTK_IMHTML(imhtml)->editable))
726 		cursor = GTK_IMHTML(imhtml)->hand_cursor;
727 
728 	if (cursor)
729 		gdk_window_set_cursor(win, cursor);
730 
731 	GTK_IMHTML(imhtml)->tip = tip;
732 	g_slist_free(tags);
733 	return FALSE;
734 }
735 
736 static gboolean
gtk_enter_event_notify(GtkWidget * imhtml,GdkEventCrossing * event,gpointer data)737 gtk_enter_event_notify(GtkWidget *imhtml, GdkEventCrossing *event, gpointer data)
738 {
739 	if (GTK_IMHTML(imhtml)->editable)
740 		gdk_window_set_cursor(
741 				gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml),
742 					GTK_TEXT_WINDOW_TEXT),
743 				GTK_IMHTML(imhtml)->text_cursor);
744 	else
745 		gdk_window_set_cursor(
746 				gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml),
747 					GTK_TEXT_WINDOW_TEXT),
748 				GTK_IMHTML(imhtml)->arrow_cursor);
749 
750 	/* propagate the event normally */
751 	return FALSE;
752 }
753 
754 static gboolean
gtk_leave_event_notify(GtkWidget * imhtml,GdkEventCrossing * event,gpointer data)755 gtk_leave_event_notify(GtkWidget *imhtml, GdkEventCrossing *event, gpointer data)
756 {
757 	/* when leaving the widget, clear any current & pending tooltips and restore the cursor */
758 	if (GTK_IMHTML(imhtml)->prelit_tag) {
759 		gtk_imhtml_set_link_color(GTK_IMHTML(imhtml), GTK_IMHTML(imhtml)->prelit_tag);
760 		GTK_IMHTML(imhtml)->prelit_tag = NULL;
761 	}
762 
763 	if (GTK_IMHTML(imhtml)->tip_window) {
764 		gtk_widget_destroy(GTK_IMHTML(imhtml)->tip_window);
765 		GTK_IMHTML(imhtml)->tip_window = NULL;
766 	}
767 	if (GTK_IMHTML(imhtml)->tip_timer) {
768 		g_source_remove(GTK_IMHTML(imhtml)->tip_timer);
769 		GTK_IMHTML(imhtml)->tip_timer = 0;
770 	}
771 	gdk_window_set_cursor(
772 			gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml),
773 				GTK_TEXT_WINDOW_TEXT), NULL);
774 
775 	/* propagate the event normally */
776 	return FALSE;
777 }
778 
779 static gint
gtk_imhtml_expose_event(GtkWidget * widget,GdkEventExpose * event)780 gtk_imhtml_expose_event (GtkWidget      *widget,
781 			 GdkEventExpose *event)
782 {
783 	GtkTextIter start, end, cur;
784 	int buf_x, buf_y;
785 	GdkRectangle visible_rect;
786 	cairo_t *cr = gdk_cairo_create(GDK_DRAWABLE(event->window));
787 	GdkColor gcolor;
788 
789 	gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget), &visible_rect);
790 	gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget),
791 					      GTK_TEXT_WINDOW_TEXT,
792 					      visible_rect.x,
793 					      visible_rect.y,
794 					      &visible_rect.x,
795 					      &visible_rect.y);
796 
797 	gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT,
798 	                                      event->area.x, event->area.y, &buf_x, &buf_y);
799 
800 	if (GTK_IMHTML(widget)->editable || GTK_IMHTML(widget)->wbfo) {
801 
802 		if (GTK_IMHTML(widget)->edit.background) {
803 			gdk_color_parse(GTK_IMHTML(widget)->edit.background, &gcolor);
804 			gdk_cairo_set_source_color(cr, &gcolor);
805 		} else {
806 			gdk_cairo_set_source_color(cr, &(widget->style->base[GTK_WIDGET_STATE(widget)]));
807 		}
808 
809 		cairo_rectangle(cr,
810 		                visible_rect.x, visible_rect.y,
811 		                visible_rect.width, visible_rect.height);
812 		cairo_fill(cr);
813 		cairo_destroy(cr);
814 
815 		if (GTK_WIDGET_CLASS (parent_class)->expose_event)
816 			return (* GTK_WIDGET_CLASS (parent_class)->expose_event)
817 				(widget, event);
818 		return FALSE;
819 
820 	}
821 
822 	gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &start, buf_x, buf_y);
823 	gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &end,
824 	                                   buf_x + event->area.width, buf_y + event->area.height);
825 
826 	gtk_text_iter_order(&start, &end);
827 
828 	cur = start;
829 
830 	while (gtk_text_iter_in_range(&cur, &start, &end)) {
831 		GSList *tags = gtk_text_iter_get_tags(&cur);
832 		GSList *l;
833 
834 		for (l = tags; l; l = l->next) {
835 			GtkTextTag *tag = l->data;
836 			GdkRectangle rect;
837 			GdkRectangle tag_area;
838 			const char *color;
839 
840 			if (strncmp(tag->name, "BACKGROUND ", 11))
841 				continue;
842 
843 			if (gtk_text_iter_ends_tag(&cur, tag))
844 				continue;
845 
846 			gtk_text_view_get_iter_location(GTK_TEXT_VIEW(widget), &cur, &tag_area);
847 			gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget),
848 			                                      GTK_TEXT_WINDOW_TEXT,
849 			                                      tag_area.x,
850 			                                      tag_area.y,
851 			                                      &tag_area.x,
852 			                                      &tag_area.y);
853 			rect.x = visible_rect.x;
854 			rect.y = tag_area.y;
855 			rect.width = visible_rect.width;
856 
857 			do
858 				gtk_text_iter_forward_to_tag_toggle(&cur, tag);
859 			while (!gtk_text_iter_is_end(&cur) && gtk_text_iter_begins_tag(&cur, tag));
860 
861 			gtk_text_view_get_iter_location(GTK_TEXT_VIEW(widget), &cur, &tag_area);
862 			gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget),
863 			                                      GTK_TEXT_WINDOW_TEXT,
864 			                                      tag_area.x,
865 			                                      tag_area.y,
866 			                                      &tag_area.x,
867 			                                      &tag_area.y);
868 
869 
870 			rect.height = tag_area.y + tag_area.height - rect.y
871 				+ gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(widget))
872 				+ gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(widget));
873 
874 			color = tag->name + 11;
875 
876 			if (!gdk_color_parse(color, &gcolor)) {
877 				gchar tmp[8];
878 				tmp[0] = '#';
879 				strncpy(&tmp[1], color, 7);
880 				tmp[7] = '\0';
881 				if (!gdk_color_parse(tmp, &gcolor))
882 					gdk_color_parse("white", &gcolor);
883 			}
884 			gdk_cairo_set_source_color(cr, &gcolor);
885 
886 			cairo_rectangle(cr,
887 			                rect.x, rect.y,
888 			                rect.width, rect.height);
889 			cairo_fill(cr);
890 			gtk_text_iter_backward_char(&cur); /* go back one, in case the end is the begining is the end
891 			                                    * note that above, we always moved cur ahead by at least
892 			                                    * one character */
893 			break;
894 		}
895 
896 		g_slist_free(tags);
897 
898 		/* loop until another tag begins, or no tag begins */
899 		while (gtk_text_iter_forward_to_tag_toggle(&cur, NULL) &&
900 		       !gtk_text_iter_is_end(&cur) &&
901 		       !gtk_text_iter_begins_tag(&cur, NULL));
902 	}
903 
904 	cairo_destroy(cr);
905 
906 	if (GTK_WIDGET_CLASS (parent_class)->expose_event)
907 		return (* GTK_WIDGET_CLASS (parent_class)->expose_event)
908 			(widget, event);
909 
910 	return FALSE;
911 }
912 
913 
paste_unformatted_cb(GtkMenuItem * menu,GtkIMHtml * imhtml)914 static void paste_unformatted_cb(GtkMenuItem *menu, GtkIMHtml *imhtml)
915 {
916 	GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD);
917 
918 	gtk_clipboard_request_text(clipboard, paste_plaintext_received_cb, imhtml);
919 
920 }
921 
clear_formatting_cb(GtkMenuItem * menu,GtkIMHtml * imhtml)922 static void clear_formatting_cb(GtkMenuItem *menu, GtkIMHtml *imhtml)
923 {
924 	gtk_imhtml_clear_formatting(imhtml);
925 }
926 
disable_smiley_selected(GtkMenuItem * item,GtkIMHtml * imhtml)927 static void disable_smiley_selected(GtkMenuItem *item, GtkIMHtml *imhtml)
928 {
929 	GtkTextIter start, end;
930 	GtkTextMark *mark;
931 	char *text;
932 
933 	if (!gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end))
934 		return;
935 
936 	text = gtk_imhtml_get_markup_range(imhtml, &start, &end);
937 
938 	mark = gtk_text_buffer_get_selection_bound(imhtml->text_buffer);
939 	gtk_text_buffer_delete_selection(imhtml->text_buffer, FALSE, FALSE);
940 
941 	gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &start, mark);
942 	gtk_imhtml_insert_html_at_iter(imhtml, text, GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_SMILEY, &start);
943 
944 	g_free(text);
945 }
946 
hijack_menu_cb(GtkIMHtml * imhtml,GtkMenu * menu,gpointer data)947 static void hijack_menu_cb(GtkIMHtml *imhtml, GtkMenu *menu, gpointer data)
948 {
949 	GtkWidget *menuitem;
950 	GtkTextIter start, end;
951 
952 	menuitem = gtk_menu_item_new_with_mnemonic(_("Paste as Plain _Text"));
953 	gtk_widget_show(menuitem);
954 	/*
955 	 * TODO: gtk_clipboard_wait_is_text_available() iterates the glib
956 	 *       mainloop, which tends to be a source of bugs.  It would
957 	 *       be good to audit this or change it to not wait.
958 	 */
959 	gtk_widget_set_sensitive(menuitem,
960 	                        (imhtml->editable &&
961 	                        gtk_clipboard_wait_is_text_available(
962 	                        gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD))));
963 	/* put it after "Paste" */
964 	gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menuitem, 3);
965 
966 	g_signal_connect(G_OBJECT(menuitem), "activate",
967 					 G_CALLBACK(paste_unformatted_cb), imhtml);
968 
969 	menuitem = gtk_menu_item_new_with_mnemonic(_("_Reset formatting"));
970 	gtk_widget_show(menuitem);
971 	gtk_widget_set_sensitive(menuitem, imhtml->editable);
972 	/* put it after Delete */
973 	gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menuitem, 5);
974 
975 	g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(clear_formatting_cb), imhtml);
976 
977 	menuitem = gtk_menu_item_new_with_mnemonic(_("Disable _smileys in selected text"));
978 	gtk_widget_show(menuitem);
979 	if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) {
980 		g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(disable_smiley_selected), imhtml);
981 	} else {
982 		gtk_widget_set_sensitive(menuitem, FALSE);
983 	}
984 	gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menuitem, 6);
985 }
986 
987 static char *
ucs2_order(gboolean swap)988 ucs2_order(gboolean swap)
989 {
990 	gboolean be;
991 
992 	be = G_BYTE_ORDER == G_BIG_ENDIAN;
993 	be = swap ? be : !be;
994 
995 	if (be)
996 		return "UTF-16BE";
997 	else
998 		return "UTF-16LE";
999 
1000 }
1001 
1002 /* Convert from UTF-16LE to UTF-8, stripping the BOM if one is present.*/
1003 static gchar *
utf16_to_utf8_with_bom_check(gchar * data,guint len)1004 utf16_to_utf8_with_bom_check(gchar *data, guint len) {
1005 	char *fromcode = NULL;
1006 	GError *error = NULL;
1007 	guint16 c;
1008 	gchar *utf8_ret;
1009 
1010 	/*
1011 	 * Unicode Techinical Report 20
1012 	 * ( http://www.unicode.org/unicode/reports/tr20/ ) says to treat an
1013 	 * initial 0xfeff (ZWNBSP) as a byte order indicator so that is
1014 	 * what we do.  If there is no indicator assume it is in the default
1015 	 * order
1016 	 */
1017 
1018 	memcpy(&c, data, 2);
1019 	switch (c) {
1020 	case 0xfeff:
1021 	case 0xfffe:
1022 		fromcode = ucs2_order(c == 0xfeff);
1023 		data += 2;
1024 		len -= 2;
1025 		break;
1026 	default:
1027 		fromcode = "UTF-16";
1028 		break;
1029 	}
1030 
1031 	utf8_ret = g_convert(data, len, "UTF-8", fromcode, NULL, NULL, &error);
1032 
1033 	if (error) {
1034 		purple_debug_warning("gtkimhtml", "g_convert error: %s\n", error->message);
1035 		g_error_free(error);
1036 	}
1037 	return utf8_ret;
1038 }
1039 
1040 
gtk_imhtml_clipboard_get(GtkClipboard * clipboard,GtkSelectionData * selection_data,guint info,GtkIMHtml * imhtml)1041 static void gtk_imhtml_clipboard_get(GtkClipboard *clipboard, GtkSelectionData *selection_data, guint info, GtkIMHtml *imhtml) {
1042 	char *text = NULL;
1043 	gboolean primary = (clipboard != clipboard_selection);
1044 	GtkTextIter start, end;
1045 
1046 	if (primary) {
1047 		GtkTextMark *sel = NULL, *ins = NULL;
1048 
1049 		g_return_if_fail(imhtml != NULL);
1050 
1051 		ins = gtk_text_buffer_get_insert(imhtml->text_buffer);
1052 		sel = gtk_text_buffer_get_selection_bound(imhtml->text_buffer);
1053 		gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &start, sel);
1054 		gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &end, ins);
1055 	}
1056 
1057 	if (info == TARGET_HTML) {
1058 		char *selection;
1059 #ifndef _WIN32
1060 		gsize len;
1061 		if (primary) {
1062 			text = gtk_imhtml_get_markup_range(imhtml, &start, &end);
1063 		} else
1064 			text = html_clipboard;
1065 
1066 		/* Mozilla asks that we start our text/html with the Unicode byte order mark */
1067 		selection = g_convert(text, -1, "UTF-16", "UTF-8", NULL, &len, NULL);
1068 		gtk_selection_data_set(selection_data, gdk_atom_intern("text/html", FALSE), 16, (const guchar *)selection, len);
1069 #else
1070 		selection = clipboard_html_to_win32(html_clipboard);
1071 		gtk_selection_data_set(selection_data, gdk_atom_intern("HTML Format", FALSE), 8, (const guchar *)selection, strlen(selection));
1072 #endif
1073 		g_free(selection);
1074 	} else {
1075 		if (primary) {
1076 			text = gtk_imhtml_get_text(imhtml, &start, &end);
1077 		} else
1078 			text = text_clipboard;
1079 		gtk_selection_data_set_text(selection_data, text, strlen(text));
1080 	}
1081 	if (primary) /* This was allocated here */
1082 		g_free(text);
1083  }
1084 
gtk_imhtml_primary_clipboard_clear(GtkClipboard * clipboard,GtkIMHtml * imhtml)1085 static void gtk_imhtml_primary_clipboard_clear(GtkClipboard *clipboard, GtkIMHtml *imhtml)
1086 {
1087 	GtkTextIter insert;
1088 	GtkTextIter selection_bound;
1089 
1090 	gtk_text_buffer_get_iter_at_mark (imhtml->text_buffer, &insert,
1091 					  gtk_text_buffer_get_mark (imhtml->text_buffer, "insert"));
1092 	gtk_text_buffer_get_iter_at_mark (imhtml->text_buffer, &selection_bound,
1093 					  gtk_text_buffer_get_mark (imhtml->text_buffer, "selection_bound"));
1094 
1095 	if (!gtk_text_iter_equal (&insert, &selection_bound))
1096 		gtk_text_buffer_move_mark (imhtml->text_buffer,
1097 					   gtk_text_buffer_get_mark (imhtml->text_buffer, "selection_bound"),
1098 					   &insert);
1099 }
1100 
gtk_imhtml_clipboard_clear(GtkClipboard * clipboard,GtkSelectionData * sel_data,guint info,gpointer user_data_or_owner)1101 static void gtk_imhtml_clipboard_clear (GtkClipboard *clipboard, GtkSelectionData *sel_data,
1102 				 guint info, gpointer user_data_or_owner)
1103 {
1104 }
1105 
copy_clipboard_cb(GtkIMHtml * imhtml,gpointer unused)1106 static void copy_clipboard_cb(GtkIMHtml *imhtml, gpointer unused)
1107 {
1108 	GtkTextIter start, end;
1109 	if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) {
1110 		if (!clipboard_selection)
1111 			clipboard_selection = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD);
1112 		gtk_clipboard_set_with_data(clipboard_selection,
1113 						 selection_targets, sizeof(selection_targets) / sizeof(GtkTargetEntry),
1114 						 (GtkClipboardGetFunc)gtk_imhtml_clipboard_get,
1115 						 (GtkClipboardClearFunc)gtk_imhtml_clipboard_clear, NULL);
1116 
1117 		g_free(html_clipboard);
1118 		g_free(text_clipboard);
1119 
1120 		html_clipboard = gtk_imhtml_get_markup_range(imhtml, &start, &end);
1121 		text_clipboard = gtk_imhtml_get_text(imhtml, &start, &end);
1122 	}
1123 
1124 	g_signal_stop_emission_by_name(imhtml, "copy-clipboard");
1125 }
1126 
cut_clipboard_cb(GtkIMHtml * imhtml,gpointer unused)1127 static void cut_clipboard_cb(GtkIMHtml *imhtml, gpointer unused)
1128 {
1129 	GtkTextIter start, end;
1130 	if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) {
1131 		if (!clipboard_selection)
1132 			clipboard_selection = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD);
1133 		gtk_clipboard_set_with_data(clipboard_selection,
1134 						 selection_targets, sizeof(selection_targets) / sizeof(GtkTargetEntry),
1135 						 (GtkClipboardGetFunc)gtk_imhtml_clipboard_get,
1136 						 (GtkClipboardClearFunc)gtk_imhtml_clipboard_clear, NULL);
1137 
1138 		g_free(html_clipboard);
1139 		g_free(text_clipboard);
1140 
1141 		html_clipboard = gtk_imhtml_get_markup_range(imhtml, &start, &end);
1142 		text_clipboard = gtk_imhtml_get_text(imhtml, &start, &end);
1143 
1144 		if (imhtml->editable)
1145 			gtk_text_buffer_delete_selection(imhtml->text_buffer, FALSE, FALSE);
1146 	}
1147 
1148 	g_signal_stop_emission_by_name(imhtml, "cut-clipboard");
1149 }
1150 
imhtml_paste_insert(GtkIMHtml * imhtml,const char * text,gboolean plaintext)1151 static void imhtml_paste_insert(GtkIMHtml *imhtml, const char *text, gboolean plaintext)
1152 {
1153 	GtkTextIter iter;
1154 	GtkIMHtmlOptions flags = plaintext ? GTK_IMHTML_NO_SMILEY : (GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_COMMENTS);
1155 
1156 	/* Delete any currently selected text */
1157 	gtk_text_buffer_delete_selection(imhtml->text_buffer, TRUE, TRUE);
1158 
1159 	gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, gtk_text_buffer_get_insert(imhtml->text_buffer));
1160 	if (!imhtml->wbfo && !plaintext)
1161 		gtk_imhtml_close_tags(imhtml, &iter);
1162 
1163 	gtk_imhtml_insert_html_at_iter(imhtml, text, flags, &iter);
1164 	gtk_text_buffer_move_mark_by_name(imhtml->text_buffer, "insert", &iter);
1165 	gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(imhtml), gtk_text_buffer_get_insert(imhtml->text_buffer),
1166 	                             0, FALSE, 0.0, 0.0);
1167 	if (!imhtml->wbfo && !plaintext)
1168 		gtk_imhtml_close_tags(imhtml, &iter);
1169 
1170 }
1171 
paste_plaintext_received_cb(GtkClipboard * clipboard,const gchar * text,gpointer data)1172 static void paste_plaintext_received_cb (GtkClipboard *clipboard, const gchar *text, gpointer data)
1173 {
1174 	char *tmp;
1175 
1176 	if (text == NULL || !(*text))
1177 		return;
1178 
1179 	tmp = g_markup_escape_text(text, -1);
1180 	imhtml_paste_insert(data, tmp, TRUE);
1181 	g_free(tmp);
1182 }
1183 
paste_received_cb(GtkClipboard * clipboard,GtkSelectionData * selection_data,gpointer data)1184 static void paste_received_cb (GtkClipboard *clipboard, GtkSelectionData *selection_data, gpointer data)
1185 {
1186 	char *text;
1187 	GtkIMHtml *imhtml = data;
1188 
1189 	if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml)))
1190 		return;
1191 
1192 	if (imhtml->wbfo || selection_data->length <= 0) {
1193 		gtk_clipboard_request_text(clipboard, paste_plaintext_received_cb, imhtml);
1194 		return;
1195 	} else {
1196 #if 0
1197 		/* Here's some debug code, for figuring out what sent to us over the clipboard. */
1198 		{
1199 		int i;
1200 
1201 		purple_debug_misc("gtkimhtml", "In paste_received_cb():\n\tformat = %d, length = %d\n\t",
1202 	                        selection_data->format, selection_data->length);
1203 
1204 		for (i = 0; i < (/*(selection_data->format / 8) **/ selection_data->length); i++) {
1205 			if ((i % 70) == 0)
1206 				printf("\n\t");
1207 			if (selection_data->data[i] == '\0')
1208 				printf(".");
1209 			else
1210 				printf("%c", selection_data->data[i]);
1211 		}
1212 		printf("\n");
1213 		}
1214 #endif
1215 
1216 		text = g_malloc(selection_data->length + 1);
1217 		memcpy(text, selection_data->data, selection_data->length);
1218 		/* Make sure the paste data is null-terminated.  Given that
1219 		 * we're passed length (but assume later that it is
1220 		 * null-terminated), this seems sensible to me.
1221 		 */
1222 		text[selection_data->length] = '\0';
1223 	}
1224 
1225 #ifdef _WIN32
1226 	if (gtk_selection_data_get_data_type(selection_data) == gdk_atom_intern("HTML Format", FALSE)) {
1227 		char *tmp = clipboard_win32_to_html(text);
1228 		g_free(text);
1229 		text = tmp;
1230 	}
1231 #endif
1232 
1233 	if (selection_data->length >= 2 &&
1234 		(*(guint16 *)text == 0xfeff || *(guint16 *)text == 0xfffe)) {
1235 		/* This is UTF-16 */
1236 		char *utf8 = utf16_to_utf8_with_bom_check(text, selection_data->length);
1237 		g_free(text);
1238 		text = utf8;
1239 		if (!text) {
1240 			purple_debug_warning("gtkimhtml", "g_convert from UTF-16 failed in paste_received_cb\n");
1241 			return;
1242 		}
1243 	}
1244 
1245 	if (!(*text) || !g_utf8_validate(text, -1, NULL)) {
1246 		purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in paste_received_cb\n");
1247 		g_free(text);
1248 		return;
1249 	}
1250 
1251 	imhtml_paste_insert(imhtml, text, FALSE);
1252 	g_free(text);
1253 }
1254 
1255 
smart_backspace_cb(GtkIMHtml * imhtml,gpointer blah)1256 static void smart_backspace_cb(GtkIMHtml *imhtml, gpointer blah)
1257 {
1258 	GtkTextIter iter;
1259 	GtkTextChildAnchor* anchor;
1260 	char * text;
1261 	gint offset;
1262 
1263 	if (!imhtml->editable)
1264 		return;
1265 
1266 	gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, gtk_text_buffer_get_insert(imhtml->text_buffer));
1267 
1268 	/* Get the character before the insertion point */
1269 	offset = gtk_text_iter_get_offset(&iter);
1270 	if (offset <= 0)
1271 		return;
1272 
1273 	gtk_text_iter_backward_char(&iter);
1274 	anchor = gtk_text_iter_get_child_anchor(&iter);
1275 
1276 	if (!anchor)
1277 		return; /* No smiley here */
1278 
1279 	text = g_object_get_data(G_OBJECT(anchor), "gtkimhtml_plaintext");
1280 	if (!text)
1281 		return;
1282 
1283 	/* ok, then we need to insert the image buffer text before the anchor */
1284 	gtk_text_buffer_insert(imhtml->text_buffer, &iter, text, -1);
1285 }
1286 
paste_clipboard_cb(GtkIMHtml * imhtml,gpointer blah)1287 static void paste_clipboard_cb(GtkIMHtml *imhtml, gpointer blah)
1288 {
1289 #ifdef _WIN32
1290 	/* If we're on windows, let's see if we can get data from the HTML Format
1291 	   clipboard before we try to paste from the GTK buffer */
1292 	if (!clipboard_paste_html_win32(imhtml) && gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml))) {
1293 		GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD);
1294 		gtk_clipboard_request_text(clipboard, paste_plaintext_received_cb, imhtml);
1295 
1296 	}
1297 #else
1298 	GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD);
1299 	gtk_clipboard_request_contents(clipboard, gdk_atom_intern("text/html", FALSE),
1300 				       paste_received_cb, imhtml);
1301 #endif
1302 	g_signal_stop_emission_by_name(imhtml, "paste-clipboard");
1303 }
1304 
imhtml_realized_remove_primary(GtkIMHtml * imhtml,gpointer unused)1305 static void imhtml_realized_remove_primary(GtkIMHtml *imhtml, gpointer unused)
1306 {
1307 	gtk_text_buffer_remove_selection_clipboard(GTK_IMHTML(imhtml)->text_buffer,
1308 	                                            gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY));
1309 
1310 }
1311 
imhtml_destroy_add_primary(GtkIMHtml * imhtml,gpointer unused)1312 static void imhtml_destroy_add_primary(GtkIMHtml *imhtml, gpointer unused)
1313 {
1314 	gtk_text_buffer_add_selection_clipboard(GTK_IMHTML(imhtml)->text_buffer,
1315 	                                        gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY));
1316 }
1317 
mark_set_so_update_selection_cb(GtkTextBuffer * buffer,GtkTextIter * arg1,GtkTextMark * mark,GtkIMHtml * imhtml)1318 static void mark_set_so_update_selection_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextMark *mark, GtkIMHtml *imhtml)
1319 {
1320 	if (gtk_text_buffer_get_selection_bounds(buffer, NULL, NULL)) {
1321 		gtk_clipboard_set_with_owner(gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY),
1322 		                             selection_targets, sizeof(selection_targets) / sizeof(GtkTargetEntry),
1323 		                             (GtkClipboardGetFunc)gtk_imhtml_clipboard_get,
1324 		                             (GtkClipboardClearFunc)gtk_imhtml_primary_clipboard_clear, G_OBJECT(imhtml));
1325 	}
1326 }
1327 
gtk_imhtml_button_press_event(GtkIMHtml * imhtml,GdkEventButton * event,gpointer unused)1328 static gboolean gtk_imhtml_button_press_event(GtkIMHtml *imhtml, GdkEventButton *event, gpointer unused)
1329 {
1330 	if (event->button == 2) {
1331 		int x, y;
1332 		GtkTextIter iter;
1333 		GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY);
1334 
1335 		if (!imhtml->editable)
1336 			return FALSE;
1337 
1338 		gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(imhtml),
1339 		                                      GTK_TEXT_WINDOW_TEXT,
1340 		                                      event->x,
1341 		                                      event->y,
1342 		                                      &x,
1343 		                                      &y);
1344 		gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml), &iter, x, y);
1345 		gtk_text_buffer_place_cursor(imhtml->text_buffer, &iter);
1346 
1347 		gtk_clipboard_request_contents(clipboard, gdk_atom_intern("text/html", FALSE),
1348 				       paste_received_cb, imhtml);
1349 
1350 		return TRUE;
1351         }
1352 
1353 	return FALSE;
1354 }
1355 
1356 static void
gtk_imhtml_undo(GtkIMHtml * imhtml)1357 gtk_imhtml_undo(GtkIMHtml *imhtml)
1358 {
1359 	g_return_if_fail(GTK_IS_IMHTML(imhtml));
1360 	if (imhtml->editable &&
1361 			gtk_source_undo_manager_can_undo(imhtml->undo_manager))
1362 		gtk_source_undo_manager_undo(imhtml->undo_manager);
1363 }
1364 
1365 static void
gtk_imhtml_redo(GtkIMHtml * imhtml)1366 gtk_imhtml_redo(GtkIMHtml *imhtml)
1367 {
1368 	g_return_if_fail(GTK_IS_IMHTML(imhtml));
1369 	if (imhtml->editable &&
1370 			gtk_source_undo_manager_can_redo(imhtml->undo_manager))
1371 		gtk_source_undo_manager_redo(imhtml->undo_manager);
1372 
1373 }
1374 
imhtml_message_send(GtkIMHtml * imhtml)1375 static gboolean imhtml_message_send(GtkIMHtml *imhtml)
1376 {
1377 	return FALSE;
1378 }
1379 
1380 static void
imhtml_paste_cb(GtkIMHtml * imhtml,const char * str)1381 imhtml_paste_cb(GtkIMHtml *imhtml, const char *str)
1382 {
1383 	if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml)))
1384 		return;
1385 
1386 	if (!str || !*str || purple_strequal(str, "html"))
1387 		g_signal_emit_by_name(imhtml, "paste_clipboard");
1388 	else if (purple_strequal(str, "text"))
1389 		paste_unformatted_cb(NULL, imhtml);
1390 }
1391 
imhtml_toggle_format(GtkIMHtml * imhtml,GtkIMHtmlButtons buttons)1392 static void imhtml_toggle_format(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons)
1393 {
1394 	/* since this function is the handler for the formatting keystrokes,
1395 	   we need to check here that the formatting attempted is permitted */
1396 	buttons &= imhtml->format_functions;
1397 
1398 	switch (buttons) {
1399 	case GTK_IMHTML_BOLD:
1400 		imhtml_toggle_bold(imhtml);
1401 		break;
1402 	case GTK_IMHTML_ITALIC:
1403 		imhtml_toggle_italic(imhtml);
1404 		break;
1405 	case GTK_IMHTML_UNDERLINE:
1406 		imhtml_toggle_underline(imhtml);
1407 		break;
1408 	case GTK_IMHTML_STRIKE:
1409 		imhtml_toggle_strike(imhtml);
1410 		break;
1411 	case GTK_IMHTML_SHRINK:
1412 		imhtml_font_shrink(imhtml);
1413 		break;
1414 	case GTK_IMHTML_GROW:
1415 		imhtml_font_grow(imhtml);
1416 		break;
1417 	default:
1418 		break;
1419 	}
1420 }
1421 
1422 static void
gtk_imhtml_finalize(GObject * object)1423 gtk_imhtml_finalize (GObject *object)
1424 {
1425 	GtkIMHtml *imhtml = GTK_IMHTML(object);
1426 	GList *scalables;
1427 	GSList *l;
1428 
1429 	if (imhtml->scroll_src)
1430 		g_source_remove(imhtml->scroll_src);
1431 	if (imhtml->scroll_time)
1432 		g_timer_destroy(imhtml->scroll_time);
1433 
1434 	g_hash_table_destroy(imhtml->smiley_data);
1435 	gtk_smiley_tree_destroy(imhtml->default_smilies);
1436 	gdk_cursor_unref(imhtml->hand_cursor);
1437 	gdk_cursor_unref(imhtml->arrow_cursor);
1438 	gdk_cursor_unref(imhtml->text_cursor);
1439 
1440 	if(imhtml->tip_window){
1441 		gtk_widget_destroy(imhtml->tip_window);
1442 	}
1443 	if(imhtml->tip_timer)
1444 		g_source_remove(imhtml->tip_timer);
1445 
1446 	for(scalables = imhtml->scalables; scalables; scalables = scalables->next) {
1447 		struct scalable_data *sd = scalables->data;
1448 		GtkIMHtmlScalable *scale = GTK_IMHTML_SCALABLE(sd->scalable);
1449 		scale->free(scale);
1450 		g_free(sd);
1451 	}
1452 
1453 	for (l = imhtml->im_images; l; l = l->next) {
1454 		struct im_image_data *img_data = l->data;
1455 		if (imhtml->funcs->image_unref)
1456 			imhtml->funcs->image_unref(img_data->id);
1457 		g_free(img_data);
1458 	}
1459 
1460 	g_list_free(imhtml->scalables);
1461 	g_slist_free(imhtml->im_images);
1462 	g_queue_free(imhtml->animations);
1463 	g_free(imhtml->protocol_name);
1464 	g_free(imhtml->search_string);
1465 	g_object_unref(imhtml->undo_manager);
1466 	G_OBJECT_CLASS(parent_class)->finalize (object);
1467 
1468 }
1469 
1470 static GtkIMHtmlProtocol *
imhtml_find_protocol(const char * url,gboolean reverse)1471 imhtml_find_protocol(const char *url, gboolean reverse)
1472 {
1473 	GtkIMHtmlClass *klass;
1474 	GList *iter;
1475 	GtkIMHtmlProtocol *proto = NULL;
1476 	int length = reverse ? strlen(url) : 0;
1477 
1478 	klass = g_type_class_ref(GTK_TYPE_IMHTML);
1479 	for (iter = klass->protocols; iter; iter = iter->next) {
1480 		proto = iter->data;
1481 		if (g_ascii_strncasecmp(url, proto->name, reverse ? MIN(length, proto->length) : proto->length) == 0) {
1482 			return proto;
1483 		}
1484 	}
1485 	return NULL;
1486 }
1487 
1488 static void
imhtml_url_clicked(GtkIMHtml * imhtml,const char * url)1489 imhtml_url_clicked(GtkIMHtml *imhtml, const char *url)
1490 {
1491 	GtkIMHtmlProtocol *proto = imhtml_find_protocol(url, FALSE);
1492 	GtkIMHtmlLink *link;
1493 	if (!proto)
1494 		return;
1495 	link = g_new0(GtkIMHtmlLink, 1);
1496 	link->imhtml = g_object_ref(imhtml);
1497 	link->url = g_strdup(url);
1498 	proto->activate(imhtml, link);   /* XXX: Do something with the return value? */
1499 	gtk_imhtml_link_destroy(link);
1500 }
1501 
1502 /* Boring GTK+ stuff */
gtk_imhtml_class_init(GtkIMHtmlClass * klass)1503 static void gtk_imhtml_class_init (GtkIMHtmlClass *klass)
1504 {
1505 	GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
1506 	GtkBindingSet *binding_set;
1507 	GObjectClass   *gobject_class;
1508 	gobject_class = (GObjectClass*) klass;
1509 	parent_class = g_type_class_ref(GTK_TYPE_TEXT_VIEW);
1510 	signals[URL_CLICKED] = g_signal_new("url_clicked",
1511 						G_TYPE_FROM_CLASS(gobject_class),
1512 						G_SIGNAL_RUN_FIRST,
1513 						G_STRUCT_OFFSET(GtkIMHtmlClass, url_clicked),
1514 						NULL,
1515 						0,
1516 						g_cclosure_marshal_VOID__POINTER,
1517 						G_TYPE_NONE, 1,
1518 						G_TYPE_POINTER);
1519 	signals[BUTTONS_UPDATE] = g_signal_new("format_buttons_update",
1520 					       G_TYPE_FROM_CLASS(gobject_class),
1521 					       G_SIGNAL_RUN_FIRST,
1522 					       G_STRUCT_OFFSET(GtkIMHtmlClass, buttons_update),
1523 					       NULL,
1524 					       0,
1525 					       g_cclosure_marshal_VOID__INT,
1526 					       G_TYPE_NONE, 1,
1527 					       G_TYPE_INT);
1528 	signals[TOGGLE_FORMAT] = g_signal_new("format_function_toggle",
1529 					      G_TYPE_FROM_CLASS(gobject_class),
1530 					      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1531 					      G_STRUCT_OFFSET(GtkIMHtmlClass, toggle_format),
1532 					      NULL,
1533 					      0,
1534 					      g_cclosure_marshal_VOID__INT,
1535 					      G_TYPE_NONE, 1,
1536 					      G_TYPE_INT);
1537 	signals[CLEAR_FORMAT] = g_signal_new("format_function_clear",
1538 					      G_TYPE_FROM_CLASS(gobject_class),
1539 					      G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
1540 					      G_STRUCT_OFFSET(GtkIMHtmlClass, clear_format),
1541 					      NULL,
1542 					      0,
1543 					     g_cclosure_marshal_VOID__VOID,
1544 					     G_TYPE_NONE, 0);
1545 	signals[UPDATE_FORMAT] = g_signal_new("format_function_update",
1546 					      G_TYPE_FROM_CLASS(gobject_class),
1547 					      G_SIGNAL_RUN_FIRST,
1548 					      G_STRUCT_OFFSET(GtkIMHtmlClass, update_format),
1549 					      NULL,
1550 					      0,
1551 					      g_cclosure_marshal_VOID__VOID,
1552 					      G_TYPE_NONE, 0);
1553 	signals[MESSAGE_SEND] = g_signal_new("message_send",
1554 					     G_TYPE_FROM_CLASS(gobject_class),
1555 					     G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1556 					     G_STRUCT_OFFSET(GtkIMHtmlClass, message_send),
1557 					     NULL,
1558 					     0, g_cclosure_marshal_VOID__VOID,
1559 					     G_TYPE_NONE, 0);
1560 	signals[PASTE] = g_signal_new("paste",
1561 					     G_TYPE_FROM_CLASS(gobject_class),
1562 					     G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1563 						 0,
1564 					     NULL,
1565 					     0, g_cclosure_marshal_VOID__STRING,
1566 					     G_TYPE_NONE, 1, G_TYPE_STRING);
1567 	signals [UNDO] = g_signal_new ("undo",
1568 			G_TYPE_FROM_CLASS (klass),
1569 			G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1570 			G_STRUCT_OFFSET (GtkIMHtmlClass, undo),
1571 			NULL,
1572 			NULL,
1573 			gtksourceview_marshal_VOID__VOID,
1574 			G_TYPE_NONE,
1575 			0);
1576 	signals [REDO] = g_signal_new ("redo",
1577 			G_TYPE_FROM_CLASS (klass),
1578 			G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1579 			G_STRUCT_OFFSET (GtkIMHtmlClass, redo),
1580 			NULL,
1581 			NULL,
1582 			gtksourceview_marshal_VOID__VOID,
1583 			G_TYPE_NONE,
1584 			0);
1585 
1586 
1587 
1588 	klass->toggle_format = imhtml_toggle_format;
1589 	klass->message_send = imhtml_message_send;
1590 	klass->clear_format = imhtml_clear_formatting;
1591 	klass->url_clicked = imhtml_url_clicked;
1592 	klass->undo = gtk_imhtml_undo;
1593 	klass->redo = gtk_imhtml_redo;
1594 
1595 	gobject_class->finalize = gtk_imhtml_finalize;
1596 	widget_class->drag_motion = gtk_text_view_drag_motion;
1597 	widget_class->expose_event = gtk_imhtml_expose_event;
1598 	parent_size_allocate = widget_class->size_allocate;
1599 	widget_class->size_allocate = gtk_imhtml_size_allocate;
1600 	parent_style_set = widget_class->style_set;
1601 	widget_class->style_set = gtk_imhtml_style_set;
1602 
1603 	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("hyperlink-color",
1604 	                                        _("Hyperlink color"),
1605 	                                        _("Color to draw hyperlinks."),
1606 	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
1607 	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("hyperlink-visited-color",
1608 	                                        _("Hyperlink visited color"),
1609 	                                        _("Color to draw hyperlink after it has been visited (or activated)."),
1610 	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
1611 	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("hyperlink-prelight-color",
1612 	                                        _("Hyperlink prelight color"),
1613 	                                        _("Color to draw hyperlinks when mouse is over them."),
1614 	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
1615 	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("send-name-color",
1616 	                                        _("Sent Message Name Color"),
1617 	                                        _("Color to draw the name of a message you sent."),
1618 	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
1619 	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("receive-name-color",
1620 	                                        _("Received Message Name Color"),
1621 	                                        _("Color to draw the name of a message you received."),
1622 	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
1623 	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("highlight-name-color",
1624 	                                        _("\"Attention\" Name Color"),
1625 	                                        _("Color to draw the name of a message you received containing your name."),
1626 	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
1627 	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("action-name-color",
1628 	                                        _("Action Message Name Color"),
1629 	                                        _("Color to draw the name of an action message."),
1630 	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
1631 	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("whisper-action-name-color",
1632 	                                        _("Action Message Name Color for Whispered Message"),
1633 	                                        _("Color to draw the name of a whispered action message."),
1634 	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
1635 	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("whisper-name-color",
1636 	                                        _("Whisper Message Name Color"),
1637 	                                        _("Color to draw the name of a whispered message."),
1638 	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
1639 
1640 	/* Customizable typing notification ... sort of. Example:
1641 	 *   GtkIMHtml::typing-notification-font = "monospace italic light 8.0"
1642 	 *   GtkIMHtml::typing-notification-color = "#ff0000"
1643 	 *   GtkIMHtml::typing-notification-enable = 1
1644 	 */
1645 	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("typing-notification-color",
1646 	                                        _("Typing notification color"),
1647 	                                        _("The color to use for the typing notification"),
1648 	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
1649 	gtk_widget_class_install_style_property(widget_class, g_param_spec_string("typing-notification-font",
1650 	                                        _("Typing notification font"),
1651 	                                        _("The font to use for the typing notification"),
1652 	                                        "light 8.0", G_PARAM_READABLE));
1653 	gtk_widget_class_install_style_property(widget_class, g_param_spec_boolean("typing-notification-enable",
1654 	                                        _("Enable typing notification"),
1655 	                                        _("Enable typing notification"),
1656 	                                        TRUE, G_PARAM_READABLE));
1657 
1658 	binding_set = gtk_binding_set_by_class (parent_class);
1659 	gtk_binding_entry_add_signal (binding_set, GDK_b, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_BOLD);
1660 	gtk_binding_entry_add_signal (binding_set, GDK_i, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_ITALIC);
1661 	gtk_binding_entry_add_signal (binding_set, GDK_u, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_UNDERLINE);
1662 	gtk_binding_entry_add_signal (binding_set, GDK_plus, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_GROW);
1663 	gtk_binding_entry_add_signal (binding_set, GDK_equal, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_GROW);
1664 	gtk_binding_entry_add_signal (binding_set, GDK_minus, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_SHRINK);
1665 	binding_set = gtk_binding_set_by_class(klass);
1666 	gtk_binding_entry_add_signal (binding_set, GDK_r, GDK_CONTROL_MASK, "format_function_clear", 0);
1667 	gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0, "message_send", 0);
1668 	gtk_binding_entry_add_signal (binding_set, GDK_Return, 0, "message_send", 0);
1669 	gtk_binding_entry_add_signal (binding_set, GDK_z, GDK_CONTROL_MASK, "undo", 0);
1670 	gtk_binding_entry_add_signal (binding_set, GDK_z, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "redo", 0);
1671 	gtk_binding_entry_add_signal (binding_set, GDK_F14, 0, "undo", 0);
1672 	gtk_binding_entry_add_signal(binding_set, GDK_v, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "paste", 1, G_TYPE_STRING, "text");
1673 }
1674 
gtk_imhtml_init(GtkIMHtml * imhtml)1675 static void gtk_imhtml_init (GtkIMHtml *imhtml)
1676 {
1677 	imhtml->text_buffer = gtk_text_buffer_new(NULL);
1678 	imhtml->undo_manager = gtk_source_undo_manager_new(imhtml->text_buffer);
1679 	gtk_text_view_set_buffer(GTK_TEXT_VIEW(imhtml), imhtml->text_buffer);
1680 	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml), GTK_WRAP_WORD_CHAR);
1681 	gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(imhtml), 2);
1682 	gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(imhtml), 3);
1683 	gtk_text_view_set_left_margin(GTK_TEXT_VIEW(imhtml), 2);
1684 	gtk_text_view_set_right_margin(GTK_TEXT_VIEW(imhtml), 2);
1685 	/*gtk_text_view_set_indent(GTK_TEXT_VIEW(imhtml), -15);*/
1686 	/*gtk_text_view_set_justification(GTK_TEXT_VIEW(imhtml), GTK_JUSTIFY_FILL);*/
1687 
1688 	/* These tags will be used often and can be reused--we create them on init and then apply them by name
1689 	 * other tags (color, size, face, etc.) will have to be created and applied dynamically
1690 	 * Note that even though we created SUB, SUP, and PRE tags here, we don't really
1691 	 * apply them anywhere yet. */
1692 	gtk_text_buffer_create_tag(imhtml->text_buffer, "BOLD", "weight", PANGO_WEIGHT_BOLD, NULL);
1693 	gtk_text_buffer_create_tag(imhtml->text_buffer, "ITALICS", "style", PANGO_STYLE_ITALIC, NULL);
1694 	gtk_text_buffer_create_tag(imhtml->text_buffer, "UNDERLINE", "underline", PANGO_UNDERLINE_SINGLE, NULL);
1695 	gtk_text_buffer_create_tag(imhtml->text_buffer, "STRIKE", "strikethrough", TRUE, NULL);
1696 	gtk_text_buffer_create_tag(imhtml->text_buffer, "SUB", "rise", -5000, NULL);
1697 	gtk_text_buffer_create_tag(imhtml->text_buffer, "SUP", "rise", 5000, NULL);
1698 	gtk_text_buffer_create_tag(imhtml->text_buffer, "PRE", "family", "Monospace", NULL);
1699 	gtk_text_buffer_create_tag(imhtml->text_buffer, "search", "background", "#22ff00", "weight", "bold", NULL);
1700 	gtk_text_buffer_create_tag(imhtml->text_buffer, "comment", "weight", PANGO_WEIGHT_NORMAL,
1701 #if FALSE && GTK_CHECK_VERSION(2,10,10)
1702 			"invisible", FALSE,
1703 #endif
1704 			NULL);
1705 
1706 	gtk_text_buffer_create_tag(imhtml->text_buffer, "send-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1707 	gtk_text_buffer_create_tag(imhtml->text_buffer, "receive-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1708 	gtk_text_buffer_create_tag(imhtml->text_buffer, "highlight-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1709 	gtk_text_buffer_create_tag(imhtml->text_buffer, "action-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1710 	gtk_text_buffer_create_tag(imhtml->text_buffer, "whisper-action-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1711 	gtk_text_buffer_create_tag(imhtml->text_buffer, "whisper-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1712 
1713 	/* When hovering over a link, we show the hand cursor--elsewhere we show the plain ol' pointer cursor */
1714 	imhtml->hand_cursor = gdk_cursor_new (GDK_HAND2);
1715 	imhtml->arrow_cursor = gdk_cursor_new (GDK_LEFT_PTR);
1716 	imhtml->text_cursor = gdk_cursor_new (GDK_XTERM);
1717 
1718 	imhtml->show_comments = TRUE;
1719 
1720 	imhtml->smiley_data = g_hash_table_new_full(g_str_hash, g_str_equal,
1721 			g_free, (GDestroyNotify)gtk_smiley_tree_destroy);
1722 	imhtml->default_smilies = gtk_smiley_tree_new();
1723 
1724 	g_signal_connect(G_OBJECT(imhtml), "motion-notify-event", G_CALLBACK(gtk_motion_event_notify), NULL);
1725 	g_signal_connect(G_OBJECT(imhtml), "leave-notify-event", G_CALLBACK(gtk_leave_event_notify), NULL);
1726 	g_signal_connect(G_OBJECT(imhtml), "enter-notify-event", G_CALLBACK(gtk_enter_event_notify), NULL);
1727 	g_signal_connect(G_OBJECT(imhtml), "button_press_event", G_CALLBACK(gtk_imhtml_button_press_event), NULL);
1728 	g_signal_connect(G_OBJECT(imhtml->text_buffer), "insert-text", G_CALLBACK(preinsert_cb), imhtml);
1729 	g_signal_connect(G_OBJECT(imhtml->text_buffer), "delete_range", G_CALLBACK(delete_cb), imhtml);
1730 	g_signal_connect_after(G_OBJECT(imhtml->text_buffer), "insert-text", G_CALLBACK(insert_cb), imhtml);
1731 	g_signal_connect_after(G_OBJECT(imhtml->text_buffer), "insert-child-anchor", G_CALLBACK(insert_ca_cb), imhtml);
1732 	gtk_drag_dest_set(GTK_WIDGET(imhtml), 0,
1733 			  link_drag_drop_targets, sizeof(link_drag_drop_targets) / sizeof(GtkTargetEntry),
1734 			  GDK_ACTION_COPY);
1735 	g_signal_connect(G_OBJECT(imhtml), "drag_data_received", G_CALLBACK(gtk_imhtml_link_drag_rcv_cb), imhtml);
1736 	g_signal_connect(G_OBJECT(imhtml), "drag_drop", G_CALLBACK(gtk_imhtml_link_drop_cb), imhtml);
1737 
1738 	g_signal_connect(G_OBJECT(imhtml), "copy-clipboard", G_CALLBACK(copy_clipboard_cb), NULL);
1739 	g_signal_connect(G_OBJECT(imhtml), "cut-clipboard", G_CALLBACK(cut_clipboard_cb), NULL);
1740 	g_signal_connect(G_OBJECT(imhtml), "paste-clipboard", G_CALLBACK(paste_clipboard_cb), NULL);
1741 	g_signal_connect_after(G_OBJECT(imhtml), "realize", G_CALLBACK(imhtml_realized_remove_primary), NULL);
1742 	g_signal_connect(G_OBJECT(imhtml), "unrealize", G_CALLBACK(imhtml_destroy_add_primary), NULL);
1743 	g_signal_connect(G_OBJECT(imhtml), "paste", G_CALLBACK(imhtml_paste_cb), NULL);
1744 
1745 #ifndef _WIN32
1746 	g_signal_connect_after(G_OBJECT(GTK_IMHTML(imhtml)->text_buffer), "mark-set",
1747 		               G_CALLBACK(mark_set_so_update_selection_cb), imhtml);
1748 #endif
1749 
1750 	gtk_widget_add_events(GTK_WIDGET(imhtml),
1751 			GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK);
1752 
1753 	imhtml->tip = NULL;
1754 	imhtml->tip_timer = 0;
1755 	imhtml->tip_window = NULL;
1756 
1757 	imhtml->edit.bold = FALSE;
1758 	imhtml->edit.italic = FALSE;
1759 	imhtml->edit.underline = FALSE;
1760 	imhtml->edit.forecolor = NULL;
1761 	imhtml->edit.backcolor = NULL;
1762 	imhtml->edit.fontface = NULL;
1763 	imhtml->edit.fontsize = 0;
1764 	imhtml->edit.link = NULL;
1765 
1766 
1767 	imhtml->scalables = NULL;
1768 	imhtml->animations = g_queue_new();
1769 	gtk_imhtml_set_editable(imhtml, FALSE);
1770 	g_signal_connect(G_OBJECT(imhtml), "populate-popup",
1771 					 G_CALLBACK(hijack_menu_cb), NULL);
1772 }
1773 
gtk_imhtml_new(void * a,void * b)1774 GtkWidget *gtk_imhtml_new(void *a, void *b)
1775 {
1776 	return GTK_WIDGET(g_object_new(gtk_imhtml_get_type(), NULL));
1777 }
1778 
gtk_imhtml_get_type()1779 GType gtk_imhtml_get_type()
1780 {
1781 	static GType imhtml_type = 0;
1782 
1783 	if (!imhtml_type) {
1784 		static const GTypeInfo imhtml_info = {
1785 			sizeof(GtkIMHtmlClass),
1786 			NULL,
1787 			NULL,
1788 			(GClassInitFunc) gtk_imhtml_class_init,
1789 			NULL,
1790 			NULL,
1791 			sizeof (GtkIMHtml),
1792 			0,
1793 			(GInstanceInitFunc) gtk_imhtml_init,
1794 			NULL
1795 		};
1796 
1797 		imhtml_type = g_type_register_static(gtk_text_view_get_type(),
1798 				"GtkIMHtml", &imhtml_info, 0);
1799 	}
1800 
1801 	return imhtml_type;
1802 }
1803 
gtk_imhtml_link_destroy(GtkIMHtmlLink * link)1804 static void gtk_imhtml_link_destroy(GtkIMHtmlLink *link)
1805 {
1806 	if (link->imhtml)
1807 		g_object_unref(link->imhtml);
1808 	if (link->tag)
1809 		g_object_unref(link->tag);
1810 	g_free(link->url);
1811 	g_free(link);
1812 }
1813 
1814 /* The callback for an event on a link tag. */
tag_event(GtkTextTag * tag,GObject * imhtml,GdkEvent * event,GtkTextIter * arg2,gpointer unused)1815 static gboolean tag_event(GtkTextTag *tag, GObject *imhtml, GdkEvent *event, GtkTextIter *arg2, gpointer unused)
1816 {
1817 	GdkEventButton *event_button = (GdkEventButton *) event;
1818 	if (GTK_IMHTML(imhtml)->editable)
1819 		return FALSE;
1820 	if (event->type == GDK_BUTTON_RELEASE) {
1821 		if ((event_button->button == 1) || (event_button->button == 2)) {
1822 			GtkTextIter start, end;
1823 			/* we shouldn't open a URL if the user has selected something: */
1824 			if (gtk_text_buffer_get_selection_bounds(
1825 						gtk_text_iter_get_buffer(arg2),	&start, &end))
1826 				return FALSE;
1827 			gtk_imhtml_activate_tag(GTK_IMHTML(imhtml), tag);
1828 			return FALSE;
1829 		} else if(event_button->button == 3) {
1830 			GList *children;
1831 			GtkWidget *menu;
1832 			GtkIMHtmlProtocol *proto;
1833 			GtkIMHtmlLink *link = g_new(GtkIMHtmlLink, 1);
1834 			link->imhtml = g_object_ref(imhtml);
1835 			link->url = g_strdup(g_object_get_data(G_OBJECT(tag), "link_url"));
1836 			link->tag = g_object_ref(tag);
1837 
1838 			/* Don't want the tooltip around if user right-clicked on link */
1839 			if (GTK_IMHTML(imhtml)->tip_window) {
1840 				gtk_widget_destroy(GTK_IMHTML(imhtml)->tip_window);
1841 				GTK_IMHTML(imhtml)->tip_window = NULL;
1842 			}
1843 			if (GTK_IMHTML(imhtml)->tip_timer) {
1844 				g_source_remove(GTK_IMHTML(imhtml)->tip_timer);
1845 				GTK_IMHTML(imhtml)->tip_timer = 0;
1846 			}
1847 			if (GTK_IMHTML(imhtml)->editable)
1848 				gdk_window_set_cursor(event_button->window, GTK_IMHTML(imhtml)->text_cursor);
1849 			else
1850 				gdk_window_set_cursor(event_button->window, GTK_IMHTML(imhtml)->arrow_cursor);
1851 			menu = gtk_menu_new();
1852 			g_object_set_data_full(G_OBJECT(menu), "x-imhtml-url-data", link,
1853 					(GDestroyNotify)gtk_imhtml_link_destroy);
1854 
1855 			proto = imhtml_find_protocol(link->url, FALSE);
1856 
1857 			if (proto && proto->context_menu) {
1858 				proto->context_menu(GTK_IMHTML(link->imhtml), link, menu);
1859 			}
1860 
1861 			children = gtk_container_get_children(GTK_CONTAINER(menu));
1862 			if (!children) {
1863 				GtkWidget *item = gtk_menu_item_new_with_label(_("No actions available"));
1864 				gtk_widget_show(item);
1865 				gtk_widget_set_sensitive(item, FALSE);
1866 				gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
1867 			} else {
1868 				g_list_free(children);
1869 			}
1870 
1871 
1872 			gtk_widget_show_all(menu);
1873 			gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
1874 							event_button->button, event_button->time);
1875 
1876 			return TRUE;
1877 		}
1878 	}
1879 	if(event->type == GDK_BUTTON_PRESS && event_button->button == 3)
1880 		return TRUE; /* Clicking the right mouse button on a link shouldn't
1881 						be caught by the regular GtkTextView menu */
1882 	else
1883 		return FALSE; /* Let clicks go through if we didn't catch anything */
1884 }
1885 
1886 static gboolean
gtk_text_view_drag_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time)1887 gtk_text_view_drag_motion (GtkWidget        *widget,
1888                            GdkDragContext   *context,
1889                            gint              x,
1890                            gint              y,
1891                            guint             time)
1892 {
1893 	GdkDragAction suggested_action = 0;
1894 
1895 	if (gtk_drag_dest_find_target (widget, context, NULL) == GDK_NONE) {
1896 		/* can't accept any of the offered targets */
1897 	} else {
1898 		GtkWidget *source_widget;
1899 		suggested_action = context->suggested_action;
1900 		source_widget = gtk_drag_get_source_widget (context);
1901 		if (source_widget == widget) {
1902 			/* Default to MOVE, unless the user has
1903 			 * pressed ctrl or alt to affect available actions
1904 			 */
1905 			if ((context->actions & GDK_ACTION_MOVE) != 0)
1906 				suggested_action = GDK_ACTION_MOVE;
1907 		}
1908 	}
1909 
1910 	gdk_drag_status (context, suggested_action, time);
1911 
1912   /* TRUE return means don't propagate the drag motion to parent
1913    * widgets that may also be drop sites.
1914    */
1915   return TRUE;
1916 }
1917 
1918 static void
gtk_imhtml_link_drop_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,gpointer user_data)1919 gtk_imhtml_link_drop_cb(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer user_data)
1920 {
1921 	GdkAtom target = gtk_drag_dest_find_target (widget, context, NULL);
1922 
1923 	if (target != GDK_NONE)
1924 		gtk_drag_get_data (widget, context, target, time);
1925 	else
1926 		gtk_drag_finish (context, FALSE, FALSE, time);
1927 
1928 	return;
1929 }
1930 
1931 static void
gtk_imhtml_link_drag_rcv_cb(GtkWidget * widget,GdkDragContext * dc,guint x,guint y,GtkSelectionData * sd,guint info,guint t,GtkIMHtml * imhtml)1932 gtk_imhtml_link_drag_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y,
1933 			    GtkSelectionData *sd, guint info, guint t, GtkIMHtml *imhtml)
1934 {
1935 	gchar **links;
1936 	gchar *link;
1937 	char *text = (char *)sd->data;
1938 	GtkTextMark *mark = gtk_text_buffer_get_insert(imhtml->text_buffer);
1939 	GtkTextIter iter;
1940 	gint i = 0;
1941 
1942 	gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, mark);
1943 
1944 	if(gtk_imhtml_get_editable(imhtml) && sd->data){
1945 		switch (info) {
1946 		case GTK_IMHTML_DRAG_URL:
1947 			/* TODO: Is it really ok to change sd->data...? */
1948 			purple_str_strip_char((char *)sd->data, '\r');
1949 
1950 			links = g_strsplit((char *)sd->data, "\n", 0);
1951 			while((link = links[i]) != NULL){
1952 				if (gtk_imhtml_is_protocol(link)) {
1953 					gchar *label;
1954 
1955 					if(links[i + 1])
1956 						i++;
1957 
1958 					label = links[i];
1959 
1960 					gtk_imhtml_insert_link(imhtml, mark, link, label);
1961 				} else if (*link == '\0') {
1962 					/* Ignore blank lines */
1963 				} else {
1964 					/* Special reasons, aka images being put in via other tag, etc. */
1965 					/* ... don't pretend we handled it if we didn't */
1966 					gtk_drag_finish(dc, FALSE, FALSE, t);
1967 					g_strfreev(links);
1968 					return;
1969 				}
1970 
1971 				i++;
1972 			}
1973             g_strfreev(links);
1974 			break;
1975 		case GTK_IMHTML_DRAG_HTML:
1976 			{
1977 			char *utf8 = NULL;
1978 			/* Ewww. This is all because mozilla thinks that text/html is 'for internal use only.'
1979 			 * as explained by this comment in gtkhtml:
1980 			 *
1981 			 * FIXME This hack decides the charset of the selection.  It seems that
1982 			 * mozilla/netscape alway use ucs2 for text/html
1983 			 * and openoffice.org seems to always use utf8 so we try to validate
1984 			 * the string as utf8 and if that fails we assume it is ucs2
1985 			 *
1986 			 * See also the comment on text/html here:
1987 			 * http://mail.gnome.org/archives/gtk-devel-list/2001-September/msg00114.html
1988 			 */
1989 			if (sd->length >= 2 && !g_utf8_validate(text, sd->length - 1, NULL)) {
1990 				utf8 = utf16_to_utf8_with_bom_check(text, sd->length);
1991 
1992 				if (!utf8) {
1993 					purple_debug_warning("gtkimhtml", "g_convert from UTF-16 failed in drag_rcv_cb\n");
1994 					return;
1995 				}
1996 			} else if (!(*text) || !g_utf8_validate(text, -1, NULL)) {
1997 				purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in drag_rcv_cb\n");
1998 				return;
1999 			}
2000 
2001 			gtk_imhtml_insert_html_at_iter(imhtml, utf8 ? utf8 : text, 0, &iter);
2002 			g_free(utf8);
2003 			break;
2004 			}
2005 		case GTK_IMHTML_DRAG_TEXT:
2006 			if (!(*text) || !g_utf8_validate(text, -1, NULL)) {
2007 				purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in drag_rcv_cb\n");
2008 				return;
2009 			} else {
2010 				char *tmp = g_markup_escape_text(text, -1);
2011 				gtk_imhtml_insert_html_at_iter(imhtml, tmp, 0, &iter);
2012 				g_free(tmp);
2013 			}
2014 			break;
2015 		default:
2016 			gtk_drag_finish(dc, FALSE, FALSE, t);
2017 			return;
2018 		}
2019 		gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
2020 	} else {
2021 		gtk_drag_finish(dc, FALSE, FALSE, t);
2022 	}
2023 }
2024 
gtk_smiley_tree_remove(GtkSmileyTree * tree,GtkIMHtmlSmiley * smiley)2025 static void gtk_smiley_tree_remove (GtkSmileyTree     *tree,
2026 			GtkIMHtmlSmiley   *smiley)
2027 {
2028 	GtkSmileyTree *t = tree;
2029 	const gchar *x = smiley->smile;
2030 	gint len = 0;
2031 
2032 	while (*x) {
2033 		gchar *pos;
2034 
2035 		if (!t->values)
2036 			return;
2037 
2038 		pos = strchr (t->values->str, *x);
2039 		if (pos)
2040 			t = t->children [pos - t->values->str];
2041 		else
2042 			return;
2043 
2044 		x++; len++;
2045 	}
2046 
2047 	if (t->image) {
2048 		t->image = NULL;
2049 	}
2050 }
2051 
2052 static gint
gtk_smiley_tree_lookup(GtkSmileyTree * tree,const gchar * text)2053 gtk_smiley_tree_lookup (GtkSmileyTree *tree,
2054 			const gchar   *text)
2055 {
2056 	GtkSmileyTree *t = tree;
2057 	const gchar *x = text;
2058 	const gchar *amp;
2059 	gint alen;
2060 	gint len = 0;
2061 	gint lastlen = 0;
2062 
2063 	while (*x) {
2064 		gchar *pos;
2065 
2066 		if (!t->values)
2067 			break;
2068 
2069 		if(*x == '&' && (amp = purple_markup_unescape_entity(x, &alen))) {
2070 			gboolean matched = TRUE;
2071 			/* Make sure all chars of the unescaped value match */
2072 			while (*(amp + 1)) {
2073 				pos = strchr (t->values->str, *amp);
2074 				if (pos)
2075 					t = t->children [GPOINTER_TO_INT(pos) - GPOINTER_TO_INT(t->values->str)];
2076 				else {
2077 					matched = FALSE;
2078 					break;
2079 				}
2080 				amp++;
2081 			}
2082 			if (!matched)
2083 				break;
2084 
2085 			pos = strchr (t->values->str, *amp);
2086 		}
2087 		else if (*x == '<') /* Because we're all WYSIWYG now, a '<'
2088 				     * char should only appear as the start of a tag.  Perhaps a safer (but costlier)
2089 				     * check would be to call gtk_imhtml_is_tag on it */
2090 			break;
2091 		else {
2092 			alen = 1;
2093 			pos = strchr (t->values->str, *x);
2094 		}
2095 
2096 		if (pos) {
2097 			t = t->children [GPOINTER_TO_INT(pos) - GPOINTER_TO_INT(t->values->str)];
2098 			if (t->image)
2099 				lastlen = len + alen;
2100 		} else
2101 			break;
2102 
2103 		x += alen;
2104 		len += alen;
2105 	}
2106 
2107 	if (t->image)
2108 		return len;
2109 
2110 	return lastlen;
2111 }
2112 
2113 static void
gtk_imhtml_disassociate_smiley_foreach(gpointer key,gpointer value,gpointer user_data)2114 gtk_imhtml_disassociate_smiley_foreach(gpointer key, gpointer value,
2115 	gpointer user_data)
2116 {
2117 	GtkSmileyTree *tree = (GtkSmileyTree *) value;
2118 	GtkIMHtmlSmiley *smiley = (GtkIMHtmlSmiley *) user_data;
2119 	gtk_smiley_tree_remove(tree, smiley);
2120 }
2121 
2122 static void
gtk_imhtml_disconnect_smiley(GtkIMHtml * imhtml,GtkIMHtmlSmiley * smiley)2123 gtk_imhtml_disconnect_smiley(GtkIMHtml *imhtml, GtkIMHtmlSmiley *smiley)
2124 {
2125 	smiley->imhtml = NULL;
2126 	g_signal_handlers_disconnect_matched(imhtml, G_SIGNAL_MATCH_DATA, 0, 0,
2127 		NULL, NULL, smiley);
2128 }
2129 
2130 static void
gtk_imhtml_disassociate_smiley(GtkIMHtmlSmiley * smiley)2131 gtk_imhtml_disassociate_smiley(GtkIMHtmlSmiley *smiley)
2132 {
2133 	if (smiley->imhtml) {
2134 		gtk_smiley_tree_remove(smiley->imhtml->default_smilies, smiley);
2135 		g_hash_table_foreach(smiley->imhtml->smiley_data,
2136 			gtk_imhtml_disassociate_smiley_foreach, smiley);
2137 		g_signal_handlers_disconnect_matched(smiley->imhtml, G_SIGNAL_MATCH_DATA,
2138 			0, 0, NULL, NULL, smiley);
2139 		smiley->imhtml = NULL;
2140 	}
2141 }
2142 
2143 void
gtk_imhtml_associate_smiley(GtkIMHtml * imhtml,const gchar * sml,GtkIMHtmlSmiley * smiley)2144 gtk_imhtml_associate_smiley (GtkIMHtml       *imhtml,
2145 			     const gchar     *sml,
2146 			     GtkIMHtmlSmiley *smiley)
2147 {
2148 	GtkSmileyTree *tree;
2149 	g_return_if_fail (imhtml != NULL);
2150 	g_return_if_fail (GTK_IS_IMHTML (imhtml));
2151 
2152 	if (sml == NULL)
2153 		tree = imhtml->default_smilies;
2154 	else if (!(tree = g_hash_table_lookup(imhtml->smiley_data, sml))) {
2155 		tree = gtk_smiley_tree_new();
2156 		g_hash_table_insert(imhtml->smiley_data, g_strdup(sml), tree);
2157 	}
2158 
2159 	/* need to disconnect old imhtml, if there is one */
2160 	if (smiley->imhtml) {
2161 		g_signal_handlers_disconnect_matched(smiley->imhtml, G_SIGNAL_MATCH_DATA,
2162 			0, 0, NULL, NULL, smiley);
2163 	}
2164 
2165 	smiley->imhtml = imhtml;
2166 
2167 	gtk_smiley_tree_insert (tree, smiley);
2168 
2169 	/* connect destroy signal for the imhtml */
2170 	g_signal_connect(imhtml, "destroy", G_CALLBACK(gtk_imhtml_disconnect_smiley),
2171 		smiley);
2172 }
2173 
2174 static gboolean
gtk_imhtml_is_smiley(GtkIMHtml * imhtml,GSList * fonts,const gchar * text,gint * len)2175 gtk_imhtml_is_smiley (GtkIMHtml   *imhtml,
2176 		      GSList      *fonts,
2177 		      const gchar *text,
2178 		      gint        *len)
2179 {
2180 	GtkSmileyTree *tree;
2181 	GtkIMHtmlFontDetail *font;
2182 	char *sml = NULL;
2183 
2184 	if (fonts) {
2185 		font = fonts->data;
2186 		sml = font->sml;
2187 	}
2188 
2189 	if (!sml)
2190 		sml = imhtml->protocol_name;
2191 
2192 	if (!sml || !(tree = g_hash_table_lookup(imhtml->smiley_data, sml)))
2193 		tree = imhtml->default_smilies;
2194 
2195 	if (tree == NULL)
2196 		return FALSE;
2197 
2198 	*len = gtk_smiley_tree_lookup (tree, text);
2199 	return (*len > 0);
2200 }
2201 
gtk_imhtml_smiley_get_from_tree(GtkSmileyTree * t,const gchar * text)2202 static GtkIMHtmlSmiley *gtk_imhtml_smiley_get_from_tree(GtkSmileyTree *t, const gchar *text)
2203 {
2204 	const gchar *x = text;
2205 	gchar *pos;
2206 
2207 	if (t == NULL)
2208 		return NULL;
2209 
2210 	while (*x) {
2211 		if (!t->values)
2212 			return NULL;
2213 
2214 		pos = strchr(t->values->str, *x);
2215 		if (!pos)
2216 			return NULL;
2217 
2218 		t = t->children[GPOINTER_TO_INT(pos) - GPOINTER_TO_INT(t->values->str)];
2219 		x++;
2220 	}
2221 
2222 	return t->image;
2223 }
2224 
2225 GtkIMHtmlSmiley *
gtk_imhtml_smiley_get(GtkIMHtml * imhtml,const gchar * sml,const gchar * text)2226 gtk_imhtml_smiley_get(GtkIMHtml *imhtml, const gchar *sml, const gchar *text)
2227 {
2228 	GtkIMHtmlSmiley *ret;
2229 
2230 	/* Look for custom smileys first */
2231 	if (sml != NULL) {
2232 		ret = gtk_imhtml_smiley_get_from_tree(g_hash_table_lookup(imhtml->smiley_data, sml), text);
2233 		if (ret != NULL)
2234 			return ret;
2235 	}
2236 
2237 	/* Fall back to check for default smileys */
2238 	return gtk_imhtml_smiley_get_from_tree(imhtml->default_smilies, text);
2239 }
2240 
2241 static GdkPixbufAnimation *
gtk_smiley_get_image(GtkIMHtmlSmiley * smiley)2242 gtk_smiley_get_image(GtkIMHtmlSmiley *smiley)
2243 {
2244 	if (!smiley->icon) {
2245 		if (smiley->file) {
2246 			smiley->icon = gdk_pixbuf_animation_new_from_file(smiley->file, NULL);
2247 		} else if (smiley->loader) {
2248 			smiley->icon = gdk_pixbuf_loader_get_animation(smiley->loader);
2249 			if (smiley->icon)
2250 				g_object_ref(G_OBJECT(smiley->icon));
2251 		}
2252 	}
2253 
2254 	return smiley->icon;
2255 }
2256 
2257 #define VALID_TAG(x)	do { \
2258 			if (!g_ascii_strncasecmp (string, x ">", strlen (x ">"))) {	\
2259 				if (tag) *tag = g_strndup (string, strlen (x));		\
2260 				if (len) *len = strlen (x) + 1;				\
2261 				return TRUE;					\
2262 			}							\
2263 			if (type) (*type)++; \
2264 		} while (0)
2265 
2266 #define VALID_OPT_TAG(x)	do { \
2267 				if (!g_ascii_strncasecmp (string, x " ", strlen (x " "))) {	\
2268 					const gchar *c = string + strlen (x " ");	\
2269 					gchar e = '"';					\
2270 					gboolean quote = FALSE;				\
2271 					while (*c) {					\
2272 						if (*c == '"' || *c == '\'') {		\
2273 							if (quote && (*c == e))		\
2274 								quote = !quote;		\
2275 							else if (!quote) {		\
2276 								quote = !quote;		\
2277 								e = *c;			\
2278 							}				\
2279 						} else if (!quote && (*c == '>'))	\
2280 							break;				\
2281 						c++;					\
2282 					}						\
2283 					if (*c) {					\
2284 						if (tag) *tag = g_strndup (string, c - string);	\
2285 						if (len) *len = c - string + 1;			\
2286 						return TRUE;				\
2287 					}						\
2288 				}							\
2289 				if (type) (*type)++; \
2290 			} while (0)
2291 
2292 
2293 static gboolean
gtk_imhtml_is_tag(const gchar * string,gchar ** tag,gint * len,gint * type)2294 gtk_imhtml_is_tag (const gchar *string,
2295 		   gchar      **tag,
2296 		   gint        *len,
2297 		   gint        *type)
2298 {
2299 	char *close;
2300 	if (type)
2301 		*type = 1;
2302 
2303 	if (!(close = strchr (string, '>')))
2304 		return FALSE;
2305 
2306 	VALID_TAG ("B");
2307 	VALID_TAG ("BOLD");
2308 	VALID_TAG ("/B");
2309 	VALID_TAG ("/BOLD");
2310 	VALID_TAG ("I");
2311 	VALID_TAG ("ITALIC");
2312 	VALID_TAG ("/I");
2313 	VALID_TAG ("/ITALIC");
2314 	VALID_TAG ("U");
2315 	VALID_TAG ("UNDERLINE");
2316 	VALID_TAG ("/U");
2317 	VALID_TAG ("/UNDERLINE");
2318 	VALID_TAG ("S");
2319 	VALID_TAG ("STRIKE");
2320 	VALID_TAG ("/S");
2321 	VALID_TAG ("/STRIKE");
2322 	VALID_TAG ("SUB");
2323 	VALID_TAG ("/SUB");
2324 	VALID_TAG ("SUP");
2325 	VALID_TAG ("/SUP");
2326 	VALID_TAG ("PRE");
2327 	VALID_TAG ("/PRE");
2328 	VALID_TAG ("TITLE");
2329 	VALID_TAG ("/TITLE");
2330 	VALID_TAG ("BR");
2331 	VALID_TAG ("HR");
2332 	VALID_TAG ("/FONT");
2333 	VALID_TAG ("/A");
2334 	VALID_TAG ("P");
2335 	VALID_TAG ("/P");
2336 	VALID_TAG ("H3");
2337 	VALID_TAG ("/H3");
2338 	VALID_TAG ("HTML");
2339 	VALID_TAG ("/HTML");
2340 	VALID_TAG ("BODY");
2341 	VALID_TAG ("/BODY");
2342 	VALID_TAG ("FONT");
2343 	VALID_TAG ("HEAD");
2344 	VALID_TAG ("/HEAD");
2345 	VALID_TAG ("BINARY");
2346 	VALID_TAG ("/BINARY");
2347 
2348 	VALID_OPT_TAG ("HR");
2349 	VALID_OPT_TAG ("FONT");
2350 	VALID_OPT_TAG ("BODY");
2351 	VALID_OPT_TAG ("A");
2352 	VALID_OPT_TAG ("IMG");
2353 	VALID_OPT_TAG ("P");
2354 	VALID_OPT_TAG ("H3");
2355 	VALID_OPT_TAG ("HTML");
2356 
2357 	VALID_TAG ("CITE");
2358 	VALID_TAG ("/CITE");
2359 	VALID_TAG ("EM");
2360 	VALID_TAG ("/EM");
2361 	VALID_TAG ("STRONG");
2362 	VALID_TAG ("/STRONG");
2363 
2364 	VALID_OPT_TAG ("SPAN");
2365 	VALID_TAG ("/SPAN");
2366 	VALID_TAG ("BR/"); /* hack until gtkimhtml handles things better */
2367 	VALID_TAG ("IMG");
2368 	VALID_TAG("SPAN");
2369 	VALID_OPT_TAG("BR");
2370 
2371 	if (!g_ascii_strncasecmp(string, "!--", strlen ("!--"))) {
2372 		gchar *e = strstr (string + strlen("!--"), "-->");
2373 		if (e) {
2374 			if (len) {
2375 				*len = e - string + strlen ("-->");
2376 				if (tag)
2377 					*tag = g_strndup (string + strlen ("!--"), *len - strlen ("!---->"));
2378 			}
2379 			return TRUE;
2380 		}
2381 	}
2382 
2383 	if (type)
2384 		*type = -1;
2385 	if (len)
2386 		*len = close - string + 1;
2387 	if (tag)
2388 		*tag = g_strndup(string, close - string);
2389 	return TRUE;
2390 }
2391 
2392 static gchar*
gtk_imhtml_get_html_opt(gchar * tag,const gchar * opt)2393 gtk_imhtml_get_html_opt (gchar       *tag,
2394 			 const gchar *opt)
2395 {
2396 	gchar *t = tag;
2397 	gchar *e, *a;
2398 	gchar *val;
2399 	gint len;
2400 	const gchar *c;
2401 	GString *ret;
2402 
2403 	while (g_ascii_strncasecmp (t, opt, strlen (opt))) {
2404 		gboolean quote = FALSE;
2405 		if (*t == '\0') break;
2406 		while (*t && !((*t == ' ') && !quote)) {
2407 			if (*t == '\"')
2408 				quote = ! quote;
2409 			t++;
2410 		}
2411 		while (*t && (*t == ' ')) t++;
2412 	}
2413 
2414 	if (!g_ascii_strncasecmp (t, opt, strlen (opt))) {
2415 		t += strlen (opt);
2416 	} else {
2417 		return NULL;
2418 	}
2419 
2420 	if ((*t == '\"') || (*t == '\'')) {
2421 		e = a = ++t;
2422 		while (*e && (*e != *(t - 1))) e++;
2423 		if  (*e == '\0') {
2424 			return NULL;
2425 		} else
2426 			val = g_strndup(a, e - a);
2427 	} else {
2428 		e = a = t;
2429 		while (*e && !isspace ((gint) *e)) e++;
2430 		val = g_strndup(a, e - a);
2431 	}
2432 
2433 	ret = g_string_new("");
2434 	e = val;
2435 	while(*e) {
2436 		if((c = purple_markup_unescape_entity(e, &len))) {
2437 			ret = g_string_append(ret, c);
2438 			e += len;
2439 		} else {
2440 			gunichar uni = g_utf8_get_char(e);
2441 			ret = g_string_append_unichar(ret, uni);
2442 			e = g_utf8_next_char(e);
2443 		}
2444 	}
2445 
2446 	g_free(val);
2447 
2448 	return g_string_free(ret, FALSE);
2449 }
2450 
2451 /* returns if the beginning of the text is a protocol. If it is the protocol, returns the length so
2452    the caller knows how long the protocol string is. */
gtk_imhtml_is_protocol(const char * text)2453 static int gtk_imhtml_is_protocol(const char *text)
2454 {
2455 	GtkIMHtmlProtocol *proto = imhtml_find_protocol(text, FALSE);
2456 	return proto ? proto->length : 0;
2457 }
2458 
2459 static gboolean smooth_scroll_cb(gpointer data);
2460 
2461 /*
2462  <KingAnt> marv: The two IM image functions in oscar are purple_odc_send_im and purple_odc_incoming
2463 
2464 
2465 [19:58] <Robot101> marv: images go into the imgstore, a refcounted... well.. hash. :)
2466 [19:59] <KingAnt> marv: I think the image tag used by the core is something like <img id="#"/>
2467 [19:59] Ro0tSiEgE robert42 RobFlynn Robot101 ross22 roz
2468 [20:00] <KingAnt> marv: Where the ID is the what is returned when you add the image to the imgstore using purple_imgstore_add
2469 [20:00] <marv> Robot101: so how does the image get passed to serv_got_im() and serv_send_im()? just as the <img id="#" and then the prpl looks it up from the store?
2470 [20:00] <KingAnt> marv: Right
2471 [20:00] <marv> alright
2472 
2473 Here's my plan with IMImages. make gtk_imhtml_[append|insert]_text_with_images instead just
2474 gtkimhtml_[append|insert]_text (hrm maybe it should be called html instead of text), add a
2475 function for purple to register for look up images, i.e. gtk_imhtml_set_get_img_fnc, so that
2476 images can be looked up like that, instead of passing a GSList of them.
2477  */
2478 
gtk_imhtml_append_text_with_images(GtkIMHtml * imhtml,const gchar * text,GtkIMHtmlOptions options,GSList * unused)2479 void gtk_imhtml_append_text_with_images (GtkIMHtml        *imhtml,
2480                                          const gchar      *text,
2481                                          GtkIMHtmlOptions  options,
2482 					 GSList *unused)
2483 {
2484 	GtkTextIter iter, ins, sel;
2485 	int ins_offset = 0, sel_offset = 0;
2486 	gboolean fixins = FALSE, fixsel = FALSE;
2487 
2488 	g_return_if_fail (imhtml != NULL);
2489 	g_return_if_fail (GTK_IS_IMHTML (imhtml));
2490 	g_return_if_fail (text != NULL);
2491 
2492 
2493 	gtk_text_buffer_get_end_iter(imhtml->text_buffer, &iter);
2494 	gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &ins, gtk_text_buffer_get_insert(imhtml->text_buffer));
2495 	if (gtk_text_iter_equal(&iter, &ins) && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, NULL, NULL)) {
2496 		fixins = TRUE;
2497 		ins_offset = gtk_text_iter_get_offset(&ins);
2498 	}
2499 
2500 	gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &sel, gtk_text_buffer_get_selection_bound(imhtml->text_buffer));
2501 	if (gtk_text_iter_equal(&iter, &sel) && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, NULL, NULL)) {
2502 		fixsel = TRUE;
2503 		sel_offset = gtk_text_iter_get_offset(&sel);
2504 	}
2505 
2506 	if (!(options & GTK_IMHTML_NO_SCROLL)) {
2507 		GdkRectangle rect;
2508 		int y, height;
2509 
2510 		gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect);
2511 		gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(imhtml), &iter, &y, &height);
2512 
2513 		if (((y + height) - (rect.y + rect.height)) > height &&
2514 				gtk_text_buffer_get_char_count(imhtml->text_buffer)) {
2515 			/* If we are in the middle of smooth-scrolling, then take a scroll step.
2516 			 * If we are not in the middle of smooth-scrolling, that means we were
2517 			 * not looking at the end of the buffer before the new text was added,
2518 			 * so do not scroll. */
2519 			if (imhtml->scroll_time)
2520 				smooth_scroll_cb(imhtml);
2521 			else
2522 				options |= GTK_IMHTML_NO_SCROLL;
2523 		}
2524 	}
2525 
2526 	gtk_imhtml_insert_html_at_iter(imhtml, text, options, &iter);
2527 
2528 	if (fixins) {
2529 		gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &ins, ins_offset);
2530 		gtk_text_buffer_move_mark(imhtml->text_buffer, gtk_text_buffer_get_insert(imhtml->text_buffer), &ins);
2531 	}
2532 
2533 	if (fixsel) {
2534 		gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &sel, sel_offset);
2535 		gtk_text_buffer_move_mark(imhtml->text_buffer, gtk_text_buffer_get_selection_bound(imhtml->text_buffer), &sel);
2536 	}
2537 
2538 	if (!(options & GTK_IMHTML_NO_SCROLL)) {
2539 		gtk_imhtml_scroll_to_end(imhtml, (options & GTK_IMHTML_USE_SMOOTHSCROLLING));
2540 	}
2541 }
2542 
2543 #define MAX_SCROLL_TIME 0.4 /* seconds */
2544 #define SCROLL_DELAY 33 /* milliseconds */
2545 
2546 /*
2547  * Smoothly scroll a GtkIMHtml.
2548  *
2549  * @return TRUE if the window needs to be scrolled further, FALSE if we're at the bottom.
2550  */
smooth_scroll_cb(gpointer data)2551 static gboolean smooth_scroll_cb(gpointer data)
2552 {
2553 	GtkIMHtml *imhtml = data;
2554 	GtkAdjustment *adj = GTK_TEXT_VIEW(imhtml)->vadjustment;
2555 	gdouble max_val = adj->upper - adj->page_size;
2556 	gdouble scroll_val = gtk_adjustment_get_value(adj) + ((max_val - gtk_adjustment_get_value(adj)) / 3);
2557 
2558 	g_return_val_if_fail(imhtml->scroll_time != NULL, FALSE);
2559 
2560 	if (g_timer_elapsed(imhtml->scroll_time, NULL) > MAX_SCROLL_TIME || scroll_val >= max_val) {
2561 		/* time's up. jump to the end and kill the timer */
2562 		gtk_adjustment_set_value(adj, max_val);
2563 		g_timer_destroy(imhtml->scroll_time);
2564 		imhtml->scroll_time = NULL;
2565 		g_source_remove(imhtml->scroll_src);
2566 		imhtml->scroll_src = 0;
2567 		return FALSE;
2568 	}
2569 
2570 	/* scroll by 1/3rd the remaining distance */
2571 	gtk_adjustment_set_value(adj, scroll_val);
2572 	return TRUE;
2573 }
2574 
scroll_idle_cb(gpointer data)2575 static gboolean scroll_idle_cb(gpointer data)
2576 {
2577 	GtkIMHtml *imhtml = data;
2578 	GtkAdjustment *adj = GTK_TEXT_VIEW(imhtml)->vadjustment;
2579 	if(adj) {
2580 		gtk_adjustment_set_value(adj, adj->upper - adj->page_size);
2581 	}
2582 	imhtml->scroll_src = 0;
2583 	return FALSE;
2584 }
2585 
gtk_imhtml_scroll_to_end(GtkIMHtml * imhtml,gboolean smooth)2586 void gtk_imhtml_scroll_to_end(GtkIMHtml *imhtml, gboolean smooth)
2587 {
2588 	if (imhtml->scroll_time)
2589 		g_timer_destroy(imhtml->scroll_time);
2590 	if (imhtml->scroll_src)
2591 		g_source_remove(imhtml->scroll_src);
2592 	if(smooth) {
2593 		imhtml->scroll_time = g_timer_new();
2594 		imhtml->scroll_src = g_timeout_add_full(G_PRIORITY_LOW, SCROLL_DELAY, smooth_scroll_cb, imhtml, NULL);
2595 	} else {
2596 		imhtml->scroll_time = NULL;
2597 		imhtml->scroll_src = g_idle_add_full(G_PRIORITY_LOW, scroll_idle_cb, imhtml, NULL);
2598 	}
2599 }
2600 
2601 /* CSS colors are either rgb (x,y,z) or #hex
2602  * we need to convert to hex if it is RGB */
2603 static gchar*
parse_css_color(gchar * in_color)2604 parse_css_color(gchar *in_color)
2605 {
2606 	char *tmp = in_color;
2607 
2608 	if (*tmp == 'r' && *(++tmp) == 'g' && *(++tmp) == 'b' && *(++tmp)) {
2609 		int rgbval[] = {0, 0, 0};
2610 		int count = 0;
2611 		const char *v_start;
2612 
2613 		while (*tmp && g_ascii_isspace(*tmp))
2614 			tmp++;
2615 		if (*tmp != '(') {
2616 			/* We don't support rgba() */
2617 			purple_debug_warning("gtkimhtml", "Invalid rgb CSS color in '%s'!\n", in_color);
2618 			return in_color;
2619 		}
2620 		tmp++;
2621 
2622 		while (count < 3) {
2623 			/* Skip any leading spaces */
2624 			while (*tmp && g_ascii_isspace(*tmp))
2625 				tmp++;
2626 
2627 			/* Find the subsequent contiguous digits */
2628 			v_start = tmp;
2629 			if (*v_start == '-')
2630 				tmp++;
2631 			while (*tmp && g_ascii_isdigit(*tmp))
2632 				tmp++;
2633 
2634 			if (tmp != v_start) {
2635 				char prev = *tmp;
2636 				*tmp = '\0';
2637 				rgbval[count] = atoi(v_start);
2638 				*tmp = prev;
2639 
2640 				/* deal with % */
2641 				while (*tmp && g_ascii_isspace(*tmp))
2642 					tmp++;
2643 				if (*tmp == '%') {
2644 					rgbval[count] = (rgbval[count] / 100.0) * 255;
2645 					tmp++;
2646 				}
2647 			} else {
2648 				purple_debug_warning("gtkimhtml", "Invalid rgb CSS color in '%s'!\n", in_color);
2649 				return in_color;
2650 			}
2651 
2652 			if (rgbval[count] > 255) {
2653 				rgbval[count] = 255;
2654 			} else if (rgbval[count] < 0) {
2655 				rgbval[count] = 0;
2656 			}
2657 
2658 			while (*tmp && g_ascii_isspace(*tmp))
2659 				tmp++;
2660 			if (*tmp == ',')
2661 				tmp++;
2662 
2663 			count++;
2664 		}
2665 
2666 		g_free(in_color);
2667 		return g_strdup_printf("#%02X%02X%02X", rgbval[0], rgbval[1], rgbval[2]);
2668 	}
2669 
2670 	return in_color;
2671 }
2672 
gtk_imhtml_insert_html_at_iter(GtkIMHtml * imhtml,const gchar * text,GtkIMHtmlOptions options,GtkTextIter * iter)2673 void gtk_imhtml_insert_html_at_iter(GtkIMHtml        *imhtml,
2674                                     const gchar      *text,
2675                                     GtkIMHtmlOptions  options,
2676                                     GtkTextIter      *iter)
2677 {
2678 	GdkRectangle rect;
2679 	gint pos = 0;
2680 	gchar *ws;
2681 	gchar *tag;
2682 	gchar *bg = NULL;
2683 	gint len;
2684 	gint tlen, smilelen, wpos=0;
2685 	gint type;
2686 	const gchar *c;
2687 	const gchar *amp;
2688 	gint len_protocol;
2689 
2690 	guint	bold = 0,
2691 		italics = 0,
2692 		underline = 0,
2693 		strike = 0,
2694 		sub = 0,
2695 		sup = 0,
2696 		title = 0,
2697 		pre = 0;
2698 
2699 	gboolean br = FALSE;
2700 	gboolean align_right = FALSE;
2701 	gboolean rtl_direction = FALSE;
2702 	gint align_line = 0;
2703 
2704 	GSList *fonts = NULL;
2705 	GObject *object;
2706 	GtkIMHtmlScalable *scalable = NULL;
2707 
2708 	g_return_if_fail (imhtml != NULL);
2709 	g_return_if_fail (GTK_IS_IMHTML (imhtml));
2710 	g_return_if_fail (text != NULL);
2711 	c = text;
2712 	len = strlen(text);
2713 	ws = g_malloc(len + 1);
2714 	ws[0] = '\0';
2715 
2716 	g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(0));
2717 
2718 	gtk_text_buffer_begin_user_action(imhtml->text_buffer);
2719 	while (pos < len) {
2720 		if (*c == '<' && gtk_imhtml_is_tag (c + 1, &tag, &tlen, &type)) {
2721 			c++;
2722 			pos++;
2723 			ws[wpos] = '\0';
2724 			br = FALSE;
2725 			switch (type)
2726 				{
2727 				case 1:		/* B */
2728 				case 2:		/* BOLD */
2729 				case 54:	/* STRONG */
2730 					if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2731 						gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2732 
2733 						if ((bold == 0) && (imhtml->format_functions & GTK_IMHTML_BOLD))
2734 							gtk_imhtml_toggle_bold(imhtml);
2735 						bold++;
2736 						ws[0] = '\0';
2737 						wpos = 0;
2738 					}
2739 					break;
2740 				case 3:		/* /B */
2741 				case 4:		/* /BOLD */
2742 				case 55:	/* /STRONG */
2743 					if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2744 						gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2745 						ws[0] = '\0';
2746 						wpos = 0;
2747 
2748 						if (bold) {
2749 							bold--;
2750 							if ((bold == 0) && (imhtml->format_functions & GTK_IMHTML_BOLD) && !imhtml->wbfo)
2751 								gtk_imhtml_toggle_bold(imhtml);
2752 						}
2753 					}
2754 					break;
2755 				case 5:		/* I */
2756 				case 6:		/* ITALIC */
2757 				case 52:	/* EM */
2758 					if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2759 						gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2760 						ws[0] = '\0';
2761 						wpos = 0;
2762 						if ((italics == 0) && (imhtml->format_functions & GTK_IMHTML_ITALIC))
2763 							gtk_imhtml_toggle_italic(imhtml);
2764 						italics++;
2765 					}
2766 					break;
2767 				case 7:		/* /I */
2768 				case 8:		/* /ITALIC */
2769 				case 53:	/* /EM */
2770 					if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2771 						gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2772 						ws[0] = '\0';
2773 						wpos = 0;
2774 						if (italics) {
2775 							italics--;
2776 							if ((italics == 0) && (imhtml->format_functions & GTK_IMHTML_ITALIC) && !imhtml->wbfo)
2777 								gtk_imhtml_toggle_italic(imhtml);
2778 						}
2779 					}
2780 					break;
2781 				case 9:		/* U */
2782 				case 10:	/* UNDERLINE */
2783 					if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2784 						gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2785 						ws[0] = '\0';
2786 						wpos = 0;
2787 						if ((underline == 0) && (imhtml->format_functions & GTK_IMHTML_UNDERLINE))
2788 							gtk_imhtml_toggle_underline(imhtml);
2789 						underline++;
2790 					}
2791 					break;
2792 				case 11:	/* /U */
2793 				case 12:	/* /UNDERLINE */
2794 					if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2795 						gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2796 						ws[0] = '\0';
2797 						wpos = 0;
2798 						if (underline) {
2799 							underline--;
2800 							if ((underline == 0) && (imhtml->format_functions & GTK_IMHTML_UNDERLINE) && !imhtml->wbfo)
2801 								gtk_imhtml_toggle_underline(imhtml);
2802 						}
2803 					}
2804 					break;
2805 				case 13:	/* S */
2806 				case 14:	/* STRIKE */
2807 					gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2808 					ws[0] = '\0';
2809 					wpos = 0;
2810 					if ((strike == 0) && (imhtml->format_functions & GTK_IMHTML_STRIKE))
2811 						gtk_imhtml_toggle_strike(imhtml);
2812 					strike++;
2813 					break;
2814 				case 15:	/* /S */
2815 				case 16:	/* /STRIKE */
2816 					gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2817 					ws[0] = '\0';
2818 					wpos = 0;
2819 					if (strike)
2820 						strike--;
2821 					if ((strike == 0) && (imhtml->format_functions & GTK_IMHTML_STRIKE) && !imhtml->wbfo)
2822 						gtk_imhtml_toggle_strike(imhtml);
2823 					break;
2824 				case 17:	/* SUB */
2825 					/* FIXME: reimpliment this */
2826 					sub++;
2827 					break;
2828 				case 18:	/* /SUB */
2829 					/* FIXME: reimpliment this */
2830 					if (sub)
2831 						sub--;
2832 					break;
2833 				case 19:	/* SUP */
2834 					/* FIXME: reimplement this */
2835 					sup++;
2836 				break;
2837 				case 20:	/* /SUP */
2838 					/* FIXME: reimplement this */
2839 					if (sup)
2840 						sup--;
2841 					break;
2842 				case 21:	/* PRE */
2843 					/* FIXME: reimplement this */
2844 					pre++;
2845 					break;
2846 				case 22:	/* /PRE */
2847 					/* FIXME: reimplement this */
2848 					if (pre)
2849 						pre--;
2850 					break;
2851 				case 23:	/* TITLE */
2852 					/* FIXME: what was this supposed to do anyway? */
2853 					title++;
2854 					break;
2855 				case 24:	/* /TITLE */
2856 					/* FIXME: make this undo whatever 23 was supposed to do */
2857 					if (title) {
2858 						if (options & GTK_IMHTML_NO_TITLE) {
2859 							wpos = 0;
2860 							ws [wpos] = '\0';
2861 						}
2862 						title--;
2863 					}
2864 					break;
2865 				case 25:	/* BR */
2866 				case 58:	/* BR/ */
2867 				case 61:	/* BR (opt) */
2868 					ws[wpos] = '\n';
2869 					wpos++;
2870 					br = TRUE;
2871 					break;
2872 				case 29:	/* P */
2873 					if(wpos > 0) {
2874 						ws[wpos] = '\n';
2875 						wpos++;
2876 						br = TRUE;
2877 					}
2878 					break;
2879 				case 26:        /* HR */
2880 				case 42:        /* HR (opt) */
2881 				{
2882 					int minus;
2883 					struct scalable_data *sd = g_new(struct scalable_data, 1);
2884 
2885 					ws[wpos++] = '\n';
2886 					gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2887 
2888 					sd->scalable = scalable = gtk_imhtml_hr_new();
2889 					sd->mark = gtk_text_buffer_create_mark(imhtml->text_buffer, NULL, iter, TRUE);
2890 					gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect);
2891 					scalable->add_to(scalable, imhtml, iter);
2892 					minus = gtk_text_view_get_left_margin(GTK_TEXT_VIEW(imhtml)) +
2893 					        gtk_text_view_get_right_margin(GTK_TEXT_VIEW(imhtml));
2894 					scalable->scale(scalable, rect.width - minus, rect.height);
2895 					imhtml->scalables = g_list_append(imhtml->scalables, sd);
2896 					ws[0] = '\0';
2897 					wpos = 0;
2898 					ws[wpos++] = '\n';
2899 
2900 					break;
2901 				}
2902 				case 27:	/* /FONT */
2903 					if (fonts && !imhtml->wbfo) {
2904 						GtkIMHtmlFontDetail *font = fonts->data;
2905 						gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2906 						ws[0] = '\0';
2907 						wpos = 0;
2908 						/* NEW_BIT (NEW_TEXT_BIT); */
2909 
2910 						if (font->face && (imhtml->format_functions & GTK_IMHTML_FACE)) {
2911 							gtk_imhtml_toggle_fontface(imhtml, NULL);
2912 						}
2913 						g_free (font->face);
2914 						if (font->fore && (imhtml->format_functions & GTK_IMHTML_FORECOLOR)) {
2915 							gtk_imhtml_toggle_forecolor(imhtml, NULL);
2916 						}
2917 						g_free (font->fore);
2918 						if (font->back && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) {
2919 							gtk_imhtml_toggle_backcolor(imhtml, NULL);
2920 						}
2921 						g_free (font->back);
2922 						g_free (font->sml);
2923 
2924 						if ((font->size != 3) && (imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK)))
2925 							gtk_imhtml_font_set_size(imhtml, 3);
2926 
2927 						fonts = g_slist_remove (fonts, font);
2928 						g_free(font);
2929 
2930 						if (fonts) {
2931 							GtkIMHtmlFontDetail *font = fonts->data;
2932 
2933 							if (font->face && (imhtml->format_functions & GTK_IMHTML_FACE))
2934 								gtk_imhtml_toggle_fontface(imhtml, font->face);
2935 							if (font->fore && (imhtml->format_functions & GTK_IMHTML_FORECOLOR))
2936 								gtk_imhtml_toggle_forecolor(imhtml, font->fore);
2937 							if (font->back && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR))
2938 								gtk_imhtml_toggle_backcolor(imhtml, font->back);
2939 							if ((font->size != 3) && (imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK)))
2940 								gtk_imhtml_font_set_size(imhtml, font->size);
2941 						}
2942 					}
2943 						break;
2944 				case 28:        /* /A    */
2945 					gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2946 					gtk_imhtml_toggle_link(imhtml, NULL);
2947 					ws[0] = '\0';
2948 					wpos = 0;
2949 					break;
2950 
2951 				case 30:	/* /P */
2952 				case 31:	/* H3 */
2953 				case 32:	/* /H3 */
2954 				case 33:	/* HTML */
2955 				case 34:	/* /HTML */
2956 				case 35:	/* BODY */
2957 					break;
2958 				case 36:	/* /BODY */
2959 					gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2960 					ws[0] = '\0';
2961 					wpos = 0;
2962 					gtk_imhtml_toggle_background(imhtml, NULL);
2963 					break;
2964 				case 37:	/* FONT */
2965 				case 38:	/* HEAD */
2966 				case 39:	/* /HEAD */
2967 				case 40:	/* BINARY */
2968 				case 41:	/* /BINARY */
2969 					break;
2970 				case 43:	/* FONT (opt) */
2971 					{
2972 						gchar *color, *back, *face, *size, *sml;
2973 						GtkIMHtmlFontDetail *font, *oldfont = NULL;
2974 						color = gtk_imhtml_get_html_opt (tag, "COLOR=");
2975 						back = gtk_imhtml_get_html_opt (tag, "BACK=");
2976 						face = gtk_imhtml_get_html_opt (tag, "FACE=");
2977 						size = gtk_imhtml_get_html_opt (tag, "SIZE=");
2978 						sml = gtk_imhtml_get_html_opt (tag, "SML=");
2979 						if (!(color || back || face || size || sml))
2980 							break;
2981 
2982 						gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2983 						ws[0] = '\0';
2984 						wpos = 0;
2985 
2986 						font = g_new0 (GtkIMHtmlFontDetail, 1);
2987 						if (fonts)
2988 							oldfont = fonts->data;
2989 
2990 						if (color && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_FORECOLOR)) {
2991 							font->fore = color;
2992 							gtk_imhtml_toggle_forecolor(imhtml, font->fore);
2993 						} else
2994 							g_free(color);
2995 
2996 						if (back && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) {
2997 							font->back = back;
2998 							gtk_imhtml_toggle_backcolor(imhtml, font->back);
2999 						} else
3000 							g_free(back);
3001 
3002 						if (face && !(options & GTK_IMHTML_NO_FONTS) && (imhtml->format_functions & GTK_IMHTML_FACE)) {
3003 							font->face = face;
3004 							gtk_imhtml_toggle_fontface(imhtml, font->face);
3005 						} else
3006 							g_free(face);
3007 
3008 						if (sml)
3009 							font->sml = sml;
3010 						else {
3011 							g_free(sml);
3012 							if (oldfont && oldfont->sml)
3013 								font->sml = g_strdup(oldfont->sml);
3014 						}
3015 
3016 						if (size && !(options & GTK_IMHTML_NO_SIZES) && (imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK))) {
3017 							if (*size == '+') {
3018 								sscanf (size + 1, "%hd", &font->size);
3019 								font->size += 3;
3020 							} else if (*size == '-') {
3021 								sscanf (size + 1, "%hd", &font->size);
3022 								font->size = MAX (0, 3 - font->size);
3023 							} else if (isdigit (*size)) {
3024 								sscanf (size, "%hd", &font->size);
3025 							}
3026 							if (font->size > 100)
3027 								font->size = 100;
3028 						} else if (oldfont)
3029 							font->size = oldfont->size;
3030 						else
3031 							font->size = 3;
3032 						if ((imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK)) && (font->size != 3 || (oldfont && oldfont->size == 3)))
3033 							gtk_imhtml_font_set_size(imhtml, font->size);
3034 						g_free(size);
3035 						fonts = g_slist_prepend (fonts, font);
3036 					}
3037 					break;
3038 				case 44:	/* BODY (opt) */
3039 					if (!(options & GTK_IMHTML_NO_COLOURS)) {
3040 						char *bgcolor = gtk_imhtml_get_html_opt (tag, "BGCOLOR=");
3041 						if (bgcolor && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) {
3042 							gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3043 							ws[0] = '\0';
3044 							wpos = 0;
3045 							/* NEW_BIT(NEW_TEXT_BIT); */
3046 							g_free(bg);
3047 							bg = bgcolor;
3048 							gtk_imhtml_toggle_background(imhtml, bg);
3049 						} else
3050 							g_free(bgcolor);
3051 					}
3052 					break;
3053 				case 45:	/* A (opt) */
3054 					{
3055 						gchar *href = gtk_imhtml_get_html_opt (tag, "HREF=");
3056 						if (href && (imhtml->format_functions & GTK_IMHTML_LINK)) {
3057 							gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3058 							ws[0] = '\0';
3059 							wpos = 0;
3060 							gtk_imhtml_toggle_link(imhtml, href);
3061 						}
3062 						g_free(href);
3063 					}
3064 					break;
3065 				case 46:	/* IMG (opt) */
3066 				case 59:	/* IMG */
3067 					{
3068 						char *id;
3069 
3070 						gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3071 						ws[0] = '\0';
3072 						wpos = 0;
3073 
3074 						if (!(imhtml->format_functions & GTK_IMHTML_IMAGE))
3075 							break;
3076 
3077 						id = gtk_imhtml_get_html_opt(tag, "ID=");
3078 						if (id) {
3079 							gtk_imhtml_insert_image_at_iter(imhtml, atoi(id), iter);
3080 							g_free(id);
3081 						} else {
3082 							char *src, *alt;
3083 							src = gtk_imhtml_get_html_opt(tag, "SRC=");
3084 							alt = gtk_imhtml_get_html_opt(tag, "ALT=");
3085 							if (src) {
3086 								gtk_imhtml_toggle_link(imhtml, src);
3087 								gtk_text_buffer_insert(imhtml->text_buffer, iter, alt ? alt : src, -1);
3088 								gtk_imhtml_toggle_link(imhtml, NULL);
3089 							}
3090 							g_free (src);
3091 							g_free (alt);
3092 						}
3093 						break;
3094 					}
3095 				case 47:	/* P (opt) */
3096 				case 48:	/* H3 (opt) */
3097 				case 49:	/* HTML (opt) */
3098 				case 50:	/* CITE */
3099 				case 51:	/* /CITE */
3100 				case 56:	/* SPAN (opt) */
3101 					/* Inline CSS Support - Douglas Thrift
3102 					 *
3103 					 * color
3104 					 * background
3105 					 * font-family
3106 					 * font-size
3107 					 * text-decoration: underline
3108 					 * font-weight: bold
3109 					 * direction: rtl
3110 					 * text-align: right
3111 					 *
3112 					 * TODO:
3113 					 * background-color
3114 					 * font-style
3115 					 */
3116 					{
3117 						gchar *style, *color, *background, *family, *size, *direction, *alignment;
3118 						gchar *textdec, *weight;
3119 						GtkIMHtmlFontDetail *font, *oldfont = NULL;
3120 						style = gtk_imhtml_get_html_opt (tag, "style=");
3121 
3122 						if (!style) break;
3123 
3124 						color = purple_markup_get_css_property (style, "color");
3125 						background = purple_markup_get_css_property (style, "background");
3126 						family = purple_markup_get_css_property (style, "font-family");
3127 						size = purple_markup_get_css_property (style, "font-size");
3128 						textdec = purple_markup_get_css_property (style, "text-decoration");
3129 						weight = purple_markup_get_css_property (style, "font-weight");
3130 						direction = purple_markup_get_css_property (style, "direction");
3131 						alignment = purple_markup_get_css_property (style, "text-align");
3132 
3133 
3134 						if (!(color || family || size || background || textdec || weight || direction || alignment)) {
3135 							g_free(style);
3136 							break;
3137 						}
3138 
3139 
3140 						gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3141 						ws[0] = '\0';
3142 						wpos = 0;
3143 						/* NEW_BIT (NEW_TEXT_BIT); */
3144 
3145 						/* Bi-Directional text support */
3146 						if (direction && (!g_ascii_strncasecmp(direction, "RTL", 3))) {
3147 							rtl_direction = TRUE;
3148 							/* insert RLE character to set direction */
3149 							ws[wpos++]  = 0xE2;
3150 							ws[wpos++]  = 0x80;
3151 							ws[wpos++]  = 0xAB;
3152 							ws[wpos]  = '\0';
3153 							gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3154 							ws[0] = '\0';
3155 							wpos = 0;
3156 						}
3157 						g_free(direction);
3158 
3159 						if (alignment && (!g_ascii_strncasecmp(alignment, "RIGHT", 5))) {
3160 							align_right = TRUE;
3161 							align_line = gtk_text_iter_get_line(iter);
3162 						}
3163 						g_free(alignment);
3164 
3165 						font = g_new0 (GtkIMHtmlFontDetail, 1);
3166 						if (fonts)
3167 							oldfont = fonts->data;
3168 
3169 						if (color && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_FORECOLOR)) {
3170 							font->fore = parse_css_color(color);
3171 							gtk_imhtml_toggle_forecolor(imhtml, font->fore);
3172 						} else {
3173 							if (oldfont && oldfont->fore)
3174 								font->fore = g_strdup(oldfont->fore);
3175 							g_free(color);
3176 						}
3177 
3178 						if (background && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) {
3179 							font->back = parse_css_color(background);
3180 							gtk_imhtml_toggle_backcolor(imhtml, font->back);
3181 						} else {
3182 							if (oldfont && oldfont->back)
3183 								font->back = g_strdup(oldfont->back);
3184 							g_free(background);
3185 						}
3186 
3187 						if (family && !(options & GTK_IMHTML_NO_FONTS) && (imhtml->format_functions & GTK_IMHTML_FACE)) {
3188 							font->face = family;
3189 							gtk_imhtml_toggle_fontface(imhtml, font->face);
3190 						} else {
3191 							if (oldfont && oldfont->face)
3192 								font->face = g_strdup(oldfont->face);
3193 							g_free(family);
3194 						}
3195 						if (font->face && (atoi(font->face) > 100)) {
3196 							/* WTF is this? */
3197 							/* Maybe it sets a max size on the font face?  I seem to
3198 							 * remember bad things happening if the font size was
3199 							 * 2 billion */
3200 							g_free(font->face);
3201 							font->face = g_strdup("100");
3202 						}
3203 
3204 						if (oldfont && oldfont->sml)
3205 							font->sml = g_strdup(oldfont->sml);
3206 
3207 						if (size && !(options & GTK_IMHTML_NO_SIZES) && (imhtml->format_functions & (GTK_IMHTML_SHRINK|GTK_IMHTML_GROW))) {
3208 							if (g_ascii_strcasecmp(size, "xx-small") == 0)
3209 								font->size = 1;
3210 							else if (g_ascii_strcasecmp(size, "smaller") == 0
3211 								  || g_ascii_strcasecmp(size, "x-small") == 0)
3212 								font->size = 2;
3213 							else if (g_ascii_strcasecmp(size, "medium") == 0)
3214 								font->size = 3;
3215 							else if (g_ascii_strcasecmp(size, "large") == 0
3216 								  || g_ascii_strcasecmp(size, "larger") == 0)
3217 								font->size = 4;
3218 							else if (g_ascii_strcasecmp(size, "x-large") == 0)
3219 								font->size = 5;
3220 							else if (g_ascii_strcasecmp(size, "xx-large") == 0)
3221 								font->size = 6;
3222 
3223 							/*
3224 							 * TODO: Handle other values, like percentages, or
3225 							 * lengths specified as em, ex, px, in, cm, mm, pt
3226 							 * or pc.  Or even better, use an actual HTML
3227 							 * renderer like webkit.
3228 							 */
3229 							if (font->size > 0)
3230 							    gtk_imhtml_font_set_size(imhtml, font->size);
3231 						}
3232 						else if (oldfont)
3233 						{
3234 						    font->size = oldfont->size;
3235 						}
3236 
3237 						if (oldfont)
3238 						{
3239 						    font->underline = oldfont->underline;
3240 						}
3241 						if (textdec && font->underline != 1
3242 							&& g_ascii_strcasecmp(textdec, "underline") == 0
3243 							&& (imhtml->format_functions & GTK_IMHTML_UNDERLINE)
3244 							&& !(options & GTK_IMHTML_NO_FORMATTING))
3245 						{
3246 						    gtk_imhtml_toggle_underline(imhtml);
3247 						    font->underline = 1;
3248 						}
3249 
3250 						if (oldfont)
3251 						{
3252 						    font->strike = oldfont->strike;
3253 						}
3254 						if (textdec && font->strike != 1
3255 							&& g_ascii_strcasecmp(textdec, "line-through") == 0
3256 							&& (imhtml->format_functions & GTK_IMHTML_STRIKE)
3257 							&& !(options & GTK_IMHTML_NO_FORMATTING))
3258 						{
3259 						    gtk_imhtml_toggle_strike(imhtml);
3260 						    font->strike = 1;
3261 						}
3262 						g_free(textdec);
3263 
3264 						if (oldfont)
3265 						{
3266 							font->bold = oldfont->bold;
3267 						}
3268 						if (weight)
3269 						{
3270 							if(!g_ascii_strcasecmp(weight, "normal")) {
3271 								font->bold = 0;
3272 							} else if(!g_ascii_strcasecmp(weight, "bold")) {
3273 								font->bold = 1;
3274 							} else if(!g_ascii_strcasecmp(weight, "bolder")) {
3275 								font->bold++;
3276 							} else if(!g_ascii_strcasecmp(weight, "lighter")) {
3277 								if(font->bold > 0)
3278 									font->bold--;
3279 							} else {
3280 								int num = atoi(weight);
3281 								if(num >= 700)
3282 									font->bold = 1;
3283 								else
3284 									font->bold = 0;
3285 							}
3286 							if (((font->bold && oldfont && !oldfont->bold) || (oldfont && oldfont->bold && !font->bold) || (font->bold && !oldfont)) && !(options & GTK_IMHTML_NO_FORMATTING))
3287 							{
3288 								gtk_imhtml_toggle_bold(imhtml);
3289 							}
3290 							g_free(weight);
3291 						}
3292 
3293 						g_free(style);
3294 						g_free(size);
3295 						fonts = g_slist_prepend (fonts, font);
3296 					}
3297 					break;
3298 				case 57:	/* /SPAN */
3299 					/* Inline CSS Support - Douglas Thrift */
3300 					if (fonts && !imhtml->wbfo) {
3301 						GtkIMHtmlFontDetail *oldfont = NULL;
3302 						GtkIMHtmlFontDetail *font = fonts->data;
3303 						gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3304 						ws[0] = '\0';
3305 						wpos = 0;
3306 						/* NEW_BIT (NEW_TEXT_BIT); */
3307 						fonts = g_slist_remove (fonts, font);
3308 						if (fonts)
3309 						    oldfont = fonts->data;
3310 
3311 						if (!oldfont) {
3312 							gtk_imhtml_font_set_size(imhtml, 3);
3313 							if (font->underline && !(options & GTK_IMHTML_NO_FORMATTING))
3314 							    gtk_imhtml_toggle_underline(imhtml);
3315 							if (font->strike && !(options & GTK_IMHTML_NO_FORMATTING))
3316 							    gtk_imhtml_toggle_strike(imhtml);
3317 							if (font->bold && !(options & GTK_IMHTML_NO_FORMATTING))
3318 								gtk_imhtml_toggle_bold(imhtml);
3319 							if (!(options & GTK_IMHTML_NO_FONTS))
3320 								gtk_imhtml_toggle_fontface(imhtml, NULL);
3321 							if (!(options & GTK_IMHTML_NO_COLOURS))
3322 								gtk_imhtml_toggle_forecolor(imhtml, NULL);
3323 							if (!(options & GTK_IMHTML_NO_COLOURS))
3324 								gtk_imhtml_toggle_backcolor(imhtml, NULL);
3325 						}
3326 						else
3327 						{
3328 
3329 							if ((font->size != oldfont->size) && !(options & GTK_IMHTML_NO_SIZES))
3330 							    gtk_imhtml_font_set_size(imhtml, oldfont->size);
3331 
3332 							if ((font->underline != oldfont->underline) && !(options & GTK_IMHTML_NO_FORMATTING))
3333 							    gtk_imhtml_toggle_underline(imhtml);
3334 
3335 							if ((font->strike != oldfont->strike) && !(options & GTK_IMHTML_NO_FORMATTING))
3336 							    gtk_imhtml_toggle_strike(imhtml);
3337 
3338 							if (((font->bold && !oldfont->bold) || (oldfont->bold && !font->bold)) && !(options & GTK_IMHTML_NO_FORMATTING))
3339 							    gtk_imhtml_toggle_bold(imhtml);
3340 
3341 							if (font->face && !purple_strequal(font->face, oldfont->face) && !(options & GTK_IMHTML_NO_FONTS))
3342 							    gtk_imhtml_toggle_fontface(imhtml, oldfont->face);
3343 
3344 							if (font->fore && !purple_strequal(font->fore, oldfont->fore) && !(options & GTK_IMHTML_NO_COLOURS))
3345 							    gtk_imhtml_toggle_forecolor(imhtml, oldfont->fore);
3346 
3347 							if (font->back && !purple_strequal(font->back, oldfont->back) && !(options & GTK_IMHTML_NO_COLOURS))
3348 							    gtk_imhtml_toggle_backcolor(imhtml, oldfont->back);
3349 						}
3350 
3351 						g_free (font->face);
3352 						g_free (font->fore);
3353 						g_free (font->back);
3354 						g_free (font->sml);
3355 
3356 						g_free (font);
3357 					}
3358 					break;
3359 				case 60:    /* SPAN */
3360 					break;
3361 				case 62:	/* comment */
3362 					/* NEW_BIT (NEW_TEXT_BIT); */
3363 					ws[wpos] = '\0';
3364 
3365 					gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3366 
3367 #if FALSE && GTK_CHECK_VERSION(2,10,10)
3368 					wpos = g_snprintf (ws, len, "%s", tag);
3369 					gtk_text_buffer_insert_with_tags_by_name(imhtml->text_buffer, iter, ws, wpos, "comment", NULL);
3370 #else
3371 					if (imhtml->show_comments && !(options & GTK_IMHTML_NO_COMMENTS)) {
3372 						wpos = g_snprintf (ws, len, "%s", tag);
3373 						gtk_text_buffer_insert_with_tags_by_name(imhtml->text_buffer, iter, ws, wpos, "comment", NULL);
3374 					}
3375 #endif
3376 					ws[0] = '\0';
3377 					wpos = 0;
3378 
3379 					/* NEW_BIT (NEW_COMMENT_BIT); */
3380 					break;
3381 				default:
3382 					break;
3383 				}
3384 			c += tlen;
3385 			pos += tlen;
3386 			g_free(tag); /* This was allocated back in VALID_TAG() */
3387 		} else if (imhtml->edit.link == NULL &&
3388 				!(options & GTK_IMHTML_NO_SMILEY) &&
3389 				gtk_imhtml_is_smiley(imhtml, fonts, c, &smilelen)) {
3390 			GtkIMHtmlFontDetail *fd;
3391 			gchar *sml = NULL;
3392 
3393 			br = FALSE;
3394 
3395 			if (fonts) {
3396 				fd = fonts->data;
3397 				sml = fd->sml;
3398 			}
3399 			if (!sml)
3400 				sml = imhtml->protocol_name;
3401 
3402 			gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3403 			g_snprintf (ws, smilelen + 1, "%s", c);
3404 
3405 			gtk_imhtml_insert_smiley_at_iter(imhtml, sml, ws, iter);
3406 
3407 			c += smilelen;
3408 			pos += smilelen;
3409 			wpos = 0;
3410 			ws[0] = 0;
3411 		} else if (*c == '&' && (amp = purple_markup_unescape_entity(c, &tlen))) {
3412 			br = FALSE;
3413 			while(*amp) {
3414 				ws [wpos++] = *amp++;
3415 			}
3416 			c += tlen;
3417 			pos += tlen;
3418 		} else if (*c == '\n') {
3419 			if (!(options & GTK_IMHTML_NO_NEWLINE)) {
3420 				ws[wpos] = '\n';
3421 				wpos++;
3422 				gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3423 				ws[0] = '\0';
3424 				wpos = 0;
3425 				/* NEW_BIT (NEW_TEXT_BIT); */
3426 			} else if (!br) {  /* Don't insert a space immediately after an HTML break */
3427 				/* A newline is defined by HTML as whitespace, which means we have to replace it with a word boundary.
3428 				 * word breaks vary depending on the language used, so the correct thing to do is to use Pango to determine
3429 				 * what language this is, determine the proper word boundary to use, and insert that. I'm just going to insert
3430 				 * a space instead.  What are the non-English speakers going to do?  Complain in a language I'll understand?
3431 				 * Bu-wahaha! */
3432 				ws[wpos] = ' ';
3433 				wpos++;
3434 				gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3435 				ws[0] = '\0';
3436 				wpos = 0;
3437 			}
3438 			c++;
3439 			pos++;
3440 		} else if ((pos == 0 || wpos == 0 || isspace(*(c - 1))) &&
3441 		           (len_protocol = gtk_imhtml_is_protocol(c)) > 0 &&
3442 				   c[len_protocol] && !isspace(c[len_protocol]) &&
3443 				   (c[len_protocol] != '<' || !gtk_imhtml_is_tag(c + 1, NULL, NULL, NULL))) {
3444 			br = FALSE;
3445 			if (wpos > 0) {
3446 				gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3447 				ws[0] = '\0';
3448 				wpos = 0;
3449 			}
3450 			while (len_protocol--) {
3451 				/* Skip the next len_protocol characters, but
3452 				 * make sure they're copied into the ws array.
3453 				 */
3454 				ws [wpos++] = *c++;
3455 				pos++;
3456 			}
3457 			if (!imhtml->edit.link && (imhtml->format_functions & GTK_IMHTML_LINK)) {
3458 				while (*c && !isspace((int)*c) &&
3459 						(*c != '<' || !gtk_imhtml_is_tag(c + 1, NULL, NULL, NULL))) {
3460 					if (*c == '&' && (amp = purple_markup_unescape_entity(c, &tlen))) {
3461 						while (*amp)
3462 							ws[wpos++] = *amp++;
3463 						c += tlen;
3464 						pos += tlen;
3465 					} else {
3466 						ws [wpos++] = *c++;
3467 						pos++;
3468 					}
3469 				}
3470 				ws[wpos] = '\0';
3471 				gtk_imhtml_toggle_link(imhtml, ws);
3472 				gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3473 				ws[0] = '\0';
3474 				wpos = 0;
3475 				gtk_imhtml_toggle_link(imhtml, NULL);
3476 			}
3477 		} else if (*c) {
3478 			br = FALSE;
3479 			ws [wpos++] = *c++;
3480 			pos++;
3481 		} else {
3482 			break;
3483 		}
3484 	}
3485 	gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3486 	ws[0] = '\0';
3487 	wpos = 0;
3488 
3489 	/* NEW_BIT(NEW_TEXT_BIT); */
3490 
3491 	if(align_right) {
3492 		/* insert RLM+LRM at beginning of the line to set alignment */
3493 		GtkTextIter line_iter;
3494 		line_iter = *iter;
3495 		gtk_text_iter_set_line(&line_iter, align_line);
3496 		/* insert RLM character to set alignment */
3497 		ws[wpos++]  = 0xE2;
3498 		ws[wpos++]  = 0x80;
3499 		ws[wpos++]  = 0x8F;
3500 
3501 		if (!rtl_direction)
3502 		{
3503 			/* insert LRM character to set direction */
3504 			/* (alignment=right and direction=LTR) */
3505 			ws[wpos++]  = 0xE2;
3506 			ws[wpos++]  = 0x80;
3507 			ws[wpos++]  = 0x8E;
3508 		}
3509 
3510 		ws[wpos]  = '\0';
3511 		gtk_text_buffer_insert(imhtml->text_buffer, &line_iter, ws, wpos);
3512 		gtk_text_buffer_get_end_iter(gtk_text_iter_get_buffer(&line_iter), iter);
3513 		ws[0] = '\0';
3514 	}
3515 
3516 	while (fonts) {
3517 		GtkIMHtmlFontDetail *font = fonts->data;
3518 		fonts = g_slist_remove (fonts, font);
3519 		g_free (font->face);
3520 		g_free (font->fore);
3521 		g_free (font->back);
3522 		g_free (font->sml);
3523 		g_free (font);
3524 	}
3525 
3526 	g_free(ws);
3527 	g_free(bg);
3528 
3529 	if (!imhtml->wbfo)
3530 		gtk_imhtml_close_tags(imhtml, iter);
3531 
3532 	object = g_object_ref(G_OBJECT(imhtml));
3533 	g_signal_emit(object, signals[UPDATE_FORMAT], 0);
3534 	g_object_unref(object);
3535 
3536 	gtk_text_buffer_end_user_action(imhtml->text_buffer);
3537 }
3538 
gtk_imhtml_remove_smileys(GtkIMHtml * imhtml)3539 void gtk_imhtml_remove_smileys(GtkIMHtml *imhtml)
3540 {
3541 	g_hash_table_destroy(imhtml->smiley_data);
3542 	gtk_smiley_tree_destroy(imhtml->default_smilies);
3543 	imhtml->smiley_data = g_hash_table_new_full(g_str_hash, g_str_equal,
3544 			g_free, (GDestroyNotify)gtk_smiley_tree_destroy);
3545 	imhtml->default_smilies = gtk_smiley_tree_new();
3546 }
3547 
gtk_imhtml_show_comments(GtkIMHtml * imhtml,gboolean show)3548 void       gtk_imhtml_show_comments    (GtkIMHtml        *imhtml,
3549 					gboolean          show)
3550 {
3551 #if FALSE && GTK_CHECK_VERSION(2,10,10)
3552 	GtkTextTag *tag;
3553 	tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), "comment");
3554 	if (tag)
3555 		g_object_set(G_OBJECT(tag), "invisible", !show, NULL);
3556 #endif
3557 	imhtml->show_comments = show;
3558 }
3559 
3560 const char *
gtk_imhtml_get_protocol_name(GtkIMHtml * imhtml)3561 gtk_imhtml_get_protocol_name(GtkIMHtml *imhtml) {
3562 	return imhtml->protocol_name;
3563 }
3564 
3565 void
gtk_imhtml_set_protocol_name(GtkIMHtml * imhtml,const gchar * protocol_name)3566 gtk_imhtml_set_protocol_name(GtkIMHtml *imhtml, const gchar *protocol_name) {
3567 	g_free(imhtml->protocol_name);
3568 	imhtml->protocol_name = g_strdup(protocol_name);
3569 }
3570 
3571 void
gtk_imhtml_delete(GtkIMHtml * imhtml,GtkTextIter * start,GtkTextIter * end)3572 gtk_imhtml_delete(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end) {
3573 	GtkTextIter i_s, i_e;
3574 	GObject *object = g_object_ref(G_OBJECT(imhtml));
3575 
3576 	if (start == NULL) {
3577 		gtk_text_buffer_get_start_iter(imhtml->text_buffer, &i_s);
3578 		start = &i_s;
3579 	}
3580 
3581 	if (end == NULL) {
3582 		gtk_text_buffer_get_end_iter(imhtml->text_buffer, &i_e);
3583 		end = &i_e;
3584 	}
3585 
3586 	/* This will cause the signal handler to get called which will call
3587 	 * delete_cb and clean up everything else. */
3588 	gtk_text_buffer_delete(imhtml->text_buffer, start, end);
3589 
3590 	/* This is wrong, but i'm not trying to total what we actually removed. */
3591 	g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(0));
3592 
3593 	g_object_unref(object);
3594 }
3595 
gtk_imhtml_page_up(GtkIMHtml * imhtml)3596 void gtk_imhtml_page_up (GtkIMHtml *imhtml)
3597 {
3598 	GdkRectangle rect;
3599 	GtkTextIter iter;
3600 
3601 	gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect);
3602 	gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml), &iter, rect.x,
3603 									   rect.y - rect.height);
3604 	gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml), &iter, 0, TRUE, 0, 0);
3605 
3606 }
gtk_imhtml_page_down(GtkIMHtml * imhtml)3607 void gtk_imhtml_page_down (GtkIMHtml *imhtml)
3608 {
3609 	GdkRectangle rect;
3610 	GtkTextIter iter;
3611 
3612 	gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect);
3613 	gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml), &iter, rect.x,
3614 									   rect.y + rect.height);
3615 	gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml), &iter, 0, TRUE, 0, 0);
3616 }
3617 
3618 /* GtkIMHtmlScalable, gtk_imhtml_image, gtk_imhtml_hr */
gtk_imhtml_image_new(GdkPixbuf * img,const gchar * filename,int id)3619 GtkIMHtmlScalable *gtk_imhtml_image_new(GdkPixbuf *img, const gchar *filename, int id)
3620 {
3621 	GtkIMHtmlImage *im_image = g_malloc(sizeof(GtkIMHtmlImage));
3622 
3623 	GTK_IMHTML_SCALABLE(im_image)->scale = gtk_imhtml_image_scale;
3624 	GTK_IMHTML_SCALABLE(im_image)->add_to = gtk_imhtml_image_add_to;
3625 	GTK_IMHTML_SCALABLE(im_image)->free = gtk_imhtml_image_free;
3626 
3627 	im_image->pixbuf = img;
3628 	im_image->image = GTK_IMAGE(gtk_image_new_from_pixbuf(im_image->pixbuf));
3629 	im_image->width = gdk_pixbuf_get_width(img);
3630 	im_image->height = gdk_pixbuf_get_height(img);
3631 	im_image->mark = NULL;
3632 	im_image->filename = g_strdup(filename);
3633 	im_image->id = id;
3634 	im_image->filesel = NULL;
3635 
3636 	g_object_ref(img);
3637 	return GTK_IMHTML_SCALABLE(im_image);
3638 }
3639 
3640 static gboolean
animate_image_cb(gpointer data)3641 animate_image_cb(gpointer data)
3642 {
3643 	GtkIMHtmlImage *im_image;
3644 	int width, height;
3645 	int delay;
3646 
3647 	im_image = data;
3648 
3649 	/* Update the pointer to this GdkPixbuf frame of the animation */
3650 	if (gdk_pixbuf_animation_iter_advance(GTK_IMHTML_ANIMATION(im_image)->iter, NULL)) {
3651 		GdkPixbuf *pb = gdk_pixbuf_animation_iter_get_pixbuf(GTK_IMHTML_ANIMATION(im_image)->iter);
3652 		g_object_unref(G_OBJECT(im_image->pixbuf));
3653 		im_image->pixbuf = gdk_pixbuf_copy(pb);
3654 
3655 		/* Update the displayed GtkImage */
3656 		width = gdk_pixbuf_get_width(gtk_image_get_pixbuf(im_image->image));
3657 		height = gdk_pixbuf_get_height(gtk_image_get_pixbuf(im_image->image));
3658 		if (width > 0 && height > 0)
3659 		{
3660 			/* Need to scale the new frame to the same size as the old frame */
3661 			GdkPixbuf *tmp;
3662 			tmp = gdk_pixbuf_scale_simple(im_image->pixbuf, width, height, GDK_INTERP_BILINEAR);
3663 			gtk_image_set_from_pixbuf(im_image->image, tmp);
3664 			g_object_unref(G_OBJECT(tmp));
3665 		} else {
3666 			/* Display at full-size */
3667 			gtk_image_set_from_pixbuf(im_image->image, im_image->pixbuf);
3668 		}
3669 	}
3670 
3671 	delay = MIN(gdk_pixbuf_animation_iter_get_delay_time(GTK_IMHTML_ANIMATION(im_image)->iter), 100);
3672 	GTK_IMHTML_ANIMATION(im_image)->timer = g_timeout_add(delay, animate_image_cb, im_image);
3673 
3674 	return FALSE;
3675 }
3676 
gtk_imhtml_animation_new(GdkPixbufAnimation * anim,const gchar * filename,int id)3677 GtkIMHtmlScalable *gtk_imhtml_animation_new(GdkPixbufAnimation *anim, const gchar *filename, int id)
3678 {
3679 	GtkIMHtmlImage *im_image = (GtkIMHtmlImage *) g_new0(GtkIMHtmlAnimation, 1);
3680 
3681 	GTK_IMHTML_SCALABLE(im_image)->scale = gtk_imhtml_image_scale;
3682 	GTK_IMHTML_SCALABLE(im_image)->add_to = gtk_imhtml_image_add_to;
3683 	GTK_IMHTML_SCALABLE(im_image)->free = gtk_imhtml_animation_free;
3684 
3685 	GTK_IMHTML_ANIMATION(im_image)->anim = anim;
3686 	if (gdk_pixbuf_animation_is_static_image(anim)) {
3687 		im_image->pixbuf = gdk_pixbuf_animation_get_static_image(anim);
3688 		g_object_ref(im_image->pixbuf);
3689 	} else {
3690 		int delay;
3691 		GdkPixbuf *pb;
3692 		GTK_IMHTML_ANIMATION(im_image)->iter = gdk_pixbuf_animation_get_iter(anim, NULL);
3693 		pb = gdk_pixbuf_animation_iter_get_pixbuf(GTK_IMHTML_ANIMATION(im_image)->iter);
3694 		im_image->pixbuf = gdk_pixbuf_copy(pb);
3695 		delay = MIN(gdk_pixbuf_animation_iter_get_delay_time(GTK_IMHTML_ANIMATION(im_image)->iter), 100);
3696 		GTK_IMHTML_ANIMATION(im_image)->timer = g_timeout_add(delay, animate_image_cb, im_image);
3697 	}
3698 	im_image->image = GTK_IMAGE(gtk_image_new_from_pixbuf(im_image->pixbuf));
3699 	im_image->width = gdk_pixbuf_animation_get_width(anim);
3700 	im_image->height = gdk_pixbuf_animation_get_height(anim);
3701 	im_image->filename = g_strdup(filename);
3702 	im_image->id = id;
3703 
3704 	g_object_ref(anim);
3705 
3706 	return GTK_IMHTML_SCALABLE(im_image);
3707 }
3708 
gtk_imhtml_image_scale(GtkIMHtmlScalable * scale,int width,int height)3709 void gtk_imhtml_image_scale(GtkIMHtmlScalable *scale, int width, int height)
3710 {
3711 	GtkIMHtmlImage *im_image = (GtkIMHtmlImage *)scale;
3712 
3713 	if (im_image->width > width || im_image->height > height) {
3714 		double ratio_w, ratio_h, ratio;
3715 		int new_h, new_w;
3716 		GdkPixbuf *new_image = NULL;
3717 
3718 		ratio_w = ((double)width - 2) / im_image->width;
3719 		ratio_h = ((double)height - 2) / im_image->height;
3720 
3721 		ratio = (ratio_w < ratio_h) ? ratio_w : ratio_h;
3722 
3723 		new_w = (int)(im_image->width * ratio);
3724 		new_h = (int)(im_image->height * ratio);
3725 
3726 		new_image = gdk_pixbuf_scale_simple(im_image->pixbuf, new_w, new_h, GDK_INTERP_BILINEAR);
3727 		gtk_image_set_from_pixbuf(im_image->image, new_image);
3728 		g_object_unref(G_OBJECT(new_image));
3729 	} else if (gdk_pixbuf_get_width(gtk_image_get_pixbuf(im_image->image)) != im_image->width) {
3730 		/* Enough space to show the full-size of the image. */
3731 		GdkPixbuf *new_image;
3732 
3733 		new_image = gdk_pixbuf_scale_simple(im_image->pixbuf, im_image->width, im_image->height, GDK_INTERP_BILINEAR);
3734 		gtk_image_set_from_pixbuf(im_image->image, new_image);
3735 		g_object_unref(G_OBJECT(new_image));
3736 	}
3737 }
3738 
3739 static void
image_save_yes_cb(GtkIMHtmlImageSave * save,const char * filename)3740 image_save_yes_cb(GtkIMHtmlImageSave *save, const char *filename)
3741 {
3742 	GError *error = NULL;
3743 	GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image;
3744 
3745 	gtk_widget_destroy(image->filesel);
3746 	image->filesel = NULL;
3747 
3748 	if (save->data && save->datasize) {
3749 		g_file_set_contents(filename, save->data, save->datasize, &error);
3750 	} else {
3751 		gchar *type = NULL;
3752 		GSList *formats = gdk_pixbuf_get_formats();
3753 		char *newfilename;
3754 
3755 		while (formats) {
3756 			GdkPixbufFormat *format = formats->data;
3757 			gchar **extensions = gdk_pixbuf_format_get_extensions(format);
3758 			gpointer p = extensions;
3759 
3760 			while(gdk_pixbuf_format_is_writable(format) && extensions && extensions[0]){
3761 				gchar *fmt_ext = extensions[0];
3762 				const gchar* file_ext = filename + strlen(filename) - strlen(fmt_ext);
3763 
3764 				if(!g_ascii_strcasecmp(fmt_ext, file_ext)){
3765 					type = gdk_pixbuf_format_get_name(format);
3766 					break;
3767 				}
3768 
3769 				extensions++;
3770 			}
3771 
3772 			g_strfreev(p);
3773 
3774 			if (type)
3775 				break;
3776 
3777 			formats = formats->next;
3778 		}
3779 
3780 		g_slist_free(formats);
3781 
3782 		/* If I can't find a valid type, I will just tell the user about it and then assume
3783 		   it's a png */
3784 		if (!type){
3785 			char *basename, *tmp;
3786 			char *dirname;
3787 			GtkWidget *dialog = gtk_message_dialog_new_with_markup(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
3788 							_("<span size='larger' weight='bold'>Unrecognized file type</span>\n\nDefaulting to PNG."));
3789 
3790 			g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog);
3791 			gtk_widget_show(dialog);
3792 
3793 			type = g_strdup("png");
3794 			dirname = g_path_get_dirname(filename);
3795 			basename = g_path_get_basename(filename);
3796 			tmp = strrchr(basename, '.');
3797 			if (tmp != NULL)
3798 				tmp[0] = '\0';
3799 			newfilename = g_strdup_printf("%s" G_DIR_SEPARATOR_S  "%s.png", dirname, basename);
3800 			g_free(dirname);
3801 			g_free(basename);
3802 		} else {
3803 			/*
3804 			 * We're able to save the file in it's original format, so we
3805 			 * can use the original file name.
3806 			 */
3807 			newfilename = g_strdup(filename);
3808 		}
3809 
3810 		gdk_pixbuf_save(image->pixbuf, newfilename, type, &error, NULL);
3811 
3812 		g_free(newfilename);
3813 		g_free(type);
3814 	}
3815 
3816 	if (error){
3817 		GtkWidget *dialog = gtk_message_dialog_new_with_markup(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
3818 				_("<span size='larger' weight='bold'>Error saving image</span>\n\n%s"), error->message);
3819 		g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog);
3820 		gtk_widget_show(dialog);
3821 		g_error_free(error);
3822 	}
3823 }
3824 
3825 static void
image_save_check_if_exists_cb(GtkWidget * widget,gint response,GtkIMHtmlImageSave * save)3826 image_save_check_if_exists_cb(GtkWidget *widget, gint response, GtkIMHtmlImageSave *save)
3827 {
3828 	gchar *filename;
3829 	GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image;
3830 
3831 	if (response != GTK_RESPONSE_ACCEPT) {
3832 		gtk_widget_destroy(widget);
3833 		image->filesel = NULL;
3834 		return;
3835 	}
3836 
3837 	filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
3838 
3839 	/*
3840 	 * XXX - We should probably prompt the user to determine if they really
3841 	 * want to overwrite the file or not.  However, I don't feel like doing
3842 	 * that, so we're just always going to overwrite if the file exists.
3843 	 */
3844 	/*
3845 	if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
3846 	} else
3847 		image_save_yes_cb(image, filename);
3848 	*/
3849 
3850 	image_save_yes_cb(save, filename);
3851 
3852 	g_free(filename);
3853 }
3854 
3855 static void
gtk_imhtml_image_save(GtkWidget * w,GtkIMHtmlImageSave * save)3856 gtk_imhtml_image_save(GtkWidget *w, GtkIMHtmlImageSave *save)
3857 {
3858 	GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image;
3859 
3860 	if (image->filesel != NULL) {
3861 		gtk_window_present(GTK_WINDOW(image->filesel));
3862 		return;
3863 	}
3864 
3865 	image->filesel = gtk_file_chooser_dialog_new(_("Save Image"),
3866 						NULL,
3867 						GTK_FILE_CHOOSER_ACTION_SAVE,
3868 						GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
3869 						GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
3870 						NULL);
3871 	gtk_dialog_set_default_response(GTK_DIALOG(image->filesel), GTK_RESPONSE_ACCEPT);
3872 	if (image->filename != NULL)
3873 		gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(image->filesel), image->filename);
3874 	g_signal_connect(G_OBJECT(GTK_FILE_CHOOSER(image->filesel)), "response",
3875 					 G_CALLBACK(image_save_check_if_exists_cb), save);
3876 
3877 	gtk_widget_show(image->filesel);
3878 }
3879 
3880 static void
gtk_imhtml_custom_smiley_save(GtkWidget * w,GtkIMHtmlImageSave * save)3881 gtk_imhtml_custom_smiley_save(GtkWidget *w, GtkIMHtmlImageSave *save)
3882 {
3883 	GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image;
3884 
3885 	/* Create an add dialog */
3886 	PidginSmiley *editor = pidgin_smiley_edit(NULL, NULL);
3887 	pidgin_smiley_editor_set_shortcut(editor, image->filename);
3888 	pidgin_smiley_editor_set_image(editor, image->pixbuf);
3889 	pidgin_smiley_editor_set_data(editor, save->data, save->datasize);
3890 }
3891 
3892 /*
3893  * So, um, AIM Direct IM lets you send any file, not just images.  You can
3894  * just insert a sound or a file or whatever in a conversation.  It's
3895  * basically like file transfer, except there is an icon to open the file
3896  * embedded in the conversation.  Someone should make the Purple core handle
3897  * all of that.
3898  */
gtk_imhtml_image_clicked(GtkWidget * w,GdkEvent * event,GtkIMHtmlImageSave * save)3899 static gboolean gtk_imhtml_image_clicked(GtkWidget *w, GdkEvent *event, GtkIMHtmlImageSave *save)
3900 {
3901 	GdkEventButton *event_button = (GdkEventButton *) event;
3902 	GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image;
3903 
3904 	if (event->type == GDK_BUTTON_RELEASE) {
3905 		if(event_button->button == 3) {
3906 			GtkWidget *img, *item, *menu;
3907 			menu = gtk_menu_new();
3908 
3909 			/* buttons and such */
3910 			img = gtk_image_new_from_stock(GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU);
3911 			item = gtk_image_menu_item_new_with_mnemonic(_("_Save Image..."));
3912 			gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3913 			g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_image_save), save);
3914 			gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3915 
3916 			/* Add menu item for adding custom smiley to local smileys */
3917 			/* we only add the menu if the image is of "custom smiley size"
3918 			  <= 96x96 pixels */
3919 			if (image->width <= 96 && image->height <= 96) {
3920 				img = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
3921 				item = gtk_image_menu_item_new_with_mnemonic(_("_Add Custom Smiley..."));
3922 				gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3923 				g_signal_connect(G_OBJECT(item), "activate",
3924 								 G_CALLBACK(gtk_imhtml_custom_smiley_save), save);
3925 				gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3926 			}
3927 
3928 			gtk_widget_show_all(menu);
3929 			gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
3930 							event_button->button, event_button->time);
3931 
3932 			return TRUE;
3933 		}
3934 	}
3935 	if(event->type == GDK_BUTTON_PRESS && event_button->button == 3)
3936 		return TRUE; /* Clicking the right mouse button on a link shouldn't
3937 						be caught by the regular GtkTextView menu */
3938 	else
3939 		return FALSE; /* Let clicks go through if we didn't catch anything */
3940 
3941 }
3942 
gtk_imhtml_smiley_clicked(GtkWidget * w,GdkEvent * event,GtkIMHtmlSmiley * smiley)3943 static gboolean gtk_imhtml_smiley_clicked(GtkWidget *w, GdkEvent *event, GtkIMHtmlSmiley *smiley)
3944 {
3945 	GdkPixbufAnimation *anim = NULL;
3946 	GtkIMHtmlImageSave *save = NULL;
3947 	gboolean ret;
3948 
3949 	if (event->type != GDK_BUTTON_RELEASE || ((GdkEventButton*)event)->button != 3)
3950 		return FALSE;
3951 
3952 	anim = gtk_smiley_get_image(smiley);
3953 	if (!anim)
3954 		return FALSE;
3955 
3956 	save = g_new0(GtkIMHtmlImageSave, 1);
3957 	save->image = (GtkIMHtmlScalable *)gtk_imhtml_animation_new(anim, smiley->smile, 0);
3958 	save->data = smiley->data;        /* Do not need to memdup here, since the smiley is not
3959 	                                     destroyed before this GtkIMHtmlImageSave */
3960 	save->datasize = smiley->datasize;
3961 	ret = gtk_imhtml_image_clicked(w, event, save);
3962 	g_object_set_data_full(G_OBJECT(w), "image-data", save->image, (GDestroyNotify)gtk_imhtml_animation_free);
3963 	g_object_set_data_full(G_OBJECT(w), "image-save-data", save, (GDestroyNotify)g_free);
3964 	return ret;
3965 }
3966 
gtk_imhtml_image_free(GtkIMHtmlScalable * scale)3967 void gtk_imhtml_image_free(GtkIMHtmlScalable *scale)
3968 {
3969 	GtkIMHtmlImage *image = (GtkIMHtmlImage *)scale;
3970 
3971 	g_object_unref(image->pixbuf);
3972 	g_free(image->filename);
3973 	if (image->filesel)
3974 		gtk_widget_destroy(image->filesel);
3975 	g_free(scale);
3976 }
3977 
gtk_imhtml_animation_free(GtkIMHtmlScalable * scale)3978 void gtk_imhtml_animation_free(GtkIMHtmlScalable *scale)
3979 {
3980 	GtkIMHtmlAnimation *animation = (GtkIMHtmlAnimation *)scale;
3981 
3982 	if (animation->timer > 0)
3983 		g_source_remove(animation->timer);
3984 	if (animation->iter != NULL)
3985 		g_object_unref(animation->iter);
3986 	g_object_unref(animation->anim);
3987 
3988 	gtk_imhtml_image_free(scale);
3989 }
3990 
gtk_imhtml_image_add_to(GtkIMHtmlScalable * scale,GtkIMHtml * imhtml,GtkTextIter * iter)3991 void gtk_imhtml_image_add_to(GtkIMHtmlScalable *scale, GtkIMHtml *imhtml, GtkTextIter *iter)
3992 {
3993 	GtkIMHtmlImage *image = (GtkIMHtmlImage *)scale;
3994 	GtkWidget *box = gtk_event_box_new();
3995 	char *tag;
3996 	GtkTextChildAnchor *anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter);
3997 	GtkIMHtmlImageSave *save;
3998 
3999 	gtk_container_add(GTK_CONTAINER(box), GTK_WIDGET(image->image));
4000 
4001 	if(!gtk_check_version(2, 4, 0))
4002 		g_object_set(G_OBJECT(box), "visible-window", FALSE, NULL);
4003 
4004 	gtk_widget_show(GTK_WIDGET(image->image));
4005 	gtk_widget_show(box);
4006 
4007 	tag = g_strdup_printf("<IMG ID=\"%d\">", image->id);
4008 	g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", tag, g_free);
4009 	g_object_set_data(G_OBJECT(anchor), "gtkimhtml_plaintext", "[Image]");
4010 
4011 	gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), box, anchor);
4012 
4013 	save = g_new0(GtkIMHtmlImageSave, 1);
4014 	save->image = scale;
4015 	g_signal_connect(G_OBJECT(box), "event", G_CALLBACK(gtk_imhtml_image_clicked), save);
4016 	g_object_set_data_full(G_OBJECT(box), "image-save-data", save, (GDestroyNotify)g_free);
4017 }
4018 
gtk_imhtml_hr_new()4019 GtkIMHtmlScalable *gtk_imhtml_hr_new()
4020 {
4021 	GtkIMHtmlHr *hr = g_malloc(sizeof(GtkIMHtmlHr));
4022 
4023 	GTK_IMHTML_SCALABLE(hr)->scale = gtk_imhtml_hr_scale;
4024 	GTK_IMHTML_SCALABLE(hr)->add_to = gtk_imhtml_hr_add_to;
4025 	GTK_IMHTML_SCALABLE(hr)->free = gtk_imhtml_hr_free;
4026 
4027 	hr->sep = gtk_hseparator_new();
4028 	gtk_widget_set_size_request(hr->sep, 5000, 2);
4029 	gtk_widget_show(hr->sep);
4030 
4031 	return GTK_IMHTML_SCALABLE(hr);
4032 }
4033 
gtk_imhtml_hr_scale(GtkIMHtmlScalable * scale,int width,int height)4034 void gtk_imhtml_hr_scale(GtkIMHtmlScalable *scale, int width, int height)
4035 {
4036 	gtk_widget_set_size_request(((GtkIMHtmlHr *)scale)->sep, width - 2, 2);
4037 }
4038 
gtk_imhtml_hr_add_to(GtkIMHtmlScalable * scale,GtkIMHtml * imhtml,GtkTextIter * iter)4039 void gtk_imhtml_hr_add_to(GtkIMHtmlScalable *scale, GtkIMHtml *imhtml, GtkTextIter *iter)
4040 {
4041 	GtkIMHtmlHr *hr = (GtkIMHtmlHr *)scale;
4042 	GtkTextChildAnchor *anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter);
4043 	g_object_set_data(G_OBJECT(anchor), "gtkimhtml_htmltext", "<hr>");
4044 	g_object_set_data(G_OBJECT(anchor), "gtkimhtml_plaintext", "\n---\n");
4045 	gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), hr->sep, anchor);
4046 }
4047 
gtk_imhtml_hr_free(GtkIMHtmlScalable * scale)4048 void gtk_imhtml_hr_free(GtkIMHtmlScalable *scale)
4049 {
4050 	g_free(scale);
4051 }
4052 
gtk_imhtml_search_find(GtkIMHtml * imhtml,const gchar * text)4053 gboolean gtk_imhtml_search_find(GtkIMHtml *imhtml, const gchar *text)
4054 {
4055 	GtkTextIter iter, start, end;
4056 	gboolean new_search = TRUE;
4057 	GtkTextMark *start_mark;
4058 
4059 	g_return_val_if_fail(imhtml != NULL, FALSE);
4060 	g_return_val_if_fail(text != NULL, FALSE);
4061 
4062 	start_mark = gtk_text_buffer_get_mark(imhtml->text_buffer, "search");
4063 
4064 	if (start_mark && imhtml->search_string && purple_strequal(text, imhtml->search_string))
4065 		new_search = FALSE;
4066 
4067 	if (new_search) {
4068 		gtk_imhtml_search_clear(imhtml);
4069 		g_free(imhtml->search_string);
4070 		imhtml->search_string = g_strdup(text);
4071 		gtk_text_buffer_get_end_iter(imhtml->text_buffer, &iter);
4072 	} else {
4073 		gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter,
4074 						 start_mark);
4075 	}
4076 
4077 	if (gtk_source_iter_backward_search(&iter, imhtml->search_string,
4078 	                                   GTK_SOURCE_SEARCH_VISIBLE_ONLY | GTK_SOURCE_SEARCH_CASE_INSENSITIVE,
4079 	                                   &start, &end, NULL))
4080 	{
4081 		gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml), &start, 0, TRUE, 0, 0);
4082 		gtk_text_buffer_create_mark(imhtml->text_buffer, "search", &start, FALSE);
4083 		if (new_search)
4084 		{
4085 			gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "search", &iter, &end);
4086 			do
4087 				gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "search", &start, &end);
4088 			while (gtk_source_iter_backward_search(&start, imhtml->search_string,
4089 							      GTK_SOURCE_SEARCH_VISIBLE_ONLY |
4090 							      GTK_SOURCE_SEARCH_CASE_INSENSITIVE,
4091 							      &start, &end, NULL));
4092 		}
4093 		return TRUE;
4094 	}
4095 	else if (!new_search)
4096 	{
4097 		/* We hit the end, so start at the beginning again. */
4098 		gtk_text_buffer_get_end_iter(imhtml->text_buffer, &iter);
4099 
4100 		if (gtk_source_iter_backward_search(&iter, imhtml->search_string,
4101 		                                   GTK_SOURCE_SEARCH_VISIBLE_ONLY | GTK_SOURCE_SEARCH_CASE_INSENSITIVE,
4102 		                                   &start, &end, NULL))
4103 		{
4104 			gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml), &start, 0, TRUE, 0, 0);
4105 			gtk_text_buffer_create_mark(imhtml->text_buffer, "search", &start, FALSE);
4106 
4107 			return TRUE;
4108 		}
4109 
4110 	}
4111 
4112 	return FALSE;
4113 }
4114 
gtk_imhtml_search_clear(GtkIMHtml * imhtml)4115 void gtk_imhtml_search_clear(GtkIMHtml *imhtml)
4116 {
4117 	GtkTextIter start, end;
4118 
4119 	g_return_if_fail(imhtml != NULL);
4120 
4121 	gtk_text_buffer_get_start_iter(imhtml->text_buffer, &start);
4122 	gtk_text_buffer_get_end_iter(imhtml->text_buffer, &end);
4123 
4124 	gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "search", &start, &end);
4125 	g_free(imhtml->search_string);
4126 	imhtml->search_string = NULL;
4127 }
4128 
find_font_forecolor_tag(GtkIMHtml * imhtml,gchar * color)4129 static GtkTextTag *find_font_forecolor_tag(GtkIMHtml *imhtml, gchar *color)
4130 {
4131 	gchar str[18];
4132 	GtkTextTag *tag;
4133 
4134 	g_snprintf(str, sizeof(str), "FORECOLOR %s", color);
4135 
4136 	tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str);
4137 	if (!tag) {
4138 		GdkColor gcolor;
4139 		if (!gdk_color_parse(color, &gcolor)) {
4140 			gchar tmp[8];
4141 			tmp[0] = '#';
4142 			strncpy(&tmp[1], color, 7);
4143 			tmp[7] = '\0';
4144 			if (!gdk_color_parse(tmp, &gcolor))
4145 				gdk_color_parse("black", &gcolor);
4146 		}
4147 		tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "foreground-gdk", &gcolor, NULL);
4148 	}
4149 
4150 	return tag;
4151 }
4152 
find_font_backcolor_tag(GtkIMHtml * imhtml,gchar * color)4153 static GtkTextTag *find_font_backcolor_tag(GtkIMHtml *imhtml, gchar *color)
4154 {
4155 	gchar str[18];
4156 	GtkTextTag *tag;
4157 
4158 	g_snprintf(str, sizeof(str), "BACKCOLOR %s", color);
4159 
4160 	tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str);
4161 	if (!tag) {
4162 		GdkColor gcolor;
4163 		if (!gdk_color_parse(color, &gcolor)) {
4164 			gchar tmp[8];
4165 			tmp[0] = '#';
4166 			strncpy(&tmp[1], color, 7);
4167 			tmp[7] = '\0';
4168 			if (!gdk_color_parse(tmp, &gcolor))
4169 				gdk_color_parse("white", &gcolor);
4170 		}
4171 		tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "background-gdk", &gcolor, NULL);
4172 	}
4173 
4174 	return tag;
4175 }
4176 
find_font_background_tag(GtkIMHtml * imhtml,gchar * color)4177 static GtkTextTag *find_font_background_tag(GtkIMHtml *imhtml, gchar *color)
4178 {
4179 	gchar str[19];
4180 	GtkTextTag *tag;
4181 
4182 	g_snprintf(str, sizeof(str), "BACKGROUND %s", color);
4183 
4184 	tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str);
4185 	if (!tag)
4186 		tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, NULL);
4187 
4188 	return tag;
4189 }
4190 
find_font_face_tag(GtkIMHtml * imhtml,gchar * face)4191 static GtkTextTag *find_font_face_tag(GtkIMHtml *imhtml, gchar *face)
4192 {
4193 	gchar str[256];
4194 	GtkTextTag *tag;
4195 
4196 	g_snprintf(str, sizeof(str), "FONT FACE %s", face);
4197 	str[255] = '\0';
4198 
4199 	tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str);
4200 	if (!tag)
4201 		tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "family", face, NULL);
4202 
4203 	return tag;
4204 }
4205 
find_font_size_tag(GtkIMHtml * imhtml,int size)4206 static GtkTextTag *find_font_size_tag(GtkIMHtml *imhtml, int size)
4207 {
4208 	gchar str[24];
4209 	GtkTextTag *tag;
4210 
4211 	g_snprintf(str, sizeof(str), "FONT SIZE %d", size);
4212 	str[23] = '\0';
4213 
4214 	tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str);
4215 	if (!tag) {
4216 		/* For reasons I don't understand, setting "scale" here scaled
4217 		 * based on some default size other than my theme's default
4218 		 * size. Our size 4 was actually smaller than our size 3 for
4219 		 * me. So this works around that oddity.
4220 		 */
4221 		GtkTextAttributes *attr = gtk_text_view_get_default_attributes(GTK_TEXT_VIEW(imhtml));
4222 		tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "size",
4223 		                                 (gint) (pango_font_description_get_size(attr->font) *
4224 		                                 (double) POINT_SIZE(size)), NULL);
4225 		gtk_text_attributes_unref(attr);
4226 	}
4227 
4228 	return tag;
4229 }
4230 
remove_tag_by_prefix(GtkIMHtml * imhtml,const GtkTextIter * i,const GtkTextIter * e,const char * prefix,guint len,gboolean homo)4231 static void remove_tag_by_prefix(GtkIMHtml *imhtml, const GtkTextIter *i, const GtkTextIter *e,
4232                                  const char *prefix, guint len, gboolean homo)
4233 {
4234 	GSList *tags, *l;
4235 	GtkTextIter iter;
4236 
4237 	tags = gtk_text_iter_get_tags(i);
4238 
4239 	for (l = tags; l; l = l->next) {
4240 		GtkTextTag *tag = l->data;
4241 
4242 		if (tag->name && !strncmp(tag->name, prefix, len))
4243 			gtk_text_buffer_remove_tag(imhtml->text_buffer, tag, i, e);
4244 	}
4245 
4246 	g_slist_free(tags);
4247 
4248 	if (homo)
4249 		return;
4250 
4251 	iter = *i;
4252 
4253 	while (gtk_text_iter_forward_char(&iter) && !gtk_text_iter_equal(&iter, e)) {
4254 		if (gtk_text_iter_begins_tag(&iter, NULL)) {
4255 			tags = gtk_text_iter_get_toggled_tags(&iter, TRUE);
4256 
4257 			for (l = tags; l; l = l->next) {
4258 				GtkTextTag *tag = l->data;
4259 
4260 				if (tag->name && !strncmp(tag->name, prefix, len))
4261 					gtk_text_buffer_remove_tag(imhtml->text_buffer, tag, &iter, e);
4262 			}
4263 
4264 			g_slist_free(tags);
4265 		}
4266 	}
4267 }
4268 
remove_font_size(GtkIMHtml * imhtml,GtkTextIter * i,GtkTextIter * e,gboolean homo)4269 static void remove_font_size(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4270 {
4271 	remove_tag_by_prefix(imhtml, i, e, "FONT SIZE ", 10, homo);
4272 }
4273 
remove_font_face(GtkIMHtml * imhtml,GtkTextIter * i,GtkTextIter * e,gboolean homo)4274 static void remove_font_face(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4275 {
4276 	remove_tag_by_prefix(imhtml, i, e, "FONT FACE ", 10, homo);
4277 }
4278 
remove_font_forecolor(GtkIMHtml * imhtml,GtkTextIter * i,GtkTextIter * e,gboolean homo)4279 static void remove_font_forecolor(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4280 {
4281 	remove_tag_by_prefix(imhtml, i, e, "FORECOLOR ", 10, homo);
4282 }
4283 
remove_font_backcolor(GtkIMHtml * imhtml,GtkTextIter * i,GtkTextIter * e,gboolean homo)4284 static void remove_font_backcolor(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4285 {
4286 	remove_tag_by_prefix(imhtml, i, e, "BACKCOLOR ", 10, homo);
4287 }
4288 
remove_font_background(GtkIMHtml * imhtml,GtkTextIter * i,GtkTextIter * e,gboolean homo)4289 static void remove_font_background(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4290 {
4291 	remove_tag_by_prefix(imhtml, i, e, "BACKGROUND ", 10, homo);
4292 }
4293 
remove_font_link(GtkIMHtml * imhtml,GtkTextIter * i,GtkTextIter * e,gboolean homo)4294 static void remove_font_link(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4295 {
4296 	remove_tag_by_prefix(imhtml, i, e, "LINK ", 5, homo);
4297 }
4298 
4299 static void
imhtml_clear_formatting(GtkIMHtml * imhtml)4300 imhtml_clear_formatting(GtkIMHtml *imhtml)
4301 {
4302 	GtkTextIter start, end;
4303 
4304 	if (!imhtml->editable)
4305 		return;
4306 
4307 	if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4308 		return;
4309 
4310 	gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "BOLD", &start, &end);
4311 	gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "ITALICS", &start, &end);
4312 	gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "UNDERLINE", &start, &end);
4313 	gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "STRIKE", &start, &end);
4314 	remove_font_size(imhtml, &start, &end, FALSE);
4315 	remove_font_face(imhtml, &start, &end, FALSE);
4316 	remove_font_forecolor(imhtml, &start, &end, FALSE);
4317 	remove_font_backcolor(imhtml, &start, &end, FALSE);
4318 	remove_font_background(imhtml, &start, &end, FALSE);
4319 	remove_font_link(imhtml, &start, &end, FALSE);
4320 
4321 	imhtml->edit.bold = 0;
4322 	imhtml->edit.italic = 0;
4323 	imhtml->edit.underline = 0;
4324 	imhtml->edit.strike = 0;
4325 	imhtml->edit.fontsize = 0;
4326 
4327 	g_free(imhtml->edit.fontface);
4328 	imhtml->edit.fontface = NULL;
4329 
4330 	g_free(imhtml->edit.forecolor);
4331 	imhtml->edit.forecolor = NULL;
4332 
4333 	g_free(imhtml->edit.backcolor);
4334 	imhtml->edit.backcolor = NULL;
4335 
4336 	g_free(imhtml->edit.background);
4337 	imhtml->edit.background = NULL;
4338 }
4339 
4340 /* Editable stuff */
preinsert_cb(GtkTextBuffer * buffer,GtkTextIter * iter,gchar * text,gint len,GtkIMHtml * imhtml)4341 static void preinsert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml)
4342 {
4343 	imhtml->insert_offset = gtk_text_iter_get_offset(iter);
4344 }
4345 
insert_ca_cb(GtkTextBuffer * buffer,GtkTextIter * arg1,GtkTextChildAnchor * arg2,gpointer user_data)4346 static void insert_ca_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextChildAnchor *arg2, gpointer user_data)
4347 {
4348 	GtkTextIter start;
4349 
4350 	start = *arg1;
4351 	gtk_text_iter_backward_char(&start);
4352 
4353 	gtk_imhtml_apply_tags_on_insert(user_data, &start, arg1);
4354 }
4355 
insert_cb(GtkTextBuffer * buffer,GtkTextIter * end,gchar * text,gint len,GtkIMHtml * imhtml)4356 static void insert_cb(GtkTextBuffer *buffer, GtkTextIter *end, gchar *text, gint len, GtkIMHtml *imhtml)
4357 {
4358 	GtkTextIter start;
4359 
4360 	if (!len)
4361 		return;
4362 
4363 	start = *end;
4364 	gtk_text_iter_set_offset(&start, imhtml->insert_offset);
4365 
4366 	gtk_imhtml_apply_tags_on_insert(imhtml, &start, end);
4367 }
4368 
delete_cb(GtkTextBuffer * buffer,GtkTextIter * start,GtkTextIter * end,GtkIMHtml * imhtml)4369 static void delete_cb(GtkTextBuffer *buffer, GtkTextIter *start, GtkTextIter *end, GtkIMHtml *imhtml)
4370 {
4371 	GList *l;
4372 	GSList *tags, *sl;
4373 	GtkTextIter i;
4374 
4375 	/* clean up tags */
4376 	tags = gtk_text_iter_get_tags(start);
4377 	for (sl = tags; sl != NULL; sl = sl->next) {
4378 		GtkTextTag *tag = GTK_TEXT_TAG(sl->data);
4379 
4380 		if (tag &&							/* Remove the formatting only if */
4381 				gtk_text_iter_starts_word(start) &&				/* beginning of a word */
4382 				gtk_text_iter_begins_tag(start, tag) &&			/* the tag starts with the selection */
4383 				(!gtk_text_iter_has_tag(end, tag) ||			/* the tag ends within the selection */
4384 					gtk_text_iter_ends_tag(end, tag))) {
4385 			gtk_text_buffer_remove_tag(imhtml->text_buffer, tag, start, end);
4386 			if (tag->name &&
4387 					strncmp(tag->name, "LINK ", 5) == 0 && imhtml->edit.link) {
4388 				gtk_imhtml_toggle_link(imhtml, NULL);
4389 			}
4390 		}
4391 	}
4392 	g_slist_free(tags);
4393 
4394 	/* remove scalables */
4395 	l = imhtml->scalables;
4396 	while (l) {
4397 		GList *next = l->next;
4398 		struct scalable_data *sd = l->data;
4399 
4400 		gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &i, sd->mark);
4401 		if (gtk_text_iter_in_range(&i, start, end)) {
4402 			GtkIMHtmlScalable *scale = GTK_IMHTML_SCALABLE(sd->scalable);
4403 			scale->free(scale);
4404 			g_free(sd);
4405 			imhtml->scalables = g_list_delete_link(imhtml->scalables, l);
4406 		}
4407 		l = next;
4408 	}
4409 
4410 	/* remove images */
4411 	sl = imhtml->im_images;
4412 	while (sl) {
4413 		GSList *next = sl->next;
4414 		struct im_image_data *img_data = sl->data;
4415 		gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer,
4416 			&i, img_data->mark);
4417 		if (gtk_text_iter_in_range(&i, start, end)) {
4418 			if (imhtml->funcs->image_unref)
4419 				imhtml->funcs->image_unref(img_data->id);
4420 			imhtml->im_images = g_slist_delete_link(imhtml->im_images, sl);
4421 			g_free(img_data);
4422 		}
4423 		sl = next;
4424 	}
4425 }
4426 
gtk_imhtml_apply_tags_on_insert(GtkIMHtml * imhtml,GtkTextIter * start,GtkTextIter * end)4427 static void gtk_imhtml_apply_tags_on_insert(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end)
4428 {
4429 	if (imhtml->edit.bold)
4430 		gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "BOLD", start, end);
4431 	else
4432 		gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "BOLD", start, end);
4433 
4434 	if (imhtml->edit.italic)
4435 		gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "ITALICS", start, end);
4436 	else
4437 		gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "ITALICS", start, end);
4438 
4439 	if (imhtml->edit.underline)
4440 		gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "UNDERLINE", start, end);
4441 	else
4442 		gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "UNDERLINE", start, end);
4443 
4444 	if (imhtml->edit.strike)
4445 		gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "STRIKE", start, end);
4446 	else
4447 		gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "STRIKE", start, end);
4448 
4449 	if (imhtml->edit.forecolor) {
4450 		remove_font_forecolor(imhtml, start, end, TRUE);
4451 		gtk_text_buffer_apply_tag(imhtml->text_buffer,
4452 		                          find_font_forecolor_tag(imhtml, imhtml->edit.forecolor),
4453 		                          start, end);
4454 	}
4455 
4456 	if (imhtml->edit.backcolor) {
4457 		remove_font_backcolor(imhtml, start, end, TRUE);
4458 		gtk_text_buffer_apply_tag(imhtml->text_buffer,
4459 		                          find_font_backcolor_tag(imhtml, imhtml->edit.backcolor),
4460 		                          start, end);
4461 	}
4462 
4463 	if (imhtml->edit.background) {
4464 		remove_font_background(imhtml, start, end, TRUE);
4465 		gtk_text_buffer_apply_tag(imhtml->text_buffer,
4466 		                          find_font_background_tag(imhtml, imhtml->edit.background),
4467 		                          start, end);
4468 	}
4469 	if (imhtml->edit.fontface) {
4470 		remove_font_face(imhtml, start, end, TRUE);
4471 		gtk_text_buffer_apply_tag(imhtml->text_buffer,
4472 		                          find_font_face_tag(imhtml, imhtml->edit.fontface),
4473 		                          start, end);
4474 	}
4475 
4476 	if (imhtml->edit.fontsize) {
4477 		remove_font_size(imhtml, start, end, TRUE);
4478 		gtk_text_buffer_apply_tag(imhtml->text_buffer,
4479 		                          find_font_size_tag(imhtml, imhtml->edit.fontsize),
4480 		                          start, end);
4481 	}
4482 
4483 	if (imhtml->edit.link) {
4484 		remove_font_link(imhtml, start, end, TRUE);
4485 		gtk_text_buffer_apply_tag(imhtml->text_buffer,
4486 		                          imhtml->edit.link,
4487 		                          start, end);
4488 	}
4489 }
4490 
gtk_imhtml_set_editable(GtkIMHtml * imhtml,gboolean editable)4491 void gtk_imhtml_set_editable(GtkIMHtml *imhtml, gboolean editable)
4492 {
4493 	gtk_text_view_set_editable(GTK_TEXT_VIEW(imhtml), editable);
4494 	/*
4495 	 * We need a visible caret for accessibility, so mouseless
4496 	 * people can highlight stuff.
4497 	 */
4498 	/* gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(imhtml), editable); */
4499 	if (editable && !imhtml->editable) {
4500 		g_signal_connect_after(G_OBJECT(GTK_IMHTML(imhtml)->text_buffer), "mark-set",
4501 				G_CALLBACK(mark_set_cb), imhtml);
4502 		g_signal_connect(G_OBJECT(imhtml), "backspace", G_CALLBACK(smart_backspace_cb), NULL);
4503 	} else if (!editable && imhtml->editable) {
4504 		g_signal_handlers_disconnect_by_func(G_OBJECT(GTK_IMHTML(imhtml)->text_buffer),
4505 			mark_set_cb, imhtml);
4506 		g_signal_handlers_disconnect_by_func(G_OBJECT(imhtml),
4507 			smart_backspace_cb, NULL);
4508 	}
4509 
4510 	imhtml->editable = editable;
4511 	imhtml->format_functions = GTK_IMHTML_ALL;
4512 }
4513 
gtk_imhtml_set_whole_buffer_formatting_only(GtkIMHtml * imhtml,gboolean wbfo)4514 void gtk_imhtml_set_whole_buffer_formatting_only(GtkIMHtml *imhtml, gboolean wbfo)
4515 {
4516 	g_return_if_fail(imhtml != NULL);
4517 
4518 	imhtml->wbfo = wbfo;
4519 }
4520 
gtk_imhtml_set_format_functions(GtkIMHtml * imhtml,GtkIMHtmlButtons buttons)4521 void gtk_imhtml_set_format_functions(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons)
4522 {
4523 	GObject *object = g_object_ref(G_OBJECT(imhtml));
4524 	imhtml->format_functions = buttons;
4525 	g_signal_emit(object, signals[BUTTONS_UPDATE], 0, buttons);
4526 	g_object_unref(object);
4527 }
4528 
gtk_imhtml_get_format_functions(GtkIMHtml * imhtml)4529 GtkIMHtmlButtons gtk_imhtml_get_format_functions(GtkIMHtml *imhtml)
4530 {
4531 	return imhtml->format_functions;
4532 }
4533 
gtk_imhtml_get_current_format(GtkIMHtml * imhtml,gboolean * bold,gboolean * italic,gboolean * underline)4534 void gtk_imhtml_get_current_format(GtkIMHtml *imhtml, gboolean *bold,
4535 								   gboolean *italic, gboolean *underline)
4536 {
4537 	if (bold != NULL)
4538 		(*bold) = imhtml->edit.bold;
4539 	if (italic != NULL)
4540 		(*italic) = imhtml->edit.italic;
4541 	if (underline != NULL)
4542 		(*underline) = imhtml->edit.underline;
4543 }
4544 
4545 char *
gtk_imhtml_get_current_fontface(GtkIMHtml * imhtml)4546 gtk_imhtml_get_current_fontface(GtkIMHtml *imhtml)
4547 {
4548 	return g_strdup(imhtml->edit.fontface);
4549 }
4550 
4551 char *
gtk_imhtml_get_current_forecolor(GtkIMHtml * imhtml)4552 gtk_imhtml_get_current_forecolor(GtkIMHtml *imhtml)
4553 {
4554 	return g_strdup(imhtml->edit.forecolor);
4555 }
4556 
4557 char *
gtk_imhtml_get_current_backcolor(GtkIMHtml * imhtml)4558 gtk_imhtml_get_current_backcolor(GtkIMHtml *imhtml)
4559 {
4560 	return g_strdup(imhtml->edit.backcolor);
4561 }
4562 
4563 char *
gtk_imhtml_get_current_background(GtkIMHtml * imhtml)4564 gtk_imhtml_get_current_background(GtkIMHtml *imhtml)
4565 {
4566 	return g_strdup(imhtml->edit.background);
4567 }
4568 
4569 gint
gtk_imhtml_get_current_fontsize(GtkIMHtml * imhtml)4570 gtk_imhtml_get_current_fontsize(GtkIMHtml *imhtml)
4571 {
4572 	return imhtml->edit.fontsize;
4573 }
4574 
gtk_imhtml_get_editable(GtkIMHtml * imhtml)4575 gboolean gtk_imhtml_get_editable(GtkIMHtml *imhtml)
4576 {
4577 	return imhtml->editable;
4578 }
4579 
4580 void
gtk_imhtml_clear_formatting(GtkIMHtml * imhtml)4581 gtk_imhtml_clear_formatting(GtkIMHtml *imhtml)
4582 {
4583 	GObject *object;
4584 
4585 	object = g_object_ref(G_OBJECT(imhtml));
4586 	g_signal_emit(object, signals[CLEAR_FORMAT], 0);
4587 
4588 	gtk_widget_grab_focus(GTK_WIDGET(imhtml));
4589 
4590 	g_object_unref(object);
4591 }
4592 
4593 /*
4594  * I had this crazy idea about changing the text cursor color to reflex the foreground color
4595  * of the text about to be entered. This is the place you'd do it, along with the place where
4596  * we actually set a new foreground color.
4597  * I may not do this, because people will bitch about Purple overriding their gtk theme's cursor
4598  * colors.
4599  *
4600  * Just in case I do do this, I asked about what to set the secondary text cursor to.
4601  *
4602  * (12:45:27) ?? ???: secondary_cursor_color = (rgb(background) + rgb(primary_cursor_color) ) / 2
4603  * (12:45:55) ?? ???: understand?
4604  * (12:46:14) Tim: yeah. i didn't know there was an exact formula
4605  * (12:46:56) ?? ???: u might need to extract separate each color from RGB
4606  */
4607 
mark_set_cb(GtkTextBuffer * buffer,GtkTextIter * arg1,GtkTextMark * mark,GtkIMHtml * imhtml)4608 static void mark_set_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextMark *mark,
4609                            GtkIMHtml *imhtml)
4610 {
4611 	GSList *tags, *l;
4612 	GtkTextIter iter;
4613 
4614 	if (mark != gtk_text_buffer_get_insert(buffer))
4615 		return;
4616 
4617 	if (!gtk_text_buffer_get_char_count(buffer))
4618 		return;
4619 
4620 	imhtml->edit.bold = imhtml->edit.italic = imhtml->edit.underline = imhtml->edit.strike = FALSE;
4621 	g_free(imhtml->edit.forecolor);
4622 	imhtml->edit.forecolor = NULL;
4623 
4624 	g_free(imhtml->edit.backcolor);
4625 	imhtml->edit.backcolor = NULL;
4626 
4627 	g_free(imhtml->edit.fontface);
4628 	imhtml->edit.fontface = NULL;
4629 
4630 	imhtml->edit.fontsize = 0;
4631 	imhtml->edit.link = NULL;
4632 
4633 	gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, mark);
4634 
4635 
4636 	if (gtk_text_iter_is_end(&iter))
4637 		tags = gtk_text_iter_get_toggled_tags(&iter, FALSE);
4638 	else
4639 		tags = gtk_text_iter_get_tags(&iter);
4640 
4641 	for (l = tags; l != NULL; l = l->next) {
4642 		GtkTextTag *tag = GTK_TEXT_TAG(l->data);
4643 
4644 		if (tag->name) {
4645 			if (purple_strequal(tag->name, "BOLD"))
4646 				imhtml->edit.bold = TRUE;
4647 			else if (purple_strequal(tag->name, "ITALICS"))
4648 				imhtml->edit.italic = TRUE;
4649 			else if (purple_strequal(tag->name, "UNDERLINE"))
4650 				imhtml->edit.underline = TRUE;
4651 			else if (purple_strequal(tag->name, "STRIKE"))
4652 				imhtml->edit.strike = TRUE;
4653 			else if (strncmp(tag->name, "FORECOLOR ", 10) == 0)
4654 				imhtml->edit.forecolor = g_strdup(&(tag->name)[10]);
4655 			else if (strncmp(tag->name, "BACKCOLOR ", 10) == 0)
4656 				imhtml->edit.backcolor = g_strdup(&(tag->name)[10]);
4657 			else if (strncmp(tag->name, "FONT FACE ", 10) == 0)
4658 				imhtml->edit.fontface = g_strdup(&(tag->name)[10]);
4659 			else if (strncmp(tag->name, "FONT SIZE ", 10) == 0)
4660 				imhtml->edit.fontsize = strtol(&(tag->name)[10], NULL, 10);
4661 			else if ((strncmp(tag->name, "LINK ", 5) == 0) && !gtk_text_iter_is_end(&iter))
4662 				imhtml->edit.link = tag;
4663 		}
4664 	}
4665 
4666 	g_slist_free(tags);
4667 }
4668 
imhtml_emit_signal_for_format(GtkIMHtml * imhtml,GtkIMHtmlButtons button)4669 static void imhtml_emit_signal_for_format(GtkIMHtml *imhtml, GtkIMHtmlButtons button)
4670 {
4671 	GObject *object;
4672 
4673 	g_return_if_fail(imhtml != NULL);
4674 
4675 	object = g_object_ref(G_OBJECT(imhtml));
4676 	g_signal_emit(object, signals[TOGGLE_FORMAT], 0, button);
4677 	g_object_unref(object);
4678 }
4679 
imhtml_toggle_bold(GtkIMHtml * imhtml)4680 static void imhtml_toggle_bold(GtkIMHtml *imhtml)
4681 {
4682 	GtkTextIter start, end;
4683 
4684 	imhtml->edit.bold = !imhtml->edit.bold;
4685 
4686 	if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4687 		return;
4688 
4689 	if (imhtml->edit.bold)
4690 		gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "BOLD", &start, &end);
4691 	else
4692 		gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "BOLD", &start, &end);
4693 }
4694 
gtk_imhtml_toggle_bold(GtkIMHtml * imhtml)4695 void gtk_imhtml_toggle_bold(GtkIMHtml *imhtml)
4696 {
4697 	imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_BOLD);
4698 }
4699 
imhtml_toggle_italic(GtkIMHtml * imhtml)4700 static void imhtml_toggle_italic(GtkIMHtml *imhtml)
4701 {
4702 	GtkTextIter start, end;
4703 
4704 	imhtml->edit.italic = !imhtml->edit.italic;
4705 
4706 	if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4707 		return;
4708 
4709 	if (imhtml->edit.italic)
4710 		gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "ITALICS", &start, &end);
4711 	else
4712 		gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "ITALICS", &start, &end);
4713 }
4714 
gtk_imhtml_toggle_italic(GtkIMHtml * imhtml)4715 void gtk_imhtml_toggle_italic(GtkIMHtml *imhtml)
4716 {
4717 	imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_ITALIC);
4718 }
4719 
imhtml_toggle_underline(GtkIMHtml * imhtml)4720 static void imhtml_toggle_underline(GtkIMHtml *imhtml)
4721 {
4722 	GtkTextIter start, end;
4723 
4724 	imhtml->edit.underline = !imhtml->edit.underline;
4725 
4726 	if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4727 		return;
4728 
4729 	if (imhtml->edit.underline)
4730 		gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "UNDERLINE", &start, &end);
4731 	else
4732 		gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "UNDERLINE", &start, &end);
4733 }
4734 
gtk_imhtml_toggle_underline(GtkIMHtml * imhtml)4735 void gtk_imhtml_toggle_underline(GtkIMHtml *imhtml)
4736 {
4737 	imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_UNDERLINE);
4738 }
4739 
imhtml_toggle_strike(GtkIMHtml * imhtml)4740 static void imhtml_toggle_strike(GtkIMHtml *imhtml)
4741 {
4742 	GtkTextIter start, end;
4743 
4744 	imhtml->edit.strike = !imhtml->edit.strike;
4745 
4746 	if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4747 		return;
4748 
4749 	if (imhtml->edit.strike)
4750 		gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "STRIKE", &start, &end);
4751 	else
4752 		gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "STRIKE", &start, &end);
4753 }
4754 
gtk_imhtml_toggle_strike(GtkIMHtml * imhtml)4755 void gtk_imhtml_toggle_strike(GtkIMHtml *imhtml)
4756 {
4757 	imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_STRIKE);
4758 }
4759 
gtk_imhtml_font_set_size(GtkIMHtml * imhtml,gint size)4760 void gtk_imhtml_font_set_size(GtkIMHtml *imhtml, gint size)
4761 {
4762 	GObject *object;
4763 	GtkTextIter start, end;
4764 
4765 	imhtml->edit.fontsize = size;
4766 
4767 	if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4768 		return;
4769 
4770 	remove_font_size(imhtml, &start, &end, imhtml->wbfo);
4771 	gtk_text_buffer_apply_tag(imhtml->text_buffer,
4772 			find_font_size_tag(imhtml, imhtml->edit.fontsize), &start, &end);
4773 
4774 	object = g_object_ref(G_OBJECT(imhtml));
4775 	g_signal_emit(object, signals[TOGGLE_FORMAT], 0, GTK_IMHTML_SHRINK | GTK_IMHTML_GROW);
4776 	g_object_unref(object);
4777 }
4778 
imhtml_font_shrink(GtkIMHtml * imhtml)4779 static void imhtml_font_shrink(GtkIMHtml *imhtml)
4780 {
4781 	GtkTextIter start, end;
4782 
4783 	if (imhtml->edit.fontsize == 1)
4784 		return;
4785 
4786 	if (!imhtml->edit.fontsize)
4787 		imhtml->edit.fontsize = 2;
4788 	else
4789 		imhtml->edit.fontsize--;
4790 
4791 	if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4792 		return;
4793 	remove_font_size(imhtml, &start, &end, imhtml->wbfo);
4794 	gtk_text_buffer_apply_tag(imhtml->text_buffer,
4795 		                                  find_font_size_tag(imhtml, imhtml->edit.fontsize), &start, &end);
4796 }
4797 
gtk_imhtml_font_shrink(GtkIMHtml * imhtml)4798 void gtk_imhtml_font_shrink(GtkIMHtml *imhtml)
4799 {
4800 	imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_SHRINK);
4801 }
4802 
imhtml_font_grow(GtkIMHtml * imhtml)4803 static void imhtml_font_grow(GtkIMHtml *imhtml)
4804 {
4805 	GtkTextIter start, end;
4806 
4807 	if (imhtml->edit.fontsize == MAX_FONT_SIZE)
4808 		return;
4809 
4810 	if (!imhtml->edit.fontsize)
4811 		imhtml->edit.fontsize = 4;
4812 	else
4813 		imhtml->edit.fontsize++;
4814 
4815 	if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4816 		return;
4817 	remove_font_size(imhtml, &start, &end, imhtml->wbfo);
4818 	gtk_text_buffer_apply_tag(imhtml->text_buffer,
4819 			find_font_size_tag(imhtml, imhtml->edit.fontsize), &start, &end);
4820 }
4821 
gtk_imhtml_font_grow(GtkIMHtml * imhtml)4822 void gtk_imhtml_font_grow(GtkIMHtml *imhtml)
4823 {
4824 	imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_GROW);
4825 }
4826 
gtk_imhtml_toggle_str_tag(GtkIMHtml * imhtml,const char * value,char ** edit_field,void (* remove_func)(GtkIMHtml * imhtml,GtkTextIter * i,GtkTextIter * e,gboolean homo),GtkTextTag * (find_func)(GtkIMHtml * imhtml,gchar * color),GtkIMHtmlButtons button)4827 static gboolean gtk_imhtml_toggle_str_tag(GtkIMHtml *imhtml, const char *value, char **edit_field,
4828 				void (*remove_func)(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo),
4829 				GtkTextTag *(find_func)(GtkIMHtml *imhtml, gchar *color), GtkIMHtmlButtons button)
4830 {
4831 	GObject *object;
4832 	GtkTextIter start;
4833 	GtkTextIter end;
4834 
4835 	g_free(*edit_field);
4836 	*edit_field = NULL;
4837 
4838 	if (value && *value)
4839 	{
4840 		*edit_field = g_strdup(value);
4841 
4842 		if (imhtml_get_iter_bounds(imhtml, &start, &end)) {
4843 			remove_func(imhtml, &start, &end, imhtml->wbfo);
4844 			gtk_text_buffer_apply_tag(imhtml->text_buffer,
4845 					find_func(imhtml, *edit_field), &start, &end);
4846 		}
4847 	}
4848 	else
4849 	{
4850 		if (imhtml_get_iter_bounds(imhtml, &start, &end))
4851 			remove_func(imhtml, &start, &end, TRUE);	/* 'TRUE' or 'imhtml->wbfo'? */
4852 	}
4853 
4854 	object = g_object_ref(G_OBJECT(imhtml));
4855 	g_signal_emit(object, signals[TOGGLE_FORMAT], 0, button);
4856 	g_object_unref(object);
4857 
4858 	return *edit_field != NULL;
4859 }
4860 
gtk_imhtml_toggle_forecolor(GtkIMHtml * imhtml,const char * color)4861 gboolean gtk_imhtml_toggle_forecolor(GtkIMHtml *imhtml, const char *color)
4862 {
4863 	return gtk_imhtml_toggle_str_tag(imhtml, color, &imhtml->edit.forecolor,
4864 	                                 remove_font_forecolor, find_font_forecolor_tag,
4865 	                                 GTK_IMHTML_FORECOLOR);
4866 }
4867 
gtk_imhtml_toggle_backcolor(GtkIMHtml * imhtml,const char * color)4868 gboolean gtk_imhtml_toggle_backcolor(GtkIMHtml *imhtml, const char *color)
4869 {
4870 	return gtk_imhtml_toggle_str_tag(imhtml, color, &imhtml->edit.backcolor,
4871 	                                 remove_font_backcolor, find_font_backcolor_tag,
4872 	                                 GTK_IMHTML_BACKCOLOR);
4873 }
4874 
gtk_imhtml_toggle_background(GtkIMHtml * imhtml,const char * color)4875 gboolean gtk_imhtml_toggle_background(GtkIMHtml *imhtml, const char *color)
4876 {
4877 	return gtk_imhtml_toggle_str_tag(imhtml, color, &imhtml->edit.background,
4878 	                                 remove_font_background, find_font_background_tag,
4879 	                                 GTK_IMHTML_BACKGROUND);
4880 }
4881 
gtk_imhtml_toggle_fontface(GtkIMHtml * imhtml,const char * face)4882 gboolean gtk_imhtml_toggle_fontface(GtkIMHtml *imhtml, const char *face)
4883 {
4884 	return gtk_imhtml_toggle_str_tag(imhtml, face, &imhtml->edit.fontface,
4885 	                                 remove_font_face, find_font_face_tag,
4886 	                                 GTK_IMHTML_FACE);
4887 }
4888 
gtk_imhtml_toggle_link(GtkIMHtml * imhtml,const char * url)4889 void gtk_imhtml_toggle_link(GtkIMHtml *imhtml, const char *url)
4890 {
4891 	GObject *object;
4892 	GtkTextIter start, end;
4893 	GtkTextTag *linktag;
4894 	static guint linkno = 0;
4895 	gchar str[48];
4896 	GdkColor *color = NULL;
4897 
4898 	imhtml->edit.link = NULL;
4899 
4900 	if (url) {
4901 		g_snprintf(str, sizeof(str), "LINK %d", linkno++);
4902 		str[47] = '\0';
4903 
4904 		gtk_widget_style_get(GTK_WIDGET(imhtml), "hyperlink-color", &color, NULL);
4905 		if (color) {
4906 			imhtml->edit.link = linktag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "foreground-gdk", color, "underline", PANGO_UNDERLINE_SINGLE, NULL);
4907 			gdk_color_free(color);
4908 		} else {
4909 			imhtml->edit.link = linktag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE, NULL);
4910 		}
4911 		g_object_set_data_full(G_OBJECT(linktag), "link_url", g_strdup(url), g_free);
4912 		g_signal_connect(G_OBJECT(linktag), "event", G_CALLBACK(tag_event), NULL);
4913 
4914 		if (imhtml->editable && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) {
4915 			remove_font_link(imhtml, &start, &end, FALSE);
4916 			gtk_text_buffer_apply_tag(imhtml->text_buffer, linktag, &start, &end);
4917 		}
4918 	}
4919 
4920 	object = g_object_ref(G_OBJECT(imhtml));
4921 	g_signal_emit(object, signals[TOGGLE_FORMAT], 0, GTK_IMHTML_LINK);
4922 	g_object_unref(object);
4923 }
4924 
gtk_imhtml_insert_link(GtkIMHtml * imhtml,GtkTextMark * mark,const char * url,const char * text)4925 void gtk_imhtml_insert_link(GtkIMHtml *imhtml, GtkTextMark *mark, const char *url, const char *text)
4926 {
4927 	GtkTextIter iter;
4928 
4929 	/* Delete any currently selected text */
4930 	gtk_text_buffer_delete_selection(imhtml->text_buffer, TRUE, TRUE);
4931 
4932 	gtk_imhtml_toggle_link(imhtml, url);
4933 	gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, mark);
4934 	gtk_text_buffer_insert(imhtml->text_buffer, &iter, text, -1);
4935 	gtk_imhtml_toggle_link(imhtml, NULL);
4936 }
4937 
gtk_imhtml_insert_smiley(GtkIMHtml * imhtml,const char * sml,char * smiley)4938 void gtk_imhtml_insert_smiley(GtkIMHtml *imhtml, const char *sml, char *smiley)
4939 {
4940 	GtkTextMark *mark;
4941 	GtkTextIter iter;
4942 
4943 	/* Delete any currently selected text */
4944 	gtk_text_buffer_delete_selection(imhtml->text_buffer, TRUE, TRUE);
4945 
4946 	mark = gtk_text_buffer_get_insert(imhtml->text_buffer);
4947 
4948 	gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, mark);
4949 	gtk_text_buffer_begin_user_action(imhtml->text_buffer);
4950 	gtk_imhtml_insert_smiley_at_iter(imhtml, sml, smiley, &iter);
4951 	gtk_text_buffer_end_user_action(imhtml->text_buffer);
4952 }
4953 
4954 static gboolean
image_expose(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)4955 image_expose(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
4956 {
4957 	GTK_WIDGET_CLASS(GTK_WIDGET_GET_CLASS(widget))->expose_event(widget, event);
4958 
4959 	return TRUE;
4960 }
4961 
4962 /* In case the smiley gets removed from the imhtml before it gets removed from the queue */
animated_smiley_destroy_cb(GtkObject * widget,GtkIMHtml * imhtml)4963 static void animated_smiley_destroy_cb(GtkObject *widget, GtkIMHtml *imhtml)
4964 {
4965 	GList *l = imhtml->animations->head;
4966 	while (l) {
4967 		GList *next = l->next;
4968 		if (l->data == widget) {
4969 			if (l == imhtml->animations->tail)
4970 				imhtml->animations->tail = imhtml->animations->tail->prev;
4971 			imhtml->animations->head = g_list_delete_link(imhtml->animations->head, l);
4972 			imhtml->num_animations--;
4973 		}
4974 		l = next;
4975 	}
4976 }
4977 
gtk_imhtml_insert_smiley_at_iter(GtkIMHtml * imhtml,const char * sml,char * smiley,GtkTextIter * iter)4978 void gtk_imhtml_insert_smiley_at_iter(GtkIMHtml *imhtml, const char *sml, char *smiley, GtkTextIter *iter)
4979 {
4980 	GdkPixbuf *pixbuf = NULL;
4981 	GdkPixbufAnimation *annipixbuf = NULL;
4982 	GtkWidget *icon = NULL;
4983 	GtkTextChildAnchor *anchor = NULL;
4984 	char *unescaped;
4985 	GtkIMHtmlSmiley *imhtml_smiley;
4986 	GtkWidget *ebox = NULL;
4987 	int numsmileys_thismsg, numsmileys_total;
4988 
4989 	/*
4990 	 * This GtkIMHtml has the maximum number of smileys allowed, so don't
4991 	 * add any more.  We do this for performance reasons, because smileys
4992 	 * are apparently pretty inefficient.  Hopefully we can remove this
4993 	 * restriction when we're using a better HTML widget.
4994 	 */
4995 	unescaped = purple_unescape_html(smiley);
4996 	numsmileys_thismsg = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_thismsg"));
4997 	if (numsmileys_thismsg >= 30) {
4998 		gtk_text_buffer_insert(imhtml->text_buffer, iter, unescaped, -1);
4999 		g_free(unescaped);
5000 		return;
5001 	}
5002 	numsmileys_total = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_total"));
5003 	if (numsmileys_total >= 300) {
5004 		gtk_text_buffer_insert(imhtml->text_buffer, iter, unescaped, -1);
5005 		g_free(unescaped);
5006 		return;
5007 	}
5008 
5009 	imhtml_smiley = gtk_imhtml_smiley_get(imhtml, sml, unescaped);
5010 
5011 	if (imhtml->format_functions & GTK_IMHTML_SMILEY) {
5012 		annipixbuf = imhtml_smiley ? gtk_smiley_get_image(imhtml_smiley) : NULL;
5013 		if (annipixbuf) {
5014 			if (gdk_pixbuf_animation_is_static_image(annipixbuf)) {
5015 				pixbuf = gdk_pixbuf_animation_get_static_image(annipixbuf);
5016 				if (pixbuf)
5017 					icon = gtk_image_new_from_pixbuf(pixbuf);
5018 			} else {
5019 				icon = gtk_image_new_from_animation(annipixbuf);
5020 				if (imhtml->num_animations == 20) {
5021 					GtkImage *image = GTK_IMAGE(g_queue_pop_head(imhtml->animations));
5022 					GdkPixbufAnimation *anim = gtk_image_get_animation(image);
5023 					g_signal_handlers_disconnect_matched(G_OBJECT(image), G_SIGNAL_MATCH_FUNC,
5024 							 0, 0, NULL, G_CALLBACK(animated_smiley_destroy_cb), NULL);
5025 					if (anim) {
5026 						GdkPixbuf *pb = gdk_pixbuf_animation_get_static_image(anim);
5027 						if (pb != NULL) {
5028 							GdkPixbuf *copy = gdk_pixbuf_copy(pb);
5029 							gtk_image_set_from_pixbuf(image, copy);
5030 							g_object_unref(G_OBJECT(copy));
5031 						}
5032 					}
5033 				} else {
5034  					imhtml->num_animations++;
5035 				}
5036 				g_signal_connect(G_OBJECT(icon), "destroy", G_CALLBACK(animated_smiley_destroy_cb), imhtml);
5037 				g_queue_push_tail(imhtml->animations, icon);
5038 			}
5039 		}
5040 	}
5041 
5042 	if (imhtml_smiley && imhtml_smiley->flags & GTK_IMHTML_SMILEY_CUSTOM) {
5043 		ebox = gtk_event_box_new();
5044 		gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox), FALSE);
5045 		gtk_widget_show(ebox);
5046 	}
5047 
5048 	if (icon) {
5049 		anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter);
5050 		g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", g_strdup(unescaped), g_free);
5051 		g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_tiptext", g_strdup(unescaped), g_free);
5052 		g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley), g_free);
5053 
5054 		/* This catches the expose events generated by animated
5055 		 * images, and ensures that they are handled by the image
5056 		 * itself, without propagating to the textview and causing
5057 		 * a complete refresh */
5058 		g_signal_connect(G_OBJECT(icon), "expose-event", G_CALLBACK(image_expose), NULL);
5059 
5060 		gtk_widget_show(icon);
5061 		if (ebox)
5062 			gtk_container_add(GTK_CONTAINER(ebox), icon);
5063 		gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), ebox ? ebox : icon, anchor);
5064 
5065 		g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(numsmileys_thismsg + 1));
5066 		g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(numsmileys_total + 1));
5067 	} else if (imhtml_smiley != NULL && (imhtml->format_functions & GTK_IMHTML_SMILEY)) {
5068 		anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter);
5069 		imhtml_smiley->anchors = g_slist_append(imhtml_smiley->anchors, g_object_ref(anchor));
5070 		if (ebox) {
5071 			GtkWidget *img = gtk_image_new_from_stock(GTK_STOCK_MISSING_IMAGE, GTK_ICON_SIZE_MENU);
5072 			gtk_container_add(GTK_CONTAINER(ebox), img);
5073 			gtk_widget_show(img);
5074 			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", g_strdup(unescaped), g_free);
5075 			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_tiptext", g_strdup(unescaped), g_free);
5076 			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley), g_free);
5077 			gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), ebox, anchor);
5078 		}
5079 
5080 		g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(numsmileys_thismsg + 1));
5081 		g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(numsmileys_total + 1));
5082 	} else {
5083 		gtk_text_buffer_insert(imhtml->text_buffer, iter, unescaped, -1);
5084 	}
5085 
5086 	if (ebox) {
5087 		g_signal_connect(G_OBJECT(ebox), "event", G_CALLBACK(gtk_imhtml_smiley_clicked), imhtml_smiley);
5088 	}
5089 
5090 	g_free(unescaped);
5091 }
5092 
gtk_imhtml_insert_image_at_iter(GtkIMHtml * imhtml,int id,GtkTextIter * iter)5093 void gtk_imhtml_insert_image_at_iter(GtkIMHtml *imhtml, int id, GtkTextIter *iter)
5094 {
5095 	GdkPixbufAnimation *anim = NULL;
5096 	const char *filename = NULL;
5097 	gpointer image;
5098 	GdkRectangle rect;
5099 	GtkIMHtmlScalable *scalable = NULL;
5100 	struct scalable_data *sd;
5101 	int minus;
5102 
5103 	if (!imhtml->funcs || !imhtml->funcs->image_get ||
5104 	    !imhtml->funcs->image_get_size || !imhtml->funcs->image_get_data ||
5105 	    !imhtml->funcs->image_get_filename || !imhtml->funcs->image_ref ||
5106 	    !imhtml->funcs->image_unref)
5107 		return;
5108 
5109 	image = imhtml->funcs->image_get(id);
5110 
5111 	if (image) {
5112 		gpointer data;
5113 		size_t len;
5114 
5115 		data = imhtml->funcs->image_get_data(image);
5116 		len = imhtml->funcs->image_get_size(image);
5117 		if (data && len)
5118 			anim = pidgin_pixbuf_anim_from_data(data, len);
5119 
5120 	}
5121 
5122 	if (anim) {
5123 		struct im_image_data *t = g_new(struct im_image_data, 1);
5124 		filename = imhtml->funcs->image_get_filename(image);
5125 		imhtml->funcs->image_ref(id);
5126 		t->id = id;
5127 		t->mark = gtk_text_buffer_create_mark(imhtml->text_buffer, NULL, iter, TRUE);
5128 		imhtml->im_images = g_slist_prepend(imhtml->im_images, t);
5129 		scalable = gtk_imhtml_animation_new(anim, filename, id);
5130 		g_object_unref(G_OBJECT(anim));
5131 	} else {
5132 		GdkPixbuf *pixbuf;
5133 		pixbuf = gtk_widget_render_icon(GTK_WIDGET(imhtml), GTK_STOCK_MISSING_IMAGE,
5134 						GTK_ICON_SIZE_BUTTON, "gtkimhtml-missing-image");
5135 		scalable = gtk_imhtml_image_new(pixbuf, filename, id);
5136 		g_object_unref(G_OBJECT(pixbuf));
5137 	}
5138 
5139 	sd = g_new(struct scalable_data, 1);
5140 	sd->scalable = scalable;
5141 	sd->mark = gtk_text_buffer_create_mark(imhtml->text_buffer, NULL, iter, TRUE);
5142 	gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect);
5143 	scalable->add_to(scalable, imhtml, iter);
5144 	minus = gtk_text_view_get_left_margin(GTK_TEXT_VIEW(imhtml)) +
5145 		gtk_text_view_get_right_margin(GTK_TEXT_VIEW(imhtml));
5146 	scalable->scale(scalable, rect.width - minus, rect.height);
5147 	imhtml->scalables = g_list_append(imhtml->scalables, sd);
5148 }
5149 
tag_to_html_start(GtkTextTag * tag)5150 static const gchar *tag_to_html_start(GtkTextTag *tag)
5151 {
5152 	const gchar *name;
5153 	static gchar buf[16384];
5154 
5155 	name = tag->name;
5156 	g_return_val_if_fail(name != NULL, "");
5157 
5158 	if (purple_strequal(name, "BOLD")) {
5159 		return "<b>";
5160 	} else if (purple_strequal(name, "ITALICS")) {
5161 		return "<i>";
5162 	} else if (purple_strequal(name, "UNDERLINE")) {
5163 		return "<u>";
5164 	} else if (purple_strequal(name, "STRIKE")) {
5165 		return "<s>";
5166 	} else if (strncmp(name, "LINK ", 5) == 0) {
5167 		char *tmp = g_object_get_data(G_OBJECT(tag), "link_url");
5168 		if (tmp) {
5169 			gchar *escaped = purple_markup_escape_text(tmp, -1);
5170 			g_snprintf(buf, sizeof(buf), "<a href=\"%s\">", escaped);
5171 			buf[sizeof(buf)-1] = '\0';
5172 			g_free(escaped);
5173 			return buf;
5174 		} else {
5175 			return "";
5176 		}
5177 	} else if (strncmp(name, "FORECOLOR ", 10) == 0) {
5178 		g_snprintf(buf, sizeof(buf), "<font color=\"%s\">", &name[10]);
5179 		return buf;
5180 	} else if (strncmp(name, "BACKCOLOR ", 10) == 0) {
5181 		g_snprintf(buf, sizeof(buf), "<font back=\"%s\">", &name[10]);
5182 		return buf;
5183 	} else if (strncmp(name, "BACKGROUND ", 10) == 0) {
5184 		g_snprintf(buf, sizeof(buf), "<body bgcolor=\"%s\">", &name[11]);
5185 		return buf;
5186 	} else if (strncmp(name, "FONT FACE ", 10) == 0) {
5187 		g_snprintf(buf, sizeof(buf), "<font face=\"%s\">", &name[10]);
5188 		return buf;
5189 	} else if (strncmp(name, "FONT SIZE ", 10) == 0) {
5190 		g_snprintf(buf, sizeof(buf), "<font size=\"%s\">", &name[10]);
5191 		return buf;
5192 	} else {
5193 		char *str = buf;
5194 		gboolean isset;
5195 		int ivalue = 0;
5196 		GdkColor *color = NULL;
5197 		GObject *obj = G_OBJECT(tag);
5198 		gboolean empty = TRUE;
5199 
5200 		str += g_snprintf(str, sizeof(buf) - (str - buf), "<span style='");
5201 
5202 		/* Weight */
5203 		g_object_get(obj, "weight-set", &isset, "weight", &ivalue, NULL);
5204 		if (isset) {
5205 			const char *weight = "";
5206 			if (ivalue >= PANGO_WEIGHT_ULTRABOLD)
5207 				weight = "bolder";
5208 			else if (ivalue >= PANGO_WEIGHT_BOLD)
5209 				weight = "bold";
5210 			else if (ivalue >= PANGO_WEIGHT_NORMAL)
5211 				weight = "normal";
5212 			else
5213 				weight = "lighter";
5214 
5215 			str += g_snprintf(str, sizeof(buf) - (str - buf), "font-weight: %s;", weight);
5216 			empty = FALSE;
5217 		}
5218 
5219 		/* Foreground color */
5220 		g_object_get(obj, "foreground-set", &isset, "foreground-gdk", &color, NULL);
5221 		if (isset && color) {
5222 			str += g_snprintf(str, sizeof(buf) - (str - buf),
5223 					"color: #%02x%02x%02x;",
5224 					color->red >> 8, color->green >> 8, color->blue >> 8);
5225 			empty = FALSE;
5226 		}
5227 		gdk_color_free(color);
5228 
5229 		/* Background color */
5230 		g_object_get(obj, "background-set", &isset, "background-gdk", &color, NULL);
5231 		if (isset && color) {
5232 			str += g_snprintf(str, sizeof(buf) - (str - buf),
5233 					"background: #%02x%02x%02x;",
5234 					color->red >> 8, color->green >> 8, color->blue >> 8);
5235 			empty = FALSE;
5236 		}
5237 		gdk_color_free(color);
5238 
5239 		/* Underline */
5240 		g_object_get(obj, "underline-set", &isset, "underline", &ivalue, NULL);
5241 		if (isset) {
5242 			switch (ivalue) {
5243 				case PANGO_UNDERLINE_NONE:
5244 				case PANGO_UNDERLINE_ERROR:
5245 					break;
5246 				default:
5247 					str += g_snprintf(str, sizeof(buf) - (str - buf), "text-decoration: underline;");
5248 					empty = FALSE;
5249 			}
5250 		}
5251 
5252 		g_snprintf(str, sizeof(buf) - (str - buf), "'>");
5253 
5254 		return (empty ? "" : buf);
5255 	}
5256 }
5257 
tag_to_html_end(GtkTextTag * tag)5258 static const gchar *tag_to_html_end(GtkTextTag *tag)
5259 {
5260 	const gchar *name;
5261 
5262 	name = tag->name;
5263 	g_return_val_if_fail(name != NULL, "");
5264 
5265 	if (purple_strequal(name, "BOLD")) {
5266 		return "</b>";
5267 	} else if (purple_strequal(name, "ITALICS")) {
5268 		return "</i>";
5269 	} else if (purple_strequal(name, "UNDERLINE")) {
5270 		return "</u>";
5271 	} else if (purple_strequal(name, "STRIKE")) {
5272 		return "</s>";
5273 	} else if (strncmp(name, "LINK ", 5) == 0) {
5274 		return "</a>";
5275 	} else if (strncmp(name, "FORECOLOR ", 10) == 0) {
5276 		return "</font>";
5277 	} else if (strncmp(name, "BACKCOLOR ", 10) == 0) {
5278 		return "</font>";
5279 	} else if (strncmp(name, "BACKGROUND ", 10) == 0) {
5280 		return "</body>";
5281 	} else if (strncmp(name, "FONT FACE ", 10) == 0) {
5282 		return "</font>";
5283 	} else if (strncmp(name, "FONT SIZE ", 10) == 0) {
5284 		return "</font>";
5285 	} else {
5286 		const char *props[] = {"weight-set", "foreground-set", "background-set",
5287 			"size-set", "underline-set", NULL};
5288 		int i;
5289 		for (i = 0; props[i]; i++) {
5290 			gboolean set = FALSE;
5291 			g_object_get(G_OBJECT(tag), props[i], &set, NULL);
5292 			if (set)
5293 				return "</span>";
5294 		}
5295 
5296 		return "";
5297 	}
5298 }
5299 
5300 typedef struct {
5301 	GtkTextTag *tag;
5302 	char *end;
5303 	char *start;
5304 } PidginTextTagData;
5305 
text_tag_data_new(GtkTextTag * tag)5306 static PidginTextTagData *text_tag_data_new(GtkTextTag *tag)
5307 {
5308 	const char *start, *end;
5309 	PidginTextTagData *ret = NULL;
5310 
5311 	start = tag_to_html_start(tag);
5312 	if (!start || !*start)
5313 		return NULL;
5314 	end = tag_to_html_end(tag);
5315 	if (!end || !*end)
5316 		return NULL;
5317 
5318 	ret = g_new0(PidginTextTagData, 1);
5319 	ret->start = g_strdup(start);
5320 	ret->end = g_strdup(end);
5321 	ret->tag = tag;
5322 	return ret;
5323 }
5324 
text_tag_data_destroy(PidginTextTagData * data)5325 static void text_tag_data_destroy(PidginTextTagData *data)
5326 {
5327 	g_free(data->start);
5328 	g_free(data->end);
5329 	g_free(data);
5330 }
5331 
tag_ends_here(GtkTextTag * tag,GtkTextIter * iter,GtkTextIter * niter)5332 static gboolean tag_ends_here(GtkTextTag *tag, GtkTextIter *iter, GtkTextIter *niter)
5333 {
5334 	return ((gtk_text_iter_has_tag(iter, GTK_TEXT_TAG(tag)) &&
5335 	         !gtk_text_iter_has_tag(niter, GTK_TEXT_TAG(tag))) ||
5336 	        gtk_text_iter_is_end(niter));
5337 }
5338 
5339 /* Basic notion here: traverse through the text buffer one-by-one, non-character elements, such
5340  * as smileys and IM images are represented by the Unicode "unknown" character.  Handle them.  Else
5341  * check for tags that are toggled on, insert their html form, and  push them on the queue. Then insert
5342  * the actual text. Then check for tags that are toggled off and insert them, after checking the queue.
5343  * Finally, replace <, >, &, and " with their HTML equivalent.
5344  */
gtk_imhtml_get_markup_range(GtkIMHtml * imhtml,GtkTextIter * start,GtkTextIter * end)5345 char *gtk_imhtml_get_markup_range(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end)
5346 {
5347 	gunichar c;
5348 	GtkTextIter iter, next_iter, non_neutral_iter;
5349 	gboolean is_rtl_message = FALSE;
5350 	GString *str = g_string_new("");
5351 	GSList *tags, *sl;
5352 	GQueue *q;
5353 	GtkTextTag *tag;
5354 	PidginTextTagData *tagdata;
5355 
5356 	q = g_queue_new();
5357 
5358 	gtk_text_iter_order(start, end);
5359 	non_neutral_iter = next_iter = iter = *start;
5360 	gtk_text_iter_forward_char(&next_iter);
5361 
5362 	/* Bi-directional text support */
5363 	/* Get to the first non-neutral character */
5364 #ifdef HAVE_PANGO14
5365 	while ((PANGO_DIRECTION_NEUTRAL == pango_unichar_direction(gtk_text_iter_get_char(&non_neutral_iter)))
5366 		&& gtk_text_iter_forward_char(&non_neutral_iter));
5367 	if (PANGO_DIRECTION_RTL == pango_unichar_direction(gtk_text_iter_get_char(&non_neutral_iter))) {
5368 		is_rtl_message = TRUE;
5369 		g_string_append(str, "<SPAN style=\"direction:rtl;text-align:right;\">");
5370 	}
5371 #endif
5372 
5373 	/* First add the tags that are already in progress (we don't care about non-printing tags)*/
5374 	tags = gtk_text_iter_get_tags(start);
5375 
5376 	for (sl = tags; sl; sl = sl->next) {
5377 		tag = sl->data;
5378 		if (!gtk_text_iter_toggles_tag(start, GTK_TEXT_TAG(tag))) {
5379 			PidginTextTagData *data = text_tag_data_new(tag);
5380 			if (data) {
5381 				g_string_append(str, data->start);
5382 				g_queue_push_tail(q, data);
5383 			}
5384 		}
5385 	}
5386 	g_slist_free(tags);
5387 
5388 	while ((c = gtk_text_iter_get_char(&iter)) != 0 && !gtk_text_iter_equal(&iter, end)) {
5389 
5390 		tags = gtk_text_iter_get_tags(&iter);
5391 
5392 		for (sl = tags; sl; sl = sl->next) {
5393 			tag = sl->data;
5394 			if (gtk_text_iter_begins_tag(&iter, GTK_TEXT_TAG(tag))) {
5395 				PidginTextTagData *data = text_tag_data_new(tag);
5396 				if (data) {
5397 					g_string_append(str, data->start);
5398 					g_queue_push_tail(q, data);
5399 				}
5400 			}
5401 		}
5402 
5403 		if (c == 0xFFFC) {
5404 			GtkTextChildAnchor* anchor = gtk_text_iter_get_child_anchor(&iter);
5405 			if (anchor) {
5406 				char *text = g_object_get_data(G_OBJECT(anchor), "gtkimhtml_htmltext");
5407 				if (text)
5408 					str = g_string_append(str, text);
5409 			}
5410 		} else if (c == '<') {
5411 			str = g_string_append(str, "&lt;");
5412 		} else if (c == '>') {
5413 			str = g_string_append(str, "&gt;");
5414 		} else if (c == '&') {
5415 			str = g_string_append(str, "&amp;");
5416 		} else if (c == '"') {
5417 			str = g_string_append(str, "&quot;");
5418 		} else if (c == '\n') {
5419 			str = g_string_append(str, "<br>");
5420 		} else {
5421 			str = g_string_append_unichar(str, c);
5422 		}
5423 
5424 		tags = g_slist_reverse(tags);
5425 		for (sl = tags; sl; sl = sl->next) {
5426 			tag = sl->data;
5427 			/** don't worry about non-printing tags ending */
5428 			if (tag_ends_here(tag, &iter, &next_iter) &&
5429 					strlen(tag_to_html_end(tag)) > 0 &&
5430 					strlen(tag_to_html_start(tag)) > 0) {
5431 
5432 				PidginTextTagData *tmp;
5433 				GQueue *r = g_queue_new();
5434 
5435 				while ((tmp = g_queue_pop_tail(q)) && tmp->tag != tag) {
5436 					g_string_append(str, tmp->end);
5437 					if (!tag_ends_here(tmp->tag, &iter, &next_iter))
5438 						g_queue_push_tail(r, tmp);
5439 					else
5440 						text_tag_data_destroy(tmp);
5441 				}
5442 
5443 				if (tmp != NULL) {
5444 					g_string_append(str, tmp->end);
5445 					text_tag_data_destroy(tmp);
5446 				}
5447 #if 0 /* This can't be allowed to happen because it causes the iters to be invalidated in the debug window imhtml during text copying */
5448 				else
5449 					purple_debug_warning("gtkimhtml", "empty queue, more closing tags than open tags!\n");
5450 #endif
5451 
5452 				while ((tmp = g_queue_pop_head(r))) {
5453 					g_string_append(str, tmp->start);
5454 					g_queue_push_tail(q, tmp);
5455 				}
5456 				g_queue_free(r);
5457 			}
5458 		}
5459 
5460 		g_slist_free(tags);
5461 		gtk_text_iter_forward_char(&iter);
5462 		gtk_text_iter_forward_char(&next_iter);
5463 	}
5464 
5465 	while ((tagdata = g_queue_pop_tail(q))) {
5466 		g_string_append(str, tagdata->end);
5467 		text_tag_data_destroy(tagdata);
5468 	}
5469 
5470 	/* Bi-directional text support - close tags */
5471 	if (is_rtl_message)
5472 		g_string_append(str, "</SPAN>");
5473 
5474 	g_queue_free(q);
5475 	return g_string_free(str, FALSE);
5476 }
5477 
gtk_imhtml_close_tags(GtkIMHtml * imhtml,GtkTextIter * iter)5478 void gtk_imhtml_close_tags(GtkIMHtml *imhtml, GtkTextIter *iter)
5479 {
5480 	if (imhtml->edit.bold)
5481 		gtk_imhtml_toggle_bold(imhtml);
5482 
5483 	if (imhtml->edit.italic)
5484 		gtk_imhtml_toggle_italic(imhtml);
5485 
5486 	if (imhtml->edit.underline)
5487 		gtk_imhtml_toggle_underline(imhtml);
5488 
5489 	if (imhtml->edit.strike)
5490 		gtk_imhtml_toggle_strike(imhtml);
5491 
5492 	if (imhtml->edit.forecolor)
5493 		gtk_imhtml_toggle_forecolor(imhtml, NULL);
5494 
5495 	if (imhtml->edit.backcolor)
5496 		gtk_imhtml_toggle_backcolor(imhtml, NULL);
5497 
5498 	if (imhtml->edit.fontface)
5499 		gtk_imhtml_toggle_fontface(imhtml, NULL);
5500 
5501 	imhtml->edit.fontsize = 0;
5502 
5503 	if (imhtml->edit.link)
5504 		gtk_imhtml_toggle_link(imhtml, NULL);
5505 }
5506 
gtk_imhtml_get_markup(GtkIMHtml * imhtml)5507 char *gtk_imhtml_get_markup(GtkIMHtml *imhtml)
5508 {
5509 	GtkTextIter start, end;
5510 
5511 	gtk_text_buffer_get_start_iter(imhtml->text_buffer, &start);
5512 	gtk_text_buffer_get_end_iter(imhtml->text_buffer, &end);
5513 	return gtk_imhtml_get_markup_range(imhtml, &start, &end);
5514 }
5515 
gtk_imhtml_get_markup_lines(GtkIMHtml * imhtml)5516 char **gtk_imhtml_get_markup_lines(GtkIMHtml *imhtml)
5517 {
5518 	int i, j, lines;
5519 	GtkTextIter start, end;
5520 	char **ret;
5521 
5522 	lines = gtk_text_buffer_get_line_count(imhtml->text_buffer);
5523 	ret = g_new0(char *, lines + 1);
5524 	gtk_text_buffer_get_start_iter(imhtml->text_buffer, &start);
5525 	end = start;
5526 	gtk_text_iter_forward_to_line_end(&end);
5527 
5528 	for (i = 0, j = 0; i < lines; i++) {
5529 		if (gtk_text_iter_get_char(&start) != '\n') {
5530 			ret[j] = gtk_imhtml_get_markup_range(imhtml, &start, &end);
5531 			if (ret[j] != NULL)
5532 				j++;
5533 		}
5534 
5535 		gtk_text_iter_forward_line(&start);
5536 		end = start;
5537 		gtk_text_iter_forward_to_line_end(&end);
5538 	}
5539 
5540 	return ret;
5541 }
5542 
gtk_imhtml_get_text(GtkIMHtml * imhtml,GtkTextIter * start,GtkTextIter * stop)5543 char *gtk_imhtml_get_text(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *stop)
5544 {
5545 	GString *str = g_string_new("");
5546 	GtkTextIter iter, end;
5547 	gunichar c;
5548 
5549 	if (start == NULL)
5550 		gtk_text_buffer_get_start_iter(imhtml->text_buffer, &iter);
5551 	else
5552 		iter = *start;
5553 
5554 	if (stop == NULL)
5555 		gtk_text_buffer_get_end_iter(imhtml->text_buffer, &end);
5556 	else
5557 		end = *stop;
5558 
5559 	gtk_text_iter_order(&iter, &end);
5560 
5561 	while ((c = gtk_text_iter_get_char(&iter)) != 0 && !gtk_text_iter_equal(&iter, &end)) {
5562 		if (c == 0xFFFC) {
5563 			GtkTextChildAnchor* anchor;
5564 			char *text = NULL;
5565 
5566 			anchor = gtk_text_iter_get_child_anchor(&iter);
5567 			if (anchor)
5568 				text = g_object_get_data(G_OBJECT(anchor), "gtkimhtml_plaintext");
5569 			if (text)
5570 				str = g_string_append(str, text);
5571 		} else {
5572 			g_string_append_unichar(str, c);
5573 		}
5574 		gtk_text_iter_forward_char(&iter);
5575 	}
5576 
5577 	return g_string_free(str, FALSE);
5578 }
5579 
gtk_imhtml_set_funcs(GtkIMHtml * imhtml,GtkIMHtmlFuncs * f)5580 void gtk_imhtml_set_funcs(GtkIMHtml *imhtml, GtkIMHtmlFuncs *f)
5581 {
5582 	g_return_if_fail(imhtml != NULL);
5583 	imhtml->funcs = f;
5584 }
5585 
gtk_imhtml_setup_entry(GtkIMHtml * imhtml,PurpleConnectionFlags flags)5586 void gtk_imhtml_setup_entry(GtkIMHtml *imhtml, PurpleConnectionFlags flags)
5587 {
5588 	GtkIMHtmlButtons buttons;
5589 
5590 	if (flags & PURPLE_CONNECTION_HTML) {
5591 		char color[8];
5592 		GdkColor fg_color, bg_color;
5593 
5594 		buttons = GTK_IMHTML_ALL;
5595 
5596 		if (flags & PURPLE_CONNECTION_NO_BGCOLOR)
5597 			buttons &= ~GTK_IMHTML_BACKCOLOR;
5598 		if (flags & PURPLE_CONNECTION_NO_FONTSIZE)
5599 		{
5600 			buttons &= ~GTK_IMHTML_GROW;
5601 			buttons &= ~GTK_IMHTML_SHRINK;
5602 		}
5603 		if (flags & PURPLE_CONNECTION_NO_URLDESC)
5604 			buttons &= ~GTK_IMHTML_LINKDESC;
5605 
5606 		gtk_imhtml_set_format_functions(imhtml, GTK_IMHTML_ALL);
5607 		if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_bold") != imhtml->edit.bold)
5608 			gtk_imhtml_toggle_bold(imhtml);
5609 
5610 		if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_italic") != imhtml->edit.italic)
5611 			gtk_imhtml_toggle_italic(imhtml);
5612 
5613 		if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_underline") != imhtml->edit.underline)
5614 			gtk_imhtml_toggle_underline(imhtml);
5615 
5616 		gtk_imhtml_toggle_fontface(imhtml,
5617 			purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/font_face"));
5618 
5619 		if (!(flags & PURPLE_CONNECTION_NO_FONTSIZE))
5620 		{
5621 			int size = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/font_size");
5622 
5623 			/* 3 is the default. */
5624 			if (size != 3)
5625 				gtk_imhtml_font_set_size(imhtml, size);
5626 		}
5627 
5628 		if(!purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"), ""))
5629 		{
5630 			gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"),
5631 							&fg_color);
5632 			g_snprintf(color, sizeof(color), "#%02x%02x%02x",
5633 									fg_color.red   / 256,
5634 									fg_color.green / 256,
5635 									fg_color.blue  / 256);
5636 		} else
5637 			strcpy(color, "");
5638 
5639 		gtk_imhtml_toggle_forecolor(imhtml, color);
5640 
5641 		if(!(flags & PURPLE_CONNECTION_NO_BGCOLOR) &&
5642 		   !purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"), ""))
5643 		{
5644 			gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"),
5645 							&bg_color);
5646 			g_snprintf(color, sizeof(color), "#%02x%02x%02x",
5647 									bg_color.red   / 256,
5648 									bg_color.green / 256,
5649 									bg_color.blue  / 256);
5650 		} else
5651 			strcpy(color, "");
5652 
5653 		gtk_imhtml_toggle_background(imhtml, color);
5654 
5655 		if (flags & PURPLE_CONNECTION_FORMATTING_WBFO)
5656 			gtk_imhtml_set_whole_buffer_formatting_only(imhtml, TRUE);
5657 		else
5658 			gtk_imhtml_set_whole_buffer_formatting_only(imhtml, FALSE);
5659 	} else {
5660 		buttons = GTK_IMHTML_SMILEY | GTK_IMHTML_IMAGE;
5661 		imhtml_clear_formatting(imhtml);
5662 	}
5663 
5664 	if (flags & PURPLE_CONNECTION_NO_IMAGES)
5665 		buttons &= ~GTK_IMHTML_IMAGE;
5666 
5667 	if (flags & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY)
5668 		buttons |= GTK_IMHTML_CUSTOM_SMILEY;
5669 	else
5670 		buttons &= ~GTK_IMHTML_CUSTOM_SMILEY;
5671 
5672 	gtk_imhtml_set_format_functions(imhtml, buttons);
5673 }
5674 
5675 /*******
5676  * GtkIMHtmlSmiley functions
5677  *******/
gtk_custom_smiley_allocated(GdkPixbufLoader * loader,gpointer user_data)5678 static void gtk_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data)
5679 {
5680 	GtkIMHtmlSmiley *smiley;
5681 
5682 	smiley = (GtkIMHtmlSmiley *)user_data;
5683 	smiley->icon = gdk_pixbuf_loader_get_animation(loader);
5684 
5685 	if (smiley->icon)
5686 		g_object_ref(G_OBJECT(smiley->icon));
5687 #ifdef DEBUG_CUSTOM_SMILEY
5688 	purple_debug_info("custom-smiley", "gtk_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s'\n", smiley->icon, smiley->smile);
5689 #endif
5690 }
5691 
gtk_custom_smiley_closed(GdkPixbufLoader * loader,gpointer user_data)5692 static void gtk_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data)
5693 {
5694 	GtkIMHtmlSmiley *smiley;
5695 	GtkWidget *icon = NULL;
5696 	GtkTextChildAnchor *anchor = NULL;
5697 	GSList *current = NULL;
5698 
5699 	smiley = (GtkIMHtmlSmiley *)user_data;
5700 	if (!smiley->imhtml) {
5701 #ifdef DEBUG_CUSTOM_SMILEY
5702 		purple_debug_error("custom-smiley", "gtk_custom_smiley_closed(): orphan smiley found: %p\n", smiley);
5703 #endif
5704 		g_object_unref(G_OBJECT(loader));
5705 		smiley->loader = NULL;
5706 		return;
5707 	}
5708 
5709 	for (current = smiley->anchors; current; current = g_slist_next(current)) {
5710 		anchor = GTK_TEXT_CHILD_ANCHOR(current->data);
5711 		if (gtk_text_child_anchor_get_deleted(anchor))
5712 			icon = NULL;
5713 		else
5714 			icon = gtk_image_new_from_animation(smiley->icon);
5715 
5716 #ifdef DEBUG_CUSTOM_SMILEY
5717 		purple_debug_info("custom-smiley", "gtk_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s'\n",
5718 				icon, smiley->icon, smiley->smile);
5719 #endif
5720 		if (icon) {
5721 			GList *wids;
5722 			gtk_widget_show(icon);
5723 
5724 			wids = gtk_text_child_anchor_get_widgets(anchor);
5725 
5726 			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", purple_unescape_html(smiley->smile), g_free);
5727 			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley->smile), g_free);
5728 
5729 			if (smiley->imhtml) {
5730 				if (wids) {
5731 					GList *children = gtk_container_get_children(GTK_CONTAINER(wids->data));
5732 					g_list_free_full(children, (GDestroyNotify)gtk_widget_destroy);
5733 					gtk_container_add(GTK_CONTAINER(wids->data), icon);
5734 				} else
5735 					gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley->imhtml), icon, anchor);
5736 			}
5737 			g_list_free(wids);
5738 		}
5739 		g_object_unref(anchor);
5740 	}
5741 
5742 	g_slist_free(smiley->anchors);
5743 	smiley->anchors = NULL;
5744 
5745 	g_object_unref(G_OBJECT(loader));
5746 	smiley->loader = NULL;
5747 }
5748 
5749 static void
gtk_custom_smiley_size_prepared(GdkPixbufLoader * loader,gint width,gint height,gpointer data)5750 gtk_custom_smiley_size_prepared(GdkPixbufLoader *loader, gint width, gint height, gpointer data)
5751 {
5752 	if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/resize_custom_smileys")) {
5753 		int custom_smileys_size = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/custom_smileys_size");
5754 		if (width <= custom_smileys_size && height <= custom_smileys_size)
5755 			return;
5756 
5757 		if (width >= height) {
5758 			height = height * custom_smileys_size / width;
5759 			width = custom_smileys_size;
5760 		} else {
5761 			width = width * custom_smileys_size / height;
5762 			height = custom_smileys_size;
5763 		}
5764 	}
5765 	gdk_pixbuf_loader_set_size(loader, width, height);
5766 }
5767 
5768 void
gtk_imhtml_smiley_reload(GtkIMHtmlSmiley * smiley)5769 gtk_imhtml_smiley_reload(GtkIMHtmlSmiley *smiley)
5770 {
5771 	if (smiley->icon)
5772 		g_object_unref(smiley->icon);
5773 	if (smiley->loader)
5774 		g_object_unref(smiley->loader);  /* XXX: does this crash? */
5775 
5776 	smiley->icon = NULL;
5777 	smiley->loader = NULL;
5778 
5779 	if (smiley->file) {
5780 		/* We do not use the pixbuf loader for a smiley that can be loaded
5781 		 * from a file. (e.g., local custom smileys)
5782 		 */
5783 		return;
5784 	}
5785 
5786 	smiley->loader = gdk_pixbuf_loader_new();
5787 
5788 	g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(gtk_custom_smiley_allocated), smiley);
5789 	g_signal_connect(smiley->loader, "closed", G_CALLBACK(gtk_custom_smiley_closed), smiley);
5790 	g_signal_connect(smiley->loader, "size_prepared", G_CALLBACK(gtk_custom_smiley_size_prepared), smiley);
5791 }
5792 
gtk_imhtml_smiley_create(const char * file,const char * shortcut,gboolean hide,GtkIMHtmlSmileyFlags flags)5793 GtkIMHtmlSmiley *gtk_imhtml_smiley_create(const char *file, const char *shortcut, gboolean hide,
5794 		GtkIMHtmlSmileyFlags flags)
5795 {
5796 	GtkIMHtmlSmiley *smiley = g_new0(GtkIMHtmlSmiley, 1);
5797 	smiley->file = g_strdup(file);
5798 	smiley->smile = g_strdup(shortcut);
5799 	smiley->hidden = hide;
5800 	smiley->flags = flags;
5801 	smiley->imhtml = NULL;
5802 	gtk_imhtml_smiley_reload(smiley);
5803 	return smiley;
5804 }
5805 
gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley * smiley)5806 void gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley *smiley)
5807 {
5808 	gtk_imhtml_disassociate_smiley(smiley);
5809 	g_free(smiley->smile);
5810 	g_free(smiley->file);
5811 	if (smiley->icon)
5812 		g_object_unref(smiley->icon);
5813 	if (smiley->loader)
5814 		g_object_unref(smiley->loader);
5815 	g_free(smiley->data);
5816 	g_free(smiley);
5817 }
5818 
gtk_imhtml_class_register_protocol(const char * name,gboolean (* activate)(GtkIMHtml * imhtml,GtkIMHtmlLink * link),gboolean (* context_menu)(GtkIMHtml * imhtml,GtkIMHtmlLink * link,GtkWidget * menu))5819 gboolean gtk_imhtml_class_register_protocol(const char *name,
5820 		gboolean (*activate)(GtkIMHtml *imhtml, GtkIMHtmlLink *link),
5821 		gboolean (*context_menu)(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu))
5822 {
5823 	GtkIMHtmlClass *klass;
5824 	GtkIMHtmlProtocol *proto;
5825 
5826 	g_return_val_if_fail(name, FALSE);
5827 
5828 	klass = g_type_class_ref(GTK_TYPE_IMHTML);
5829 	g_return_val_if_fail(klass, FALSE);
5830 
5831 	if ((proto = imhtml_find_protocol(name, TRUE))) {
5832 		if (activate) {
5833 			return FALSE;
5834 		}
5835 		klass->protocols = g_list_remove(klass->protocols, proto);
5836 		g_free(proto->name);
5837 		g_free(proto);
5838 		return TRUE;
5839 	} else if (!activate) {
5840 		return FALSE;
5841 	}
5842 
5843 	proto = g_new0(GtkIMHtmlProtocol, 1);
5844 	proto->name = g_strdup(name);
5845 	proto->length = strlen(name);
5846 	proto->activate = activate;
5847 	proto->context_menu = context_menu;
5848 	klass->protocols = g_list_prepend(klass->protocols, proto);
5849 
5850 	return TRUE;
5851 }
5852 
5853 static void
gtk_imhtml_activate_tag(GtkIMHtml * imhtml,GtkTextTag * tag)5854 gtk_imhtml_activate_tag(GtkIMHtml *imhtml, GtkTextTag *tag)
5855 {
5856 	/* A link was clicked--we emit the "url_clicked" signal
5857 	 * with the URL as the argument */
5858 	g_object_ref(G_OBJECT(tag));
5859 	g_signal_emit(imhtml, signals[URL_CLICKED], 0, g_object_get_data(G_OBJECT(tag), "link_url"));
5860 	g_object_unref(G_OBJECT(tag));
5861 	g_object_set_data(G_OBJECT(tag), "visited", GINT_TO_POINTER(TRUE));
5862 	gtk_imhtml_set_link_color(GTK_IMHTML(imhtml), tag);
5863 }
5864 
gtk_imhtml_link_activate(GtkIMHtmlLink * link)5865 gboolean gtk_imhtml_link_activate(GtkIMHtmlLink *link)
5866 {
5867 	g_return_val_if_fail(link, FALSE);
5868 
5869 	if (link->tag) {
5870 		gtk_imhtml_activate_tag(link->imhtml, link->tag);
5871 	} else if (link->url) {
5872 		g_signal_emit(link->imhtml, signals[URL_CLICKED], 0, link->url);
5873 	} else
5874 		return FALSE;
5875 	return TRUE;
5876 }
5877 
gtk_imhtml_link_get_url(GtkIMHtmlLink * link)5878 const char *gtk_imhtml_link_get_url(GtkIMHtmlLink *link)
5879 {
5880 	return link->url;
5881 }
5882 
gtk_imhtml_link_get_text_tag(GtkIMHtmlLink * link)5883 const GtkTextTag * gtk_imhtml_link_get_text_tag(GtkIMHtmlLink *link)
5884 {
5885 	return link->tag;
5886 }
5887 
return_add_newline_cb(GtkWidget * widget,gpointer data)5888 static gboolean return_add_newline_cb(GtkWidget *widget, gpointer data)
5889 {
5890 	GtkTextBuffer *buffer;
5891 	GtkTextMark *mark;
5892 	GtkTextIter iter;
5893 
5894 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
5895 
5896 	/* Delete any currently selected text */
5897 	gtk_text_buffer_delete_selection(buffer, TRUE, TRUE);
5898 
5899 	/* Insert a newline at the current cursor position */
5900 	mark = gtk_text_buffer_get_insert(buffer);
5901 	gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
5902 	gtk_imhtml_insert_html_at_iter(GTK_IMHTML(widget), "\n", 0, &iter);
5903 
5904 	/*
5905 	 * If we just newlined ourselves past the end of the visible area
5906 	 * then scroll down so the cursor is in view.
5907 	 */
5908 	gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(widget),
5909 			gtk_text_buffer_get_insert(buffer),
5910 			0, FALSE, 0.0, 0.0);
5911 
5912 	return TRUE;
5913 }
5914 
5915 /*
5916  * It's kind of a pain that we need this function and the above just
5917  * to reinstate the default GtkTextView behavior.  It might be better
5918  * if GtkIMHtml didn't intercept the enter key and just required the
5919  * application to deal with it--it's really not much more work than it
5920  * is to connect to the current "message_send" signal.
5921  */
gtk_imhtml_set_return_inserts_newline(GtkIMHtml * imhtml)5922 void gtk_imhtml_set_return_inserts_newline(GtkIMHtml *imhtml)
5923 {
5924 	g_signal_connect(G_OBJECT(imhtml), "message_send",
5925 		G_CALLBACK(return_add_newline_cb), NULL);
5926 }
5927 
gtk_imhtml_set_populate_primary_clipboard(GtkIMHtml * imhtml,gboolean populate)5928 void gtk_imhtml_set_populate_primary_clipboard(GtkIMHtml *imhtml, gboolean populate)
5929 {
5930 	gulong signal_id;
5931 	signal_id = g_signal_handler_find(imhtml->text_buffer,
5932 			G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_UNBLOCKED, 0, 0, NULL,
5933 			mark_set_so_update_selection_cb, NULL);
5934 	if (populate) {
5935 		if (!signal_id) {
5936 			/* We didn't find an unblocked signal handler, which means there
5937 			   is a blocked handler. Now unblock it.
5938 			   This is necessary to avoid a mutex-lock when the debug message
5939 			   saying 'no handler is blocked' is printed in the debug window.
5940 				-- sad
5941 			 */
5942 			g_signal_handlers_unblock_matched(imhtml->text_buffer,
5943 					G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
5944 					mark_set_so_update_selection_cb, NULL);
5945 		}
5946 	} else {
5947 		/* Block only if we found an unblocked handler */
5948 		if (signal_id)
5949 			g_signal_handler_block(imhtml->text_buffer, signal_id);
5950 	}
5951 }
5952