/* -*-mode: C; c-basic-offset: 4; fill-column: 76 -*- */
/* 
 * Copyright 1997 - 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.
 */
 
////////////////////////////////////////////////////////////////////////////////
// This is an editor for two types of database records (resources) in the
// Crosswords application: Xbrd, the description of the special scoring squares,
// and Xloc, which describes what the tile faces, numbers, and values are.
//
// There are two main forms, one for each of the resource types, that share
// the same menu.  The application keeps a shadow copy of each resource (created
// on demand) in its own database mainly so that state can be preserved across
// app swaps other than by writing back into the Crosswords app.  There are
// also save and revert menuitems that write the local copy into Crosswords
// and read it from Crosswords, respectively.
//
// So on formOpen time, a form's handler reads in the Crosswords resource and
// creates the shadow copy in the database.  It then creates an onscreen view
// into that "model."  Edits in the view are reflected in the model immediately,
// but are only written out when the user saves.  Whether or not to prompt the
// user to save on stopApplication events needs to be determined.
////////////////////////////////////////////////////////////////////////////////

#include <PalmTypes.h>
#include <Window.h>
#include <Event.h>
#include <MemoryMgr.h>
#include <FeatureMgr.h>
#include <DataMgr.h>
#include <CharAttr.h>
#include <SystemMgr.h>
#include <Menu.h>
#include <Form.h>
#include <StringMgr.h>

#include "xwconfig.h"
/* #include "../xwcommon.h" */
#include "xwconfigdefines.h"
#include "../xwords4defines.h"
#include "colorprf.h"
#include "palmutil.h"
#include "callback.h"

#define XWConfigAppType 'XWcf'
#define XWConfigPrefID 0
#define XWConfigVersionNum 1
#define XW_DB_NAME XW_PREFS_DBNAME
#define ASSERT(b)

typedef Int32 Int ;
typedef  char* CharPtr;
typedef UInt32 DWord;
typedef void* Ptr;

#define CELL_WIDTH 9
/* #define gDBName "XWConfig Database" */
/* #define APPID 'XWcf' */

typedef unsigned short LetterEntry;

#ifdef DEBUG_OFF
  #define MEMHANDLELOCK(h)       MemHandleLock(h)
  #define MEMHANDLEUNLOCK(h)     MemHandleUnlock(h)
  #define MEMPTRUNLOCK(p)	   MemPtrUnlock(p)

#else

#define MEMHANDLELOCK( h ) _MEMHANDLELOCK( (h), __LINE__ )
MemPtr _MEMHANDLELOCK( MemHandle h, short line ) {
    return MemHandleLock(h);
}

#define MEMHANDLEUNLOCK( h ) _MEMHANDLEUNLOCK( (h), __LINE__ )
void _MEMHANDLEUNLOCK( MemHandle h, short line ) {
    MemHandleUnlock(h);
}

#define MEMPTRUNLOCK(p)		_MEMPTRUNLOCK( (p), __LINE__ )
void _MEMPTRUNLOCK( MemPtr p, short line ) {
    MemPtrUnlock(p);
}

#endif

// prototypes
static Boolean startApplication();
static void eventLoop();
static void stopApplication();
static Boolean boardFormHandleEvent( EventPtr event );
static Boolean editLetterFormHandleEvent( EventPtr event );
static Boolean applicationHandleEvent( EventPtr event );
/* static void setDBResourceName( MemPtr* ptr, CharPtr newstr ); */
//static void askAndSaveIfDirty( void );
//static MemPtr loadOrCopyXWordsResource( UInt3232 resType, Int32 resID );
static void drawBoard( void );
static void clearBoard();
static unsigned char* skipName( unsigned char* start );
//static void drawTilesList( void );
static void userError( Char* str );
static void setModelSquares( Int16 x, Int16 y, Char result );
static void pointToBoard( Int16 x, Int16 y, Int16* col, Int16* row );
static Char getBoardTile( Int16 col, Int16 row );
static Boolean confirm( Char* str );
static void chosenToSelector( ListPtr list, UInt16 chosen, char* chosenName );

/* static Boolean confirmS( CharPtr format, CharPtr str ); */
/* static MemHandle openDBResource( UInt32 resType, Int32 resID, UInt size ); */
/* static Xloc_specialEntry* specialEntryForChr( Char ch ); */
static Boolean XWordsExists();
/* static MemHandle Munger( MemHandle hand, UInt32 offset, UInt32 destLen,  */
/* 			void* srcBytes, UInt32 srcLen, Boolean inDbHeap ); */
/* static void tilesNameFieldToRsrc( MemPtr* globalResPtr ); */
/* static void boardNameFieldToRsrc( MemPtr* globalResPtr ); */
static MemPtr copyResFromXWords( UInt32 resType, Int32 resID );
static void writeResToXWords( UInt32 resType, UInt16 resID, MemPtr resPtr );
static void closeResPtr( MemPtr* ptr );
static void closeResHand( MemHandle* hand );
static void setPair( Int16 col, Int16 row, Char result );

static void tryEgg( char key );
//////////////////////////////// GLOBAL VARS ////////////////////////////////
static XWConfigPreferenceType gState;
unsigned char* gBoardResPtr[NUM_BOARD_SIZES] = {NULL,NULL,NULL};
static unsigned char* gTilesResPtr = NULL;
/* static unsigned char* gTilesInfoPtr = NULL; */

/* static ListPtr gList = NULL; */
/* static short gNumChars = 0; */
/* static short gLangLen; */
static Boolean gClosing = false;
Boolean gBoardDirty = false;
Boolean gColorsDirty = false;
Boolean gDictDirty = false;
static Boolean gDoesColor = false;
// ref to our database -- since I can't seem to modify the app itself 
static DmOpenRef gDBRef = NULL;
/* static RectangleType gBoardRect = { {13, 20}, {CELL_WIDTH*15,CELL_WIDTH*15}}; */
static RectangleType gBoardRect = { {1, 25}, {CELL_WIDTH*15,CELL_WIDTH*15}};
ColorSelState gColorSelState;

#ifdef DO_DICTS
static Boolean tilesFormHandleEvent( EventPtr event );
static ListPtr buildTilesList( ListPtr list );
static unsigned char* findBase( unsigned char* resStart );
static Boolean changeNumtilesOK( unsigned char* resPtr );
static void warnCantChangeTiles( void );
static void handleDelete();
static Boolean validateLetterChanges( UInt16 sSelItem, unsigned short result );
static LetterEntry uniqueLetterTemplate( short* newLoc );
static void sortTiles();
static void incrXlocHeader( MemPtr resPtr, signed short incr );
#endif

/**********************************************************************
 * PilotMain
 **********************************************************************/
DWord 
PilotMain( UInt16 cmd, Ptr cmdPBP, UInt16 launchFlags) 
{
    if ( cmd == sysAppLaunchCmdNormalLaunch ) {
	// Initialize the application's global variables and database.
	if ( !startApplication() ) {

	    eventLoop();
	    stopApplication();
	}
    }
    return 0;
} /* PilotMain */

/**********************************************************************
 * eventLoop
 **********************************************************************/
static void eventLoop() {
    EventType event;
    UInt16 error;
	
    do {
	// Get the next available event.
	EvtGetEvent(&event, evtWaitForever);
		
	// Give the system a chance to handle the event.
	if ( !SysHandleEvent(&event)) {
	    if ( !MenuHandleEvent(0, &event, &error)) {
		if ( !applicationHandleEvent(&event)) {
		    FrmDispatchEvent(&event);
		}
	    }
	}
    } while (event.eType != appStopEvent);
}

/**********************************************************************
 * applicationHandleEvent
 **********************************************************************/
static Boolean 
applicationHandleEvent( EventPtr event ) 
{
    FormPtr frm;
    Int32 formId;
    Boolean result;

    CALLBACK_PROLOGUE();

    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);

	/*Set the event handler for the form.  The handler of the
	  currently active form is called by FrmDispatchEvent each time it
	  receives an event. */
	switch (formId)	{
	case XWC_BOARD_FORM:
	    FrmSetEventHandler( frm, boardFormHandleEvent );
	    gState.fLastOpenForm = XWC_BOARD_FORM;
	    result = true;
	    break;
	case XWC_COLORPREF_DIALOG_ID:
	    FrmSetEventHandler( frm, colorPrefHandleEvent );
	    gState.fLastOpenForm = XWC_COLORPREF_DIALOG_ID;
	    result = true;
	    break;

#ifdef DO_DICTS
	case XWC_TILES_FORM:
	    FrmSetEventHandler( frm, tilesFormHandleEvent );
	    gState.fLastOpenForm = XWC_TILES_FORM;
	    result = true;
	    break;
#endif
	case XWC_EDIT_LETTER_FORM:
	    FrmSetEventHandler( frm, editLetterFormHandleEvent );
	    result = true;
	}
	break;
    default:
    }

    CALLBACK_EPILOGUE();

    return result;
} /* applicationHandleEvent */

/*****************************************************************************
 * updateFieldFromStr
 ****************************************************************************/
#if 0
static void
updateFieldFromStr( UInt16 fieldID, CharPtr s )
{
    MemHandle boardNameHand;
    FieldPtr fieldP;

    boardNameHand = MemHandleNew( StrLen( s ) + 1 );
    MemMove( MEMHANDLELOCK( boardNameHand ), s, StrLen(s) + 1 );
    MEMHANDLEUNLOCK( boardNameHand );
    fieldP = getActiveObjectPtr( fieldID );
    FldSetTextHandle( fieldP, (Handle)boardNameHand );
    FldDrawField( fieldP );
} // updateFieldFromStr
#endif

