/* maze_face.c
 * User interface for plug-in-maze.
 * 
 * Implemented as a GIMP 0.99 Plugin by 
 * Kevin Turner <kevint@poboxes.com>
 * http://www.poboxes.com/kevint/gimp/maze.html
 */

/*
 * 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 <stdio.h>
#include <stdlib.h>

#include "maze.h"
#include "libgimp/gimp.h"
#include "libgimp/gimpui.h"

#define BORDER_TOLERANCE 1.00 /* maximum ratio of (max % divs) to width */
#define ENTRY_WIDTH 75

/* entscale stuff begin */
#define ENTSCALE_INT_SCALE_WIDTH 125
#define ENTSCALE_INT_ENTRY_WIDTH 40

typedef void (*EntscaleIntCallbackFunc) (gint value, gpointer data);

typedef struct {
  GtkObject     *adjustment;
  GtkWidget     *entry;
  gint          constraint;
  EntscaleIntCallbackFunc	callback;
  gpointer	call_data;
} EntscaleIntData;
/* entscale stuff end */

gint maze_dialog (void);

static void maze_msg (gchar *msg);
static void maze_close_callback (GtkWidget *widget, gpointer data);
static void maze_ok_callback  (GtkWidget *widget, gpointer data);
static void maze_entry_callback  (GtkWidget *widget, gpointer data);
static void maze_help (GtkWidget *widget, gpointer foo);

/* Looking back, it would probably have been easier to completely
 * re-write the whole entry/scale thing to work with the divbox stuff.
 * It would undoubtably be cleaner code.  But since I already *had*
 * the entry/scale routines, I was under the (somewhat mistaken)
 * impression that it would be easier to work with them... */

/* Now, it goes like this:

   To update entscale (width) when div_entry changes:

    entscale_int_new has been slightly modified to return a pointer to
      its entry widget.

    This is fed to divbox_new as a "friend", which is in turn fed to
      the div_entry_callback routine.  And that's not really so bad,
      except...

    Oh, well, maybe it isn't so bad.  We can play with our friend's
      userdata to block his callbacks so we don't get feedback loops,
      that works nicely enough.

   To update div_entry when entscale (width) changes:

    The entry/scale setup graciously provides for callbacks.  However,
      this means we need to know about div_entry when we set up
      entry/scale, which we don't...  Chicken and egg problem.  So we
      set up a pointer to where div_entry will be, and pass this
      through to divbox_new when it happens.

    We need to block signal handlers for div_entry this time.  We
      happen to know that div_entry's callback data is our old
      "friend", so we pull our friend out from where we stuck him in
      the entry's userdata...  Hopefully that does it.  */

/* Questions:

     Gosh that was dumb.  Is there a way to
       signal_handler_block_by_name?
     That would make life so much nicer.

     Pointing to static variables "less" and "more" (for the buttons
     in divbox_new) is stupid.  Is there a way to store integer values
     in userdata or use intergers as parameters to callbacks?  The
     only alternative I could think of was seperate "button_less" and
     "button_more" callbacks, which did nothing but pass data on to
     what is now the div_button_callback function with an additional
     -1 or 1 parameter...  And that idea was at least as brain-damaged. 

*/

static void div_button_callback (GtkWidget *button, GtkWidget *entry);
static void div_entry_callback (GtkWidget *entry, GtkWidget *friend);
static void height_width_callback (gint width, GtkWidget **div_entry);
static void toggle_callback (GtkWidget *widget, gboolean *data);
static void alg_radio_callback (GtkWidget *widget, gpointer data);

static GtkWidget* divbox_new (guint *max,
			      GtkWidget *friend, 
			      GtkWidget **div_entry);

#if 0
static void div_buttonl_callback (GtkObject *object);
static void div_buttonr_callback (GtkObject *object);
#endif 

/* entscale stuff begin */
static GtkWidget*   entscale_int_new ( GtkWidget *table, gint x, gint y,
				       gchar *caption, gint *intvar, 
				       gint min, gint max, gboolean constraint,
				       EntscaleIntCallbackFunc callback,
				       gpointer data );

static void   entscale_int_destroy_callback (GtkWidget *widget,
					     gpointer data);
static void   entscale_int_scale_update (GtkAdjustment *adjustment,
					 gpointer      data);
static void   entscale_int_entry_update (GtkWidget *widget,
					 gpointer   data);
