[Commit] gwaterfall/src Makefile.am,NONE,1.1 main.c,NONE,1.1 waterfall.c,NONE,1.1 waterfall.h,NONE,1.1

Noah Levitt commit@keithp.com
Sun, 27 Apr 2003 19:45:48 -0700


Committed by: nlevitt

Update of /local/src/CVS/gwaterfall/src
In directory home.keithp.com:/tmp/cvs-serv11286/src

Added Files:
	Makefile.am main.c waterfall.c waterfall.h 
Log Message:
Initial checkin.


--- NEW FILE: Makefile.am ---
## 
## $Id: Makefile.am,v 1.1 2003/04/28 02:45:45 nlevitt Exp $
## 
## Copyright (c) 2003 Noah Levitt
## 
## This program is free software; the author gives unlimited permission to
## copy and/or distribute it, with or without modifications, as long as
## this notice is preserved.
## 
## This program is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
## implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
## 

AM_CFLAGS = $(WATERFALL_CFLAGS)

bin_PROGRAMS = waterfall
waterfall_SOURCES = main.c waterfall.c waterfall.h

waterfall_LDADD = @WATERFALL_LIBS@


--- NEW FILE: main.c ---
/*
 * $Id: main.c,v 1.1 2003/04/28 02:45:45 nlevitt Exp $
 *
 * Copyright (c) 2003 Noah Levitt
 * 
 * This program is free software; the author gives unlimited permission to
 * copy and/or distribute it, with or without modifications, as long as
 * this notice is preserved.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <gtk/gtk.h>
#include <fontconfig/fontconfig.h>
#include <waterfall.h>


static GtkWidget *page_label = NULL;
static GtkWidget *style_combo = NULL;


static void
set_style_choices (Waterfall *waterfall)
{
  FcObjectSet *set;
  FcPattern *pattern;
  FcFontSet *list;
  GList *styles = NULL;
  FcChar8 *style;
  gint i;

  if (style_combo == NULL)
    return;

  set = FcObjectSetBuild (FC_STYLE, 0);
  pattern = FcPatternBuild (0, FC_FAMILY, FcTypeString, 
                            waterfall_get_font_family (waterfall), 0);
  g_assert (pattern != NULL);

  list = FcFontList (0, pattern, set);
  FcPatternDestroy (pattern);

  g_assert (list->nfont > 0);

  for (i = 0;  i < list->nfont;  i++)
    {
      FcPatternGetString (list->fonts[i], FC_STYLE, 0, &style);
      styles = g_list_append (styles, style);
    }

  gtk_combo_set_popdown_strings (GTK_COMBO (style_combo), styles);
  FcFontSetDestroy (list);
}


static void
family_changed (GtkEntry *entry,
                Waterfall *waterfall)
{
  const gchar *new_family = gtk_entry_get_text (entry);

  /* ignore empty string (it sends these a lot) */
  if (new_family[0] == '\0')
    return;

  waterfall_set_font_family (waterfall, new_family);
  set_style_choices (waterfall);
}


static void
style_changed (GtkEntry *entry,
               Waterfall *waterfall)
{
  const gchar *new_style = gtk_entry_get_text (entry);

  /* ignore empty string (it sends these a lot) */
  if (new_style[0] == '\0')
    return;

  waterfall_set_font_style (waterfall, new_style);
}


static GtkWidget *
construct_font_family_chooser (Waterfall *waterfall)
{
  GtkWidget *combo;
  FcFontSet *list;
  FcObjectSet *set = FcObjectSetBuild (FC_FAMILY, 0);
  FcPattern *pattern = FcPatternCreate ();
  GList *family_names = NULL;
  FcChar8 *family_name;
  int i;

  combo = gtk_combo_new ();
  gtk_widget_show (combo);
  gtk_editable_set_editable (GTK_EDITABLE (GTK_COMBO (combo)->entry), FALSE);

  g_signal_connect (GTK_COMBO (combo)->entry, "changed",
                    G_CALLBACK (family_changed), waterfall);

  list = FcFontList (0, pattern, set);
  FcObjectSetDestroy (set);
  FcPatternDestroy (pattern);

  g_assert (list->nfont > 0);
  for (i = 0;  i < list->nfont;  i++)
    {
      FcPatternGetString (list->fonts[i], FC_FAMILY, 0, &family_name);
      family_names = g_list_append (family_names, family_name);
    }

  family_names = g_list_sort (family_names, (GCompareFunc) FcStrCmpIgnoreCase);
  gtk_combo_set_popdown_strings (GTK_COMBO (combo), family_names);

  g_list_free (family_names);
  FcFontSetDestroy (list);

  return combo;
}


