/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * IfsCompose is a interface for creating IFS fractals by
 * direct manipulation.
 * Copyright (C) 1997 Owen Taylor
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* TODO
 * ----
 *
 * 1. Run in non-interactive mode (need to figure out useful
 *    way for a script to give the 19N paramters for an image).
 *    Perhaps just support saving parameters to a file, script
 *    passes file name.
 * 2. Save settings on a per-layer basis (long term, needs GIMP
 *    support to do properly). Load/save from affine parameters?
 * 3. Figure out if we need multiple phases for supersampled
 *    brushes.
 * 4. (minor) Make undo work correctly when focus is in entry widget.
 */

#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "gtk/gtk.h"
#include "libgimp/gimp.h"
#include "ifscompose.h"

#ifndef M_PI
#define M_PI    3.14159265358979323846
#endif /* M_PI */

#define SCALE_WIDTH     150
#define ENTRY_WIDTH 60
#define DESIGN_AREA_MAX_SIZE 256

#define PREVIEW_RENDER_CHUNK 10000

#define UNDO_LEVELS 10

typedef enum {
  OP_TRANSLATE,
  OP_ROTATE,			/* or scale */
  OP_STRETCH
} DesignOp;

typedef enum {
  VALUE_PAIR_INT,
  VALUE_PAIR_DOUBLE,
} ValuePairType;

typedef struct
{
  GtkObject *adjustment;
  GtkWidget *scale;
  GtkWidget *entry;

  ValuePairType type;

  union {
    gdouble *d;
    gint    *i;
  } data;

  gint entry_handler_id;
} ValuePair;

typedef struct
{
  IfsComposeVals ifsvals;
  AffElement **elements;
  gint *element_selected;
  gint current_element;
} UndoItem;

typedef struct
{
  IfsColor *color;
  gchar *name;
  GtkWidget *hbox;
  GtkWidget *orig_preview;
  GtkWidget *preview;
  GtkWidget *dialog;
  gint fixed_point;

  gint in_change_callback;
} ColorMap;

typedef struct
{
  GtkWidget *dialog;

  ValuePair *iterations_pair;
  ValuePair *subdivide_pair;
  ValuePair *radius_pair;
  ValuePair *memory_pair;
} IfsOptionsDialog;

typedef struct
{
  GtkWidget *area;
  GtkWidget *op_menu;
  GdkPixmap *pixmap;

  DesignOp op;
  gdouble op_x;
  gdouble op_y;
  gdouble op_xcenter;
  gdouble op_ycenter;
  gdouble op_center_x;
  gdouble op_center_y;
  guint button_state;
  gint num_selected;

  GdkGC *selected_gc;
} IfsDesignArea;

typedef struct
{
  ValuePair *prob_pair;
  ValuePair *x_pair;
  ValuePair *y_pair;
  ValuePair *scale_pair;
  ValuePair *angle_pair;
  ValuePair *asym_pair;
  ValuePair *shear_pair;
  GtkWidget *flip_check_button;

  ColorMap  *red_cmap;
  ColorMap  *green_cmap;
  ColorMap  *blue_cmap;
  ColorMap  *black_cmap;
  ColorMap  *target_cmap;
  ValuePair *hue_scale_pair;
  ValuePair *value_scale_pair;
  GtkWidget *simple_button;
  GtkWidget *full_button;
  GtkWidget *current_frame;

  GtkWidget *move_button;
  gint move_handler;
  GtkWidget *rotate_button;
  gint rotate_handler;
  GtkWidget *stretch_button;
  gint stretch_handler;

  GtkWidget *preview;
  guchar *preview_data;
  gint preview_iterations;

  gint drawable_width,drawable_height;

  AffElement *selected_orig;
  gint current_element;
  AffElementVals current_vals;
  gint auto_preview;

  gint in_update;		/* true if we're currently in
				   update_values() - don't do anything
				   on updates */
} IfsDialog;

typedef struct
{
  gint       run;
} IfsComposeInterface;

/* Declare local functions.
 */
static void      query  (void);
static void      run    (char      *name,
			 int        nparams,
			 GParam    *param,
			 int       *nreturn_vals,
			 GParam   **return_vals);

/*  user interface functions  */
static gint      ifs_compose_dialog     (GDrawable *drawable);
static void      ifs_options_dialog      ();
static GtkWidget *ifs_compose_trans_page ();
static GtkWidget *ifs_compose_color_page ();
static void design_op_menu_popup         (gint button, guint32 activate_time);
static void design_op_menu_create        (GtkWidget *window);
static void design_area_create(GtkWidget *window,gint design_width,
			       gint design_height);

/* functions for drawing design window */
static void update_values();
static void set_current_element(gint index);
static gint design_area_expose(GtkWidget *widget,GdkEventExpose *event);
static gint design_area_button_press(GtkWidget *widget,
				     GdkEventButton *event);
static gint design_area_button_release(GtkWidget *widget,
				       GdkEventButton *event);
static void design_area_select_all_callback(GtkWidget *w, gpointer data);
static gint design_area_configure(GtkWidget *widget,
				  GdkEventConfigure *event);
static gint design_area_motion(GtkWidget *widget, GdkEventMotion *event);
static void design_area_redraw(void);

/* Undo ring functions */
static void undo_begin(void);
static void undo_update(gint element);
static void undo_exchange(gint el);
static void undo(void);
static void redo(void);

static void recompute_center(int save_undo);
static void recompute_center_cb(GtkWidget *, gpointer);

static void ifs_compose(GDrawable *drawable);

static void color_map_set_preview_color(GtkWidget *preview,
					IfsColor *color);
static ColorMap *color_map_create(gchar *name,IfsColor *orig_color,
				  IfsColor *data, gint fixed_point);
static void color_map_clicked_callback(GtkWidget *widget,ColorMap *colormap);
static void color_map_destroy_callback(GtkWidget *widget,ColorMap *colormap);
static void color_map_color_changed_cb(GtkWidget *widget,
				       ColorMap *color_map);
static void color_map_update(ColorMap *color_map);

/* interface functions */
static void simple_color_toggled(GtkWidget *widget,gpointer data);
static void simple_color_set_sensitive();
static void val_changed_update ();
static ValuePair *value_pair_create (gpointer data, gdouble lower, gdouble upper,
	      gboolean create_scale, ValuePairType type);
static void value_pair_update(ValuePair *value_pair);
static void value_pair_entry_callback (GtkWidget   *w,
				       ValuePair   *value_pair);
static void value_pair_destroy_callback (GtkWidget   *widget,
					 ValuePair   *value_pair);
static void value_pair_button_release (GtkWidget *widget,
				       GdkEventButton *event,
				       gpointer data);
static void value_pair_scale_callback   (GtkAdjustment *adjustment,
					 ValuePair *value_pair);

static void auto_preview_callback (GtkWidget *widget, gpointer data);
static void design_op_callback (GtkWidget *widget, gpointer data);
static void design_op_update_callback (GtkWidget *widget, gpointer data);
static void flip_check_button_callback (GtkWidget *widget, gpointer data);
static gint preview_idle_render();

static void ifs_options_close_callback ();
static void ifs_compose_set_defaults ();
static void ifs_compose_defaults_callback (GtkWidget *widget,
					   gpointer   data);
static void ifs_compose_new_callback (GtkWidget *widget,
				      gpointer   data);
static void ifs_compose_delete_callback (GtkWidget *widget,
					 gpointer   data);
static void ifs_compose_preview_callback (GtkWidget *widget,
					  GtkWidget *preview);

static void ifs_compose_close_callback (GtkWidget *widget,
					GtkWidget **destroyed_widget);
static void ifs_compose_ok_callback (GtkWidget *widget,
				     GtkWidget *window);


/*
 *  Some static variables
 */

IfsDialog *ifsD = 0;
IfsOptionsDialog *ifsOptD = 0;
IfsDesignArea *ifsDesign = 0;

static AffElement **elements = 0;
static gint *element_selected = 0;
static gint element_count = 0;

static UndoItem undo_ring[UNDO_LEVELS];
static gint undo_cur = -1;
static gint undo_num = 0;
static gint undo_start = 0;

/* num_elements = 0, signals not inited */
static IfsComposeVals ifsvals =
{
  0,				/* num_elements */
  50000,			/* iterations */
  4096,				/* max_memory */
  4,				/* subdivide */
  0.75,				/* radius */
  1.0,				/* aspect ratio */
  0.5,				/* center_x */
  0.5,				/* center_y */
};

static
 IfsComposeInterface ifscint =
{
  FALSE,        /* run */
};

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


MAIN ()

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

  static GParamDef *return_vals = NULL;

  static int nargs = sizeof (args) / sizeof (args[0]);
  static int nreturn_vals = 0;

  gimp_install_procedure ("plug_in_ifs_compose",
			  "Create an Iterated Function System Fractal",
   "Interactively create an Iterated Function System fractal."
   "Use the window on the upper left to adjust the component"
   "transformations of the fractal. The operation that is performed"
   "is selected by the buttons underneath the window, or from a"
   "menu popped up by the right mouse button. The fractal will be"
   "rendered with a transparent background if the current image has"
   "a transparent background.",
			  "Owen Taylor",
			  "Owen Taylor",
			  "1997",
			  "<Image>/Filters/Render/IfsCompose",
			  "RGB*, GRAY*",
			  PROC_PLUG_IN,
			  nargs, nreturn_vals,
			  args, return_vals);
}

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

  run_mode = param[0].data.d_int32;

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

  *nreturn_vals = 1;
  *return_vals = values;

  /*  getchar(); */

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

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

      if (ifsvals.num_elements != 0)
        {
	  AffElementVals *element_vals = g_new(AffElementVals,
					       ifsvals.num_elements);
	  IfsColor color = {{0.0,0.0,0.0}};
	  int i;
	  elements = g_new(AffElement *,ifsvals.num_elements);
	  gimp_get_data ("plug_in_ifs_compose_elements", element_vals);

	  for (i=0;i<ifsvals.num_elements;i++)
	    {
	      elements[i] = aff_element_new(0.0,0.0,color,element_count++);
	      elements[i]->v = element_vals[i];
	    }
	  g_free(element_vals);
	}

      /*  First acquire information with a dialog  */
      if (! ifs_compose_dialog (active_drawable))
	return;
      break;

    case RUN_NONINTERACTIVE:
      /*  Make sure all the arguments are there!  */
      status = STATUS_CALLING_ERROR;
      break;

    case RUN_WITH_LAST_VALS:
      /*  Possibly retrieve data  */
      status = STATUS_CALLING_ERROR;
/*      gimp_get_data ("plug_in_ifs_compose", &mvals); */
      break;

    default:
      break;
    }

  /*  Render the fractal  */
  if ((status == STATUS_SUCCESS) &&
      (gimp_drawable_color (active_drawable->id) || gimp_drawable_gray (active_drawable->id)))
    {
      /*  set the tile cache size so that the gaussian blur works well  */
      gimp_tile_cache_ntiles (2 * (MAX (active_drawable->width, active_drawable->height) /
				   gimp_tile_width () + 1));

      /*  run the effect  */
      ifs_compose (active_drawable);

      /*  If the run mode is interactive, flush the displays  */
      if (run_mode != RUN_NONINTERACTIVE)
	gimp_displays_flush ();

      /*  Store data for next invocation */
     if (run_mode == RUN_INTERACTIVE)
       {
	 AffElementVals *element_vals = g_new(AffElementVals,
					      ifsvals.num_elements);
	 int i;

	 for (i=0;i<ifsvals.num_elements;i++)
	   element_vals[i] = elements[i]->v;

	 gimp_set_data ("plug_in_ifs_compose_vals", &ifsvals,
			sizeof (IfsComposeVals));
	 gimp_set_data ("plug_in_ifs_compose_elements", element_vals,
			sizeof (AffElementVals)*ifsvals.num_elements);

	 g_free(element_vals);
       }
    }
  else if (status == STATUS_SUCCESS)
    {
      /* gimp_message ("mosaic: cannot operate on indexed color images"); */
      status = STATUS_EXECUTION_ERROR;
    }

  values[0].data.d_status = status;

  gimp_drawable_detach (active_drawable);
}

