URI: 
       tgtk_client.c - vaccinewars - be a doctor and try to vaccinate the world
  HTML git clone git://src.adamsgaard.dk/vaccinewars
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tgtk_client.c (115287B)
       ---
            1 /************************************************************************
            2  * gtk_client.c   dopewars client using the GTK+ toolkit                *
            3  * Copyright (C)  1998-2021  Ben Webb                                   *
            4  *                Email: benwebb@users.sf.net                           *
            5  *                WWW: https://dopewars.sourceforge.io/                 *
            6  *                                                                      *
            7  * This program is free software; you can redistribute it and/or        *
            8  * modify it under the terms of the GNU General Public License          *
            9  * as published by the Free Software Foundation; either version 2       *
           10  * of the License, or (at your option) any later version.               *
           11  *                                                                      *
           12  * This program is distributed in the hope that it will be useful,      *
           13  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *
           14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
           15  * GNU General Public License for more details.                         *
           16  *                                                                      *
           17  * You should have received a copy of the GNU General Public License    *
           18  * along with this program; if not, write to the Free Software          *
           19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston,               *
           20  *                   MA  02111-1307, USA.                               *
           21  ************************************************************************/
           22 
           23 #ifdef HAVE_CONFIG_H
           24 #include <config.h>
           25 #endif
           26 
           27 #include <stdlib.h>
           28 #include <ctype.h>
           29 #include <string.h>
           30 
           31 #include "configfile.h"
           32 #include "convert.h"
           33 #include "dopewars.h"
           34 #include "gtk_client.h"
           35 #include "message.h"
           36 #include "nls.h"
           37 #include "serverside.h"
           38 #include "sound.h"
           39 #include "tstring.h"
           40 #include "util.h"
           41 #include "gtkport/gtkport.h"
           42 #include "dopewars-pill.xpm"
           43 #include "optdialog.h"
           44 #include "newgamedia.h"
           45 
           46 #define BT_BUY  (GINT_TO_POINTER(1))
           47 #define BT_SELL (GINT_TO_POINTER(2))
           48 #define BT_DROP (GINT_TO_POINTER(3))
           49 
           50 #define ET_SPY    0
           51 #define ET_TIPOFF 1
           52 
           53 struct InventoryWidgets {
           54   GtkWidget *HereList, *CarriedList;
           55   GtkWidget *HereFrame, *CarriedFrame;
           56   GtkWidget *BuyButton, *SellButton, *DropButton;
           57   GtkWidget *vbbox;
           58 };
           59 
           60 struct StatusWidgets {
           61   GtkWidget *Location, *Date, *SpaceName, *SpaceValue, *CashName;
           62   GtkWidget *CashValue, *DebtName, *DebtValue, *BankName, *BankValue;
           63   GtkWidget *GunsName, *GunsValue, *BitchesName, *BitchesValue;
           64   GtkWidget *HealthName, *HealthValue;
           65 };
           66 
           67 struct ClientDataStruct {
           68   GtkWidget *window, *messages;
           69   Player *Play;
           70   DPGtkItemFactory *Menu;
           71   struct StatusWidgets Status;
           72   struct InventoryWidgets Drug, Gun, InvenDrug, InvenGun;
           73   GtkWidget *JetButton, *vbox, *PlayerList, *TalkList;
           74   guint JetAccel;
           75   struct CMDLINE *cmdline;
           76 };
           77 
           78 struct DealDiaStruct {
           79   GtkWidget *dialog, *cost, *carrying, *space, *afford, *amount;
           80   gint DrugInd;
           81   gpointer Type;
           82 };
           83 static struct DealDiaStruct DealDialog;
           84 
           85 GtkWidget *MainWindow = NULL;
           86 
           87 static struct ClientDataStruct ClientData;
           88 static gboolean InGame = FALSE;
           89 
           90 static GtkWidget *FightDialog = NULL, *SpyReportsDialog;
           91 static gboolean IsShowingPlayerList = FALSE, IsShowingTalkList = FALSE;
           92 static gboolean IsShowingInventory = FALSE, IsShowingGunShop = FALSE;
           93 static gboolean IsShowingDealDrugs = FALSE;
           94 
           95 static void display_intro(GtkWidget *widget, gpointer data);
           96 static void QuitGame(GtkWidget *widget, gpointer data);
           97 static void DestroyGtk(GtkWidget *widget, gpointer data);
           98 static void NewGame(GtkWidget *widget, gpointer data);
           99 static void AbandonGame(GtkWidget *widget, gpointer data);
          100 static void ToggleSound(GtkWidget *widget, gpointer data);
          101 static void ListScores(GtkWidget *widget, gpointer data);
          102 static void ListInventory(GtkWidget *widget, gpointer data);
          103 static void EndGame(void);
          104 static void Jet(GtkWidget *parent);
          105 static void UpdateMenus(void);
          106 
          107 #ifdef NETWORKING
          108 gboolean GetClientMessage(GIOChannel *source, GIOCondition condition,
          109                           gpointer data);
          110 void SocketStatus(NetworkBuffer *NetBuf, gboolean Read, gboolean Write,
          111                   gboolean Exception, gboolean CallNow);
          112 
          113 /* Data waiting to be sent to/read from the metaserver */
          114 CurlConnection MetaConn;
          115 #endif /* NETWORKING */
          116 
          117 static void HandleClientMessage(char *buf, Player *Play);
          118 static void PrepareHighScoreDialog(void);
          119 static void AddScoreToDialog(char *Data);
          120 static void CompleteHighScoreDialog(gboolean AtEnd);
          121 static void PrintMessage(char *Data, char *tagname);
          122 static void DisplayFightMessage(char *Data);
          123 static GtkWidget *CreateStatusWidgets(struct StatusWidgets *Status);
          124 static void DisplayStats(Player *Play, struct StatusWidgets *Status);
          125 static void UpdateStatus(Player *Play);
          126 static void SetJetButtonTitle(GtkAccelGroup *accel_group);
          127 static void UpdateInventory(struct InventoryWidgets *Inven,
          128                             Inventory *Objects, int NumObjects,
          129                             gboolean AreDrugs);
          130 static void JetButtonPressed(GtkWidget *widget, gpointer data);
          131 static void DealDrugs(GtkWidget *widget, gpointer data);
          132 static void DealGuns(GtkWidget *widget, gpointer data);
          133 static void QuestionDialog(char *Data, Player *From);
          134 static void TransferDialog(gboolean Debt);
          135 static void ListPlayers(GtkWidget *widget, gpointer data);
          136 static void TalkToAll(GtkWidget *widget, gpointer data);
          137 static void TalkToPlayers(GtkWidget *widget, gpointer data);
          138 static void TalkDialog(gboolean TalkToAll);
          139 static GtkWidget *CreatePlayerList(void);
          140 static void UpdatePlayerList(GtkWidget *clist, gboolean IncludeSelf);
          141 static void TipOff(GtkWidget *widget, gpointer data);
          142 static void SpyOnPlayer(GtkWidget *widget, gpointer data);
          143 static void ErrandDialog(gint ErrandType);
          144 static void SackBitch(GtkWidget *widget, gpointer data);
          145 static void DestroyShowing(GtkWidget *widget, gpointer data);
          146 static void SetShowing(GtkWidget *window, gboolean *showing);
          147 static gint DisallowDelete(GtkWidget *widget, GdkEvent * event,
          148                            gpointer data);
          149 static void GunShopDialog(void);
          150 static void NewNameDialog(void);
          151 static void UpdatePlayerLists(void);
          152 static void CreateInventory(GtkWidget *hbox, gchar *Objects,
          153                             GtkAccelGroup *accel_group,
          154                             gboolean CreateButtons, gboolean CreateHere,
          155                             struct InventoryWidgets *widgets,
          156                             GCallback CallBack);
          157 static void GetSpyReports(GtkWidget *widget, gpointer data);
          158 static void DisplaySpyReports(Player *Play);
          159 
          160 static DPGtkItemFactoryEntry menu_items[] = {
          161   /* The names of the menus and their items in the GTK+ client */
          162   {N_("/_Game"), NULL, NULL, 0, "<Branch>"},
          163   {N_("/Game/_New..."), "<control>N", NewGame, 0, NULL},
          164   {N_("/Game/_Abandon..."), "<control>A", AbandonGame, 0, NULL},
          165   {N_("/Game/_Options..."), "<control>O", OptDialog, 0, NULL},
          166   {N_("/Game/Enable _sound"), NULL, ToggleSound, 0, "<CheckItem>"},
          167   {N_("/Game/_Quit..."), "<control>Q", QuitGame, 0, NULL},
          168   {N_("/_Talk"), NULL, NULL, 0, "<Branch>"},
          169   {N_("/Talk/To _All..."), NULL, TalkToAll, 0, NULL},
          170   {N_("/Talk/To _Player..."), NULL, TalkToPlayers, 0, NULL},
          171   {N_("/_List"), NULL, NULL, 0, "<Branch>"},
          172   {N_("/List/_Players..."), NULL, ListPlayers, 0, NULL},
          173   {N_("/List/_Scores..."), NULL, ListScores, 0, NULL},
          174   {N_("/List/_Inventory..."), NULL, ListInventory, 0, NULL},
          175   {N_("/_Errands"), NULL, NULL, 0, "<Branch>"},
          176   {N_("/Errands/_Spy..."), NULL, SpyOnPlayer, 0, NULL},
          177   {N_("/Errands/_Tipoff..."), NULL, TipOff, 0, NULL},
          178   /* N.B. "Sack Bitch" has to be recreated (and thus translated) at the
          179    * start of each game, below, so is not marked for gettext here */
          180   {"/Errands/S_ack Bitch...", NULL, SackBitch, 0, NULL},
          181   {N_("/Errands/_Get spy reports..."), NULL, GetSpyReports, 0, NULL},
          182   {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
          183   {N_("/Help/_About..."), "F1", display_intro, 0, NULL}
          184 };
          185 
          186 static gchar *MenuTranslate(const gchar *path, gpointer func_data)
          187 {
          188   /* Translate menu items, using gettext */
          189   return _(path);
          190 }
          191 
          192 static void LogMessage(const gchar *log_domain, GLogLevelFlags log_level,
          193                        const gchar *message, gpointer user_data)
          194 {
          195   GtkMessageBox(MainWindow, message,
          196                 /* Titles of the message boxes for warnings and errors */
          197                 log_level & G_LOG_LEVEL_WARNING ? _("Warning") :
          198                 log_level & G_LOG_LEVEL_CRITICAL ? _("Error") :
          199                 _("Message"), GTK_MESSAGE_INFO,
          200                 MB_OK | (gtk_main_level() > 0 ? MB_IMMRETURN : 0));
          201 }
          202 
          203 /*
          204  * Creates an hbutton_box widget, and sets a sensible spacing and layout.
          205  */
          206 GtkWidget *my_hbbox_new(void)
          207 {
          208   GtkWidget *hbbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
          209   gtk_button_box_set_layout(GTK_BUTTON_BOX(hbbox), GTK_BUTTONBOX_END);
          210   gtk_box_set_spacing(GTK_BOX(hbbox), 8);
          211   return hbbox;
          212 }
          213 
          214 /*
          215  * Do the equivalent of gtk_box_pack_start_defaults().
          216  * This has been removed from GTK+3.
          217  */
          218 void my_gtk_box_pack_start_defaults(GtkBox *box, GtkWidget *child)
          219 {
          220 #ifdef CYGWIN
          221   /* For compatibility with older dopewars */
          222   gtk_box_pack_start(box, child, FALSE, FALSE, 0);
          223 #else
          224   gtk_box_pack_start(box, child, TRUE, TRUE, 0);
          225 #endif
          226 }
          227 
          228 /*
          229  * Sets the initial size and window manager hints of a dialog.
          230  */
          231 void my_set_dialog_position(GtkWindow *dialog)
          232 {
          233   gtk_window_set_type_hint(dialog, GDK_WINDOW_TYPE_HINT_DIALOG);
          234   gtk_window_set_position(dialog, GTK_WIN_POS_CENTER_ON_PARENT);
          235 }
          236 
          237 void QuitGame(GtkWidget *widget, gpointer data)
          238 {
          239   if (!InGame || GtkMessageBox(ClientData.window,
          240                                /* Prompt in 'quit game' dialog */
          241                                _("Abandon current game?"),
          242                                /* Title of 'quit game' dialog */
          243                                _("Quit Game"), GTK_MESSAGE_QUESTION,
          244                                MB_YESNO) == IDYES) {
          245     gtk_main_quit();
          246   }
          247 }
          248 
          249 void DestroyGtk(GtkWidget *widget, gpointer data)
          250 {
          251   gtk_main_quit();
          252 }
          253 
          254 gint MainDelete(GtkWidget *widget, GdkEvent * event, gpointer data)
          255 {
          256   return (InGame
          257           && GtkMessageBox(ClientData.window, _("Abandon current game?"),
          258                            _("Quit Game"), GTK_MESSAGE_QUESTION,
          259                            MB_YESNO) == IDNO);
          260 }
          261 
          262 
          263 void NewGame(GtkWidget *widget, gpointer data)
          264 {
          265   if (InGame) {
          266     if (GtkMessageBox(ClientData.window, _("Abandon current game?"),
          267                       /* Title of 'stop game to start a new game' dialog */
          268                       _("Start new game"), GTK_MESSAGE_QUESTION,
          269                       MB_YESNO) == IDYES)
          270       EndGame();
          271     else
          272       return;
          273   }
          274 
          275   /* Save the configuration, so we can restore those elements that get
          276    * overwritten when we connect to a dopewars server */
          277   BackupConfig();
          278 
          279 #ifdef NETWORKING
          280   NewGameDialog(ClientData.Play, SocketStatus, &MetaConn);
          281 #else
          282   NewGameDialog(ClientData.Play);
          283 #endif
          284 }
          285 
          286 void AbandonGame(GtkWidget *widget, gpointer data)
          287 {
          288   if (InGame && GtkMessageBox(ClientData.window, _("Abandon current game?"),
          289                               /* Title of 'abandon game' dialog */
          290                               _("Abandon game"), GTK_MESSAGE_QUESTION,
          291                               MB_YESNO) == IDYES) {
          292     EndGame();
          293   }
          294 }
          295 
          296 void ToggleSound(GtkWidget *widget, gpointer data)
          297 {
          298   gboolean enable;
          299 
          300   widget = dp_gtk_item_factory_get_widget(ClientData.Menu,
          301                                           "<main>/Game/Enable sound");
          302   if (widget) {
          303     enable = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
          304     SoundEnable(enable);
          305   }
          306 }
          307 
          308 void ListScores(GtkWidget *widget, gpointer data)
          309 {
          310   if (InGame) {
          311     SendClientMessage(ClientData.Play, C_NONE, C_REQUESTSCORE, NULL, NULL);
          312   } else {
          313     SendNullClientMessage(ClientData.Play, C_NONE, C_REQUESTSCORE, NULL, NULL);
          314   }
          315 }
          316 
          317 void ListInventory(GtkWidget *widget, gpointer data)
          318 {
          319   GtkWidget *window, *button, *hsep, *vbox, *hbox, *hbbox;
          320   GtkAccelGroup *accel_group;
          321 
          322   if (IsShowingInventory)
          323     return;
          324   window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
          325   gtk_window_set_default_size(GTK_WINDOW(window), 550, 120);
          326   accel_group = gtk_accel_group_new();
          327   gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
          328 
          329   /* Title of inventory window */
          330   gtk_window_set_title(GTK_WINDOW(window), _("Inventory"));
          331   my_set_dialog_position(GTK_WINDOW(window));
          332 
          333   SetShowing(window, &IsShowingInventory);
          334 
          335   gtk_window_set_transient_for(GTK_WINDOW(window),
          336                                GTK_WINDOW(ClientData.window));
          337   gtk_container_set_border_width(GTK_CONTAINER(window), 7);
          338 
          339   vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 7);
          340 
          341   hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 7);
          342   CreateInventory(hbox, Names.Drugs, accel_group, FALSE, FALSE,
          343                   &ClientData.InvenDrug, NULL);
          344   CreateInventory(hbox, Names.Guns, accel_group, FALSE, FALSE,
          345                   &ClientData.InvenGun, NULL);
          346 
          347   gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
          348 
          349   hsep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
          350   gtk_box_pack_start(GTK_BOX(vbox), hsep, FALSE, FALSE, 0);
          351 
          352   hbbox = my_hbbox_new();
          353   button = gtk_button_new_with_mnemonic(_("_Close"));
          354   g_signal_connect_swapped(G_OBJECT(button), "clicked",
          355                            G_CALLBACK(gtk_widget_destroy),
          356                            G_OBJECT(window));
          357   my_gtk_box_pack_start_defaults(GTK_BOX(hbbox), button);
          358   gtk_box_pack_start(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
          359 
          360   gtk_container_add(GTK_CONTAINER(window), vbox);
          361 
          362   UpdateInventory(&ClientData.InvenDrug, ClientData.Play->Drugs, NumDrug,
          363                   TRUE);
          364   UpdateInventory(&ClientData.InvenGun, ClientData.Play->Guns, NumGun,
          365                   FALSE);
          366 
          367   gtk_widget_show_all(window);
          368 }
          369 
          370 #ifdef NETWORKING
          371 gboolean GetClientMessage(GIOChannel *source, GIOCondition condition,
          372                           gpointer data)
          373 {
          374   gchar *pt;
          375   NetworkBuffer *NetBuf;
          376   gboolean DoneOK, datawaiting;
          377   NBStatus status, oldstatus;
          378   NBSocksStatus oldsocks;
          379 
          380   NetBuf = &ClientData.Play->NetBuf;
          381 
          382   oldstatus = NetBuf->status;
          383   oldsocks = NetBuf->sockstat;
          384 
          385   datawaiting =
          386       PlayerHandleNetwork(ClientData.Play, condition & G_IO_IN,
          387                           condition & G_IO_OUT,
          388                           condition & G_IO_ERR, &DoneOK);
          389   status = NetBuf->status;
          390 
          391   /* Handle pre-game stuff */
          392   if (status != NBS_CONNECTED) {
          393     /* The start game dialog isn't visible once we're connected... */
          394     DisplayConnectStatus(oldstatus, oldsocks);
          395   }
          396   if (oldstatus != NBS_CONNECTED && (status == NBS_CONNECTED || !DoneOK)) {
          397     FinishServerConnect(DoneOK);
          398   }
          399 
          400   if (status == NBS_CONNECTED && datawaiting) {
          401     while ((pt = GetWaitingPlayerMessage(ClientData.Play)) != NULL) {
          402       HandleClientMessage(pt, ClientData.Play);
          403       g_free(pt);
          404     }
          405   }
          406   if (!DoneOK) {
          407     if (status == NBS_CONNECTED) {
          408       /* The network connection to the server was dropped unexpectedly */
          409       g_warning(_("Connection to server lost - switching to "
          410                   "single player mode"));
          411       SwitchToSinglePlayer(ClientData.Play);
          412       UpdatePlayerLists();
          413       UpdateMenus();
          414     } else {
          415       ShutdownNetworkBuffer(&ClientData.Play->NetBuf);
          416     }
          417   }
          418   return TRUE;
          419 }
          420 
          421 void SocketStatus(NetworkBuffer *NetBuf, gboolean Read, gboolean Write,
          422                   gboolean Exception, gboolean CallNow)
          423 {
          424   if (NetBuf->InputTag)
          425     dp_g_source_remove(NetBuf->InputTag);
          426   NetBuf->InputTag = 0;
          427   if (Read || Write || Exception) {
          428     NetBuf->InputTag = dp_g_io_add_watch(NetBuf->ioch,
          429                                      (Read ? G_IO_IN : 0) |
          430                                      (Write ? G_IO_OUT : 0) |
          431                                      (Exception ? G_IO_ERR : 0),
          432                                      GetClientMessage,
          433                                      NetBuf->CallBackData);
          434   }
          435   if (CallNow)
          436     GetClientMessage(NetBuf->ioch, 0, NetBuf->CallBackData);
          437 }
          438 #endif /* NETWORKING */
          439 
          440 void HandleClientMessage(char *pt, Player *Play)
          441 {
          442   char *Data;
          443   DispMode DisplayMode;
          444   AICode AI;
          445   MsgCode Code;
          446   Player *From, *tmp;
          447   gchar *text;
          448   gboolean Handled;
          449   GtkWidget *MenuItem;
          450   GSList *list;
          451 
          452   if (ProcessMessage(pt, Play, &From, &AI, &Code,
          453                      &Data, FirstClient) == -1) {
          454     return;
          455   }
          456 
          457   Handled =
          458       HandleGenericClientMessage(From, AI, Code, Play, Data, &DisplayMode);
          459   switch (Code) {
          460   case C_STARTHISCORE:
          461     PrepareHighScoreDialog();
          462     break;
          463   case C_HISCORE:
          464     AddScoreToDialog(Data);
          465     break;
          466   case C_ENDHISCORE:
          467     CompleteHighScoreDialog((strcmp(Data, "end") == 0));
          468     break;
          469   case C_PRINTMESSAGE:
          470     PrintMessage(Data, NULL);
          471     break;
          472   case C_FIGHTPRINT:
          473     DisplayFightMessage(Data);
          474     break;
          475   case C_PUSH:
          476     /* The server admin has asked us to leave - so warn the user, and do
          477        so */
          478     g_warning(_("You have been pushed from the server.\n"
          479                 "Switching to single player mode."));
          480     SwitchToSinglePlayer(Play);
          481     UpdatePlayerLists();
          482     UpdateMenus();
          483     break;
          484   case C_QUIT:
          485     /* The server has sent us notice that it is shutting down */
          486     g_warning(_("The server has terminated.\n"
          487                 "Switching to single player mode."));
          488     SwitchToSinglePlayer(Play);
          489     UpdatePlayerLists();
          490     UpdateMenus();
          491     break;
          492   case C_NEWNAME:
          493     NewNameDialog();
          494     break;
          495   case C_BANK:
          496     TransferDialog(FALSE);
          497     break;
          498   case C_LOANSHARK:
          499     TransferDialog(TRUE);
          500     break;
          501   case C_GUNSHOP:
          502     GunShopDialog();
          503     break;
          504   case C_MSG:
          505     text = g_strdup_printf("%s: %s", GetPlayerName(From), Data);
          506     PrintMessage(text, "talk");
          507     g_free(text);
          508     SoundPlay(Sounds.TalkToAll);
          509     break;
          510   case C_MSGTO:
          511     text = g_strdup_printf("%s->%s: %s", GetPlayerName(From),
          512                            GetPlayerName(Play), Data);
          513     PrintMessage(text, "page");
          514     g_free(text);
          515     SoundPlay(Sounds.TalkPrivate);
          516     break;
          517   case C_JOIN:
          518     text = g_strdup_printf(_("%s joins the game!"), Data);
          519     PrintMessage(text, "join");
          520     g_free(text);
          521     SoundPlay(Sounds.JoinGame);
          522     UpdatePlayerLists();
          523     UpdateMenus();
          524     break;
          525   case C_LEAVE:
          526     if (From != &Noone) {
          527       text = g_strdup_printf(_("%s has left the game."), Data);
          528       PrintMessage(text, "leave");
          529       g_free(text);
          530       SoundPlay(Sounds.LeaveGame);
          531       UpdatePlayerLists();
          532       UpdateMenus();
          533     }
          534     break;
          535   case C_QUESTION:
          536     QuestionDialog(Data, From == &Noone ? NULL : From);
          537     break;
          538   case C_SUBWAYFLASH:
          539     DisplayFightMessage(NULL);
          540     for (list = FirstClient; list; list = g_slist_next(list)) {
          541       tmp = (Player *)list->data;
          542       tmp->Flags &= ~FIGHTING;
          543     }
          544     /* Message displayed when the player "jets" to a new location */
          545     text = dpg_strdup_printf(_("Jetting to %tde"),
          546                              Location[(int)Play->IsAt].Name);
          547     PrintMessage(text, "jet");
          548     g_free(text);
          549     SoundPlay(Sounds.Jet);
          550     break;
          551   case C_ENDLIST:
          552     MenuItem = dp_gtk_item_factory_get_widget(ClientData.Menu,
          553                                               "<main>/Errands/Sack Bitch...");
          554 
          555     /* Text for the Errands/Sack Bitch menu item */
          556     text = dpg_strdup_printf(_("%/Sack Bitch menu item/S_ack %Tde..."),
          557                              Names.Bitch);
          558     SetAccelerator(MenuItem, text, NULL, NULL, NULL, FALSE);
          559     g_free(text);
          560 
          561     MenuItem = dp_gtk_item_factory_get_widget(ClientData.Menu,
          562                                               "<main>/Errands/Spy...");
          563 
          564     /* Text to update the Errands/Spy menu item with the price for spying */
          565     text = dpg_strdup_printf(_("_Spy (%P)"), Prices.Spy);
          566     SetAccelerator(MenuItem, text, NULL, NULL, NULL, FALSE);
          567     g_free(text);
          568 
          569     /* Text to update the Errands/Tipoff menu item with the price for a
          570        tipoff */
          571     text = dpg_strdup_printf(_("_Tipoff (%P)"), Prices.Tipoff);
          572     MenuItem = dp_gtk_item_factory_get_widget(ClientData.Menu,
          573                                               "<main>/Errands/Tipoff...");
          574     SetAccelerator(MenuItem, text, NULL, NULL, NULL, FALSE);
          575     g_free(text);
          576     if (FirstClient->next)
          577       ListPlayers(NULL, NULL);
          578     UpdateMenus();
          579     break;
          580   case C_UPDATE:
          581     if (From == &Noone) {
          582       ReceivePlayerData(Play, Data, Play);
          583       UpdateStatus(Play);
          584     } else {
          585       ReceivePlayerData(Play, Data, From);
          586       DisplaySpyReports(From);
          587     }
          588     break;
          589   case C_DRUGHERE:
          590     UpdateInventory(&ClientData.Drug, Play->Drugs, NumDrug, TRUE);
          591     if (IsShowingInventory) {
          592       UpdateInventory(&ClientData.InvenDrug, Play->Drugs, NumDrug, TRUE);
          593     }
          594     if (IsShowingDealDrugs) {
          595       gtk_widget_destroy(DealDialog.dialog);
          596     }
          597     break;
          598   default:
          599     if (!Handled) {
          600       g_print("Unknown network message received: %s^%c^%s^%s",
          601               GetPlayerName(From), Code, GetPlayerName(Play), Data);
          602     }
          603     break;
          604   }
          605 }
          606 
          607 struct HiScoreDiaStruct {
          608   GtkWidget *dialog, *grid, *vbox;
          609   GtkAccelGroup *accel_group;
          610 };
          611 static struct HiScoreDiaStruct HiScoreDialog = { NULL, NULL, NULL, NULL };
          612 
          613 /* 
          614  * Creates an empty dialog to display high scores.
          615  */
          616 void PrepareHighScoreDialog(void)
          617 {
          618   GtkWidget *dialog, *vbox, *hsep, *grid;
          619 
          620   /* Make sure the server doesn't fool us into creating multiple dialogs */
          621   if (HiScoreDialog.dialog)
          622     return;
          623 
          624   HiScoreDialog.dialog = dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
          625   HiScoreDialog.accel_group = gtk_accel_group_new();
          626   gtk_window_add_accel_group(GTK_WINDOW(dialog), HiScoreDialog.accel_group);
          627 
          628   /* Title of the GTK+ high score dialog */
          629   gtk_window_set_title(GTK_WINDOW(dialog), _("High Scores"));
          630   my_set_dialog_position(GTK_WINDOW(dialog));
          631 
          632   gtk_container_set_border_width(GTK_CONTAINER(dialog), 7);
          633   gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
          634   gtk_window_set_transient_for(GTK_WINDOW(dialog),
          635                                GTK_WINDOW(ClientData.window));
          636 
          637   HiScoreDialog.vbox = vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 7);
          638   HiScoreDialog.grid = grid = dp_gtk_grid_new(NUMHISCORE, 4, FALSE);
          639   gtk_grid_set_row_spacing(GTK_GRID(grid), 5);
          640   gtk_grid_set_column_spacing(GTK_GRID(grid), 30);
          641 
          642   gtk_box_pack_start(GTK_BOX(vbox), grid, TRUE, TRUE, 0);
          643   hsep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
          644   gtk_box_pack_start(GTK_BOX(vbox), hsep, FALSE, FALSE, 0);
          645   gtk_container_add(GTK_CONTAINER(dialog), vbox);
          646   gtk_widget_show_all(dialog);
          647 }
          648 
          649 /* 
          650  * Adds a single high score (coded in "Data", which is the information
          651  * received in the relevant network message) to the dialog created by
          652  * PrepareHighScoreDialog(), above.
          653  */
          654 void AddScoreToDialog(char *Data)
          655 {
          656   GtkWidget *label;
          657   char *cp;
          658   gchar **spl1, **spl2;
          659   int index, slen;
          660   gboolean bold;
          661 
          662   if (!HiScoreDialog.dialog)
          663     return;
          664 
          665   cp = Data;
          666   index = GetNextInt(&cp, 0);
          667   if (!cp || strlen(cp) < 3)
          668     return;
          669 
          670   bold = (*cp == 'B');          /* Is this score "our" score? (Currently
          671                                  * ignored) */
          672 
          673   /* Step past the 'bold' character, and the initial '>' (if present) */
          674   cp += 2;
          675   g_strchug(cp);
          676 
          677   /* Get the first word - the score */
          678   spl1 = g_strsplit(cp, " ", 2);
          679   if (!spl1 || !spl1[0] || !spl1[1]) {
          680     /* Error - the high score from the server is invalid */
          681     g_warning(_("Corrupt high score!"));
          682     g_strfreev(spl1);
          683     return;
          684   }
          685   label = make_bold_label(spl1[0], bold);
          686   set_label_alignment(label, 1.0, 0.5);
          687   dp_gtk_grid_attach(GTK_GRID(HiScoreDialog.grid), label, 0, index, 1, 1, TRUE);
          688   gtk_widget_show(label);
          689 
          690   /* Remove any leading whitespace from the remainder, since g_strsplit
          691    * will split at every space character, not at a run of them */
          692   g_strchug(spl1[1]);
          693 
          694   /* Get the second word - the date */
          695   spl2 = g_strsplit(spl1[1], " ", 2);
          696   if (!spl2 || !spl2[0] || !spl2[1]) {
          697     g_warning(_("Corrupt high score!"));
          698     g_strfreev(spl2);
          699     return;
          700   }
          701   label = make_bold_label(spl2[0], bold);
          702   set_label_alignment(label, 0.5, 0.5);
          703   dp_gtk_grid_attach(GTK_GRID(HiScoreDialog.grid), label, 1, index, 1, 1, TRUE);
          704   gtk_widget_show(label);
          705 
          706   /* The remainder is the name, terminated with (R.I.P.) if the player
          707    * died, and '<' for the 'current' score */
          708   g_strchug(spl2[1]);
          709 
          710   /* Remove '<' suffix if present */
          711   slen = strlen(spl2[1]);
          712   if (slen >= 1 && spl2[1][slen - 1] == '<') {
          713     spl2[1][slen - 1] = '\0';
          714   }
          715   slen--;
          716 
          717   /* Check for (R.I.P.) suffix, and add it to the 4th column if found */
          718   if (slen > 8 && spl2[1][slen - 1] == ')' && spl2[1][slen - 8] == '(') {
          719     label = make_bold_label(&spl2[1][slen - 8], bold);
          720     set_label_alignment(label, 0.5, 0.5);
          721     dp_gtk_grid_attach(GTK_GRID(HiScoreDialog.grid), label, 3, index, 1, 1,
          722                        TRUE);
          723     gtk_widget_show(label);
          724     spl2[1][slen - 8] = '\0';   /* Remove suffix from the player name */
          725   }
          726 
          727   /* Finally, add in what's left of the player name */
          728   g_strchomp(spl2[1]);
          729   label = make_bold_label(spl2[1], bold);
          730   set_label_alignment(label, 0, 0.5);
          731   dp_gtk_grid_attach(GTK_GRID(HiScoreDialog.grid), label, 2, index, 1, 1, TRUE);
          732   gtk_widget_show(label);
          733 
          734   g_strfreev(spl1);
          735   g_strfreev(spl2);
          736 }
          737 
          738 /* 
          739  * If the high scores are being displayed at the end of the game,
          740  * this function is used to end the game when the high score dialog's
          741  * "OK" button is pressed.
          742  */
          743 static void EndHighScore(GtkWidget *widget)
          744 {
          745   EndGame();
          746 }
          747 
          748 /* 
          749  * Called when all high scores have been received. Finishes off the
          750  * high score dialog by adding an "OK" button. If the game has ended,
          751  * then "AtEnd" is TRUE, and clicking this button will end the game.
          752  */
          753 void CompleteHighScoreDialog(gboolean AtEnd)
          754 {
          755   GtkWidget *button, *dialog, *hbbox;
          756 
          757   dialog = HiScoreDialog.dialog;
          758 
          759   if (!HiScoreDialog.dialog) {
          760     return;
          761   }
          762 
          763   hbbox = my_hbbox_new();
          764   button = gtk_button_new_with_mnemonic(_("_Close"));
          765   g_signal_connect_swapped(G_OBJECT(button), "clicked",
          766                            G_CALLBACK(gtk_widget_destroy),
          767                            G_OBJECT(dialog));
          768   if (AtEnd) {
          769     InGame = FALSE;
          770     g_signal_connect(G_OBJECT(dialog), "destroy",
          771                      G_CALLBACK(EndHighScore), NULL);
          772   }
          773   my_gtk_box_pack_start_defaults(GTK_BOX(hbbox), button);
          774   gtk_box_pack_start(GTK_BOX(HiScoreDialog.vbox), hbbox, FALSE, FALSE, 0);
          775 
          776   gtk_widget_set_can_default(button, TRUE);
          777   gtk_widget_grab_default(button);
          778   gtk_widget_show_all(hbbox);
          779 
          780   /* OK, we're done - allow the creation of new high score dialogs */
          781   HiScoreDialog.dialog = NULL;
          782 }
          783 
          784 /* 
          785  * Prints an information message in the display area of the GTK+ client.
          786  * This area is used for displaying drug busts, messages from other
          787  * players, etc. The message is passed in as the string "text".
          788  */
          789 void PrintMessage(char *text, char *tagname)
          790 {
          791   GtkTextView *messages = GTK_TEXT_VIEW(ClientData.messages);
          792 
          793   g_strdelimit(text, "^", '\n');
          794   TextViewAppend(messages, text, tagname, FALSE);
          795   TextViewAppend(messages, "\n", NULL, TRUE);
          796 }
          797 
          798 static void FreeCombatants(void);
          799 
          800 /* 
          801  * Called when one of the action buttons in the Fight dialog is clicked.
          802  * "data" specifies which button (Deal Drugs/Run/Fight/Stand) was pressed.
          803  */
          804 static void FightCallback(GtkWidget *widget, gpointer data)
          805 {
          806   gint Answer;
          807   Player *Play;
          808   gchar text[4];
          809   GtkWidget *window;
          810   gpointer CanRunHere = NULL;
          811 
          812   window = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
          813   if (window) {
          814     CanRunHere = g_object_get_data(G_OBJECT(window), "CanRunHere");
          815   }
          816 
          817   Answer = GPOINTER_TO_INT(data);
          818   Play = ClientData.Play;
          819   switch (Answer) {
          820   case 'D':
          821     gtk_widget_hide(FightDialog);
          822     if (!(Play->Flags & FIGHTING)) {
          823       FreeCombatants();
          824       gtk_widget_destroy(FightDialog);
          825       FightDialog = NULL;
          826       if (HaveAbility(Play, A_DONEFIGHT)) {
          827         SendClientMessage(Play, C_NONE, C_DONE, NULL, NULL);
          828       }
          829     }
          830     break;
          831   case 'R':
          832     if (CanRunHere) {
          833       SendClientMessage(Play, C_NONE, C_FIGHTACT, NULL, "R");
          834     } else {
          835       Jet(FightDialog);
          836     }
          837     break;
          838   case 'F':
          839   case 'S':
          840     text[0] = Answer;
          841     text[1] = '\0';
          842     SendClientMessage(Play, C_NONE, C_FIGHTACT, NULL, text);
          843     break;
          844   }
          845 }
          846 
          847 /* 
          848  * Adds an action button to the hbox at the base of the Fight dialog.
          849  * The button's caption is given by "Text", and the keyboard shortcut
          850  * (if any) is added to "accel_group". "Answer" gives the identifier
          851  * passed to FightCallback, above.
          852  */
          853 static GtkWidget *AddFightButton(gchar *Text, GtkAccelGroup *accel_group,
          854                                  GtkBox *box, gint Answer)
          855 {
          856   GtkWidget *button;
          857 
          858   button = gtk_button_new_with_label("");
          859   SetAccelerator(button, Text, button, "clicked", accel_group, FALSE);
          860   g_signal_connect(G_OBJECT(button), "clicked",
          861                    G_CALLBACK(FightCallback),
          862                    GINT_TO_POINTER(Answer));
          863   gtk_box_pack_start(box, button, TRUE, TRUE, 0);
          864   return button;
          865 }
          866 
          867 /* Data used to keep track of the widgets giving the information about a
          868  * player/cop involved in a fight */
          869 struct combatant {
          870   GtkWidget *name, *bitches, *healthprog, *healthlabel;
          871 };
          872 
          873 /* 
          874  * Creates an empty Fight dialog. Usually this only needs to be done once,
          875  * as when the user "closes" it, it is only hidden, ready to be reshown
          876  * later. Buttons for all actions are added here, and are hidden/shown
          877  * as necessary.
          878  */
          879 static void CreateFightDialog(void)
          880 {
          881   GtkWidget *dialog, *vbox, *button, *hbox, *hbbox, *hsep, *text, *grid;
          882   GtkAccelGroup *accel_group;
          883   GArray *combatants;
          884   gchar *buf;
          885 
          886   FightDialog = dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
          887   gtk_window_set_default_size(GTK_WINDOW(dialog), 350, 250);
          888   g_signal_connect(G_OBJECT(dialog), "delete_event",
          889                    G_CALLBACK(DisallowDelete), NULL);
          890   accel_group = gtk_accel_group_new();
          891   gtk_window_add_accel_group(GTK_WINDOW(dialog), accel_group);
          892   gtk_window_set_title(GTK_WINDOW(dialog), _("Fight"));
          893   my_set_dialog_position(GTK_WINDOW(dialog));
          894   gtk_container_set_border_width(GTK_CONTAINER(dialog), 7);
          895 
          896   gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
          897   gtk_window_set_transient_for(GTK_WINDOW(dialog),
          898                                GTK_WINDOW(ClientData.window));
          899 
          900   vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 7);
          901 
          902   grid = dp_gtk_grid_new(2, 4, FALSE);
          903   gtk_grid_set_row_spacing(GTK_GRID(grid), 7);
          904   gtk_grid_set_column_spacing(GTK_GRID(grid), 10);
          905 
          906   hsep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
          907   dp_gtk_grid_attach(GTK_GRID(grid), hsep, 0, 1, 3, 1, TRUE);
          908   gtk_widget_show_all(grid);
          909   gtk_box_pack_start(GTK_BOX(vbox), grid, FALSE, FALSE, 0);
          910   g_object_set_data(G_OBJECT(dialog), "grid", grid);
          911 
          912   combatants = g_array_new(FALSE, TRUE, sizeof(struct combatant));
          913   g_array_set_size(combatants, 1);
          914   g_object_set_data(G_OBJECT(dialog), "combatants", combatants);
          915 
          916   text = gtk_scrolled_text_view_new(&hbox);
          917   gtk_widget_set_size_request(text, 150, 120);
          918 
          919   gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
          920   gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
          921   g_object_set_data(G_OBJECT(dialog), "text", text);
          922   gtk_widget_show_all(hbox);
          923   gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
          924 
          925   hsep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
          926   gtk_box_pack_start(GTK_BOX(vbox), hsep, FALSE, FALSE, 0);
          927   gtk_widget_show(hsep);
          928 
          929   hbbox = my_hbbox_new();
          930 
          931   /* Button for closing the "Fight" dialog and going back to dealing drugs
          932      (%Tde = "Drugs" by default) */
          933   buf = dpg_strdup_printf(_("_Deal %Tde"), Names.Drugs);
          934   button = AddFightButton(buf, accel_group, GTK_BOX(hbbox), 'D');
          935   g_object_set_data(G_OBJECT(dialog), "deal", button);
          936   g_free(buf);
          937 
          938   /* Button for shooting at other players in the "Fight" dialog, or for
          939      popping up the "Fight" dialog from the main window */
          940   button = AddFightButton(_("_Fight"), accel_group, GTK_BOX(hbbox), 'F');
          941   g_object_set_data(G_OBJECT(dialog), "fight", button);
          942 
          943   /* Button to stand and take it in the "Fight" dialog */
          944   button = AddFightButton(_("_Stand"), accel_group, GTK_BOX(hbbox), 'S');
          945   g_object_set_data(G_OBJECT(dialog), "stand", button);
          946 
          947   /* Button to run from combat in the "Fight" dialog */
          948   button = AddFightButton(_("_Run"), accel_group, GTK_BOX(hbbox), 'R');
          949   g_object_set_data(G_OBJECT(dialog), "run", button);
          950 
          951   gtk_widget_show(hsep);
          952   gtk_box_pack_start(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
          953   gtk_widget_show(hbbox);
          954   gtk_widget_show(vbox);
          955   gtk_container_add(GTK_CONTAINER(dialog), vbox);
          956   gtk_widget_show(dialog);
          957 }
          958 
          959 /* 
          960  * Updates the display of information for a player/cop in the Fight dialog.
          961  * If the player's name (DefendName) already exists, updates the display of
          962  * total health and number of bitches - otherwise, adds a new entry. If
          963  * DefendBitches is -1, then the player has left.
          964  */
          965 static void UpdateCombatant(gchar *DefendName, int DefendBitches,
          966                             gchar *BitchName, int DefendHealth)
          967 {
          968   guint i, RowIndex;
          969   const gchar *name;
          970   struct combatant *compt;
          971   GArray *combatants;
          972   GtkWidget *grid;
          973   gchar *BitchText, *HealthText;
          974   gfloat ProgPercent;
          975 
          976   combatants = (GArray *)g_object_get_data(G_OBJECT(FightDialog),
          977                                            "combatants");
          978   grid = GTK_WIDGET(g_object_get_data(G_OBJECT(FightDialog), "grid"));
          979   if (!combatants) {
          980     return;
          981   }
          982 
          983   if (DefendName[0]) {
          984     compt = NULL;
          985     for (i = 1, RowIndex = 2; i < combatants->len; i++, RowIndex++) {
          986       compt = &g_array_index(combatants, struct combatant, i);
          987 
          988       if (!compt || !compt->name) {
          989         compt = NULL;
          990         continue;
          991       }
          992       name = gtk_label_get_text(GTK_LABEL(compt->name));
          993       if (name && strcmp(name, DefendName) == 0) {
          994         break;
          995       }
          996       compt = NULL;
          997     }
          998     if (!compt) {
          999       i = combatants->len;
         1000       g_array_set_size(combatants, i + 1);
         1001       compt = &g_array_index(combatants, struct combatant, i);
         1002 
         1003       dp_gtk_grid_resize(GTK_GRID(grid), i + 2, 4);
         1004       RowIndex = i + 1;
         1005     }
         1006   } else {
         1007     compt = &g_array_index(combatants, struct combatant, 0);
         1008 
         1009     RowIndex = 0;
         1010   }
         1011 
         1012   /* Display of number of bitches or deputies during combat
         1013      (%tde="bitches" or "deputies" (etc.) by default) */
         1014   BitchText = dpg_strdup_printf(_("%/Combat: Bitches/%d %tde"),
         1015                                 DefendBitches, BitchName);
         1016 
         1017   /* Display of health during combat */
         1018   if (DefendBitches == -1) {
         1019     HealthText = g_strdup(_("(Left)"));
         1020   } else if (DefendHealth == 0 && DefendBitches == 0) {
         1021     HealthText = g_strdup(_("(Dead)"));
         1022   } else {
         1023     HealthText = g_strdup_printf(_("Health: %d"), DefendHealth);
         1024   }
         1025 
         1026   ProgPercent = (gfloat)DefendHealth / 100.0;
         1027 
         1028   if (compt->name) {
         1029     if (DefendName[0]) {
         1030       gtk_label_set_text(GTK_LABEL(compt->name), DefendName);
         1031     }
         1032     if (DefendBitches >= 0) {
         1033       gtk_label_set_text(GTK_LABEL(compt->bitches), BitchText);
         1034     }
         1035     gtk_label_set_text(GTK_LABEL(compt->healthlabel), HealthText);
         1036     gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(compt->healthprog),
         1037                                   ProgPercent);
         1038   } else {
         1039     /* Display of the current player's name during combat */
         1040     compt->name = gtk_label_new(DefendName[0] ? DefendName : _("You"));
         1041 
         1042     dp_gtk_grid_attach(GTK_GRID(grid), compt->name, 0, RowIndex, 1, 1, FALSE);
         1043     compt->bitches = gtk_label_new(DefendBitches >= 0 ? BitchText : "");
         1044     dp_gtk_grid_attach(GTK_GRID(grid), compt->bitches, 1, RowIndex, 1, 1,
         1045                        FALSE);
         1046     compt->healthprog = gtk_progress_bar_new();
         1047     gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(compt->healthprog),
         1048                                   ProgPercent);
         1049     dp_gtk_grid_attach(GTK_GRID(grid), compt->healthprog, 2, RowIndex, 1, 1,
         1050                        TRUE);
         1051     compt->healthlabel = gtk_label_new(HealthText);
         1052     dp_gtk_grid_attach(GTK_GRID(grid), compt->healthlabel, 3, RowIndex, 1, 1,
         1053                        FALSE);
         1054     gtk_widget_show(compt->name);
         1055     gtk_widget_show(compt->bitches);
         1056     gtk_widget_show(compt->healthprog);
         1057     gtk_widget_show(compt->healthlabel);
         1058   }
         1059 
         1060   g_free(BitchText);
         1061   g_free(HealthText);
         1062 }
         1063 
         1064 /* 
         1065  * Cleans up the list of all players/cops involved in a fight.
         1066  */
         1067 static void FreeCombatants(void)
         1068 {
         1069   GArray *combatants;
         1070 
         1071   combatants = (GArray *)g_object_get_data(G_OBJECT(FightDialog),
         1072                                            "combatants");
         1073   if (combatants) {
         1074     g_array_free(combatants, TRUE);
         1075   }
         1076 }
         1077 
         1078 static void EnableFightButton(GtkWidget *button, gboolean enable)
         1079 {
         1080   if (enable) {
         1081     gtk_widget_set_sensitive(button, TRUE);
         1082     gtk_widget_show(button);
         1083   } else {
         1084     gtk_widget_hide(button);
         1085     gtk_widget_set_sensitive(button, FALSE);
         1086   }
         1087 }
         1088 
         1089 /* 
         1090  * Given the network message "Data" concerning some happening during
         1091  * combat, extracts the relevant data and updates the Fight dialog,
         1092  * creating and/or showing it if necessary.
         1093  * If "Data" is NULL, then closes the dialog. If "Data" is a blank
         1094  * string, then just shows the dialog, displaying no new messages.
         1095  */
         1096 void DisplayFightMessage(char *Data)
         1097 {
         1098   Player *Play;
         1099   GtkAccelGroup *accel_group;
         1100   GtkWidget *Deal, *Fight, *Stand, *Run;
         1101   GtkTextView *textview;
         1102   gchar *AttackName, *DefendName, *BitchName, *Message;
         1103   FightPoint fp;
         1104   int DefendHealth, DefendBitches, BitchesKilled, ArmPercent;
         1105   gboolean CanRunHere, Loot, CanFire;
         1106 
         1107   if (!Data) {
         1108     if (FightDialog) {
         1109       FreeCombatants();
         1110       gtk_widget_destroy(FightDialog);
         1111       FightDialog = NULL;
         1112     }
         1113     return;
         1114   }
         1115   if (FightDialog) {
         1116     if (IsShowingDealDrugs) {
         1117       gtk_widget_destroy(DealDialog.dialog);
         1118     }
         1119     if (!gtk_widget_get_visible(FightDialog)) {
         1120       gtk_widget_show(FightDialog);
         1121     }
         1122   } else {
         1123     CreateFightDialog();
         1124   }
         1125   if (!FightDialog || !Data[0]) {
         1126     return;
         1127   }
         1128 
         1129   Deal = GTK_WIDGET(g_object_get_data(G_OBJECT(FightDialog), "deal"));
         1130   Fight = GTK_WIDGET(g_object_get_data(G_OBJECT(FightDialog), "fight"));
         1131   Stand = GTK_WIDGET(g_object_get_data(G_OBJECT(FightDialog), "stand"));
         1132   Run = GTK_WIDGET(g_object_get_data(G_OBJECT(FightDialog), "run"));
         1133   textview = GTK_TEXT_VIEW(g_object_get_data(G_OBJECT(FightDialog), "text"));
         1134 
         1135   Play = ClientData.Play;
         1136 
         1137   if (HaveAbility(Play, A_NEWFIGHT)) {
         1138     ReceiveFightMessage(Data, &AttackName, &DefendName, &DefendHealth,
         1139                         &DefendBitches, &BitchName, &BitchesKilled,
         1140                         &ArmPercent, &fp, &CanRunHere, &Loot, &CanFire,
         1141                         &Message);
         1142     Play->Flags |= FIGHTING;
         1143     switch (fp) {
         1144     case F_HIT:
         1145     case F_ARRIVED:
         1146     case F_MISS:
         1147       UpdateCombatant(DefendName, DefendBitches, BitchName, DefendHealth);
         1148       break;
         1149     case F_LEAVE:
         1150       if (AttackName[0]) {
         1151         UpdateCombatant(AttackName, -1, BitchName, 0);
         1152       }
         1153       break;
         1154     case F_LASTLEAVE:
         1155       Play->Flags &= ~FIGHTING;
         1156       break;
         1157     default:
         1158       break;
         1159     }
         1160     accel_group = (GtkAccelGroup *)
         1161         g_object_get_data(G_OBJECT(ClientData.window), "accel_group");
         1162     SetJetButtonTitle(accel_group);
         1163   } else {
         1164     Message = Data;
         1165     if (Play->Flags & FIGHTING) {
         1166       fp = F_MSG;
         1167     } else {
         1168       fp = F_LASTLEAVE;
         1169     }
         1170     CanFire = (Play->Flags & CANSHOOT);
         1171     CanRunHere = FALSE;
         1172   }
         1173   g_object_set_data(G_OBJECT(FightDialog), "CanRunHere",
         1174                     GINT_TO_POINTER(CanRunHere));
         1175 
         1176   g_strdelimit(Message, "^", '\n');
         1177   if (strlen(Message) > 0) {
         1178     TextViewAppend(textview, Message, NULL, FALSE);
         1179     TextViewAppend(textview, "\n", NULL, TRUE);
         1180   }
         1181 
         1182   EnableFightButton(Deal, !CanRunHere || fp == F_LASTLEAVE);
         1183   EnableFightButton(Fight, CanFire && TotalGunsCarried(Play) > 0);
         1184   EnableFightButton(Stand, CanFire && TotalGunsCarried(Play) == 0);
         1185   EnableFightButton(Run, fp != F_LASTLEAVE);
         1186 }
         1187 
         1188 /* 
         1189  * Updates the display of pertinent data about player "Play" (location,
         1190  * health, etc. in the status widgets given by "Status". This can point
         1191  * to the widgets at the top of the main window, or those in a Spy
         1192  * Reports dialog.
         1193  */
         1194 void DisplayStats(Player *Play, struct StatusWidgets *Status)
         1195 {
         1196   gchar *prstr;
         1197   GString *text;
         1198 
         1199   text = g_string_new(NULL);
         1200 
         1201   dpg_string_printf(text, _("%/Current location/%tde"),
         1202                      Location[Play->IsAt].Name);
         1203   gtk_label_set_text(GTK_LABEL(Status->Location), text->str);
         1204 
         1205   GetDateString(text, Play);
         1206   gtk_label_set_text(GTK_LABEL(Status->Date), text->str);
         1207 
         1208   g_string_printf(text, "%d", Play->CoatSize);
         1209   gtk_label_set_text(GTK_LABEL(Status->SpaceValue), text->str);
         1210 
         1211   prstr = FormatPrice(Play->Cash);
         1212   gtk_label_set_text(GTK_LABEL(Status->CashValue), prstr);
         1213   g_free(prstr);
         1214 
         1215   prstr = FormatPrice(Play->Bank);
         1216   gtk_label_set_text(GTK_LABEL(Status->BankValue), prstr);
         1217   g_free(prstr);
         1218 
         1219   prstr = FormatPrice(Play->Debt);
         1220   gtk_label_set_text(GTK_LABEL(Status->DebtValue), prstr);
         1221   g_free(prstr);
         1222 
         1223   /* Display of the total number of guns carried (%Tde="Guns" by default) */
         1224   dpg_string_printf(text, _("%/Stats: Guns/%Tde"), Names.Guns);
         1225   gtk_label_set_text(GTK_LABEL(Status->GunsName), text->str);
         1226   g_string_printf(text, "%d", TotalGunsCarried(Play));
         1227   gtk_label_set_text(GTK_LABEL(Status->GunsValue), text->str);
         1228 
         1229   if (!WantAntique) {
         1230     /* Display of number of bitches in GTK+ client status window
         1231        (%Tde="Bitches" by default) */
         1232     dpg_string_printf(text, _("%/GTK Stats: Bitches/%Tde"),
         1233                        Names.Bitches);
         1234     gtk_label_set_text(GTK_LABEL(Status->BitchesName), text->str);
         1235     g_string_printf(text, "%d", Play->Bitches.Carried);
         1236     gtk_label_set_text(GTK_LABEL(Status->BitchesValue), text->str);
         1237   } else {
         1238     gtk_label_set_text(GTK_LABEL(Status->BitchesName), NULL);
         1239     gtk_label_set_text(GTK_LABEL(Status->BitchesValue), NULL);
         1240   }
         1241 
         1242   g_string_printf(text, "%d", Play->Health);
         1243   gtk_label_set_text(GTK_LABEL(Status->HealthValue), text->str);
         1244 
         1245   g_string_free(text, TRUE);
         1246 }
         1247 
         1248 /* 
         1249  * Updates all of the player status in response to a message from the
         1250  * server. This includes the main window display, the gun shop (if
         1251  * displayed) and the inventory (if displayed).
         1252  */
         1253 void UpdateStatus(Player *Play)
         1254 {
         1255   GtkAccelGroup *accel_group;
         1256 
         1257   DisplayStats(Play, &ClientData.Status);
         1258   UpdateInventory(&ClientData.Drug, ClientData.Play->Drugs, NumDrug, TRUE);
         1259   accel_group = (GtkAccelGroup *)
         1260       g_object_get_data(G_OBJECT(ClientData.window), "accel_group");
         1261   SetJetButtonTitle(accel_group);
         1262   if (IsShowingGunShop) {
         1263     UpdateInventory(&ClientData.Gun, ClientData.Play->Guns, NumGun, FALSE);
         1264   }
         1265   if (IsShowingInventory) {
         1266     UpdateInventory(&ClientData.InvenDrug, ClientData.Play->Drugs,
         1267                     NumDrug, TRUE);
         1268     UpdateInventory(&ClientData.InvenGun, ClientData.Play->Guns,
         1269                     NumGun, FALSE);
         1270   }
         1271 }
         1272 
         1273 /* Columns in inventory list */
         1274 enum {
         1275   INVEN_COL_NAME = 0,
         1276   INVEN_COL_NUM,
         1277   INVEN_COL_INDEX,
         1278   INVEN_NUM_COLS
         1279 };
         1280 
         1281 /* Get the currently selected inventory item (drug/gun) as an index into
         1282    the drug/gun array, or -1 if none is selected */
         1283 static int get_selected_inventory(GtkTreeSelection *treesel)
         1284 {
         1285   GtkTreeModel *model;
         1286   GtkTreeIter iter;
         1287   if (gtk_tree_selection_get_selected(treesel, &model, &iter)) {
         1288     int ind;
         1289     gtk_tree_model_get(model, &iter, INVEN_COL_INDEX, &ind, -1);
         1290     return ind;
         1291   } else {
         1292     return -1;
         1293   }
         1294 }
         1295 
         1296 static void scroll_to_selection(GtkTreeModel *model, GtkTreePath *path,
         1297                                 GtkTreeIter *iter, gpointer data)
         1298 {
         1299   GtkTreeView *tv = data;
         1300   gtk_tree_view_scroll_to_cell(tv, path, NULL, FALSE, 0., 0.);
         1301 }
         1302 
         1303 void UpdateInventory(struct InventoryWidgets *Inven,
         1304                      Inventory *Objects, int NumObjects, gboolean AreDrugs)
         1305 {
         1306   GtkWidget *herelist, *carrylist;
         1307   gint i;
         1308   price_t price;
         1309   gchar *titles[2];
         1310   gboolean CanBuy = FALSE, CanSell = FALSE, CanDrop = FALSE;
         1311   GtkTreeIter iter;
         1312   GtkTreeView *tv[2];
         1313   GtkListStore *carrystore, *herestore;
         1314   int numlist, selectrow[2];
         1315 
         1316   herelist = Inven->HereList;
         1317   carrylist = Inven->CarriedList;
         1318 
         1319   numlist = (herelist ? 2 : 1);
         1320 
         1321   /* Get current selections */
         1322   tv[0] = GTK_TREE_VIEW(carrylist);
         1323   carrystore = GTK_LIST_STORE(gtk_tree_view_get_model(tv[0]));
         1324   if (herelist) {
         1325     tv[1] = GTK_TREE_VIEW(herelist);
         1326     herestore = GTK_LIST_STORE(gtk_tree_view_get_model(tv[1]));
         1327   } else {
         1328     tv[1] = NULL;
         1329     herestore = NULL;
         1330   }
         1331 
         1332   for (i = 0; i < numlist; i++) {
         1333     selectrow[i] = get_selected_inventory(gtk_tree_view_get_selection(tv[i]));
         1334   }
         1335 
         1336   gtk_list_store_clear(carrystore);
         1337 
         1338   if (herelist) {
         1339     gtk_list_store_clear(herestore);
         1340   }
         1341 
         1342   for (i = 0; i < NumObjects; i++) {
         1343     if (AreDrugs) {
         1344       titles[0] = dpg_strdup_printf(_("%/Inventory drug name/%tde"),
         1345                                     Drug[i].Name);
         1346       price = Objects[i].Price;
         1347     } else {
         1348       titles[0] = dpg_strdup_printf(_("%/Inventory gun name/%tde"),
         1349                                     Gun[i].Name);
         1350       price = Gun[i].Price;
         1351     }
         1352 
         1353     if (herelist && price > 0) {
         1354       CanBuy = TRUE;
         1355       titles[1] = FormatPrice(price);
         1356       gtk_list_store_append(herestore, &iter);
         1357       gtk_list_store_set(herestore, &iter, INVEN_COL_NAME, titles[0],
         1358                          INVEN_COL_NUM, titles[1], INVEN_COL_INDEX, i, -1);
         1359       g_free(titles[1]);
         1360       if (i == selectrow[1]) {
         1361         gtk_tree_selection_select_iter(gtk_tree_view_get_selection(tv[1]),
         1362                                        &iter);
         1363       }
         1364     }
         1365 
         1366     if (Objects[i].Carried > 0) {
         1367       if (price > 0) {
         1368         CanSell = TRUE;
         1369       } else {
         1370         CanDrop = TRUE;
         1371       }
         1372       if (HaveAbility(ClientData.Play, A_DRUGVALUE) && AreDrugs) {
         1373         titles[1] = dpg_strdup_printf("%d @ %P", Objects[i].Carried,
         1374                                       Objects[i].TotalValue /
         1375                                       Objects[i].Carried);
         1376       } else {
         1377         titles[1] = g_strdup_printf("%d", Objects[i].Carried);
         1378       }
         1379       gtk_list_store_append(carrystore, &iter);
         1380       gtk_list_store_set(carrystore, &iter, INVEN_COL_NAME, titles[0],
         1381                          INVEN_COL_NUM, titles[1], INVEN_COL_INDEX, i, -1);
         1382       g_free(titles[1]);
         1383       if (i == selectrow[0]) {
         1384         gtk_tree_selection_select_iter(gtk_tree_view_get_selection(tv[0]),
         1385                                        &iter);
         1386       }
         1387     }
         1388     g_free(titles[0]);
         1389   }
         1390 
         1391 #ifdef CYGWIN
         1392   /* Our Win32 GtkTreeView implementation doesn't auto-sort, so force it */
         1393   if (herelist) {
         1394     gtk_tree_view_sort(GTK_TREE_VIEW(herelist));
         1395   }
         1396 #endif
         1397 
         1398   /* Scroll so that selection is visible */
         1399   for (i = 0; i < numlist; i++) {
         1400     gtk_tree_selection_selected_foreach(gtk_tree_view_get_selection(tv[i]),
         1401                     scroll_to_selection, tv[i]);
         1402   }
         1403 
         1404   if (Inven->vbbox) {
         1405     gtk_widget_set_sensitive(Inven->BuyButton, CanBuy);
         1406     gtk_widget_set_sensitive(Inven->SellButton, CanSell);
         1407     gtk_widget_set_sensitive(Inven->DropButton, CanDrop);
         1408   }
         1409 }
         1410 
         1411 static void JetCallback(GtkWidget *widget, gpointer data)
         1412 {
         1413   int NewLocation;
         1414   gchar *text;
         1415   GtkWidget *JetDialog;
         1416 
         1417   JetDialog = GTK_WIDGET(g_object_get_data(G_OBJECT(widget), "dialog"));
         1418   NewLocation = GPOINTER_TO_INT(data);
         1419   gtk_widget_destroy(JetDialog);
         1420   text = g_strdup_printf("%d", NewLocation);
         1421   SendClientMessage(ClientData.Play, C_NONE, C_REQUESTJET, NULL, text);
         1422   g_free(text);
         1423 }
         1424 
         1425 void JetButtonPressed(GtkWidget *widget, gpointer data)
         1426 {
         1427   if (InGame) {
         1428     if (ClientData.Play->Flags & FIGHTING) {
         1429       DisplayFightMessage("");
         1430     } else {
         1431       Jet(NULL);
         1432     }
         1433   }
         1434 }
         1435 
         1436 void Jet(GtkWidget *parent)
         1437 {
         1438   GtkWidget *dialog, *grid, *button, *label, *vbox;
         1439   GtkAccelGroup *accel_group;
         1440   gint boxsize, i, row, col;
         1441   gchar *name, AccelChar;
         1442 
         1443   accel_group = gtk_accel_group_new();
         1444 
         1445   dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
         1446   /* Title of 'Jet' dialog */
         1447   gtk_window_set_title(GTK_WINDOW(dialog), _("Jet to location"));
         1448   my_set_dialog_position(GTK_WINDOW(dialog));
         1449 
         1450   gtk_container_set_border_width(GTK_CONTAINER(dialog), 7);
         1451   gtk_window_add_accel_group(GTK_WINDOW(dialog), accel_group);
         1452   gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
         1453   gtk_window_set_transient_for(GTK_WINDOW(dialog),
         1454                                parent ? GTK_WINDOW(parent)
         1455                                : GTK_WINDOW(ClientData.window));
         1456 
         1457   vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 7);
         1458 
         1459   /* Prompt in 'Jet' dialog */
         1460   label = gtk_label_new(_("Where to, dude ? "));
         1461   gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
         1462 
         1463   /* Generate a square box of buttons for all locations */
         1464   boxsize = 1;
         1465   while (boxsize * boxsize < NumLocation) {
         1466     boxsize++;
         1467   }
         1468   col = boxsize;
         1469   row = 1;
         1470 
         1471   /* Avoid creating a box with an entire row empty at the bottom */
         1472   while (row * col < NumLocation) {
         1473     row++;
         1474   }
         1475 
         1476   grid = dp_gtk_grid_new(row, col, TRUE);
         1477 
         1478   for (i = 0; i < NumLocation; i++) {
         1479     if (i < 9) {
         1480       AccelChar = '1' + i;
         1481     } else if (i < 35) {
         1482       AccelChar = 'A' + i - 9;
         1483     } else {
         1484       AccelChar = '\0';
         1485     }
         1486 
         1487     row = i / boxsize;
         1488     col = i % boxsize;
         1489     if (AccelChar == '\0') {
         1490       name = dpg_strdup_printf(_("%/Location to jet to/%tde"),
         1491                                Location[i].Name);
         1492       button = gtk_button_new_with_label(name);
         1493       g_free(name);
         1494     } else {
         1495       button = gtk_button_new_with_label("");
         1496 
         1497       /* Display of locations in 'Jet' window (%tde="The Bronx" etc. by
         1498          default) */
         1499       name = dpg_strdup_printf(_("_%c. %tde"), AccelChar, Location[i].Name);
         1500       SetAccelerator(button, name, button, "clicked", accel_group, FALSE);
         1501       /* Add keypad shortcuts as well */
         1502       if (i < 9) {
         1503         gtk_widget_add_accelerator(button, "clicked", accel_group,
         1504                                    GDK_KEY_KP_1 + i, 0,
         1505                                    GTK_ACCEL_VISIBLE);
         1506       }
         1507       g_free(name);
         1508     }
         1509     gtk_widget_set_sensitive(button, i != ClientData.Play->IsAt);
         1510     g_object_set_data(G_OBJECT(button), "dialog", dialog);
         1511     g_signal_connect(G_OBJECT(button), "clicked",
         1512                      G_CALLBACK(JetCallback), GINT_TO_POINTER(i));
         1513     dp_gtk_grid_attach(GTK_GRID(grid), button, col, row, 1, 1, TRUE);
         1514   }
         1515   gtk_box_pack_start(GTK_BOX(vbox), grid, TRUE, TRUE, 0);
         1516 
         1517   gtk_container_add(GTK_CONTAINER(dialog), vbox);
         1518   gtk_widget_show_all(dialog);
         1519 }
         1520 
         1521 static void UpdateDealDialog(void)
         1522 {
         1523   GString *text;
         1524   GtkAdjustment *spin_adj;
         1525   gint DrugInd, CanDrop, CanCarry, CanAfford, MaxDrug;
         1526   Player *Play;
         1527 
         1528   text = g_string_new(NULL);
         1529   DrugInd = DealDialog.DrugInd;
         1530   Play = ClientData.Play;
         1531 
         1532   /* Display of the current price of the selected drug in 'Deal Drugs'
         1533      dialog */
         1534   dpg_string_printf(text, _("at %P"), Play->Drugs[DrugInd].Price);
         1535   gtk_label_set_text(GTK_LABEL(DealDialog.cost), text->str);
         1536 
         1537   CanDrop = Play->Drugs[DrugInd].Carried;
         1538 
         1539   /* Display of current inventory of the selected drug in 'Deal Drugs'
         1540      dialog (%tde="Opium" etc. by default) */
         1541   dpg_string_printf(text, _("You are currently carrying %d %tde"),
         1542                      CanDrop, Drug[DrugInd].Name);
         1543   gtk_label_set_text(GTK_LABEL(DealDialog.carrying), text->str);
         1544 
         1545   CanCarry = Play->CoatSize;
         1546 
         1547   /* Available space for drugs in 'Deal Drugs' dialog */
         1548   g_string_printf(text, _("Available space: %d"), CanCarry);
         1549   gtk_label_set_text(GTK_LABEL(DealDialog.space), text->str);
         1550 
         1551   if (DealDialog.Type == BT_BUY) {
         1552     /* Just in case a price update from the server slips through */
         1553     if (Play->Drugs[DrugInd].Price == 0) {
         1554       CanAfford = 0;
         1555     } else {
         1556       CanAfford = Play->Cash / Play->Drugs[DrugInd].Price;
         1557     }
         1558 
         1559     /* Number of the selected drug that you can afford in 'Deal Drugs'
         1560        dialog */
         1561     g_string_printf(text, _("You can afford %d"), CanAfford);
         1562     gtk_label_set_text(GTK_LABEL(DealDialog.afford), text->str);
         1563     MaxDrug = MIN(CanCarry, CanAfford);
         1564   } else {
         1565     MaxDrug = CanDrop;
         1566   }
         1567 
         1568   spin_adj = (GtkAdjustment *)gtk_adjustment_new(MaxDrug, 0.0, MaxDrug,
         1569                                                  1.0, 10.0, 0.0);
         1570   gtk_spin_button_set_adjustment(GTK_SPIN_BUTTON(DealDialog.amount),
         1571                                  spin_adj);
         1572   gtk_spin_button_set_value(GTK_SPIN_BUTTON(DealDialog.amount), MaxDrug);
         1573 
         1574   g_string_free(text, TRUE);
         1575 }
         1576 
         1577 /* Columns in deal list */
         1578 enum {
         1579   DEAL_COL_NAME = 0,
         1580   DEAL_COL_INDEX = 1,
         1581   DEAL_NUM_COLS
         1582 };
         1583 
         1584 static void DealSelectCallback(GtkWidget *widget, gpointer data)
         1585 {
         1586   GtkTreeIter iter;
         1587   if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter)) {
         1588     GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget));
         1589     gtk_tree_model_get(model, &iter, DEAL_COL_INDEX, &DealDialog.DrugInd, -1);
         1590     UpdateDealDialog();
         1591   }
         1592 }
         1593 
         1594 static void DealOKCallback(GtkWidget *widget, gpointer data)
         1595 {
         1596   GtkWidget *spinner;
         1597   gint amount;
         1598   gchar *text;
         1599 
         1600   spinner = DealDialog.amount;
         1601 
         1602   gtk_spin_button_update(GTK_SPIN_BUTTON(spinner));
         1603   amount = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spinner));
         1604 
         1605   text = g_strdup_printf("drug^%d^%d", DealDialog.DrugInd,
         1606                          data == BT_BUY ? amount : -amount);
         1607 
         1608   gtk_widget_destroy(DealDialog.dialog);
         1609 
         1610   SendClientMessage(ClientData.Play, C_NONE, C_BUYOBJECT, NULL, text);
         1611   g_free(text);
         1612 }
         1613 
         1614 void DealDrugs(GtkWidget *widget, gpointer data)
         1615 {
         1616   GtkWidget *dialog, *label, *hbox, *hbbox, *button, *spinner, *combo_box,
         1617       *vbox, *hsep, *defbutton;
         1618   GtkListStore *store;
         1619   GtkTreeIter iter;
         1620   GtkCellRenderer *renderer;
         1621   GtkAdjustment *spin_adj;
         1622   GtkAccelGroup *accel_group;
         1623   GtkWidget *tv;
         1624   gchar *Action;
         1625   GString *text;
         1626   Player *Play;
         1627   gint DrugInd, i, SelIndex, FirstInd;
         1628   gboolean DrugIndOK;
         1629 
         1630   g_assert(!IsShowingDealDrugs);
         1631 
         1632   /* Action in 'Deal Drugs' dialog - "Buy/Sell/Drop Drugs" */
         1633   if (data == BT_BUY) {
         1634     Action = _("Buy");
         1635   } else if (data == BT_SELL) {
         1636     Action = _("Sell");
         1637   } else if (data == BT_DROP) {
         1638     Action = _("Drop");
         1639   } else {
         1640     g_warning("Bad DealDrug type");
         1641     return;
         1642   }
         1643 
         1644   DealDialog.Type = data;
         1645   Play = ClientData.Play;
         1646 
         1647   if (data == BT_BUY) {
         1648     tv = ClientData.Drug.HereList;
         1649   } else {
         1650     tv = ClientData.Drug.CarriedList;
         1651   }
         1652   DrugInd = get_selected_inventory(
         1653                      gtk_tree_view_get_selection(GTK_TREE_VIEW(tv)));
         1654 
         1655   DrugIndOK = FALSE;
         1656   FirstInd = -1;
         1657   for (i = 0; i < NumDrug; i++) {
         1658     if ((data == BT_DROP && Play->Drugs[i].Carried > 0
         1659          && Play->Drugs[i].Price == 0)
         1660         || (data == BT_SELL && Play->Drugs[i].Carried > 0
         1661          && Play->Drugs[i].Price != 0)
         1662         || (data == BT_BUY && Play->Drugs[i].Price != 0)) {
         1663       if (FirstInd == -1) {
         1664         FirstInd = i;
         1665       }
         1666       if (DrugInd == i) {
         1667         DrugIndOK = TRUE;
         1668       }
         1669     }
         1670   }
         1671   if (!DrugIndOK) {
         1672     if (FirstInd == -1) {
         1673       return;
         1674     } else {
         1675       DrugInd = FirstInd;
         1676     }
         1677   }
         1678 
         1679   text = g_string_new(NULL);
         1680   accel_group = gtk_accel_group_new();
         1681 
         1682   dialog = DealDialog.dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
         1683   gtk_window_set_title(GTK_WINDOW(dialog), Action);
         1684   my_set_dialog_position(GTK_WINDOW(dialog));
         1685   gtk_window_add_accel_group(GTK_WINDOW(dialog), accel_group);
         1686   gtk_container_set_border_width(GTK_CONTAINER(dialog), 7);
         1687   gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
         1688   gtk_window_set_transient_for(GTK_WINDOW(dialog),
         1689                                GTK_WINDOW(ClientData.window));
         1690   SetShowing(dialog, &IsShowingDealDrugs);
         1691 
         1692   vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 7);
         1693 
         1694   hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 7);
         1695 
         1696   label = gtk_label_new(Action);
         1697   gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
         1698 
         1699   store = gtk_list_store_new(DEAL_NUM_COLS, G_TYPE_STRING, G_TYPE_INT);
         1700   SelIndex = -1;
         1701   for (i = 0; i < NumDrug; i++) {
         1702     if ((data == BT_DROP && Play->Drugs[i].Carried > 0
         1703          && Play->Drugs[i].Price == 0)
         1704         || (data == BT_SELL && Play->Drugs[i].Carried > 0
         1705          && Play->Drugs[i].Price != 0)
         1706         || (data == BT_BUY && Play->Drugs[i].Price != 0)) {
         1707       dpg_string_printf(text, _("%/DealDrugs drug name/%tde"), Drug[i].Name);
         1708       gtk_list_store_append(store, &iter);
         1709       gtk_list_store_set(store, &iter, DEAL_COL_NAME, text->str,
         1710                          DEAL_COL_INDEX, i, -1);
         1711       if (DrugInd >= i) {
         1712         SelIndex++;
         1713       }
         1714     }
         1715   }
         1716   combo_box = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
         1717   g_object_unref(store);
         1718   renderer = gtk_cell_renderer_text_new();
         1719   gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo_box), renderer, TRUE);
         1720   gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo_box), renderer,
         1721                                  "text", DEAL_COL_NAME, NULL);
         1722   gtk_combo_box_set_active(GTK_COMBO_BOX(combo_box), SelIndex);
         1723   gtk_box_pack_start(GTK_BOX(hbox), combo_box, TRUE, TRUE, 0);
         1724 
         1725   DealDialog.DrugInd = DrugInd;
         1726 
         1727   label = DealDialog.cost = gtk_label_new(NULL);
         1728   gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
         1729   gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
         1730 
         1731   label = DealDialog.carrying = gtk_label_new(NULL);
         1732   gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
         1733 
         1734   label = DealDialog.space = gtk_label_new(NULL);
         1735   gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
         1736 
         1737   if (data == BT_BUY) {
         1738     label = DealDialog.afford = gtk_label_new(NULL);
         1739     gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
         1740   }
         1741   hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 7);
         1742   if (data == BT_BUY) {
         1743     /* Prompts for action in the "deal drugs" dialog */
         1744     g_string_printf(text, _("Buy how many?"));
         1745   } else if (data == BT_SELL) {
         1746     g_string_printf(text, _("Sell how many?"));
         1747   } else {
         1748     g_string_printf(text, _("Drop how many?"));
         1749   }
         1750   label = gtk_label_new(text->str);
         1751   gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
         1752   spin_adj = (GtkAdjustment *)gtk_adjustment_new(1.0, 0.0, 2.0,
         1753                                                  1.0, 10.0, 0.0);
         1754   spinner = DealDialog.amount = gtk_spin_button_new(spin_adj, 1.0, 0);
         1755   g_signal_connect(G_OBJECT(spinner), "activate",
         1756                    G_CALLBACK(DealOKCallback), data);
         1757   gtk_box_pack_start(GTK_BOX(hbox), spinner, FALSE, FALSE, 0);
         1758   gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
         1759 
         1760   hsep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
         1761   gtk_box_pack_start(GTK_BOX(vbox), hsep, FALSE, FALSE, 0);
         1762 
         1763   hbbox = my_hbbox_new();
         1764   button = gtk_button_new_with_mnemonic(_("_OK"));
         1765   g_signal_connect(G_OBJECT(button), "clicked",
         1766                    G_CALLBACK(DealOKCallback), data);
         1767   gtk_widget_set_can_default(button, TRUE);
         1768   defbutton = button;
         1769   my_gtk_box_pack_start_defaults(GTK_BOX(hbbox), button);
         1770 
         1771   button = gtk_button_new_with_mnemonic(_("_Cancel"));
         1772   g_signal_connect_swapped(G_OBJECT(button), "clicked",
         1773                            G_CALLBACK(gtk_widget_destroy),
         1774                            G_OBJECT(dialog));
         1775   my_gtk_box_pack_start_defaults(GTK_BOX(hbbox), button);
         1776 
         1777   gtk_box_pack_start(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
         1778   gtk_container_add(GTK_CONTAINER(dialog), vbox);
         1779 
         1780   g_signal_connect(G_OBJECT(combo_box), "changed",
         1781                    G_CALLBACK(DealSelectCallback), NULL);
         1782 
         1783   g_string_free(text, TRUE);
         1784   UpdateDealDialog();
         1785 
         1786   gtk_widget_show_all(dialog);
         1787   gtk_widget_grab_default(defbutton);
         1788 }
         1789 
         1790 void DealGuns(GtkWidget *widget, gpointer data)
         1791 {
         1792   GtkWidget *tv, *dialog;
         1793   gint GunInd;
         1794   gchar *Title;
         1795   GString *text;
         1796 
         1797   dialog = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
         1798 
         1799   if (data == BT_BUY) {
         1800     tv = ClientData.Gun.HereList;
         1801   } else {
         1802     tv = ClientData.Gun.CarriedList;
         1803   }
         1804   GunInd = get_selected_inventory(
         1805                      gtk_tree_view_get_selection(GTK_TREE_VIEW(tv)));
         1806   if (GunInd < 0) {
         1807     return;
         1808   }
         1809 
         1810 
         1811   /* Title of 'gun shop' dialog (%tde="guns" by default) "Buy/Sell/Drop
         1812    * Guns" */
         1813   if (data == BT_BUY) {
         1814     Title = dpg_strdup_printf(_("Buy %tde"), Names.Guns);
         1815   } else if (data == BT_SELL) {
         1816     Title = dpg_strdup_printf(_("Sell %tde"), Names.Guns);
         1817   } else {
         1818     Title = dpg_strdup_printf(_("Drop %tde"), Names.Guns);
         1819   }
         1820 
         1821   text = g_string_new("");
         1822 
         1823   if (data != BT_BUY && TotalGunsCarried(ClientData.Play) == 0) {
         1824     dpg_string_printf(text, _("You don't have any %tde to sell!"),
         1825                        Names.Guns);
         1826     GtkMessageBox(dialog, text->str, Title, GTK_MESSAGE_WARNING, MB_OK);
         1827   } else if (data == BT_BUY && TotalGunsCarried(ClientData.Play) >=
         1828              ClientData.Play->Bitches.Carried + 2) {
         1829     dpg_string_printf(text,
         1830                        _("You'll need more %tde to carry any more %tde!"),
         1831                        Names.Bitches, Names.Guns);
         1832     GtkMessageBox(dialog, text->str, Title, GTK_MESSAGE_WARNING, MB_OK);
         1833   } else if (data == BT_BUY
         1834              && Gun[GunInd].Space > ClientData.Play->CoatSize) {
         1835     dpg_string_printf(text,
         1836                        _("You don't have enough space to carry that %tde!"),
         1837                        Names.Gun);
         1838     GtkMessageBox(dialog, text->str, Title, GTK_MESSAGE_WARNING, MB_OK);
         1839   } else if (data == BT_BUY && Gun[GunInd].Price > ClientData.Play->Cash) {
         1840     dpg_string_printf(text,
         1841                        _("You don't have enough cash to buy that %tde!"),
         1842                        Names.Gun);
         1843     GtkMessageBox(dialog, text->str, Title, GTK_MESSAGE_WARNING, MB_OK);
         1844   } else if (data == BT_SELL && ClientData.Play->Guns[GunInd].Carried == 0) {
         1845     GtkMessageBox(dialog, _("You don't have any to sell!"), Title, 
         1846                   GTK_MESSAGE_WARNING, MB_OK);
         1847   } else {
         1848     g_string_printf(text, "gun^%d^%d", GunInd, data == BT_BUY ? 1 : -1);
         1849     SendClientMessage(ClientData.Play, C_NONE, C_BUYOBJECT, NULL,
         1850                       text->str);
         1851   }
         1852   g_free(Title);
         1853   g_string_free(text, TRUE);
         1854 }
         1855 
         1856 static void QuestionCallback(GtkWidget *widget, gpointer data)
         1857 {
         1858   gint Answer;
         1859   gchar text[5];
         1860   GtkWidget *dialog;
         1861   Player *To;
         1862 
         1863   dialog = GTK_WIDGET(g_object_get_data(G_OBJECT(widget), "dialog"));
         1864   To = (Player *)g_object_get_data(G_OBJECT(dialog), "From");
         1865   Answer = GPOINTER_TO_INT(data);
         1866 
         1867   text[0] = (gchar)Answer;
         1868   text[1] = '\0';
         1869   SendClientMessage(ClientData.Play, C_NONE, C_ANSWER, To, text);
         1870 
         1871   gtk_widget_destroy(dialog);
         1872 }
         1873 
         1874 void QuestionDialog(char *Data, Player *From)
         1875 {
         1876   GtkWidget *dialog, *label, *vbox, *hsep, *hbbox, *button;
         1877   GtkAccelGroup *accel_group;
         1878   gchar *Responses, **split, *LabelText, *trword, *underline;
         1879 
         1880   /* Button titles that correspond to the single-keypress options provided
         1881      by the curses client (e.g. _Yes corresponds to 'Y' etc.) */
         1882   gchar *Words[] = { N_("_Yes"), N_("_No"), N_("_Run"),
         1883     N_("_Fight"), N_("_Attack"), N_("_Evade")
         1884   };
         1885   guint numWords = sizeof(Words) / sizeof(Words[0]);
         1886   guint i, j;
         1887 
         1888   split = g_strsplit(Data, "^", 2);
         1889   if (!split[0] || !split[1]) {
         1890     g_warning("Bad QUESTION message %s", Data);
         1891     return;
         1892   }
         1893 
         1894   g_strdelimit(split[1], "^", '\n');
         1895 
         1896   Responses = split[0];
         1897   LabelText = split[1];
         1898 
         1899   dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
         1900   accel_group = gtk_accel_group_new();
         1901   g_signal_connect(G_OBJECT(dialog), "delete_event",
         1902                    G_CALLBACK(DisallowDelete), NULL);
         1903   g_object_set_data(G_OBJECT(dialog), "From", (gpointer)From);
         1904 
         1905   /* Title of the 'ask player a question' dialog */
         1906   gtk_window_set_title(GTK_WINDOW(dialog), _("Question"));
         1907   my_set_dialog_position(GTK_WINDOW(dialog));
         1908 
         1909   gtk_window_add_accel_group(GTK_WINDOW(dialog), accel_group);
         1910   gtk_container_set_border_width(GTK_CONTAINER(dialog), 7);
         1911   gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
         1912   gtk_window_set_transient_for(GTK_WINDOW(dialog),
         1913                                GTK_WINDOW(ClientData.window));
         1914 
         1915   vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 7);
         1916   while (*LabelText == '\n')
         1917     LabelText++;
         1918   label = gtk_label_new(LabelText);
         1919   gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
         1920 
         1921   hsep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
         1922   gtk_box_pack_start(GTK_BOX(vbox), hsep, FALSE, FALSE, 0);
         1923 
         1924   hbbox = my_hbbox_new();
         1925 
         1926   for (i = 0; i < strlen(Responses); i++) {
         1927     switch (Responses[i]) {
         1928     case 'Y':
         1929       button = gtk_button_new_with_mnemonic(_("_Yes"));
         1930       break;
         1931     case 'N':
         1932       button = gtk_button_new_with_mnemonic(_("_No"));
         1933       break;
         1934     default:
         1935       for (j = 0, trword = NULL; j < numWords && !trword; j++) {
         1936         underline = strchr(Words[j], '_');
         1937         if (underline && toupper(underline[1]) == Responses[i]) {
         1938           trword = _(Words[j]);
         1939         }
         1940       }
         1941       button = gtk_button_new_with_label("");
         1942       if (trword) {
         1943         SetAccelerator(button, trword, button, "clicked", accel_group, FALSE);
         1944       } else {
         1945         trword = g_strdup_printf("_%c", Responses[i]);
         1946         SetAccelerator(button, trword, button, "clicked", accel_group, FALSE);
         1947         g_free(trword);
         1948       }
         1949       break;
         1950     }
         1951     g_object_set_data(G_OBJECT(button), "dialog", (gpointer)dialog);
         1952     g_signal_connect(G_OBJECT(button), "clicked",
         1953                      G_CALLBACK(QuestionCallback),
         1954                      GINT_TO_POINTER((gint)Responses[i]));
         1955     my_gtk_box_pack_start_defaults(GTK_BOX(hbbox), button);
         1956   }
         1957   gtk_box_pack_start(GTK_BOX(vbox), hbbox, TRUE, TRUE, 0);
         1958   gtk_container_add(GTK_CONTAINER(dialog), vbox);
         1959   gtk_widget_show_all(dialog);
         1960 
         1961   g_strfreev(split);
         1962 }
         1963 
         1964 void GuiStartGame(void)
         1965 {
         1966   Player *Play = ClientData.Play;
         1967 
         1968   if (!Network) {
         1969     ClientData.cmdline->antique = WantAntique;
         1970     InitConfiguration(ClientData.cmdline);
         1971   }
         1972   StripTerminators(GetPlayerName(Play));
         1973   InitAbilities(Play);
         1974   SendAbilities(Play);
         1975   SendNullClientMessage(Play, C_NONE, C_NAME, NULL, GetPlayerName(Play));
         1976   InGame = TRUE;
         1977   UpdateMenus();
         1978   gtk_widget_show_all(ClientData.vbox);
         1979   UpdatePlayerLists();
         1980   SoundPlay(Sounds.StartGame);
         1981 }
         1982 
         1983 void EndGame(void)
         1984 {
         1985   DisplayFightMessage(NULL);
         1986   gtk_widget_hide(ClientData.vbox);
         1987   TextViewClear(GTK_TEXT_VIEW(ClientData.messages));
         1988   ShutdownNetwork(ClientData.Play);
         1989   UpdatePlayerLists();
         1990   CleanUpServer();
         1991   RestoreConfig();
         1992   InGame = FALSE;
         1993   UpdateMenus();
         1994   SoundPlay(Sounds.EndGame);
         1995 }
         1996 
         1997 static gint DrugSortByName(GtkTreeModel *model, GtkTreeIter *a,
         1998                            GtkTreeIter *b, gpointer data)
         1999 {
         2000   int indexa, indexb;
         2001   gtk_tree_model_get(model, a, INVEN_COL_INDEX, &indexa, -1);
         2002   gtk_tree_model_get(model, b, INVEN_COL_INDEX, &indexb, -1);
         2003   if (indexa < 0 || indexa >= NumDrug || indexb < 0 || indexb >= NumDrug) {
         2004     return 0;
         2005   }
         2006   return g_ascii_strcasecmp(Drug[indexa].Name, Drug[indexb].Name);
         2007 }
         2008 
         2009 static gint DrugSortByPrice(GtkTreeModel *model, GtkTreeIter *a,
         2010                             GtkTreeIter *b, gpointer data)
         2011 {
         2012   int indexa, indexb;
         2013   price_t pricediff;
         2014   gtk_tree_model_get(model, a, INVEN_COL_INDEX, &indexa, -1);
         2015   gtk_tree_model_get(model, b, INVEN_COL_INDEX, &indexb, -1);
         2016   if (indexa < 0 || indexa >= NumDrug || indexb < 0 || indexb >= NumDrug) {
         2017     return 0;
         2018   }
         2019   pricediff = ClientData.Play->Drugs[indexa].Price -
         2020               ClientData.Play->Drugs[indexb].Price;
         2021   return pricediff == 0 ? 0 : pricediff < 0 ? -1 : 1;
         2022 }
         2023 
         2024 void UpdateMenus(void)
         2025 {
         2026   gboolean MultiPlayer;
         2027   gint Bitches;
         2028 
         2029   MultiPlayer = (FirstClient && FirstClient->next != NULL);
         2030   Bitches = InGame && ClientData.Play ? ClientData.Play->Bitches.Carried : 0;
         2031 
         2032   gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget(ClientData.Menu,
         2033                                                           "<main>/Talk"),
         2034                            InGame && Network);
         2035   gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
         2036                            (ClientData.Menu, "<main>/Game/Options..."),
         2037                            !InGame);
         2038   gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
         2039                            (ClientData.Menu, "<main>/Game/Abandon..."),
         2040                            InGame);
         2041   gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
         2042                            (ClientData.Menu, "<main>/List/Inventory..."),
         2043                            InGame);
         2044   gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
         2045                            (ClientData.Menu, "<main>/List/Players..."),
         2046                            InGame && Network);
         2047   gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
         2048                            (ClientData.Menu, "<main>/Errands"), InGame);
         2049   gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
         2050                            (ClientData.Menu, "<main>/Errands/Spy..."),
         2051                            InGame && MultiPlayer);
         2052   gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
         2053                            (ClientData.Menu, "<main>/Errands/Tipoff..."),
         2054                            InGame && MultiPlayer);
         2055   gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
         2056                            (ClientData.Menu,
         2057                             "<main>/Errands/Sack Bitch..."), Bitches > 0);
         2058   gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
         2059                            (ClientData.Menu,
         2060                             "<main>/Errands/Get spy reports..."), InGame
         2061                            && MultiPlayer);
         2062 }
         2063 
         2064 GtkWidget *CreateStatusWidgets(struct StatusWidgets *Status)
         2065 {
         2066   GtkWidget *grid, *label;
         2067 
         2068   grid = dp_gtk_grid_new(3, 6, FALSE);
         2069   gtk_grid_set_row_spacing(GTK_GRID(grid), 3);
         2070   gtk_grid_set_column_spacing(GTK_GRID(grid), 3);
         2071   gtk_container_set_border_width(GTK_CONTAINER(grid), 3);
         2072 
         2073   label = Status->Location = gtk_label_new(NULL);
         2074   dp_gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 2, 1, TRUE);
         2075 
         2076   label = Status->Date = gtk_label_new(NULL);
         2077   dp_gtk_grid_attach(GTK_GRID(grid), label, 2, 0, 2, 1, TRUE);
         2078 
         2079   /* Available space label in GTK+ client status display */
         2080   label = Status->SpaceName = gtk_label_new(_("Space"));
         2081 
         2082   dp_gtk_grid_attach(GTK_GRID(grid), label, 4, 0, 1, 1, TRUE);
         2083   label = Status->SpaceValue = gtk_label_new(NULL);
         2084   dp_gtk_grid_attach(GTK_GRID(grid), label, 5, 0, 1, 1, TRUE);
         2085 
         2086   /* Player's cash label in GTK+ client status display */
         2087   label = Status->CashName = gtk_label_new(_("Cash"));
         2088   dp_gtk_grid_attach(GTK_GRID(grid), label, 0, 1, 1, 1, TRUE);
         2089 
         2090   label = Status->CashValue = gtk_label_new(NULL);
         2091   dp_gtk_grid_attach(GTK_GRID(grid), label, 1, 1, 1, 1, TRUE);
         2092 
         2093   /* Player's debt label in GTK+ client status display */
         2094   label = Status->DebtName = gtk_label_new(_("Debt"));
         2095   dp_gtk_grid_attach(GTK_GRID(grid), label, 2, 1, 1, 1, TRUE);
         2096 
         2097   label = Status->DebtValue = gtk_label_new(NULL);
         2098   dp_gtk_grid_attach(GTK_GRID(grid), label, 3, 1, 1, 1, TRUE);
         2099 
         2100   /* Player's bank balance label in GTK+ client status display */
         2101   label = Status->BankName = gtk_label_new(_("Bank"));
         2102   dp_gtk_grid_attach(GTK_GRID(grid), label, 4, 1, 1, 1, TRUE);
         2103 
         2104   label = Status->BankValue = gtk_label_new(NULL);
         2105   dp_gtk_grid_attach(GTK_GRID(grid), label, 5, 1, 1, 1, TRUE);
         2106 
         2107   label = Status->GunsName = gtk_label_new(NULL);
         2108   dp_gtk_grid_attach(GTK_GRID(grid), label, 0, 2, 1, 1, TRUE);
         2109   label = Status->GunsValue = gtk_label_new(NULL);
         2110   dp_gtk_grid_attach(GTK_GRID(grid), label, 1, 2, 1, 1, TRUE);
         2111 
         2112   label = Status->BitchesName = gtk_label_new(NULL);
         2113   dp_gtk_grid_attach(GTK_GRID(grid), label, 2, 2, 1, 1, TRUE);
         2114   label = Status->BitchesValue = gtk_label_new(NULL);
         2115   dp_gtk_grid_attach(GTK_GRID(grid), label, 3, 2, 1, 1, TRUE);
         2116 
         2117   /* Player's health label in GTK+ client status display */
         2118   label = Status->HealthName = gtk_label_new(_("Health"));
         2119   dp_gtk_grid_attach(GTK_GRID(grid), label, 4, 2, 1, 1, TRUE);
         2120 
         2121   label = Status->HealthValue = gtk_label_new(NULL);
         2122   dp_gtk_grid_attach(GTK_GRID(grid), label, 5, 2, 1, 1, TRUE);
         2123   return grid;
         2124 }
         2125 
         2126 void SetJetButtonTitle(GtkAccelGroup *accel_group)
         2127 {
         2128   GtkWidget *button;
         2129   guint accel_key;
         2130   gchar *caption;
         2131 
         2132   button = ClientData.JetButton;
         2133   accel_key = ClientData.JetAccel;
         2134 
         2135   if (accel_key) {
         2136     gtk_widget_remove_accelerator(button, accel_group, accel_key, 0);
         2137   }
         2138 
         2139   if (ClientData.Play && ClientData.Play->Flags & FIGHTING) {
         2140     caption = _("_Fight");
         2141   } else {
         2142     /* Caption of 'Jet' button in main window */
         2143     caption = _("_Jet!");
         2144   }
         2145   ClientData.JetAccel = SetAccelerator(button, caption, button,
         2146                                        "clicked", accel_group, FALSE);
         2147 }
         2148 
         2149 static void SetIcon(GtkWidget *window, char **xpmdata)
         2150 {
         2151 #ifndef CYGWIN
         2152   GdkPixbuf *icon;
         2153   icon = gdk_pixbuf_new_from_xpm_data((const char**)xpmdata);
         2154   gtk_window_set_icon(GTK_WINDOW(window), icon);
         2155 #endif
         2156 }
         2157 
         2158 static void make_tags(GtkTextView *textview)
         2159 {
         2160   GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview);
         2161 
         2162   gtk_text_buffer_create_tag(buffer, "jet", "foreground",
         2163                              "#00000000FFFF", NULL);
         2164   gtk_text_buffer_create_tag(buffer, "talk", "foreground",
         2165                              "#FFFF00000000", NULL);
         2166   gtk_text_buffer_create_tag(buffer, "page", "foreground",
         2167                              "#FFFF0000FFFF", NULL);
         2168   gtk_text_buffer_create_tag(buffer, "join", "foreground",
         2169                              "#000000008B8B", NULL);
         2170   gtk_text_buffer_create_tag(buffer, "leave", "foreground",
         2171                              "#8B8B00000000", NULL);
         2172 }
         2173 
         2174 #ifdef CYGWIN
         2175 gboolean GtkLoop(HINSTANCE hInstance, HINSTANCE hPrevInstance,
         2176                  struct CMDLINE *cmdline, gboolean ReturnOnFail)
         2177 #else
         2178 gboolean GtkLoop(int *argc, char **argv[],
         2179                  struct CMDLINE *cmdline, gboolean ReturnOnFail)
         2180 #endif
         2181 {
         2182   GtkWidget *window, *vbox, *vbox2, *hbox, *frame, *grid, *menubar, *text,
         2183       *vpaned, *button, *tv, *widget;
         2184   GtkAccelGroup *accel_group;
         2185   GtkTreeSortable *sortable;
         2186   int i;
         2187   DPGtkItemFactory *item_factory;
         2188   gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
         2189 
         2190 #ifdef CYGWIN
         2191   win32_init(hInstance, hPrevInstance, "mainicon");
         2192 #else
         2193   if (ReturnOnFail && !gtk_init_check(argc, argv))
         2194     return FALSE;
         2195   else if (!ReturnOnFail)
         2196     gtk_init(argc, argv);
         2197 #endif
         2198 
         2199   /* GTK+2 (and the GTK emulation code on WinNT systems) expects all
         2200    * strings to be UTF-8, so we force gettext to return all translations
         2201    * in this encoding here. */
         2202   bind_textdomain_codeset(PACKAGE, "UTF-8");
         2203 
         2204   Conv_SetInternalCodeset("UTF-8");
         2205   WantUTF8Errors(TRUE);
         2206 
         2207   InitConfiguration(cmdline);
         2208   ClientData.cmdline = cmdline;
         2209 
         2210   /* Set up message handlers */
         2211   ClientMessageHandlerPt = HandleClientMessage;
         2212 
         2213   if (!CheckHighScoreFileConfig()) {
         2214     return TRUE;
         2215   }
         2216 
         2217   /* Have the GLib log messages pop up in a nice dialog box */
         2218   g_log_set_handler(NULL,
         2219                     G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_WARNING |
         2220                     G_LOG_LEVEL_CRITICAL, LogMessage, NULL);
         2221 
         2222   SoundOpen(cmdline->plugin);
         2223 
         2224   /* Create the main player */
         2225   ClientData.Play = g_new(Player, 1);
         2226   FirstClient = AddPlayer(0, ClientData.Play, FirstClient);
         2227   if (PlayerName && PlayerName[0]) {
         2228     SetPlayerName(ClientData.Play, PlayerName);
         2229   }
         2230 
         2231   window = MainWindow = ClientData.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
         2232 
         2233   /* Title of main window in GTK+ client */
         2234   gtk_window_set_title(GTK_WINDOW(window), _("dopewars"));
         2235   gtk_window_set_default_size(GTK_WINDOW(window), 450, 390);
         2236   g_signal_connect(G_OBJECT(window), "delete_event",
         2237                    G_CALLBACK(MainDelete), NULL);
         2238   g_signal_connect(G_OBJECT(window), "destroy",
         2239                    G_CALLBACK(DestroyGtk), NULL);
         2240 
         2241   accel_group = gtk_accel_group_new();
         2242   g_object_set_data(G_OBJECT(window), "accel_group", accel_group);
         2243   item_factory = ClientData.Menu = dp_gtk_item_factory_new("<main>",
         2244                                                            accel_group);
         2245   dp_gtk_item_factory_set_translate_func(item_factory, MenuTranslate, NULL,
         2246                                          NULL);
         2247 
         2248   dp_gtk_item_factory_create_items(item_factory, nmenu_items, menu_items,
         2249                                    NULL);
         2250   gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
         2251   menubar = dp_gtk_item_factory_get_widget(item_factory, "<main>");
         2252 
         2253   vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
         2254   gtk_box_pack_start(GTK_BOX(vbox2), menubar, FALSE, FALSE, 0);
         2255   gtk_widget_show_all(menubar);
         2256   UpdateMenus();
         2257   SoundEnable(UseSounds);
         2258   widget = dp_gtk_item_factory_get_widget(ClientData.Menu,
         2259                                           "<main>/Game/Enable sound");
         2260   gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget), UseSounds);
         2261 
         2262   vbox = ClientData.vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
         2263   frame = gtk_frame_new(_("Stats"));
         2264   gtk_container_set_border_width(GTK_CONTAINER(frame), 3);
         2265 
         2266   grid = CreateStatusWidgets(&ClientData.Status);
         2267 
         2268   gtk_container_add(GTK_CONTAINER(frame), grid);
         2269 
         2270   gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0);
         2271 
         2272   vpaned = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
         2273 
         2274   text = ClientData.messages = gtk_scrolled_text_view_new(&hbox);
         2275   make_tags(GTK_TEXT_VIEW(text));
         2276   gtk_widget_set_size_request(text, 100, 80);
         2277   gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
         2278   gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
         2279   gtk_paned_pack1(GTK_PANED(vpaned), hbox, TRUE, TRUE);
         2280 
         2281   hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 7);
         2282   CreateInventory(hbox, Names.Drugs, accel_group, TRUE, TRUE,
         2283                   &ClientData.Drug, G_CALLBACK(DealDrugs));
         2284   tv = ClientData.Drug.HereList;
         2285   gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(tv), TRUE);
         2286   sortable = GTK_TREE_SORTABLE(gtk_tree_view_get_model(GTK_TREE_VIEW(tv)));
         2287   gtk_tree_sortable_set_sort_func(sortable, 0, DrugSortByName, NULL, NULL);
         2288   gtk_tree_sortable_set_sort_func(sortable, 1, DrugSortByPrice, NULL, NULL);
         2289   for (i = 0; i < 2; ++i) {
         2290     GtkTreeViewColumn *col = gtk_tree_view_get_column(GTK_TREE_VIEW(tv), i);
         2291     gtk_tree_view_column_set_sort_column_id(col, i);
         2292   }
         2293 
         2294   button = ClientData.JetButton = gtk_button_new_with_label("");
         2295   ClientData.JetAccel = 0;
         2296   g_signal_connect(G_OBJECT(button), "clicked",
         2297                    G_CALLBACK(JetButtonPressed), NULL);
         2298   gtk_box_pack_start(GTK_BOX(ClientData.Drug.vbbox), button, TRUE, TRUE, 0);
         2299   SetJetButtonTitle(accel_group);
         2300 
         2301 #ifdef CYGWIN
         2302   /* GtkFrames don't look quite right in Win32 yet */
         2303   gtk_paned_pack2(GTK_PANED(vpaned), hbox, TRUE, TRUE);
         2304 #else
         2305   frame = gtk_frame_new(NULL);
         2306   gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
         2307   gtk_container_add(GTK_CONTAINER(frame), hbox);
         2308   gtk_paned_pack2(GTK_PANED(vpaned), frame, TRUE, TRUE);
         2309 #endif
         2310 
         2311   gtk_box_pack_start(GTK_BOX(vbox), vpaned, TRUE, TRUE, 0);
         2312 
         2313   gtk_box_pack_start(GTK_BOX(vbox2), vbox, TRUE, TRUE, 0);
         2314   gtk_container_add(GTK_CONTAINER(window), vbox2);
         2315 
         2316   /* Just show the window, not the vbox - we'll do that when the game
         2317    * starts */
         2318   gtk_widget_show(vbox2);
         2319   gtk_widget_show(window);
         2320 
         2321   gtk_widget_realize(window);
         2322 
         2323   SetIcon(window, dopewars_pill_xpm);
         2324 
         2325 #ifdef NETWORKING
         2326   CurlInit(&MetaConn);
         2327 #endif
         2328 
         2329   gtk_main();
         2330 
         2331 #ifdef NETWORKING
         2332   CurlCleanup(&MetaConn);
         2333 #endif
         2334 
         2335   /* Free the main player */
         2336   FirstClient = RemovePlayer(ClientData.Play, FirstClient);
         2337 
         2338   return TRUE;
         2339 }
         2340 
         2341 static void PackCentredURL(GtkWidget *vbox, gchar *title, gchar *target,
         2342                            gchar *browser)
         2343 {
         2344   GtkWidget *hbox, *label, *url;
         2345 
         2346   /* There must surely be a nicer way of making the URL centred - but I
         2347    * can't think of one... */
         2348   hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
         2349   label = gtk_label_new("");
         2350   gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
         2351 
         2352   url = gtk_url_new(title, target, browser);
         2353   gtk_box_pack_start(GTK_BOX(hbox), url, FALSE, FALSE, 0);
         2354 
         2355   label = gtk_label_new("");
         2356   gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, FALSE, 0);
         2357   gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
         2358 }
         2359 
         2360 void display_intro(GtkWidget *widget, gpointer data)
         2361 {
         2362   GtkWidget *dialog, *label, *grid, *OKButton, *vbox, *hsep, *hbbox;
         2363   gchar *VersionStr, *docindex;
         2364   const int rows = 8, cols = 3;
         2365   int i, j;
         2366   GtkAccelGroup *accel_group;
         2367   gchar *table_data[8][3] = {
         2368     /* Credits labels in GTK+ 'about' dialog */
         2369     {N_("English Translation"), N_("Ben Webb"), NULL},
         2370     {N_("Icons and graphics"), "Ocelot Mantis", NULL},
         2371     {N_("Sounds"), "Robin Kohli, 19.5degs.com", NULL},
         2372     {N_("Drug Dealing and Research"), "Dan Wolf", NULL},
         2373     {N_("Play Testing"), "Phil Davis", "Owen Walsh"},
         2374     {N_("Extensive Play Testing"), "Katherine Holt",
         2375      "Caroline Moore"},
         2376     {N_("Constructive Criticism"), "Andrea Elliot-Smith",
         2377      "Pete Winn"},
         2378     {N_("Unconstructive Criticism"), "James Matthews", NULL}
         2379   };
         2380 
         2381   dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
         2382   accel_group = gtk_accel_group_new();
         2383   gtk_window_add_accel_group(GTK_WINDOW(dialog), accel_group);
         2384 
         2385   /* Title of GTK+ 'about' dialog */
         2386   gtk_window_set_title(GTK_WINDOW(dialog), _("About dopewars"));
         2387   my_set_dialog_position(GTK_WINDOW(dialog));
         2388 
         2389   gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
         2390   gtk_window_set_transient_for(GTK_WINDOW(dialog),
         2391                                GTK_WINDOW(ClientData.window));
         2392   gtk_container_set_border_width(GTK_CONTAINER(dialog), 10);
         2393 
         2394   vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
         2395 
         2396   /* Main content of GTK+ 'about' dialog */
         2397   label = gtk_label_new(_("Based on John E. Dell's old Drug Wars game, "
         2398                           "dopewars is a simulation of an\nimaginary drug "
         2399                           "market.  dopewars is an All-American game which "
         2400                           "features\nbuying, selling, and trying to get "
         2401                           "past the cops!\n\nThe first thing you need to "
         2402                           "do is pay off your debt to the Loan Shark. "
         2403                           "After\nthat, your goal is to make as much "
         2404                           "money as possible (and stay alive)! You\n"
         2405                           "have one month of game time to make "
         2406                           "your fortune.\n"));
         2407   gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
         2408 
         2409   /* Version and copyright notice in GTK+ 'about' dialog */
         2410   VersionStr = g_strdup_printf(_("Version %s     "
         2411                                  "Copyright (C) 1998-2021  "
         2412                                  "Ben Webb benwebb@users.sf.net\n"
         2413                                  "dopewars is released under the "
         2414                                  "GNU General Public License\n"), VERSION);
         2415   label = gtk_label_new(VersionStr);
         2416   gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
         2417   g_free(VersionStr);
         2418 
         2419   grid = dp_gtk_grid_new(rows, cols, FALSE);
         2420   gtk_grid_set_row_spacing(GTK_GRID(grid), 3);
         2421   gtk_grid_set_column_spacing(GTK_GRID(grid), 3);
         2422   for (i = 0; i < rows; i++) {
         2423     if (i > 0 || strcmp(_(table_data[i][1]), "Ben Webb") != 0) {
         2424       for (j = 0; j < cols; j++) {
         2425         if (table_data[i][j]) {
         2426           if (j == 0 || i == 0) {
         2427             label = gtk_label_new(_(table_data[i][j]));
         2428           } else {
         2429             label = gtk_label_new(table_data[i][j]);
         2430           }
         2431           dp_gtk_grid_attach(GTK_GRID(grid), label, j, i, 1, 1, TRUE);
         2432         }
         2433       }
         2434     }
         2435   }
         2436   gtk_box_pack_start(GTK_BOX(vbox), grid, FALSE, FALSE, 0);
         2437 
         2438   /* Label at the bottom of GTK+ 'about' dialog */
         2439   label = gtk_label_new(_("\nFor information on the command line "
         2440                           "options, type dopewars -h at your\n"
         2441                           "Unix prompt. This will display a help "
         2442                           "screen, listing the available options.\n"));
         2443   gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
         2444 
         2445   docindex = GetDocIndex();
         2446   PackCentredURL(vbox, _("Local HTML documentation"), docindex, OurWebBrowser);
         2447   g_free(docindex);
         2448 
         2449   PackCentredURL(vbox, "https://dopewars.sourceforge.io/",
         2450                  "https://dopewars.sourceforge.io/", OurWebBrowser);
         2451 
         2452   hsep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
         2453   gtk_box_pack_start(GTK_BOX(vbox), hsep, FALSE, FALSE, 0);
         2454 
         2455   hbbox = my_hbbox_new();
         2456   OKButton = gtk_button_new_with_mnemonic(_("_OK"));
         2457   g_signal_connect_swapped(G_OBJECT(OKButton), "clicked",
         2458                            G_CALLBACK(gtk_widget_destroy),
         2459                            G_OBJECT(dialog));
         2460   my_gtk_box_pack_start_defaults(GTK_BOX(hbbox), OKButton);
         2461 
         2462   gtk_box_pack_start(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
         2463   gtk_container_add(GTK_CONTAINER(dialog), vbox);
         2464 
         2465   gtk_widget_set_can_default(OKButton, TRUE);
         2466   gtk_widget_grab_default(OKButton);
         2467 
         2468   gtk_widget_show_all(dialog);
         2469 }
         2470 
         2471 static void SendDoneMessage(GtkWidget *widget, gpointer data)
         2472 {
         2473   SendClientMessage(ClientData.Play, C_NONE, C_DONE, NULL, NULL);
         2474 }
         2475 
         2476 static void TransferPayAll(GtkWidget *widget, GtkWidget *dialog)
         2477 {
         2478   gchar *text;
         2479 
         2480   text = pricetostr(ClientData.Play->Debt);
         2481   SendClientMessage(ClientData.Play, C_NONE, C_PAYLOAN, NULL, text);
         2482   g_free(text);
         2483   gtk_widget_destroy(dialog);
         2484 }
         2485 
         2486 static void TransferOK(GtkWidget *widget, GtkWidget *dialog)
         2487 {
         2488   gpointer Debt;
         2489   GtkWidget *deposit, *entry;
         2490   gchar *text, *title;
         2491   price_t money;
         2492   gboolean withdraw = FALSE;
         2493 
         2494   Debt = g_object_get_data(G_OBJECT(dialog), "debt");
         2495   entry = GTK_WIDGET(g_object_get_data(G_OBJECT(dialog), "entry"));
         2496   text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
         2497   money = strtoprice(text);
         2498   g_free(text);
         2499 
         2500   if (Debt) {
         2501     /* Title of loan shark dialog - (%Tde="The Loan Shark" by default) */
         2502     title = dpg_strdup_printf(_("%/LoanShark window title/%Tde"),
         2503                               Names.LoanSharkName);
         2504     if (money > ClientData.Play->Debt) {
         2505       money = ClientData.Play->Debt;
         2506     }
         2507   } else {
         2508     /* Title of bank dialog - (%Tde="The Bank" by default) */
         2509     title = dpg_strdup_printf(_("%/BankName window title/%Tde"),
         2510                               Names.BankName);
         2511     deposit = GTK_WIDGET(g_object_get_data(G_OBJECT(dialog), "deposit"));
         2512     if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(deposit))) {
         2513       withdraw = TRUE;
         2514     }
         2515   }
         2516 
         2517   if (money < 0) {
         2518     GtkMessageBox(dialog, _("You must enter a positive amount of money!"),
         2519                   title, GTK_MESSAGE_WARNING, MB_OK);
         2520   } else if (!Debt && withdraw && money > ClientData.Play->Bank) {
         2521     GtkMessageBox(dialog, _("There isn't that much money available..."),
         2522                   title, GTK_MESSAGE_WARNING, MB_OK);
         2523   } else if (!withdraw && money > ClientData.Play->Cash) {
         2524     GtkMessageBox(dialog, _("You don't have that much money!"),
         2525                   title, GTK_MESSAGE_WARNING, MB_OK);
         2526   } else {
         2527     text = pricetostr(withdraw ? -money : money);
         2528     SendClientMessage(ClientData.Play, C_NONE,
         2529                       Debt ? C_PAYLOAN : C_DEPOSIT, NULL, text);
         2530     g_free(text);
         2531     gtk_widget_destroy(dialog);
         2532   }
         2533   g_free(title);
         2534 }
         2535 
         2536 void TransferDialog(gboolean Debt)
         2537 {
         2538   GtkWidget *dialog, *button, *label, *radio, *grid, *vbox;
         2539   GtkWidget *hbbox, *hsep, *entry;
         2540   GtkAccelGroup *accel_group;
         2541   GSList *group;
         2542   GString *text;
         2543 
         2544   text = g_string_new("");
         2545 
         2546   dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
         2547   accel_group = gtk_accel_group_new();
         2548   gtk_window_add_accel_group(GTK_WINDOW(dialog), accel_group);
         2549 
         2550   g_signal_connect(G_OBJECT(dialog), "destroy",
         2551                    G_CALLBACK(SendDoneMessage), NULL);
         2552   if (Debt) {
         2553     /* Title of loan shark dialog - (%Tde="The Loan Shark" by default) */
         2554     dpg_string_printf(text, _("%/LoanShark window title/%Tde"),
         2555                        Names.LoanSharkName);
         2556   } else {
         2557     /* Title of bank dialog - (%Tde="The Bank" by default) */
         2558     dpg_string_printf(text, _("%/BankName window title/%Tde"),
         2559                        Names.BankName);
         2560   }
         2561   gtk_window_set_title(GTK_WINDOW(dialog), text->str);
         2562   my_set_dialog_position(GTK_WINDOW(dialog));
         2563   gtk_container_set_border_width(GTK_CONTAINER(dialog), 7);
         2564   gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
         2565   gtk_window_set_transient_for(GTK_WINDOW(dialog),
         2566                                GTK_WINDOW(ClientData.window));
         2567 
         2568   vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 7);
         2569   grid = dp_gtk_grid_new(4, 3, FALSE);
         2570   gtk_grid_set_row_spacing(GTK_GRID(grid), 4);
         2571   gtk_grid_set_column_spacing(GTK_GRID(grid), 4);
         2572 
         2573   /* Display of player's cash in bank or loan shark dialog */
         2574   dpg_string_printf(text, _("Cash: %P"), ClientData.Play->Cash);
         2575   label = gtk_label_new(text->str);
         2576   dp_gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 3, 1, TRUE);
         2577 
         2578   if (Debt) {
         2579     /* Display of player's debt in loan shark dialog */
         2580     dpg_string_printf(text, _("Debt: %P"), ClientData.Play->Debt);
         2581   } else {
         2582     /* Display of player's bank balance in bank dialog */
         2583     dpg_string_printf(text, _("Bank: %P"), ClientData.Play->Bank);
         2584   }
         2585   label = gtk_label_new(text->str);
         2586   dp_gtk_grid_attach(GTK_GRID(grid), label, 0, 1, 3, 1, TRUE);
         2587 
         2588   g_object_set_data(G_OBJECT(dialog), "debt", GINT_TO_POINTER(Debt));
         2589   if (Debt) {
         2590     /* Prompt for paying back a loan */
         2591     label = gtk_label_new(_("Pay back:"));
         2592     dp_gtk_grid_attach(GTK_GRID(grid), label, 0, 2, 1, 2, FALSE);
         2593   } else {
         2594     /* Radio button selected if you want to pay money into the bank */
         2595     radio = gtk_radio_button_new_with_label(NULL, _("Deposit"));
         2596     g_object_set_data(G_OBJECT(dialog), "deposit", radio);
         2597     group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio));
         2598     dp_gtk_grid_attach(GTK_GRID(grid), radio, 0, 2, 1, 1, FALSE);
         2599 
         2600     /* Radio button selected if you want to withdraw money from the bank */
         2601     radio = gtk_radio_button_new_with_label(group, _("Withdraw"));
         2602     dp_gtk_grid_attach(GTK_GRID(grid), radio, 0, 3, 1, 1, FALSE);
         2603   }
         2604   label = gtk_label_new(Currency.Symbol);
         2605   entry = gtk_entry_new();
         2606   gtk_entry_set_text(GTK_ENTRY(entry), "0");
         2607   g_object_set_data(G_OBJECT(dialog), "entry", entry);
         2608   g_signal_connect(G_OBJECT(entry), "activate",
         2609                    G_CALLBACK(TransferOK), dialog);
         2610 
         2611   if (Currency.Prefix) {
         2612     dp_gtk_grid_attach(GTK_GRID(grid), label, 1, 2, 1, 2, FALSE);
         2613     dp_gtk_grid_attach(GTK_GRID(grid), entry, 2, 2, 1, 2, TRUE);
         2614   } else {
         2615     dp_gtk_grid_attach(GTK_GRID(grid), label, 2, 2, 1, 2, FALSE);
         2616     dp_gtk_grid_attach(GTK_GRID(grid), entry, 1, 2, 1, 2, TRUE);
         2617   }
         2618 
         2619   gtk_box_pack_start(GTK_BOX(vbox), grid, TRUE, TRUE, 0);
         2620 
         2621   hsep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
         2622   gtk_box_pack_start(GTK_BOX(vbox), hsep, FALSE, FALSE, 0);
         2623 
         2624   hbbox = my_hbbox_new();
         2625   button = gtk_button_new_with_mnemonic(_("_OK"));
         2626   g_signal_connect(G_OBJECT(button), "clicked",
         2627                    G_CALLBACK(TransferOK), dialog);
         2628   my_gtk_box_pack_start_defaults(GTK_BOX(hbbox), button);
         2629 
         2630   if (Debt && ClientData.Play->Cash >= ClientData.Play->Debt) {
         2631     /* Button to pay back the entire loan/debt */
         2632     button = gtk_button_new_with_label(_("Pay all"));
         2633     g_signal_connect(G_OBJECT(button), "clicked",
         2634                      G_CALLBACK(TransferPayAll), dialog);
         2635     my_gtk_box_pack_start_defaults(GTK_BOX(hbbox), button);
         2636   }
         2637   button = gtk_button_new_with_mnemonic(_("_Cancel"));
         2638   g_signal_connect_swapped(G_OBJECT(button), "clicked",
         2639                            G_CALLBACK(gtk_widget_destroy),
         2640                            G_OBJECT(dialog));
         2641   my_gtk_box_pack_start_defaults(GTK_BOX(hbbox), button);
         2642   gtk_box_pack_start(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
         2643 
         2644   gtk_container_add(GTK_CONTAINER(dialog), vbox);
         2645 
         2646   gtk_widget_show_all(dialog);
         2647 
         2648   g_string_free(text, TRUE);
         2649 }
         2650 
         2651 void ListPlayers(GtkWidget *widget, gpointer data)
         2652 {
         2653   GtkWidget *dialog, *clist, *button, *vbox, *hsep, *hbbox;
         2654   GtkAccelGroup *accel_group;
         2655 
         2656   if (IsShowingPlayerList)
         2657     return;
         2658   dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
         2659   accel_group = gtk_accel_group_new();
         2660   gtk_window_add_accel_group(GTK_WINDOW(dialog), accel_group);
         2661 
         2662   /* Title of player list dialog */
         2663   gtk_window_set_title(GTK_WINDOW(dialog), _("Player List"));
         2664   my_set_dialog_position(GTK_WINDOW(dialog));
         2665 
         2666   gtk_window_set_default_size(GTK_WINDOW(dialog), 200, 180);
         2667   gtk_container_set_border_width(GTK_CONTAINER(dialog), 7);
         2668 
         2669   gtk_window_set_modal(GTK_WINDOW(dialog), FALSE);
         2670   gtk_window_set_transient_for(GTK_WINDOW(dialog),
         2671                                GTK_WINDOW(ClientData.window));
         2672   SetShowing(dialog, &IsShowingPlayerList);
         2673 
         2674   vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 7);
         2675 
         2676   clist = ClientData.PlayerList = CreatePlayerList();
         2677   UpdatePlayerList(clist, FALSE);
         2678   gtk_box_pack_start(GTK_BOX(vbox), clist, TRUE, TRUE, 0);
         2679 
         2680   hsep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
         2681   gtk_box_pack_start(GTK_BOX(vbox), hsep, FALSE, FALSE, 0);
         2682 
         2683   hbbox = my_hbbox_new();
         2684   button = gtk_button_new_with_mnemonic(_("_Close"));
         2685   g_signal_connect_swapped(G_OBJECT(button), "clicked",
         2686                            G_CALLBACK(gtk_widget_destroy),
         2687                            G_OBJECT(dialog));
         2688   my_gtk_box_pack_start_defaults(GTK_BOX(hbbox), button);
         2689 
         2690   gtk_box_pack_start(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
         2691   gtk_container_add(GTK_CONTAINER(dialog), vbox);
         2692   gtk_widget_show_all(dialog);
         2693 }
         2694 
         2695 struct TalkStruct {
         2696   GtkWidget *dialog, *clist, *entry, *checkbutton;
         2697 };
         2698 
         2699 /* Columns in player list */
         2700 enum {
         2701   PLAYER_COL_NAME = 0,
         2702   PLAYER_COL_PT,
         2703   PLAYER_NUM_COLS
         2704 };
         2705 
         2706 static void TalkSendSelected(GtkTreeModel *model, GtkTreePath *path,
         2707                              GtkTreeIter *iter, gpointer data)
         2708 {
         2709   Player *Play;
         2710   gchar *text = data;
         2711   gtk_tree_model_get(model, iter, PLAYER_COL_PT, &Play, -1);
         2712   if (Play) {
         2713     gchar *msg = g_strdup_printf(
         2714                      "%s->%s: %s", GetPlayerName(ClientData.Play),
         2715                      GetPlayerName(Play), text);
         2716     SendClientMessage(ClientData.Play, C_NONE, C_MSGTO, Play, text);
         2717     PrintMessage(msg, "page");
         2718     g_free(msg);
         2719   }
         2720 }
         2721 
         2722 static void TalkSend(GtkWidget *widget, struct TalkStruct *TalkData)
         2723 {
         2724   gboolean AllPlayers;
         2725   gchar *text;
         2726   GString *msg;
         2727 
         2728   AllPlayers =
         2729       gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
         2730                                    (TalkData->checkbutton));
         2731   text = gtk_editable_get_chars(GTK_EDITABLE(TalkData->entry), 0, -1);
         2732   gtk_editable_delete_text(GTK_EDITABLE(TalkData->entry), 0, -1);
         2733   if (!text)
         2734     return;
         2735 
         2736   msg = g_string_new("");
         2737 
         2738   if (AllPlayers) {
         2739     SendClientMessage(ClientData.Play, C_NONE, C_MSG, NULL, text);
         2740     g_string_printf(msg, "%s: %s", GetPlayerName(ClientData.Play), text);
         2741     PrintMessage(msg->str, "talk");
         2742   } else {
         2743     GtkTreeSelection *tsel = gtk_tree_view_get_selection(
         2744                                         GTK_TREE_VIEW(TalkData->clist));
         2745     gtk_tree_selection_selected_foreach(tsel, TalkSendSelected, text);
         2746   }
         2747   g_free(text);
         2748   g_string_free(msg, TRUE);
         2749 }
         2750 
         2751 void TalkToAll(GtkWidget *widget, gpointer data)
         2752 {
         2753   TalkDialog(TRUE);
         2754 }
         2755 
         2756 void TalkToPlayers(GtkWidget *widget, gpointer data)
         2757 {
         2758   TalkDialog(FALSE);
         2759 }
         2760 
         2761 void TalkDialog(gboolean TalkToAll)
         2762 {
         2763   GtkWidget *dialog, *clist, *button, *entry, *label, *vbox, *hsep,
         2764       *checkbutton, *hbbox;
         2765   GtkAccelGroup *accel_group;
         2766   static struct TalkStruct TalkData;
         2767 
         2768   if (IsShowingTalkList)
         2769     return;
         2770   dialog = TalkData.dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
         2771   accel_group = gtk_accel_group_new();
         2772   gtk_window_add_accel_group(GTK_WINDOW(dialog), accel_group);
         2773 
         2774   /* Title of talk dialog */
         2775   gtk_window_set_title(GTK_WINDOW(dialog), _("Talk to player(s)"));
         2776   my_set_dialog_position(GTK_WINDOW(dialog));
         2777 
         2778   gtk_window_set_default_size(GTK_WINDOW(dialog), 200, 190);
         2779   gtk_container_set_border_width(GTK_CONTAINER(dialog), 7);
         2780 
         2781   gtk_window_set_modal(GTK_WINDOW(dialog), FALSE);
         2782   gtk_window_set_transient_for(GTK_WINDOW(dialog),
         2783                                GTK_WINDOW(ClientData.window));
         2784   SetShowing(dialog, &IsShowingTalkList);
         2785 
         2786   vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 7);
         2787 
         2788   clist = TalkData.clist = ClientData.TalkList = CreatePlayerList();
         2789   UpdatePlayerList(clist, FALSE);
         2790   gtk_tree_selection_set_mode(
         2791           gtk_tree_view_get_selection(GTK_TREE_VIEW(clist)),
         2792           GTK_SELECTION_MULTIPLE);
         2793   gtk_box_pack_start(GTK_BOX(vbox), clist, TRUE, TRUE, 0);
         2794 
         2795   checkbutton = TalkData.checkbutton =
         2796       /* Checkbutton set if you want to talk to all players */
         2797       gtk_check_button_new_with_label(_("Talk to all players"));
         2798 
         2799   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton), TalkToAll);
         2800   gtk_box_pack_start(GTK_BOX(vbox), checkbutton, FALSE, FALSE, 0);
         2801 
         2802   /* Prompt for you to enter the message to be sent to other players */
         2803   label = gtk_label_new(_("Message:-"));
         2804 
         2805   gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
         2806 
         2807   entry = TalkData.entry = gtk_entry_new();
         2808   g_signal_connect(G_OBJECT(entry), "activate",
         2809                    G_CALLBACK(TalkSend), (gpointer)&TalkData);
         2810   gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
         2811 
         2812   hsep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
         2813   gtk_box_pack_start(GTK_BOX(vbox), hsep, FALSE, FALSE, 0);
         2814 
         2815   hbbox = my_hbbox_new();
         2816 
         2817   /* Button to send a message to other players */
         2818   button = gtk_button_new_with_label(_("Send"));
         2819 
         2820   g_signal_connect(G_OBJECT(button), "clicked",
         2821                    G_CALLBACK(TalkSend), (gpointer)&TalkData);
         2822   my_gtk_box_pack_start_defaults(GTK_BOX(hbbox), button);
         2823 
         2824   button = gtk_button_new_with_mnemonic(_("_Close"));
         2825   g_signal_connect_swapped(G_OBJECT(button), "clicked",
         2826                            G_CALLBACK(gtk_widget_destroy),
         2827                            G_OBJECT(dialog));
         2828   my_gtk_box_pack_start_defaults(GTK_BOX(hbbox), button);
         2829 
         2830   gtk_box_pack_start(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
         2831 
         2832   gtk_container_add(GTK_CONTAINER(dialog), vbox);
         2833   gtk_widget_show_all(dialog);
         2834 }
         2835 
         2836 GtkWidget *CreatePlayerList(void)
         2837 {
         2838   GtkWidget *view;
         2839   GtkListStore *store;
         2840   GtkCellRenderer *renderer;
         2841 
         2842   store = gtk_list_store_new(PLAYER_NUM_COLS, G_TYPE_STRING, G_TYPE_POINTER);
         2843 
         2844   view = gtk_tree_view_new();
         2845   renderer = gtk_cell_renderer_text_new();
         2846   gtk_tree_view_insert_column_with_attributes(
         2847                GTK_TREE_VIEW(view), -1, "Name", renderer, "text",
         2848                PLAYER_COL_NAME, NULL);
         2849   gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store));
         2850   g_object_unref(store);  /* so it is freed when the view is */
         2851   gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(view), FALSE);
         2852   return view;
         2853 }
         2854 
         2855 void UpdatePlayerList(GtkWidget *clist, gboolean IncludeSelf)
         2856 {
         2857   GtkListStore *store;
         2858   GSList *list;
         2859   GtkTreeIter iter;
         2860   Player *Play;
         2861 
         2862   store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(clist)));
         2863 
         2864   /* Don't update the widget until we're done */
         2865   g_object_ref(store);
         2866   gtk_tree_view_set_model(GTK_TREE_VIEW(clist), NULL);
         2867 
         2868   gtk_list_store_clear(store);
         2869   for (list = FirstClient; list; list = g_slist_next(list)) {
         2870     Play = (Player *)list->data;
         2871     if (IncludeSelf || Play != ClientData.Play) {
         2872       gtk_list_store_append(store, &iter);
         2873       gtk_list_store_set(store, &iter, PLAYER_COL_NAME, GetPlayerName(Play),
         2874                          PLAYER_COL_PT, Play, -1);
         2875     }
         2876   }
         2877 
         2878   gtk_tree_view_set_model(GTK_TREE_VIEW(clist), GTK_TREE_MODEL(store));
         2879   g_object_unref(store);
         2880 }
         2881 
         2882 static void ErrandOK(GtkWidget *widget, GtkWidget *clist)
         2883 {
         2884   GtkTreeSelection *treesel;
         2885   GtkTreeModel *model;
         2886   GtkTreeIter iter;
         2887   GtkWidget *dialog;
         2888   gint ErrandType;
         2889 
         2890 
         2891   dialog = GTK_WIDGET(g_object_get_data(G_OBJECT(widget), "dialog"));
         2892   ErrandType = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget),
         2893                                                  "errandtype"));
         2894   treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(clist));
         2895   if (gtk_tree_selection_get_selected(treesel, &model, &iter)) {
         2896     Player *Play;
         2897     gtk_tree_model_get(model, &iter, PLAYER_COL_PT, &Play, -1);
         2898     if (ErrandType == ET_SPY) {
         2899       SendClientMessage(ClientData.Play, C_NONE, C_SPYON, Play, NULL);
         2900     } else {
         2901       SendClientMessage(ClientData.Play, C_NONE, C_TIPOFF, Play, NULL);
         2902     }
         2903     gtk_widget_destroy(dialog);
         2904   }
         2905 }
         2906 
         2907 void SpyOnPlayer(GtkWidget *widget, gpointer data)
         2908 {
         2909   ErrandDialog(ET_SPY);
         2910 }
         2911 
         2912 void TipOff(GtkWidget *widget, gpointer data)
         2913 {
         2914   ErrandDialog(ET_TIPOFF);
         2915 }
         2916 
         2917 void ErrandDialog(gint ErrandType)
         2918 {
         2919   GtkWidget *dialog, *clist, *button, *vbox, *hbbox, *hsep, *label;
         2920   GtkAccelGroup *accel_group;
         2921   gchar *text;
         2922 
         2923   dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
         2924   accel_group = gtk_accel_group_new();
         2925   gtk_window_add_accel_group(GTK_WINDOW(dialog), accel_group);
         2926 
         2927   gtk_container_set_border_width(GTK_CONTAINER(dialog), 7);
         2928 
         2929   gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
         2930   gtk_window_set_transient_for(GTK_WINDOW(dialog),
         2931                                GTK_WINDOW(ClientData.window));
         2932 
         2933   vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 7);
         2934 
         2935   if (ErrandType == ET_SPY) {
         2936     /* Title of dialog to select a player to spy on */
         2937     gtk_window_set_title(GTK_WINDOW(dialog), _("Spy On Player"));
         2938 
         2939     /* Informative text for "spy on player" dialog. (%tde = "bitch",
         2940        "bitch", "guns", "drugs", respectively, by default) */
         2941     text = dpg_strdup_printf(_("Please choose the player to spy on. "
         2942                                "Your %tde will\nthen offer his "
         2943                                "services to the player, and if "
         2944                                "successful,\nyou will be able to "
         2945                                "view the player's stats with the\n"
         2946                                "\"Get spy reports\" menu. Remember "
         2947                                "that the %tde will leave\nyou, so "
         2948                                "any %tde or %tde that he's "
         2949                                "carrying may be lost!"), Names.Bitch,
         2950                              Names.Bitch, Names.Guns, Names.Drugs);
         2951     label = gtk_label_new(text);
         2952     g_free(text);
         2953   } else {
         2954 
         2955     /* Title of dialog to select a player to tip the cops off to */
         2956     gtk_window_set_title(GTK_WINDOW(dialog), _("Tip Off The Cops"));
         2957 
         2958     /* Informative text for "tip off cops" dialog. (%tde = "bitch",
         2959        "bitch", "guns", "drugs", respectively, by default) */
         2960     text = dpg_strdup_printf(_("Please choose the player to tip off "
         2961                                "the cops to. Your %tde will\nhelp "
         2962                                "the cops to attack that player, "
         2963                                "and then report back to you\non "
         2964                                "the encounter. Remember that the "
         2965                                "%tde will leave you temporarily,\n"
         2966                                "so any %tde or %tde that he's "
         2967                                "carrying may be lost!"), Names.Bitch,
         2968                              Names.Bitch, Names.Guns, Names.Drugs);
         2969     label = gtk_label_new(text);
         2970     g_free(text);
         2971   }
         2972   my_set_dialog_position(GTK_WINDOW(dialog));
         2973 
         2974   gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
         2975 
         2976   clist = ClientData.PlayerList = CreatePlayerList();
         2977   UpdatePlayerList(clist, FALSE);
         2978   gtk_box_pack_start(GTK_BOX(vbox), clist, TRUE, TRUE, 0);
         2979 
         2980   hsep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
         2981   gtk_box_pack_start(GTK_BOX(vbox), hsep, FALSE, FALSE, 0);
         2982 
         2983   hbbox = my_hbbox_new();
         2984   button = gtk_button_new_with_mnemonic(_("_OK"));
         2985   g_object_set_data(G_OBJECT(button), "dialog", dialog);
         2986   g_object_set_data(G_OBJECT(button), "errandtype",
         2987                     GINT_TO_POINTER(ErrandType));
         2988   g_signal_connect(G_OBJECT(button), "clicked",
         2989                    G_CALLBACK(ErrandOK), (gpointer)clist);
         2990   my_gtk_box_pack_start_defaults(GTK_BOX(hbbox), button);
         2991   button = gtk_button_new_with_mnemonic(_("_Cancel"));
         2992   g_signal_connect_swapped(G_OBJECT(button), "clicked",
         2993                            G_CALLBACK(gtk_widget_destroy),
         2994                            G_OBJECT(dialog));
         2995   my_gtk_box_pack_start_defaults(GTK_BOX(hbbox), button);
         2996 
         2997   gtk_box_pack_start(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
         2998   gtk_container_add(GTK_CONTAINER(dialog), vbox);
         2999   gtk_widget_show_all(dialog);
         3000 }
         3001 
         3002 void SackBitch(GtkWidget *widget, gpointer data)
         3003 {
         3004   char *title, *text;
         3005 
         3006   /* Cannot sack bitches if you don't have any! */
         3007   if (ClientData.Play->Bitches.Carried <= 0)
         3008     return;
         3009 
         3010   /* Title of dialog to sack a bitch (%Tde = "Bitch" by default) */
         3011   title = dpg_strdup_printf(_("%/Sack Bitch dialog title/Sack %Tde"),
         3012                             Names.Bitch);
         3013 
         3014   /* Confirmation message for sacking a bitch. (%tde = "guns", "drugs",
         3015      "bitch", respectively, by default) */
         3016   text = dpg_strdup_printf(_("Are you sure? (Any %tde or %tde carried\n"
         3017                              "by this %tde may be lost!)"), Names.Guns,
         3018                            Names.Drugs, Names.Bitch);
         3019 
         3020   if (GtkMessageBox(ClientData.window, text, title, GTK_MESSAGE_QUESTION,
         3021                     MB_YESNO) == IDYES) {
         3022     ClientData.Play->Bitches.Carried--;
         3023     UpdateMenus();
         3024     SendClientMessage(ClientData.Play, C_NONE, C_SACKBITCH, NULL, NULL);
         3025   }
         3026   g_free(text);
         3027   g_free(title);
         3028 }
         3029 
         3030 void CreateInventory(GtkWidget *hbox, gchar *Objects,
         3031                      GtkAccelGroup *accel_group, gboolean CreateButtons,
         3032                      gboolean CreateHere, struct InventoryWidgets *widgets,
         3033                      GCallback CallBack)
         3034 {
         3035   GtkWidget *scrollwin, *tv, *vbbox, *frame[2], *button[3];
         3036   GtkCellRenderer *renderer;
         3037   GtkListStore *store;
         3038   GtkTreeSelection *treesel;
         3039   gint i, mini, icol;
         3040   GString *text;
         3041   gchar *titles[2][2];
         3042   gchar *button_text[3];
         3043   gpointer button_type[3] = { BT_BUY, BT_SELL, BT_DROP };
         3044 
         3045   /* Column titles for display of drugs/guns carried or available for
         3046      purchase */
         3047   titles[0][0] = titles[1][0] = _("Name");
         3048   titles[0][1] = _("Price");
         3049   titles[1][1] = _("Number");
         3050 
         3051   /* Button titles for buying/selling/dropping guns or drugs */
         3052   button_text[0] = _("_Buy ->");
         3053   button_text[1] = _("<- _Sell");
         3054   button_text[2] = _("_Drop <-");
         3055 
         3056   text = g_string_new("");
         3057 
         3058   if (CreateHere) {
         3059     /* Title of the display of available drugs/guns (%Tde = "Guns" or
         3060        "Drugs" by default) */
         3061     dpg_string_printf(text, _("%Tde here"), Objects);
         3062     widgets->HereFrame = frame[0] = gtk_frame_new(text->str);
         3063   }
         3064 
         3065   /* Title of the display of carried drugs/guns (%Tde = "Guns" or "Drugs"
         3066      by default) */
         3067   dpg_string_printf(text, _("%Tde carried"), Objects);
         3068 
         3069   widgets->CarriedFrame = frame[1] = gtk_frame_new(text->str);
         3070 
         3071   widgets->HereList = widgets->CarriedList = NULL;
         3072   mini = (CreateHere ? 0 : 1);
         3073   for (i = mini; i < 2; i++) {
         3074     GtkWidget *hbox2 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
         3075     gtk_box_set_homogeneous(GTK_BOX(hbox2), TRUE);
         3076     gtk_container_set_border_width(GTK_CONTAINER(frame[i]), 3);
         3077 
         3078     tv = gtk_scrolled_tree_view_new(&scrollwin);
         3079     renderer = gtk_cell_renderer_text_new();
         3080     store = gtk_list_store_new(INVEN_NUM_COLS, G_TYPE_STRING,
         3081                                G_TYPE_STRING, G_TYPE_INT);
         3082     gtk_tree_view_set_model(GTK_TREE_VIEW(tv), GTK_TREE_MODEL(store));
         3083     g_object_unref(store);
         3084     for (icol = 0; icol < 2; ++icol) {
         3085       GtkTreeViewColumn *col;
         3086       if (i == 0 && icol == 1) {
         3087         /* Right align prices */
         3088         GtkCellRenderer *rren = gtk_cell_renderer_text_new();
         3089         g_object_set(G_OBJECT(rren), "xalign", 1.0, NULL);
         3090         col = gtk_tree_view_column_new_with_attributes(
         3091                        titles[i][icol], rren, "text", icol, NULL);
         3092         gtk_tree_view_column_set_alignment(col, 1.0);
         3093       } else {
         3094         col = gtk_tree_view_column_new_with_attributes(
         3095                        titles[i][icol], renderer, "text", icol, NULL);
         3096       }
         3097       gtk_tree_view_insert_column(GTK_TREE_VIEW(tv), col, -1);
         3098     }
         3099     gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(tv), FALSE);
         3100     treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
         3101     gtk_tree_selection_set_mode(treesel, GTK_SELECTION_SINGLE);
         3102     gtk_box_pack_start(GTK_BOX(hbox2), scrollwin, TRUE, TRUE, 0);
         3103     gtk_container_set_border_width(GTK_CONTAINER(hbox2), 3);
         3104     gtk_container_add(GTK_CONTAINER(frame[i]), hbox2);
         3105     if (i == 0) {
         3106       widgets->HereList = tv;
         3107     } else {
         3108       widgets->CarriedList = tv;
         3109     }
         3110   }
         3111   if (CreateHere) {
         3112     gtk_box_pack_start(GTK_BOX(hbox), frame[0], TRUE, TRUE, 0);
         3113   }
         3114 
         3115   if (CreateButtons) {
         3116     widgets->vbbox = vbbox = gtk_button_box_new(GTK_ORIENTATION_VERTICAL);
         3117     gtk_button_box_set_layout(GTK_BUTTON_BOX(vbbox), GTK_BUTTONBOX_SPREAD);
         3118 
         3119 
         3120     for (i = 0; i < 3; i++) {
         3121       button[i] = gtk_button_new_with_label("");
         3122       SetAccelerator(button[i], _(button_text[i]), button[i],
         3123                      "clicked", accel_group, FALSE);
         3124       if (CallBack) {
         3125         g_signal_connect(G_OBJECT(button[i]), "clicked",
         3126                          G_CALLBACK(CallBack), button_type[i]);
         3127       }
         3128       gtk_box_pack_start(GTK_BOX(vbbox), button[i], TRUE, TRUE, 0);
         3129     }
         3130     widgets->BuyButton = button[0];
         3131     widgets->SellButton = button[1];
         3132     widgets->DropButton = button[2];
         3133     gtk_box_pack_start(GTK_BOX(hbox), vbbox, FALSE, FALSE, 0);
         3134   } else {
         3135     widgets->vbbox = NULL;
         3136   }
         3137 
         3138   gtk_box_pack_start(GTK_BOX(hbox), frame[1], TRUE, TRUE, 0);
         3139   g_string_free(text, TRUE);
         3140 }
         3141 
         3142 void SetShowing(GtkWidget *window, gboolean *showing)
         3143 {
         3144   g_assert(showing);
         3145 
         3146   *showing = TRUE;
         3147   g_signal_connect(G_OBJECT(window), "destroy",
         3148                    G_CALLBACK(DestroyShowing), (gpointer)showing);
         3149 }
         3150 
         3151 void DestroyShowing(GtkWidget *widget, gpointer data)
         3152 {
         3153   gboolean *IsShowing = (gboolean *)data;
         3154 
         3155   if (IsShowing) {
         3156     *IsShowing = FALSE;
         3157   }
         3158 }
         3159 
         3160 static void NewNameOK(GtkWidget *widget, GtkWidget *window)
         3161 {
         3162   GtkWidget *entry;
         3163   gchar *text;
         3164 
         3165   entry = GTK_WIDGET(g_object_get_data(G_OBJECT(window), "entry"));
         3166   text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
         3167   if (text[0]) {
         3168     StripTerminators(text);
         3169     SetPlayerName(ClientData.Play, text);
         3170     SendNullClientMessage(ClientData.Play, C_NONE, C_NAME, NULL, text);
         3171     gtk_widget_destroy(window);
         3172   }
         3173   g_free(text);
         3174 }
         3175 
         3176 void NewNameDialog(void)
         3177 {
         3178   GtkWidget *window, *button, *hsep, *vbox, *label, *entry;
         3179   GtkAccelGroup *accel_group;
         3180 
         3181   window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
         3182   accel_group = gtk_accel_group_new();
         3183   gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
         3184 
         3185   /* Title of dialog for changing a player's name */
         3186   gtk_window_set_title(GTK_WINDOW(window), _("Change Name"));
         3187   my_set_dialog_position(GTK_WINDOW(window));
         3188 
         3189   gtk_window_set_modal(GTK_WINDOW(window), TRUE);
         3190   gtk_window_set_transient_for(GTK_WINDOW(window),
         3191                                GTK_WINDOW(ClientData.window));
         3192   gtk_container_set_border_width(GTK_CONTAINER(window), 7);
         3193   g_signal_connect(G_OBJECT(window), "delete_event",
         3194                    G_CALLBACK(DisallowDelete), NULL);
         3195 
         3196   vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 7);
         3197 
         3198   /* Informational text to prompt the player to change his/her name */
         3199   label = gtk_label_new(_("Unfortunately, somebody else is already "
         3200                           "using \"your\" name. Please change it:-"));
         3201   gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
         3202 
         3203   entry = gtk_entry_new();
         3204   g_object_set_data(G_OBJECT(window), "entry", entry);
         3205   g_signal_connect(G_OBJECT(entry), "activate",
         3206                    G_CALLBACK(NewNameOK), window);
         3207   gtk_entry_set_text(GTK_ENTRY(entry), GetPlayerName(ClientData.Play));
         3208   gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
         3209 
         3210   hsep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
         3211   gtk_box_pack_start(GTK_BOX(vbox), hsep, FALSE, FALSE, 0);
         3212 
         3213   button = gtk_button_new_with_mnemonic(_("_OK"));
         3214   g_signal_connect(G_OBJECT(button), "clicked",
         3215                    G_CALLBACK(NewNameOK), window);
         3216   gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);
         3217   gtk_widget_set_can_default(button, TRUE);
         3218   gtk_widget_grab_default(button);
         3219 
         3220   gtk_container_add(GTK_CONTAINER(window), vbox);
         3221   gtk_widget_show_all(window);
         3222 }
         3223 
         3224 gint DisallowDelete(GtkWidget *widget, GdkEvent *event, gpointer data)
         3225 {
         3226   return TRUE;
         3227 }
         3228 
         3229 void GunShopDialog(void)
         3230 {
         3231   GtkWidget *window, *button, *hsep, *vbox, *hbox, *hbbox;
         3232   GtkAccelGroup *accel_group;
         3233   gchar *text;
         3234 
         3235   window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
         3236   gtk_window_set_default_size(GTK_WINDOW(window), 600, 190);
         3237   g_signal_connect(G_OBJECT(window), "destroy",
         3238                    G_CALLBACK(SendDoneMessage), NULL);
         3239   accel_group = gtk_accel_group_new();
         3240   gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
         3241 
         3242   /* Title of 'gun shop' dialog in GTK+ client (%Tde="Dan's House of Guns"
         3243      by default) */
         3244   text = dpg_strdup_printf(_("%/GTK GunShop window title/%Tde"),
         3245                            Names.GunShopName);
         3246   gtk_window_set_title(GTK_WINDOW(window), text);
         3247   my_set_dialog_position(GTK_WINDOW(window));
         3248   g_free(text);
         3249   gtk_window_set_modal(GTK_WINDOW(window), TRUE);
         3250   gtk_window_set_transient_for(GTK_WINDOW(window),
         3251                                GTK_WINDOW(ClientData.window));
         3252   gtk_container_set_border_width(GTK_CONTAINER(window), 7);
         3253   SetShowing(window, &IsShowingGunShop);
         3254 
         3255   vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 7);
         3256 
         3257   hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 7);
         3258   CreateInventory(hbox, Names.Guns, accel_group, TRUE, TRUE,
         3259                   &ClientData.Gun, G_CALLBACK(DealGuns));
         3260 
         3261   gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
         3262 
         3263   hsep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
         3264   gtk_box_pack_start(GTK_BOX(vbox), hsep, FALSE, FALSE, 0);
         3265 
         3266   hbbox = my_hbbox_new();
         3267   button = gtk_button_new_with_mnemonic(_("_Close"));
         3268   g_signal_connect_swapped(G_OBJECT(button), "clicked",
         3269                            G_CALLBACK(gtk_widget_destroy),
         3270                            G_OBJECT(window));
         3271   my_gtk_box_pack_start_defaults(GTK_BOX(hbbox), button);
         3272 
         3273   gtk_box_pack_start(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
         3274   gtk_container_add(GTK_CONTAINER(window), vbox);
         3275 
         3276   UpdateInventory(&ClientData.Gun, ClientData.Play->Guns, NumGun, FALSE);
         3277   gtk_widget_show_all(window);
         3278 }
         3279 
         3280 void UpdatePlayerLists(void)
         3281 {
         3282   if (IsShowingPlayerList) {
         3283     UpdatePlayerList(ClientData.PlayerList, FALSE);
         3284   }
         3285   if (IsShowingTalkList) {
         3286     UpdatePlayerList(ClientData.TalkList, FALSE);
         3287   }
         3288 }
         3289 
         3290 void GetSpyReports(GtkWidget *Widget, gpointer data)
         3291 {
         3292   SendClientMessage(ClientData.Play, C_NONE, C_CONTACTSPY, NULL, NULL);
         3293 }
         3294 
         3295 static void DestroySpyReports(GtkWidget *widget, gpointer data)
         3296 {
         3297   SpyReportsDialog = NULL;
         3298 }
         3299 
         3300 static void CreateSpyReports(void)
         3301 {
         3302   GtkWidget *window, *button, *vbox, *notebook;
         3303   GtkAccelGroup *accel_group;
         3304 
         3305   SpyReportsDialog = window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
         3306   accel_group = gtk_accel_group_new();
         3307   g_object_set_data(G_OBJECT(window), "accel_group", accel_group);
         3308   gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
         3309 
         3310   /* Title of window to display reports from spies with other players */
         3311   gtk_window_set_title(GTK_WINDOW(window), _("Spy reports"));
         3312   my_set_dialog_position(GTK_WINDOW(window));
         3313 
         3314   gtk_window_set_modal(GTK_WINDOW(window), TRUE);
         3315   gtk_window_set_transient_for(GTK_WINDOW(window),
         3316                                GTK_WINDOW(ClientData.window));
         3317   gtk_container_set_border_width(GTK_CONTAINER(window), 7);
         3318   g_signal_connect(G_OBJECT(window), "destroy",
         3319                    G_CALLBACK(DestroySpyReports), NULL);
         3320 
         3321   vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
         3322   notebook = gtk_notebook_new();
         3323   g_object_set_data(G_OBJECT(window), "notebook", notebook);
         3324 
         3325   gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0);
         3326 
         3327   button = gtk_button_new_with_mnemonic(_("_Close"));
         3328   g_signal_connect_swapped(G_OBJECT(button), "clicked",
         3329                            G_CALLBACK(gtk_widget_destroy),
         3330                            G_OBJECT(window));
         3331   gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);
         3332 
         3333   gtk_container_add(GTK_CONTAINER(window), vbox);
         3334 
         3335   gtk_widget_show_all(window);
         3336 }
         3337 
         3338 void DisplaySpyReports(Player *Play)
         3339 {
         3340   GtkWidget *dialog, *notebook, *vbox, *hbox, *frame, *label, *grid;
         3341   GtkAccelGroup *accel_group;
         3342   struct StatusWidgets Status;
         3343   struct InventoryWidgets SpyDrugs, SpyGuns;
         3344 
         3345   if (!SpyReportsDialog)
         3346     CreateSpyReports();
         3347   dialog = SpyReportsDialog;
         3348   notebook = GTK_WIDGET(g_object_get_data(G_OBJECT(dialog), "notebook"));
         3349   accel_group =
         3350       (GtkAccelGroup *)(g_object_get_data(G_OBJECT(dialog), "accel_group"));
         3351   vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
         3352   frame = gtk_frame_new("Stats");
         3353   gtk_container_set_border_width(GTK_CONTAINER(frame), 3);
         3354   grid = CreateStatusWidgets(&Status);
         3355   gtk_container_add(GTK_CONTAINER(frame), grid);
         3356   gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0);
         3357 
         3358   hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
         3359   CreateInventory(hbox, Names.Drugs, accel_group, FALSE, FALSE, &SpyDrugs,
         3360                   NULL);
         3361   CreateInventory(hbox, Names.Guns, accel_group, FALSE, FALSE, &SpyGuns,
         3362                   NULL);
         3363 
         3364   gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
         3365   label = gtk_label_new(GetPlayerName(Play));
         3366 
         3367   DisplayStats(Play, &Status);
         3368   UpdateInventory(&SpyDrugs, Play->Drugs, NumDrug, TRUE);
         3369   UpdateInventory(&SpyGuns, Play->Guns, NumGun, FALSE);
         3370 
         3371   gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, label);
         3372 
         3373   gtk_widget_show_all(notebook);
         3374 }