/*	SCCS Id: @(#)gnmenu.c	3.4	2000/07/16	*/
/* Copyright (C) 1998 by Erik Andersen <andersee@debian.org> */
/* NetHack may be freely redistributed.  See license for details. */

#include <string.h>
#include <gtk/gtk.h>
#include <gnome.h>
#include "gnmenu.h"
#include "gnmain.h"
#include "gnbind.h"
#include "func_tab.h"

typedef enum {
	MenuUnknown = 0,
	MenuText,
	MenuMenu
} MenuWinType;

typedef struct {
	ANY_P identifier;
	gchar accelerator[BUFSZ];
	int itemNumber;
	int selected;
} menuItem;

typedef struct {
	int curItem;
	int numRows;
	int charIdx;
	guint32 lastTime;
} extMenu;

static GdkColor color_blue = { 0, 0, 0, 0xffff };


static void
ghack_menu_window_key(GtkWidget *menuWin, GdkEventKey *event, gpointer data)
{
    int i, numRows;
    menuItem* item;
    MenuWinType isMenu;

    isMenu = (MenuWinType) GPOINTER_TO_INT
	(gtk_object_get_data (GTK_OBJECT (menuWin), "isMenu"));

    if (isMenu == MenuMenu) {
	GtkWidget *clist;
	gint selection_mode;

	clist = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (menuWin), "clist"));
	g_assert (clist != NULL);
	numRows = GPOINTER_TO_INT
	    (gtk_object_get_data(GTK_OBJECT(clist), "numRows"));
	selection_mode = GPOINTER_TO_INT
	    (gtk_object_get_data (GTK_OBJECT(clist), "selection_mode"));
	for (i = 0; i <= numRows; ++i) {
	    item = (menuItem*) gtk_clist_get_row_data(GTK_CLIST(clist), i);
	    if (item == NULL) continue;
	    if (!strcmp(item->accelerator, "")) continue;

	    if ((!strcmp(item->accelerator, event->string)) ||
		((selection_mode == GTK_SELECTION_MULTIPLE) &&
		 (event->keyval == ','))) {
		if (item->selected) {
		    gtk_clist_unselect_row( GTK_CLIST (clist),
					    item->itemNumber, 0);
		    item->selected = FALSE;
		} else {
		    gtk_clist_select_row(GTK_CLIST (clist),
					 item->itemNumber, 0);
		    if (gtk_clist_row_is_visible(GTK_CLIST(clist),
				item->itemNumber) != GTK_VISIBILITY_FULL)
			gtk_clist_moveto(GTK_CLIST(clist),
					 item->itemNumber, 0, 0.5, 0);
		    item->selected = TRUE;
		}
	    }
	}
    }
}


static void
ghack_menu_row_selected (GtkCList *clist, int row, int col, GdkEvent *event)
{
    /* FIXME: Do something */
}


void
ghack_menu_window_clear(GtkWidget *menuWin, gpointer data)
{
    MenuWinType isMenu;
    int i, numRows;
    menuItem* item;

    isMenu = (MenuWinType) GPOINTER_TO_INT
    	(gtk_object_get_data (GTK_OBJECT (menuWin), "isMenu"));

    if (isMenu == MenuMenu) {
      GtkWidget *clist;

      clist = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (menuWin), "clist"));
      g_assert (clist != NULL);

      /* destroy existing menu data, if any */
      if (clist) {
	/* destroy all the row_data we stored in the clist */
	numRows = GPOINTER_TO_INT( gtk_object_get_data(
		    GTK_OBJECT(clist), "numRows") );
	for( i=0; i<numRows; i++) {
	    item = (menuItem*) gtk_clist_get_row_data( 
		    GTK_CLIST (clist), i);
	    if (item != NULL) {
		g_free( item);
		gtk_clist_set_row_data (GTK_CLIST (clist), i, 
			(gpointer) NULL);
	    }
	}
	gtk_object_set_data (GTK_OBJECT (clist), "numItems",
				GINT_TO_POINTER (-1));
	gtk_clist_clear (GTK_CLIST (clist));
      }
    }
    
    else if (isMenu == MenuText) {
      GnomeLess *gless;

      gless = GNOME_LESS (gtk_object_get_data (GTK_OBJECT (menuWin), "gless"));
      g_assert (gless != NULL);

      gtk_editable_delete_text (GTK_EDITABLE (gless->text), 0, 0);
    }

}

