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