/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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.
 */
#include "config.h"

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

#include "appenv.h"
#include "color_select.h"
#include "colormaps.h"
#include "errors.h"
#include "gimpdnd.h"
#include "gimprc.h"
#include "gimpui.h"
#include "session.h"
#include "color_area.h" /* for color_area_draw_rect */

#include "libgimp/color_selector.h"
#include "libgimp/gimpintl.h"

#define XY_DEF_WIDTH       240
#define XY_DEF_HEIGHT      240
#define Z_DEF_WIDTH        15
#define Z_DEF_HEIGHT       240
#define COLOR_AREA_WIDTH   74
#define COLOR_AREA_HEIGHT  20

#define COLOR_AREA_MASK GDK_EXPOSURE_MASK | \
                        GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | \
			GDK_BUTTON1_MOTION_MASK | GDK_ENTER_NOTIFY_MASK

typedef enum
{
  COLOR_SELECT_OK,
  COLOR_SELECT_CANCEL,
  COLOR_SELECT_UPDATE
} ColorSelectState;

typedef enum
{
  COLOR_SELECT_HUE = 0,
  COLOR_SELECT_SATURATION,
  COLOR_SELECT_VALUE,
  COLOR_SELECT_RED,
  COLOR_SELECT_GREEN,
  COLOR_SELECT_BLUE,
  COLOR_SELECT_HUE_SATURATION,
  COLOR_SELECT_HUE_VALUE,
  COLOR_SELECT_SATURATION_VALUE,
  COLOR_SELECT_RED_GREEN,
  COLOR_SELECT_RED_BLUE,
  COLOR_SELECT_GREEN_BLUE
} ColorSelectFillType;

typedef enum
{
  UPDATE_VALUES     = 1 << 0,
  UPDATE_POS        = 1 << 1,
  UPDATE_XY_COLOR   = 1 << 2,
  UPDATE_Z_COLOR    = 1 << 3,
  UPDATE_NEW_COLOR  = 1 << 4,
  UPDATE_ORIG_COLOR = 1 << 5,
  UPDATE_CALLER     = 1 << 6
} ColorSelectUpdateType;

typedef void (*ColorSelectCallback) (gint, gint, gint, ColorSelectState, void *);

typedef struct _ColorSelect ColorSelect, *ColorSelectP;

struct _ColorSelect
{
  GtkWidget     *xy_color;
  GtkWidget     *z_color;
  GtkWidget     *new_color;
  GtkWidget     *orig_color;
  GtkWidget     *toggles[6];
  GtkWidget     *entries[6];
  GtkWidget     *hex_entry;
  GtkAdjustment *slider_data[6];

  gint      pos[3];
  gint      values[6];
  gint      z_color_fill;
  gint      xy_color_fill;
  gint      orig_values[3];
  gboolean  wants_updates;
  GdkGC    *gc;

  ColorSelectCallback  callback;
  void                *client_data;
};

typedef struct _ColorSelectFill ColorSelectFill;

typedef void (*ColorSelectFillUpdateProc) (ColorSelectFill *);

struct _ColorSelectFill
{
  guchar *buffer;
  gint    y;
  gint    width;
  gint    height;
  gint   *values;

  ColorSelectFillUpdateProc update;
};

static GtkWidget * color_select_widget_new (ColorSelect *, gint, gint, gint);

static void color_select_drag_new_color    (GtkWidget *,
					    guchar *, guchar *, guchar *,
					    gpointer);
static void color_select_drop_new_color    (GtkWidget *,
					    guchar, guchar, guchar,
					    gpointer);
static void color_select_drag_old_color    (GtkWidget *,
					    guchar *, guchar *, guchar *,
					    gpointer);

static void color_select_update            (ColorSelect *,
					    ColorSelectUpdateType);
static void color_select_update_caller     (ColorSelect *);
static void color_select_update_values     (ColorSelect *);
static void color_select_update_rgb_values (ColorSelect *);
static void color_select_update_hsv_values (ColorSelect *);
static void color_select_update_pos        (ColorSelect *);
static void color_select_update_sliders    (ColorSelect *, gint);
static void color_select_update_entries    (ColorSelect *, gint);
static void color_select_update_colors     (ColorSelect *, gint);

static gint color_select_xy_expose       (GtkWidget *, GdkEventExpose *,
					  ColorSelect *);
static gint color_select_xy_events       (GtkWidget *, GdkEvent *,
					  ColorSelect *);
static gint color_select_z_expose        (GtkWidget *, GdkEventExpose *,
					  ColorSelect *);
static gint color_select_z_events        (GtkWidget *, GdkEvent *,
					  ColorSelect *);
static gint color_select_color_events    (GtkWidget *, GdkEvent *);
static void color_select_slider_update   (GtkAdjustment *, gpointer);
static void color_select_entry_update    (GtkWidget *, gpointer);
static void color_select_toggle_update   (GtkWidget *, gpointer);
static gint color_select_hex_entry_leave (GtkWidget *, GdkEvent *, gpointer);

static void color_select_image_fill      (GtkWidget *, ColorSelectFillType,
					  gint *);

static void color_select_draw_z_marker   (ColorSelect *, GdkRectangle *);
static void color_select_draw_xy_marker  (ColorSelect *, GdkRectangle *);

static void color_select_update_red              (ColorSelectFill *);
static void color_select_update_green            (ColorSelectFill *);
static void color_select_update_blue             (ColorSelectFill *);
static void color_select_update_hue              (ColorSelectFill *);
static void color_select_update_saturation       (ColorSelectFill *);
static void color_select_update_value            (ColorSelectFill *);
static void color_select_update_red_green        (ColorSelectFill *);
static void color_select_update_red_blue         (ColorSelectFill *);
static void color_select_update_green_blue       (ColorSelectFill *);
static void color_select_update_hue_saturation   (ColorSelectFill *);
static void color_select_update_hue_value        (ColorSelectFill *);
static void color_select_update_saturation_value (ColorSelectFill *);

static GtkWidget * color_select_notebook_new      (gint, gint, gint,
						   GimpColorSelector_Callback,
						   void *, void **);
static void        color_select_notebook_free     (void *);
static void        color_select_notebook_setcolor (void *, gint, gint, gint,
						   gboolean);
static void        color_select_notebook_update_callback (gint, gint, gint,
						          ColorSelectState,
							  void *);

/*  Static variables  */
static ColorSelectFillUpdateProc update_procs[] =
{
  color_select_update_hue,
  color_select_update_saturation,
  color_select_update_value,
  color_select_update_red,
  color_select_update_green,
  color_select_update_blue,
  color_select_update_hue_saturation,
  color_select_update_hue_value,
  color_select_update_saturation_value,
  color_select_update_red_green,
  color_select_update_red_blue,
  color_select_update_green_blue,
};

/*  dnd stuff  */
static GtkTargetEntry color_select_target_table[] =
{
  GIMP_TARGET_COLOR
};
static guint n_color_select_targets = (sizeof (color_select_target_table) /
				       sizeof (color_select_target_table[0]));


/*  Register the GIMP colour selector with the color notebook  */
void
color_select_init (void)
{
  GimpColorSelectorMethods methods =
  {
    color_select_notebook_new,
    color_select_notebook_free,
    color_select_notebook_setcolor
  };

  gimp_color_selector_register ("GIMP", "built_in.html", &methods);
}


