/* Plug-in to save .gpb (GIMP Pixmap Brush) files.
 * The format for pixmap brushes might well change, and this will have to
 * be updated.
 *
 * Copyright (C) 1999 Tor Lillqvist
 *
 * 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.
 */

/* set to the level of debugging output you want, 0 for none */
#define GPB_DEBUG 0

#define IFDBG(level) if (GPB_DEBUG >= level)

#include "config.h"

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

#include <gtk/gtk.h>
#include <libgimp/gimp.h>
#include <libgimp/parasiteio.h>

#include "app/brush_header.h"
#include "app/pattern_header.h"

#define DUMMY_PATTERN_NAME "x"

#define MAXDESCLEN 256

/* Parameters applicable each time we save a gpb or gih, saved
 * in the main gimp application between invocations of this plug-in.
 */
static struct {
  /* Use by both gpb and gih: */
  guint spacing;
  guchar description[MAXDESCLEN+1];
} info =
/* Initialize to this, change if non-interactive later */
{  
  10,
  "GIMP Pixmap Brush"
};

static gint run_flag = 0;
static gint num_layers_with_alpha;

static PixPipeParams gihparms;

typedef struct
{
  GOrientation orientation;
  gint32 image;
  gint32 toplayer;
  gint nguides;
  gint32 *guides;
  gint *value;
  GtkWidget *count_label;	/* Corresponding count adjustment, */
  gint *count;			/* cols or rows */
  gint *other_count;		/* And the other one */
  GtkObject *ncells;
  GtkObject *rank0;
  GtkWidget *warning_label;
} SizeAdjustmentData;

static gint32 *vguides, *hguides;
static gint nvguides = 0, nhguides = 0;

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

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

MAIN ()

static void
query ()
{
  static GParamDef gpb_save_args[] =
  {
    { PARAM_INT32, "run_mode", "Interactive, non-interactive" },
    { PARAM_IMAGE, "image", "Input image" },
    { PARAM_DRAWABLE, "drawable", "Drawable to save" },
    { PARAM_STRING, "filename", "The name of the file to save the brush in" },
    { PARAM_STRING, "raw_filename", "The name of the file to save the brush in" },
    { PARAM_INT32, "spacing", "Spacing of the brush" },
    { PARAM_STRING, "description", "Short description of the brush" },
  };
  static int ngpb_save_args = sizeof (gpb_save_args)
    / sizeof (gpb_save_args[0]);

  static GParamDef gih_save_args[] =
  {
    { PARAM_INT32, "run_mode", "Interactive, non-interactive" },
    { PARAM_IMAGE, "image", "Input image" },
    { PARAM_DRAWABLE, "drawable", "Drawable to save" },
    { PARAM_STRING, "filename", "The name of the file to save the brush pipe in" },
    { PARAM_STRING, "raw_filename", "The name of the file to save the brush pipe in" },
    { PARAM_INT32, "spacing", "Spacing of the brush" },
    { PARAM_STRING, "description", "Short description of the brush pipe" },
  };
  static int ngih_save_args = sizeof (gih_save_args)
    / sizeof (gih_save_args[0]);

  gimp_install_procedure ("file_gpb_save",
			  "saves images in GIMP pixmap brush format", 
                          "This plug-in saves a layer of an image in "
			  "the GIMP pixmap brush format. "
                          "The image must have an alpha channel.",
                          "Tor Lillqvist",
                          "Tor Lillqvist",
                          "1999",
                          "<Save>/GPB",
			  "RGBA",
                          PROC_PLUG_IN,
                          ngpb_save_args, 0,
                          gpb_save_args, NULL);

  gimp_register_save_handler ("file_gpb_save", "gpb", "");

  gimp_install_procedure ("file_gih_save",
			  "saves images in GIMP pixmap brush pipe format", 
                          "This plug-in saves an image in "
			  "the GIMP pixmap brush pipe format. "
                          "The image must have an alpha channel."
			  "The image can be multi-layered, "
			  "and additionally the layers can be divided into "
			  "a rectangular array of brushes.",
                          "Tor Lillqvist",
                          "Tor Lillqvist",
                          "1999",
                          "<Save>/GIH",
			  "RGBA",
                          PROC_PLUG_IN,
                          ngih_save_args, 0,
                          gih_save_args, NULL);

  gimp_register_save_handler ("file_gih_save", "gih", "");
}

static void
adjustment_callback (GtkWidget *widget,
		     gpointer   data)
{
  *((gint *) data) = GTK_ADJUSTMENT (widget)->value;
}