/* entscale stuff end */

extern MazeValues mvals;
extern guint sel_w, sel_h;

static gint maze_run=FALSE;
static GtkWidget *msg_label;

/* I only deal with setting up a few widgets at a time, so I could get
   by on a handful of generic GtkWidget variables.  But I've noticed
   that's not the way things are done around here...  I read it
   enhances optimization or some such thing.  Oh well.  Pointers are
   cheap, right? */
gint maze_dialog() 
{
  GtkWidget *dlg;
  GtkWidget *msg_frame;
  GtkWidget *frame;
  GtkWidget *table;
  gint trow;
  GtkWidget *button;
  GtkWidget *label;
  GtkWidget *entry;
  GtkWidget *notebook;
  GtkWidget *tilecheck;

  GtkWidget *help_button;

  GtkWidget *width_entry, *height_entry;
  GtkWidget *seed_hbox, *seed_entry, *time_button;
  GtkWidget *div_x_hbox, *div_y_hbox;
  GtkWidget *div_x_label, *div_y_label, *div_x_entry, *div_y_entry;

  GtkWidget *alg_box, *alg_button;

  gchar **argv;
  gint  argc;
  gchar buffer[32];

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

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

  dlg = gtk_dialog_new ();

  gtk_window_set_title (GTK_WINDOW (dlg), MAZE_TITLE);
  gtk_window_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE);
  gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
		      (GtkSignalFunc) maze_close_callback,
		      NULL);

  /*  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) maze_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);

  help_button = gtk_button_new_with_label ("Help");
  gtk_signal_connect (GTK_OBJECT (help_button), "clicked",
		      (GtkSignalFunc) maze_help, NULL);

  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), 
		      help_button, TRUE, TRUE, 0);
  gtk_widget_show (help_button);


  /* Create notebook */
  notebook = gtk_notebook_new ();
  gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), notebook, FALSE, FALSE, 0);
  gtk_widget_show (notebook);

  msg_frame = gtk_frame_new(MAZE_TITLE);
  gtk_frame_set_shadow_type (GTK_FRAME (msg_frame), GTK_SHADOW_ETCHED_IN);
  gtk_container_border_width (GTK_CONTAINER (msg_frame), 5);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), msg_frame, FALSE, FALSE, 0);

  sprintf(buffer,"Selection is %dx%d",sel_w, sel_h);
  msg_label = gtk_label_new (buffer);
  gtk_container_add (GTK_CONTAINER(msg_frame), msg_label);
  gtk_widget_show (msg_label);
  gtk_widget_show (msg_frame);

#if 0
  g_print("label_width: %d, %d\n",
	  GTK_FRAME(msg_frame)->label_width,
	  gdk_string_measure (GTK_WIDGET(msg_frame)->style->font, 
			      GTK_FRAME(msg_frame)->label) + 7);
