/* This module contains the gtk_page widget, which is the "back end" to
   Web text widgets including html. */

/* Now that page and html are split, we might not need all these includes. */

/* todo: fix wrapping so it doesn't break in the middle of a word when
   the attributes change in the middle of the word. */

/* todo: make clear and redraw procesing fancier, by only adding lines
   when they really get wrapped, and by including a highwater for expose
   events. */

#undef TRACE_GTK_METHODS
#undef NO_WINDOW
#define HUGE_WINDOW
#define GTK_PAGE_MAX_WINDOW_SIZE 32760

#include <ctype.h> /* for isspace */
#include <string.h> /* for memcpy and memmove */
#include <stdlib.h> /* for atoi */
#include <stdio.h> /* for sprintf */

#include <gtk/gtk.h>
#include "gtkpage.h"

#ifdef USE_TYPE1
#include "t1lib.h"
#endif

enum {
  LINK,
  STATUS,
  LAST_SIGNAL
};

typedef void (*GtkPageSignal1) (GtkObject *object,
				gpointer  arg1,
				gpointer  data);

static void gtk_page_marshal_signal_1 (GtkObject     *object,
				       GtkSignalFunc  func,
				       gpointer       func_data,
				       GtkArg        *args);

static void gtk_page_class_init   (GtkPageClass    *klass);
static void gtk_page_init         (GtkPage         *page);
static void gtk_page_destroy      (GtkObject       *object);
static gint gtk_page_button_press (GtkWidget       *widget,
				   GdkEventButton  *event);
static gint gtk_page_button_release (GtkWidget       *widget,
				     GdkEventButton  *event);
static gint gtk_page_motion_notify (GtkWidget       *widget,
				    GdkEventMotion  *event);
static gint gtk_page_expose       (GtkWidget       *widget,
				   GdkEventExpose *event);
static void gtk_page_map          (GtkWidget       *widget);
static void gtk_page_unmap        (GtkWidget       *widget);
static void gtk_page_realize      (GtkWidget       *widget);
static void gtk_page_draw         (GtkWidget       *widget,
				   GdkRectangle    *area);
static void gtk_page_size_request (GtkWidget       *widget,
				   GtkRequisition  *requisition);
static void gtk_page_size_allocate (GtkWidget      *widget,
				    GtkAllocation  *allocation);
static void gtk_page_foreach      (GtkContainer    *widget,
				   GtkCallback     callback,
				   gpointer        callback_data);
static void gtk_page_link (GtkPage *page,
			   char *url);
static void gtk_page_status (GtkPage *page,
			     char *string);

static GtkContainerClass *parent_class = NULL;
static gint page_signals[LAST_SIGNAL] = { 0 };

#ifdef USE_TYPE1
static gint t1_init;

typedef struct T1Font {
  gint fontnum;
  gint fontsize;
} T1Font;
#endif

guint
gtk_page_get_type ()
{
  static guint page_type = 0;

  if (!page_type)
    {
      GtkTypeInfo page_info =
      {
	"GtkPage",
	sizeof (GtkPage),
	sizeof (GtkPageClass),
	(GtkClassInitFunc) gtk_page_class_init,
	(GtkObjectInitFunc) gtk_page_init,
	(GtkArgFunc) NULL,
      };

      page_type = gtk_type_unique (gtk_container_get_type (), &page_info);
    }

  return page_type;
}

void
gtk_page_class_init (GtkPageClass *class)
{
  gint i;
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;
  GtkContainerClass *container_class;

  object_class = (GtkObjectClass*) class;
  widget_class = (GtkWidgetClass*) class;
  container_class = (GtkContainerClass*) class;

  parent_class = gtk_type_class (gtk_container_get_type ());

  i = 0;
  page_signals[i++] =
    gtk_signal_new ("link",
		    GTK_RUN_FIRST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GtkPageClass, link),
		    gtk_page_marshal_signal_1, GTK_ARG_NONE, 1,
		    GTK_ARG_POINTER);
  page_signals[i++] =
    gtk_signal_new ("status",
		    GTK_RUN_FIRST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GtkPageClass, status),
		    gtk_page_marshal_signal_1, GTK_ARG_NONE, 1,
		    GTK_ARG_POINTER);

  gtk_object_class_add_signals (object_class, page_signals, LAST_SIGNAL);

  object_class->destroy = gtk_page_destroy;

  class->link = NULL;

  widget_class->button_press_event = gtk_page_button_press;
  widget_class->button_release_event = gtk_page_button_release;
  widget_class->motion_notify_event = gtk_page_motion_notify;
  widget_class->expose_event = gtk_page_expose;
  widget_class->map = gtk_page_map;
  widget_class->unmap = gtk_page_unmap;
  widget_class->realize = gtk_page_realize;
  widget_class->draw = gtk_page_draw;
  widget_class->size_request = gtk_page_size_request;
  widget_class->size_allocate = gtk_page_size_allocate;

  container_class->foreach = gtk_page_foreach;
}


/* Find the color in the page structure, or add a new one if it doesn't
   already exist. In either case, return the index. */

gint gtk_page_find_color (GtkPage *page, gint32 color) {
  gint color_index;
  gint red, green, blue;

  red = (color >> 16) & 255;
  red |= red << 8;
  green = (color >> 8) & 255;
  green |= green << 8;
  blue = color & 255;
  blue |= blue << 8;
  for (color_index = 0; color_index < page->num_colors; color_index++) {
    if (page->colors[color_index].red == red &&
	page->colors[color_index].green == green &&
	page->colors[color_index].blue == blue) {
      return color_index;
    }
  }
  if (page->num_colors == page->num_colors_max ) {
    page->num_colors_max <<= 1;
    page->colors = g_realloc (page->colors,
			      page->num_colors_max * sizeof (GdkColor));
  }
  page->colors[color_index].pixel = -1;
  page->colors[color_index].red = red;
  page->colors[color_index].green = green;
  page->colors[color_index].blue = blue;
  page->num_colors++;
  return color_index;
}

/* Set all attribute fields except font to reasonable defaults. */

void gtk_page_init_attr (GtkPage *page, GtkPageAttr *attr) {
  attr->link = -1;
  attr->color = gtk_page_find_color (page, 0);
  attr->left_indent_first = 0;
  attr->left_indent_rest = 0;
  attr->right_indent = 0;
  attr->align = GTK_PAGE_ALIGN_LEFT;
}

/* Find the font in the page structure, or add a new one if it doesn't
   already exist. In either case, return the index. */

