/*
 * This is a plugin for the GIMP.
 *
 * Copyright (C) 1997 Xavier Bouchoux
 *
 * This program is free software; you can redistribute it and/or modify
 * it 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.
 *
 */

/*
 * This plug-in produces sinus textures.
 *
 * Please send any patches or suggestions to me: Xavier.Bouchoux@ensimag.imag.fr.
 */

/* Version 0.99 */

#define USE_LOGO

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>

#include <gtk/gtk.h>

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>

#include "libgimp/stdplugins-intl.h"

#ifdef USE_LOGO
#include "sinus_logo.h"
#endif

/*
 * This structure is used for persistent data.
 */

#define B_W        0L  /* colors setting */
#define USE_FG_BG  1L
#define USE_COLORS 2L

#define LINEAR     0L  /* colorization settings */
#define BILINEAR   1L
#define SINUS      2L

#define IDEAL      0L  /* Perturbation settings */
#define PERTURBED  1L

typedef struct
{
  gdouble   scalex;
  gdouble   scaley;
  gdouble   cmplx;
  gdouble   blend_power;
  gint      seed;
  gint      tiling;
  glong     perturbation;
  glong     colorization;
  glong     colors;
  guchar    col1[4];
  guchar    col2[4];
} SinusVals;

static SinusVals svals = 
{
  15.0,
  15.0,
  1.0,
  0.0,
  42,
  TRUE,
  PERTURBED,
  LINEAR, 
  USE_COLORS,
  { 255, 255, 0, 255 },
  { 0, 0, 255, 255 }
};

typedef struct
{
  gint   height, width;
  double c11, c12, c13, c21, c22, c23, c31, c32, c33;
  double (*blend) (double );
  guchar r, g, b, a;
  int    dr, dg, db, da;
} params;


static gint              drawable_is_grayscale = FALSE;
static struct mwPreview *thePreview;
static GDrawable        *drawable;

/*  preview stuff -- to be removed as soon as we have a real libgimp preview  */

struct mwPreview
{
  gint     width;
  gint     height;
  gint     bpp;
  gdouble  scale;
  guchar  *bits;
};

#define PREVIEW_SIZE 100

static gint do_preview = TRUE;

static GtkWidget        * mw_preview_new          (GtkWidget        *parent,
						   struct mwPreview *mwp);
static struct mwPreview * mw_preview_build_virgin (GDrawable        *drw);

/* Declare functions */

static void query (void);
static void run   (gchar   *name,
		   gint     nparams,
		   GParam  *param,
		   gint    *nreturn_vals,
		   GParam **return_vals);
static void sinus (void);

static gdouble linear   (gdouble v);
static gdouble bilinear (gdouble v);
static gdouble cosinus  (gdouble v);

static gint    sinus_dialog     (void);
static void    sinus_do_preview (GtkWidget *widget);

static inline void compute_block_4 (guchar *dest_row, guint rowstride,
				    gint x0, gint y0, gint h, gint w, params *p);
static inline void compute_block_3 (guchar *dest_row, guint rowstride,
				    gint x0, gint y0, gint h, gint w, params *p);
static inline void compute_block_2 (guchar *dest_row, guint rowstride,
				    gint x0, gint y0, gint h, gint w, params *p);
static inline void compute_block_1 (guchar *dest_row, guint rowstride,
				    gint x0, gint y0, gint h, gint w, params *p);

GPlugInInfo PLUG_IN_INFO =
{
  NULL,    /* init_proc */
  NULL,    /* quit_proc */
  query,   /* query_proc */
  run,     /* run_proc */
};

MAIN ()

static void
query (void)
{
  static GParamDef args[] =
  {
    { PARAM_INT32,    "run_mode", "Interactive, non-interactive" },
    { PARAM_IMAGE,    "image", "Input image (unused)" },
    { PARAM_DRAWABLE, "drawable", "Input drawable" },

    { PARAM_FLOAT,    "xscale", "Scale value for x axis" },
    { PARAM_FLOAT,    "yscale", "Scale value dor y axis" },
    { PARAM_FLOAT,    "complex", "Complexity factor" },
    { PARAM_INT32,    "seed", "Seed value for random number generator" },
    { PARAM_INT32,    "tiling", "If set, the pattern generated will tile" },
    { PARAM_INT32,    "perturb", "If set, the pattern is a little more distorted..." },
    { PARAM_INT32,    "colors", "where to take the colors (0= B&W,  1= fg/bg, 2= col1/col2)"},
    { PARAM_COLOR,    "col1", "fist color (sometimes unused)" },
    { PARAM_COLOR,    "col2", "second color (sometimes unused)" },
    { PARAM_FLOAT,    "alpha1", "alpha for the first color (used if the drawable has an alpha chanel)" },
    { PARAM_FLOAT,    "alpha2", "alpha for the second color (used if the drawable has an alpha chanel)" },
    { PARAM_INT32,    "blend", "0= linear, 1= bilinear, 2= sinusoidal" },
    { PARAM_FLOAT,    "blend_power", "Power used to strech the blend" }
  };

  static GParamDef *return_vals = NULL;
  static gint nargs = sizeof (args) / sizeof (args[0]);
  static gint nreturn_vals = 0;

  INIT_I18N ();

  gimp_install_procedure ("plug_in_sinus",
			  "Generates a texture with sinus functions",
			  "FIX ME: sinus help",
			  "Xavier Bouchoux",
			  "Xavier Bouchoux",
			  "1997",
			  N_("<Image>/Filters/Render/Sinus..."),
			  "RGB*, GRAY*",
			  PROC_PLUG_IN,
			  nargs, nreturn_vals,
			  args, return_vals);
}