#endif

  /*  Set up Options page  */
  frame = gtk_frame_new ("Maze Options");
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
  gtk_container_border_width (GTK_CONTAINER (frame), 5);
  table = gtk_table_new (5, 2, FALSE);
  gtk_container_border_width (GTK_CONTAINER (table), 5);
  gtk_container_add (GTK_CONTAINER (frame), table);

  trow = 0;

  /* Tileable checkbox */
  tilecheck = gtk_check_button_new_with_label ("Tileable?");
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tilecheck), mvals.tile);
  gtk_signal_connect (GTK_OBJECT (tilecheck), "clicked",
		      GTK_SIGNAL_FUNC (toggle_callback), &mvals.tile);
  gtk_table_attach (GTK_TABLE (table), tilecheck, 0, 2, trow, trow+1, 
		    GTK_FILL, 0, 5, 0 );
  gtk_widget_show (tilecheck);

  trow++;

  /* entscale == Entry and Scale pair function found in pixelize.c */
  width_entry = entscale_int_new (table, 0, trow, "Width (pixels):", 
				  &mvals.width, 
				  1, sel_w/4, TRUE, 
				  (EntscaleIntCallbackFunc) height_width_callback,
				  &div_x_entry);


  /* Number of Divisions entry */
  trow++;

  div_x_label = gtk_label_new("Pieces:");

  gtk_table_attach (GTK_TABLE (table), div_x_label, 0,1, trow, trow+1, 
		    0, 0, 5, 5);

  gtk_widget_show(div_x_label);

  div_x_hbox = divbox_new(&sel_w, 
			  width_entry,
			  &div_x_entry);

  sprintf(buffer, "%d", (sel_w / mvals.width) );
  gtk_entry_set_text(GTK_ENTRY(div_x_entry), buffer);

  gtk_table_attach (GTK_TABLE (table), div_x_hbox, 1, 2, trow, trow+1, 
		    0, 0, 5, 5);

  gtk_widget_show (div_x_hbox);

  trow++;


  height_entry = entscale_int_new (table, 0, trow, "Height (pixels):", 
				   &mvals.height, 
				   1, sel_h/4, TRUE, 
				   (EntscaleIntCallbackFunc) height_width_callback,
				   &div_y_entry);

  trow++;

  div_y_label = gtk_label_new("Pieces:");

  gtk_table_attach (GTK_TABLE (table), div_y_label, 0, 1, trow, trow+1, 
		    0, 0, 5, 5);

  gtk_widget_show(div_y_label);

  div_y_hbox = divbox_new(&sel_h,
			  height_entry,
			  &div_y_entry);

  sprintf(buffer, "%d", (sel_h / mvals.height) );
  gtk_entry_set_text(GTK_ENTRY(div_y_entry), buffer);

  gtk_table_attach (GTK_TABLE (table), div_y_hbox, 1, 2, trow, trow+1, 
		    0, 0, 5, 5);

  gtk_widget_show (div_y_hbox);

  /* Add Options page to notebook */
  gtk_widget_show (frame);
  gtk_widget_show (table);

  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, 
			    gtk_label_new ("Options"));

  /* Set up other page */
  frame = gtk_frame_new ("At Your Own Risk");
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
  gtk_container_border_width (GTK_CONTAINER (frame), 10);
  table = gtk_table_new (4, 2, FALSE);
  gtk_container_border_width (GTK_CONTAINER (table), 10);
  gtk_container_add (GTK_CONTAINER (frame), table);

  /* Multiple input box */
  label = gtk_label_new ("Multiple (57)");
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_FILL, 0, 5, 0 );
  gtk_widget_show (label);
  entry = gtk_entry_new ();
  gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0 );
  gtk_widget_set_usize( entry, ENTRY_WIDTH, 0 );
  sprintf( buffer, "%d", mvals.multiple );
  gtk_entry_set_text (GTK_ENTRY (entry), buffer );
  gtk_signal_connect (GTK_OBJECT (entry), "changed",
		      (GtkSignalFunc) maze_entry_callback,
		      &mvals.multiple);
  gtk_widget_show (entry);

  /* Offset input box */
  label = gtk_label_new ("Offset (1)");
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, GTK_FILL, 0, 5, 0 );
  gtk_widget_show (label);
  entry = gtk_entry_new ();
  gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0 );
  gtk_widget_set_usize( entry, ENTRY_WIDTH, 0 );
  sprintf( buffer, "%d", mvals.offset );
  gtk_entry_set_text (GTK_ENTRY (entry), buffer );
  gtk_signal_connect (GTK_OBJECT (entry), "changed",
		      (GtkSignalFunc) maze_entry_callback,
		      &mvals.offset);
  gtk_widget_show (entry);

  /* Seed input box */
  label = gtk_label_new ("Seed");
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, 
		    GTK_FILL, 0, 5, 5);
  gtk_widget_show (label);

  seed_hbox = gtk_hbox_new(FALSE, 2);
  gtk_table_attach (GTK_TABLE (table), seed_hbox, 1, 2, 2, 3, 
		    GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 5);

  seed_entry = gtk_entry_new ();
  gtk_widget_set_usize( seed_entry, ENTRY_WIDTH, 0 );
  sprintf( buffer, "%d", mvals.seed );
  gtk_entry_set_text (GTK_ENTRY (seed_entry), buffer );
  gtk_signal_connect (GTK_OBJECT (seed_entry), "changed",
		      (GtkSignalFunc) maze_entry_callback,
		      &mvals.seed);
  gtk_box_pack_start(GTK_BOX(seed_hbox), seed_entry, TRUE, TRUE, 0);
  gtk_widget_show (seed_entry);

  time_button = gtk_toggle_button_new_with_label ("Time");
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(time_button),mvals.timeseed);
  gtk_signal_connect (GTK_OBJECT (time_button), "clicked",
		      (GtkSignalFunc) toggle_callback,
		      &mvals.timeseed);
  gtk_box_pack_end (GTK_BOX (seed_hbox), time_button, FALSE, FALSE, 0);
  gtk_widget_show (time_button);
  gtk_widget_show (seed_hbox);

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

  alg_box=gtk_vbox_new(FALSE, 5);
  gtk_table_attach (GTK_TABLE (table), alg_box, 1, 2, 3, 4, 
		    GTK_FILL, 0, 5, 5);
  gtk_widget_show (alg_box);

  alg_button=gtk_radio_button_new_with_label (NULL,"Depth First");
  gtk_signal_connect(GTK_OBJECT(alg_button),"toggled",
		     GTK_SIGNAL_FUNC(alg_radio_callback), (gpointer)DEPTH_FIRST);
  if(mvals.algorithm==DEPTH_FIRST)
       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(alg_button), TRUE);
  gtk_container_add(GTK_CONTAINER(alg_box),alg_button);
  gtk_widget_show(alg_button);

  alg_button=gtk_radio_button_new_with_label (gtk_radio_button_group(
       GTK_RADIO_BUTTON(alg_button)), "Prim's Algorithm");
  gtk_signal_connect(GTK_OBJECT(alg_button),"toggled",
		     GTK_SIGNAL_FUNC(alg_radio_callback), (gpointer)PRIMS_ALGORITHM);
  if(mvals.algorithm==PRIMS_ALGORITHM)
       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(alg_button), TRUE);

  gtk_container_add(GTK_CONTAINER(alg_box),alg_button);
  gtk_widget_show(alg_button);

   /* Add Advanced page to notebook */
  gtk_widget_show (frame);
  gtk_widget_show (table);
  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, 
			    gtk_label_new ("Advanced"));

  gtk_widget_show (dlg);

  gtk_main ();
  gdk_flush ();

  return maze_run;
}