gint gtk_page_find_font (GtkPage *page, const GtkPageFont *font) {
  gint font_index;

  for (font_index = 0; font_index < page->num_fonts; font_index++) {
    if (page->fonts[font_index].size == font->size &&
	page->fonts[font_index].bold == font->bold &&
	page->fonts[font_index].italic == font->italic &&
	!strcmp (page->fonts[font_index].name, font->name)) {
      return font_index;
    }
  }
  if (page->num_fonts == page->num_fonts_max ) {
    page->num_fonts_max <<= 1;
    page->fonts = g_realloc (page->fonts,
			     page->num_fonts_max * sizeof (GtkPageFont));
  }
  page->fonts[font_index].name = g_strdup (font->name);
  page->fonts[font_index].size = font->size;
  page->fonts[font_index].bold = font->bold;
  page->fonts[font_index].italic = font->italic;
#ifdef USE_TYPE1
  page->fonts[font_index].t1fontid = -1;
#else
  page->fonts[font_index].font = NULL;
#endif
  page->fonts[font_index].space_width = 0;
  page->num_fonts++;
  return font_index;
}

/* Create a new link, return the index. */
gint gtk_page_new_link (GtkPage *page, const char *url) {
  if (page->num_links == page->num_links_max ) {
    page->num_links_max <<= 1;
    page->links = g_realloc (page->links,
			     page->num_links_max * sizeof (GtkPageLink));
  }
  page->links[page->num_links].url = g_strdup (url);
  return page->num_links++;
}

/* Find the attr in the page structure, or add a new one if it doesn't
   already exist. In either case, return the index. */

gint gtk_page_find_attr (GtkPage *page, const GtkPageAttr *attr) {
  gint attr_index;

  for (attr_index = 0; attr_index < page->num_attrs; attr_index++) {
    if (page->attrs[attr_index].font == attr->font &&
	page->attrs[attr_index].link == attr->link &&
	page->attrs[attr_index].color == attr->color &&
	page->attrs[attr_index].left_indent_first == attr->left_indent_first &&
	page->attrs[attr_index].left_indent_rest == attr->left_indent_rest &&
	page->attrs[attr_index].right_indent == attr->right_indent &&
	page->attrs[attr_index].align == attr->align) {
      return attr_index;
    }
  }
  if (page->num_attrs == page->num_attrs_max ) {
    page->num_attrs_max <<= 1;
    page->attrs = g_realloc (page->attrs,
			     page->num_attrs_max * sizeof (GtkPageAttr));
  }
  page->attrs[attr_index].font = attr->font;
  page->attrs[attr_index].link = attr->link;
  page->attrs[attr_index].color = attr->color;
  page->attrs[attr_index].left_indent_first = attr->left_indent_first;
  page->attrs[attr_index].left_indent_rest = attr->left_indent_rest;
  page->attrs[attr_index].right_indent = attr->right_indent;
  page->attrs[attr_index].align = attr->align;
  page->num_attrs++;
  return attr_index;
}

void
gtk_page_init (GtkPage *page)
{
#ifdef NO_WINDOW
  GTK_WIDGET_SET_FLAGS (page, GTK_NO_WINDOW);
#endif

  GTK_WIDGET_SET_FLAGS (page, GTK_CAN_FOCUS);

  page->gc = NULL;

  page->num_lines_max = 16;
  page->num_lines = 0;
  page->lines = g_new (GtkPageLine, page->num_lines_max);

  page->num_fonts_max = 16;
  page->num_fonts = 0;
  page->fonts = g_new (GtkPageFont, page->num_fonts_max);

  page->num_links_max = 16;
  page->num_links = 0;
  page->links = g_new (GtkPageLink, page->num_links_max);

  page->num_colors_max = 16;
  page->num_colors = 0;
  page->colors = g_new (GdkColor, page->num_colors_max);

  page->num_attrs_max = 16;
  page->num_attrs = 0;
  page->attrs = g_new (GtkPageAttr, page->num_attrs_max);

  page->width = 450; /* a HACK! */

  page->hover_link = -1;
}

/* Load the gdk font for the font data structure if necessary. In either
   case, return the gdk font. (NULL if using t1lib fonts) */

#ifdef USE_TYPE1
static GdkFont *gtk_page_realize_font (GtkPageFont *font) {
  gint base;
#if 0
  T1Font *t1font;
#endif

  if (font->t1fontid != -1)
    return NULL;

  if (!t1_init) {
    T1_InitLib (0);
    T1_SetDeviceResolutions (72.0, 72.0);
    T1_AASetBitsPerPixel (16); /* hack! */
    T1_AASetGrayValues (0xd6ba,
			0xa514,
			0x6b4d,
			0x31a6,
			0x0000); /* hack! */
    t1_init = TRUE;
  }

  base = 0;
  if (!strcmp (font->name, "times")) { /* hack! */
    base = 46;
    if (font->bold) base += 1;
    if (font->italic) base += 2;
  } else if (!strcmp (font->name, "courier")) { /* hack! */
    base = 50;
    if (font->bold) base += 1;
    if (font->italic) base += 2;
  } else if (!strcmp (font->name, "palatino")) { /* hack! */
    base = 54;
    if (font->bold) base += 1;
    if (font->italic) base += 2;
  } else if (!strcmp (font->name, "helvetica")) { /* hack! */
    base = 38;
    if (font->bold) base += 1;
    if (font->italic) base += 2;
  }
#if 0
  t1font = (T1Font *)g_malloc (sizeof (T1Font));
  t1font->fontnum = base;
  t1font->fontsize = font->size;
#endif
  font->t1fontid = base;
  T1_LoadFont (base);
  font->space_width = (T1_GetCharWidth (base, ' ') * font->size) / 1000;
  return NULL;
}
#else
static GdkFont *gtk_page_realize_font (GtkPageFont *font) {
  char fontname[256];
  if (font->font != NULL)
    return font->font;

  sprintf (fontname, "-*-%s-%s-%s-*-*-%d-*-75-75-*-*-*-*",
	   font->name,
	   font->bold ? "bold" : "medium",
	   font->italic ? "i" : "r",
	   font->size);
  font->font = gdk_font_load (fontname);

  if (font->font == NULL && font->italic) {
    /* Some italic fonts (e.g. Courier) use "o" instead of "i". */
    sprintf (fontname, "-*-%s-%s-o-*-*-%d-*-75-75-*-*-*-*",
	     font->name,
	     font->bold ? "bold" : "medium",
	     font->size);
    font->font = gdk_font_load (fontname);
  }

  if (font->font == NULL) {
    /* Can't load the font - substitute the default instead. */
    font->font = gdk_font_load ("-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*");
  }

  if (font->font == NULL) {
    g_print ("Can't load any fonts!\n");
    /* Should probably abort here. */
  }

  font->space_width = gdk_char_width (font->font, ' ');
  return font->font;
}
#endif

