/* Convolution Matrix plug-in for the GIMP -- Version 0.1
 * Copyright (C) 1997 Lauri Alanko <la@iki.fi>
 *
 *
 * 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.
 *
 * The GNU General Public License is also available from
 * http://www.fsf.org/copyleft/gpl.html
 *
 *
 * CHANGELOG:
 * v0.13	15.12.2000
 * 	Made the PDB interface actually work.   (Simon Budig <simon@gimp.org>)
 *
 * v0.12	15.9.1997
 *	Got rid of the unportable snprintf. Also made some _tiny_ GUI fixes.
 *
 * v0.11	20.7.1997
 *	Negative values in the matrix are now abs'ed when used to weight
 *      alpha. Embossing effects should work properly now. Also fixed a
 *      totally idiotic bug with embossing.
 *
 * v0.1 	2.7.1997
 *	Initial release. Works... kinda.
 *
 *
 * TODO:
 *
 * - remove channels selector (that's what the channels dialog is for)
 * - remove idiotic slowdowns
 * - clean up code
 * - preview
 * - optimize properly
 * - save & load matrices
 * - spiffy frontend for designing matrices
 *
 * What else?
 *
 *
 */

#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <gtk/gtk.h>

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

#include "libgimp/stdplugins-intl.h"


#define RESPONSE_RESET 1

typedef enum
{
  EXTEND,
  WRAP,
  CLEAR,
  MIRROR
} BorderMode;

static GimpDrawable *drawable;

static gchar * const channel_labels[] =
{
  N_("Gr_ey"),
  N_("Re_d"),
  N_("_Green"),
  N_("_Blue"),
  N_("_Alpha")
};

static gchar * const bmode_labels[] =
{
  N_("E_xtend"),
  N_("_Wrap"),
  N_("Cro_p")
};

/* Declare local functions. */
static void query (void);
static void run   (const gchar      *name,
		   gint              nparams,
		   const GimpParam  *param,
		   gint             *nreturn_vals,
		   GimpParam       **return_vals);

static gboolean  dialog       (void);

static void      convmatrix   (void);
static void      check_config (void);


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

static gint     bytes;
static gboolean run_flag = FALSE;

typedef struct
{
  gfloat     matrix[5][5];
  gfloat     divisor;
  gfloat     offset;
  gint       alpha_alg;
  BorderMode bmode;
  gint       channels[5];
  gint       autoset;
} config;

static const config default_config =
{
  {
    { 0.0, 0.0, 0.0, 0.0, 0.0 },
    { 0.0, 0.0, 0.0, 0.0, 0.0 },
    { 0.0, 0.0, 1.0, 0.0, 0.0 },
    { 0.0, 0.0, 0.0, 0.0, 0.0 },
    { 0.0, 0.0, 0.0, 0.0, 0.0 }
  },                 /* matrix */
  1,                 /* divisor */
  0,                 /* offset */
  1,                 /* Alpha-handling algorithm */
  CLEAR,             /* border-mode */
  { 1, 1, 1, 1, 1 }, /* Channels mask */
  0                  /* autoset */
};

static config my_config;

struct
{
  GtkWidget *matrix[5][5];
  GtkWidget *divisor;
  GtkWidget *offset;
  GtkWidget *alpha_alg;
  GtkWidget *bmode[3];
  GtkWidget *channels[5];
  GtkWidget *autoset;
} my_widgets;


MAIN ()