static GtkWidget *
ifs_compose_trans_page ()
{
  GtkWidget *vbox;
  GtkWidget *table;
  GtkWidget *label;

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_border_width(GTK_CONTAINER(vbox), 4);

  table = gtk_table_new(3, 6, FALSE);
  gtk_table_set_row_spacings(GTK_TABLE(table),6);
  gtk_container_border_width(GTK_CONTAINER(table), 0);
  gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 0);
  gtk_widget_show(table);

  /* X */

  label = gtk_label_new("X");
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 1.0);
  gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, GTK_FILL,
		   4, 0);
  gtk_widget_show(label);

  ifsD->x_pair = value_pair_create(&ifsD->current_vals.x, 0.0, 1.0, FALSE,
				   VALUE_PAIR_DOUBLE);
  gtk_table_attach(GTK_TABLE(table), ifsD->x_pair->entry,1,2,0,1,
		   GTK_FILL,GTK_FILL,4,0);
  gtk_widget_show (ifsD->x_pair->entry);

  /* Y */

  label = gtk_label_new("Y");
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 1.0);
  gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
		   GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show(label);

  ifsD->y_pair = value_pair_create(&ifsD->current_vals.y, 0.0, 1.0, FALSE,
				   VALUE_PAIR_DOUBLE);
  gtk_table_attach(GTK_TABLE(table), ifsD->y_pair->entry,1,2,1,2,
		   GTK_FILL,GTK_FILL,4,0);
  gtk_widget_show (ifsD->y_pair->entry);

  /* Scale */

  label = gtk_label_new("Scale");
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 1.0);
  gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1,
		   GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show(label);

  ifsD->scale_pair = value_pair_create(&ifsD->current_vals.scale, 0.0,1.0, 
				       FALSE, VALUE_PAIR_DOUBLE);
  gtk_table_attach(GTK_TABLE(table), ifsD->scale_pair->entry,3,4,0,1,
		   GTK_FILL,GTK_FILL,4,0);
  gtk_widget_show (ifsD->scale_pair->entry);

  /* Angle */

  label = gtk_label_new("Angle");
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 1.0);
  gtk_table_attach(GTK_TABLE(table), label, 2, 3, 1, 2,
		   GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show(label);

  ifsD->angle_pair = value_pair_create(&ifsD->current_vals.theta,-180,180,
				       FALSE, VALUE_PAIR_DOUBLE);
  gtk_table_attach(GTK_TABLE(table), ifsD->angle_pair->entry,3,4,1,2,
		   GTK_FILL,GTK_FILL,4,0);
  gtk_widget_show (ifsD->angle_pair->entry);

  /* Asym */

  label = gtk_label_new("Asymmetry");
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 1.0);
  gtk_table_attach(GTK_TABLE(table), label, 4, 5, 0, 1,
		   GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show(label);

  ifsD->asym_pair = value_pair_create(&ifsD->current_vals.asym,0.10,10.0,
				      FALSE, VALUE_PAIR_DOUBLE);
  gtk_table_attach(GTK_TABLE(table), ifsD->asym_pair->entry,5,6,0,1,
		   GTK_FILL,GTK_FILL,4,0);
  gtk_widget_show (ifsD->asym_pair->entry);

  /* Shear */

  label = gtk_label_new("Shear");
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 1.0);
  gtk_table_attach(GTK_TABLE(table), label, 4, 5, 1, 2,
		   GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show(label);

  ifsD->shear_pair = value_pair_create(&ifsD->current_vals.shear,-10.0,10.0,
				       FALSE, VALUE_PAIR_DOUBLE);
  gtk_table_attach(GTK_TABLE(table), ifsD->shear_pair->entry,5,6,1,2,
		   GTK_FILL,GTK_FILL,4,0);
  gtk_widget_show (ifsD->shear_pair->entry);

  /* Flip */

  ifsD->flip_check_button = gtk_check_button_new_with_label("Flip");
  gtk_table_attach(GTK_TABLE(table), ifsD->flip_check_button,0,1,2,3,
		   GTK_FILL,GTK_FILL,4,0);
  gtk_signal_connect(GTK_OBJECT(ifsD->flip_check_button), "toggled",
		     (GtkSignalFunc)flip_check_button_callback,NULL);
  gtk_widget_show(ifsD->flip_check_button);

  return vbox;
}