/*****************************************************************************
 *
 ****************************************************************************/
/*  static CharPtr getStrFromField( UInt16 fieldID ) { */
/*      FieldPtr fieldP = getActiveObjectPtr( fieldID ); */
/*      return */
/*  } // getStrFromField */

/*****************************************************************************
 * boardFormHandleEvent
 *
 * Editing of the board is simple: a popup menu can be pulled down on any
 * of the squares in the 15x15 matrix.  It lists five choices corresponding
 * to the four bonus squares plus "empty", with (I hope) the current one
 * highlighted in some way.  When the user chooses one of the five it is
 * reflected into the model in up to eight places so that symmetry across
 * horizontal, vertical and diagonal axes is preserved.  Then a redraw pushes
 * these changes into the view.
 ****************************************************************************/
static Boolean
boardFormHandleEvent( EventPtr event ) 
{
    static ListPtr sSizesList = NULL;
    static char* chosenName = NULL;
    Int16 chosen;
    Boolean result = false;

    CALLBACK_PROLOGUE();

    switch( event->eType ) {
    case frmOpenEvent:
	sSizesList = getActiveObjectPtr( XWC_BOARD_SIZE_LIST_ID );
	chosenName = MemPtrNew( 3 );
	chosenName[2] = '\0';

    case frmUpdateEvent:
	chosen = (PALM_MAX_ROWS - gState.fCurBoardSize) / 2;
	chosenToSelector( sSizesList, chosen, chosenName );

	ASSERT( gBoardResPtr[chosen] != NULL );
	FrmDrawForm( FrmGetActiveForm() );
	drawBoard();

	result = true;
	break;

    case penDownEvent: {
	Int16 x = event->screenX;
	Int16 y = event->screenY;
	if ( RctPtInRectangle( x, y, &gBoardRect ) ) {
	    Int16 result;
	    Char tile;
	    Int16 col, row;
	    ListPtr pList = getActiveObjectPtr( XWC_BOARD_LIST_ID );
	    ASSERT( pList != NULL );

	    LstSetPosition( pList, x, y );
	    pointToBoard( x, y, &col, &row );
	    tile = getBoardTile( col, row ) - BONUS_NONE;
	    LstSetSelection( pList, tile );
	
	    result = LstPopupList( pList );
	    LstEraseList( pList );
	    if ( result >= 0 && result != tile ) {
		// there's a selection and a change
		setModelSquares( x, y, result );
	    }
	    /* in case it erased the buttons */
	    FrmDrawForm( FrmGetActiveForm() );
	    drawBoard();
	}
    }
    break;

    case ctlSelectEvent:
	switch ( event->data.ctlEnter.controlID ) {

	case XWC_BOARD_SIZE_TRIGGER_ID:
	    chosen = LstPopupList( sSizesList );
	    if ( chosen >= 0 ) {
		gState.fCurBoardSize = PALM_MAX_ROWS - (chosen*2);
		WinEraseWindow();
		FrmUpdateForm( XWC_BOARD_FORM, frmRedrawUpdateCode );
	    }
	    break;

	case XWC_BOARD_CLEAR_BUTTON_ID:
	  if ( confirm("Do you want to remove all bonus squares?") ) {
	      clearBoard();
	      drawBoard();
	  }
	  break;

	case XWC_BOARD_REVERT_BUTTON_ID:
	    if ( confirm("Do you want to revert to the board saved in "
			 "Crosswords?") ) {
	        chosen = (PALM_MAX_ROWS - gState.fCurBoardSize) / 2;
		MemPtrFree( gBoardResPtr[chosen] );
		gBoardResPtr[chosen] = (unsigned char*)copyResFromXWords(
		    BOARD_RES_TYPE, BASE_BOARD_ID+chosen );
		drawBoard();
		gBoardDirty = false;
	    }
	  break;
	}
	break;

    case keyDownEvent:
        tryEgg( event->data.keyDown.chr );
        break;

    case menuEvent:
	result = handleMenu( event->data.menu.itemID );
	break;
    default:
    }

    CALLBACK_EPILOGUE();

    return result;
} // boardFormHandleEvent

static void
chosenToSelector( ListPtr list, UInt16 chosen, char* chosenName )
{
    char* fullName = LstGetSelectionText( list, chosen );
    *(short*)chosenName = *(short*)fullName; /* use the '15' of '15x15' */
    CtlSetLabel( getActiveObjectPtr(XWC_BOARD_SIZE_TRIGGER_ID), chosenName );
} /* chosenToSelector */

/*****************************************************************************
 *
 ****************************************************************************/
#ifdef DO_DICTS
static void
setPopupItem( Int listID, Int triggerID, short sel ) 
{ 
    ListPtr list = getActiveObjectPtr( listID );
    ControlPtr trigger = getActiveObjectPtr( triggerID );
    LstSetSelection( list, sel );
    CtlSetLabel( trigger, LstGetSelectionText( list, sel ) );
}
#endif

/*****************************************************************************
 * Handler for the letter editor form that's called up within the tiles
 * editor.
 ****************************************************************************/
static Boolean
editLetterFormHandleEvent( EventPtr event ) 
{
/*     static LetterEntry sLetterInfo; */
/*     static FieldPtr sField; */
/*     static UInt16 sSelItem; */

    Boolean result = false;
#if 0
    switch( event->eType ) {
    case frmOpenEvent: {
	MemHandle letterH;
	CharPtr letterP;
	UChar tile;

	FrmDrawForm( FrmGetActiveForm() );

	sSelItem = LstGetSelection( gList );
	sLetterInfo = ((LetterEntry*)gTilesInfoPtr)[A_TILE+sSelItem];
	tile = sLetterInfo & 0x00FF;
	sField = getActiveObjectPtr( XWC_LETTER_LETTERFIELD_ID );
	ASSERT( sField!=NULL );
	letterH = MemHandleNew( 4 );
	letterP = (CharPtr)MEMHANDLELOCK(letterH);
	if ( tile >= 0x20 ) {
	    StrPrintF( letterP, "%c\0", tile );
	} else {
	    ASSERT( StrLen(specialEntryForChr(tile)->textVersion) <= 3 );
	    StrCopy( letterP, specialEntryForChr(tile)->textVersion );
	}
	MEMHANDLEUNLOCK( letterH );
	FldSetTextHandle( sField, (Handle)letterH );

	FldDrawField( sField );
	FldSetUsable( sField, changeNumtilesOK(gTilesResPtr) );

	setPopupItem( XWC_NUMBER_LIST_ID, XWC_NUMBER_TRIGGER_ID, 
		      (sLetterInfo>>12&0x000F) - 1 ); // a_tile??
	setPopupItem( XWC_VALUE_LIST_ID, XWC_VALUE_TRIGGER_ID,
		      sLetterInfo>>8&0x000F );
    }
    break;

    case ctlSelectEvent:
	switch ( event->data.ctlEnter.controlID ) {
	case XWC_LETTER_OK_BUTTON_ID: {
	    Char ch;
	    UInt16 number, value;
	    LetterEntry result;

	    ch = FldGetTextPtr( sField )[0];
	    if ( ch == '\0' || ch == ' ' ) {
		userError( "need to get the popup's value here." );
		//ch = BLANK_FACE;
	    }
	    number = LstGetSelection(
		getActiveObjectPtr(XWC_NUMBER_LIST_ID) ) + 1;
	    ASSERT( number != -1 );
	    value = LstGetSelection(
		getActiveObjectPtr(XWC_VALUE_LIST_ID) );
	    ASSERT( value != -1 );
	    result = ch & 0x00FF;
	    result |= ((((number<<12)&0xF000) | ((value<<8)&0x0F00)) & 0xFF00);

	    if ( result != sLetterInfo ) {
		if ( !validateLetterChanges( sSelItem, result ) ) {
		     break;
		}
		// if there's been a change, confirm that it breaks none of the
		// invariants, and if not then update the "model" and do a
		// reload of the list.  Just rebuild the whole thing, as that's
		// easier to write/maintain and speed be damned for this app.
		DmWrite( gTilesResPtr, 
			 gLangLen + sizeof( Xloc_header )
			 + sizeof(LetterEntry)*(A_TILE+sSelItem), 
			 &result, 2 );
		// If the letter's been changed, sort so that it winds up in the
		// right place.
/*  		if ( (result&0x00FF) != (sLetterInfo&0x00FF) ) { */
/*  		    sortTiles(); */
/*  		} */

		gList = buildTilesList( gList );
		FrmUpdateForm( XWC_TILES_FORM, frmRedrawUpdateCode );
	    }
	}
	case XWC_LETTER_CANCEL_BUTTON_ID:
	    FrmReturnToForm( 0 );
	    break;
	}
    }
#endif
    return result;
} // editLetterFormHandleEvent 

/******************************************************************************
 * checkLetterIsUnique
 * Ensure that the letter isn't repeated elsewhere.
 ******************************************************************************/
#ifdef DO_DICTS
static Boolean
checkLetterIsUnique( UInt16 currentSel, LetterEntry candidate ) 
{
    short i;
    Char ch = candidate & 0x00FF;
    for ( i = A_TILE; i < gNumChars; ++i ) {
	if ( i-A_TILE != currentSel ) {
	    LetterEntry entry = ((LetterEntry*)gTilesInfoPtr)[i];
	    if ( (entry&0x00FF) == ch ) {
		if ( (entry&0x0F00) != (candidate&0x0F00) ) {
		    return false;
		}
	    }
	}
    }
    return true;
} // checkLetterIsUnique
#endif
/******************************************************************************
 * validateLetterChanges
 * Check that the proposed change doesn't mean there are more tiles than we
 * can handle or more blanks.
 ******************************************************************************/