void
ghack_menu_window_display(GtkWidget *menuWin, gboolean blocking,
                          gpointer data)
{
    //if(blocking) {
        gnome_dialog_close_hides (GNOME_DIALOG (menuWin), TRUE);
	gnome_dialog_set_close (GNOME_DIALOG (menuWin), TRUE);
	gnome_dialog_run_and_close(GNOME_DIALOG (menuWin));
    //}
    //else {
        //gtk_widget_show(menuWin);
    //}
}

gint
ghack_menu_hide( GtkWidget *menuWin, GdkEvent *event, gpointer data )
{
    gtk_widget_hide (menuWin);
    return FALSE; /* FIXME: what is correct result here? */
}


void 
ghack_menu_window_start_menu (GtkWidget *menuWin, gpointer data)
{
    GtkWidget *frame1, *swin, *clist;
    MenuWinType isMenu;
    
    g_assert (menuWin != NULL);
    g_assert (data == NULL);

    /* destroy existing menu data, if any */
    frame1 = gtk_object_get_data (GTK_OBJECT (menuWin), "frame1");
    if (frame1)
      gtk_widget_destroy (frame1);
 
    isMenu = MenuMenu;
    gtk_object_set_data (GTK_OBJECT (menuWin), "isMenu",
    			 GINT_TO_POINTER (isMenu));

    gtk_widget_set_usize (GTK_WIDGET (menuWin), 500, 400);
    gtk_window_set_policy (GTK_WINDOW (menuWin), TRUE, TRUE, FALSE);

    frame1 = gtk_frame_new ("Make your selection");
    g_assert (frame1 != NULL);
    gtk_object_set_data (GTK_OBJECT(menuWin), "frame1", frame1);
    gtk_widget_show (GTK_WIDGET (frame1));
    gtk_container_set_border_width (GTK_CONTAINER (frame1), 5);
    gtk_box_pack_start (GTK_BOX (GNOME_DIALOG(menuWin)->vbox), frame1,
                        TRUE, TRUE, 0);
    
    swin = gtk_scrolled_window_new (NULL, NULL);
    g_assert (swin != NULL);
    gtk_object_set_data (GTK_OBJECT(menuWin), "swin", swin);
    gtk_widget_show (GTK_WIDGET (swin));
    gtk_container_add (GTK_CONTAINER (frame1), swin);

    clist = gtk_clist_new (4);
    g_assert (clist != NULL);
    gtk_object_set_data (GTK_OBJECT(menuWin), "clist", clist);
    gtk_widget_show (GTK_WIDGET (clist));
    gtk_container_add (GTK_CONTAINER (swin), clist);

    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin),
	    GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

    gtk_signal_connect (GTK_OBJECT (clist), "select_row",
	    GTK_SIGNAL_FUNC (ghack_menu_row_selected), NULL);
    gtk_object_set_data (GTK_OBJECT (clist), "numItems",
			    GINT_TO_POINTER (-1));
}    

    
int 
ghack_menu_window_select_menu (GtkWidget *menuWin, 
	MENU_ITEM_P **_selected, gint how)
{
   gint rc;
   guint num_sel, i, idx;
   GtkWidget *clist;
   GList *cur;
   MENU_ITEM_P *selected = NULL;
   menuItem*	item;

   g_assert (_selected != NULL);
   *_selected = NULL;


   if (how == PICK_NONE) {
     gnome_dialog_close_hides (GNOME_DIALOG (menuWin), TRUE);
     rc = gnome_dialog_run_and_close (GNOME_DIALOG (menuWin));
     return( rc == 1 ? -1 : 0);
   }

   clist = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (menuWin), "clist"));
   g_assert (clist != NULL);

   gtk_object_set_data (GTK_OBJECT (clist), "selection_mode",
                   GINT_TO_POINTER ((how == PICK_ANY)? 
		       GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE));
   gtk_clist_set_selection_mode (GTK_CLIST (clist), 
	   (how == PICK_ANY)? GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE);
   gnome_dialog_close_hides (GNOME_DIALOG (menuWin), TRUE);
   rc = gnome_dialog_run_and_close (GNOME_DIALOG (menuWin));
   if ((rc == 1) || (GTK_CLIST (clist)->selection == NULL)) {
     return(-1);
   }

   num_sel = g_list_length (GTK_CLIST (clist)->selection);
   if (num_sel < 1) {
     return(-1);
   }

   /* fill in array with selections from clist */
   selected =  g_new0( MENU_ITEM_P, num_sel);
   g_assert (selected != NULL);
   cur = GTK_CLIST (clist)->selection;
   i = 0;
   while (cur) {
     g_assert (i < num_sel);

     /* grab row number from clist selection list */
     idx = GPOINTER_TO_INT (cur->data);

     item = (menuItem*) gtk_clist_get_row_data( GTK_CLIST (clist), idx);
     selected[i].item = item->identifier;
     selected[i].count = -1;
     cur = g_list_next(cur);
     i++;
   }

   *_selected = selected;

   return( (int) num_sel);
}    

