/* -*- 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 #include #include #include /* time() */ #include #ifdef _WIN32_WCE # include #endif /* #include */ #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 ¬Done ); 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