static GtkWidget*
divbox_new (guint *max, GtkWidget *friend, GtkWidget **div_entry)
{
     GtkWidget *div_hbox;
     GtkWidget *arrowl, *arrowr, *buttonl, *buttonr;
     static gshort less= -1, more= 1;
#if DIVBOX_LOOKS_LIKE_SPINBUTTON
     GtkWidget *buttonbox;
#endif     


     div_hbox=gtk_hbox_new(FALSE, 0);

#if DIVBOX_LOOKS_LIKE_SPINBUTTON     
     arrowl=gtk_arrow_new(GTK_ARROW_DOWN,  GTK_SHADOW_OUT);
     arrowr=gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_OUT);
#else
     arrowl=gtk_arrow_new(GTK_ARROW_LEFT,  GTK_SHADOW_IN);
     arrowr=gtk_arrow_new(GTK_ARROW_RIGHT, GTK_SHADOW_IN);
#endif

     buttonl=gtk_button_new();
     buttonr=gtk_button_new();
     
     gtk_object_set_data(GTK_OBJECT(buttonl), "direction", &less);
     gtk_object_set_data(GTK_OBJECT(buttonr), "direction", &more);

     *div_entry= gtk_entry_new();

     gtk_object_set_data(GTK_OBJECT(*div_entry), "max", max);
     gtk_object_set_data(GTK_OBJECT(*div_entry), "friend", friend);

     gtk_container_add(GTK_CONTAINER(buttonl),arrowl);
     gtk_container_add(GTK_CONTAINER(buttonr),arrowr);

     gtk_widget_set_usize( *div_entry, ENTRY_WIDTH, 0 );

#if DIVBOX_LOOKS_LIKE_SPINBUTTON
     buttonbox = gtk_vbox_new(FALSE, 0);

     gtk_box_pack_start(GTK_BOX(buttonbox), buttonr, FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(buttonbox), buttonl, FALSE, FALSE, 0);
     gtk_widget_show(buttonbox);

     gtk_box_pack_start(GTK_BOX(div_hbox), *div_entry, FALSE, FALSE, 2);
     gtk_box_pack_start(GTK_BOX(div_hbox), buttonbox, FALSE, FALSE, 0);
#else
     gtk_misc_set_padding(GTK_MISC(arrowl),2,2);
     gtk_misc_set_padding(GTK_MISC(arrowr),2,2);

     gtk_box_pack_start(GTK_BOX(div_hbox), buttonl, FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(div_hbox), *div_entry,   FALSE, FALSE, 2);
     gtk_box_pack_start(GTK_BOX(div_hbox), buttonr, FALSE, FALSE, 0);