static GtkWidget *
ifs_compose_color_page ()
{
  GtkWidget *vbox;
  GtkWidget *table;
  GtkWidget *label;
  GSList *group = NULL;
  IfsColor color;

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_border_width(GTK_CONTAINER(vbox), 4);

  table = gtk_table_new(3, 5, FALSE);
  gtk_table_set_row_spacings(GTK_TABLE(table),6);
  gtk_container_border_width(GTK_CONTAINER(table), 0);
  gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 0);
  gtk_widget_show(table);

  /* Simple color control section */

  ifsD->simple_button = gtk_radio_button_new_with_label (group, "Simple");
  gtk_table_attach(GTK_TABLE(table), ifsD->simple_button, 0, 1, 0, 2,
		   GTK_FILL, GTK_FILL, 4, 0);
  group = gtk_radio_button_group (GTK_RADIO_BUTTON (ifsD->simple_button));
  gtk_signal_connect (GTK_OBJECT (ifsD->simple_button), "toggled",
		      (GtkSignalFunc) simple_color_toggled, NULL);
  gtk_widget_show (ifsD->simple_button);

  color.vals[0] = 1.0;
  color.vals[1] = 0.0;
  color.vals[2] = 0.0;
  ifsD->target_cmap = color_map_create("IfsCompose: Target",NULL,
				       &ifsD->current_vals.target_color,TRUE);
  gtk_table_attach(GTK_TABLE(table), ifsD->target_cmap->hbox, 1, 2, 0, 2,
		   GTK_FILL, 0, 4, 0);
  gtk_widget_show(ifsD->target_cmap->hbox);

  label = gtk_label_new("Scale hue by:");
  gtk_misc_set_alignment(GTK_MISC(label),1.0,0.5);
  gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1,
		   GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show(label);

  ifsD->hue_scale_pair = value_pair_create(&ifsD->current_vals.hue_scale,
				       0.0,1.0, TRUE, VALUE_PAIR_DOUBLE);
  gtk_table_attach(GTK_TABLE(table), ifsD->hue_scale_pair->scale, 3, 4, 0, 1,
		   GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show (ifsD->hue_scale_pair->scale);
  gtk_table_attach(GTK_TABLE(table), ifsD->hue_scale_pair->entry, 4, 5, 0, 1,
		   GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show (ifsD->hue_scale_pair->entry);

  label = gtk_label_new("Scale value by:");
  gtk_misc_set_alignment(GTK_MISC(label),1.0,0.5);
  gtk_table_attach(GTK_TABLE(table), label, 2, 3, 1, 2,
		   GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show(label);

  ifsD->value_scale_pair = value_pair_create(&ifsD->current_vals.value_scale,
				       0.0,1.0, TRUE, VALUE_PAIR_DOUBLE);
  gtk_table_attach(GTK_TABLE(table), ifsD->value_scale_pair->scale,
		   3, 4, 1, 2, GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show (ifsD->value_scale_pair->scale);
  gtk_table_attach(GTK_TABLE(table), ifsD->value_scale_pair->entry,
		   4, 5, 1, 2, GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show (ifsD->value_scale_pair->entry);

  /* Full color control section */

  ifsD->full_button = gtk_radio_button_new_with_label (group, "Full");
  gtk_table_attach(GTK_TABLE(table), ifsD->full_button, 0, 1, 2, 3,
		   GTK_FILL, GTK_FILL, 4, 0);
  group = gtk_radio_button_group (GTK_RADIO_BUTTON (ifsD->full_button));
  gtk_widget_show (ifsD->full_button);

  color.vals[0] = 1.0;
  color.vals[1] = 0.0;
  color.vals[2] = 0.0;
  ifsD->red_cmap = color_map_create("IfsCompose: Red",&color,
				    &ifsD->current_vals.red_color,FALSE);
  gtk_table_attach(GTK_TABLE(table), ifsD->red_cmap->hbox, 1, 2, 2, 3,
		   GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show(ifsD->red_cmap->hbox);

  color.vals[0] = 0.0;
  color.vals[1] = 1.0;
  color.vals[2] = 0.0;
  ifsD->green_cmap = color_map_create("IfsCompose: Green",&color,
				    &ifsD->current_vals.green_color,FALSE);
  gtk_table_attach(GTK_TABLE(table), ifsD->green_cmap->hbox, 2, 3, 2, 3,
		   GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show(ifsD->green_cmap->hbox);

  color.vals[0] = 0.0;
  color.vals[1] = 0.0;
  color.vals[2] = 2.0;
  ifsD->blue_cmap = color_map_create("IfsCompose: Blue",&color,
				    &ifsD->current_vals.blue_color,FALSE);
  gtk_table_attach(GTK_TABLE(table), ifsD->blue_cmap->hbox, 3, 4, 2, 3,
		   GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show(ifsD->blue_cmap->hbox);

  color.vals[0] = 0.0;
  color.vals[1] = 0.0;
  color.vals[2] = 0.0;
  ifsD->black_cmap = color_map_create("IfsCompose: Black",&color,
				    &ifsD->current_vals.black_color,FALSE);
  gtk_table_attach(GTK_TABLE(table), ifsD->black_cmap->hbox, 4, 5, 2, 3,
		   GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show(ifsD->black_cmap->hbox);

  return vbox;
}

static gint
ifs_compose_dialog (GDrawable *drawable)
{
  GtkWidget *dlg;
  GtkWidget *label;
  GtkWidget *button;
  GtkWidget *check_button;
  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *util_hbox;
  GtkWidget *main_vbox;
  GtkWidget *alignment;
  GtkWidget *aspect_frame;
  GtkWidget *notebook;
  GtkWidget *page;
  guchar *color_cube;
  gchar **argv;
  gint argc;

  gint design_width, design_height;

  design_width = drawable->width;
  design_height = drawable->height;

  if (design_width > design_height)
    {
      if (design_width > DESIGN_AREA_MAX_SIZE)
	{
	  design_height = design_height * DESIGN_AREA_MAX_SIZE / design_width;
	  design_width = DESIGN_AREA_MAX_SIZE;
	}
    }
  else
    {
      if (design_height > DESIGN_AREA_MAX_SIZE)
	{
	  design_width = design_width * DESIGN_AREA_MAX_SIZE / design_height;
	  design_height = DESIGN_AREA_MAX_SIZE;
	}
    }

  ifsD = g_new(IfsDialog,1);
  ifsD->auto_preview = TRUE;
  ifsD->drawable_width = drawable->width;
  ifsD->drawable_height = drawable->height;

  ifsD->selected_orig = NULL;

  ifsD->preview_data = NULL;
  ifsD->preview_iterations = 0;

  ifsD->in_update = 0;

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

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

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

  dlg = gtk_dialog_new ();
  gtk_window_set_title (GTK_WINDOW (dlg), "IfsCompose");
  gtk_window_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE);
  gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
		      (GtkSignalFunc) ifs_compose_close_callback,
		      &dlg);

  /*  Action area  */

  button = gtk_button_new_with_label ("New");
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      (GtkSignalFunc) ifs_compose_new_callback,
                      dlg);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0);
  gtk_widget_show (button);

  button = gtk_button_new_with_label ("Delete");
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      (GtkSignalFunc) ifs_compose_delete_callback,
                      dlg);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0);
  gtk_widget_show (button);

  button = gtk_button_new_with_label ("Defaults");
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      GTK_SIGNAL_FUNC (ifs_compose_defaults_callback),
                      NULL);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0);
  gtk_widget_show (button);

  button = gtk_button_new_with_label ("OK");
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      GTK_SIGNAL_FUNC (ifs_compose_ok_callback),
                      dlg);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0);
  gtk_widget_grab_default (button);
  gtk_widget_show (button);

  button = gtk_button_new_with_label ("Cancel");
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
			     (GtkSignalFunc) gtk_widget_destroy,
			     GTK_OBJECT (dlg));
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0);
  gtk_widget_show (button);

  /*  The main vbox */
  main_vbox = gtk_vbox_new (FALSE, 0);
  gtk_container_border_width (GTK_CONTAINER (main_vbox), 10);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), main_vbox, TRUE, TRUE, 0);

  /*  The design area */
  hbox = gtk_hbox_new (FALSE, 5);
  gtk_box_pack_start (GTK_BOX (main_vbox), hbox, TRUE, TRUE, 0);

  aspect_frame = gtk_aspect_frame_new(NULL,
				      0.5, 0.5,
				      (gdouble)design_width/design_height,0);
  gtk_frame_set_shadow_type (GTK_FRAME (aspect_frame), GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX (hbox), aspect_frame, TRUE, TRUE, 0);

  design_area_create(dlg,design_width,design_height);
  gtk_container_add (GTK_CONTAINER (aspect_frame), ifsDesign->area);

  gtk_widget_show (ifsDesign->area);
  gtk_widget_show (aspect_frame);

  /* the preview */

  aspect_frame = gtk_aspect_frame_new(NULL,
				      0.5, 0.5,
				      (gdouble)design_width/design_height,0);
  gtk_frame_set_shadow_type (GTK_FRAME (aspect_frame), GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX (hbox), aspect_frame, TRUE, TRUE, 0);

  ifsD->preview = gtk_preview_new (GTK_PREVIEW_COLOR);
  gtk_preview_size (GTK_PREVIEW(ifsD->preview),design_width,design_height);
  gtk_container_add (GTK_CONTAINER (aspect_frame), ifsD->preview);
  gtk_widget_show (ifsD->preview);

  gtk_widget_show (aspect_frame);

  gtk_widget_show (hbox);

  /* Iterations and preview options */

  hbox = gtk_hbox_new(FALSE,1);
  gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 5);

  util_hbox = gtk_hbox_new(FALSE,5);
  gtk_container_add(GTK_CONTAINER(hbox), util_hbox);

  ifsD->move_button = gtk_toggle_button_new_with_label("Move");
  gtk_box_pack_start (GTK_BOX(util_hbox), ifsD->move_button, TRUE, TRUE, 0);
  gtk_widget_show (ifsD->move_button);
  ifsD->move_handler = gtk_signal_connect(GTK_OBJECT(ifsD->move_button),"toggled",
		     (GtkSignalFunc)design_op_callback,
		     (gpointer)((long)OP_TRANSLATE));

  ifsD->rotate_button = gtk_toggle_button_new_with_label("Rotate/Scale");
  gtk_box_pack_start (GTK_BOX(util_hbox), ifsD->rotate_button, TRUE, TRUE, 0);
  gtk_widget_show (ifsD->rotate_button);
  ifsD->rotate_handler = gtk_signal_connect(GTK_OBJECT(ifsD->rotate_button),
					    "toggled",
					    (GtkSignalFunc)design_op_callback,
					    (gpointer)((long)OP_ROTATE));

  ifsD->stretch_button = gtk_toggle_button_new_with_label("Stretch");
  gtk_box_pack_start (GTK_BOX(util_hbox), ifsD->stretch_button, TRUE, TRUE, 0);
  gtk_widget_show (ifsD->stretch_button);
  ifsD->stretch_handler = gtk_signal_connect(GTK_OBJECT(ifsD->stretch_button),
				     "toggled",
				     (GtkSignalFunc)design_op_callback,
				     (gpointer)((long)OP_STRETCH));

  gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(ifsD->move_button),TRUE);

  gtk_widget_show (util_hbox);

  alignment = gtk_alignment_new(1.0,0.5,0.5,0.0);
  gtk_box_pack_start (GTK_BOX (hbox), alignment, TRUE, TRUE, 0);

  util_hbox = gtk_hbox_new(FALSE,5);
  gtk_container_add(GTK_CONTAINER(alignment), util_hbox);

  button = gtk_button_new_with_label ("Render Options");
  gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
			     (GtkSignalFunc) ifs_options_dialog,
			     NULL);
  gtk_box_pack_start (GTK_BOX (util_hbox), button,
		      TRUE, TRUE, 0);
  gtk_widget_show (button);

  button = gtk_button_new_with_label ("Preview");
  gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
			     (GtkSignalFunc) ifs_compose_preview_callback,
			     GTK_OBJECT (ifsD->preview));
  gtk_box_pack_start (GTK_BOX (util_hbox), button,
		      TRUE, TRUE, 0);
  gtk_widget_show (button);

  check_button = gtk_check_button_new_with_label ("Auto");
  gtk_box_pack_start (GTK_BOX (util_hbox), check_button,
		      FALSE, FALSE, 0);
  gtk_toggle_button_set_state ( GTK_TOGGLE_BUTTON(check_button) ,
				ifsD->auto_preview );
  gtk_signal_connect ( GTK_OBJECT (check_button), "toggled",
		       (GtkSignalFunc) auto_preview_callback,
		       NULL );
  gtk_widget_show (check_button);

  gtk_widget_show (util_hbox);
  gtk_widget_show (alignment);
  gtk_widget_show (hbox);

  /* The current transformation frame */

  ifsD->current_frame = gtk_frame_new(NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (ifsD->current_frame),
			     GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start(GTK_BOX(main_vbox),ifsD->current_frame,FALSE,FALSE,0);

  vbox = gtk_vbox_new(FALSE,0);
  gtk_container_border_width (GTK_CONTAINER (vbox), 5);
  gtk_container_add (GTK_CONTAINER(ifsD->current_frame), vbox);

  /* The notebook */

  notebook = gtk_notebook_new();
  gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_TOP);
  gtk_box_pack_start(GTK_BOX(vbox),notebook,FALSE,FALSE,5);
  gtk_widget_show(notebook);

  page = ifs_compose_trans_page();
  label = gtk_label_new("Spatial Transformation");
  gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.5);
  gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, label);
  gtk_widget_show(page);

  page = ifs_compose_color_page();
  label = gtk_label_new("Color Transformation");
  gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.5);
  gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, label);
  gtk_widget_show(page);

  /* The probability entry */

  hbox = gtk_hbox_new(FALSE,5);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 5);
  label = gtk_label_new ("Relative Probability:");
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_box_pack_start(GTK_BOX (hbox), label, FALSE, FALSE, 0);
  gtk_widget_show (label);

  ifsD->prob_pair = value_pair_create(&ifsD->current_vals.prob,0.0,5.0, TRUE,
				      VALUE_PAIR_DOUBLE);
  gtk_box_pack_start (GTK_BOX (hbox), ifsD->prob_pair->scale, TRUE, TRUE, 0);
  gtk_widget_show (ifsD->prob_pair->scale);
  gtk_box_pack_start (GTK_BOX (hbox), ifsD->prob_pair->entry, FALSE, TRUE, 0);
  gtk_widget_show (ifsD->prob_pair->entry);

  gtk_widget_show(hbox);
  gtk_widget_show(vbox);
  gtk_widget_show(ifsD->current_frame);

  gtk_widget_show (main_vbox);

  if (ifsvals.num_elements == 0)
    {
      ifs_compose_set_defaults();
      if (ifsD->auto_preview)
	ifs_compose_preview_callback(NULL, ifsD->preview);
    }
  else
    {
      int i;
      gdouble ratio = (gdouble)ifsD->drawable_height/ifsD->drawable_width;

      element_selected = g_new(gint, ifsvals.num_elements);
      element_selected[0] = TRUE;
      for (i=1;i<ifsvals.num_elements;i++)
	element_selected[i] = FALSE;

      if (ratio != ifsvals.aspect_ratio)
	{
	  /* Adjust things so that what fit onto the old image, fits
	     onto the new image */
	  Aff2 t1,t2,t3;
	  gdouble x_offset, y_offset;
	  gdouble center_x, center_y;
	  gdouble scale;

	  if (ratio < ifsvals.aspect_ratio)
	    {
	      scale = ratio/ifsvals.aspect_ratio;
	      x_offset = (1-scale)/2;
	      y_offset = 0;
	    }
	  else
	    {
	      scale = 1;
	      x_offset = 0;
	      y_offset = (ratio - ifsvals.aspect_ratio)/2;
	    }
	  aff2_scale(&t1,scale,0);
	  aff2_translate(&t2,x_offset,y_offset);
	  aff2_compose(&t3,&t2,&t1);
	  aff2_invert(&t1,&t3);

	  aff2_apply(&t3,ifsvals.center_x,ifsvals.center_y,&center_x,
		     &center_y);

	  for (i=0;i<ifsvals.num_elements;i++)
	    {
	      aff_element_compute_trans(elements[i],1,ifsvals.aspect_ratio,
					ifsvals.center_x,ifsvals.center_y);
	      aff2_compose(&t2,&elements[i]->trans,&t1);
	      aff2_compose(&elements[i]->trans,&t3,&t2);
	      aff_element_decompose_trans(elements[i],&elements[i]->trans,
					   1,ifsvals.aspect_ratio,
					   center_x,center_y);
	    }
	  ifsvals.center_x = center_x;
	  ifsvals.center_y = center_y;

	  ifsvals.aspect_ratio = ratio;
	}

      for (i=0;i<ifsvals.num_elements;i++)
	aff_element_compute_color_trans(elements[i]);
      /* boundary and spatial transformations will be computed
	 when the design_area gets a ConfigureNotify event */

      set_current_element(0);
      if (ifsD->auto_preview)
	ifs_compose_preview_callback(NULL, ifsD->preview);

      ifsD->selected_orig = g_new(AffElement,ifsvals.num_elements);
    }

  gtk_widget_show (dlg);
  gtk_main ();

  gtk_object_unref (GTK_OBJECT (ifsDesign->op_menu));
  
  if (dlg)
    gtk_widget_destroy (dlg);

  if (ifsOptD)
    gtk_widget_destroy (ifsOptD->dialog);

  gdk_flush ();

  gdk_gc_destroy(ifsDesign->selected_gc);

  g_free(ifsD);

  return ifscint.run;
}