#ifdef USE_TYPE1
gint type1_string_width (gint fontid, gint size, const char *text) {
  METRICSINFO metrics;

  metrics = T1_GetMetricsInfo (fontid, (char *)text, 0, 0, TRUE);
  return (metrics.width * size) / 1000;
}

void type1_draw_string (GdkDrawable *drawable,
			GdkGC *gc,
			gint fontid,
			gint fontsize,
			gint x_cursor,
			gint y_cursor,
			const char *text) {
  GLYPH *glyph;
  gint w, h;
  GdkImage *gdkimage;
  gint y;

  glyph = T1_AASetString (fontid, (char *)text, 0, 0, TRUE, fontsize, 0);
  if (glyph == NULL) return;
  w = glyph->metrics.rightSideBearing - glyph->metrics.leftSideBearing;
  h = glyph->metrics.ascent + glyph->metrics.descent;
  gdkimage = gdk_image_new (GDK_IMAGE_FASTEST,
			    gdk_window_get_visual (drawable),
			    w,
			    h);
  for (y = 0; y < h; y++) {
    memcpy (gdkimage->mem + gdkimage->bpl * y,
	    glyph->bits + w * y * 2,
	    w * 2);
  }
  gdk_draw_image (drawable,
		  gc,
		  gdkimage,
		  0,
		  0,
		  x_cursor,
		  y_cursor - glyph->metrics.ascent,
		  w,
		  h);
  gdk_image_destroy (gdkimage);
}
#endif

GtkWidget*
gtk_page_new (void)
{
  GtkPage *page;

  page = gtk_type_new (gtk_page_get_type ());

  return GTK_WIDGET (page);
}

static void gtk_page_add_line (GtkPage *page) {
  gint num_lines;
  GtkPageLine *line;

  num_lines = page->num_lines;
  if (num_lines == page->num_lines_max) {
    page->num_lines_max <<= 1;
    page->lines = g_realloc (page->lines,
			     page->num_lines_max * sizeof (GtkPageLine));
  }
  line = &(page->lines[num_lines]);
  line->num_words = 0;
  line->num_words_max = 16;
  line->words = g_new (GtkPageWord, line->num_words_max);
  if (num_lines == 0) {
    line->y_top = 0;
  } else {
    line->y_top =
      page->lines[num_lines - 1].y_top + page->lines[num_lines - 1].y_ascent +
      page->lines[num_lines - 1].y_descent +
      page->lines[num_lines - 1].y_space;
  }
  line->x_size = 0;
  line->y_ascent = 6;
  line->y_descent = 3;
  line->y_space = 0;
  line->hard = FALSE;
  line->first = (num_lines == 0 || (page->lines[num_lines - 1].hard &&
				    page->lines[num_lines - 1].y_space > 0));
  page->num_lines++;
}

/* Allocate a new word in a page structure. This routine is where word
   wrapping happens. The newly allocated word has the x_size, x_space,
   y_ascent, and y_descent fields filled in, but no others.

*/

static GtkPageWord *gtk_page_new_word (GtkPage *page,
				       gint x_size,
				       gint y_ascent,
				       gint y_descent,
				       gint attr) {
  GtkPageLine *line;
  GtkPageWord *word;
  gboolean new_line;
  gint width;
  gint set_width;
  gint j;
  gint num_lines;

  new_line = FALSE;
  num_lines = page->num_lines;
  if (num_lines == 0) {
    new_line = TRUE;
  } else {
    line = &(page->lines[num_lines - 1]);
    if (line->hard) {
      new_line = TRUE;
    } else {
      /* Calclulate width of new line and see if it exceeds page width. */
      j = line->num_words;
      if (j > 0) {
	set_width = page->width - page->attrs[attr].right_indent;
	if (line->first)
	  set_width -= page->attrs[attr].left_indent_first;
	else
	  set_width -= page->attrs[attr].left_indent_rest;
	if (set_width < 100) set_width = 100;
	width = line->x_size + line->words[j - 1].x_space + x_size;
	if (width > set_width)
	  new_line = TRUE;
      }
    }
  }

  if (new_line) {
    gtk_page_add_line (page);
    line = &(page->lines[num_lines]);
  }

  if (line->num_words == line->num_words_max) {
    line->num_words_max <<= 1;
    line->words = g_realloc (line->words,
			     line->num_words_max * sizeof (GtkPageWord));
  }
  j = line->num_words;
  line->num_words++;
  word = &(line->words[j]);
  word->x_size = x_size;
  word->x_space = 0;
  word->y_ascent = y_ascent;
  word->y_descent = y_descent;
  if (j)
    line->x_size += line->words[j - 1].x_space;
  line->x_size += x_size;
  line->y_ascent = MAX (line->y_ascent, y_ascent);
  line->y_descent = MAX (line->y_descent, y_descent);

  return word;
}

/* Add a word to the page structure. Stashes the argument pointer in
   the page data structure so that it will be deallocated on destroy.
   */

void gtk_page_add_text (GtkPage *page, char *text, gint attr) {
  GtkPageWord *word;
  gint x_size, y_ascent, y_descent;
  GtkPageFont *font;
  GdkFont *gdkfont;

  font = &(page->fonts[page->attrs[attr].font]);
  gdkfont = gtk_page_realize_font (font);

#ifdef USE_TYPE1
  x_size = type1_string_width (font->t1fontid, font->size, text);
  y_descent = font->size >> 2; /* a bit of a hack */
  y_ascent = font->size - y_descent;
#else
  x_size = gdk_string_width (gdkfont, text);
  y_ascent = gdkfont->ascent;
  y_descent = gdkfont->descent;
#endif

  word = gtk_page_new_word (page, x_size, y_ascent, y_descent, attr);
  word->content_type = GTK_PAGE_CONTENT_TEXT;
  word->content.text = text;
  word->attr = attr;
}

/* Add a widget to the page structure. */