static void
size_adjustment_callback (GtkWidget *widget,
			  gpointer   data)
{
  /* Unfortunately this doesn't work, sigh. The guides don't show up unless
   * you manually force a redraw of the image.
   */
  int i;
  int size;
  int newn;
  SizeAdjustmentData *adj = (SizeAdjustmentData *) data;
  char buf[10];

  for (i = 0; i < adj->nguides; i++)
    gimp_image_delete_guide (adj->image, adj->guides[i]);
  g_free (adj->guides);
  adj->guides = NULL;
  gimp_displays_flush ();

  *(adj->value) = GTK_ADJUSTMENT (widget)->value;

  if (adj->orientation == ORIENTATION_VERTICAL)
    {
      size = gimp_image_width (adj->image);
      newn = size / *(adj->value);
      adj->nguides = newn - 1;
      adj->guides = g_new (gint32, adj->nguides);
      for (i = 0; i < adj->nguides; i++)
	adj->guides[i] = gimp_image_add_vguide (adj->image,
						*(adj->value) * (i+1));
    }
  else
    {
      size = gimp_image_height (adj->image);
      newn = size / *(adj->value);
      adj->nguides = newn - 1;
      adj->guides = g_new (gint32, adj->nguides);
      for (i = 0; i < adj->nguides; i++)
	adj->guides[i] = gimp_image_add_hguide (adj->image,
						*(adj->value) * (i+1));
    }
  gimp_displays_flush ();
  sprintf (buf, "%2d", newn);
  gtk_label_set_text (GTK_LABEL (adj->count_label), buf);
  *(adj->count) = newn;
  if (newn * *(adj->value) != size)
    gtk_widget_show (GTK_WIDGET (adj->warning_label));
  else
    gtk_widget_hide (GTK_WIDGET (adj->warning_label));
  if (adj->ncells != NULL)
    gtk_adjustment_set_value (GTK_ADJUSTMENT (adj->ncells),
			      *(adj->other_count) * *(adj->count));
  if (adj->rank0 != NULL)
    gtk_adjustment_set_value (GTK_ADJUSTMENT (adj->rank0),
			      *(adj->other_count) * *(adj->count));
			    
}

static void
entry_callback (GtkWidget *widget,
		gpointer   data)
{
  if (data == info.description)
    {
      strncpy (info.description, gtk_entry_get_text (GTK_ENTRY (widget)), MAXDESCLEN);
      info.description[MAXDESCLEN]  = 0;
    }
}

static void
cb_callback (GtkWidget *widget,
	     gpointer   data)
{
  *((char **) data) = gtk_entry_get_text (GTK_ENTRY (widget));
}

static void
close_callback (GtkWidget *widget,
		gpointer   data)
{
  gtk_main_quit ();
}

static void
ok_callback (GtkWidget *widget,
	     gpointer    data)
{
  run_flag = 1;
  gtk_widget_destroy (GTK_WIDGET (data));
}

static void
common_save_dialog (GtkWidget *dlg,
		    GtkWidget *table)
{
  GtkWidget *button;
  GtkWidget *label;
  GtkObject *adjustment;
  GtkWidget *box;
  GtkWidget *spinbutton;
  GtkWidget *entry;
  gchar buffer[12];

  /*  Action area  */
  button = gtk_button_new_with_label ("OK");
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      (GtkSignalFunc) ok_callback, dlg);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0);
  gtk_widget_grab_default (button);
  gtk_widget_show (button);

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

  gtk_table_set_row_spacings (GTK_TABLE (table), 10);
  gtk_table_set_col_spacings (GTK_TABLE (table), 10);


  /*
   * Spacing: __
   */
  gtk_table_resize (GTK_TABLE (table),
		    GTK_TABLE (table)->nrows + 1, GTK_TABLE (table)->ncols);

  label = gtk_label_new ("Spacing (percent):");
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), label, 0, 1,
		    GTK_TABLE (table)->nrows - 1, GTK_TABLE (table)->nrows,
		    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (label);

  adjustment = gtk_adjustment_new (info.spacing, 1, 1000, 1, 10, 10);
  spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (adjustment), 1, 0);
  gtk_spin_button_set_shadow_type (GTK_SPIN_BUTTON (spinbutton),
				   GTK_SHADOW_NONE);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
  gtk_widget_set_usize (spinbutton, 75, 0);
  box = gtk_hbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (box), spinbutton, FALSE, TRUE, 0);
  gtk_table_attach (GTK_TABLE (table), box, 1, 2, 
		    GTK_TABLE (table)->nrows - 1, GTK_TABLE (table)->nrows,
		    GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
  gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
		      (GtkSignalFunc) adjustment_callback, &info.spacing);
  gtk_widget_show (spinbutton);
  gtk_widget_show (box);

  /*
   * Description: ___________
   */
  gtk_table_resize (GTK_TABLE (table),
		    GTK_TABLE (table)->nrows + 1, GTK_TABLE (table)->ncols);

  label = gtk_label_new ("Description:");
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), label, 0, 1,
		    GTK_TABLE (table)->nrows - 1, GTK_TABLE (table)->nrows,
		    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (label);

  entry = gtk_entry_new ();
  gtk_table_attach (GTK_TABLE (table), entry, 1, 2,
		    GTK_TABLE (table)->nrows - 1, GTK_TABLE (table)->nrows,
		    GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
  gtk_widget_set_usize (entry, 200, 0);
  gtk_entry_set_text (GTK_ENTRY (entry), info.description);
  gtk_signal_connect (GTK_OBJECT (entry), "changed",
		      (GtkSignalFunc) entry_callback, info.description);
  gtk_widget_show (entry);
}

