1 /* $Header: /home/jcb/MahJong/newmj/RCS/gui.c,v 12.29 2020/05/23 10:07:21 jcb Exp $
2 * gui.c
3 * A graphical interface, using gtk.
4 */
5 /****************** COPYRIGHT STATEMENT **********************
6 * This file is Copyright (c) 2000 by J. C. Bradfield. *
7 * Distribution and use is governed by the LICENCE file that *
8 * accompanies this file. *
9 * The moral rights of the author are asserted. *
10 * *
11 ***************** DISCLAIMER OF WARRANTY ********************
12 * This code is not warranted fit for any purpose. See the *
13 * LICENCE file for further information. *
14 * *
15 *************************************************************/
16
17
18 #include "gui.h"
19 #ifndef WIN32
20 #ifndef GTK2
21 /* this is a bit sordid. This is needed for iconifying windows */
22 #include <gdk/gdkx.h>
23 #endif
24 #endif
25
26 /* for a debugging hack */
27 #ifndef WIN32
28 #include <signal.h>
29 #endif
30
31 /* string constants for gtk setup */
32 #include "gtkrc.h"
33
34
35 /* local forward declarations */
36 static AnimInfo *adjust_wall_loose(int tile_drawn);
37 static void playerdisp_popup_claim_window(PlayerDisp *pd, const char *lab,int thinking);
38 static gint check_claim_window(gpointer data);
39 static void create_wall(void);
40 static void clear_wall(void);
41 static int get_relative_posn(GtkWidget *w, GtkWidget *z, int *x, int *y);
42 static AnimInfo *playerdisp_discard(PlayerDisp *pd,Tile t);
43 static void playerdisp_init(PlayerDisp *pd, int ori);
44 static void playerdisp_update(PlayerDisp *pd, CMsgUnion *m);
45 static int playerdisp_update_concealed(PlayerDisp *pd,Tile t);
46 static void playerdisp_clear_discards(PlayerDisp *pd);
47 static void iconify_window(GtkWidget *w);
48 static void uniconify_window(GtkWidget *w);
49 static int read_rcfile(char *rcfile);
50 static void rotate_pixmap(GdkPixmap *east, GdkPixmap **south,
51 GdkPixmap **west, GdkPixmap **north);
52 static void change_tile_selection(GtkWidget *w,gpointer data);
53 static void move_selected_tile(GtkWidget *w,gpointer data);
54 static void server_input_callback(gpointer data, gint source,
55 GdkInputCondition condition);
56 static void start_animation(AnimInfo *source,AnimInfo *target);
57 static void stdin_input_callback(gpointer data, gint source,
58 GdkInputCondition condition);
59 /* currently used only in this file */
60 void button_set_tile_without_tiletip(GtkWidget *b, Tile t, int ori);
61
62
63 /* This global variable contains XPM data for fallback tiles.
64 It is defined in fbtiles.c */
65 extern char **fallbackpixmaps[];
66
67 /*********************
68 GLOBAL VARIABLES
69 *********************/
70
71 /* The search path for tile sets */
72 /* Confusing terminology: this is nothing to do with tilesets
73 as in pungs. */
74 #ifndef TILESETPATH
75 #define TILESETPATH "."
76 #endif
77 /* And the name of the tile set */
78 #ifndef TILESET
79 #define TILESET NULL
80 #endif
81 /* separator character in search paths */
82 #ifdef WIN32
83 #define PATHSEP ';'
84 #else
85 #define PATHSEP ':'
86 #endif
87 /* the tileset search path given on the command line or via
88 options. */
89 char *tileset_path = TILESETPATH;
90 /* ditto for the tile set */
91 char *tileset = TILESET;
92
93 static char *tilepixmapdir; /* where to find tiles */
94 int debug = 0;
95 int server_pversion;
96 PlayerP our_player;
97 int our_id;
98 seats our_seat; /* our seat in the game */
99 /* convert from a game seat to a position on our display board */
100 #define game_to_board(s) ((s+NUM_SEATS-our_seat)%NUM_SEATS)
101 /* and conversely */
102 #define board_to_game(s) ((s+our_seat)%NUM_SEATS)
103 /* convert from game wall position to board wall position */
104 #define wall_game_to_board(i) ((i + wallstart) % WALL_SIZE)
105 /* and the other way */
106 #define wall_board_to_game(i) ((i + WALL_SIZE - wallstart)%WALL_SIZE)
107 Game *the_game;
108 int selected_button; /* the index of the user's selected
109 tile, or -1 if none */
110
111 int monitor = 0; /* are we playing normally, or instrumenting
112 somebody else? */
113
114 int game_over = 0;
115
116 int closed_set_in_progress = 0;
117
118 static int do_connect = 0; /* --connect given? */
119
120 /* tag for the server input callback */
121 static gint server_callback_tag = 0;
122 static gint stdin_callback_tag = 0; /* and stdin */
123
124 /* This is a list of windows (i.e. GTK windows) that should be iconified along
125 with the main window. It's null-terminated, for simplicity.
126 The second element of each struct records whether the window
127 is visible at the time the main window is iconified.
128 */
129
130 static struct { GtkWidget **window; int visible; } windows_following_main[] =
131 { {&textwindow,0}, {&messagewindow,0}, {&status_window,0},
132 /* these will only be relevant when they're standalone */
133 {&discard_dialog->widget,0}, {&chow_dialog,0}, {&ds_dialog,0},
134 {&continue_dialog,0}, {&turn_dialog,0},
135 {&scoring_dialog,0},
136 /* these should always be relevant */
137 {&open_dialog,0}, {&nag_window,0},
138 {&display_option_dialog,0}, {&game_option_dialog,0},
139 {&game_prefs_dialog,0}, {&playing_prefs_dialog,0},
140 {NULL,0} };
141
142 /* callback attached to [un]map_event */
vis_callback(GtkWidget * w UNUSED,GdkEvent * ev,gpointer data UNUSED)143 static void vis_callback(GtkWidget *w UNUSED, GdkEvent *ev, gpointer data UNUSED)
144 {
145 if ( ev->type == GDK_MAP ) {
146 int i;
147
148 /* map previously open subwindows */
149 if ( iconify_dialogs_with_main ) {
150 /* unfortunately, if we have a wm that raises on map, but waits
151 for the map before raising, we will find that the main window
152 is raised over the subwindows, which is probably not intended.
153 So we sleep a little to try to wait for it. */
154 usleep(50000);
155 for ( i = 0; windows_following_main[i].window ; i++ ) {
156 GtkWidget *ww = *windows_following_main[i].window;
157 if ( ww == NULL ) continue;
158 /* not completely sure why this is needed, but it is */
159 #ifdef GTK2
160 if ( ! GDK_IS_WINDOW((gtk_widget_get_window(ww) )) ) continue;
161 #endif
162 if ( windows_following_main[i].visible )
163 uniconify_window(ww);
164 }
165 }
166 } else if ( ev->type == GDK_UNMAP ) {
167 int i = 0;
168
169 /* unmap any auxiliary windows that are open, and keep state */
170 if ( iconify_dialogs_with_main ) {
171 for ( i = 0; windows_following_main[i].window ; i++ ) {
172 GtkWidget *ww = *windows_following_main[i].window;
173 if ( ww == NULL ) continue;
174 #ifdef GTK2
175 if ( ! GDK_IS_WINDOW((gtk_widget_get_window(ww) )) ) continue;
176 #endif
177 if (
178 #ifdef GTK2
179 ! (gdk_window_get_state(gtk_widget_get_window(ww)) && GDK_WINDOW_STATE_ICONIFIED)
180 #else
181 // this doesn't really work, but it's the best we can do in gtk1
182 GTK_WIDGET_VISIBLE(ww)
183 #endif
184 ) {
185 iconify_window(ww);
186 windows_following_main[i].visible = 1;
187 } else {
188 windows_following_main[i].visible = 0;
189 }
190 }
191 }
192 } else {
193 warn("vis_callback called on unexpected event");
194 }
195 }
196
197 /* These are used as time stamps. now_time() is the time since program
198 start measured in milliseconds. start_tv is the start time.
199 */
200
201 static struct timeval start_tv;
now_time(void)202 static int now_time(void) {
203 struct timeval now_tv;
204 gettimeofday(&now_tv,NULL);
205 return (now_tv.tv_sec-start_tv.tv_sec)*1000
206 +(now_tv.tv_usec-start_tv.tv_usec)/1000;
207 }
208
209
210 int ptimeout = 15000; /* claim timeout time in milliseconds [global] */
211 int local_timeouts = 0; /* should we handle timeouts ourselves? */
212
213 #ifdef GTK2
214 /* gtk2 rc file to use instead of default */
215 char gtk2_rcfile[512];
216 /* whether to use system gtkrc */
217 int use_system_gtkrc = 0;
218 #endif
219
220 /* These are the height and width of a tile (including
221 the button border. The spacing is currently
222 1/4 the width of an actual tile pixmap. */
223 static int tile_height = 0;
224 static int tile_width = 0;
225 static int tile_spacing = 0;
226
227 /* variable concerned with the display of the wall */
228 int showwall = -1;
229 /* preference value for this */
230 int pref_showwall = -1;
231
232 /* do the message and game info windows get wrapped into the main ? */
233 int info_windows_in_main = 0;
234
235 /* preference values for robot names */
236 char robot_names[3][128];
237 /* and robot options */
238 char robot_options[3][128];
239
240 /* address of server */
241 char address[256] = "localhost:5000";
242 /* hack: we don't want to save in the rc file the address
243 if we've been redirected */
244 static int redirected = 0;
245 static char origaddress[256];
246 /* name of player */
247 char name[256] = "" ;
248
249 /* name of the main system font (empty if use default) */
250 char main_font_name[256] = "";
251 /* and the "fixed" font used for text display */
252 char text_font_name[256] = "";
253 /* one that we believe to work */
254 char fallback_text_font_name[256] = "";
255 /* name of the colour of the table */
256 char table_colour_name[256] = "";
257
258 /* playing options */
259 int playing_auto_declare_specials = 0;
260 int playing_auto_declare_losing = 0;
261 int playing_auto_declare_winning = 0;
262 /* local variable to note when we are in the middle of an auto-declare */
263 static int auto_declaring_special = 0;
264 static int auto_declaring_hand = 0;
265 /* to override the suppression of declaring specials dialog when
266 we have a kong in hand */
267 static int may_declare_kong = 0;
268
269 /* a count of completed games, for nagging purposes */
270 int completed_games = 0;
271 int nag_state = 0; /* nag state */
272 time_t nag_date = 0; /* time of last nag, as a time(). We won't worry
273 about the rollover! */
274
275 /* array of buttons for the tiles in the wall.
276 button 0 is the top rightmost tile in our wall, and
277 we count clockwise as the tiles are dealt. */
278 static GtkWidget *wall[MAX_WALL_SIZE];
279 /* This gives the wall size with a sensible value when there's no
280 game or when there's no wall established */
281 #define WALL_SIZE ((the_game && the_game->wall.size) ? the_game->wall.size : MAX_WALL_SIZE)
282 static int wallstart; /* where did the live wall start ? */
283 /* given a tile number in the wall, what orientation shd it have? */
284 #define wall_ori(n) (n < WALL_SIZE/4 ? 0 : n < 2*WALL_SIZE/4 ? 3 : n < 3*WALL_SIZE/4 ? 2 : 1)
285
286
287 /* Pixmaps for tiles. We need one for each orientation.
288 The reason for this slightly sick procedure is so that the
289 error tile can also have a pixmap here, with the indexing
290 working as expected
291 */
292 static GdkPixmap *_tilepixmaps[4][MaxTile+1];
293
294 /* this is global */
295 GdkPixmap **tilepixmaps[4] = {
296 &_tilepixmaps[0][1], &_tilepixmaps[1][1],
297 &_tilepixmaps[2][1], &_tilepixmaps[3][1] };
298
299 /* pixmaps for tong box, and mask */
300 static GdkPixmap *tongpixmaps[4][4]; /* [ori,wind-1] */
301 static GdkBitmap *tongmask;
302
303 GtkWidget *topwindow; /* main window */
304 /* used to remember the position of the top level window when
305 recreating it */
306 static gint top_x, top_y, top_pos_set;
307 GtkWidget *menubar; /* menubar */
308 GtkWidget *info_box; /* box to hold info windows */
309 GtkWidget *board; /* the table area itself */
310 GtkWidget *boardfixed; /* fixed widget enclosing boardframe */
311 GtkWidget *boardframe; /* event box widget wrapping the board */
312 GtkWidget *outerframe; /* outermost frame */
313 #ifndef GTK2
314 GtkStyle *tablestyle; /* for the dark green stuff */
315 #endif
316 #ifndef GTK2
317 GtkStyle *highlightstyle; /* to highlight tiles */
318 #else
319 static GdkColor highlightcolor;
320 #endif
321 GtkWidget *highlittile = NULL ; /* the unique highlighted discard */
322 GtkWidget *highlitinhand = NULL; /* the highlighted tile to show that
323 that it's a player's turn */
324 GtkWidget *dialoglowerbox = NULL; /* encloses dialogs when dialogs are below */
325 GdkFont *main_font;
326 GdkFont *fixed_font; /* a fixed width font */
327 GdkFont *big_font; /* big font for claim windows */
328 static GtkStyle *original_default_style;
329 #ifndef GTK2
330 static GtkStyle *defstyle;
331 #endif
332 /* option table for our game preferences */
333 GameOptionTable prefs_table;
334
335 /* This gives the width of a player display in units
336 of tiles */
337
338 int pdispwidth = 0;
339 int display_size = 0;
340 int square_aspect = 0; /* force a square table */
341 int animate = 0; /* do fancy animation */
342 /* This variable determines whether animated tiles are done by
343 moving a tile within the board, which is preferable since it
344 means tiles don't mysteriously float above other higher windows;
345 or by moving top-level popup windows. The only reason for the
346 latter is that it seems to make the animation smoother under Windows.
347 This variable is local and read-only now; it may become an option later.
348 */
349 #ifdef WIN32
350 static int animate_with_popups = 1;
351 #else
352 static int animate_with_popups = 0;
353 #endif
354
355 int sort_tiles = SortAlways; /* when to sort tiles in our hand */
356 int iconify_dialogs_with_main = 1;
357 int nopopups = 0; /* suppress popups */
358 int tiletips = 0; /* show tile tips all the time */
359 int rotatelabels = 1; /* rotate the player info labels on the table */
360 int thinking_claim = 0; /* show ... before players have noclaimed */
361 int alert_mahjong = 0; /* pop up notice when can mahjong */
362
363 DebugReports debug_reports = DebugReportsUnspecified; /* send debugging reports? */
364
365 /* record to keep copy of server input to add to debugging reports */
366 struct databuffer {
367 char *data; /* malloced buffer */
368 int alloced; /* length of malloced buffer */
369 int length; /* length used (including termination null of strings) */
370 } server_history;
371
databuffer_clear(struct databuffer * b)372 static void databuffer_clear(struct databuffer *b) {
373 if ( b->data ) free(b->data);
374 b->data = NULL;
375 b->alloced = 0;
376 b->length = 0;
377 }
378
379 /* extend a databuffer to at least the given length. Returns
380 1 on success, 0 on failure */
databuffer_extend(struct databuffer * b,int new)381 static int databuffer_extend(struct databuffer *b,int new) {
382 char *bnew;
383 int anew;
384 anew = b->alloced;
385 if ( new <= anew ) return 1;
386 if ( anew == 0 ) anew = 1024;
387 while ( anew < new ) anew *= 2;
388 bnew = realloc(b->data,anew);
389 if ( bnew == NULL ) {
390 warn("databuffer_extend: out of memory");
391 return 0;
392 }
393 b->data = bnew;
394 b->alloced = anew;
395 return 1;
396 }
397
398 /* returns 1 on success, 0 on failure.
399 Assuming the databuffer is a null-terminated string (which is not
400 checked), extend it by the argument string */
databuffer_add_string(struct databuffer * b,char * new)401 static int databuffer_add_string(struct databuffer *b, char *new) {
402 int l;
403 l = strlen(new);
404 if ( ! databuffer_extend(b,b->length+l) ) return 0;
405 if ( b->length == 0 ) {
406 b->data[0] = 0;
407 b->length = 1;
408 }
409 strcpy(b->data + b->length - 1, new);
410 b->length += l;
411 return 1;
412 }
413
414
415
416 /* the player display areas */
417 PlayerDisp pdisps[NUM_SEATS];
418
419 int calling = 0; /* disgusting global flag */
420
421 /* the widget in which we put discards */
422 GtkWidget *discard_area;
423 /* This is an allocation structure in which we
424 note its allocated size, at some point when we
425 know the allocation is valid.
426 */
427 GtkAllocation discard_area_alloc;
428
429 /* This is used to keep a history of the discard actions in
430 the current hand, so we can reposition the discards if we
431 change size etc. in mid hand */
432 struct {
433 int count;
434 struct { PlayerDisp *pd ; Tile t ; } hist[2*MAX_WALL_SIZE];
435 } discard_history;
436
437 GtkWidget *just_doubleclicked = NULL; /* yech yech yech. See doubleclicked */
438
439 /* space round edge of dialog boxes */
440 const gint dialog_border_width = 10;
441 /* horiz space between buttons */
442 const gint dialog_button_spacing = 10;
443 /* vert space between text and buttons etc */
444 const gint dialog_vert_spacing = 10;
445
446 /* space around player boxes. N.B. this should only
447 apply to the outermost boxes */
448 const gint player_border_width = 10;
449
450 DialogPosition dialogs_position = DialogsUnspecified;
451
452
453 int pass_stdin = 0; /* 1 if commands are to read from stdin and
454 passed to the server. */
455
usage(char * pname,char * msg)456 void usage(char *pname,char *msg) {
457 fprintf(stderr,"%s: %s\nUsage: %s [ --id N ]\n\
458 [ --server ADDR ]\n\
459 [ --name NAME ]\n\
460 [ --connect ]\n\
461 [ --[no-]show-wall ]\n\
462 [ --size N ]\n\
463 [ --animate ]\n\
464 [ --tile-pixmap-dir DIR ]\n\
465 [ --dialogs-popup | --dialogs-below | --dialogs-central ]\n"
466 #ifdef GTK2
467 " [ --gtk2-rcfile FILE ]\n"
468 #endif
469 " [ --monitor ]\n\
470 [ --echo-server ]\n\
471 [ --pass-stdin ]\n",
472 pname,msg,pname);
473 }
474
475 #ifdef WIN32
476 /* In Windows, we can't use sockets as they come, but have to convert
477 them to glib channels. The following functions are passed to the
478 sysdep.c initialize_sockets routine to do this. */
479
skt_open_transform(SOCKET s)480 static void *skt_open_transform(SOCKET s) {
481 GIOChannel *chan;
482
483 chan = g_io_channel_unix_new(s); /* don't know what do if this fails */
484 if ( ! chan ) {
485 warn("Couldn't convert socket to channel");
486 return NULL;
487 }
488 return (void *)chan;
489 }
490
skt_closer(void * chan)491 static int skt_closer(void *chan) {
492 g_io_channel_close((GIOChannel *)chan);
493 g_io_channel_unref((GIOChannel *)chan); /* unref the ref at creation */
494 return 1;
495 }
496
skt_reader(void * chan,void * buf,size_t n)497 static int skt_reader(void *chan,void *buf, size_t n) {
498 int len = 0; int e;
499 while ( 1 ) {
500 e = g_io_channel_read((GIOChannel *)chan,buf,n,&len);
501 if ( e == G_IO_ERROR_NONE )
502 /* fprintf(stderr,"g_io_channel_read returned %d bytes\r\n",len); */
503 return len;
504 if ( e != G_IO_ERROR_AGAIN )
505 return -1;
506 }
507 }
508
skt_writer(void * chan,const void * buf,size_t n)509 static int skt_writer(void *chan, const void *buf, size_t n) {
510 int len = 0;
511 if ( g_io_channel_write((GIOChannel *)chan, (void *)buf,n,&len)
512 == G_IO_ERROR_NONE )
513 return len;
514 else
515 return -1;
516 }
517
518 /* a glib level wrapper for the server input callback */
519
gcallback(GIOChannel * source UNUSED,GIOCondition condition UNUSED,gpointer data UNUSED)520 static gboolean gcallback(GIOChannel *source UNUSED,
521 GIOCondition condition UNUSED,
522 gpointer data UNUSED) {
523 server_input_callback(NULL,0,GDK_INPUT_READ);
524 return 1;
525 }
526
527
528
529 #endif /* WIN32 */
530
531 /* this is a little hack to make it easy to provoke errors for testing */
532 #ifndef WIN32
533 static int signalled = 0;
534
sighandler(int n UNUSED)535 static void sighandler(int n UNUSED) {
536 signalled = 1;
537 }
538 #endif
539
540 /* used to keep the last line received from controller */
541 static char last_line[1024];
542
log_msg_dump_state(LogLevel l)543 static char *log_msg_dump_state(LogLevel l) {
544 static struct databuffer r;
545 if ( l > LogWarning ) {
546 int bytes_left;
547 static char head[] = "Last cmsg: ";
548 static char head1[] = "Server history:\n";
549 char *ret;
550 ret = game_print_state(the_game,&bytes_left);
551 databuffer_clear(&r);
552 if ( ! databuffer_extend(&r,strlen(head)
553 + strlen(last_line) + 2 + strlen(ret)
554 + strlen(head1) + server_history.length + 1) ) {
555 warn("Unable to build fault report");
556 return NULL;
557 }
558 databuffer_add_string(&r,head);
559 databuffer_add_string(&r,last_line);
560 databuffer_add_string(&r,"\n\n");
561 databuffer_add_string(&r,ret);
562 databuffer_add_string(&r,head1);
563 databuffer_add_string(&r,server_history.data);
564 return r.data;
565 } else {
566 return NULL;
567 }
568 }
569
570 int
main(int argc,char * argv[])571 main (int argc, char *argv[])
572 {
573 int i;
574
575 /* we could potentially get SIGPIPE if we're passing server commands
576 to stdout. We don't want to fail in that case. */
577 ignore_sigpipe();
578
579 /* install the warning hook for gui users */
580 log_msg_hook = log_msg_add;
581
582 /* add a dump of the game state to errors */
583 log_msg_add_note_hook = log_msg_dump_state;
584
585 /* we have to set up the default preferences table before
586 reading the rcfile */
587 game_copy_option_table(&prefs_table,&game_default_optiontable);
588
589 /* read rc file. We should have an option to suppress this */
590 read_rcfile(NULL);
591 showwall = pref_showwall;
592
593 /* options. I should use getopt ... */
594 for (i=1;i<argc;i++) {
595 if ( strcmp(argv[i],"--id") == 0 ) {
596 if ( ++i == argc ) usage(argv[0],"missing argument to --id");
597 our_id = atoi(argv[i]);
598 } else if ( strcmp(argv[i],"--server") == 0 ) {
599 if ( ++i == argc ) usage(argv[0],"missing argument to --server");
600 strmcpy(address,argv[i],255);
601 } else if ( strcmp(argv[i],"--name") == 0 ) {
602 if ( ++i == argc ) usage(argv[0],"missing argument to --name");
603 strmcpy(name,argv[i],255);
604 } else if ( strcmp(argv[i],"--connect") == 0 ) {
605 do_connect = 1;
606 } else if ( strcmp(argv[i],"--show-wall") == 0 ) {
607 pref_showwall = showwall = 1;
608 } else if ( strcmp(argv[i],"--no-show-wall") == 0 ) {
609 pref_showwall = showwall = 0;
610 } else if ( strcmp(argv[i],"--size") == 0 ) {
611 if ( ++i == argc ) usage(argv[0],"missing argument to --size");
612 display_size = atoi(argv[i]);
613 } else if ( strcmp(argv[i],"--animate") == 0 ) {
614 animate = 1;
615 } else if ( strcmp(argv[i],"--no-animate") == 0 ) {
616 animate = 0;
617 } else if ( strcmp(argv[i],"--tileset") == 0 ) {
618 if ( ++i == argc ) usage(argv[0],"missing argument to --tileset");
619 tileset = argv[i];
620 } else if ( strcmp(argv[i],"--tileset-path") == 0 ) {
621 if ( ++i == argc ) usage(argv[0],"missing argument to --tileset-path");
622 tileset_path = argv[i];
623 #ifdef GTK2
624 } else if ( strcmp(argv[i],"--use-system-gtkrc") == 0 ) {
625 use_system_gtkrc = 1;
626 } else if ( strcmp(argv[i],"--no-use-system-gtkrc") == 0 ) {
627 use_system_gtkrc = 0;
628 } else if ( strcmp(argv[i],"--gtk2-rcfile") == 0 ) {
629 if ( ++i == argc ) usage(argv[0],"missing argument to --gtk2-rcfile");
630 strmcpy(gtk2_rcfile,argv[i],512);
631 #endif
632 } else if ( strcmp(argv[i],"--echo-server") == 0 ) {
633 debug = 1;
634 } else if ( strcmp(argv[i],"--monitor") == 0 ) {
635 monitor = 1;
636 } else if ( strcmp(argv[i],"--dialogs-popup") == 0 ) {
637 dialogs_position = DialogsPopup;
638 } else if ( strcmp(argv[i],"--dialogs-below") == 0 ) {
639 dialogs_position = DialogsBelow;
640 } else if ( strcmp(argv[i],"--dialogs-central") == 0 ) {
641 dialogs_position = DialogsCentral;
642 } else if ( strcmp(argv[i],"--pass-stdin") == 0 ) {
643 pass_stdin = 1;
644 } else {
645 usage(argv[0],"unknown option or argument");
646 exit(1);
647 }
648 }
649
650 #ifdef GTK2
651 /* The Gtk rc mechanism is totally insane. Contrary to the documentation,
652 the rc files are not parsed at the end of gtk_init, but on demand when
653 the first style is created. Therefore, we can't parse a string here that
654 relies on things in the rc files. But we can't add a string to the rc
655 file list, we can only parse it (which also adds it to the rc file list!).
656 So we have to force the parsing of rc files by making a style. */
657
658 #ifdef WIN32
659 /* make sure we pick up modules in the distribution directory */
660 putenv("GTK_PATH=.;");
661 #endif
662 gchar *rc_files = { NULL };
663 if ( ! use_system_gtkrc ) gtk_rc_set_default_files(&rc_files);
664 gtk_init (&argc, &argv);
665 if (gtk2_rcfile[0]) {
666 gtk_rc_parse_string(gtkrc_minimal_styles);
667 if ( access(gtk2_rcfile,R_OK) == 0 )
668 gtk_rc_add_default_file(gtk2_rcfile);
669 else
670 warn("Can't read gtk2-rcfile %s - display will be broken",gtk2_rcfile);
671 } else if ( ! use_system_gtkrc ) {
672 // do we have clearlooks? This seems to be the only way to find out.
673 if ( gtk_rc_find_module_in_path(
674 "libclearlooks."
675 #ifdef WIN32
676 "dll"
677 #else
678 "so"
679 #endif
680 ) ) {
681 #ifdef WIN32
682 /* Installing clearlooks properly is far too difficult. So we
683 just ship the theme gtkrc with the libraries and load it
684 explicitly */
685 /* FIXME - we don't do this, because I haven't yet managed to
686 build a clearlooks library for Windows. */
687 gtk_rc_parse("cgtkrc");
688 #else
689 gtk_rc_parse_string("gtk-theme-name = \"Clearlooks\"");
690 #endif
691 gtk_widget_get_default_style(); /* force style to be calculated */
692 gtk_rc_parse_string(gtkrc_clearlooks_styles);
693 } else {
694 gtk_rc_parse_string(gtkrc_plain_styles);
695 }
696 gtk_rc_parse_string(gtkrc_apply_styles);
697 }
698 #else
699 gtk_init (&argc, &argv);
700 #endif
701
702 #ifdef WIN32
703 initialize_sockets(skt_open_transform,skt_closer,
704 skt_reader,skt_writer);
705 #endif
706
707 /* stash the unmodified default style, so we know what the
708 default font is */
709 original_default_style = gtk_style_copy(gtk_widget_get_default_style());
710 /* Now we've done basic setup, create all the windows.
711 This also involves reading tile pixmaps etc */
712
713 /* These two signals are bound to left and right arrow accelerators
714 in order to move the tile selection left or right */
715 #ifdef GTK2
716 gtk_signal_new("selectleft",GTK_RUN_ACTION,GTK_TYPE_WIDGET,0,
717 gtk_signal_default_marshaller, GTK_TYPE_NONE,0);
718 gtk_signal_new("selectright",GTK_RUN_ACTION,GTK_TYPE_WIDGET,0,
719 gtk_signal_default_marshaller, GTK_TYPE_NONE,0);
720 #else
721 gtk_object_class_user_signal_new(
722 GTK_OBJECT_CLASS(gtk_type_class(GTK_TYPE_WIDGET)),
723 "selectleft", GTK_RUN_FIRST, gtk_signal_default_marshaller, GTK_TYPE_NONE,0);
724 gtk_object_class_user_signal_new(
725 GTK_OBJECT_CLASS(gtk_type_class(GTK_TYPE_WIDGET)),
726 "selectright", GTK_RUN_FIRST, gtk_signal_default_marshaller, GTK_TYPE_NONE,0);
727 #endif
728 /* These two signals are bound to shift left and right arrow accelerators
729 in order to move the selected tile left or right */
730 #ifdef GTK2
731 gtk_signal_new("moveleft",GTK_RUN_ACTION,GTK_TYPE_WIDGET,0,
732 gtk_signal_default_marshaller, GTK_TYPE_NONE,0);
733 gtk_signal_new("moveright",GTK_RUN_ACTION,GTK_TYPE_WIDGET,0,
734 gtk_signal_default_marshaller, GTK_TYPE_NONE,0);
735 #else
736 gtk_object_class_user_signal_new(
737 GTK_OBJECT_CLASS(gtk_type_class(GTK_TYPE_WIDGET)),
738 "moveleft", GTK_RUN_FIRST, gtk_signal_default_marshaller, GTK_TYPE_NONE,0);
739 gtk_object_class_user_signal_new(
740 GTK_OBJECT_CLASS(gtk_type_class(GTK_TYPE_WIDGET)),
741 "moveright", GTK_RUN_FIRST, gtk_signal_default_marshaller, GTK_TYPE_NONE,0);
742 #endif
743 create_display();
744
745 #if 0 /* not wanted at present, but left here so we remember how to do it */
746 /* we create a new popup signal which we will send to dialogs when
747 they're popped up and we want them to set focus to the appropriate
748 button */
749 gtk_object_class_user_signal_new(GTK_OBJECT_CLASS(gtk_type_class(gtk_type_from_name("GtkWidget"))), "popup", GTK_RUN_FIRST, gtk_signal_default_marshaller, GTK_TYPE_NONE,0);
750 #endif
751
752 /* now check the dialog state */
753 setup_dialogs();
754
755 if ( do_connect ) open_connection(0,0,0,NULL);
756
757 /* initialize time stamps */
758 gettimeofday(&start_tv,NULL);
759
760 # ifndef WIN32
761 signal(SIGUSR1,sighandler);
762 # endif
763
764 /* if this is the first time this version with debug reports
765 has been run, popup the window to set options */
766 if ( debug_reports == DebugReportsUnspecified ) {
767 debug_options_dialog_popup();
768 }
769
770 gtk_main ();
771
772 return 0;
773 }
774
775
776 static int querykong = 0; /* used during kong robbing query */
777
778
server_input_callback(gpointer data UNUSED,gint sourceparam UNUSED,GdkInputCondition conditionparam UNUSED)779 static void server_input_callback(gpointer data UNUSED,
780 gint sourceparam UNUSED,
781 GdkInputCondition conditionparam UNUSED) {
782 char *l;
783 CMsgUnion *m;
784 int id;
785 int i;
786
787 /* hack for testing error routines */
788 # ifndef WIN32
789 if ( signalled ) {
790 signalled = 0;
791 error("We got a (Unix) signal!");
792 }
793 # endif
794
795 l = get_line(the_game->fd);
796 strmcpy(last_line,(l ? l : "NULL LINE\n"),sizeof(last_line));
797 databuffer_add_string(&server_history,l ? l : "NULL LINE\n");
798 if ( debug && l) {
799 put_line(1,l);
800 }
801 if ( ! l ) {
802 if ( game_over ) {
803 /* not an error - just stop listening */
804 control_server_processing(0);
805 } else {
806 fprintf(stderr,"receive error from controller\n");
807 error_dialog_popup("Lost connection to server");
808 close_connection(0);
809 }
810 return;
811 }
812
813 m = (CMsgUnion *)decode_cmsg(l);
814
815 if ( ! m ) {
816 warn("protocol error from controller: %s\n",l);
817 return;
818 }
819
820 if ( m->type == CMsgConnectReply ) {
821 our_id = ((CMsgConnectReplyMsg *)m)->id;
822 /* need to set this again if this is a second connectreply
823 in response to loadstate */
824 our_player = the_game->players[0];
825 if ( our_id == 0 ) {
826 char s[1024];
827 char refmsg[] = "Server refused connection because: ";
828 close_connection(0);
829 strcpy(s,refmsg);
830 strmcat(s,((CMsgConnectReplyMsg *)m)->reason,sizeof(s)-sizeof(refmsg));
831 error_dialog_popup(s);
832 return;
833 } else {
834 PMsgSetPlayerOptionMsg spom;
835 /* stash the server's protocol version */
836 server_pversion = m->connectreply.pvers;
837 /* set our name again, in case we got deleted! */
838 set_player_name(our_player,name);
839 if ( !monitor ) {
840 /* since we have a human driving us, ask the controller to slow down
841 a bit; especially if we're animating */
842 spom.type = PMsgSetPlayerOption;
843 spom.option = PODelayTime;
844 spom.ack = 0;
845 spom.value = animate ? 10 : 5; /* 0.5 or 1 second min delay */
846 spom.text = NULL;
847 send_packet(&spom);
848 /* if the controller is likely to understand it,
849 request that we may handle timeouts locally */
850 if ( m->connectreply.pvers >= 1036 ) {
851 spom.option = POLocalTimeouts;
852 spom.value = 1;
853 send_packet(&spom);
854 }
855 }
856 }
857 }
858
859 if ( m->type == CMsgReconnect ) {
860 int fd = the_game->fd;
861 close_connection(1);
862 open_connection(0,(gpointer)3,fd,NULL);
863 return;
864 }
865
866 if ( m->type == CMsgRedirect ) {
867 char buf[256];
868 int id = our_id;
869 close_connection(0);
870 our_id = id;
871 open_connection(0,(gpointer)4,0,((CMsgRedirectMsg *)m)->dest);
872 sprintf(buf,"Server has redirected us to %.220s",address);
873 info_dialog_popup(buf);
874 return;
875 }
876
877 if ( m->type == CMsgAuthReqd ) {
878 password_showraise();
879 return;
880 }
881
882 /* skip InfoTiles */
883 if ( m->type == CMsgInfoTiles ) return;
884
885 /* error messages */
886 if ( m->type == CMsgError ) {
887 error_dialog_popup(m->error.error);
888 closed_set_in_progress = 0;
889 /* better set up dialogs here */
890 setup_dialogs();
891 return;
892 }
893
894 /* player options */
895 if ( m->type == CMsgPlayerOptionSet ) {
896 /* the only player option that is significant for us
897 is LocalTimeouts */
898 if ( m->playeroptionset.option == POLocalTimeouts ) {
899 local_timeouts = m->playeroptionset.value;
900 if ( monitor ) return;
901 if ( ! m->playeroptionset.ack ) {
902 /* this was a command from the server -- acknowledge it */
903 PMsgSetPlayerOptionMsg spom;
904 spom.type = PMsgSetPlayerOption;
905 spom.option = POLocalTimeouts;
906 spom.value = local_timeouts;
907 spom.text = NULL;
908 spom.ack = 1;
909 send_packet(&spom);
910 }
911 }
912 /* any other option we just ignore, since we have no corresponding
913 state */
914 return;
915 }
916
917 id = game_handle_cmsg(the_game,(CMsgMsg *)m);
918 /* it should not, I think be possible to get an error here. But
919 if we do, let's report it and handle it */
920 if ( id < 0 ) {
921 error("game_handle_cmsg returned %d; ignoring it",id);
922 return;
923 }
924
925 calling = 0; /* awful global flag to detect calling */
926 /* This is the place where we take intelligent action in
927 response to the game. As a matter of principle, we don't do this,
928 but there is an exception: if there is a kong to be robbed,
929 we will check to see whether we can actually go out with the tile,
930 and if we can't, we'll send in NoClaim directly, rather than
931 asking the user */
932
933 /* We can't assume that we know whether we can go mahjong.
934 The server might allow special winning hands that we don't know
935 about. So we have to ask the server whether we can go MJ.
936 */
937
938 /* we might get the kong message as part of the replay. So we have
939 to respond the first time we get a message after a kong and
940 the game is active */
941
942 if ( !monitor
943 && the_game->active && id != our_id && ! querykong
944 && (the_game->state == Discarding
945 || the_game->state == DeclaringSpecials)
946 && the_game->konging
947 && the_game->claims[our_seat] == UnknownClaim ) {
948 PMsgQueryMahJongMsg qmjm;
949 qmjm.type = PMsgQueryMahJong;
950 qmjm.tile = the_game->tile;
951 send_packet(&qmjm);
952 querykong = 1;
953 }
954 /* and if this is the reply, send no claim if appropriate */
955 if ( !monitor
956 && the_game->active && (the_game->state == Discarding
957 || the_game->state == DeclaringSpecials)
958 && the_game->konging
959 && m->type == CMsgCanMahJong
960 && ! m->canmahjong.answer) {
961 disc_callback(NULL,(gpointer) NoClaim); /* sends the message */
962 the_game->claims[our_seat] = NoClaim; /* hack to stop the window coming up */
963 }
964 if ( m->type == CMsgCanMahJong ) querykong = 0;
965
966 status_update(m->type == CMsgGameOver);
967
968 if ( id == our_id ) {
969 /* do we want to sort the tiles? */
970 if ( sort_tiles == SortAlways
971 || (sort_tiles == SortDeal
972 && (the_game->state == Dealing || the_game->state == DeclaringSpecials )))
973 player_sort_tiles(our_player);
974 }
975
976 /* some more "intelligent action" comes now */
977
978 /* if we should be auto-declaring specials, deal with it */
979 if ( !monitor && playing_auto_declare_specials ) {
980 if ( m->type == CMsgPlayerDeclaresSpecial
981 && id == our_id ) {
982 auto_declaring_special = 0;
983 may_declare_kong = 0;
984 }
985 if ( the_game->active
986 && ! the_game->paused
987 && ( the_game->state == DeclaringSpecials
988 || the_game->state == Discarding )
989 && the_game->needs == FromNone
990 && the_game->player == our_seat
991 && !auto_declaring_special ) {
992 PMsgDeclareSpecialMsg pdsm;
993 pdsm.type = PMsgDeclareSpecial;
994 /* if we have a special, declare it. Now that we allow non-sorting,
995 we have to look for specials */
996 /* if we're declaring specials, but have none, send the finished
997 message */
998 assert(our_player->num_concealed > 0);
999 int j;
1000 for (j=0;j<our_player->num_concealed && !is_special(our_player->concealed[j]);j++);
1001 if ( j < our_player->num_concealed ) {
1002 pdsm.tile = our_player->concealed[j];
1003 send_packet(&pdsm);
1004 auto_declaring_special = 1;
1005 } else if ( the_game->state == DeclaringSpecials ) {
1006 /* First, we have to check whether we have a kong in hand,
1007 since then the human must have the option to declare it */
1008 int i;
1009 for ( i = 0; i < our_player->num_concealed; i++ ) {
1010 if ( player_can_declare_closed_kong(our_player,
1011 our_player->concealed[i]) ) {
1012 may_declare_kong = 1;
1013 break;
1014 }
1015 }
1016 if ( ! may_declare_kong ) {
1017 pdsm.tile = HiddenTile;
1018 send_packet(&pdsm);
1019 auto_declaring_special = 1;
1020 } else {
1021 /* select the kong we found */
1022 selected_button = i;
1023 conc_callback(pdisps[0].conc[i],(gpointer)(intptr_t)(i | 128));
1024 }
1025 }
1026 }
1027 }
1028
1029 /* if we should be auto-declaring hands, deal with it */
1030 if ( !monitor && (playing_auto_declare_losing || playing_auto_declare_winning) ) {
1031 /* this is mind-numbingly tedious. There has to be a better way. */
1032 if ( (m->type == CMsgPlayerPungs
1033 || m->type == CMsgPlayerFormsClosedPung
1034 || m->type == CMsgPlayerKongs
1035 || m->type == CMsgPlayerDeclaresClosedKong
1036 || m->type == CMsgPlayerChows
1037 || m->type == CMsgPlayerFormsClosedChow
1038 || m->type == CMsgPlayerPairs
1039 || m->type == CMsgPlayerFormsClosedPair
1040 || m->type == CMsgPlayerSpecialSet
1041 || m->type == CMsgPlayerFormsClosedSpecialSet )
1042 && id == our_id ) auto_declaring_hand = 0;
1043
1044 /* and to make sure it does get cleared */
1045 if ( pflag(our_player,HandDeclared) ) auto_declaring_hand = 0;
1046
1047 if ( !auto_declaring_hand
1048 && the_game->active
1049 && ! the_game->paused
1050 && ( the_game->state == MahJonging )
1051 && !(the_game->mjpending && the_game->player != our_seat)
1052 && !pflag(our_player,HandDeclared)
1053 && ((the_game->player == our_seat && playing_auto_declare_winning)
1054 || (pflag(the_game->players[the_game->player],HandDeclared)
1055 && playing_auto_declare_losing) ) ) {
1056 TileSet *tsp;
1057 MJSpecialHandFlags mjspecflags;
1058 PMsgUnion m;
1059
1060 mjspecflags = 0;
1061 if ( game_get_option_value(the_game,GOSevenPairs,NULL).optbool )
1062 mjspecflags |= MJSevenPairs;
1063
1064 /* OK, let's find a concealed set to declare */
1065 auto_declaring_hand = 1;
1066 /* following code nicked from greedy.c */
1067 /* get the list of possible decls */
1068 tsp = client_find_sets(our_player,
1069 (the_game->player == our_seat
1070 && the_game->mjpending)
1071 ? the_game->tile : HiddenTile,
1072 the_game->player == our_seat,
1073 (PlayerP *)0,mjspecflags);
1074 if ( !tsp && our_player->num_concealed > 0 ) {
1075 /* Can't find a set to declare, and still have concealed
1076 tiles left. Either we're a loser, or we have Thirteen
1077 Orphans (or some other special hand). */
1078 if ( the_game->player == our_seat ) {
1079 if ( the_game->mjpending ) {
1080 m.type = PMsgSpecialSet;
1081 } else {
1082 m.type = PMsgFormClosedSpecialSet;
1083 }
1084 } else {
1085 m.type = PMsgShowTiles;
1086 }
1087 send_packet(&m);
1088 } else {
1089 /* just do the first one */
1090 switch ( tsp->type ) {
1091 case Kong:
1092 /* we can't declare a kong now, so declare the pung instead */
1093 case Pung:
1094 m.type = PMsgPung;
1095 m.pung.discard = 0;
1096 break;
1097 case Chow:
1098 m.type = PMsgChow;
1099 m.chow.discard = 0;
1100 m.chow.cpos = the_game->tile - tsp->tile;
1101 break;
1102 case Pair:
1103 m.type = PMsgPair;
1104 break;
1105 case ClosedPung:
1106 m.type = PMsgFormClosedPung;
1107 m.formclosedpung.tile = tsp->tile;
1108 break;
1109 case ClosedChow:
1110 m.type = PMsgFormClosedChow;
1111 m.formclosedchow.tile = tsp->tile;
1112 break;
1113 case ClosedPair:
1114 m.type = PMsgFormClosedPair;
1115 m.formclosedpair.tile = tsp->tile;
1116 break;
1117 case Empty: /* can't happen, just to suppress warning */
1118 case ClosedKong: /* ditto */
1119 ;
1120 }
1121 send_packet(&m);
1122 }
1123 }
1124 }
1125
1126 /* now deal with the display. There are basically two sorts
1127 of things to do: some triggered by a message, such as
1128 displaying draws, claims, etc; and some depending only on game state,
1129 such as making sure the right dialogs are up.
1130 We'll deal with message-dependent things first.
1131 */
1132
1133 switch ( m->type ) {
1134 case CMsgError:
1135 /* this has been dealt with above, and cannot happen */
1136 warn("Error message in impossible place");
1137 break;
1138 case CMsgAuthReqd:
1139 /* this has been dealt with above, and cannot happen */
1140 warn("AuthReqd message in impossible place");
1141 break;
1142 case CMsgRedirect:
1143 /* this has been dealt with above, and cannot happen */
1144 warn("Redirect message in impossible place");
1145 break;
1146 case CMsgPlayerOptionSet:
1147 /* this has been dealt with above, and cannot happen */
1148 warn("PlayerOptionSet message in impossible place");
1149 break;
1150 case CMsgStopPlay:
1151 { char buf[512];
1152 char suspmsg[] = "Server has suspended play because:\n";
1153 strcpy(buf,suspmsg);
1154 strmcat(buf,(m->stopplay.reason),sizeof(buf)-sizeof(suspmsg));
1155 error_dialog_popup(buf);
1156 }
1157 break;
1158 case CMsgGameOption:
1159 /* update the timeout value */
1160 if ( m->gameoption.optentry.option == GOTimeout ) {
1161 ptimeout = 1000*m->gameoption.optentry.value.optint;
1162 }
1163 /* if this is the Flowers option, recreate the declaring
1164 specials dialog */
1165 if ( m->gameoption.optentry.option == GOFlowers ) {
1166 ds_dialog_init();
1167 }
1168 /* and refresh the game option panel */
1169 game_option_panel = build_or_refresh_option_panel(&the_game->option_table,game_option_panel);
1170 break;
1171 case CMsgGame:
1172 /* now we know the seating order: make the userdata field of
1173 each player point to the appropriate pdisp */
1174 our_seat = game_id_to_seat(the_game,our_id);
1175 for ( i = 0; i < NUM_SEATS; i++ ) {
1176 set_player_userdata(the_game->players[i],
1177 (void *)&pdisps[game_to_board(i)]);
1178 /* and make the pdisp point to the player */
1179 pdisps[game_to_board(i)].player = the_game->players[i];
1180 }
1181 /* zap the game option dialog, because the option table has
1182 been reset */
1183 game_option_panel = build_or_refresh_option_panel(NULL,game_option_panel);
1184 /* ask what the current options are */
1185 if ( ! monitor ) {
1186 PMsgListGameOptionsMsg plgom;
1187 plgom.type = PMsgListGameOptions;
1188 plgom.include_disabled = 0;
1189 send_packet(&plgom);
1190 }
1191 /* and enable the menu item */
1192 gtk_widget_set_sensitive(gameoptionsmenuentry,1);
1193 /* set the info on the scoring window */
1194 for ( i=0; i < 4; i++ ) {
1195 gtk_label_set_text(GTK_LABEL(textlabels[i]),
1196 the_game->players[board_to_game(i)]->name);
1197 }
1198 break;
1199 case CMsgNewRound:
1200 /* update seat when it might change */
1201 our_seat = game_id_to_seat(the_game,our_id);
1202 break;
1203 case CMsgNewHand:
1204 /* update seat when it might change */
1205 our_seat = game_id_to_seat(the_game,our_id);
1206 /* After a new hand message, clear the display */
1207 for ( i=0; i < 4; i++ ) {
1208 playerdisp_update(&pdisps[i],m);
1209 }
1210 discard_history.count = 0;
1211 /* need to set wallstart even if not showing wall, in case
1212 we change options */
1213 wallstart = m->newhand.start_wall*(the_game->wall.size/4) + m->newhand.start_stack*2;
1214 gextras(the_game)->orig_live_end = the_game->wall.live_end;
1215 if ( showwall ) {
1216 create_wall();
1217 adjust_wall_loose(the_game->wall.dead_end); /* set the loose tiles in place */
1218 }
1219 break;
1220 case CMsgPlayerDiscards:
1221 /* if calling declaration, set flag */
1222 if ( m->playerdiscards.calling ) calling = 1;
1223 /* and then update */
1224 case CMsgPlayerFormsClosedPung:
1225 case CMsgPlayerDeclaresClosedKong:
1226 case CMsgPlayerAddsToPung:
1227 case CMsgPlayerFormsClosedChow:
1228 case CMsgPlayerFormsClosedPair:
1229 case CMsgPlayerShowsTiles:
1230 case CMsgPlayerFormsClosedSpecialSet:
1231 case CMsgPlayerDraws:
1232 case CMsgPlayerDrawsLoose:
1233 case CMsgPlayerDeclaresSpecial:
1234 case CMsgPlayerPairs:
1235 case CMsgPlayerChows:
1236 case CMsgPlayerPungs:
1237 case CMsgPlayerKongs:
1238 case CMsgPlayerSpecialSet:
1239 case CMsgPlayerClaimsPung:
1240 case CMsgPlayerClaimsKong:
1241 case CMsgPlayerClaimsChow:
1242 case CMsgPlayerClaimsMahJong:
1243 case CMsgSwapTile:
1244 {
1245 PlayerP p;
1246 p = game_id_to_player(the_game,id);
1247 if ( p == NULL ) {
1248 error("gui.c error 1: no player found for id %d\n",id);
1249 } else {
1250 if ( p->userdata == NULL ) {
1251 error("gui.c error 2: no player disp for player id %d\n",id);
1252 } else {
1253 playerdisp_update((PlayerDisp *)p->userdata,m);
1254 }
1255 }
1256 }
1257 break;
1258 case CMsgGameOver:
1259 /* we don't close the connection, we pop up a box to do it */
1260 game_over = 1;
1261 end_dialog_popup();
1262 status_showraise();
1263 /* increment and save the game count */
1264 if ( completed_games >= 0 ) {
1265 completed_games++;
1266 read_or_update_rcfile(NULL,XmjrcNone,XmjrcAll);
1267 }
1268 if ( nag_state == 0 && completed_games % 10 == 0 && time(NULL) - nag_date >= 30*24*3600) {
1269 nag_date = time(NULL);
1270 nag_popup();
1271 }
1272 return;
1273 /* in the following case, we need do nothing (in this switch) */
1274 case CMsgInfoTiles:
1275 case CMsgConnectReply:
1276 case CMsgReconnect:
1277 case CMsgPlayer:
1278 case CMsgPause:
1279 case CMsgPlayerReady:
1280 case CMsgClaimDenied:
1281 case CMsgDangerousDiscard:
1282 case CMsgCanMahJong:
1283 case CMsgHandScore:
1284 case CMsgSettlement:
1285 case CMsgChangeManager:
1286 case CMsgWall:
1287 case CMsgComment:
1288 break;
1289 case CMsgPlayerDoesntClaim:
1290 /* there may be a thinking window up, so hide it */
1291 {
1292 PlayerP p;
1293 p = game_id_to_player(the_game,id);
1294 if ( p == NULL ) {
1295 error("gui.c error 1: no player found for id %d\n",id);
1296 } else {
1297 if ( p->userdata == NULL ) {
1298 error("gui.c error 2: no player disp for player id %d\n",id);
1299 } else {
1300 PlayerDisp *pd = (PlayerDisp *)p->userdata;
1301 if ( pd->thinkbox )
1302 gtk_widget_hide(pd->thinkbox);
1303 }
1304 }
1305 }
1306 break;
1307 case CMsgStateSaved:
1308 { static char buf[512];
1309 char savmsg[] = "Game state saved in ";
1310 strcpy(buf,savmsg);
1311 strmcat(buf,m->statesaved.filename,sizeof(buf)-sizeof(savmsg)-2);
1312 info_dialog_popup(buf);
1313 break;
1314 }
1315 case CMsgStartPlay:
1316 /* If this is a new game, try to apply our option preferences */
1317 if ( the_game->state == HandComplete
1318 && the_game->round == EastWind
1319 && the_game->players[east]->id == the_game->firsteast
1320 && the_game->hands_as_east == 0
1321 && (the_game->manager == 0 || the_game->manager == our_id ) )
1322 apply_game_prefs();
1323 break;
1324 case CMsgMessage:
1325 {
1326 #ifdef GTK2
1327 GtkTextBuffer *messagetextbuf;
1328 GtkTextIter messagetextiter;
1329 GtkTextMark *messagemark;
1330 messagetextbuf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (messagetext));
1331 gtk_text_buffer_get_end_iter(messagetextbuf, &messagetextiter);
1332 messagemark = gtk_text_buffer_create_mark(messagetextbuf,
1333 "end",&messagetextiter,FALSE);
1334
1335 gtk_text_buffer_insert(messagetextbuf,&messagetextiter,
1336 "\n",-1);
1337 gtk_text_buffer_get_end_iter(messagetextbuf, &messagetextiter);
1338 gtk_text_buffer_insert(messagetextbuf,&messagetextiter,
1339 m->message.sender == 0 ? "CONTROLLER" : game_id_to_player(the_game,m->message.sender)->name,-1);
1340 gtk_text_buffer_get_end_iter(messagetextbuf, &messagetextiter);
1341 gtk_text_buffer_insert(messagetextbuf,&messagetextiter,
1342 "> ",-1);
1343 gtk_text_buffer_get_end_iter(messagetextbuf, &messagetextiter);
1344 if ( m->message.text ) {
1345 gtk_text_buffer_insert(messagetextbuf,&messagetextiter,
1346 m->message.text,-1);
1347 }
1348 gtk_text_buffer_get_end_iter(messagetextbuf, &messagetextiter);
1349 gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(messagetext),
1350 messagemark);
1351 gtk_text_buffer_delete_mark(messagetextbuf,messagemark);
1352 #else
1353 gtk_text_insert(GTK_TEXT(messagetext),0,0,0,
1354 "\n",-1);
1355 gtk_text_insert(GTK_TEXT(messagetext),0,0,0,
1356 m->message.sender == 0 ? "CONTROLLER" : game_id_to_player(the_game,m->message.sender)->name,-1);
1357 gtk_text_insert(GTK_TEXT(messagetext),0,0,0,
1358 "> ",-1);
1359 if ( m->message.text ) {
1360 gtk_text_insert(GTK_TEXT(messagetext),0,0,0,
1361 m->message.text,-1);
1362 }
1363 #endif
1364 if ( !nopopups && !info_windows_in_main) {
1365 showraise(messagewindow);
1366 }
1367 }
1368 break;
1369 case CMsgPlayerRobsKong:
1370 /* we need to update the victim */
1371 playerdisp_update(&pdisps[game_to_board(the_game->supplier)],m);
1372 /* and now fall through and do the mah-jong stuff, since the
1373 protocol does not issue a MahJongs in addition */
1374 case CMsgPlayerMahJongs:
1375 /* this could be from hand.
1376 Clear all the texts in the scoring displays.
1377 Unselect tiles (if we're about to claim the discard,
1378 it's visually confusing seeing the first tile get selected;
1379 so instead, we'll select the first tile when we pop up
1380 the "declare closed sets" dialog. */
1381 {
1382 int i;
1383 if ( the_game->player == our_seat )
1384 conc_callback(NULL,NULL);
1385 for (i=0;i<5;i++)
1386 #ifdef GTK2
1387 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(gtk_text_view_get_buffer(GTK_TEXT_VIEW(textpages[i]))),"",0);
1388 #else
1389 gtk_editable_delete_text(GTK_EDITABLE(textpages[i]),0,-1);
1390 #endif
1391 }
1392 playerdisp_update((PlayerDisp *)game_id_to_player(the_game,id)->userdata,m);
1393 break;
1394 case CMsgWashOut:
1395 /* display a message in every window of the scoring info */
1396 {
1397 CMsgWashOutMsg *wom = (CMsgWashOutMsg *)m;
1398 int i,npos=0;
1399 char text[1024] = "This hand is a WASH-OUT:\n";
1400 text[1023] = 0;
1401 if ( wom->reason ) {
1402 strmcat(text,wom->reason,1023-strlen(text));
1403 }
1404 for (i=0;i<5;i++) {
1405 npos=0;
1406 #ifdef GTK2
1407 GtkTextBuffer *g = GTK_TEXT_BUFFER(gtk_text_view_get_buffer(GTK_TEXT_VIEW(textpages[i])));
1408 gtk_text_buffer_set_text(g,"",0);
1409 gtk_text_buffer_insert_at_cursor(g,
1410 text,strlen(text));
1411 #else
1412 gtk_editable_delete_text(GTK_EDITABLE(textpages[i]),0,-1);
1413 gtk_editable_insert_text(GTK_EDITABLE(textpages[i]),
1414 text,strlen(text),&npos);
1415 #endif
1416 }
1417 if (!nopopups) {
1418 showraise(textwindow);
1419 //gdk_window_show(textwindow->window); /* uniconify */
1420 uniconify_window(textwindow);
1421 }
1422 }
1423 }
1424
1425 /* if the message was an error, or a closed set formation
1426 message about us, clear the closed_set_in_progress flag.
1427 The error case is dealt with above, since our error
1428 handling returns immediately.
1429 */
1430 {
1431 int mid = -99;
1432 switch ( m->type ) {
1433 case CMsgPlayerFormsClosedSpecialSet:
1434 mid = ((CMsgPlayerFormsClosedSpecialSetMsg *)m)->id;
1435 break;
1436 case CMsgPlayerFormsClosedPair:
1437 mid = ((CMsgPlayerFormsClosedPairMsg *)m)->id;
1438 break;
1439 case CMsgPlayerFormsClosedPung:
1440 mid = ((CMsgPlayerFormsClosedPungMsg *)m)->id;
1441 break;
1442 case CMsgPlayerFormsClosedChow:
1443 mid = ((CMsgPlayerFormsClosedChowMsg *)m)->id;
1444 break;
1445 case CMsgPlayerShowsTiles:
1446 mid = ((CMsgPlayerShowsTilesMsg *)m)->id;
1447 break;
1448 default: ;
1449 }
1450 if ( mid == our_id ) {
1451 closed_set_in_progress = 0;
1452 }
1453 }
1454
1455
1456 /* check the state of all the dialogs */
1457 /* As a special case, if the message was a Message, we won't
1458 check the dialog state, since that forces focus to the control
1459 dialogs. If we send a message from a chat window incorporated
1460 into the main table, we probably do not expect to lose focus
1461 when our message arrives */
1462 if ( m->type != CMsgMessage ) {
1463 setup_dialogs();
1464 }
1465
1466 /* after a HandScore message, stick the text in the display */
1467 if ( m->type == CMsgHandScore || m->type == CMsgSettlement ) {
1468 static char bigbuf[8192];
1469 CMsgHandScoreMsg *hsm = (CMsgHandScoreMsg *)m;
1470 CMsgSettlementMsg *sm = (CMsgSettlementMsg *)m;
1471 int pos;
1472 #ifndef GTK2
1473 int npos;
1474 #endif
1475 char *text;
1476
1477 if ( m->type == CMsgHandScore ) {
1478 pos = (game_id_to_seat(the_game,hsm->id)+NUM_SEATS-our_seat)%NUM_SEATS;
1479 text = hsm->explanation;
1480 } else {
1481 pos = 4;
1482 text = sm->explanation;
1483 }
1484
1485 expand_protocol_text(bigbuf,text,8192);
1486 #ifdef GTK2
1487 gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(gtk_text_view_get_buffer(GTK_TEXT_VIEW(textpages[pos]))),
1488 bigbuf,strlen(bigbuf));
1489 #else
1490 gtk_editable_insert_text(GTK_EDITABLE(textpages[pos]),
1491 bigbuf,strlen(bigbuf),&npos);
1492 #endif
1493 if ( m->type == CMsgSettlement ) {
1494 if ( ! nopopups ) {
1495 /* Since we have our own player selected in the notebook, when
1496 this pop up, it takes its size from the first page. This is
1497 usually wrong. So find the size requests of each page, and
1498 make page 0 ask for the maximum */
1499 GtkRequisition r; int i; int w=-1,h=-1;
1500 GtkAllocation a = { 0, 0, 2000, 2000 };
1501 gtk_widget_size_allocate(scoring_notebook,&a);
1502 for (i=0; i < 5; i++) {
1503 gtk_widget_size_request(textpages[i],&r);
1504 if ( r.width > w && r.width > 0 ) w = r.width;
1505 if ( r.height > h && r.height > 0 ) h = r.height;
1506 }
1507 if ( h > 0 ) h += 10; /* something not quite right */
1508 #ifdef GTK2
1509 gtk_widget_set_size_request(textpages[0],w,h);
1510 gtk_notebook_set_current_page(GTK_NOTEBOOK(scoring_notebook),0);
1511 #else
1512 gtk_widget_set_usize(textpages[0],w,h);
1513 gtk_notebook_set_page(GTK_NOTEBOOK(scoring_notebook),0);
1514 #endif
1515 if ( ! nopopups ) {
1516 showraise(textwindow);
1517 uniconify_window(textwindow);
1518 }
1519 }
1520 }
1521 }
1522
1523 /* update the scoring history after a Game or Settlement message */
1524
1525 if ( m->type == CMsgGame || m->type == CMsgSettlement ) {
1526 #ifdef GTK2
1527 GtkTextBuffer *g = GTK_TEXT_BUFFER(gtk_text_view_get_buffer(GTK_TEXT_VIEW(scorehistorytext)));
1528 static GtkTextTag *tag = NULL;
1529 if ( tag == NULL ) {
1530 tag = gtk_text_buffer_create_tag(g,
1531 "underline","underline",PANGO_UNDERLINE_SINGLE,NULL);
1532 }
1533 GtkTextIter iter;
1534 gtk_text_buffer_get_end_iter(g,&iter);
1535 gtk_text_buffer_place_cursor(g,&iter);
1536 #else
1537 int npos;
1538 GtkEditable *g = GTK_EDITABLE(scorehistorytext);
1539 gtk_text_set_point(GTK_TEXT(scorehistorytext),gtk_text_get_length(GTK_TEXT(scorehistorytext)));
1540 #endif
1541 char buf[80];
1542 int i;
1543 static seats lastround = noseat;
1544
1545 if ( m->type == CMsgGame ) {
1546 /* clear it */
1547 #ifdef GTK2
1548 gtk_text_buffer_set_text(g,"",0);
1549 #else
1550 gtk_editable_delete_text(g,0,-1);
1551 #endif
1552 /* build a line with the player names (in board order),
1553 marking original east with @ */
1554 strcpy(buf," ");
1555 for (i=0; i < NUM_SEATS; i++) {
1556 PlayerP p = the_game->players[board_to_game(i)];
1557 sprintf(1+buf+16*i,"%13.13s%c%s",p->name,
1558 (p->id == the_game->firsteast ? '@' : ' '),
1559 (i == NUM_SEATS-1 ? "\n" : " "));
1560 }
1561 #ifdef GTK2
1562 #define ADDBUF gtk_text_buffer_insert_at_cursor(g,buf,strlen(buf))
1563 #else
1564 #define ADDBUF gtk_editable_insert_text(g,buf,strlen(buf),&npos)
1565 #endif
1566 ADDBUF;
1567 /* now the initial scores */
1568 strcpy(buf," ");
1569 for (i=0; i < NUM_SEATS; i++) {
1570 PlayerP p = the_game->players[board_to_game(i)];
1571 sprintf(1+buf+16*i,"%13d %s",p->cumulative_score,
1572 (i == NUM_SEATS-1 ? "\n" : " "));
1573 }
1574 ADDBUF;
1575 /* and state the round */
1576 sprintf(buf,"%s round\n",windnames[lastround = the_game->round]);
1577 ADDBUF;
1578 } else {
1579 /* for settlement, display the handscores and the change,
1580 and then the scores. Underline the changes. */
1581 int chg[NUM_SEATS];
1582 CMsgSettlementMsg *sm = (CMsgSettlementMsg *)m;
1583 chg[east] = sm->east; chg[south] = sm->south;
1584 chg[west] = sm->west; chg[north] = sm->north;
1585 if ( lastround != the_game->round ) {
1586 sprintf(buf,"%s round\n",windnames[lastround = the_game->round]);
1587 ADDBUF;
1588 }
1589 for (i=0; i < NUM_SEATS; i++) {
1590 PlayerP p = the_game->players[board_to_game(i)];
1591 sprintf(buf,"%s%s%c",
1592 (i == 0 ? " " : ""),
1593 shortwindnames[p->wind],
1594 (the_game->player == board_to_game(i) ? '*' : ' '));
1595 ADDBUF;
1596 int len;
1597 sprintf(buf,"%d",p->hand_score);
1598 len = strlen(buf);
1599 sprintf(buf,"%*s(%d)",4-len,"",p->hand_score);
1600 ADDBUF;
1601 sprintf(buf,"%+5d",chg[board_to_game(i)]);
1602 #ifdef GTK2
1603 GtkTextIter iter;
1604 gtk_text_buffer_get_end_iter(g,&iter);
1605 gtk_text_buffer_insert_with_tags(g,&iter,
1606 buf,strlen(buf),tag,NULL);
1607 gtk_text_buffer_get_end_iter(g,&iter);
1608 gtk_text_buffer_place_cursor(g,&iter);
1609 #else
1610 ADDBUF;
1611 #endif
1612 strcpy(buf,(i < NUM_SEATS-1 ? " " : " \n"));
1613 ADDBUF;
1614 }
1615 #ifndef GTK2
1616 strcpy(buf," ----- ----- ----- ----- \n");
1617 ADDBUF;
1618 #endif
1619 /* and now the cumulative scores again */
1620 strcpy(buf," ");
1621 for (i=0; i < NUM_SEATS; i++) {
1622 PlayerP p = the_game->players[board_to_game(i)];
1623 sprintf(1+buf+16*i,"%13d %s",p->cumulative_score,
1624 (i == NUM_SEATS-1 ? "\n" : " "));
1625 }
1626 ADDBUF;
1627 }
1628 /* force window to scroll to end */
1629 #ifdef GTK2
1630 static GtkTextMark *mark = NULL;
1631 if ( mark == NULL ) {
1632 GtkTextIter eiter;
1633 gtk_text_buffer_get_end_iter(g,&eiter);
1634 mark = gtk_text_buffer_create_mark(g,"end",&eiter,FALSE);
1635 }
1636 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(scorehistorytext),
1637 mark, 0.0, TRUE, 0.0, 0.0);
1638 #endif
1639 }
1640
1641 }
1642
1643 /* setup the dialogs appropriate to the game state */
setup_dialogs(void)1644 void setup_dialogs(void) {
1645 if ( ! the_game ) {
1646 gtk_widget_hide(discard_dialog->widget);
1647 /* that might have been left up */
1648 return;
1649 }
1650
1651 if ( game_over ) {
1652 end_dialog_popup();
1653 return;
1654 }
1655
1656 /* in the discarded state, the dialog box should be up if somebody
1657 else discarded and we haven't claimed. If we've claimed,
1658 it should be down.
1659 Further, if we have a chowclaim in and the chowpending flag is set,
1660 the chow dialog should be up. */
1661 if ( the_game->state == Discarded
1662 && the_game->player != our_seat ) {
1663 int s;
1664 /* which player display is it? */
1665 s = the_game->player; /* seat */
1666 s = (s+NUM_SEATS-our_seat)%NUM_SEATS; /* posn relative to us */
1667 if ( the_game->active && !the_game->paused && the_game->claims[our_seat] == UnknownClaim )
1668 discard_dialog_popup(the_game->tile,s,0);
1669 else
1670 gtk_widget_hide(discard_dialog->widget);
1671 if ( the_game->active && !the_game->paused && the_game->claims[our_seat] == ChowClaim
1672 && the_game->chowpending )
1673 do_chow(NULL,(gpointer)AnyPos);
1674 }
1675
1676 /* check whether to pop down the claim windows */
1677 {
1678 int i;
1679 for (i=0;i<4;i++) {
1680 if ( ! pdisps[i].claimw || (! GTK_WIDGET_VISIBLE(pdisps[i].claimw) && ! GTK_WIDGET_VISIBLE(pdisps[i].thinkbox) ) ) continue;
1681 check_claim_window(&pdisps[i]);
1682 }
1683 }
1684
1685 /* in discarded or mahjonging states, the turn dialog should be down */
1686 if ( the_game->state == Discarded || the_game->state == MahJonging )
1687 gtk_widget_hide(turn_dialog);
1688
1689 /* in the discarding state, make sure the chow and ds dialogs are down.
1690 If it's our turn, the turn dialog should be up (unless we are in a
1691 konging state, when we're waiting for other claims), otherwise down.
1692 If there's a kong in progress, and we haven't claimed, the discard
1693 dialog should be up */
1694 if ( the_game->state == Discarding ) {
1695 gtk_widget_hide(chow_dialog);
1696 gtk_widget_hide(ds_dialog);
1697 if ( the_game->active && !the_game->paused
1698 && the_game->player == our_seat
1699 && ! the_game->konging )
1700 turn_dialog_popup();
1701 else
1702 gtk_widget_hide(turn_dialog);
1703 if ( the_game->active && !the_game->paused && the_game->player != our_seat
1704 && the_game->konging
1705 && ! querykong /* wait till we know whether we can claim */
1706 && the_game->claims[our_seat] == UnknownClaim ) {
1707 seats s;
1708 /* which player display is it? */
1709 s = the_game->player; /* seat */
1710 s = (s+NUM_SEATS-our_seat)%NUM_SEATS; /* posn relative to us */
1711 discard_dialog_popup(the_game->tile,s,2);
1712 } else gtk_widget_hide(discard_dialog->widget);
1713 }
1714
1715 /* If it is declaring specials time, and our turn,
1716 make sure the ds dialog is up. If it's not our turn,
1717 make sure it's down.
1718 Also make sure the turn and discard dialogs are down
1719 If somebody else has a kong in progress, and we haven't claimed,
1720 then the claim box should be up.
1721 */
1722 if ( the_game->active && !the_game->paused && the_game->state == DeclaringSpecials ) {
1723 if ( the_game->player == our_seat ) {
1724 if ( !playing_auto_declare_specials || may_declare_kong ) ds_dialog_popup();
1725 } else {
1726 gtk_widget_hide(ds_dialog);
1727 }
1728 gtk_widget_hide(turn_dialog);
1729 if ( the_game->konging
1730 && the_game->player != our_seat
1731 && the_game->claims[our_seat] == UnknownClaim ) {
1732 seats s;
1733 /* which player display is it? */
1734 s = the_game->player; /* seat */
1735 s = (s+NUM_SEATS-our_seat)%NUM_SEATS; /* posn relative to us */
1736 discard_dialog_popup(the_game->tile,s,2);
1737 } else
1738 gtk_widget_hide(discard_dialog->widget);
1739 } else
1740 gtk_widget_hide(ds_dialog);
1741
1742
1743
1744 /* In the mahjonging state, if we're the winner:
1745 If the pending flag is set, pop up the discard dialog if
1746 the chowpending is not set, otherwise the chowdialog.
1747 Otherwise, pop down both it and the chow dialog.
1748 Pop down the turn dialog.
1749 */
1750 if ( the_game->state == MahJonging
1751 && the_game->player == our_seat ) {
1752 gtk_widget_hide(turn_dialog);
1753 if ( the_game->active && !the_game->paused
1754 && the_game->mjpending ) {
1755 int s;
1756 /* which player display is it? */
1757 s = the_game->supplier;
1758 s = (s+NUM_SEATS-our_seat)%NUM_SEATS; /* posn relative to us */
1759 if ( the_game->chowpending ) {
1760 do_chow(NULL,(gpointer)AnyPos);
1761 } else {
1762 discard_dialog_popup(the_game->tile,s,1);
1763 }
1764 } else {
1765 gtk_widget_hide(discard_dialog->widget);
1766 gtk_widget_hide(chow_dialog);
1767 }
1768 }
1769
1770 /* In the mahjonging state, if our hand is not declared,
1771 and the pending flag is not set, and if either we
1772 are the winner or the winner's hand is declared
1773 popup the scoring dialog, otherwise down */
1774 if ( !auto_declaring_hand
1775 && the_game->active && !the_game->paused
1776 && the_game->state == MahJonging
1777 && !the_game->mjpending
1778 && !pflag(our_player,HandDeclared)
1779 && (the_game->player == our_seat
1780 || pflag(the_game->players[the_game->player],HandDeclared) ) ) {
1781 /* if we don't have a tile selected and we're the winner, select the first */
1782 if ( selected_button < 0 && the_game->player == our_seat )
1783 conc_callback(pdisps[0].conc[0],(gpointer)(0 | 128));
1784 scoring_dialog_popup();
1785 /* to reduce confusing, make it insensitive if we've made a request
1786 and are waiting for the reply */
1787 gtk_widget_set_sensitive(scoring_dialog,!closed_set_in_progress);
1788 } else {
1789 gtk_widget_hide(scoring_dialog);
1790 }
1791
1792 /* If the game is not active, or active but paused,
1793 pop up the continue dialog */
1794 if ( (!the_game->active) || the_game->paused )
1795 continue_dialog_popup();
1796 else
1797 gtk_widget_hide(continue_dialog);
1798
1799 /* make sure the discard dialog (or turn) is down */
1800 if ( the_game->state == HandComplete ) {
1801 gtk_widget_hide(discard_dialog->widget);
1802 gtk_widget_hide(turn_dialog);
1803 }
1804
1805 }
1806
stdin_input_callback(gpointer data UNUSED,gint source,GdkInputCondition condition UNUSED)1807 static void stdin_input_callback(gpointer data UNUSED,
1808 gint source,
1809 GdkInputCondition condition UNUSED) {
1810 char *l;
1811
1812 l = get_line(source);
1813 if ( l ) {
1814 put_line(the_game->fd,l);
1815 /* we're bypassing the client routines, so we must update the
1816 sequence number ourselves */
1817 the_game->cseqno++;
1818 }
1819 }
1820
1821 #ifdef GTK2
1822 /* functions to achieve moving tiles around by dragging */
1823 static int drag_tile = -1; /* index of tile being dragged */
1824 /* call back when a drag starts */
drag_begin_callback(GtkWidget * w UNUSED,GdkDragContext * c UNUSED,gpointer u)1825 static void drag_begin_callback(GtkWidget *w UNUSED, GdkDragContext *c UNUSED, gpointer u) {
1826 drag_tile = (int)(intptr_t)u;
1827 }
1828 /* motion callback */
drag_motion_callback(GtkWidget * w UNUSED,GdkDragContext * c,gint x UNUSED,gint y UNUSED,guint ts,gpointer u UNUSED)1829 static gboolean drag_motion_callback(GtkWidget *w UNUSED, GdkDragContext *c,
1830 gint x UNUSED, gint y UNUSED, guint ts, gpointer u UNUSED) {
1831 gdk_drag_status(c,c->suggested_action,ts);
1832 return TRUE;
1833 }
1834 /* call back when a drop occurs */
drag_drop_callback(GtkWidget * w UNUSED,GdkDragContext * c,gint x,gint y UNUSED,guint ts,gpointer u)1835 static gboolean drag_drop_callback(GtkWidget *w UNUSED, GdkDragContext *c,
1836 gint x, gint y UNUSED, guint ts, gpointer u) {
1837 int new = (int)(intptr_t)u;
1838 /* if the tile is moving right, and the drop is in the left half,
1839 the new position is one less than here, otherwise it's here.
1840 If the tile is moving left, and the drop is in the left half,
1841 the new position is here, otherwise one more than here */
1842 if ( new == drag_tile ) return FALSE;
1843 gtk_drag_finish(c,TRUE,FALSE,ts);
1844 if ( new > drag_tile ) {
1845 if ( x < tile_width/2 ) new--;
1846 assert(new >= 0);
1847 } else {
1848 if ( x > tile_width/2 ) new++;
1849 assert(new < our_player->num_concealed);
1850 }
1851 player_reorder_tile(our_player,drag_tile,new);
1852 playerdisp_update_concealed(&pdisps[0],HiddenTile);
1853 /* clear selection to avoid confusion */
1854 conc_callback(NULL,NULL);
1855 return TRUE;
1856 }
1857
1858
1859 #endif
1860
1861 /* given an already allocated PlayerDisp* and an orientation,
1862 initialize a player display.
1863 The player widget is NOT shown, but everything else (appropriate) is */
playerdisp_init(PlayerDisp * pd,int ori)1864 static void playerdisp_init(PlayerDisp *pd, int ori) {
1865 /* here is the widget hierarchy we want:
1866 w -- the parent widget
1867 concealed -- the concealed row
1868 csets[MAX_TILESETS] -- five boxes which will have tiles packed in them
1869 conc -- the concealed & special tile subbox, with...
1870 conc[14] -- 14 tile buttons
1871 extras[8] -- more spaces for specials if we run out.
1872 pdispwidth-14 are initially set, to get the spacing.
1873 exposed -- the exposed row
1874 esets[MAX_TILESETS] -- with sets, each of which has
1875 tile buttons as appropriate.
1876 specbox --
1877 spec[8] -- places for specials
1878 (Not all of these can always be used; so we may
1879 need to balance dynamically between this and extras.)
1880 tongbox -- the tong box pixmap widget
1881 */
1882 GtkWidget *concealed, *exposed, *conc, *c, *b, *pm, *w,*specbox;
1883 int i;
1884 int tb,br,bl;
1885 int eori; /* orientation of exposed tiles, if different */
1886
1887
1888 /* pd->player = NULL; */ /* don't do this: playerdisp_init is
1889 also called when rebuilding the display, in which case we
1890 shouldn't null the player. The player field should be nulled
1891 when the PlayerDisp is created */
1892 pd->orientation = ori;
1893 eori = flipori(ori);
1894
1895 /* sometimes it's enough to know whether the player is top/bottom,
1896 or the left/right */
1897 tb = ! (ori%2);
1898 /* and sometimes the bottom and right players are similar
1899 (they are to the increasing x/y of their tiles */
1900 br = (ori < 2);
1901 /* and sometimes the bottom and left are similar (their right
1902 is at the positive and of the box */
1903 bl = (ori == 0 || ori == 3);
1904
1905 /* homogeneous to force exposed row to right height */
1906 if ( tb )
1907 w = gtk_vbox_new(1,tile_spacing);
1908 else
1909 w = gtk_hbox_new(1,tile_spacing);
1910 #ifndef GTK2
1911 gtk_widget_set_style(w,tablestyle);
1912 #endif
1913 gtk_widget_set_name(w,"table");
1914
1915 pd->widget = w;
1916 gtk_container_set_border_width(GTK_CONTAINER(w),player_border_width);
1917
1918 if ( tb )
1919 concealed = gtk_hbox_new(0,tile_spacing);
1920 else
1921 concealed = gtk_vbox_new(0,tile_spacing);
1922 #ifndef GTK2
1923 gtk_widget_set_style(concealed,tablestyle);
1924 #endif
1925 gtk_widget_set_name(concealed,"table");
1926 gtk_widget_show(concealed);
1927 /* the concealed box is towards the player */
1928 /* This box needs to stay expanded to the width of the surrounding,
1929 which should be fixed at start up.*/
1930 if ( br )
1931 gtk_box_pack_end(GTK_BOX(w),concealed,1,1,0);
1932 else
1933 gtk_box_pack_start(GTK_BOX(w),concealed,1,1,0);
1934
1935 /* players put closed (nonkong) tilesets in to the left of their
1936 concealed tiles */
1937 for ( i=0; i < MAX_TILESETS; i++) {
1938 if ( tb )
1939 c = gtk_hbox_new(0,0);
1940 else
1941 c = gtk_vbox_new(0,0);
1942 if ( bl )
1943 gtk_box_pack_start(GTK_BOX(concealed),c,0,0,0);
1944 else
1945 gtk_box_pack_end(GTK_BOX(concealed),c,0,0,0);
1946 pd->csets[i].widget = c;
1947 tilesetbox_init(&pd->csets[i],ori,NULL,0);
1948 }
1949
1950 if ( tb )
1951 conc = gtk_hbox_new(0,0);
1952 else
1953 conc = gtk_vbox_new(0,0);
1954 #ifndef GTK2
1955 gtk_widget_set_style(conc,tablestyle);
1956 #endif
1957 gtk_widget_set_name(conc,"table");
1958 gtk_widget_show(conc);
1959
1960 /* the concealed tiles are to the right of the concealed sets */
1961 /* This also needs to stay expanded */
1962 if ( bl )
1963 gtk_box_pack_start(GTK_BOX(concealed),conc,1,1,0);
1964 else
1965 gtk_box_pack_end(GTK_BOX(concealed),conc,1,1,0);
1966
1967 for ( i=0; i < 14; i++ ) {
1968 /* normally, just buttons, but for us toggle buttons */
1969 if ( ori == 0 )
1970 b = gtk_toggle_button_new();
1971 else
1972 b = gtk_button_new();
1973 /* for gtk2 style info */
1974 gtk_widget_set_name(b, (ori == 0) ? "mytile" : "tile");
1975 /* we don't want any of the buttons taking the focus */
1976 GTK_WIDGET_UNSET_FLAGS(b,GTK_CAN_FOCUS);
1977 gtk_widget_show(b);
1978 pm = gtk_pixmap_new(tilepixmaps[ori][HiddenTile],NULL);
1979 gtk_widget_show(pm);
1980 gtk_container_add(GTK_CONTAINER(b),pm);
1981 /* the concealed tiles are placed from the player's left */
1982 if ( bl )
1983 gtk_box_pack_start(GTK_BOX(conc),b,0,0,0);
1984 else
1985 gtk_box_pack_end(GTK_BOX(conc),b,0,0,0);
1986 pd->conc[i] = b;
1987 /* if this is our player (ori = 0), attach a callback */
1988 if ( ori == 0 ) {
1989 gtk_signal_connect(GTK_OBJECT(b),"toggled",
1990 GTK_SIGNAL_FUNC(conc_callback),(gpointer)(intptr_t)i);
1991 gtk_signal_connect(GTK_OBJECT(b),"button_press_event",
1992 GTK_SIGNAL_FUNC(doubleclicked),0);
1993 #ifdef GTK2
1994 /* and set as a drag source */
1995 /* Why do I have to use ACTION_MOVE ? Why can't I use
1996 ACTION_PRIVATE, which just doesn't work ? */
1997 gtk_drag_source_set(b,GDK_BUTTON1_MASK,NULL,0,GDK_ACTION_MOVE);
1998 /* and a drag destination, or at least an emitter of events */
1999 gtk_drag_dest_set(b,0,NULL,0,0);
2000 gtk_drag_dest_set_track_motion(b,TRUE);
2001 gtk_signal_connect(GTK_OBJECT(b),"drag_begin",
2002 GTK_SIGNAL_FUNC(drag_begin_callback),(gpointer)(intptr_t)i);
2003 gtk_signal_connect(GTK_OBJECT(b),"drag_motion",
2004 GTK_SIGNAL_FUNC(drag_motion_callback),(gpointer)(intptr_t)i);
2005 gtk_signal_connect(GTK_OBJECT(b),"drag_drop",
2006 GTK_SIGNAL_FUNC(drag_drop_callback),(gpointer)(intptr_t)i);
2007 #endif
2008 }
2009 }
2010
2011 /* to the right of the concealed tiles, we will place
2012 up to 8 other tiles. These will be used for flowers
2013 and seasons when we run out of room in the exposed row.
2014 By setting pdispwidth-14 (default 5) of them initially,
2015 it also serves the purpose of fixing the row to be
2016 the desired 19 (currently) tiles wide */
2017 for ( i=0; i < 8; i++ ) {
2018 b = gtk_button_new();
2019 gtk_widget_set_name(b,"tile");
2020 /* we don't want any of the buttons taking the focus */
2021 GTK_WIDGET_UNSET_FLAGS(b,GTK_CAN_FOCUS);
2022 if ( i < pdispwidth-14 ) gtk_widget_show(b);
2023 pm = gtk_pixmap_new(tilepixmaps[ori][HiddenTile],NULL);
2024 gtk_widget_show(pm);
2025 gtk_container_add(GTK_CONTAINER(b),pm);
2026 /* these extra tiles are placed from the player's right */
2027 if ( bl )
2028 gtk_box_pack_end(GTK_BOX(conc),b,0,0,0);
2029 else
2030 gtk_box_pack_start(GTK_BOX(conc),b,0,0,0);
2031 pd->extras[i] = b;
2032 }
2033
2034
2035
2036
2037 if ( tb )
2038 exposed = gtk_hbox_new(0,tile_spacing);
2039 else
2040 exposed = gtk_vbox_new(0,tile_spacing);
2041 #ifndef GTK2
2042 gtk_widget_set_style(exposed,tablestyle);
2043 #endif
2044 gtk_widget_set_name(exposed,"table");
2045 gtk_widget_show(exposed);
2046 /* the exposed box is away from the player */
2047 if ( br )
2048 gtk_box_pack_start(GTK_BOX(w),exposed,0,0,0);
2049 else
2050 gtk_box_pack_end(GTK_BOX(w),exposed,0,0,0);
2051
2052 /* the exposed tiles will be in front of the player, from
2053 the left */
2054 for ( i=0; i < MAX_TILESETS; i++ ) {
2055 if ( tb )
2056 c = gtk_hbox_new(0,0);
2057 else
2058 c = gtk_vbox_new(0,0);
2059 #ifndef GTK2
2060 gtk_widget_set_style(c,tablestyle);
2061 #endif
2062 gtk_widget_set_name(c,"table");
2063 gtk_widget_show(c);
2064 if ( bl )
2065 gtk_box_pack_start(GTK_BOX(exposed),c,0,0,0);
2066 else
2067 gtk_box_pack_end(GTK_BOX(exposed),c,0,0,0);
2068 pd->esets[i].widget = c;
2069 tilesetbox_init(&pd->esets[i],eori,NULL,0);
2070 }
2071 /* the special tiles are at the right of the exposed row */
2072 if ( tb )
2073 specbox = gtk_hbox_new(0,0);
2074 else
2075 specbox = gtk_vbox_new(0,0);
2076 #ifndef GTK2
2077 gtk_widget_set_style(specbox,tablestyle);
2078 #endif
2079 gtk_widget_set_name(specbox,"table");
2080 gtk_widget_show(specbox);
2081 if ( bl )
2082 gtk_box_pack_end(GTK_BOX(exposed),specbox,0,0,0);
2083 else
2084 gtk_box_pack_start(GTK_BOX(exposed),specbox,0,0,0);
2085
2086 /* at the right of the spec box, we place a tongbox pixmap.
2087 This is not initially shown */
2088 pd->tongbox = gtk_pixmap_new(tongpixmaps[ori][east],tongmask);
2089 if ( bl )
2090 gtk_box_pack_end(GTK_BOX(specbox),pd->tongbox,0,0,0);
2091 else
2092 gtk_box_pack_start(GTK_BOX(specbox),pd->tongbox,0,0,0);
2093
2094 for ( i=0 ; i < 8; i++ ) {
2095 b = gtk_button_new();
2096 gtk_widget_set_name(b,"tile");
2097 /* we don't want any of the buttons taking the focus */
2098 GTK_WIDGET_UNSET_FLAGS(b,GTK_CAN_FOCUS);
2099 pm = gtk_pixmap_new(tilepixmaps[eori][HiddenTile],NULL);
2100 gtk_widget_show(pm);
2101 gtk_container_add(GTK_CONTAINER(b),pm);
2102 if ( bl )
2103 gtk_box_pack_end(GTK_BOX(specbox),b,0,0,0);
2104 else
2105 gtk_box_pack_start(GTK_BOX(specbox),b,0,0,0);
2106 pd->spec[i] = b;
2107 }
2108
2109 /* the player's discard buttons are created as required,
2110 so just zero them */
2111 for ( i=0; i<32; i++ ) pd->discards[i] = NULL;
2112 pd->num_discards = 0;
2113 /* also initialize the info kept by the discard routine */
2114 pd->x = pd->y = 0;
2115 pd->row = 0;
2116 pd->plane = 0;
2117 for (i=0;i<5;i++) {
2118 pd->xmin[i] = 10000;
2119 pd->xmax[i] = 0;
2120 }
2121
2122 pd->claimw = NULL;
2123 /* the claim window is initialized on first popup, because it
2124 needs to have all the rest of the board set out to get the size right */
2125 }
2126
2127 /* event callback used below. The widget is a tile button, and the
2128 callback data is its tiletip window. For use below, a NULL event
2129 means always pop it up */
maybe_popup_tiletip(GtkWidget * w,GdkEvent * ev,gpointer data)2130 static gint maybe_popup_tiletip(GtkWidget *w, GdkEvent *ev, gpointer data)
2131 {
2132 GtkWidget *tiletip = (GtkWidget *)data;
2133
2134 /* popup if this is an enternotify with the right button already
2135 down, or if this is a button press with the right button */
2136 if ( ev == NULL
2137 || ( ev->type == GDK_ENTER_NOTIFY
2138 && ( (((GdkEventCrossing *)ev)->state & GDK_BUTTON3_MASK)
2139 || tiletips ) )
2140 || ( ev->type == GDK_BUTTON_PRESS
2141 && ((GdkEventButton *)ev)->button == 3 ) ) {
2142 /* pop it up just above, right, left, below the tile */
2143 int ori = 0;
2144 gint x, y;
2145 gint ttx = 0, tty = 0;
2146 GtkRequisition r;
2147 get_relative_posn(boardfixed,w,&x,&y);
2148 gtk_widget_size_request(tiletip,&r);
2149 ori = (int)(intptr_t)gtk_object_get_data(GTK_OBJECT(w),"ori");
2150 switch ( ori ) {
2151 case 0: ttx = x; tty = y-r.height; break;
2152 case 1: ttx = x - r.width; tty = y; break;
2153 case 2: ttx = x; tty = y + tile_height; break;
2154 case 3: ttx = x + tile_height; tty = y; break;
2155 default: warn("impossible ori in maybe_popup_tiletip");
2156 }
2157 vlazy_fixed_move(VLAZY_FIXED(boardfixed),tiletip,ttx,tty);
2158 gtk_widget_show(tiletip);
2159 }
2160 /* if it's a leavenotify, and the tile is not selected, pop down,
2161 unless the tile is a selected button */
2162 if ( ev && ev->type == GDK_LEAVE_NOTIFY ) {
2163 if ( tiletips
2164 && GTK_WIDGET_VISIBLE(w)
2165 && GTK_IS_TOGGLE_BUTTON(w)
2166 && GTK_TOGGLE_BUTTON(w)->active ) {
2167 ; // leave it up
2168 } else {
2169 gtk_widget_hide(tiletip);
2170 }
2171 }
2172 return 0;
2173 }
2174 /* signal callback used to popup tiletips on selecting a tile */
maybe_popup_tiletip_when_selected(GtkWidget * w,gpointer data)2175 static gint maybe_popup_tiletip_when_selected(GtkWidget *w, gpointer data)
2176 {
2177 GtkWidget *tiletip = (GtkWidget *)data;
2178 if ( tiletips && GTK_IS_TOGGLE_BUTTON(w) && GTK_TOGGLE_BUTTON(w)->active && GTK_WIDGET_VISIBLE(w) ) {
2179 maybe_popup_tiletip(w,NULL,data);
2180 } else {
2181 gtk_widget_hide(tiletip);
2182 }
2183 return 0;
2184 }
2185
2186
2187 /* given a button, tile, and orientation, set the pixmap (and tooltip when
2188 we have them. Stash the tile in the user_data field.
2189 Stash the ori as ori.
2190 Last arg is true if a tiletip should be installed */
button_set_tile_aux(GtkWidget * b,Tile t,int ori,int use_tiletip)2191 static void button_set_tile_aux(GtkWidget *b, Tile t, int ori,int use_tiletip) {
2192 intptr_t ti;
2193 GtkWidget *tiletip, *lab;
2194 gtk_pixmap_set(GTK_PIXMAP(GTK_BIN(b)->child),
2195 tilepixmaps[ori][t],NULL);
2196 ti = t;
2197 gtk_object_set_user_data(GTK_OBJECT(b),(gpointer)ti);
2198 gtk_object_set_data(GTK_OBJECT(b),"ori",(gpointer)(intptr_t)ori);
2199 if ( use_tiletip ) {
2200 /* update, create or delete the tiletip */
2201 tiletip = (GtkWidget *)gtk_object_get_data(GTK_OBJECT(b),"tiletip");
2202 if ( t == HiddenTile ) {
2203 if ( tiletip ) gtk_widget_destroy(tiletip);
2204 gtk_object_set_data(GTK_OBJECT(b),"tiletip",0);
2205 } else {
2206 if ( tiletip ) {
2207 /* update label */
2208 gtk_label_set_text(GTK_LABEL(GTK_BIN(tiletip)->child),
2209 tile_name(t));
2210 } else {
2211 /* create tiletip */
2212 tiletip = gtk_event_box_new();
2213 /* it will be a child of board fixed, but its window
2214 will be moved around, and its widget position ignored */
2215 vlazy_fixed_put(VLAZY_FIXED(boardfixed),tiletip,0,0);
2216 gtk_object_set_data(GTK_OBJECT(b),"tiletip",tiletip);
2217 lab = gtk_label_new(tile_name(t));
2218 gtk_widget_show(lab);
2219 gtk_container_add(GTK_CONTAINER(tiletip),lab);
2220 /* install callbacks on the button, which will die when
2221 the popup is killed */
2222 /* callback on enter */
2223 gtk_signal_connect_while_alive(GTK_OBJECT(b),"enter_notify_event",
2224 (GtkSignalFunc)maybe_popup_tiletip,
2225 tiletip,
2226 GTK_OBJECT(tiletip));
2227 /* callback on button press */
2228 gtk_signal_connect_while_alive(GTK_OBJECT(b),"button_press_event",
2229 (GtkSignalFunc)maybe_popup_tiletip,
2230 tiletip,
2231 GTK_OBJECT(tiletip));
2232 /* leaving may pop down the window */
2233 gtk_signal_connect_while_alive(GTK_OBJECT(b),"leave_notify_event",
2234 (GtkSignalFunc)maybe_popup_tiletip,
2235 tiletip,
2236 GTK_OBJECT(tiletip));
2237 /* toggling may require tile tips */
2238 if ( GTK_IS_TOGGLE_BUTTON(b) ) {
2239 gtk_signal_connect_while_alive(GTK_OBJECT(b),"toggled",
2240 (GtkSignalFunc)maybe_popup_tiletip_when_selected,
2241 tiletip,
2242 GTK_OBJECT(tiletip));
2243 }
2244 /* also want to make sure tip goes away on being hidden. */
2245 gtk_signal_connect_object_while_alive(GTK_OBJECT(b),"hide",
2246 (GtkSignalFunc)gtk_widget_hide,GTK_OBJECT(tiletip));
2247 /* and if the tile is destroyed, the tip had better be */
2248 gtk_signal_connect_object_while_alive(GTK_OBJECT(b),"destroy",
2249 (GtkSignalFunc)gtk_widget_destroy,GTK_OBJECT(tiletip));
2250 }
2251 }
2252 }
2253 }
2254
button_set_tile(GtkWidget * b,Tile t,int ori)2255 void button_set_tile(GtkWidget *b, Tile t, int ori)
2256 {
2257 button_set_tile_aux(b,t,ori,1);
2258 }
2259
button_set_tile_without_tiletip(GtkWidget * b,Tile t,int ori)2260 void button_set_tile_without_tiletip(GtkWidget *b, Tile t, int ori)
2261 {
2262 button_set_tile_aux(b,t,ori,0);
2263 }
2264
2265 /* given a pointer to a TileSetBox, and an orientation,
2266 and a callback function and data
2267 initialize the box by creating the widgets and attaching
2268 the signal function.
2269 */
tilesetbox_init(TileSetBox * tb,int ori,GtkSignalFunc func,gpointer func_data)2270 void tilesetbox_init(TileSetBox *tb, int ori,
2271 GtkSignalFunc func,gpointer func_data) {
2272 int i;
2273 GtkWidget *b, *pm;
2274
2275 for (i=0; i<4; i++) {
2276 /* make a button */
2277 b = gtk_button_new();
2278 gtk_widget_set_name(b,"tile");
2279 tb->tiles[i] = b;
2280 if ( func )
2281 gtk_signal_connect(GTK_OBJECT(b),"clicked",
2282 func,func_data);
2283 /* we don't want any of the tiles taking the focus */
2284 GTK_WIDGET_UNSET_FLAGS(b,GTK_CAN_FOCUS);
2285 pm = gtk_pixmap_new(tilepixmaps[ori][HiddenTile],NULL);
2286 gtk_widget_show(pm);
2287 gtk_container_add(GTK_CONTAINER(b),pm);
2288 /* we want chows to grow from top and left for left and bottom */
2289 if ( ori == 0 || ori == 3 )
2290 gtk_box_pack_start(GTK_BOX(tb->widget),b,0,0,0);
2291 else
2292 gtk_box_pack_end(GTK_BOX(tb->widget),b,0,0,0);
2293 }
2294
2295 tb->set.type = Empty;
2296 }
2297
2298 /* given a pointer to a TileSetBox, and a pointer to a TileSet,
2299 and an orientation,
2300 make the box display the tileset.
2301 */
tilesetbox_set(TileSetBox * tb,const TileSet * ts,int ori)2302 void tilesetbox_set(TileSetBox *tb, const TileSet *ts, int ori) {
2303 int n, i, ck, millingkong;
2304 Tile t[4];
2305
2306 if (tb->set.type == ts->type
2307 && (ts->type == Empty || tb->set.tile == ts->tile) ) {
2308 /* nothing to do, except show for safety */
2309 if ( ts->type == Empty )
2310 gtk_widget_hide(tb->widget);
2311 else
2312 gtk_widget_show(tb->widget);
2313 return;
2314 }
2315
2316 tb->set = *ts;
2317
2318 n = 2; /* for pairs etc */
2319 ck = 0; /* closed kong */
2320 millingkong = 0; /* playing Millington's rules, and the kong
2321 is claimed rather than annexed */
2322 switch (ts->type) {
2323 case ClosedKong:
2324 ck = 1;
2325 case Kong:
2326 n++;
2327 if ( !ck && ! ts->annexed
2328 && the_game && game_get_option_value(the_game,GOKongHas3Types,NULL).optbool )
2329 millingkong = 1;
2330 case Pung:
2331 case ClosedPung:
2332 n++;
2333 case Pair:
2334 case ClosedPair:
2335 for ( i=0 ; i < n ; i++ ) {
2336 t[i] = ts->tile;
2337 if ( ck && ( i == 0 || i == 3 ) ) t[i] = HiddenTile;
2338 if ( millingkong && i == 3 ) t[i] = HiddenTile;
2339 }
2340 break;
2341 case Chow:
2342 case ClosedChow:
2343 n = 3;
2344 for (i=0; i<n; i++) {
2345 t[i] = ts->tile+i;
2346 }
2347 break;
2348 case Empty:
2349 n = 0;
2350 }
2351
2352 for (i=0; i<n; i++) {
2353 button_set_tile(tb->tiles[i],t[i],ori);
2354 gtk_widget_show(tb->tiles[i]);
2355 }
2356 for ( ; i < 4; i++ )
2357 if ( tb->tiles[i] ) gtk_widget_hide(tb->tiles[i]);
2358
2359 if ( ts->type == Empty )
2360 gtk_widget_hide(tb->widget);
2361 else
2362 gtk_widget_show(tb->widget);
2363 }
2364
2365 /* utility used by the chow functions. Given a TileSetBox,
2366 highlight the nth tile by setting the others insensitive.
2367 (n counts from zero). */
tilesetbox_highlight_nth(TileSetBox * tb,int n)2368 void tilesetbox_highlight_nth(TileSetBox *tb,int n) {
2369 int i;
2370 for (i=0; i<4; i++)
2371 if ( tb->tiles[i] )
2372 gtk_widget_set_sensitive(tb->tiles[i],i==n);
2373 }
2374
2375
2376 /* two routines used internally */
2377
2378 /* update the concealed tiles of the playerdisp.
2379 Returns the *index* of the last tile matching the third argument.
2380 */
playerdisp_update_concealed(PlayerDisp * pd,Tile t)2381 static int playerdisp_update_concealed(PlayerDisp *pd,Tile t) {
2382 PlayerP p = pd->player;
2383 int tindex = -1;
2384 int i;
2385 int cori,eori;
2386
2387 cori = pd->orientation;
2388 eori = flipori(cori);
2389 if ( p && pflag(p,HandDeclared) ) cori = eori;
2390
2391 for ( i=0 ; p && i < p->num_concealed ; i++ ) {
2392 button_set_tile(pd->conc[i],p->concealed[i],cori);
2393 gtk_widget_show(pd->conc[i]);
2394 if ( p->concealed[i] == t ) tindex = i;
2395 }
2396 for ( ; i < MAX_CONCEALED ; i++ ) {
2397 gtk_widget_hide(pd->conc[i]);
2398 }
2399 /* if it's the player's turn, other than ours, highlight their
2400 rightmost tile to indicate this */
2401 if ( the_game != NULL ) {
2402 if ( the_game->player != our_seat
2403 && p == the_game->players[the_game->player] ) {
2404 if ( the_game->state == DeclaringSpecials
2405 || the_game->state == Discarding ) {
2406 GtkWidget *t = pd->conc[p->num_concealed-1];
2407 if ( t != highlitinhand && highlitinhand != NULL ) {
2408 #ifdef GTK2
2409 gtk_widget_modify_bg(highlitinhand,GTK_STATE_NORMAL,NULL);
2410 #else
2411 gtk_widget_restore_default_style(highlitinhand);
2412 #endif
2413 highlitinhand = NULL;
2414 }
2415 #ifdef GTK2
2416 gtk_widget_modify_bg(highlitinhand = t,GTK_STATE_NORMAL,&highlightcolor);
2417 #else
2418 gtk_widget_set_style(highlitinhand = t,highlightstyle);
2419 #endif
2420 } else {
2421 if ( highlitinhand != NULL ) {
2422 #ifdef GTK2
2423 gtk_widget_modify_bg(highlitinhand,GTK_STATE_NORMAL,NULL);
2424 #else
2425 gtk_widget_restore_default_style(highlitinhand);
2426 #endif
2427 highlitinhand = NULL;
2428 }
2429 }
2430 } else if ( the_game->player == our_seat ) {
2431 /* clear any highlighting */
2432 if ( highlitinhand != NULL ) {
2433 #ifdef GTK2
2434 gtk_widget_modify_bg(highlitinhand,GTK_STATE_NORMAL,NULL);
2435 #else
2436 gtk_widget_restore_default_style(highlitinhand);
2437 #endif
2438 highlitinhand = NULL;
2439 }
2440 }
2441 }
2442 return tindex;
2443 }
2444
2445 /* update the exposed tiles of the playerdisp.
2446 Returns the _button widget_ of the last tile that
2447 matched the second argument.
2448 The third argument causes special treatment: an exposed pung matching
2449 the tile is displayed as a kong.
2450 */
playerdisp_update_exposed(PlayerDisp * pd,Tile t,int robbingkong)2451 static GtkWidget *playerdisp_update_exposed(PlayerDisp *pd,Tile t,int robbingkong){
2452 int i,scount,excount,ccount,ecount,qtiles;
2453 GtkWidget *w;
2454 TileSet emptyset;
2455 PlayerP p = pd->player;
2456 int cori, eori; /* orientations of concealed and exposed tiles */
2457
2458 cori = pd->orientation;
2459 eori = flipori(cori);
2460 emptyset.type = Empty;
2461 if ( p && pflag(p,HandDeclared) ) cori = eori;
2462
2463 w = NULL; /* destination widget */
2464 /* We need to count the space taken up by the exposed tilesets,
2465 in order to know where to put the specials */
2466 qtiles = 0; /* number of quarter tile widths */
2467 ccount = ecount = 0;
2468 for ( i=0; p && i < MAX_TILESETS; i++) {
2469 switch (p->tilesets[i].type) {
2470 case ClosedPair:
2471 case ClosedChow:
2472 /* in the concealed row, so no space */
2473 /* NB, these are declared, they are oriented as
2474 exposed sets, despite the name */
2475 tilesetbox_set(&pd->csets[ccount++],&p->tilesets[i],eori);
2476 break;
2477 case ClosedPung:
2478 /* in the special case that we are in the middle of robbing
2479 a kong (13 wonders), and this is the robbed set, we keep
2480 it in the exposed row displayed as a kong */
2481 if ( robbingkong && t == p->tilesets[i].tile) {
2482 TileSet ts = p->tilesets[i];
2483 ts.type = ClosedKong;
2484 tilesetbox_set(&pd->esets[ecount],&ts,eori);
2485 w = pd->esets[ecount].tiles[3];
2486 qtiles += 16; /* four tiles */
2487 ecount++;
2488 } else {
2489 tilesetbox_set(&pd->csets[ccount++],&p->tilesets[i],eori);
2490 }
2491 break;
2492 case ClosedKong:
2493 case Kong:
2494 tilesetbox_set(&pd->esets[ecount],&p->tilesets[i],eori);
2495 if ( t == p->tilesets[i].tile )
2496 w = pd->esets[ecount].tiles[3];
2497 qtiles += 16; /* four tiles */
2498 ecount++;
2499 break;
2500 case Pung:
2501 if ( robbingkong && t == p->tilesets[i].tile) {
2502 TileSet ts = p->tilesets[i];
2503 ts.type = Kong;
2504 tilesetbox_set(&pd->esets[ecount],&ts,eori);
2505 w = pd->esets[ecount].tiles[3];
2506 qtiles += 16; /* four tiles */
2507 } else {
2508 tilesetbox_set(&pd->esets[ecount],&p->tilesets[i],eori);
2509 if ( t == p->tilesets[i].tile )
2510 w = pd->esets[ecount].tiles[2];
2511 qtiles += 12;
2512 }
2513 ecount++;
2514 break;
2515 case Chow:
2516 tilesetbox_set(&pd->esets[ecount],&p->tilesets[i],eori);
2517 if ( t >= p->tilesets[i].tile
2518 && t <= p->tilesets[i].tile+2 )
2519 w = pd->esets[ecount].tiles[t-p->tilesets[i].tile];
2520 qtiles += 12;
2521 ecount++;
2522 break;
2523 case Pair:
2524 tilesetbox_set(&pd->esets[ecount],&p->tilesets[i],eori);
2525 if ( t == p->tilesets[i].tile )
2526 w = pd->esets[ecount].tiles[1];
2527 qtiles += 9; /* for the two tiles and space */
2528 ecount++;
2529 break;
2530 case Empty:
2531 ;
2532 }
2533 }
2534 for ( ; ccount < MAX_TILESETS; )
2535 tilesetbox_set(&pd->csets[ccount++],&emptyset,eori);
2536 for ( ; ecount < MAX_TILESETS; )
2537 tilesetbox_set(&pd->esets[ecount++],&emptyset,eori);
2538 /* if we are the dealer, the tongbox takes up
2539 about 1.5 tiles */
2540 if ( p && p->wind == EastWind ) {
2541 gtk_pixmap_set(GTK_PIXMAP(pd->tongbox),tongpixmaps[cori][the_game->round-1],
2542 tongmask);
2543 gtk_widget_show(pd->tongbox);
2544 qtiles += 6;
2545 } else {
2546 gtk_widget_hide(pd->tongbox);
2547 }
2548
2549 /* for the special tiles, we put as many as
2550 possible in the exposed row (specs),
2551 and then overflow into the concealed row */
2552 qtiles = (qtiles+3)/4; /* turn quarter tiles into tiles */
2553 scount = excount = 0;
2554 for ( i = 0; p && i < p->num_specials
2555 && i <= pdispwidth - qtiles ; i++ ) {
2556 button_set_tile(pd->spec[i],p->specials[i],eori);
2557 gtk_widget_show(pd->spec[i]);
2558 if ( t == p->specials[i] ) w = pd->spec[i];
2559 scount++;
2560 }
2561 for ( ; p && i < p->num_specials ; i++ ) {
2562 button_set_tile(pd->extras[i-scount],p->specials[i],eori);
2563 gtk_widget_show(pd->extras[i-scount]);
2564 if ( t == p->specials[i] ) w = pd->extras[i-scount];
2565 excount++;
2566 }
2567 for ( ; scount < 8; scount ++ )
2568 gtk_widget_hide(pd->spec[scount]);
2569 for ( ; excount < 8; excount ++ )
2570 gtk_widget_hide(pd->extras[excount]);
2571
2572 return w;
2573 }
2574
2575 /* given a playerdisp, update the hand.
2576 The second argument gives the message prompting this.
2577 Animation is handled entirely in here.
2578 */
2579
2580
playerdisp_update(PlayerDisp * pd,CMsgUnion * m)2581 static void playerdisp_update(PlayerDisp *pd, CMsgUnion *m) {
2582 Tile t; int tindex;
2583 static AnimInfo srcs[4], dests[4];
2584 TileSet emptyset;
2585 int numanims = 0;
2586 static GtkWidget *robbedfromkong; /* yech */
2587 PlayerP p = pd->player;
2588 int deftile = -1; /* default tile to be discarded */
2589 int cori, eori; /* orientations of concealed and exposed tiles */
2590 int updated = 0; /* flag to say we've done our stuff */
2591
2592 cori = pd->orientation;
2593 eori = flipori(cori);
2594 emptyset.type = Empty;
2595 if ( p && pflag(p,HandDeclared) ) cori = eori;
2596
2597 if ( m == NULL ) {
2598 /* just update */
2599 playerdisp_update_concealed(pd,HiddenTile);
2600 playerdisp_update_exposed(pd,HiddenTile,0);
2601 return;
2602 }
2603
2604 if ( m->type == CMsgNewHand && p) {
2605 playerdisp_clear_discards(pd);
2606 playerdisp_update_concealed(pd,HiddenTile);
2607 playerdisp_update_exposed(pd,HiddenTile,0);
2608 return;
2609 }
2610
2611 if ( m->type == CMsgPlayerDraws || m->type == CMsgPlayerDrawsLoose) {
2612 updated = 1;
2613 t = (m->type == CMsgPlayerDraws) ? m->playerdraws.tile :
2614 m->playerdrawsloose.tile;
2615 tindex = playerdisp_update_concealed(pd,t);
2616 assert(tindex >= 0);
2617 /* do we want to select a tile? Usually, but in declaring
2618 specials state, we want to select a special if there is one,
2619 and otherwise not */
2620 if ( p == our_player ) {
2621 if ( the_game->state == DeclaringSpecials ) {
2622 int i;
2623 for (i=0; i<p->num_concealed && !is_special(p->concealed[i]);i++);
2624 if ( i < p->num_concealed ) {
2625 deftile = i;
2626 conc_callback(pd->conc[deftile],(gpointer)(intptr_t)(deftile | 128));
2627 } else {
2628 conc_callback(NULL,NULL);
2629 }
2630 } else {
2631 deftile = tindex;
2632 conc_callback(pd->conc[deftile],(gpointer)(intptr_t)(deftile | 128));
2633 }
2634 }
2635 dests[numanims].target = pd->conc[tindex];
2636 dests[numanims].t = t;
2637 get_relative_posn(boardframe,dests[numanims].target,&dests[numanims].x,&dests[numanims].y);
2638 /* that was the destination, now the source */
2639 if ( showwall ) {
2640 if ( m->type == CMsgPlayerDraws ) {
2641 int i;
2642 /* wall has already been updated, so it's -1 */
2643 i = wall_game_to_board(the_game->wall.live_used-1);
2644 srcs[numanims].target = NULL;
2645 srcs[numanims].t = m->playerdraws.tile; /* we may as well see our tile now */
2646 srcs[numanims].ori = wall_ori(i);
2647 get_relative_posn(boardframe,wall[i],&srcs[numanims].x,&srcs[numanims].y);
2648 gtk_widget_destroy(wall[i]);
2649 wall[i] = NULL;
2650 } else {
2651 /* draws loose */
2652 srcs[numanims] = *adjust_wall_loose(the_game->wall.dead_end);
2653 /* in fact, we may as well see the tile if we're drawing */
2654 srcs[numanims].t = t;
2655 }
2656 } else {
2657 /* if we're animating, we'll just have the tile spring up from
2658 nowhere! */
2659 srcs[numanims].target = NULL;
2660 srcs[numanims].t = t; /* we may as well see our tile now */
2661 srcs[numanims].ori = cori;
2662 /* these should be corrected for orientation */
2663 srcs[numanims].x = boardframe->allocation.width/2 - tile_width/2;
2664 srcs[numanims].y = boardframe->allocation.height/2 - tile_height/2;
2665 }
2666 if ( p == our_player && alert_mahjong
2667 && player_can_mah_jong(our_player,HiddenTile,
2668 game_get_option_value(the_game,GOSevenPairs,NULL).optbool) ) {
2669 playerdisp_popup_claim_window(&pdisps[0],"Mah-Jong possible!",0);
2670 }
2671 numanims++;
2672 } /* end of Draws and DrawsLoose */
2673 if ( m->type == CMsgPlayerDiscards ) {
2674 updated = 1;
2675 /* update the concealed tiles */
2676 playerdisp_update_concealed(pd,HiddenTile);
2677 /* the source for animation is the previous rightmost tile
2678 for other players, or the selected tile, for us.
2679 Note that selected button may not be set, if we're
2680 replaying history. In that case, we won't bother to
2681 set the animation position, in the knowledge that
2682 it won't actually be animated.
2683 This is far too convoluted.
2684 */
2685 /* FIXME: actually the selected button could be wrong, as the user
2686 might have moved it between sending the discard request and
2687 getting this controller message */
2688 tindex = (p == our_player) ? selected_button : p->num_concealed;
2689 srcs[numanims].t = m->playerdiscards.tile;
2690 srcs[numanims].ori = eori;
2691 if ( tindex >= 0 ) {
2692 get_relative_posn(boardframe,pd->conc[tindex],&srcs[numanims].x,
2693 &srcs[numanims].y);
2694 }
2695 /* now display the new discard */
2696 dests[numanims] = *playerdisp_discard(pd,m->playerdiscards.tile);
2697 discard_history.hist[discard_history.count].pd = pd;
2698 discard_history.hist[discard_history.count].t = m->playerdiscards.tile;
2699 discard_history.count++;
2700 /* if we're not animating, highlight the tile */
2701 if ( ! (animate && the_game->active) ) {
2702 #ifdef GTK2
2703 gtk_widget_modify_bg(dests[numanims].target,GTK_STATE_NORMAL,&highlightcolor);
2704 #else
2705 gtk_widget_set_style(dests[numanims].target,highlightstyle);
2706 #endif
2707
2708 highlittile = dests[numanims].target;
2709 }
2710 /* if we're discarding, clear the selected tile */
2711 if ( p == our_player ) conc_callback(NULL,NULL);
2712 numanims++;
2713 } /* end of CMsgPlayerDiscards */
2714 if ( m->type == CMsgPlayerDeclaresSpecial ) {
2715 updated = 1;
2716 if ( m->playerdeclaresspecial.tile != HiddenTile ) {
2717 GtkWidget *w = NULL;
2718 /* the source tile in this case is either the tile
2719 after the rightmost concealed tile (for other players)
2720 or the selected tile (for us) */
2721 /* FIXME: again, the selected tile is not necessarily right */
2722 if ( p == our_player ) {
2723 if ( selected_button >= 0 ) w = pd->conc[selected_button];
2724 /* else we're probably replaying history */
2725 } else {
2726 w = pd->conc[p->num_concealed];
2727 }
2728 if ( w ) get_relative_posn(boardframe,w,&srcs[numanims].x,&srcs[numanims].y);
2729 srcs[numanims].t = m->playerdeclaresspecial.tile;
2730 srcs[numanims].ori = cori;
2731 /* now update the special tiles, noting the destination */
2732 w = playerdisp_update_exposed(pd,m->playerdeclaresspecial.tile,0);
2733 assert(w);
2734 /* this forces a resize calculation so that the special tiles
2735 are in place */
2736 gtk_widget_size_request(pd->widget,NULL);
2737 gtk_widget_size_allocate(pd->widget,&pd->widget->allocation);
2738 dests[numanims].target = w;
2739 dests[numanims].ori = eori;
2740 get_relative_posn(boardframe,w,&dests[numanims].x,&dests[numanims].y);
2741 numanims++; /* that's it */
2742 } else {
2743 /* blank tile. nothing to do except select a tile if it's now
2744 our turn to declare */
2745 if ( the_game->state == DeclaringSpecials
2746 && the_game->player == our_seat ) {
2747 if ( is_special(our_player->concealed[our_player->num_concealed-1]) ) {
2748 deftile = our_player->num_concealed-1;
2749 conc_callback(pdisps[0].conc[deftile],(gpointer)(intptr_t)(deftile | 128));
2750 } else {
2751 conc_callback(NULL,NULL);
2752 }
2753 }
2754 /* update the concealed tiles of the *next* player, because
2755 it's their turn - this gets highlighting correct */
2756 playerdisp_update_concealed(&pdisps[game_to_board(the_game->player)],HiddenTile);
2757 }
2758 } /* end of CMsgPlayerDeclaresSpecial */
2759 if ( m->type == CMsgPlayerClaimsPung
2760 || m->type == CMsgPlayerClaimsKong
2761 || m->type == CMsgPlayerClaimsChow
2762 || m->type == CMsgPlayerClaimsMahJong
2763 || m->type == CMsgPlayerMahJongs
2764 || (m->type == CMsgPlayerDiscards
2765 ) ) {
2766 char *lab = NULL;
2767 switch ( m->type ) {
2768 case CMsgPlayerClaimsPung: lab = "Pung!"; break;
2769 case CMsgPlayerClaimsKong: lab = "Kong!"; break;
2770 case CMsgPlayerClaimsChow: lab = "Chow!"; break;
2771 case CMsgPlayerMahJongs:
2772 case CMsgPlayerClaimsMahJong: lab = "Mah Jong!"; break;
2773 case CMsgPlayerDiscards:
2774 lab = m->playerdiscards.calling ? "Calling!" : NULL; break;
2775 default: assert(0);
2776 }
2777 updated = 1;
2778 if ( lab ) { playerdisp_popup_claim_window(pd,lab,0); }
2779 if ( thinking_claim && server_pversion < 1111 ) {
2780 static int warned = 0;
2781 if ( ! warned ) {
2782 warned = 1;
2783 warn("`Thinking' cannot be shown with this server.");
2784 }
2785 }
2786 if ( m->type == CMsgPlayerDiscards
2787 && thinking_claim
2788 /* before 1111, the server did not send doesntclaims to
2789 all players */
2790 && server_pversion >= 1111
2791 ) {
2792 // put up thinking labels for the other players
2793 for (int i=0; i < 4;i++) {
2794 PlayerDisp *pd = &pdisps[i];
2795 if ( pd->player->id == our_id ) continue;
2796 if ( m->playerdiscards.id == pd->player->id ) continue;
2797 playerdisp_popup_claim_window(pd,"...",1);
2798 }
2799 }
2800 if ( m->type == CMsgPlayerDiscards
2801 && m->playerdiscards.id != our_id && alert_mahjong
2802 && player_can_mah_jong(our_player,m->playerdiscards.tile,
2803 game_get_option_value(the_game,GOSevenPairs,NULL).optbool) ) {
2804 playerdisp_popup_claim_window(&pdisps[0],"Mah-Jong possible!",0);
2805 }
2806 } /* end of the claims */
2807 /* On receiving a successful claim, withdraw the last discard.
2808 And if mahjonging, select the first tile if this is us */
2809 if ( (m->type == CMsgPlayerChows
2810 && ((CMsgPlayerChowsMsg *)m)->cpos != AnyPos)
2811 || m->type == CMsgPlayerPungs
2812 || m->type == CMsgPlayerKongs
2813 || m->type == CMsgPlayerPairs
2814 || m->type == CMsgPlayerSpecialSet ) {
2815 GtkWidget *w;
2816
2817 updated = 1;
2818 /* withdraw discard, or use the saved widget if a kong was robbed */
2819 if ( the_game->whence == FromRobbedKong ) {
2820 srcs[numanims].t = the_game->tile;
2821 get_relative_posn(boardframe,robbedfromkong,
2822 &srcs[numanims].x,&srcs[numanims].y);
2823 playerdisp_update_exposed(&pdisps[game_to_board(the_game->supplier)],HiddenTile,0);
2824 } else {
2825 srcs[numanims] = *playerdisp_discard(&pdisps[game_to_board(the_game->supplier)],HiddenTile);
2826 discard_history.hist[discard_history.count].pd = &pdisps[game_to_board(the_game->supplier)];
2827 discard_history.hist[discard_history.count].t = HiddenTile;
2828 discard_history.count++;
2829 }
2830 /* update exposed tiles, and get the destination widget */
2831 w = playerdisp_update_exposed(pd,the_game->tile,0);
2832 /* In the case of a special set, the destination tile will be
2833 in the concealed tiles */
2834 /* update the concealed tiles. At present we don't animate the
2835 movement of tiles from hand to exposed sets, so just update */
2836 if ( m->type == CMsgPlayerSpecialSet ) {
2837 w =
2838 pd->conc[playerdisp_update_concealed(pd,the_game->tile)];
2839 } else {
2840 playerdisp_update_concealed(pd,HiddenTile);
2841 }
2842 assert(w);
2843 /* this should force a resize calculation so that
2844 the declared tiles are in place. It seems to work. */
2845 gtk_widget_size_request(pd->widget,NULL);
2846 gtk_widget_size_allocate(pd->widget,&pd->widget->allocation);
2847 dests[numanims].target = w;
2848 srcs[numanims].ori = dests[numanims].ori = eori;
2849 get_relative_posn(boardframe,w,&dests[numanims].x,&dests[numanims].y);
2850 numanims++; /* that's it */
2851
2852 if ( the_game->state == MahJonging
2853 && the_game->player == our_seat
2854 && p == our_player
2855 && p->num_concealed > 0 )
2856 conc_callback(pd->conc[0],(gpointer)(intptr_t)(0 | 128));
2857 } /* end of the successful claim cases */
2858 else if ( m->type == CMsgPlayerRobsKong ) {
2859 GtkWidget *t;
2860 /* This should be called on the ROBBED player, so we
2861 can stash away the tile that will be robbed */
2862 t = playerdisp_update_exposed(pd,m->playerrobskong.tile,1);
2863 /* if we found the tile, then this is it. Otherwise, we were
2864 looking at somebody else! */
2865 if ( t ) robbedfromkong = t;
2866 updated = 1;
2867 } /* end of robbing kong */
2868 /* if not yet handled, just update the tiles */
2869 if ( ! updated ) {
2870 playerdisp_update_concealed(pd,HiddenTile);
2871 playerdisp_update_exposed(pd,HiddenTile,0);
2872 }
2873
2874 /* now kick off the animations (or not) */
2875 while ( numanims-- > 0 ) {
2876 if ( animate
2877 && the_game->active
2878 && the_game->state != Dealing ) {
2879 gtk_widget_unmap(dests[numanims].target);
2880 start_animation(&srcs[numanims],&dests[numanims]);
2881 }
2882 }
2883 }
2884
2885 /* utility function: given a gdkpixmap, create rotated versions
2886 and put them in the last three arguments.
2887 */
rotate_pixmap(GdkPixmap * east,GdkPixmap ** south,GdkPixmap ** west,GdkPixmap ** north)2888 static void rotate_pixmap(GdkPixmap *east,
2889 GdkPixmap **south,
2890 GdkPixmap **west,
2891 GdkPixmap **north) {
2892 static GdkVisual *v = NULL;
2893 int w=-1, h=-1;
2894 int x, y;
2895 GdkImage *im;
2896 GdkPixmap *p1;
2897 GdkImage *im1;
2898 GdkGC *gc;
2899
2900 if ( v == NULL ) {
2901 v = gdk_window_get_visual(topwindow->window);
2902 }
2903
2904 gdk_window_get_size(east,&w,&h);
2905 /* remember to destroy these */
2906 im = gdk_image_get(east,0,0,w,h);
2907 gc = gdk_gc_new(east);
2908
2909 /* do the south */
2910 im1 = gdk_image_new(GDK_IMAGE_NORMAL,v,h,w);
2911 for ( x=0; x < w; x++ )
2912 for ( y=0; y < h; y++ )
2913 gdk_image_put_pixel(im1,y,w-1-x,gdk_image_get_pixel(im,x,y));
2914 p1 = gdk_pixmap_new(NULL,h,w,im->depth);
2915 gdk_draw_image(p1,gc,im1,0,0,0,0,h,w);
2916 *south = p1;
2917 /* do the north */
2918 for ( x=0; x < w; x++ )
2919 for ( y=0; y < h; y++ )
2920 gdk_image_put_pixel(im1,h-1-y,x,gdk_image_get_pixel(im,x,y));
2921 p1 = gdk_pixmap_new(NULL,h,w,im->depth);
2922 gdk_draw_image(p1,gc,im1,0,0,0,0,h,w);
2923 *north = p1;
2924
2925 /* and the west */
2926 gdk_image_destroy(im1);
2927 im1 = gdk_image_new(GDK_IMAGE_NORMAL,v,w,h);
2928 for ( x=0; x < w; x++ )
2929 for ( y=0; y < h; y++ )
2930 gdk_image_put_pixel(im1,w-1-x,h-1-y,gdk_image_get_pixel(im,x,y));
2931 p1 = gdk_pixmap_new(NULL,w,h,im->depth);
2932 gdk_draw_image(p1,gc,im1,0,0,0,0,w,h);
2933 *west = p1;
2934
2935 /* clean up */
2936 gdk_image_destroy(im1);
2937 gdk_image_destroy(im);
2938 gdk_gc_destroy(gc);
2939 }
2940
2941 /* new animation routines. The previous way was all very object-oriented,
2942 but not very good: having separate timeouts for every animated tile
2943 tends to slow things done. So we will instead maintain a global list
2944 of animations in progress (of which there will be at most five!),
2945 and have a single timeout that deals with all of them */
2946 /* struct for recording information about an animation in progress */
2947 struct AnimProg {
2948 GtkWidget *floater; /* the moving window. Set to NULL if this record
2949 is not in use */
2950 gint x1,y1,x2,y2; /* start and end posns relative to either boardframe
2951 or root, depending on animate_with_popups */
2952 gint steps, n; /* total number of steps, number completed */
2953 GtkWidget *target; /* target widget */
2954 };
2955
2956 struct AnimProg animlist[5];
2957 int animator_running = 0;
2958
2959 /* timeout function that moves all running animations by one step */
discard_animator(gpointer data UNUSED)2960 static gint discard_animator(gpointer data UNUSED) {
2961 struct AnimProg *animp;
2962 int i; int numinprogress = 0;
2963
2964 for ( i = 0 ; i < 5 ; i++ ) {
2965 animp = &animlist[i];
2966 if ( ! animp->floater ) continue;
2967 if ( animp->n == 0 ) {
2968 gtk_widget_destroy(animp->floater);
2969 animp->floater = NULL;
2970 gtk_widget_show(animp->target);
2971 gtk_widget_map(animp->target);
2972 } else {
2973 numinprogress++;
2974 animp->n--;
2975 /* Here we move the widget, *and* we move the window.
2976 Why?
2977 If we move the widget, gtk doesn't actually move its window,
2978 as we've arranged for vlazy_fixed_move not to call
2979 queue_resize, so relayout doesn't get done. Thus we have
2980 to move the window.
2981 However, if we don't move the widget, then GTK uses its
2982 original position for calculating event deliveries, resulting
2983 in total confusion and leave/enter loop if the pointer is
2984 in the button.
2985 But if we're using popup windows, we only move the window.
2986 */
2987 if ( ! animate_with_popups ) {
2988 vlazy_fixed_move(VLAZY_FIXED(boardfixed),animp->floater,
2989 (animp->n*animp->x1
2990 + (animp->steps-animp->n)*animp->x2)
2991 /animp->steps,
2992 (animp->n*animp->y1
2993 + (animp->steps-animp->n)*animp->y2)
2994 /animp->steps);
2995 }
2996 gdk_window_move(animp->floater->window,
2997 (animp->n*animp->x1
2998 + (animp->steps-animp->n)*animp->x2)
2999 /animp->steps,
3000 (animp->n*animp->y1
3001 + (animp->steps-animp->n)*animp->y2)
3002 /animp->steps);
3003 }
3004 }
3005 if ( numinprogress > 0 ) { return TRUE ; }
3006 else { animator_running = 0; return FALSE; }
3007 }
3008
3009 /* kick off an animation from the given source and target info */
start_animation(AnimInfo * source,AnimInfo * target)3010 static void start_animation(AnimInfo *source,AnimInfo *target) {
3011 GtkWidget *floater,*b;
3012 int i;
3013 struct AnimProg *animp;
3014 GtkWidget *p;
3015
3016 /* find a free slot in the animation list */
3017 for ( i = 0 ; animlist[i].floater && i < 5 ; i++ ) ;
3018 if ( i == 5 ) {
3019 warn("no room in animation list; not animating");
3020 gtk_widget_map(target->target);
3021 return;
3022 }
3023 animp = &animlist[i];
3024
3025 /* create the floating button */
3026 b = gtk_button_new();
3027 gtk_widget_set_name(b,"tile");
3028 gtk_widget_show(b);
3029 GTK_WIDGET_UNSET_FLAGS(b,GTK_CAN_FOCUS);
3030 p = gtk_pixmap_new(tilepixmaps[0][HiddenTile],NULL);
3031 gtk_widget_show(p);
3032 gtk_container_add(GTK_CONTAINER(b),p);
3033 button_set_tile_without_tiletip(b,source->t,source->ori);
3034 if ( animate_with_popups ) {
3035 floater = gtk_window_new(GTK_WINDOW_POPUP);
3036 gtk_container_add(GTK_CONTAINER(floater),b);
3037 } else {
3038 #ifdef GTK2
3039 /* in GTK2, buttons don't have windows */
3040 floater = gtk_event_box_new();
3041 gtk_container_add(GTK_CONTAINER(floater),b);
3042 #else
3043 floater = b;
3044 #endif
3045 }
3046 animp->x1 = source->x;
3047 animp->x2 = target->x;
3048 animp->y1 = source->y;
3049 animp->y2 = target->y;
3050 if ( animate_with_popups ) {
3051 int x,y;
3052 gdk_window_get_deskrelative_origin(boardframe->window,&x,&y);
3053 animp->x1 += x;
3054 animp->x2 += x;
3055 animp->y1 += y;
3056 animp->y2 += y;
3057 }
3058 /* how many steps? the frame interval is 20ms.
3059 We want it to move across the screen in about a second max.
3060 So say 1600 pixels in 1 second, i.e. 50 frames.
3061 However, we don't want it to move instantaneously over short distances,
3062 so let's say a minimum of 10 frames. Hence
3063 numframes = 10 + 40 * dist/1600 */
3064 animp->steps = 10 + floor(hypot(source->x-target->x,source->y-target->y))*40/1600;
3065 animp->n = animp->steps;
3066 animp->target = target->target;
3067 if ( animate_with_popups ) {
3068 gtk_widget_set_uposition(floater,animp->x1,animp->y1);
3069 } else {
3070 vlazy_fixed_put(VLAZY_FIXED(boardfixed),floater,animp->x1,animp->y1);
3071 }
3072 gtk_widget_show(floater);
3073 animp->floater = floater;
3074 if ( ! animator_running ) {
3075 animator_running = 1;
3076 gtk_timeout_add(/* 1000 */ 20 ,discard_animator,(gpointer)NULL);
3077 }
3078 }
3079
3080 /* local variable used in next two. Tells playerdisp_discard
3081 whether to compute some information */
3082 static int playerdisp_discard_recompute = 1;
3083
3084 /* playerdisp_clear_discards: clear the discard area for the player */
playerdisp_clear_discards(PlayerDisp * pd)3085 static void playerdisp_clear_discards(PlayerDisp *pd) {
3086 int i;
3087
3088 /* destroy ALL widgets, since there might be widgets after
3089 the current last discard (and will be, if the last mah jong
3090 was from the wall */
3091 for (i=0; i < 32; i++) {
3092 if ( pd->discards[i] ) {
3093 gtk_widget_destroy(pd->discards[i]);
3094 pd->discards[i] = NULL;
3095 }
3096 }
3097 highlittile = NULL;
3098 pd->num_discards = 0;
3099 pd->x = pd->y = pd->row = pd->plane = 0;
3100 for (i=0;i<5;i++) { pd->xmin[i] = 10000; pd->xmax[i] = 0; }
3101 playerdisp_discard_recompute = 1;
3102 }
3103
3104 /* Given a player disp and a tile, display the tile as
3105 the player's next discard. If HiddenTile, then withdraw
3106 the last discard. Also, if pd is NULL: assume pd is the
3107 last player on which we were called.
3108 Buttons are created as required.
3109 Tiles are packed in rows, putting a 1/4 tile spacing between them
3110 horizontally and vertically.
3111 In the spirit of Mah Jong, each player grabs the next available space,
3112 working away from it; a little cleverness detects clashes.
3113 Returns an animinfo which, in the normal case, is the position
3114 of the new discard.
3115 In the withdrawing case, it's the discard being withdrawn.
3116 */
3117
playerdisp_discard(PlayerDisp * pd,Tile t)3118 static AnimInfo *playerdisp_discard(PlayerDisp *pd,Tile t) {
3119 static PlayerDisp *last_pd = NULL;
3120 static gint16 dw,dh; /* width and height of discard area */
3121 static gint16 bw,bh; /* width and height of tile button in ori 0 */
3122 static gint16 spacing;
3123 gint16 w,h; /* width and height of discard area viewed by this player */
3124 int row,plane; /* which row and plane are we in */
3125 int i;
3126 gint16 x,y,xx,yy;
3127 PlayerDisp *left,*right; /* our left and right neighbours */
3128 GtkWidget *b;
3129 int ori, eori;
3130 static AnimInfo anim;
3131
3132 if ( pd == NULL ) pd = last_pd;
3133 last_pd = pd;
3134 ori = pd->orientation;
3135 eori = flipori(ori);
3136
3137 if ( highlittile ) {
3138 #ifdef GTK2
3139 gtk_widget_modify_bg(highlittile,GTK_STATE_NORMAL,NULL);
3140 #else
3141 gtk_widget_restore_default_style(highlittile);
3142 #endif
3143 highlittile = NULL;
3144 }
3145
3146 if ( t == HiddenTile ) {
3147 if ( pd == NULL ) {
3148 warn("Internal error: playerdisp_discard called to clear null discard");
3149 return NULL;
3150 }
3151 b = pd->discards[--pd->num_discards];
3152 if ( b == NULL ) {
3153 warn("Internal error: playerdisp_discard clearing nonexistent button");
3154 return NULL;
3155 }
3156 anim.t = (int)(intptr_t) gtk_object_get_user_data(GTK_OBJECT(b));
3157 anim.target = b;
3158 anim.ori = eori;
3159 get_relative_posn(boardframe,b,&anim.x,&anim.y);
3160 gtk_widget_hide(b);
3161 return &anim;
3162 }
3163
3164 ori = pd->orientation;
3165 eori = flipori(ori);
3166 b = pd->discards[pd->num_discards];
3167 left = &pdisps[(ori+3)%4];
3168 right = &pdisps[(ori+1)%4];
3169
3170 if ( b == NULL ) {
3171 GtkWidget *p;
3172 b = pd->discards[pd->num_discards] = gtk_button_new();
3173 gtk_widget_set_name(b,"tile");
3174 GTK_WIDGET_UNSET_FLAGS(b,GTK_CAN_FOCUS);
3175 p = gtk_pixmap_new(tilepixmaps[0][HiddenTile],NULL);
3176 gtk_widget_show(p);
3177 gtk_container_add(GTK_CONTAINER(b),p);
3178 /* if this is the first time we've ever been called, we need
3179 to compute some information */
3180 if ( playerdisp_discard_recompute ) {
3181 playerdisp_discard_recompute = 0;
3182 /* This discard area alloc was computed at a known safe time
3183 in the main routine */
3184 dw = discard_area_alloc.width
3185 - 2 * GTK_CONTAINER(discard_area)->border_width;
3186 dh = discard_area_alloc.height
3187 - 2 * GTK_CONTAINER(discard_area)->border_width;
3188 bw = tile_width;
3189 bh = tile_height;
3190 spacing = tile_spacing;
3191 /* if we're showing the wall, the effective dimensions are
3192 is reduced by 2*(tileheight+3*tilespacing) --
3193 each wall occupies tile_height+2*tile_spacing, and we
3194 also want some space */
3195 if ( showwall ) {
3196 dw -= 2*(tile_height+3*tile_spacing);
3197 dh -= 2*(tile_height+3*tile_spacing);
3198 /* moreover, the wall might be smaller than the max wall
3199 we used to set the discard area */
3200 dw -= ((MAX_WALL_SIZE-WALL_SIZE)/8)*tile_width;
3201 dh -= ((MAX_WALL_SIZE-WALL_SIZE)/8)*tile_width;
3202 }
3203 }
3204
3205 /* Now we need to work out where to put this button.
3206 To make life easier, we will always look at it as if
3207 we were at the bottom, and then rotate the answer later */
3208 /* we can't assume the discard area is square, so swap w and h
3209 if we're working for a side player */
3210 if ( ori == 0 || ori == 2 ) {
3211 w = dw; h = dh;
3212 } else {
3213 w = dh; h = dw;
3214 }
3215 /* get the positions from last time */
3216 x = pd->x; y = pd->y; row = pd->row; plane = pd->plane;
3217 /* working in the X coordinate system is too painful.
3218 We work in a normal Cartesian system and convert
3219 only at the last moment */
3220 /* x,y is the pos of the bottom left corner */
3221 /* repeat until success */
3222 while ( 1 ) {
3223 /* Does the current position intrude into the opposite area?
3224 If so, we're stuffed, and need to think what to do */
3225 /* also if we've run out of rows */
3226 if ( row >= 5 || y+bh > h/2 ) {
3227 /* start stacking tiles */
3228 plane++;
3229 row = 0;
3230 x = 0 + plane*(bw+spacing)/2; /* two planes is enough, surely! */
3231 y = 0 + plane*(bh+spacing)/2;
3232 continue;
3233 }
3234 /* Does the current position intrude into the left hand neighbour?
3235 The left hand neighbour has stored in its xmax array
3236 the rightmost points it's used in each row. So for each of
3237 its rows, we see if either our top left or top right intrude
3238 on it, and if so we move along */
3239 for (i=0; i<5; i++) {
3240 if ( (
3241 /* our top left is in the part occupied by the tile */
3242 (x >= i*(bh+spacing) && x < i*(bh+spacing)+bh)
3243 /* ditto for top right */
3244 || (x+bw >= i*(bh+spacing) && x+bw < i*(bh+spacing)+bh) )
3245 /* and our top intrudes */
3246 && y+bh > h - left->xmax[i] )
3247 x = i*(bh+spacing)+bh+spacing/2; /* half space all that's needed, I think */
3248 }
3249
3250 /* Now see if the current position interferes with the right
3251 neighbour similarly
3252 */
3253 for (i=0; i<5; i++) {
3254 if ( (
3255 /* our top left is in the column */
3256 (x >= w-(i*(bh+spacing)+bh) && x < w - i*(bh+spacing))
3257 /* ditto for top right */
3258 || (x+bw >= w-(i*(bh+spacing)+bh) && x+bw < w - i*(bh+spacing)))
3259 /* and our top intrudes */
3260 && y+bh > right->xmin[i] ) {
3261 /* we have to move up to the next row */
3262 row++;
3263 x = 0;
3264 y += (bh+spacing);
3265 i=-1; /* signal */
3266 break;
3267 }
3268 }
3269 /* and restart the loop if we moved */
3270 if ( i < 0 ) continue;
3271
3272 /* Phew, we've found it, if we get here */
3273 /* store our information for next time */
3274 pd->row = row;
3275 pd->plane = plane;
3276 pd->x = x+bw+spacing;
3277 pd->y = y;
3278
3279 if ( x < pd->xmin[row] ) pd->xmin[row] = x;
3280 if ( x+bw > pd->xmax[row] ) pd->xmax[row] = x+bw;
3281 break;
3282 }
3283
3284 /* Now we have to rotate for the appropriate player.
3285 This means giving a point for the top left corner.
3286 And switching y round */
3287 xx = x; yy = y;
3288 switch ( ori ) {
3289 case 0: x = xx; y = dh-yy-bh; break;
3290 case 1: y = dh - xx - bw; x = dw - yy - bh; break;
3291 case 2: x = dw - xx - bw; y = yy; break;
3292 case 3: y = xx; x = yy; break;
3293 }
3294 /* and if we are showing the wall, we have to shift everything
3295 down and right by the wall space */
3296 if ( showwall ) {
3297 x += (tile_height+3*tile_spacing);
3298 y += (tile_height+3*tile_spacing);
3299 /* and adjust for wall size */
3300 x += ((MAX_WALL_SIZE-WALL_SIZE)/8)*tile_width/2;
3301 y += ((MAX_WALL_SIZE-WALL_SIZE)/8)*tile_width/2;
3302 }
3303
3304 gtk_fixed_put(GTK_FIXED(discard_area),b,x,y);
3305 pd->dx[pd->num_discards] = x;
3306 pd->dy[pd->num_discards] = y;
3307 } else {
3308 x = pd->dx[pd->num_discards];
3309 y = pd->dy[pd->num_discards];
3310 }
3311
3312 /* now we have a button created and in place: set it */
3313 button_set_tile(b,t,eori);
3314
3315 /* now we need to compute the animation info to return */
3316 /* position is that of the button we've just created */
3317 /* unfortunately, if we've just created the button, it won't
3318 actually be in position (since it hasn't been realized),
3319 so we have to use our calculated position relative to the
3320 discard area */
3321 get_relative_posn(boardframe,discard_area,&anim.x,&anim.y);
3322 anim.x += x; anim.y += y;
3323 /* and don't forget the border of the discard area */
3324 anim.x += GTK_CONTAINER(discard_area)->border_width;
3325 anim.y += GTK_CONTAINER(discard_area)->border_width;
3326 anim.target = b;
3327 anim.t = t;
3328 anim.ori = eori;
3329 gtk_widget_show(b);
3330 pd->num_discards++;
3331 return &anim;
3332 }
3333
3334 /* open_connection: connect to a server and announce,
3335 using values from the open dialog box where available.
3336 If data is one, start local server and players.
3337 If data is 2, load game from file in opengamefiletext.
3338 If data is 3, reconnect using existing fd in third arg.
3339 If data is 4, open to host:port in fourth arg, where :NNNN means new port on same host. */
open_connection(GtkWidget * w UNUSED,gpointer data,int oldfd,char * newaddr)3340 void open_connection(GtkWidget *w UNUSED, gpointer data, int oldfd, char *newaddr) {
3341 char buf[256], buf2[256];
3342 int i;
3343 int mode = (int)(intptr_t)data;
3344 int toserver, fromserver;
3345 FILE *serverstream;
3346
3347 databuffer_clear(&server_history);
3348
3349 if ( mode < 3 || mode == 4 ) {
3350 if ( GTK_WIDGET_SENSITIVE(openfile) ) {
3351 sprintf(address,"%s",gtk_entry_get_text(GTK_ENTRY(openfiletext)));
3352 /* strip any initial spaces */
3353 while ( address[0] == ' ' ) {
3354 int jj;
3355 int ll = strlen(address);
3356 for (jj=0;jj<ll;jj++) address[jj] = address[jj+1];
3357 }
3358 /* if the field is blank, make up something */
3359 if ( address[0] == 0 ) {
3360 sprintf(address,"/tmp/mj-%d",getpid());
3361 }
3362 } else {
3363 /* out of kindness we'll strip any trailing spaces */
3364 char t[256];
3365 int i;
3366 t[255] = 0;
3367 if ( mode == 4 && ! redirected ) strcpy(origaddress,address);
3368 strmcpy(t,gtk_entry_get_text(GTK_ENTRY(openhosttext)),255);
3369 for ( i = strlen(t) - 1; isspace(t[i]); i-- ) t[i] = 0;
3370 if ( mode == 4 && newaddr[0] != ':' ) {
3371 sscanf(newaddr,"%255[^:]",t);
3372 }
3373 if ( mode == 4 ) { newaddr = index(newaddr,':')+1; }
3374 sprintf(address,"%s:%s",t,(mode == 4) ? newaddr : gtk_entry_get_text(GTK_ENTRY(openporttext)));
3375 }
3376
3377 if ( mode != 4 ) our_id = atoi(gtk_entry_get_text(GTK_ENTRY(openidtext)));
3378 strmcpy(name,gtk_entry_get_text(GTK_ENTRY(opennametext)),256);
3379 for ( i = 0 ; i <= 2; i++ ) {
3380 strmcpy(robot_names[i],gtk_entry_get_text(GTK_ENTRY(openplayernames[i])),128);
3381 strmcpy(robot_options[i],gtk_entry_get_text(GTK_ENTRY(openplayeroptions[i])),128);
3382 }
3383
3384 if ( mode == 4 ) { redirected = 1; }
3385 if ( mode < 3 ) { redirected = 0; }
3386
3387 /* save the values */
3388 read_or_update_rcfile(NULL,XmjrcNone,XmjrcOpen);
3389
3390 /* start server ? */
3391 if ( mode > 0 && mode < 4) {
3392 char cmd[1024];
3393 char ibuf[10];
3394
3395 strcpy(cmd,"mj-server --id-order-seats --server ");
3396 strmcat(cmd,address,sizeof(cmd)-strlen(cmd));
3397 if ( ! GTK_TOGGLE_BUTTON(openallowdisconnectbutton)->active ) {
3398 strmcat(cmd, " --exit-on-disconnect",sizeof(cmd)-strlen(cmd));
3399 }
3400 if ( GTK_TOGGLE_BUTTON(opensaveonexitbutton)->active ) {
3401 strmcat(cmd, " --save-on-exit",sizeof(cmd)-strlen(cmd));
3402 }
3403 if ( GTK_TOGGLE_BUTTON(openrandomseatsbutton)->active ) {
3404 strmcat(cmd, " --random-seats",sizeof(cmd)-strlen(cmd));
3405 }
3406 if ( mode == 1 ) {
3407 /* if resuming a game, timeout was set initially */
3408 gtk_spin_button_update(GTK_SPIN_BUTTON(opentimeoutspinbutton));
3409 strcat(cmd, " --timeout ");
3410 sprintf(ibuf,"%d",
3411 gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(opentimeoutspinbutton)));
3412 strmcat(cmd,ibuf,sizeof(cmd)-strlen(cmd));
3413 }
3414 if ( mode == 2 ) {
3415 const char *gfile; FILE *foo;
3416 /* if we're restarting a game, allow the robots to take over
3417 the previous players */
3418 strmcat(cmd, " --no-id-required",sizeof(cmd)-strlen(cmd));
3419 gfile = gtk_entry_get_text(GTK_ENTRY(opengamefiletext));
3420 /* check that we can actually read the file */
3421 if ( !gfile || !(foo = fopen(gfile,"r")) ) {
3422 char rdmsg[] = "Can't read game file: ";
3423 strcpy(buf,rdmsg);
3424 strmcat(buf,strerror(errno),sizeof(buf)-sizeof(rdmsg));
3425 error_dialog_popup(buf);
3426 return;
3427 }
3428 fclose(foo);
3429 strmcat(cmd, " --load-game ",sizeof(cmd)-strlen(cmd));
3430 strmcat(cmd, gfile,sizeof(cmd)-strlen(cmd));
3431 }
3432 if ( ! start_background_program_with_io(cmd,&toserver,&fromserver) ) {
3433 error_dialog_popup("Couldn't start server; unexpected failure");
3434 return;
3435 }
3436 close(toserver);
3437 serverstream = fdopen(fromserver,"r");
3438 fgets(buf2,255,serverstream);
3439 /* if the server managed to open its socket, the first
3440 thing it writes to stdout is
3441 OK: server-address */
3442 if ( strncmp(buf2,"OK",2) != 0 ) {
3443 error_dialog_popup("server failed to start");
3444 return;
3445 }
3446 }
3447 }
3448
3449 if ( mode == 3 ) {
3450 the_game = client_reinit(oldfd);
3451 } else {
3452 the_game = client_init(address);
3453 }
3454
3455 if ( the_game == NULL ) {
3456 sprintf(buf,"Couldn't connect to server:\n%s",strerror(errno));
3457 if ( data ) {
3458 strcat(buf,"\nPerhaps server didn't start -- check error messages");
3459 }
3460 error_dialog_popup(buf);
3461 return;
3462 }
3463 the_game->userdata = malloc(sizeof(GameExtras));
3464 if ( the_game->userdata == NULL ) {
3465 warn("Fatal error: couldn't malloc game extra data");
3466 exit(1);
3467 }
3468 our_player = the_game->players[0];
3469
3470 gtk_widget_set_sensitive(openmenuentry,0);
3471 gtk_widget_set_sensitive(newgamemenuentry,0);
3472 gtk_widget_set_sensitive(resumegamemenuentry,0);
3473 gtk_widget_set_sensitive(savemenuentry,1);
3474 gtk_widget_set_sensitive(saveasmenuentry,1);
3475 gtk_widget_set_sensitive(closemenuentry,1);
3476 close_saving_posn(open_dialog);
3477
3478 control_server_processing(1);
3479 if ( monitor ) {
3480 // do everything from client_connect except send packet. Yech.
3481 initialize_player(the_game->players[0]);
3482 set_player_id(the_game->players[0],our_id);
3483 set_player_name(the_game->players[0],name);
3484 } else {
3485 client_connect(the_game,our_id,name);
3486 }
3487
3488 if ( mode == 1 || mode == 2 ) {
3489 for ( i = 0 ; i < 3 ; i++ ) {
3490 if ( GTK_TOGGLE_BUTTON(openplayercheckboxes[i])->active ) {
3491 char cmd[1024];
3492 strcpy(cmd,"mj-player --server ");
3493 strmcat(cmd,address,sizeof(cmd)-strlen(cmd));
3494 sprintf(cmd+strlen(cmd)," --id %d ",i+2);
3495 strmcat(cmd," ",sizeof(cmd)-strlen(cmd));
3496 if ( robot_names[i][0] ) {
3497 strmcat(cmd," --name ",sizeof(cmd)-strlen(cmd));
3498 qstrmcat(cmd,robot_names[i],sizeof(cmd)-strlen(cmd));
3499 }
3500 if ( robot_options[i][0] ) {
3501 strmcat(cmd," ",sizeof(cmd)-strlen(cmd));
3502 /* options are not quoted, just added as is */
3503 strmcat(cmd,robot_options[i],sizeof(cmd)-strlen(cmd));
3504 }
3505 if ( ! start_background_program(cmd) ) {
3506 error_dialog_popup("Unexpected error starting player");
3507 return;
3508 }
3509 if ( i < 2 ) usleep(250*1000);
3510 }
3511 }
3512 }
3513 }
3514
3515 /* shut down the connection - unless arg is given, in which case
3516 do everything except actually closing the connection */
close_connection(int keepalive)3517 void close_connection(int keepalive) {
3518 int i;
3519 game_over = 0; /* cos there is now no game to be over */
3520 control_server_processing(0);
3521 free(the_game->userdata);
3522 if ( keepalive )
3523 client_close_keepconnection(the_game);
3524 else
3525 client_close(the_game);
3526 the_game = NULL;
3527 gtk_widget_set_sensitive(openmenuentry,1);
3528 gtk_widget_set_sensitive(newgamemenuentry,1);
3529 gtk_widget_set_sensitive(resumegamemenuentry,1);
3530 gtk_widget_set_sensitive(savemenuentry,0);
3531 gtk_widget_set_sensitive(saveasmenuentry,0);
3532 gtk_widget_set_sensitive(closemenuentry,0);
3533 /* clear display */
3534 for ( i=0; i < 4; i++ ) {
3535 pdisps[i].player = NULL;
3536 playerdisp_update(&pdisps[i],0);
3537 playerdisp_clear_discards(&pdisps[i]);
3538 }
3539 clear_wall();
3540 /* and pop down irrelevant dialogs that are up */
3541 gtk_widget_hide(chow_dialog);
3542 gtk_widget_hide(ds_dialog);
3543 gtk_widget_hide(discard_dialog->widget);
3544 gtk_widget_hide(continue_dialog);
3545 gtk_widget_hide(turn_dialog);
3546 gtk_widget_hide(scoring_dialog);
3547 /* zap the game option dialog */
3548 game_option_panel = build_or_refresh_option_panel(NULL,game_option_panel);
3549 gtk_widget_set_sensitive(gameoptionsmenuentry,0);
3550 }
3551
3552 /* convenience function: given a widget w and a descendant z,
3553 give the position of z relative to w.
3554 To make sense of this function, one needs to know the insane
3555 conventions of gtk, which of course are entirely undocumented.
3556 To wit, the allocation of a widget is measured relative to its
3557 window, i.e. the window of the nearest ancestor widget that has one.
3558 The origin viewed *externally* of a widget is the top left of its
3559 border; the origin viewed *internally* is the top of the non-border.
3560 Returns true on success, false on failure */
get_relative_posn_aux(GtkWidget * w,GtkWidget * z,int * x,int * y)3561 static int get_relative_posn_aux(GtkWidget *w, GtkWidget *z, int *x, int *y) {
3562 GtkWidget *target = z;
3563
3564 /* base case */
3565 if ( z == w ) {
3566 /* I don't think this should ever happen, unless we actually
3567 call the main function with z == w. Let's check */
3568 assert(0);
3569 return 1;
3570 }
3571 /* find the nearest ancestor of the target widget with a window */
3572 z = z->parent;
3573 while ( z && z != w && GTK_WIDGET_NO_WINDOW(z) ) z = z->parent;
3574 /* if z has a window, then the target position relative to
3575 z is its allocation */
3576 if ( ! z ) {
3577 /* something wrong */
3578 /* whoops. Original z wasn't a child of w */
3579 warn("get_relative_posn: z is not a descendant of w");
3580 return 0;
3581 } else if ( z == w ) {
3582 /* the position of the target is its allocation minus the
3583 allocation of w, if w is not a window; */
3584 *x += target->allocation.x;
3585 *y += target->allocation.y;
3586 if ( GTK_WIDGET_NO_WINDOW(w) ) {
3587 *x -= w->allocation.x;
3588 *y -= w->allocation.y;
3589 /* and minus the borderwidth of w */
3590 if ( GTK_IS_CONTAINER(w) ) {
3591 *x -= GTK_CONTAINER(w)->border_width;
3592 *y -= GTK_CONTAINER(w)->border_width;
3593 }
3594 }
3595 /* and that's it */
3596 return 1;
3597 } else if ( !GTK_WIDGET_NO_WINDOW(z) ) {
3598 *x += target->allocation.x;
3599 *y += target->allocation.y;
3600 }
3601 /* if we get here, z is an ancestor window, but a descendant of
3602 w. So we have to pass through it.
3603 Previously, I thought we had to add in z's borderwidth, but
3604 this appears not to be the case. */
3605 /* now recurse */
3606 return get_relative_posn_aux(w,z,x,y);
3607 }
3608
get_relative_posn(GtkWidget * w,GtkWidget * z,int * x,int * y)3609 static int get_relative_posn(GtkWidget *w, GtkWidget *z, int *x, int *y) {
3610 #ifdef GTK2
3611 return gtk_widget_translate_coordinates(z,w,0,0,x,y);
3612 #endif
3613 *x = 0; *y = 0; return ((w == z) || get_relative_posn_aux(w,z,x,y));
3614 }
3615
3616 /* utility function for wall display: given the tile in
3617 wall ori, stack j, and top layer (k = 0), bottom layer (k = 1)
3618 or loose tile (k = -1), return its x and y coordinates in the
3619 discard area.
3620 If the isdead argument is one, the tile is to be displaced as
3621 in the dead wall. This assumes that the board widget has a border
3622 into which we can shift the wall a little bit.
3623 */
3624
wall_posn(int ori,int j,int k,int isdead,int * xp,int * yp)3625 static void wall_posn(int ori, int j, int k, int isdead, int *xp, int *yp) {
3626 int x=0, y=0;
3627 /* calculate basic position with respect to discard area */
3628 /* because there are possibly three stacks (the two layers
3629 and the loose tiles), which we want to display in a
3630 formalized perspective, by displacing layers by
3631 tile_spacing from each other, each wall occupies
3632 tile_height + 2*tile_spacing in the forward direction */
3633 switch (ori) {
3634 case 0:
3635 x = ((WALL_SIZE/4)/2 - (j+1))*tile_width +tile_spacing;
3636 y = (WALL_SIZE/4)/2 * tile_width + (k+1)*tile_spacing ;
3637 break;
3638 case 1:
3639 x = (WALL_SIZE/4)/2 * tile_width + (-k)*tile_spacing + 2*tile_spacing;
3640 y = tile_height + 2*tile_spacing + j*tile_width;
3641 break;
3642 case 2:
3643 x = tile_height + 2*tile_spacing + j*tile_width;
3644 y = (k+1)*tile_spacing;
3645 break;
3646 case 3:
3647 x = (k+1)*tile_spacing;
3648 y = ((WALL_SIZE/4)/2 - (j+1))*tile_width + tile_spacing;
3649 break;
3650 }
3651 /* the board has been sized assuming MAX_WALL_SIZE; so if we
3652 are playing with a smaller wall, we should centre the walls */
3653 x += tile_width*((MAX_WALL_SIZE - WALL_SIZE)/8)/2;
3654 y += tile_width*((MAX_WALL_SIZE - WALL_SIZE)/8)/2;
3655 if ( isdead ) {
3656 switch ( wall_ori(wall_game_to_board(gextras(the_game)->orig_live_end)) ) {
3657 case 0: x -= tile_spacing; break;
3658 case 1: y += tile_spacing; break;
3659 case 2: x += tile_spacing; break;
3660 case 3: y -= tile_spacing; break;
3661 }
3662 }
3663 *xp = x; *yp = y;
3664 }
3665
clear_wall(void)3666 static void clear_wall(void) {
3667 int i;
3668 /* destroy all existing widgets */
3669 for ( i=0; i<MAX_WALL_SIZE; i++ ) {
3670 if ( wall[i] ) gtk_widget_destroy(wall[i]);
3671 wall[i] = NULL;
3672 }
3673 }
3674
3675 /* create a new wall */
create_wall(void)3676 static void create_wall(void) {
3677 int i,j,k,n,ori,x,y;
3678 GtkWidget *w;
3679 GtkWidget *pm;
3680
3681 clear_wall();
3682 /* create buttons and place in discard area */
3683 for ( i = 0; i < 4; i++ ) {
3684 for ( j = 0; j < (WALL_SIZE/4)/2 ; j++ ) {
3685 /* j is stack within wall */
3686 /* 0 is top, 1 is bottom */
3687 for (k=1; k >= 0 ; k-- ) {
3688 n = i*(WALL_SIZE/4)+(2*j)+k;
3689 ori = wall_ori(n);
3690 wall_posn(ori,j,k,the_game
3691 && wall_board_to_game(n) >= the_game->wall.live_end,&x,&y);
3692 w = gtk_button_new();
3693 gtk_widget_set_name(w,"tile");
3694 GTK_WIDGET_UNSET_FLAGS(w,GTK_CAN_FOCUS);
3695 pm = gtk_pixmap_new(tilepixmaps[ori][HiddenTile],NULL);
3696 gtk_widget_show(pm);
3697 gtk_container_add(GTK_CONTAINER(w),pm);
3698 button_set_tile(w,HiddenTile,ori);
3699 gtk_widget_show(w);
3700 gtk_fixed_put(GTK_FIXED(discard_area),w,x,y);
3701 wall[n] = w;
3702 }
3703 }
3704 }
3705 }
3706
3707 /* adjust the loose tiles.
3708 The argument is the tile that *has just* been drawn, which
3709 is normally the_game->wall.dead_end */
adjust_wall_loose(int tile_drawn)3710 static AnimInfo *adjust_wall_loose (int tile_drawn) {
3711 int i,n,m,ori,j,x,y;
3712 AnimInfo *retval = NULL;
3713 static AnimInfo anim;
3714
3715 i = wall_game_to_board(tile_drawn);
3716 if ( tile_drawn < the_game->wall.size ) {
3717 /* which tile gets drawn? If dead_end was even before
3718 (i.e. it's now odd), the tile drawn was the 2nd to last;
3719 otherwise it's the last */
3720 if ( tile_drawn%2 == 1 ) {
3721 /* tile drawn was 2nd to last */
3722 i = wall_game_to_board(tile_drawn-1);
3723 } else {
3724 /* tile drawn was actually beyond the then dead end */
3725 i = wall_game_to_board(tile_drawn+1);
3726 }
3727 assert(i < the_game->wall.size && wall[i]);
3728 anim.target = NULL;
3729 anim.t = HiddenTile;
3730 anim.ori = wall_ori(i);
3731 get_relative_posn(boardframe,wall[i],
3732 &anim.x,&anim.y);
3733 retval = &anim;
3734 gtk_widget_destroy(wall[i]);
3735 wall[i] = NULL;
3736 /* if we are playing with a 14 tile wall, and the dead wall
3737 is now 14 again, then move two tiles from the live wall
3738 into the dead wall. */
3739 if ( !game_get_option_value(the_game,GODeadWall16,NULL).optbool
3740 && game_get_option_value(the_game,GODeadWall,NULL).optbool
3741 && (tile_drawn-the_game->wall.live_end == 14) ) {
3742 /* the bottom tile that gets moved: now second tile in dead wall */
3743 n = wall_game_to_board(the_game->wall.live_end+1);
3744 /* if tile exists, reposition */
3745 if ( the_game->wall.live_used < the_game->wall.live_end+1 ) {
3746 ori = wall_ori(n);
3747 j = (n - (the_game->wall.size/4)*(n/(the_game->wall.size/4)))/2;
3748 wall_posn(ori,j,1,1,&x,&y);
3749 gtk_fixed_move(GTK_FIXED(discard_area),wall[n],x,y);
3750 }
3751 /* and the top tile */
3752 n = wall_game_to_board(the_game->wall.live_end);
3753 /* if tile exists, reposition */
3754 if ( the_game->wall.live_used < the_game->wall.live_end ) {
3755 ori = wall_ori(n);
3756 j = (n - (the_game->wall.size/4)*(n/(the_game->wall.size/4)))/2;
3757 wall_posn(ori,j,0,1,&x,&y);
3758 gtk_fixed_move(GTK_FIXED(discard_area),wall[n],x,y);
3759 }
3760 }
3761 }
3762 /* if the number of loose tiles remaining is even, move the
3763 last two into position */
3764 if ( tile_drawn%2 == 0 ) {
3765 /* number of the top loose tile */
3766 n = wall_game_to_board(tile_drawn-2);
3767 /* it gets moved back three stacks */
3768 m = ((n-6)+the_game->wall.size)%the_game->wall.size;
3769 ori = wall_ori(m);
3770 j = (m - (the_game->wall.size/4)*(m/(the_game->wall.size/4)))/2;
3771 wall_posn(ori,j,-1,game_get_option_value(the_game,GODeadWall,NULL).optbool && 1,&x,&y);
3772 gtk_fixed_move(GTK_FIXED(discard_area),wall[n],x,y);
3773 gdk_window_raise(wall[n]->window);
3774 button_set_tile(wall[n],HiddenTile,ori);
3775 /* and now the bottom tile, gets moved back one stack */
3776 n = wall_game_to_board(tile_drawn-1);
3777 m = ((n-2)+the_game->wall.size)%the_game->wall.size;
3778 ori = wall_ori(m);
3779 j = (m - (the_game->wall.size/4)*(m/(the_game->wall.size/4)))/2;
3780 wall_posn(ori,j,-1,game_get_option_value(the_game,GODeadWall,NULL).optbool && 1,&x,&y);
3781 gtk_fixed_move(GTK_FIXED(discard_area),wall[n],x,y);
3782 gdk_window_raise(wall[n]->window);
3783 button_set_tile(wall[n],HiddenTile,ori);
3784 }
3785 return retval;
3786 }
3787
3788
3789 /* popup a claim window.
3790 This is a real pain, cos we have to work out where to put it.
3791 thinking means uses the transparent thinking box
3792 To keep it simple, we'll put it half way along the appropriate
3793 edge, centered two tileheights in. */
playerdisp_popup_claim_window(PlayerDisp * pd,const char * lab,int thinking)3794 static void playerdisp_popup_claim_window(PlayerDisp *pd, const char *lab, int thinking) {
3795 GtkRequisition r;
3796 gint x,y,w,h;
3797 int cori = pd->orientation;
3798
3799 /* first of all, create the claim window if not already done */
3800 if ( pd->claimw == NULL ) {
3801 GtkWidget *box;
3802 // all this should be handled by styles
3803 #ifndef GTK2
3804 static GtkStyle *s;
3805 GdkColor c;
3806 if ( !s ) {
3807 s = gtk_style_copy(defstyle);
3808 gdk_color_parse("yellow",&c);
3809 s->bg[GTK_STATE_NORMAL] = c;
3810 if ( big_font )
3811 s->font = big_font;
3812 }
3813 gtk_widget_push_style(s);
3814 #endif // ifndef GTK2
3815 pd->claimw = gtk_event_box_new();
3816 gtk_widget_set_name(GTK_WIDGET(pd->claimw),"claim");
3817 box = gtk_hbox_new(0,0);
3818 gtk_widget_show(box);
3819 gtk_widget_set_name(box,"claim");
3820 gtk_container_add(GTK_CONTAINER(pd->claimw),box);
3821 /* should parametrize this some time */
3822 gtk_container_set_border_width(GTK_CONTAINER(box),7);
3823 pd->claimlab = gtk_label_new("");
3824 gtk_widget_set_name(pd->claimlab,"claim");
3825 pd->claim_serial = -1;
3826 pd->claim_time = -10000;
3827 gtk_widget_show(pd->claimlab);
3828 gtk_box_pack_start(GTK_BOX(box),pd->claimlab,0,0,0);
3829 #ifndef GTK2
3830 gtk_widget_pop_style();
3831 #endif
3832 vlazy_fixed_put(VLAZY_FIXED(boardfixed),pd->claimw,0,0);
3833 // now do the same for thinkbox - we no longer support gtk1
3834 box = pd->thinkbox = gtk_hbox_new(0,0);
3835 gtk_widget_show(box);
3836 gtk_widget_set_name(box,"think");
3837 GtkWidget *tlab = gtk_label_new("...");
3838 gtk_widget_show(tlab);
3839 gtk_widget_set_name(tlab,"think");
3840 gtk_box_pack_start(GTK_BOX(box),tlab,0,0,0);
3841 vlazy_fixed_put(VLAZY_FIXED(boardfixed),box,0,0);
3842 }
3843
3844
3845 GtkWidget *thisw = thinking ? pd->thinkbox : pd->claimw;
3846 /* set the label */
3847 if ( ! thinking ) gtk_label_set_text(GTK_LABEL(pd->claimlab),lab);
3848 pd->claim_serial = the_game->serial;
3849 pd->claim_time = now_time();
3850
3851 /* calculate the position given the current label */
3852 gtk_widget_size_request(board,&r);
3853 w = r.width; h = r.height;
3854 gtk_widget_size_request(thisw,&r);
3855 switch ( cori ) {
3856 case 0:
3857 x = w/2 - r.width/2;
3858 y = h - r.height/2 - 2*tile_height;
3859 break;
3860 case 1:
3861 x = w - r.width/2 - 2*tile_height;
3862 y = h/2 - r.height/2;
3863 break;
3864 case 2:
3865 x = w/2 - r.width/2;
3866 y = - r.height/2 + 2*tile_height;
3867 break;
3868 case 3:
3869 x = - r.width/2 + 2*tile_height;
3870 y = h/2 - r.height/2;
3871 break;
3872 default:
3873 /* to shut up warnings */
3874 x = 0; y = 0;
3875 error("bad value of cori in switch in playerdisp_update");
3876 }
3877 vlazy_fixed_move(VLAZY_FIXED(boardfixed),thisw,x,y);
3878
3879 /* if not already popped up */
3880 if ( !GTK_WIDGET_VISIBLE(thisw) ) {
3881 gtk_widget_show(thisw);
3882 }
3883 }
3884
3885 /* check_claim_window: see whether a claim window should be
3886 popped down. This calls itself as a timeout, hence the prototype.
3887 If a claim window refers to an old discard, or we are in state
3888 handcomplete, pop it down unconditionally.
3889 Otherwise, if it's more than two seconds old, and we're not
3890 in the state Discarded, pop it down; if we're not in the discarded
3891 state, but it's less than two seconds old, check again later.
3892 */
check_claim_window(gpointer data)3893 static gint check_claim_window(gpointer data) {
3894 PlayerDisp *pd = data;
3895
3896 /* Unconditional popdown */
3897 if ( !the_game || pd->claim_serial < the_game->serial
3898 || the_game->state == HandComplete ) {
3899 gtk_widget_hide(pd->claimw);
3900 gtk_widget_hide(pd->thinkbox);
3901 pd->claim_serial = -1;
3902 pd->claim_time = -10000;
3903 } else if ( the_game->state != Discarded ) {
3904 int interval = now_time() - pd->claim_time;
3905 if ( interval > 2000 ) {
3906 /* pop down now */
3907 gtk_widget_hide(pd->claimw);
3908 gtk_widget_hide(pd->thinkbox);
3909 pd->claim_serial = -1;
3910 pd->claim_time = -10000;
3911 } else {
3912 /* schedule a time out to check this window again when
3913 the two seconds have expired */
3914 gtk_timeout_add(interval+100,check_claim_window,(gpointer)pd);
3915 }
3916 }
3917 return FALSE; /* don't run *this* timeout again! */
3918 }
3919
3920 /* read_rcfile: given a file name, parse it as an rcfile */
3921 /* at the moment, this just copies options, but doesn't act
3922 on them. This should be fixed for the display options at least.
3923 The arguments say which options are to be read or updated,
3924 using groups defined by the XmjrcGroup enum flag bits.
3925 It is an error both to read and update a group.
3926 */
read_or_update_rcfile(char * rcfile,XmjrcGroup read_groups,XmjrcGroup update_groups)3927 int read_or_update_rcfile(char *rcfile,
3928 XmjrcGroup read_groups,
3929 XmjrcGroup update_groups)
3930 {
3931 char inbuf[1024];
3932 char rcfbuf[1024];
3933 char nrcfile[1024];
3934 char *inptr;
3935 int i;
3936 int usingrc = 0;
3937 FILE *rcf = NULL;
3938 FILE *ncf = NULL;
3939
3940 # ifndef WIN32
3941 const char *nl = "\n";
3942 # else
3943 const char *nl = "\r\n";
3944 # endif
3945
3946 /* sanity check */
3947 if ( read_groups & update_groups ) {
3948 warn("read_or_update_rcfile called with simultaneous read and update");
3949 return 0;
3950 }
3951
3952 if ( ! rcfile ) {
3953 char *hd = get_homedir();
3954 if ( hd == NULL ) {
3955 # ifdef WIN32
3956 // no home dir. If we can write into the execution directory
3957 // use that. Otherwise, use root.
3958 if ( access(".",W_OK) ) {
3959 info("no home directory, using execution directory");
3960 hd = ".";
3961 } else {
3962 info("no home directory, can't write exec directory, using root");
3963 hd = "C:";
3964 }
3965 # else
3966 // no homedir, just use tmp
3967 info("no home diretory, using /tmp");
3968 hd = "/tmp";
3969 # endif
3970 }
3971 strmcpy(rcfbuf,hd,sizeof(rcfbuf)); // $HOME can trigger overflow...
3972 # ifdef WIN32
3973 strmcat(rcfbuf,"/xmj.ini",sizeof(rcfbuf)-strlen(rcfbuf));
3974 # else
3975 strmcat(rcfbuf,"/.xmjrc",sizeof(rcfbuf)-strlen(rcfbuf));
3976 # endif
3977 rcfile = rcfbuf;
3978 usingrc = 1; /* not an error not to find the standard rc file */
3979 }
3980 rcf = fopen(rcfile,"r");
3981 // on Windows, if this failed on the standard file,
3982 // look in the root, in case there's an old file there.
3983 // If that fails, look in the exec directory, in case there's
3984 // an ini file shipped with the distribution.
3985 # ifdef WIN32
3986 if ( !rcf && usingrc ) {
3987 rcf = fopen("C:/xmj.ini","r");
3988 if ( rcf ) {
3989 info("Found old xmj.ini in root directory");
3990 } else if ( (rcf = fopen("./xmj.ini","r")) ) {
3991 info("Found old/system xmj.ini in exec directory");
3992 }
3993 }
3994 # endif
3995 if ( ! rcf && ! usingrc ) {
3996 warn("Couldn't open rcfile %s: %s",rcfile,strerror(errno));
3997 return 0;
3998 }
3999 if ( update_groups ) {
4000 strmcpy(nrcfile,rcfile,1020);
4001 # ifdef WIN32
4002 /* I don't know whether this helps on Windows ME */
4003 if ( strcmp(nrcfile + strlen(nrcfile) - 4, ".ini") == 0 ) {
4004 strcpy(nrcfile + strlen(nrcfile) - 4,".new");
4005 } else {
4006 strcat(nrcfile,".new");
4007 }
4008 # else
4009 strcat(nrcfile,".new");
4010 # endif
4011 ncf = fopen(nrcfile,"w");
4012 if ( ! ncf ) {
4013 warn("Couldn't open new rcfile %s: %s",nrcfile,strerror(errno));
4014 if ( rcf ) fclose(rcf);
4015 return 0;
4016 }
4017 }
4018 while ( rcf && ! feof(rcf) ) {
4019 if ( ! fgets(inbuf,1024,rcf) ) {
4020 if ( feof(rcf) ) break;
4021 warn("Unexpected error from fgets: %s",strerror(errno));
4022 fclose(rcf);
4023 return 0;
4024 }
4025 inptr = inbuf;
4026 while ( isspace(*inptr) ) inptr++;
4027 if ( *inptr == '#' ) {
4028 ; /* comment line */
4029 } else if ( ( (strncmp(inptr,"XmjOption",9) == 0) && (inptr += 9) )
4030 || ( (strncmp(inptr,"Display",7) == 0) && (inptr += 7) ) ) {
4031 /* if updating display, don't even copy this, since
4032 we'll be writing it from UI later */
4033 if ( update_groups & XmjrcDisplay ) continue;
4034 /* if not reading display, skip */
4035 if ( read_groups & XmjrcDisplay ) {
4036 while ( isspace(*inptr) ) inptr++;
4037 if ( strncmp(inptr,"Animate",7) == 0 ) {
4038 inptr += 7;
4039 sscanf(inptr,"%d",&animate);
4040 } else if ( strncmp(inptr,"DialogPosition",14) == 0 ) {
4041 inptr += 14;
4042 while ( isspace(*inptr) ) inptr++;
4043 if ( strncmp(inptr,"central",7) == 0 ) {
4044 dialogs_position = DialogsCentral;
4045 } else if ( strncmp(inptr,"below",5) == 0 ) {
4046 dialogs_position = DialogsBelow;
4047 } else if ( strncmp(inptr,"popup",5) == 0 ) {
4048 dialogs_position = DialogsPopup;
4049 } else {
4050 warn("unknown argument for Display DialogPosition: %s",
4051 inptr);
4052 }
4053 } else if ( strncmp(inptr,"NoPopups",8) == 0 ) {
4054 inptr += 8;
4055 sscanf(inptr,"%d",&nopopups);
4056 } else if ( strncmp(inptr,"Tiletips",8) == 0 ) {
4057 inptr += 8;
4058 sscanf(inptr,"%d",&tiletips);
4059 } else if ( strncmp(inptr,"RotateLabels",12) == 0 ) {
4060 inptr += 12;
4061 sscanf(inptr,"%d",&rotatelabels);
4062 } else if ( strncmp(inptr,"ThinkingClaim",13) == 0 ) {
4063 inptr += 13;
4064 sscanf(inptr,"%d",&thinking_claim);
4065 } else if ( strncmp(inptr,"WarnMahjong",11) == 0 ) {
4066 inptr += 11;
4067 sscanf(inptr,"%d",&alert_mahjong);
4068 } else if ( strncmp(inptr,"IconifyDialogs",14) == 0 ) {
4069 inptr += 14;
4070 sscanf(inptr,"%d",&iconify_dialogs_with_main);
4071 } else if ( strncmp(inptr,"InfoInMain",10) == 0 ) {
4072 inptr += 10;
4073 sscanf(inptr,"%d",&info_windows_in_main);
4074 } else if ( strncmp(inptr,"MainFont",8) == 0 ) {
4075 inptr += 8;
4076 while ( isspace(*inptr) ) inptr++;
4077 sscanf(inptr,"%255[^\r\n]",main_font_name);
4078 } else if ( strncmp(inptr,"TextFont",8) == 0 ) {
4079 inptr += 8;
4080 while ( isspace(*inptr) ) inptr++;
4081 sscanf(inptr,"%255[^\r\n]",text_font_name);
4082 } else if ( strncmp(inptr,"TableColour",11) == 0 ) {
4083 inptr += 11;
4084 while ( isspace(*inptr) ) inptr++;
4085 sscanf(inptr,"%255[^\r\n]",table_colour_name);
4086 #ifdef GTK2
4087 /* handle colour name formats from gtk1 versions,
4088 but not vice versa */
4089 if ( strncmp("rgb:",table_colour_name,4) == 0 ) {
4090 char* p = table_colour_name+4;
4091 char buf[256];
4092 buf[0] = '#';
4093 int j=1;
4094 while ( *p && (*p != '/' || p++)) buf[j++] = *(p++);
4095 buf[j] = 0;
4096 strcpy(table_colour_name,buf);
4097 }
4098 #endif
4099 } else if ( strncmp(inptr,"ShowWall",8) == 0 ) {
4100 inptr += 8;
4101 while ( isspace(*inptr) ) inptr++;
4102 if ( strncmp(inptr,"always",6) == 0 ) {
4103 pref_showwall = 1;
4104 } else if ( strncmp(inptr,"when-room",9) == 0 ) {
4105 pref_showwall = -1;
4106 } else if ( strncmp(inptr,"never",5) == 0 ) {
4107 pref_showwall = 0;
4108 } else {
4109 warn("unknown argument for Display ShowWall: %s",
4110 inptr);
4111 }
4112 } else if ( strncmp(inptr,"Size",4) == 0) {
4113 inptr += 4;
4114 sscanf(inptr,"%d",&display_size);
4115 } else if ( strncmp(inptr,"SortTiles",9) == 0) {
4116 inptr += 9;
4117 while ( isspace(*inptr) ) inptr++;
4118 if ( strncmp(inptr,"always",6) == 0 ) {
4119 sort_tiles = SortAlways;
4120 } else if ( strncmp(inptr,"deal",4) == 0 ) {
4121 sort_tiles = SortDeal;
4122 } else if ( strncmp(inptr,"never",5) == 0 ) {
4123 sort_tiles = SortNever;
4124 } else {
4125 warn("unknown argument for Display SortTiles: %s",
4126 inptr);
4127 }
4128 /* N.B. This has to be tested before Tileset ! */
4129 } else if ( strncmp(inptr,"TilesetPath",11) == 0 ) {
4130 inptr += 11;
4131 while ( isspace(*inptr) ) inptr++;
4132 tileset_path = malloc(strlen(inptr+1));
4133 /* FIXME: memory leak */
4134 if ( ! tileset_path ) {
4135 warn("out of memory");
4136 exit(1);
4137 }
4138 sscanf(inptr,"%[^\r\n]",tileset_path);
4139 } else if ( strncmp(inptr,"Tileset",7) == 0 ) {
4140 inptr += 7;
4141 while ( isspace(*inptr) ) inptr++;
4142 tileset = malloc(strlen(inptr+1));
4143 /* FIXME: memory leak */
4144 if ( ! tileset ) {
4145 warn("out of memory");
4146 exit(1);
4147 }
4148 sscanf(inptr,"%[^\r\n]",tileset);
4149 } else if ( strncmp(inptr,"RobotName",9) == 0 ) {
4150 /* This is obsoleted -- it's been moved to the
4151 OpenDialog settings. We won't take any notice
4152 of it, but we won't complain about it either. */
4153 #ifdef GTK2
4154 } else if ( strncmp(inptr,"Gtk2Rcfile",10) == 0 ) {
4155 inptr += 10;
4156 while ( isspace(*inptr) ) inptr++;
4157 char *buf = malloc(strlen(inptr+1));
4158 sscanf(inptr,"%[^\r\n]",buf);
4159 strmcpy(gtk2_rcfile,buf,sizeof(gtk2_rcfile));
4160 free(buf);
4161 } else if ( strncmp(inptr,"UseSystemGtkrc",14) == 0 ) {
4162 inptr += 14;
4163 sscanf(inptr,"%d",&use_system_gtkrc);
4164 #endif
4165 } else {
4166 warn("unknown Display: %s",inptr);
4167 }
4168 }
4169 } else if ( strncmp(inptr,"GameOption",10) == 0 ) {
4170 CMsgGameOptionMsg *gom;
4171 if ( update_groups & XmjrcGame ) continue;
4172 if ( read_groups & XmjrcGame ) {
4173 gom = (CMsgGameOptionMsg *)decode_cmsg(inptr);
4174 if ( gom ) {
4175 game_set_option_in_table(&prefs_table,&gom->optentry);
4176 } else {
4177 warn("Error reading game option from rcfile; deleting option");
4178 }
4179 }
4180 } else if ( strncmp(inptr,"CompletedGames",14) == 0 ) {
4181 if ( update_groups & XmjrcMisc ) continue;
4182 if ( read_groups & XmjrcMisc ) {
4183 inptr += 14;
4184 sscanf(inptr,"%d",&completed_games);
4185 }
4186 } else if ( strncmp(inptr,"NagDate",7) == 0 ) {
4187 if ( update_groups & XmjrcMisc ) continue;
4188 if ( read_groups & XmjrcMisc ) {
4189 inptr += 7;
4190 /* there's no portable way to scan a time_t ... */
4191 long long ttt;
4192 sscanf(inptr,"%lld",&ttt);
4193 nag_date = (time_t)ttt;
4194 }
4195 } else if ( strncmp(inptr,"NagState",8) == 0 ) {
4196 if ( update_groups & XmjrcMisc ) continue;
4197 if ( read_groups & XmjrcMisc ) {
4198 inptr += 8;
4199 sscanf(inptr,"%d",&nag_state);
4200 }
4201 } else if ( strncmp(inptr,"DebugReports",12) == 0 ) {
4202 if ( update_groups & XmjrcMisc ) continue;
4203 if ( read_groups & XmjrcMisc ) {
4204 inptr += 12;
4205 while ( isspace(*inptr) ) inptr++;
4206 if ( strncmp(inptr,"never",5) == 0 ) {
4207 debug_reports = DebugReportsNever;
4208 } else if ( strncmp(inptr,"ask",3) == 0 ) {
4209 debug_reports = DebugReportsAsk;
4210 } else if ( strncmp(inptr,"always",6) == 0 ) {
4211 debug_reports = DebugReportsAlways;
4212 } else {
4213 warn("unknown DebugReports value %s",inptr);
4214 }
4215 }
4216 } else if ( strncmp(inptr,"OpenDialog",10) == 0 ) {
4217 inptr += 10;
4218 if ( update_groups & XmjrcOpen ) continue;
4219 if ( read_groups & XmjrcOpen ) {
4220 while ( isspace(*inptr) ) inptr++;
4221 if ( strncmp(inptr,"Address",7) == 0 ) {
4222 inptr += 7;
4223 sscanf(inptr,"%255s",address);
4224 } else if ( strncmp(inptr,"Name",4) == 0 ) {
4225 inptr += 4;
4226 sscanf(inptr," %255[^\r\n]",name);
4227 } else if ( strncmp(inptr,"RobotName",9) == 0 ) {
4228 int i;
4229 char buf[128];
4230 buf[127] = 0;
4231 inptr += 9;
4232 sscanf(inptr,"%d %127[^\r\n]",&i,buf);
4233 if ( i < 2 || i > 4 ) {
4234 warn("bad robot argument to OpenDialog RobotName");
4235 } else {
4236 strcpy(robot_names[i-2],buf);
4237 }
4238 } else if ( strncmp(inptr,"RobotOptions",12) == 0 ) {
4239 int i;
4240 char buf[128];
4241 buf[127] = 0;
4242 inptr += 12;
4243 sscanf(inptr,"%d %127[^\r\n]",&i,buf);
4244 if ( i < 2 || i > 4 ) {
4245 warn("bad robot argument to OpenDialog RobotOptions");
4246 } else {
4247 strcpy(robot_options[i-2],buf);
4248 }
4249 }
4250 }
4251 } else if ( strncmp(inptr,"Playing",7) == 0 ) {
4252 inptr += 7;
4253 if ( update_groups & XmjrcPlaying ) continue;
4254 if ( read_groups & XmjrcPlaying ) {
4255 while ( isspace(*inptr) ) inptr++;
4256 if ( strncmp(inptr,"AutoSpecials",12) == 0 ) {
4257 inptr += 12;
4258 sscanf(inptr,"%d",&playing_auto_declare_specials);
4259 } else if ( strncmp(inptr,"AutoLosing",10) == 0 ) {
4260 inptr += 10;
4261 sscanf(inptr,"%d",&playing_auto_declare_losing);
4262 } else if ( strncmp(inptr,"AutoWinning",11) == 0 ) {
4263 inptr += 11;
4264 sscanf(inptr,"%d",&playing_auto_declare_winning);
4265 }
4266 }
4267 } else {
4268 warn("unknown setting in rcfile %s: %s",rcfile,inptr);
4269 }
4270 if ( update_groups ) {
4271 if ( fputs(inbuf,ncf) == EOF ) {
4272 warn("Error writing to new rcfile %s: %s",nrcfile,strerror(errno));
4273 fclose(rcf); fclose(ncf);
4274 return 0;
4275 }
4276 }
4277 }
4278 if ( rcf ) fclose(rcf);
4279 if ( update_groups & XmjrcDisplay ) {
4280 fprintf(ncf,"Display Animate %d%s",animate,nl);
4281 fprintf(ncf,"Display DialogPosition %s%s",
4282 (dialogs_position == DialogsBelow)
4283 ? "below" :
4284 (dialogs_position == DialogsPopup)
4285 ? "popup" :
4286 "central", nl);
4287 fprintf(ncf,"Display NoPopups %d%s",nopopups,nl);
4288 fprintf(ncf,"Display Tiletips %d%s",tiletips,nl);
4289 fprintf(ncf,"Display RotateLabels %d%s",rotatelabels,nl);
4290 fprintf(ncf,"Display ThinkingClaim %d%s",thinking_claim,nl);
4291 fprintf(ncf,"Display WarnMahjong %d%s",alert_mahjong,nl);
4292 fprintf(ncf,"Display IconifyDialogs %d%s",iconify_dialogs_with_main,nl);
4293 fprintf(ncf,"Display InfoInMain %d%s",info_windows_in_main,nl);
4294 if ( main_font_name[0] ) {
4295 fprintf(ncf,"Display MainFont %s%s",main_font_name,nl);
4296 }
4297 if ( text_font_name[0] ) {
4298 fprintf(ncf,"Display TextFont %s%s",text_font_name,nl);
4299 }
4300 if ( table_colour_name[0] ) {
4301 fprintf(ncf,"Display TableColour %s%s",table_colour_name,nl);
4302 }
4303 fprintf(ncf,"Display ShowWall %s%s",
4304 pref_showwall == 1 ? "always" :
4305 pref_showwall == 0 ? "never" :
4306 "when-room", nl);
4307 if ( display_size ) {
4308 fprintf(ncf,"Display Size %d%s",display_size,nl);
4309 }
4310 static const char *stlist[] = { "always", "deal", "never" };
4311 fprintf(ncf,"Display SortTiles %s%s",stlist[sort_tiles],nl);
4312 if ( tileset && tileset[0] )
4313 fprintf(ncf,"Display Tileset %s%s",tileset,nl);
4314 if ( tileset_path && tileset_path[0] )
4315 fprintf(ncf,"Display TilesetPath %s%s",tileset_path,nl);
4316 #ifdef GTK2
4317 if ( gtk2_rcfile[0] ) {
4318 fprintf(ncf,"Display Gtk2Rcfile %s%s",gtk2_rcfile,nl);
4319 }
4320 if ( use_system_gtkrc ) {
4321 fprintf(ncf,"Display UseSystemGtkrc %d%s",use_system_gtkrc,nl);
4322 }
4323 #endif
4324 }
4325 if ( update_groups & XmjrcGame ) {
4326 char *pline;
4327 CMsgGameOptionMsg gom;
4328 gom.type = CMsgGameOption;
4329 gom.id = 0;
4330 for ( i = 0 ; i < prefs_table.numoptions ; i++ ) {
4331 if ( ! prefs_table.options[i].enabled ) continue;
4332 gom.optentry = prefs_table.options[i];
4333 pline = encode_cmsg((CMsgMsg *)&gom);
4334 # ifndef WIN32
4335 pline[strlen(pline)-1] = 0;
4336 pline[strlen(pline)-1] = '\n';
4337 # endif
4338 fprintf(ncf,"%s",pline);
4339 }
4340 }
4341 if ( update_groups & XmjrcMisc ) {
4342 fprintf(ncf,"CompletedGames %d%s",completed_games,nl);
4343 fprintf(ncf,"NagState %d%s",nag_state,nl);
4344 /* no portable way to deal with time_t */
4345 long long ttt = nag_date;
4346 fprintf(ncf,"NagDate %lld%s",ttt,nl);
4347 fprintf(ncf,"DebugReports %s%s",
4348 debug_reports == DebugReportsNever ? "never" :
4349 debug_reports == DebugReportsAsk ? "ask" :
4350 debug_reports == DebugReportsAlways ? "always" :
4351 "unspecified",nl);
4352 }
4353 if ( update_groups & XmjrcOpen ) {
4354 /* don't save if it's the default */
4355 if ( strcmp(redirected ? origaddress : address,"localhost:5000") != 0 ) {
4356 fprintf(ncf,"OpenDialog Address %s%s",redirected ? origaddress : address,nl);
4357 }
4358 /* don't save name if it's empty */
4359 if ( name[0] ) {
4360 fprintf(ncf,"OpenDialog Name %s%s",name,nl);
4361 }
4362 for ( i = 0; i < 3; i++ ) {
4363 if ( robot_names[i][0] )
4364 fprintf(ncf,"OpenDialog RobotName %d %s%s",i+2,robot_names[i],nl);
4365 if ( robot_options[i][0] )
4366 fprintf(ncf,"OpenDialog RobotOptions %d %s%s",i+2,robot_options[i],nl);
4367 }
4368 }
4369 if ( update_groups & XmjrcPlaying ) {
4370 fprintf(ncf,"Playing AutoSpecials %d%s",playing_auto_declare_specials,nl);
4371 fprintf(ncf,"Playing AutoLosing %d%s",playing_auto_declare_losing,nl);
4372 fprintf(ncf,"Playing AutoWinning %d%s",playing_auto_declare_winning,nl);
4373 }
4374 if ( update_groups ) {
4375 fclose(ncf);
4376 ncf = fopen(nrcfile,"r");
4377 if ( ! ncf ) {
4378 warn("Couldn't re-open new rc file %s: %s",nrcfile,strerror(errno));
4379 return 0;
4380 }
4381 rcf = fopen(rcfile,"w");
4382 if ( ! rcf ) {
4383 warn("couldn't open rcfile %s for update: %s",rcfile,strerror(errno));
4384 fclose(ncf);
4385 return 0;
4386 }
4387 while ( ! feof(ncf) ) {
4388 fgets(inbuf,1024,ncf);
4389 if ( feof(ncf) ) continue;
4390 if ( fputs(inbuf,rcf) == EOF ) {
4391 warn("Error writing to rcfile %s: %s",rcfile,strerror(errno));
4392 fclose(rcf); fclose(ncf);
4393 return 0;
4394 }
4395 }
4396 fclose(rcf);
4397 fclose(ncf);
4398 unlink(nrcfile);
4399 }
4400 return 1;
4401 }
4402
read_rcfile(char * rcfile)4403 static int read_rcfile(char *rcfile) {
4404 return read_or_update_rcfile(rcfile,XmjrcAll,XmjrcNone);
4405 }
4406
4407 /* Apply the game options from the preferences to the current game */
apply_game_prefs(void)4408 void apply_game_prefs(void)
4409 {
4410 int i;
4411 GameOptionEntry *goe;
4412 PMsgSetGameOptionMsg psgom;
4413 char buf[256];
4414
4415 if ( ! the_game ) return;
4416 psgom.type = PMsgSetGameOption;
4417 for ( i = 0; i < prefs_table.numoptions; i++ ) {
4418 goe = &prefs_table.options[i];
4419 if ( ! goe->enabled ) continue; /* not in preferences */
4420 if ( goe->protversion > the_game->protversion ) continue; /* can't use it */
4421 strmcpy(psgom.optname,goe->name,16);
4422 switch ( goe->type ) {
4423 case GOTInt:
4424 sprintf(buf,"%d",goe->value.optint);
4425 break;
4426 case GOTNat:
4427 sprintf(buf,"%d",goe->value.optnat);
4428 break;
4429 case GOTBool:
4430 sprintf(buf,"%d",goe->value.optbool);
4431 break;
4432 case GOTScore:
4433 sprintf(buf,"%d",goe->value.optscore);
4434 break;
4435 case GOTString:
4436 sprintf(buf,"%s",goe->value.optstring);
4437 break;
4438 }
4439 psgom.optvalue = buf;
4440 send_packet(&psgom);
4441 }
4442 }
4443
create_display(void)4444 void create_display(void)
4445 {
4446 GtkWidget *tmpw, *inboard;
4447 #ifndef GTK2
4448 GtkAccelGroup *tile_accel;
4449 #endif
4450 int i;
4451
4452 #ifdef GTK2
4453 if ( main_font_name[0] ) {
4454 char c[300];
4455 strcpy(c,"gtk-font-name = \"");
4456 char *d = c + strlen(c);
4457 strmcpy(d,main_font_name,256);
4458 strcat(c,"\"");
4459 gtk_rc_parse_string(c);
4460 }
4461 if ( text_font_name[0] ) {
4462 char c[300];
4463 strcpy(c,"style \"text\" { font_name = \"");
4464 char *d = c + strlen(c);
4465 strmcpy(d,text_font_name,256);
4466 strcat(c,"\" }");
4467 gtk_rc_parse_string(c);
4468 /* having redefined the styles, we have to recompute them */
4469 gtk_rc_reset_styles(gtk_settings_get_default());
4470 }
4471 if ( table_colour_name[0] ) {
4472 char c[300];
4473 strcpy(c,"style \"table\" { bg[NORMAL] = \"");
4474 char *d = c + strlen(c);
4475 strmcpy(d,table_colour_name,256);
4476 strcat(c,"\" }");
4477 gtk_rc_parse_string(c);
4478 /* having redefined the styles, we have to recompute them */
4479 gtk_rc_reset_styles(gtk_settings_get_default());
4480 }
4481 #else
4482 /* deal with fonts */
4483 defstyle = gtk_widget_get_default_style();
4484
4485 if ( main_font_name[0] ) {
4486 main_font = gdk_font_load(main_font_name);
4487 if ( ! main_font ) {
4488 /* whoops */
4489 warn("Unable to load specified main font -- using default");
4490 main_font = original_default_style->font;
4491 }
4492 } else {
4493 main_font = original_default_style->font;
4494 }
4495 defstyle->font = main_font;
4496
4497 /* It is completely $@#$@# insane that I can't specify these
4498 things with resources. Sometimes I think the gtk developers
4499 have never heard of networked systems. */
4500 /* try to get a fixed font. Courier for preference */
4501 #ifdef WIN32
4502 fixed_font = gdk_font_load("courier new");
4503 strcpy(fallback_text_font_name,"courier new");
4504 if ( ! fixed_font ) {
4505 fixed_font = gdk_font_load("courier");
4506 strcpy(fallback_text_font_name,"courier");
4507 }
4508 #else
4509 fixed_font = gdk_font_load("-*-courier-medium-r-normal-*-*-120-*-*-*-*-*-*");
4510 strcpy(fallback_text_font_name,"-*-courier-medium-r-normal-*-*-120-*-*-*-*-*-*");
4511 #endif
4512 /* if that didn't work, try fixed */
4513 if ( ! fixed_font ) {
4514 fixed_font = gdk_font_load("-misc-fixed-medium-r-normal-*-*-120-*-*-*-*-*-*");
4515 strcpy(fallback_text_font_name,"-misc-fixed-medium-r-normal-*-*-120-*-*-*-*-*-*");
4516 }
4517 /* if that didn't work, try just "fixed" */
4518 if ( ! fixed_font ) {
4519 fixed_font = gdk_font_load("fixed");
4520 strcpy(fallback_text_font_name,"fixed");
4521 }
4522 /* if that didn't work; what can we do? */
4523 if ( ! fixed_font ) {
4524 warn("unable to load a fixed font for scoring window etc");
4525 fallback_text_font_name[0] = 0;
4526 }
4527
4528 /* having hopefully found one that works, to use as a fallback,
4529 now get what the user asked for */
4530 if ( text_font_name[0] ) {
4531 GdkFont *f;
4532 f = gdk_font_load(text_font_name);
4533 if ( ! f ) {
4534 /* whoops */
4535 warn("Unable to load specified main font -- using default");
4536 } else {
4537 fixed_font = f;
4538 }
4539 }
4540
4541 /* And now try to get a big font for claim windows. It seems that
4542 this does actually work on both X and Windows... */
4543 if ( !big_font ) {
4544 big_font = gdk_font_load("-*-helvetica-bold-r-*-*-*-180-*-*-*-*-iso8859-*");
4545 }
4546 if ( !big_font )
4547 big_font = gdk_font_load("-*-fixed-bold-r-*-*-*-140-*-*-*-*-iso8859-*");
4548 if ( !big_font )
4549 big_font = gdk_font_load("12x24");
4550 if ( !big_font )
4551 big_font = gdk_font_load("9x15bold");
4552
4553
4554 /* all the board widgets want a dark green background
4555 -- or whatever the user asked for */
4556 tablestyle = gtk_style_copy(defstyle);
4557
4558 gdk_color_parse(table_colour_name[0] ? table_colour_name : "darkgreen",
4559 &(tablestyle->bg[GTK_STATE_NORMAL]));
4560 /* to highlight discarded tiles */
4561 #endif /* end of ifndef GTK2 a while ago */
4562
4563 #ifdef GTK2
4564 gdk_color_parse("red",&highlightcolor);
4565 #else
4566 highlightstyle = gtk_style_copy(defstyle);
4567 gdk_color_parse("red",&(highlightstyle->bg[GTK_STATE_NORMAL]));
4568 #endif
4569
4570 /* set the size if not given by user */
4571 pdispwidth = display_size;
4572 if ( pdispwidth == 0 ) {
4573 pdispwidth = 19;
4574 #ifdef WIN32
4575 if ( gdk_screen_width() <= 800 ) {
4576 pdispwidth = 16;
4577 if (info_windows_in_main) pdispwidth = 14;
4578 }
4579 #else
4580 if ( gdk_screen_width() <= 800 ) pdispwidth = 17;
4581 #endif
4582 }
4583 /* choose a sensible value for show-wall */
4584 if ( showwall < 0 ) {
4585 showwall = ( gdk_screen_height() >= ((dialogs_position == DialogsBelow)
4586 ? 1000 : 900));
4587 }
4588
4589 if ( dialogs_position == DialogsUnspecified )
4590 dialogs_position = DialogsCentral;
4591
4592 topwindow = gtk_window_new (GTK_WINDOW_TOPLEVEL);
4593 gtk_widget_set_name(topwindow,"topwindow");
4594 gtk_signal_connect (GTK_OBJECT (topwindow), "delete_event",
4595 GTK_SIGNAL_FUNC (gtk_main_quit), NULL);
4596 /* (Un)iconify our auxiliary windows when we are
4597 */
4598 gtk_signal_connect (GTK_OBJECT (topwindow), "map_event",
4599 GTK_SIGNAL_FUNC (vis_callback), NULL);
4600 gtk_signal_connect (GTK_OBJECT (topwindow), "unmap_event",
4601 GTK_SIGNAL_FUNC (vis_callback), NULL);
4602
4603 /* do we have a position remembered ?
4604 There will some downward drift because of the window manager
4605 decorations, but I really can't be bothered to work round that. */
4606 if ( top_pos_set ) {
4607 gtk_widget_set_uposition(topwindow,top_x,top_y);
4608 }
4609
4610 /* Why do we realize the window? Because we want to use it
4611 in creating pixmaps. */
4612 gtk_widget_realize(topwindow);
4613
4614 /* create the tile pixmaps */
4615 {
4616 char pmfile[512];
4617 const char *code;
4618 const char *wl[] = { "E", "S", "W", "N" };
4619 GtkRequisition r;
4620 GtkWidget *b,*p;
4621 Tile i;
4622 int numfailures = 0;
4623
4624 /* specifying tileset as the empty string is equivalent
4625 to having it null, meaning don't look for tiles */
4626 if ( tileset && tileset[0] == 0 ) tileset = 0;
4627
4628 /* Now try to find a tile set: we look for a directory named
4629 tileset in the tileset_path search path. If it has a file
4630 called 1B.xpm (to choose at random) we'll assume it's
4631 a tileset directory */
4632 tilepixmapdir = 0;
4633 if ( tileset ) {
4634 char *start, *end, *tpend;
4635 int lastcmpt = 0;
4636 int nopath = 0;
4637 static char tp[1024];
4638 if ( tileset_path ) start = tileset_path;
4639 else start = "";
4640 /* if the tileset is given as an absolute path, we
4641 shouldn't use the tileset-path at all */
4642 # ifdef WIN32
4643 if ( tileset[0] == '/' || tileset[0] == '\\' || tileset[1] == ':' )
4644 { start = ""; nopath = 1; }
4645 # else
4646 if ( tileset[0] == '/' ) { start = ""; nopath = 1; }
4647 # endif
4648 while ( ! lastcmpt ) {
4649 end = strchr(start,PATHSEP);
4650 if ( end == 0 ) { end = start+strlen(start); lastcmpt = 1; }
4651 if ( start == end && ! nopath ) { strcpy(tp,"."); }
4652 else { strmcpy(tp,start,end-start+1); }
4653 strcat(tp,"/");
4654 strcat(tp,tileset);
4655 tpend = tp + strlen(tp);
4656 strcat(tp,"/1B.xpm");
4657 if ( access(tp,R_OK) == 0 ) {
4658 *tpend = 0; /* blank out the /1B.xpm */
4659 tilepixmapdir = tp;
4660 break;
4661 }
4662 /* failed; next component */
4663 start = end+1;
4664 }
4665 if ( ! tilepixmapdir ) {
4666 warn("Can't find tileset called %s - using fallback",tileset);
4667 }
4668 }
4669
4670 for ( i = -1; i < MaxTile; i++ ) {
4671 if ( tilepixmapdir ) {
4672 strmcpy(pmfile,tilepixmapdir,sizeof(pmfile)-1);
4673 strcat(pmfile,"/");
4674 }
4675 code = tile_code(i);
4676 /* only create the error pixmaps once! */
4677 if ( i >= 0 && strcmp(code,"XX") == 0 ) {
4678 tilepixmaps[0][i] = tilepixmaps[0][-1];
4679 tilepixmaps[1][i] = tilepixmaps[1][-1];
4680 tilepixmaps[2][i] = tilepixmaps[2][-1];
4681 tilepixmaps[3][i] = tilepixmaps[3][-1];
4682 }
4683 else {
4684 if ( tilepixmapdir ) {
4685 strmcat(pmfile,code,sizeof(pmfile)-strlen(pmfile));
4686 strmcat(pmfile,".xpm",sizeof(pmfile)-strlen(pmfile));
4687 tilepixmaps[0][i] = gdk_pixmap_create_from_xpm(topwindow->window,
4688 NULL,
4689 NULL,
4690 pmfile);
4691 }
4692 /* If this fails, use the fallback data. The error tile
4693 is in position 99, the others are in their natural position */
4694 if ( tilepixmaps[0][i] == NULL ) {
4695 int j;
4696 if ( i < 0 ) j = 99; else j = i;
4697 tilepixmaps[0][i] =
4698 gdk_pixmap_create_from_xpm_d(topwindow->window,
4699 NULL,
4700 NULL,fallbackpixmaps[j]);
4701 numfailures++;
4702 }
4703 rotate_pixmap(tilepixmaps[0][i],
4704 &tilepixmaps[1][i],
4705 &tilepixmaps[2][i],
4706 &tilepixmaps[3][i]);
4707 }
4708 }
4709 /* and the tong pixmaps */
4710 for (i=0; i<4; i++) {
4711 if ( tilepixmapdir ) {
4712 strmcpy(pmfile,tilepixmapdir,sizeof(pmfile)-1);
4713 strcat(pmfile,"/");
4714 strmcat(pmfile,"tong",sizeof(pmfile)-strlen(pmfile));
4715 strmcat(pmfile,wl[i],sizeof(pmfile)-strlen(pmfile));
4716 strmcat(pmfile,".xpm",sizeof(pmfile)-strlen(pmfile));
4717 tongpixmaps[0][i] = gdk_pixmap_create_from_xpm(topwindow->window,
4718 i>0 ? NULL : &tongmask,
4719 NULL,
4720 pmfile);
4721 }
4722 if ( tongpixmaps[0][i] == NULL ) {
4723 tongpixmaps[0][i] =
4724 gdk_pixmap_create_from_xpm_d(topwindow->window,
4725 i>0 ? NULL : &tongmask,
4726 NULL,fallbackpixmaps[101+i]);
4727 numfailures++;
4728 }
4729 rotate_pixmap(tongpixmaps[0][i],
4730 &tongpixmaps[1][i],
4731 &tongpixmaps[2][i],
4732 &tongpixmaps[3][i]);
4733 }
4734
4735 if ( tilepixmapdir && numfailures > 0 ) {
4736 char buf[50];
4737 sprintf(buf,"%d tile pixmaps not found: using fallbacks",numfailures);
4738 warn(buf);
4739 }
4740
4741 /* and compute the tile spacing */
4742 gdk_window_get_size(tilepixmaps[0][0],&tile_spacing,NULL);
4743 tile_spacing /= 4;
4744 b = gtk_button_new();
4745 gtk_widget_set_name(b,"tile");
4746 p = gtk_pixmap_new(tilepixmaps[0][HiddenTile],NULL);
4747 gtk_widget_show(p);
4748 gtk_container_add(GTK_CONTAINER(b),p);
4749 #ifdef GTK2
4750 /* we have to ensure that when we get the size of a tile button,
4751 it has the style that will be used on the table */
4752 gtk_container_add(GTK_CONTAINER(topwindow),b);
4753 gtk_widget_ensure_style(p);
4754 #endif
4755 gtk_widget_size_request(b,&r);
4756 tile_width = r.width;
4757 tile_height = r.height;
4758 gtk_widget_destroy(b);
4759 }
4760
4761 /* the outerframe contains the menubar and the board, and if
4762 dialogs are at the bottom, a box for them */
4763 /* between the menubar and the board, there is a box for
4764 the message and info windows, if info_windows_in_main is set */
4765 outerframe = gtk_vbox_new(0,0);
4766 gtk_container_add(GTK_CONTAINER(topwindow),outerframe);
4767 gtk_widget_show(outerframe);
4768 menubar = menubar_create();
4769 gtk_box_pack_start(GTK_BOX(outerframe),menubar,0,0,0);
4770 if ( info_windows_in_main ) {
4771 info_box = gtk_hbox_new(0,0);
4772 gtk_widget_show(info_box);
4773 gtk_box_pack_start(GTK_BOX(outerframe),info_box,0,0,0);
4774 } else {
4775 info_box = NULL;
4776 }
4777
4778 /* The structure of the whole board is (at present)
4779 an hbox containing leftplayer, vbox, rightplayer
4780 with the vbox containing
4781 topplayer discardarea bottomplayer (us).
4782 The left/right players are actually sandwiched inside
4783 vboxes so that they will centre if we force a square
4784 aspect ratio.
4785 If the wall is shown, player info labels are inserted into the
4786 vboxes containing the left and right players.
4787 */
4788
4789 /* 2009-06-17. There was something very odd here. Why was the
4790 boardframe event box inside the boardfixed rather than the
4791 other way round? */
4792
4793 boardfixed = vlazy_fixed_new();
4794 gtk_widget_set_name(boardfixed,"table");
4795 #ifndef GTK2
4796 gtk_widget_set_style(boardfixed,tablestyle);
4797 #endif
4798 gtk_widget_show(boardfixed);
4799 boardframe = gtk_event_box_new();
4800 gtk_widget_show(boardframe);
4801 gtk_widget_set_name(boardframe,"table");
4802 gtk_box_pack_start(GTK_BOX(outerframe),boardframe,0,0,0);
4803 gtk_container_add(GTK_CONTAINER(boardframe),boardfixed);
4804 #ifndef GTK2
4805 gtk_widget_set_style(boardframe,tablestyle);
4806 #endif
4807 gtk_widget_set_name(boardframe,"table");
4808 board = gtk_hbox_new(0,0);
4809 #ifndef GTK2
4810 gtk_widget_set_style(board,tablestyle);
4811 #endif
4812 gtk_widget_set_name(board,"table");
4813 gtk_widget_show(board);
4814 vlazy_fixed_put(VLAZY_FIXED(boardfixed),board,0,0);
4815
4816
4817 /* the left player */
4818 playerdisp_init(&pdisps[3],3);
4819 gtk_widget_show(pdisps[3].widget);
4820 tmpw = gtk_vbox_new(0,0);
4821 #ifndef GTK2
4822 gtk_widget_set_style(tmpw,tablestyle);
4823 #endif
4824 gtk_widget_set_name(tmpw,"table");
4825 gtk_widget_show(tmpw);
4826 gtk_box_pack_start(GTK_BOX(board),tmpw,0,0,0);
4827 #ifdef GTK2
4828 /* label for top player */
4829 if ( showwall ) {
4830 GtkWidget *l = gtk_label_new("Player 2");
4831 gtk_widget_set_name(l,"playerlabel");
4832 gtk_label_set_justify(GTK_LABEL(l),GTK_JUSTIFY_CENTER);
4833 gtk_label_set_ellipsize(GTK_LABEL(l),PANGO_ELLIPSIZE_END);
4834 pdisps[2].infolab = GTK_LABEL(l);
4835 gtk_widget_show(l);
4836 gtk_box_pack_start(GTK_BOX(tmpw),l,1,1,0);
4837 }
4838 #endif
4839 gtk_box_pack_start(GTK_BOX(tmpw),pdisps[3].widget,1,0,0);
4840 #ifdef GTK2
4841 /* label for left player */
4842 if ( showwall ) {
4843 GtkWidget *l = gtk_label_new("Player 3");
4844 gtk_widget_set_name(l,"playerlabel");
4845 gtk_label_set_justify(GTK_LABEL(l),GTK_JUSTIFY_CENTER);
4846 // gtk_label_set_ellipsize(GTK_LABEL(l),PANGO_ELLIPSIZE_END);
4847 pdisps[3].infolab = GTK_LABEL(l);
4848 gtk_widget_show(l);
4849 if ( rotatelabels ) gtk_label_set_angle(GTK_LABEL(l),270);
4850 gtk_box_pack_start(GTK_BOX(tmpw),l,1,1,0);
4851 }
4852 #endif
4853
4854 /* the inner box */
4855 inboard = gtk_vbox_new(0,0);
4856 #ifndef GTK2
4857 gtk_widget_set_style(inboard,tablestyle);
4858 #endif
4859 gtk_widget_set_name(inboard,"table");
4860 gtk_widget_show(inboard);
4861 /* The inner box needs to expand to fit the outer, or else
4862 it will shrink to fit the top and bottom players */
4863 gtk_box_pack_start(GTK_BOX(board),inboard,1,1,0);
4864
4865 /* the right player */
4866 playerdisp_init(&pdisps[1],1);
4867 gtk_widget_show(pdisps[1].widget);
4868 tmpw = gtk_vbox_new(0,0);
4869 #ifndef GTK2
4870 gtk_widget_set_style(tmpw,tablestyle);
4871 #endif
4872 gtk_widget_set_name(tmpw,"table");
4873 gtk_widget_show(tmpw);
4874 gtk_box_pack_start(GTK_BOX(board),tmpw,0,0,0);
4875 #ifdef GTK2
4876 /* label for right player */
4877 if ( showwall ) {
4878 GtkWidget *l = gtk_label_new("Player 1");
4879 gtk_widget_set_name(l,"playerlabel");
4880 gtk_label_set_justify(GTK_LABEL(l),GTK_JUSTIFY_CENTER);
4881 // gtk_label_set_ellipsize(GTK_LABEL(l),PANGO_ELLIPSIZE_END);
4882 pdisps[1].infolab = GTK_LABEL(l);
4883 gtk_widget_show(l);
4884 if ( rotatelabels ) gtk_label_set_angle(GTK_LABEL(l),90);
4885 gtk_box_pack_start(GTK_BOX(tmpw),l,1,1,0);
4886 }
4887 #endif
4888 gtk_box_pack_start(GTK_BOX(tmpw),pdisps[1].widget,1,0,0);
4889 #ifdef GTK2
4890 /* label for bottom player */
4891 if ( showwall ) {
4892 GtkWidget *l = gtk_label_new("Player 0");
4893 gtk_widget_set_name(l,"playerlabel");
4894 gtk_label_set_justify(GTK_LABEL(l),GTK_JUSTIFY_CENTER);
4895 gtk_label_set_ellipsize(GTK_LABEL(l),PANGO_ELLIPSIZE_END);
4896 pdisps[0].infolab = GTK_LABEL(l);
4897 gtk_widget_show(l);
4898 gtk_box_pack_start(GTK_BOX(tmpw),l,1,1,0);
4899 }
4900 #endif
4901
4902 /* the top player */
4903 playerdisp_init(&pdisps[2],2);
4904 gtk_widget_show(pdisps[2].widget);
4905 gtk_box_pack_start(GTK_BOX(inboard),pdisps[2].widget,0,0,0);
4906
4907 /* the discard area */
4908 discard_area = lazy_fixed_new();
4909 #ifndef GTK2
4910 gtk_widget_set_style(discard_area,tablestyle);
4911 #endif
4912 gtk_widget_set_name(discard_area,"table");
4913 gtk_container_set_border_width(GTK_CONTAINER(discard_area),dialog_border_width);
4914 gtk_widget_show(discard_area);
4915 gtk_box_pack_start(GTK_BOX(inboard),discard_area,1,1,0);
4916
4917 /* the bottom player (us) */
4918 playerdisp_init(&pdisps[0],0);
4919 gtk_widget_show(pdisps[0].widget);
4920 gtk_box_pack_end(GTK_BOX(inboard),pdisps[0].widget,0,0,0);
4921
4922 /* we'll install two signals on the player widget, which deal
4923 with changing the tile selection, and likewise to move */
4924 #ifndef GTK2
4925 gtk_signal_connect(GTK_OBJECT(pdisps[0].widget),"selectleft",
4926 (GtkSignalFunc)change_tile_selection,(gpointer)0);
4927 gtk_signal_connect(GTK_OBJECT(pdisps[0].widget),"selectright",
4928 (GtkSignalFunc)change_tile_selection,(gpointer)1);
4929 gtk_signal_connect(GTK_OBJECT(pdisps[0].widget),"moveleft",
4930 (GtkSignalFunc)move_selected_tile,(gpointer)0);
4931 gtk_signal_connect(GTK_OBJECT(pdisps[0].widget),"moveright",
4932 (GtkSignalFunc)move_selected_tile,(gpointer)1);
4933 #else
4934 /* install on the top window */
4935 gtk_signal_connect(GTK_OBJECT(topwindow),"selectleft",
4936 (GtkSignalFunc)change_tile_selection,(gpointer)0);
4937 gtk_signal_connect(GTK_OBJECT(topwindow),"selectright",
4938 (GtkSignalFunc)change_tile_selection,(gpointer)1);
4939 gtk_signal_connect(GTK_OBJECT(topwindow),"moveleft",
4940 (GtkSignalFunc)move_selected_tile,(gpointer)0);
4941 gtk_signal_connect(GTK_OBJECT(topwindow),"moveright",
4942 (GtkSignalFunc)move_selected_tile,(gpointer)1);
4943 #endif
4944
4945 #ifndef GTK2
4946 /* now create the accelerator group and install it on the top
4947 window */
4948 tile_accel = gtk_accel_group_new();
4949 gtk_accel_group_add(tile_accel,
4950 #ifdef WIN32
4951 GDK_l,
4952 #else
4953 GDK_Left,
4954 #endif
4955 0,0,GTK_OBJECT(pdisps[0].widget),"selectleft");
4956 gtk_accel_group_add(tile_accel,
4957 #ifdef WIN32
4958 GDK_r,
4959 #else
4960 GDK_Right,
4961 #endif
4962 0,0,GTK_OBJECT(pdisps[0].widget),"selectright");
4963 gtk_accel_group_add(tile_accel,
4964 #ifdef WIN32
4965 GDK_l,
4966 #else
4967 GDK_Left,
4968 #endif
4969 GDK_SHIFT_MASK,0,GTK_OBJECT(pdisps[0].widget),"moveleft");
4970 gtk_accel_group_add(tile_accel,
4971 #ifdef WIN32
4972 GDK_r,
4973 #else
4974 GDK_Right,
4975 #endif
4976 GDK_SHIFT_MASK,0,GTK_OBJECT(pdisps[0].widget),"moveright");
4977 gtk_window_add_accel_group(GTK_WINDOW(topwindow),tile_accel);
4978 #endif
4979
4980 /* At this point, all the boxes are where we want them.
4981 So to prevent everything being completely mucked up, we now
4982 lock the sizes of the player boxes.
4983 This works, without showing the window, because we're asking
4984 the widgets what they want.
4985 */
4986 for (i=0;i<4;i++) {
4987 GtkRequisition r;
4988 GtkWidget *w;
4989 w = pdisps[i].widget;
4990 gtk_widget_size_request(w,&r);
4991 gtk_widget_set_usize(w,r.width,r.height);
4992 }
4993
4994 /* if we are showing the wall, create it */
4995 if ( showwall ) create_wall();
4996
4997 /* now we can force square aspect ratio */
4998 if ( square_aspect ) {
4999 GtkRequisition r;
5000 GtkWidget *w;
5001 w = board;
5002 gtk_widget_size_request(w,&r);
5003 gtk_widget_set_usize(w,r.width,r.width);
5004 }
5005
5006 /* these dialogs may be part of the top level window,
5007 so they get created now */
5008 create_dialogs();
5009
5010 /* create the other dialogs */
5011
5012 /* the scoring message thing */
5013 textwindow_init();
5014
5015 /* and the message window */
5016 messagewindow_init();
5017
5018 /* and the scoring history window */
5019 scorehistory_init();
5020
5021 /* and the warning window */
5022 warningwindow_init();
5023
5024 /* and the status window */
5025 status_init();
5026
5027 /* and the open dialog */
5028 {
5029 char idt[10];
5030 idt[0] = 0;
5031 sprintf(idt,"%d",our_id);
5032 open_dialog_init(idt,name);
5033 }
5034
5035
5036 /* It's essential that we map the main window now,
5037 since if we are resuming an interrupted game, messages
5038 will arrive quickly; and the playerdisp_discard
5039 needs to know the allocation of the discard area.
5040 If we look at the wrong time, before it's mapped, we get
5041 -1: the discard_area won't get its allocation until after
5042 the topwindow has got its real size from the WM */
5043 /* Note. In GTK1, it was harmless, but not effective, to do
5044 gtk_widget_show_now. In GTK2, not only doesn't it work as desired,
5045 it messes things up. So we only show, and then process events.*/
5046 gtk_widget_show(topwindow);
5047
5048 /* Now we have to wait until stuff is mapped and placed, and I
5049 don't know how that happens.
5050 So we'll just iterate through the main loop until we
5051 find some sensible values in here. It seems that the non
5052 sensible values include 1. */
5053 while ( discard_area->allocation.width <= 1 )
5054 gtk_main_iteration();
5055
5056 discard_area_alloc = discard_area->allocation;
5057
5058 /* dissuade the discard area from shrinking */
5059 gtk_widget_set_usize(discard_area,discard_area_alloc.width,discard_area_alloc.height);
5060
5061 /* now expand the left and right players to match the bottom */
5062 /* now we set the vertical size of the left and right players to
5063 match the horizontal size of the top and bottom */
5064 gtk_widget_set_usize(pdisps[1].widget,
5065 pdisps[1].widget->allocation.width,
5066 pdisps[0].widget->allocation.width);
5067 gtk_widget_set_usize(pdisps[3].widget,
5068 pdisps[3].widget->allocation.width,
5069 pdisps[0].widget->allocation.width);
5070
5071 /* The window is now built, and sizes calculated We now
5072 clear everything. I don't quite know what the order of events
5073 is here, but it seems to work with no flashing. */
5074 for (i=0;i<4;i++) playerdisp_update(&pdisps[i],NULL);
5075
5076 /* if there's a wall, and a game, make the wall match reality.
5077 We should check that there is a non-zero wall! */
5078 if ( the_game && showwall && the_game->wall.size > 0 ) {
5079 int i;
5080 for ( i = 0; i < the_game->wall.live_used; i++ ) {
5081 gtk_widget_destroy(wall[wall_game_to_board(i)]);
5082 wall[wall_game_to_board(i)] = NULL;
5083 }
5084 for ( i = the_game->wall.size;
5085 i >= the_game->wall.dead_end; i-- )
5086 adjust_wall_loose(i);
5087 }
5088
5089 /* if there's a game, remake the discards */
5090 playerdisp_discard_recompute = 1;
5091 if ( the_game ) {
5092 int i;
5093 for ( i = 0; i < discard_history.count; i++ )
5094 playerdisp_discard(discard_history.hist[i].pd,discard_history.hist[i].t);
5095 }
5096
5097 /* set up appropriate dialogs */
5098 setup_dialogs();
5099
5100 /* add the callbacks. It's important that we do this after
5101 doing main loop processing to fix the discard area! */
5102
5103 /* and if there's a current connection, set callbacks
5104 (also on stdin if passing stdin) */
5105 control_server_processing(1);
5106
5107 }
5108
5109 /* This function destroys the display and frees everything created
5110 for it, preparatory to creating it again with new parameters */
destroy_display(void)5111 void destroy_display(void)
5112 {
5113 int i,ori;
5114 const char *code;
5115
5116 /* first of all remove the input callbacks, since we don't
5117 want to process them with no display */
5118 control_server_processing(0);
5119 if ( pass_stdin ) {
5120 gtk_input_remove(stdin_callback_tag);
5121 stdin_callback_tag = 0;
5122 }
5123
5124 /* destroy and zero all the dialogs known publicly */
5125 destroy_dialogs();
5126
5127 /* destroy and clear the wall */
5128 clear_wall();
5129
5130 /* save the position of the top level window */
5131 gdk_window_get_deskrelative_origin(topwindow->window,&top_x,&top_y);
5132 top_pos_set = 1;
5133
5134 /* destroy the message, warning, status and scoring windows (which may
5135 be top-level, or may be in the topwindow */
5136 if ( messagewindow ) gtk_widget_destroy(messagewindow); messagewindow = NULL;
5137 if ( status_window ) gtk_widget_destroy(status_window); status_window = NULL;
5138 if ( textwindow ) gtk_widget_destroy(textwindow); textwindow = NULL;
5139 if ( warningwindow ) gtk_widget_destroy(warningwindow); warningwindow = NULL;
5140
5141 gtk_widget_destroy(topwindow); /* kills most everything else */
5142
5143 /* get rid of the tile pixmaps */
5144 for ( i = -1 ; i < MaxTile; i++ ) {
5145 code = tile_code(i);
5146 /* only destroy the error pixmaps once ... */
5147 if ( tilepixmaps[0][i] && (i < 0 || strcmp(code,"XX") != 0) ) {
5148 for ( ori = 0; ori < 4; ori++ ) {
5149 gdk_pixmap_unref(tilepixmaps[ori][i]);
5150 tilepixmaps[ori][i] = 0;
5151 }
5152 }
5153 }
5154 for ( i = 0; i < 4 ; i++ ) {
5155 for ( ori = 0; ori < 4; ori++ ) {
5156 gdk_pixmap_unref(tongpixmaps[ori][i]);
5157 tongpixmaps[ori][i] = 0;
5158 }
5159 }
5160 /* not necessary to destroy dialogs, as create_dialogs does that */
5161
5162 }
5163
5164 /* control server processing: disables or enables the processing
5165 of input from the game server */
control_server_processing(int on)5166 void control_server_processing(int on) {
5167 if ( on && the_game && the_game->fd != (int)INVALID_SOCKET ) {
5168 #ifdef WIN32
5169 if ( the_game->fd == STDOUT_FILENO ) {
5170 server_callback_tag
5171 = gdk_input_add(STDIN_FILENO,GDK_INPUT_READ,server_input_callback,NULL);
5172 } else {
5173 /* the socket routines have already converted the socket into
5174 a GIOChannel *. */
5175 server_callback_tag
5176 = g_io_add_watch((GIOChannel *)the_game->fd,
5177 G_IO_IN|G_IO_ERR,gcallback,NULL);
5178 }
5179 #else
5180 if ( the_game->fd == STDOUT_FILENO ) {
5181 server_callback_tag
5182 = gdk_input_add(STDIN_FILENO,GDK_INPUT_READ,server_input_callback,NULL);
5183 } else {
5184 server_callback_tag
5185 = gdk_input_add(the_game->fd,GDK_INPUT_READ,server_input_callback,NULL);
5186 }
5187 #endif
5188 if ( pass_stdin ) {
5189 stdin_callback_tag = gdk_input_add(0,GDK_INPUT_READ,stdin_input_callback,NULL);
5190 }
5191 } else {
5192 if ( server_callback_tag ) {
5193 gdk_input_remove(server_callback_tag);
5194 server_callback_tag = 0;
5195 }
5196 if ( stdin_callback_tag ) {
5197 gdk_input_remove(stdin_callback_tag);
5198 stdin_callback_tag = 0;
5199 }
5200 }
5201 }
5202
5203 /* This function moves the tile selection for our player left
5204 (if data = 0) or right. If no tile is selected, it selects
5205 the leftmost or rightmost (not sure whether this is the right
5206 way round...) */
change_tile_selection(GtkWidget * w UNUSED,gpointer right)5207 static void change_tile_selection(GtkWidget *w UNUSED,gpointer right)
5208 {
5209 int i = selected_button;
5210 if ( !our_player || our_player->num_concealed == 0 ) return;
5211 if ( i < 0 ) {
5212 i = right ? 0 : our_player->num_concealed - 1 ;
5213 } else {
5214 if ( !right ) i = (i == 0) ? our_player->num_concealed - 1 : i - 1;
5215 else i = (i >= our_player->num_concealed - 1) ? 0 : i + 1;
5216 }
5217 conc_callback(pdisps[0].conc[i],(gpointer)(intptr_t)(i | 128));
5218 }
5219
5220 /* This function moves the selected tile left or right in the hand */
move_selected_tile(GtkWidget * w UNUSED,gpointer right)5221 static void move_selected_tile(GtkWidget *w UNUSED,gpointer right)
5222 {
5223 int i = selected_button;
5224 if ( !our_player || our_player->num_concealed == 0 ) return;
5225 if ( i < 0 ) return;
5226 if ( right ) {
5227 player_reorder_tile(our_player,i,
5228 (i == our_player->num_concealed-1) ? 0 : i+1);
5229 } else {
5230 player_reorder_tile(our_player,i,
5231 (i == 0) ? our_player->num_concealed-1 : i-1);
5232 }
5233 playerdisp_update_concealed(&pdisps[0],HiddenTile);
5234 change_tile_selection(w,right);
5235 }
5236
5237 /* Would you ****ing believe it? GTK thinks you aren't allowed to
5238 use arrow keys as accelerators, because it knows best about what
5239 to do with them.
5240 Well, **** that for a lark. We'll override some internal code
5241 right here. */
5242 /* This stuff (a) doesn't work in GTK2, because they have prevented the
5243 function from being overridden, (b) is unnecessary, because the desired
5244 effect can be obtained simply by binding Left and Right in the top window.
5245 Pity that wasn't available in GTK1
5246 */
5247
5248 #ifndef GTK2
5249 gboolean
gtk_accelerator_valid(guint keyval,GdkModifierType modifiers)5250 gtk_accelerator_valid (guint keyval,
5251 GdkModifierType modifiers)
5252 {
5253 static const guint invalid_accelerator_vals[] = {
5254 GDK_BackSpace, GDK_Delete, GDK_KP_Delete,
5255 GDK_Shift_L, GDK_Shift_R, GDK_Shift_Lock, GDK_Caps_Lock, GDK_ISO_Lock,
5256 GDK_Control_L, GDK_Control_R, GDK_Meta_L, GDK_Meta_R,
5257 GDK_Alt_L, GDK_Alt_R, GDK_Super_L, GDK_Super_R, GDK_Hyper_L, GDK_Hyper_R,
5258 GDK_Mode_switch, GDK_Num_Lock, GDK_Multi_key,
5259 GDK_Scroll_Lock, GDK_Sys_Req,
5260 /* I don't care what gtk thinks, I want to use these
5261 GDK_Up, GDK_Down, GDK_Left, GDK_Right, GDK_Tab, GDK_ISO_Left_Tab,
5262 GDK_KP_Up, GDK_KP_Down, GDK_KP_Left, GDK_KP_Right, GDK_KP_Tab,
5263 */
5264 GDK_First_Virtual_Screen, GDK_Prev_Virtual_Screen,
5265 GDK_Next_Virtual_Screen, GDK_Last_Virtual_Screen,
5266 GDK_Terminate_Server, GDK_AudibleBell_Enable,
5267 0
5268 };
5269 const guint *ac_val;
5270
5271 modifiers &= GDK_MODIFIER_MASK;
5272
5273 if (keyval <= 0xFF)
5274 return keyval >= 0x20;
5275
5276 ac_val = invalid_accelerator_vals;
5277 while (*ac_val)
5278 {
5279 if (keyval == *ac_val++)
5280 return FALSE;
5281 }
5282
5283 return TRUE;
5284 }
5285 #endif
5286
iconify_window(GtkWidget * w)5287 static void iconify_window(GtkWidget *w)
5288 {
5289 #ifdef GTK2
5290 if ( ! GTK_IS_WINDOW(w) ) return;
5291 return gtk_window_iconify(GTK_WINDOW(w));
5292 #else
5293 #ifndef WIN32
5294 XEvent ev;
5295 static guint32 atom = 0;
5296 if ( ! GTK_IS_WINDOW(w) ) return;
5297 if ( atom == 0 ) {
5298 atom = gdk_atom_intern("WM_CHANGE_STATE",FALSE);
5299 }
5300 ev.xclient.type = ClientMessage;
5301 ev.xclient.display = gdk_display;
5302 ev.xclient.format = 32;
5303 ev.xclient.data.l[0] = 3; /* iconic state */
5304 ev.xclient.message_type = (guint32) atom;
5305 ev.xclient.window = GDK_WINDOW_XWINDOW(w->window);
5306 gdk_send_xevent(gdk_root_window,False,SubstructureRedirectMask|SubstructureNotifyMask, &ev);
5307 #endif
5308 #endif
5309 }
5310
uniconify_window(GtkWidget * w)5311 static void uniconify_window(GtkWidget *w)
5312 {
5313 if ( ! GTK_IS_WINDOW(w) ) return;
5314 /* if the widget is not visible in the gtk sense, somebody's
5315 closed it while we thought it should be iconified.
5316 So don't raise it. */
5317 if ( ! GTK_WIDGET_VISIBLE(w) ) return;
5318 gdk_window_show(w->window);
5319 }
5320