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