static void 
run (gchar   *name,
     gint     nparams,
     GParam  *param,
     gint    *nreturn_vals,
     GParam **return_vals)
{
  static GParam values[1];
  GRunModeType run_mode;
  GStatusType status = STATUS_SUCCESS;

  run_mode = param[0].data.d_int32;

  *nreturn_vals = 1;
  *return_vals = values;

  INIT_I18N_UI();

  values[0].type = PARAM_STATUS;
  values[0].data.d_status = status;

  switch (run_mode)
    {
    case RUN_INTERACTIVE:
      /*  Possibly retrieve data  */
      gimp_get_data ("plug_in_sinus", &svals);

      /* In order to prepare the dialog I need to know wether it's grayscale or not */
      drawable = gimp_drawable_get (param[2].data.d_drawable);
      thePreview = mw_preview_build_virgin(drawable);
      if (gimp_drawable_is_gray (drawable->id))
	drawable_is_grayscale = TRUE;
      else
	drawable_is_grayscale = FALSE;

      if (!sinus_dialog())
        return;

      break;

    case RUN_NONINTERACTIVE:
      /*  Make sure all the arguments are there!  */
      if (nparams != 16)
	status = STATUS_CALLING_ERROR;
      if (status == STATUS_SUCCESS)
	{
	  svals.scalex       = param[3].data.d_float;
	  svals.scaley       = param[4].data.d_float;
	  svals.cmplx        = param[5].data.d_float;
	  svals.seed         = param[6].data.d_int32;
	  svals.tiling       = param[7].data.d_int32;
	  svals.perturbation = param[8].data.d_int32;
	  svals.colors       = param[9].data.d_int32;
	  svals.col1[0]      = param[10].data.d_color.red;
	  svals.col1[1]      = param[10].data.d_color.green;
	  svals.col1[2]      = param[10].data.d_color.blue;
	  svals.col2[0]      = param[11].data.d_color.red;
	  svals.col2[1]      = param[11].data.d_color.green;
	  svals.col2[2]      = param[11].data.d_color.blue;
	  svals.col1[3]      = param[12].data.d_float * 255.0;
	  svals.col2[3]      = param[13].data.d_float * 255.0;
	  svals.colorization = param[14].data.d_int32;
	  svals.blend_power  = param[15].data.d_float;
	}
      break;

    case RUN_WITH_LAST_VALS:
      /*  Possibly retrieve data  */
      gimp_get_data ("plug_in_sinus", &svals);
      break;

    default:
      break;
    }

  /*  Get the specified drawable  */
  drawable = gimp_drawable_get (param[2].data.d_drawable);

  /*  Make sure that the drawable is gray or RGB */
  if ((status == STATUS_SUCCESS) &&
      (gimp_drawable_is_rgb (drawable->id) ||
       gimp_drawable_is_gray (drawable->id)))
    {
      gimp_progress_init ("Calculating picture...");
      gimp_tile_cache_ntiles (1);
      sinus ();

      if (run_mode != RUN_NONINTERACTIVE)
	gimp_displays_flush ();

      /*  Store data  */
      if (run_mode == RUN_INTERACTIVE)
        gimp_set_data ("plug_in_sinus", &svals, sizeof (SinusVals));
    }
  else
    {
      status = STATUS_EXECUTION_ERROR;
    }

  values[0].data.d_status = status;

  gimp_drawable_detach (drawable);
}

/*
 *  Main procedure
 */