static GtkWidget *
color_select_widget_new (ColorSelect *csp,
			 gint         r,
			 gint         g,
			 gint         b)
{
  static gchar  *toggle_titles[6] = { "H", "S", "V", "R", "G", "B" };
  static gfloat  slider_max_vals[6] = { 360, 100, 100, 255, 255, 255 };
  static gfloat  slider_incs[6] = { 0.1, 0.1, 0.1, 1.0, 1.0, 1.0 };

  GtkWidget *main_vbox;
  GtkWidget *main_hbox;
  GtkWidget *xy_frame;
  GtkWidget *z_frame;
  GtkWidget *colors_frame;
  GtkWidget *colors_hbox;
  GtkWidget *right_vbox;
  GtkWidget *table;
  GtkWidget *slider;
  GtkWidget *hex_hbox;
  GtkWidget *label;
  GSList *group;
  gchar buffer[16];
  gint i;

  main_vbox = gtk_vbox_new (FALSE, 2);
  gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 2);

  main_hbox = gtk_hbox_new (FALSE, 2);
  gtk_container_set_border_width (GTK_CONTAINER (main_hbox), 0);
  gtk_box_pack_start (GTK_BOX (main_vbox), main_hbox, TRUE, TRUE, 2);
  gtk_widget_show (main_hbox);

  /*  The x/y component preview  */
  xy_frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (xy_frame), GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX (main_hbox), xy_frame, FALSE, FALSE, 2);
  gtk_widget_show (xy_frame);

  csp->xy_color = gtk_preview_new (GTK_PREVIEW_COLOR);
  gtk_preview_set_dither (GTK_PREVIEW (csp->xy_color), GDK_RGB_DITHER_MAX);
  gtk_preview_size (GTK_PREVIEW (csp->xy_color), XY_DEF_WIDTH, XY_DEF_HEIGHT);
  gtk_widget_set_events (csp->xy_color, COLOR_AREA_MASK);
  gtk_signal_connect_after (GTK_OBJECT (csp->xy_color), "expose_event",
			    (GtkSignalFunc) color_select_xy_expose,
			    csp);
  gtk_signal_connect (GTK_OBJECT (csp->xy_color), "event",
		      (GtkSignalFunc) color_select_xy_events,
		      csp);
  gtk_container_add (GTK_CONTAINER (xy_frame), csp->xy_color);
  gtk_widget_show (csp->xy_color);

  /*  dnd stuff  */
  gtk_drag_dest_set (csp->xy_color,
                     GTK_DEST_DEFAULT_HIGHLIGHT |
                     GTK_DEST_DEFAULT_MOTION |
                     GTK_DEST_DEFAULT_DROP,
                     color_select_target_table, n_color_select_targets,
                     GDK_ACTION_COPY);
  gimp_dnd_color_dest_set (csp->xy_color, color_select_drop_new_color, csp);

  /*  The z component preview  */
  z_frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (z_frame), GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX (main_hbox), z_frame, FALSE, FALSE, 2);
  gtk_widget_show (z_frame);

  csp->z_color = gtk_preview_new (GTK_PREVIEW_COLOR);
  gtk_preview_set_dither (GTK_PREVIEW (csp->z_color), GDK_RGB_DITHER_MAX);
  gtk_preview_size (GTK_PREVIEW (csp->z_color), Z_DEF_WIDTH, Z_DEF_HEIGHT);
  gtk_widget_set_events (csp->z_color, COLOR_AREA_MASK);
  gtk_signal_connect_after (GTK_OBJECT (csp->z_color), "expose_event",
			    (GtkSignalFunc) color_select_z_expose,
			    csp);
  gtk_signal_connect (GTK_OBJECT (csp->z_color), "event",
		      (GtkSignalFunc) color_select_z_events,
		      csp);
  gtk_container_add (GTK_CONTAINER (z_frame), csp->z_color);
  gtk_widget_show (csp->z_color);

  /*  The right vertical box with old/new color area and color space sliders  */
  right_vbox = gtk_vbox_new (FALSE, 2);
  gtk_container_set_border_width (GTK_CONTAINER (right_vbox), 0);
  gtk_box_pack_start (GTK_BOX (main_hbox), right_vbox, TRUE, TRUE, 0);
  gtk_widget_show (right_vbox);

  /*  The old/new color area frame and hbox  */
  colors_frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (colors_frame), GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX (right_vbox), colors_frame, FALSE, FALSE, 0);
  gtk_widget_show (colors_frame);

  colors_hbox = gtk_hbox_new (TRUE, 2);
  gtk_container_add (GTK_CONTAINER (colors_frame), colors_hbox);
  gtk_widget_show (colors_hbox);

  /*  The new color area  */
  csp->new_color = gtk_drawing_area_new ();
  gtk_drawing_area_size (GTK_DRAWING_AREA (csp->new_color),
			 COLOR_AREA_WIDTH, COLOR_AREA_HEIGHT);
  gtk_widget_set_events (csp->new_color, GDK_EXPOSURE_MASK);
  gtk_signal_connect (GTK_OBJECT (csp->new_color), "event",
		      (GtkSignalFunc) color_select_color_events,
		      csp);
  gtk_object_set_user_data (GTK_OBJECT (csp->new_color), csp);
  gtk_box_pack_start (GTK_BOX (colors_hbox), csp->new_color, TRUE, TRUE, 0);
  gtk_widget_show (csp->new_color);

  /*  dnd stuff  */
  gtk_drag_source_set (csp->new_color,
                       GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
                       color_select_target_table, n_color_select_targets,
                       GDK_ACTION_COPY | GDK_ACTION_MOVE);
  gimp_dnd_color_source_set (csp->new_color, color_select_drag_new_color, csp);

  gtk_drag_dest_set (csp->new_color,
                     GTK_DEST_DEFAULT_HIGHLIGHT |
                     GTK_DEST_DEFAULT_MOTION |
                     GTK_DEST_DEFAULT_DROP,
                     color_select_target_table, n_color_select_targets,
                     GDK_ACTION_COPY);
  gimp_dnd_color_dest_set (csp->new_color, color_select_drop_new_color, csp);

  /*  The old color area  */
  csp->orig_color = gtk_drawing_area_new ();
  gtk_drawing_area_size (GTK_DRAWING_AREA (csp->orig_color),
			 COLOR_AREA_WIDTH, COLOR_AREA_HEIGHT);
  gtk_widget_set_events (csp->orig_color, GDK_EXPOSURE_MASK);
  gtk_signal_connect (GTK_OBJECT (csp->orig_color), "event",
		      (GtkSignalFunc) color_select_color_events,
		      csp);
  gtk_object_set_user_data (GTK_OBJECT (csp->orig_color), csp);
  gtk_box_pack_start (GTK_BOX (colors_hbox), csp->orig_color, TRUE, TRUE, 0);
  gtk_widget_show (csp->orig_color);

  /*  dnd stuff  */
  gtk_drag_source_set (csp->orig_color,
                       GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
                       color_select_target_table, n_color_select_targets,
                       GDK_ACTION_COPY | GDK_ACTION_MOVE);
  gimp_dnd_color_source_set (csp->orig_color, color_select_drag_old_color, csp);

  /*  The color space sliders, toggle buttons and entries  */
  table = gtk_table_new (6, 3, FALSE);
  gtk_table_set_row_spacings (GTK_TABLE (table), 3);
  gtk_table_set_col_spacings (GTK_TABLE (table), 3);
  gtk_container_set_border_width (GTK_CONTAINER (table), 2);
  gtk_box_pack_start (GTK_BOX (right_vbox), table, TRUE, TRUE, 0);
  gtk_widget_show (table);

  group = NULL;
  for (i = 0; i < 6; i++)
    {
      csp->toggles[i] =
	gtk_radio_button_new_with_label (group, toggle_titles[i]);
      group = gtk_radio_button_group (GTK_RADIO_BUTTON (csp->toggles[i]));
      gtk_table_attach (GTK_TABLE (table), csp->toggles[i],
			0, 1, i, i+1, GTK_FILL, GTK_EXPAND, 0, 0);
      gtk_signal_connect (GTK_OBJECT (csp->toggles[i]), "toggled",
			  (GtkSignalFunc) color_select_toggle_update,
			  csp);
      gtk_widget_show (csp->toggles[i]);

      csp->slider_data[i] =
	GTK_ADJUSTMENT (gtk_adjustment_new (csp->values[i], 0.0,
					    slider_max_vals[i],
					    slider_incs[i],
					    1.0, 0.0));

      slider = gtk_hscale_new (csp->slider_data[i]);
      gtk_table_attach (GTK_TABLE (table), slider, 1, 2, i, i+1,
			GTK_EXPAND | GTK_FILL, GTK_EXPAND, 0, 0);
      gtk_scale_set_value_pos (GTK_SCALE (slider), GTK_POS_TOP);
      gtk_scale_set_draw_value (GTK_SCALE (slider), FALSE);
      gtk_signal_connect (GTK_OBJECT (csp->slider_data[i]), "value_changed",
			  (GtkSignalFunc) color_select_slider_update,
			  csp);
      gtk_widget_show (slider);

      csp->entries[i] = gtk_entry_new ();
      g_snprintf (buffer, sizeof (buffer), "%d", csp->values[i]);
      gtk_entry_set_text (GTK_ENTRY (csp->entries[i]), buffer);
      gtk_widget_set_usize (GTK_WIDGET (csp->entries[i]), 40, 0);
      gtk_table_attach (GTK_TABLE (table), csp->entries[i],
			2, 3, i, i+1, GTK_FILL, GTK_EXPAND, 0, 0);
      gtk_signal_connect (GTK_OBJECT (csp->entries[i]), "changed",
			  (GtkSignalFunc) color_select_entry_update,
			  csp);
      gtk_widget_show (csp->entries[i]);
    }

  /* The hex triplet entry */
  hex_hbox = gtk_hbox_new (FALSE, 3);
  gtk_box_pack_start (GTK_BOX (right_vbox), hex_hbox, FALSE, FALSE, 0);
  gtk_widget_show (hex_hbox);

  csp->hex_entry = gtk_entry_new ();
  g_snprintf (buffer, sizeof (buffer), "#%.2x%.2x%.2x", r, g, b);
  gtk_entry_set_text (GTK_ENTRY (csp->hex_entry), buffer);
  gtk_widget_set_usize (GTK_WIDGET (csp->hex_entry), 75, 0);
  gtk_box_pack_end (GTK_BOX (hex_hbox), csp->hex_entry, FALSE, FALSE, 2);
  gtk_signal_connect (GTK_OBJECT (csp->hex_entry), "focus_out_event",
		      (GtkSignalFunc) color_select_hex_entry_leave,
		      csp);
  gtk_widget_show (csp->hex_entry);

  label = gtk_label_new (_("Hex Triplet:"));
  gtk_box_pack_end (GTK_BOX (hex_hbox), label, FALSE, FALSE, 0);
  gtk_widget_show (label);

  return main_vbox;
}