#ifdef DO_DICTS
static Boolean
validateLetterChanges( UInt16 currentSel, LetterEntry result ) 
{
    short tileCount = 0;
    short blankCount = 0;
    Char ch[2] = {0,0};
    Char errBuf[100];

    short i;
    Boolean letterIsUnique;

    for ( i = A_TILE; i < gNumChars; ++i ) {
	LetterEntry val;
	if ( i == currentSel+1 ) {//a_tile??
	    val = result;
	} else {
	    val = *(LetterEntry*)&gTilesInfoPtr[i*2];
	}
	if ( (val & 0x00FF) == BLANK_FACE ) {
	    blankCount += (val>>12)&0x000F;
	}
	tileCount += (val>>12)&0x000F;
    }

    ch[0]=result&0x00FF;
    letterIsUnique = checkLetterIsUnique( currentSel, result );
    if ( !letterIsUnique ) {
	StrPrintF( errBuf, "You already have an entry for letter %s and "
		   "its value differs from this one's.", 
		   ch[0]==BLANK_FACE?"<BLANK>":ch );
	userError( errBuf );
    } else if ( blankCount > MAX_NUM_BLANKS ) {
	StrPrintF( errBuf, "Only %d blank tiles supported; you have %d.",
		    MAX_NUM_BLANKS, blankCount );
	userError( errBuf );
    } else if ( tileCount > MAX_NUM_TILES ) {
	// I don't think this can happen any longer...
	ASSERT(false);
	StrPrintF( errBuf, "There can be up to %d total tiles in Crosswords;"
		   "you have a total of %d.", MAX_NUM_TILES, tileCount );
	userError( errBuf );
    }
    return letterIsUnique && (blankCount <= MAX_NUM_BLANKS)
	&& (tileCount <= MAX_NUM_TILES);
} // validateLetterChanges
#endif

/*****************************************************************************
 * sameNameExists
 * Loop until out of resources or until find identical one.
 * If it has the same name but different contents, just warn the user.
 * Not sure how to recover from that for now.  Perhaps should copy it over
 * still but modify the name?
 ****************************************************************************/
#if 0
static Boolean
sameNameExists( Char* name, UInt32 resType, DmOpenRef destRef ) 
{
    Boolean result = false;
    Int indexType;

    for ( indexType = 0; result==false; ++indexType  ) {
	Int index = DmFindResourceType( destRef, resType, indexType );
	if ( index == -1 ) {
	    break;
	} else {
	    MemHandle hand = DmGetResourceIndex( destRef, index );
	    CharPtr ptr = MEMHANDLELOCK(hand);
	    ASSERT( hand!=NULL );
	    if ( StrCompare( name, ptr ) == 0 ) {
		if ( MemPtrSize(name) == MemPtrSize(ptr)
		     && ( MemCmp( name, ptr, MemPtrSize(name) ) == 0 ) ) {
		    result = true;
		} else {
		    userError( "Resources with same name differ in content." );
		}
	    }
	    closeResHand( &hand );
/*  	    MEMHANDLEUNLOCK( hand ); */
/*  	    DmReleaseResource( hand ); */
	}
    }
    return result;
} // sameNameExists
#endif

/*****************************************************************************
 * findUnusedID
 ****************************************************************************/
#if 0
static Int
findUnusedID( DmOpenRef dbP, UInt32 resType, Int start ) 
{
    for ( ; ; ++start ) {
	if ( DmFindResource( dbP, resType, start, NULL ) == -1 ) {
	    // resource doesn't exist.  Got it!
	    return start;
	}
    }
    ASSERT( false );
    return -1;
} // findUnusedID
#endif

/*****************************************************************************
 * copyResFromTo 
 *
 * Copy contents of hand into a new resource of type resType created in
 * destRef
 ****************************************************************************/
#if 0
static Int
copyResFromTo( MemHandle hand, DmOpenRef destRef, UInt32 resType ) 
{
    Int resID;
    Err err;
    MemPtr destP;
    UInt32 size = MemHandleSize(hand);
    MemHandle newHand = NULL;

    resID = findUnusedID( destRef, resType, 1000 );
    newHand = DmNewResource( destRef, resType, resID, size );
    ASSERT( newHand != NULL );

    destP = MEMHANDLELOCK( newHand );
    err = DmWrite( destP, 0, MEMHANDLELOCK( hand ), size );
    ASSERT( err==0 );
    closeResHand( &newHand );
/*      MEMHANDLEUNLOCK( newHand ); */
/*      ASSERT( MemHandleLockCount( newHand ) == 0 ); */
/*      DmReleaseResource( newHand ); */

    MEMHANDLEUNLOCK( hand );

    return resID;
} // copyResFromTo
#endif

/*****************************************************************************
 * readAllFromTo
 ****************************************************************************/
#if 0
static void
readAllFromTo( UInt32 resType, DmOpenRef srcRef, DmOpenRef destRef )
{
    Int indexType;

    for ( indexType = 0; ; ++indexType  ) {
	Int index = DmFindResourceType( srcRef, resType, indexType );
	if ( index == -1 ) {
	    break;
	} else {
	    MemHandle hand = DmGetResourceIndex( srcRef, index );
	    ASSERT( hand!=NULL );
	    if ( !sameNameExists( MEMHANDLELOCK(hand), resType, destRef ) ) {
		(void)copyResFromTo( hand, destRef, resType );
	    }
	    MEMHANDLEUNLOCK( hand );
	    ASSERT( MemHandleLockCount( hand ) == 0 );
	}
    }
} // readAllFromTo
#endif

/*****************************************************************************
 * syncDBWithApp
 * Since I'm read-only *and* want to be where uncorruptible versions of
 * boards and languages live, on startup I copy all my language and board
 * resources into the database.  Reading in from Crosswords will augment
 * this set further.
 ****************************************************************************/
#if 0
static void
syncDBWithApp() 
{
    LocalID dbID;
    DmOpenRef appRef = NULL;
    DmOpenRef dbRef = NULL;

    Err err = DmCreateDatabase( 0, gDBName, APPID, 'Xwcd', true );
    if ( err == dmErrAlreadyExists ) {
	// if the database already exists, leave
    } else if ( err != 0 ) {
	ASSERT( false );
    } else {
	dbID = DmFindDatabase( 0, gDBName );
	ASSERT( dbID != 0 );
	dbRef = DmOpenDatabase( 0, dbID, dmModeReadWrite );

	dbID = DmFindDatabase( 0, "XWConfig" );
	ASSERT( dbID != 0 );
	appRef = DmOpenDatabase( 0, dbID, dmModeReadOnly );

	readAllFromTo( BOARD_RES_TYPE, appRef, dbRef );
#ifdef DO_DICTS
	readAllFromTo( TILES_RES_TYPE, appRef, dbRef );
#endif
	DmCloseDatabase( appRef );
	DmCloseDatabase( dbRef );
    }
} // syncDBWithApp
#endif

/*****************************************************************************
 * syncNameField
 * update the name field from the current resource ptr -- in case it's changed.
 ****************************************************************************/
#ifdef DO_DICTS
static void
syncNameField( MemPtr resPtr ) 
{
    FieldPtr langNameField;
    MemHandle langHand = MemHandleNew( StrLen( gTilesResPtr ) + 1 );
    MemMove( MEMHANDLELOCK( langHand ), resPtr, 
	     StrLen( resPtr ) + 1 );
    MEMHANDLEUNLOCK( langHand );
/*     langNameField = getActiveObjectPtr( XWC_BOTH_NAMEFIELD_ID ); */
    FldSetTextHandle( langNameField, (Handle)langHand );
    FldDrawField( langNameField );
} // syncNameField
#endif

/*****************************************************************************
 * tilesFormHandleEvent
 ****************************************************************************/
