/* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "appenv.h"
#include "actionarea.h"
#include "buildmenu.h"
#include "colormaps.h"
#include "color_area.h"
#include "color_select.h"
#include "datafiles.h"
#include "errors.h"
#include "general.h"
#include "gimprc.h"
#include "interface.h"
#include "palette.h"

#define ENTRY_WIDTH  14
#define ENTRY_HEIGHT 10
#define SPACING 1
#define COLUMNS 16
#define ROWS 16

#define PREVIEW_WIDTH ((ENTRY_WIDTH * COLUMNS) + (SPACING * (COLUMNS + 1)))
#define PREVIEW_HEIGHT ((ENTRY_HEIGHT * ROWS) + (SPACING * (ROWS + 1)))

#define PALETTE_EVENT_MASK GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_ENTER_NOTIFY_MASK

typedef struct _Palette _Palette, *PaletteP;

struct _Palette {
  GtkWidget *shell;
  GtkWidget *vbox;
  GtkWidget *frame;
  GtkWidget *menu;
  GtkWidget *option_menu;
  GtkWidget *color_area;
  GtkWidget *color_name;
  GtkWidget *palette_ops;
  GdkGC *gc;
  GtkAdjustment *sbar_data;
  PaletteEntriesP entries;
  PaletteEntryP color;
  ColorSelectP color_select;
  int color_select_active;
  int scroll_offset;
  int updating;
};

static void palette_create_palette_menu (PaletteP, PaletteEntriesP);
static PaletteEntryP palette_add_entry (PaletteEntriesP, char *, int, int, int);
static void palette_delete_entry (PaletteP);
static void palette_calc_scrollbar (PaletteP);

static void palette_entries_load (char *);
static GSList * palette_entries_insert_list (GSList *, PaletteEntriesP);
static void palette_entries_delete (char *);
static void palette_entries_save (PaletteEntriesP, char *);
static void palette_entries_free (PaletteEntriesP);
static void palette_entry_free (PaletteEntryP);
static void palette_entries_set_callback (GtkWidget *, gpointer);

static void palette_change_color (int, int, int, int);
static gint palette_color_area_expose (GtkWidget *, GdkEventExpose *, PaletteP);
static gint palette_color_area_events (GtkWidget *, GdkEvent *, PaletteP);
static void palette_scroll_update (GtkAdjustment *, gpointer);
static void palette_new_callback (GtkWidget *, gpointer);
static void palette_delete_callback (GtkWidget *, gpointer);
static void palette_edit_callback (GtkWidget *, gpointer);
static void palette_close_callback (GtkWidget *, gpointer);
static gint palette_dialog_delete_callback (GtkWidget *, GdkEvent *, gpointer);
static void palette_new_entries_callback (GtkWidget *, gpointer);
static void palette_add_entries_callback (GtkWidget *, gpointer, gpointer);
static void palette_merge_entries_callback (GtkWidget *, gpointer);
static void palette_delete_entries_callback (GtkWidget *, gpointer);
static void palette_select_callback (int, int, int, ColorSelectState, void *);

static void palette_draw_entries (PaletteP);
static void palette_draw_current_entry (PaletteP);
static void palette_update_current_entry (PaletteP);

GSList *palette_entries_list = NULL;

static PaletteP         palette = NULL;
static PaletteEntriesP  default_palette_entries = NULL;
static int              num_palette_entries = 0;
static unsigned char    foreground[3] = { 0, 0, 0 };
static unsigned char    background[3] = { 255, 255, 255 };


static ActionAreaItem action_items[] =
{
  { "New", palette_new_callback, NULL, NULL },
  { "Edit", palette_edit_callback, NULL, NULL },
  { "Delete", palette_delete_callback, NULL, NULL },
  { "Close", palette_close_callback, NULL, NULL },
};

static MenuItem palette_ops[] =
{
  { "New Palette", 0, 0, palette_new_entries_callback, NULL, NULL, NULL },
  { "Merge Palette", 0, 0, palette_merge_entries_callback, NULL, NULL, NULL },
  { "Delete Palette", 0, 0, palette_delete_entries_callback, NULL, NULL, NULL },
  { "Close", 0, 0, palette_close_callback, NULL, NULL, NULL },
  { NULL, 0, 0, NULL, NULL, NULL, NULL },
};

void
palettes_init ()
{
  palette_init_palettes ();
}

void
palettes_free ()
{
  palette_free_palettes ();
}

void
palette_create ()
{
  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *sbar;
  GtkWidget *frame;
  GtkWidget *options_box;
  GtkWidget *arrow_hbox;
  GtkWidget *label;
  GtkWidget *arrow;
  GtkWidget *menu_bar;
  GtkWidget *menu_bar_item;
  int i;

  if (!palette)
    {
      palette = g_malloc (sizeof (_Palette));

      palette->entries = default_palette_entries;
      palette->color = NULL;
      palette->color_select = NULL;
      palette->color_select_active = 0;
      palette->scroll_offset = 0;
      palette->gc = NULL;
      palette->updating = FALSE;

      /*  The shell and main vbox  */
      palette->shell = gtk_dialog_new ();
      gtk_window_set_wmclass (GTK_WINDOW (palette->shell), "color_palette", "Gimp");
      gtk_window_set_policy (GTK_WINDOW (palette->shell), FALSE, FALSE, FALSE);
      gtk_window_set_title (GTK_WINDOW (palette->shell), "Color Palette");
      vbox = gtk_vbox_new (FALSE, 1);
      gtk_container_border_width (GTK_CONTAINER (vbox), 1);
      gtk_box_pack_start (GTK_BOX (GTK_DIALOG (palette->shell)->vbox), vbox, TRUE, TRUE, 0);

      /* handle the wm close event */
      gtk_signal_connect (GTK_OBJECT (palette->shell), "delete_event",
			  GTK_SIGNAL_FUNC (palette_dialog_delete_callback),
			  palette);

      /*  The palette options box  */
      options_box = gtk_hbox_new (FALSE, 1);
      gtk_box_pack_start (GTK_BOX (vbox), options_box, FALSE, FALSE, 0);

      /*  The popup menu -- palette_ops  */
      for (i = 0; palette_ops[i].label; i++)
	  palette_ops[i].user_data = palette;

      palette->palette_ops = build_menu (palette_ops, NULL);

      /*  The palette commands pulldown menu  */
      menu_bar = gtk_menu_bar_new ();
      gtk_box_pack_start (GTK_BOX (options_box), menu_bar, FALSE, FALSE, 0);
      menu_bar_item = gtk_menu_item_new ();
      gtk_container_add (GTK_CONTAINER (menu_bar), menu_bar_item);
      gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_bar_item), palette->palette_ops);
      arrow_hbox = gtk_hbox_new (FALSE, 1);
      gtk_container_add (GTK_CONTAINER (menu_bar_item), arrow_hbox);
      label = gtk_label_new ("Ops");
      arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT);
      gtk_box_pack_start (GTK_BOX (arrow_hbox), arrow, FALSE, FALSE, 0);
      gtk_box_pack_start (GTK_BOX (arrow_hbox), label, FALSE, FALSE, 4);
      gtk_misc_set_alignment (GTK_MISC (label), 0.5, 0.5);
      gtk_misc_set_alignment (GTK_MISC (arrow), 0.5, 0.5);

      gtk_widget_show (arrow);
      gtk_widget_show (label);
      gtk_widget_show (arrow_hbox);
      gtk_widget_show (menu_bar_item);
      gtk_widget_show (menu_bar);

      /*  The option menu  */
      palette->option_menu = gtk_option_menu_new ();
      gtk_box_pack_start (GTK_BOX (options_box), palette->option_menu, TRUE, TRUE, 0);
      gtk_widget_show (palette->option_menu);
      gtk_widget_show (options_box);

      /*  The active color name  */
      palette->color_name = gtk_entry_new ();
      gtk_entry_set_text (GTK_ENTRY (palette->color_name), "Active Color Name");
      gtk_box_pack_start (GTK_BOX (vbox), palette->color_name, FALSE, FALSE, 0);

      gtk_widget_show (palette->color_name);

      /*  The horizontal box containing preview & scrollbar  */
      hbox = gtk_hbox_new (FALSE, 1);
      gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
      frame = gtk_frame_new (NULL);
      gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
      gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0);
      palette->sbar_data = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, PREVIEW_HEIGHT, 1, 1, PREVIEW_HEIGHT));
      gtk_signal_connect (GTK_OBJECT (palette->sbar_data), "value_changed",
			  (GtkSignalFunc) palette_scroll_update,
			  palette);
      sbar = gtk_vscrollbar_new (palette->sbar_data);
      gtk_box_pack_start (GTK_BOX (hbox), sbar, FALSE, FALSE, 0);

      /*  Create the color area window and the underlying image  */
      palette->color_area = gtk_preview_new (GTK_PREVIEW_COLOR);
      gtk_preview_size (GTK_PREVIEW (palette->color_area), PREVIEW_WIDTH, PREVIEW_HEIGHT);
      gtk_widget_set_events (palette->color_area, PALETTE_EVENT_MASK);
      gtk_signal_connect_after (GTK_OBJECT (palette->color_area), "expose_event",
				(GtkSignalFunc) palette_color_area_expose,
				palette);
      gtk_signal_connect (GTK_OBJECT (palette->color_area), "event",
			  (GtkSignalFunc) palette_color_area_events,
			  palette);
      gtk_container_add (GTK_CONTAINER (frame), palette->color_area);

      gtk_widget_show (palette->color_area);
      gtk_widget_show (sbar);
      gtk_widget_show (frame);
      gtk_widget_show (hbox);

      /*  The action area  */
      action_items[0].user_data = palette;
      action_items[1].user_data = palette;
      action_items[2].user_data = palette;
      action_items[3].user_data = palette;
      build_action_area (GTK_DIALOG (palette->shell), action_items, 4, 0);

      gtk_widget_show (vbox);
      gtk_widget_show (palette->shell);

      palette_create_palette_menu (palette, default_palette_entries);
      palette_calc_scrollbar (palette);
    }
  else
    {
      if (!GTK_WIDGET_VISIBLE (palette->shell))
	{
	  gtk_widget_show (palette->shell);
	}
      else
	{
	  gdk_window_raise(palette->shell->window);
	}
    }
}