void gtk_page_add_widget (GtkPage *page,
			  GtkWidget *widget, gint attr) {
  GtkPageWord *word;
  gint x_size, y_ascent, y_descent;

  gtk_widget_set_parent (widget, GTK_WIDGET (page));

  /* todo: only do this if widget is visible */
  gtk_widget_size_request (widget, &widget->requisition);

  x_size = widget->requisition.width;
  y_descent = widget->requisition.height >> 2;
  y_ascent = widget->requisition.height - y_descent;

  word = gtk_page_new_word (page, x_size, y_ascent, y_descent, attr);
  word->content_type = GTK_PAGE_CONTENT_WIDGET;
  word->content.widget = widget;
  word->attr = attr;

#if 0
  /* The problem here is that we really can't allocate the widget until
     the line is finished. todo: create a finish_line function that
     performs alignment, allocation of child widgets, etc. */

  /* this code adapted from size_allocate */

  border_width = GTK_CONTAINER (page)->border_width;

  line = &(page->lines[page->num_lines - 1]);
  x_cursor = border_width;
  if (line->first)
    x_cursor += page->attrs[line->words[0].attr].left_indent_first;
  else
    x_cursor += page->attrs[line->words[0].attr].left_indent_rest;
  for (word_index = 0; word_index < line->num_words; word_index++) {
    word = &(line->words[word_index]);
    if (word_index == line->num_words - 1 &&
	word->content_type == GTK_PAGE_CONTENT_WIDGET &&
	GTK_WIDGET_VISIBLE (word->content.widget)) {
      child_allocation.x = x_cursor;
      child_allocation.y = border_width +
	line->y_top + line->y_ascent - word->y_ascent;
      child_allocation.width = word->x_size;
      child_allocation.height = word->y_ascent + word->y_descent;
      gtk_widget_size_allocate (word->content.widget, &child_allocation);
    }
    x_cursor += word->x_size + word->x_space;
  }

  /* this code adapted from gtk_real_container_need_resize */
  if (GTK_WIDGET_VISIBLE (widget)) {
    if (GTK_WIDGET_REALIZED (page) &&
	!GTK_WIDGET_REALIZED (widget))
      gtk_widget_realize (widget);
    if (GTK_WIDGET_MAPPED (page) &&
	!GTK_WIDGET_MAPPED (widget))
      gtk_widget_map (widget);
  }
#endif

#if 1
  /* This code is only exercised if the widget is shown _before_ the
     call to gtk_page_add_widget. */
  if (GTK_WIDGET_VISIBLE (widget) && GTK_WIDGET_VISIBLE (page)) {
#ifdef TRACE_GTK_METHODS
    g_print ("gtk_page_add_widget: check_resize\n");
#endif
    gtk_container_check_resize (GTK_CONTAINER (page), widget);
  }
#endif
}

void gtk_page_add_space (GtkPage *page, gint attr) {
  gint i, j;
  gint space;
  GtkPageFont *font;
  GdkFont *gdkfont;

  font = &(page->fonts[page->attrs[attr].font]);
  gdkfont = gtk_page_realize_font (font);

  i = page->num_lines - 1;
  if (i >= 0) {
    j = page->lines[i].num_words - 1;
    if (j >= 0) {
      space = font->space_width;
      page->lines[i].words[j].x_space = space;
    }
  }
}

/* Cause a line break */

void gtk_page_linebreak (GtkPage *page) {
  int i;

  i = page->num_lines;
  if (i == 0) return;
  page->lines[i - 1].hard = TRUE;
}

/* Cause a paragraph break */
void gtk_page_parbreak (GtkPage *page, gint space) {
  int i;

  i = page->num_lines;
  if (i == 0) return;
  page->lines[i - 1].hard = TRUE;
  if (space > page->lines[i - 1].y_space)
    page->lines[i - 1].y_space = space;
}

/* Compute smallest rectangle that includes both source rectangles. */

static void gtk_page_rectangle_union (const GdkRectangle *src1,
				      const GdkRectangle *src2,
				      GdkRectangle *dest) {
  if (src1->width == 0 || src1->height == 0) {
    *dest = *src2;
  } else if (src2->width == 0 || src2->height == 0) {
    *dest = *src1;
  } else {
    dest->x = MIN (src1->x, src2->x);
    dest->y = MIN (src1->y, src2->y);
    dest->width = MAX (src1->x + src1->width, src2->x + src2->width) -
      dest->x;
    dest->height = MAX (src1->y + src1->height, src2->y + src2->height) -
      dest->y;
  }
}

/* Add a rectangle to the clear area. */

static void gtk_page_add_clear (GtkPage *page, GdkRectangle *rect) {
  gtk_page_rectangle_union (rect, &(page->clear), &(page->clear));
}

/* Add a rectangle to the redraw area. */

static void gtk_page_add_redraw (GtkPage *page, GdkRectangle *rect) {
  gtk_page_rectangle_union (rect, &(page->redraw), &(page->redraw));
}

/* Call this routine before updating any of the internal state. */

void gtk_page_update_begin (GtkPage *page) {
  /* should probably put gtk_container_disable_resize call here. */
  page->clear.width = 0;
  page->redraw.width = 0;
  page->redraw_start_line = page->num_lines;
  if (page->num_lines > 0 && !page->lines[page->num_lines - 1].hard)
    page->redraw_start_line--;
}

/* Call this routine after updating any of the internal state. Among other
 things, it causes the changes to the page to be drawn. */

void gtk_page_do_redraws (GtkPage *page, gboolean do_resize) {
  GdkEventExpose event;

  if (GTK_WIDGET_VISIBLE (page)) {
    if (GTK_WIDGET_MAPPED (page)) {
      if (page->clear.width > 0 && page->clear.height > 0) {
#ifdef VERBOSE
	g_print ("gtk_page_do_redraws: clear (%d, %d) (%d x %d)\n",
			       page->clear.x,
			       page->clear.y,
			       page->clear.width,
			       page->clear.height);
#endif

	gdk_window_clear_area (GTK_WIDGET (page)->window,
			       page->clear.x,
			       page->clear.y,
			       page->clear.width,
			       page->clear.height);
      }
      page->clear.width = 0;

      if (page->redraw.width > 0 && page->redraw.height > 0) {
#ifdef VERBOSE
	g_print ("gtk_page_do_redraws: redraw (%d, %d) (%d x %d)\n",
			       page->redraw.x,
			       page->redraw.y,
			       page->redraw.width,
			       page->redraw.height);
#endif
	event.type = GDK_EXPOSE;
	event.window = GTK_WIDGET (page)->window;
	event.area = page->redraw;
	event.count = 0;
	gtk_page_expose (GTK_WIDGET (page), &event);
      }
      page->redraw.width = 0;

    }
    if (do_resize)
      gtk_container_check_resize (GTK_CONTAINER (GTK_WIDGET (page)->parent),
				  GTK_WIDGET (page));
  }
}

void gtk_page_update_end (GtkPage *page) {
  gint line_index;
  GdkRectangle rect;
  GtkPageLine *line;
  gint x0, y0;

#ifndef NO_WINDOW
  x0 = page->container.border_width;
  y0 = page->container.border_width;
#else
  x0 = GTK_WIDGET (page)->allocation.x + page->container.border_width;
  y0 = GTK_WIDGET (page)->allocation.y + page->container.border_width;
#endif

  /* add [page->redraw_start_line..page->num_lines) to redraw rect */
  for (line_index = page->redraw_start_line; line_index < page->num_lines;
       line_index++) {
    line = &(page->lines[line_index]);
    rect.x = x0;
    rect.y = y0 + line->y_top;
    rect.width = line->x_size;
    rect.height = line->y_ascent + line->y_descent;
    gtk_page_add_redraw (page, &rect);
  }
  gtk_page_do_redraws (page, TRUE);
}