static void 
prepare_coef (params *p)
{
  typedef struct { guchar r, g, b, a; } type_color;
  type_color col1,col2;
  double scalex = svals.scalex;
  double scaley = svals.scaley;

  srand(svals.seed);
  switch (svals.colorization)
    {
    case BILINEAR:
      p->blend = bilinear;
      break;
    case SINUS:
      p->blend = cosinus;
      break;
    case LINEAR:
    default:
      p->blend = linear;
    }

  if (svals.perturbation==IDEAL)
    {
      p->c11= 0*rand();
      p->c12= (2.0*rand()/(G_MAXRAND+1.0)-1)*scaley; /*rand+rand is used to keep */
      p->c13= (2*G_PI*rand())/G_MAXRAND;
      p->c21= 0*rand();
      p->c22= (2.0*rand()/(G_MAXRAND+1.0)-1)*scaley; /*correspondance beetween Ideal*/
      p->c23= (2*G_PI*rand())/G_MAXRAND;
      p->c31= (2.0*rand()/(G_MAXRAND+1.0)-1)*scalex; /*and perturbed coefs (I hope...)*/
      p->c32= 0*rand();
      p->c33= (2*G_PI*rand())/G_MAXRAND;
    }
  else
    {
      p->c11= (2.0*rand()/(G_MAXRAND+1.0)-1)*scalex;
      p->c12= (2.0*rand()/(G_MAXRAND+1.0)-1)*scaley;
      p->c13= (2*G_PI*rand())/G_MAXRAND;
      p->c21= (2.0*rand()/(G_MAXRAND+1.0)-1)*scalex;
      p->c22= (2.0*rand()/(G_MAXRAND+1.0)-1)*scaley;
      p->c23= (2*G_PI*rand())/G_MAXRAND;
      p->c31= (2.0*rand()/(G_MAXRAND+1.0)-1)*scalex;
      p->c32= (2.0*rand()/(G_MAXRAND+1.0)-1)*scaley;
      p->c33= (2*G_PI*rand())/G_MAXRAND;
    }

  if (svals.tiling)
    {
      p->c11= ROUND (p->c11/(2*G_PI))*2*G_PI;
      p->c12= ROUND (p->c12/(2*G_PI))*2*G_PI;
      p->c21= ROUND (p->c21/(2*G_PI))*2*G_PI;
      p->c22= ROUND (p->c22/(2*G_PI))*2*G_PI;
      p->c31= ROUND (p->c31/(2*G_PI))*2*G_PI;
      p->c32= ROUND (p->c32/(2*G_PI))*2*G_PI;
    }

  col2.a=svals.col2[3];
  col1.a=svals.col1[3];

  if (drawable_is_grayscale)
    {
      col1.r=col1.g=col1.b=255;
      col2.r=col2.g=col2.b=0;
    }
  else
    {
      switch (svals.colors)
	{
	case USE_COLORS:
	  col1.r=svals.col1[0];
	  col1.g=svals.col1[1];
	  col1.b=svals.col1[2];
	  col2.r=svals.col2[0];
	  col2.g=svals.col2[1];
	  col2.b=svals.col2[2];
	  break;
	case B_W:
	  col1.r=col1.g=col1.b=255;
	  col2.r=col2.g=col2.b=0;
	  break;
	case USE_FG_BG:
	  gimp_palette_get_foreground(&col2.r, &col2.g, &col2.b);
	  gimp_palette_get_background(&col1.r, &col1.g, &col1.b);
	  break;
	}
    }
  p->r = col1.r;
  p->g = col1.g;
  p->b = col1.b;
  p->a = col1.a;
  p->dr=(int)col2.r-col1.r;
  p->dg=(int)col2.g-col1.g;
  p->db=(int)col2.b-col1.b;
  p->da=(int)col2.a-col1.a;
}

static void
sinus (void)
{
  params  p;
  gint    bytes;
  GPixelRgn dest_rgn;
  int     ix1, iy1, ix2, iy2;     /* Selected image size. */
  gpointer pr;
  gint progress, max_progress;
  
  prepare_coef(&p);
  
  gimp_drawable_mask_bounds(drawable->id, &ix1, &iy1, &ix2, &iy2);
  
  p.width = drawable->width;
  p.height = drawable->height;
  bytes = drawable->bpp;
  
  gimp_pixel_rgn_init(&dest_rgn, drawable, ix1, iy1, ix2-ix1, iy2-iy1, TRUE,TRUE);
  progress = 0;
  max_progress = (ix2-ix1)*(iy2-iy1);
  
  for (pr = gimp_pixel_rgns_register (1, &dest_rgn);
       pr != NULL;
       pr = gimp_pixel_rgns_process (pr))
    {
      switch (bytes)
	{
	case 4:
	  compute_block_4 (dest_rgn.data, dest_rgn.rowstride,
			   dest_rgn.x, dest_rgn.y, dest_rgn.w, dest_rgn.h, &p);
	  break;
	case 3:
	  compute_block_3 (dest_rgn.data, dest_rgn.rowstride,
			   dest_rgn.x, dest_rgn.y, dest_rgn.w, dest_rgn.h, &p);
	  break;
	case 2:
	  compute_block_2 (dest_rgn.data, dest_rgn.rowstride,
			   dest_rgn.x, dest_rgn.y, dest_rgn.w, dest_rgn.h, &p);
	  break;
	case 1:
	  compute_block_1 (dest_rgn.data, dest_rgn.rowstride,
			   dest_rgn.x, dest_rgn.y, dest_rgn.w, dest_rgn.h, &p);
	  break;
	}
      progress += dest_rgn.w * dest_rgn.h;
      gimp_progress_update ((double) progress / (double) max_progress);
    }

  gimp_drawable_flush (drawable);
  gimp_drawable_merge_shadow (drawable->id, TRUE);
  gimp_drawable_update (drawable->id, ix1, iy1, (ix2-ix1), (iy2-iy1));
}