#ifdef DO_DICTS
static Boolean
tilesFormHandleEvent( EventPtr event ) 
{
    Boolean result = false;

    CALLBACK_PROLOGUE();

    switch( event->eType ) {
    case frmOpenEvent: {
	// if there's already a resource in our database, use it; otherwise 
	// read in from Crosswords.
	FieldPtr langNameField;
	MemHandle langHand;
	MemHandle hand;

	setDebugForm( FrmGetActiveForm()  );

	hand = openDBResource( TILES_RES_TYPE, gState.fLangID, 0 );
	ASSERT( hand != NULL );
	if ( false ) {//hand == NULL ) {
	    gTilesResPtr = (unsigned char*)copyResFromXWords(
		TILES_RES_TYPE, gState.fLangID );
	} else {
	    gTilesResPtr = (unsigned char*)MEMHANDLELOCK( hand );
	}
	ASSERT( gTilesResPtr != NULL );
	ASSERT( MemHandleLockCount( MemPtrRecoverHandle(gTilesResPtr) ) == 1 );
	gTilesInfoPtr = findBase( gTilesResPtr );

	FrmDrawForm( FrmGetActiveForm() );
	gList = buildTilesList( getActiveObjectPtr( XWC_TILES_LIST_ID ) );
	LstDrawList( gList );

	syncNameField( gTilesResPtr );

/*  	langHand = MemHandleNew( StrLen( gTilesResPtr ) + 1 ); */
/*  	MemMove( MEMHANDLELOCK( langHand ), gTilesResPtr,  */
/*  		 StrLen( gTilesResPtr ) + 1 ); */
/*  	MEMHANDLEUNLOCK( langHand ); */
/*  	langNameField = getActiveObjectPtr( XWC_BOTH_NAMEFIELD_ID ); */
/*  	FldSetTextHandle( langNameField, (Handle)langHand ); */
/*  	FldDrawField( langNameField ); */
	result = true;
    }
    break;

    case frmUpdateEvent:
	if ( gList != NULL ) {
	    LstEraseList( gList );
	    LstDrawList( gList );
	}
	syncNameField( gTilesResPtr );
	result = true;
	break;

    case frmCloseEvent:
	gList = NULL;
	break;
 
    case menuEvent:
	result = handleMenu( event->data.menu.itemID );
	gTilesInfoPtr = findBase( gTilesResPtr );
	break;

    case ctlSelectEvent:
	switch ( event->data.ctlEnter.controlID ) {
	case XWC_TILES_EDIT_BUTTON_ID:
	    FrmPopupForm( XWC_EDIT_LETTER_FORM );
	    result = true;
	    break;

	case XWC_TILES_ADD_BUTTON_ID:
	    // check that we won't be over the limit (assuming at least one of
	    // the new tile), then insert an entry in the resource immediately
	    // after the current selection and select it and open the letters
	    // form on it.
	    if ( !changeNumtilesOK(gTilesResPtr) ){
		warnCantChangeTiles();
	    } else if ( gNumChars >= MAX_UNIQUE_TILES ) {
		userError( "You're already at the 31 tile limit." );
	    } else {
		LetterEntry template = 0x1141; // 1 'A' worth 1
		MemHandle resHand = MemPtrRecoverHandle( gTilesResPtr );
		Err err;
		short newLoc;
		short offset;
		short bytesToMove;

		template = uniqueLetterTemplate( &newLoc );

		newLoc = LstGetSelection(gList)+A_TILE+1; // 1 places it *after*
		offset = newLoc * sizeof(template);
		offset += gLangLen + sizeof( Xloc_header );

/*  		if ( newLoc > gNumChars ) { */
/*  		    newLoc = gNumChars; */
/*  		} */
/*  		ASSERT( newLoc <= gNumChars ); */
		//offset = (newLoc + A_TILE) * sizeof(template) + gLangLen;

		MEMHANDLEUNLOCK( resHand );

/*  		resHand = DmResizeResource(  */
/*  		    resHand, */
/*  		    (gNumChars+A_TILE)*sizeof(unsigned short) + gLangLen); */
/*  		gTilesResPtr = MEMHANDLELOCK( resHand ); */
/*  		ASSERT( MemHandleLockCount( resHand ) == 1 ); */
/*  		gTilesInfoPtr = findBase( gTilesResPtr ); */

		// first, make room at the current selection
/*  		bytesToMove =  */
/*  		    MemHandleSize(resHand) - offset - sizeof(unsigned short); */
/*  		ASSERT( bytesToMove >= 0 ); */

		resHand = Munger( resHand, offset, 0, &template,
				  sizeof(template), true );
		gTilesResPtr = MemHandleLock( resHand );

		// update the XLOC header
		incrXlocHeader( gTilesResPtr, sizeof(template) );
		gTilesInfoPtr = findBase( gTilesResPtr );

		gList = buildTilesList( gList );
		LstSetSelection( gList, newLoc );
		FrmUpdateForm( XWC_TILES_FORM, frmRedrawUpdateCode );
	    }
	    break;
	case XWC_TILES_DELETE_BUTTON_ID:
	    handleDelete();
	    break;
	}
	break;
    }

    CALLBACK_EPILOGUE();

    return result;
} // tilesFormHandleEvent
#endif

/*****************************************************************************
 * To keep the world safe for dictionaries, only those tile sets that aren't
 * "offical" may be edited beyond changing the number and value of the tiles.
 * Attempts to add or delete tiles or to change their faces are flagged here.
 *
 * The language is "official", and so uneditable, if the bit is set.
 ****************************************************************************/
#ifdef DO_DICTS
static Boolean
changeNumtilesOK( unsigned char* resPtr ) 
{
    Boolean result =
	(((Xloc_header*)skipName(resPtr))->langCodeFlags & XLOC_LANG_MASK) == 0;
    return result;
} // changeNumtilesOK
#endif

/*****************************************************************************
 * put up an alert.
 ****************************************************************************/
#ifdef DO_DICTS
static void
warnCantChangeTiles( void ) 
{
    userError( "You can't change the number of tiles or the face character "
	       "of an \"official\" Crosswords tile set.  Please clone "
	       "this tile set first." );
}
#endif

/*****************************************************************************
 * factored out the delete code from tilesFormHandleEvent
 ****************************************************************************/
#ifdef DO_DICTS
static void
handleDelete() 
{
    // confirm, then remove the current selection from the resource.
    if ( !changeNumtilesOK(gTilesResPtr) ){
	warnCantChangeTiles();
    } else if ( confirm( "Do you really want to delete the selected entry?") ){
	Err err;
	MemHandle resHand;
	unsigned char start;
	short prevSel = LstGetSelection(gList);
	short offset = (prevSel+A_TILE) * sizeof(LetterEntry);
	offset += gLangLen + sizeof( Xloc_header );

	resHand = MemPtrRecoverHandle( gTilesResPtr );
	MEMHANDLEUNLOCK( resHand );
	resHand = Munger( resHand, offset, sizeof(LetterEntry),
			  NULL, 0, true );

	gTilesResPtr = MEMHANDLELOCK( resHand );
	ASSERT( MemHandleLockCount( resHand ) == 1 );

	// update the XLOC header
	incrXlocHeader( gTilesResPtr, 0-sizeof(LetterEntry) );
	gTilesInfoPtr = findBase( gTilesResPtr );
	gList = buildTilesList( gList );
	LstSetSelection( gList, prevSel );
	FrmUpdateForm( XWC_TILES_FORM, frmRedrawUpdateCode );
    } // XWC_TILES_DELETE_BUTTON_ID
} // handleDelete
#endif

/*****************************************************************************
 * Change the offset to the beginning of the special chars, either up or down.
 * Since the size of the handle doesn't change we can ignore the return value
 * of Munger.
 ****************************************************************************/
#ifdef DO_DICTS
static void
incrXlocHeader( MemPtr resPtr, signed short incr ) 
{
    short offset;
    unsigned char start;

    offset = skipName(resPtr) - (unsigned char*)resPtr;
    start = ((Xloc_header*)&((unsigned char*)resPtr)[offset])->specialCharStart;
    start += incr;
    (void)Munger( MemPtrRecoverHandle(resPtr), offset, sizeof(start),
		      &start, sizeof(start), true );
} // incrXlocHeader
#endif

/*****************************************************************************
 * like the name says
 ****************************************************************************/
#ifdef DO_DICTS
static void
setIsOfficialFlag( MemPtr resPtr, char newVal) 
{
    short offset;
    unsigned char flags;
    unsigned char* flagsP;

    flagsP = &(((Xloc_header*)skipName(resPtr))->langCodeFlags);
    flags = *flagsP;
    ASSERT( (newVal==1) || (newVal == 0) );
    flags = (flags & ~XLOC_LANG_MASK) | (newVal << XLOC_LANG_OFFSET);
    offset = flagsP - (unsigned char*)resPtr;
    (void)Munger( MemPtrRecoverHandle(resPtr), offset, sizeof(flags),
		      &flags, sizeof(flags), true );
} // setIsOfficialFlag
#endif

/******************************************************************************
 * sortTiles
 ******************************************************************************/
#ifdef DO_DICTS
static void
sortTiles() 
{
    LetterEntry* tmpPtr;
    short numWritten, i;
    Byte ch;

    tmpPtr = (LetterEntry*)MemPtrNew( MemPtrSize(gTilesResPtr) - gLangLen );
	    
    // This is a bogus sort algorithm.  Sue me.
    // For each possible tile value starting at 0 (blank) see if there's
    // one there and if so write it into the tmp buffer.  When the
    // number written is sufficient, we're done.
    numWritten = A_TILE;
    for ( ch = 0; (ch != 0xFF) && (numWritten <= gNumChars); ++ch ) {
	for ( i = A_TILE; i < gNumChars; ++i ) {
	    if ( ch == gTilesInfoPtr[i*2+1] ) {
		tmpPtr[numWritten++] = 
		    *(LetterEntry*)&gTilesInfoPtr[i*2];
		break;
	    }
	}
    }

    if ( numWritten == gNumChars ) {
	Err err = DmWrite( gTilesResPtr, gLangLen, tmpPtr, 
			   (gNumChars+A_TILE)*2 );
	ASSERT( err == 0 );
    } else {
	Char buf[100];
	StrPrintF( buf, "numWritten: %d; gNumChars: %d",
		   numWritten, gNumChars );
	userError( buf );
    }

    MemPtrFree( tmpPtr );
    gList = buildTilesList( gList );
    FrmUpdateForm( XWC_TILES_FORM, frmRedrawUpdateCode );
} // sortTiles
#endif

/*****************************************************************************
 * buildTilesList
 ****************************************************************************/