static void
gtk_page_destroy (GtkObject *object)
{
  gint line_index, word_index, font_index;
  GtkPage *page;
  GtkPageLine *line;
  GtkPageWord *word;

  g_return_if_fail (object != NULL);
  g_return_if_fail (GTK_IS_PAGE (object));

  page = GTK_PAGE (object);

  if (GTK_WIDGET (object)->parent &&
      GTK_WIDGET_MAPPED (object))
    gtk_widget_unmap (GTK_WIDGET (object));

  for (line_index = 0; line_index < page->num_lines; line_index++) {
    line = &(page->lines[line_index]);
    for (word_index = 0; word_index < line->num_words; word_index++) {
      word = &(line->words[word_index]);
      switch (word->content_type) {
      case GTK_PAGE_CONTENT_TEXT:
	g_free (word->content.text);
	break;
      case GTK_PAGE_CONTENT_WIDGET:
	gtk_widget_destroy (word->content.widget);
	break;
      }
    }
    g_free (line->words);
  }
  g_free (page->lines);

#ifndef USE_TYPE1
  for (font_index = 0; font_index < page->num_fonts; font_index++) {
    if (page->fonts[font_index].font != NULL)
      gdk_font_free (page->fonts[font_index].font);
  }
#endif
  g_free (page->fonts);
  g_free (page->attrs);

  if (GTK_OBJECT_CLASS (parent_class)->destroy)
    (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}

/* Rewrap the page. There are basically two times we'll want to do this:
   either when the viewport is resized, or when the size changes on one
   of the child widgets. */

/* Performance is not as good as it could be - it could only rewrap if
   it needs to (should be a cheap test: the line is ok if it fits
   within the page->width and either it's hard or the width plus the
   width of the first word on the next line (plus the spacing of the
   last word) exceed the page->width). This would save on memory
   allocation too.

   But hey, it works!

*/

static void gtk_page_rewrap (GtkPage *page) {
  GtkPageLine *old_lines;
  gint old_num_lines;
  GdkRectangle rect;

  gint line_index, word_index;
  GtkPageLine *old_line, *line;
  GtkPageWord *old_word, *word;
  gint width, line_width;

  old_lines = page->lines;
  old_num_lines = page->num_lines;

  /* Initialize the lines structure of the page*/
  page->num_lines = 0;
  page->num_lines_max = 16;
  page->lines = g_new (GtkPageLine, page->num_lines_max);

  /* Iterate over the old lines, adding the words to the page structure.
   */
  for (line_index = 0; line_index < old_num_lines; line_index++) {
    old_line = &(old_lines[line_index]);
    /* Here, we're actually relying on the invariant that a soft line
       is never followed by an empty hard line. */
    if (old_line->num_words == 0 && old_line->hard) {
      gtk_page_add_line (page);
    }
    for (word_index = 0; word_index < old_line->num_words; word_index++) {
      old_word = &(old_line->words[word_index]);
      word = gtk_page_new_word (page,
				old_word->x_size,
				old_word->y_ascent, old_word->y_descent,
				old_word->attr);
      word->x_space      =  old_word->x_space;
      word->content_type =  old_word->content_type;
      word->content      =  old_word->content;
      word->attr         =  old_word->attr;
    }

    if (old_line->hard) {
      gtk_page_linebreak (page);
      page->lines[page->num_lines - 1].y_space = old_line->y_space;
    }

    g_free (old_line->words);
  }
  g_free (old_lines);

  /* to: make the rectangles more minimal in the case where not all lines
     need rewrapping. */
  rect.x = page->container.border_width;
  rect.y = page->container.border_width;
  width = 0;
  for (line_index = 0; line_index < page->num_lines; line_index++) {
    line = &(page->lines[line_index]);
    line_width = line->x_size;
    if (line->first)
      line_width += page->attrs[line->words[0].attr].left_indent_first;
    else
      line_width += page->attrs[line->words[0].attr].left_indent_rest;
    width = MAX (width, line_width);
  }
  rect.width = width;
  if (page->num_lines == 0) {
    rect.height = 0;
  } else {
    rect.height = page->lines[page->num_lines - 1].y_top +
      page->lines[page->num_lines - 1].y_ascent +
      page->lines[page->num_lines - 1].y_descent;
  }

  gtk_page_add_clear (page, &rect);
  gtk_page_add_redraw (page, &rect);
}

/* Set the width. Cause a rewrap and check_resize if necessary. */

void gtk_page_set_width (GtkPage *page, gint width) {
  gint new_width;

  new_width = width - 2 * GTK_CONTAINER (page)->border_width;
  if (new_width != page->width) {
    page->width = new_width;
    gtk_page_rewrap (page);

    gtk_page_do_redraws (page, TRUE);
  }
}

static void
gtk_page_size_request (GtkWidget      *widget,
		       GtkRequisition *requisition)
{
  GtkPage *page;
  gint line_index, word_index;
  GtkPageLine *line;
  GtkPageWord *word;
  gint width, height;
  GtkRequisition *child_requisition;
  gboolean rewrap;
  gboolean any_soft;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_PAGE (widget));
  g_return_if_fail (requisition != NULL);

  page = GTK_PAGE (widget);

#ifdef TRACE_GTK_METHODS
  g_print ("gtk_page_size_request\n");
#endif

  rewrap = FALSE;
  any_soft = FALSE;
  width = 0;
  for (line_index = 0; line_index < page->num_lines; line_index++) {
    line = &(page->lines[line_index]);
    for (word_index = 0; word_index < line->num_words; word_index++) {
      word = &(line->words[word_index]);
      if (word->content_type == GTK_PAGE_CONTENT_WIDGET) {
	if (GTK_WIDGET_VISIBLE (word->content.widget)) {
	  child_requisition = &word->content.widget->requisition;
	  gtk_widget_size_request (word->content.widget, child_requisition);
	  if (child_requisition->width != word->x_size ||
	      child_requisition->height != word->y_ascent + word->y_descent) {
	    word->x_size = child_requisition->width;
	    word->y_descent = child_requisition->height >> 2;
	    word->y_ascent = child_requisition->height - word->y_descent;
	    rewrap = TRUE;
	  }
	}
      }
    }
    if (!line->hard) any_soft = TRUE;
    /* todo: add left indent to width */
    width = MAX (width, line->x_size);
    }

  if (rewrap) {
    gtk_page_rewrap (page);
    any_soft = FALSE;
    width = 0;
    for (line_index = 0; line_index < page->num_lines; line_index++) {
      line = &(page->lines[line_index]);
      /* todo: add left indent to width */
      width = MAX (width, line->x_size);
      if (!line->hard) any_soft = TRUE;
    }
  }

  if (page->num_lines == 0) {
    height = 0;
  } else {
    height = page->lines[page->num_lines - 1].y_top +
      page->lines[page->num_lines - 1].y_ascent +
      page->lines[page->num_lines - 1].y_descent;
  }

  if (any_soft && page->width > width)
    width = page->width;

  requisition->width = width + page->container.border_width * 2;
  requisition->height = height + page->container.border_width * 2;
#ifdef TRACE_GTK_METHODS
  g_print (" requisition = (%d, %d)\n", requisition->width, requisition->height);
#endif
}