void 
ghack_menu_window_add_menu( GtkWidget *menuWin, gpointer menu_item,
			    gpointer data)
{
    GHackMenuItem*  item;
    GtkWidget	    *clist;
    gchar buf[BUFSZ]="", accelBuf[BUFSZ]="";
    gchar *pbuf;
    char *text[4] = { buf, NULL, NULL, NULL };
    gint nCurrentRow = -1, numItems = -1;
    MenuWinType isMenu;
    GtkStyle *bigStyle = NULL;
    gboolean item_selectable;
    GdkImlibImage* image;
    static gboolean special;

    g_assert (menu_item != NULL);
    item = (GHackMenuItem*) menu_item;
    item_selectable = ( item->identifier->a_int == 0)? FALSE : TRUE;
    isMenu = (MenuWinType) GPOINTER_TO_INT
    	(gtk_object_get_data (GTK_OBJECT (menuWin), "isMenu"));

    clist = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (menuWin), "clist"));
    g_assert (clist != NULL);
    /* This is a special kludge to make the special hidden help menu item work as designed */ 
    if ( special==TRUE ) {
	special=FALSE;
	item_selectable=TRUE;
    }
    if ( ! strcmp( item->str, "The NetHack license.")) {
	special=TRUE;
    }
    
    if (item->str) {

	/* First, make a new blank entry in the clist */
	nCurrentRow = gtk_clist_append (GTK_CLIST (clist), text);

	if (item->glyph != NO_GLYPH) {
	    image = ghack_image_from_glyph( item->glyph, FALSE);
	    if (image==NULL || image->pixmap==NULL) {
		g_warning("Bummer -- having to force rendering for glyph %d!", item->glyph);
		/* wierd -- pixmap is NULL so retry rendering it */
		image = ghack_image_from_glyph( item->glyph, TRUE);
	    }
	    if (image==NULL || image->pixmap==NULL) {
		    g_error("Aiiee! glyph is still NULL for item\n\"%s\"", 
			    item->str);
	    }
	    else 
		gtk_clist_set_pixmap (GTK_CLIST (clist), 
			nCurrentRow, 1, 
			gdk_imlib_move_image( image), 
			gdk_imlib_move_mask( image));
	}
	if (item->accelerator) {
	    /* FIXME: handle accelerator, */
	    g_snprintf(accelBuf, sizeof(accelBuf), "%c ", item->accelerator);
	    gtk_clist_set_text (GTK_CLIST (clist), nCurrentRow, 0, accelBuf);
	    g_snprintf(buf, sizeof(buf), "%s", item->str);
	    gtk_clist_set_text (GTK_CLIST (clist), nCurrentRow, 2, buf);
	} else {
	    if (item->group_accel) {
		/* FIXME: maybe some day I should try to handle 
		 * group accelerators... */
	    }
	    if (( (item->attr == 0) && (item->identifier->a_int != 0)) || (special ==TRUE) ) {
		numItems = GPOINTER_TO_INT( gtk_object_get_data(
			    GTK_OBJECT(clist), "numItems") )+1;

		/* Ok, now invent a unique accelerator */
		if (  ('a'+numItems) <= 'z' ) {
		    g_snprintf(accelBuf, sizeof(accelBuf), "%c ", 'a'+numItems);
		    gtk_clist_set_text(GTK_CLIST(clist), nCurrentRow, 0, accelBuf);
		}
		else if ( ('A'+numItems-26)<='Z') {
		    g_snprintf(accelBuf, sizeof(accelBuf), "%c ", 'A'+numItems-26); 
		    gtk_clist_set_text(GTK_CLIST(clist), nCurrentRow, 0, accelBuf);
		} else {
		    accelBuf[0] = buf[0] = 0;
		}
		g_snprintf(buf, sizeof(buf), "%s", item->str);
		gtk_clist_set_text (GTK_CLIST (clist), nCurrentRow, 2, buf);
		gtk_object_set_data (GTK_OBJECT (clist), "numItems",
					GINT_TO_POINTER (numItems));

		/* This junk is to specially handle the options menu */
		pbuf = strstr( buf, " [");
		if (pbuf == NULL) {
		    pbuf = strstr( buf, "\t[");
		}
		if (pbuf != NULL) {
		    *pbuf=0;
		    pbuf++;
		    gtk_clist_set_text (GTK_CLIST (clist), nCurrentRow, 3, pbuf);
		}
	    }
	    /* FIXME: handle more than 26*2 accelerators (but how?
	     * since I only have so many keys to work with???)
	    else
	    {
		foo();
	    }
	    */
	    else {
		g_snprintf(buf, sizeof(buf), "%s", item->str);
		pbuf = strstr( buf, " [");
		if (pbuf == NULL) {
		    pbuf = strstr( buf, "\t[");
		}
		if (pbuf != NULL) {
		    *pbuf=0;
		    pbuf++;
		    gtk_clist_set_text (GTK_CLIST (clist), nCurrentRow, 3, pbuf);
		}
		gtk_clist_set_text (GTK_CLIST (clist), nCurrentRow, 2, buf);
		
	    }
	}

	if (item->attr) {
	    switch(item->attr) {
		case ATR_ULINE:
		case ATR_BOLD:
		case ATR_BLINK:
		case ATR_INVERSE:
		    bigStyle = gtk_style_copy (GTK_WIDGET (clist)->style);
		    g_assert (bigStyle != NULL);
		    gdk_font_unref (bigStyle->font);
		    bigStyle->font = gdk_font_load (
				"-misc-fixed-*-*-*-*-20-*-*-*-*-*-*-*");
		    bigStyle->fg[GTK_STATE_NORMAL] = color_blue;
		    gtk_clist_set_cell_style (GTK_CLIST (clist), 
			    nCurrentRow, 2, bigStyle);
		    item_selectable = FALSE;
	    }
	}


	g_assert (nCurrentRow >= 0);
	gtk_clist_set_selectable (GTK_CLIST (clist), nCurrentRow,
			      item_selectable);

	if ( item_selectable==TRUE && item->presel== TRUE) {
	    /* pre-select this item */
	    gtk_clist_select_row( GTK_CLIST (clist), nCurrentRow, 0);
	}
	
	gtk_object_set_data (GTK_OBJECT (clist), "numRows",
		                GINT_TO_POINTER (nCurrentRow));
        
	/* We have to allocate memory here, since the menu_item currently
	 * lives on the stack, and will otherwise go to the great bit bucket 
	 * in the sky as soon as this function exits, which would leave a 
	 * pointer to crap in the row_data.  Use g_memdup to make a private, 
	 * persistant copy of the item identifier.
	 *
	 * We need to arrange to blow away this memory somewhere (like 
	 * ghack_menu_destroy and ghack_menu_window_clear for example).
	 *
	 *  -Erik
	 */
	{
	    menuItem newItem;
	    menuItem *pNewItem;
	    
	    newItem.identifier = *item->identifier;
	    newItem.itemNumber=nCurrentRow;
	    newItem.selected=FALSE;
	    newItem.accelerator[0]=0;
	    /* only copy 1 char, since accel keys are by definition 1 char */
	    if (accelBuf[0]) {
		strncpy(newItem.accelerator, accelBuf, 1);
	    }
	    newItem.accelerator[1]=0;

	    pNewItem = g_memdup(&newItem, sizeof( menuItem));
	    gtk_clist_set_row_data (GTK_CLIST (clist), nCurrentRow,
				    (gpointer) pNewItem);
	}
    }
    /* Now adjust the column widths to match the contents */
    gtk_clist_columns_autosize (GTK_CLIST (clist));
}

