| ---
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
25 #endif
26
27 #include
28 #include
29 #include
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, ""},
163 {N_("/Game/_New..."), "N", NewGame, 0, NULL},
164 {N_("/Game/_Abandon..."), "A", AbandonGame, 0, NULL},
165 {N_("/Game/_Options..."), "O", OptDialog, 0, NULL},
166 {N_("/Game/Enable _sound"), NULL, ToggleSound, 0, ""},
167 {N_("/Game/_Quit..."), "Q", QuitGame, 0, NULL},
168 {N_("/_Talk"), NULL, NULL, 0, ""},
169 {N_("/Talk/To _All..."), NULL, TalkToAll, 0, NULL},
170 {N_("/Talk/To _Player..."), NULL, TalkToPlayers, 0, NULL},
171 {N_("/_List"), NULL, NULL, 0, ""},
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, ""},
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, ""},
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 "/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 "/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 "/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 "/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 "/Talk"),
2034 InGame && Network);
2035 gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
2036 (ClientData.Menu, "/Game/Options..."),
2037 !InGame);
2038 gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
2039 (ClientData.Menu, "/Game/Abandon..."),
2040 InGame);
2041 gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
2042 (ClientData.Menu, "/List/Inventory..."),
2043 InGame);
2044 gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
2045 (ClientData.Menu, "/List/Players..."),
2046 InGame && Network);
2047 gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
2048 (ClientData.Menu, "/Errands"), InGame);
2049 gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
2050 (ClientData.Menu, "/Errands/Spy..."),
2051 InGame && MultiPlayer);
2052 gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
2053 (ClientData.Menu, "/Errands/Tipoff..."),
2054 InGame && MultiPlayer);
2055 gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
2056 (ClientData.Menu,
2057 "/Errands/Sack Bitch..."), Bitches > 0);
2058 gtk_widget_set_sensitive(dp_gtk_item_factory_get_widget
2059 (ClientData.Menu,
2060 "/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("",
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, "");
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 "/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 } |