#ifdef DO_DICTS
static ListPtr
buildTilesList( ListPtr list ) 
{
    static MemHandle stringsH = NULL;
    static MemHandle stringListH = NULL;
    static Char* format = "letter %s; count: %d; value: %d";
    Char letterString[4];
    UInt16 numItems;
    Char* stringsP;
    Char** stringsA;
    short i;

    if ( stringListH != NULL ) {
	MemHandleFree( stringListH );
    }
    if ( stringsH != NULL ) {
	MemHandleFree( stringsH );
    }

    ASSERT( gTilesResPtr!=NULL );
    ASSERT( A_TILE == 0 );

/*     gNumChars =  */
/* 	((MemPtrSize(gTilesResPtr) - gLangLen) */
/* 	 / BYTES_PER_LETTER) - A_TILE; */

    stringListH = MemHandleNew( gNumChars * sizeof(Char*) );
    stringsH = MemHandleNew( gNumChars * (StrLen(format) + 5) );
    stringsP = MEMHANDLELOCK( stringsH );
    stringsA = MEMHANDLELOCK( stringListH );

    for ( i = A_TILE; i < gNumChars; ++i ) {
	UInt16 numWritten;
	short value = (gTilesInfoPtr[i*2])&0x0F;
	UChar tile = gTilesInfoPtr[i*2+1];
	if ( tile >= 0x20 ) {
	    letterString[0] = tile;
	    letterString[1] = '\0';
	} else {
	    ASSERT( StrLen(specialEntryForChr(tile)->textVersion) <= 3 );
	    StrCopy( letterString, specialEntryForChr(tile)->textVersion );
	}
	numWritten = StrPrintF( 
	    stringsP, format, 
	    //letterString[0] == BLANK_FACE? "<BLANK>":
	    letterString,
	    (gTilesInfoPtr[i*2]>>4)&0x0F, // count,
	    value );
	stringsA[i-A_TILE] = stringsP;
	stringsP += numWritten + 1; // 1 for the null byte
    }

    LstSetListChoices( list, stringsA, gNumChars );
    return list;
} // buildTilesList
#endif

/*****************************************************************************
 * Return a pointer to the appropriate "special entry" record.
 ****************************************************************************/
#ifdef DO_DICTS
static Xloc_specialEntry* 
specialEntryForChr( Char ch )
{
    Xloc_specialEntry* spEntries = (Xloc_specialEntry*)
	(&gTilesInfoPtr[gNumChars*BYTES_PER_LETTER]);
    ASSERT( ch < 0x20 );
    ASSERT( (long)(&spEntries[ch]) < 
	    ((long)gTilesResPtr) + MemPtrSize(gTilesResPtr) );
    return &spEntries[ch];
} // specialEntryForChr
#endif

/*****************************************************************************
 * uniqueLetterTemplate
 ****************************************************************************/
#ifdef DO_DICTS
static LetterEntry
uniqueLetterTemplate( short* newLoc ) 
{
    LetterEntry result;
    Char newch;
    short i;
    for ( newch = 'A'; ; ) {
    outer:
/* 	StrPrintF( buf, "looking at %c", newch ); */
/* 	userError(buf); */
	for ( i = A_TILE; i < gNumChars; ++i ) {
	    if ( gTilesInfoPtr[i*2+1] == newch ) {
		// it's not unique; start over
		++newch;
		goto outer;
	    }
	}
	// we got here:  it's unique!!
	*newLoc = newch-'A'+1; // 1 is to put it beyond existing; not "A_TILE"
	result = 0x1100 | newch;
	return result;
    }
    ASSERT( false );
    *newLoc = 1;    
    return 0x1146;
} // uniqueLetterTemplate
#endif

/*****************************************************************************
 * boardNameFieldToRsrc
 ****************************************************************************/
#if 0
static void
boardNameFieldToRsrc( MemPtr* globalResPtr ) {
    FieldPtr fieldP;
    ASSERT( FrmGetFormId( FrmGetActiveForm() ) == XWC_BOARD_FORM );
    fieldP = getActiveObjectPtr( XWC_BOTH_NAMEFIELD_ID );
    setDBResourceName( globalResPtr,  FldGetTextPtr( fieldP ) );
} // boardNameFieldToRsrc
#endif

/*****************************************************************************
 * tilesNameFieldToRsrc
 ****************************************************************************/
#ifdef DO_DICTS
static void
tilesNameFieldToRsrc( MemPtr* globalResPtr )
{
    CharPtr fldText;
    ASSERT( FrmGetFormId( FrmGetActiveForm() ) == XWC_TILES_FORM );
    fldText = FldGetTextPtr( getActiveObjectPtr( XWC_BOTH_NAMEFIELD_ID ) );
    setDBResourceName( globalResPtr, fldText );

/*      if ( StrCompare( fldText, gTilesResPtr ) != 0 ) { */
/*  	short textLen = StrLen( fldText ); */
/*  	MemHandle resHand = MemPtrRecoverHandle( gTilesResPtr ); */

/*  	MEMHANDLEUNLOCK( resHand ); */

/*  	resHand = Munger( resHand, 0, gLangLen, fldText, textLen+1, true ); */
/*  	// now we need to call it again to insert a pad byte -- if the new */
/*  	// language name plus null byte is an odd number of bytes long. */
/*  	if ( textLen % 2 == 0 ) { */
/*  	    Char nullByte = '\0'; */
/*  	    resHand = Munger( resHand, textLen+1, 0, &nullByte, 1, true ); */
/*  	} */

/*  	gTilesResPtr = MEMHANDLELOCK( resHand ); */
/*  	ASSERT( MemHandleLockCount( resHand ) == 1 ); */
/*  	gTilesInfoPtr = findBase( gTilesResPtr ); */
/*      } */
} // tilesNameFieldToRsrc
#endif

/*****************************************************************************
 * Munger -- inspired by the MacOS function of the same name.  Takes an
 * unlocked handle to a resource or regular heap block and returns a new
 * unlocked handle with bytes replaced.
 ****************************************************************************/
#if 0
static MemHandle
Munger( MemHandle hand, UInt32 offset, UInt32 destLen, 
	void* srcBytes, UInt32 srcLen, Boolean inDbHeap ) 
{
    UInt32 handleSize = MemHandleSize( hand );
    Boolean needsMoving = destLen != srcLen;

    // first, remove the bytes that'll be overwritten: write what's above
    // them down.
    ASSERT( destLen >= 0 );
    if ( needsMoving && destLen > 0 ) {
	Byte* ptr = MEMHANDLELOCK( hand );
	if ( inDbHeap ) {
	    Err err = DmWrite( ptr, offset, ptr+offset+destLen, 
			       handleSize - offset - destLen );
	    ASSERT( err == 0 );
	} else {
	    ASSERT( false );//not done yet
	}
	MEMHANDLEUNLOCK( hand );
	handleSize -= destLen;
	if ( inDbHeap ) {
	    ASSERT( MemHandleLockCount(hand) == 0 );
	    ASSERT( handleSize >= 0 );
	    DmResizeResource( hand, handleSize );
	} else {
	    ASSERT( false );
	    MemHandleResize( hand, handleSize );
	}
    }
    // then write the new ones in
    if ( srcLen > 0 ) {
	Byte* ptr;
	UInt32 oldHandleSize = handleSize;
	if ( needsMoving ) {
	    handleSize += srcLen;
	    if ( inDbHeap ) {
		ASSERT( MemHandleLockCount(hand) == 0 );
		ASSERT( handleSize >= 0 );
		DmResizeResource( hand, handleSize );
	    } else {
		ASSERT( false );
		MemHandleResize( hand, handleSize );
	    }
	}
	ptr = MEMHANDLELOCK( hand );
	if ( inDbHeap ) {
	    Err err;
	    if( needsMoving ) {
		err= DmWrite( ptr, offset+srcLen, ptr+offset,
			       oldHandleSize-offset );
		ASSERT( err == 0 );
	    }
	    err = DmWrite( ptr, offset, srcBytes, srcLen );
	    ASSERT( err == 0 );
	} else {
	    ASSERT( false );
	}
	MEMHANDLEUNLOCK( hand );
    }
    return hand;
} // Munger
#endif

/*****************************************************************************
 * chooselanguage
 *
 * If there's more than one resource of the given type, put up a dialog showing
 * the language strings of all Xloc resources available in our own database or
 * in Crosswords.  Set the default to be what's pointed to by langID == unless
 * it's 0.  If the user clicks OK, set *langID to the resource ID of the
 * selection.  Otherwise leave it alone.  Return true if OK, false if Cancel
 * tapped.
 * 
 * Called from both openLang code and installLang code.
 ****************************************************************************/