static GtkWidget *
construct_font_style_chooser (Waterfall *waterfall)
{
  style_combo = gtk_combo_new ();
  gtk_widget_show (style_combo);
  gtk_editable_set_editable (GTK_EDITABLE (GTK_COMBO (style_combo)->entry), 
                             FALSE);

  g_signal_connect (GTK_COMBO (style_combo)->entry, "changed",
                    G_CALLBACK (style_changed), waterfall);

  set_style_choices (waterfall);

  return style_combo;
}


static void
hint_toggled (GtkToggleButton *toggle_button,
              Waterfall *waterfall)
{
  waterfall_set_hint (waterfall, 
                      gtk_toggle_button_get_active (toggle_button));
}


static void
antialias_toggled (GtkToggleButton *toggle_button,
                   Waterfall *waterfall)
{
  waterfall_set_antialias (waterfall, 
                           gtk_toggle_button_get_active (toggle_button));
}


static void
min_size_changed (GtkAdjustment *adjustment,
                  Waterfall *waterfall)
{
  waterfall_set_min_size (waterfall, gtk_adjustment_get_value (adjustment));
}


static void
max_size_changed (GtkAdjustment *adjustment,
                  Waterfall *waterfall)
{
  waterfall_set_max_size (waterfall, gtk_adjustment_get_value (adjustment));
}


static void
increment_changed (GtkAdjustment *adjustment,
                  Waterfall *waterfall)
{
  waterfall_set_increment (waterfall, gtk_adjustment_get_value (adjustment));
}


static GtkWidget *
construct_size_bar (Waterfall *waterfall)
{
  GtkWidget *hbox;
  GtkObject *adjustment;
  GtkWidget *spin_button;
  GtkWidget *label;

  hbox = gtk_hbox_new (FALSE, 12);
  gtk_widget_show (hbox);

  label = gtk_label_new ("Smallest:");
  gtk_widget_show (label);
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);

  adjustment = gtk_adjustment_new (waterfall_get_min_size (waterfall),
                                   1.0, 1000.0, 2.0, 12.0, 0);
  g_signal_connect (adjustment, "value-changed", 
                    G_CALLBACK (min_size_changed), waterfall);
  spin_button = gtk_spin_button_new (GTK_ADJUSTMENT (adjustment), 0, 1);
  gtk_widget_show (spin_button);
  gtk_box_pack_start (GTK_BOX (hbox), spin_button, FALSE, FALSE, 0);

  label = gtk_label_new ("Biggest:");
  gtk_widget_show (label);
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);

  adjustment = gtk_adjustment_new (waterfall_get_max_size (waterfall),
                                   1.0, 1000.0, 2.0, 12.0, 0);
  g_signal_connect (adjustment, "value-changed", 
                    G_CALLBACK (max_size_changed), waterfall);
  spin_button = gtk_spin_button_new (GTK_ADJUSTMENT (adjustment), 0, 1);
  gtk_widget_show (spin_button);
  gtk_box_pack_start (GTK_BOX (hbox), spin_button, FALSE, FALSE, 0);

  label = gtk_label_new ("Increment:");
  gtk_widget_show (label);
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);

  adjustment = gtk_adjustment_new (waterfall_get_increment (waterfall),
                                   0.1, 100.0, 0.1, 1.0, 0);
  g_signal_connect (adjustment, "value-changed", 
                    G_CALLBACK (increment_changed), waterfall);
  spin_button = gtk_spin_button_new (GTK_ADJUSTMENT (adjustment), 0, 1);
  gtk_widget_show (spin_button);
  gtk_box_pack_start (GTK_BOX (hbox), spin_button, FALSE, FALSE, 0);

  return hbox;
}