static gint
gpb_save_dialog ()
{
  GtkWidget *dlg;
  GtkWidget *table;
  GtkWidget *entry;
  gchar **argv;
  gint argc;
  gchar buffer[100];

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

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

  dlg = gtk_dialog_new ();
  gtk_window_set_title (GTK_WINDOW (dlg), "Save As Pixmap Brush");
  gtk_window_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE);
  gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
		      (GtkSignalFunc) close_callback, NULL);

  /* The main table */
  table = gtk_table_new (2, 0, FALSE);
  gtk_container_border_width (GTK_CONTAINER (table), 10);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), table, TRUE, TRUE, 0);
  gtk_widget_show (table);

  common_save_dialog (dlg, table);

  gtk_widget_show (dlg);

  gtk_main ();
  gdk_flush ();

  return run_flag;
}

static gint
gih_save_dialog (gint32 image_ID)
{
  GtkWidget *dlg;
  GtkWidget *table, *dimtable;
  GtkWidget *label;
  GtkObject *adjustment;
  GtkWidget *spinbutton;
  GtkWidget *entry;
  GtkWidget *box;
  GtkWidget *cb;
  GList *cbitems = NULL;
  gint i;
  gchar **argv;
  gint argc;
  gchar buffer[100];
  SizeAdjustmentData cellw_adjust, cellh_adjust;
  gint32 *layer_ID;
  gint32 nlayers;

  /* Setup default values */
  if (gihparms.rows >= 1 && gihparms.cols >= 1)
    gihparms.ncells = num_layers_with_alpha * gihparms.rows * gihparms.cols;
  else
    gihparms.ncells = num_layers_with_alpha;

  if (gihparms.cellwidth == 1 && gihparms.cellheight == 1)
    {
      gihparms.cellwidth = gimp_image_width (image_ID) / gihparms.cols;
      gihparms.cellheight = gimp_image_height (image_ID) / gihparms.rows;
    }

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

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

  dlg = gtk_dialog_new ();
  gtk_window_set_title (GTK_WINDOW (dlg), "Save As Pixmap Brush Pipe");
  gtk_window_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE);
  gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
		      (GtkSignalFunc) close_callback, NULL);

  /* The main table */
  table = gtk_table_new (2, 0, FALSE);
  gtk_container_border_width (GTK_CONTAINER (table), 10);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), table, TRUE, TRUE, 0);
  gtk_widget_show (table);

  common_save_dialog (dlg, table);

  /*
   * Cell size: __ x __ pixels
   */
  
  gtk_table_resize (GTK_TABLE (table),
		    GTK_TABLE (table)->nrows + 1, GTK_TABLE (table)->ncols);

  label = gtk_label_new ("Cell size:");
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), label, 0, 1,
		    GTK_TABLE (table)->nrows - 1, GTK_TABLE (table)->nrows,
		    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (label);

  box = gtk_hbox_new (FALSE, 0);
  
  adjustment = gtk_adjustment_new (gihparms.cellwidth,
				   2, gimp_image_width (image_ID), 1, 1, 1);
  spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (adjustment), 1, 0);
  gtk_spin_button_set_shadow_type (GTK_SPIN_BUTTON (spinbutton),
				    GTK_SHADOW_NONE);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
  gtk_widget_set_usize (spinbutton, 75, 0);
  gtk_box_pack_start (GTK_BOX (box), spinbutton, FALSE, FALSE, 0);

  layer_ID = gimp_image_get_layers (image_ID, &nlayers);
  cellw_adjust.orientation = ORIENTATION_VERTICAL;
  cellw_adjust.image = image_ID;
  cellw_adjust.toplayer = layer_ID[nlayers-1];
  cellw_adjust.nguides = 0;
  cellw_adjust.guides = NULL;
  cellw_adjust.value = &gihparms.cellwidth;
  gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
		      (GtkSignalFunc) size_adjustment_callback,
		      &cellw_adjust);
  gtk_widget_show (spinbutton);

  label = gtk_label_new (" x ");
  gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
  gtk_widget_show (label);
  
  adjustment = gtk_adjustment_new (gihparms.cellheight,
				   2, gimp_image_height (image_ID), 1, 1, 1);
  spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (adjustment), 1, 0);
  gtk_spin_button_set_shadow_type (GTK_SPIN_BUTTON (spinbutton),
				    GTK_SHADOW_NONE);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
  gtk_widget_set_usize (spinbutton, 75, 0);
  gtk_box_pack_start (GTK_BOX (box), spinbutton, FALSE, FALSE, 0);
  cellh_adjust.orientation = ORIENTATION_HORIZONTAL;
  cellh_adjust.image = image_ID;
  cellh_adjust.toplayer = layer_ID[nlayers-1];
  cellh_adjust.nguides = 0;
  cellh_adjust.guides = NULL;
  cellh_adjust.value = &gihparms.cellheight;
  gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
		      (GtkSignalFunc) size_adjustment_callback,
		      &cellh_adjust);
  gtk_widget_show (spinbutton);

  label = gtk_label_new (" pixels");
  gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
  gtk_widget_show (label);
  
  gtk_table_attach (GTK_TABLE (table), box, 1, 2,
		    GTK_TABLE (table)->nrows - 1, GTK_TABLE (table)->nrows,
		    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (box);

  g_free (layer_ID);

  /*
   * Number of cells: ___
   */

  gtk_table_resize (GTK_TABLE (table),
		    GTK_TABLE (table)->nrows + 1, GTK_TABLE (table)->ncols);

  label = gtk_label_new ("Number of cells:");
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), label, 0, 1,
		    GTK_TABLE (table)->nrows - 1, GTK_TABLE (table)->nrows,
		    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (label);

  adjustment = gtk_adjustment_new (gihparms.ncells, 1, 1000, 1, 10, 10);
  spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (adjustment), 1, 0);
  gtk_spin_button_set_shadow_type (GTK_SPIN_BUTTON (spinbutton),
				   GTK_SHADOW_NONE);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
  gtk_widget_set_usize (spinbutton, 75, 0);
  box = gtk_hbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (box), spinbutton, FALSE, TRUE, 0);
  gtk_table_attach (GTK_TABLE (table), box, 1, 2,
		    GTK_TABLE (table)->nrows - 1, GTK_TABLE (table)->nrows,
		    GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
  gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
		      (GtkSignalFunc) adjustment_callback, &gihparms.ncells);
  gtk_widget_show (spinbutton);
  gtk_widget_show (box);

  if (gihparms.dim == 1)
    cellw_adjust.ncells = cellh_adjust.ncells = adjustment;
  else
    cellw_adjust.ncells = cellh_adjust.ncells = NULL;

  /*
   * Display as: __ rows x __ cols
   */
  gtk_table_resize (GTK_TABLE (table),
		    GTK_TABLE (table)->nrows + 1, GTK_TABLE (table)->ncols);

  label = gtk_label_new ("Display as:");
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), label, 0, 1,
		    GTK_TABLE (table)->nrows - 1, GTK_TABLE (table)->nrows,
		    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (label);

  box = gtk_hbox_new (FALSE, 0);
  
  sprintf (buffer, "%2d", gihparms.rows);
  label = gtk_label_new (buffer);
  gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
  cellh_adjust.count_label = label;
  cellh_adjust.count = &gihparms.rows;
  cellh_adjust.other_count = &gihparms.cols;
  gtk_widget_show (label);

  label = gtk_label_new (" rows of ");
  gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
  gtk_widget_show (label);
  
  sprintf (buffer, "%2d", gihparms.cols);
  label = gtk_label_new (buffer);
  gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
  cellw_adjust.count_label = label;
  cellw_adjust.count = &gihparms.cols;  
  cellw_adjust.other_count = &gihparms.rows;
  gtk_widget_show (label);

  label = gtk_label_new (" columns on each layer");
  gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
  gtk_widget_show (label);
  
  label = gtk_label_new (" (width mismatch!) ");
  gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
  cellw_adjust.warning_label = label;
  
  label = gtk_label_new (" (height mismatch!) ");
  gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
  cellh_adjust.warning_label = label;
  
  gtk_table_attach (GTK_TABLE (table), box, 1, 2,
		    GTK_TABLE (table)->nrows - 1, GTK_TABLE (table)->nrows,
		    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (box);
  
  /*
   * Dimension: ___
   */

  gtk_table_resize (GTK_TABLE (table),
		    GTK_TABLE (table)->nrows + 1, GTK_TABLE (table)->ncols);

  label = gtk_label_new ("Dimension:");
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), label, 0, 1,
		    GTK_TABLE (table)->nrows - 1, GTK_TABLE (table)->nrows,
		    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (label);

  adjustment = gtk_adjustment_new (gihparms.dim, 1, 5, 1, 1, 1);
  spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (adjustment), 1, 0);
  gtk_spin_button_set_shadow_type (GTK_SPIN_BUTTON (spinbutton),
				    GTK_SHADOW_NONE);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
  gtk_widget_set_usize (spinbutton, 75, 0);
  box = gtk_hbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (box), spinbutton, FALSE, FALSE, 0);
  gtk_table_attach (GTK_TABLE (table), box, 1, 2,
		    GTK_TABLE (table)->nrows - 1, GTK_TABLE (table)->nrows,
		    GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
  gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
		      (GtkSignalFunc) adjustment_callback, &gihparms.dim);
  gtk_widget_show (spinbutton);
  gtk_widget_show (box);

  /*
   * Ranks: __ __ __ __ __
   */

  gtk_table_resize (GTK_TABLE (table),
		    GTK_TABLE (table)->nrows + 1, GTK_TABLE (table)->ncols);

  label = gtk_label_new ("Ranks:");
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), label, 0, 1,
		    GTK_TABLE (table)->nrows - 1, GTK_TABLE (table)->nrows,
		    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (label);

  dimtable = gtk_table_new (PIXPIPE_MAXDIM, 1, FALSE);
  for (i = 0; i < PIXPIPE_MAXDIM; i++)
    {
      adjustment = gtk_adjustment_new (gihparms.rank[i], 0, 100, 1, 1, 1);
      spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (adjustment), 1, 0);
      gtk_spin_button_set_shadow_type (GTK_SPIN_BUTTON (spinbutton),
				       GTK_SHADOW_NONE);
      gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
      gtk_widget_set_usize (spinbutton, 75, 0);
      box = gtk_hbox_new (FALSE, 0);
      gtk_box_pack_start (GTK_BOX (box), spinbutton, FALSE, TRUE, 0);
      gtk_table_attach (GTK_TABLE (dimtable), box, i, i + 1, 0, 1,
			GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 3, 0);
      gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
			  (GtkSignalFunc) adjustment_callback, &gihparms.rank[i]);
      gtk_widget_show (spinbutton);
      gtk_widget_show (box);
      if (i == 0)
	if (gihparms.dim == 1)
	  cellw_adjust.rank0 = cellh_adjust.rank0 = adjustment;
	else
	  cellw_adjust.rank0 = cellh_adjust.rank0 = NULL;
    }
  gtk_table_attach (GTK_TABLE (table), dimtable, 1, 2,
		    GTK_TABLE (table)->nrows - 1, GTK_TABLE (table)->nrows,
		    GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
  gtk_widget_show (dimtable);

  /*
   * Selection: ______ ______ ______ ______ ______
   */
  
  gtk_table_resize (GTK_TABLE (table),
		    GTK_TABLE (table)->nrows + 1, GTK_TABLE (table)->ncols);

  label = gtk_label_new ("Selection:");
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), label, 0, 1,
		    GTK_TABLE (table)->nrows - 1, GTK_TABLE (table)->nrows,
		    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (label);

  cbitems = g_list_append (cbitems, "incremental");
  cbitems = g_list_append (cbitems, "angular");
  cbitems = g_list_append (cbitems, "random");
  cbitems = g_list_append (cbitems, "velocity");
  cbitems = g_list_append (cbitems, "pressure");
  cbitems = g_list_append (cbitems, "xtilt");
  cbitems = g_list_append (cbitems, "ytilt");

  box = gtk_hbox_new (FALSE, 0);
  for (i = 0; i < PIXPIPE_MAXDIM; i++)
    {
      cb = gtk_combo_new ();
      gtk_combo_set_popdown_strings (GTK_COMBO (cb), cbitems);
      if (gihparms.selection[i])
	gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (cb)->entry), gihparms.selection[i]);
      else
	gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (cb)->entry), "random");
      gtk_entry_set_editable (GTK_ENTRY (GTK_COMBO (cb)->entry), FALSE);

      gtk_box_pack_start (GTK_BOX (box), cb, FALSE, TRUE, 2);
      gtk_signal_connect (GTK_OBJECT (GTK_COMBO (cb)->entry), "changed",
			  (GtkSignalFunc) cb_callback, &gihparms.selection[i]);
      gtk_widget_show (cb);
    }

  gtk_table_attach (GTK_TABLE (table), box, 1, 2,
		    GTK_TABLE (table)->nrows - 1, GTK_TABLE (table)->nrows,
		    GTK_SHRINK, GTK_EXPAND | GTK_FILL, 0, 0);
  gtk_widget_show (box);
  
  gtk_widget_show (dlg);

  gtk_main ();
  gdk_flush ();

  for (i = 0; i < cellw_adjust.nguides; i++)
    gimp_image_delete_guide (image_ID, cellw_adjust.guides[i]);
  for (i = 0; i < cellh_adjust.nguides; i++)
    gimp_image_delete_guide (image_ID, cellh_adjust.guides[i]);

  if (run_flag)
    {
      /* Fix up bogus values */
      gihparms.ncells =
	MIN (gihparms.ncells,
	     num_layers_with_alpha * gihparms.rows * gihparms.cols);
    }
  return run_flag;
}