void
palette_free ()
{
  if (palette)
    {
      gdk_gc_destroy (palette->gc);

      if (palette->color_select)
	color_select_free (palette->color_select);

      g_free (palette);

      palette = NULL;
    }
}

void
palette_get_foreground (unsigned char *r,
			unsigned char *g,
			unsigned char *b)
{
  *r = foreground[0];
  *g = foreground[1];
  *b = foreground[2];
}

void
palette_get_background (unsigned char *r,
			unsigned char *g,
			unsigned char *b)
{
  *r = background[0];
  *g = background[1];
  *b = background[2];
}

void
palette_set_foreground (int r,
			int g,
			int b)
{
  unsigned char rr, gg, bb;

  /*  Foreground  */
  foreground[0] = r;
  foreground[1] = g;
  foreground[2] = b;

  palette_get_foreground (&rr, &gg, &bb);
  if (no_interface == FALSE)
    {
      store_color (&foreground_pixel, rr, gg, bb);
      color_area_update ();
    }
}

void
palette_set_background (int r,
			int g,
			int b)
{
  unsigned char rr, gg, bb;

  /*  Background  */
  background[0] = r;
  background[1] = g;
  background[2] = b;

  palette_get_background (&rr, &gg, &bb);
  if (no_interface == FALSE)
    {
      store_color (&background_pixel, rr, gg, bb);
      color_area_update ();
    }
}

