src/gtkimhtml.c

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

mercurial