static void
color_select_drag_new_color (GtkWidget *widget,
			     guchar    *r,
			     guchar    *g,
			     guchar    *b,
			     gpointer   data)
{
  ColorSelect *csp;

  csp = (ColorSelect *) data;

  *r = (guchar) csp->values[COLOR_SELECT_RED];
  *g = (guchar) csp->values[COLOR_SELECT_GREEN];
  *b = (guchar) csp->values[COLOR_SELECT_BLUE];
}

static void
color_select_drop_new_color (GtkWidget *widget,
			     guchar     r,
			     guchar     g,
			     guchar     b,
			     gpointer   data)
{
  ColorSelect *csp;

  csp = (ColorSelect *) data;

  csp->values[COLOR_SELECT_RED]   = (gint) r;
  csp->values[COLOR_SELECT_GREEN] = (gint) g;
  csp->values[COLOR_SELECT_BLUE]  = (gint) b;

  color_select_update_hsv_values (csp);
  color_select_update_pos (csp);
  color_select_update_sliders (csp, -1);
  color_select_update_entries (csp, -1);
  color_select_update_colors (csp, 0);

  color_select_update (csp, UPDATE_Z_COLOR);
  color_select_update (csp, UPDATE_XY_COLOR);
}

static void
color_select_drag_old_color (GtkWidget *widget,
			     guchar    *r,
			     guchar    *g,
			     guchar    *b,
			     gpointer   data)
{
  ColorSelect *csp;

  csp = (ColorSelect *) data;

  *r = (guchar) csp->orig_values[0];
  *g = (guchar) csp->orig_values[1];
  *b = (guchar) csp->orig_values[2];
}

static void
color_select_set_color (ColorSelect *csp,
			gint         r,
			gint         g,
			gint         b,
			gboolean     set_current)
{
  if (csp)
    {
      csp->orig_values[0] = r;
      csp->orig_values[1] = g;
      csp->orig_values[2] = b;

      color_select_update_colors (csp, 1);

      if (set_current)
	{
	  csp->values[COLOR_SELECT_RED] = r;
	  csp->values[COLOR_SELECT_GREEN] = g;
	  csp->values[COLOR_SELECT_BLUE] = b;

	  color_select_update_hsv_values (csp);
	  color_select_update_pos (csp);
	  color_select_update_sliders (csp, -1);
	  color_select_update_entries (csp, -1);
	  color_select_update_colors (csp, 0);

	  color_select_update (csp, UPDATE_Z_COLOR);
	  color_select_update (csp, UPDATE_XY_COLOR);
	}
    }
}

static void
color_select_update (ColorSelect           *csp,
		     ColorSelectUpdateType  update)
{
  if (csp)
    {
      if (update & UPDATE_POS)
	color_select_update_pos (csp);

      if (update & UPDATE_VALUES)
	{
	  color_select_update_values (csp);
	  color_select_update_sliders (csp, -1);
	  color_select_update_entries (csp, -1);

	  if (!(update & UPDATE_NEW_COLOR))
	    color_select_update_colors (csp, 0);
	}

      if (update & UPDATE_XY_COLOR)
	{
	  color_select_image_fill (csp->xy_color, csp->xy_color_fill, csp->values);
	  gtk_widget_draw (csp->xy_color, NULL);
	}

      if (update & UPDATE_Z_COLOR)
	{
	  color_select_image_fill (csp->z_color, csp->z_color_fill, csp->values);
	  gtk_widget_draw (csp->z_color, NULL);
	}

      if (update & UPDATE_NEW_COLOR)
	color_select_update_colors (csp, 0);

      if (update & UPDATE_ORIG_COLOR)
	color_select_update_colors (csp, 1);

      /*if (update & UPDATE_CALLER)*/
      color_select_update_caller (csp);
    }
}

static void
color_select_update_caller (ColorSelect *csp)
{
  if (csp && csp->wants_updates && csp->callback)
    {
      (* csp->callback) (csp->values[COLOR_SELECT_RED],
			 csp->values[COLOR_SELECT_GREEN],
			 csp->values[COLOR_SELECT_BLUE],
			 COLOR_SELECT_UPDATE,
			 csp->client_data);
    }
}

static void
color_select_update_values (ColorSelect *csp)
{
  if (csp)
    {
      switch (csp->z_color_fill)
	{
	case COLOR_SELECT_RED:
	  csp->values[COLOR_SELECT_BLUE] = csp->pos[0];
	  csp->values[COLOR_SELECT_GREEN] = csp->pos[1];
	  csp->values[COLOR_SELECT_RED] = csp->pos[2];
	  break;
	case COLOR_SELECT_GREEN:
	  csp->values[COLOR_SELECT_BLUE] = csp->pos[0];
	  csp->values[COLOR_SELECT_RED] = csp->pos[1];
	  csp->values[COLOR_SELECT_GREEN] = csp->pos[2];
	  break;
	case COLOR_SELECT_BLUE:
	  csp->values[COLOR_SELECT_GREEN] = csp->pos[0];
	  csp->values[COLOR_SELECT_RED] = csp->pos[1];
	  csp->values[COLOR_SELECT_BLUE] = csp->pos[2];
	  break;
	case COLOR_SELECT_HUE:
	  csp->values[COLOR_SELECT_VALUE] = csp->pos[0] * 100 / 255;
	  csp->values[COLOR_SELECT_SATURATION] = csp->pos[1] * 100 / 255;
	  csp->values[COLOR_SELECT_HUE] = csp->pos[2] * 360 / 255;
	  break;
	case COLOR_SELECT_SATURATION:
	  csp->values[COLOR_SELECT_VALUE] = csp->pos[0] * 100 / 255;
	  csp->values[COLOR_SELECT_HUE] = csp->pos[1] * 360 / 255;
	  csp->values[COLOR_SELECT_SATURATION] = csp->pos[2] * 100 / 255;
	  break;
	case COLOR_SELECT_VALUE:
	  csp->values[COLOR_SELECT_SATURATION] = csp->pos[0] * 100 / 255;
	  csp->values[COLOR_SELECT_HUE] = csp->pos[1] * 360 / 255;
	  csp->values[COLOR_SELECT_VALUE] = csp->pos[2] * 100 / 255;
	  break;
	}

      switch (csp->z_color_fill)
	{
	case COLOR_SELECT_RED:
	case COLOR_SELECT_GREEN:
	case COLOR_SELECT_BLUE:
	  color_select_update_hsv_values (csp);
	  break;
	case COLOR_SELECT_HUE:
	case COLOR_SELECT_SATURATION:
	case COLOR_SELECT_VALUE:
	  color_select_update_rgb_values (csp);
	  break;
	}
    }
}