#endif     

     gtk_widget_show (arrowl); gtk_widget_show (arrowr);
     gtk_widget_show (*div_entry);
     gtk_widget_show (buttonl); gtk_widget_show (buttonr);

     gtk_signal_connect(GTK_OBJECT(buttonl), "clicked",
			(GtkSignalFunc) div_button_callback, 
			*div_entry);

     gtk_signal_connect(GTK_OBJECT(buttonr), "clicked",
			(GtkSignalFunc) div_button_callback, 
			*div_entry);     

     gtk_signal_connect(GTK_OBJECT(*div_entry), "changed",
			(GtkSignalFunc) div_entry_callback,
			friend);

     return div_hbox;
}

static void
div_button_callback (GtkWidget *button, GtkWidget *entry)
{
     guint max, divs, even;
     gchar *text, *text2;
     gshort direction;

     direction = *((gshort*) gtk_object_get_data(GTK_OBJECT(button), "direction"));
     max = *((guint*) gtk_object_get_data(GTK_OBJECT(entry), "max"));

     /* Tileable mazes shall have only an even number of divisions.
        Other mazes have odd. */
     /* Logic games!

     If BIT1 is and "even" is then add:
	 FALSE        TRUE        0
         TRUE         TRUE        1
	 FALSE        FALSE       1
	 TRUE         FALSE       0

	 That's where the +((foo & 1) == even) stuff comes from. */

     /* Sanity check: */
     if (mvals.tile && (max & 1)) {
	  maze_msg("Selection size is not even.  \nTileable maze won't work perfectly.");
	  return;
     }

     even = mvals.tile ? 1 : 0;

     text = gtk_entry_get_text (GTK_ENTRY (entry));

     divs=atoi(text);
     if (divs <= 3) {
	  divs= max - ((max & 1) == even);	  
     } else if (divs > max) {
	  divs= 5 + even;
     }
     
     /* Makes sure we're appropriately even or odd, adjusting in the
        proper direction. */
     divs += direction * ((divs & 1) == even);
	  
     if (mvals.tile) {	  
	  if (direction > 0) {
	       do {
		    divs += 2;
		    if (divs > max)
			 divs = 4;
	       } while (max % divs);
	  } else { /* direction < 0 */
	       do {
		    divs -= 2;
		    if (divs < 4)
			 divs = max - (max & 1);
	       } while (max % divs);
	  } /* endif direction < 0 */
     } else { /* If not tiling, having a non-zero remainder doesn't bother us much. */
	  if (direction > 0) {
	       do {
		    divs += 2;
	       } while ((max % divs > max / divs * BORDER_TOLERANCE ) && divs < max);
	  } else { /* direction < 0 */
	       do {
		    divs -= 2;
	       } while ((max % divs > max / divs * BORDER_TOLERANCE) && divs > 5);
	  } /* endif direction < 0 */
     } /* endif not tiling */

     if (divs <= 3) {
	  divs= max - ((max & 1) == even);	  
     } else if (divs > max) {
	  divs= 5 - even;
     } /* endif divs > max */

     text2 = g_new(gchar, 16);
     sprintf (text2,"%d",divs);

     gtk_entry_set_text (GTK_ENTRY(entry),text2);

     return;
}

static void
div_entry_callback (GtkWidget *entry, GtkWidget *friend)
{
     guint divs, width, max;
     gchar *buffer;
     EntscaleIntData *userdata;
     EntscaleIntCallbackFunc friend_callback;

     divs = atoi(gtk_entry_get_text (GTK_ENTRY (entry)));
     if (divs < 4) /* If this is under 4 (e.g. 0), something's weird. */
	  return;  /* But it'll probably be ok, so just return and ignore. */

     max = *((guint*) gtk_object_get_data(GTK_OBJECT(entry), "max"));     
     buffer = g_new(gchar, 16);

     /* I say "width" here, but it could be height.*/

     width = max/divs;
     sprintf (buffer,"%d", width );

     /* No tagbacks from our friend... */
     userdata = gtk_object_get_user_data (GTK_OBJECT (friend));
     friend_callback = userdata->callback;
     userdata->callback = NULL;

     gtk_entry_set_text(GTK_ENTRY(friend), buffer);

     userdata->callback = friend_callback;
}