static gboolean
try_fwrite (gpointer buffer,
	    guint   size,
	    guint   nitems,
	    FILE   *file)
{
  if (fwrite (buffer, size, nitems, file) < nitems)
    {
      g_message ("GPB: write error");
      fclose (file);
      return FALSE;
    }
  return TRUE;
}

static gboolean
save_one_gpb (FILE      *file,
	      GPixelRgn *pixel_rgn,
	      int	 index,
	      int        total)
{
  BrushHeader brushheader;
  PatternHeader patternheader;
  guint y, x;
  guchar *buffer;
  guint width, height;

  width = pixel_rgn->w;
  height = pixel_rgn->h;

  brushheader.header_size = g_htonl (sizeof (brushheader) + strlen (info.description) + 1);
  brushheader.version = g_htonl (2);
  brushheader.width = g_htonl (width);
  brushheader.height = g_htonl (height);
  brushheader.bytes = g_htonl (1);
  brushheader.magic_number = g_htonl (GBRUSH_MAGIC);
  brushheader.spacing = g_htonl (info.spacing);

  if (!try_fwrite (&brushheader, sizeof (brushheader), 1, file))
    return FALSE;
  if (!try_fwrite (info.description, strlen (info.description) + 1, 1, file))
    return FALSE;

  IFDBG(3) g_message ("saving gpb %d of %d: %dx%d, offset %d,%d stride %d drawable %d ",
		      index+1, total,
		      width, height, pixel_rgn->x, pixel_rgn->y,
		      pixel_rgn->rowstride, pixel_rgn->drawable->id);
  
  buffer = g_malloc (width * pixel_rgn->bpp);
  for (y = 0; y < height; y++)
    {
#if 0
      /* No; the brushes don't have to be the same size, of course. */
      if (y >= pixel_rgn->h)
	{
	  if (y == pixel_rgn->h)
	    memset (buffer, 0, width);
	  if (!try_fwrite (buffer, width, 1, file))
	    {
	      g_free (buffer);
	      return FALSE;
	    }
	}
      else
#endif
	{
	  gimp_pixel_rgn_get_row (pixel_rgn, buffer,
				  0 + pixel_rgn->x, y + pixel_rgn->y,
				  pixel_rgn->w);
	  for (x = 0; x < pixel_rgn->w; x++)
	    if (!try_fwrite (buffer + x*pixel_rgn->bpp + 3, 1, 1, file))
	      {
		g_free (buffer);
		return FALSE;
	      }
#if 0
	  if (width > pixel_rgn->w)
	    {
	      memset (buffer, 0, width - pixel_rgn->w);
	      if (!try_fwrite (buffer, width - pixel_rgn->w, 1, file))
		{
		  g_free (buffer);
		  return FALSE;
		}
	    }
#endif
	}
      if (y % 10 == 0)
	gimp_progress_update
	  (((double) index + 0.5 * y / pixel_rgn->h) / total);
    }

  gimp_progress_update (((double) index + 0.5) / total);

  patternheader.header_size = g_htonl (sizeof (patternheader) + strlen (DUMMY_PATTERN_NAME) + 1 );
  patternheader.version = g_htonl (1);
  patternheader.width = g_htonl (width);
  patternheader.height = g_htonl (height);
  patternheader.bytes = g_htonl (3);
  patternheader.magic_number = g_htonl (GPATTERN_MAGIC);

  if (!try_fwrite (&patternheader, sizeof (patternheader), 1, file))
    return FALSE;

  /* Pattern name is irrelevant */
  if (!try_fwrite (DUMMY_PATTERN_NAME, strlen (DUMMY_PATTERN_NAME) + 1, 1, file))
    return FALSE;

  for (y = 0; y < height; y++)
    {
#if 0
      if (y >= pixel_rgn->h)
	{
	  if (y == pixel_rgn->h)
	    memset (buffer, 0, width * 3);
	  if (!try_fwrite (buffer, width*3, 1, file))
	    {
	      g_free (buffer);
	      return FALSE;
	    }
	}
      else
#endif
	{
	  gimp_pixel_rgn_get_row (pixel_rgn, buffer,
				  0 + pixel_rgn->x, y + pixel_rgn->y, pixel_rgn->w);
	  for (x = 0; x < pixel_rgn->w; x++)
	    if (!try_fwrite (buffer + x*pixel_rgn->bpp, 3, 1, file))
	      return FALSE;
#if 0
	  if (width > pixel_rgn->w)
	    {
	      memset (buffer, 0, (width - pixel_rgn->w) * 3);
	      if (!try_fwrite (buffer, (width - pixel_rgn->w) * 3, 1, file))
		{
		  g_free (buffer);
		  return FALSE;
		}
	    }
#endif
	}
      
      if (y % 10 == 0)
	gimp_progress_update
	  (((double) index + 0.5 + 0.5 * y / pixel_rgn->h) / total);
    }
  g_free (buffer);

  gimp_progress_update (((double) index + 1.0) / total);

  return TRUE;
}

