pidgin/gtkimhtml.c

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

mercurial