Mercurial > pidgin
diff gtk/gtkimhtml.c @ 14191:009db0b357b5
This is a hand-crafted commit to migrate across subversion revisions
16854:16861, due to some vagaries of the way the original renames were
done. Witness that monotone can do in one revision what svn had to
spread across several.
author | Ethan Blanton <elb@pidgin.im> |
---|---|
date | Sat, 16 Dec 2006 04:59:55 +0000 |
parents | |
children | cf8d25072151 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gtk/gtkimhtml.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,4810 @@ +/* + * @file gtkimhtml.c GTK+ IMHtml + * @ingroup gtkui + * + * gaim + * + * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include "debug.h" +#include "util.h" +#include "gtkimhtml.h" +#include "gtksourceiter.h" +#include <gtk/gtk.h> +#include <glib/gerror.h> +#include <gdk/gdkkeysyms.h> +#include <string.h> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#ifdef HAVE_LANGINFO_CODESET +#include <langinfo.h> +#include <locale.h> +#endif +#ifdef _WIN32 +#include <gdk/gdkwin32.h> +#include <windows.h> +#endif + +#ifdef ENABLE_NLS +# include <libintl.h> +# define _(x) gettext(x) +# ifdef gettext_noop +# define N_(String) gettext_noop (String) +# else +# define N_(String) (String) +# endif +#else +# define N_(String) (String) +# define _(x) (x) +#endif + +#include <pango/pango-font.h> + +/* GTK+ < 2.4.x hack, see gtkgaim.h for details. */ +#if (!GTK_CHECK_VERSION(2,4,0)) +#define GTK_WRAP_WORD_CHAR GTK_WRAP_WORD +#endif + +#define TOOLTIP_TIMEOUT 500 + +/* GTK+ 2.0 hack */ +#if (!GTK_CHECK_VERSION(2,2,0)) +#define gtk_widget_get_clipboard(x, y) gtk_clipboard_get(y) +#endif + +static GtkTextViewClass *parent_class = NULL; + +struct scalable_data { + GtkIMHtmlScalable *scalable; + GtkTextMark *mark; +}; + + +struct im_image_data { + int id; + GtkTextMark *mark; +}; + +static gboolean +gtk_text_view_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time); + +static void preinsert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml); +static void insert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml); +static void delete_cb(GtkTextBuffer *buffer, GtkTextIter *iter, GtkTextIter *end, GtkIMHtml *imhtml); +static void insert_ca_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextChildAnchor *arg2, gpointer user_data); +static void gtk_imhtml_apply_tags_on_insert(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end); +static gboolean gtk_imhtml_is_amp_escape (const gchar *string, gchar **replace, gint *length); +void gtk_imhtml_close_tags(GtkIMHtml *imhtml, GtkTextIter *iter); +static void gtk_imhtml_link_drop_cb(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer user_data); +static void gtk_imhtml_link_drag_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y, GtkSelectionData *sd, guint info, guint t, GtkIMHtml *imhtml); +static void mark_set_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextMark *mark, GtkIMHtml *imhtml); +static void hijack_menu_cb(GtkIMHtml *imhtml, GtkMenu *menu, gpointer data); +static void paste_received_cb (GtkClipboard *clipboard, GtkSelectionData *selection_data, gpointer data); +static void paste_plaintext_received_cb (GtkClipboard *clipboard, const gchar *text, gpointer data); +static void imhtml_paste_insert(GtkIMHtml *imhtml, const char *text, gboolean plaintext); +static void imhtml_toggle_bold(GtkIMHtml *imhtml); +static void imhtml_toggle_italic(GtkIMHtml *imhtml); +static void imhtml_toggle_strike(GtkIMHtml *imhtml); +static void imhtml_toggle_underline(GtkIMHtml *imhtml); +static void imhtml_font_grow(GtkIMHtml *imhtml); +static void imhtml_font_shrink(GtkIMHtml *imhtml); +static void imhtml_clear_formatting(GtkIMHtml *imhtml); + +/* POINT_SIZE converts from AIM font sizes to a point size scale factor. */ +#define MAX_FONT_SIZE 7 +#define POINT_SIZE(x) (_point_sizes [MIN ((x > 0 ? x : 1), MAX_FONT_SIZE) - 1]) +static gdouble _point_sizes [] = { .69444444, .8333333, 1, 1.2, 1.44, 1.728, 2.0736}; + +enum { + TARGET_HTML, + TARGET_UTF8_STRING, + TARGET_COMPOUND_TEXT, + TARGET_STRING, + TARGET_TEXT +}; + +enum { + URL_CLICKED, + BUTTONS_UPDATE, + TOGGLE_FORMAT, + CLEAR_FORMAT, + UPDATE_FORMAT, + MESSAGE_SEND, + LAST_SIGNAL +}; +static guint signals [LAST_SIGNAL] = { 0 }; + +static GtkTargetEntry selection_targets[] = { + { "text/html", 0, TARGET_HTML }, + { "UTF8_STRING", 0, TARGET_UTF8_STRING }, + { "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT }, + { "STRING", 0, TARGET_STRING }, + { "TEXT", 0, TARGET_TEXT}}; + +static GtkTargetEntry link_drag_drop_targets[] = { + GTK_IMHTML_DND_TARGETS +}; + +#ifdef _WIN32 +/* Win32 clipboard format value, and functions to convert back and + * forth between HTML and the clipboard format. + */ +static UINT win_html_fmt; + +static gchar * +clipboard_win32_to_html(char *clipboard) { + const char *header; + const char *begin, *end; + gint start = 0; + gint finish = 0; + gchar *html; + gchar **split; + int clipboard_length = 0; + +#if 0 /* Debugging for Windows clipboard */ + FILE *fd; + + gaim_debug_info("imhtml clipboard", "from clipboard: %s\n", clipboard); + + fd = g_fopen("e:\\gaimcb.txt", "wb"); + fprintf(fd, "%s", clipboard); + fclose(fd); +#endif + + clipboard_length = strlen(clipboard); + + if (!(header = strstr(clipboard, "StartFragment:")) || (header - clipboard) >= clipboard_length) + return NULL; + sscanf(header, "StartFragment:%d", &start); + + if (!(header = strstr(clipboard, "EndFragment:")) || (header - clipboard) >= clipboard_length) + return NULL; + sscanf(header, "EndFragment:%d", &finish); + + if (finish > clipboard_length) + finish = clipboard_length; + + if (start > finish) + start = finish; + + begin = clipboard + start; + + end = clipboard + finish; + + html = g_strndup(begin, end - begin); + + /* any newlines in the string will now be \r\n, so we need to strip out the \r */ + split = g_strsplit(html, "\r\n", 0); + g_free(html); + html = g_strjoinv("\n", split); + g_strfreev(split); + + html = g_strstrip(html); + +#if 0 /* Debugging for Windows clipboard */ + gaim_debug_info("imhtml clipboard", "HTML fragment: '%s'\n", html); +#endif + + return html; +} + +static gchar * +clipboard_html_to_win32(char *html) { + int length; + GString *clipboard; + gchar *tmp; + + if (html == NULL) + return NULL; + + length = strlen(html); + clipboard = g_string_new ("Version:1.0\r\n"); + g_string_append(clipboard, "StartHTML:0000000105\r\n"); + tmp = g_strdup_printf("EndHTML:%010d\r\n", 147 + length); + g_string_append(clipboard, tmp); + g_free(tmp); + g_string_append(clipboard, "StartFragment:0000000127\r\n"); + tmp = g_strdup_printf("EndFragment:%010d\r\n", 127 + length); + g_string_append(clipboard, tmp); + g_free(tmp); + g_string_append(clipboard, "<!--StartFragment-->\r\n"); + g_string_append(clipboard, html); + g_string_append(clipboard, "\r\n<!--EndFragment-->"); + + return g_string_free(clipboard, FALSE); +} + +static void clipboard_copy_html_win32(GtkIMHtml *imhtml) { + gchar *clipboard = clipboard_html_to_win32(imhtml->clipboard_html_string); + if (clipboard != NULL) { + HWND hwnd = GDK_WINDOW_HWND(GTK_WIDGET(imhtml)->window); + if (OpenClipboard(hwnd)) { + if (EmptyClipboard()) { + gint length = strlen(clipboard); + HGLOBAL hdata = GlobalAlloc(GMEM_MOVEABLE, length); + if (hdata != NULL) { + gchar *buffer = GlobalLock(hdata); + memcpy(buffer, clipboard, length); + GlobalUnlock(hdata); + + if (SetClipboardData(win_html_fmt, hdata) == NULL) { + gchar *err_msg = + g_win32_error_message(GetLastError()); + gaim_debug_info("html clipboard", + "Unable to set clipboard data: %s\n", + err_msg ? err_msg : "Unknown Error"); + g_free(err_msg); + } + } + } + CloseClipboard(); + } + g_free(clipboard); + } +} + +static gboolean clipboard_paste_html_win32(GtkIMHtml *imhtml) { + gboolean pasted = FALSE; + + if (gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml)) + && IsClipboardFormatAvailable(win_html_fmt)) { + gboolean error_reading_clipboard = FALSE; + HWND hwnd = GDK_WINDOW_HWND(GTK_WIDGET(imhtml)->window); + + if (OpenClipboard(hwnd)) { + HGLOBAL hdata = GetClipboardData(win_html_fmt); + if (hdata == NULL) { + error_reading_clipboard = TRUE; + } else { + char *buffer = GlobalLock(hdata); + if (buffer == NULL) { + error_reading_clipboard = TRUE; + } else { + char *text = clipboard_win32_to_html( + buffer); + imhtml_paste_insert(imhtml, text, + FALSE); + g_free(text); + pasted = TRUE; + } + GlobalUnlock(hdata); + } + + CloseClipboard(); + + } else { + error_reading_clipboard = TRUE; + } + + if (error_reading_clipboard) { + gchar *err_msg = g_win32_error_message(GetLastError()); + gaim_debug_info("html clipboard", + "Unable to read clipboard data: %s\n", + err_msg ? err_msg : "Unknown Error"); + g_free(err_msg); + } + } + + return pasted; +} +#endif + +static GtkSmileyTree* +gtk_smiley_tree_new () +{ + return g_new0 (GtkSmileyTree, 1); +} + +static void +gtk_smiley_tree_insert (GtkSmileyTree *tree, + GtkIMHtmlSmiley *smiley) +{ + GtkSmileyTree *t = tree; + const gchar *x = smiley->smile; + + if (!(*x)) + return; + + do { + gchar *pos; + gint index; + + if (!t->values) + t->values = g_string_new (""); + + pos = strchr (t->values->str, *x); + if (!pos) { + t->values = g_string_append_c (t->values, *x); + index = t->values->len - 1; + t->children = g_realloc (t->children, t->values->len * sizeof (GtkSmileyTree *)); + t->children [index] = g_new0 (GtkSmileyTree, 1); + } else + index = GPOINTER_TO_INT(pos) - GPOINTER_TO_INT(t->values->str); + + t = t->children [index]; + + x++; + } while (*x); + + t->image = smiley; +} + + +static void +gtk_smiley_tree_destroy (GtkSmileyTree *tree) +{ + GSList *list = g_slist_prepend (NULL, tree); + + while (list) { + GtkSmileyTree *t = list->data; + gsize i; + list = g_slist_remove(list, t); + if (t && t->values) { + for (i = 0; i < t->values->len; i++) + list = g_slist_prepend (list, t->children [i]); + g_string_free (t->values, TRUE); + g_free (t->children); + } + g_free (t); + } +} + +static void gtk_size_allocate_cb(GtkIMHtml *widget, GtkAllocation *alloc, gpointer user_data) +{ + GdkRectangle rect; + int xminus; + + gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget), &rect); + if(widget->old_rect.width != rect.width || widget->old_rect.height != rect.height){ + GList *iter = GTK_IMHTML(widget)->scalables; + + xminus = gtk_text_view_get_left_margin(GTK_TEXT_VIEW(widget)) + + gtk_text_view_get_right_margin(GTK_TEXT_VIEW(widget)); + + while(iter){ + struct scalable_data *sd = iter->data; + GtkIMHtmlScalable *scale = GTK_IMHTML_SCALABLE(sd->scalable); + scale->scale(scale, rect.width - xminus, rect.height); + + iter = iter->next; + } + } + + widget->old_rect = rect; + return; +} + +static gint +gtk_imhtml_tip_paint (GtkIMHtml *imhtml) +{ + PangoLayout *layout; + + g_return_val_if_fail(GTK_IS_IMHTML(imhtml), FALSE); + + layout = gtk_widget_create_pango_layout(imhtml->tip_window, imhtml->tip); + + gtk_paint_flat_box (imhtml->tip_window->style, imhtml->tip_window->window, + GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL, imhtml->tip_window, + "tooltip", 0, 0, -1, -1); + + gtk_paint_layout (imhtml->tip_window->style, imhtml->tip_window->window, GTK_STATE_NORMAL, + FALSE, NULL, imhtml->tip_window, NULL, 4, 4, layout); + + g_object_unref(layout); + return FALSE; +} + +static gint +gtk_imhtml_tip (gpointer data) +{ + GtkIMHtml *imhtml = data; + PangoFontMetrics *font_metrics; + PangoLayout *layout; + PangoFont *font; + + gint gap, x, y, h, w, scr_w, baseline_skip; + + g_return_val_if_fail(GTK_IS_IMHTML(imhtml), FALSE); + + if (!imhtml->tip || !GTK_WIDGET_DRAWABLE (GTK_WIDGET(imhtml))) { + imhtml->tip_timer = 0; + return FALSE; + } + + if (imhtml->tip_window){ + gtk_widget_destroy (imhtml->tip_window); + imhtml->tip_window = NULL; + } + + imhtml->tip_timer = 0; + imhtml->tip_window = gtk_window_new (GTK_WINDOW_POPUP); + gtk_widget_set_app_paintable (imhtml->tip_window, TRUE); + gtk_window_set_resizable (GTK_WINDOW (imhtml->tip_window), FALSE); + gtk_widget_set_name (imhtml->tip_window, "gtk-tooltips"); + g_signal_connect_swapped (G_OBJECT (imhtml->tip_window), "expose_event", + G_CALLBACK (gtk_imhtml_tip_paint), imhtml); + + gtk_widget_ensure_style (imhtml->tip_window); + layout = gtk_widget_create_pango_layout(imhtml->tip_window, imhtml->tip); + font = pango_context_load_font(pango_layout_get_context(layout), + imhtml->tip_window->style->font_desc); + + if (font == NULL) { + char *tmp = pango_font_description_to_string( + imhtml->tip_window->style->font_desc); + + gaim_debug(GAIM_DEBUG_ERROR, "gtk_imhtml_tip", + "pango_context_load_font() couldn't load font: '%s'\n", + tmp); + g_free(tmp); + + return FALSE; + } + + font_metrics = pango_font_get_metrics(font, NULL); + + pango_layout_get_pixel_size(layout, &scr_w, NULL); + gap = PANGO_PIXELS((pango_font_metrics_get_ascent(font_metrics) + + pango_font_metrics_get_descent(font_metrics))/ 4); + + if (gap < 2) + gap = 2; + baseline_skip = PANGO_PIXELS(pango_font_metrics_get_ascent(font_metrics) + + pango_font_metrics_get_descent(font_metrics)); + w = 8 + scr_w; + h = 8 + baseline_skip; + + gdk_window_get_pointer (NULL, &x, &y, NULL); + if (GTK_WIDGET_NO_WINDOW (GTK_WIDGET(imhtml))) + y += GTK_WIDGET(imhtml)->allocation.y; + + scr_w = gdk_screen_width(); + + x -= ((w >> 1) + 4); + + if ((x + w) > scr_w) + x -= (x + w) - scr_w; + else if (x < 0) + x = 0; + + y = y + PANGO_PIXELS(pango_font_metrics_get_ascent(font_metrics) + + pango_font_metrics_get_descent(font_metrics)); + + gtk_widget_set_size_request (imhtml->tip_window, w, h); + gtk_widget_show (imhtml->tip_window); + gtk_window_move (GTK_WINDOW(imhtml->tip_window), x, y); + + pango_font_metrics_unref(font_metrics); + g_object_unref(layout); + + return FALSE; +} + +static gboolean +gtk_motion_event_notify(GtkWidget *imhtml, GdkEventMotion *event, gpointer data) +{ + GtkTextIter iter; + GdkWindow *win = event->window; + int x, y; + char *tip = NULL; + GSList *tags = NULL, *templist = NULL; + GdkColor *norm, *pre; + GtkTextTag *tag = NULL, *oldprelit_tag; + + oldprelit_tag = GTK_IMHTML(imhtml)->prelit_tag; + + gdk_window_get_pointer(GTK_WIDGET(imhtml)->window, NULL, NULL, NULL); + gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(imhtml), GTK_TEXT_WINDOW_WIDGET, + event->x, event->y, &x, &y); + gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml), &iter, x, y); + tags = gtk_text_iter_get_tags(&iter); + + templist = tags; + while (templist) { + tag = templist->data; + tip = g_object_get_data(G_OBJECT(tag), "link_url"); + if (tip) + break; + templist = templist->next; + } + + if (tip) { + gtk_widget_style_get(GTK_WIDGET(imhtml), "hyperlink-prelight-color", &pre, NULL); + GTK_IMHTML(imhtml)->prelit_tag = tag; + if (tag != oldprelit_tag) { + if (pre) + g_object_set(G_OBJECT(tag), "foreground-gdk", pre, NULL); + else + g_object_set(G_OBJECT(tag), "foreground", "#70a0ff", NULL); + } + } else { + GTK_IMHTML(imhtml)->prelit_tag = NULL; + } + + if ((oldprelit_tag != NULL) && (GTK_IMHTML(imhtml)->prelit_tag != oldprelit_tag)) { + gtk_widget_style_get(GTK_WIDGET(imhtml), "hyperlink-color", &norm, NULL); + if (norm) + g_object_set(G_OBJECT(oldprelit_tag), "foreground-gdk", norm, NULL); + else + g_object_set(G_OBJECT(oldprelit_tag), "foreground", "blue", NULL); + } + + if (GTK_IMHTML(imhtml)->tip) { + if ((tip == GTK_IMHTML(imhtml)->tip)) { + return FALSE; + } + /* We've left the cell. Remove the timeout and create a new one below */ + if (GTK_IMHTML(imhtml)->tip_window) { + gtk_widget_destroy(GTK_IMHTML(imhtml)->tip_window); + GTK_IMHTML(imhtml)->tip_window = NULL; + } + if (GTK_IMHTML(imhtml)->editable) + gdk_window_set_cursor(win, GTK_IMHTML(imhtml)->text_cursor); + else + gdk_window_set_cursor(win, GTK_IMHTML(imhtml)->arrow_cursor); + if (GTK_IMHTML(imhtml)->tip_timer) + g_source_remove(GTK_IMHTML(imhtml)->tip_timer); + GTK_IMHTML(imhtml)->tip_timer = 0; + } + + if (tip){ + if (!GTK_IMHTML(imhtml)->editable) + gdk_window_set_cursor(win, GTK_IMHTML(imhtml)->hand_cursor); + GTK_IMHTML(imhtml)->tip_timer = g_timeout_add (TOOLTIP_TIMEOUT, + gtk_imhtml_tip, imhtml); + } + + GTK_IMHTML(imhtml)->tip = tip; + g_slist_free(tags); + return FALSE; +} + +static gboolean +gtk_enter_event_notify(GtkWidget *imhtml, GdkEventCrossing *event, gpointer data) +{ + if (GTK_IMHTML(imhtml)->editable) + gdk_window_set_cursor( + gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml), + GTK_TEXT_WINDOW_TEXT), + GTK_IMHTML(imhtml)->text_cursor); + else + gdk_window_set_cursor( + gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml), + GTK_TEXT_WINDOW_TEXT), + GTK_IMHTML(imhtml)->arrow_cursor); + + /* propagate the event normally */ + return FALSE; +} + +static gboolean +gtk_leave_event_notify(GtkWidget *imhtml, GdkEventCrossing *event, gpointer data) +{ + /* when leaving the widget, clear any current & pending tooltips and restore the cursor */ + if (GTK_IMHTML(imhtml)->prelit_tag) { + GdkColor *norm; + gtk_widget_style_get(GTK_WIDGET(imhtml), "hyperlink-color", &norm, NULL); + if (norm) + g_object_set(G_OBJECT(GTK_IMHTML(imhtml)->prelit_tag), "foreground-gdk", norm, NULL); + else + g_object_set(G_OBJECT(GTK_IMHTML(imhtml)->prelit_tag), "foreground", "blue", NULL); + GTK_IMHTML(imhtml)->prelit_tag = NULL; + } + + if (GTK_IMHTML(imhtml)->tip_window) { + gtk_widget_destroy(GTK_IMHTML(imhtml)->tip_window); + GTK_IMHTML(imhtml)->tip_window = NULL; + } + if (GTK_IMHTML(imhtml)->tip_timer) { + g_source_remove(GTK_IMHTML(imhtml)->tip_timer); + GTK_IMHTML(imhtml)->tip_timer = 0; + } + gdk_window_set_cursor( + gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml), + GTK_TEXT_WINDOW_TEXT), NULL); + + /* propagate the event normally */ + return FALSE; +} + +#if (!GTK_CHECK_VERSION(2,2,0)) +/* + * XXX - This should be removed eventually. + * + * This function exists to work around a gross bug in GtkTextView. + * Basically, we short circuit ctrl+a and ctrl+end because they make + * el program go boom. + * + * It's supposed to be fixed in gtk2.2. You can view the bug report at + * http://bugzilla.gnome.org/show_bug.cgi?id=107939 + */ +static gboolean +gtk_key_pressed_cb(GtkIMHtml *imhtml, GdkEventKey *event, gpointer data) +{ + if (event->state & GDK_CONTROL_MASK) { + switch (event->keyval) { + case 'a': + case GDK_Home: + case GDK_End: + return TRUE; + } + } + return FALSE; +} +#endif /* !(GTK+ >= 2.2.0) */ + +static gint +gtk_imhtml_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + GtkTextIter start, end, cur; + int buf_x, buf_y; + GdkRectangle visible_rect; + GdkGC *gc = gdk_gc_new(GDK_DRAWABLE(event->window)); + GdkColor gcolor; + + gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget), &visible_rect); + gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget), + GTK_TEXT_WINDOW_TEXT, + visible_rect.x, + visible_rect.y, + &visible_rect.x, + &visible_rect.y); + + gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT, + event->area.x, event->area.y, &buf_x, &buf_y); + + if (GTK_IMHTML(widget)->editable || GTK_IMHTML(widget)->wbfo) { + + if (GTK_IMHTML(widget)->edit.background) { + gdk_color_parse(GTK_IMHTML(widget)->edit.background, &gcolor); + gdk_gc_set_rgb_fg_color(gc, &gcolor); + } else { + gdk_gc_set_rgb_fg_color(gc, &(widget->style->base[GTK_WIDGET_STATE(widget)])); + } + + gdk_draw_rectangle(event->window, + gc, + TRUE, + visible_rect.x, visible_rect.y, visible_rect.width, visible_rect.height); + gdk_gc_unref(gc); + + if (GTK_WIDGET_CLASS (parent_class)->expose_event) + return (* GTK_WIDGET_CLASS (parent_class)->expose_event) + (widget, event); + return FALSE; + + } + + gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &start, buf_x, buf_y); + gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &end, + buf_x + event->area.width, buf_y + event->area.height); + + + + cur = start; + + while (gtk_text_iter_in_range(&cur, &start, &end)) { + GSList *tags = gtk_text_iter_get_tags(&cur); + GSList *l; + + for (l = tags; l; l = l->next) { + GtkTextTag *tag = l->data; + GdkRectangle rect; + GdkRectangle tag_area; + const char *color; + + if (strncmp(tag->name, "BACKGROUND ", 11)) + continue; + + if (gtk_text_iter_ends_tag(&cur, tag)) + continue; + + gtk_text_view_get_iter_location(GTK_TEXT_VIEW(widget), &cur, &tag_area); + gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget), + GTK_TEXT_WINDOW_TEXT, + tag_area.x, + tag_area.y, + &tag_area.x, + &tag_area.y); + rect.x = visible_rect.x; + rect.y = tag_area.y; + rect.width = visible_rect.width; + + do + gtk_text_iter_forward_to_tag_toggle(&cur, tag); + while (!gtk_text_iter_is_end(&cur) && gtk_text_iter_begins_tag(&cur, tag)); + + gtk_text_view_get_iter_location(GTK_TEXT_VIEW(widget), &cur, &tag_area); + gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget), + GTK_TEXT_WINDOW_TEXT, + tag_area.x, + tag_area.y, + &tag_area.x, + &tag_area.y); + + + rect.height = tag_area.y + tag_area.height - rect.y + + gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(widget)); + + color = tag->name + 11; + + if (!gdk_color_parse(color, &gcolor)) { + gchar tmp[8]; + tmp[0] = '#'; + strncpy(&tmp[1], color, 7); + tmp[7] = '\0'; + if (!gdk_color_parse(tmp, &gcolor)) + gdk_color_parse("white", &gcolor); + } + gdk_gc_set_rgb_fg_color(gc, &gcolor); + + gdk_draw_rectangle(event->window, + gc, + TRUE, + rect.x, rect.y, rect.width, rect.height); + gtk_text_iter_backward_char(&cur); /* go back one, in case the end is the begining is the end + * note that above, we always moved cur ahead by at least + * one character */ + break; + } + + g_slist_free(tags); + + /* loop until another tag begins, or no tag begins */ + while (gtk_text_iter_forward_to_tag_toggle(&cur, NULL) && + !gtk_text_iter_is_end(&cur) && + !gtk_text_iter_begins_tag(&cur, NULL)); + } + + gdk_gc_unref(gc); + + if (GTK_WIDGET_CLASS (parent_class)->expose_event) + return (* GTK_WIDGET_CLASS (parent_class)->expose_event) + (widget, event); + + return FALSE; +} + + +static void paste_unformatted_cb(GtkMenuItem *menu, GtkIMHtml *imhtml) +{ + GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD); + + gtk_clipboard_request_text(clipboard, paste_plaintext_received_cb, imhtml); + +} + +static void clear_formatting_cb(GtkMenuItem *menu, GtkIMHtml *imhtml) +{ + gtk_imhtml_clear_formatting(imhtml); +} + +static void hijack_menu_cb(GtkIMHtml *imhtml, GtkMenu *menu, gpointer data) +{ + GtkWidget *menuitem; + + menuitem = gtk_menu_item_new_with_mnemonic(_("Paste as Plain _Text")); + gtk_widget_show(menuitem); + gtk_widget_set_sensitive(menuitem, + (imhtml->editable && + gtk_clipboard_wait_is_text_available( + gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD)))); + /* put it after "Paste" */ + gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menuitem, 3); + + g_signal_connect(G_OBJECT(menuitem), "activate", + G_CALLBACK(paste_unformatted_cb), imhtml); + + menuitem = gtk_menu_item_new_with_mnemonic(_("_Reset formatting")); + gtk_widget_show(menuitem); + gtk_widget_set_sensitive(menuitem, imhtml->editable); + /* put it after Delete */ + gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menuitem, 5); + + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(clear_formatting_cb), imhtml); +} + +static char * +ucs2_order(gboolean swap) +{ + gboolean be; + + be = G_BYTE_ORDER == G_BIG_ENDIAN; + be = swap ? be : !be; + + if (be) + return "UCS-2BE"; + else + return "UCS-2LE"; + +} + +/* Convert from UCS-2 to UTF-8, stripping the BOM if one is present.*/ +static gchar * +ucs2_to_utf8_with_bom_check(gchar *data, guint len) { + char *fromcode = NULL; + GError *error = NULL; + guint16 c; + gchar *utf8_ret; + + /* + * Unicode Techinical Report 20 + * ( http://www.unicode.org/unicode/reports/tr20/ ) says to treat an + * initial 0xfeff (ZWNBSP) as a byte order indicator so that is + * what we do. If there is no indicator assume it is in the default + * order + */ + + memcpy(&c, data, 2); + switch (c) { + case 0xfeff: + case 0xfffe: + fromcode = ucs2_order(c == 0xfeff); + data += 2; + len -= 2; + break; + default: + fromcode = "UCS-2"; + break; + } + + utf8_ret = g_convert(data, len, "UTF-8", fromcode, NULL, NULL, &error); + + if (error) { + gaim_debug_warning("gtkimhtml", "g_convert error: %s\n", error->message); + g_error_free(error); + } + return utf8_ret; +} + + +static void gtk_imhtml_clipboard_get(GtkClipboard *clipboard, GtkSelectionData *selection_data, guint info, GtkIMHtml *imhtml) { + char *text; + gboolean primary; + GtkTextIter start, end; + GtkTextMark *sel = gtk_text_buffer_get_selection_bound(imhtml->text_buffer); + GtkTextMark *ins = gtk_text_buffer_get_insert(imhtml->text_buffer); + + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &start, sel); + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &end, ins); + primary = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY) == clipboard; + + if (info == TARGET_HTML) { + gsize len; + char *selection; + GString *str = g_string_new(NULL); + if (primary) { + text = gtk_imhtml_get_markup_range(imhtml, &start, &end); + } else + text = imhtml->clipboard_html_string; + + /* Mozilla asks that we start our text/html with the Unicode byte order mark */ + str = g_string_append_unichar(str, 0xfeff); + str = g_string_append(str, text); + str = g_string_append_unichar(str, 0x0000); + selection = g_convert(str->str, str->len, "UCS-2", "UTF-8", NULL, &len, NULL); + gtk_selection_data_set(selection_data, gdk_atom_intern("text/html", FALSE), 16, (const guchar *)selection, len); + g_string_free(str, TRUE); + g_free(selection); + } else { + if (primary) { + text = gtk_imhtml_get_text(imhtml, &start, &end); + } else + text = imhtml->clipboard_text_string; + gtk_selection_data_set_text(selection_data, text, strlen(text)); + } + if (primary) /* This was allocated here */ + g_free(text); + } + +static void gtk_imhtml_primary_clipboard_clear(GtkClipboard *clipboard, GtkIMHtml *imhtml) +{ + GtkTextIter insert; + GtkTextIter selection_bound; + + gtk_text_buffer_get_iter_at_mark (imhtml->text_buffer, &insert, + gtk_text_buffer_get_mark (imhtml->text_buffer, "insert")); + gtk_text_buffer_get_iter_at_mark (imhtml->text_buffer, &selection_bound, + gtk_text_buffer_get_mark (imhtml->text_buffer, "selection_bound")); + + if (!gtk_text_iter_equal (&insert, &selection_bound)) + gtk_text_buffer_move_mark (imhtml->text_buffer, + gtk_text_buffer_get_mark (imhtml->text_buffer, "selection_bound"), + &insert); +} + +static void copy_clipboard_cb(GtkIMHtml *imhtml, gpointer unused) +{ + GtkTextIter start, end; + GtkTextMark *sel = gtk_text_buffer_get_selection_bound(imhtml->text_buffer); + GtkTextMark *ins = gtk_text_buffer_get_insert(imhtml->text_buffer); + + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &start, sel); + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &end, ins); + + gtk_clipboard_set_with_owner(gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD), + selection_targets, sizeof(selection_targets) / sizeof(GtkTargetEntry), + (GtkClipboardGetFunc)gtk_imhtml_clipboard_get, + (GtkClipboardClearFunc)NULL, G_OBJECT(imhtml)); + + if (imhtml->clipboard_html_string) { + g_free(imhtml->clipboard_html_string); + g_free(imhtml->clipboard_text_string); + } + + imhtml->clipboard_html_string = gtk_imhtml_get_markup_range(imhtml, &start, &end); + imhtml->clipboard_text_string = gtk_imhtml_get_text(imhtml, &start, &end); + +#ifdef _WIN32 + /* We're going to still copy plain text, but let's toss the "HTML Format" + we need into the windows clipboard now as well. */ + clipboard_copy_html_win32(imhtml); +#endif + + g_signal_stop_emission_by_name(imhtml, "copy-clipboard"); +} + +static void cut_clipboard_cb(GtkIMHtml *imhtml, gpointer unused) +{ + GtkTextIter start, end; + GtkTextMark *sel = gtk_text_buffer_get_selection_bound(imhtml->text_buffer); + GtkTextMark *ins = gtk_text_buffer_get_insert(imhtml->text_buffer); + + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &start, sel); + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &end, ins); + + gtk_clipboard_set_with_owner(gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD), + selection_targets, sizeof(selection_targets) / sizeof(GtkTargetEntry), + (GtkClipboardGetFunc)gtk_imhtml_clipboard_get, + (GtkClipboardClearFunc)NULL, G_OBJECT(imhtml)); + + if (imhtml->clipboard_html_string) { + g_free(imhtml->clipboard_html_string); + g_free(imhtml->clipboard_text_string); + } + + imhtml->clipboard_html_string = gtk_imhtml_get_markup_range(imhtml, &start, &end); + imhtml->clipboard_text_string = gtk_imhtml_get_text(imhtml, &start, &end); + +#ifdef _WIN32 + /* We're going to still copy plain text, but let's toss the "HTML Format" + we need into the windows clipboard now as well. */ + clipboard_copy_html_win32(imhtml); +#endif + + if (imhtml->editable) + gtk_text_buffer_delete_selection(imhtml->text_buffer, FALSE, FALSE); + g_signal_stop_emission_by_name(imhtml, "cut-clipboard"); +} + +static void imhtml_paste_insert(GtkIMHtml *imhtml, const char *text, gboolean plaintext) +{ + GtkTextIter iter; + GtkIMHtmlOptions flags = plaintext ? 0 : (GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_COMMENTS); + + if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, NULL, NULL)) + gtk_text_buffer_delete_selection(imhtml->text_buffer, TRUE, TRUE); + + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, gtk_text_buffer_get_insert(imhtml->text_buffer)); + if (!imhtml->wbfo && !plaintext) + gtk_imhtml_close_tags(imhtml, &iter); + + gtk_imhtml_insert_html_at_iter(imhtml, text, flags, &iter); + if (!imhtml->wbfo && !plaintext) + gtk_imhtml_close_tags(imhtml, &iter); + gtk_text_buffer_move_mark_by_name(imhtml->text_buffer, "insert", &iter); + gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(imhtml), gtk_text_buffer_get_insert(imhtml->text_buffer), + 0, FALSE, 0.0, 0.0); +} + +static void paste_plaintext_received_cb (GtkClipboard *clipboard, const gchar *text, gpointer data) +{ + char *tmp; + + if (text == NULL) + return; + + tmp = g_markup_escape_text(text, -1); + imhtml_paste_insert(data, tmp, TRUE); + g_free(tmp); +} + +static void paste_received_cb (GtkClipboard *clipboard, GtkSelectionData *selection_data, gpointer data) +{ + char *text; + GtkIMHtml *imhtml = data; + + if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml))) + return; + + if (selection_data->length < 0) { + gtk_clipboard_request_text(clipboard, paste_plaintext_received_cb, imhtml); + return; + } else { +#if 0 + /* Here's some debug code, for figuring out what sent to us over the clipboard. */ + { + int i; + + gaim_debug_misc("gtkimhtml", "In paste_received_cb():\n\tformat = %d, length = %d\n\t", + selection_data->format, selection_data->length); + + for (i = 0; i < (/*(selection_data->format / 8) **/ selection_data->length); i++) { + if ((i % 70) == 0) + printf("\n\t"); + if (selection_data->data[i] == '\0') + printf("."); + else + printf("%c", selection_data->data[i]); + } + printf("\n"); + } +#endif + text = g_malloc(selection_data->length); + memcpy(text, selection_data->data, selection_data->length); + } + + if (selection_data->length >= 2 && + (*(guint16 *)text == 0xfeff || *(guint16 *)text == 0xfffe)) { + /* This is UCS-2 */ + char *utf8 = ucs2_to_utf8_with_bom_check(text, selection_data->length); + g_free(text); + text = utf8; + if (!text) { + gaim_debug_warning("gtkimhtml", "g_convert from UCS-2 failed in paste_received_cb\n"); + return; + } + } + + if (!(*text) || !g_utf8_validate(text, -1, NULL)) { + gaim_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in paste_received_cb\n"); + g_free(text); + return; + } + + imhtml_paste_insert(imhtml, text, FALSE); + g_free(text); +} + +static void paste_clipboard_cb(GtkIMHtml *imhtml, gpointer blah) +{ +#ifdef _WIN32 + /* If we're on windows, let's see if we can get data from the HTML Format + clipboard before we try to paste from the GTK buffer */ + if (!clipboard_paste_html_win32(imhtml)) { +#endif + GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD); + gtk_clipboard_request_contents(clipboard, gdk_atom_intern("text/html", FALSE), + paste_received_cb, imhtml); +#ifdef _WIN32 + } +#endif + g_signal_stop_emission_by_name(imhtml, "paste-clipboard"); +} + +static void imhtml_realized_remove_primary(GtkIMHtml *imhtml, gpointer unused) +{ + gtk_text_buffer_remove_selection_clipboard(GTK_IMHTML(imhtml)->text_buffer, + gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY)); + +} + +static void imhtml_destroy_add_primary(GtkIMHtml *imhtml, gpointer unused) +{ + gtk_text_buffer_add_selection_clipboard(GTK_IMHTML(imhtml)->text_buffer, + gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY)); +} + +static void mark_set_so_update_selection_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextMark *mark, GtkIMHtml *imhtml) +{ + if (gtk_text_buffer_get_selection_bounds(buffer, NULL, NULL)) { + gtk_clipboard_set_with_owner(gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY), + selection_targets, sizeof(selection_targets) / sizeof(GtkTargetEntry), + (GtkClipboardGetFunc)gtk_imhtml_clipboard_get, + (GtkClipboardClearFunc)gtk_imhtml_primary_clipboard_clear, G_OBJECT(imhtml)); + } +} + +static gboolean gtk_imhtml_button_press_event(GtkIMHtml *imhtml, GdkEventButton *event, gpointer unused) +{ + if (event->button == 2) { + int x, y; + GtkTextIter iter; + GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY); + + if (!imhtml->editable) + return FALSE; + + gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(imhtml), + GTK_TEXT_WINDOW_TEXT, + event->x, + event->y, + &x, + &y); + gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml), &iter, x, y); + gtk_text_buffer_place_cursor(imhtml->text_buffer, &iter); + + gtk_clipboard_request_contents(clipboard, gdk_atom_intern("text/html", FALSE), + paste_received_cb, imhtml); + + return TRUE; + } + + return FALSE; +} + +static gboolean imhtml_message_send(GtkIMHtml *imhtml) +{ + return FALSE; +} + +static void imhtml_toggle_format(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons) +{ + /* since this function is the handler for the formatting keystrokes, + we need to check here that the formatting attempted is permitted */ + buttons &= imhtml->format_functions; + + switch (buttons) { + case GTK_IMHTML_BOLD: + imhtml_toggle_bold(imhtml); + break; + case GTK_IMHTML_ITALIC: + imhtml_toggle_italic(imhtml); + break; + case GTK_IMHTML_UNDERLINE: + imhtml_toggle_underline(imhtml); + break; + case GTK_IMHTML_STRIKE: + imhtml_toggle_strike(imhtml); + break; + case GTK_IMHTML_SHRINK: + imhtml_font_shrink(imhtml); + break; + case GTK_IMHTML_GROW: + imhtml_font_grow(imhtml); + break; + default: + break; + } +} + +static void +gtk_imhtml_finalize (GObject *object) +{ + GtkIMHtml *imhtml = GTK_IMHTML(object); + GList *scalables; + GSList *l; + + if (imhtml->scroll_src) + g_source_remove(imhtml->scroll_src); + if (imhtml->scroll_time) + g_timer_destroy(imhtml->scroll_time); + + g_hash_table_destroy(imhtml->smiley_data); + gtk_smiley_tree_destroy(imhtml->default_smilies); + gdk_cursor_unref(imhtml->hand_cursor); + gdk_cursor_unref(imhtml->arrow_cursor); + gdk_cursor_unref(imhtml->text_cursor); + + if(imhtml->tip_window){ + gtk_widget_destroy(imhtml->tip_window); + } + if(imhtml->tip_timer) + gtk_timeout_remove(imhtml->tip_timer); + + for(scalables = imhtml->scalables; scalables; scalables = scalables->next) { + struct scalable_data *sd = scalables->data; + GtkIMHtmlScalable *scale = GTK_IMHTML_SCALABLE(sd->scalable); + scale->free(scale); + g_free(sd); + } + + for (l = imhtml->im_images; l; l = l->next) { + struct im_image_data *img_data = l->data; + if (imhtml->funcs->image_unref) + imhtml->funcs->image_unref(img_data->id); + g_free(img_data); + } + + if (imhtml->clipboard_text_string) { + g_free(imhtml->clipboard_text_string); + g_free(imhtml->clipboard_html_string); + } + + g_list_free(imhtml->scalables); + g_slist_free(imhtml->im_images); + g_free(imhtml->protocol_name); + g_free(imhtml->search_string); + G_OBJECT_CLASS(parent_class)->finalize (object); +} + +/* Boring GTK+ stuff */ +static void gtk_imhtml_class_init (GtkIMHtmlClass *klass) +{ + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + GtkObjectClass *object_class; + GtkBindingSet *binding_set; + GObjectClass *gobject_class; + object_class = (GtkObjectClass*) klass; + gobject_class = (GObjectClass*) klass; + parent_class = gtk_type_class(GTK_TYPE_TEXT_VIEW); + signals[URL_CLICKED] = g_signal_new("url_clicked", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(GtkIMHtmlClass, url_clicked), + NULL, + 0, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + signals[BUTTONS_UPDATE] = g_signal_new("format_buttons_update", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(GtkIMHtmlClass, buttons_update), + NULL, + 0, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, + G_TYPE_INT); + signals[TOGGLE_FORMAT] = g_signal_new("format_function_toggle", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(GtkIMHtmlClass, toggle_format), + NULL, + 0, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, + G_TYPE_INT); + signals[CLEAR_FORMAT] = g_signal_new("format_function_clear", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(GtkIMHtmlClass, clear_format), + NULL, + 0, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[UPDATE_FORMAT] = g_signal_new("format_function_update", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(GtkIMHtmlClass, update_format), + NULL, + 0, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[MESSAGE_SEND] = g_signal_new("message_send", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(GtkIMHtmlClass, message_send), + NULL, + 0, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + klass->toggle_format = imhtml_toggle_format; + klass->message_send = imhtml_message_send; + klass->clear_format = imhtml_clear_formatting; + + gobject_class->finalize = gtk_imhtml_finalize; + widget_class->drag_motion = gtk_text_view_drag_motion; + widget_class->expose_event = gtk_imhtml_expose_event; + gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("hyperlink-color", + _("Hyperlink color"), + _("Color to draw hyperlinks."), + GDK_TYPE_COLOR, G_PARAM_READABLE)); + gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("hyperlink-prelight-color", + _("Hyperlink prelight color"), + _("Color to draw hyperlinks when mouse is over them."), + GDK_TYPE_COLOR, G_PARAM_READABLE)); + + binding_set = gtk_binding_set_by_class (parent_class); + gtk_binding_entry_add_signal (binding_set, GDK_b, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_BOLD); + gtk_binding_entry_add_signal (binding_set, GDK_i, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_ITALIC); + gtk_binding_entry_add_signal (binding_set, GDK_u, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_UNDERLINE); + gtk_binding_entry_add_signal (binding_set, GDK_plus, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_GROW); + gtk_binding_entry_add_signal (binding_set, GDK_equal, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_GROW); + gtk_binding_entry_add_signal (binding_set, GDK_minus, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_SHRINK); + binding_set = gtk_binding_set_by_class(klass); + gtk_binding_entry_add_signal (binding_set, GDK_r, GDK_CONTROL_MASK, "format_function_clear", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0, "message_send", 0); + gtk_binding_entry_add_signal (binding_set, GDK_Return, 0, "message_send", 0); +} + +static void gtk_imhtml_init (GtkIMHtml *imhtml) +{ + GtkTextIter iter; + imhtml->text_buffer = gtk_text_buffer_new(NULL); + gtk_text_buffer_get_end_iter (imhtml->text_buffer, &iter); + gtk_text_view_set_buffer(GTK_TEXT_VIEW(imhtml), imhtml->text_buffer); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml), GTK_WRAP_WORD_CHAR); + gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(imhtml), 5); + gtk_text_view_set_left_margin(GTK_TEXT_VIEW(imhtml), 2); + gtk_text_view_set_right_margin(GTK_TEXT_VIEW(imhtml), 2); + /*gtk_text_view_set_indent(GTK_TEXT_VIEW(imhtml), -15);*/ + /*gtk_text_view_set_justification(GTK_TEXT_VIEW(imhtml), GTK_JUSTIFY_FILL);*/ + + /* These tags will be used often and can be reused--we create them on init and then apply them by name + * other tags (color, size, face, etc.) will have to be created and applied dynamically + * Note that even though we created SUB, SUP, and PRE tags here, we don't really + * apply them anywhere yet. */ + gtk_text_buffer_create_tag(imhtml->text_buffer, "BOLD", "weight", PANGO_WEIGHT_BOLD, NULL); + gtk_text_buffer_create_tag(imhtml->text_buffer, "ITALICS", "style", PANGO_STYLE_ITALIC, NULL); + gtk_text_buffer_create_tag(imhtml->text_buffer, "UNDERLINE", "underline", PANGO_UNDERLINE_SINGLE, NULL); + gtk_text_buffer_create_tag(imhtml->text_buffer, "STRIKE", "strikethrough", TRUE, NULL); + gtk_text_buffer_create_tag(imhtml->text_buffer, "SUB", "rise", -5000, NULL); + gtk_text_buffer_create_tag(imhtml->text_buffer, "SUP", "rise", 5000, NULL); + gtk_text_buffer_create_tag(imhtml->text_buffer, "PRE", "family", "Monospace", NULL); + gtk_text_buffer_create_tag(imhtml->text_buffer, "search", "background", "#22ff00", "weight", "bold", NULL); + + /* When hovering over a link, we show the hand cursor--elsewhere we show the plain ol' pointer cursor */ + imhtml->hand_cursor = gdk_cursor_new (GDK_HAND2); + imhtml->arrow_cursor = gdk_cursor_new (GDK_LEFT_PTR); + imhtml->text_cursor = gdk_cursor_new (GDK_XTERM); + + imhtml->show_comments = TRUE; + + imhtml->smiley_data = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, (GDestroyNotify)gtk_smiley_tree_destroy); + imhtml->default_smilies = gtk_smiley_tree_new(); + + g_signal_connect(G_OBJECT(imhtml), "size-allocate", G_CALLBACK(gtk_size_allocate_cb), NULL); + g_signal_connect(G_OBJECT(imhtml), "motion-notify-event", G_CALLBACK(gtk_motion_event_notify), NULL); + g_signal_connect(G_OBJECT(imhtml), "leave-notify-event", G_CALLBACK(gtk_leave_event_notify), NULL); + g_signal_connect(G_OBJECT(imhtml), "enter-notify-event", G_CALLBACK(gtk_enter_event_notify), NULL); +#if (!GTK_CHECK_VERSION(2,2,0)) + /* See the comment for gtk_key_pressed_cb */ + g_signal_connect(G_OBJECT(imhtml), "key_press_event", G_CALLBACK(gtk_key_pressed_cb), NULL); +#endif + g_signal_connect(G_OBJECT(imhtml), "button_press_event", G_CALLBACK(gtk_imhtml_button_press_event), NULL); + g_signal_connect(G_OBJECT(imhtml->text_buffer), "insert-text", G_CALLBACK(preinsert_cb), imhtml); + g_signal_connect(G_OBJECT(imhtml->text_buffer), "delete_range", G_CALLBACK(delete_cb), imhtml); + g_signal_connect_after(G_OBJECT(imhtml->text_buffer), "insert-text", G_CALLBACK(insert_cb), imhtml); + g_signal_connect_after(G_OBJECT(imhtml->text_buffer), "insert-child-anchor", G_CALLBACK(insert_ca_cb), imhtml); + gtk_drag_dest_set(GTK_WIDGET(imhtml), 0, + link_drag_drop_targets, sizeof(link_drag_drop_targets) / sizeof(GtkTargetEntry), + GDK_ACTION_COPY); + g_signal_connect(G_OBJECT(imhtml), "drag_data_received", G_CALLBACK(gtk_imhtml_link_drag_rcv_cb), imhtml); + g_signal_connect(G_OBJECT(imhtml), "drag_drop", G_CALLBACK(gtk_imhtml_link_drop_cb), imhtml); + + g_signal_connect(G_OBJECT(imhtml), "copy-clipboard", G_CALLBACK(copy_clipboard_cb), NULL); + g_signal_connect(G_OBJECT(imhtml), "cut-clipboard", G_CALLBACK(cut_clipboard_cb), NULL); + g_signal_connect(G_OBJECT(imhtml), "paste-clipboard", G_CALLBACK(paste_clipboard_cb), NULL); + g_signal_connect_after(G_OBJECT(imhtml), "realize", G_CALLBACK(imhtml_realized_remove_primary), NULL); + g_signal_connect(G_OBJECT(imhtml), "unrealize", G_CALLBACK(imhtml_destroy_add_primary), NULL); + + g_signal_connect_after(G_OBJECT(GTK_IMHTML(imhtml)->text_buffer), "mark-set", + G_CALLBACK(mark_set_so_update_selection_cb), imhtml); + + gtk_widget_add_events(GTK_WIDGET(imhtml), + GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK); + + imhtml->clipboard_text_string = NULL; + imhtml->clipboard_html_string = NULL; + + imhtml->tip = NULL; + imhtml->tip_timer = 0; + imhtml->tip_window = NULL; + + imhtml->edit.bold = FALSE; + imhtml->edit.italic = FALSE; + imhtml->edit.underline = FALSE; + imhtml->edit.forecolor = NULL; + imhtml->edit.backcolor = NULL; + imhtml->edit.fontface = NULL; + imhtml->edit.fontsize = 0; + imhtml->edit.link = NULL; + + + imhtml->scalables = NULL; + + gtk_imhtml_set_editable(imhtml, FALSE); + g_signal_connect(G_OBJECT(imhtml), "populate-popup", + G_CALLBACK(hijack_menu_cb), NULL); + +#ifdef _WIN32 + /* Register HTML Format as desired clipboard format */ + win_html_fmt = RegisterClipboardFormat("HTML Format"); +#endif +} + +GtkWidget *gtk_imhtml_new(void *a, void *b) +{ + return GTK_WIDGET(g_object_new(gtk_imhtml_get_type(), NULL)); +} + +GType gtk_imhtml_get_type() +{ + static GType imhtml_type = 0; + + if (!imhtml_type) { + static const GTypeInfo imhtml_info = { + sizeof(GtkIMHtmlClass), + NULL, + NULL, + (GClassInitFunc) gtk_imhtml_class_init, + NULL, + NULL, + sizeof (GtkIMHtml), + 0, + (GInstanceInitFunc) gtk_imhtml_init, + NULL + }; + + imhtml_type = g_type_register_static(gtk_text_view_get_type(), + "GtkIMHtml", &imhtml_info, 0); + } + + return imhtml_type; +} + +struct url_data { + GObject *object; + gchar *url; +}; + +static void url_data_destroy(gpointer mydata) +{ + struct url_data *data = mydata; + g_object_unref(data->object); + g_free(data->url); + g_free(data); +} + +static void url_open(GtkWidget *w, struct url_data *data) { + if(!data) return; + g_signal_emit(data->object, signals[URL_CLICKED], 0, data->url); + +} + +static void url_copy(GtkWidget *w, gchar *url) { + GtkClipboard *clipboard; + + clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_PRIMARY); + gtk_clipboard_set_text(clipboard, url, -1); + + clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text(clipboard, url, -1); +} + +/* The callback for an event on a link tag. */ +static gboolean tag_event(GtkTextTag *tag, GObject *imhtml, GdkEvent *event, GtkTextIter *arg2, gpointer unused) { + GdkEventButton *event_button = (GdkEventButton *) event; + if (GTK_IMHTML(imhtml)->editable) + return FALSE; + if (event->type == GDK_BUTTON_RELEASE) { + if ((event_button->button == 1) || (event_button->button == 2)) { + GtkTextIter start, end; + /* we shouldn't open a URL if the user has selected something: */ + if (gtk_text_buffer_get_selection_bounds( + gtk_text_iter_get_buffer(arg2), &start, &end)) + return FALSE; + + /* A link was clicked--we emit the "url_clicked" signal + * with the URL as the argument */ + g_object_ref(G_OBJECT(tag)); + g_signal_emit(imhtml, signals[URL_CLICKED], 0, g_object_get_data(G_OBJECT(tag), "link_url")); + g_object_unref(G_OBJECT(tag)); + return FALSE; + } else if(event_button->button == 3) { + GtkWidget *img, *item, *menu; + struct url_data *tempdata = g_new(struct url_data, 1); + tempdata->object = g_object_ref(imhtml); + tempdata->url = g_strdup(g_object_get_data(G_OBJECT(tag), "link_url")); + + /* Don't want the tooltip around if user right-clicked on link */ + if (GTK_IMHTML(imhtml)->tip_window) { + gtk_widget_destroy(GTK_IMHTML(imhtml)->tip_window); + GTK_IMHTML(imhtml)->tip_window = NULL; + } + if (GTK_IMHTML(imhtml)->tip_timer) { + g_source_remove(GTK_IMHTML(imhtml)->tip_timer); + GTK_IMHTML(imhtml)->tip_timer = 0; + } + if (GTK_IMHTML(imhtml)->editable) + gdk_window_set_cursor(event_button->window, GTK_IMHTML(imhtml)->text_cursor); + else + gdk_window_set_cursor(event_button->window, GTK_IMHTML(imhtml)->arrow_cursor); + menu = gtk_menu_new(); + g_object_set_data_full(G_OBJECT(menu), "x-imhtml-url-data", tempdata, url_data_destroy); + + /* buttons and such */ + + if (!strncmp(tempdata->url, "mailto:", 7)) + { + /* Copy E-Mail Address */ + img = gtk_image_new_from_stock(GTK_STOCK_COPY, + GTK_ICON_SIZE_MENU); + item = gtk_image_menu_item_new_with_mnemonic( + _("_Copy E-Mail Address")); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(url_copy), tempdata->url + 7); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + } + else + { + /* Open Link in Browser */ + img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, + GTK_ICON_SIZE_MENU); + item = gtk_image_menu_item_new_with_mnemonic( + _("_Open Link in Browser")); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(url_open), tempdata); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + + /* Copy Link Location */ + img = gtk_image_new_from_stock(GTK_STOCK_COPY, + GTK_ICON_SIZE_MENU); + item = gtk_image_menu_item_new_with_mnemonic( + _("_Copy Link Location")); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(url_copy), tempdata->url); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + } + + + gtk_widget_show_all(menu); + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, + event_button->button, event_button->time); + + return TRUE; + } + } + if(event->type == GDK_BUTTON_PRESS && event_button->button == 3) + return TRUE; /* Clicking the right mouse button on a link shouldn't + be caught by the regular GtkTextView menu */ + else + return FALSE; /* Let clicks go through if we didn't catch anything */ +} + +static gboolean +gtk_text_view_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time) +{ + GdkDragAction suggested_action = 0; + + if (gtk_drag_dest_find_target (widget, context, NULL) == GDK_NONE) { + /* can't accept any of the offered targets */ + } else { + GtkWidget *source_widget; + suggested_action = context->suggested_action; + source_widget = gtk_drag_get_source_widget (context); + if (source_widget == widget) { + /* Default to MOVE, unless the user has + * pressed ctrl or alt to affect available actions + */ + if ((context->actions & GDK_ACTION_MOVE) != 0) + suggested_action = GDK_ACTION_MOVE; + } + } + + gdk_drag_status (context, suggested_action, time); + + /* TRUE return means don't propagate the drag motion to parent + * widgets that may also be drop sites. + */ + return TRUE; +} + +static void +gtk_imhtml_link_drop_cb(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer user_data) +{ + GdkAtom target = gtk_drag_dest_find_target (widget, context, NULL); + + if (target != GDK_NONE) + gtk_drag_get_data (widget, context, target, time); + else + gtk_drag_finish (context, FALSE, FALSE, time); + + return; +} + +static void +gtk_imhtml_link_drag_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y, + GtkSelectionData *sd, guint info, guint t, GtkIMHtml *imhtml) +{ + gchar **links; + gchar *link; + char *text = (char *)sd->data; + GtkTextMark *mark = gtk_text_buffer_get_insert(imhtml->text_buffer); + GtkTextIter iter; + gint i = 0; + + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, mark); + + if(gtk_imhtml_get_editable(imhtml) && sd->data){ + switch (info) { + case GTK_IMHTML_DRAG_URL: + /* TODO: Is it really ok to change sd->data...? */ + gaim_str_strip_char((char *)sd->data, '\r'); + + links = g_strsplit((char *)sd->data, "\n", 0); + while((link = links[i]) != NULL){ + if(gaim_str_has_prefix(link, "http://") || + gaim_str_has_prefix(link, "https://") || + gaim_str_has_prefix(link, "ftp://")) + { + gchar *label; + + if(links[i + 1]) + i++; + + label = links[i]; + + gtk_imhtml_insert_link(imhtml, mark, link, label); + } else if (link=='\0') { + /* Ignore blank lines */ + } else { + /* Special reasons, aka images being put in via other tag, etc. */ + /* ... don't pretend we handled it if we didn't */ + gtk_drag_finish(dc, FALSE, FALSE, t); + g_strfreev(links); + return; + } + + i++; + } + g_strfreev(links); + break; + case GTK_IMHTML_DRAG_HTML: + { + char *utf8 = NULL; + /* Ewww. This is all because mozilla thinks that text/html is 'for internal use only.' + * as explained by this comment in gtkhtml: + * + * FIXME This hack decides the charset of the selection. It seems that + * mozilla/netscape alway use ucs2 for text/html + * and openoffice.org seems to always use utf8 so we try to validate + * the string as utf8 and if that fails we assume it is ucs2 + * + * See also the comment on text/html here: + * http://mail.gnome.org/archives/gtk-devel-list/2001-September/msg00114.html + */ + if (sd->length >= 2 && !g_utf8_validate(text, sd->length - 1, NULL)) { + utf8 = ucs2_to_utf8_with_bom_check(text, sd->length); + + if (!utf8) { + gaim_debug_warning("gtkimhtml", "g_convert from UCS-2 failed in drag_rcv_cb\n"); + return; + } + } else if (!(*text) || !g_utf8_validate(text, -1, NULL)) { + gaim_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in drag_rcv_cb\n"); + return; + } + + gtk_imhtml_insert_html_at_iter(imhtml, utf8 ? utf8 : text, 0, &iter); + g_free(utf8); + break; + } + case GTK_IMHTML_DRAG_TEXT: + if (!(*text) || !g_utf8_validate(text, -1, NULL)) { + gaim_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in drag_rcv_cb\n"); + return; + } else { + char *tmp = g_markup_escape_text(text, -1); + gtk_imhtml_insert_html_at_iter(imhtml, tmp, 0, &iter); + g_free(tmp); + } + break; + default: + gtk_drag_finish(dc, FALSE, FALSE, t); + return; + } + gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t); + } else { + gtk_drag_finish(dc, FALSE, FALSE, t); + } +} + +/* this isn't used yet +static void gtk_smiley_tree_remove (GtkSmileyTree *tree, + GtkIMHtmlSmiley *smiley) +{ + GtkSmileyTree *t = tree; + const gchar *x = smiley->smile; + gint len = 0; + + while (*x) { + gchar *pos; + + if (!t->values) + return; + + pos = strchr (t->values->str, *x); + if (pos) + t = t->children [(int) pos - (int) t->values->str]; + else + return; + + x++; len++; + } + + if (t->image) { + t->image = NULL; + } +} +*/ + + +static gint +gtk_smiley_tree_lookup (GtkSmileyTree *tree, + const gchar *text) +{ + GtkSmileyTree *t = tree; + const gchar *x = text; + gint len = 0; + gchar *amp; + gint alen; + + while (*x) { + gchar *pos; + + if (!t->values) + break; + + if(*x == '&' && gtk_imhtml_is_amp_escape(x, &, &alen)) { + gboolean matched = TRUE; + /* Make sure all chars of the unescaped value match */ + while (*(amp + 1)) { + pos = strchr (t->values->str, *amp); + if (pos) + t = t->children [GPOINTER_TO_INT(pos) - GPOINTER_TO_INT(t->values->str)]; + else { + matched = FALSE; + break; + } + amp++; + } + if (!matched) + break; + + pos = strchr (t->values->str, *amp); + } + else if (*x == '<') /* Because we're all WYSIWYG now, a '<' + * char should only appear as the start of a tag. Perhaps a safer (but costlier) + * check would be to call gtk_imhtml_is_tag on it */ + break; + else { + alen = 1; + pos = strchr (t->values->str, *x); + } + + if (pos) + t = t->children [GPOINTER_TO_INT(pos) - GPOINTER_TO_INT(t->values->str)]; + else + break; + + x += alen; + len += alen; + } + + if (t->image) + return len; + + return 0; +} + +void +gtk_imhtml_associate_smiley (GtkIMHtml *imhtml, + const gchar *sml, + GtkIMHtmlSmiley *smiley) +{ + GtkSmileyTree *tree; + g_return_if_fail (imhtml != NULL); + g_return_if_fail (GTK_IS_IMHTML (imhtml)); + + if (sml == NULL) + tree = imhtml->default_smilies; + else if (!(tree = g_hash_table_lookup(imhtml->smiley_data, sml))) { + tree = gtk_smiley_tree_new(); + g_hash_table_insert(imhtml->smiley_data, g_strdup(sml), tree); + } + + smiley->imhtml = imhtml; + + gtk_smiley_tree_insert (tree, smiley); +} + +static gboolean +gtk_imhtml_is_smiley (GtkIMHtml *imhtml, + GSList *fonts, + const gchar *text, + gint *len) +{ + GtkSmileyTree *tree; + GtkIMHtmlFontDetail *font; + char *sml = NULL; + + if (fonts) { + font = fonts->data; + sml = font->sml; + } + + if (!sml) + sml = imhtml->protocol_name; + + if (!sml || !(tree = g_hash_table_lookup(imhtml->smiley_data, sml))) + tree = imhtml->default_smilies; + + if (tree == NULL) + return FALSE; + + *len = gtk_smiley_tree_lookup (tree, text); + return (*len > 0); +} + +GtkIMHtmlSmiley * +gtk_imhtml_smiley_get(GtkIMHtml *imhtml, + const gchar *sml, + const gchar *text) +{ + GtkSmileyTree *t; + const gchar *x = text; + if (sml == NULL) + t = imhtml->default_smilies; + else + t = g_hash_table_lookup(imhtml->smiley_data, sml); + + + if (t == NULL) + return sml ? gtk_imhtml_smiley_get(imhtml, NULL, text) : NULL; + + while (*x) { + gchar *pos; + + if (!t->values) { + return sml ? gtk_imhtml_smiley_get(imhtml, NULL, text) : NULL; + } + + pos = strchr (t->values->str, *x); + if (pos) { + t = t->children [GPOINTER_TO_INT(pos) - GPOINTER_TO_INT(t->values->str)]; + } else { + return sml ? gtk_imhtml_smiley_get(imhtml, NULL, text) : NULL; + } + x++; + } + + return t->image; +} + +static GdkPixbufAnimation * +gtk_smiley_tree_image (GtkIMHtml *imhtml, + const gchar *sml, + const gchar *text) +{ + + GtkIMHtmlSmiley *smiley; + + smiley = gtk_imhtml_smiley_get(imhtml,sml,text); + + if (!smiley) + return NULL; + + if (!smiley->icon && smiley->file) { + smiley->icon = gdk_pixbuf_animation_new_from_file(smiley->file, NULL); + } else if (!smiley->icon && smiley->loader) { + smiley->icon = gdk_pixbuf_loader_get_animation(smiley->loader); + if (smiley->icon) + g_object_ref(G_OBJECT(smiley->icon)); + } + + return smiley->icon; +} + +#define VALID_TAG(x) if (!g_ascii_strncasecmp (string, x ">", strlen (x ">"))) { \ + *tag = g_strndup (string, strlen (x)); \ + *len = strlen (x) + 1; \ + return TRUE; \ + } \ + (*type)++ + +#define VALID_OPT_TAG(x) if (!g_ascii_strncasecmp (string, x " ", strlen (x " "))) { \ + const gchar *c = string + strlen (x " "); \ + gchar e = '"'; \ + gboolean quote = FALSE; \ + while (*c) { \ + if (*c == '"' || *c == '\'') { \ + if (quote && (*c == e)) \ + quote = !quote; \ + else if (!quote) { \ + quote = !quote; \ + e = *c; \ + } \ + } else if (!quote && (*c == '>')) \ + break; \ + c++; \ + } \ + if (*c) { \ + *tag = g_strndup (string, c - string); \ + *len = c - string + 1; \ + return TRUE; \ + } \ + } \ + (*type)++ + + +static gboolean +gtk_imhtml_is_amp_escape (const gchar *string, + gchar **replace, + gint *length) +{ + static char buf[7]; + g_return_val_if_fail (string != NULL, FALSE); + g_return_val_if_fail (replace != NULL, FALSE); + g_return_val_if_fail (length != NULL, FALSE); + + if (!g_ascii_strncasecmp (string, "&", 5)) { + *replace = "&"; + *length = 5; + } else if (!g_ascii_strncasecmp (string, "<", 4)) { + *replace = "<"; + *length = 4; + } else if (!g_ascii_strncasecmp (string, ">", 4)) { + *replace = ">"; + *length = 4; + } else if (!g_ascii_strncasecmp (string, " ", 6)) { + *replace = " "; + *length = 6; + } else if (!g_ascii_strncasecmp (string, "©", 6)) { + *replace = "©"; + *length = 6; + } else if (!g_ascii_strncasecmp (string, """, 6)) { + *replace = "\""; + *length = 6; + } else if (!g_ascii_strncasecmp (string, "®", 5)) { + *replace = "®"; + *length = 5; + } else if (!g_ascii_strncasecmp (string, "'", 6)) { + *replace = "\'"; + *length = 6; + } else if (*(string + 1) == '#') { + guint pound = 0; + if ((sscanf (string, "&#%u;", £) == 1) && pound != 0) { + int buflen; + if (*(string + 3 + (gint)log10 (pound)) != ';') + return FALSE; + buflen = g_unichar_to_utf8((gunichar)pound, buf); + buf[buflen] = '\0'; + *replace = buf; + *length = 2; + while (isdigit ((gint) string [*length])) (*length)++; + if (string [*length] == ';') (*length)++; + } else { + return FALSE; + } + } else { + return FALSE; + } + + return TRUE; +} + +static gboolean +gtk_imhtml_is_tag (const gchar *string, + gchar **tag, + gint *len, + gint *type) +{ + char *close; + *type = 1; + + + if (!(close = strchr (string, '>'))) + return FALSE; + + VALID_TAG ("B"); + VALID_TAG ("BOLD"); + VALID_TAG ("/B"); + VALID_TAG ("/BOLD"); + VALID_TAG ("I"); + VALID_TAG ("ITALIC"); + VALID_TAG ("/I"); + VALID_TAG ("/ITALIC"); + VALID_TAG ("U"); + VALID_TAG ("UNDERLINE"); + VALID_TAG ("/U"); + VALID_TAG ("/UNDERLINE"); + VALID_TAG ("S"); + VALID_TAG ("STRIKE"); + VALID_TAG ("/S"); + VALID_TAG ("/STRIKE"); + VALID_TAG ("SUB"); + VALID_TAG ("/SUB"); + VALID_TAG ("SUP"); + VALID_TAG ("/SUP"); + VALID_TAG ("PRE"); + VALID_TAG ("/PRE"); + VALID_TAG ("TITLE"); + VALID_TAG ("/TITLE"); + VALID_TAG ("BR"); + VALID_TAG ("HR"); + VALID_TAG ("/FONT"); + VALID_TAG ("/A"); + VALID_TAG ("P"); + VALID_TAG ("/P"); + VALID_TAG ("H3"); + VALID_TAG ("/H3"); + VALID_TAG ("HTML"); + VALID_TAG ("/HTML"); + VALID_TAG ("BODY"); + VALID_TAG ("/BODY"); + VALID_TAG ("FONT"); + VALID_TAG ("HEAD"); + VALID_TAG ("/HEAD"); + VALID_TAG ("BINARY"); + VALID_TAG ("/BINARY"); + + VALID_OPT_TAG ("HR"); + VALID_OPT_TAG ("FONT"); + VALID_OPT_TAG ("BODY"); + VALID_OPT_TAG ("A"); + VALID_OPT_TAG ("IMG"); + VALID_OPT_TAG ("P"); + VALID_OPT_TAG ("H3"); + VALID_OPT_TAG ("HTML"); + + VALID_TAG ("CITE"); + VALID_TAG ("/CITE"); + VALID_TAG ("EM"); + VALID_TAG ("/EM"); + VALID_TAG ("STRONG"); + VALID_TAG ("/STRONG"); + + VALID_OPT_TAG ("SPAN"); + VALID_TAG ("/SPAN"); + VALID_TAG ("BR/"); /* hack until gtkimhtml handles things better */ + VALID_TAG ("IMG"); + VALID_TAG("SPAN"); + VALID_OPT_TAG("BR"); + + if (!g_ascii_strncasecmp(string, "!--", strlen ("!--"))) { + gchar *e = strstr (string + strlen("!--"), "-->"); + if (e) { + *len = e - string + strlen ("-->"); + *tag = g_strndup (string + strlen ("!--"), *len - strlen ("!---->")); + return TRUE; + } + } + + *type = -1; + *len = close - string + 1; + *tag = g_strndup(string, *len - 1); + return TRUE; +} + +static gchar* +gtk_imhtml_get_html_opt (gchar *tag, + const gchar *opt) +{ + gchar *t = tag; + gchar *e, *a; + gchar *val; + gint len; + gchar *c; + GString *ret; + + while (g_ascii_strncasecmp (t, opt, strlen (opt))) { + gboolean quote = FALSE; + if (*t == '\0') break; + while (*t && !((*t == ' ') && !quote)) { + if (*t == '\"') + quote = ! quote; + t++; + } + while (*t && (*t == ' ')) t++; + } + + if (!g_ascii_strncasecmp (t, opt, strlen (opt))) { + t += strlen (opt); + } else { + return NULL; + } + + if ((*t == '\"') || (*t == '\'')) { + e = a = ++t; + while (*e && (*e != *(t - 1))) e++; + if (*e == '\0') { + return NULL; + } else + val = g_strndup(a, e - a); + } else { + e = a = t; + while (*e && !isspace ((gint) *e)) e++; + val = g_strndup(a, e - a); + } + + ret = g_string_new(""); + e = val; + while(*e) { + if(gtk_imhtml_is_amp_escape(e, &c, &len)) { + ret = g_string_append(ret, c); + e += len; + } else { + ret = g_string_append_c(ret, *e); + e++; + } + } + + g_free(val); + + return g_string_free(ret, FALSE); +} + +/* Inline CSS Support - Douglas Thrift */ +static gchar* +gtk_imhtml_get_css_opt (gchar *style, + const gchar *opt) +{ + gchar *t = style; + gchar *e, *a; + gchar *val; + gint len; + gchar *c; + GString *ret; + + while (g_ascii_strncasecmp (t, opt, strlen (opt))) { +/* gboolean quote = FALSE; */ + if (*t == '\0') break; + while (*t && !((*t == ' ') /*&& !quote*/)) { +/* if (*t == '\"') + quote = ! quote; */ + t++; + } + while (*t && (*t == ' ')) t++; + } + + if (!g_ascii_strncasecmp (t, opt, strlen (opt))) { + t += strlen (opt); + while (*t && (*t == ' ')) t++; + if (!*t) + return NULL; + } else { + return NULL; + } + +/* if ((*t == '\"') || (*t == '\'')) { + e = a = ++t; + while (*e && (*e != *(t - 1))) e++; + if (*e == '\0') { + return NULL; + } else + val = g_strndup(a, e - a); + } else { + e = a = t; + while (*e && !isspace ((gint) *e)) e++; + val = g_strndup(a, e - a); + }*/ + + e = a = t; + while (*e && *e != ';') e++; + val = g_strndup(a, e - a); + + ret = g_string_new(""); + e = val; + while(*e) { + if(gtk_imhtml_is_amp_escape(e, &c, &len)) { + ret = g_string_append(ret, c); + e += len; + } else { + ret = g_string_append_c(ret, *e); + e++; + } + } + g_free(val); + + return g_string_free(ret, FALSE); +} + +static const char *accepted_protocols[] = { + "http://", + "https://", + "ftp://" +}; + +static const int accepted_protocols_size = 3; + +/* returns if the beginning of the text is a protocol. If it is the protocol, returns the length so + the caller knows how long the protocol string is. */ +static int gtk_imhtml_is_protocol(const char *text) +{ + gint i; + + for(i=0; i<accepted_protocols_size; i++){ + if( strncasecmp(text, accepted_protocols[i], strlen(accepted_protocols[i])) == 0 ){ + return strlen(accepted_protocols[i]); + } + } + return 0; +} + +/* + <KingAnt> marv: The two IM image functions in oscar are gaim_odc_send_im and gaim_odc_incoming + + +[19:58] <Robot101> marv: images go into the imgstore, a refcounted... well.. hash. :) +[19:59] <KingAnt> marv: I think the image tag used by the core is something like <img id="#"/> +[19:59] Ro0tSiEgE robert42 RobFlynn Robot101 ross22 roz +[20:00] <KingAnt> marv: Where the ID is the what is returned when you add the image to the imgstore using gaim_imgstore_add +[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? +[20:00] <KingAnt> marv: Right +[20:00] <marv> alright + +Here's my plan with IMImages. make gtk_imhtml_[append|insert]_text_with_images instead just +gtkimhtml_[append|insert]_text (hrm maybe it should be called html instead of text), add a +function for gaim to register for look up images, i.e. gtk_imhtml_set_get_img_fnc, so that +images can be looked up like that, instead of passing a GSList of them. + */ + +void gtk_imhtml_append_text_with_images (GtkIMHtml *imhtml, + const gchar *text, + GtkIMHtmlOptions options, + GSList *unused) +{ + GtkTextIter iter, ins, sel; + GdkRectangle rect; + int y, height, ins_offset = 0, sel_offset = 0; + gboolean fixins = FALSE, fixsel = FALSE; + + g_return_if_fail (imhtml != NULL); + g_return_if_fail (GTK_IS_IMHTML (imhtml)); + g_return_if_fail (text != NULL); + + + gtk_text_buffer_get_end_iter(imhtml->text_buffer, &iter); + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &ins, gtk_text_buffer_get_insert(imhtml->text_buffer)); + if (gtk_text_iter_equal(&iter, &ins) && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, NULL, NULL)) { + fixins = TRUE; + ins_offset = gtk_text_iter_get_offset(&ins); + } + + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &sel, gtk_text_buffer_get_selection_bound(imhtml->text_buffer)); + if (gtk_text_iter_equal(&iter, &sel) && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, NULL, NULL)) { + fixsel = TRUE; + sel_offset = gtk_text_iter_get_offset(&sel); + } + + gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect); + gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(imhtml), &iter, &y, &height); + + + if(((y + height) - (rect.y + rect.height)) > height + && gtk_text_buffer_get_char_count(imhtml->text_buffer)){ + options |= GTK_IMHTML_NO_SCROLL; + } + + gtk_imhtml_insert_html_at_iter(imhtml, text, options, &iter); + + if (fixins) { + gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &ins, ins_offset); + gtk_text_buffer_move_mark(imhtml->text_buffer, gtk_text_buffer_get_insert(imhtml->text_buffer), &ins); + } + + if (fixsel) { + gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &sel, sel_offset); + gtk_text_buffer_move_mark(imhtml->text_buffer, gtk_text_buffer_get_selection_bound(imhtml->text_buffer), &sel); + } + + if (!(options & GTK_IMHTML_NO_SCROLL)) { + gtk_imhtml_scroll_to_end(imhtml, (options & GTK_IMHTML_USE_SMOOTHSCROLLING)); + } +} + +#define MAX_SCROLL_TIME 0.4 /* seconds */ +#define SCROLL_DELAY 33 /* milliseconds */ + +/* + * Smoothly scroll a GtkIMHtml. + * + * @return TRUE if the window needs to be scrolled further, FALSE if we're at the bottom. + */ +static gboolean scroll_cb(gpointer data) +{ + GtkIMHtml *imhtml = data; + GtkAdjustment *adj = GTK_TEXT_VIEW(imhtml)->vadjustment; + gdouble max_val = adj->upper - adj->page_size; + + g_return_val_if_fail(imhtml->scroll_time != NULL, FALSE); + + if (g_timer_elapsed(imhtml->scroll_time, NULL) > MAX_SCROLL_TIME) { + /* time's up. jump to the end and kill the timer */ + gtk_adjustment_set_value(adj, max_val); + g_timer_destroy(imhtml->scroll_time); + imhtml->scroll_time = NULL; + return FALSE; + } + + /* scroll by 1/3rd the remaining distance */ + gtk_adjustment_set_value(adj, gtk_adjustment_get_value(adj) + ((max_val - gtk_adjustment_get_value(adj)) / 3)); + return TRUE; +} + +static gboolean smooth_scroll_idle_cb(gpointer data) +{ + GtkIMHtml *imhtml = data; + imhtml->scroll_src = g_timeout_add(SCROLL_DELAY, scroll_cb, imhtml); + return FALSE; +} + +static gboolean scroll_idle_cb(gpointer data) +{ + GtkIMHtml *imhtml = data; + GtkAdjustment *adj = GTK_TEXT_VIEW(imhtml)->vadjustment; + if(adj) { + gtk_adjustment_set_value(adj, adj->upper - adj->page_size); + } + imhtml->scroll_src = 0; + return FALSE; +} + +void gtk_imhtml_scroll_to_end(GtkIMHtml *imhtml, gboolean smooth) +{ + if (imhtml->scroll_time) + g_timer_destroy(imhtml->scroll_time); + if (imhtml->scroll_src) + g_source_remove(imhtml->scroll_src); + if(smooth) { + imhtml->scroll_time = g_timer_new(); + imhtml->scroll_src = g_idle_add_full(G_PRIORITY_LOW, smooth_scroll_idle_cb, imhtml, NULL); + } else { + imhtml->scroll_time = NULL; + imhtml->scroll_src = g_idle_add_full(G_PRIORITY_LOW, scroll_idle_cb, imhtml, NULL); + } +} + +void gtk_imhtml_insert_html_at_iter(GtkIMHtml *imhtml, + const gchar *text, + GtkIMHtmlOptions options, + GtkTextIter *iter) +{ + GdkRectangle rect; + gint pos = 0; + gchar *ws; + gchar *tag; + gchar *bg = NULL; + gint len; + gint tlen, smilelen, wpos=0; + gint type; + const gchar *c; + gchar *amp; + gint len_protocol; + + guint bold = 0, + italics = 0, + underline = 0, + strike = 0, + sub = 0, + sup = 0, + title = 0, + pre = 0; + + gboolean br = FALSE; + + GSList *fonts = NULL; + GObject *object; + GtkIMHtmlScalable *scalable = NULL; + + g_return_if_fail (imhtml != NULL); + g_return_if_fail (GTK_IS_IMHTML (imhtml)); + g_return_if_fail (text != NULL); + c = text; + len = strlen(text); + ws = g_malloc(len + 1); + ws[0] = 0; + + while (pos < len) { + if (*c == '<' && gtk_imhtml_is_tag (c + 1, &tag, &tlen, &type)) { + c++; + pos++; + ws[wpos] = '\0'; + br = FALSE; + switch (type) + { + case 1: /* B */ + case 2: /* BOLD */ + case 54: /* STRONG */ + if (!(options & GTK_IMHTML_NO_FORMATTING)) { + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + + if ((bold == 0) && (imhtml->format_functions & GTK_IMHTML_BOLD)) + gtk_imhtml_toggle_bold(imhtml); + bold++; + ws[0] = '\0'; wpos = 0; + } + break; + case 3: /* /B */ + case 4: /* /BOLD */ + case 55: /* /STRONG */ + if (!(options & GTK_IMHTML_NO_FORMATTING)) { + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; wpos = 0; + + if (bold) { + bold--; + if ((bold == 0) && (imhtml->format_functions & GTK_IMHTML_BOLD) && !imhtml->wbfo) + gtk_imhtml_toggle_bold(imhtml); + } + } + break; + case 5: /* I */ + case 6: /* ITALIC */ + case 52: /* EM */ + if (!(options & GTK_IMHTML_NO_FORMATTING)) { + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; wpos = 0; + if ((italics == 0) && (imhtml->format_functions & GTK_IMHTML_ITALIC)) + gtk_imhtml_toggle_italic(imhtml); + italics++; + } + break; + case 7: /* /I */ + case 8: /* /ITALIC */ + case 53: /* /EM */ + if (!(options & GTK_IMHTML_NO_FORMATTING)) { + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; wpos = 0; + if (italics) { + italics--; + if ((italics == 0) && (imhtml->format_functions & GTK_IMHTML_ITALIC) && !imhtml->wbfo) + gtk_imhtml_toggle_italic(imhtml); + } + } + break; + case 9: /* U */ + case 10: /* UNDERLINE */ + if (!(options & GTK_IMHTML_NO_FORMATTING)) { + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; wpos = 0; + if ((underline == 0) && (imhtml->format_functions & GTK_IMHTML_UNDERLINE)) + gtk_imhtml_toggle_underline(imhtml); + underline++; + } + break; + case 11: /* /U */ + case 12: /* /UNDERLINE */ + if (!(options & GTK_IMHTML_NO_FORMATTING)) { + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; wpos = 0; + if (underline) { + underline--; + if ((underline == 0) && (imhtml->format_functions & GTK_IMHTML_UNDERLINE) && !imhtml->wbfo) + gtk_imhtml_toggle_underline(imhtml); + } + } + break; + case 13: /* S */ + case 14: /* STRIKE */ + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; wpos = 0; + if ((strike == 0) && (imhtml->format_functions & GTK_IMHTML_STRIKE)) + gtk_imhtml_toggle_strike(imhtml); + strike++; + break; + case 15: /* /S */ + case 16: /* /STRIKE */ + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; wpos = 0; + if (strike) + strike--; + if ((strike == 0) && (imhtml->format_functions & GTK_IMHTML_STRIKE) && !imhtml->wbfo) + gtk_imhtml_toggle_strike(imhtml); + break; + case 17: /* SUB */ + /* FIXME: reimpliment this */ + sub++; + break; + case 18: /* /SUB */ + /* FIXME: reimpliment this */ + if (sub) + sub--; + break; + case 19: /* SUP */ + /* FIXME: reimplement this */ + sup++; + break; + case 20: /* /SUP */ + /* FIXME: reimplement this */ + if (sup) + sup--; + break; + case 21: /* PRE */ + /* FIXME: reimplement this */ + pre++; + break; + case 22: /* /PRE */ + /* FIXME: reimplement this */ + if (pre) + pre--; + break; + case 23: /* TITLE */ + /* FIXME: what was this supposed to do anyway? */ + title++; + break; + case 24: /* /TITLE */ + /* FIXME: make this undo whatever 23 was supposed to do */ + if (title) { + if (options & GTK_IMHTML_NO_TITLE) { + wpos = 0; + ws [wpos] = '\0'; + } + title--; + } + break; + case 25: /* BR */ + case 58: /* BR/ */ + case 61: /* BR (opt) */ + ws[wpos] = '\n'; + wpos++; + br = TRUE; + break; + case 26: /* HR */ + case 42: /* HR (opt) */ + { + int minus; + struct scalable_data *sd = g_new(struct scalable_data, 1); + + ws[wpos++] = '\n'; + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + + sd->scalable = scalable = gtk_imhtml_hr_new(); + sd->mark = gtk_text_buffer_create_mark(imhtml->text_buffer, NULL, iter, TRUE); + gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect); + scalable->add_to(scalable, imhtml, iter); + minus = gtk_text_view_get_left_margin(GTK_TEXT_VIEW(imhtml)) + + gtk_text_view_get_right_margin(GTK_TEXT_VIEW(imhtml)); + scalable->scale(scalable, rect.width - minus, rect.height); + imhtml->scalables = g_list_append(imhtml->scalables, sd); + ws[0] = '\0'; wpos = 0; + ws[wpos++] = '\n'; + + break; + } + case 27: /* /FONT */ + if (fonts && !imhtml->wbfo) { + GtkIMHtmlFontDetail *font = fonts->data; + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; wpos = 0; + /* NEW_BIT (NEW_TEXT_BIT); */ + + if (font->face && (imhtml->format_functions & GTK_IMHTML_FACE)) { + gtk_imhtml_toggle_fontface(imhtml, NULL); + g_free (font->face); + } + if (font->fore && (imhtml->format_functions & GTK_IMHTML_FORECOLOR)) { + gtk_imhtml_toggle_forecolor(imhtml, NULL); + g_free (font->fore); + } + if (font->back && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) { + gtk_imhtml_toggle_backcolor(imhtml, NULL); + g_free (font->back); + } + if (font->sml) + g_free (font->sml); + + if ((font->size != 3) && (imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK))) + gtk_imhtml_font_set_size(imhtml, 3); + + + fonts = g_slist_remove (fonts, font); + g_free(font); + + if (fonts) { + GtkIMHtmlFontDetail *font = fonts->data; + + if (font->face && (imhtml->format_functions & GTK_IMHTML_FACE)) + gtk_imhtml_toggle_fontface(imhtml, font->face); + if (font->fore && (imhtml->format_functions & GTK_IMHTML_FORECOLOR)) + gtk_imhtml_toggle_forecolor(imhtml, font->fore); + if (font->back && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) + gtk_imhtml_toggle_backcolor(imhtml, font->back); + if ((font->size != 3) && (imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK))) + gtk_imhtml_font_set_size(imhtml, font->size); + } + } + break; + case 28: /* /A */ + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + gtk_imhtml_toggle_link(imhtml, NULL); + ws[0] = '\0'; wpos = 0; + break; + + case 29: /* P */ + case 30: /* /P */ + case 31: /* H3 */ + case 32: /* /H3 */ + case 33: /* HTML */ + case 34: /* /HTML */ + case 35: /* BODY */ + break; + case 36: /* /BODY */ + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; wpos = 0; + gtk_imhtml_toggle_background(imhtml, NULL); + break; + case 37: /* FONT */ + case 38: /* HEAD */ + case 39: /* /HEAD */ + case 40: /* BINARY */ + case 41: /* /BINARY */ + break; + case 43: /* FONT (opt) */ + { + gchar *color, *back, *face, *size, *sml; + GtkIMHtmlFontDetail *font, *oldfont = NULL; + color = gtk_imhtml_get_html_opt (tag, "COLOR="); + back = gtk_imhtml_get_html_opt (tag, "BACK="); + face = gtk_imhtml_get_html_opt (tag, "FACE="); + size = gtk_imhtml_get_html_opt (tag, "SIZE="); + sml = gtk_imhtml_get_html_opt (tag, "SML="); + if (!(color || back || face || size || sml)) + break; + + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; wpos = 0; + + font = g_new0 (GtkIMHtmlFontDetail, 1); + if (fonts) + oldfont = fonts->data; + + if (color && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_FORECOLOR)) { + font->fore = color; + gtk_imhtml_toggle_forecolor(imhtml, font->fore); + } + + if (back && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) { + font->back = back; + gtk_imhtml_toggle_backcolor(imhtml, font->back); + } + + if (face && !(options & GTK_IMHTML_NO_FONTS) && (imhtml->format_functions & GTK_IMHTML_FACE)) { + font->face = face; + gtk_imhtml_toggle_fontface(imhtml, font->face); + } + + if (sml) + font->sml = sml; + else if (oldfont && oldfont->sml) + font->sml = g_strdup(oldfont->sml); + + if (size && !(options & GTK_IMHTML_NO_SIZES) && (imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK))) { + if (*size == '+') { + sscanf (size + 1, "%hd", &font->size); + font->size += 3; + } else if (*size == '-') { + sscanf (size + 1, "%hd", &font->size); + font->size = MAX (0, 3 - font->size); + } else if (isdigit (*size)) { + sscanf (size, "%hd", &font->size); + } + if (font->size > 100) + font->size = 100; + } else if (oldfont) + font->size = oldfont->size; + else + font->size = 3; + if ((imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK))) + gtk_imhtml_font_set_size(imhtml, font->size); + g_free(size); + fonts = g_slist_prepend (fonts, font); + } + break; + case 44: /* BODY (opt) */ + if (!(options & GTK_IMHTML_NO_COLOURS)) { + char *bgcolor = gtk_imhtml_get_html_opt (tag, "BGCOLOR="); + if (bgcolor && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) { + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; wpos = 0; + /* NEW_BIT(NEW_TEXT_BIT); */ + g_free(bg); + bg = bgcolor; + gtk_imhtml_toggle_background(imhtml, bg); + } + } + break; + case 45: /* A (opt) */ + { + gchar *href = gtk_imhtml_get_html_opt (tag, "HREF="); + if (href && (imhtml->format_functions & GTK_IMHTML_LINK)) { + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; wpos = 0; + gtk_imhtml_toggle_link(imhtml, href); + } + g_free(href); + } + break; + case 46: /* IMG (opt) */ + case 59: /* IMG */ + { + const char *id; + + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; wpos = 0; + + if (!(imhtml->format_functions & GTK_IMHTML_IMAGE)) + break; + + id = gtk_imhtml_get_html_opt(tag, "ID="); + if (!id) + break; + gtk_imhtml_insert_image_at_iter(imhtml, atoi(id), iter); + break; + } + case 47: /* P (opt) */ + case 48: /* H3 (opt) */ + case 49: /* HTML (opt) */ + case 50: /* CITE */ + case 51: /* /CITE */ + case 56: /* SPAN (opt) */ + /* Inline CSS Support - Douglas Thrift + * + * color + * background + * font-family + * font-size + * text-decoration: underline + * + * TODO: + * background-color + * font-style + * font-weight + */ + { + gchar *style, *color, *background, *family, *size; + gchar *textdec; + GtkIMHtmlFontDetail *font, *oldfont = NULL; + style = gtk_imhtml_get_html_opt (tag, "style="); + + if (!style) break; + + color = gtk_imhtml_get_css_opt (style, "color:"); + background = gtk_imhtml_get_css_opt (style, "background:"); + family = gtk_imhtml_get_css_opt (style, + "font-family:"); + size = gtk_imhtml_get_css_opt (style, "font-size:"); + textdec = gtk_imhtml_get_css_opt (style, "text-decoration:"); + + if (!(color || family || size || background || textdec)) { + g_free(style); + break; + } + + + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; wpos = 0; + /* NEW_BIT (NEW_TEXT_BIT); */ + + font = g_new0 (GtkIMHtmlFontDetail, 1); + if (fonts) + oldfont = fonts->data; + + if (color && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_FORECOLOR)) + { + font->fore = color; + gtk_imhtml_toggle_forecolor(imhtml, font->fore); + } + else if (oldfont && oldfont->fore) + font->fore = g_strdup(oldfont->fore); + + if (background && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) + { + font->back = background; + gtk_imhtml_toggle_backcolor(imhtml, font->back); + } + else if (oldfont && oldfont->back) + font->back = g_strdup(oldfont->back); + + if (family && !(options & GTK_IMHTML_NO_FONTS) && (imhtml->format_functions & GTK_IMHTML_FACE)) + { + font->face = family; + gtk_imhtml_toggle_fontface(imhtml, font->face); + } + else if (oldfont && oldfont->face) + font->face = g_strdup(oldfont->face); + if (font->face && (atoi(font->face) > 100)) { + /* WTF is this? */ + /* Maybe it sets a max size on the font face? I seem to + * remember bad things happening if the font size was + * 2 billion */ + g_free(font->face); + font->face = g_strdup("100"); + } + + if (oldfont && oldfont->sml) + font->sml = g_strdup(oldfont->sml); + + if (size && !(options & GTK_IMHTML_NO_SIZES) && (imhtml->format_functions & (GTK_IMHTML_SHRINK|GTK_IMHTML_GROW))) { + if (g_ascii_strcasecmp(size, "xx-small") == 0) + font->size = 1; + else if (g_ascii_strcasecmp(size, "smaller") == 0 + || g_ascii_strcasecmp(size, "x-small") == 0) + font->size = 2; + else if (g_ascii_strcasecmp(size, "larger") == 0 + || g_ascii_strcasecmp(size, "medium") == 0) + font->size = 4; + else if (g_ascii_strcasecmp(size, "large") == 0) + font->size = 5; + else if (g_ascii_strcasecmp(size, "x-large") == 0) + font->size = 6; + else if (g_ascii_strcasecmp(size, "xx-large") == 0) + font->size = 7; + else + font->size = 3; + gtk_imhtml_font_set_size(imhtml, font->size); + } + else if (oldfont) + { + font->size = oldfont->size; + } + + if (oldfont) + { + font->underline = oldfont->underline; + } + if (textdec && font->underline != 1 + && g_ascii_strcasecmp(textdec, "underline") == 0 + && (imhtml->format_functions & GTK_IMHTML_UNDERLINE)) + { + gtk_imhtml_toggle_underline(imhtml); + font->underline = 1; + } + + g_free(style); + g_free(size); + fonts = g_slist_prepend (fonts, font); + } + break; + case 57: /* /SPAN */ + /* Inline CSS Support - Douglas Thrift */ + if (fonts && !imhtml->wbfo) { + GtkIMHtmlFontDetail *oldfont = NULL; + GtkIMHtmlFontDetail *font = fonts->data; + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; wpos = 0; + /* NEW_BIT (NEW_TEXT_BIT); */ + fonts = g_slist_remove (fonts, font); + if (fonts) + oldfont = fonts->data; + + if (!oldfont) { + gtk_imhtml_font_set_size(imhtml, 3); + if (font->underline) + gtk_imhtml_toggle_underline(imhtml); + gtk_imhtml_toggle_fontface(imhtml, NULL); + gtk_imhtml_toggle_forecolor(imhtml, NULL); + gtk_imhtml_toggle_backcolor(imhtml, NULL); + } + else + { + + if (font->size != oldfont->size) + gtk_imhtml_font_set_size(imhtml, oldfont->size); + + if (font->underline != oldfont->underline) + gtk_imhtml_toggle_underline(imhtml); + + if (font->face && (!oldfont->face || strcmp(font->face, oldfont->face) != 0)) + gtk_imhtml_toggle_fontface(imhtml, oldfont->face); + + if (font->fore && (!oldfont->fore || strcmp(font->fore, oldfont->fore) != 0)) + gtk_imhtml_toggle_forecolor(imhtml, oldfont->fore); + + if (font->back && (!oldfont->back || strcmp(font->back, oldfont->back) != 0)) + gtk_imhtml_toggle_backcolor(imhtml, oldfont->back); + } + + g_free (font->face); + g_free (font->fore); + g_free (font->back); + g_free (font->sml); + + g_free (font); + } + break; + case 60: /* SPAN */ + break; + case 62: /* comment */ + /* NEW_BIT (NEW_TEXT_BIT); */ + ws[wpos] = '\0'; + + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + + if (imhtml->show_comments && !(options & GTK_IMHTML_NO_COMMENTS)) { + wpos = g_snprintf (ws, len, "%s", tag); + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + } + ws[0] = '\0'; wpos = 0; + + /* NEW_BIT (NEW_COMMENT_BIT); */ + break; + default: + break; + } + c += tlen; + pos += tlen; + g_free(tag); /* This was allocated back in VALID_TAG() */ + } else if (gtk_imhtml_is_smiley(imhtml, fonts, c, &smilelen)) { + GtkIMHtmlFontDetail *fd; + + gchar *sml = NULL; + if (fonts) { + fd = fonts->data; + sml = fd->sml; + } + if (!sml) + sml = imhtml->protocol_name; + + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + wpos = g_snprintf (ws, smilelen + 1, "%s", c); + + gtk_imhtml_insert_smiley_at_iter(imhtml, sml, ws, iter); + + c += smilelen; + pos += smilelen; + wpos = 0; + ws[0] = 0; + } else if (*c == '&' && gtk_imhtml_is_amp_escape (c, &, &tlen)) { + while(*amp) { + ws [wpos++] = *amp++; + } + c += tlen; + pos += tlen; + } else if (*c == '\n') { + if (!(options & GTK_IMHTML_NO_NEWLINE)) { + ws[wpos] = '\n'; + wpos++; + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; + wpos = 0; + /* NEW_BIT (NEW_TEXT_BIT); */ + } else if (!br) { /* Don't insert a space immediately after an HTML break */ + /* A newline is defined by HTML as whitespace, which means we have to replace it with a word boundary. + * word breaks vary depending on the language used, so the correct thing to do is to use Pango to determine + * what language this is, determine the proper word boundary to use, and insert that. I'm just going to insert + * a space instead. What are the non-English speakers going to do? Complain in a language I'll understand? + * Bu-wahaha! */ + ws[wpos] = ' '; + wpos++; + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; + wpos = 0; + } + c++; + pos++; + } else if ((len_protocol = gtk_imhtml_is_protocol(c)) > 0){ + while(len_protocol--){ + /* Skip the next len_protocol characters, but make sure they're + copied into the ws array. + */ + ws [wpos++] = *c++; + pos++; + } + } else if (*c) { + ws [wpos++] = *c++; + pos++; + } else { + break; + } + } + gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); + ws[0] = '\0'; wpos = 0; + + /* NEW_BIT(NEW_TEXT_BIT); */ + + while (fonts) { + GtkIMHtmlFontDetail *font = fonts->data; + fonts = g_slist_remove (fonts, font); + g_free (font->face); + g_free (font->fore); + g_free (font->back); + g_free (font->sml); + g_free (font); + } + + g_free(ws); + g_free(bg); + + if (!imhtml->wbfo) + gtk_imhtml_close_tags(imhtml, iter); + + object = g_object_ref(G_OBJECT(imhtml)); + g_signal_emit(object, signals[UPDATE_FORMAT], 0); + g_object_unref(object); + +} + +void gtk_imhtml_remove_smileys(GtkIMHtml *imhtml) +{ + g_hash_table_destroy(imhtml->smiley_data); + gtk_smiley_tree_destroy(imhtml->default_smilies); + imhtml->smiley_data = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, (GDestroyNotify)gtk_smiley_tree_destroy); + imhtml->default_smilies = gtk_smiley_tree_new(); +} + +void gtk_imhtml_show_comments (GtkIMHtml *imhtml, + gboolean show) +{ + imhtml->show_comments = show; +} + +const char * +gtk_imhtml_get_protocol_name(GtkIMHtml *imhtml) { + return imhtml->protocol_name; +} + +void +gtk_imhtml_set_protocol_name(GtkIMHtml *imhtml, const gchar *protocol_name) { + g_free(imhtml->protocol_name); + imhtml->protocol_name = g_strdup(protocol_name); +} + +void +gtk_imhtml_delete(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end) { + GList *l; + GSList *sl; + GtkTextIter i, i_s, i_e; + GObject *object = g_object_ref(G_OBJECT(imhtml)); + + if (start == NULL) { + gtk_text_buffer_get_start_iter(imhtml->text_buffer, &i_s); + start = &i_s; + } + + if (end == NULL) { + gtk_text_buffer_get_end_iter(imhtml->text_buffer, &i_e); + end = &i_e; + } + + l = imhtml->scalables; + while (l) { + GList *next = l->next; + struct scalable_data *sd = l->data; + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, + &i, sd->mark); + if (gtk_text_iter_in_range(&i, start, end)) { + GtkIMHtmlScalable *scale = sd->scalable; + scale->free(scale); + imhtml->scalables = g_list_remove_link(imhtml->scalables, l); + } + l = next; + } + + sl = imhtml->im_images; + while (sl) { + GSList *next = sl->next; + struct im_image_data *img_data = sl->data; + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, + &i, img_data->mark); + if (gtk_text_iter_in_range(&i, start, end)) { + if (imhtml->funcs->image_unref) + imhtml->funcs->image_unref(img_data->id); + imhtml->im_images = g_slist_delete_link(imhtml->im_images, sl); + g_free(img_data); + } + sl = next; + } + gtk_text_buffer_delete(imhtml->text_buffer, start, end); + + g_object_unref(object); +} + +void gtk_imhtml_page_up (GtkIMHtml *imhtml) +{ + GdkRectangle rect; + GtkTextIter iter; + + gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect); + gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml), &iter, rect.x, + rect.y - rect.height); + gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml), &iter, 0, TRUE, 0, 0); + +} +void gtk_imhtml_page_down (GtkIMHtml *imhtml) +{ + GdkRectangle rect; + GtkTextIter iter; + + gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect); + gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml), &iter, rect.x, + rect.y + rect.height); + gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml), &iter, 0, TRUE, 0, 0); +} + +/* GtkIMHtmlScalable, gtk_imhtml_image, gtk_imhtml_hr */ +GtkIMHtmlScalable *gtk_imhtml_image_new(GdkPixbuf *img, const gchar *filename, int id) +{ + GtkIMHtmlImage *im_image = g_malloc(sizeof(GtkIMHtmlImage)); + GtkImage *image = GTK_IMAGE(gtk_image_new_from_pixbuf(img)); + + GTK_IMHTML_SCALABLE(im_image)->scale = gtk_imhtml_image_scale; + GTK_IMHTML_SCALABLE(im_image)->add_to = gtk_imhtml_image_add_to; + GTK_IMHTML_SCALABLE(im_image)->free = gtk_imhtml_image_free; + + im_image->pixbuf = img; + im_image->image = image; + im_image->width = gdk_pixbuf_get_width(img); + im_image->height = gdk_pixbuf_get_height(img); + im_image->mark = NULL; + im_image->filename = g_strdup(filename); + im_image->id = id; + im_image->filesel = NULL; + + g_object_ref(img); + return GTK_IMHTML_SCALABLE(im_image); +} + +void gtk_imhtml_image_scale(GtkIMHtmlScalable *scale, int width, int height) +{ + GtkIMHtmlImage *im_image = (GtkIMHtmlImage *)scale; + + if (im_image->width > width || im_image->height > height) { + double ratio_w, ratio_h, ratio; + int new_h, new_w; + GdkPixbuf *new_image = NULL; + + ratio_w = ((double)width - 2) / im_image->width; + ratio_h = ((double)height - 2) / im_image->height; + + ratio = (ratio_w < ratio_h) ? ratio_w : ratio_h; + + new_w = (int)(im_image->width * ratio); + new_h = (int)(im_image->height * ratio); + + new_image = gdk_pixbuf_scale_simple(im_image->pixbuf, new_w, new_h, GDK_INTERP_BILINEAR); + gtk_image_set_from_pixbuf(im_image->image, new_image); + g_object_unref(G_OBJECT(new_image)); + } else if (gdk_pixbuf_get_width(gtk_image_get_pixbuf(im_image->image)) != im_image->width) { + /* Enough space to show the full-size of the image. */ + GdkPixbuf *new_image; + + new_image = gdk_pixbuf_scale_simple(im_image->pixbuf, im_image->width, im_image->height, GDK_INTERP_BILINEAR); + gtk_image_set_from_pixbuf(im_image->image, new_image); + g_object_unref(G_OBJECT(new_image)); + } +} + +static void +image_save_yes_cb(GtkIMHtmlImage *image, const char *filename) +{ + gchar *type = NULL; + GError *error = NULL; +#if GTK_CHECK_VERSION(2,2,0) + GSList *formats = gdk_pixbuf_get_formats(); +#else + char *basename = g_path_get_basename(filename); + char *ext = strrchr(basename, '.'); +#endif + + gtk_widget_destroy(image->filesel); + image->filesel = NULL; + +#if GTK_CHECK_VERSION(2,2,0) + while (formats) { + GdkPixbufFormat *format = formats->data; + gchar **extensions = gdk_pixbuf_format_get_extensions(format); + gpointer p = extensions; + + while(gdk_pixbuf_format_is_writable(format) && extensions && extensions[0]){ + gchar *fmt_ext = extensions[0]; + const gchar* file_ext = filename + strlen(filename) - strlen(fmt_ext); + + if(!strcmp(fmt_ext, file_ext)){ + type = gdk_pixbuf_format_get_name(format); + break; + } + + extensions++; + } + + g_strfreev(p); + + if (type) + break; + + formats = formats->next; + } + + g_slist_free(formats); +#else + /* this is really ugly code, but I think it will work */ + if (ext) { + ext++; + if (!g_ascii_strcasecmp(ext, "jpeg") || !g_ascii_strcasecmp(ext, "jpg")) + type = g_strdup("jpeg"); + else if (!g_ascii_strcasecmp(ext, "png")) + type = g_strdup("png"); + } + + g_free(basename); +#endif + + /* If I can't find a valid type, I will just tell the user about it and then assume + it's a png */ + if (!type){ +#if GTK_CHECK_VERSION(2,4,0) + GtkWidget *dialog = gtk_message_dialog_new_with_markup(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + _("<span size='larger' weight='bold'>Unrecognized file type</span>\n\nDefaulting to PNG.")); +#else + GtkWidget *dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + _("Unrecognized file type\n\nDefaulting to PNG.")); +#endif + + g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog); + gtk_widget_show(dialog); + type = g_strdup("png"); + } + + gdk_pixbuf_save(image->pixbuf, filename, type, &error, NULL); + + if (error){ +#if GTK_CHECK_VERSION(2,4,0) + GtkWidget *dialog = gtk_message_dialog_new_with_markup(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + _("<span size='larger' weight='bold'>Error saving image</span>\n\n%s"), error->message); +#else + GtkWidget *dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + _("Error saving image\n\n%s"), error->message); +#endif + g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog); + gtk_widget_show(dialog); + g_error_free(error); + } + + g_free(type); +} + +#if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */ +static void +image_save_check_if_exists_cb(GtkWidget *widget, gint response, GtkIMHtmlImage *image) +{ + gchar *filename; + + if (response != GTK_RESPONSE_ACCEPT) { + gtk_widget_destroy(widget); + image->filesel = NULL; + return; + } + + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget)); +#else /* FILECHOOSER */ +static void +image_save_check_if_exists_cb(GtkWidget *button, GtkIMHtmlImage *image) +{ + gchar *filename; + + filename = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(image->filesel))); + + if (g_file_test(filename, G_FILE_TEST_IS_DIR)) { + gchar *dirname; + /* append a / is needed */ + if (filename[strlen(filename) - 1] != G_DIR_SEPARATOR) { + dirname = g_strconcat(filename, G_DIR_SEPARATOR_S, NULL); + } else { + dirname = g_strdup(filename); + } + gtk_file_selection_set_filename(GTK_FILE_SELECTION(image->filesel), dirname); + g_free(dirname); + g_free(filename); + return; + } +#endif /* FILECHOOSER */ + + /* + * XXX - We should probably prompt the user to determine if they really + * want to overwrite the file or not. However, I don't feel like doing + * that, so we're just always going to overwrite if the file exists. + */ + /* + if (g_file_test(filename, G_FILE_TEST_EXISTS)) { + } else + image_save_yes_cb(image, filename); + */ + + image_save_yes_cb(image, filename); + + g_free(filename); +} + +#if !GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */ +static void +image_save_cancel_cb(GtkIMHtmlImage *image) +{ + gtk_widget_destroy(image->filesel); + image->filesel = NULL; +} +#endif /* FILECHOOSER */ + +static void +gtk_imhtml_image_save(GtkWidget *w, GtkIMHtmlImage *image) +{ + if (image->filesel != NULL) { + gtk_window_present(GTK_WINDOW(image->filesel)); + return; + } + +#if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */ + image->filesel = gtk_file_chooser_dialog_new(_("Save Image"), + NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gtk_dialog_set_default_response(GTK_DIALOG(image->filesel), GTK_RESPONSE_ACCEPT); + if (image->filename != NULL) + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(image->filesel), image->filename); + g_signal_connect(G_OBJECT(GTK_FILE_CHOOSER(image->filesel)), "response", + G_CALLBACK(image_save_check_if_exists_cb), image); +#else /* FILECHOOSER */ + image->filesel = gtk_file_selection_new(_("Save Image")); + if (image->filename != NULL) + gtk_file_selection_set_filename(GTK_FILE_SELECTION(image->filesel), image->filename); + g_signal_connect_swapped(G_OBJECT(GTK_FILE_SELECTION(image->filesel)), "delete_event", + G_CALLBACK(image_save_cancel_cb), image); + g_signal_connect_swapped(G_OBJECT(GTK_FILE_SELECTION(image->filesel)->cancel_button), + "clicked", G_CALLBACK(image_save_cancel_cb), image); + g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(image->filesel)->ok_button), "clicked", + G_CALLBACK(image_save_check_if_exists_cb), image); +#endif /* FILECHOOSER */ + + gtk_widget_show(image->filesel); +} + +/* + * So, um, AIM Direct IM lets you send any file, not just images. You can + * just insert a sound or a file or whatever in a conversation. It's + * basically like file transfer, except there is an icon to open the file + * embedded in the conversation. Someone should make the Gaim core handle + * all of that. + */ +static gboolean gtk_imhtml_image_clicked(GtkWidget *w, GdkEvent *event, GtkIMHtmlImage *image) +{ + GdkEventButton *event_button = (GdkEventButton *) event; + + if (event->type == GDK_BUTTON_RELEASE) { + if(event_button->button == 3) { + GtkWidget *img, *item, *menu; + gchar *text = g_strdup_printf(_("_Save Image...")); + menu = gtk_menu_new(); + + /* buttons and such */ + img = gtk_image_new_from_stock(GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU); + item = gtk_image_menu_item_new_with_mnemonic(text); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img); + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_image_save), image); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + + gtk_widget_show_all(menu); + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, + event_button->button, event_button->time); + + g_free(text); + return TRUE; + } + } + if(event->type == GDK_BUTTON_PRESS && event_button->button == 3) + return TRUE; /* Clicking the right mouse button on a link shouldn't + be caught by the regular GtkTextView menu */ + else + return FALSE; /* Let clicks go through if we didn't catch anything */ + +} +void gtk_imhtml_image_free(GtkIMHtmlScalable *scale) +{ + GtkIMHtmlImage *image = (GtkIMHtmlImage *)scale; + + g_object_unref(image->pixbuf); + g_free(image->filename); + if (image->filesel) + gtk_widget_destroy(image->filesel); + g_free(scale); +} + +void gtk_imhtml_image_add_to(GtkIMHtmlScalable *scale, GtkIMHtml *imhtml, GtkTextIter *iter) +{ + GtkIMHtmlImage *image = (GtkIMHtmlImage *)scale; + GtkWidget *box = gtk_event_box_new(); + char *tag; + GtkTextChildAnchor *anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter); + + gtk_container_add(GTK_CONTAINER(box), GTK_WIDGET(image->image)); + + if(!gtk_check_version(2, 4, 0)) + g_object_set(G_OBJECT(box), "visible-window", FALSE, NULL); + + gtk_widget_show(GTK_WIDGET(image->image)); + gtk_widget_show(box); + + tag = g_strdup_printf("<IMG ID=\"%d\">", image->id); + g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", tag, g_free); + g_object_set_data(G_OBJECT(anchor), "gtkimhtml_plaintext", "[Image]"); + + gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), box, anchor); + g_signal_connect(G_OBJECT(box), "event", G_CALLBACK(gtk_imhtml_image_clicked), image); +} + +GtkIMHtmlScalable *gtk_imhtml_hr_new() +{ + GtkIMHtmlHr *hr = g_malloc(sizeof(GtkIMHtmlHr)); + + GTK_IMHTML_SCALABLE(hr)->scale = gtk_imhtml_hr_scale; + GTK_IMHTML_SCALABLE(hr)->add_to = gtk_imhtml_hr_add_to; + GTK_IMHTML_SCALABLE(hr)->free = gtk_imhtml_hr_free; + + hr->sep = gtk_hseparator_new(); + gtk_widget_set_size_request(hr->sep, 5000, 2); + gtk_widget_show(hr->sep); + + return GTK_IMHTML_SCALABLE(hr); +} + +void gtk_imhtml_hr_scale(GtkIMHtmlScalable *scale, int width, int height) +{ + gtk_widget_set_size_request(((GtkIMHtmlHr *)scale)->sep, width - 2, 2); +} + +void gtk_imhtml_hr_add_to(GtkIMHtmlScalable *scale, GtkIMHtml *imhtml, GtkTextIter *iter) +{ + GtkIMHtmlHr *hr = (GtkIMHtmlHr *)scale; + GtkTextChildAnchor *anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter); + g_object_set_data(G_OBJECT(anchor), "gtkimhtml_htmltext", "<hr>"); + g_object_set_data(G_OBJECT(anchor), "gtkimhtml_plaintext", "\n---\n"); + gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), hr->sep, anchor); +} + +void gtk_imhtml_hr_free(GtkIMHtmlScalable *scale) +{ + g_free(scale); +} + +gboolean gtk_imhtml_search_find(GtkIMHtml *imhtml, const gchar *text) +{ + GtkTextIter iter, start, end; + gboolean new_search = TRUE; + + g_return_val_if_fail(imhtml != NULL, FALSE); + g_return_val_if_fail(text != NULL, FALSE); + + if (imhtml->search_string && !strcmp(text, imhtml->search_string)) + new_search = FALSE; + + if (new_search) { + gtk_imhtml_search_clear(imhtml); + gtk_text_buffer_get_start_iter(imhtml->text_buffer, &iter); + } else { + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, + gtk_text_buffer_get_mark(imhtml->text_buffer, "search")); + } + g_free(imhtml->search_string); + imhtml->search_string = g_strdup(text); + + if (gtk_source_iter_forward_search(&iter, imhtml->search_string, + GTK_SOURCE_SEARCH_VISIBLE_ONLY | GTK_SOURCE_SEARCH_CASE_INSENSITIVE, + &start, &end, NULL)) { + + gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml), &start, 0, TRUE, 0, 0); + gtk_text_buffer_create_mark(imhtml->text_buffer, "search", &end, FALSE); + if (new_search) { + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "search", &iter, &end); + do + gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "search", &start, &end); + while (gtk_source_iter_forward_search(&end, imhtml->search_string, + GTK_SOURCE_SEARCH_VISIBLE_ONLY | + GTK_SOURCE_SEARCH_CASE_INSENSITIVE, + &start, &end, NULL)); + } + return TRUE; + } + + gtk_imhtml_search_clear(imhtml); + + return FALSE; +} + +void gtk_imhtml_search_clear(GtkIMHtml *imhtml) +{ + GtkTextIter start, end; + + g_return_if_fail(imhtml != NULL); + + gtk_text_buffer_get_start_iter(imhtml->text_buffer, &start); + gtk_text_buffer_get_end_iter(imhtml->text_buffer, &end); + + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "search", &start, &end); + g_free(imhtml->search_string); + imhtml->search_string = NULL; +} + +static GtkTextTag *find_font_forecolor_tag(GtkIMHtml *imhtml, gchar *color) +{ + gchar str[18]; + GtkTextTag *tag; + + g_snprintf(str, sizeof(str), "FORECOLOR %s", color); + + tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str); + if (!tag) { + GdkColor gcolor; + if (!gdk_color_parse(color, &gcolor)) { + gchar tmp[8]; + tmp[0] = '#'; + strncpy(&tmp[1], color, 7); + tmp[7] = '\0'; + if (!gdk_color_parse(tmp, &gcolor)) + gdk_color_parse("black", &gcolor); + } + tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "foreground-gdk", &gcolor, NULL); + } + + return tag; +} + +static GtkTextTag *find_font_backcolor_tag(GtkIMHtml *imhtml, gchar *color) +{ + gchar str[18]; + GtkTextTag *tag; + + g_snprintf(str, sizeof(str), "BACKCOLOR %s", color); + + tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str); + if (!tag) { + GdkColor gcolor; + if (!gdk_color_parse(color, &gcolor)) { + gchar tmp[8]; + tmp[0] = '#'; + strncpy(&tmp[1], color, 7); + tmp[7] = '\0'; + if (!gdk_color_parse(tmp, &gcolor)) + gdk_color_parse("white", &gcolor); + } + tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "background-gdk", &gcolor, NULL); + } + + return tag; +} + +static GtkTextTag *find_font_background_tag(GtkIMHtml *imhtml, gchar *color) +{ + gchar str[19]; + GtkTextTag *tag; + + g_snprintf(str, sizeof(str), "BACKGROUND %s", color); + + tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str); + if (!tag) + tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, NULL); + + return tag; +} + +static GtkTextTag *find_font_face_tag(GtkIMHtml *imhtml, gchar *face) +{ + gchar str[256]; + GtkTextTag *tag; + + g_snprintf(str, sizeof(str), "FONT FACE %s", face); + str[255] = '\0'; + + tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str); + if (!tag) + tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "family", face, NULL); + + return tag; +} + +static GtkTextTag *find_font_size_tag(GtkIMHtml *imhtml, int size) +{ + gchar str[24]; + GtkTextTag *tag; + + g_snprintf(str, sizeof(str), "FONT SIZE %d", size); + str[23] = '\0'; + + tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), str); + if (!tag) { + /* For reasons I don't understand, setting "scale" here scaled + * based on some default size other than my theme's default + * size. Our size 4 was actually smaller than our size 3 for + * me. So this works around that oddity. + */ + GtkTextAttributes *attr = gtk_text_view_get_default_attributes(GTK_TEXT_VIEW(imhtml)); + tag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "size", + (gint) (pango_font_description_get_size(attr->font) * + (double) POINT_SIZE(size)), NULL); + gtk_text_attributes_unref(attr); + } + + return tag; +} + +static void remove_tag_by_prefix(GtkIMHtml *imhtml, const GtkTextIter *i, const GtkTextIter *e, + const char *prefix, guint len, gboolean homo) +{ + GSList *tags, *l; + GtkTextIter iter; + + tags = gtk_text_iter_get_tags(i); + + for (l = tags; l; l = l->next) { + GtkTextTag *tag = l->data; + + if (tag->name && !strncmp(tag->name, prefix, len)) + gtk_text_buffer_remove_tag(imhtml->text_buffer, tag, i, e); + } + + g_slist_free(tags); + + if (homo) + return; + + iter = *i; + + while (gtk_text_iter_forward_char(&iter) && !gtk_text_iter_equal(&iter, e)) { + if (gtk_text_iter_begins_tag(&iter, NULL)) { + tags = gtk_text_iter_get_toggled_tags(&iter, TRUE); + + for (l = tags; l; l = l->next) { + GtkTextTag *tag = l->data; + + if (tag->name && !strncmp(tag->name, prefix, len)) + gtk_text_buffer_remove_tag(imhtml->text_buffer, tag, &iter, e); + } + + g_slist_free(tags); + } + } +} + +static void remove_font_size(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo) +{ + remove_tag_by_prefix(imhtml, i, e, "FONT SIZE ", 10, homo); +} + +static void remove_font_face(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo) +{ + remove_tag_by_prefix(imhtml, i, e, "FONT FACE ", 10, homo); +} + +static void remove_font_forecolor(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo) +{ + remove_tag_by_prefix(imhtml, i, e, "FORECOLOR ", 10, homo); +} + +static void remove_font_backcolor(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo) +{ + remove_tag_by_prefix(imhtml, i, e, "BACKCOLOR ", 10, homo); +} + +static void remove_font_background(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo) +{ + remove_tag_by_prefix(imhtml, i, e, "BACKGROUND ", 10, homo); +} + +static void remove_font_link(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo) +{ + remove_tag_by_prefix(imhtml, i, e, "LINK ", 5, homo); +} + +static void +imhtml_clear_formatting(GtkIMHtml *imhtml) +{ + GtkTextIter start, end; + + if (!imhtml->editable) + return; + + if (imhtml->wbfo) + gtk_text_buffer_get_bounds(imhtml->text_buffer, &start, &end); + else + if (!gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) + gtk_text_buffer_get_bounds(imhtml->text_buffer, &start, &end); + + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "BOLD", &start, &end); + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "ITALICS", &start, &end); + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "UNDERLINE", &start, &end); + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "STRIKE", &start, &end); + remove_font_size(imhtml, &start, &end, FALSE); + remove_font_face(imhtml, &start, &end, FALSE); + remove_font_forecolor(imhtml, &start, &end, FALSE); + remove_font_backcolor(imhtml, &start, &end, FALSE); + remove_font_background(imhtml, &start, &end, FALSE); + remove_font_link(imhtml, &start, &end, FALSE); + + imhtml->edit.bold = 0; + imhtml->edit.italic = 0; + imhtml->edit.underline = 0; + imhtml->edit.strike = 0; + imhtml->edit.fontsize = 0; + + g_free(imhtml->edit.fontface); + imhtml->edit.fontface = NULL; + + g_free(imhtml->edit.forecolor); + imhtml->edit.forecolor = NULL; + + g_free(imhtml->edit.backcolor); + imhtml->edit.backcolor = NULL; + + g_free(imhtml->edit.background); + imhtml->edit.background = NULL; +} + +/* Editable stuff */ +static void preinsert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml) +{ + imhtml->insert_offset = gtk_text_iter_get_offset(iter); +} + +static void insert_ca_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextChildAnchor *arg2, gpointer user_data) +{ + GtkTextIter start; + + start = *arg1; + gtk_text_iter_backward_char(&start); + + gtk_imhtml_apply_tags_on_insert(user_data, &start, arg1); +} + +static void insert_cb(GtkTextBuffer *buffer, GtkTextIter *end, gchar *text, gint len, GtkIMHtml *imhtml) +{ + GtkTextIter start; + + if (!len) + return; + + start = *end; + gtk_text_iter_set_offset(&start, imhtml->insert_offset); + + gtk_imhtml_apply_tags_on_insert(imhtml, &start, end); +} + +static void delete_cb(GtkTextBuffer *buffer, GtkTextIter *start, GtkTextIter *end, GtkIMHtml *imhtml) +{ + GSList *tags, *l; + + tags = gtk_text_iter_get_tags(start); + for (l = tags; l != NULL; l = l->next) { + GtkTextTag *tag = GTK_TEXT_TAG(l->data); + + if (tag && /* Remove the formatting only if */ + gtk_text_iter_starts_word(start) && /* beginning of a word */ + gtk_text_iter_begins_tag(start, tag) && /* the tag starts with the selection */ + (!gtk_text_iter_has_tag(end, tag) || /* the tag ends within the selection */ + gtk_text_iter_ends_tag(end, tag))) { + gtk_text_buffer_remove_tag(imhtml->text_buffer, tag, start, end); + if (tag->name && + strncmp(tag->name, "LINK ", 5) == 0 && imhtml->edit.link) { + gtk_imhtml_toggle_link(imhtml, NULL); + } + } + } + g_slist_free(tags); +} + +static void gtk_imhtml_apply_tags_on_insert(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end) +{ + if (imhtml->edit.bold) + gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "BOLD", start, end); + else + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "BOLD", start, end); + + if (imhtml->edit.italic) + gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "ITALICS", start, end); + else + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "ITALICS", start, end); + + if (imhtml->edit.underline) + gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "UNDERLINE", start, end); + else + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "UNDERLINE", start, end); + + if (imhtml->edit.strike) + gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "STRIKE", start, end); + else + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "STRIKE", start, end); + + if (imhtml->edit.forecolor) { + remove_font_forecolor(imhtml, start, end, TRUE); + gtk_text_buffer_apply_tag(imhtml->text_buffer, + find_font_forecolor_tag(imhtml, imhtml->edit.forecolor), + start, end); + } + + if (imhtml->edit.backcolor) { + remove_font_backcolor(imhtml, start, end, TRUE); + gtk_text_buffer_apply_tag(imhtml->text_buffer, + find_font_backcolor_tag(imhtml, imhtml->edit.backcolor), + start, end); + } + + if (imhtml->edit.background) { + remove_font_background(imhtml, start, end, TRUE); + gtk_text_buffer_apply_tag(imhtml->text_buffer, + find_font_background_tag(imhtml, imhtml->edit.background), + start, end); + } + if (imhtml->edit.fontface) { + remove_font_face(imhtml, start, end, TRUE); + gtk_text_buffer_apply_tag(imhtml->text_buffer, + find_font_face_tag(imhtml, imhtml->edit.fontface), + start, end); + } + + if (imhtml->edit.fontsize) { + remove_font_size(imhtml, start, end, TRUE); + gtk_text_buffer_apply_tag(imhtml->text_buffer, + find_font_size_tag(imhtml, imhtml->edit.fontsize), + start, end); + } + + if (imhtml->edit.link) { + remove_font_link(imhtml, start, end, TRUE); + gtk_text_buffer_apply_tag(imhtml->text_buffer, + imhtml->edit.link, + start, end); + } +} + +void gtk_imhtml_set_editable(GtkIMHtml *imhtml, gboolean editable) +{ + gtk_text_view_set_editable(GTK_TEXT_VIEW(imhtml), editable); + /* + * We need a visible caret for accessibility, so mouseless + * people can highlight stuff. + */ + /* gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(imhtml), editable); */ + imhtml->editable = editable; + imhtml->format_functions = GTK_IMHTML_ALL; + + if (editable) + g_signal_connect_after(G_OBJECT(GTK_IMHTML(imhtml)->text_buffer), "mark-set", + G_CALLBACK(mark_set_cb), imhtml); +} + +void gtk_imhtml_set_whole_buffer_formatting_only(GtkIMHtml *imhtml, gboolean wbfo) +{ + g_return_if_fail(imhtml != NULL); + + imhtml->wbfo = wbfo; +} + +void gtk_imhtml_set_format_functions(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons) +{ + GObject *object = g_object_ref(G_OBJECT(imhtml)); + imhtml->format_functions = buttons; + g_signal_emit(object, signals[BUTTONS_UPDATE], 0, buttons); + g_object_unref(object); +} + +GtkIMHtmlButtons gtk_imhtml_get_format_functions(GtkIMHtml *imhtml) +{ + return imhtml->format_functions; +} + +void gtk_imhtml_get_current_format(GtkIMHtml *imhtml, gboolean *bold, + gboolean *italic, gboolean *underline) +{ + if (bold != NULL) + (*bold) = imhtml->edit.bold; + if (italic != NULL) + (*italic) = imhtml->edit.italic; + if (underline != NULL) + (*underline) = imhtml->edit.underline; +} + +char * +gtk_imhtml_get_current_fontface(GtkIMHtml *imhtml) +{ + return g_strdup(imhtml->edit.fontface); +} + +char * +gtk_imhtml_get_current_forecolor(GtkIMHtml *imhtml) +{ + return g_strdup(imhtml->edit.forecolor); +} + +char * +gtk_imhtml_get_current_backcolor(GtkIMHtml *imhtml) +{ + return g_strdup(imhtml->edit.backcolor); +} + +char * +gtk_imhtml_get_current_background(GtkIMHtml *imhtml) +{ + return g_strdup(imhtml->edit.background); +} + +gint +gtk_imhtml_get_current_fontsize(GtkIMHtml *imhtml) +{ + return imhtml->edit.fontsize; +} + +gboolean gtk_imhtml_get_editable(GtkIMHtml *imhtml) +{ + return imhtml->editable; +} + +void +gtk_imhtml_clear_formatting(GtkIMHtml *imhtml) +{ + GObject *object; + + object = g_object_ref(G_OBJECT(imhtml)); + g_signal_emit(object, signals[CLEAR_FORMAT], 0); + + gtk_widget_grab_focus(GTK_WIDGET(imhtml)); + + g_object_unref(object); +} + +/* + * I had this crazy idea about changing the text cursor color to reflex the foreground color + * of the text about to be entered. This is the place you'd do it, along with the place where + * we actually set a new foreground color. + * I may not do this, because people will bitch about Gaim overriding their gtk theme's cursor + * colors. + * + * Just in case I do do this, I asked about what to set the secondary text cursor to. + * + * (12:45:27) ?? ???: secondary_cursor_color = (rgb(background) + rgb(primary_cursor_color) ) / 2 + * (12:45:55) ?? ???: understand? + * (12:46:14) Tim: yeah. i didn't know there was an exact formula + * (12:46:56) ?? ???: u might need to extract separate each color from RGB + */ + +static void mark_set_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextMark *mark, + GtkIMHtml *imhtml) +{ + GSList *tags, *l; + GtkTextIter iter; + + if (mark != gtk_text_buffer_get_insert(buffer)) + return; + + if (!gtk_text_buffer_get_char_count(buffer)) + return; + + imhtml->edit.bold = imhtml->edit.italic = imhtml->edit.underline = imhtml->edit.strike = FALSE; + g_free(imhtml->edit.forecolor); + imhtml->edit.forecolor = NULL; + + g_free(imhtml->edit.backcolor); + imhtml->edit.backcolor = NULL; + + g_free(imhtml->edit.fontface); + imhtml->edit.fontface = NULL; + + imhtml->edit.fontsize = 0; + imhtml->edit.link = NULL; + + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, mark); + + + if (gtk_text_iter_is_end(&iter)) + tags = gtk_text_iter_get_toggled_tags(&iter, FALSE); + else + tags = gtk_text_iter_get_tags(&iter); + + for (l = tags; l != NULL; l = l->next) { + GtkTextTag *tag = GTK_TEXT_TAG(l->data); + + if (tag->name) { + if (strcmp(tag->name, "BOLD") == 0) + imhtml->edit.bold = TRUE; + else if (strcmp(tag->name, "ITALICS") == 0) + imhtml->edit.italic = TRUE; + else if (strcmp(tag->name, "UNDERLINE") == 0) + imhtml->edit.underline = TRUE; + else if (strcmp(tag->name, "STRIKE") == 0) + imhtml->edit.strike = TRUE; + else if (strncmp(tag->name, "FORECOLOR ", 10) == 0) + imhtml->edit.forecolor = g_strdup(&(tag->name)[10]); + else if (strncmp(tag->name, "BACKCOLOR ", 10) == 0) + imhtml->edit.backcolor = g_strdup(&(tag->name)[10]); + else if (strncmp(tag->name, "FONT FACE ", 10) == 0) + imhtml->edit.fontface = g_strdup(&(tag->name)[10]); + else if (strncmp(tag->name, "FONT SIZE ", 10) == 0) + imhtml->edit.fontsize = strtol(&(tag->name)[10], NULL, 10); + else if ((strncmp(tag->name, "LINK ", 5) == 0) && !gtk_text_iter_is_end(&iter)) + imhtml->edit.link = tag; + } + } + + g_slist_free(tags); +} + +static void imhtml_emit_signal_for_format(GtkIMHtml *imhtml, GtkIMHtmlButtons button) +{ + GObject *object; + + g_return_if_fail(imhtml != NULL); + + object = g_object_ref(G_OBJECT(imhtml)); + g_signal_emit(object, signals[TOGGLE_FORMAT], 0, button); + g_object_unref(object); +} + +static void imhtml_toggle_bold(GtkIMHtml *imhtml) +{ + GtkTextIter start, end; + + imhtml->edit.bold = !imhtml->edit.bold; + + if (imhtml->wbfo) { + gtk_text_buffer_get_bounds(imhtml->text_buffer, &start, &end); + if (imhtml->edit.bold) + gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "BOLD", &start, &end); + else + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "BOLD", &start, &end); + } else if (imhtml->editable && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) { + if (imhtml->edit.bold) + gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "BOLD", &start, &end); + else + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "BOLD", &start, &end); + + } +} + +void gtk_imhtml_toggle_bold(GtkIMHtml *imhtml) +{ + imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_BOLD); +} + +static void imhtml_toggle_italic(GtkIMHtml *imhtml) +{ + GtkTextIter start, end; + + imhtml->edit.italic = !imhtml->edit.italic; + + if (imhtml->wbfo) { + gtk_text_buffer_get_bounds(imhtml->text_buffer, &start, &end); + if (imhtml->edit.italic) + gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "ITALICS", &start, &end); + else + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "ITALICS", &start, &end); + } else if (imhtml->editable && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) { + if (imhtml->edit.italic) + gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "ITALICS", &start, &end); + else + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "ITALICS", &start, &end); + } +} + +void gtk_imhtml_toggle_italic(GtkIMHtml *imhtml) +{ + imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_ITALIC); +} + +static void imhtml_toggle_underline(GtkIMHtml *imhtml) +{ + GtkTextIter start, end; + + imhtml->edit.underline = !imhtml->edit.underline; + + if (imhtml->wbfo) { + gtk_text_buffer_get_bounds(imhtml->text_buffer, &start, &end); + if (imhtml->edit.underline) + gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "UNDERLINE", &start, &end); + else + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "UNDERLINE", &start, &end); + } else if (imhtml->editable && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) { + if (imhtml->edit.underline) + gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "UNDERLINE", &start, &end); + else + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "UNDERLINE", &start, &end); + } +} + +void gtk_imhtml_toggle_underline(GtkIMHtml *imhtml) +{ + imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_UNDERLINE); +} + +static void imhtml_toggle_strike(GtkIMHtml *imhtml) +{ + GtkTextIter start, end; + + imhtml->edit.strike = !imhtml->edit.strike; + + if (imhtml->wbfo) { + gtk_text_buffer_get_bounds(imhtml->text_buffer, &start, &end); + if (imhtml->edit.strike) + gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "STRIKE", &start, &end); + else + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "STRIKE", &start, &end); + } else if (imhtml->editable && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) { + if (imhtml->edit.strike) + gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "STRIKE", &start, &end); + else + gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "STRIKE", &start, &end); + } +} + +void gtk_imhtml_toggle_strike(GtkIMHtml *imhtml) +{ + imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_STRIKE); +} + +void gtk_imhtml_font_set_size(GtkIMHtml *imhtml, gint size) +{ + GObject *object; + GtkTextIter start, end; + + imhtml->edit.fontsize = size; + + if (imhtml->wbfo) { + gtk_text_buffer_get_bounds(imhtml->text_buffer, &start, &end); + remove_font_size(imhtml, &start, &end, TRUE); + gtk_text_buffer_apply_tag(imhtml->text_buffer, + find_font_size_tag(imhtml, imhtml->edit.fontsize), &start, &end); + } else if (imhtml->editable && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) { + remove_font_size(imhtml, &start, &end, FALSE); + gtk_text_buffer_apply_tag(imhtml->text_buffer, + find_font_size_tag(imhtml, imhtml->edit.fontsize), &start, &end); + } + + object = g_object_ref(G_OBJECT(imhtml)); + g_signal_emit(object, signals[TOGGLE_FORMAT], 0, GTK_IMHTML_SHRINK | GTK_IMHTML_GROW); + g_object_unref(object); +} + +static void imhtml_font_shrink(GtkIMHtml *imhtml) +{ + GtkTextIter start, end; + + if (imhtml->edit.fontsize == 1) + return; + + if (!imhtml->edit.fontsize) + imhtml->edit.fontsize = 2; + else + imhtml->edit.fontsize--; + + if (imhtml->wbfo) { + gtk_text_buffer_get_bounds(imhtml->text_buffer, &start, &end); + remove_font_size(imhtml, &start, &end, TRUE); + gtk_text_buffer_apply_tag(imhtml->text_buffer, + find_font_size_tag(imhtml, imhtml->edit.fontsize), &start, &end); + } else if (imhtml->editable && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) { + remove_font_size(imhtml, &start, &end, FALSE); + gtk_text_buffer_apply_tag(imhtml->text_buffer, + find_font_size_tag(imhtml, imhtml->edit.fontsize), &start, &end); + } +} + +void gtk_imhtml_font_shrink(GtkIMHtml *imhtml) +{ + imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_SHRINK); +} + +static void imhtml_font_grow(GtkIMHtml *imhtml) +{ + GtkTextIter start, end; + + if (imhtml->edit.fontsize == MAX_FONT_SIZE) + return; + + if (!imhtml->edit.fontsize) + imhtml->edit.fontsize = 4; + else + imhtml->edit.fontsize++; + + if (imhtml->wbfo) { + gtk_text_buffer_get_bounds(imhtml->text_buffer, &start, &end); + remove_font_size(imhtml, &start, &end, TRUE); + gtk_text_buffer_apply_tag(imhtml->text_buffer, + find_font_size_tag(imhtml, imhtml->edit.fontsize), &start, &end); + } else if (imhtml->editable && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) { + remove_font_size(imhtml, &start, &end, FALSE); + gtk_text_buffer_apply_tag(imhtml->text_buffer, + find_font_size_tag(imhtml, imhtml->edit.fontsize), &start, &end); + } +} + +void gtk_imhtml_font_grow(GtkIMHtml *imhtml) +{ + imhtml_emit_signal_for_format(imhtml, GTK_IMHTML_GROW); +} + +static gboolean gtk_imhtml_toggle_str_tag(GtkIMHtml *imhtml, const char *value, char **edit_field, + void (*remove_func)(GtkIMHtml *imhtml, GtkTextIter *i, GtkTextIter *e, gboolean homo), + GtkTextTag *(find_func)(GtkIMHtml *imhtml, gchar *color), GtkIMHtmlButtons button) +{ + GObject *object; + GtkTextIter start; + GtkTextIter end; + + g_free(*edit_field); + *edit_field = NULL; + + if (value && strcmp(value, "") != 0) + { + *edit_field = g_strdup(value); + + if (imhtml->wbfo) + { + gtk_text_buffer_get_bounds(imhtml->text_buffer, &start, &end); + remove_func(imhtml, &start, &end, TRUE); + gtk_text_buffer_apply_tag(imhtml->text_buffer, + find_func(imhtml, *edit_field), &start, &end); + } + else + { + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &start, + gtk_text_buffer_get_mark(imhtml->text_buffer, "insert")); + if (imhtml->editable && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) + { + remove_func(imhtml, &start, &end, FALSE); + gtk_text_buffer_apply_tag(imhtml->text_buffer, + find_func(imhtml, + *edit_field), + &start, &end); + } + } + } + else + { + if (imhtml->wbfo) + { + gtk_text_buffer_get_bounds(imhtml->text_buffer, &start, &end); + remove_func(imhtml, &start, &end, TRUE); + } + else + { + if (imhtml->editable && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) + remove_func(imhtml, &start, &end, TRUE); + } + } + + object = g_object_ref(G_OBJECT(imhtml)); + g_signal_emit(object, signals[TOGGLE_FORMAT], 0, button); + g_object_unref(object); + + return *edit_field != NULL; +} + +gboolean gtk_imhtml_toggle_forecolor(GtkIMHtml *imhtml, const char *color) +{ + return gtk_imhtml_toggle_str_tag(imhtml, color, &imhtml->edit.forecolor, + remove_font_forecolor, find_font_forecolor_tag, + GTK_IMHTML_FORECOLOR); +} + +gboolean gtk_imhtml_toggle_backcolor(GtkIMHtml *imhtml, const char *color) +{ + return gtk_imhtml_toggle_str_tag(imhtml, color, &imhtml->edit.backcolor, + remove_font_backcolor, find_font_backcolor_tag, + GTK_IMHTML_BACKCOLOR); +} + +gboolean gtk_imhtml_toggle_background(GtkIMHtml *imhtml, const char *color) +{ + return gtk_imhtml_toggle_str_tag(imhtml, color, &imhtml->edit.background, + remove_font_background, find_font_background_tag, + GTK_IMHTML_BACKGROUND); +} + +gboolean gtk_imhtml_toggle_fontface(GtkIMHtml *imhtml, const char *face) +{ + return gtk_imhtml_toggle_str_tag(imhtml, face, &imhtml->edit.fontface, + remove_font_face, find_font_face_tag, + GTK_IMHTML_FACE); +} + +void gtk_imhtml_toggle_link(GtkIMHtml *imhtml, const char *url) +{ + GObject *object; + GtkTextIter start, end; + GtkTextTag *linktag; + static guint linkno = 0; + gchar str[48]; + GdkColor *color = NULL; + + imhtml->edit.link = NULL; + + if (url) { + g_snprintf(str, sizeof(str), "LINK %d", linkno++); + str[47] = '\0'; + + gtk_widget_style_get(GTK_WIDGET(imhtml), "hyperlink-color", &color, NULL); + if (color) { + imhtml->edit.link = linktag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "foreground-gdk", color, "underline", PANGO_UNDERLINE_SINGLE, NULL); + gdk_color_free(color); + } else { + imhtml->edit.link = linktag = gtk_text_buffer_create_tag(imhtml->text_buffer, str, "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE, NULL); + } + g_object_set_data_full(G_OBJECT(linktag), "link_url", g_strdup(url), g_free); + g_signal_connect(G_OBJECT(linktag), "event", G_CALLBACK(tag_event), NULL); + + if (imhtml->editable && gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) { + remove_font_link(imhtml, &start, &end, FALSE); + gtk_text_buffer_apply_tag(imhtml->text_buffer, linktag, &start, &end); + } + } + + object = g_object_ref(G_OBJECT(imhtml)); + g_signal_emit(object, signals[TOGGLE_FORMAT], 0, GTK_IMHTML_LINK); + g_object_unref(object); +} + +void gtk_imhtml_insert_link(GtkIMHtml *imhtml, GtkTextMark *mark, const char *url, const char *text) +{ + GtkTextIter iter; + + if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, NULL, NULL)) + gtk_text_buffer_delete_selection(imhtml->text_buffer, TRUE, TRUE); + + gtk_imhtml_toggle_link(imhtml, url); + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, mark); + gtk_text_buffer_insert(imhtml->text_buffer, &iter, text, -1); + gtk_imhtml_toggle_link(imhtml, NULL); +} + +void gtk_imhtml_insert_smiley(GtkIMHtml *imhtml, const char *sml, char *smiley) +{ + GtkTextMark *mark; + GtkTextIter iter; + + if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, NULL, NULL)) + gtk_text_buffer_delete_selection(imhtml->text_buffer, TRUE, TRUE); + + mark = gtk_text_buffer_get_insert(imhtml->text_buffer); + + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &iter, mark); + gtk_imhtml_insert_smiley_at_iter(imhtml, sml, smiley, &iter); +} + +static gboolean +image_expose(GtkWidget *widget, GdkEventExpose *event, gpointer user_data) +{ + GTK_WIDGET_CLASS(GTK_WIDGET_GET_CLASS(widget))->expose_event(widget, event); + + return TRUE; +} + +void gtk_imhtml_insert_smiley_at_iter(GtkIMHtml *imhtml, const char *sml, char *smiley, GtkTextIter *iter) +{ + GdkPixbuf *pixbuf = NULL; + GdkPixbufAnimation *annipixbuf = NULL; + GtkWidget *icon = NULL; + GtkTextChildAnchor *anchor; + char *unescaped = gaim_unescape_html(smiley); + GtkIMHtmlSmiley *imhtml_smiley = gtk_imhtml_smiley_get(imhtml, sml, unescaped); + + if (imhtml->format_functions & GTK_IMHTML_SMILEY) { + annipixbuf = gtk_smiley_tree_image(imhtml, sml, unescaped); + if (annipixbuf) { + if (gdk_pixbuf_animation_is_static_image(annipixbuf)) { + pixbuf = gdk_pixbuf_animation_get_static_image(annipixbuf); + if (pixbuf) + icon = gtk_image_new_from_pixbuf(pixbuf); + } else { + icon = gtk_image_new_from_animation(annipixbuf); + } + } + } + + if (icon) { + anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter); + g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", g_strdup(unescaped), g_free); + g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley), g_free); + + /* This catches the expose events generated by animated + * images, and ensures that they are handled by the image + * itself, without propagating to the textview and causing + * a complete refresh */ + g_signal_connect(G_OBJECT(icon), "expose-event", G_CALLBACK(image_expose), NULL); + + gtk_widget_show(icon); + gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), icon, anchor); + } else if (imhtml_smiley != NULL && (imhtml->format_functions & GTK_IMHTML_SMILEY)) { + anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter); + imhtml_smiley->anchors = g_slist_append(imhtml_smiley->anchors, anchor); + } else { + gtk_text_buffer_insert(imhtml->text_buffer, iter, smiley, -1); + } + + g_free(unescaped); +} + +void gtk_imhtml_insert_image_at_iter(GtkIMHtml *imhtml, int id, GtkTextIter *iter) +{ + GdkPixbuf *pixbuf = NULL; + const char *filename = NULL; + gpointer image; + GdkRectangle rect; + GtkIMHtmlScalable *scalable = NULL; + struct scalable_data *sd; + int minus; + + if (!imhtml->funcs || !imhtml->funcs->image_get || + !imhtml->funcs->image_get_size || !imhtml->funcs->image_get_data || + !imhtml->funcs->image_get_filename || !imhtml->funcs->image_ref || + !imhtml->funcs->image_unref) + return; + + image = imhtml->funcs->image_get(id); + + if (image) { + gpointer data; + size_t len; + + data = imhtml->funcs->image_get_data(image); + len = imhtml->funcs->image_get_size(image); + + if (data && len) { + GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); + gdk_pixbuf_loader_write(loader, data, len, NULL); + gdk_pixbuf_loader_close(loader, NULL); + pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); + if (pixbuf) + g_object_ref(G_OBJECT(pixbuf)); + g_object_unref(G_OBJECT(loader)); + } + + } + + if (pixbuf) { + struct im_image_data *t = g_new(struct im_image_data, 1); + filename = imhtml->funcs->image_get_filename(image); + imhtml->funcs->image_ref(id); + t->id = id; + t->mark = gtk_text_buffer_create_mark(imhtml->text_buffer, NULL, iter, TRUE); + imhtml->im_images = g_slist_prepend(imhtml->im_images, t); + } else { + pixbuf = gtk_widget_render_icon(GTK_WIDGET(imhtml), GTK_STOCK_MISSING_IMAGE, + GTK_ICON_SIZE_BUTTON, "gtkimhtml-missing-image"); + } + + sd = g_new(struct scalable_data, 1); + sd->scalable = scalable = gtk_imhtml_image_new(pixbuf, filename, id); + sd->mark = gtk_text_buffer_create_mark(imhtml->text_buffer, NULL, iter, TRUE); + gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect); + scalable->add_to(scalable, imhtml, iter); + minus = gtk_text_view_get_left_margin(GTK_TEXT_VIEW(imhtml)) + + gtk_text_view_get_right_margin(GTK_TEXT_VIEW(imhtml)); + scalable->scale(scalable, rect.width - minus, rect.height); + imhtml->scalables = g_list_append(imhtml->scalables, sd); + + g_object_unref(G_OBJECT(pixbuf)); +} + +static const gchar *tag_to_html_start(GtkTextTag *tag) +{ + const gchar *name; + static gchar buf[1024]; + + name = tag->name; + g_return_val_if_fail(name != NULL, ""); + + if (strcmp(name, "BOLD") == 0) { + return "<b>"; + } else if (strcmp(name, "ITALICS") == 0) { + return "<i>"; + } else if (strcmp(name, "UNDERLINE") == 0) { + return "<u>"; + } else if (strcmp(name, "STRIKE") == 0) { + return "<s>"; + } else if (strncmp(name, "LINK ", 5) == 0) { + char *tmp = g_object_get_data(G_OBJECT(tag), "link_url"); + if (tmp) { + g_snprintf(buf, sizeof(buf), "<a href=\"%s\">", tmp); + buf[sizeof(buf)-1] = '\0'; + return buf; + } else { + return ""; + } + } else if (strncmp(name, "FORECOLOR ", 10) == 0) { + g_snprintf(buf, sizeof(buf), "<font color=\"%s\">", &name[10]); + return buf; + } else if (strncmp(name, "BACKCOLOR ", 10) == 0) { + g_snprintf(buf, sizeof(buf), "<font back=\"%s\">", &name[10]); + return buf; + } else if (strncmp(name, "BACKGROUND ", 10) == 0) { + g_snprintf(buf, sizeof(buf), "<body bgcolor=\"%s\">", &name[11]); + return buf; + } else if (strncmp(name, "FONT FACE ", 10) == 0) { + g_snprintf(buf, sizeof(buf), "<font face=\"%s\">", &name[10]); + return buf; + } else if (strncmp(name, "FONT SIZE ", 10) == 0) { + g_snprintf(buf, sizeof(buf), "<font size=\"%s\">", &name[10]); + return buf; + } else { + return ""; + } +} + +static const gchar *tag_to_html_end(GtkTextTag *tag) +{ + const gchar *name; + + name = tag->name; + g_return_val_if_fail(name != NULL, ""); + + if (strcmp(name, "BOLD") == 0) { + return "</b>"; + } else if (strcmp(name, "ITALICS") == 0) { + return "</i>"; + } else if (strcmp(name, "UNDERLINE") == 0) { + return "</u>"; + } else if (strcmp(name, "STRIKE") == 0) { + return "</s>"; + } else if (strncmp(name, "LINK ", 5) == 0) { + return "</a>"; + } else if (strncmp(name, "FORECOLOR ", 10) == 0) { + return "</font>"; + } else if (strncmp(name, "BACKCOLOR ", 10) == 0) { + return "</font>"; + } else if (strncmp(name, "BACKGROUND ", 10) == 0) { + return "</body>"; + } else if (strncmp(name, "FONT FACE ", 10) == 0) { + return "</font>"; + } else if (strncmp(name, "FONT SIZE ", 10) == 0) { + return "</font>"; + } else { + return ""; + } +} + +static gboolean tag_ends_here(GtkTextTag *tag, GtkTextIter *iter, GtkTextIter *niter) +{ + return ((gtk_text_iter_has_tag(iter, GTK_TEXT_TAG(tag)) && + !gtk_text_iter_has_tag(niter, GTK_TEXT_TAG(tag))) || + gtk_text_iter_is_end(niter)); +} + +/* Basic notion here: traverse through the text buffer one-by-one, non-character elements, such + * as smileys and IM images are represented by the Unicode "unknown" character. Handle them. Else + * check for tags that are toggled on, insert their html form, and push them on the queue. Then insert + * the actual text. Then check for tags that are toggled off and insert them, after checking the queue. + * Finally, replace <, >, &, and " with their HTML equivalent. + */ +char *gtk_imhtml_get_markup_range(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end) +{ + gunichar c; + GtkTextIter iter, nextiter; + GString *str = g_string_new(""); + GSList *tags, *sl; + GQueue *q, *r; + GtkTextTag *tag; + + q = g_queue_new(); + r = g_queue_new(); + + + gtk_text_iter_order(start, end); + nextiter = iter = *start; + gtk_text_iter_forward_char(&nextiter); + + /* First add the tags that are already in progress (we don't care about non-printing tags)*/ + tags = gtk_text_iter_get_tags(start); + + for (sl = tags; sl; sl = sl->next) { + tag = sl->data; + if (!gtk_text_iter_toggles_tag(start, GTK_TEXT_TAG(tag))) { + if (strlen(tag_to_html_end(tag)) > 0) + g_string_append(str, tag_to_html_start(tag)); + g_queue_push_tail(q, tag); + } + } + g_slist_free(tags); + + while ((c = gtk_text_iter_get_char(&iter)) != 0 && !gtk_text_iter_equal(&iter, end)) { + + tags = gtk_text_iter_get_tags(&iter); + + for (sl = tags; sl; sl = sl->next) { + tag = sl->data; + if (gtk_text_iter_begins_tag(&iter, GTK_TEXT_TAG(tag))) { + if (strlen(tag_to_html_end(tag)) > 0) + g_string_append(str, tag_to_html_start(tag)); + g_queue_push_tail(q, tag); + } + } + + + if (c == 0xFFFC) { + GtkTextChildAnchor* anchor = gtk_text_iter_get_child_anchor(&iter); + if (anchor) { + char *text = g_object_get_data(G_OBJECT(anchor), "gtkimhtml_htmltext"); + if (text) + str = g_string_append(str, text); + } + } else if (c == '<') { + str = g_string_append(str, "<"); + } else if (c == '>') { + str = g_string_append(str, ">"); + } else if (c == '&') { + str = g_string_append(str, "&"); + } else if (c == '"') { + str = g_string_append(str, """); + } else if (c == '\n') { + str = g_string_append(str, "<br>"); + } else { + str = g_string_append_unichar(str, c); + } + + tags = g_slist_reverse(tags); + for (sl = tags; sl; sl = sl->next) { + tag = sl->data; + /** don't worry about non-printing tags ending */ + if (tag_ends_here(tag, &iter, &nextiter) && strlen(tag_to_html_end(tag)) > 0) { + + GtkTextTag *tmp; + + while ((tmp = g_queue_pop_tail(q)) != tag) { + if (tmp == NULL) + break; + + if (!tag_ends_here(tmp, &iter, &nextiter) && strlen(tag_to_html_end(tmp)) > 0) + g_queue_push_tail(r, tmp); + g_string_append(str, tag_to_html_end(GTK_TEXT_TAG(tmp))); + } + + if (tmp == NULL) + gaim_debug_warning("gtkimhtml", "empty queue, more closing tags than open tags!\n"); + else + g_string_append(str, tag_to_html_end(GTK_TEXT_TAG(tag))); + + while ((tmp = g_queue_pop_head(r))) { + g_string_append(str, tag_to_html_start(GTK_TEXT_TAG(tmp))); + g_queue_push_tail(q, tmp); + } + } + } + + g_slist_free(tags); + gtk_text_iter_forward_char(&iter); + gtk_text_iter_forward_char(&nextiter); + } + + while ((tag = g_queue_pop_tail(q))) + g_string_append(str, tag_to_html_end(GTK_TEXT_TAG(tag))); + + g_queue_free(q); + g_queue_free(r); + return g_string_free(str, FALSE); +} + +void gtk_imhtml_close_tags(GtkIMHtml *imhtml, GtkTextIter *iter) +{ + if (imhtml->edit.bold) + gtk_imhtml_toggle_bold(imhtml); + + if (imhtml->edit.italic) + gtk_imhtml_toggle_italic(imhtml); + + if (imhtml->edit.underline) + gtk_imhtml_toggle_underline(imhtml); + + if (imhtml->edit.strike) + gtk_imhtml_toggle_strike(imhtml); + + if (imhtml->edit.forecolor) + gtk_imhtml_toggle_forecolor(imhtml, NULL); + + if (imhtml->edit.backcolor) + gtk_imhtml_toggle_backcolor(imhtml, NULL); + + if (imhtml->edit.fontface) + gtk_imhtml_toggle_fontface(imhtml, NULL); + + imhtml->edit.fontsize = 0; + + if (imhtml->edit.link) + gtk_imhtml_toggle_link(imhtml, NULL); + + gtk_text_buffer_remove_all_tags(imhtml->text_buffer, iter, iter); + +} + +char *gtk_imhtml_get_markup(GtkIMHtml *imhtml) +{ + GtkTextIter start, end; + + gtk_text_buffer_get_start_iter(imhtml->text_buffer, &start); + gtk_text_buffer_get_end_iter(imhtml->text_buffer, &end); + return gtk_imhtml_get_markup_range(imhtml, &start, &end); +} + +char **gtk_imhtml_get_markup_lines(GtkIMHtml *imhtml) +{ + int i, j, lines; + GtkTextIter start, end; + char **ret; + + lines = gtk_text_buffer_get_line_count(imhtml->text_buffer); + ret = g_new0(char *, lines + 1); + gtk_text_buffer_get_start_iter(imhtml->text_buffer, &start); + end = start; + gtk_text_iter_forward_to_line_end(&end); + + for (i = 0, j = 0; i < lines; i++) { + if (gtk_text_iter_get_char(&start) != '\n') { + ret[j] = gtk_imhtml_get_markup_range(imhtml, &start, &end); + if (ret[j] != NULL) + j++; + } + + gtk_text_iter_forward_line(&start); + end = start; + gtk_text_iter_forward_to_line_end(&end); + } + + return ret; +} + +char *gtk_imhtml_get_text(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *stop) +{ + GString *str = g_string_new(""); + GtkTextIter iter, end; + gunichar c; + + if (start == NULL) + gtk_text_buffer_get_start_iter(imhtml->text_buffer, &iter); + else + iter = *start; + + if (stop == NULL) + gtk_text_buffer_get_end_iter(imhtml->text_buffer, &end); + else + end = *stop; + + gtk_text_iter_order(&iter, &end); + + while ((c = gtk_text_iter_get_char(&iter)) != 0 && !gtk_text_iter_equal(&iter, &end)) { + if (c == 0xFFFC) { + GtkTextChildAnchor* anchor; + char *text = NULL; + + anchor = gtk_text_iter_get_child_anchor(&iter); + if (anchor) + text = g_object_get_data(G_OBJECT(anchor), "gtkimhtml_plaintext"); + if (text) + str = g_string_append(str, text); + } else { + g_string_append_unichar(str, c); + } + gtk_text_iter_forward_char(&iter); + } + + return g_string_free(str, FALSE); +} + +void gtk_imhtml_set_funcs(GtkIMHtml *imhtml, GtkIMHtmlFuncs *f) +{ + g_return_if_fail(imhtml != NULL); + imhtml->funcs = f; +}