static gboolean
gpb_save_image (char   *filename,
		gint32  image_ID,
		gint32  drawable_ID)
{
  GDrawable *drawable;
  GPixelRgn pixel_rgn;
  FILE *file;
  gchar *temp;

  g_assert (gimp_drawable_has_alpha (drawable_ID));

  drawable = gimp_drawable_get (drawable_ID);
  gimp_tile_cache_size (gimp_tile_height () * drawable->width * 4);

  gimp_pixel_rgn_init (&pixel_rgn, drawable, 0, 0, drawable->width, drawable->height, FALSE, FALSE);

  temp = g_strdup_printf ("Saving %s:", filename);
  gimp_progress_init (temp);
  g_free (temp);

  file = fopen (filename, "wb");

  if (file == NULL)
    {
      g_message ("GPB: can't open \"%s\"", filename);
      return FALSE;
    }

  if (!save_one_gpb (file, &pixel_rgn, 0, 1))
    return FALSE;

  fclose (file);

  return TRUE;
}

static gboolean
gih_save_image (char   *filename,
		gint32  image_ID,
		gint32  drawable_ID)
{
  GDrawable *drawable;
  GPixelRgn pixel_rgn;
  FILE *file;
  Parasite *pipe_parasite;
  gchar *msg, *parstring, *ncells;
  gint32 *layer_ID;
  gint nlayers, layer, row, col;
  gint imagew, imageh, offsetx, offsety;
  gint k, x, y, thisx, thisy, xnext, ynext, thisw, thish;

  imagew = gimp_image_width (image_ID);
  imageh = gimp_image_height (image_ID);
  gimp_tile_cache_size (gimp_tile_height () * imagew * 4);

  msg = g_strdup_printf ("Saving %s:", filename);
  gimp_progress_init (msg);
  g_free (msg);

  file = fopen (filename, "wb");

  if (file == NULL)
    {
      g_message ("GPB: can't open \"%s\"", filename);
      return FALSE;
    }

  parstring = pixpipeparams_build (&gihparms);
  IFDBG(2) g_message ("parameter string: %s", parstring);
  ncells = g_strdup_printf ("%d ", gihparms.ncells);
  if (!(try_fwrite (info.description, strlen (info.description), 1, file)
	&& try_fwrite ("\n", 1, 1, file)
	&& try_fwrite (ncells, strlen (ncells), 1, file)
	&& try_fwrite (parstring, strlen (parstring), 1, file)
	&& try_fwrite ("\n", 1, 1, file)))
    {
      g_free (parstring);
      g_free (ncells);
      return FALSE;
    }

  pipe_parasite = parasite_new ("gimp-brush-pipe-parameters",
				PARASITE_PERSISTENT,
				strlen (parstring) + 1, parstring);
  gimp_image_attach_parasite (image_ID, pipe_parasite);
  parasite_free (pipe_parasite);

  g_free (parstring);
  g_free (ncells);

  layer_ID = gimp_image_get_layers (image_ID, &nlayers);
  
  k = 0;
  for (layer = 0; layer < nlayers; layer++)
    {
      if (!gimp_drawable_has_alpha (layer_ID[layer]))
	continue;
      drawable = gimp_drawable_get (layer_ID[layer]);
      gimp_drawable_offsets(layer_ID[layer], &offsetx, &offsety);

      for (row = 0; row < gihparms.rows; row++)
	{
	  y = (row * imageh) / gihparms.rows ;
	  ynext = ((row + 1) * imageh / gihparms.rows);
	  /* Assume layer is offset to positive direction in x and y.
	   * That's reasonable, as otherwise all of the layer
	   * won't be visible.
	   * thisy and thisx are in the drawable's coordinate space.
	   */
	  thisy = MAX (0, y - offsety);
	  thish = (ynext - offsety) - thisy;
	  thish = MIN (thish, drawable->height - thisy);
	  for (col = 0; col < gihparms.cols; col++)
	    {
	      x = (col * imagew / gihparms.cols);
	      xnext = ((col + 1) * imagew / gihparms.cols);
	      thisx = MAX (0, x - offsetx);
	      thisw = (xnext - offsetx) - thisx;
	      thisw = MIN (thisw, drawable->width - thisx);
	      IFDBG(3) g_message ("gimp_pixel_rgn_init %d %d %d %d",
				  thisx, thisy, thisw, thish);
	      gimp_pixel_rgn_init (&pixel_rgn, drawable, thisx, thisy, 
				   thisw, thish, FALSE, FALSE);
	      if (!save_one_gpb (file, &pixel_rgn, k, gihparms.ncells))
		return FALSE;
	      k++;
	    }
	}

    }
  
  gimp_progress_update (1.0);

  fclose (file);

  return TRUE;
}