static void
set_page_label (Waterfall *waterfall)
{
  gchar *label;
  gint page = waterfall_get_page (waterfall);

  if (page == -1)
    label = g_strdup ("Quick Brown"); /* strdup just so it can be freed */
  else
    label = g_strdup_printf ("U+%4.4X", page * WATERFALL_PAGE_SIZE);

  gtk_label_set_text (GTK_LABEL (page_label), label);

  g_free (label);
}


/* direction should be +1 or -1 */
static void
set_page (Waterfall *waterfall, 
          gint page,
          gint direction)
{
  while (waterfall_is_page_empty (waterfall, page)
         && page + direction <= WATERFALL_LAST_PAGE
         && page + direction >= -1)
    page += direction;

  waterfall_set_page (waterfall, page);
  set_page_label (waterfall);
}


static void
back_back_clicked (GtkButton *button, 
                   Waterfall *waterfall)
{
  set_page (waterfall, waterfall_get_page (waterfall) - 16, -1);
}


static void
back_clicked (GtkButton *button, 
              Waterfall *waterfall)
{
  set_page (waterfall, waterfall_get_page (waterfall) - 1, -1);
}


static void
forward_clicked (GtkButton *button, 
                 Waterfall *waterfall)
{
  set_page (waterfall, waterfall_get_page (waterfall) + 1, 1);
}


static void
forward_forward_clicked (GtkButton *button, 
                         Waterfall *waterfall)
{
  set_page (waterfall, waterfall_get_page (waterfall) + 16, 1);
}


static void
forward_forward_forward_clicked (GtkButton *button, 
                                 Waterfall *waterfall)
{
  set_page (waterfall, WATERFALL_LAST_PAGE, 1);
}


static void
back_back_back_clicked (GtkButton *button, 
                        Waterfall *waterfall)
{
  set_page (waterfall, 0, -1);
}


static void
quick_brown_clicked (GtkButton *button, 
                     Waterfall *waterfall)
{
  set_page (waterfall, -1, -1);
}


static GtkWidget *
construct_page_navigator (Waterfall *waterfall)
{
  GtkWidget *hbox;
  GtkWidget *button;

  hbox = gtk_hbox_new (FALSE, 3);
  gtk_widget_show (hbox);

  button = gtk_button_new_with_label (" <<< ");
  gtk_widget_show (button);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  g_signal_connect (G_OBJECT (button), "clicked", 
                    G_CALLBACK (back_back_back_clicked), waterfall);

  button = gtk_button_new_with_label ("  <<  ");
  gtk_widget_show (button);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  g_signal_connect (G_OBJECT (button), "clicked", 
                    G_CALLBACK (back_back_clicked), waterfall);

  button = gtk_button_new_with_label ("  <  ");
  gtk_widget_show (button);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  g_signal_connect (G_OBJECT (button), "clicked", 
                    G_CALLBACK (back_clicked), waterfall);

  page_label = gtk_label_new (NULL);
  gtk_widget_show (page_label);
  gtk_label_set_selectable (GTK_LABEL (page_label), TRUE);
  gtk_box_pack_start (GTK_BOX (hbox), page_label, FALSE, FALSE, 0);
  set_page_label (waterfall);

  button = gtk_button_new_with_label ("  >  ");
  gtk_widget_show (button);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  g_signal_connect (G_OBJECT (button), "clicked", 
                    G_CALLBACK (forward_clicked), waterfall);

  button = gtk_button_new_with_label ("  >>  ");
  gtk_widget_show (button);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  g_signal_connect (G_OBJECT (button), "clicked", 
                    G_CALLBACK (forward_forward_clicked), waterfall);

  button = gtk_button_new_with_label (" >>> ");
  gtk_widget_show (button);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  g_signal_connect (G_OBJECT (button), "clicked", 
                    G_CALLBACK (forward_forward_forward_clicked), waterfall);

  button = gtk_button_new_with_label ("Quick Brown");
  gtk_widget_show (button);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  g_signal_connect (G_OBJECT (button), "clicked", 
                    G_CALLBACK (quick_brown_clicked), waterfall);

  return hbox;
}


