OSDN Git Service

Initial Import
[nethackexpress/trunk.git] / win / gnome / gnmenu.c
1 /*      SCCS Id: @(#)gnmenu.c   3.4     2000/07/16      */
2 /* Copyright (C) 1998 by Erik Andersen <andersee@debian.org> */
3 /* NetHack may be freely redistributed.  See license for details. */
4
5 #include <string.h>
6 #include <gtk/gtk.h>
7 #include <gnome.h>
8 #include "gnmenu.h"
9 #include "gnmain.h"
10 #include "gnbind.h"
11 #include "func_tab.h"
12
13 typedef enum {
14         MenuUnknown = 0,
15         MenuText,
16         MenuMenu
17 } MenuWinType;
18
19 typedef struct {
20         ANY_P identifier;
21         gchar accelerator[BUFSZ];
22         int itemNumber;
23         int selected;
24 } menuItem;
25
26 typedef struct {
27         int curItem;
28         int numRows;
29         int charIdx;
30         guint32 lastTime;
31 } extMenu;
32
33 static GdkColor color_blue = { 0, 0, 0, 0xffff };
34
35
36 static void
37 ghack_menu_window_key(GtkWidget *menuWin, GdkEventKey *event, gpointer data)
38 {
39     int i, numRows;
40     menuItem* item;
41     MenuWinType isMenu;
42
43     isMenu = (MenuWinType) GPOINTER_TO_INT
44         (gtk_object_get_data (GTK_OBJECT (menuWin), "isMenu"));
45
46     if (isMenu == MenuMenu) {
47         GtkWidget *clist;
48         gint selection_mode;
49
50         clist = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (menuWin), "clist"));
51         g_assert (clist != NULL);
52         numRows = GPOINTER_TO_INT
53             (gtk_object_get_data(GTK_OBJECT(clist), "numRows"));
54         selection_mode = GPOINTER_TO_INT
55             (gtk_object_get_data (GTK_OBJECT(clist), "selection_mode"));
56         for (i = 0; i <= numRows; ++i) {
57             item = (menuItem*) gtk_clist_get_row_data(GTK_CLIST(clist), i);
58             if (item == NULL) continue;
59             if (!strcmp(item->accelerator, "")) continue;
60
61             if ((!strcmp(item->accelerator, event->string)) ||
62                 ((selection_mode == GTK_SELECTION_MULTIPLE) &&
63                  (event->keyval == ','))) {
64                 if (item->selected) {
65                     gtk_clist_unselect_row( GTK_CLIST (clist),
66                                             item->itemNumber, 0);
67                     item->selected = FALSE;
68                 } else {
69                     gtk_clist_select_row(GTK_CLIST (clist),
70                                          item->itemNumber, 0);
71                     if (gtk_clist_row_is_visible(GTK_CLIST(clist),
72                                 item->itemNumber) != GTK_VISIBILITY_FULL)
73                         gtk_clist_moveto(GTK_CLIST(clist),
74                                          item->itemNumber, 0, 0.5, 0);
75                     item->selected = TRUE;
76                 }
77             }
78         }
79     }
80 }
81
82
83 static void
84 ghack_menu_row_selected (GtkCList *clist, int row, int col, GdkEvent *event)
85 {
86     /* FIXME: Do something */
87 }
88
89
90 void
91 ghack_menu_window_clear(GtkWidget *menuWin, gpointer data)
92 {
93     MenuWinType isMenu;
94     int i, numRows;
95     menuItem* item;
96
97     isMenu = (MenuWinType) GPOINTER_TO_INT
98         (gtk_object_get_data (GTK_OBJECT (menuWin), "isMenu"));
99
100     if (isMenu == MenuMenu) {
101       GtkWidget *clist;
102
103       clist = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (menuWin), "clist"));
104       g_assert (clist != NULL);
105
106       /* destroy existing menu data, if any */
107       if (clist) {
108         /* destroy all the row_data we stored in the clist */
109         numRows = GPOINTER_TO_INT( gtk_object_get_data(
110                     GTK_OBJECT(clist), "numRows") );
111         for( i=0; i<numRows; i++) {
112             item = (menuItem*) gtk_clist_get_row_data( 
113                     GTK_CLIST (clist), i);
114             if (item != NULL) {
115                 g_free( item);
116                 gtk_clist_set_row_data (GTK_CLIST (clist), i, 
117                         (gpointer) NULL);
118             }
119         }
120         gtk_object_set_data (GTK_OBJECT (clist), "numItems",
121                                 GINT_TO_POINTER (-1));
122         gtk_clist_clear (GTK_CLIST (clist));
123       }
124     }
125     
126     else if (isMenu == MenuText) {
127       GnomeLess *gless;
128
129       gless = GNOME_LESS (gtk_object_get_data (GTK_OBJECT (menuWin), "gless"));
130       g_assert (gless != NULL);
131
132       gtk_editable_delete_text (GTK_EDITABLE (gless->text), 0, 0);
133     }
134
135 }
136
137 void
138 ghack_menu_window_display(GtkWidget *menuWin, gboolean blocking,
139                           gpointer data)
140 {
141     //if(blocking) {
142         gnome_dialog_close_hides (GNOME_DIALOG (menuWin), TRUE);
143         gnome_dialog_set_close (GNOME_DIALOG (menuWin), TRUE);
144         gnome_dialog_run_and_close(GNOME_DIALOG (menuWin));
145     //}
146     //else {
147         //gtk_widget_show(menuWin);
148     //}
149 }
150
151 gint
152 ghack_menu_hide( GtkWidget *menuWin, GdkEvent *event, gpointer data )
153 {
154     gtk_widget_hide (menuWin);
155     return FALSE; /* FIXME: what is correct result here? */
156 }
157
158
159 void 
160 ghack_menu_window_start_menu (GtkWidget *menuWin, gpointer data)
161 {
162     GtkWidget *frame1, *swin, *clist;
163     MenuWinType isMenu;
164     
165     g_assert (menuWin != NULL);
166     g_assert (data == NULL);
167
168     /* destroy existing menu data, if any */
169     frame1 = gtk_object_get_data (GTK_OBJECT (menuWin), "frame1");
170     if (frame1)
171       gtk_widget_destroy (frame1);
172  
173     isMenu = MenuMenu;
174     gtk_object_set_data (GTK_OBJECT (menuWin), "isMenu",
175                          GINT_TO_POINTER (isMenu));
176
177     gtk_widget_set_usize (GTK_WIDGET (menuWin), 500, 400);
178     gtk_window_set_policy (GTK_WINDOW (menuWin), TRUE, TRUE, FALSE);
179
180     frame1 = gtk_frame_new ("Make your selection");
181     g_assert (frame1 != NULL);
182     gtk_object_set_data (GTK_OBJECT(menuWin), "frame1", frame1);
183     gtk_widget_show (GTK_WIDGET (frame1));
184     gtk_container_set_border_width (GTK_CONTAINER (frame1), 5);
185     gtk_box_pack_start (GTK_BOX (GNOME_DIALOG(menuWin)->vbox), frame1,
186                         TRUE, TRUE, 0);
187     
188     swin = gtk_scrolled_window_new (NULL, NULL);
189     g_assert (swin != NULL);
190     gtk_object_set_data (GTK_OBJECT(menuWin), "swin", swin);
191     gtk_widget_show (GTK_WIDGET (swin));
192     gtk_container_add (GTK_CONTAINER (frame1), swin);
193
194     clist = gtk_clist_new (4);
195     g_assert (clist != NULL);
196     gtk_object_set_data (GTK_OBJECT(menuWin), "clist", clist);
197     gtk_widget_show (GTK_WIDGET (clist));
198     gtk_container_add (GTK_CONTAINER (swin), clist);
199
200     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin),
201             GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
202
203     gtk_signal_connect (GTK_OBJECT (clist), "select_row",
204             GTK_SIGNAL_FUNC (ghack_menu_row_selected), NULL);
205     gtk_object_set_data (GTK_OBJECT (clist), "numItems",
206                             GINT_TO_POINTER (-1));
207 }    
208
209     
210 int 
211 ghack_menu_window_select_menu (GtkWidget *menuWin, 
212         MENU_ITEM_P **_selected, gint how)
213 {
214    gint rc;
215    guint num_sel, i, idx;
216    GtkWidget *clist;
217    GList *cur;
218    MENU_ITEM_P *selected = NULL;
219    menuItem*    item;
220
221    g_assert (_selected != NULL);
222    *_selected = NULL;
223
224
225    if (how == PICK_NONE) {
226      gnome_dialog_close_hides (GNOME_DIALOG (menuWin), TRUE);
227      rc = gnome_dialog_run_and_close (GNOME_DIALOG (menuWin));
228      return( rc == 1 ? -1 : 0);
229    }
230
231    clist = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (menuWin), "clist"));
232    g_assert (clist != NULL);
233
234    gtk_object_set_data (GTK_OBJECT (clist), "selection_mode",
235                    GINT_TO_POINTER ((how == PICK_ANY)? 
236                        GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE));
237    gtk_clist_set_selection_mode (GTK_CLIST (clist), 
238            (how == PICK_ANY)? GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE);
239    gnome_dialog_close_hides (GNOME_DIALOG (menuWin), TRUE);
240    rc = gnome_dialog_run_and_close (GNOME_DIALOG (menuWin));
241    if ((rc == 1) || (GTK_CLIST (clist)->selection == NULL)) {
242      return(-1);
243    }
244
245    num_sel = g_list_length (GTK_CLIST (clist)->selection);
246    if (num_sel < 1) {
247      return(-1);
248    }
249
250    /* fill in array with selections from clist */
251    selected =  g_new0( MENU_ITEM_P, num_sel);
252    g_assert (selected != NULL);
253    cur = GTK_CLIST (clist)->selection;
254    i = 0;
255    while (cur) {
256      g_assert (i < num_sel);
257
258      /* grab row number from clist selection list */
259      idx = GPOINTER_TO_INT (cur->data);
260
261      item = (menuItem*) gtk_clist_get_row_data( GTK_CLIST (clist), idx);
262      selected[i].item = item->identifier;
263      selected[i].count = -1;
264      cur = g_list_next(cur);
265      i++;
266    }
267
268    *_selected = selected;
269
270    return( (int) num_sel);
271 }    
272
273 void 
274 ghack_menu_window_add_menu( GtkWidget *menuWin, gpointer menu_item,
275                             gpointer data)
276 {
277     GHackMenuItem*  item;
278     GtkWidget       *clist;
279     gchar buf[BUFSZ]="", accelBuf[BUFSZ]="";
280     gchar *pbuf;
281     char *text[4] = { buf, NULL, NULL, NULL };
282     gint nCurrentRow = -1, numItems = -1;
283     MenuWinType isMenu;
284     GtkStyle *bigStyle = NULL;
285     gboolean item_selectable;
286     GdkImlibImage* image;
287     static gboolean special;
288
289     g_assert (menu_item != NULL);
290     item = (GHackMenuItem*) menu_item;
291     item_selectable = ( item->identifier->a_int == 0)? FALSE : TRUE;
292     isMenu = (MenuWinType) GPOINTER_TO_INT
293         (gtk_object_get_data (GTK_OBJECT (menuWin), "isMenu"));
294
295     clist = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (menuWin), "clist"));
296     g_assert (clist != NULL);
297     /* This is a special kludge to make the special hidden help menu item work as designed */ 
298     if ( special==TRUE ) {
299         special=FALSE;
300         item_selectable=TRUE;
301     }
302     if ( ! strcmp( item->str, "The NetHack license.")) {
303         special=TRUE;
304     }
305     
306     if (item->str) {
307
308         /* First, make a new blank entry in the clist */
309         nCurrentRow = gtk_clist_append (GTK_CLIST (clist), text);
310
311         if (item->glyph != NO_GLYPH) {
312             image = ghack_image_from_glyph( item->glyph, FALSE);
313             if (image==NULL || image->pixmap==NULL) {
314                 g_warning("Bummer -- having to force rendering for glyph %d!", item->glyph);
315                 /* wierd -- pixmap is NULL so retry rendering it */
316                 image = ghack_image_from_glyph( item->glyph, TRUE);
317             }
318             if (image==NULL || image->pixmap==NULL) {
319                     g_error("Aiiee! glyph is still NULL for item\n\"%s\"", 
320                             item->str);
321             }
322             else 
323                 gtk_clist_set_pixmap (GTK_CLIST (clist), 
324                         nCurrentRow, 1, 
325                         gdk_imlib_move_image( image), 
326                         gdk_imlib_move_mask( image));
327         }
328         if (item->accelerator) {
329             /* FIXME: handle accelerator, */
330             g_snprintf(accelBuf, sizeof(accelBuf), "%c ", item->accelerator);
331             gtk_clist_set_text (GTK_CLIST (clist), nCurrentRow, 0, accelBuf);
332             g_snprintf(buf, sizeof(buf), "%s", item->str);
333             gtk_clist_set_text (GTK_CLIST (clist), nCurrentRow, 2, buf);
334         } else {
335             if (item->group_accel) {
336                 /* FIXME: maybe some day I should try to handle 
337                  * group accelerators... */
338             }
339             if (( (item->attr == 0) && (item->identifier->a_int != 0)) || (special ==TRUE) ) {
340                 numItems = GPOINTER_TO_INT( gtk_object_get_data(
341                             GTK_OBJECT(clist), "numItems") )+1;
342
343                 /* Ok, now invent a unique accelerator */
344                 if (  ('a'+numItems) <= 'z' ) {
345                     g_snprintf(accelBuf, sizeof(accelBuf), "%c ", 'a'+numItems);
346                     gtk_clist_set_text(GTK_CLIST(clist), nCurrentRow, 0, accelBuf);
347                 }
348                 else if ( ('A'+numItems-26)<='Z') {
349                     g_snprintf(accelBuf, sizeof(accelBuf), "%c ", 'A'+numItems-26); 
350                     gtk_clist_set_text(GTK_CLIST(clist), nCurrentRow, 0, accelBuf);
351                 } else {
352                     accelBuf[0] = buf[0] = 0;
353                 }
354                 g_snprintf(buf, sizeof(buf), "%s", item->str);
355                 gtk_clist_set_text (GTK_CLIST (clist), nCurrentRow, 2, buf);
356                 gtk_object_set_data (GTK_OBJECT (clist), "numItems",
357                                         GINT_TO_POINTER (numItems));
358
359                 /* This junk is to specially handle the options menu */
360                 pbuf = strstr( buf, " [");
361                 if (pbuf == NULL) {
362                     pbuf = strstr( buf, "\t[");
363                 }
364                 if (pbuf != NULL) {
365                     *pbuf=0;
366                     pbuf++;
367                     gtk_clist_set_text (GTK_CLIST (clist), nCurrentRow, 3, pbuf);
368                 }
369             }
370             /* FIXME: handle more than 26*2 accelerators (but how?
371              * since I only have so many keys to work with???)
372             else
373             {
374                 foo();
375             }
376             */
377             else {
378                 g_snprintf(buf, sizeof(buf), "%s", item->str);
379                 pbuf = strstr( buf, " [");
380                 if (pbuf == NULL) {
381                     pbuf = strstr( buf, "\t[");
382                 }
383                 if (pbuf != NULL) {
384                     *pbuf=0;
385                     pbuf++;
386                     gtk_clist_set_text (GTK_CLIST (clist), nCurrentRow, 3, pbuf);
387                 }
388                 gtk_clist_set_text (GTK_CLIST (clist), nCurrentRow, 2, buf);
389                 
390             }
391         }
392
393         if (item->attr) {
394             switch(item->attr) {
395                 case ATR_ULINE:
396                 case ATR_BOLD:
397                 case ATR_BLINK:
398                 case ATR_INVERSE:
399                     bigStyle = gtk_style_copy (GTK_WIDGET (clist)->style);
400                     g_assert (bigStyle != NULL);
401                     gdk_font_unref (bigStyle->font);
402                     bigStyle->font = gdk_font_load (
403                                 "-misc-fixed-*-*-*-*-20-*-*-*-*-*-*-*");
404                     bigStyle->fg[GTK_STATE_NORMAL] = color_blue;
405                     gtk_clist_set_cell_style (GTK_CLIST (clist), 
406                             nCurrentRow, 2, bigStyle);
407                     item_selectable = FALSE;
408             }
409         }
410
411
412         g_assert (nCurrentRow >= 0);
413         gtk_clist_set_selectable (GTK_CLIST (clist), nCurrentRow,
414                               item_selectable);
415
416         if ( item_selectable==TRUE && item->presel== TRUE) {
417             /* pre-select this item */
418             gtk_clist_select_row( GTK_CLIST (clist), nCurrentRow, 0);
419         }
420         
421         gtk_object_set_data (GTK_OBJECT (clist), "numRows",
422                                 GINT_TO_POINTER (nCurrentRow));
423         
424         /* We have to allocate memory here, since the menu_item currently
425          * lives on the stack, and will otherwise go to the great bit bucket 
426          * in the sky as soon as this function exits, which would leave a 
427          * pointer to crap in the row_data.  Use g_memdup to make a private, 
428          * persistant copy of the item identifier.
429          *
430          * We need to arrange to blow away this memory somewhere (like 
431          * ghack_menu_destroy and ghack_menu_window_clear for example).
432          *
433          *  -Erik
434          */
435         {
436             menuItem newItem;
437             menuItem *pNewItem;
438             
439             newItem.identifier = *item->identifier;
440             newItem.itemNumber=nCurrentRow;
441             newItem.selected=FALSE;
442             newItem.accelerator[0]=0;
443             /* only copy 1 char, since accel keys are by definition 1 char */
444             if (accelBuf[0]) {
445                 strncpy(newItem.accelerator, accelBuf, 1);
446             }
447             newItem.accelerator[1]=0;
448
449             pNewItem = g_memdup(&newItem, sizeof( menuItem));
450             gtk_clist_set_row_data (GTK_CLIST (clist), nCurrentRow,
451                                     (gpointer) pNewItem);
452         }
453     }
454     /* Now adjust the column widths to match the contents */
455     gtk_clist_columns_autosize (GTK_CLIST (clist));
456 }
457
458 void
459 ghack_menu_window_end_menu (GtkWidget *menuWin, gpointer data)
460 {
461   const char* p = (const char*) data;
462
463   if ((p) && (*p)) {
464     GtkWidget *frame1 = gtk_object_get_data (GTK_OBJECT (menuWin), "frame1");
465     g_assert (frame1 != NULL);
466
467     gtk_frame_set_label (GTK_FRAME(frame1), p);
468   }
469
470 }
471
472
473 void ghack_menu_window_put_string(GtkWidget *menuWin, int attr,
474                                   const char* text, gpointer data)
475 {
476     GnomeLess *gless;
477     MenuWinType isMenu;
478
479     if (text == NULL)
480         return;
481     
482     isMenu = (MenuWinType) GPOINTER_TO_INT
483         (gtk_object_get_data (GTK_OBJECT (menuWin), "isMenu"));
484
485     if (isMenu == MenuText) {
486       gless = GNOME_LESS (gtk_object_get_data (GTK_OBJECT (menuWin), "gless"));
487       g_assert (gless != NULL);
488       g_assert (gless->text != NULL);
489       g_assert (GTK_IS_TEXT (gless->text));
490
491       /* Don't bother with attributes yet */
492       gtk_text_insert (GTK_TEXT (gless->text), NULL, NULL, NULL, text, -1);
493       gtk_text_insert (GTK_TEXT (gless->text), NULL, NULL, NULL, "\n", -1);
494
495     }
496
497     else if (isMenu == MenuUnknown) {
498       isMenu = MenuText;
499       gtk_object_set_data (GTK_OBJECT (menuWin), "isMenu",
500                            GINT_TO_POINTER (isMenu));
501
502       gtk_widget_set_usize (GTK_WIDGET (menuWin), 500, 400);
503       gtk_window_set_policy (GTK_WINDOW (menuWin), TRUE, TRUE, FALSE);
504     
505       gless = GNOME_LESS (gnome_less_new ());
506       g_assert (gless != NULL);
507       gtk_object_set_data (GTK_OBJECT (menuWin), "gless", gless);
508       gtk_widget_show (GTK_WIDGET (gless));
509
510       gnome_less_show_string (gless, text);
511       gtk_text_insert (GTK_TEXT (gless->text), NULL, NULL, NULL, "\n", -1);
512
513       gtk_box_pack_start (GTK_BOX (GNOME_DIALOG (menuWin)->vbox),
514                           GTK_WIDGET (gless), TRUE, TRUE, 0);
515     }
516 }
517
518
519 void
520 ghack_menu_destroy (GtkWidget *menuWin, gpointer data)
521 {
522     MenuWinType isMenu;
523
524     isMenu = (MenuWinType) GPOINTER_TO_INT
525         (gtk_object_get_data (GTK_OBJECT (menuWin), "isMenu"));
526
527     if (isMenu == MenuText) {
528       GnomeLess *gless;
529
530       gless = GNOME_LESS (gtk_object_get_data (GTK_OBJECT (menuWin), "gless"));
531       g_assert (gless != NULL);
532       g_assert (gless->text != NULL);
533       g_assert (GTK_IS_TEXT (gless->text));
534       gtk_widget_destroy(GTK_WIDGET(gless));
535     }
536
537     else if (isMenu == MenuMenu) {
538       GtkWidget *frame1, *swin, *clist; 
539
540       /* destroy existing menu data, if any */
541       clist = gtk_object_get_data (GTK_OBJECT (menuWin), "clist");
542       if (clist) {
543         /* destroy all the row_data we stored in the clist */
544         int i, numRows;
545         menuItem* item;
546         numRows = GPOINTER_TO_INT( gtk_object_get_data(
547                     GTK_OBJECT(clist), "numRows") );
548         for( i=0; i<numRows; i++) {
549             item = (menuItem*) gtk_clist_get_row_data( 
550                     GTK_CLIST (clist), i);
551             if (item != NULL) {
552                 g_free( item);
553                 gtk_clist_set_row_data (GTK_CLIST (clist), i, 
554                         (gpointer) NULL);
555             }
556         }
557
558         gtk_object_set_data (GTK_OBJECT (clist), "numItems",
559                                 GINT_TO_POINTER (-1));
560         gtk_widget_destroy (clist);
561       }
562       swin = gtk_object_get_data (GTK_OBJECT (menuWin), "swin");
563       if (swin) {
564         gtk_widget_destroy (swin);
565       }
566       frame1 = gtk_object_get_data (GTK_OBJECT (menuWin), "frame1");
567       if (frame1) {
568         gtk_widget_destroy (frame1);
569       }
570     }
571     gnome_delete_nhwindow_by_reference (menuWin);    
572 }
573
574
575 GtkWidget*
576 ghack_init_menu_window (void)
577 {
578     GtkWidget *menuWin = NULL;
579     GtkWidget *parent = ghack_get_main_window ();
580
581     menuWin = gnome_dialog_new("GnomeHack", GNOME_STOCK_BUTTON_OK, 
582                     GNOME_STOCK_BUTTON_CANCEL, NULL);
583     
584     gnome_dialog_set_default( GNOME_DIALOG(menuWin), 0);
585     gtk_signal_connect(GTK_OBJECT(menuWin), "destroy",
586                     GTK_SIGNAL_FUNC(ghack_menu_destroy),
587                     NULL);
588
589     gtk_signal_connect (GTK_OBJECT (menuWin), "delete_event",
590                         GTK_SIGNAL_FUNC (ghack_menu_hide),
591                         NULL);
592           
593     gtk_signal_connect(GTK_OBJECT(menuWin), "ghack_clear",
594                     GTK_SIGNAL_FUNC(ghack_menu_window_clear),
595                     NULL);
596
597     gtk_signal_connect(GTK_OBJECT(menuWin), "ghack_display",
598                        GTK_SIGNAL_FUNC(ghack_menu_window_display),
599                        NULL);
600
601     gtk_signal_connect(GTK_OBJECT(menuWin), "ghack_start_menu",
602                        GTK_SIGNAL_FUNC(ghack_menu_window_start_menu),
603                        NULL);
604
605     gtk_signal_connect(GTK_OBJECT(menuWin), "ghack_add_menu",
606                        GTK_SIGNAL_FUNC(ghack_menu_window_add_menu),
607                        NULL);
608
609     gtk_signal_connect(GTK_OBJECT(menuWin), "ghack_end_menu",
610                        GTK_SIGNAL_FUNC(ghack_menu_window_end_menu),
611                        NULL);
612
613     gtk_signal_connect(GTK_OBJECT(menuWin), "ghack_select_menu",
614                        GTK_SIGNAL_FUNC(ghack_menu_window_select_menu),
615                        NULL);
616
617     gtk_signal_connect(GTK_OBJECT(menuWin), "ghack_putstr",
618                        GTK_SIGNAL_FUNC(ghack_menu_window_put_string),
619                        NULL);
620
621     gtk_signal_connect(GTK_OBJECT(menuWin), "key_press_event",
622                        GTK_SIGNAL_FUNC(ghack_menu_window_key),
623                        NULL);
624
625     /* Center the dialog over parent */
626     g_assert (parent != NULL);
627     g_assert (menuWin != NULL);
628     g_assert (GTK_IS_WINDOW (parent));
629     g_assert (GNOME_IS_DIALOG (menuWin));
630     gnome_dialog_set_parent (GNOME_DIALOG (menuWin), GTK_WINDOW (parent));
631     
632     return menuWin;
633 }
634
635 static void
636 ghack_ext_key_hit(GtkWidget *menuWin, GdkEventKey *event, gpointer data)
637 {
638     GtkWidget* clist;
639     extMenu* info = (extMenu*) data;
640     int i;
641     char c = event->string[0];
642
643     clist = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(menuWin), "clist"));
644     g_assert(clist != NULL);
645
646     /* if too long between keystrokes, reset to initial state */
647     if (event->time - info->lastTime > 500) goto init_state;
648
649     /* see if current item continue to match */
650     if (info->charIdx > 0) {
651         if (extcmdlist[info->curItem].ef_txt[info->charIdx] == c) {
652             ++info->charIdx;
653             goto found;
654         }
655     }
656
657     /* see if the prefix matches a later command in the list */
658     if (info->curItem >= 0) {
659         for (i = info->curItem + 1; i < info->numRows; ++i) {
660             if (!strncmp(extcmdlist[info->curItem].ef_txt,
661                          extcmdlist[i].ef_txt, info->charIdx)) {
662                 if (extcmdlist[i].ef_txt[info->charIdx] == c) {
663                     ++info->charIdx;
664                     info->curItem = i;
665                     goto found;
666                 }
667             }
668         }
669     }
670                 
671 init_state:
672     /* reset to initial state, look for matching 1st character */
673     for (i = 0; i < info->numRows; ++i) {
674         if (extcmdlist[i].ef_txt[0] == c) {
675             info->charIdx = 1;
676             info->curItem = i;
677             goto found;
678         }
679     }
680
681     /* no match: leave prior, if any selection in place */
682     return;
683
684 found:
685     info->lastTime = event->time;
686     gtk_clist_select_row(GTK_CLIST(clist), info->curItem, 0);
687     if (gtk_clist_row_is_visible(GTK_CLIST(clist),
688                                  info->curItem) != GTK_VISIBILITY_FULL)
689         gtk_clist_moveto(GTK_CLIST(clist), info->curItem, 0, 0.5, 0);
690 }
691
692 int
693 ghack_menu_ext_cmd(void)
694 {
695     int n;
696     GtkWidget* dialog;
697     GtkWidget* swin;
698     GtkWidget* frame1;
699     GtkWidget* clist;
700     extMenu info;
701
702     dialog = gnome_dialog_new("Extended Commands",
703                               GNOME_STOCK_BUTTON_OK,
704                               GNOME_STOCK_BUTTON_CANCEL,
705                               NULL);
706     gnome_dialog_close_hides(GNOME_DIALOG(dialog), FALSE);
707     gtk_signal_connect(GTK_OBJECT(dialog), "key_press_event",
708                        GTK_SIGNAL_FUNC(ghack_ext_key_hit), &info);
709
710     frame1 = gtk_frame_new("Make your selection");
711     gtk_object_set_data(GTK_OBJECT(dialog), "frame1", frame1);
712     gtk_widget_show(frame1);
713     gtk_container_border_width(GTK_CONTAINER(frame1), 3);
714
715     swin = gtk_scrolled_window_new(NULL, NULL);
716     clist = gtk_clist_new(2);
717     gtk_object_set_data(GTK_OBJECT(dialog), "clist", clist);
718     gtk_widget_set_usize(clist, 500, 400);
719     gtk_container_add(GTK_CONTAINER(swin), clist);
720     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin),
721                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
722
723     gtk_signal_connect(GTK_OBJECT(clist), "select_row",
724                        GTK_SIGNAL_FUNC(ghack_menu_row_selected), NULL);
725
726     gtk_container_add(GTK_CONTAINER(frame1), swin);
727     gtk_box_pack_start_defaults(GTK_BOX(GNOME_DIALOG(dialog)->vbox), frame1);
728
729     /* Add the extended commands into the list here... */
730     for (n = 0; extcmdlist[n].ef_txt; ++n) {
731         const char *text[3]={extcmdlist[n].ef_txt,extcmdlist[n].ef_desc,NULL};
732         gtk_clist_insert(GTK_CLIST(clist), n, (char**) text);
733     }
734
735     /* fill in starting info fields */
736     info.curItem = -1;
737     info.numRows = n;
738     info.charIdx = 0;
739     info.lastTime = 0;
740
741     gtk_clist_columns_autosize(GTK_CLIST(clist));
742     gtk_widget_show_all(swin);
743
744     /* Center the dialog over over parent */
745     gnome_dialog_set_default(GNOME_DIALOG(dialog), 0);
746     gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
747     gnome_dialog_set_parent(GNOME_DIALOG(dialog),
748                             GTK_WINDOW(ghack_get_main_window()));
749
750     /* Run the dialog -- returning whichever button was pressed */
751     n = gnome_dialog_run_and_close(GNOME_DIALOG(dialog));
752
753     /* Quit on button 2 or error */
754     return (n != 0) ? -1 : info.curItem;
755 }