static gdouble 
linear (gdouble v)
{
  register double a = v - (int) v;

  return (a < 0 ? 1.0 + a : a);
}

static gdouble 
bilinear (gdouble v)
{
  register double a = v - (int) v;

  a = (a < 0 ? 1.0 + a : a);
  return (a > 0.5 ? 2 - 2 * a : 2 * a);
}

static gdouble 
cosinus (gdouble v)
{
  return 0.5 - 0.5 * sin ((v + 0.25) * G_PI * 2);
}

static inline void 
compute_block_4 (guchar *dest_row, 
		 guint   rowstride,
		 gint    x0,
		 gint    y0,
		 gint    w,
		 gint    h, 
		 params *p)
{
  gint i,j;
  double x,y, grey;
  guchar *dest;

  for (j = y0; j < (y0 + h); j++)
    {
      y=((double)j)/p->height;
      dest = dest_row;
      for (i= x0; i<(x0+w); i++)
	{
	  x=((double)i)/p->width;

	  grey = sin(p->c11*x + p->c12*y + p->c13) * (0.5+0.5*sin(p->c31*x + p->c32*y +p->c33)) \
	    + sin(p->c21*x + p->c22*y + p->c23) * (0.5-0.5*sin(p->c31*x + p->c32*y +p->c33));
	  grey=pow(p->blend(svals.cmplx*(0.5+0.5*grey)),exp(svals.blend_power));

	  *dest++= p->r+(int)(grey*p->dr);
	  *dest++= p->g+(int)(grey*p->dg);
	  *dest++= p->b+(int)(grey*p->db);
	  *dest++= p->a+(int)(grey*p->da);
	}
      dest_row += rowstride;
    }
}

static inline void 
compute_block_3 (guchar *dest_row, 
		 guint   rowstride,
		 gint    x0,
		 gint    y0,
		 gint    w,
		 gint    h, 
		 params *p)
{
  gint i,j;
  double x,y, grey;
  guchar *dest;

  for (j=y0; j<(y0+h); j++)
    {
      y=((double)j)/p->height;
      dest = dest_row;
      for (i= x0; i<(x0+w); i++)
	{
	  x=((double)i)/p->width;

	  grey = sin(p->c11*x + p->c12*y + p->c13) * (0.5+0.5*sin(p->c31*x + p->c32*y +p->c33)) \
	    + sin(p->c21*x + p->c22*y + p->c23) * (0.5-0.5*sin(p->c31*x + p->c32*y +p->c33));
	  grey=pow(p->blend(svals.cmplx*(0.5+0.5*grey)),exp(svals.blend_power));

	  *dest++= p->r+(int)(grey*p->dr);
	  *dest++= p->g+(int)(grey*p->dg);
	  *dest++= p->b+(int)(grey*p->db);
	}
      dest_row += rowstride;
    }
}

static inline void 
compute_block_2 (guchar *dest_row, 
		 guint   rowstride,
		 gint    x0,
		 gint    y0,
		 gint    w,
		 gint    h, 
		 params *p)
{
  gint i,j;
  double x,y, grey;
  guchar *dest;

  for (j=y0; j<(y0+h); j++)
    {
      y=((double)j)/p->height;
      dest = dest_row;
      for (i= x0; i<(x0+w); i++)
	{
	  x=((double)i)/p->width;

	  grey = sin(p->c11*x + p->c12*y + p->c13) * (0.5+0.5*sin(p->c31*x + p->c32*y +p->c33)) \
	    + sin(p->c21*x + p->c22*y + p->c23) * (0.5-0.5*sin(p->c31*x + p->c32*y +p->c33));
	  grey=pow(p->blend(svals.cmplx*(0.5+0.5*grey)),exp(svals.blend_power));

	  *dest++= (guchar)(grey*255.0);
	  *dest++= p->a+(int)(grey*p->da);
	}
      dest_row += rowstride;
    }
}

static inline void 
compute_block_1 (guchar *dest_row, 
		 guint   rowstride,
		 gint    x0,
		 gint    y0,
		 gint    w,
		 gint    h, 
		 params *p)
{
  gint i,j;
  double x,y, grey;
  guchar *dest;

  for (j=y0; j<(y0+h); j++)
    {
      y=((double)j)/p->height;
      dest = dest_row;
      for (i= x0; i<(x0+w); i++)
	{
	  x=((double)i)/p->width;

	  grey = sin(p->c11*x + p->c12*y + p->c13) * (0.5+0.5*sin(p->c31*x + p->c32*y +p->c33)) \
	    + sin(p->c21*x + p->c22*y + p->c23) * (0.5-0.5*sin(p->c31*x + p->c32*y +p->c33));
	  grey=pow(p->blend(svals.cmplx*(0.5+0.5*grey)),exp(svals.blend_power));

	  *dest++= (guchar)(grey*255.0);
	}
      dest_row += rowstride;
    }
}