#define MAX_TILE_SETS 100
#if 0
static Boolean chooseLanguage( CharPtr newLabel, UInt32 resType, Int* langID ) {
    Boolean result = false;
    FormPtr form = NULL, prevForm = NULL;
    ListPtr rulesList;
    CharPtr* strPtrs;
    Int* resIDs;
    Int index = 0, savedIndex = 0;
    Int indexType;

    strPtrs = (CharPtr*)MemPtrNew( MAX_TILE_SETS * sizeof(Char*) );
    resIDs = (Int*)MemPtrNew( MAX_TILE_SETS * sizeof(UInt32) );
    for ( indexType = 0; ; ++indexType  ) {
	index = DmFindResourceType( gDBRef, resType, indexType );
	if ( index == -1 ) {
	    break;
	} else {
	    MemHandle hand = DmGetResourceIndex( gDBRef, index );
	    DmResID resID;
	    Err err;
	    // we'll be locking one of these a second time: the one that's
	    // currently open
	    strPtrs[indexType] = MEMHANDLELOCK( hand );

	    err = DmResourceInfo( gDBRef, index, NULL, &resID, NULL );
	    ASSERT( err==0 );
	    resIDs[indexType] = resID;
	    if ( resID == *langID ) {
		savedIndex = indexType;
	    }
	}
    }

    if ( indexType > 1 ) { // if there's any choice to be made
	prevForm = FrmGetActiveForm();
	form = FrmInitForm( XWC_CHOOSELANG_DIALOG_ID );
	FrmSetActiveForm( form );
	FrmDrawForm( form );

	FrmCopyLabel( form, XWC_CHOOSELANG_LABEL_ID, newLabel );

	rulesList = getActiveObjectPtr( XWC_CHOOSELANG_LIST_ID );
	ASSERT( rulesList != NULL );
	LstSetListChoices( rulesList, strPtrs, indexType );
	LstSetHeight( rulesList, min( indexType, 7 ) );
	LstSetSelection( rulesList, savedIndex );
	LstDrawList( rulesList );

	if ( FrmDoDialog( form ) != XWC_CHOOSELANG_CANCEL_BUTTON_ID ) {
	    UInt16 sel = LstGetSelection( rulesList );
	    *langID = resIDs[sel];
	    result = true;
	} else {
	    result = false;
	}
    } else if ( indexType == 1 ) {
	result = true;
	*langID = resIDs[0];	
    }

    MemPtrFree( resIDs );

    FrmDeleteForm( form );
    FrmSetActiveForm( prevForm );

    while ( indexType-- ) {
	closeResPtr( (MemPtr*)&strPtrs[indexType] );
/*  	MemHandle hand = MemPtrRecoverHandle( strPtrs[indexType] ); */
/*  	MEMHANDLEUNLOCK( hand ); */
/*  	DmReleaseResource( hand ); */
    }

    return result;
} // chooseLanguage
#endif
/*****************************************************************************
 * setDBResourceName
 * replace the null-terminated string at the beginning fo the resource
 * with a new one.
 * I'd *better* be able to unlock this puppy....
 ****************************************************************************/
#if 0
static void
setDBResourceName( MemPtr* ptr, CharPtr newstr ) 
{
    UInt32 oldlen = StrLen( *ptr );
    UInt32 newlen = StrLen( newstr );
    MemHandle newHand = MemPtrRecoverHandle( *ptr );
/*     unsigned long testBytes = *(unsigned long*)skipName(*ptr); */
    MEMHANDLEUNLOCK( newHand );
    ASSERT( MemHandleLockCount(newHand) == 0 );
    newHand = Munger( newHand, 0, oldlen, newstr, newlen, true );

    // now IFF the new string and old aren't both either odd or even
    // numbers of bytes in length adjust the pad byte.
    if ( (newlen % 2) != (oldlen % 2) ) {
	UChar nullByte = '\0';
	if ( (newlen % 2) == 0 ) { // we need to add a pad byte
	    newHand = Munger( newHand, newlen+1, 0, &nullByte, 1, true );    
	} else if ( (oldlen % 2) == 0 ) { // need to remove a pad byte
	    newHand = Munger( newHand, newlen+1, 1, &nullByte, 0, true );    
	} else {
	    ASSERT( false );
	}
    }

    *ptr = MEMHANDLELOCK( newHand );
    ASSERT( testBytes == *(unsigned long*)skipName(*ptr) );
} // setDBResourceName
#endif

/*****************************************************************************
 * tilesFormHandleEvent
 ****************************************************************************/
Boolean 
handleMenu( UInt16 itemID ) 
{
    Boolean result = true;

    if ( gState.fLastOpenForm == itemID ) {
	return true;
    }

    switch ( itemID ) {
    case XWC_BOARD_PULLDOWN:
	if ( gState.fLastOpenForm != XWC_BOARD_FORM ) {
/* 	    tilesNameFieldToRsrc( globalResPtr ); */
/* 	    if ( chooseLanguage( "Open which:", BOARD_RES_TYPE, */
/* 				 &gState.fBoardID ) ) { */
/* 	    closeResPtr( globalResPtr); */
	    FrmGotoForm( XWC_BOARD_FORM );
	}
	break;

    case XWC_COLORS_PULLDOWN:
	if ( !gDoesColor ) {
	    userError( "Not a color device!!!" );
	} else {
/* 	    closeResPtr( globalResPtr); */
	    FrmGotoForm( XWC_COLORPREF_DIALOG_ID );
	}
	break;

#ifdef DO_DICTS
    case XWC_TILES_PULLDOWN:
	if ( gState.fLastOpenForm != XWC_TILES_FORM ) {
	    
	    if ( chooseLanguage( "Open which:", TILES_RES_TYPE,
				 &gState.fLangID ) ) {
/* 		closeResPtr( globalResPtr); */
/*  		MEMPTRUNLOCK( *globalResPtr ); */
/*  		DmReleaseResource( MemPtrRecoverHandle( *globalResPtr ) ); */
		FrmGotoForm( XWC_TILES_FORM );
	    }
	}
	break;
#else
    case XWC_DICTS_PULLDOWN:
      userError( "Not yet implemented" );
      break;
#endif


    // copy the currently open resource into an available slot, giving
    // it a new name.
#if 0
    case XWC_CLONE_PULLDOWN: {
	MemHandle thisHand = MemPtrRecoverHandle( *globalResPtr );
	Int newID = copyResFromTo( thisHand, gDBRef, resType );

	closeResPtr( globalResPtr );
/*  	MEMPTRUNLOCK( *globalResPtr ); */
/*  	DmReleaseResource( MemPtrRecoverHandle( *globalResPtr ) ); */

	*resIDP = newID;
	*globalResPtr = 
	    MEMHANDLELOCK( openDBResource( resType, newID, 0 ) );
	setDBResourceName( globalResPtr, "New Resource" );
	setIsOfficialFlag( *globalResPtr, 0 );
	if ( resType == BOARD_RES_TYPE ) {
	    updateFieldFromStr( XWC_BOTH_NAMEFIELD_ID, *globalResPtr );
	    drawBoard();
	} else {
	    ASSERT( gList != NULL );
	    gList = buildTilesList( gList );
	    FrmUpdateForm( XWC_TILES_FORM, frmRedrawUpdateCode );
	}
    }
#endif
    break;

#if 0
    case XWC_DELETE_PULLDOWN: {
	Int newID = *resIDP;
	if ( chooseLanguage( "Delete which:", resType, &newID ) ) {
	    if ( newID == *resIDP ) {
		userError( "You can't delete the open resource." );
	    } else {
		CharPtr cp;
		Boolean okToNuke;
		Int index = DmFindResource( gDBRef, resType, newID, NULL );
		ASSERT( index != -1 );
		cp = MemHandleLock( DmGetResourceIndex( gDBRef, index ) );
		okToNuke = confirmS( "Are you sure you want to "
				     "delete the resource named "
				     "\"%s\"?", cp );
		closeResPtr( (MemPtr*)&cp );
		DmRemoveResource( gDBRef, index );
	    }
	}
    }
    break;
#endif

/*     case XWC_REVERT_PULLDOWN: */
/* 	if ( confirm( "Are you sure you want to revert?\n" */
/* 		      "Changes will be lost" ) ) { */
/* 	    MemPtrUnlock( *globalResPtr ); */
/* 	    DmReleaseResource( MemPtrRecoverHandle( *globalResPtr ) ); */
/* 	    *globalResPtr = copyResFromXWords( resType, *resID ); */
/* 	    if ( resType == BOARD_RES_TYPE ) { */
/* 		drawBoard(); */
/* 	    } else { */
/* 		ASSERT( gList != NULL ); */
/* 		gList = buildTilesList( gList ); */
/* 		FrmUpdateForm( XWC_TILES_FORM, frmRedrawUpdateCode ); */
/* 	    } */
/* 	} */
/* 	break; */

#if 0
    case XWC_WRITE_PULLDOWN: 
	if ( confirm( "Are you sure you want to modify Crosswords?" ) ) {
#ifdef DO_DICTS
	    if ( resType == TILES_RES_TYPE ) {
/* 		tilesNameFieldToRsrc( globalResPtr ); */
	    } else
#endif
		if ( resType == BOARD_RES_TYPE ) {
		boardNameFieldToRsrc( globalResPtr );
	    }
	    writeResToXWords( resType, 1000, *globalResPtr );
	}
	break;
#endif
    case XWC_ABOUT_PULLDOWN:
	(void)confirm( "XWConfig, a "
		       "configuration program for Crosswords 4 (for which see "
		       "www.peak.org/~fixin/xwords).\n"
		       "Copyright 1998-2001 by Eric House." );
	break;

    default:
	result = false;
    }
    return result;
} // handleMenu

static UInt16 
romVersion( void )
{
    UInt32 dwOSVer;

    FtrGet(sysFtrCreator, sysFtrNumROMVersion, &dwOSVer );
    /* should turn 3 and 5 into 35 */
    return (sysGetROMVerMajor(dwOSVer)*10) + sysGetROMVerMinor(dwOSVer);
} /* romVersion */

static UInt32
cur_screen_depth( void )
{
    UInt32 curDepth;

    WinScreenMode( winScreenModeGet, 0, 0, &curDepth, 0 );
    return curDepth;
} /* cur_screen_depth */