static GtkWidget *
construct_options_bar (Waterfall *waterfall)
{
  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *check_button;

  vbox = gtk_vbox_new (FALSE, 6);
  gtk_widget_show (vbox);

  hbox = gtk_hbox_new (FALSE, 12);
  gtk_widget_show (hbox);

  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);

  gtk_box_pack_start (GTK_BOX (hbox), 
                      construct_font_family_chooser (waterfall), 
                      FALSE, FALSE, 0);

  gtk_box_pack_start (GTK_BOX (hbox), 
                      construct_font_style_chooser (waterfall), 
                      FALSE, FALSE, 0);

  check_button = gtk_check_button_new_with_mnemonic ("_Hinting");
  gtk_widget_show (check_button);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button),
                                waterfall_get_hint (waterfall));
  gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0);
  g_signal_connect (G_OBJECT (check_button), "toggled",
                    G_CALLBACK (hint_toggled), waterfall);

  check_button = gtk_check_button_new_with_mnemonic ("_Anti-Aliasing");
  gtk_widget_show (check_button);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button),
                                waterfall_get_antialias (waterfall));
  gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0);
  g_signal_connect (G_OBJECT (check_button), "toggled",
                    G_CALLBACK (antialias_toggled), waterfall);

  gtk_box_pack_start (GTK_BOX (hbox), construct_size_bar (waterfall), 
                      FALSE, FALSE, 0);

  gtk_box_pack_start (GTK_BOX (vbox), construct_page_navigator (waterfall), 
                      FALSE, FALSE, 0);

  return vbox;
}


gint
main (gint argc, gchar **argv)
{
  GtkWidget *window;
  GtkWidget *vbox;
  GtkWidget *waterfall;
  GtkWidget *scrolled_window;
  GdkScreen *screen;

  gtk_init (&argc, &argv);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (window), "Waterfall");
  g_signal_connect (G_OBJECT (window), "destroy",
                    G_CALLBACK (gtk_main_quit), NULL);

  screen = gtk_window_get_screen (GTK_WINDOW (window));
  gtk_window_set_default_size (GTK_WINDOW (window),
                               gdk_screen_get_width (screen) * 3/4,
                               gdk_screen_get_height (screen) * 3/4);

  vbox = gtk_vbox_new (FALSE, 6);
  gtk_widget_show (vbox);
  gtk_container_add (GTK_CONTAINER (window), vbox);

  waterfall = waterfall_new ();
  gtk_widget_show (waterfall);

  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
  gtk_widget_show (scrolled_window);
  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_window),
                                         waterfall);

  gtk_box_pack_start (GTK_BOX (vbox), 
                      construct_options_bar (WATERFALL (waterfall)), 
                      FALSE, FALSE, 0);

  gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, 
                      TRUE, TRUE, 0);

  gtk_widget_show (window);

  gtk_main ();

  return 0;
}



--- NEW FILE: waterfall.c ---
/*
 * $Id: waterfall.c,v 1.1 2003/04/28 02:45:45 nlevitt Exp $
 *
 * Copyright (c) 2003 Noah Levitt
 * 
 * This program is free software; the author gives unlimited permission to
 * copy and/or distribute it, with or without modifications, as long as
 * this notice is preserved.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <X11/Xft/Xft.h>
#include <string.h>
#include "waterfall.h"

#define DEFAULT_STRING \
"the quick brown fox jumped over the lazy dog. " \
"THE QUICK BROWN FOX JUMPED OVER THE LAZY DOG. " \
"0123456789!\"#$%&'()*+,-./:;<=>?@[\\]{|}~"

enum { BUFFER = 12 };


/* buf should have at least WATERFALL_PAGE_SIZE slots */
static void
get_string_for_page (Waterfall *waterfall,
                     gunichar *buf,
                     gint *len)
{
  gint i;

  if (waterfall->page == -1)
    {
      gchar *p = DEFAULT_STRING;

      for (i = 0;  p && i < WATERFALL_PAGE_SIZE;  i++)
        {
          buf[i] = g_utf8_get_char (p);
          p = g_utf8_find_next_char (p, NULL);
        }

      *len = i;
    }
  else
    {
      for (i = 0;  i < WATERFALL_PAGE_SIZE;  i++)
        buf[i] = (gunichar) (waterfall->page * WATERFALL_PAGE_SIZE + i);
      *len = WATERFALL_PAGE_SIZE;
    }
}