/* Draw a line, starting at (x, y), where y is the top of the line. */

static void gtk_page_expose_line (GtkWidget *widget,
				GdkEventExpose *event,
				GtkPageLine *line,
				gint x,
				gint y) {
  GtkPage *page;
  GtkPageWord *word;
  gint word_index;
  gint x_cursor, y_cursor;
  gint last_font = -1;
  gint font;
  gint32 last_color = -1;
  gint32 color;
  GdkGC *gc;
  GdkEventExpose child_event;
  GdkColor *gdk_color;
  GdkColormap *colormap;
  gint uline_width;

  /* Here's an idea on how to optimize this routine to minimize the number
     of calls to gdk_draw_string:

     Copy the text from the words into a buffer, adding a new word
     only if: the attributes match, and the spacing is either zero or
     equal to the width of ' '. In the latter case, copy a " " into
     the buffer. Then draw the buffer. */

  /* A gtk question: should we be creating our own gc? */

  page = GTK_PAGE (widget);
  colormap = gtk_widget_get_colormap (widget);
  x_cursor = x;
  y_cursor = y + line->y_ascent;
  gc = page->gc;
  child_event = *event;
  for (word_index = 0; word_index < line->num_words; word_index++) {
    word = &(line->words[word_index]);
    switch (word->content_type) {
    case GTK_PAGE_CONTENT_TEXT:
      font = page->attrs[word->attr].font;
#ifndef USE_TYPE1
      if (font != last_font) {
	gdk_gc_set_font (gc, page->fonts[font].font);
	last_font = font;
      }
#endif
      color = page->attrs[word->attr].color;
      if (color != last_color) {
	gdk_color = &(page->colors[color]);
	if (gdk_color->pixel == -1) {
	  gdk_color_alloc (colormap, gdk_color);
	}
	gdk_gc_set_foreground (gc, gdk_color);
	last_color = color;
      }
#ifdef USE_TYPE1
      type1_draw_string (widget->window,
			 gc,
			 page->fonts[font].t1fontid,
			 page->fonts[font].size,
			 x_cursor,
			 y_cursor,
			 word->content.text);
#else
      gdk_draw_string (widget->window,
		       gc,
		       x_cursor,
		       y_cursor,
		       word->content.text);
#endif
      if (page->attrs[word->attr].link >= 0) {
	uline_width = word->x_size;
	if (word_index + 1 < line->num_words &&
	    page->attrs[word->attr].link ==
	    page->attrs[line->words[word_index + 1].attr].link)
	  uline_width += word->x_space;
	gdk_draw_line (widget->window,
		       gc,
		       x_cursor,
		       y_cursor + 1,
		       x_cursor + uline_width - 1,
		       y_cursor + 1);
      }
      break;
    case GTK_PAGE_CONTENT_WIDGET:
      if (GTK_WIDGET_NO_WINDOW (word->content.widget) &&
	  gtk_widget_intersect (word->content.widget,
				&event->area,
				&child_event.area))
	gtk_widget_event (word->content.widget,
			  (GdkEvent *) &child_event);
      break;
    }
    x_cursor += line->words[word_index].x_size +
      line->words[word_index].x_space;
  }
}


/* Find the first line index that includes y. */

static gint gtk_page_find_line_index (GtkPage *page, gint y) {
  gint line_index;

  /* This could be a faster algorithm, for example a binary search. */

  for (line_index = 0; line_index < page->num_lines; line_index++) {
    if (page->lines[line_index].y_top +
	page->lines[line_index].y_ascent +
	page->lines[line_index].y_descent > y)
      break;
  }

  return line_index;
}

/* Draw the actual lines, starting at (x, y). */

static void gtk_page_expose_lines (GtkWidget *widget,
				   GdkEventExpose *event,
				   gint x,
				   gint y) {
  GtkPage *page;
  gint line_index;
  GtkPageLine *line;
  gint x1;

  page = GTK_PAGE (widget);

  line_index = gtk_page_find_line_index (page, event->area.y - y);
  for (; line_index < page->num_lines; line_index++) {
    if (y +
	page->lines[line_index].y_top >= event->area.y + event->area.height)
      break;
#ifdef SHOW_SCROLL
    g_print (".");
#endif
    line = &(page->lines[line_index]);
    /* Maybe should move this into expose_line */
    if (line->first)
      x1 = x + page->attrs[line->words[0].attr].left_indent_first;
    else
      x1 = x + page->attrs[line->words[0].attr].left_indent_rest;
    gtk_page_expose_line (widget, event,
			  &(page->lines[line_index]),
			  x1,
			  y + page->lines[line_index].y_top);
  }
#ifdef SHOW_SCROLL
  g_print ("\n");
#endif
}

static gint gtk_page_find_link (GtkPage *page, gint x, gint y) {
  gint line_index;
  gint word_index;
  gint x_cursor;
  gint x1, y1; /* have border subtracted off */
  GtkPageLine *line;
  GtkPageWord *word;

  x1 = x - page->container.border_width;
  y1 = y - page->container.border_width;

  line_index = gtk_page_find_line_index (page, y1);
  if (line_index >= page->num_lines) return -1;
  line = &(page->lines[line_index]);
  if (line->first)
    x_cursor = page->attrs[line->words[0].attr].left_indent_first;
  else
    x_cursor = page->attrs[line->words[0].attr].left_indent_rest;
  for (word_index = 0; word_index < line->num_words; word_index++) {
    word = &(line->words[word_index]);
    if (x_cursor <= x1 && x_cursor + word->x_size + word->x_space > x1)
      return page->attrs[word->attr].link;
    x_cursor += word->x_size + word->x_space;
  }
  return -1;
}