void
palette_set_default_colors (void)
{
  palette_set_foreground (0, 0, 0);
  palette_set_background (255, 255, 255);
}


void 
palette_swap_colors (void)
{
  unsigned char fg_r, fg_g, fg_b;
  unsigned char bg_r, bg_g, bg_b;
  
  palette_get_foreground (&fg_r, &fg_g, &fg_b);
  palette_get_background (&bg_r, &bg_g, &bg_b);
  
  palette_set_foreground (bg_r, bg_g, bg_b);
  palette_set_background (fg_r, fg_g, fg_b);
}

void
palette_init_palettes (void)
{
  datafiles_read_directories (palette_path, palette_entries_load, 0);
}


void
palette_free_palettes (void)
{
  GSList *list;
  PaletteEntriesP entries;

  list = palette_entries_list;

  while (list)
    {
      entries = (PaletteEntriesP) list->data;

      /*  If the palette has been changed, save it, if possible  */
      if (entries->changed)
	/*  save the palette  */
	palette_entries_save (entries, entries->filename);

      palette_entries_free (entries);
      list = g_slist_next (list);
    }
  g_slist_free (palette_entries_list);

  if (palette)
    {
      palette->entries = NULL;
      palette->color = NULL;
      palette->color_select = NULL;
      palette->color_select_active = 0;
      palette->scroll_offset = 0;
    }
  num_palette_entries = 0;
  palette_entries_list = NULL;
}


/*****************************************/
/*         Local functions               */
/*****************************************/

static void
palette_create_palette_menu (PaletteP        palette,
			     PaletteEntriesP default_entries)
{
  GtkWidget *menu_item;
  GSList *list;
  PaletteEntriesP p_entries = NULL;
  PaletteEntriesP found_entries = NULL;
  int i = 0;
  int default_index = -1;

  palette->menu = gtk_menu_new ();

  list = palette_entries_list;
  while (list)
    {
      p_entries = (PaletteEntriesP) list->data;
      list = g_slist_next (list);

      /*  to make sure we get something!  */
      if (p_entries == NULL)
	{
	  found_entries = p_entries;
	  default_index = i;
	}

      if (p_entries == default_entries)
	{
	  found_entries = default_entries;
	  default_index = i;
	}

      menu_item = gtk_menu_item_new_with_label (p_entries->name);
      gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
			  (GtkSignalFunc) palette_entries_set_callback,
			  (gpointer) p_entries);
      gtk_container_add (GTK_CONTAINER (palette->menu), menu_item);
      gtk_widget_show (menu_item);

      i++;
    }

  if (i == 0)
    {
      menu_item = gtk_menu_item_new_with_label ("none");
      gtk_container_add (GTK_CONTAINER (palette->menu), menu_item);
      gtk_widget_show (menu_item);

      /*  Set the action area and option menus to insensitive  */
      gtk_widget_set_sensitive (palette->option_menu, FALSE);
      gtk_widget_set_sensitive (GTK_DIALOG (palette->shell)->action_area, FALSE);

      /*  Clear the color area  */
      gdk_window_clear (palette->color_area->window);
    }
  else
    {
      /*  Make sure the action area and option menus are sensitive  */
      gtk_widget_set_sensitive (palette->option_menu, TRUE);
      gtk_widget_set_sensitive (GTK_DIALOG (palette->shell)->action_area, TRUE);

      /*  Clear the color area  */
      gdk_window_clear (palette->color_area->window);
    }

  gtk_option_menu_set_menu (GTK_OPTION_MENU (palette->option_menu), palette->menu);

  /*  Set the current item of the option menu to reflect
   *  the default palette.  Need to refresh here too
   */
  if (default_index != -1)
    {
      gtk_option_menu_set_history (GTK_OPTION_MENU (palette->option_menu), default_index);
      palette_entries_set_callback (NULL, found_entries);
    }
}

static void
palette_entries_load (char *filename)
{
  PaletteEntriesP entries;
  char            str[512];
  char           *tok;
  FILE           *fp;
  int             r, g, b;

  r = g = b = 0;

  entries = (PaletteEntriesP) g_malloc (sizeof (_PaletteEntries));

  entries->filename = g_strdup (filename);
  entries->name = g_strdup (prune_filename (filename));
  entries->colors = NULL;
  entries->n_colors = 0;

  /*  Open the requested file  */

  if (!(fp = fopen (filename, "r")))
    {
      palette_entries_free (entries);
      return;
    }

  fread (str, 13, 1, fp);
  str[13] = '\0';
  if (strcmp (str, "GIMP Palette\n"))
    {
      fclose (fp);
      return;
    }

  while (!feof (fp))
    {
      if (!fgets (str, 512, fp))
	continue;

      if (str[0] != '#')
	{
	  tok = strtok (str, " \t");
	  if (tok)
	    r = atoi (tok);

	  tok = strtok (NULL, " \t");
	  if (tok)
	    g = atoi (tok);

	  tok = strtok (NULL, " \t");
	  if (tok)
	    b = atoi (tok);

	  tok = strtok (NULL, "\n");

	  palette_add_entry (entries, tok, r, g, b);
	} /* if */
    } /* while */

  /*  Clean up  */

  fclose (fp);
  entries->changed = 0;

  palette_entries_list = palette_entries_insert_list(palette_entries_list, entries);

  /* Check if the current palette is the default one */
  if (strcmp(default_palette, prune_filename(filename)) == 0)
    default_palette_entries = entries;
}