static XftFont *
open_font (Waterfall *waterfall, 
           gdouble size)
{
  FcPattern   *pattern, *match;
  FcResult    result;
  XftFont     *font;

  pattern = FcPatternCreate ();

  if (waterfall->font_family != NULL)
    FcPatternAddString (pattern, FC_FAMILY, waterfall->font_family);

  if (waterfall->font_style != NULL)
    FcPatternAddString (pattern, FC_STYLE, waterfall->font_style);

  if (size)
    FcPatternAddDouble (pattern, FC_PIXEL_SIZE, size);

  FcPatternAddBool (pattern, FC_HINTING, waterfall->hint);
  FcPatternAddBool (pattern, FC_ANTIALIAS, waterfall->antialias);

  match = XftFontMatch (GDK_DISPLAY (), gdk_x11_get_default_screen (), 
                        pattern, &result);

  FcPatternDestroy (pattern);

  if (!match)
    return NULL;

  font = XftFontOpenPattern (GDK_DISPLAY (), match);
  if (!font)
    FcPatternDestroy (match);

  return font;
}


static void
optimal_size (Waterfall *waterfall,
              gint *width,
              gint *height)
{
  XGlyphInfo extents;
  gdouble lines;
  XftFont *big_font, *small_font, *theme_font;
  gchar *buf;

  small_font = open_font (waterfall, waterfall->min_size);
  big_font = open_font (waterfall, waterfall->max_size);
  theme_font = XftFontOpenName (GDK_DISPLAY (), gdk_x11_get_default_screen (), 
                                pango_font_description_to_string (
                                    GTK_WIDGET (waterfall)->style->font_desc));

  XftTextExtentsUtf8 (GDK_DISPLAY (), big_font, DEFAULT_STRING, 
                      strlen (DEFAULT_STRING), &extents);
  *width = extents.width + BUFFER;

  buf = g_strdup_printf ("%5.1f", waterfall->max_size);
  XftTextExtentsUtf8 (GDK_DISPLAY (), theme_font, buf, strlen (buf), &extents);
  *width += extents.width + BUFFER;
  g_free (buf);

  if (*width > 6000)
    {
      g_warning ("Calculated width %d is too big, clipping at 6000.", *width);
      *width = 6000;
    }

  lines = (waterfall->max_size - waterfall->min_size) 
          / waterfall->increment + 1;
  *height = 0.5 * (big_font->height + small_font->height) * lines + BUFFER;

  if (*height > 3000)
    {
      g_warning ("Calculated height %d is too big, clipping at 3000.", *height);
      *height = 3000;
    }

  XftFontClose (GDK_DISPLAY (), big_font);
  XftFontClose (GDK_DISPLAY (), small_font);
  XftFontClose (GDK_DISPLAY (), theme_font);
}