static void
query (void)
{
  static GimpParamDef args[] =
  {
    { GIMP_PDB_INT32,    "run_mode", "Interactive, non-interactive" },
    { GIMP_PDB_IMAGE,    "image",    "Input image (unused)" },
    { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" },
    { GIMP_PDB_INT32, "argc_matrix", "The number of elements in the following array. Should be always 25." },
    { GIMP_PDB_FLOATARRAY, "matrix", "The 5x5 convolution matrix" },
    { GIMP_PDB_INT32, "alpha_alg", "Enable weighting by alpha channel" },
    { GIMP_PDB_FLOAT, "divisor", "Divisor" },
    { GIMP_PDB_FLOAT, "offset", "Offset" },

    { GIMP_PDB_INT32, "argc_channels", "The number of elements in following array. Should be always 5." },
    { GIMP_PDB_INT32ARRAY, "channels", "Mask of the channels to be filtered" },
    { GIMP_PDB_INT32, "bmode", "Mode for treating image borders" }
  };

  gimp_install_procedure ("plug_in_convmatrix",
			  "A generic 5x5 convolution matrix",
			  "",
			  "Lauri Alanko",
			  "Lauri Alanko",
			  "1997",
			  N_("_Convolution Matrix..."),
			  "RGB*, GRAY*",
			  GIMP_PLUGIN,
			  G_N_ELEMENTS (args), 0,
			  args, NULL);

  gimp_plugin_menu_register ("plug_in_convmatrix", "<Image>/Filters/Generic");
}

static void
run (const gchar      *name,
     gint              nparams,
     const GimpParam  *param,
     gint             *nreturn_vals,
     GimpParam       **return_vals)
{
  static GimpParam  values[1];
  GimpRunMode       run_mode;
  GimpPDBStatusType status = GIMP_PDB_SUCCESS;
  gint              x, y;

  INIT_I18N ();

  *nreturn_vals = 1;
  *return_vals = values;

  run_mode = param[0].data.d_int32;

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

  /*  The plug-in is not able to handle images smaller than 3 pixels  */
  if (drawable->width < 3 || drawable->height < 3)
    {
      g_message (_("Convolution Matrix does not work on layers "
                   "smaller than 3 pixels."));
      status = GIMP_PDB_EXECUTION_ERROR;
      values[0].type = GIMP_PDB_STATUS;
      values[0].data.d_status = status;
      return;
    }

  my_config = default_config;
  if (run_mode == GIMP_RUN_NONINTERACTIVE)
    {
      if (nparams != 11)
        {
          status = GIMP_PDB_CALLING_ERROR;
        }
      else
	{
	  if (param[3].data.d_int32 != 25)
            {
              status = GIMP_PDB_CALLING_ERROR;
            }
          else
            {
              for (y = 0; y < 5; y++)
                for (x = 0; x < 5; x++)
                  my_config.matrix[x][y]=param[4].data.d_floatarray[y*5+x];
            }

	  my_config.alpha_alg = param[5].data.d_int32;
	  my_config.divisor   = param[6].data.d_float;
	  my_config.offset    = param[7].data.d_float;


	  if (param[8].data.d_int32 != 5)
            {
              status = GIMP_PDB_CALLING_ERROR;
            }
          else
            {
              for (y = 0; y < 5; y++)
                my_config.channels[y] = param[9].data.d_int32array[y];
            }

	  my_config.bmode     = param[10].data.d_int32;

	  check_config ();
	}
    }
  else
    {
      gimp_get_data ("plug_in_convmatrix", &my_config);

      if (run_mode == GIMP_RUN_INTERACTIVE)
	{
	  /*  Oh boy. We get to do a dialog box, because we can't really
	   *  expect the user to set us up with the right values using gdb.
	   */
	  check_config ();

	  if (! dialog ())
	    {
	      /* The dialog was closed, or something similarly evil happened. */
	      status = GIMP_PDB_EXECUTION_ERROR;
	    }
	}
    }

  if (status == GIMP_PDB_SUCCESS)
    {
      /*  Make sure that the drawable is gray or RGB color  */
      if (gimp_drawable_is_rgb (drawable->drawable_id) ||
	  gimp_drawable_is_gray (drawable->drawable_id))
	{
	  gimp_progress_init (_("Applying convolution"));
	  gimp_tile_cache_ntiles (2 * (drawable->width /
				       gimp_tile_width () + 1));

	  convmatrix ();

	  if (run_mode != GIMP_RUN_NONINTERACTIVE)
	    gimp_displays_flush ();

	  if (run_mode == GIMP_RUN_INTERACTIVE)
	    gimp_set_data ("plug_in_convmatrix",
                           &my_config, sizeof (my_config));
	}
      else
	{
	  status = GIMP_PDB_EXECUTION_ERROR;
	}

      gimp_drawable_detach (drawable);
    }

  values[0].type = GIMP_PDB_STATUS;
  values[0].data.d_status = status;
}


/*  A generic wrapper to gimp_pixel_rgn_get_row which handles unlimited
 *  wrapping or gives you transparent regions outside the image
 */

static void
my_get_row (GimpPixelRgn *PR,
	    guchar       *dest,
	    gint          x,
	    gint          y,
	    gint          w)
{
  gint width, height, bytes;
  gint i;

  width  = PR->drawable->width;
  height = PR->drawable->height;
  bytes  = PR->drawable->bpp;

  /* Y-wrappings */

  switch (my_config.bmode)
    {
    case WRAP:
      /* Wrapped, so we get the proper row from the other side */
      while (y < 0) /* This is the _sure_ way to wrap. :) */
	y += height;
      while (y >= height)
	y -= height;
      break;

    case CLEAR:
      /* Beyond borders, so set full transparent. */
      if (y < 0 || y >= height)
	{
	  memset (dest, 0, w * bytes);
	  return; /* Done, so back. */
	}
    case MIRROR:
      /* The border lines are _not_ duplicated in the mirror image */
      /* is this right? */
      while (y < 0 || y >= height)
	{
	  if (y < 0)
	    y = -y; /* y=-y-1 */
	  if (y >= height)
	    y = 2 * height - y - 2; /* y=2*height-y-1 */
	}
      break;

    case EXTEND:
      y = CLAMP (y , 0 , height - 1);
      break;
    }

  switch (my_config.bmode)
    {
    case CLEAR:
      if (x < 0)
	{
	  i = MIN (w, -x);
	  memset (dest, 0, i * bytes);
	  dest += i * bytes;
	  w -= i;
	  x += i;
	}
      if (w)
	{
	  i = MIN (w, width);
	  gimp_pixel_rgn_get_row (PR, dest, x, y, i);
	  dest += i * bytes;
	  w -= i;
	  x += i;
	}
      if (w)
	memset (dest, 0, w * bytes);
      break;

    case WRAP:
      while (x < 0)
	x += width;
      i = MIN (w, width - x);
      gimp_pixel_rgn_get_row (PR, dest, x, y, i);
      w -= i;
      dest += i * bytes;
      x = 0;
      while (w)
	{
	  i = MIN (w, width);
	  gimp_pixel_rgn_get_row (PR, dest, x, y, i);
	  w -= i;
	  dest += i * bytes;
	}
      break;

    case EXTEND:
      if (x < 0)
	{
	  gimp_pixel_rgn_get_pixel (PR, dest, 0, y);
	  x++;
	  w--;
	  dest += bytes;

	  while (x < 0 && w)
	    {
	      for (i = 0; i < bytes; i++)
		{
		  *dest = *(dest - bytes);
		  dest++;
		}
	      x++;
	      w--;
	    }
	}
      if (w && width - x > 0)
	{
	  i = MIN (w, width - x);
	  gimp_pixel_rgn_get_row (PR, dest, x, y, i);
	  w -= i;
	  dest += i * bytes;
	}
      while (w)
	{
	  for (i = 0; i < bytes; i++)
	    {
	      *dest= *(dest - bytes);
	      dest++;
	    }
	  x++;
	  w--;
	}
      break;

    case MIRROR: /* Not yet handled */
      break;
    }
}

static gfloat
calcmatrix (guchar **srcrow,
	    gint     xoff,
	    gint     i)
{
  static gfloat matrixsum = 0;
  static gint bytes       = 0;

  gfloat sum      = 0;
  gfloat alphasum = 0;
  gfloat temp;
  gint   x, y;

  if (!bytes)
    {
      bytes = drawable->bpp;
      for (y = 0; y < 5; y++)
	for (x = 0; x < 5; x++)
	  {
	    temp = my_config.matrix[x][y];
	    matrixsum += ABS (temp);
	  }
    }
  for (y = 0; y < 5; y++)
    for (x = 0; x < 5; x++)
      {
	temp = my_config.matrix[x][y];
	if (i != (bytes - 1) && my_config.alpha_alg == 1)
	  {
	    temp *= srcrow[y][xoff + x * bytes +bytes - 1 - i];
	    alphasum += ABS (temp);
	  }
	temp *= srcrow[y][xoff + x * bytes];
	sum += temp;
      }
  sum /= my_config.divisor;
  if (i != (bytes - 1) && my_config.alpha_alg == 1)
    {
      if (alphasum != 0)
	sum = sum * matrixsum / alphasum;
      else
	sum = 0;
      /* sum = srcrow[2][xoff + 2 * bytes] * my_config.matrix[2][2];*/
    }
  sum += my_config.offset;

  return sum;
}

static void
convmatrix (void)
{
  GimpPixelRgn  srcPR, destPR;
  gint          width, height, row, col;
  gint          w, h, i;
  gint 		sx1, sy1, sx2, sy2;
  gint          x1, x2, y1, y2;
  guchar       *destrow[3];
  guchar       *srcrow[5];
  guchar       *temprow;
  gfloat        sum;
  gint          xoff;
  gint          chanmask[4];

  /* Get the input area. This is the bounding box of the selection in
   *  the image (or the entire image if there is no selection). Only
   *  operating on the input area is simply an optimization. It doesn't
   *  need to be done for correct operation. (It simply makes it go
   *  faster, since fewer pixels need to be operated on).
   */
  gimp_drawable_mask_bounds (drawable->drawable_id, &sx1, &sy1, &sx2, &sy2);
  w = sx2 - sx1;
  h = sy2 - sy1;

  /* Get the size of the input image. (This will/must be the same
   *  as the size of the output image.
   */
  width  = drawable->width;
  height = drawable->height;
  bytes  = drawable->bpp;

  if (gimp_drawable_is_rgb (drawable->drawable_id))
    for (i = 0; i <3; i++)
      chanmask[i] = my_config.channels[i + 1];
  else /* Grayscale */
    chanmask[0] = my_config.channels[0];

  if (gimp_drawable_has_alpha (drawable->drawable_id))
    chanmask[bytes - 1] = my_config.channels[4];

  for (i = 0; i < 5; i++)
    srcrow[i] = g_new (guchar, (w + 4) * bytes);
  for (i = 0; i < 3; i++)
    destrow[i]= g_new (guchar, w * bytes);

  /*  initialize the pixel regions  */
  x1 = MAX (sx1 - 2, 0);
  y1 = MAX (sy1 - 2, 0);
  x2 = MIN (sx2 + 2, width);
  y2 = MIN (sy2 + 2, height);
  gimp_pixel_rgn_init (&srcPR, drawable,
                       x1, y1, x2 - x1, y2 - y1, FALSE, FALSE);
  gimp_pixel_rgn_init (&destPR, drawable, sx1, sy1, w, h, TRUE, TRUE);

  /* initialize source arrays */
  for (i = 0; i < 5; i++)
    my_get_row (&srcPR, srcrow[i], sx1 - 2, sy1 + i - 2, w + 4);

  for (row = sy1; row < sy2; row++)
    {
      xoff = 0;

      for (col = sx1; col < sx2; col++)
	for (i = 0; i < bytes; i++)
	  {
	    if (chanmask[i] <= 0)
	      sum = srcrow[2][xoff + 2 * bytes];
	    else
	      sum = calcmatrix(srcrow, xoff, i);

	    destrow[2][xoff]= (guchar) CLAMP (sum, 0, 255);
	    xoff++;
	  }

      if (row > sy1 + 1)
	gimp_pixel_rgn_set_row (&destPR, destrow[0], sx1, row - 2, w);

      temprow = destrow[0];
      destrow[0] = destrow[1];
      destrow[1] = destrow[2];
      destrow[2] = temprow;
      temprow = srcrow[0];

      for (i = 0; i < 4; i++)
	srcrow[i] = srcrow[i + 1];

      srcrow[4] = temprow;
      my_get_row (&srcPR, srcrow[4], sx1 - 2, row + 3, w + 4);

      if (row % 10 == 0)
        gimp_progress_update ((double) (row - sy1) / h);
    }

  /* put the final rows in the buffer in place */
  if (h < 3)
    gimp_pixel_rgn_set_row (&destPR, destrow[2], sx1, row - 3, w);
  if (h > 1)
    gimp_pixel_rgn_set_row (&destPR, destrow[0], sx1, row - 2, w);
  if (h > 2)
    gimp_pixel_rgn_set_row (&destPR, destrow[1], sx1, row - 1, w);

  /*  update the timred region  */
  gimp_drawable_flush (drawable);
  gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
  gimp_drawable_update (drawable->drawable_id, sx1, sy1, sx2 - sx1, sy2 - sy1);
}

/***************************************************
 * GUI stuff
 */

static void
fprint (gfloat  f,
	gchar  *buffer,
        gsize   len)
{
  gint i, t;

  g_snprintf (buffer, len, "%.7f", f);
  buffer[len - 1] = '\0';

  for (t = 0; t < len - 1 && buffer[t] != '.'; t++);

  i = t + 1;

  while (buffer[i] != '\0')
    {
      if (buffer[i] != '0')
	t = i + 1;

      i++;
    }

  buffer[t] = '\0';
}

static void
redraw_matrix (void)
{
  gint  x, y;
  gchar buffer[12];

  for (y = 0; y < 5; y++)
    for (x = 0; x < 5; x++)
      {
        fprint (my_config.matrix[x][y], buffer, sizeof (buffer));
	gtk_entry_set_text (GTK_ENTRY (my_widgets.matrix[x][y]), buffer);
      }
}

static void
redraw_channels (void)
{
  gint i;

  for (i = 0; i < 5; i++)
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (my_widgets.channels[i]),
				  my_config.channels[i] > 0);
}