static void
palette_entries_delete (char *filename)
{
  if (filename)
    unlink (filename);
}

static GSList *
palette_entries_insert_list (GSList *        list,
			     PaletteEntriesP entries)
{
  /*  add it to the list  */
  num_palette_entries++;
  return g_slist_append (list, (void *) entries);
}

static void
palette_entries_save (PaletteEntriesP  palette,
		      char            *filename)
{
  FILE * fp;
  GSList * list;
  PaletteEntryP entry;

  if (! filename)
    return;

  /*  Open the requested file  */
  if (! (fp = fopen (filename, "w")))
    {
      warning ("can't save palette \"%s\"\n", filename);
      return;
    }

  fprintf (fp, "GIMP Palette\n");
  fprintf (fp, "# %s -- GIMP Palette file\n", palette->name);

  list = palette->colors;
  while (list)
    {
      entry = (PaletteEntryP) list->data;
      fprintf (fp, "%d %d %d\t%s\n", entry->color[0], entry->color[1],
	       entry->color[2], entry->name);
      list = g_slist_next (list);
    }

  /*  Clean up  */
  fclose (fp);
}

static void
palette_entries_free (PaletteEntriesP entries)
{
  PaletteEntryP entry;
  GSList * list;

  list = entries->colors;
  while (list)
    {
      entry = (PaletteEntryP) list->data;
      palette_entry_free (entry);
      list = list->next;
    }

  g_free (entries->name);
  if (entries->filename)
    g_free (entries->filename);
  g_free (entries);
}

static void
palette_entry_free (PaletteEntryP entry)
{
  if (entry->name)
    g_free (entry->name);

  g_free (entry);
}

static void
palette_entries_set_callback (GtkWidget *w,
			      gpointer   client_data)
{
  PaletteEntriesP pal;

  if (palette)
    {
      pal = (PaletteEntriesP) client_data;

      palette->entries = pal;
      palette->color = NULL;
      if (palette->color_select_active)
	{
	  palette->color_select_active = 0;
	  color_select_hide (palette->color_select);
	}
      palette->color_select = NULL;
      palette->scroll_offset = 0;

      palette_calc_scrollbar (palette);
      palette_draw_entries (palette);
      palette_draw_current_entry (palette);
    }
}

static void
palette_change_color (int r,
		      int g,
		      int b,
		      int state)
{
  if (palette && palette->entries)
    {
      switch (state)
	{
	case COLOR_NEW:
	  palette->color = palette_add_entry (palette->entries, "Untitled", r, g, b);

	  palette_calc_scrollbar (palette);
	  palette_draw_entries (palette);
	  palette_draw_current_entry (palette);
	  break;

	case COLOR_UPDATE_NEW:
	  palette->color->color[0] = r;
	  palette->color->color[1] = g;
	  palette->color->color[2] = b;
	  palette_draw_entries (palette);
	  palette_draw_current_entry (palette);
	  break;

	default:
	  break;
	}
    }

  if (active_color == FOREGROUND)
    palette_set_foreground (r, g, b);
  else if (active_color == BACKGROUND)
    palette_set_background (r, g, b);
}

void
palette_set_active_color (int r,
			  int g,
			  int b,
			  int state)
{
  palette_change_color (r, g, b, state);
}

static gint
palette_color_area_expose (GtkWidget      *widget,
			   GdkEventExpose *event,
			   PaletteP        palette)
{
  if (!palette->gc)
    palette->gc = gdk_gc_new (widget->window);

  palette_draw_current_entry (palette);

  return FALSE;
}

static gint
palette_color_area_events (GtkWidget *widget,
			   GdkEvent  *event,
			   PaletteP   palette)
{
  GdkEventButton *bevent;
  GSList *tmp_link;
  int r, g, b;
  int width, height;
  int entry_width;
  int entry_height;
  int row, col;
  int pos;

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

      if (bevent->button == 1 && palette->entries)
	{
	  width = palette->color_area->requisition.width;
	  height = palette->color_area->requisition.height;
	  entry_width = ((width - (SPACING * (COLUMNS + 1))) / COLUMNS) + SPACING;
	  entry_height = ((height - (SPACING * (ROWS + 1))) / ROWS) + SPACING;

	  col = (bevent->x - 1) / entry_width;
	  row = (palette->scroll_offset + bevent->y - 1) / entry_height;
	  pos = row * COLUMNS + col;

	  tmp_link = g_slist_nth (palette->entries->colors, pos);
	  if (tmp_link)
	    {
	      palette_draw_current_entry (palette);
	      palette->color = tmp_link->data;

	      /*  Update either foreground or background colors  */
	      r = palette->color->color[0];
	      g = palette->color->color[1];
	      b = palette->color->color[2];
	      if (active_color == FOREGROUND)
		palette_set_foreground (r, g, b);
	      else if (active_color == BACKGROUND)
		palette_set_background (r, g, b);

	      palette_update_current_entry (palette);
	    }
	}
      break;

    default:
      break;
    }

  return FALSE;
}

static void
palette_scroll_update (GtkAdjustment *adjustment,
		       gpointer       data)
{
  PaletteP palette;

  palette = (PaletteP) data;

  if (palette)
    {
      palette->scroll_offset = adjustment->value;
      palette_draw_entries (palette);
      palette_draw_current_entry (palette);
    }
}