static void
draw_pixmap (Waterfall *waterfall)
{
  Colormap colormap;
  Drawable drawable;
  Visual *visual;
  gint width, height;
  XGlyphInfo extents;
  XftFont *theme_font;
  XftColor fg;
  XftFont *font;
  XftDraw *xftdraw;
  gdouble size;
  gint y;
  gint size_width; /* width allotted to "12.0", etc */
  gchar *buf;
  gunichar ucs[128];
  gint ucs_len;

  width = GTK_WIDGET (waterfall)->allocation.width;
  height = GTK_WIDGET (waterfall)->allocation.height;

  theme_font = XftFontOpenName (GDK_DISPLAY (), gdk_x11_get_default_screen (), 
                                pango_font_description_to_string (
                                    GTK_WIDGET (waterfall)->style->font_desc));

  waterfall->pixmap = gdk_pixmap_new (GTK_WIDGET (waterfall)->window,
                                      width, height, -1);

  gdk_draw_rectangle (GDK_DRAWABLE (waterfall->pixmap),
                      GTK_WIDGET (waterfall)->style->base_gc[GTK_STATE_NORMAL], 
                      TRUE, 0, 0, width, height);

  colormap = gdk_x11_colormap_get_xcolormap (
          gdk_drawable_get_colormap (GTK_WIDGET (waterfall)->window));
  drawable = gdk_x11_drawable_get_xid (GDK_DRAWABLE (waterfall->pixmap));
  visual = gdk_x11_visual_get_xvisual (
          gtk_widget_get_visual (GTK_WIDGET (waterfall)));

  xftdraw = XftDrawCreate (GDK_DISPLAY (), drawable, visual, colormap);

  fg.color.red = GTK_WIDGET (waterfall)->style->fg[GTK_STATE_NORMAL].red;
  fg.color.green = GTK_WIDGET (waterfall)->style->fg[GTK_STATE_NORMAL].green;
  fg.color.blue = GTK_WIDGET (waterfall)->style->fg[GTK_STATE_NORMAL].blue;
  fg.color.alpha = 0xffff;

  buf = g_strdup_printf ("%5.1f", waterfall->max_size);
  XftTextExtentsUtf8 (GDK_DISPLAY (), theme_font, buf, strlen (buf), &extents);
  size_width = extents.width + BUFFER;
  g_free (buf);

  for (y = 0, size = waterfall->min_size;  size <= waterfall->max_size;  
       size += waterfall->increment)
    {
      font = open_font (waterfall, size);
      y += font->height;

      buf = g_strdup_printf ("%5.1f", size);
      XftDrawStringUtf8 (xftdraw, &fg, theme_font, 0, y, buf, strlen (buf));
      g_free (buf);

      get_string_for_page (waterfall, ucs, &ucs_len);
      XftDrawString32(xftdraw, &fg, font, size_width, y, ucs, ucs_len);

      XftFontClose (GDK_DISPLAY (), font);
    }

  XftFontClose (GDK_DISPLAY (), theme_font);
  XftDrawDestroy (xftdraw);
}


static gint
expose_event (Waterfall *waterfall,
              GdkEventExpose *event)
{
  gdk_window_set_back_pixmap (GTK_WIDGET (waterfall)->window, NULL, FALSE);

  if (waterfall->pixmap == NULL)
    draw_pixmap (waterfall);

  gdk_draw_drawable (GTK_WIDGET (waterfall)->window,
                     GTK_WIDGET (waterfall)->style->fg_gc[GTK_STATE_NORMAL],
                     waterfall->pixmap,
                     event->area.x, event->area.y,
                     event->area.x, event->area.y,
                     event->area.width, event->area.height);

  return FALSE;
}


static void
size_allocate (GtkWidget *widget,
               GtkAllocation *allocation)
{
}


void
waterfall_init (Waterfall *waterfall)
{
  waterfall->font_family = NULL;
  waterfall->font_style = NULL;
  waterfall->min_size = 5.0;
  waterfall->max_size = 36.0;
  waterfall->increment = 1.0;
  waterfall->hint = TRUE;
  waterfall->antialias = TRUE;
  waterfall->page = -1;

  gtk_widget_set_events (GTK_WIDGET (waterfall), GDK_EXPOSURE_MASK);
  g_signal_connect (G_OBJECT (waterfall), "expose-event", 
                    G_CALLBACK (expose_event), NULL);
  g_signal_connect (G_OBJECT (waterfall), "size-allocate",
                    G_CALLBACK (size_allocate), NULL);
}