static void
redraw_autoset (void)
{
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (my_widgets.autoset),
				my_config.autoset);
}

static void
redraw_alpha_alg (void)
{
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (my_widgets.alpha_alg),
				my_config.alpha_alg > 0);
}

static void
redraw_off_and_div (void)
{
  gchar buffer[12];

  fprint (my_config.divisor, buffer, sizeof (buffer));
  gtk_entry_set_text (GTK_ENTRY (my_widgets.divisor), buffer);

  fprint (my_config.offset, buffer, sizeof (buffer));
  gtk_entry_set_text (GTK_ENTRY (my_widgets.offset), buffer);
}

static void
redraw_bmode (void)
{
  gtk_toggle_button_set_active
    (GTK_TOGGLE_BUTTON (my_widgets.bmode[my_config.bmode]), TRUE);
}

static void
redraw_all (void)
{
  redraw_matrix ();
  redraw_off_and_div ();
  redraw_autoset ();
  redraw_alpha_alg ();
  redraw_bmode ();
  redraw_channels ();
}

static void
check_matrix (void)
{
  gint      x, y;
  gboolean  valid = FALSE;
  gfloat    sum   = 0.0;

  for (y = 0; y < 5; y++)
    for (x = 0; x < 5; x++)
      {
	sum += my_config.matrix[x][y];
	if (my_config.matrix[x][y] != 0.0)
	  valid = TRUE;
      }

  if (my_config.autoset)
    {
      if (sum > 0)
	{
	  my_config.offset = 0;
	  my_config.divisor = sum;
	}
      else if (sum < 0)
	{
	  my_config.offset = 255;
	  my_config.divisor = -sum;
	}
      else
	{
	  my_config.offset = 128;
	  /* The sum is 0, so this is probably some sort of
	   * embossing filter. Should divisor be autoset to 1
	   * or left undefined, ie. for the user to define? */
	  my_config.divisor = 1;
	}
      redraw_off_and_div ();
    }
  /* gtk_widget_set_sensitive(my_widgets.ok,valid); */
}