static gint
gtk_page_button_press (GtkWidget *widget,
		       GdkEventButton *event) {
  GtkPage *page;

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_PAGE (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  /* perhaps this test should check whether widget !=
     gtk_get_event_widget (event). But I think this is about the same. */
  if (event->window != widget->window)
    return FALSE;

  if (event->type == GDK_BUTTON_PRESS)
    {
      page = GTK_PAGE (widget);

#if 0
      /* it's just not clear to me yet how to handle issues of focus
	 and default */
      if (GTK_WIDGET_CAN_DEFAULT (widget) && (event->button == 1))
	gtk_widget_grab_default (widget);
      if (!GTK_WIDGET_HAS_FOCUS (widget))
	gtk_widget_grab_focus (widget);
#endif

#if 1
      /* todo: be smarter about when to add the grab. We get the button
	 press events sent to child widgets - perhaps we should skip
	 the grab if hit testing shows the event went to a child widget
	 without NO_WINDOW set. But this seems like a hack. */
      if (event->button == 1)
	{
	  gtk_grab_add (GTK_WIDGET (page));
	}
#endif

#ifdef TRACE_GTK_METHODS
      g_print ("gtk_page_button_press: (%d, %d) %d\n",
	       event->x, event->y, event->button);
#endif

      if (event->button == 1) {
	page->link_pressed = gtk_page_find_link (page, event->x, event->y);
      }
    }

  return TRUE;
}

static void gtk_page_link (GtkPage *page,
			   char *url) {
  gtk_signal_emit (GTK_OBJECT (page), page_signals[LINK],
		   url);
}

static void gtk_page_status (GtkPage *page,
			     char *string) {
#ifdef VERBOSE
  g_print ("gtk_page_status: %s\n", string);
#endif
  gtk_signal_emit (GTK_OBJECT (page), page_signals[STATUS],
		   string);
}

static gint
gtk_page_button_release (GtkWidget      *widget,
			 GdkEventButton *event)
{
  GtkPage *page;

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_PAGE (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  if (event->window != widget->window)
    return FALSE;

  if (event->button == 1)
    {
      page = GTK_PAGE (widget);
      gtk_grab_remove (GTK_WIDGET (page));
    }

#ifdef TRACE_GTK_METHODS
  g_print ("gtk_page_button_release: (%d, %d) %d\n",
	   event->x, event->y, event->button);
#endif
  if (event->button == 1) {
    if (page->link_pressed == gtk_page_find_link (page, event->x, event->y) &&
	page->link_pressed >= 0) {
#ifdef VERBOSE
      g_print ("gtk_page_button_release: link %s\n",
	       page->links[page->link_pressed].url);
#endif
      gtk_page_link (page, page->links[page->link_pressed].url);
    }
  }

  return TRUE;
}

static gint
gtk_page_motion_notify (GtkWidget      *widget,
			GdkEventMotion *event)
{
  GtkPage *page;
  gint hover_link;

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_PAGE (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  if (event->window != widget->window)
    return FALSE;

  page = GTK_PAGE (widget);

  /* maybe should modify the behavior when the button is actually pushed */

#ifdef VERBOSE
  g_print ("gtk_page_motion_notify: (%d, %d)\n", event->x, event->y);
#endif
  hover_link = gtk_page_find_link (page, event->x, event->y);
  if (page->hover_link != hover_link) {
    if (hover_link >= 0)
      gtk_page_status (page, page->links[hover_link].url);
    else
      gtk_page_status (page, "");
    page->hover_link = hover_link;
  }
#if 0
  if (page->link_pressed == gtk_page_find_link (page, event->x, event->y) &&
      page->link_pressed >= 0) {
#ifdef VERBOSE
      g_print ("gtk_page_button_release: link %s\n",
	       page->links[page->link_pressed].url);
#endif
      gtk_page_link (page, page->links[page->link_pressed].url);
    }
  }
#endif

  return TRUE;
}

static gint
gtk_page_expose (GtkWidget      *widget,
		  GdkEventExpose *event)
{
  GtkPage *page;
  GtkContainer *container;
  gint x, y;
  double xalign, yalign;
  gint border;

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_PAGE (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

#ifdef TRACE_GTK_METHODS
  g_print ("gtk_page_expose (%d, %d) (%d x %d)\n",
	   event->area.x, event->area.y,
	   event->area.width, event->area.height);
#endif
  if (GTK_WIDGET_VISIBLE (widget) && GTK_WIDGET_MAPPED (widget))
    {
      page = GTK_PAGE (widget);
      container = GTK_CONTAINER (widget);

      /* todo: this should probably be in the realize method, not here. */
      if (page->gc == NULL) {
#ifdef USE_TYPE1
#endif
	page->gc = gdk_gc_new (widget->window);
      }

      /* Note: we _always_ draw. Many gtk widgets check that the
	 requisition is at least as big as the allocation. However,
	 since we have our own huge window, we can't count on getting
	 an expose event later on. */

      /* These should probably be in the gtk_page structure and
	 settable by the caller (which would be done in the case of
	 align and valign parameters in recursive page. */
      xalign = 0.0;
      yalign = 0.0;
      border = container->border_width;
#ifndef NO_WINDOW
      x = ((border) * (1.0 - xalign) +
	   (widget->allocation.width -
	    (widget->requisition.width - border)) *
	   xalign) + 0.5;
      y = ((border) * (1.0 - yalign) +
	   (widget->allocation.height -
	    (widget->requisition.height - border)) *
	   yalign) + 0.5;
#else
      x = ((widget->allocation.x + border) * (1.0 - xalign) +
	   (widget->allocation.x + widget->allocation.width -
	    (widget->requisition.width - border)) *
	   xalign) + 0.5;
      y = ((widget->allocation.y + border) * (1.0 - yalign) +
	   (widget->allocation.y + widget->allocation.height -
	    (widget->requisition.height - border)) *
	   yalign) + 0.5;
#endif

      gtk_page_expose_lines (widget, event, x, y);
    }
  return TRUE;
}

static void
gtk_page_map (GtkWidget *widget) {
  gint line_index, word_index;
  GtkPage *page;
  GtkPageLine *line;
  GtkPageWord *word;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_PAGE (widget));

  GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
#ifndef NO_WINDOW
  gdk_window_show (widget->window);
#endif

  page = GTK_PAGE (widget);

#ifdef TRACE_GTK_METHODS
  g_print ("gtk_page_map\n");
#endif

  for (line_index = 0; line_index < page->num_lines; line_index++) {
    line = &(page->lines[line_index]);
    for (word_index = 0; word_index < line->num_words; word_index++) {
      word = &(line->words[word_index]);
      if (word->content_type == GTK_PAGE_CONTENT_WIDGET) {
	if (GTK_WIDGET_VISIBLE (word->content.widget) &&
	    !GTK_WIDGET_MAPPED (word->content.widget))
	  gtk_widget_map (word->content.widget);
      }
    }
  }
}

static void
gtk_page_unmap (GtkWidget *widget) {
  gint line_index, word_index;
  GtkPage *page;
  GtkPageLine *line;
  GtkPageWord *word;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_PAGE (widget));

  GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);

  page = GTK_PAGE (widget);

#ifdef TRACE_GTK_METHODS
  g_print ("gtk_page_unmap\n");
#endif

  for (line_index = 0; line_index < page->num_lines; line_index++) {
    line = &(page->lines[line_index]);
    for (word_index = 0; word_index < line->num_words; word_index++) {
      word = &(line->words[word_index]);
      if (word->content_type == GTK_PAGE_CONTENT_WIDGET) {
	if (GTK_WIDGET_VISIBLE (word->content.widget) &&
	    GTK_WIDGET_MAPPED (word->content.widget))
	  gtk_widget_unmap (word->content.widget);
      }
    }
  }
}

