/* $Id: gtk.c,v 1.53.2.4 2005/01/22 19:09:35 j_ali Exp $ */ /* GTK+ NetHack Copyright (c) Issei Numata 1999-2000 Copyright (c) Slash'EM Development Team 2001-2004 GTK+ NetHack may be freely redistributed. See license for details. */ /* #define DEBUG */ /* Uncomment for debugging */ /* #define DEBUG_SESSION */ /* For debugging session windows */ #include #include #ifdef WIN32 #include #endif #include "md5.h" #include "winGTK.h" #include "wintype.h" #include "func_tab.h" #include "dlb.h" #ifdef SHORT_FILENAMES #include "patchlev.h" #else #include "patchlevel.h" #endif #include "proxycb.h" #include "prxyclnt.h" int GTK_initialized; static int display_inventory_needed; static int in_topten; static int in_player_selection; static void select_player(GtkWidget *w, guint data); static void key_command(GtkWidget *w, gpointer data); static void move_command(GtkWidget *w, gpointer data); static void fight_command(GtkWidget *w, gpointer data); static void ext_command(GtkWidget *w, gpointer data); static void game_option(GtkWidget *w, gpointer data); static void game_preferences(GtkWidget *w, gpointer data); static void game_quit(GtkWidget *w, gpointer data); static void game_topten(GtkWidget *w, gpointer data); #ifdef GTKHACK static void game_troubleshooting(GtkWidget *widget, gpointer data); #endif static void nh_menu_sensitive(char *menu, boolean f); static int GTK_display_file(char *name); static void GTK_display_inventory(void); static void help_help(GtkWidget *w, gpointer data); static void help_shelp(GtkWidget *w, gpointer data); static void help_option(GtkWidget *w, gpointer data); static void help_je(GtkWidget *w, gpointer data); static void help_history(GtkWidget *w, gpointer data); static void help_license(GtkWidget *w, gpointer data); static void text_destroy_all(void); #define GTK_NORTH 0 #define GTK_EAST 1 #define GTK_SOUTH 2 #define GTK_WEST 3 #define GTK_NORTHEAST 4 #define GTK_NORTHWEST 5 #define GTK_SOUTHEAST 6 #define GTK_SOUTHWEST 7 NHWindow gtkWindows[MAXWIN]; struct nbtw *non_blocking_text_windows; /* * The Gtk interface maintains information about most top level windows * so that it can be saved in the profile between sessions. */ struct session_window_info { const char *name; /* The name is only used in the rc file */ unsigned int flags; GdkRectangle bounding; /* Position and size */ int ox, oy; /* Offset between set_geometry and configure */ GtkRequisition requisition; /* Most recent requisition request */ } session_window_info[] = { {"main", NH_SESSION_RESIZABLE, }, {"radar", 0, }, {"inventory", NH_SESSION_RESIZABLE, }, }; static gchar *help_font_name = NULL; static winid rawprint_win = WIN_ERR; GtkAccelGroup *accel_group=NULL; GtkWidget *main_window, *main_vbox; #ifdef WINGTK_RADAR static GtkWidget *main_radar; #endif static GtkItemFactory *main_item_factory; int root_width; int root_height; static int exiting = 0; GdkColor nh_color[N_NH_COLORS] = { /* * Standard NetHack colours (CLR_...) * Note: black & white may be remapped depending on map background */ {0, 0*257, 0*257, 0*257,}, /* black */ {0, 255*257, 0*257, 0*257,}, /* red */ {0, 152*257, 251*257, 152*257,}, /* pale green */ {0, 165*257, 42*257, 42*257,}, /* brown */ {0, 0*257, 0*257, 255*257,}, /* blue */ {0, 255*257, 0*257, 255*257,}, /* magenta */ {0, 224*257, 255*257, 255*257,}, /* light cyan */ {0, 190*257, 190*257, 190*257,}, /* gray */ {1, 0*257, 0*257, 0*257,}, /* default */ {0, 255*257, 165*257, 0*257,}, /* orange */ {0, 0*257, 255*257, 0*257,}, /* green */ {0, 255*257, 255*257, 0*257,}, /* yellow */ {0, 65*257, 105*257, 225*257,}, /* royal blue */ {0, 238*257, 130*257, 238*257,}, /* violet */ {0, 0*257, 255*257, 255*257,}, /* cyan */ {0, 255*257, 255*257, 255*257,}, /* white */ /* * Local additions (MAP_...) */ {0, 0*257, 100*257, 0*257,}, /* dark green */ {0, 0*257, 0*257, 0*257,}, /* fixed black */ {0, 255*257, 255*257, 255*257,}, /* fixed white */ }; #ifdef GTK_PROXY static struct proxycb_get_player_choices_res *player_choices = NULL; #endif char * GTK_ext_askname() { return GTK_getline("Who are you? ", FALSE); } static GtkItemFactoryEntry mainmenu_items[] = { {"/Game", NULL, NULL, 0, ""}, {"/Game/Gtear1", NULL, NULL, 0, ""}, {"/Game/Play", NULL, NULL, 0, ""}, {"/Game/Gsep1", NULL, NULL, 0, ""}, {"/Game/Save", "S", key_command, 'S', NULL}, {"/Game/Option", "O", game_option, 'O', NULL}, {"/Game/Preferences", NULL, game_preferences, 0, NULL}, {"/Game/Score", NULL, game_topten, 0, NULL}, #ifdef GTKHACK {"/Game/Troubleshooting", NULL, game_troubleshooting, 0,NULL}, #endif {"/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/Htear1", NULL, NULL, 0, ""}, {"/Help/Command Help", NULL, help_help, 0, NULL}, {"/Help/Key Help", NULL, help_shelp, 0, NULL}, {"/Help/Option Help", NULL, help_option, 0, NULL}, {"/Help/sep3", NULL, NULL, 0, ""}, {"/Help/History", NULL, help_history, 0, NULL}, {"/Help/License", NULL, help_license, 0, NULL}, }; static GtkItemFactoryEntry playmenu_items[] = { {"/Move", NULL, NULL, 0, ""}, {"/Move/Mtear1", NULL, NULL, 0, ""}, {"/Move/North", NULL, move_command, GTK_NORTH, NULL}, {"/Move/East", NULL, move_command, GTK_EAST, NULL}, {"/Move/South", NULL, move_command, GTK_SOUTH, NULL}, {"/Move/West", NULL, move_command, GTK_WEST, NULL}, {"/Move/Northeast", NULL, move_command, GTK_NORTHEAST, NULL}, {"/Move/Northwest", NULL, move_command, GTK_NORTHWEST, NULL}, {"/Move/Southeast", NULL, move_command, GTK_SOUTHEAST, NULL}, {"/Move/Southwest", NULL, move_command, GTK_SOUTHWEST, NULL}, {"/Move/Down", "greater", key_command, '>', NULL}, {"/Move/Up", "less", key_command, '<', NULL}, {"/Fight", NULL, NULL, 0, ""}, {"/Fight/Ftear1", NULL, NULL, 0, ""}, {"/Fight/North", NULL, fight_command, GTK_NORTH, NULL}, {"/Fight/East", NULL, fight_command, GTK_EAST, NULL}, {"/Fight/South", NULL, fight_command, GTK_SOUTH, NULL}, {"/Fight/West", NULL, fight_command, GTK_WEST, NULL}, {"/Fight/Northeast", NULL, fight_command, GTK_NORTHEAST, NULL}, {"/Fight/Northwest", NULL, fight_command, GTK_NORTHWEST, NULL}, {"/Fight/Southeast", NULL, fight_command, GTK_SOUTHEAST, NULL}, {"/Fight/Southwest", NULL, fight_command, GTK_SOUTHWEST, NULL}, {"/Check", NULL, NULL, 0, ""}, {"/Check/Ctear1", NULL, NULL, 0, ""}, {"/Check/Here", "colon", key_command, ':', NULL}, {"/Check/There", "semicolon", key_command, ';', NULL}, {"/Check/Trap", "asciicircum", key_command, '^', NULL}, {"/Equip", NULL, NULL, 0, ""}, {"/Equip/Etear1", NULL, NULL, 0, ""}, {"/Equip/Wield", "w", key_command, 'w', NULL}, {"/Equip/Exchange weapons", "x", key_command, 'x', NULL}, {"/Equip/Two-handed", NULL, ext_command, EXT_CMD_TWOWEAPON, NULL}, {"/Equip/Quiver", "Q", key_command, 'Q', NULL}, {"/Equip/Wear", "w", key_command, 'W', NULL}, {"/Equip/Take off", "T", key_command, 'T', NULL}, {"/Equip/Puton", "p", key_command, 'P', NULL}, {"/Equip/Remove", "r", key_command, 'R', NULL}, {"/You", NULL, NULL, 0, ""}, {"/You/Ytear1", NULL, NULL, 0, ""}, {"/You/Inventory", "i", key_command, 'i', NULL}, {"/You/Weapon", "parenright", key_command, ')', NULL}, {"/You/Armor", "bracketleft", key_command, '[', NULL}, {"/You/Ring", "equal", key_command, '=', NULL}, {"/You/Amulet", "quotedbl", key_command, '"', NULL}, {"/You/Tool", "parenleft", key_command, '(', NULL}, {"/You/Spells", "plus", key_command, '+', NULL}, {"/You/Known Item", "backslash", key_command, '\\', NULL}, {"/You/Conduct", NULL, ext_command, EXT_CMD_CONDUCT, NULL}, {"/Adventure", NULL, NULL, 0, ""}, {"/Adventure/ADtear1", NULL, NULL, 0, ""}, {"/Adventure/Name", "n", key_command, 'n' | 0x80, NULL}, {"/Adventure/Call", "C", key_command, 'C', NULL}, {"/Adventure/Adjust", "a", key_command, 'a' | 0x80, NULL}, {"/Action", NULL, NULL, 0, ""}, {"/Action/ACtear1", NULL, NULL, 0, ""}, {"/Action/Rest", "period", key_command, '.', NULL}, {"/Action/Search", "s", key_command, 's', NULL}, {"/Action/Eat", "e", key_command, 'e', NULL}, {"/Action/Asep1", NULL, NULL, 0, ""}, {"/Action/Open", "o", key_command, 'o', NULL}, {"/Action/Close", "c", key_command, 'c', NULL}, {"/Action/Asep2", NULL, NULL, 0, ""}, {"/Action/Pickup", "comma", key_command, ',', NULL}, {"/Action/Drop", "d", key_command, 'd', NULL}, {"/Action/Loot", "l", key_command, 'l' | 0x80, NULL}, {"/Action/Apply", "a", key_command, 'a', NULL}, {"/Action/Kick", "D", key_command, '\04', NULL}, {"/Action/Throw", "t", key_command, 't', NULL}, {"/Action/Fire", "f", key_command, 'f', NULL}, {"/Action/Asep3", NULL, NULL, 0, ""}, {"/Action/Drink", "q", key_command, 'q', NULL}, {"/Action/Read", "r", key_command, 'r', NULL}, {"/Action/Cast Spell", "Z", key_command, 'Z', NULL}, {"/Action/Zap", "z", key_command, 'z', NULL}, {"/Action/Dip", "d", key_command, 'd' | 0x80, NULL}, {"/Action/Sit", "s", key_command, 's' | 0x80, NULL}, {"/Religion", NULL, NULL, 0, ""}, {"/Religion/Rtear1", NULL, NULL, 0, ""}, {"/Religion/Pray", "p", key_command, 'p' | 0x80, NULL}, {"/Religion/Offer", "o", key_command, 'o' | 0x80, NULL}, {"/Special", NULL, NULL, 0, ""}, {"/Special/Stear1", NULL, NULL, 0, ""}, {"/Special/Engrave", "E", key_command, 'E', NULL}, {"/Special/Pay", "p", key_command, 'p', NULL}, {"/Special/Borrow", "b", key_command, 'b' | 0x80, NULL}, {"/Special/Chat", "c", key_command, 'c' | 0x80, NULL}, {"/Special/Enhance", "e", key_command, 'e' | 0x80, NULL}, {"/Special/Force", "f", key_command, 'f' | 0x80, NULL}, {"/Special/Invoke", "i", key_command, 'i' | 0x80, NULL}, {"/Special/Monster", "m", key_command, 'm' | 0x80, NULL}, {"/Special/Ride", NULL, ext_command, EXT_CMD_RIDE, NULL}, {"/Special/Rub", "r", key_command, 'r' | 0x80, NULL}, {"/Special/Wipe", "w", key_command, 'w' | 0x80, NULL}, {"/Special/Technique", "t", key_command, 't' | 0x80, NULL}, {"/Special/Teleport", "t", key_command, '', NULL}, {"/Special/Turn", NULL, ext_command, EXT_CMD_TURN, NULL}, {"/Special/Untrap", "u", key_command, 'u' | 0x80, NULL}, {"/Special/Jump", "j", key_command, 'j' | 0x80, NULL}, }; void win_GTK_init() { } /* * Non-modal dialog windows use a partial grab system whereby a number of * events which would normally be passed to other widgets are blocked, but * still allows scrolling etc. */ static gint nh_dialog_partial_grab(GtkWidget *widget, gpointer data) { boolean enable = !GPOINTER_TO_INT(data); #if GTK_CHECK_VERSION(1,3,12) if (enable) gtk_window_add_accel_group(GTK_WINDOW(main_window), accel_group); else gtk_window_remove_accel_group(GTK_WINDOW(main_window), accel_group); #else if (enable) gtk_accel_group_attach(accel_group, G_OBJECT(main_window)); else gtk_accel_group_detach(accel_group, G_OBJECT(main_window)); #endif nh_menu_sensitive("/Game", enable); nh_menu_sensitive("/Move", enable); nh_menu_sensitive("/Fight", enable); nh_menu_sensitive("/Check", enable); nh_menu_sensitive("/Equip", enable); nh_menu_sensitive("/You", enable); nh_menu_sensitive("/Adventure", enable); nh_menu_sensitive("/Action", enable); nh_menu_sensitive("/Religion", enable); nh_menu_sensitive("/Special", enable); nh_menu_sensitive("/Help", enable); return 0; } GtkWidget *nh_gtk_window_dialog(boolean is_modal) { GtkWidget *w; w = gtk_window_new(GTK_WINDOW_TOPLEVEL); if (w) { gtk_window_set_transient_for(GTK_WINDOW(w), GTK_WINDOW(main_window)); gtk_window_set_modal(GTK_WINDOW(w), is_modal); if (!is_modal) { nh_dialog_partial_grab(w, GINT_TO_POINTER(1)); gtk_signal_connect(GTK_OBJECT(w), "destroy", GTK_SIGNAL_FUNC(nh_dialog_partial_grab), 0); } #if GTK_CHECK_VERSION(1,3,2) gtk_window_set_destroy_with_parent(GTK_WINDOW(w), TRUE); #endif } return w; } GtkWidget * nh_gtk_new(GtkWidget *w, GtkWidget *parent, gchar *lbl) { #if 0 /* [ALI] Removed this code, it doesn't seem to achive anything */ gtk_widget_ref(w); gtk_object_set_data_full(GTK_OBJECT(parent), lbl, w, (GtkDestroyNotify)gtk_widget_unref); #endif gtk_widget_show(w); return w; } GtkWidget * nh_gtk_new_and_add(GtkWidget *w, GtkWidget *parent, gchar *lbl) { #if 0 gtk_widget_ref(w); gtk_object_set_data_full(GTK_OBJECT(parent), lbl, w, (GtkDestroyNotify)gtk_widget_unref); #endif gtk_widget_show(w); gtk_container_add(GTK_CONTAINER(parent), w); return w; } GtkWidget * nh_gtk_new_and_pack(GtkWidget *w, GtkWidget *parent, gchar *lbl, gboolean a1, gboolean a2, guint a3) { #if 0 gtk_widget_ref(w); gtk_object_set_data_full(GTK_OBJECT(parent), lbl, w, (GtkDestroyNotify)gtk_widget_unref); #endif gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(parent), w, a1, a2, a3); return w; } GtkWidget * nh_gtk_new_and_attach(GtkWidget *w, GtkWidget *parent, gchar *lbl, guint a1, guint a2, guint a3, guint a4) { #if 0 gtk_widget_ref(w); gtk_object_set_data_full(GTK_OBJECT(parent), lbl, w, (GtkDestroyNotify)gtk_widget_unref); #endif gtk_widget_show(w); gtk_table_attach_defaults(GTK_TABLE(parent), w, a1, a2, a3, a4); return w; } GtkWidget * nh_gtk_new_and_attach2(GtkWidget *w, GtkWidget *parent, gchar *lbl, guint a1, guint a2, guint a3, guint a4, GtkAttachOptions a5, GtkAttachOptions a6, guint a7, guint a8) { #if 0 gtk_widget_ref(w); gtk_object_set_data_full(GTK_OBJECT(parent), lbl, w, (GtkDestroyNotify)gtk_widget_unref); #endif gtk_widget_show(w); gtk_table_attach(GTK_TABLE(parent), w, a1, a2, a3, a4, a5, a6, a7, a8); return w; } int nh_keysym(GdkEventKey *ev) { int ret; switch(ev->keyval) { case GDK_Return: case GDK_KP_Enter: ret = '\n'; break; case GDK_Escape: ret = '\033'; break; default: if (ev->length) ret = ev->string[0]; else ret = 0; } return ret; } static void nh_menu_sensitive(char *menu, boolean f) { GtkWidget *p; p = gtk_item_factory_get_widget(main_item_factory, menu); gtk_widget_set_sensitive(p, f); } /* ALI * We need to know the border width and height so that we can correctly * clamp dialogs to the screen. Unfortunately, GDK allows us no way to * determine the border sizes before we map the window. We get around this * by remembering what they were for the last dialog displayed and * checking that they haven't changed after the window is mapped. If * we find we're offscreen after mapping we have to re-position the * window. Clumsy, but effective. * * Note: there's no way that I know of to determine the width of the * right border or the height of the bottom border. For now, we just * assume that these are both equal to the left border. Typically, * only the top border is different and this is a valid assumption. * * This is all further complicated by the fact that, at least under * the Enlightenment window manager, gtk_widget_set_uposition() sets * the position of the top left pixel of the border before the window * is mapped, but the top left pixel of the window after it is mapped. * * Note: If the window manager is set up such that gtk_widget_set_upostion() * always sets the position of the left left pixel of the window, and if * gtk_window_get_origin() returns this same position, then this code * will fail (gracefully) and dialogs may be positioned with their borders * just off-screen. */ static gint popup_dialog_bw = 0, popup_dialog_bh = 0; static void popup_dialog_mapped(GtkWidget *w, gpointer data) { gint screen_width, screen_height; GdkWindow *window = GTK_WIDGET(w)->window; GTimer *t; gulong ms; int abandon = 0; gint x, y, nx, ny; data = gtk_object_get_user_data(GTK_OBJECT(w)); if (data) { x = GPOINTER_TO_UINT(data) >> 16; y = GPOINTER_TO_UINT(data) & 0xffff; t = g_timer_new(); if (!t) return; /* Wait for Window Manager to place window and add borders */ g_timer_start(t); do { if (abandon) { g_timer_destroy(t); return; } (void)g_timer_elapsed(t, &ms); if (ms >= 500) abandon++; /* But only after one final check */ gdk_window_get_origin(window, &nx, &ny); } while (nx == 0 && ny == 0 || nx == x && ny == y); g_timer_destroy(t); popup_dialog_bw = nx - x; popup_dialog_bh = ny - y; /* Do we need to re-position this window to stay on-screen? */ screen_width = gdk_screen_width(); screen_height = gdk_screen_height(); if (nx + popup_dialog_bw + w->allocation.width > screen_width) x = screen_width - popup_dialog_bw - w->allocation.width; else x = nx; if (ny + popup_dialog_bw + w->allocation.height > screen_height) /* popup_dialog_bw because we assume bottom border same as left */ y = screen_height - popup_dialog_bw - w->allocation.height; else y = ny; if (x != nx || y != ny) gtk_widget_set_uposition(w, x, y); } } static void popup_dialog_realizing(GtkWidget *w, gpointer data) { GdkWindow *window = GTK_WIDGET(main_window)->window; gint x, y, width, height, ox, oy; gint screen_width = gdk_screen_width(), screen_height = gdk_screen_height(); if (w->allocation.width > screen_width || w->allocation.height > screen_height) { /* This is going to look bad anyway, we can't centre it because * a position less than 0 is not valid, so the best we can do * is place the pop-up at the top left of the screen and accept * that the right and/or bottom edge will be off-screen. */ x = y = 0; } else { gdk_window_get_geometry(window, &x, &y, &width, &height, NULL); gdk_window_get_origin(window, &ox, &oy); x += ox + (width - w->allocation.width) / 2; y += oy + (height - w->allocation.height) / 2; if (x + w->allocation.width + popup_dialog_bw > screen_width) x = screen_width - popup_dialog_bw - w->allocation.width; if (y + w->allocation.height + popup_dialog_bw > screen_height) y = screen_height - popup_dialog_bw - w->allocation.height; /* (x,y) is the desired position of the window, * adjust to take into account the expected border. */ x -= popup_dialog_bw; y -= popup_dialog_bh; if (x < 0) x = 0; if (y < 0) y = 0; } gtk_widget_set_uposition(w, x, y); gtk_object_set_user_data(GTK_OBJECT(w), GUINT_TO_POINTER(x << 16 | y)); } void nh_position_popup_dialog(GtkWidget *w) { if (GTK_WIDGET_REALIZED(w)) popup_dialog_realizing(w, 0); if (GTK_WIDGET_MAPPED(w)) popup_dialog_mapped(w, 0); gtk_signal_connect(GTK_OBJECT(w), "realize", GTK_SIGNAL_FUNC(popup_dialog_realizing), 0); gtk_signal_connect_after(GTK_OBJECT(w), "map", GTK_SIGNAL_FUNC(popup_dialog_mapped), 0); } static int session_window_dump(FILE *fp, int i, const char *tag) { fprintf(fp, "%s(%s): flags:", tag, session_window_info[i].name); if (session_window_info[i].flags & NH_SESSION_RESIZABLE) fprintf(fp, " resizable"); if (session_window_info[i].flags & NH_SESSION_USER_POS) fprintf(fp, " user-pos"); if (session_window_info[i].flags & NH_SESSION_USER_SIZE) fprintf(fp, " user-size"); if (session_window_info[i].flags & NH_SESSION_PLACED) fprintf(fp, " placed"); fputc('\n', fp); fprintf(fp, "bounding is %d x %d @ (%d, %d)\n", session_window_info[i].bounding.width, session_window_info[i].bounding.height, session_window_info[i].bounding.x, session_window_info[i].bounding.y); fprintf(fp, "requistion is %d x %d\n", session_window_info[i].requisition.width, session_window_info[i].requisition.height); fprintf(fp, "Offset is (%d, %d)\n", session_window_info[i].ox, session_window_info[i].oy); } static int session_window_configure_event(GtkWidget *widget, GdkEventConfigure *event, gpointer data) { int i = GPOINTER_TO_INT(data), j; GdkRectangle frame; #ifdef DEBUG_SESSION session_window_dump(stderr, i, "configure start"); fprintf(stderr, "event is %d x %d @ (%d, %d)\n", event->width, event->height, event->x, event->y); fprintf(stderr, "widget state is:"); if (GTK_WIDGET_MAPPED(widget)) fprintf(stderr, " mapped"); if (GTK_WIDGET_REALIZED(widget)) fprintf(stderr, " realized"); if (GTK_WIDGET_VISIBLE(widget)) fprintf(stderr, " visible"); fputc('\n', stderr); #endif if (session_window_info[i].flags & NH_SESSION_PLACED) { /* Check if user has re-positioned and/or re-sized window. */ if (session_window_info[i].bounding.x != event->x - session_window_info[i].ox || session_window_info[i].bounding.y != event->y - session_window_info[i].oy) session_window_info[i].flags |= NH_SESSION_USER_POS; if (session_window_info[i].requisition.width == event->width && session_window_info[i].requisition.height == event->height) /* Resized under program control */ session_window_info[i].flags &= ~NH_SESSION_USER_SIZE; else if (session_window_info[i].bounding.width != event->width || session_window_info[i].bounding.height != event->height) /* Resized under user control */ session_window_info[i].flags |= NH_SESSION_USER_SIZE; } else { /* Initial placement */ #ifdef WIN32 /* Under win32 Gtk+, we appear to get two configure events in * quick sucession. This is probably a bug in Gtk+ with the * first configure event representing the default window * geometry and the second event representing the geometry we * have requested. We cope with this by ignoring configure * events if we have requested a geometry and the offset is * implausible. */ if (session_window_info[i].flags & NH_SESSION_USER_POS) { int ox, oy; ox = event->x - session_window_info[i].bounding.x; oy = event->y - session_window_info[i].bounding.y; if (ox >= 0 && ox <= 8 && oy >= 0 && oy <= 48) { session_window_info[i].ox = ox; session_window_info[i].oy = oy; session_window_info[i].flags |= NH_SESSION_PLACED; } else { session_window_info[i].ox = 0; session_window_info[i].oy = 0; } } else { session_window_info[i].ox = 0; session_window_info[i].oy = 0; session_window_info[i].flags |= NH_SESSION_PLACED; } #else if (session_window_info[i].flags & NH_SESSION_USER_POS) { session_window_info[i].ox = event->x - session_window_info[i].bounding.x; session_window_info[i].oy = event->y - session_window_info[i].bounding.y; } else { /* Take a good guess */ for(j = SIZE(session_window_info) - 1; j >= 0; j--) if (j != i && session_window_info[j].ox > 0) break; if (j >= 0) { session_window_info[i].ox = session_window_info[j].ox; session_window_info[i].oy = session_window_info[j].oy; } else { gdk_window_get_frame_extents(widget->window, &frame); #ifdef DEBUG_SESSION fprintf(stderr, "frame is %d x %d @ (%d, %d)\n", frame.width, frame.height, frame.x, frame.y); #endif session_window_info[i].ox = event->x - frame.x; session_window_info[i].oy = event->y - frame.y; } } session_window_info[i].flags |= NH_SESSION_PLACED; #endif } if (session_window_info[i].flags & NH_SESSION_PLACED) { session_window_info[i].bounding.x = event->x - session_window_info[i].ox; session_window_info[i].bounding.y = event->y - session_window_info[i].oy; session_window_info[i].bounding.width = event->width; session_window_info[i].bounding.height = event->height; } #ifdef DEBUG_SESSION session_window_dump(stderr, i, "configure done"); #endif return FALSE; } static int session_window_size_request(GtkWidget *widget, GtkRequisition *requisition, gpointer data) { int i = GPOINTER_TO_INT(data); #ifdef DEBUG_SESSION session_window_dump(stderr, i, "size_request start"); #endif if (session_window_info[i].flags & NH_SESSION_PLACED) { session_window_info[i].bounding.width += requisition->width - session_window_info[i].requisition.width; session_window_info[i].bounding.height += requisition->height - session_window_info[i].requisition.height; } session_window_info[i].requisition = *requisition; #ifdef DEBUG_SESSION session_window_dump(stderr, i, "size_request done"); #endif } unsigned long nh_session_window_flags(const char *name) { int i; for(i = SIZE(session_window_info) - 1; i >= 0; i--) if (!strcmp(name, session_window_info[i].name)) return (unsigned long)session_window_info[i].flags; return 0; } GtkWidget * nh_session_window_new(const char *name) { int i; GtkWidget *w; char buf[64] = ""; for(i = SIZE(session_window_info) - 1; i >= 0; i--) if (!strcmp(name, session_window_info[i].name)) break; w = gtk_window_new(GTK_WINDOW_TOPLEVEL); if (i >= 0) { if (!(session_window_info[i].flags & NH_SESSION_RESIZABLE)) gtk_window_set_resizable(GTK_WINDOW(w), FALSE); if (session_window_info[i].flags & NH_SESSION_USER_SIZE) sprintf(buf, "%ux%u", session_window_info[i].bounding.width, session_window_info[i].bounding.height); if (session_window_info[i].flags & NH_SESSION_USER_POS) sprintf(eos(buf), "+%d+%d", session_window_info[i].bounding.x, session_window_info[i].bounding.y); if (*buf) { #ifdef DEBUG_SESSION fprintf(stderr, "parse_geometry(\"%s\")\n", buf); #endif gtk_window_parse_geometry(GTK_WINDOW(w), buf); } gtk_signal_connect(GTK_OBJECT(w), "configure_event", GTK_SIGNAL_FUNC(session_window_configure_event), GINT_TO_POINTER(i)); gtk_signal_connect(GTK_OBJECT(w), "size_request", GTK_SIGNAL_FUNC(session_window_size_request), GINT_TO_POINTER(i)); } #ifdef DEBUG_SESSION session_window_dump(stderr, i, "new"); #endif return w; } int nh_session_set_geometry(const char *name, int x, int y, int width, int height) { int i; for(i = 0; i < SIZE(session_window_info); i++) if (!strcmp(name, session_window_info[i].name)) { if (x >= 0 && y >= 0) { session_window_info[i].bounding.x = x; session_window_info[i].bounding.y = y; session_window_info[i].flags |= NH_SESSION_USER_POS; } if (session_window_info[i].flags & NH_SESSION_RESIZABLE && width >= 0 && height >= 0) { session_window_info[i].bounding.width = width; session_window_info[i].bounding.height = height; session_window_info[i].flags |= NH_SESSION_USER_SIZE; } return 0; } return -1; } int nh_session_save(struct gtkhackrc *rc) { int i; for(i = 0; i < SIZE(session_window_info); i++) { if (session_window_info[i].flags & NH_SESSION_USER_POS) nh_gtkhackrc_store(rc, "window.position(\"%s\") = {%d, %d}", session_window_info[i].name, session_window_info[i].bounding.x, session_window_info[i].bounding.y); if (session_window_info[i].flags & NH_SESSION_USER_SIZE) nh_gtkhackrc_store(rc, "window.size(\"%s\") = {%d, %d}", session_window_info[i].name, session_window_info[i].bounding.width - session_window_info[i].requisition.width, session_window_info[i].bounding.height - session_window_info[i].requisition.height); } } void quit_hook() { } static char nh_key_buffer[32]; static int nh_key_rp = 0; static int nh_key_wp = 0; void nh_key_add(char c) { int p; p = nh_key_wp + 1; if (p >= sizeof(nh_key_buffer)) p = 0; if (p != nh_key_rp) { nh_key_buffer[nh_key_wp] = c; nh_key_wp = p; } /* else buffer full */ } int nh_key_check() { return nh_key_rp != nh_key_wp; } int nh_key_get() { int c; if (nh_key_rp == nh_key_wp) c = -1; else { c = nh_key_buffer[nh_key_rp++]; if (nh_key_rp >= sizeof(nh_key_buffer)) nh_key_rp = 0; } return c; } /* * Do some housekeeping that needs doing on a regular basis * and run the Gtk+ main loop until either a key is available * or the provided watch is non-zero. * * Returns TRUE if returning only because user initiated exit. */ int main_hook(int *watch) { nh_map_check_visibility(); if (display_inventory_needed) GTK_display_inventory(); #ifdef WINGTK_RADAR if (!in_topten) nh_radar_update(); #endif while(!exiting && !nh_key_check() && (!watch || !*watch)) gtk_main_iteration(); return (!nh_key_check() && (!watch || !*watch)); } static void game_option(GtkWidget *widget, gpointer data) { nh_option_new(); if (display_inventory_needed) GTK_display_inventory(); } static void game_preferences(GtkWidget *widget, gpointer data) { static GtkWidget *GTK_prefs = NULL; if (GTK_prefs) gtk_window_present(GTK_WINDOW(GTK_prefs)); else { GTK_prefs = GTK_preferences_new(); gtk_signal_connect(GTK_OBJECT(GTK_prefs), "destroy", GTK_SIGNAL_FUNC(gtk_widget_destroyed), >K_prefs); } } static void game_topten(GtkWidget *widget, gpointer data) { #ifdef GTK_PROXY in_topten++; /* Prevent radar window appearing */ proxy_cb_display_score(); #else winid id; char *argv[] = { "nethack", "-sall", }; in_topten++; /* Prevent radar window appearing */ id = rawprint_win = create_toptenwin(); prscore(2, argv); GTK_display_nhwindow(id, TRUE); destroy_toptenwin(); dlb_init(); /* Re-initialise DLB */ rawprint_win = WIN_ERR; #endif in_topten--; } #ifdef GTKHACK static void game_troubleshooting(GtkWidget *widget, gpointer data) { static GtkWidget *GTK_troubleshooting = NULL; if (GTK_troubleshooting) gtk_window_present(GTK_WINDOW(GTK_troubleshooting)); else { GTK_troubleshooting = GTK_troubleshooting_new(); gtk_signal_connect(GTK_OBJECT(GTK_troubleshooting), "destroy", GTK_SIGNAL_FUNC(gtk_widget_destroyed), >K_troubleshooting); } } #endif static void help_license(GtkWidget *widget, gpointer data) { #ifdef GTK_PROXY if (!GTK_display_file("$(LICENSE)")) pline("Cannot display license. Sorry."); #else #ifndef FILE_AREAS display_file(NH_LICENSE, TRUE); #else display_file(NH_LICENSE_AREA, NH_LICENSE, TRUE); #endif #endif } static void help_history(GtkWidget *widget, gpointer data) { #ifdef GTK_PROXY if (!GTK_display_file("$(HISTORY)")) pline("Cannot display history. Sorry."); #else dohistory(); #endif } static void help_option(GtkWidget *widget, gpointer data) { #ifdef GTK_PROXY if (!GTK_display_file("$(OPTIONFILE)")) pline("Cannot display option help. Sorry."); #else #ifndef FILE_AREAS display_file(NH_OPTIONFILE, TRUE); #else display_file(NH_OPTIONAREA, NH_OPTIONFILE, TRUE); #endif #endif } static void help_shelp(GtkWidget *widget, gpointer data) { #ifdef GTK_PROXY if (!GTK_display_file("$(SHELP)")) pline("Cannot display short help. Sorry."); #else #ifndef FILE_AREAS display_file(NH_SHELP, TRUE); #else display_file(NH_SHELP_AREA, NH_SHELP, TRUE); #endif #endif } static void help_help(GtkWidget *widget, gpointer data) { #ifdef GTK_PROXY if (!GTK_display_file("$(HELP)")) pline("Cannot display command help. Sorry."); #else #ifndef FILE_AREAS display_file(NH_HELP, TRUE); #else display_file(NH_HELP_AREA, NH_HELP, TRUE); #endif #endif } static void key_command(GtkWidget *widget, gpointer data) { nh_key_add((int)data); quit_hook(); } static const int dir_keys[8][2] = { {'k','8'}, /* North */ {'l','6'}, /* East */ {'j','2'}, /* South */ {'h','4'}, /* West */ {'u','9'}, /* Northeast */ {'y','7'}, /* Northwest */ {'n','3'}, /* Southeast */ {'b','1'}, /* Southwest */ }; static void move_command(GtkWidget *widget, gpointer data) { nh_key_add('m'); nh_key_add(copts.num_pad ? dir_keys[(int)data][1] : dir_keys[(int)data][0]); quit_hook(); } static void fight_command(GtkWidget *widget, gpointer data) { nh_key_add('F'); nh_key_add(copts.num_pad ? dir_keys[(int)data][1] : dir_keys[(int)data][0]); quit_hook(); } static void ext_command(GtkWidget *widget, gpointer data) { nh_key_add('#'); GTK_extcmd_set((int)data); quit_hook(); } static gint main_window_delete(GtkWidget *widget, gpointer data) { exiting++; if (in_player_selection) gtk_main_quit(); return TRUE; } static void game_quit(GtkWidget *widget, gpointer data) { #ifdef GTK_PROXY proxy_cb_quit_game(); #else if (program_state.something_worth_saving) done2(); else { clearlocks(); GTK_exit_nhwindows(NULL); terminate(0); } #endif } static gint default_destroy(GtkWidget *widget, gpointer data) { GtkWindow **w = (GtkWindow **)data; *w = NULL; nh_key_add('\033'); quit_hook(); return FALSE; } int nh_dir_keysym(GdkEventKey *ev) { int ret; switch(ev->keyval) { case GDK_End: case GDK_KP_End: ret = dir_keys[GTK_SOUTHWEST][!!copts.num_pad]; break; case GDK_Down: case GDK_KP_Down: ret = dir_keys[GTK_SOUTH][!!copts.num_pad]; break; case GDK_Page_Down: case GDK_KP_Page_Down: ret = dir_keys[GTK_SOUTHEAST][!!copts.num_pad]; break; case GDK_Left: case GDK_KP_Left: ret = dir_keys[GTK_WEST][!!copts.num_pad]; break; case GDK_Begin: case GDK_KP_Begin: ret = '.'; break; case GDK_Right: case GDK_KP_Right: ret = dir_keys[GTK_EAST][!!copts.num_pad]; break; case GDK_Home: case GDK_KP_Home: ret = dir_keys[GTK_NORTHWEST][!!copts.num_pad]; break; case GDK_Up: case GDK_KP_Up: ret = dir_keys[GTK_NORTH][!!copts.num_pad]; break; case GDK_Page_Up: case GDK_KP_Page_Up: ret = dir_keys[GTK_NORTHEAST][!!copts.num_pad]; break; default: ret = 0; } return ret; } /* * Our model for focus (where key presses should go) is fairly simple. * As each window is opened that should receive key presses * we start a new layer of the hierarchy. Windows with slave * focus don't accept key presses themselves; instead they forward them * to another window. Such windows are considered to be at the same * level as the window to which they forward key presses (the master). * * We do not allow the user to select which window should have focus. * If an attempt is made we simply over-ride it. On the other hand, * users must be able to give the focus to a seperate application and * to return focus to the game when they choose. * * It is important to give the user as much feedback regarding focus * as possible. Ideally, we would like to keep our internal concept of * focus and the window manager's tied together but this causes a lot * of problems with slave windows (if a slave window receives the focus * and we attempt to pass it to the master window Gdk will raise the * master window at the same time. We can fudge this afterwards but it * looks very bad). As a compromise we allow the focus to remain on * slave windows and quietly forward the key presses. */ #define FOCUS_USE_FOCUS_KEYS 1 /* This window uses focus keys */ static struct focus_hierarchy { GtkWindow *master; GSList *slaves; gint (*handler)(GtkWidget *widget, GdkEventKey *event, gpointer data); gpointer data; struct focus_hierarchy *next; unsigned long flags; } *focus_top = NULL; static int focus_game = FALSE; /* Does any game window have focus? */ #ifdef DEBUG static gint focus_dump(void) { struct focus_hierarchy *fh; GSList *list; fprintf(stderr,"Focus hierarchy\n"); fprintf(stderr,"Master\t\tHandler\t\tSlaves\n"); for(fh = focus_top; fh; fh = fh->next) { fprintf(stderr,"%p\t%p\t",fh->master,fh->handler); if (fh->slaves) for(list = fh->slaves; list; ) { fprintf(stderr,"%p",list->data); list = g_slist_next(list); if (list) fputs(", ",stderr); } else fputs("",stderr); fputc('\n',stderr); } } #endif static gint focus_out(GtkWidget *widget, GdkEventFocus *event, gpointer data) { if (focus_game) focus_game--; } static gint focus_in(GtkWidget *widget, GdkEventFocus *event, gpointer data) { GtkWindow *w = GTK_WINDOW(widget); focus_game++; if (!focus_top || w == focus_top->master || g_slist_find(focus_top->slaves, w)) return 0; if (focus_top->master && GTK_WIDGET_VISIBLE(focus_top->master)) gtk_window_present(focus_top->master); return 1; } static gint focus_map(GtkWidget *widget, gpointer data) { if (focus_game && focus_top && focus_top->master == GTK_WINDOW(widget)) gdk_window_focus(widget->window, gtk_get_current_event_time()); return 0; } static gint focus_destroy(GtkWidget *widget, gpointer data) { GtkWindow *w = GTK_WINDOW(widget); GSList *list; struct focus_hierarchy *fh, *fhl = NULL; for(fh = focus_top; fh; fhl = fh, fh = fh->next) { if (fh->master == w) { /* This should only happen during game exit when a master window * is destroyed before one or more of its slaves. We mark this * special case by setting the master to NULL and hope to * clean-up when the last slave is destroyed. */ if (fh->slaves) fh->master = NULL; else { if (fhl) fhl->next = fh->next; else focus_top = fh->next; free(fh); } break; } else if (list = g_slist_find(fh->slaves, w)) { fh->slaves = g_slist_remove(fh->slaves, w); if (!fh->slaves && !fh->master) { /* Last slave destroyed of a previously destroyed master */ if (fhl) fhl->next = fh->next; else focus_top = fh->next; free(fh); } break; } } if (!fh) impossible("Destroying unknown focus window (%p)", w); else gtk_widget_unref(widget); } gint focus_key_press_early(GtkWidget *widget, GdkEventKey *event, gpointer data) { /* Certain keys should be intercepted before passing to the default * Gtk+ handler so that we can guarantee to receive them. For such * keys we call focus_key_press() ourselves thus bypassing the * default handler (should focus_key_press() not handle the event * it will be passed back to the default handler anyway). For other * keys we do nothing and allow the default handler to run. If this * handler doesn't deal with the key (eg., an accelerator) then * Gtk+ will call focus_key_press() in due course. */ if (focus_top && focus_top->master && !(focus_top->flags & FOCUS_USE_FOCUS_KEYS) && (event->keyval == GDK_Up || event->keyval == GDK_KP_Up || event->keyval == GDK_Down || event->keyval == GDK_KP_Down || event->keyval == GDK_Left || event->keyval == GDK_KP_Left || event->keyval == GDK_Right || event->keyval == GDK_KP_Right || event->keyval == GDK_Tab || event->keyval == GDK_ISO_Left_Tab)) return focus_key_press(widget, event, data); if (event->keyval == GDK_Escape || event->keyval == GDK_space || event->keyval == GDK_KP_0 || event->keyval == GDK_KP_1 || event->keyval == GDK_KP_2 || event->keyval == GDK_KP_3 || event->keyval == GDK_KP_4 || event->keyval == GDK_KP_5 || event->keyval == GDK_KP_6 || event->keyval == GDK_KP_7 || event->keyval == GDK_KP_8 || event->keyval == GDK_KP_9) return focus_key_press(widget, event, data); else return FALSE; } gint focus_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) { if (focus_top && focus_top->master) return focus_top->handler(GTK_WIDGET(focus_top->master), event, focus_top->data); else return GTK_default_key_press(widget, event, data); } static void focus_set_events(GtkWindow *w) { gtk_widget_ref(GTK_WIDGET(w)); gtk_signal_connect(GTK_OBJECT(w), "focus_in_event", GTK_SIGNAL_FUNC(focus_in), 0); gtk_signal_connect(GTK_OBJECT(w), "focus_out_event", GTK_SIGNAL_FUNC(focus_out), 0); gtk_signal_connect(GTK_OBJECT(w), "map_event", GTK_SIGNAL_FUNC(focus_map), 0); gtk_signal_connect(GTK_OBJECT(w), "destroy", GTK_SIGNAL_FUNC(focus_destroy), 0); gtk_signal_connect(GTK_OBJECT(w), "key_press_event", GTK_SIGNAL_FUNC(focus_key_press_early), 0); gtk_signal_connect_after(GTK_OBJECT(w), "key_press_event", GTK_SIGNAL_FUNC(focus_key_press), 0); } /* * Master windows may be configured to use the standard Gtk+ focus keys * (arrow keys, tab, left tab). If they do not use the focus keys then * the key presses will be passed on to the provided signal function. */ void nh_gtk_focus_set_master(GtkWindow *w, GtkSignalFunc func, gpointer data, gboolean use_focus_keys) { struct focus_hierarchy *fh; g_return_if_fail(w != NULL); fh = (struct focus_hierarchy *)alloc(sizeof(struct focus_hierarchy)); fh->master = w; fh->slaves = NULL; fh->handler = (void *)func; fh->data = data; fh->next = focus_top; fh->flags = use_focus_keys ? FOCUS_USE_FOCUS_KEYS : 0; focus_top = fh; focus_set_events(w); #ifdef DEBUG focus_dump(); #endif } void nh_gtk_focus_set_slave_for(GtkWindow *w,GtkWindow *slave_for) { struct focus_hierarchy *fh; g_return_if_fail(w != NULL); for(fh = focus_top; fh; fh = fh->next) { if (slave_for == fh->master) { fh->slaves = g_slist_prepend(fh->slaves, w); break; } } if (!fh) { impossible("Window focus slave for unknown window?"); return; } focus_set_events(w); #ifdef DEBUG focus_dump(); #endif } gint GTK_default_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) { int keysym; if (in_player_selection) return FALSE; keysym = nh_dir_keysym(event); if (!keysym) switch(event->keyval) { case GDK_Insert: case GDK_KP_Insert: keysym = 'i'; break; default: keysym = nh_keysym(event); } if (keysym) { nh_key_add(keysym); quit_hook(); return TRUE; } else return FALSE; } static gint default_button_press(GtkWidget *widget, GdkEventButton *event, gpointer data) { nh_key_add(data ? (int)data : '\n'); quit_hook(); return FALSE; } static gint credit_expose_event(GtkWidget *widget, GdkEventExpose *event) { gtk_main_quit(); return FALSE; } static void nh_rc(void) { int i; char *rc_file; gchar **files, **new_files; #if defined(GTK_PROXY) || !defined(FILE_AREAS) # ifdef UNIX rc_file = strdup("/etc/gtkhack/gtkrc"); # else rc_file = strdup("gtkrc"); # endif #else rc_file = make_file_name(FILE_AREA_SHARE, "gtkrc"); #endif files = gtk_rc_get_default_files(); for(i = 0; files[i]; i++) ; new_files = g_new(gchar *, i + 2); if (!new_files) return; new_files[0] = (gchar *)rc_file; for(i = 0; files[i]; i++) { new_files[i + 1] = g_strdup(files[i]); if (!new_files[i + 1]) { for(; i > 0; i--) g_free(new_files[i]); g_free(new_files); return; } } new_files[i + 1] = NULL; gtk_rc_set_default_files(new_files); for(i = 1; new_files[i]; i++) g_free(new_files[i]); g_free(new_files); free(rc_file); nh_read_gtkhackrc(); } /* * 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. */ struct { int role; int race; int gend; int align; } select_player_flags; /* Used to pass data to/from select_player */ #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) { select_player_flags.role = SELECT_KEY_ROLENUM(data); select_player_flags.race = SELECT_KEY_RACENUM(data); select_player_flags.gend = SELECT_KEY_GENDNUM(data); select_player_flags.align = SELECT_KEY_ALIGNNUM(data); gtk_main_quit(); } /* * 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; }; #ifdef GTK_PROXY #define valid_race(role, race) proxy_cb_valid_selection_check(role, race, -1, -1) static boolean valid_gend(int role, int race, int gend) { return proxy_cb_valid_selection_check(role, race, gend, -1); } static boolean valid_align(int role, int race, int align) { return proxy_cb_valid_selection_check(role, race, -1, align); } #define number_roles (player_choices->n_roles) #define number_races (player_choices->n_races) #define number_genders (player_choices->n_genders) #define number_aligns (player_choices->n_aligns) #define PLAYER_ROLE_M(i) (player_choices->roles[i].male) #define PLAYER_ROLE_F(i) (player_choices->roles[i].female) #define PLAYER_RACE(i) (player_choices->races[i]) #define PLAYER_GENDER(i) (player_choices->genders[i]) #define PLAYER_ALIGN(i) (player_choices->aligns[i]) #else /* GTK_PROXY */ #define valid_race validrace #define valid_gend validgend #define valid_align validalign static int number_roles, number_races; #define number_genders ROLE_GENDERS #define number_aligns ROLE_ALIGNS #define PLAYER_ROLE_M(i) (roles[i].name.m) #define PLAYER_ROLE_F(i) (roles[i].name.f) #define PLAYER_RACE(i) (races[i].noun) #define PLAYER_GENDER(i) (genders[i].adj) #define PLAYER_ALIGN(i) (aligns[i].adj) #endif /* GTK_PROXY */ /* * 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 gendalignnum); switch(level) { case 0: /* Role */ if (select_player_flags.role >= 0) return indx ? -1 : select_player_flags.role; for (i = 0; i < number_roles; i++) if (!indx--) return i; return -1; break; case 1: /* Race */ rolenum = SELECT_KEY_ROLENUM(key); if (select_player_flags.race >= 0 && (rolenum < 0 || valid_race(rolenum, select_player_flags.race))) return indx ? -1 : select_player_flags.race; for (i = 0; i < number_races; i++) if (rolenum < 0 || valid_race(rolenum, i)) if (!indx--) return i; return -1; break; case 3: /* Alignmnent */ /* FALL THROUGH */ case 2: /* Gender */ if (level == 2) { n = number_genders; valid = valid_gend; i = select_player_flags.gend; } else { n = number_aligns; valid = valid_align; i = select_player_flags.align; } 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; j < number_roles; j++) if (valid(j, racenum, i)) return indx ? -1 : i; } else if (racenum < 0) { for (j = 0; j < number_races; 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; j < number_roles; j++) if (valid(j, racenum, i)) { if (!indx--) return i; break; } } else if (racenum < 0) { for (i = 0; i < n; i++) for (j = 0; j < number_races; 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 + 2; } else { node->no_sons = no_opts; count = no_opts; } if (!level) count++; 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(PLAYER_ROLE_M(rolenum)); else len += 6; if (level > 1) if (racenum >= 0) len += strlen(PLAYER_RACE(racenum)); else len += 6; if (level > 2) if (gendnum >= 0) len += strlen(PLAYER_GENDER(gendnum)); else len += 6; if (level > 3) if (alignnum >= 0) len += strlen(PLAYER_ALIGN(alignnum)); 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, PLAYER_ROLE_M(rolenum))); } if (level > 1) { strcat(path, "/"); strcat(path, SELECT_STR(racenum, PLAYER_RACE(racenum))); } if (level > 2) { strcat(path, "/"); strcat(path, SELECT_STR(gendnum, PLAYER_GENDER(gendnum))); } if (level > 3) { strcat(path, "/"); strcat(path, SELECT_STR(alignnum, PLAYER_ALIGN(alignnum))); } 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 select_player_flags.foo 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 > 8 ? 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); } #ifdef DEBUG static void select_node_dump(struct select_node *node, int level) { int i; static int count; char buf[BUFSZ]; char *titles[] = { "Role", "Race", "Gender", "Alignment" }; if (level) { for(i = 0; i < level; i++) fputs(" ", stderr); sprintf(buf, "[%d] 0x%lX: ", count++, node->key); switch (level) { case 1: if (SELECT_KEY_ROLENUM(node->key) >= 0) fprintf(stderr, "%s%s", buf, PLAYER_ROLE_M(SELECT_KEY_ROLENUM(node->key))); else { fprintf(stderr, "[%d] ---\n", count - 1); for(i = 0; i < level; i++) fputs(" ", stderr); fprintf(stderr, "[%d] 0x%lX: random", count++, node->key); } break; case 2: if (SELECT_KEY_RACENUM(node->key) >= 0) fprintf(stderr, "%s%s", buf, PLAYER_RACE(SELECT_KEY_RACENUM(node->key))); else { fprintf(stderr, "[%d] ---\n", count - 1); for(i = 0; i < level; i++) fputs(" ", stderr); fprintf(stderr, "[%d] 0x%lX: random", count++, node->key); } break; case 3: if (SELECT_KEY_GENDNUM(node->key) >= 0) fprintf(stderr, "%s%s", buf, PLAYER_GENDER(SELECT_KEY_GENDNUM(node->key))); else { fprintf(stderr, "[%d] ---\n", count - 1); for(i = 0; i < level; i++) fputs(" ", stderr); fprintf(stderr, "[%d] 0x%lX: random", count++, node->key); } break; case 4: if (SELECT_KEY_ALIGNNUM(node->key) >= 0) fprintf(stderr, "%s%s", buf, PLAYER_ALIGN(SELECT_KEY_ALIGNNUM(node->key))); else { fprintf(stderr, "[%d] ---\n", count - 1); for(i = 0; i < level; i++) fputs(" ", stderr); fprintf(stderr, "[%d] 0x%lX: random", count++, node->key); } break; } fputc('\n', stderr); } else count = 0; if (node->no_sons) { for(i = 0; i < level; i++) fputs(" ", stderr); if (level) fprintf(stderr, "%s\n", titles[level]); else fprintf(stderr, "[%d] %s\n", count++, titles[0]); for(i = 0; i < node->no_sons; i++) select_node_dump(node->sons + i, level + 1); } } #endif /* * 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(boolean init) { int num_opts, i; struct select_node *root; if (!init) { /* Exit */ for (i = 0; i < nmenu_items; i++) { free(menu_items[i].path); free(menu_items[i].accelerator); } nmenu_items = 0; free(menu_items); menu_items = NULL; #ifdef GTK_PROXY proxy_cb_valid_selection_close(); if (player_choices) proxy_cb_free_player_choices(player_choices); player_choices = NULL; #endif return; } #ifdef GTK_PROXY player_choices = proxy_cb_get_player_choices(); proxy_cb_valid_selection_open(); #else for(number_roles = 0; roles[number_roles].name.m; number_roles++) ; for(number_races = 0; races[number_races].noun; number_races++) ; #endif root = (struct select_node *)alloc(sizeof(struct select_node)); root->key = 0; num_opts = select_node_fill(root, 0); #ifdef DEBUG select_node_dump(root, 0); #endif menu_items = (GtkItemFactoryEntry *)alloc(sizeof(GtkItemFactoryEntry) * num_opts); nmenu_items = num_opts; nmenu_items = select_node_traverse(root, 0, 0); if (nmenu_items != num_opts) panic("GTK: init_select_player: Expecting %d options, got %d", num_opts, nmenu_items); select_node_free(root); free(root); } /* If you want to change these you probably also want to change the * equivalent values in the non-proxified version of GTK_procs. */ static char *GTK_capv[] = { "color", "hilite_pet", #if 0 "ascii_map", "tiled_map", "splash_screen", "popup_dialog", "eight_bit_tty", #endif "perm_invent", (char *)0 }; #ifdef GTK_PROXY #define set_option_mod_status(option, status) \ proxy_cb_set_option_mod_status(option, status) #endif void GTK_init_gtk(int *argc, char **argv) { gtk_set_locale(); g_type_init(); nh_rc(); gtk_init(argc, &argv); } void GTK_init_nhwindows(char ***capvp) { char *credit_file; int i; GtkWidget *credit_window, *credit_vbox, *credit_credit, *main_hbox; GtkWidget *main_bar; GtkStyle *credit_style; GdkPixmap *credit_pixmap; GdkBitmap *credit_mask; GdkColormap *cmap; *capvp = GTK_capv; /* None of these options are supported in the GTK interface * and their display may be confusing. */ set_option_mod_status("extmenu", SET_IN_FILE); set_option_mod_status("msg_window", SET_IN_FILE); set_option_mod_status("menucolors", SET_IN_FILE); set_option_mod_status("standout", SET_IN_FILE); set_option_mod_status("timed_delay", SET_IN_FILE); nh_option_cache_set_bool_addr("color", &copts.use_color); nh_option_cache_set_bool_callback("color", nh_map_color_changed); nh_option_cache_set_bool_addr("hilite_pet", &copts.hilite_pet); copts.num_pad = nh_option_cache_get_bool("number_pad"); nh_option_cache_set_bool_callback("hilite_pet", nh_map_hilite_pet_changed); /* Init windows to nothing. */ for (i = 0; i < MAXWIN; i++) gtkWindows[i].type = NHW_NONE; /* * create credit widget and show */ credit_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_position(GTK_WINDOW(credit_window), GTK_WIN_POS_CENTER); gtk_container_border_width(GTK_CONTAINER(credit_window), 2); gtk_signal_connect(GTK_OBJECT(credit_window), "expose_event", GTK_SIGNAL_FUNC(credit_expose_event), NULL); gtk_widget_realize(credit_window); root_width = gdk_screen_width(); root_height = gdk_screen_height(); credit_vbox = nh_gtk_new_and_add(gtk_vbox_new(FALSE, 0), credit_window, ""); credit_style = gtk_widget_get_style(credit_window); #if defined(GTK_PROXY) || !defined(FILE_AREAS) # ifdef UNIX credit_file = "/usr/share/games/gtkhack/credit.xpm"; # else credit_file = "credit.xpm"; # endif #else credit_file = make_file_name(FILE_AREA_SHARE, "credit.xpm"); #endif credit_pixmap = gdk_pixmap_create_from_xpm(credit_window->window, &credit_mask, &credit_style->bg[GTK_STATE_NORMAL], credit_file); #if !defined(GTK_PROXY) && defined(FILE_AREAS) free(credit_file); #endif if (credit_pixmap) { credit_credit = nh_gtk_new_and_pack(gtk_pixmap_new(credit_pixmap, credit_mask), credit_vbox, "", FALSE, FALSE, NH_PAD); gdk_pixmap_unref(credit_pixmap); } gtk_widget_show_all(credit_window); gtk_main(); /* * create main widget */ main_window = nh_session_window_new("main"); if (!(nh_session_window_flags("main") & NH_SESSION_USER_SIZE)) gtk_window_set_default_size(GTK_WINDOW(main_window), min(1000, root_width - 50), min(700, root_height * 0.75)); nh_gtk_focus_set_master(GTK_WINDOW(main_window), GTK_SIGNAL_FUNC(GTK_default_key_press), 0, FALSE); gtk_signal_connect(GTK_OBJECT(main_window), "delete_event", GTK_SIGNAL_FUNC(main_window_delete), 0); gtk_window_set_title(GTK_WINDOW(main_window), DEF_GAME_NAME); gtk_widget_set_name(main_window, DEF_GAME_NAME); gtk_widget_set_events(main_window, GDK_KEY_PRESS_MASK); gtk_widget_realize(main_window); /* * allocate color */ cmap = gdk_window_get_colormap(main_window->window); for(i = 0; i < N_NH_COLORS; i++) if (!gdk_colormap_alloc_color(cmap, &nh_color[i], FALSE, TRUE)) fprintf(stderr, "cannot allocate color\n"); main_vbox = nh_gtk_new_and_add(gtk_vbox_new(FALSE, 0), main_window, ""); accel_group = gtk_accel_group_new(); #if GTK_CHECK_VERSION(1,3,12) gtk_window_add_accel_group(GTK_WINDOW(main_window), accel_group); #else gtk_accel_group_attach(accel_group, G_OBJECT(main_window)); #endif main_item_factory = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<main>", accel_group); main_bar = nh_gtk_new_and_pack( gtk_item_factory_get_widget(main_item_factory, "<main>"), main_vbox, "", FALSE, FALSE, 0); main_hbox = nh_gtk_new_and_pack(gtk_hbox_new(FALSE, 1), main_vbox, "", FALSE, FALSE, 0); gtk_item_factory_create_items(main_item_factory, SIZE(mainmenu_items), mainmenu_items, NULL); gtk_item_factory_create_items(main_item_factory, SIZE(playmenu_items), playmenu_items, NULL); gtk_item_factory_create_items(main_item_factory, SIZE(helpmenu_items), helpmenu_items, NULL); nh_menu_sensitive("/Game/Play", FALSE); nh_option_lock(TRUE); (void) nh_gtk_new_and_pack(nh_message_new(), main_hbox, "", TRUE, TRUE, 0); (void) nh_gtk_new_and_pack(nh_status_new(), main_hbox, "", FALSE, FALSE, 0); #ifdef WINGTK_RADAR main_radar = nh_radar_new(); nh_gtk_focus_set_slave_for(GTK_WINDOW(main_radar), GTK_WINDOW(main_window)); #endif (void) nh_gtk_new_and_pack(nh_map_new(main_window), main_vbox, "", TRUE, TRUE, 0); /* * Initialize standard windows. It used to be the case that window type * and id were equivalent (and therefore there could only be one type * of each window). We maintain this correlation for the standard * windows for compatibility -ALI */ gtkWindows[NHW_MESSAGE].type = NHW_MESSAGE; gtkWindows[NHW_MESSAGE].w = main_window; gtkWindows[NHW_STATUS].type = NHW_STATUS; gtkWindows[NHW_STATUS].w = main_window; gtkWindows[NHW_MAP].type = NHW_MAP; gtkWindows[NHW_MAP].w = main_window; #if !defined(GTKHACK) && defined(GTK_PROXY) /* * GtkHack doesn't have (or need) this variable. Conventional window * interfaces share it with the game core (which is responsible for * setting it). Proxified window interfaces, however, need to maintain * their own copy so that pline() can be called from the window interface * and still direct its output to the mesage window. */ WIN_MESSAGE = NHW_MESSAGE; #endif #ifdef GTK_PROXY proxy_cb_interface_mode(EXT_IM_STATUS|EXT_IM_DISPLAY_LAYERS); #else bot_set_handler(GTK_ext_status); #endif GTK_initialized = 1; gtk_widget_hide(credit_window); gtk_widget_destroy(credit_window); gtk_widget_show_all(main_window); } int GTK_ext_init_nhwindows(int *argc, char **argv, char ***capvp) { GTK_init_gtk(argc, argv); GTK_init_nhwindows(capvp); return TRUE; } #ifdef MONITOR_XRESOURCES static int GTK_X11_error_handler(Display *dpy, XErrorEvent *e) { return 0; } #endif void GTK_exit_nhwindows(const char *str) { int id; if (str && *str) { id = GTK_create_nhwindow(NHW_MENU); GTK_putstr(id, 0, str); GTK_display_nhwindow(id, TRUE); GTK_destroy_nhwindow(id); } init_select_player(FALSE); text_destroy_all(); for(id = 0; id < MAXWIN; id++) if (gtkWindows[id].type != NHW_NONE) GTK_destroy_nhwindow(id); nh_map_destroy(); nh_status_destroy(); #ifdef WINGTK_RADAR nh_radar_destroy(); #endif nh_message_destroy(); gtk_widget_destroy(main_window); g_blow_chunks(); #ifdef MONITOR_XRESOURCES /* ALI: An exceedingly unportable way of checking that * there are no X drawables still allocated at this point. */ { struct _XDisplay *dpy = (struct _XDisplay *)GDK_DISPLAY(); XID i; Drawable d; Window root; int x, y; unsigned int w, h, bw, dp; XWindowAttributes wa; char *type; fprintf(stderr, "X Drawables not freed:\n"); fprintf(stderr, "XID Type Width Height\n"); XSetErrorHandler(GTK_X11_error_handler); for(i = 0; i < dpy->resource_id; i++) { d = (Drawable)(dpy->resource_base + (i << dpy->resource_shift)); if (XGetGeometry(dpy, d, &root, &x, &y, &w, &h, &bw, &dp)) { if (x || y || bw) type = "Window"; else if (XGetWindowAttributes(dpy, (Window)d, &wa)) type = wa.screen ? "Window" : "Pixmap"; else type = "Pixmap"; fprintf(stderr, "%-10lX%-8s%-8u%u\n", (unsigned long)d, type, w, h); } } } #endif #if defined(GTK_PROXY) proxy_exit_client_services(); #endif } static GtkTextTagTable *text_tag_table; static void init_text_nhwindow(NHWindow *w) { GtkTextTag *tag; w->text_information = (struct text_info_t *)alloc(sizeof(*w->text_information)); md5_init(&w->text_information->md5_state); if (!text_tag_table) { text_tag_table = gtk_text_tag_table_new(); tag = gtk_text_tag_new("uline"); g_object_set(G_OBJECT(tag), "underline", PANGO_UNDERLINE_SINGLE, NULL); gtk_text_tag_table_add(text_tag_table, tag); g_object_unref(G_OBJECT(tag)); tag = gtk_text_tag_new("bold"); g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_BOLD, NULL); gtk_text_tag_table_add(text_tag_table, tag); g_object_unref(G_OBJECT(tag)); tag = gtk_text_tag_new("blink"); gtk_text_tag_table_add(text_tag_table, tag); g_object_unref(G_OBJECT(tag)); tag = gtk_text_tag_new("inverse"); g_object_set(G_OBJECT(tag), "foreground", "white", "background", "black", NULL); gtk_text_tag_table_add(text_tag_table, tag); g_object_unref(G_OBJECT(tag)); } w->text_information->buffer = gtk_text_buffer_new(text_tag_table); } winid GTK_create_nhwindow(int type) { int retval = -1; winid id; NHWindow *w; switch(type) { /* * these windows have already been created */ case NHW_MESSAGE: case NHW_STATUS: case NHW_MAP: if (gtkWindows[type].type != type) panic("GTK_create_nhwindow: standard window (%d) not valid", type); retval = type; break; /* * create new window */ case NHW_MENU: case NHW_TEXT: for (id = 0; id < MAXWIN; id++) { w = >kWindows[id]; if (w->type == NHW_NONE) { memset(w, 0, sizeof(NHWindow)); w->type = type; if (type == NHW_MENU) GTK_create_menu_window(w); retval = id; break; } } if (retval < 0) panic("GTK_create_nhwindow: no free windows!"); if (type == NHW_TEXT) init_text_nhwindow(w); break; default: panic("GTK_create_nhwindow: Unknown type (%d)!", type); } return retval; } void GTK_destroy_nhwindow(winid id) { NHWindow *w = >kWindows[id]; switch (w->type) { case NHW_STATUS: case NHW_MESSAGE: case NHW_MAP: return; case NHW_MENU: GTK_destroy_menu_window(w); break; case NHW_TEXT: if (w->w) { gtk_widget_hide_all(w->w); if(w->hid > 0) gtk_signal_disconnect(GTK_OBJECT(w->w), w->hid); gtk_widget_destroy(w->w); } free(w->text_information); break; } memset(w, 0, sizeof(NHWindow)); w->type = NHW_NONE; } /* * Used for close button on blocking text windows. */ static gint blocking_text_clicked(GtkWidget *widget, gpointer data) { nh_key_add('\033'); quit_hook(); return FALSE; } /* * Used for close button on non-blocking text windows. Don't affect the * game state (eg., by setting keysym) and don't call quit_hook. Instead * all we do is close the window. */ static gint text_clicked(GtkWidget *widget, gpointer data) { GtkWidget *window; window = GTK_WIDGET(data); gtk_widget_destroy(window); return FALSE; } /* * Used for destroy signal on non-blocking text windows. All we need to do * is remove the window from the linked list of such windows. */ static gint text_destroy(GtkWidget *widget, gpointer data) { struct nbtw *n = (struct nbtw *)data, *nn; if (n == non_blocking_text_windows) non_blocking_text_windows = n->next; else { for(nn = non_blocking_text_windows; nn; nn = nn->next) if (nn->next == n) { nn->next = n->next; break; } if (!nn) g_warning("Non-blocking text window not in linked list"); } free(n); } /* * Used to destroy all non-blocking text windows when main window is destroyed. */ static void text_destroy_all(void) { struct nbtw *n, *next; for(n = non_blocking_text_windows; n; n = next) { gtk_widget_destroy(n->w); next = n->next; free(n); } non_blocking_text_windows = NULL; } void GTK_display_nhwindow(winid id, BOOLEAN_P blocking) { NHWindow *w = >kWindows[id]; struct nbtw *n, *nbtw; int type = w->type; extern int root_height; GtkWidget *tv; switch(type) { case NHW_STATUS: case NHW_MESSAGE: return; /* We never block on status or message windows */ case NHW_MAP: /* flush out */ nh_map_flush(); break; case NHW_TEXT: /* * [ALI] This probably needs some tidying up. Menus will * already have the relevant signals connected. Text windows * are postponed until now. This allows us to treat blocking * and non-blocking windows differently. */ if (!blocking) { nbtw = (struct nbtw *) alloc(sizeof(struct nbtw)); md5_finish(&w->text_information->md5_state, nbtw->digest); /* See if there already exists a non-blocking text window * with the same contents and if so present it rather than * creating a duplicate window. */ for (n = non_blocking_text_windows; n; n = n->next) if (!memcmp(nbtw->digest, n->digest, sizeof(n->digest))) { gtk_window_present(GTK_WINDOW(n->w)); g_object_unref(w->text_information->buffer); w->text_information->buffer = NULL; free(nbtw); return; } nbtw->next = non_blocking_text_windows; non_blocking_text_windows = nbtw; nbtw->w = w->w = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_transient_for(GTK_WINDOW(w->w), GTK_WINDOW(main_window)); } else { w->w = nh_gtk_window_dialog(TRUE); #if GTK_CHECK_VERSION(1,3,2) gtk_window_set_destroy_with_parent(GTK_WINDOW(w->w), TRUE); #endif } gtk_widget_set_name(GTK_WIDGET(w->w), "fixed font"); nh_gtk_focus_set_slave_for(GTK_WINDOW(w->w), GTK_WINDOW(main_window)); w->frame = nh_gtk_new_and_add(gtk_frame_new(NULL), w->w, ""); w->vbox = nh_gtk_new_and_add(gtk_vbox_new(FALSE, 0), w->frame, ""); w->scrolled = nh_gtk_new_and_add(gtk_scrolled_window_new(NULL, NULL), w->vbox, ""); /* We switch to automatic after the window is first displayed. * This has the effect of sizing the window based on the contents. */ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w->scrolled), GTK_POLICY_NEVER, GTK_POLICY_NEVER); tv = gtk_text_view_new_with_buffer(w->text_information->buffer); g_object_unref(w->text_information->buffer); w->text_information->buffer = NULL; GTK_WIDGET_UNSET_FLAGS(tv, GTK_CAN_FOCUS); gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(tv), GTK_WRAP_NONE); gtk_text_view_set_editable(GTK_TEXT_VIEW(tv), FALSE); gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(tv), FALSE); gtk_container_add(GTK_CONTAINER(w->scrolled), tv); w->hbox = nh_gtk_new_and_pack(gtk_hbox_new(FALSE, 0), w->vbox, "", FALSE, FALSE, NH_PAD); w->button[0] = nh_gtk_new_and_pack( gtk_button_new_from_stock(GTK_STOCK_CLOSE), w->hbox, "", TRUE, FALSE, 0); GTK_WIDGET_SET_FLAGS(w->button[0], GTK_CAN_DEFAULT); gtk_widget_grab_default(w->button[0]); if (!blocking) { gtk_signal_connect(GTK_OBJECT(w->w), "destroy", GTK_SIGNAL_FUNC(text_destroy), nbtw); gtk_signal_connect(GTK_OBJECT(w->button[0]), "clicked", GTK_SIGNAL_FUNC(text_clicked), (gpointer)w->w); } else { w->hid = gtk_signal_connect(GTK_OBJECT(w->w), "destroy", GTK_SIGNAL_FUNC(default_destroy), &w->w); gtk_signal_connect(GTK_OBJECT(w->button[0]), "clicked", GTK_SIGNAL_FUNC(blocking_text_clicked), (gpointer)w->w); } w->flags |= NHWF_DISPLAYED; nh_position_popup_dialog(GTK_WIDGET(w->w)); gtk_widget_show_all(w->w); while(gtk_events_pending()) gtk_main_iteration(); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w->scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); nh_position_popup_dialog(GTK_WIDGET(w->w)); if (!blocking) w->w = NULL; /* Gtk window now independant */ break; case NHW_MENU: if (w->clist && w->clist->requisition.height >= (2 * root_height) / 3) gtk_widget_set_usize(w->clist, -1, (2 * root_height) / 3); blocking = TRUE; /* Menus always block */ #if 0 if (blocking) gtk_grab_add(w->w); #endif w->flags |= NHWF_DISPLAYED; gtk_widget_show_all(w->w); break; } if (blocking && !main_hook(NULL)) (void) nh_key_get(); } void GTK_ext_clear_nhwindow(winid id, int rows, int cols, int layers) { if (gtkWindows[id].type == NHW_MAP) nh_map_clear(rows, cols, layers); } void GTK_putstr(winid id, int attr, const char *str) { const gchar *text[1]; NHWindow *w = >kWindows[id]; int len; char *buf, *tag; GtkTextIter iter; GtkTextBuffer *t; switch (w->type) { case NHW_MESSAGE: nh_message_putstr(str); break; case NHW_STATUS: case NHW_MAP: panic("bad window"); break; case NHW_MENU: /* We don't treat windows of type NHW_MENU any differently from * NHW_TEXT windows once putstr() has been called and it makes * it easier if all text windows have type NHW_TEXT. */ GTK_destroy_menu_window(w); w->type = NHW_TEXT; init_text_nhwindow(w); /* Fall through */ case NHW_TEXT: len = strlen(str); md5_append(&w->text_information->md5_state, str, len); t = w->text_information->buffer; gtk_text_buffer_get_end_iter(t, &iter); if (gtk_text_buffer_get_char_count(t) > 0) { buf = (char *)alloc(++len + 1); sprintf(buf, "\n%s", str); } else buf = (char *)str; if (attr == ATR_NONE) gtk_text_buffer_insert(t, &iter, buf, len); else { if (attr == ATR_ULINE) tag = "uline"; else if (attr == ATR_BOLD) tag = "bold"; else if (attr == ATR_BLINK) tag = "blink"; else tag = "inverse"; gtk_text_buffer_insert_with_tags_by_name(t, &iter, buf, len, tag, NULL); } if (buf != str) free(buf); } } void GTK_get_nh_event() { return; } int GTK_nhgetch(void) { for(;;) { #ifdef WINGTK_RADAR nh_radar_update(); #endif if (!main_hook(NULL)) return nh_key_get(); else { exiting = 0; game_quit(main_window, 0); } } } int GTK_nh_poskey(int *x, int *y, int *mod) { extern int cursm; if (exiting) { exiting = 0; game_quit(main_window, 0); } for(;;) { #ifdef WINGTK_RADAR nh_radar_update(); #endif nh_map_click(TRUE); cursm = 0; if (!main_hook(&cursm)) { nh_map_pos(x, y, mod); nh_map_click(FALSE); return nh_key_check() ? nh_key_get() : 0; } else { exiting = 0; game_quit(main_window, 0); } } } #ifdef GTK_PROXY static char *nh_proxy_cache_defaultdir = #ifdef WIN32 "cache"; #else "$(HOME)/.gtkhack/cache"; #endif static char *nh_proxy_cache_dir = NULL; static int nh_proxy_cache_dir_set = FALSE; /* * Setting the default cache directory (by passing dir as NULL) * has the side effect of creating the directory. If a non-default * directory is used, then it should already exist (if not, then * the cache will never be utilized). */ void nh_proxy_cache_set_dir(const char *dir) { if (nh_proxy_cache_dir) g_free(nh_proxy_cache_dir); nh_proxy_cache_dir_set = !!dir; if (dir) nh_proxy_cache_dir = g_strdup(dir); else { #ifdef WIN32 nh_proxy_cache_dir = g_strdup("cache"); CreateDirectory("cache", NULL); #else char *home = getenv("HOME"); if (!home) home = "/tmp"; nh_proxy_cache_dir = g_strdup_printf("%s/.gtkhack", home); mkdir(nh_proxy_cache_dir, 0777); g_free(nh_proxy_cache_dir); nh_proxy_cache_dir = g_strdup_printf("%s/.gtkhack/cache", home); mkdir(nh_proxy_cache_dir, 0777); #endif } } int nh_proxy_cache_save(struct gtkhackrc *rc) { if (nh_proxy_cache_dir_set) nh_gtkhackrc_store(rc, "proxy.cachedir = \"%s\"", nh_proxy_cache_dir); } char *nh_proxy_cache_file(const char *class, const char *name) { char *path; if (!nh_proxy_cache_dir) nh_proxy_cache_set_dir(NULL); #ifdef WIN32 path = g_strdup_printf("%s\\%s\\%s", nh_proxy_cache_dir, class, name); #else path = g_strdup_printf("%s/%s/%s", nh_proxy_cache_dir, class, name); #endif return path; } void nh_proxy_cache_mkdir(const char *class, const char *name) { char *path; if (!nh_proxy_cache_dir) nh_proxy_cache_set_dir(NULL); #ifdef WIN32 path = g_strdup_printf("%s\\%s", nh_proxy_cache_dir, class); CreateDirectory(path, NULL); #else path = g_strdup_printf("%s/%s", nh_proxy_cache_dir, class); mkdir(path, 0777); #endif g_free(path); } static char *nh_proxy_game_cache_subdir = NULL; /* Take a NhExt sub-protocol 0 value (up to 64 printable ASCII characters * (space to "~") and produce a legal filename. */ char *GTK_regularize(const char *value) { /* * Based on win32 (UNIX is more lenient). Illegal characters are replaced * by a two character code starting with "!" (with "!!" representing "!") * as follows: * / !f (forward slash) * \ !b (backward slash) * < !l (less than) * > !g (greater than) * : !c (colon) * " !q (quote) * | !p (pipe) */ int i, j; char fname[129]; for(i = j = 0; value[i]; i++) switch (value[i]) { case '!': fname[j++] = '!'; fname[j++] = '!'; break; case '/': fname[j++] = '!'; fname[j++] = 'f'; break; case '\\': fname[j++] = '!'; fname[j++] = 'b'; break; case '<': fname[j++] = '!'; fname[j++] = 'l'; break; case '>': fname[j++] = '!'; fname[j++] = 'g'; break; case ':': fname[j++] = '!'; fname[j++] = 'c'; break; case '"': fname[j++] = '!'; fname[j++] = 'q'; break; case '|': fname[j++] = '!'; fname[j++] = 'p'; break; default: fname[j++] = value[i]; break; } fname[j] = '\0'; return strdup(fname); } void nh_proxy_game_cache_set_dir(void) { char *game, *version, *dir; #ifdef WIN32 const char sep = '\\'; #else const char sep = '/'; #endif if (!nh_proxy_cache_dir) nh_proxy_cache_set_dir(NULL); if (nh_proxy_game_cache_subdir) free(nh_proxy_game_cache_subdir); game = GTK_regularize(win_proxy_clnt_gettag("game")); version = GTK_regularize(win_proxy_clnt_gettag("version")); nh_proxy_game_cache_subdir = g_strdup_printf("%s%c%s", game, sep, version); dir = g_strdup_printf("%s%c%s", nh_proxy_cache_dir, sep, game); #ifdef WIN32 CreateDirectory(dir, NULL); #else mkdir(dir, 0777); #endif g_free(dir); dir = g_strdup_printf("%s%c%s", nh_proxy_cache_dir, sep, nh_proxy_game_cache_subdir); #ifdef WIN32 CreateDirectory(dir, NULL); #else mkdir(dir, 0777); #endif g_free(dir); free(game); free(version); } char *nh_proxy_game_cache_file(const char *name) { if (!nh_proxy_game_cache_subdir) nh_proxy_game_cache_set_dir(); return nh_proxy_cache_file(nh_proxy_game_cache_subdir, name); } static int GTK_fp_read(void *handle, void *buf, unsigned int len) { int nb; nb = fread(buf, 1, len, (FILE *)handle); return nb >= 0 ? nb : -1; } static int GTK_fp_write(void *handle, void *buf, unsigned int len) { int nb; nb = fwrite(buf, 1, len, (FILE *)handle); return nb >= 0 ? nb : -1; } static GHashTable *GTK_io_hash = NULL; NhExtIO *GTK_io_open(const char *filename, unsigned int flags) { FILE *fp; NhExtIO *io; if (!GTK_io_hash) { GTK_io_hash = g_hash_table_new(NULL, NULL); if (!GTK_io_hash) return NULL; } if (flags & NHEXT_IO_WRONLY) { fp = fopen(filename, "wb"); if (fp) io = nhext_io_open(GTK_fp_write, (void *)fp, flags); else return NULL; } else { fp = fopen(filename, "rb"); if (fp) io = nhext_io_open(GTK_fp_read, (void *)fp, flags); else return NULL; } if (!io) { fclose(fp); if (!g_hash_table_size(GTK_io_hash)) { g_hash_table_destroy(GTK_io_hash); GTK_io_hash = NULL; } } else g_hash_table_insert(GTK_io_hash, (gpointer)io, (gpointer)fp); return io; } int GTK_io_close(NhExtIO *io) { int retval; FILE *fp; if (!GTK_io_hash) return -1; fp = (FILE *)g_hash_table_lookup(GTK_io_hash, io); if (!fp) return -1; retval = nhext_io_close(io); retval |= fclose(fp); g_hash_table_remove(GTK_io_hash, io); if (!g_hash_table_size(GTK_io_hash)) { g_hash_table_destroy(GTK_io_hash); GTK_io_hash = NULL; } return retval; } static GHashTable *GTK_dlbh_md5sum_hash = NULL; char * nh_proxy_cache_dlbh_fmd5sum(const char *name) { char *md5sum = NULL; if (!GTK_dlbh_md5sum_hash) GTK_dlbh_md5sum_hash = g_hash_table_new(g_str_hash, g_str_equal); else { md5sum = g_hash_table_lookup(GTK_dlbh_md5sum_hash, name); if (md5sum) md5sum = strdup(md5sum); } if (!md5sum) { md5sum = proxy_cb_dlbh_fmd5sum(name); if (GTK_dlbh_md5sum_hash) g_hash_table_insert(GTK_dlbh_md5sum_hash, strdup(name), strdup(md5sum)); } return md5sum; } static int GTK_display_file(char *name) { int fh; fh = proxy_cb_dlbh_fopen(name, "r"); if (fh < 0) return FALSE; GTK_ext_display_file(fh); proxy_cb_dlbh_fclose(fh); return TRUE; } #define NH_DLBH_PROXY_FREE 1 #define NH_DLBH_PROXY_DATACACHED 2 struct nh_dlbh_proxy { int fh; FILE *cache; char *cname; long read_ahead; char *buffer; unsigned long flags; }; static struct nh_dlbh_proxy *nh_dlbh_nodes = NULL; static int n_nh_dlbh_nodes = 0; static int nh_dlbh_proxy_new() { int i; for(i = 0; i < n_nh_dlbh_nodes; i++) if (nh_dlbh_nodes[i].flags & NH_DLBH_PROXY_FREE) { memset(nh_dlbh_nodes + i, 0, sizeof(*nh_dlbh_nodes)); return i; } ++n_nh_dlbh_nodes; nh_dlbh_nodes = g_renew(struct nh_dlbh_proxy, nh_dlbh_nodes, n_nh_dlbh_nodes); memset(nh_dlbh_nodes + n_nh_dlbh_nodes - 1, 0, sizeof(*nh_dlbh_nodes)); return n_nh_dlbh_nodes - 1; } static void nh_dlbh_proxy_free(int n) { int i; for(i = n + 1; i < n_nh_dlbh_nodes; i++) if (!(nh_dlbh_nodes[i].flags & NH_DLBH_PROXY_FREE)) { nh_dlbh_nodes[n].flags |= NH_DLBH_PROXY_FREE; return; } for(i = n - 1; i >= 0; i--) if (!(nh_dlbh_nodes[i].flags & NH_DLBH_PROXY_FREE)) { n_nh_dlbh_nodes = i + 1; return; } n_nh_dlbh_nodes = 0; g_free(nh_dlbh_nodes); nh_dlbh_nodes = NULL; } #endif /* GTK_PROXY */ int nh_dlbh_fopen(const char *class, const char *name, const char *mode) { #ifdef GTK_PROXY int fh; char *md5sum; md5sum = nh_proxy_cache_dlbh_fmd5sum(name); if (!md5sum) return -1; fh = nh_dlbh_proxy_new(); nh_dlbh_nodes[fh].cname = nh_proxy_cache_file(class, md5sum); nh_dlbh_nodes[fh].cache = fopen(nh_dlbh_nodes[fh].cname, mode); if (nh_dlbh_nodes[fh].cache) { g_free(md5sum); nh_dlbh_nodes[fh].fh = -1; return fh; } nh_dlbh_nodes[fh].fh = proxy_cb_dlbh_fopen(name, mode); if (nh_dlbh_nodes[fh].fh >= 0) { nh_dlbh_nodes[fh].cache = fopen(nh_dlbh_nodes[fh].cname, "wb"); if (!nh_dlbh_nodes[fh].cache) { nh_proxy_cache_mkdir(class, md5sum); nh_dlbh_nodes[fh].cache = fopen(nh_dlbh_nodes[fh].cname, "wb"); } if (!nh_dlbh_nodes[fh].cache) { g_free(nh_dlbh_nodes[fh].cname); nh_dlbh_nodes[fh].cname = NULL; } g_free(md5sum); return fh; } g_free(nh_dlbh_nodes[fh].cname); g_free(md5sum); nh_dlbh_proxy_free(fh); return -1; #else # ifdef FILE_AREAS return dlbh_fopen(FILE_AREA_SHARE, name, mode); # else return dlbh_fopen(name, mode); # endif #endif } int nh_dlbh_fclose(int fh) { #ifdef GTK_PROXY int retval = 0; char *buf; if (nh_dlbh_nodes[fh].fh >= 0) { if (nh_dlbh_nodes[fh].cache) { if (nh_dlbh_nodes[fh].flags & NH_DLBH_PROXY_DATACACHED) { /* Caller closed file before EOF. To ensure the cached copy * is valid we must finish reading the file. nh_dlbh_fread() * will close the cache file on completion or error. */ buf = g_malloc(1024); while(nh_dlbh_nodes[fh].cache) (void) nh_dlbh_fread(buf, 1, 1024, fh); g_free(buf); } else { /* No data cached (typically used to check file is available * for reading). Discard the empty cache file. */ fclose(nh_dlbh_nodes[fh].cache); nh_dlbh_nodes[fh].cache = NULL; remove(nh_dlbh_nodes[fh].cname); g_free(nh_dlbh_nodes[fh].cname); nh_dlbh_nodes[fh].cname = NULL; } } retval = proxy_cb_dlbh_fclose(nh_dlbh_nodes[fh].fh); } if (nh_dlbh_nodes[fh].cache) fclose(nh_dlbh_nodes[fh].cache); if (nh_dlbh_nodes[fh].cname) g_free(nh_dlbh_nodes[fh].cname); nh_dlbh_proxy_free(fh); return retval; #else return dlbh_fclose(fh); #endif } char *nh_dlbh_fgets(char *buf, int len, int fh) { #ifdef GTK_PROXY int nr; /* Number of bytes read */ char *s; if (nh_dlbh_nodes[fh].fh >= 0) { if (!nh_dlbh_nodes[fh].cache) return proxy_cb_dlbh_fgets(buf, len, nh_dlbh_nodes[fh].fh); if (nh_dlbh_nodes[fh].read_ahead > 0) { s = memchr(nh_dlbh_nodes[fh].buffer, '\n', nh_dlbh_nodes[fh].read_ahead); if (s) { nr = min(len - 1, s - nh_dlbh_nodes[fh].buffer + 1); memcpy(buf, nh_dlbh_nodes[fh].buffer, nr); buf[nr] = '\0'; nh_dlbh_nodes[fh].read_ahead -= nr; if (nh_dlbh_nodes[fh].read_ahead) memmove(nh_dlbh_nodes[fh].buffer, nh_dlbh_nodes[fh].buffer + nr, nh_dlbh_nodes[fh].read_ahead); else { g_free(nh_dlbh_nodes[fh].buffer); nh_dlbh_nodes[fh].buffer = NULL; } return buf; } nr = nh_dlbh_nodes[fh].read_ahead; memcpy(buf, nh_dlbh_nodes[fh].buffer, nr); nh_dlbh_nodes[fh].read_ahead = 0; g_free(nh_dlbh_nodes[fh].buffer); nh_dlbh_nodes[fh].buffer = NULL; } else nr = 0; s = proxy_cb_dlbh_fgets(buf + nr, len - nr, nh_dlbh_nodes[fh].fh); if (!s) { /* * On EOF/ERROR, finish up the cache file * (which we assume is now complete). */ fclose(nh_dlbh_nodes[fh].cache); nh_dlbh_nodes[fh].cache = NULL; g_free(nh_dlbh_nodes[fh].cname); nh_dlbh_nodes[fh].cname = NULL; } else { if (fputs(buf + nr, nh_dlbh_nodes[fh].cache) < 0) { /* Write error occured - discard cached copy */ fclose(nh_dlbh_nodes[fh].cache); nh_dlbh_nodes[fh].cache = NULL; remove(nh_dlbh_nodes[fh].cname); g_free(nh_dlbh_nodes[fh].cname); nh_dlbh_nodes[fh].cname = NULL; } nh_dlbh_nodes[fh].flags |= NH_DLBH_PROXY_DATACACHED; } return s ? buf : NULL; } else return fgets(buf, len, nh_dlbh_nodes[fh].cache); #else return dlbh_fgets(buf, len, fh); #endif } int nh_dlbh_fread(char *buf, int size, int quan, int fh) { #ifdef GTK_PROXY int retval; int nb; /* Number of bytes still to read */ int nr; /* Number of bytes read */ char *buffer; if (nh_dlbh_nodes[fh].fh >= 0) { if (!nh_dlbh_nodes[fh].cache) return proxy_cb_dlbh_fread(buf, size, quan, nh_dlbh_nodes[fh].fh); /* * Note: proxy_cb_dlbh_fread() may discard bytes on EOF if size is * greater than 1, so we have to take care never to call it with * size > 1 if we are caching a copy. */ nb = size * quan; nr = 0; if (nh_dlbh_nodes[fh].read_ahead > 0) { nr = min(nh_dlbh_nodes[fh].read_ahead, nb); memcpy(buf, nh_dlbh_nodes[fh].buffer, nr); nb -= nr; nh_dlbh_nodes[fh].read_ahead -= nr; if (nh_dlbh_nodes[fh].read_ahead) memmove(nh_dlbh_nodes[fh].buffer, nh_dlbh_nodes[fh].buffer + nr, nh_dlbh_nodes[fh].read_ahead); else { g_free(nh_dlbh_nodes[fh].buffer); nh_dlbh_nodes[fh].buffer = NULL; } } /* At this point, either nb or read_ahead must be zero */ while (nb) { retval = proxy_cb_dlbh_fread(buf + nr, 1, nb, nh_dlbh_nodes[fh].fh); if (retval < 0) { /* * If we get a read error, then discard the cache file and * return an error immediately. * Note: We could do better than this by returning what * data we do have now and setting a flag so that an error * can be returned later, but there seems little point. */ fclose(nh_dlbh_nodes[fh].cache); nh_dlbh_nodes[fh].cache = NULL; remove(nh_dlbh_nodes[fh].cname); g_free(nh_dlbh_nodes[fh].cname); nh_dlbh_nodes[fh].cname = NULL; return -1; } else if (!retval) { /* * On EOF, finish up the cache file (which is now complete), * save any part objects in the read-ahead buffer and return * the number of complete objects read. */ fclose(nh_dlbh_nodes[fh].cache); nh_dlbh_nodes[fh].cache = NULL; g_free(nh_dlbh_nodes[fh].cname); nh_dlbh_nodes[fh].cname = NULL; nh_dlbh_nodes[fh].read_ahead = nr % size; if (nh_dlbh_nodes[fh].read_ahead) { nh_dlbh_nodes[fh].buffer = g_malloc(nh_dlbh_nodes[fh].read_ahead); memcpy(nh_dlbh_nodes[fh].buffer, buf + nr - nh_dlbh_nodes[fh].read_ahead, nh_dlbh_nodes[fh].read_ahead); } return nr / size; } else { if (fwrite(buf + nr, 1, retval, nh_dlbh_nodes[fh].cache) != retval) { /* Write error occured - discard cached copy */ fclose(nh_dlbh_nodes[fh].cache); nh_dlbh_nodes[fh].cache = NULL; remove(nh_dlbh_nodes[fh].cname); g_free(nh_dlbh_nodes[fh].cname); nh_dlbh_nodes[fh].cname = NULL; } nr += retval; nb -= retval; nh_dlbh_nodes[fh].flags |= NH_DLBH_PROXY_DATACACHED; } } /* All requested data read */ return quan; } else return fread(buf, size, quan, nh_dlbh_nodes[fh].cache); #else return dlbh_fread(buf, size, quan, fh); #endif } int nh_dlbh_fseek(int fh, long pos, int whence) { #ifdef GTK_PROXY if (nh_dlbh_nodes[fh].fh >= 0) return -1; else return fseek(nh_dlbh_nodes[fh].cache, pos, whence); #else return dlbh_fseek(fh, pos, whence); #endif } int nh_dlbh_ftell(int fh) { #ifdef GTK_PROXY if (nh_dlbh_nodes[fh].fh >= 0) return -1; else return ftell(nh_dlbh_nodes[fh].cache); #else return dlbh_ftell(fh); #endif } #if GTK_CHECK_VERSION(2,0,0) # define USE_TEXTVIEW #endif int nh_set_help_font(gchar *name) { if (help_font_name) g_free(help_font_name); help_font_name = g_strdup(name); return 0; } int nh_help_save(struct gtkhackrc *rc) { if (help_font_name) nh_gtkhackrc_store(rc, "help.font = \"%s\"", help_font_name); } #ifdef USE_TEXTVIEW static void GTK_ext_display_file_setfont_apply(GtkButton *button, GtkFontSelectionDialog *fsd) { GtkTextBuffer *buffer; GtkTextTagTable *tagtable; GtkTextTag *tag; GtkTextIter start, end; if (help_font_name) g_free(help_font_name); help_font_name = gtk_font_selection_dialog_get_font_name(fsd); if (help_font_name) { buffer = GTK_TEXT_BUFFER(g_object_get_data(G_OBJECT(fsd), "display-file-buffer")); tagtable = gtk_text_buffer_get_tag_table(buffer); tag = gtk_text_tag_table_lookup(tagtable, "body"); g_object_set(tag, "font", help_font_name, NULL); gtk_text_buffer_get_bounds(buffer, &start, &end); gtk_text_buffer_apply_tag(buffer, tag, &start, &end); } } static void GTK_ext_display_file_setfont(GtkButton *button, GtkTextBuffer *buffer) { static GtkWidget *SetFont = NULL; if (SetFont) gtk_window_present(GTK_WINDOW(SetFont)); else { SetFont = gtk_font_selection_dialog_new("Set font for documentation"); gtk_font_selection_dialog_set_font_name( GTK_FONT_SELECTION_DIALOG(SetFont), help_font_name); g_object_ref(G_OBJECT(buffer)); g_object_set_data_full(G_OBJECT(SetFont), "display-file-buffer", buffer, g_object_unref); gtk_widget_show(GTK_FONT_SELECTION_DIALOG(SetFont)->apply_button); g_signal_connect( GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(SetFont)->apply_button), "clicked", G_CALLBACK(GTK_ext_display_file_setfont_apply), SetFont); g_signal_connect( GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(SetFont)->ok_button), "clicked", G_CALLBACK(GTK_ext_display_file_setfont_apply), SetFont); g_signal_connect_swapped( GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(SetFont)->ok_button), "clicked", G_CALLBACK(gtk_widget_destroy), SetFont); g_signal_connect_swapped( GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(SetFont)->cancel_button), "clicked", G_CALLBACK(gtk_widget_destroy), SetFont); gtk_widget_show(SetFont); gtk_signal_connect(GTK_OBJECT(SetFont), "destroy", GTK_SIGNAL_FUNC(gtk_widget_destroyed), &SetFont); } } #endif void GTK_ext_display_file(int fh) { guint hid; GtkWidget *w; #ifdef USE_TEXTVIEW GtkWidget *scrolledwindow; static GtkTextTagTable *tagtable = NULL; static GtkTextTag *tag = NULL; GtkTextBuffer *buffer; GtkTextIter start, end; #else GtkWidget *scrollbar; GtkWidget *hbox; #endif GtkWidget *label; GtkWidget *vbox; GtkWidget *bbox; GtkWidget *text; GtkWidget *button; char buf[NH_BUFSIZ]; w = nh_gtk_window_dialog(FALSE); gtk_widget_set_name(GTK_WIDGET(w), "fixed font"); nh_position_popup_dialog(GTK_WIDGET(w)); nh_gtk_focus_set_slave_for(GTK_WINDOW(w), GTK_WINDOW(main_window)); hid = gtk_signal_connect(GTK_OBJECT(w), "destroy", GTK_SIGNAL_FUNC(default_destroy), &w); vbox = nh_gtk_new_and_add(gtk_vbox_new(FALSE, 0), w, ""); label = nh_gtk_new_and_pack(gtk_label_new("HELP"), vbox, "", FALSE, FALSE, NH_PAD); #ifdef USE_TEXTVIEW gtk_window_set_default_size(GTK_WINDOW(w), 600, (root_height * 2)/3); scrolledwindow = nh_gtk_new_and_pack(gtk_scrolled_window_new(NULL, NULL), vbox, "", TRUE, TRUE, NH_PAD); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); if (!tagtable) { tagtable = gtk_text_tag_table_new(); tag = gtk_text_tag_new("body"); if (!help_font_name) help_font_name = g_strdup("Courier 12"); g_object_set(G_OBJECT(tag), "font", help_font_name, NULL); gtk_text_tag_table_add(tagtable, tag); } buffer = gtk_text_buffer_new(tagtable); text = nh_gtk_new_and_add(gtk_text_view_new_with_buffer(buffer), scrolledwindow, ""); g_object_unref(buffer); GTK_WIDGET_UNSET_FLAGS(text, GTK_CAN_FOCUS); gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD); gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE); #else hbox = nh_gtk_new_and_pack(gtk_hbox_new(FALSE, 0), vbox, "", FALSE, FALSE, NH_PAD); text = nh_gtk_new_and_pack(gtk_text_new(NULL, NULL), hbox, "", FALSE, FALSE, NH_PAD); gtk_widget_set_usize(GTK_WIDGET(text), 600, (root_height * 2)/3); scrollbar = nh_gtk_new_and_pack(gtk_vscrollbar_new(GTK_TEXT(text)->vadj), hbox, "", FALSE, FALSE, NH_PAD); #endif bbox = nh_gtk_new_and_pack(gtk_hbutton_box_new(), vbox, "", FALSE, FALSE, NH_PAD); gtk_container_set_border_width(GTK_CONTAINER(bbox), 5); gtk_box_set_spacing(GTK_BOX(bbox), 5); gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); #ifdef USE_TEXTVIEW button = nh_gtk_new_and_add(gtk_button_new_with_mnemonic("_Set font..."), bbox, ""); gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(GTK_ext_display_file_setfont), buffer); #endif button = nh_gtk_new_and_add(gtk_button_new_from_stock(GTK_STOCK_CLOSE), bbox, ""); gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(default_button_press), (gpointer)'\033'); #ifndef GTK_PROXY while(dlbh_fgets(buf, NH_BUFSIZ, fh)) { #else while(proxy_cb_dlbh_fgets(buf, NH_BUFSIZ, fh)) { #endif char *s; if ((s = index(buf, '\r')) != 0) memmove(s, s + 1, strlen(s)); #ifdef USE_TEXTVIEW gtk_text_buffer_insert_at_cursor(buffer, buf, strlen(buf)); #else gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL, buf, strlen(buf)); #endif } #ifdef USE_TEXTVIEW gtk_text_buffer_get_bounds(buffer, &start, &end); gtk_text_buffer_apply_tag(buffer, tag, &start, &end); #endif gtk_widget_show_all(w); if (!main_hook(NULL)) (void)nh_key_get(); if (w) { gtk_signal_disconnect(GTK_OBJECT(w), hid); #ifndef USE_TEXTVIEW gtk_widget_destroy(hbox2); gtk_widget_destroy(scrollbar); #endif gtk_widget_destroy(text); gtk_widget_destroy(bbox); gtk_widget_destroy(label); gtk_widget_destroy(vbox); gtk_widget_destroy(w); } } int GTK_ext_player_selection(int *role, int *race, int *gend, int *align) { in_player_selection = TRUE; select_player_flags.role = *role; select_player_flags.race = *race; select_player_flags.gend = *gend; select_player_flags.align = *align; init_select_player(TRUE); gtk_item_factory_create_items(main_item_factory, nmenu_items, menu_items, NULL); nh_menu_sensitive("/Game/Play", TRUE); nh_menu_sensitive("/Game/Save", FALSE); nh_menu_sensitive("/Move", FALSE); nh_menu_sensitive("/Fight", FALSE); nh_menu_sensitive("/Check", FALSE); nh_menu_sensitive("/Equip", FALSE); nh_menu_sensitive("/You", FALSE); nh_menu_sensitive("/Adventure", FALSE); nh_menu_sensitive("/Action", FALSE); nh_menu_sensitive("/Religion", FALSE); nh_menu_sensitive("/Special", FALSE); nh_option_lock(FALSE); gtk_main(); nh_menu_sensitive("/Game/Play", FALSE); nh_menu_sensitive("/Game/Save", TRUE); nh_menu_sensitive("/Game/Save", TRUE); nh_menu_sensitive("/Move", TRUE); nh_menu_sensitive("/Fight", TRUE); nh_menu_sensitive("/Check", TRUE); nh_menu_sensitive("/Equip", TRUE); nh_menu_sensitive("/You", TRUE); nh_menu_sensitive("/Adventure", TRUE); nh_menu_sensitive("/Action", TRUE); nh_menu_sensitive("/Religion", TRUE); nh_menu_sensitive("/Special", TRUE); nh_option_lock(TRUE); *role = select_player_flags.role; *race = select_player_flags.race; *gend = select_player_flags.gend; *align = select_player_flags.align; in_player_selection = FALSE; return exiting; } static void GTK_display_inventory(void) { display_inventory_needed = FALSE; #ifdef GTK_PROXY proxy_cb_display_inventory(); #else (void) display_inventory((char *)0, FALSE); #endif } void GTK_update_inventory(void) { #ifdef GTK_PROXY winid inven = proxy_cb_get_standard_winid("INVEN"); #else winid inven = WIN_INVEN; #endif copts.perm_invent = nh_option_cache_get_bool("perm_invent"); if (copts.perm_invent) { if (inven == WIN_ERR || !(gtkWindows[inven].flags & NHWF_DISPLAYED)) display_inventory_needed = TRUE; else GTK_display_inventory(); } else if (inven != WIN_ERR) GTK_unmap_menu_window(>kWindows[inven]); } void GTK_wait_synch() { } void GTK_mark_synch() { } volatile int delay_finished; static gint delay_timeout(gpointer data) { delay_finished++; return FALSE; } void GTK_number_pad(int state) { copts.num_pad = (boolean)state; } void GTK_delay_output() { if (GTK_initialized) { delay_finished = 0; gtk_timeout_add(50L, delay_timeout, 0); while (!delay_finished) gtk_main_iteration(); } } #define NAME_LINE 0 /* line # for player name */ #define GOLD_LINE 1 /* line # for amount of gold */ #define DEATH_LINE 2 /* line # for death description */ #define YEAR_LINE 6 /* line # for year */ static struct{ GdkWChar str[NH_BUFSIZ]; int len; int width; } rip_line[YEAR_LINE + 1]; int GTK_ext_outrip(winid id, char *str) { int x, y; int width; int total_len, len, line; GtkWidget *w; GtkWidget *vbox; GtkWidget *rip; GdkPixmap *rip_pixmap; char mstr[NH_BUFSIZ]; GdkWChar *wc; GdkWChar wstr[NH_BUFSIZ]; extern const char *killed_by_prefix[]; char *rip_file; GdkFont *rip_font; char *gold; char *player; gint dummy; gint height = 0, ascent, descent; w = nh_gtk_window_dialog(TRUE); gtk_window_set_position(GTK_WINDOW(w), GTK_WIN_POS_CENTER); gtk_widget_set_events(w, GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK); gtk_signal_connect(GTK_OBJECT(w), "button_press_event", GTK_SIGNAL_FUNC(default_button_press), NULL); nh_gtk_focus_set_slave_for(GTK_WINDOW(w), GTK_WINDOW(main_window)); gtk_signal_connect(GTK_OBJECT(w), "destroy", GTK_SIGNAL_FUNC(default_destroy), &w); gtk_widget_realize(w); vbox = nh_gtk_new_and_add(gtk_vbox_new(FALSE, 0), w, ""); #if defined(GTK_PROXY) || !defined(FILE_AREAS) # ifdef UNIX rip_file = "/usr/share/games/gtkhack/rip.xpm"; # else rip_file = "rip.xpm"; # endif #else rip_file = make_file_name(FILE_AREA_SHARE, "rip.xpm"); #endif rip_pixmap = gdk_pixmap_create_from_xpm(w->window, 0, 0, rip_file); #if !defined(GTK_PROXY) && defined(FILE_AREAS) free(rip_file); #endif if (!rip_pixmap) { rip_pixmap = gdk_pixmap_new(w->window, 310, 200, -1); gdk_draw_rectangle(rip_pixmap, w->style->white_gc, TRUE, 0, 0, 310, 200); } rip = nh_gtk_new_and_pack(gtk_pixmap_new(rip_pixmap, 0), vbox, "", FALSE, FALSE, NH_PAD); rip_font = gtk_style_get_font(rip->style); player = nh_status_last_displayed("player"); Sprintf(mstr, "%s", player ? player : "Rodney"); rip_line[NAME_LINE].len = gdk_mbstowcs(rip_line[NAME_LINE].str, mstr, NH_BUFSIZ); gold = nh_status_last_displayed("gold"); Sprintf(mstr, "%s Au", gold ? gold : "0"); rip_line[GOLD_LINE].len = gdk_mbstowcs(rip_line[GOLD_LINE].str, mstr, NH_BUFSIZ); Sprintf(mstr, "%4d", getyear()); rip_line[YEAR_LINE].len = gdk_mbstowcs(rip_line[YEAR_LINE].str, mstr, NH_BUFSIZ); total_len = gdk_mbstowcs(wstr, str, NH_BUFSIZ); line = DEATH_LINE; wc = wstr; while(total_len > 0 && line < YEAR_LINE) { len = total_len; while(1) { width = gdk_text_width_wc(rip_font, wc, len); if (width < 96) break; --len; } memcpy(rip_line[line].str, wc, len * sizeof(GdkWChar)); rip_line[line].len = len; wc += len; total_len -= len; ++line; } x = 155; y = 78; for(line = 0; line <= YEAR_LINE; line++) { gdk_text_extents_wc(rip_font, rip_line[line].str, rip_line[line].len, &dummy, &dummy, &rip_line[line].width, &ascent, &descent); if (height < (ascent + descent)) height = ascent + descent; } for(line = 0; line <= YEAR_LINE; line++) { gdk_draw_text_wc(rip_pixmap, rip_font, rip->style->black_gc, x - rip_line[line].width / 2, y, rip_line[line].str, rip_line[line].len); y += height; } gtk_widget_show_all(w); if (!main_hook(NULL)) (void) nh_key_get(); return TRUE; } void GTK_raw_print(const char *str) { if (rawprint_win != WIN_ERR) GTK_putstr(rawprint_win, 0, str); else { #if defined(TTY_GRAPHICS) && !defined(GTK_PROXY) tty_raw_print(str); #else puts(str); (void) fflush(stdout); #endif } } void GTK_raw_print_bold(const char *str) { if (rawprint_win != WIN_ERR) GTK_putstr(rawprint_win, ATR_BOLD, str); else { #if defined(TTY_GRAPHICS) && !defined(GTK_PROXY) tty_raw_print_bold(str); #else puts(str); (void) fflush(stdout); #endif } }