static void
loadColorsFromRsrc( DrawingPrefs* prefs )
{
    UInt8* colorP = (unsigned char*)copyResFromXWords( COLORS_RES_TYPE,
						       COLORS_RES_ID );
    UInt8* base = colorP;
    RGBColorType color;
    short index = 0;
    UInt32 count = MemPtrSize( colorP );
        
    do {
	color.r = *colorP++;
	color.g = *colorP++;
	color.b = *colorP++;
	prefs->drawColors[index++] = WinRGBToIndex( &color );
    } while ( (count -= 3) != 0 );

    MemPtrFree( base );
} /* loadColorsFromRsrc */

static void
writeColorsToRsrc( DrawingPrefs* prefs )
{
    UInt8* colorP = (unsigned char*)copyResFromXWords( COLORS_RES_TYPE,
						       COLORS_RES_ID );
    UInt8* base = colorP;
    UInt32 count = MemPtrSize( colorP );
    RGBColorType color;
    short index = 0;

    do { 
	WinIndexToRGB( prefs->drawColors[index++], &color );
	*colorP++ = color.r;
	*colorP++ = color.g;
	*colorP++ = color.b;
    } while ( (count -= 3) > 0 );
    writeResToXWords( COLORS_RES_TYPE, COLORS_RES_ID, base );
    MemPtrFree( base );
} /* writeColorsToRsrc */

/**********************************************************************
 * startApplication
 **********************************************************************/
static Boolean
startApplication(void) 
{
    UInt16 prefSize, i;
    XWConfigPreferenceType prefs;

    if ( !XWordsExists() ) {
	userError( "Can't find Crosswords database.  You must have "
		   "run Crosswords 4 at least once before using this app." );
	return true; 
    }

    gDoesColor = (romVersion() >= 35) && (cur_screen_depth() >= 8);

    prefSize = sizeof( prefs );
    if ( (PrefGetAppPreferences( XWConfigAppType, XWConfigPrefID, &prefs, 
				 &prefSize, true ) == XWConfigVersionNum )
	 && (prefSize == sizeof(prefs)) ) {
	MemMove( &gState, &prefs, sizeof(prefs) );
    } else {
	gState.fLastOpenForm = XWC_BOARD_FORM;
	gState.fCurBoardSize = PALM_MAX_ROWS;
	gState.fLangID = 1000;
    }

    /* load up the resources we'll be editing.  We'll simply write them
       back out at quit time. */
    if ( gDoesColor ) {
	loadColorsFromRsrc( &gColorSelState.drawingPrefs );
    }
    for ( i = 0; i < NUM_BOARD_SIZES; ++i ) {
	gBoardResPtr[i] = (unsigned char*)copyResFromXWords(
	    BOARD_RES_TYPE, BASE_BOARD_ID+i );
    }

/*     syncDBWithApp(); */

/*     dbID = DmFindDatabase( 0, "XWConfig" ); */
/*     ASSERT( dbID != 0 ); */
/*     gAppRef = DmOpenDatabase( 0, dbID, dmModeReadOnly ); */

    FrmGotoForm( gState.fLastOpenForm );
    return false;
} /* startApplication */

//////////////////////////////////////////////////////////////////////////////
// stopApplication
//////////////////////////////////////////////////////////////////////////////
static void
stopApplication( void ) 
{
    UInt16 i;
    Boolean saveChanges = (gBoardDirty || gColorsDirty || gDictDirty)
	&& confirm( "Do you want to save your changes to Crosswords?" );
    
    gClosing = true;
    FrmCloseAllForms();

    // Write the state information.
    PrefSetAppPreferences( XWConfigAppType, XWConfigPrefID,
			   XWConfigVersionNum, &gState, sizeof(gState), true );

    if ( gBoardDirty ) {

	if ( saveChanges ) {

	    for ( i = 0; i < NUM_BOARD_SIZES; ++i ) {
		if ( !!gBoardResPtr[i] ) {
		    writeResToXWords( BOARD_RES_TYPE, BASE_BOARD_ID+i,
				      gBoardResPtr[i] );
		    MemPtrFree( gBoardResPtr[i] );
		}
	    }
	}

    }
    if ( gTilesResPtr != NULL ) {
	closeResPtr( (MemPtr*)&gTilesResPtr );
    }

    if ( gColorsDirty ) {
	writeColorsToRsrc( &gColorSelState.drawingPrefs );
    }

    if ( gDBRef != NULL ) {
	DmCloseDatabase( gDBRef );
    }
} // stopApplication

/*****************************************************************************
 * openXWordsRes
 *
 * Open a resource inside Crosswords.  NOTE that callers need to follow up
 * quickly with a call to DmCloseDatabase to get Crosswords off the head of
 * the open DB list.
 ****************************************************************************/
static MemHandle 
openXWordsRes( UInt32 resType, UInt16 resId, UInt32 mode, DmOpenRef* dbref ) 
{
    DmOpenRef XWordsRef;
    Int index;
    LocalID dbID = DmFindDatabase( 0, XW_DB_NAME );
    MemHandle appResHand;

    if ( dbID == 0 ) {
	userError( "Crosswords setting database not found." );
	return NULL;
    }
    XWordsRef = DmOpenDatabase( 0, dbID, mode );
    ASSERT( XWordsRef != 0 );
    index = DmFindResource( XWordsRef, resType, resId, NULL );
    ASSERT( index>=0 );
    appResHand = DmGetResourceIndex( XWordsRef, index );
    ASSERT( appResHand!= NULL );
    *dbref = XWordsRef;
    return appResHand;
} // openXWordsRes

/******************************************************************************
 * XWordsExists
 ******************************************************************************/
static Boolean
XWordsExists() 
{
    return DmFindDatabase( 0, XW_PREFS_DBNAME ) != 0;
} /* XWordsExists */

/*****************************************************************************
 * copyResFromXWords 
 *
 * Return a ptr to (writable) memory containing a copy of what's in XWords'
 * corresponding resource.
 ****************************************************************************/
static MemPtr
copyResFromXWords( UInt32 resType, Int resID ) 
{
    MemPtr result = NULL;
    MemHandle appResHand;
    UInt32 size;
    DmOpenRef XWordsRef;

    appResHand = openXWordsRes( resType, resID, dmModeReadOnly, &XWordsRef );
    if ( appResHand == NULL ) {
	return NULL;
    }
    size = MemHandleSize( appResHand );

/*     localH = openDBResource( resType, resID, size ); */
    result = MemPtrNew( size );

    // now read the data in and return the ptr to the resource.
    /*     err = DmWrite( result, 0, MEMHANDLELOCK(appResHand), size ); */
    MemMove( result, MEMHANDLELOCK(appResHand), size );
    ASSERT( err == 0 );
    closeResHand( &appResHand );
    DmCloseDatabase( XWordsRef );

    return result;
} // copyResFromXWords

/*****************************************************************************
 * Find the appropriate resource in this app's database.  The size parameter
 * should be set to 0 if we're not to create a resource when failing to find
 * one.
 ****************************************************************************/
#if 0
static MemHandle 
openDBResource( UInt32 resType, Int resID, UInt size ) 
{
    MemHandle localH;
    Int index;

    if ( gDBRef == NULL ) {
	LocalID dbID;
	(void)DmCreateDatabase( 0, gDBName, APPID, 'Xwcd', true );
	dbID = DmFindDatabase( 0, gDBName );
	ASSERT( dbID != 0 );
	gDBRef = DmOpenDatabase( 0, dbID, dmModeReadWrite );
	ASSERT( gDBRef!=NULL );
    }
    index = DmFindResource( gDBRef, resType, resID, NULL );
    if ( index < 0 ) {
	if ( size == 0 ) {
	    return NULL;
	}
	localH = DmNewResource( gDBRef, resType, resID, size );
	userError( "made new rsrc" );
    } else {
	localH = DmGetResourceIndex( gDBRef, index );
    }
    ASSERT( localH != NULL );
    ASSERT( MemHandleLockCount( localH ) == 0 );

    return localH;
} // openDBResource
#endif

/*****************************************************************************
 * writeResToXWords
 * Open the app database, size its corresponding resource to match this one
 * and then write this one in.
 ****************************************************************************/
static void 
writeResToXWords( UInt32 resType, UInt16 resID, MemPtr resPtr ) 
{
    UInt32 size;
    DmOpenRef xwref;
    MemHandle xwordsRes = openXWordsRes( resType, resID, dmModeWrite, &xwref );
    ASSERT( xwordsRes != NULL );
    ASSERT( MemHandleLockCount( xwordsRes ) == 0 );
    size = MemPtrSize( resPtr );
    
    xwordsRes = DmResizeResource( xwordsRes, size );
    ASSERT( xwordsRes != NULL );

    DmWrite( MEMHANDLELOCK(xwordsRes), 0, resPtr, size );

    closeResHand( &xwordsRes );
/*      MEMHANDLEUNLOCK( xwordsRes ); */
/*      DmReleaseResource( xwordsRes ); */
    DmCloseDatabase( xwref );
} // writeResToXWords

static unsigned char*
findBoardByte( Int16 col, Int16 row, Boolean* highByte )
{
    XP_U16 nCols = gState.fCurBoardSize;
    XP_U16 midCol = nCols / 2;
    XP_U16 resIndex = (PALM_MAX_COLS - nCols) / 2;    
    char* bonusResPtr = skipName(gBoardResPtr[resIndex]);
    XP_U16 index;

    if ( col > midCol ) col = nCols - 1 - col;
    if ( row > midCol ) row = nCols - 1 - row;
    index = (row*(midCol+1)) + col;

    *highByte = index%2 == 0;
    return bonusResPtr + (index/2);
} /* findBoardByte */