static void
color_select_update_rgb_values (ColorSelect *csp)
{
  gfloat h, s, v;
  gfloat f, p, q, t;

  if (csp)
    {
      h = csp->values[COLOR_SELECT_HUE];
      s = csp->values[COLOR_SELECT_SATURATION] / 100.0;
      v = csp->values[COLOR_SELECT_VALUE] / 100.0;

      if (s == 0)
	{
	  csp->values[COLOR_SELECT_RED] = v * 255;
	  csp->values[COLOR_SELECT_GREEN] = v * 255;
	  csp->values[COLOR_SELECT_BLUE] = v * 255;
	}
      else
	{
	  if (h == 360)
	    h = 0;

	  h /= 60;
	  f = h - (int) h;
	  p = v * (1 - s);
	  q = v * (1 - (s * f));
	  t = v * (1 - (s * (1 - f)));

	  switch ((int) h)
	    {
	    case 0:
	      csp->values[COLOR_SELECT_RED] = v * 255;
	      csp->values[COLOR_SELECT_GREEN] = t * 255;
	      csp->values[COLOR_SELECT_BLUE] = p * 255;
	      break;
	    case 1:
	      csp->values[COLOR_SELECT_RED] = q * 255;
	      csp->values[COLOR_SELECT_GREEN] = v * 255;
	      csp->values[COLOR_SELECT_BLUE] = p * 255;
	      break;
	    case 2:
	      csp->values[COLOR_SELECT_RED] = p * 255;
	      csp->values[COLOR_SELECT_GREEN] = v * 255;
	      csp->values[COLOR_SELECT_BLUE] = t * 255;
	      break;
	    case 3:
	      csp->values[COLOR_SELECT_RED] = p * 255;
	      csp->values[COLOR_SELECT_GREEN] = q * 255;
	      csp->values[COLOR_SELECT_BLUE] = v * 255;
	      break;
	    case 4:
	      csp->values[COLOR_SELECT_RED] = t * 255;
	      csp->values[COLOR_SELECT_GREEN] = p * 255;
	      csp->values[COLOR_SELECT_BLUE] = v * 255;
	      break;
	    case 5:
	      csp->values[COLOR_SELECT_RED] = v * 255;
	      csp->values[COLOR_SELECT_GREEN] = p * 255;
	      csp->values[COLOR_SELECT_BLUE] = q * 255;
	      break;
	    }
	}
    }
}

static void
color_select_update_hsv_values (ColorSelect *csp)
{
  gint r, g, b;
  gfloat h, s, v;
  gint min, max;
  gint delta;

  if (csp)
    {
      r = csp->values[COLOR_SELECT_RED];
      g = csp->values[COLOR_SELECT_GREEN];
      b = csp->values[COLOR_SELECT_BLUE];

      if (r > g)
	{
	  if (r > b)
	    max = r;
	  else
	    max = b;

	  if (g < b)
	    min = g;
	  else
	    min = b;
	}
      else
	{
	  if (g > b)
	    max = g;
	  else
	    max = b;

	  if (r < b)
	    min = r;
	  else
	    min = b;
	}

      v = max;

      if (max != 0)
	s = (max - min) / (float) max;
      else
	s = 0;

      if (s == 0)
	h = 0;
      else
	{
	  h = 0;
	  delta = max - min;
	  if (r == max)
	    h = (g - b) / (float) delta;
	  else if (g == max)
	    h = 2 + (b - r) / (float) delta;
	  else if (b == max)
	    h = 4 + (r - g) / (float) delta;
	  h *= 60;

	  if (h < 0)
	    h += 360;
	}

      csp->values[COLOR_SELECT_HUE] = h;
      csp->values[COLOR_SELECT_SATURATION] = s * 100;
      csp->values[COLOR_SELECT_VALUE] = v * 100 / 255;
    }
}

static void
color_select_update_pos (ColorSelect *csp)
{
  if (csp)
    {
      switch (csp->z_color_fill)
	{
	case COLOR_SELECT_RED:
	  csp->pos[0] = csp->values[COLOR_SELECT_BLUE];
	  csp->pos[1] = csp->values[COLOR_SELECT_GREEN];
	  csp->pos[2] = csp->values[COLOR_SELECT_RED];
	  break;
	case COLOR_SELECT_GREEN:
	  csp->pos[0] = csp->values[COLOR_SELECT_BLUE];
	  csp->pos[1] = csp->values[COLOR_SELECT_RED];
	  csp->pos[2] = csp->values[COLOR_SELECT_GREEN];
	  break;
	case COLOR_SELECT_BLUE:
	  csp->pos[0] = csp->values[COLOR_SELECT_GREEN];
	  csp->pos[1] = csp->values[COLOR_SELECT_RED];
	  csp->pos[2] = csp->values[COLOR_SELECT_BLUE];
	  break;
	case COLOR_SELECT_HUE:
	  csp->pos[0] = csp->values[COLOR_SELECT_VALUE] * 255 / 100;
	  csp->pos[1] = csp->values[COLOR_SELECT_SATURATION] * 255 / 100;
	  csp->pos[2] = csp->values[COLOR_SELECT_HUE] * 255 / 360;
	  break;
	case COLOR_SELECT_SATURATION:
	  csp->pos[0] = csp->values[COLOR_SELECT_VALUE] * 255 / 100;
	  csp->pos[1] = csp->values[COLOR_SELECT_HUE] * 255 / 360;
	  csp->pos[2] = csp->values[COLOR_SELECT_SATURATION] * 255 / 100;
	  break;
	case COLOR_SELECT_VALUE:
	  csp->pos[0] = csp->values[COLOR_SELECT_SATURATION] * 255 / 100;
	  csp->pos[1] = csp->values[COLOR_SELECT_HUE] * 255 / 360;
	  csp->pos[2] = csp->values[COLOR_SELECT_VALUE] * 255 / 100;
	  break;
	}
    }
}

static void
color_select_update_sliders (ColorSelect *csp,
			     gint         skip)
{
  gint i;

  if (csp)
    {
      for (i = 0; i < 6; i++)
	if (i != skip)
	  {
	    csp->slider_data[i]->value = (gfloat) csp->values[i];

	    gtk_signal_handler_block_by_data (GTK_OBJECT (csp->slider_data[i]), csp);
	    gtk_signal_emit_by_name (GTK_OBJECT (csp->slider_data[i]), "value_changed");
	    gtk_signal_handler_unblock_by_data (GTK_OBJECT (csp->slider_data[i]), csp);
	  }
    }
}