static void
design_area_create(GtkWidget *window,gint design_width,gint design_height)
{
  ifsDesign = g_new(IfsDesignArea,1);

  ifsDesign->op = OP_TRANSLATE;
  ifsDesign->button_state = 0;
  ifsDesign->pixmap = NULL;
  ifsDesign->selected_gc = NULL;

  ifsDesign->area = gtk_drawing_area_new();
  gtk_drawing_area_size (GTK_DRAWING_AREA(ifsDesign->area),design_width,
					  design_height);

  gtk_signal_connect(GTK_OBJECT(ifsDesign->area),"expose_event",
		     (GtkSignalFunc)design_area_expose,NULL);
  gtk_signal_connect(GTK_OBJECT(ifsDesign->area),"button_press_event",
		     (GtkSignalFunc)design_area_button_press,NULL);
  gtk_signal_connect(GTK_OBJECT(ifsDesign->area),"button_release_event",
		     (GtkSignalFunc)design_area_button_release,NULL);
  gtk_signal_connect(GTK_OBJECT(ifsDesign->area),"motion_notify_event",
		     (GtkSignalFunc)design_area_motion,NULL);
  gtk_signal_connect(GTK_OBJECT(ifsDesign->area),"configure_event",
		     (GtkSignalFunc) design_area_configure, NULL);
  gtk_widget_set_events (ifsDesign->area,
			 GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
			 GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
			 GDK_POINTER_MOTION_HINT_MASK);

  design_op_menu_create(window);
}

static void
design_op_menu_create(GtkWidget *window)
{
  GtkWidget *menu_item;
  GtkAcceleratorTable *accelerator_table;

  ifsDesign->op_menu = gtk_menu_new();
  gtk_object_ref (GTK_OBJECT (ifsDesign->op_menu));
  gtk_object_sink (GTK_OBJECT (ifsDesign->op_menu));

  accelerator_table = gtk_accelerator_table_new();
  gtk_menu_set_accelerator_table(GTK_MENU(ifsDesign->op_menu),
				 accelerator_table);
  gtk_window_add_accelerator_table(GTK_WINDOW(window),accelerator_table);

  menu_item = gtk_menu_item_new_with_label("Move");
  gtk_menu_append(GTK_MENU(ifsDesign->op_menu),menu_item);
  gtk_widget_show(menu_item);
  gtk_signal_connect(GTK_OBJECT(menu_item),"activate",
		     (GtkSignalFunc)design_op_update_callback,
		     (gpointer)((long)OP_TRANSLATE));
  gtk_widget_install_accelerator(menu_item,
				 accelerator_table,
				"activate",'M',0);

  menu_item = gtk_menu_item_new_with_label("Rotate/Scale");
  gtk_menu_append(GTK_MENU(ifsDesign->op_menu),menu_item);
  gtk_widget_show(menu_item);
  gtk_signal_connect(GTK_OBJECT(menu_item),"activate",
		     (GtkSignalFunc)design_op_update_callback,
		     (gpointer)((long)OP_ROTATE));
  gtk_widget_install_accelerator(menu_item,
				 accelerator_table,
				"activate",'R',0);

  menu_item = gtk_menu_item_new_with_label("Stretch");
  gtk_menu_append(GTK_MENU(ifsDesign->op_menu),menu_item);
  gtk_widget_show(menu_item);
  gtk_signal_connect(GTK_OBJECT(menu_item),"activate",
		     (GtkSignalFunc)design_op_update_callback,
		     (gpointer)((long)OP_STRETCH));
  gtk_widget_install_accelerator(menu_item,
				 accelerator_table,
				"activate",'S',0);

  /* A separator */
  menu_item = gtk_menu_item_new();
  gtk_menu_append(GTK_MENU(ifsDesign->op_menu),menu_item);
  gtk_widget_show(menu_item);

  menu_item = gtk_menu_item_new_with_label("Select All");
  gtk_menu_append(GTK_MENU(ifsDesign->op_menu),menu_item);
  gtk_widget_show(menu_item);
  gtk_signal_connect(GTK_OBJECT(menu_item),"activate",
		     (GtkSignalFunc)design_area_select_all_callback,
		     NULL);
  gtk_widget_install_accelerator(menu_item,
				 accelerator_table,
				"activate",'A',GDK_CONTROL_MASK);

  menu_item = gtk_menu_item_new_with_label("Recompute Center");
  gtk_menu_append(GTK_MENU(ifsDesign->op_menu),menu_item);
  gtk_widget_show(menu_item);
  gtk_signal_connect(GTK_OBJECT(menu_item),"activate",
		     (GtkSignalFunc)recompute_center_cb,
		     NULL);
  gtk_widget_install_accelerator(menu_item,
				 accelerator_table,
				"activate",'R',GDK_MOD1_MASK);

  menu_item = gtk_menu_item_new_with_label("Undo");
  gtk_menu_append(GTK_MENU(ifsDesign->op_menu),menu_item);
  gtk_widget_show(menu_item);
  gtk_signal_connect(GTK_OBJECT(menu_item),"activate",
		     (GtkSignalFunc)undo,
		     NULL);
  gtk_widget_install_accelerator(menu_item,
				 accelerator_table,
				"activate",'Z',GDK_CONTROL_MASK);

  menu_item = gtk_menu_item_new_with_label("Redo");
  gtk_menu_append(GTK_MENU(ifsDesign->op_menu),menu_item);
  gtk_widget_show(menu_item);
  gtk_signal_connect(GTK_OBJECT(menu_item),"activate",
		     (GtkSignalFunc)redo,
		     NULL);
  gtk_widget_install_accelerator(menu_item,
				 accelerator_table,
				"activate",'R',GDK_CONTROL_MASK);
}

static void
design_op_menu_popup(gint button, guint32 activate_time)
{
  gtk_menu_popup(GTK_MENU(ifsDesign->op_menu),NULL,NULL,NULL,NULL,button,activate_time);
}

static void
ifs_options_dialog()
{
  GtkWidget *button;
  GtkWidget *table;
  GtkWidget *label;

  if (!ifsOptD)
    {
      ifsOptD = g_new(IfsOptionsDialog,1);

      ifsOptD->dialog = gtk_dialog_new();
      gtk_window_set_title(GTK_WINDOW(ifsOptD->dialog),"IfsCompose Options");
      gtk_window_position(GTK_WINDOW(ifsOptD->dialog), GTK_WIN_POS_MOUSE);
      gtk_signal_connect (GTK_OBJECT(ifsOptD->dialog),
			   "delete_event",
			   GTK_SIGNAL_FUNC (gtk_widget_hide_on_delete),
			   &ifsOptD->dialog);
      gtk_signal_connect(GTK_OBJECT(ifsOptD->dialog), "destroy",
			 (GtkSignalFunc) ifs_options_close_callback,
			 NULL);
      /* Action area */

      button = gtk_button_new_with_label ("Close");
      GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
      gtk_signal_connect (GTK_OBJECT (button), "clicked",
			  (GtkSignalFunc) ifs_options_close_callback,
			  NULL);
      gtk_box_pack_start (GTK_BOX (GTK_DIALOG (ifsOptD->dialog)->action_area),
			  button, TRUE, TRUE, 0);
      gtk_widget_show (button);

      /* Table of options */

      table = gtk_table_new(4,3,FALSE);
      gtk_container_border_width(GTK_CONTAINER(table),10);
      gtk_table_set_row_spacings(GTK_TABLE(table), 4);
      gtk_table_set_col_spacings(GTK_TABLE(table), 4);
      gtk_box_pack_start(GTK_BOX(GTK_DIALOG(ifsOptD->dialog)->vbox), table,
			 FALSE,FALSE,0);
      gtk_widget_show(table);

      label = gtk_label_new("Max. Memory:");
      gtk_misc_set_alignment(GTK_MISC(label),1.0,0.5);
      gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1,
		       GTK_FILL, GTK_FILL, 4, 0);
      gtk_widget_show(label);

      ifsOptD->memory_pair = value_pair_create(&ifsvals.max_memory,
					       1,1000000,FALSE,
					       VALUE_PAIR_INT);
      gtk_table_attach(GTK_TABLE(table), ifsOptD->memory_pair->entry,
		       1, 2, 0, 1, GTK_FILL, GTK_FILL, 4, 0);
      gtk_widget_show (ifsOptD->memory_pair->entry);

      label = gtk_label_new("Iterations:");
      gtk_misc_set_alignment(GTK_MISC(label),1.0,0.5);
      gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
		       GTK_FILL, GTK_FILL, 4, 0);
      gtk_widget_show(label);

      ifsOptD->iterations_pair = value_pair_create(&ifsvals.iterations,1,10000000,FALSE,
						VALUE_PAIR_INT);
      gtk_table_attach(GTK_TABLE(table), ifsOptD->iterations_pair->entry,
		       1, 2, 1, 2, GTK_FILL, GTK_FILL, 4, 0);
      gtk_widget_show (ifsOptD->iterations_pair->entry);
      gtk_widget_show (label);

      label = gtk_label_new("Subdivide:");
      gtk_misc_set_alignment(GTK_MISC(label),1.0,0.5);
      gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3,
		       GTK_FILL, GTK_FILL, 4, 0);
      gtk_widget_show(label);

      ifsOptD->subdivide_pair = value_pair_create(&ifsvals.subdivide,1,10,
						  FALSE,
						  VALUE_PAIR_INT);
      gtk_table_attach(GTK_TABLE(table), ifsOptD->subdivide_pair->entry,
		       1, 2, 2, 3, GTK_FILL, GTK_FILL, 4, 0);
      gtk_widget_show (ifsOptD->subdivide_pair->entry);

      label = gtk_label_new("Spot Radius:");
      gtk_misc_set_alignment(GTK_MISC(label),1.0,0.5);
      gtk_table_attach(GTK_TABLE(table), label, 0, 1, 3, 4,
		       GTK_FILL, GTK_FILL, 4, 0);
      gtk_widget_show(label);

      ifsOptD->radius_pair = value_pair_create(&ifsvals.radius,0,5,
					       TRUE,
					       VALUE_PAIR_DOUBLE);
      gtk_table_attach(GTK_TABLE(table), ifsOptD->radius_pair->scale,
		       1, 2, 3, 4, GTK_FILL, GTK_FILL, 4, 0);
      gtk_widget_show (ifsOptD->radius_pair->scale);
      gtk_table_attach(GTK_TABLE(table), ifsOptD->radius_pair->entry,
		       2, 3, 3, 4, GTK_FILL, GTK_FILL, 4, 0);
      gtk_widget_show (ifsOptD->radius_pair->entry);

      value_pair_update(ifsOptD->iterations_pair);
      value_pair_update(ifsOptD->subdivide_pair);
      value_pair_update(ifsOptD->memory_pair);
      value_pair_update(ifsOptD->radius_pair);

      gtk_widget_show (ifsOptD->dialog);
    }
  else
    {
      if (!GTK_WIDGET_VISIBLE (ifsOptD->dialog))
	gtk_widget_show (ifsOptD->dialog);
    }
}