static void
height_width_callback (gint width, GtkWidget **div_entry)
{
     guint divs, max;
     gpointer data;
     gchar *buffer;

     max = *((guint*) gtk_object_get_data(GTK_OBJECT(*div_entry), "max"));
     divs = max / width;

     buffer = g_new(gchar, 16);
     sprintf (buffer,"%d", divs );

     data = gtk_object_get_data(GTK_OBJECT(*div_entry), "friend");
     gtk_signal_handler_block_by_data ( GTK_OBJECT(*div_entry), data );
     
     gtk_entry_set_text(GTK_ENTRY(*div_entry), buffer);

     gtk_signal_handler_unblock_by_data ( GTK_OBJECT(*div_entry), data );
     
}


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

static void
maze_help (GtkWidget *widget, gpointer foo)
{
     char *proc_blurb, *proc_help, *proc_author, *proc_copyright, *proc_date;
     int proc_type, nparams, nreturn_vals;
     GParamDef *params, *return_vals;
     gint baz;

     if (gimp_query_procedure("extension_web_browser",
                              &proc_blurb, &proc_help, 
			      &proc_author, &proc_copyright, &proc_date,
			      &proc_type, &nparams, &nreturn_vals,
			      &params, &return_vals)) {
          maze_msg("Opening " MAZE_URL);
          gimp_run_procedure("extension_web_browser", &baz,
                             PARAM_INT32, RUN_NONINTERACTIVE,
                             PARAM_STRING, MAZE_URL,
                             PARAM_INT32, HELP_OPENS_NEW_WINDOW,
                             PARAM_END);
     } else {
          maze_msg("See " MAZE_URL);
     }                                            
}

static void
maze_msg (gchar *msg)
{
     gtk_label_set_text(GTK_LABEL(msg_label), msg);
}

static void
maze_ok_callback (GtkWidget *widget,
		  gpointer data)
{
    maze_run = TRUE;
    gtk_widget_destroy (GTK_WIDGET (data));
}

static void 
maze_entry_callback (GtkWidget *widget,
		       gpointer data)
{
    gint *text_val;

    text_val = (gint *) data;

    *text_val = atoi (gtk_entry_get_text (GTK_ENTRY (widget)));
}

static void 
toggle_callback (GtkWidget *widget, gboolean *data)
{
    *data = GTK_TOGGLE_BUTTON (widget)->active;
}

static void
alg_radio_callback (GtkWidget *widget, gpointer data)
{
     mvals.algorithm=(MazeAlgoType)data;
}

/* ==================================================================== */
/* As found in pixelize.c, 
 * hacked to return a pointer to the entry widget. */

/*
  Entry and Scale pair 1.03

  TODO:
  - Do the proper thing when the user changes value in entry,
  so that callback should not be called when value is actually not changed.
  - Update delay
 */

/*
 *  entscale: create new entscale with label. (int)
 *  1 row and 2 cols of table are needed.
 *  Input:
 *    x, y:       starting row and col in table
 *    caption:    label string
 *    intvar:     pointer to variable
 *    min, max:   the boundary of scale
 *    constraint: (bool) true iff the value of *intvar should be constraint
 *                by min and max
 *    callback:	  called when the value is actually changed
 *    call_data:  data for callback func
 */