void
ghack_menu_window_end_menu (GtkWidget *menuWin, gpointer data)
{
  const char* p = (const char*) data;

  if ((p) && (*p)) {
    GtkWidget *frame1 = gtk_object_get_data (GTK_OBJECT (menuWin), "frame1");
    g_assert (frame1 != NULL);

    gtk_frame_set_label (GTK_FRAME(frame1), p);
  }

}


void ghack_menu_window_put_string(GtkWidget *menuWin, int attr,
                                  const char* text, gpointer data)
{
    GnomeLess *gless;
    MenuWinType isMenu;

    if (text == NULL)
        return;
    
    isMenu = (MenuWinType) GPOINTER_TO_INT
    	(gtk_object_get_data (GTK_OBJECT (menuWin), "isMenu"));

    if (isMenu == MenuText) {
      gless = GNOME_LESS (gtk_object_get_data (GTK_OBJECT (menuWin), "gless"));
      g_assert (gless != NULL);
      g_assert (gless->text != NULL);
      g_assert (GTK_IS_TEXT (gless->text));

      /* Don't bother with attributes yet */
      gtk_text_insert (GTK_TEXT (gless->text), NULL, NULL, NULL, text, -1);
      gtk_text_insert (GTK_TEXT (gless->text), NULL, NULL, NULL, "\n", -1);

    }

    else if (isMenu == MenuUnknown) {
      isMenu = MenuText;
      gtk_object_set_data (GTK_OBJECT (menuWin), "isMenu",
      			   GINT_TO_POINTER (isMenu));

      gtk_widget_set_usize (GTK_WIDGET (menuWin), 500, 400);
      gtk_window_set_policy (GTK_WINDOW (menuWin), TRUE, TRUE, FALSE);
    
      gless = GNOME_LESS (gnome_less_new ());
      g_assert (gless != NULL);
      gtk_object_set_data (GTK_OBJECT (menuWin), "gless", gless);
      gtk_widget_show (GTK_WIDGET (gless));

      gnome_less_show_string (gless, text);
      gtk_text_insert (GTK_TEXT (gless->text), NULL, NULL, NULL, "\n", -1);

      gtk_box_pack_start (GTK_BOX (GNOME_DIALOG (menuWin)->vbox),
      			  GTK_WIDGET (gless), TRUE, TRUE, 0);
    }
}