/*****************************************************************************
 * setBoardTile
 ****************************************************************************/
static void
setBoardTile( Int16 col, Int16 row, Char tile ) 
{
    Boolean highByte;
    unsigned char* boardCharPtr = findBoardByte( col, row, &highByte );
    unsigned char newEntry = *boardCharPtr;

    if ( highByte ) {
	newEntry &= 0x0F;
	newEntry |= tile << 4;
    } else {
	newEntry &= 0xF0;
	newEntry |= tile;
    }

    *boardCharPtr = newEntry;
    gBoardDirty = true;
} // setBoardTile

/*****************************************************************************
 * return the ptr just past the null-terminated string that begins the
 * resource, skipping any pad byte necessary to word-align what follows.
 ****************************************************************************/
static unsigned char* skipName( unsigned char* start ) {
/*     UInt32 len = StrLen( start ) + 1; */
/*     if ( len % 2 != 0 ) len++; */
/*     return start + len; */
  return start;
} // skipName

/*****************************************************************************
 * getBoardTile
 ****************************************************************************/
static Char 
getBoardTile( Int16 col, Int16 row ) 
{
    unsigned char* boardCharPtr;
    Char result;
    Boolean highByte;

    boardCharPtr = findBoardByte( col, row, &highByte );
    result = *boardCharPtr;
    if ( highByte ) {
	result >>= 4;
    }
    return result & 0x0F;
} /* getBoardTile */

/*****************************************************************************
 * drawCell
 ****************************************************************************/
static void
drawCell( short row, short col ) 
{
    RectangleType cell = { {0,0}, {CELL_WIDTH-1, CELL_WIDTH-1} };
    Char tile;
    /* from palmdraw.c */

    /* I can't figure out how to declare this so that it compiles without
       refs into the clibrary and doesn't generate warnings at the
       WinSetPattern line below.  Warnings being better than a runtime error
       we'll leave it this way. */
    UInt16 valuePatterns[4][4] = { 
	{0x8844, 0x2211, 0x8844, 0x2211},
	{0xaa55, 0xaa55, 0xaa55, 0xaa55},  
	{0xCC66, 0x3399, 0xCC66, 0x3399},
	{0xCCCC, 0x3333, 0xCCCC, 0x3333},
    };

    cell.topLeft.x = col*CELL_WIDTH + gBoardRect.topLeft.x;
    cell.topLeft.y = row * CELL_WIDTH + gBoardRect.topLeft.y;

    tile = getBoardTile( col, row );
    if ( tile >= BONUS_NONE && tile <= BONUS_TRIPLE_WORD ) {
	if ( gDoesColor ) {
	    UInt16 index;
	    IndexedColorType color;

	    if ( tile == BONUS_NONE ) {
		index = COLOR_EMPTY;
	    } else {
		index = COLOR_DBL_LTTR + tile - 1; /* -1 for BONUS_NONE */
	    }

	    color = gColorSelState.drawingPrefs.drawColors[index];
	    WinSetBackColor( color );
	    WinEraseRectangle( &cell, 0 );
	} else if ( tile != BONUS_NONE ) {
	    WinSetPattern( valuePatterns[tile-BONUS_DOUBLE_LETTER] );
	    WinFillRectangle( &cell, 0 );
	} else {
	    WinEraseRectangle( &cell, 0 );
	}
    }

    WinDrawRectangleFrame( rectangleFrame, &cell );
} // drawCell

/*****************************************************************************
 * drawBoard
 ****************************************************************************/
static void 
drawBoard( void )
{
    short row, col, i;
    char* str = "123456";
    if ( gDoesColor ) {
	WinPushDrawState();
    }

    FntSetFont( stdFont );
    for ( i = 0; i < 4; ++i ) {
	if ( gDoesColor ) {
	    UInt16 index = COLOR_PLAYER1 + i;
	    WinSetTextColor(gColorSelState.drawingPrefs.drawColors[index]);
	    WinSetBackColor( 
		gColorSelState.drawingPrefs.drawColors[COLOR_WHITE] );
	}
	WinDrawChars( str+i, 3, 10 + (30*i), 15 );
    }

    for ( row = 0; row < gState.fCurBoardSize; ++row ) {
	for ( col = 0; col < gState.fCurBoardSize; ++col ) {
	    drawCell( col, row );
	}
    }

    if ( gDoesColor ) {
	WinPopDrawState();
    }
} // drawBoard

static void
clearBoard()
{
    short row, col;
    for ( row = 0; row < 8; ++row ) {
	for ( col = 0; col < 8 ; ++col ) {
	  setBoardTile( col, row, BONUS_NONE );
	}
    }
} /* clearBoard */

static void
tryEgg( char key )
{
    static UInt32 buf = 0L;
    
    buf >>= 8;
    *((char*)&buf) = key;
    if ( buf == 'bsah' ) {
        struct {
            char col;
            char row;
            char val;
        } foo [] = {
            { 0, 0, 4 },
            { 0, 3, 1 },
            { 0, 7, 4 },
            { 1, 1, 2 },
            { 1, 5, 3 },
            { 2, 2, 2 },
            { 2, 6, 1 },
            { 3, 0, 1 },
            { 3, 3, 2 },
            { 3, 7, 1 },
            { 4, 4, 2 },
            { 5, 1, 3 },
            { 5, 5, 3 },
            { 6, 2, 1 },
            { 6, 6, 1 },
            { 7, 0, 4 },
            { 7, 3, 1 },
            { 7, 7, 2 },

            { -1, -1, -1 }
        }, *sp;

        for ( sp = foo; sp->col >= 0; ++sp ) {
            setPair( sp->col, sp->row, sp->val );
        }
        drawBoard();
    }
} /* tryEgg */

static void
setPair( Int16 col, Int16 row, Char result )
{
    setBoardTile( col, row, result );
    setBoardTile( row, col, result );
}

/******************************************************************************
 * setModelSquares
 ****************************************************************************/
static void setModelSquares( Int16 x, Int16 y, Char result ) {
    Int16 col, row;
    pointToBoard( x, y, &col, &row );
    setPair( col, row, result );
} // setModelSquares

/*****************************************************************************
 * setModelSquares
 ****************************************************************************/
static void pointToBoard( Int16 x, Int16 y, Int16* col, Int16* row ) {
    x -= gBoardRect.topLeft.x;
    y -= gBoardRect.topLeft.y;
    *col = x / CELL_WIDTH;
    *row = y / CELL_WIDTH;
} // pointToBoard

/*****************************************************************************
 * Calculate a ptr to the first tile entry -- after the language string and any
 * extra pad byte.  As a side effect, set gNumChars.
 ****************************************************************************/
#ifdef DO_DICTS
static unsigned char* 
findBase( unsigned char* resStart ) 
{
    unsigned char* result;
    result = skipName( resStart );
    gLangLen = result - resStart;
    ASSERT( (unsigned long)(resStart + gLangLen)%2==0 );
    ASSERT( resStart[gLangLen-1] == '\0' );

    gNumChars =
	((Xloc_header*)result)->specialCharStart / BYTES_PER_LETTER;
    result += sizeof( Xloc_header );

    return result;
} // findBase
#endif

/*****************************************************************************
 * closeResPtr
 ****************************************************************************/
static void 
closeResPtr( MemPtr* ptr ) 
{
    MEMPTRUNLOCK( *ptr );
/*     DmReleaseResource( MemPtrRecoverHandle( *ptr ) ); */
    *ptr = NULL;
} // closeResPtr

/*****************************************************************************
 * closeResHand
 ****************************************************************************/
static void
closeResHand( MemHandle* hand ) 
{
    MEMHANDLEUNLOCK( *hand );
    ASSERT( MemHandleLockCount( *hand ) == 0 );
    DmReleaseResource( *hand );
    *hand = NULL;
} // closeResHand

/*****************************************************************************
 * userError
 ****************************************************************************/
void userError( Char* str ) {
    if ( !gClosing ) {
	FrmCustomAlert( XWC_ERROR_ALERT_ID, str, NULL, NULL );
    }
}

/******************************************************************************
 * confirm
 ****************************************************************************/
static Boolean confirm( Char* str ) {
    FormPtr form, prevForm;
    UInt16 buttonHit;
    FieldPtr field;
    MemHandle hand;

    prevForm = FrmGetActiveForm();
    form = FrmInitForm( XWC_GENERIC_DIALOG_ID );
    FrmSetActiveForm( form );
    
    hand = MemHandleNew( StrLen(str)+1 );
    StrCopy( MEMHANDLELOCK(hand), str );
    MEMHANDLEUNLOCK( hand );
    field = getActiveObjectPtr( XWC_GENERIC_FIELD_ID );
    ASSERT( field != NULL );
    FldSetTextHandle( field, (MemHandle)hand );

    FrmDrawForm( form );
    
    buttonHit = FrmDoDialog( form );
    FrmDeleteForm( form );
    FrmSetActiveForm( prevForm );

    return buttonHit == XWC_GENERIC_OK_BUTTON_ID;
}  // confirm

/*****************************************************************************
 * confirmS
 ****************************************************************************/
/* static Boolean confirmS( CharPtr format, CharPtr str ) { */
/*     Char buf[100]; */
/*     StrPrintF( buf, format, str ); */
/*     return confirm( buf ); */
/* } // confirmS */
