gaim/gaim

Yo.
gtk1-stable v0_59_9
2003-03-01, Sean Egan
87952f35ac6d
Yo.
/*
* 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
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gdk-pixbuf/gdk-pixbuf-loader.h>
#else
#include "pixmaps/broken.xpm"
#endif
#define GTK_IMHTML_GET_STYLE_FONT(style) (style)->font
#define GTK_CLASS_TYPE(class) (class)->type
#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) (imhtml->use_pointsize ? x * 10 : \
_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->values)
break;
pos = strchr (t->values->str, *x);
if (pos)
t = t->children [(int) pos - (int) t->values->str];
else
break;
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);
}
void
gtk_imhtml_set_use_pointsize (GtkIMHtml *imhtml, gboolean point)
{
imhtml->use_pointsize = point;
}
struct im_image {
gchar *filename;
gint len;
gpointer data;
gint x,y;
gint width,height;
GtkIMHtml *imhtml;
GtkIMHtmlBit *bit;
#if USE_PIXBUF
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
gtk_imhtml_destroy (GtkObject *object)
{
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_OBJECT_CLASS (parent_class)->destroy != NULL)
(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}
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);
attributes.x = widget->style->klass->xthickness + BORDER_SIZE;
attributes.y = widget->style->klass->xthickness + BORDER_SIZE;
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 (&GTK_WIDGET (imhtml)->style->fg [GTK_STATE_NORMAL]);
imhtml->default_bg_color = gdk_color_copy (&GTK_WIDGET (imhtml)->style->base [GTK_STATE_NORMAL]);
imhtml->default_hl_color = gdk_color_copy (&GTK_WIDGET (imhtml)->style->bg [GTK_STATE_SELECTED]);
imhtml->default_hlfg_color=gdk_color_copy (&GTK_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)) {
gtk_paint_focus (widget->style, widget->window, NULL, widget, "text", 0, 0,
widget->allocation.width - 1, widget->allocation.height - 1);
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;
gint width, height;
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));
}
static void
gtk_imhtml_draw (GtkWidget *widget,
GdkRectangle *area)
{
GtkIMHtml *imhtml;
imhtml = GTK_IMHTML (widget);
gtk_imhtml_draw_exposed (imhtml);
}
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 (&GTK_WIDGET (imhtml)->style->fg [GTK_STATE_NORMAL]);
imhtml->default_bg_color = gdk_color_copy (&GTK_WIDGET (imhtml)->style->base [GTK_STATE_NORMAL]);
imhtml->default_hl_color = gdk_color_copy (&GTK_WIDGET (imhtml)->style->bg [GTK_STATE_SELECTED]);
imhtml->default_hlfg_color=gdk_color_copy (&GTK_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;
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);
}
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 ((uw->bit->url) && (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;
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)
{
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,
GTK_CLASS_TYPE (object_class),
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->draw = gtk_imhtml_draw;
widget_class->draw_focus = gtk_imhtml_draw_focus;
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 gchar*
gtk_imhtml_get_font_name (GdkFont *font)
{
GdkFontPrivate *fontpriv = (GdkFontPrivate *) font;
return fontpriv->names->data;
}
static GdkFont*
gtk_imhtml_font_load (GtkIMHtml *imhtml,
gchar *name,
gboolean bold,
gboolean italics,
gint fontsize)
{
GdkFont *default_font = imhtml->default_font;
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 {
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
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, "&amp;", 5)) {
*replace = '&';
*length = 5;
} else if (!g_strncasecmp (string, "&lt;", 4)) {
*replace = '<';
*length = 4;
} else if (!g_strncasecmp (string, "&gt;", 4)) {
*replace = '>';
*length = 4;
} else if (!g_strncasecmp (string, "&nbsp;", 6)) {
*replace = ' ';
*length = 6;
} else if (!g_strncasecmp (string, "&copy;", 6)) {
*replace = '�';
*length = 6;
} else if (!g_strncasecmp (string, "&quot;", 6)) {
*replace = '\"';
*length = 6;
} else if (!g_strncasecmp (string, "&reg;", 5)) {
*replace = '�';
*length = 5;
} else if (*(string + 1) == '#') {
guint pound = 0;
if ((sscanf (string, "&#%u;", &pound) == 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
GdkPixbufLoader *load;
GdkPixbuf *imagepb = NULL;
#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
load = gdk_pixbuf_loader_new();
if (!gdk_pixbuf_loader_write(load, imagedata, img->len))
g_print("IM Image corrupt or unreadable.\n");
else
imagepb = gdk_pixbuf_loader_get_pixbuf(load);
img->pb = imagepb;
#endif
}
#if USE_PIXBUF
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, &amp, &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 (!(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)));
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
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
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
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");
}