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