applied changes from 8300e1e9a38c8d28e0710569d22be71f6c1abca9
[pidgin-git.git] / pidgin / gtkimhtml.c
blobd74407fe2a5e98beb7a92e0249ac012a703b6918
1 /*
2 * @file gtkimhtml.c GTK+ IMHtml
3 * @ingroup pidgin
4 */
6 /* pidgin
8 * Pidgin is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
27 #define _PIDGIN_GTKIMHTML_C_
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif
33 #include "internal.h"
34 #include "pidgin.h"
35 #include "pidginstock.h"
36 #include "gtkutils.h"
37 #include "smiley.h"
38 #include "imgstore.h"
40 #include "debug.h"
41 #include "util.h"
42 #include "gtkimhtml.h"
43 #include "gtksmiley.h"
44 #include "gtksourceiter.h"
45 #include "gtksourceundomanager.h"
46 #include "gtksourceview-marshal.h"
47 #include <gtk/gtk.h>
48 #include <glib.h>
49 #include <gdk/gdkkeysyms.h>
50 #include <string.h>
51 #include <ctype.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <math.h>
55 #ifdef HAVE_LANGINFO_CODESET
56 #include <langinfo.h>
57 #include <locale.h>
58 #endif
59 #ifdef _WIN32
60 #include <gdk/gdkwin32.h>
61 #include <windows.h>
62 #endif
64 #include <pango/pango-font.h>
66 #define TOOLTIP_TIMEOUT 500
68 static GtkTextViewClass *parent_class = NULL;
70 struct scalable_data {
71 GtkIMHtmlScalable *scalable;
72 GtkTextMark *mark;
75 typedef struct {
76 GtkIMHtmlScalable *image;
77 gpointer data;
78 gsize datasize;
79 } GtkIMHtmlImageSave;
81 struct im_image_data {
82 int id;
83 GtkTextMark *mark;
86 struct _GtkIMHtmlLink
88 GtkIMHtml *imhtml;
89 gchar *url;
90 GtkTextTag *tag;
93 typedef struct _GtkIMHtmlProtocol
95 char *name;
96 int length;
98 gboolean (*activate)(GtkIMHtml *imhtml, GtkIMHtmlLink *link);
99 gboolean (*context_menu)(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu);
100 } GtkIMHtmlProtocol;
102 typedef struct _GtkIMHtmlFontDetail {
103 gushort size;
104 gchar *face;
105 gchar *fore;
106 gchar *back;
107 gchar *bg;
108 gchar *sml;
109 gboolean underline;
110 gboolean strike;
111 gshort bold;
112 } GtkIMHtmlFontDetail;
114 static gboolean
115 gtk_text_view_drag_motion (GtkWidget *widget,
116 GdkDragContext *context,
117 gint x,
118 gint y,
119 guint time);
121 static void preinsert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml);
122 static void insert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml);
123 static void delete_cb(GtkTextBuffer *buffer, GtkTextIter *iter, GtkTextIter *end, GtkIMHtml *imhtml);
124 static void insert_ca_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextChildAnchor *arg2, gpointer user_data);
125 static void gtk_imhtml_apply_tags_on_insert(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end);
126 void gtk_imhtml_close_tags(GtkIMHtml *imhtml, GtkTextIter *iter);
127 static void gtk_imhtml_link_drop_cb(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer user_data);
128 static void gtk_imhtml_link_drag_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y, GtkSelectionData *sd, guint info, guint t, GtkIMHtml *imhtml);
129 static void mark_set_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextMark *mark, GtkIMHtml *imhtml);
130 static void hijack_menu_cb(GtkIMHtml *imhtml, GtkMenu *menu, gpointer data);
131 static void paste_received_cb (GtkClipboard *clipboard, GtkSelectionData *selection_data, gpointer data);
132 static void paste_plaintext_received_cb (GtkClipboard *clipboard, const gchar *text, gpointer data);
133 static void imhtml_paste_insert(GtkIMHtml *imhtml, const char *text, gboolean plaintext);
134 static void imhtml_toggle_bold(GtkIMHtml *imhtml);
135 static void imhtml_toggle_italic(GtkIMHtml *imhtml);
136 static void imhtml_toggle_strike(GtkIMHtml *imhtml);
137 static void imhtml_toggle_underline(GtkIMHtml *imhtml);
138 static void imhtml_font_grow(GtkIMHtml *imhtml);
139 static void imhtml_font_shrink(GtkIMHtml *imhtml);
140 static void imhtml_clear_formatting(GtkIMHtml *imhtml);
141 static int gtk_imhtml_is_protocol(const char *text);
142 static void gtk_imhtml_activate_tag(GtkIMHtml *imhtml, GtkTextTag *tag);
143 static void gtk_imhtml_link_destroy(GtkIMHtmlLink *link);
145 /* POINT_SIZE converts from AIM font sizes to a point size scale factor. */
146 #define MAX_FONT_SIZE 7
147 #define POINT_SIZE(x) (_point_sizes [MIN ((x > 0 ? x : 1), MAX_FONT_SIZE) - 1])
148 static const gdouble _point_sizes [] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736};
150 enum {
151 TARGET_HTML,
152 TARGET_UTF8_STRING,
153 TARGET_COMPOUND_TEXT,
154 TARGET_STRING,
155 TARGET_TEXT
158 enum {
159 URL_CLICKED,
160 BUTTONS_UPDATE,
161 TOGGLE_FORMAT,
162 CLEAR_FORMAT,
163 UPDATE_FORMAT,
164 MESSAGE_SEND,
165 UNDO,
166 REDO,
167 PASTE,
168 LAST_SIGNAL
170 static guint signals [LAST_SIGNAL] = { 0 };
172 static char *html_clipboard = NULL;
173 static char *text_clipboard = NULL;
174 static GtkClipboard *clipboard_selection = NULL;
176 static const GtkTargetEntry selection_targets[] = {
177 #ifndef _WIN32
178 { "text/html", 0, TARGET_HTML },
179 #else
180 { "HTML Format", 0, TARGET_HTML },
181 #endif
182 { "UTF8_STRING", 0, TARGET_UTF8_STRING },
183 { "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT },
184 { "STRING", 0, TARGET_STRING },
185 { "TEXT", 0, TARGET_TEXT}};
187 static const GtkTargetEntry link_drag_drop_targets[] = {
188 GTK_IMHTML_DND_TARGETS
191 #ifdef _WIN32
192 static gchar *
193 clipboard_win32_to_html(char *clipboard) {
194 const char *header;
195 const char *begin, *end;
196 gint start = 0;
197 gint finish = 0;
198 gchar *html;
199 gchar **split;
200 int clipboard_length = 0;
202 #if 0 /* Debugging for Windows clipboard */
203 FILE *fd;
205 purple_debug_info("imhtml clipboard", "from clipboard: %s\n", clipboard);
207 fd = g_fopen("c:\\purplecb.txt", "wb");
208 fprintf(fd, "%s", clipboard);
209 fclose(fd);
210 #endif
212 clipboard_length = strlen(clipboard);
214 if (!(header = strstr(clipboard, "StartFragment:")) || (header - clipboard) >= clipboard_length)
215 return NULL;
216 sscanf(header, "StartFragment:%d", &start);
218 if (!(header = strstr(clipboard, "EndFragment:")) || (header - clipboard) >= clipboard_length)
219 return NULL;
220 sscanf(header, "EndFragment:%d", &finish);
222 if (finish > clipboard_length)
223 finish = clipboard_length;
225 if (start > finish)
226 start = finish;
228 begin = clipboard + start;
230 end = clipboard + finish;
232 html = g_strndup(begin, end - begin);
234 /* any newlines in the string will now be \r\n, so we need to strip out the \r */
235 split = g_strsplit(html, "\r\n", 0);
236 g_free(html);
237 html = g_strjoinv("\n", split);
238 g_strfreev(split);
240 #if 0 /* Debugging for Windows clipboard */
241 purple_debug_info("imhtml clipboard", "HTML fragment: '%s'\n", html);
242 #endif
244 return html;
247 static gchar *
248 clipboard_html_to_win32(char *html) {
249 int length;
250 GString *clipboard;
252 if (html == NULL)
253 return NULL;
255 length = strlen(html);
256 clipboard = g_string_new ("Version:1.0\r\n");
257 g_string_append(clipboard, "StartHTML:0000000105\r\n");
258 g_string_append_printf(clipboard, "EndHTML:%010d\r\n", 147 + length);
259 g_string_append(clipboard, "StartFragment:0000000127\r\n");
260 g_string_append_printf(clipboard, "EndFragment:%010d\r\n", 127 + length);
261 g_string_append(clipboard, "<!--StartFragment-->\r\n");
262 g_string_append(clipboard, html);
263 g_string_append(clipboard, "\r\n<!--EndFragment-->");
265 return g_string_free(clipboard, FALSE);
268 static gboolean clipboard_paste_html_win32(GtkIMHtml *imhtml) {
269 gboolean pasted = FALSE;
271 /* Win32 clipboard format value, and functions to convert back and
272 * forth between HTML and the clipboard format.
274 static UINT win_html_fmt = 0;
276 /* Register HTML Format as desired clipboard format */
277 if (!win_html_fmt)
278 win_html_fmt = RegisterClipboardFormat("HTML Format");
280 if (gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml))
281 && IsClipboardFormatAvailable(win_html_fmt)) {
282 gboolean error_reading_clipboard = FALSE;
283 HWND hwnd = GDK_WINDOW_HWND(GTK_WIDGET(imhtml)->window);
285 if (OpenClipboard(hwnd)) {
286 HGLOBAL hdata = GetClipboardData(win_html_fmt);
287 if (hdata == NULL) {
288 if (GetLastError() != ERROR_SUCCESS)
289 error_reading_clipboard = TRUE;
290 } else {
291 char *buffer = GlobalLock(hdata);
292 if (buffer == NULL) {
293 error_reading_clipboard = TRUE;
294 } else {
295 char *text = clipboard_win32_to_html(
296 buffer);
297 imhtml_paste_insert(imhtml, text,
298 FALSE);
299 g_free(text);
300 pasted = TRUE;
302 GlobalUnlock(hdata);
305 CloseClipboard();
306 } else {
307 error_reading_clipboard = TRUE;
310 if (error_reading_clipboard) {
311 gchar *err_msg = g_win32_error_message(GetLastError());
312 purple_debug_info("html clipboard",
313 "Unable to read clipboard data: %s\n",
314 err_msg ? err_msg : "Unknown Error");
315 g_free(err_msg);
319 return pasted;
321 #endif
323 static GtkSmileyTree*
324 gtk_smiley_tree_new (void)
326 return g_new0 (GtkSmileyTree, 1);
329 static void
330 gtk_smiley_tree_insert (GtkSmileyTree *tree,
331 GtkIMHtmlSmiley *smiley)
333 GtkSmileyTree *t = tree;
334 const gchar *x = smiley->smile;
336 if (!(*x))
337 return;
339 do {
340 gchar *pos;
341 gint index;
343 if (!t->values)
344 t->values = g_string_new ("");
346 pos = strchr (t->values->str, *x);
347 if (!pos) {
348 t->values = g_string_append_c (t->values, *x);
349 index = t->values->len - 1;
350 t->children = g_realloc (t->children, t->values->len * sizeof (GtkSmileyTree *));
351 t->children [index] = g_new0 (GtkSmileyTree, 1);
352 } else
353 index = GPOINTER_TO_INT(pos) - GPOINTER_TO_INT(t->values->str);
355 t = t->children [index];
357 x++;
358 } while (*x);
360 t->image = smiley;
364 static void
365 gtk_smiley_tree_destroy (GtkSmileyTree *tree)
367 GSList *list = g_slist_prepend (NULL, tree);
369 while (list) {
370 GtkSmileyTree *t = list->data;
371 gsize i;
372 list = g_slist_remove(list, t);
373 if (t && t->values) {
374 for (i = 0; i < t->values->len; i++)
375 list = g_slist_prepend (list, t->children [i]);
376 g_string_free (t->values, TRUE);
377 g_free (t->children);
380 g_free (t);
384 static void (*parent_size_allocate)(GtkWidget *widget, GtkAllocation *alloc);
386 static void gtk_imhtml_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
388 GtkIMHtml *imhtml = GTK_IMHTML(widget);
389 GdkRectangle rect;
390 int xminus;
391 int height = 0, y = 0;
392 GtkTextIter iter;
393 gboolean scroll = TRUE;
395 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &iter);
397 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget), &rect);
398 gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(imhtml), &iter, &y, &height);
400 if (((y + height) - (rect.y + rect.height)) > height &&
401 gtk_text_buffer_get_char_count(imhtml->text_buffer)) {
402 scroll = FALSE;
405 if(imhtml->old_rect.width != rect.width || imhtml->old_rect.height != rect.height) {
406 GList *iter = GTK_IMHTML(widget)->scalables;
408 xminus = gtk_text_view_get_left_margin(GTK_TEXT_VIEW(widget)) +
409 gtk_text_view_get_right_margin(GTK_TEXT_VIEW(widget));
411 while(iter){
412 struct scalable_data *sd = iter->data;
413 GtkIMHtmlScalable *scale = GTK_IMHTML_SCALABLE(sd->scalable);
414 scale->scale(scale, rect.width - xminus, rect.height);
416 iter = iter->next;
420 imhtml->old_rect = rect;
421 parent_size_allocate(widget, alloc);
423 /* Don't scroll here if we're in the middle of a smooth scroll */
424 if (scroll && imhtml->scroll_time == NULL &&
425 GTK_WIDGET_REALIZED(imhtml))
426 gtk_imhtml_scroll_to_end(imhtml, FALSE);
429 #define DEFAULT_SEND_COLOR "#204a87"
430 #define DEFAULT_RECV_COLOR "#cc0000"
431 #define DEFAULT_HIGHLIGHT_COLOR "#AF7F00"
432 #define DEFAULT_ACTION_COLOR "#062585"
433 #define DEFAULT_WHISPER_ACTION_COLOR "#6C2585"
434 #define DEFAULT_WHISPER_COLOR "#00FF00"
436 static void (*parent_style_set)(GtkWidget *widget, GtkStyle *prev_style);
438 static void
439 gtk_imhtml_style_set(GtkWidget *widget, GtkStyle *prev_style)
441 int i;
442 struct {
443 const char *tag;
444 const char *color;
445 const char *def;
446 } styles[] = {
447 {"send-name", "send-name-color", DEFAULT_SEND_COLOR},
448 {"receive-name", "receive-name-color", DEFAULT_RECV_COLOR},
449 {"highlight-name", "highlight-name-color", DEFAULT_HIGHLIGHT_COLOR},
450 {"action-name", "action-name-color", DEFAULT_ACTION_COLOR},
451 {"whisper-action-name", "whisper-action-name-color", DEFAULT_WHISPER_ACTION_COLOR},
452 {"whisper-name", "whisper-name-color", DEFAULT_WHISPER_COLOR},
453 {NULL, NULL, NULL}
455 GtkIMHtml *imhtml = GTK_IMHTML(widget);
456 GtkTextTagTable *table = gtk_text_buffer_get_tag_table(imhtml->text_buffer);
458 for (i = 0; styles[i].tag; i++) {
459 GdkColor *color = NULL;
460 GtkTextTag *tag = gtk_text_tag_table_lookup(table, styles[i].tag);
461 if (!tag) {
462 purple_debug_warning("gtkimhtml", "Cannot find tag '%s'. This should never happen. Please file a bug.\n", styles[i].tag);
463 continue;
465 gtk_widget_style_get(widget, styles[i].color, &color, NULL);
466 if (color) {
467 g_object_set(tag, "foreground-gdk", color, NULL);
468 gdk_color_free(color);
469 } else {
470 GdkColor defcolor;
471 gdk_color_parse(styles[i].def, &defcolor);
472 g_object_set(tag, "foreground-gdk", &defcolor, NULL);
475 parent_style_set(widget, prev_style);
478 static gboolean
479 imhtml_get_iter_bounds(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end)
481 if (imhtml->wbfo) {
482 gtk_text_buffer_get_bounds(imhtml->text_buffer, start, end);
483 return TRUE;
484 } else if (imhtml->editable) {
485 if (!gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, start, end)) {
486 GtkTextMark *mark = gtk_text_buffer_get_insert(imhtml->text_buffer);
487 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, start, mark);
488 *end = *start;
490 return TRUE;
493 return FALSE;
496 static void
497 gtk_imhtml_set_link_color(GtkIMHtml *imhtml, GtkTextTag *tag)
499 GdkColor *color = NULL;
500 gboolean visited = !!g_object_get_data(G_OBJECT(tag), "visited");
501 gtk_widget_style_get(GTK_WIDGET(imhtml), visited ? "hyperlink-visited-color" : "hyperlink-color", &color, NULL);
502 if (color) {
503 g_object_set(G_OBJECT(tag), "foreground-gdk", color, NULL);
504 gdk_color_free(color);
505 } else {
506 g_object_set(G_OBJECT(tag), "foreground", visited ? "#800000" : "blue", NULL);
510 static gint
511 gtk_imhtml_tip_paint (GtkIMHtml *imhtml)
513 PangoLayout *layout;
515 g_return_val_if_fail(GTK_IS_IMHTML(imhtml), FALSE);
517 layout = gtk_widget_create_pango_layout(imhtml->tip_window, imhtml->tip);
519 gtk_paint_flat_box (imhtml->tip_window->style, imhtml->tip_window->window,
520 GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL, imhtml->tip_window,
521 "tooltip", 0, 0, -1, -1);
523 gtk_paint_layout (imhtml->tip_window->style, imhtml->tip_window->window, GTK_STATE_NORMAL,
524 FALSE, NULL, imhtml->tip_window, NULL, 4, 4, layout);
526 g_object_unref(layout);
527 return FALSE;
530 static gint
531 gtk_imhtml_tip (gpointer data)
533 GtkIMHtml *imhtml = data;
534 PangoFontMetrics *font_metrics;
535 PangoLayout *layout;
536 PangoFont *font;
538 gint gap, x, y, h, w, scr_w, baseline_skip;
540 g_return_val_if_fail(GTK_IS_IMHTML(imhtml), FALSE);
542 if (!imhtml->tip || !GTK_WIDGET_DRAWABLE (GTK_WIDGET(imhtml))) {
543 imhtml->tip_timer = 0;
544 return FALSE;
547 if (imhtml->tip_window){
548 gtk_widget_destroy (imhtml->tip_window);
549 imhtml->tip_window = NULL;
552 imhtml->tip_timer = 0;
553 imhtml->tip_window = gtk_window_new (GTK_WINDOW_POPUP);
554 gtk_widget_set_app_paintable (imhtml->tip_window, TRUE);
555 gtk_window_set_title(GTK_WINDOW(imhtml->tip_window), "GtkIMHtml");
556 gtk_window_set_resizable (GTK_WINDOW (imhtml->tip_window), FALSE);
557 gtk_widget_set_name (imhtml->tip_window, "gtk-tooltips");
558 gtk_window_set_type_hint (GTK_WINDOW (imhtml->tip_window),
559 GDK_WINDOW_TYPE_HINT_TOOLTIP);
560 g_signal_connect_swapped (G_OBJECT (imhtml->tip_window), "expose_event",
561 G_CALLBACK (gtk_imhtml_tip_paint), imhtml);
563 gtk_widget_ensure_style (imhtml->tip_window);
564 layout = gtk_widget_create_pango_layout(imhtml->tip_window, imhtml->tip);
565 font = pango_context_load_font(pango_layout_get_context(layout),
566 imhtml->tip_window->style->font_desc);
568 if (font == NULL) {
569 char *tmp = pango_font_description_to_string(
570 imhtml->tip_window->style->font_desc);
572 purple_debug(PURPLE_DEBUG_ERROR, "gtk_imhtml_tip",
573 "pango_context_load_font() couldn't load font: '%s'\n",
574 tmp);
575 g_free(tmp);
577 g_object_unref(layout);
578 return FALSE;
581 font_metrics = pango_font_get_metrics(font, NULL);
583 pango_layout_get_pixel_size(layout, &scr_w, NULL);
584 gap = PANGO_PIXELS((pango_font_metrics_get_ascent(font_metrics) +
585 pango_font_metrics_get_descent(font_metrics))/ 4);
587 if (gap < 2)
588 gap = 2;
589 baseline_skip = PANGO_PIXELS(pango_font_metrics_get_ascent(font_metrics) +
590 pango_font_metrics_get_descent(font_metrics));
591 w = 8 + scr_w;
592 h = 8 + baseline_skip;
594 gdk_window_get_pointer (NULL, &x, &y, NULL);
595 if (GTK_WIDGET_NO_WINDOW (GTK_WIDGET(imhtml)))
596 y += GTK_WIDGET(imhtml)->allocation.y;
598 scr_w = gdk_screen_width();
600 x -= ((w >> 1) + 4);
602 if ((x + w) > scr_w)
603 x -= (x + w) - scr_w;
604 else if (x < 0)
605 x = 0;
607 y = y + PANGO_PIXELS(pango_font_metrics_get_ascent(font_metrics) +
608 pango_font_metrics_get_descent(font_metrics));
610 gtk_widget_set_size_request (imhtml->tip_window, w, h);
611 gtk_window_move (GTK_WINDOW(imhtml->tip_window), x, y);
612 gtk_widget_show (imhtml->tip_window);
614 pango_font_metrics_unref(font_metrics);
615 g_object_unref(font);
616 g_object_unref(layout);
618 return FALSE;
621 static gboolean
622 gtk_motion_event_notify(GtkWidget *imhtml, GdkEventMotion *event, gpointer data)
624 GtkTextIter iter;
625 GdkWindow *win = event->window;
626 int x, y;
627 char *tip = NULL;
628 GSList *tags = NULL, *templist = NULL;
629 GtkTextTag *tag = NULL, *oldprelit_tag;
630 GtkTextChildAnchor* anchor;
631 gboolean hand = TRUE;
632 GdkCursor *cursor = NULL;
634 oldprelit_tag = GTK_IMHTML(imhtml)->prelit_tag;
636 gdk_window_get_pointer(GTK_WIDGET(imhtml)->window, NULL, NULL, NULL);
637 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(imhtml), GTK_TEXT_WINDOW_WIDGET,
638 event->x, event->y, &x, &y);
639 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml), &iter, x, y);
640 tags = gtk_text_iter_get_tags(&iter);
642 templist = tags;
643 while (templist) {
644 tag = templist->data;
645 tip = g_object_get_data(G_OBJECT(tag), "link_url");
646 if (tip)
647 break;
648 templist = templist->next;
651 if (tip && (!tag || !g_object_get_data(G_OBJECT(tag), "visited"))) {
652 GTK_IMHTML(imhtml)->prelit_tag = tag;
653 if (tag != oldprelit_tag) {
654 GdkColor *pre = NULL;
655 gtk_widget_style_get(GTK_WIDGET(imhtml), "hyperlink-prelight-color", &pre, NULL);
656 if (pre) {
657 g_object_set(G_OBJECT(tag), "foreground-gdk", pre, NULL);
658 gdk_color_free(pre);
659 } else
660 g_object_set(G_OBJECT(tag), "foreground", "#70a0ff", NULL);
662 } else {
663 GTK_IMHTML(imhtml)->prelit_tag = NULL;
666 if ((oldprelit_tag != NULL) && (GTK_IMHTML(imhtml)->prelit_tag != oldprelit_tag)) {
667 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml), oldprelit_tag);
670 if (GTK_IMHTML(imhtml)->tip) {
671 if ((tip == GTK_IMHTML(imhtml)->tip)) {
672 g_slist_free(tags);
673 return FALSE;
675 /* We've left the cell. Remove the timeout and create a new one below */
676 if (GTK_IMHTML(imhtml)->tip_window) {
677 gtk_widget_destroy(GTK_IMHTML(imhtml)->tip_window);
678 GTK_IMHTML(imhtml)->tip_window = NULL;
680 if (GTK_IMHTML(imhtml)->editable)
681 cursor = GTK_IMHTML(imhtml)->text_cursor;
682 else
683 cursor = GTK_IMHTML(imhtml)->arrow_cursor;
684 if (GTK_IMHTML(imhtml)->tip_timer)
685 g_source_remove(GTK_IMHTML(imhtml)->tip_timer);
686 GTK_IMHTML(imhtml)->tip_timer = 0;
689 /* If we don't have a tip from a URL, let's see if we have a tip from a smiley */
690 anchor = gtk_text_iter_get_child_anchor(&iter);
691 if (anchor) {
692 tip = g_object_get_data(G_OBJECT(anchor), "gtkimhtml_tiptext");
693 hand = FALSE;
696 if (tip && *tip) {
697 GTK_IMHTML(imhtml)->tip_timer = g_timeout_add (TOOLTIP_TIMEOUT,
698 gtk_imhtml_tip, imhtml);
699 } else if (!tip) {
700 hand = FALSE;
701 for (templist = tags; templist; templist = templist->next) {
702 tag = templist->data;
703 if ((tip = g_object_get_data(G_OBJECT(tag), "cursor"))) {
704 hand = TRUE;
705 break;
710 if (hand && !(GTK_IMHTML(imhtml)->editable))
711 cursor = GTK_IMHTML(imhtml)->hand_cursor;
713 if (cursor)
714 gdk_window_set_cursor(win, cursor);
716 GTK_IMHTML(imhtml)->tip = tip;
717 g_slist_free(tags);
718 return FALSE;
721 static gboolean
722 gtk_enter_event_notify(GtkWidget *imhtml, GdkEventCrossing *event, gpointer data)
724 if (GTK_IMHTML(imhtml)->editable)
725 gdk_window_set_cursor(
726 gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml),
727 GTK_TEXT_WINDOW_TEXT),
728 GTK_IMHTML(imhtml)->text_cursor);
729 else
730 gdk_window_set_cursor(
731 gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml),
732 GTK_TEXT_WINDOW_TEXT),
733 GTK_IMHTML(imhtml)->arrow_cursor);
735 /* propagate the event normally */
736 return FALSE;
739 static gboolean
740 gtk_leave_event_notify(GtkWidget *imhtml, GdkEventCrossing *event, gpointer data)
742 /* when leaving the widget, clear any current & pending tooltips and restore the cursor */
743 if (GTK_IMHTML(imhtml)->prelit_tag) {
744 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml), GTK_IMHTML(imhtml)->prelit_tag);
745 GTK_IMHTML(imhtml)->prelit_tag = NULL;
748 if (GTK_IMHTML(imhtml)->tip_window) {
749 gtk_widget_destroy(GTK_IMHTML(imhtml)->tip_window);
750 GTK_IMHTML(imhtml)->tip_window = NULL;
752 if (GTK_IMHTML(imhtml)->tip_timer) {
753 g_source_remove(GTK_IMHTML(imhtml)->tip_timer);
754 GTK_IMHTML(imhtml)->tip_timer = 0;
756 gdk_window_set_cursor(
757 gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml),
758 GTK_TEXT_WINDOW_TEXT), NULL);
760 /* propagate the event normally */
761 return FALSE;
764 static gint
765 gtk_imhtml_expose_event (GtkWidget *widget,
766 GdkEventExpose *event)
768 GtkTextIter start, end, cur;
769 int buf_x, buf_y;
770 GdkRectangle visible_rect;
771 cairo_t *cr = gdk_cairo_create(GDK_DRAWABLE(event->window));
772 GdkColor gcolor;
774 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget), &visible_rect);
775 gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget),
776 GTK_TEXT_WINDOW_TEXT,
777 visible_rect.x,
778 visible_rect.y,
779 &visible_rect.x,
780 &visible_rect.y);
782 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT,
783 event->area.x, event->area.y, &buf_x, &buf_y);
785 if (GTK_IMHTML(widget)->editable || GTK_IMHTML(widget)->wbfo) {
787 if (GTK_IMHTML(widget)->edit.background) {
788 gdk_color_parse(GTK_IMHTML(widget)->edit.background, &gcolor);
789 gdk_cairo_set_source_color(cr, &gcolor);
790 } else {
791 gdk_cairo_set_source_color(cr, &(widget->style->base[GTK_WIDGET_STATE(widget)]));
794 cairo_rectangle(cr,
795 visible_rect.x, visible_rect.y,
796 visible_rect.width, visible_rect.height);
797 cairo_fill(cr);
798 cairo_destroy(cr);
800 if (GTK_WIDGET_CLASS (parent_class)->expose_event)
801 return (* GTK_WIDGET_CLASS (parent_class)->expose_event)
802 (widget, event);
803 return FALSE;
807 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &start, buf_x, buf_y);
808 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &end,
809 buf_x + event->area.width, buf_y + event->area.height);
811 gtk_text_iter_order(&start, &end);
813 cur = start;
815 while (gtk_text_iter_in_range(&cur, &start, &end)) {
816 GSList *tags = gtk_text_iter_get_tags(&cur);
817 GSList *l;
819 for (l = tags; l; l = l->next) {
820 GtkTextTag *tag = l->data;
821 GdkRectangle rect;
822 GdkRectangle tag_area;
823 const char *color;
825 if (strncmp(tag->name, "BACKGROUND ", 11))
826 continue;
828 if (gtk_text_iter_ends_tag(&cur, tag))
829 continue;
831 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(widget), &cur, &tag_area);
832 gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget),
833 GTK_TEXT_WINDOW_TEXT,
834 tag_area.x,
835 tag_area.y,
836 &tag_area.x,
837 &tag_area.y);
838 rect.x = visible_rect.x;
839 rect.y = tag_area.y;
840 rect.width = visible_rect.width;
843 gtk_text_iter_forward_to_tag_toggle(&cur, tag);
844 while (!gtk_text_iter_is_end(&cur) && gtk_text_iter_begins_tag(&cur, tag));
846 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(widget), &cur, &tag_area);
847 gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget),
848 GTK_TEXT_WINDOW_TEXT,
849 tag_area.x,
850 tag_area.y,
851 &tag_area.x,
852 &tag_area.y);
855 rect.height = tag_area.y + tag_area.height - rect.y
856 + gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(widget))
857 + gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(widget));
859 color = tag->name + 11;
861 if (!gdk_color_parse(color, &gcolor)) {
862 gchar tmp[8];
863 tmp[0] = '#';
864 strncpy(&tmp[1], color, 7);
865 tmp[7] = '\0';
866 if (!gdk_color_parse(tmp, &gcolor))
867 gdk_color_parse("white", &gcolor);
869 gdk_cairo_set_source_color(cr, &gcolor);
871 cairo_rectangle(cr,
872 rect.x, rect.y,
873 rect.width, rect.height);
874 cairo_fill(cr);
875 gtk_text_iter_backward_char(&cur); /* go back one, in case the end is the begining is the end
876 * note that above, we always moved cur ahead by at least
877 * one character */
878 break;
881 g_slist_free(tags);
883 /* loop until another tag begins, or no tag begins */
884 while (gtk_text_iter_forward_to_tag_toggle(&cur, NULL) &&
885 !gtk_text_iter_is_end(&cur) &&
886 !gtk_text_iter_begins_tag(&cur, NULL));
889 cairo_destroy(cr);
891 if (GTK_WIDGET_CLASS (parent_class)->expose_event)
892 return (* GTK_WIDGET_CLASS (parent_class)->expose_event)
893 (widget, event);
895 return FALSE;
899 static void paste_unformatted_cb(GtkMenuItem *menu, GtkIMHtml *imhtml)
901 GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD);
903 gtk_clipboard_request_text(clipboard, paste_plaintext_received_cb, imhtml);
907 static void clear_formatting_cb(GtkMenuItem *menu, GtkIMHtml *imhtml)
909 gtk_imhtml_clear_formatting(imhtml);
912 static void disable_smiley_selected(GtkMenuItem *item, GtkIMHtml *imhtml)
914 GtkTextIter start, end;
915 GtkTextMark *mark;
916 char *text;
918 if (!gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end))
919 return;
921 text = gtk_imhtml_get_markup_range(imhtml, &start, &end);
923 mark = gtk_text_buffer_get_selection_bound(imhtml->text_buffer);
924 gtk_text_buffer_delete_selection(imhtml->text_buffer, FALSE, FALSE);
926 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &start, mark);
927 gtk_imhtml_insert_html_at_iter(imhtml, text, GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_SMILEY, &start);
929 g_free(text);
932 static void hijack_menu_cb(GtkIMHtml *imhtml, GtkMenu *menu, gpointer data)
934 GtkWidget *menuitem;
935 GtkTextIter start, end;
937 menuitem = gtk_menu_item_new_with_mnemonic(_("Paste as Plain _Text"));
938 gtk_widget_show(menuitem);
940 * TODO: gtk_clipboard_wait_is_text_available() iterates the glib
941 * mainloop, which tends to be a source of bugs. It would
942 * be good to audit this or change it to not wait.
944 gtk_widget_set_sensitive(menuitem,
945 (imhtml->editable &&
946 gtk_clipboard_wait_is_text_available(
947 gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD))));
948 /* put it after "Paste" */
949 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menuitem, 3);
951 g_signal_connect(G_OBJECT(menuitem), "activate",
952 G_CALLBACK(paste_unformatted_cb), imhtml);
954 menuitem = gtk_menu_item_new_with_mnemonic(_("_Reset formatting"));
955 gtk_widget_show(menuitem);
956 gtk_widget_set_sensitive(menuitem, imhtml->editable);
957 /* put it after Delete */
958 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menuitem, 5);
960 g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(clear_formatting_cb), imhtml);
962 menuitem = gtk_menu_item_new_with_mnemonic(_("Disable _smileys in selected text"));
963 gtk_widget_show(menuitem);
964 if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) {
965 g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(disable_smiley_selected), imhtml);
966 } else {
967 gtk_widget_set_sensitive(menuitem, FALSE);
969 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menuitem, 6);
972 static char *
973 ucs2_order(gboolean swap)
975 gboolean be;
977 be = G_BYTE_ORDER == G_BIG_ENDIAN;
978 be = swap ? be : !be;
980 if (be)
981 return "UTF-16BE";
982 else
983 return "UTF-16LE";
987 /* Convert from UTF-16LE to UTF-8, stripping the BOM if one is present.*/
988 static gchar *
989 utf16_to_utf8_with_bom_check(gchar *data, guint len) {
990 char *fromcode = NULL;
991 GError *error = NULL;
992 guint16 c;
993 gchar *utf8_ret;
996 * Unicode Techinical Report 20
997 * ( http://www.unicode.org/unicode/reports/tr20/ ) says to treat an
998 * initial 0xfeff (ZWNBSP) as a byte order indicator so that is
999 * what we do. If there is no indicator assume it is in the default
1000 * order
1003 memcpy(&c, data, 2);
1004 switch (c) {
1005 case 0xfeff:
1006 case 0xfffe:
1007 fromcode = ucs2_order(c == 0xfeff);
1008 data += 2;
1009 len -= 2;
1010 break;
1011 default:
1012 fromcode = "UTF-16";
1013 break;
1016 utf8_ret = g_convert(data, len, "UTF-8", fromcode, NULL, NULL, &error);
1018 if (error) {
1019 purple_debug_warning("gtkimhtml", "g_convert error: %s\n", error->message);
1020 g_error_free(error);
1022 return utf8_ret;
1026 static void gtk_imhtml_clipboard_get(GtkClipboard *clipboard, GtkSelectionData *selection_data, guint info, GtkIMHtml *imhtml) {
1027 char *text = NULL;
1028 gboolean primary = (clipboard != clipboard_selection);
1029 GtkTextIter start, end;
1031 if (primary) {
1032 GtkTextMark *sel = NULL, *ins = NULL;
1034 g_return_if_fail(imhtml != NULL);
1036 ins = gtk_text_buffer_get_insert(imhtml->text_buffer);
1037 sel = gtk_text_buffer_get_selection_bound(imhtml->text_buffer);
1038 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &start, sel);
1039 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &end, ins);
1042 if (info == TARGET_HTML) {
1043 char *selection;
1044 #ifndef _WIN32
1045 gsize len;
1046 if (primary) {
1047 text = gtk_imhtml_get_markup_range(imhtml, &start, &end);
1048 } else
1049 text = html_clipboard;
1051 /* Mozilla asks that we start our text/html with the Unicode byte order mark */
1052 selection = g_convert(text, -1, "UTF-16", "UTF-8", NULL, &len, NULL);
1053 gtk_selection_data_set(selection_data, gdk_atom_intern("text/html", FALSE), 16, (const guchar *)selection, len);
1054 #else
1055 selection = clipboard_html_to_win32(html_clipboard);
1056 gtk_selection_data_set(selection_data, gdk_atom_intern("HTML Format", FALSE), 8, (const guchar *)selection, strlen(selection));
1057 #endif
1058 g_free(selection);
1059 } else {
1060 if (primary) {
1061 text = gtk_imhtml_get_text(imhtml, &start, &end);
1062 } else
1063 text = text_clipboard;
1064 gtk_selection_data_set_text(selection_data, text, strlen(text));
1066 if (primary) /* This was allocated here */
1067 g_free(text);
1070 static void gtk_imhtml_primary_clipboard_clear(GtkClipboard *clipboard, GtkIMHtml *imhtml)
1072 GtkTextIter insert;
1073 GtkTextIter selection_bound;
1075 gtk_text_buffer_get_iter_at_mark (imhtml->text_buffer, &insert,
1076 gtk_text_buffer_get_mark (imhtml->text_buffer, "insert"));
1077 gtk_text_buffer_get_iter_at_mark (imhtml->text_buffer, &selection_bound,
1078 gtk_text_buffer_get_mark (imhtml->text_buffer, "selection_bound"));
1080 if (!gtk_text_iter_equal (&insert, &selection_bound))
1081 gtk_text_buffer_move_mark (imhtml->text_buffer,
1082 gtk_text_buffer_get_mark (imhtml->text_buffer, "selection_bound"),
1083 &insert);
1086 static void gtk_imhtml_clipboard_clear (GtkClipboard *clipboard, GtkSelectionData *sel_data,
1087 guint info, gpointer user_data_or_owner)
1091 static void copy_clipboard_cb(GtkIMHtml *imhtml, gpointer unused)
1093 GtkTextIter start, end;
1094 if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) {
1095 if (!clipboard_selection)
1096 clipboard_selection = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD);
1097 gtk_clipboard_set_with_data(clipboard_selection,
1098 selection_targets, sizeof(selection_targets) / sizeof(GtkTargetEntry),
1099 (GtkClipboardGetFunc)gtk_imhtml_clipboard_get,
1100 (GtkClipboardClearFunc)gtk_imhtml_clipboard_clear, NULL);
1102 g_free(html_clipboard);
1103 g_free(text_clipboard);
1105 html_clipboard = gtk_imhtml_get_markup_range(imhtml, &start, &end);
1106 text_clipboard = gtk_imhtml_get_text(imhtml, &start, &end);
1109 g_signal_stop_emission_by_name(imhtml, "copy-clipboard");
1112 static void cut_clipboard_cb(GtkIMHtml *imhtml, gpointer unused)
1114 GtkTextIter start, end;
1115 if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) {
1116 if (!clipboard_selection)
1117 clipboard_selection = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD);
1118 gtk_clipboard_set_with_data(clipboard_selection,
1119 selection_targets, sizeof(selection_targets) / sizeof(GtkTargetEntry),
1120 (GtkClipboardGetFunc)gtk_imhtml_clipboard_get,
1121 (GtkClipboardClearFunc)gtk_imhtml_clipboard_clear, NULL);
1123 g_free(html_clipboard);
1124 g_free(text_clipboard);
1126 html_clipboard = gtk_imhtml_get_markup_range(imhtml, &start, &end);
1127 text_clipboard = gtk_imhtml_get_text(imhtml, &start, &end);
1129 if (imhtml->editable)
1130 gtk_text_buffer_delete_selection(imhtml->text_buffer, FALSE, FALSE);
1133 g_signal_stop_emission_by_name(imhtml, "cut-clipboard");
1136 static void imhtml_paste_insert(GtkIMHtml *imhtml, const char *text, gboolean plaintext)
1138 GtkTextIter iter;
1139 GtkIMHtmlOptions flags = plaintext ? GTK_IMHTML_NO_SMILEY : (GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_COMMENTS);
1141 /* Delete any currently selected text */
1142 gtk_text_buffer_delete_selection(imhtml->text_buffer, TRUE, TRUE);
1144 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, gtk_text_buffer_get_insert(imhtml->text_buffer));
1145 if (!imhtml->wbfo && !plaintext)
1146 gtk_imhtml_close_tags(imhtml, &iter);
1148 gtk_imhtml_insert_html_at_iter(imhtml, text, flags, &iter);
1149 gtk_text_buffer_move_mark_by_name(imhtml->text_buffer, "insert", &iter);
1150 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(imhtml), gtk_text_buffer_get_insert(imhtml->text_buffer),
1151 0, FALSE, 0.0, 0.0);
1152 if (!imhtml->wbfo && !plaintext)
1153 gtk_imhtml_close_tags(imhtml, &iter);
1157 static void paste_plaintext_received_cb (GtkClipboard *clipboard, const gchar *text, gpointer data)
1159 char *tmp;
1161 if (text == NULL || !(*text))
1162 return;
1164 tmp = g_markup_escape_text(text, -1);
1165 imhtml_paste_insert(data, tmp, TRUE);
1166 g_free(tmp);
1169 static void paste_received_cb (GtkClipboard *clipboard, GtkSelectionData *selection_data, gpointer data)
1171 char *text;
1172 GtkIMHtml *imhtml = data;
1174 if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml)))
1175 return;
1177 if (imhtml->wbfo || selection_data->length <= 0) {
1178 gtk_clipboard_request_text(clipboard, paste_plaintext_received_cb, imhtml);
1179 return;
1180 } else {
1181 #if 0
1182 /* Here's some debug code, for figuring out what sent to us over the clipboard. */
1184 int i;
1186 purple_debug_misc("gtkimhtml", "In paste_received_cb():\n\tformat = %d, length = %d\n\t",
1187 selection_data->format, selection_data->length);
1189 for (i = 0; i < (/*(selection_data->format / 8) **/ selection_data->length); i++) {
1190 if ((i % 70) == 0)
1191 printf("\n\t");
1192 if (selection_data->data[i] == '\0')
1193 printf(".");
1194 else
1195 printf("%c", selection_data->data[i]);
1197 printf("\n");
1199 #endif
1201 text = g_malloc(selection_data->length + 1);
1202 memcpy(text, selection_data->data, selection_data->length);
1203 /* Make sure the paste data is null-terminated. Given that
1204 * we're passed length (but assume later that it is
1205 * null-terminated), this seems sensible to me.
1207 text[selection_data->length] = '\0';
1210 #ifdef _WIN32
1211 if (gtk_selection_data_get_data_type(selection_data) == gdk_atom_intern("HTML Format", FALSE)) {
1212 char *tmp = clipboard_win32_to_html(text);
1213 g_free(text);
1214 text = tmp;
1216 #endif
1218 if (selection_data->length >= 2 &&
1219 (*(guint16 *)text == 0xfeff || *(guint16 *)text == 0xfffe)) {
1220 /* This is UTF-16 */
1221 char *utf8 = utf16_to_utf8_with_bom_check(text, selection_data->length);
1222 g_free(text);
1223 text = utf8;
1224 if (!text) {
1225 purple_debug_warning("gtkimhtml", "g_convert from UTF-16 failed in paste_received_cb\n");
1226 return;
1230 if (!(*text) || !g_utf8_validate(text, -1, NULL)) {
1231 purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in paste_received_cb\n");
1232 g_free(text);
1233 return;
1236 imhtml_paste_insert(imhtml, text, FALSE);
1237 g_free(text);
1241 static void smart_backspace_cb(GtkIMHtml *imhtml, gpointer blah)
1243 GtkTextIter iter;
1244 GtkTextChildAnchor* anchor;
1245 char * text;
1246 gint offset;
1248 if (!imhtml->editable)
1249 return;
1251 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, gtk_text_buffer_get_insert(imhtml->text_buffer));
1253 /* Get the character before the insertion point */
1254 offset = gtk_text_iter_get_offset(&iter);
1255 if (offset <= 0)
1256 return;
1258 gtk_text_iter_backward_char(&iter);
1259 anchor = gtk_text_iter_get_child_anchor(&iter);
1261 if (!anchor)
1262 return; /* No smiley here */
1264 text = g_object_get_data(G_OBJECT(anchor), "gtkimhtml_plaintext");
1265 if (!text)
1266 return;
1268 /* ok, then we need to insert the image buffer text before the anchor */
1269 gtk_text_buffer_insert(imhtml->text_buffer, &iter, text, -1);
1272 static void paste_clipboard_cb(GtkIMHtml *imhtml, gpointer blah)
1274 #ifdef _WIN32
1275 /* If we're on windows, let's see if we can get data from the HTML Format
1276 clipboard before we try to paste from the GTK buffer */
1277 if (!clipboard_paste_html_win32(imhtml) && gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml))) {
1278 GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD);
1279 gtk_clipboard_request_text(clipboard, paste_plaintext_received_cb, imhtml);
1282 #else
1283 GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD);
1284 gtk_clipboard_request_contents(clipboard, gdk_atom_intern("text/html", FALSE),
1285 paste_received_cb, imhtml);
1286 #endif
1287 g_signal_stop_emission_by_name(imhtml, "paste-clipboard");
1290 static void imhtml_realized_remove_primary(GtkIMHtml *imhtml, gpointer unused)
1292 gtk_text_buffer_remove_selection_clipboard(GTK_IMHTML(imhtml)->text_buffer,
1293 gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY));
1297 static void imhtml_destroy_add_primary(GtkIMHtml *imhtml, gpointer unused)
1299 gtk_text_buffer_add_selection_clipboard(GTK_IMHTML(imhtml)->text_buffer,
1300 gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY));
1303 static void mark_set_so_update_selection_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextMark *mark, GtkIMHtml *imhtml)
1305 if (gtk_text_buffer_get_selection_bounds(buffer, NULL, NULL)) {
1306 gtk_clipboard_set_with_owner(gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY),
1307 selection_targets, sizeof(selection_targets) / sizeof(GtkTargetEntry),
1308 (GtkClipboardGetFunc)gtk_imhtml_clipboard_get,
1309 (GtkClipboardClearFunc)gtk_imhtml_primary_clipboard_clear, G_OBJECT(imhtml));
1313 static gboolean gtk_imhtml_button_press_event(GtkIMHtml *imhtml, GdkEventButton *event, gpointer unused)
1315 if (event->button == 2) {
1316 int x, y;
1317 GtkTextIter iter;
1318 GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY);
1320 if (!imhtml->editable)
1321 return FALSE;
1323 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(imhtml),
1324 GTK_TEXT_WINDOW_TEXT,
1325 event->x,
1326 event->y,
1328 &y);
1329 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml), &iter, x, y);
1330 gtk_text_buffer_place_cursor(imhtml->text_buffer, &iter);
1332 gtk_clipboard_request_contents(clipboard, gdk_atom_intern("text/html", FALSE),
1333 paste_received_cb, imhtml);
1335 return TRUE;
1338 return FALSE;
1341 static void
1342 gtk_imhtml_undo(GtkIMHtml *imhtml)
1344 g_return_if_fail(GTK_IS_IMHTML(imhtml));
1345 if (imhtml->editable &&
1346 gtk_source_undo_manager_can_undo(imhtml->undo_manager))
1347 gtk_source_undo_manager_undo(imhtml->undo_manager);
1350 static void
1351 gtk_imhtml_redo(GtkIMHtml *imhtml)
1353 g_return_if_fail(GTK_IS_IMHTML(imhtml));
1354 if (imhtml->editable &&
1355 gtk_source_undo_manager_can_redo(imhtml->undo_manager))
1356 gtk_source_undo_manager_redo(imhtml->undo_manager);
1360 static gboolean imhtml_message_send(GtkIMHtml *imhtml)
1362 return FALSE;
1365 static void
1366 imhtml_paste_cb(GtkIMHtml *imhtml, const char *str)
1368 if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml)))
1369 return;
1371 if (!str || !*str || !strcmp(str, "html"))
1372 g_signal_emit_by_name(imhtml, "paste_clipboard");
1373 else if (!strcmp(str, "text"))
1374 paste_unformatted_cb(NULL, imhtml);
1377 static void imhtml_toggle_format(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons)
1379 /* since this function is the handler for the formatting keystrokes,
1380 we need to check here that the formatting attempted is permitted */
1381 buttons &= imhtml->format_functions;
1383 switch (buttons) {
1384 case GTK_IMHTML_BOLD:
1385 imhtml_toggle_bold(imhtml);
1386 break;
1387 case GTK_IMHTML_ITALIC:
1388 imhtml_toggle_italic(imhtml);
1389 break;
1390 case GTK_IMHTML_UNDERLINE:
1391 imhtml_toggle_underline(imhtml);
1392 break;
1393 case GTK_IMHTML_STRIKE:
1394 imhtml_toggle_strike(imhtml);
1395 break;
1396 case GTK_IMHTML_SHRINK:
1397 imhtml_font_shrink(imhtml);
1398 break;
1399 case GTK_IMHTML_GROW:
1400 imhtml_font_grow(imhtml);
1401 break;
1402 default:
1403 break;
1407 static void
1408 gtk_imhtml_finalize (GObject *object)
1410 GtkIMHtml *imhtml = GTK_IMHTML(object);
1411 GList *scalables;
1412 GSList *l;
1414 if (imhtml->scroll_src)
1415 g_source_remove(imhtml->scroll_src);
1416 if (imhtml->scroll_time)
1417 g_timer_destroy(imhtml->scroll_time);
1419 g_hash_table_destroy(imhtml->smiley_data);
1420 gtk_smiley_tree_destroy(imhtml->default_smilies);
1421 gdk_cursor_unref(imhtml->hand_cursor);
1422 gdk_cursor_unref(imhtml->arrow_cursor);
1423 gdk_cursor_unref(imhtml->text_cursor);
1425 if(imhtml->tip_window){
1426 gtk_widget_destroy(imhtml->tip_window);
1428 if(imhtml->tip_timer)
1429 g_source_remove(imhtml->tip_timer);
1431 for(scalables = imhtml->scalables; scalables; scalables = scalables->next) {
1432 struct scalable_data *sd = scalables->data;
1433 GtkIMHtmlScalable *scale = GTK_IMHTML_SCALABLE(sd->scalable);
1434 scale->free(scale);
1435 g_free(sd);
1438 for (l = imhtml->im_images; l; l = l->next) {
1439 struct im_image_data *img_data = l->data;
1440 if (imhtml->funcs->image_unref)
1441 imhtml->funcs->image_unref(img_data->id);
1442 g_free(img_data);
1445 g_list_free(imhtml->scalables);
1446 g_slist_free(imhtml->im_images);
1447 g_queue_free(imhtml->animations);
1448 g_free(imhtml->protocol_name);
1449 g_free(imhtml->search_string);
1450 g_object_unref(imhtml->undo_manager);
1451 G_OBJECT_CLASS(parent_class)->finalize (object);
1455 static GtkIMHtmlProtocol *
1456 imhtml_find_protocol(const char *url, gboolean reverse)
1458 GtkIMHtmlClass *klass;
1459 GList *iter;
1460 GtkIMHtmlProtocol *proto = NULL;
1461 int length = reverse ? strlen(url) : -1;
1463 klass = g_type_class_ref(GTK_TYPE_IMHTML);
1464 for (iter = klass->protocols; iter; iter = iter->next) {
1465 proto = iter->data;
1466 if (g_ascii_strncasecmp(url, proto->name, reverse ? MIN(length, proto->length) : proto->length) == 0) {
1467 return proto;
1470 return NULL;
1473 static void
1474 imhtml_url_clicked(GtkIMHtml *imhtml, const char *url)
1476 GtkIMHtmlProtocol *proto = imhtml_find_protocol(url, FALSE);
1477 GtkIMHtmlLink *link;
1478 if (!proto)
1479 return;
1480 link = g_new0(GtkIMHtmlLink, 1);
1481 link->imhtml = g_object_ref(imhtml);
1482 link->url = g_strdup(url);
1483 proto->activate(imhtml, link); /* XXX: Do something with the return value? */
1484 gtk_imhtml_link_destroy(link);
1487 /* Boring GTK+ stuff */
1488 static void gtk_imhtml_class_init (GtkIMHtmlClass *klass)
1490 GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
1491 GtkBindingSet *binding_set;
1492 GObjectClass *gobject_class;
1493 gobject_class = (GObjectClass*) klass;
1494 parent_class = g_type_class_ref(GTK_TYPE_TEXT_VIEW);
1495 signals[URL_CLICKED] = g_signal_new("url_clicked",
1496 G_TYPE_FROM_CLASS(gobject_class),
1497 G_SIGNAL_RUN_FIRST,
1498 G_STRUCT_OFFSET(GtkIMHtmlClass, url_clicked),
1499 NULL,
1501 g_cclosure_marshal_VOID__POINTER,
1502 G_TYPE_NONE, 1,
1503 G_TYPE_POINTER);
1504 signals[BUTTONS_UPDATE] = g_signal_new("format_buttons_update",
1505 G_TYPE_FROM_CLASS(gobject_class),
1506 G_SIGNAL_RUN_FIRST,
1507 G_STRUCT_OFFSET(GtkIMHtmlClass, buttons_update),
1508 NULL,
1510 g_cclosure_marshal_VOID__INT,
1511 G_TYPE_NONE, 1,
1512 G_TYPE_INT);
1513 signals[TOGGLE_FORMAT] = g_signal_new("format_function_toggle",
1514 G_TYPE_FROM_CLASS(gobject_class),
1515 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1516 G_STRUCT_OFFSET(GtkIMHtmlClass, toggle_format),
1517 NULL,
1519 g_cclosure_marshal_VOID__INT,
1520 G_TYPE_NONE, 1,
1521 G_TYPE_INT);
1522 signals[CLEAR_FORMAT] = g_signal_new("format_function_clear",
1523 G_TYPE_FROM_CLASS(gobject_class),
1524 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
1525 G_STRUCT_OFFSET(GtkIMHtmlClass, clear_format),
1526 NULL,
1528 g_cclosure_marshal_VOID__VOID,
1529 G_TYPE_NONE, 0);
1530 signals[UPDATE_FORMAT] = g_signal_new("format_function_update",
1531 G_TYPE_FROM_CLASS(gobject_class),
1532 G_SIGNAL_RUN_FIRST,
1533 G_STRUCT_OFFSET(GtkIMHtmlClass, update_format),
1534 NULL,
1536 g_cclosure_marshal_VOID__VOID,
1537 G_TYPE_NONE, 0);
1538 signals[MESSAGE_SEND] = g_signal_new("message_send",
1539 G_TYPE_FROM_CLASS(gobject_class),
1540 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1541 G_STRUCT_OFFSET(GtkIMHtmlClass, message_send),
1542 NULL,
1543 0, g_cclosure_marshal_VOID__VOID,
1544 G_TYPE_NONE, 0);
1545 signals[PASTE] = g_signal_new("paste",
1546 G_TYPE_FROM_CLASS(gobject_class),
1547 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1549 NULL,
1550 0, g_cclosure_marshal_VOID__STRING,
1551 G_TYPE_NONE, 1, G_TYPE_STRING);
1552 signals [UNDO] = g_signal_new ("undo",
1553 G_TYPE_FROM_CLASS (klass),
1554 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1555 G_STRUCT_OFFSET (GtkIMHtmlClass, undo),
1556 NULL,
1557 NULL,
1558 gtksourceview_marshal_VOID__VOID,
1559 G_TYPE_NONE,
1561 signals [REDO] = g_signal_new ("redo",
1562 G_TYPE_FROM_CLASS (klass),
1563 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1564 G_STRUCT_OFFSET (GtkIMHtmlClass, redo),
1565 NULL,
1566 NULL,
1567 gtksourceview_marshal_VOID__VOID,
1568 G_TYPE_NONE,
1573 klass->toggle_format = imhtml_toggle_format;
1574 klass->message_send = imhtml_message_send;
1575 klass->clear_format = imhtml_clear_formatting;
1576 klass->url_clicked = imhtml_url_clicked;
1577 klass->undo = gtk_imhtml_undo;
1578 klass->redo = gtk_imhtml_redo;
1580 gobject_class->finalize = gtk_imhtml_finalize;
1581 widget_class->drag_motion = gtk_text_view_drag_motion;
1582 widget_class->expose_event = gtk_imhtml_expose_event;
1583 parent_size_allocate = widget_class->size_allocate;
1584 widget_class->size_allocate = gtk_imhtml_size_allocate;
1585 parent_style_set = widget_class->style_set;
1586 widget_class->style_set = gtk_imhtml_style_set;
1588 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("hyperlink-color",
1589 _("Hyperlink color"),
1590 _("Color to draw hyperlinks."),
1591 GDK_TYPE_COLOR, G_PARAM_READABLE));
1592 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("hyperlink-visited-color",
1593 _("Hyperlink visited color"),
1594 _("Color to draw hyperlink after it has been visited (or activated)."),
1595 GDK_TYPE_COLOR, G_PARAM_READABLE));
1596 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("hyperlink-prelight-color",
1597 _("Hyperlink prelight color"),
1598 _("Color to draw hyperlinks when mouse is over them."),
1599 GDK_TYPE_COLOR, G_PARAM_READABLE));
1600 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("send-name-color",
1601 _("Sent Message Name Color"),
1602 _("Color to draw the name of a message you sent."),
1603 GDK_TYPE_COLOR, G_PARAM_READABLE));
1604 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("receive-name-color",
1605 _("Received Message Name Color"),
1606 _("Color to draw the name of a message you received."),
1607 GDK_TYPE_COLOR, G_PARAM_READABLE));
1608 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("highlight-name-color",
1609 _("\"Attention\" Name Color"),
1610 _("Color to draw the name of a message you received containing your name."),
1611 GDK_TYPE_COLOR, G_PARAM_READABLE));
1612 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("action-name-color",
1613 _("Action Message Name Color"),
1614 _("Color to draw the name of an action message."),
1615 GDK_TYPE_COLOR, G_PARAM_READABLE));
1616 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("whisper-action-name-color",
1617 _("Action Message Name Color for Whispered Message"),
1618 _("Color to draw the name of a whispered action message."),
1619 GDK_TYPE_COLOR, G_PARAM_READABLE));
1620 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("whisper-name-color",
1621 _("Whisper Message Name Color"),
1622 _("Color to draw the name of a whispered message."),
1623 GDK_TYPE_COLOR, G_PARAM_READABLE));
1625 /* Customizable typing notification ... sort of. Example:
1626 * GtkIMHtml::typing-notification-font = "monospace italic light 8.0"
1627 * GtkIMHtml::typing-notification-color = "#ff0000"
1628 * GtkIMHtml::typing-notification-enable = 1
1630 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("typing-notification-color",
1631 _("Typing notification color"),
1632 _("The color to use for the typing notification"),
1633 GDK_TYPE_COLOR, G_PARAM_READABLE));
1634 gtk_widget_class_install_style_property(widget_class, g_param_spec_string("typing-notification-font",
1635 _("Typing notification font"),
1636 _("The font to use for the typing notification"),
1637 "light 8.0", G_PARAM_READABLE));
1638 gtk_widget_class_install_style_property(widget_class, g_param_spec_boolean("typing-notification-enable",
1639 _("Enable typing notification"),
1640 _("Enable typing notification"),
1641 TRUE, G_PARAM_READABLE));
1643 binding_set = gtk_binding_set_by_class (parent_class);
1644 gtk_binding_entry_add_signal (binding_set, GDK_b, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_BOLD);
1645 gtk_binding_entry_add_signal (binding_set, GDK_i, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_ITALIC);
1646 gtk_binding_entry_add_signal (binding_set, GDK_u, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_UNDERLINE);
1647 gtk_binding_entry_add_signal (binding_set, GDK_plus, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_GROW);
1648 gtk_binding_entry_add_signal (binding_set, GDK_equal, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_GROW);
1649 gtk_binding_entry_add_signal (binding_set, GDK_minus, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_SHRINK);
1650 binding_set = gtk_binding_set_by_class(klass);
1651 gtk_binding_entry_add_signal (binding_set, GDK_r, GDK_CONTROL_MASK, "format_function_clear", 0);
1652 gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0, "message_send", 0);
1653 gtk_binding_entry_add_signal (binding_set, GDK_Return, 0, "message_send", 0);
1654 gtk_binding_entry_add_signal (binding_set, GDK_z, GDK_CONTROL_MASK, "undo", 0);
1655 gtk_binding_entry_add_signal (binding_set, GDK_z, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "redo", 0);
1656 gtk_binding_entry_add_signal (binding_set, GDK_F14, 0, "undo", 0);
1657 gtk_binding_entry_add_signal(binding_set, GDK_v, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "paste", 1, G_TYPE_STRING, "text");
1660 static void gtk_imhtml_init (GtkIMHtml *imhtml)
1662 imhtml->text_buffer = gtk_text_buffer_new(NULL);
1663 imhtml->undo_manager = gtk_source_undo_manager_new(imhtml->text_buffer);
1664 gtk_text_view_set_buffer(GTK_TEXT_VIEW(imhtml), imhtml->text_buffer);
1665 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml), GTK_WRAP_WORD_CHAR);
1666 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(imhtml), 2);
1667 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(imhtml), 3);
1668 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(imhtml), 2);
1669 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(imhtml), 2);
1670 /*gtk_text_view_set_indent(GTK_TEXT_VIEW(imhtml), -15);*/
1671 /*gtk_text_view_set_justification(GTK_TEXT_VIEW(imhtml), GTK_JUSTIFY_FILL);*/
1673 /* These tags will be used often and can be reused--we create them on init and then apply them by name
1674 * other tags (color, size, face, etc.) will have to be created and applied dynamically
1675 * Note that even though we created SUB, SUP, and PRE tags here, we don't really
1676 * apply them anywhere yet. */
1677 gtk_text_buffer_create_tag(imhtml->text_buffer, "BOLD", "weight", PANGO_WEIGHT_BOLD, NULL);
1678 gtk_text_buffer_create_tag(imhtml->text_buffer, "ITALICS", "style", PANGO_STYLE_ITALIC, NULL);
1679 gtk_text_buffer_create_tag(imhtml->text_buffer, "UNDERLINE", "underline", PANGO_UNDERLINE_SINGLE, NULL);
1680 gtk_text_buffer_create_tag(imhtml->text_buffer, "STRIKE", "strikethrough", TRUE, NULL);
1681 gtk_text_buffer_create_tag(imhtml->text_buffer, "SUB", "rise", -5000, NULL);
1682 gtk_text_buffer_create_tag(imhtml->text_buffer, "SUP", "rise", 5000, NULL);
1683 gtk_text_buffer_create_tag(imhtml->text_buffer, "PRE", "family", "Monospace", NULL);
1684 gtk_text_buffer_create_tag(imhtml->text_buffer, "search", "background", "#22ff00", "weight", "bold", NULL);
1685 gtk_text_buffer_create_tag(imhtml->text_buffer, "comment", "weight", PANGO_WEIGHT_NORMAL,
1686 #if FALSE && GTK_CHECK_VERSION(2,10,10)
1687 "invisible", FALSE,
1688 #endif
1689 NULL);
1691 gtk_text_buffer_create_tag(imhtml->text_buffer, "send-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1692 gtk_text_buffer_create_tag(imhtml->text_buffer, "receive-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1693 gtk_text_buffer_create_tag(imhtml->text_buffer, "highlight-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1694 gtk_text_buffer_create_tag(imhtml->text_buffer, "action-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1695 gtk_text_buffer_create_tag(imhtml->text_buffer, "whisper-action-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1696 gtk_text_buffer_create_tag(imhtml->text_buffer, "whisper-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1698 /* When hovering over a link, we show the hand cursor--elsewhere we show the plain ol' pointer cursor */
1699 imhtml->hand_cursor = gdk_cursor_new (GDK_HAND2);
1700 imhtml->arrow_cursor = gdk_cursor_new (GDK_LEFT_PTR);
1701 imhtml->text_cursor = gdk_cursor_new (GDK_XTERM);
1703 imhtml->show_comments = TRUE;
1705 imhtml->smiley_data = g_hash_table_new_full(g_str_hash, g_str_equal,
1706 g_free, (GDestroyNotify)gtk_smiley_tree_destroy);
1707 imhtml->default_smilies = gtk_smiley_tree_new();
1709 g_signal_connect(G_OBJECT(imhtml), "motion-notify-event", G_CALLBACK(gtk_motion_event_notify), NULL);
1710 g_signal_connect(G_OBJECT(imhtml), "leave-notify-event", G_CALLBACK(gtk_leave_event_notify), NULL);
1711 g_signal_connect(G_OBJECT(imhtml), "enter-notify-event", G_CALLBACK(gtk_enter_event_notify), NULL);
1712 g_signal_connect(G_OBJECT(imhtml), "button_press_event", G_CALLBACK(gtk_imhtml_button_press_event), NULL);
1713 g_signal_connect(G_OBJECT(imhtml->text_buffer), "insert-text", G_CALLBACK(preinsert_cb), imhtml);
1714 g_signal_connect(G_OBJECT(imhtml->text_buffer), "delete_range", G_CALLBACK(delete_cb), imhtml);
1715 g_signal_connect_after(G_OBJECT(imhtml->text_buffer), "insert-text", G_CALLBACK(insert_cb), imhtml);
1716 g_signal_connect_after(G_OBJECT(imhtml->text_buffer), "insert-child-anchor", G_CALLBACK(insert_ca_cb), imhtml);
1717 gtk_drag_dest_set(GTK_WIDGET(imhtml), 0,
1718 link_drag_drop_targets, sizeof(link_drag_drop_targets) / sizeof(GtkTargetEntry),
1719 GDK_ACTION_COPY);
1720 g_signal_connect(G_OBJECT(imhtml), "drag_data_received", G_CALLBACK(gtk_imhtml_link_drag_rcv_cb), imhtml);
1721 g_signal_connect(G_OBJECT(imhtml), "drag_drop", G_CALLBACK(gtk_imhtml_link_drop_cb), imhtml);
1723 g_signal_connect(G_OBJECT(imhtml), "copy-clipboard", G_CALLBACK(copy_clipboard_cb), NULL);
1724 g_signal_connect(G_OBJECT(imhtml), "cut-clipboard", G_CALLBACK(cut_clipboard_cb), NULL);
1725 g_signal_connect(G_OBJECT(imhtml), "paste-clipboard", G_CALLBACK(paste_clipboard_cb), NULL);
1726 g_signal_connect_after(G_OBJECT(imhtml), "realize", G_CALLBACK(imhtml_realized_remove_primary), NULL);
1727 g_signal_connect(G_OBJECT(imhtml), "unrealize", G_CALLBACK(imhtml_destroy_add_primary), NULL);
1728 g_signal_connect(G_OBJECT(imhtml), "paste", G_CALLBACK(imhtml_paste_cb), NULL);
1730 #ifndef _WIN32
1731 g_signal_connect_after(G_OBJECT(GTK_IMHTML(imhtml)->text_buffer), "mark-set",
1732 G_CALLBACK(mark_set_so_update_selection_cb), imhtml);
1733 #endif
1735 gtk_widget_add_events(GTK_WIDGET(imhtml),
1736 GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK);
1738 imhtml->tip = NULL;
1739 imhtml->tip_timer = 0;
1740 imhtml->tip_window = NULL;
1742 imhtml->edit.bold = FALSE;
1743 imhtml->edit.italic = FALSE;
1744 imhtml->edit.underline = FALSE;
1745 imhtml->edit.forecolor = NULL;
1746 imhtml->edit.backcolor = NULL;
1747 imhtml->edit.fontface = NULL;
1748 imhtml->edit.fontsize = 0;
1749 imhtml->edit.link = NULL;
1752 imhtml->scalables = NULL;
1753 imhtml->animations = g_queue_new();
1754 gtk_imhtml_set_editable(imhtml, FALSE);
1755 g_signal_connect(G_OBJECT(imhtml), "populate-popup",
1756 G_CALLBACK(hijack_menu_cb), NULL);
1759 GtkWidget *gtk_imhtml_new(void *a, void *b)
1761 return GTK_WIDGET(g_object_new(gtk_imhtml_get_type(), NULL));
1764 GType gtk_imhtml_get_type()
1766 static GType imhtml_type = 0;
1768 if (!imhtml_type) {
1769 static const GTypeInfo imhtml_info = {
1770 sizeof(GtkIMHtmlClass),
1771 NULL,
1772 NULL,
1773 (GClassInitFunc) gtk_imhtml_class_init,
1774 NULL,
1775 NULL,
1776 sizeof (GtkIMHtml),
1778 (GInstanceInitFunc) gtk_imhtml_init,
1779 NULL
1782 imhtml_type = g_type_register_static(gtk_text_view_get_type(),
1783 "GtkIMHtml", &imhtml_info, 0);
1786 return imhtml_type;
1789 static void gtk_imhtml_link_destroy(GtkIMHtmlLink *link)
1791 if (link->imhtml)
1792 g_object_unref(link->imhtml);
1793 if (link->tag)
1794 g_object_unref(link->tag);
1795 g_free(link->url);
1796 g_free(link);
1799 /* The callback for an event on a link tag. */
1800 static gboolean tag_event(GtkTextTag *tag, GObject *imhtml, GdkEvent *event, GtkTextIter *arg2, gpointer unused)
1802 GdkEventButton *event_button = (GdkEventButton *) event;
1803 if (GTK_IMHTML(imhtml)->editable)
1804 return FALSE;
1805 if (event->type == GDK_BUTTON_RELEASE) {
1806 if ((event_button->button == 1) || (event_button->button == 2)) {
1807 GtkTextIter start, end;
1808 /* we shouldn't open a URL if the user has selected something: */
1809 if (gtk_text_buffer_get_selection_bounds(
1810 gtk_text_iter_get_buffer(arg2), &start, &end))
1811 return FALSE;
1812 gtk_imhtml_activate_tag(GTK_IMHTML(imhtml), tag);
1813 return FALSE;
1814 } else if(event_button->button == 3) {
1815 GList *children;
1816 GtkWidget *menu;
1817 GtkIMHtmlProtocol *proto;
1818 GtkIMHtmlLink *link = g_new(GtkIMHtmlLink, 1);
1819 link->imhtml = g_object_ref(imhtml);
1820 link->url = g_strdup(g_object_get_data(G_OBJECT(tag), "link_url"));
1821 link->tag = g_object_ref(tag);
1823 /* Don't want the tooltip around if user right-clicked on link */
1824 if (GTK_IMHTML(imhtml)->tip_window) {
1825 gtk_widget_destroy(GTK_IMHTML(imhtml)->tip_window);
1826 GTK_IMHTML(imhtml)->tip_window = NULL;
1828 if (GTK_IMHTML(imhtml)->tip_timer) {
1829 g_source_remove(GTK_IMHTML(imhtml)->tip_timer);
1830 GTK_IMHTML(imhtml)->tip_timer = 0;
1832 if (GTK_IMHTML(imhtml)->editable)
1833 gdk_window_set_cursor(event_button->window, GTK_IMHTML(imhtml)->text_cursor);
1834 else
1835 gdk_window_set_cursor(event_button->window, GTK_IMHTML(imhtml)->arrow_cursor);
1836 menu = gtk_menu_new();
1837 g_object_set_data_full(G_OBJECT(menu), "x-imhtml-url-data", link,
1838 (GDestroyNotify)gtk_imhtml_link_destroy);
1840 proto = imhtml_find_protocol(link->url, FALSE);
1842 if (proto && proto->context_menu) {
1843 proto->context_menu(GTK_IMHTML(link->imhtml), link, menu);
1846 children = gtk_container_get_children(GTK_CONTAINER(menu));
1847 if (!children) {
1848 GtkWidget *item = gtk_menu_item_new_with_label(_("No actions available"));
1849 gtk_widget_show(item);
1850 gtk_widget_set_sensitive(item, FALSE);
1851 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
1852 } else {
1853 g_list_free(children);
1857 gtk_widget_show_all(menu);
1858 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
1859 event_button->button, event_button->time);
1861 return TRUE;
1864 if(event->type == GDK_BUTTON_PRESS && event_button->button == 3)
1865 return TRUE; /* Clicking the right mouse button on a link shouldn't
1866 be caught by the regular GtkTextView menu */
1867 else
1868 return FALSE; /* Let clicks go through if we didn't catch anything */
1871 static gboolean
1872 gtk_text_view_drag_motion (GtkWidget *widget,
1873 GdkDragContext *context,
1874 gint x,
1875 gint y,
1876 guint time)
1878 GdkDragAction suggested_action = 0;
1880 if (gtk_drag_dest_find_target (widget, context, NULL) == GDK_NONE) {
1881 /* can't accept any of the offered targets */
1882 } else {
1883 GtkWidget *source_widget;
1884 suggested_action = context->suggested_action;
1885 source_widget = gtk_drag_get_source_widget (context);
1886 if (source_widget == widget) {
1887 /* Default to MOVE, unless the user has
1888 * pressed ctrl or alt to affect available actions
1890 if ((context->actions & GDK_ACTION_MOVE) != 0)
1891 suggested_action = GDK_ACTION_MOVE;
1895 gdk_drag_status (context, suggested_action, time);
1897 /* TRUE return means don't propagate the drag motion to parent
1898 * widgets that may also be drop sites.
1900 return TRUE;
1903 static void
1904 gtk_imhtml_link_drop_cb(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer user_data)
1906 GdkAtom target = gtk_drag_dest_find_target (widget, context, NULL);
1908 if (target != GDK_NONE)
1909 gtk_drag_get_data (widget, context, target, time);
1910 else
1911 gtk_drag_finish (context, FALSE, FALSE, time);
1913 return;
1916 static void
1917 gtk_imhtml_link_drag_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y,
1918 GtkSelectionData *sd, guint info, guint t, GtkIMHtml *imhtml)
1920 gchar **links;
1921 gchar *link;
1922 char *text = (char *)sd->data;
1923 GtkTextMark *mark = gtk_text_buffer_get_insert(imhtml->text_buffer);
1924 GtkTextIter iter;
1925 gint i = 0;
1927 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, mark);
1929 if(gtk_imhtml_get_editable(imhtml) && sd->data){
1930 switch (info) {
1931 case GTK_IMHTML_DRAG_URL:
1932 /* TODO: Is it really ok to change sd->data...? */
1933 purple_str_strip_char((char *)sd->data, '\r');
1935 links = g_strsplit((char *)sd->data, "\n", 0);
1936 while((link = links[i]) != NULL){
1937 if (gtk_imhtml_is_protocol(link)) {
1938 gchar *label;
1940 if(links[i + 1])
1941 i++;
1943 label = links[i];
1945 gtk_imhtml_insert_link(imhtml, mark, link, label);
1946 } else if (*link == '\0') {
1947 /* Ignore blank lines */
1948 } else {
1949 /* Special reasons, aka images being put in via other tag, etc. */
1950 /* ... don't pretend we handled it if we didn't */
1951 gtk_drag_finish(dc, FALSE, FALSE, t);
1952 g_strfreev(links);
1953 return;
1956 i++;
1958 g_strfreev(links);
1959 break;
1960 case GTK_IMHTML_DRAG_HTML:
1962 char *utf8 = NULL;
1963 /* Ewww. This is all because mozilla thinks that text/html is 'for internal use only.'
1964 * as explained by this comment in gtkhtml:
1966 * FIXME This hack decides the charset of the selection. It seems that
1967 * mozilla/netscape alway use ucs2 for text/html
1968 * and openoffice.org seems to always use utf8 so we try to validate
1969 * the string as utf8 and if that fails we assume it is ucs2
1971 * See also the comment on text/html here:
1972 * http://mail.gnome.org/archives/gtk-devel-list/2001-September/msg00114.html
1974 if (sd->length >= 2 && !g_utf8_validate(text, sd->length - 1, NULL)) {
1975 utf8 = utf16_to_utf8_with_bom_check(text, sd->length);
1977 if (!utf8) {
1978 purple_debug_warning("gtkimhtml", "g_convert from UTF-16 failed in drag_rcv_cb\n");
1979 return;
1981 } else if (!(*text) || !g_utf8_validate(text, -1, NULL)) {
1982 purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in drag_rcv_cb\n");
1983 return;
1986 gtk_imhtml_insert_html_at_iter(imhtml, utf8 ? utf8 : text, 0, &iter);
1987 g_free(utf8);
1988 break;
1990 case GTK_IMHTML_DRAG_TEXT:
1991 if (!(*text) || !g_utf8_validate(text, -1, NULL)) {
1992 purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in drag_rcv_cb\n");
1993 return;
1994 } else {
1995 char *tmp = g_markup_escape_text(text, -1);
1996 gtk_imhtml_insert_html_at_iter(imhtml, tmp, 0, &iter);
1997 g_free(tmp);
1999 break;
2000 default:
2001 gtk_drag_finish(dc, FALSE, FALSE, t);
2002 return;
2004 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
2005 } else {
2006 gtk_drag_finish(dc, FALSE, FALSE, t);
2010 static void gtk_smiley_tree_remove (GtkSmileyTree *tree,
2011 GtkIMHtmlSmiley *smiley)
2013 GtkSmileyTree *t = tree;
2014 const gchar *x = smiley->smile;
2015 gint len = 0;
2017 while (*x) {
2018 gchar *pos;
2020 if (!t->values)
2021 return;
2023 pos = strchr (t->values->str, *x);
2024 if (pos)
2025 t = t->children [pos - t->values->str];
2026 else
2027 return;
2029 x++; len++;
2032 if (t->image) {
2033 t->image = NULL;
2037 static gint
2038 gtk_smiley_tree_lookup (GtkSmileyTree *tree,
2039 const gchar *text)
2041 GtkSmileyTree *t = tree;
2042 const gchar *x = text;
2043 gint len = 0;
2044 const gchar *amp;
2045 gint alen;
2047 while (*x) {
2048 gchar *pos;
2050 if (!t->values)
2051 break;
2053 if(*x == '&' && (amp = purple_markup_unescape_entity(x, &alen))) {
2054 gboolean matched = TRUE;
2055 /* Make sure all chars of the unescaped value match */
2056 while (*(amp + 1)) {
2057 pos = strchr (t->values->str, *amp);
2058 if (pos)
2059 t = t->children [GPOINTER_TO_INT(pos) - GPOINTER_TO_INT(t->values->str)];
2060 else {
2061 matched = FALSE;
2062 break;
2064 amp++;
2066 if (!matched)
2067 break;
2069 pos = strchr (t->values->str, *amp);
2071 else if (*x == '<') /* Because we're all WYSIWYG now, a '<'
2072 * char should only appear as the start of a tag. Perhaps a safer (but costlier)
2073 * check would be to call gtk_imhtml_is_tag on it */
2074 break;
2075 else {
2076 alen = 1;
2077 pos = strchr (t->values->str, *x);
2080 if (pos)
2081 t = t->children [GPOINTER_TO_INT(pos) - GPOINTER_TO_INT(t->values->str)];
2082 else
2083 break;
2085 x += alen;
2086 len += alen;
2089 if (t->image)
2090 return len;
2092 return 0;
2095 static void
2096 gtk_imhtml_disassociate_smiley_foreach(gpointer key, gpointer value,
2097 gpointer user_data)
2099 GtkSmileyTree *tree = (GtkSmileyTree *) value;
2100 GtkIMHtmlSmiley *smiley = (GtkIMHtmlSmiley *) user_data;
2101 gtk_smiley_tree_remove(tree, smiley);
2104 static void
2105 gtk_imhtml_disconnect_smiley(GtkIMHtml *imhtml, GtkIMHtmlSmiley *smiley)
2107 smiley->imhtml = NULL;
2108 g_signal_handlers_disconnect_matched(imhtml, G_SIGNAL_MATCH_DATA, 0, 0,
2109 NULL, NULL, smiley);
2112 static void
2113 gtk_imhtml_disassociate_smiley(GtkIMHtmlSmiley *smiley)
2115 if (smiley->imhtml) {
2116 gtk_smiley_tree_remove(smiley->imhtml->default_smilies, smiley);
2117 g_hash_table_foreach(smiley->imhtml->smiley_data,
2118 gtk_imhtml_disassociate_smiley_foreach, smiley);
2119 g_signal_handlers_disconnect_matched(smiley->imhtml, G_SIGNAL_MATCH_DATA,
2120 0, 0, NULL, NULL, smiley);
2121 smiley->imhtml = NULL;
2125 void
2126 gtk_imhtml_associate_smiley (GtkIMHtml *imhtml,
2127 const gchar *sml,
2128 GtkIMHtmlSmiley *smiley)
2130 GtkSmileyTree *tree;
2131 g_return_if_fail (imhtml != NULL);
2132 g_return_if_fail (GTK_IS_IMHTML (imhtml));
2134 if (sml == NULL)
2135 tree = imhtml->default_smilies;
2136 else if (!(tree = g_hash_table_lookup(imhtml->smiley_data, sml))) {
2137 tree = gtk_smiley_tree_new();
2138 g_hash_table_insert(imhtml->smiley_data, g_strdup(sml), tree);
2141 /* need to disconnect old imhtml, if there is one */
2142 if (smiley->imhtml) {
2143 g_signal_handlers_disconnect_matched(smiley->imhtml, G_SIGNAL_MATCH_DATA,
2144 0, 0, NULL, NULL, smiley);
2147 smiley->imhtml = imhtml;
2149 gtk_smiley_tree_insert (tree, smiley);
2151 /* connect destroy signal for the imhtml */
2152 g_signal_connect(imhtml, "destroy", G_CALLBACK(gtk_imhtml_disconnect_smiley),
2153 smiley);
2156 static gboolean
2157 gtk_imhtml_is_smiley (GtkIMHtml *imhtml,
2158 GSList *fonts,
2159 const gchar *text,
2160 gint *len)
2162 GtkSmileyTree *tree;
2163 GtkIMHtmlFontDetail *font;
2164 char *sml = NULL;
2166 if (fonts) {
2167 font = fonts->data;
2168 sml = font->sml;
2171 if (!sml)
2172 sml = imhtml->protocol_name;
2174 if (!sml || !(tree = g_hash_table_lookup(imhtml->smiley_data, sml)))
2175 tree = imhtml->default_smilies;
2177 if (tree == NULL)
2178 return FALSE;
2180 *len = gtk_smiley_tree_lookup (tree, text);
2181 return (*len > 0);
2184 static GtkIMHtmlSmiley *gtk_imhtml_smiley_get_from_tree(GtkSmileyTree *t, const gchar *text)
2186 const gchar *x = text;
2187 gchar *pos;
2189 if (t == NULL)
2190 return NULL;
2192 while (*x) {
2193 if (!t->values)
2194 return NULL;
2196 pos = strchr(t->values->str, *x);
2197 if (!pos)
2198 return NULL;
2200 t = t->children[GPOINTER_TO_INT(pos) - GPOINTER_TO_INT(t->values->str)];
2201 x++;
2204 return t->image;
2207 GtkIMHtmlSmiley *
2208 gtk_imhtml_smiley_get(GtkIMHtml *imhtml, const gchar *sml, const gchar *text)
2210 GtkIMHtmlSmiley *ret;
2212 /* Look for custom smileys first */
2213 if (sml != NULL) {
2214 ret = gtk_imhtml_smiley_get_from_tree(g_hash_table_lookup(imhtml->smiley_data, sml), text);
2215 if (ret != NULL)
2216 return ret;
2219 /* Fall back to check for default smileys */
2220 return gtk_imhtml_smiley_get_from_tree(imhtml->default_smilies, text);
2223 static GdkPixbufAnimation *
2224 gtk_smiley_get_image(GtkIMHtmlSmiley *smiley)
2226 if (!smiley->icon) {
2227 if (smiley->file) {
2228 smiley->icon = gdk_pixbuf_animation_new_from_file(smiley->file, NULL);
2229 } else if (smiley->loader) {
2230 smiley->icon = gdk_pixbuf_loader_get_animation(smiley->loader);
2231 if (smiley->icon)
2232 g_object_ref(G_OBJECT(smiley->icon));
2236 return smiley->icon;
2239 #define VALID_TAG(x) do { \
2240 if (!g_ascii_strncasecmp (string, x ">", strlen (x ">"))) { \
2241 if (tag) *tag = g_strndup (string, strlen (x)); \
2242 if (len) *len = strlen (x) + 1; \
2243 return TRUE; \
2245 if (type) (*type)++; \
2246 } while (0)
2248 #define VALID_OPT_TAG(x) do { \
2249 if (!g_ascii_strncasecmp (string, x " ", strlen (x " "))) { \
2250 const gchar *c = string + strlen (x " "); \
2251 gchar e = '"'; \
2252 gboolean quote = FALSE; \
2253 while (*c) { \
2254 if (*c == '"' || *c == '\'') { \
2255 if (quote && (*c == e)) \
2256 quote = !quote; \
2257 else if (!quote) { \
2258 quote = !quote; \
2259 e = *c; \
2261 } else if (!quote && (*c == '>')) \
2262 break; \
2263 c++; \
2265 if (*c) { \
2266 if (tag) *tag = g_strndup (string, c - string); \
2267 if (len) *len = c - string + 1; \
2268 return TRUE; \
2271 if (type) (*type)++; \
2272 } while (0)
2275 static gboolean
2276 gtk_imhtml_is_tag (const gchar *string,
2277 gchar **tag,
2278 gint *len,
2279 gint *type)
2281 char *close;
2282 if (type)
2283 *type = 1;
2285 if (!(close = strchr (string, '>')))
2286 return FALSE;
2288 VALID_TAG ("B");
2289 VALID_TAG ("BOLD");
2290 VALID_TAG ("/B");
2291 VALID_TAG ("/BOLD");
2292 VALID_TAG ("I");
2293 VALID_TAG ("ITALIC");
2294 VALID_TAG ("/I");
2295 VALID_TAG ("/ITALIC");
2296 VALID_TAG ("U");
2297 VALID_TAG ("UNDERLINE");
2298 VALID_TAG ("/U");
2299 VALID_TAG ("/UNDERLINE");
2300 VALID_TAG ("S");
2301 VALID_TAG ("STRIKE");
2302 VALID_TAG ("/S");
2303 VALID_TAG ("/STRIKE");
2304 VALID_TAG ("SUB");
2305 VALID_TAG ("/SUB");
2306 VALID_TAG ("SUP");
2307 VALID_TAG ("/SUP");
2308 VALID_TAG ("PRE");
2309 VALID_TAG ("/PRE");
2310 VALID_TAG ("TITLE");
2311 VALID_TAG ("/TITLE");
2312 VALID_TAG ("BR");
2313 VALID_TAG ("HR");
2314 VALID_TAG ("/FONT");
2315 VALID_TAG ("/A");
2316 VALID_TAG ("P");
2317 VALID_TAG ("/P");
2318 VALID_TAG ("H3");
2319 VALID_TAG ("/H3");
2320 VALID_TAG ("HTML");
2321 VALID_TAG ("/HTML");
2322 VALID_TAG ("BODY");
2323 VALID_TAG ("/BODY");
2324 VALID_TAG ("FONT");
2325 VALID_TAG ("HEAD");
2326 VALID_TAG ("/HEAD");
2327 VALID_TAG ("BINARY");
2328 VALID_TAG ("/BINARY");
2330 VALID_OPT_TAG ("HR");
2331 VALID_OPT_TAG ("FONT");
2332 VALID_OPT_TAG ("BODY");
2333 VALID_OPT_TAG ("A");
2334 VALID_OPT_TAG ("IMG");
2335 VALID_OPT_TAG ("P");
2336 VALID_OPT_TAG ("H3");
2337 VALID_OPT_TAG ("HTML");
2339 VALID_TAG ("CITE");
2340 VALID_TAG ("/CITE");
2341 VALID_TAG ("EM");
2342 VALID_TAG ("/EM");
2343 VALID_TAG ("STRONG");
2344 VALID_TAG ("/STRONG");
2346 VALID_OPT_TAG ("SPAN");
2347 VALID_TAG ("/SPAN");
2348 VALID_TAG ("BR/"); /* hack until gtkimhtml handles things better */
2349 VALID_TAG ("IMG");
2350 VALID_TAG("SPAN");
2351 VALID_OPT_TAG("BR");
2353 if (!g_ascii_strncasecmp(string, "!--", strlen ("!--"))) {
2354 gchar *e = strstr (string + strlen("!--"), "-->");
2355 if (e) {
2356 if (len)
2357 *len = e - string + strlen ("-->");
2358 if (tag)
2359 *tag = g_strndup (string + strlen ("!--"), *len - strlen ("!---->"));
2360 return TRUE;
2364 if (type)
2365 *type = -1;
2366 if (len)
2367 *len = close - string + 1;
2368 if (tag)
2369 *tag = g_strndup(string, *len - 1);
2370 return TRUE;
2373 static gchar*
2374 gtk_imhtml_get_html_opt (gchar *tag,
2375 const gchar *opt)
2377 gchar *t = tag;
2378 gchar *e, *a;
2379 gchar *val;
2380 gint len;
2381 const gchar *c;
2382 GString *ret;
2384 while (g_ascii_strncasecmp (t, opt, strlen (opt))) {
2385 gboolean quote = FALSE;
2386 if (*t == '\0') break;
2387 while (*t && !((*t == ' ') && !quote)) {
2388 if (*t == '\"')
2389 quote = ! quote;
2390 t++;
2392 while (*t && (*t == ' ')) t++;
2395 if (!g_ascii_strncasecmp (t, opt, strlen (opt))) {
2396 t += strlen (opt);
2397 } else {
2398 return NULL;
2401 if ((*t == '\"') || (*t == '\'')) {
2402 e = a = ++t;
2403 while (*e && (*e != *(t - 1))) e++;
2404 if (*e == '\0') {
2405 return NULL;
2406 } else
2407 val = g_strndup(a, e - a);
2408 } else {
2409 e = a = t;
2410 while (*e && !isspace ((gint) *e)) e++;
2411 val = g_strndup(a, e - a);
2414 ret = g_string_new("");
2415 e = val;
2416 while(*e) {
2417 if((c = purple_markup_unescape_entity(e, &len))) {
2418 ret = g_string_append(ret, c);
2419 e += len;
2420 } else {
2421 gunichar uni = g_utf8_get_char(e);
2422 ret = g_string_append_unichar(ret, uni);
2423 e = g_utf8_next_char(e);
2427 g_free(val);
2429 return g_string_free(ret, FALSE);
2432 /* returns if the beginning of the text is a protocol. If it is the protocol, returns the length so
2433 the caller knows how long the protocol string is. */
2434 static int gtk_imhtml_is_protocol(const char *text)
2436 GtkIMHtmlProtocol *proto = imhtml_find_protocol(text, FALSE);
2437 return proto ? proto->length : 0;
2440 static gboolean smooth_scroll_cb(gpointer data);
2443 <KingAnt> marv: The two IM image functions in oscar are purple_odc_send_im and purple_odc_incoming
2446 [19:58] <Robot101> marv: images go into the imgstore, a refcounted... well.. hash. :)
2447 [19:59] <KingAnt> marv: I think the image tag used by the core is something like <img id="#"/>
2448 [19:59] Ro0tSiEgE robert42 RobFlynn Robot101 ross22 roz
2449 [20:00] <KingAnt> marv: Where the ID is the what is returned when you add the image to the imgstore using purple_imgstore_add
2450 [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?
2451 [20:00] <KingAnt> marv: Right
2452 [20:00] <marv> alright
2454 Here's my plan with IMImages. make gtk_imhtml_[append|insert]_text_with_images instead just
2455 gtkimhtml_[append|insert]_text (hrm maybe it should be called html instead of text), add a
2456 function for purple to register for look up images, i.e. gtk_imhtml_set_get_img_fnc, so that
2457 images can be looked up like that, instead of passing a GSList of them.
2460 void gtk_imhtml_append_text_with_images (GtkIMHtml *imhtml,
2461 const gchar *text,
2462 GtkIMHtmlOptions options,
2463 GSList *unused)
2465 GtkTextIter iter, ins, sel;
2466 int ins_offset = 0, sel_offset = 0;
2467 gboolean fixins = FALSE, fixsel = FALSE;
2469 g_return_if_fail (imhtml != NULL);
2470 g_return_if_fail (GTK_IS_IMHTML (imhtml));
2471 g_return_if_fail (text != NULL);
2474 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &iter);
2475 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &ins, gtk_text_buffer_get_insert(imhtml->text_buffer));
2476 if (gtk_text_iter_equal(&iter, &ins) && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, NULL, NULL)) {
2477 fixins = TRUE;
2478 ins_offset = gtk_text_iter_get_offset(&ins);
2481 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &sel, gtk_text_buffer_get_selection_bound(imhtml->text_buffer));
2482 if (gtk_text_iter_equal(&iter, &sel) && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, NULL, NULL)) {
2483 fixsel = TRUE;
2484 sel_offset = gtk_text_iter_get_offset(&sel);
2487 if (!(options & GTK_IMHTML_NO_SCROLL)) {
2488 GdkRectangle rect;
2489 int y, height;
2491 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect);
2492 gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(imhtml), &iter, &y, &height);
2494 if (((y + height) - (rect.y + rect.height)) > height &&
2495 gtk_text_buffer_get_char_count(imhtml->text_buffer)) {
2496 /* If we are in the middle of smooth-scrolling, then take a scroll step.
2497 * If we are not in the middle of smooth-scrolling, that means we were
2498 * not looking at the end of the buffer before the new text was added,
2499 * so do not scroll. */
2500 if (imhtml->scroll_time)
2501 smooth_scroll_cb(imhtml);
2502 else
2503 options |= GTK_IMHTML_NO_SCROLL;
2507 gtk_imhtml_insert_html_at_iter(imhtml, text, options, &iter);
2509 if (fixins) {
2510 gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &ins, ins_offset);
2511 gtk_text_buffer_move_mark(imhtml->text_buffer, gtk_text_buffer_get_insert(imhtml->text_buffer), &ins);
2514 if (fixsel) {
2515 gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &sel, sel_offset);
2516 gtk_text_buffer_move_mark(imhtml->text_buffer, gtk_text_buffer_get_selection_bound(imhtml->text_buffer), &sel);
2519 if (!(options & GTK_IMHTML_NO_SCROLL)) {
2520 gtk_imhtml_scroll_to_end(imhtml, (options & GTK_IMHTML_USE_SMOOTHSCROLLING));
2524 #define MAX_SCROLL_TIME 0.4 /* seconds */
2525 #define SCROLL_DELAY 33 /* milliseconds */
2528 * Smoothly scroll a GtkIMHtml.
2530 * @return TRUE if the window needs to be scrolled further, FALSE if we're at the bottom.
2532 static gboolean smooth_scroll_cb(gpointer data)
2534 GtkIMHtml *imhtml = data;
2535 GtkAdjustment *adj = GTK_TEXT_VIEW(imhtml)->vadjustment;
2536 gdouble max_val = adj->upper - adj->page_size;
2537 gdouble scroll_val = gtk_adjustment_get_value(adj) + ((max_val - gtk_adjustment_get_value(adj)) / 3);
2539 g_return_val_if_fail(imhtml->scroll_time != NULL, FALSE);
2541 if (g_timer_elapsed(imhtml->scroll_time, NULL) > MAX_SCROLL_TIME || scroll_val >= max_val) {
2542 /* time's up. jump to the end and kill the timer */
2543 gtk_adjustment_set_value(adj, max_val);
2544 g_timer_destroy(imhtml->scroll_time);
2545 imhtml->scroll_time = NULL;
2546 g_source_remove(imhtml->scroll_src);
2547 imhtml->scroll_src = 0;
2548 return FALSE;
2551 /* scroll by 1/3rd the remaining distance */
2552 gtk_adjustment_set_value(adj, scroll_val);
2553 return TRUE;
2556 static gboolean scroll_idle_cb(gpointer data)
2558 GtkIMHtml *imhtml = data;
2559 GtkAdjustment *adj = GTK_TEXT_VIEW(imhtml)->vadjustment;
2560 if(adj) {
2561 gtk_adjustment_set_value(adj, adj->upper - adj->page_size);
2563 imhtml->scroll_src = 0;
2564 return FALSE;
2567 void gtk_imhtml_scroll_to_end(GtkIMHtml *imhtml, gboolean smooth)
2569 if (imhtml->scroll_time)
2570 g_timer_destroy(imhtml->scroll_time);
2571 if (imhtml->scroll_src)
2572 g_source_remove(imhtml->scroll_src);
2573 if(smooth) {
2574 imhtml->scroll_time = g_timer_new();
2575 imhtml->scroll_src = g_timeout_add_full(G_PRIORITY_LOW, SCROLL_DELAY, smooth_scroll_cb, imhtml, NULL);
2576 } else {
2577 imhtml->scroll_time = NULL;
2578 imhtml->scroll_src = g_idle_add_full(G_PRIORITY_LOW, scroll_idle_cb, imhtml, NULL);
2582 /* CSS colors are either rgb (x,y,z) or #hex
2583 * we need to convert to hex if it is RGB */
2584 static gchar*
2585 parse_css_color(gchar *in_color)
2587 char *tmp = in_color;
2589 if (*tmp == 'r' && *(++tmp) == 'g' && *(++tmp) == 'b' && *(++tmp)) {
2590 int rgbval[] = {0, 0, 0};
2591 int count = 0;
2592 const char *v_start;
2594 while (*tmp && g_ascii_isspace(*tmp))
2595 tmp++;
2596 if (*tmp != '(') {
2597 /* We don't support rgba() */
2598 purple_debug_warning("gtkimhtml", "Invalid rgb CSS color in '%s'!\n", in_color);
2599 return in_color;
2601 tmp++;
2603 while (count < 3) {
2604 /* Skip any leading spaces */
2605 while (*tmp && g_ascii_isspace(*tmp))
2606 tmp++;
2608 /* Find the subsequent contiguous digits */
2609 v_start = tmp;
2610 if (*v_start == '-')
2611 tmp++;
2612 while (*tmp && g_ascii_isdigit(*tmp))
2613 tmp++;
2615 if (tmp != v_start) {
2616 char prev = *tmp;
2617 *tmp = '\0';
2618 rgbval[count] = atoi(v_start);
2619 *tmp = prev;
2621 /* deal with % */
2622 while (*tmp && g_ascii_isspace(*tmp))
2623 tmp++;
2624 if (*tmp == '%') {
2625 rgbval[count] = (rgbval[count] / 100.0) * 255;
2626 tmp++;
2628 } else {
2629 purple_debug_warning("gtkimhtml", "Invalid rgb CSS color in '%s'!\n", in_color);
2630 return in_color;
2633 if (rgbval[count] > 255) {
2634 rgbval[count] = 255;
2635 } else if (rgbval[count] < 0) {
2636 rgbval[count] = 0;
2639 while (*tmp && g_ascii_isspace(*tmp))
2640 tmp++;
2641 if (*tmp == ',')
2642 tmp++;
2644 count++;
2647 g_free(in_color);
2648 return g_strdup_printf("#%02X%02X%02X", rgbval[0], rgbval[1], rgbval[2]);
2651 return in_color;
2654 void gtk_imhtml_insert_html_at_iter(GtkIMHtml *imhtml,
2655 const gchar *text,
2656 GtkIMHtmlOptions options,
2657 GtkTextIter *iter)
2659 GdkRectangle rect;
2660 gint pos = 0;
2661 gchar *ws;
2662 gchar *tag;
2663 gchar *bg = NULL;
2664 gint len;
2665 gint tlen, smilelen, wpos=0;
2666 gint type;
2667 const gchar *c;
2668 const gchar *amp;
2669 gint len_protocol;
2671 guint bold = 0,
2672 italics = 0,
2673 underline = 0,
2674 strike = 0,
2675 sub = 0,
2676 sup = 0,
2677 title = 0,
2678 pre = 0;
2680 gboolean br = FALSE;
2681 gboolean align_right = FALSE;
2682 gboolean rtl_direction = FALSE;
2683 gint align_line = 0;
2685 GSList *fonts = NULL;
2686 GObject *object;
2687 GtkIMHtmlScalable *scalable = NULL;
2689 g_return_if_fail (imhtml != NULL);
2690 g_return_if_fail (GTK_IS_IMHTML (imhtml));
2691 g_return_if_fail (text != NULL);
2692 c = text;
2693 len = strlen(text);
2694 ws = g_malloc(len + 1);
2695 ws[0] = '\0';
2697 g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(0));
2699 gtk_text_buffer_begin_user_action(imhtml->text_buffer);
2700 while (pos < len) {
2701 if (*c == '<' && gtk_imhtml_is_tag (c + 1, &tag, &tlen, &type)) {
2702 c++;
2703 pos++;
2704 ws[wpos] = '\0';
2705 br = FALSE;
2706 switch (type)
2708 case 1: /* B */
2709 case 2: /* BOLD */
2710 case 54: /* STRONG */
2711 if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2712 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2714 if ((bold == 0) && (imhtml->format_functions & GTK_IMHTML_BOLD))
2715 gtk_imhtml_toggle_bold(imhtml);
2716 bold++;
2717 ws[0] = '\0'; wpos = 0;
2719 break;
2720 case 3: /* /B */
2721 case 4: /* /BOLD */
2722 case 55: /* /STRONG */
2723 if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2724 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2725 ws[0] = '\0'; wpos = 0;
2727 if (bold) {
2728 bold--;
2729 if ((bold == 0) && (imhtml->format_functions & GTK_IMHTML_BOLD) && !imhtml->wbfo)
2730 gtk_imhtml_toggle_bold(imhtml);
2733 break;
2734 case 5: /* I */
2735 case 6: /* ITALIC */
2736 case 52: /* EM */
2737 if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2738 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2739 ws[0] = '\0'; wpos = 0;
2740 if ((italics == 0) && (imhtml->format_functions & GTK_IMHTML_ITALIC))
2741 gtk_imhtml_toggle_italic(imhtml);
2742 italics++;
2744 break;
2745 case 7: /* /I */
2746 case 8: /* /ITALIC */
2747 case 53: /* /EM */
2748 if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2749 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2750 ws[0] = '\0'; wpos = 0;
2751 if (italics) {
2752 italics--;
2753 if ((italics == 0) && (imhtml->format_functions & GTK_IMHTML_ITALIC) && !imhtml->wbfo)
2754 gtk_imhtml_toggle_italic(imhtml);
2757 break;
2758 case 9: /* U */
2759 case 10: /* UNDERLINE */
2760 if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2761 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2762 ws[0] = '\0'; wpos = 0;
2763 if ((underline == 0) && (imhtml->format_functions & GTK_IMHTML_UNDERLINE))
2764 gtk_imhtml_toggle_underline(imhtml);
2765 underline++;
2767 break;
2768 case 11: /* /U */
2769 case 12: /* /UNDERLINE */
2770 if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2771 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2772 ws[0] = '\0'; wpos = 0;
2773 if (underline) {
2774 underline--;
2775 if ((underline == 0) && (imhtml->format_functions & GTK_IMHTML_UNDERLINE) && !imhtml->wbfo)
2776 gtk_imhtml_toggle_underline(imhtml);
2779 break;
2780 case 13: /* S */
2781 case 14: /* STRIKE */
2782 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2783 ws[0] = '\0'; wpos = 0;
2784 if ((strike == 0) && (imhtml->format_functions & GTK_IMHTML_STRIKE))
2785 gtk_imhtml_toggle_strike(imhtml);
2786 strike++;
2787 break;
2788 case 15: /* /S */
2789 case 16: /* /STRIKE */
2790 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2791 ws[0] = '\0'; wpos = 0;
2792 if (strike)
2793 strike--;
2794 if ((strike == 0) && (imhtml->format_functions & GTK_IMHTML_STRIKE) && !imhtml->wbfo)
2795 gtk_imhtml_toggle_strike(imhtml);
2796 break;
2797 case 17: /* SUB */
2798 /* FIXME: reimpliment this */
2799 sub++;
2800 break;
2801 case 18: /* /SUB */
2802 /* FIXME: reimpliment this */
2803 if (sub)
2804 sub--;
2805 break;
2806 case 19: /* SUP */
2807 /* FIXME: reimplement this */
2808 sup++;
2809 break;
2810 case 20: /* /SUP */
2811 /* FIXME: reimplement this */
2812 if (sup)
2813 sup--;
2814 break;
2815 case 21: /* PRE */
2816 /* FIXME: reimplement this */
2817 pre++;
2818 break;
2819 case 22: /* /PRE */
2820 /* FIXME: reimplement this */
2821 if (pre)
2822 pre--;
2823 break;
2824 case 23: /* TITLE */
2825 /* FIXME: what was this supposed to do anyway? */
2826 title++;
2827 break;
2828 case 24: /* /TITLE */
2829 /* FIXME: make this undo whatever 23 was supposed to do */
2830 if (title) {
2831 if (options & GTK_IMHTML_NO_TITLE) {
2832 wpos = 0;
2833 ws [wpos] = '\0';
2835 title--;
2837 break;
2838 case 25: /* BR */
2839 case 58: /* BR/ */
2840 case 61: /* BR (opt) */
2841 ws[wpos] = '\n';
2842 wpos++;
2843 br = TRUE;
2844 break;
2845 case 26: /* HR */
2846 case 42: /* HR (opt) */
2848 int minus;
2849 struct scalable_data *sd = g_new(struct scalable_data, 1);
2851 ws[wpos++] = '\n';
2852 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2854 sd->scalable = scalable = gtk_imhtml_hr_new();
2855 sd->mark = gtk_text_buffer_create_mark(imhtml->text_buffer, NULL, iter, TRUE);
2856 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect);
2857 scalable->add_to(scalable, imhtml, iter);
2858 minus = gtk_text_view_get_left_margin(GTK_TEXT_VIEW(imhtml)) +
2859 gtk_text_view_get_right_margin(GTK_TEXT_VIEW(imhtml));
2860 scalable->scale(scalable, rect.width - minus, rect.height);
2861 imhtml->scalables = g_list_append(imhtml->scalables, sd);
2862 ws[0] = '\0'; wpos = 0;
2863 ws[wpos++] = '\n';
2865 break;
2867 case 27: /* /FONT */
2868 if (fonts && !imhtml->wbfo) {
2869 GtkIMHtmlFontDetail *font = fonts->data;
2870 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2871 ws[0] = '\0'; wpos = 0;
2872 /* NEW_BIT (NEW_TEXT_BIT); */
2874 if (font->face && (imhtml->format_functions & GTK_IMHTML_FACE)) {
2875 gtk_imhtml_toggle_fontface(imhtml, NULL);
2877 g_free (font->face);
2878 if (font->fore && (imhtml->format_functions & GTK_IMHTML_FORECOLOR)) {
2879 gtk_imhtml_toggle_forecolor(imhtml, NULL);
2881 g_free (font->fore);
2882 if (font->back && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) {
2883 gtk_imhtml_toggle_backcolor(imhtml, NULL);
2885 g_free (font->back);
2886 g_free (font->sml);
2888 if ((font->size != 3) && (imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK)))
2889 gtk_imhtml_font_set_size(imhtml, 3);
2891 fonts = g_slist_remove (fonts, font);
2892 g_free(font);
2894 if (fonts) {
2895 GtkIMHtmlFontDetail *font = fonts->data;
2897 if (font->face && (imhtml->format_functions & GTK_IMHTML_FACE))
2898 gtk_imhtml_toggle_fontface(imhtml, font->face);
2899 if (font->fore && (imhtml->format_functions & GTK_IMHTML_FORECOLOR))
2900 gtk_imhtml_toggle_forecolor(imhtml, font->fore);
2901 if (font->back && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR))
2902 gtk_imhtml_toggle_backcolor(imhtml, font->back);
2903 if ((font->size != 3) && (imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK)))
2904 gtk_imhtml_font_set_size(imhtml, font->size);
2907 break;
2908 case 28: /* /A */
2909 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2910 gtk_imhtml_toggle_link(imhtml, NULL);
2911 ws[0] = '\0'; wpos = 0;
2912 break;
2914 case 29: /* P */
2915 case 30: /* /P */
2916 case 31: /* H3 */
2917 case 32: /* /H3 */
2918 case 33: /* HTML */
2919 case 34: /* /HTML */
2920 case 35: /* BODY */
2921 break;
2922 case 36: /* /BODY */
2923 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2924 ws[0] = '\0'; wpos = 0;
2925 gtk_imhtml_toggle_background(imhtml, NULL);
2926 break;
2927 case 37: /* FONT */
2928 case 38: /* HEAD */
2929 case 39: /* /HEAD */
2930 case 40: /* BINARY */
2931 case 41: /* /BINARY */
2932 break;
2933 case 43: /* FONT (opt) */
2935 gchar *color, *back, *face, *size, *sml;
2936 GtkIMHtmlFontDetail *font, *oldfont = NULL;
2937 color = gtk_imhtml_get_html_opt (tag, "COLOR=");
2938 back = gtk_imhtml_get_html_opt (tag, "BACK=");
2939 face = gtk_imhtml_get_html_opt (tag, "FACE=");
2940 size = gtk_imhtml_get_html_opt (tag, "SIZE=");
2941 sml = gtk_imhtml_get_html_opt (tag, "SML=");
2942 if (!(color || back || face || size || sml))
2943 break;
2945 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2946 ws[0] = '\0'; wpos = 0;
2948 font = g_new0 (GtkIMHtmlFontDetail, 1);
2949 if (fonts)
2950 oldfont = fonts->data;
2952 if (color && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_FORECOLOR)) {
2953 font->fore = color;
2954 gtk_imhtml_toggle_forecolor(imhtml, font->fore);
2955 } else
2956 g_free(color);
2958 if (back && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) {
2959 font->back = back;
2960 gtk_imhtml_toggle_backcolor(imhtml, font->back);
2961 } else
2962 g_free(back);
2964 if (face && !(options & GTK_IMHTML_NO_FONTS) && (imhtml->format_functions & GTK_IMHTML_FACE)) {
2965 font->face = face;
2966 gtk_imhtml_toggle_fontface(imhtml, font->face);
2967 } else
2968 g_free(face);
2970 if (sml)
2971 font->sml = sml;
2972 else {
2973 g_free(sml);
2974 if (oldfont && oldfont->sml)
2975 font->sml = g_strdup(oldfont->sml);
2978 if (size && !(options & GTK_IMHTML_NO_SIZES) && (imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK))) {
2979 if (*size == '+') {
2980 sscanf (size + 1, "%hd", &font->size);
2981 font->size += 3;
2982 } else if (*size == '-') {
2983 sscanf (size + 1, "%hd", &font->size);
2984 font->size = MAX (0, 3 - font->size);
2985 } else if (isdigit (*size)) {
2986 sscanf (size, "%hd", &font->size);
2988 if (font->size > 100)
2989 font->size = 100;
2990 } else if (oldfont)
2991 font->size = oldfont->size;
2992 else
2993 font->size = 3;
2994 if ((imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK)) && (font->size != 3 || (oldfont && oldfont->size == 3)))
2995 gtk_imhtml_font_set_size(imhtml, font->size);
2996 g_free(size);
2997 fonts = g_slist_prepend (fonts, font);
2999 break;
3000 case 44: /* BODY (opt) */
3001 if (!(options & GTK_IMHTML_NO_COLOURS)) {
3002 char *bgcolor = gtk_imhtml_get_html_opt (tag, "BGCOLOR=");
3003 if (bgcolor && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) {
3004 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3005 ws[0] = '\0'; wpos = 0;
3006 /* NEW_BIT(NEW_TEXT_BIT); */
3007 g_free(bg);
3008 bg = bgcolor;
3009 gtk_imhtml_toggle_background(imhtml, bg);
3010 } else
3011 g_free(bgcolor);
3013 break;
3014 case 45: /* A (opt) */
3016 gchar *href = gtk_imhtml_get_html_opt (tag, "HREF=");
3017 if (href && (imhtml->format_functions & GTK_IMHTML_LINK)) {
3018 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3019 ws[0] = '\0'; wpos = 0;
3020 gtk_imhtml_toggle_link(imhtml, href);
3022 g_free(href);
3024 break;
3025 case 46: /* IMG (opt) */
3026 case 59: /* IMG */
3028 char *id;
3030 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3031 ws[0] = '\0'; wpos = 0;
3033 if (!(imhtml->format_functions & GTK_IMHTML_IMAGE))
3034 break;
3036 id = gtk_imhtml_get_html_opt(tag, "ID=");
3037 if (id) {
3038 gtk_imhtml_insert_image_at_iter(imhtml, atoi(id), iter);
3039 g_free(id);
3040 } else {
3041 char *src, *alt;
3042 src = gtk_imhtml_get_html_opt(tag, "SRC=");
3043 alt = gtk_imhtml_get_html_opt(tag, "ALT=");
3044 if (src) {
3045 gtk_imhtml_toggle_link(imhtml, src);
3046 gtk_text_buffer_insert(imhtml->text_buffer, iter, alt ? alt : src, -1);
3047 gtk_imhtml_toggle_link(imhtml, NULL);
3049 g_free (src);
3050 g_free (alt);
3052 break;
3054 case 47: /* P (opt) */
3055 case 48: /* H3 (opt) */
3056 case 49: /* HTML (opt) */
3057 case 50: /* CITE */
3058 case 51: /* /CITE */
3059 case 56: /* SPAN (opt) */
3060 /* Inline CSS Support - Douglas Thrift
3062 * color
3063 * background
3064 * font-family
3065 * font-size
3066 * text-decoration: underline
3067 * font-weight: bold
3068 * direction: rtl
3069 * text-align: right
3071 * TODO:
3072 * background-color
3073 * font-style
3076 gchar *style, *color, *background, *family, *size, *direction, *alignment;
3077 gchar *textdec, *weight;
3078 GtkIMHtmlFontDetail *font, *oldfont = NULL;
3079 style = gtk_imhtml_get_html_opt (tag, "style=");
3081 if (!style) break;
3083 color = purple_markup_get_css_property (style, "color");
3084 background = purple_markup_get_css_property (style, "background");
3085 family = purple_markup_get_css_property (style, "font-family");
3086 size = purple_markup_get_css_property (style, "font-size");
3087 textdec = purple_markup_get_css_property (style, "text-decoration");
3088 weight = purple_markup_get_css_property (style, "font-weight");
3089 direction = purple_markup_get_css_property (style, "direction");
3090 alignment = purple_markup_get_css_property (style, "text-align");
3093 if (!(color || family || size || background || textdec || weight || direction || alignment)) {
3094 g_free(style);
3095 break;
3099 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3100 ws[0] = '\0'; wpos = 0;
3101 /* NEW_BIT (NEW_TEXT_BIT); */
3103 /* Bi-Directional text support */
3104 if (direction && (!g_ascii_strncasecmp(direction, "RTL", 3))) {
3105 rtl_direction = TRUE;
3106 /* insert RLE character to set direction */
3107 ws[wpos++] = 0xE2;
3108 ws[wpos++] = 0x80;
3109 ws[wpos++] = 0xAB;
3110 ws[wpos] = '\0';
3111 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3112 ws[0] = '\0'; wpos = 0;
3114 g_free(direction);
3116 if (alignment && (!g_ascii_strncasecmp(alignment, "RIGHT", 5))) {
3117 align_right = TRUE;
3118 align_line = gtk_text_iter_get_line(iter);
3120 g_free(alignment);
3122 font = g_new0 (GtkIMHtmlFontDetail, 1);
3123 if (fonts)
3124 oldfont = fonts->data;
3126 if (color && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_FORECOLOR)) {
3127 font->fore = parse_css_color(color);
3128 gtk_imhtml_toggle_forecolor(imhtml, font->fore);
3129 } else {
3130 if (oldfont && oldfont->fore)
3131 font->fore = g_strdup(oldfont->fore);
3132 g_free(color);
3135 if (background && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) {
3136 font->back = parse_css_color(background);
3137 gtk_imhtml_toggle_backcolor(imhtml, font->back);
3138 } else {
3139 if (oldfont && oldfont->back)
3140 font->back = g_strdup(oldfont->back);
3141 g_free(background);
3144 if (family && !(options & GTK_IMHTML_NO_FONTS) && (imhtml->format_functions & GTK_IMHTML_FACE)) {
3145 font->face = family;
3146 gtk_imhtml_toggle_fontface(imhtml, font->face);
3147 } else {
3148 if (oldfont && oldfont->face)
3149 font->face = g_strdup(oldfont->face);
3150 g_free(family);
3152 if (font->face && (atoi(font->face) > 100)) {
3153 /* WTF is this? */
3154 /* Maybe it sets a max size on the font face? I seem to
3155 * remember bad things happening if the font size was
3156 * 2 billion */
3157 g_free(font->face);
3158 font->face = g_strdup("100");
3161 if (oldfont && oldfont->sml)
3162 font->sml = g_strdup(oldfont->sml);
3164 if (size && !(options & GTK_IMHTML_NO_SIZES) && (imhtml->format_functions & (GTK_IMHTML_SHRINK|GTK_IMHTML_GROW))) {
3165 if (g_ascii_strcasecmp(size, "xx-small") == 0)
3166 font->size = 1;
3167 else if (g_ascii_strcasecmp(size, "smaller") == 0
3168 || g_ascii_strcasecmp(size, "x-small") == 0)
3169 font->size = 2;
3170 else if (g_ascii_strcasecmp(size, "medium") == 0)
3171 font->size = 3;
3172 else if (g_ascii_strcasecmp(size, "large") == 0
3173 || g_ascii_strcasecmp(size, "larger") == 0)
3174 font->size = 4;
3175 else if (g_ascii_strcasecmp(size, "x-large") == 0)
3176 font->size = 5;
3177 else if (g_ascii_strcasecmp(size, "xx-large") == 0)
3178 font->size = 6;
3181 * TODO: Handle other values, like percentages, or
3182 * lengths specified as em, ex, px, in, cm, mm, pt
3183 * or pc. Or even better, use an actual HTML
3184 * renderer like webkit.
3186 if (font->size > 0)
3187 gtk_imhtml_font_set_size(imhtml, font->size);
3189 else if (oldfont)
3191 font->size = oldfont->size;
3194 if (oldfont)
3196 font->underline = oldfont->underline;
3198 if (textdec && font->underline != 1
3199 && g_ascii_strcasecmp(textdec, "underline") == 0
3200 && (imhtml->format_functions & GTK_IMHTML_UNDERLINE)
3201 && !(options & GTK_IMHTML_NO_FORMATTING))
3203 gtk_imhtml_toggle_underline(imhtml);
3204 font->underline = 1;
3207 if (oldfont)
3209 font->strike = oldfont->strike;
3211 if (textdec && font->strike != 1
3212 && g_ascii_strcasecmp(textdec, "line-through") == 0
3213 && (imhtml->format_functions & GTK_IMHTML_STRIKE)
3214 && !(options & GTK_IMHTML_NO_FORMATTING))
3216 gtk_imhtml_toggle_strike(imhtml);
3217 font->strike = 1;
3219 g_free(textdec);
3221 if (oldfont)
3223 font->bold = oldfont->bold;
3225 if (weight)
3227 if(!g_ascii_strcasecmp(weight, "normal")) {
3228 font->bold = 0;
3229 } else if(!g_ascii_strcasecmp(weight, "bold")) {
3230 font->bold = 1;
3231 } else if(!g_ascii_strcasecmp(weight, "bolder")) {
3232 font->bold++;
3233 } else if(!g_ascii_strcasecmp(weight, "lighter")) {
3234 if(font->bold > 0)
3235 font->bold--;
3236 } else {
3237 int num = atoi(weight);
3238 if(num >= 700)
3239 font->bold = 1;
3240 else
3241 font->bold = 0;
3243 if (((font->bold && oldfont && !oldfont->bold) || (oldfont && oldfont->bold && !font->bold) || (font->bold && !oldfont)) && !(options & GTK_IMHTML_NO_FORMATTING))
3245 gtk_imhtml_toggle_bold(imhtml);
3247 g_free(weight);
3250 g_free(style);
3251 g_free(size);
3252 fonts = g_slist_prepend (fonts, font);
3254 break;
3255 case 57: /* /SPAN */
3256 /* Inline CSS Support - Douglas Thrift */
3257 if (fonts && !imhtml->wbfo) {
3258 GtkIMHtmlFontDetail *oldfont = NULL;
3259 GtkIMHtmlFontDetail *font = fonts->data;
3260 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3261 ws[0] = '\0'; wpos = 0;
3262 /* NEW_BIT (NEW_TEXT_BIT); */
3263 fonts = g_slist_remove (fonts, font);
3264 if (fonts)
3265 oldfont = fonts->data;
3267 if (!oldfont) {
3268 gtk_imhtml_font_set_size(imhtml, 3);
3269 if (font->underline && !(options & GTK_IMHTML_NO_FORMATTING))
3270 gtk_imhtml_toggle_underline(imhtml);
3271 if (font->strike && !(options & GTK_IMHTML_NO_FORMATTING))
3272 gtk_imhtml_toggle_strike(imhtml);
3273 if (font->bold && !(options & GTK_IMHTML_NO_FORMATTING))
3274 gtk_imhtml_toggle_bold(imhtml);
3275 if (!(options & GTK_IMHTML_NO_FONTS))
3276 gtk_imhtml_toggle_fontface(imhtml, NULL);
3277 if (!(options & GTK_IMHTML_NO_COLOURS))
3278 gtk_imhtml_toggle_forecolor(imhtml, NULL);
3279 if (!(options & GTK_IMHTML_NO_COLOURS))
3280 gtk_imhtml_toggle_backcolor(imhtml, NULL);
3282 else
3285 if ((font->size != oldfont->size) && !(options & GTK_IMHTML_NO_SIZES))
3286 gtk_imhtml_font_set_size(imhtml, oldfont->size);
3288 if ((font->underline != oldfont->underline) && !(options & GTK_IMHTML_NO_FORMATTING))
3289 gtk_imhtml_toggle_underline(imhtml);
3291 if ((font->strike != oldfont->strike) && !(options & GTK_IMHTML_NO_FORMATTING))
3292 gtk_imhtml_toggle_strike(imhtml);
3294 if (((font->bold && !oldfont->bold) || (oldfont->bold && !font->bold)) && !(options & GTK_IMHTML_NO_FORMATTING))
3295 gtk_imhtml_toggle_bold(imhtml);
3297 if (font->face && (!oldfont->face || strcmp(font->face, oldfont->face) != 0) && !(options & GTK_IMHTML_NO_FONTS))
3298 gtk_imhtml_toggle_fontface(imhtml, oldfont->face);
3300 if (font->fore && (!oldfont->fore || strcmp(font->fore, oldfont->fore) != 0) && !(options & GTK_IMHTML_NO_COLOURS))
3301 gtk_imhtml_toggle_forecolor(imhtml, oldfont->fore);
3303 if (font->back && (!oldfont->back || strcmp(font->back, oldfont->back) != 0) && !(options & GTK_IMHTML_NO_COLOURS))
3304 gtk_imhtml_toggle_backcolor(imhtml, oldfont->back);
3307 g_free (font->face);
3308 g_free (font->fore);
3309 g_free (font->back);
3310 g_free (font->sml);
3312 g_free (font);
3314 break;
3315 case 60: /* SPAN */
3316 break;
3317 case 62: /* comment */
3318 /* NEW_BIT (NEW_TEXT_BIT); */
3319 ws[wpos] = '\0';
3321 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3323 #if FALSE && GTK_CHECK_VERSION(2,10,10)
3324 wpos = g_snprintf (ws, len, "%s", tag);
3325 gtk_text_buffer_insert_with_tags_by_name(imhtml->text_buffer, iter, ws, wpos, "comment", NULL);
3326 #else
3327 if (imhtml->show_comments && !(options & GTK_IMHTML_NO_COMMENTS)) {
3328 wpos = g_snprintf (ws, len, "%s", tag);
3329 gtk_text_buffer_insert_with_tags_by_name(imhtml->text_buffer, iter, ws, wpos, "comment", NULL);
3331 #endif
3332 ws[0] = '\0'; wpos = 0;
3334 /* NEW_BIT (NEW_COMMENT_BIT); */
3335 break;
3336 default:
3337 break;
3339 c += tlen;
3340 pos += tlen;
3341 g_free(tag); /* This was allocated back in VALID_TAG() */
3342 } else if (imhtml->edit.link == NULL &&
3343 !(options & GTK_IMHTML_NO_SMILEY) &&
3344 gtk_imhtml_is_smiley(imhtml, fonts, c, &smilelen)) {
3345 GtkIMHtmlFontDetail *fd;
3346 gchar *sml = NULL;
3348 br = FALSE;
3350 if (fonts) {
3351 fd = fonts->data;
3352 sml = fd->sml;
3354 if (!sml)
3355 sml = imhtml->protocol_name;
3357 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3358 wpos = g_snprintf (ws, smilelen + 1, "%s", c);
3360 gtk_imhtml_insert_smiley_at_iter(imhtml, sml, ws, iter);
3362 c += smilelen;
3363 pos += smilelen;
3364 wpos = 0;
3365 ws[0] = 0;
3366 } else if (*c == '&' && (amp = purple_markup_unescape_entity(c, &tlen))) {
3367 br = FALSE;
3368 while(*amp) {
3369 ws [wpos++] = *amp++;
3371 c += tlen;
3372 pos += tlen;
3373 } else if (*c == '\n') {
3374 if (!(options & GTK_IMHTML_NO_NEWLINE)) {
3375 ws[wpos] = '\n';
3376 wpos++;
3377 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3378 ws[0] = '\0'; wpos = 0;
3379 /* NEW_BIT (NEW_TEXT_BIT); */
3380 } else if (!br) { /* Don't insert a space immediately after an HTML break */
3381 /* A newline is defined by HTML as whitespace, which means we have to replace it with a word boundary.
3382 * word breaks vary depending on the language used, so the correct thing to do is to use Pango to determine
3383 * what language this is, determine the proper word boundary to use, and insert that. I'm just going to insert
3384 * a space instead. What are the non-English speakers going to do? Complain in a language I'll understand?
3385 * Bu-wahaha! */
3386 ws[wpos] = ' ';
3387 wpos++;
3388 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3389 ws[0] = '\0'; wpos = 0;
3391 c++;
3392 pos++;
3393 } else if ((pos == 0 || wpos == 0 || isspace(*(c - 1))) &&
3394 (len_protocol = gtk_imhtml_is_protocol(c)) > 0 &&
3395 c[len_protocol] && !isspace(c[len_protocol]) &&
3396 (c[len_protocol] != '<' || !gtk_imhtml_is_tag(c + 1, NULL, NULL, NULL))) {
3397 br = FALSE;
3398 if (wpos > 0) {
3399 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3400 ws[0] = '\0'; wpos = 0;
3402 while (len_protocol--) {
3403 /* Skip the next len_protocol characters, but
3404 * make sure they're copied into the ws array.
3406 ws [wpos++] = *c++;
3407 pos++;
3409 if (!imhtml->edit.link && (imhtml->format_functions & GTK_IMHTML_LINK)) {
3410 while (*c && !isspace((int)*c) &&
3411 (*c != '<' || !gtk_imhtml_is_tag(c + 1, NULL, NULL, NULL))) {
3412 if (*c == '&' && (amp = purple_markup_unescape_entity(c, &tlen))) {
3413 while (*amp)
3414 ws[wpos++] = *amp++;
3415 c += tlen;
3416 pos += tlen;
3417 } else {
3418 ws [wpos++] = *c++;
3419 pos++;
3422 ws[wpos] = '\0';
3423 gtk_imhtml_toggle_link(imhtml, ws);
3424 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3425 ws[0] = '\0'; wpos = 0;
3426 gtk_imhtml_toggle_link(imhtml, NULL);
3428 } else if (*c) {
3429 br = FALSE;
3430 ws [wpos++] = *c++;
3431 pos++;
3432 } else {
3433 break;
3436 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3437 ws[0] = '\0'; wpos = 0;
3439 /* NEW_BIT(NEW_TEXT_BIT); */
3441 if(align_right) {
3442 /* insert RLM+LRM at beginning of the line to set alignment */
3443 GtkTextIter line_iter;
3444 line_iter = *iter;
3445 gtk_text_iter_set_line(&line_iter, align_line);
3446 /* insert RLM character to set alignment */
3447 ws[wpos++] = 0xE2;
3448 ws[wpos++] = 0x80;
3449 ws[wpos++] = 0x8F;
3451 if (!rtl_direction)
3453 /* insert LRM character to set direction */
3454 /* (alignment=right and direction=LTR) */
3455 ws[wpos++] = 0xE2;
3456 ws[wpos++] = 0x80;
3457 ws[wpos++] = 0x8E;
3460 ws[wpos] = '\0';
3461 gtk_text_buffer_insert(imhtml->text_buffer, &line_iter, ws, wpos);
3462 gtk_text_buffer_get_end_iter(gtk_text_iter_get_buffer(&line_iter), iter);
3463 ws[0] = '\0'; wpos = 0;
3466 while (fonts) {
3467 GtkIMHtmlFontDetail *font = fonts->data;
3468 fonts = g_slist_remove (fonts, font);
3469 g_free (font->face);
3470 g_free (font->fore);
3471 g_free (font->back);
3472 g_free (font->sml);
3473 g_free (font);
3476 g_free(ws);
3477 g_free(bg);
3479 if (!imhtml->wbfo)
3480 gtk_imhtml_close_tags(imhtml, iter);
3482 object = g_object_ref(G_OBJECT(imhtml));
3483 g_signal_emit(object, signals[UPDATE_FORMAT], 0);
3484 g_object_unref(object);
3486 gtk_text_buffer_end_user_action(imhtml->text_buffer);
3489 void gtk_imhtml_remove_smileys(GtkIMHtml *imhtml)
3491 g_hash_table_destroy(imhtml->smiley_data);
3492 gtk_smiley_tree_destroy(imhtml->default_smilies);
3493 imhtml->smiley_data = g_hash_table_new_full(g_str_hash, g_str_equal,
3494 g_free, (GDestroyNotify)gtk_smiley_tree_destroy);
3495 imhtml->default_smilies = gtk_smiley_tree_new();
3498 void gtk_imhtml_show_comments (GtkIMHtml *imhtml,
3499 gboolean show)
3501 #if FALSE && GTK_CHECK_VERSION(2,10,10)
3502 GtkTextTag *tag;
3503 tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), "comment");
3504 if (tag)
3505 g_object_set(G_OBJECT(tag), "invisible", !show, NULL);
3506 #endif
3507 imhtml->show_comments = show;
3510 const char *
3511 gtk_imhtml_get_protocol_name(GtkIMHtml *imhtml) {
3512 return imhtml->protocol_name;
3515 void
3516 gtk_imhtml_set_protocol_name(GtkIMHtml *imhtml, const gchar *protocol_name) {
3517 g_free(imhtml->protocol_name);
3518 imhtml->protocol_name = g_strdup(protocol_name);
3521 void
3522 gtk_imhtml_delete(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end) {
3523 GList *l;
3524 GSList *sl;
3525 GtkTextIter i, i_s, i_e;
3526 GObject *object = g_object_ref(G_OBJECT(imhtml));
3528 if (start == NULL) {
3529 gtk_text_buffer_get_start_iter(imhtml->text_buffer, &i_s);
3530 start = &i_s;
3533 if (end == NULL) {
3534 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &i_e);
3535 end = &i_e;
3538 l = imhtml->scalables;
3539 while (l) {
3540 GList *next = l->next;
3541 struct scalable_data *sd = l->data;
3542 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer,
3543 &i, sd->mark);
3544 if (gtk_text_iter_in_range(&i, start, end)) {
3545 GtkIMHtmlScalable *scale = GTK_IMHTML_SCALABLE(sd->scalable);
3546 scale->free(scale);
3547 g_free(sd);
3548 imhtml->scalables = g_list_delete_link(imhtml->scalables, l);
3550 l = next;
3553 sl = imhtml->im_images;
3554 while (sl) {
3555 GSList *next = sl->next;
3556 struct im_image_data *img_data = sl->data;
3557 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer,
3558 &i, img_data->mark);
3559 if (gtk_text_iter_in_range(&i, start, end)) {
3560 if (imhtml->funcs->image_unref)
3561 imhtml->funcs->image_unref(img_data->id);
3562 imhtml->im_images = g_slist_delete_link(imhtml->im_images, sl);
3563 g_free(img_data);
3565 sl = next;
3567 gtk_text_buffer_delete(imhtml->text_buffer, start, end);
3569 g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(0));
3571 g_object_unref(object);
3574 void gtk_imhtml_page_up (GtkIMHtml *imhtml)
3576 GdkRectangle rect;
3577 GtkTextIter iter;
3579 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect);
3580 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml), &iter, rect.x,
3581 rect.y - rect.height);
3582 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml), &iter, 0, TRUE, 0, 0);
3585 void gtk_imhtml_page_down (GtkIMHtml *imhtml)
3587 GdkRectangle rect;
3588 GtkTextIter iter;
3590 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect);
3591 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml), &iter, rect.x,
3592 rect.y + rect.height);
3593 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml), &iter, 0, TRUE, 0, 0);
3596 /* GtkIMHtmlScalable, gtk_imhtml_image, gtk_imhtml_hr */
3597 GtkIMHtmlScalable *gtk_imhtml_image_new(GdkPixbuf *img, const gchar *filename, int id)
3599 GtkIMHtmlImage *im_image = g_malloc(sizeof(GtkIMHtmlImage));
3601 GTK_IMHTML_SCALABLE(im_image)->scale = gtk_imhtml_image_scale;
3602 GTK_IMHTML_SCALABLE(im_image)->add_to = gtk_imhtml_image_add_to;
3603 GTK_IMHTML_SCALABLE(im_image)->free = gtk_imhtml_image_free;
3605 im_image->pixbuf = img;
3606 im_image->image = GTK_IMAGE(gtk_image_new_from_pixbuf(im_image->pixbuf));
3607 im_image->width = gdk_pixbuf_get_width(img);
3608 im_image->height = gdk_pixbuf_get_height(img);
3609 im_image->mark = NULL;
3610 im_image->filename = g_strdup(filename);
3611 im_image->id = id;
3612 im_image->filesel = NULL;
3614 g_object_ref(img);
3615 return GTK_IMHTML_SCALABLE(im_image);
3618 static gboolean
3619 animate_image_cb(gpointer data)
3621 GtkIMHtmlImage *im_image;
3622 int width, height;
3623 int delay;
3625 im_image = data;
3627 /* Update the pointer to this GdkPixbuf frame of the animation */
3628 if (gdk_pixbuf_animation_iter_advance(GTK_IMHTML_ANIMATION(im_image)->iter, NULL)) {
3629 GdkPixbuf *pb = gdk_pixbuf_animation_iter_get_pixbuf(GTK_IMHTML_ANIMATION(im_image)->iter);
3630 g_object_unref(G_OBJECT(im_image->pixbuf));
3631 im_image->pixbuf = gdk_pixbuf_copy(pb);
3633 /* Update the displayed GtkImage */
3634 width = gdk_pixbuf_get_width(gtk_image_get_pixbuf(im_image->image));
3635 height = gdk_pixbuf_get_height(gtk_image_get_pixbuf(im_image->image));
3636 if (width > 0 && height > 0)
3638 /* Need to scale the new frame to the same size as the old frame */
3639 GdkPixbuf *tmp;
3640 tmp = gdk_pixbuf_scale_simple(im_image->pixbuf, width, height, GDK_INTERP_BILINEAR);
3641 gtk_image_set_from_pixbuf(im_image->image, tmp);
3642 g_object_unref(G_OBJECT(tmp));
3643 } else {
3644 /* Display at full-size */
3645 gtk_image_set_from_pixbuf(im_image->image, im_image->pixbuf);
3649 delay = MIN(gdk_pixbuf_animation_iter_get_delay_time(GTK_IMHTML_ANIMATION(im_image)->iter), 100);
3650 GTK_IMHTML_ANIMATION(im_image)->timer = g_timeout_add(delay, animate_image_cb, im_image);
3652 return FALSE;
3655 GtkIMHtmlScalable *gtk_imhtml_animation_new(GdkPixbufAnimation *anim, const gchar *filename, int id)
3657 GtkIMHtmlImage *im_image = (GtkIMHtmlImage *) g_new0(GtkIMHtmlAnimation, 1);
3659 GTK_IMHTML_SCALABLE(im_image)->scale = gtk_imhtml_image_scale;
3660 GTK_IMHTML_SCALABLE(im_image)->add_to = gtk_imhtml_image_add_to;
3661 GTK_IMHTML_SCALABLE(im_image)->free = gtk_imhtml_animation_free;
3663 GTK_IMHTML_ANIMATION(im_image)->anim = anim;
3664 if (gdk_pixbuf_animation_is_static_image(anim)) {
3665 im_image->pixbuf = gdk_pixbuf_animation_get_static_image(anim);
3666 g_object_ref(im_image->pixbuf);
3667 } else {
3668 int delay;
3669 GdkPixbuf *pb;
3670 GTK_IMHTML_ANIMATION(im_image)->iter = gdk_pixbuf_animation_get_iter(anim, NULL);
3671 pb = gdk_pixbuf_animation_iter_get_pixbuf(GTK_IMHTML_ANIMATION(im_image)->iter);
3672 im_image->pixbuf = gdk_pixbuf_copy(pb);
3673 delay = MIN(gdk_pixbuf_animation_iter_get_delay_time(GTK_IMHTML_ANIMATION(im_image)->iter), 100);
3674 GTK_IMHTML_ANIMATION(im_image)->timer = g_timeout_add(delay, animate_image_cb, im_image);
3676 im_image->image = GTK_IMAGE(gtk_image_new_from_pixbuf(im_image->pixbuf));
3677 im_image->width = gdk_pixbuf_animation_get_width(anim);
3678 im_image->height = gdk_pixbuf_animation_get_height(anim);
3679 im_image->filename = g_strdup(filename);
3680 im_image->id = id;
3682 g_object_ref(anim);
3684 return GTK_IMHTML_SCALABLE(im_image);
3687 void gtk_imhtml_image_scale(GtkIMHtmlScalable *scale, int width, int height)
3689 GtkIMHtmlImage *im_image = (GtkIMHtmlImage *)scale;
3691 if (im_image->width > width || im_image->height > height) {
3692 double ratio_w, ratio_h, ratio;
3693 int new_h, new_w;
3694 GdkPixbuf *new_image = NULL;
3696 ratio_w = ((double)width - 2) / im_image->width;
3697 ratio_h = ((double)height - 2) / im_image->height;
3699 ratio = (ratio_w < ratio_h) ? ratio_w : ratio_h;
3701 new_w = (int)(im_image->width * ratio);
3702 new_h = (int)(im_image->height * ratio);
3704 new_image = gdk_pixbuf_scale_simple(im_image->pixbuf, new_w, new_h, GDK_INTERP_BILINEAR);
3705 gtk_image_set_from_pixbuf(im_image->image, new_image);
3706 g_object_unref(G_OBJECT(new_image));
3707 } else if (gdk_pixbuf_get_width(gtk_image_get_pixbuf(im_image->image)) != im_image->width) {
3708 /* Enough space to show the full-size of the image. */
3709 GdkPixbuf *new_image;
3711 new_image = gdk_pixbuf_scale_simple(im_image->pixbuf, im_image->width, im_image->height, GDK_INTERP_BILINEAR);
3712 gtk_image_set_from_pixbuf(im_image->image, new_image);
3713 g_object_unref(G_OBJECT(new_image));
3717 static void
3718 image_save_yes_cb(GtkIMHtmlImageSave *save, const char *filename)
3720 GError *error = NULL;
3721 GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image;
3723 gtk_widget_destroy(image->filesel);
3724 image->filesel = NULL;
3726 if (save->data && save->datasize) {
3727 g_file_set_contents(filename, save->data, save->datasize, &error);
3728 } else {
3729 gchar *type = NULL;
3730 GSList *formats = gdk_pixbuf_get_formats();
3731 char *newfilename;
3733 while (formats) {
3734 GdkPixbufFormat *format = formats->data;
3735 gchar **extensions = gdk_pixbuf_format_get_extensions(format);
3736 gpointer p = extensions;
3738 while(gdk_pixbuf_format_is_writable(format) && extensions && extensions[0]){
3739 gchar *fmt_ext = extensions[0];
3740 const gchar* file_ext = filename + strlen(filename) - strlen(fmt_ext);
3742 if(!g_ascii_strcasecmp(fmt_ext, file_ext)){
3743 type = gdk_pixbuf_format_get_name(format);
3744 break;
3747 extensions++;
3750 g_strfreev(p);
3752 if (type)
3753 break;
3755 formats = formats->next;
3758 g_slist_free(formats);
3760 /* If I can't find a valid type, I will just tell the user about it and then assume
3761 it's a png */
3762 if (!type){
3763 char *basename, *tmp;
3764 char *dirname;
3765 GtkWidget *dialog = gtk_message_dialog_new_with_markup(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
3766 _("<span size='larger' weight='bold'>Unrecognized file type</span>\n\nDefaulting to PNG."));
3768 g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog);
3769 gtk_widget_show(dialog);
3771 type = g_strdup("png");
3772 dirname = g_path_get_dirname(filename);
3773 basename = g_path_get_basename(filename);
3774 tmp = strrchr(basename, '.');
3775 if (tmp != NULL)
3776 tmp[0] = '\0';
3777 newfilename = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s.png", dirname, basename);
3778 g_free(dirname);
3779 g_free(basename);
3780 } else {
3782 * We're able to save the file in it's original format, so we
3783 * can use the original file name.
3785 newfilename = g_strdup(filename);
3788 gdk_pixbuf_save(image->pixbuf, newfilename, type, &error, NULL);
3790 g_free(newfilename);
3791 g_free(type);
3794 if (error){
3795 GtkWidget *dialog = gtk_message_dialog_new_with_markup(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
3796 _("<span size='larger' weight='bold'>Error saving image</span>\n\n%s"), error->message);
3797 g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog);
3798 gtk_widget_show(dialog);
3799 g_error_free(error);
3803 static void
3804 image_save_check_if_exists_cb(GtkWidget *widget, gint response, GtkIMHtmlImageSave *save)
3806 gchar *filename;
3807 GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image;
3809 if (response != GTK_RESPONSE_ACCEPT) {
3810 gtk_widget_destroy(widget);
3811 image->filesel = NULL;
3812 return;
3815 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
3818 * XXX - We should probably prompt the user to determine if they really
3819 * want to overwrite the file or not. However, I don't feel like doing
3820 * that, so we're just always going to overwrite if the file exists.
3823 if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
3824 } else
3825 image_save_yes_cb(image, filename);
3828 image_save_yes_cb(save, filename);
3830 g_free(filename);
3833 static void
3834 gtk_imhtml_image_save(GtkWidget *w, GtkIMHtmlImageSave *save)
3836 GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image;
3838 if (image->filesel != NULL) {
3839 gtk_window_present(GTK_WINDOW(image->filesel));
3840 return;
3843 image->filesel = gtk_file_chooser_dialog_new(_("Save Image"),
3844 NULL,
3845 GTK_FILE_CHOOSER_ACTION_SAVE,
3846 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
3847 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
3848 NULL);
3849 gtk_dialog_set_default_response(GTK_DIALOG(image->filesel), GTK_RESPONSE_ACCEPT);
3850 if (image->filename != NULL)
3851 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(image->filesel), image->filename);
3852 g_signal_connect(G_OBJECT(GTK_FILE_CHOOSER(image->filesel)), "response",
3853 G_CALLBACK(image_save_check_if_exists_cb), save);
3855 gtk_widget_show(image->filesel);
3858 static void
3859 gtk_imhtml_custom_smiley_save(GtkWidget *w, GtkIMHtmlImageSave *save)
3861 GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image;
3863 /* Create an add dialog */
3864 PidginSmiley *editor = pidgin_smiley_edit(NULL, NULL);
3865 pidgin_smiley_editor_set_shortcut(editor, image->filename);
3866 pidgin_smiley_editor_set_image(editor, image->pixbuf);
3867 pidgin_smiley_editor_set_data(editor, save->data, save->datasize);
3871 * So, um, AIM Direct IM lets you send any file, not just images. You can
3872 * just insert a sound or a file or whatever in a conversation. It's
3873 * basically like file transfer, except there is an icon to open the file
3874 * embedded in the conversation. Someone should make the Purple core handle
3875 * all of that.
3877 static gboolean gtk_imhtml_image_clicked(GtkWidget *w, GdkEvent *event, GtkIMHtmlImageSave *save)
3879 GdkEventButton *event_button = (GdkEventButton *) event;
3880 GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image;
3882 if (event->type == GDK_BUTTON_RELEASE) {
3883 if(event_button->button == 3) {
3884 GtkWidget *img, *item, *menu;
3885 menu = gtk_menu_new();
3887 /* buttons and such */
3888 img = gtk_image_new_from_stock(GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU);
3889 item = gtk_image_menu_item_new_with_mnemonic(_("_Save Image..."));
3890 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3891 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_image_save), save);
3892 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3894 /* Add menu item for adding custom smiley to local smileys */
3895 /* we only add the menu if the image is of "custom smiley size"
3896 <= 96x96 pixels */
3897 if (image->width <= 96 && image->height <= 96) {
3898 img = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
3899 item = gtk_image_menu_item_new_with_mnemonic(_("_Add Custom Smiley..."));
3900 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3901 g_signal_connect(G_OBJECT(item), "activate",
3902 G_CALLBACK(gtk_imhtml_custom_smiley_save), save);
3903 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3906 gtk_widget_show_all(menu);
3907 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
3908 event_button->button, event_button->time);
3910 return TRUE;
3913 if(event->type == GDK_BUTTON_PRESS && event_button->button == 3)
3914 return TRUE; /* Clicking the right mouse button on a link shouldn't
3915 be caught by the regular GtkTextView menu */
3916 else
3917 return FALSE; /* Let clicks go through if we didn't catch anything */
3921 static gboolean gtk_imhtml_smiley_clicked(GtkWidget *w, GdkEvent *event, GtkIMHtmlSmiley *smiley)
3923 GdkPixbufAnimation *anim = NULL;
3924 GtkIMHtmlImageSave *save = NULL;
3925 gboolean ret;
3927 if (event->type != GDK_BUTTON_RELEASE || ((GdkEventButton*)event)->button != 3)
3928 return FALSE;
3930 anim = gtk_smiley_get_image(smiley);
3931 if (!anim)
3932 return FALSE;
3934 save = g_new0(GtkIMHtmlImageSave, 1);
3935 save->image = (GtkIMHtmlScalable *)gtk_imhtml_animation_new(anim, smiley->smile, 0);
3936 save->data = smiley->data; /* Do not need to memdup here, since the smiley is not
3937 destroyed before this GtkIMHtmlImageSave */
3938 save->datasize = smiley->datasize;
3939 ret = gtk_imhtml_image_clicked(w, event, save);
3940 g_object_set_data_full(G_OBJECT(w), "image-data", save->image, (GDestroyNotify)gtk_imhtml_animation_free);
3941 g_object_set_data_full(G_OBJECT(w), "image-save-data", save, (GDestroyNotify)g_free);
3942 return ret;
3945 void gtk_imhtml_image_free(GtkIMHtmlScalable *scale)
3947 GtkIMHtmlImage *image = (GtkIMHtmlImage *)scale;
3949 g_object_unref(image->pixbuf);
3950 g_free(image->filename);
3951 if (image->filesel)
3952 gtk_widget_destroy(image->filesel);
3953 g_free(scale);
3956 void gtk_imhtml_animation_free(GtkIMHtmlScalable *scale)
3958 GtkIMHtmlAnimation *animation = (GtkIMHtmlAnimation *)scale;
3960 if (animation->timer > 0)
3961 g_source_remove(animation->timer);
3962 if (animation->iter != NULL)
3963 g_object_unref(animation->iter);
3964 g_object_unref(animation->anim);
3966 gtk_imhtml_image_free(scale);
3969 void gtk_imhtml_image_add_to(GtkIMHtmlScalable *scale, GtkIMHtml *imhtml, GtkTextIter *iter)
3971 GtkIMHtmlImage *image = (GtkIMHtmlImage *)scale;
3972 GtkWidget *box = gtk_event_box_new();
3973 char *tag;
3974 GtkTextChildAnchor *anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter);
3975 GtkIMHtmlImageSave *save;
3977 gtk_container_add(GTK_CONTAINER(box), GTK_WIDGET(image->image));
3979 if(!gtk_check_version(2, 4, 0))
3980 g_object_set(G_OBJECT(box), "visible-window", FALSE, NULL);
3982 gtk_widget_show(GTK_WIDGET(image->image));
3983 gtk_widget_show(box);
3985 tag = g_strdup_printf("<IMG ID=\"%d\">", image->id);
3986 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", tag, g_free);
3987 g_object_set_data(G_OBJECT(anchor), "gtkimhtml_plaintext", "[Image]");
3989 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), box, anchor);
3991 save = g_new0(GtkIMHtmlImageSave, 1);
3992 save->image = scale;
3993 g_signal_connect(G_OBJECT(box), "event", G_CALLBACK(gtk_imhtml_image_clicked), save);
3994 g_object_set_data_full(G_OBJECT(box), "image-save-data", save, (GDestroyNotify)g_free);
3997 GtkIMHtmlScalable *gtk_imhtml_hr_new()
3999 GtkIMHtmlHr *hr = g_malloc(sizeof(GtkIMHtmlHr));
4001 GTK_IMHTML_SCALABLE(hr)->scale = gtk_imhtml_hr_scale;
4002 GTK_IMHTML_SCALABLE(hr)->add_to = gtk_imhtml_hr_add_to;
4003 GTK_IMHTML_SCALABLE(hr)->free = gtk_imhtml_hr_free;
4005 hr->sep = gtk_hseparator_new();
4006 gtk_widget_set_size_request(hr->sep, 5000, 2);
4007 gtk_widget_show(hr->sep);
4009 return GTK_IMHTML_SCALABLE(hr);
4012 void gtk_imhtml_hr_scale(GtkIMHtmlScalable *scale, int width, int height)
4014 gtk_widget_set_size_request(((GtkIMHtmlHr *)scale)->sep, width - 2, 2);
4017 void gtk_imhtml_hr_add_to(GtkIMHtmlScalable *scale, GtkIMHtml *imhtml, GtkTextIter *iter)
4019 GtkIMHtmlHr *hr = (GtkIMHtmlHr *)scale;
4020 GtkTextChildAnchor *anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter);
4021 g_object_set_data(G_OBJECT(anchor), "gtkimhtml_htmltext", "<hr>");
4022 g_object_set_data(G_OBJECT(anchor), "gtkimhtml_plaintext", "\n---\n");
4023 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), hr->sep, anchor);
4026 void gtk_imhtml_hr_free(GtkIMHtmlScalable *scale)
4028 g_free(scale);
4031 gboolean gtk_imhtml_search_find(GtkIMHtml *imhtml, const gchar *text)
4033 GtkTextIter iter, start, end;
4034 gboolean new_search = TRUE;
4035 GtkTextMark *start_mark;
4037 g_return_val_if_fail(imhtml != NULL, FALSE);
4038 g_return_val_if_fail(text != NULL, FALSE);
4040 start_mark = gtk_text_buffer_get_mark(imhtml->text_buffer, "search");
4042 if (start_mark && imhtml->search_string && !strcmp(text, imhtml->search_string))
4043 new_search = FALSE;
4045 if (new_search) {
4046 gtk_imhtml_search_clear(imhtml);
4047 g_free(imhtml->search_string);
4048 imhtml->search_string = g_strdup(text);
4049 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &iter);
4050 } else {
4051 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter,
4052 start_mark);
4055 if (gtk_source_iter_backward_search(&iter, imhtml->search_string,
4056 GTK_SOURCE_SEARCH_VISIBLE_ONLY | GTK_SOURCE_SEARCH_CASE_INSENSITIVE,
4057 &start, &end, NULL))
4059 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml), &start, 0, TRUE, 0, 0);
4060 gtk_text_buffer_create_mark(imhtml->text_buffer, "search", &start, FALSE);
4061 if (new_search)
4063 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "search", &iter, &end);
4065 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "search", &start, &end);
4066 while (gtk_source_iter_backward_search(&start, imhtml->search_string,
4067 GTK_SOURCE_SEARCH_VISIBLE_ONLY |
4068 GTK_SOURCE_SEARCH_CASE_INSENSITIVE,
4069 &start, &end, NULL));
4071 return TRUE;
4073 else if (!new_search)
4075 /* We hit the end, so start at the beginning again. */
4076 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &iter);
4078 if (gtk_source_iter_backward_search(&iter, imhtml->search_string,
4079 GTK_SOURCE_SEARCH_VISIBLE_ONLY | GTK_SOURCE_SEARCH_CASE_INSENSITIVE,
4080 &start, &end, NULL))
4082 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml), &start, 0, TRUE, 0, 0);
4083 gtk_text_buffer_create_mark(imhtml->text_buffer, "search", &start, FALSE);
4085 return TRUE;
4090 return FALSE;
4093 void gtk_imhtml_search_clear(GtkIMHtml *imhtml)
4095 GtkTextIter start, end;
4097 g_return_if_fail(imhtml != NULL);
4099 gtk_text_buffer_get_start_iter(imhtml->text_buffer, &start);
4100 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &end);
4102 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "search", &start, &end);
4103 g_free(imhtml->search_string);
4104 imhtml->search_string = NULL;
4107 static GtkTextTag *find_font_forecolor_tag(GtkIMHtml *imhtml, gchar *color)
4109 gchar str[18];
4110 GtkTextTag *tag;
4112 g_snprintf(str, sizeof(str), "FORECOLOR %s", color);
4114 tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str);
4115 if (!tag) {
4116 GdkColor gcolor;
4117 if (!gdk_color_parse(color, &gcolor)) {
4118 gchar tmp[8];
4119 tmp[0] = '#';
4120 strncpy(&tmp[1], color, 7);
4121 tmp[7] = '\0';
4122 if (!gdk_color_parse(tmp, &gcolor))
4123 gdk_color_parse("black", &gcolor);
4125 tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "foreground-gdk", &gcolor, NULL);
4128 return tag;
4131 static GtkTextTag *find_font_backcolor_tag(GtkIMHtml *imhtml, gchar *color)
4133 gchar str[18];
4134 GtkTextTag *tag;
4136 g_snprintf(str, sizeof(str), "BACKCOLOR %s", color);
4138 tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str);
4139 if (!tag) {
4140 GdkColor gcolor;
4141 if (!gdk_color_parse(color, &gcolor)) {
4142 gchar tmp[8];
4143 tmp[0] = '#';
4144 strncpy(&tmp[1], color, 7);
4145 tmp[7] = '\0';
4146 if (!gdk_color_parse(tmp, &gcolor))
4147 gdk_color_parse("white", &gcolor);
4149 tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "background-gdk", &gcolor, NULL);
4152 return tag;
4155 static GtkTextTag *find_font_background_tag(GtkIMHtml *imhtml, gchar *color)
4157 gchar str[19];
4158 GtkTextTag *tag;
4160 g_snprintf(str, sizeof(str), "BACKGROUND %s", color);
4162 tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str);
4163 if (!tag)
4164 tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, NULL);
4166 return tag;
4169 static GtkTextTag *find_font_face_tag(GtkIMHtml *imhtml, gchar *face)
4171 gchar str[256];
4172 GtkTextTag *tag;
4174 g_snprintf(str, sizeof(str), "FONT FACE %s", face);
4175 str[255] = '\0';
4177 tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str);
4178 if (!tag)
4179 tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "family", face, NULL);
4181 return tag;
4184 static GtkTextTag *find_font_size_tag(GtkIMHtml *imhtml, int size)
4186 gchar str[24];
4187 GtkTextTag *tag;
4189 g_snprintf(str, sizeof(str), "FONT SIZE %d", size);
4190 str[23] = '\0';
4192 tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str);
4193 if (!tag) {
4194 /* For reasons I don't understand, setting "scale" here scaled
4195 * based on some default size other than my theme's default
4196 * size. Our size 4 was actually smaller than our size 3 for
4197 * me. So this works around that oddity.
4199 GtkTextAttributes *attr = gtk_text_view_get_default_attributes(GTK_TEXT_VIEW(imhtml));
4200 tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "size",
4201 (gint) (pango_font_description_get_size(attr->font) *
4202 (double) POINT_SIZE(size)), NULL);
4203 gtk_text_attributes_unref(attr);
4206 return tag;
4209 static void remove_tag_by_prefix(GtkIMHtml *imhtml, const GtkTextIter *i, const GtkTextIter *e,
4210 const char *prefix, guint len, gboolean homo)
4212 GSList *tags, *l;
4213 GtkTextIter iter;
4215 tags = gtk_text_iter_get_tags(i);
4217 for (l = tags; l; l = l->next) {
4218 GtkTextTag *tag = l->data;
4220 if (tag->name && !strncmp(tag->name, prefix, len))
4221 gtk_text_buffer_remove_tag(imhtml->text_buffer, tag, i, e);
4224 g_slist_free(tags);
4226 if (homo)
4227 return;
4229 iter = *i;
4231 while (gtk_text_iter_forward_char(&iter) && !gtk_text_iter_equal(&iter, e)) {
4232 if (gtk_text_iter_begins_tag(&iter, NULL)) {
4233 tags = gtk_text_iter_get_toggled_tags(&iter, TRUE);
4235 for (l = tags; l; l = l->next) {
4236 GtkTextTag *tag = l->data;
4238 if (tag->name && !strncmp(tag->name, prefix, len))
4239 gtk_text_buffer_remove_tag(imhtml->text_buffer, tag, &iter, e);
4242 g_slist_free(tags);
4247 static void remove_font_size(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4249 remove_tag_by_prefix(imhtml, i, e, "FONT SIZE ", 10, homo);
4252 static void remove_font_face(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4254 remove_tag_by_prefix(imhtml, i, e, "FONT FACE ", 10, homo);
4257 static void remove_font_forecolor(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4259 remove_tag_by_prefix(imhtml, i, e, "FORECOLOR ", 10, homo);
4262 static void remove_font_backcolor(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4264 remove_tag_by_prefix(imhtml, i, e, "BACKCOLOR ", 10, homo);
4267 static void remove_font_background(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4269 remove_tag_by_prefix(imhtml, i, e, "BACKGROUND ", 10, homo);
4272 static void remove_font_link(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4274 remove_tag_by_prefix(imhtml, i, e, "LINK ", 5, homo);
4277 static void
4278 imhtml_clear_formatting(GtkIMHtml *imhtml)
4280 GtkTextIter start, end;
4282 if (!imhtml->editable)
4283 return;
4285 if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4286 return;
4288 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "BOLD", &start, &end);
4289 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "ITALICS", &start, &end);
4290 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "UNDERLINE", &start, &end);
4291 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "STRIKE", &start, &end);
4292 remove_font_size(imhtml, &start, &end, FALSE);
4293 remove_font_face(imhtml, &start, &end, FALSE);
4294 remove_font_forecolor(imhtml, &start, &end, FALSE);
4295 remove_font_backcolor(imhtml, &start, &end, FALSE);
4296 remove_font_background(imhtml, &start, &end, FALSE);
4297 remove_font_link(imhtml, &start, &end, FALSE);
4299 imhtml->edit.bold = 0;
4300 imhtml->edit.italic = 0;
4301 imhtml->edit.underline = 0;
4302 imhtml->edit.strike = 0;
4303 imhtml->edit.fontsize = 0;
4305 g_free(imhtml->edit.fontface);
4306 imhtml->edit.fontface = NULL;
4308 g_free(imhtml->edit.forecolor);
4309 imhtml->edit.forecolor = NULL;
4311 g_free(imhtml->edit.backcolor);
4312 imhtml->edit.backcolor = NULL;
4314 g_free(imhtml->edit.background);
4315 imhtml->edit.background = NULL;
4318 /* Editable stuff */
4319 static void preinsert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml)
4321 imhtml->insert_offset = gtk_text_iter_get_offset(iter);
4324 static void insert_ca_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextChildAnchor *arg2, gpointer user_data)
4326 GtkTextIter start;
4328 start = *arg1;
4329 gtk_text_iter_backward_char(&start);
4331 gtk_imhtml_apply_tags_on_insert(user_data, &start, arg1);
4334 static void insert_cb(GtkTextBuffer *buffer, GtkTextIter *end, gchar *text, gint len, GtkIMHtml *imhtml)
4336 GtkTextIter start;
4338 if (!len)
4339 return;
4341 start = *end;
4342 gtk_text_iter_set_offset(&start, imhtml->insert_offset);
4344 gtk_imhtml_apply_tags_on_insert(imhtml, &start, end);
4347 static void delete_cb(GtkTextBuffer *buffer, GtkTextIter *start, GtkTextIter *end, GtkIMHtml *imhtml)
4349 GSList *tags, *l;
4351 tags = gtk_text_iter_get_tags(start);
4352 for (l = tags; l != NULL; l = l->next) {
4353 GtkTextTag *tag = GTK_TEXT_TAG(l->data);
4355 if (tag && /* Remove the formatting only if */
4356 gtk_text_iter_starts_word(start) && /* beginning of a word */
4357 gtk_text_iter_begins_tag(start, tag) && /* the tag starts with the selection */
4358 (!gtk_text_iter_has_tag(end, tag) || /* the tag ends within the selection */
4359 gtk_text_iter_ends_tag(end, tag))) {
4360 gtk_text_buffer_remove_tag(imhtml->text_buffer, tag, start, end);
4361 if (tag->name &&
4362 strncmp(tag->name, "LINK ", 5) == 0 && imhtml->edit.link) {
4363 gtk_imhtml_toggle_link(imhtml, NULL);
4367 g_slist_free(tags);
4370 static void gtk_imhtml_apply_tags_on_insert(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end)
4372 if (imhtml->edit.bold)
4373 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "BOLD", start, end);
4374 else
4375 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "BOLD", start, end);
4377 if (imhtml->edit.italic)
4378 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "ITALICS", start, end);
4379 else
4380 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "ITALICS", start, end);
4382 if (imhtml->edit.underline)
4383 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "UNDERLINE", start, end);
4384 else
4385 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "UNDERLINE", start, end);
4387 if (imhtml->edit.strike)
4388 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "STRIKE", start, end);
4389 else
4390 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "STRIKE", start, end);
4392 if (imhtml->edit.forecolor) {
4393 remove_font_forecolor(imhtml, start, end, TRUE);
4394 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4395 find_font_forecolor_tag(imhtml, imhtml->edit.forecolor),
4396 start, end);
4399 if (imhtml->edit.backcolor) {
4400 remove_font_backcolor(imhtml, start, end, TRUE);
4401 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4402 find_font_backcolor_tag(imhtml, imhtml->edit.backcolor),
4403 start, end);
4406 if (imhtml->edit.background) {
4407 remove_font_background(imhtml, start, end, TRUE);
4408 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4409 find_font_background_tag(imhtml, imhtml->edit.background),
4410 start, end);
4412 if (imhtml->edit.fontface) {
4413 remove_font_face(imhtml, start, end, TRUE);
4414 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4415 find_font_face_tag(imhtml, imhtml->edit.fontface),
4416 start, end);
4419 if (imhtml->edit.fontsize) {
4420 remove_font_size(imhtml, start, end, TRUE);
4421 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4422 find_font_size_tag(imhtml, imhtml->edit.fontsize),
4423 start, end);
4426 if (imhtml->edit.link) {
4427 remove_font_link(imhtml, start, end, TRUE);
4428 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4429 imhtml->edit.link,
4430 start, end);
4434 void gtk_imhtml_set_editable(GtkIMHtml *imhtml, gboolean editable)
4436 gtk_text_view_set_editable(GTK_TEXT_VIEW(imhtml), editable);
4438 * We need a visible caret for accessibility, so mouseless
4439 * people can highlight stuff.
4441 /* gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(imhtml), editable); */
4442 if (editable && !imhtml->editable) {
4443 g_signal_connect_after(G_OBJECT(GTK_IMHTML(imhtml)->text_buffer), "mark-set",
4444 G_CALLBACK(mark_set_cb), imhtml);
4445 g_signal_connect(G_OBJECT(imhtml), "backspace", G_CALLBACK(smart_backspace_cb), NULL);
4446 } else if (!editable && imhtml->editable) {
4447 g_signal_handlers_disconnect_by_func(G_OBJECT(GTK_IMHTML(imhtml)->text_buffer),
4448 mark_set_cb, imhtml);
4449 g_signal_handlers_disconnect_by_func(G_OBJECT(imhtml),
4450 smart_backspace_cb, NULL);
4453 imhtml->editable = editable;
4454 imhtml->format_functions = GTK_IMHTML_ALL;
4457 void gtk_imhtml_set_whole_buffer_formatting_only(GtkIMHtml *imhtml, gboolean wbfo)
4459 g_return_if_fail(imhtml != NULL);
4461 imhtml->wbfo = wbfo;
4464 void gtk_imhtml_set_format_functions(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons)
4466 GObject *object = g_object_ref(G_OBJECT(imhtml));
4467 imhtml->format_functions = buttons;
4468 g_signal_emit(object, signals[BUTTONS_UPDATE], 0, buttons);
4469 g_object_unref(object);
4472 GtkIMHtmlButtons gtk_imhtml_get_format_functions(GtkIMHtml *imhtml)
4474 return imhtml->format_functions;
4477 void gtk_imhtml_get_current_format(GtkIMHtml *imhtml, gboolean *bold,
4478 gboolean *italic, gboolean *underline)
4480 if (bold != NULL)
4481 (*bold) = imhtml->edit.bold;
4482 if (italic != NULL)
4483 (*italic) = imhtml->edit.italic;
4484 if (underline != NULL)
4485 (*underline) = imhtml->edit.underline;
4488 char *
4489 gtk_imhtml_get_current_fontface(GtkIMHtml *imhtml)
4491 return g_strdup(imhtml->edit.fontface);
4494 char *
4495 gtk_imhtml_get_current_forecolor(GtkIMHtml *imhtml)
4497 return g_strdup(imhtml->edit.forecolor);
4500 char *
4501 gtk_imhtml_get_current_backcolor(GtkIMHtml *imhtml)
4503 return g_strdup(imhtml->edit.backcolor);
4506 char *
4507 gtk_imhtml_get_current_background(GtkIMHtml *imhtml)
4509 return g_strdup(imhtml->edit.background);
4512 gint
4513 gtk_imhtml_get_current_fontsize(GtkIMHtml *imhtml)
4515 return imhtml->edit.fontsize;
4518 gboolean gtk_imhtml_get_editable(GtkIMHtml *imhtml)
4520 return imhtml->editable;
4523 void
4524 gtk_imhtml_clear_formatting(GtkIMHtml *imhtml)
4526 GObject *object;
4528 object = g_object_ref(G_OBJECT(imhtml));
4529 g_signal_emit(object, signals[CLEAR_FORMAT], 0);
4531 gtk_widget_grab_focus(GTK_WIDGET(imhtml));
4533 g_object_unref(object);
4537 * I had this crazy idea about changing the text cursor color to reflex the foreground color
4538 * of the text about to be entered. This is the place you'd do it, along with the place where
4539 * we actually set a new foreground color.
4540 * I may not do this, because people will bitch about Purple overriding their gtk theme's cursor
4541 * colors.
4543 * Just in case I do do this, I asked about what to set the secondary text cursor to.
4545 * (12:45:27) ?? ???: secondary_cursor_color = (rgb(background) + rgb(primary_cursor_color) ) / 2
4546 * (12:45:55) ?? ???: understand?
4547 * (12:46:14) Tim: yeah. i didn't know there was an exact formula
4548 * (12:46:56) ?? ???: u might need to extract separate each color from RGB
4551 static void mark_set_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextMark *mark,
4552 GtkIMHtml *imhtml)
4554 GSList *tags, *l;
4555 GtkTextIter iter;
4557 if (mark != gtk_text_buffer_get_insert(buffer))
4558 return;
4560 if (!gtk_text_buffer_get_char_count(buffer))
4561 return;
4563 imhtml->edit.bold = imhtml->edit.italic = imhtml->edit.underline = imhtml->edit.strike = FALSE;
4564 g_free(imhtml->edit.forecolor);
4565 imhtml->edit.forecolor = NULL;
4567 g_free(imhtml->edit.backcolor);
4568 imhtml->edit.backcolor = NULL;
4570 g_free(imhtml->edit.fontface);
4571 imhtml->edit.fontface = NULL;
4573 imhtml->edit.fontsize = 0;
4574 imhtml->edit.link = NULL;
4576 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, mark);
4579 if (gtk_text_iter_is_end(&iter))
4580 tags = gtk_text_iter_get_toggled_tags(&iter, FALSE);
4581 else
4582 tags = gtk_text_iter_get_tags(&iter);
4584 for (l = tags; l != NULL; l = l->next) {
4585 GtkTextTag *tag = GTK_TEXT_TAG(l->data);
4587 if (tag->name) {
4588 if (strcmp(tag->name, "BOLD") == 0)
4589 imhtml->edit.bold = TRUE;
4590 else if (strcmp(tag->name, "ITALICS") == 0)
4591 imhtml->edit.italic = TRUE;
4592 else if (strcmp(tag->name, "UNDERLINE") == 0)
4593 imhtml->edit.underline = TRUE;
4594 else if (strcmp(tag->name, "STRIKE") == 0)
4595 imhtml->edit.strike = TRUE;
4596 else if (strncmp(tag->name, "FORECOLOR ", 10) == 0)
4597 imhtml->edit.forecolor = g_strdup(&(tag->name)[10]);
4598 else if (strncmp(tag->name, "BACKCOLOR ", 10) == 0)
4599 imhtml->edit.backcolor = g_strdup(&(tag->name)[10]);
4600 else if (strncmp(tag->name, "FONT FACE ", 10) == 0)
4601 imhtml->edit.fontface = g_strdup(&(tag->name)[10]);
4602 else if (strncmp(tag->name, "FONT SIZE ", 10) == 0)
4603 imhtml->edit.fontsize = strtol(&(tag->name)[10], NULL, 10);
4604 else if ((strncmp(tag->name, "LINK ", 5) == 0) && !gtk_text_iter_is_end(&iter))
4605 imhtml->edit.link = tag;
4609 g_slist_free(tags);
4612 static void imhtml_emit_signal_for_format(GtkIMHtml *imhtml, GtkIMHtmlButtons button)
4614 GObject *object;
4616 g_return_if_fail(imhtml != NULL);
4618 object = g_object_ref(G_OBJECT(imhtml));
4619 g_signal_emit(object, signals[TOGGLE_FORMAT], 0, button);
4620 g_object_unref(object);
4623 static void imhtml_toggle_bold(GtkIMHtml *imhtml)
4625 GtkTextIter start, end;
4627 imhtml->edit.bold = !imhtml->edit.bold;
4629 if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4630 return;
4632 if (imhtml->edit.bold)
4633 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "BOLD", &start, &end);
4634 else
4635 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "BOLD", &start, &end);
4638 void gtk_imhtml_toggle_bold(GtkIMHtml *imhtml)
4640 imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_BOLD);
4643 static void imhtml_toggle_italic(GtkIMHtml *imhtml)
4645 GtkTextIter start, end;
4647 imhtml->edit.italic = !imhtml->edit.italic;
4649 if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4650 return;
4652 if (imhtml->edit.italic)
4653 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "ITALICS", &start, &end);
4654 else
4655 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "ITALICS", &start, &end);
4658 void gtk_imhtml_toggle_italic(GtkIMHtml *imhtml)
4660 imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_ITALIC);
4663 static void imhtml_toggle_underline(GtkIMHtml *imhtml)
4665 GtkTextIter start, end;
4667 imhtml->edit.underline = !imhtml->edit.underline;
4669 if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4670 return;
4672 if (imhtml->edit.underline)
4673 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "UNDERLINE", &start, &end);
4674 else
4675 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "UNDERLINE", &start, &end);
4678 void gtk_imhtml_toggle_underline(GtkIMHtml *imhtml)
4680 imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_UNDERLINE);
4683 static void imhtml_toggle_strike(GtkIMHtml *imhtml)
4685 GtkTextIter start, end;
4687 imhtml->edit.strike = !imhtml->edit.strike;
4689 if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4690 return;
4692 if (imhtml->edit.strike)
4693 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "STRIKE", &start, &end);
4694 else
4695 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "STRIKE", &start, &end);
4698 void gtk_imhtml_toggle_strike(GtkIMHtml *imhtml)
4700 imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_STRIKE);
4703 void gtk_imhtml_font_set_size(GtkIMHtml *imhtml, gint size)
4705 GObject *object;
4706 GtkTextIter start, end;
4708 imhtml->edit.fontsize = size;
4710 if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4711 return;
4713 remove_font_size(imhtml, &start, &end, imhtml->wbfo);
4714 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4715 find_font_size_tag(imhtml, imhtml->edit.fontsize), &start, &end);
4717 object = g_object_ref(G_OBJECT(imhtml));
4718 g_signal_emit(object, signals[TOGGLE_FORMAT], 0, GTK_IMHTML_SHRINK | GTK_IMHTML_GROW);
4719 g_object_unref(object);
4722 static void imhtml_font_shrink(GtkIMHtml *imhtml)
4724 GtkTextIter start, end;
4726 if (imhtml->edit.fontsize == 1)
4727 return;
4729 if (!imhtml->edit.fontsize)
4730 imhtml->edit.fontsize = 2;
4731 else
4732 imhtml->edit.fontsize--;
4734 if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4735 return;
4736 remove_font_size(imhtml, &start, &end, imhtml->wbfo);
4737 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4738 find_font_size_tag(imhtml, imhtml->edit.fontsize), &start, &end);
4741 void gtk_imhtml_font_shrink(GtkIMHtml *imhtml)
4743 imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_SHRINK);
4746 static void imhtml_font_grow(GtkIMHtml *imhtml)
4748 GtkTextIter start, end;
4750 if (imhtml->edit.fontsize == MAX_FONT_SIZE)
4751 return;
4753 if (!imhtml->edit.fontsize)
4754 imhtml->edit.fontsize = 4;
4755 else
4756 imhtml->edit.fontsize++;
4758 if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4759 return;
4760 remove_font_size(imhtml, &start, &end, imhtml->wbfo);
4761 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4762 find_font_size_tag(imhtml, imhtml->edit.fontsize), &start, &end);
4765 void gtk_imhtml_font_grow(GtkIMHtml *imhtml)
4767 imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_GROW);
4770 static gboolean gtk_imhtml_toggle_str_tag(GtkIMHtml *imhtml, const char *value, char **edit_field,
4771 void (*remove_func)(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo),
4772 GtkTextTag *(find_func)(GtkIMHtml *imhtml, gchar *color), GtkIMHtmlButtons button)
4774 GObject *object;
4775 GtkTextIter start;
4776 GtkTextIter end;
4778 g_free(*edit_field);
4779 *edit_field = NULL;
4781 if (value && strcmp(value, "") != 0)
4783 *edit_field = g_strdup(value);
4785 if (imhtml_get_iter_bounds(imhtml, &start, &end)) {
4786 remove_func(imhtml, &start, &end, imhtml->wbfo);
4787 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4788 find_func(imhtml, *edit_field), &start, &end);
4791 else
4793 if (imhtml_get_iter_bounds(imhtml, &start, &end))
4794 remove_func(imhtml, &start, &end, TRUE); /* 'TRUE' or 'imhtml->wbfo'? */
4797 object = g_object_ref(G_OBJECT(imhtml));
4798 g_signal_emit(object, signals[TOGGLE_FORMAT], 0, button);
4799 g_object_unref(object);
4801 return *edit_field != NULL;
4804 gboolean gtk_imhtml_toggle_forecolor(GtkIMHtml *imhtml, const char *color)
4806 return gtk_imhtml_toggle_str_tag(imhtml, color, &imhtml->edit.forecolor,
4807 remove_font_forecolor, find_font_forecolor_tag,
4808 GTK_IMHTML_FORECOLOR);
4811 gboolean gtk_imhtml_toggle_backcolor(GtkIMHtml *imhtml, const char *color)
4813 return gtk_imhtml_toggle_str_tag(imhtml, color, &imhtml->edit.backcolor,
4814 remove_font_backcolor, find_font_backcolor_tag,
4815 GTK_IMHTML_BACKCOLOR);
4818 gboolean gtk_imhtml_toggle_background(GtkIMHtml *imhtml, const char *color)
4820 return gtk_imhtml_toggle_str_tag(imhtml, color, &imhtml->edit.background,
4821 remove_font_background, find_font_background_tag,
4822 GTK_IMHTML_BACKGROUND);
4825 gboolean gtk_imhtml_toggle_fontface(GtkIMHtml *imhtml, const char *face)
4827 return gtk_imhtml_toggle_str_tag(imhtml, face, &imhtml->edit.fontface,
4828 remove_font_face, find_font_face_tag,
4829 GTK_IMHTML_FACE);
4832 void gtk_imhtml_toggle_link(GtkIMHtml *imhtml, const char *url)
4834 GObject *object;
4835 GtkTextIter start, end;
4836 GtkTextTag *linktag;
4837 static guint linkno = 0;
4838 gchar str[48];
4839 GdkColor *color = NULL;
4841 imhtml->edit.link = NULL;
4843 if (url) {
4844 g_snprintf(str, sizeof(str), "LINK %d", linkno++);
4845 str[47] = '\0';
4847 gtk_widget_style_get(GTK_WIDGET(imhtml), "hyperlink-color", &color, NULL);
4848 if (color) {
4849 imhtml->edit.link = linktag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "foreground-gdk", color, "underline", PANGO_UNDERLINE_SINGLE, NULL);
4850 gdk_color_free(color);
4851 } else {
4852 imhtml->edit.link = linktag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE, NULL);
4854 g_object_set_data_full(G_OBJECT(linktag), "link_url", g_strdup(url), g_free);
4855 g_signal_connect(G_OBJECT(linktag), "event", G_CALLBACK(tag_event), NULL);
4857 if (imhtml->editable && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) {
4858 remove_font_link(imhtml, &start, &end, FALSE);
4859 gtk_text_buffer_apply_tag(imhtml->text_buffer, linktag, &start, &end);
4863 object = g_object_ref(G_OBJECT(imhtml));
4864 g_signal_emit(object, signals[TOGGLE_FORMAT], 0, GTK_IMHTML_LINK);
4865 g_object_unref(object);
4868 void gtk_imhtml_insert_link(GtkIMHtml *imhtml, GtkTextMark *mark, const char *url, const char *text)
4870 GtkTextIter iter;
4872 /* Delete any currently selected text */
4873 gtk_text_buffer_delete_selection(imhtml->text_buffer, TRUE, TRUE);
4875 gtk_imhtml_toggle_link(imhtml, url);
4876 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, mark);
4877 gtk_text_buffer_insert(imhtml->text_buffer, &iter, text, -1);
4878 gtk_imhtml_toggle_link(imhtml, NULL);
4881 void gtk_imhtml_insert_smiley(GtkIMHtml *imhtml, const char *sml, char *smiley)
4883 GtkTextMark *mark;
4884 GtkTextIter iter;
4886 /* Delete any currently selected text */
4887 gtk_text_buffer_delete_selection(imhtml->text_buffer, TRUE, TRUE);
4889 mark = gtk_text_buffer_get_insert(imhtml->text_buffer);
4891 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, mark);
4892 gtk_text_buffer_begin_user_action(imhtml->text_buffer);
4893 gtk_imhtml_insert_smiley_at_iter(imhtml, sml, smiley, &iter);
4894 gtk_text_buffer_end_user_action(imhtml->text_buffer);
4897 static gboolean
4898 image_expose(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
4900 GTK_WIDGET_CLASS(GTK_WIDGET_GET_CLASS(widget))->expose_event(widget, event);
4902 return TRUE;
4905 /* In case the smiley gets removed from the imhtml before it gets removed from the queue */
4906 static void animated_smiley_destroy_cb(GtkObject *widget, GtkIMHtml *imhtml)
4908 GList *l = imhtml->animations->head;
4909 while (l) {
4910 GList *next = l->next;
4911 if (l->data == widget) {
4912 if (l == imhtml->animations->tail)
4913 imhtml->animations->tail = imhtml->animations->tail->prev;
4914 imhtml->animations->head = g_list_delete_link(imhtml->animations->head, l);
4915 imhtml->num_animations--;
4917 l = next;
4921 void gtk_imhtml_insert_smiley_at_iter(GtkIMHtml *imhtml, const char *sml, char *smiley, GtkTextIter *iter)
4923 GdkPixbuf *pixbuf = NULL;
4924 GdkPixbufAnimation *annipixbuf = NULL;
4925 GtkWidget *icon = NULL;
4926 GtkTextChildAnchor *anchor = NULL;
4927 char *unescaped;
4928 GtkIMHtmlSmiley *imhtml_smiley;
4929 GtkWidget *ebox = NULL;
4930 int numsmileys_thismsg, numsmileys_total;
4933 * This GtkIMHtml has the maximum number of smileys allowed, so don't
4934 * add any more. We do this for performance reasons, because smileys
4935 * are apparently pretty inefficient. Hopefully we can remove this
4936 * restriction when we're using a better HTML widget.
4938 unescaped = purple_unescape_html(smiley);
4939 numsmileys_thismsg = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_thismsg"));
4940 if (numsmileys_thismsg >= 30) {
4941 gtk_text_buffer_insert(imhtml->text_buffer, iter, unescaped, -1);
4942 g_free(unescaped);
4943 return;
4945 numsmileys_total = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_total"));
4946 if (numsmileys_total >= 300) {
4947 gtk_text_buffer_insert(imhtml->text_buffer, iter, unescaped, -1);
4948 g_free(unescaped);
4949 return;
4952 imhtml_smiley = gtk_imhtml_smiley_get(imhtml, sml, unescaped);
4954 if (imhtml->format_functions & GTK_IMHTML_SMILEY) {
4955 annipixbuf = imhtml_smiley ? gtk_smiley_get_image(imhtml_smiley) : NULL;
4956 if (annipixbuf) {
4957 if (gdk_pixbuf_animation_is_static_image(annipixbuf)) {
4958 pixbuf = gdk_pixbuf_animation_get_static_image(annipixbuf);
4959 if (pixbuf)
4960 icon = gtk_image_new_from_pixbuf(pixbuf);
4961 } else {
4962 icon = gtk_image_new_from_animation(annipixbuf);
4963 if (imhtml->num_animations == 20) {
4964 GtkImage *image = GTK_IMAGE(g_queue_pop_head(imhtml->animations));
4965 GdkPixbufAnimation *anim = gtk_image_get_animation(image);
4966 g_signal_handlers_disconnect_matched(G_OBJECT(image), G_SIGNAL_MATCH_FUNC,
4967 0, 0, NULL, G_CALLBACK(animated_smiley_destroy_cb), NULL);
4968 if (anim) {
4969 GdkPixbuf *pb = gdk_pixbuf_animation_get_static_image(anim);
4970 if (pb != NULL) {
4971 GdkPixbuf *copy = gdk_pixbuf_copy(pb);
4972 gtk_image_set_from_pixbuf(image, copy);
4973 g_object_unref(G_OBJECT(copy));
4976 } else {
4977 imhtml->num_animations++;
4979 g_signal_connect(G_OBJECT(icon), "destroy", G_CALLBACK(animated_smiley_destroy_cb), imhtml);
4980 g_queue_push_tail(imhtml->animations, icon);
4985 if (imhtml_smiley && imhtml_smiley->flags & GTK_IMHTML_SMILEY_CUSTOM) {
4986 ebox = gtk_event_box_new();
4987 gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox), FALSE);
4988 gtk_widget_show(ebox);
4991 if (icon) {
4992 anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter);
4993 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", g_strdup(unescaped), g_free);
4994 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_tiptext", g_strdup(unescaped), g_free);
4995 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley), g_free);
4997 /* This catches the expose events generated by animated
4998 * images, and ensures that they are handled by the image
4999 * itself, without propagating to the textview and causing
5000 * a complete refresh */
5001 g_signal_connect(G_OBJECT(icon), "expose-event", G_CALLBACK(image_expose), NULL);
5003 gtk_widget_show(icon);
5004 if (ebox)
5005 gtk_container_add(GTK_CONTAINER(ebox), icon);
5006 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), ebox ? ebox : icon, anchor);
5008 g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(numsmileys_thismsg + 1));
5009 g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(numsmileys_total + 1));
5010 } else if (imhtml_smiley != NULL && (imhtml->format_functions & GTK_IMHTML_SMILEY)) {
5011 anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter);
5012 imhtml_smiley->anchors = g_slist_append(imhtml_smiley->anchors, g_object_ref(anchor));
5013 if (ebox) {
5014 GtkWidget *img = gtk_image_new_from_stock(GTK_STOCK_MISSING_IMAGE, GTK_ICON_SIZE_MENU);
5015 gtk_container_add(GTK_CONTAINER(ebox), img);
5016 gtk_widget_show(img);
5017 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", g_strdup(unescaped), g_free);
5018 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_tiptext", g_strdup(unescaped), g_free);
5019 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley), g_free);
5020 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), ebox, anchor);
5023 g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(numsmileys_thismsg + 1));
5024 g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(numsmileys_total + 1));
5025 } else {
5026 gtk_text_buffer_insert(imhtml->text_buffer, iter, unescaped, -1);
5029 if (ebox) {
5030 g_signal_connect(G_OBJECT(ebox), "event", G_CALLBACK(gtk_imhtml_smiley_clicked), imhtml_smiley);
5033 g_free(unescaped);
5036 void gtk_imhtml_insert_image_at_iter(GtkIMHtml *imhtml, int id, GtkTextIter *iter)
5038 GdkPixbufAnimation *anim = NULL;
5039 const char *filename = NULL;
5040 gpointer image;
5041 GdkRectangle rect;
5042 GtkIMHtmlScalable *scalable = NULL;
5043 struct scalable_data *sd;
5044 int minus;
5046 if (!imhtml->funcs || !imhtml->funcs->image_get ||
5047 !imhtml->funcs->image_get_size || !imhtml->funcs->image_get_data ||
5048 !imhtml->funcs->image_get_filename || !imhtml->funcs->image_ref ||
5049 !imhtml->funcs->image_unref)
5050 return;
5052 image = imhtml->funcs->image_get(id);
5054 if (image) {
5055 gpointer data;
5056 size_t len;
5058 data = imhtml->funcs->image_get_data(image);
5059 len = imhtml->funcs->image_get_size(image);
5060 if (data && len)
5061 anim = pidgin_pixbuf_anim_from_data(data, len);
5065 if (anim) {
5066 struct im_image_data *t = g_new(struct im_image_data, 1);
5067 filename = imhtml->funcs->image_get_filename(image);
5068 imhtml->funcs->image_ref(id);
5069 t->id = id;
5070 t->mark = gtk_text_buffer_create_mark(imhtml->text_buffer, NULL, iter, TRUE);
5071 imhtml->im_images = g_slist_prepend(imhtml->im_images, t);
5072 scalable = gtk_imhtml_animation_new(anim, filename, id);
5073 g_object_unref(G_OBJECT(anim));
5074 } else {
5075 GdkPixbuf *pixbuf;
5076 pixbuf = gtk_widget_render_icon(GTK_WIDGET(imhtml), GTK_STOCK_MISSING_IMAGE,
5077 GTK_ICON_SIZE_BUTTON, "gtkimhtml-missing-image");
5078 scalable = gtk_imhtml_image_new(pixbuf, filename, id);
5079 g_object_unref(G_OBJECT(pixbuf));
5082 sd = g_new(struct scalable_data, 1);
5083 sd->scalable = scalable;
5084 sd->mark = gtk_text_buffer_create_mark(imhtml->text_buffer, NULL, iter, TRUE);
5085 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect);
5086 scalable->add_to(scalable, imhtml, iter);
5087 minus = gtk_text_view_get_left_margin(GTK_TEXT_VIEW(imhtml)) +
5088 gtk_text_view_get_right_margin(GTK_TEXT_VIEW(imhtml));
5089 scalable->scale(scalable, rect.width - minus, rect.height);
5090 imhtml->scalables = g_list_append(imhtml->scalables, sd);
5093 static const gchar *tag_to_html_start(GtkTextTag *tag)
5095 const gchar *name;
5096 static gchar buf[1024];
5098 name = tag->name;
5099 g_return_val_if_fail(name != NULL, "");
5101 if (strcmp(name, "BOLD") == 0) {
5102 return "<b>";
5103 } else if (strcmp(name, "ITALICS") == 0) {
5104 return "<i>";
5105 } else if (strcmp(name, "UNDERLINE") == 0) {
5106 return "<u>";
5107 } else if (strcmp(name, "STRIKE") == 0) {
5108 return "<s>";
5109 } else if (strncmp(name, "LINK ", 5) == 0) {
5110 char *tmp = g_object_get_data(G_OBJECT(tag), "link_url");
5111 if (tmp) {
5112 g_snprintf(buf, sizeof(buf), "<a href=\"%s\">", tmp);
5113 buf[sizeof(buf)-1] = '\0';
5114 return buf;
5115 } else {
5116 return "";
5118 } else if (strncmp(name, "FORECOLOR ", 10) == 0) {
5119 g_snprintf(buf, sizeof(buf), "<font color=\"%s\">", &name[10]);
5120 return buf;
5121 } else if (strncmp(name, "BACKCOLOR ", 10) == 0) {
5122 g_snprintf(buf, sizeof(buf), "<font back=\"%s\">", &name[10]);
5123 return buf;
5124 } else if (strncmp(name, "BACKGROUND ", 10) == 0) {
5125 g_snprintf(buf, sizeof(buf), "<body bgcolor=\"%s\">", &name[11]);
5126 return buf;
5127 } else if (strncmp(name, "FONT FACE ", 10) == 0) {
5128 g_snprintf(buf, sizeof(buf), "<font face=\"%s\">", &name[10]);
5129 return buf;
5130 } else if (strncmp(name, "FONT SIZE ", 10) == 0) {
5131 g_snprintf(buf, sizeof(buf), "<font size=\"%s\">", &name[10]);
5132 return buf;
5133 } else {
5134 char *str = buf;
5135 gboolean isset;
5136 int ivalue = 0;
5137 GdkColor *color = NULL;
5138 GObject *obj = G_OBJECT(tag);
5139 gboolean empty = TRUE;
5141 str += g_snprintf(str, sizeof(buf) - (str - buf), "<span style='");
5143 /* Weight */
5144 g_object_get(obj, "weight-set", &isset, "weight", &ivalue, NULL);
5145 if (isset) {
5146 const char *weight = "";
5147 if (ivalue >= PANGO_WEIGHT_ULTRABOLD)
5148 weight = "bolder";
5149 else if (ivalue >= PANGO_WEIGHT_BOLD)
5150 weight = "bold";
5151 else if (ivalue >= PANGO_WEIGHT_NORMAL)
5152 weight = "normal";
5153 else
5154 weight = "lighter";
5156 str += g_snprintf(str, sizeof(buf) - (str - buf), "font-weight: %s;", weight);
5157 empty = FALSE;
5160 /* Foreground color */
5161 g_object_get(obj, "foreground-set", &isset, "foreground-gdk", &color, NULL);
5162 if (isset && color) {
5163 str += g_snprintf(str, sizeof(buf) - (str - buf),
5164 "color: #%02x%02x%02x;",
5165 color->red >> 8, color->green >> 8, color->blue >> 8);
5166 empty = FALSE;
5168 gdk_color_free(color);
5170 /* Background color */
5171 g_object_get(obj, "background-set", &isset, "background-gdk", &color, NULL);
5172 if (isset && color) {
5173 str += g_snprintf(str, sizeof(buf) - (str - buf),
5174 "background: #%02x%02x%02x;",
5175 color->red >> 8, color->green >> 8, color->blue >> 8);
5176 empty = FALSE;
5178 gdk_color_free(color);
5180 /* Underline */
5181 g_object_get(obj, "underline-set", &isset, "underline", &ivalue, NULL);
5182 if (isset) {
5183 switch (ivalue) {
5184 case PANGO_UNDERLINE_NONE:
5185 case PANGO_UNDERLINE_ERROR:
5186 break;
5187 default:
5188 str += g_snprintf(str, sizeof(buf) - (str - buf), "text-decoration: underline;");
5189 empty = FALSE;
5193 g_snprintf(str, sizeof(buf) - (str - buf), "'>");
5195 return (empty ? "" : buf);
5199 static const gchar *tag_to_html_end(GtkTextTag *tag)
5201 const gchar *name;
5203 name = tag->name;
5204 g_return_val_if_fail(name != NULL, "");
5206 if (strcmp(name, "BOLD") == 0) {
5207 return "</b>";
5208 } else if (strcmp(name, "ITALICS") == 0) {
5209 return "</i>";
5210 } else if (strcmp(name, "UNDERLINE") == 0) {
5211 return "</u>";
5212 } else if (strcmp(name, "STRIKE") == 0) {
5213 return "</s>";
5214 } else if (strncmp(name, "LINK ", 5) == 0) {
5215 return "</a>";
5216 } else if (strncmp(name, "FORECOLOR ", 10) == 0) {
5217 return "</font>";
5218 } else if (strncmp(name, "BACKCOLOR ", 10) == 0) {
5219 return "</font>";
5220 } else if (strncmp(name, "BACKGROUND ", 10) == 0) {
5221 return "</body>";
5222 } else if (strncmp(name, "FONT FACE ", 10) == 0) {
5223 return "</font>";
5224 } else if (strncmp(name, "FONT SIZE ", 10) == 0) {
5225 return "</font>";
5226 } else {
5227 const char *props[] = {"weight-set", "foreground-set", "background-set",
5228 "size-set", "underline-set", NULL};
5229 int i;
5230 for (i = 0; props[i]; i++) {
5231 gboolean set = FALSE;
5232 g_object_get(G_OBJECT(tag), props[i], &set, NULL);
5233 if (set)
5234 return "</span>";
5237 return "";
5241 typedef struct {
5242 GtkTextTag *tag;
5243 char *end;
5244 char *start;
5245 } PidginTextTagData;
5247 static PidginTextTagData *text_tag_data_new(GtkTextTag *tag)
5249 const char *start, *end;
5250 PidginTextTagData *ret = NULL;
5252 start = tag_to_html_start(tag);
5253 if (!start || !*start)
5254 return NULL;
5255 end = tag_to_html_end(tag);
5256 if (!end || !*end)
5257 return NULL;
5259 ret = g_new0(PidginTextTagData, 1);
5260 ret->start = g_strdup(start);
5261 ret->end = g_strdup(end);
5262 ret->tag = tag;
5263 return ret;
5266 static void text_tag_data_destroy(PidginTextTagData *data)
5268 g_free(data->start);
5269 g_free(data->end);
5270 g_free(data);
5273 static gboolean tag_ends_here(GtkTextTag *tag, GtkTextIter *iter, GtkTextIter *niter)
5275 return ((gtk_text_iter_has_tag(iter, GTK_TEXT_TAG(tag)) &&
5276 !gtk_text_iter_has_tag(niter, GTK_TEXT_TAG(tag))) ||
5277 gtk_text_iter_is_end(niter));
5280 /* Basic notion here: traverse through the text buffer one-by-one, non-character elements, such
5281 * as smileys and IM images are represented by the Unicode "unknown" character. Handle them. Else
5282 * check for tags that are toggled on, insert their html form, and push them on the queue. Then insert
5283 * the actual text. Then check for tags that are toggled off and insert them, after checking the queue.
5284 * Finally, replace <, >, &, and " with their HTML equivalent.
5286 char *gtk_imhtml_get_markup_range(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end)
5288 gunichar c;
5289 GtkTextIter iter, next_iter, non_neutral_iter;
5290 gboolean is_rtl_message = FALSE;
5291 GString *str = g_string_new("");
5292 GSList *tags, *sl;
5293 GQueue *q;
5294 GtkTextTag *tag;
5295 PidginTextTagData *tagdata;
5297 q = g_queue_new();
5299 gtk_text_iter_order(start, end);
5300 non_neutral_iter = next_iter = iter = *start;
5301 gtk_text_iter_forward_char(&next_iter);
5303 /* Bi-directional text support */
5304 /* Get to the first non-neutral character */
5305 #ifdef HAVE_PANGO14
5306 while ((PANGO_DIRECTION_NEUTRAL == pango_unichar_direction(gtk_text_iter_get_char(&non_neutral_iter)))
5307 && gtk_text_iter_forward_char(&non_neutral_iter));
5308 if (PANGO_DIRECTION_RTL == pango_unichar_direction(gtk_text_iter_get_char(&non_neutral_iter))) {
5309 is_rtl_message = TRUE;
5310 g_string_append(str, "<SPAN style=\"direction:rtl;text-align:right;\">");
5312 #endif
5314 /* First add the tags that are already in progress (we don't care about non-printing tags)*/
5315 tags = gtk_text_iter_get_tags(start);
5317 for (sl = tags; sl; sl = sl->next) {
5318 tag = sl->data;
5319 if (!gtk_text_iter_toggles_tag(start, GTK_TEXT_TAG(tag))) {
5320 PidginTextTagData *data = text_tag_data_new(tag);
5321 if (data) {
5322 g_string_append(str, data->start);
5323 g_queue_push_tail(q, data);
5327 g_slist_free(tags);
5329 while ((c = gtk_text_iter_get_char(&iter)) != 0 && !gtk_text_iter_equal(&iter, end)) {
5331 tags = gtk_text_iter_get_tags(&iter);
5333 for (sl = tags; sl; sl = sl->next) {
5334 tag = sl->data;
5335 if (gtk_text_iter_begins_tag(&iter, GTK_TEXT_TAG(tag))) {
5336 PidginTextTagData *data = text_tag_data_new(tag);
5337 if (data) {
5338 g_string_append(str, data->start);
5339 g_queue_push_tail(q, data);
5344 if (c == 0xFFFC) {
5345 GtkTextChildAnchor* anchor = gtk_text_iter_get_child_anchor(&iter);
5346 if (anchor) {
5347 char *text = g_object_get_data(G_OBJECT(anchor), "gtkimhtml_htmltext");
5348 if (text)
5349 str = g_string_append(str, text);
5351 } else if (c == '<') {
5352 str = g_string_append(str, "&lt;");
5353 } else if (c == '>') {
5354 str = g_string_append(str, "&gt;");
5355 } else if (c == '&') {
5356 str = g_string_append(str, "&amp;");
5357 } else if (c == '"') {
5358 str = g_string_append(str, "&quot;");
5359 } else if (c == '\n') {
5360 str = g_string_append(str, "<br>");
5361 } else {
5362 str = g_string_append_unichar(str, c);
5365 tags = g_slist_reverse(tags);
5366 for (sl = tags; sl; sl = sl->next) {
5367 tag = sl->data;
5368 /** don't worry about non-printing tags ending */
5369 if (tag_ends_here(tag, &iter, &next_iter) &&
5370 strlen(tag_to_html_end(tag)) > 0 &&
5371 strlen(tag_to_html_start(tag)) > 0) {
5373 PidginTextTagData *tmp;
5374 GQueue *r = g_queue_new();
5376 while ((tmp = g_queue_pop_tail(q)) && tmp->tag != tag) {
5377 g_string_append(str, tmp->end);
5378 if (!tag_ends_here(tmp->tag, &iter, &next_iter))
5379 g_queue_push_tail(r, tmp);
5380 else
5381 text_tag_data_destroy(tmp);
5384 if (tmp != NULL) {
5385 g_string_append(str, tmp->end);
5386 text_tag_data_destroy(tmp);
5388 #if 0 /* This can't be allowed to happen because it causes the iters to be invalidated in the debug window imhtml during text copying */
5389 else
5390 purple_debug_warning("gtkimhtml", "empty queue, more closing tags than open tags!\n");
5391 #endif
5393 while ((tmp = g_queue_pop_head(r))) {
5394 g_string_append(str, tmp->start);
5395 g_queue_push_tail(q, tmp);
5397 g_queue_free(r);
5401 g_slist_free(tags);
5402 gtk_text_iter_forward_char(&iter);
5403 gtk_text_iter_forward_char(&next_iter);
5406 while ((tagdata = g_queue_pop_tail(q))) {
5407 g_string_append(str, tagdata->end);
5408 text_tag_data_destroy(tagdata);
5411 /* Bi-directional text support - close tags */
5412 if (is_rtl_message)
5413 g_string_append(str, "</SPAN>");
5415 g_queue_free(q);
5416 return g_string_free(str, FALSE);
5419 void gtk_imhtml_close_tags(GtkIMHtml *imhtml, GtkTextIter *iter)
5421 if (imhtml->edit.bold)
5422 gtk_imhtml_toggle_bold(imhtml);
5424 if (imhtml->edit.italic)
5425 gtk_imhtml_toggle_italic(imhtml);
5427 if (imhtml->edit.underline)
5428 gtk_imhtml_toggle_underline(imhtml);
5430 if (imhtml->edit.strike)
5431 gtk_imhtml_toggle_strike(imhtml);
5433 if (imhtml->edit.forecolor)
5434 gtk_imhtml_toggle_forecolor(imhtml, NULL);
5436 if (imhtml->edit.backcolor)
5437 gtk_imhtml_toggle_backcolor(imhtml, NULL);
5439 if (imhtml->edit.fontface)
5440 gtk_imhtml_toggle_fontface(imhtml, NULL);
5442 imhtml->edit.fontsize = 0;
5444 if (imhtml->edit.link)
5445 gtk_imhtml_toggle_link(imhtml, NULL);
5448 char *gtk_imhtml_get_markup(GtkIMHtml *imhtml)
5450 GtkTextIter start, end;
5452 gtk_text_buffer_get_start_iter(imhtml->text_buffer, &start);
5453 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &end);
5454 return gtk_imhtml_get_markup_range(imhtml, &start, &end);
5457 char **gtk_imhtml_get_markup_lines(GtkIMHtml *imhtml)
5459 int i, j, lines;
5460 GtkTextIter start, end;
5461 char **ret;
5463 lines = gtk_text_buffer_get_line_count(imhtml->text_buffer);
5464 ret = g_new0(char *, lines + 1);
5465 gtk_text_buffer_get_start_iter(imhtml->text_buffer, &start);
5466 end = start;
5467 gtk_text_iter_forward_to_line_end(&end);
5469 for (i = 0, j = 0; i < lines; i++) {
5470 if (gtk_text_iter_get_char(&start) != '\n') {
5471 ret[j] = gtk_imhtml_get_markup_range(imhtml, &start, &end);
5472 if (ret[j] != NULL)
5473 j++;
5476 gtk_text_iter_forward_line(&start);
5477 end = start;
5478 gtk_text_iter_forward_to_line_end(&end);
5481 return ret;
5484 char *gtk_imhtml_get_text(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *stop)
5486 GString *str = g_string_new("");
5487 GtkTextIter iter, end;
5488 gunichar c;
5490 if (start == NULL)
5491 gtk_text_buffer_get_start_iter(imhtml->text_buffer, &iter);
5492 else
5493 iter = *start;
5495 if (stop == NULL)
5496 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &end);
5497 else
5498 end = *stop;
5500 gtk_text_iter_order(&iter, &end);
5502 while ((c = gtk_text_iter_get_char(&iter)) != 0 && !gtk_text_iter_equal(&iter, &end)) {
5503 if (c == 0xFFFC) {
5504 GtkTextChildAnchor* anchor;
5505 char *text = NULL;
5507 anchor = gtk_text_iter_get_child_anchor(&iter);
5508 if (anchor)
5509 text = g_object_get_data(G_OBJECT(anchor), "gtkimhtml_plaintext");
5510 if (text)
5511 str = g_string_append(str, text);
5512 } else {
5513 g_string_append_unichar(str, c);
5515 gtk_text_iter_forward_char(&iter);
5518 return g_string_free(str, FALSE);
5521 void gtk_imhtml_set_funcs(GtkIMHtml *imhtml, GtkIMHtmlFuncs *f)
5523 g_return_if_fail(imhtml != NULL);
5524 imhtml->funcs = f;
5527 void gtk_imhtml_setup_entry(GtkIMHtml *imhtml, PurpleConnectionFlags flags)
5529 GtkIMHtmlButtons buttons;
5531 if (flags & PURPLE_CONNECTION_HTML) {
5532 char color[8];
5533 GdkColor fg_color, bg_color;
5535 buttons = GTK_IMHTML_ALL;
5537 if (flags & PURPLE_CONNECTION_NO_BGCOLOR)
5538 buttons &= ~GTK_IMHTML_BACKCOLOR;
5539 if (flags & PURPLE_CONNECTION_NO_FONTSIZE)
5541 buttons &= ~GTK_IMHTML_GROW;
5542 buttons &= ~GTK_IMHTML_SHRINK;
5544 if (flags & PURPLE_CONNECTION_NO_URLDESC)
5545 buttons &= ~GTK_IMHTML_LINKDESC;
5547 gtk_imhtml_set_format_functions(imhtml, GTK_IMHTML_ALL);
5548 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_bold") != imhtml->edit.bold)
5549 gtk_imhtml_toggle_bold(imhtml);
5551 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_italic") != imhtml->edit.italic)
5552 gtk_imhtml_toggle_italic(imhtml);
5554 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_underline") != imhtml->edit.underline)
5555 gtk_imhtml_toggle_underline(imhtml);
5557 gtk_imhtml_toggle_fontface(imhtml,
5558 purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/font_face"));
5560 if (!(flags & PURPLE_CONNECTION_NO_FONTSIZE))
5562 int size = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/font_size");
5564 /* 3 is the default. */
5565 if (size != 3)
5566 gtk_imhtml_font_set_size(imhtml, size);
5569 if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"), "") != 0)
5571 gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"),
5572 &fg_color);
5573 g_snprintf(color, sizeof(color), "#%02x%02x%02x",
5574 fg_color.red / 256,
5575 fg_color.green / 256,
5576 fg_color.blue / 256);
5577 } else
5578 strcpy(color, "");
5580 gtk_imhtml_toggle_forecolor(imhtml, color);
5582 if(!(flags & PURPLE_CONNECTION_NO_BGCOLOR) &&
5583 strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"), "") != 0)
5585 gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"),
5586 &bg_color);
5587 g_snprintf(color, sizeof(color), "#%02x%02x%02x",
5588 bg_color.red / 256,
5589 bg_color.green / 256,
5590 bg_color.blue / 256);
5591 } else
5592 strcpy(color, "");
5594 gtk_imhtml_toggle_background(imhtml, color);
5596 if (flags & PURPLE_CONNECTION_FORMATTING_WBFO)
5597 gtk_imhtml_set_whole_buffer_formatting_only(imhtml, TRUE);
5598 else
5599 gtk_imhtml_set_whole_buffer_formatting_only(imhtml, FALSE);
5600 } else {
5601 buttons = GTK_IMHTML_SMILEY | GTK_IMHTML_IMAGE;
5602 imhtml_clear_formatting(imhtml);
5605 if (flags & PURPLE_CONNECTION_NO_IMAGES)
5606 buttons &= ~GTK_IMHTML_IMAGE;
5608 if (flags & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY)
5609 buttons |= GTK_IMHTML_CUSTOM_SMILEY;
5610 else
5611 buttons &= ~GTK_IMHTML_CUSTOM_SMILEY;
5613 gtk_imhtml_set_format_functions(imhtml, buttons);
5616 /*******
5617 * GtkIMHtmlSmiley functions
5618 *******/
5619 static void gtk_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data)
5621 GtkIMHtmlSmiley *smiley;
5623 smiley = (GtkIMHtmlSmiley *)user_data;
5624 smiley->icon = gdk_pixbuf_loader_get_animation(loader);
5626 if (smiley->icon)
5627 g_object_ref(G_OBJECT(smiley->icon));
5628 #ifdef DEBUG_CUSTOM_SMILEY
5629 purple_debug_info("custom-smiley", "gtk_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s'\n", smiley->icon, smiley->smile);
5630 #endif
5633 static void gtk_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data)
5635 GtkIMHtmlSmiley *smiley;
5636 GtkWidget *icon = NULL;
5637 GtkTextChildAnchor *anchor = NULL;
5638 GSList *current = NULL;
5640 smiley = (GtkIMHtmlSmiley *)user_data;
5641 if (!smiley->imhtml) {
5642 #ifdef DEBUG_CUSTOM_SMILEY
5643 purple_debug_error("custom-smiley", "gtk_custom_smiley_closed(): orphan smiley found: %p\n", smiley);
5644 #endif
5645 g_object_unref(G_OBJECT(loader));
5646 smiley->loader = NULL;
5647 return;
5650 for (current = smiley->anchors; current; current = g_slist_next(current)) {
5651 anchor = GTK_TEXT_CHILD_ANCHOR(current->data);
5652 if (gtk_text_child_anchor_get_deleted(anchor))
5653 icon = NULL;
5654 else
5655 icon = gtk_image_new_from_animation(smiley->icon);
5657 #ifdef DEBUG_CUSTOM_SMILEY
5658 purple_debug_info("custom-smiley", "gtk_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s'\n",
5659 icon, smiley->icon, smiley->smile);
5660 #endif
5661 if (icon) {
5662 GList *wids;
5663 gtk_widget_show(icon);
5665 wids = gtk_text_child_anchor_get_widgets(anchor);
5667 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", purple_unescape_html(smiley->smile), g_free);
5668 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley->smile), g_free);
5670 if (smiley->imhtml) {
5671 if (wids) {
5672 GList *children = gtk_container_get_children(GTK_CONTAINER(wids->data));
5673 g_list_foreach(children, (GFunc)gtk_widget_destroy, NULL);
5674 g_list_free(children);
5675 gtk_container_add(GTK_CONTAINER(wids->data), icon);
5676 } else
5677 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley->imhtml), icon, anchor);
5679 g_list_free(wids);
5681 g_object_unref(anchor);
5684 g_slist_free(smiley->anchors);
5685 smiley->anchors = NULL;
5687 g_object_unref(G_OBJECT(loader));
5688 smiley->loader = NULL;
5691 static void
5692 gtk_custom_smiley_size_prepared(GdkPixbufLoader *loader, gint width, gint height, gpointer data)
5694 #define CUSTOM_SMILEY_SIZE 96 /* XXX: Should this be a theme setting? */
5695 if (width <= CUSTOM_SMILEY_SIZE && height <= CUSTOM_SMILEY_SIZE)
5696 return;
5698 if (width >= height) {
5699 height = height * CUSTOM_SMILEY_SIZE / width;
5700 width = CUSTOM_SMILEY_SIZE;
5701 } else {
5702 width = width * CUSTOM_SMILEY_SIZE / height;
5703 height = CUSTOM_SMILEY_SIZE;
5706 gdk_pixbuf_loader_set_size(loader, width, height);
5709 void
5710 gtk_imhtml_smiley_reload(GtkIMHtmlSmiley *smiley)
5712 if (smiley->icon)
5713 g_object_unref(smiley->icon);
5714 if (smiley->loader)
5715 g_object_unref(smiley->loader); /* XXX: does this crash? */
5717 smiley->icon = NULL;
5718 smiley->loader = NULL;
5720 if (smiley->file) {
5721 /* We do not use the pixbuf loader for a smiley that can be loaded
5722 * from a file. (e.g., local custom smileys)
5724 return;
5727 smiley->loader = gdk_pixbuf_loader_new();
5729 g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(gtk_custom_smiley_allocated), smiley);
5730 g_signal_connect(smiley->loader, "closed", G_CALLBACK(gtk_custom_smiley_closed), smiley);
5731 g_signal_connect(smiley->loader, "size_prepared", G_CALLBACK(gtk_custom_smiley_size_prepared), smiley);
5734 GtkIMHtmlSmiley *gtk_imhtml_smiley_create(const char *file, const char *shortcut, gboolean hide,
5735 GtkIMHtmlSmileyFlags flags)
5737 GtkIMHtmlSmiley *smiley = g_new0(GtkIMHtmlSmiley, 1);
5738 smiley->file = g_strdup(file);
5739 smiley->smile = g_strdup(shortcut);
5740 smiley->hidden = hide;
5741 smiley->flags = flags;
5742 smiley->imhtml = NULL;
5743 gtk_imhtml_smiley_reload(smiley);
5744 return smiley;
5747 void gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley *smiley)
5749 gtk_imhtml_disassociate_smiley(smiley);
5750 g_free(smiley->smile);
5751 g_free(smiley->file);
5752 if (smiley->icon)
5753 g_object_unref(smiley->icon);
5754 if (smiley->loader)
5755 g_object_unref(smiley->loader);
5756 g_free(smiley->data);
5757 g_free(smiley);
5760 gboolean gtk_imhtml_class_register_protocol(const char *name,
5761 gboolean (*activate)(GtkIMHtml *imhtml, GtkIMHtmlLink *link),
5762 gboolean (*context_menu)(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu))
5764 GtkIMHtmlClass *klass;
5765 GtkIMHtmlProtocol *proto;
5767 g_return_val_if_fail(name, FALSE);
5769 klass = g_type_class_ref(GTK_TYPE_IMHTML);
5770 g_return_val_if_fail(klass, FALSE);
5772 if ((proto = imhtml_find_protocol(name, TRUE))) {
5773 if (activate) {
5774 return FALSE;
5776 klass->protocols = g_list_remove(klass->protocols, proto);
5777 g_free(proto->name);
5778 g_free(proto);
5779 return TRUE;
5780 } else if (!activate) {
5781 return FALSE;
5784 proto = g_new0(GtkIMHtmlProtocol, 1);
5785 proto->name = g_strdup(name);
5786 proto->length = strlen(name);
5787 proto->activate = activate;
5788 proto->context_menu = context_menu;
5789 klass->protocols = g_list_prepend(klass->protocols, proto);
5791 return TRUE;
5794 static void
5795 gtk_imhtml_activate_tag(GtkIMHtml *imhtml, GtkTextTag *tag)
5797 /* A link was clicked--we emit the "url_clicked" signal
5798 * with the URL as the argument */
5799 g_object_ref(G_OBJECT(tag));
5800 g_signal_emit(imhtml, signals[URL_CLICKED], 0, g_object_get_data(G_OBJECT(tag), "link_url"));
5801 g_object_unref(G_OBJECT(tag));
5802 g_object_set_data(G_OBJECT(tag), "visited", GINT_TO_POINTER(TRUE));
5803 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml), tag);
5806 gboolean gtk_imhtml_link_activate(GtkIMHtmlLink *link)
5808 g_return_val_if_fail(link, FALSE);
5810 if (link->tag) {
5811 gtk_imhtml_activate_tag(link->imhtml, link->tag);
5812 } else if (link->url) {
5813 g_signal_emit(link->imhtml, signals[URL_CLICKED], 0, link->url);
5814 } else
5815 return FALSE;
5816 return TRUE;
5819 const char *gtk_imhtml_link_get_url(GtkIMHtmlLink *link)
5821 return link->url;
5824 const GtkTextTag * gtk_imhtml_link_get_text_tag(GtkIMHtmlLink *link)
5826 return link->tag;
5829 static gboolean return_add_newline_cb(GtkWidget *widget, gpointer data)
5831 GtkTextBuffer *buffer;
5832 GtkTextMark *mark;
5833 GtkTextIter iter;
5835 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
5837 /* Delete any currently selected text */
5838 gtk_text_buffer_delete_selection(buffer, TRUE, TRUE);
5840 /* Insert a newline at the current cursor position */
5841 mark = gtk_text_buffer_get_insert(buffer);
5842 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
5843 gtk_imhtml_insert_html_at_iter(GTK_IMHTML(widget), "\n", 0, &iter);
5846 * If we just newlined ourselves past the end of the visible area
5847 * then scroll down so the cursor is in view.
5849 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(widget),
5850 gtk_text_buffer_get_insert(buffer),
5851 0, FALSE, 0.0, 0.0);
5853 return TRUE;
5857 * It's kind of a pain that we need this function and the above just
5858 * to reinstate the default GtkTextView behavior. It might be better
5859 * if GtkIMHtml didn't intercept the enter key and just required the
5860 * application to deal with it--it's really not much more work than it
5861 * is to connect to the current "message_send" signal.
5863 void gtk_imhtml_set_return_inserts_newline(GtkIMHtml *imhtml)
5865 g_signal_connect(G_OBJECT(imhtml), "message_send",
5866 G_CALLBACK(return_add_newline_cb), NULL);
5869 void gtk_imhtml_set_populate_primary_clipboard(GtkIMHtml *imhtml, gboolean populate)
5871 gulong signal_id;
5872 signal_id = g_signal_handler_find(imhtml->text_buffer,
5873 G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_UNBLOCKED, 0, 0, NULL,
5874 mark_set_so_update_selection_cb, NULL);
5875 if (populate) {
5876 if (!signal_id) {
5877 /* We didn't find an unblocked signal handler, which means there
5878 is a blocked handler. Now unblock it.
5879 This is necessary to avoid a mutex-lock when the debug message
5880 saying 'no handler is blocked' is printed in the debug window.
5881 -- sad
5883 g_signal_handlers_unblock_matched(imhtml->text_buffer,
5884 G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
5885 mark_set_so_update_selection_cb, NULL);
5887 } else {
5888 /* Block only if we found an unblocked handler */
5889 if (signal_id)
5890 g_signal_handler_block(imhtml->text_buffer, signal_id);