static void
ifs_compose(GDrawable *drawable)
{
  gint i,j;
  GDrawableType type = gimp_drawable_type(drawable->id);
  gchar buffer[128];

  gint width = drawable->width;
  gint height = drawable->height;
  gint num_bands,band_height,band_y,band_no;
  guchar *data;
  guchar *mask = NULL;
  guchar *nhits;
  guchar rc,gc,bc;

  num_bands = ceil((gdouble)(width*height*SQR(ifsvals.subdivide)*5)
		   / (1024 * ifsvals.max_memory));
  band_height = height / num_bands;
  if (band_height > height)
    band_height = height;

  mask = g_new(guchar,width*band_height*SQR(ifsvals.subdivide));
  data = g_new(guchar,width*band_height*SQR(ifsvals.subdivide)*3);
  nhits = g_new(guchar,width*band_height*SQR(ifsvals.subdivide));
  gimp_palette_get_background ( &rc, &gc, &bc );

  band_y = 0;
  for (band_no = 0; band_no < num_bands; band_no++)
    {
      guchar *ptr;
      guchar *maskptr;
      guchar *dest;
      guchar *destrow;
      guchar maskval;
      GPixelRgn dest_rgn;
      gint progress;
      gint max_progress;

      gpointer pr;

      sprintf(buffer,"Rendering IFS (%d/%d)...",band_no+1,num_bands);
      gimp_progress_init(buffer);

      /* render the band to a buffer */
      if (band_y + band_height > height)
	band_height = height - band_y;

      /* we don't need to clear data since we store nhits */
      memset(mask, 0, width*band_height*SQR(ifsvals.subdivide));
      memset(nhits, 0, width*band_height*SQR(ifsvals.subdivide));

      ifs_render(elements, ifsvals.num_elements, width, height, ifsvals.iterations,
		 &ifsvals, band_y, band_height, data, mask, nhits, FALSE);

      /* transfer the image to the drawable */

      sprintf(buffer,"Copying IFS to image (%d/%d)...",band_no+1,num_bands);
      gimp_progress_init(buffer);

      progress = 0;
      max_progress = band_height * width;

      gimp_pixel_rgn_init (&dest_rgn, drawable, 0, band_y,
			   width, band_height, TRUE, TRUE);

      for (pr = gimp_pixel_rgns_register (1, &dest_rgn); pr != NULL; pr = gimp_pixel_rgns_process (pr))
	{
	  destrow = dest_rgn.data;

	  for (j = dest_rgn.y; j < (dest_rgn.y + dest_rgn.h); j++)
	    {
	      dest = destrow;

	      for (i = dest_rgn.x; i < (dest_rgn.x + dest_rgn.w); i++)
		{
		  /* Accumulate a reduced pixel */

		  gint ii,jj;
		  gint rtot=0;
		  gint btot=0;
		  gint gtot=0;
		  gint mtot=0;
		  for (jj=0;jj<ifsvals.subdivide;jj++)
		    {
		      ptr = data + 3 *
			(((j-band_y)*ifsvals.subdivide+jj)*ifsvals.subdivide*width +
			 i*ifsvals.subdivide);

		      maskptr = mask +
			((j-band_y)*ifsvals.subdivide+jj)*ifsvals.subdivide*width +
			i*ifsvals.subdivide;
		      for (ii=0;ii<ifsvals.subdivide;ii++)
			{
			  maskval = *maskptr++;
			  mtot += maskval;
			  rtot += maskval* *ptr++;
			  gtot += maskval* *ptr++;
			  btot += maskval* *ptr++;
			}
		    }
		  if (mtot)
		    {
		      rtot /= mtot;
		      gtot /= mtot;
		      btot /= mtot;
		      mtot /= SQR(ifsvals.subdivide);
		    }
		  /* and store it */
		  switch (type)
		    {
 		    case GRAY_IMAGE:
		      *dest++ = (mtot*(rtot+btot+gtot)+
				 (255-mtot)*(rc+gc+bc))/(3*255);
		      break;
		    case GRAYA_IMAGE:
		      *dest++ = (rtot+btot+gtot)/3;
		      *dest++ = mtot;
		      break;
		    case RGB_IMAGE:
		      *dest++ = (mtot*rtot + (255-mtot)*rc)/255;
		      *dest++ = (mtot*gtot + (255-mtot)*gc)/255;
		      *dest++ = (mtot*btot + (255-mtot)*bc)/255;
		      break;
		    case RGBA_IMAGE:
		      *dest++ = rtot;
		      *dest++ = gtot;
		      *dest++ = btot;
		      *dest++ = mtot;
		      break;
		    case INDEXED_IMAGE:
		    case INDEXEDA_IMAGE:
		      g_error("Indexed images not supported by IfsCompose");
		      break;
		    }
		}
	      destrow += dest_rgn.rowstride;;
	    }
	  progress += dest_rgn.w * dest_rgn.h;
	  gimp_progress_update ((gdouble) progress / (gdouble) max_progress);
	}
      band_y += band_height;
    }

  g_free(mask);
  g_free(data);
  g_free(nhits);

  gimp_drawable_flush (drawable);
  gimp_drawable_merge_shadow (drawable->id, TRUE);
  gimp_drawable_update (drawable->id,0,0,width,height);
}

static void
update_values()
{
  ifsD->in_update = TRUE;

  ifsD->current_vals = elements[ifsD->current_element]->v;
  ifsD->current_vals.theta *= 180/M_PI;

  value_pair_update(ifsD->prob_pair);
  value_pair_update(ifsD->x_pair);
  value_pair_update(ifsD->y_pair);
  value_pair_update(ifsD->scale_pair);
  value_pair_update(ifsD->angle_pair);
  value_pair_update(ifsD->asym_pair);
  value_pair_update(ifsD->shear_pair);
  color_map_update(ifsD->red_cmap);
  color_map_update(ifsD->green_cmap);
  color_map_update(ifsD->blue_cmap);
  color_map_update(ifsD->black_cmap);
  color_map_update(ifsD->target_cmap);
  value_pair_update(ifsD->hue_scale_pair);
  value_pair_update(ifsD->value_scale_pair);
  if (elements[ifsD->current_element]->v.simple_color)
    gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ifsD->simple_button),
				 TRUE);
  else
    gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ifsD->full_button),
				 TRUE);
  gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(ifsD->flip_check_button),
			      elements[ifsD->current_element]->v.flip);

  ifsD->in_update = FALSE;

  simple_color_set_sensitive();
}

static void
set_current_element(gint index)
{
  ifsD->current_element = index;

  gtk_frame_set_label(GTK_FRAME(ifsD->current_frame),elements[index]->name);

  update_values();
}

static gint
design_area_expose(GtkWidget *widget,GdkEventExpose *event)
{
  gint i;
  gint cx,cy;

  if (!ifsDesign->selected_gc)
    {
      ifsDesign->selected_gc = gdk_gc_new(ifsDesign->area->window);
      gdk_gc_set_line_attributes(ifsDesign->selected_gc,2,
				 GDK_LINE_SOLID,GDK_CAP_ROUND,
				 GDK_JOIN_ROUND);
    }

  gdk_draw_rectangle(ifsDesign->pixmap,
		     widget->style->bg_gc[widget->state],
		     TRUE,
		     event->area.x,
		     event->area.y,
		     event->area.width,event->area.height);

  /* draw an indicator for the center */

  cx = ifsvals.center_x * widget->allocation.width;
  cy = ifsvals.center_y * widget->allocation.width;
  gdk_draw_line(ifsDesign->pixmap,
		widget->style->fg_gc[widget->state],
		cx - 10, cy, cx + 10, cy);
  gdk_draw_line(ifsDesign->pixmap,
		widget->style->fg_gc[widget->state],
		cx, cy - 10, cx, cy + 10);

  for (i=0;i<ifsvals.num_elements;i++)
    {
      aff_element_draw(elements[i], element_selected[i],
		       widget->allocation.width,
		       widget->allocation.height,
		       ifsDesign->pixmap,
		       widget->style->fg_gc[widget->state],
		       ifsDesign->selected_gc,
		       ifsDesign->area->style->font);
    }

  gdk_draw_pixmap(widget->window,
		  widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
		  ifsDesign->pixmap,
		  event->area.x, event->area.y,
		  event->area.x, event->area.y,
		  event->area.width, event->area.height);

  return FALSE;
}

static gint
design_area_configure(GtkWidget *widget, GdkEventConfigure *event)
{
  int i;
  gdouble width = widget->allocation.width;
  gdouble height = widget->allocation.height;

  for (i=0;i<ifsvals.num_elements;i++)
    aff_element_compute_trans(elements[i],width,height,
			      ifsvals.center_x, ifsvals.center_y);
  for (i=0;i<ifsvals.num_elements;i++)
    aff_element_compute_boundary(elements[i],width,height,
				 elements, ifsvals.num_elements);

  if (ifsDesign->pixmap)
    {
      gdk_pixmap_unref(ifsDesign->pixmap);
    }
  ifsDesign->pixmap = gdk_pixmap_new(widget->window,
				     widget->allocation.width,
				     widget->allocation.height,
				     gtk_preview_get_visual()->depth);

  return FALSE;
}

static gint
design_area_button_press(GtkWidget *widget, GdkEventButton *event)
{
  gint i;
  gdouble width = ifsDesign->area->allocation.width;
  gint old_current;

  gtk_widget_grab_focus(widget);

  if (event->button != 1 ||
      (ifsDesign->button_state & GDK_BUTTON1_MASK))
    {
      if (event->button == 3)
	  design_op_menu_popup(event->button, event->time);
      return FALSE;
    }

  old_current = ifsD->current_element;
  ifsD->current_element = -1;

  /* Find out where the button press was */
  for (i=0;i<ifsvals.num_elements;i++)
    {
      if ( ipolygon_contains(elements[i]->click_boundary,event->x,event->y) )
	{
	  set_current_element(i);
	  break;
	}
    }

  /* if the user started manipulating an object, set up a new
     position on the undo ring */
  if (ifsD->current_element >= 0)
    undo_begin();

  if (!(event->state & GDK_SHIFT_MASK)
      && ( (ifsD->current_element<0)
	   || !element_selected[ifsD->current_element] ))
    {
      for (i=0;i<ifsvals.num_elements;i++)
	element_selected[i] = 0;
    }

  if (ifsD->current_element >= 0)
    {
      ifsDesign->button_state |= GDK_BUTTON1_MASK;

      element_selected[ifsD->current_element] = TRUE;

      ifsDesign->num_selected = 0;
      ifsDesign->op_xcenter = 0.0;
      ifsDesign->op_ycenter = 0.0;
      for (i=0;i<ifsvals.num_elements;i++)
	{
	  if (element_selected[i])
	    {
	      ifsD->selected_orig[i] = *elements[i];
	      ifsDesign->op_xcenter += elements[i]->v.x;
	      ifsDesign->op_ycenter += elements[i]->v.y;
	      ifsDesign->num_selected++;
	      undo_update(i);
	    }
	}
      ifsDesign->op_xcenter /= ifsDesign->num_selected;
      ifsDesign->op_ycenter /= ifsDesign->num_selected;
      ifsDesign->op_x = (gdouble)event->x/width;
      ifsDesign->op_y = (gdouble)event->y/width;
      ifsDesign->op_center_x = ifsvals.center_x;
      ifsDesign->op_center_y = ifsvals.center_y;
    }
  else
    {
      ifsD->current_element = old_current;
      element_selected[old_current] = TRUE;
    }

  design_area_redraw();

  return FALSE;
}

static gint
design_area_button_release(GtkWidget *widget, GdkEventButton *event)
{
  if (event->button == 1 &&
      (ifsDesign->button_state & GDK_BUTTON1_MASK))
    {
      ifsDesign->button_state &= ~GDK_BUTTON1_MASK;
      if (ifsD->auto_preview)
	ifs_compose_preview_callback(NULL, ifsD->preview);
    }
  return FALSE;
}