static void
color_select_update_entries (ColorSelect *csp,
			     gint         skip)
{
  gchar buffer[16];
  gint i;

  if (csp)
    {
      for (i = 0; i < 6; i++)
	if (i != skip)
	  {
	    g_snprintf (buffer, sizeof (buffer), "%d", csp->values[i]);

	    gtk_signal_handler_block_by_data (GTK_OBJECT (csp->entries[i]), csp);
	    gtk_entry_set_text (GTK_ENTRY (csp->entries[i]), buffer);
	    gtk_signal_handler_unblock_by_data (GTK_OBJECT (csp->entries[i]), csp);
	  }

      g_snprintf (buffer, sizeof (buffer), "#%.2x%.2x%.2x",
		  csp->values[COLOR_SELECT_RED],
		  csp->values[COLOR_SELECT_GREEN],
		  csp->values[COLOR_SELECT_BLUE]);
      gtk_entry_set_text (GTK_ENTRY (csp->hex_entry), buffer);
    }
}

static void
color_select_update_colors (ColorSelect *csp,
			    gint         which)
{
  GdkWindow *window;
  GdkColor color;
  gint red, green, blue;
  gint width, height;

  if (csp)
    {
      if (which)
	{
	  window = csp->orig_color->window;
	  color.pixel = old_color_pixel;
	  red   = csp->orig_values[0];
	  green = csp->orig_values[1];
	  blue  = csp->orig_values[2];
	}
      else
	{
	  window = csp->new_color->window;
	  color.pixel = new_color_pixel;
	  red   = csp->values[COLOR_SELECT_RED];
	  green = csp->values[COLOR_SELECT_GREEN];
	  blue  = csp->values[COLOR_SELECT_BLUE];
	}

      /* if we haven't yet been realised, there's no need to redraw
       * anything. */
      if (!window)
	return;

      color.pixel = get_color (red, green, blue);

      gdk_window_get_size (window, &width, &height);

      if (csp->gc)
	{
#ifdef OLD_COLOR_AREA
	  gdk_gc_set_foreground (csp->gc, &color);
	  gdk_draw_rectangle (window, csp->gc, 1,
			      0, 0, width, height);
#else
	  color_area_draw_rect (window, csp->gc,
				0, 0, width, height,
				red, green, blue);
#endif
	}
    }
}

static gint
color_select_xy_expose (GtkWidget      *widget,
			GdkEventExpose *event,
			ColorSelect    *csp)
{
  if (!csp->gc)
    csp->gc = gdk_gc_new (widget->window);

  color_select_draw_xy_marker (csp, &event->area);

  return FALSE;
}

static gint
color_select_xy_events (GtkWidget   *widget,
			GdkEvent    *event,
			ColorSelect *csp)
{
  GdkEventButton *bevent;
  GdkEventMotion *mevent;
  gint tx, ty;

  switch (event->type)
    {
    case GDK_BUTTON_PRESS:
      bevent = (GdkEventButton *) event;

      color_select_draw_xy_marker (csp, NULL);

      csp->pos[0] = (bevent->x * 255) / (XY_DEF_WIDTH - 1);
      csp->pos[1] = 255 - (bevent->y * 255) / (XY_DEF_HEIGHT - 1);

      if (csp->pos[0] < 0)
	csp->pos[0] = 0;
      if (csp->pos[0] > 255)
	csp->pos[0] = 255;
      if (csp->pos[1] < 0)
	csp->pos[1] = 0;
      if (csp->pos[1] > 255)
	csp->pos[1] = 255;

      gdk_pointer_grab (csp->xy_color->window, FALSE,
			GDK_POINTER_MOTION_HINT_MASK |
			GDK_BUTTON1_MOTION_MASK |
			GDK_BUTTON_RELEASE_MASK,
			NULL, NULL, bevent->time);
      color_select_draw_xy_marker (csp, NULL);

      color_select_update (csp, UPDATE_VALUES);
      break;

    case GDK_BUTTON_RELEASE:
      bevent = (GdkEventButton *) event;

      color_select_draw_xy_marker (csp, NULL);

      csp->pos[0] = (bevent->x * 255) / (XY_DEF_WIDTH - 1);
      csp->pos[1] = 255 - (bevent->y * 255) / (XY_DEF_HEIGHT - 1);

      if (csp->pos[0] < 0)
	csp->pos[0] = 0;
      if (csp->pos[0] > 255)
	csp->pos[0] = 255;
      if (csp->pos[1] < 0)
	csp->pos[1] = 0;
      if (csp->pos[1] > 255)
	csp->pos[1] = 255;

      gdk_pointer_ungrab (bevent->time);
      color_select_draw_xy_marker (csp, NULL);
      color_select_update (csp, UPDATE_VALUES);
      break;

    case GDK_MOTION_NOTIFY:
      mevent = (GdkEventMotion *) event;
      if (mevent->is_hint)
	{
	  gdk_window_get_pointer (widget->window, &tx, &ty, NULL);
	  mevent->x = tx;
	  mevent->y = ty;
	}

      color_select_draw_xy_marker (csp, NULL);

      csp->pos[0] = (mevent->x * 255) / (XY_DEF_WIDTH - 1);
      csp->pos[1] = 255 - (mevent->y * 255) / (XY_DEF_HEIGHT - 1);

      if (csp->pos[0] < 0)
	csp->pos[0] = 0;
      if (csp->pos[0] > 255)
	csp->pos[0] = 255;
      if (csp->pos[1] < 0)
	csp->pos[1] = 0;
      if (csp->pos[1] > 255)
	csp->pos[1] = 255;

      color_select_draw_xy_marker (csp, NULL);
      color_select_update (csp, UPDATE_VALUES);
      break;

    default:
      break;
    }

  return FALSE;
}

static gint
color_select_z_expose (GtkWidget      *widget,
		       GdkEventExpose *event,
		       ColorSelect    *csp)
{
  if (!csp->gc)
    csp->gc = gdk_gc_new (widget->window);

  color_select_draw_z_marker (csp, &event->area);

  return FALSE;
}

static gint
color_select_z_events (GtkWidget   *widget,
		       GdkEvent    *event,
		       ColorSelect *csp)
{
  GdkEventButton *bevent;
  GdkEventMotion *mevent;
  gint tx, ty;

  switch (event->type)
    {
    case GDK_BUTTON_PRESS:
      bevent = (GdkEventButton *) event;

      color_select_draw_z_marker (csp, NULL);

      csp->pos[2] = 255 - (bevent->y * 255) / (Z_DEF_HEIGHT - 1);
      if (csp->pos[2] < 0)
	csp->pos[2] = 0;
      if (csp->pos[2] > 255)
	csp->pos[2] = 255;

      gdk_pointer_grab (csp->z_color->window, FALSE,
			GDK_POINTER_MOTION_HINT_MASK |
			GDK_BUTTON1_MOTION_MASK |
			GDK_BUTTON_RELEASE_MASK,
			NULL, NULL, bevent->time);
      color_select_draw_z_marker (csp, NULL);
      color_select_update (csp, UPDATE_VALUES);
      break;

    case GDK_BUTTON_RELEASE:
      bevent = (GdkEventButton *) event;

      color_select_draw_z_marker (csp, NULL);

      csp->pos[2] = 255 - (bevent->y * 255) / (Z_DEF_HEIGHT - 1);
      if (csp->pos[2] < 0)
	csp->pos[2] = 0;
      if (csp->pos[2] > 255)
	csp->pos[2] = 255;

      gdk_pointer_ungrab (bevent->time);
      color_select_draw_z_marker (csp, NULL);
      color_select_update (csp, UPDATE_VALUES | UPDATE_XY_COLOR);
      break;

    case GDK_MOTION_NOTIFY:
      mevent = (GdkEventMotion *) event;
      if (mevent->is_hint)
	{
	  gdk_window_get_pointer (widget->window, &tx, &ty, NULL);
	  mevent->x = tx;
	  mevent->y = ty;
	}

      color_select_draw_z_marker (csp, NULL);

      csp->pos[2] = 255 - (mevent->y * 255) / (Z_DEF_HEIGHT - 1);
      if (csp->pos[2] < 0)
	csp->pos[2] = 0;
      if (csp->pos[2] > 255)
	csp->pos[2] = 255;

      color_select_draw_z_marker (csp, NULL);
      color_select_update (csp, UPDATE_VALUES);
      break;

    default:
      break;
    }

  return FALSE;
}