/* ---------------------------------------------------------------*/
/*  -------------------------- UI ------------------------------- */
/* -------------------------------------------------------------- */

static void
alpha_scale_cb (GtkAdjustment *adj,
		gpointer       data)
{
  guchar *val;
  GtkWidget *color_button;

  val = (guchar*) data;

  *val = (guchar)(adj->value * 255.0);

  color_button = gtk_object_get_user_data (GTK_OBJECT (adj));
  if (GIMP_IS_COLOR_BUTTON (color_button))
    gimp_color_button_update (GIMP_COLOR_BUTTON (color_button));
}

static void
alpha_scale_update (GtkWidget *color_button,
		    gpointer   data)
{
  guchar *val;
  GtkWidget *adj;

  val = (guchar*)data;

  adj = gtk_object_get_user_data (GTK_OBJECT (color_button));
  gtk_signal_handler_block_by_data (GTK_OBJECT (adj), data);
  gtk_adjustment_set_value (GTK_ADJUSTMENT (adj), (gfloat)(*val) / 255.0);
  gtk_signal_handler_unblock_by_data (GTK_OBJECT (adj), data);

  if (do_preview)
    sinus_do_preview (NULL);
}

gboolean run_flag = FALSE;

static void
sinus_ok_callback (GtkWidget *widget,
		   gpointer   data)
{
  run_flag = TRUE;

  gtk_widget_destroy (GTK_WIDGET (data));
}

static void
sinus_toggle_button_update (GtkWidget *widget,
			    gpointer   data)
{
  gimp_toggle_button_update (widget, data);

  if (do_preview)
    sinus_do_preview (NULL);
}

static void
sinus_radio_button_update (GtkWidget *widget,
			   gpointer   data)
{
  gimp_radio_button_update (widget, data);

  if (do_preview)
    sinus_do_preview (NULL);
}

static void
sinus_int_adjustment_update (GtkAdjustment *adjustment,
			     gpointer       data)
{
  gimp_int_adjustment_update (adjustment, data);

  if (do_preview)
    sinus_do_preview (NULL);
}

static void
sinus_double_adjustment_update (GtkAdjustment *adjustment,
				gpointer       data)
{
  gimp_double_adjustment_update (adjustment, data);

  if (do_preview)
    sinus_do_preview (NULL);
}

/*****************************************/
/* The note book                         */
/*****************************************/

gint
sinus_dialog (void)
{
  GtkWidget *dlg;
  GtkWidget *main_hbox;
  GtkWidget *preview;
  GtkWidget *notebook;
  GtkWidget *page;
  GtkWidget *frame;
  GtkWidget *label;
  GtkWidget *vbox;
  GtkWidget *vbox2;
  GtkWidget *hbox;
  GtkWidget *table;
  GtkWidget *toggle;
  GtkWidget *push_col1 = NULL;
  GtkWidget *push_col2 = NULL;
  GtkWidget *spinbutton;
  GtkObject *adj;
#ifdef USE_LOGO
  GtkWidget *logo;
  gint x,y;
  char buf[3*100];
  guchar *data;
#endif
  gchar **argv;
  gint argc;

  argc    = 1;
  argv    = g_new (gchar *, 1);
  argv[0] = g_strdup ("sinus");

  gtk_init (&argc, &argv);
  gtk_rc_parse (gimp_gtkrc ());

  /* Create Main window with a vbox */
  /* ============================== */
  dlg = gimp_dialog_new (_("Sinus"), "sinus",
			 gimp_plugin_help_func, "filters/sinus.html",
			 GTK_WIN_POS_MOUSE,
			 FALSE, TRUE, FALSE,

			 _("OK"), sinus_ok_callback,
			 NULL, NULL, NULL, TRUE, FALSE,
			 _("Cancel"), gtk_widget_destroy,
			 NULL, 1, NULL, FALSE, TRUE,

			 NULL);

  gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
		      GTK_SIGNAL_FUNC (gtk_main_quit),
		      NULL);

  main_hbox = gtk_hbox_new (FALSE, 6);
  gtk_container_set_border_width (GTK_CONTAINER (main_hbox), 6);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), main_hbox,
		      TRUE, TRUE, 0);
  gtk_widget_show (main_hbox);

  /* Create preview */
  /* ============== */
  vbox = gtk_vbox_new (TRUE, 4);
  gtk_box_pack_start (GTK_BOX (main_hbox), vbox, FALSE, FALSE, 0);
  gtk_widget_show (vbox);

  preview = mw_preview_new (vbox, thePreview);
  sinus_do_preview (preview);