static void
palette_new_callback (GtkWidget *w,
		      gpointer   client_data)
{
  PaletteP palette;

  palette = client_data;
  if (palette && palette->entries)
    {
      if (active_color == FOREGROUND)
	palette->color =
	  palette_add_entry (palette->entries, "Untitled",
			     foreground[0], foreground[1], foreground[2]);
      else if (active_color == BACKGROUND)
	palette->color =
	  palette_add_entry (palette->entries, "Untitled",
			     background[0], background[1], background[2]);

      palette_calc_scrollbar (palette);
      palette_draw_entries (palette);
      palette_draw_current_entry (palette);
    }
}

static void
palette_delete_callback (GtkWidget *w,
			 gpointer   client_data)
{
  PaletteP palette;

  palette = client_data;
  if (palette)
    palette_delete_entry (palette);
}

static void
palette_edit_callback (GtkWidget *w,
		       gpointer   client_data)
{
  PaletteP palette;
  unsigned char *color;

  palette = client_data;
  if (palette && palette->entries && palette->color)
    {
      color = palette->color->color;

      if (!palette->color_select)
	{
	  palette->color_select = color_select_new (color[0], color[1], color[2],
						    palette_select_callback, NULL,
						    FALSE);
	  palette->color_select_active = 1;
	}
      else
	{
	  if (!palette->color_select_active)
	    {
	      color_select_show (palette->color_select);
	      palette->color_select_active = 1;
	    }

	  color_select_set_color (palette->color_select, color[0], color[1], color[2], 1);
	}
    }
}

static gint
palette_dialog_delete_callback (GtkWidget *w,
			 GdkEvent *e,
			 gpointer client_data) 
{
  palette_close_callback (w, client_data);

  return TRUE;
}


static void
palette_close_callback (GtkWidget *w,
			gpointer   client_data)
{
  PaletteP palette;

  palette = client_data;
  if (palette)
    {
      if (palette->color_select_active)
	{
	  palette->color_select_active = 0;
	  color_select_hide (palette->color_select);
	}

      if (GTK_WIDGET_VISIBLE (palette->shell))
	gtk_widget_hide (palette->shell);
    }
}

static void
palette_new_entries_callback (GtkWidget *w,
			      gpointer   client_data)
{
  query_string_box ("New Palette", "Enter a name for new palette", NULL,
		    palette_add_entries_callback, NULL);
}

static void
palette_add_entries_callback (GtkWidget *w,
			      gpointer   client_data,
			      gpointer   call_data)
{
  char *home;
  char *palette_name;
  char *local_path;
  char *first_token;
  char *token;
  char *path;
  PaletteEntriesP entries;

  palette_name = (char *) call_data;
  if (palette && palette_name)
    {
      entries = g_malloc (sizeof (_PaletteEntries));
      if (palette_path)
	{
	  /*  Get the first path specified in the palette path list  */
	  home = getenv("HOME");
	  local_path = g_strdup (palette_path);
	  first_token = local_path;
	  token = xstrsep(&first_token, ":");

	  if (token)
	    {
	      if (*token == '~')
		{
		  path = g_malloc(strlen(home) + strlen(token) + 1);
		  sprintf(path, "%s%s", home, token + 1);
		}
	      else
		{
		  path = g_malloc(strlen(token) + 1);
		  strcpy(path, token);
		}

	      entries->filename = g_malloc (strlen (path) + strlen (palette_name) + 2);
	      sprintf (entries->filename, "%s/%s", path, palette_name);

	      g_free (path);
	    }

	  g_free (local_path);
	}
      else
	entries->filename = NULL;

      entries->name = palette_name;  /*  don't need to copy because this memory is ours  */
      entries->colors = NULL;
      entries->n_colors = 0;
      entries->changed = 1;

      palette_entries_list = palette_entries_insert_list (palette_entries_list, entries);

      gtk_option_menu_remove_menu (GTK_OPTION_MENU (palette->option_menu));
      gtk_widget_destroy (palette->menu);
      palette_create_palette_menu (palette, entries);
    }
}

static void
palette_merge_entries_callback (GtkWidget *w,
				gpointer   client_data)
{
}

static void
palette_delete_entries_callback (GtkWidget *w,
				 gpointer   client_data)
{
  PaletteP palette;
  PaletteEntriesP entries;

  palette = client_data;
  if (palette && palette->entries)
    {
      /*  If a color selection dialog is up, hide it  */
      if (palette->color_select_active)
	{
	  palette->color_select_active = 0;
	  color_select_hide (palette->color_select);
	}

      gtk_option_menu_remove_menu (GTK_OPTION_MENU (palette->option_menu));
      gtk_widget_destroy (palette->menu);

      entries = palette->entries;
      if (entries && entries->filename)
	palette_entries_delete (entries->filename);

      if (default_palette_entries == entries)
	default_palette_entries = NULL;
      palette->entries = NULL;

      palette_free_palettes ();   /*  free palettes, don't save any modified versions  */
      palette_init_palettes ();   /*  load in brand new palettes  */

      palette_create_palette_menu (palette, default_palette_entries);
    }
}

static void
palette_select_callback (int   r,
			 int   g,
			 int   b,
			 ColorSelectState state,
			 void *client_data)
{
  unsigned char * color;

  if (palette && palette->entries)
    {
      switch (state) {
      case COLOR_SELECT_UPDATE:
	break;
      case COLOR_SELECT_OK:
	if (palette->color)
	  {
	  color = palette->color->color;

	  color[0] = r;
	  color[1] = g;
	  color[2] = b;

	  /*  Update either foreground or background colors  */
	  if (active_color == FOREGROUND)
	    palette_set_foreground (r, g, b);
	  else if (active_color == BACKGROUND)
	    palette_set_background (r, g, b);
	  
	  palette_calc_scrollbar (palette);
	  palette_draw_entries (palette);
	  palette_draw_current_entry (palette);
	  }
	/* Fallthrough */
      case COLOR_SELECT_CANCEL:
	color_select_hide (palette->color_select);
	palette->color_select_active = 0;
      }
    }
}