static gint
color_select_color_events (GtkWidget *widget,
			   GdkEvent  *event)
{
  ColorSelect *csp;

  csp = (ColorSelect *) gtk_object_get_user_data (GTK_OBJECT (widget));

  if (!csp)
    return FALSE;

  switch (event->type)
    {
    case GDK_EXPOSE:
      if (!csp->gc)
	csp->gc = gdk_gc_new (widget->window);

      if (widget == csp->new_color)
	color_select_update (csp, UPDATE_NEW_COLOR);
      else if (widget == csp->orig_color)
	color_select_update (csp, UPDATE_ORIG_COLOR);
      break;

    default:
      break;
    }

  return FALSE;
}

static void
color_select_slider_update (GtkAdjustment *adjustment,
			    gpointer       data)
{
  ColorSelect *csp;
  gint old_values[6];
  gint update_z_marker;
  gint update_xy_marker;
  gint i, j;

  csp = (ColorSelect *) data;

  if (csp)
    {
      for (i = 0; i < 6; i++)
	if (csp->slider_data[i] == adjustment)
	  break;

      for (j = 0; j < 6; j++)
	old_values[j] = csp->values[j];

      csp->values[i] = (int) adjustment->value;

      if ((i >= COLOR_SELECT_HUE) && (i <= COLOR_SELECT_VALUE))
	color_select_update_rgb_values (csp);
      else if ((i >= COLOR_SELECT_RED) && (i <= COLOR_SELECT_BLUE))
	color_select_update_hsv_values (csp);
      color_select_update_sliders (csp, i);
      color_select_update_entries (csp, -1);

      update_z_marker = 0;
      update_xy_marker = 0;
      for (j = 0; j < 6; j++)
	{
	  if (j == csp->z_color_fill)
	    {
	      if (old_values[j] != csp->values[j])
		update_z_marker = 1;
	    }
	  else
	    {
	      if (old_values[j] != csp->values[j])
		update_xy_marker = 1;
	    }
	}

      if (update_z_marker)
	{
	  color_select_draw_z_marker (csp, NULL);
	  color_select_update (csp, UPDATE_POS | UPDATE_XY_COLOR);
	  color_select_draw_z_marker (csp, NULL);
	}
      else
	{
	  if (update_z_marker)
	    color_select_draw_z_marker (csp, NULL);
	  if (update_xy_marker)
	    color_select_draw_xy_marker (csp, NULL);

	  color_select_update (csp, UPDATE_POS);

	  if (update_z_marker)
	    color_select_draw_z_marker (csp, NULL);
	  if (update_xy_marker)
	    color_select_draw_xy_marker (csp, NULL);
	}

      color_select_update (csp, UPDATE_NEW_COLOR);
    }
}

static void
color_select_entry_update (GtkWidget *widget,
			   gpointer   data)
{
  ColorSelect *csp;
  int old_values[6];
  int update_z_marker;
  int update_xy_marker;
  gint i, j;

  csp = (ColorSelect *) data;

  if (csp)
    {
      for (i = 0; i < 6; i++)
	if (csp->entries[i] == widget)
	  break;

      for (j = 0; j < 6; j++)
	old_values[j] = csp->values[j];

      csp->values[i] = atoi (gtk_entry_get_text (GTK_ENTRY (csp->entries[i])));
      if (csp->values[i] == old_values[i])
	return;

      if ((i >= COLOR_SELECT_HUE) && (i <= COLOR_SELECT_VALUE))
	color_select_update_rgb_values (csp);
      else if ((i >= COLOR_SELECT_RED) && (i <= COLOR_SELECT_BLUE))
	color_select_update_hsv_values (csp);
      color_select_update_entries (csp, i);
      color_select_update_sliders (csp, -1);

      update_z_marker = 0;
      update_xy_marker = 0;
      for (j = 0; j < 6; j++)
	{
	  if (j == csp->z_color_fill)
	    {
	      if (old_values[j] != csp->values[j])
		update_z_marker = 1;
	    }
	  else
	    {
	      if (old_values[j] != csp->values[j])
		update_xy_marker = 1;
	    }
	}

      if (update_z_marker)
	{
	  color_select_draw_z_marker (csp, NULL);
	  color_select_update (csp, UPDATE_POS | UPDATE_XY_COLOR);
	  color_select_draw_z_marker (csp, NULL);
	}
      else
	{
	  if (update_z_marker)
	    color_select_draw_z_marker (csp, NULL);
	  if (update_xy_marker)
	    color_select_draw_xy_marker (csp, NULL);

	  color_select_update (csp, UPDATE_POS);

	  if (update_z_marker)
	    color_select_draw_z_marker (csp, NULL);
	  if (update_xy_marker)
	    color_select_draw_xy_marker (csp, NULL);
	}

      color_select_update (csp, UPDATE_NEW_COLOR);
    }
}

static void
color_select_toggle_update (GtkWidget *widget,
			    gpointer   data)
{
  ColorSelect *csp;
  ColorSelectFillType type = COLOR_SELECT_HUE;
  gint i;

  if (!GTK_TOGGLE_BUTTON (widget)->active)
    return;

  csp = (ColorSelect *) data;

  if (csp)
    {
      for (i = 0; i < 6; i++)
	if (widget == csp->toggles[i])
	  type = (ColorSelectFillType) i;

      switch (type)
	{
	case COLOR_SELECT_HUE:
	  csp->z_color_fill = COLOR_SELECT_HUE;
	  csp->xy_color_fill = COLOR_SELECT_SATURATION_VALUE;
	  break;
	case COLOR_SELECT_SATURATION:
	  csp->z_color_fill = COLOR_SELECT_SATURATION;
	  csp->xy_color_fill = COLOR_SELECT_HUE_VALUE;
	  break;
	case COLOR_SELECT_VALUE:
	  csp->z_color_fill = COLOR_SELECT_VALUE;
	  csp->xy_color_fill = COLOR_SELECT_HUE_SATURATION;
	  break;
	case COLOR_SELECT_RED:
	  csp->z_color_fill = COLOR_SELECT_RED;
	  csp->xy_color_fill = COLOR_SELECT_GREEN_BLUE;
	  break;
	case COLOR_SELECT_GREEN:
	  csp->z_color_fill = COLOR_SELECT_GREEN;
	  csp->xy_color_fill = COLOR_SELECT_RED_BLUE;
	  break;
	case COLOR_SELECT_BLUE:
	  csp->z_color_fill = COLOR_SELECT_BLUE;
	  csp->xy_color_fill = COLOR_SELECT_RED_GREEN;
	  break;
	default:
	  break;
	}

      color_select_update (csp, UPDATE_POS);
      color_select_update (csp, UPDATE_Z_COLOR | UPDATE_XY_COLOR);
    }
}

static gint
color_select_hex_entry_leave (GtkWidget *widget,
			      GdkEvent  *event,
			      gpointer   data)
{
  ColorSelect *csp;
  gchar  buffer[8];
  gchar *hex_color;
  guint  hex_rgb;

  csp = (ColorSelect *) data;

  if (csp)
    {
      hex_color = g_strdup (gtk_entry_get_text (GTK_ENTRY (csp->hex_entry)));

      g_snprintf(buffer, sizeof (buffer), "#%.2x%.2x%.2x",
		 csp->values[COLOR_SELECT_RED],
		 csp->values[COLOR_SELECT_GREEN],
		 csp->values[COLOR_SELECT_BLUE]);

      if ((strlen (hex_color) == 7) &&
	  (g_strcasecmp (buffer, hex_color) != 0))
	{
	  if ((sscanf (hex_color, "#%x", &hex_rgb) == 1) &&
	      (hex_rgb < (1 << 24)))
	    color_select_set_color (csp,
				    (hex_rgb & 0xff0000) >> 16,
				    (hex_rgb & 0x00ff00) >> 8,
				    hex_rgb & 0x0000ff,
				    TRUE);
	}

      g_free (hex_color);
    }

  return FALSE;
}

