xwords/wince/cemain.c
ehouse 8f08667f5d Lots more changes, and some reversions. Win32, gtk and ncurses all
work now including saving/opening games.  Network play probably
doesn't as writing/reading a dict from stream has to change.  It's
doubtful patches from this branch will be merged.  Rather, I'll take
the concepts and crib some code when re-implementing.  Concepts:
XP_UCHAR becomes UTF-8 on both platforms, and on wince we translate to
wchar_t just as now but from the utf-8 codepage.  That keeps the work
and risk to a minimum.
2009-03-29 15:53:15 +00:00

3443 lines
107 KiB
C
Executable file

/* -*- fill-column: 77; compile-command: "make -j TARGET_OS=wince DEBUG=TRUE" -*- */
/*
* Copyright 2002-2009 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.
*
* Derived from code generated by M$'s eVC++.
*/
#include "stdafx.h"
#include "xwords4.h"
#include <commctrl.h>
#include <commdlg.h>
#include <stdio.h>
#include <time.h> /* time() */
#include <winuser.h>
#ifdef _WIN32_WCE
# include <aygshell.h>
#endif
/* #include <imm.h> */
#include "strutils.h"
#include "memstream.h"
#include "cemain.h"
#include "cedefines.h"
#include "ceginfo.h"
#include "cestrbx.h"
#include "cedict.h"
#include "ceblank.h"
#include "ceprefs.h"
#include "ceaskpwd.h"
#include "ceutil.h"
#include "ceir.h"
#include "ceclrsel.h"
#include "cehntlim.h"
#include "cedebug.h"
#include "LocalizedStrIncludes.h"
#include "debhacks.h"
#include "cesvdgms.h"
#include "cedraw.h"
#include "cesms.h"
#include "cesockwr.h"
#include "dbgutil.h"
#define MAX_LOADSTRING 100
#define MAX_SCROLLBAR_WIDTH 12
#define MIN_SCROLLBAR_WIDTH 6
#define SCROLLBARID 0x4321 /* needs to be unique! */
#ifdef MEM_DEBUG
# define MEMPOOL globals->mpool,
#else
# define MEMPOOL
#endif
typedef struct FileWriteState {
CEAppGlobals* globals;
XP_UCHAR* path;
} FileWriteState;
/* forward util function decls */
#ifndef XWFEATURE_STANDALONE_ONLY
static XP_S16 ce_send_proc( const XP_U8* buf, XP_U16 len,
const CommsAddrRec* addr,
void* closure );
#define CE_SEND_PROC ce_send_proc
#else
#define CE_SEND_PROC NULL
#endif
#ifdef COMMS_HEARTBEAT
static void ce_reset_proc( void* closure );
# define CE_RESET_PROC ce_reset_proc,
#else
# define CE_RESET_PROC
#endif
static VTableMgr* ce_util_getVTManager( XW_UtilCtxt* uc );
static void ce_util_userError( XW_UtilCtxt* uc, UtilErrID id );
static XP_Bool ce_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id,
XWStreamCtxt* stream );
static XWBonusType ce_util_getSquareBonus( XW_UtilCtxt* uc,
const ModelCtxt* model,
XP_U16 col, XP_U16 row );
static XP_S16 ce_util_userPickTile( XW_UtilCtxt* uc, const PickInfo* pi,
XP_U16 playerNum,
const XP_UCHAR4* texts, XP_U16 nTiles );
static XP_Bool ce_util_askPassword( XW_UtilCtxt* uc, const XP_UCHAR* name,
XP_UCHAR* buf, XP_U16* len );
static void ce_util_trayHiddenChange( XW_UtilCtxt* uc,
XW_TrayVisState newState,
XP_U16 nVisibleRows );
static void ce_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 oldOffset,
XP_U16 newOffset );
static void ce_util_turnChanged( XW_UtilCtxt* uc );
static void ce_util_notifyGameOver( XW_UtilCtxt* uc );
static XP_Bool ce_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 col,
XP_U16 row );
static XP_Bool ce_util_engineProgressCallback( XW_UtilCtxt* uc );
static void ce_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, XP_U16 when,
XWTimerProc proc, void* closure);
static XP_Bool ce_util_altKeyDown( XW_UtilCtxt* uc );
static void ce_util_requestTime( XW_UtilCtxt* uc );
static XP_U32 ce_util_getCurSeconds( XW_UtilCtxt* uc );
static DictionaryCtxt* ce_util_makeEmptyDict( XW_UtilCtxt* uc );
#ifdef XWFEATURE_RELAY
static XWStreamCtxt* ce_util_makeStreamFromAddr( XW_UtilCtxt* uc,
XP_PlayerAddr channelNo );
#endif
static const XP_UCHAR* ce_util_getUserString( XW_UtilCtxt* uc,
XP_U16 stringCode );
static XP_Bool ce_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi,
XP_U16 turn, XP_Bool turnLost );
static void ce_util_remSelected( XW_UtilCtxt* uc );
#ifndef XWFEATURE_STANDALONE_ONLY
static void ce_util_addrChange( XW_UtilCtxt* uc, const CommsAddrRec* oldAddr,
const CommsAddrRec* newAddr );
#endif
#ifdef XWFEATURE_SEARCHLIMIT
static XP_Bool ce_util_getTraySearchLimits( XW_UtilCtxt* uc, XP_U16* min,
XP_U16* max );
#endif
#ifdef SHOW_PROGRESS
static void ce_util_engineStarting( XW_UtilCtxt* uc );
static void ce_util_engineStopping( XW_UtilCtxt* uc );
#endif
static XP_Bool ceMsgFromStream( CEAppGlobals* globals, XWStreamCtxt* stream,
wchar_t* title, XP_U16 buttons,
XP_Bool destroy );
static void RECTtoXPR( XP_Rect* dest, const RECT* src );
static XP_Bool ceDoNewGame( CEAppGlobals* globals );
static XP_Bool ceSaveCurGame( CEAppGlobals* globals, XP_Bool autoSave );
static void closeGame( CEAppGlobals* globals );
static void ceInitPrefs( CEAppGlobals* globals, CEAppPrefs* prefs );
static void updateForColors( CEAppGlobals* globals );
static XWStreamCtxt* make_generic_stream( const CEAppGlobals* globals );
#ifdef XWFEATURE_RELAY
static void ce_send_on_close( XWStreamCtxt* stream, void* closure );
#endif
static XP_Bool ceSetDictName( const wchar_t* wPath, XP_U16 index, void* ctxt );
static int messageBoxStream( CEAppGlobals* globals, XWStreamCtxt* stream,
wchar_t* title, XP_U16 buttons );
static XP_Bool ceQueryFromStream( CEAppGlobals* globals, XWStreamCtxt* stream);
static XP_Bool isDefaultName( CEAppGlobals* globals, const XP_UCHAR* name );
static void ceSetTitleFromName( CEAppGlobals* globals );
static void removeScrollbar( CEAppGlobals* globals );
#ifndef _WIN32_WCE
/* Very basic cmdline args meant at first to let me vary the size of the
* screen. Form is of arg=digits, with no spaces and digits having to be an
* integer. Right now only width and height work: e.g.
* "wine obj_win32_dbg/xwords4.exe height=240 width=320"
*/
static XP_Bool
tryIntParam( const char* buf, const char* param, XP_U16* value )
{
XP_U16 len = strlen( param );
XP_Bool found = 0 == strncmp( buf, param, len );
if ( found ) {
*value = atoi( &buf[len] );
}
return found;
}
static void
parseCmdLine( const char* cmdline, XP_U16* width, XP_U16* height )
{
XP_U16 ii;
for ( ii = 0; ; ++ii ) {
const char* cmd;
char ch;
char buf[64];
int len;
for ( cmd = cmdline ; ; ++cmd ) {
ch = *cmd;
if ( ch == '\0' || ch == ' ' ) {
break;
}
}
len = cmd - cmdline;
if ( len < sizeof(buf) ) {
memcpy( buf, cmdline, cmd - cmdline );
buf[len] = '\0';
if ( ii > 0 ) { /* skip argv[0] */
if ( tryIntParam( buf, "width=", width ) ) {
} else if ( tryIntParam( buf, "height=", height ) ) {
} else {
XP_LOGF( "failed to match cmdline arg \"%s\"", buf );
}
}
}
if ( ch == '\0' ) {
break;
}
cmdline = ++cmd;
}
}
#endif
// Forward declarations of functions included in this code module:
ATOM MyRegisterClass (HINSTANCE, LPTSTR);
BOOL InitInstance (HINSTANCE, int
#ifndef _WIN32_WCE
, XP_U16, XP_U16
#endif
);
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ceAbout (HWND, UINT, WPARAM, LPARAM);
int WINAPI
WinMain( HINSTANCE hInstance,
HINSTANCE XP_UNUSED(hPrevInstance),
#ifdef _WIN32_WCE
LPWSTR XP_UNUSED_CE(lpCmdLine),
#else
LPSTR lpCmdLine,
#endif
int nCmdShow)
{
MSG msg;
HACCEL hAccelTable;
#ifndef _WIN32_WCE
XP_U16 width = 320, height = 320;
parseCmdLine( lpCmdLine, &width, &height );
#endif
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow
#ifndef _WIN32_WCE
, width, height
#endif
)) {
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_XWORDS4);
// Main message loop. Return of 0 indicates quit message. Return of -1
// indicates major error (so we just bail.)
while ( 0 < GetMessage(&msg, NULL, 0, 0) ) {
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
/* This would be a good place to free up memory, close sockets, etc. */
LOG_RETURNF( "%d", msg.wParam );
return msg.wParam;
}
#ifdef __GNUC__
int
main()
{
LOG_FUNC();
return WinMain( GetModuleHandle(NULL), 0,
#ifdef _WIN32_WCE
GetCommandLineW(),
#else
GetCommandLineA(),
#endif
SW_SHOWDEFAULT );
}
#endif
//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
// COMMENTS:
//
// It is important to call this function so that the application
// will get 'well formed' small icons associated with it.
//
ATOM
MyRegisterClass(HINSTANCE hInstance, LPTSTR szWindowClass)
{
WNDCLASS wc;
XP_MEMSET( &wc, 0, sizeof(wc) );
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbWndExtra = sizeof(CEAppGlobals*);
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_XWORDS4));
wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
#ifndef _WIN32_WCE
wc.lpszMenuName = (LPCTSTR)IDM_MENU;
#endif
wc.lpszClassName = szWindowClass;
return RegisterClass(&wc);
}
static void
ceInitUtilFuncs( CEAppGlobals* globals )
{
UtilVtable* vtable = globals->util.vtable =
XP_MALLOC( globals->mpool, sizeof( UtilVtable ) );
globals->util.closure = (void*)globals;
globals->util.gameInfo = &globals->gameInfo;
MPASSIGN( globals->util.mpool, globals->mpool );
vtable->m_util_getVTManager = ce_util_getVTManager;
vtable->m_util_userError = ce_util_userError;
vtable->m_util_getSquareBonus = ce_util_getSquareBonus;
vtable->m_util_userQuery = ce_util_userQuery;
vtable->m_util_userPickTile = ce_util_userPickTile;
vtable->m_util_askPassword = ce_util_askPassword;
vtable->m_util_trayHiddenChange = ce_util_trayHiddenChange;
vtable->m_util_yOffsetChange = ce_util_yOffsetChange;
vtable->m_util_turnChanged = ce_util_turnChanged;
vtable->m_util_notifyGameOver = ce_util_notifyGameOver;
vtable->m_util_hiliteCell = ce_util_hiliteCell;
vtable->m_util_engineProgressCallback = ce_util_engineProgressCallback;
vtable->m_util_setTimer = ce_util_setTimer;
vtable->m_util_altKeyDown = ce_util_altKeyDown;
vtable->m_util_requestTime = ce_util_requestTime;
vtable->m_util_getCurSeconds = ce_util_getCurSeconds;
vtable->m_util_makeEmptyDict = ce_util_makeEmptyDict;
vtable->m_util_getUserString = ce_util_getUserString;
vtable->m_util_warnIllegalWord = ce_util_warnIllegalWord;
vtable->m_util_remSelected = ce_util_remSelected;
#ifdef XWFEATURE_RELAY
vtable->m_util_addrChange = ce_util_addrChange;
vtable->m_util_makeStreamFromAddr = ce_util_makeStreamFromAddr;
#endif
#ifdef XWFEATURE_SEARCHLIMIT
vtable->m_util_getTraySearchLimits = ce_util_getTraySearchLimits;
#endif
#ifdef SHOW_PROGRESS
vtable->m_util_engineStarting = ce_util_engineStarting;
vtable->m_util_engineStopping = ce_util_engineStopping;
#endif
} /* ceInitUtilFuncs */
#ifdef CEFEATURE_CANSCROLL
# define SCROLL_SHRINK 1
static void
updateScrollInfo( CEAppGlobals* globals, XP_U16 nHidden )
{
SCROLLINFO sinfo;
XP_MEMSET( &sinfo, 0, sizeof(sinfo) );
sinfo.cbSize = sizeof(sinfo);
sinfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
sinfo.nMax = model_numRows( globals->game.model );
sinfo.nPage = sinfo.nMax - nHidden + 1;
(void)SetScrollInfo( globals->scrollHandle, SB_CTL, &sinfo, TRUE );
}
LRESULT CALLBACK
scrollWindowProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
CEAppGlobals* globals = (CEAppGlobals*)GetWindowLongPtr( hWnd, GWL_USERDATA );
LRESULT result = 1;
/* XP_LOGF( "%s: event=%s (%d)", __func__, messageToStr(message), message ); */
/* Trap key events. Left and right always shift the focus off. Up and
down shift focus off IFF they're going to be no-ops on the theory that
the user needs to get some visual feedback and on some devices the
scrollbar isn't even drawn differently when focussed. */
if ( WM_KEYDOWN == message ) {
XP_Bool setFocus = XP_FALSE;
if ( (VK_RIGHT == wParam) ||
(VK_LEFT == wParam) ||
(VK_TAB == wParam) ) {
setFocus = XP_TRUE;
} else if ( (VK_UP == wParam) || (VK_DOWN == wParam) ) {
SCROLLINFO sinfo;
sinfo.cbSize = sizeof(sinfo);
sinfo.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
GetScrollInfo( globals->scrollHandle, SB_CTL, &sinfo );
if ( (VK_UP == wParam) && (sinfo.nPos <= sinfo.nMin) ) {
setFocus = XP_TRUE;
} else if ( (VK_DOWN == wParam)
&& (sinfo.nPos > (sinfo.nMax - sinfo.nPage)) ) {
setFocus = XP_TRUE;
}
}
if ( setFocus ) {
SetFocus( globals->hWnd );
result = 0;
globals->keyDown = XP_TRUE;
}
}
if ( 0 != result ) {
result = CallWindowProc( globals->oldScrollProc, hWnd, message,
wParam, lParam );
}
return result;
} /* scrollWindowProc */
static void
makeScrollbar( CEAppGlobals* globals, XP_U16 nHidden, XP_U16 xx, XP_U16 yy,
XP_U16 width, XP_U16 height )
{
HWND hwndSB;
#ifdef _WIN32_WCE
RECT tmp;
XP_U16 rectHt;
#endif
++xx;
width -= 2; /* make narrower: on CE will erase cell border */
#ifdef _WIN32_WCE
tmp.left = xx;
tmp.right = xx + width;
rectHt = height / 10; /* each focus rect to be 1/10th height */
tmp.top = yy;
tmp.bottom = yy + rectHt;
XP_MEMCPY( &globals->scrollRects[0], &tmp, sizeof(globals->scrollRects[0]) );
tmp.bottom = yy + height;
tmp.top = tmp.bottom - rectHt;
XP_MEMCPY( &globals->scrollRects[1], &tmp, sizeof(globals->scrollRects[1]) );
yy += rectHt;
height -= rectHt * 2; /* above and below */
#endif
/* Need to destroy it, or resize it, because board size may be changing
in case where portrait display's been flipped. */
removeScrollbar( globals );
hwndSB = CreateWindow( TEXT("SCROLLBAR"), // Class name
NULL, // Window text
// Window style
SBS_VERT|WS_VISIBLE|WS_CHILD,
xx, yy, width, height, globals->hWnd,
(HMENU)SCROLLBARID, // The control identifier
globals->hInst, // The instance handle
NULL ); // s'pposed to be NULL
globals->scrollHandle = hwndSB;
globals->oldScrollProc = (WNDPROC) GetWindowLongPtr( hwndSB,
GWL_WNDPROC );
XP_ASSERT( 0 == GetWindowLongPtr( hwndSB, GWL_USERDATA ) );
SetWindowLongPtr( hwndSB, GWL_WNDPROC, (LPARAM)scrollWindowProc );
SetWindowLongPtr( hwndSB, GWL_USERDATA, (LPARAM)globals );
updateScrollInfo( globals, nHidden );
EnableWindow( hwndSB, nHidden > 0 );
ShowWindow( globals->scrollHandle, SW_SHOW );
} /* makeScrollbar */
static void
removeScrollbar( CEAppGlobals* globals )
{
if ( !!globals->scrollHandle ) {
DestroyWindow( globals->scrollHandle );
globals->scrollHandle = NULL;
globals->scrollerHasFocus = XP_FALSE;
}
} /* removeScrollbar */
#endif
typedef struct CEBoardParms {
XP_U16 scrnWidth;
XP_U16 scrnHeight;
XP_U16 adjLeft;
XP_U16 adjTop;
XP_U16 boardHScale;
XP_U16 boardVScale;
XP_U16 boardTop;
XP_U16 trayTop;
XP_U16 trayHeight;
XP_U16 trayWidth;
XP_U16 timerLeft, timerTop, timerWidth, timerHeight;
XP_U16 boardLeft, trayLeft;
XP_U16 scoreWidth;
XP_U16 scoreHeight;
XP_U16 scrollWidth;
XP_Bool horiz;
#ifdef CEFEATURE_CANSCROLL
XP_Bool needsScroller;
#endif
} CEBoardParms;
static void
figureBoardParms( CEAppGlobals* globals, const XP_U16 nRows,
CEBoardParms* bparms )
{
RECT rc;
XP_U16 scrnWidth, scrnHeight;
XP_U16 trayHeight, scoreWidth, scoreHeight;
XP_U16 hScale, vScale, nVisibleRows;
XP_U16 tmp;
XP_Bool horiz;
XP_U16 scrollWidth = 0;
XP_U16 adjLeft, adjTop;
GetClientRect( globals->hWnd, &rc );
#ifndef _WIN32_WCE
# if defined FORCE_HEIGHT && defined FORCE_WIDTH
rc.right = rc.left + FORCE_WIDTH;
rc.bottom = rc.top + FORCE_HEIGHT;
# else
if ( !globals->appPrefs.fullScreen ) {
if ( globals->dbWidth != 0 ) {
rc.right = rc.left + globals->dbWidth;
}
if ( globals->dbHeight != 0 ) {
rc.bottom = rc.top + globals->dbHeight;
}
}
# endif
#endif /* #ifndef _WIN32_WCE */
scrnWidth = (XP_U16)(rc.right - rc.left);
scrnHeight = (XP_U16)(rc.bottom - rc.top);
XP_LOGF( "%s: scrnWidth: %d, scrnHeight: %d", __func__,
scrnWidth, scrnHeight );
/* Figure layout style based on proportion UNLESS there's just no room
for anything but 15 columns on the board -- because vertically is the
only dimension in which we can scroll. */
if ( scrnWidth <= (MIN_CELL_WIDTH * (nRows+2)) ) {
horiz = XP_TRUE;
} else {
horiz = scrnHeight >= scrnWidth;
}
/* vScale. Tray must be a few pixels taller than cells to use same
sized, minimum-sized font. Subtract out that difference here and it's
guaranteed that tray will wind up at least that much taller later
though for the rest of the calculation we reserve only a cell's height
for it. */
vScale = (scrnHeight - (MIN_TRAY_HEIGHT-MIN_CELL_HEIGHT))
/ (nRows + (horiz?2:1)); /* horiz means scoreboard *and* tray */
if ( vScale >= MIN_CELL_HEIGHT ) {
nVisibleRows = nRows; /* no scrolling needed */
} else {
XP_U16 nRowsPossible = (scrnHeight-MIN_TRAY_HEIGHT) / MIN_CELL_HEIGHT;
XP_S16 num2Scroll;
num2Scroll = (nRows + (horiz?1:0)) - nRowsPossible; /* 1: scoreboard */
XP_ASSERT( num2Scroll > 0 );
if ( num2Scroll < 0 ) { /* no-scroll case should be caught above */
num2Scroll = 0;
}
nVisibleRows = nRows - num2Scroll;
vScale = (scrnHeight-MIN_TRAY_HEIGHT) / (nVisibleRows + (horiz? 1:0));
}
tmp = nRows + (horiz ? 0 : 2);
hScale = scrnWidth / tmp;
if ( vScale > hScale ) {
vScale = XP_MAX( MIN_CELL_HEIGHT, hScale );
} else if ( hScale > vScale ) {
hScale = XP_MAX( MIN_CELL_WIDTH, vScale );
}
/* Figure out tray size */
tmp = vScale * (nVisibleRows + (horiz? 1:0));
trayHeight = XP_MIN( vScale * 2, scrnHeight - tmp );
#ifdef CEFEATURE_CANSCROLL
/* Does this need to be in a loop? */
while ( nVisibleRows < nRows && hScale > MIN_CELL_WIDTH ) { /* need scroller? (while allows break) */
scrollWidth = scrnWidth - (tmp * hScale);
if ( scrollWidth >= MIN_SCROLLBAR_WIDTH ) {
break;
}
--hScale;
}
if ( scrollWidth > MAX_SCROLLBAR_WIDTH ) {
scrollWidth = MAX_SCROLLBAR_WIDTH;
}
#endif
if ( horiz ) {
scoreWidth = scrollWidth + (hScale * nRows);
scoreHeight = vScale - SCORE_TWEAK;
trayHeight += SCORE_TWEAK;
} else {
scoreWidth = XP_MIN( 2*hScale, scrnWidth - (hScale * nRows) );
scoreHeight = (nVisibleRows * vScale) + trayHeight;
}
/* XP_LOGF( "hScale=%d; vScale=%d; trayHeight=%d", hScale, vScale, trayHeight ); */
if ( globals->gameInfo.timerEnabled ) {
if ( horiz ) {
bparms->timerWidth = scoreWidth / 6; /* arbitrarily, one sixth */
scoreWidth -= bparms->timerWidth;
bparms->timerLeft = scoreWidth;
bparms->timerTop = 0;
bparms->timerHeight = scoreHeight;
} else {
bparms->timerLeft = 0;
bparms->timerHeight = vScale * 2;
bparms->timerTop = scoreHeight - bparms->timerHeight;
bparms->timerWidth = scoreWidth;
scoreHeight -= bparms->timerHeight;
}
}
globals->cellHt = vScale;
/* figure actual width and height */
tmp = scrollWidth + (hScale * nRows) + (horiz ? 0 : scoreWidth);
adjLeft = (scrnWidth - tmp)/2;
tmp = trayHeight + (vScale * nVisibleRows) + (horiz?scoreHeight:0);
adjTop = (scrnHeight - tmp)/2;
bparms->scrnWidth = scrnWidth;
bparms->scrnHeight = scrnHeight;
bparms->adjLeft = adjLeft;
bparms->adjTop = adjTop;
bparms->boardHScale = hScale;
bparms->boardVScale = vScale;
bparms->boardTop = adjTop + (horiz? scoreHeight : 0);
bparms->trayTop = bparms->boardTop + (nVisibleRows * vScale) + 1;
bparms->trayHeight = trayHeight - 1;
bparms->trayWidth = (hScale * nRows) + scrollWidth;
bparms->boardLeft = adjLeft + (horiz ? 0 : scoreWidth);
bparms->trayLeft = bparms->boardLeft;//horiz? 0 : scoreWidth;
bparms->scoreWidth = scoreWidth;
bparms->scoreHeight = scoreHeight;
bparms->scrollWidth = scrollWidth;
bparms->horiz = horiz;
#ifdef CEFEATURE_CANSCROLL
bparms->needsScroller = scrollWidth > 0;
if ( bparms->needsScroller ) {
XP_U16 boardRight;
boardRight = bparms->boardLeft + (nRows * hScale);
makeScrollbar( globals, nRows - nVisibleRows,
boardRight, bparms->boardTop,
scrollWidth,
vScale * nVisibleRows );
} else {
removeScrollbar( globals );
}
#endif
} /* figureBoardParms */
static void
setOwnedRects( CEAppGlobals* globals, XP_U16 nRows,
const CEBoardParms* bparms )
{
RECT tmp;
XP_U16 scrollWidth = bparms->scrollWidth;
XP_MEMSET( &globals->ownedRects, 0, sizeof(globals->ownedRects) );
tmp.top = bparms->adjTop; /* Same for both */
tmp.bottom = bparms->trayTop + bparms->trayHeight; /* Same for both */
tmp.left = 0;
tmp.right = bparms->adjLeft;
XP_MEMCPY( &globals->ownedRects[OWNED_RECT_LEFT], &tmp,
sizeof(globals->ownedRects[OWNED_RECT_LEFT]) );
tmp.left = tmp.right + (bparms->boardHScale * nRows) + scrollWidth;
tmp.right = bparms->scrnWidth;
XP_MEMCPY( &globals->ownedRects[OWNED_RECT_RIGHT], &tmp,
sizeof(globals->ownedRects[OWNED_RECT_RIGHT]) );
tmp.left = 0;
tmp.top = 0;
tmp.right = bparms->scrnWidth;
tmp.bottom = bparms->adjTop;
XP_MEMCPY( &globals->ownedRects[OWNED_RECT_TOP], &tmp,
sizeof(globals->ownedRects[OWNED_RECT_TOP]) );
tmp.top = bparms->trayTop + bparms->trayHeight;
tmp.bottom = bparms->scrnHeight;
XP_MEMCPY( &globals->ownedRects[OWNED_RECT_BOTTOM], &tmp,
sizeof(globals->ownedRects[OWNED_RECT_BOTTOM]) );
} /* setOwnedRects */
/* PENDING cePositionBoard gets called a lot when the screen size hasn't
changed. It'd be better to cache the size used to do layout and not
repeat those steps (including possibly nuking and rebuilding a
scrollbar). */
static XP_Bool
cePositionBoard( CEAppGlobals* globals )
{
XP_Bool erase = XP_FALSE;
XP_U16 nCols;
CEBoardParms bparms;
XP_ASSERT( !!globals->game.model );
nCols = model_numCols( globals->game.model );
XP_ASSERT( nCols <= CE_MAX_ROWS );
figureBoardParms( globals, nCols, &bparms );
setOwnedRects( globals, nCols, &bparms );
if ( globals->gameInfo.timerEnabled ) {
board_setTimerLoc( globals->game.board,
bparms.adjLeft + bparms.timerLeft,
bparms.adjTop + bparms.timerTop, bparms.timerWidth,
bparms.timerHeight );
}
board_setPos( globals->game.board, bparms.boardLeft,
bparms.boardTop, XP_FALSE );
board_setScale( globals->game.board, bparms.boardHScale,
bparms.boardVScale );
board_setScoreboardLoc( globals->game.board, bparms.adjLeft, bparms.adjTop,
bparms.scoreWidth,
bparms.scoreHeight, bparms.horiz );
board_setShowColors( globals->game.board, globals->appPrefs.showColors );
board_setYOffset( globals->game.board, 0 );
board_prefsChanged( globals->game.board, &globals->appPrefs.cp );
board_setTrayLoc( globals->game.board, bparms.trayLeft, bparms.trayTop,
bparms.trayWidth, bparms.trayHeight,
bparms.trayWidth/40 ); /* 1/8 of a tile width, roughly */
server_prefsChanged( globals->game.server, &globals->appPrefs.cp );
#if ! defined _WIN32_WCE && defined DEBUG
ceSetTitleFromName( globals );
#endif
ceCheckMenus( globals );
return erase;
} /* cePositionBoard */
/* Set the title. If there's a game name, replace the window title with that
* in case both won't fit. If there's no name yet, install the app name as
* title.
*/
static void
ceSetTitleFromName( CEAppGlobals* globals )
{
wchar_t widebuf[64];
const XP_UCHAR* gameName = globals->curGameName;
/* if default name, remove any current name */
if ( !gameName || isDefaultName( globals, gameName ) ) {
LoadString( globals->hInst, IDS_APP_TITLE, widebuf, VSIZE(widebuf) );
} else {
wchar_t* dotPos;
XP_UCHAR* baseStart = 1 + strrchr( gameName, '\\' );
XP_U16 len = (XP_U16)XP_STRLEN( baseStart );
MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, baseStart, len + 1,
widebuf, len + 1 );
/* now get rid of the ".xwg" */
dotPos = wcsrchr( widebuf, '.' );
if ( dotPos != NULL ) {
*dotPos = 0;
}
}
#if ! defined _WIN32_WCE && defined DEBUG
swprintf( &widebuf[wcslen(widebuf)], L":%dx%d",
globals->dbWidth, globals->dbHeight );
#endif
SendMessage( globals->hWnd, WM_SETTEXT, 0, (long)widebuf );
} /* ceSetTitleFromName */
static void
ceInitAndStartBoard( CEAppGlobals* globals, XP_Bool newGame,
const CommsAddrRec* XP_UNUSED_STANDALONE(addr) )
{
DictionaryCtxt* dict;
DictionaryCtxt* toBeDestroyed = NULL;
XP_UCHAR* newDictName = globals->gameInfo.dictName;
/* This needs to happen even when !newGame because it's how the dict
slots in PlayerInfo get loaded. That really ought to happen earlier,
though. */
XP_ASSERT( !!globals->game.model );
dict = model_getDictionary( globals->game.model );
if ( !!dict ) {
const XP_UCHAR* curDictName = dict_getName( dict );
if ( !!newDictName &&
( !curDictName || 0 != XP_STRCMP( curDictName, newDictName ) ) ) {
toBeDestroyed = dict;
dict = NULL;
} else {
replaceStringIfDifferent( globals->mpool,
&globals->gameInfo.dictName,
curDictName );
}
}
if ( !dict ) {
#ifdef STUBBED_DICT
dict = make_stubbed_dict( MPPARM_NOCOMMA(globals->mpool) );
#else
XP_ASSERT( !!newDictName );
XP_LOGF( "calling ce_dictionary_make" );
dict = ce_dictionary_make( globals, newDictName);
#endif
XP_ASSERT( !!dict );
model_setDictionary( globals->game.model, dict );
}
if ( newGame ) {
game_reset( MEMPOOL &globals->game, &globals->gameInfo, &globals->util,
0, &globals->appPrefs.cp, CE_SEND_PROC,
CE_RESET_PROC globals );
#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH
if ( !!addr ) {
XP_ASSERT( globals->game.comms != NULL );
comms_setAddr( globals->game.comms, addr );
}
#endif
}
XP_ASSERT( !!globals->game.board );
ceSizeIfFullscreen( globals, globals->hWnd );
(void)cePositionBoard( globals );
board_invalAll( globals->game.board );
InvalidateRect( globals->hWnd, NULL, TRUE /* erase */ );
#ifndef XWFEATURE_STANDALONE_ONLY
if ( !!globals->game.comms ) {
comms_start( globals->game.comms );
}
if ( newGame && globals->gameInfo.serverRole == SERVER_ISCLIENT ) {
XWStreamCtxt* stream;
XP_ASSERT( !!globals->game.comms );
stream = make_generic_stream( globals );
stream_setOnCloseProc( stream, ce_send_on_close );
server_initClientConnection( globals->game.server, stream );
}
#endif
ceSetLeftSoftkey( globals, ID_MOVE_TURNDONE );
server_do( globals->game.server );
globals->isNewGame = FALSE;
if ( !!toBeDestroyed ) {
dict_destroy( toBeDestroyed );
}
} /* ceInitAndStartBoard */
static void
ceSavePrefs( CEAppGlobals* globals )
{
HANDLE fileH;
wchar_t path[CE_MAX_PATH_LEN];
(void)ceGetPath( globals, PREFS_FILE_PATH_L, path, VSIZE(path) );
fileH = CreateFile( path, GENERIC_WRITE, 0, NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_HIDDEN, NULL );
if ( fileH != INVALID_HANDLE_VALUE ) {
XP_U32 nWritten;
XP_U16 nameLen = 0;
XP_UCHAR* name = globals->curGameName;
if ( name != NULL ) {
nameLen = (XP_U16)XP_STRLEN( name );
}
SetFilePointer( fileH, 0, 0, FILE_BEGIN );
/* write prefs, including version num */
WriteFile( fileH, &globals->appPrefs, sizeof(globals->appPrefs),
&nWritten, NULL );
WriteFile( fileH, &nameLen, sizeof(nameLen), &nWritten, NULL );
WriteFile( fileH, name, nameLen, &nWritten, NULL );
WriteFile( fileH, &globals->flags, sizeof(globals->flags), &nWritten,
NULL );
SetEndOfFile( fileH ); /* truncate anything previously there */
CloseHandle( fileH ); /* am I not supposed to do this? PENDING */
XP_DEBUGF( "ceSavePrefs: prefs file written" );
} else {
logLastError( "failed to create prefs file" );
}
} /* ceSavePrefs */
static XP_Bool
peekVersion( HANDLE fileH, XP_U16* version )
{
XP_Bool success = XP_FALSE;
XP_U32 nRead;
success = ReadFile( fileH, version, sizeof(*version), &nRead, NULL );
if ( success ) {
SetFilePointer( fileH, -nRead, 0, FILE_CURRENT );
}
return success;
} /* peekVersion */
static XP_Bool
canUpdatePrefs( CEAppGlobals* globals, HANDLE fileH, XP_U16 curVersion,
CEAppPrefs* prefs )
{
XP_Bool success = XP_FALSE;
LOG_FUNC();
if ( (curVersion == 0x0002) && (CUR_CE_PREFS_FLAGS == 0x0003) ) {
CEAppPrefs0002 oldPrefs;
XP_U32 nRead;
if ( ReadFile( fileH, &oldPrefs, sizeof(oldPrefs), &nRead, NULL ) ) {
ceInitPrefs( globals, prefs );
XP_MEMCPY( &prefs->cp, &oldPrefs.cp, sizeof(prefs->cp) );
prefs->showColors = oldPrefs.showColors;
XP_MEMCPY( &prefs->colors[0], &oldPrefs.colors[0],
CE_FOCUS_COLOR*sizeof(prefs->colors[0]));
XP_ASSERT( CE_PLAYER0_COLOR - 1 == CE_FOCUS_COLOR );
XP_MEMCPY( &prefs->colors[CE_PLAYER0_COLOR],
&oldPrefs.colors[CE_FOCUS_COLOR],
(CE_NUM_COLORS-CE_PLAYER0_COLOR)
* sizeof(prefs->colors[0]));
success = XP_TRUE;
} else {
XP_LOGF( "%s: ReadFile bad", __func__ );
}
} else {
XP_LOGF( "%s: can't convert from %d to %d", __func__,
curVersion, CUR_CE_PREFS_FLAGS );
}
LOG_RETURNF( "%d", (int)success );
return success;
} /* canUpdatePrefs */
static XP_Bool
ceLoadPrefs( CEAppGlobals* globals )
{
XP_Bool result = XP_FALSE;
HANDLE fileH;
wchar_t path[CE_MAX_PATH_LEN];
(void)ceGetPath( globals, PREFS_FILE_PATH_L, path, VSIZE(path) );
fileH = CreateFile( path, GENERIC_READ, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
if ( fileH != INVALID_HANDLE_VALUE ) {
XP_U32 fileSize = GetFileSize( fileH, NULL );
XP_U16 curVersion;
if ( fileSize >= sizeof(curVersion) && peekVersion( fileH,
&curVersion ) ) {
CEAppPrefs tmpPrefs;
if ( curVersion == CUR_CE_PREFS_FLAGS ) {
if ( fileSize >= sizeof( CEAppPrefs ) ) {
XP_U32 bytesRead;
if ( ReadFile( fileH, &tmpPrefs, sizeof(tmpPrefs),
&bytesRead, NULL ) ) {
XP_ASSERT( tmpPrefs.versionFlags == CUR_CE_PREFS_FLAGS );
result = XP_TRUE;
}
}
} else if ( canUpdatePrefs( globals, fileH, curVersion,
&tmpPrefs ) ) {
result = XP_TRUE;
} else {
XP_LOGF( "%s: old prefs; cannot read.", __func__ );
}
if ( result ) {
XP_U16 flags;
XP_U16 nameLen;
XP_UCHAR* name;
XP_U32 nRead;
XP_MEMCPY( &globals->appPrefs, &tmpPrefs,
sizeof(globals->appPrefs) );
ReadFile( fileH, &nameLen, sizeof(nameLen), &nRead,
NULL );
name = XP_MALLOC( globals->mpool, nameLen + 1 );
ReadFile( fileH, name, nameLen, &nRead, NULL );
name[nameLen] = '\0';
globals->curGameName = name;
if ( ReadFile( fileH, &flags, sizeof(flags), &nRead,
NULL )
&& nRead == sizeof(flags) ) {
} else {
flags = 0;
}
globals->flags = flags;
}
}
CloseHandle( fileH );
} else {
XP_LOGF( "ceLoadPrefs: prefs file not found" );
}
return result; /* none found */
} /* ceLoadPrefs */
static XWStreamCtxt*
make_generic_stream( const CEAppGlobals* globals )
{
return mem_stream_make( MPPARM(globals->mpool) globals->vtMgr,
(void*)globals, 0, NULL );
} /* make_generic_stream */
static XWStreamCtxt*
fileToStream( const CEAppGlobals* globals, const XP_UCHAR* path )
{
XWStreamCtxt* stream = NULL;
HANDLE fileH;
wchar_t widebuf[257];
XP_U16 len;
len = (XP_U16)XP_STRLEN( path );
MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, path, len + 1,
widebuf, len + 1 );
fileH = CreateFile( widebuf, GENERIC_READ, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
if ( fileH != INVALID_HANDLE_VALUE ) {
XP_U32 len = GetFileSize( fileH, NULL );
XP_U32 nRead;
XP_UCHAR* buf = XP_MALLOC( globals->mpool, len );
XP_DEBUGF( "fileSize = %ld", len );
ReadFile( fileH, buf, len, &nRead, NULL );
CloseHandle( fileH );
stream = make_generic_stream( globals );
stream_open( stream );
stream_putBytes( stream, buf, (XP_U16)nRead );
XP_FREE( globals->mpool, buf );
}
XP_DEBUGF( "fileToStream => 0x%lx", (unsigned long)stream );
return stream;
} /* fileToStream */
static XP_Bool
ceLoadSavedGame( CEAppGlobals* globals )
{
XP_Bool success = XP_FALSE;
XWStreamCtxt* stream;
LOG_FUNC();
stream = fileToStream( globals, globals->curGameName );
success = stream != NULL;
if ( success ) {
DictionaryCtxt* dict;
XP_U8 flags = stream_getU8( stream );
XP_Bool hasDict = (flags & 0x01) != 0;
flags >>= 1;
if ( hasDict ) {
#ifdef STUBBED_DICT
XP_ASSERT(0); /* just don't do this!!!! */
#else
XP_UCHAR* dictName = stringFromStream( globals->mpool, stream );
dict = ce_dictionary_make( globals, dictName );
success = dict != NULL;
if ( !success ) {
XP_UCHAR buf[128];
snprintf( buf, VSIZE(buf), "Unable to open dictionary: %s",
dictName );
buf[VSIZE(buf)-1] = '\0';
ceOops( globals, buf );
}
XP_FREE( globals->mpool, dictName );
#endif
} else {
dict = NULL;
}
if ( success ) {
if ( flags >= CE_GAMEFILE_VERSION1 ) {
ce_draw_fromStream( globals->draw, stream, flags );
}
XP_DEBUGF( "calling game_makeFromStream" );
success = game_makeFromStream( MEMPOOL stream, &globals->game,
&globals->gameInfo, dict,
&globals->util,
(DrawCtx*)globals->draw,
&globals->appPrefs.cp, CE_SEND_PROC,
CE_RESET_PROC globals );
if ( success ) {
ceSetTitleFromName( globals );
} else {
if ( !!dict ) {
dict_destroy( dict );
}
ceOops( globals, "Saved game cannot be opened." );
}
}
stream_destroy( stream );
}
return success;
} /* ceLoadSavedGame */
static void
colorsFromRsrc( const CEAppGlobals* globals, CEAppPrefs* prefs )
{
XP_U16 i;
HGLOBAL globH;
HRSRC rsrcH;
XP_U16* ptr;
rsrcH = FindResource( globals->hInst, MAKEINTRESOURCE(ID_COLORS_RES),
TEXT("CLRS") );
globH = LoadResource( globals->hInst, rsrcH );
ptr = (XP_U16*)globH;
for ( i = 0; i < CE_NUM_COLORS; ++i ) {
XP_U8 r = (XP_U8)*ptr++;
XP_U8 g = (XP_U8)*ptr++;
XP_U8 b = (XP_U8)*ptr++;
prefs->colors[i] = RGB( r, g, b );
}
DeleteObject( globH );
} /* colorsFromRsrc */
static void
ceInitPrefs( CEAppGlobals* globals, CEAppPrefs* prefs )
{
prefs->versionFlags = CUR_CE_PREFS_FLAGS;
prefs->showColors = XP_TRUE;
prefs->fullScreen = XP_FALSE;
prefs->cp.showBoardArrow = XP_TRUE;
prefs->cp.showRobotScores = XP_FALSE;
colorsFromRsrc( globals, prefs );
} /* ceInitPrefs */
#ifdef _WIN32_WCE
static void
getOSInfo( CEAppGlobals* globals )
{
OSVERSIONINFO ver = {0};
TCHAR buf[128];
XW_WinceVersion winceVersion = WINCE_UNKNOWN;
if ( GetVersionEx( &ver )) {
XP_LOGF( "version = %ld.%ld", ver.dwMajorVersion, ver.dwMinorVersion );
} else {
XP_WARNF( "GetVersionEx failed" );
}
if ( SystemParametersInfo( SPI_GETPLATFORMTYPE, sizeof(buf), buf, FALSE ) ) {
if ( 0 == lstrcmp( buf, L"PocketPC") ) {
// We are on a Pocket PC, so check the OS version,
// Pocket PC 2003 used WinCE 4.2
if ( ver.dwMajorVersion < 4 ) {
winceVersion = WINCE_PPC_V1;
} else if ( ver.dwMajorVersion == 4 ) {
winceVersion = WINCE_PPC_2003;
} else if ( ver.dwMajorVersion > 4 ) {
winceVersion = WINCE_PPC_2005;
}
} else if ( 0 == lstrcmp( buf, L"SmartPhone") ) {
if ( ver.dwMajorVersion < 4 ) {
winceVersion = WINCE_SMARTPHONE_V1;
} else if ( ver.dwMajorVersion == 4 ) {
winceVersion = WINCE_SMARTPHONE_2003;
} else if ( ver.dwMajorVersion > 4 ) {
winceVersion = WINCE_SMARTPHONE_2005;
}
} else {
XP_LOGW( "unknown OS type", buf );
}
} else if ( GetLastError() == ERROR_ACCESS_DENIED ) {
if ( ver.dwMajorVersion < 4 ) {
winceVersion = WINCE_SMARTPHONE_V1;
} else {
winceVersion = WINCE_SMARTPHONE_2003;
}
}
XP_ASSERT( winceVersion != WINCE_UNKNOWN );
globals->winceVersion = winceVersion;
XP_LOGF( "%s: set version to %d", __func__, winceVersion );
} /* getOSInfo */
#else
#define getOSInfo( g )
#endif
//
// FUNCTION: InitInstance(HANDLE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
BOOL
InitInstance(HINSTANCE hInstance, int nCmdShow
#ifndef _WIN32_WCE
,XP_U16 width, XP_U16 height
#endif
)
{
HWND hWnd;
TCHAR szTitle[MAX_LOADSTRING]; // The title bar text
TCHAR szWindowClass[MAX_LOADSTRING]; // The window class name
CEAppGlobals* globals;
BOOL result = TRUE;
XP_Bool oldGameLoaded;
XP_Bool prevStateExists;
XP_Bool newDone = XP_FALSE;
wchar_t path[CE_MAX_PATH_LEN];
MPSLOT;
#ifndef XWFEATURE_STANDALONE_ONLY
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
XP_LOGF( "unable to load winsock" );
}
}
#endif
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_XWORDS4, szWindowClass, MAX_LOADSTRING);
// If it is already running, then focus on the window. Skip title in
// search as we change title to include game name
hWnd = FindWindow( szWindowClass, NULL );
if ( hWnd ) {
SetForegroundWindow( (HWND)((ULONG) hWnd | 0x00000001) );
result = FALSE;
goto exit;
}
#ifdef MEM_DEBUG
mpool = mpool_make();
#endif
globals = (CEAppGlobals*)XP_MALLOC( mpool, sizeof(*globals) );
XP_DEBUGF( "globals created: 0x%p", globals );
XP_MEMSET( globals, 0, sizeof(*globals) );
MPASSIGN( globals->mpool, mpool );
#ifndef _WIN32_WCE
globals->dbWidth = width;
globals->dbHeight = height;
#endif
(void)ceGetPath( globals, DEFAULT_DIR_PATH_L, path, VSIZE(path) );
(void)CreateDirectory( path, 0 );
getOSInfo( globals );
globals->vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) );
globals->hInst = hInstance;
// Initialize global strings
MyRegisterClass(hInstance, szWindowClass);
hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, hInstance, globals);
globals->hWnd = hWnd;
if (!hWnd) {
result = FALSE;
goto exit;
}
#ifdef _WIN32_WCE
if ( globals->hwndCB && !IS_SMARTPHONE(globals) ) {
RECT rc, rcmb;
GetWindowRect( hWnd, &rc );
GetWindowRect( globals->hwndCB, &rcmb );
rc.bottom -= (rcmb.bottom - rcmb.top);
MoveWindow( hWnd, rc.left, rc.top, rc.right-rc.left,
rc.bottom-rc.top, FALSE );
}
#endif
#ifndef _WIN32_WCE
srand( time(NULL) );
#endif
ceInitUtilFuncs( globals );
gi_initPlayerInfo( MPPARM(mpool) &globals->gameInfo, NULL );
/* choose one. If none found it's an error. */
#ifndef STUBBED_DICT
result = 1 == ceLocateNDicts( globals, 1, ceSetDictName,
globals );
if ( !result ) {
wchar_t buf[512];
(void)LoadString( globals->hInst, (UINT)IDS_DICTLOC, buf, VSIZE(buf) );
assertOnTop( globals->hWnd );
MessageBox( globals->hWnd, buf, L"Dictionary Not Found",
MB_OK | MB_ICONHAND );
result = FALSE;
goto exit;
}
#endif
/* here's where we want to behave differently if there's saved state.
But that's a long ways off. */
prevStateExists = ceLoadPrefs( globals );
if ( !prevStateExists ) {
ceInitPrefs( globals, &globals->appPrefs );
}
/* must load prefs before creating draw ctxt */
globals->draw = ce_drawctxt_make( MPPARM(globals->mpool)
hWnd, globals );
oldGameLoaded = prevStateExists && ceLoadSavedGame( globals );
if ( !oldGameLoaded ) {
game_makeNewGame( MPPARM(mpool) &globals->game, &globals->gameInfo,
&globals->util, (DrawCtx*)globals->draw, 0,
&globals->appPrefs.cp,
CE_SEND_PROC, CE_RESET_PROC globals );
newDone = ceDoNewGame( globals ); /* calls ceInitAndStartBoard */
if ( !newDone ) {
result = FALSE;
}
}
trapBackspaceKey( hWnd );
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
#ifdef _WIN32_WCE
if (globals->hwndCB) {
CommandBar_Show(globals->hwndCB, TRUE);
}
#endif
if ( result && !newDone ) {
ceInitAndStartBoard( globals, !oldGameLoaded, NULL );
}
exit:
return result;
} /* InitInstance */
static XP_Bool
ceSetDictName( const wchar_t* XP_UNUSED(wPath), XP_U16 XP_UNUSED_DBG(index),
void* XP_UNUSED(ctxt) )
{
/* CEAppGlobals* globals = (CEAppGlobals*)ctxt; */
/* XP_UCHAR* str; */
/* XP_UCHAR buf[CE_MAX_PATH_LEN]; */
XP_ASSERT( index == 0 ); /* we said one only! */
/* WideCharToMultiByte( CP_ACP, 0, wPath, -1, */
/* buf, sizeof(buf)-1, NULL, NULL ); */
/* XP_LOGF( "%s: got path \"%s\"", __func__, buf ); */
/* str = copyString( MPPARM(globals->mpool) buf ); */
/* XP_LOGF( "%s: got %p", __func__, str ); /\* this is leaking *\/ */
/* XP_ASSERT( NULL == globals->gameInfo.dictName ); */
/* globals->gameInfo.dictName = str; */
return XP_FALSE;
} /* ceSetDictName */
static XP_Bool
ceHandleHintRequest( CEAppGlobals* globals )
{
XP_Bool notDone;
XP_Bool draw;
XP_ASSERT( !!globals->game.board );
draw = board_requestHint( globals->game.board,
#ifdef XWFEATURE_SEARCHLIMIT
globals->askTrayLimits,
#endif
&notDone );
globals->hintPending = notDone;
if ( draw ) { /* don't turn on if disallowed */
ceSetLeftSoftkey( globals, ID_MOVE_NEXTHINT );
}
return draw;
} /* ceHandleHintRequest */
static XP_Bool
handleTradeCmd( CEAppGlobals* globals )
{
XP_Bool success = board_beginTrade( globals->game.board );
if ( success ) {
ceSetLeftSoftkey( globals, ID_MOVE_TURNDONE );
}
return success;
} /* handleTradeCmd */
static XP_Bool
handleJuggleCmd( CEAppGlobals* globals )
{
ceSetLeftSoftkey( globals, ID_MOVE_JUGGLE );
return board_juggleTray( globals->game.board );
} /* handleJuggleCmd */
static XP_Bool
handleHidetrayCmd( CEAppGlobals* globals )
{
XP_Bool result;
XW_TrayVisState curState = board_getTrayVisState( globals->game.board );
if ( curState == TRAY_REVEALED ) {
result = board_hideTray( globals->game.board );
} else {
result = board_showTray( globals->game.board );
}
ceSetLeftSoftkey( globals, ID_MOVE_HIDETRAY );
return result;
} /* handleHidetrayCmd */
static XP_Bool
handleDoneCmd( CEAppGlobals* globals)
{
return board_commitTurn( globals->game.board );
} /* handleDoneCmd */
static void
ceCountsAndValues( CEAppGlobals* globals )
{
if ( !!globals->game.server ) {
XWStreamCtxt* stream = make_generic_stream( globals );
server_formatDictCounts( globals->game.server, stream, 3 );
(void)ceMsgFromStream( globals, stream, L"Tile Counts and Values",
MB_OK | MB_ICONINFORMATION, XP_TRUE );
}
} /* ceCountsAndValues */
static void
ceTilesLeft( CEAppGlobals* globals )
{
if ( !!globals->game.board ) {
XWStreamCtxt* stream = make_generic_stream( globals );
board_formatRemainingTiles( globals->game.board, stream );
(void)ceMsgFromStream( globals, stream, L"Remaining tiles",
MB_OK | MB_ICONINFORMATION, XP_TRUE );
}
} /* ceTilesLeft */
static void
ceDoHistory( CEAppGlobals* globals )
{
XP_Bool gameOver = server_getGameIsOver(globals->game.server);
XWStreamCtxt* stream;
stream = make_generic_stream( globals );
model_writeGameHistory( globals->game.model, stream,
globals->game.server, gameOver );
(void)ceMsgFromStream( globals, stream, L"Game history",
MB_OK | MB_ICONINFORMATION, XP_TRUE );
} /* ceDoHistory */
static void
drawInsidePaint( CEAppGlobals* globals, const RECT* invalR )
{
HDC hdc;
hdc = GetDC( globals->hWnd );
if ( !hdc ) {
logLastError( __func__ );
} else {
int oldMode = SetBkMode( hdc, TRANSPARENT );
HDC prevHDC = globals->hdc;
globals->hdc = hdc;
if ( !!invalR ) {
XP_U16 ii;
RECT interR;
for ( ii = 0; ii < N_OWNED_RECTS; ++ii ) {
if ( IntersectRect( &interR, invalR,
&globals->ownedRects[ii] ) ) {
ce_draw_erase( globals->draw, &interR );
}
}
#ifdef _WIN32_WCE
for ( ii = 0; ii < VSIZE(globals->scrollRects); ++ii ) {
if ( IntersectRect( &interR, invalR,
&globals->scrollRects[ii] ) ) {
if ( globals->scrollerHasFocus ) {
ce_draw_focus( globals->draw, &interR );
} else {
ce_draw_erase( globals->draw, &interR );
}
}
}
#endif
}
board_draw( globals->game.board );
(void)SetBkMode( hdc, oldMode );
globals->hdc = prevHDC;
}
} /* drawInsidePaint */
static void
ceDisplayFinalScores( CEAppGlobals* globals )
{
XWStreamCtxt* stream;
stream = make_generic_stream( globals );
server_writeFinalScores( globals->game.server, stream );
stream_putU8( stream, '\0' );
(void)ceMsgFromStream( globals, stream, L"Final scores",
MB_OK | MB_ICONINFORMATION, XP_TRUE );
} /* ceDisplayFinalScores */
static XP_Bool
ceDoNewGame( CEAppGlobals* globals )
{
GameInfoState giState;
CommsAddrRec* addr = NULL;
XP_Bool changed = XP_FALSE;
XP_MEMSET( &giState, 0, sizeof(giState) );
giState.dlgHdr.globals = globals;
giState.isNewGame = XP_TRUE;
assertOnTop( globals->hWnd );
DialogBoxParam( globals->hInst, (LPCTSTR)IDD_GAMEINFO, globals->hWnd,
(DLGPROC)GameInfo, (long)&giState );
if ( !giState.userCancelled
#ifndef STUBBED_DICT
&& ( giState.newDictName[0] != '\0' )
#endif
) {
if ( globals->curGameName != NULL ) {
XP_FREE( globals->mpool, globals->curGameName );
globals->curGameName = NULL;
}
if ( giState.prefsChanged ) {
loadCurPrefsFromState( globals, &globals->appPrefs,
&globals->gameInfo, &giState.prefsPrefs );
if ( giState.colorsChanged ) {
updateForColors( globals );
}
}
#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH
if ( giState.addrChanged ) {
addr = &giState.prefsPrefs.addrRec;
}
#endif
ceInitAndStartBoard( globals, XP_TRUE, addr );
ceSetTitleFromName( globals );
changed = XP_TRUE;
}
return changed;
} /* ceDoNewGame */
static void
ceChooseAndOpen( CEAppGlobals* globals )
{
assertOnTop( globals->hWnd );
// Save in case we'll be duplicating it
again:
if ( ceSaveCurGame( globals, XP_FALSE ) ) {
SavedGamesResult choice;
wchar_t newName[256];
newName[0] = 0;
ceSetTitleFromName( globals ); /* in case we named it above */
choice = ceSavedGamesDlg( globals, globals->curGameName, newName,
VSIZE(newName) );
if ( CE_SVGAME_CANCEL != choice ) {
XP_UCHAR* name;
XP_U16 len;
len = wcslen(newName);
name = XP_MALLOC( globals->mpool, len + 1 );
WideCharToMultiByte( CP_ACP, 0, newName, len + 1,
name, len + 1, NULL, NULL );
if ( globals->curGameName != NULL
&& 0 == XP_STRCMP( name, globals->curGameName ) ){
/* User chose already-open game; no-op */
XP_FREE( globals->mpool, name );
} else {
/* Save old name in case fail to open new, e.g. because dict
not there */
XP_UCHAR* oldName;
/* Need to save a second time, with auto-save, in case user
wants to overwrite yet chooses a game whose dict is
missing -- since then we'll be re-opening this game! */
ceSaveCurGame( globals, XP_TRUE ); /* may change curGameName */
oldName = globals->curGameName;
globals->curGameName = NULL; /* prevent being destroyed */
closeGame( globals );
if ( CE_SVGAME_RENAME == choice ) {
XP_U16 len = 1 + XP_STRLEN( oldName );
wchar_t widebuf[len];
MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, oldName, len,
widebuf, len );
(void)MoveFile( widebuf, newName );
}
globals->curGameName = name;
if ( ceLoadSavedGame( globals ) ) {
XP_FREE( globals->mpool, oldName );
} else {
XP_ASSERT( CE_SVGAME_RENAME != choice );
XP_LOGF( "failed to open chosen game" );
XP_FREE( globals->mpool, globals->curGameName );
globals->curGameName = oldName;
if ( !ceLoadSavedGame( globals ) ) {
XP_LOGF( "failed to open old game too!!!" );
}
}
ceInitAndStartBoard( globals, XP_FALSE, NULL );
if ( CE_SVGAME_RENAME == choice ) {
goto again;
}
}
} else {
XP_LOGF( "GetOpenFileName() failed" );
}
}
} /* ceChooseAndOpen */
static void
updateForColors( CEAppGlobals* globals )
{
ce_draw_update( globals->draw );
if ( !!globals->game.board ) {
board_invalAll( globals->game.board );
}
} /* updateForColors */
static void
ceDoPrefsDlg( CEAppGlobals* globals )
{
CePrefsDlgState state;
CePrefsPrefs prefsPrefs;
loadStateFromCurPrefs( globals, &globals->appPrefs, &globals->gameInfo,
&prefsPrefs );
assertOnTop( globals->hWnd );
(void)WrapPrefsDialog( globals->hWnd, globals, &state, &prefsPrefs,
XP_FALSE );
if ( !state.userCancelled ) {
loadCurPrefsFromState( globals, &globals->appPrefs, &globals->gameInfo,
&prefsPrefs );
(void)cePositionBoard( globals );
if ( state.colorsChanged ) {
updateForColors( globals );
}
/* need to reflect vars set in state into globals, and update/inval
as appropriate. */
}
} /* ceDoPrefsDlg */
static void
ceWriteToFile( XWStreamCtxt* stream, void* closure )
{
FileWriteState* fwState = (FileWriteState*)closure;
#ifdef MEM_DEBUG
CEAppGlobals* globals = fwState->globals;
#endif
XP_UCHAR* path = fwState->path;
XP_U16 len = (XP_U16)XP_STRLEN( path ) + 1; /* 1: null byte */
wchar_t widebuf[len];
HANDLE fileH;
MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, path, len, widebuf, len );
fileH = CreateFile( widebuf, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
if ( fileH != INVALID_HANDLE_VALUE ) {
XP_UCHAR* buf;
XP_U32 nWritten;
len = stream_getSize( stream );
buf = XP_MALLOC( globals->mpool, len );
stream_getBytes( stream, buf, len );
WriteFile( fileH, buf, len, &nWritten, NULL );
SetEndOfFile( fileH );
CloseHandle( fileH );
XP_FREE( globals->mpool, buf );
}
} /* ceWriteToFile */
static XP_Bool
isDefaultName( CEAppGlobals* globals, const XP_UCHAR* name )
{
wchar_t path[CE_MAX_PATH_LEN];
(void)ceGetPath( globals, DEFAULT_GAME_PATH, path, VSIZE(path) );
XP_UCHAR nbuf[CE_MAX_PATH_LEN];
WideCharToMultiByte( CP_ACP, 0, path, -1,
nbuf, VSIZE(nbuf), NULL, NULL );
return 0 == XP_STRCMP( nbuf, name );
} /* isDefaultName */
static XP_Bool
ceSaveCurGame( CEAppGlobals* globals, XP_Bool autoSave )
{
XP_Bool confirmed = XP_FALSE;
/* If it doesn't yet have a name, get a path at which to save it. User
has a chance to cancel this. But if there is a name go ahead and save
using it and don't bother giving cancel chance -- since there's no
harm in making 'em restart. Not sure how this changes when IR's
involved. */
XP_UCHAR* name = globals->curGameName;
if ( name == NULL || isDefaultName( globals, name ) ) {
XP_UCHAR* newName = NULL;
if ( autoSave ) {
XP_U16 len;
wchar_t path[CE_MAX_PATH_LEN];
len = 1 + ceGetPath( globals, DEFAULT_GAME_PATH,
path, VSIZE(path) );
newName = XP_MALLOC( globals->mpool, len );
XP_MEMCPY( newName, path, len );
confirmed = XP_TRUE;
} else {
wchar_t nameBuf[MAX_PATH];
confirmed = ceConfirmUniqueName( globals, globals->hWnd, IDS_SAVENAME,
nameBuf, VSIZE(nameBuf) );
if ( confirmed ) {
XP_U16 len = wcslen(nameBuf);
newName = XP_MALLOC( globals->mpool, len + 1 );
WideCharToMultiByte( CP_ACP, 0, nameBuf, len + 1,
newName, len + 1, NULL, NULL );
}
}
if ( confirmed ) {
XP_ASSERT( !!newName );
if ( !!globals->curGameName ) {
XP_FREE( globals->mpool, globals->curGameName );
}
globals->curGameName = newName;
}
} else {
confirmed = XP_TRUE;
}
if ( confirmed ) {
if ( !!globals->game.server ) {
XWStreamCtxt* memStream;
DictionaryCtxt* dict;
FileWriteState fwState;
const char* dictName;
XP_U8 flags;
fwState.path = globals->curGameName;
fwState.globals = globals;
board_hideTray( globals->game.board ); /* so won't be visible when
next opened */
memStream = mem_stream_make( MEMPOOL globals->vtMgr, &fwState, 0,
ceWriteToFile );
stream_open( memStream );
/* the dictionary */
dict = model_getDictionary( globals->game.model );
#ifdef STUBBED_DICT /* don't check this in!!! */
dictName = NULL;
#else
dictName = !!dict? dict_getName( dict ) : NULL;
#endif
flags = !!dictName? 0x01 : 0x00;
flags |= CE_GAMEFILE_VERSION << 1;
stream_putU8( memStream, flags );
if ( !!dictName ) {
stringToStream( memStream, dictName );
}
ce_draw_toStream( globals->draw, memStream );
game_saveToStream( &globals->game, &globals->gameInfo, memStream );
stream_destroy( memStream );
}
}
return confirmed;
} /* ceSaveCurGame */
static void
ceSaveAndExit( CEAppGlobals* globals )
{
(void)ceSaveCurGame( globals, XP_TRUE );
ceSavePrefs( globals );
DestroyWindow(globals->hWnd);
} /* ceSaveAndExit */
static void
closeGame( CEAppGlobals* globals )
{
game_dispose( &globals->game );
gi_disposePlayerInfo( MPPARM(globals->mpool) &globals->gameInfo );
if ( !!globals->curGameName ) {
XP_FREE( globals->mpool, globals->curGameName );
}
}
static void
freeGlobals( CEAppGlobals* globals )
{
XP_U16 ii;
MPSLOT;
MPASSIGN( mpool, globals->mpool );
draw_destroyCtxt( (DrawCtx*)globals->draw );
closeGame( globals );
#ifndef XWFEATURE_STANDALONE_ONLY
if ( !!globals->socketWrap ) {
ce_sockwrap_delete( globals->socketWrap );
globals->socketWrap = NULL;
}
WSACleanup();
#endif
if ( !!globals->vtMgr ) {
vtmgr_destroy( MPPARM(mpool) globals->vtMgr );
}
if ( !!globals->util.vtable ) {
XP_FREE( mpool, globals->util.vtable );
}
for ( ii = 0; ii < N_CACHED_PATHS; ++ii ) {
if ( !!globals->specialDirs[ii] ) {
XP_FREE( mpool, globals->specialDirs[ii] );
}
}
XP_FREE( globals->mpool, globals );
mpool_destroy( mpool );
} /* freeGlobals */
#ifdef _WIN32_WCE
static HWND
makeCommandBar( HWND hwnd, HINSTANCE hInst )
{
SHMENUBARINFO mbi;
XP_MEMSET( &mbi, 0, sizeof(mbi) );
mbi.cbSize = sizeof(mbi);
mbi.hwndParent = hwnd;
mbi.nToolBarId = IDM_MAIN_MENUBAR;
mbi.hInstRes = hInst;
/* Don't set dwFlags if you want the Wince5 two-button softkey menu. */
/* mbi.dwFlags = SHCMBF_HMENU; */
//mbi.dwFlags = SHCMBF_HIDESIPBUTTON; /* eeh added. Why??? */
if (!SHCreateMenuBar(&mbi)) {
/* will want to use this to change menubar: SHEnableSoftkey? */
XP_LOGF( "SHCreateMenuBar failed" );
return NULL;
}
return mbi.hwndMB;
} /* makeCommandBar */
#endif
#ifdef CEFEATURE_CANSCROLL
static XP_Bool
handleScroll( CEAppGlobals* globals, XP_S16 pos, /* only valid for THUMB* */
XP_S16 code, HWND wnd )
{
XP_Bool result = XP_FALSE;
if ( wnd == globals->scrollHandle ) {
XP_U16 curYOffset = board_getYOffset( globals->game.board );
XP_S16 newOffset = curYOffset;
XP_ASSERT( !!globals->game.board );
switch ( code ) {
/* case SB_BOTTOM: // Scrolls to the lower right */
/* case SB_ENDSCROLL: // Ends scroll */
case SB_LINEUP: // Scrolls one line up
case SB_PAGEUP: //
--newOffset;
break;
case SB_LINEDOWN: // Scrolls one line down
case SB_PAGEDOWN: // Scrolls one page down
++newOffset;
break;
case SB_THUMBTRACK: /* still dragging; don't redraw */
case SB_THUMBPOSITION:
newOffset = pos;
default:
break;
/* do nothing: leave newOffset == curYOffset */
}
result = curYOffset != newOffset
&& board_setYOffset( globals->game.board, newOffset );
}
return result;
} /* handleScroll */
#endif
static XP_Bool
ceFireTimer( CEAppGlobals* globals, XWTimerReason why )
{
XP_Bool draw = XP_FALSE;
XWTimerProc proc;
void* closure;
proc = globals->timerProcs[why];
if ( !!proc ) {
globals->timerProcs[why] = NULL;
closure = globals->timerClosures[why];
draw = (*proc)( closure, why );
/* Setting draw after firing timer allows scrolling to happen
while pen is held down. This is a hack. Perhaps having
the timer proc return whether drawing is needed would be
more consistent. */
} else {
XP_LOGF( "skipped timer; alread fired?" );
}
return draw;
} /* ceFireTimer */
/* WM_TIMER messages are low-priority. Hold a key down and key events will
* crowd it out of the queue so that the app doesn't see it until the key is
* released. There are more reliable timers, but they seem to require
* advanced techniques like semaphores. At least one article recommends
* polling over going to those lengths. This is better that polling. I hope
* it's enough.
*/
static XP_Bool
checkFireLateKeyTimer( CEAppGlobals* globals )
{
XP_Bool drop = XP_FALSE;
XWTimerReason whys[] = { TIMER_PENDOWN, TIMER_TIMERTICK
#if defined XWFEATURE_RELAY || defined COMMS_HEARTBEAT
, TIMER_COMMS
#endif
};
XP_U32 now = GetCurrentTime();
XP_U16 i;
for ( i = 0; i < sizeof(whys)/sizeof(whys[0]); ++i ) {
XWTimerReason why = whys[i];
if ( !!globals->timerProcs[why] ) {
if ( now >= globals->timerWhens[why] ) {
(void)ceFireTimer( globals, why );
drop = XP_TRUE;
}
}
}
return drop;
} /* checkFireLateKeyTimer */
static XP_Bool
checkPenDown( CEAppGlobals* globals )
{
XP_Bool draw = globals->penDown;
if ( draw ) {
draw = board_handlePenUp( globals->game.board, 0x7FFF, 0x7FFF );
globals->penDown = XP_FALSE;
}
return draw;
} /* checkPenDown */
#ifdef KEYBOARD_NAV
static XP_Bool
ceCheckHandleFocusKey( CEAppGlobals* globals, WPARAM wParam, LPARAM lParam,
XP_Bool keyDown, XP_Bool* handledP )
{
XP_Bool draw = XP_FALSE;
/* Sometimes, e.g. after a menu is released, we get KEY_UP not preceeded
by KEY_DOWN. Just drop those. */
if ( !keyDown && !globals->keyDown ) {
XP_LOGF( "%s: keyUp not preceeded by keyDown: dropping", __func__ );
} else {
XP_Bool isRepeat = keyDown && ((HIWORD(lParam) & KF_REPEAT) != 0);
XP_Key key;
XP_S16 incr = 0;
switch ( wParam ) {
case VK_UP:
key = XP_CURSOR_KEY_UP;
incr = -1;
break;
case VK_RIGHT:
key = XP_CURSOR_KEY_RIGHT;
incr = 1;
break;
case VK_DOWN:
key = XP_CURSOR_KEY_DOWN;
incr = 1;
break;
case VK_LEFT:
key = XP_CURSOR_KEY_LEFT;
incr = -1;
break;
case 0x0d:
case 0x5d: /* center key on WinMo5 Treo (at least) -- but also ']'*/
case VK_HOME:
key = XP_RETURN_KEY;
if ( isRepeat ) {
(void)checkFireLateKeyTimer( globals );
}
break;
/* Still need to produce these somehow */
/* XP_CURSOR_KEY_ALTRIGHT, */
/* XP_CURSOR_KEY_ALTUP, */
/* XP_CURSOR_KEY_ALTLEFT, */
/* XP_CURSOR_KEY_ALTDOWN, */
default:
key = XP_KEY_NONE;
break;
}
if ( key != XP_KEY_NONE ) {
BoardCtxt* board = globals->game.board;
if ( isRepeat ) {
draw = board_handleKeyRepeat( board, key, handledP );
} else if ( keyDown ) {
draw = board_handleKeyDown( board, key, handledP );
} else {
draw = board_handleKeyUp( board, key, handledP );
}
if ( !*handledP && incr != 0 && !keyDown ) {
BoardObjectType orderScroll[] = {
OBJ_SCORE, OBJ_BOARD, OBJ_NONE, OBJ_TRAY };
BoardObjectType orderNoScroll[] = {
OBJ_SCORE, OBJ_BOARD, OBJ_TRAY };
BoardObjectType* order;
XP_U16 orderLen;
BoardObjectType cur = board_getFocusOwner( board );
XP_U16 index = 0;
if ( !!globals->scrollHandle ) {
order = orderScroll;
orderLen = VSIZE(orderScroll);
} else {
order = orderNoScroll;
orderLen = VSIZE(orderNoScroll);
}
if ( !!globals->scrollHandle || (cur != OBJ_NONE) ) {
for ( ; ; ) {
if ( order[index] == cur ) {
break;
}
++index;
XP_ASSERT( index < orderLen );
}
index = (index + orderLen + incr) % orderLen;
}
draw = board_focusChanged( board, order[index], XP_TRUE );
if ( !!globals->scrollHandle ) {
XP_Bool scrollerHasFocus = globals->scrollerHasFocus;
if ( order[index] == OBJ_NONE ) {
XP_ASSERT( !scrollerHasFocus );
SetFocus( globals->scrollHandle );
scrollerHasFocus = XP_TRUE;
} else if ( scrollerHasFocus ) {
SetFocus( globals->hWnd );
scrollerHasFocus = XP_FALSE;
}
if ( scrollerHasFocus != globals->scrollerHasFocus ) {
globals->scrollerHasFocus = scrollerHasFocus;
#ifdef _WIN32_WCE
InvalidateRect( globals->hWnd, &globals->scrollRects[0], FALSE );
InvalidateRect( globals->hWnd, &globals->scrollRects[1], FALSE );
#else
InvalidateRect( globals->scrollHandle, NULL, FALSE );
#endif
}
}
}
}
}
globals->keyDown = keyDown;
return draw;
} /* ceCheckHandleFocusKey */
#endif /* KEYBOARD_NAV */
static void
ceToggleFullScreen( CEAppGlobals* globals )
{
globals->appPrefs.fullScreen = !globals->appPrefs.fullScreen;
ceSizeIfFullscreen( globals, globals->hWnd );
(void)cePositionBoard( globals );
} /* ceToggleFullScreen */
static void
doAbout( CEAppGlobals* globals )
{
wchar_t buf[1024];
(void)LoadString( globals->hInst, (UINT)IDS_ABOUT, buf, VSIZE(buf) );
assertOnTop( globals->hWnd );
MessageBox( globals->hWnd, buf, L"About " LCROSSWORDS_DIR,
MB_OK | MB_ICONINFORMATION );
}
LRESULT CALLBACK
WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT result = 0;
int wmId;
XP_Bool draw = XP_FALSE;
XWTimerReason why;
CEAppGlobals* globals;
XP_Bool handled = XP_FALSE;
XP_Bool callDefault = XP_FALSE;
if ( message == WM_CREATE ) {
globals = ((CREATESTRUCT*)lParam)->lpCreateParams;
SetWindowLongPtr( hWnd, GWL_USERDATA, (long)globals );
#ifdef _WIN32_WCE
globals->hwndCB = makeCommandBar( hWnd, globals->hInst );
#endif
#ifdef _WIN32_WCE
globals->sai.cbSize = sizeof(globals->sai);
#endif
} else {
/* XP_LOGF( "%s: event=%s (%d)", __func__, messageToStr(message), message ); */
globals = (CEAppGlobals*)GetWindowLongPtr( hWnd, GWL_USERDATA );
switch (message) {
#ifdef _WIN32_WCE
case WM_ACTIVATE:
// Notify shell of our activate message
SHHandleWMActivate( hWnd, wParam, lParam, &globals->sai, FALSE );
break;
case WM_SETTINGCHANGE:
SHHandleWMSettingChange( hWnd, wParam, lParam, &globals->sai );
if ( !!globals && !!globals->game.model ) {
cePositionBoard( globals );
board_invalAll( globals->game.board );
draw = XP_TRUE;
}
break;
#endif
#ifdef CEFEATURE_CANSCROLL
# ifndef _WIN32_WCE
/* WM_CTLCOLORSCROLLBAR aren't delivered on CE. Some say can
* work around using WM_PAINT or WM_ERASEBKGND but no luck
* yet. */
case WM_CTLCOLORSCROLLBAR:
if ( (HWND)lParam == globals->scrollHandle ) {
if ( globals->scrollerHasFocus ) {
return (LRESULT)ce_draw_getFocusBrush( globals->draw );
}
}
break;
# endif
case WM_VSCROLL:
draw = checkPenDown( globals );
draw = handleScroll( globals, (short int)HIWORD(wParam),
(short int)LOWORD(wParam),
(HWND)lParam ) || draw;
break;
#endif
case WM_COMMAND:
(void)checkPenDown( globals );
wmId = LOWORD(wParam);
// Parse the menu selections:
switch (wmId) {
case ID_FILE_ABOUT:
doAbout( globals );
break;
case ID_GAME_GAMEINFO: {
GameInfoState state;
XP_MEMSET( &state, 0, sizeof(state) );
state.dlgHdr.globals = globals;
DialogBoxParam(globals->hInst, (LPCTSTR)IDD_GAMEINFO, hWnd,
(DLGPROC)GameInfo, (long)&state );
if ( !state.userCancelled ) {
if ( state.prefsChanged ) {
updateForColors( globals );
}
draw = server_do( globals->game.server );
}
}
break;
case ID_FILE_NEWGAME:
XP_LOGF( "ID_FILE_NEWGAME" );
if ( ceSaveCurGame( globals, XP_FALSE )
|| queryBoxChar( hWnd, "Do you really want to "
"overwrite the current game?" ) ) {
draw = ceDoNewGame( globals );
}
break;
case ID_FILE_SAVEDGAMES:
ceChooseAndOpen( globals );
break;
case ID_FILE_PREFERENCES:
ceDoPrefsDlg( globals );
break;
case ID_FILE_FULLSCREEN:
ceToggleFullScreen( globals );
break;
case ID_GAME_FINALSCORES:
if ( server_getGameIsOver( globals->game.server ) ) {
ceDisplayFinalScores( globals );
} else if ( queryBoxChar( hWnd, "Are you sure you want to end "
"the game now?" ) ) {
server_endGame( globals->game.server );
draw = TRUE;
}
break;
case ID_GAME_TILECOUNTSANDVALUES:
ceCountsAndValues( globals );
break;
case ID_GAME_TILESLEFT:
ceTilesLeft( globals );
break;
case ID_GAME_HISTORY:
ceDoHistory( globals );
break;
case ID_MOVE_TRADE:
draw = handleTradeCmd( globals );
break;
case ID_MOVE_JUGGLE:
draw = handleJuggleCmd( globals );
break;
case ID_MOVE_HIDETRAY:
draw = handleHidetrayCmd( globals );
break;
case ID_MOVE_TURNDONE:
draw = handleDoneCmd( globals);
break;
case ID_MOVE_FLIP:
draw = board_flip( globals->game.board );
ceCheckMenus( globals );
break;
case ID_MOVE_VALUES:
draw = board_toggle_showValues( globals->game.board );
ceSetLeftSoftkey( globals, ID_MOVE_VALUES );
ceCheckMenus( globals );
break;
case ID_MOVE_HINT:
#ifdef XWFEATURE_SEARCHLIMIT
case ID_MOVE_LIMITEDHINT:
globals->askTrayLimits = wmId == ID_MOVE_LIMITEDHINT;
#endif
board_resetEngine( globals->game.board );
/* fallthru */
case ID_MOVE_NEXTHINT:
draw = ceHandleHintRequest( globals );
break;
case ID_FILE_EXIT:
ceSaveAndExit( globals ); /* autosaves; no user interaction */
break;
case ID_MOVE_UNDOCURRENT:
draw = board_replaceTiles( globals->game.board );
break;
case ID_MOVE_UNDOLAST:
draw = server_handleUndo( globals->game.server );
break;
default:
callDefault = XP_TRUE;
}
break;
case WM_PAINT:
if ( !!globals ) {
RECT winrect;
if ( GetUpdateRect( hWnd, &winrect, FALSE ) ) {
if ( !!globals->game.board ) {
XP_Rect xprect;
/* When an obscuring window goes away, the update region
needs to be redrawn. This allows invalidating it. */
RECTtoXPR( &xprect, &winrect );
board_invalRect( globals->game.board, &xprect );
XP_ASSERT( globals->hWnd == hWnd );
drawInsidePaint( globals, &winrect );
}
if ( !ValidateRect( hWnd, &winrect ) ) {
logLastError( "WM_PAINT:ValidateRect" );
}
}
}
break;
case WM_LBUTTONDOWN:
draw = checkPenDown( globals );
globals->penDown = XP_TRUE;
draw = board_handlePenDown( globals->game.board, LOWORD(lParam),
HIWORD(lParam), &handled )
|| draw;
break;
case WM_MOUSEMOVE:
if ( globals->penDown ) {
draw = board_handlePenMove( globals->game.board,
LOWORD(lParam),
HIWORD(lParam) );
}
break;
case WM_LBUTTONUP:
if ( globals->penDown ) {
draw = board_handlePenUp( globals->game.board, LOWORD(lParam),
HIWORD(lParam) );
globals->penDown = XP_FALSE;
}
break;
#ifdef OVERRIDE_BACKKEY
/* Make the back key mean raise focus, but only if dived.
Otherwise allow the OS to do what it wants. Which means
exit? */
case WM_HOTKEY:
if ( (VK_TBACK == HIWORD(lParam)) && !!globals->game.board ) {
draw = board_handleKey( globals->game.board,
XP_RAISEFOCUS_KEY, &handled );
if ( !draw && !handled
/* Hack alert. Winders sends two WM_HOTKEY events per
press of the key. (lParam isn't well documented for
this event, but likely they're down and up.)
Unfiltered, the first raises focus and the second
exits the app. Bad. So we'll only raise if the
first was not handled. Note that this may well break
on devices I haven't tested on, later, whenever. */
&& (0 == (BACK_KEY_UP_MAYBE & LOWORD(lParam))) ) {
XP_LOGF( "calling ceSaveAndExit for VK_TBACK" );
/* I'm actually exiting the app rather than minimize. As
it stands, minimizing means that even if I relaunch
the app and quit properly I can't delete the .exe,
suggesting that the minimized guy isn't getting
reassociated when I relaunch. Until I fix that
exiting is best.
*/
ceSaveAndExit( globals );
/* SHNavigateBack() is the right way to handle this, but
isn't available via cegcc yet. Others have suggested
this as well as ShowWindow( hWnd, SW_MINIMIZE );
or SetWindowPos( hWnd, &CWnd::wndBottom, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE );
*/
}
}
break;
#endif
#ifdef KEYBOARD_NAV
case WM_KEYDOWN:
case WM_KEYUP:
draw = ceCheckHandleFocusKey( globals, wParam, lParam,
message==WM_KEYDOWN, &handled );
break;
#endif
case WM_CHAR:
if ( wParam == 0x08 ) {
wParam = XP_CURSOR_KEY_DEL;
#ifdef KEYBOARD_NAV
} else if ( wParam == ' ' ) {
wParam = XP_RAISEFOCUS_KEY;
#endif
}
draw = board_handleKey( globals->game.board, wParam, &handled )
|| board_handleKey( globals->game.board, wParam - ('a'-'A'),
&handled );
break;
case WM_TIMER:
why = (XWTimerReason)wParam;
if ( why == TIMER_PENDOWN || why == TIMER_TIMERTICK
#if defined XWFEATURE_RELAY || defined COMMS_HEARTBEAT
|| why == TIMER_COMMS
#endif
) {
XP_ASSERT( why < NUM_TIMERS_PLUS_ONE );
/* Kill since they otherwise repeat, but kill before firing
as fired proc may set another. */
(void)KillTimer( hWnd, globals->timerIDs[why] );
draw = ceFireTimer( globals, why );
}
break;
#ifdef _WIN32_WCE
/* case WM_SETFOCUS: */
/* hC = ImmGetContext( hWnd ); */
/* globals->imeWasOpen = ImmGetOpenStatus( hC ); */
/* ImmSetOpenStatus( hC, TRUE ); */
/* ImmEscape( NULL, hC, IME_ESC_SET_MODE, (LPVOID)IM_SPELL ); */
/* break; */
/* case WM_KILLFOCUS: */
/* ImmSetOpenStatus( hC, globals->imeWasOpen ); */
/* break; */
/* The code above this point works to turn 12-key->text
translation on, but not to turn it off, so other apps wind up
with it on after Crosswords quits. The recommended code is
below, but includes constants not in the version of cegcc I'm
using. Need to look into upgrading, but that requires a lot
of changes. Post B2.... */
/* DWORD dwRes = SendMessage((HWND)wParam, WM_IME_REQUEST, IMR_ISIMEAWARE, 0); */
/* hC = ImmGetContext( hWnd ); */
/* if ( (dwRes & IMEAF_AWARE) == IMEAF_AWARE ) { */
/* ImmEscape( NULL, hC, IME_ESC_RETAIN_MODE_ICON, (LPVOID)TRUE); */
/* } */
/* ImmSetOpenStatus( hC, FALSE); */
/* } */
/* break; */
/* case WM_IME_REQUEST: */
/* if ( wParam == IMR_ISIMEAWARE ) { */
/* return IMEAF_AWARE; */
/* } */
/* break; */
#endif
case WM_DESTROY:
#ifdef _WIN32_WCE
CommandBar_Destroy(globals->hwndCB); /* supposedly not needed */
#endif
PostQuitMessage(0);
freeGlobals( globals );
break;
case XWWM_TIME_RQST:
draw = server_do( globals->game.server );
break;
case XWWM_REM_SEL:
ceTilesLeft( globals );
break;
#ifndef XWFEATURE_STANDALONE_ONLY
case XWWM_HOSTNAME_ARRIVED:
ce_sockwrap_hostname( globals->socketWrap, wParam, lParam );
break;
case XWWM_SOCKET_EVT:
draw = ce_sockwrap_event( globals->socketWrap, wParam, lParam );
break;
#endif
default:
callDefault = XP_TRUE;
}
}
if ( callDefault ) {
result = DefWindowProc(hWnd, message, wParam, lParam );
} else if ( draw ) {
/* This is stupid. We can't just say "draw" because windoze clips
drawing to the inval rect, and the board isn't set up to tell us
what its inval rect is. So we inval everything, and then when the
WM_PAINT message comes we inval the whole board because there's a
huge inval rect. Dumb. Need to figure out how to have the
methods in cedraw.c set the clip region to encompass the object
being drawn -- taking board's word for it -- or the intersection
of that with the actual clip rgn in the case where some window's
gone away and revealed a large rect board didn't know about. That
was the source of some trouble on Palm, and CE's so fast this
works. But it's stupid. */
RECT r = { 100, 100, 102, 102 };
InvalidateRect( globals->hWnd, &r, FALSE /* erase */ );
}
return result;
} /* WndProc */
// Mesage handler for the About box.
LRESULT CALLBACK
ceAbout(HWND hDlg, UINT message, WPARAM wParam, LPARAM XP_UNUSED(lParam))
{
switch (message) {
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
if ((LOWORD(wParam) == IDOK) || (LOWORD(wParam) == IDCANCEL)) {
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
}
return FALSE;
} /* ceAbout */
static XP_Bool
ceMsgFromStream( CEAppGlobals* globals, XWStreamCtxt* stream,
wchar_t* title, XP_U16 buttons, XP_Bool destroy )
{
/* It seems we want to use messagebox for everything on smartphone and
Windows, but not on PPC since it doesn't scroll and doesn't use
softkeys. Well, on Windows since there's no scrolling limit by
size */
XP_Bool saidYes;
XP_Bool useMB;
#ifdef _WIN32_WCE
useMB = IS_SMARTPHONE(globals);
#else
useMB = stream_getSize(stream) <= 256; /* arbitrary... */
#endif
if ( useMB ) {
int result = messageBoxStream( globals, stream, title, buttons );
saidYes = (IDOK == result) | (IDRETRY == result) | (IDYES == result);
} else {
StrBoxState state;
XP_MEMSET( &state, 0, sizeof(state) );
state.title = title;
state.stream = stream;
state.isQuery = (buttons & ~MB_ICONMASK) != MB_OK;
state.dlgHdr.globals = globals;
assertOnTop( globals->hWnd );
DialogBoxParam( globals->hInst, (LPCTSTR)IDD_STRBOX, globals->hWnd,
(DLGPROC)StrBox, (long)&state );
saidYes = state.result == IDOK;
}
if ( destroy ) {
stream_destroy( stream );
}
return saidYes;
} /* ceMsgFromStream */
static XP_UCHAR*
ceStreamToStrBuf( MPFORMAL XWStreamCtxt* stream )
{
XP_U16 len = stream_getSize( stream );
XP_UCHAR* buf = XP_MALLOC( mpool, len + 1 );
stream_getBytes( stream, buf, len );
buf[len] = '\0';
return buf;
} /* ceStreamToStrBuf */
static int
messageBoxStream( CEAppGlobals* globals, XWStreamCtxt* stream, wchar_t* title,
XP_U16 buttons )
{
XP_UCHAR* buf = ceStreamToStrBuf( MPPARM(globals->mpool) stream );
int result;
result = ceMessageBoxChar( globals, buf, title, buttons );
XP_FREE( globals->mpool, buf );
return result;
} /* messageBoxStream */
XP_Bool
queryBoxChar( HWND hWnd, const XP_UCHAR* msg )
{
wchar_t widebuf[128];
XP_U16 answer;
XP_U16 len = MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, msg, -1,
widebuf, VSIZE(widebuf) );
widebuf[len] = 0;
answer = MessageBox( hWnd, widebuf, L"Question",
MB_YESNO | MB_ICONQUESTION );
return answer == IDOK || answer == IDYES;
} /* queryBoxChar */
static XP_Bool
ceQueryFromStream( CEAppGlobals* globals, XWStreamCtxt* stream )
{
return ceMsgFromStream( globals, stream, L"Question",
MB_OKCANCEL | MB_ICONQUESTION, XP_FALSE );
} /* ceQueryFromStream */
static void
RECTtoXPR( XP_Rect* dest, const RECT* src )
{
dest->top = (short)src->top;
dest->left = (short)src->left;
dest->width = (short)(src->right - src->left);
dest->height = (short)(src->bottom - src->top);
} /* RECTtoXPR */
void
wince_assert( const char* XP_UNUSED_LOG(s), int XP_UNUSED_LOG(line),
const char* XP_UNUSED_LOG(fileName),
const char* XP_UNUSED_LOG(func) )
{
XP_WARNF( "ASSERTION FAILED %s: %s in file %s, line %d\n", s,
func, fileName, line );
} /* wince_assert */
#ifdef ENABLE_LOGGING
static void
makeTimeStamp( char* timeStamp, XP_U16 XP_UNUSED_DBG(len) )
{
SYSTEMTIME st;
DWORD tid;
tid = GetCurrentThreadId();
GetLocalTime( &st );
sprintf( timeStamp, "<%lx>%d:%.2d:%.2d ", tid, st.wHour, st.wMinute,
st.wSecond );
XP_ASSERT( len > XP_STRLEN(timeStamp) );
} /* makeTimeStamp */
void
wince_warnf(const char* format, ...)
{
char buf[256];
va_list ap;
XP_U16 slen;
va_start( ap, format );
vsnprintf( buf, sizeof(buf), format, ap );
va_end(ap);
wince_debugf( "%s", buf );
slen = strlen(buf)+1;
wchar_t widebuf[slen];
MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, buf, slen,
widebuf, slen );
MessageBox( NULL, widebuf, L"WARNF", MB_OK | MB_ICONHAND );
} /* wince_warnf */
void
wince_debugf(const char* format, ...)
{
#ifdef XWFEATURE_RELAY
static HANDLE s_logMutex = NULL;
#endif
char buf[256];
char timeStamp[32];
XP_U16 nBytes;
XP_U32 nWritten;
HANDLE fileH;
va_list ap;
wchar_t* logFileName;
va_start( ap, format );
vsprintf( buf, format, ap );
va_end(ap);
/* Create logfile if necessary and write to it in ascii. If there are
multiple threads, protect with mutex. */
#ifdef XWFEATURE_RELAY
if ( s_logMutex == NULL ) {
s_logMutex = CreateMutex( NULL, FALSE, NULL );
}
if ( WaitForSingleObject( s_logMutex, 200L ) == WAIT_OBJECT_0 ) {
#endif
makeTimeStamp(timeStamp, VSIZE(timeStamp) );
#ifdef _WIN32_WCE
logFileName = L"\\My Documents\\" LCROSSWORDS_DIR L"\\xwDbgLog.txt";
#else
logFileName = L"xwDbgLog.txt";
#endif
fileH = CreateFile( logFileName,
GENERIC_WRITE, 0, NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
#ifdef _WIN32_WCE_EMULATION
strcat( buf, "\n" );
#else
strcat( buf, XP_CR );
#endif
SetFilePointer( fileH, 0, 0, FILE_END );
#ifdef DEBUG_TS
nBytes = strlen( timeStamp );
WriteFile( fileH, timeStamp, nBytes, &nWritten, NULL );
#endif
nBytes = strlen( buf );
WriteFile( fileH, buf, nBytes, &nWritten, NULL );
CloseHandle( fileH );
#ifdef XWFEATURE_RELAY
ReleaseMutex( s_logMutex );
}
#endif
} /* wince_debugf */
#endif /* ENABLE_LOGGING */
XP_U16
wince_snprintf( XP_UCHAR* buf, XP_U16 len, const XP_UCHAR* format, ... )
{
va_list ap;
va_start( ap, format );
_vsnprintf( buf, len, format, ap );
/* FormatMessage( */
/* FORMAT_MESSAGE_FROM_STRING, */
/* format, */
/* 0, */
/* 0, // Default language */
/* (LPTSTR)buf, */
/* len, &ap ); */
va_end(ap);
return XP_STRLEN(buf);
} /* wince_snprintf */
#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH
static XP_Bool
got_data_proc( XP_U8* data, XP_U16 len, void* closure )
{
CEAppGlobals* globals = (CEAppGlobals*)closure;
XWStreamCtxt* stream;
XP_Bool draw;
stream = make_generic_stream( globals );
stream_putBytes( stream, data, len );
XP_ASSERT( !!globals->game.comms );
if ( comms_checkIncomingStream( globals->game.comms, stream, NULL ) ) {
draw = server_receiveMessage( globals->game.server, stream );
}
stream_destroy( stream );
ce_util_requestTime( &globals->util );
return draw;
} /* got_data_proc */
#endif
#ifdef COMMS_HEARTBEAT
static void
ce_reset_proc( void* XP_UNUSED_STANDALONE(closure) )
{
LOG_FUNC();
}
#endif
#ifndef XWFEATURE_STANDALONE_ONLY
static XP_S16
ce_send_proc( const XP_U8* buf, XP_U16 len, const CommsAddrRec* addrp,
void* closure )
{
XP_S16 nSent = -1;
CEAppGlobals* globals = (CEAppGlobals*)closure;
CommsAddrRec addr;
LOG_FUNC();
if ( !addrp ) {
comms_getAddr( globals->game.comms, &addr );
addrp = &addr;
}
XP_ASSERT( !!addrp ); /* firing */
switch( addrp->conType ) {
#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH
case COMMS_CONN_IP_DIRECT:
break;
case COMMS_CONN_RELAY:
if ( !globals->socketWrap ) {
globals->socketWrap = ce_sockwrap_new( MPPARM(globals->mpool)
got_data_proc, globals );
}
nSent = ce_sockwrap_send( globals->socketWrap, buf, len, addrp );
break;
#endif
#ifdef XWFEATURE_SMS
case COMMS_CONN_SMS:
nSent = ce_sms_send( globals, buf, len, addrp );
break;
#endif
default:
XP_LOGF( "unexpected conType %d", addrp->conType );
XP_ASSERT( 0 );
}
return nSent;
} /* ce_send_proc */
static void
ce_send_on_close( XWStreamCtxt* stream, void* closure )
{
CEAppGlobals* globals = (CEAppGlobals*)closure;
XP_ASSERT( !!globals->game.comms );
comms_send( globals->game.comms, stream );
}
#endif
static VTableMgr*
ce_util_getVTManager( XW_UtilCtxt* uc )
{
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
return globals->vtMgr;
} /* ce_util_getVTManager */
static void
ce_util_userError( XW_UtilCtxt* uc, UtilErrID id )
{
XP_UCHAR* message;
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
switch( id ) {
case ERR_TILES_NOT_IN_LINE:
message = "All tiles played must be in a line.";
break;
case ERR_NO_EMPTIES_IN_TURN:
message = "Empty squares cannot separate tiles played.";
break;
case ERR_TWO_TILES_FIRST_MOVE:
message = "Must play two or more pieces on the first move.";
break;
case ERR_TILES_MUST_CONTACT:
message = "New pieces must contact others already in place (or "
"the middle square on the first move).";
break;
case ERR_NOT_YOUR_TURN:
message = "You can't do that; it's not your turn!";
break;
case ERR_NO_PEEK_ROBOT_TILES:
message = "No peeking at the robot's tiles!";
break;
case ERR_CANT_TRADE_MID_MOVE:
message = "Remove played tiles before trading.";
break;
case ERR_TOO_FEW_TILES_LEFT_TO_TRADE:
message = "Too few tiles left to trade.";
break;
case ERR_CANT_UNDO_TILEASSIGN:
message = "Tile assignment can't be undone.";
break;
case ERR_CANT_HINT_WHILE_DISABLED:
message = "The hint feature is disabled for this game. Enable "
"it for a new game using the Preferences dialog.";
break;
#ifndef XWFEATURE_STANDALONE_ONLY
case ERR_NO_PEEK_REMOTE_TILES:
message = "No peeking at remote players' tiles!";
break;
case ERR_REG_UNEXPECTED_USER:
message = "Refused attempt to register unexpected user[s].";
break;
case ERR_SERVER_DICT_WINS:
message = "Conflict between Host and Guest dictionaries; Host wins.";
XP_WARNF( "GTK may have problems here." );
break;
case ERR_REG_SERVER_SANS_REMOTE:
message = "At least one player must be marked remote for a game "
"started as Host.";
break;
#endif
#ifdef XWFEATURE_RELAY
case ERR_RELAY_BASE + XWRELAY_ERROR_TIMEOUT:
message = "The relay timed you out; usually that means "
"the other players didn't show.";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_HEART_YOU:
message = "You were disconnected from relay because it didn't "
"hear from you in too long.";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_HEART_OTHER:
case ERR_RELAY_BASE + XWRELAY_ERROR_LOST_OTHER:
message = "The relay has lost contact with a device in this game.";
break;
#endif
default:
XP_LOGF( "%s(%d)", __func__, id );
message = "unknown errorcode ID!!!";
break;
}
ceOops( globals, message );
} /* ce_util_userError */
static XP_Bool
ce_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, XWStreamCtxt* stream )
{
XP_UCHAR* query = NULL;
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
switch( id ) {
case QUERY_COMMIT_TURN:
return ceQueryFromStream( globals, stream );
case QUERY_COMMIT_TRADE:
query = "Are you sure you want to trade the selected tiles?";
assertOnTop( globals->hWnd );
return queryBoxChar( globals->hWnd, query );
case QUERY_ROBOT_MOVE:
return ceMsgFromStream( globals, stream, L"FYI",
MB_OK | MB_ICONINFORMATION, XP_FALSE );
case QUERY_ROBOT_TRADE:
messageBoxStream( globals, stream, L"FYI", MB_OK | MB_ICONINFORMATION);
break;
default:
XP_ASSERT(0);
}
return XP_FALSE;
} /* ce_util_userQuery */
static XWBonusType
ce_util_getSquareBonus( XW_UtilCtxt* uc, const ModelCtxt* XP_UNUSED(model),
XP_U16 col, XP_U16 row )
{
XP_U16 index;
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
if ( !globals->bonusInfo ) {
HRSRC rsrcH;
HGLOBAL globH;
rsrcH = FindResource( globals->hInst, MAKEINTRESOURCE(ID_BONUS_RES),
TEXT("BONS") );
if ( !!rsrcH ) {
globH = LoadResource( globals->hInst, rsrcH );
if ( !!globH ) {
globals->bonusInfo = (XP_U16*)globH;
/* We don't want to call DeleteObject here, but should when
the app closes. Or does Wince free up all memory
associated with a process when it closes? PENDING(eeh) */
// DeleteObject( globH );
}
}
}
if ( col > 7 ) col = 14 - col;
if ( row > 7 ) row = 14 - row;
index = (row*8) + col;
if ( !globals->bonusInfo || (index >= 8*8) ) {
XP_ASSERT( 0 );
return (XWBonusType)BONUS_NONE;
} else {
/* This is probably a bit slow. Consider caching the resource in
memory with one bonus value per byte. */
XP_U16 value = globals->bonusInfo[index/4];
value >>= ((3 - (index % 4)) * 4);
return value & 0x0F;
}
} /* ce_util_getSquareBonus */
static XP_S16
ce_util_userPickTile( XW_UtilCtxt* uc, const PickInfo* pi,
XP_U16 playerNum,
const XP_UCHAR4* texts, XP_U16 nTiles )
{
BlankDialogState state;
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
XP_MEMSET( &state, 0, sizeof(state) );
state.dlgHdr.globals = globals;
state.texts = texts;
state.nTiles = nTiles;
state.playerNum = playerNum;
state.pi = pi;
assertOnTop( globals->hWnd );
DialogBoxParam( globals->hInst, (LPCTSTR)IDD_ASKBLANK, globals->hWnd,
(DLGPROC)BlankDlg, (long)&state );
return state.result;
} /* ce_util_userPickTile */
static XP_Bool
ce_util_askPassword( XW_UtilCtxt* uc, const XP_UCHAR* name,
XP_UCHAR* buf, XP_U16* len )
{
PasswdDialogState state;
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
XP_MEMSET( &state, 0, sizeof(state) );
state.dlgHdr.globals = globals;
state.name = name;
state.buf = buf;
state.lenp = len;
assertOnTop( globals->hWnd );
DialogBoxParam( globals->hInst, (LPCTSTR)IDD_ASKPASS, globals->hWnd,
(DLGPROC)PasswdDlg, (long)&state );
return !state.userCancelled;
} /* ce_util_askPassword */
static void
ce_util_trayHiddenChange( XW_UtilCtxt* uc, XW_TrayVisState XP_UNUSED(newState),
XP_U16 nVisibleRows )
{
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
XP_U16 nHiddenRows;
#ifdef CEFEATURE_CANSCROLL
/* If there's a scrollbar, hide/show it. It wants to be
active/visible only when the tray is NOT hidden */
if ( !!globals->scrollHandle ) {
nHiddenRows = model_numRows( globals->game.model ) - nVisibleRows;
updateScrollInfo( globals, nHiddenRows );
}
#endif
ceCheckMenus( globals );
drawInsidePaint( globals, NULL );
} /* ce_util_trayHiddenChange */
static void
ce_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 XP_UNUSED(oldOffset),
XP_U16 newOffset )
{
#ifdef CEFEATURE_CANSCROLL
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
if ( !!globals->scrollHandle ) {
(void)SetScrollPos( globals->scrollHandle, SB_CTL, newOffset, XP_TRUE );
}
#endif
} /* ce_util_yOffsetChange */
static void
ce_util_turnChanged( XW_UtilCtxt* uc )
{
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
ceSetLeftSoftkey( globals, ID_MOVE_TURNDONE );
}
static void
ce_util_notifyGameOver( XW_UtilCtxt* uc )
{
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
drawInsidePaint( globals, NULL );
ceDisplayFinalScores( globals );
ceSetLeftSoftkey( globals, ID_FILE_NEWGAME );
} /* ce_util_notifyGameOver */
static XP_Bool
ce_util_hiliteCell( XW_UtilCtxt* XP_UNUSED(uc), XP_U16 XP_UNUSED(col),
XP_U16 XP_UNUSED(row) )
{
return XP_TRUE;
} /* ce_util_hiliteCell */
static XP_Bool
ce_util_engineProgressCallback( XW_UtilCtxt* XP_UNUSED(uc) )
{
return XP_TRUE;
} /* ce_util_engineProgressCallback */
static void
ce_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why,
XP_U16 XP_UNUSED_STANDALONE(when), XWTimerProc proc,
void* closure )
{
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
XP_U32 timerID;
XP_U32 howLong;
XP_ASSERT( why < NUM_TIMERS_PLUS_ONE );
globals->timerProcs[why] = proc;
globals->timerClosures[why] = closure;
switch ( why ) {
case TIMER_PENDOWN:
howLong = 400;
break;
case TIMER_TIMERTICK:
howLong = 1000; /* 1 second */
break;
#if defined XWFEATURE_RELAY || defined COMMS_HEARTBEAT
case TIMER_COMMS:
howLong = when * 1000;
break;
#endif
default:
XP_ASSERT(0);
return;
}
globals->timerWhens[why] = GetCurrentTime() + howLong;
timerID = SetTimer( globals->hWnd, why, howLong, NULL);
globals->timerIDs[why] = timerID;
} /* ce_util_setTimer */
static XP_Bool
ce_util_altKeyDown( XW_UtilCtxt* XP_UNUSED(uc) )
{
return GetKeyState(VK_LSHIFT) < 0
|| GetKeyState(VK_RSHIFT) < 0;
}
static void
ce_util_requestTime( XW_UtilCtxt* uc )
{
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
PostMessage( globals->hWnd, XWWM_TIME_RQST, 0, 0 );
} /* palm_util_requestTime */
static XP_U32
ce_util_getCurSeconds( XW_UtilCtxt* XP_UNUSED(uc) )
{
/* This function is never called! */
XP_U32 ticks = GetCurrentTime();
ticks /= 1000;
LOG_RETURNF( "%ld", ticks );
return ticks;
/* return 0L; */
} /* ce_util_getCurSeconds */
static DictionaryCtxt*
ce_util_makeEmptyDict( XW_UtilCtxt* uc )
{
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
#ifdef STUBBED_DICT
return make_stubbed_dict( MPPARM_NOCOMMA(globals->mpool) );
#else
return ce_dictionary_make_empty( globals );
#endif
} /* ce_util_makeEmptyDict */
#ifdef XWFEATURE_RELAY
static XWStreamCtxt*
ce_util_makeStreamFromAddr( XW_UtilCtxt* uc, XP_PlayerAddr channelNo )
{
XWStreamCtxt* stream;
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
stream = make_generic_stream( globals );
stream_setOnCloseProc( stream, ce_send_on_close );
stream_setAddress( stream, channelNo );
return stream;
} /* ce_util_makeStreamFromAddr */
#endif
static const XP_UCHAR*
ce_util_getUserString( XW_UtilCtxt* XP_UNUSED(uc), XP_U16 stringCode )
{
switch( stringCode ) {
case STRD_REMAINING_TILES_ADD:
return (XP_UCHAR*)"+ %d [all remaining tiles]";
case STRD_UNUSED_TILES_SUB:
return (XP_UCHAR*)"- %d [unused tiles]";
case STR_BONUS_ALL:
return (XP_UCHAR*)"Bonus for using all tiles: 50" XP_CR;
case STRD_TURN_SCORE:
return (XP_UCHAR*)"Score for turn: %d" XP_CR;
case STR_COMMIT_CONFIRM:
return (XP_UCHAR*)"Commit the current move?" XP_CR;
case STR_LOCAL_NAME:
return (XP_UCHAR*)"%s";
case STR_NONLOCAL_NAME:
return (XP_UCHAR*)"%s (remote)";
case STRD_TIME_PENALTY_SUB:
return (XP_UCHAR*)" - %d [time]";
case STRD_CUMULATIVE_SCORE:
return (XP_UCHAR*)"Cumulative score: %d" XP_CR;
case STRS_MOVE_ACROSS:
return (XP_UCHAR*)"move (from %s across)" XP_CR;
case STRS_MOVE_DOWN:
return (XP_UCHAR*)"move (from %s down)" XP_CR;
case STRS_TRAY_AT_START:
return (XP_UCHAR*)"Tray at start: %s" XP_CR;
case STRS_NEW_TILES:
return (XP_UCHAR*)"New tiles: %s" XP_CR;
case STRSS_TRADED_FOR:
return (XP_UCHAR*)"Traded %s for %s.";
case STR_PASS:
return (XP_UCHAR*)"pass" XP_CR;
case STR_PHONY_REJECTED:
return (XP_UCHAR*)"Illegal word in move; turn lost!" XP_CR;
case STRD_ROBOT_TRADED:
return (XP_UCHAR*)"Robot traded tiles %d this turn.";
case STR_ROBOT_MOVED:
return (XP_UCHAR*)"The robot made this move:" XP_CR;
case STR_REMOTE_MOVED:
return (XP_UCHAR*)"Remote player made this move:" XP_CR;
case STR_PASSED:
return (XP_UCHAR*)"Passed";
case STRSD_SUMMARYSCORED:
return (XP_UCHAR*)"%s:%d";
case STRD_TRADED:
return (XP_UCHAR*)"Traded %d";
case STR_LOSTTURN:
return (XP_UCHAR*)"Lost turn";
#ifndef XWFEATURE_STANDALONE_ONLY
case STR_LOCALPLAYERS:
return (XP_UCHAR*)"Locl playrs:";
#endif
case STR_TOTALPLAYERS:
return (XP_UCHAR*)"Player count:";
case STRS_VALUES_HEADER:
return (XP_UCHAR*)"%s counts/values:" XP_CR;
default:
XP_LOGF( "stringCode=%d", stringCode );
return (XP_UCHAR*)"unknown code";
}
} /* ce_util_getUserString */
static void
ce_formatBadWords( BadWordInfo* bwi, XP_UCHAR buf[], XP_U16 bufsiz )
{
XP_U16 i;
for ( i = 0, buf[0] = '\0'; ; ) {
XP_UCHAR wordBuf[18];
sprintf( wordBuf, "\"%s\"", bwi->words[i] );
XP_ASSERT( strlen(wordBuf) < sizeof(wordBuf)-1 );
strncat( buf, wordBuf, bufsiz - 1 );
if ( ++i == bwi->nWords ) {
break;
}
bufsiz -= strlen( wordBuf );
strncat( buf, ", ", bufsiz - 1 );
bufsiz -= 2;
}
} /* ce_formatBadWords */
static XP_Bool
ce_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi,
XP_U16 XP_UNUSED(turn), XP_Bool turnLost )
{
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
XP_UCHAR wordsBuf[256];
XP_UCHAR msgBuf[256];
XP_Bool isOk;
ce_formatBadWords( bwi, wordsBuf, sizeof(wordsBuf) );
XP_SNPRINTF( msgBuf, VSIZE(msgBuf), XP_L("Word[s] %s not found in dictionary."), wordsBuf );
if ( turnLost ) {
ceMessageBoxChar( globals, msgBuf, L"Illegal word",
MB_OK | MB_ICONHAND );
isOk = XP_TRUE;
} else {
XP_STRCAT( msgBuf, XP_L(" Use it anyway?") );
assertOnTop( globals->hWnd );
isOk = queryBoxChar( globals->hWnd, msgBuf );
}
return isOk;
} /* ce_util_warnIllegalWord */
static void
ce_util_remSelected( XW_UtilCtxt* uc )
{
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
PostMessage( globals->hWnd, XWWM_REM_SEL, 0, 0 );
}
#ifndef XWFEATURE_STANDALONE_ONLY
static void
ce_util_addrChange( XW_UtilCtxt* uc,
const CommsAddrRec* oldAddr,
const CommsAddrRec* newAddr )
{
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
XP_LOGF( "%s: old: %s -> new: %s", __func__,
ConnType2Str( oldAddr->conType ),
ConnType2Str( newAddr->conType ) );
/* A lot more needs to be tested for and done here... */
if ( COMMS_CONN_NONE == newAddr->conType ) {
if ( !!globals->socketWrap ) {
ce_sockwrap_delete( globals->socketWrap );
globals->socketWrap = NULL;
}
}
XP_LOGF( "ce_util_addrChange called; DO SOMETHING." );
} /* ce_util_addrChange */
#endif
#ifdef XWFEATURE_SEARCHLIMIT
static XP_Bool
ce_util_getTraySearchLimits( XW_UtilCtxt* uc, XP_U16* min, XP_U16* max )
{
CEAppGlobals* globals = (CEAppGlobals*)uc->closure;
HintLimitsState hls;
XP_MEMSET( &hls, 0, sizeof(hls) );
hls.dlgHdr.globals = globals;
hls.min = *min;
hls.max = *max;
assertOnTop( globals->hWnd );
DialogBoxParam( globals->hInst, (LPCTSTR)IDD_ASKHINTLIMTS, globals->hWnd,
(DLGPROC)HintLimitsDlg, (long)&hls );
if ( !hls.cancelled ) {
*min = hls.min;
*max = hls.max;
}
return !hls.cancelled;
} /* ce_util_getTraySearchLimits */
#endif
#ifdef SHOW_PROGRESS
blah blah -- these are not in use
static void
ce_util_engineStarting( XW_UtilCtxt* uc )
{
} /* ce_util_engineStarting */
static void
ce_util_engineStopping( XW_UtilCtxt* uc )
{
} /* ce_util_engineStopping */
#endif