pidgin/gtkimhtml.c

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

mercurial