static void
color_select_image_fill (GtkWidget           *preview,
			 ColorSelectFillType  type,
			 gint                *values)
{
  ColorSelectFill csf;
  gint height;

  csf.buffer = g_malloc (preview->requisition.width * 3);

  csf.update = update_procs[type];

  csf.y = -1;
  csf.width = preview->requisition.width;
  csf.height = preview->requisition.height;
  csf.values = values;

  height = csf.height;
  if (height > 0)
    while (height--)
      {
	(* csf.update) (&csf);
	gtk_preview_draw_row (GTK_PREVIEW (preview),
			      csf.buffer, 0, csf.y, csf.width);
      }

  g_free (csf.buffer);
}

static void
color_select_draw_z_marker (ColorSelect  *csp,
			    GdkRectangle *clip)
{
  gint width;
  gint height;
  gint y;
  gint minx;
  gint miny;

  if (csp->gc)
    {
      y = (Z_DEF_HEIGHT - 1) - ((Z_DEF_HEIGHT - 1) * csp->pos[2]) / 255;
      width = csp->z_color->requisition.width;
      height = csp->z_color->requisition.height;
      minx = 0;
      miny = 0;
      if (width <= 0)
	return;

      if (clip)
        {
	  width  = MIN(width,  clip->x + clip->width);
	  height = MIN(height, clip->y + clip->height);
	  minx   = MAX(0, clip->x);
	  miny   = MAX(0, clip->y);
	}

      if (y >= miny && y < height)
        {
	  gdk_gc_set_function (csp->gc, GDK_INVERT);
	  gdk_draw_line (csp->z_color->window, csp->gc, minx, y, width - 1, y);
	  gdk_gc_set_function (csp->gc, GDK_COPY);
	}
    }
}

static void
color_select_draw_xy_marker (ColorSelect  *csp,
			     GdkRectangle *clip)
{
  gint width;
  gint height;
  gint x, y;
  gint minx, miny;

  if (csp->gc)
    {
      x = ((XY_DEF_WIDTH - 1) * csp->pos[0]) / 255;
      y = (XY_DEF_HEIGHT - 1) - ((XY_DEF_HEIGHT - 1) * csp->pos[1]) / 255;
      width = csp->xy_color->requisition.width;
      height = csp->xy_color->requisition.height;
      minx = 0;
      miny = 0;
      if ((width <= 0) || (height <= 0))
	return;

      gdk_gc_set_function (csp->gc, GDK_INVERT);

      if (clip)
        {
	  width  = MIN(width,  clip->x + clip->width);
	  height = MIN(height, clip->y + clip->height);
	  minx   = MAX(0, clip->x);
	  miny   = MAX(0, clip->y);
	}

      if (y >= miny && y < height)
	gdk_draw_line (csp->xy_color->window, csp->gc, minx, y, width - 1, y);

      if (x >= minx && x < width)
	gdk_draw_line (csp->xy_color->window, csp->gc, x, miny, x, height - 1);

      gdk_gc_set_function (csp->gc, GDK_COPY);
    }
}

static void
color_select_update_red (ColorSelectFill *csf)
{
  guchar *p;
  gint i, r;

  p = csf->buffer;

  csf->y += 1;
  r = (csf->height - csf->y + 1) * 255 / csf->height;

  if (r < 0)
    r = 0;
  if (r > 255)
    r = 255;

  for (i = 0; i < csf->width; i++)
    {
      *p++ = r;
      *p++ = 0;
      *p++ = 0;
    }
}

static void
color_select_update_green (ColorSelectFill *csf)
{
  guchar *p;
  gint i, g;

  p = csf->buffer;

  csf->y += 1;
  g = (csf->height - csf->y + 1) * 255 / csf->height;

  if (g < 0)
    g = 0;
  if (g > 255)
    g = 255;

  for (i = 0; i < csf->width; i++)
    {
      *p++ = 0;
      *p++ = g;
      *p++ = 0;
    }
}

static void
color_select_update_blue (ColorSelectFill *csf)
{
  guchar *p;
  gint i, b;

  p = csf->buffer;

  csf->y += 1;
  b = (csf->height - csf->y + 1) * 255 / csf->height;

  if (b < 0)
    b = 0;
  if (b > 255)
    b = 255;

  for (i = 0; i < csf->width; i++)
    {
      *p++ = 0;
      *p++ = 0;
      *p++ = b;
    }
}

static void
color_select_update_hue (ColorSelectFill *csf)
{
  guchar *p;
  gfloat h, f;
  gint r, g, b;
  gint i;

  p = csf->buffer;

  csf->y += 1;
  h = csf->y * 360 / csf->height;

  h = 360 - h;

  if (h < 0)
    h = 0;
  if (h >= 360)
    h = 0;

  h /= 60;
  f = (h - (int) h) * 255;

  r = g = b = 0;

  switch ((int) h)
    {
    case 0:
      r = 255;
      g = f;
      b = 0;
      break;
    case 1:
      r = 255 - f;
      g = 255;
      b = 0;
      break;
    case 2:
      r = 0;
      g = 255;
      b = f;
      break;
    case 3:
      r = 0;
      g = 255 - f;
      b = 255;
      break;
    case 4:
      r = f;
      g = 0;
      b = 255;
      break;
    case 5:
      r = 255;
      g = 0;
      b = 255 - f;
      break;
    }

  for (i = 0; i < csf->width; i++)
    {
      *p++ = r;
      *p++ = g;
      *p++ = b;
    }
}

static void
color_select_update_saturation (ColorSelectFill *csf)
{
  guchar *p;
  gint s;
  gint i;

  p = csf->buffer;

  csf->y += 1;
  s = csf->y * 255 / csf->height;

  if (s < 0)
    s = 0;
  if (s > 255)
    s = 255;

  s = 255 - s;

  for (i = 0; i < csf->width; i++)
    {
      *p++ = s;
      *p++ = s;
      *p++ = s;
    }
}

static void
color_select_update_value (ColorSelectFill *csf)
{
  guchar *p;
  gint v;
  gint i;

  p = csf->buffer;

  csf->y += 1;
  v = csf->y * 255 / csf->height;

  if (v < 0)
    v = 0;
  if (v > 255)
    v = 255;

  v = 255 - v;

  for (i = 0; i < csf->width; i++)
    {
      *p++ = v;
      *p++ = v;
      *p++ = v;
    }
}

static void
color_select_update_red_green (ColorSelectFill *csf)
{
  guchar *p;
  gint i, r, b;
  gfloat g, dg;

  p = csf->buffer;

  csf->y += 1;
  b = csf->values[COLOR_SELECT_BLUE];
  r = (csf->height - csf->y + 1) * 255 / csf->height;

  if (r < 0)
    r = 0;
  if (r > 255)
    r = 255;

  g = 0;
  dg = 255.0 / csf->width;

  for (i = 0; i < csf->width; i++)
    {
      *p++ = r;
      *p++ = g;
      *p++ = b;

      g += dg;
    }
}

static void
color_select_update_red_blue (ColorSelectFill *csf)
{
  guchar *p;
  gint i, r, g;
  gfloat b, db;

  p = csf->buffer;

  csf->y += 1;
  g = csf->values[COLOR_SELECT_GREEN];
  r = (csf->height - csf->y + 1) * 255 / csf->height;

  if (r < 0)
    r = 0;
  if (r > 255)
    r = 255;

  b = 0;
  db = 255.0 / csf->width;

  for (i = 0; i < csf->width; i++)
    {
      *p++ = r;
      *p++ = g;
      *p++ = b;

      b += db;
    }
}