static int
palette_draw_color_row (unsigned char **colors,
			int             ncolors,
			int             y,
			unsigned char  *buffer,
			GtkWidget      *preview)
{
  unsigned char *p;
  unsigned char bcolor;
  int width, height;
  int entry_width;
  int entry_height;
  int vsize;
  int vspacing;
  int i, j;

  bcolor = 0;

  width = preview->requisition.width;
  height = preview->requisition.height;
  entry_width = (width - (SPACING * (COLUMNS + 1))) / COLUMNS;
  entry_height = (height - (SPACING * (ROWS + 1))) / ROWS;

  if ((y >= 0) && ((y + SPACING) < height))
    vspacing = SPACING;
  else if (y < 0)
    vspacing = SPACING + y;
  else
    vspacing = height - y;

  if (vspacing > 0)
    {
      if (y < 0)
	y += SPACING - vspacing;

      for (i = SPACING - vspacing; i < SPACING; i++, y++)
	{
	  p = buffer;
	  for (j = 0; j < width; j++)
	    {
	      *p++ = bcolor;
	      *p++ = bcolor;
	      *p++ = bcolor;
	    }

	  gtk_preview_draw_row (GTK_PREVIEW (preview), buffer, 0, y, width);
	}

      if (y > SPACING)
	y += SPACING - vspacing;
    }
  else
    y += SPACING;

  vsize = (y >= 0) ? (entry_height) : (entry_height + y);

  if ((y >= 0) && ((y + entry_height) < height))
    vsize = entry_height;
  else if (y < 0)
    vsize = entry_height + y;
  else
    vsize = height - y;

  if (vsize > 0)
    {
      p = buffer;
      for (i = 0; i < ncolors; i++)
	{
	  for (j = 0; j < SPACING; j++)
	    {
	      *p++ = bcolor;
	      *p++ = bcolor;
	      *p++ = bcolor;
	    }

	  for (j = 0; j < entry_width; j++)
	    {
	      *p++ = colors[i][0];
	      *p++ = colors[i][1];
	      *p++ = colors[i][2];
	    }
	}

      for (i = 0; i < (COLUMNS - ncolors); i++)
	{
	  for (j = 0; j < (SPACING + entry_width); j++)
	    {
	      *p++ = 0;
	      *p++ = 0;
	      *p++ = 0;
	    }
	}

      for (j = 0; j < SPACING; j++)
	{
	  *p++ = bcolor;
	  *p++ = bcolor;
	  *p++ = bcolor;
	}

      if (y < 0)
	y += entry_height - vsize;
      for (i = 0; i < vsize; i++, y++)
	gtk_preview_draw_row (GTK_PREVIEW (preview), buffer, 0, y, width);
      if (y > entry_height)
	y += entry_height - vsize;
    }
  else
    y += entry_height;

  return y;
}

static void
palette_draw_entries (PaletteP palette)
{
  PaletteEntryP entry;
  unsigned char *buffer;
  unsigned char *colors[COLUMNS];
  GSList *tmp_link;
  int width, height;
  int entry_width;
  int entry_height;
  int row_vsize;
  int index, y;

  if (palette && palette->entries && !palette->updating)
    {
      width = palette->color_area->requisition.width;
      height = palette->color_area->requisition.height;
      entry_width = (width - (SPACING * (COLUMNS + 1))) / COLUMNS;
      entry_height = (height - (SPACING * (ROWS + 1))) / ROWS;

      buffer = g_malloc (width * 3);

      y = -palette->scroll_offset;
      row_vsize = SPACING + entry_height;
      tmp_link = palette->entries->colors;
      index = 0;

      while ((tmp_link) && (y < -row_vsize))
	{
	  tmp_link = tmp_link->next;

	  if (++index == COLUMNS)
	    {
	      index = 0;
	      y += row_vsize;
	    }
	}

      index = 0;
      while (tmp_link)
	{
	  entry = tmp_link->data;
	  tmp_link = tmp_link->next;

	  colors[index] = entry->color;
	  index++;

	  if (index == COLUMNS)
	    {
	      index = 0;
	      y = palette_draw_color_row (colors, COLUMNS, y, buffer, palette->color_area);
	      if (y >= height)
		break;
	    }
	}

      while (y < height)
	{
	  y = palette_draw_color_row (colors, index, y, buffer, palette->color_area);
	  index = 0;
	}

      gtk_widget_draw (palette->color_area, NULL);

      g_free (buffer);
    }
}