#ifdef USE_LOGO
  logo = gtk_preview_new (GTK_PREVIEW_COLOR);
  gtk_preview_size (GTK_PREVIEW(logo), 100, 100);
  gtk_box_pack_start (GTK_BOX (vbox), logo, TRUE, FALSE, 0);
  gtk_widget_show (logo);

  data = (guchar *) logo_data;
  for (y = 0; y < 100; y++)
    {
      for (x=0; x<100; x++)
	{
	  HEADER_PIXEL (data, (&buf[3 * x]));
	}
      gtk_preview_draw_row (GTK_PREVIEW(logo), (guchar *) buf, 0, y, 100);
    }
#endif

  /* Create the notebook */
  /* =================== */
  notebook = gtk_notebook_new ();
  gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP);
  gtk_box_pack_start (GTK_BOX (main_hbox), notebook, FALSE, FALSE, 0);
  gtk_widget_show (notebook);

  /* Create the drawing settings frame */
  /* ================================= */
  page = gtk_vbox_new (FALSE, 5);
  gtk_container_set_border_width (GTK_CONTAINER (page), 4);

  frame = gtk_frame_new (_("Drawing Settings"));
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start (GTK_BOX (page), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  table = gtk_table_new(3, 3, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 4);
  gtk_table_set_row_spacings (GTK_TABLE (table), 2);
  gtk_container_set_border_width (GTK_CONTAINER (table), 4);
  gtk_container_add (GTK_CONTAINER(frame), table);

  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
			      _("X Scale:"), 140, 0,
			      svals.scalex, 0.0001, 100.0, 0.0001, 5, 4,
			      NULL, NULL);
  gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
		      GTK_SIGNAL_FUNC (sinus_double_adjustment_update),
		      &svals.scalex);
  
  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1,
			      _("Y Scale:"), 140, 0,
			      svals.scaley, 0.0001, 100.0, 0.0001, 5, 4,
			      NULL, NULL);
  gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
		      GTK_SIGNAL_FUNC (sinus_double_adjustment_update),
		      &svals.scaley);
  
  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 2,
			      _("Complexity:"), 140, 0,
			      svals.cmplx, 0.0, 15.0, 0.01, 5, 2,
			      NULL, NULL);
  gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
		      GTK_SIGNAL_FUNC (sinus_double_adjustment_update),
		      &svals.cmplx);

  gtk_widget_show (table);

  frame= gtk_frame_new (_("Calculation Settings"));
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start (GTK_BOX (page), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  vbox = gtk_vbox_new (FALSE, 4);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 4);
  gtk_container_add (GTK_CONTAINER (frame), vbox);
  gtk_widget_show (vbox);

  hbox = gtk_hbox_new (FALSE, 4);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  label = gtk_label_new (_("Random Seed:"));
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
  gtk_widget_show (label);

  spinbutton = gimp_spin_button_new (&adj, svals.seed,
				     -10000000000, 1000000000, 1, 10,
				     0, 0, 0);
  gtk_widget_set_usize (spinbutton, 100, -1);
  gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0);
  gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
		      GTK_SIGNAL_FUNC (sinus_int_adjustment_update),
		      &svals.seed);
  gtk_widget_show (spinbutton);

  toggle = gtk_check_button_new_with_label (_("Force Tiling?"));
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), svals.tiling);
  gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
  gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
		      GTK_SIGNAL_FUNC (sinus_toggle_button_update),
		      &svals.tiling);
  gtk_widget_show (toggle);

  vbox2 =
    gimp_radio_group_new2 (FALSE, NULL,
			   sinus_radio_button_update,
			   &svals.perturbation, (gpointer) svals.perturbation,

			   _("Ideal"),     (gpointer) IDEAL, NULL,
			   _("Distorted"), (gpointer) PERTURBED, NULL,

			   NULL);

  gtk_container_set_border_width (GTK_CONTAINER (vbox2), 0);
  gtk_box_pack_start (GTK_BOX (vbox), vbox2, FALSE, FALSE, 0);
  gtk_widget_show (vbox2);

  label = gtk_label_new (_("Settings"));
  gtk_misc_set_alignment (GTK_MISC (label), 0.5, 0.5);
  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), page, label);
  gtk_widget_show (page);

  /* Color settings dialog: */
  /* ====================== */
  page = gtk_vbox_new (FALSE, 4);
  gtk_container_set_border_width (GTK_CONTAINER (page), 4);

  if (drawable_is_grayscale) 
    {
      frame = gtk_frame_new (_("Colors"));
      gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
      gtk_box_pack_start(GTK_BOX(page), frame, FALSE, FALSE, 0);
      gtk_widget_show (frame);

      vbox = gtk_vbox_new (FALSE, 2);
      gtk_container_set_border_width (GTK_CONTAINER (vbox), 4);
      gtk_container_add (GTK_CONTAINER (frame), vbox);
      gtk_widget_show (vbox);

      /*if in grey scale, the colors are necessarily black and white */
      label = gtk_label_new (_("The colors are white and black."));
      gtk_misc_set_alignment (GTK_MISC (label), 0.5, 0.5);
      gtk_container_add (GTK_CONTAINER (vbox), label);
      gtk_widget_show (label);
    } 
  else
    {
      frame = gimp_radio_group_new2 (TRUE, _("Colors"),
				     sinus_radio_button_update,
				     &svals.colors, (gpointer) svals.colors,

				     _("Black & White"),
				     (gpointer) B_W, NULL,
				     _("Foreground & Background"),
				     (gpointer) USE_FG_BG, NULL,
				     _("Choose here:"),
				     (gpointer) USE_COLORS, NULL,

				     NULL);

      gtk_box_pack_start(GTK_BOX(page), frame, FALSE, FALSE, 0);
      gtk_widget_show (frame);

      vbox = GTK_BIN (frame)->child;

      hbox = gtk_hbox_new (TRUE, 20);
      gtk_container_set_border_width (GTK_CONTAINER (hbox), 4);
      gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);

      push_col1 = gimp_color_button_new (_("First Color"),
					 32, 32, svals.col1, 4);
      gtk_box_pack_start (GTK_BOX (hbox), push_col1, FALSE, FALSE, 0);
      gtk_widget_show (push_col1);

      push_col2 = gimp_color_button_new (_("Second Color"),
					 32, 32, svals.col2, 4);
      gtk_box_pack_start (GTK_BOX (hbox), push_col2, FALSE, FALSE, 0);
      gtk_widget_show (push_col2);

      gtk_widget_show (hbox);
    }

  frame = gtk_frame_new (_("Alpha Channels"));
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start (GTK_BOX (page), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  table = gtk_table_new (2, 3, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 4);
  gtk_table_set_row_spacings (GTK_TABLE (table), 2);
  gtk_container_set_border_width (GTK_CONTAINER (table), 4);
  gtk_container_add (GTK_CONTAINER (frame), table);

  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
			      _("First Color:"), 0, 0,
			      svals.col1[3] / 255.0, 0.0, 1.0, 0.01, 0.1, 2,
			      NULL, NULL);

  gtk_object_set_user_data (GTK_OBJECT (adj), push_col1);      
  gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
                      GTK_SIGNAL_FUNC (alpha_scale_cb),
                      &svals.col1[3]);

  if (push_col1)
    {
      gtk_object_set_user_data (GTK_OBJECT (push_col1), adj);
      gtk_signal_connect (GTK_OBJECT (push_col1), "color_changed",
			  GTK_SIGNAL_FUNC (alpha_scale_update),
			  &svals.col1[3]);
    }

  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1,
			      _("Second Color:"), 0, 0,
			      svals.col2[3] / 255.0, 0.0, 1.0, 0.01, 0.1, 2,
			      NULL, NULL);

  gtk_object_set_user_data (GTK_OBJECT (adj), push_col2);
  gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
                      GTK_SIGNAL_FUNC (alpha_scale_cb),
                      &svals.col2[3]);

  if (push_col2)
    {
      gtk_object_set_user_data (GTK_OBJECT (push_col2), adj);
      gtk_signal_connect (GTK_OBJECT (push_col2), "color_changed",
			  GTK_SIGNAL_FUNC (alpha_scale_update),
			  &svals.col2[3]);
    }

  gtk_widget_show (table);

  label = gtk_label_new (_("Colors"));
  gtk_misc_set_alignment (GTK_MISC (label), 0.5, 0.5);
  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), page, label);
  gtk_widget_show (page);

  /* blend settings dialog: */
  /* ====================== */
  page = gtk_vbox_new (FALSE, 4);
  gtk_container_set_border_width (GTK_CONTAINER (page), 4);

  frame = gtk_frame_new (_("Blend Settings"));
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start (GTK_BOX (page), frame, TRUE, TRUE, 0);
  gtk_widget_show (frame);

  vbox = gtk_vbox_new (FALSE, 4);
  gtk_container_add (GTK_CONTAINER (frame), vbox);
  gtk_container_border_width (GTK_CONTAINER (vbox), 4);
  gtk_widget_show (vbox);

  frame =
    gimp_radio_group_new2 (TRUE, _("Gradient"),
			   sinus_radio_button_update,
			   &svals.colorization, (gpointer) svals.colorization,

			   _("Linear"),     (gpointer) LINEAR, NULL,
			   _("Bilinear"),   (gpointer) BILINEAR, NULL,
			   _("Sinusodial"), (gpointer) SINUS, NULL,

			   NULL);

  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  table = gtk_table_new (1, 3, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 4);
  gtk_container_add (GTK_CONTAINER (vbox), table);

  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
			      _("Exponent:"), 0, 0,
			      svals.blend_power, -7.5, 7.5, 0.01, 5.0, 2,
			      NULL, NULL);
  gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
		      GTK_SIGNAL_FUNC (sinus_double_adjustment_update),
		      &svals.blend_power);

  gtk_widget_show (table);

  label = gtk_label_new (_("Blend"));
  gtk_misc_set_alignment (GTK_MISC (label), 0.5, 0.5);
  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), page, label);
  gtk_widget_show (page);

  gtk_widget_show (dlg);

  gtk_main ();
  gdk_flush ();

  return run_flag;
}

