Fix: SE061 Problem: The GTK interface player selection is based on the 3.2 codebase and needs updating for the 3.3 codebase. Note that this fix is not specific to Slash'EM. Indeed it would be a starting point for JNetHack when this moves to the 3.3 codebase, but no attempt has been made to include Japanese language support. Compatible with: Slash'EM 0.0.6E0F3 Author: J. Ali Harlow, ali@avrc.city.ac.uk Date: 6 Apr 2000 *** ../slashem-0.0.6E0F3/win/gtk/gtk.c Sat Mar 4 17:47:11 2000 --- win/gtk/gtk.c Thu Apr 6 15:17:03 2000 *************** *** 21,29 **** static int initialized; static int initialized2; ! static void select_player(GtkWidget *w, gpointer data); static void key_command(GtkWidget *w, gpointer data); static void game_option(GtkWidget *w, gpointer data); static void game_topten(GtkWidget *w, gpointer data); static void help_help(GtkWidget *w, gpointer data); --- 21,30 ---- static int initialized; static int initialized2; ! static void select_player(GtkWidget *w, guint data); static void key_command(GtkWidget *w, gpointer data); static void game_option(GtkWidget *w, gpointer data); + static void game_quit(GtkWidget *w, gpointer data); static void game_topten(GtkWidget *w, gpointer data); static void help_help(GtkWidget *w, gpointer data); *************** *** 143,185 **** #endif }; ! static GtkItemFactoryEntry menu_items[] = { {"/Game", NULL, NULL, 0, ""}, {"/Game/Play", NULL, NULL, 0, ""}, ! {"/Game/Play/Archeologist", "F1", select_player, 'A', NULL}, ! {"/Game/Play/Barbarian", "F2", select_player, 'B', NULL}, ! {"/Game/Play/Caveman", "F3", select_player, 'C', NULL}, ! {"/Game/Play/Elf", "F4", select_player, 'E', NULL}, ! #ifdef FIGHTER ! {"/Game/Play/Fighter", "F5", select_player, 'F', NULL}, ! {"/Game/Play/Healer", "F6", select_player, 'H', NULL}, ! {"/Game/Play/Knight", "F7", select_player, 'K', NULL}, ! {"/Game/Play/Priest", "F8", select_player, 'P', NULL}, ! {"/Game/Play/Rogue", "F9", select_player, 'R', NULL}, ! {"/Game/Play/Samurai", "F10", select_player, 'S', NULL}, ! {"/Game/Play/Tourist", "F11", select_player, 'T', NULL}, ! {"/Game/Play/Valkyrie", "F12", select_player, 'V', NULL}, ! {"/Game/Play/Wizard", "F1", select_player, 'W', NULL}, ! #else ! {"/Game/Play/Healer", "F5", select_player, 'H', NULL}, ! {"/Game/Play/Knight", "F6", select_player, 'K', NULL}, ! {"/Game/Play/Priest", "F7", select_player, 'P', NULL}, ! {"/Game/Play/Rogue", "F8", select_player, 'R', NULL}, ! {"/Game/Play/Samurai", "F9", select_player, 'S', NULL}, ! {"/Game/Play/Tourist", "F10", select_player, 'T', NULL}, ! {"/Game/Play/Valkyrie", "F11", select_player, 'V', NULL}, ! {"/Game/Play/Wizard", "F12", select_player, 'W', NULL}, ! #endif ! {"/Game/Play/GPsep1", NULL, NULL, 0, ""}, ! {"/Game/Play/Random", "F2", select_player, 'Y', NULL}, {"/Game/Gsep1", NULL, NULL, 0, ""}, {"/Game/Save", "S", key_command, 'S', NULL}, {"/Game/Option", "O", game_option, 'O', NULL}, {"/Game/Score", NULL, game_topten, 0, NULL}, {"/Game/Gsep2", NULL, NULL, 0, ""}, ! {"/Game/Quit", NULL, select_player, 'Q', NULL}, }; static GtkItemFactoryEntry helpmenu_items[] = { {"/Help", NULL, NULL, 0, ""}, {"/Help/Command Help", NULL, help_help, 0, NULL}, --- 144,165 ---- #endif }; ! static GtkItemFactoryEntry menu_template[] = { {"/Game", NULL, NULL, 0, ""}, {"/Game/Play", NULL, NULL, 0, ""}, ! /* Roles are inserted in the place of this NULL element */ ! {NULL, NULL, NULL, 0, NULL}, {"/Game/Gsep1", NULL, NULL, 0, ""}, {"/Game/Save", "S", key_command, 'S', NULL}, {"/Game/Option", "O", game_option, 'O', NULL}, {"/Game/Score", NULL, game_topten, 0, NULL}, {"/Game/Gsep2", NULL, NULL, 0, ""}, ! {"/Game/Quit", NULL, game_quit, 0, NULL}, }; + static GtkItemFactoryEntry *menu_items; + static int nmenu_items; + static GtkItemFactoryEntry helpmenu_items[] = { {"/Help", NULL, NULL, 0, ""}, {"/Help/Command Help", NULL, help_help, 0, NULL}, *************** *** 530,535 **** --- 510,521 ---- return TRUE; } + static void + game_quit(GtkWidget *widget, gpointer data) + { + quit(); + } + static gint default_destroy(GtkWidget *widget, gpointer data) { *************** *** 565,605 **** return FALSE; } static void ! select_player(GtkWidget *widget, gpointer data) { ! #ifdef FIGHTER ! static char *class = "ABCEFHKPRSTVW"; ! #else ! static char *class = "ABCEHKPRSTVW"; ! #endif ! pl_selection = 0; ! if((int)data == 'Y') ! pl_selection = class[rn2(strlen(class))]; ! else if((int)data == 'Q'){ ! quit(); } else ! pl_selection = (int)data; ! ! if(pl_selection) ! quit_hook(); } ! static gint ! credit_expose_event(GtkWidget *widget, GdkEventExpose *event) { ! gtk_main_quit(); ! return FALSE; } static void ! nh_rc(void) { ! gtk_rc_parse("gtkrc"); } void --- 551,1018 ---- return FALSE; } + static gint + credit_expose_event(GtkWidget *widget, GdkEventExpose *event) + { + gtk_main_quit(); + + return FALSE; + } + static void ! nh_rc(void) { ! gtk_rc_parse("gtkrc"); ! } ! /* ! * ALI ! * ! * Player selection code for 3.3 codebase. ! * ! * This code tries to keep to the spirit of the GTK interface by having ! * all possible selections available as menus for the user (rather than ! * bringing up a dialog). It also keeps the keyboard accelerators (one ! * per role), which are numbered from F1..F12, Shift-F1..Shift-F11 in ! * sequence. Shift-F12 is reserved for a random role. ! * ! * The interaction of the selection code and the flags.initfoo fields ! * (set for example with an OPTIONS=race:gnome line) is complex. These ! * values could either be treated as requirements (so that specifying ! * race:gnome would rule out any possibility of playing a Knight, for ! * example), or as cached answers to questions (so that the answer is ! * ignored until the question is asked and since role is queried before ! * race, the problem does not arise). The TTY interface follows the ! * latter interpretation, so we do too. ! * ! * For our purposes, displaying a menu is equivalent to asking a question ! * and the user choosing one the options (either by actually clicking ! * on an option or simply by moving the mouse) equates to giving an ! * answer. The joy of the GTK interface is that the user can un-answer ! * many questions by simply moving the mouse pointer back to the previous ! * menu. ! */ ! #define SELECT_KEY_ROLESHIFT 20 ! #define SELECT_KEY_RACESHIFT 8 ! #define SELECT_KEY_GENDSHIFT 4 ! #define SELECT_KEY_ALIGNSHIFT 0 ! ! #define SELECT_KEY_NUM(key, shift, mask) \ ! ((int)((key)>>(shift)&(mask)) - 1) ! ! #define SELECT_KEY_ROLENUM(key) \ ! SELECT_KEY_NUM(key, SELECT_KEY_ROLESHIFT, 0xfff) ! #define SELECT_KEY_RACENUM(key) \ ! SELECT_KEY_NUM(key, SELECT_KEY_RACESHIFT, 0xfff) ! #define SELECT_KEY_GENDNUM(key) \ ! SELECT_KEY_NUM(key, SELECT_KEY_GENDSHIFT, 0xf) ! #define SELECT_KEY_ALIGNNUM(key) \ ! SELECT_KEY_NUM(key, SELECT_KEY_ALIGNSHIFT, 0xf) ! ! /* ! * The call back routine. This extracts the relevant answers from the ! * menu option selected and arranges for a new game to be started. ! */ ! ! static void ! select_player(GtkWidget *widget, guint data) ! { ! flags.initrole = SELECT_KEY_ROLENUM(data); ! flags.initrace = SELECT_KEY_RACENUM(data); ! flags.initgend = SELECT_KEY_GENDNUM(data); ! flags.initalign = SELECT_KEY_ALIGNNUM(data); ! ! quit_hook(); ! } ! ! /* ! * A node of the temporary tree which is used to generate the menu options. ! * ! * The key field contains the answers which the user would have given in ! * order to reach this point. Note that you need to know the level that the ! * node is in the tree to be able to distinguish an answer of 'random' from ! * `not yet asked'. This information is not stored in the tree itself. ! * Each son represents a possible answer to the next question. Again, you ! * need to know the level to know what the question is. ! * ! * Level 0 1 2 3 ! * Question Role? Race? Gender? Alignment? ! * ! * Tree root--->Arch--->human------+--->male---------+->lawful ! * | \->neutral ! * | \->random ! * \--->female-------+->lawful ! * \... ! */ ! ! struct select_node { ! unsigned long key; ! int no_sons; ! struct select_node *sons; ! }; ! ! /* ! * Return the th possible answer to the question at level ! * given the answers already given in . Note that indx==0 represents ! * the first possible answer. ! * ! * If indx is out of range, return _exactly_ -1. ! * ! * We use a cached answer if it is a valid answer. If not, we simply ignore it. ! * ! * Note that this function never lists random as a possible answer, the ! * caller must add this to the list if appropriate. ! */ ! ! static int ! select_node_option(unsigned long key, int level, int indx) ! { ! int rolenum, racenum, n, i, j; ! boolean (*valid)(int rolenum, int racenum, int alignnum); ! switch(level) ! { ! case 0: ! /* Role */ ! if (flags.initrole >= 0) ! return indx ? -1 : flags.initrole; ! for (i = 0; roles[i].name.m; i++) ! if (!indx--) ! return i; ! return -1; ! break; ! case 1: ! /* Race */ ! rolenum = SELECT_KEY_ROLENUM(key); ! if (flags.initrace >= 0 && ! (rolenum < 0 || validrace(rolenum, flags.initrace))) ! return indx ? -1 : flags.initrace; ! for (i = 0; races[i].noun; i++) ! if (rolenum < 0 || validrace(rolenum, i)) ! if (!indx--) ! return i; ! return -1; ! break; ! case 3: ! /* Alignmnent */ ! /* FALL THROUGH */ ! case 2: ! /* Gender */ ! if (level == 2) ! { ! n = ROLE_GENDERS; ! valid = validgend; ! i = flags.initgend; ! } ! else ! { ! n = ROLE_ALIGNS; ! valid = validalign; ! i = flags.initalign; ! } ! rolenum = SELECT_KEY_ROLENUM(key); ! racenum = SELECT_KEY_RACENUM(key); ! if (i >= 0) ! { ! if (rolenum < 0) ! if (racenum < 0) ! return indx ? -1 : i; ! else ! { ! for (j = 0; roles[j].name.m; j++) ! if (valid(j, racenum, i)) ! return indx ? -1 : i; ! } ! else if (racenum < 0) ! { ! for (j = 0; races[j].noun; j++) ! if (valid(rolenum, j, i)) ! return indx ? -1 : i; ! } ! else if (valid(rolenum, racenum, i)) ! return indx ? -1 : i; ! } ! if (rolenum < 0) ! if (racenum < 0) ! { ! if (indx >= 0 && indx < n) ! return indx; ! } ! else ! { ! for (i = 0; i < n; i++) ! for (j = 0; roles[j].name.m; j++) ! if (valid(j, racenum, i)) ! { ! if (!indx--) ! return i; ! break; ! } ! } ! else if (racenum < 0) ! { ! for (i = 0; i < n; i++) ! for (j = 0; races[j].noun; j++) ! if (valid(rolenum, j, i)) ! { ! if (!indx--) ! return i; ! break; ! } ! } ! else ! for (i = 0; i < n; i++) ! if (valid(rolenum, racenum, i)) ! if (!indx--) ! return i; ! return -1; ! break; } + } + + /* + * Fill a tree starting at which is in level . + * Return the number of menu items (including titles and seperators) + * that this tree represents. + */ + + static int + select_node_fill(struct select_node *node, int level) + { + int shift, no_opts, count, option, i; + for (no_opts = 0; ; no_opts++) + if (select_node_option(node->key, level, no_opts) < 0) + break; + if (level == 0) + shift = SELECT_KEY_ROLESHIFT; + else if (level == 1) + shift = SELECT_KEY_RACESHIFT; + else if (level == 2) + shift = SELECT_KEY_GENDSHIFT; else ! shift = SELECT_KEY_ALIGNSHIFT; ! if (no_opts > 1) ! { ! node->no_sons = no_opts + 1; ! count = no_opts + 3; ! } ! else ! { ! node->no_sons = no_opts; ! count = no_opts + 1; ! } ! node->sons = (struct select_node *)alloc(node->no_sons * ! sizeof(struct select_node)); ! for (i = 0; i < node->no_sons; i++) ! { ! option = select_node_option(node->key, level, i); ! node->sons[i].key = node->key | (option + 1 << shift); ! if (level<3) ! count += select_node_fill(node->sons + i, level + 1); ! else ! { ! node->sons[i].no_sons = 0; ! node->sons[i].sons = NULL; ! } ! } ! return count; } ! /* ! * Return a menu item path representing the answers given to ! * questions stored in . If is not NULL, append this as ! * a leaf to the path. ! */ ! ! static char * ! select_node_path(unsigned long key, int level, char *leaf) { ! int rolenum, racenum, gendnum, alignnum, len, i; ! char *path; ! rolenum = SELECT_KEY_ROLENUM(key); ! racenum = SELECT_KEY_RACENUM(key); ! gendnum = SELECT_KEY_GENDNUM(key); ! alignnum = SELECT_KEY_ALIGNNUM(key); ! len = 11 + level; ! if (level > 0) ! if (rolenum >= 0) ! len += strlen(roles[rolenum].name.m); ! else ! len += 6; ! if (level > 1) ! if (racenum >= 0) ! len += strlen(races[racenum].noun); ! else ! len += 6; ! if (level > 2) ! if (gendnum >= 0) ! len += strlen(genders[gendnum].adj); ! else ! len += 6; ! if (level > 3) ! if (alignnum >= 0) ! len += strlen(aligns[alignnum].adj); ! else ! len += 6; ! if (leaf) ! len += 1 + strlen(leaf); ! path = (gchar *) alloc(len); ! strcpy(path, "/Game/Play"); ! #define SELECT_STR(num, str) (((num) >= 0) ? (str) : "Random") ! if (level > 0) ! { ! strcat(path, "/"); ! strcat(path, SELECT_STR(rolenum, roles[rolenum].name.m)); ! } ! if (level > 1) ! { ! strcat(path, "/"); ! strcat(path, SELECT_STR(racenum, races[racenum].noun)); ! } ! if (level > 2) ! { ! strcat(path, "/"); ! strcat(path, SELECT_STR(gendnum, genders[gendnum].adj)); ! } ! if (level > 3) ! { ! strcat(path, "/"); ! strcat(path, SELECT_STR(alignnum, aligns[alignnum].adj)); ! } ! if (leaf) ! { ! strcat(path, "/"); ! strcat(path, leaf); ! } ! #undef SELECT_STR ! return path; ! } ! ! /* ! * Return a suitable keyboard accelrator to go with this menu item. ! * For most menu items this will be NULL, but we allocate one menu ! * item from each role to a function key. This allows the user to ! * press a function key to start the game in that role. The race, ! * gender and alignment of the character will either be as required ! * by the role, as specified in the flags.initfoo fields or randomly ! * chosen. ! */ ! ! static char * ! select_node_accel(unsigned long key) ! { ! int rolenum; ! char *accel; ! ! rolenum = SELECT_KEY_ROLENUM(key); ! if (rolenum >= 23) ! return NULL; ! if (SELECT_KEY_RACENUM(key) >= 0 && select_node_option(key, 1, 1) >= 0) ! return NULL; ! if (SELECT_KEY_GENDNUM(key) >= 0 && select_node_option(key, 2, 1) >= 0) ! return NULL; ! if (SELECT_KEY_ALIGNNUM(key) >= 0 && select_node_option(key, 3, 1) >= 0) ! return NULL; ! if (rolenum < 0) ! { ! accel = (gchar *) alloc(11); ! sprintf(accel, "F12"); ! } ! else if (rolenum < 12) ! { ! accel = (gchar *) alloc(rolenum > 9 ? 4 : 3); ! sprintf(accel, "F%d", rolenum + 1); ! } ! else ! { ! accel = (gchar *) alloc(rolenum > 20 ? 11 : 10); ! sprintf(accel, "F%d", rolenum - 11); ! } ! return accel; ! } ! ! /* ! * Walk the tree, generating menu items as we go. ! */ ! ! static int ! select_node_traverse(struct select_node *node, int offset, int level) ! { ! int i; ! char *titles[] = { "Role", "Race", "Gender", "Alignment" }; ! if (node->no_sons) ! { ! menu_items[offset].path = ! select_node_path(node->key, level, titles[level]); ! menu_items[offset].accelerator = NULL; ! menu_items[offset].callback = NULL; ! menu_items[offset].callback_action = 0; ! menu_items[offset++].item_type = ""; ! for (i = 0; i < node->no_sons; i++) ! { ! if (node->sons[i].key == node->key) ! { ! menu_items[offset].path = ! select_node_path(node->key, level, "GPSepR"); ! menu_items[offset].accelerator = NULL; ! menu_items[offset].callback = NULL; ! menu_items[offset].callback_action = 0; ! menu_items[offset++].item_type = "<Separator>"; ! } ! offset = select_node_traverse(node->sons + i, offset, level + 1); ! } ! } ! else ! { ! menu_items[offset].path = select_node_path(node->key, level, NULL); ! menu_items[offset].accelerator = select_node_accel(node->key); ! menu_items[offset].callback = select_player; ! menu_items[offset].callback_action = node->key; ! menu_items[offset++].item_type = NULL; ! } ! return offset; } static void ! select_node_free(struct select_node *node) { ! int i; ! for (i = 0; i < node->no_sons; i++) ! select_node_free(node->sons + i); ! free(node->sons); ! } ! ! /* ! * Initialise the player selection code by creating a temporary tree ! * of all the possible options, using it to generate a set of menu ! * items that can be passed to GTK and finally freeing the tree. ! */ ! ! static void ! init_select_player(void) ! { ! int num_opts, i; ! struct select_node *root; ! ! root = (struct select_node *)alloc(sizeof(struct select_node)); ! root->key = 0; ! num_opts = select_node_fill(root, 0); ! menu_items = (GtkItemFactoryEntry *)alloc(sizeof(GtkItemFactoryEntry) * ! (num_opts + SIZE(menu_template) - 1)); ! nmenu_items = 0; ! for (i = 0; i < SIZE(menu_template); i++) ! { ! if (menu_template[i].path) ! menu_items[nmenu_items++] = menu_template[i]; ! else ! nmenu_items = select_node_traverse(root, nmenu_items, 0); ! } ! if (nmenu_items > num_opts) ! panic("GTK: init_select_player: Too many options (%d instead of %d)", ! nmenu_items, num_opts); ! select_node_free(root); ! free(root); } void *************** *** 613,618 **** --- 1026,1034 ---- nh_rc(); gtk_init(argc, &argv); + + init_select_player(); + /* creat credit widget and show */ *************** *** 696,702 **** main_vbox = nh_gtk_new_and_add(gtk_vbox_new(FALSE, 0), main_window, ""); { - int nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]); int nplaymenu_items = sizeof(playmenu_items) / sizeof(playmenu_items[0]); int nhelpmenu_items = sizeof(helpmenu_items) / sizeof(helpmenu_items[0]); --- 1112,1117 ----