static gint
design_area_motion(GtkWidget *widget, GdkEventMotion *event)
{
  gint i;
  gdouble xo;
  gdouble yo;
  gdouble xn;
  gdouble yn;
  gint px,py;
  gdouble width = ifsDesign->area->allocation.width;
  gdouble height = ifsDesign->area->allocation.height;

  Aff2 trans,t1,t2,t3;

  if (!(ifsDesign->button_state & GDK_BUTTON1_MASK)) return FALSE;

  if (event->is_hint)
    {
      gtk_widget_get_pointer(ifsDesign->area, &px, &py);
      event->x = px;
      event->y = py;
    }

  xo = (ifsDesign->op_x - ifsDesign->op_xcenter);
  yo = (ifsDesign->op_y - ifsDesign->op_ycenter);
  xn = (gdouble)event->x/width - ifsDesign->op_xcenter;
  yn = (gdouble)event->y/width - ifsDesign->op_ycenter;

  /*  for (i=0;i<num_elements;i++)
    {
      gtk_widget_draw(widget,&elements[i]->bounding_box);
    } */

  switch (ifsDesign->op)
    {
    case OP_ROTATE:
      {
	aff2_translate(&t1,-ifsDesign->op_xcenter*width,
		       -ifsDesign->op_ycenter*width);
	aff2_scale(&t2,
		   sqrt((SQR(xn)+SQR(yn))/(SQR(xo)+SQR(yo))),
		   0);
	aff2_compose(&t3, &t2, &t1);
	aff2_rotate(&t1, - atan2(yn,xn) + atan2(yo,xo));
	aff2_compose(&t2, &t1, &t3);
	aff2_translate(&t3,ifsDesign->op_xcenter*width,
		       ifsDesign->op_ycenter*width);
	aff2_compose(&trans, &t3, &t2);
	break;
      }
    case OP_STRETCH:
      {
	aff2_translate(&t1,-ifsDesign->op_xcenter*width,
		       -ifsDesign->op_ycenter*width);
	aff2_compute_stretch(&t2, xo, yo, xn, yn);
	aff2_compose(&t3, &t2, &t1);
	aff2_translate(&t1,ifsDesign->op_xcenter*width,
		       ifsDesign->op_ycenter*width);
	aff2_compose(&trans, &t1, &t3);
	break;
      }
    case OP_TRANSLATE:
      {
	aff2_translate(&trans,(xn-xo)*width,(yn-yo)*width);
	break;
      }
    }

  for (i=0;i<ifsvals.num_elements;i++)
    if (element_selected[i])
      {
	if (ifsDesign->num_selected == ifsvals.num_elements)
	  {
	    gdouble cx,cy;
	    aff2_invert(&t1, &trans);
	    aff2_compose(&t2, &trans, &ifsD->selected_orig[i].trans);
	    aff2_compose(&elements[i]->trans, &t2, &t1);

	    cx = ifsDesign->op_center_x * width;
	    cy = ifsDesign->op_center_y * width;
	    aff2_apply(&trans,cx,cy,&cx,&cy);
	    ifsvals.center_x = cx / width;
	    ifsvals.center_y = cy / width;
	  }
	else
	  {
	    aff2_compose(&elements[i]->trans, &trans,
			 &ifsD->selected_orig[i].trans);
	  }
	aff_element_decompose_trans(elements[i],&elements[i]->trans,
				    width,height, ifsvals.center_x,
				    ifsvals.center_y);
	aff_element_compute_trans(elements[i],width,height,
			      ifsvals.center_x, ifsvals.center_y);
      }

  update_values();
  design_area_redraw();

  return FALSE;
}

static void
design_area_redraw(void)
{
  gint i;
  gdouble width = ifsDesign->area->allocation.width;
  gdouble height = ifsDesign->area->allocation.height;

  for (i=0;i<ifsvals.num_elements;i++)
    {
      aff_element_compute_boundary(elements[i],width,height,
				   elements,ifsvals.num_elements);
    }
  gtk_widget_draw(ifsDesign->area,NULL);
}

/* Undo ring functions */
static void
undo_begin(void)
{
  gint i,j;
  gint to_delete;
  gint new_index;

  if (undo_cur == UNDO_LEVELS-1)
    {
      to_delete = 1;
      undo_start = (undo_start + 1)%UNDO_LEVELS;
    }
  else
    {
      undo_cur++;
      to_delete = undo_num - undo_cur;
    }

  undo_num = undo_num - to_delete + 1;
  new_index = (undo_start+undo_cur)%UNDO_LEVELS;

  /* remove any redo elements or the oldest element if necessary */
  for (j=new_index;to_delete>0;j=(j+1)%UNDO_LEVELS,to_delete--)
    {
      for (i=0;i<undo_ring[j].ifsvals.num_elements;i++)
	if (undo_ring[j].elements[i])
	  aff_element_free(undo_ring[j].elements[i]);
      g_free(undo_ring[j].elements);
      g_free(undo_ring[j].element_selected);
    }

  undo_ring[new_index].ifsvals = ifsvals;
  undo_ring[new_index].elements = g_new(AffElement *,ifsvals.num_elements);
  undo_ring[new_index].element_selected = g_new(gint,ifsvals.num_elements);
  undo_ring[new_index].current_element = ifsD->current_element;

  for (i=0;i<ifsvals.num_elements;i++)
    {
      undo_ring[new_index].elements[i] = NULL;
      undo_ring[new_index].element_selected[i] = element_selected[i];
    }
}

static void
undo_update(gint el)
{
  AffElement *elem;
  /* initialize */
  
  elem = NULL;

  if (!undo_ring[(undo_start+undo_cur)%UNDO_LEVELS].elements[el])
    undo_ring[(undo_start+undo_cur)%UNDO_LEVELS].elements[el]
      = elem = g_new(AffElement,1);

  *elem = *elements[el];
  elem->draw_boundary = NULL;
  elem->click_boundary = NULL;
}

static void
undo_exchange(gint el)
{
  gint i;
  AffElement **telements;
  gint *tselected;
  IfsComposeVals tifsvals;
  gint tcurrent;
  gdouble width = ifsDesign->area->allocation.width;
  gdouble height = ifsDesign->area->allocation.height;

  /* swap the arrays and values*/
  telements = elements;
  elements = undo_ring[el].elements;
  undo_ring[el].elements = telements;

  tifsvals = ifsvals;
  ifsvals = undo_ring[el].ifsvals;
  undo_ring[el].ifsvals = tifsvals;

  tselected = element_selected;
  element_selected = undo_ring[el].element_selected;
  undo_ring[el].element_selected = tselected;

  tcurrent = ifsD->current_element;
  ifsD->current_element = undo_ring[el].current_element;
  undo_ring[el].current_element = tcurrent;

  /* now swap back any unchanged elements */
  for (i=0;i<ifsvals.num_elements;i++)
    if (!elements[i])
      {
	elements[i] = undo_ring[el].elements[i];
	undo_ring[el].elements[i] = 0;
      }
    else
      aff_element_compute_trans(elements[i],width,height,
				ifsvals.center_x,ifsvals.center_y);

  set_current_element(ifsD->current_element);

  design_area_redraw();

  if (ifsD->auto_preview)
    ifs_compose_preview_callback(NULL, ifsD->preview);
}

static void
undo(void)
{
  if (undo_cur >= 0)
    {
      undo_exchange((undo_start+undo_cur)%UNDO_LEVELS);
      undo_cur--;
    }
}

static void
redo(void)
{
  if (undo_cur != undo_num - 1)
    {
      undo_cur++;
      undo_exchange((undo_start+undo_cur)%UNDO_LEVELS);
    }
}

static void
design_area_select_all_callback(GtkWidget *w, gpointer data)
{
  gint i;

  for (i=0;i<ifsvals.num_elements;i++)
    element_selected[i] = TRUE;

  design_area_redraw();
}

/*  Interface functions  */

static void
val_changed_update ()
{
  gdouble width = ifsDesign->area->allocation.width;
  gdouble height = ifsDesign->area->allocation.height;
  AffElement *cur = elements[ifsD->current_element];

  if (ifsD->in_update)
    return;

  undo_begin();
  undo_update(ifsD->current_element);

  cur->v = ifsD->current_vals;
  cur->v.theta *= M_PI/180.0;
  aff_element_compute_trans(cur,width,height,
			      ifsvals.center_x, ifsvals.center_y);
  aff_element_compute_color_trans(cur);

  design_area_redraw();

  if (ifsD->auto_preview)
    ifs_compose_preview_callback(NULL, ifsD->preview);
}

/* Pseudo-widget representing a color mapping */

#define COLOR_SAMPLE_SIZE 30

static void
color_map_set_preview_color(GtkWidget *preview, IfsColor *color)
{
  gint i;
  guchar buf[3*COLOR_SAMPLE_SIZE];

  for (i=0;i<COLOR_SAMPLE_SIZE;i++)
    {
      buf[3*i] = (guint)(255.999*color->vals[0]);
      buf[3*i+1] = (guint)(255.999*color->vals[1]);
      buf[3*i+2] = (guint)(255.999*color->vals[2]);
    }
  for (i=0;i<COLOR_SAMPLE_SIZE;i++)
    gtk_preview_draw_row(GTK_PREVIEW(preview),buf,0,i,COLOR_SAMPLE_SIZE);

  gtk_widget_draw (preview, NULL);
}