static void
color_select_update_green_blue (ColorSelectFill *csf)
{
  guchar *p;
  gint i, g, r;
  gfloat b, db;

  p = csf->buffer;

  csf->y += 1;
  r = csf->values[COLOR_SELECT_RED];
  g = (csf->height - csf->y + 1) * 255 / csf->height;

  if (g < 0)
    g = 0;
  if (g > 255)
    g = 255;

  b = 0;
  db = 255.0 / csf->width;

  for (i = 0; i < csf->width; i++)
    {
      *p++ = r;
      *p++ = g;
      *p++ = b;

      b += db;
    }
}

static void
color_select_update_hue_saturation (ColorSelectFill *csf)
{
  guchar *p;
  gfloat h, v, s, ds;
  gint f;
  gint i;

  p = csf->buffer;

  csf->y += 1;
  h = 360 - (csf->y * 360 / csf->height);

  if (h < 0)
    h = 0;
  if (h > 359)
    h = 359;

  h /= 60;
  f = (h - (int) h) * 255;

  s = 0;
  ds = 1.0 / csf->width;

  v = csf->values[COLOR_SELECT_VALUE] / 100.0;

  switch ((int) h)
    {
    case 0:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * 255;
	  *p++ = v * (255 - (s * (255 - f)));
	  *p++ = v * 255 * (1 - s);

	  s += ds;
	}
      break;
    case 1:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * (255 - s * f);
	  *p++ = v * 255;
	  *p++ = v * 255 * (1 - s);

	  s += ds;
	}
      break;
    case 2:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * 255 * (1 - s);
	  *p++ = v *255;
	  *p++ = v * (255 - (s * (255 - f)));

	  s += ds;
	}
      break;
    case 3:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * 255 * (1 - s);
	  *p++ = v * (255 - s * f);
	  *p++ = v * 255;

	  s += ds;
	}
      break;
    case 4:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * (255 - (s * (255 - f)));
	  *p++ = v * (255 * (1 - s));
	  *p++ = v * 255;

	  s += ds;
	}
      break;
    case 5:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * 255;
	  *p++ = v * 255 * (1 - s);
	  *p++ = v * (255 - s * f);

	  s += ds;
	}
      break;
    }
}

static void
color_select_update_hue_value (ColorSelectFill *csf)
{
  guchar *p;
  gfloat h, v, dv, s;
  gint f;
  gint i;

  p = csf->buffer;

  csf->y += 1;
  h = 360 - (csf->y * 360 / csf->height);

  if (h < 0)
    h = 0;
  if (h > 359)
    h = 359;

  h /= 60;
  f = (h - (int) h) * 255;

  v = 0;
  dv = 1.0 / csf->width;

  s = csf->values[COLOR_SELECT_SATURATION] / 100.0;

  switch ((int) h)
    {
    case 0:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * 255;
	  *p++ = v * (255 - (s * (255 - f)));
	  *p++ = v * 255 * (1 - s);

	  v += dv;
	}
      break;
    case 1:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * (255 - s * f);
	  *p++ = v * 255;
	  *p++ = v * 255 * (1 - s);

	  v += dv;
	}
      break;
    case 2:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * 255 * (1 - s);
	  *p++ = v *255;
	  *p++ = v * (255 - (s * (255 - f)));

	  v += dv;
	}
      break;
    case 3:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * 255 * (1 - s);
	  *p++ = v * (255 - s * f);
	  *p++ = v * 255;

	  v += dv;
	}
      break;
    case 4:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * (255 - (s * (255 - f)));
	  *p++ = v * (255 * (1 - s));
	  *p++ = v * 255;

	  v += dv;
	}
      break;
    case 5:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * 255;
	  *p++ = v * 255 * (1 - s);
	  *p++ = v * (255 - s * f);

	  v += dv;
	}
      break;
    }
}

static void
color_select_update_saturation_value (ColorSelectFill *csf)
{
  guchar *p;
  gfloat h, v, dv, s;
  gint f;
  gint i;

  p = csf->buffer;

  csf->y += 1;
  s = (float) csf->y / csf->height;

  if (s < 0)
    s = 0;
  if (s > 1)
    s = 1;

  s = 1 - s;

  h = (float) csf->values[COLOR_SELECT_HUE];
  if (h >= 360)
    h -= 360;
  h /= 60;
  f = (h - (int) h) * 255;

  v = 0;
  dv = 1.0 / csf->width;

  switch ((int) h)
    {
    case 0:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * 255;
	  *p++ = v * (255 - (s * (255 - f)));
	  *p++ = v * 255 * (1 - s);

	  v += dv;
	}
      break;
    case 1:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * (255 - s * f);
	  *p++ = v * 255;
	  *p++ = v * 255 * (1 - s);

	  v += dv;
	}
      break;
    case 2:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * 255 * (1 - s);
	  *p++ = v *255;
	  *p++ = v * (255 - (s * (255 - f)));

	  v += dv;
	}
      break;
    case 3:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * 255 * (1 - s);
	  *p++ = v * (255 - s * f);
	  *p++ = v * 255;

	  v += dv;
	}
      break;
    case 4:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * (255 - (s * (255 - f)));
	  *p++ = v * (255 * (1 - s));
	  *p++ = v * 255;

	  v += dv;
	}
      break;
    case 5:
      for (i = 0; i < csf->width; i++)
	{
	  *p++ = v * 255;
	  *p++ = v * 255 * (1 - s);
	  *p++ = v * (255 - s * f);

	  v += dv;
	}
      break;
    }
}


/*****************************/
/* Colour notebook glue      */

typedef struct
{
  GimpColorSelector_Callback  callback;
  gpointer                   *client_data;
  ColorSelect                *csp;
  GtkWidget                  *main_vbox;
} notebook_glue;

static GtkWidget *
color_select_notebook_new (gint                         r,
			   gint                         g,
			   gint                         b,
			   GimpColorSelector_Callback   callback,
			   gpointer                     data,
			   /* RETURNS: */
			   void                       **selector_data)
{
  ColorSelect   *csp;
  notebook_glue *glue;

  glue = g_new (notebook_glue, 1);

  csp = g_new (ColorSelect, 1);

  glue->csp         = csp;
  glue->callback    = callback;
  glue->client_data = data;

  csp->callback      = color_select_notebook_update_callback;
  csp->client_data   = glue;
  csp->z_color_fill  = COLOR_SELECT_HUE;
  csp->xy_color_fill = COLOR_SELECT_SATURATION_VALUE;
  csp->gc            = NULL;
  csp->wants_updates = TRUE;

  csp->values[COLOR_SELECT_RED]   = csp->orig_values[0] = r;
  csp->values[COLOR_SELECT_GREEN] = csp->orig_values[1] = g;
  csp->values[COLOR_SELECT_BLUE]  = csp->orig_values[2] = b;
  color_select_update_hsv_values (csp);
  color_select_update_pos (csp);

  glue->main_vbox = color_select_widget_new (csp, r, g, b);

  color_select_image_fill (csp->z_color, csp->z_color_fill, csp->values);
  color_select_image_fill (csp->xy_color, csp->xy_color_fill, csp->values);

  (*selector_data) = glue;
  return glue->main_vbox;
}

static void
color_select_notebook_free (void *data)
{
  notebook_glue *glue = data;

  gdk_gc_destroy (glue->csp->gc);
  g_free (glue->csp);
  /* don't need to destroy the widget, since it's done by the caller
   * of this function */
  g_free (glue);
}


static void
color_select_notebook_setcolor (void *data,
				int r, int g, int b, int set_current)
{
  notebook_glue *glue = data;

  color_select_set_color (glue->csp, r, g, b, set_current);
}

static void
color_select_notebook_update_callback (gint              r,
				       gint              g,
				       gint              b,
				       ColorSelectState  state,
				       void             *data)
{
  notebook_glue *glue = data;

  switch (state)
    {
    case COLOR_SELECT_UPDATE:
      glue->callback (glue->client_data, r, g, b);
      break;

    default:
      g_warning ("state %d can't happen!", state);
      break;
    }
}