GType waterfall_get_type ()
{
  static GType waterfall_type = 0;

  if (!waterfall_type)
    {
      static const GTypeInfo waterfall_info =
      {
        sizeof (WaterfallClass),
        NULL,     /* base_init */
        NULL,     /* base_finalize */
        NULL,     /* class_init */
        NULL,     /* class_finalize */
        NULL,     /* class_data */
        sizeof (Waterfall),
        0,        /* n_preallocs */
        (GInstanceInitFunc) waterfall_init,
      };

      waterfall_type = g_type_register_static (GTK_TYPE_DRAWING_AREA, 
                                               "Waterfall", 
                                               &waterfall_info, 0);
    }

  return waterfall_type;
}


GtkWidget *
waterfall_new ()
{
  return GTK_WIDGET (g_object_new (waterfall_get_type (), NULL));
}


static void
queue_redraw (Waterfall *waterfall)
{
  gint width, height;

  if (waterfall->pixmap)
    {
      g_object_unref (waterfall->pixmap);
      waterfall->pixmap = NULL;
    }

  optimal_size (waterfall, &width, &height);
  gtk_widget_set_size_request (GTK_WIDGET (waterfall), width, height);

  gtk_widget_queue_draw (GTK_WIDGET (waterfall));
}


void
waterfall_set_font_family (Waterfall *waterfall,
                           const gchar *new_family)
{
  if (waterfall->font_family)
    g_free (waterfall->font_family);

  if (waterfall->font)
    XftFontClose (GDK_DISPLAY (), waterfall->font);

  waterfall->font_family = g_strdup (new_family);
  waterfall->font = open_font (waterfall, waterfall->min_size);

  queue_redraw (waterfall);
}


const gchar *
waterfall_get_font_family (Waterfall *waterfall)
{
  return waterfall->font_family;
}


void
waterfall_set_font_style (Waterfall *waterfall,
                           const gchar *new_style)
{
  if (waterfall->font_style)
    g_free (waterfall->font_style);

  if (waterfall->font)
    XftFontClose (GDK_DISPLAY (), waterfall->font);

  waterfall->font_style = g_strdup (new_style);
  waterfall->font = open_font (waterfall, waterfall->min_size);

  queue_redraw (waterfall);
}


const gchar *
waterfall_get_font_style (Waterfall *waterfall)
{
  return waterfall->font_style;
}


gboolean
waterfall_get_hint (Waterfall *waterfall)
{
  return waterfall->hint;
}


void
waterfall_set_hint (Waterfall *waterfall, 
                    gboolean hint)
{
  if (hint != waterfall->hint)
    {
      waterfall->hint = hint;
      queue_redraw (waterfall);
    }
}


gboolean
waterfall_get_antialias (Waterfall *waterfall)
{
  return waterfall->antialias;
}


void
waterfall_set_antialias (Waterfall *waterfall, 
                         gboolean antialias)
{
  if (antialias != waterfall->antialias)
    {
      waterfall->antialias = antialias;
      queue_redraw (waterfall);
    }
}


gdouble 
waterfall_get_min_size (Waterfall *waterfall)
{
  return waterfall->min_size;
}


gdouble 
waterfall_get_max_size (Waterfall *waterfall)
{
  return waterfall->max_size;
}


gdouble 
waterfall_get_increment (Waterfall *waterfall)
{
  return waterfall->increment;
}


void
waterfall_set_min_size (Waterfall *waterfall, 
                        gdouble min_size)
{
  if (min_size != waterfall->min_size)
    {
      waterfall->min_size = min_size;
      queue_redraw (waterfall);
    }
}


void
waterfall_set_max_size (Waterfall *waterfall, 
                        gdouble max_size)
{
  if (max_size == waterfall->max_size)
    return;

  waterfall->max_size = max_size;

  queue_redraw (waterfall);
}


void
waterfall_set_increment (Waterfall *waterfall,
                         gdouble increment)
{
  if (increment == waterfall->increment)
    return;

  waterfall->increment = increment;

  queue_redraw (waterfall);
}


gint
waterfall_get_page (Waterfall *waterfall)
{
  return waterfall->page;
}


