diff --git a/xwords4/palm/ownerhash.h b/xwords4/palm/ownerhash.h new file mode 100644 index 000000000..01b850d52 --- /dev/null +++ b/xwords4/palm/ownerhash.h @@ -0,0 +1,33 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 1999 - 2002 by Eric House (fixin@peak.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. + */ + +#ifndef _OWNERHASH_H_ + +static unsigned long +my_hash( unsigned char* str ) { + unsigned char ch; + unsigned long result = 0; + while ( ( ch = *str++ ) != '\0' ) { + result += result << 2 ^ ~ch; + } + return result; +} /* my_hash */ + +#define HASH(s) my_hash(s) +#endif diff --git a/xwords4/palm/palmdict.c b/xwords4/palm/palmdict.c new file mode 100644 index 000000000..b811f0c71 --- /dev/null +++ b/xwords4/palm/palmdict.c @@ -0,0 +1,340 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 1997-2001 by Eric House (fixin@peak.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 +#include +#include + +#include "dictnryp.h" +#include "dawg.h" +#include "palmdict.h" +#include "dictlist.h" +#include "dictui.h" + +typedef struct DictStart { + unsigned long indexStart; + array_edge* array; +} DictStart; + + +#define NO_REFCOUNT -2 + +struct PalmDictionaryCtxt { + DictionaryCtxt super; + dawg_header* headerRecP; + DmOpenRef dbRef; + XP_U16 nRecords; + UInt16 cardNo; /* added to track VFS imported file for */ + LocalID dbID; /* deletion later */ + XP_UCHAR location; /* VFS or storage mem */ + XP_S8 refCount; + PalmDictList* dl; + DictStart dictStarts[1]; +}; + +static void palm_dictionary_destroy( DictionaryCtxt* dict ); +static XP_U16 countSpecials( unsigned char* ptr, UInt16 nChars ); +static void setupSpecials( MPFORMAL PalmDictionaryCtxt* ctxt, + Xloc_specialEntry* specialStart, XP_U16 nSpecials ); +static void unlockAndRelease( MemPtr p, DmOpenRef dbRef, short recIndex ); + +DictionaryCtxt* +palm_dictionary_make( MPFORMAL XP_UCHAR* dictName, PalmDictList* dl ) +{ + Boolean found; + UInt16 cardNo; + LocalID dbID; + DmOpenRef dbRef; + PalmDictionaryCtxt tDictBuf; + PalmDictionaryCtxt* ctxt; + MemHandle tmpH; + dawg_header* headerRecP; + unsigned char* charPtr; + UInt16 nChars, nSpecials; + unsigned long offset = 0; + DictListEntry* dle; + Err err; + XP_U16 i; + + /* check and see if there's already a dict for this name. If yes, + increment its refcount and return. */ + if ( !!dictName && getDictWithName( dl, dictName, &dle ) ) { + PalmDictionaryCtxt* dict = (PalmDictionaryCtxt*)dle->dict; + if ( !!dict ) { + ++dict->refCount; + return (DictionaryCtxt*)dict; + } + } + + if ( !!dictName ) { + ctxt = &tDictBuf; + } else { + /* If the name's NULL, we still need to create a dict, as the server + may be called to fill it in from the stream later. */ + ctxt = XP_MALLOC( mpool, sizeof(*ctxt) ); + } + + XP_MEMSET( ctxt, 0, sizeof(*ctxt) ); + MPASSIGN( ctxt->super.mpool, mpool ); + + if ( !!dictName ) { + XP_ASSERT( XP_STRLEN((const char*)dictName) > 0 ); + + ctxt->super.name = dictName; + ctxt->super.destructor = palm_dictionary_destroy; + + found = getDictWithName( dl, dictName, &dle ); + + if ( !found ) { + goto errExit; + } + + if ( dle->location == DL_VFS ) { + err = VFSImportDatabaseFromFile( dle->u.vfsData.volNum, + (const char*)dle->path, + &cardNo, &dbID ); + if ( err != errNone ) { + goto errExit; + } + } else { + cardNo = dle->u.dmData.cardNo; + dbID = dle->u.dmData.dbID; + } + + ctxt->refCount = 1; + ctxt->dl = dl; + ctxt->dbID = dbID; + ctxt->cardNo = cardNo; + ctxt->location = dle->location; + + ctxt->dbRef = dbRef = DmOpenDatabase( cardNo, dbID, dmModeReadOnly ); + tmpH = DmGetRecord( dbRef, 0 ); // <- should be a constant + ctxt->headerRecP = headerRecP = (dawg_header*)MemHandleLock( tmpH ); + XP_ASSERT( MemHandleLockCount(tmpH) == 1 ); + + tmpH = DmQueryRecord( dbRef, headerRecP->charTableRecNum ); + XP_ASSERT( !!tmpH ); + charPtr = (unsigned char*)MemHandleLock( tmpH ); + XP_ASSERT( MemHandleLockCount( tmpH ) == 1 ); + ctxt->super.nFaces = nChars = MemPtrSize(charPtr); + ctxt->super.faces16 = + XP_MALLOC( mpool, nChars * sizeof(ctxt->super.faces16[0])); + XP_ASSERT( !!ctxt->super.faces16 ); + for ( i = 0; i < nChars; ++i ) { + ctxt->super.faces16[i] = charPtr[i]; + } + nSpecials = countSpecials( charPtr, nChars ); + unlockAndRelease( charPtr, dbRef, headerRecP->charTableRecNum ); + + tmpH = DmGetRecord( dbRef, headerRecP->valTableRecNum ); + charPtr = (unsigned char*)MemHandleLock(tmpH); + XP_ASSERT( MemHandleLockCount( tmpH ) == 1 ); + ctxt->super.countsAndValues = charPtr + sizeof(Xloc_header); + + /* for those dicts with special chars */ + if ( nSpecials > 0 ) { + Xloc_specialEntry* specialStart = (Xloc_specialEntry*) + (ctxt->super.countsAndValues + (ctxt->super.nFaces*2)); + setupSpecials( MPPARM(mpool) ctxt, specialStart, nSpecials ); + } + + if ( headerRecP->firstEdgeRecNum == 0 ) { /* emtpy dict */ + ctxt->super.topEdge = NULL; + ctxt->nRecords = 0; + } else { + short index; + XP_U16 nRecords; + + nRecords = DmNumRecords(dbRef) - headerRecP->firstEdgeRecNum; + + /* alloacate now that we know the size. */ + ctxt = XP_MALLOC( mpool, + sizeof(*ctxt) + (nRecords * sizeof(DictStart))); + XP_MEMCPY( ctxt, &tDictBuf, sizeof(tDictBuf) ); + ctxt->nRecords = nRecords; + + for ( index = 0; index < nRecords; ++index ) { + MemHandle record = + DmGetRecord( dbRef, index + headerRecP->firstEdgeRecNum ); + ctxt->dictStarts[index].indexStart = offset; + + /* cast to short to avoid libc call */ + XP_ASSERT( MemHandleSize(record) < 0xFFFF ); + XP_ASSERT( ((unsigned short)(MemHandleSize(record)) % 3 ) == 0); + offset += ((unsigned short)MemHandleSize(record)) / 3; + ctxt->dictStarts[index].array = + (array_edge*)MemHandleLock( record ); + XP_ASSERT( MemHandleLockCount(record) == 1 ); + } + + XP_ASSERT( index == ctxt->nRecords ); + ctxt->dictStarts[index].indexStart = 0xFFFFFFFFL; + + ctxt->super.topEdge = ctxt->dictStarts[0].array; + } + + setBlankTile( (DictionaryCtxt*)ctxt ); + + cacheDictForName( dl, dictName, (DictionaryCtxt*)ctxt ); + } else { + ctxt->refCount = NO_REFCOUNT; + } + + return (DictionaryCtxt*)ctxt; + + errExit: + if ( !!ctxt ) { + XP_ASSERT( ctxt == &tDictBuf ); + } + return NULL; +} /* palm_dictionary_make */ + +static XP_U16 +countSpecials( unsigned char* ptr, UInt16 nChars ) +{ + XP_U16 result = 0; + + while ( nChars-- ) { + unsigned char face = *ptr++; + if ( IS_SPECIAL(face) ) { + ++result; + } + } + return result; +} /* countSpecials */ + +/* We need an array of ptrs to bitmaps plus an array of ptrs to the text + * equivalents. + */ +static void +setupSpecials( MPFORMAL PalmDictionaryCtxt* ctxt, + Xloc_specialEntry* specialStart, XP_U16 nSpecials ) +{ + XP_U16 i; + char* base = (char*)specialStart; + XP_UCHAR** chars = XP_MALLOC( mpool, nSpecials * sizeof(*chars) ); + SpecialBitmaps* bitmaps = XP_MALLOC( mpool, nSpecials * sizeof(*bitmaps) ); + + XP_MEMSET( bitmaps, 0, nSpecials * sizeof(*bitmaps ) ); + + for ( i = 0; i < nSpecials; ++i ) { + chars[i] = specialStart->textVersion; + if ( specialStart->hasLarge ) { + bitmaps[i].largeBM = base + specialStart->hasLarge; + } + if ( specialStart->hasSmall ) { + bitmaps[i].smallBM = base + specialStart->hasSmall; + } + ++specialStart; + } + + ctxt->super.bitmaps = bitmaps; + ctxt->super.chars = chars; +} /* setupSpecials */ + +static void +unlockAndRelease( MemPtr p, DmOpenRef dbRef, short recIndex ) +{ + MemPtrUnlock( p ); + DmReleaseRecord( dbRef, recIndex, false ); +} /* unlockAndRelease */ + +static void +palm_dictionary_destroy( DictionaryCtxt* dict ) +{ + PalmDictionaryCtxt* ctxt = (PalmDictionaryCtxt*)dict; + + if ( ctxt->refCount != NO_REFCOUNT && --ctxt->refCount == 0 ) { + short i; + DmOpenRef dbRef; + dawg_header* headerRecP; + + if ( !!ctxt->super.name ) { + /* Remove from dict list */ + removeFromDictCache( ctxt->dl, ctxt->super.name, dict ); + } + + dbRef = ctxt->dbRef; + headerRecP = ctxt->headerRecP; + + XP_ASSERT( !!dbRef ); + + unlockAndRelease( ctxt->super.countsAndValues - sizeof(Xloc_header), + dbRef, headerRecP->valTableRecNum ); + + XP_FREE( dict->mpool, ctxt->super.faces16 ); + + for ( i = 0; i < ctxt->nRecords; ++i ) { + unlockAndRelease( ctxt->dictStarts[i].array, + dbRef, i + headerRecP->firstEdgeRecNum ); + } + + unlockAndRelease( headerRecP, dbRef, 0 ); + + DmCloseDatabase( dbRef ); + + if ( !!ctxt->super.name ) { + XP_FREE( dict->mpool, ctxt->super.name ); + } + if ( !!ctxt->super.bitmaps ) { + XP_FREE( dict->mpool, ctxt->super.bitmaps ); + } + if ( !!ctxt->super.chars ) { + XP_FREE( dict->mpool, ctxt->super.chars ); + } + + /* if we copied the db to memory we should nuke it, since user would + have copied it himself if he wanted it here permanently */ + if ( ctxt->location == DL_VFS ) { + DmDeleteDatabase( ctxt->cardNo, ctxt->dbID ); + } + + XP_FREE( dict->mpool, dict ); + } +} /* palm_dictionary_destroy */ + +#ifdef OVERRIDE_EDGE_FOR_INDEX +array_edge* +dict_edge_for_index( DictionaryCtxt* dict, XP_U32 index ) +{ + PalmDictionaryCtxt* ctxt = (PalmDictionaryCtxt*)dict; + array_edge* result; + + if ( index == 0 ) { + result = NULL; + } else { + DictStart* headerP; + for ( headerP = &ctxt->dictStarts[1]; + index >= headerP->indexStart; + ++headerP ) { + /* do nothing */ + } + --headerP; + index -= headerP->indexStart; +#ifdef NODE_CAN_4 + index *= dict->nodeSize; +#else + index *= 3; +#endif + result = headerP->array + index; + } + + return result; +} /* dict_edge_for_index */ +#endif diff --git a/xwords4/palm/palmdict.h b/xwords4/palm/palmdict.h new file mode 100644 index 000000000..8a72ca11b --- /dev/null +++ b/xwords4/palm/palmdict.h @@ -0,0 +1,34 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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. + */ + +#ifndef _PALMDICT_H_ +#define _PALMDICT_H_ + +#include "dictnry.h" +#include "mempool.h" +#include "dictui.h" + +DictionaryCtxt* palm_dictionary_make( MPFORMAL XP_UCHAR* dictName, + PalmDictList* dl ); +/* might eventually belong in the superclass */ +XP_UCHAR* dict_getName( DictionaryCtxt* dict ); + +typedef struct PalmDictionaryCtxt PalmDictionaryCtxt; + +#endif /* _PALMDICT_H_ */ diff --git a/xwords4/palm/palmdraw.c b/xwords4/palm/palmdraw.c new file mode 100644 index 000000000..2b2f1f504 --- /dev/null +++ b/xwords4/palm/palmdraw.c @@ -0,0 +1,1120 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 1999 - 2001 by Eric House (fixin@peak.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 +#include + +#include "draw.h" + +#include "palmmain.h" +#include "xwords4defines.h" +#include "LocalizedStrIncludes.h" + +#ifdef DIRECT_PALMOS_CALLS + +# define WINDRAWRECTANGLEFRAME(dc,f,rp) \ + (*(dc->winDrawRectangleFrameTrap))( (f), (rp) ) +# define WINFILLRECTANGLE(dc,r,f ) \ + (*(dc->winFillRectangleTrap))( (r), (f) ) +# define WINDRAWCHARS(dc, s, l, x, y ) \ + (*(dc->winDrawCharsTrap))((s), (l), (x), (y) ) +#else + +# define WINDRAWRECTANGLEFRAME(dc,f,rp) WinDrawRectangleFrame( f, rp ) +# define WINFILLRECTANGLE(dc,r,f ) WinFillRectangle((r),(f)) +# define WINDRAWCHARS(dc, s, l, x, y) WinDrawChars( (s), (l), (x), (y) ) +#endif + +#define NUMRECT_WIDTH 10 +#define NUMRECT_HEIGHT 10 + +#define FONT_HEIGHT 8 +#define LINE_SPACING 1 + +#define SCORE_SEP '.' +#define SCORE_SEPSTR "." + +#define TILE_SUBSCRIPT 1 /* draw tile with numbers below letters? */ + +static XP_Bool palm_common_draw_drawCell( DrawCtx* p_dctx, XP_Rect* rect, + XP_UCHAR* letters, XP_Bitmap bitmap, + XP_S16 owner, XWBonusType bonus, + XP_Bool isBlank, + XP_Bool isPending, XP_Bool isStar ); +static void palm_bnw_draw_score_drawPlayer( DrawCtx* p_dctx, XP_S16 playerNum, + XP_Rect* rInner, XP_Rect* rOuter, + DrawScoreInfo* dsi ); +static void palm_bnw_draw_trayBegin( DrawCtx* p_dctx, XP_Rect* rect, + XP_U16 owner, XP_Bool hasfocus ); +static void palm_bnw_draw_trayFinished( DrawCtx* p_dctx ); +static void palm_clr_draw_clearRect( DrawCtx* p_dctx, XP_Rect* rectP ); +static void palm_draw_drawMiniWindow( DrawCtx* p_dctx, unsigned char* text, + XP_Rect* rect, void** closureP ); + +static void +eraseRect( /* PalmDrawCtx* dctx, */XP_Rect* rect ) +{ + WinEraseRectangle( (const RectangleType*)rect, 0 ); +} /* eraseRect */ + +static void +insetRect( XP_Rect* rect, XP_S16 by ) +{ + rect->left += by; + rect->top += by; + by *= 2; + rect->width -= by; + rect->height -= by; +} /* insetRect */ + +static void +drawBitmapAt( DrawCtx* p_dctx, Int16 resID, Int16 x, Int16 y ) +{ + MemHandle handle; + handle = DmGetResource( bitmapRsc, resID ); + XP_ASSERT( handle != NULL ); + + if ( handle != NULL ) { + WinDrawBitmap( (BitmapPtr)MemHandleLock(handle), x, y ); + XP_ASSERT( MemHandleLockCount(handle ) == 1 ); + MemHandleUnlock( handle ); + DmReleaseResource( handle ); + } +} /* drawBitmapAt */ + +static void +bitmapInRect( PalmDrawCtx* dctx, Int16 resID, XP_Rect* rectP ) +{ + XP_U16 left = rectP->left; + XP_U16 top = rectP->top; + if ( dctx->globals->gState.showGrid ) { + ++left; + ++top; + } + drawBitmapAt( (DrawCtx*)dctx, resID, left, top ); +} /* bitmapInRect */ + +static void +palm_common_draw_boardBegin( DrawCtx* p_dctx, XP_Rect* rect, XP_Bool hasfocus ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + PalmAppGlobals* globals = dctx->globals; + if ( !globals->gState.showGrid ) { + WINDRAWRECTANGLEFRAME(dctx, rectangleFrame, (RectangleType*)rect); + } +} /* palm_common_draw_boardBegin */ + +#ifdef COLOR_SUPPORT +static void +palm_clr_draw_boardBegin( DrawCtx* p_dctx, XP_Rect* rect, XP_Bool hasfocus ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + + palm_common_draw_boardBegin( p_dctx, rect, hasfocus ); + + WinPushDrawState(); + + WinSetForeColor( dctx->drawingPrefs->drawColors[COLOR_BLACK] ); + WinSetTextColor( dctx->drawingPrefs->drawColors[COLOR_BLACK] ); + WinSetBackColor( dctx->drawingPrefs->drawColors[COLOR_WHITE] ); +} /* palm_clr_draw_boardBegin */ + +static void +palm_clr_draw_boardFinished( DrawCtx* p_dctx ) +{ + WinPopDrawState(); +} /* palm_clr_draw_boardFinished */ + +static XP_Bool +palm_clr_draw_drawCell( DrawCtx* p_dctx, XP_Rect* rect, + XP_UCHAR* letters, XP_Bitmap bitmap, + XP_S16 owner, XWBonusType bonus, XP_Bool isBlank, + XP_Bool isPending, XP_Bool isStar ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + IndexedColorType color; + XP_U16 index; + + if ( isPending ) { + /* don't color background if will invert */ + index = COLOR_WHITE; + } else if ( !!bitmap || (!!letters && XP_STRLEN((const char*)letters) > 0) ) { + index = COLOR_TILE; + } else if ( bonus == BONUS_NONE ) { + index = COLOR_EMPTY; + } else { + index = COLOR_DBL_LTTR + bonus - 1; + } + color = dctx->drawingPrefs->drawColors[index]; + WinSetBackColor( color ); + + index = (owner >= 0)? COLOR_PLAYER1 + owner: COLOR_BLACK; + color = dctx->drawingPrefs->drawColors[index]; + if ( !!letters ) { + WinSetTextColor( color ); + } + + return palm_common_draw_drawCell( p_dctx, rect, letters, bitmap, owner, + bonus, isBlank, isPending, isStar ); +} /* palm_clr_draw_drawCell */ + +static void +palm_clr_draw_score_drawPlayer( DrawCtx* p_dctx, XP_S16 playerNum, + XP_Rect* rInner, XP_Rect* rOuter, + DrawScoreInfo* dsi ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + IndexedColorType newColor; + IndexedColorType oldTextColor; + IndexedColorType oldFGColor; + + newColor = dctx->drawingPrefs->drawColors[COLOR_PLAYER1+playerNum]; + oldTextColor = WinSetTextColor( newColor ); + oldFGColor = WinSetForeColor( newColor ); + + palm_bnw_draw_score_drawPlayer( p_dctx, playerNum, rInner, rOuter, dsi ); + WinSetTextColor( oldTextColor ); + WinSetForeColor( oldFGColor ); +} /* palm_clr_draw_score_drawPlayer */ + +#endif + +static XP_Bool +palm_common_draw_drawCell( DrawCtx* p_dctx, XP_Rect* rect, + XP_UCHAR* letters, XP_Bitmap bitmap, + XP_S16 owner, XWBonusType bonus, XP_Bool isBlank, + XP_Bool isPending, XP_Bool isStar ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + GraphicsAbility able = dctx->able; + XP_Rect localR = *rect; + XP_U16 len; + RectangleType saveClip, intersectR; + PalmAppGlobals* globals = dctx->globals; + Boolean showGrid = globals->gState.showGrid; + Boolean showBonus = bonus != BONUS_NONE; + Boolean complete; + XP_Bool empty = XP_TRUE; + + if ( showGrid ) { + ++localR.width; + ++localR.height; + } + + WinGetClip( &saveClip ); + + RctGetIntersection( &saveClip, (RectangleType*)&localR, &intersectR ); + + /* If there's no rect left inside the clip rgn, exit. But if the rect's + only partial go ahead and draw, but still return false indicating that + we'd like to be allowed to draw again. This is necessary when a cell + needs to be redrawn for two reasons, e.g. because its bottom half + overlaps a form that's gone away (and that supplied the clip region) + and because its top is covered by the trading miniwindow that's also + going away. Under no circumstances draw outside the clip region or + risk overdrawing menus, other forms, etc. */ + + if ( intersectR.extent.x == 0 || intersectR.extent.y == 0 ) { + return XP_FALSE; + } else if ( intersectR.extent.x < localR.width || + intersectR.extent.y < localR.height ) { + complete = XP_FALSE; + } else { + complete = XP_TRUE; + } + WinSetClip( (RectangleType*)&intersectR ); + + if ( showGrid ) { + insetRect( &localR, 1 ); + } + + eraseRect( &localR ); + + if ( !!letters ) { + len = XP_STRLEN( (const char*)letters ); + if ( len > 0 ) { + XP_S16 strWidth = FntCharsWidth( (const char*)letters, len ); + XP_U16 x = localR.left + ((localR.width-strWidth) / 2); + if ( len == 1 ) { + ++x; + } + WINDRAWCHARS( dctx, (const char*)letters, len, x, localR.top-1 ); + + showBonus = XP_FALSE; + empty = XP_FALSE; + } + } else if ( !!bitmap ) { + XP_Bool doColor = (able == COLOR) && (owner >= 0); + /* cheating again; this belongs in a palm_clr method. But the + special bitmaps are rare enough that we shouldn't change the palm + draw state every time. */ + if ( doColor ) { + WinSetForeColor( + dctx->drawingPrefs->drawColors[COLOR_PLAYER1+owner] ); + } + WinDrawBitmap( (BitmapPtr)bitmap, localR.left+1, localR.top+1 ); + if ( doColor ) { + WinSetForeColor( dctx->drawingPrefs->drawColors[COLOR_BLACK] ); + } + showBonus = doColor; /* skip bonus in B&W case; can't draw both! */ + empty = XP_FALSE; + } + + if ( isStar ) { + bitmapInRect( dctx, STAR_BMP_RES_ID, rect ); + } else if ( showBonus && (able == ONEBIT) ) { + /* this is my one refusal to totally factor bandw and color + code */ + WinSetPattern( (const CustomPatternType*) + &dctx->u.bnw.valuePatterns[bonus-1] ); + WINFILLRECTANGLE( dctx, (RectangleType*)&localR, 0 ); + } else if ( !showBonus && empty && !showGrid ) { + /* should this be in the v-table so I don't have to test each + time? */ + if ( globals->romVersion >= 35 ) { + WinDrawPixel( localR.left + ((PALM_BOARD_SCALE-1)/2), + localR.top + ((PALM_BOARD_SCALE-1)/2) ); + } else { + RectangleType r; + r.topLeft.x = localR.left + ((PALM_BOARD_SCALE-1)/2); + r.topLeft.y = localR.top + ((PALM_BOARD_SCALE-1)/2); + r.extent.x = r.extent.y = 1; + WinDrawRectangle( &r, 0 ); + } + } + + if ( isPending ) { + XP_ASSERT( !!bitmap || + (!!letters && XP_STRLEN((const char*)letters)>0)); + WinInvertRectangle( (RectangleType*)&localR, 0 ); + } + + if ( showGrid ) { + WINDRAWRECTANGLEFRAME(dctx, rectangleFrame, (RectangleType*)&localR); + } + + if ( isBlank ) { + WinEraseRectangleFrame( roundFrame, (RectangleType*)&localR ); + } + + WinSetClip( &saveClip ); + return complete; +} /* palm_common_draw_drawCell */ + +static void +palm_draw_invertCell( DrawCtx* p_dctx, XP_Rect* rect ) +{ + XP_Rect localR = *rect; + /* insetRect( &localR, 3 ); */ + localR.top += 3; + localR.left += 3; + localR.width -= 5; + localR.height -= 5; + WinInvertRectangle( (RectangleType*)&localR, 0 ); +} /* palm_draw_invertCell */ + +static void +palm_clr_draw_trayBegin( DrawCtx* p_dctx, XP_Rect* rect, + XP_U16 owner, XP_Bool hasfocus ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + + dctx->trayOwner = owner; + + WinPushDrawState(); + WinSetBackColor( dctx->drawingPrefs->drawColors[COLOR_TILE] ); + WinSetTextColor( dctx->drawingPrefs->drawColors[COLOR_PLAYER1+owner] ); + WinSetForeColor( dctx->drawingPrefs->drawColors[COLOR_PLAYER1+owner] ); + + palm_bnw_draw_trayBegin( p_dctx, rect, owner, hasfocus ); +} /* palm_clr_draw_trayBegin */ + +static void +palm_bnw_draw_trayBegin( DrawCtx* p_dctx, XP_Rect* rect, XP_U16 owner, + XP_Bool hasfocus ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + + WinGetClip( &dctx->oldTrayClip ); + WinSetClip( (RectangleType*)rect ); +} /* palm_draw_trayBegin */ + +static void +palm_clr_draw_trayFinished( DrawCtx* p_dctx ) +{ + palm_bnw_draw_trayFinished( p_dctx ); + WinPopDrawState(); +} /* palm_clr_draw_trayFinished */ + +static void +palm_bnw_draw_trayFinished( DrawCtx* p_dctx ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + WinSetClip( &dctx->oldTrayClip ); +} /* palm_draw_trayFinished */ + +static void +palm_draw_drawTile( DrawCtx* p_dctx, XP_Rect* rect, + XP_UCHAR* letters, XP_Bitmap bitmap, + XP_S16 val, XP_Bool highlighted ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + char valBuf[3]; + XP_Rect localR = *rect; + XP_U16 len, width; + WinHandle numberWin = dctx->numberWin; + + draw_clearRect( p_dctx, &localR ); + + localR.width -= 3; + localR.height -= 3; + localR.top += 2; + localR.left += 2; + + /* this will fill it with the tile background color */ + WinEraseRectangle( (const RectangleType*)&localR, 0 ); + + if ( !!letters ) { + if ( *letters != LETTER_NONE ) { /* blank */ + FontID curFont = FntGetFont(); + FntSetFont( largeFont ); +#ifdef TILE_SUBSCRIPT + WINDRAWCHARS( dctx, (const char*)letters, 1, localR.left+2, + rect->top+2 ); +#else + WINDRAWCHARS( dctx, letters, 1, localR.left+2, rect->top+7 ); +#endif + FntSetFont( curFont ); + } + } else if ( !!bitmap ) { + WinDrawBitmap( (BitmapPtr)bitmap, localR.left+2, localR.top+2 ); + } + + if ( val >= 0 ) { + RectangleType numRect = {{0,0}, {NUMRECT_WIDTH, NUMRECT_HEIGHT}}; + WinHandle curWind; + + (void)StrPrintF( valBuf, "%d", val ); + len = XP_STRLEN((const char*)valBuf); + + XP_ASSERT( !!numberWin ); + curWind = WinSetDrawWindow( numberWin ); + + WinEraseRectangle( &numRect, 0 ); + + WinDrawChars( valBuf, len, 0, 0 ); + width = FntCharsWidth( valBuf, len ); + + (void)WinSetDrawWindow( curWind ); + numRect.extent.x = width; +#ifdef TILE_SUBSCRIPT + WinCopyRectangle( numberWin, 0, &numRect, + localR.left + localR.width - width, + localR.top + localR.height - 10, + winOverlay ); +#else + WinCopyRectangle( numberWin, 0, &numRect, + localR.left + localR.width - width, + localR.top, + winOverlay ); +#endif + } + + WINDRAWRECTANGLEFRAME( dctx, rectangleFrame, (RectangleType*)&localR ); + if ( highlighted ) { + insetRect( &localR, 1 ); + WINDRAWRECTANGLEFRAME(dctx, rectangleFrame, (RectangleType*)&localR ); + } +} /* palm_draw_drawTile */ + +static void +palm_draw_drawTileBack( DrawCtx* p_dctx, XP_Rect* rect ) +{ + palm_draw_drawTile( p_dctx, rect, (unsigned char*)"?", (XP_Bitmap)NULL, + -1, XP_FALSE ); +} /* palm_draw_drawTileBack */ + +static void +palm_draw_drawTrayDivider( DrawCtx* p_dctx, XP_Rect* rect, XP_Bool selected ) +{ + XP_Rect lRect = *rect; + + draw_clearRect( p_dctx, &lRect ); + + ++lRect.left; + --lRect.width; + + if ( selected ) { + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + short pattern[] = { 0xFF00, 0xFF00, 0xFF00, 0xFF00 }; + + WinSetPattern( (const CustomPatternType*)&pattern ); + WINFILLRECTANGLE( dctx, (RectangleType*)&lRect, 0 ); + + } else { + WinDrawRectangle( (RectangleType*)&lRect, 0 ); + } +} /* palm_draw_drawTrayDivider */ + +static void +palm_bnw_draw_clearRect( DrawCtx* p_dctx, XP_Rect* rectP ) +{ + eraseRect( rectP ); +} /* palm_draw_clearRect */ + +static void +palm_clr_draw_clearRect( DrawCtx* p_dctx, XP_Rect* rectP ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + WinPushDrawState(); + WinSetBackColor( dctx->drawingPrefs->drawColors[COLOR_WHITE] ); + eraseRect( rectP ); + WinPopDrawState(); +} /* palm_clr_draw_clearRect */ + +static void +palm_clr_draw_drawMiniWindow( DrawCtx* p_dctx, unsigned char* text, + XP_Rect* rect, void** closureP ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + WinSetBackColor( dctx->drawingPrefs->drawColors[COLOR_WHITE] ); + WinSetTextColor( dctx->drawingPrefs->drawColors[COLOR_BLACK] ); + + palm_draw_drawMiniWindow( p_dctx, text, rect, closureP ); +} /* palm_clr_draw_drawMiniWindow */ + +static void +palm_draw_drawBoardArrow( DrawCtx* p_dctx, XP_Rect* rectP, + XWBonusType cursorBonus, XP_Bool vertical ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + + Int16 resID = vertical? DOWN_ARROW_RESID:RIGHT_ARROW_RESID; + + bitmapInRect( dctx, resID, rectP ); +} /* palm_draw_drawBoardArrow */ + +#ifdef COLOR_SUPPORT +static void +palm_clr_draw_drawBoardArrow( DrawCtx* p_dctx, XP_Rect* rectP, + XWBonusType cursorBonus, XP_Bool vertical ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + XP_U16 index; + + if ( cursorBonus == BONUS_NONE ) { + index = COLOR_EMPTY; + } else { + index = COLOR_DBL_LTTR + cursorBonus - 1; + } + + WinSetBackColor( dctx->drawingPrefs->drawColors[index] ); + palm_draw_drawBoardArrow( p_dctx, rectP, cursorBonus, vertical ); +} /* palm_clr_draw_drawBoardArrow */ +#endif + +static void +palm_draw_scoreBegin( DrawCtx* p_dctx, XP_Rect* rect, XP_U16 numPlayers, + XP_Bool hasfocus ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + + WinGetClip( &dctx->oldScoreClip ); + WinSetClip( (RectangleType*)rect ); + + eraseRect( rect ); +} /* palm_draw_scoreBegin */ + +/* rectContainsRect: Dup of something in board.c. They could share if I were + * willing to link from here out. + */ +static XP_Bool +rectContainsRect( XP_Rect* rect1, XP_Rect* rect2 ) +{ + return ( rect1->top <= rect2->top + && rect1->left <= rect2->left + && rect1->top + rect1->height >= rect2->top + rect2->height + && rect1->left + rect1->width >= rect2->left + rect2->width ); +} /* rectContainsRect */ + +static XP_Bool +palm_draw_vertScrollBoard( DrawCtx* p_dctx, XP_Rect* rect, XP_S16 dist ) +{ + RectangleType clip; + XP_Bool canDoIt; + + /* if the clip rect doesn't contain the scroll rect we can't do anything + right now: WinScrollRectangle won't do its job. */ + WinGetClip( &clip ); + canDoIt = rectContainsRect( (XP_Rect*)&clip, rect ); + + if ( canDoIt ) { + RectangleType vacated; + WinDirectionType dir; + + if ( dist >= 0 ) { + dir = winUp; + } else { + dir = winDown; + dist = -dist; + } + + WinScrollRectangle( (RectangleType*)rect, dir, dist, &vacated ); + *rect = *(XP_Rect*)&vacated; + } + return canDoIt; +} /* palm_draw_vertScrollBoard */ + +/* Given some text, determine its bounds and draw it if requested, else + * return the bounds. If the width of the string exceeds that of the rect in + * which it can be fit, split it at ':'. + */ +static void +palmMeasureDrawText( PalmDrawCtx* dctx, XP_Rect* bounds, XP_UCHAR* txt, + XP_Bool vertical, XP_Bool isTurn, XP_UCHAR skipChar, + XP_Bool draw ) +{ + XP_U16 len = XP_STRLEN( (const char*)txt ); + XP_U16 widths[2]; + XP_U16 maxWidth, height; + XP_U16 nLines = 1; + XP_U16 secondLen = 0; + XP_UCHAR* second = NULL; + + widths[0] = FntCharsWidth( (const char*)txt, len ) + 1; + + if ( widths[0] > bounds->width ) { + XP_UCHAR ch[2]; + ch[0] = skipChar; + ch[1] = '\0'; + + XP_ASSERT( skipChar ); + second = (XP_UCHAR*)StrStr( (const char*)txt, (const char*)ch ); + XP_ASSERT( !!second ); + ++second; /* colon's on the first line */ + secondLen = XP_STRLEN( (const char*)second ); + + len -= secondLen; + + if ( skipChar ) { + --len; + } + + widths[0] = FntCharsWidth( (const char*)txt, len ); + widths[1] = FntCharsWidth( (const char*)second, secondLen ); + maxWidth = XP_MAX( widths[0], widths[1] ); + ++nLines; + } else { + maxWidth = widths[0]; + } + + height = nLines * FONT_HEIGHT + ( LINE_SPACING * (nLines-1) ); + if ( vertical && isTurn ) { + height += 5; /* for the horizontal bars */ + } + + XP_ASSERT( height <= bounds->height ); + XP_ASSERT( maxWidth <= bounds->width ); + + if ( draw ) { + XP_U16 x, y; + + /* Center what we'll be drawing by advancing the appropriate + coordinate to eat up half the extra space */ + x = bounds->left + 1;// + (bounds->width - widths[0]) / 2; + y = bounds->top; + if ( vertical && isTurn ) { + y += 1; + } else { + y -= 2; + } + + WINDRAWCHARS( dctx, (const char*)txt, len, x, y ); + if ( nLines == 2 ) { + XP_ASSERT( vertical ); + y += FONT_HEIGHT + LINE_SPACING; + x = bounds->left + ((bounds->width - widths[1]) / 2); + WINDRAWCHARS( dctx, (const char*)second, secondLen, x, y ); + } + + } else { + /* return the measurements */ + bounds->width = maxWidth; + bounds->height = height; + } + +} /* palmMeasureDrawText */ + +static void +palmFormatRemText( PalmDrawCtx* dctx, XP_UCHAR* buf, XP_S16 nTilesLeft ) +{ + XP_UCHAR* remStr = (*dctx->getResStrFunc)(dctx->globals, STR_REMTILES); + if ( nTilesLeft < 0 ) { + nTilesLeft = 0; + } + (void)StrPrintF( (char*)buf, (const char*)remStr, nTilesLeft ); +} /* palmFormatRemText */ + +static void +palm_draw_measureRemText( DrawCtx* p_dctx, XP_Rect* rect, XP_S16 nTilesLeft, + XP_U16* widthP, XP_U16* heightP ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + PalmAppGlobals* globals = dctx->globals; + XP_UCHAR buf[10]; + XP_Rect localRect; + XP_Bool isVertical = !globals->gState.showGrid; + + palmFormatRemText( dctx, buf, nTilesLeft ); + + localRect = *rect; + palmMeasureDrawText( dctx, &localRect, buf, isVertical, XP_FALSE, + ':', XP_FALSE ); + + *widthP = localRect.width; + *heightP = localRect.height; +} /* palm_draw_measureRemText */ + +static void +palm_draw_drawRemText( DrawCtx* p_dctx, XP_Rect* rInner, XP_Rect* rOuter, + XP_S16 nTilesLeft ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + PalmAppGlobals* globals = dctx->globals; + XP_UCHAR buf[10]; + + XP_Bool isVertical = !globals->gState.showGrid; + + palmFormatRemText( dctx, buf, nTilesLeft ); + + palmMeasureDrawText( dctx, rInner, buf, isVertical, XP_FALSE, + ':', XP_TRUE ); +} /* palm_draw_drawRemText */ + +/* Measure text that'll be drawn for player. If vertical, it'll often get + * split into two lines, esp after the number of remaining tiles appears. + */ +static void +palmFormatScore( char* buf, DrawScoreInfo* dsi, XP_Bool vertical ) +{ + char borders[] = {'•', '\0'}; + char remBuf[10]; + char* remPart = remBuf; + + if ( vertical || !dsi->isTurn ) { + borders[0] = '\0'; + } + + if ( dsi->nTilesLeft >= 0 ) { + StrPrintF( remPart, SCORE_SEPSTR "%d", dsi->nTilesLeft ); + } else { + *remPart = '\0'; + } + + (void)StrPrintF( buf, "%s%d%s%s", borders, dsi->score, remPart, borders ); +} /* palmFormatScore */ + +static void +palm_draw_measureScoreText( DrawCtx* p_dctx, XP_Rect* rect, DrawScoreInfo* dsi, + XP_U16* widthP, XP_U16* heightP ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + PalmAppGlobals* globals = dctx->globals; + char buf[20]; + /* FontID oldFont = 0; */ + XP_Bool vertical = !globals->gState.showGrid; + XP_Rect localRect = *rect; + + /* if ( !vertical && dsi->selected ) { */ + /* oldFont = FntGetFont(); */ + /* FntSetFont( boldFont ); */ + /* } */ + + palmFormatScore( buf, dsi, vertical ); + palmMeasureDrawText( dctx, &localRect, (XP_UCHAR*)buf, dsi->isTurn, vertical, + SCORE_SEP, XP_FALSE ); + + *widthP = localRect.width; + *heightP = localRect.height; + + /* result = widthAndText( buf, score, nTilesInTray, isTurn, */ + /* !globals->gState.showGrid, &ignore, ignoreLines ); */ + + /* if ( !vertical && dsi->selected ) { */ + /* FntSetFont( oldFont ); */ + /* } */ +} /* palm_draw_measureScoreText */ + +static void +palm_bnw_draw_score_drawPlayer( DrawCtx* p_dctx, XP_S16 playerNum, + XP_Rect* rInner, XP_Rect* rOuter, + DrawScoreInfo* dsi ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + PalmAppGlobals* globals = dctx->globals; + XP_UCHAR scoreBuf[20]; + XP_Bool vertical = !globals->gState.showGrid; + + palmFormatScore( (char*)scoreBuf, dsi, vertical ); + palmMeasureDrawText( dctx, rInner, (XP_UCHAR*)scoreBuf, vertical, dsi->isTurn, + SCORE_SEP, XP_TRUE ); + + if ( vertical && dsi->isTurn ) { + RectangleType r = *(RectangleType*)rInner; + XP_U16 x, y; + + x = r.topLeft.x; + y = r.topLeft.y + 1; + + WinDrawLine( x, y, x + r.extent.x - 1, y); + y += r.extent.y - 3; + WinDrawLine( x, y, x + r.extent.x - 1, y ); + } + + if ( dsi->selected ) { + WinInvertRectangle( (RectangleType*)rInner, 0 ); + /* if ( !vertical ) { */ + /* FntSetFont( oldFont ); */ + /* } */ + } +} /* palm_bnw_draw_score_drawPlayer */ + +#define PENDING_DIGITS 3 +static void +palm_draw_score_pendingScore( DrawCtx* p_dctx, XP_Rect* rect, XP_S16 score, + XP_U16 playerNum ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + XP_UCHAR* str; + char buf[PENDING_DIGITS+1] = "000"; + RectangleType oldClip, newClip; + XP_U16 x = rect->left + 1; + + str = (*dctx->getResStrFunc)( dctx->globals, STR_PTS ); + + WinGetClip( &oldClip ); + RctGetIntersection( &oldClip, (RectangleType*)rect, &newClip ); + if ( newClip.extent.y > 0 ) { + WinSetClip( &newClip ); + eraseRect( rect ); + + if ( score >= 0 ) { + XP_UCHAR tbuf[4]; + UInt16 len; + if ( score <= 999 ) { + StrPrintF( (char*)tbuf, "%d", score ); + } else { + StrCopy( (char*)tbuf, "wow" ); /* thanks, Marcus :-) */ + } + len = XP_STRLEN( (const char*)tbuf ); + XP_MEMCPY( &buf[PENDING_DIGITS-len], tbuf, len ); + } else { + StrCopy( buf, "???" ); + } + + if ( rect->height >= PALM_TRAY_SCALEH ) { + WINDRAWCHARS( dctx, (const char*)str, + XP_STRLEN((const char*)str), x, rect->top ); + } + WINDRAWCHARS( dctx, buf, PENDING_DIGITS, x, + rect->top + (rect->height/2) - 1 ); + WinSetClip( &oldClip ); + } +} /* palm_draw_score_pendingScore */ + +static void +palm_draw_scoreFinished( DrawCtx* p_dctx ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + WinSetClip( &dctx->oldScoreClip ); +} /* palm_draw_scoreFinished */ + +static void +palmFormatTimerText( XP_UCHAR* buf, XP_S16 secondsLeft ) +{ + XP_U16 minutes, seconds; + XP_UCHAR secBuf[6]; + + if ( secondsLeft < 0 ) { + *buf++ = '-'; + secondsLeft *= -1; + } + minutes = secondsLeft / 60; + seconds = secondsLeft % 60; + + /* StrPrintF can't do 0-padding; do it manually. Otherwise 5:03 will + come out 5:3 */ + StrPrintF( (char*)secBuf, "0%d", seconds ); + StrPrintF( (char*)buf, "%d:%s", minutes, + secBuf[2] == '\0'? secBuf:&secBuf[1] ); +} /* palmFormatTimerText */ + +static void +palm_draw_drawTimer( DrawCtx* p_dctx, XP_Rect* rInner, XP_Rect* rOuter, + XP_U16 player, XP_S16 secondsLeft ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + XP_UCHAR buf[10]; + XP_Rect localR = *rInner; + RectangleType saveClip; + XP_U16 len, width; + + palmFormatTimerText( buf, secondsLeft ); + len = XP_STRLEN( (const char*)buf ); + + width = FntCharsWidth( (const char*)buf, len ); + + eraseRect( &localR ); + + if ( width < localR.width ) { + localR.left += localR.width - width; + localR.width = width; + } + + localR.top -= 2; + localR.height += 1; + + WinGetClip( &saveClip ); + WinSetClip( (RectangleType*)&localR ); + WINDRAWCHARS( dctx, (const char*)buf, len, localR.left, localR.top ); + WinSetClip( &saveClip ); +} /* palm_draw_drawTimer */ + +#define MINI_LINE_HT 12 +#define MINI_V_PADDING 6 +#define MINI_H_PADDING 8 + +static XP_UCHAR* +palm_draw_getMiniWText( DrawCtx* p_dctx, XWMiniTextType textHint ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + XP_UCHAR* str; + XP_U16 strID = 0; /* make compiler happy */ + + switch( textHint ) { + case BONUS_DOUBLE_LETTER: + strID = STR_DOUBLE_LETTER; break; + case BONUS_DOUBLE_WORD: + strID = STR_DOUBLE_WORD; break; + case BONUS_TRIPLE_LETTER: + strID = STR_TRIPLE_LETTER; break; + case BONUS_TRIPLE_WORD: + strID = STR_TRIPLE_WORD; break; + case INTRADE_MW_TEXT: + strID = STR_TRADING_REMINDER; break; + default: + XP_ASSERT( XP_FALSE ); + } + + str = (*dctx->getResStrFunc)( dctx->globals, strID ); + + return str; +} /* palm_draw_getMiniWText */ + +#define VALUE_HINT_RECT_HEIGHT 16 +static void +palm_draw_measureMiniWText( DrawCtx* p_dctx, unsigned char* str, + XP_U16* widthP, XP_U16* heightP ) +{ + /* PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; */ + FntSetFont( stdFont ); + /* 8 stolen from xwords.c*/ + *widthP = FntCharsWidth( (const char*)str, + XP_STRLEN((const char*)str) ) + 8; + *heightP = VALUE_HINT_RECT_HEIGHT; +} /* palm_draw_measureMiniWText */ + +typedef struct MiniWinData { + WinHandle bitsBehind; + XP_S16 miniX; + XP_S16 miniY; +} MiniWinData; + +static void +palm_draw_drawMiniWindow( DrawCtx* p_dctx, unsigned char* text, + XP_Rect* rect, void** closureP ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + RectangleType localR = *(RectangleType*)rect; + XP_U16 ignoreErr; + XP_Bool hasClosure = !!closureP; + MiniWinData* data = (MiniWinData*)(hasClosure? *closureP: NULL); + + if ( hasClosure ) { + if ( !data ) { + data = XP_MALLOC( dctx->mpool, sizeof(MiniWinData) ); + data->bitsBehind = WinSaveBits( &localR, &ignoreErr ); + data->miniX = localR.topLeft.x; + data->miniY = localR.topLeft.y; + *closureP = data; + } else { + XP_ASSERT( data->miniX == localR.topLeft.x ); + XP_ASSERT( data->miniY == localR.topLeft.y ); + } + } + + WinEraseRectangle( &localR, 0 ); + localR.topLeft.x++; + localR.topLeft.y++; + localR.extent.x -= 3; + localR.extent.y -= 3; + WINDRAWRECTANGLEFRAME( dctx, popupFrame, &localR ); + WINDRAWCHARS( dctx, (const char*)text, XP_STRLEN((const char*)text), + localR.topLeft.x+2, localR.topLeft.y+1 ); +} /* palm_draw_drawMiniWindow */ + +static void +palm_draw_eraseMiniWindow( DrawCtx* p_dctx, XP_Rect* rect, XP_Bool lastTime, + void** closure, XP_Bool* invalUnder ) +{ + MiniWinData* data = *closure; +#ifdef DEBUG + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; +#endif + + if ( !!closure && !!*closure ) { + /* this DELETES data->bitsBehind */ + WinRestoreBits( data->bitsBehind, data->miniX, data->miniY ); + XP_FREE( dctx->mpool, data ); + *closure = NULL; + } +} /* palm_draw_eraseMiniWindow */ + +static void +draw_doNothing( DrawCtx* dctx, ... ) +{ +} /* draw_doNothing */ + +DrawCtx* +palm_drawctxt_make( MPFORMAL GraphicsAbility able, + PalmAppGlobals* globals, + GetResStringFunc getRSF, DrawingPrefs* drawprefs ) +{ + PalmDrawCtx* dctx; + XP_U16 i; + Err ignore; + + dctx = XP_MALLOC( mpool, sizeof(PalmDrawCtx) ); + XP_MEMSET( dctx, 0, sizeof(PalmDrawCtx) ); + + MPASSIGN(dctx->mpool, mpool); + + dctx->able = able; + dctx->globals = globals; + dctx->getResStrFunc = getRSF; + dctx->drawingPrefs = drawprefs; + + dctx->vtable = XP_MALLOC( mpool, + sizeof(*(((PalmDrawCtx*)dctx)->vtable)) ); + for ( i = 0; i < sizeof(*dctx->vtable)/4; ++i ) { + ((void**)(dctx->vtable))[i] = draw_doNothing; + } + + /* To keep the number of entry points this file has to 1, all + functions but the initter called from here must go through a + vtable call. so....*/ + dctx->drawBitmapFunc = drawBitmapAt; + +#ifdef DIRECT_PALMOS_CALLS + /* It'd probably be best to do this with metadata, with an array + associating the offset of the procptr within the draw ctxt with the + trap number so I could just loop through setting addresses. */ + + dctx->winDrawRectangleFrameTrap = + SysGetTrapAddress(sysTrapWinDrawRectangleFrame); + dctx->winFillRectangleTrap = + SysGetTrapAddress(sysTrapWinFillRectangle); + dctx->winDrawCharsTrap = + SysGetTrapAddress(sysTrapWinDrawChars); +#endif + + SET_VTABLE_ENTRY( dctx->vtable, draw_invertCell, palm ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTile, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTileBack, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTrayDivider, palm ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawBoardArrow, palm ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_scoreBegin, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_vertScrollBoard, palm ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_measureRemText, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawRemText, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureScoreText, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_pendingScore, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_scoreFinished, palm ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTimer, palm ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_getMiniWText, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureMiniWText, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawMiniWindow, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_eraseMiniWindow, palm ); + + if ( able == COLOR ) { +#ifdef COLOR_SUPPORT + SET_VTABLE_ENTRY( dctx->vtable, draw_boardBegin, palm_clr ); + SET_VTABLE_ENTRY( dctx->vtable, draw_boardFinished, palm_clr ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawCell, palm_clr ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_drawPlayer, palm_clr ); + SET_VTABLE_ENTRY( dctx->vtable, draw_trayBegin, palm_clr ); + SET_VTABLE_ENTRY( dctx->vtable, draw_trayFinished, palm_clr ); + SET_VTABLE_ENTRY( dctx->vtable, draw_clearRect, palm_clr ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawMiniWindow, palm_clr ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawBoardArrow, palm_clr ); +#else + XP_ASSERT(0); +#endif + } else { + SET_VTABLE_ENTRY( dctx->vtable, draw_boardBegin, palm_common ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawCell, palm_common ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_drawPlayer, palm_bnw ); + SET_VTABLE_ENTRY( dctx->vtable, draw_trayBegin, palm_bnw ); + SET_VTABLE_ENTRY( dctx->vtable, draw_trayFinished, palm_bnw ); + SET_VTABLE_ENTRY( dctx->vtable, draw_clearRect, palm_bnw ); + } + + dctx->numberWin = WinCreateOffscreenWindow( NUMRECT_WIDTH, NUMRECT_HEIGHT, + screenFormat, &ignore ); + + if ( able == COLOR ) { + } else { + short patBits[] = { 0x8844, 0x2211, 0x8844, 0x2211, + 0xaa55, 0xaa55, 0xaa55, 0xaa55, + 0xCC66, 0x3399, 0xCC66, 0x3399, + 0xCCCC, 0x3333, 0xCCCC, 0x3333 }; + XP_MEMCPY( &dctx->u.bnw.valuePatterns[0], patBits, sizeof(patBits) ); + } + + return (DrawCtx*)dctx; +} /* palm_drawctxt_make */ + +void +palm_drawctxt_destroy( DrawCtx* p_dctx ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + + XP_ASSERT( !!dctx->numberWin ); + WinDeleteWindow( dctx->numberWin, false ); + + XP_FREE( dctx->mpool, p_dctx->vtable ); + XP_FREE( dctx->mpool, dctx ); +} /* palm_drawctxt_destroy */ diff --git a/xwords4/palm/palmir.c b/xwords4/palm/palmir.c new file mode 100644 index 000000000..2733a20fd --- /dev/null +++ b/xwords4/palm/palmir.c @@ -0,0 +1,1048 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef IR_SUPPORT + +#include + +#include "palmir.h" + +#include "callback.h" +#include "xwords4defines.h" +#include "comms.h" +#include "memstream.h" +#include "palmutil.h" +#include "LocalizedStrIncludes.h" +#ifdef BEYOND_IR +# include +#endif + +# ifndef IR_EXCHMGR + +#define IR_NO_TIMEOUT 0xFFFFFFFF +#if 1 +# define IR_TIMEOUT (15*60) /* 10 seconds during debugging */ +#else +# define IR_TIMEOUT (150*60) /* 100 seconds during debugging */ +#endif + +#define RESET_TIMER(g) (g)->ir_timeout = TimGetTicks() + IR_TIMEOUT + +struct MyIrPacket { + IrPacket packet; + struct MyIrPacket* next; + Boolean in_use; +}; + +/***************************************************************************/ +static void receiveData( PalmAppGlobals* globals, UInt8* buf, UInt16 len ); +#define addFreeQueue( g,p ) (--(g)->irSendQueueLen) +static void addToSendQueue( PalmAppGlobals* globals, MyIrPacket* packet ); +static void clearSendQueue( PalmAppGlobals* globals ); +static MyIrPacket* getFreeSendPacket( PalmAppGlobals* globals ); +#ifdef DEBUG +static void printStateTransition( PalmAppGlobals* globals ); +static void assert_state1( PalmAppGlobals* globals, short line, + IR_STATE assertState ); +static void assert_state2( PalmAppGlobals* globals, short line, + IR_STATE assertState1,IR_STATE assertState2 ); +#else +#define printStateTransition( globals ) +#define assert_state1( globals, line, assertState ) +#define assert_state2( globals, line, assertState1, assertState2 ) +#endif +/***************************************************************************/ + +static Boolean +storeDiscovery( PalmAppGlobals* globals, IrDeviceList* deviceList, + IrConnect* con ) +{ + short i; + + XP_ASSERT( deviceList->nItems <= 1 ); + + for ( i = 0; i < deviceList->nItems; ++i ) { + globals->irDev = deviceList->dev[i].hDevice; + XP_ASSERT( &globals->irC_out.irCon == con ); + con->rLsap = deviceList->dev[i].xid[0]; +#ifdef DEBUG + globals->save_rLsap = con->rLsap; +#endif + } + return deviceList->nItems > 0; +} /* storeDiscovery */ + +void +ir_callback_out( IrConnect* con, IrCallBackParms* parms ) +{ + PalmAppGlobals* globals; + IrStatus status; + + CALLBACK_PROLOGUE(); + + globals = ((MyIrConnect*)con)->globals; + + switch ( parms->event ) { + + case LEVENT_LAP_DISCON_IND: /* IrLAP connection has gone down, or + IrConnectIrLap failed */ + + XP_STATUSF( "LAP_DISCON_IND received" ); + if ( !!globals->rcvBuffSize ) { + /* we've received a buffer and now need to do something with it */ + assert_state1( globals, __LINE__, IR_STATE_NONE ); + /* globals->ir_state = IR_STATE_MESSAGE_RECD; */ + } else { + globals->ir_state = IR_STATE_NONE; /* was IR_STATE_DOLAP */ + } + break; + + case LEVENT_LM_CON_IND: + XP_ASSERT( !globals->conPacketInUse ); + XP_STATUSF( "responding to incomming connection" ); + assert_state2( globals, __LINE__, IR_STATE_CONN_RECD, + IR_STATE_LAP_RCV ); + + globals->conPacket.buff = globals->conBuff; + globals->conPacket.len = sizeof(globals->conBuff); + XP_ASSERT( globals->conPacket.len <= IR_MAX_CON_PACKET ); + status = IrConnectRsp( globals->irLibRefNum, con, + &globals->conPacket, 0 /* credit: ignored */); + + XP_ASSERT( status == IR_STATUS_PENDING ); + if ( status == IR_STATUS_PENDING ) { + globals->conPacketInUse = true; + globals->ir_state = IR_STATE_CONN_INCOMMING; + } else { + XP_STATUSF( "IrConnectRsp call failed with %d", status ); + } + break; + + case LEVENT_LM_DISCON_IND: + XP_WARNF( "LEVENT_LM_DISCON_IND received; failure???" ); + break; + + case LEVENT_PACKET_HANDLED: { + IrPacket* packetPtr = parms->packet; + + packetPtr->buff = NULL; + packetPtr->len = 0; + + if ( packetPtr == &globals->conPacket ) { + + /* don't change the state here. This is just telling us the + packet's free */ + /* assert_state2( globals, __LINE__, IR_STATE_LMPREQ_SENT, */ + /* IR_STATE_LMPRCV_REQ_SENT ); */ + XP_ASSERT( globals->conPacketInUse ); + /* not true if an incomming connection */ + /* XP_ASSERT( !!getSendQueueHead(globals) ); */ + globals->conPacketInUse = false; + } else { + assert_state1( globals, __LINE__, IR_STATE_SEND_DONE ); + ((MyIrPacket*)packetPtr)->in_use = false; + addFreeQueue( globals, packetPtr ); + /* if we've received notification that a send was successful, and + if we've no further sends to do, shut down the connection.*/ + if ( !!getSendQueueHead(globals) ) { /* another message? */ + globals->ir_state = IR_STATE_LMP_ESTAB; + } else { + globals->ir_state = IR_STATE_CAN_DISCONNECT; + XP_STATUSF( "state:IR_STATE_CAN_DISCONNECT" ); + } + } + } + break; + + case LEVENT_DATA_IND: + assert_state1( globals, __LINE__, IR_STATE_CONN_INCOMMING ); + receiveData( globals, parms->rxBuff, parms->rxLen ); + globals->ir_state = IR_STATE_MESSAGE_RECD; + break; + + case LEVENT_STATUS_IND: + break; + case LEVENT_DISCOVERY_CNF: /* both sides must do this */ + assert_state1( globals, __LINE__, IR_STATE_DISCOVERY_SENT ); + if ( storeDiscovery( globals, parms->deviceList, con ) ) { + if ( !!getSendQueueHead( globals ) ) { + globals->ir_state = IR_STATE_DOLAP; + } else { + globals->ir_state = IR_STATE_DISCOVERY_COMPLETE; + } + } else { /* discovery failed */ + globals->ir_state = IR_STATE_REDO_DISCOVERY; + } + break; + case LEVENT_LAP_CON_CNF: + XP_STATUSF( "irlap established" ); + assert_state1( globals, __LINE__, IR_STATE_LAP_SENT ); + globals->ir_state = IR_STATE_LAP_ESTAB; + break; + + case LEVENT_LM_CON_CNF: /* requested IrLMP connection successful */ + + XP_STATUSF( "IrLMP connection is up" ); + assert_state1( globals, __LINE__, IR_STATE_LMPREQ_SENT ); + XP_ASSERT( ir_work_exists(globals) ); + /* I'm not sure whether we get this event before or after the one + releasing the packet passed to IrConnectReq. Both need to happen + before we can do a send -- though I guess since we're using a + different packet that's not strictly true. */ + globals->ir_state = IR_STATE_LMP_ESTAB; + break; + + case LEVENT_LAP_CON_IND: + /* indicates that the other side's opened up a connection */ + XP_STATUSF( "other side opened up a LAP connection" ); + globals->ir_state = IR_STATE_LAP_RCV; + break; + + /* case LEVENT_TEST_CNF: */ + /* XP_ASSERT( globals->packet_in_use ); */ + /* globals->packet_in_use = false; */ + /* XP_DEBUGF( "LEVENT_TEST_CNF: returned %d", parms->status ); */ + /* break; */ + + /* case LEVENT_TEST_IND: */ + /* XP_DEBUGF( "LEVENT_TEST_IND received" ); */ + /* receiveData( globals, parms->rxBuff, parms->rxLen ); */ + /* break; */ + + default: + } + + CALLBACK_EPILOGUE(); +} /* ir_callback_out */ +#endif + +#ifdef BEYOND_IR +static void checkAndDeliver( PalmAppGlobals* globals, XWStreamCtxt* instream, + CommsAddrRec* addr ); +#endif + +void +palm_send_on_close( XWStreamCtxt* stream, void* closure ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)closure; + CommsConnType conType = comms_getConType( globals->game.comms ); + + XP_ASSERT( !!globals->game.comms ); + comms_send( globals->game.comms, conType, stream ); +} /* palm_send_on_close */ + +# ifndef IR_EXCHMGR +Boolean +ir_do_work( PalmAppGlobals* globals ) +{ + IrStatus status; + Boolean result = false; + XWStreamCtxt* instream; + MyIrPacket* packetPtr; + + printStateTransition( globals ); + + if ( !!getSendQueueHead(globals) /* we're here to send something */ + && globals->ir_state > IR_STATE_NOTHING_TO_DO + && globals->ir_timeout < TimGetTicks() ) { + Boolean retry; + + retry = palmaskFromStrId( globals, STR_RESEND_IR, -1, STR_NO ); + + /* why did I do this? */ + if ( IrIsIrLapConnected( globals->irLibRefNum ) ) { + status = IrDisconnectIrLap( globals->irLibRefNum ); + XP_ASSERT( status == IR_STATUS_PENDING ); + } + + if ( retry ) { + RESET_TIMER(globals); + } else { + clearSendQueue( globals ); + } + globals->ir_state = retry? IR_STATE_DO_DISCOVERY : IR_STATE_NONE; + + } else { + + switch( globals->ir_state ) { + + case IR_STATE_NONE: /* do we need this state anymore? */ + XP_ASSERT( !!getSendQueueHead( globals ) ); + if ( IrIsIrLapConnected(globals->irLibRefNum) ) { + globals->ir_state = IR_STATE_LAP_ESTAB; + } else { + globals->ir_state = IR_STATE_DO_DISCOVERY; + } + break; + + case IR_STATE_DO_DISCOVERY: + /* might a well set it up here */ + globals->conPacket.buff = globals->conBuff; + globals->conPacket.len = IR_MAX_CON_PACKET; + + RESET_TIMER(globals); + case IR_STATE_REDO_DISCOVERY: + if ( IrIsIrLapConnected(globals->irLibRefNum) ) { + globals->ir_state = IR_STATE_LAP_ESTAB; + } else { + status = IrDiscoverReq( globals->irLibRefNum, + &globals->irC_out.irCon ); + + if (status == IR_STATUS_SUCCESS || + status == IR_STATUS_PENDING) { + globals->ir_state = IR_STATE_DISCOVERY_SENT; + } else { + XP_STATUSF( "discov failed: %d", status ); + globals->ir_state = IR_STATE_REDO_DISCOVERY; + } + } + break; + + case IR_STATE_DISCOVERY_SENT: + break; + + case IR_STATE_DOLAP: + /* better be a message to send! */ + XP_ASSERT( !!getSendQueueHead( globals ) ); + XP_STATUSF( "calling IrConnectIrLap" ); + status = IrConnectIrLap( globals->irLibRefNum, globals->irDev ); + if (status != IR_STATUS_SUCCESS && status != IR_STATUS_PENDING) { + XP_STATUSF( "IrConnectIrLap call failed: %d", status ); + } else { + globals->ir_state = IR_STATE_LAP_SENT; + } + break; + + case IR_STATE_LAP_SENT: +/* XP_DEBUGF( "state still IR_STATE_LAP_SENT" ); */ + break; + + case IR_STATE_LAP_ESTAB: + if ( !globals->conPacketInUse ) { + /* not true if from other side */ +/* XP_ASSERT( !!globals->conPacket.buff ); */ + XP_ASSERT( IrIsIrLapConnected(globals->irLibRefNum) ); + /* not sure what this means anyway.... */ +/* XP_ASSERT(globals->irC_out.irCon.rLsap== */ +/* globals->save_rLsap); */ + status = IrConnectReq( globals->irLibRefNum, + &globals->irC_out.irCon, + &globals->conPacket, 0 ); + if ( status == IR_STATUS_PENDING ) { + + if ( globals->ir_state == IR_STATE_LAP_ESTAB ) { + globals->ir_state = IR_STATE_LMPREQ_SENT; + } else { + globals->ir_state = IR_STATE_LMPRCV_REQ_SENT; + } + + globals->conPacketInUse = true; + XP_STATUSF( "IrConnectReq succeeded" ); + + } else { + XP_STATUSF( "IrConnectReq returned %d; will try again", + status ); + } + } else { + XP_WARNF( "Can't call IrConnectReq b/c packet_in_use" ); + } + break; + + case IR_STATE_LMP_ESTAB: + packetPtr = getSendQueueHead( globals ); + XP_ASSERT( !!packetPtr ); + if ( !!packetPtr ) { + XP_ASSERT( !!packetPtr->packet.buff ); + XP_ASSERT( packetPtr->packet.len > 0 ); + status = IrDataReq( globals->irLibRefNum, + &globals->irC_out.irCon, + &packetPtr->packet ); + if ( status == IR_STATUS_PENDING ) { + packetPtr->in_use = true; + globals->ir_state = IR_STATE_SEND_DONE; + } else { + XP_WARNF( "IrDataReq returned %d", status ); + } + } + break; + + case IR_STATE_MESSAGE_RECD: + XP_ASSERT( !!globals->rcvBuffSize ); + + instream = mem_stream_make( MEMPOOL globals, 0, NULL ); + stream_open( instream ); + stream_putBytes( instream, globals->rcvBuff, + globals->rcvBuffSize ); + globals->rcvBuffSize = 0; + + if ( comms_checkIncommingStream( globals->game.comms, instream, + &instream, 1 ) ) { /* FIXME!!! */ + result = server_receiveMessage( globals->game.server, + instream ); + } + stream_destroy( instream ); + + palm_util_requestTime( &globals->util ); + + globals->ir_state = IR_STATE_CAN_DISCONNECT; + break; /* comment this out? */ + +#if 1 + case IR_STATE_CAN_DISCONNECT: + /* send the disconnect message so receiver will know the + message is finished */ + + /* if the other side disconnects, it'll already be down?? */ + if ( IrIsIrLapConnected( globals->irLibRefNum ) ) { + status = IrDisconnectIrLap( globals->irLibRefNum ); + XP_ASSERT( status == IR_STATUS_PENDING ); + } + globals->ir_state = IR_STATE_NONE; + + break; +#endif + default: + break; + } + } + return result; +} /* ir_do_work */ + +void +ir_show_status( PalmAppGlobals* globals ) +{ + if ( !!globals->mainForm ) { + XP_U16 x, y; + WinHandle oldHand = WinSetDrawWindow( (WinHandle)globals->mainForm ); + + x = globals->isLefty?1:154; + y = 160 - TRAY_HEIGHT - IR_STATUS_HEIGHT; + + if ( globals->ir_state > IR_STATE_NOTHING_TO_DO ) { + char buf[2] = { 0, 0 }; + if ( globals->ir_state <= 9 ) { + buf[0] = '0' + globals->ir_state; + } else { + buf[0] = 'A' + globals->ir_state-10; + } + WinDrawChars( buf, 1, x, y ); + } else { + RectangleType r = { {x, y}, {8, 10} }; + WinEraseRectangle( &r, 0); + } + + WinSetDrawWindow( oldHand ); + } +} /* ir_show_status */ + +/* Free any memory associated with message queues, etc. + */ +void +ir_cleanup( PalmAppGlobals* globals ) +{ + MyIrPacket* packet; + MyIrPacket* next; + + for ( packet = globals->packetListHead; !!packet; packet = next ) { + next = packet->next; + XP_FREE( globals->mpool, packet ); + } + globals->packetListHead = NULL; +} /* ir_cleanup */ +#endif + +#ifdef BEYOND_IR +# define NETLIB_TIMEOUT 2 + +XP_Bool +openNetLibIfNot( PalmAppGlobals* globals ) +{ + Err err; + XP_Bool done = globals->nlStuff.netLibRef != 0; + + if ( !done ) { + UInt16 libRef; + err = SysLibFind( "Net.lib", &libRef); + if ( err == errNone ) { + UInt16 ifErrs; + err = NetLibOpen( libRef, &ifErrs ); + if ( err == errNone ) { + globals->nlStuff.netLibRef = libRef; + done = XP_TRUE; + XP_LOGF( "successful netlib open" ); + } else { + XP_LOGF( "NetLibOpen failed: err=%d; ifErrs=%d", + err, ifErrs ); + } + } + } + + return done; +} /* openNetLibIfNot */ + +static void +bindSocket( PalmAppGlobals* globals, XP_U16 listenPort ) +{ + NetSocketAddrINType socketAddr; + UInt16 res; + Err err; + + XP_MEMSET( &socketAddr, 0, sizeof(socketAddr) ); + socketAddr.family = netSocketAddrINET; + socketAddr.port = listenPort; + + res = NetLibSocketBind( globals->nlStuff.netLibRef, + globals->nlStuff.socketRef, + (NetSocketAddrType*)&socketAddr, + sizeof(socketAddr), -1, &err ); + + if ( res == 0 && err == errNone ) { + globals->nlStuff.listenPort = listenPort; + XP_LOGF( "successful socket bind on port %d", + socketAddr.port ); + } else { + XP_LOGF( "bind failed: %d", err ); + } +} /* bindSocket */ + +static XP_Bool +openSocketIfNot( PalmAppGlobals* globals ) +{ + XP_Bool open = globals->nlStuff.socketRef != 0; + XP_Bool newlyOpen = XP_FALSE; + + if ( !open ) { + Err err; + NetSocketRef socketRef; + + XP_ASSERT( globals->nlStuff.netLibRef != 0 ); + + socketRef = NetLibSocketOpen( globals->nlStuff.netLibRef, + netSocketAddrINET, + netSocketTypeDatagram, + 0, /* protocol (ignored) */ + NETLIB_TIMEOUT, + &err ); + if ( err == errNone ) { + globals->nlStuff.socketRef = socketRef; + newlyOpen = open = XP_TRUE; + + XP_LOGF( "Opened socket" ); + } else { + XP_LOGF( "Failed to open socket" ); + } + + if ( globals->nlStuff.listenPort != 0 ) { + bindSocket( globals, globals->nlStuff.listenPort ); + } + } + return open; +} /* openSocketIfNot */ + +void +palm_bind_socket( PalmAppGlobals* globals, XP_U16 newPort ) +{ + if ( openNetLibIfNot( globals ) && openSocketIfNot(globals) ) { + bindSocket( globals, newPort ); + } +} /* palm_bind_socket */ + +void +palm_ip_close( PalmAppGlobals* globals ) +{ + if ( globals->nlStuff.netLibRef != 0 ) { + + if ( globals->nlStuff.socketRef != 0 ) { + Err ignore; + NetLibSocketClose( globals->nlStuff.netLibRef, + globals->nlStuff.socketRef, 0, &ignore); + globals->nlStuff.socketRef = 0; + } + + NetLibClose(globals->nlStuff.netLibRef, 0); + + globals->nlStuff.netLibRef = 0; + } +} /* palm_ip_close */ + +#define MAX_PACKET_SIZE 256 + +static XP_S16 +palm_ip_send( XP_U8* buf, XP_U16 len, CommsAddrRec* addr, + PalmAppGlobals* globals ) +{ + XP_S16 nSent = 0; + + XP_LOGF( "palm_ip_send: len=%d", len ); + XP_ASSERT( len < MAX_PACKET_SIZE ); + + if ( openNetLibIfNot( globals ) && openSocketIfNot( globals ) ) { + Err err; + NetSocketAddrINType outSocket; + + XP_MEMSET( &outSocket, 0, sizeof(outSocket) ); + outSocket.family = netSocketAddrINET; + if ( 0 && !!addr ) { + outSocket.port = addr->u.ip.port; + outSocket.addr = addr->u.ip.ipAddr; + } else { + /* first time, get address from user settings */ + CommsAddrRec addr; + XP_U16 ignore; + comms_getAddr( globals->game.comms, &addr, &ignore ); + + outSocket.port = addr.u.ip.port; + outSocket.addr = addr.u.ip.ipAddr; + } + XP_LOGF( "sending to host 0x%lx, port %d", + outSocket.addr, outSocket.port ); + + nSent = NetLibSend( globals->nlStuff.netLibRef, + globals->nlStuff.socketRef, + (void*) buf, len, 0, /* flags */ + &outSocket, sizeof(outSocket), + NETLIB_TIMEOUT, + &err ); + + if ( err != errNone || nSent != len ) { + XP_LOGF( "failed to send %d bytes; sent %d; err=%d", + len, nSent, err ); + nSent = 0; + } else { + XP_LOGF( "sent %d bytes", nSent ); + } + } + return nSent; +} /* palm_ip_send */ +#endif + +#ifdef BEYOND_IR +static XWStreamCtxt* +packetToStream( PalmAppGlobals* globals, CommsAddrRec* retAddr ) +{ + XP_U8 buf[MAX_PACKET_SIZE]; + Err err; + XP_S16 nRead; + NetSocketAddrINType fromAddr; + void* fromAddrP; + UInt16 fromLen; + XWStreamCtxt* result; + + if ( globals->romVersion >= 50 ) { + fromAddrP = NULL; + fromLen = 0; + } else { + fromAddrP = (void*)&fromAddr; + fromLen = sizeof( fromAddr ); + } + + XP_LOGF( "calling NetLibReceive" ); + nRead = NetLibReceive( globals->nlStuff.netLibRef, + globals->nlStuff.socketRef, + buf, MAX_PACKET_SIZE, + 0, /* flags */ + fromAddrP, &fromLen, + 0, &err ); + XP_LOGF( "back from NetLibReceive" ); + + if ( (nRead > 0) && (err == errNone) ) { + XP_LOGF( "read data: %d bytes", nRead ); + + result = mem_stream_make( MEMPOOL globals->vtMgr, + globals, 0, NULL); + stream_open( result ); + stream_putBytes( result, buf, nRead ); + + /* harvest the return address */ + retAddr->conType = COMMS_CONN_IP; + retAddr->u.ip.ipAddr = fromAddr.addr; + retAddr->u.ip.port = fromAddr.port; + XP_LOGF( "received packet from port %d on host 0x%lx", + fromAddr.port, fromAddr.addr ); + } else { + result = NULL; + XP_LOGF( "didn't read data: %d bytes or err=%d", + nRead, err ); + } + + return result; +} /* packetToStream */ + +void +checkHandleNetEvents( PalmAppGlobals* globals ) +{ + if ( openNetLibIfNot( globals ) && openSocketIfNot( globals ) ) { + NetFDSetType readFDs; + NetFDSetType writeFDs; + NetFDSetType ignoreFDs; + XP_S16 nSockets; + Err err; + + netFDZero( &readFDs ); + netFDZero( &writeFDs ); + netFDZero( &ignoreFDs ); + + netFDSet( globals->nlStuff.socketRef, &readFDs ); + nSockets = NetLibSelect( globals->nlStuff.netLibRef, + netFDSetSize, /* built-in constant PENDING */ + &readFDs, &writeFDs, &ignoreFDs, + 1, /* timeout */ + &err ); + + if ( nSockets > 0 && err == errNone ) { + if ( netFDIsSet( globals->nlStuff.socketRef, &readFDs ) ) { + + XWStreamCtxt* instream; + CommsAddrRec raddr; + + XP_MEMSET( &raddr, 0, sizeof(raddr) ); + + instream = packetToStream( globals, &raddr ); + if ( !!instream ) { + checkAndDeliver( globals, instream, &raddr ); + } + } + } + } +} /* checkHandleNetEvents */ +#endif + +/* We're passed an address as we've previously defined it and a buffer + * containing a message to send. Prepend any palm/ir specific headers to the + * message, save the buffer somewhere, and fire up the state machine that + * will eventually get it sent to the address. + * + * Note that the caller will queue the message for possible resend, but + * won't automatically schedule that resend whatever results we return. + * + * NOTE also that simply stuffing the buf ptr into the packet won't work + * if there's any ir-specific packet header I need to prepend to what's + * outgoing. + */ +static XP_S16 +palm_ir_send( XP_U8* buf, XP_U16 len, PalmAppGlobals* globals ) +{ +#ifdef IR_EXCHMGR + UInt32 sent = 0; + Err err; + ExgSocketType exgSocket; + XP_MEMSET( &exgSocket, 0, sizeof(exgSocket) ); + exgSocket.description = "Crosswords data"; + exgSocket.length = len; + exgSocket.target = APPID; + + if ( globals->romVersion >= 40 ) { +#ifdef BEYOND_IR + if ( globals->exgLibraryRef == 0 ) { + exgSocket.name = exgSendBeamPrefix; + } else { + exgSocket.libraryRef = globals->exgLibraryRef; + } +#else + exgSocket.name = exgBeamPrefix; +#endif + } + + err = ExgPut( &exgSocket ); + while ( !err && sent < len ) { + sent += ExgSend( &exgSocket, buf+sent, len-sent, &err ); + XP_ASSERT( sent < 0x7FFF ); + } + err = ExgDisconnect( &exgSocket, err ); + +#ifdef BEYOND_IR + /* no need to check for ROM version here */ + if ( globals->exgLibraryRef == 0 ) { + globals->exgLibraryRef = exgSocket.libraryRef; + } +#endif + + return err==0? sent : 0; +#else + MyIrPacket* packet = getFreeSendPacket( globals ); + + packet->packet.buff = buf; + packet->packet.len = len; + XP_ASSERT( !packet->in_use ); + + addToSendQueue( globals, packet ); + + return len; +#endif +} /* palm_ir_send */ + +XP_S16 +palm_send( XP_U8* buf, XP_U16 len, CommsAddrRec* addr, void* closure ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)closure; +#ifdef BEYOND_IR + + XP_S16 result = 0; + switch( comms_getConType(globals->game.comms) ) { + case COMMS_CONN_IR: + result = palm_ir_send( buf, len, globals ); + break; + case COMMS_CONN_IP: + result = palm_ip_send( buf, len, addr, globals ); + break; + default: + XP_ASSERT(0); + } + return result; +#else + return palm_ir_send( buf, len, globals ); +#endif +} /* palm_send */ + +static void +checkAndDeliver( PalmAppGlobals* globals, XWStreamCtxt* instream, + CommsAddrRec* addr ) +{ + if ( comms_checkIncommingStream( globals->game.comms, + instream, addr ) ) { + globals->msgReceivedDraw = + server_receiveMessage( globals->game.server, instream ); + globals->msgReceivedDraw = true; + } + stream_destroy( instream ); + palm_util_requestTime( &globals->util ); +} /* checkAndDeliver */ + +#ifdef IR_EXCHMGR +void +palm_ir_receiveMove( PalmAppGlobals* globals, ExgSocketPtr socket ) +{ + UInt32 nBytesReceived = -1; + Err err; + + err = ExgAccept( socket ); + if ( err == 0 ) { + XWStreamCtxt* instream; + + instream = mem_stream_make(MEMPOOL globals->vtMgr, globals, 0, NULL); + stream_open( instream ); + + for ( ; ; ) { + UInt8 buf[128]; + nBytesReceived = ExgReceive( socket, buf, sizeof(buf), &err ); + if ( nBytesReceived == 0 || err != 0 ) { + break; + } + + stream_putBytes( instream, buf, nBytesReceived ); + } + (void)ExgDisconnect( socket, err ); + + if ( nBytesReceived == 0 ) { /* successful loop exit */ + + CommsAddrRec rec; + rec.conType = COMMS_CONN_IR; + checkAndDeliver( globals, instream, &rec ); + } + } +} /* palm_ir_receiveMove */ +#else +static void +receiveData( PalmAppGlobals* globals, UInt8* buf, UInt16 len ) +{ + XP_ASSERT( !!len ); + XP_ASSERT( !globals->conPacketInUse ); + + XP_ASSERT( !globals->rcvBuffSize ); /* else messages coming in several + parts; old code handled this */ + XP_MEMCPY( globals->rcvBuff, buf, len ); + globals->rcvBuffSize = len; + + globals->ir_timeout = IR_NO_TIMEOUT; +} /* receiveData */ + +/* return the first packet ready to be sent, i.e. whose buf ptr is non-null + * and whose in_use flag is not set. To make searching faster, keep track of + * whether there are actually any on the queue. */ +MyIrPacket* +getSendQueueHead( PalmAppGlobals* globals ) +{ + MyIrPacket* packet = NULL; + + if ( globals->irSendQueueLen > 0 ) { + + packet = (MyIrPacket*)globals->packetListHead; + for ( ; !!packet; packet = packet->next ) { + if ( !!packet->packet.buff && !packet->in_use ) { + break; + } + } + } + return packet; +} /* getSendQueueHead */ +#endif + +/* The ptr's already on the list, but we need to move it to the end, behind + * anything that's already there waiting to be sent. That's because messages + * need to get sent in order. + */ +#ifndef IR_EXCHMGR +static void +addToSendQueue( PalmAppGlobals* globals, MyIrPacket* packet ) +{ + MyIrPacket* end = globals->packetListHead; + + packet->next = NULL; + + if ( !end ) { + globals->packetListHead = packet; + } else { + + for ( ; !!end->next; end = end->next ) { + + } + end->next = packet; + } + ++globals->irSendQueueLen; + RESET_TIMER(globals); +} /* addToSendQueue */ +#endif /* ifndef IR_EXCHMGR */ + +#ifndef IR_EXCHMGR +static void +clearSendQueue( PalmAppGlobals* globals ) +{ + MyIrPacket* packet; + MyIrPacket* next; + + for ( packet = globals->packetListHead; !!packet; packet = next ) { + next = packet->next; + if ( packet->packet.buff != NULL ) { + packet->packet.buff = NULL; + packet->packet.len = 0; + --globals->irSendQueueLen; + } + } + + XP_ASSERT( globals->irSendQueueLen == 0 ) ; +} /* clearSendQueue */ + +static MyIrPacket* +getFreeSendPacket( PalmAppGlobals* globals ) +{ + MyIrPacket* packet = globals->packetListHead; + MyIrPacket* prev; + + for ( prev = NULL; !!packet; prev = packet, packet = packet->next ) { + if ( !packet->packet.buff ) { + XP_ASSERT( packet->packet.len == 0 ); + + /* cut out of list before returning */ + if ( !!prev ) { + prev->next = packet->next; + } else { + XP_ASSERT( globals->packetListHead == packet ); + globals->packetListHead = NULL; + } + + return packet; + } + } + packet = XP_MALLOC( globals->mpool, sizeof(*packet) ); + XP_MEMSET( packet, 0, sizeof(*packet) ); + + return packet; +} /* getFreeSendPacket */ +#endif + +#ifdef DEBUG +#ifndef IR_EXCHMGR +static char* +getStateName( IR_STATE state ) +{ + switch ( state ) { + + case IR_STATE_NONE: return "NONE"; + case IR_STATE_DISCOVERY_COMPLETE: return "DISCOVERY_COMPLETE"; + + + case IR_STATE_NOTHING_TO_DO: return "NOTHING_TO_DO"; + case IR_STATE_NO_OTHER_FOUND: return "NO_OTHER_FOUND"; + case IR_STATE_DO_DISCOVERY: return "DO_DISCOVERY"; + case IR_STATE_REDO_DISCOVERY: return "REDO_DISCOVERY"; + case IR_STATE_DISCOVERY_SENT: return "DISCOVERY_SENT"; + case IR_STATE_DOLAP: return "DOLAP"; + + + case IR_STATE_LAP_SENT: return "LAP_SENT"; + case IR_STATE_LAP_ESTAB: return "LAP_ESTAB"; + + + case IR_STATE_LMPREQ_SENT: return "LMPREQ_SENT"; + case IR_STATE_LMP_ESTAB: return "LMP_ESTAB"; + + + case IR_STATE_SEND_DONE: return "SEND_DONE"; + case IR_STATE_CAN_DISCONNECT: return "CAN_DISCONNECT"; + + case IR_STATE_CONN_RECD: return "CONN_RECD"; + case IR_STATE_LAP_RCV: return "LAP_RCV"; + case IR_STATE_LMPRCV_REQ_SENT: return "LMPRCV_REQ_SENT"; + case IR_STATE_CONN_INCOMMING: return "CONN_INCOMMING"; + case IR_STATE_MESSAGE_RECD: return "MESSAGE_RECD"; + + default: + return "unknown"; + } + +} /* getStateName */ + +static void +assert_state1( PalmAppGlobals* globals, short line, IR_STATE assertState ) +{ + if ( globals->ir_state != assertState ) { + XP_WARNF( "Line %d: sought %s; found %s", line, + getStateName(assertState), getStateName(globals->ir_state)); + } +} /* assert_state1 */ + +static void +assert_state2( PalmAppGlobals* globals, short line, IR_STATE assertState1, + IR_STATE assertState2 ) +{ + if ( globals->ir_state != assertState1 + && globals->ir_state != assertState2){ + XP_WARNF( "Line %d: sought %s or %s; found %s", line, + getStateName(assertState1), getStateName(assertState2), + getStateName( globals->ir_state ) ); + } + +} /* assertState2 */ + +static void +printStateTransition( PalmAppGlobals* globals ) +{ + if ( globals->ir_state != globals->ir_state_prev ) { + char* oldState = getStateName( globals->ir_state_prev ); + char* newState = getStateName( globals->ir_state ); + + XP_STATUSF( "ir_st:%s->%s", oldState, newState ); + + globals->ir_state_prev = globals->ir_state; + } +} /* printStateTransition */ +# endif /* IR_EXCHMGR */ +#endif /* DEBUG */ + +#endif /* IR_SUPPORT */ diff --git a/xwords4/palm/palmir.h b/xwords4/palm/palmir.h new file mode 100644 index 000000000..630b4d5b3 --- /dev/null +++ b/xwords4/palm/palmir.h @@ -0,0 +1,108 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 1999 - 2001 by Eric House (fixin@peak.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. + */ + +#ifndef _PALMIR_H_ +#define _PALMIR_H_ + +#include "comms.h" +#include "palmmain.h" + +/* IR strategy. We'll attempt to keep the IR link up, but won't bring it up + * in the first place, or fix it once it goes down, until there's something + * to send. + * + * So when there's a packet to send, if the link is up + * (state==IR_STATE_CANDODATA) we just send it. Otherwise we do what's + * needed to get to that state. What state we're actually in (in the + * difficult case where we brought the link up before but the players have + * had their machines separated and the link's died) will, I hope, be + * tracable by the messages passed to each device's callback. + * + * This isn't going to make a game between three or four devices very easy + * to manage, but perhaps that will have to wait until whatever's wrong with + * shutting down and restarting links gets fixed. + */ +enum { /* values for IR_STATE */ + IR_STATE_NONE = 0, /* nothing's up or been done */ + IR_STATE_DISCOVERY_COMPLETE = 1,/* nothing to do til there's a message to + send */ + /* Discovery */ + IR_STATE_NOTHING_TO_DO = 2, /* exists just for testing against */ + IR_STATE_NO_OTHER_FOUND = 3, /* first discovery attempt failed */ + IR_STATE_DO_DISCOVERY = 4, + IR_STATE_REDO_DISCOVERY = 5, + IR_STATE_DISCOVERY_SENT = 6, + IR_STATE_DOLAP = 7, + + /* IR Lap */ + IR_STATE_LAP_SENT = 8, + IR_STATE_LAP_ESTAB = 9,//was IR_STATE_DOLMP, + + /* LMP */ +/* IR_STATE_CONNECTREQ_SENT, */ + IR_STATE_LMPREQ_SENT = 10, /* was IR_STATE_CONNECTREQ_SENT */ + IR_STATE_LMP_ESTAB = 11, //was IR_STATE_SEND_READY, + + /* Send */ + IR_STATE_SEND_DONE = 12, + IR_STATE_CAN_DISCONNECT = 13, + + /* receive (doesn't require even discovery) */ + IR_STATE_CONN_RECD = 14, /* triggered by LEVENT_LAP_CON_IND */ + IR_STATE_LAP_RCV = 15, //was IR_STATE_DOLMP_RCV, + IR_STATE_LMPRCV_REQ_SENT = 16, + IR_STATE_CONN_INCOMMING = 17, /* triggered by LEVENT_LM_CON_IND */ + IR_STATE_MESSAGE_RECD = 18 +}; + +#define ir_work_exists(g) \ + ((g)->ir_state > IR_STATE_NOTHING_TO_DO || (getSendQueueHead(g)!=NULL)) + +MyIrPacket* getSendQueueHead( PalmAppGlobals* globals ); + +void ir_callback_out( IrConnect* con, IrCallBackParms* parms ); + +Boolean ir_do_work( PalmAppGlobals* globals ); +void ir_show_status( PalmAppGlobals* globals ); +void ir_cleanup( PalmAppGlobals* globals ); + +/* CommsCtxt send callback; a driver of sorts. Switches off global state to + decide how to actually send the message. */ +XP_S16 palm_send( XP_U8* buf, XP_U16 len, CommsAddrRec* addr, void* closure ); + +void palm_send_on_close( XWStreamCtxt* stream, void* closure ); + +void receiveMove( ExgSocketPtr cmdPBP ); +XP_Bool loadReceivedMove( PalmAppGlobals* globals, MemHandle moveData ); + +void palm_ir_receiveMove( PalmAppGlobals* globals, ExgSocketPtr socket ); + +void palm_ip_close( PalmAppGlobals* globals ); + +#ifdef BEYOND_IR +void checkHandleNetEvents( PalmAppGlobals* globals ); +XP_Bool openNetLibIfNot( PalmAppGlobals* globals ); +void palm_bind_socket( PalmAppGlobals* globals, XP_U16 newPort ); +#endif + +#ifdef XWFEATURE_STANDALONE_ONLY +# define palm_ir_send (TransportSend)NULL +#endif + +#endif diff --git a/xwords4/palm/palmmain.c b/xwords4/palm/palmmain.c new file mode 100644 index 000000000..c375406f5 --- /dev/null +++ b/xwords4/palm/palmmain.c @@ -0,0 +1,3041 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 1999 - 2002 by Eric House (fixin@peak.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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* #include */ +/* #include */ +/* #include */ +#include +#include +/* #include */ +/* #include */ +#include +#include +/* #include */ +#include + +#include "comtypes.h" +#include "comms.h" + +#include "xwords4defines.h" +#include "palmmain.h" +#include "newgame.h" +#include "dictui.h" +#include "dictlist.h" +#include "palmutil.h" +#include "palmdict.h" +#include "palmsavg.h" +#include "memstream.h" +#include "strutils.h" +#include "palmir.h" +#include "xwcolors.h" +#include "prefsdlg.h" +#include "connsdlg.h" +#include "gameutil.h" +#include "dictui.h" +#include "LocalizedStrIncludes.h" + +#include "callback.h" + +#ifdef SUPPORT_SONY_JOGDIAL +#include "SonyChars.h" +#endif + +#define TIMER_OFF 0L +#define PALM_TIMER_DELAY 25 + +#ifdef IR_SUPPORT +# ifndef IR_EXCHMGR +/* These are globals! But the ptrs must remain valid until they're removed + from the IR lib, and is seems stupid to alloc them on the heap when they + have to live in the code anyway */ +UInt8 deviceInfo[] = { IR_HINT_PDA, + IR_CHAR_ASCII, + 'X','W','O','R','D','S','4' }; +UInt8 deviceName[] = { + IAS_ATTRIB_USER_STRING, IR_CHAR_ASCII, + 7,'X','W','O','R','D','S','4'}; + +UInt8 xwordsIRResult[] = { + 0x01, /* Type for Integer is 1 */ + 0x00,0x00,0x00,0x02 /* Assumed Lsap */ +}; + +/* IrDemo attribute */ +const IrIasAttribute xwordsIRAttribs = { + (UInt8*) "IrDA:IrLMP:LsapSel",18, 0, + (UInt8*)xwordsIRResult, sizeof(xwordsIRResult) +}; + +static IrIasObject xwordsIRObject = { + (UInt8*)"Xwords4", 7, 1, + NULL + /* (IrIasAttribute*)&irdemoAttribs */ +}; +# endif +# else +# define ir_setup(g) +#endif + +/*-------------------------------- defines and consts-----------------------*/ +/* #define COLORCHANGE_THRESHOLD 300 */ + +/*-------------------------------- prototypes ------------------------------*/ +static XP_Bool startApplication( PalmAppGlobals** globalsP ); +static void eventLoop( PalmAppGlobals* globals ); +static void stopApplication( PalmAppGlobals* globals ); +static Boolean applicationHandleEvent( PalmAppGlobals* globals, + EventPtr event ); +static Boolean mainViewHandleEvent( EventPtr event ); + +static UInt16 romVersion( void ); +static Boolean handleHintRequest( PalmAppGlobals* globals ); + +/* callbacks */ +static VTableMgr* palm_util_getVTManager( XW_UtilCtxt* uc ); +static void palm_util_userError( XW_UtilCtxt* uc, UtilErrID id ); +static XP_U16 palm_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, + XWStreamCtxt* stream ); +static XWBonusType palm_util_getSquareBonus( XW_UtilCtxt* uc, + ModelCtxt* model, + XP_U16 col, XP_U16 row ); +static XP_S16 palm_util_userPickTile( XW_UtilCtxt* uc, PickInfo* pi, + XP_U16 playerNum, XP_UCHAR4* texts, + XP_U16 nTiles ); +static XP_Bool palm_util_askPassword( XW_UtilCtxt* uc, const XP_UCHAR* name, + XP_UCHAR* buf, XP_U16* len ); +static void palm_util_trayHiddenChange( XW_UtilCtxt* uc, + XW_TrayVisState newState ); +static void palm_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 oldOffset, + XP_U16 newOffset ); +static void palm_util_notifyGameOver( XW_UtilCtxt* uc ); +static XP_Bool palm_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 col, + XP_U16 row ); +static XP_Bool palm_util_engineProgressCallback( XW_UtilCtxt* uc ); +static void palm_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why ); +static XP_U32 palm_util_getCurSeconds( XW_UtilCtxt* uc ); +static DictionaryCtxt* palm_util_makeEmptyDict( XW_UtilCtxt* uc ); +#ifndef XWFEATURE_STANDALONE_ONLY +static XWStreamCtxt* palm_util_makeStreamFromAddr( XW_UtilCtxt* uc, + XP_U16 channelNo ); +#ifdef BEYOND_IR +static void palm_util_listenPortChange( XW_UtilCtxt* uc, XP_U16 newPort ); +#endif +#endif +static XP_UCHAR* palm_util_getUserString( XW_UtilCtxt* uc, XP_U16 stringCode ); +static XP_Bool palm_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, + XP_U16 turn, XP_Bool turnLost ); + + +static void userErrorFromStrId( PalmAppGlobals* globals, XP_U16 strID ); +static Boolean askFromStream( PalmAppGlobals* globals, XWStreamCtxt* stream, + XP_S16 titleID, Boolean closeAndDestroy ); +static void displayFinalScores( PalmAppGlobals* globals ); +static void updateScrollbar( PalmAppGlobals* globals, Int16 newValue ); +static void askStartNewGame( PalmAppGlobals* globals ); +static void palmSetCtrlsForTray( PalmAppGlobals* globals ); +static void drawFormButtons( PalmAppGlobals* globals ); +static MemHandle findXWPrefsRsrc( PalmAppGlobals* globals, UInt32 resType, + UInt16 resID ); +static Boolean palmask( PalmAppGlobals* globals, XP_UCHAR* str, XP_UCHAR* alt, + XP_S16 titleStrID ); +#ifdef SHOW_PROGRESS +static void palm_util_engineStarting( XW_UtilCtxt* uc ); +static void palm_util_engineStopping( XW_UtilCtxt* uc ); +#endif +static void initAndStartBoard( PalmAppGlobals* globals, XP_Bool newGame ); + +/*-------------------------------- Globals ---------------------------------*/ +/* NONE!!! */ + +/***************************************************************************** + * + ****************************************************************************/ +#define XW_MOVE_EXG_TYPE "XwMv" +UInt32 +PilotMain( UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags) +{ + PalmAppGlobals* globals; + if ( cmd == sysAppLaunchCmdNormalLaunch ) { + if ( ((launchFlags & sysAppLaunchFlagNewGlobals) != 0) + && startApplication( &globals ) ) { + XP_ASSERT( (launchFlags & sysAppLaunchFlagNewGlobals) != 0 ); + // Initialize the application's global variables and database. + eventLoop( globals ); + } + stopApplication( globals ); + +#ifdef IR_EXCHMGR + } else if ( cmd == sysAppLaunchCmdExgAskUser ) { + if ( (launchFlags & sysAppLaunchFlagSubCall) != 0 ) { + ((ExgAskParamPtr)cmdPBP)->result = exgAskOk; + } + } else if ( cmd == sysAppLaunchCmdSyncNotify ) { + if ( romVersion() >= 30 ) { + ExgRegisterData( APPID, exgRegTypeID, XW_MOVE_EXG_TYPE ); + } + } else if ( cmd == sysAppLaunchCmdExgReceiveData ) { + if ( (launchFlags & sysAppLaunchFlagSubCall) != 0 ) { + globals = getFormRefcon(); + palm_ir_receiveMove( globals, (ExgSocketPtr)cmdPBP ); + } +#endif + } + return 0; +} /* PilotMain */ + +/***************************************************************************** + * + ****************************************************************************/ +static UInt16 +romVersion( void ) +{ + UInt32 dwOSVer; + + FtrGet(sysFtrCreator, sysFtrNumROMVersion, &dwOSVer ); + /* should turn 3 and 5 into 35 */ + return (sysGetROMVerMajor(dwOSVer)*10) + sysGetROMVerMinor(dwOSVer); +} /* romVersion */ + +#ifdef COLOR_SUPPORT +/***************************************************************************** + * + ****************************************************************************/ +static UInt32 +cur_screen_depth( void ) +{ + UInt32 curDepth; + + XP_ASSERT( romVersion() >= 30 ); /* */ + + WinScreenMode( winScreenModeGet, 0, 0, &curDepth, 0 ); + return curDepth; +} /* cur_screen_depth */ +#endif + +static XP_Bool +positionBoard( PalmAppGlobals* globals ) +{ + XP_Bool erase = XP_FALSE; + XP_Bool isLefty = globals->isLefty; + XP_U16 nCols, leftEdge; + XP_U16 scale = PALM_BOARD_SCALE; + XP_U16 boardHeight, trayTop, trayScaleV; + XP_U16 boardTop, scoreTop, scoreLeft, scoreWidth, scoreHeight; + XP_U16 timerWidth, timerLeft; + XP_U16 freeSpace; + XP_Bool showGrid = globals->gState.showGrid; +#ifdef SHOW_PROGRESS + RectangleType bounds; +#endif + + if ( !showGrid ) { + --scale; + } + + XP_ASSERT( !!globals->game.model ); + nCols = model_numCols( globals->game.model ); + XP_ASSERT( nCols <= PALM_MAX_ROWS ); + + freeSpace = ((PALM_MAX_ROWS-nCols)/2) * scale; + if ( isLefty ) { + leftEdge = 160 /*hwrDisplayWidth*/ - (nCols * scale) - freeSpace - 1; + } else { + leftEdge = PALM_BOARD_LEFT_RH + freeSpace; + } + + /* position the timer. There are really four cases: width depends on + whether the grid's visible, and left edge depends on isLefty _and_ + width in the non-lefty case. */ + + if ( showGrid ) { + timerWidth = FntCharsWidth( "-00:00", 6 ); /* the ideal */ + } else { + timerWidth = PALM_GRIDLESS_SCORE_WIDTH; + } + if ( isLefty && !showGrid ) { + timerLeft = 0; + } else { + timerLeft = 160 - timerWidth; + } + board_setTimerLoc( globals->game.board, timerLeft, PALM_TIMER_TOP, + timerWidth, PALM_TIMER_HEIGHT ); + + if ( showGrid ) { + boardTop = PALM_BOARD_TOP; + scoreLeft = PALM_SCORE_LEFT; + scoreTop = PALM_SCORE_TOP; + scoreWidth = 160 - PALM_SCORE_LEFT - timerWidth; + scoreHeight = PALM_SCORE_HEIGHT; + } else { + scoreTop = PALM_GRIDLESS_SCORE_TOP; + boardTop = PALM_GRIDLESS_BOARD_TOP; + + scoreHeight = PALM_TRAY_TOP - PALM_GRIDLESS_SCORE_TOP - 2; + + scoreLeft = isLefty? 0: PALM_GRIDLESS_SCORE_LEFT; + + scoreWidth = PALM_GRIDLESS_SCORE_WIDTH; + + if ( !isLefty ) { + ++leftEdge; /* for the frame */ + } + } + + board_setPos( globals->game.board, leftEdge, + boardTop, isLefty ); + board_setScale( globals->game.board, scale, scale ); + + board_setScoreboardLoc( globals->game.board, scoreLeft, scoreTop, + scoreWidth, scoreHeight, showGrid ); + + board_setShowColors( globals->game.board, globals->gState.showColors ); + board_setYOffset( globals->game.board, 0, XP_FALSE /* why bother */ ); + + /* figure location for the tray. If possible, make it smaller than the + ideal to avoid using a scrollbar. Also, note at this point whether a + scrollbar will be required. */ + globals->needsScrollbar = false; /* default */ + boardHeight = scale * nCols; + trayTop = boardHeight + boardTop + 1; + if ( trayTop < PALM_TRAY_TOP ) { + trayTop = PALM_TRAY_TOP;/* we want it this low even if not + necessary */ + } else { + while ( trayTop > PALM_TRAY_TOP_MAX ) { + trayTop -= scale; + globals->needsScrollbar = true; + } + } + trayScaleV = 160 - trayTop; + + board_setTrayLoc( globals->game.board, + (isLefty? PALM_TRAY_LEFT_LH:PALM_TRAY_LEFT_RH), + trayTop, + PALM_TRAY_SCALEH, trayScaleV, + PALM_DIVIDER_WIDTH ); + + board_prefsChanged( globals->game.board, &globals->gState.cp ); + +#ifdef SHOW_PROGRESS + if ( showGrid ) { + getObjectBounds( XW_MAIN_SCROLLBAR_ID, &bounds ); + + bounds.topLeft.x += 1; + bounds.extent.x -= 2; + } else { + bounds.topLeft.y = PALM_TIMER_HEIGHT + 2; + bounds.topLeft.x = globals->isLefty? FLIP_BUTTON_WIDTH+3: + PALM_GRIDLESS_SCORE_LEFT+2; + + bounds.extent.x = RECOMMENDED_SBAR_WIDTH + 2; + bounds.extent.y = PALM_GRIDLESS_SCORE_TOP - bounds.topLeft.y - 2; + } + globals->progress.boundsRect = bounds; +#endif + + updateScrollbar( globals, globals->scrollValue ); /* changing visibility? */ + palmSetCtrlsForTray( globals ); + drawFormButtons( globals ); + + return erase; +} /* positionBoard */ + +static XWStreamCtxt* +gameRecordToStream( PalmAppGlobals* globals, XP_U16 index ) +{ + XWStreamCtxt* recStream = NULL; + LocalID id; + MemHandle handle; + Err err; + + id = DMFINDDATABASE( globals, CARD_0, XW_GAMES_DBNAME ); + if ( id != 0 ) { + UInt16 numRecs; + DmOpenRef dbP; + + dbP = DMOPENDATABASE( globals, CARD_0, id, dmModeReadOnly ); + numRecs = DmNumRecords( dbP ); + + if ( index < numRecs ) { + handle = DmGetRecord( dbP, index ); + + recStream = mem_stream_make( MEMPOOL globals->vtMgr, globals, + 0, NULL ); + stream_open( recStream ); + stream_putBytes( recStream, MemHandleLock(handle), + MemHandleSize(handle) ); + MemHandleUnlock(handle); + err = DmReleaseRecord( dbP, index, false ); + XP_ASSERT( err == 0 ); + } + DMCLOSEDATABASE( dbP ); + } + return recStream; +} /* gameRecordToStream */ + +static void +loadGamePrefs( /*PalmAppGlobals* globals, */XWStreamCtxt* stream ) +{ + /* Keep in sync with games saved in prev version, which foolishly saved + hintsNotAllowed separate from the current game's value for the same + thing. When the version changes get rid of this bit. */ + (void)stream_getBits( stream, 1 ); +} /* loadGamePrefs */ + +static void +saveGamePrefs( /*PalmAppGlobals* globals, */XWStreamCtxt* stream ) +{ + stream_putBits( stream, 1, 0 ); +} /* saveGamePrefs */ + +static Boolean +loadCurrentGame( PalmAppGlobals* globals, XP_U16 gIndex, + XWGame* game, CurGameInfo* ginfo ) +{ + XP_Bool hasDict; + XWStreamCtxt* recStream; + Boolean success = false; + DictionaryCtxt* dict; + + recStream = gameRecordToStream( globals, gIndex ); + + /* now read everything out of the stream */ + XP_ASSERT(!!recStream); + if ( !!recStream ) { + char ignore[MAX_GAMENAME_LENGTH]; + + /* skip the name */ + stream_getBytes( recStream, ignore, MAX_GAMENAME_LENGTH ); + + loadGamePrefs( /*globals, */recStream ); + + hasDict = stream_getU8( recStream ); + if ( hasDict ) { + XP_UCHAR* name = stringFromStream( MPPARM(globals->mpool) + recStream ); + dict = palm_dictionary_make( MPPARM(globals->mpool) name, + globals->dictList ); + success = dict != NULL; + + if ( !success ) { + /* I don't know why having this here crashes later... */ +/* userErrorFromStrId( globals, STR_DICT_NOT_FOUND ); */ + XP_FREE( globals->mpool, name ); + beep(); + } + } else { + dict = NULL; + success = true; + } + + if ( success ) { + game_makeFromStream( MEMPOOL recStream, game, ginfo, dict, + &globals->util, globals->draw, + &globals->gState.cp, palm_send, globals ); + } + + stream_destroy( recStream ); + } + + return success; +} /* loadCurrentGame */ + +static void +initUtilFuncs( PalmAppGlobals* 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 = palm_util_getVTManager; + vtable->m_util_userError = palm_util_userError; + vtable->m_util_getSquareBonus = palm_util_getSquareBonus; + vtable->m_util_userQuery = palm_util_userQuery; + vtable->m_util_userPickTile = palm_util_userPickTile; + vtable->m_util_askPassword = palm_util_askPassword; + vtable->m_util_trayHiddenChange = palm_util_trayHiddenChange; + vtable->m_util_yOffsetChange = palm_util_yOffsetChange; + vtable->m_util_notifyGameOver = palm_util_notifyGameOver; + vtable->m_util_hiliteCell = palm_util_hiliteCell; + vtable->m_util_engineProgressCallback = palm_util_engineProgressCallback; + vtable->m_util_setTimer = palm_util_setTimer; + vtable->m_util_requestTime = palm_util_requestTime; + vtable->m_util_getCurSeconds = palm_util_getCurSeconds; + vtable->m_util_makeEmptyDict = palm_util_makeEmptyDict; +#ifndef XWFEATURE_STANDALONE_ONLY + vtable->m_util_makeStreamFromAddr = palm_util_makeStreamFromAddr; +#ifdef BEYOND_IR + vtable->m_util_listenPortChange = palm_util_listenPortChange; +#endif +#endif + vtable->m_util_getUserString = palm_util_getUserString; + vtable->m_util_warnIllegalWord = palm_util_warnIllegalWord; +#ifdef SHOW_PROGRESS + vtable->m_util_engineStarting = palm_util_engineStarting; + vtable->m_util_engineStopping = palm_util_engineStopping; +#endif +} /* initUtilFuncs */ + +#ifdef IR_SUPPORT +#ifndef IR_EXCHMGR +/* Make the IR library available if possible. Or should this be put off + * until we know we're not playing stand-alone? + */ +static void +ir_setup( PalmAppGlobals* globals ) +{ + Err err; + UInt16 refNum; + + if ( globals->irLibRefNum == 0 ) { /* been inited before? */ + + err = SysLibFind( irLibName, &refNum ); + XP_ASSERT( !err ); + + if ( err == 0 ) { /* we have the library */ + err = IrOpen( refNum, irOpenOptSpeed9600 ); + XP_ASSERT( !err ); + + if ( err == 0 ) { + /* IrSetConTypeLMP( &globals->irC_in.irCon ); */ + IrSetConTypeLMP( &globals->irC_out.irCon ); + + /* globals->irC_in.globals = globals; */ + /* err = IrBind( refNum, (IrConnect*)&globals->irC_in, */ + /* ir_callback_in ); */ + /* XP_ASSERT( !err ); */ + + globals->irC_out.globals = globals; + err = IrBind( refNum, &globals->irC_out.irCon, + ir_callback_out); + XP_ASSERT( !err ); + + if ( err == 0 ) { + + XP_DEBUGF( "con->lLsap == %d", + globals->irC_out.irCon.lLsap ); + + if (IR_STATUS_SUCCESS == + IrSetDeviceInfo( refNum, deviceInfo, + sizeof(deviceInfo) ) ) { +#if 1 + + xwordsIRObject.attribs = + (IrIasAttribute*)&xwordsIRAttribs; + + IrIAS_SetDeviceName( refNum, + deviceName, + sizeof(deviceName)); + IrIAS_Add( refNum, &xwordsIRObject ); +#endif + globals->irLibRefNum = refNum; + } else { + IrUnbind( refNum, &globals->irC_out.irCon ); + IrClose( refNum ); + } + } else { + IrClose( refNum ); + } + } + } + } + + ir_cleanup( globals ); +} /* ir_setup */ +# endif +#endif + +#ifdef COLOR_SUPPORT +static void +loadColorsFromRsrc( DrawingPrefs* prefs, MemHandle colorH ) +{ + RGBColorType color; + UInt8* colorP; + short index = 0; + UInt32 count; + + count = MemHandleSize( colorH ); + XP_ASSERT( count < 0xFFFF ); + XP_ASSERT( (((XP_U16)count) % 3) == 0 ); + colorP = MemHandleLock( colorH ); + + do { + color.r = *colorP++; + color.g = *colorP++; + color.b = *colorP++; + prefs->drawColors[index++] = WinRGBToIndex( &color ); + } while ( (count -= 3) != 0 ); + + MemHandleUnlock( colorH ); +} /* loadColorsFromRsrc */ +#endif + +static void +palmInitPrefs( PalmAppGlobals* globals ) +{ + globals->gState.showGrid = true; + globals->gState.versionNum = CUR_PREFS_VERS; + globals->isFirstLaunch = true; + globals->gState.cp.showBoardArrow = XP_TRUE; + +#ifdef SHOW_PROGRESS + globals->gState.showProgress = true; +#endif +} /* palmInitPrefs */ + +static void +openXWPrefsDB( PalmAppGlobals* globals ) +{ + Err err; + + err = DmCreateDatabase( CARD_0, XW_PREFS_DBNAME, + APPID, XWORDS_PREFS_TYPE, true ); + globals->boardDBID = DmFindDatabase( CARD_0, XW_PREFS_DBNAME ); + globals->boardDBP = DmOpenDatabase( CARD_0, globals->boardDBID, + dmModeWrite ); +} /* openXWPrefsDB */ + +static XP_Bool +setupBonusPtrs( PalmAppGlobals* globals ) +{ + XP_U16 i; + for ( i = 0; i < NUM_BOARD_SIZES; ++i ) { + MemHandle hand = findXWPrefsRsrc( globals, BOARD_RES_TYPE, + BOARD_RES_ID + i ); + if ( !hand ) { + return XP_FALSE; + } + + XP_ASSERT( MemHandleLockCount(hand) == 0 ); + globals->bonusResPtr[i] = MemHandleLock( hand ); + } + return XP_TRUE; +} /* setupBonusPtrs */ + +static void +unlockBonusPtrs( PalmAppGlobals* globals ) +{ + XP_U16 i; + for ( i = 0; i < NUM_BOARD_SIZES; ++i ) { + MemPtrUnlock( (MemPtr)globals->bonusResPtr[i] ); + } +} /* unlockBonusPtrs */ + +static void +openGamesDB( PalmAppGlobals* globals ) +{ + Err err; + + err = DmCreateDatabase( CARD_0, XW_GAMES_DBNAME, + APPID, XWORDS_GAMES_TYPE, false ); + globals->gamesDBID = DmFindDatabase( CARD_0, XW_GAMES_DBNAME ); + globals->gamesDBP = DmOpenDatabase( CARD_0, globals->gamesDBID, + dmModeReadWrite ); + XP_ASSERT( !!globals->gamesDBP ); +} /* openGamesDB */ + +static MemHandle +findXWPrefsRsrc( PalmAppGlobals* globals, UInt32 resType, UInt16 resID ) +{ + Int16 index; + MemHandle handle = NULL; + Boolean beenThere = false; + + for ( ; ; ) { + XP_ASSERT( !!globals->boardDBP ); + index = DmFindResource( globals->boardDBP, resType, resID, NULL ); + + if ( index == -1 ) { /* not found */ + MemHandle builtinH; + MemHandle newH; + UInt32 size; + + if ( beenThere ) { + return NULL; + } + + builtinH = DmGetResource( resType, resID ); + size = MemHandleSize( builtinH ); + newH = DmNewResource( globals->boardDBP, resType, + resID, size ); + DmWrite( MemHandleLock( newH ), 0, MemHandleLock(builtinH), + size ); + MemHandleUnlock( newH ); + MemHandleUnlock( builtinH ); + DmReleaseResource( newH ); + DmReleaseResource( builtinH ); + + beenThere = true; + + continue; + } + + handle = DmGetResourceIndex( globals->boardDBP, index ); + break; + } + + return handle; +} /* findXWPrefsRsrc */ + +static XP_Bool +initResources( PalmAppGlobals* globals ) +{ + /* strings */ + MemHandle hand; + + XP_ASSERT( !globals->stringsResPtr ); + + hand = DmGetResource( STRL_RES_TYPE, STRL_RES_ID ); + XP_ASSERT( !!hand ); + XP_ASSERT( MemHandleLockCount(hand) == 0 ); + globals->stringsResPtr = (XP_UCHAR*)MemHandleLock( hand ); + + /* bonus square and color values. These live in a separate database, + which we create if it doesn't already exist. */ + openXWPrefsDB( globals ); + if ( !setupBonusPtrs( globals ) ) { + return XP_FALSE; + } + + openGamesDB( globals ); + + if ( globals->able == COLOR ) { + hand = findXWPrefsRsrc( globals, COLORS_RES_TYPE, COLORS_RES_ID ); + if ( !hand ) { + return XP_FALSE; + } + loadColorsFromRsrc( &globals->drawingPrefs, hand ); + DmReleaseResource( hand ); + } + + return XP_TRUE; +} /* initResources */ + +static void +freeAndUnlockPtr( MemPtr ptr ) +{ + MemHandle hand; + XP_ASSERT( !!ptr ); + hand = MemPtrRecoverHandle(ptr ); + XP_ASSERT( !!hand ); + MemHandleUnlock( hand ); + DmReleaseResource( hand ); +} /* freeAndUnlockPtr */ + +static void +uninitResources( PalmAppGlobals* globals ) +{ + XP_U16 i; + + /* strings */ + freeAndUnlockPtr( globals->stringsResPtr ); + globals->stringsResPtr = NULL; + + /* bonus square values */ + for ( i = 0; i < NUM_BOARD_SIZES; ++i ) { + freeAndUnlockPtr( globals->bonusResPtr[i] ); + } + + XP_ASSERT( !!globals->boardDBP ); + DmCloseDatabase( globals->boardDBP ); + +} /* uninitResources */ + +static XP_UCHAR* +getResString( PalmAppGlobals* globals, XP_U16 strID ) +{ + XP_ASSERT( !!globals->stringsResPtr ); + XP_ASSERT( strID < MemPtrSize( globals->stringsResPtr ) ); + XP_ASSERT( (strID == 0) || (globals->stringsResPtr[strID-1] == '\0') ); + XP_ASSERT( strID < STR_LAST_STRING ); + return &globals->stringsResPtr[strID]; +} /* getResString */ + +static Err +volChangeEventProc( SysNotifyParamType* notifyParamsP ) +{ +#ifdef REALLY_HANDLE_MEDIA + PalmAppGlobals* globals = (PalmAppGlobals*)notifyParamsP->userDataP; +#else + EventType eventToPost; +#endif + +#if 0 + if ( notifyParamsP->notifyType == sysNotifyVolumeUnmountedEvent ) { + + DictListHandleUnmount( globals->dictList ); + + } else if ( notifyParamsP->notifyType == sysNotifyVolumeMountedEvent ) { + + DictListHandleMount( &globals->dictList ); + + } else { + XP_ASSERT(0); + return errNone; + } +#endif + + /* for now, just blow outta here! Force the app to rebuild + datastructures when it's relaunched. This is a hack but I like + it. :-) */ +#ifndef REALLY_HANDLE_MEDIA + eventToPost.eType = appStopEvent; + EvtAddEventToQueue( &eventToPost ); +#endif + + return errNone; +} /* volChangeEventProc */ + +static void +doCallbackReg( PalmAppGlobals* globals, XP_Bool reg ) +{ + /* The mounted/unmounted events aren't there unless we're PalmOS version + 4.0 or greater. No need to use FtrGet to check for Notification Mgr + here, as it's useless without these. */ + if ( globals->romVersion >= 40 ) { + XP_U16 i; + UInt32 notifyTypes[] = { sysNotifyVolumeUnmountedEvent, + sysNotifyVolumeMountedEvent }; + + for ( i = 0; i < sizeof(notifyTypes) / sizeof(notifyTypes[0]); ++i ) { + UInt32 notifyType = notifyTypes[i]; + + if ( reg ) { + SysNotifyRegister( 0, 0, notifyType, volChangeEventProc, + sysNotifyNormalPriority, globals ); + } else { + SysNotifyUnregister( 0, 0, notifyType, + sysNotifyNormalPriority); + } + } + } +} /* doCallbackReg */ + +/***************************************************************************** + * + ****************************************************************************/ +static XP_Bool +startApplication( PalmAppGlobals** globalsP ) +{ + UInt16 prefSize; + Boolean prefsFound; + XWords4PreferenceType prefs; + PalmAppGlobals* globals; + Boolean leftyFlag; + MPSLOT; + +#ifdef FOR_GREMLINS + SysRandom( 1 ); +#else + SysRandom( TimGetTicks() ); /* initialize */ +#endif + +#ifdef MEM_DEBUG + mpool = mpool_make(); +#endif + + globals = (PalmAppGlobals*)XP_MALLOC( mpool, sizeof( PalmAppGlobals ) ); + *globalsP = globals; + XP_MEMSET( globals, 0, sizeof(PalmAppGlobals) ); + MPASSIGN( globals->mpool, mpool ); + + globals->vtMgr = make_vtablemgr( MPPARM_NOCOMMA(globals->mpool) ); + + globals->romVersion = romVersion(); + + leftyFlag = 0; + if ( !PrefGetAppPreferencesV10('Lfty', 1, &leftyFlag, + sizeof(leftyFlag) )) { + leftyFlag = 0; + } + globals->isLefty = leftyFlag != 0; + +#ifdef COLOR_SUPPORT + if ( (globals->romVersion >= 35) && (cur_screen_depth() >= 8) ) { + globals->able = COLOR; + } else { + globals->able = ONEBIT; + } +#else + globals->able = ONEBIT; +#endif + + if ( !initResources( globals ) ) { + return XP_FALSE; + } + + doCallbackReg( globals, XP_TRUE ); + + initUtilFuncs( globals ); + + globals->dictList = DictListMake( MPPARM_NOCOMMA(globals->mpool) ); + if ( DictListCount( globals->dictList ) == 0 ) { + userErrorFromStrId( globals, STR_NO_DICT_INSTALLED ); + return XP_FALSE; + } + + prefSize = sizeof( prefs ); + prefsFound = PrefGetAppPreferences( AppType, PrefID, &prefs, &prefSize, + true) == VERSION_NUM; + if ( prefsFound ) { + + if ( (prefSize == sizeof(prefs)) + && (prefs.versionNum == CUR_PREFS_VERS) ) { + /* all's well */ + } else if ( prefs.versionNum < CUR_PREFS_VERS ) { + /* Init the new guys. Later this may get more complex!! */ + prefs.cp.showBoardArrow = XP_TRUE; + prefs.cp.showRobotScores = XP_FALSE; + } else { + prefsFound = XP_FALSE; + } + + if ( prefsFound ) { + MemMove( &globals->gState, &prefs, sizeof(prefs) ); + } + } + + globals->draw = palm_drawctxt_make( MPPARM(globals->mpool) + globals->able, + globals, + getResString, + &globals->drawingPrefs ); + + FrmGotoForm( XW_MAIN_FORM ); + + /* do this first so players who don't exist have default names */ + gi_initPlayerInfo( MEMPOOL &globals->gameInfo, + getResString( globals, STR_DEFAULT_NAME ) ); + + if ( prefsFound && loadCurrentGame( globals, globals->gState.curGameIndex, + &globals->game, &globals->gameInfo) ) { + EventType eventToPost; + eventToPost.eType = loadGameEvent; + EvtAddEventToQueue( &eventToPost ); + } else { + DictListEntry* dlep; + + /* if we're here because dict missing, don't re-init all prefs! */ + if ( !prefsFound ) { + palmInitPrefs( globals ); + } else if ( checkUserName() ) { + /* increment count so we get a new game rather than replace + existing one. We want it still there if somebody puts the + missing dict back. */ + globals->gState.curGameIndex = countGameRecords( globals ); + } + globals->isNewGame = true; + + getNthDict( globals->dictList, 0, &dlep ); + globals->gameInfo.dictName = copyString( MPPARM(globals->mpool) + dlep->baseName ); + + game_makeNewGame( MEMPOOL &globals->game, &globals->gameInfo, + &globals->util, globals->draw, &globals->gState.cp, + palm_send, globals ); + FrmPopupForm( XW_NEWGAMES_FORM ); + } + + return XP_TRUE; +} /* startApplication */ + +/* save the stream's contents to a database. */ +static void +writeToDb( XWStreamCtxt* stream, void* closure ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)closure; + Err err; + + err = DmCreateDatabase( CARD_0, XW_GAMES_DBNAME, + APPID, XWORDS_GAMES_TYPE, false ); + + streamToGameRecord( globals, stream, globals->gState.curGameIndex ); +} /* writeToDb */ + +static void +saveOpenGame( PalmAppGlobals* globals ) +{ + if ( !!globals->game.server ) { + XWStreamCtxt* memStream; + DictionaryCtxt* dict; + XP_UCHAR* dictName; + char namebuf[MAX_GAMENAME_LENGTH]; + + board_hideTray( globals->game.board ); /* so won't be visible when + next opened */ + memStream = mem_stream_make( MEMPOOL globals->vtMgr, globals, 0, + writeToDb ); + stream_open( memStream ); + + /* write the things's name. Name is first because we want to be able + to manipulate it without knowing about the other stuff. */ + nameFromRecord( globals, globals->gState.curGameIndex, namebuf ); + stream_putBytes( memStream, namebuf, MAX_GAMENAME_LENGTH ); + + saveGamePrefs( /*globals, */memStream ); + + /* the dictionary */ + dict = model_getDictionary( globals->game.model ); + dictName = !!dict? dict_getName( dict ) : NULL; + stream_putU8( memStream, !!dictName ); + if ( !!dictName ) { + stringToStream( memStream, dictName ); + } + + game_saveToStream( &globals->game, &globals->gameInfo, memStream ); + + stream_destroy( memStream ); + } +} /* saveOpenGame */ + +/***************************************************************************** + * + ****************************************************************************/ +static void +stopApplication( PalmAppGlobals* globals ) +{ + if ( globals != NULL ) { + MPSLOT; + + saveOpenGame( globals ); + + FrmCloseAllForms(); + + uninitResources( globals ); + + /* Write the state information -- once we're ready to read it in. + But skip the save if user cancelled launching the first time. */ + if ( !globals->isFirstLaunch ) { + PrefSetAppPreferences( AppType, PrefID, VERSION_NUM, + &globals->gState, sizeof(globals->gState), + true ); + } + + if ( !!globals->draw ) { + palm_drawctxt_destroy( globals->draw ); + } + + game_dispose( &globals->game ); + gi_disposePlayerInfo( MEMPOOL &globals->gameInfo ); + +#ifdef IR_SUPPORT +# ifndef IR_EXCHMGR + /* close down the IR library if it's there */ + if ( globals->irLibRefNum != 0 ) { + if ( IrIsIrLapConnected( globals->irLibRefNum ) ) { + IrDisconnectIrLap( globals->irLibRefNum ); + } + + IrClose( globals->irLibRefNum ); + } +# endif +#ifdef BEYOND_IR + palm_ip_close( globals ); +#endif +#endif + + if ( !!globals->dictList ) { + DictListFree( MPPARM(globals->mpool) globals->dictList ); + } + + if ( !!globals->util.vtable ) { + XP_FREE( globals->mpool, globals->util.vtable ); + } + + if ( !!globals->prefsDlgState ) { + XP_FREE( globals->mpool, globals->prefsDlgState ); + } + +#if defined OWNER_HASH || defined NO_REG_REQUIRED + if ( !!globals->savedGamesState && !globals->isFirstLaunch ) { + freeSavedGamesData( MPPARM(globals->mpool) + globals->savedGamesState ); + XP_FREE( globals->mpool, globals->savedGamesState ); + } +#endif + + XP_ASSERT( !!globals->gamesDBP ); + DmCloseDatabase( globals->gamesDBP ); + + if ( !!globals->vtMgr ) { + vtmgr_destroy( MPPARM(globals->mpool) globals->vtMgr ); + } + + doCallbackReg( globals, XP_FALSE ); + + MPASSIGN( mpool, globals->mpool ); + XP_FREE( globals->mpool, globals ); + mpool_destroy( mpool ); + } +} /* stopApplication */ + +static Int32 +figureWaitTicks( PalmAppGlobals* globals ) +{ + Int32 result = evtWaitForever; + XP_U32 fireTime; + + if ( 0 ) { +#ifdef BEYOND_IR + } else if ( socketIsOpen(globals) ) { +/* we'll do our sleeping in NetLibSelect */ + result = 0; +#endif + } else if ( globals->timeRequested || globals->hintPending ) { + result = 0; +#ifdef IR_SUPPORT +# ifndef IR_EXCHMGR + } else if ( ir_work_exists(globals) ) { + /* XP_DEBUGF( "message pending" ); */ + result = 0; + } else if ( globals->ir_state == IR_STATE_MESSAGE_RECD ) { + /* XP_DEBUGF( "message recd" ); */ + result = 0; +# endif +#endif + } else if ( (fireTime = globals->penTimerFireAt) != TIMER_OFF + || (fireTime = globals->timerTimerFireAt ) != TIMER_OFF ) { + result = fireTime - TimGetTicks(); + if ( result < 0 ) { + result = 0; + } + } else { + /* leave it */ + } + /* XP_DEBUGF( "figureWaitTicks returning %d", result ); */ + return result; +} /* figureWaitTicks */ + +static XP_Bool +closeNonMainForms( PalmAppGlobals* globals ) +{ +#if 1 + return FrmGetActiveForm() == globals->mainForm; +#else + /* This doesn't work. If there's a form in front of the main form + sending it the close event closes it, but then FrmGetActiveForm() + returns null the next time called.*/ + FormPtr prevActive; + FormPtr curActive = NULL; + + for ( ; ; ) { + EventType event; + + prevActive = curActive; + curActive = FrmGetActiveForm(); + if ( prevActive == curActive ) { + return XP_FALSE; + } + + if ( curActive == globals->mainForm ) { + return XP_TRUE; + } + event.eType = frmCloseEvent; + event.data.frmClose.formID = FrmGetFormId(curActive); + FrmDispatchEvent( &event ); + } +#endif +} /* closeNonMainForms */ + +/***************************************************************************** + * + ****************************************************************************/ +static void +eventLoop( PalmAppGlobals* globals ) +{ + EventType event; + UInt16 error; + + do { +#ifdef BEYOND_IR + if ( !!globals->game.comms + && (comms_getConType(globals->game.comms) == COMMS_CONN_IP) ) { + checkHandleNetEvents( globals ); + } +#endif + + /* EvtGetEvent( &event, evtWaitForever ); */ + EvtGetEvent( &event, figureWaitTicks(globals) ); + + if ( event.eType == keyDownEvent ) { + if ( 0 ) { +#ifdef FOR_GREMLINS + } else if ( event.data.keyDown.chr == findChr ) { + continue; +#endif + } else if ( (event.data.keyDown.modifiers & commandKeyMask) != 0 + && ( (event.data.keyDown.chr == autoOffChr) + || (event.data.keyDown.chr == hardPowerChr) ) + && !!globals->game.board ) { + if ( !globals->menuIsDown /* hi Marcus :-) */ + && closeNonMainForms(globals) + && board_hideTray( globals->game.board ) ) { + board_draw( globals->game.board ); + } + } + } + + /* Give the system a chance to handle the event. */ + if ( !SysHandleEvent(&event)) { + if ( !MenuHandleEvent(0, &event, &error)) { + if ( !applicationHandleEvent( globals, &event )) { + FrmDispatchEvent(&event); + } + } + } + } while (event.eType != appStopEvent); +} /* eventLoop */ + +/********************************************************************** + * applicationHandleEvent + **********************************************************************/ +static Boolean +applicationHandleEvent( PalmAppGlobals* globals, EventPtr event ) +{ + FormPtr frm; + Int16 formId; + Boolean result = false; + + switch ( event->eType ) { + case frmLoadEvent: + /*Load the form resource specified in the event then activate the + form.*/ + formId = event->data.frmLoad.formID; + frm = FrmInitForm(formId); + FrmSetActiveForm(frm); + + switch (formId) { + case XW_MAIN_FORM: + setFormRefcon( globals ); + FrmSetEventHandler( frm, mainViewHandleEvent ); + result = true; + break; + case XW_NEWGAMES_FORM: + FrmSetEventHandler( frm, newGameHandleEvent ); + result = true; + break; + case XW_DICTINFO_FORM: + FrmSetEventHandler( frm, dictFormHandleEvent ); + result = true; + break; + case XW_PREFS_FORM: + FrmSetEventHandler( frm, PrefsFormHandleEvent ); + result = true; + break; +#ifdef BEYOND_IR + case XW_CONNS_FORM: + FrmSetEventHandler( frm, ConnsFormHandleEvent ); + result = true; + break; +#endif +#if defined OWNER_HASH || defined NO_REG_REQUIRED + case XW_SAVEDGAMES_DIALOG_ID: + FrmSetEventHandler( frm, savedGamesHandleEvent ); + result = true; + break; +#endif + } + break; + default: + break; + } + return result; +} // applicationHandleEvent + +#if 0 +static void +destroy_on_close( XWStreamCtxt* p_stream ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_stream; + /* PalmAppGlobals* globals = stream->globals; */ + MemHandle handle; + + XP_WARNF( "destroy_on_close called" ); + handle = stream->bufHandle; + MemHandleFree( handle ); + stream_destroy( p_stream ); +} /* destroy_on_close */ +#endif + +static Boolean +handleNilEvent( PalmAppGlobals* globals, EventPtr event ) +{ + Boolean handled = true; + + if ( false ) { + } else if ( globals->menuIsDown ) { + /* do nothing */ +#ifdef IR_SUPPORT +# ifndef IR_EXCHMGR + } else if ( ir_work_exists(globals) ) { + handled = ir_do_work( globals ); + ir_show_status( globals ); + /* } else if ( globals->ir_state == IR_STATE_MESSAGE_RECD ) { */ + /* handled = do_ir_work( globals ); */ +# endif +#endif + } else if ( globals->hintPending ) { + handled = handleHintRequest( globals ); + + } else if ( globals->penTimerFireAt != TIMER_OFF && + globals->penTimerFireAt <= TimGetTicks() ) { + globals->penTimerFireAt = TIMER_OFF; + board_timerFired( globals->game.board, TIMER_PENDOWN ); + + } else if ( globals->timeRequested ) { + globals->timeRequested = false; + if ( globals->msgReceivedDraw ) { + XP_ASSERT ( !!globals->game.board ); + board_draw( globals->game.board ); + globals->msgReceivedDraw = XP_FALSE; + } + handled = server_do( globals->game.server ); + + } else if ( globals->timerTimerFireAt != TIMER_OFF && + globals->timerTimerFireAt <= TimGetTicks() ) { + globals->timerTimerFireAt = TIMER_OFF; + board_timerFired( globals->game.board, TIMER_TIMERTICK ); + } else { + handled = false; + } + return handled; +} /* handleNilEvent */ + +static Boolean +handleFlip( PalmAppGlobals* globals ) +{ + XP_ASSERT( !!globals->game.board ); + return board_flip( globals->game.board ); +} /* handle_flip_button */ + +static Boolean +handleValueToggle( PalmAppGlobals* globals ) +{ + return board_toggle_showValues( globals->game.board ); +} /* handleValueToggle */ + +static Boolean +handleHideTray( PalmAppGlobals* globals ) +{ + Boolean draw; + XW_TrayVisState curState = board_getTrayVisState( globals->game.board ); + + if ( curState == TRAY_REVEALED ) { + draw = board_hideTray( globals->game.board ); + } else { + draw = board_showTray( globals->game.board ); + } + + return draw; +} /* handleHideTray */ + +static Boolean +handleHintRequest( PalmAppGlobals* globals ) +{ + Boolean notDone; + Boolean draw; + XP_ASSERT( !!globals->game.board ); + draw = board_requestHint( globals->game.board, ¬Done ); + globals->hintPending = notDone; + return draw; +} /* handleHintRequest */ + +static Boolean +handleDone( PalmAppGlobals* globals ) +{ + return board_commitTurn( globals->game.board ); +} /* handleDone */ + +static Boolean +handleJuggle( PalmAppGlobals* globals ) +{ + return board_juggleTray( globals->game.board ); +} /* handleJuggle */ + +static Boolean +handleTrade( PalmAppGlobals* globals ) +{ + return board_beginTrade( globals->game.board ); +} /* handleJuggle */ + +static Boolean +buttonIsUsable( ControlPtr button ) +{ + return CtlEnabled( button ); +} /* buttonIsUsable */ + +static void +drawBitmapButton( PalmAppGlobals* globals, UInt16 ctrlID, UInt16 resID, + XP_Bool eraseIfDisabled ) +{ + FormPtr form = FrmGetActiveForm(); + UInt16 index = FrmGetObjectIndex( form, ctrlID ); + RectangleType bounds; + + FrmGetObjectBounds( form, index, &bounds ); + + if ( buttonIsUsable( getActiveObjectPtr( ctrlID ) ) ) { + draw_drawBitmapAt( globals->draw, resID, bounds.topLeft.x, + bounds.topLeft.y ); + } else if ( eraseIfDisabled ) { + /* gross hack; the button represents a larger bitmap; erase the + whole thing.*/ +#ifndef EIGHT_TILES + if ( ctrlID == XW_MAIN_HIDE_BUTTON_ID ) { + bounds.extent.x += TRAY_BUTTON_WIDTH + 1; + bounds.extent.y += TRAY_BUTTON_WIDTH + 1; + } +#endif + WinEraseRectangle( &bounds, 0 ); + } +} /* drawBitmapButton */ + +static void +drawFormButtons( PalmAppGlobals* globals ) +{ + XP_U16 pairs[] = { + XW_MAIN_FLIP_BUTTON_ID, FLIP_BUTTON_BMP_RES_ID, XP_TRUE, + XW_MAIN_VALUE_BUTTON_ID, VALUE_BUTTON_BMP_RES_ID, XP_TRUE, + XW_MAIN_HINT_BUTTON_ID, HINT_BUTTON_BMP_RES_ID, XP_TRUE, +#ifndef EIGHT_TILES + XW_MAIN_HIDE_BUTTON_ID, TRAY_BUTTONS_BMP_RES_ID, XP_TRUE, +#endif + XW_MAIN_SHOWTRAY_BUTTON_ID, SHOWTRAY_BUTTON_BMP_RES_ID, XP_FALSE, + 0, + }; + XP_U16* pair = pairs; + + if ( FrmGetActiveFormID() == XW_MAIN_FORM ) { + while ( !!*pair ) { + drawBitmapButton( globals, pair[0], pair[1], pair[2] ); + pair += 3; + } + } +} /* drawFormButtons */ + +static void +updateScrollbar( PalmAppGlobals* globals, Int16 newValue ) +{ + if ( FrmGetActiveFormID() == XW_MAIN_FORM ) { + ScrollBarPtr scroll = getActiveObjectPtr( XW_MAIN_SCROLLBAR_ID ); + XW_TrayVisState state = board_getTrayVisState( globals->game.board ); + XP_U16 max, min; + + max = model_numRows( globals->game.model ); + min = max; + if ( globals->needsScrollbar && (max == SBAR_MAX) + && state != TRAY_HIDDEN ) { + min -= 2; /* fragile!!! PENDING */ + } + + SclSetScrollBar( scroll, newValue + min, min, max, SBAR_PAGESIZE ); + } +} /* updateScrollbar */ + +static void +palmSetCtrlsForTray( PalmAppGlobals* globals ) +{ + XW_TrayVisState state = board_getTrayVisState( globals->game.board ); + FormPtr form = globals->mainForm; + + /* In rare circumstances, e.g. when an appStopEvent comes in while the + prefs dialog is up, this'll get called when the main form's not on + top. In that case it's probably ok to just do nothing. But if not + I'll need to queue an event of some sort so it gets done later. */ + if ( FrmGetActiveFormID() == XW_MAIN_FORM ) { + + disOrEnable( form, XW_MAIN_HINT_BUTTON_ID, + (state==TRAY_REVEALED) && + !globals->gameInfo.hintsNotAllowed ); + +#ifndef EIGHT_TILES + disOrEnable( form, XW_MAIN_DONE_BUTTON_ID, state==TRAY_REVEALED ); + disOrEnable( form, XW_MAIN_JUGGLE_BUTTON_ID, state==TRAY_REVEALED ); + disOrEnable( form, XW_MAIN_TRADE_BUTTON_ID, state==TRAY_REVEALED ); + disOrEnable( form, XW_MAIN_HIDE_BUTTON_ID, state!=TRAY_HIDDEN ); +#endif + disOrEnable( form, XW_MAIN_SHOWTRAY_BUTTON_ID, state==TRAY_HIDDEN + && globals->gameInfo.nPlayers > 0 ); + + globals->scrollValue = board_getYOffset( globals->game.board ); + updateScrollbar( globals, globals->scrollValue ); + + /* PENDING(ehouse) Can't the board just do this itself? */ + if ( state==TRAY_HIDDEN ) { + board_setYOffset( globals->game.board, 0, XP_TRUE ); + } + } +} /* palmSetCtrlsForTray */ + +static Boolean +scrollBoard( PalmAppGlobals* globals, Int16 newValue, Boolean fromBar ) +{ + XP_Bool result = XP_FALSE; + XP_U16 curYOffset; + + XP_ASSERT( !!globals->game.board ); + curYOffset = board_getYOffset( globals->game.board ); + + result = curYOffset != newValue; + if ( result ) { + result = board_setYOffset( globals->game.board, newValue, XP_FALSE ); + } + + if ( !fromBar ) { + updateScrollbar( globals, newValue ); + } + return result; +} /* scrollBoard */ + +/* We can't create the board back in newgame.c because the wrong form's + * frontmost at that point. So we do it here instead -- and must also call + * server_do. + */ +static void +initAndStartBoard( PalmAppGlobals* globals, XP_Bool newGame ) +{ + DictionaryCtxt* dict; + 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 ) { + XP_UCHAR* dictName = dict_getName( dict ); + if ( !!newDictName && 0 != XP_STRCMP( (const char*)dictName, + (const char*)newDictName ) ) { + dict_destroy( dict ); + dict = NULL; + } else { + replaceStringIfDifferent( MEMPOOL &globals->gameInfo.dictName, + dictName ); + } + } + + if ( !dict ) { + XP_ASSERT( !!newDictName ); + dict = palm_dictionary_make( MPPARM(globals->mpool) + copyString( MEMPOOL newDictName ), + globals->dictList ); + XP_ASSERT( !!dict ); + model_setDictionary( globals->game.model, dict ); + } + + if ( newGame ) { + XP_U32 newGameID = TimGetSeconds(); + game_reset( MEMPOOL &globals->game, &globals->gameInfo, + newGameID, &globals->gState.cp, palm_send, globals ); + } + + XP_ASSERT( !!globals->game.board ); + (void)positionBoard( globals ); + +#ifdef IR_SUPPORT +# ifndef IR_EXCHMGR + if ( globals->gameInfo.serverRole == SERVER_STANDALONE ) { + ir_cleanup( globals ); + } else { + ir_setup( globals ); + } +# endif + + if ( newGame && globals->gameInfo.serverRole == SERVER_ISCLIENT ) { + XWStreamCtxt* stream; + XP_ASSERT( !!globals->game.comms ); + stream = mem_stream_make( MEMPOOL globals->vtMgr, globals, + CHANNEL_NONE, palm_send_on_close ); + server_initClientConnection( globals->game.server, stream ); + } +#endif + + /* do this before drawing the board. If it assigns tiles, for example, + that'll make a difference on the screen. */ + (void)server_do( globals->game.server ); + + board_invalAll( globals->game.board ); + board_draw( globals->game.board ); + + globals->isNewGame = false; +} /* initAndStartBoard */ + +#ifdef DEBUG +static void +askOnClose( XWStreamCtxt* stream, void* closure ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)closure; + + askFromStream( globals, stream, -1, false ); +} /* askOnClose */ +#endif + +static void +updateForLefty( PalmAppGlobals* globals, FormPtr form ) +{ + XP_S16 idsAndXs[] = { + /* ButtonID, x-coord-when-lefty, */ + XW_MAIN_FLIP_BUTTON_ID, 0, + XW_MAIN_VALUE_BUTTON_ID, 0, + XW_MAIN_HINT_BUTTON_ID, 0, + XW_MAIN_SCROLLBAR_ID, 0, + XW_MAIN_SHOWTRAY_BUTTON_ID, 0, + +#ifdef FOR_GREMLINS + GREMLIN_BOARD_GADGET_IDAUTOID, 9, + GREMLIN_TRAY_GADGET_IDAUTOID, 9, +#endif + +#ifndef EIGHT_TILES + XW_MAIN_HIDE_BUTTON_ID, -1, + XW_MAIN_JUGGLE_BUTTON_ID, TRAY_BUTTON_WIDTH-1, + XW_MAIN_TRADE_BUTTON_ID, -1, + XW_MAIN_DONE_BUTTON_ID, TRAY_BUTTON_WIDTH-1, +#endif + 0, + }; + if ( globals->isLefty ) { + UInt16 id; + UInt16* idp = (UInt16*)idsAndXs; + for ( id = *idp; !!id; id = *(idp+=2) ) { + XP_S16 x, y; + UInt16 objIndex = FrmGetObjectIndex( form, id ); + FrmGetObjectPosition( form, objIndex, &x, &y ); + + FrmSetObjectPosition( form, objIndex, idp[1], y ); + } + } +} /* updateForLefty */ + +static void +beamBoard( PalmAppGlobals* globals ) +{ + Err err; + XP_UCHAR prcName[50]; + + unlockBonusPtrs( globals ); + DmCloseDatabase( globals->boardDBP ); + + /* do we need to close the db first, and reopen after? */ + XP_SNPRINTF( prcName, sizeof(prcName), (XP_UCHAR*)"%s.prc", + XW_PREFS_DBNAME ); + err = sendDatabase( CARD_0, globals->boardDBID, + prcName, (XP_UCHAR*)"board prefs" ); + + globals->boardDBP = DmOpenDatabase( CARD_0, globals->boardDBID, + dmModeWrite ); + setupBonusPtrs( globals ); +} /* beamBoard */ + +static XP_Bool +considerMenuShow( PalmAppGlobals* globals, EventPtr event ) +{ + XP_S16 y = event->screenY; + XP_Bool penInRightPlace = (y < PALM_BOARD_TOP) && (y >= 0); + + if ( penInRightPlace ) { + EventType menuEvent; + XP_MEMSET( &menuEvent, 0, sizeof(menuEvent) ); + menuEvent.eType = keyDownEvent; + menuEvent.data.keyDown.chr = menuChr; + menuEvent.data.keyDown.keyCode = 0; + menuEvent.data.keyDown.modifiers = commandKeyMask; + EvtAddEventToQueue( &menuEvent ); + } + + return penInRightPlace; +} /* considerMenuShow */ + +/* Draw immediately, because we've made a change we need reflected + immediately. */ +static void +drawChangedBoard( PalmAppGlobals* globals ) +{ + if ( !!globals->game.board && !globals->menuIsDown ) { + board_draw( globals->game.board ); + } +} /* drawChangedBoard */ + +static XP_Bool +tryLoadSavedGame( PalmAppGlobals* globals, XP_U16 newIndex ) +{ + XWGame tmpGame; + CurGameInfo tmpGInfo; + XP_Bool loaded; + + XP_MEMSET( &tmpGame, 0, sizeof(tmpGame) ); + XP_MEMSET( &tmpGInfo, 0, sizeof(tmpGInfo) ); + + loaded = loadCurrentGame( globals, newIndex, &tmpGame, &tmpGInfo ); + if ( loaded ) { + game_dispose( &globals->game ); + gi_disposePlayerInfo( MEMPOOL &globals->gameInfo ); + globals->game = tmpGame; + /* we leaking dictName here? PENDING(ehouse) */ + XP_MEMCPY( &globals->gameInfo, &tmpGInfo, sizeof(globals->gameInfo) ); + globals->gState.curGameIndex = newIndex; + } else { + game_dispose( &tmpGame ); + gi_disposePlayerInfo( MEMPOOL &tmpGInfo ); + } + + return loaded; +} /* tryLoadSavedGame */ + +/***************************************************************************** + * + ****************************************************************************/ +static Boolean +mainViewHandleEvent( EventPtr event ) +{ + Boolean result = true; + Boolean draw = false; + Boolean erase; +#if defined CURSOR_MOVEMENT && defined DEBUG + CursorDirection cursorDir; + Boolean movePiece; +#endif + PalmAppGlobals* globals; + OpenSavedGameData* savedGameData; + char newName[MAX_GAMENAME_LENGTH]; + XP_U16 prevSize; + + CALLBACK_PROLOGUE(); + + globals = getFormRefcon(); + + switch ( event->eType ) { + + case nilEvent: + draw = result = handleNilEvent( globals, event ); + break; + + case newGameCancelEvent: + /* If user cancelled the new game dialog that came up the first time + he launched (i.e. when there's no game to fall back to) then just + quit. It's easier than dealing with everything that can go wrong + in this state. */ + if ( globals->isFirstLaunch ) { + EventType eventToPost; + eventToPost.eType = appStopEvent; + EvtAddEventToQueue( &eventToPost ); + } + globals->isNewGame = false; + break; + + case openSavedGameEvent: + globals->postponeDraw = XP_FALSE; + prevSize = globals->gameInfo.boardSize; + savedGameData = (OpenSavedGameData*)&event->data.generic; + + if ( tryLoadSavedGame( globals, savedGameData->newGameIndex ) ) { + if ( prevSize > globals->gameInfo.boardSize ) { + WinEraseWindow(); + } + initAndStartBoard( globals, XP_FALSE ); + } + draw = true; + break; + + case newGameOkEvent: + + if ( globals->newGameIsNew ) { + globals->gState.curGameIndex = countGameRecords( globals ); + } + globals->postponeDraw = false; + makeDefaultGameName( newName ); + writeNameToGameRecord( globals, globals->gState.curGameIndex, + newName, XP_STRLEN(newName) ); + globals->isFirstLaunch = false; /* so we'll save the game */ + + case loadGameEvent: + XP_ASSERT( !!globals->game.server ); + initAndStartBoard( globals, event->eType == newGameOkEvent ); + draw = true; + XP_ASSERT( !!globals->game.board ); + + case boardRedrawEvt: + draw = true; + break; + + case prefsChangedEvent: + erase = LocalPrefsToGlobal( globals ); + draw = board_prefsChanged( globals->game.board, &globals->gState.cp ); + server_prefsChanged( globals->game.server, &globals->gState.cp ); + /* watch out for short-circuiting. Both must be called */ + erase = positionBoard( globals ) || erase; + if ( erase ) { + WinEraseWindow(); + } + globals->postponeDraw = false; + FrmUpdateForm( 0, frmRedrawUpdateCode ); /* <- why is this necessary? */ + break; + + case winExitEvent: + if ( event->data.winExit.exitWindow == (WinHandle)FrmGetActiveForm() ) { + globals->menuIsDown = true; + } + break; + + case winEnterEvent: + // From PalmOS's "Knowledge base": In the current code, the menu + // doesn't remove itself when it receives a winExitEvent so we need + // an extra check to make sure that the window being entered is the + // first form. This may be different in your implementation (ie: if + // the first form opened is not the one you are currently watching + // for) + if (event->data.winEnter.enterWindow == (WinHandle)FrmGetActiveForm() && + event->data.winEnter.enterWindow == (WinHandle)FrmGetFirstForm() ) { + globals->menuIsDown = false; + } + break; + + case frmOpenEvent: + globals->gState.windowAvail = true; + globals->mainForm = FrmGetActiveForm(); + updateForLefty( globals, globals->mainForm ); + FrmDrawForm( globals->mainForm ); + break; + + case frmUpdateEvent: + FrmDrawForm( globals->mainForm ); /* on 3.5 and higher, this erases + the window before drawing, so + there's nothing to be done about + the erase after user clicks OK + in prefs dialog. */ + if ( !!globals->game.board ) { + RectangleType clip; + WinGetClip( &clip ); + + drawFormButtons( globals ); + board_invalRect( globals->game.board, (XP_Rect*)&clip ); + draw = !globals->postponeDraw; + } + break; + + case penDownEvent: + globals->penDown = true; + draw = board_handlePenDown( globals->game.board, event->screenX, + event->screenY, 0 ); + result = draw; + break; + + case penMoveEvent: + if ( globals->penDown ) { + result = draw = board_handlePenMove( globals->game.board, + event->screenX, + event->screenY ); + } + break; + + case penUpEvent: + if ( globals->penDown ) { + result = draw = board_handlePenUp( globals->game.board, + event->screenX, event->screenY, + 0 ); + globals->penDown = false; + + if ( !result ) { + result = considerMenuShow( globals, event ); + } + } + break; + + case menuEvent: + MenuEraseStatus(0); + switch ( event->data.menu.itemID ) { + + case XW_TILEVALUES_PULLDOWN_ID: + if ( !!globals->game.server ) { + XWStreamCtxt* stream = mem_stream_make( MEMPOOL + globals->vtMgr, + globals, + CHANNEL_NONE, + NULL ); + XP_UCHAR* s = getResString( globals, STR_VALUES_HEADER ); + stream_putBytes( stream, s, XP_STRLEN((const char*)s) ); + + server_formatPoolCounts( globals->game.server, stream, + 3 ); /* 3: ncols */ + + askFromStream( globals, stream, STR_VALUES_TITLE, true ); + } + break; + + case XW_HISTORY_PULLDOWN_ID: + if ( !!globals->game.server ) { + XP_Bool gameOver = server_getGameIsOver(globals->game.server); + XWStreamCtxt* stream = mem_stream_make( MEMPOOL + globals->vtMgr, + globals, + CHANNEL_NONE, NULL ); + + model_writeGameHistory( globals->game.model, stream, + globals->game.server, gameOver ); + + askFromStream( globals, stream, STR_HISTORY_TITLE, true ); + } + break; + + case XW_NEWGAME_PULLDOWN_ID: + askStartNewGame( globals ); + break; + + case XW_SAVEDGAMES_PULLDOWN_ID: + if ( 0 ) { +#if defined OWNER_HASH || defined NO_REG_REQUIRED + } else if ( checkUserName() ) { + saveOpenGame( globals );/* so it can be accurately duped */ + /* save game changes state; reflect on screen before + popping up dialog */ + drawChangedBoard( globals ); + FrmPopupForm( XW_SAVEDGAMES_DIALOG_ID ); +#endif +#ifndef NO_REG_REQUIRED + } else { + userErrorFromStrId( globals, STR_NOT_UNREG_VERS ); +#endif + } + break; + + case XW_FINISH_PULLDOWN_ID: + if ( server_getGameIsOver( globals->game.server ) ) { + displayFinalScores( globals ); + } else if ( palmaskFromStrId(globals, STR_CONFIRM_END_GAME, + -1, -1) ) { + server_endGame( globals->game.server ); + draw = true; + } + break; + +#ifndef XWFEATURE_STANDALONE_ONLY + case XW_RESENDIR_PULLDOWN_ID: + if ( !!globals->game.comms ) { + (void)comms_resendAll( globals->game.comms ); + } + break; +#endif + case XW_BEAMDICT_PULLDOWN_ID: + globals->dictuiForBeaming = true; + FrmPopupForm( XW_DICTINFO_FORM ); + break; + + case XW_BEAMBOARD_PULLDOWN_ID: + beamBoard( globals ); + break; + + case XW_PASSWORDS_PULLDOWN_ID: + globals->isNewGame = false; + FrmPopupForm( XW_NEWGAMES_FORM ); + break; + +#ifdef COLOR_EDIT + case XW_EDITCOLORS_PULLDOWN_ID: + if ( globals->able == COLOR ) { + FrmPopupForm( XW_COLORPREF_DIALOG_ID ); + } + break; +# ifdef DEBUG + case XW_DUMPCOLORS_PULLDOWN_ID: + dumpColors( globals ); + break; +# endif +#endif + + case XW_PREFS_PULLDOWN_ID: + globals->stateTypeIsGlobal = XP_TRUE; + GlobalPrefsToLocal( globals ); + FrmPopupForm( XW_PREFS_FORM ); + break; + + case XW_ABOUT_PULLDOWN_ID: + palmaskFromStrId(globals, STR_ABOUT_CONTENT, STR_ABOUT_TITLE,-1); + break; + + case XW_HINT_PULLDOWN_ID: + board_resetEngine( globals->game.board ); + case XW_NEXTHINT_PULLDOWN_ID: + draw = handleHintRequest( globals ); + break; + + case XW_UNDOCUR_PULLDOWN_ID: + draw = board_replaceTiles( globals->game.board ); + break; + + case XW_UNDOLAST_PULLDOWN_ID: + draw = server_handleUndo( globals->game.server ); + break; + + case XW_DONE_PULLDOWN_ID: + draw = handleDone( globals ); + break; + + case XW_JUGGLE_PULLDOWN_ID: + draw = handleJuggle( globals ); + break; + + case XW_TRADEIN_PULLDOWN_ID: + draw = handleTrade( globals ); + break; + + case XW_HIDETRAY_PULLDOWN_ID: + draw = handleHideTray( globals ); + break; + +#ifdef FOR_GREMLINS + case XW_GREMLIN_DIVIDER_RIGHT: + if ( !!globals->game.board ) { + board_moveDivider( globals->game.board, XP_TRUE ); + draw = XP_TRUE; + } + break; + case XW_GREMLIN_DIVIDER_LEFT: + if ( !!globals->game.board ) { + board_moveDivider( globals->game.board, XP_FALSE ); + draw = XP_TRUE; + } + break; +#endif + +#ifdef DEBUG + case XW_DEBUGSHOW_PULLDOWN_ID: + globals->gState.showDebugstrs = true; + break; + case XW_DEBUGHIDE_PULLDOWN_ID: + globals->gState.showDebugstrs = false; + board_invalAll( globals->game.board ); + draw = true; + break; + + case XW_DEBUGMEMO_PULLDOWN_ID: + globals->gState.logToMemo = true; + break; + case XW_DEBUGSCREEN_PULLDOWN_ID: + globals->gState.logToMemo = false; + break; + + +# if 0 + case XW_RESET_PULLDOWN_ID: { + EventType eventToPost; + eventToPost.eType = appStopEvent; + EvtAddEventToQueue( &eventToPost ); + } + + globals->resetGame = true; + break; +# endif + + case XW_NETSTATS_PULLDOWN_ID: + if ( !!globals->game.comms ) { + XWStreamCtxt* stream = mem_stream_make( MEMPOOL + globals->vtMgr, + globals, + CHANNEL_NONE, + askOnClose ); + comms_getStats( globals->game.comms, stream ); + stream_destroy( stream ); + } + break; +#ifdef MEM_DEBUG + case XW_MEMSTATS_PULLDOWN_ID : + if ( !!globals->mpool ) { + XWStreamCtxt* stream = mem_stream_make( MEMPOOL + globals->vtMgr, + globals, + CHANNEL_NONE, + askOnClose ); + mpool_stats( globals->mpool, stream ); + stream_destroy( stream ); + } + break; +#endif + +#endif + + default: + break; + } + break; + + case keyDownEvent: { + XP_Key xpkey = XP_KEY_NONE; + Int16 ch = event->data.keyDown.chr; + + switch ( ch ) { + case pageUpChr: + draw = scrollBoard( globals, 0, false ); + break; + case pageDownChr: + draw = scrollBoard( globals, 2, false ); + break; + case backspaceChr: + xpkey = XP_CURSOR_KEY_DEL; + break; + default: + /* I'm not interested in being dependent on a particular version + of the OS, (can't manage to link against the intl library + anyway) and so don't want to use the 3.5-only text tests. So + let's give the board two shots at each char, one lower case + and another upper. */ + if ( ch < 255 && ch > ' ' ) { + draw = board_handleKey( globals->game.board, ch ); + if ( !draw && ch >= 'a' ) { + draw = board_handleKey( globals->game.board, ch - ('a' - 'A') ); + } + } + } + if ( xpkey != XP_KEY_NONE ) { + draw = board_handleKey( globals->game.board, xpkey ); + } + } + break; + + case sclRepeatEvent: + draw = scrollBoard( globals, event->data.sclRepeat.newValue-SBAR_MIN, + true ); + result = false; + break; + + case ctlSelectEvent: + switch ( event->data.ctlEnter.controlID ) { + case XW_MAIN_FLIP_BUTTON_ID: + draw = handleFlip( globals ); + break; + case XW_MAIN_VALUE_BUTTON_ID: + draw = handleValueToggle( globals ); + break; + case XW_MAIN_HINT_BUTTON_ID: + draw = handleHintRequest( globals ); + break; +#ifndef EIGHT_TILES + case XW_MAIN_DONE_BUTTON_ID: + draw = handleDone( globals ); + break; + case XW_MAIN_JUGGLE_BUTTON_ID: + draw = handleJuggle( globals ); + break; + case XW_MAIN_TRADE_BUTTON_ID: + draw = handleTrade( globals ); + break; + case XW_MAIN_HIDE_BUTTON_ID: + draw = handleHideTray( globals ); + break; +#endif + case XW_MAIN_SHOWTRAY_BUTTON_ID: + draw = board_showTray( globals->game.board ); + break; + + default: + break; + } /* switch event->data.ctlEnter.controlID */ + + default: + result = false; + break; + } + + if ( draw && !!globals->game.board && !globals->menuIsDown ) { + XP_Bool drewAll = board_draw( globals->game.board ); + if ( !drewAll ) { + globals->msgReceivedDraw = XP_TRUE; + palm_util_requestTime( &globals->util ); + } + } + + CALLBACK_EPILOGUE(); + return result; +} /* mainViewHandleEvent */ + +static void +askStartNewGame( PalmAppGlobals* globals ) +{ + if ( palmaskFromStrId( globals, STR_ASK_REPLACE_GAME, -1, STR_NO)) { + /* do nothing; popping up the NEWGAMES dlg will do it -- if not + cancelled */ + globals->newGameIsNew = XP_FALSE; + } else if ( checkUserName() ) { + saveOpenGame( globals ); + + drawChangedBoard( globals ); + + globals->newGameIsNew = XP_TRUE; + } else { + return; + } + globals->isNewGame = true; + globals->isFirstLaunch = false; + FrmPopupForm( XW_NEWGAMES_FORM ); +} /* askStartNewGame */ + +static void +displayFinalScores( PalmAppGlobals* globals ) +{ + XWStreamCtxt* stream; + + stream = mem_stream_make( MEMPOOL globals->vtMgr, globals, + CHANNEL_NONE, NULL ); + server_writeFinalScores( globals->game.server, stream ); + stream_putU8( stream, '\0' ); + + askFromStream( globals, stream, STR_FINAL_SCORES_TITLE, true ); +} /* displayFinalScores */ + +XP_S16 +palm_memcmp( XP_U8* p1, XP_U8* p2, XP_U16 nBytes ) +{ + /* man memcmp: The memcmp() function compares the first n bytes of the + memory areas s1 and s2. It returns an integer less than, equal to, or + greater than zero if s1 is found, respectively, to be less than, to + match, or be greater than s2.*/ + XP_S16 result = 0; + while ( result == 0 && nBytes-- ) { + result = *p1++ - *p2++; + } + return result; +} /* palm_memcmp */ + +static void +askScrollbarAdjust( PalmAppGlobals* globals, FieldPtr field ) +{ + UInt16 scrollPos; + UInt16 textHeight; + UInt16 fieldHeight; + UInt16 maxValue; + ScrollBarPtr scroll; + + FldGetScrollValues( field, &scrollPos, &textHeight, &fieldHeight ); + + if ( textHeight > fieldHeight ) { + maxValue = textHeight - fieldHeight; + } else if ( scrollPos != 0 ) { + maxValue = scrollPos; + } else { + maxValue = 0; + } + + scroll = getActiveObjectPtr( XW_ASK_SCROLLBAR_ID ); + SclSetScrollBar( scroll, scrollPos, 0, maxValue, fieldHeight-1 ); + globals->prevScroll = scrollPos; +} /* askScrollbarAdjust */ + +static void +getWindowBounds( WinHandle wHand, RectangleType* r ) +{ + WinHandle prev = WinSetDrawWindow( wHand ); + WinGetDrawWindowBounds( r ); + (void)WinSetDrawWindow( prev ); +} /* getWindowBounds */ + +static void +tryGrowAskToFit( FormPtr form, FieldPtr field, XP_UCHAR* str ) +{ + RectangleType fieldRect, dlgRect; + UInt16 scrollPos; + UInt16 textHeight; + UInt16 fieldHeight; + WinHandle wHand; + XP_S16 fldWidth, fldHeight, maxHeight, needsHeight; + XP_U16 lineHeight; + XP_U16 growthAmt, i; + RectangleType objBounds; + + FldGetScrollValues( field, &scrollPos, &textHeight, &fieldHeight ); + + getObjectBounds( XW_ASK_TXT_FIELD_ID, &fieldRect ); + fldWidth = fieldRect.extent.x; + fldHeight = fieldRect.extent.y; + + /* max is cur height plus diff between dialog's height and what it could + be */ + wHand = FrmGetWindowHandle( form ); + getWindowBounds( wHand, &dlgRect ); + maxHeight = fldHeight + (156 - dlgRect.extent.y); + + lineHeight = FntLineHeight(); + + needsHeight = FldCalcFieldHeight((const char*)str, fldWidth) * lineHeight; + + if ( needsHeight > maxHeight ) { + /* make window as large as it can be */ + needsHeight = maxHeight; + } + + /* now round down to a multiple of lineHeight */ + needsHeight = (needsHeight / lineHeight) * lineHeight; + + growthAmt = needsHeight - fldHeight; + + /* now reflect the new size by moving things around. Window first */ + dlgRect.topLeft.y -= growthAmt; + dlgRect.extent.y += growthAmt; + WinSetBounds( wHand, &dlgRect ); + + /* then the field */ + fieldRect.extent.y += growthAmt; + setObjectBounds( XW_ASK_TXT_FIELD_ID, &fieldRect ); + + /* the scrollbar */ + getObjectBounds( XW_ASK_SCROLLBAR_ID, &objBounds ); + objBounds.extent.y = fieldRect.extent.y; + setObjectBounds( XW_ASK_SCROLLBAR_ID, &objBounds ); + + /* and the buttons */ + XP_ASSERT( XW_ASK_CANCEL_BUTTON_ID - XW_ASK_OK_BUTTON_ID == 1 ); + for ( i = XW_ASK_OK_BUTTON_ID; i <= XW_ASK_CANCEL_BUTTON_ID; ++i ){ + getObjectBounds( i, &objBounds ); + objBounds.topLeft.y += growthAmt; + setObjectBounds( i, &objBounds ); + } +} /* tryGrowAskToFit */ + +static Boolean +handleScrollInAsk( EventPtr event ) +{ + Int16 linesToScroll = 0; + Boolean scrollFromButton = false; + Boolean result = true; + WinDirectionType direction = 5; + PalmAppGlobals* globals = (PalmAppGlobals*)getFormRefcon(); + FieldPtr field; + UInt16 endPosition; + + field = getActiveObjectPtr( XW_ASK_TXT_FIELD_ID ); + + switch ( event->eType ) { + + case penUpEvent: + /* When user drags pen through text and causes a scroll the scrollbar + will get out of sync. So we listen to the event but don't claim to + have handled it. */ + askScrollbarAdjust( globals, field ); + result = false; + break; + + case keyDownEvent: + switch ( event->data.keyDown.chr ) { + case pageUpChr: + direction = winUp; + break; + case pageDownChr: + direction = winDown; + break; + default: + result = false; + } + linesToScroll = 3; + scrollFromButton = true; + break; + + case sclRepeatEvent: { + short newVal = event->data.sclExit.newValue; + linesToScroll = XP_ABS(newVal - globals->prevScroll); + XP_ASSERT( linesToScroll != 0 ); + direction = newVal > globals->prevScroll? winDown: winUp; + globals->prevScroll = newVal; + scrollFromButton = false; + } + break; + + case menuEvent: + MenuEraseStatus(0); + result = true; + switch ( event->data.menu.itemID ) { + case ASK_COPY_PULLDOWN_ID: + FldCopy( field ); + break; + case ASK_SELECTALL_PULLDOWN_ID: + endPosition = FldGetTextLength( field ); + FldSetSelection( field, 0, endPosition ); + break; + } + break; + + default: + result = false; + } + + if ( result && FldScrollable( field, direction ) ) { + FldScrollField( field, linesToScroll, direction ); + if ( scrollFromButton ) { + askScrollbarAdjust( globals, field ); + } else { + result = false; /* for some reason this is necessary to make + scrolbar work right. */ + } + } + + return result; +} /* handleScrollInAsk */ + +/* Swap the two elements, preserving their outside borders */ +static void +moveLeftOf( UInt16 rightID, UInt16 leftID ) +{ + UInt16 leftIndex, rightIndex; + UInt16 middleMargin; + RectangleType leftBounds, rightBounds; + FormPtr form = FrmGetActiveForm(); + + leftIndex = FrmGetObjectIndex( form, leftID ); + rightIndex = FrmGetObjectIndex( form, rightID ); + + FrmGetObjectBounds( form, rightIndex, &rightBounds ); + FrmGetObjectBounds( form, leftIndex, &leftBounds ); + + XP_ASSERT( rightBounds.topLeft.y == leftBounds.topLeft.y ); + + middleMargin = rightBounds.topLeft.x - + (leftBounds.topLeft.x+leftBounds.extent.x); + + FrmSetObjectPosition( form, rightIndex, leftBounds.topLeft.x, + leftBounds.topLeft.y ); + + FrmSetObjectPosition( + form, leftIndex, + leftBounds.topLeft.x + rightBounds.extent.x + middleMargin, + leftBounds.topLeft.y ); + +} /* moveLeftOf */ + +Boolean +palmaskFromStrId( PalmAppGlobals* globals, XP_U16 strId, XP_S16 titleID, + XP_S16 altButtonID ) +{ + XP_UCHAR* message = getResString( globals, strId ); + XP_UCHAR* alt = altButtonID < 0? NULL: getResString( globals, altButtonID ); + return palmask( globals, message, alt, titleID ); +} /* palmaskFromStrId */ + +static Boolean +palmask( PalmAppGlobals* globals, XP_UCHAR* str, XP_UCHAR* altButton, + XP_S16 titleID ) +{ + FormPtr form, prevForm; + FieldPtr field; + XP_UCHAR* title; + UInt16 buttonHit; + + board_pushTimerSave( globals->game.board ); + + title = titleID >= 0? getResString( globals, titleID ): NULL; + + prevForm = FrmGetActiveForm(); + form = FrmInitForm( XW_ASK_FORM_ID ); + + FrmSetActiveForm( form ); + + if ( title != NULL ) { + FrmSetTitle( form, (char*)title ); + /* Hack: take advantage of fact that for now only non-queries should + not have a cancel button. */ + disOrEnable( form, XW_ASK_CANCEL_BUTTON_ID, false ); + centerControl( form, XW_ASK_OK_BUTTON_ID ); + } else if ( !!altButton ) { + CtlSetLabel( getActiveObjectPtr(XW_ASK_CANCEL_BUTTON_ID), + (const char*)altButton ); + } + FrmSetEventHandler( form, handleScrollInAsk ); + globals->prevScroll = 0; + + if ( globals->isLefty ) { + moveLeftOf( XW_ASK_SCROLLBAR_ID, XW_ASK_TXT_FIELD_ID ); + } + + field = getActiveObjectPtr( XW_ASK_TXT_FIELD_ID ); + FldSetTextPtr( field, (char*)str ); + + FldRecalculateField( field, true ); + + if ( globals->romVersion >= 35 ) { + /* I'm not sure how to do this pre 3.5 */ + tryGrowAskToFit( form, field, str ); + } + + askScrollbarAdjust( globals, field ); + + FrmDrawForm( form ); + + buttonHit = FrmDoDialog( form ); + + FrmDeleteForm( form ); + FrmSetActiveForm( prevForm ); + + board_popTimerSave( globals->game.board ); + + return buttonHit == XW_ASK_OK_BUTTON_ID; +} /* palmask */ + +static Boolean +askFromStream( PalmAppGlobals* globals, XWStreamCtxt* stream, XP_S16 titleID, + Boolean closeAndDestroy ) +{ + XP_U16 nBytes = stream_getSize( stream ); + Boolean result; + XP_UCHAR* buffer; + + XP_ASSERT( nBytes < maxFieldTextLen ); + + buffer = MemPtrNew( nBytes + 1 ); + stream_getBytes( stream, buffer, nBytes ); + /* nuke trailing chars to they don't extend length of field */ + while ( buffer[nBytes-1] == '\n' ) { + --nBytes; + } + buffer[nBytes] = '\0'; /* just to be safe */ + + result = palmask( globals, buffer, NULL, titleID ); + MemPtrFree( buffer ); + + if ( closeAndDestroy ) { + stream_destroy( stream ); + } + + return result; +} /* askFromStream */ + +Boolean +askPassword( PalmAppGlobals* globals, const XP_UCHAR* name, Boolean isNew, + XP_UCHAR* retbuf, XP_U16* len ) +{ + Boolean result = false; + FormPtr prevForm, form; + FieldPtr field; + UInt16 showMe; + + prevForm = FrmGetActiveForm(); + form = FrmInitForm( XW_PASSWORD_DIALOG_ID ); + FrmSetActiveForm( form ); + + if ( isNew ) { + showMe = XW_PASSWORD_NEWNAME_LABEL; + } else { + showMe = XW_PASSWORD_NAME_LABEL; + } + FrmShowObject( form, FrmGetObjectIndex( form, showMe ) ); + + FrmDrawForm( form ); + + if ( !!name ) { + field = getActiveObjectPtr( XW_PASSWORD_NAME_FIELD ); + FldSetTextPtr( field, (char*)name ); + FldDrawField( field ); + } + + FrmSetFocus( form, FrmGetObjectIndex( form, XW_PASSWORD_PASS_FIELD ) ); + field = getActiveObjectPtr( XW_PASSWORD_PASS_FIELD ); + + if ( FrmDoDialog( form ) == XW_PASSWORD_OK_BUTTON ) { + char* enteredPass = FldGetTextPtr( field ); + XP_U16 enteredLen = !enteredPass? 0: StrLen(enteredPass); + if ( enteredLen < *len ) { + result = true; + if ( enteredLen > 0 ) { + XP_MEMCPY( retbuf, enteredPass, enteredLen ); + } + retbuf[enteredLen] = '\0'; + *len = enteredLen; + } + } + + FrmDeleteForm( form ); + FrmSetActiveForm( prevForm ); + + return result; +} /* askPassword */ + +static Boolean +handleKeysInBlank( EventPtr event ) +{ + Boolean handled = false; + + if ( event->eType == keyDownEvent ) { + char ch = event->data.keyDown.chr; + + if ( ch >= 'a' && ch <= 'z' ) { + ch += 'A' - 'a'; + } + if ( ch >= 'A' && ch <= 'Z' ) { + ListPtr lettersList = getActiveObjectPtr( XW_BLANK_LIST_ID ); + XP_U16 nItems; + XP_U16 i; + + XP_ASSERT( !!lettersList ); + nItems = LstGetNumberOfItems( lettersList ); + + for ( i = 0; i < nItems; ++i ) { + XP_UCHAR* itext = LstGetSelectionText( lettersList, i ); + + if ( !!itext && (itext[0] == ch) ) { + LstSetSelection( lettersList, i ); + LstMakeItemVisible( lettersList, i ); + handled = true; + break; + } + } + } + } + + return handled; +} /* handleKeysInBlank */ + +static XP_S16 +askBlankValue( PalmAppGlobals* globals, XP_U16 playerNum, PickInfo* pi, + XP_U16 nTiles, XP_UCHAR4* texts ) +{ + FormPtr form, prevForm; + ListPtr lettersList; + ListData ld; + XP_U16 i; + XP_S16 chosen; + XP_UCHAR labelBuf[64]; + XP_UCHAR* name; + XP_UCHAR* labelFmt; + FieldPtr fld; + XP_U16 tapped; + XP_Bool forBlank = pi->why == PICK_FOR_BLANK; + + initListData( MEMPOOL &ld, nTiles ); + + for ( i = 0; i < nTiles; ++i ) { + addListTextItem( MEMPOOL &ld, (XP_UCHAR*)texts[i] ); + } + + prevForm = FrmGetActiveForm(); + form = FrmInitForm( XW_BLANK_DIALOG_ID ); + FrmSetActiveForm( form ); + +#ifdef FEATURE_TRAY_EDIT + if ( forBlank ) { + disOrEnable( form, XW_BLANK_PICK_BUTTON_ID, XP_FALSE ); + } +#endif + + lettersList = getActiveObjectPtr( XW_BLANK_LIST_ID ); + setListChoices( &ld, lettersList, NULL ); + + LstSetSelection( lettersList, 0 ); + + fld = getActiveObjectPtr( XW_BLANK_LABEL_FIELD_ID ); + name = globals->gameInfo.players[playerNum].name; + labelFmt = getResString( globals, +#ifdef FEATURE_TRAY_EDIT + !forBlank? STRS_PICK_TILE: +#endif + STR_PICK_BLANK ); + XP_SNPRINTF( labelBuf, sizeof(labelBuf), labelFmt, name ); + +#ifdef FEATURE_TRAY_EDIT + if ( !forBlank ) { + XP_U16 lenSoFar = XP_STRLEN(labelBuf); + XP_SNPRINTF( labelBuf + lenSoFar, sizeof(labelBuf) - lenSoFar, + " (%d/%d)", pi->thisPick, pi->nTotal ); + } +#endif + + FldSetTextPtr( fld, labelBuf ); + FldRecalculateField( fld, false ); + + FrmDrawForm( form ); + + FrmSetEventHandler( form, handleKeysInBlank ); + tapped = FrmDoDialog( form ); + + if ( 0 ) { +#ifdef FEATURE_TRAY_EDIT + } else if ( tapped == XW_BLANK_PICK_BUTTON_ID ) { + chosen = -1; +#endif + } else { + chosen = LstGetSelection( lettersList ); + } + + FrmDeleteForm( form ); + FrmSetActiveForm( prevForm ); + + freeListData( MEMPOOL &ld ); + + return chosen; +} /* askBlankValue */ + +/***************************************************************************** + * Callbacks + ****************************************************************************/ +static VTableMgr* +palm_util_getVTManager( XW_UtilCtxt* uc ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + return globals->vtMgr; +} /* palm_util_getVTManager */ + +static void +palm_util_userError( XW_UtilCtxt* uc, UtilErrID id ) +{ + PalmAppGlobals* globals; + XP_U16 strID = STR_LAST_STRING; + + switch( id ) { + case ERR_TILES_NOT_IN_LINE: + strID = STR_ALL_IN_LINE_ERR; + break; + case ERR_NO_EMPTIES_IN_TURN: + strID = STR_NO_EMPTIES_ERR; + break; + + case ERR_TWO_TILES_FIRST_MOVE: + strID = STR_FIRST_MOVE_ERR; + break; + case ERR_TILES_MUST_CONTACT: + strID = STR_MUST_CONTACT_ERR; + break; + case ERR_NOT_YOUR_TURN: + strID = STR_NOT_YOUR_TURN; + break; + case ERR_NO_PEEK_ROBOT_TILES: + strID = STR_NO_PEEK_ROBOT_TILES; + break; + +#ifndef XWFEATURE_STANDALONE_ONLY + case ERR_NO_PEEK_REMOTE_TILES: + strID = STR_NO_PEEK_REMOTE_TILES; + break; + case ERR_SERVER_DICT_WINS: + strID = STR_SERVER_DICT_WINS; + break; + case ERR_REG_UNEXPECTED_USER: + strID = STR_REG_UNEXPECTED_USER; + break; +#endif + + case ERR_CANT_TRADE_MID_MOVE: + strID = STR_CANT_TRADE_MIDTURN; + break; + + case ERR_TOO_FEW_TILES_LEFT_TO_TRADE: + strID = STR_TOO_FEW_TILES; + break; + default: + XP_DEBUGF( "errcode=%d", id ); + break; + } + + XP_ASSERT( strID < STR_LAST_STRING ); + globals = (PalmAppGlobals*)uc->closure; + userErrorFromStrId( globals, strID ); +} /* palm_util_userError */ + +static void +userErrorFromStrId( PalmAppGlobals* globals, XP_U16 strID ) +{ + XP_UCHAR* message = getResString( globals, strID ); + (void)FrmCustomAlert( XW_ERROR_ALERT_ID, (const char*)message, " ", " " ); +} /* userErrorFromStrId */ + +static XP_U16 +palm_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, XWStreamCtxt* stream ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + XP_U16 strID = STR_LAST_STRING; /* error if not changed */ + + switch( id ) { + case QUERY_COMMIT_TURN: + return askFromStream( globals, stream, -1, false ); + break; + case QUERY_COMMIT_TRADE: + strID = STR_CONFIRM_TRADE; + break; + case QUERY_ROBOT_MOVE: + case QUERY_ROBOT_TRADE: + return askFromStream( globals, stream, STR_ROBOT_TITLE, false ); + break; + default: + XP_ASSERT(0); + break; + } + + return palmaskFromStrId( globals, strID, -1, -1 ); +} /* palm_util_userQuery */ + +static XWBonusType +palm_util_getSquareBonus( XW_UtilCtxt* uc, ModelCtxt* model, + XP_U16 col, XP_U16 row ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + XP_U16 nCols, nRows; + XP_U16 midCol, index, resIndex; + XP_UCHAR* bonusResPtr; + XP_U8 value; + + XP_ASSERT( !!model ); + + nCols = model_numCols( model ); + nRows = model_numRows( model ); + midCol = nCols / 2; + resIndex = (PALM_MAX_COLS - nCols) / 2; + bonusResPtr = globals->bonusResPtr[resIndex]; + + if ( col > midCol ) col = nCols - 1 - col; + if ( row > midCol ) row = nRows - 1 - row; + index = (row*(midCol+1)) + col; + + XP_ASSERT( index/2 < MemPtrSize(bonusResPtr) ); + + value = bonusResPtr[index/2]; + if ( index%2 == 0 ) { + value >>= 4; + } + + return value & 0x0F; +} /* palm_util_getSquareBonus */ + +static XP_S16 +palm_util_userPickTile( XW_UtilCtxt* uc, PickInfo* pi, + XP_U16 playerNum, XP_UCHAR4* texts, XP_U16 nTiles ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + return askBlankValue( globals, playerNum, pi, nTiles, texts ); +} /* palm_util_userPickTile */ + +static XP_Bool +palm_util_askPassword( XW_UtilCtxt* uc, const XP_UCHAR* name, XP_UCHAR* buf, + XP_U16* len ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + return askPassword( globals, name, false, buf, len ); +} /* palm_util_askPassword */ + +static void +palm_util_trayHiddenChange( XW_UtilCtxt* uc, XW_TrayVisState newState ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + palmSetCtrlsForTray( globals ); + + drawFormButtons( globals ); +} /* palm_util_trayHiddenChange */ + +static void +palm_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 oldOffset, XP_U16 newOffset ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + XP_ASSERT( oldOffset != newOffset ); + updateScrollbar( globals, newOffset ); +} /* palm_util_yOffsetChange */ + +static void +palm_util_notifyGameOver( XW_UtilCtxt* uc ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + board_draw( globals->game.board ); /* refresh scoreboard so it agrees + with dialog */ + displayFinalScores( globals ); +} /* palm_util_notifyGameOver */ + +static XP_Bool +palm_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 col, XP_U16 row ) +{ + /* EvtSysEventAvail, not EvtEventAvail, because the former ignores nil + events, and it appears that when there's an IR connection up the + system floods us with nil events.*/ + XP_Bool eventPending = EvtSysEventAvail( true ); + + if ( !eventPending ) { + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + board_hiliteCellAt( globals->game.board, col, row ); + } + + + return !eventPending; +} /* palm_util_hiliteCell */ + +static XP_Bool +palm_util_engineProgressCallback( XW_UtilCtxt* uc ) +{ +#ifdef SHOW_PROGRESS + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + if ( globals->gState.showProgress ) { + RectangleType rect = globals->progress.boundsRect; + short line; + Boolean draw; + + globals->progress.curLine %= rect.extent.y * 2; + draw = globals->progress.curLine < rect.extent.y; + + line = globals->progress.curLine % (rect.extent.y) + 1; + line = rect.topLeft.y + rect.extent.y - line; + if ( draw ) { + WinDrawLine( rect.topLeft.x, line, + rect.topLeft.x + rect.extent.x - 1, line); + } else { + WinEraseLine( rect.topLeft.x, line, + rect.topLeft.x + rect.extent.x - 1, + line ); + } + ++globals->progress.curLine; + } +#endif + return !EvtSysEventAvail( true ); +} /* palm_util_engineProgressCallback */ + +static void +palm_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + if ( why == TIMER_PENDOWN ) { + globals->penTimerFireAt = TimGetTicks() + PALM_TIMER_DELAY; + } else { + globals->timerTimerFireAt = TimGetTicks() + SysTicksPerSecond(); + } +} /* palm_util_setTimer */ + +void +palm_util_requestTime( XW_UtilCtxt* uc ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + globals->timeRequested = true; +} /* palm_util_requestTime */ + +static XP_U32 +palm_util_getCurSeconds( XW_UtilCtxt* uc ) +{ + return TimGetSeconds(); +} /* palm_util_getCurSeconds */ + +static DictionaryCtxt* +palm_util_makeEmptyDict( XW_UtilCtxt* uc ) +{ + DictionaryCtxt* result = palm_dictionary_make( MPPARM(uc->mpool) NULL, NULL ); + XP_ASSERT( !!result ); + return result; +} /* palm_util_makeEmptyDict */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static XWStreamCtxt* +palm_util_makeStreamFromAddr( XW_UtilCtxt* uc, XP_U16 channelNo ) +{ + XWStreamCtxt* stream; + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + + XP_ASSERT( !!globals->game.comms ); /* shouldn't be making stream in case + where can't send -- or should I + just be passing a null on-close + function? */ + XP_LOGF( "making stream for channel %d", channelNo ); + stream = mem_stream_make( MEMPOOL globals->vtMgr, globals, + channelNo, palm_send_on_close ); + return stream; +} /* palm_util_makeStreamFromAddr */ + +#ifdef BEYOND_IR +static void +palm_util_listenPortChange( XW_UtilCtxt* uc, XP_U16 newPort ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + palm_bind_socket( globals, newPort ); +} /* palm_util_getListeningPort */ +#endif +#endif + +static XP_UCHAR* +palm_util_getUserString( XW_UtilCtxt* uc, XP_U16 stringCode ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + XP_UCHAR* str = getResString( globals, stringCode ); + return str; +} /* palm_util_getUserString */ + +static void +formatBadWords( BadWordInfo* bwi, char buf[] ) +{ + XP_U16 i; + + for ( i = 0, buf[0] = '\0'; ; ) { + char wordBuf[18]; + StrPrintF( wordBuf, "\"%s\"", bwi->words[i] ); + StrCat( buf, wordBuf ); + if ( ++i == bwi->nWords ) { + break; + } + StrCat( buf, ", " ); + } +} /* formatBadWords */ + +static XP_Bool +palm_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 turn, + XP_Bool turnLost ) +{ + XP_Bool result = XP_TRUE; + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + XP_UCHAR buf[200]; + char wordsBuf[150]; + XP_UCHAR* format = getResString( globals, STR_ILLEGAL_WORD ); + formatBadWords( bwi, wordsBuf ); + StrPrintF( (char*)buf, (const char*)format, wordsBuf ); + if ( turnLost ) { + (void)FrmCustomAlert( XW_ERROR_ALERT_ID, (const char*)buf, " ", " " ); + } else { + result = palmask( globals, buf, NULL, -1 ); + } + return result; +} /* palm_util_warnIllegalWord */ + +#ifdef SHOW_PROGRESS +static void +palm_util_engineStarting( XW_UtilCtxt* uc ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + if ( globals->gState.showProgress ) { + RectangleType* bounds = &globals->progress.boundsRect; + + WinEraseRectangle( bounds, 0 ); + WinDrawRectangleFrame( rectangleFrame, bounds ); + + globals->progress.curLine = 0; + } +} /* palm_util_engineStarting */ + +static void +palm_util_engineStopping( XW_UtilCtxt* uc ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + if ( globals->gState.showProgress ) { + + WinEraseRectangle( &globals->progress.boundsRect, 0 ); + WinEraseRectangleFrame( rectangleFrame, + &globals->progress.boundsRect ); + + if ( globals->needsScrollbar ) { + SclDrawScrollBar( getActiveObjectPtr( XW_MAIN_SCROLLBAR_ID ) ); + } + } +} /* palm_util_engineStopping */ +#endif diff --git a/xwords4/palm/palmmain.h b/xwords4/palm/palmmain.h new file mode 100644 index 000000000..276cc925f --- /dev/null +++ b/xwords4/palm/palmmain.h @@ -0,0 +1,327 @@ +/* -*-mode: C; fill-column: 76; c-basic-offset: 4; -*- */ +/* + * Copyright 1999 - 2003 by Eric House (fixin@peak.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. + */ +#ifndef _PALMMAIN_H_ +#define _PALMMAIN_H_ + +#define AppType APPID +#define PrefID 0 +#define VERSION_NUM 1 + +#include +#include +#include +#include +#include +#include +#ifdef BEYOND_IR +# include +#endif + +#include "game.h" +#include "util.h" +#include "mempool.h" + +/* #include "prefsdlg.h" */ +#include "xwcolors.h" + +#include "plmtraps.h" /* if DIRECT_PALMOS_CALLS */ +#include "xwords4defines.h" + +#ifdef MEM_DEBUG +# define MEMPOOL globals->mpool, +#else +# define MEMPOOL +#endif + + +enum { ONEBIT, /* GREYSCALE, */COLOR }; +typedef unsigned char GraphicsAbility; /* don't let above be 4 bytes */ +typedef struct PalmAppGlobals PalmAppGlobals; + +typedef XP_UCHAR* (*GetResStringFunc)( PalmAppGlobals* globals, + XP_U16 strID ); +typedef struct PalmDrawCtx { + DrawCtxVTable* vtable; + PalmAppGlobals* globals; + + void (*drawBitmapFunc)( DrawCtx* dc, Int16 resID, Int16 x, Int16 y ); + GetResStringFunc getResStrFunc; + + DrawingPrefs* drawingPrefs; + +#ifdef DIRECT_PALMOS_CALLS + DrawRectFrameProc winDrawRectangleFrameTrap; + FillRectangleProc winFillRectangleTrap; + DrawCharsProc winDrawCharsTrap; +#endif + + RectangleType oldScoreClip; + RectangleType oldTrayClip; + WinHandle numberWin; + + XP_S16 trayOwner; + + GraphicsAbility able; + + union { + struct { +/* IndexedColorType black; */ +/* IndexedColorType white; */ +/* IndexedColorType playerColors[MAX_NUM_PLAYERS]; */ +/* IndexedColorType bonusColors[BONUS_LAST-1]; */ + XP_U8 reserved; /* make CW compiler happy */ + } clr; + struct { + CustomPatternType valuePatterns[4]; + } bnw; + } u; + MPSLOT +} PalmDrawCtx; + +#define draw_drawBitmapAt(dc,id,x,y) \ + (*((((PalmDrawCtx*)dc))->drawBitmapFunc))((dc),(id),(x),(y)) + +typedef struct ListData { + char** strings; + XP_U16 nItems; + XP_U16 nextIndex; + XP_S16 selIndex; +} ListData; + +typedef struct XWords4PreferenceType { + Int16 versionNum; + + Int16 curGameIndex; /* which game is currently open */ + + /* these are true global preferences */ + Boolean showProgress; + Boolean showGrid; + Boolean showColors; +#ifndef DEBUG_OFF + Boolean showDebugstrs; + Boolean windowAvail; + Boolean logToMemo; +#endif + /* New for 0x0405 */ + CommonPrefs cp; + +} XWords4PreferenceType; + +typedef struct MyIrConnect { + IrConnect irCon; + PalmAppGlobals* globals; +} MyIrConnect; + +typedef XP_U8 IR_STATE; /* enums are in palmir.h */ + +#define IR_BUF_SIZE 256 + +typedef struct MyIrPacket MyIrPacket; + +typedef struct ProgressCtxt { + RectangleType boundsRect; + XP_U16 curLine; +} ProgressCtxt; + +/* I *hate* having to define these globally... */ +typedef struct SavedGamesState { + struct PalmAppGlobals* globals; + FormPtr form; + ListPtr gamesList; + FieldPtr nameField; + char** stringPtrs; + Int16 nStrings; + Int16 displayGameIndex; +} SavedGamesState; + +typedef struct PrefsDlgState { + ListPtr playerBdSizeList; + ListPtr phoniesList; + + CommonPrefs cp; + + XP_U16 gameSeconds; + XP_Bool stateTypeIsGlobal; + + XP_U8 phoniesAction; + XP_U8 curBdSize; + XP_Bool showColors; + XP_Bool smartRobot; + XP_Bool showProgress; + XP_Bool showGrid; + XP_Bool hintsNotAllowed; + XP_Bool timerEnabled; +} PrefsDlgState; + +typedef struct DictState { + ListPtr dictList; + ListData sLd; + XP_U16 nDicts; +} DictState; + +typedef struct ConnDlgAddrs { + XP_U32 remoteIP; + XP_U16 remotePort; + XP_U16 localPort; + CommsConnType conType; +} ConnDlgAddrs; + +typedef struct PalmNewGameState { + ListPtr playerNumList; + XP_UCHAR* passwds[MAX_NUM_PLAYERS]; + XP_UCHAR* dictName; + XP_Bool isLocal[MAX_NUM_PLAYERS]; + XP_Bool isRobot[MAX_NUM_PLAYERS]; + XP_UCHAR shortDictName[32]; /* as long as a dict name can be */ + XP_U8 curNPlayersLocal; + XP_U8 curNPlayersTotal; + XP_Bool forwardChange; + Connectedness curServerHilite; +#ifdef BEYOND_IR + ConnDlgAddrs connAddrs; + XP_Bool connsSettingChanged; +#endif +} PalmNewGameState; + +typedef struct PalmDictList PalmDictList; + +#ifdef BEYOND_IR +typedef struct NetLibStuff { + UInt16 netLibRef; + XP_U16 listenPort; + NetSocketRef socketRef; +} NetLibStuff; +#define socketIsOpen(g) ((g)->nlStuff.listenPort != 0) +#endif + +#define MAX_DLG_PARAMS 2 + +struct PalmAppGlobals { + FormPtr mainForm; + PrefsDlgState* prefsDlgState; +#if defined OWNER_HASH || defined NO_REG_REQUIRED + SavedGamesState* savedGamesState; +#endif + XWGame game; + DrawCtx* draw; + XW_UtilCtxt util; + + XP_U32 dlgParams[MAX_DLG_PARAMS]; + + VTableMgr* vtMgr; + + XWords4PreferenceType gState; + + DrawingPrefs drawingPrefs; + + PalmDictList* dictList; + + DmOpenRef boardDBP; + LocalID boardDBID; + + DmOpenRef gamesDBP; + LocalID gamesDBID; + +#ifdef BEYOND_IR + UInt16 exgLibraryRef; /* what library did user choose for sending? */ +#endif + + XP_UCHAR* stringsResPtr; + XP_U8* bonusResPtr[NUM_BOARD_SIZES]; + Boolean penDown; + Boolean isNewGame; + Boolean stateTypeIsGlobal; + Boolean timeRequested; + Boolean hintPending; + Boolean isLefty; + Boolean dictuiForBeaming; + Boolean postponeDraw; + Boolean needsScrollbar; + Boolean msgReceivedDraw; + Boolean isFirstLaunch; + Boolean menuIsDown; + XP_Bool newGameIsNew; + + GraphicsAbility able; + XP_U32 penTimerFireAt; + XP_U32 timerTimerFireAt; + XP_U16 prevScroll; /* for scrolling in 'ask' dialog */ + UInt16 romVersion; + + XP_U8 scrollValue; /* 0..2: scrolled position of board */ + +#ifdef SHOW_PROGRESS + ProgressCtxt progress; +#endif + + CurGameInfo gameInfo; /* for the currently open, or new, game */ + + /* dialog/forms state */ + PalmNewGameState newGameState; + + DictState dictState; + + struct ConnsDlgState* connState; + +#ifdef BEYOND_IR + NetLibStuff nlStuff; +#endif + +#ifdef DEBUG + UInt8 save_rLsap; + IR_STATE ir_state_prev; + XP_U16 yCount; +/* Boolean resetGame; */ +#endif + MPSLOT +}; /* PalmAppGlobals */ + +/* custom events */ +enum { dictSelectedEvent = firstUserEvent /* 0x6000 */ + ,newGameOkEvent + ,newGameCancelEvent + ,loadGameEvent + ,boardRedrawEvt + ,prefsChangedEvent + ,openSavedGameEvent +#ifdef BEYOND_IR + ,connsSettingChgEvent +#endif +}; + +DrawCtx* palm_drawctxt_make( MPFORMAL GraphicsAbility able, + PalmAppGlobals* globals, + GetResStringFunc getRSF, + DrawingPrefs* drawprefs ); +void palm_drawctxt_destroy( DrawCtx* dctx ); + +void palm_warnf( char* format, ... ); + +Boolean askPassword( PalmAppGlobals* globals, const XP_UCHAR* name, + Boolean isNew, XP_UCHAR* retbuf, XP_U16* len ); +Boolean palmaskFromStrId( PalmAppGlobals* globals, XP_U16 strId, + XP_S16 titleID, XP_S16 altID ); +void freeSavedGamesData( MPFORMAL SavedGamesState* state ); + +void palm_util_requestTime( XW_UtilCtxt* uc ); /* so palmir can call */ +void writeNameToGameRecord( PalmAppGlobals* globals, XP_S16 index, + char* newName, XP_U16 len ); + + +#endif /* _PALMMAIN_H_ */ diff --git a/xwords4/palm/palmsavg.c b/xwords4/palm/palmsavg.c new file mode 100644 index 000000000..de45fd3e0 --- /dev/null +++ b/xwords4/palm/palmsavg.c @@ -0,0 +1,293 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/**************************************************************************** + * * + * Copyright 1999, 2001 by Eric House (fixin@peak.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. + ****************************************************************************/ + +#if defined OWNER_HASH || defined NO_REG_REQUIRED + +#include +#include +#include + +#include "callback.h" +#include "strutils.h" +#include "palmsavg.h" +#include "palmmain.h" +#include "palmutil.h" +#include "gameutil.h" +#include "ownerhash.h" + +#include "xwords4defines.h" +#include "LocalizedStrIncludes.h" + +/* Prototypes */ +static void populateGameList( SavedGamesState* state ); +static void setFieldToSelText( SavedGamesState* state ); +static void listDrawFunc(Int16 index, RectanglePtr bounds, char** itemsText); + +#ifndef NO_REG_REQUIRED +Boolean +checkUserName() +{ +#ifdef FOR_GREMLINS + return true; +#else + XP_UCHAR ownerName[dlkMaxUserNameLength + 1]; + Boolean result; + Err err; + + ownerName[0] = '\0'; + /* Try and use the user's name */ + err = DlkGetSyncInfo( NULL, NULL, NULL, ownerName, NULL, NULL ); + XP_ASSERT( err == 0 ); + + result = HASH(ownerName)==((unsigned long)OWNER_HASH); + + return result; +#endif +} /* checkUserName */ +#endif + +/***************************************************************************** + * Handler for dictionary info form. + ****************************************************************************/ +#define USE_POPULATE 1 +Boolean +savedGamesHandleEvent( EventPtr event ) +{ + Boolean result; + PalmAppGlobals* globals; + SavedGamesState* state; + EventType eventToPost; + XP_S16 newGameIndex; + Int16* curGameIndexP; + char* newName; + + CALLBACK_PROLOGUE(); + + result = false; + globals = getFormRefcon(); + state = globals->savedGamesState; + + curGameIndexP = &globals->gState.curGameIndex; + + switch ( event->eType ) { + case frmOpenEvent: + + if ( !state ) { + state = globals->savedGamesState = XP_MALLOC( globals->mpool, + sizeof(*state) ); + } + XP_MEMSET( state, 0, sizeof(*state) ); + + state->globals = globals; + state->form = FrmGetActiveForm(); + + /* dictionary list setup */ + state->gamesList = getActiveObjectPtr( XW_SAVEDGAMES_LIST_ID ); + state->nameField = getActiveObjectPtr( XW_SAVEDGAMES_NAME_FIELD ); + state->displayGameIndex = globals->gState.curGameIndex; + XP_ASSERT( state->displayGameIndex < countGameRecords(globals) ); + + /* must preceed drawing calls so they have a valid window */ + FrmDrawForm( state->form ); + + /* LstSetDrawFunction must follow FrmDrawForm since populateGameList + must be called before listDrawFunc has valid globals; can't let + listDrawFunc get called from FrmDrawForm until set up correctly. */ + LstSetDrawFunction( state->gamesList, listDrawFunc ); + populateGameList( state ); + setFieldToSelText( state ); + break; + + case frmUpdateEvent: + FrmDrawForm( state->form ); + /* don't update field here! The keyboard may have been the form whose + disappearance triggered the frmUpdateEvent, and resetting the field + would undo the user's edits. Also causes a crash in Keyboard.c in + the ROMs for reasons I don't understand. */ + break; + + case lstSelectEvent: + state->displayGameIndex = LstGetSelection( state->gamesList ); + setFieldToSelText( state ); + result = true; + break; + + case ctlSelectEvent: + result = true; + switch ( event->data.ctlEnter.controlID ) { + + case XW_SAVEDGAMES_USE_BUTTON: /* write the new name to the selected + record */ + newName = FldGetTextPtr( state->nameField ); + if ( !!newName && + (*newName != '\0') && + 0 != StrCompare(newName, + state->stringPtrs[state->displayGameIndex])){ + XP_U16 len = FldGetTextLength( state->nameField ); + writeNameToGameRecord( globals, state->displayGameIndex, + newName, len ); + + populateGameList( state ); + setFieldToSelText( state ); + } + break; + + case XW_SAVEDGAMES_DUPE_BUTTON: /* copy the selected record */ + newGameIndex = duplicateGameRecord( globals, + state->displayGameIndex ); + state->displayGameIndex = newGameIndex; + if ( *curGameIndexP >= newGameIndex ) { + ++*curGameIndexP; + } + populateGameList( state ); + setFieldToSelText( state ); + break; + + case XW_SAVEDGAMES_DELETE_BUTTON: /* delete the selected record. + Refuse if it's open. */ + if ( state->displayGameIndex == *curGameIndexP ) { + beep(); + } else if ( palmaskFromStrId( globals, STR_CONFIRM_DEL_GAME, + -1, -1 ) ) { + XP_S16 index = state->displayGameIndex; + deleteGameRecord( globals, index ); + if ( *curGameIndexP > index ) { + --*curGameIndexP; + } + if ( index == countGameRecords(globals) ) { + --index; + } + state->displayGameIndex = index; + populateGameList( state ); + } + break; + + case XW_SAVEDGAMES_OPEN_BUTTON: /* open the selected db if not already + open. */ + if ( *curGameIndexP != state->displayGameIndex ) { + eventToPost.eType = openSavedGameEvent; + ((OpenSavedGameData*)&eventToPost.data.generic)->newGameIndex + = state->displayGameIndex; + EvtAddEventToQueue( &eventToPost ); + globals->postponeDraw = true; + } + + case XW_SAVEDGAMES_DONE_BUTTON: + /* Update the app's idea of which record to save the current game + into -- in case any with lower IDs have been deleted. */ + FrmReturnToForm( 0 ); + + freeSavedGamesData( MPPARM(globals->mpool) state ); + + break; + } + + default: + break; + } /* switch */ + + CALLBACK_EPILOGUE(); + return result; +} /* savedGamesHandleEvent */ + +static void +setFieldToSelText( SavedGamesState* state ) +{ + FieldPtr field = state->nameField; + char name[MAX_GAMENAME_LENGTH]; + nameFromRecord( state->globals, state->displayGameIndex, name ); + XP_ASSERT( XP_STRLEN(name) < MAX_GAMENAME_LENGTH ); + XP_ASSERT( XP_STRLEN(name) > 0 ); + + FldSetSelection( field, 0, FldGetTextLength(field) ); + + FldInsert( field, name, XP_STRLEN(name) ); + FldSetSelection( field, 0, FldGetTextLength(field) ); + FrmSetFocus( state->form, FrmGetObjectIndex(state->form, + XW_SAVEDGAMES_NAME_FIELD) ); + FldDrawField( field ); +} /* setFieldToSelText */ + +void +freeSavedGamesData( MPFORMAL SavedGamesState* state ) +{ + if ( !!state->stringPtrs ) { + XP_FREE( mpool, state->stringPtrs ); + state->stringPtrs = NULL; + } +} /* freeSavedGamesData */ + +static void +populateGameList( SavedGamesState* state ) +{ + XP_U16 nRecords; + PalmAppGlobals* globals = state->globals; + ListPtr gamesList = state->gamesList; + + nRecords = countGameRecords( globals ); + if ( state->nStrings != nRecords ) { + char** stringPtrs; + XP_U16 i; + + LstEraseList( gamesList ); + + freeSavedGamesData( MPPARM(globals->mpool) state ); + + state->nStrings = nRecords; + state->stringPtrs = stringPtrs = + XP_MALLOC( globals->mpool, + (nRecords+1) * sizeof(state->stringPtrs[0] ) ); + + stringPtrs[0] = (char*)globals; + for ( i = 1; i <= nRecords; ++i ) { + stringPtrs[i] = ""; + } + + LstSetListChoices( gamesList, &state->stringPtrs[1], nRecords ); + LstSetHeight( gamesList, XP_MIN(nRecords, 9) ); + } + + LstSetSelection( gamesList, state->displayGameIndex ); + XP_ASSERT( state->displayGameIndex < nRecords ); + LstMakeItemVisible( gamesList, state->displayGameIndex ); + LstDrawList( gamesList ); +} /* populateGameList */ + +static void +listDrawFunc( Int16 index, RectanglePtr bounds, char** itemsText ) +{ + char buf[MAX_GAMENAME_LENGTH]; + XP_U16 len; + PalmAppGlobals* globals; + + CALLBACK_PROLOGUE(); + + globals = (PalmAppGlobals*)itemsText[-1]; + + nameFromRecord( globals, index, buf ); + len = XP_STRLEN( buf ); + XP_ASSERT( len > 0 ); + WinDrawChars( buf, len, bounds->topLeft.x, bounds->topLeft.y ); + + CALLBACK_EPILOGUE(); +} /* listDrawFunc */ + +#endif /* OWNER_HASH */ diff --git a/xwords4/palm/palmsavg.h b/xwords4/palm/palmsavg.h new file mode 100644 index 000000000..d4297e787 --- /dev/null +++ b/xwords4/palm/palmsavg.h @@ -0,0 +1,39 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/**************************************************************************** + * * + * Copyright 1999 - 2001 by Eric House (fixin@peak.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. + ****************************************************************************/ + +#ifndef _PALMSAVG_H_ +#define _PALMSAVG_H_ + +typedef struct OpenSavedGameData { + Int16 newGameIndex; +} OpenSavedGameData; + +Boolean savedGamesHandleEvent( EventPtr event ); + +#if defined NO_REG_REQUIRED +# define checkUserName() XP_TRUE +#elif defined OWNER_HASH + Boolean checkUserName(); +#else +# define checkUserName() XP_FALSE +#endif + +#endif diff --git a/xwords4/palm/palmutil.c b/xwords4/palm/palmutil.c new file mode 100644 index 000000000..eb314ee18 --- /dev/null +++ b/xwords4/palm/palmutil.c @@ -0,0 +1,558 @@ +// -*-mode: C; fill-column: 80; c-basic-offset: 4; -*- +/**************************************************************************** + * * + * Copyright 1998-2001 by Eric House (fixin@peak.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 +#include +#include +#include +#include +#include + +#include "strutils.h" +#include "palmutil.h" +#include "xwords4defines.h" +#include "palmmain.h" +#include "comtypes.h" + +/***************************************************************************** + * This is meant to be replaced by an actual beep.... + ****************************************************************************/ +#if defined FEATURE_BEEP || defined XW_FEATURE_UTILS +void beep( void ) { + SndPlaySystemSound( sndError ); +} /* beep */ +#endif + +/***************************************************************************** + * + ****************************************************************************/ +MemPtr +getActiveObjectPtr( UInt16 objectID ) +{ + FormPtr form = FrmGetActiveForm(); + XP_ASSERT( FrmGetObjectPtr( + form, FrmGetObjectIndex( form, objectID ) )!= NULL ); + return FrmGetObjectPtr( form, FrmGetObjectIndex( form, objectID ) ); +} /* getActiveObjectPtr */ + +/***************************************************************************** + * + ****************************************************************************/ +void +getObjectBounds( UInt16 objectID, RectangleType* rectP ) +{ + FormPtr form = FrmGetActiveForm(); + FrmGetObjectBounds( form, FrmGetObjectIndex( form, objectID ), rectP ); +} /* getObjectBounds */ + +#if defined XW_FEATURE_UTILS +/***************************************************************************** + * + ****************************************************************************/ +void +setObjectBounds( UInt16 objectID, RectangleType* rectP ) +{ + FormPtr form = FrmGetActiveForm(); + FrmSetObjectBounds( form, FrmGetObjectIndex( form, objectID ), rectP ); +} /* getObjectBounds */ + +/***************************************************************************** + * + ****************************************************************************/ +void +setBooleanCtrl( UInt16 objectID, Boolean isSet ) +{ + CtlSetValue( getActiveObjectPtr( objectID ), isSet ); +} /* setBooleanCtrl */ + +void +setFieldEditable( FieldPtr fld, Boolean editable ) +{ + FieldAttrType attrs; + + FldGetAttributes( fld, &attrs ); + if ( attrs.editable != editable ) { + attrs.editable = editable; + FldSetAttributes( fld, &attrs ); + } +} /* setFieldEditable */ + +void +disOrEnable( FormPtr form, UInt16 id, Boolean enable ) +{ + UInt16 index = FrmGetObjectIndex( form, id ); + FormObjectKind typ; + + if ( enable ) { + FrmShowObject( form, index ); + } else { + FrmHideObject( form, index ); + } + + typ = FrmGetObjectType( form, index ); + if ( typ == frmControlObj ) { + ControlPtr ctl = getActiveObjectPtr( id ); + CtlSetEnabled( ctl, enable ); + } +} /* disOrEnable */ + +void +disOrEnableSet( FormPtr form, const UInt16* ids, Boolean enable ) +{ + for ( ; ; ) { + XP_U16 id = *ids++; + if ( !id ) { + break; + } + disOrEnable( form, id, enable ); + } +} /* disOrEnableSet */ + +/* Position a control at the horizontal center of its container. + */ +void +centerControl( FormPtr form, UInt16 id ) +{ + RectangleType cBounds, fBounds; + UInt16 index = FrmGetObjectIndex( form, id ); + + FrmGetObjectBounds( form, index, &cBounds ); + FrmGetFormBounds( form, &fBounds ); + + cBounds.topLeft.x = (fBounds.extent.x - cBounds.extent.x) / 2; + FrmSetObjectBounds( form, index, &cBounds ); +} /* centerButton */ + +/***************************************************************************** + * + ****************************************************************************/ +Boolean +getBooleanCtrl( UInt16 objectID ) +{ + return CtlGetValue( getActiveObjectPtr( objectID ) ); +} /* getBooleanCtrl */ + +/***************************************************************************** + * Set up to build the string and ptr-to-string lists needed for the + * LstSetListChoices system call. + ****************************************************************************/ +void +initListData( MPFORMAL ListData* ld, XP_U16 nItems ) +{ + /* include room for the closure */ + ld->strings = XP_MALLOC( mpool, ((nItems+1) * sizeof(*ld->strings)) ); + + ld->nItems = nItems; + ld->nextIndex = 0; + ld->selIndex = -1; +} /* initListData */ + +void +addListTextItem( MPFORMAL ListData* ld, XP_UCHAR* txt ) +{ + XP_UCHAR* copy = copyString( MPPARM(mpool) txt ); + XP_ASSERT( ld->nextIndex < ld->nItems ); + ld->strings[++ld->nextIndex] = (char*)copy; /* ++ skips 0th for closure */ +} /* addListTextItem */ + +/***************************************************************************** + * Turn the list of offsets into ptrs, free the offsets list, and call + * LstSetListChoices + ****************************************************************************/ +void +setListChoices( ListData* ld, ListPtr list, void* closure ) +{ + ld->strings[0] = closure; + LstSetListChoices( list, &ld->strings[1], ld->nextIndex ); + if ( ld->selIndex >= 0 ) { + LstSetSelection( list, ld->selIndex ); + } +} /* setListChoices */ + +/***************************************************************************** + * Given a string, figure out which item it matches and save off that item's + * index so that at show time it can be used to set the selection. Note that + * anything that changes the order will invalidate this, but also that there's + * no harm in calling it again. + ****************************************************************************/ +void +setListSelection( ListData* ld, char* selName ) +{ + if ( !!selName ) { + XP_U16 i; + for ( i = 0; i < ld->nextIndex; ++i ) { + if ( StrCompare( ld->strings[i+1], selName ) == 0 ) { + ld->selIndex = i; + break; + } + } + } else { + ld->selIndex = 0; + } +} /* setListSelection */ + +/***************************************************************************** + * Meant to be called after all items are added to the list, this function + * sorts them in place. For now I'll build in a call to StrCompare; later + * it could be a callback passed in. + ****************************************************************************/ +void +sortList( ListData* ld ) +{ + XP_S16 i, j, smallest; + char** strings = ld->strings; + char* tmp; + + for ( i = 1; i <= ld->nextIndex; ++i ) { /* skip 0th (closure) slot */ + for ( smallest = i, j = i+1; j <= ld->nextIndex; ++j ) { + if ( StrCompare( strings[smallest], strings[j] ) > 0 ) { + smallest = j; + } + } + + if ( smallest == i ) { /* we got to the end without finding anything */ + break; + } + + tmp = strings[i]; + strings[i] = strings[smallest]; + strings[smallest] = tmp; + } +} /* sortList */ + +/***************************************************************************** + * Dispose the memory. Docs don't say whether LstSetListChoices does this for + * me so I assume not. It'll crash if I'm wrong. :-) + ****************************************************************************/ +void +freeListData( MPFORMAL ListData* ld ) +{ + char** strings = ld->strings; + XP_U16 i; + + for ( i = 0; i < ld->nextIndex; ++i ) { + XP_FREE( mpool, *++strings ); /* skip 0th */ + } + XP_FREE( mpool, ld->strings ); +} /* freeListData */ + +/***************************************************************************** + * + ****************************************************************************/ +void +setSelectorFromList( UInt16 triggerID, ListPtr list, short listSelIndex ) +{ + XP_ASSERT( list != NULL ); + XP_ASSERT( getActiveObjectPtr( triggerID ) != NULL ); + XP_ASSERT( !!LstGetSelectionText( list, listSelIndex ) ); + CtlSetLabel( getActiveObjectPtr( triggerID ), + LstGetSelectionText( list, listSelIndex ) ); +} /* setTriggerFromList */ + +#define MAX_GADGET_COUNT 3 /* the most we need at this point */ +void +sizeGadgetsForStrings( FormPtr form, ListPtr list, XP_U16 firstGadgetID ) +{ + XP_U16 i; + XP_U16 nGadgets = LstGetNumberOfItems( list ); + XP_U16 strWidths[MAX_GADGET_COUNT]; + XP_U16 totalStrWidth, totalGadgetWidth; + XP_U16 left, extra; + RectangleType rects[MAX_GADGET_COUNT]; + RectangleType* rp; + + XP_ASSERT( nGadgets <= MAX_GADGET_COUNT ); + + totalStrWidth = totalGadgetWidth = 0; + for ( i = 0, rp = rects; i < nGadgets; ++i, ++rp ) { + XP_U16 len; + XP_UCHAR* txt = (XP_UCHAR*)LstGetSelectionText( list, i ); + + len = XP_STRLEN( (const char*)txt ); + totalStrWidth += strWidths[i] = FntCharsWidth( (const char*)txt, len ); + + getObjectBounds( firstGadgetID+i, rp ); + totalGadgetWidth += rp->extent.x; + } + + XP_ASSERT( totalGadgetWidth >= totalStrWidth ); + extra = (totalGadgetWidth - totalStrWidth) / nGadgets; + + left = rects[0].topLeft.x; + for ( i = 0, rp = rects; i < nGadgets; ++i, ++rp ) { + UInt16 index; + UInt16 width; + + rp->topLeft.x = left; + rp->extent.x = width = strWidths[i] + extra; + + index = FrmGetObjectIndex( form, firstGadgetID+i ); + FrmSetObjectBounds( form, index, rp ); + + left += width; + } +} /* sizeGadgetsForStrings */ + +XP_Bool +penInGadget( EventPtr event, UInt16* whichGadget ) +{ + UInt16 x = event->screenX; + UInt16 y = event->screenY; + FormPtr form = FrmGetActiveForm(); + UInt16 nObjects, i; + XP_Bool result = XP_FALSE; + + for ( i = 0, nObjects = FrmGetNumberOfObjects(form); i < nObjects; ++i ) { + if ( frmGadgetObj == FrmGetObjectType( form, i ) ) { + UInt16 objId = FrmGetObjectId( form, i ); + if ( objId != REFCON_GADGET_ID ) { + RectangleType rect; + FrmGetObjectBounds( form, i, &rect ); + if ( RctPtInRectangle( x, y, &rect ) ) { + *whichGadget = objId; + result = XP_TRUE; + break; + } + } + } + } + + return result; +} /* penInGadget */ + +void +setFormRefcon( void* refcon ) +{ + UInt16 index; + FormPtr form = FrmGetFormPtr( XW_MAIN_FORM ); + XP_ASSERT( !!form ); + index = FrmGetObjectIndex( form, REFCON_GADGET_ID ); + FrmSetGadgetData( form, index, refcon ); +} /* setFormRefcon */ + +void* +getFormRefcon() +{ + void* result = NULL; + FormPtr form = FrmGetFormPtr( XW_MAIN_FORM ); + XP_ASSERT( !!form ); + if ( !!form ) { + UInt16 index = FrmGetObjectIndex( form, REFCON_GADGET_ID ); + result = FrmGetGadgetData( form, index ); + } + return result; +} /* getFormRefcon */ +#endif + +#if defined FEATURE_REALLOC || defined XW_FEATURE_UTILS +XP_U8* +palm_realloc( XP_U8* in, XP_U16 size ) +{ + MemPtr ptr = (MemPtr)in; + XP_U32 oldsize = MemPtrSize( ptr ); + MemPtr newptr = MemPtrNew( size ); + XP_ASSERT( !!newptr ); + XP_MEMCPY( newptr, ptr, oldsize ); + MemPtrFree( ptr ); + return newptr; +} /* palm_realloc */ + +XP_U16 +palm_snprintf( XP_UCHAR* buf, XP_U16 len, XP_UCHAR* format, ... ) +{ + XP_U16 nChars; + /* PENDING use len to avoid writing too many chars */ + va_list ap; + + va_start( ap, format ); + nChars = StrVPrintF( (char*)buf, (char*)format, ap ); + va_end( ap ); + XP_ASSERT( len >= nChars ); + return nChars; +} /* palm_snprintf */ +#endif + +#ifdef DEBUG +void +palm_warnf( char* format, ... ) +{ + char buf[200]; + va_list ap; + + va_start( ap, format ); + StrVPrintF( buf, format, ap ); + va_end( ap ); + +#ifdef FOR_GREMLINS + /* If gremlins are active, we want all activity to stop here! That + means no cancellation */ + { + FormPtr form; + FieldPtr field; + + form = FrmInitForm( XW_GREMLIN_WARN_FORM_ID ); + + FrmSetActiveForm( form ); + + FrmSetEventHandler( form, doNothing ); + + field = getActiveObjectPtr( XW_GREMLIN_WARN_FIELD_ID ); + FldSetTextPtr( field, buf ); + FldRecalculateField( field, true ); + + FrmDrawForm( form ); +#if 1 + /* This next may freeze X (go to a console to kill POSE), but when + you look at the logs you'll see what event you were on when the + ASSERT fired. Otherwise the events just keep coming and POSE + fills up the log. */ + while(1); +#endif + /* this should NEVER return */ + (void)FrmDoDialog( form ); + } +#else + (void)FrmCustomAlert( XW_ERROR_ALERT_ID, buf, " ", " " ); +#endif +} /* palm_warnf */ + +void +palm_assert( Boolean b, int line, char* fileName ) +{ + if ( !b ) { + XP_LOGF( "ASSERTION FAILED: line %d in %s", line, fileName ); + XP_WARNF( "ASSERTION FAILED: line %d in %s", line, fileName ); + } +} /* palmassert */ + +static void +logToMemo( char* buf ) +{ + const XP_U16 MAX_MEMO_SIZE = 4000; + const XP_U16 MAX_NRECORDS = 200; + DmOpenRef ref; + UInt16 nRecords, index; + UInt16 len = XP_STRLEN( buf ); + UInt16 hSize, slen; + MemHandle hand; + MemPtr ptr; + char tsBuf[20]; + UInt16 tsLen; + DateTimeType dtType; + LocalID dbID; + + TimSecondsToDateTime( TimGetSeconds(), &dtType ); + StrPrintF( tsBuf, "\n%d:%d:%d-", dtType.hour, dtType.minute, + dtType.second ); + tsLen = XP_STRLEN(tsBuf); + + (void)DmCreateDatabase( CARD_0, "MemoDB", 'memo', 'DATA', false ); + dbID = DmFindDatabase( CARD_0, "MemoDB" ); + ref = DmOpenDatabase( CARD_0, dbID, dmModeWrite ); + + nRecords = DmNumRecordsInCategory( ref, dmAllCategories ); + if ( nRecords == 0 ) { + index = dmMaxRecordIndex; + hSize = 0; + hand = DmNewRecord( ref, &index, 1 ); + DmReleaseRecord( ref, index, true ); + } else { + + while ( nRecords > MAX_NRECORDS ) { + index = 0; + DmSeekRecordInCategory( ref, &index, 0, dmSeekForward, + dmAllCategories); + DmRemoveRecord( ref, index ); + --nRecords; + } + + index = 0; + DmSeekRecordInCategory( ref, &index, nRecords, dmSeekForward, + dmAllCategories); + hand = DmGetRecord( ref, index ); + + XP_ASSERT( !!hand ); + hSize = MemHandleSize( hand ) - 1; + ptr = MemHandleLock( hand ); + slen = XP_STRLEN(ptr); + MemHandleUnlock(hand); + + if ( hSize > slen ) { + hSize = slen; + } + (void)DmReleaseRecord( ref, index, false ); + } + + if ( (hSize + len + tsLen) > MAX_MEMO_SIZE ) { + index = dmMaxRecordIndex; + hand = DmNewRecord( ref, &index, len + tsLen + 1 ); + hSize = 0; + } else { + (void)DmResizeRecord( ref, index, len + hSize + tsLen + 1 ); + hand = DmGetRecord( ref, index ); + } + + ptr = MemHandleLock( hand ); + DmWrite( ptr, hSize, tsBuf, tsLen ); + DmWrite( ptr, hSize + tsLen, buf, len + 1 ); + MemHandleUnlock( hand ); + + DmReleaseRecord( ref, index, true ); + DmCloseDatabase( ref ); +} /* logToMemo */ + +void +palm_debugf( char* format, ...) +{ + char buf[200]; + va_list ap; + + va_start( ap, format ); + StrVPrintF( buf, format, ap ); + va_end( ap ); + + logToMemo( buf ); +} /* debugf */ + +/* if -0 isn't passed to compiler it wants to find this!!! */ +void p_ignore(char* s, ...) {} + +#ifdef FOR_GREMLINS +static Boolean +doNothing( EventPtr event ) +{ + return true; +} /* doNothing */ +#endif + +void +palm_logf( char* format, ... ) +{ + char buf[200]; + va_list ap; + + va_start( ap, format ); + StrVPrintF( buf, format, ap ); + va_end( ap ); + + logToMemo( buf ); +} /* palm_logf */ + +#endif /* DEBUG */ diff --git a/xwords4/palm/palmutil.h b/xwords4/palm/palmutil.h new file mode 100644 index 000000000..d6f4ea603 --- /dev/null +++ b/xwords4/palm/palmutil.h @@ -0,0 +1,76 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/**************************************************************************** + * * + * Copyright 1998-1999, 2001 by Eric House (fixin@peak.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. + ****************************************************************************/ + +#ifndef _PALMUTIL_H_ +#define _PALMUTIL_H_ + +#include +#include +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include "xwdefines.h" */ +/* #include "xwords.h" */ +/* #include "xwdebug.h" */ + +#include "palmmain.h" + +/* short myMemCmp( unsigned char* src1, unsigned char* src2, short size ); */ +/* void userError( CharPtr* str ); */ +/* void userErrorRes( short resId ); */ +/* void userErrorResS( short resId, CharPtr data ); */ +void beep( void ); + +MemPtr getActiveObjectPtr( UInt16 objectID ); +void getObjectBounds( UInt16 objectID, RectangleType* rectP ); +void setObjectBounds( UInt16 objectID, RectangleType* rectP ); + +void disOrEnable( FormPtr form, UInt16 id, Boolean enable ); +void disOrEnableSet( FormPtr form, const UInt16* id, Boolean enable ); + +void centerControl( FormPtr form, UInt16 id ); + +void setBooleanCtrl( UInt16 objectID, Boolean isSet ); +Boolean getBooleanCtrl( UInt16 objectID ); + +void setFieldEditable( FieldPtr fld, Boolean editable ); + +/* list item stuff */ +void initListData( MPFORMAL ListData* ld, XP_U16 nItems ); +void addListTextItem( MPFORMAL ListData* ld, XP_UCHAR* txt ); +void setListChoices( ListData* ld, ListPtr list, void* closure ); +void setListSelection( ListData* ld, char* selName ); +void sortList( ListData* ld ); +void freeListData( MPFORMAL ListData* ld ); + +/* this should work on either trigger or selector */ +void setSelectorFromList( UInt16 selectorID, ListPtr list, + short listSelIndex ); + +void sizeGadgetsForStrings( FormPtr form, ListPtr list, XP_U16 firstGadgetID ); + +XP_Bool penInGadget( EventPtr event, UInt16* whichGadget ); + +void setFormRefcon( void* refcon ); +void* getFormRefcon(); + +#endif