static void
gtk_page_realize (GtkWidget *widget)
{
  GtkPage *page;
  GdkWindowAttr attributes;
  gint attributes_mask;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_PAGE (widget));

  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
  page = GTK_PAGE (widget);

#ifdef TRACE_GTK_METHODS
  g_print ("gtk_page_realize\n");
#endif

  if (GTK_WIDGET_NO_WINDOW (widget))
    {
      widget->window = widget->parent->window;
      widget->style = gtk_style_attach (widget->style, widget->window);
    }
  else
    {
      attributes.window_type = GDK_WINDOW_CHILD;
      attributes.x = widget->allocation.x;
      attributes.y = widget->allocation.y;
#ifdef HUGE_WINDOW
      attributes.width = GTK_PAGE_MAX_WINDOW_SIZE;
      attributes.height = GTK_PAGE_MAX_WINDOW_SIZE;
#else
      attributes.width = widget->allocation.width;
      attributes.height = widget->allocation.height;
#endif
      attributes.wclass = GDK_INPUT_OUTPUT;
      attributes.visual = gtk_widget_get_visual (widget);
      attributes.colormap = gtk_widget_get_colormap (widget);
      attributes.event_mask = gtk_widget_get_events (widget) |
	GDK_EXPOSURE_MASK |
	GDK_BUTTON_PRESS_MASK |
	GDK_BUTTON_RELEASE_MASK |
	GDK_POINTER_MOTION_MASK |
	GDK_ENTER_NOTIFY_MASK |
	GDK_LEAVE_NOTIFY_MASK;
      attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;

      widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
      gdk_window_set_user_data (widget->window, widget);

      widget->style = gtk_style_attach (widget->style, widget->window);
      gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
    }
}

static void
gtk_page_draw (GtkWidget *widget,
	       GdkRectangle *area) {
  gint line_index, word_index;
  GtkPage *page;
  GtkPageLine *line;
  GtkPageWord *word;
  GdkRectangle child_area;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_PAGE (widget));

  GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);

  page = GTK_PAGE (widget);

#ifdef TRACE_GTK_METHODS
  g_print ("gtk_page_draw\n");
#endif

  if (GTK_WIDGET_DRAWABLE (widget)) {
    for (line_index = 0; line_index < page->num_lines; line_index++) {
      line = &(page->lines[line_index]);
      for (word_index = 0; word_index < line->num_words; word_index++) {
	word = &(line->words[word_index]);
	if (word->content_type == GTK_PAGE_CONTENT_WIDGET) {
	  if (gtk_widget_intersect (word->content.widget, area, &child_area))
	    gtk_widget_draw (word->content.widget, &child_area);
	}
      }
    }
  }
}

static void
gtk_page_size_allocate (GtkWidget     *widget,
			GtkAllocation *allocation)
{
  gint line_index, word_index;
  GtkPage *page;
  GtkPageLine *line;
  GtkPageWord *word;
  gint x_cursor;
  GtkAllocation child_allocation;
  gint border_width;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_PAGE (widget));
  g_return_if_fail (allocation != NULL);

  page = GTK_PAGE (widget);
  widget->allocation = *allocation;

#ifdef TRACE_GTK_METHODS
  g_print ("gtk_page_size_allocate: (%d, %d) (%d x %d)\n",
	   allocation->x, allocation->y,
	   allocation->width, allocation->height);
#endif

  border_width = GTK_CONTAINER (page)->border_width;
  for (line_index = 0; line_index < page->num_lines; line_index++) {
    line = &(page->lines[line_index]);
#ifndef NO_WINDOW
    x_cursor = border_width;
#else
    x_cursor = widget->allocation.x + border_width;
#endif
    if (line->first)
      x_cursor += page->attrs[line->words[0].attr].left_indent_first;
    else
      x_cursor += page->attrs[line->words[0].attr].left_indent_rest;
    for (word_index = 0; word_index < line->num_words; word_index++) {
      word = &(line->words[word_index]);
      if (word->content_type == GTK_PAGE_CONTENT_WIDGET &&
	  GTK_WIDGET_VISIBLE (word->content.widget)) {
	child_allocation.x = x_cursor;
#ifndef NO_WINDOW
	child_allocation.y = border_width +
	  line->y_top + line->y_ascent - word->y_ascent;
#else
	child_allocation.y = widget->allocation.y + border_width +
	  line->y_top + line->y_ascent - word->y_ascent;
#endif
	child_allocation.width = word->x_size;
	child_allocation.height = word->y_ascent + word->y_descent;
	gtk_widget_size_allocate (word->content.widget, &child_allocation);
      }
      x_cursor += word->x_size + word->x_space;
    }
  }
  gtk_page_do_redraws (page, FALSE);
#ifndef HUGE_WINDOW
  if (GTK_WIDGET_REALIZED (widget) &&
      !GTK_WIDGET_NO_WINDOW (widget))
    gdk_window_move_resize (widget->window,
			    allocation->x, allocation->y,
			    allocation->width, allocation->height);
#endif
}

static void
gtk_page_foreach (GtkContainer  *container,
		  GtkCallback   callback,
		  gpointer      callback_data) {
  gint line_index, word_index;
  GtkPage *page;
  GtkPageLine *line;
  GtkPageWord *word;

  g_return_if_fail (container != NULL);
  g_return_if_fail (GTK_IS_PAGE (container));

  page = GTK_PAGE (container);

#ifdef TRACE_GTK_METHODS
  g_print ("gtk_page_foreach\n");
#endif

  for (line_index = 0; line_index < page->num_lines; line_index++) {
    line = &(page->lines[line_index]);
    for (word_index = 0; word_index < line->num_words; word_index++) {
      word = &(line->words[word_index]);
      if (word->content_type == GTK_PAGE_CONTENT_WIDGET) {
	(*callback) (word->content.widget, callback_data);
      }
    }
  }
}

static void
gtk_page_marshal_signal_1 (GtkObject      *object,
			   GtkSignalFunc   func,
			   gpointer        func_data,
			   GtkArg         *args)
{
  GtkPageSignal1 rfunc;

  rfunc = (GtkPageSignal1) func;

  (* rfunc) (object, args[0].d.p, func_data);
}
