xwords/xwords4/linux/gtkboard.c
Eric House ad6be1bb7e add debug-only "tag" to open games so logging can more easily track
what's going on when multiple participants in a single game are on the
same device. But for a couple of strings passed into the jni the
changes are only in DEBUG code.
2015-02-09 19:15:43 -08:00

2743 lines
85 KiB
C

/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
/*
* Copyright 2000-2013 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 "gtkask.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 "filestream.h"
#include "gamesdb.h"
#include "relaycon.h"
/* static guint gtkSetupClientSocket( GtkGameGlobals* globals, int sock ); */
static void setCtrlsForTray( GtkGameGlobals* globals );
static void new_game( GtkWidget* widget, GtkGameGlobals* globals );
static XP_Bool new_game_impl( GtkGameGlobals* globals, XP_Bool fireConnDlg );
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 );
#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,
event->x, event->y, &handled );
if ( redraw ) {
board_draw( globals->cGlobals.game.board );
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, event->x,
event->y );
if ( handled ) {
board_draw( globals->cGlobals.game.board );
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,
event->x,
event->y );
if ( redraw ) {
board_draw( globals->cGlobals.game.board );
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_Return:
xpkey = XP_RETURN_KEY;
break;
case GDK_space:
xpkey = XP_RAISEFOCUS_KEY;
break;
case GDK_Left:
xpkey = XP_CURSOR_KEY_LEFT;
movesCursor = XP_TRUE;
break;
case GDK_Right:
xpkey = XP_CURSOR_KEY_RIGHT;
movesCursor = XP_TRUE;
break;
case GDK_Up:
xpkey = XP_CURSOR_KEY_UP;
movesCursor = XP_TRUE;
break;
case GDK_Down:
xpkey = XP_CURSOR_KEY_DOWN;
movesCursor = XP_TRUE;
break;
#endif
case GDK_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, xpkey,
&handled )
: board_handleKeyDown( globals->cGlobals.game.board, xpkey,
&handled );
if ( draw ) {
board_draw( globals->cGlobals.game.board );
}
}
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, 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 );
}
}
/* 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
static void
relay_status_gtk( void* closure, CommsRelayState state )
{
XP_LOGF( "%s got status: %s", __func__, CommsRelayState2Str(state) );
GtkGameGlobals* globals = (GtkGameGlobals*)closure;
if ( !!globals->draw ) {
globals->cGlobals.state = state;
globals->stateChar = 'A' + COMMS_RELAYSTATE_ALLCONNECTED - state;
draw_gtk_status( globals->draw, globals->stateChar );
}
}
static void
relay_connd_gtk( 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 ) {
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( 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( const XP_U8* msg, XP_U16 len, const XP_UCHAR* relayID,
void* closure )
{
GtkGameGlobals* globals = (GtkGameGlobals*)closure;
XP_Bool success = XP_FALSE;
LaunchParams* params = globals->cGlobals.params;
if ( params->useUdp && !globals->draw ) {
XP_U16 seed = comms_getChannelSeed( globals->cGlobals.game.comms );
XP_U32 clientToken = makeClientToken( globals->cGlobals.selRow, seed );
XP_S16 nSent = relaycon_sendnoconn( params, msg, len, relayID,
clientToken );
success = nSent == len;
}
return success;
} /* relay_sendNoConn_gtk */
#ifdef COMMS_XPORT_FLAGSPROC
static XP_U32
gtk_getFlags( void* closure )
{
GtkGameGlobals* globals = (GtkGameGlobals*)closure;
return (!!globals->draw) ? COMMS_XPORT_FLAGS_NONE
: COMMS_XPORT_FLAGS_HASNOCONN;
}
#endif
static void
setTransportProcs( TransportProcs* procs, GtkGameGlobals* globals )
{
procs->closure = globals;
procs->send = LINUX_SEND;
#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;
#endif
}
#ifndef XWFEATURE_STANDALONE_ONLY
# 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 addr;
comms_getAddr( comms, &addr );
CommsConnType typ;
for ( XP_U32 st = 0; addr_iter( &addr, &typ, &st ); ) {
DropTypeData* datum = &globals->dropData[typ];
datum->typ = typ;
datum->comms = comms;
GtkWidget* hbox = gtk_hbox_new( FALSE, 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( GTK_OBJECT(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( GTK_OBJECT(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 */
#endif
static void
createOrLoadObjects( GtkGameGlobals* globals )
{
XWStreamCtxt* stream = NULL;
XP_Bool opened = XP_FALSE;
#ifndef XWFEATURE_STANDALONE_ONLY
#endif
CommonGlobals* cGlobals = &globals->cGlobals;
LaunchParams* params = cGlobals->params;
globals->draw = (GtkDrawCtx*)gtkDrawCtxtMake( globals->drawing_area,
globals );
TransportProcs procs;
setTransportProcs( &procs, globals );
if ( !!params->fileName && file_exists( params->fileName ) ) {
stream = streamFromFile( cGlobals, params->fileName, globals );
#ifdef USE_SQLITE
} else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) {
XP_UCHAR buf[32];
XP_SNPRINTF( buf, sizeof(buf), "%d", params->dbFileID );
mpool_setTag( MEMPOOL buf );
stream = streamFromDB( cGlobals, globals );
#endif
} else if ( !!cGlobals->pDb && 0 <= cGlobals->selRow ) {
stream = mem_stream_make( MPPARM(cGlobals->util->mpool)
params->vtMgr,
cGlobals, CHANNEL_NONE, NULL );
if ( !loadGame( stream, cGlobals->pDb, cGlobals->selRow ) ) {
stream_destroy( stream );
stream = NULL;
}
}
if ( !!stream ) {
if ( NULL == cGlobals->dict ) {
cGlobals->dict = makeDictForStream( cGlobals, stream );
}
opened = game_makeFromStream( MEMPOOL stream, &cGlobals->game,
cGlobals->gi, cGlobals->dict,
&cGlobals->dicts, cGlobals->util,
(DrawCtx*)globals->draw,
&cGlobals->cp, &procs );
XP_LOGF( "%s: loaded gi at %p", __func__, &cGlobals->gi );
stream_destroy( stream );
}
if ( !opened ) {
CommsAddrRec addr = cGlobals->addr;
/* XP_MEMSET( &addr, 0, sizeof(addr) ); */
/* addr.conType = cGlobals->addr.conType; */
#ifdef XWFEATURE_RELAY
/* if ( addr.conType == COMMS_CONN_RELAY ) { */
/* XP_ASSERT( !!params->connInfo.relay.relayName ); */
/* globals->cGlobals.defaultServerName */
/* = params->connInfo.relay.relayName; */
/* } */
#endif
game_makeNewGame( MEMPOOL &cGlobals->game, cGlobals->gi,
cGlobals->util, (DrawCtx*)globals->draw,
&cGlobals->cp, &procs, params->gameSeed );
// addr.conType = params->conType;
CommsConnType typ;
for ( XP_U32 st = 0; addr_iter( &addr, &typ, &st ); ) {
if ( params->commsDisableds[typ][0] ) {
comms_setAddrDisabled( cGlobals->game.comms, typ, XP_FALSE, XP_TRUE );
}
if ( params->commsDisableds[typ][1] ) {
comms_setAddrDisabled( cGlobals->game.comms, typ, XP_TRUE, XP_TRUE );
}
switch( typ ) {
#ifdef XWFEATURE_RELAY
case COMMS_CONN_RELAY:
/* addr.u.ip_relay.ipAddr = 0; */
/* addr.u.ip_relay.port = params->connInfo.relay.defaultSendPort; */
/* addr.u.ip_relay.seeksPublicRoom = params->connInfo.relay.seeksPublicRoom; */
/* addr.u.ip_relay.advertiseRoom = params->connInfo.relay.advertiseRoom; */
/* XP_STRNCPY( addr.u.ip_relay.hostName, params->connInfo.relay.relayName, */
/* sizeof(addr.u.ip_relay.hostName) - 1 ); */
/* XP_STRNCPY( addr.u.ip_relay.invite, params->connInfo.relay.invite, */
/* sizeof(addr.u.ip_relay.invite) - 1 ); */
break;
#endif
#ifdef XWFEATURE_BLUETOOTH
case COMMS_CONN_BT:
XP_ASSERT( sizeof(addr.u.bt.btAddr)
>= sizeof(params->connInfo.bt.hostAddr));
XP_MEMCPY( &addr.u.bt.btAddr, &params->connInfo.bt.hostAddr,
sizeof(params->connInfo.bt.hostAddr) );
break;
#endif
#ifdef XWFEATURE_IP_DIRECT
case COMMS_CONN_IP_DIRECT:
XP_STRNCPY( addr.u.ip.hostName_ip, params->connInfo.ip.hostName,
sizeof(addr.u.ip.hostName_ip) - 1 );
addr.u.ip.port_ip = params->connInfo.ip.port;
break;
#endif
#ifdef XWFEATURE_SMS
case COMMS_CONN_SMS:
/* No! Don't overwrite what may be a return address with local
stuff */
/* XP_STRNCPY( addr.u.sms.phone, params->connInfo.sms.phone, */
/* sizeof(addr.u.sms.phone) - 1 ); */
/* addr.u.sms.port = params->connInfo.sms.port; */
break;
#endif
default:
break;
}
}
/* Need to save in order to have a valid selRow for the first send */
saveGame( cGlobals );
#ifndef XWFEATURE_STANDALONE_ONLY
/* This may trigger network activity */
if ( !!cGlobals->game.comms ) {
comms_setAddr( cGlobals->game.comms, &addr );
}
#endif
model_setDictionary( cGlobals->game.model, cGlobals->dict );
setSquareBonuses( cGlobals );
model_setPlayerDicts( cGlobals->game.model, &cGlobals->dicts );
#ifdef XWFEATURE_SEARCHLIMIT
cGlobals->gi->allowHintRect = params->allowHintRect;
#endif
if ( params->needsNewGame ) {
new_game_impl( globals, XP_FALSE );
#ifndef XWFEATURE_STANDALONE_ONLY
} else {
DeviceRole serverRole = cGlobals->gi->serverRole;
if ( serverRole == SERVER_ISCLIENT ) {
XWStreamCtxt* stream =
mem_stream_make( MEMPOOL params->vtMgr,
cGlobals, CHANNEL_NONE,
sendOnClose );
(void)server_initClientConnection( cGlobals->game.server,
stream );
}
#endif
}
}
if ( !params->fileName && !!params->dbName ) {
XP_UCHAR buf[64];
snprintf( buf, sizeof(buf), "%s / %lld", params->dbName,
cGlobals->selRow );
gtk_window_set_title( GTK_WINDOW(globals->window), buf );
}
#ifndef XWFEATURE_STANDALONE_ONLY
if ( !!globals->cGlobals.game.comms ) {
comms_start( globals->cGlobals.game.comms );
}
#endif
server_do( globals->cGlobals.game.server );
saveGame( cGlobals ); /* again, to include address etc. */
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
configure_event( GtkWidget* widget, GdkEventConfigure* XP_UNUSED(event),
GtkGameGlobals* globals )
{
if ( globals->draw == NULL ) {
createOrLoadObjects( globals );
}
CommonGlobals* cGlobals = &globals->cGlobals;
BoardCtxt* board = cGlobals->game.board;
short bdWidth = widget->allocation.width - (GTK_RIGHT_MARGIN
+ GTK_BOARD_LEFT_MARGIN);
short bdHeight = widget->allocation.height - (GTK_TOP_MARGIN + GTK_BOTTOM_MARGIN)
- GTK_MIN_TRAY_SCALEV - GTK_BOTTOM_MARGIN;
#ifdef COMMON_LAYOUT
XP_ASSERT( !cGlobals->params->verticalScore ); /* not supported */
BoardDims dims;
board_figureLayout( board, cGlobals->gi,
GTK_BOARD_LEFT, GTK_HOR_SCORE_TOP, bdWidth, bdHeight,
#if 1
150, 200,
#else
0, 0,
#endif
bdWidth-25, 16, 16,
XP_FALSE, &dims );
board_applyLayout( board, &dims );
#else
short timerLeft, timerTop;
gint hscale, vscale;
gint trayTop;
gint boardTop = 0;
XP_U16 netStatWidth = 0;
gint nCols;
gint nRows;
nCols = cGlobals->gi->boardSize;
nRows = nCols;
if ( cGlobals->params->verticalScore ) {
bdWidth -= GTK_VERT_SCORE_WIDTH;
}
hscale = bdWidth / nCols;
if ( 0 != cGlobals->params->nHidden ) {
vscale = hscale;
} else {
vscale = (bdHeight / (nCols + GTK_TRAY_HT_ROWS)); /* makd tray height
3x cell height */
}
if ( !cGlobals->params->verticalScore ) {
boardTop += GTK_HOR_SCORE_HEIGHT;
}
trayTop = boardTop + (vscale * nRows);
/* move tray up if part of board's meant to be hidden */
trayTop -= vscale * cGlobals->params->nHidden;
board_setPos( board, GTK_BOARD_LEFT, boardTop,
hscale * nCols, vscale * nRows, hscale * 4, XP_FALSE );
/* board_setScale( board, hscale, vscale ); */
if ( !!cGlobals->game.comms ) {
netStatWidth = GTK_NETSTAT_WIDTH;
}
timerTop = GTK_TIMER_TOP;
if ( cGlobals->params->verticalScore ) {
timerLeft = GTK_BOARD_LEFT + (hscale*nCols) + 1;
board_setScoreboardLoc( board,
timerLeft,
GTK_VERT_SCORE_TOP,
GTK_VERT_SCORE_WIDTH,
vscale*nCols,
XP_FALSE );
} else {
timerLeft = GTK_BOARD_LEFT + (hscale*nCols)
- GTK_TIMER_WIDTH - netStatWidth;
board_setScoreboardLoc( board,
GTK_BOARD_LEFT, GTK_HOR_SCORE_TOP,
timerLeft-GTK_BOARD_LEFT,
GTK_HOR_SCORE_HEIGHT,
XP_TRUE );
}
/* Still pending: do this for the vertical score case */
if ( cGlobals->game.comms ) {
globals->netStatLeft = timerLeft + GTK_TIMER_WIDTH;
globals->netStatTop = 0;
}
board_setTimerLoc( board, timerLeft, timerTop,
GTK_TIMER_WIDTH, GTK_HOR_SCORE_HEIGHT );
board_setTrayLoc( board, GTK_TRAY_LEFT, trayTop,
hscale * nCols, vscale * GTK_TRAY_HT_ROWS + 10,
GTK_DIVIDER_WIDTH );
#endif
globals->gridOn = XP_TRUE;
setCtrlsForTray( globals );
board_invalAll( board );
XP_Bool inOut[2];
board_zoom( board, 0, inOut );
setZoomButtons( globals, inOut );
return TRUE;
} /* configure_event */
/* Redraw the screen from the backing pixmap */
static gint
expose_event( GtkWidget* XP_UNUSED(widget),
GdkEventExpose* XP_UNUSED(event),
GtkGameGlobals* globals )
{
/*
gdk_draw_rectangle( widget->window,//((GtkDrawCtx*)globals->draw)->pixmap,
widget->style->white_gc,
TRUE,
0, 0,
widget->allocation.width,
widget->allocation.height+widget->allocation.y );
*/
/* I want to inval only the area that's exposed, but the rect is always
empty, even when clearly shouldn't be. Need to investigate. Until
fixed, use board_invalAll to ensure board is drawn.*/
/* board_invalRect( globals->cGlobals.game.board, (XP_Rect*)&event->area ); */
board_invalAll( globals->cGlobals.game.board );
board_draw( globals->cGlobals.game.board );
draw_gtk_status( globals->draw, globals->stateChar );
/* gdk_draw_pixmap( widget->window, */
/* widget->style->fg_gc[GTK_WIDGET_STATE (widget)], */
/* ((GtkDrawCtx*)globals->draw)->pixmap, */
/* event->area.x, event->area.y, */
/* event->area.x, event->area.y, */
/* event->area.width, event->area.height ); */
return FALSE;
} /* expose_event */
#if 0
static gint
handle_client_event( GtkWidget *widget, GdkEventClient *event,
GtkGameGlobals* globals )
{
XP_LOGF( "handle_client_event called: event->type = " );
if ( event->type == GDK_CLIENT_EVENT ) {
XP_LOGF( "GDK_CLIENT_EVENT" );
return 1;
} else {
XP_LOGF( "%d", event->type );
return 0;
}
} /* handle_client_event */
#endif
void
destroy_board_window( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
{
LOG_FUNC();
if ( !!globals->cGlobals.game.comms ) {
comms_stop( globals->cGlobals.game.comms );
}
saveGame( &globals->cGlobals );
windowDestroyed( globals );
// gtk_main_quit();
}
static void
cleanup( GtkGameGlobals* globals )
{
CommonGlobals* cGlobals = &globals->cGlobals;
saveGame( cGlobals );
g_source_remove( globals->idleID );
#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 );
gi_disposePlayerInfo( MEMPOOL cGlobals->gi );
dict_unref( cGlobals->dict );
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_bar_append( GTK_MENU_BAR(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( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
{
CommonGlobals* cGlobals = &globals->cGlobals;
if ( !!cGlobals->game.server ) {
XWStreamCtxt* stream =
mem_stream_make( MEMPOOL
cGlobals->params->vtMgr,
globals,
CHANNEL_NONE,
catOnClose );
server_formatDictCounts( cGlobals->game.server, stream, 5 );
stream_putU8( stream, '\n' );
stream_destroy( stream );
}
} /* tile_values */
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 );
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 );
gameOver = TRUE;
}
/* the end game listener will take care of printing the final scores */
} /* final_scores */
static XP_Bool
new_game_impl( GtkGameGlobals* globals, XP_Bool fireConnDlg )
{
XP_Bool success = XP_FALSE;
CommsAddrRec addr;
if ( !!globals->cGlobals.game.comms ) {
comms_getAddr( globals->cGlobals.game.comms, &addr );
} else {
comms_getInitialAddr( &addr, RELAY_NAME_DEFAULT, RELAY_PORT_DEFAULT );
}
CurGameInfo* gi = globals->cGlobals.gi;
success = newGameDialog( globals, gi, &addr, XP_TRUE, fireConnDlg );
if ( success ) {
#ifndef XWFEATURE_STANDALONE_ONLY
XP_Bool isClient = gi->serverRole == SERVER_ISCLIENT;
#endif
TransportProcs procs = {
.closure = globals,
.send = LINUX_SEND,
#ifdef COMMS_HEARTBEAT
.reset = linux_reset,
#endif
};
if ( !game_reset( MEMPOOL &globals->cGlobals.game, gi,
globals->cGlobals.util,
&globals->cGlobals.cp, &procs ) ) {
/* if ( NULL == globals->draw ) { */
/* globals->draw = (GtkDrawCtx*)gtkDrawCtxtMake( globals->drawing_area, */
/* globals ); */
/* } */
/* game_makeNewGame( MEMPOOL &globals->cGlobals.game, gi, */
/* globals->cGlobals.params->util, */
/* (DrawCtx*)globals->draw, */
/* &globals->cGlobals.cp, &procs, */
/* globals->cGlobals.params->gameSeed ); */
/* ModelCtxt* model = globals->cGlobals.game.model; */
/* if ( NULL == model_getDictionary( model ) ) { */
/* DictionaryCtxt* dict = */
/* linux_dictionary_make( MEMPOOL globals->cGlobals.params, */
/* gi->dictName, XP_TRUE ); */
/* model_setDictionary( model, dict ); */
/* } */
}
#ifndef XWFEATURE_STANDALONE_ONLY
if ( !!globals->cGlobals.game.comms ) {
comms_setAddr( globals->cGlobals.game.comms, &addr );
} else if ( gi->serverRole != SERVER_STANDALONE ) {
XP_ASSERT(0);
}
if ( isClient ) {
XWStreamCtxt* stream =
mem_stream_make( MEMPOOL globals->cGlobals.params->vtMgr,
&globals->cGlobals, CHANNEL_NONE,
sendOnClose );
(void)server_initClientConnection( globals->cGlobals.game.server,
stream );
}
#endif
(void)server_do( globals->cGlobals.game.server ); /* assign tiles, etc. */
board_invalAll( globals->cGlobals.game.board );
board_draw( globals->cGlobals.game.board );
}
return success;
} /* new_game_impl */
static void
new_game( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
{
new_game_impl( globals, XP_FALSE );
} /* new_game */
static void
game_info( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
{
CommsAddrRec addr;
comms_getAddr( globals->cGlobals.game.comms, &addr );
/* 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 ( newGameDialog( globals, gi, &addr, XP_FALSE, XP_FALSE ) ) {
if ( server_do( globals->cGlobals.game.server ) ) {
board_draw( globals->cGlobals.game.board );
}
}
}
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) params, name,
params->useMmap );
game_changeDict( MPPARM(cGlobals->util->mpool) &cGlobals->game,
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 );
}
}
#ifndef XWFEATURE_STANDALONE_ONLY
static void
handle_resend( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
{
CommsCtxt* comms = globals->cGlobals.game.comms;
if ( comms != NULL ) {
comms_resendAll( comms, 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 );
}
}
#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 );
comms_getStats( comms, stream );
stream_destroy( stream );
}
} /* handle_commstats */
#endif
#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 );
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 );
return XP_FALSE;
} /* pen_timer_func */
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
static GtkWidget*
createAddItem( GtkWidget* parent, gchar* label,
GtkSignalFunc handlerFunc, GtkGameGlobals* globals )
{
GtkWidget* item = gtk_menu_item_new_with_label( label );
/* g_print( "createAddItem called with label %s\n", label ); */
if ( handlerFunc != NULL ) {
g_signal_connect( GTK_OBJECT(item), "activate",
G_CALLBACK(handlerFunc), globals );
}
gtk_menu_append( GTK_MENU(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",
GTK_SIGNAL_FUNC(tile_values), globals );
(void)createAddItem( fileMenu, "Game history",
GTK_SIGNAL_FUNC(game_history), globals );
#ifdef TEXT_MODEL
(void)createAddItem( fileMenu, "Dump board",
GTK_SIGNAL_FUNC(dump_board), globals );
#endif
(void)createAddItem( fileMenu, "Final scores",
GTK_SIGNAL_FUNC(final_scores), globals );
(void)createAddItem( fileMenu, "New game",
GTK_SIGNAL_FUNC(new_game), globals );
(void)createAddItem( fileMenu, "Game info",
GTK_SIGNAL_FUNC(game_info), globals );
(void)createAddItem( fileMenu, "Load game",
GTK_SIGNAL_FUNC(load_game), globals );
(void)createAddItem( fileMenu, "Save game",
GTK_SIGNAL_FUNC(save_game), globals );
#ifdef XWFEATURE_CHANGEDICT
(void)createAddItem( fileMenu, "Change dictionary",
GTK_SIGNAL_FUNC(change_dictionary), globals );
#endif
(void)createAddItem( fileMenu, "Cancel trade",
GTK_SIGNAL_FUNC(handle_trade_cancel), globals );
fileMenu = makeAddSubmenu( menubar, "Edit" );
(void)createAddItem( fileMenu, "Undo",
GTK_SIGNAL_FUNC(handle_undo), globals );
(void)createAddItem( fileMenu, "Redo",
GTK_SIGNAL_FUNC(handle_redo), globals );
#ifdef FEATURE_TRAY_EDIT
(void)createAddItem( fileMenu, "Allow tray edit",
GTK_SIGNAL_FUNC(handle_trayEditToggle_on), globals );
(void)createAddItem( fileMenu, "Dis-allow tray edit",
GTK_SIGNAL_FUNC(handle_trayEditToggle_off), globals );
#endif
fileMenu = makeAddSubmenu( menubar, "Network" );
#ifndef XWFEATURE_STANDALONE_ONLY
(void)createAddItem( fileMenu, "Resend",
GTK_SIGNAL_FUNC(handle_resend), globals );
#ifdef XWFEATURE_COMMSACK
(void)createAddItem( fileMenu, "ack any",
GTK_SIGNAL_FUNC(handle_ack), globals );
#endif
# ifdef DEBUG
(void)createAddItem( fileMenu, "Stats",
GTK_SIGNAL_FUNC(handle_commstats), globals );
# endif
#endif
#ifdef MEM_DEBUG
(void)createAddItem( fileMenu, "Mem stats",
GTK_SIGNAL_FUNC(handle_memstats), globals );
#endif
#ifdef XWFEATURE_ACTIVERECT
fileMenu = makeAddSubmenu( menubar, "Test" );
(void)createAddItem( fileMenu, "Frame active area",
GTK_SIGNAL_FUNC(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 )
{
XP_U16 nPending = server_getPendingRegs( globals->cGlobals.game.server );
if ( !globals->invite_button && 0 < nPending ) {
globals->invite_button =
addButton( globals->buttons_hbox, "Invite",
G_CALLBACK(handle_invite_button), globals );
}
GameStateInfo gsi;
game_getState( &globals->cGlobals.game, &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
}
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 );
}
return TRUE;
} /* handle_flip_button */
static gboolean
handle_value_button( GtkWidget* XP_UNUSED(widget), gpointer closure )
{
GtkGameGlobals* globals = (GtkGameGlobals*)closure;
if ( board_toggle_showValues( globals->cGlobals.game.board ) ) {
board_draw( globals->cGlobals.game.board );
}
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,
#ifdef XWFEATURE_SEARCHLIMIT
XP_FALSE,
#endif
prev, &redo ) ) {
board_draw( globals->cGlobals.game.board );
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,
#ifdef XWFEATURE_SEARCHLIMIT
XP_TRUE,
#endif
XP_FALSE, &redo ) ) {
board_draw( globals->cGlobals.game.board );
}
} /* 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 ) ) {
board_draw( globals->cGlobals.game.board );
}
} /* handle_juggle_button */
static void
handle_undo_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
{
if ( server_handleUndo( globals->cGlobals.game.server, 0 ) ) {
board_draw( globals->cGlobals.game.board );
}
} /* 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 ) || board_replaceTiles( board ) ) {
board_draw( board );
}
}
static void
handle_trade_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
{
if ( board_beginTrade( globals->cGlobals.game.board ) ) {
board_draw( globals->cGlobals.game.board );
disenable_buttons( globals );
}
} /* handle_juggle_button */
static void
handle_done_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
{
if ( board_commitTurn( globals->cGlobals.game.board ) ) {
board_draw( globals->cGlobals.game.board );
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, 1, inOut ) ) {
board_draw( globals->cGlobals.game.board );
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, -1, inOut ) ) {
board_draw( globals->cGlobals.game.board );
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 ) {
server_sendChat( globals->cGlobals.game.server, msg );
g_free( msg );
}
}
#endif
static void
scroll_value_changed( GtkAdjustment *adj, GtkGameGlobals* globals )
{
XP_U16 newValue;
gfloat newValueF = adj->value;
/* XP_ASSERT( newValueF >= 0.0 */
/* && newValueF <= globals->cGlobals.params->nHidden ); */
newValue = (XP_U16)newValueF;
if ( board_setYOffset( globals->cGlobals.game.board, newValue ) ) {
board_draw( globals->cGlobals.game.board );
}
} /* 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 );
} /* 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;
globals->adjustment->page_size = nRows;
globals->adjustment->value = 0.0;
gtk_signal_emit_by_name( GTK_OBJECT(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 );
} else {
draw = board_showTray( board );
}
if ( draw ) {
board_draw( board );
}
} /* handle_hide_button */
static void
handle_commit_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
{
if ( board_commitTurn( globals->cGlobals.game.board ) ) {
board_draw( globals->cGlobals.game.board );
}
} /* handle_commit_button */
static void
handle_invite_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
{
const CurGameInfo* gi = globals->cGlobals.gi;
gchar* countStr;
gchar* phone = NULL;
gchar* portstr = NULL;
gchar* forceChannelStr;
AskMInfo infos[] = {
{ "Number of players", &countStr },
{ "Remote phone#", &phone },
{ "Remote port", &portstr },
{ "Force channel", &forceChannelStr },
};
XP_U16 nMissing = server_getPendingRegs( globals->cGlobals.game.server );
gchar buf[64];
sprintf( buf, "%d", nMissing );
countStr = buf;
gchar forceChannelBuf[64];
sprintf( forceChannelBuf, "%d", 1 );
forceChannelStr = forceChannelBuf;
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 );
}
} /* handle_invite_button */
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 VTableMgr*
gtk_util_getVTManager(XW_UtilCtxt* uc)
{
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
return globals->cGlobals.params->vtMgr;
} /* linux_util_getVTManager */
static XP_S16
gtk_util_userPickTileBlank( XW_UtilCtxt* uc, XP_U16 playerNum,
const XP_UCHAR** texts, XP_U16 nTiles )
{
XP_S16 chosen;
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
XP_UCHAR* name = globals->cGlobals.gi->players[playerNum].name;
chosen = gtkletterask( NULL, XP_FALSE, name, nTiles, texts );
return chosen;
}
static XP_S16
gtk_util_userPickTileTray( XW_UtilCtxt* uc, const PickInfo* pi,
XP_U16 playerNum, const XP_UCHAR** texts,
XP_U16 nTiles )
{
XP_S16 chosen;
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
XP_UCHAR* name = globals->cGlobals.gi->players[playerNum].name;
chosen = gtkletterask( pi, XP_TRUE, name, nTiles, texts );
return chosen;
} /* gtk_util_userPickTile */
static XP_Bool
gtk_util_askPassword( XW_UtilCtxt* XP_UNUSED(uc), const XP_UCHAR* name,
XP_UCHAR* buf, XP_U16* len )
{
XP_Bool ok = gtkpasswdask( name, buf, len );
return ok;
} /* 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( GTK_OBJECT(globals->adjustment), "changed" );
}
#endif
} /* setCtrlsForTray */
static void
gtk_util_trayHiddenChange( XW_UtilCtxt* uc, 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, XP_U16 maxOffset,
XP_U16 XP_UNUSED(oldOffset),
XP_U16 newOffset )
{
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
if ( !!globals->adjustment ) {
gint nRows = globals->cGlobals.gi->boardSize;
globals->adjustment->page_size = nRows - maxOffset;
globals->adjustment->value = newOffset;
gtk_adjustment_value_changed( GTK_ADJUSTMENT(globals->adjustment) );
}
} /* gtk_util_yOffsetChange */
static void
gtkShowFinalScores( const GtkGameGlobals* globals )
{
XWStreamCtxt* stream;
XP_UCHAR* text;
const CommonGlobals* cGlobals = &globals->cGlobals;
stream = mem_stream_make( MPPARM(cGlobals->util->mpool)
cGlobals->params->vtMgr,
NULL, CHANNEL_NONE, NULL );
server_writeFinalScores( cGlobals->game.server, stream );
text = strFromStream( stream );
stream_destroy( stream );
XP_U16 timeout = 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 ) {
XP_LOGF( "%s: rematch chosen!", __func__ );
XP_ASSERT( 0 );
}
} /* gtkShowFinalScores */
static void
gtk_util_informMove( XW_UtilCtxt* uc, XWStreamCtxt* expl,
XWStreamCtxt* words )
{
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
char* question = strFromStream( !!words? words : expl );
(void)gtkask( globals->window, question, GTK_BUTTONS_OK, NULL );
free( question );
}
static void
gtk_util_informUndo( XW_UtilCtxt* uc )
{
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, 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, 0 );
board_draw( cGlobals->game.board );
} else if ( !cGlobals->params->skipGameOver ) {
gtkShowFinalScores( globals );
}
} /* gtk_util_notifyGameOver */
static void
gtk_util_informNetDict( XW_UtilCtxt* uc, XP_LangCode XP_UNUSED(lang),
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 );
}
}
static gint
changeRoles( gpointer data )
{
linuxChangeRoles( (CommonGlobals*)data );
return 0;
}
static void
gtk_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer )
{
CommonGlobals* cGlobals = (CommonGlobals*)uc->closure;
linuxSetIsServer( cGlobals, isServer );
(void)g_idle_add( changeRoles, cGlobals );
}
/* 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, XP_U16 col, XP_U16 row )
{
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
#ifndef DONT_ABORT_ENGINE
gboolean pending;
#endif
board_hiliteCellAt( globals->cGlobals.game.board, 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 )
{
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
return globals->altKeyDown;
}
static XP_Bool
gtk_util_engineProgressCallback( XW_UtilCtxt* XP_UNUSED(uc) )
{
#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 void
cancelTimer( GtkGameGlobals* globals, XWTimerReason why )
{
guint src = globals->timerSources[why-1];
if ( src != 0 ) {
g_source_remove( src );
globals->timerSources[why-1] = 0;
}
} /* cancelTimer */
static gint
pen_timer_func( gpointer data )
{
GtkGameGlobals* globals = (GtkGameGlobals*)data;
if ( linuxFireTimer( &globals->cGlobals, TIMER_PENDOWN ) ) {
board_draw( globals->cGlobals.game.board );
}
return XP_FALSE;
} /* pen_timer_func */
static gint
score_timer_func( gpointer data )
{
GtkGameGlobals* globals = (GtkGameGlobals*)data;
if ( linuxFireTimer( &globals->cGlobals, TIMER_TIMERTICK ) ) {
board_draw( globals->cGlobals.game.board );
}
return XP_FALSE;
} /* score_timer_func */
#ifndef XWFEATURE_STANDALONE_ONLY
static gint
comms_timer_func( gpointer data )
{
GtkGameGlobals* globals = (GtkGameGlobals*)data;
if ( linuxFireTimer( &globals->cGlobals, TIMER_COMMS ) ) {
board_draw( globals->cGlobals.game.board );
}
return (gint)0;
}
#endif
#ifdef XWFEATURE_SLOW_ROBOT
static gint
slowrob_timer_func( gpointer data )
{
GtkGameGlobals* globals = (GtkGameGlobals*)data;
if ( linuxFireTimer( &globals->cGlobals, TIMER_SLOWROBOT ) ) {
board_draw( globals->cGlobals.game.board );
}
return (gint)0;
}
#endif
static void
gtk_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why,
XP_U16 XP_UNUSED_STANDALONE(when),
XWTimerProc proc, void* closure )
{
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
guint newSrc;
cancelTimer( globals, why );
if ( why == TIMER_PENDOWN ) {
if ( 0 != globals->timerSources[why-1] ) {
g_source_remove( globals->timerSources[why-1] );
}
newSrc = g_timeout_add( 1000, pen_timer_func, globals );
} else if ( why == TIMER_TIMERTICK ) {
/* one second */
globals->scoreTimerInterval = 100 * 10000;
(void)gettimeofday( &globals->scoreTv, NULL );
newSrc = g_timeout_add( 1000, score_timer_func, globals );
#ifndef XWFEATURE_STANDALONE_ONLY
} else if ( why == TIMER_COMMS ) {
newSrc = g_timeout_add( 1000 * when, comms_timer_func, globals );
#endif
#ifdef XWFEATURE_SLOW_ROBOT
} else if ( why == TIMER_SLOWROBOT ) {
newSrc = g_timeout_add( 1000 * when, slowrob_timer_func, globals );
#endif
} else {
XP_ASSERT( 0 );
}
globals->cGlobals.timerInfo[why].proc = proc;
globals->cGlobals.timerInfo[why].closure = closure;
XP_ASSERT( newSrc != 0 );
globals->timerSources[why-1] = newSrc;
} /* gtk_util_setTimer */
static void
gtk_util_clearTimer( XW_UtilCtxt* uc, XWTimerReason why )
{
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
globals->cGlobals.timerInfo[why].proc = NULL;
}
static gint
idle_func( gpointer data )
{
GtkGameGlobals* globals = (GtkGameGlobals*)data;
/* XP_DEBUGF( "idle_func called\n" ); */
/* remove before calling server_do. If server_do puts up a dialog that
calls gtk_main, then this idle proc will also apply to that event loop
and bad things can happen. So kill the idle proc asap. */
g_source_remove( globals->idleID );
ServerCtxt* server = globals->cGlobals.game.server;
if ( !!server && server_do( server ) ) {
if ( !!globals->cGlobals.game.board ) {
board_draw( globals->cGlobals.game.board );
}
}
return 0; /* 0 will stop it from being called again */
} /* idle_func */
static void
gtk_util_requestTime( XW_UtilCtxt* uc )
{
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
globals->idleID = g_idle_add( idle_func, globals );
} /* gtk_util_requestTime */
static XP_Bool
gtk_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 player,
XP_Bool turnLost )
{
XP_Bool result;
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
char buf[300];
if ( turnLost ) {
char wordsBuf[256];
XP_U16 i;
XP_UCHAR* name = globals->cGlobals.gi->players[player].name;
XP_ASSERT( !!name );
for ( i = 0, wordsBuf[0] = '\0'; ; ) {
char wordBuf[18];
sprintf( wordBuf, "\"%s\"", bwi->words[i] );
strcat( wordsBuf, wordBuf );
if ( ++i == bwi->nWords ) {
break;
}
strcat( wordsBuf, ", " );
}
sprintf( buf, "Player %d (%s) played illegal word[s] %s; loses turn.",
player+1, name, wordsBuf );
if ( globals->cGlobals.params->skipWarnings ) {
XP_LOGF( "%s", buf );
} else {
gtkUserError( globals, buf );
}
result = XP_TRUE;
} else {
XP_ASSERT( bwi->nWords == 1 );
sprintf( buf, "Word \"%s\" not in the current dictionary (%s). "
"Use it anyway?", bwi->words[0], bwi->dictName );
result = GTK_RESPONSE_YES == gtkask( globals->window, buf,
GTK_BUTTONS_YES_NO, NULL );
}
return result;
} /* gtk_util_warnIllegalWord */
static void
gtk_util_remSelected( XW_UtilCtxt* uc )
{
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
XWStreamCtxt* stream;
XP_UCHAR* text;
stream = mem_stream_make( MEMPOOL
globals->cGlobals.params->vtMgr,
globals, CHANNEL_NONE, NULL );
board_formatRemainingTiles( globals->cGlobals.game.board, stream );
text = strFromStream( stream );
stream_destroy( stream );
(void)gtkask( globals->window, text, GTK_BUTTONS_OK, NULL );
free( text );
}
#ifndef XWFEATURE_STANDALONE_ONLY
static XWStreamCtxt*
gtk_util_makeStreamFromAddr(XW_UtilCtxt* uc, XP_PlayerAddr channelNo )
{
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
XWStreamCtxt* stream = mem_stream_make( MEMPOOL
globals->cGlobals.params->vtMgr,
&globals->cGlobals, channelNo,
sendOnClose );
return stream;
} /* gtk_util_makeStreamFromAddr */
#ifdef XWFEATURE_CHAT
static void
gtk_util_showChat( XW_UtilCtxt* uc, const XP_UCHAR* const msg )
{
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
(void)gtkask( globals->window, msg, GTK_BUTTONS_OK, NULL );
}
#endif
#endif
#ifdef XWFEATURE_SEARCHLIMIT
static XP_Bool
gtk_util_getTraySearchLimits( XW_UtilCtxt* XP_UNUSED(uc),
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, XWBonusType bonus )
{
LOG_FUNC();
XP_USE( uc );
XP_USE( bonus );
}
static void
gtk_util_playerScoreHeld( XW_UtilCtxt* uc, XP_U16 player )
{
LOG_FUNC();
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
LastMoveInfo lmi;
if ( model_getPlayersLastScore( globals->cGlobals.game.model,
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, XWStreamCtxt* words )
{
XP_USE( uc );
catOnClose( words, NULL );
fprintf( stderr, "\n" );
}
#endif
static void
gtk_util_userError( XW_UtilCtxt* uc, 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 XP_Bool
gtk_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id,
XWStreamCtxt* stream )
{
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
XP_Bool result;
char* question;
XP_Bool freeMe = XP_FALSE;
GtkButtonsType buttons = GTK_BUTTONS_YES_NO;
switch( id ) {
case QUERY_COMMIT_TURN:
question = strFromStream( stream );
freeMe = XP_TRUE;
break;
case QUERY_ROBOT_TRADE:
question = strFromStream( stream );
freeMe = XP_TRUE;
buttons = GTK_BUTTONS_OK;
break;
default:
XP_ASSERT( 0 );
return XP_FALSE;
}
gint chosen = gtkask( globals->window, question, buttons, NULL );
result = GTK_RESPONSE_OK == chosen || chosen == GTK_RESPONSE_YES;
if ( freeMe ) {
free( question );
}
return result;
} /* gtk_util_userQuery */
static XP_Bool
gtk_util_confirmTrade( XW_UtilCtxt* uc,
const XP_UCHAR** tiles, XP_U16 nTiles )
{
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
char question[256];
formatConfirmTrade( tiles, nTiles, question, sizeof(question) );
return GTK_RESPONSE_YES == gtkask( globals->window, question,
GTK_BUTTONS_YES_NO, NULL );
}
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( GTK_OBJECT(button), "clicked", func, closure );
}
return button;
} /* makeShowButtonFromBitmap */
static GtkWidget*
makeVerticalBar( GtkGameGlobals* globals, GtkWidget* XP_UNUSED(window) )
{
GtkWidget* vbox;
GtkWidget* button;
vbox = gtk_vbutton_box_new();
button = makeShowButtonFromBitmap( globals, "../flip.xpm", "f",
G_CALLBACK(handle_flip_button) );
gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 );
globals->flip_button = button;
button = makeShowButtonFromBitmap( globals, "../value.xpm", "v",
G_CALLBACK(handle_value_button) );
gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 );
button = makeShowButtonFromBitmap( globals, "../hint.xpm", "?-",
G_CALLBACK(handle_prevhint_button) );
globals->prevhint_button = button;
gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 );
button = makeShowButtonFromBitmap( globals, "../hint.xpm", "?+",
G_CALLBACK(handle_nexthint_button) );
globals->nexthint_button = button;
gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 );
button = makeShowButtonFromBitmap( globals, "../hintNum.xpm", "n",
G_CALLBACK(handle_nhint_button) );
gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 );
button = makeShowButtonFromBitmap( globals, "../colors.xpm", "c",
G_CALLBACK(handle_colors_button) );
gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 );
/* undo and redo buttons */
button = makeShowButtonFromBitmap( globals, "../undo.xpm", "U",
G_CALLBACK(handle_undo_button) );
gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 );
button = makeShowButtonFromBitmap( globals, "../redo.xpm", "R",
G_CALLBACK(handle_redo_button) );
gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 );
button = makeShowButtonFromBitmap( globals, "", "u/r",
G_CALLBACK(handle_toggle_undo) );
globals->toggle_undo_button = button;
gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 );
/* the four buttons that on palm are beside the tray */
button = makeShowButtonFromBitmap( globals, "../juggle.xpm", "j",
G_CALLBACK(handle_juggle_button) );
gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 );
button = makeShowButtonFromBitmap( globals, "../trade.xpm", "t",
G_CALLBACK(handle_trade_button) );
gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 );
button = makeShowButtonFromBitmap( globals, "../done.xpm", "d",
G_CALLBACK(handle_done_button) );
gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 );
button = makeShowButtonFromBitmap( globals, "../done.xpm", "+",
G_CALLBACK(handle_zoomin_button) );
globals->zoomin_button = button;
gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 );
button = makeShowButtonFromBitmap( globals, "../done.xpm", "-",
G_CALLBACK(handle_zoomout_button) );
globals->zoomout_button = button;
gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 );
#ifdef XWFEATURE_CHAT
button = makeShowButtonFromBitmap( globals, "", "chat",
G_CALLBACK(handle_chat_button) );
globals->chat_button = button;
gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 );
#endif
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( GTK_OBJECT(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_hbox_new( FALSE, 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->vtable->m_util_userError = gtk_util_userError;
util->vtable->m_util_userQuery = gtk_util_userQuery;
util->vtable->m_util_confirmTrade = gtk_util_confirmTrade;
util->vtable->m_util_getVTManager = gtk_util_getVTManager;
util->vtable->m_util_userPickTileBlank = gtk_util_userPickTileBlank;
util->vtable->m_util_userPickTileTray = gtk_util_userPickTileTray;
util->vtable->m_util_askPassword = gtk_util_askPassword;
util->vtable->m_util_trayHiddenChange = gtk_util_trayHiddenChange;
util->vtable->m_util_yOffsetChange = gtk_util_yOffsetChange;
util->vtable->m_util_informMove = gtk_util_informMove;
util->vtable->m_util_informUndo = gtk_util_informUndo;
util->vtable->m_util_notifyGameOver = gtk_util_notifyGameOver;
util->vtable->m_util_informNetDict = gtk_util_informNetDict;
util->vtable->m_util_setIsServer = gtk_util_setIsServer;
#ifdef XWFEATURE_HILITECELL
util->vtable->m_util_hiliteCell = gtk_util_hiliteCell;
#endif
util->vtable->m_util_altKeyDown = gtk_util_altKeyDown;
util->vtable->m_util_engineProgressCallback =
gtk_util_engineProgressCallback;
util->vtable->m_util_setTimer = gtk_util_setTimer;
util->vtable->m_util_clearTimer = gtk_util_clearTimer;
util->vtable->m_util_requestTime = gtk_util_requestTime;
util->vtable->m_util_warnIllegalWord = gtk_util_warnIllegalWord;
util->vtable->m_util_remSelected = gtk_util_remSelected;
#ifndef XWFEATURE_STANDALONE_ONLY
util->vtable->m_util_makeStreamFromAddr = gtk_util_makeStreamFromAddr;
#endif
#ifdef XWFEATURE_CHAT
util->vtable->m_util_showChat = gtk_util_showChat;
#endif
#ifdef XWFEATURE_SEARCHLIMIT
util->vtable->m_util_getTraySearchLimits = gtk_util_getTraySearchLimits;
#endif
#ifndef XWFEATURE_MINIWIN
util->vtable->m_util_bonusSquareHeld = gtk_util_bonusSquareHeld;
util->vtable->m_util_playerScoreHeld = gtk_util_playerScoreHeld;
#endif
#ifdef XWFEATURE_BOARDWORDS
util->vtable->m_util_cellSquareHeld = gtk_util_cellSquareHeld;
#endif
util->closure = globals;
} /* setupGtkUtilCallbacks */
#ifndef XWFEATURE_STANDALONE_ONLY
typedef struct _SockInfo {
GIOChannel* channel;
guint watch;
int socket;
} SockInfo;
static void
gtk_socket_added( void* closure, int newSock, GIOFunc proc )
{
GtkGameGlobals* globals = (GtkGameGlobals*)closure;
if ( newSock != -1 ) {
XP_ASSERT( !!proc );
GIOChannel* channel = g_io_channel_unix_new( newSock );
g_io_channel_set_close_on_unref( channel, TRUE );
#ifdef DEBUG
guint result =
#endif
g_io_add_watch( channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI,
proc, globals );
XP_LOGF( "g_io_add_watch(%d) => %d", newSock, result );
}
/* A hack for the bluetooth case. */
CommsCtxt* comms = globals->cGlobals.game.comms;
CommsAddrRec addr;
comms_getAddr( comms, &addr );
if ( (comms != NULL) && (addr_hasType( &addr, COMMS_CONN_BT) ) ) {
comms_resendAll( comms, XP_FALSE );
}
LOG_RETURN_VOID();
} /* gtk_socket_changed */
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 */
#endif /* #ifndef XWFEATURE_STANDALONE_ONLY */
/* 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,
CurGameInfo* gi )
{
memset( globals, 0, sizeof(*globals) );
globals->cGlobals.gi = &globals->gi;
if ( !gi ) {
gi = &params->pgi;
}
gi_copy( MPPARM(params->mpool) globals->cGlobals.gi, gi );
globals->cGlobals.params = params;
globals->cGlobals.lastNTilesToUse = MAX_TRAY_TILES;
#ifndef XWFEATURE_STANDALONE_ONLY
# ifdef XWFEATURE_RELAY
globals->cGlobals.relaySocket = -1;
# endif
globals->cGlobals.socketAdded = gtk_socket_added;
globals->cGlobals.socketAddedClosure = globals;
globals->cGlobals.onSave = onGameSaved;
globals->cGlobals.onSaveClosure = globals;
globals->cGlobals.addAcceptor = gtk_socket_acceptor;
#endif
globals->cGlobals.cp.showBoardArrow = XP_TRUE;
globals->cGlobals.cp.hideTileValues = params->hideValues;
globals->cGlobals.cp.skipCommitConfirm = params->skipCommitConfirm;
globals->cGlobals.cp.sortNewTiles = params->sortNewTiles;
globals->cGlobals.cp.showColors = params->showColors;
globals->cGlobals.cp.allowPeek = params->allowPeek;
globals->cGlobals.cp.showRobotScores = params->showRobotScores;
#ifdef XWFEATURE_SLOW_ROBOT
globals->cGlobals.cp.robotThinkMin = params->robotThinkMin;
globals->cGlobals.cp.robotThinkMax = params->robotThinkMax;
globals->cGlobals.cp.robotTradePct = params->robotTradePct;
#endif
#ifdef XWFEATURE_CROSSHAIRS
globals->cGlobals.cp.hideCrosshairs = params->hideCrosshairs;
#endif
setupUtil( &globals->cGlobals );
setupGtkUtilCallbacks( globals, globals->cGlobals.util );
}
void
initGlobals( GtkGameGlobals* globals, LaunchParams* params, CurGameInfo* gi )
{
CommonGlobals* cGlobals = &globals->cGlobals;
short width, height;
GtkWidget* window;
GtkWidget* drawing_area;
GtkWidget* menubar;
GtkWidget* vbox;
GtkWidget* hbox;
initGlobalsNoDraw( globals, params, gi );
if ( !!gi ) {
XP_ASSERT( !cGlobals->dict );
cGlobals->dict = linux_dictionary_make( MEMPOOL params,
gi->dictName, XP_TRUE );
gi->dictLang = dict_getLangCode( cGlobals->dict );
}
globals->window = window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
if ( !!params->fileName ) {
gtk_window_set_title( GTK_WINDOW(window), params->fileName );
}
vbox = gtk_vbox_new (FALSE, 0);
gtk_container_add( GTK_CONTAINER(window), vbox );
gtk_widget_show( vbox );
g_signal_connect( G_OBJECT (window), "destroy",
G_CALLBACK( destroy_board_window ), globals );
menubar = makeMenus( globals );
gtk_box_pack_start( GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
#if ! defined XWFEATURE_STANDALONE_ONLY && defined DEBUG
globals->drop_checks_vbox = gtk_vbox_new( FALSE, 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();
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_hbox_new( FALSE, 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;
gint nRows = cGlobals->gi->boardSize;
globals->adjustment = (GtkAdjustment*)
gtk_adjustment_new( 0, 0, nRows, 1, 2,
nRows - params->nHidden );
vscrollbar = gtk_vscrollbar_new( globals->adjustment );
g_signal_connect( GTK_OBJECT(globals->adjustment), "value_changed",
G_CALLBACK(scroll_value_changed), globals );
gtk_widget_show( vscrollbar );
gtk_box_pack_start( GTK_BOX(hbox), vscrollbar, TRUE, TRUE, 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);
g_signal_connect( GTK_OBJECT(drawing_area), "expose_event",
G_CALLBACK(expose_event), globals );
g_signal_connect( GTK_OBJECT(drawing_area),"configure_event",
G_CALLBACK(configure_event), globals );
g_signal_connect( GTK_OBJECT(drawing_area), "button_press_event",
G_CALLBACK(button_press_event), globals );
g_signal_connect( GTK_OBJECT(drawing_area), "motion_notify_event",
G_CALLBACK(motion_notify_event), globals );
g_signal_connect( GTK_OBJECT(drawing_area), "button_release_event",
G_CALLBACK(button_release_event), globals );
setOneSecondTimer( cGlobals );
#ifdef KEY_SUPPORT
# ifdef KEYBOARD_NAV
g_signal_connect( GTK_OBJECT(window), "key_press_event",
G_CALLBACK(key_press_event), globals );
# endif
g_signal_connect( GTK_OBJECT(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 );
TransportProcs procs;
setTransportProcs( &procs, globals );
CommonGlobals* cGlobals = &globals->cGlobals;
cGlobals->selRow = rowid;
cGlobals->pDb = pDb;
XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool)
params->vtMgr, cGlobals,
CHANNEL_NONE, NULL );
XP_Bool loaded = loadGame( stream, cGlobals->pDb, rowid );
if ( loaded ) {
if ( NULL == cGlobals->dict ) {
cGlobals->dict = makeDictForStream( cGlobals, stream );
}
loaded = game_makeFromStream( MEMPOOL stream, &cGlobals->game,
cGlobals->gi, cGlobals->dict,
&cGlobals->dicts, cGlobals->util,
(DrawCtx*)NULL, &cGlobals->cp, &procs );
if ( loaded ) {
XP_LOGF( "%s: game loaded", __func__ );
#ifndef XWFEATURE_STANDALONE_ONLY
if ( !!globals->cGlobals.game.comms ) {
comms_resendAll( globals->cGlobals.game.comms, XP_FALSE );
}
#endif
}
}
stream_destroy( stream );
return loaded;
}
XP_Bool
makeNewGame( GtkGameGlobals* globals )
{
CommonGlobals* cGlobals = &globals->cGlobals;
if ( !!cGlobals->game.comms ) {
comms_getAddr( cGlobals->game.comms, &cGlobals->addr );
} else {
LaunchParams* params = globals->cGlobals.params;
const XP_UCHAR* relayName = params->connInfo.relay.relayName;
if ( !relayName ) {
relayName = RELAY_NAME_DEFAULT;
}
XP_U16 relayPort = params->connInfo.relay.defaultSendPort;
if ( 0 == relayPort ) {
relayPort = RELAY_PORT_DEFAULT;
}
comms_getInitialAddr( &cGlobals->addr, relayName, relayPort );
}
CurGameInfo* gi = cGlobals->gi;
XP_Bool success = newGameDialog( globals, gi, &cGlobals->addr,
XP_TRUE, XP_FALSE );
if ( success && !!gi->dictName && !cGlobals->dict ) {
cGlobals->dict =
linux_dictionary_make( MEMPOOL cGlobals->params,
gi->dictName, XP_TRUE );
gi->dictLang = dict_getLangCode( cGlobals->dict );
}
LOG_RETURNF( "%d", success );
return success;
}
#endif /* PLATFORM_GTK */