static void
response_callback (GtkWidget *widget,
                   gint       response_id,
		   gpointer   data)
{
  switch (response_id)
    {
    case RESPONSE_RESET:
      my_config = default_config;
      check_config ();
      redraw_all ();
      break;

    case GTK_RESPONSE_OK:
      run_flag = TRUE;

    default:
      gtk_widget_destroy (GTK_WIDGET (widget));
      break;
    }
}

/* Checks that the configuration is valid for the image type */
static void
check_config (void)
{
  gint i;

  for (i = 0; i < 5; i++)
    if (my_config.channels[i] < 0)
      my_config.channels[i] = 0;

  if (gimp_drawable_is_rgb (drawable->drawable_id))
    my_config.channels[0] = -1;
  else if (gimp_drawable_is_gray (drawable->drawable_id))
    for (i = 1; i < 4; i++)
      my_config.channels[i] = -1;

  if (!gimp_drawable_has_alpha (drawable->drawable_id))
    {
      my_config.channels[4] = -1;
      my_config.alpha_alg   = -1;
      my_config.bmode       = EXTEND;
    }
}

static void
entry_callback (GtkWidget *widget,
		gpointer   data)
{
  gfloat *value = (gfloat *) data;

  *value = atof (gtk_entry_get_text (GTK_ENTRY (widget)));

#if 0
  check_matrix ();
#else
  if (widget == my_widgets.divisor)
    gtk_dialog_set_response_sensitive (GTK_DIALOG (gtk_widget_get_toplevel (widget)),
                                       GTK_RESPONSE_OK, (*value != 0.0));
  else if (widget != my_widgets.offset)
    check_matrix ();
#endif
}