/******************************************************************/
/* Draw preview image. if DoCompute is TRUE then recompute image. */
/******************************************************************/

void
sinus_do_preview (GtkWidget *widget)
{
  static GtkWidget *theWidget = NULL;
  gint y,rowsize;
  guchar *buf, *savbuf;
  params p;

  if (theWidget == NULL)
    {
      theWidget = widget;
    }

  rowsize = thePreview->width * thePreview->bpp;
  savbuf = buf = g_new (guchar, thePreview->width*thePreview->height*thePreview->bpp);
  if (buf != NULL)
    {
      p.height = thePreview->height;
      p.width = thePreview->width;
      prepare_coef (&p);

      if (thePreview->bpp == 3)
	compute_block_3 (buf, rowsize, 0, 0,
			 thePreview->width, thePreview->height, &p);
      else if (thePreview->bpp == 1)
	{
	  compute_block_1 (buf, rowsize, 0, 0,
			   thePreview->width, thePreview->height, &p);
	}
      else
	fprintf (stderr, "Uh Oh....  Little sinus preview-only problem...\n");

      for (y = 0; y < thePreview->height; y++)
	{
	  gtk_preview_draw_row (GTK_PREVIEW (theWidget),
				buf, 0, y, thePreview->width);
	  buf += rowsize;
	}
      g_free (savbuf);
      gtk_widget_draw (theWidget, NULL);
      gdk_flush ();
    }
  else
    {
      fprintf (stderr,"Not enough mem for sinus Preview...\n");
    }
}