static void
palette_draw_current_entry (PaletteP palette)
{
  PaletteEntryP entry;
  int width, height;
  int entry_width;
  int entry_height;
  int row, col;
  int x, y;

  if (palette && palette->entries && !palette->updating && palette->color)
    {
      gdk_gc_set_function (palette->gc, GDK_INVERT);

      entry = palette->color;

      row = entry->position / COLUMNS;
      col = entry->position % COLUMNS;

      entry_width = (palette->color_area->requisition.width -
		     (SPACING * (COLUMNS + 1))) / COLUMNS;
      entry_height = (palette->color_area->requisition.height -
		      (SPACING * (ROWS + 1))) / ROWS;

      x = col * (entry_width + SPACING);
      y = row * (entry_height + SPACING);
      y -= palette->scroll_offset;

      width = entry_width + SPACING;
      height = entry_height + SPACING;

      gdk_draw_rectangle (palette->color_area->window, palette->gc,
			  0, x, y, width, height);

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

static void
palette_update_current_entry (PaletteP palette)
{
  if (palette && palette->entries)
    {
      /*  Draw the current entry  */
      palette_draw_current_entry (palette);

      /*  Update the active color name  */
      gtk_entry_set_text (GTK_ENTRY (palette->color_name), palette->color->name);
    }
}

static PaletteEntryP
palette_add_entry (PaletteEntriesP  entries,
		   char            *name,
		   int              r,
		   int              g,
		   int              b)
{
  PaletteEntryP entry;

  if (entries)
    {
      entry = g_malloc (sizeof (_PaletteEntry));

      entry->color[0] = r;
      entry->color[1] = g;
      entry->color[2] = b;
      if (name)
	entry->name = g_strdup (name);
      else
	entry->name = g_strdup ("Untitled");
      entry->position = entries->n_colors;

      entries->colors = g_slist_append (entries->colors, entry);
      entries->n_colors += 1;

      entries->changed = 1;

      return entry;
    }

  return NULL;
}

static void
palette_delete_entry (PaletteP palette)
{
  PaletteEntryP entry;
  GSList *tmp_link;
  int pos;

  if (palette && palette->entries && palette->color)
    {
      entry = palette->color;
      palette->entries->colors = g_slist_remove (palette->entries->colors, entry);
      palette->entries->n_colors--;
      palette->entries->changed = 1;

      pos = entry->position;
      palette_entry_free (entry);

      tmp_link = g_slist_nth (palette->entries->colors, pos);

      if (tmp_link)
	{
	  palette->color = tmp_link->data;

	  while (tmp_link)
	    {
	      entry = tmp_link->data;
	      tmp_link = tmp_link->next;
	      entry->position = pos++;
	    }
	}
      else
	{
	  tmp_link = g_slist_nth (palette->entries->colors, pos - 1);
	  if (tmp_link)
	    palette->color = tmp_link->data;
	}

      if (palette->entries->n_colors == 0)
	palette->color = palette_add_entry (palette->entries, "Black", 0, 0, 0);

      palette_calc_scrollbar (palette);
      palette_draw_entries (palette);
      palette_draw_current_entry (palette);
    }
}

static void
palette_calc_scrollbar (PaletteP palette)
{
  int n_entries;
  int cur_entry_row;
  int nrows;
  int row_vsize;
  int vsize;
  int page_size;
  int new_offset;

  if (palette && palette->entries)
    {
      n_entries = palette->entries->n_colors;
      nrows = n_entries / COLUMNS;
      if (n_entries % COLUMNS)
	nrows += 1;
      row_vsize = SPACING + ((palette->color_area->requisition.height -
			      (SPACING * (ROWS + 1))) / ROWS);
      vsize = row_vsize * nrows;
      page_size = row_vsize * ROWS;

      if (palette->color)
	cur_entry_row = palette->color->position / COLUMNS;
      else
	cur_entry_row = 0;

      new_offset = cur_entry_row * row_vsize;

      /* scroll only if necessary */
      if (new_offset < palette->scroll_offset)
	{
	  palette->scroll_offset = new_offset;
	}
      else if (new_offset > palette->scroll_offset)
	{
	  /* only scroll the minimum amount to bring the current color into view */
	  if ((palette->scroll_offset + page_size - row_vsize) < new_offset)
	    palette->scroll_offset = new_offset - (page_size - row_vsize);
	}

      /* sanity check to make sure the scrollbar offset is valid */
      if (vsize > page_size)
	if (palette->scroll_offset > (vsize - page_size))
	  palette->scroll_offset = vsize - page_size;

      palette->sbar_data->value = palette->scroll_offset;
      palette->sbar_data->upper = vsize;
      palette->sbar_data->page_size = (page_size < vsize) ? page_size : vsize;
      palette->sbar_data->page_increment = page_size;
      palette->sbar_data->step_increment = row_vsize;

      gtk_signal_emit_by_name (GTK_OBJECT (palette->sbar_data), "changed");
    }
}


/*  Procedural database entries  */

/****************************/
/*  PALETTE_GET_FOREGROUND  */

static Argument *
palette_get_foreground_invoker (Argument *args)
{
  Argument *return_args;
  unsigned char r, g, b;
  unsigned char *col;

  palette_get_foreground (&r, &g, &b);
  col = (unsigned char *) g_malloc (3);
  col[RED_PIX] = r;
  col[GREEN_PIX] = g;
  col[BLUE_PIX] = b;

  return_args = procedural_db_return_args (&palette_get_foreground_proc, TRUE);
  return_args[1].value.pdb_pointer = col;

  return return_args;
}

/*  The procedure definition  */
ProcArg palette_get_foreground_args[] =
{
  { PDB_COLOR,
    "foreground",
    "the foreground color"
  }
};

ProcRecord palette_get_foreground_proc =
{
  "gimp_palette_get_foreground",
  "Get the current GIMP foreground color",
  "This procedure retrieves the current GIMP foreground color.  The foreground color is used in a variety of tools such as paint tools, blending, and bucket fill.",
  "Spencer Kimball & Peter Mattis",
  "Spencer Kimball & Peter Mattis",
  "1995-1996",
  PDB_INTERNAL,

  /*  Input arguments  */
  0,
  NULL,

  /*  Output arguments  */
  1,
  palette_get_foreground_args,

  /*  Exec method  */
  { { palette_get_foreground_invoker } },
};


/****************************/
/*  PALETTE_GET_BACKGROUND  */

static Argument *
palette_get_background_invoker (Argument *args)
{
  Argument *return_args;
  unsigned char r, g, b;
  unsigned char *col;

  palette_get_background (&r, &g, &b);
  col = (unsigned char *) g_malloc (3);
  col[RED_PIX] = r;
  col[GREEN_PIX] = g;
  col[BLUE_PIX] = b;

  return_args = procedural_db_return_args (&palette_get_background_proc, TRUE);
  return_args[1].value.pdb_pointer = col;

  return return_args;
}

/*  The procedure definition  */
ProcArg palette_get_background_args[] =
{
  { PDB_COLOR,
    "background",
    "the background color"
  }
};

ProcRecord palette_get_background_proc =
{
  "gimp_palette_get_background",
  "Get the current GIMP background color",
  "This procedure retrieves the current GIMP background color.  The background color is used in a variety of tools such as blending, erasing (with non-apha images), and image filling.",
  "Spencer Kimball & Peter Mattis",
  "Spencer Kimball & Peter Mattis",
  "1995-1996",
  PDB_INTERNAL,

  /*  Input arguments  */
  0,
  NULL,

  /*  Output arguments  */
  1,
  palette_get_background_args,

  /*  Exec method  */
  { { palette_get_background_invoker } },
};


/****************************/
/*  PALETTE_SET_FOREGROUND  */

static Argument *
palette_set_foreground_invoker (Argument *args)
{
  unsigned char *color;
  int success;

  success = TRUE;
  if (success)
    color = (unsigned char *) args[0].value.pdb_pointer;

  if (success)
    palette_set_foreground (color[RED_PIX], color[GREEN_PIX], color[BLUE_PIX]);

  return procedural_db_return_args (&palette_set_foreground_proc, success);
}

/*  The procedure definition  */
ProcArg palette_set_foreground_args[] =
{
  { PDB_COLOR,
    "foreground",
    "the foreground color"
  }
};

ProcRecord palette_set_foreground_proc =
{
  "gimp_palette_set_foreground",
  "Set the current GIMP foreground color",
  "This procedure sets the current GIMP foreground color.  After this is set, operations which use foreground such as paint tools, blending, and bucket fill will use the new value.",
  "Spencer Kimball & Peter Mattis",
  "Spencer Kimball & Peter Mattis",
  "1995-1996",
  PDB_INTERNAL,

  /*  Input arguments  */
  1,
  palette_set_foreground_args,

  /*  Output arguments  */
  0,
  NULL,

  /*  Exec method  */
  { { palette_set_foreground_invoker } },
};


/****************************/
/*  PALETTE_SET_BACKGROUND  */

static Argument *
palette_set_background_invoker (Argument *args)
{
  unsigned char *color;
  int success;

  success = TRUE;
  if (success)
    color = (unsigned char *) args[0].value.pdb_pointer;

  if (success)
    palette_set_background (color[RED_PIX], color[GREEN_PIX], color[BLUE_PIX]);

  return procedural_db_return_args (&palette_set_background_proc, success);
}

/*  The procedure definition  */
ProcArg palette_set_background_args[] =
{
  { PDB_COLOR,
    "background",
    "the background color"
  }
};

ProcRecord palette_set_background_proc =
{
  "gimp_palette_set_background",
  "Set the current GIMP background color",
  "This procedure sets the current GIMP background color.  After this is set, operations which use background such as blending, filling images, clearing, and erasing (in non-alpha images) will use the new value.",
  "Spencer Kimball & Peter Mattis",
  "Spencer Kimball & Peter Mattis",
  "1995-1996",
  PDB_INTERNAL,

  /*  Input arguments  */
  1,
  palette_set_background_args,

  /*  Output arguments  */
  0,
  NULL,

  /*  Exec method  */
  { { palette_set_background_invoker } },
};

/*******************************/
/*  PALETTE_SET_DEFAULT_COLORS */

static Argument *
palette_set_default_colors_invoker (Argument *args)
{
  int success;

  success = TRUE;
  if (success)
    palette_set_default_colors ();

  return procedural_db_return_args (&palette_set_background_proc, success);
}

/*  The procedure definition  */

ProcRecord palette_set_default_colors_proc =
{
  "gimp_palette_set_default_colors",
  "Set the current GIMP foreground and background colors to black and white",
  "This procedure sets the current GIMP foreground and background colors to their initial default values, black and white.",
  "Spencer Kimball & Peter Mattis",
  "Spencer Kimball & Peter Mattis",
  "1995-1996",
  PDB_INTERNAL,

  /*  Input arguments  */
  0,
  NULL,

  /*  Output arguments  */
  0,
  NULL,

  /*  Exec method  */
  { { palette_set_default_colors_invoker } },
};

/************************/
/*  PALETTE_SWAP_COLORS */

static Argument *
palette_swap_colors_invoker (Argument *args)
{
  int success;

  success = TRUE;
  if (success)
    palette_swap_colors ();

  return procedural_db_return_args (&palette_swap_colors_proc, success);
}

/*  The procedure definition  */

ProcRecord palette_swap_colors_proc =
{
  "gimp_palette_swap_colors",
  "Swap the current GIMP foreground and background colors",
  "This procedure swaps the current GIMP foreground and background colors, so that the new foreground color becomes the old background color and vice versa.",
  "Spencer Kimball & Peter Mattis",
  "Spencer Kimball & Peter Mattis",
  "1995-1996",
  PDB_INTERNAL,

  /*  Input arguments  */
  0,
  NULL,

  /*  Output arguments  */
  0,
  NULL,

  /*  Exec method  */
  { { palette_swap_colors_invoker } },
};
