Mercurial > pidgin.yaz
view src/gtkimhtml.c @ 3391:412d1035d666
[gaim-migrate @ 3410]
Pluginzed the ticker. the ticker will load itself if the old ticker option
was set.
committer: Tailor Script <tailor@pidgin.im>
author | Sean Egan <seanegan@gmail.com> |
---|---|
date | Sun, 11 Aug 2002 06:42:17 +0000 |
parents | e9a89676d120 |
children | c731327dbc47 |
line wrap: on
line source
/* * 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 * */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include "gtkimhtml.h" #include <X11/Xlib.h> #include <stdlib.h> #include <gdk/gdkx.h> #include <gtk/gtk.h> #include <string.h> #include <ctype.h> #include <stdio.h> #include <math.h> #ifdef HAVE_LANGINFO_CODESET #include <langinfo.h> #include <locale.h> #endif #if USE_PIXBUF || GTK_CHECK_VERSION(1,3,0) #include <gdk-pixbuf/gdk-pixbuf.h> #include <gdk-pixbuf/gdk-pixbuf-loader.h> #else #include "pixmaps/broken.xpm" #endif #if GTK_CHECK_VERSION(1,3,0) # define GTK_IMHTML_GET_STYLE_FONT(style) gtk_style_get_font (style) #else # define GTK_IMHTML_GET_STYLE_FONT(style) (style)->font # define GTK_CLASS_TYPE(class) (class)->type #endif #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 MAX_FONT_SIZE 7 #define POINT_SIZE(x) (_point_sizes [MIN ((x), MAX_FONT_SIZE) - 1]) static gint _point_sizes [] = { 80, 100, 120, 140, 200, 300, 400 }; #define DEFAULT_PRE_FACE "courier" #define BORDER_SIZE 2 #define TOP_BORDER 10 #define MIN_HEIGHT 20 #define HR_HEIGHT 2 #define TOOLTIP_TIMEOUT 500 #define DIFF(a, b) (((a) > (b)) ? ((a) - (b)) : ((b) - (a))) #define COLOR_MOD 0x8000 #define COLOR_DIFF 0x800 #define TYPE_TEXT 0 #define TYPE_SMILEY 1 #define TYPE_IMG 2 #define TYPE_SEP 3 #define TYPE_BR 4 #define TYPE_COMMENT 5 #define DRAW_IMG(x) (((x)->type == TYPE_IMG) || (imhtml->smileys && ((x)->type == TYPE_SMILEY))) typedef struct _GtkIMHtmlBit GtkIMHtmlBit; typedef struct _FontDetail FontDetail; struct _GtkSmileyTree { GString *values; GtkSmileyTree **children; gchar **image; }; static GtkSmileyTree* gtk_smiley_tree_new () { return g_new0 (GtkSmileyTree, 1); } static void gtk_smiley_tree_insert (GtkSmileyTree *tree, const gchar *text, gchar **image) { GtkSmileyTree *t = tree; const gchar *x = text; if (!strlen (x)) return; while (*x) { 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 = (int) pos - (int) t->values->str; t = t->children [index]; x++; } t->image = image; } static void gtk_smiley_tree_remove (GtkSmileyTree *tree, const gchar *text) { GtkSmileyTree *t = tree; const gchar *x = text; 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; while (*x) { gchar *pos; if (t->image) return len; if (!t->values) return 0; pos = strchr (t->values->str, *x); if (pos) t = t->children [(int) pos - (int) t->values->str]; else return 0; x++; len++; } if (t->image) return len; return 0; } static gchar** gtk_smiley_tree_image (GtkSmileyTree *tree, const gchar *text) { GtkSmileyTree *t = tree; const gchar *x = text; while (*x) { gchar *pos; if (!t->values) return NULL; pos = strchr (t->values->str, *x); if (pos) { t = t->children [(int) pos - (int) t->values->str]; } else return NULL; x++; } return t->image; } static void gtk_smiley_tree_destroy (GtkSmileyTree *tree) { GSList *list = g_slist_append (NULL, tree); while (list) { GtkSmileyTree *t = list->data; gint i; list = g_slist_remove(list, t); if (t->values) { for (i = 0; i < t->values->len; i++) list = g_slist_append (list, t->children [i]); g_string_free (t->values, TRUE); g_free (t->children); } g_free (t); } } void gtk_imhtml_remove_smileys (GtkIMHtml *imhtml) { g_return_if_fail (imhtml != NULL); g_return_if_fail (GTK_IS_IMHTML (imhtml)); gtk_smiley_tree_destroy (imhtml->smiley_data); imhtml->smiley_data = gtk_smiley_tree_new (); } void gtk_imhtml_reset_smileys (GtkIMHtml *imhtml) { gtk_imhtml_remove_smileys(imhtml); gtk_imhtml_init_smileys (imhtml); } struct im_image { gchar *filename; gint len; gpointer data; gint x,y; gint width,height; GtkIMHtml *imhtml; GtkIMHtmlBit *bit; #if USE_PIXBUF || GTK_CHECK_VERSION(1,3,0) GdkPixbuf *pb; #endif }; struct _GtkIMHtmlBit { gint type; gchar *text; struct im_image *img; 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 clickable { gint x; gint y; gint width; gint height; GtkIMHtml *imhtml; GtkIMHtmlBit *bit; }; 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 #if GTK_CHECK_VERSION(1,3,0) gtk_imhtml_finalize (GObject *object) #else gtk_imhtml_destroy (GtkObject *object) #endif { GtkIMHtml *imhtml; imhtml = GTK_IMHTML (object); gtk_imhtml_clear (imhtml); if (imhtml->selected_text) g_string_free (imhtml->selected_text, TRUE); if (imhtml->default_font) gdk_font_unref (imhtml->default_font); if (imhtml->default_fg_color) gdk_color_free (imhtml->default_fg_color); if (imhtml->default_bg_color) gdk_color_free (imhtml->default_bg_color); if (imhtml->default_hl_color) gdk_color_free (imhtml->default_hl_color); if (imhtml->default_hlfg_color) gdk_color_free (imhtml->default_hlfg_color); gdk_cursor_destroy (imhtml->hand_cursor); gdk_cursor_destroy (imhtml->arrow_cursor); gtk_smiley_tree_destroy (imhtml->smiley_data); #if GTK_CHECK_VERSION(1,3,0) G_OBJECT_CLASS (parent_class)->finalize (object); #else if (GTK_OBJECT_CLASS (parent_class)->destroy != NULL) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); #endif } static void gtk_imhtml_realize (GtkWidget *widget) { GtkIMHtml *imhtml; GdkWindowAttr attributes; gint attributes_mask; g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_IMHTML (widget)); imhtml = GTK_IMHTML (widget); GTK_WIDGET_SET_FLAGS (imhtml, GTK_REALIZED); attributes.window_type = GDK_WINDOW_CHILD; attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget); attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK; attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask); gdk_window_set_user_data (widget->window, widget); #if GTK_CHECK_VERSION(1,3,0) attributes.x = widget->style->xthickness + BORDER_SIZE; attributes.y = widget->style->xthickness + BORDER_SIZE; #else attributes.x = widget->style->klass->xthickness + BORDER_SIZE; attributes.y = widget->style->klass->xthickness + BORDER_SIZE; #endif attributes.width = MAX (1, (gint) widget->allocation.width - (gint) attributes.x * 2); attributes.height = MAX (1, (gint) widget->allocation.height - (gint) attributes.y * 2); attributes.event_mask = gtk_widget_get_events (widget) | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK; GTK_LAYOUT (imhtml)->bin_window = gdk_window_new (widget->window, &attributes, attributes_mask); gdk_window_set_user_data (GTK_LAYOUT (imhtml)->bin_window, widget); widget->style = gtk_style_attach (widget->style, widget->window); gdk_window_set_cursor (widget->window, imhtml->arrow_cursor); imhtml->default_font = gdk_font_ref (GTK_IMHTML_GET_STYLE_FONT (widget->style)); gdk_window_set_background (widget->window, &widget->style->base [GTK_STATE_NORMAL]); gdk_window_set_background (GTK_LAYOUT (imhtml)->bin_window, &widget->style->base [GTK_STATE_NORMAL]); imhtml->default_fg_color = gdk_color_copy (>K_WIDGET (imhtml)->style->fg [GTK_STATE_NORMAL]); imhtml->default_bg_color = gdk_color_copy (>K_WIDGET (imhtml)->style->base [GTK_STATE_NORMAL]); imhtml->default_hl_color = gdk_color_copy (>K_WIDGET (imhtml)->style->bg [GTK_STATE_SELECTED]); imhtml->default_hlfg_color=gdk_color_copy (>K_WIDGET (imhtml)->style->fg [GTK_STATE_SELECTED]); gdk_window_show (GTK_LAYOUT (imhtml)->bin_window); } static gboolean similar_colors (GdkColor *bg, GdkColor *fg) { if ((DIFF (bg->red, fg->red) < COLOR_DIFF) && (DIFF (bg->green, fg->green) < COLOR_DIFF) && (DIFF (bg->blue, fg->blue) < COLOR_DIFF)) { fg->red = (0xff00 - COLOR_MOD > bg->red) ? bg->red + COLOR_MOD : bg->red - COLOR_MOD; fg->green = (0xff00 - COLOR_MOD > bg->green) ? bg->green + COLOR_MOD : bg->green - COLOR_MOD; fg->blue = (0xff00 - COLOR_MOD > bg->blue) ? bg->blue + COLOR_MOD : bg->blue - COLOR_MOD; return TRUE; } return FALSE; } static void draw_text (GtkIMHtml *imhtml, struct line_info *line) { GtkIMHtmlBit *bit; GdkGC *gc; GdkColormap *cmap; GdkWindow *window = GTK_LAYOUT (imhtml)->bin_window; gfloat xoff, yoff; GdkColor *bg, *fg; gchar *start = NULL, *end = NULL; if (GTK_LAYOUT (imhtml)->freeze_count) return; bit = line->bit; gc = gdk_gc_new (window); cmap = gtk_widget_get_colormap (GTK_WIDGET (imhtml)); xoff = GTK_LAYOUT (imhtml)->hadjustment->value; yoff = GTK_LAYOUT (imhtml)->vadjustment->value; if (bit->bg != NULL) { gdk_color_alloc (cmap, bit->bg); gdk_gc_set_foreground (gc, bit->bg); bg = bit->bg; } else { gdk_color_alloc (cmap, imhtml->default_bg_color); gdk_gc_set_foreground (gc, imhtml->default_bg_color); bg = imhtml->default_bg_color; } gdk_draw_rectangle (window, gc, TRUE, line->x - xoff, line->y - yoff, line->width ? line->width : imhtml->xsize, line->height); if (!line->text) { gdk_gc_unref (gc); return; } if (bit->back != NULL) { gdk_color_alloc (cmap, bit->back); gdk_gc_set_foreground (gc, bit->back); gdk_draw_rectangle (window, gc, TRUE, line->x - xoff, line->y - yoff, gdk_string_width (bit->font, line->text), line->height); bg = bit->back; } bg = gdk_color_copy (bg); if (line->selected) { gint width, x; 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) end = strchr(line->text, '\0'); width = gdk_text_width (bit->font, line->text, end - line->text) - x; gdk_gc_set_foreground (gc, imhtml->default_hl_color); gdk_draw_rectangle (window, gc, TRUE, x + line->x - xoff, line->y - yoff, width, line->height); gdk_gc_set_foreground (gc, imhtml->default_hlfg_color); fg = gdk_color_copy(imhtml->default_hlfg_color); } if (bit->url) { GdkColor *tc = gtk_imhtml_get_color ("#0000a0"); gdk_color_alloc (cmap, tc); gdk_gc_set_foreground (gc, tc); fg = gdk_color_copy (tc); gdk_color_free (tc); } else if (bit->fore) { gdk_color_alloc (cmap, bit->fore); gdk_gc_set_foreground (gc, bit->fore); fg = gdk_color_copy (bit->fore); } else { gdk_color_alloc (cmap, imhtml->default_fg_color); gdk_gc_set_foreground (gc, imhtml->default_fg_color); fg = gdk_color_copy (imhtml->default_fg_color); } if (similar_colors (bg, fg)) { gdk_color_alloc (cmap, fg); gdk_gc_set_foreground (gc, fg); } if (start) { int offset = 0; gdk_draw_text (window, bit->font, gc, line->x - xoff, line->y - yoff + line->ascent, line->text, start - line->text); offset = gdk_text_width(bit->font, line->text, start - line->text); if (bit->underline || bit->url) gdk_draw_rectangle (window, gc, TRUE, line->x - xoff, line->y - yoff + line->ascent + 1, offset, 1); if (bit->strike) gdk_draw_rectangle (window, gc, TRUE, line->x - xoff, line->y - yoff + line->ascent - (bit->font->ascent / 2), offset, 1); gdk_gc_set_foreground (gc, imhtml->default_hlfg_color); gdk_draw_text (window, bit->font, gc, line->x - xoff + offset, line->y - yoff + line->ascent, start, end - start); if (bit->underline || bit->url) gdk_draw_rectangle (window, gc, TRUE, line->x - xoff + offset, line->y - yoff + line->ascent + 1, gdk_text_width(bit->font, line->text, end - start), 1); if (bit->strike) gdk_draw_rectangle (window, gc, TRUE, line->x - xoff + offset, line->y - yoff + line->ascent - (bit->font->ascent / 2), gdk_text_width(bit->font, line->text, end - start), 1); offset = gdk_text_width(bit->font, line->text, end - line->text); gdk_gc_set_foreground (gc, fg); gdk_draw_string (window, bit->font, gc, line->x - xoff + offset, line->y - yoff + line->ascent, end); if (bit->underline || bit->url) gdk_draw_rectangle (window, gc, TRUE, line->x - xoff + offset, line->y - yoff + line->ascent + 1, gdk_string_width(bit->font, end), 1); if (bit->strike) gdk_draw_rectangle (window, gc, TRUE, line->x - xoff + offset, line->y - yoff + line->ascent - (bit->font->ascent / 2), gdk_string_width(bit->font, end), 1); } else { gdk_draw_string (window, bit->font, gc, line->x - xoff, line->y - yoff + line->ascent, line->text); if (bit->underline || bit->url) gdk_draw_rectangle (window, gc, TRUE, line->x - xoff, line->y - yoff + line->ascent + 1, gdk_string_width (bit->font, line->text), 1); if (bit->strike) gdk_draw_rectangle (window, gc, TRUE, line->x - xoff, line->y - yoff + line->ascent - (bit->font->ascent / 2), gdk_string_width (bit->font, line->text), 1); } gdk_color_free (bg); gdk_color_free (fg); gdk_gc_unref (gc); } static void draw_img (GtkIMHtml *imhtml, struct line_info *line) { GtkIMHtmlBit *bit; GdkGC *gc; GdkColormap *cmap; gint width, height, hoff; GdkWindow *window = GTK_LAYOUT (imhtml)->bin_window; gfloat xoff, yoff; if (GTK_LAYOUT (imhtml)->freeze_count) return; bit = line->bit; gdk_window_get_size (bit->pm, &width, &height); hoff = (line->height - height) / 2; xoff = GTK_LAYOUT (imhtml)->hadjustment->value; yoff = GTK_LAYOUT (imhtml)->vadjustment->value; gc = gdk_gc_new (window); cmap = gtk_widget_get_colormap (GTK_WIDGET (imhtml)); if (bit->bg != NULL) { gdk_color_alloc (cmap, bit->bg); gdk_gc_set_foreground (gc, bit->bg); } else { gdk_color_alloc (cmap, imhtml->default_bg_color); gdk_gc_set_foreground (gc, imhtml->default_bg_color); } gdk_draw_rectangle (window, gc, TRUE, line->x - xoff, line->y - yoff, line->width, line->height); if (line->selected) { gdk_color_alloc (cmap, imhtml->default_hl_color); gdk_gc_set_foreground(gc, imhtml->default_hl_color); gdk_draw_rectangle (window, gc, TRUE, line->x - xoff, line->y - yoff, width, line->height); } else if (bit->back != NULL) { gdk_color_alloc (cmap, bit->back); gdk_gc_set_foreground (gc, bit->back); } if (bit->bm) { gdk_gc_set_clip_mask(gc, bit->bm); gdk_gc_set_clip_origin(gc, line->x - xoff, line->y - yoff + hoff); } gdk_draw_pixmap (window, gc, bit->pm, 0, 0, line->x - xoff, line->y - yoff + hoff, -1, -1); gdk_gc_unref (gc); } static void draw_line (GtkIMHtml *imhtml, struct line_info *line) { GtkIMHtmlBit *bit; GdkDrawable *drawable; GdkColormap *cmap; GdkGC *gc; guint line_height; gfloat xoff, yoff; if (GTK_LAYOUT (imhtml)->freeze_count) return; xoff = GTK_LAYOUT (imhtml)->hadjustment->value; yoff = GTK_LAYOUT (imhtml)->vadjustment->value; bit = line->bit; drawable = GTK_LAYOUT (imhtml)->bin_window; cmap = gtk_widget_get_colormap (GTK_WIDGET (imhtml)); gc = gdk_gc_new (drawable); if (line->selected) { gdk_color_alloc (cmap, imhtml->default_hl_color); gdk_gc_set_foreground (gc, imhtml->default_hl_color); } else if (bit->bg != NULL) { gdk_color_alloc (cmap, bit->bg); gdk_gc_set_foreground (gc, bit->bg); } else { gdk_color_alloc (cmap, imhtml->default_bg_color); gdk_gc_set_foreground (gc, imhtml->default_bg_color); } gdk_draw_rectangle (drawable, gc, TRUE, line->x - xoff, line->y - yoff, line->width, line->height); if (line->selected) { gdk_color_alloc (cmap, imhtml->default_hlfg_color); gdk_gc_set_foreground (gc, imhtml->default_hlfg_color); } else { gdk_color_alloc (cmap, imhtml->default_fg_color); gdk_gc_set_foreground (gc, imhtml->default_fg_color); } line_height = line->height / 2; gdk_draw_rectangle (drawable, gc, TRUE, line->x - xoff, line->y - yoff + line_height / 2, line->width, line_height); gdk_gc_unref (gc); } static void gtk_imhtml_draw_focus (GtkWidget *widget) { GtkIMHtml *imhtml; gint x = 0, y = 0, w = 0, h = 0; imhtml = GTK_IMHTML (widget); if (!GTK_WIDGET_DRAWABLE (widget)) return; if (GTK_WIDGET_HAS_FOCUS (widget)) { #if GTK_CHECK_VERSION(1,3,0) gtk_paint_focus (widget->style, widget->window, GTK_STATE_NORMAL, NULL, widget, "text", 0, 0, widget->allocation.width - 1, widget->allocation.height - 1); #else gtk_paint_focus (widget->style, widget->window, NULL, widget, "text", 0, 0, widget->allocation.width - 1, widget->allocation.height - 1); #endif x = 1; y = 1; w = 2; h = 2; } gtk_paint_shadow (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, "text", x, y, widget->allocation.width - w, widget->allocation.height - h); } static void gtk_imhtml_draw_exposed (GtkIMHtml *imhtml) { GList *bits; GtkIMHtmlBit *bit; GList *chunks; struct line_info *line; gfloat x, y; #if GTK_CHECK_VERSION(1,3,0) guint32 width, height; #else gint width, height; #endif x = GTK_LAYOUT (imhtml)->hadjustment->value; y = GTK_LAYOUT (imhtml)->vadjustment->value; gdk_window_get_size (GTK_LAYOUT (imhtml)->bin_window, &width, &height); bits = imhtml->bits; while (bits) { bit = bits->data; chunks = bit->chunks; if (DRAW_IMG (bit)) { if (chunks) { line = chunks->data; if ((line->x <= x + width) && (line->y <= y + height) && (x <= line->x + line->width) && (y <= line->y + line->height)) draw_img (imhtml, line); } } else if (bit->type == TYPE_SEP) { if (chunks) { line = chunks->data; if ((line->x <= x + width) && (line->y <= y + height) && (x <= line->x + line->width) && (y <= line->y + line->height)) draw_line (imhtml, line); line = chunks->next->data; if ((line->x <= x + width) && (line->y <= y + height) && (x <= line->x + line->width) && (y <= line->y + line->height)) draw_text (imhtml, line); } } else { while (chunks) { line = chunks->data; if ((line->x <= x + width) && (line->y <= y + height) && (x <= line->x + line->width) && (y <= line->y + line->height)) draw_text (imhtml, line); chunks = g_list_next (chunks); } } bits = g_list_next (bits); } gtk_imhtml_draw_focus (GTK_WIDGET (imhtml)); } #if !GTK_CHECK_VERSION(1,3,0) static void gtk_imhtml_draw (GtkWidget *widget, GdkRectangle *area) { GtkIMHtml *imhtml; imhtml = GTK_IMHTML (widget); gtk_imhtml_draw_exposed (imhtml); } #endif 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_CLASS (parent_class)->style_set) (* GTK_WIDGET_CLASS (parent_class)->style_set) (widget, style); if (!GTK_WIDGET_REALIZED (widget)) return; imhtml = GTK_IMHTML (widget); if (imhtml->default_fg_color) gdk_color_free(imhtml->default_fg_color); if (imhtml->default_bg_color) gdk_color_free(imhtml->default_bg_color); if (imhtml->default_hl_color) gdk_color_free(imhtml->default_hl_color); if (imhtml->default_hlfg_color) gdk_color_free(imhtml->default_hlfg_color); imhtml->default_fg_color = gdk_color_copy (>K_WIDGET (imhtml)->style->fg [GTK_STATE_NORMAL]); imhtml->default_bg_color = gdk_color_copy (>K_WIDGET (imhtml)->style->base [GTK_STATE_NORMAL]); imhtml->default_hl_color = gdk_color_copy (>K_WIDGET (imhtml)->style->bg [GTK_STATE_SELECTED]); imhtml->default_hlfg_color=gdk_color_copy (>K_WIDGET (imhtml)->style->fg [GTK_STATE_SELECTED]); if (imhtml->default_font) gdk_font_unref (imhtml->default_font); imhtml->default_font = gdk_font_ref (GTK_IMHTML_GET_STYLE_FONT (widget->style)); gdk_window_set_background (widget->window, &widget->style->base [GTK_STATE_NORMAL]); gdk_window_set_background (GTK_LAYOUT (imhtml)->bin_window, &widget->style->base [GTK_STATE_NORMAL]); gtk_imhtml_draw_exposed (imhtml); } static gint gtk_imhtml_expose_event (GtkWidget *widget, GdkEventExpose *event) { GtkIMHtml *imhtml; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_IMHTML (widget), FALSE); imhtml = GTK_IMHTML (widget); gtk_imhtml_draw_exposed (imhtml); return FALSE; } static void gtk_imhtml_redraw_all (GtkIMHtml *imhtml) { GList *b; GtkIMHtmlBit *bit; GtkAdjustment *vadj; gfloat oldvalue; gint oldy; vadj = GTK_LAYOUT (imhtml)->vadjustment; oldvalue = vadj->value / vadj->upper; oldy = imhtml->y; gtk_layout_freeze (GTK_LAYOUT (imhtml)); g_list_free (imhtml->line); imhtml->line = NULL; while (imhtml->click) { g_free (imhtml->click->data); imhtml->click = g_list_remove (imhtml->click, imhtml->click->data); } imhtml->x = 0; imhtml->y = TOP_BORDER; imhtml->llheight = 0; imhtml->llascent = 0; if (GTK_LAYOUT (imhtml)->vadjustment->value < TOP_BORDER) gdk_window_clear_area (GTK_LAYOUT (imhtml)->bin_window, 0, 0, imhtml->xsize, TOP_BORDER - GTK_LAYOUT (imhtml)->vadjustment->value); b = imhtml->bits; while (b) { bit = b->data; b = g_list_next (b); while (bit->chunks) { struct line_info *li = bit->chunks->data; if (li->text) g_free (li->text); bit->chunks = g_list_remove (bit->chunks, li); g_free (li); } gtk_imhtml_draw_bit (imhtml, bit); } GTK_LAYOUT (imhtml)->height = imhtml->y; GTK_LAYOUT (imhtml)->vadjustment->upper = imhtml->y; gtk_signal_emit_by_name (GTK_OBJECT (GTK_LAYOUT (imhtml)->vadjustment), "changed"); gtk_widget_set_usize (GTK_WIDGET (imhtml), -1, imhtml->y); gtk_adjustment_set_value (vadj, vadj->upper * oldvalue); if (GTK_LAYOUT (imhtml)->bin_window && (imhtml->y < oldy)) { GdkGC *gc; GdkColormap *cmap; gc = gdk_gc_new (GTK_LAYOUT (imhtml)->bin_window); cmap = gtk_widget_get_colormap (GTK_WIDGET (imhtml)); gdk_color_alloc (cmap, imhtml->default_bg_color); gdk_gc_set_foreground (gc, imhtml->default_bg_color); gdk_draw_rectangle (GTK_LAYOUT (imhtml)->bin_window, gc, TRUE, 0, imhtml->y - GTK_LAYOUT (imhtml)->vadjustment->value, GTK_WIDGET (imhtml)->allocation.width, oldy - imhtml->y); gdk_gc_unref (gc); } gtk_layout_thaw (GTK_LAYOUT (imhtml)); gtk_imhtml_draw_focus (GTK_WIDGET (imhtml)); } static void gtk_imhtml_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkIMHtml *imhtml; GtkLayout *layout; gint new_xsize, new_ysize; g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_IMHTML (widget)); g_return_if_fail (allocation != NULL); imhtml = GTK_IMHTML (widget); layout = GTK_LAYOUT (widget); widget->allocation = *allocation; #if GTK_CHECK_VERSION(1,3,0) new_xsize = MAX (1, (gint) allocation->width - (gint) (widget->style->xthickness + BORDER_SIZE) * 2); new_ysize = MAX (1, (gint) allocation->height - (gint) (widget->style->ythickness + BORDER_SIZE) * 2); if (GTK_WIDGET_REALIZED (widget)) { gint x = widget->style->xthickness + BORDER_SIZE; gint y = widget->style->ythickness + BORDER_SIZE; gdk_window_move_resize (widget->window, allocation->x, allocation->y, allocation->width, allocation->height); gdk_window_move_resize (layout->bin_window, x, y, new_xsize, new_ysize); } #else new_xsize = MAX (1, (gint) allocation->width - (gint) (widget->style->klass->xthickness + BORDER_SIZE) * 2); new_ysize = MAX (1, (gint) allocation->height - (gint) (widget->style->klass->ythickness + BORDER_SIZE) * 2); if (GTK_WIDGET_REALIZED (widget)) { gint x = widget->style->klass->xthickness + BORDER_SIZE; gint y = widget->style->klass->ythickness + BORDER_SIZE; gdk_window_move_resize (widget->window, allocation->x, allocation->y, allocation->width, allocation->height); gdk_window_move_resize (layout->bin_window, x, y, new_xsize, new_ysize); } #endif layout->hadjustment->page_size = new_xsize; layout->hadjustment->page_increment = new_xsize / 2; layout->hadjustment->lower = 0; layout->hadjustment->upper = imhtml->x; layout->vadjustment->page_size = new_ysize; layout->vadjustment->page_increment = new_ysize / 2; layout->vadjustment->lower = 0; layout->vadjustment->upper = imhtml->y; gtk_signal_emit_by_name (GTK_OBJECT (layout->hadjustment), "changed"); gtk_signal_emit_by_name (GTK_OBJECT (layout->vadjustment), "changed"); if (new_xsize == imhtml->xsize) { if ((GTK_LAYOUT (imhtml)->vadjustment->value > imhtml->y - new_ysize)) { if (imhtml->y > new_ysize) gtk_adjustment_set_value (GTK_LAYOUT (imhtml)->vadjustment, imhtml->y - new_ysize); else gtk_adjustment_set_value (GTK_LAYOUT (imhtml)->vadjustment, 0); } return; } imhtml->xsize = new_xsize; if (GTK_WIDGET_REALIZED (widget)) gtk_imhtml_redraw_all (imhtml); } static void gtk_imhtml_select_none (GtkIMHtml *imhtml) { GList *bits; GList *chunks; GtkIMHtmlBit *bit; struct line_info *chunk; g_return_if_fail (GTK_IS_IMHTML (imhtml)); bits = imhtml->bits; while (bits) { bit = bits->data; chunks = bit->chunks; while (chunks) { chunk = chunks->data; if (chunk->selected) { chunk->selected = FALSE; chunk->sel_start = chunk->text; chunk->sel_end = NULL; if (DRAW_IMG (bit)) draw_img (imhtml, chunk); else if ((bit->type == TYPE_SEP) && (bit->chunks->data == chunk)) draw_line (imhtml, chunk); else if (chunk->width) draw_text (imhtml, chunk); } chunks = g_list_next (chunks); } bits = g_list_next (bits); } imhtml->sel_endchunk = NULL; } 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 / 2)) 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; } static void chunk_select_words (struct line_info *chunk) { char *start, *end; start = chunk->sel_start; end = chunk->sel_end; if (start != chunk->text) { if (isalnum(*start) || *start == '\'') while (start > chunk->text && (isalnum(*(start-1)) || *(start-1) == '\'')) start--; else if (isspace(*start)) while (start > chunk->text && isspace(*(start-1))) start--; else if (ispunct(*start)) while (start > chunk->text && ispunct(*(start-1))) start--; } chunk->sel_start = start; if (end != NULL) { if (isalnum(*end) || *end == '\'') while (*end != '\0' && (isalnum(*end) || *end == '\'')) end++; else if (isspace(*end)) while (*end != '\0' && isspace(*end)) end++; else if (ispunct(*end)) while (*end != '\0' && ispunct(*end)) end++; } chunk->sel_end = end; } #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; guint startx = imhtml->sel_startx, starty = imhtml->sel_starty, endx = imhtml->sel_endx, endy = imhtml->sel_endy; gchar *new_pos; gint selection = 0; guint mode = imhtml->sel_mode; 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 (""); } if (mode == 2) startx = endx = 0; bits = imhtml->bits; while (bits) { bit = bits->data; chunks = bit->chunks; while (chunks) { chunk = chunks->data; 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 (mode == 2) endy += chunk->height; if (mode == 1) chunk_select_words (chunk); } 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; imhtml->sel_endchunk = chunk; got_end = TRUE; if (mode == 1) chunk_select_words (chunk); } 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++; imhtml->sel_endchunk = chunk; got_end = TRUE; if (mode == 2) starty += chunk->height; if (mode == 1) chunk_select_words (chunk); } } 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; if (mode == 1) chunk_select_words (chunk); } 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++; imhtml->sel_endchunk = chunk; got_end = TRUE; if (mode == 1) chunk_select_words (chunk); } else { if ( !chunk->selected || (chunk->sel_end != NULL) || (chunk->sel_start != chunk->text)) redraw = TRUE; chunk->selected = TRUE; chunk->sel_start = chunk->text; chunk->sel_end = NULL; } break; case 2: if (chunk->selected) 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) { if (DRAW_IMG (bit)) draw_img (imhtml, chunk); else if ((bit->type == TYPE_SEP) && (bit->chunks->data == chunk)) draw_line (imhtml, chunk); else if (chunk->width) draw_text (imhtml, chunk); redraw = FALSE; } chunks = g_list_next (chunks); } bits = g_list_next (bits); } } static void gtk_imhtml_select_in_chunk (GtkIMHtml *imhtml, struct line_info *chunk) { GtkIMHtmlBit *bit = chunk->bit; gchar *new_pos; guint endx = imhtml->sel_endx; guint startx = imhtml->sel_startx; guint starty = imhtml->sel_starty; gboolean smileys = imhtml->smileys; gboolean redraw = FALSE; new_pos = get_position (chunk, endx, smileys); if ((starty < chunk->y) || ((starty < chunk->y + chunk->height) && (startx < endx))) { if (chunk->sel_end != new_pos) redraw = TRUE; chunk->sel_end = new_pos; } else { if (chunk->sel_start != new_pos) redraw = TRUE; chunk->sel_start = new_pos; } if (redraw) { if (DRAW_IMG (bit)) draw_img (imhtml, chunk); else if ((bit->type == TYPE_SEP) && (bit->chunks->data == chunk)) draw_line (imhtml, chunk); else if (chunk->width) draw_text (imhtml, chunk); } } static gint scroll_timeout (GtkIMHtml *imhtml) { GdkEventMotion event; gint x, y; GdkModifierType mask; imhtml->scroll_timer = 0; gdk_window_get_pointer (GTK_LAYOUT (imhtml)->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_tip_paint (GtkIMHtml *imhtml) { GtkStyle *style; GdkFont *font; gint y, baseline_skip, gap; style = imhtml->tip_window->style; font = GTK_IMHTML_GET_STYLE_FONT (style); gap = (font->ascent + font->descent) / 4; if (gap < 2) gap = 2; baseline_skip = font->ascent + font->descent + gap; if (!imhtml->tip_bit) return FALSE; gtk_paint_flat_box (style, imhtml->tip_window->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL, imhtml->tip_window, "tooltip", 0, 0, -1, -1); y = font->ascent + 4; if (imhtml->tip_bit->url) gtk_paint_string (style, imhtml->tip_window->window, GTK_STATE_NORMAL, NULL, imhtml->tip_window, "tooltip", 4, y, imhtml->tip_bit->url); else if (imhtml->tip_bit->img) gtk_paint_string (style, imhtml->tip_window->window, GTK_STATE_NORMAL, NULL, imhtml->tip_window, "tooltip", 4, y, imhtml->tip_bit->img->filename); return FALSE; } static gint gtk_imhtml_tip (gpointer data) { GtkIMHtml *imhtml = data; GtkWidget *widget = GTK_WIDGET (imhtml); GtkStyle *style; GdkFont *font; gint gap, x, y, w, h, scr_w, scr_h, baseline_skip; if (!imhtml->tip_bit || !GTK_WIDGET_DRAWABLE (widget)) { imhtml->tip_timer = 0; return FALSE; } if (imhtml->tip_window) gtk_widget_destroy (imhtml->tip_window); imhtml->tip_window = gtk_window_new (GTK_WINDOW_POPUP); gtk_widget_set_app_paintable (imhtml->tip_window, TRUE); gtk_window_set_policy (GTK_WINDOW (imhtml->tip_window), FALSE, FALSE, TRUE); gtk_widget_set_name (imhtml->tip_window, "gtk-tooltips"); gtk_signal_connect_object (GTK_OBJECT (imhtml->tip_window), "expose_event", GTK_SIGNAL_FUNC (gtk_imhtml_tip_paint), GTK_OBJECT (imhtml)); gtk_signal_connect_object (GTK_OBJECT (imhtml->tip_window), "draw", GTK_SIGNAL_FUNC (gtk_imhtml_tip_paint), GTK_OBJECT (imhtml)); gtk_widget_ensure_style (imhtml->tip_window); style = imhtml->tip_window->style; font = GTK_IMHTML_GET_STYLE_FONT (style); scr_w = gdk_screen_width (); scr_h = gdk_screen_height (); gap = (font->ascent + font->descent) / 4; if (gap < 2) gap = 2; baseline_skip = font->ascent + font->descent + gap; w = 8 + gdk_string_width (font, imhtml->tip_bit->img ? imhtml->tip_bit->img->filename : imhtml->tip_bit->url); h = 8 - gap + baseline_skip; gdk_window_get_pointer (NULL, &x, &y, NULL); if (GTK_WIDGET_NO_WINDOW (widget)) y += widget->allocation.y; x -= ((w >> 1) + 4); if ((x + w) > scr_w) x -= (x + w) - scr_w; else if (x < 0) x = 0; if ((y + h + + 4) > scr_h) y = y - imhtml->tip_bit->font->ascent + imhtml->tip_bit->font->descent; else if (imhtml->tip_bit->font) y = y + imhtml->tip_bit->font->ascent + imhtml->tip_bit->font->descent; else y = y + font->ascent + font->descent; gtk_widget_set_usize (imhtml->tip_window, w, h); gtk_widget_set_uposition (imhtml->tip_window, x, y); gtk_widget_show (imhtml->tip_window); imhtml->tip_timer = 0; 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 / 2) : ((yy - height) / 2); gtk_adjustment_set_value (vadj, MIN (vadj->value + diff, vadj->upper - height)); } if (imhtml->selection) { struct line_info *chunk = imhtml->sel_endchunk; imhtml->sel_endx = MAX (x, 0); imhtml->sel_endy = MAX (y, 0); if ((chunk == NULL) || (x < chunk->x) || (x > chunk->x + chunk->width) || (y < chunk->y) || (y > chunk->y + chunk->height) || (imhtml->sel_mode > 0)) gtk_imhtml_select_bits (imhtml); else gtk_imhtml_select_in_chunk (imhtml, chunk); } } else { GList *click = imhtml->click; struct clickable *uw; while (click) { uw = (struct clickable *) click->data; if ((x > uw->x) && (x < uw->x + uw->width) && (y > uw->y) && (y < uw->y + uw->height) && (uw->bit->url || uw->bit->img)) { if (imhtml->tip_bit != uw->bit) { imhtml->tip_bit = uw->bit; if (imhtml->tip_timer != 0) gtk_timeout_remove (imhtml->tip_timer); if (imhtml->tip_window) { gtk_widget_destroy (imhtml->tip_window); imhtml->tip_window = NULL; } imhtml->tip_timer = gtk_timeout_add (TOOLTIP_TIMEOUT, gtk_imhtml_tip, imhtml); } if (uw->bit->url) gdk_window_set_cursor (GTK_LAYOUT (imhtml)->bin_window, imhtml->hand_cursor); return TRUE; } click = g_list_next (click); } } if (imhtml->tip_timer) { gtk_timeout_remove (imhtml->tip_timer); imhtml->tip_timer = 0; } if (imhtml->tip_window) { gtk_widget_destroy (imhtml->tip_window); imhtml->tip_window = NULL; } imhtml->tip_bit = NULL; gdk_window_set_cursor (GTK_LAYOUT (imhtml)->bin_window, imhtml->arrow_cursor); return TRUE; } static gint gtk_imhtml_leave_notify_event (GtkWidget *widget, GdkEventCrossing *event) { GtkIMHtml *imhtml = GTK_IMHTML (widget); if (imhtml->tip_timer) { gtk_timeout_remove (imhtml->tip_timer); imhtml->tip_timer = 0; } if (imhtml->tip_window) { gtk_widget_destroy (imhtml->tip_window); imhtml->tip_window = NULL; } imhtml->tip_bit = NULL; return TRUE; } struct imgsv { GtkWidget *savedialog; struct im_image *img; }; static void save_img (GtkObject *object, gpointer data) { struct imgsv *is = data; struct im_image *img = is->img; const gchar *filename; FILE *f; filename = gtk_file_selection_get_filename(GTK_FILE_SELECTION(is->savedialog)); g_print("Saving %s\n", filename); if (! (f=fopen(filename, "w"))) { /* There should be some sort of dialog */ g_print("Could not open file for writing.\n"); gtk_widget_destroy(is->savedialog); g_free(is); return; } fwrite(img->data, 1, img->len, f); fclose(f); gtk_widget_destroy(is->savedialog); g_free(is); } static void save_img_dialog (GtkObject *object, gpointer data) { struct imgsv *is = g_malloc(sizeof(struct imgsv)); struct im_image *img = data; GtkWidget *savedialog = gtk_file_selection_new ("Gaim - Save Image"); gtk_file_selection_set_filename (GTK_FILE_SELECTION(savedialog), img->filename); gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION(savedialog)->cancel_button), "clicked", GTK_SIGNAL_FUNC (gtk_widget_destroy), (gpointer) savedialog); is->img = img; is->savedialog = savedialog; gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION(savedialog)->ok_button), "clicked", GTK_SIGNAL_FUNC (save_img), is); gtk_widget_show (savedialog); } static void menu_open_url (GtkObject *object, gpointer data) { struct clickable *uw = data; gtk_signal_emit (GTK_OBJECT (uw->imhtml), signals [URL_CLICKED], uw->bit->url); } static void menu_copy_link (GtkObject *object, gpointer data) { struct clickable *uw = data; GtkIMHtml *imhtml = uw->imhtml; if (imhtml->selected_text) g_string_free (imhtml->selected_text, TRUE); gtk_imhtml_select_none (uw->imhtml); imhtml->selection = TRUE; imhtml->selected_text = g_string_new (uw->bit->url); gtk_selection_owner_set (GTK_WIDGET (imhtml), GDK_SELECTION_PRIMARY, GDK_CURRENT_TIME); } 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; x = event->x + hadj->value; y = event->y + vadj->value; if (event->button == 1) { imhtml->sel_startx = imhtml->sel_endx = x; imhtml->sel_starty = imhtml->sel_endy = y; imhtml->selection = TRUE; if (event->type == GDK_BUTTON_PRESS) { imhtml->sel_mode = 0; /* select by letter */ gtk_imhtml_select_none (imhtml); } else if (event->type == GDK_2BUTTON_PRESS) { imhtml->sel_mode = 1; /* select by word */ gtk_imhtml_select_none (imhtml); } else if (event->type == GDK_3BUTTON_PRESS) { imhtml->sel_mode = 2; /* select by line */ gtk_imhtml_select_bits (imhtml); } } if (event->button == 3) { GList *click = imhtml->click; struct clickable *uw; while (click) { uw = click->data; if ((x > uw->x) && (x < uw->x + uw->width) && (y > uw->y) && (y < uw->y + uw->height)) { static GtkWidget *menu = NULL; GtkWidget *button; /* * If a menu already exists, destroy it before creating a new one, * thus freeing-up the memory it occupied. */ if(menu) gtk_widget_destroy(menu); menu = gtk_menu_new(); if (uw->bit->url) { button = gtk_menu_item_new_with_label ("Open URL"); gtk_signal_connect (GTK_OBJECT (button), "activate", GTK_SIGNAL_FUNC (menu_open_url), uw); gtk_menu_append (GTK_MENU (menu), button); gtk_widget_show (button); button = gtk_menu_item_new_with_label ("Copy Link Location"); gtk_signal_connect (GTK_OBJECT (button), "activate", GTK_SIGNAL_FUNC (menu_copy_link), uw); gtk_menu_append (GTK_MENU (menu), button); gtk_widget_show (button); } if (uw->bit->img) { button = gtk_menu_item_new_with_label ("Save Image"); gtk_signal_connect (GTK_OBJECT (button), "activate", GTK_SIGNAL_FUNC (save_img_dialog), uw->bit->img); gtk_menu_append (GTK_MENU (menu), button); gtk_widget_show (button); } gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 3, event->time); if (imhtml->tip_timer) { gtk_timeout_remove (imhtml->tip_timer); imhtml->tip_timer = 0; } if (imhtml->tip_window) { gtk_widget_destroy (imhtml->tip_window); imhtml->tip_window = NULL; } imhtml->tip_bit = NULL; return TRUE; } click = g_list_next (click); } } 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; x = event->x + hadj->value; y = event->y + vadj->value; if ((event->button == 1) && imhtml->selection) { if ((x == imhtml->sel_startx) && (y == imhtml->sel_starty) && (imhtml->sel_mode == 0)) { 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); } if ((event->button == 1) && (imhtml->sel_startx == 0)) { GList *click = imhtml->click; struct clickable *uw; while (click) { uw = (struct clickable *) click->data; if ((x > uw->x) && (x < uw->x + uw->width) && (y > uw->y) && (y < uw->y + uw->height)) { gtk_signal_emit (GTK_OBJECT (imhtml), signals [URL_CLICKED], uw->bit->url); return TRUE; } click = g_list_next (click); } } 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) return; 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); } 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_adjustment_changed (GtkAdjustment *adjustment, GtkIMHtml *imhtml) { GtkLayout *layout = GTK_LAYOUT (imhtml); if (!GTK_WIDGET_MAPPED (imhtml) || !GTK_WIDGET_REALIZED (imhtml)) return; if (layout->freeze_count) return; if (layout->vadjustment->value < TOP_BORDER) gdk_window_clear_area (layout->bin_window, 0, 0, imhtml->xsize, TOP_BORDER - layout->vadjustment->value); gtk_imhtml_draw_exposed (imhtml); } static void gtk_imhtml_set_scroll_adjustments (GtkLayout *layout, GtkAdjustment *hadj, GtkAdjustment *vadj) { gboolean need_adjust = FALSE; g_return_if_fail (layout != NULL); g_return_if_fail (GTK_IS_IMHTML (layout)); if (hadj) g_return_if_fail (GTK_IS_ADJUSTMENT (hadj)); else hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); if (vadj) g_return_if_fail (GTK_IS_ADJUSTMENT (vadj)); else vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); if (layout->hadjustment && (layout->hadjustment != hadj)) { gtk_signal_disconnect_by_data (GTK_OBJECT (layout->hadjustment), layout); gtk_object_unref (GTK_OBJECT (layout->hadjustment)); } if (layout->vadjustment && (layout->vadjustment != vadj)) { gtk_signal_disconnect_by_data (GTK_OBJECT (layout->vadjustment), layout); gtk_object_unref (GTK_OBJECT (layout->vadjustment)); } if (layout->hadjustment != hadj) { layout->hadjustment = hadj; gtk_object_ref (GTK_OBJECT (layout->hadjustment)); gtk_object_sink (GTK_OBJECT (layout->hadjustment)); gtk_signal_connect (GTK_OBJECT (layout->hadjustment), "value_changed", (GtkSignalFunc) gtk_imhtml_adjustment_changed, layout); need_adjust = TRUE; } if (layout->vadjustment != vadj) { layout->vadjustment = vadj; gtk_object_ref (GTK_OBJECT (layout->vadjustment)); gtk_object_sink (GTK_OBJECT (layout->vadjustment)); gtk_signal_connect (GTK_OBJECT (layout->vadjustment), "value_changed", (GtkSignalFunc) gtk_imhtml_adjustment_changed, layout); need_adjust = TRUE; } if (need_adjust) gtk_imhtml_adjustment_changed (NULL, GTK_IMHTML (layout)); } static void gtk_imhtml_class_init (GtkIMHtmlClass *class) { #if GTK_CHECK_VERSION(1,3,0) GObjectClass *gobject_class; #endif GtkObjectClass *object_class; GtkWidgetClass *widget_class; GtkLayoutClass *layout_class; #if GTK_CHECK_VERSION(1,3,0) gobject_class = (GObjectClass*) class; #endif 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, GTK_CLASS_TYPE (object_class), GTK_SIGNAL_OFFSET (GtkIMHtmlClass, url_clicked), gtk_marshal_NONE__POINTER, GTK_TYPE_NONE, 1, GTK_TYPE_POINTER); #if GTK_CHECK_VERSION(1,3,0) gobject_class->finalize = gtk_imhtml_finalize; #else gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL); object_class->destroy = gtk_imhtml_destroy; #endif widget_class->realize = gtk_imhtml_realize; #if !GTK_CHECK_VERSION(1,3,0) widget_class->draw = gtk_imhtml_draw; widget_class->draw_focus = gtk_imhtml_draw_focus; #endif widget_class->style_set = gtk_imhtml_style_set; widget_class->expose_event = gtk_imhtml_expose_event; widget_class->size_allocate = gtk_imhtml_size_allocate; widget_class->motion_notify_event = gtk_imhtml_motion_notify_event; widget_class->leave_notify_event = gtk_imhtml_leave_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; } /* the font stuff is the most insane stuff. i don't understand it half * the time. so we're going to comment it. isn't that wonderful. */ /* when you g_strsplit a valid font name, these are the positions of all the various parts of it. */ #define FNDRY 1 #define FMLY 2 #define WGHT 3 #define SLANT 4 #define SWDTH 5 #define ADSTYL 6 #define PXLSZ 7 #define PTSZ 8 #define RESX 9 #define RESY 10 #define SPC 11 #define AVGWDTH 12 #define RGSTRY 13 #define ENCDNG 14 static const gchar* gtk_imhtml_get_font_name (GdkFont *font) { #if GTK_CHECK_VERSION(1,3,0) return gdk_x11_font_get_name(font); #else GdkFontPrivate *fontpriv = (GdkFontPrivate *) font; return fontpriv->names->data; #endif } static GdkFont* gtk_imhtml_font_load (GtkIMHtml *imhtml, gchar *name, gboolean bold, gboolean italics, gint fontsize) { GdkFont *default_font = imhtml->default_font; const gchar *default_name; gchar **xnames; gchar **pos; gchar *tmp = NULL; GdkFont *ret_font; gchar *xname; gchar **xflds; gchar **newvals; gchar **names = NULL; char *italicstrings[] = {"i","o","*"}; int italicsind = 0, nameind = 0; gboolean usebold = TRUE, usesize = TRUE, useregenc = TRUE; /* if we're not changing anything, use the default. this is the common case */ if (!name && !bold && !italics && !fontsize) return gdk_font_ref (default_font); /* base things off of the default font name */ default_name = gtk_imhtml_get_font_name (default_font); /* the default font name can actually be several names separated by ','. * This is a fontset... used in foreign encodings. */ do { xnames = g_strsplit (default_name, ",", -1); for (pos = xnames; pos && *pos; pos++) { gint i, j; gchar fs[10]; gchar *garbage; xname = *pos; xname = g_strchomp (xname); xname = g_strchug (xname); xflds = g_strsplit (xname, "-", -1); /* figure out if we have a valid name. i wish there were an * easier way for determining how many values g_strplit gave */ for (i = 0; xflds [i]; i++); if (i != 15) { int tmp; newvals = g_malloc0 (16 * sizeof (gchar *)); newvals [0] = ""; for (tmp = 1; tmp < 15; tmp++) newvals [tmp] = "*"; } else newvals = g_memdup (xflds, 16 * sizeof (xflds)); /* we force foundry as "*" because i hate them. i should give a better reason. */ newvals [FNDRY] = "*"; /* if it's "*" then it defaults to (nil) anyway. some fonts don't want (nil) */ if ((i > ADSTYL) && !xflds [ADSTYL][0]) newvals [ADSTYL] = "*"; /* If the font doesn't work the first time, we try it with * registry and encoding as "*" */ if (!useregenc) { newvals [RGSTRY] = "*"; newvals [ENCDNG] = "*"; } /* right. */ if (usebold && bold) newvals [WGHT] = "bold"; else if (!usebold) newvals [WGHT] = "*"; if (italics) /* We'll try "i" "o" to get italics and then just use "*" */ newvals [SLANT] = italicstrings[italicsind]; if (usesize && fontsize) { g_snprintf (fs, sizeof (fs), "%d", POINT_SIZE (fontsize)); newvals [PTSZ] = fs; newvals [PXLSZ] = "*"; } else if (!usesize) { newvals [PTSZ] = "*"; newvals [PXLSZ] = "*"; } if (name) { /* we got passed a name. it might be a list of names. */ gchar **tmp_nms = g_strsplit (name, ",", -1); for (j = 0; tmp_nms [j]; j++); names = g_new0 (char *, j + 2); for (j = 0; tmp_nms [j]; j++) names [j] = tmp_nms [j]; g_free (tmp_nms); /* Put the default font on the array. */ if (i > FMLY) { names [j] = g_strdup (xflds [FMLY]); } newvals [FMLY] = names[nameind]; } else if (i > FMLY) { /* we didn't get a name. we come here if the gtk font name is valid */ names = g_new0 (gchar *, 2); names [0] = g_strdup (xflds [FMLY]); } else { /* we got fucked */ names = g_new0 (gchar *, 2); names [0] = g_strdup ("*"); } if (!tmp) tmp = g_strjoinv("-", newvals); else { /* We have to concat the xlfds in the fontset */ garbage = tmp; tmp = g_strconcat(garbage, ",", g_strjoinv ("-", newvals), NULL); g_free(garbage); } g_free (newvals); g_strfreev (xflds); } g_strfreev (xnames); if (default_font->type == GDK_FONT_FONT) ret_font = gdk_font_load (tmp); else { /* For some reason, fontsets must end with a single * as an xlfd */ gchar *garbage = tmp; tmp = g_strconcat(garbage, ",*", NULL); ret_font = gdk_fontset_load (tmp); } /* If the font didn't load, we change some of the xlfds one by one * to get the closest we can. */ if (!ret_font) { if (useregenc) { useregenc = FALSE; } else if (italics && italicsind != 2) { useregenc = TRUE; italicsind++; } else if (bold && usebold) { useregenc = TRUE; italicsind=0; usebold = FALSE; } else if (usesize) { useregenc = TRUE; italicsind = 0; usebold = TRUE; usesize = FALSE; } else if (names && names[nameind++]) { useregenc = TRUE; italicsind = 0; usebold = TRUE; usesize = TRUE; } else { ret_font = gdk_font_ref(default_font); } } g_strfreev (names); names = NULL; g_free(tmp); tmp=NULL; } while (!ret_font); /* Loop with the new options */ return ret_font; } 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->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; } void gtk_imhtml_init_smileys (GtkIMHtml *imhtml) { g_return_if_fail (imhtml != NULL); g_return_if_fail (GTK_IS_IMHTML (imhtml)); imhtml->smiley_data = gtk_smiley_tree_new (); 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, "=-o", scream_xpm); gtk_imhtml_associate_smiley (imhtml, ":-*", kiss_xpm); gtk_imhtml_associate_smiley (imhtml, ">:O", yell_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, ":-x", crossedlips_xpm); gtk_imhtml_associate_smiley (imhtml, ":-D", bigsmile_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->im_images = NULL; imhtml->bits = NULL; imhtml->click = NULL; imhtml->x = 0; imhtml->y = TOP_BORDER; 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; gtk_imhtml_init_smileys (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, GdkColor *bg_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); } if (bg_color) { if (imhtml->default_bg_color) gdk_color_free (imhtml->default_bg_color); imhtml->default_bg_color = gdk_color_copy (bg_color); gdk_window_set_background (GTK_LAYOUT (imhtml)->bin_window, imhtml->default_bg_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 (xpm == NULL) gtk_smiley_tree_remove (imhtml->smiley_data, text); else gtk_smiley_tree_insert (imhtml->smiley_data, text, xpm); } static void new_line (GtkIMHtml *imhtml) { GList *last = g_list_last (imhtml->line); struct line_info *li; if (last) { li = last->data; if (li->x + li->width != imhtml->xsize) li->width = imhtml->xsize - li->x; } last = imhtml->line; if (last) { li = last->data; if (li->height < MIN_HEIGHT) { while (last) { gint diff; li = last->data; diff = MIN_HEIGHT - li->height; li->height = MIN_HEIGHT; li->ascent += diff / 2; last = g_list_next (last); } imhtml->llheight = MIN_HEIGHT; } } g_list_free (imhtml->line); imhtml->line = NULL; imhtml->x = 0; imhtml->y += imhtml->llheight; imhtml->llheight = 0; imhtml->llascent = 0; } static void backwards_update (GtkIMHtml *imhtml, GtkIMHtmlBit *bit, gint height, gint ascent) { gint diff; GList *ls = NULL; struct line_info *li; struct clickable *uw; struct im_image *img; if (height > imhtml->llheight) { diff = height - imhtml->llheight; ls = imhtml->line; while (ls) { li = ls->data; li->height += diff; if (ascent) li->ascent = ascent; else li->ascent += diff / 2; ls = g_list_next (ls); } ls = imhtml->click; while (ls) { uw = ls->data; if (uw->y + diff > imhtml->y) uw->y += diff; ls = g_list_next (ls); } ls = imhtml->im_images; while(ls) { img = ls->data; if (img->y + diff > imhtml->y) img->y += diff; ls = g_list_next(ls); } imhtml->llheight = height; if (ascent) imhtml->llascent = ascent; else imhtml->llascent += diff / 2; } } static void add_text_renderer (GtkIMHtml *imhtml, GtkIMHtmlBit *bit, gchar *text) { struct line_info *li; struct clickable *uw; gint width; if (text) width = gdk_string_width (bit->font, text); else width = 0; 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; if (bit->url) { uw = g_new0 (struct clickable, 1); uw->x = imhtml->x; uw->y = imhtml->y; uw->width = width; uw->height = imhtml->llheight; uw->imhtml = imhtml; uw->bit = bit; imhtml->click = g_list_append (imhtml->click, uw); } bit->chunks = g_list_append (bit->chunks, li); imhtml->line = g_list_append (imhtml->line, li); } static void add_img_renderer (GtkIMHtml *imhtml, GtkIMHtmlBit *bit) { struct line_info *li; struct clickable *uw; gint width; gdk_window_get_size (bit->pm, &width, 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; if (bit->url || bit->img) { uw = g_new0 (struct clickable, 1); uw->x = imhtml->x; uw->y = imhtml->y; uw->width = width; uw->height = imhtml->llheight; uw->imhtml = imhtml; uw->bit = bit; imhtml->click = g_list_append (imhtml->click, uw); } bit->chunks = g_list_append (bit->chunks, li); imhtml->line = g_list_append (imhtml->line, li); imhtml->x += width; } static void gtk_imhtml_draw_bit (GtkIMHtml *imhtml, GtkIMHtmlBit *bit) { gint width, height; g_return_if_fail (imhtml != NULL); g_return_if_fail (GTK_IS_IMHTML (imhtml)); g_return_if_fail (bit != NULL); if ( ((bit->type == TYPE_TEXT) || ((bit->type == TYPE_SMILEY) && !imhtml->smileys) || ((bit->type == TYPE_COMMENT) && imhtml->comments)) && bit->text) { 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 != 0) && ((imhtml->x + width) > imhtml->xsize)) { gint remain = imhtml->xsize - imhtml->x; 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); } 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 > imhtml->xsize) { gint newpos = 0; gint remain = imhtml->xsize - imhtml->x; 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; backwards_update (imhtml, bit, height, bit->font->ascent); add_text_renderer (imhtml, bit, tmp); seenspace = FALSE; new_line (imhtml); } else { tmp = g_strdup (copy + pos); backwards_update (imhtml, bit, height, bit->font->ascent); 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)) { #if USE_PIXBUF || GTK_CHECK_VERSION(1,3,0) if (bit->img) { GdkPixbuf *imagepb = bit->img->pb; GdkPixbuf *tmp = NULL; if (gdk_pixbuf_get_width(imagepb) > imhtml->xsize - imhtml->x) new_line (imhtml); if (gdk_pixbuf_get_width(imagepb) > imhtml->xsize) { tmp = gdk_pixbuf_scale_simple(imagepb, imhtml->xsize, gdk_pixbuf_get_height(imagepb) * imhtml->xsize/ gdk_pixbuf_get_width(imagepb), GDK_INTERP_TILES); if (bit->pm) gdk_pixmap_unref (bit->pm); if (bit->bm) gdk_bitmap_unref (bit->bm); gdk_pixbuf_render_pixmap_and_mask(tmp, &(bit->pm), &(bit->bm), 100); gdk_pixbuf_unref(tmp); } else { if (bit->pm) gdk_pixmap_unref (bit->pm); if (bit->bm) gdk_bitmap_unref (bit->bm); gdk_pixbuf_render_pixmap_and_mask(imagepb, &(bit->pm), &(bit->bm), 100); } } #endif gdk_window_get_size (bit->pm, &width, &height); if ((imhtml->x != 0) && ((imhtml->x + width) > imhtml->xsize)) new_line (imhtml); else backwards_update (imhtml, bit, height, 0); add_img_renderer (imhtml, bit); } else if (bit->type == TYPE_BR) { new_line (imhtml); add_text_renderer (imhtml, bit, NULL); } else if (bit->type == TYPE_SEP) { struct line_info *li; if (imhtml->llheight) new_line (imhtml); li = g_new0 (struct line_info, 1); li->x = imhtml->x; li->y = imhtml->y; li->width = imhtml->xsize; li->height = HR_HEIGHT * 2; li->ascent = 0; li->text = NULL; li->bit = bit; bit->chunks = g_list_append (bit->chunks, li); imhtml->llheight = HR_HEIGHT * 2; new_line (imhtml); add_text_renderer (imhtml, bit, NULL); } } 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; if (!gdk_color_parse (color, &c)) return NULL; return gdk_color_copy (&c); } static gboolean gtk_imhtml_is_smiley (GtkIMHtml *imhtml, const gchar *text, gint *len) { *len = gtk_smiley_tree_lookup (imhtml->smiley_data, text); return (*len > 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, gint pre, gint sub, gint sup) { 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 || pre) { if (font && (bold || italics || font->size || font->face || pre)) { if (pre) { bit->font = gtk_imhtml_font_load (imhtml, DEFAULT_PRE_FACE, bold, italics, font->size); } else { bit->font = gtk_imhtml_font_load (imhtml, font->face, bold, italics, font->size); } } else if (bold || italics || pre) { if (pre) { bit->font = gtk_imhtml_font_load (imhtml, DEFAULT_PRE_FACE, bold, italics, 0); } else { 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 : imhtml->default_bg_color; bit->pm = gdk_pixmap_create_from_xpm_d (GTK_WIDGET (imhtml)->window, &bit->bm, clr, gtk_smiley_tree_image (imhtml->smiley_data, 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, pre, sub, sup) #define NEW_SMILEY_BIT gtk_imhtml_new_bit (imhtml, TYPE_SMILEY, ws, bold, italics, underline, strike, \ fonts ? fonts->data : NULL, bg, url, pre, sub, sup) #define NEW_SEP_BIT gtk_imhtml_new_bit (imhtml, TYPE_SEP, NULL, 0, 0, 0, 0, NULL, bg, NULL, 0, 0, 0) #define NEW_BR_BIT gtk_imhtml_new_bit (imhtml, TYPE_BR, NULL, 0, 0, 0, 0, \ fonts ? fonts->data : NULL, bg, NULL, 0, 0, 0) #define NEW_COMMENT_BIT gtk_imhtml_new_bit (imhtml, TYPE_COMMENT, ws, bold, italics, underline, strike, \ fonts ? fonts->data : NULL, bg, url, pre, sub, sup) #define NEW_BIT(bit) ws [wpos] = '\0'; \ { GtkIMHtmlBit *tmp = bit; if (tmp != NULL) \ newbits = g_list_append (newbits, tmp); } \ wpos = 0; ws [wpos] = '\0' #define UPDATE_BG_COLORS \ { \ GdkColormap *cmap; \ GList *rev; \ cmap = gtk_widget_get_colormap (GTK_WIDGET (imhtml)); \ rev = g_list_last (newbits); \ while (rev) { \ GtkIMHtmlBit *bit = rev->data; \ if (bit->bg) \ gdk_color_free (bit->bg); \ bit->bg = gdk_color_copy (bg); \ if (bit->type == TYPE_BR) \ break; \ rev = g_list_previous (rev); \ } \ if (!rev) { \ rev = g_list_last (imhtml->bits); \ while (rev) { \ GtkIMHtmlBit *bit = rev->data; \ if (bit->bg) \ gdk_color_free (bit->bg); \ bit->bg = gdk_color_copy (bg); \ gdk_color_alloc (cmap, bit->bg); \ if (bit->type == TYPE_BR) \ break; \ rev = g_list_previous (rev); \ } \ } \ } static gboolean gtk_imhtml_is_amp_escape (const gchar *string, gchar *replace, gint *length) { 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_strncasecmp (string, "&", 5)) { *replace = '&'; *length = 5; } else if (!g_strncasecmp (string, "<", 4)) { *replace = '<'; *length = 4; } else if (!g_strncasecmp (string, ">", 4)) { *replace = '>'; *length = 4; } else if (!g_strncasecmp (string, " ", 6)) { *replace = ' '; *length = 6; } else if (!g_strncasecmp (string, "©", 6)) { *replace = '©'; *length = 6; } else if (!g_strncasecmp (string, """, 6)) { *replace = '\"'; *length = 6; } else if (!g_strncasecmp (string, "®", 5)) { *replace = '®'; *length = 5; } else if (*(string + 1) == '#') { guint pound = 0; if ((sscanf (string, "&#%u;", £) == 1) && pound != 0) { if (*(string + 3 + (gint)log10 (pound)) != ';') return FALSE; *replace = (gchar)pound; *length = 2; while (isdigit ((gint) string [*length])) (*length)++; if (string [*length] == ';') (*length)++; } else { return FALSE; } } else { return FALSE; } return TRUE; } #define VALID_TAG(x) if (!g_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_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_tag (const gchar *string, gchar **tag, gint *len, gint *type) { *type = 1; if (!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"); if (!g_strncasecmp(string, "!--", strlen ("!--"))) { gchar *e = strstr (string + strlen("!--"), "-->"); if (e) { *len = e - string + strlen ("-->"); *tag = g_strndup (string + strlen ("!--"), *len - strlen ("!---->")); return TRUE; } } return FALSE; } static gchar* gtk_imhtml_get_html_opt (gchar *tag, const gchar *opt) { gchar *t = tag; gchar *e, *a; while (g_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_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 return g_strndup (a, e - a); } else { e = a = t; while (*e && !isspace ((gint) *e)) e++; return g_strndup (a, e - a); } } GString* gtk_imhtml_append_text (GtkIMHtml *imhtml, const gchar *text, gint len, GtkIMHtmlOptions options) { const gchar *c; gboolean binary = TRUE; gchar *ws; gint pos = 0; gint wpos = 0; gchar *tag; gint tlen; gint type; gchar amp; gint smilelen; GList *newbits = NULL; guint bold = 0, italics = 0, underline = 0, strike = 0, sub = 0, sup = 0, title = 0, pre = 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 - GTK_WIDGET (imhtml)->allocation.height) && (vadj->upper >= GTK_WIDGET (imhtml)->allocation.height)) scrolldown = FALSE; c = text; if (len == -1) { binary = FALSE; 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++; switch (type) { case 1: /* B */ case 2: /* BOLD */ NEW_BIT (NEW_TEXT_BIT); bold++; break; case 3: /* /B */ case 4: /* /BOLD */ NEW_BIT (NEW_TEXT_BIT); if (bold) bold--; break; case 5: /* I */ case 6: /* ITALIC */ NEW_BIT (NEW_TEXT_BIT); italics++; break; case 7: /* /I */ case 8: /* /ITALIC */ NEW_BIT (NEW_TEXT_BIT); if (italics) italics--; break; case 9: /* U */ case 10: /* UNDERLINE */ NEW_BIT (NEW_TEXT_BIT); underline++; break; case 11: /* /U */ case 12: /* /UNDERLINE */ NEW_BIT (NEW_TEXT_BIT); if (underline) underline--; break; case 13: /* S */ case 14: /* STRIKE */ NEW_BIT (NEW_TEXT_BIT); strike++; break; case 15: /* /S */ case 16: /* /STRIKE */ NEW_BIT (NEW_TEXT_BIT); if (strike) strike--; break; case 17: /* SUB */ NEW_BIT (NEW_TEXT_BIT); sub++; break; case 18: /* /SUB */ NEW_BIT (NEW_TEXT_BIT); if (sub) sub--; break; case 19: /* SUP */ NEW_BIT (NEW_TEXT_BIT); sup++; break; case 20: /* /SUP */ NEW_BIT (NEW_TEXT_BIT); if (sup) sup--; break; case 21: /* PRE */ NEW_BIT (NEW_TEXT_BIT); pre++; break; case 22: /* /PRE */ NEW_BIT (NEW_TEXT_BIT); if (pre) pre--; break; case 23: /* TITLE */ NEW_BIT (NEW_TEXT_BIT); title++; break; case 24: /* /TITLE */ if (title) { if (options & GTK_IMHTML_NO_TITLE) { wpos = 0; ws [wpos] = '\0'; } title--; } break; case 25: /* BR */ NEW_BIT (NEW_TEXT_BIT); NEW_BIT (NEW_BR_BIT); break; case 26: /* HR */ NEW_BIT (NEW_TEXT_BIT); NEW_BIT (NEW_SEP_BIT); break; case 27: /* /FONT */ if (fonts) { FontDetail *font = fonts->data; NEW_BIT (NEW_TEXT_BIT); fonts = g_slist_remove (fonts, font); if (font->face) g_free (font->face); if (font->fore) gdk_color_free (font->fore); if (font->back) gdk_color_free (font->back); g_free (font); } break; case 28: /* /A */ if (url) { NEW_BIT (NEW_TEXT_BIT); g_free (url); url = NULL; } break; case 29: /* P */ case 30: /* /P */ case 31: /* H3 */ case 32: /* /H3 */ case 33: /* HTML */ case 34: /* /HTML */ case 35: /* BODY */ case 36: /* /BODY */ case 37: /* FONT */ case 38: /* HEAD */ case 39: /* /HEAD */ break; case 40: /* BINARY */ NEW_BIT (NEW_TEXT_BIT); while (pos < len) { if (!g_strncasecmp("</BINARY>", c, strlen("</BINARY>"))) break; else { c++; pos++; } } c = c - tlen; /* Because it will add this later */ break; case 41: /* /BINARY */ break; case 42: /* HR (opt) */ NEW_BIT (NEW_TEXT_BIT); NEW_BIT (NEW_SEP_BIT); break; case 43: /* FONT (opt) */ { gchar *color, *back, *face, *size; FontDetail *font; 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="); if (!(color || back || face || size)) break; NEW_BIT (NEW_TEXT_BIT); font = g_new0 (FontDetail, 1); if (color && !(options & GTK_IMHTML_NO_COLOURS)) font->fore = gtk_imhtml_get_color (color); if (back && !(options & GTK_IMHTML_NO_COLOURS)) font->back = gtk_imhtml_get_color (back); if (face && !(options & GTK_IMHTML_NO_FONTS)) font->face = g_strdup (face); if (size && !(options & GTK_IMHTML_NO_SIZES)) { 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); } } g_free (color); g_free (back); g_free (face); g_free (size); if (fonts) { FontDetail *oldfont = fonts->data; if (!font->size) font->size = oldfont->size; if (!font->face && oldfont->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); } fonts = g_slist_prepend (fonts, font); } break; case 44: /* BODY (opt) */ if (!(options & GTK_IMHTML_NO_COLOURS)) { gchar *bgcolor = gtk_imhtml_get_html_opt (tag, "BGCOLOR="); if (bgcolor) { GdkColor *tmp = gtk_imhtml_get_color (bgcolor); g_free (bgcolor); if (tmp) { NEW_BIT (NEW_TEXT_BIT); bg = tmp; UPDATE_BG_COLORS; } } } break; case 45: /* A (opt) */ { gchar *href = gtk_imhtml_get_html_opt (tag, "HREF="); if (href) { NEW_BIT (NEW_TEXT_BIT); g_free (url); url = href; } } break; case 46: /* IMG (opt) */ { gchar *src = gtk_imhtml_get_html_opt (tag, "SRC="); gchar *id = gtk_imhtml_get_html_opt (tag, "ID="); gchar *datasize = gtk_imhtml_get_html_opt (tag, "DATASIZE="); gchar **xpm; GdkColor *clr; GtkIMHtmlBit *bit; if (!src) break; if (!imhtml->img && id && datasize) { /* This is an embedded IM image */ char *tmp, *imagedata, *e; const gchar *alltext; struct im_image *img; #if USE_PIXBUF || GTK_CHECK_VERSION(1,3,0) GdkPixbufLoader *load; GdkPixbuf *imagepb = NULL; #if GTK_CHECK_VERSION(1,3,0) GError *err; #endif #endif NEW_BIT (NEW_TEXT_BIT); if (!id || !datasize) break; tmp = g_malloc(strlen("<DATA ID=\"\" SIZE=\"\">") + strlen(id) + strlen(datasize)); g_snprintf(tmp, strlen("<DATA ID=\"\" SIZE=\"\">") + strlen(id) + strlen(datasize) + 1, "<DATA ID=\"%s\" SIZE=\"%s\">", id, datasize); alltext = c; while (g_strncasecmp(alltext, tmp, strlen(tmp)) && alltext < (c + len)) alltext++; alltext = alltext + strlen("<DATA ID=\"\" SIZE=\"\">") + strlen(id) + strlen(datasize); g_free(tmp); if (atoi(datasize) > len - pos) break; imagedata = g_malloc(atoi(datasize)); memcpy(imagedata, alltext, atoi(datasize)); if (!GTK_WIDGET_REALIZED (imhtml)) gtk_widget_realize (GTK_WIDGET (imhtml)); img = g_new0 (struct im_image, 1); tmp = e = src; while (*tmp){ if (*tmp == '/' || *tmp == '\\') { tmp++; src = tmp; } else tmp++; } *tmp = '\0'; img->filename = g_strdup(src); img->len = atoi(datasize); if (img->len) { img->data = g_malloc(img->len); memcpy(img->data, imagedata, img->len); #if USE_PIXBUF || GTK_CHECK_VERSION(1,3,0) load = gdk_pixbuf_loader_new(); #if GTK_CHECK_VERSION(1,3,0) if (!gdk_pixbuf_loader_write(load, imagedata, img->len, &err)) #else if (!gdk_pixbuf_loader_write(load, imagedata, img->len)) #endif g_print("IM Image corrupt or unreadable.\n"); else imagepb = gdk_pixbuf_loader_get_pixbuf(load); img->pb = imagepb; #endif } #if USE_PIXBUF || GTK_CHECK_VERSION(1,3,0) if (imagepb) { bit = g_new0 (GtkIMHtmlBit, 1); bit->type = TYPE_IMG; bit->img = img; if (url) bit->url = g_strdup (url); NEW_BIT (bit); } else { g_free(img->filename); g_free(img->data); } #else bit = g_new0 (GtkIMHtmlBit, 1); bit->type = TYPE_IMG; bit->img = img; if (url) bit->url = g_strdup (url); if (!fonts || ((clr = ((FontDetail *) fonts->data)->back) == NULL)) clr = (bg != NULL) ? bg : imhtml->default_bg_color; bit->pm = gdk_pixmap_create_from_xpm_d (GTK_WIDGET (imhtml)->window, &bit->bm, clr, broken_xpm); NEW_BIT (bit); #endif g_free(imagedata); g_free(e); g_free(id); g_free(datasize); break; } if (!imhtml->img || ((xpm = imhtml->img (src)) == NULL)) { g_free (src); break; } if (!fonts || ((clr = ((FontDetail *) fonts->data)->back) == NULL)) clr = (bg != NULL) ? bg : imhtml->default_bg_color; 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); } break; case 47: /* P (opt) */ case 48: /* H3 (opt) */ break; case 49: /* comment */ NEW_BIT (NEW_TEXT_BIT); wpos = g_snprintf (ws, len, "%s", tag); NEW_BIT (NEW_COMMENT_BIT); break; default: break; } g_free (tag); c += tlen; pos += tlen; } else if (*c == '&' && gtk_imhtml_is_amp_escape (c, &, &tlen)) { ws [wpos++] = amp; c += tlen; pos += tlen; } else if (*c == '\n') { if (!(options & GTK_IMHTML_NO_NEWLINE)) { NEW_BIT (NEW_TEXT_BIT); NEW_BIT (NEW_BR_BIT); } c++; pos++; } else if (gtk_imhtml_is_smiley (imhtml, c, &smilelen)) { NEW_BIT (NEW_TEXT_BIT); wpos = g_snprintf (ws, smilelen + 1, "%s", c); NEW_BIT (NEW_SMILEY_BIT); c += smilelen; pos += smilelen; } else if (*c) { ws [wpos++] = *c++; pos++; } else { break; } } 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_LAYOUT (imhtml)->height = imhtml->y; GTK_LAYOUT (imhtml)->vadjustment->upper = imhtml->y; gtk_signal_emit_by_name (GTK_OBJECT (GTK_LAYOUT (imhtml)->vadjustment), "changed"); gtk_widget_set_usize (GTK_WIDGET (imhtml), -1, imhtml->y); #if GTK_CHECK_VERSION(1,3,0) if (!(options & GTK_IMHTML_NO_SCROLL) && scrolldown && (imhtml->y >= MAX (1, (GTK_WIDGET (imhtml)->allocation.height - (GTK_WIDGET (imhtml)->style->ythickness + BORDER_SIZE) * 2)))) gtk_adjustment_set_value (vadj, imhtml->y - MAX (1, (GTK_WIDGET (imhtml)->allocation.height - (GTK_WIDGET (imhtml)->style->ythickness + BORDER_SIZE) * 2))); #else if (!(options & GTK_IMHTML_NO_SCROLL) && scrolldown && (imhtml->y >= MAX (1, (GTK_WIDGET (imhtml)->allocation.height - (GTK_WIDGET (imhtml)->style->klass->ythickness + BORDER_SIZE) * 2)))) gtk_adjustment_set_value (vadj, imhtml->y - MAX (1, (GTK_WIDGET (imhtml)->allocation.height - (GTK_WIDGET (imhtml)->style->klass->ythickness + BORDER_SIZE) * 2))); #endif 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); if (font->face) 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--; } while (pre) { retval = g_string_append (retval, "</PRE>"); pre--; } } g_free (ws); return retval; } void gtk_imhtml_clear (GtkIMHtml *imhtml) { GtkLayout *layout; g_return_if_fail (imhtml != NULL); g_return_if_fail (GTK_IS_IMHTML (imhtml)); layout = GTK_LAYOUT (imhtml); 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); #if USE_PIXBUF || GTK_CHECK_VERSION(1,3,0) if (bit->img) { g_free(bit->img->filename); g_free(bit->img->data); gdk_pixbuf_unref(bit->img->pb); g_free(bit->img); } #endif while (bit->chunks) { struct line_info *li = bit->chunks->data; if (li->text) g_free (li->text); bit->chunks = g_list_remove (bit->chunks, li); g_free (li); } g_free (bit); } while (imhtml->click) { g_free (imhtml->click->data); imhtml->click = g_list_remove (imhtml->click, imhtml->click->data); } #if USE_PIXBUF || GTK_CHECK_VERSION(1,3,0) while (imhtml->im_images) { imhtml->im_images = g_list_remove(imhtml->im_images, imhtml->im_images->data); } #endif if (imhtml->selected_text) { g_string_free (imhtml->selected_text, TRUE); imhtml->selected_text = g_string_new (""); } imhtml->sel_startx = 0; imhtml->sel_starty = 0; imhtml->sel_endx = 0; imhtml->sel_endx = 0; imhtml->sel_endchunk = NULL; if (imhtml->tip_timer) { gtk_timeout_remove (imhtml->tip_timer); imhtml->tip_timer = 0; } if (imhtml->tip_window) { gtk_widget_destroy (imhtml->tip_window); imhtml->tip_window = NULL; } imhtml->tip_bit = NULL; if (imhtml->scroll_timer) { gtk_timeout_remove (imhtml->scroll_timer); imhtml->scroll_timer = 0; } #if USE_PIXBUF || GTK_CHECK_VERSION(1,3,0) g_list_free(imhtml->im_images); imhtml->im_images = NULL; #endif imhtml->x = 0; imhtml->y = TOP_BORDER; imhtml->xsize = 0; imhtml->llheight = 0; imhtml->llascent = 0; if (imhtml->line) g_list_free (imhtml->line); imhtml->line = NULL; layout->hadjustment->page_size = 0; layout->hadjustment->page_increment = 0; layout->hadjustment->lower = 0; layout->hadjustment->upper = imhtml->x; gtk_adjustment_set_value (layout->hadjustment, 0); layout->vadjustment->page_size = 0; layout->vadjustment->page_increment = 0; layout->vadjustment->lower = 0; layout->vadjustment->upper = imhtml->y; gtk_adjustment_set_value (layout->vadjustment, 0); if (GTK_WIDGET_REALIZED (GTK_WIDGET (imhtml))) { gdk_window_set_cursor (GTK_LAYOUT (imhtml)->bin_window, imhtml->arrow_cursor); gdk_window_clear (GTK_LAYOUT (imhtml)->bin_window); gtk_signal_emit_by_name (GTK_OBJECT (layout->hadjustment), "changed"); gtk_signal_emit_by_name (GTK_OBJECT (layout->vadjustment), "changed"); } } void gtk_imhtml_page_up (GtkIMHtml *imhtml) { GtkAdjustment *vadj; g_return_if_fail (imhtml != NULL); g_return_if_fail (GTK_IS_IMHTML (imhtml)); vadj = GTK_LAYOUT (imhtml)->vadjustment; gtk_adjustment_set_value (vadj, MAX (vadj->value - vadj->page_increment, vadj->lower)); gtk_signal_emit_by_name (GTK_OBJECT (vadj), "changed"); } void gtk_imhtml_page_down (GtkIMHtml *imhtml) { GtkAdjustment *vadj; g_return_if_fail (imhtml != NULL); g_return_if_fail (GTK_IS_IMHTML (imhtml)); vadj = GTK_LAYOUT (imhtml)->vadjustment; gtk_adjustment_set_value (vadj, MIN (vadj->value + vadj->page_increment, vadj->upper - vadj->page_size)); gtk_signal_emit_by_name (GTK_OBJECT (vadj), "changed"); }