void
ghack_menu_destroy (GtkWidget *menuWin, gpointer data)
{
    MenuWinType isMenu;

    isMenu = (MenuWinType) GPOINTER_TO_INT
    	(gtk_object_get_data (GTK_OBJECT (menuWin), "isMenu"));

    if (isMenu == MenuText) {
      GnomeLess *gless;

      gless = GNOME_LESS (gtk_object_get_data (GTK_OBJECT (menuWin), "gless"));
      g_assert (gless != NULL);
      g_assert (gless->text != NULL);
      g_assert (GTK_IS_TEXT (gless->text));
      gtk_widget_destroy(GTK_WIDGET(gless));
    }

    else if (isMenu == MenuMenu) {
      GtkWidget *frame1, *swin, *clist; 

      /* destroy existing menu data, if any */
      clist = gtk_object_get_data (GTK_OBJECT (menuWin), "clist");
      if (clist) {
	/* destroy all the row_data we stored in the clist */
	int i, numRows;
	menuItem* item;
	numRows = GPOINTER_TO_INT( gtk_object_get_data(
		    GTK_OBJECT(clist), "numRows") );
	for( i=0; i<numRows; i++) {
	    item = (menuItem*) gtk_clist_get_row_data( 
		    GTK_CLIST (clist), i);
	    if (item != NULL) {
		g_free( item);
		gtk_clist_set_row_data (GTK_CLIST (clist), i, 
			(gpointer) NULL);
	    }
	}

	gtk_object_set_data (GTK_OBJECT (clist), "numItems",
				GINT_TO_POINTER (-1));
	gtk_widget_destroy (clist);
      }
      swin = gtk_object_get_data (GTK_OBJECT (menuWin), "swin");
      if (swin) {
	gtk_widget_destroy (swin);
      }
      frame1 = gtk_object_get_data (GTK_OBJECT (menuWin), "frame1");
      if (frame1) {
	gtk_widget_destroy (frame1);
      }
    }
    gnome_delete_nhwindow_by_reference (menuWin);    
}


