Mercurial > pidgin.yaz
diff src/gtkimhtml.c @ 1428:00b3d02a2168
[gaim-migrate @ 1438]
gtkhtml has gotten replaced by gtkimhtml.
committer: Tailor Script <tailor@pidgin.im>
author | Eric Warmenhoven <eric@warmenhoven.org> |
---|---|
date | Fri, 26 Jan 2001 02:02:36 +0000 |
parents | |
children | 5df631739769 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gtkimhtml.c Fri Jan 26 02:02:36 2001 +0000 @@ -0,0 +1,2918 @@ +/* + * GtkIMHtml + * + * Copyright (C) 2000, Eric Warmenhoven <warmenhoven@yahoo.com> + * + * 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 + * + */ + +#include "gtkimhtml.h" +#include <gtk/gtk.h> +#include <string.h> +#include <ctype.h> +#include <stdio.h> +#include <math.h> + +#include "pixmaps/angel.xpm" +#include "pixmaps/bigsmile.xpm" +#include "pixmaps/burp.xpm" +#include "pixmaps/crossedlips.xpm" +#include "pixmaps/cry.xpm" +#include "pixmaps/embarrassed.xpm" +#include "pixmaps/kiss.xpm" +#include "pixmaps/moneymouth.xpm" +#include "pixmaps/sad.xpm" +#include "pixmaps/scream.xpm" +#include "pixmaps/smile.xpm" +#include "pixmaps/smile8.xpm" +#include "pixmaps/think.xpm" +#include "pixmaps/tongue.xpm" +#include "pixmaps/wink.xpm" +#include "pixmaps/yell.xpm" + +#define DEFAULT_FONT_NAME "helvetica" +#define MAX_SIZE 7 + +gint font_sizes [] = { 80, 100, 120, 140, 200, 300, 400 }; + +#define BORDER_SIZE 3 +#define MIN_HEIGHT 20 +#define HR_HEIGHT 2 + +#define TYPE_TEXT 0 +#define TYPE_SMILEY 1 +#define TYPE_IMG 2 +#define TYPE_SEP 3 +#define TYPE_BR 4 +#define TYPE_COMMENT 5 + +typedef struct _GtkIMHtmlBit GtkIMHtmlBit; +typedef struct _FontDetail FontDetail; + +struct _GtkIMHtmlBit { + gint type; + + gchar *text; + GdkPixmap *pm; + GdkBitmap *bm; + + GdkFont *font; + GdkColor *fore; + GdkColor *back; + GdkColor *bg; + gboolean underline; + gboolean strike; + gchar *url; + + GList *chunks; +}; + +struct _FontDetail { + gushort size; + gchar *face; + GdkColor *fore; + GdkColor *back; +}; + +struct line_info { + gint x; + gint y; + gint width; + gint height; + gint ascent; + + gboolean selected; + gchar *sel_start; + gchar *sel_end; + + gchar *text; + GtkIMHtmlBit *bit; +}; + +struct url_widget { + gint x; + gint y; + gint width; + gint height; + gchar *url; +}; + +static GtkLayoutClass *parent_class = NULL; + +enum { + TARGET_STRING, + TARGET_TEXT, + TARGET_COMPOUND_TEXT +}; + +enum { + URL_CLICKED, + LAST_SIGNAL +}; +static guint signals [LAST_SIGNAL] = { 0 }; + +static void gtk_imhtml_draw_bit (GtkIMHtml *, GtkIMHtmlBit *); +static GdkColor *gtk_imhtml_get_color (const gchar *); +static gint gtk_imhtml_motion_notify_event (GtkWidget *, GdkEventMotion *); + +static void +gtk_imhtml_destroy (GtkObject *object) +{ + GtkIMHtml *imhtml; + + imhtml = GTK_IMHTML (object); + + while (imhtml->bits) { + GtkIMHtmlBit *bit = imhtml->bits->data; + imhtml->bits = g_list_remove (imhtml->bits, bit); + if (bit->text) + g_free (bit->text); + if (bit->font) + gdk_font_unref (bit->font); + if (bit->fore) + gdk_color_free (bit->fore); + if (bit->back) + gdk_color_free (bit->back); + if (bit->bg) + gdk_color_free (bit->bg); + if (bit->url) + g_free (bit->url); + if (bit->pm) + gdk_pixmap_unref (bit->pm); + if (bit->bm) + gdk_bitmap_unref (bit->bm); + while (bit->chunks) { + GtkObject *obj = bit->chunks->data; + struct line_info *li = gtk_object_get_user_data (obj); + if (li->text) + g_free (li->text); + g_free (li); + bit->chunks = g_list_remove (bit->chunks, obj); + } + g_free (bit); + } + + while (imhtml->urls) { + g_free (imhtml->urls->data); + imhtml->urls = g_list_remove (imhtml->urls, imhtml->urls->data); + } + + if (imhtml->selected_text) + g_string_free (imhtml->selected_text, TRUE); + + gdk_font_unref (imhtml->default_font); + gdk_color_free (imhtml->default_fg_color); + + gdk_cursor_destroy (imhtml->hand_cursor); + gdk_cursor_destroy (imhtml->arrow_cursor); + + g_hash_table_destroy (imhtml->smiley_hash); + + if (GTK_OBJECT_CLASS (parent_class)->destroy != NULL) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +gtk_imhtml_realize (GtkWidget *widget) +{ + GtkIMHtml *imhtml; + + g_return_if_fail (widget != NULL); + g_return_if_fail (GTK_IS_IMHTML (widget)); + + imhtml = GTK_IMHTML (widget); + + if (GTK_WIDGET_CLASS (parent_class)->realize) + (* GTK_WIDGET_CLASS (parent_class)->realize) (widget); + + widget->style = gtk_style_attach (widget->style, widget->window); + gdk_window_set_events (imhtml->layout.bin_window, + (gdk_window_get_events (imhtml->layout.bin_window) + | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + | GDK_POINTER_MOTION_MASK | GDK_EXPOSURE_MASK)); + + gdk_window_set_cursor (widget->window, imhtml->arrow_cursor); + + gdk_window_set_background (GTK_LAYOUT (imhtml)->bin_window, >K_WIDGET (imhtml)->style->white); +} + +static void +gtk_imhtml_unrealize (GtkWidget *widget) +{ + if (GTK_WIDGET_CLASS (parent_class)->unrealize) + (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); +} + +static void +gtk_imhtml_draw (GtkWidget *widget, + GdkRectangle *area) +{ + if (GTK_WIDGET_CLASS (parent_class)->draw) + (* GTK_WIDGET_CLASS (parent_class)->draw) (widget, area); +} + +static void +gtk_imhtml_style_set (GtkWidget *widget, + GtkStyle *style) +{ + GtkIMHtml *imhtml; + + g_return_if_fail (widget != NULL); + g_return_if_fail (GTK_IS_IMHTML (widget)); + if (!GTK_WIDGET_REALIZED (widget)) + return; + + imhtml = GTK_IMHTML (widget); + + gdk_window_set_background (GTK_LAYOUT (imhtml)->bin_window, >K_WIDGET (imhtml)->style->white); +} + +static gint +gtk_imhtml_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + if (GTK_WIDGET_CLASS (parent_class)->expose_event) + (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event); + + return TRUE; +} + +static void +gtk_imhtml_redraw_all (GtkIMHtml *imhtml) +{ + GList *b; + GtkIMHtmlBit *bit; + GtkAdjustment *vadj; + gfloat oldvalue; + + vadj = GTK_LAYOUT (imhtml)->vadjustment; + oldvalue = vadj->value / vadj->upper; + + b = imhtml->bits; + while (b) { + bit = b->data; + b = g_list_next (b); + while (bit->chunks) { + GtkObject *obj = bit->chunks->data; + struct line_info *li = gtk_object_get_user_data (obj); + if (li->text) + g_free (li->text); + g_free (li); + bit->chunks = g_list_remove (bit->chunks, obj); + } + } + + g_list_free (imhtml->line); + imhtml->line = NULL; + + while (imhtml->urls) { + g_free (imhtml->urls->data); + imhtml->urls = g_list_remove (imhtml->urls, imhtml->urls->data); + } + + while (GTK_LAYOUT (imhtml)->children) + gtk_container_remove (GTK_CONTAINER (imhtml), + *(GtkWidget **)GTK_LAYOUT (imhtml)->children->data); + + imhtml->x = BORDER_SIZE; + imhtml->y = BORDER_SIZE + 10; + imhtml->llheight = 0; + imhtml->llascent = 0; + + b = imhtml->bits; + while (b) { + gtk_imhtml_draw_bit (imhtml, b->data); + b = g_list_next (b); + } + + gtk_adjustment_set_value (vadj, vadj->upper * oldvalue); +} + +static void +gtk_imhtml_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkIMHtml *imhtml; + + g_return_if_fail (widget != NULL); + g_return_if_fail (GTK_IS_IMHTML (widget)); + g_return_if_fail (allocation != NULL); + + imhtml = GTK_IMHTML (widget); + + if (GTK_WIDGET_CLASS (parent_class)->size_allocate) + ( *GTK_WIDGET_CLASS (parent_class)->size_allocate) (widget, allocation); + + if (allocation->width == imhtml->xsize) + return; + + imhtml->x = BORDER_SIZE; + imhtml->y = BORDER_SIZE + 10; + imhtml->llheight = 0; + imhtml->llascent = 0; + + imhtml->xsize = allocation->width; + + gtk_imhtml_redraw_all (imhtml); +} + +static void +gtk_imhtml_select_none (GtkIMHtml *imhtml) +{ + GList *bits; + GList *chunks; + GtkIMHtmlBit *bit; + struct line_info *chunk; + GtkWidget *darea; + + g_return_if_fail (GTK_IS_IMHTML (imhtml)); + + bits = imhtml->bits; + while (bits) { + bit = bits->data; + chunks = bit->chunks; + + while (chunks) { + darea = chunks->data; + chunk = gtk_object_get_user_data (GTK_OBJECT (darea)); + + if (chunk->selected) + gtk_widget_queue_draw (darea); + chunk->selected = FALSE; + chunk->sel_start = chunk->sel_end = NULL; + + chunks = g_list_next (chunks); + } + + bits = g_list_next (bits); + } +} + +static gchar* +get_position (struct line_info *chunk, + gint x, + gboolean smileys) +{ + gint width = x - chunk->x; + gchar *text; + gchar *pos; + guint total = 0; + + switch (chunk->bit->type) { + case TYPE_TEXT: + case TYPE_COMMENT: + text = chunk->text; + break; + case TYPE_SMILEY: + if (smileys) + return NULL; + else + text = chunk->text; + break; + default: + return NULL; + break; + } + + if (width <= 0) + return text; + + for (pos = text; *pos != '\0'; pos++) { + gint char_width = gdk_text_width (chunk->bit->font, pos, 1); + if ((width > total) && (width <= total + char_width)) { + if (width < total + (char_width >> 1)) + return pos; + else + return ++pos; + } + total += char_width; + } + + return pos; +} + +static GString* +append_to_sel (GString *string, + struct line_info *chunk, + gboolean smileys) +{ + GString *new_string; + gchar *buf; + gchar *start; + gint length; + + switch (chunk->bit->type) { + case TYPE_TEXT: + case TYPE_COMMENT: + start = (chunk->sel_start == NULL) ? chunk->text : chunk->sel_start; + length = (chunk->sel_end == NULL) ? strlen (start) : chunk->sel_end - start; + if (length <= 0) + return string; + buf = g_strndup (start, length); + break; + case TYPE_SMILEY: + if (smileys) { + start = (chunk->sel_start == NULL) ? chunk->bit->text : chunk->sel_start; + length = (chunk->sel_end == NULL) ? strlen (start) : chunk->sel_end - start; + if (length <= 0) + return string; + buf = g_strndup (start, length); + } else { + start = (chunk->sel_start == NULL) ? chunk->text : chunk->sel_start; + length = (chunk->sel_end == NULL) ? strlen (start) : chunk->sel_end - start; + if (length <= 0) + return string; + buf = g_strndup (start, length); + } + break; + case TYPE_BR: + buf = g_strdup ("\n"); + break; + default: + return string; + break; + } + + new_string = g_string_append (string, buf); + g_free (buf); + + return new_string; +} + +#define COORDS_IN_CHUNK(xx, yy) (((xx) < chunk->x + chunk->width) && \ + ((yy) < chunk->y + chunk->height)) + +static void +gtk_imhtml_select_bits (GtkIMHtml *imhtml) +{ + GList *bits; + GList *chunks; + GtkIMHtmlBit *bit; + struct line_info *chunk; + GtkWidget *darea; + + guint startx = imhtml->sel_startx, + starty = imhtml->sel_starty, + endx = imhtml->sel_endx, + endy = imhtml->sel_endy; + gchar *new_pos; + gint selection = 0; + gboolean smileys = imhtml->smileys; + gboolean redraw = FALSE; + gboolean got_start = FALSE; + gboolean got_end = FALSE; + + g_return_if_fail (GTK_IS_IMHTML (imhtml)); + + if (!imhtml->selection) + return; + + if (imhtml->selected_text) { + g_string_free (imhtml->selected_text, TRUE); + imhtml->selected_text = g_string_new (""); + } + + bits = imhtml->bits; + while (bits) { + bit = bits->data; + chunks = bit->chunks; + + while (chunks) { + darea = chunks->data; + chunk = gtk_object_get_user_data (GTK_OBJECT (darea)); + + switch (selection) { + case 0: + if (COORDS_IN_CHUNK (startx, starty)) { + new_pos = get_position (chunk, startx, smileys); + if ( !chunk->selected || + (chunk->sel_start != new_pos) || + (chunk->sel_end != NULL)) + redraw = TRUE; + chunk->selected = TRUE; + chunk->sel_start = new_pos; + chunk->sel_end = NULL; + selection++; + got_start = TRUE; + } + + if (COORDS_IN_CHUNK (endx, endy)) { + if (got_start) { + new_pos = get_position (chunk, endx, smileys); + if (chunk->sel_end != new_pos) + redraw = TRUE; + if (chunk->sel_start > new_pos) { + chunk->sel_end = chunk->sel_start; + chunk->sel_start = new_pos; + } else + chunk->sel_end = new_pos; + selection = 2; + got_end = TRUE; + } else { + new_pos = get_position (chunk, endx, smileys); + if ( !chunk->selected || + (chunk->sel_start != new_pos) || + (chunk->sel_end != NULL)) + redraw = TRUE; + chunk->selected = TRUE; + chunk->sel_start = new_pos; + chunk->sel_end = NULL; + selection++; + got_end = TRUE; + } + } else if (!COORDS_IN_CHUNK (startx, starty) && !got_start) { + if (chunk->selected) + redraw = TRUE; + chunk->selected = FALSE; + chunk->sel_start = chunk->text; + chunk->sel_end = NULL; + } + + break; + case 1: + if (!got_start && COORDS_IN_CHUNK (startx, starty)) { + new_pos = get_position (chunk, startx, smileys); + if ( !chunk->selected || + (chunk->sel_end != new_pos) || + (chunk->sel_start != chunk->text)) + redraw = TRUE; + chunk->selected = TRUE; + chunk->sel_start = chunk->text; + chunk->sel_end = new_pos; + selection++; + got_start = TRUE; + } else if (!got_end && COORDS_IN_CHUNK (endx, endy)) { + new_pos = get_position (chunk, endx, smileys); + if ( !chunk->selected || + (chunk->sel_end != new_pos) || + (chunk->sel_start != chunk->text)) + redraw = TRUE; + chunk->selected = TRUE; + chunk->sel_start = chunk->text; + chunk->sel_end = new_pos; + selection++; + got_end = TRUE; + } else { + if ( !chunk->selected || + (chunk->sel_end != new_pos) || + (chunk->sel_start != NULL)) + redraw = TRUE; + chunk->selected = TRUE; + chunk->sel_start = chunk->text; + chunk->sel_end = NULL; + } + + break; + case 2: + if ( chunk->selected || + (chunk->sel_start != chunk->text) || + (chunk->sel_end != NULL)) + redraw = TRUE; + chunk->selected = FALSE; + chunk->sel_start = chunk->text; + chunk->sel_end = NULL; + break; + } + + if (chunk->selected == TRUE) + imhtml->selected_text = append_to_sel (imhtml->selected_text, + chunk, smileys); + + if (redraw) { + gtk_widget_queue_draw (darea); + redraw = FALSE; + } + + chunks = g_list_next (chunks); + } + + bits = g_list_next (bits); + } +} + +static gint +scroll_timeout (GtkIMHtml *imhtml) +{ + GdkEventMotion event; + gint x, y; + GdkModifierType mask; + + imhtml->scroll_timer = 0; + + gdk_window_get_pointer (imhtml->layout.bin_window, &x, &y, &mask); + + if (mask & GDK_BUTTON1_MASK) { + event.is_hint = 0; + event.x = x; + event.y = y; + event.state = mask; + + gtk_imhtml_motion_notify_event (GTK_WIDGET (imhtml), &event); + } + + return FALSE; +} + +static gint +gtk_imhtml_motion_notify_event (GtkWidget *widget, + GdkEventMotion *event) +{ + gint x, y; + GdkModifierType state; + GtkIMHtml *imhtml = GTK_IMHTML (widget); + GtkAdjustment *vadj = GTK_LAYOUT (widget)->vadjustment; + GtkAdjustment *hadj = GTK_LAYOUT (widget)->hadjustment; + + if (event->is_hint) + gdk_window_get_pointer (event->window, &x, &y, &state); + else { + x = event->x + hadj->value; + y = event->y + vadj->value; + state = event->state; + } + + if (state & GDK_BUTTON1_MASK) { + gint diff; + gint height = vadj->page_size; + gint yy = y - vadj->value; + + if (((yy < 0) || (yy > height)) && + (imhtml->scroll_timer == 0) && + (vadj->upper > vadj->page_size)) { + imhtml->scroll_timer = gtk_timeout_add (100, + (GtkFunction) scroll_timeout, + imhtml); + diff = (yy < 0) ? (yy >> 1) : ((yy - height) >> 1); + gtk_adjustment_set_value (vadj, + MIN (vadj->value + diff, vadj->upper - height + 20)); + } + + if (imhtml->selection) { + imhtml->sel_endx = MAX (x, 0); + imhtml->sel_endy = MAX (y, 0); + gtk_imhtml_select_bits (imhtml); + } + } else { + GList *urls = imhtml->urls; + struct url_widget *uw; + + while (urls) { + uw = (struct url_widget *) urls->data; + if ((x > uw->x) && (x < uw->x + uw->width) && + (y > uw->y) && (y < uw->y + uw->height)) { + gdk_window_set_cursor (imhtml->layout.bin_window, imhtml->hand_cursor); + return TRUE; + } + urls = g_list_next (urls); + } + } + + gdk_window_set_cursor (imhtml->layout.bin_window, imhtml->arrow_cursor); + + return TRUE; +} + +static gint +gtk_imhtml_button_press_event (GtkWidget *widget, + GdkEventButton *event) +{ + GtkIMHtml *imhtml = GTK_IMHTML (widget); + GtkAdjustment *vadj = GTK_LAYOUT (widget)->vadjustment; + GtkAdjustment *hadj = GTK_LAYOUT (widget)->hadjustment; + gint x, y; + + if (event->button == 1) { + x = event->x + hadj->value; + y = event->y + vadj->value; + + imhtml->sel_startx = x; + imhtml->sel_starty = y; + imhtml->selection = TRUE; + gtk_imhtml_select_none (imhtml); + } + + return TRUE; +} + +static gint +gtk_imhtml_button_release_event (GtkWidget *widget, + GdkEventButton *event) +{ + GtkIMHtml *imhtml = GTK_IMHTML (widget); + GtkAdjustment *vadj = GTK_LAYOUT (widget)->vadjustment; + GtkAdjustment *hadj = GTK_LAYOUT (widget)->hadjustment; + gint x, y; + + if ((event->button == 1) && imhtml->selection) { + x = event->x + hadj->value; + y = event->y + vadj->value; + + if ((x == imhtml->sel_startx) && (y == imhtml->sel_starty)) { + imhtml->sel_startx = imhtml->sel_starty = 0; + imhtml->selection = FALSE; + gtk_imhtml_select_none (imhtml); + } else { + imhtml->sel_endx = MAX (x, 0); + imhtml->sel_endy = MAX (y, 0); + gtk_imhtml_select_bits (imhtml); + } + + gtk_selection_owner_set (widget, GDK_SELECTION_PRIMARY, event->time); + } + + return TRUE; +} + +static void +gtk_imhtml_selection_get (GtkWidget *widget, + GtkSelectionData *sel_data, + guint sel_info, + guint32 time) +{ + GtkIMHtml *imhtml; + gchar *string; + gint length; + + g_return_if_fail (widget != NULL); + g_return_if_fail (GTK_IS_IMHTML (widget)); + g_return_if_fail (sel_data->selection == GDK_SELECTION_PRIMARY); + + imhtml = GTK_IMHTML (widget); + + g_return_if_fail (imhtml->selected_text != NULL); + g_return_if_fail (imhtml->selected_text->str != NULL); + + if (imhtml->selected_text->len <= 0) { + string = NULL; + length = 0; + } else { + string = g_strdup (imhtml->selected_text->str); + length = strlen (string); + } + + if (sel_info == TARGET_STRING) { + gtk_selection_data_set (sel_data, + GDK_SELECTION_TYPE_STRING, + 8 * sizeof (gchar), + (guchar *) string, + length); + } else if ((sel_info == TARGET_TEXT) || (sel_info == TARGET_COMPOUND_TEXT)) { + guchar *text; + GdkAtom encoding; + gint format; + gint new_length; + + gdk_string_to_compound_text (string, &encoding, &format, &text, &new_length); + gtk_selection_data_set (sel_data, encoding, format, text, new_length); + gdk_free_compound_text (text); + } + + if (string) + g_free (string); +} + +static gint +gtk_imhtml_selection_clear_event (GtkWidget *widget, + GdkEventSelection *event) +{ + GtkIMHtml *imhtml; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_IMHTML (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + g_return_val_if_fail (event->selection == GDK_SELECTION_PRIMARY, TRUE); + + if (!gtk_selection_clear (widget, event)) + return FALSE; + + imhtml = GTK_IMHTML (widget); + + gtk_imhtml_select_none (imhtml); + + return TRUE; +} + +static void +gtk_imhtml_set_scroll_adjustments (GtkLayout *layout, + GtkAdjustment *hadj, + GtkAdjustment *vadj) +{ + if (parent_class->set_scroll_adjustments) + (* parent_class->set_scroll_adjustments) (layout, hadj, vadj); +} + +static void +gtk_imhtml_class_init (GtkIMHtmlClass *class) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkLayoutClass *layout_class; + + object_class = (GtkObjectClass*) class; + widget_class = (GtkWidgetClass*) class; + layout_class = (GtkLayoutClass*) class; + + parent_class = gtk_type_class (GTK_TYPE_LAYOUT); + + signals [URL_CLICKED] = + gtk_signal_new ("url_clicked", + GTK_RUN_FIRST, + object_class->type, + GTK_SIGNAL_OFFSET (GtkIMHtmlClass, url_clicked), + gtk_marshal_NONE__POINTER, + GTK_TYPE_NONE, 1, + GTK_TYPE_POINTER); + + gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL); + + object_class->destroy = gtk_imhtml_destroy; + + widget_class->realize = gtk_imhtml_realize; + widget_class->unrealize = gtk_imhtml_unrealize; + widget_class->draw = gtk_imhtml_draw; + widget_class->style_set = gtk_imhtml_style_set; + widget_class->expose_event = gtk_imhtml_expose; + widget_class->size_allocate = gtk_imhtml_size_allocate; + widget_class->motion_notify_event = gtk_imhtml_motion_notify_event; + widget_class->button_press_event = gtk_imhtml_button_press_event; + widget_class->button_release_event = gtk_imhtml_button_release_event; + widget_class->selection_get = gtk_imhtml_selection_get; + widget_class->selection_clear_event = gtk_imhtml_selection_clear_event; + + layout_class->set_scroll_adjustments = gtk_imhtml_set_scroll_adjustments; +} + +static GdkFont* +gtk_imhtml_font_load (GtkIMHtml *imhtml, + gchar *name, + gboolean bold, + gboolean italics, + gint fontsize) +{ + gchar buf [16 * 1024]; + GdkFont *font; + gint size = fontsize ? font_sizes [MIN (fontsize, MAX_SIZE) - 1] : 120; + + g_snprintf (buf, sizeof (buf), "-*-%s-%s-%c-*-*-*-%d-*-*-*-*-*-*", + name ? name : DEFAULT_FONT_NAME, + bold ? "bold" : "medium", + italics ? 'i' : 'r', + size); + font = gdk_font_load (buf); + if (font) + return font; + + if (italics) { + g_snprintf (buf, sizeof (buf), "-*-%s-%s-%c-*-*-*-%d-*-*-*-*-*-*", + name ? name : DEFAULT_FONT_NAME, + bold ? "bold" : "medium", + 'o', + size); + font = gdk_font_load (buf); + if (font) + return font; + + if (bold) { + g_snprintf (buf, sizeof (buf), "-*-%s-%s-%c-*-*-*-%d-*-*-*-*-*-*", + name ? name : DEFAULT_FONT_NAME, + "bold", + 'r', + size); + font = gdk_font_load (buf); + if (font) + return font; + } + } + + if (bold) { + g_snprintf (buf, sizeof (buf), "-*-%s-%s-%c-*-*-*-%d-*-*-*-*-*-*", + name ? name : DEFAULT_FONT_NAME, + "medium", + italics ? 'i' : 'r', + size); + font = gdk_font_load (buf); + if (font) + return font; + + if (italics) { + g_snprintf (buf, sizeof (buf), "-*-%s-%s-%c-*-*-*-%d-*-*-*-*-*-*", + name ? name : DEFAULT_FONT_NAME, + "medium", + 'o', + size); + font = gdk_font_load (buf); + if (font) + return font; + } + } + + if (!bold && !italics) { + g_snprintf (buf, sizeof (buf), "-*-%s-medium-r-*-*-*-%d-*-*-*-*-*-*", + name ? name : DEFAULT_FONT_NAME, + size); + font = gdk_font_load (buf); + if (font) + return font; + } + + g_snprintf (buf, sizeof (buf), "-*-%s-medium-r-*-*-*-%d-*-*-*-*-*-*", + DEFAULT_FONT_NAME, + size); + font = gdk_font_load (buf); + if (font) + return font; + + if (imhtml->default_font) + return gdk_font_ref (imhtml->default_font); + + return NULL; +} + +static void +gtk_imhtml_init (GtkIMHtml *imhtml) +{ + static const GtkTargetEntry targets [] = { + { "STRING", 0, TARGET_STRING }, + { "TEXT", 0, TARGET_TEXT }, + { "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT } + }; + + imhtml->default_font = gtk_imhtml_font_load (imhtml, NULL, FALSE, FALSE, 0); + if (imhtml->default_font == NULL) + g_warning ("GtkIMHtml: Could not load default font!"); + imhtml->default_fg_color = gdk_color_copy (>K_WIDGET (imhtml)->style->black); + imhtml->hand_cursor = gdk_cursor_new (GDK_HAND2); + imhtml->arrow_cursor = gdk_cursor_new (GDK_LEFT_PTR); + + GTK_WIDGET_SET_FLAGS (GTK_WIDGET (imhtml), GTK_CAN_FOCUS); + gtk_selection_add_targets (GTK_WIDGET (imhtml), GDK_SELECTION_PRIMARY, targets, 3); +} + +GtkType +gtk_imhtml_get_type (void) +{ + static GtkType imhtml_type = 0; + + if (!imhtml_type) { + static const GtkTypeInfo imhtml_info = { + "GtkIMHtml", + sizeof (GtkIMHtml), + sizeof (GtkIMHtmlClass), + (GtkClassInitFunc) gtk_imhtml_class_init, + (GtkObjectInitFunc) gtk_imhtml_init, + NULL, + NULL, + NULL + }; + + imhtml_type = gtk_type_unique (GTK_TYPE_LAYOUT, &imhtml_info); + } + + return imhtml_type; +} + +static void +gtk_imhtml_init_smiley_hash (GtkIMHtml *imhtml) +{ + g_return_if_fail (imhtml != NULL); + g_return_if_fail (GTK_IS_IMHTML (imhtml)); + + imhtml->smiley_hash = g_hash_table_new (g_str_hash, g_str_equal); + + gtk_imhtml_associate_smiley (imhtml, ":)", smile_xpm); + gtk_imhtml_associate_smiley (imhtml, ":-)", smile_xpm); + + gtk_imhtml_associate_smiley (imhtml, ":(", sad_xpm); + gtk_imhtml_associate_smiley (imhtml, ":-(", sad_xpm); + + gtk_imhtml_associate_smiley (imhtml, ";)", wink_xpm); + gtk_imhtml_associate_smiley (imhtml, ";-)", wink_xpm); + + gtk_imhtml_associate_smiley (imhtml, ":-p", tongue_xpm); + gtk_imhtml_associate_smiley (imhtml, ":-P", tongue_xpm); + + gtk_imhtml_associate_smiley (imhtml, "=-O", scream_xpm); + gtk_imhtml_associate_smiley (imhtml, ":-*", kiss_xpm); + gtk_imhtml_associate_smiley (imhtml, ">:o", yell_xpm); + gtk_imhtml_associate_smiley (imhtml, "8-)", smile8_xpm); + gtk_imhtml_associate_smiley (imhtml, ":-$", moneymouth_xpm); + gtk_imhtml_associate_smiley (imhtml, ":-!", burp_xpm); + gtk_imhtml_associate_smiley (imhtml, ":-[", embarrassed_xpm); + gtk_imhtml_associate_smiley (imhtml, ":'(", cry_xpm); + + gtk_imhtml_associate_smiley (imhtml, ":-/", think_xpm); + gtk_imhtml_associate_smiley (imhtml, ":-\\", think_xpm); + + gtk_imhtml_associate_smiley (imhtml, ":-X", crossedlips_xpm); + gtk_imhtml_associate_smiley (imhtml, ":-D", bigsmile_xpm); + gtk_imhtml_associate_smiley (imhtml, "O:-)", angel_xpm); +} + +GtkWidget* +gtk_imhtml_new (GtkAdjustment *hadj, + GtkAdjustment *vadj) +{ + GtkIMHtml *imhtml = gtk_type_new (GTK_TYPE_IMHTML); + + gtk_imhtml_set_adjustments (imhtml, hadj, vadj); + + imhtml->bits = NULL; + imhtml->urls = NULL; + + imhtml->x = BORDER_SIZE; + imhtml->y = BORDER_SIZE + 10; + imhtml->llheight = 0; + imhtml->llascent = 0; + imhtml->line = NULL; + + imhtml->selected_text = g_string_new (""); + imhtml->scroll_timer = 0; + + imhtml->img = NULL; + + imhtml->smileys = TRUE; + imhtml->comments = FALSE; + + imhtml->smin = G_MAXINT; + imhtml->smax = 0; + gtk_imhtml_init_smiley_hash (imhtml); + + return GTK_WIDGET (imhtml); +} + +void +gtk_imhtml_set_adjustments (GtkIMHtml *imhtml, + GtkAdjustment *hadj, + GtkAdjustment *vadj) +{ + gtk_layout_set_hadjustment (GTK_LAYOUT (imhtml), hadj); + gtk_layout_set_vadjustment (GTK_LAYOUT (imhtml), vadj); +} + +void +gtk_imhtml_set_defaults (GtkIMHtml *imhtml, + GdkFont *font, + GdkColor *fg_color) +{ + g_return_if_fail (imhtml != NULL); + g_return_if_fail (GTK_IS_IMHTML (imhtml)); + + if (font) { + if (imhtml->default_font) + gdk_font_unref (imhtml->default_font); + imhtml->default_font = gdk_font_ref (font); + } + + if (fg_color) { + if (imhtml->default_fg_color) + gdk_color_free (imhtml->default_fg_color); + imhtml->default_fg_color = gdk_color_copy (fg_color); + } +} + +void +gtk_imhtml_set_img_handler (GtkIMHtml *imhtml, + GtkIMHtmlImage handler) +{ + g_return_if_fail (imhtml != NULL); + g_return_if_fail (GTK_IS_IMHTML (imhtml)); + + imhtml->img = handler; +} + +void +gtk_imhtml_associate_smiley (GtkIMHtml *imhtml, + gchar *text, + gchar **xpm) +{ + g_return_if_fail (imhtml != NULL); + g_return_if_fail (GTK_IS_IMHTML (imhtml)); + g_return_if_fail (text != NULL); + + if (strlen (text) < imhtml->smin) + imhtml->smin = strlen (text); + + if (strlen (text) > imhtml->smax) + imhtml->smax = strlen (text); + + if (xpm == NULL) + g_hash_table_remove (imhtml->smiley_hash, text); + else + g_hash_table_insert (imhtml->smiley_hash, text, xpm); +} + +static gint +draw_text (GtkWidget *widget, + GdkEvent *event, + gpointer data) +{ + GtkIMHtmlBit *bit; + struct line_info *line; + GdkGC *gc; + GdkColormap *cmap; + + line = gtk_object_get_user_data (GTK_OBJECT (widget)); + bit = line->bit; + gc = gdk_gc_new (widget->window); + cmap = gdk_colormap_new (gdk_visual_get_best (), FALSE); + + if (bit->bg != NULL) { + gdk_color_alloc (cmap, bit->bg); + gdk_gc_set_foreground (gc, bit->bg); + } else + gdk_gc_copy (gc, widget->style->white_gc); + + gdk_draw_rectangle (widget->window, gc, TRUE, 0, 0, + widget->allocation.width, + widget->allocation.height); + + if (!line->text) { + gdk_colormap_unref (cmap); + gdk_gc_unref (gc); + return TRUE; + } + + if (bit->back != NULL) { + gdk_color_alloc (cmap, bit->back); + gdk_gc_set_foreground (gc, bit->back); + gdk_draw_rectangle (widget->window, gc, TRUE, 0, 0, + gdk_string_width (bit->font, line->text), + widget->allocation.height); + } + + if (line->selected) { + gint width, x; + gchar *start, *end; + GdkColor col; + + if ((line->sel_start > line->sel_end) && (line->sel_end != NULL)) { + start = line->sel_end; + end = line->sel_start; + } else { + start = line->sel_start; + end = line->sel_end; + } + + if (start == NULL) + x = 0; + else + x = gdk_text_width (bit->font, line->text, start - line->text); + + if (end == NULL) + width = gdk_string_width (bit->font, line->text) - x; + else + width = gdk_text_width (bit->font, line->text, end - line->text) - x; + + col.red = col.green = col.blue = 0xc000; + gdk_color_alloc (cmap, &col); + gdk_gc_set_foreground (gc, &col); + + gdk_draw_rectangle (widget->window, gc, TRUE, x, 0, + width, widget->allocation.height); + } + + if (bit->url) { + GdkColor *tc = gtk_imhtml_get_color ("#0000a0"); + gdk_color_alloc (cmap, tc); + gdk_gc_set_foreground (gc, tc); + gdk_color_free (tc); + } else if (bit->fore) { + gdk_color_alloc (cmap, bit->fore); + gdk_gc_set_foreground (gc, bit->fore); + } else + gdk_gc_copy (gc, widget->style->black_gc); + + gdk_draw_string (widget->window, bit->font, gc, + 0, line->ascent, line->text); + + if (bit->underline || bit->url) + gdk_draw_rectangle (widget->window, gc, TRUE, + 0, line->ascent + 1, + gdk_string_width (bit->font, line->text), 1); + if (bit->strike) + gdk_draw_rectangle (widget->window, gc, TRUE, + 0, line->ascent - (bit->font->ascent >> 1), + gdk_string_width (bit->font, line->text), 1); + + gdk_colormap_unref (cmap); + gdk_gc_unref (gc); + + return TRUE; +} + +static gint +draw_img (GtkWidget *widget, + GdkEvent *event, + gpointer data) +{ + GtkIMHtmlBit *bit; + struct line_info *line; + GdkGC *gc; + GdkColormap *cmap; + gint width, height, hoff; + + line = gtk_object_get_user_data (GTK_OBJECT (widget)); + bit = line->bit; + gdk_window_get_size (bit->pm, &width, &height); + hoff = (widget->allocation.height - height) / 2; + gc = gdk_gc_new (widget->window); + cmap = gdk_colormap_new (gdk_visual_get_best (), FALSE); + + if (bit->bg != NULL) { + gdk_color_alloc (cmap, bit->bg); + gdk_gc_set_foreground (gc, bit->bg); + } else + gdk_gc_copy (gc, widget->style->white_gc); + + gdk_draw_rectangle (widget->window, gc, TRUE, 0, 0, + widget->allocation.width, + widget->allocation.height); + + if (bit->back != NULL) { + gdk_color_alloc (cmap, bit->back); + gdk_gc_set_foreground (gc, bit->back); + gdk_draw_rectangle (widget->window, gc, TRUE, 0, 0, + width, widget->allocation.height); + } + + gdk_draw_pixmap (widget->window, gc, bit->pm, 0, 0, 0, hoff, -1, -1); + + gdk_colormap_unref (cmap); + gdk_gc_unref (gc); + + return TRUE; +} + +static gint +draw_line (GtkWidget *widget, + GdkEvent *event, + gpointer data) +{ + GtkIMHtmlBit *bit; + GdkDrawable *drawable; + GdkColormap *cmap; + GdkGC *gc; + guint max_width; + guint max_height; + + bit = data; + drawable = widget->window; + cmap = gdk_colormap_new (gdk_visual_get_best (), FALSE); + gc = gdk_gc_new (drawable); + + if (bit->bg != NULL) { + gdk_color_alloc (cmap, bit->bg); + gdk_gc_set_foreground (gc, bit->bg); + + gdk_draw_rectangle (widget->window, gc, TRUE, 0, 0, + widget->allocation.width, + widget->allocation.height); + } + + gdk_gc_copy (gc, widget->style->black_gc); + + max_width = widget->allocation.width; + max_height = widget->allocation.height / 2; + + gdk_draw_rectangle (drawable, gc, + TRUE, + 0, max_height / 2, + max_width, max_height); + + gdk_colormap_unref (cmap); + gdk_gc_unref (gc); + + return TRUE; +} + +static gint +click_event_box (GtkBin *bin, + GdkEventButton *event, + GtkIMHtml *imhtml) +{ + struct line_info *li = gtk_object_get_user_data (GTK_OBJECT (bin->child)); + + if (event->button == 1 && event->type == GDK_BUTTON_PRESS) + gtk_signal_emit (GTK_OBJECT (imhtml), signals [URL_CLICKED], li->bit->url); + + return TRUE; +} + +static void +new_line (GtkIMHtml *imhtml) +{ + GList *last = g_list_last (imhtml->line); + GtkWidget *widget; + struct line_info *li; + + if (last) { + widget = last->data; + li = gtk_object_get_user_data (GTK_OBJECT (widget)); + if (li->x + li->width != imhtml->xsize - BORDER_SIZE) { + li->width = imhtml->xsize - BORDER_SIZE - li->x; + gtk_widget_set_usize (widget, li->width, li->height); + } + } + + last = imhtml->line; + if (last) { + widget = last->data; + li = gtk_object_get_user_data (GTK_OBJECT (widget)); + if (li->height < MIN_HEIGHT) { + while (last) { + gint diff; + widget = last->data; + li = gtk_object_get_user_data (GTK_OBJECT (widget)); + diff = MIN_HEIGHT - li->height; + li->height = MIN_HEIGHT; + gtk_widget_set_usize (widget, li->width, li->height); + li->ascent += diff >> 1; + last = g_list_next (last); + } + imhtml->llheight = MIN_HEIGHT; + } + } + + g_list_free (imhtml->line); + imhtml->line = NULL; + + imhtml->x = BORDER_SIZE; + imhtml->y += imhtml->llheight; +} + +static void +backwards_update (GtkIMHtml *imhtml, + GtkIMHtmlBit *bit, + gint height, + gint ascent) +{ + gint diff; + GList *ls = NULL; + struct line_info *li; + struct url_widget *uw; + + if (height > imhtml->llheight) { + diff = height - imhtml->llheight; + + ls = imhtml->line; + while (ls) { + GtkWidget *data = ls->data; + li = gtk_object_get_user_data (GTK_OBJECT (data)); + li->height += diff; + if (ascent) + li->ascent = ascent; + else + li->ascent += diff >> 1; + gtk_widget_set_usize (data, li->width, li->height); + ls = g_list_next (ls); + } + + ls = imhtml->urls; + while (ls) { + uw = ls->data; + if (uw->y + diff > imhtml->y) + uw->y += diff; + ls = g_list_next (ls); + } + + imhtml->llheight = height; + if (ascent) + imhtml->llascent = ascent; + else + imhtml->llascent += diff >> 1; + } +} + +static GtkTooltips *tips = NULL; + +static void +add_text_renderer (GtkIMHtml *imhtml, + GtkIMHtmlBit *bit, + gchar *text) +{ + GtkWidget *darea; + GtkWidget *eventbox; + struct line_info *li; + struct url_widget *uw; + gint width; + + if (text) + width = gdk_string_width (bit->font, text); + else + width = 0; + + darea = gtk_drawing_area_new (); + gtk_widget_set_usize (darea, width, imhtml->llheight); + gtk_signal_connect (GTK_OBJECT (darea), "expose_event", GTK_SIGNAL_FUNC (draw_text), NULL); + + li = g_new0 (struct line_info, 1); + li->x = imhtml->x; + li->y = imhtml->y; + li->width = width; + li->height = imhtml->llheight; + if (text) + li->ascent = MAX (imhtml->llascent, bit->font->ascent); + else + li->ascent = 0; + li->text = text; + li->bit = bit; + + gtk_object_set_user_data (GTK_OBJECT (darea), li); + + if (bit->url) { + eventbox = gtk_event_box_new (); + gtk_layout_put (GTK_LAYOUT (imhtml), eventbox, imhtml->x, imhtml->y); + gtk_signal_connect (GTK_OBJECT (eventbox), "button_press_event", + GTK_SIGNAL_FUNC (click_event_box), imhtml); + gtk_widget_show (eventbox); + + gtk_container_add (GTK_CONTAINER (eventbox), darea); + + uw = g_new0 (struct url_widget, 1); + uw->x = imhtml->x; + uw->y = imhtml->y; + uw->width = width; + uw->height = imhtml->llheight; + uw->url = bit->url; + imhtml->urls = g_list_append (imhtml->urls, uw); + + if (!tips) + tips = gtk_tooltips_new (); + gtk_tooltips_set_tip (tips, eventbox, bit->url, ""); + } else { + gtk_layout_put (GTK_LAYOUT (imhtml), darea, imhtml->x, imhtml->y); + } + + bit->chunks = g_list_append (bit->chunks, darea); + imhtml->line = g_list_append (imhtml->line, darea); + gtk_widget_show (darea); + + if (bit->bg) { + GdkColormap *cmap; + + cmap = gdk_colormap_new (gdk_visual_get_best (), FALSE); + gdk_color_alloc (cmap, bit->bg); + gdk_window_set_background (darea->window, bit->bg); + gdk_colormap_unref (cmap); + } else + gdk_window_set_background (darea->window, &darea->style->white); +} + +static void +add_img_renderer (GtkIMHtml *imhtml, + GtkIMHtmlBit *bit) +{ + GtkWidget *darea; + GtkWidget *eventbox; + struct line_info *li; + struct url_widget *uw; + gint width; + + gdk_window_get_size (bit->pm, &width, NULL); + + darea = gtk_drawing_area_new (); + gtk_widget_set_usize (darea, width, imhtml->llheight); + gtk_signal_connect (GTK_OBJECT (darea), "expose_event", GTK_SIGNAL_FUNC (draw_img), NULL); + + li = g_new0 (struct line_info, 1); + li->x = imhtml->x; + li->y = imhtml->y; + li->width = width; + li->height = imhtml->llheight; + li->ascent = 0; + li->bit = bit; + + gtk_object_set_user_data (GTK_OBJECT (darea), li); + + if (bit->url) { + eventbox = gtk_event_box_new (); + gtk_layout_put (GTK_LAYOUT (imhtml), eventbox, imhtml->x, imhtml->y); + gtk_signal_connect (GTK_OBJECT (eventbox), "button_press_event", + GTK_SIGNAL_FUNC (click_event_box), imhtml); + gtk_widget_show (eventbox); + + gtk_container_add (GTK_CONTAINER (eventbox), darea); + + uw = g_new0 (struct url_widget, 1); + uw->x = imhtml->x; + uw->y = imhtml->y; + uw->width = width; + uw->height = imhtml->llheight; + uw->url = bit->url; + imhtml->urls = g_list_append (imhtml->urls, uw); + + if (!tips) + tips = gtk_tooltips_new (); + gtk_tooltips_set_tip (tips, eventbox, bit->url, ""); + } else { + gtk_layout_put (GTK_LAYOUT (imhtml), darea, imhtml->x, imhtml->y); + } + + bit->chunks = g_list_append (bit->chunks, darea); + imhtml->line = g_list_append (imhtml->line, darea); + gtk_widget_show (darea); + + if (bit->bg) { + GdkColormap *cmap; + + cmap = gdk_colormap_new (gdk_visual_get_best (), FALSE); + gdk_color_alloc (cmap, bit->bg); + gdk_window_set_background (darea->window, bit->bg); + gdk_colormap_unref (cmap); + } else + gdk_window_set_background (darea->window, &darea->style->white); + + imhtml->x += width; +} + +static void +gtk_imhtml_draw_bit (GtkIMHtml *imhtml, + GtkIMHtmlBit *bit) +{ + gint width, height; + GdkWindow *window; + GdkGC *gc; + + g_return_if_fail (imhtml != NULL); + g_return_if_fail (GTK_IS_IMHTML (imhtml)); + g_return_if_fail (bit != NULL); + + window = GTK_LAYOUT (imhtml)->bin_window; + gc = gdk_gc_new (window); + + if ( (bit->type == TYPE_TEXT) || + ((bit->type == TYPE_SMILEY) && !imhtml->smileys) || + ((bit->type == TYPE_COMMENT) && imhtml->comments)) { + gchar *copy = g_strdup (bit->text); + gint pos = 0; + gboolean seenspace = FALSE; + gchar *tmp; + + height = bit->font->ascent + bit->font->descent; + width = gdk_string_width (bit->font, bit->text); + + if ((imhtml->x != BORDER_SIZE) && + ((imhtml->x + width + BORDER_SIZE + BORDER_SIZE + 5) > imhtml->xsize)) { + gint remain = imhtml->xsize - imhtml->x - BORDER_SIZE - BORDER_SIZE - 5; + while (gdk_text_width (bit->font, copy, pos) < remain) { + if (copy [pos] == ' ') + seenspace = TRUE; + pos++; + } + if (seenspace) { + while (copy [pos - 1] != ' ') pos--; + + tmp = g_strndup (copy, pos); + + backwards_update (imhtml, bit, height, bit->font->ascent); + add_text_renderer (imhtml, bit, tmp); + } else + pos = 0; + seenspace = FALSE; + new_line (imhtml); + imhtml->llheight = 0; + imhtml->llascent = 0; + } + + backwards_update (imhtml, bit, height, bit->font->ascent); + + while (pos < strlen (bit->text)) { + width = gdk_string_width (bit->font, copy + pos); + if (imhtml->x + width + BORDER_SIZE + BORDER_SIZE + 5 > imhtml->xsize) { + gint newpos = 0; + gint remain = imhtml->xsize - imhtml->x - BORDER_SIZE - BORDER_SIZE - 5; + while (gdk_text_width (bit->font, copy + pos, newpos) < remain) { + if (copy [pos + newpos] == ' ') + seenspace = TRUE; + newpos++; + } + + if (seenspace) + while (copy [pos + newpos - 1] != ' ') newpos--; + + if (newpos == 0) + break; + + tmp = g_strndup (copy + pos, newpos); + pos += newpos; + + add_text_renderer (imhtml, bit, tmp); + + seenspace = FALSE; + new_line (imhtml); + } else { + tmp = g_strdup (copy + pos); + + add_text_renderer (imhtml, bit, tmp); + + pos = strlen (bit->text); + + imhtml->x += width; + } + } + + g_free (copy); + } else if ((bit->type == TYPE_SMILEY) || (bit->type == TYPE_IMG)) { + gdk_window_get_size (bit->pm, &width, &height); + + if ((imhtml->x != BORDER_SIZE) && + ((imhtml->x + width + BORDER_SIZE + BORDER_SIZE + 5) > imhtml->xsize)) { + new_line (imhtml); + imhtml->llheight = 0; + imhtml->llascent = 0; + } else + backwards_update (imhtml, bit, height, height * 3 / 4); + + add_img_renderer (imhtml, bit); + } else if (bit->type == TYPE_BR) { + new_line (imhtml); + imhtml->llheight = 0; + imhtml->llascent = 0; + add_text_renderer (imhtml, bit, NULL); + } else if (bit->type == TYPE_SEP) { + GtkWidget *darea; + if (imhtml->llheight) { + new_line (imhtml); + imhtml->llheight = 0; + imhtml->llascent = 0; + } + darea = gtk_drawing_area_new (); + gtk_widget_set_usize (darea, imhtml->xsize - (BORDER_SIZE * 2), HR_HEIGHT * 2); + gtk_layout_put (GTK_LAYOUT (imhtml), darea, imhtml->x, imhtml->y); + gtk_signal_connect (GTK_OBJECT (darea), "expose_event", + GTK_SIGNAL_FUNC (draw_line), bit); + gtk_widget_show (darea); + imhtml->llheight = HR_HEIGHT * 2; + new_line (imhtml); + imhtml->llheight = 0; + imhtml->llascent = 0; + add_text_renderer (imhtml, bit, NULL); + } + + gtk_layout_set_size (GTK_LAYOUT (imhtml), imhtml->xsize, imhtml->y + 5); + + gdk_gc_destroy (gc); +} + +void +gtk_imhtml_show_smileys (GtkIMHtml *imhtml, + gboolean show) +{ + g_return_if_fail (imhtml != NULL); + g_return_if_fail (GTK_IS_IMHTML (imhtml)); + + imhtml->smileys = show; + + if (GTK_WIDGET_VISIBLE (GTK_WIDGET (imhtml))) + gtk_imhtml_redraw_all (imhtml); +} + +void +gtk_imhtml_show_comments (GtkIMHtml *imhtml, + gboolean show) +{ + g_return_if_fail (imhtml != NULL); + g_return_if_fail (GTK_IS_IMHTML (imhtml)); + + imhtml->comments = show; + + if (GTK_WIDGET_VISIBLE (GTK_WIDGET (imhtml))) + gtk_imhtml_redraw_all (imhtml); +} + +static GdkColor * +gtk_imhtml_get_color (const gchar *color) +{ + GdkColor c; + gboolean valid = TRUE; + + g_return_val_if_fail (color != NULL, NULL); + + c.red = 0; c.green = 0; c.blue = 0; + + if (!g_strcasecmp (color, "aliceblue")) { + c.red = 0xf000; c.green = 0xf800; c.blue = 0xff00; + } else if (!g_strcasecmp (color, "antiquewhite")) { + c.red = 0xfa00; c.green = 0xeb00; c.blue = 0xd700; + } else if (!g_strcasecmp (color, "aqua")) { + c.red = 0; c.green = 0xff00; c.blue = 0xff00; + } else if (!g_strcasecmp (color, "aquamarine")) { + c.red = 0; c.green = 0xff00; c.blue = 0xff00; + } else if (!g_strcasecmp (color, "azure")) { + c.red = 0xf000; c.green = 0xff00; c.blue = 0xff00; + } else if (!g_strcasecmp (color, "beige")) { + c.red = 0xf500; c.green = 0xf500; c.blue = 0xdc00; + } else if (!g_strcasecmp (color, "bisque")) { + c.red = 0xff00; c.green = 0xe400; c.blue = 0xc400; + } else if (!g_strcasecmp (color, "black")) { + c.red = 0; c.green = 0; c.blue = 0; + } else if (!g_strcasecmp (color, "blanchedalmond")) { + c.red = 0xff00; c.green = 0xeb00; c.blue = 0xcd00; + } else if (!g_strcasecmp (color, "blue")) { + c.red = 0; c.green = 0; c.blue = 0xff00; + } else if (!g_strcasecmp (color, "blueviolet")) { + c.red = 0; c.green = 0; c.blue = 0xff00; + } else if (!g_strcasecmp (color, "brown")) { + c.red = 0xa500; c.green = 0x2a00; c.blue = 0x2a00; + } else if (!g_strcasecmp (color, "burlywood")) { + c.red = 0xde00; c.green = 0xb800; c.blue = 0x8700; + } else if (!g_strcasecmp (color, "cadetblue")) { + c.red = 0x5f00; c.green = 0x9e00; c.blue = 0xa000; + } else if (!g_strcasecmp (color, "chartreuse")) { + c.red = 0x7f00; c.green = 0xff00; c.blue = 0; + } else if (!g_strcasecmp (color, "chocolate")) { + c.red = 0xd200; c.green = 0x6900; c.blue = 0x1e00; + } else if (!g_strcasecmp (color, "coral")) { + c.red = 0xff00; c.green = 0x7f00; c.blue = 0x5000; + } else if (!g_strcasecmp (color, "cornflowerblue")) { + c.red = 0x6400; c.green = 0x9500; c.blue = 0xed00; + } else if (!g_strcasecmp (color, "cornsilk")) { + c.red = 0xff00; c.green = 0xf800; c.blue = 0xdc00; + } else if (!g_strcasecmp (color, "crimson")) { + c.red = 0xdc00; c.green = 0x1400; c.blue = 0x3c00; + } else if (!g_strcasecmp (color, "cyan")) { + c.red = 0; c.green = 0xff00; c.blue = 0xff00; + } else if (!g_strcasecmp (color, "darkblue")) { + c.red = 0; c.green = 0; c.blue = 0x8b00; + } else if (!g_strcasecmp (color, "darkcyan")) { + c.red = 0; c.green = 0x8b00; c.blue = 0x8b00; + } else if (!g_strcasecmp (color, "darkgoldenrod")) { + c.red = 0xb800; c.green = 0x8600; c.blue = 0x0b00; + } else if (!g_strcasecmp (color, "darkgray")) { + c.red = 0xa900; c.green = 0xa900; c.blue = 0xa900; + } else if (!g_strcasecmp (color, "darkgreen")) { + c.red = 0; c.green = 0x6400; c.blue = 0; + } else if (!g_strcasecmp (color, "darkkhaki")) { + c.red = 0xbd00; c.green = 0xb700; c.blue = 0x6b00; + } else if (!g_strcasecmp (color, "darkmagenta")) { + c.red = 0x8b00; c.green = 0; c.blue = 0x8b00; + } else if (!g_strcasecmp (color, "darkolivegreen")) { + c.red = 0x5500; c.green = 0x6b00; c.blue = 0x2f00; + } else if (!g_strcasecmp (color, "darkorange")) { + c.red = 0xff00; c.green = 0x8c00; c.blue = 0; + } else if (!g_strcasecmp (color, "darkorchid")) { + c.red = 0x9900; c.green = 0x3200; c.blue = 0xcc00; + } else if (!g_strcasecmp (color, "darkred")) { + c.red = 0x8b00; c.green = 0; c.blue = 0; + } else if (!g_strcasecmp (color, "darksalmon")) { + c.red = 0xe900; c.green = 0x9600; c.blue = 0x7a00; + } else if (!g_strcasecmp (color, "darkseagreen")) { + c.red = 0x8f00; c.green = 0xbc00; c.blue = 0x8f00; + } else if (!g_strcasecmp (color, "darkslateblue")) { + c.red = 0x4800; c.green = 0x3d00; c.blue = 0x8b00; + } else if (!g_strcasecmp (color, "darkslategray")) { + c.red = 0x2f00; c.green = 0x4f00; c.blue = 0x4f00; + } else if (!g_strcasecmp (color, "darkturquoise")) { + c.red = 0; c.green = 0xce00; c.blue = 0xd100; + } else if (!g_strcasecmp (color, "darkviolet")) { + c.red = 0x9400; c.green = 0; c.blue = 0xd300; + } else if (!g_strcasecmp (color, "deeppink")) { + c.red = 0xff00; c.green = 0x1400; c.blue = 0x9300; + } else if (!g_strcasecmp (color, "deepskyblue")) { + c.red = 0; c.green = 0xbf00; c.blue = 0xff00; + } else if (!g_strcasecmp (color, "dimgray")) { + c.red = 0x6900; c.green = 0x6900; c.blue = 0x6900; + } else if (!g_strcasecmp (color, "dodgerblue")) { + c.red = 0x1e00; c.green = 0x9000; c.blue = 0xff00; + } else if (!g_strcasecmp (color, "firebrick")) { + c.red = 0xb200; c.green = 0x2200; c.blue = 0x2200; + } else if (!g_strcasecmp (color, "floralwhite")) { + c.red = 0xff00; c.green = 0xfa00; c.blue = 0xf000; + } else if (!g_strcasecmp (color, "forestgreen")) { + c.red = 0x2200; c.green = 0x8b00; c.blue = 0x2200; + } else if (!g_strcasecmp (color, "fuchsia")) { + c.red = 0xff00; c.green = 0; c.blue = 0xff00; + } else if (!g_strcasecmp (color, "gainsboro")) { + c.red = 0xdc00; c.green = 0xdc00; c.blue = 0xdc00; + } else if (!g_strcasecmp (color, "ghostwhite")) { + c.red = 0xf800; c.green = 0xf800; c.blue = 0xff00; + } else if (!g_strcasecmp (color, "gold")) { + c.red = 0xff00; c.green = 0xd700; c.blue = 0; + } else if (!g_strcasecmp (color, "goldenrod")) { + c.red = 0xff00; c.green = 0xd700; c.blue = 0; + } else if (!g_strcasecmp (color, "gray")) { + c.red = 0x8000; c.green = 0x8000; c.blue = 0x8000; + } else if (!g_strcasecmp (color, "green")) { + c.red = 0; c.green = 0x8000; c.blue = 0; + } else if (!g_strcasecmp (color, "greenyellow")) { + c.red = 0; c.green = 0x8000; c.blue = 0; + } else if (!g_strcasecmp (color, "honeydew")) { + c.red = 0xf000; c.green = 0xff00; c.blue = 0xf000; + } else if (!g_strcasecmp (color, "hotpink")) { + c.red = 0xff00; c.green = 0x6900; c.blue = 0xb400; + } else if (!g_strcasecmp (color, "indianred")) { + c.red = 0xcd00; c.green = 0x5c00; c.blue = 0x5c00; + } else if (!g_strcasecmp (color, "indigo")) { + c.red = 0x4b00; c.green = 0; c.blue = 0x8200; + } else if (!g_strcasecmp (color, "ivory")) { + c.red = 0xff00; c.green = 0xff00; c.blue = 0xf000; + } else if (!g_strcasecmp (color, "khaki")) { + c.red = 0xf000; c.green = 0xe600; c.blue = 0x8c00; + } else if (!g_strcasecmp (color, "lavender")) { + c.red = 0xe600; c.green = 0xe600; c.blue = 0xfa00; + } else if (!g_strcasecmp (color, "lavenderblush")) { + c.red = 0xe600; c.green = 0xe600; c.blue = 0xfa00; + } else if (!g_strcasecmp (color, "lawngreen")) { + c.red = 0x7c00; c.green = 0xfc00; c.blue = 0; + } else if (!g_strcasecmp (color, "lemonchiffon")) { + c.red = 0xff00; c.green = 0xfa00; c.blue = 0xcd00; + } else if (!g_strcasecmp (color, "lightblue")) { + c.red = 0xad00; c.green = 0xd800; c.blue = 0xe600; + } else if (!g_strcasecmp (color, "lightcoral")) { + c.red = 0xf000; c.green = 0x8000; c.blue = 0x8000; + } else if (!g_strcasecmp (color, "lightcyan")) { + c.red = 0xe000; c.green = 0xff00; c.blue = 0xff00; + } else if (!g_strcasecmp (color, "lightgoldenrodyellow")) { + c.red = 0xfa00; c.green = 0xfa00; c.blue = 0xd200; + } else if (!g_strcasecmp (color, "lightgreen")) { + c.red = 0x9000; c.green = 0xee00; c.blue = 0x9000; + } else if (!g_strcasecmp (color, "lightgray")) { + c.red = 0xd300; c.green = 0xd300; c.blue = 0xd300; + } else if (!g_strcasecmp (color, "lightpink")) { + c.red = 0xff00; c.green = 0xb600; c.blue = 0xc100; + } else if (!g_strcasecmp (color, "lightsalmon")) { + c.red = 0xff00; c.green = 0xa000; c.blue = 0x7a00; + } else if (!g_strcasecmp (color, "lightseagreen")) { + c.red = 0x2000; c.green = 0xb200; c.blue = 0xaa00; + } else if (!g_strcasecmp (color, "lightskyblue")) { + c.red = 0x8700; c.green = 0xce00; c.blue = 0xfa00; + } else if (!g_strcasecmp (color, "lightslategray")) { + c.red = 0x7700; c.green = 0x8800; c.blue = 0x9900; + } else if (!g_strcasecmp (color, "lightsteelblue")) { + c.red = 0xb000; c.green = 0xc400; c.blue = 0xde00; + } else if (!g_strcasecmp (color, "lightyellow")) { + c.red = 0xff00; c.green = 0xff00; c.blue = 0xe000; + } else if (!g_strcasecmp (color, "lime")) { + c.red = 0; c.green = 0xff00; c.blue = 0; + } else if (!g_strcasecmp (color, "limegreen")) { + c.red = 0; c.green = 0xff00; c.blue = 0; + } else if (!g_strcasecmp (color, "linen")) { + c.red = 0xfa00; c.green = 0xf000; c.blue = 0xe600; + } else if (!g_strcasecmp (color, "magenta")) { + c.red = 0xff00; c.green = 0; c.blue = 0xff00; + } else if (!g_strcasecmp (color, "maroon")) { + c.red = 0x8000; c.green = 0; c.blue = 0; + } else if (!g_strcasecmp (color, "mediumaquamarine")) { + c.red = 0x6600; c.green = 0xcd00; c.blue = 0xaa00; + } else if (!g_strcasecmp (color, "mediumblue")) { + c.red = 0; c.green = 0; c.blue = 0xcd00; + } else if (!g_strcasecmp (color, "mediumorchid")) { + c.red = 0xba00; c.green = 0x5500; c.blue = 0xd300; + } else if (!g_strcasecmp (color, "mediumpurple")) { + c.red = 0x93; c.green = 0x7000; c.blue = 0xdb00; + } else if (!g_strcasecmp (color, "mediumseagreen")) { + c.red = 0x3c00; c.green = 0xb300; c.blue = 0x7100; + } else if (!g_strcasecmp (color, "mediumslateblue")) { + c.red = 0x7b00; c.green = 0x6800; c.blue = 0xee00; + } else if (!g_strcasecmp (color, "mediumspringgreen")) { + c.red = 0; c.green = 0xfa00; c.blue = 0x9a00; + } else if (!g_strcasecmp (color, "mediumturquoise")) { + c.red = 0x4800; c.green = 0xd100; c.blue = 0xcc00; + } else if (!g_strcasecmp (color, "mediumvioletred")) { + c.red = 0xc700; c.green = 0x1500; c.blue = 0x8500; + } else if (!g_strcasecmp (color, "midnightblue")) { + c.red = 0x1900; c.green = 0x1900; c.blue = 0x7000; + } else if (!g_strcasecmp (color, "mintcream")) { + c.red = 0xf500; c.green = 0xff00; c.blue = 0xfa00; + } else if (!g_strcasecmp (color, "mistyrose")) { + c.red = 0xff00; c.green = 0xe400; c.blue = 0xe100; + } else if (!g_strcasecmp (color, "moccasin")) { + c.red = 0xff00; c.green = 0xe400; c.blue = 0xb500; + } else if (!g_strcasecmp (color, "navajowhite")) { + c.red = 0xff00; c.green = 0xde00; c.blue = 0xad00; + } else if (!g_strcasecmp (color, "navy")) { + c.red = 0; c.green = 0x8000; c.blue = 0; + } else if (!g_strcasecmp (color, "oldlace")) { + c.red = 0xfd00; c.green = 0xf500; c.blue = 0xe600; + } else if (!g_strcasecmp (color, "olive")) { + c.red = 0x8000; c.green = 0x8000; c.blue = 0; + } else if (!g_strcasecmp (color, "olivedrab")) { + c.red = 0x8000; c.green = 0x8000; c.blue = 0; + } else if (!g_strcasecmp (color, "orange")) { + c.red = 0xff00; c.green = 0xa500; c.blue = 0; + } else if (!g_strcasecmp (color, "orangered")) { + c.red = 0xff00; c.green = 0xa500; c.blue = 0; + } else if (!g_strcasecmp (color, "orchid")) { + c.red = 0xda00; c.green = 0x7000; c.blue = 0xd600; + } else if (!g_strcasecmp (color, "palegoldenrod")) { + c.red = 0xee00; c.green = 0xe800; c.blue = 0xaa00; + } else if (!g_strcasecmp (color, "palegreen")) { + c.red = 0x9800; c.green = 0xfb00; c.blue = 0x9800; + } else if (!g_strcasecmp (color, "paleturquoise")) { + c.red = 0xaf00; c.green = 0xee00; c.blue = 0xee00; + } else if (!g_strcasecmp (color, "palevioletred")) { + c.red = 0xdb00; c.green = 0x7000; c.blue = 0x9300; + } else if (!g_strcasecmp (color, "papayawhip")) { + c.red = 0xff00; c.green = 0xef00; c.blue = 0xd500; + } else if (!g_strcasecmp (color, "peachpuff")) { + c.red = 0xff00; c.green = 0xda00; c.blue = 0xb900; + } else if (!g_strcasecmp (color, "peru")) { + c.red = 0xcd00; c.green = 0x8500; c.blue = 0x3f00; + } else if (!g_strcasecmp (color, "pink")) { + c.red = 0xff00; c.green = 0xc000; c.blue = 0xcb00; + } else if (!g_strcasecmp (color, "plum")) { + c.red = 0xdd00; c.green = 0xa000; c.blue = 0xdd00; + } else if (!g_strcasecmp (color, "powderblue")) { + c.red = 0xb000; c.green = 0xe000; c.blue = 0xe600; + } else if (!g_strcasecmp (color, "purple")) { + c.red = 0x8000; c.green = 0; c.blue = 0x8000; + } else if (!g_strcasecmp (color, "red")) { + c.red = 0xff00; c.green = 0; c.blue = 0; + } else if (!g_strcasecmp (color, "rosybrown")) { + c.red = 0xbc00; c.green = 0x8f00; c.blue = 0x8f00; + } else if (!g_strcasecmp (color, "royalblue")) { + c.red = 0x4100; c.green = 0x6900; c.blue = 0xe100; + } else if (!g_strcasecmp (color, "saddlebrown")) { + c.red = 0x8b00; c.green = 0x4500; c.blue = 0x1300; + } else if (!g_strcasecmp (color, "salmon")) { + c.red = 0xfa00; c.green = 0x8000; c.blue = 0x7200; + } else if (!g_strcasecmp (color, "sandybrown")) { + c.red = 0xf400; c.green = 0xa400; c.blue = 0x6000; + } else if (!g_strcasecmp (color, "seagreen")) { + c.red = 0x2e00; c.green = 0x8b00; c.blue = 0x5700; + } else if (!g_strcasecmp (color, "seashell")) { + c.red = 0xff00; c.green = 0xf500; c.blue = 0xee00; + } else if (!g_strcasecmp (color, "sienna")) { + c.red = 0xa000; c.green = 0x5200; c.blue = 0x2d00; + } else if (!g_strcasecmp (color, "silver")) { + c.red = 0xc000; c.green = 0xc000; c.blue = 0xc000; + } else if (!g_strcasecmp (color, "skyblue")) { + c.red = 0x8700; c.green = 0xce00; c.blue = 0xeb00; + } else if (!g_strcasecmp (color, "slateblue")) { + c.red = 0x6a00; c.green = 0x5a00; c.blue = 0xcd00; + } else if (!g_strcasecmp (color, "slategray")) { + c.red = 0x7000; c.green = 0x8000; c.blue = 0x9000; + } else if (!g_strcasecmp (color, "springgreen")) { + c.red = 0; c.green = 0xff00; c.blue = 0x7f00; + } else if (!g_strcasecmp (color, "steelblue")) { + c.red = 0x4600; c.green = 0x8200; c.blue = 0xb400; + } else if (!g_strcasecmp (color, "teal")) { + c.red = 0; c.green = 0x8000; c.blue = 0x8000; + } else if (!g_strcasecmp (color, "thistle")) { + c.red = 0xd800; c.green = 0xbf00; c.blue = 0xd800; + } else if (!g_strcasecmp (color, "tomato")) { + c.red = 0xff00; c.green = 0x6300; c.blue = 0x4700; + } else if (!g_strcasecmp (color, "turquoise")) { + c.red = 0x4000; c.green = 0xe000; c.blue = 0xd000; + } else if (!g_strcasecmp (color, "violet")) { + c.red = 0xee00; c.green = 0x8200; c.blue = 0xee00; + } else if (!g_strcasecmp (color, "wheat")) { + c.red = 0xf500; c.green = 0xde00; c.blue = 0xb300; + } else if (!g_strcasecmp (color, "white")) { + c.red = 0xfe00; c.green = 0xfe00; c.blue = 0xfe00; + } else if (!g_strcasecmp (color, "whitesmoke")) { + c.red = 0xfe00; c.green = 0xfe00; c.blue = 0xfe00; + } else if (!g_strcasecmp (color, "yellow")) { + c.red = 0xff00; c.green = 0xff00; c.blue = 0; + } else if (!g_strcasecmp (color, "yellowgreen")) { + c.red = 0xff00; c.green = 0xff00; c.blue = 0; + } else { + const gchar *hex; + guint32 value; + + if (color [0] == '#') + hex = color + 1; + else + hex = color; + + if (strlen (hex) == 6) { + gint i = 0; + for ( ; i < 6; i++) + if (!isxdigit ((gint) hex [i])) + break; + if (i == 6) { + sscanf (hex, "%x", &value); + c.red = (value & 0xff0000) >> 8; + c.green = value & 0xff00; + c.blue = (value & 0xff) << 8; + } else { + valid = FALSE; + } + } else { + valid = FALSE; + } + } + + if (valid) + return gdk_color_copy (&c); + + return NULL; +} + +static gint +gtk_imhtml_is_smiley (GtkIMHtml *imhtml, + const gchar *text) +{ + gchar *tmp; + gint i; + + g_return_val_if_fail (imhtml != NULL, 0); + g_return_val_if_fail (GTK_IS_IMHTML (imhtml), 0); + g_return_val_if_fail (text != NULL, 0); + + tmp = g_malloc (imhtml->smax + 1); + + for (i = imhtml->smin; i <= imhtml->smax; i++) { + if (strlen (text) < i) { + g_free (tmp); + return 0; + } + g_snprintf (tmp, i + 1, "%s", text); + if (g_hash_table_lookup (imhtml->smiley_hash, tmp)) { + g_free (tmp); + return i; + } + } + + g_free (tmp); + return 0; +} + +static GtkIMHtmlBit * +gtk_imhtml_new_bit (GtkIMHtml *imhtml, + gint type, + gchar *text, + gint bold, + gint italics, + gint underline, + gint strike, + FontDetail *font, + GdkColor *bg, + gchar *url) +{ + GtkIMHtmlBit *bit = NULL; + + g_return_val_if_fail (imhtml != NULL, NULL); + g_return_val_if_fail (GTK_IS_IMHTML (imhtml), NULL); + + if ((type == TYPE_TEXT) && ((text == NULL) || (strlen (text) == 0))) + return NULL; + + bit = g_new0 (GtkIMHtmlBit, 1); + bit->type = type; + + if ((text != NULL) && (strlen (text) != 0)) + bit->text = g_strdup (text); + + if ((font != NULL) || bold || italics) { + if (font && (bold || italics || font->size || font->face)) { + bit->font = gtk_imhtml_font_load (imhtml, font->face, bold, italics, font->size); + } else if (bold || italics) { + bit->font = gtk_imhtml_font_load (imhtml, NULL, bold, italics, 0); + } + + if (font && (type != TYPE_BR)) { + if (font->fore != NULL) + bit->fore = gdk_color_copy (font->fore); + + if (font->back != NULL) + bit->back = gdk_color_copy (font->back); + } + } + + if (((bit->type == TYPE_TEXT) || (bit->type == TYPE_SMILEY) || (bit->type == TYPE_COMMENT)) && + (bit->font == NULL)) + bit->font = gdk_font_ref (imhtml->default_font); + + if (bg != NULL) + bit->bg = gdk_color_copy (bg); + + bit->underline = underline; + bit->strike = strike; + + if (url != NULL) + bit->url = g_strdup (url); + + if (type == TYPE_SMILEY) { + GdkColor *clr; + + if ((font != NULL) && (font->back != NULL)) + clr = font->back; + else + clr = (bg != NULL) ? bg : >K_WIDGET (imhtml)->style->white; + + bit->pm = gdk_pixmap_create_from_xpm_d (GTK_WIDGET (imhtml)->window, + &bit->bm, + clr, + g_hash_table_lookup (imhtml->smiley_hash, text)); + } + + return bit; +} + +#define NEW_TEXT_BIT gtk_imhtml_new_bit (imhtml, TYPE_TEXT, ws, bold, italics, underline, strike, \ + fonts ? fonts->data : NULL, bg, url) +#define NEW_SMILEY_BIT gtk_imhtml_new_bit (imhtml, TYPE_SMILEY, ws, bold, italics, underline, strike, \ + fonts ? fonts->data : NULL, bg, url) +#define NEW_SEP_BIT gtk_imhtml_new_bit (imhtml, TYPE_SEP, NULL, 0, 0, 0, 0, NULL, bg, NULL) +#define NEW_BR_BIT gtk_imhtml_new_bit (imhtml, TYPE_BR, NULL, 0, 0, 0, 0, \ + fonts ? fonts->data : NULL, bg, NULL) +#define NEW_COMMENT_BIT gtk_imhtml_new_bit (imhtml, TYPE_COMMENT, ws, bold, italics, underline, strike, \ + fonts ? fonts->data : NULL, bg, url) + +#define NEW_BIT(bit) { GtkIMHtmlBit *tmp = bit; if (tmp != NULL) \ + newbits = g_list_append (newbits, tmp); } + +#define UPDATE_BG_COLORS \ + { \ + GdkColormap *cmap; \ + GList *rev; \ + cmap = gdk_colormap_new (gdk_visual_get_best (), FALSE); \ + rev = g_list_last (newbits); \ + while (rev) { \ + GtkIMHtmlBit *bit = rev->data; \ + if (bit->type == TYPE_BR) \ + break; \ + if (bit->bg) \ + gdk_color_free (bit->bg); \ + bit->bg = gdk_color_copy (bg); \ + rev = g_list_previous (rev); \ + } \ + if (!rev) { \ + rev = g_list_last (imhtml->bits); \ + while (rev) { \ + GList *ln; \ + GtkIMHtmlBit *bit = rev->data; \ + if (bit->type == TYPE_BR) \ + break; \ + if (bit->bg) \ + gdk_color_free (bit->bg); \ + bit->bg = gdk_color_copy (bg); \ + gdk_color_alloc (cmap, bit->bg); \ + ln = bit->chunks; \ + while (ln) { \ + GtkWidget *widget = ln->data; \ + gdk_window_set_background (widget->window, bit->bg); \ + ln = g_list_next (ln); \ + } \ + rev = g_list_previous (rev); \ + } \ + gdk_colormap_unref (cmap); \ + } \ + } + +GString* +gtk_imhtml_append_text (GtkIMHtml *imhtml, + const gchar *text, + GtkIMHtmlOptions options) +{ + const gchar *c; + gboolean intag = FALSE; + gboolean tagquote = FALSE; + gboolean incomment = FALSE; + gchar *ws; + gchar *tag; + gint wpos = 0; + gint tpos = 0; + int smilelen; + GList *newbits = NULL; + + guint bold = 0, + italics = 0, + underline = 0, + strike = 0, + sub = 0, + sup = 0, + title = 0; + GSList *fonts = NULL; + GdkColor *bg = NULL; + gchar *url = NULL; + + GtkAdjustment *vadj; + gboolean scrolldown = TRUE; + + GString *retval = NULL; + + g_return_val_if_fail (imhtml != NULL, NULL); + g_return_val_if_fail (GTK_IS_IMHTML (imhtml), NULL); + g_return_val_if_fail (text != NULL, NULL); + + if (options & GTK_IMHTML_RETURN_LOG) + retval = g_string_new (""); + + vadj = GTK_LAYOUT (imhtml)->vadjustment; + if ((vadj->value < imhtml->y + 5 - GTK_WIDGET (imhtml)->allocation.height) && + (vadj->upper >= GTK_WIDGET (imhtml)->allocation.height)) + scrolldown = FALSE; + + c = text; + ws = g_malloc (strlen (text) + 1); + tag = g_malloc (strlen (text) + 1); + + ws [0] = '\0'; + + while (*c) { + if (*c == '<') { + if (intag) { + ws [wpos] = 0; + tag [tpos] = 0; + tpos = 0; + strcat (ws, tag); + wpos = strlen (ws); + } + + if (incomment) { + ws [wpos++] = *c++; + continue; + } + + if (!g_strncasecmp (c, "<!--", strlen ("<!--"))) { + if (!(options & GTK_IMHTML_NO_COMMENTS)) { + ws [wpos] = 0; + wpos = 0; + tag [tpos] = 0; + strcat (tag, ws); + incomment = TRUE; + intag = FALSE; + } + ws [wpos++] = *c++; + ws [wpos++] = *c++; + ws [wpos++] = *c++; + ws [wpos++] = *c++; + continue; + } + + tag [tpos++] = *c++; + intag = TRUE; + } else if (incomment && (*c == '-') && !g_strncasecmp (c, "-->", strlen ("-->"))) { + gchar *tmp; + ws [wpos] = 0; + wpos = 0; + tmp = g_strdup (ws); + ws [wpos] = 0; + strcat (ws, tag); + NEW_BIT (NEW_TEXT_BIT); + ws [wpos] = 0; + strcat (ws, tmp + strlen ("<!--")); + g_free (tmp); + NEW_BIT (NEW_COMMENT_BIT); + incomment = FALSE; + c += strlen ("-->"); + } else if (*c == '>' && intag && !tagquote) { + gboolean got_tag = FALSE; + tag [tpos++] = *c++; + tag [tpos] = 0; + ws [wpos] = 0; + + if (!g_strcasecmp (tag, "<B>") || !g_strcasecmp (tag, "<BOLD>")) { + got_tag = TRUE; + NEW_BIT (NEW_TEXT_BIT); + bold++; + } else if (!g_strcasecmp (tag, "</B>") || !g_strcasecmp (tag, "</BOLD>")) { + got_tag = TRUE; + if (bold) { + NEW_BIT (NEW_TEXT_BIT); + bold--; + } + } else if (!g_strcasecmp (tag, "<I>") || !g_strcasecmp (tag, "<ITALIC>")) { + got_tag = TRUE; + NEW_BIT (NEW_TEXT_BIT); + italics++; + } else if (!g_strcasecmp (tag, "</I>") || !g_strcasecmp (tag, "</ITALIC>")) { + got_tag = TRUE; + if (italics) { + NEW_BIT (NEW_TEXT_BIT); + italics--; + } + } else if (!g_strcasecmp (tag, "<U>") || !g_strcasecmp (tag, "<UNDERLINE>")) { + got_tag = TRUE; + NEW_BIT (NEW_TEXT_BIT); + underline++; + } else if (!g_strcasecmp (tag, "</U>") || !g_strcasecmp (tag, "</UNDERLINE>")) { + got_tag = TRUE; + if (underline) { + NEW_BIT (NEW_TEXT_BIT); + underline--; + } + } else if (!g_strcasecmp (tag, "<S>") || !g_strcasecmp (tag, "<STRIKE>")) { + got_tag = TRUE; + NEW_BIT (NEW_TEXT_BIT); + strike++; + } else if (!g_strcasecmp (tag, "</S>") || !g_strcasecmp (tag, "</STRIKE>")) { + got_tag = TRUE; + if (strike) { + NEW_BIT (NEW_TEXT_BIT); + strike--; + } + } else if (!g_strcasecmp (tag, "<SUB>")) { + got_tag = TRUE; + sub++; + } else if (!g_strcasecmp (tag, "</SUB>")) { + got_tag = TRUE; + if (sub) { + sub--; + } + } else if (!g_strcasecmp (tag, "<SUP>")) { + got_tag = TRUE; + sup++; + } else if (!g_strcasecmp (tag, "</SUP>")) { + got_tag = TRUE; + if (sup) { + sup--; + } + } else if (!g_strcasecmp (tag, "<TITLE>")) { + if (options & GTK_IMHTML_NO_TITLE) { + got_tag = TRUE; + title++; + } else { + intag = FALSE; + tpos = 0; + continue; + } + } else if (!g_strcasecmp (tag, "</TITLE>")) { + if (title) { + got_tag = TRUE; + wpos = 0; + ws [wpos] = '\0'; + title--; + } else { + intag = FALSE; + tpos = 0; + continue; + } + } else if (!g_strcasecmp (tag, "<BR>")) { + got_tag = TRUE; + NEW_BIT (NEW_TEXT_BIT); + NEW_BIT (NEW_BR_BIT); + } else if (!g_strcasecmp (tag, "<HR>") || + !g_strncasecmp (tag, "<HR ", strlen ("<HR "))) { + got_tag = TRUE; + NEW_BIT (NEW_TEXT_BIT); + NEW_BIT (NEW_SEP_BIT); + } else if (!g_strncasecmp (tag, "<FONT ", strlen ("<FONT "))) { + gchar *t, *e, *a, *value; + FontDetail *font = NULL; + GdkColor *clr; + gint saw; + gint i; + + t = tag + strlen ("<FONT "); + + while (*t != '\0') { + value = NULL; + saw = 0; + + while (g_strncasecmp (t, "COLOR=", strlen ("COLOR=")) + && g_strncasecmp (t, "BACK=", strlen ("BACK=")) + && g_strncasecmp (t, "FACE=", strlen ("FACE=")) + && g_strncasecmp (t, "SIZE=", strlen ("SIZE="))) { + gboolean quote = FALSE; + if (*t == '\0') break; + while (*t && !((*t == ' ') && !quote)) { + if (*t == '\"') + quote = ! quote; + t++; + } + while (*t && (*t == ' ')) t++; + } + + if (!g_strncasecmp (t, "COLOR=", strlen ("COLOR="))) { + t += strlen ("COLOR="); + saw = 1; + } else if (!g_strncasecmp (t, "BACK=", strlen ("BACK="))) { + t += strlen ("BACK="); + saw = 2; + } else if (!g_strncasecmp (t, "FACE=", strlen ("FACE="))) { + t += strlen ("FACE="); + saw = 3; + } else if (!g_strncasecmp (t, "SIZE=", strlen ("SIZE="))) { + t += strlen ("SIZE="); + saw = 4; + } + + if (!saw) + continue; + + if ((*t == '\"') || (*t == '\'')) { + e = a = ++t; + while (*e && (*e != *(t - 1))) e++; + if (*e != '\0') { + *e = '\0'; + t = e + 1; + value = g_strdup (a); + } else { + *t = '\0'; + } + } else { + e = a = t; + while (*e && !isspace ((gint) *e)) e++; + if (*e == '\0') e--; + *e = '\0'; + t = e + 1; + value = g_strdup (a); + } + + if (value == NULL) + continue; + + if (font == NULL) + font = g_new0 (FontDetail, 1); + + switch (saw) { + case 1: + clr = gtk_imhtml_get_color (value); + if (clr != NULL) { + if ( (font->fore == NULL) && + !(options & GTK_IMHTML_NO_COLOURS)) + font->fore = clr; + } + break; + case 2: + clr = gtk_imhtml_get_color (value); + if (clr != NULL) { + if ( (font->back == NULL) && + !(options & GTK_IMHTML_NO_COLOURS)) + font->back = clr; + } + break; + case 3: + if ( (font->face == NULL) && + !(options & GTK_IMHTML_NO_FONTS)) + font->face = g_strdup (value); + break; + case 4: + if ((font->size != 0) || + (options & GTK_IMHTML_NO_SIZES)) + break; + + if (isdigit ((gint) value [0])) { + for (i = 0; i < strlen (value); i++) + if (!isdigit ((gint) value [i])) + break; + if (i != strlen (value)) + break; + + sscanf (value, "%hd", &font->size); + break; + } + + if ((value [0] == '+') && (value [1] != '\0')) { + for (i = 1; i < strlen (value); i++) + if (!isdigit ((gint) value [i])) + break; + if (i != strlen (value)) + break; + + sscanf (value + 1, "%hd", &font->size); + font->size += 3; + break; + } + + if ((value [0] == '-') && (value [1] != '\0')) { + for (i = 1; i < strlen (value); i++) + if (!isdigit ((gint) value [i])) + break; + if (i != strlen (value)) + break; + + sscanf (value + 1, "%hd", &font->size); + font->size = MIN (font->size, 2); + font->size = 3 - font->size; + break; + } + + break; + } + + g_free (value); + } + + if (!font) { + intag = FALSE; + tpos = 0; + continue; + } + + if (!(font->size || font->face || font->fore || font->back)) { + g_free (font); + intag = FALSE; + tpos = 0; + continue; + } + + NEW_BIT (NEW_TEXT_BIT); + + if (fonts) { + FontDetail *oldfont = fonts->data; + if (!font->size) + font->size = oldfont->size; + if (!font->face) + font->face = g_strdup (oldfont->face); + if (!font->fore && oldfont->fore) + font->fore = gdk_color_copy (oldfont->fore); + if (!font->back && oldfont->back) + font->back = gdk_color_copy (oldfont->back); + } else { + if (!font->size) + font->size = 3; + if (!font->face) + font->face = g_strdup ("helvetica"); + } + + fonts = g_slist_prepend (fonts, font); + got_tag = TRUE; + } else if (!g_strcasecmp (tag, "</FONT>")) { + FontDetail *font; + + if (fonts) { + got_tag = TRUE; + NEW_BIT (NEW_TEXT_BIT); + font = fonts->data; + fonts = g_slist_remove (fonts, font); + g_free (font->face); + if (font->fore) + gdk_color_free (font->fore); + if (font->back) + gdk_color_free (font->back); + g_free (font); + } else { + intag = FALSE; + tpos = 0; + continue; + } + } else if (!g_strncasecmp (tag, "<BODY ", strlen ("<BODY "))) { + gchar *t, *e, *color = NULL; + GdkColor *tmp; + + got_tag = TRUE; + + if (!(options & GTK_IMHTML_NO_COLOURS)) { + t = tag + strlen ("<BODY"); + do { + gboolean quote = FALSE; + if (*t == '\0') break; + while (*t && !((*t == ' ') && !quote)) { + if (*t == '\"') + quote = ! quote; + t++; + } + while (*t && (*t == ' ')) t++; + } while (g_strncasecmp (t, "BGCOLOR=", strlen ("BGCOLOR="))); + + if (!g_strncasecmp (t, "BGCOLOR=", strlen ("BGCOLOR="))) { + t += strlen ("BGCOLOR="); + if ((*t == '\"') || (*t == '\'')) { + e = ++t; + while (*e && (*e != *(t - 1))) e++; + if (*e != '\0') { + *e = '\0'; + color = g_strdup (t); + } + } else { + e = t; + while (*e && !isspace ((gint) *e)) e++; + if (*e == '\0') e--; + *e = '\0'; + color = g_strdup (t); + } + + if (color != NULL) { + tmp = gtk_imhtml_get_color (color); + g_free (color); + if (tmp != NULL) { + NEW_BIT (NEW_TEXT_BIT); + bg = tmp; + UPDATE_BG_COLORS; + } + } + } + } + } else if (!g_strncasecmp (tag, "<A ", strlen ("<A "))) { + gchar *t, *e; + + got_tag = TRUE; + NEW_BIT (NEW_TEXT_BIT); + + if (url != NULL) + g_free (url); + url = NULL; + + t = tag + strlen ("<A"); + do { + gboolean quote = FALSE; + if (*t == '\0') break; + while (*t && !((*t == ' ') && !quote)) { + if (*t == '\"') + quote = ! quote; + t++; + } + while (*t && (*t == ' ')) t++; + } while (g_strncasecmp (t, "HREF=", strlen ("HREF="))); + + if (!g_strncasecmp (t, "HREF=", strlen ("HREF="))) { + t += strlen ("HREF="); + if ((*t == '\"') || (*t == '\'')) { + e = ++t; + while (*e && (*e != *(t - 1))) e++; + if (*e != '\0') { + *e = '\0'; + url = g_strdup (t); + } + } else { + e = t; + while (*e && !isspace ((gint) *e)) e++; + if (*e == '\0') e--; + *e = '\0'; + url = g_strdup (t); + } + } + } else if (!g_strcasecmp (tag, "</A>")) { + got_tag = TRUE; + if (url != NULL) { + NEW_BIT (NEW_TEXT_BIT); + g_free (url); + } + url = NULL; + } else if (!g_strncasecmp (tag, "<IMG ", strlen ("<IMG "))) { + gchar *t, *e, *src = NULL; + gchar *copy = g_strdup (tag); + gchar **xpm; + GdkColor *clr = NULL; + GtkIMHtmlBit *bit; + + intag = FALSE; + tpos = 0; + + if (imhtml->img == NULL) + continue; + + t = tag + strlen ("<IMG"); + do { + gboolean quote = FALSE; + if (*t == '\0') break; + while (*t && !((*t == ' ') && !quote)) { + if (*t == '\"') + quote = ! quote; + t++; + } + while (*t && (*t == ' ')) t++; + } while (g_strncasecmp (t, "SRC=", strlen ("SRC="))); + + if (!g_strncasecmp (t, "SRC=", strlen ("SRC="))) { + t += strlen ("SRC="); + if ((*t == '\"') || (*t == '\'')) { + e = ++t; + while (*e && (*e != *(t - 1))) e++; + if (*e != '\0') { + *e = '\0'; + src = g_strdup (t); + } + } else { + e = t; + while (*e && !isspace ((gint) *e)) e++; + if (*e == '\0') e--; + *e = '\0'; + src = g_strdup (t); + } + } + + if (src == NULL) { + ws [wpos] = 0; + strcat (ws, copy); + wpos = strlen (ws); + g_free (copy); + continue; + } + + xpm = (* imhtml->img) (src); + if (xpm == NULL) { + g_free (src); + ws [wpos] = 0; + strcat (ws, copy); + wpos = strlen (ws); + g_free (copy); + continue; + } + + g_free (copy); + + if (!fonts || ((clr = ((FontDetail *)fonts->data)->back) == NULL)) + clr = (bg != NULL) ? bg : >K_WIDGET (imhtml)->style->white; + + if (!GTK_WIDGET_REALIZED (imhtml)) + gtk_widget_realize (GTK_WIDGET (imhtml)); + + bit = g_new0 (GtkIMHtmlBit, 1); + bit->type = TYPE_IMG; + bit->pm = gdk_pixmap_create_from_xpm_d (GTK_WIDGET (imhtml)->window, + &bit->bm, + clr, + xpm); + if (url) + bit->url = g_strdup (url); + + NEW_BIT (bit); + + g_free (src); + + continue; + } else if (!g_strcasecmp (tag, "<P>") || + !g_strcasecmp (tag, "</P>") || + !g_strncasecmp (tag, "<P ", strlen ("<P ")) || + !g_strcasecmp (tag, "<PRE>") || + !g_strcasecmp (tag, "</PRE>") || + !g_strcasecmp (tag, "<HTML>") || + !g_strcasecmp (tag, "</HTML>") || + !g_strcasecmp (tag, "<BODY>") || + !g_strcasecmp (tag, "</BODY>") || + !g_strcasecmp (tag, "<FONT>") || + !g_strcasecmp (tag, "<HEAD>") || + !g_strcasecmp (tag, "</HEAD>")) { + intag = FALSE; + tpos = 0; + continue; + } + + if (!got_tag) { + ws [wpos] = 0; + strcat (ws, tag); + wpos = strlen (ws); + } else { + wpos = 0; + } + intag = FALSE; + tpos = 0; + } else if (*c == '&' && !intag) { + if (!g_strncasecmp (c, "&", 5)) { + ws [wpos++] = '&'; + c += 5; + } else if (!g_strncasecmp (c, "<", 4)) { + ws [wpos++] = '<'; + c += 4; + } else if (!g_strncasecmp (c, ">", 4)) { + ws [wpos++] = '>'; + c += 4; + } else if (!g_strncasecmp (c, " ", 6)) { + ws [wpos++] = ' '; + c += 6; + } else if (!g_strncasecmp (c, "©", 6)) { + ws [wpos++] = '©'; + c += 6; + } else if (!g_strncasecmp (c, """, 6)) { + ws [wpos++] = '\"'; + c += 6; + } else if (!g_strncasecmp (c, "®", 5)) { + ws [wpos++] = '®'; + c += 5; + } else if (*(c + 1) == '#') { + gint pound = 0; + if (sscanf (c, "&#%d;", £) == 1) { + if (*(c + 3 + (gint)log10 (pound)) != ';') { + ws [wpos++] = *c++; + continue; + } + ws [wpos++] = (gchar)pound; + c += 2; + while (isdigit ((gint) *c)) c++; + if (*c == ';') c++; + } else { + ws [wpos++] = *c++; + } + } else { + ws [wpos++] = *c++; + } + } else if (intag) { + if (*c == '\"') + tagquote = !tagquote; + tag [tpos++] = *c++; + } else if (incomment) { + ws [wpos++] = *c++; + } else if (((smilelen = gtk_imhtml_is_smiley (imhtml, c)) != 0)) { + ws [wpos] = 0; + wpos = 0; + NEW_BIT (NEW_TEXT_BIT); + g_snprintf (ws, smilelen + 1, "%s", c); + NEW_BIT (NEW_SMILEY_BIT); + c += smilelen; + } else if (*c == '\n') { + if (!(options & GTK_IMHTML_NO_NEWLINE)) { + ws [wpos] = 0; + wpos = 0; + NEW_BIT (NEW_TEXT_BIT); + NEW_BIT (NEW_BR_BIT); + } + c++; + } else { + ws [wpos++] = *c++; + } + } + + if (intag) { + tag [tpos] = 0; + c = tag; + while (*c) { + if ((smilelen = gtk_imhtml_is_smiley (imhtml, c)) != 0) { + ws [wpos] = 0; + wpos = 0; + NEW_BIT (NEW_TEXT_BIT); + g_snprintf (ws, smilelen + 1, "%s", c); + NEW_BIT (NEW_SMILEY_BIT); + c += smilelen; + } else { + ws [wpos++] = *c++; + } + } + } else if (incomment) { + ws [wpos] = 0; + wpos = 0; + strcat (tag, ws); + ws [wpos] = 0; + c = tag; + while (*c) { + if ((smilelen = gtk_imhtml_is_smiley (imhtml, c)) != 0) { + ws [wpos] = 0; + wpos = 0; + NEW_BIT (NEW_TEXT_BIT); + g_snprintf (ws, smilelen + 1, "%s", c); + NEW_BIT (NEW_SMILEY_BIT); + c += smilelen; + } else { + ws [wpos++] = *c++; + } + } + } + + ws [wpos] = 0; + NEW_BIT (NEW_TEXT_BIT); + + while (newbits) { + GtkIMHtmlBit *bit = newbits->data; + imhtml->bits = g_list_append (imhtml->bits, bit); + newbits = g_list_remove (newbits, bit); + gtk_imhtml_draw_bit (imhtml, bit); + } + + gtk_widget_set_usize (GTK_WIDGET (imhtml), -1, imhtml->y + 5); + + if (!(options & GTK_IMHTML_NO_SCROLL) && + scrolldown && + (imhtml->y + 5 >= GTK_WIDGET (imhtml)->allocation.height)) + gtk_adjustment_set_value (vadj, imhtml->y + 5 - GTK_WIDGET (imhtml)->allocation.height); + + if (url) { + g_free (url); + if (retval) + retval = g_string_append (retval, "</A>"); + } + if (bg) + gdk_color_free (bg); + while (fonts) { + FontDetail *font = fonts->data; + fonts = g_slist_remove (fonts, font); + g_free (font->face); + if (font->fore) + gdk_color_free (font->fore); + if (font->back) + gdk_color_free (font->back); + g_free (font); + if (retval) + retval = g_string_append (retval, "</FONT>"); + } + if (retval) { + while (bold) { + retval = g_string_append (retval, "</B>"); + bold--; + } + while (italics) { + retval = g_string_append (retval, "</I>"); + italics--; + } + while (underline) { + retval = g_string_append (retval, "</U>"); + underline--; + } + while (strike) { + retval = g_string_append (retval, "</S>"); + strike--; + } + while (sub) { + retval = g_string_append (retval, "</SUB>"); + sub--; + } + while (sup) { + retval = g_string_append (retval, "</SUP>"); + sup--; + } + while (title) { + retval = g_string_append (retval, "</TITLE>"); + title--; + } + } + g_free (ws); + g_free (tag); + + return retval; +}