static void
mw_preview_toggle_callback (GtkWidget *widget,
                            gpointer   data)
{
  gimp_toggle_button_update (widget, data);

  if (do_preview)
    sinus_do_preview (NULL);
}

static struct mwPreview *
mw_preview_build_virgin (GDrawable *drw)
{
  struct mwPreview *mwp;

  mwp = g_new (struct mwPreview, 1);

  if (drw->width > drw->height)
    {
      mwp->scale  = (gdouble) drw->width / (gdouble) PREVIEW_SIZE;
      mwp->width  = PREVIEW_SIZE;
      mwp->height = drw->height / mwp->scale;
    }
  else
    {
      mwp->scale  = (gdouble) drw->height / (gdouble) PREVIEW_SIZE;
      mwp->height = PREVIEW_SIZE;
      mwp->width  = drw->width / mwp->scale;
    }

  mwp->bpp  = 3;
  mwp->bits = NULL;

  return mwp;
}

static GtkWidget *
mw_preview_new (GtkWidget        *parent,
                struct mwPreview *mwp)
{
  GtkWidget *preview;
  GtkWidget *frame;
  GtkWidget *pframe;
  GtkWidget *vbox;
  GtkWidget *button;
  guchar *color_cube;
   
  gtk_preview_set_gamma (gimp_gamma ());
  gtk_preview_set_install_cmap (gimp_install_cmap ());
  color_cube = gimp_color_cube ();
  gtk_preview_set_color_cube (color_cube[0], color_cube[1],
                              color_cube[2], color_cube[3]);

  gtk_widget_set_default_visual (gtk_preview_get_visual ());
  gtk_widget_set_default_colormap (gtk_preview_get_cmap ());

  frame = gtk_frame_new (_("Preview"));
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start (GTK_BOX (parent), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  vbox = gtk_vbox_new (FALSE, 2);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 4);
  gtk_container_add (GTK_CONTAINER (frame), vbox);
  gtk_widget_show (vbox);

  pframe = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME(pframe), GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX (vbox), pframe, FALSE, FALSE, 0);
  gtk_widget_show (pframe);

  preview = gtk_preview_new (GTK_PREVIEW_COLOR);
  gtk_preview_size (GTK_PREVIEW (preview), mwp->width, mwp->height);
  gtk_container_add (GTK_CONTAINER (pframe), preview);
  gtk_widget_show (preview);

  button = gtk_check_button_new_with_label (_("Do Preview"));
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), do_preview);
  gtk_signal_connect (GTK_OBJECT (button), "toggled",
                      GTK_SIGNAL_FUNC (mw_preview_toggle_callback),
                      &do_preview);
  gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
  gtk_widget_show (button);

  return preview;
}