static GtkWidget*
entscale_int_new ( GtkWidget *table, gint x, gint y,
		   gchar *caption, gint *intvar,
		   gint min, gint max, gboolean constraint,
		   EntscaleIntCallbackFunc callback,
		   gpointer call_data)
{
  EntscaleIntData *userdata;
  GtkWidget *hbox;
  GtkWidget *label;
  GtkWidget *entry;
  GtkWidget *scale;
  GtkObject *adjustment;
  gchar    buffer[256];
  gint	    constraint_val;

  userdata = g_new ( EntscaleIntData, 1 );

  label = gtk_label_new (caption);
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);

  /*
    If the first arg of gtk_adjustment_new() isn't between min and
    max, it is automatically corrected by gtk later with
    "value_changed" signal. I don't like this, since I want to leave
    *intvar untouched when `constraint' is false.
    The lines below might look oppositely, but this is OK.
   */
  userdata->constraint = constraint;
  if( constraint )
    constraint_val = *intvar;
  else
    constraint_val = ( *intvar < min ? min : *intvar > max ? max : *intvar );

  userdata->adjustment = adjustment = 
    gtk_adjustment_new ( constraint_val, min, max, 1.0, 1.0, 0.0);
  scale = gtk_hscale_new ( GTK_ADJUSTMENT(adjustment) );
  gtk_widget_set_usize (scale, ENTSCALE_INT_SCALE_WIDTH, 0);
  gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE);

  userdata->entry = entry = gtk_entry_new ();
  gtk_widget_set_usize (entry, ENTSCALE_INT_ENTRY_WIDTH, 0);
  sprintf( buffer, "%d", *intvar );
  gtk_entry_set_text( GTK_ENTRY (entry), buffer );

  userdata->callback = callback;
  userdata->call_data = call_data;

  /* userdata is done */
  gtk_object_set_user_data (GTK_OBJECT(adjustment), userdata);
  gtk_object_set_user_data (GTK_OBJECT(entry), userdata);

  /* now ready for signals */
  gtk_signal_connect (GTK_OBJECT (entry), "changed",
		      (GtkSignalFunc) entscale_int_entry_update,
		      intvar);
  gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
		      (GtkSignalFunc) entscale_int_scale_update,
		      intvar);
  gtk_signal_connect (GTK_OBJECT (entry), "destroy",
		      (GtkSignalFunc) entscale_int_destroy_callback,
		      userdata );

  /* start packing */
  hbox = gtk_hbox_new (FALSE, 5);
  gtk_box_pack_start (GTK_BOX (hbox), scale, TRUE, TRUE, 0);
  gtk_box_pack_start (GTK_BOX (hbox), entry, FALSE, TRUE, 0);

  gtk_table_attach (GTK_TABLE (table), label, x, x+1, y, y+1,
		    GTK_FILL, GTK_FILL, 0, 0);
  gtk_table_attach (GTK_TABLE (table), hbox, x+1, x+2, y, y+1,
		    GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);

  gtk_widget_show (label);
  gtk_widget_show (entry);
  gtk_widget_show (scale);
  gtk_widget_show (hbox);

  return entry;
}  


/* when destroyed, userdata is destroyed too */
static void
entscale_int_destroy_callback (GtkWidget *widget,
			       gpointer data)
{
  EntscaleIntData *userdata;

  userdata = data;
  g_free ( userdata );
}

static void
entscale_int_scale_update (GtkAdjustment *adjustment,
			   gpointer      data)
{
  EntscaleIntData *userdata;
  GtkEntry	*entry;
  gchar		buffer[256];
  gint		*intvar = data;
  gint		new_val;

  userdata = gtk_object_get_user_data (GTK_OBJECT (adjustment));

  new_val = (gint) adjustment->value;

  *intvar = new_val;

  entry = GTK_ENTRY( userdata->entry );
  sprintf (buffer, "%d", (int) new_val );
  
  /* avoid infinite loop (scale, entry, scale, entry ...) */
  gtk_signal_handler_block_by_data ( GTK_OBJECT(entry), data );
  gtk_entry_set_text ( entry, buffer);
  gtk_signal_handler_unblock_by_data ( GTK_OBJECT(entry), data );

  if (userdata->callback)
    (*userdata->callback) (*intvar, userdata->call_data);
}

static void
entscale_int_entry_update (GtkWidget *widget,
			   gpointer   data)
{
  EntscaleIntData *userdata;
  GtkAdjustment	*adjustment;
  int		new_val, constraint_val;
  int		*intvar = data;

  userdata = gtk_object_get_user_data (GTK_OBJECT (widget));
  adjustment = GTK_ADJUSTMENT( userdata->adjustment );

  new_val = atoi (gtk_entry_get_text (GTK_ENTRY (widget)));
  constraint_val = new_val;
  if ( constraint_val < adjustment->lower )
    constraint_val = adjustment->lower;
  if ( constraint_val > adjustment->upper )
    constraint_val = adjustment->upper;

  if ( userdata->constraint )
    *intvar = constraint_val;
  else
    *intvar = new_val;

  adjustment->value = constraint_val;
  gtk_signal_handler_block_by_data ( GTK_OBJECT(adjustment), data );
  gtk_signal_emit_by_name ( GTK_OBJECT(adjustment), "value_changed");
  gtk_signal_handler_unblock_by_data ( GTK_OBJECT(adjustment), data );
  
  if (userdata->callback)
    (*userdata->callback) (*intvar, userdata->call_data);
}
