Let's make Ivan a developer!
[pidgin-git.git] / pidgin / gtkimhtml.c
blob1aed5fbbf35384bce5d810f03d4c5cb1276ad9ea
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 static gboolean
103 gtk_text_view_drag_motion (GtkWidget *widget,
104 GdkDragContext *context,
105 gint x,
106 gint y,
107 guint time);
109 static void preinsert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml);
110 static void insert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml);
111 static void delete_cb(GtkTextBuffer *buffer, GtkTextIter *iter, GtkTextIter *end, GtkIMHtml *imhtml);
112 static void insert_ca_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextChildAnchor *arg2, gpointer user_data);
113 static void gtk_imhtml_apply_tags_on_insert(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end);
114 void gtk_imhtml_close_tags(GtkIMHtml *imhtml, GtkTextIter *iter);
115 static void gtk_imhtml_link_drop_cb(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer user_data);
116 static void gtk_imhtml_link_drag_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y, GtkSelectionData *sd, guint info, guint t, GtkIMHtml *imhtml);
117 static void mark_set_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextMark *mark, GtkIMHtml *imhtml);
118 static void hijack_menu_cb(GtkIMHtml *imhtml, GtkMenu *menu, gpointer data);
119 static void paste_received_cb (GtkClipboard *clipboard, GtkSelectionData *selection_data, gpointer data);
120 static void paste_plaintext_received_cb (GtkClipboard *clipboard, const gchar *text, gpointer data);
121 static void imhtml_paste_insert(GtkIMHtml *imhtml, const char *text, gboolean plaintext);
122 static void imhtml_toggle_bold(GtkIMHtml *imhtml);
123 static void imhtml_toggle_italic(GtkIMHtml *imhtml);
124 static void imhtml_toggle_strike(GtkIMHtml *imhtml);
125 static void imhtml_toggle_underline(GtkIMHtml *imhtml);
126 static void imhtml_font_grow(GtkIMHtml *imhtml);
127 static void imhtml_font_shrink(GtkIMHtml *imhtml);
128 static void imhtml_clear_formatting(GtkIMHtml *imhtml);
129 static int gtk_imhtml_is_protocol(const char *text);
130 static void gtk_imhtml_activate_tag(GtkIMHtml *imhtml, GtkTextTag *tag);
131 static void gtk_imhtml_link_destroy(GtkIMHtmlLink *link);
133 /* POINT_SIZE converts from AIM font sizes to a point size scale factor. */
134 #define MAX_FONT_SIZE 7
135 #define POINT_SIZE(x) (_point_sizes [MIN ((x > 0 ? x : 1), MAX_FONT_SIZE) - 1])
136 static const gdouble _point_sizes [] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736};
138 enum {
139 TARGET_HTML,
140 TARGET_UTF8_STRING,
141 TARGET_COMPOUND_TEXT,
142 TARGET_STRING,
143 TARGET_TEXT
146 enum {
147 URL_CLICKED,
148 BUTTONS_UPDATE,
149 TOGGLE_FORMAT,
150 CLEAR_FORMAT,
151 UPDATE_FORMAT,
152 MESSAGE_SEND,
153 UNDO,
154 REDO,
155 PASTE,
156 LAST_SIGNAL
158 static guint signals [LAST_SIGNAL] = { 0 };
160 static char *html_clipboard = NULL;
161 static char *text_clipboard = NULL;
162 static GtkClipboard *clipboard_selection = NULL;
164 static const GtkTargetEntry selection_targets[] = {
165 #ifndef _WIN32
166 { "text/html", 0, TARGET_HTML },
167 #else
168 { "HTML Format", 0, TARGET_HTML },
169 #endif
170 { "UTF8_STRING", 0, TARGET_UTF8_STRING },
171 { "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT },
172 { "STRING", 0, TARGET_STRING },
173 { "TEXT", 0, TARGET_TEXT}};
175 static const GtkTargetEntry link_drag_drop_targets[] = {
176 GTK_IMHTML_DND_TARGETS
179 #ifdef _WIN32
180 static gchar *
181 clipboard_win32_to_html(char *clipboard) {
182 const char *header;
183 const char *begin, *end;
184 gint start = 0;
185 gint finish = 0;
186 gchar *html;
187 gchar **split;
188 int clipboard_length = 0;
190 #if 0 /* Debugging for Windows clipboard */
191 FILE *fd;
193 purple_debug_info("imhtml clipboard", "from clipboard: %s\n", clipboard);
195 fd = g_fopen("c:\\purplecb.txt", "wb");
196 fprintf(fd, "%s", clipboard);
197 fclose(fd);
198 #endif
200 clipboard_length = strlen(clipboard);
202 if (!(header = strstr(clipboard, "StartFragment:")) || (header - clipboard) >= clipboard_length)
203 return NULL;
204 sscanf(header, "StartFragment:%d", &start);
206 if (!(header = strstr(clipboard, "EndFragment:")) || (header - clipboard) >= clipboard_length)
207 return NULL;
208 sscanf(header, "EndFragment:%d", &finish);
210 if (finish > clipboard_length)
211 finish = clipboard_length;
213 if (start > finish)
214 start = finish;
216 begin = clipboard + start;
218 end = clipboard + finish;
220 html = g_strndup(begin, end - begin);
222 /* any newlines in the string will now be \r\n, so we need to strip out the \r */
223 split = g_strsplit(html, "\r\n", 0);
224 g_free(html);
225 html = g_strjoinv("\n", split);
226 g_strfreev(split);
228 #if 0 /* Debugging for Windows clipboard */
229 purple_debug_info("imhtml clipboard", "HTML fragment: '%s'\n", html);
230 #endif
232 return html;
235 static gchar *
236 clipboard_html_to_win32(char *html) {
237 int length;
238 GString *clipboard;
240 if (html == NULL)
241 return NULL;
243 length = strlen(html);
244 clipboard = g_string_new ("Version:1.0\r\n");
245 g_string_append(clipboard, "StartHTML:0000000105\r\n");
246 g_string_append_printf(clipboard, "EndHTML:%010d\r\n", 147 + length);
247 g_string_append(clipboard, "StartFragment:0000000127\r\n");
248 g_string_append_printf(clipboard, "EndFragment:%010d\r\n", 127 + length);
249 g_string_append(clipboard, "<!--StartFragment-->\r\n");
250 g_string_append(clipboard, html);
251 g_string_append(clipboard, "\r\n<!--EndFragment-->");
253 return g_string_free(clipboard, FALSE);
256 static gboolean clipboard_paste_html_win32(GtkIMHtml *imhtml) {
257 gboolean pasted = FALSE;
259 /* Win32 clipboard format value, and functions to convert back and
260 * forth between HTML and the clipboard format.
262 static UINT win_html_fmt = 0;
264 /* Register HTML Format as desired clipboard format */
265 if (!win_html_fmt)
266 win_html_fmt = RegisterClipboardFormat("HTML Format");
268 if (gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml))
269 && IsClipboardFormatAvailable(win_html_fmt)) {
270 gboolean error_reading_clipboard = FALSE;
271 HWND hwnd = GDK_WINDOW_HWND(GTK_WIDGET(imhtml)->window);
273 if (OpenClipboard(hwnd)) {
274 HGLOBAL hdata = GetClipboardData(win_html_fmt);
275 if (hdata == NULL) {
276 if (GetLastError() != ERROR_SUCCESS)
277 error_reading_clipboard = TRUE;
278 } else {
279 char *buffer = GlobalLock(hdata);
280 if (buffer == NULL) {
281 error_reading_clipboard = TRUE;
282 } else {
283 char *text = clipboard_win32_to_html(
284 buffer);
285 imhtml_paste_insert(imhtml, text,
286 FALSE);
287 g_free(text);
288 pasted = TRUE;
290 GlobalUnlock(hdata);
293 CloseClipboard();
294 } else {
295 error_reading_clipboard = TRUE;
298 if (error_reading_clipboard) {
299 gchar *err_msg = g_win32_error_message(GetLastError());
300 purple_debug_info("html clipboard",
301 "Unable to read clipboard data: %s\n",
302 err_msg ? err_msg : "Unknown Error");
303 g_free(err_msg);
307 return pasted;
309 #endif
311 static GtkSmileyTree*
312 gtk_smiley_tree_new (void)
314 return g_new0 (GtkSmileyTree, 1);
317 static void
318 gtk_smiley_tree_insert (GtkSmileyTree *tree,
319 GtkIMHtmlSmiley *smiley)
321 GtkSmileyTree *t = tree;
322 const gchar *x = smiley->smile;
324 if (!(*x))
325 return;
327 do {
328 gchar *pos;
329 gint index;
331 if (!t->values)
332 t->values = g_string_new ("");
334 pos = strchr (t->values->str, *x);
335 if (!pos) {
336 t->values = g_string_append_c (t->values, *x);
337 index = t->values->len - 1;
338 t->children = g_realloc (t->children, t->values->len * sizeof (GtkSmileyTree *));
339 t->children [index] = g_new0 (GtkSmileyTree, 1);
340 } else
341 index = GPOINTER_TO_INT(pos) - GPOINTER_TO_INT(t->values->str);
343 t = t->children [index];
345 x++;
346 } while (*x);
348 t->image = smiley;
352 static void
353 gtk_smiley_tree_destroy (GtkSmileyTree *tree)
355 GSList *list = g_slist_prepend (NULL, tree);
357 while (list) {
358 GtkSmileyTree *t = list->data;
359 gsize i;
360 list = g_slist_remove(list, t);
361 if (t && t->values) {
362 for (i = 0; i < t->values->len; i++)
363 list = g_slist_prepend (list, t->children [i]);
364 g_string_free (t->values, TRUE);
365 g_free (t->children);
368 g_free (t);
372 static void (*parent_size_allocate)(GtkWidget *widget, GtkAllocation *alloc);
374 static void gtk_imhtml_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
376 GtkIMHtml *imhtml = GTK_IMHTML(widget);
377 GdkRectangle rect;
378 int xminus;
379 int height = 0, y = 0;
380 GtkTextIter iter;
381 gboolean scroll = TRUE;
383 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &iter);
385 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget), &rect);
386 gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(imhtml), &iter, &y, &height);
388 if (((y + height) - (rect.y + rect.height)) > height &&
389 gtk_text_buffer_get_char_count(imhtml->text_buffer)) {
390 scroll = FALSE;
393 if(imhtml->old_rect.width != rect.width || imhtml->old_rect.height != rect.height) {
394 GList *iter = GTK_IMHTML(widget)->scalables;
396 xminus = gtk_text_view_get_left_margin(GTK_TEXT_VIEW(widget)) +
397 gtk_text_view_get_right_margin(GTK_TEXT_VIEW(widget));
399 while(iter){
400 struct scalable_data *sd = iter->data;
401 GtkIMHtmlScalable *scale = GTK_IMHTML_SCALABLE(sd->scalable);
402 scale->scale(scale, rect.width - xminus, rect.height);
404 iter = iter->next;
408 imhtml->old_rect = rect;
409 parent_size_allocate(widget, alloc);
411 /* Don't scroll here if we're in the middle of a smooth scroll */
412 if (scroll && imhtml->scroll_time == NULL &&
413 GTK_WIDGET_REALIZED(imhtml))
414 gtk_imhtml_scroll_to_end(imhtml, FALSE);
417 #define DEFAULT_SEND_COLOR "#204a87"
418 #define DEFAULT_RECV_COLOR "#cc0000"
419 #define DEFAULT_HIGHLIGHT_COLOR "#AF7F00"
420 #define DEFAULT_ACTION_COLOR "#062585"
421 #define DEFAULT_WHISPER_ACTION_COLOR "#6C2585"
422 #define DEFAULT_WHISPER_COLOR "#00FF00"
424 static void (*parent_style_set)(GtkWidget *widget, GtkStyle *prev_style);
426 static void
427 gtk_imhtml_style_set(GtkWidget *widget, GtkStyle *prev_style)
429 int i;
430 struct {
431 const char *tag;
432 const char *color;
433 const char *def;
434 } styles[] = {
435 {"send-name", "send-name-color", DEFAULT_SEND_COLOR},
436 {"receive-name", "receive-name-color", DEFAULT_RECV_COLOR},
437 {"highlight-name", "highlight-name-color", DEFAULT_HIGHLIGHT_COLOR},
438 {"action-name", "action-name-color", DEFAULT_ACTION_COLOR},
439 {"whisper-action-name", "whisper-action-name-color", DEFAULT_WHISPER_ACTION_COLOR},
440 {"whisper-name", "whisper-name-color", DEFAULT_WHISPER_COLOR},
441 {NULL, NULL, NULL}
443 GtkIMHtml *imhtml = GTK_IMHTML(widget);
444 GtkTextTagTable *table = gtk_text_buffer_get_tag_table(imhtml->text_buffer);
446 for (i = 0; styles[i].tag; i++) {
447 GdkColor *color = NULL;
448 GtkTextTag *tag = gtk_text_tag_table_lookup(table, styles[i].tag);
449 if (!tag) {
450 purple_debug_warning("gtkimhtml", "Cannot find tag '%s'. This should never happen. Please file a bug.\n", styles[i].tag);
451 continue;
453 gtk_widget_style_get(widget, styles[i].color, &color, NULL);
454 if (color) {
455 g_object_set(tag, "foreground-gdk", color, NULL);
456 gdk_color_free(color);
457 } else {
458 GdkColor defcolor;
459 gdk_color_parse(styles[i].def, &defcolor);
460 g_object_set(tag, "foreground-gdk", &defcolor, NULL);
463 parent_style_set(widget, prev_style);
466 static gboolean
467 imhtml_get_iter_bounds(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end)
469 if (imhtml->wbfo) {
470 gtk_text_buffer_get_bounds(imhtml->text_buffer, start, end);
471 return TRUE;
472 } else if (imhtml->editable) {
473 if (!gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, start, end)) {
474 GtkTextMark *mark = gtk_text_buffer_get_insert(imhtml->text_buffer);
475 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, start, mark);
476 *end = *start;
478 return TRUE;
481 return FALSE;
484 static void
485 gtk_imhtml_set_link_color(GtkIMHtml *imhtml, GtkTextTag *tag)
487 GdkColor *color = NULL;
488 gboolean visited = !!g_object_get_data(G_OBJECT(tag), "visited");
489 gtk_widget_style_get(GTK_WIDGET(imhtml), visited ? "hyperlink-visited-color" : "hyperlink-color", &color, NULL);
490 if (color) {
491 g_object_set(G_OBJECT(tag), "foreground-gdk", color, NULL);
492 gdk_color_free(color);
493 } else {
494 g_object_set(G_OBJECT(tag), "foreground", visited ? "#800000" : "blue", NULL);
498 static gint
499 gtk_imhtml_tip_paint (GtkIMHtml *imhtml)
501 PangoLayout *layout;
503 g_return_val_if_fail(GTK_IS_IMHTML(imhtml), FALSE);
505 layout = gtk_widget_create_pango_layout(imhtml->tip_window, imhtml->tip);
507 gtk_paint_flat_box (imhtml->tip_window->style, imhtml->tip_window->window,
508 GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL, imhtml->tip_window,
509 "tooltip", 0, 0, -1, -1);
511 gtk_paint_layout (imhtml->tip_window->style, imhtml->tip_window->window, GTK_STATE_NORMAL,
512 FALSE, NULL, imhtml->tip_window, NULL, 4, 4, layout);
514 g_object_unref(layout);
515 return FALSE;
518 static gint
519 gtk_imhtml_tip (gpointer data)
521 GtkIMHtml *imhtml = data;
522 PangoFontMetrics *font_metrics;
523 PangoLayout *layout;
524 PangoFont *font;
526 gint gap, x, y, h, w, scr_w, baseline_skip;
528 g_return_val_if_fail(GTK_IS_IMHTML(imhtml), FALSE);
530 if (!imhtml->tip || !GTK_WIDGET_DRAWABLE (GTK_WIDGET(imhtml))) {
531 imhtml->tip_timer = 0;
532 return FALSE;
535 if (imhtml->tip_window){
536 gtk_widget_destroy (imhtml->tip_window);
537 imhtml->tip_window = NULL;
540 imhtml->tip_timer = 0;
541 imhtml->tip_window = gtk_window_new (GTK_WINDOW_POPUP);
542 gtk_widget_set_app_paintable (imhtml->tip_window, TRUE);
543 gtk_window_set_title(GTK_WINDOW(imhtml->tip_window), "GtkIMHtml");
544 gtk_window_set_resizable (GTK_WINDOW (imhtml->tip_window), FALSE);
545 gtk_widget_set_name (imhtml->tip_window, "gtk-tooltips");
546 gtk_window_set_type_hint (GTK_WINDOW (imhtml->tip_window),
547 GDK_WINDOW_TYPE_HINT_TOOLTIP);
548 g_signal_connect_swapped (G_OBJECT (imhtml->tip_window), "expose_event",
549 G_CALLBACK (gtk_imhtml_tip_paint), imhtml);
551 gtk_widget_ensure_style (imhtml->tip_window);
552 layout = gtk_widget_create_pango_layout(imhtml->tip_window, imhtml->tip);
553 font = pango_context_load_font(pango_layout_get_context(layout),
554 imhtml->tip_window->style->font_desc);
556 if (font == NULL) {
557 char *tmp = pango_font_description_to_string(
558 imhtml->tip_window->style->font_desc);
560 purple_debug(PURPLE_DEBUG_ERROR, "gtk_imhtml_tip",
561 "pango_context_load_font() couldn't load font: '%s'\n",
562 tmp);
563 g_free(tmp);
565 g_object_unref(layout);
566 return FALSE;
569 font_metrics = pango_font_get_metrics(font, NULL);
571 pango_layout_get_pixel_size(layout, &scr_w, NULL);
572 gap = PANGO_PIXELS((pango_font_metrics_get_ascent(font_metrics) +
573 pango_font_metrics_get_descent(font_metrics))/ 4);
575 if (gap < 2)
576 gap = 2;
577 baseline_skip = PANGO_PIXELS(pango_font_metrics_get_ascent(font_metrics) +
578 pango_font_metrics_get_descent(font_metrics));
579 w = 8 + scr_w;
580 h = 8 + baseline_skip;
582 gdk_window_get_pointer (NULL, &x, &y, NULL);
583 if (GTK_WIDGET_NO_WINDOW (GTK_WIDGET(imhtml)))
584 y += GTK_WIDGET(imhtml)->allocation.y;
586 scr_w = gdk_screen_width();
588 x -= ((w >> 1) + 4);
590 if ((x + w) > scr_w)
591 x -= (x + w) - scr_w;
592 else if (x < 0)
593 x = 0;
595 y = y + PANGO_PIXELS(pango_font_metrics_get_ascent(font_metrics) +
596 pango_font_metrics_get_descent(font_metrics));
598 gtk_widget_set_size_request (imhtml->tip_window, w, h);
599 gtk_window_move (GTK_WINDOW(imhtml->tip_window), x, y);
600 gtk_widget_show (imhtml->tip_window);
602 pango_font_metrics_unref(font_metrics);
603 g_object_unref(font);
604 g_object_unref(layout);
606 return FALSE;
609 static gboolean
610 gtk_motion_event_notify(GtkWidget *imhtml, GdkEventMotion *event, gpointer data)
612 GtkTextIter iter;
613 GdkWindow *win = event->window;
614 int x, y;
615 char *tip = NULL;
616 GSList *tags = NULL, *templist = NULL;
617 GtkTextTag *tag = NULL, *oldprelit_tag;
618 GtkTextChildAnchor* anchor;
619 gboolean hand = TRUE;
620 GdkCursor *cursor = NULL;
622 oldprelit_tag = GTK_IMHTML(imhtml)->prelit_tag;
624 gdk_window_get_pointer(GTK_WIDGET(imhtml)->window, NULL, NULL, NULL);
625 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(imhtml), GTK_TEXT_WINDOW_WIDGET,
626 event->x, event->y, &x, &y);
627 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml), &iter, x, y);
628 tags = gtk_text_iter_get_tags(&iter);
630 templist = tags;
631 while (templist) {
632 tag = templist->data;
633 tip = g_object_get_data(G_OBJECT(tag), "link_url");
634 if (tip)
635 break;
636 templist = templist->next;
639 if (tip && (!tag || !g_object_get_data(G_OBJECT(tag), "visited"))) {
640 GTK_IMHTML(imhtml)->prelit_tag = tag;
641 if (tag != oldprelit_tag) {
642 GdkColor *pre = NULL;
643 gtk_widget_style_get(GTK_WIDGET(imhtml), "hyperlink-prelight-color", &pre, NULL);
644 if (pre) {
645 g_object_set(G_OBJECT(tag), "foreground-gdk", pre, NULL);
646 gdk_color_free(pre);
647 } else
648 g_object_set(G_OBJECT(tag), "foreground", "#70a0ff", NULL);
650 } else {
651 GTK_IMHTML(imhtml)->prelit_tag = NULL;
654 if ((oldprelit_tag != NULL) && (GTK_IMHTML(imhtml)->prelit_tag != oldprelit_tag)) {
655 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml), oldprelit_tag);
658 if (GTK_IMHTML(imhtml)->tip) {
659 if ((tip == GTK_IMHTML(imhtml)->tip)) {
660 g_slist_free(tags);
661 return FALSE;
663 /* We've left the cell. Remove the timeout and create a new one below */
664 if (GTK_IMHTML(imhtml)->tip_window) {
665 gtk_widget_destroy(GTK_IMHTML(imhtml)->tip_window);
666 GTK_IMHTML(imhtml)->tip_window = NULL;
668 if (GTK_IMHTML(imhtml)->editable)
669 cursor = GTK_IMHTML(imhtml)->text_cursor;
670 else
671 cursor = GTK_IMHTML(imhtml)->arrow_cursor;
672 if (GTK_IMHTML(imhtml)->tip_timer)
673 g_source_remove(GTK_IMHTML(imhtml)->tip_timer);
674 GTK_IMHTML(imhtml)->tip_timer = 0;
677 /* If we don't have a tip from a URL, let's see if we have a tip from a smiley */
678 anchor = gtk_text_iter_get_child_anchor(&iter);
679 if (anchor) {
680 tip = g_object_get_data(G_OBJECT(anchor), "gtkimhtml_tiptext");
681 hand = FALSE;
684 if (tip && *tip) {
685 GTK_IMHTML(imhtml)->tip_timer = g_timeout_add (TOOLTIP_TIMEOUT,
686 gtk_imhtml_tip, imhtml);
687 } else if (!tip) {
688 hand = FALSE;
689 for (templist = tags; templist; templist = templist->next) {
690 tag = templist->data;
691 if ((tip = g_object_get_data(G_OBJECT(tag), "cursor"))) {
692 hand = TRUE;
693 break;
698 if (hand && !(GTK_IMHTML(imhtml)->editable))
699 cursor = GTK_IMHTML(imhtml)->hand_cursor;
701 if (cursor)
702 gdk_window_set_cursor(win, cursor);
704 GTK_IMHTML(imhtml)->tip = tip;
705 g_slist_free(tags);
706 return FALSE;
709 static gboolean
710 gtk_enter_event_notify(GtkWidget *imhtml, GdkEventCrossing *event, gpointer data)
712 if (GTK_IMHTML(imhtml)->editable)
713 gdk_window_set_cursor(
714 gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml),
715 GTK_TEXT_WINDOW_TEXT),
716 GTK_IMHTML(imhtml)->text_cursor);
717 else
718 gdk_window_set_cursor(
719 gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml),
720 GTK_TEXT_WINDOW_TEXT),
721 GTK_IMHTML(imhtml)->arrow_cursor);
723 /* propagate the event normally */
724 return FALSE;
727 static gboolean
728 gtk_leave_event_notify(GtkWidget *imhtml, GdkEventCrossing *event, gpointer data)
730 /* when leaving the widget, clear any current & pending tooltips and restore the cursor */
731 if (GTK_IMHTML(imhtml)->prelit_tag) {
732 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml), GTK_IMHTML(imhtml)->prelit_tag);
733 GTK_IMHTML(imhtml)->prelit_tag = NULL;
736 if (GTK_IMHTML(imhtml)->tip_window) {
737 gtk_widget_destroy(GTK_IMHTML(imhtml)->tip_window);
738 GTK_IMHTML(imhtml)->tip_window = NULL;
740 if (GTK_IMHTML(imhtml)->tip_timer) {
741 g_source_remove(GTK_IMHTML(imhtml)->tip_timer);
742 GTK_IMHTML(imhtml)->tip_timer = 0;
744 gdk_window_set_cursor(
745 gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml),
746 GTK_TEXT_WINDOW_TEXT), NULL);
748 /* propagate the event normally */
749 return FALSE;
752 static gint
753 gtk_imhtml_expose_event (GtkWidget *widget,
754 GdkEventExpose *event)
756 GtkTextIter start, end, cur;
757 int buf_x, buf_y;
758 GdkRectangle visible_rect;
759 GdkGC *gc = gdk_gc_new(GDK_DRAWABLE(event->window));
760 GdkColor gcolor;
762 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget), &visible_rect);
763 gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget),
764 GTK_TEXT_WINDOW_TEXT,
765 visible_rect.x,
766 visible_rect.y,
767 &visible_rect.x,
768 &visible_rect.y);
770 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT,
771 event->area.x, event->area.y, &buf_x, &buf_y);
773 if (GTK_IMHTML(widget)->editable || GTK_IMHTML(widget)->wbfo) {
775 if (GTK_IMHTML(widget)->edit.background) {
776 gdk_color_parse(GTK_IMHTML(widget)->edit.background, &gcolor);
777 gdk_gc_set_rgb_fg_color(gc, &gcolor);
778 } else {
779 gdk_gc_set_rgb_fg_color(gc, &(widget->style->base[GTK_WIDGET_STATE(widget)]));
782 gdk_draw_rectangle(event->window,
784 TRUE,
785 visible_rect.x, visible_rect.y, visible_rect.width, visible_rect.height);
786 g_object_unref(G_OBJECT(gc));
788 if (GTK_WIDGET_CLASS (parent_class)->expose_event)
789 return (* GTK_WIDGET_CLASS (parent_class)->expose_event)
790 (widget, event);
791 return FALSE;
795 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &start, buf_x, buf_y);
796 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &end,
797 buf_x + event->area.width, buf_y + event->area.height);
799 gtk_text_iter_order(&start, &end);
801 cur = start;
803 while (gtk_text_iter_in_range(&cur, &start, &end)) {
804 GSList *tags = gtk_text_iter_get_tags(&cur);
805 GSList *l;
807 for (l = tags; l; l = l->next) {
808 GtkTextTag *tag = l->data;
809 GdkRectangle rect;
810 GdkRectangle tag_area;
811 const char *color;
813 if (strncmp(tag->name, "BACKGROUND ", 11))
814 continue;
816 if (gtk_text_iter_ends_tag(&cur, tag))
817 continue;
819 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(widget), &cur, &tag_area);
820 gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget),
821 GTK_TEXT_WINDOW_TEXT,
822 tag_area.x,
823 tag_area.y,
824 &tag_area.x,
825 &tag_area.y);
826 rect.x = visible_rect.x;
827 rect.y = tag_area.y;
828 rect.width = visible_rect.width;
831 gtk_text_iter_forward_to_tag_toggle(&cur, tag);
832 while (!gtk_text_iter_is_end(&cur) && gtk_text_iter_begins_tag(&cur, tag));
834 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(widget), &cur, &tag_area);
835 gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget),
836 GTK_TEXT_WINDOW_TEXT,
837 tag_area.x,
838 tag_area.y,
839 &tag_area.x,
840 &tag_area.y);
843 rect.height = tag_area.y + tag_area.height - rect.y
844 + gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(widget))
845 + gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(widget));
847 color = tag->name + 11;
849 if (!gdk_color_parse(color, &gcolor)) {
850 gchar tmp[8];
851 tmp[0] = '#';
852 strncpy(&tmp[1], color, 7);
853 tmp[7] = '\0';
854 if (!gdk_color_parse(tmp, &gcolor))
855 gdk_color_parse("white", &gcolor);
857 gdk_gc_set_rgb_fg_color(gc, &gcolor);
859 gdk_draw_rectangle(event->window,
861 TRUE,
862 rect.x, rect.y, rect.width, rect.height);
863 gtk_text_iter_backward_char(&cur); /* go back one, in case the end is the begining is the end
864 * note that above, we always moved cur ahead by at least
865 * one character */
866 break;
869 g_slist_free(tags);
871 /* loop until another tag begins, or no tag begins */
872 while (gtk_text_iter_forward_to_tag_toggle(&cur, NULL) &&
873 !gtk_text_iter_is_end(&cur) &&
874 !gtk_text_iter_begins_tag(&cur, NULL));
877 g_object_unref(G_OBJECT(gc));
879 if (GTK_WIDGET_CLASS (parent_class)->expose_event)
880 return (* GTK_WIDGET_CLASS (parent_class)->expose_event)
881 (widget, event);
883 return FALSE;
887 static void paste_unformatted_cb(GtkMenuItem *menu, GtkIMHtml *imhtml)
889 GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD);
891 gtk_clipboard_request_text(clipboard, paste_plaintext_received_cb, imhtml);
895 static void clear_formatting_cb(GtkMenuItem *menu, GtkIMHtml *imhtml)
897 gtk_imhtml_clear_formatting(imhtml);
900 static void disable_smiley_selected(GtkMenuItem *item, GtkIMHtml *imhtml)
902 GtkTextIter start, end;
903 GtkTextMark *mark;
904 char *text;
906 if (!gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end))
907 return;
909 text = gtk_imhtml_get_markup_range(imhtml, &start, &end);
911 mark = gtk_text_buffer_get_selection_bound(imhtml->text_buffer);
912 gtk_text_buffer_delete_selection(imhtml->text_buffer, FALSE, FALSE);
914 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &start, mark);
915 gtk_imhtml_insert_html_at_iter(imhtml, text, GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_SMILEY, &start);
917 g_free(text);
920 static void hijack_menu_cb(GtkIMHtml *imhtml, GtkMenu *menu, gpointer data)
922 GtkWidget *menuitem;
923 GtkTextIter start, end;
925 menuitem = gtk_menu_item_new_with_mnemonic(_("Paste as Plain _Text"));
926 gtk_widget_show(menuitem);
928 * TODO: gtk_clipboard_wait_is_text_available() iterates the glib
929 * mainloop, which tends to be a source of bugs. It would
930 * be good to audit this or change it to not wait.
932 gtk_widget_set_sensitive(menuitem,
933 (imhtml->editable &&
934 gtk_clipboard_wait_is_text_available(
935 gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD))));
936 /* put it after "Paste" */
937 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menuitem, 3);
939 g_signal_connect(G_OBJECT(menuitem), "activate",
940 G_CALLBACK(paste_unformatted_cb), imhtml);
942 menuitem = gtk_menu_item_new_with_mnemonic(_("_Reset formatting"));
943 gtk_widget_show(menuitem);
944 gtk_widget_set_sensitive(menuitem, imhtml->editable);
945 /* put it after Delete */
946 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menuitem, 5);
948 g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(clear_formatting_cb), imhtml);
950 menuitem = gtk_menu_item_new_with_mnemonic(_("Disable _smileys in selected text"));
951 gtk_widget_show(menuitem);
952 if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) {
953 g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(disable_smiley_selected), imhtml);
954 } else {
955 gtk_widget_set_sensitive(menuitem, FALSE);
957 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menuitem, 6);
960 static char *
961 ucs2_order(gboolean swap)
963 gboolean be;
965 be = G_BYTE_ORDER == G_BIG_ENDIAN;
966 be = swap ? be : !be;
968 if (be)
969 return "UTF-16BE";
970 else
971 return "UTF-16LE";
975 /* Convert from UTF-16LE to UTF-8, stripping the BOM if one is present.*/
976 static gchar *
977 utf16_to_utf8_with_bom_check(gchar *data, guint len) {
978 char *fromcode = NULL;
979 GError *error = NULL;
980 guint16 c;
981 gchar *utf8_ret;
984 * Unicode Techinical Report 20
985 * ( http://www.unicode.org/unicode/reports/tr20/ ) says to treat an
986 * initial 0xfeff (ZWNBSP) as a byte order indicator so that is
987 * what we do. If there is no indicator assume it is in the default
988 * order
991 memcpy(&c, data, 2);
992 switch (c) {
993 case 0xfeff:
994 case 0xfffe:
995 fromcode = ucs2_order(c == 0xfeff);
996 data += 2;
997 len -= 2;
998 break;
999 default:
1000 fromcode = "UTF-16";
1001 break;
1004 utf8_ret = g_convert(data, len, "UTF-8", fromcode, NULL, NULL, &error);
1006 if (error) {
1007 purple_debug_warning("gtkimhtml", "g_convert error: %s\n", error->message);
1008 g_error_free(error);
1010 return utf8_ret;
1014 static void gtk_imhtml_clipboard_get(GtkClipboard *clipboard, GtkSelectionData *selection_data, guint info, GtkIMHtml *imhtml) {
1015 char *text = NULL;
1016 gboolean primary = (clipboard != clipboard_selection);
1017 GtkTextIter start, end;
1019 if (primary) {
1020 GtkTextMark *sel = NULL, *ins = NULL;
1022 g_return_if_fail(imhtml != NULL);
1024 ins = gtk_text_buffer_get_insert(imhtml->text_buffer);
1025 sel = gtk_text_buffer_get_selection_bound(imhtml->text_buffer);
1026 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &start, sel);
1027 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &end, ins);
1030 if (info == TARGET_HTML) {
1031 char *selection;
1032 #ifndef _WIN32
1033 gsize len;
1034 if (primary) {
1035 text = gtk_imhtml_get_markup_range(imhtml, &start, &end);
1036 } else
1037 text = html_clipboard;
1039 /* Mozilla asks that we start our text/html with the Unicode byte order mark */
1040 selection = g_convert(text, -1, "UTF-16", "UTF-8", NULL, &len, NULL);
1041 gtk_selection_data_set(selection_data, gdk_atom_intern("text/html", FALSE), 16, (const guchar *)selection, len);
1042 #else
1043 selection = clipboard_html_to_win32(html_clipboard);
1044 gtk_selection_data_set(selection_data, gdk_atom_intern("HTML Format", FALSE), 8, (const guchar *)selection, strlen(selection));
1045 #endif
1046 g_free(selection);
1047 } else {
1048 if (primary) {
1049 text = gtk_imhtml_get_text(imhtml, &start, &end);
1050 } else
1051 text = text_clipboard;
1052 gtk_selection_data_set_text(selection_data, text, strlen(text));
1054 if (primary) /* This was allocated here */
1055 g_free(text);
1058 static void gtk_imhtml_primary_clipboard_clear(GtkClipboard *clipboard, GtkIMHtml *imhtml)
1060 GtkTextIter insert;
1061 GtkTextIter selection_bound;
1063 gtk_text_buffer_get_iter_at_mark (imhtml->text_buffer, &insert,
1064 gtk_text_buffer_get_mark (imhtml->text_buffer, "insert"));
1065 gtk_text_buffer_get_iter_at_mark (imhtml->text_buffer, &selection_bound,
1066 gtk_text_buffer_get_mark (imhtml->text_buffer, "selection_bound"));
1068 if (!gtk_text_iter_equal (&insert, &selection_bound))
1069 gtk_text_buffer_move_mark (imhtml->text_buffer,
1070 gtk_text_buffer_get_mark (imhtml->text_buffer, "selection_bound"),
1071 &insert);
1074 static void gtk_imhtml_clipboard_clear (GtkClipboard *clipboard, GtkSelectionData *sel_data,
1075 guint info, gpointer user_data_or_owner)
1079 static void copy_clipboard_cb(GtkIMHtml *imhtml, gpointer unused)
1081 GtkTextIter start, end;
1082 if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) {
1083 if (!clipboard_selection)
1084 clipboard_selection = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD);
1085 gtk_clipboard_set_with_data(clipboard_selection,
1086 selection_targets, sizeof(selection_targets) / sizeof(GtkTargetEntry),
1087 (GtkClipboardGetFunc)gtk_imhtml_clipboard_get,
1088 (GtkClipboardClearFunc)gtk_imhtml_clipboard_clear, NULL);
1090 g_free(html_clipboard);
1091 g_free(text_clipboard);
1093 html_clipboard = gtk_imhtml_get_markup_range(imhtml, &start, &end);
1094 text_clipboard = gtk_imhtml_get_text(imhtml, &start, &end);
1097 g_signal_stop_emission_by_name(imhtml, "copy-clipboard");
1100 static void cut_clipboard_cb(GtkIMHtml *imhtml, gpointer unused)
1102 GtkTextIter start, end;
1103 if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) {
1104 if (!clipboard_selection)
1105 clipboard_selection = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD);
1106 gtk_clipboard_set_with_data(clipboard_selection,
1107 selection_targets, sizeof(selection_targets) / sizeof(GtkTargetEntry),
1108 (GtkClipboardGetFunc)gtk_imhtml_clipboard_get,
1109 (GtkClipboardClearFunc)gtk_imhtml_clipboard_clear, NULL);
1111 g_free(html_clipboard);
1112 g_free(text_clipboard);
1114 html_clipboard = gtk_imhtml_get_markup_range(imhtml, &start, &end);
1115 text_clipboard = gtk_imhtml_get_text(imhtml, &start, &end);
1117 if (imhtml->editable)
1118 gtk_text_buffer_delete_selection(imhtml->text_buffer, FALSE, FALSE);
1121 g_signal_stop_emission_by_name(imhtml, "cut-clipboard");
1124 static void imhtml_paste_insert(GtkIMHtml *imhtml, const char *text, gboolean plaintext)
1126 GtkTextIter iter;
1127 GtkIMHtmlOptions flags = plaintext ? GTK_IMHTML_NO_SMILEY : (GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_COMMENTS);
1129 /* Delete any currently selected text */
1130 gtk_text_buffer_delete_selection(imhtml->text_buffer, TRUE, TRUE);
1132 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, gtk_text_buffer_get_insert(imhtml->text_buffer));
1133 if (!imhtml->wbfo && !plaintext)
1134 gtk_imhtml_close_tags(imhtml, &iter);
1136 gtk_imhtml_insert_html_at_iter(imhtml, text, flags, &iter);
1137 gtk_text_buffer_move_mark_by_name(imhtml->text_buffer, "insert", &iter);
1138 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(imhtml), gtk_text_buffer_get_insert(imhtml->text_buffer),
1139 0, FALSE, 0.0, 0.0);
1140 if (!imhtml->wbfo && !plaintext)
1141 gtk_imhtml_close_tags(imhtml, &iter);
1145 static void paste_plaintext_received_cb (GtkClipboard *clipboard, const gchar *text, gpointer data)
1147 char *tmp;
1149 if (text == NULL || !(*text))
1150 return;
1152 tmp = g_markup_escape_text(text, -1);
1153 imhtml_paste_insert(data, tmp, TRUE);
1154 g_free(tmp);
1157 static void paste_received_cb (GtkClipboard *clipboard, GtkSelectionData *selection_data, gpointer data)
1159 char *text;
1160 GtkIMHtml *imhtml = data;
1162 if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml)))
1163 return;
1165 if (imhtml->wbfo || selection_data->length <= 0) {
1166 gtk_clipboard_request_text(clipboard, paste_plaintext_received_cb, imhtml);
1167 return;
1168 } else {
1169 #if 0
1170 /* Here's some debug code, for figuring out what sent to us over the clipboard. */
1172 int i;
1174 purple_debug_misc("gtkimhtml", "In paste_received_cb():\n\tformat = %d, length = %d\n\t",
1175 selection_data->format, selection_data->length);
1177 for (i = 0; i < (/*(selection_data->format / 8) **/ selection_data->length); i++) {
1178 if ((i % 70) == 0)
1179 printf("\n\t");
1180 if (selection_data->data[i] == '\0')
1181 printf(".");
1182 else
1183 printf("%c", selection_data->data[i]);
1185 printf("\n");
1187 #endif
1189 text = g_malloc(selection_data->length + 1);
1190 memcpy(text, selection_data->data, selection_data->length);
1191 /* Make sure the paste data is null-terminated. Given that
1192 * we're passed length (but assume later that it is
1193 * null-terminated), this seems sensible to me.
1195 text[selection_data->length] = '\0';
1198 #ifdef _WIN32
1199 if (gtk_selection_data_get_data_type(selection_data) == gdk_atom_intern("HTML Format", FALSE)) {
1200 char *tmp = clipboard_win32_to_html(text);
1201 g_free(text);
1202 text = tmp;
1204 #endif
1206 if (selection_data->length >= 2 &&
1207 (*(guint16 *)text == 0xfeff || *(guint16 *)text == 0xfffe)) {
1208 /* This is UTF-16 */
1209 char *utf8 = utf16_to_utf8_with_bom_check(text, selection_data->length);
1210 g_free(text);
1211 text = utf8;
1212 if (!text) {
1213 purple_debug_warning("gtkimhtml", "g_convert from UTF-16 failed in paste_received_cb\n");
1214 return;
1218 if (!(*text) || !g_utf8_validate(text, -1, NULL)) {
1219 purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in paste_received_cb\n");
1220 g_free(text);
1221 return;
1224 imhtml_paste_insert(imhtml, text, FALSE);
1225 g_free(text);
1229 static void smart_backspace_cb(GtkIMHtml *imhtml, gpointer blah)
1231 GtkTextIter iter;
1232 GtkTextChildAnchor* anchor;
1233 char * text;
1234 gint offset;
1236 if (!imhtml->editable)
1237 return;
1239 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, gtk_text_buffer_get_insert(imhtml->text_buffer));
1241 /* Get the character before the insertion point */
1242 offset = gtk_text_iter_get_offset(&iter);
1243 if (offset <= 0)
1244 return;
1246 gtk_text_iter_backward_char(&iter);
1247 anchor = gtk_text_iter_get_child_anchor(&iter);
1249 if (!anchor)
1250 return; /* No smiley here */
1252 text = g_object_get_data(G_OBJECT(anchor), "gtkimhtml_plaintext");
1253 if (!text)
1254 return;
1256 /* ok, then we need to insert the image buffer text before the anchor */
1257 gtk_text_buffer_insert(imhtml->text_buffer, &iter, text, -1);
1260 static void paste_clipboard_cb(GtkIMHtml *imhtml, gpointer blah)
1262 #ifdef _WIN32
1263 /* If we're on windows, let's see if we can get data from the HTML Format
1264 clipboard before we try to paste from the GTK buffer */
1265 if (!clipboard_paste_html_win32(imhtml) && gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml))) {
1266 GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD);
1267 gtk_clipboard_request_text(clipboard, paste_plaintext_received_cb, imhtml);
1270 #else
1271 GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD);
1272 gtk_clipboard_request_contents(clipboard, gdk_atom_intern("text/html", FALSE),
1273 paste_received_cb, imhtml);
1274 #endif
1275 g_signal_stop_emission_by_name(imhtml, "paste-clipboard");
1278 static void imhtml_realized_remove_primary(GtkIMHtml *imhtml, gpointer unused)
1280 gtk_text_buffer_remove_selection_clipboard(GTK_IMHTML(imhtml)->text_buffer,
1281 gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY));
1285 static void imhtml_destroy_add_primary(GtkIMHtml *imhtml, gpointer unused)
1287 gtk_text_buffer_add_selection_clipboard(GTK_IMHTML(imhtml)->text_buffer,
1288 gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY));
1291 static void mark_set_so_update_selection_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextMark *mark, GtkIMHtml *imhtml)
1293 if (gtk_text_buffer_get_selection_bounds(buffer, NULL, NULL)) {
1294 gtk_clipboard_set_with_owner(gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY),
1295 selection_targets, sizeof(selection_targets) / sizeof(GtkTargetEntry),
1296 (GtkClipboardGetFunc)gtk_imhtml_clipboard_get,
1297 (GtkClipboardClearFunc)gtk_imhtml_primary_clipboard_clear, G_OBJECT(imhtml));
1301 static gboolean gtk_imhtml_button_press_event(GtkIMHtml *imhtml, GdkEventButton *event, gpointer unused)
1303 if (event->button == 2) {
1304 int x, y;
1305 GtkTextIter iter;
1306 GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY);
1308 if (!imhtml->editable)
1309 return FALSE;
1311 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(imhtml),
1312 GTK_TEXT_WINDOW_TEXT,
1313 event->x,
1314 event->y,
1316 &y);
1317 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml), &iter, x, y);
1318 gtk_text_buffer_place_cursor(imhtml->text_buffer, &iter);
1320 gtk_clipboard_request_contents(clipboard, gdk_atom_intern("text/html", FALSE),
1321 paste_received_cb, imhtml);
1323 return TRUE;
1326 return FALSE;
1329 static void
1330 gtk_imhtml_undo(GtkIMHtml *imhtml)
1332 g_return_if_fail(GTK_IS_IMHTML(imhtml));
1333 if (imhtml->editable &&
1334 gtk_source_undo_manager_can_undo(imhtml->undo_manager))
1335 gtk_source_undo_manager_undo(imhtml->undo_manager);
1338 static void
1339 gtk_imhtml_redo(GtkIMHtml *imhtml)
1341 g_return_if_fail(GTK_IS_IMHTML(imhtml));
1342 if (imhtml->editable &&
1343 gtk_source_undo_manager_can_redo(imhtml->undo_manager))
1344 gtk_source_undo_manager_redo(imhtml->undo_manager);
1348 static gboolean imhtml_message_send(GtkIMHtml *imhtml)
1350 return FALSE;
1353 static void
1354 imhtml_paste_cb(GtkIMHtml *imhtml, const char *str)
1356 if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml)))
1357 return;
1359 if (!str || !*str || !strcmp(str, "html"))
1360 g_signal_emit_by_name(imhtml, "paste_clipboard");
1361 else if (!strcmp(str, "text"))
1362 paste_unformatted_cb(NULL, imhtml);
1365 static void imhtml_toggle_format(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons)
1367 /* since this function is the handler for the formatting keystrokes,
1368 we need to check here that the formatting attempted is permitted */
1369 buttons &= imhtml->format_functions;
1371 switch (buttons) {
1372 case GTK_IMHTML_BOLD:
1373 imhtml_toggle_bold(imhtml);
1374 break;
1375 case GTK_IMHTML_ITALIC:
1376 imhtml_toggle_italic(imhtml);
1377 break;
1378 case GTK_IMHTML_UNDERLINE:
1379 imhtml_toggle_underline(imhtml);
1380 break;
1381 case GTK_IMHTML_STRIKE:
1382 imhtml_toggle_strike(imhtml);
1383 break;
1384 case GTK_IMHTML_SHRINK:
1385 imhtml_font_shrink(imhtml);
1386 break;
1387 case GTK_IMHTML_GROW:
1388 imhtml_font_grow(imhtml);
1389 break;
1390 default:
1391 break;
1395 static void
1396 gtk_imhtml_finalize (GObject *object)
1398 GtkIMHtml *imhtml = GTK_IMHTML(object);
1399 GList *scalables;
1400 GSList *l;
1402 if (imhtml->scroll_src)
1403 g_source_remove(imhtml->scroll_src);
1404 if (imhtml->scroll_time)
1405 g_timer_destroy(imhtml->scroll_time);
1407 g_hash_table_destroy(imhtml->smiley_data);
1408 gtk_smiley_tree_destroy(imhtml->default_smilies);
1409 gdk_cursor_unref(imhtml->hand_cursor);
1410 gdk_cursor_unref(imhtml->arrow_cursor);
1411 gdk_cursor_unref(imhtml->text_cursor);
1413 if(imhtml->tip_window){
1414 gtk_widget_destroy(imhtml->tip_window);
1416 if(imhtml->tip_timer)
1417 g_source_remove(imhtml->tip_timer);
1419 for(scalables = imhtml->scalables; scalables; scalables = scalables->next) {
1420 struct scalable_data *sd = scalables->data;
1421 GtkIMHtmlScalable *scale = GTK_IMHTML_SCALABLE(sd->scalable);
1422 scale->free(scale);
1423 g_free(sd);
1426 for (l = imhtml->im_images; l; l = l->next) {
1427 struct im_image_data *img_data = l->data;
1428 if (imhtml->funcs->image_unref)
1429 imhtml->funcs->image_unref(img_data->id);
1430 g_free(img_data);
1433 g_list_free(imhtml->scalables);
1434 g_slist_free(imhtml->im_images);
1435 g_queue_free(imhtml->animations);
1436 g_free(imhtml->protocol_name);
1437 g_free(imhtml->search_string);
1438 g_object_unref(imhtml->undo_manager);
1439 G_OBJECT_CLASS(parent_class)->finalize (object);
1443 static GtkIMHtmlProtocol *
1444 imhtml_find_protocol(const char *url, gboolean reverse)
1446 GtkIMHtmlClass *klass;
1447 GList *iter;
1448 GtkIMHtmlProtocol *proto = NULL;
1449 int length = reverse ? strlen(url) : -1;
1451 klass = g_type_class_ref(GTK_TYPE_IMHTML);
1452 for (iter = klass->protocols; iter; iter = iter->next) {
1453 proto = iter->data;
1454 if (g_ascii_strncasecmp(url, proto->name, reverse ? MIN(length, proto->length) : proto->length) == 0) {
1455 return proto;
1458 return NULL;
1461 static void
1462 imhtml_url_clicked(GtkIMHtml *imhtml, const char *url)
1464 GtkIMHtmlProtocol *proto = imhtml_find_protocol(url, FALSE);
1465 GtkIMHtmlLink *link;
1466 if (!proto)
1467 return;
1468 link = g_new0(GtkIMHtmlLink, 1);
1469 link->imhtml = g_object_ref(imhtml);
1470 link->url = g_strdup(url);
1471 proto->activate(imhtml, link); /* XXX: Do something with the return value? */
1472 gtk_imhtml_link_destroy(link);
1475 /* Boring GTK+ stuff */
1476 static void gtk_imhtml_class_init (GtkIMHtmlClass *klass)
1478 GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
1479 GtkBindingSet *binding_set;
1480 GObjectClass *gobject_class;
1481 gobject_class = (GObjectClass*) klass;
1482 parent_class = g_type_class_ref(GTK_TYPE_TEXT_VIEW);
1483 signals[URL_CLICKED] = g_signal_new("url_clicked",
1484 G_TYPE_FROM_CLASS(gobject_class),
1485 G_SIGNAL_RUN_FIRST,
1486 G_STRUCT_OFFSET(GtkIMHtmlClass, url_clicked),
1487 NULL,
1489 g_cclosure_marshal_VOID__POINTER,
1490 G_TYPE_NONE, 1,
1491 G_TYPE_POINTER);
1492 signals[BUTTONS_UPDATE] = g_signal_new("format_buttons_update",
1493 G_TYPE_FROM_CLASS(gobject_class),
1494 G_SIGNAL_RUN_FIRST,
1495 G_STRUCT_OFFSET(GtkIMHtmlClass, buttons_update),
1496 NULL,
1498 g_cclosure_marshal_VOID__INT,
1499 G_TYPE_NONE, 1,
1500 G_TYPE_INT);
1501 signals[TOGGLE_FORMAT] = g_signal_new("format_function_toggle",
1502 G_TYPE_FROM_CLASS(gobject_class),
1503 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1504 G_STRUCT_OFFSET(GtkIMHtmlClass, toggle_format),
1505 NULL,
1507 g_cclosure_marshal_VOID__INT,
1508 G_TYPE_NONE, 1,
1509 G_TYPE_INT);
1510 signals[CLEAR_FORMAT] = g_signal_new("format_function_clear",
1511 G_TYPE_FROM_CLASS(gobject_class),
1512 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
1513 G_STRUCT_OFFSET(GtkIMHtmlClass, clear_format),
1514 NULL,
1516 g_cclosure_marshal_VOID__VOID,
1517 G_TYPE_NONE, 0);
1518 signals[UPDATE_FORMAT] = g_signal_new("format_function_update",
1519 G_TYPE_FROM_CLASS(gobject_class),
1520 G_SIGNAL_RUN_FIRST,
1521 G_STRUCT_OFFSET(GtkIMHtmlClass, update_format),
1522 NULL,
1524 g_cclosure_marshal_VOID__VOID,
1525 G_TYPE_NONE, 0);
1526 signals[MESSAGE_SEND] = g_signal_new("message_send",
1527 G_TYPE_FROM_CLASS(gobject_class),
1528 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1529 G_STRUCT_OFFSET(GtkIMHtmlClass, message_send),
1530 NULL,
1531 0, g_cclosure_marshal_VOID__VOID,
1532 G_TYPE_NONE, 0);
1533 signals[PASTE] = g_signal_new("paste",
1534 G_TYPE_FROM_CLASS(gobject_class),
1535 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1537 NULL,
1538 0, g_cclosure_marshal_VOID__STRING,
1539 G_TYPE_NONE, 1, G_TYPE_STRING);
1540 signals [UNDO] = g_signal_new ("undo",
1541 G_TYPE_FROM_CLASS (klass),
1542 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1543 G_STRUCT_OFFSET (GtkIMHtmlClass, undo),
1544 NULL,
1545 NULL,
1546 gtksourceview_marshal_VOID__VOID,
1547 G_TYPE_NONE,
1549 signals [REDO] = g_signal_new ("redo",
1550 G_TYPE_FROM_CLASS (klass),
1551 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1552 G_STRUCT_OFFSET (GtkIMHtmlClass, redo),
1553 NULL,
1554 NULL,
1555 gtksourceview_marshal_VOID__VOID,
1556 G_TYPE_NONE,
1561 klass->toggle_format = imhtml_toggle_format;
1562 klass->message_send = imhtml_message_send;
1563 klass->clear_format = imhtml_clear_formatting;
1564 klass->url_clicked = imhtml_url_clicked;
1565 klass->undo = gtk_imhtml_undo;
1566 klass->redo = gtk_imhtml_redo;
1568 gobject_class->finalize = gtk_imhtml_finalize;
1569 widget_class->drag_motion = gtk_text_view_drag_motion;
1570 widget_class->expose_event = gtk_imhtml_expose_event;
1571 parent_size_allocate = widget_class->size_allocate;
1572 widget_class->size_allocate = gtk_imhtml_size_allocate;
1573 parent_style_set = widget_class->style_set;
1574 widget_class->style_set = gtk_imhtml_style_set;
1576 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("hyperlink-color",
1577 _("Hyperlink color"),
1578 _("Color to draw hyperlinks."),
1579 GDK_TYPE_COLOR, G_PARAM_READABLE));
1580 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("hyperlink-visited-color",
1581 _("Hyperlink visited color"),
1582 _("Color to draw hyperlink after it has been visited (or activated)."),
1583 GDK_TYPE_COLOR, G_PARAM_READABLE));
1584 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("hyperlink-prelight-color",
1585 _("Hyperlink prelight color"),
1586 _("Color to draw hyperlinks when mouse is over them."),
1587 GDK_TYPE_COLOR, G_PARAM_READABLE));
1588 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("send-name-color",
1589 _("Sent Message Name Color"),
1590 _("Color to draw the name of a message you sent."),
1591 GDK_TYPE_COLOR, G_PARAM_READABLE));
1592 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("receive-name-color",
1593 _("Received Message Name Color"),
1594 _("Color to draw the name of a message you received."),
1595 GDK_TYPE_COLOR, G_PARAM_READABLE));
1596 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("highlight-name-color",
1597 _("\"Attention\" Name Color"),
1598 _("Color to draw the name of a message you received containing your name."),
1599 GDK_TYPE_COLOR, G_PARAM_READABLE));
1600 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("action-name-color",
1601 _("Action Message Name Color"),
1602 _("Color to draw the name of an action message."),
1603 GDK_TYPE_COLOR, G_PARAM_READABLE));
1604 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("whisper-action-name-color",
1605 _("Action Message Name Color for Whispered Message"),
1606 _("Color to draw the name of a whispered action message."),
1607 GDK_TYPE_COLOR, G_PARAM_READABLE));
1608 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("whisper-name-color",
1609 _("Whisper Message Name Color"),
1610 _("Color to draw the name of a whispered message."),
1611 GDK_TYPE_COLOR, G_PARAM_READABLE));
1613 /* Customizable typing notification ... sort of. Example:
1614 * GtkIMHtml::typing-notification-font = "monospace italic light 8.0"
1615 * GtkIMHtml::typing-notification-color = "#ff0000"
1616 * GtkIMHtml::typing-notification-enable = 1
1618 gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("typing-notification-color",
1619 _("Typing notification color"),
1620 _("The color to use for the typing notification"),
1621 GDK_TYPE_COLOR, G_PARAM_READABLE));
1622 gtk_widget_class_install_style_property(widget_class, g_param_spec_string("typing-notification-font",
1623 _("Typing notification font"),
1624 _("The font to use for the typing notification"),
1625 "light 8.0", G_PARAM_READABLE));
1626 gtk_widget_class_install_style_property(widget_class, g_param_spec_boolean("typing-notification-enable",
1627 _("Enable typing notification"),
1628 _("Enable typing notification"),
1629 TRUE, G_PARAM_READABLE));
1631 binding_set = gtk_binding_set_by_class (parent_class);
1632 gtk_binding_entry_add_signal (binding_set, GDK_b, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_BOLD);
1633 gtk_binding_entry_add_signal (binding_set, GDK_i, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_ITALIC);
1634 gtk_binding_entry_add_signal (binding_set, GDK_u, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_UNDERLINE);
1635 gtk_binding_entry_add_signal (binding_set, GDK_plus, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_GROW);
1636 gtk_binding_entry_add_signal (binding_set, GDK_equal, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_GROW);
1637 gtk_binding_entry_add_signal (binding_set, GDK_minus, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_SHRINK);
1638 binding_set = gtk_binding_set_by_class(klass);
1639 gtk_binding_entry_add_signal (binding_set, GDK_r, GDK_CONTROL_MASK, "format_function_clear", 0);
1640 gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0, "message_send", 0);
1641 gtk_binding_entry_add_signal (binding_set, GDK_Return, 0, "message_send", 0);
1642 gtk_binding_entry_add_signal (binding_set, GDK_z, GDK_CONTROL_MASK, "undo", 0);
1643 gtk_binding_entry_add_signal (binding_set, GDK_z, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "redo", 0);
1644 gtk_binding_entry_add_signal (binding_set, GDK_F14, 0, "undo", 0);
1645 gtk_binding_entry_add_signal(binding_set, GDK_v, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "paste", 1, G_TYPE_STRING, "text");
1648 static void gtk_imhtml_init (GtkIMHtml *imhtml)
1650 imhtml->text_buffer = gtk_text_buffer_new(NULL);
1651 imhtml->undo_manager = gtk_source_undo_manager_new(imhtml->text_buffer);
1652 gtk_text_view_set_buffer(GTK_TEXT_VIEW(imhtml), imhtml->text_buffer);
1653 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml), GTK_WRAP_WORD_CHAR);
1654 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(imhtml), 2);
1655 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(imhtml), 3);
1656 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(imhtml), 2);
1657 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(imhtml), 2);
1658 /*gtk_text_view_set_indent(GTK_TEXT_VIEW(imhtml), -15);*/
1659 /*gtk_text_view_set_justification(GTK_TEXT_VIEW(imhtml), GTK_JUSTIFY_FILL);*/
1661 /* These tags will be used often and can be reused--we create them on init and then apply them by name
1662 * other tags (color, size, face, etc.) will have to be created and applied dynamically
1663 * Note that even though we created SUB, SUP, and PRE tags here, we don't really
1664 * apply them anywhere yet. */
1665 gtk_text_buffer_create_tag(imhtml->text_buffer, "BOLD", "weight", PANGO_WEIGHT_BOLD, NULL);
1666 gtk_text_buffer_create_tag(imhtml->text_buffer, "ITALICS", "style", PANGO_STYLE_ITALIC, NULL);
1667 gtk_text_buffer_create_tag(imhtml->text_buffer, "UNDERLINE", "underline", PANGO_UNDERLINE_SINGLE, NULL);
1668 gtk_text_buffer_create_tag(imhtml->text_buffer, "STRIKE", "strikethrough", TRUE, NULL);
1669 gtk_text_buffer_create_tag(imhtml->text_buffer, "SUB", "rise", -5000, NULL);
1670 gtk_text_buffer_create_tag(imhtml->text_buffer, "SUP", "rise", 5000, NULL);
1671 gtk_text_buffer_create_tag(imhtml->text_buffer, "PRE", "family", "Monospace", NULL);
1672 gtk_text_buffer_create_tag(imhtml->text_buffer, "search", "background", "#22ff00", "weight", "bold", NULL);
1673 gtk_text_buffer_create_tag(imhtml->text_buffer, "comment", "weight", PANGO_WEIGHT_NORMAL,
1674 #if FALSE && GTK_CHECK_VERSION(2,10,10)
1675 "invisible", FALSE,
1676 #endif
1677 NULL);
1679 gtk_text_buffer_create_tag(imhtml->text_buffer, "send-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1680 gtk_text_buffer_create_tag(imhtml->text_buffer, "receive-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1681 gtk_text_buffer_create_tag(imhtml->text_buffer, "highlight-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1682 gtk_text_buffer_create_tag(imhtml->text_buffer, "action-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1683 gtk_text_buffer_create_tag(imhtml->text_buffer, "whisper-action-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1684 gtk_text_buffer_create_tag(imhtml->text_buffer, "whisper-name", "weight", PANGO_WEIGHT_BOLD, NULL);
1686 /* When hovering over a link, we show the hand cursor--elsewhere we show the plain ol' pointer cursor */
1687 imhtml->hand_cursor = gdk_cursor_new (GDK_HAND2);
1688 imhtml->arrow_cursor = gdk_cursor_new (GDK_LEFT_PTR);
1689 imhtml->text_cursor = gdk_cursor_new (GDK_XTERM);
1691 imhtml->show_comments = TRUE;
1693 imhtml->smiley_data = g_hash_table_new_full(g_str_hash, g_str_equal,
1694 g_free, (GDestroyNotify)gtk_smiley_tree_destroy);
1695 imhtml->default_smilies = gtk_smiley_tree_new();
1697 g_signal_connect(G_OBJECT(imhtml), "motion-notify-event", G_CALLBACK(gtk_motion_event_notify), NULL);
1698 g_signal_connect(G_OBJECT(imhtml), "leave-notify-event", G_CALLBACK(gtk_leave_event_notify), NULL);
1699 g_signal_connect(G_OBJECT(imhtml), "enter-notify-event", G_CALLBACK(gtk_enter_event_notify), NULL);
1700 g_signal_connect(G_OBJECT(imhtml), "button_press_event", G_CALLBACK(gtk_imhtml_button_press_event), NULL);
1701 g_signal_connect(G_OBJECT(imhtml->text_buffer), "insert-text", G_CALLBACK(preinsert_cb), imhtml);
1702 g_signal_connect(G_OBJECT(imhtml->text_buffer), "delete_range", G_CALLBACK(delete_cb), imhtml);
1703 g_signal_connect_after(G_OBJECT(imhtml->text_buffer), "insert-text", G_CALLBACK(insert_cb), imhtml);
1704 g_signal_connect_after(G_OBJECT(imhtml->text_buffer), "insert-child-anchor", G_CALLBACK(insert_ca_cb), imhtml);
1705 gtk_drag_dest_set(GTK_WIDGET(imhtml), 0,
1706 link_drag_drop_targets, sizeof(link_drag_drop_targets) / sizeof(GtkTargetEntry),
1707 GDK_ACTION_COPY);
1708 g_signal_connect(G_OBJECT(imhtml), "drag_data_received", G_CALLBACK(gtk_imhtml_link_drag_rcv_cb), imhtml);
1709 g_signal_connect(G_OBJECT(imhtml), "drag_drop", G_CALLBACK(gtk_imhtml_link_drop_cb), imhtml);
1711 g_signal_connect(G_OBJECT(imhtml), "copy-clipboard", G_CALLBACK(copy_clipboard_cb), NULL);
1712 g_signal_connect(G_OBJECT(imhtml), "cut-clipboard", G_CALLBACK(cut_clipboard_cb), NULL);
1713 g_signal_connect(G_OBJECT(imhtml), "paste-clipboard", G_CALLBACK(paste_clipboard_cb), NULL);
1714 g_signal_connect_after(G_OBJECT(imhtml), "realize", G_CALLBACK(imhtml_realized_remove_primary), NULL);
1715 g_signal_connect(G_OBJECT(imhtml), "unrealize", G_CALLBACK(imhtml_destroy_add_primary), NULL);
1716 g_signal_connect(G_OBJECT(imhtml), "paste", G_CALLBACK(imhtml_paste_cb), NULL);
1718 #ifndef _WIN32
1719 g_signal_connect_after(G_OBJECT(GTK_IMHTML(imhtml)->text_buffer), "mark-set",
1720 G_CALLBACK(mark_set_so_update_selection_cb), imhtml);
1721 #endif
1723 gtk_widget_add_events(GTK_WIDGET(imhtml),
1724 GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK);
1726 imhtml->tip = NULL;
1727 imhtml->tip_timer = 0;
1728 imhtml->tip_window = NULL;
1730 imhtml->edit.bold = FALSE;
1731 imhtml->edit.italic = FALSE;
1732 imhtml->edit.underline = FALSE;
1733 imhtml->edit.forecolor = NULL;
1734 imhtml->edit.backcolor = NULL;
1735 imhtml->edit.fontface = NULL;
1736 imhtml->edit.fontsize = 0;
1737 imhtml->edit.link = NULL;
1740 imhtml->scalables = NULL;
1741 imhtml->animations = g_queue_new();
1742 gtk_imhtml_set_editable(imhtml, FALSE);
1743 g_signal_connect(G_OBJECT(imhtml), "populate-popup",
1744 G_CALLBACK(hijack_menu_cb), NULL);
1747 GtkWidget *gtk_imhtml_new(void *a, void *b)
1749 return GTK_WIDGET(g_object_new(gtk_imhtml_get_type(), NULL));
1752 GType gtk_imhtml_get_type()
1754 static GType imhtml_type = 0;
1756 if (!imhtml_type) {
1757 static const GTypeInfo imhtml_info = {
1758 sizeof(GtkIMHtmlClass),
1759 NULL,
1760 NULL,
1761 (GClassInitFunc) gtk_imhtml_class_init,
1762 NULL,
1763 NULL,
1764 sizeof (GtkIMHtml),
1766 (GInstanceInitFunc) gtk_imhtml_init,
1767 NULL
1770 imhtml_type = g_type_register_static(gtk_text_view_get_type(),
1771 "GtkIMHtml", &imhtml_info, 0);
1774 return imhtml_type;
1777 static void gtk_imhtml_link_destroy(GtkIMHtmlLink *link)
1779 if (link->imhtml)
1780 g_object_unref(link->imhtml);
1781 if (link->tag)
1782 g_object_unref(link->tag);
1783 g_free(link->url);
1784 g_free(link);
1787 /* The callback for an event on a link tag. */
1788 static gboolean tag_event(GtkTextTag *tag, GObject *imhtml, GdkEvent *event, GtkTextIter *arg2, gpointer unused)
1790 GdkEventButton *event_button = (GdkEventButton *) event;
1791 if (GTK_IMHTML(imhtml)->editable)
1792 return FALSE;
1793 if (event->type == GDK_BUTTON_RELEASE) {
1794 if ((event_button->button == 1) || (event_button->button == 2)) {
1795 GtkTextIter start, end;
1796 /* we shouldn't open a URL if the user has selected something: */
1797 if (gtk_text_buffer_get_selection_bounds(
1798 gtk_text_iter_get_buffer(arg2), &start, &end))
1799 return FALSE;
1800 gtk_imhtml_activate_tag(GTK_IMHTML(imhtml), tag);
1801 return FALSE;
1802 } else if(event_button->button == 3) {
1803 GList *children;
1804 GtkWidget *menu;
1805 GtkIMHtmlProtocol *proto;
1806 GtkIMHtmlLink *link = g_new(GtkIMHtmlLink, 1);
1807 link->imhtml = g_object_ref(imhtml);
1808 link->url = g_strdup(g_object_get_data(G_OBJECT(tag), "link_url"));
1809 link->tag = g_object_ref(tag);
1811 /* Don't want the tooltip around if user right-clicked on link */
1812 if (GTK_IMHTML(imhtml)->tip_window) {
1813 gtk_widget_destroy(GTK_IMHTML(imhtml)->tip_window);
1814 GTK_IMHTML(imhtml)->tip_window = NULL;
1816 if (GTK_IMHTML(imhtml)->tip_timer) {
1817 g_source_remove(GTK_IMHTML(imhtml)->tip_timer);
1818 GTK_IMHTML(imhtml)->tip_timer = 0;
1820 if (GTK_IMHTML(imhtml)->editable)
1821 gdk_window_set_cursor(event_button->window, GTK_IMHTML(imhtml)->text_cursor);
1822 else
1823 gdk_window_set_cursor(event_button->window, GTK_IMHTML(imhtml)->arrow_cursor);
1824 menu = gtk_menu_new();
1825 g_object_set_data_full(G_OBJECT(menu), "x-imhtml-url-data", link,
1826 (GDestroyNotify)gtk_imhtml_link_destroy);
1828 proto = imhtml_find_protocol(link->url, FALSE);
1830 if (proto && proto->context_menu) {
1831 proto->context_menu(GTK_IMHTML(link->imhtml), link, menu);
1834 children = gtk_container_get_children(GTK_CONTAINER(menu));
1835 if (!children) {
1836 GtkWidget *item = gtk_menu_item_new_with_label(_("No actions available"));
1837 gtk_widget_show(item);
1838 gtk_widget_set_sensitive(item, FALSE);
1839 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
1840 } else {
1841 g_list_free(children);
1845 gtk_widget_show_all(menu);
1846 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
1847 event_button->button, event_button->time);
1849 return TRUE;
1852 if(event->type == GDK_BUTTON_PRESS && event_button->button == 3)
1853 return TRUE; /* Clicking the right mouse button on a link shouldn't
1854 be caught by the regular GtkTextView menu */
1855 else
1856 return FALSE; /* Let clicks go through if we didn't catch anything */
1859 static gboolean
1860 gtk_text_view_drag_motion (GtkWidget *widget,
1861 GdkDragContext *context,
1862 gint x,
1863 gint y,
1864 guint time)
1866 GdkDragAction suggested_action = 0;
1868 if (gtk_drag_dest_find_target (widget, context, NULL) == GDK_NONE) {
1869 /* can't accept any of the offered targets */
1870 } else {
1871 GtkWidget *source_widget;
1872 suggested_action = context->suggested_action;
1873 source_widget = gtk_drag_get_source_widget (context);
1874 if (source_widget == widget) {
1875 /* Default to MOVE, unless the user has
1876 * pressed ctrl or alt to affect available actions
1878 if ((context->actions & GDK_ACTION_MOVE) != 0)
1879 suggested_action = GDK_ACTION_MOVE;
1883 gdk_drag_status (context, suggested_action, time);
1885 /* TRUE return means don't propagate the drag motion to parent
1886 * widgets that may also be drop sites.
1888 return TRUE;
1891 static void
1892 gtk_imhtml_link_drop_cb(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer user_data)
1894 GdkAtom target = gtk_drag_dest_find_target (widget, context, NULL);
1896 if (target != GDK_NONE)
1897 gtk_drag_get_data (widget, context, target, time);
1898 else
1899 gtk_drag_finish (context, FALSE, FALSE, time);
1901 return;
1904 static void
1905 gtk_imhtml_link_drag_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y,
1906 GtkSelectionData *sd, guint info, guint t, GtkIMHtml *imhtml)
1908 gchar **links;
1909 gchar *link;
1910 char *text = (char *)sd->data;
1911 GtkTextMark *mark = gtk_text_buffer_get_insert(imhtml->text_buffer);
1912 GtkTextIter iter;
1913 gint i = 0;
1915 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, mark);
1917 if(gtk_imhtml_get_editable(imhtml) && sd->data){
1918 switch (info) {
1919 case GTK_IMHTML_DRAG_URL:
1920 /* TODO: Is it really ok to change sd->data...? */
1921 purple_str_strip_char((char *)sd->data, '\r');
1923 links = g_strsplit((char *)sd->data, "\n", 0);
1924 while((link = links[i]) != NULL){
1925 if (gtk_imhtml_is_protocol(link)) {
1926 gchar *label;
1928 if(links[i + 1])
1929 i++;
1931 label = links[i];
1933 gtk_imhtml_insert_link(imhtml, mark, link, label);
1934 } else if (*link == '\0') {
1935 /* Ignore blank lines */
1936 } else {
1937 /* Special reasons, aka images being put in via other tag, etc. */
1938 /* ... don't pretend we handled it if we didn't */
1939 gtk_drag_finish(dc, FALSE, FALSE, t);
1940 g_strfreev(links);
1941 return;
1944 i++;
1946 g_strfreev(links);
1947 break;
1948 case GTK_IMHTML_DRAG_HTML:
1950 char *utf8 = NULL;
1951 /* Ewww. This is all because mozilla thinks that text/html is 'for internal use only.'
1952 * as explained by this comment in gtkhtml:
1954 * FIXME This hack decides the charset of the selection. It seems that
1955 * mozilla/netscape alway use ucs2 for text/html
1956 * and openoffice.org seems to always use utf8 so we try to validate
1957 * the string as utf8 and if that fails we assume it is ucs2
1959 * See also the comment on text/html here:
1960 * http://mail.gnome.org/archives/gtk-devel-list/2001-September/msg00114.html
1962 if (sd->length >= 2 && !g_utf8_validate(text, sd->length - 1, NULL)) {
1963 utf8 = utf16_to_utf8_with_bom_check(text, sd->length);
1965 if (!utf8) {
1966 purple_debug_warning("gtkimhtml", "g_convert from UTF-16 failed in drag_rcv_cb\n");
1967 return;
1969 } else if (!(*text) || !g_utf8_validate(text, -1, NULL)) {
1970 purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in drag_rcv_cb\n");
1971 return;
1974 gtk_imhtml_insert_html_at_iter(imhtml, utf8 ? utf8 : text, 0, &iter);
1975 g_free(utf8);
1976 break;
1978 case GTK_IMHTML_DRAG_TEXT:
1979 if (!(*text) || !g_utf8_validate(text, -1, NULL)) {
1980 purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in drag_rcv_cb\n");
1981 return;
1982 } else {
1983 char *tmp = g_markup_escape_text(text, -1);
1984 gtk_imhtml_insert_html_at_iter(imhtml, tmp, 0, &iter);
1985 g_free(tmp);
1987 break;
1988 default:
1989 gtk_drag_finish(dc, FALSE, FALSE, t);
1990 return;
1992 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
1993 } else {
1994 gtk_drag_finish(dc, FALSE, FALSE, t);
1998 static void gtk_smiley_tree_remove (GtkSmileyTree *tree,
1999 GtkIMHtmlSmiley *smiley)
2001 GtkSmileyTree *t = tree;
2002 const gchar *x = smiley->smile;
2003 gint len = 0;
2005 while (*x) {
2006 gchar *pos;
2008 if (!t->values)
2009 return;
2011 pos = strchr (t->values->str, *x);
2012 if (pos)
2013 t = t->children [pos - t->values->str];
2014 else
2015 return;
2017 x++; len++;
2020 if (t->image) {
2021 t->image = NULL;
2025 static gint
2026 gtk_smiley_tree_lookup (GtkSmileyTree *tree,
2027 const gchar *text)
2029 GtkSmileyTree *t = tree;
2030 const gchar *x = text;
2031 gint len = 0;
2032 const gchar *amp;
2033 gint alen;
2035 while (*x) {
2036 gchar *pos;
2038 if (!t->values)
2039 break;
2041 if(*x == '&' && (amp = purple_markup_unescape_entity(x, &alen))) {
2042 gboolean matched = TRUE;
2043 /* Make sure all chars of the unescaped value match */
2044 while (*(amp + 1)) {
2045 pos = strchr (t->values->str, *amp);
2046 if (pos)
2047 t = t->children [GPOINTER_TO_INT(pos) - GPOINTER_TO_INT(t->values->str)];
2048 else {
2049 matched = FALSE;
2050 break;
2052 amp++;
2054 if (!matched)
2055 break;
2057 pos = strchr (t->values->str, *amp);
2059 else if (*x == '<') /* Because we're all WYSIWYG now, a '<'
2060 * char should only appear as the start of a tag. Perhaps a safer (but costlier)
2061 * check would be to call gtk_imhtml_is_tag on it */
2062 break;
2063 else {
2064 alen = 1;
2065 pos = strchr (t->values->str, *x);
2068 if (pos)
2069 t = t->children [GPOINTER_TO_INT(pos) - GPOINTER_TO_INT(t->values->str)];
2070 else
2071 break;
2073 x += alen;
2074 len += alen;
2077 if (t->image)
2078 return len;
2080 return 0;
2083 static void
2084 gtk_imhtml_disassociate_smiley_foreach(gpointer key, gpointer value,
2085 gpointer user_data)
2087 GtkSmileyTree *tree = (GtkSmileyTree *) value;
2088 GtkIMHtmlSmiley *smiley = (GtkIMHtmlSmiley *) user_data;
2089 gtk_smiley_tree_remove(tree, smiley);
2092 static void
2093 gtk_imhtml_disconnect_smiley(GtkIMHtml *imhtml, GtkIMHtmlSmiley *smiley)
2095 smiley->imhtml = NULL;
2096 g_signal_handlers_disconnect_matched(imhtml, G_SIGNAL_MATCH_DATA, 0, 0,
2097 NULL, NULL, smiley);
2100 static void
2101 gtk_imhtml_disassociate_smiley(GtkIMHtmlSmiley *smiley)
2103 if (smiley->imhtml) {
2104 gtk_smiley_tree_remove(smiley->imhtml->default_smilies, smiley);
2105 g_hash_table_foreach(smiley->imhtml->smiley_data,
2106 gtk_imhtml_disassociate_smiley_foreach, smiley);
2107 g_signal_handlers_disconnect_matched(smiley->imhtml, G_SIGNAL_MATCH_DATA,
2108 0, 0, NULL, NULL, smiley);
2109 smiley->imhtml = NULL;
2113 void
2114 gtk_imhtml_associate_smiley (GtkIMHtml *imhtml,
2115 const gchar *sml,
2116 GtkIMHtmlSmiley *smiley)
2118 GtkSmileyTree *tree;
2119 g_return_if_fail (imhtml != NULL);
2120 g_return_if_fail (GTK_IS_IMHTML (imhtml));
2122 if (sml == NULL)
2123 tree = imhtml->default_smilies;
2124 else if (!(tree = g_hash_table_lookup(imhtml->smiley_data, sml))) {
2125 tree = gtk_smiley_tree_new();
2126 g_hash_table_insert(imhtml->smiley_data, g_strdup(sml), tree);
2129 /* need to disconnect old imhtml, if there is one */
2130 if (smiley->imhtml) {
2131 g_signal_handlers_disconnect_matched(smiley->imhtml, G_SIGNAL_MATCH_DATA,
2132 0, 0, NULL, NULL, smiley);
2135 smiley->imhtml = imhtml;
2137 gtk_smiley_tree_insert (tree, smiley);
2139 /* connect destroy signal for the imhtml */
2140 g_signal_connect(imhtml, "destroy", G_CALLBACK(gtk_imhtml_disconnect_smiley),
2141 smiley);
2144 static gboolean
2145 gtk_imhtml_is_smiley (GtkIMHtml *imhtml,
2146 GSList *fonts,
2147 const gchar *text,
2148 gint *len)
2150 GtkSmileyTree *tree;
2151 GtkIMHtmlFontDetail *font;
2152 char *sml = NULL;
2154 if (fonts) {
2155 font = fonts->data;
2156 sml = font->sml;
2159 if (!sml)
2160 sml = imhtml->protocol_name;
2162 if (!sml || !(tree = g_hash_table_lookup(imhtml->smiley_data, sml)))
2163 tree = imhtml->default_smilies;
2165 if (tree == NULL)
2166 return FALSE;
2168 *len = gtk_smiley_tree_lookup (tree, text);
2169 return (*len > 0);
2172 static GtkIMHtmlSmiley *gtk_imhtml_smiley_get_from_tree(GtkSmileyTree *t, const gchar *text)
2174 const gchar *x = text;
2175 gchar *pos;
2177 if (t == NULL)
2178 return NULL;
2180 while (*x) {
2181 if (!t->values)
2182 return NULL;
2184 pos = strchr(t->values->str, *x);
2185 if (!pos)
2186 return NULL;
2188 t = t->children[GPOINTER_TO_INT(pos) - GPOINTER_TO_INT(t->values->str)];
2189 x++;
2192 return t->image;
2195 GtkIMHtmlSmiley *
2196 gtk_imhtml_smiley_get(GtkIMHtml *imhtml, const gchar *sml, const gchar *text)
2198 GtkIMHtmlSmiley *ret;
2200 /* Look for custom smileys first */
2201 if (sml != NULL) {
2202 ret = gtk_imhtml_smiley_get_from_tree(g_hash_table_lookup(imhtml->smiley_data, sml), text);
2203 if (ret != NULL)
2204 return ret;
2207 /* Fall back to check for default smileys */
2208 return gtk_imhtml_smiley_get_from_tree(imhtml->default_smilies, text);
2211 static GdkPixbufAnimation *
2212 gtk_smiley_get_image(GtkIMHtmlSmiley *smiley)
2214 if (!smiley->icon) {
2215 if (smiley->file) {
2216 smiley->icon = gdk_pixbuf_animation_new_from_file(smiley->file, NULL);
2217 } else if (smiley->loader) {
2218 smiley->icon = gdk_pixbuf_loader_get_animation(smiley->loader);
2219 if (smiley->icon)
2220 g_object_ref(G_OBJECT(smiley->icon));
2224 return smiley->icon;
2227 #define VALID_TAG(x) do { \
2228 if (!g_ascii_strncasecmp (string, x ">", strlen (x ">"))) { \
2229 if (tag) *tag = g_strndup (string, strlen (x)); \
2230 if (len) *len = strlen (x) + 1; \
2231 return TRUE; \
2233 if (type) (*type)++; \
2234 } while (0)
2236 #define VALID_OPT_TAG(x) do { \
2237 if (!g_ascii_strncasecmp (string, x " ", strlen (x " "))) { \
2238 const gchar *c = string + strlen (x " "); \
2239 gchar e = '"'; \
2240 gboolean quote = FALSE; \
2241 while (*c) { \
2242 if (*c == '"' || *c == '\'') { \
2243 if (quote && (*c == e)) \
2244 quote = !quote; \
2245 else if (!quote) { \
2246 quote = !quote; \
2247 e = *c; \
2249 } else if (!quote && (*c == '>')) \
2250 break; \
2251 c++; \
2253 if (*c) { \
2254 if (tag) *tag = g_strndup (string, c - string); \
2255 if (len) *len = c - string + 1; \
2256 return TRUE; \
2259 if (type) (*type)++; \
2260 } while (0)
2263 static gboolean
2264 gtk_imhtml_is_tag (const gchar *string,
2265 gchar **tag,
2266 gint *len,
2267 gint *type)
2269 char *close;
2270 if (type)
2271 *type = 1;
2273 if (!(close = strchr (string, '>')))
2274 return FALSE;
2276 VALID_TAG ("B");
2277 VALID_TAG ("BOLD");
2278 VALID_TAG ("/B");
2279 VALID_TAG ("/BOLD");
2280 VALID_TAG ("I");
2281 VALID_TAG ("ITALIC");
2282 VALID_TAG ("/I");
2283 VALID_TAG ("/ITALIC");
2284 VALID_TAG ("U");
2285 VALID_TAG ("UNDERLINE");
2286 VALID_TAG ("/U");
2287 VALID_TAG ("/UNDERLINE");
2288 VALID_TAG ("S");
2289 VALID_TAG ("STRIKE");
2290 VALID_TAG ("/S");
2291 VALID_TAG ("/STRIKE");
2292 VALID_TAG ("SUB");
2293 VALID_TAG ("/SUB");
2294 VALID_TAG ("SUP");
2295 VALID_TAG ("/SUP");
2296 VALID_TAG ("PRE");
2297 VALID_TAG ("/PRE");
2298 VALID_TAG ("TITLE");
2299 VALID_TAG ("/TITLE");
2300 VALID_TAG ("BR");
2301 VALID_TAG ("HR");
2302 VALID_TAG ("/FONT");
2303 VALID_TAG ("/A");
2304 VALID_TAG ("P");
2305 VALID_TAG ("/P");
2306 VALID_TAG ("H3");
2307 VALID_TAG ("/H3");
2308 VALID_TAG ("HTML");
2309 VALID_TAG ("/HTML");
2310 VALID_TAG ("BODY");
2311 VALID_TAG ("/BODY");
2312 VALID_TAG ("FONT");
2313 VALID_TAG ("HEAD");
2314 VALID_TAG ("/HEAD");
2315 VALID_TAG ("BINARY");
2316 VALID_TAG ("/BINARY");
2318 VALID_OPT_TAG ("HR");
2319 VALID_OPT_TAG ("FONT");
2320 VALID_OPT_TAG ("BODY");
2321 VALID_OPT_TAG ("A");
2322 VALID_OPT_TAG ("IMG");
2323 VALID_OPT_TAG ("P");
2324 VALID_OPT_TAG ("H3");
2325 VALID_OPT_TAG ("HTML");
2327 VALID_TAG ("CITE");
2328 VALID_TAG ("/CITE");
2329 VALID_TAG ("EM");
2330 VALID_TAG ("/EM");
2331 VALID_TAG ("STRONG");
2332 VALID_TAG ("/STRONG");
2334 VALID_OPT_TAG ("SPAN");
2335 VALID_TAG ("/SPAN");
2336 VALID_TAG ("BR/"); /* hack until gtkimhtml handles things better */
2337 VALID_TAG ("IMG");
2338 VALID_TAG("SPAN");
2339 VALID_OPT_TAG("BR");
2341 if (!g_ascii_strncasecmp(string, "!--", strlen ("!--"))) {
2342 gchar *e = strstr (string + strlen("!--"), "-->");
2343 if (e) {
2344 if (len)
2345 *len = e - string + strlen ("-->");
2346 if (tag)
2347 *tag = g_strndup (string + strlen ("!--"), *len - strlen ("!---->"));
2348 return TRUE;
2352 if (type)
2353 *type = -1;
2354 if (len)
2355 *len = close - string + 1;
2356 if (tag)
2357 *tag = g_strndup(string, *len - 1);
2358 return TRUE;
2361 static gchar*
2362 gtk_imhtml_get_html_opt (gchar *tag,
2363 const gchar *opt)
2365 gchar *t = tag;
2366 gchar *e, *a;
2367 gchar *val;
2368 gint len;
2369 const gchar *c;
2370 GString *ret;
2372 while (g_ascii_strncasecmp (t, opt, strlen (opt))) {
2373 gboolean quote = FALSE;
2374 if (*t == '\0') break;
2375 while (*t && !((*t == ' ') && !quote)) {
2376 if (*t == '\"')
2377 quote = ! quote;
2378 t++;
2380 while (*t && (*t == ' ')) t++;
2383 if (!g_ascii_strncasecmp (t, opt, strlen (opt))) {
2384 t += strlen (opt);
2385 } else {
2386 return NULL;
2389 if ((*t == '\"') || (*t == '\'')) {
2390 e = a = ++t;
2391 while (*e && (*e != *(t - 1))) e++;
2392 if (*e == '\0') {
2393 return NULL;
2394 } else
2395 val = g_strndup(a, e - a);
2396 } else {
2397 e = a = t;
2398 while (*e && !isspace ((gint) *e)) e++;
2399 val = g_strndup(a, e - a);
2402 ret = g_string_new("");
2403 e = val;
2404 while(*e) {
2405 if((c = purple_markup_unescape_entity(e, &len))) {
2406 ret = g_string_append(ret, c);
2407 e += len;
2408 } else {
2409 gunichar uni = g_utf8_get_char(e);
2410 ret = g_string_append_unichar(ret, uni);
2411 e = g_utf8_next_char(e);
2415 g_free(val);
2417 return g_string_free(ret, FALSE);
2420 /* returns if the beginning of the text is a protocol. If it is the protocol, returns the length so
2421 the caller knows how long the protocol string is. */
2422 static int gtk_imhtml_is_protocol(const char *text)
2424 GtkIMHtmlProtocol *proto = imhtml_find_protocol(text, FALSE);
2425 return proto ? proto->length : 0;
2428 static gboolean smooth_scroll_cb(gpointer data);
2431 <KingAnt> marv: The two IM image functions in oscar are purple_odc_send_im and purple_odc_incoming
2434 [19:58] <Robot101> marv: images go into the imgstore, a refcounted... well.. hash. :)
2435 [19:59] <KingAnt> marv: I think the image tag used by the core is something like <img id="#"/>
2436 [19:59] Ro0tSiEgE robert42 RobFlynn Robot101 ross22 roz
2437 [20:00] <KingAnt> marv: Where the ID is the what is returned when you add the image to the imgstore using purple_imgstore_add
2438 [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?
2439 [20:00] <KingAnt> marv: Right
2440 [20:00] <marv> alright
2442 Here's my plan with IMImages. make gtk_imhtml_[append|insert]_text_with_images instead just
2443 gtkimhtml_[append|insert]_text (hrm maybe it should be called html instead of text), add a
2444 function for purple to register for look up images, i.e. gtk_imhtml_set_get_img_fnc, so that
2445 images can be looked up like that, instead of passing a GSList of them.
2448 void gtk_imhtml_append_text_with_images (GtkIMHtml *imhtml,
2449 const gchar *text,
2450 GtkIMHtmlOptions options,
2451 GSList *unused)
2453 GtkTextIter iter, ins, sel;
2454 int ins_offset = 0, sel_offset = 0;
2455 gboolean fixins = FALSE, fixsel = FALSE;
2457 g_return_if_fail (imhtml != NULL);
2458 g_return_if_fail (GTK_IS_IMHTML (imhtml));
2459 g_return_if_fail (text != NULL);
2462 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &iter);
2463 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &ins, gtk_text_buffer_get_insert(imhtml->text_buffer));
2464 if (gtk_text_iter_equal(&iter, &ins) && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, NULL, NULL)) {
2465 fixins = TRUE;
2466 ins_offset = gtk_text_iter_get_offset(&ins);
2469 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &sel, gtk_text_buffer_get_selection_bound(imhtml->text_buffer));
2470 if (gtk_text_iter_equal(&iter, &sel) && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, NULL, NULL)) {
2471 fixsel = TRUE;
2472 sel_offset = gtk_text_iter_get_offset(&sel);
2475 if (!(options & GTK_IMHTML_NO_SCROLL)) {
2476 GdkRectangle rect;
2477 int y, height;
2479 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect);
2480 gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(imhtml), &iter, &y, &height);
2482 if (((y + height) - (rect.y + rect.height)) > height &&
2483 gtk_text_buffer_get_char_count(imhtml->text_buffer)) {
2484 /* If we are in the middle of smooth-scrolling, then take a scroll step.
2485 * If we are not in the middle of smooth-scrolling, that means we were
2486 * not looking at the end of the buffer before the new text was added,
2487 * so do not scroll. */
2488 if (imhtml->scroll_time)
2489 smooth_scroll_cb(imhtml);
2490 else
2491 options |= GTK_IMHTML_NO_SCROLL;
2495 gtk_imhtml_insert_html_at_iter(imhtml, text, options, &iter);
2497 if (fixins) {
2498 gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &ins, ins_offset);
2499 gtk_text_buffer_move_mark(imhtml->text_buffer, gtk_text_buffer_get_insert(imhtml->text_buffer), &ins);
2502 if (fixsel) {
2503 gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &sel, sel_offset);
2504 gtk_text_buffer_move_mark(imhtml->text_buffer, gtk_text_buffer_get_selection_bound(imhtml->text_buffer), &sel);
2507 if (!(options & GTK_IMHTML_NO_SCROLL)) {
2508 gtk_imhtml_scroll_to_end(imhtml, (options & GTK_IMHTML_USE_SMOOTHSCROLLING));
2512 #define MAX_SCROLL_TIME 0.4 /* seconds */
2513 #define SCROLL_DELAY 33 /* milliseconds */
2516 * Smoothly scroll a GtkIMHtml.
2518 * @return TRUE if the window needs to be scrolled further, FALSE if we're at the bottom.
2520 static gboolean smooth_scroll_cb(gpointer data)
2522 GtkIMHtml *imhtml = data;
2523 GtkAdjustment *adj = GTK_TEXT_VIEW(imhtml)->vadjustment;
2524 gdouble max_val = adj->upper - adj->page_size;
2525 gdouble scroll_val = gtk_adjustment_get_value(adj) + ((max_val - gtk_adjustment_get_value(adj)) / 3);
2527 g_return_val_if_fail(imhtml->scroll_time != NULL, FALSE);
2529 if (g_timer_elapsed(imhtml->scroll_time, NULL) > MAX_SCROLL_TIME || scroll_val >= max_val) {
2530 /* time's up. jump to the end and kill the timer */
2531 gtk_adjustment_set_value(adj, max_val);
2532 g_timer_destroy(imhtml->scroll_time);
2533 imhtml->scroll_time = NULL;
2534 g_source_remove(imhtml->scroll_src);
2535 imhtml->scroll_src = 0;
2536 return FALSE;
2539 /* scroll by 1/3rd the remaining distance */
2540 gtk_adjustment_set_value(adj, scroll_val);
2541 return TRUE;
2544 static gboolean scroll_idle_cb(gpointer data)
2546 GtkIMHtml *imhtml = data;
2547 GtkAdjustment *adj = GTK_TEXT_VIEW(imhtml)->vadjustment;
2548 if(adj) {
2549 gtk_adjustment_set_value(adj, adj->upper - adj->page_size);
2551 imhtml->scroll_src = 0;
2552 return FALSE;
2555 void gtk_imhtml_scroll_to_end(GtkIMHtml *imhtml, gboolean smooth)
2557 if (imhtml->scroll_time)
2558 g_timer_destroy(imhtml->scroll_time);
2559 if (imhtml->scroll_src)
2560 g_source_remove(imhtml->scroll_src);
2561 if(smooth) {
2562 imhtml->scroll_time = g_timer_new();
2563 imhtml->scroll_src = g_timeout_add_full(G_PRIORITY_LOW, SCROLL_DELAY, smooth_scroll_cb, imhtml, NULL);
2564 } else {
2565 imhtml->scroll_time = NULL;
2566 imhtml->scroll_src = g_idle_add_full(G_PRIORITY_LOW, scroll_idle_cb, imhtml, NULL);
2570 /* CSS colors are either rgb (x,y,z) or #hex
2571 * we need to convert to hex if it is RGB */
2572 static gchar*
2573 parse_css_color(gchar *in_color)
2575 char *tmp = in_color;
2577 if (*tmp == 'r' && *(++tmp) == 'g' && *(++tmp) == 'b' && *(++tmp)) {
2578 int rgbval[] = {0, 0, 0};
2579 int count = 0;
2580 const char *v_start;
2582 while (*tmp && g_ascii_isspace(*tmp))
2583 tmp++;
2584 if (*tmp != '(') {
2585 /* We don't support rgba() */
2586 purple_debug_warning("gtkimhtml", "Invalid rgb CSS color in '%s'!\n", in_color);
2587 return in_color;
2589 tmp++;
2591 while (count < 3) {
2592 /* Skip any leading spaces */
2593 while (*tmp && g_ascii_isspace(*tmp))
2594 tmp++;
2596 /* Find the subsequent contiguous digits */
2597 v_start = tmp;
2598 if (*v_start == '-')
2599 tmp++;
2600 while (*tmp && g_ascii_isdigit(*tmp))
2601 tmp++;
2603 if (tmp != v_start) {
2604 char prev = *tmp;
2605 *tmp = '\0';
2606 rgbval[count] = atoi(v_start);
2607 *tmp = prev;
2609 /* deal with % */
2610 while (*tmp && g_ascii_isspace(*tmp))
2611 tmp++;
2612 if (*tmp == '%') {
2613 rgbval[count] = (rgbval[count] / 100.0) * 255;
2614 tmp++;
2616 } else {
2617 purple_debug_warning("gtkimhtml", "Invalid rgb CSS color in '%s'!\n", in_color);
2618 return in_color;
2621 if (rgbval[count] > 255) {
2622 rgbval[count] = 255;
2623 } else if (rgbval[count] < 0) {
2624 rgbval[count] = 0;
2627 while (*tmp && g_ascii_isspace(*tmp))
2628 tmp++;
2629 if (*tmp == ',')
2630 tmp++;
2632 count++;
2635 g_free(in_color);
2636 return g_strdup_printf("#%02X%02X%02X", rgbval[0], rgbval[1], rgbval[2]);
2639 return in_color;
2642 void gtk_imhtml_insert_html_at_iter(GtkIMHtml *imhtml,
2643 const gchar *text,
2644 GtkIMHtmlOptions options,
2645 GtkTextIter *iter)
2647 GdkRectangle rect;
2648 gint pos = 0;
2649 gchar *ws;
2650 gchar *tag;
2651 gchar *bg = NULL;
2652 gint len;
2653 gint tlen, smilelen, wpos=0;
2654 gint type;
2655 const gchar *c;
2656 const gchar *amp;
2657 gint len_protocol;
2659 guint bold = 0,
2660 italics = 0,
2661 underline = 0,
2662 strike = 0,
2663 sub = 0,
2664 sup = 0,
2665 title = 0,
2666 pre = 0;
2668 gboolean br = FALSE;
2669 gboolean align_right = FALSE;
2670 gboolean rtl_direction = FALSE;
2671 gint align_line = 0;
2673 GSList *fonts = NULL;
2674 GObject *object;
2675 GtkIMHtmlScalable *scalable = NULL;
2677 g_return_if_fail (imhtml != NULL);
2678 g_return_if_fail (GTK_IS_IMHTML (imhtml));
2679 g_return_if_fail (text != NULL);
2680 c = text;
2681 len = strlen(text);
2682 ws = g_malloc(len + 1);
2683 ws[0] = '\0';
2685 g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(0));
2687 gtk_text_buffer_begin_user_action(imhtml->text_buffer);
2688 while (pos < len) {
2689 if (*c == '<' && gtk_imhtml_is_tag (c + 1, &tag, &tlen, &type)) {
2690 c++;
2691 pos++;
2692 ws[wpos] = '\0';
2693 br = FALSE;
2694 switch (type)
2696 case 1: /* B */
2697 case 2: /* BOLD */
2698 case 54: /* STRONG */
2699 if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2700 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2702 if ((bold == 0) && (imhtml->format_functions & GTK_IMHTML_BOLD))
2703 gtk_imhtml_toggle_bold(imhtml);
2704 bold++;
2705 ws[0] = '\0'; wpos = 0;
2707 break;
2708 case 3: /* /B */
2709 case 4: /* /BOLD */
2710 case 55: /* /STRONG */
2711 if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2712 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2713 ws[0] = '\0'; wpos = 0;
2715 if (bold) {
2716 bold--;
2717 if ((bold == 0) && (imhtml->format_functions & GTK_IMHTML_BOLD) && !imhtml->wbfo)
2718 gtk_imhtml_toggle_bold(imhtml);
2721 break;
2722 case 5: /* I */
2723 case 6: /* ITALIC */
2724 case 52: /* EM */
2725 if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2726 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2727 ws[0] = '\0'; wpos = 0;
2728 if ((italics == 0) && (imhtml->format_functions & GTK_IMHTML_ITALIC))
2729 gtk_imhtml_toggle_italic(imhtml);
2730 italics++;
2732 break;
2733 case 7: /* /I */
2734 case 8: /* /ITALIC */
2735 case 53: /* /EM */
2736 if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2737 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2738 ws[0] = '\0'; wpos = 0;
2739 if (italics) {
2740 italics--;
2741 if ((italics == 0) && (imhtml->format_functions & GTK_IMHTML_ITALIC) && !imhtml->wbfo)
2742 gtk_imhtml_toggle_italic(imhtml);
2745 break;
2746 case 9: /* U */
2747 case 10: /* UNDERLINE */
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 ((underline == 0) && (imhtml->format_functions & GTK_IMHTML_UNDERLINE))
2752 gtk_imhtml_toggle_underline(imhtml);
2753 underline++;
2755 break;
2756 case 11: /* /U */
2757 case 12: /* /UNDERLINE */
2758 if (!(options & GTK_IMHTML_NO_FORMATTING)) {
2759 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2760 ws[0] = '\0'; wpos = 0;
2761 if (underline) {
2762 underline--;
2763 if ((underline == 0) && (imhtml->format_functions & GTK_IMHTML_UNDERLINE) && !imhtml->wbfo)
2764 gtk_imhtml_toggle_underline(imhtml);
2767 break;
2768 case 13: /* S */
2769 case 14: /* STRIKE */
2770 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2771 ws[0] = '\0'; wpos = 0;
2772 if ((strike == 0) && (imhtml->format_functions & GTK_IMHTML_STRIKE))
2773 gtk_imhtml_toggle_strike(imhtml);
2774 strike++;
2775 break;
2776 case 15: /* /S */
2777 case 16: /* /STRIKE */
2778 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2779 ws[0] = '\0'; wpos = 0;
2780 if (strike)
2781 strike--;
2782 if ((strike == 0) && (imhtml->format_functions & GTK_IMHTML_STRIKE) && !imhtml->wbfo)
2783 gtk_imhtml_toggle_strike(imhtml);
2784 break;
2785 case 17: /* SUB */
2786 /* FIXME: reimpliment this */
2787 sub++;
2788 break;
2789 case 18: /* /SUB */
2790 /* FIXME: reimpliment this */
2791 if (sub)
2792 sub--;
2793 break;
2794 case 19: /* SUP */
2795 /* FIXME: reimplement this */
2796 sup++;
2797 break;
2798 case 20: /* /SUP */
2799 /* FIXME: reimplement this */
2800 if (sup)
2801 sup--;
2802 break;
2803 case 21: /* PRE */
2804 /* FIXME: reimplement this */
2805 pre++;
2806 break;
2807 case 22: /* /PRE */
2808 /* FIXME: reimplement this */
2809 if (pre)
2810 pre--;
2811 break;
2812 case 23: /* TITLE */
2813 /* FIXME: what was this supposed to do anyway? */
2814 title++;
2815 break;
2816 case 24: /* /TITLE */
2817 /* FIXME: make this undo whatever 23 was supposed to do */
2818 if (title) {
2819 if (options & GTK_IMHTML_NO_TITLE) {
2820 wpos = 0;
2821 ws [wpos] = '\0';
2823 title--;
2825 break;
2826 case 25: /* BR */
2827 case 58: /* BR/ */
2828 case 61: /* BR (opt) */
2829 ws[wpos] = '\n';
2830 wpos++;
2831 br = TRUE;
2832 break;
2833 case 26: /* HR */
2834 case 42: /* HR (opt) */
2836 int minus;
2837 struct scalable_data *sd = g_new(struct scalable_data, 1);
2839 ws[wpos++] = '\n';
2840 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2842 sd->scalable = scalable = gtk_imhtml_hr_new();
2843 sd->mark = gtk_text_buffer_create_mark(imhtml->text_buffer, NULL, iter, TRUE);
2844 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect);
2845 scalable->add_to(scalable, imhtml, iter);
2846 minus = gtk_text_view_get_left_margin(GTK_TEXT_VIEW(imhtml)) +
2847 gtk_text_view_get_right_margin(GTK_TEXT_VIEW(imhtml));
2848 scalable->scale(scalable, rect.width - minus, rect.height);
2849 imhtml->scalables = g_list_append(imhtml->scalables, sd);
2850 ws[0] = '\0'; wpos = 0;
2851 ws[wpos++] = '\n';
2853 break;
2855 case 27: /* /FONT */
2856 if (fonts && !imhtml->wbfo) {
2857 GtkIMHtmlFontDetail *font = fonts->data;
2858 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2859 ws[0] = '\0'; wpos = 0;
2860 /* NEW_BIT (NEW_TEXT_BIT); */
2862 if (font->face && (imhtml->format_functions & GTK_IMHTML_FACE)) {
2863 gtk_imhtml_toggle_fontface(imhtml, NULL);
2865 g_free (font->face);
2866 if (font->fore && (imhtml->format_functions & GTK_IMHTML_FORECOLOR)) {
2867 gtk_imhtml_toggle_forecolor(imhtml, NULL);
2869 g_free (font->fore);
2870 if (font->back && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) {
2871 gtk_imhtml_toggle_backcolor(imhtml, NULL);
2873 g_free (font->back);
2874 g_free (font->sml);
2876 if ((font->size != 3) && (imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK)))
2877 gtk_imhtml_font_set_size(imhtml, 3);
2879 fonts = g_slist_remove (fonts, font);
2880 g_free(font);
2882 if (fonts) {
2883 GtkIMHtmlFontDetail *font = fonts->data;
2885 if (font->face && (imhtml->format_functions & GTK_IMHTML_FACE))
2886 gtk_imhtml_toggle_fontface(imhtml, font->face);
2887 if (font->fore && (imhtml->format_functions & GTK_IMHTML_FORECOLOR))
2888 gtk_imhtml_toggle_forecolor(imhtml, font->fore);
2889 if (font->back && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR))
2890 gtk_imhtml_toggle_backcolor(imhtml, font->back);
2891 if ((font->size != 3) && (imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK)))
2892 gtk_imhtml_font_set_size(imhtml, font->size);
2895 break;
2896 case 28: /* /A */
2897 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2898 gtk_imhtml_toggle_link(imhtml, NULL);
2899 ws[0] = '\0'; wpos = 0;
2900 break;
2902 case 29: /* P */
2903 case 30: /* /P */
2904 case 31: /* H3 */
2905 case 32: /* /H3 */
2906 case 33: /* HTML */
2907 case 34: /* /HTML */
2908 case 35: /* BODY */
2909 break;
2910 case 36: /* /BODY */
2911 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2912 ws[0] = '\0'; wpos = 0;
2913 gtk_imhtml_toggle_background(imhtml, NULL);
2914 break;
2915 case 37: /* FONT */
2916 case 38: /* HEAD */
2917 case 39: /* /HEAD */
2918 case 40: /* BINARY */
2919 case 41: /* /BINARY */
2920 break;
2921 case 43: /* FONT (opt) */
2923 gchar *color, *back, *face, *size, *sml;
2924 GtkIMHtmlFontDetail *font, *oldfont = NULL;
2925 color = gtk_imhtml_get_html_opt (tag, "COLOR=");
2926 back = gtk_imhtml_get_html_opt (tag, "BACK=");
2927 face = gtk_imhtml_get_html_opt (tag, "FACE=");
2928 size = gtk_imhtml_get_html_opt (tag, "SIZE=");
2929 sml = gtk_imhtml_get_html_opt (tag, "SML=");
2930 if (!(color || back || face || size || sml))
2931 break;
2933 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2934 ws[0] = '\0'; wpos = 0;
2936 font = g_new0 (GtkIMHtmlFontDetail, 1);
2937 if (fonts)
2938 oldfont = fonts->data;
2940 if (color && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_FORECOLOR)) {
2941 font->fore = color;
2942 gtk_imhtml_toggle_forecolor(imhtml, font->fore);
2943 } else
2944 g_free(color);
2946 if (back && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) {
2947 font->back = back;
2948 gtk_imhtml_toggle_backcolor(imhtml, font->back);
2949 } else
2950 g_free(back);
2952 if (face && !(options & GTK_IMHTML_NO_FONTS) && (imhtml->format_functions & GTK_IMHTML_FACE)) {
2953 font->face = face;
2954 gtk_imhtml_toggle_fontface(imhtml, font->face);
2955 } else
2956 g_free(face);
2958 if (sml)
2959 font->sml = sml;
2960 else {
2961 g_free(sml);
2962 if (oldfont && oldfont->sml)
2963 font->sml = g_strdup(oldfont->sml);
2966 if (size && !(options & GTK_IMHTML_NO_SIZES) && (imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK))) {
2967 if (*size == '+') {
2968 sscanf (size + 1, "%hd", &font->size);
2969 font->size += 3;
2970 } else if (*size == '-') {
2971 sscanf (size + 1, "%hd", &font->size);
2972 font->size = MAX (0, 3 - font->size);
2973 } else if (isdigit (*size)) {
2974 sscanf (size, "%hd", &font->size);
2976 if (font->size > 100)
2977 font->size = 100;
2978 } else if (oldfont)
2979 font->size = oldfont->size;
2980 else
2981 font->size = 3;
2982 if ((imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK)) && (font->size != 3 || (oldfont && oldfont->size == 3)))
2983 gtk_imhtml_font_set_size(imhtml, font->size);
2984 g_free(size);
2985 fonts = g_slist_prepend (fonts, font);
2987 break;
2988 case 44: /* BODY (opt) */
2989 if (!(options & GTK_IMHTML_NO_COLOURS)) {
2990 char *bgcolor = gtk_imhtml_get_html_opt (tag, "BGCOLOR=");
2991 if (bgcolor && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) {
2992 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2993 ws[0] = '\0'; wpos = 0;
2994 /* NEW_BIT(NEW_TEXT_BIT); */
2995 g_free(bg);
2996 bg = bgcolor;
2997 gtk_imhtml_toggle_background(imhtml, bg);
2998 } else
2999 g_free(bgcolor);
3001 break;
3002 case 45: /* A (opt) */
3004 gchar *href = gtk_imhtml_get_html_opt (tag, "HREF=");
3005 if (href && (imhtml->format_functions & GTK_IMHTML_LINK)) {
3006 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3007 ws[0] = '\0'; wpos = 0;
3008 gtk_imhtml_toggle_link(imhtml, href);
3010 g_free(href);
3012 break;
3013 case 46: /* IMG (opt) */
3014 case 59: /* IMG */
3016 char *id;
3018 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3019 ws[0] = '\0'; wpos = 0;
3021 if (!(imhtml->format_functions & GTK_IMHTML_IMAGE))
3022 break;
3024 id = gtk_imhtml_get_html_opt(tag, "ID=");
3025 if (id) {
3026 gtk_imhtml_insert_image_at_iter(imhtml, atoi(id), iter);
3027 g_free(id);
3028 } else {
3029 char *src, *alt;
3030 src = gtk_imhtml_get_html_opt(tag, "SRC=");
3031 alt = gtk_imhtml_get_html_opt(tag, "ALT=");
3032 if (src) {
3033 gtk_imhtml_toggle_link(imhtml, src);
3034 gtk_text_buffer_insert(imhtml->text_buffer, iter, alt ? alt : src, -1);
3035 gtk_imhtml_toggle_link(imhtml, NULL);
3037 g_free (src);
3038 g_free (alt);
3040 break;
3042 case 47: /* P (opt) */
3043 case 48: /* H3 (opt) */
3044 case 49: /* HTML (opt) */
3045 case 50: /* CITE */
3046 case 51: /* /CITE */
3047 case 56: /* SPAN (opt) */
3048 /* Inline CSS Support - Douglas Thrift
3050 * color
3051 * background
3052 * font-family
3053 * font-size
3054 * text-decoration: underline
3055 * font-weight: bold
3056 * direction: rtl
3057 * text-align: right
3059 * TODO:
3060 * background-color
3061 * font-style
3064 gchar *style, *color, *background, *family, *size, *direction, *alignment;
3065 gchar *textdec, *weight;
3066 GtkIMHtmlFontDetail *font, *oldfont = NULL;
3067 style = gtk_imhtml_get_html_opt (tag, "style=");
3069 if (!style) break;
3071 color = purple_markup_get_css_property (style, "color");
3072 background = purple_markup_get_css_property (style, "background");
3073 family = purple_markup_get_css_property (style, "font-family");
3074 size = purple_markup_get_css_property (style, "font-size");
3075 textdec = purple_markup_get_css_property (style, "text-decoration");
3076 weight = purple_markup_get_css_property (style, "font-weight");
3077 direction = purple_markup_get_css_property (style, "direction");
3078 alignment = purple_markup_get_css_property (style, "text-align");
3081 if (!(color || family || size || background || textdec || weight || direction || alignment)) {
3082 g_free(style);
3083 break;
3087 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3088 ws[0] = '\0'; wpos = 0;
3089 /* NEW_BIT (NEW_TEXT_BIT); */
3091 /* Bi-Directional text support */
3092 if (direction && (!g_ascii_strncasecmp(direction, "RTL", 3))) {
3093 rtl_direction = TRUE;
3094 /* insert RLE character to set direction */
3095 ws[wpos++] = 0xE2;
3096 ws[wpos++] = 0x80;
3097 ws[wpos++] = 0xAB;
3098 ws[wpos] = '\0';
3099 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3100 ws[0] = '\0'; wpos = 0;
3102 g_free(direction);
3104 if (alignment && (!g_ascii_strncasecmp(alignment, "RIGHT", 5))) {
3105 align_right = TRUE;
3106 align_line = gtk_text_iter_get_line(iter);
3108 g_free(alignment);
3110 font = g_new0 (GtkIMHtmlFontDetail, 1);
3111 if (fonts)
3112 oldfont = fonts->data;
3114 if (color && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_FORECOLOR)) {
3115 font->fore = parse_css_color(color);
3116 gtk_imhtml_toggle_forecolor(imhtml, font->fore);
3117 } else {
3118 if (oldfont && oldfont->fore)
3119 font->fore = g_strdup(oldfont->fore);
3120 g_free(color);
3123 if (background && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) {
3124 font->back = parse_css_color(background);
3125 gtk_imhtml_toggle_backcolor(imhtml, font->back);
3126 } else {
3127 if (oldfont && oldfont->back)
3128 font->back = g_strdup(oldfont->back);
3129 g_free(background);
3132 if (family && !(options & GTK_IMHTML_NO_FONTS) && (imhtml->format_functions & GTK_IMHTML_FACE)) {
3133 font->face = family;
3134 gtk_imhtml_toggle_fontface(imhtml, font->face);
3135 } else {
3136 if (oldfont && oldfont->face)
3137 font->face = g_strdup(oldfont->face);
3138 g_free(family);
3140 if (font->face && (atoi(font->face) > 100)) {
3141 /* WTF is this? */
3142 /* Maybe it sets a max size on the font face? I seem to
3143 * remember bad things happening if the font size was
3144 * 2 billion */
3145 g_free(font->face);
3146 font->face = g_strdup("100");
3149 if (oldfont && oldfont->sml)
3150 font->sml = g_strdup(oldfont->sml);
3152 if (size && !(options & GTK_IMHTML_NO_SIZES) && (imhtml->format_functions & (GTK_IMHTML_SHRINK|GTK_IMHTML_GROW))) {
3153 if (g_ascii_strcasecmp(size, "xx-small") == 0)
3154 font->size = 1;
3155 else if (g_ascii_strcasecmp(size, "smaller") == 0
3156 || g_ascii_strcasecmp(size, "x-small") == 0)
3157 font->size = 2;
3158 else if (g_ascii_strcasecmp(size, "medium") == 0)
3159 font->size = 3;
3160 else if (g_ascii_strcasecmp(size, "large") == 0
3161 || g_ascii_strcasecmp(size, "larger") == 0)
3162 font->size = 4;
3163 else if (g_ascii_strcasecmp(size, "x-large") == 0)
3164 font->size = 5;
3165 else if (g_ascii_strcasecmp(size, "xx-large") == 0)
3166 font->size = 6;
3169 * TODO: Handle other values, like percentages, or
3170 * lengths specified as em, ex, px, in, cm, mm, pt
3171 * or pc. Or even better, use an actual HTML
3172 * renderer like webkit.
3174 if (font->size > 0)
3175 gtk_imhtml_font_set_size(imhtml, font->size);
3177 else if (oldfont)
3179 font->size = oldfont->size;
3182 if (oldfont)
3184 font->underline = oldfont->underline;
3186 if (textdec && font->underline != 1
3187 && g_ascii_strcasecmp(textdec, "underline") == 0
3188 && (imhtml->format_functions & GTK_IMHTML_UNDERLINE)
3189 && !(options & GTK_IMHTML_NO_FORMATTING))
3191 gtk_imhtml_toggle_underline(imhtml);
3192 font->underline = 1;
3194 g_free(textdec);
3196 if (oldfont)
3198 font->bold = oldfont->bold;
3200 if (weight)
3202 if(!g_ascii_strcasecmp(weight, "normal")) {
3203 font->bold = 0;
3204 } else if(!g_ascii_strcasecmp(weight, "bold")) {
3205 font->bold = 1;
3206 } else if(!g_ascii_strcasecmp(weight, "bolder")) {
3207 font->bold++;
3208 } else if(!g_ascii_strcasecmp(weight, "lighter")) {
3209 if(font->bold > 0)
3210 font->bold--;
3211 } else {
3212 int num = atoi(weight);
3213 if(num >= 700)
3214 font->bold = 1;
3215 else
3216 font->bold = 0;
3218 if (((font->bold && oldfont && !oldfont->bold) || (oldfont && oldfont->bold && !font->bold) || (font->bold && !oldfont)) && !(options & GTK_IMHTML_NO_FORMATTING))
3220 gtk_imhtml_toggle_bold(imhtml);
3222 g_free(weight);
3225 g_free(style);
3226 g_free(size);
3227 fonts = g_slist_prepend (fonts, font);
3229 break;
3230 case 57: /* /SPAN */
3231 /* Inline CSS Support - Douglas Thrift */
3232 if (fonts && !imhtml->wbfo) {
3233 GtkIMHtmlFontDetail *oldfont = NULL;
3234 GtkIMHtmlFontDetail *font = fonts->data;
3235 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3236 ws[0] = '\0'; wpos = 0;
3237 /* NEW_BIT (NEW_TEXT_BIT); */
3238 fonts = g_slist_remove (fonts, font);
3239 if (fonts)
3240 oldfont = fonts->data;
3242 if (!oldfont) {
3243 gtk_imhtml_font_set_size(imhtml, 3);
3244 if (font->underline && !(options & GTK_IMHTML_NO_FORMATTING))
3245 gtk_imhtml_toggle_underline(imhtml);
3246 if (font->bold && !(options & GTK_IMHTML_NO_FORMATTING))
3247 gtk_imhtml_toggle_bold(imhtml);
3248 if (!(options & GTK_IMHTML_NO_FONTS))
3249 gtk_imhtml_toggle_fontface(imhtml, NULL);
3250 if (!(options & GTK_IMHTML_NO_COLOURS))
3251 gtk_imhtml_toggle_forecolor(imhtml, NULL);
3252 if (!(options & GTK_IMHTML_NO_COLOURS))
3253 gtk_imhtml_toggle_backcolor(imhtml, NULL);
3255 else
3258 if ((font->size != oldfont->size) && !(options & GTK_IMHTML_NO_SIZES))
3259 gtk_imhtml_font_set_size(imhtml, oldfont->size);
3261 if ((font->underline != oldfont->underline) && !(options & GTK_IMHTML_NO_FORMATTING))
3262 gtk_imhtml_toggle_underline(imhtml);
3264 if (((font->bold && !oldfont->bold) || (oldfont->bold && !font->bold)) && !(options & GTK_IMHTML_NO_FORMATTING))
3265 gtk_imhtml_toggle_bold(imhtml);
3267 if (font->face && (!oldfont->face || strcmp(font->face, oldfont->face) != 0) && !(options & GTK_IMHTML_NO_FONTS))
3268 gtk_imhtml_toggle_fontface(imhtml, oldfont->face);
3270 if (font->fore && (!oldfont->fore || strcmp(font->fore, oldfont->fore) != 0) && !(options & GTK_IMHTML_NO_COLOURS))
3271 gtk_imhtml_toggle_forecolor(imhtml, oldfont->fore);
3273 if (font->back && (!oldfont->back || strcmp(font->back, oldfont->back) != 0) && !(options & GTK_IMHTML_NO_COLOURS))
3274 gtk_imhtml_toggle_backcolor(imhtml, oldfont->back);
3277 g_free (font->face);
3278 g_free (font->fore);
3279 g_free (font->back);
3280 g_free (font->sml);
3282 g_free (font);
3284 break;
3285 case 60: /* SPAN */
3286 break;
3287 case 62: /* comment */
3288 /* NEW_BIT (NEW_TEXT_BIT); */
3289 ws[wpos] = '\0';
3291 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3293 #if FALSE && GTK_CHECK_VERSION(2,10,10)
3294 wpos = g_snprintf (ws, len, "%s", tag);
3295 gtk_text_buffer_insert_with_tags_by_name(imhtml->text_buffer, iter, ws, wpos, "comment", NULL);
3296 #else
3297 if (imhtml->show_comments && !(options & GTK_IMHTML_NO_COMMENTS)) {
3298 wpos = g_snprintf (ws, len, "%s", tag);
3299 gtk_text_buffer_insert_with_tags_by_name(imhtml->text_buffer, iter, ws, wpos, "comment", NULL);
3301 #endif
3302 ws[0] = '\0'; wpos = 0;
3304 /* NEW_BIT (NEW_COMMENT_BIT); */
3305 break;
3306 default:
3307 break;
3309 c += tlen;
3310 pos += tlen;
3311 g_free(tag); /* This was allocated back in VALID_TAG() */
3312 } else if (imhtml->edit.link == NULL &&
3313 !(options & GTK_IMHTML_NO_SMILEY) &&
3314 gtk_imhtml_is_smiley(imhtml, fonts, c, &smilelen)) {
3315 GtkIMHtmlFontDetail *fd;
3316 gchar *sml = NULL;
3318 br = FALSE;
3320 if (fonts) {
3321 fd = fonts->data;
3322 sml = fd->sml;
3324 if (!sml)
3325 sml = imhtml->protocol_name;
3327 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3328 wpos = g_snprintf (ws, smilelen + 1, "%s", c);
3330 gtk_imhtml_insert_smiley_at_iter(imhtml, sml, ws, iter);
3332 c += smilelen;
3333 pos += smilelen;
3334 wpos = 0;
3335 ws[0] = 0;
3336 } else if (*c == '&' && (amp = purple_markup_unescape_entity(c, &tlen))) {
3337 br = FALSE;
3338 while(*amp) {
3339 ws [wpos++] = *amp++;
3341 c += tlen;
3342 pos += tlen;
3343 } else if (*c == '\n') {
3344 if (!(options & GTK_IMHTML_NO_NEWLINE)) {
3345 ws[wpos] = '\n';
3346 wpos++;
3347 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3348 ws[0] = '\0'; wpos = 0;
3349 /* NEW_BIT (NEW_TEXT_BIT); */
3350 } else if (!br) { /* Don't insert a space immediately after an HTML break */
3351 /* A newline is defined by HTML as whitespace, which means we have to replace it with a word boundary.
3352 * word breaks vary depending on the language used, so the correct thing to do is to use Pango to determine
3353 * what language this is, determine the proper word boundary to use, and insert that. I'm just going to insert
3354 * a space instead. What are the non-English speakers going to do? Complain in a language I'll understand?
3355 * Bu-wahaha! */
3356 ws[wpos] = ' ';
3357 wpos++;
3358 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3359 ws[0] = '\0'; wpos = 0;
3361 c++;
3362 pos++;
3363 } else if ((pos == 0 || wpos == 0 || isspace(*(c - 1))) &&
3364 (len_protocol = gtk_imhtml_is_protocol(c)) > 0 &&
3365 c[len_protocol] && !isspace(c[len_protocol]) &&
3366 (c[len_protocol] != '<' || !gtk_imhtml_is_tag(c + 1, NULL, NULL, NULL))) {
3367 br = FALSE;
3368 if (wpos > 0) {
3369 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3370 ws[0] = '\0'; wpos = 0;
3372 while (len_protocol--) {
3373 /* Skip the next len_protocol characters, but
3374 * make sure they're copied into the ws array.
3376 ws [wpos++] = *c++;
3377 pos++;
3379 if (!imhtml->edit.link && (imhtml->format_functions & GTK_IMHTML_LINK)) {
3380 while (*c && !isspace((int)*c) &&
3381 (*c != '<' || !gtk_imhtml_is_tag(c + 1, NULL, NULL, NULL))) {
3382 if (*c == '&' && (amp = purple_markup_unescape_entity(c, &tlen))) {
3383 while (*amp)
3384 ws[wpos++] = *amp++;
3385 c += tlen;
3386 pos += tlen;
3387 } else {
3388 ws [wpos++] = *c++;
3389 pos++;
3392 ws[wpos] = '\0';
3393 gtk_imhtml_toggle_link(imhtml, ws);
3394 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3395 ws[0] = '\0'; wpos = 0;
3396 gtk_imhtml_toggle_link(imhtml, NULL);
3398 } else if (*c) {
3399 br = FALSE;
3400 ws [wpos++] = *c++;
3401 pos++;
3402 } else {
3403 break;
3406 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3407 ws[0] = '\0'; wpos = 0;
3409 /* NEW_BIT(NEW_TEXT_BIT); */
3411 if(align_right) {
3412 /* insert RLM+LRM at beginning of the line to set alignment */
3413 GtkTextIter line_iter;
3414 line_iter = *iter;
3415 gtk_text_iter_set_line(&line_iter, align_line);
3416 /* insert RLM character to set alignment */
3417 ws[wpos++] = 0xE2;
3418 ws[wpos++] = 0x80;
3419 ws[wpos++] = 0x8F;
3421 if (!rtl_direction)
3423 /* insert LRM character to set direction */
3424 /* (alignment=right and direction=LTR) */
3425 ws[wpos++] = 0xE2;
3426 ws[wpos++] = 0x80;
3427 ws[wpos++] = 0x8E;
3430 ws[wpos] = '\0';
3431 gtk_text_buffer_insert(imhtml->text_buffer, &line_iter, ws, wpos);
3432 gtk_text_buffer_get_end_iter(gtk_text_iter_get_buffer(&line_iter), iter);
3433 ws[0] = '\0'; wpos = 0;
3436 while (fonts) {
3437 GtkIMHtmlFontDetail *font = fonts->data;
3438 fonts = g_slist_remove (fonts, font);
3439 g_free (font->face);
3440 g_free (font->fore);
3441 g_free (font->back);
3442 g_free (font->sml);
3443 g_free (font);
3446 g_free(ws);
3447 g_free(bg);
3449 if (!imhtml->wbfo)
3450 gtk_imhtml_close_tags(imhtml, iter);
3452 object = g_object_ref(G_OBJECT(imhtml));
3453 g_signal_emit(object, signals[UPDATE_FORMAT], 0);
3454 g_object_unref(object);
3456 gtk_text_buffer_end_user_action(imhtml->text_buffer);
3459 void gtk_imhtml_remove_smileys(GtkIMHtml *imhtml)
3461 g_hash_table_destroy(imhtml->smiley_data);
3462 gtk_smiley_tree_destroy(imhtml->default_smilies);
3463 imhtml->smiley_data = g_hash_table_new_full(g_str_hash, g_str_equal,
3464 g_free, (GDestroyNotify)gtk_smiley_tree_destroy);
3465 imhtml->default_smilies = gtk_smiley_tree_new();
3468 void gtk_imhtml_show_comments (GtkIMHtml *imhtml,
3469 gboolean show)
3471 #if FALSE && GTK_CHECK_VERSION(2,10,10)
3472 GtkTextTag *tag;
3473 tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), "comment");
3474 if (tag)
3475 g_object_set(G_OBJECT(tag), "invisible", !show, NULL);
3476 #endif
3477 imhtml->show_comments = show;
3480 const char *
3481 gtk_imhtml_get_protocol_name(GtkIMHtml *imhtml) {
3482 return imhtml->protocol_name;
3485 void
3486 gtk_imhtml_set_protocol_name(GtkIMHtml *imhtml, const gchar *protocol_name) {
3487 g_free(imhtml->protocol_name);
3488 imhtml->protocol_name = g_strdup(protocol_name);
3491 void
3492 gtk_imhtml_delete(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end) {
3493 GList *l;
3494 GSList *sl;
3495 GtkTextIter i, i_s, i_e;
3496 GObject *object = g_object_ref(G_OBJECT(imhtml));
3498 if (start == NULL) {
3499 gtk_text_buffer_get_start_iter(imhtml->text_buffer, &i_s);
3500 start = &i_s;
3503 if (end == NULL) {
3504 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &i_e);
3505 end = &i_e;
3508 l = imhtml->scalables;
3509 while (l) {
3510 GList *next = l->next;
3511 struct scalable_data *sd = l->data;
3512 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer,
3513 &i, sd->mark);
3514 if (gtk_text_iter_in_range(&i, start, end)) {
3515 GtkIMHtmlScalable *scale = GTK_IMHTML_SCALABLE(sd->scalable);
3516 scale->free(scale);
3517 g_free(sd);
3518 imhtml->scalables = g_list_delete_link(imhtml->scalables, l);
3520 l = next;
3523 sl = imhtml->im_images;
3524 while (sl) {
3525 GSList *next = sl->next;
3526 struct im_image_data *img_data = sl->data;
3527 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer,
3528 &i, img_data->mark);
3529 if (gtk_text_iter_in_range(&i, start, end)) {
3530 if (imhtml->funcs->image_unref)
3531 imhtml->funcs->image_unref(img_data->id);
3532 imhtml->im_images = g_slist_delete_link(imhtml->im_images, sl);
3533 g_free(img_data);
3535 sl = next;
3537 gtk_text_buffer_delete(imhtml->text_buffer, start, end);
3539 g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(0));
3541 g_object_unref(object);
3544 void gtk_imhtml_page_up (GtkIMHtml *imhtml)
3546 GdkRectangle rect;
3547 GtkTextIter iter;
3549 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect);
3550 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml), &iter, rect.x,
3551 rect.y - rect.height);
3552 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml), &iter, 0, TRUE, 0, 0);
3555 void gtk_imhtml_page_down (GtkIMHtml *imhtml)
3557 GdkRectangle rect;
3558 GtkTextIter iter;
3560 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect);
3561 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml), &iter, rect.x,
3562 rect.y + rect.height);
3563 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml), &iter, 0, TRUE, 0, 0);
3566 /* GtkIMHtmlScalable, gtk_imhtml_image, gtk_imhtml_hr */
3567 GtkIMHtmlScalable *gtk_imhtml_image_new(GdkPixbuf *img, const gchar *filename, int id)
3569 GtkIMHtmlImage *im_image = g_malloc(sizeof(GtkIMHtmlImage));
3571 GTK_IMHTML_SCALABLE(im_image)->scale = gtk_imhtml_image_scale;
3572 GTK_IMHTML_SCALABLE(im_image)->add_to = gtk_imhtml_image_add_to;
3573 GTK_IMHTML_SCALABLE(im_image)->free = gtk_imhtml_image_free;
3575 im_image->pixbuf = img;
3576 im_image->image = GTK_IMAGE(gtk_image_new_from_pixbuf(im_image->pixbuf));
3577 im_image->width = gdk_pixbuf_get_width(img);
3578 im_image->height = gdk_pixbuf_get_height(img);
3579 im_image->mark = NULL;
3580 im_image->filename = g_strdup(filename);
3581 im_image->id = id;
3582 im_image->filesel = NULL;
3584 g_object_ref(img);
3585 return GTK_IMHTML_SCALABLE(im_image);
3588 static gboolean
3589 animate_image_cb(gpointer data)
3591 GtkIMHtmlImage *im_image;
3592 int width, height;
3593 int delay;
3595 im_image = data;
3597 /* Update the pointer to this GdkPixbuf frame of the animation */
3598 if (gdk_pixbuf_animation_iter_advance(GTK_IMHTML_ANIMATION(im_image)->iter, NULL)) {
3599 GdkPixbuf *pb = gdk_pixbuf_animation_iter_get_pixbuf(GTK_IMHTML_ANIMATION(im_image)->iter);
3600 g_object_unref(G_OBJECT(im_image->pixbuf));
3601 im_image->pixbuf = gdk_pixbuf_copy(pb);
3603 /* Update the displayed GtkImage */
3604 width = gdk_pixbuf_get_width(gtk_image_get_pixbuf(im_image->image));
3605 height = gdk_pixbuf_get_height(gtk_image_get_pixbuf(im_image->image));
3606 if (width > 0 && height > 0)
3608 /* Need to scale the new frame to the same size as the old frame */
3609 GdkPixbuf *tmp;
3610 tmp = gdk_pixbuf_scale_simple(im_image->pixbuf, width, height, GDK_INTERP_BILINEAR);
3611 gtk_image_set_from_pixbuf(im_image->image, tmp);
3612 g_object_unref(G_OBJECT(tmp));
3613 } else {
3614 /* Display at full-size */
3615 gtk_image_set_from_pixbuf(im_image->image, im_image->pixbuf);
3619 delay = MIN(gdk_pixbuf_animation_iter_get_delay_time(GTK_IMHTML_ANIMATION(im_image)->iter), 100);
3620 GTK_IMHTML_ANIMATION(im_image)->timer = g_timeout_add(delay, animate_image_cb, im_image);
3622 return FALSE;
3625 GtkIMHtmlScalable *gtk_imhtml_animation_new(GdkPixbufAnimation *anim, const gchar *filename, int id)
3627 GtkIMHtmlImage *im_image = (GtkIMHtmlImage *) g_new0(GtkIMHtmlAnimation, 1);
3629 GTK_IMHTML_SCALABLE(im_image)->scale = gtk_imhtml_image_scale;
3630 GTK_IMHTML_SCALABLE(im_image)->add_to = gtk_imhtml_image_add_to;
3631 GTK_IMHTML_SCALABLE(im_image)->free = gtk_imhtml_animation_free;
3633 GTK_IMHTML_ANIMATION(im_image)->anim = anim;
3634 if (gdk_pixbuf_animation_is_static_image(anim)) {
3635 im_image->pixbuf = gdk_pixbuf_animation_get_static_image(anim);
3636 g_object_ref(im_image->pixbuf);
3637 } else {
3638 int delay;
3639 GdkPixbuf *pb;
3640 GTK_IMHTML_ANIMATION(im_image)->iter = gdk_pixbuf_animation_get_iter(anim, NULL);
3641 pb = gdk_pixbuf_animation_iter_get_pixbuf(GTK_IMHTML_ANIMATION(im_image)->iter);
3642 im_image->pixbuf = gdk_pixbuf_copy(pb);
3643 delay = MIN(gdk_pixbuf_animation_iter_get_delay_time(GTK_IMHTML_ANIMATION(im_image)->iter), 100);
3644 GTK_IMHTML_ANIMATION(im_image)->timer = g_timeout_add(delay, animate_image_cb, im_image);
3646 im_image->image = GTK_IMAGE(gtk_image_new_from_pixbuf(im_image->pixbuf));
3647 im_image->width = gdk_pixbuf_animation_get_width(anim);
3648 im_image->height = gdk_pixbuf_animation_get_height(anim);
3649 im_image->filename = g_strdup(filename);
3650 im_image->id = id;
3652 g_object_ref(anim);
3654 return GTK_IMHTML_SCALABLE(im_image);
3657 void gtk_imhtml_image_scale(GtkIMHtmlScalable *scale, int width, int height)
3659 GtkIMHtmlImage *im_image = (GtkIMHtmlImage *)scale;
3661 if (im_image->width > width || im_image->height > height) {
3662 double ratio_w, ratio_h, ratio;
3663 int new_h, new_w;
3664 GdkPixbuf *new_image = NULL;
3666 ratio_w = ((double)width - 2) / im_image->width;
3667 ratio_h = ((double)height - 2) / im_image->height;
3669 ratio = (ratio_w < ratio_h) ? ratio_w : ratio_h;
3671 new_w = (int)(im_image->width * ratio);
3672 new_h = (int)(im_image->height * ratio);
3674 new_image = gdk_pixbuf_scale_simple(im_image->pixbuf, new_w, new_h, GDK_INTERP_BILINEAR);
3675 gtk_image_set_from_pixbuf(im_image->image, new_image);
3676 g_object_unref(G_OBJECT(new_image));
3677 } else if (gdk_pixbuf_get_width(gtk_image_get_pixbuf(im_image->image)) != im_image->width) {
3678 /* Enough space to show the full-size of the image. */
3679 GdkPixbuf *new_image;
3681 new_image = gdk_pixbuf_scale_simple(im_image->pixbuf, im_image->width, im_image->height, GDK_INTERP_BILINEAR);
3682 gtk_image_set_from_pixbuf(im_image->image, new_image);
3683 g_object_unref(G_OBJECT(new_image));
3687 static void
3688 image_save_yes_cb(GtkIMHtmlImageSave *save, const char *filename)
3690 GError *error = NULL;
3691 GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image;
3693 gtk_widget_destroy(image->filesel);
3694 image->filesel = NULL;
3696 if (save->data && save->datasize) {
3697 g_file_set_contents(filename, save->data, save->datasize, &error);
3698 } else {
3699 gchar *type = NULL;
3700 GSList *formats = gdk_pixbuf_get_formats();
3701 char *newfilename;
3703 while (formats) {
3704 GdkPixbufFormat *format = formats->data;
3705 gchar **extensions = gdk_pixbuf_format_get_extensions(format);
3706 gpointer p = extensions;
3708 while(gdk_pixbuf_format_is_writable(format) && extensions && extensions[0]){
3709 gchar *fmt_ext = extensions[0];
3710 const gchar* file_ext = filename + strlen(filename) - strlen(fmt_ext);
3712 if(!g_ascii_strcasecmp(fmt_ext, file_ext)){
3713 type = gdk_pixbuf_format_get_name(format);
3714 break;
3717 extensions++;
3720 g_strfreev(p);
3722 if (type)
3723 break;
3725 formats = formats->next;
3728 g_slist_free(formats);
3730 /* If I can't find a valid type, I will just tell the user about it and then assume
3731 it's a png */
3732 if (!type){
3733 char *basename, *tmp;
3734 char *dirname;
3735 GtkWidget *dialog = gtk_message_dialog_new_with_markup(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
3736 _("<span size='larger' weight='bold'>Unrecognized file type</span>\n\nDefaulting to PNG."));
3738 g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog);
3739 gtk_widget_show(dialog);
3741 type = g_strdup("png");
3742 dirname = g_path_get_dirname(filename);
3743 basename = g_path_get_basename(filename);
3744 tmp = strrchr(basename, '.');
3745 if (tmp != NULL)
3746 tmp[0] = '\0';
3747 newfilename = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s.png", dirname, basename);
3748 g_free(dirname);
3749 g_free(basename);
3750 } else {
3752 * We're able to save the file in it's original format, so we
3753 * can use the original file name.
3755 newfilename = g_strdup(filename);
3758 gdk_pixbuf_save(image->pixbuf, newfilename, type, &error, NULL);
3760 g_free(newfilename);
3761 g_free(type);
3764 if (error){
3765 GtkWidget *dialog = gtk_message_dialog_new_with_markup(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
3766 _("<span size='larger' weight='bold'>Error saving image</span>\n\n%s"), error->message);
3767 g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog);
3768 gtk_widget_show(dialog);
3769 g_error_free(error);
3773 static void
3774 image_save_check_if_exists_cb(GtkWidget *widget, gint response, GtkIMHtmlImageSave *save)
3776 gchar *filename;
3777 GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image;
3779 if (response != GTK_RESPONSE_ACCEPT) {
3780 gtk_widget_destroy(widget);
3781 image->filesel = NULL;
3782 return;
3785 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
3788 * XXX - We should probably prompt the user to determine if they really
3789 * want to overwrite the file or not. However, I don't feel like doing
3790 * that, so we're just always going to overwrite if the file exists.
3793 if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
3794 } else
3795 image_save_yes_cb(image, filename);
3798 image_save_yes_cb(save, filename);
3800 g_free(filename);
3803 static void
3804 gtk_imhtml_image_save(GtkWidget *w, GtkIMHtmlImageSave *save)
3806 GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image;
3808 if (image->filesel != NULL) {
3809 gtk_window_present(GTK_WINDOW(image->filesel));
3810 return;
3813 image->filesel = gtk_file_chooser_dialog_new(_("Save Image"),
3814 NULL,
3815 GTK_FILE_CHOOSER_ACTION_SAVE,
3816 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
3817 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
3818 NULL);
3819 gtk_dialog_set_default_response(GTK_DIALOG(image->filesel), GTK_RESPONSE_ACCEPT);
3820 if (image->filename != NULL)
3821 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(image->filesel), image->filename);
3822 g_signal_connect(G_OBJECT(GTK_FILE_CHOOSER(image->filesel)), "response",
3823 G_CALLBACK(image_save_check_if_exists_cb), save);
3825 gtk_widget_show(image->filesel);
3828 static void
3829 gtk_imhtml_custom_smiley_save(GtkWidget *w, GtkIMHtmlImageSave *save)
3831 GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image;
3833 /* Create an add dialog */
3834 PidginSmiley *editor = pidgin_smiley_edit(NULL, NULL);
3835 pidgin_smiley_editor_set_shortcut(editor, image->filename);
3836 pidgin_smiley_editor_set_image(editor, image->pixbuf);
3837 pidgin_smiley_editor_set_data(editor, save->data, save->datasize);
3841 * So, um, AIM Direct IM lets you send any file, not just images. You can
3842 * just insert a sound or a file or whatever in a conversation. It's
3843 * basically like file transfer, except there is an icon to open the file
3844 * embedded in the conversation. Someone should make the Purple core handle
3845 * all of that.
3847 static gboolean gtk_imhtml_image_clicked(GtkWidget *w, GdkEvent *event, GtkIMHtmlImageSave *save)
3849 GdkEventButton *event_button = (GdkEventButton *) event;
3850 GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image;
3852 if (event->type == GDK_BUTTON_RELEASE) {
3853 if(event_button->button == 3) {
3854 GtkWidget *img, *item, *menu;
3855 menu = gtk_menu_new();
3857 /* buttons and such */
3858 img = gtk_image_new_from_stock(GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU);
3859 item = gtk_image_menu_item_new_with_mnemonic(_("_Save Image..."));
3860 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3861 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_image_save), save);
3862 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3864 /* Add menu item for adding custom smiley to local smileys */
3865 /* we only add the menu if the image is of "custom smiley size"
3866 <= 96x96 pixels */
3867 if (image->width <= 96 && image->height <= 96) {
3868 img = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
3869 item = gtk_image_menu_item_new_with_mnemonic(_("_Add Custom Smiley..."));
3870 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3871 g_signal_connect(G_OBJECT(item), "activate",
3872 G_CALLBACK(gtk_imhtml_custom_smiley_save), save);
3873 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3876 gtk_widget_show_all(menu);
3877 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
3878 event_button->button, event_button->time);
3880 return TRUE;
3883 if(event->type == GDK_BUTTON_PRESS && event_button->button == 3)
3884 return TRUE; /* Clicking the right mouse button on a link shouldn't
3885 be caught by the regular GtkTextView menu */
3886 else
3887 return FALSE; /* Let clicks go through if we didn't catch anything */
3891 static gboolean gtk_imhtml_smiley_clicked(GtkWidget *w, GdkEvent *event, GtkIMHtmlSmiley *smiley)
3893 GdkPixbufAnimation *anim = NULL;
3894 GtkIMHtmlImageSave *save = NULL;
3895 gboolean ret;
3897 if (event->type != GDK_BUTTON_RELEASE || ((GdkEventButton*)event)->button != 3)
3898 return FALSE;
3900 anim = gtk_smiley_get_image(smiley);
3901 if (!anim)
3902 return FALSE;
3904 save = g_new0(GtkIMHtmlImageSave, 1);
3905 save->image = (GtkIMHtmlScalable *)gtk_imhtml_animation_new(anim, smiley->smile, 0);
3906 save->data = smiley->data; /* Do not need to memdup here, since the smiley is not
3907 destroyed before this GtkIMHtmlImageSave */
3908 save->datasize = smiley->datasize;
3909 ret = gtk_imhtml_image_clicked(w, event, save);
3910 g_object_set_data_full(G_OBJECT(w), "image-data", save->image, (GDestroyNotify)gtk_imhtml_animation_free);
3911 g_object_set_data_full(G_OBJECT(w), "image-save-data", save, (GDestroyNotify)g_free);
3912 return ret;
3915 void gtk_imhtml_image_free(GtkIMHtmlScalable *scale)
3917 GtkIMHtmlImage *image = (GtkIMHtmlImage *)scale;
3919 g_object_unref(image->pixbuf);
3920 g_free(image->filename);
3921 if (image->filesel)
3922 gtk_widget_destroy(image->filesel);
3923 g_free(scale);
3926 void gtk_imhtml_animation_free(GtkIMHtmlScalable *scale)
3928 GtkIMHtmlAnimation *animation = (GtkIMHtmlAnimation *)scale;
3930 if (animation->timer > 0)
3931 g_source_remove(animation->timer);
3932 if (animation->iter != NULL)
3933 g_object_unref(animation->iter);
3934 g_object_unref(animation->anim);
3936 gtk_imhtml_image_free(scale);
3939 void gtk_imhtml_image_add_to(GtkIMHtmlScalable *scale, GtkIMHtml *imhtml, GtkTextIter *iter)
3941 GtkIMHtmlImage *image = (GtkIMHtmlImage *)scale;
3942 GtkWidget *box = gtk_event_box_new();
3943 char *tag;
3944 GtkTextChildAnchor *anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter);
3945 GtkIMHtmlImageSave *save;
3947 gtk_container_add(GTK_CONTAINER(box), GTK_WIDGET(image->image));
3949 if(!gtk_check_version(2, 4, 0))
3950 g_object_set(G_OBJECT(box), "visible-window", FALSE, NULL);
3952 gtk_widget_show(GTK_WIDGET(image->image));
3953 gtk_widget_show(box);
3955 tag = g_strdup_printf("<IMG ID=\"%d\">", image->id);
3956 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", tag, g_free);
3957 g_object_set_data(G_OBJECT(anchor), "gtkimhtml_plaintext", "[Image]");
3959 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), box, anchor);
3961 save = g_new0(GtkIMHtmlImageSave, 1);
3962 save->image = scale;
3963 g_signal_connect(G_OBJECT(box), "event", G_CALLBACK(gtk_imhtml_image_clicked), save);
3964 g_object_set_data_full(G_OBJECT(box), "image-save-data", save, (GDestroyNotify)g_free);
3967 GtkIMHtmlScalable *gtk_imhtml_hr_new()
3969 GtkIMHtmlHr *hr = g_malloc(sizeof(GtkIMHtmlHr));
3971 GTK_IMHTML_SCALABLE(hr)->scale = gtk_imhtml_hr_scale;
3972 GTK_IMHTML_SCALABLE(hr)->add_to = gtk_imhtml_hr_add_to;
3973 GTK_IMHTML_SCALABLE(hr)->free = gtk_imhtml_hr_free;
3975 hr->sep = gtk_hseparator_new();
3976 gtk_widget_set_size_request(hr->sep, 5000, 2);
3977 gtk_widget_show(hr->sep);
3979 return GTK_IMHTML_SCALABLE(hr);
3982 void gtk_imhtml_hr_scale(GtkIMHtmlScalable *scale, int width, int height)
3984 gtk_widget_set_size_request(((GtkIMHtmlHr *)scale)->sep, width - 2, 2);
3987 void gtk_imhtml_hr_add_to(GtkIMHtmlScalable *scale, GtkIMHtml *imhtml, GtkTextIter *iter)
3989 GtkIMHtmlHr *hr = (GtkIMHtmlHr *)scale;
3990 GtkTextChildAnchor *anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter);
3991 g_object_set_data(G_OBJECT(anchor), "gtkimhtml_htmltext", "<hr>");
3992 g_object_set_data(G_OBJECT(anchor), "gtkimhtml_plaintext", "\n---\n");
3993 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), hr->sep, anchor);
3996 void gtk_imhtml_hr_free(GtkIMHtmlScalable *scale)
3998 g_free(scale);
4001 gboolean gtk_imhtml_search_find(GtkIMHtml *imhtml, const gchar *text)
4003 GtkTextIter iter, start, end;
4004 gboolean new_search = TRUE;
4005 GtkTextMark *start_mark;
4007 g_return_val_if_fail(imhtml != NULL, FALSE);
4008 g_return_val_if_fail(text != NULL, FALSE);
4010 start_mark = gtk_text_buffer_get_mark(imhtml->text_buffer, "search");
4012 if (start_mark && imhtml->search_string && !strcmp(text, imhtml->search_string))
4013 new_search = FALSE;
4015 if (new_search) {
4016 gtk_imhtml_search_clear(imhtml);
4017 g_free(imhtml->search_string);
4018 imhtml->search_string = g_strdup(text);
4019 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &iter);
4020 } else {
4021 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter,
4022 start_mark);
4025 if (gtk_source_iter_backward_search(&iter, imhtml->search_string,
4026 GTK_SOURCE_SEARCH_VISIBLE_ONLY | GTK_SOURCE_SEARCH_CASE_INSENSITIVE,
4027 &start, &end, NULL))
4029 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml), &start, 0, TRUE, 0, 0);
4030 gtk_text_buffer_create_mark(imhtml->text_buffer, "search", &start, FALSE);
4031 if (new_search)
4033 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "search", &iter, &end);
4035 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "search", &start, &end);
4036 while (gtk_source_iter_backward_search(&start, imhtml->search_string,
4037 GTK_SOURCE_SEARCH_VISIBLE_ONLY |
4038 GTK_SOURCE_SEARCH_CASE_INSENSITIVE,
4039 &start, &end, NULL));
4041 return TRUE;
4043 else if (!new_search)
4045 /* We hit the end, so start at the beginning again. */
4046 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &iter);
4048 if (gtk_source_iter_backward_search(&iter, imhtml->search_string,
4049 GTK_SOURCE_SEARCH_VISIBLE_ONLY | GTK_SOURCE_SEARCH_CASE_INSENSITIVE,
4050 &start, &end, NULL))
4052 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml), &start, 0, TRUE, 0, 0);
4053 gtk_text_buffer_create_mark(imhtml->text_buffer, "search", &start, FALSE);
4055 return TRUE;
4060 return FALSE;
4063 void gtk_imhtml_search_clear(GtkIMHtml *imhtml)
4065 GtkTextIter start, end;
4067 g_return_if_fail(imhtml != NULL);
4069 gtk_text_buffer_get_start_iter(imhtml->text_buffer, &start);
4070 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &end);
4072 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "search", &start, &end);
4073 g_free(imhtml->search_string);
4074 imhtml->search_string = NULL;
4077 static GtkTextTag *find_font_forecolor_tag(GtkIMHtml *imhtml, gchar *color)
4079 gchar str[18];
4080 GtkTextTag *tag;
4082 g_snprintf(str, sizeof(str), "FORECOLOR %s", color);
4084 tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str);
4085 if (!tag) {
4086 GdkColor gcolor;
4087 if (!gdk_color_parse(color, &gcolor)) {
4088 gchar tmp[8];
4089 tmp[0] = '#';
4090 strncpy(&tmp[1], color, 7);
4091 tmp[7] = '\0';
4092 if (!gdk_color_parse(tmp, &gcolor))
4093 gdk_color_parse("black", &gcolor);
4095 tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "foreground-gdk", &gcolor, NULL);
4098 return tag;
4101 static GtkTextTag *find_font_backcolor_tag(GtkIMHtml *imhtml, gchar *color)
4103 gchar str[18];
4104 GtkTextTag *tag;
4106 g_snprintf(str, sizeof(str), "BACKCOLOR %s", color);
4108 tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str);
4109 if (!tag) {
4110 GdkColor gcolor;
4111 if (!gdk_color_parse(color, &gcolor)) {
4112 gchar tmp[8];
4113 tmp[0] = '#';
4114 strncpy(&tmp[1], color, 7);
4115 tmp[7] = '\0';
4116 if (!gdk_color_parse(tmp, &gcolor))
4117 gdk_color_parse("white", &gcolor);
4119 tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "background-gdk", &gcolor, NULL);
4122 return tag;
4125 static GtkTextTag *find_font_background_tag(GtkIMHtml *imhtml, gchar *color)
4127 gchar str[19];
4128 GtkTextTag *tag;
4130 g_snprintf(str, sizeof(str), "BACKGROUND %s", color);
4132 tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str);
4133 if (!tag)
4134 tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, NULL);
4136 return tag;
4139 static GtkTextTag *find_font_face_tag(GtkIMHtml *imhtml, gchar *face)
4141 gchar str[256];
4142 GtkTextTag *tag;
4144 g_snprintf(str, sizeof(str), "FONT FACE %s", face);
4145 str[255] = '\0';
4147 tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str);
4148 if (!tag)
4149 tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "family", face, NULL);
4151 return tag;
4154 static GtkTextTag *find_font_size_tag(GtkIMHtml *imhtml, int size)
4156 gchar str[24];
4157 GtkTextTag *tag;
4159 g_snprintf(str, sizeof(str), "FONT SIZE %d", size);
4160 str[23] = '\0';
4162 tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str);
4163 if (!tag) {
4164 /* For reasons I don't understand, setting "scale" here scaled
4165 * based on some default size other than my theme's default
4166 * size. Our size 4 was actually smaller than our size 3 for
4167 * me. So this works around that oddity.
4169 GtkTextAttributes *attr = gtk_text_view_get_default_attributes(GTK_TEXT_VIEW(imhtml));
4170 tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "size",
4171 (gint) (pango_font_description_get_size(attr->font) *
4172 (double) POINT_SIZE(size)), NULL);
4173 gtk_text_attributes_unref(attr);
4176 return tag;
4179 static void remove_tag_by_prefix(GtkIMHtml *imhtml, const GtkTextIter *i, const GtkTextIter *e,
4180 const char *prefix, guint len, gboolean homo)
4182 GSList *tags, *l;
4183 GtkTextIter iter;
4185 tags = gtk_text_iter_get_tags(i);
4187 for (l = tags; l; l = l->next) {
4188 GtkTextTag *tag = l->data;
4190 if (tag->name && !strncmp(tag->name, prefix, len))
4191 gtk_text_buffer_remove_tag(imhtml->text_buffer, tag, i, e);
4194 g_slist_free(tags);
4196 if (homo)
4197 return;
4199 iter = *i;
4201 while (gtk_text_iter_forward_char(&iter) && !gtk_text_iter_equal(&iter, e)) {
4202 if (gtk_text_iter_begins_tag(&iter, NULL)) {
4203 tags = gtk_text_iter_get_toggled_tags(&iter, TRUE);
4205 for (l = tags; l; l = l->next) {
4206 GtkTextTag *tag = l->data;
4208 if (tag->name && !strncmp(tag->name, prefix, len))
4209 gtk_text_buffer_remove_tag(imhtml->text_buffer, tag, &iter, e);
4212 g_slist_free(tags);
4217 static void remove_font_size(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4219 remove_tag_by_prefix(imhtml, i, e, "FONT SIZE ", 10, homo);
4222 static void remove_font_face(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4224 remove_tag_by_prefix(imhtml, i, e, "FONT FACE ", 10, homo);
4227 static void remove_font_forecolor(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4229 remove_tag_by_prefix(imhtml, i, e, "FORECOLOR ", 10, homo);
4232 static void remove_font_backcolor(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4234 remove_tag_by_prefix(imhtml, i, e, "BACKCOLOR ", 10, homo);
4237 static void remove_font_background(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4239 remove_tag_by_prefix(imhtml, i, e, "BACKGROUND ", 10, homo);
4242 static void remove_font_link(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo)
4244 remove_tag_by_prefix(imhtml, i, e, "LINK ", 5, homo);
4247 static void
4248 imhtml_clear_formatting(GtkIMHtml *imhtml)
4250 GtkTextIter start, end;
4252 if (!imhtml->editable)
4253 return;
4255 if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4256 return;
4258 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "BOLD", &start, &end);
4259 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "ITALICS", &start, &end);
4260 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "UNDERLINE", &start, &end);
4261 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "STRIKE", &start, &end);
4262 remove_font_size(imhtml, &start, &end, FALSE);
4263 remove_font_face(imhtml, &start, &end, FALSE);
4264 remove_font_forecolor(imhtml, &start, &end, FALSE);
4265 remove_font_backcolor(imhtml, &start, &end, FALSE);
4266 remove_font_background(imhtml, &start, &end, FALSE);
4267 remove_font_link(imhtml, &start, &end, FALSE);
4269 imhtml->edit.bold = 0;
4270 imhtml->edit.italic = 0;
4271 imhtml->edit.underline = 0;
4272 imhtml->edit.strike = 0;
4273 imhtml->edit.fontsize = 0;
4275 g_free(imhtml->edit.fontface);
4276 imhtml->edit.fontface = NULL;
4278 g_free(imhtml->edit.forecolor);
4279 imhtml->edit.forecolor = NULL;
4281 g_free(imhtml->edit.backcolor);
4282 imhtml->edit.backcolor = NULL;
4284 g_free(imhtml->edit.background);
4285 imhtml->edit.background = NULL;
4288 /* Editable stuff */
4289 static void preinsert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml)
4291 imhtml->insert_offset = gtk_text_iter_get_offset(iter);
4294 static void insert_ca_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextChildAnchor *arg2, gpointer user_data)
4296 GtkTextIter start;
4298 start = *arg1;
4299 gtk_text_iter_backward_char(&start);
4301 gtk_imhtml_apply_tags_on_insert(user_data, &start, arg1);
4304 static void insert_cb(GtkTextBuffer *buffer, GtkTextIter *end, gchar *text, gint len, GtkIMHtml *imhtml)
4306 GtkTextIter start;
4308 if (!len)
4309 return;
4311 start = *end;
4312 gtk_text_iter_set_offset(&start, imhtml->insert_offset);
4314 gtk_imhtml_apply_tags_on_insert(imhtml, &start, end);
4317 static void delete_cb(GtkTextBuffer *buffer, GtkTextIter *start, GtkTextIter *end, GtkIMHtml *imhtml)
4319 GSList *tags, *l;
4321 tags = gtk_text_iter_get_tags(start);
4322 for (l = tags; l != NULL; l = l->next) {
4323 GtkTextTag *tag = GTK_TEXT_TAG(l->data);
4325 if (tag && /* Remove the formatting only if */
4326 gtk_text_iter_starts_word(start) && /* beginning of a word */
4327 gtk_text_iter_begins_tag(start, tag) && /* the tag starts with the selection */
4328 (!gtk_text_iter_has_tag(end, tag) || /* the tag ends within the selection */
4329 gtk_text_iter_ends_tag(end, tag))) {
4330 gtk_text_buffer_remove_tag(imhtml->text_buffer, tag, start, end);
4331 if (tag->name &&
4332 strncmp(tag->name, "LINK ", 5) == 0 && imhtml->edit.link) {
4333 gtk_imhtml_toggle_link(imhtml, NULL);
4337 g_slist_free(tags);
4340 static void gtk_imhtml_apply_tags_on_insert(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end)
4342 if (imhtml->edit.bold)
4343 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "BOLD", start, end);
4344 else
4345 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "BOLD", start, end);
4347 if (imhtml->edit.italic)
4348 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "ITALICS", start, end);
4349 else
4350 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "ITALICS", start, end);
4352 if (imhtml->edit.underline)
4353 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "UNDERLINE", start, end);
4354 else
4355 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "UNDERLINE", start, end);
4357 if (imhtml->edit.strike)
4358 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "STRIKE", start, end);
4359 else
4360 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "STRIKE", start, end);
4362 if (imhtml->edit.forecolor) {
4363 remove_font_forecolor(imhtml, start, end, TRUE);
4364 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4365 find_font_forecolor_tag(imhtml, imhtml->edit.forecolor),
4366 start, end);
4369 if (imhtml->edit.backcolor) {
4370 remove_font_backcolor(imhtml, start, end, TRUE);
4371 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4372 find_font_backcolor_tag(imhtml, imhtml->edit.backcolor),
4373 start, end);
4376 if (imhtml->edit.background) {
4377 remove_font_background(imhtml, start, end, TRUE);
4378 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4379 find_font_background_tag(imhtml, imhtml->edit.background),
4380 start, end);
4382 if (imhtml->edit.fontface) {
4383 remove_font_face(imhtml, start, end, TRUE);
4384 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4385 find_font_face_tag(imhtml, imhtml->edit.fontface),
4386 start, end);
4389 if (imhtml->edit.fontsize) {
4390 remove_font_size(imhtml, start, end, TRUE);
4391 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4392 find_font_size_tag(imhtml, imhtml->edit.fontsize),
4393 start, end);
4396 if (imhtml->edit.link) {
4397 remove_font_link(imhtml, start, end, TRUE);
4398 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4399 imhtml->edit.link,
4400 start, end);
4404 void gtk_imhtml_set_editable(GtkIMHtml *imhtml, gboolean editable)
4406 gtk_text_view_set_editable(GTK_TEXT_VIEW(imhtml), editable);
4408 * We need a visible caret for accessibility, so mouseless
4409 * people can highlight stuff.
4411 /* gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(imhtml), editable); */
4412 if (editable && !imhtml->editable) {
4413 g_signal_connect_after(G_OBJECT(GTK_IMHTML(imhtml)->text_buffer), "mark-set",
4414 G_CALLBACK(mark_set_cb), imhtml);
4415 g_signal_connect(G_OBJECT(imhtml), "backspace", G_CALLBACK(smart_backspace_cb), NULL);
4416 } else if (!editable && imhtml->editable) {
4417 g_signal_handlers_disconnect_by_func(G_OBJECT(GTK_IMHTML(imhtml)->text_buffer),
4418 mark_set_cb, imhtml);
4419 g_signal_handlers_disconnect_by_func(G_OBJECT(imhtml),
4420 smart_backspace_cb, NULL);
4423 imhtml->editable = editable;
4424 imhtml->format_functions = GTK_IMHTML_ALL;
4427 void gtk_imhtml_set_whole_buffer_formatting_only(GtkIMHtml *imhtml, gboolean wbfo)
4429 g_return_if_fail(imhtml != NULL);
4431 imhtml->wbfo = wbfo;
4434 void gtk_imhtml_set_format_functions(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons)
4436 GObject *object = g_object_ref(G_OBJECT(imhtml));
4437 imhtml->format_functions = buttons;
4438 g_signal_emit(object, signals[BUTTONS_UPDATE], 0, buttons);
4439 g_object_unref(object);
4442 GtkIMHtmlButtons gtk_imhtml_get_format_functions(GtkIMHtml *imhtml)
4444 return imhtml->format_functions;
4447 void gtk_imhtml_get_current_format(GtkIMHtml *imhtml, gboolean *bold,
4448 gboolean *italic, gboolean *underline)
4450 if (bold != NULL)
4451 (*bold) = imhtml->edit.bold;
4452 if (italic != NULL)
4453 (*italic) = imhtml->edit.italic;
4454 if (underline != NULL)
4455 (*underline) = imhtml->edit.underline;
4458 char *
4459 gtk_imhtml_get_current_fontface(GtkIMHtml *imhtml)
4461 return g_strdup(imhtml->edit.fontface);
4464 char *
4465 gtk_imhtml_get_current_forecolor(GtkIMHtml *imhtml)
4467 return g_strdup(imhtml->edit.forecolor);
4470 char *
4471 gtk_imhtml_get_current_backcolor(GtkIMHtml *imhtml)
4473 return g_strdup(imhtml->edit.backcolor);
4476 char *
4477 gtk_imhtml_get_current_background(GtkIMHtml *imhtml)
4479 return g_strdup(imhtml->edit.background);
4482 gint
4483 gtk_imhtml_get_current_fontsize(GtkIMHtml *imhtml)
4485 return imhtml->edit.fontsize;
4488 gboolean gtk_imhtml_get_editable(GtkIMHtml *imhtml)
4490 return imhtml->editable;
4493 void
4494 gtk_imhtml_clear_formatting(GtkIMHtml *imhtml)
4496 GObject *object;
4498 object = g_object_ref(G_OBJECT(imhtml));
4499 g_signal_emit(object, signals[CLEAR_FORMAT], 0);
4501 gtk_widget_grab_focus(GTK_WIDGET(imhtml));
4503 g_object_unref(object);
4507 * I had this crazy idea about changing the text cursor color to reflex the foreground color
4508 * of the text about to be entered. This is the place you'd do it, along with the place where
4509 * we actually set a new foreground color.
4510 * I may not do this, because people will bitch about Purple overriding their gtk theme's cursor
4511 * colors.
4513 * Just in case I do do this, I asked about what to set the secondary text cursor to.
4515 * (12:45:27) ?? ???: secondary_cursor_color = (rgb(background) + rgb(primary_cursor_color) ) / 2
4516 * (12:45:55) ?? ???: understand?
4517 * (12:46:14) Tim: yeah. i didn't know there was an exact formula
4518 * (12:46:56) ?? ???: u might need to extract separate each color from RGB
4521 static void mark_set_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextMark *mark,
4522 GtkIMHtml *imhtml)
4524 GSList *tags, *l;
4525 GtkTextIter iter;
4527 if (mark != gtk_text_buffer_get_insert(buffer))
4528 return;
4530 if (!gtk_text_buffer_get_char_count(buffer))
4531 return;
4533 imhtml->edit.bold = imhtml->edit.italic = imhtml->edit.underline = imhtml->edit.strike = FALSE;
4534 g_free(imhtml->edit.forecolor);
4535 imhtml->edit.forecolor = NULL;
4537 g_free(imhtml->edit.backcolor);
4538 imhtml->edit.backcolor = NULL;
4540 g_free(imhtml->edit.fontface);
4541 imhtml->edit.fontface = NULL;
4543 imhtml->edit.fontsize = 0;
4544 imhtml->edit.link = NULL;
4546 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, mark);
4549 if (gtk_text_iter_is_end(&iter))
4550 tags = gtk_text_iter_get_toggled_tags(&iter, FALSE);
4551 else
4552 tags = gtk_text_iter_get_tags(&iter);
4554 for (l = tags; l != NULL; l = l->next) {
4555 GtkTextTag *tag = GTK_TEXT_TAG(l->data);
4557 if (tag->name) {
4558 if (strcmp(tag->name, "BOLD") == 0)
4559 imhtml->edit.bold = TRUE;
4560 else if (strcmp(tag->name, "ITALICS") == 0)
4561 imhtml->edit.italic = TRUE;
4562 else if (strcmp(tag->name, "UNDERLINE") == 0)
4563 imhtml->edit.underline = TRUE;
4564 else if (strcmp(tag->name, "STRIKE") == 0)
4565 imhtml->edit.strike = TRUE;
4566 else if (strncmp(tag->name, "FORECOLOR ", 10) == 0)
4567 imhtml->edit.forecolor = g_strdup(&(tag->name)[10]);
4568 else if (strncmp(tag->name, "BACKCOLOR ", 10) == 0)
4569 imhtml->edit.backcolor = g_strdup(&(tag->name)[10]);
4570 else if (strncmp(tag->name, "FONT FACE ", 10) == 0)
4571 imhtml->edit.fontface = g_strdup(&(tag->name)[10]);
4572 else if (strncmp(tag->name, "FONT SIZE ", 10) == 0)
4573 imhtml->edit.fontsize = strtol(&(tag->name)[10], NULL, 10);
4574 else if ((strncmp(tag->name, "LINK ", 5) == 0) && !gtk_text_iter_is_end(&iter))
4575 imhtml->edit.link = tag;
4579 g_slist_free(tags);
4582 static void imhtml_emit_signal_for_format(GtkIMHtml *imhtml, GtkIMHtmlButtons button)
4584 GObject *object;
4586 g_return_if_fail(imhtml != NULL);
4588 object = g_object_ref(G_OBJECT(imhtml));
4589 g_signal_emit(object, signals[TOGGLE_FORMAT], 0, button);
4590 g_object_unref(object);
4593 static void imhtml_toggle_bold(GtkIMHtml *imhtml)
4595 GtkTextIter start, end;
4597 imhtml->edit.bold = !imhtml->edit.bold;
4599 if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4600 return;
4602 if (imhtml->edit.bold)
4603 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "BOLD", &start, &end);
4604 else
4605 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "BOLD", &start, &end);
4608 void gtk_imhtml_toggle_bold(GtkIMHtml *imhtml)
4610 imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_BOLD);
4613 static void imhtml_toggle_italic(GtkIMHtml *imhtml)
4615 GtkTextIter start, end;
4617 imhtml->edit.italic = !imhtml->edit.italic;
4619 if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4620 return;
4622 if (imhtml->edit.italic)
4623 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "ITALICS", &start, &end);
4624 else
4625 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "ITALICS", &start, &end);
4628 void gtk_imhtml_toggle_italic(GtkIMHtml *imhtml)
4630 imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_ITALIC);
4633 static void imhtml_toggle_underline(GtkIMHtml *imhtml)
4635 GtkTextIter start, end;
4637 imhtml->edit.underline = !imhtml->edit.underline;
4639 if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4640 return;
4642 if (imhtml->edit.underline)
4643 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "UNDERLINE", &start, &end);
4644 else
4645 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "UNDERLINE", &start, &end);
4648 void gtk_imhtml_toggle_underline(GtkIMHtml *imhtml)
4650 imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_UNDERLINE);
4653 static void imhtml_toggle_strike(GtkIMHtml *imhtml)
4655 GtkTextIter start, end;
4657 imhtml->edit.strike = !imhtml->edit.strike;
4659 if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4660 return;
4662 if (imhtml->edit.strike)
4663 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "STRIKE", &start, &end);
4664 else
4665 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "STRIKE", &start, &end);
4668 void gtk_imhtml_toggle_strike(GtkIMHtml *imhtml)
4670 imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_STRIKE);
4673 void gtk_imhtml_font_set_size(GtkIMHtml *imhtml, gint size)
4675 GObject *object;
4676 GtkTextIter start, end;
4678 imhtml->edit.fontsize = size;
4680 if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4681 return;
4683 remove_font_size(imhtml, &start, &end, imhtml->wbfo);
4684 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4685 find_font_size_tag(imhtml, imhtml->edit.fontsize), &start, &end);
4687 object = g_object_ref(G_OBJECT(imhtml));
4688 g_signal_emit(object, signals[TOGGLE_FORMAT], 0, GTK_IMHTML_SHRINK | GTK_IMHTML_GROW);
4689 g_object_unref(object);
4692 static void imhtml_font_shrink(GtkIMHtml *imhtml)
4694 GtkTextIter start, end;
4696 if (imhtml->edit.fontsize == 1)
4697 return;
4699 if (!imhtml->edit.fontsize)
4700 imhtml->edit.fontsize = 2;
4701 else
4702 imhtml->edit.fontsize--;
4704 if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4705 return;
4706 remove_font_size(imhtml, &start, &end, imhtml->wbfo);
4707 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4708 find_font_size_tag(imhtml, imhtml->edit.fontsize), &start, &end);
4711 void gtk_imhtml_font_shrink(GtkIMHtml *imhtml)
4713 imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_SHRINK);
4716 static void imhtml_font_grow(GtkIMHtml *imhtml)
4718 GtkTextIter start, end;
4720 if (imhtml->edit.fontsize == MAX_FONT_SIZE)
4721 return;
4723 if (!imhtml->edit.fontsize)
4724 imhtml->edit.fontsize = 4;
4725 else
4726 imhtml->edit.fontsize++;
4728 if (!imhtml_get_iter_bounds(imhtml, &start, &end))
4729 return;
4730 remove_font_size(imhtml, &start, &end, imhtml->wbfo);
4731 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4732 find_font_size_tag(imhtml, imhtml->edit.fontsize), &start, &end);
4735 void gtk_imhtml_font_grow(GtkIMHtml *imhtml)
4737 imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_GROW);
4740 static gboolean gtk_imhtml_toggle_str_tag(GtkIMHtml *imhtml, const char *value, char **edit_field,
4741 void (*remove_func)(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo),
4742 GtkTextTag *(find_func)(GtkIMHtml *imhtml, gchar *color), GtkIMHtmlButtons button)
4744 GObject *object;
4745 GtkTextIter start;
4746 GtkTextIter end;
4748 g_free(*edit_field);
4749 *edit_field = NULL;
4751 if (value && strcmp(value, "") != 0)
4753 *edit_field = g_strdup(value);
4755 if (imhtml_get_iter_bounds(imhtml, &start, &end)) {
4756 remove_func(imhtml, &start, &end, imhtml->wbfo);
4757 gtk_text_buffer_apply_tag(imhtml->text_buffer,
4758 find_func(imhtml, *edit_field), &start, &end);
4761 else
4763 if (imhtml_get_iter_bounds(imhtml, &start, &end))
4764 remove_func(imhtml, &start, &end, TRUE); /* 'TRUE' or 'imhtml->wbfo'? */
4767 object = g_object_ref(G_OBJECT(imhtml));
4768 g_signal_emit(object, signals[TOGGLE_FORMAT], 0, button);
4769 g_object_unref(object);
4771 return *edit_field != NULL;
4774 gboolean gtk_imhtml_toggle_forecolor(GtkIMHtml *imhtml, const char *color)
4776 return gtk_imhtml_toggle_str_tag(imhtml, color, &imhtml->edit.forecolor,
4777 remove_font_forecolor, find_font_forecolor_tag,
4778 GTK_IMHTML_FORECOLOR);
4781 gboolean gtk_imhtml_toggle_backcolor(GtkIMHtml *imhtml, const char *color)
4783 return gtk_imhtml_toggle_str_tag(imhtml, color, &imhtml->edit.backcolor,
4784 remove_font_backcolor, find_font_backcolor_tag,
4785 GTK_IMHTML_BACKCOLOR);
4788 gboolean gtk_imhtml_toggle_background(GtkIMHtml *imhtml, const char *color)
4790 return gtk_imhtml_toggle_str_tag(imhtml, color, &imhtml->edit.background,
4791 remove_font_background, find_font_background_tag,
4792 GTK_IMHTML_BACKGROUND);
4795 gboolean gtk_imhtml_toggle_fontface(GtkIMHtml *imhtml, const char *face)
4797 return gtk_imhtml_toggle_str_tag(imhtml, face, &imhtml->edit.fontface,
4798 remove_font_face, find_font_face_tag,
4799 GTK_IMHTML_FACE);
4802 void gtk_imhtml_toggle_link(GtkIMHtml *imhtml, const char *url)
4804 GObject *object;
4805 GtkTextIter start, end;
4806 GtkTextTag *linktag;
4807 static guint linkno = 0;
4808 gchar str[48];
4809 GdkColor *color = NULL;
4811 imhtml->edit.link = NULL;
4813 if (url) {
4814 g_snprintf(str, sizeof(str), "LINK %d", linkno++);
4815 str[47] = '\0';
4817 gtk_widget_style_get(GTK_WIDGET(imhtml), "hyperlink-color", &color, NULL);
4818 if (color) {
4819 imhtml->edit.link = linktag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "foreground-gdk", color, "underline", PANGO_UNDERLINE_SINGLE, NULL);
4820 gdk_color_free(color);
4821 } else {
4822 imhtml->edit.link = linktag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE, NULL);
4824 g_object_set_data_full(G_OBJECT(linktag), "link_url", g_strdup(url), g_free);
4825 g_signal_connect(G_OBJECT(linktag), "event", G_CALLBACK(tag_event), NULL);
4827 if (imhtml->editable && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) {
4828 remove_font_link(imhtml, &start, &end, FALSE);
4829 gtk_text_buffer_apply_tag(imhtml->text_buffer, linktag, &start, &end);
4833 object = g_object_ref(G_OBJECT(imhtml));
4834 g_signal_emit(object, signals[TOGGLE_FORMAT], 0, GTK_IMHTML_LINK);
4835 g_object_unref(object);
4838 void gtk_imhtml_insert_link(GtkIMHtml *imhtml, GtkTextMark *mark, const char *url, const char *text)
4840 GtkTextIter iter;
4842 /* Delete any currently selected text */
4843 gtk_text_buffer_delete_selection(imhtml->text_buffer, TRUE, TRUE);
4845 gtk_imhtml_toggle_link(imhtml, url);
4846 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, mark);
4847 gtk_text_buffer_insert(imhtml->text_buffer, &iter, text, -1);
4848 gtk_imhtml_toggle_link(imhtml, NULL);
4851 void gtk_imhtml_insert_smiley(GtkIMHtml *imhtml, const char *sml, char *smiley)
4853 GtkTextMark *mark;
4854 GtkTextIter iter;
4856 /* Delete any currently selected text */
4857 gtk_text_buffer_delete_selection(imhtml->text_buffer, TRUE, TRUE);
4859 mark = gtk_text_buffer_get_insert(imhtml->text_buffer);
4861 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, mark);
4862 gtk_text_buffer_begin_user_action(imhtml->text_buffer);
4863 gtk_imhtml_insert_smiley_at_iter(imhtml, sml, smiley, &iter);
4864 gtk_text_buffer_end_user_action(imhtml->text_buffer);
4867 static gboolean
4868 image_expose(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
4870 GTK_WIDGET_CLASS(GTK_WIDGET_GET_CLASS(widget))->expose_event(widget, event);
4872 return TRUE;
4875 /* In case the smiley gets removed from the imhtml before it gets removed from the queue */
4876 static void animated_smiley_destroy_cb(GtkObject *widget, GtkIMHtml *imhtml)
4878 GList *l = imhtml->animations->head;
4879 while (l) {
4880 GList *next = l->next;
4881 if (l->data == widget) {
4882 if (l == imhtml->animations->tail)
4883 imhtml->animations->tail = imhtml->animations->tail->prev;
4884 imhtml->animations->head = g_list_delete_link(imhtml->animations->head, l);
4885 imhtml->num_animations--;
4887 l = next;
4891 void gtk_imhtml_insert_smiley_at_iter(GtkIMHtml *imhtml, const char *sml, char *smiley, GtkTextIter *iter)
4893 GdkPixbuf *pixbuf = NULL;
4894 GdkPixbufAnimation *annipixbuf = NULL;
4895 GtkWidget *icon = NULL;
4896 GtkTextChildAnchor *anchor = NULL;
4897 char *unescaped;
4898 GtkIMHtmlSmiley *imhtml_smiley;
4899 GtkWidget *ebox = NULL;
4900 int numsmileys_thismsg, numsmileys_total;
4903 * This GtkIMHtml has the maximum number of smileys allowed, so don't
4904 * add any more. We do this for performance reasons, because smileys
4905 * are apparently pretty inefficient. Hopefully we can remove this
4906 * restriction when we're using a better HTML widget.
4908 numsmileys_thismsg = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_thismsg"));
4909 if (numsmileys_thismsg >= 30) {
4910 gtk_text_buffer_insert(imhtml->text_buffer, iter, smiley, -1);
4911 return;
4913 numsmileys_total = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_total"));
4914 if (numsmileys_total >= 300) {
4915 gtk_text_buffer_insert(imhtml->text_buffer, iter, smiley, -1);
4916 return;
4919 unescaped = purple_unescape_html(smiley);
4920 imhtml_smiley = gtk_imhtml_smiley_get(imhtml, sml, unescaped);
4922 if (imhtml->format_functions & GTK_IMHTML_SMILEY) {
4923 annipixbuf = imhtml_smiley ? gtk_smiley_get_image(imhtml_smiley) : NULL;
4924 if (annipixbuf) {
4925 if (gdk_pixbuf_animation_is_static_image(annipixbuf)) {
4926 pixbuf = gdk_pixbuf_animation_get_static_image(annipixbuf);
4927 if (pixbuf)
4928 icon = gtk_image_new_from_pixbuf(pixbuf);
4929 } else {
4930 icon = gtk_image_new_from_animation(annipixbuf);
4931 if (imhtml->num_animations == 20) {
4932 GtkImage *image = GTK_IMAGE(g_queue_pop_head(imhtml->animations));
4933 GdkPixbufAnimation *anim = gtk_image_get_animation(image);
4934 g_signal_handlers_disconnect_matched(G_OBJECT(image), G_SIGNAL_MATCH_FUNC,
4935 0, 0, NULL, G_CALLBACK(animated_smiley_destroy_cb), NULL);
4936 if (anim) {
4937 GdkPixbuf *pb = gdk_pixbuf_animation_get_static_image(anim);
4938 if (pb != NULL) {
4939 GdkPixbuf *copy = gdk_pixbuf_copy(pb);
4940 gtk_image_set_from_pixbuf(image, copy);
4941 g_object_unref(G_OBJECT(copy));
4944 } else {
4945 imhtml->num_animations++;
4947 g_signal_connect(G_OBJECT(icon), "destroy", G_CALLBACK(animated_smiley_destroy_cb), imhtml);
4948 g_queue_push_tail(imhtml->animations, icon);
4953 if (imhtml_smiley && imhtml_smiley->flags & GTK_IMHTML_SMILEY_CUSTOM) {
4954 ebox = gtk_event_box_new();
4955 gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox), FALSE);
4956 gtk_widget_show(ebox);
4959 if (icon) {
4960 anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter);
4961 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", g_strdup(unescaped), g_free);
4962 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_tiptext", g_strdup(unescaped), g_free);
4963 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley), g_free);
4965 /* This catches the expose events generated by animated
4966 * images, and ensures that they are handled by the image
4967 * itself, without propagating to the textview and causing
4968 * a complete refresh */
4969 g_signal_connect(G_OBJECT(icon), "expose-event", G_CALLBACK(image_expose), NULL);
4971 gtk_widget_show(icon);
4972 if (ebox)
4973 gtk_container_add(GTK_CONTAINER(ebox), icon);
4974 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), ebox ? ebox : icon, anchor);
4976 g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(numsmileys_thismsg + 1));
4977 g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(numsmileys_total + 1));
4978 } else if (imhtml_smiley != NULL && (imhtml->format_functions & GTK_IMHTML_SMILEY)) {
4979 anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter);
4980 imhtml_smiley->anchors = g_slist_append(imhtml_smiley->anchors, g_object_ref(anchor));
4981 if (ebox) {
4982 GtkWidget *img = gtk_image_new_from_stock(GTK_STOCK_MISSING_IMAGE, GTK_ICON_SIZE_MENU);
4983 gtk_container_add(GTK_CONTAINER(ebox), img);
4984 gtk_widget_show(img);
4985 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", g_strdup(unescaped), g_free);
4986 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_tiptext", g_strdup(unescaped), g_free);
4987 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley), g_free);
4988 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), ebox, anchor);
4991 g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(numsmileys_thismsg + 1));
4992 g_object_set_data(G_OBJECT(imhtml), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(numsmileys_total + 1));
4993 } else {
4994 gtk_text_buffer_insert(imhtml->text_buffer, iter, smiley, -1);
4997 if (ebox) {
4998 g_signal_connect(G_OBJECT(ebox), "event", G_CALLBACK(gtk_imhtml_smiley_clicked), imhtml_smiley);
5001 g_free(unescaped);
5004 void gtk_imhtml_insert_image_at_iter(GtkIMHtml *imhtml, int id, GtkTextIter *iter)
5006 GdkPixbufAnimation *anim = NULL;
5007 const char *filename = NULL;
5008 gpointer image;
5009 GdkRectangle rect;
5010 GtkIMHtmlScalable *scalable = NULL;
5011 struct scalable_data *sd;
5012 int minus;
5014 if (!imhtml->funcs || !imhtml->funcs->image_get ||
5015 !imhtml->funcs->image_get_size || !imhtml->funcs->image_get_data ||
5016 !imhtml->funcs->image_get_filename || !imhtml->funcs->image_ref ||
5017 !imhtml->funcs->image_unref)
5018 return;
5020 image = imhtml->funcs->image_get(id);
5022 if (image) {
5023 gpointer data;
5024 size_t len;
5026 data = imhtml->funcs->image_get_data(image);
5027 len = imhtml->funcs->image_get_size(image);
5029 if (data && len) {
5030 GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
5031 gdk_pixbuf_loader_write(loader, data, len, NULL);
5032 gdk_pixbuf_loader_close(loader, NULL);
5033 anim = gdk_pixbuf_loader_get_animation(loader);
5034 if (anim)
5035 g_object_ref(G_OBJECT(anim));
5036 g_object_unref(G_OBJECT(loader));
5041 if (anim) {
5042 struct im_image_data *t = g_new(struct im_image_data, 1);
5043 filename = imhtml->funcs->image_get_filename(image);
5044 imhtml->funcs->image_ref(id);
5045 t->id = id;
5046 t->mark = gtk_text_buffer_create_mark(imhtml->text_buffer, NULL, iter, TRUE);
5047 imhtml->im_images = g_slist_prepend(imhtml->im_images, t);
5048 scalable = gtk_imhtml_animation_new(anim, filename, id);
5049 g_object_unref(G_OBJECT(anim));
5050 } else {
5051 GdkPixbuf *pixbuf;
5052 pixbuf = gtk_widget_render_icon(GTK_WIDGET(imhtml), GTK_STOCK_MISSING_IMAGE,
5053 GTK_ICON_SIZE_BUTTON, "gtkimhtml-missing-image");
5054 scalable = gtk_imhtml_image_new(pixbuf, filename, id);
5055 g_object_unref(G_OBJECT(pixbuf));
5058 sd = g_new(struct scalable_data, 1);
5059 sd->scalable = scalable;
5060 sd->mark = gtk_text_buffer_create_mark(imhtml->text_buffer, NULL, iter, TRUE);
5061 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect);
5062 scalable->add_to(scalable, imhtml, iter);
5063 minus = gtk_text_view_get_left_margin(GTK_TEXT_VIEW(imhtml)) +
5064 gtk_text_view_get_right_margin(GTK_TEXT_VIEW(imhtml));
5065 scalable->scale(scalable, rect.width - minus, rect.height);
5066 imhtml->scalables = g_list_append(imhtml->scalables, sd);
5069 static const gchar *tag_to_html_start(GtkTextTag *tag)
5071 const gchar *name;
5072 static gchar buf[1024];
5074 name = tag->name;
5075 g_return_val_if_fail(name != NULL, "");
5077 if (strcmp(name, "BOLD") == 0) {
5078 return "<b>";
5079 } else if (strcmp(name, "ITALICS") == 0) {
5080 return "<i>";
5081 } else if (strcmp(name, "UNDERLINE") == 0) {
5082 return "<u>";
5083 } else if (strcmp(name, "STRIKE") == 0) {
5084 return "<s>";
5085 } else if (strncmp(name, "LINK ", 5) == 0) {
5086 char *tmp = g_object_get_data(G_OBJECT(tag), "link_url");
5087 if (tmp) {
5088 g_snprintf(buf, sizeof(buf), "<a href=\"%s\">", tmp);
5089 buf[sizeof(buf)-1] = '\0';
5090 return buf;
5091 } else {
5092 return "";
5094 } else if (strncmp(name, "FORECOLOR ", 10) == 0) {
5095 g_snprintf(buf, sizeof(buf), "<font color=\"%s\">", &name[10]);
5096 return buf;
5097 } else if (strncmp(name, "BACKCOLOR ", 10) == 0) {
5098 g_snprintf(buf, sizeof(buf), "<font back=\"%s\">", &name[10]);
5099 return buf;
5100 } else if (strncmp(name, "BACKGROUND ", 10) == 0) {
5101 g_snprintf(buf, sizeof(buf), "<body bgcolor=\"%s\">", &name[11]);
5102 return buf;
5103 } else if (strncmp(name, "FONT FACE ", 10) == 0) {
5104 g_snprintf(buf, sizeof(buf), "<font face=\"%s\">", &name[10]);
5105 return buf;
5106 } else if (strncmp(name, "FONT SIZE ", 10) == 0) {
5107 g_snprintf(buf, sizeof(buf), "<font size=\"%s\">", &name[10]);
5108 return buf;
5109 } else {
5110 char *str = buf;
5111 gboolean isset;
5112 int ivalue = 0;
5113 GdkColor *color = NULL;
5114 GObject *obj = G_OBJECT(tag);
5115 gboolean empty = TRUE;
5117 str += g_snprintf(str, sizeof(buf) - (str - buf), "<span style='");
5119 /* Weight */
5120 g_object_get(obj, "weight-set", &isset, "weight", &ivalue, NULL);
5121 if (isset) {
5122 const char *weight = "";
5123 if (ivalue >= PANGO_WEIGHT_ULTRABOLD)
5124 weight = "bolder";
5125 else if (ivalue >= PANGO_WEIGHT_BOLD)
5126 weight = "bold";
5127 else if (ivalue >= PANGO_WEIGHT_NORMAL)
5128 weight = "normal";
5129 else
5130 weight = "lighter";
5132 str += g_snprintf(str, sizeof(buf) - (str - buf), "font-weight: %s;", weight);
5133 empty = FALSE;
5136 /* Foreground color */
5137 g_object_get(obj, "foreground-set", &isset, "foreground-gdk", &color, NULL);
5138 if (isset && color) {
5139 str += g_snprintf(str, sizeof(buf) - (str - buf),
5140 "color: #%02x%02x%02x;",
5141 color->red >> 8, color->green >> 8, color->blue >> 8);
5142 empty = FALSE;
5144 gdk_color_free(color);
5146 /* Background color */
5147 g_object_get(obj, "background-set", &isset, "background-gdk", &color, NULL);
5148 if (isset && color) {
5149 str += g_snprintf(str, sizeof(buf) - (str - buf),
5150 "background: #%02x%02x%02x;",
5151 color->red >> 8, color->green >> 8, color->blue >> 8);
5152 empty = FALSE;
5154 gdk_color_free(color);
5156 /* Underline */
5157 g_object_get(obj, "underline-set", &isset, "underline", &ivalue, NULL);
5158 if (isset) {
5159 switch (ivalue) {
5160 case PANGO_UNDERLINE_NONE:
5161 case PANGO_UNDERLINE_ERROR:
5162 break;
5163 default:
5164 str += g_snprintf(str, sizeof(buf) - (str - buf), "text-decoration: underline;");
5165 empty = FALSE;
5169 g_snprintf(str, sizeof(buf) - (str - buf), "'>");
5171 return (empty ? "" : buf);
5175 static const gchar *tag_to_html_end(GtkTextTag *tag)
5177 const gchar *name;
5179 name = tag->name;
5180 g_return_val_if_fail(name != NULL, "");
5182 if (strcmp(name, "BOLD") == 0) {
5183 return "</b>";
5184 } else if (strcmp(name, "ITALICS") == 0) {
5185 return "</i>";
5186 } else if (strcmp(name, "UNDERLINE") == 0) {
5187 return "</u>";
5188 } else if (strcmp(name, "STRIKE") == 0) {
5189 return "</s>";
5190 } else if (strncmp(name, "LINK ", 5) == 0) {
5191 return "</a>";
5192 } else if (strncmp(name, "FORECOLOR ", 10) == 0) {
5193 return "</font>";
5194 } else if (strncmp(name, "BACKCOLOR ", 10) == 0) {
5195 return "</font>";
5196 } else if (strncmp(name, "BACKGROUND ", 10) == 0) {
5197 return "</body>";
5198 } else if (strncmp(name, "FONT FACE ", 10) == 0) {
5199 return "</font>";
5200 } else if (strncmp(name, "FONT SIZE ", 10) == 0) {
5201 return "</font>";
5202 } else {
5203 const char *props[] = {"weight-set", "foreground-set", "background-set",
5204 "size-set", "underline-set", NULL};
5205 int i;
5206 for (i = 0; props[i]; i++) {
5207 gboolean set = FALSE;
5208 g_object_get(G_OBJECT(tag), props[i], &set, NULL);
5209 if (set)
5210 return "</span>";
5213 return "";
5217 typedef struct {
5218 GtkTextTag *tag;
5219 char *end;
5220 char *start;
5221 } PidginTextTagData;
5223 static PidginTextTagData *text_tag_data_new(GtkTextTag *tag)
5225 const char *start, *end;
5226 PidginTextTagData *ret = NULL;
5228 start = tag_to_html_start(tag);
5229 if (!start || !*start)
5230 return NULL;
5231 end = tag_to_html_end(tag);
5232 if (!end || !*end)
5233 return NULL;
5235 ret = g_new0(PidginTextTagData, 1);
5236 ret->start = g_strdup(start);
5237 ret->end = g_strdup(end);
5238 ret->tag = tag;
5239 return ret;
5242 static void text_tag_data_destroy(PidginTextTagData *data)
5244 g_free(data->start);
5245 g_free(data->end);
5246 g_free(data);
5249 static gboolean tag_ends_here(GtkTextTag *tag, GtkTextIter *iter, GtkTextIter *niter)
5251 return ((gtk_text_iter_has_tag(iter, GTK_TEXT_TAG(tag)) &&
5252 !gtk_text_iter_has_tag(niter, GTK_TEXT_TAG(tag))) ||
5253 gtk_text_iter_is_end(niter));
5256 /* Basic notion here: traverse through the text buffer one-by-one, non-character elements, such
5257 * as smileys and IM images are represented by the Unicode "unknown" character. Handle them. Else
5258 * check for tags that are toggled on, insert their html form, and push them on the queue. Then insert
5259 * the actual text. Then check for tags that are toggled off and insert them, after checking the queue.
5260 * Finally, replace <, >, &, and " with their HTML equivalent.
5262 char *gtk_imhtml_get_markup_range(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end)
5264 gunichar c;
5265 GtkTextIter iter, next_iter, non_neutral_iter;
5266 gboolean is_rtl_message = FALSE;
5267 GString *str = g_string_new("");
5268 GSList *tags, *sl;
5269 GQueue *q;
5270 GtkTextTag *tag;
5271 PidginTextTagData *tagdata;
5273 q = g_queue_new();
5275 gtk_text_iter_order(start, end);
5276 non_neutral_iter = next_iter = iter = *start;
5277 gtk_text_iter_forward_char(&next_iter);
5279 /* Bi-directional text support */
5280 /* Get to the first non-neutral character */
5281 #ifdef HAVE_PANGO14
5282 while ((PANGO_DIRECTION_NEUTRAL == pango_unichar_direction(gtk_text_iter_get_char(&non_neutral_iter)))
5283 && gtk_text_iter_forward_char(&non_neutral_iter));
5284 if (PANGO_DIRECTION_RTL == pango_unichar_direction(gtk_text_iter_get_char(&non_neutral_iter))) {
5285 is_rtl_message = TRUE;
5286 g_string_append(str, "<SPAN style=\"direction:rtl;text-align:right;\">");
5288 #endif
5290 /* First add the tags that are already in progress (we don't care about non-printing tags)*/
5291 tags = gtk_text_iter_get_tags(start);
5293 for (sl = tags; sl; sl = sl->next) {
5294 tag = sl->data;
5295 if (!gtk_text_iter_toggles_tag(start, GTK_TEXT_TAG(tag))) {
5296 PidginTextTagData *data = text_tag_data_new(tag);
5297 if (data) {
5298 g_string_append(str, data->start);
5299 g_queue_push_tail(q, data);
5303 g_slist_free(tags);
5305 while ((c = gtk_text_iter_get_char(&iter)) != 0 && !gtk_text_iter_equal(&iter, end)) {
5307 tags = gtk_text_iter_get_tags(&iter);
5309 for (sl = tags; sl; sl = sl->next) {
5310 tag = sl->data;
5311 if (gtk_text_iter_begins_tag(&iter, GTK_TEXT_TAG(tag))) {
5312 PidginTextTagData *data = text_tag_data_new(tag);
5313 if (data) {
5314 g_string_append(str, data->start);
5315 g_queue_push_tail(q, data);
5320 if (c == 0xFFFC) {
5321 GtkTextChildAnchor* anchor = gtk_text_iter_get_child_anchor(&iter);
5322 if (anchor) {
5323 char *text = g_object_get_data(G_OBJECT(anchor), "gtkimhtml_htmltext");
5324 if (text)
5325 str = g_string_append(str, text);
5327 } else if (c == '<') {
5328 str = g_string_append(str, "&lt;");
5329 } else if (c == '>') {
5330 str = g_string_append(str, "&gt;");
5331 } else if (c == '&') {
5332 str = g_string_append(str, "&amp;");
5333 } else if (c == '"') {
5334 str = g_string_append(str, "&quot;");
5335 } else if (c == '\n') {
5336 str = g_string_append(str, "<br>");
5337 } else {
5338 str = g_string_append_unichar(str, c);
5341 tags = g_slist_reverse(tags);
5342 for (sl = tags; sl; sl = sl->next) {
5343 tag = sl->data;
5344 /** don't worry about non-printing tags ending */
5345 if (tag_ends_here(tag, &iter, &next_iter) &&
5346 strlen(tag_to_html_end(tag)) > 0 &&
5347 strlen(tag_to_html_start(tag)) > 0) {
5349 PidginTextTagData *tmp;
5350 GQueue *r = g_queue_new();
5352 while ((tmp = g_queue_pop_tail(q)) && tmp->tag != tag) {
5353 g_string_append(str, tmp->end);
5354 if (!tag_ends_here(tmp->tag, &iter, &next_iter))
5355 g_queue_push_tail(r, tmp);
5356 else
5357 text_tag_data_destroy(tmp);
5360 if (tmp != NULL) {
5361 g_string_append(str, tmp->end);
5362 text_tag_data_destroy(tmp);
5364 #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 */
5365 else
5366 purple_debug_warning("gtkimhtml", "empty queue, more closing tags than open tags!\n");
5367 #endif
5369 while ((tmp = g_queue_pop_head(r))) {
5370 g_string_append(str, tmp->start);
5371 g_queue_push_tail(q, tmp);
5373 g_queue_free(r);
5377 g_slist_free(tags);
5378 gtk_text_iter_forward_char(&iter);
5379 gtk_text_iter_forward_char(&next_iter);
5382 while ((tagdata = g_queue_pop_tail(q))) {
5383 g_string_append(str, tagdata->end);
5384 text_tag_data_destroy(tagdata);
5387 /* Bi-directional text support - close tags */
5388 if (is_rtl_message)
5389 g_string_append(str, "</SPAN>");
5391 g_queue_free(q);
5392 return g_string_free(str, FALSE);
5395 void gtk_imhtml_close_tags(GtkIMHtml *imhtml, GtkTextIter *iter)
5397 if (imhtml->edit.bold)
5398 gtk_imhtml_toggle_bold(imhtml);
5400 if (imhtml->edit.italic)
5401 gtk_imhtml_toggle_italic(imhtml);
5403 if (imhtml->edit.underline)
5404 gtk_imhtml_toggle_underline(imhtml);
5406 if (imhtml->edit.strike)
5407 gtk_imhtml_toggle_strike(imhtml);
5409 if (imhtml->edit.forecolor)
5410 gtk_imhtml_toggle_forecolor(imhtml, NULL);
5412 if (imhtml->edit.backcolor)
5413 gtk_imhtml_toggle_backcolor(imhtml, NULL);
5415 if (imhtml->edit.fontface)
5416 gtk_imhtml_toggle_fontface(imhtml, NULL);
5418 imhtml->edit.fontsize = 0;
5420 if (imhtml->edit.link)
5421 gtk_imhtml_toggle_link(imhtml, NULL);
5424 char *gtk_imhtml_get_markup(GtkIMHtml *imhtml)
5426 GtkTextIter start, end;
5428 gtk_text_buffer_get_start_iter(imhtml->text_buffer, &start);
5429 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &end);
5430 return gtk_imhtml_get_markup_range(imhtml, &start, &end);
5433 char **gtk_imhtml_get_markup_lines(GtkIMHtml *imhtml)
5435 int i, j, lines;
5436 GtkTextIter start, end;
5437 char **ret;
5439 lines = gtk_text_buffer_get_line_count(imhtml->text_buffer);
5440 ret = g_new0(char *, lines + 1);
5441 gtk_text_buffer_get_start_iter(imhtml->text_buffer, &start);
5442 end = start;
5443 gtk_text_iter_forward_to_line_end(&end);
5445 for (i = 0, j = 0; i < lines; i++) {
5446 if (gtk_text_iter_get_char(&start) != '\n') {
5447 ret[j] = gtk_imhtml_get_markup_range(imhtml, &start, &end);
5448 if (ret[j] != NULL)
5449 j++;
5452 gtk_text_iter_forward_line(&start);
5453 end = start;
5454 gtk_text_iter_forward_to_line_end(&end);
5457 return ret;
5460 char *gtk_imhtml_get_text(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *stop)
5462 GString *str = g_string_new("");
5463 GtkTextIter iter, end;
5464 gunichar c;
5466 if (start == NULL)
5467 gtk_text_buffer_get_start_iter(imhtml->text_buffer, &iter);
5468 else
5469 iter = *start;
5471 if (stop == NULL)
5472 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &end);
5473 else
5474 end = *stop;
5476 gtk_text_iter_order(&iter, &end);
5478 while ((c = gtk_text_iter_get_char(&iter)) != 0 && !gtk_text_iter_equal(&iter, &end)) {
5479 if (c == 0xFFFC) {
5480 GtkTextChildAnchor* anchor;
5481 char *text = NULL;
5483 anchor = gtk_text_iter_get_child_anchor(&iter);
5484 if (anchor)
5485 text = g_object_get_data(G_OBJECT(anchor), "gtkimhtml_plaintext");
5486 if (text)
5487 str = g_string_append(str, text);
5488 } else {
5489 g_string_append_unichar(str, c);
5491 gtk_text_iter_forward_char(&iter);
5494 return g_string_free(str, FALSE);
5497 void gtk_imhtml_set_funcs(GtkIMHtml *imhtml, GtkIMHtmlFuncs *f)
5499 g_return_if_fail(imhtml != NULL);
5500 imhtml->funcs = f;
5503 void gtk_imhtml_setup_entry(GtkIMHtml *imhtml, PurpleConnectionFlags flags)
5505 GtkIMHtmlButtons buttons;
5507 if (flags & PURPLE_CONNECTION_HTML) {
5508 char color[8];
5509 GdkColor fg_color, bg_color;
5511 buttons = GTK_IMHTML_ALL;
5513 if (flags & PURPLE_CONNECTION_NO_BGCOLOR)
5514 buttons &= ~GTK_IMHTML_BACKCOLOR;
5515 if (flags & PURPLE_CONNECTION_NO_FONTSIZE)
5517 buttons &= ~GTK_IMHTML_GROW;
5518 buttons &= ~GTK_IMHTML_SHRINK;
5520 if (flags & PURPLE_CONNECTION_NO_URLDESC)
5521 buttons &= ~GTK_IMHTML_LINKDESC;
5523 gtk_imhtml_set_format_functions(imhtml, GTK_IMHTML_ALL);
5524 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_bold") != imhtml->edit.bold)
5525 gtk_imhtml_toggle_bold(imhtml);
5527 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_italic") != imhtml->edit.italic)
5528 gtk_imhtml_toggle_italic(imhtml);
5530 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_underline") != imhtml->edit.underline)
5531 gtk_imhtml_toggle_underline(imhtml);
5533 gtk_imhtml_toggle_fontface(imhtml,
5534 purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/font_face"));
5536 if (!(flags & PURPLE_CONNECTION_NO_FONTSIZE))
5538 int size = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/font_size");
5540 /* 3 is the default. */
5541 if (size != 3)
5542 gtk_imhtml_font_set_size(imhtml, size);
5545 if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"), "") != 0)
5547 gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"),
5548 &fg_color);
5549 g_snprintf(color, sizeof(color), "#%02x%02x%02x",
5550 fg_color.red / 256,
5551 fg_color.green / 256,
5552 fg_color.blue / 256);
5553 } else
5554 strcpy(color, "");
5556 gtk_imhtml_toggle_forecolor(imhtml, color);
5558 if(!(flags & PURPLE_CONNECTION_NO_BGCOLOR) &&
5559 strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"), "") != 0)
5561 gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"),
5562 &bg_color);
5563 g_snprintf(color, sizeof(color), "#%02x%02x%02x",
5564 bg_color.red / 256,
5565 bg_color.green / 256,
5566 bg_color.blue / 256);
5567 } else
5568 strcpy(color, "");
5570 gtk_imhtml_toggle_background(imhtml, color);
5572 if (flags & PURPLE_CONNECTION_FORMATTING_WBFO)
5573 gtk_imhtml_set_whole_buffer_formatting_only(imhtml, TRUE);
5574 else
5575 gtk_imhtml_set_whole_buffer_formatting_only(imhtml, FALSE);
5576 } else {
5577 buttons = GTK_IMHTML_SMILEY | GTK_IMHTML_IMAGE;
5578 imhtml_clear_formatting(imhtml);
5581 if (flags & PURPLE_CONNECTION_NO_IMAGES)
5582 buttons &= ~GTK_IMHTML_IMAGE;
5584 if (flags & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY)
5585 buttons |= GTK_IMHTML_CUSTOM_SMILEY;
5586 else
5587 buttons &= ~GTK_IMHTML_CUSTOM_SMILEY;
5589 gtk_imhtml_set_format_functions(imhtml, buttons);
5592 /*******
5593 * GtkIMHtmlSmiley functions
5594 *******/
5595 static void gtk_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data)
5597 GtkIMHtmlSmiley *smiley;
5599 smiley = (GtkIMHtmlSmiley *)user_data;
5600 smiley->icon = gdk_pixbuf_loader_get_animation(loader);
5602 if (smiley->icon)
5603 g_object_ref(G_OBJECT(smiley->icon));
5604 #ifdef DEBUG_CUSTOM_SMILEY
5605 purple_debug_info("custom-smiley", "gtk_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s'\n", smiley->icon, smiley->smile);
5606 #endif
5609 static void gtk_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data)
5611 GtkIMHtmlSmiley *smiley;
5612 GtkWidget *icon = NULL;
5613 GtkTextChildAnchor *anchor = NULL;
5614 GSList *current = NULL;
5616 smiley = (GtkIMHtmlSmiley *)user_data;
5617 if (!smiley->imhtml) {
5618 #ifdef DEBUG_CUSTOM_SMILEY
5619 purple_debug_error("custom-smiley", "gtk_custom_smiley_closed(): orphan smiley found: %p\n", smiley);
5620 #endif
5621 g_object_unref(G_OBJECT(loader));
5622 smiley->loader = NULL;
5623 return;
5626 for (current = smiley->anchors; current; current = g_slist_next(current)) {
5627 anchor = GTK_TEXT_CHILD_ANCHOR(current->data);
5628 if (gtk_text_child_anchor_get_deleted(anchor))
5629 icon = NULL;
5630 else
5631 icon = gtk_image_new_from_animation(smiley->icon);
5633 #ifdef DEBUG_CUSTOM_SMILEY
5634 purple_debug_info("custom-smiley", "gtk_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s'\n",
5635 icon, smiley->icon, smiley->smile);
5636 #endif
5637 if (icon) {
5638 GList *wids;
5639 gtk_widget_show(icon);
5641 wids = gtk_text_child_anchor_get_widgets(anchor);
5643 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", purple_unescape_html(smiley->smile), g_free);
5644 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley->smile), g_free);
5646 if (smiley->imhtml) {
5647 if (wids) {
5648 GList *children = gtk_container_get_children(GTK_CONTAINER(wids->data));
5649 g_list_foreach(children, (GFunc)gtk_widget_destroy, NULL);
5650 g_list_free(children);
5651 gtk_container_add(GTK_CONTAINER(wids->data), icon);
5652 } else
5653 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley->imhtml), icon, anchor);
5655 g_list_free(wids);
5657 g_object_unref(anchor);
5660 g_slist_free(smiley->anchors);
5661 smiley->anchors = NULL;
5663 g_object_unref(G_OBJECT(loader));
5664 smiley->loader = NULL;
5667 static void
5668 gtk_custom_smiley_size_prepared(GdkPixbufLoader *loader, gint width, gint height, gpointer data)
5670 #define CUSTOM_SMILEY_SIZE 96 /* XXX: Should this be a theme setting? */
5671 if (width <= CUSTOM_SMILEY_SIZE && height <= CUSTOM_SMILEY_SIZE)
5672 return;
5674 if (width >= height) {
5675 height = height * CUSTOM_SMILEY_SIZE / width;
5676 width = CUSTOM_SMILEY_SIZE;
5677 } else {
5678 width = width * CUSTOM_SMILEY_SIZE / height;
5679 height = CUSTOM_SMILEY_SIZE;
5682 gdk_pixbuf_loader_set_size(loader, width, height);
5685 void
5686 gtk_imhtml_smiley_reload(GtkIMHtmlSmiley *smiley)
5688 if (smiley->icon)
5689 g_object_unref(smiley->icon);
5690 if (smiley->loader)
5691 g_object_unref(smiley->loader); /* XXX: does this crash? */
5693 smiley->icon = NULL;
5694 smiley->loader = NULL;
5696 if (smiley->file) {
5697 /* We do not use the pixbuf loader for a smiley that can be loaded
5698 * from a file. (e.g., local custom smileys)
5700 return;
5703 smiley->loader = gdk_pixbuf_loader_new();
5705 g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(gtk_custom_smiley_allocated), smiley);
5706 g_signal_connect(smiley->loader, "closed", G_CALLBACK(gtk_custom_smiley_closed), smiley);
5707 g_signal_connect(smiley->loader, "size_prepared", G_CALLBACK(gtk_custom_smiley_size_prepared), smiley);
5710 GtkIMHtmlSmiley *gtk_imhtml_smiley_create(const char *file, const char *shortcut, gboolean hide,
5711 GtkIMHtmlSmileyFlags flags)
5713 GtkIMHtmlSmiley *smiley = g_new0(GtkIMHtmlSmiley, 1);
5714 smiley->file = g_strdup(file);
5715 smiley->smile = g_strdup(shortcut);
5716 smiley->hidden = hide;
5717 smiley->flags = flags;
5718 smiley->imhtml = NULL;
5719 gtk_imhtml_smiley_reload(smiley);
5720 return smiley;
5723 void gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley *smiley)
5725 gtk_imhtml_disassociate_smiley(smiley);
5726 g_free(smiley->smile);
5727 g_free(smiley->file);
5728 if (smiley->icon)
5729 g_object_unref(smiley->icon);
5730 if (smiley->loader)
5731 g_object_unref(smiley->loader);
5732 g_free(smiley->data);
5733 g_free(smiley);
5736 gboolean gtk_imhtml_class_register_protocol(const char *name,
5737 gboolean (*activate)(GtkIMHtml *imhtml, GtkIMHtmlLink *link),
5738 gboolean (*context_menu)(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu))
5740 GtkIMHtmlClass *klass;
5741 GtkIMHtmlProtocol *proto;
5743 g_return_val_if_fail(name, FALSE);
5745 klass = g_type_class_ref(GTK_TYPE_IMHTML);
5746 g_return_val_if_fail(klass, FALSE);
5748 if ((proto = imhtml_find_protocol(name, TRUE))) {
5749 if (activate) {
5750 return FALSE;
5752 klass->protocols = g_list_remove(klass->protocols, proto);
5753 g_free(proto->name);
5754 g_free(proto);
5755 return TRUE;
5756 } else if (!activate) {
5757 return FALSE;
5760 proto = g_new0(GtkIMHtmlProtocol, 1);
5761 proto->name = g_strdup(name);
5762 proto->length = strlen(name);
5763 proto->activate = activate;
5764 proto->context_menu = context_menu;
5765 klass->protocols = g_list_prepend(klass->protocols, proto);
5767 return TRUE;
5770 static void
5771 gtk_imhtml_activate_tag(GtkIMHtml *imhtml, GtkTextTag *tag)
5773 /* A link was clicked--we emit the "url_clicked" signal
5774 * with the URL as the argument */
5775 g_object_ref(G_OBJECT(tag));
5776 g_signal_emit(imhtml, signals[URL_CLICKED], 0, g_object_get_data(G_OBJECT(tag), "link_url"));
5777 g_object_unref(G_OBJECT(tag));
5778 g_object_set_data(G_OBJECT(tag), "visited", GINT_TO_POINTER(TRUE));
5779 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml), tag);
5782 gboolean gtk_imhtml_link_activate(GtkIMHtmlLink *link)
5784 g_return_val_if_fail(link, FALSE);
5786 if (link->tag) {
5787 gtk_imhtml_activate_tag(link->imhtml, link->tag);
5788 } else if (link->url) {
5789 g_signal_emit(link->imhtml, signals[URL_CLICKED], 0, link->url);
5790 } else
5791 return FALSE;
5792 return TRUE;
5795 const char *gtk_imhtml_link_get_url(GtkIMHtmlLink *link)
5797 return link->url;
5800 const GtkTextTag * gtk_imhtml_link_get_text_tag(GtkIMHtmlLink *link)
5802 return link->tag;
5805 static gboolean return_add_newline_cb(GtkWidget *widget, gpointer data)
5807 GtkTextBuffer *buffer;
5808 GtkTextMark *mark;
5809 GtkTextIter iter;
5811 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
5813 /* Delete any currently selected text */
5814 gtk_text_buffer_delete_selection(buffer, TRUE, TRUE);
5816 /* Insert a newline at the current cursor position */
5817 mark = gtk_text_buffer_get_insert(buffer);
5818 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
5819 gtk_imhtml_insert_html_at_iter(GTK_IMHTML(widget), "\n", 0, &iter);
5822 * If we just newlined ourselves past the end of the visible area
5823 * then scroll down so the cursor is in view.
5825 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(widget),
5826 gtk_text_buffer_get_insert(buffer),
5827 0, FALSE, 0.0, 0.0);
5829 return TRUE;
5833 * It's kind of a pain that we need this function and the above just
5834 * to reinstate the default GtkTextView behavior. It might be better
5835 * if GtkIMHtml didn't intercept the enter key and just required the
5836 * application to deal with it--it's really not much more work than it
5837 * is to connect to the current "message_send" signal.
5839 void gtk_imhtml_set_return_inserts_newline(GtkIMHtml *imhtml)
5841 g_signal_connect(G_OBJECT(imhtml), "message_send",
5842 G_CALLBACK(return_add_newline_cb), NULL);
5845 void gtk_imhtml_set_populate_primary_clipboard(GtkIMHtml *imhtml, gboolean populate)
5847 gulong signal_id;
5848 signal_id = g_signal_handler_find(imhtml->text_buffer,
5849 G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_UNBLOCKED, 0, 0, NULL,
5850 mark_set_so_update_selection_cb, NULL);
5851 if (populate) {
5852 if (!signal_id) {
5853 /* We didn't find an unblocked signal handler, which means there
5854 is a blocked handler. Now unblock it.
5855 This is necessary to avoid a mutex-lock when the debug message
5856 saying 'no handler is blocked' is printed in the debug window.
5857 -- sad
5859 g_signal_handlers_unblock_matched(imhtml->text_buffer,
5860 G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
5861 mark_set_so_update_selection_cb, NULL);
5863 } else {
5864 /* Block only if we found an unblocked handler */
5865 if (signal_id)
5866 g_signal_handler_block(imhtml->text_buffer, signal_id);