static void
my_toggle_callback (GtkWidget *widget,
		    gpointer   data)
{
  gint val = GTK_TOGGLE_BUTTON (widget)->active;

  if (val)
    *(gint *) data = TRUE;
  else
    *(gint *) data = FALSE;

  if (widget == my_widgets.alpha_alg)
    {
      gtk_widget_set_sensitive (my_widgets.bmode[CLEAR], val);
      if (val == 0 && my_config.bmode == CLEAR)
	{
	  my_config.bmode = EXTEND;
	  redraw_bmode ();
	}
    }
  else if (widget == my_widgets.autoset)
    {
      gtk_widget_set_sensitive (my_widgets.divisor, !val);
      gtk_widget_set_sensitive (my_widgets.offset, !val);
      check_matrix ();
    }
}

static void
my_bmode_callback (GtkWidget *widget,
		   gpointer   data)
{
  my_config.bmode = GPOINTER_TO_INT (data) - 1;
}

static gboolean
dialog (void)
{
  GtkWidget *dlg;
  GtkWidget *main_hbox;
  GtkWidget *table;
  GtkWidget *label;
  GtkWidget *entry;
  GtkWidget *button;
  GtkWidget *box;
  GtkWidget *inbox;
  GtkWidget *vbox;
  GtkWidget *frame;
  gint       x, y, i;
  GSList    *group;

  gimp_ui_init ("convmatrix", FALSE);

  dlg = gimp_dialog_new (_("Convolution Matrix"), "convmatrix",
                         NULL, 0,
			 gimp_standard_help_func, "plug-in-convmatrix",

			 GIMP_STOCK_RESET, RESPONSE_RESET,
			 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
			 GTK_STOCK_OK,     GTK_RESPONSE_OK,

			 NULL);

  g_signal_connect (dlg, "response",
                    G_CALLBACK (response_callback),
                    NULL);
  g_signal_connect (dlg, "destroy",
                    G_CALLBACK (gtk_main_quit),
                    NULL);

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

  vbox = gtk_vbox_new (FALSE, 12);
  gtk_box_pack_start (GTK_BOX (main_hbox), vbox, TRUE, TRUE, 0);

  frame = gimp_frame_new (_("Matrix"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);

  inbox = gtk_vbox_new (FALSE, 12);
  gtk_container_add (GTK_CONTAINER (frame), inbox);

  table = gtk_table_new (5, 5, FALSE);
  gtk_table_set_row_spacings (GTK_TABLE (table), 4);
  gtk_table_set_col_spacings (GTK_TABLE (table), 4);
  gtk_box_pack_start (GTK_BOX (inbox), table, TRUE, TRUE, 0);

  for (y = 0; y < 5; y++)
    for (x = 0; x < 5; x++)
      {
	my_widgets.matrix[x][y] = entry = gtk_entry_new ();
	gtk_widget_set_size_request (entry, 40, -1);
	gtk_table_attach (GTK_TABLE (table), entry, x, x+1, y, y+1,
			  GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	gtk_widget_show (entry);

	g_signal_connect (entry, "changed",
                          G_CALLBACK (entry_callback),
                          &my_config.matrix[x][y]);
      }

  gtk_widget_show (table);

  box = gtk_hbox_new (FALSE, 6);
  gtk_box_pack_start (GTK_BOX (inbox), box, FALSE, FALSE, 0);

  table = gtk_table_new (1, 2, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
  gtk_box_pack_start (GTK_BOX (box), table, TRUE, FALSE, 0);

  label = gtk_label_new_with_mnemonic (_("D_ivisor:"));
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 0, 1);
  gtk_widget_show (label);

  my_widgets.divisor = entry = gtk_entry_new ();
  gtk_widget_set_size_request (entry, 40, -1);
  gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 0, 1);
  gtk_widget_show (entry);
  gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);

  g_signal_connect (entry, "changed",
                    G_CALLBACK (entry_callback),
                    &my_config.divisor);

  gtk_widget_show (table);

  table = gtk_table_new (1, 2, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
  gtk_box_pack_start (GTK_BOX (box), table, TRUE, FALSE, 0);

  label = gtk_label_new_with_mnemonic (_("O_ffset:"));
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 0, 1);
  gtk_widget_show (label);

  my_widgets.offset = entry = gtk_entry_new ();
  gtk_widget_set_size_request (entry, 40, -1);
  gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 0, 1);
  gtk_widget_show (entry);
  gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);

  g_signal_connect (entry, "changed",
                    G_CALLBACK (entry_callback),
                    &my_config.offset);

  gtk_widget_show (table);

  gtk_widget_show (box);

  gtk_widget_show (inbox);
  gtk_widget_show (frame);

  box = gtk_vbox_new (FALSE, 6);
  gtk_box_pack_start (GTK_BOX (vbox), box, FALSE, FALSE, 0);

  my_widgets.autoset = button =
    gtk_check_button_new_with_mnemonic (_("A_utomatic"));
  gtk_box_pack_start (GTK_BOX (box), button, TRUE, FALSE, 0);
  gtk_widget_show (button);

  g_signal_connect (button, "toggled",
                    G_CALLBACK (my_toggle_callback),
                    &my_config.autoset);

  my_widgets.alpha_alg = button =
    gtk_check_button_new_with_mnemonic (_("A_lpha-weighting"));
  if (my_config.alpha_alg == -1)
    gtk_widget_set_sensitive (button, FALSE);
  gtk_box_pack_start (GTK_BOX (box), button, TRUE, FALSE, 0);
  gtk_widget_show (button);

  g_signal_connect (button, "toggled",
                    G_CALLBACK (my_toggle_callback),
                    &my_config.alpha_alg);

  gtk_widget_show (box);
  gtk_widget_show (vbox);

  inbox = gtk_vbox_new (FALSE, 12);
  gtk_box_pack_start (GTK_BOX (main_hbox), inbox, FALSE, FALSE, 0);

  frame = gimp_frame_new (_("Border"));
  gtk_box_pack_start (GTK_BOX (inbox), frame, FALSE, FALSE, 0);

  box = gtk_vbox_new (FALSE, 2);
  gtk_container_add (GTK_CONTAINER (frame), box);

  group = NULL;

  for (i = 0; i < 3; i++)
    {
      my_widgets.bmode[i] = button =
	gtk_radio_button_new_with_mnemonic (group, gettext (bmode_labels[i]));
      group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));
      gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
      gtk_widget_show (button);

      g_signal_connect (button, "toggled",
                        G_CALLBACK (my_bmode_callback),
                        GINT_TO_POINTER (i + 1));
    }

  gtk_widget_show (box);
  gtk_widget_show (frame);

  frame = gimp_frame_new (_("Channels"));
  gtk_box_pack_start (GTK_BOX (inbox), frame, FALSE, FALSE, 0);

  box = gtk_vbox_new (FALSE, 2);
  gtk_container_add (GTK_CONTAINER (frame), box);

  for (i = 0; i < 5; i++)
    {
      my_widgets.channels[i] = button =
	gtk_check_button_new_with_mnemonic (gettext (channel_labels[i]));

      if (my_config.channels[i] < 0)
	gtk_widget_set_sensitive (button, FALSE);

      gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
      gtk_widget_show (button);

      g_signal_connect (button, "toggled",
                        G_CALLBACK (my_toggle_callback),
                        &my_config.channels[i]);
    }

  gtk_widget_show (box);
  gtk_widget_show (frame);

  gtk_widget_show (inbox);

  gtk_widget_show (main_hbox);

  gtk_widget_show (dlg);
  redraw_all ();

  gtk_widget_set_sensitive (my_widgets.bmode[CLEAR],
			    (my_config.alpha_alg > 0));

  gtk_main ();

  return run_flag;
}
