/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* * Copyright 2000-2015 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef PLATFORM_GTK #include #include #include #include #include #include #include #include #include #include #include #include #ifndef CLIENT_ONLY /* # include */ #endif #include #include #include #include #include "main.h" #include "linuxmain.h" #include "linuxutl.h" #include "linuxbt.h" #include "linuxudp.h" #include "linuxsms.h" #include "gtkmain.h" #include "draw.h" #include "game.h" #include "movestak.h" #include "strutils.h" #include "dbgutil.h" #include "gtkask.h" #include "gtkinvit.h" #include "gtkaskm.h" #include "gtkchat.h" #include "gtknewgame.h" #include "gtkletterask.h" #include "gtkpasswdask.h" #include "gtkntilesask.h" #include "gtkaskdict.h" #include "linuxdict.h" /* #include "undo.h" */ #include "gtkdraw.h" #include "memstream.h" #include "gamesdb.h" #include "relaycon.h" #include "mqttcon.h" /* static guint gtkSetupClientSocket( GtkGameGlobals* globals, int sock ); */ static void setCtrlsForTray( GtkGameGlobals* globals ); static void setZoomButtons( GtkGameGlobals* globals, XP_Bool* inOut ); static void disenable_buttons( GtkGameGlobals* globals ); static GtkWidget* addButton( GtkWidget* hbox, gchar* label, GCallback func, GtkGameGlobals* globals ); static void handle_invite_button( GtkWidget* widget, GtkGameGlobals* globals ); static void gtkShowFinalScores( const GtkGameGlobals* globals, XP_Bool ignoreTimeout ); static void send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers, const CommsAddrRec* addrs ); #define GTK_TRAY_HT_ROWS 3 #if 0 static XWStreamCtxt* lookupClientStream( GtkGameGlobals* globals, int sock ) { short i; for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { ClientStreamRec* rec = &globals->clientRecs[i]; if ( rec->sock == sock ) { XP_ASSERT( rec->stream != NULL ); return rec->stream; } } XP_ASSERT( i < MAX_NUM_PLAYERS ); return NULL; } /* lookupClientStream */ static void rememberClient( GtkGameGlobals* globals, guint key, int sock, XWStreamCtxt* stream ) { short i; for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { ClientStreamRec* rec = &globals->clientRecs[i]; if ( rec->stream == NULL ) { XP_ASSERT( stream != NULL ); rec->stream = stream; rec->key = key; rec->sock = sock; break; } } XP_ASSERT( i < MAX_NUM_PLAYERS ); } /* rememberClient */ #endif static void gtkSetAltState( GtkGameGlobals* globals, guint state ) { globals->altKeyDown = (state & (GDK_MOD1_MASK|GDK_SHIFT_MASK|GDK_CONTROL_MASK)) != 0; } static gint button_press_event( GtkWidget* XP_UNUSED(widget), GdkEventButton *event, GtkGameGlobals* globals ) { XP_Bool redraw, handled; gtkSetAltState( globals, event->state ); if ( !globals->mouseDown ) { globals->mouseDown = XP_TRUE; redraw = board_handlePenDown( globals->cGlobals.game.board, NULL_XWE, event->x, event->y, &handled ); if ( redraw ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); disenable_buttons( globals ); } } return 1; } /* button_press_event */ static gint motion_notify_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, GtkGameGlobals* globals ) { XP_Bool handled; gtkSetAltState( globals, event->state ); if ( globals->mouseDown ) { handled = board_handlePenMove( globals->cGlobals.game.board, NULL_XWE, event->x, event->y ); if ( handled ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); disenable_buttons( globals ); } } else { handled = XP_FALSE; } return handled; } /* motion_notify_event */ static gint button_release_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, GtkGameGlobals* globals ) { XP_Bool redraw; gtkSetAltState( globals, event->state ); if ( globals->mouseDown ) { redraw = board_handlePenUp( globals->cGlobals.game.board, NULL_XWE, event->x, event->y ); if ( redraw ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); disenable_buttons( globals ); } globals->mouseDown = XP_FALSE; } return 1; } /* button_release_event */ #ifdef KEY_SUPPORT static XP_Key evtToXPKey( GdkEventKey* event, XP_Bool* movesCursorP ) { XP_Key xpkey = XP_KEY_NONE; XP_Bool movesCursor = XP_FALSE; guint keyval = event->keyval; switch( keyval ) { #ifdef KEYBOARD_NAV case GDK_KEY_Return: xpkey = XP_RETURN_KEY; break; case GDK_KEY_space: xpkey = XP_RAISEFOCUS_KEY; break; case GDK_KEY_Left: xpkey = XP_CURSOR_KEY_LEFT; movesCursor = XP_TRUE; break; case GDK_KEY_Right: xpkey = XP_CURSOR_KEY_RIGHT; movesCursor = XP_TRUE; break; case GDK_KEY_Up: xpkey = XP_CURSOR_KEY_UP; movesCursor = XP_TRUE; break; case GDK_KEY_Down: xpkey = XP_CURSOR_KEY_DOWN; movesCursor = XP_TRUE; break; #endif case GDK_KEY_BackSpace: XP_LOGF( "... it's a DEL" ); xpkey = XP_CURSOR_KEY_DEL; break; default: keyval = keyval & 0x00FF; /* mask out gtk stuff */ if ( isalpha( keyval ) ) { xpkey = toupper(keyval); break; #ifdef NUMBER_KEY_AS_INDEX } else if ( isdigit( keyval ) ) { xpkey = keyval; break; #endif } } *movesCursorP = movesCursor; return xpkey; } /* evtToXPKey */ #ifdef KEYBOARD_NAV static gint key_press_event( GtkWidget* XP_UNUSED(widget), GdkEventKey* event, GtkGameGlobals* globals ) { XP_Bool handled = XP_FALSE; XP_Bool movesCursor; XP_Key xpkey = evtToXPKey( event, &movesCursor ); gtkSetAltState( globals, event->state ); if ( xpkey != XP_KEY_NONE ) { XP_Bool draw = globals->keyDown ? board_handleKeyRepeat( globals->cGlobals.game.board, NULL_XWE, xpkey, &handled ) : board_handleKeyDown( globals->cGlobals.game.board, NULL_XWE, xpkey, &handled ); if ( draw ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); } } globals->keyDown = XP_TRUE; return 1; } #endif static gint key_release_event( GtkWidget* XP_UNUSED(widget), GdkEventKey* event, GtkGameGlobals* globals ) { XP_Bool handled = XP_FALSE; XP_Bool movesCursor; XP_Key xpkey = evtToXPKey( event, &movesCursor ); gtkSetAltState( globals, event->state ); if ( xpkey != XP_KEY_NONE ) { XP_Bool draw; draw = board_handleKeyUp( globals->cGlobals.game.board, NULL_XWE, xpkey, &handled ); #ifdef KEYBOARD_NAV if ( movesCursor && !handled ) { BoardObjectType order[] = { OBJ_SCORE, OBJ_BOARD, OBJ_TRAY }; draw = linShiftFocus( &globals->cGlobals, xpkey, order, NULL ) || draw; } #endif if ( draw ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); } } /* XP_ASSERT( globals->keyDown ); */ #ifdef KEYBOARD_NAV globals->keyDown = XP_FALSE; #endif return handled? 1 : 0; /* gtk will do something with the key if 0 returned */ } /* key_release_event */ #endif #ifdef MEM_DEBUG # define MEMPOOL globals->cGlobals.util->mpool, #else # define MEMPOOL #endif #ifdef XWFEATURE_RELAY static void relay_status_gtk( XWEnv XP_UNUSED(xwe), void* closure, CommsRelayState state ) { XP_LOGF( "%s got status: %s", __func__, CommsRelayState2Str(state) ); GtkGameGlobals* globals = (GtkGameGlobals*)closure; CommonGlobals* cGlobals = &globals->cGlobals; if ( !!cGlobals->draw ) { cGlobals->state = state; globals->stateChar = 'A' + COMMS_RELAYSTATE_ALLCONNECTED - state; draw_gtk_status( (GtkDrawCtx*)(void*)cGlobals->draw, globals->stateChar ); } } static void relay_connd_gtk( XWEnv XP_UNUSED(xwe), void* closure, XP_UCHAR* const room, XP_Bool XP_UNUSED(reconnect), XP_U16 devOrder, XP_Bool allHere, XP_U16 nMissing ) { GtkGameGlobals* globals = (GtkGameGlobals*)closure; globals->cGlobals.nMissing = nMissing; XP_Bool skip = XP_FALSE; char buf[256]; if ( allHere ) { /* disable for now. Seeing this too often */ skip = XP_TRUE; snprintf( buf, sizeof(buf), "All expected players have joined in %s. Play!", room ); } else { if ( nMissing > 0 ) { snprintf( buf, sizeof(buf), "%dth connected to relay; waiting " "in %s for %d player[s].", devOrder, room, nMissing ); } else { /* an allHere message should be coming immediately, so no notification now. */ skip = XP_TRUE; } } if ( !skip ) { (void)gtkask_timeout( globals->window, buf, GTK_BUTTONS_OK, NULL, 500 ); } disenable_buttons( globals ); } static gint invoke_new_game( gpointer data ) { GtkGameGlobals* globals = (GtkGameGlobals*)data; new_game_impl( globals, XP_FALSE ); return 0; } static gint invoke_new_game_conns( gpointer data ) { GtkGameGlobals* globals = (GtkGameGlobals*)data; new_game_impl( globals, XP_TRUE ); return 0; } static void relay_error_gtk( XWEnv XP_UNUSED(xwe), void* closure, XWREASON relayErr ) { LOG_FUNC(); GtkGameGlobals* globals = (GtkGameGlobals*)closure; gint (*proc)( gpointer data ) = NULL; switch( relayErr ) { case XWRELAY_ERROR_NO_ROOM: case XWRELAY_ERROR_DUP_ROOM: proc = invoke_new_game_conns; break; case XWRELAY_ERROR_TOO_MANY: case XWRELAY_ERROR_BADPROTO: proc = invoke_new_game; break; case XWRELAY_ERROR_DELETED: (void)gtkask_timeout( globals->window, "relay says another device deleted game.", GTK_BUTTONS_OK, NULL, 1000 ); break; case XWRELAY_ERROR_DEADGAME: break; default: assert(0); break; } if ( !!proc ) { (void)g_idle_add( proc, globals ); } } static XP_Bool relay_sendNoConn_gtk( XWEnv XP_UNUSED(xwe), const XP_U8* msg, XP_U16 len, const XP_UCHAR* XP_UNUSED(msgNo), const XP_UCHAR* relayID, void* closure ) { GtkGameGlobals* globals = (GtkGameGlobals*)closure; XP_Bool success = XP_FALSE; CommonGlobals* cGlobals = &globals->cGlobals; LaunchParams* params = cGlobals->params; if ( params->useUdp && !cGlobals->draw ) { XP_U16 seed = comms_getChannelSeed( cGlobals->game.comms ); XP_U32 clientToken = makeClientToken( cGlobals->rowid, seed ); XP_S16 nSent = relaycon_sendnoconn( params, msg, len, relayID, clientToken ); success = nSent == len; } return success; } /* relay_sendNoConn_gtk */ #endif #ifdef RELAY_VIA_HTTP static void onJoined( void* closure, const XP_UCHAR* connname, XWHostID hid ) { GtkGameGlobals* globals = (GtkGameGlobals*)closure; XWGame* game = &globals->cGlobals.game; CommsCtxt* comms = game->comms; comms_gameJoined( comms, connname, hid ); if ( hid > 1 ) { globals->cGlobals.gi->serverRole = SERVER_ISCLIENT; server_reset( game->server, game->comms ); tryConnectToServer( &globals->cGlobals ); } } static void relay_requestJoin_gtk( void* closure, const XP_UCHAR* devID, const XP_UCHAR* room, XP_U16 nPlayersHere, XP_U16 nPlayersTotal, XP_U16 seed, XP_U16 lang ) { GtkGameGlobals* globals = (GtkGameGlobals*)closure; LaunchParams* params = globals->cGlobals.params; relaycon_join( params, devID, room, nPlayersHere, nPlayersTotal, seed, lang, onJoined, globals ); } #endif #ifdef COMMS_XPORT_FLAGSPROC static XP_U32 gtk_getFlags( XWEnv XP_UNUSED(xwe), void* closure ) { GtkGameGlobals* globals = (GtkGameGlobals*)closure; # ifdef RELAY_VIA_HTTP XP_USE( globals ); return COMMS_XPORT_FLAGS_HASNOCONN; # else return (!!globals->cGlobals.draw) ? COMMS_XPORT_FLAGS_NONE : COMMS_XPORT_FLAGS_HASNOCONN; # endif } #endif static void countChanged_gtk( XWEnv XP_UNUSED(xwe), void* closure, XP_U16 newCount, XP_Bool quashed ) { GtkGameGlobals* globals = (GtkGameGlobals*)closure; gchar buf[128]; snprintf( buf, VSIZE(buf), "pending count: %d%s", newCount, quashed?"q":""); gtk_label_set_text( GTK_LABEL(globals->countLabel), buf ); } static void setTransportProcs( TransportProcs* procs, GtkGameGlobals* globals ) { XP_ASSERT( !procs->closure ); procs->closure = globals; procs->sendMsgs = linux_send; #ifdef XWFEATURE_COMMS_INVITE procs->sendInvt = linux_send_invt; #endif #ifdef COMMS_XPORT_FLAGSPROC procs->getFlags = gtk_getFlags; #endif #ifdef COMMS_HEARTBEAT procs->reset = linux_reset; #endif #ifdef XWFEATURE_RELAY procs->rstatus = relay_status_gtk; procs->rconnd = relay_connd_gtk; procs->rerror = relay_error_gtk; procs->sendNoConn = relay_sendNoConn_gtk; # ifdef RELAY_VIA_HTTP procs->requestJoin = relay_requestJoin_gtk; # endif #endif procs->countChanged = countChanged_gtk; } #ifdef DEBUG static void drop_msg_toggle( GtkWidget* toggle, void* data ) { XP_Bool disabled = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(toggle) ); long asInt = (long)data; XP_Bool send = 0 != (asInt & 1); asInt &= ~1; DropTypeData* datum = (DropTypeData*)asInt; comms_setAddrDisabled( datum->comms, datum->typ, send, disabled ); } /* drop_msg_toggle */ static void addDropChecks( GtkGameGlobals* globals ) { CommsCtxt* comms = globals->cGlobals.game.comms; if ( !!comms ) { CommsAddrRec selfAddr; comms_getSelfAddr( comms, &selfAddr ); CommsConnType typ; for ( XP_U32 st = 0; addr_iter( &selfAddr, &typ, &st ); ) { DropTypeData* datum = &globals->dropData[typ]; datum->typ = typ; datum->comms = comms; GtkWidget* hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gchar buf[32]; snprintf( buf, sizeof(buf), "Drop %s messages", ConnType2Str( typ ) ); GtkWidget* widget = gtk_label_new( buf ); gtk_box_pack_start( GTK_BOX(hbox), widget, FALSE, TRUE, 0); gtk_widget_show( widget ); widget = gtk_check_button_new_with_label( "Incoming" ); if ( comms_getAddrDisabled( comms, typ, XP_FALSE ) ) { gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(widget), TRUE ); } g_signal_connect( widget, "toggled", G_CALLBACK(drop_msg_toggle), datum ); gtk_box_pack_start( GTK_BOX(hbox), widget, FALSE, TRUE, 0); gtk_widget_show( widget ); widget = gtk_check_button_new_with_label( "Outgoing" ); if ( comms_getAddrDisabled( comms, typ, XP_TRUE ) ) { gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(widget), TRUE ); } g_signal_connect( widget, "toggled", G_CALLBACK(drop_msg_toggle), (void*)(((long)datum) | 1) ); gtk_box_pack_start( GTK_BOX(hbox), widget, FALSE, TRUE, 0); gtk_widget_show( widget ); gtk_widget_show( hbox ); gtk_box_pack_start( GTK_BOX(globals->drop_checks_vbox), hbox, FALSE, TRUE, 0); } gtk_widget_show(globals->drop_checks_vbox); } } #else # define addDropChecks( globals ) #endif /* DEBUG */ static void formatSizeKey( gchar* key, sqlite3_int64 rowid ) { sprintf( key, KEY_WIN_LOC ":%llx", rowid ); } static void resizeFromRowid( GtkGameGlobals* globals ) { CommonGlobals* cGlobals = &globals->cGlobals; const sqlite3_int64 rowid = cGlobals->rowid; if ( 0 != rowid ) { // XP_LOGFF( "(rowid=%lld)", rowid ); gchar key[128]; formatSizeKey( key, rowid ); resizeFromSaved( globals->window, cGlobals->params->pDb, key ); globals->winSizeSet = TRUE; } } static void saveSizeRowid( GtkGameGlobals* globals ) { if ( globals->winSizeSet ) { sqlite3_int64 rowid = globals->cGlobals.rowid; gchar key[128]; formatSizeKey( key, rowid ); // XP_LOGFF( "key: %s", key ); saveSize( &globals->lastConfigure, globals->cGlobals.params->pDb, key ); } } static void createOrLoadObjects( GtkGameGlobals* globals ) { CommonGlobals* cGlobals = &globals->cGlobals; LaunchParams* params = cGlobals->params; cGlobals->draw = gtkDrawCtxtMake( globals->drawing_area, globals ); if ( linuxOpenGame( cGlobals ) ) { if ( !params->fileName && !!params->dbName ) { XP_UCHAR buf[64]; snprintf( buf, sizeof(buf), "%s / %lld", params->dbName, cGlobals->rowid ); gtk_window_set_title( GTK_WINDOW(globals->window), buf ); } addDropChecks( globals ); disenable_buttons( globals ); } } /* createOrLoadObjects */ /* Create a new backing pixmap of the appropriate size and set up contxt to * draw using that size. */ static gboolean on_drawing_configure( GtkWidget* widget, GdkEventConfigure* XP_UNUSED(event), GtkGameGlobals* globals ) { globals->gridOn = XP_TRUE; CommonGlobals* cGlobals = &globals->cGlobals; if ( cGlobals->draw == NULL ) { createOrLoadObjects( globals ); } BoardCtxt* board = cGlobals->game.board; GtkAllocation alloc; gtk_widget_get_allocation( widget, &alloc ); short bdWidth = alloc.width - (GTK_RIGHT_MARGIN + GTK_BOARD_LEFT_MARGIN); short bdHeight = alloc.height - (GTK_TOP_MARGIN + GTK_BOTTOM_MARGIN) - GTK_MIN_TRAY_SCALEV - GTK_BOTTOM_MARGIN; XP_ASSERT( !cGlobals->params->verticalScore ); /* not supported */ BoardDims dims; board_figureLayout( board, NULL_XWE, cGlobals->gi, GTK_BOARD_LEFT, GTK_HOR_SCORE_TOP, bdWidth, bdHeight, 110, 150, 200, bdWidth-25, 16, 16, XP_FALSE, &dims ); board_applyLayout( board, NULL_XWE, &dims ); setCtrlsForTray( globals ); board_invalAll( board ); XP_Bool inOut[2]; board_zoom( board, NULL_XWE, 0, inOut ); setZoomButtons( globals, inOut ); return FALSE; } /* on_drawing_configure */ static gboolean on_window_configure( GtkWidget* XP_UNUSED(widget), GdkEventConfigure* event, GtkGameGlobals* globals ) { globals->lastConfigure = *event; // saveSizeRowid( globals ); return FALSE; } void destroy_board_window( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { LOG_FUNC(); if ( !!globals->cGlobals.game.comms ) { comms_stop( globals->cGlobals.game.comms #ifdef XWFEATURE_RELAY , NULL_XWE #endif ); } linuxSaveGame( &globals->cGlobals ); saveSizeRowid( globals ); windowDestroyed( globals ); } static void on_board_window_shown( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { LOG_FUNC(); CommonGlobals* cGlobals = &globals->cGlobals; if ( server_getGameIsOver( cGlobals->game.server ) ) { gtkShowFinalScores( globals, XP_TRUE ); } resizeFromRowid( globals ); } /* on_board_window_shown */ static void cleanup( GtkGameGlobals* globals ) { CommonGlobals* cGlobals = &globals->cGlobals; linuxSaveGame( cGlobals ); if ( 0 < cGlobals->idleID ) { g_source_remove( cGlobals->idleID ); } cancelTimers( cGlobals ); #ifdef XWFEATURE_BLUETOOTH linux_bt_close( cGlobals ); #endif #ifdef XWFEATURE_SMS // linux_sms_close( cGlobals ); #endif #ifdef XWFEATURE_IP_DIRECT linux_udp_close( cGlobals ); #endif #ifdef XWFEATURE_RELAY linux_close_socket( cGlobals ); #endif game_dispose( &cGlobals->game, NULL_XWE ); gi_disposePlayerInfo( MEMPOOL cGlobals->gi ); linux_util_vt_destroy( cGlobals->util ); free( cGlobals->util ); } /* cleanup */ GtkWidget* makeAddSubmenu( GtkWidget* menubar, gchar* label ) { GtkWidget* submenu; GtkWidget* item; item = gtk_menu_item_new_with_label( label ); gtk_menu_shell_append( GTK_MENU_SHELL(menubar), item ); submenu = gtk_menu_new(); gtk_menu_item_set_submenu( GTK_MENU_ITEM(item), submenu ); gtk_widget_show(item); return submenu; } /* makeAddSubmenu */ static void tile_values_impl( GtkGameGlobals* globals, bool full ) { CommonGlobals* cGlobals = &globals->cGlobals; if ( !!cGlobals->game.server ) { XWStreamCtxt* stream = mem_stream_make( MEMPOOL cGlobals->params->vtMgr, globals, CHANNEL_NONE, catOnClose, NULL_XWE ); server_formatDictCounts( cGlobals->game.server, NULL_XWE, stream, 5, full ); stream_putU8( stream, '\n' ); stream_destroy( stream ); } } /* tile_values */ static void tile_values( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { tile_values_impl( globals, false ); } static void tile_values_full( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { tile_values_impl( globals, true ); } static void game_history( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { catGameHistory( &globals->cGlobals ); } /* game_history */ #ifdef TEXT_MODEL static void dump_board( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { if ( !!globals->cGlobals.game.model ) { XWStreamCtxt* stream = mem_stream_make( MEMPOOL globals->cGlobals.params->vtMgr, globals, CHANNEL_NONE, catOnClose, NULL_XWE ); model_writeToTextStream( globals->cGlobals.game.model, stream ); stream_destroy( stream ); } } #endif static void final_scores( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { XP_Bool gameOver = server_getGameIsOver( globals->cGlobals.game.server ); if ( gameOver ) { catFinalScores( &globals->cGlobals, -1 ); } else if ( GTK_RESPONSE_YES == gtkask( globals->window, "Are you sure you want to resign?", GTK_BUTTONS_YES_NO, NULL ) ) { globals->cGlobals.manualFinal = XP_TRUE; server_endGame( globals->cGlobals.game.server, NULL_XWE ); gameOver = TRUE; } /* the end game listener will take care of printing the final scores */ } /* final_scores */ static void game_info( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { CommsAddrRec selfAddr; comms_getSelfAddr( globals->cGlobals.game.comms, &selfAddr ); /* Anything to do if OK is clicked? Changed names etc. already saved. Try server_do in case one's become a robot. */ CurGameInfo* gi = globals->cGlobals.gi; if ( gtkNewGameDialog( globals, gi, &selfAddr, XP_FALSE, XP_FALSE ) ) { if ( server_do( globals->cGlobals.game.server, NULL_XWE ) ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); } } } static void load_game( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* XP_UNUSED(globals) ) { XP_ASSERT(0); } /* load_game */ static void save_game( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* XP_UNUSED(globals) ) { XP_ASSERT(0); } /* save_game */ #ifdef XWFEATURE_CHANGEDICT static void change_dictionary( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { CommonGlobals* cGlobals = &globals->cGlobals; LaunchParams* params = cGlobals->params; GSList* dicts = listDicts( params ); gchar buf[265]; gchar* name = gtkaskdict( dicts, buf, VSIZE(buf) ); if ( !!name ) { DictionaryCtxt* dict = linux_dictionary_make( MPPARM(cGlobals->util->mpool) NULL_XWE, params, name, params->useMmap ); game_changeDict( MPPARM(cGlobals->util->mpool) &cGlobals->game, NULL_XWE, cGlobals->gi, dict ); } g_slist_free( dicts ); } /* change_dictionary */ #endif static void handle_undo( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* XP_UNUSED(globals) ) { } /* handle_undo */ static void handle_redo( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* XP_UNUSED(globals) ) { } /* handle_redo */ #ifdef FEATURE_TRAY_EDIT static void handle_trayEditToggle( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* XP_UNUSED(globals), XP_Bool XP_UNUSED(on) ) { } /* handle_trayEditToggle */ static void handle_trayEditToggle_on( GtkWidget* widget, GtkGameGlobals* globals ) { handle_trayEditToggle( widget, globals, XP_TRUE ); } static void handle_trayEditToggle_off( GtkWidget* widget, GtkGameGlobals* globals ) { handle_trayEditToggle( widget, globals, XP_FALSE ); } #endif static void handle_trade_cancel( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { BoardCtxt* board = globals->cGlobals.game.board; if ( board_endTrade( board ) ) { board_draw( board, NULL_XWE ); } } static void handle_resend( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { CommsCtxt* comms = globals->cGlobals.game.comms; if ( comms != NULL ) { comms_resendAll( comms, NULL_XWE, COMMS_CONN_NONE, XP_TRUE ); } } /* handle_resend */ #ifdef XWFEATURE_COMMSACK static void handle_ack( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { CommsCtxt* comms = globals->cGlobals.game.comms; if ( comms != NULL ) { comms_ackAny( comms, NULL_XWE ); } } #endif #ifdef DEBUG static void handle_commstats( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { CommsCtxt* comms = globals->cGlobals.game.comms; if ( !!comms ) { XWStreamCtxt* stream = mem_stream_make( MEMPOOL globals->cGlobals.params->vtMgr, globals, CHANNEL_NONE, catOnClose, NULL_XWE ); comms_getStats( comms, stream ); stream_destroy( stream ); } } /* handle_commstats */ #endif #ifdef MEM_DEBUG static void handle_memstats( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { XWStreamCtxt* stream = mem_stream_make( MEMPOOL globals->cGlobals.params->vtMgr, globals, CHANNEL_NONE, catOnClose, NULL_XWE ); mpool_stats( MEMPOOL stream ); stream_destroy( stream ); } /* handle_memstats */ #endif #ifdef XWFEATURE_ACTIVERECT static gint inval_board_ontimer( gpointer data ) { GtkGameGlobals* globals = (GtkGameGlobals*)data; BoardCtxt* board = globals->cGlobals.game.board; board_draw( board, NULL_XWE ); return XP_FALSE; } /* inval_board_ontimer */ static void frame_active( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { XP_Rect rect; BoardCtxt* board = globals->cGlobals.game.board; board_getActiveRect( board, &rect ); frame_active_rect( globals->draw, &rect ); board_invalRect( board, &rect ); (void)g_timeout_add( 1000, inval_board_ontimer, globals ); } #endif GtkWidget* createAddItem( GtkWidget* parent, gchar* label, GCallback handlerFunc, gpointer closure ) { GtkWidget* item = gtk_menu_item_new_with_label( label ); if ( handlerFunc != NULL ) { g_signal_connect( item, "activate", G_CALLBACK(handlerFunc), closure ); } gtk_menu_shell_append( GTK_MENU_SHELL(parent), item ); gtk_widget_show( item ); return item; } /* createAddItem */ static GtkWidget* makeMenus( GtkGameGlobals* globals ) { GtkWidget* menubar = gtk_menu_bar_new(); GtkWidget* fileMenu; fileMenu = makeAddSubmenu( menubar, "File" ); (void)createAddItem( fileMenu, "Tile values", (GCallback)tile_values, globals ); (void)createAddItem( fileMenu, "Tile values full", (GCallback)tile_values_full, globals ); (void)createAddItem( fileMenu, "Game history", (GCallback)game_history, globals ); #ifdef TEXT_MODEL (void)createAddItem( fileMenu, "Dump board", (GCallback)dump_board, globals ); #endif (void)createAddItem( fileMenu, "Final scores", (GCallback)final_scores, globals ); (void)createAddItem( fileMenu, "Game info", (GCallback)game_info, globals ); (void)createAddItem( fileMenu, "Load game", (GCallback)load_game, globals ); (void)createAddItem( fileMenu, "Save game", (GCallback)save_game, globals ); #ifdef XWFEATURE_CHANGEDICT (void)createAddItem( fileMenu, "Change dictionary", (GCallback)change_dictionary, globals ); #endif (void)createAddItem( fileMenu, "Cancel trade", (GCallback)handle_trade_cancel, globals ); fileMenu = makeAddSubmenu( menubar, "Edit" ); (void)createAddItem( fileMenu, "Undo", (GCallback)handle_undo, globals ); (void)createAddItem( fileMenu, "Redo", (GCallback)handle_redo, globals ); #ifdef FEATURE_TRAY_EDIT (void)createAddItem( fileMenu, "Allow tray edit", (GCallback)handle_trayEditToggle_on, globals ); (void)createAddItem( fileMenu, "Dis-allow tray edit", (GCallback)handle_trayEditToggle_off, globals ); #endif fileMenu = makeAddSubmenu( menubar, "Network" ); (void)createAddItem( fileMenu, "Resend", (GCallback)handle_resend, globals ); #ifdef XWFEATURE_COMMSACK (void)createAddItem( fileMenu, "ack any", (GCallback)handle_ack, globals ); #endif # ifdef DEBUG (void)createAddItem( fileMenu, "Stats", (GCallback)handle_commstats, globals ); # endif #ifdef MEM_DEBUG (void)createAddItem( fileMenu, "Mem stats", (GCallback)handle_memstats, globals ); #endif #ifdef XWFEATURE_ACTIVERECT fileMenu = makeAddSubmenu( menubar, "Test" ); (void)createAddItem( fileMenu, "Frame active area", (GCallback)frame_active, globals ); #endif /* (void)createAddItem( fileMenu, "Print board", */ /* GTK_SIGNAL_FUNC(handle_print_board), globals ); */ /* listAllGames( menubar, argc, argv, globals ); */ gtk_widget_show( menubar ); return menubar; } /* makeMenus */ static void disenable_buttons( GtkGameGlobals* globals ) { XWGame* game = &globals->cGlobals.game; XP_U16 nPending = server_getPendingRegs( game->server ); if ( !globals->invite_button && 0 < nPending && !server_isFromRematch( game->server ) && !!globals->buttons_hbox ) { globals->invite_button = addButton( globals->buttons_hbox, "Invite", G_CALLBACK(handle_invite_button), globals ); } GameStateInfo gsi; game_getState( &globals->cGlobals.game, NULL_XWE, &gsi ); XP_Bool canFlip = 1 < board_visTileCount( globals->cGlobals.game.board ); gtk_widget_set_sensitive( globals->flip_button, canFlip ); XP_Bool canToggle = board_canTogglePending( globals->cGlobals.game.board ); gtk_widget_set_sensitive( globals->toggle_undo_button, canToggle ); gtk_widget_set_sensitive( globals->prevhint_button, gsi.canHint ); gtk_widget_set_sensitive( globals->nexthint_button, gsi.canHint ); if ( !!globals->invite_button ) { gtk_widget_set_sensitive( globals->invite_button, 0 < nPending ); } gtk_widget_set_sensitive( globals->commit_button, gsi.curTurnSelected ); #ifdef XWFEATURE_CHAT gtk_widget_set_sensitive( globals->chat_button, gsi.canChat ); #endif gtk_widget_set_sensitive( globals->pause_button, gsi.canPause ); gtk_widget_set_sensitive( globals->unpause_button, gsi.canUnpause ); } static gboolean handle_flip_button( GtkWidget* XP_UNUSED(widget), gpointer _globals ) { GtkGameGlobals* globals = (GtkGameGlobals*)_globals; if ( board_flip( globals->cGlobals.game.board ) ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); } return TRUE; } /* handle_flip_button */ static gboolean handle_value_button( GtkWidget* XP_UNUSED(widget), gpointer closure ) { GtkGameGlobals* globals = (GtkGameGlobals*)closure; CommonGlobals* cGlobals = &globals->cGlobals; cGlobals->cp.tvType = (cGlobals->cp.tvType + 1) % TVT_N_ENTRIES; board_prefsChanged( cGlobals->game.board, &cGlobals->cp ); board_draw( cGlobals->game.board, NULL_XWE ); return TRUE; } /* handle_value_button */ static void handle_hint_button( GtkGameGlobals* globals, XP_Bool prev ) { XP_Bool redo; if ( board_requestHint( globals->cGlobals.game.board, NULL_XWE, #ifdef XWFEATURE_SEARCHLIMIT XP_FALSE, #endif prev, &redo ) ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); disenable_buttons( globals ); } } /* handle_hint_button */ static void handle_prevhint_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { handle_hint_button( globals, XP_TRUE ); } /* handle_prevhint_button */ static void handle_nexthint_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { handle_hint_button( globals, XP_FALSE ); } /* handle_nexthint_button */ static void handle_nhint_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { XP_Bool redo; board_resetEngine( globals->cGlobals.game.board ); if ( board_requestHint( globals->cGlobals.game.board, NULL_XWE, #ifdef XWFEATURE_SEARCHLIMIT XP_TRUE, #endif XP_FALSE, &redo ) ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); } } /* handle_nhint_button */ static void handle_colors_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* XP_UNUSED(globals) ) { /* XP_Bool oldVal = board_getShowColors( globals->cGlobals.game.board ); */ /* if ( board_setShowColors( globals->cGlobals.game.board, !oldVal ) ) { */ /* board_draw( globals->cGlobals.game.board ); */ /* } */ } /* handle_colors_button */ static void handle_juggle_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { if ( board_juggleTray( globals->cGlobals.game.board, NULL_XWE ) ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); } } /* handle_juggle_button */ static void handle_undo_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { if ( server_handleUndo( globals->cGlobals.game.server, NULL_XWE, 0 ) ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); } } /* handle_undo_button */ static void handle_redo_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* XP_UNUSED(globals) ) { } /* handle_redo_button */ static void handle_toggle_undo( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { BoardCtxt* board = globals->cGlobals.game.board; if ( board_redoReplacedTiles( board, NULL_XWE ) || board_replaceTiles( board, NULL_XWE ) ) { board_draw( board, NULL_XWE ); } } static void handle_trade_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { if ( board_beginTrade( globals->cGlobals.game.board, NULL_XWE ) ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); disenable_buttons( globals ); } } /* handle_juggle_button */ static void handle_done_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { if ( board_commitTurn( globals->cGlobals.game.board, NULL_XWE, XP_FALSE, XP_FALSE, NULL ) ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); disenable_buttons( globals ); } } /* handle_done_button */ static void setZoomButtons( GtkGameGlobals* globals, XP_Bool* inOut ) { gtk_widget_set_sensitive( globals->zoomin_button, inOut[0] ); gtk_widget_set_sensitive( globals->zoomout_button, inOut[1] ); } static void handle_zoomin_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { XP_Bool inOut[2]; if ( board_zoom( globals->cGlobals.game.board, NULL_XWE, 1, inOut ) ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); setZoomButtons( globals, inOut ); } } /* handle_zoomin_button */ static void handle_zoomout_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { XP_Bool inOut[2]; if ( board_zoom( globals->cGlobals.game.board, NULL_XWE, -1, inOut ) ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); setZoomButtons( globals, inOut ); } } /* handle_zoomout_button */ #ifdef XWFEATURE_CHAT static void handle_chat_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { gchar* msg = gtkGetChatMessage( globals ); if ( NULL != msg ) { board_sendChat( globals->cGlobals.game.board, NULL_XWE, msg ); g_free( msg ); } } #endif static void handle_pause_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { board_pause( globals->cGlobals.game.board, NULL_XWE, "whatever" ); disenable_buttons( globals ); } static void handle_unpause_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { board_unpause( globals->cGlobals.game.board, NULL_XWE, "whatever" ); disenable_buttons( globals ); } static void scroll_value_changed( GtkAdjustment *adj, GtkGameGlobals* globals ) { XP_U16 newValue; gfloat newValueF = gtk_adjustment_get_value( adj ); /* XP_ASSERT( newValueF >= 0.0 */ /* && newValueF <= globals->cGlobals.params->nHidden ); */ newValue = (XP_U16)newValueF; if ( board_setYOffset( globals->cGlobals.game.board, NULL_XWE, newValue ) ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); } } /* scroll_value_changed */ static void handle_grid_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { globals->gridOn = !globals->gridOn; board_invalAll( globals->cGlobals.game.board ); board_draw( globals->cGlobals.game.board, NULL_XWE ); } /* handle_grid_button */ static void handle_hide_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { BoardCtxt* board; XP_Bool draw = XP_FALSE; if ( globals->cGlobals.params->nHidden > 0 ) { gint nRows = globals->cGlobals.gi->boardSize; gtk_adjustment_set_page_size( globals->adjustment, nRows ); gtk_adjustment_set_value( globals->adjustment, 0.0 ); g_signal_emit_by_name( globals->adjustment, "changed" ); // gtk_adjustment_value_changed( GTK_ADJUSTMENT(globals->adjustment) ); } board = globals->cGlobals.game.board; if ( TRAY_REVEALED == board_getTrayVisState( board ) ) { draw = board_hideTray( board, NULL_XWE ); } else { draw = board_showTray( board, NULL_XWE ); } if ( draw ) { board_draw( board, NULL_XWE ); } } /* handle_hide_button */ static void handle_commit_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { if ( board_commitTurn( globals->cGlobals.game.board, NULL_XWE, XP_FALSE, XP_FALSE, NULL ) ) { board_draw( globals->cGlobals.game.board, NULL_XWE ); } } /* handle_commit_button */ static void handle_invite_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { CommonGlobals* cGlobals = &globals->cGlobals; XP_U16 nMissing = server_getPendingRegs( globals->cGlobals.game.server ); CommsAddrRec inviteAddr = {0}; gint nPlayers = nMissing; XP_Bool confirmed = gtkInviteDlg( globals, &inviteAddr, &nPlayers ); XP_LOGFF( "gtkInviteDlg() => %s", boolToStr(confirmed) ); if ( confirmed ) { send_invites( cGlobals, nPlayers, &inviteAddr ); } } /* handle_invite_button */ static void send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers, const CommsAddrRec* destAddr ) { CommsCtxt* comms = cGlobals->game.comms; CommsAddrRec myAddr = {0}; XP_ASSERT( comms ); comms_getSelfAddr( comms, &myAddr ); XP_U16 channel; if ( server_getOpenChannel( cGlobals->game.server, &channel ) ) { gint forceChannel = channel; NetLaunchInfo nli = {0}; /* include everything!!! */ nli_init( &nli, cGlobals->gi, &myAddr, nPlayers, forceChannel ); #ifdef XWFEATURE_RELAY if ( addr_hasType( &myAddr, COMMS_CONN_RELAY ) ) { XP_UCHAR buf[32]; snprintf( buf, sizeof(buf), "%X", makeRandomInt() ); nli_setInviteID( &nli, buf ); /* PENDING: should not be relay only!!! */ } #endif // nli_setDevID( &nli, linux_getDevIDRelay( cGlobals->params ) ); if ( addr_hasType( &myAddr, COMMS_CONN_MQTT ) ) { const MQTTDevID* devid = mqttc_getDevID( cGlobals->params ); nli_setMQTTDevID( &nli, devid ); } #ifdef DEBUG { XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr ); nli_saveToStream( &nli, stream ); NetLaunchInfo tmp; nli_makeFromStream( &tmp, stream ); stream_destroy( stream ); XP_ASSERT( 0 == memcmp( &nli, &tmp, sizeof(nli) ) ); } #endif #ifdef XWFEATURE_COMMS_INVITE comms_invite( comms, NULL_XWE, &nli, destAddr, XP_TRUE ); #else if ( !!destAddr && '\0' != destAddr->u.sms.phone[0] && 0 < destAddr->u.sms.port ) { linux_sms_invite( cGlobals->params, &nli, destAddr->u.sms.phone, destAddr->u.sms.port ); } # ifdef XWFEATURE_RELAY if ( 0 != relayDevID || !!relayID ) { XP_ASSERT( 0 != relayDevID || (!!relayID && !!relayID[0]) ); relaycon_invite( cGlobals->params, relayDevID, relayID, &nli ); } # endif if ( addr_hasType( destAddr, COMMS_CONN_MQTT ) ) { mqttc_invite( cGlobals->params, 0, &nli, &destAddr->u.mqtt.devID ); } #endif } /* while ( gtkaskm( "Invite how many and how?", infos, VSIZE(infos) ) ) { */ /* int nPlayers = atoi( countStr ); */ /* if ( 0 >= nPlayers || nPlayers > nMissing ) { */ /* gchar buf[128]; */ /* sprintf( buf, "Please invite between 1 and %d players (inclusive).", */ /* nMissing ); */ /* gtktell( globals->window, buf ); */ /* break; */ /* } */ /* int port = atoi( portstr ); */ /* if ( 0 == port ) { */ /* gtktell( globals->window, "Port must be a number and not 0." ); */ /* break; */ /* } */ /* int forceChannel = atoi( forceChannelStr ); */ /* if ( 1 > forceChannel || 4 <= forceChannel ) { */ /* gtktell( globals->window, "Channel must be between 1 and the number of client devices." ); */ /* break; */ /* } */ /* gchar gameName[64]; */ /* snprintf( gameName, VSIZE(gameName), "Game %d", gi->gameID ); */ /* CommsAddrRec addr; */ /* CommsCtxt* comms = globals->cGlobals.game.comms; */ /* XP_ASSERT( comms ); */ /* comms_getAddr( comms, &addr ); */ /* linux_sms_invite( globals->cGlobals.params, gi, &addr, gameName, */ /* nPlayers, forceChannel, phone, port ); */ /* break; */ /* } */ /* for ( int ii = 0; ii < VSIZE(infos); ++ii ) { */ /* g_free( *infos[ii].result ); */ /* } */ } static void gtkUserError( GtkGameGlobals* globals, const char* format, ... ) { char buf[512]; va_list ap; va_start( ap, format ); vsprintf( buf, format, ap ); (void)gtkask( globals->window, buf, GTK_BUTTONS_OK, NULL ); va_end(ap); } /* gtkUserError */ static gint ask_blank( gpointer data ) { GtkGameGlobals* globals = (GtkGameGlobals*)data; CommonGlobals* cGlobals = &globals->cGlobals; XP_UCHAR* name = globals->cGlobals.gi->players[cGlobals->selPlayer].name; XP_S16 result = gtkletterask( NULL, XP_FALSE, name, 1, cGlobals->nTiles, cGlobals->tiles, NULL ); for ( int ii = 0; ii < cGlobals->nTiles; ++ii ) { g_free( (gpointer)cGlobals->tiles[ii] ); } if ( result >= 0 && board_setBlankValue( cGlobals->game.board, cGlobals->selPlayer, cGlobals->blankCol, cGlobals->blankRow, result ) ) { board_draw( cGlobals->game.board, NULL_XWE ); } return 0; } static void gtk_util_notifyPickTileBlank( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_U16 playerNum, XP_U16 col, XP_U16 row, const XP_UCHAR** texts, XP_U16 nTiles ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; CommonGlobals* cGlobals = &globals->cGlobals; cGlobals->selPlayer = playerNum; cGlobals->blankCol = col; cGlobals->blankRow = row; cGlobals->nTiles = nTiles; for ( int ii = 0; ii < nTiles; ++ii ) { cGlobals->tiles[ii] = g_strdup( texts[ii] ); } (void)g_idle_add( ask_blank, globals ); } static gint ask_tiles( gpointer data ) { GtkGameGlobals* globals = (GtkGameGlobals*)data; CommonGlobals* cGlobals = &globals->cGlobals; TrayTileSet newTiles = {0}; XP_UCHAR* name = cGlobals->gi->players[cGlobals->selPlayer].name; for ( XP_Bool done = XP_FALSE; !done; ) { XP_S16 picked = gtkletterask( &newTiles, XP_TRUE, name, cGlobals->nToPick, cGlobals->nTiles, cGlobals->tiles, cGlobals->tileCounts ); switch ( picked ) { case PICKER_PICKALL: done = XP_TRUE; break; case PICKER_BACKUP: if ( newTiles.nTiles > 0 ) { Tile backed = newTiles.tiles[--newTiles.nTiles]; ++cGlobals->tileCounts[backed]; } break; default: XP_ASSERT( picked >= 0 && picked < cGlobals->nTiles ); --cGlobals->tileCounts[picked]; newTiles.tiles[newTiles.nTiles++] = picked; done = newTiles.nTiles == cGlobals->nToPick; break; } } for ( int ii = 0; ii < cGlobals->nTiles; ++ii ) { g_free( (gpointer)cGlobals->tiles[ii] ); } BoardCtxt* board = cGlobals->game.board; XP_Bool draw = XP_TRUE; if ( cGlobals->pickIsInitial ) { server_tilesPicked( cGlobals->game.server, NULL_XWE, cGlobals->selPlayer, &newTiles ); } else { draw = board_commitTurn( cGlobals->game.board, NULL_XWE, XP_TRUE, XP_TRUE, &newTiles ); } if ( draw ) { board_draw( board, NULL_XWE ); } return 0; } /* ask_tiles */ static void gtk_util_informNeedPickTiles( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_Bool isInitial, XP_U16 player, XP_U16 nToPick, XP_U16 nFaces, const XP_UCHAR** faces, const XP_U16* counts ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; CommonGlobals* cGlobals = &globals->cGlobals; cGlobals->selPlayer = player; cGlobals->pickIsInitial = isInitial; cGlobals->nToPick = nToPick; cGlobals->nTiles = nFaces; for ( int ii = 0; ii < nFaces; ++ii ) { cGlobals->tiles[ii] = g_strdup( faces[ii] ); cGlobals->tileCounts[ii] = counts[ii]; } (void)g_idle_add( ask_tiles, globals ); } /* gtk_util_informNeedPickTiles */ static gint ask_password( gpointer data ) { GtkGameGlobals* globals = (GtkGameGlobals*)data; CommonGlobals* cGlobals = &globals->cGlobals; XP_UCHAR buf[32]; XP_U16 len = VSIZE(buf); if ( gtkpasswdask( cGlobals->askPassName, buf, &len ) ) { BoardCtxt* board = cGlobals->game.board; if ( board_passwordProvided( board, NULL_XWE, cGlobals->selPlayer, buf ) ) { board_draw( board, NULL_XWE ); } } return 0; } static void gtk_util_informNeedPassword( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_U16 player, const XP_UCHAR* name ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; CommonGlobals* cGlobals = &globals->cGlobals; cGlobals->askPassName = name; cGlobals->selPlayer = player; (void)g_idle_add( ask_password, globals ); } /* gtk_util_askPassword */ static void setCtrlsForTray( GtkGameGlobals* XP_UNUSED(globals) ) { #if 0 XW_TrayVisState state = board_getTrayVisState( globals->cGlobals.game.board ); XP_S16 nHidden = globals->cGlobals.params->nHidden; if ( nHidden != 0 ) { XP_U16 pageSize = nRows; if ( state == TRAY_HIDDEN ) { /* we recover what tray covers */ nHidden -= GTK_TRAY_HT_ROWS; } if ( nHidden > 0 ) { pageSize -= nHidden; } globals->adjustment->page_size = pageSize; globals->adjustment->value = board_getYOffset( globals->cGlobals.game.board ); gtk_signal_emit_by_name( globals->adjustment, "changed" ); } #endif } /* setCtrlsForTray */ static void gtk_util_trayHiddenChange( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XW_TrayVisState XP_UNUSED(state), XP_U16 XP_UNUSED(nVisibleRows) ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; setCtrlsForTray( globals ); } /* gtk_util_trayHiddenChange */ static void gtk_util_yOffsetChange( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_U16 maxOffset, XP_U16 XP_UNUSED(oldOffset), XP_U16 newOffset ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; /* adjustment is invalid when gtk's shutting down; ignore */ if ( !!globals->adjustment && GTK_IS_ADJUSTMENT(globals->adjustment) ) { gint nRows = globals->cGlobals.gi->boardSize; gtk_adjustment_set_page_size(globals->adjustment, nRows - maxOffset); gtk_adjustment_set_value(globals->adjustment, newOffset); // gtk_adjustment_value_changed( globals->adjustment ); } } /* gtk_util_yOffsetChange */ #ifdef XWFEATURE_TURNCHANGENOTIFY static void gtk_util_turnChanged( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_S16 XP_UNUSED(newTurn) ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; linuxSaveGame( &globals->cGlobals ); } #endif static void gtkShowFinalScores( const GtkGameGlobals* globals, XP_Bool ignoreTimeout ) { XWStreamCtxt* stream; XP_UCHAR* text; const CommonGlobals* cGlobals = &globals->cGlobals; stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr ); server_writeFinalScores( cGlobals->game.server, NULL_XWE, stream ); text = strFromStream( stream ); stream_destroy( stream ); XP_U16 timeout = (ignoreTimeout || cGlobals->manualFinal) ? 0 : cGlobals->params->askTimeout; const AskPair buttons[] = { { "OK", 1 }, { "Rematch", 2 }, { NULL, 0 } }; gint chosen = gtkask_timeout( globals->window, text, GTK_BUTTONS_NONE, buttons, timeout ); free( text ); if ( 2 == chosen ) { make_rematch( globals->apg, cGlobals ); } } /* gtkShowFinalScores */ static void gtk_util_notifyDupStatus( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_Bool XP_UNUSED(amHost), const XP_UCHAR* msg ) { LOG_FUNC(); GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; XP_UCHAR buf[256]; XP_SNPRINTF( buf, VSIZE(buf), "notifyDupStatus(): msg: %s", msg ); (void)gtkask( globals->window, buf, GTK_BUTTONS_OK, NULL ); } static void gtk_util_informMove( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_S16 XP_UNUSED(turn), XWStreamCtxt* expl, XWStreamCtxt* words ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; char* explStr = strFromStream( expl ); gchar* msg = g_strdup_printf( "informMove():\nexpl: %s", explStr ); if ( NULL != words ) { char* wordsStr = strFromStream( words ); gchar* prev = msg; gchar* postfix = g_strdup_printf( "words: %s", wordsStr ); free( wordsStr ); msg = g_strconcat( msg, postfix, NULL ); g_free( prev ); g_free( postfix ); } (void)gtkask( globals->window, msg, GTK_BUTTONS_OK, NULL ); free( explStr ); g_free( msg ); } static void gtk_util_informUndo( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe) ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; (void)gtkask_timeout( globals->window, "Remote player undid a move", GTK_BUTTONS_OK, NULL, 500 ); } static void gtk_util_notifyGameOver( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_S16 quitter ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; CommonGlobals* cGlobals = &globals->cGlobals; if ( cGlobals->params->printHistory ) { catGameHistory( cGlobals ); } catFinalScores( cGlobals, quitter ); if ( cGlobals->params->quitAfter >= 0 ) { sleep( cGlobals->params->quitAfter ); destroy_board_window( NULL, globals ); } else if ( cGlobals->params->undoWhenDone ) { server_handleUndo( cGlobals->game.server, NULL_XWE, 0 ); board_draw( cGlobals->game.board, NULL_XWE ); } else if ( !cGlobals->params->skipGameOver ) { gtkShowFinalScores( globals, XP_TRUE ); } } /* gtk_util_notifyGameOver */ static void gtk_util_informNetDict( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), const XP_UCHAR* XP_UNUSED(isoCode), const XP_UCHAR* oldName, const XP_UCHAR* newName, const XP_UCHAR* newSum, XWPhoniesChoice phoniesAction ) { if ( 0 != strcmp( oldName, newName ) ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; gchar buf[512]; int offset = snprintf( buf, VSIZE(buf), "dict changing from %s to %s (sum=%s).", oldName, newName, newSum ); if ( PHONIES_DISALLOW == phoniesAction ) { snprintf( &buf[offset], VSIZE(buf)-offset, "%s", "\nPHONIES_DISALLOW is set so this may " "lead to some surprises." ); } (void)gtkask( globals->window, buf, GTK_BUTTONS_OK, NULL ); } } /* define this to prevent user events during debugging from stopping the engine */ /* #define DONT_ABORT_ENGINE */ #ifdef XWFEATURE_HILITECELL static XP_Bool gtk_util_hiliteCell( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_U16 col, XP_U16 row ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; #ifndef DONT_ABORT_ENGINE gboolean pending; #endif board_hiliteCellAt( globals->cGlobals.game.board, NULL_XWE, col, row ); if ( globals->cGlobals.params->sleepOnAnchor ) { usleep( 10000 ); } #ifdef DONT_ABORT_ENGINE return XP_TRUE; /* keep going */ #else pending = gdk_events_pending(); if ( pending ) { XP_DEBUGF( "gtk_util_hiliteCell=>%d", pending ); } return !pending; #endif } /* gtk_util_hiliteCell */ #endif static XP_Bool gtk_util_altKeyDown( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe) ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; return globals->altKeyDown; } static XP_Bool gtk_util_engineProgressCallback( XW_UtilCtxt* XP_UNUSED(uc), XWEnv XP_UNUSED(xwe) ) { #ifdef DONT_ABORT_ENGINE return XP_TRUE; /* keep going */ #else gboolean pending = gdk_events_pending(); /* XP_DEBUGF( "gdk_events_pending returned %d\n", pending ); */ return !pending; #endif } /* gtk_util_engineProgressCallback */ static gint ask_bad_words( gpointer data ) { GtkGameGlobals* globals = (GtkGameGlobals*)data; CommonGlobals* cGlobals = &globals->cGlobals; if ( GTK_RESPONSE_YES == gtkask( globals->window, cGlobals->question, GTK_BUTTONS_YES_NO, NULL ) ) { board_commitTurn( cGlobals->game.board, NULL_XWE, XP_TRUE, XP_FALSE, NULL ); } return 0; } static void gtk_util_notifyIllegalWords( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), BadWordInfo* bwi, XP_U16 player, XP_Bool turnLost ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; CommonGlobals* cGlobals = &globals->cGlobals; char buf[300]; gchar* strs = g_strjoinv( "\", \"", (gchar**)bwi->words ); if ( turnLost ) { XP_UCHAR* name = cGlobals->gi->players[player].name; XP_ASSERT( !!name ); sprintf( buf, "Player %d (%s) played illegal word[s] \"%s\"; loses turn.", player+1, name, strs ); if ( cGlobals->params->skipWarnings ) { XP_LOGF( "%s", buf ); } else { gtkUserError( globals, buf ); } } else { sprintf( cGlobals->question, "Word[s] \"%s\" not in the current dictionary (%s). " "Use anyway?", strs, bwi->dictName ); (void)g_idle_add( ask_bad_words, globals ); } g_free( strs ); } /* gtk_util_notifyIllegalWords */ static void gtk_util_remSelected( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe) ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; XWStreamCtxt* stream; XP_UCHAR* text; stream = mem_stream_make_raw( MEMPOOL globals->cGlobals.params->vtMgr ); board_formatRemainingTiles( globals->cGlobals.game.board, NULL_XWE, stream ); text = strFromStream( stream ); stream_destroy( stream ); (void)gtkask( globals->window, text, GTK_BUTTONS_OK, NULL ); free( text ); } static void gtk_util_timerSelected( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_Bool inDuplicateMode, XP_Bool canPause ) { if ( inDuplicateMode ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; if ( canPause ) { handle_pause_button( NULL, globals ); } else { handle_unpause_button( NULL, globals ); } } } static void gtk_util_getMQTTIDsFor( XW_UtilCtxt* uc, XWEnv xwe, XP_U16 nRelayIDs, const XP_UCHAR* relayIDs[] ) { XP_ASSERT(0); /* implement me */ XP_USE( uc ); XP_USE( xwe ); XP_USE( nRelayIDs ); XP_USE( relayIDs ); } #ifdef XWFEATURE_CHAT static void gtk_util_showChat( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), const XP_UCHAR* const msg, XP_S16 from, XP_U32 tsSecs ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; XP_UCHAR buf[1024]; XP_UCHAR* name = ""; if ( 0 <= from ) { name = globals->cGlobals.gi->players[from].name; } GDateTime* dt = g_date_time_new_from_unix_utc( tsSecs ); gchar* tsStr = g_date_time_format( dt, "%T" ); XP_SNPRINTF( buf, VSIZE(buf), "Quoth %s at %s: \"%s\"", name, tsStr, msg ); g_free( tsStr ); g_date_time_unref (dt); (void)gtkask( globals->window, buf, GTK_BUTTONS_OK, NULL ); } #endif #ifdef XWFEATURE_SEARCHLIMIT static XP_Bool gtk_util_getTraySearchLimits( XW_UtilCtxt* XP_UNUSED(uc), XWEnv XP_UNUSED(xwe), XP_U16* XP_UNUSED(min), XP_U16* max ) { *max = askNTiles( MAX_TRAY_TILES, *max ); return XP_TRUE; } #endif #ifndef XWFEATURE_MINIWIN static void gtk_util_bonusSquareHeld( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XWBonusType bonus ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; gchar* msg = g_strdup_printf( "bonusSquareHeld(bonus=%d)", bonus ); gtkask_timeout( globals->window, msg, GTK_BUTTONS_OK, NULL, 1000 ); g_free( msg ); } static void gtk_util_playerScoreHeld( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_U16 player ) { LOG_FUNC(); GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; LastMoveInfo lmi; if ( model_getPlayersLastScore( globals->cGlobals.game.model, NULL_XWE, player, &lmi ) ) { XP_UCHAR buf[128]; formatLMI( &lmi, buf, VSIZE(buf) ); (void)gtkask( globals->window, buf, GTK_BUTTONS_OK, NULL ); } } #endif #ifdef XWFEATURE_BOARDWORDS static void gtk_util_cellSquareHeld( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XWStreamCtxt* words ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; const XP_U8* bytes = stream_getPtr( words ); gchar* msg = g_strdup_printf( "words for lookup:\n%s", (XP_UCHAR*)bytes ); gtktell( globals->window, msg ); g_free( msg ); } #endif static void gtk_util_informWordsBlocked( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_U16 nBadWords, XWStreamCtxt* words, const XP_UCHAR* dict ) { XP_U16 len = stream_getSize( words ); XP_UCHAR buf[len]; stream_getBytes( words, buf, len ); buf[len-1] = '\0'; /* overwrite \n */ GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; gchar* msg = g_strdup_printf( "%d word[s] not found in %s:\n%s", nBadWords, dict, buf ); gtkUserError( globals, msg ); g_free( msg ); } static void gtk_util_userError( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), UtilErrID id ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; XP_Bool silent; const XP_UCHAR* message = linux_getErrString( id, &silent ); XP_LOGF( "%s: %s", __func__, message ); if ( !silent ) { gtkUserError( globals, message ); } } /* gtk_util_userError */ static gint ask_move( gpointer data ) { GtkGameGlobals* globals = (GtkGameGlobals*)data; CommonGlobals* cGlobals = &globals->cGlobals; GtkButtonsType buttons = GTK_BUTTONS_YES_NO; gint chosen = gtkask( globals->window, cGlobals->question, buttons, NULL ); if ( GTK_RESPONSE_OK == chosen || chosen == GTK_RESPONSE_YES ) { BoardCtxt* board = cGlobals->game.board; if ( board_commitTurn( board, NULL_XWE, XP_TRUE, XP_TRUE, NULL ) ) { board_draw( board, NULL_XWE ); } } return 0; } static void gtk_util_notifyMove( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XWStreamCtxt* stream ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; CommonGlobals* cGlobals = &globals->cGlobals; /* char* question; */ /* XP_Bool freeMe = XP_FALSE; */ XP_U16 len = stream_getSize( stream ); XP_ASSERT( len <= VSIZE(cGlobals->question) ); stream_getBytes( stream, cGlobals->question, len ); cGlobals->question[len] = '\0'; (void)g_idle_add( ask_move, globals ); } /* gtk_util_userQuery */ static gint ask_trade( gpointer data ) { GtkGameGlobals* globals = (GtkGameGlobals*)data; CommonGlobals* cGlobals = &globals->cGlobals; if ( GTK_RESPONSE_YES == gtkask( globals->window, cGlobals->question, GTK_BUTTONS_YES_NO, NULL ) ) { BoardCtxt* board = cGlobals->game.board; if ( board_commitTurn( board, NULL_XWE, XP_TRUE, XP_TRUE, NULL ) ) { board_draw( board, NULL_XWE ); } } return 0; } static void gtk_util_notifyTrade( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), const XP_UCHAR** tiles, XP_U16 nTiles ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; formatConfirmTrade( &globals->cGlobals, tiles, nTiles ); (void)g_idle_add( ask_trade, globals ); } static GtkWidget* makeShowButtonFromBitmap( void* closure, const gchar* filename, const gchar* alt, GCallback func ) { GtkWidget* widget; GtkWidget* button; if ( file_exists( filename ) ) { widget = gtk_image_new_from_file( filename ); } else { widget = gtk_label_new( alt ); } gtk_widget_show( widget ); button = gtk_button_new(); gtk_container_add (GTK_CONTAINER (button), widget ); gtk_widget_show (button); if ( func != NULL ) { g_signal_connect( button, "clicked", func, closure ); } return button; } /* makeShowButtonFromBitmap */ static GtkWidget* addVBarButton( GtkGameGlobals* globals, const gchar* icon, const gchar* title, GCallback func, GtkWidget* vbox ) { GtkWidget* button = makeShowButtonFromBitmap( globals, icon, title, G_CALLBACK(func) ); gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); return button; } static GtkWidget* makeVerticalBar( GtkGameGlobals* globals, GtkWidget* XP_UNUSED(window) ) { GtkWidget* vbox = gtk_button_box_new( GTK_ORIENTATION_VERTICAL ); globals->flip_button = addVBarButton( globals, "../flip.xpm", "f", G_CALLBACK(handle_flip_button), vbox ); addVBarButton( globals, "../value.xpm", "v", G_CALLBACK(handle_value_button), vbox ); globals->prevhint_button = addVBarButton( globals, "../hint.xpm", "?-", G_CALLBACK(handle_prevhint_button), vbox ); globals->nexthint_button = addVBarButton( globals, "../hint.xpm", "?+", G_CALLBACK(handle_nexthint_button), vbox ); addVBarButton( globals, "../hintNum.xpm", "n", G_CALLBACK(handle_nhint_button), vbox ); addVBarButton( globals, "../colors.xpm", "c", G_CALLBACK(handle_colors_button), vbox ); /* undo and redo buttons */ addVBarButton( globals, "../undo.xpm", "U", G_CALLBACK(handle_undo_button), vbox ); addVBarButton( globals, "../redo.xpm", "R", G_CALLBACK(handle_redo_button), vbox ); globals->toggle_undo_button = addVBarButton( globals, "", "u/r", G_CALLBACK(handle_toggle_undo), vbox ); /* the four buttons that on palm are beside the tray */ addVBarButton( globals, "../juggle.xpm", "j", G_CALLBACK(handle_juggle_button), vbox ); addVBarButton( globals, "../trade.xpm", "t", G_CALLBACK(handle_trade_button), vbox ); addVBarButton( globals, "../done.xpm", "d", G_CALLBACK(handle_done_button), vbox ); globals->zoomin_button = addVBarButton( globals, "../done.xpm", "+", G_CALLBACK(handle_zoomin_button), vbox ); globals->zoomout_button = addVBarButton( globals, "../done.xpm", "-", G_CALLBACK(handle_zoomout_button), vbox ); #ifdef XWFEATURE_CHAT globals->chat_button = addVBarButton( globals, "", "Chat", G_CALLBACK(handle_chat_button), vbox ); #endif globals->pause_button = addVBarButton( globals, "", "Pause", G_CALLBACK(handle_pause_button), vbox ); globals->unpause_button = addVBarButton( globals, "", "Unpause", G_CALLBACK(handle_unpause_button), vbox ); gtk_widget_show( vbox ); return vbox; } /* makeVerticalBar */ static GtkWidget* addButton( GtkWidget* hbox, gchar* label, GCallback func, GtkGameGlobals* globals ) { GtkWidget* button = gtk_button_new_with_label( label ); gtk_widget_show( button ); g_signal_connect( button, "clicked", G_CALLBACK(func), globals ); gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, TRUE, 0); return button; } static GtkWidget* makeButtons( GtkGameGlobals* globals ) { GtkWidget* hbox = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 0 ); globals->buttons_hbox = hbox; (void)addButton( hbox, "Grid", G_CALLBACK(handle_grid_button), globals ); (void)addButton( hbox, "Hide", G_CALLBACK(handle_hide_button), globals ); globals->commit_button = addButton( hbox, "Commit", G_CALLBACK(handle_commit_button), globals ); gtk_widget_show( hbox ); return hbox; } /* makeButtons */ static void setupGtkUtilCallbacks( GtkGameGlobals* globals, XW_UtilCtxt* util ) { util->closure = globals; #define SET_PROC(NAM) util->vtable->m_util_##NAM = gtk_util_##NAM SET_PROC(userError); SET_PROC(notifyMove); SET_PROC(notifyTrade); SET_PROC(notifyPickTileBlank); SET_PROC(informNeedPickTiles); SET_PROC(informNeedPassword); SET_PROC(trayHiddenChange); SET_PROC(yOffsetChange); #ifdef XWFEATURE_TURNCHANGENOTIFY SET_PROC(turnChanged); #endif SET_PROC(notifyDupStatus); SET_PROC(informMove); SET_PROC(informUndo); SET_PROC(notifyGameOver); SET_PROC(informNetDict); #ifdef XWFEATURE_HILITECELL SET_PROC(hiliteCell); #endif SET_PROC(altKeyDown); SET_PROC(engineProgressCallback); SET_PROC(notifyIllegalWords); SET_PROC(remSelected); SET_PROC(getMQTTIDsFor); SET_PROC(timerSelected); #ifdef XWFEATURE_CHAT SET_PROC(showChat); #endif #ifdef XWFEATURE_SEARCHLIMIT SET_PROC(getTraySearchLimits); #endif #ifndef XWFEATURE_MINIWIN SET_PROC(bonusSquareHeld); SET_PROC(playerScoreHeld); #endif #ifdef XWFEATURE_BOARDWORDS SET_PROC(cellSquareHeld); #endif SET_PROC(informWordsBlocked); #undef SET_PROC assertTableFull( util->vtable, sizeof(*util->vtable), "gtk util" ); } /* setupGtkUtilCallbacks */ typedef struct _SockInfo { GIOChannel* channel; guint watch; int socket; } SockInfo; static gboolean acceptorInput( GIOChannel* source, GIOCondition condition, gpointer data ) { gboolean keepSource; CommonGlobals* globals = (CommonGlobals*)data; LOG_FUNC(); if ( (condition & G_IO_IN) != 0 ) { int listener = g_io_channel_unix_get_fd( source ); XP_LOGF( "%s: input on socket %d", __func__, listener ); keepSource = (*globals->acceptor)( listener, data ); } else { keepSource = FALSE; } return keepSource; } /* acceptorInput */ static void gtk_socket_acceptor( int listener, Acceptor func, CommonGlobals* globals, void** storage ) { SockInfo* info = (SockInfo*)*storage; GIOChannel* channel; guint watch; LOG_FUNC(); if ( listener == -1 ) { XP_ASSERT( !!globals->acceptor ); globals->acceptor = NULL; XP_ASSERT( !!info ); #ifdef DEBUG int oldSock = info->socket; #endif g_source_remove( info->watch ); g_io_channel_unref( info->channel ); XP_FREE( globals->util->mpool, info ); *storage = NULL; XP_LOGF( "Removed listener %d from gtk's list of listened-to sockets", oldSock ); } else { XP_ASSERT( !globals->acceptor || (func == globals->acceptor) ); globals->acceptor = func; channel = g_io_channel_unix_new( listener ); g_io_channel_set_close_on_unref( channel, TRUE ); watch = g_io_add_watch( channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI, acceptorInput, globals ); g_io_channel_unref( channel ); /* only main loop holds it now */ XP_LOGF( "%s: g_io_add_watch(%d) => %d", __func__, listener, watch ); XP_ASSERT( NULL == info ); info = XP_MALLOC( globals->util->mpool, sizeof(*info) ); info->channel = channel; info->watch = watch; info->socket = listener; *storage = info; } } /* gtk_socket_acceptor */ /* int */ /* board_main( LaunchParams* params ) */ /* { */ /* GtkGameGlobals globals; */ /* initGlobals( &globals, params ); */ /* if ( !!params->pipe && !!params->fileName ) { */ /* read_pipe_then_close( &globals.cGlobals, NULL ); */ /* } else { */ /* gtk_widget_show( globals.window ); */ /* gtk_main(); */ /* } */ /* /\* MONCONTROL(1); *\/ */ /* cleanup( &globals ); */ /* return 0; */ /* } */ static void initGlobalsNoDraw( GtkGameGlobals* globals, LaunchParams* params, const CurGameInfo* gi ) { memset( globals, 0, sizeof(*globals) ); CommonGlobals* cGlobals = &globals->cGlobals; setTransportProcs( &cGlobals->procs, globals ); cGlobals->gi = &cGlobals->_gi; if ( !gi ) { gi = ¶ms->pgi; } gi_copy( MPPARM(params->mpool) cGlobals->gi, gi ); cGlobals->params = params; cGlobals->lastNTilesToUse = MAX_TRAY_TILES; cGlobals->rowid = -1; #ifdef XWFEATURE_RELAY cGlobals->relaySocket = -1; #endif cGlobals->socketAddedClosure = globals; cGlobals->onSave = gtkOnGameSaved; cGlobals->onSaveClosure = globals; cGlobals->addAcceptor = gtk_socket_acceptor; cGlobals->cp.showBoardArrow = XP_TRUE; cGlobals->cp.hideTileValues = params->hideValues; cGlobals->cp.skipMQTTAdd = params->skipMQTTAdd; cGlobals->cp.skipCommitConfirm = params->skipCommitConfirm; cGlobals->cp.sortNewTiles = params->sortNewTiles; cGlobals->cp.showColors = params->showColors; cGlobals->cp.allowPeek = params->allowPeek; cGlobals->cp.showRobotScores = params->showRobotScores; #ifdef XWFEATURE_SLOW_ROBOT cGlobals->cp.robotThinkMin = params->robotThinkMin; cGlobals->cp.robotThinkMax = params->robotThinkMax; cGlobals->cp.robotTradePct = params->robotTradePct; #endif #ifdef XWFEATURE_ROBOTPHONIES cGlobals->cp.makePhonyPct = params->makePhonyPct; #endif #ifdef XWFEATURE_CROSSHAIRS cGlobals->cp.hideCrosshairs = params->hideCrosshairs; #endif setupUtil( cGlobals ); setupGtkUtilCallbacks( globals, cGlobals->util ); makeSelfAddress( &cGlobals->selfAddr, params ); } /* This gets called all the time, e.g. when the mouse moves across drawing-area boundaries. So invalidating is crazy expensive. But this is a test app....*/ static gboolean on_draw_event( GtkWidget* widget, cairo_t* cr, gpointer user_data ) { // XP_LOGF( "%s(widget=%p)", __func__, widget ); /* GdkRectangle rect; */ /* if ( gdk_cairo_get_clip_rectangle( cr, &rect) ) { */ /* XP_LOGF( "%s(): clip: x:%d,y:%d,w:%d,h:%d", __func__, */ /* rect.x, rect.y, rect.width, rect.height ); */ /* } */ GtkGameGlobals* globals = (GtkGameGlobals*)user_data; CommonGlobals* cGlobals = &globals->cGlobals; board_invalAll( cGlobals->game.board ); board_draw( cGlobals->game.board, NULL_XWE ); draw_gtk_status( (GtkDrawCtx*)(void*)cGlobals->draw, globals->stateChar ); XP_USE(widget); XP_USE(cr); return FALSE; } void initBoardGlobalsGtk( GtkGameGlobals* globals, LaunchParams* params, const CurGameInfo* gi ) { short width, height; GtkWidget* window; GtkWidget* drawing_area; GtkWidget* menubar; GtkWidget* vbox; GtkWidget* hbox; initGlobalsNoDraw( globals, params, gi ); globals->window = window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); if ( !!params->fileName ) { gtk_window_set_title( GTK_WINDOW(window), params->fileName ); } g_signal_connect( window, "configure-event", G_CALLBACK(on_window_configure), globals ); vbox = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); gtk_container_add( GTK_CONTAINER(window), vbox ); gtk_widget_show( vbox ); #ifdef DEBUG gulong id = #endif g_signal_connect( window, "destroy", G_CALLBACK(destroy_board_window), globals ); XP_ASSERT( id > 0 ); XP_ASSERT( !!globals ); #ifdef DEBUG id = #endif g_signal_connect( window, "show", G_CALLBACK( on_board_window_shown ), globals ); XP_ASSERT( id > 0 ); menubar = makeMenus( globals ); gtk_box_pack_start( GTK_BOX(vbox), menubar, FALSE, TRUE, 0); #ifdef DEBUG globals->drop_checks_vbox = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); gtk_box_pack_start( GTK_BOX(vbox), globals->drop_checks_vbox, FALSE, TRUE, 0 ); #endif gtk_box_pack_start( GTK_BOX(vbox), makeButtons( globals ), FALSE, TRUE, 0); drawing_area = gtk_drawing_area_new(); gtk_widget_add_events( drawing_area, GDK_ALL_EVENTS_MASK ); #ifdef DEBUG id = #endif g_signal_connect(G_OBJECT(drawing_area), "draw", G_CALLBACK(on_draw_event), globals); XP_ASSERT( id > 0 ); globals->drawing_area = drawing_area; gtk_widget_show( drawing_area ); width = GTK_HOR_SCORE_WIDTH + GTK_TIMER_WIDTH + GTK_TIMER_PAD; if ( params->verticalScore ) { width += GTK_VERT_SCORE_WIDTH; } height = 196; if ( params->nHidden == 0 ) { height += GTK_MIN_SCALE * GTK_TRAY_HT_ROWS; } gtk_widget_set_size_request( GTK_WIDGET(drawing_area), width, height ); hbox = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 0 ); gtk_box_pack_start( GTK_BOX(hbox), drawing_area, TRUE, TRUE, 0); /* install scrollbar even if not needed -- since zooming can make it needed later */ GtkWidget* vscrollbar; CommonGlobals* cGlobals = &globals->cGlobals; gint nRows = cGlobals->gi->boardSize; globals->adjustment = (GtkAdjustment*) gtk_adjustment_new( 0, 0, nRows, 1, 2, nRows - params->nHidden ); vscrollbar = gtk_scrollbar_new( GTK_ORIENTATION_VERTICAL, globals->adjustment ); #ifdef DEBUG id = #endif g_signal_connect( globals->adjustment, "value_changed", G_CALLBACK(scroll_value_changed), globals ); XP_ASSERT( id > 0 ); gtk_widget_show( vscrollbar ); gtk_box_pack_start( GTK_BOX(hbox), vscrollbar, FALSE, FALSE, 0 ); gtk_box_pack_start( GTK_BOX (hbox), makeVerticalBar( globals, window ), FALSE, TRUE, 0 ); gtk_widget_show( hbox ); gtk_box_pack_start( GTK_BOX(vbox), hbox/* drawing_area */, TRUE, TRUE, 0); GtkWidget* label = globals->countLabel = gtk_label_new( "" ); gtk_box_pack_start( GTK_BOX(vbox), label, TRUE, TRUE, 0); gtk_widget_show( label ); #ifdef DEBUG id = #endif g_signal_connect( drawing_area, "configure-event", G_CALLBACK(on_drawing_configure), globals ); XP_ASSERT( id > 0 ); #ifdef DEBUG id = #endif g_signal_connect( drawing_area, "button_press_event", G_CALLBACK(button_press_event), globals ); XP_ASSERT( id > 0 ); #ifdef DEBUG id = #endif g_signal_connect( drawing_area, "motion_notify_event", G_CALLBACK(motion_notify_event), globals ); XP_ASSERT( id > 0 ); #ifdef DEBUG id = #endif g_signal_connect( drawing_area, "button_release_event", G_CALLBACK(button_release_event), globals ); XP_ASSERT( id > 0 ); setOneSecondTimer( cGlobals ); #ifdef KEY_SUPPORT # ifdef KEYBOARD_NAV g_signal_connect( window, "key_press_event", G_CALLBACK(key_press_event), globals ); # endif g_signal_connect( window, "key_release_event", G_CALLBACK(key_release_event), globals ); #endif gtk_widget_set_events( drawing_area, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK #ifdef KEY_SUPPORT # ifdef KEYBOARD_NAV | GDK_KEY_PRESS_MASK # endif | GDK_KEY_RELEASE_MASK #endif /* | GDK_POINTER_MOTION_HINT_MASK */ ); } /* initGlobals */ void freeGlobals( GtkGameGlobals* globals ) { cleanup( globals ); } XP_Bool loadGameNoDraw( GtkGameGlobals* globals, LaunchParams* params, sqlite3_int64 rowid ) { LOG_FUNC(); sqlite3* pDb = params->pDb; initGlobalsNoDraw( globals, params, NULL ); CommonGlobals* cGlobals = &globals->cGlobals; cGlobals->rowid = rowid; XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) params->vtMgr ); XP_Bool loaded = gdb_loadGame( stream, pDb, rowid ); if ( loaded ) { loaded = game_makeFromStream( MEMPOOL NULL_XWE, stream, &cGlobals->game, cGlobals->gi, cGlobals->util, (DrawCtx*)NULL, &cGlobals->cp, &cGlobals->procs ); if ( loaded ) { XP_LOGF( "%s: game loaded", __func__ ); if ( !!globals->cGlobals.game.comms ) { comms_resendAll( globals->cGlobals.game.comms, NULL_XWE, COMMS_CONN_NONE, XP_FALSE ); } } else { game_dispose( &cGlobals->game, NULL_XWE ); } } stream_destroy( stream ); return loaded; } #endif /* PLATFORM_GTK */