/*
 * $Id: waterfall.c,v 1.6 2003/04/29 03:09: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 jumps over the lazy dog. " \
"THE QUICK BROWN FOX JUMPS 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);

  FcPatternAddDouble (pattern, FC_PIXEL_SIZE, size);
  FcPatternAddBool (pattern, FC_HINTING, waterfall->hint);
  FcPatternAddBool (pattern, FC_ANTIALIAS, waterfall->antialias);
  FcPatternAddBool (pattern, FC_AUTOHINT, waterfall->autohint);
  FcPatternAddInteger (pattern, FC_RGBA, waterfall->rgba);
  FcPatternAddDouble (pattern, FC_ASPECT, waterfall->aspect);

  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;
  gunichar ucs[128];
  gint ucs_len;

  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));

  get_string_for_page (waterfall, ucs, &ucs_len);
  XftTextExtents32 (GDK_DISPLAY (), big_font, ucs, ucs_len, &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->autohint = FALSE;
  waterfall->rgba = FC_RGBA_NONE;
  waterfall->aspect = 1.0;
  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);
    }
}


gboolean
waterfall_get_autohint (Waterfall *waterfall)
{
  return waterfall->autohint;
}


void
waterfall_set_autohint (Waterfall *waterfall, 
                        gboolean autohint)
{
  if (autohint != waterfall->autohint)
    {
      waterfall->autohint = autohint;
      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;
}


gint
waterfall_get_rgba (Waterfall *waterfall)
{
  return waterfall->rgba;
}


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



gdouble 
waterfall_get_aspect (Waterfall *waterfall)
{
  return waterfall->aspect;
}


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