static ColorMap *
color_map_create(gchar *name, IfsColor *orig_color, IfsColor *data,
		 gint fixed_point)
{
  GtkWidget *frame;
  GtkWidget *label;
  GtkWidget *button;

  ColorMap *color_map = g_new(ColorMap,1);
  color_map->name = name;
  color_map->color = data;
  color_map->dialog = NULL;
  color_map->fixed_point = fixed_point;

  color_map->in_change_callback = FALSE;

  color_map->hbox = gtk_hbox_new(FALSE,2);

  frame = gtk_frame_new(NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
  gtk_box_pack_start(GTK_BOX(color_map->hbox),frame,FALSE,FALSE,0);
  gtk_widget_show(frame);

  color_map->orig_preview = gtk_preview_new(GTK_PREVIEW_COLOR);
  gtk_preview_size(GTK_PREVIEW(color_map->orig_preview),
		   COLOR_SAMPLE_SIZE,COLOR_SAMPLE_SIZE);
  gtk_container_add (GTK_CONTAINER(frame),color_map->orig_preview);
  gtk_widget_show(color_map->orig_preview);

  if (fixed_point)
    color_map_set_preview_color(color_map->orig_preview,data);
  else
    color_map_set_preview_color(color_map->orig_preview,orig_color);

  label = gtk_label_new("=>");
  gtk_box_pack_start(GTK_BOX(color_map->hbox),label,FALSE,FALSE,0);
  gtk_widget_show(label);

  button = gtk_button_new();
  gtk_box_pack_start(GTK_BOX(color_map->hbox),button,FALSE,FALSE,0);
  gtk_widget_show(button);

  color_map->preview = gtk_preview_new(GTK_PREVIEW_COLOR);
  gtk_preview_size(GTK_PREVIEW(color_map->preview),
		   COLOR_SAMPLE_SIZE,COLOR_SAMPLE_SIZE);
  gtk_container_add (GTK_CONTAINER(button),color_map->preview);
  gtk_widget_show(color_map->preview);

  color_map_set_preview_color(color_map->preview,data);

  gtk_signal_connect(GTK_OBJECT(button),"clicked",
		     GTK_SIGNAL_FUNC (color_map_clicked_callback),
		     color_map);

  gtk_signal_connect(GTK_OBJECT(frame),"destroy",
		     GTK_SIGNAL_FUNC (color_map_destroy_callback),
		     color_map);

  return color_map;
}

static void
color_map_clicked_callback(GtkWidget *widget,
			   ColorMap *color_map)
{
  GtkColorSelectionDialog *csd;

  if (!color_map->dialog)
    {
      color_map->dialog = gtk_color_selection_dialog_new(color_map->name);
      csd  = GTK_COLOR_SELECTION_DIALOG(color_map->dialog);
      gtk_color_selection_set_update_policy(
					 GTK_COLOR_SELECTION(csd->colorsel),
					 GTK_UPDATE_DELAYED);

      gtk_widget_destroy ( csd->help_button );
      gtk_widget_destroy ( csd->cancel_button );

      gtk_signal_connect_object( GTK_OBJECT(csd->ok_button),
				 "clicked",
				 (GtkSignalFunc)gtk_widget_hide,
				 GTK_OBJECT(color_map->dialog));
      gtk_signal_connect ( GTK_OBJECT(csd->colorsel),
			   "color_changed",
			   (GtkSignalFunc)color_map_color_changed_cb,
			   color_map );

      gtk_signal_connect ( GTK_OBJECT(csd->colorsel),
			   "destroy",
			   GTK_SIGNAL_FUNC (gtk_widget_destroyed),
			   &color_map->dialog );

      /* call here so the old color is set */
      gtk_color_selection_set_color( GTK_COLOR_SELECTION(csd->colorsel),
				     color_map->color->vals);
    }
  else
    csd  = GTK_COLOR_SELECTION_DIALOG(color_map->dialog);

  undo_begin();
  undo_update(ifsD->current_element);

  gtk_color_selection_set_color(GTK_COLOR_SELECTION(csd->colorsel),
				color_map->color->vals);

  gtk_window_position(GTK_WINDOW(color_map->dialog), GTK_WIN_POS_MOUSE);
  gtk_widget_show(color_map->dialog);
}

static void
color_map_destroy_callback(GtkWidget *widget,
			   ColorMap *color_map)
{
  if (color_map->dialog)
    gtk_widget_destroy (color_map->dialog);
}

static void
color_map_color_changed_cb(GtkWidget *widget,
			   ColorMap *color_map)
{
  color_map->in_change_callback = TRUE;

  gtk_color_selection_get_color(
    GTK_COLOR_SELECTION(GTK_COLOR_SELECTION_DIALOG(color_map->dialog)
       ->colorsel),
    color_map->color->vals);

  elements[ifsD->current_element]->v = ifsD->current_vals;
  elements[ifsD->current_element]->v.theta *= M_PI/180.0;
  aff_element_compute_color_trans(elements[ifsD->current_element]);

  update_values();

  if (ifsD->auto_preview)
    ifs_compose_preview_callback(NULL,ifsD->preview);

  color_map->in_change_callback = FALSE;
}

static void
color_map_update(ColorMap *color_map)
{
  color_map_set_preview_color(color_map->preview,color_map->color);
  if (color_map->fixed_point)
    color_map_set_preview_color(color_map->orig_preview,color_map->color);

  if (color_map->dialog && !color_map->in_change_callback)
    {
      gtk_color_selection_set_color(
         GTK_COLOR_SELECTION(GTK_COLOR_SELECTION_DIALOG(color_map->dialog)
	    ->colorsel),
	 color_map->color->vals);
    }
}

static void
simple_color_set_sensitive()
{
  gint sc = elements[ifsD->current_element]->v.simple_color;

  gtk_widget_set_sensitive(ifsD->target_cmap->hbox,sc);
  gtk_widget_set_sensitive(ifsD->hue_scale_pair->scale,sc);
  gtk_widget_set_sensitive(ifsD->hue_scale_pair->entry,sc);
  gtk_widget_set_sensitive(ifsD->value_scale_pair->scale,sc);
  gtk_widget_set_sensitive(ifsD->value_scale_pair->entry,sc);

  gtk_widget_set_sensitive(ifsD->red_cmap->hbox,!sc);
  gtk_widget_set_sensitive(ifsD->green_cmap->hbox,!sc);
  gtk_widget_set_sensitive(ifsD->blue_cmap->hbox,!sc);
  gtk_widget_set_sensitive(ifsD->black_cmap->hbox,!sc);
}

static void
simple_color_toggled(GtkWidget *widget,gpointer data)
{
  AffElement *cur = elements[ifsD->current_element];
  cur->v.simple_color = GTK_TOGGLE_BUTTON(widget)->active;
  ifsD->current_vals.simple_color = cur->v.simple_color;
  if (cur->v.simple_color)
    {
      aff_element_compute_color_trans(cur);
      val_changed_update();
    }
  simple_color_set_sensitive();
}

/* Generic mechanism for scale/entry combination (possibly without
   scale) */

static ValuePair *
value_pair_create (gpointer data, gdouble lower, gdouble upper,
		   gboolean create_scale, ValuePairType type)
{

  ValuePair *value_pair = g_new(ValuePair,1);
  value_pair->data.d = data;
  value_pair->type = type;

  value_pair->adjustment = gtk_adjustment_new (1.0, lower, upper,
					  (upper-lower)/100, (upper-lower)/10,
					  0.0);
  /* We need to sink the adjustment, since we may not create a scale for
   * it, so nobody will assume the initial refcount
   */
  gtk_object_ref (value_pair->adjustment);
  gtk_object_sink (value_pair->adjustment);
  gtk_signal_connect (GTK_OBJECT (value_pair->adjustment), "value_changed",
		      (GtkSignalFunc) value_pair_scale_callback,
		      value_pair);

  if (create_scale)
    {
      value_pair->scale = gtk_hscale_new(GTK_ADJUSTMENT (value_pair->adjustment));
      gtk_widget_ref (value_pair->scale);

      if (type == VALUE_PAIR_INT)
	  gtk_scale_set_digits (GTK_SCALE (value_pair->scale), 0);
      else
	  gtk_scale_set_digits (GTK_SCALE (value_pair->scale), 2);

      gtk_scale_set_draw_value (GTK_SCALE (value_pair->scale), FALSE);
      gtk_signal_connect (GTK_OBJECT (value_pair->scale),
			  "button_release_event",
			  (GtkSignalFunc) value_pair_button_release,
			  NULL);
      gtk_widget_set_events (value_pair->scale,
		       gtk_widget_get_events(GTK_WIDGET (value_pair->scale))
			     | GDK_BUTTON_RELEASE_MASK );
    }
  else
    value_pair->scale = NULL;

  /* We destroy the value pair when the entry is destroyed, so
   * we don't need to hold a refcount on the entry
   */

  value_pair->entry = gtk_entry_new ();
  gtk_widget_set_usize (value_pair->entry, ENTRY_WIDTH, 0);
  value_pair->entry_handler_id =
    gtk_signal_connect (GTK_OBJECT (value_pair->entry), "changed",
			(GtkSignalFunc) value_pair_entry_callback, value_pair);
  gtk_signal_connect (GTK_OBJECT (value_pair->entry), "destroy",
		      (GtkSignalFunc) value_pair_destroy_callback, value_pair);

  return value_pair;
}

static void
value_pair_update(ValuePair *value_pair)
{
  gchar buffer[32];

  if (value_pair->type == VALUE_PAIR_INT)
    {
      GTK_ADJUSTMENT(value_pair->adjustment)->value = *value_pair->data.i;
      sprintf (buffer, "%d", *value_pair->data.i);
    }
  else
    {
      GTK_ADJUSTMENT(value_pair->adjustment)->value = *value_pair->data.d;
      sprintf (buffer, "%0.2f", *value_pair->data.d);
    }
  gtk_signal_emit_by_name (value_pair->adjustment, "value_changed");

  gtk_signal_handler_block(GTK_OBJECT(value_pair->entry),
			   value_pair->entry_handler_id);
  gtk_entry_set_text (GTK_ENTRY (value_pair->entry), buffer);
  gtk_signal_handler_unblock(GTK_OBJECT(value_pair->entry),
			     value_pair->entry_handler_id);
}

static void
value_pair_button_release (GtkWidget *widget,
		       GdkEventButton *event,
		       gpointer data)
{
  val_changed_update();
}

static void
value_pair_scale_callback (GtkAdjustment *adjustment,
			   ValuePair *value_pair)
{
  gchar buffer[32];
  gint changed = FALSE;

  if (value_pair->type == VALUE_PAIR_DOUBLE)
    {
      if ((gfloat)*value_pair->data.d != adjustment->value)
	{
	  changed = TRUE;
	  *value_pair->data.d = adjustment->value;
	  sprintf (buffer, "%0.2f", adjustment->value);
	}
    }
  else
    {
      if (*value_pair->data.i != (gint)adjustment->value)
	{
	  changed = TRUE;
	  *value_pair->data.i = adjustment->value;
	  sprintf (buffer, "%d", (gint)adjustment->value);
	}
    }
  if (changed)
    {
      gtk_signal_handler_block(GTK_OBJECT(value_pair->entry),
			       value_pair->entry_handler_id);
      gtk_entry_set_text (GTK_ENTRY (value_pair->entry), buffer);
      gtk_signal_handler_unblock(GTK_OBJECT(value_pair->entry),
				 value_pair->entry_handler_id);
    }
}

static void
value_pair_entry_callback (GtkWidget   *widget,
			   ValuePair   *value_pair)
{
  GtkAdjustment *adjustment = GTK_ADJUSTMENT(value_pair->adjustment);
  gdouble new_value;
  gdouble old_value;

  if (value_pair->type == VALUE_PAIR_INT)
    {
      old_value = *value_pair->data.i;
      new_value = atoi(gtk_entry_get_text(GTK_ENTRY(widget)));
    }
  else
    {
      old_value = *value_pair->data.d;
      new_value = atof(gtk_entry_get_text(GTK_ENTRY(widget)));
    }

  if (floor(0.5+old_value*10000) != floor(0.5+new_value*10000))
    {
      if ((new_value >= adjustment->lower) &&
	  (new_value <= adjustment->upper))
	{
	  if (value_pair->type == VALUE_PAIR_INT)
	    *value_pair->data.i = new_value;
	  else
	    *value_pair->data.d = new_value;
	  adjustment->value = new_value;
	  gtk_signal_emit_by_name(GTK_OBJECT(adjustment), "value_changed");

	  val_changed_update();
	}
    }
}

static void
value_pair_destroy_callback (GtkWidget   *widget,
			     ValuePair   *value_pair)
{
  if (value_pair->scale)
    gtk_object_unref (GTK_OBJECT (value_pair->scale));
  gtk_object_unref (value_pair->adjustment);
}
 
static void
design_op_callback (GtkWidget *widget, gpointer data)
{
  DesignOp op = (DesignOp)data;

  if (op != ifsDesign->op)
    {
      switch (ifsDesign->op)
	{
	case OP_TRANSLATE:
	  gtk_signal_handler_block(GTK_OBJECT(ifsD->move_button),
				   ifsD->move_handler);
	  gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(ifsD->move_button),
				      FALSE);
	  gtk_signal_handler_unblock(GTK_OBJECT(ifsD->move_button),
				     ifsD->move_handler);
	  break;
	case OP_ROTATE:
	  gtk_signal_handler_block(GTK_OBJECT(ifsD->rotate_button),
				   ifsD->rotate_handler);
	  gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(ifsD->rotate_button),
				      FALSE);
	  gtk_signal_handler_unblock(GTK_OBJECT(ifsD->rotate_button),
				     ifsD->rotate_handler);
	  break;
	case OP_STRETCH:
	  gtk_signal_handler_block(GTK_OBJECT(ifsD->stretch_button),
				   ifsD->stretch_handler);
	  gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(ifsD->stretch_button),
				      FALSE);
	  gtk_signal_handler_unblock(GTK_OBJECT(ifsD->stretch_button),
				     ifsD->stretch_handler);
	  break;
	}
      ifsDesign->op = op;
    }
  else
    {
      GTK_TOGGLE_BUTTON(widget)->active = TRUE;
    }
}