GtkWidget*
ghack_init_menu_window (void)
{
    GtkWidget *menuWin = NULL;
    GtkWidget *parent = ghack_get_main_window ();

    menuWin = gnome_dialog_new("GnomeHack", GNOME_STOCK_BUTTON_OK, 
		    GNOME_STOCK_BUTTON_CANCEL, NULL);
    
    gnome_dialog_set_default( GNOME_DIALOG(menuWin), 0);
    gtk_signal_connect(GTK_OBJECT(menuWin), "destroy",
		    GTK_SIGNAL_FUNC(ghack_menu_destroy),
		    NULL);

    gtk_signal_connect (GTK_OBJECT (menuWin), "delete_event",
			GTK_SIGNAL_FUNC (ghack_menu_hide),
			NULL);
          
    gtk_signal_connect(GTK_OBJECT(menuWin), "ghack_clear",
		    GTK_SIGNAL_FUNC(ghack_menu_window_clear),
		    NULL);

    gtk_signal_connect(GTK_OBJECT(menuWin), "ghack_display",
                       GTK_SIGNAL_FUNC(ghack_menu_window_display),
                       NULL);

    gtk_signal_connect(GTK_OBJECT(menuWin), "ghack_start_menu",
                       GTK_SIGNAL_FUNC(ghack_menu_window_start_menu),
                       NULL);

    gtk_signal_connect(GTK_OBJECT(menuWin), "ghack_add_menu",
                       GTK_SIGNAL_FUNC(ghack_menu_window_add_menu),
                       NULL);

    gtk_signal_connect(GTK_OBJECT(menuWin), "ghack_end_menu",
                       GTK_SIGNAL_FUNC(ghack_menu_window_end_menu),
                       NULL);

    gtk_signal_connect(GTK_OBJECT(menuWin), "ghack_select_menu",
                       GTK_SIGNAL_FUNC(ghack_menu_window_select_menu),
                       NULL);

    gtk_signal_connect(GTK_OBJECT(menuWin), "ghack_putstr",
                       GTK_SIGNAL_FUNC(ghack_menu_window_put_string),
                       NULL);

    gtk_signal_connect(GTK_OBJECT(menuWin), "key_press_event",
                       GTK_SIGNAL_FUNC(ghack_menu_window_key),
                       NULL);

    /* Center the dialog over parent */
    g_assert (parent != NULL);
    g_assert (menuWin != NULL);
    g_assert (GTK_IS_WINDOW (parent));
    g_assert (GNOME_IS_DIALOG (menuWin));
    gnome_dialog_set_parent (GNOME_DIALOG (menuWin), GTK_WINDOW (parent));
    
    return menuWin;
}

