mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2024-12-28 09:58:30 +01:00
2627 lines
80 KiB
C
2627 lines
80 KiB
C
/* -*- 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 <gtk/gtk.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
|
|
#include <netdb.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
|
|
#ifndef CLIENT_ONLY
|
|
/* # include <prc.h> */
|
|
#endif
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
|
|
#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 = "<unknown>";
|
|
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 */
|