static void
design_op_update_callback (GtkWidget *widget, gpointer data)
{
  DesignOp op = (DesignOp)data;

  if (op != ifsDesign->op)
    {
      switch (op)
	{
	case OP_TRANSLATE:
	  gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(ifsD->move_button),
				      TRUE);
	  break;
	case OP_ROTATE:
	  gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(ifsD->rotate_button),
				      TRUE);
	  break;
	case OP_STRETCH:
	  gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(ifsD->stretch_button),
				      TRUE);
	  break;
	}
    }
}

static void
recompute_center_cb(GtkWidget *w, gpointer data)
{
  recompute_center(TRUE);
}

static void
recompute_center(int save_undo)
{
  int i;
  gdouble x,y;
  gdouble center_x = 0.0;
  gdouble center_y = 0.0;

  gdouble width = ifsDesign->area->allocation.width;
  gdouble height = ifsDesign->area->allocation.height;

  if (save_undo)
    undo_begin();

  for (i=0;i<ifsvals.num_elements;i++)
    {
      if (save_undo)
	undo_update(i);
      aff_element_compute_trans(elements[i],1,ifsvals.aspect_ratio,
				ifsvals.center_x, ifsvals.center_y);
      aff2_fixed_point(&elements[i]->trans,&x,&y);
      center_x += x;
      center_y += y;
    }

  ifsvals.center_x = center_x/ifsvals.num_elements;
  ifsvals.center_y = center_y/ifsvals.num_elements;

  for (i=0;i<ifsvals.num_elements;i++)
    {
	aff_element_decompose_trans(elements[i],&elements[i]->trans,
				    1,ifsvals.aspect_ratio,
				    ifsvals.center_x, ifsvals.center_y);
    }

  if (width > 1 && height > 1)
    {
      for (i=0;i<ifsvals.num_elements;i++)
	aff_element_compute_trans(elements[i],width,height,
				  ifsvals.center_x, ifsvals.center_y);
      design_area_redraw();
      update_values();
    }
}

static void
auto_preview_callback (GtkWidget *widget,
		       gpointer data)
{
  if (ifsD->auto_preview)
    {
      ifsD->auto_preview = 0;
    }
  else
    {
      ifsD->auto_preview = 1;
      ifs_compose_preview_callback(NULL, ifsD->preview);
    }
}

static void
flip_check_button_callback (GtkWidget *widget,
		      gpointer data)
{
  ifsD->current_vals.flip = GTK_TOGGLE_BUTTON(widget)->active;
  val_changed_update();
}

static void
ifs_options_close_callback ()
{
  if (ifsOptD)
    gtk_widget_hide(ifsOptD->dialog);
}

static void
ifs_compose_set_defaults ()
{
  gint i;
  IfsColor color;
  guchar rc,bc,gc;

  gimp_palette_get_foreground (&rc,&gc,&bc);

  color.vals[0] = (gdouble)rc/255;
  color.vals[1] = (gdouble)gc/255;
  color.vals[2] = (gdouble)bc/255;

  ifsvals.aspect_ratio = (gdouble)ifsD->drawable_height/ifsD->drawable_width;

  for (i=0;i<ifsvals.num_elements;i++)
    aff_element_free(elements[i]);

  ifsvals.num_elements = 3;
  elements = g_realloc(elements, ifsvals.num_elements*sizeof(AffElement *));
  element_selected = g_realloc(element_selected,
			       ifsvals.num_elements*sizeof(gint));

  elements[0] = aff_element_new(0.3,0.37*ifsvals.aspect_ratio,color,
				element_count++);
  element_selected[0] = FALSE;
  elements[1] = aff_element_new(0.7,0.37*ifsvals.aspect_ratio,color,
				element_count++);
  element_selected[1] = FALSE;
  elements[2] = aff_element_new(0.5,0.7*ifsvals.aspect_ratio,color,
				element_count++);
  element_selected[2] = FALSE;

  ifsvals.center_x = 0.5;
  ifsvals.center_y = 0.5*ifsvals.aspect_ratio;
  ifsvals.iterations = ifsD->drawable_height*ifsD->drawable_width;

  ifsvals.subdivide = 3;
  ifsvals.max_memory = 4096;

  if (ifsOptD)
    {
      value_pair_update(ifsOptD->iterations_pair);
      value_pair_update(ifsOptD->subdivide_pair);
      value_pair_update(ifsOptD->radius_pair);
      value_pair_update(ifsOptD->memory_pair);
    }

  ifsvals.radius = 0.7;

  set_current_element(0);
  element_selected[0] = TRUE;
  recompute_center(FALSE);

  if (ifsD->selected_orig)
    g_free(ifsD->selected_orig);

  ifsD->selected_orig = g_new(AffElement,ifsvals.num_elements);
}

static void
ifs_compose_defaults_callback (GtkWidget *widget,
			       gpointer data)
{
  gint i;
  gdouble width = ifsDesign->area->allocation.width;
  gdouble height = ifsDesign->area->allocation.height;

  undo_begin();
  for (i=0;i<ifsvals.num_elements;i++)
    undo_update(i);

  ifs_compose_set_defaults();

  if (ifsD->auto_preview)
    ifs_compose_preview_callback(NULL, ifsD->preview);

  for (i=0;i<ifsvals.num_elements;i++)
    aff_element_compute_trans(elements[i],width,height,
			      ifsvals.center_x, ifsvals.center_y);

  design_area_redraw();
}

static void
ifs_compose_new_callback (GtkWidget *widget,
			  gpointer   data)
{
  IfsColor color;
  guchar rc,bc,gc;
  gint i;
  gdouble width = ifsDesign->area->allocation.width;
  gdouble height = ifsDesign->area->allocation.height;
  AffElement *elem;

  undo_begin();

  gimp_palette_get_foreground (&rc,&gc,&bc);

  color.vals[0] = (gdouble)rc/255;
  color.vals[1] = (gdouble)gc/255;
  color.vals[2] = (gdouble)bc/255;

  elem = aff_element_new(0.5, 0.5*height/width,color,
			 element_count++);

  ifsvals.num_elements++;
  elements = g_realloc(elements, ifsvals.num_elements*sizeof(AffElement *));
  element_selected = g_realloc(element_selected,
			       ifsvals.num_elements*sizeof(gint));

  for (i=0;i<ifsvals.num_elements-1;i++)
    element_selected[i] = FALSE;
  element_selected[ifsvals.num_elements-1] = TRUE;

  elements[ifsvals.num_elements-1] = elem;
  set_current_element(ifsvals.num_elements-1);

  ifsD->selected_orig = g_realloc(ifsD->selected_orig,
				  ifsvals.num_elements*sizeof(AffElement));
  aff_element_compute_trans(elem,width,height,
			      ifsvals.center_x, ifsvals.center_y);

  design_area_redraw();

  if (ifsD->auto_preview)
    ifs_compose_preview_callback(NULL, ifsD->preview);
}

static void
ifs_compose_delete_callback (GtkWidget *widget,
			     gpointer   data)
{
  gint i;
  gint new_current;
  if (ifsvals.num_elements <= 2)
    return;

  undo_begin();
  undo_update(ifsD->current_element);

  aff_element_free(elements[ifsD->current_element]);

  if (ifsD->current_element < ifsvals.num_elements-1)
    {
      undo_update(ifsvals.num_elements-1);
      elements[ifsD->current_element] = elements[ifsvals.num_elements-1];
      new_current = ifsD->current_element;
    }
  else
    new_current = ifsvals.num_elements-2;

  ifsvals.num_elements--;

  for (i=0;i<ifsvals.num_elements;i++)
    if (element_selected[i])
      {
	new_current = i;
	break;
      }

  element_selected[new_current] = TRUE;
  set_current_element(new_current);

  design_area_redraw();

  if (ifsD->auto_preview)
    ifs_compose_preview_callback(NULL, ifsD->preview);
}

static void
ifs_compose_close_callback (GtkWidget *widget,
			    GtkWidget **destroyed_widget)
{
  *destroyed_widget = NULL;
  gtk_main_quit ();
}

static gint
preview_idle_render()
{
  gint i;
  gint width = GTK_WIDGET(ifsD->preview)->requisition.width;
  gint height = GTK_WIDGET(ifsD->preview)->requisition.height;

  gint iterations = PREVIEW_RENDER_CHUNK;
  if (iterations > ifsD->preview_iterations)
    iterations = ifsD->preview_iterations;

  for (i=0;i<ifsvals.num_elements;i++)
    aff_element_compute_trans(elements[i], width, height,
			      ifsvals.center_x, ifsvals.center_y);

  ifs_render(elements,ifsvals.num_elements,width,height,
	     iterations,&ifsvals,0,height,
	     ifsD->preview_data,NULL,NULL,TRUE);

  for (i=0;i<ifsvals.num_elements;i++)
    aff_element_compute_trans(elements[i],
			      ifsDesign->area->allocation.width,
			      ifsDesign->area->allocation.height,
			      ifsvals.center_x, ifsvals.center_y);

  ifsD->preview_iterations -= iterations;

  for (i = 0; i < height; i++)
    gtk_preview_draw_row (GTK_PREVIEW (ifsD->preview),
			  ifsD->preview_data + i * width * 3,
			  0, i, width);
  gtk_widget_draw (ifsD->preview, NULL);

  return (ifsD->preview_iterations != 0);
}

static void
ifs_compose_preview_callback (GtkWidget *widget,
			      GtkWidget *preview)
{
  /* Expansion isn't really supported for previews */
  gint i;
  gint width = GTK_WIDGET(ifsD->preview)->requisition.width;
  gint height = GTK_WIDGET(ifsD->preview)->requisition.height;
  guchar rc,gc,bc;
  guchar *ptr;

  if (!ifsD->preview_data)
    ifsD->preview_data = g_new(guchar,3*width*height);

  gimp_palette_get_background ( &rc, &gc, &bc );

  ptr = ifsD->preview_data;
  for (i=0;i<width*height;i++)
    {
      *ptr++ = rc;
      *ptr++ = gc;
      *ptr++ = bc;
    }

  if (ifsD->preview_iterations == 0)
    gtk_idle_add ((GtkFunction)preview_idle_render, NULL);

  ifsD->preview_iterations = ifsvals.iterations*((gdouble)width*height/
				 (ifsD->drawable_width*ifsD->drawable_height));
}

static void
ifs_compose_ok_callback (GtkWidget *widget,
			 GtkWidget *window)
{
  ifscint.run = TRUE;

  gtk_widget_destroy (window);
}
