/* -*-mode: C; fill-column: 77; c-basic-offset: 4; compile-command: "make 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. */ #include /* swprintf */ #include "ceginfo.h" #include "cemain.h" #include "ceutil.h" #include "cedict.h" #include "cecondlg.h" #include "strutils.h" #include "cedebug.h" #include "strutils.h" #include "ceresstr.h" #define NUM_COLS 4 #define MENUDICTS_INCR 16 static XP_S16 findInsertPoint( const wchar_t* wPath, wchar_t** menuDicts, XP_U16 nMenuDicts ) { XP_S16 loc = 0; /* simple case: nothing here */ if ( nMenuDicts > 0 ) { wchar_t thisShortBuf[CE_MAX_PATH_LEN+1]; wchar_t* thisShortName = wbname( thisShortBuf, sizeof(thisShortBuf), wPath ); /* If the short path doesn't already exist, find where it belongs. This is wasteful if we're doing this a lot since the short path isn't cached. */ for ( /* loc = 0*/; loc < nMenuDicts; ++loc ) { wchar_t oneShortBuf[CE_MAX_PATH_LEN+1]; wchar_t* oneShortName = wbname( oneShortBuf, sizeof(oneShortBuf), menuDicts[loc] ); int diff = _wcsicmp( thisShortName, oneShortName ); if ( diff > 0 ) { continue; } else if ( diff == 0 ) { loc = -1; } break; } } return loc; } /* findInsertPoint */ static XP_Bool addDictToState( const wchar_t* wPath, XP_U16 XP_UNUSED(index), void* ctxt ) { GameInfoState* state = (GameInfoState*)ctxt; /* Let's display only the short form, but save the whole path */ wchar_t* wstr; XP_U16 len; XP_S16 loc; /* < 0 means skip it */ loc = findInsertPoint( wPath, state->menuDicts, state->nMenuDicts ); if ( loc >= 0 ) { /* make a copy of the long name */ len = wcslen( wPath ) + 1; wstr = (wchar_t*)XP_MALLOC( state->dlgHdr.globals->mpool, len * sizeof(wstr[0]) ); XP_MEMCPY( wstr, wPath, len*sizeof(wstr[0]) ); if ( !state->menuDicts ) { XP_ASSERT( state->nMenuDicts == 0 ); XP_ASSERT( state->capMenuDicts == 0 ); state->capMenuDicts = MENUDICTS_INCR; state->menuDicts = (wchar_t**)XP_MALLOC( state->dlgHdr.globals->mpool, state->capMenuDicts * sizeof(state->menuDicts[0]) ); } else if ( state->nMenuDicts == state->capMenuDicts ) { state->capMenuDicts += MENUDICTS_INCR; state->menuDicts = (wchar_t**)XP_REALLOC( state->dlgHdr.globals->mpool, state->menuDicts, state->capMenuDicts * sizeof(state->menuDicts[0]) ); } if ( loc < state->nMenuDicts ) { XP_MEMMOVE( &state->menuDicts[loc+1], &state->menuDicts[loc], (state->nMenuDicts - loc) * sizeof(state->menuDicts[0]) ); } state->menuDicts[loc] = wstr; ++state->nMenuDicts; } return XP_FALSE; } /* addDictToState */ static void addDictsToMenu( GameInfoState* state ) { wchar_t* shortname; wchar_t shortPath[CE_MAX_PATH_LEN+1]; XP_U16 i, nMenuDicts = state->nMenuDicts; XP_S16 sel = 0; CEAppGlobals* globals = state->dlgHdr.globals; /* insert the short names in the menu */ for ( i = 0; i < nMenuDicts; ++i ) { wchar_t* wPath = state->menuDicts[i]; shortname = wbname( shortPath, sizeof(shortPath), wPath ); SendDlgItemMessage( state->dlgHdr.hDlg, state->dictListId, ADDSTRING(globals), 0, (long)shortname ); if ( state->newDictName[0] != 0 && sel == 0 ) { XP_UCHAR buf[CE_MAX_PATH_LEN+1]; WideCharToMultiByte( CP_ACP, 0, wPath, -1, buf, sizeof(buf), NULL, NULL ); if ( 0 == XP_STRCMP( buf, state->newDictName ) ) { sel = i; } } } SendDlgItemMessage( state->dlgHdr.hDlg, state->dictListId, SETCURSEL(globals), sel, 0L ); } /* addDictsToMenu */ static void cleanupGameInfoState( GameInfoState* state ) { if ( !!state->menuDicts ) { XP_U16 nMenuDicts = state->nMenuDicts; XP_U16 i; for ( i = 0; i < nMenuDicts; ++i ) { XP_FREE( state->dlgHdr.globals->mpool, state->menuDicts[i] ); } XP_FREE( state->dlgHdr.globals->mpool, state->menuDicts ); state->menuDicts = NULL; } if ( !!state->moveIds ) { XP_FREE( state->dlgHdr.globals->mpool, state->moveIds ); state->moveIds = NULL; } } /* cleanupGameInfoState */ static void loadFromGameInfo( GameInfoState* state ) { XP_U16 ii; CEAppGlobals* globals = state->dlgHdr.globals; CurGameInfo* gi = &globals->gameInfo; #ifndef XWFEATURE_STANDALONE_ONLY XP_U16 role_ids[] = { IDS_ROLE_STANDALONE, IDS_ROLE_HOST, IDS_ROLE_GUEST }; for ( ii = 0; ii < VSIZE(role_ids); ++ii ) { const wchar_t* wstr = ceGetResStringL( globals, role_ids[ii] ); SendDlgItemMessage( state->dlgHdr.hDlg, state->roleComboId, ADDSTRING(globals), 0, (long)wstr ); } #endif for ( ii = 0; ii < MAX_NUM_PLAYERS; ++ii ) { wchar_t widebuf[8]; /* put a string in the moronic combobox */ swprintf( widebuf, L"%d", ii + 1 ); SendDlgItemMessage( state->dlgHdr.hDlg, state->nPlayersId, ADDSTRING(globals), 0, (long)widebuf ); } newg_load( state->newGameCtx, gi ); #ifndef STUBBED_DICT if ( !!gi->dictName ) { XP_MEMCPY( state->newDictName, gi->dictName, (XP_U16)XP_STRLEN(gi->dictName)+1 ); } if ( state->isNewGame ) { (void)ceLocateNDicts( globals, CE_MAXDICTS, addDictToState, state ); } else { wchar_t wPath[CE_MAX_PATH_LEN+1]; XP_ASSERT( gi->dictName[0] != '\0' ); MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, gi->dictName, -1, wPath, VSIZE(wPath) ); (void)addDictToState( wPath, 0, state ); } addDictsToMenu( state ); #endif if ( !state->isNewGame ) { ceEnOrDisable( state->dlgHdr.hDlg, state->dictListId, XP_FALSE ); } } /* loadFromGameInfo */ static XP_Bool stateToGameInfo( GameInfoState* state ) { CEAppGlobals* globals = state->dlgHdr.globals; CurGameInfo* gi = &globals->gameInfo; HWND hDlg = state->dlgHdr.hDlg; XP_Bool timerOn; XP_Bool success = newg_store( state->newGameCtx, gi, XP_TRUE ); if ( success ) { /* dictionary */ { int sel; sel = SendDlgItemMessage( hDlg, state->dictListId, GETCURSEL(globals), 0, 0L ); if ( sel >= 0 ) { WideCharToMultiByte( CP_ACP, 0, state->menuDicts[sel], -1, state->newDictName, sizeof(state->newDictName), NULL, NULL ); } replaceStringIfDifferent( globals->mpool, &gi->dictName, state->newDictName ); } /* timer */ timerOn = ceGetChecked( hDlg, TIMER_CHECK ); gi->timerEnabled = timerOn; if ( timerOn ) { XP_UCHAR numBuf[10]; XP_U16 len = sizeof(numBuf); ceGetDlgItemText( hDlg, TIMER_EDIT, numBuf, &len ); if ( len > 0 ) { XP_U16 num = atoi( numBuf ); gi->gameSeconds = num * 60; } } /* preferences */ if ( state->prefsChanged ) { loadCurPrefsFromState( globals, &globals->appPrefs, gi, &state->prefsPrefs ); } } return success; } /* stateToGameInfo */ static void raiseForHiddenPlayers( GameInfoState* state, XP_U16 nPlayers ) { HWND hDlg = state->dlgHdr.hDlg; XP_U16 ii; XP_S16 moveY; if ( nPlayers != state->prevNPlayers ) { if ( !state->moveIds ) { XP_S16 ids[32]; HWND child; RECT rect; XP_U16 playersBottom; ceGetItemRect( hDlg, NAME_EDIT4, &rect ); playersBottom = rect.bottom; ceGetItemRect( hDlg, NAME_EDIT3, &rect ); state->playersSpacing = playersBottom - rect.bottom; for ( child = GetWindow( hDlg, GW_CHILD ), ii = 0; !!child; child = GetWindow( child, GW_HWNDNEXT ) ) { XP_S16 resID = GetDlgCtrlID( child ); if ( resID > 0 ) { ceGetItemRect( hDlg, resID, &rect ); if ( rect.top > playersBottom ) { XP_ASSERT( ii < VSIZE(ids)-1 ); ids[ii] = resID; ++ii; } } } state->moveIds = XP_MALLOC( state->dlgHdr.globals->mpool, sizeof(state->moveIds[0]) * ii ); XP_MEMCPY( state->moveIds, ids, sizeof(state->moveIds[0]) * ii ); state->nMoveIds = ii; } moveY = state->playersSpacing * (nPlayers - state->prevNPlayers); for ( ii = 0; ii < state->nMoveIds; ++ii ) { ceMoveItem( hDlg, state->moveIds[ii], 0, moveY ); } state->prevNPlayers = nPlayers; #ifdef _WIN32_WCE if ( IS_SMARTPHONE(state->dlgHdr.globals) ) { SendMessage( hDlg, DM_RESETSCROLL, (WPARAM)FALSE, (LPARAM)TRUE ); } #endif } } static void handlePrefsButton( HWND hDlg, CEAppGlobals* globals, GameInfoState* state ) { XP_Bool colorsChanged; if ( WrapPrefsDialog( hDlg, globals, &state->prefsPrefs, state->isNewGame, &colorsChanged ) ) { state->prefsChanged = XP_TRUE; state->colorsChanged = colorsChanged; /* nothing to do until user finally does confirm the parent dialog */ } } /* handlePrefsButton */ #ifndef XWFEATURE_STANDALONE_ONLY static XP_Bool callConnsDlg( GameInfoState* state ) { XP_Bool connsComplete = XP_FALSE; /* maybe flag when this isn't changed? No. Check on "Ok" as tagged elsewhere. */ if ( WrapConnsDlg( state->dlgHdr.hDlg, state->dlgHdr.globals, &state->prefsPrefs.addrRec, &state->prefsPrefs.addrRec, state->lastRole, state->isNewGame, &connsComplete ) ) { state->addrChanged = XP_TRUE; } return connsComplete; } static void handleConnOptionsButton( GameInfoState* state ) { HWND hDlg = state->dlgHdr.hDlg; CEAppGlobals* globals = state->dlgHdr.globals; DeviceRole role; NGValue value; role = (DeviceRole)SendDlgItemMessage( hDlg, state->roleComboId, GETCURSEL(globals), 0, 0L); value.ng_role = role; newg_attrChanged( state->newGameCtx, NG_ATTR_ROLE, value ); } /* handleConnOptionsButton */ #endif static XP_U16 resIDForCol( XP_U16 player, NewGameColumn col ) { XP_U16 resID = 0; switch ( col ) { #ifndef XWFEATURE_STANDALONE_ONLY case NG_COL_REMOTE: resID = REMOTE_CHECK1; break; #endif case NG_COL_ROBOT: resID = ROBOT_CHECK1; break; case NG_COL_NAME: resID = NAME_EDIT1; break; case NG_COL_PASSWD: resID = PASS_EDIT1; break; } XP_ASSERT( resID != 0 ); return resID + ( player * NUM_COLS ); } /* resIDForCol */ static XP_U16 resIDForAttr( GameInfoState* state, NewGameAttr attr ) { XP_U16 resID = 0; switch( attr ) { case NG_ATTR_NPLAYERS: resID = state->nPlayersId; break; #ifndef XWFEATURE_STANDALONE_ONLY case NG_ATTR_ROLE: resID = state->roleComboId; break; case NG_ATTR_CANCONFIG: resID = GIROLECONF_BUTTON; break; case NG_ATTR_REMHEADER: resID = IDC_REMOTE_LABEL; break; #endif case NG_ATTR_NPLAYHEADER: resID = IDC_TOTAL_LABEL; break; case NG_ATTR_CANJUGGLE: resID = GIJUGGLE_BUTTON; break; default: break; } XP_ASSERT( resID != 0 ); return resID; } /* resIDForAttr */ static void doForNWEnable( HWND hDlg, XP_U16 resID, XP_TriEnable enable ) { XP_Bool makeVisible = enable != TRI_ENAB_HIDDEN; ceShowOrHide( hDlg, resID, makeVisible ); if ( makeVisible ) { ceEnOrDisable( hDlg, resID, enable == TRI_ENAB_ENABLED ); } } /* doForNWEnable */ static void ceEnableColProc( void* closure, XP_U16 player, NewGameColumn col, XP_TriEnable enable ) { GameInfoState* state = (GameInfoState*)closure; XP_U16 resID = resIDForCol( player, col ); doForNWEnable( state->dlgHdr.hDlg, resID, enable ); } static void ceEnableAttrProc( void* closure, NewGameAttr attr, XP_TriEnable enable ) { GameInfoState* state = (GameInfoState*)closure; XP_U16 resID = resIDForAttr( state, attr ); doForNWEnable( state->dlgHdr.hDlg, resID, enable ); } /* ceEnableAttrProc */ static void ceGetColProc( void* closure, XP_U16 player, NewGameColumn col, NgCpCallbk cpcb, const void* cpClosure ) { NGValue value; GameInfoState* state = (GameInfoState*)closure; XP_U16 resID = resIDForCol( player, col ); XP_UCHAR txt[128]; XP_U16 len; switch ( col ) { #ifndef XWFEATURE_STANDALONE_ONLY case NG_COL_REMOTE: #endif case NG_COL_ROBOT: value.ng_bool = ceGetChecked( state->dlgHdr.hDlg, resID ); break; case NG_COL_NAME: case NG_COL_PASSWD: len = sizeof(txt); ceGetDlgItemText( state->dlgHdr.hDlg, resID, txt, &len ); value.ng_cp = &txt[0]; break; default: XP_ASSERT(0); } (*cpcb)( value, cpClosure ); } /* ceGetColProc */ static void ceSetColProc( void* closure, XP_U16 player, NewGameColumn col, const NGValue value ) { GameInfoState* state = (GameInfoState*)closure; XP_U16 resID = resIDForCol( player, col ); const XP_UCHAR* cp; XP_UCHAR buf[32]; switch( col ) { case NG_COL_PASSWD: case NG_COL_NAME: if ( NULL != value.ng_cp ) { cp = value.ng_cp; } else if ( col == NG_COL_NAME ) { const XP_UCHAR* str = ceGetResString( state->dlgHdr.globals, IDS_PLAYER_FORMAT ); snprintf( buf, sizeof(buf), str, player + 1 ); cp = buf; } else { cp = ""; } ceSetDlgItemText( state->dlgHdr.hDlg, resID, cp ); break; #ifndef XWFEATURE_STANDALONE_ONLY case NG_COL_REMOTE: #endif case NG_COL_ROBOT: ceSetChecked( state->dlgHdr.hDlg, resID, value.ng_bool ); break; default: XP_ASSERT(0); } } /* ceSetColProc */ static void ceSetAttrProc(void* closure, NewGameAttr attr, const NGValue value ) { GameInfoState* state = (GameInfoState*)closure; XP_U16 resID = resIDForAttr( state, attr ); CEAppGlobals* globals = state->dlgHdr.globals; switch ( attr ) { case NG_ATTR_NPLAYERS: SendDlgItemMessage( state->dlgHdr.hDlg, resID, SETCURSEL(globals), value.ng_u16 - 1, 0L ); raiseForHiddenPlayers( state, value.ng_u16 ); break; #ifndef XWFEATURE_STANDALONE_ONLY case NG_ATTR_ROLE: SendDlgItemMessage( state->dlgHdr.hDlg, resID, SETCURSEL(globals), value.ng_role, 0L ); break; #endif case NG_ATTR_NPLAYHEADER: ceSetDlgItemText( state->dlgHdr.hDlg, resID, value.ng_cp ); break; default: break; } } /* ceSetAttrProc */ static XP_U16 playerFromID( XP_U16 id, XP_U16 base ) { XP_U16 player; player = (id - base) / NUM_COLS; return player; } static void handleColChecked( GameInfoState* state, XP_U16 id, XP_U16 base ) { NGValue value; XP_U16 player = playerFromID( id, base ); value.ng_bool = ceGetChecked( state->dlgHdr.hDlg, id ); newg_colChanged( state->newGameCtx, player ); } /* It's too much work at this point to get the icon button looking good, * e.g. properly greyed-out when disabled. So I'm sticking with the "J". * Here's the code to start with if I get more ambitious. Remember: the * GIJUGGLE_BUTTON needs to have the BS_OWNERDRAW attribute for this to work. */ #ifdef OWNERDRAW_JUGGLE static void ceDrawIconButton( CEAppGlobals* globals, DRAWITEMSTRUCT* dis ) { HBITMAP bmp = LoadBitmap( globals->hInst, MAKEINTRESOURCE(IDB_JUGGLEBUTTON) ); if ( !!bmp ) { ceDrawBitmapInRect( dis->hDC, &dis->rcItem, bmp ); DeleteObject( bmp ); } } /* ceDrawColorButton */ #endif static void checkUpdateCombo( GameInfoState* state, XP_U16 id ) { HWND hDlg = state->dlgHdr.hDlg; if ( id == state->nPlayersId ) { if ( state->isNewGame ) { /* ignore if in info mode */ XP_S16 sel; XP_U16 nPlayers; NGValue value; sel = SendDlgItemMessage( hDlg, id, GETCURSEL(state->dlgHdr.globals), 0, 0L); nPlayers = 1 + sel; value.ng_u16 = nPlayers; XP_ASSERT( !!state->newGameCtx ); newg_attrChanged( state->newGameCtx, NG_ATTR_NPLAYERS, value ); raiseForHiddenPlayers( state, nPlayers ); } } else if ( id == state->roleComboId ) { XP_ASSERT( SERVER_STANDALONE == 0 ); handleConnOptionsButton( state ); } } /* checkUpdateCombo */ LRESULT CALLBACK GameInfo(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { CEAppGlobals* globals; XP_U16 id; GameInfoState* state; LRESULT result = FALSE; /* XP_LOGF( "%s: %s(%d)", __func__, messageToStr( message ), message ); */ if ( message == WM_INITDIALOG ) { SetWindowLongPtr( hDlg, GWL_USERDATA, lParam ); state = (GameInfoState*)lParam; globals = state->dlgHdr.globals; state->nPlayersId = LB_IF_PPC(globals,IDC_NPLAYERSCOMBO); #ifndef XWFEATURE_STANDALONE_ONLY state->roleComboId = LB_IF_PPC(globals, IDC_ROLECOMBO); #endif state->dictListId = LB_IF_PPC(globals,IDC_DICTLIST); state->prevNPlayers = MAX_NUM_PLAYERS; ceDlgSetup( &state->dlgHdr, hDlg, DLG_STATE_TRAPBACK ); #ifndef XWFEATURE_STANDALONE_ONLY ceDlgComboShowHide( &state->dlgHdr, IDC_ROLECOMBO ); #endif ceDlgComboShowHide( &state->dlgHdr, IDC_NPLAYERSCOMBO ); ceDlgComboShowHide( &state->dlgHdr, IDC_DICTLIST ); state->newGameCtx = newg_make( MPPARM(globals->mpool) state->isNewGame, &globals->util, ceEnableColProc, ceEnableAttrProc, ceGetColProc, ceSetColProc, ceSetAttrProc, state ); loadFromGameInfo( state ); loadStateFromCurPrefs( globals, &globals->appPrefs, &globals->gameInfo, &state->prefsPrefs ); if ( state->isNewGame ) { (void)SetWindowText( hDlg, ceGetResStringL( globals, IDS_NEW_GAME ) ); } result = TRUE; } else { state = (GameInfoState*)GetWindowLongPtr( hDlg, GWL_USERDATA ); if ( !!state ) { globals = state->dlgHdr.globals; XP_ASSERT( hDlg == state->dlgHdr.hDlg ); result = ceDoDlgHandle( &state->dlgHdr, message, wParam, lParam ); if ( !result ) { switch (message) { #ifdef OWNERDRAW_JUGGLE case WM_DRAWITEM: /* for BS_OWNERDRAW style */ ceDrawIconButton( globals, (DRAWITEMSTRUCT*)lParam ); result = TRUE; break; #endif case WM_NOTIFY: if ( !!state->newGameCtx ) { checkUpdateCombo( state, LOWORD(wParam)-1 ); } break; case WM_COMMAND: result = TRUE; id = LOWORD(wParam); if ( id == state->nPlayersId #ifndef XWFEATURE_STANDALONE_ONLY || id == state->roleComboId #endif ) { if ( HIWORD(wParam) == CBN_SELCHANGE ) { checkUpdateCombo( state, id ); } } else { switch( id ) { case ROBOT_CHECK1: case ROBOT_CHECK2: case ROBOT_CHECK3: case ROBOT_CHECK4: handleColChecked( state, id, ROBOT_CHECK1 ); break; #ifndef XWFEATURE_STANDALONE_ONLY case REMOTE_CHECK1: case REMOTE_CHECK2: case REMOTE_CHECK3: case REMOTE_CHECK4: handleColChecked( state, id, REMOTE_CHECK1 ); break; case IDC_ROLECOMBO: case IDC_ROLECOMBO_PPC: if ( HIWORD(wParam) == CBN_SELCHANGE ) { /* If we've switched to a state where we'll be connecting */ handleConnOptionsButton( state ); } break; case GIROLECONF_BUTTON: (void)callConnsDlg( state ); break; #endif case GIJUGGLE_BUTTON: XP_ASSERT( state->isNewGame ); /* Juggle vs switch. On Win32, updates are coalesced so you don't see anything on screen if you change a field then change it back. In terms of messages, all we see here is a WM_CTLCOLOREDIT for each field being changed. If I post a custom event here, it comes in *before* the WM_CTLCOLOREDIT events. Short of a timer, which starts a race with the user, I see no way to get notified after the drawing's done. So for now, we switch rather than juggle: call juggle until something actually happens. */ while ( !newg_juggle( state->newGameCtx ) ) { } break; case OPTIONS_BUTTON: handlePrefsButton( hDlg, globals, state ); break; case IDOK: { DeviceRole role = (DeviceRole) SendDlgItemMessage( hDlg, state->roleComboId, GETCURSEL(globals), 0, 0L ); if ( role != SERVER_STANDALONE && !comms_checkComplete( &state->prefsPrefs.addrRec ) && !callConnsDlg( state ) ) { break; } else if ( !stateToGameInfo( state ) ) { break; } } /* FALLTHRU */ case IDCANCEL: EndDialog(hDlg, id); state->userCancelled = id == IDCANCEL; cleanupGameInfoState( state ); newg_destroy( state->newGameCtx ); state->newGameCtx = NULL; } break; default: result = FALSE; } } } } } return result; } /* GameInfo */