static void
run (char    *name,
     int      nparams,
     GParam  *param,
     int     *nreturn_vals,
     GParam **return_vals)
{
  static GParam values[1];
  GRunModeType run_mode;
  GStatusType status = STATUS_SUCCESS;
  Parasite *pipe_parasite;
  gint32 image_ID, *layer_ID;
  gint nlayers, layer;
  gchar *layer_name;

  run_mode = param[0].data.d_int32;

  *return_vals = values;
  *nreturn_vals = 1;
  values[0].type = PARAM_STATUS;
  values[0].data.d_status = STATUS_CALLING_ERROR;

  if (strcmp (name, "file_gpb_save") == 0)
    {
      switch (run_mode) {
      case RUN_INTERACTIVE:
	/*  Possibly retrieve data  */
	gimp_get_data ("file_gpb_save", &info);
	if (!gpb_save_dialog ())
	  return;
	break;

      case RUN_NONINTERACTIVE:  /* FIXME - need a real RUN_NONINTERACTIVE */
	if (nparams != 7)
	  status = STATUS_CALLING_ERROR;
	if (status == STATUS_SUCCESS)
	  {
	    info.spacing = param[5].data.d_int32;
	    strncpy (info.description, param[6].data.d_string, MAXDESCLEN);
	    info.description[MAXDESCLEN] = 0;
	  }
	break;

      case RUN_WITH_LAST_VALS:
	gimp_get_data ("file_gpb_save", &info);
	break;
      }

      if (gpb_save_image (param[3].data.d_string, param[1].data.d_int32, param[2].data.d_int32))
	{
	  gimp_set_data ("file_gpb_save", &info, sizeof (info));
	  status = STATUS_SUCCESS;
	}
      else
	status = STATUS_EXECUTION_ERROR;
    }
  else if (strcmp (name, "file_gih_save") == 0)
    {
      image_ID = param[1].data.d_int32;
      layer_ID = gimp_image_get_layers (image_ID, &nlayers);
      num_layers_with_alpha = 0;
      for (layer = 0; layer < nlayers; layer++)
	{
	  if (!gimp_drawable_has_alpha (layer_ID[layer]))
	    {
	      layer_name = gimp_layer_get_name (layer_ID[layer]);
	      g_message ("Layer %s doesn't have an alpha channel, skipped",
			 layer_name);
	      g_free (layer_name);
	    }
	  num_layers_with_alpha++;
	}
      
      IFDBG(2) g_message ("nlayers:%d num_layers_with_alpha:%d",
			  nlayers, num_layers_with_alpha);
      switch (run_mode)
	{
	case RUN_INTERACTIVE:
	  /*  Possibly retrieve data  */
	  gimp_get_data ("file_gih_save", &info);
	  pipe_parasite = gimp_image_find_parasite (image_ID, "gimp-brush-pipe-parameters");
	  pixpipeparams_init (&gihparms);
	  if (pipe_parasite)
	    pixpipeparams_parse (pipe_parasite->data, &gihparms);

	  if (!gih_save_dialog (image_ID))
	    return;
	  break;

	case RUN_NONINTERACTIVE:  /* FIXME - need a real RUN_NONINTERACTIVE */
	  if (nparams != 7)
	    status = STATUS_CALLING_ERROR;
	  if (status == STATUS_SUCCESS)
	    {
	      info.spacing = param[5].data.d_int32;
	      strncpy (info.description, param[6].data.d_string, MAXDESCLEN);
	      info.description[MAXDESCLEN] = 0;
	    }
	  break;
	  
	case RUN_WITH_LAST_VALS:
	  gimp_get_data ("file_gih_save", &info);
	  break;
	}
      
      if (gih_save_image (param[3].data.d_string, param[1].data.d_int32, param[2].data.d_int32))
	{
	  gimp_set_data ("file_gih_save", &info, sizeof (info));
	  status = STATUS_SUCCESS;
	}
      else
	status = STATUS_EXECUTION_ERROR;
    }
  values[0].data.d_status = status;
}