static void
ghack_ext_key_hit(GtkWidget *menuWin, GdkEventKey *event, gpointer data)
{
    GtkWidget* clist;
    extMenu* info = (extMenu*) data;
    int i;
    char c = event->string[0];

    clist = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(menuWin), "clist"));
    g_assert(clist != NULL);

    /* if too long between keystrokes, reset to initial state */
    if (event->time - info->lastTime > 500) goto init_state;

    /* see if current item continue to match */
    if (info->charIdx > 0) {
	if (extcmdlist[info->curItem].ef_txt[info->charIdx] == c) {
	    ++info->charIdx;
	    goto found;
	}
    }

    /* see if the prefix matches a later command in the list */
    if (info->curItem >= 0) {
	for (i = info->curItem + 1; i < info->numRows; ++i) {
	    if (!strncmp(extcmdlist[info->curItem].ef_txt,
			 extcmdlist[i].ef_txt, info->charIdx)) {
		if (extcmdlist[i].ef_txt[info->charIdx] == c) {
		    ++info->charIdx;
		    info->curItem = i;
		    goto found;
		}
	    }
	}
    }
		
init_state:
    /* reset to initial state, look for matching 1st character */
    for (i = 0; i < info->numRows; ++i) {
	if (extcmdlist[i].ef_txt[0] == c) {
	    info->charIdx = 1;
	    info->curItem = i;
	    goto found;
	}
    }

    /* no match: leave prior, if any selection in place */
    return;

found:
    info->lastTime = event->time;
    gtk_clist_select_row(GTK_CLIST(clist), info->curItem, 0);
    if (gtk_clist_row_is_visible(GTK_CLIST(clist),
				 info->curItem) != GTK_VISIBILITY_FULL)
	gtk_clist_moveto(GTK_CLIST(clist), info->curItem, 0, 0.5, 0);
}

int
ghack_menu_ext_cmd(void)
{
    int n;
    GtkWidget* dialog;
    GtkWidget* swin;
    GtkWidget* frame1;
    GtkWidget* clist;
    extMenu info;

    dialog = gnome_dialog_new("Extended Commands",
			      GNOME_STOCK_BUTTON_OK,
			      GNOME_STOCK_BUTTON_CANCEL,
			      NULL);
    gnome_dialog_close_hides(GNOME_DIALOG(dialog), FALSE);
    gtk_signal_connect(GTK_OBJECT(dialog), "key_press_event",
		       GTK_SIGNAL_FUNC(ghack_ext_key_hit), &info);

    frame1 = gtk_frame_new("Make your selection");
    gtk_object_set_data(GTK_OBJECT(dialog), "frame1", frame1);
    gtk_widget_show(frame1);
    gtk_container_border_width(GTK_CONTAINER(frame1), 3);

    swin = gtk_scrolled_window_new(NULL, NULL);
    clist = gtk_clist_new(2);
    gtk_object_set_data(GTK_OBJECT(dialog), "clist", clist);
    gtk_widget_set_usize(clist, 500, 400);
    gtk_container_add(GTK_CONTAINER(swin), clist);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin),
				   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

    gtk_signal_connect(GTK_OBJECT(clist), "select_row",
		       GTK_SIGNAL_FUNC(ghack_menu_row_selected), NULL);

    gtk_container_add(GTK_CONTAINER(frame1), swin);
    gtk_box_pack_start_defaults(GTK_BOX(GNOME_DIALOG(dialog)->vbox), frame1);

    /* Add the extended commands into the list here... */
    for (n = 0; extcmdlist[n].ef_txt; ++n) {
	const char *text[3]={extcmdlist[n].ef_txt,extcmdlist[n].ef_desc,NULL};
	gtk_clist_insert(GTK_CLIST(clist), n, (char**) text);
    }

    /* fill in starting info fields */
    info.curItem = -1;
    info.numRows = n;
    info.charIdx = 0;
    info.lastTime = 0;

    gtk_clist_columns_autosize(GTK_CLIST(clist));
    gtk_widget_show_all(swin);

    /* Center the dialog over over parent */
    gnome_dialog_set_default(GNOME_DIALOG(dialog), 0);
    gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
    gnome_dialog_set_parent(GNOME_DIALOG(dialog),
			    GTK_WINDOW(ghack_get_main_window()));

    /* Run the dialog -- returning whichever button was pressed */
    n = gnome_dialog_run_and_close(GNOME_DIALOG(dialog));

    /* Quit on button 2 or error */
    return (n != 0) ? -1 : info.curItem;
}