void
waterfall_set_page (Waterfall *waterfall,
                    gint page)
{
  if (page > WATERFALL_LAST_PAGE)
    page = WATERFALL_LAST_PAGE;
  else if (page < -1)
    page = -1;

  if (page != waterfall->page)
    {
      waterfall->page = page;
      queue_redraw (waterfall);
    }
}


gboolean
waterfall_is_page_empty (Waterfall *waterfall,
                         gint page)
{
  FcCharSet *charset;
  FcChar32 count;
  guint i;

  if (page == -1)
    return FALSE;

  charset = FcCharSetCreate ();

  for (i = 0;  i < WATERFALL_PAGE_SIZE;  i++)
    FcCharSetAddChar (charset, page * WATERFALL_PAGE_SIZE + i);

  count = FcCharSetIntersectCount (waterfall->font->charset, charset);

  FcCharSetDestroy (charset);

  return count == 0;
}



--- NEW FILE: waterfall.h ---
/*
 * $Id: waterfall.h,v 1.1 2003/04/28 02:45:45 nlevitt Exp $
 *
 * Copyright (c) 2003 Noah Levitt
 * 
 * This program is free software; the author gives unlimited permission to
 * copy and/or distribute it, with or without modifications, as long as
 * this notice is preserved.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

#ifndef WATERFALL_H
#define WATERFALL_H

#include <gtk/gtk.h>
#include <X11/Xft/Xft.h>

G_BEGIN_DECLS

#define WATERFALL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
                        waterfall_get_type (), Waterfall))

#define WATERFALL_CLASS(clazz) (G_TYPE_CHECK_CLASS_CAST ((clazz), \
                                                         waterfall_get_type (),\
                                                         WaterfallClass))

#define IS_WATERFALL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
                           waterfall_get_type ()))


typedef struct _Waterfall Waterfall;
typedef struct _WaterfallClass WaterfallClass;

enum { UNICODE_MAX = 0x10ffff };
enum { WATERFALL_PAGE_SIZE = 128 };
enum { WATERFALL_LAST_PAGE = UNICODE_MAX / WATERFALL_PAGE_SIZE };

struct _Waterfall
{
  GtkDrawingArea parent;

  GdkPixmap *pixmap;
  XftDraw *xftdraw;

  gchar *font_family;
  gchar *font_style;
  XftFont *font;

  gdouble min_size;
  gdouble max_size;
  gdouble increment;

  gboolean hint;
  gboolean antialias;

  gint page;
};


struct _WaterfallClass
{
  GtkDrawingAreaClass parent_class;
};


GType waterfall_get_type ();
GtkWidget *waterfall_new ();

const gchar * waterfall_get_font_family (Waterfall *waterfall);
void waterfall_set_font_family (Waterfall *waterfall, 
                                const gchar *new_family);
void waterfall_set_font_style (Waterfall *waterfall,
                               const gchar *new_style);
const gchar * waterfall_get_font_style (Waterfall *waterfall);
void waterfall_set_antialias (Waterfall *waterfall, 
                              gboolean antialias);
void waterfall_set_hint (Waterfall *waterfall, 
                         gboolean hint);
gboolean waterfall_get_antialias (Waterfall *waterfall);
gboolean waterfall_get_hint (Waterfall *waterfall);
gdouble waterfall_get_min_size (Waterfall *waterfall);
gdouble waterfall_get_max_size (Waterfall *waterfall);
gdouble waterfall_get_increment (Waterfall *waterfall);
void waterfall_set_min_size (Waterfall *waterfall, 
                             gdouble min_size);
void waterfall_set_max_size (Waterfall *waterfall, 
                             gdouble max_size);
void waterfall_set_increment (Waterfall *waterfall,
                              gdouble increment);
gint waterfall_get_page (Waterfall *waterfall);
void waterfall_set_page (Waterfall *waterfall,
                         gint page);
gboolean waterfall_is_page_empty (Waterfall *waterfall,
                                  gint page);


G_END_DECLS

#endif /* #ifndef WATERFALL_H */