// RecordHandler.cpp
//
// Copyright й Diehl Graphsoft, Inc. 1996-1998.
// All Rights Reserved.
//
// This file contains the source for the record handler objects.
//
// Defines classes to simplify working with Records and Formats
//
// Started 5/8/96 by Darick DeHart
// Modified 1997, Jan 1998 by Jeff Koppi to handle Parametric-FormatNodes and their new field types.
//
// SourceSafe information for this version:
//    $Revision: 20 $
//    $Date: 2/27/01 9:06a $
//    $Author: Paul $


//---------------------------------------------------------------------------------
//	How to add new TRecordItem type to MiniCAD:
//
//	File:								Function or Data:
//	-----								-----------------
//
//	MiniCadCallbacks.h					
//										enum EFieldStyle { kFieldXXXXX = X, }
//
//	RecordHandler.h		
//										struct RecordItemType
//										TRecordItem::SetFieldValue
//										TRecordItem::GetFieldValue
//									
//	RecordHandler.cpp		
//										TRecordItem::TRecordItem() constructor
//										TRecordItem::SetFieldValue()
//										TRecordItem::GetFieldValue()
//										TRecordHandler::SetFieldObject() switch case
//										TRecordHandler::GetFieldObject() switch case
//										FieldSize()
//										StringToFieldValue()
//										SetFieldValueAsString()
//										FieldValueToString()
//										GetFieldValueAsString()
//										
//	ParametricObj.cpp
//										TParametricTypeController::ParmFieldKindToWidgetKind()
//										TParametricTypeController::TransferValue()
//										
//	FileTran.cpp
//										RecordType::PostByteSwap()
//
//	UFile.cpp
//										LLCheckObjectManUnld()
//
//---------------------------------------------------------------------------------


#if _MINICAD_
	// Inside VectorWorks, we need access to the full suite of types

	#include "StdHeaders.h"	// Must be first non-comment line.  See DMB.
	
	#include "MCMemory.h"
	
#else
	// As an external, the types are declared in RecordTypes.h, the Mac-specific types
	// come from MCTypes.h, and some of the code in this file does not even get built

	#include <stdio.h>

	#if _WINDOWS
		#include <Windows.h>
	#endif

	// JAK 12/7/99 Changed from SharedDefines.h to CoreDefines.h to fix WIN lib compile error.
	// I guess RecordHandler.cpp needs altura when built into app but not when built into lib.
	#include "CoreDefines.h"	
	#include "GSCore.h"
	#include "GSString.h"
#endif


#ifdef _MINICAD_EXTERNAL_
	#define RECORDLIBRARY
#else
	#include "FileTranVW8Types.h"
	#include "Objs.UserDataType.h"
#endif

// When compiling the the external record library ...
// RecordHandler.x depends on RecordTypes.h being included first 
// If this is not done, then RecordHandler.h assumes that it is being compiled
// for client code and you will get compile and or link errors.
#include "RecordTypes.h"
#include "RecordHandler.h"

#if _MINICAD_
	#include "Objs.UserDataType.h"
	#include "Pk.h"
	#include "TDQuickDraw.h"
	#include "StringUtilities.h"
	#include "MCNotify.h"
	#include "ParametricExternals.h"
#endif

// This must follow all header files.
#undef THIS_FILE
DECLARE_THIS_FILE


//---------------------------------------------------------------------------------

/*#if GS_WIN
	#define scalb _scalb
#else
	using std::scalb;
#endif*/


short	gVersion;
Boolean	globalVersionSet = false;


extern "C" void FEOdeleteField(MCObjectHandle h, CallBackPtr cbp, void* env);
extern "C" void FEOaddField(MCObjectHandle h, CallBackPtr cbp, void* env);
extern "C" void FEOCountRecords(MCObjectHandle h, CallBackPtr cbp, void* env);



static short GetMCSDKVersion()
// This function calls the GetHostInfo callback once and stores the version in a global.
// The GetHostInfo callback is much too slow to call repeatedly.
{
	if (! globalVersionSet){
		// Get MiniCad Version;
		short hostID; 
		Byte version = 0, majorRev = 0, minorRev = 0;
		StringPtr productName = nil,  productVersion = nil;
		GS_GetHostInfo(gCBP, hostID, version, majorRev, minorRev, productName, productVersion);
		gVersion = version;
		globalVersionSet = true;
	}
	return gVersion;
}


void SetUpRecordHandler()
// Sets the global version, this is necessary for externals only because there
// is no global initialization. 
{
	DMSG((kDarick, "gVersion = %d\n", gVersion));
	globalVersionSet = false;
	gVersion = 0;
	
	gVersion = GetMCSDKVersion();
}


void SetDownRecordHandler()
// nothing exciting here, but its good style
{
	globalVersionSet = false;
	gVersion = 0;
}


static void UpdateInfoPal (void)
// Ugly little trick to make the Object Info Window redraw.
// Used because externals can't send notifications (don't use elsewhere).
// The GS_DeleteObject() causes a redraw of the object info palette.
{
	FormatHandle format;
	
	format = NewFormat( TSmartFormat::OffsetOfFieldList());
	
	if (format)
		GS_DeleteObject(gCBP, (MCObjectHandle)format, false);    // false indicates undo is not handling this object
}


/***********************************************************************************

	OtherFunctions
	
************************************************************************************/

extern "C" void FEOdeleteField(MCObjectHandle h, CallBackPtr cbp, void* env)
// expands each record with format == actionEnv->mName and deletes the deleteField
{
	#ifdef _MINICAD_EXTERNAL_
//	A4Recaller a4r(cbp);
	#endif
	
	FormatHandle format; 
	RecordHandle  record;
	TDeleteFieldEnv *actionEnv =  (TDeleteFieldEnv *) env;

	record = (RecordHandle) GS_FindAuxObject( cbp, h, kRecordNode);

	if(record) {							// if there is a record field
		TSmartRecord sRecord(record);
		if(sRecord.FormatUsed() == actionEnv->mName){		//  and it is the same format, then
			format = (FormatHandle)GS_GetDefinition(cbp, (MCObjectHandle)record);
			if(format) {		
				DeleteField_ConvertRecord(record, actionEnv->oldFormat, format, actionEnv->deleteFieldIndex);
			}	
		} 
	}
}


extern "C" void FEOaddField(MCObjectHandle h, CallBackPtr cbp, void* env)
//adds default field to all records with the same format
// This routine was modified to traverse the entire aux list of the object; it used to process only the first record.  -DLD 10/8/2003
{
	if (VERIFYN(kDaveD, env)) {

		TAddFieldEnv *addFieldEnv = (TAddFieldEnv *) env; 	// add field parameters
		
		MCObjectHandle auxObj = GS_FindAuxObject(cbp, h, recordNode);

		while (auxObj) {							// if there is a record field, 
			
			RecordHandle record = (RecordHandle) auxObj;
			
			TSmartRecord sRecord(record);
			if (sRecord.FormatUsed() == addFieldEnv->mName) {	//  and it is the same format, then ...
				
				FormatHandle format = (FormatHandle) GS_GetDefinition(cbp, (MCObjectHandle) record);
				if (format)
					AddField_ConvertRecord(record, addFieldEnv->oldFormat, format, *(addFieldEnv->value));
			}

			auxObj = GS_NextAuxObject(cbp, GS_NextObject(cbp, auxObj), recordNode);
		}
	}
}


//-------------------------------
extern "C" void FEOCountRecords(MCObjectHandle h, CallBackPtr cbp, void* env)
//
//	Counts all records with the same format.
//	This function is passed as argument to GS_ForEachObject(), so it must be declared static extern "C".
//	This function is called once for each object in drawing which matches flags of ForEachObject.
//	We assume this is never called for nodes on aux lists, so this function must decend into aux lists itself.
//
//	JAK 6/30/99
//
{
	#ifdef _MINICAD_EXTERNAL_
//	A4Recaller a4r(cbp);
	#endif

	// FormatHandle format; 
	RecordHandle record;
	TCountExistingRecordsEnv *countRecEnv =  (TCountExistingRecordsEnv *) env;

	record = (RecordHandle) GS_FindAuxObject(cbp, h, kRecordNode);	
	while (record) {							// if there is a record, 
		// if ((*(NGNodeHandle)h)->tdType == kRecordNode)
		TSmartRecord sRecord(record);
		if (sRecord.FormatUsed() == countRecEnv->mName) {	//  and it is the same format, then ...
			countRecEnv->count++;
		}
		record = (RecordHandle) GS_NextAuxObject(cbp, GS_NextObject(cbp, (MCObjectHandle)record), kRecordNode);	// loop increment
	}
}	




/***********************************************************************************

	TSmartFormat Methods -
	
	Use these methods to safely handle the different file versions of 
	format nodes in externals. 
	
************************************************************************************/

TSmartFormat::TSmartFormat(FormatHandle h)
// Initialize the smart format class
// if h is not a valid format then fValid will be set to false
{
	fMCVer = kMCUnknown;
	f6Format = nil;
	f7Format = nil;
	f8Format = nil;
	f9Format = nil;
	f13Format = nil;
	fValid = false;
	
	if (h)
		this->SetFormat(h);
}


void TSmartFormat::SetFormat( FormatHandle h)
// initialize the format handle and verify it
{
	//Assign the type and handle

	// Note: VectorWorks 9 uses same record format structure as VectorWorks 8.  JAK 8/10/99
	// 6/15/00, JAG: VectorWorks 9 now uses a different structure than VW 8 (due to expansion of NGNodeType)
	// 3/21/02 JAK: I think VW 10 will use same format as VW 9.
	// Test for >= 10 because we assume new versions will use same format node type.  
	// i.e. Newer apps will default to use the 9 format node, not the 6 format node.
	if (GetMCSDKVersion() >= 13) {
		fMCVer = kMC13;
		f13Format = (FormatTypeVW13**)h;
	}
	else if ((GetMCSDKVersion() <= 12) && (GetMCSDKVersion() >= 9)) {
		fMCVer = kMC9;
		f9Format = (FormatTypeVW9**)h;
	}
	else if (GetMCSDKVersion() == 8) {
		fMCVer = kMC8;
		f8Format = (FormatTypeVW8**)h;
	}
	else if (GetMCSDKVersion() == 7) {
		fMCVer = kMC7;
		f7Format = (FormatTypeMC7**)h;
	}
	else {
		fMCVer = kMC6;
		f6Format = (FormatTypeMC6**)h;
	}
	
	fValid = this->Verify();
}

Boolean TSmartFormat::Verify()
// check to see that we are using a valid format
{
	if (fMCVer >= kMC13) {
		fValid = VERIFYHANDLE(kDarick, f13Format, TSmartFormat::OffsetOfFieldList())
					&& VERIFYN(kDarick, (*f13Format)->tdType == kFormatNode);
	}
	else if (fMCVer == kMC9) {
		fValid = VERIFYHANDLE(kDarick, f9Format, TSmartFormat::OffsetOfFieldList())
					&& VERIFYN(kDarick, (*f9Format)->tdType == kFormatNode);
	}
	else if (fMCVer == kMC8) {
		fValid = VERIFYHANDLE(kDarick, f8Format, TSmartFormat::OffsetOfFieldList())
					&& VERIFYN(kDarick, (*f8Format)->tdType == kFormatNode);
	}
	else if (fMCVer == kMC7){
		fValid = VERIFYHANDLE(kDarick, f7Format, TSmartFormat::OffsetOfFieldList())
					&& VERIFYN(kDarick, (*f7Format)->tdType == kFormatNode);
	}
	else{
		fValid = VERIFYHANDLE(kDarick, f6Format, TSmartFormat::OffsetOfFieldList()) 
					&& VERIFYN(kDarick, (*f6Format)->tdType == kFormatNode);
	}	
	return fValid;
}


size_t TSmartFormat::SizeOf()
// asserts the handle and 
// returns the size of the entire handle
{
	size_t handleSize = 0;
	if (fMCVer >= kMC13) {
		if (IsValid() && VERIFYHANDLE(kDarick, f13Format, offsetof(FormatTypeVW13, fieldList)))
			 handleSize = offsetof(FormatTypeVW13, fieldList) + (*f13Format)->numberOfFields * sizeof(FieldType);	
	}
	else if (fMCVer == kMC9) {
		if (IsValid() && VERIFYHANDLE(kDarick, f9Format, offsetof(FormatTypeVW9, fieldList)))
			 handleSize = offsetof(FormatTypeVW9, fieldList) + (*f9Format)->numberOfFields * sizeof(FieldType);	
	}
	else if (fMCVer == kMC8) {
		if (IsValid() && VERIFYHANDLE(kDarick, f8Format, offsetof(FormatTypeVW8, fieldList)))
			 handleSize = offsetof(FormatTypeVW8, fieldList) + (*f8Format)->numberOfFields * sizeof(FieldType);	
	}
	else if (fMCVer == kMC7){
		if (IsValid() && VERIFYHANDLE(kDarick, f7Format, offsetof(FormatTypeMC7, fieldList)))
			 handleSize = offsetof(FormatTypeMC7, fieldList) + (*f7Format)->numberOfFields * sizeof(FieldType);
	}
	else {
		if (IsValid() && VERIFYHANDLE(kDarick, f6Format, offsetof(FormatTypeMC6, fieldList)))
			 handleSize = offsetof(FormatTypeMC6, fieldList) + (*f6Format)->numberOfFields * sizeof(FieldType);	
	}
	
	return handleSize;
}


size_t TSmartFormat::OffsetOfNumberOfFields()
// static function that returns the correct offset based on the MiniCad version
{
	if (GetMCSDKVersion() >= 13)
		return offsetof(FormatTypeVW13, numberOfFields);
	else if (GetMCSDKVersion() <= 12 && GetMCSDKVersion() >= 9)
		return offsetof(FormatTypeVW9, numberOfFields);
	else if (GetMCSDKVersion() == 8)
		return offsetof(FormatTypeVW8, numberOfFields);
	else if (GetMCSDKVersion() == 7)
		return offsetof(FormatTypeMC7, numberOfFields);
	else
		return offsetof(FormatTypeMC6, numberOfFields);
}


size_t TSmartFormat::OffsetOfFieldList()
// static function that returns the correct offset based on the MiniCad version
{
	if (GetMCSDKVersion() >= 13)
		return offsetof(FormatTypeVW13, fieldList);
	else if (GetMCSDKVersion() <= 12 && GetMCSDKVersion() >= 9)
		return offsetof(FormatTypeVW9, fieldList);	
	else if (GetMCSDKVersion() == 8)
		return offsetof(FormatTypeVW8, fieldList);	
	else if (GetMCSDKVersion() == 7)
		return offsetof(FormatTypeMC7, fieldList);
	else
		return offsetof(FormatTypeMC6, fieldList);
}


short* TSmartFormat::NumberOfFields()
// checks that the class was created ok, asserts the pointer, returns the pointer
{
	if (IsValid()) {
		if (fMCVer >= kMC13) {
			ASSERTPOINTER(kDarick, &(*f13Format)->numberOfFields, sizeof(short));
			return &(*f13Format)->numberOfFields;
		}
		if (fMCVer == kMC9) {
			ASSERTPOINTER(kDarick, &(*f9Format)->numberOfFields, sizeof(short));
			return &(*f9Format)->numberOfFields;
		}
		if (fMCVer == kMC8) {
			ASSERTPOINTER(kDarick, &(*f8Format)->numberOfFields, sizeof(short));
			return &(*f8Format)->numberOfFields;
		}
		if (fMCVer == kMC7) {
			ASSERTPOINTER(kDarick, &(*f7Format)->numberOfFields, sizeof(short));
			return &(*f7Format)->numberOfFields;
		}
		else {
			ASSERTPOINTER(kDarick, &(*f6Format)->numberOfFields, sizeof(short));
			return &(*f6Format)->numberOfFields;
		}	
	}
	else {
		DMSG((kDarick, "Using an invalid TSmartFormat\n"));
		return nil;
	}
}


void TSmartFormat::SetSubscribeID (short subID)
// Checks that the class was created ok, asserts the pointer, and sets the field
{
	if (IsValid()) {
		if (fMCVer >= kMC13) {
			ASSERTPOINTER(kDarick, &(*f13Format)->subscribeID, sizeof(short));
			(*f13Format)->subscribeID = subID;
		}
		else if (fMCVer == kMC9) {
			ASSERTPOINTER(kDarick, &(*f9Format)->subscribeID, sizeof(short));
			(*f9Format)->subscribeID = subID;
		}
		else if (fMCVer == kMC8) {
			ASSERTPOINTER(kDarick, &(*f8Format)->subscribeID, sizeof(short));
			(*f8Format)->subscribeID = subID;
		}
		else if (fMCVer == kMC7){
			ASSERTPOINTER(kDarick, &(*f7Format)->subscribeID, sizeof(short));
			(*f7Format)->subscribeID = subID;
		}
		else {
			/* No subscribeID field in version 6 formats */
			;
		}	
	}
	else {
		DMSG((kDarick, "Using an invalid TSmartFormat\n"));
	}
}


long* TSmartFormat::TimeStamp()
// checks that the class was created ok, asserts the pointer, returns the pointer
{
	if (IsValid()) {
		if (fMCVer >= kMC13) {
			ASSERTPOINTER(kDarick, &(*f13Format)->timeStamp, sizeof(long));
			return &(*f13Format)->timeStamp;
		}
		else if (fMCVer == kMC9) {
			ASSERTPOINTER(kDarick, &(*f9Format)->timeStamp, sizeof(long));
			return &(*f9Format)->timeStamp;
		}
		else if (fMCVer == kMC8) {
			ASSERTPOINTER(kDarick, &(*f8Format)->timeStamp, sizeof(long));
			return &(*f8Format)->timeStamp;		
		}
		else if (fMCVer == kMC7){
			ASSERTPOINTER(kDarick, &(*f7Format)->timeStamp, sizeof(long));
			return &(*f7Format)->timeStamp;
		}
		else {
			ASSERTPOINTER(kDarick, &(*f6Format)->timeStamp, sizeof(long));
			return &(*f6Format)->timeStamp;
		}
	}
	else {
		DMSG((kDarick, "Using an invalid TSmartFormat\n"));
		return nil;
	}	
}
		

FieldListType* TSmartFormat::FieldList()
// checks that the class was created ok, asserts the pointer, returns the pointer
// 
// NOTE: pointer is only valid if at least one field has been created 
//
{
	if (*(this->NumberOfFields()) >= 1) {

		if (fMCVer >= kMC13) { 
			ASSERTPOINTER(kDarick, &(*f13Format)->fieldList, sizeof(FieldListType));
			return &(*f13Format)->fieldList;		
		}
		else if (fMCVer == kMC9) { 
			ASSERTPOINTER(kDarick, &(*f9Format)->fieldList, sizeof(FieldListType));
			return &(*f9Format)->fieldList;		
		}
		else if (fMCVer == kMC8) {
			ASSERTPOINTER(kDarick, &(*f8Format)->fieldList, sizeof(FieldListType));
			return &(*f8Format)->fieldList;		
		}
		else if (fMCVer == kMC7) {
			ASSERTPOINTER(kDarick, &(*f7Format)->fieldList, sizeof(FieldListType));
			return &(*f7Format)->fieldList;
		}
		else {
			ASSERTPOINTER(kDarick, &(*f6Format)->fieldList, sizeof(FieldListType));
			return &(*f6Format)->fieldList;
		}	
	}
	else {
		return nil;
	}	
}

void TSmartFormat::SetIsParametric(Boolean is)
//	If MiniCAD 8, set the flag to indicate this format is used for parametric object.
{
	if (fMCVer >= kMC13) {
		(*f13Format)->isParametric = is;
	}
	else if (fMCVer == kMC9) {
		(*f9Format)->isParametric = is;
	}
	else if (fMCVer == kMC8) {
		(*f8Format)->isParametric = is;
	}
	else {
		DSTOP((kJeff, "Attempt to set parametric flag of old or non-paramertic format."));
	}
}

Boolean TSmartFormat::IsParametric()
// If MiniCAD 8, test parametric flag.
{
	if (fMCVer >= kMC13)
		return (*f13Format)->isParametric;
	if (fMCVer == kMC9)
		return (*f9Format)->isParametric;
	if (fMCVer == kMC8) 
		return (*f8Format)->isParametric;
	else
		return false;
}	

void TSmartFormat::SetParametricVersion(short ver)
// if MiniCAD 8, and this format is a parametric, set its version number.
{
	if ((fMCVer >= kMC13) && (this->IsParametric()))
		(*f13Format)->parametricVersion = ver;
	else if ((fMCVer == kMC9) && (this->IsParametric()))
		(*f9Format)->parametricVersion = ver;
	else if ((fMCVer == kMC8) && (this->IsParametric()))
		(*f8Format)->parametricVersion = ver;
	else {
		DSTOP((kJeff, "Attempt to set parametric version for non-parametric format"));
	}
}

short TSmartFormat::GetParametricVersion()
// if MiniCAD 8, return parametric version or -1.
{
	if (fMCVer >= kMC13)
		return (*f13Format)->parametricVersion;
	else if (fMCVer == kMC9)
		return (*f9Format)->parametricVersion;
	else if (fMCVer == kMC8)
		return (*f8Format)->parametricVersion;
	else
		return -1;
}


/***********************************************************************************

	TSmartRecord Methods -
	
	Use these methods to safely handle the different file format versions of 
	records in externals. 
	
************************************************************************************/

TSmartRecord::TSmartRecord(RecordHandle h)
// initialize the fields and check that h is a valid record handle
{
	fMCVer = kMCUnknown;
	f6Record = nil;
	f8Record = nil;
	f9Record = nil;

	if (GetMCSDKVersion() >= 13) {
		fMCVer = kVW13;
		f13Record = (RecordTypeVW13**)h;
	}
	else if (GetMCSDKVersion() <= 12 && GetMCSDKVersion() >= 9) {	// JAK 3/21/02 use operator >= so future versions default to 9, not default to 6.
		fMCVer = kVW9;
		f9Record = (RecordTypeVW9**)h;
	}
	else if ((GetMCSDKVersion() == 7) || (GetMCSDKVersion() == 8)) {
		fMCVer = kVW8;
		f8Record = (RecordTypeVW8**)h;
	}
	else {
		fMCVer = kMC6;
		f6Record = (RecordTypeMC6**)h;
	}
		
	fValid = this->Verify();
}


Boolean TSmartRecord::Verify()
// check that we actually have a record node
// and check the size to make sure everything adds up 
{
	if (fMCVer >= kVW13) {
		fValid = VERIFYHANDLE(kDarick, f13Record, offsetof(RecordTypeVW13, firstByte)) 
			&& VERIFYN(kDarick, (*f13Record)->tdType == kRecordNode);
	}
	else if (fMCVer == kVW9) {		// JAK 3/21/02 use operator >= so future versions default to 9, not default to 6.
		fValid = VERIFYHANDLE(kDarick, f9Record, offsetof(RecordTypeVW9, firstByte)) 
					&& VERIFYN(kDarick, (*f9Record)->tdType == kRecordNode);
	}
	else if (fMCVer == kVW8) {
		fValid = VERIFYHANDLE(kDarick, f8Record, offsetof(RecordTypeVW8, firstByte)) 
					&& VERIFYN(kDarick, (*f8Record)->tdType == kRecordNode);
	}
	else{
		fValid = VERIFYHANDLE(kDarick, f6Record, offsetof(RecordTypeMC6, firstByte))
					&& VERIFYN(kDarick, (*f6Record)->tdType == kRecordNode);
	}
	return fValid;
}


size_t TSmartRecord::SizeOf()
// asserts the handle and 
// returns the size of the "fixed size" header
{
	size_t handleSize = 0;

	if (fMCVer >= kVW13) {
		if (IsValid() && VERIFYHANDLE(kDarick, f13Record, offsetof(RecordTypeVW13, firstByte)))
			 handleSize = offsetof(RecordTypeVW13, firstByte);
	}
	else if (fMCVer == kVW9) {		// JAK 3/21/02 use operator >= so future versions default to 9, not default to 6.
		if (IsValid() && VERIFYHANDLE(kDarick, f9Record, offsetof(RecordTypeVW9, firstByte)))
			 handleSize = offsetof(RecordTypeVW9, firstByte);
	}
	else if (fMCVer == kVW8){
		if (IsValid() && VERIFYHANDLE(kDarick, f8Record, offsetof(RecordTypeVW8, firstByte)))
			 handleSize = offsetof(RecordTypeVW8, firstByte);
	}
	else {
		if (IsValid() && VERIFYHANDLE(kDarick, f6Record, offsetof(RecordTypeMC6, firstByte)))
			 handleSize = offsetof(RecordTypeMC6, firstByte);	
	}
	
	return handleSize;
}


size_t TSmartRecord::OffsetOfFirstByte()
// static function that returns the correct offset based on the MiniCad version
{
	if (GetMCSDKVersion() >= 13)			// JAK 3/21/02 use operator >= so future versions default to 9, not default to 6.
		return offsetof(RecordTypeVW13, firstByte);
	else if (GetMCSDKVersion() <= 12 && GetMCSDKVersion() >= 9)			// JAK 3/21/02 use operator >= so future versions default to 9, not default to 6.
		return offsetof(RecordTypeVW9, firstByte);
	else if ((GetMCSDKVersion() == 7) || (GetMCSDKVersion() == 8))
		return offsetof(RecordTypeVW8, firstByte);
	else
		return offsetof(RecordTypeMC6, firstByte);
}

RefNumber TSmartRecord::FormatUsed()
// checks that the object was created ok, asserts the pointer, returns contents of the pointer
{
	if (IsValid()) {
		if (fMCVer >= kVW13){
			ASSERTPOINTER(kDarick, &(*f13Record)->formatUsed, sizeof(RefNumber));
			return (*f13Record)->formatUsed;
		}
		else if (fMCVer == kVW9){
			ASSERTPOINTER(kDarick, &(*f9Record)->formatUsed, sizeof(RefNumber));
			return (*f9Record)->formatUsed;
		}
		else if (fMCVer == kVW8){
			ASSERTPOINTER(kDarick, &(*f8Record)->formatUsed, sizeof(short));
			return (*f8Record)->formatUsed;
		}
		else {
			ASSERTPOINTER(kDarick, &(*f6Record)->formatUsed, sizeof(short));
			return (*f6Record)->formatUsed;
		}	
	}
	else {
		DMSG((kDarick, "Using an invalid TSmartRecord\n"));
		return nil;
	}
}

void TSmartRecord::SetFormatUsed(RefNumber format)
// checks that the object was created ok, asserts the pointer, sets the contents of the pointer
{		
	if (IsValid()) {
		if (fMCVer >= kVW13){
			ASSERTPOINTER(kDarick, &(*f13Record)->formatUsed, sizeof(RefNumber));
			(*f13Record)->formatUsed = format;
		}
		else if (fMCVer == kVW9){
			ASSERTPOINTER(kDarick, &(*f9Record)->formatUsed, sizeof(RefNumber));
			(*f9Record)->formatUsed = format;
		}
		else if (fMCVer == kVW8){
			ASSERTPOINTER(kDarick, &(*f8Record)->formatUsed, sizeof(short));
			(*f8Record)->formatUsed = (short)format;
		}
		else {
			ASSERTPOINTER(kDarick, &(*f6Record)->formatUsed, sizeof(short));
			(*f6Record)->formatUsed = (short)format;
		}	
	}
	else {
		DMSG((kDarick, "Using an invalid TSmartRecord\n"));
	}
}
		
short* TSmartRecord::FirstByte()
// checks that the class was created ok, asserts the pointer, returns the pointer
{
	if (IsValid()) {
		if (fMCVer >= kVW13){
			ASSERTPOINTER(kDarick, &(*f13Record)->firstByte, sizeof(long));
			return &(*f13Record)->firstByte;
		}
		if (fMCVer == kVW9){
			ASSERTPOINTER(kDarick, &(*f9Record)->firstByte, sizeof(long));
			return &(*f9Record)->firstByte;
		}
		else if (fMCVer == kVW8){
			ASSERTPOINTER(kDarick, &(*f8Record)->firstByte, sizeof(long));
			return &(*f8Record)->firstByte;
		}
		else {
			ASSERTPOINTER(kDarick, &(*f6Record)->firstByte, sizeof(long));
			return &(*f6Record)->firstByte;
		}
	}
	else {
		DMSG((kDarick, "Using an invalid TSmartRecord\n"));
		return nil;
	}	
}


/***********************************************************************************

	TRecordItem ClassFunctions
	
************************************************************************************/


TRecordItem::TRecordItem( FieldStyle aFieldStyle, TNumStyleClass ns, Byte accuracy)
// 
//	Creates an object for handling the RecordItemType Union. Sets the field variables
//	fFieldStyle and fNumSyle based on the following parameters.
//
//	aFieldStyle		input	the style of the new field 
//	ns				input	for numeric fields this specifies the number style, the default value is nsGeneral
//	accuracy		input	for numeric fields this specifies the number of decimal places for the number
//
// 	WARNING: when aFieldStyle is equal to kFieldCoordDisp or kFieldCoordLocX or kFieldCoordLocY
// 	the ns parameter is ignored and fNumStyle is automatically set to nsDimension.
//
{	

	fSize = 0;
	fTextP = nil;
	fUserTextP = nil;
	fNumStyle.numStyleClass = ns;
	fNumStyle.accuracy = accuracy;

	switch ( aFieldStyle) {
		case kFieldLongInt: {
			fFieldStyle = kFieldLongInt; 
			this->SetFieldValue( (long) 0);
			break;
		}
		case kFieldBoolean: {
			fFieldStyle = kFieldBoolean; 
			this->SetFieldValue( (Boolean) false); 
			break;
		}
		case kFieldReal: {
			fFieldStyle = kFieldReal; 
			double_gs zeroVal = 0.0;
			this->SetFieldValue(zeroVal);			
			break;
		}
		case kFieldText:
		case kFieldCalculation:{
			fFieldStyle = kFieldText;
			break;
		}
		case kFieldHandle: {
			DMSG((kDarick,"aFieldStyle = %d, is out of range\n", aFieldStyle));
			fFieldStyle = kFieldLongInt; 
			this->SetFieldValue( (long) 0);
			break;
		}
		
		// New fields below. JAK 10/7/97 
		case kFieldCoordDisp: {
			fFieldStyle = kFieldCoordDisp;
			fNumStyle.numStyleClass = nsDimension;
			double_gs zeroVal = 0.0;
			this->SetFieldValue(zeroVal);		// call same SetFieldValue override as kFieldReal case
			break;
		}
		case kFieldPopUp: {
			fFieldStyle = kFieldPopUp;
			// this->SetFieldValue("", 0);		// maybe empty string?
			break;
		}

		case kFieldRadio: {
			fFieldStyle = kFieldRadio;
			// this->SetFieldValue("", 0);		// maybe empty string?
			break;
		}

		case kFieldCoordLocX: {
			fFieldStyle = kFieldCoordLocX;
			fNumStyle.numStyleClass = nsDimension;
			double_gs zeroVal = 0.0;
			this->SetFieldValue(zeroVal);		// call same SetFieldValue override as kFieldReal case
			break;
		}
		
		case kFieldCoordLocY: {
			fFieldStyle = kFieldCoordLocY;
			fNumStyle.numStyleClass = nsDimension;
			double_gs zeroVal = 0.0;
			this->SetFieldValue(zeroVal);		// call same SetFieldValue override as kFieldReal case
			break;
		}
		#if GS_TEST_NEW_UI_PARAMETERS_SAJ
		case kFieldUnderDev1:	// 12
			fFieldStyle = kFieldUnderDev1;
			break;
			
		case kFieldUnderDev2:	// 13
			fFieldStyle = kFieldUnderDev2;
			break;
			
		//case kFieldUnderDev3:	// 14
		//	fFieldStyle = 14;
		//	break;
		#endif	//GS_TEST_NEW_UI_PARAMETERS_SAJ

		case kFieldStaticText:
			fFieldStyle = kFieldStaticText;
			break;

		case kFieldDimStdPopUp:
			fFieldStyle = kFieldDimStdPopUp;
			this->SetFieldValue( (long) 0);
			break;

		case kFieldPrecisionPopUp:
			fFieldStyle = kFieldPrecisionPopUp;
			this->SetFieldValue(0L, 0L);
			break;

		default:{
			DMSG((kDarick,"aFieldStyle = %d, is out of range\n", aFieldStyle));
			fFieldStyle = kFieldLongInt; 
			this->SetFieldValue( (long) 0);
			break;
		}
		
	}
}


/*
Since this is a rather compilcated object, we have a common copy function called by both the
assignment operator and the copy constructor. Note that we must handle deleting pointers
from the to object if they exist. - PCP
*/

void TRecordItem::CopyTRecordItem_(const TRecordItem& from, TRecordItem& to)
{
	to.fValue = from.fValue;
	to.fFieldStyle = from.fFieldStyle;
	to.fSize = from.fSize;
	to.fNumStyle = from.fNumStyle;
	
	delete []to.fTextP; // this is necessary because we may be copying to a TRecordItem which has a value
	if(from.fTextP) {
		#if _MINICAD_
		to.fTextP = NEW_NOTHROW char[from.fSize];
		#else
		to.fTextP = new char[from.fSize];
		#endif
		if (to.fTextP) 
			memcpy(to.fTextP, from.fTextP, from.fSize);
		else
			to.fSize = 0;
	}
	else
		to.fTextP = nil;
	
	delete []to.fUserTextP; // this is necessary because we may be copying to a TRecordItem which has a value
	if(from.fUserTextP) {
		#if _MINICAD_
		to.fUserTextP = NEW_NOTHROW char[from.fSize];
		#else
		to.fUserTextP = new char[from.fSize];
		#endif
		if (to.fUserTextP) 
			memcpy(to.fUserTextP, from.fUserTextP, from.fSize);
	}
	else
		to.fUserTextP = nil;
}


TRecordItem::~TRecordItem()
//delete on nil ok; delete on non-new variable is bad
{
		delete []fTextP;
		delete []fUserTextP;
}


/* 
	- TRecordItem Get Functions; 
	- returns false if this->GetFieldStyle() != the type of theValue
*/

Boolean TRecordItem::GetFieldValue( long& theValue) const
{
	if ( (this->GetFieldStyle() != kFieldLongInt) &&
		 (this->GetFieldStyle() != kFieldDimStdPopUp))
	{
		DMSG((kDarick, "TRecordItem:: Get called with wrong parameter type\n"));
		return false;
	}
	else {
		theValue = fValue.longView;
		return true;
	}
}

Boolean TRecordItem::GetFieldValue( Boolean& theValue) const
{
	if ( this->GetFieldStyle() != kFieldBoolean) {
		DMSG((kDarick, "TRecordItem:: Get called with wrong parameter type\n"));
		return false;
	}
	else {
		theValue = fValue.boolView;
		return true;
	}
}

Boolean TRecordItem::GetFieldValue(double_gs& theValue) const
//
//	Return floating point value from my realView field.
//	This override is used for several field types now: real, coord displacement, coord location X and Y.
// 	JAK
//
{
	if ((this->GetFieldStyle() == kFieldReal) ||
		 (this->GetFieldStyle() == kFieldCoordDisp) ||
		 (this->GetFieldStyle() == kFieldCoordLocX) ||
		 (this->GetFieldStyle() == kFieldCoordLocY)) {
		theValue = fValue.realView;
		return true;
	}
	else {
		DMSG((kDarick | kJeff, "TRecordItem:: Get called with wrong parameter type\n"));
		return false;
	}
}

Boolean TRecordItem::GetFieldValue(long& lowValue, long& highValue) const
{
	if (this->GetFieldStyle() == kFieldPrecisionPopUp) {
		lowValue = fValue.longlongView[0];
		highValue = fValue.longlongView[1];
		return true;
	}
	else {
		DMSG((kDarick | kJGeraci, "TRecordItem:: Get called with wrong parameter type\n"));
		return false;
	}
}

Boolean TRecordItem::GetFieldValue( char*& text, size_t& size) 		
//
//	I create and own new space for string, and TRecordItem frees the space in destructor
//	This override is used for kFieldText, kFieldPopUp and kFieldRadio types now.
//	JAK 
//
{
	if (( this->GetFieldStyle() == kFieldText) ||
		(this->GetFieldStyle() == kFieldPopUp) ||
		(this->GetFieldStyle() == kFieldRadio) ||
		(this->GetFieldStyle() == kFieldStaticText)) 
	{
		delete [] fUserTextP;
		
		// I'm changing this to null-terminated! JDW - 7/26/00 
		#if _MINICAD_
		fUserTextP = NEW_NOTHROW char[fSize+1];
		#else
		fUserTextP = new char[fSize+1];
		#endif
		if (fUserTextP) {
			memcpy( fUserTextP, fTextP, fSize);//use memcpy std c
			size = fSize;
			text = fUserTextP;
			fUserTextP[fSize] = '\0';
			return true;
		}
		else {
			size = 0;
			text = nil;
			return false;
		}
	}
	else {
		DMSG((kDarick, "TRecordItem:: Get called with wrong parameter type\n"));
		return false;
	}
}

#if _MINICAD_
#if GS_WIN
	static Str255 gTRUE_Str("");
	static Str255 gFALSE_Str("");
	static Str255 gYES_Str("");
#elif GS_MAC
	static Str255 gTRUE_Str = "\0";
	static Str255 gFALSE_Str = "\0";
	static Str255 gYES_Str = "\0";
#endif
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
static PString GetCachedMiscString(Str255 inGlobalString, short inMiscStrID)
{
	if (IsEmpty(inGlobalString)) 
		GS_GetResourceString(gCBP, kMiscStringID, inMiscStrID, inGlobalString, false);
	return inGlobalString; 
} 
#endif

//*****************************************************************************
//
// Function Name:  FieldValueToString
//
// Description:
//	Given the data container for a record field, this routine extracts the
//	field's value and converts it to a Pascal string.  Integer, real, and
//	Boolean field types are supported (the handle address is returned for
//	handle fields, which is probably not very useful).  Numerical values are
//	formatted as per the specified style, while the localized True/False terms
//	are used for Boolean values.
//
//	NOTE: This routine does not handle fields of type kFieldText or kFieldCalculation
//	      because the text extraction is most efficiently done by a routine
//	      that is closer to the data source (be it RecordItem or RecordNode).
//	      See TRecordItem::GetFieldValueAsString and GetFieldString callers.
//
// Parameters:
//	recItem   - Pointer to the data container for the record field
//	fldType   - Type of the record field (kFieldLongInt, kFieldReal, etc.)
//	numStyle  - Display style for numeric value (nsGeneral, nsFixDecimal, etc.)
//	theString - Returns a Pascal string representing the field's value
//	bMaxAccuracy - Set to true to use the maximum accuracy possible.  Added 1/30/04 by JAG.
//
// Return Value:
//	Whether the field value was successfully converted to a string
//	(note that some text useful for display may be returned when not the value)
//
// Created by:  Michael Hartman, June 1997, based on original GetFieldString
//
//*****************************************************************************

static Boolean FieldValueToString (const RecordItemPtr recItem, FieldStyle fldType,
                                    TNumStyle numStyle, PStr255& theString, bool bMaxAccuracy)
{
	#if !_MINICAD_
	bMaxAccuracy;
	#endif
	
	Boolean			gotString;
	double_gs		dblFromLong;
	double_gs		doubleVal;
	
	ASSERTN(kMike, (recItem != nil));
	
	gotString = false;
	theString = NULLSTR;
	
	switch (fldType) {
		case kFieldLongInt:
			dblFromLong = recItem->longView;

			if (IsIllegal(dblFromLong))
				theString << '0';
			else {
				GS_DoubleToString(gCBP, numStyle.numStyleClass, numStyle.accuracy, 0, dblFromLong, theString);
				gotString = true;
			}
			break;
			
		case kFieldReal: {
			// split into its own case because plug-in field types need special stuff JAK 7/23/99
			doubleVal = recItem->realView;
			if (IsIllegal(doubleVal))
				theString << '0';
			else {
				GS_DoubleToString(gCBP, numStyle.numStyleClass, numStyle.accuracy, 0, doubleVal, theString);
				gotString = true;
			}
			break;
		}
			
		case kFieldCoordDisp:
		case kFieldCoordLocX:		// Maybe LocX and LocY should have own case and call the UserCoord2UnitString() to offset by user origin component. EvalStr doesn't but ObjInfo palette does.
		case kFieldCoordLocY: {		// XXX_JDW_MISC - User Origin - not using origin
			doubleVal = recItem->realView;
			WorldCoord myCoord;
			
			myCoord = doubleVal;

			if (IsIllegal(doubleVal))
				theString << '0';
			else {
				// WorldCoord2UnitString(xc, theString);		// Coords in, string out, does not consider user origin. XXX_JDW_MISC - User Origin - review

			#if _MINICAD_
				if (bMaxAccuracy) {
					// Need to use WorldCoord2UnitStringMaxAccuracy because GS_CoordToDimString assumes document
					// accuracy, which is not always what we want.
					WorldCoord2UnitStringMaxAccuracy(myCoord, theString);
				}
				else {												// We used to call GS_DoubleToString() here but that was wrong.
					GS_CoordToDimString(gCBP, myCoord, theString);	// GS_CoordToDimString() calls WorldCoord2UnitString().
				}

				gotString = true;
			#else
				GS_CoordToDimString(gCBP, myCoord, theString);	// GS_CoordToDimString() calls WorldCoord2UnitString().
				gotString = true;
			#endif
			}
			
			break;
		}
			
		case kFieldBoolean:
			#if _MINICAD_
			if (recItem->boolView)
				theString = GetCachedMiscString(gTRUE_Str, 6);
			else
				theString = GetCachedMiscString(gFALSE_Str, 7);
			#else
				psprintf( theString, recItem->boolView ? "true" : "false");
			#endif
			gotString = true;
			break;
			
		case kFieldText:
		case kFieldCalculation:
		case kFieldPopUp:
		case kFieldRadio:
		case kFieldStaticText:
			DSTOP((kMike, "FieldValueToString - Field types of kFieldText or kFieldCalculation or kFieldPopUp not supported here!"));
			theString = "#Text not retrieved";
			break;
			
		case kFieldHandle:
			theString << (long)recItem->handleView;
			gotString = true;
			break;

		case kFieldDimStdPopUp:
		case kFieldPrecisionPopUp:
			theString << recItem->longlongView[1] << ", " << recItem->longlongView[0];
			gotString = true;
			break;

		#if GS_TEST_NEW_UI_PARAMETERS_SAJ
		case kFieldUnderDev1:	// 12
		case kFieldUnderDev2:	// 13
		//case kFieldUnderDev3:	// 14
			theString = NULLSTR;
			break;
		#endif	//GS_TEST_NEW_UI_PARAMETERS_SAJ
		
		default:
			DSTOP((kMike, "FieldValueToString - Unrecognized field type of %d!", fldType));
			theString = "#Unknown field type";
			break;
	}
	
	return(gotString);
} /* FieldValueToString */


Boolean TRecordItem::GetFieldValueAsString (Str255 theString) const
// Returns a string version of the data value
/* Best not to use for text fields, as maximum of 255 characters is returned */
// JAK Use for kFieldPopUp type also.
{
	Boolean			gotString;
	FieldStyle		fieldType;
	size_t			stringSize;
	RecordItemType	recItem;
	PStr255			stringObj;
	
	ASSERTN(kMike, (theString != nil));
	
	fieldType = this->GetFieldStyle();
	if ((fieldType == kFieldText) || (fieldType == kFieldCalculation) || (fieldType == kFieldPopUp) || (fieldType == kFieldRadio) ||
		(fieldType == kFieldStaticText)) {
		// special processing for text fields.
		if (fSize >= sizeof(Str255))
			stringSize = sizeof(Str255) - 1; 
		else
			stringSize = fSize;
		memcpy(&theString[1], fTextP, stringSize);
		SetLength(theString, stringSize);
		gotString = true;
		}
	else {
		// non-text fields get passed to conversion function
		recItem = fValue;  /* Can't pass private field by address */
		gotString = FieldValueToString(&recItem, fieldType, fNumStyle, stringObj, false);
		StringCopy(stringObj, theString, sizeof(Str255));  /* stringObj always set */
	}
	return(gotString);
} /* TRecordItem::GetFieldValueAsString */


void TRecordItem::GetNumStyle(TNumStyleClass& numStyle, Byte& accuracy) const
{
	numStyle = fNumStyle.numStyleClass;
	accuracy = fNumStyle.accuracy;
}

/* 
	- TRecordItem Set Functions; 
	- returns false if this->GetFieldStyle() != the type of theValue
*/

Boolean TRecordItem::SetFieldValue( long theValue)
{
	if ( (this->GetFieldStyle() != kFieldLongInt) &&
		 (this->GetFieldStyle() != kFieldDimStdPopUp))
	{
		DMSG((kDarick, "TRecordItem:: Set called with wrong parameter type\n"));
		return false;
	}
	else {
		fValue.longView = theValue;
		return true;
	}
}

Boolean TRecordItem::SetFieldValue(long lowValue, long highValue)
{
	if (this->GetFieldStyle() != kFieldPrecisionPopUp)
	{
		DMSG((kJGeraci, "TRecordItem:: Set called with wrong parameter type\n"));
		return false;
	}
	else {
		fValue.longlongView[0] = lowValue;
		fValue.longlongView[1] = highValue;
		return true;
	}
}

Boolean TRecordItem::SetFieldValue(Boolean theValue)
{
	if ( this->GetFieldStyle() != kFieldBoolean){
		DMSG((kDarick, "TRecordItem:: Set called with wrong parameter type\n"));
		return false;
	}
	else {
		fValue.boolView = theValue;
		return true;
	}
}

Boolean TRecordItem::SetFieldValue(double_param theValue)
//
//	Set the realView field of the TRecordItem object with value passed in.
//	Accept several field types now and store them all in the same field.
//	JAK 
//
{
	if ((this->GetFieldStyle() == kFieldReal) || 
		(this->GetFieldStyle() == kFieldCoordDisp) || 
		(this->GetFieldStyle() == kFieldCoordLocX) ||
		(this->GetFieldStyle() == kFieldCoordLocY))
	{
		fValue.realView = theValue;
		return true;
	}
	else {
		DMSG((kDarick | kJeff, "TRecordItem:: Set called with wrong parameter type\n"));
		return false;
	}
}

Boolean TRecordItem::SetFieldValue(const char* text, size_t size)
//
//	JAK Use for popup and radio types also.
//
{
	if (( this->GetFieldStyle() != kFieldText) && 
		(this->GetFieldStyle() != kFieldPopUp) &&
		(this->GetFieldStyle() != kFieldRadio) &&
		(this->GetFieldStyle() != kFieldStaticText)) {
		DMSG((kDarick, "TRecordItem:: Set called with wrong parameter type\n"));
		return false;
	}
	else {
		delete[] fTextP;				// JAK 6/30/99 Avoid leaks by doing array delete to match array allocation.
		#if _MINICAD_
		fTextP = NEW_NOTHROW char[size];
		#else
		fTextP = new char[size];
		#endif
		if (fTextP) {
			memcpy( fTextP, text, size);
			fSize = size;
			return true;
		}
		else {
			fSize = 0;
			return false;
		}
	}
}


//*****************************************************************************
//
// Function Name:  StringToFieldValue
//
// Description:
//	Given the data container for a record field, this routine sets the field's
//	value to that represented in the provided Pascal string.  Integer, real,
//	and Boolean field types are supported.  Real values are expected to be
//	formatted as per the specified style.  Boolean values are true if the
//	the string is the localized "True" or "Yes", or the first letter of "True".
//
//	NOTE: This routine does not handle fields of type kFieldText or kFieldCalculation
//	      because the text setting is most efficiently done by a routine
//	      that is closer to the data source (be it RecordItem or RecordNode).
//	      See TRecordItem::SetFieldValueAsString and SetFieldToString callers.
//
// Parameters:
//	theString - Pascal string containing the field value to set
//	fldType   - Type of the record field (kFieldLongInt, kFieldReal, etc.)
//	numStyle  - String style for real value (nsGeneral, nsFixDecimal, etc.)
//	recItem   - Pointer to the data container for the record field
//
// Return Value:
//	Whether the string was successfully converted to a field value
//	(note that the value will be "zeroed" when the string is invalid)
//
// Created by:  Michael Hartman, June 1997, based on original SetFieldToString
//
//*****************************************************************************

static Boolean StringToFieldValue (const Str255 theString, FieldStyle fldType,
                                    TNumStyle numStyle,  RecordItemPtr recItem)
{
	Boolean			stringValid, isNumStr;
	double_gs		doubleVal;
	PStr255			trueStr;
	PStr255			trueInitial;
	PStr255			yesStr;


	ASSERTN(kMike, ((recItem != nil) && (theString != nil)));
	
	stringValid = false;
	
	switch (fldType) {
		case kFieldLongInt:
			isNumStr = GS_StringToDouble(gCBP, numStyle.numStyleClass, theString, doubleVal);
			//if (!isNumStr || /*isnan(doubleVal)*/0)

			if (!isNumStr || IsNAN(doubleVal))
				recItem->longView = 0;
			else {
				recItem->longView = DoubleToLong(doubleVal);
				stringValid = true;
			}
			break;
			
		case kFieldReal: {
			doubleVal = 0.0;  /* Value if string not a number */
			isNumStr = GS_StringToDouble(gCBP, numStyle.numStyleClass, theString, doubleVal);	// Returns WorldCoords for dimension styles

			double_gs d = doubleVal; 
				
			if (numStyle.numStyleClass == nsDimension || numStyle.numStyleClass == nsDimensionWithDivider) {
				// Kludge it so that doubleVal is interpreted as feet
				double		worldCoordsPerUnit;
				short		unitFormat;

				GS_GetProgramVariable(gCBP, varUnit1Fraction, &worldCoordsPerUnit);	
				GS_GetProgramVariable(gCBP, varUnit1Format, &unitFormat);

				if (unitFormat == ufFeetAndInches)
					d /= 12;

				d /= worldCoordsPerUnit;
			}
			else if (numStyle.numStyleClass == nsDimensionArea) {

				double worldCoordsSquaredPerUnit;
				
				GS_GetProgramVariable(gCBP, varUnit1AreaFraction, &worldCoordsSquaredPerUnit);

				d /= worldCoordsSquaredPerUnit;
			}
			else if (numStyle.numStyleClass == nsDimensionVolume) {

				double worldCoordsCubedPerUnit;
				
				GS_GetProgramVariable(gCBP, varUnit1VolumeFraction, &worldCoordsCubedPerUnit);

				d /= worldCoordsCubedPerUnit;
			}

			recItem->realView = d;
			stringValid = isNumStr;
			break;
		}
		case kFieldCoordDisp:		// JAK	Hopefully the caller passed in dimension numstyle for WorldCoord case.
		case kFieldCoordLocX:
		case kFieldCoordLocY: {		// XXX_JDW_MISC - not using origin
			// Convert string to Coords, converting units if necessary.
			WorldCoord c = 0.0;
			isNumStr = GS_DimStringToCoord(gCBP, theString, c);	
			// еее JAK Maybe GS_DimStringToCoord should take NumStyle arg like GS_StringToDouble does.

			recItem->realView = c;
			stringValid = isNumStr;
			break;
		}
			
		case kFieldBoolean: {
			#if _MINICAD_
			trueStr = GetCachedMiscString(gTRUE_Str, 6);
			yesStr  = GetCachedMiscString(gYES_Str, 69);
			#else
			trueStr = "TRUE";
			yesStr = "Yes";
			#endif

			trueInitial = trueStr;
			trueInitial.Truncate(1);

			#if 0
			recItem->boolView =
				(IUMagIDString(&theString[1], &trueStr[1], theString[0], trueStr[0]) == 0) ||
				(IUMagIDString(&theString[1], &trueStr[1], theString[0],          1) == 0) ||
				(IUMagIDString(&theString[1], &yesStr[1],  theString[0], yesStr[0])  == 0) ||
				(IUMagIDString(&theString[1], "TRUE", theString[0], 4) == 0) ||  /* Legacy pre-8 */
				(IUMagIDString(&theString[1], "Yes",  theString[0], 3) == 0);    /* Legacy pre-8 */
			#else
			recItem->boolView =
				StringEqualIgnoringCase(theString, trueStr) ||
				StringEqualIgnoringCase(theString, trueInitial) ||
				StringEqualIgnoringCase(theString, yesStr) ||
				StringEqualIgnoringCase(theString, "TRUE") ||  /* Legacy pre-8 */
				StringEqualIgnoringCase(theString, "Yes")||    /* Legacy pre-8 */
				StringEqualIgnoringCase(theString, "1");
			#endif

			stringValid = true;
			break;
		}
		case kFieldText:
		case kFieldCalculation:
		case kFieldPopUp:
		case kFieldRadio:
		case kFieldStaticText:
			DSTOP((kMike, "StringToFieldValue - Field types of kFieldText or kFieldCalculation or kFieldPopUp not supported here!"));
			break;
			
		case kFieldHandle:
			DSTOP((kMike, "StringToFieldValue - Attempt to set handle field using string!"));
			recItem->handleView = 0;
			break;

		case kFieldDimStdPopUp: {
			PStr255  tempStr(theString);
			::memset(tempStr, 0, sizeof(PStr255));

			recItem->longView = atol((char*)&tempStr[1]);

			stringValid = true;
			break;
		}

		case kFieldPrecisionPopUp: {
			PStr255  tempStr(theString);
			PStr255  lowValueStr, highValueStr;

			::memset(lowValueStr, 0, sizeof(PStr255));
			::memset(highValueStr, 0, sizeof(PStr255));

			// The left 8 characters are for the high value, and the next 8 characters
			// are for the low value.
			highValueStr = tempStr.Left(8);
			lowValueStr = tempStr.Mid(8, 8);

			recItem->longlongView[0] = atol((char*)&lowValueStr[1]);
			recItem->longlongView[1] = atol((char*)&highValueStr[1]);

			stringValid = true;
			break;
		}

		#if GS_TEST_NEW_UI_PARAMETERS_SAJ
		case kFieldUnderDev1:
		case kFieldUnderDev2:
		//case kFieldUnderDev3:
			stringValid = true;
			break;
		#endif	//GS_TEST_NEW_UI_PARAMETERS_SAJ

		default:
			DSTOP((kMike, "StringToFieldValue - Unrecognized field type of %d!", fldType));
			break;
	}
	
	return(stringValid);
} /* StringToFieldValue */


Boolean TRecordItem::SetFieldValueAsString (Str255 theString)
// Sets the data value from a string version
/* Best not to use for text fields, as maximum of 255 characters can be passed */
// JAK Use for popup type.
{
	Boolean			setValidly;
	FieldStyle		fieldType;
	RecordItemType	recItem;
	
	ASSERTN(kMike, (theString != nil));
	
	fieldType = this->GetFieldStyle();
	if ((fieldType == kFieldText) || (fieldType == kFieldCalculation) || (fieldType == kFieldPopUp) || (fieldType == kFieldRadio) || (fieldType == kFieldStaticText)) {
		setValidly = this->SetFieldValue((char*)&theString[1], GetLength(theString));
		}
	else {
		setValidly = StringToFieldValue(theString, fieldType, fNumStyle, &recItem);
		fValue = recItem;
	}
	return(setValidly);
} /* TRecordItem::SetFieldValueAsString */


void TRecordItem::SetNumStyle(TNumStyleClass numStyle, Byte accuracy)
{
	fNumStyle.numStyleClass = numStyle;
	fNumStyle.accuracy = accuracy;
}


/***********************************************************************************

	TFormatBase ClassFunctions 
	
	-used in TRecordHandler and TFormatHandler
	
************************************************************************************/


TFormatBase::TFormatBase(MCObjectHandle f)
{
	fFormat = (FormatHandle) f;
	sFormat = nil;
	sFormat = (TSmartFormat *) new TSmartFormat((FormatHandle) f);
}

TFormatBase::~TFormatBase()
{
	delete sFormat;
}

void TFormatBase::GetNameOfFormat( Str63 nameOfFormat)	const
{
	GS_GetObjectName( gCBP, (MCObjectHandle) fFormat, nameOfFormat);
}

short TFormatBase::GetNumOfFields() const  
// Checks fFormat for NIL and returns the number fields
{	
	short numFields = 0;
	
	if( sFormat->Verify())
		numFields = *sFormat->NumberOfFields();
		
	return numFields;
}

void TFormatBase::GetNameOfField( short fieldIndex, Str20 nameOfField) const
{
	SetLength(nameOfField, 0);
	
	if( (fieldIndex <= this->GetNumOfFields()) && (fieldIndex >= 1)){
		ASSERTPOINTER(kDarick, nameOfField, sizeof(Str20));
		ASSERTPOINTER(kDarick, (*sFormat->FieldList())[ fieldIndex - 1].fieldName, sizeof(Str20));
		memcpy( nameOfField, (*sFormat->FieldList())[ fieldIndex - 1].fieldName, sizeof(Str20));
	}
	else {
		DMSG((kDarick, "fieldIndex = %d is out of range (1 - %d)\n", fieldIndex, this->GetNumOfFields()));
	}
}

short TFormatBase::GetFieldIndex( const Str20 nameOfField) const
{
	int i, numFields;
	CStr20 cName;
	
	numFields = this->GetNumOfFields(); // GetNumOfFields checks fFormat for NIL
	cName = nameOfField;
	for( i = 1; i <= numFields; i++){
		if( cName == (unsigned char*) (*sFormat->FieldList())[ i-1].fieldName)
			return i;
	}
	DMSG((kDarick, "TFormatBase::GetFieldIndex - String not found, returning 0"));
	return 0; // no match found
}

FieldStyle TFormatBase::GetFieldStyle( short fieldIndex) const
{
	if( (fieldIndex <= this->GetNumOfFields()) && (fieldIndex >= 1))
		return (*sFormat->FieldList())[ fieldIndex - 1].fieldID;
	else {
		DMSG((kDarick, "fieldIndex = %d is out of range (1 - %d)\n", fieldIndex, this->GetNumOfFields()));
		return 0;
	}
}

Boolean TFormatBase::GetFieldNumStyle( short fieldIndex, TNumStyleClass& numStyle, Byte& accuracy)
// Get the number style of the format
// returns false if fieldIndex is out of range
{
	FormatHandle format = (FormatHandle)this->GetFormatHandle();
	FieldType fieldFormatInfo;		// the format of the draw object data
	
	if ( VERIFYN(kDarick, fieldIndex >= 1 && fieldIndex <= this->GetNumOfFields())) {
		fieldFormatInfo = GetFieldInfo(format, fieldIndex);
		numStyle = fieldFormatInfo.numStyle.numStyleClass;
		accuracy = fieldFormatInfo.numStyle.accuracy;
		return true;
	}
	else
		return false;
}


Boolean TFormatBase::SetFieldNumStyle( short fieldIndex, TNumStyleClass numStyle, Byte accuracy)
// set the number syle of the format
// returns false if fieldIndex is out of range
{
	FormatHandle format = (FormatHandle)this->GetFormatHandle();
	FieldPtr fieldFormatPtr;		// the format of the draw object data
	fieldFormatPtr = GetFieldInfoPtr(format, fieldIndex);
	
	if (VERIFYN(kDarick, fieldIndex >= 1 && fieldIndex <= this->GetNumOfFields()) 
			&& VERIFYPOINTER(kDarick, fieldFormatPtr, sizeof(FieldType))) {
		fieldFormatPtr->numStyle.numStyleClass = numStyle;
		fieldFormatPtr->numStyle.accuracy = accuracy;
		return true;
	}
	else
		return false;
}


void TFormatBase::SetFormat(FormatHandle f)
// sets fFormat and sFormat
{
	fFormat = f;
	sFormat->SetFormat( f);
}

//
// JAK: These parametric functions should be inlined but RH.x does not include RecordTypes.h, it only declares "class TSmartFormat".
// So either include it, or move definition of TSmartFormat to RH.h.
//

void TFormatBase::SetIsParametric(Boolean is) 
{ 
	sFormat->SetIsParametric(is); 
}

Boolean TFormatBase::IsParametric() const
{ 
	return sFormat->IsParametric();
}

void TFormatBase::SetParametricVersion(short ver) 
{ 
	sFormat->SetParametricVersion(ver);
}

short TFormatBase::GetParametricVersion() 
{ 
	return sFormat->GetParametricVersion();
}

//	New for Hidden Formats:

static inline MCObjectHandle GetActiveDataContainer()
{
	return GS_FindAuxObject(gCBP, GS_GetDrawingHeader(gCBP), kDataNode);
}

#ifdef _MINICAD_EXTERNAL_
const OSType kHiddenFormatContainerType = 'HFMT'; 	//	Hidden Format Container.  Duplicated in Objs.UserDataType.h
#endif

static void AddToInvisFormatContainer(MCObjectHandle ioFormat)
{	
	MCObjectHandle hiddenFormatContainer = GS_FindDataObject(gCBP, GetActiveDataContainer(), kHiddenFormatContainerType);
	
	if (hiddenFormatContainer) {
	
		GS_AttachAuxObject(gCBP, (MCObjectHandle) ioFormat, hiddenFormatContainer);
	}
	else {
		GS_AttachAuxObject(gCBP, (MCObjectHandle) ioFormat, GS_NewDataObject(gCBP, GetActiveDataContainer(), kHiddenFormatContainerType, sizeof(UserDataType)));
	}
}

static void RemoveInvisFormatContainerIfEmpty_()
{
	MCObjectHandle hiddenFormatContainer = GS_FindDataObject(gCBP, GetActiveDataContainer(), kHiddenFormatContainerType);
	
	if (GS_FindAuxObject(gCBP, hiddenFormatContainer, kFormatNode) == nil) {
	
		MCObjectHandle dataContainer = GetActiveDataContainer();
		GS_RemoveAuxObject(gCBP, hiddenFormatContainer, dataContainer);
		GS_DeleteObject(gCBP, hiddenFormatContainer);
		
		hiddenFormatContainer = GS_FindDataObject(gCBP, dataContainer, kHiddenFormatContainerType);
	}
}

static void RemoveFromInvisFormatContainer(MCObjectHandle ioFormat)
{
	MCObjectHandle hiddenFormatContainer = GS_FindDataObject(gCBP, GetActiveDataContainer(), kHiddenFormatContainerType);
	
	if (hiddenFormatContainer) {
	
		MCObjectHandle format = GS_FindAuxObject(gCBP, hiddenFormatContainer, kFormatNode);

		while (format) {
		
			if (format == ioFormat) {
			
				GS_RemoveAuxObject(gCBP, ioFormat, hiddenFormatContainer);
								
				GS_AddObjectToContainer(gCBP, ioFormat, GetActiveDataContainer());
				
				break;
			}
			
			format = GS_NextObject(gCBP, format);
		}
		
		::RemoveInvisFormatContainerIfEmpty_();
	}
}

void TFormatBase::HideFromUserInterface(Boolean inHideIt)
{ 
	if (inHideIt)
		::AddToInvisFormatContainer((MCObjectHandle) fFormat);
	else 
		::RemoveFromInvisFormatContainer((MCObjectHandle) fFormat);
}

Boolean TFormatBase::IsHiddenFromUserInterface() const
{
	return 	this->IsParametric() || GS_ParentObject(gCBP, (MCObjectHandle)fFormat) == nil;	// Hidden formats stored in aux list of 'HFMT' user data node
}

/***********************************************************************************

	TRecordHandler Class Functions
	
************************************************************************************/

TRecordHandler::TRecordHandler( MCObjectHandle r) 
				: TFormatBase( GS_GetDefinition( gCBP, (MCObjectHandle) r))
{
	fRecord = (RecordHandle) r;
}

#ifndef _MINICAD_EXTERNAL_
TRecordHandler::TRecordHandler (MCObjectHandle r, MCObjectHandle fmtDef) 
				: TFormatBase(fmtDef)
{
	fRecord = (RecordHandle) r;
}
#endif

Boolean TRecordHandler::GetFieldObject( short fieldIndex, TRecordItem& theValue, Boolean needsByteSwap) 
// Places a copy of the requested field in theValue 
{
	FormatHandle format = (FormatHandle)this->GetFormatHandle();
	FieldType fieldFormatInfo;		// the format of the draw object data
	RecordItemPtr data; 			// the data in the draw object's record
	const char * text;
	FieldStyle theFieldStyle = (FieldStyle) this->GetFieldStyle( fieldIndex); 
	Boolean myReturn = false;				
	
	//error checking
	if ( (fieldIndex >= 1) && (fieldIndex <= this->GetNumOfFields()) &&
		( theValue.GetFieldStyle() == theFieldStyle) ){
	
		// set theValue with the data record info	
		fieldFormatInfo = GetFieldInfo(format, fieldIndex);
		data = GetField( fRecord, fieldFormatInfo);
		ASSERTPOINTER(kDarick, data, FieldSize(theFieldStyle));
		if (fieldFormatInfo.fieldOffset >= 0){
			switch ( theFieldStyle) {
				case kFieldLongInt:
				case kFieldDimStdPopUp: {			// JAG
					long value = data->longView;
					if (needsByteSwap)
						ByteSwapLONG(&value);
					theValue.SetFieldValue( value);
					myReturn = true;
					break;
				}

				case kFieldBoolean: {
					theValue.SetFieldValue( data->boolView); 
					myReturn = true;
					break;
				}

				case kFieldReal:
				case kFieldCoordDisp:
				case kFieldCoordLocX:
				case kFieldCoordLocY: {
					double value;
					memcpy(&value, &(data->realView), sizeof(double));
					if (needsByteSwap)
						ByteSwapDouble(&value);
					theValue.SetFieldValue(value); 
					myReturn = true;
					break;
				}

				case kFieldText:
				case kFieldCalculation:
				case kFieldPopUp:		// JAK
				case kFieldStaticText:	// JAG
				case kFieldRadio: {		// JAK
					text = GetTextField( fRecord, data);
					if(data->textView.offset >= 0 && 
						VERIFYPOINTER(kDarick, text, data->textView.size)){
						theValue.SetFieldValue( text, data->textView.size);
						myReturn = true;
					}
					else{
						myReturn = false;
					}
					
					break;
				}

				case kFieldHandle: {
					DMSG((kDarick, "GetFieldObject: theFieldStyle = %d is out of range\n", theFieldStyle));
					myReturn = false;
					break;
				}

				#if GS_TEST_NEW_UI_PARAMETERS_SAJ
				case kFieldUnderDev1:
				case kFieldUnderDev2:
				//case kFieldUnderDev3:
					myReturn = true;
					break;
				#endif	//GS_TEST_NEW_UI_PARAMETERS_SAJ

				case kFieldPrecisionPopUp: {	// JAG
					long lowValue, highValue;

					lowValue = data->longlongView[0];
					highValue = data->longlongView[1];

					if (needsByteSwap) {
						ByteSwapLONG(&lowValue);
						ByteSwapLONG(&highValue);
					}

					theValue.SetFieldValue(lowValue, highValue);
					myReturn = true;
					break;
				}

				default: {
					DMSG((kDarick, "GetFieldObject: theFieldStyle = %d is out of range\n", theFieldStyle));
					myReturn = false;
				}
			}
		}
		else{
			DMSG((kHTsafak, "Bogus record node with negative offset(%d) found in field Nr: %d\n", fieldFormatInfo.fieldOffset, fieldIndex));
			myReturn = false;
		}
		// Set the numStyle
		TNumStyleClass numStyle;
		Byte accuracy;
		this->GetFieldNumStyle(fieldIndex, numStyle, accuracy);
		theValue.SetNumStyle(numStyle, accuracy);
	}
	else
		myReturn = false;
	return myReturn;
}

#if 1
#define CALLUPDATEINFOPAL !this->IsHiddenFromUserInterface()
#else
#define CALLUPDATEINFOPAL true
#endif

FieldPtr GetFieldInfoPtr(FormatHandle theFormat, short field);

Boolean TRecordHandler::SetFieldObject( short fieldIndex, TRecordItem& theValue) 
//
// Places the TRecordItem value into the graphic object's RecordItem data field
//
{
	FormatHandle format = (FormatHandle)this->GetFormatHandle();
	FieldType fieldFormatInfo;		// the format of the draw object data
	RecordItemPtr data;				// the data in the draw object's record
	char * text;
	size_t textSize;
	FieldStyle theFieldStyle = (FieldStyle) this->GetFieldStyle( fieldIndex); 
	Boolean myReturn;
	

	// error checking
	if ( (fieldIndex >= 1) && (fieldIndex <= this->GetNumOfFields()) &&
		( theValue.GetFieldStyle() == theFieldStyle) ){
	
		// change the data record 
		fieldFormatInfo = GetFieldInfo(format, fieldIndex);
		switch ( theFieldStyle) {
			case kFieldLongInt:
			case kFieldDimStdPopUp: {			// JAG
				data = GetField(fRecord, fieldFormatInfo);
				ASSERTPOINTER(kDarick, data, FieldSize(theFieldStyle));
				theValue.GetFieldValue(data->longView);
				myReturn = true;
				break;
			}

			case kFieldBoolean: {
				data = GetField(fRecord, fieldFormatInfo);
				ASSERTPOINTER(kDarick, data, FieldSize(theFieldStyle));
				theValue.GetFieldValue(data->boolView); 
				myReturn = true;
				break;
			}

			case kFieldReal:
			case kFieldCoordDisp:
			case kFieldCoordLocX:
			case kFieldCoordLocY: {
				data = GetField(fRecord, fieldFormatInfo);
				ASSERTPOINTER(kDarick, data, FieldSize(theFieldStyle));
				double_gs dt; 
				theValue.GetFieldValue(dt); 
				data->realView = dt;
				myReturn = true;
				break;
			}
			
			case kFieldText:
			case kFieldCalculation: 		//handles the special case of a text record
			case kFieldStaticText:	// JAG
			case kFieldPopUp:		// JAK 11/4/97
			case kFieldRadio: {		// JAK
				ExpandRecord( format, fRecord);
				data = GetField(fRecord, fieldFormatInfo);
				ASSERTPOINTER(kDarick, data, FieldSize(theFieldStyle));
				theValue.GetFieldValue(text, textSize);
				ASSERTPOINTER(kDarick, text, textSize);
				if( DDSetHandleSize( data->handleView, textSize))
					memcpy( *data->handleView, text, textSize );
				CompactRecord( format, fRecord);
				myReturn = true;
				break;
			}
			case kFieldHandle: {
				DMSG((kDarick, "SetFieldObject: theFieldStyle = %d is not implemented?\n", theFieldStyle));
				myReturn = false;
				break;			
			}
			
			#if GS_TEST_NEW_UI_PARAMETERS_SAJ
			case kFieldUnderDev1:
			case kFieldUnderDev2:
			//case kFieldUnderDev3:
				myReturn = true;
				break;
			#endif	//GS_TEST_NEW_UI_PARAMETERS_SAJ

			case kFieldPrecisionPopUp: {	// JAG
				data = GetField(fRecord, fieldFormatInfo);
				ASSERTPOINTER(kJGeraci, data, FieldSize(theFieldStyle));
				theValue.GetFieldValue(data->longlongView[0], data->longlongView[1]);
				myReturn = true;
				break;
			}

			default: {
				DMSG((kDarick, "SetFieldObject: theFieldStyle = %d is out of range\n", theFieldStyle));
				myReturn = false;
			}
		}
		// Get numStyle and set it in the format
		TNumStyleClass numStyle;
		Byte accuracy;
		theValue.GetNumStyle(numStyle, accuracy);
		this->SetFieldNumStyle(fieldIndex, numStyle, accuracy);
		
		if (CALLUPDATEINFOPAL)
			UpdateInfoPal();
		
	} else
		myReturn = false;
		
	return myReturn;
	
}


static Boolean RecordFieldsEqual(TRecordHandler& handler1, TRecordHandler& handler2, short index, Boolean firstNeedsByteSwap)
// Returns true if the handlers' fields have equal type and value at the given index 
{
	Boolean fieldsEqual = false;

	if (handler1.GetFieldStyle(index) == handler2.GetFieldStyle(index)) {

		FieldStyle fieldStyle = handler1.GetFieldStyle(index);
		TRecordItem item1(fieldStyle);
		TRecordItem item2(fieldStyle);

		if (handler1.GetFieldObject(index, item1, firstNeedsByteSwap) && handler2.GetFieldObject(index, item2)) {

			switch (fieldStyle) {

				case kFieldLongInt:
				case kFieldHandle: {

					long value1;
					long value2;
					if (item1.GetFieldValue(value1) && item2.GetFieldValue(value2)) {
						fieldsEqual = value1 == value2;
					}

					break;
				}
				case kFieldBoolean: {
				
					Boolean value1;
					Boolean value2;
					if (item1.GetFieldValue(value1) && item2.GetFieldValue(value2))
						fieldsEqual = value1 == value2;

					break;
				}
				case kFieldReal:
				case kFieldCoordDisp:
				case kFieldCoordLocX:
				case kFieldCoordLocY: {
				
					double value1;
					double value2;
					if (item1.GetFieldValue(value1) && item2.GetFieldValue(value2)) {
						fieldsEqual = DoublesAreNearlyEqual(value1, value2);
					}

					break;
				}
				case kFieldText:
				case kFieldCalculation:
				case kFieldPopUp:
				case kFieldStaticText:
				case kFieldRadio: {

					char* value1 = nil;
					char* value2 = nil;
					size_t size1;
					size_t size2;
					if (item1.GetFieldValue(value1, size1) && item2.GetFieldValue(value2, size2)) {
					
						fieldsEqual = strlen(value1) == strlen(value2) && strcmp(value1, value2) == 0;
					}

					break;
				}
				
				default:
					DSTOP((kDaveD, "### RecordFieldsEqual - Unknown field style %d!\n", fieldStyle));
			} 	// switch
		}
	}
	
	return fieldsEqual;
}

Boolean TRecordHandler::RecordsEqual(MCObjectHandle rec, Boolean thisRecNeedsByteSwap)
// Returns true if all fields are identical between the object's record and the passed in record
{
	bool recordsEqual = false;

	if (VERIFYHANDLE(kDaveD, rec, 1)) {

		TRecordHandler handler(rec);
		
		if (this->GetNumOfFields() == handler.GetNumOfFields()) {

			recordsEqual = true; 	// Until found otherwise

			for (short i = 1; i <= this->GetNumOfFields() && recordsEqual; i++)
				recordsEqual = RecordFieldsEqual(*this, handler, i, thisRecNeedsByteSwap);
		}
	}

	return recordsEqual;
}

Boolean TRecordHandler::RecordsEqual(MCObjectHandle rec1, MCObjectHandle rec2)
// Returns true if all fields are identical between the two records
{
	bool recordsEqual = false;

	if (VERIFYHANDLE(kDaveD, rec1, 1) && VERIFYHANDLE(kDaveD, rec2, 1)) {

		TRecordHandler handler1(rec1);
		TRecordHandler handler2(rec2);
		
		if (handler1.GetNumOfFields() == handler2.GetNumOfFields()) {

			recordsEqual = true; 	// Until found otherwise

			for (short i = 1; i <= handler1.GetNumOfFields() && recordsEqual; i++)
				recordsEqual = RecordFieldsEqual(handler1, handler2, i, false);
		}
	}

	return recordsEqual;
}


/***********************************************************************************

	TFormatHandler Class Functions
	
************************************************************************************/

TFormatHandler::TFormatHandler( MCObjectHandle f) : TFormatBase( f)
// Inits TFormatBase class
{
}

//	Code common to TFormatHandler constuctors below. 
//
static void FormatHandlerInit_(Str63 name, MCObjectHandle& outNewFormat)
// looks for a format named name, creates a new format if name does not exist
// creates a unique variant of name if name is used for another object
//
{	
	MCObjectHandle dataNodeH, theFormatH, theRecordH, h;
	size_t recordSize;
	
	// look for a format named name
	theFormatH = nil;
	h = GS_GetNamedObject(gCBP, name);		
	if (h != nil && (*(NGNodeHandle)h)->tdType == kFormatNode) {
		theFormatH = h;		// found a format with the proper name	
	} 
	else if (h != nil && (*(NGNodeHandle)h)->tdType != kFormatNode){ // name exists, so create a new unique name
		theFormatH = nil; // 
		short i = 1;
		PStr63 pName;
		PStr20 endString;
		do {
			pName = name;
			endString.Clear();
		 	endString << '-' << i++;
//			if ((pName.GetLength() + endString.GetLength()) > sizeof(RefName))
			if ((pName.GetLength() + endString.GetLength()) > sizeof(PStr63))
				pName.Truncate( pName.GetLength() - endString.GetLength());
			pName << endString;		
		}while( GS_GetNamedObject(gCBP, pName));

		name = pName;
	}
	else if (h == nil)
		theFormatH = nil;	// name does not exist so create a new format


	// This is strange code. It looks like we can easily SetFormat(nil) - PCP

	if (theFormatH == nil) { // if theFormatH is nil, then create new Format handle
		theFormatH = (MCObjectHandle)NewFormat(TSmartFormat::OffsetOfFieldList());
		if (theFormatH != nil) {
			#if _MINICAD_
			//	Caller takes care of this
			//gNotification.Notify(kNotifyRecordChange);
			#endif
			GS_SetObjectName(gCBP, theFormatH, name);
			dataNodeH = GS_FindAuxObject(gCBP, GS_GetDrawingHeader(gCBP), dataNode);
			if (VERIFYHANDLE(kDarick, dataNodeH, 1)) {
				GS_AddObjectToContainer(gCBP, theFormatH, dataNodeH);
			}
			// create default record 
			recordSize = TSmartRecord::OffsetOfFirstByte();

//			theRecordH = GS_NewDataObject( gCBP, theFormatH, 0, sizeof( UserDataType));  // insertion deferred [MAF 2/11/04]
			theRecordH = GS_NewDataObject( gCBP, 0, 0, sizeof( UserDataType));

			// the record handle is created with size UserDataType, and then reduced to recordSize
			// to avoid a minimum size requirement in the NewDataObject callback
			if( DDSetHandleSize( (GSHandle)theRecordH, recordSize) ){

				#ifdef _MINICAD_EXTERNAL_
						(*(NGNodeHandle)theRecordH)->SetTDType(kRecordNode);
				#else
						(*(NGNodeHandle)theRecordH)->tdType = kRecordNode; 
				#endif
				 

				// The undo/userAction system isn't too happy about all of this activity when 
				// neither an undo event nor a user action has been initiated (can happen when called 
				// from TToolSystem::ToolSelected() -> TToolSystem::SetCurTool() -> VerifyParametricFormat()
				// (I clicked on the simple door tool, saw the preview, then clicked on the simple window tool).		.
				// We can bypass the asserts by not attaching the record to the format node until after 
				// the record is "selected", but this is really all sneaking around outside of the 
				// standard userAction system. [MAF 2/11/04]


				GS_SelectObject(gCBP,  (MCObjectHandle) theRecordH, true);
				GS_SetVisibility(gCBP, (MCObjectHandle) theRecordH, true);

				GS_AttachAuxObject(gCBP, theRecordH, theFormatH);  // insert after the selection [MAF 2/11/04]

				TSmartRecord sRecord = ((RecordHandle)theRecordH);
				sRecord.SetFormatUsed((*(NGNodeHandle)theFormatH)->GetRefNumber());
			}
		}
	}
	
	outNewFormat = theFormatH;
}

//----------------------------------------------------------------------------------------
TFormatHandler::TFormatHandler(Str63 name) : TFormatBase( nil)
//
//	This is the public constructor using a Str63 name. 
{
	MCObjectHandle theFormat = nil;
	FormatHandlerInit_(name, theFormat);

	if (theFormat) {
	
		this->SetFormat((FormatHandle) theFormat);
		if (CALLUPDATEINFOPAL)
			UpdateInfoPal();
	}
}

//----------------------------------------------------------------------------------------

TFormatHandler::TFormatHandler(Str63 name, Boolean inIsParametric) : TFormatBase( nil)
//	Added inIsParametric argument so the constructor can skip the call to UpdateInfoPal below
//	Many temporary parametric formats are created and the user interface flashes
{
	MCObjectHandle newFormat = nil;
	FormatHandlerInit_(name, newFormat);

	if (newFormat) {
	
		this->SetFormat((FormatHandle) newFormat);
		this->SetIsParametric(inIsParametric);
		if (CALLUPDATEINFOPAL)
			UpdateInfoPal();
	}
}

//----------------------------------------------------------------------------------------

void TFormatHandler::DeleteFormat()
// deletes the format and all of the corresponding records
{
	GS_DeleteObject( gCBP, (MCObjectHandle)this->GetFormatHandle());
}

//----------------------------------------------------------------------------------------

static void AddFieldToShaderRecords(TAddFieldEnv& addFieldEnv)
// Add new field to records in the data header.  GS_ForEachObject (in AddField) doesn't traverse 
// into the drawing header and so these records would be missed otherwise.  -DLD 10/8/2003
{
	MCObjectHandle header = GS_GetDrawingHeader(gCBP);
	if (VERIFYN(kDaveD, header)) {

		MCObjectHandle dataHeader = GS_FindAuxObject(gCBP, header, dataNode);
		if (VERIFYN(kDaveD, dataHeader)) {
			
			// Look for material nodes in data header
			MCObjectHandle obj = GS_FirstMemberObj(gCBP, dataHeader);
			while (obj) {
			
				if (GS_GetObjectType(gCBP, obj) == kMaterialNode)
					FEOaddField(obj, gCBP, &addFieldEnv);
				
				obj = GS_NextObject(gCBP, obj);
			}
		}
	}
}


short TFormatHandler::AddField( const Str20 fieldName, TRecordItem& defaultValue, Boolean updateExistingRecords)
/*	Saves a copy of the old format; changes the format; calls conversion routines
 * 	for all records with this format
 */ 
//
//	Returns the number of fields in the format, or 0 for failure condition.
//	
//	The argument updateExistingRecords will default to true, which causes this function to traverse entire drawing
//	looking for existing records related to the this format and update them to reflect the newly added field.
//	If the caller knows that there are no existing records (other than the default record) then the caller can pass
//	"false" into that argument which will cause AddField() to skip the traversal of the drawing.  This will increase
//	the performance (speed) of this function call.  You would only do this if for example you _just_ created the format
//	so you know that there are no matching records.
//	JAK 6/30/99 for VW 8.5b3
//
{
	// *** This TFormatHandler function requires a fieldName that has an allocated string buffer of at least 20 characters. 
	// We need to make sure that this condition will always be satisfied to prevent potential crash for the case 
	// that the buffer of 'fieldName' is not large enough (i.e the user passed a TXString ). 
	// We use a copy of 'fieldName'. 
	// Fix B045491: Record Handler is unstable when used with TXString. [Hugues 9/14/05]
	const unsigned char* fieldName20 = PStr20(fieldName);
	// ***
	short returnValue = 0; // 0 indicates that the field was not added properly
	
	FormatHandle format = (FormatHandle) this->GetFormatHandle();
	size_t oldFormatSize;
	
	GS_GetHandleSize(gCBP, (GSHandle)format, oldFormatSize);

	TAddFieldEnv	addFieldEnv;	// parameters to pass to For Each Object function
	
	if((oldFormatSize) &&(VERIFYHANDLE(kDarick, format, oldFormatSize))){
		FormatHandle oldFormat = NewFormat(oldFormatSize);
		
		if(VERIFYHANDLE(kDarick, oldFormat, oldFormatSize)){
			TSmartFormat sOldFormat(oldFormat);
// sFormat(format);
			ASSERTPOINTER(kDarick, sOldFormat.NumberOfFields(), oldFormatSize - TSmartFormat::OffsetOfNumberOfFields());
			ASSERTPOINTER(kDarick, sFormat->NumberOfFields(), oldFormatSize - TSmartFormat::OffsetOfNumberOfFields());
			memcpy(sOldFormat.NumberOfFields(), sFormat->NumberOfFields(), 
					oldFormatSize - TSmartFormat::OffsetOfNumberOfFields());		// save copy of old format
			
			//Change the Format node
		 	short numFields = this->GetNumOfFields() + 1;	// XXX_JDW_MISC [2003-03-26] - Overflow (but currently won't reach it because will crash earlier than this number)
			size_t newFormatSize = TSmartFormat::OffsetOfFieldList() + (numFields * sizeof(FieldType));
			
			#if Chao
			DMSGIF(newFormatSize > MaxInt - 100L, (kChao, "New format size: %d\n", newFormatSize));
			#endif
			
			if(DDSetHandleSize((GSHandle)format, newFormatSize) ) {
				(*sFormat->NumberOfFields())++;
				(*sFormat->FieldList())[ numFields -1].fieldID = DemoteTo<short>(kJeff, defaultValue.GetFieldStyle());
				(*sFormat->FieldList())[ numFields -1].fieldOffset = CalcFieldOffset( numFields, (*sFormat->FieldList()));

				#if Chao
				DMSGIF(newFormatSize > MaxInt - 100L, (kChao, "field %d offset: %d\n", numFields, (*sFormat->FieldList())[ numFields -1].fieldOffset));
				#endif

				// Get numStyle and set it in the format
				TNumStyleClass numStyle;
				Byte accuracy;
				defaultValue.GetNumStyle(numStyle, accuracy);
				this->SetFieldNumStyle(numFields, numStyle, accuracy);
				ASSERTPOINTER(kDarick, (*sFormat->FieldList())[ numFields -1].fieldName, sizeof(Str20));
				ASSERTPOINTER(kDarick, fieldName20, sizeof(Str20));
				memcpy( (*sFormat->FieldList())[ numFields -1].fieldName, fieldName20, sizeof(Str20));
			
				//Change the default record attached to the format node, always.
				RecordHandle r = (RecordHandle) GS_FindAuxObject( gCBP, (MCObjectHandle) format, kRecordNode);
				AddField_ConvertRecord(r, oldFormat, format, defaultValue);
						
				//Change all records with this format, if requested.  This is the default behavior.
				if (updateExistingRecords) {							// JAK 6/30/99
					addFieldEnv.mName = (*(NGNodeHandle)format)->GetRefNumber();
					addFieldEnv.value = &defaultValue;
					addFieldEnv.oldFormat = oldFormat;
					// Traverse entire drawing searching for existing records to change.
					GS_ForEachObject(gCBP, allObjects+descendIntoAll, FEOaddField, &addFieldEnv);
					
					// Must also look into data header because shader records are stored there
					AddFieldToShaderRecords(addFieldEnv);
				}

				if (CALLUPDATEINFOPAL)
					UpdateInfoPal();
				returnValue = this->GetNumOfFields();
			}
		} //oldFormat
		
		// delete this temporary format allocated above
		GS_DeleteObject( gCBP,(MCObjectHandle)oldFormat, false);
	} 
	return returnValue;
} // AddField


//------------------------------------------
Boolean TFormatHandler::CountExistingRecords(long& count)
//
//	Count the number of existing recordNodes which use this kFormatNode.
//	Return true for success and false for failure.
//	Pass the count back in the argument.  
//	Does _not_ count the "default record" up in the dataNode's aux list, because dataNode itself is on drawingHeader's aux list.
//	Only counts recordNodes at top level of any node's aux list.
//
//	JAK 6/30/99
//
{
	FormatHandle format = (FormatHandle) this->GetFormatHandle();
	if (format != NULL) {
		TCountExistingRecordsEnv countRecEnv;	// stuff to pass into the ForEachObject function.
		countRecEnv.mName = (*(NGNodeHandle)format)->GetRefNumber();
		countRecEnv.count = 0;
		// The flags here should be same as flags used in AddField(), because we are using this count 
		// function to skip the loop in AddField().
		GS_ForEachObject(gCBP, allObjects+descendIntoAll, FEOCountRecords, &countRecEnv);
		
		count = countRecEnv.count; 
		return true;
	}
	else
		return false;
}


void AddField_ConvertRecord(RecordHandle &r, const FormatHandle oldFormat, const FormatHandle newFormat, TRecordItem& defaultValue)
/* Expands r using the oldFormat and compacts using the newFormat then sets the the defualt field to the last record item */
{
	TSmartRecord sr(r);
	TSmartFormat sOldFormat(oldFormat), sNewFormat(newFormat);
	if(sr.IsValid() && sOldFormat.IsValid() && sNewFormat.IsValid()){ // takes the place of an assert
		
		ExpandRecord( oldFormat, r);
		short index = *sOldFormat.NumberOfFields() + 1;
		short fieldOffset = CalcFieldOffset(index, *sOldFormat.FieldList());
		size_t newRecordSize = TSmartRecord::OffsetOfFirstByte() + 
								 fieldOffset +
								FieldSize(defaultValue.GetFieldStyle());

		DSTOPIF((fieldOffset < 0), (kHTsafak, "BOGUS RECORDNODE DETECTED BY AddField_ConvertRecord! The field Nr:%d has a negative index %d \n", index, fieldOffset));
		
		if(fieldOffset >= 0 && DDSetHandleSize( (GSHandle) r, newRecordSize)){		// make room for the new data
			if (( defaultValue.GetFieldStyle() == kFieldText) || 
				( defaultValue.GetFieldStyle() == kFieldCalculation) ||
				( defaultValue.GetFieldStyle() == kFieldPopUp)	||
				( defaultValue.GetFieldStyle() == kFieldRadio) ||
				( defaultValue.GetFieldStyle() == kFieldStaticText)) {
																			// for text records create a new handle
				FieldType fieldType = GetFieldInfo(newFormat, *sNewFormat.NumberOfFields());
				RecordItemPtr recItem = GetField( r, fieldType);
				
				recItem->handleView = GS_NewHandle(gCBP, 0);
				
				ASSERTHANDLE(kDarick, recItem->handleView, 0);
			}
			CompactRecord(newFormat, r);	
			TRecordHandler aRecord((MCObjectHandle) r);
			aRecord.SetFieldObject(*sNewFormat.NumberOfFields(), defaultValue);
		}
		else{
			DSTOP((kDarick, "Memory error while converting records. Document in unstable state\n"));
			// This should undo all changed records when an error occurs, but it doesn't.
			CompactRecord(oldFormat, r);
		}
	}
}



void TFormatHandler::DeleteField( short deleteFieldIndex)
/* 
	## deletes field in format and all records 
	## order of major operations is: save a copy of the old format; change the format; call a
	## conversion routine for the defualt record and all records with this format
*/
{	
	FormatHandle format = (FormatHandle) this->GetFormatHandle();
	size_t oldFormatSize;

	GS_GetHandleSize(gCBP, (GSHandle)format, oldFormatSize);
	FormatHandle oldFormat = NewFormat(oldFormatSize);
	
	if (oldFormat) {
		TSmartFormat sOldFormat(oldFormat);
		TDeleteFieldEnv actionEnv;					// parameters to pass to For Each Object function
		
		if(oldFormatSize && sFormat->IsValid() && sOldFormat.IsValid() 
				&& VERIFYN(kDarick, deleteFieldIndex >0 && deleteFieldIndex <= *sFormat->NumberOfFields())){

			ASSERTPOINTER(kDarick, sOldFormat.NumberOfFields(), oldFormatSize - TSmartFormat::OffsetOfNumberOfFields());
			ASSERTPOINTER(kDarick, sFormat->NumberOfFields(), oldFormatSize - TSmartFormat::OffsetOfNumberOfFields());
			memcpy(sOldFormat.NumberOfFields(), sFormat->NumberOfFields(), 
					oldFormatSize - TSmartFormat::OffsetOfNumberOfFields());		// save copy of old format
			
			//Change the Format node
			// Shrink the fieldList by one element
			size_t sizeOfMove = this->GetNumOfFields() - deleteFieldIndex;
			short deleteFieldSize =  FieldSize( (FieldStyle) (*sFormat->FieldList())[ deleteFieldIndex - 1].fieldID);
			if( sizeOfMove != 0){  	// if a move is needed (not deleting last element)
				sizeOfMove = sizeOfMove * sizeof(FieldType);
				// update the field offsets after the delete field
				for( int i = deleteFieldIndex; i < this->GetNumOfFields(); i++){
					(*sFormat->FieldList())[ i ].fieldOffset -= deleteFieldSize;
				} 
			FieldPtr dst = &(*sFormat->FieldList())[ deleteFieldIndex - 1];
			FieldPtr src = &(*sFormat->FieldList())[ deleteFieldIndex];
			ASSERTPOINTER(kDarick, dst, sizeof(FieldType));
			ASSERTPOINTER(kDarick, src, sizeof(FieldType));
			memmove( dst, src, sizeOfMove);
			}						//
			short newNumFields = --(*sFormat->NumberOfFields());
			size_t newFormatSize = TSmartFormat::OffsetOfFieldList() + (newNumFields * sizeof(FieldType));
			DDReduceHandleSize( (Handle) format, newFormatSize); // shrink the format

				
			//Convert the default record attached to the format node
			RecordHandle defRec = (RecordHandle) GS_FindAuxObject( gCBP, (MCObjectHandle) format, kRecordNode);
			DeleteField_ConvertRecord(defRec, oldFormat, format, deleteFieldIndex);
			
			//Change all records with this format
			actionEnv.mName = (*(NGNodeHandle)format)->GetRefNumber();
			actionEnv.deleteFieldIndex = deleteFieldIndex;
			actionEnv.oldFormat = oldFormat;

			GS_ForEachObject(gCBP, allObjects+descendIntoAll, FEOdeleteField, &actionEnv);

			if (CALLUPDATEINFOPAL)
				UpdateInfoPal();
			
		}
		
		// delete this temporary format allocated above
		GS_DeleteObject( gCBP, (MCObjectHandle) oldFormat, false);
	}
}


void DeleteField_ConvertRecord(RecordHandle &r, const FormatHandle oldFormat, const FormatHandle newFormat, short deleteFieldIndex)
/*  Expand r using the old format, shrink the record and compact using the new format
 */
{
	TSmartRecord sr(r);
	TSmartFormat sOldFormat(oldFormat), sNewFormat(newFormat);
	if(sr.IsValid() && sOldFormat.IsValid() && sNewFormat.IsValid() // takes the place of an assert
			&& VERIFYN(kDarick, deleteFieldIndex >0 && deleteFieldIndex <= *sOldFormat.NumberOfFields())){
		
		ExpandRecord( oldFormat, r);
	
		FieldStyle deleteFieldStyle = (FieldStyle) (*sOldFormat.FieldList())[ deleteFieldIndex - 1].fieldID;
		short deleteFieldSize =  FieldSize( deleteFieldStyle);
		short deleteFieldOff = (*sOldFormat.FieldList())[ deleteFieldIndex - 1].fieldOffset;
		short nextFieldOff = (*sOldFormat.FieldList())[ deleteFieldIndex ].fieldOffset;

		size_t newRecordSize = TSmartRecord::OffsetOfFirstByte() + 
								CalcFieldOffset(*sOldFormat.NumberOfFields() + 1, *sOldFormat.FieldList()) -
								deleteFieldSize;
		
		if (( deleteFieldStyle == kFieldText) || 
			( deleteFieldStyle == kFieldCalculation) ||
			( deleteFieldStyle == kFieldPopUp)	||
			( deleteFieldStyle == kFieldRadio) ||
			( deleteFieldStyle == kFieldStaticText)) {
			//delete special text space allocation
			RecordItemPtr recDataP = (RecordItemPtr)(deleteFieldOff + ((long)sr.FirstByte()));
			ASSERTPOINTER(kDarick, recDataP, 4);
			ASSERTHANDLE(kDarick, recDataP->handleView, 0);
			GS_DisposeHandle(gCBP, recDataP->handleView);
		}
		
		short recMoveSize = 0;
		for(int i = deleteFieldIndex; i < *sOldFormat.NumberOfFields(); i++){
			// add up size of fields after delete field
			recMoveSize += FieldSize( (*sOldFormat.FieldList())[ i ].fieldID);
		} 
		void * dest = (void *)(deleteFieldOff + ((long)sr.FirstByte()));  //move the other fields
		void * src = (void *)(nextFieldOff + ((long)sr.FirstByte())); 
		ASSERTPOINTER(kDarick, dest, deleteFieldSize + recMoveSize);
		ASSERTPOINTER(kDarick, src, recMoveSize);
		memmove( dest, src, recMoveSize);
		
		newRecordSize = TSmartRecord::OffsetOfFirstByte() + 
									CalcFieldOffset( *sNewFormat.NumberOfFields() + 1, *sNewFormat.FieldList()); 
		ASSERTN(kDarick, newRecordSize >= TSmartRecord::OffsetOfFirstByte());			

		DDReduceHandleSize( (Handle) r, newRecordSize); // shrink the record
		
		//Compact the default record, and all the records, using the new format
		CompactRecord( newFormat, r);
	}
}

  
Boolean TFormatHandler::GetFieldDefault( short fieldIndex, TRecordItem& theValue) 
// returns a copy of the default field [fieldIndex] in theValue
{
	MCObjectHandle recordH = GS_FindAuxObject( gCBP, this->GetFormatHandle(), kRecordNode);

	if( (fieldIndex < 1) || (fieldIndex > this->GetNumOfFields()) || (recordH == nil))
		return false;
	else 	{
		TRecordHandler defaultHandler(recordH);
		defaultHandler.GetFieldObject( fieldIndex, theValue);
		return true;
	}
}

Boolean TFormatHandler::SetFieldDefault( short fieldIndex, TRecordItem& theValue)
// sets the default field [fieldIndex] to theValue
{
	MCObjectHandle recordH = GS_FindAuxObject(gCBP, this->GetFormatHandle(), kRecordNode);

	if((fieldIndex < 1) || (fieldIndex > this->GetNumOfFields()) || (recordH == nil))
		return false;
	else {
		TRecordHandler defaultHandler(recordH);
		defaultHandler.SetFieldObject(fieldIndex, theValue);
		if (CALLUPDATEINFOPAL)
			UpdateInfoPal();
		return true;
	}
}

void TFormatHandler::RemoveRecordFromObject(MCObjectHandle object)
// removes the record with the same format as fFormat from object
{
	RefNumber thisFormatName = 0;
	FormatHandle format;
	
	format = (FormatHandle) this->GetFormatHandle();
	if(VERIFYHANDLE(kDarick, format, TSmartFormat::OffsetOfFieldList()))
		thisFormatName = (*(NGNodeHandle)format)->GetRefNumber();
	
	MCObjectHandle temp;
	MCObjectHandle h = GS_FirstAuxObject(gCBP, object);
	while(h && VERIFYHANDLE(kDarick, h, sizeof(NGNodeHandle))) {
		temp = h;
		h = GS_NextObject(gCBP, h);
		if((*(NGNodeHandle)temp)->tdType == kRecordNode && VERIFYHANDLE(kDarick, h, TSmartRecord::OffsetOfFirstByte())){
			TSmartRecord sTemp((RecordHandle)temp);
			if(sTemp.FormatUsed() == thisFormatName)
				GS_DeleteAuxObject( gCBP, temp, object);
		}
	}
	if (CALLUPDATEINFOPAL)
		UpdateInfoPal();
}

MCObjectHandle TFormatHandler::AttachRecordToObject( MCObjectHandle object)
//
// Creates a copy of the default rec and attaches it to object, returning
// a handle to the new record. If the object already has the record, a handle
// to the existing record is returned. If the record couldn't be created,
// nil is returned. [Return value added by JDW, 5/6/98]
//
{
	MCObjectHandle	defaultRec		= nil; 
	MCObjectHandle	newRec			= nil;
	FormatHandle	format			= nil;
	RefNumber		thisFormatName	= 0;
	Boolean			formatExists	= false;
	
	// first check to see if the object already has this format
	format = (FormatHandle) this->GetFormatHandle();
	if(VERIFYHANDLE(kDarick, format, TSmartFormat::OffsetOfFieldList()))
		thisFormatName = (*(NGNodeHandle)format)->GetRefNumber();
	
	MCObjectHandle h = GS_FirstAuxObject(gCBP, object);
	while(h && VERIFYHANDLE(kDarick, h, sizeof(NGNodeHandle))) {
		if((*(NGNodeHandle)h)->tdType == recordNode && VERIFYHANDLE(kDarick, h, TSmartRecord::OffsetOfFirstByte())){
			TSmartRecord sh((RecordHandle)h);
			if(sh.FormatUsed() == thisFormatName){
				formatExists = true;
				newRec = h;
				break;
			}	
		}
		h = GS_NextObject(gCBP, h);
	}
	
	// if the object does not have this format then add it
	if(!formatExists){
		defaultRec = GS_FindAuxObject( gCBP, this->GetFormatHandle(), kRecordNode);
		if(VERIFYHANDLE(kDarick, defaultRec, TSmartRecord::OffsetOfFirstByte())) {
			newRec = GS_DuplicateObject(gCBP, defaultRec);
			if(VERIFYHANDLE(kDarick, newRec, TSmartRecord::OffsetOfFirstByte()))
				GS_AttachAuxObject( gCBP, newRec, object);
			if (CALLUPDATEINFOPAL)
				UpdateInfoPal();	
		} 
	}
	
	return newRec;
}



/***********************************************************************************

	Utility Functions
	
************************************************************************************/


FormatHandle NewFormat( size_t size)
// When calling this (versus NewFormat(void)), post kNotifyRecordChange if relevant.
//  Not done here because this form is often called to create a temporary format node.
{
	FormatHandle format = nil;
	format = (FormatHandle)GS_NewDataObject( gCBP, nil, 0L, size);
	
	if (VERIFYHANDLE( kDarick, format, size) && VERIFYN(kDarick, size >= TSmartFormat::OffsetOfFieldList())){

#ifdef _MINICAD_EXTERNAL_
		(*(NGNodeHandle)format)->SetTDType(kFormatNode);
#else
		(*(NGNodeHandle)format)->tdType = kFormatNode; 
#endif

		GS_SelectObject(gCBP,  (MCObjectHandle) format, true);
		GS_SetVisibility(gCBP, (MCObjectHandle) format, true);

		TSmartFormat sFormat(format);
		*sFormat.NumberOfFields() = 0;
		sFormat.SetSubscribeID(0);
		*sFormat.TimeStamp() = 0L;
	}
	
	return format;
}

char * GetTextField( const RecordHandle rec, const RecordItemPtr data)
// returns the chars of a compact text field
// which are located in an offset at the end of the record
{
	TSmartRecord sRec(rec);
	return (char *) (((long)sRec.FirstByte()) + data->textView.offset);
}


short CalcFieldOffset( short fieldIndex, const FieldListType & fieldList) 
//	calculates the offset to fieldIndex
//  setting fieldIndex = numFields + 1 would give the total size of the data
//	using the total size of all previous fields
{
	short offset = 0;
	if (fieldIndex == 1)
		offset = 0;
	else {
		offset = fieldList[ fieldIndex - 2].fieldOffset + FieldSize( fieldList[ fieldIndex - 2].fieldID);
	}
	ASSERTN(kHTsafak, offset >= 0);

	return offset;
}


Boolean DDSetHandleSize(GSHandle h, size_t newSize)
// my SetHandleSize which does almost no error checking
{
	Boolean myReturn;

	GS_HUnlock(gCBP, h);
	myReturn = GS_SetHandleSize(gCBP, h, newSize);
	GS_HLock(gCBP, h);
	return myReturn;
}

void DDReduceHandleSize(GSHandle h, size_t newSize)
// my SetHandleSize which does almost no error checking
{
	GS_HUnlock(gCBP, h);
	GS_ReduceHandleSize(gCBP, h, newSize);
	GS_HLock(gCBP, h);
}


#if _MINICAD_
void FixFormatWithNoDefaultRecord (BlankHandle formatH)
// takes formatH, if its a format without a defualt record then it creates a
//  default record and attaches it to formatH
{
	if (VERIFYHANDLE(kDarick, formatH, sizeof(FormatType))  // if h is a format
			&& (*formatH)->tdType == kFormatNode) {
		
		// look for default record if there is a default record, then we're done
	//	RecordHandle r = (RecordHandle) GS_FindAuxObject( gCBP, (MCObjectHandle)formatH, kRecordNode);

		Boolean found = false;

		RecordHandle r = (RecordHandle) GS_FindAuxObject(gCBP, formatH, kRecordNode);
		while (r) {
			if (GS_GetObjectType(gCBP, (BlankHandle)r) == kRecordNode && (*r)->formatUsed != 0)
				found = true;
			r = (RecordHandle) GS_NextObject(gCBP, (BlankHandle)r);
		}

		
		// if we have any recordNode with formatUsed not 0, then we assume that is the correct record
		// otherwise we create the correct one. Any others will be deleted during file read - PCP

		if (!found) {

			#if BUG
			RefName formatName = GetRefName((*formatH)->GetRefNumber());
			DMSG((kEveryone, "CREATING new default record for format '%.*s' (GetRefNumber() %d).\n", PASCALPRINT(formatName), (*(FormatHandle)formatH)->GetRefNumber()));
			#endif

			TFormatBase format((MCObjectHandle)formatH);
			long		recordSize = 0;
			short		numFields = format.GetNumOfFields();
			short		i;
			Boolean		success;
			
			// create a new aux object, add fields as necessary	
			// create default record 
			recordSize = offsetof( RecordType, firstByte);
			MCObjectHandle theRecordH = GS_NewDataObject( gCBP, (MCObjectHandle)formatH, 0, sizeof( UserDataType));
			// the record handle is created with size UserDataType, and then reduced to recordSize
			// to avoid a minimum size requirement in the NewDataObject callback
			
			for(i = 1; i <= numFields; i++){
				recordSize += FieldSize(format.GetFieldStyle(i));
			}
			
			GS_HUnlock(gCBP, (GSHandle)theRecordH);
			success = GS_SetHandleSize(gCBP, (GSHandle)theRecordH, recordSize);
			GS_HLock(gCBP, (GSHandle) theRecordH);
			if (success) {
				r = (RecordHandle) theRecordH;
				(*r)->formatUsed = (*(FormatHandle)formatH)->GetRefNumber();

#ifdef _MINICAD_EXTERNAL_
		(*(NGNodeHandle)r)->SetTDType(recordNode);
#else
		(*(NGNodeHandle)r)->tdType = recordNode; 
#endif

				GS_SelectObject(gCBP,  (MCObjectHandle) r, true);
				GS_SetVisibility(gCBP, (MCObjectHandle) r, true);

				RecordItemPtr	theRec;
				FieldType		theField;
				short			defValue = 0;
				long 			dataSize = recordSize - offsetof(RecordType, firstByte);
	
				for (i = 1; i <= numFields; i++) {		// for each field
					theField = GetFieldInfo((FormatHandle)formatH, i);		// get the field type
					theRec = GetField(r, theField);		// Field is located at r->firstByte + theField->offset
					switch (theField.fieldID) {			// Set each field to the default value (0)
						case kFieldText:						// Except for text fields, whose size is 0 but store offset to end of record
						case kFieldCalculation:
						case kFieldPopUp:		// JAK
						case kFieldRadio:
						case kFieldStaticText:
							theRec->textView.size   = 0;
							theRec->textView.offset = DemoteTo<short>(kJeff, dataSize);
							break;
						case kFieldLongInt:
						case kFieldHandle:
							theRec->longView = (long) defValue;
							break;
						case kFieldBoolean:
							theRec->boolView = DemoteTo<Boolean>(kJeff, defValue);
							break;
						case kFieldReal:
							// Use memset because reals in records take up 10 bytes
							memset(&(theRec->realView), defValue, FieldSize(theField.fieldID));
							break;
						default:
							DSTOP((kDarick, "Unknown Field Type in FixFormat\n"));
							break;
					};
				}
			}
		}
	}
} /* FixFormatWithNoDefaultRecord */
#endif  /* _MINICAD_ */


short LastField(FormatHandle theFormat)
// Returns the number of the field with the greatest fieldOffset
{
	long						maxOff;
	short						i;
	short						lastFld;

	TSmartFormat sTheFormat(theFormat);

	maxOff = -1L;
	lastFld = 1;
	{		
		for (i = *sTheFormat.NumberOfFields(); i >= 1; i--) {
			if ((*sTheFormat.FieldList())[i-1].fieldOffset >= maxOff) {
				lastFld = i;
				maxOff = (long) (*sTheFormat.FieldList())[i-1].fieldOffset;
			}
		}
	}
	return lastFld;
}


void CompactRecord(FormatHandle f, RecordHandle r)
/*
 ## changes a non-compact record into std form
 ## records should only be non-compact for short periods of time
 
 ## An Expanded Record uses the handleView structure
 ## which points to the actual text of a text field.  
 ## A Compact Record uses a textView structure which consists of 
 ## a size field and an offset from the the first byte of the record
 ## to the text data, which is stored at the end of the record
*/
//	JAK 11/4/97 Added case for kFieldPopUp because it is text-based now.
//	JAK 3/9/98 Added case for kFieldRadio.
//
{
	RecordItemPtr				theRec;
	short						i, limit;
	short						theEnd;
	size_t						theSize;
	FieldType					theField;
	GSCharsHandle 				theChars;
	
	TSmartFormat sf(f);		// These constructors assert the handles
	TSmartRecord sr(r);
	
	if (*sf.NumberOfFields() > 0) {
		i = LastField(f);

		theEnd = (*sf.FieldList())[i-1].fieldOffset+FieldSize((*sf.FieldList())[i-1].fieldID);
					// theEnd is the offset to the end of the record

		limit = *sf.NumberOfFields();
		for (i = 1; i <= limit; i++) {

			theField = GetFieldInfo(f, i);
			switch (theField.fieldID) {					// for each text field
				case kFieldText:
				case kFieldCalculation:
				case kFieldStaticText:	// JAG
				case kFieldPopUp: 		// JAK
				case kFieldRadio: {
					/* Get the handle from the expanded record.
					*/
#if 0
					DMSG((kEveryone, mongo, "Doing text record compression\n"));
#endif
					theRec = GetField(r, theField);
					
					/* NOTE: handleView is nil if the field has just been added to the format.
					*/
					#ifdef TEST
					if (theRec->handleView)
						ASSERTHANDLE(kPaul, theRec->handleView, 0L);
					#endif					
					
					theChars = theRec->handleView;

					if(theChars)
						GS_GetHandleSize(gCBP, (GSHandle) theChars, theSize);
					else
						theSize = 0;

					theRec->textView.size = theSize;
					theRec->textView.offset = theEnd;
					if (podd(theSize))
						theSize++;
					if (theChars) {
						/* Reset the handle size to make room for more data.
						*/
						GS_HUnlock(gCBP, (GSHandle) r);
						size_t oldSize;
						GS_GetHandleSize(gCBP, (GSHandle) r, oldSize);

						if (GS_SetHandleSize(gCBP, (GSHandle) r, oldSize + theSize)) 
						{
							// Store the chars at the end of the record.
							//memcpy( ((char *) sr.FirstByte() + theEnd, *theChars, (long) theSize);
							ASSERTN(kHTsafak, theEnd > 0);
							if (theEnd >= 0 && VERIFYPOINTER(kHTsafak, ((char *)sr.FirstByte()) + theEnd, (long) theSize)){
								ASSERTPOINTER(kDarick, *theChars, (long) theSize);
								memcpy(((char *)sr.FirstByte()) + theEnd, *theChars, (long) theSize);
							}
							
							theEnd += theSize;
						}
						GS_HLock(gCBP, (GSHandle) r);
						GS_DisposeHandle(gCBP, (GSHandle) theChars);
					}
					break;
				}
			}
		}
	}
}// CompactRecord



// *compile* - make library call
void GS_PointerToHandle(CallBackPtr cbp, void* pointer, GSHandle& handle, size_t size);
void GS_PointerToHandle(CallBackPtr cbp, void* pointer, GSHandle& handle, size_t size)
{
	handle = GS_NewHandle(cbp, size);
	if (handle)
		memcpy(*handle, pointer, size);
}


void ExpandRecord(FormatHandle f, RecordHandle r)
/*
 ## Changes a normal record into a form more easily manipulated.
 ## After changes are made, it should be made compact again.
 
 ## An Expanded Record uses the handleView structure
 ## which points to the actual text of a text field.  
 ## A Compact Record uses a textView structure which consists of 
 ## a size field and an offset from the the first byte of the record
 ## to the text data, which is stored at the end of the record
*/
//	JAK 11/4/97 Added case for kFieldPopUp because it is text-based now.
//	JAK 3/9/98 Added case for kFieldRadio.
//	JAK 5/4/00 Error checking!
//
{
	RecordItemPtr				theRec;
	short						i, limit;
	FieldType					theField;
	
	TSmartFormat sf(f);
	TSmartRecord sr(r);


	#if BUG
	// get this here to ease debugging this code
	///RefName debugName = GetName((*f)->mName);
	// How about an SDK friendly version of this call
	InternalName debugName;
	GS_GetObjectName( gCBP, (MCObjectHandle)f, debugName);
	#endif


//	ASSERTHANDLE(kPaul, f, f ? (offsetof(FormatType, fieldList)+(*f)->numberOfFields * sizeof(FieldType)): 0);
//	ASSERTHANDLE(kPaul, r, offsetof(RecordType, firstByte));
	
	//
	// JAK 5/4/00 - TESTING - Try to verify if recordNode agrees with its formatNode.
	//	This will sometimes catch case where formatNode has more fields than recordNode. The exception is if the recordNode has a big text field in it. 
	//	Then our handle size check will succeed even though the node could still be bogus.
	//	How do we recover from this type of error?  Maybe copy the default recordNode into this recordNode? XXX
	//
	size_t recHandleSize = 0;
	GS_GetHandleSize(gCBP, (Handle)r, recHandleSize);
	size_t recFirstByte = offsetof(RecordType, firstByte);
	short lastFieldIndex = *sf.NumberOfFields();	// 1-based
	if (lastFieldIndex !=  0) {
		FieldType lastFieldInfo = GetFieldInfo(f, lastFieldIndex);		// get last field info from FormatNode. Undefined if FormatNode has no fields so avoid calling it if index is 0.
		size_t lastFieldOffset = recFirstByte + lastFieldInfo.fieldOffset;
		DSTOPIF((lastFieldOffset > recHandleSize), (kJeff, "BOGUS RECORDNODE DETECTED BY EXPANDRECORD! RecordNode is %d bytes but FormatNode thinks it is at least %d bytes.", recHandleSize, lastFieldOffset));
	}
	
	ASSERTLOCKED(kDarick, (Handle) r);
	GS_HLock(gCBP, (GSHandle) r);

	ASSERTLOCKED(kDarick, (Handle) f);
	GS_HLock(gCBP, (GSHandle) f);

	limit = *sf.NumberOfFields();
	for (i = 1; i <= limit; i++) {					// for each field
		theField = GetFieldInfo(f, i);				// get the field type from the formatNode.
		switch (theField.fieldID) {
			case kFieldText:
			case kFieldCalculation:
			case kFieldStaticText:	// JAG
			case kFieldPopUp: 		// JAK
			case kFieldRadio: {	
				theRec = GetField(r, theField);		// Field is located at r->firstByte + theField->offset
				if (theRec->textView.offset != 0) {				
					// Get the handle from the compressed record.
					Handle	theChars;
					size_t	theCharSize = theRec->textView.size;
					
					//
					// JAK 5/4/00 - Verify this record field before we use it.
					//	This will catch the case where formatNode has more fields than recordNode even if recordNode has a big string in it,
					//	because when we look for a field offset and size we'll be getting character data from the string and it will likely be out of range
					//	so we can detect it here.  
					//	Can we recover from this type of error?  Without crashing?  XXX
					//
					// The offset to end of this field = offset to first byte of field info area + offset of this field's text + size of this field's text.
					// (use the size_t type here because it is unsigned and we don't want bogus values showing up as negative here when we do our > comparison).
					size_t endOfFieldOffset = offsetof(RecordType, firstByte) + theRec->textView.offset + theRec->textView.size;
					if (endOfFieldOffset > recHandleSize) {
						DSTOPIF((endOfFieldOffset > recHandleSize), (kJeff, "BOGUS RECORDNODE DETECTED BY EXPANDRECORD! The text field %d claims to extend to %d bytes which is beyond record node of %d bytes.", i, endOfFieldOffset, recHandleSize));
						// 	Maybe if we find a bogus field here we can set theCharSize to 0 so it allocates an empty handle and puts it in the handleView union field.
						theCharSize = 0;
					}									
										
					// Move the chars from the end of the record into a separate handle.
					GS_PointerToHandle(gCBP, GetTextField( r, theRec), theChars, theCharSize);

					#ifdef TEST
					if (theChars)
						ASSERTHANDLE(kPaul, theChars, 0L);
					else
						DSTOP((kPaul, "GS_PointerToHandle failed -- theRec->handleView being set to NIL!!"));
					#endif
					
					theRec->handleView = theChars;
					
				}
				else {
					// Assume that somebody knows what they're doing.
					
					// actually, let's not assume that - PCP
					DSTOP((kDarick, "ExpandRecord found textView.offset of 0 -- handleView is 0x%lx!!", theRec->handleView));
					// if there are no characters, then the handle should be nil -DWD
					theRec->handleView = nil;
				}
				break;
			}
		}
	}
	
	/* Get rid of extra space that may be at end of handle. (where the text used to be)
	*/
	if (limit > 0) {
		i = LastField(f);
		GS_HUnlock(gCBP, (GSHandle) r);

		GS_ReduceHandleSize(gCBP, (GSHandle) r, TSmartRecord::OffsetOfFirstByte() +
				(*sf.FieldList())[i-1].fieldOffset + FieldSize((*sf.FieldList())[i-1].fieldID));

		GS_HLock(gCBP, (GSHandle) r);
	}
}// ExpandRecord









short FieldSize(long t)
{
	switch (Abs(t)) {
		case kFieldText:
		case kFieldPopUp:		// JAK
		case kFieldRadio:		// JAK
		case kFieldStaticText:	// JAG
		case kFieldDimStdPopUp:		// JAG
			return 4;

		case kFieldReal:
		case kFieldCoordDisp:	// JAK
		case kFieldCoordLocX:	// JAK
		case kFieldCoordLocY:	// JAK
			return 10;

		case kFieldPrecisionPopUp:	// JAG
			return 8;

		case kFieldLongInt:
			return 4;

		case kFieldBoolean:
			return 2;

		case kFieldCalculation:
			return 4;

		#if GS_TEST_NEW_UI_PARAMETERS_SAJ
		case kFieldUnderDev1:
		case kFieldUnderDev2:
		//case kFieldUnderDev3:
			return 0;
			break;
		#endif	//GS_TEST_NEW_UI_PARAMETERS_SAJ

		default:
			#ifdef BUG
			DSTOP((kDarick | kJeff, "Bad t:%d passed to FieldSize.\n",t));
			#endif
			return 0;
	}
}


FieldType GetFieldInfo(FormatHandle theFormat, short field)
//
//	returns a fieldInfo record for a particular field
//	The "field" argument is a 1-based index.
//	If there are no fields in the format, then this function is undefined. ***
//
{
	ASSERTN(kJeff, (field != 0));	// 
	TSmartFormat sTheFormat(theFormat);
	return (*sTheFormat.FieldList())[field-1];
}


FieldPtr GetFieldInfoPtr(FormatHandle theFormat, short field)
//returns a fieldInfo record for a particular field
{
	ASSERTN(kJeff, (field != 0));	// 
	TSmartFormat sTheFormat(theFormat);
	return &((*sTheFormat.FieldList())[field-1]);
}


RecordItemPtr GetField(RecordHandle rec, const FieldType &info)
{
	TSmartRecord sRec(rec);
	RecordItemPtr retVal = (RecordItemPtr) (((long) sRec.FirstByte()) + info.fieldOffset);
	ASSERTPOINTER(kDarick, retVal, FieldSize(info.fieldID));
	ASSERTN(kChao, info.fieldOffset >= 0);
	return retVal;
}





#if _MINICAD_ // **** These functions have been moved from pk.cpp ***


/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
//
// From this point on, the functionality of this file is not avaliable to externals
//
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////


RecordItemPtr GetField(RecordPtr rec, const FieldType &info)
{
	ASSERTPOINTER(kPaul, rec, offsetof(RecordType, firstByte));

	RecordItemPtr result = (RecordItemPtr) (((long) &rec->firstByte) + info.fieldOffset);
	ASSERTPOINTER(kPaul, result, sizeof(RecordItemType));
	return result;
}

//----------------------------------------------------------------------------------------

FormatHandle NewFormat()
// Creates a new format record with no fields, puts it into
//  the format list, and posts a record change notification.
{
	RecordHandle				r;
	FormatHandle				f;
	
	LLNewObjectHandle(sizeof(FormatType)-sizeof(FieldListType), kFormatNode, true, CAST(BlankHandle, f));
	if (f != nil) {
		InsertObject((BlankHandle) f, eBefore, ActFirstDataObject());
		(*f)->numberOfFields = 0;
		(*f)->subscribeID = 0;
		(*f)->timeStamp = 0;
		r = NewRecord((BlankHandle) f, f);
#if _MINICAD_  // Notifications meaningless in externals
		gNotification.Notify(kNotifyRecordChange);
		//gNotification.Notify(kNotifySelChange);	// Replaced by kNotifyRecordChange, 10/98
#endif
	}
	
	return f;
}

//----------------------------------------------------------------------------------------

// Adds a field to an existing expanded format record. newField ** should have
// all fields set except fieldOffset. this routine ** should only be called
// immediately after creation of format, because it does not attempt to update any existing recordNodes in the drawing.
struct AddFormatXUULink {
};

#ifdef BUG
static Boolean AddFormatXUU_PtrInBlock(long thePtr, long theHand)
{
	long						handPtr;

	handPtr = (long) *((Handle) theHand);
	return (handPtr < thePtr && handPtr + GetHandleSize((Handle) theHand) > thePtr);
}
#endif

static void AddFormatXUU_AddFieldToRecord(FieldType newField, RecordHandle defRecH, FormatHandle f) 
{
	RecordItemPtr				theRec;
	short						i;
	FieldType					info;

	ASSERTLOCKED(kPaul, (Handle) defRecH);

	GS_HUnlock(gCBP, (GSHandle) defRecH);
	size_t originalSize;

	GS_GetHandleSize(gCBP, (GSHandle) defRecH, originalSize);
	Boolean result = GS_SetHandleSize(gCBP, (GSHandle) defRecH, originalSize+FieldSize(newField.fieldID));
	GS_HLock(gCBP, (GSHandle) defRecH);
	
	if (result) {

		DMSG((kEveryone, mongo, "Done setting default handle size\n"));

		/* Initialize the new field.
		*/
		info = GetFieldInfo(f, (*f)->numberOfFields);
		theRec = GetField(defRecH, info);
		#ifdef BUG
			if (AddFormatXUU_PtrInBlock((long) theRec, (long) defRecH)) {
				DMSG((kEveryone, mongo,"Ptr is in block\n"));
				DMSG((kEveryone, mongo,"block size = %ld\n", GetHandleSize((Handle) defRecH)));
				DMSG((kEveryone, mongo,"Ptr offset = %ld\n", (long) theRec - (long) *defRecH));
			}
			else {
				DMSG((kEveryone, mongo,"Ptr is not in block\n"));
			}
		#endif
		// XXX this is an incredibly BOGUS way of clearing out this block
		for (i = 1; i <= FieldSize(newField.fieldID); i++) {
			// Initialize the field
			*((char *) theRec) = 0;
			// Longint( theRec ) := Longint( theRec ) + 1;
			theRec = (RecordItemPtr) ((long) theRec+1);
		}
	}
	else {
		DSTOP((kPaul, "FATAL ERROR - memory allocation failed and there is no error return!"));
	}
}


void AddFormatXUU(FormatHandle f, const FieldType &newFieldValue)
{
	Size						newSize;
	FieldType					newField;
	RecordHandle				defRecH;

	newField = newFieldValue;
	DMSG((kEveryone, verbose, "Adding field: %P\n", (ConstStr255Param) newField.fieldName));
	if (NNFindObject((*f)->Aux(), CAST(BlankHandle, defRecH), recordNode)) {
		ExpandRecord(f, defRecH);
		
		ASSERTLOCKED(kPaul, (Handle) f);
		
		GS_HUnlock(gCBP, (GSHandle) f);
		newSize = sizeof(FormatType)-sizeof(FieldListType)+((*f)->numberOfFields+1)*sizeof(FieldType);
		if (GS_SetHandleSize(gCBP, (GSHandle) f, (long) newSize)) {
			GS_HLock(gCBP, (GSHandle) f);
			(*f)->numberOfFields++;
			(*f)->fieldList[(*f)->numberOfFields-1].fieldOffset = -1;
	
			
			
			DMSG((kEveryone, mongo, "numberOfFields = %hd in AddFormat\n", (*f)->numberOfFields));
			if ((*f)->numberOfFields == 1) {
				newField.fieldOffset = 0;
			}
			else {
				FieldType	lastFld;
				
				lastFld = (*f)->fieldList[LastField(f)-1];
				newField.fieldOffset = lastFld.fieldOffset + FieldSize((short) Abs(lastFld.fieldID));
			}
			(*f)->fieldList[(*f)->numberOfFields-1] = newField;
			#ifdef BUG_WRITELN
			if (iWantToDebug) {
				WRITELN("\044About to Add field to default record");
				WRITELN("\027newField.fieldOffset = ", newField.fieldOffset);
			}
			#endif
			AddFormatXUU_AddFieldToRecord(newField, defRecH, f);
			DMSG((kEveryone, mongo, "Done adding field to default record\n"));
	
			CompactRecord(f, defRecH);
			DMSG((kEveryone, mongo, "Done compacting record\n"));
			//GS_HUnlock(gCBP, (GSHandle) f);
		}
		else {
			DSTOP((kPaul, "FATAL ERROR - Memory allocation failed and there is no error return!!"));
			GS_HLock(gCBP, (GSHandle) f);
		}
	}
}



//е Intermediate level

//returns nil if Errorif a record is not already in the formats aux list, it
// sets the defaults to 0's, no textputs into the aux list of the owner,
// pass the format Handle in order to set defaults
// returns nil of owner or theFormat are nil
//
RecordHandle NewRecord(BlankHandle owner, FormatHandle theFormat)
{
//	BlankHandle 				theAux;
	RecordHandle				defRecH;

	if (owner && theFormat) {
		if (NNFindObject((*theFormat)->Aux(), CAST(BlankHandle, defRecH), recordNode))
			defRecH = (RecordHandle) DuplicateNode((BlankHandle) defRecH);
		else {
			// copy the default record * Create a new default record. The
			// firstByte field ** is subtracted from the Size, because no data
			// exists. *
			LLNewObjectHandle(offsetof(RecordType, firstByte), recordNode, true, CAST(BlankHandle, defRecH));
			if (defRecH)
				(*defRecH)->formatUsed = AssignOrGetRefNumber((BlankHandle) theFormat);
		}
		
		InsertObject((BlankHandle) defRecH, eBeforeAuxListOf, owner);

		return defRecH;
	}
	else
		return nil;
}


void SetField(FormatHandle theFormat, RecordHandle rec, FieldPtr info,
			const RecordItemType &rValue, const void *dataPtr)
/*
 ## Sets data in a RecordHandle to that contained in r.
 ## Text to be added is pointed to by dataPtr, with its size
 ## already set in the actual record item.
*/
//	Fixed to work with Plug-in Object parameters of dimension types.
//
{
	RecordItemType				r;

	r = rValue;
	if (theFormat) {
		switch (info->fieldID) {
			case kFieldText:
			case kFieldCalculation:
			case kFieldStaticText:
			case kFieldPopUp:
			case kFieldRadio: {
				RecordItemPtr	recItem	= GetField(rec, *info);
				if ( rValue.textView.size <= recItem->textView.size && 3 * rValue.textView.size > recItem->textView.size ) {
					// if there is enough space already in the recItem, do not expant, but just put in the new text
					// however make sure that if the difference is too big then do expant-collapse to save memory
					char*	destString	= GetTextField( rec, recItem );
					memcpy( destString, dataPtr, rValue.textView.size);
					recItem->textView.size	= rValue.textView.size;
				}
				else {
					RecordItemPtr	theRecP;
					CharsHandle 	theChars;
					
					ExpandRecord(theFormat, rec);
					
					/* - Dispose of old chars.
					*/
					theRecP = GetField(rec, *info);
					theChars = theRecP->handleView;
					if (theChars)
						GS_DisposeHandle(gCBP, (GSHandle)theChars);
					
					/* Stuff in new chars.
					*/
					theChars = (CharsHandle) GS_NewHandle(gCBP, (long) r.textView.size);
					if (theChars) {
						BlockMove(dataPtr, *((Handle) theChars), (long) r.textView.size);
						theRecP = GetField(rec, *info);
						theRecP->handleView = theChars;
					}
					
					CompactRecord(theFormat, rec);
				}
				break;
			}
			case kFieldLongInt: {
				GetField(rec, *info)->longView = r.longView;
				break;
			}
			case kFieldReal: {
				GetField(rec, *info)->realView = r.realView;
				break;
			}
			case kFieldBoolean: {
				GetField(rec, *info)->boolView = r.boolView;
				break;
			}
			case kFieldCoordDisp:
			case kFieldCoordLocX:
			case kFieldCoordLocY: {
				// These values are in deci-microns, but stored in the realView also.  Used by Plug-in Objects. JAK
				GetField(rec, *info)->realView = r.realView;
				break;
			}
			default:
				DSTOP((kJeff, "SetField encountered an unknown field type %d", info->fieldID));
				break;
		}
	}
}



//е High Level

Ptr GetFieldData(RecordHandle rec, const FieldType &/*info*/, const RecordItemType &r)
/*
 ## Gets pointer to the data associated with a record; use only when data exists
*/
{
	ASSERTHANDLE(kPaul, rec, offsetof(RecordType, firstByte));
	return (r.textView.offset != 0) ? (Ptr) &(*rec)->firstByte + r.textView.offset : nil;
}



Boolean GetFieldText (RecordHandle h, const FieldType &info, const char* &outText, size_t &length)
{
	if ((info.fieldID == kFieldText) || 
		(info.fieldID == kFieldCalculation) ||
		(info.fieldID == kFieldPopUp)	||
		(info.fieldID == kFieldRadio) ||
		(info.fieldID == kFieldStaticText)) {
			RecordItemPtr theRec = GetField(h, info);		// Field is located at r->firstByte + info->offset
			if (theRec->textView.offset != 0) {
				length = theRec->textView.size;
				outText = GetTextField( h, theRec);

				#if TEST
				size_t recHandleSize;

				GS_GetHandleSize(gCBP, (Handle)h, recHandleSize);

				size_t endOfFieldOffset = offsetof(RecordType, firstByte) + theRec->textView.offset + theRec->textView.size;
				if (endOfFieldOffset > recHandleSize) {
					DSTOP((kJeff, "BOGUS RECORDNODE DETECTED BY GetFieldText! The text field claims to extend to %d bytes which is beyond record node of %d bytes.", endOfFieldOffset, recHandleSize));
				}									
				#endif


				return true;
			}			
	}
	return false;

}





PString GetFieldString (RecordHandle h, const FieldType &info, bool bMaxAccuracy)
/* Best not to use for text fields, as maximum of 255 characters is returned */
//
// 1/30/04, JAG: Added bMaxAccuracy.  Needed because FieldValueToString assumes document
// accuracy, which is not always correct.  The VectorScript function, GetRField, for
// example, always wants to return the full value of a field, not a rounded value.
{
	PStr255			theString;
	FormatHandle	theFormat;
	RecordItemPtr	theRecItem;
	short			len;		// Length of text to be copied into the string
	Boolean			gotString;
	
	theString = NULLSTR;
	if ((h == nil) || ((*h)->tdType != recordNode)) {
		DSTOP((kMike, "GetFieldString - NIL handle or non-record node passed!"));
		return(theString);
	}
	
	if ((info.fieldID == kFieldText) || 
		(info.fieldID == kFieldCalculation) ||
		(info.fieldID == kFieldPopUp)	||
		(info.fieldID == kFieldRadio) ||
		(info.fieldID == kFieldStaticText)) 
	{
		theFormat = (FormatHandle) GetObject((*h)->formatUsed);
		ASSERTN(kMike, (theFormat != nil));
		if (theFormat != nil) {
			theRecItem = GetField(h, info);
			if (theRecItem->textView.offset > 0) {
				size_t recHandleSize = 0;
				GS_GetHandleSize(gCBP, (Handle)h, recHandleSize);
				size_t endOfFieldOffset = offsetof(RecordType, firstByte) + 
											theRecItem->textView.offset + theRecItem->textView.size;
				if (endOfFieldOffset <= recHandleSize) {
					len = theRecItem->textView.size;
					if (len > 255) len = 255;
					StringCopy(GetTextField(h, theRecItem), theString, len + 1);
					SetLength(theString, DemoteTo<Uint8>(kJeff, len));
				}
			}
	#if BUG  // This is the old way of getting the string.  It may be a little easier to understand, but
			PStr255	testString;		// it is much slower.  Leave it here in debug build for a while to
			ExpandRecord(theFormat, h);	// confirm new method is working correctly.  BF 10/12/01
			ASSERTLOCKED(kPaul, (Handle) h);
			GS_HLock(gCBP, (GSHandle) h);
			theRecItem = GetField(h, info);
			len = (short) GetHandleSize((Handle) theRecItem->handleView);
			if (len > 255) len = 255;
			SetLength(testString, DemoteTo<Uint8>(kJeff, len));
			BlockMove(*theRecItem->handleView, (Ptr) &testString[1],
			           (long) len);
			CompactRecord(theFormat, h);
			ASSERTN(kBruce, StringEqual(theString, testString));
	#endif
		}
	}
	else {
		theRecItem = GetField(h, info);
		if(info.fieldOffset >= 0)
			gotString = FieldValueToString(theRecItem, info.fieldID, info.numStyle, theString, bMaxAccuracy);
		else
			gotString = false;
	}
	
	return(theString);
} /* GetFieldString */


Boolean SetFieldToString (FormatHandle theFormat, RecordHandle rec,
                           FieldPtr infoPtr, ConstStr255Param newVal, bool bOnlySetIfValid)
/* Best not to use for text fields, as maximum of 255 characters can be passed */
{
	Boolean 		setValidly;
	RecordItemType	theRecItem;
	
	/* Validate the parameters */
	setValidly = false;
	if ((rec == nil) || ((*rec)->tdType != recordNode)) {
		DSTOP((kMike, "SetFieldToString - NIL handle or non-record node passed!"));
		return(false);
	}
	if ((theFormat == nil) || ((*theFormat)->tdType != kFormatNode)) {
		DSTOP((kMike, "SetFieldToString - NIL handle or non-format node passed!"));
		return(false);
	}
	if (infoPtr == nil) {
		DSTOP((kMike, "SetFieldToString - NIL pointer for field info passed!"));
		return(false);
	}
	
	/* Zero-out field value in case it is not set */
	memset(&theRecItem, 0, sizeof(RecordItemType));
	
	/* Convert the string to a field value */
	if ((infoPtr->fieldID == kFieldText) || 
		(infoPtr->fieldID == kFieldCalculation) ||
		(infoPtr->fieldID == kFieldPopUp) ||	// JAK
		(infoPtr->fieldID == kFieldRadio) ||	// JAK
		(infoPtr->fieldID == kFieldStaticText)) {	// JAG
		theRecItem.textView.size   = GetLength(newVal);
		theRecItem.textView.offset = 0;
		setValidly = true;
		}
	else {
		setValidly = StringToFieldValue(newVal, infoPtr->fieldID,
		                                 infoPtr->numStyle, &theRecItem);
	}
	// I think the value should not be zeroed-out if the string is invalid. 
	// But maybe there are situations where it is necessary, so I am just introducing a flag and use it
	// for the cases where it doesn't make sense. -Hughes 05/02/07
	if(bOnlySetIfValid){ 
		if(setValidly)
			SetField(theFormat, rec, infoPtr, theRecItem, &newVal[1]);
	}
	else{
		/* Store the field value in the record itself*/
		SetField(theFormat, rec, infoPtr, theRecItem, &newVal[1]);
	}
	return(setValidly);
} /* SetFieldToString */

// pass the SysName of a format, proc returns handles to the
// format and its default records. Does not descend into groups

void FindFormAndRec(RefNumber formSys, BlankHandle &theFormat, BlankHandle &theDefault)
{
	theFormat = GetObject(formSys);
	if (theFormat) {
		if (!NNFindObject((*theFormat)->Aux(), theDefault, recordNode))
			theDefault = nil;
	}
	else
		theDefault = nil;
}


Boolean CheckAndInsertRecord(BlankHandle theObj, BlankHandle aRecord, Boolean modifyExisting)
/*
	Adds aRecord to theObj. If theObj already contains a record of the same format
	as aRecord, then no new record is added. The function returns true if a new
	record is created or the existing is modified. If modifyExisting then if a record
	of similar type exists it is changed to reflect the field values of aRecord.
	aRecord is NOT modified by this routine, only copies are inserted.
*/
{
	BlankHandle 				newRecord;

	if (theObj == 0 || aRecord == 0)
		return false;

	ASSERTN(kPaul, (*aRecord)->tdType == recordNode);

	// Search to make sure a duplicate record is not added
	newRecord = (*theObj)->Aux();

	while (SearchForType(recordNode, newRecord)) {
		if ((*((RecordHandle) newRecord))->formatUsed == (*((RecordHandle) aRecord))->formatUsed) {
			if (modifyExisting) {
				InsertObject(DuplicateNode(aRecord), eAfter, newRecord);
				DeleteObjectWithoutUndo(newRecord);
			}

			return false;
		}
		else
			newRecord = (*newRecord)->Next();
	}
	newRecord = DuplicateNode(aRecord);
	InsertObject(newRecord, eBeforeAuxListOf, theObj);
	return true;
}
// CheckAndInsertRecord


/*============================================================================*/
/**********************************/
/* Format Conversion Routines     */
/**********************************/


//*****************************************************************************
//
// Function Name:  BuildFormatConvInfo
//
// Description:
//	Builds a conversion information array for mapping each field in an "old"
//	version of a format definition to its "new" version.  Old fields are
//	matched to new ones strictly by name (ignoring case).  The new field
//	number (0 if not matched) and the old and new types of each old field
//	are recorded.  (Caller should use SameFormat to check whether the two
//	format definitions are in fact different before calling this routine.)
//
//	Matching fields by name is appropriate because if a field
//	is removed or added, the other fields retain their names and types but
//	change position within the format.  This is probably the most common scenario.
//
//	NOTE: Format definitions passed may not be in any document's RefList,
//	      so no routines that assume this can be called from here!
//
// Parameters:
//	oldFmtObj    - Handle to the old format definition object
//	newFmtObj    - Handle to the new format definition object
//	pConvInfoHdl - Returns the conversion information array that is built.
//	               Caller is responsible for disposing of the allocated memory.
//
// Return Value:
//	Whether the conversion info was built
//
// Created by:  Michael Hartman, June 1997
//
//*****************************************************************************

void BuildFormatConvInfo (const FormatHandle oldFmtObj, const FormatHandle newFmtObj,
                           FormatConvInfoHdl* pConvInfoHdl)
{
	Boolean				wasBuilt;
	TFormatBase			*oldFmtBase, *newFmtBase;
	PStr20				oldFieldName;
	PStr20				*newFieldNames;  /* Array */
	short				numOldFields, numNewFields, oldFld, newFld;
	
	/* Validate arguments */
	ASSERTN(kMike, (oldFmtObj != nil));
	ASSERTN(kMike, (newFmtObj != nil));
	if (((oldFmtObj == nil) || (newFmtObj == nil)) ||
	 (((*oldFmtObj)->tdType != kFormatNode) || ((*newFmtObj)->tdType != kFormatNode))) {
		*pConvInfoHdl = nil;
		return;
	}
	
	/* Initialize variables */
	wasBuilt = false;
	oldFmtBase = nil;
	newFmtBase = nil;
	newFieldNames = nil;
	*pConvInfoHdl = nil;
	
	/* Create objects for accessing the old and new format definitions */
	oldFmtBase = NEW_NOTHROW TFormatBase((MCObjectHandle)oldFmtObj);
	if (oldFmtBase == nil) goto exit;
	newFmtBase = NEW_NOTHROW TFormatBase((MCObjectHandle)newFmtObj);
	if (newFmtBase == nil) goto exit;
	
	/* Build an array with the new format's field names for efficiency */
	numNewFields = newFmtBase->GetNumOfFields();
	newFieldNames = (PStr20*)Mem_NewPtr(numNewFields*sizeof(PStr20));
	if (newFieldNames == nil) goto exit;
	for (newFld = 1; newFld <= numNewFields; newFld++) {
		newFmtBase->GetNameOfField(newFld, newFieldNames[newFld-1]);
	}
	
	/* Match old format fields to new fields by name only, and if   */
	/*  match found also store field types in conversion info array */
	// First compare with universal names of fields.
	numOldFields  = oldFmtBase->GetNumOfFields();
	*pConvInfoHdl = (FormatConvInfoHdl)GS_NewHandle(gCBP, numOldFields*sizeof(FieldConvInfo));
	if (*pConvInfoHdl == nil) goto exit;
	GS_HLock(gCBP, (GSHandle)*pConvInfoHdl);
	for (oldFld = 1; oldFld <= numOldFields; oldFld++) {		// 1-based field index
		(***pConvInfoHdl)[oldFld-1].newFieldNum = 0;
		(***pConvInfoHdl)[oldFld-1].oldFieldType = 0;	// JAK	Clear the whole structure just in case. 
		(***pConvInfoHdl)[oldFld-1].newFieldType = 0;	// JAK	Clear the whole structure just in case.
		oldFmtBase->GetNameOfField(oldFld, oldFieldName);
		for (newFld = 1; newFld <= numNewFields; newFld++) {
			if (StringEqualIgnoringCase(oldFieldName, newFieldNames[newFld-1])) {
				(***pConvInfoHdl)[oldFld-1].newFieldNum  = newFld;
				(***pConvInfoHdl)[oldFld-1].oldFieldType = DemoteTo<short>(kJeff, oldFmtBase->GetFieldStyle(oldFld));
				(***pConvInfoHdl)[oldFld-1].newFieldType = DemoteTo<short>(kJeff, newFmtBase->GetFieldStyle(newFld));
				break;
			}
		}
		
		#if _MINICAD_
		// If not found, and this is a "Parametric" format node for use by a Plug-in Object,
		// then search for matching fieldname in list of LOCALIZED field names for new format. 
		// Cache a list of the local field names before loop to improve performance. *************
		// JAK 12/6/02
		Boolean found = (***pConvInfoHdl)[oldFld-1].newFieldNum != 0;	// Since it is 1-based, a 0 means not found. 
		if ( (!found) && (oldFmtBase->IsParametric()) ) {
			PStr63 objectName;
			oldFmtBase->GetNameOfFormat(objectName);
			short objectIndex = gParametrics->NameToIndex(objectName);
			for (newFld = 1; newFld <= numNewFields; newFld++) {
				short parameterIndex = (newFld-1);	// 0-based index for call to GetPluginParemeterDisplayString
				PStr63 localFieldName;
				gParametrics->GetPluginParameterDisplayString(objectIndex, parameterIndex, localFieldName);
				// if (oldFieldName, localFieldName)
				if (StringEqualIgnoringCase(oldFieldName, localFieldName)) {
					(***pConvInfoHdl)[oldFld-1].newFieldNum  = newFld;	// 1-based index 
					(***pConvInfoHdl)[oldFld-1].oldFieldType = DemoteTo<short>(kJeff, oldFmtBase->GetFieldStyle(oldFld));
					(***pConvInfoHdl)[oldFld-1].newFieldType = DemoteTo<short>(kJeff, newFmtBase->GetFieldStyle(newFld));
					break;
				}
			}
		}
		#endif		

	}
	wasBuilt = true;
	
exit:
	/* Dispose of variables if error occurred */
	if (newFieldNames != nil) DisposePtr((Ptr)newFieldNames);
	if (oldFmtBase != nil) delete oldFmtBase;
	if (newFmtBase != nil) delete newFmtBase;
	if (!wasBuilt) {
		if (*pConvInfoHdl != nil) {
			GS_DisposeHandle(gCBP, (GSHandle)*pConvInfoHdl);
			*pConvInfoHdl = nil;
		}
		DSTOP((kMike, "### BuildFormatConvInfo - Failed for SysNames %d and %d!", (*oldFmtObj)->GetRefNumber(), (*newFmtObj)->GetRefNumber()));
	}
} /* BuildFormatConvInfo */


//*****************************************************************************
//
// Function Name:  TransferFieldValue
//
// Description:
//	Given a RecordItem object loaded with field data from one record, tranfers
//	the data value to a RecordItem object created for the corresponding field
//	in another record.  Where the fields' data types differ, the data is
//	converted as best as possible to the new type.  Conversions between
//	numeric and boolean fields are handled locally; text conversions are
//	handled by the RecordItem methods.  Note that since numeric style is not
//	stored in RecordItems, real number text is always given the general style.
//
//	Called by RecordToNewFormat().
//
//	NOTE: This relies on various routines in the RecordHandler module working
//	      on records and format definitions that are not in any document.
//	      Also, this makes no attempt to deal with kFieldCalculation fields;
//	      unused in 6/97, their ultimate contents might be document-dependent.
//
// Parameters:
//	oldItem - Pointer to a record item object containing the old field data
//	newItem - Pointer to a record item object for the converted field data
//
// Return Value:
//	Whether the data value was transferred to the new record item
//
// Created by:  Michael Hartman, June 1997
// Modified:	Jeff Koppi, 	Jan 1998
//		Added cases for new parametric-formatNode field types.  Many of the 
//		conversions are meaningless and so the value is not transferred.  In this
//		case the newItem will retain its default value so that the results are 
//		more predictable.
//
//*****************************************************************************

Boolean TransferFieldValue (TRecordItem* oldItem, TRecordItem* newItem)
{
	Boolean		success;
	long		longVal;
	double_gs	realVal;
	Boolean		boolVal;
	Str255		valStr;
	char*		textPtr;  /* Allocation/deallocation by TRecordItem */
	size_t		textSize;
	
	/* According to old and new field types, get old record's */
	/*  field value, do any type conversion, and set value of */
	/*  corresponding field in new record (when convertable)  */
	ASSERTN(kMike, (oldItem != nil));
	ASSERTN(kMike, (newItem != nil));
	success = false;
	switch (oldItem->GetFieldStyle()) {
		case kFieldLongInt:
			success = oldItem->GetFieldValue(longVal);
			if (!success) goto exit;
			success = false;
			switch (newItem->GetFieldStyle()) {
				case kFieldLongInt:
					success = newItem->SetFieldValue(longVal);
					break;
					
				case kFieldReal:
					realVal = longVal;
					success = newItem->SetFieldValue(realVal);
					break;
					
				case kFieldBoolean:
					boolVal = (longVal != 0);
					success = newItem->SetFieldValue(boolVal);
					break;
					
				case kFieldText:
				case kFieldStaticText:
					success = oldItem->GetFieldValueAsString(valStr);
					if (success)
					  success = newItem->SetFieldValueAsString(valStr);
					break;
					
				case kFieldPopUp:
				case kFieldRadio:
				case kFieldCoordDisp:
				case kFieldCoordLocX:
				case kFieldCoordLocY:
					// No meaningful conversion available.  Use default value of newItem.
					break;
					
				default:
					DSTOP((kMike, "### TransferFieldValue - Unrecognized/unsupported new field type of %d!", newItem->GetFieldStyle()));
					break;
			}
			break;
			
		case kFieldReal:
			success = oldItem->GetFieldValue(realVal);
			if (!success) goto exit;
			success = false;
			switch (newItem->GetFieldStyle()) {
				case kFieldLongInt:
					longVal = DoubleToLong(realVal);
					success = newItem->SetFieldValue(longVal);
					break;
					
				case kFieldReal:
					success = newItem->SetFieldValue(realVal);
					break;
					
				case kFieldBoolean:
					boolVal = (realVal != 0);
					success = newItem->SetFieldValue(boolVal);
					break;
					
				case kFieldText:
				case kFieldStaticText:
					success = oldItem->GetFieldValueAsString(valStr);
					if (success)
					  success = newItem->SetFieldValueAsString(valStr);
					break;
					
				case kFieldCoordDisp: {

					success = oldItem->GetFieldValueAsString(valStr);
					WorldCoord newVal = 0;
					if (success && UnitString2WorldCoord(valStr, newVal, eNoUserOrigin, kDualPrimary))
						success = newItem->SetFieldValue(newVal);
					
					break;
				}
				case kFieldPopUp:
				case kFieldRadio:
				case kFieldCoordLocX:
				case kFieldCoordLocY:
					// No meaningful conversion available.  Use default value of newItem.
					break;

				default:
					DSTOP((kMike, "### TransferFieldValue - Unrecognized/unsupported new field type of %d!", newItem->GetFieldStyle()));
					break;
			}
			break;
			
		case kFieldBoolean:
			success = oldItem->GetFieldValue(boolVal);
			if (!success) goto exit;
			success = false;
			switch (newItem->GetFieldStyle()) {
				case kFieldLongInt:
					longVal = boolVal ? 1 : 0;
					success = newItem->SetFieldValue(longVal);
					break;
					
				case kFieldReal:
					realVal = boolVal ? 1.0 : 0.0;
					success = newItem->SetFieldValue(realVal);
					break;
					
				case kFieldBoolean:
					success = newItem->SetFieldValue(boolVal);
					break;
					
				case kFieldText:
				case kFieldStaticText:
					success = oldItem->GetFieldValueAsString(valStr);
					if (success)
					  success = newItem->SetFieldValueAsString(valStr);
					break;
					
				case kFieldPopUp:
				case kFieldRadio:
				case kFieldCoordDisp:
				case kFieldCoordLocX:
				case kFieldCoordLocY:
					// No meaningful conversion available.  Use default value of newItem.
					break;
					
				default:
					DSTOP((kMike, "### TransferFieldValue - Unrecognized/unsupported new field type of %d!", newItem->GetFieldStyle()));
					break;
			}
			break;

		case kFieldText:
		case kFieldStaticText:
			success = oldItem->GetFieldValueAsString(valStr);
			if (!success) goto exit;
			success = false;
			switch (newItem->GetFieldStyle()) {
				case kFieldLongInt:
					success = newItem->SetFieldValueAsString(valStr);
					break;
					
				case kFieldReal:
					success = newItem->SetFieldValueAsString(valStr);
					break;
					
				case kFieldBoolean:
					success = newItem->SetFieldValueAsString(valStr);
					break;
					
				case kFieldText:
				case kFieldPopUp:	// JAK- User may have entered text equiv of a popup item.
				case kFieldRadio:
				case kFieldStaticText:
					success = oldItem->GetFieldValue(textPtr, textSize);
					if (success)
					  success = newItem->SetFieldValue(textPtr, textSize);
					break;
					
				case kFieldCoordDisp:
				case kFieldCoordLocX:
				case kFieldCoordLocY:
					// No meaningful conversion available.  Use default value of newItem.
					break;
			
				default:
					DSTOP((kMike, "### TransferFieldValue - Unrecognized/unsupported new field type of %d!", newItem->GetFieldStyle()));
					break;
			}
			break;
			
		case kFieldPopUp:
		case kFieldRadio:
			// These field types are used for parametric-formatNodes.  
			success = false;
			switch (newItem->GetFieldStyle()) {
				case kFieldText:
				case kFieldPopUp:
				case kFieldRadio:
				case kFieldStaticText:
					success = oldItem->GetFieldValue(textPtr, textSize);
					if (success)
					  success = newItem->SetFieldValue(textPtr, textSize);
					break;
				
				case kFieldLongInt:
				case kFieldBoolean:
				case kFieldReal:
				case kFieldCalculation:
				case kFieldHandle:
				case kFieldCoordDisp:
				case kFieldCoordLocX:
				case kFieldCoordLocY:
					// No meaningful conversion available.  Use default value of newItem.
					break;
	
				default:
					DSTOP((kMike | kJeff, "### TransferFieldValue - Unrecognized/unsupported new field type of %d!", newItem->GetFieldStyle()));
					break;
			}
			break;
			
		case kFieldCoordDisp:
		case kFieldCoordLocX:
		case kFieldCoordLocY:
			// These field types are used for parametric-formatNodes.  
			// Value is in decimicrons which would only be meaningful in another similar field.
			success = oldItem->GetFieldValue(realVal);
			if (!success) goto exit;
			success = false;
			switch (newItem->GetFieldStyle()) {
				case kFieldCoordDisp:
				case kFieldCoordLocX:
				case kFieldCoordLocY:
					success = newItem->SetFieldValue(realVal);
					break;

				case kFieldLongInt:
				case kFieldBoolean:
				case kFieldReal:
				case kFieldText:
				case kFieldCalculation:
				case kFieldHandle:
				case kFieldPopUp:
				case kFieldRadio:
				case kFieldStaticText:
					// No meaningful conversion available.  Use default value of newItem.
					break;
	
				default:
					DSTOP((kMike | kJeff, "### TransferFieldValue - Unrecognized/unsupported new field type of %d!", newItem->GetFieldStyle()));
					break;
			}
			break;

		default:
			DSTOP((kMike, "### TransferFieldValue - Unrecognized/unsupported old field type of %d!", oldItem->GetFieldStyle()));
			break;
	}
	
exit:
	return(success);
} /* TransferFieldValue */


//*****************************************************************************
//
// Function Name:  RecordToNewFormat
//
// Description:
//	Converts a record node object from one version of a format definition to
//	another.  This is accomplished using format conversion information that
//	must be built by BuildFormatConvInfo and passed to this routine.  Fields
//	whose data type has changed are converted as best as possible; fields in
//	only the new format get their default value; fields in only the old format
//	have their data discarded.  The record's formatUsed field is also updated.
//
//	NOTE: This relies on various routines in the RecordHandler module working
//	      on records and format definitions that are not in any document.
//	      Also, this should never be called for a format's default record.
//
// Parameters:
//	recObj      - Handle to the record node object to be converted
//	oldFmtObj   - Handle to the format definition the record currently uses
//	newFmtObj   - Handle to the format definition the record is to use;
//	               its mName must be set to its value in the record's document
//	fmtConvInfo - Format conversion information built by BuildFormatConvInfo
//
// Return Value:
//	Whether the conversion was successful
//
// Created by:  Michael Hartman, June 1997
// Note:	Jeff Koppi, Jan 1998
//			This function will also be called to update parametric object's 
//			recordNodes when their version numbers do not agree with the external 
//			tool's version number.  
//
//*****************************************************************************

Boolean	RecordToNewFormat (RecordHandle recObj, FormatHandle oldFmtObj,
                            FormatHandle newFmtObj, FormatConvInfo fmtConvInfo)
{
	Boolean			converted, success;
	RecordHandle	newRecObj;
	TRecordHandler	*oldRecord, *newRecord;
	short			numOldFields, oldFld;
	FieldConvInfo	fldConvInfo;
	TRecordItem		*oldItem, *newItem;
	size_t			newSize;
	
	/* Validate arguments */
	ASSERTN(kMike, (recObj != nil));
	ASSERTN(kMike, (oldFmtObj != nil));
	ASSERTN(kMike, (newFmtObj != nil));
	if ((recObj == nil) || (oldFmtObj == nil) || (newFmtObj == nil))
	  return(false);
	if (((*recObj)->tdType != recordNode) ||
	 ((*oldFmtObj)->tdType != kFormatNode) || ((*newFmtObj)->tdType != kFormatNode))
	  return(false);
	
	/* Initialize variables */
	converted = false;
	newRecObj = nil;
	oldRecord = nil;
	newRecord = nil;
	oldItem = nil;
	newItem = nil;
	
	/* Create a new record from the new format's default record, */
	/*  and create objects for accessing the old and new records */
	oldRecord = NEW_NOTHROW TRecordHandler((MCObjectHandle)recObj,
	                                        (MCObjectHandle)oldFmtObj);
	if (oldRecord == nil) goto exit;
	success = NNFindObject((*newFmtObj)->Aux(), CAST(BlankHandle, newRecObj), recordNode);
	if (!success) goto exit;
	newRecObj = (RecordHandle)DuplicateNode((BlankHandle)newRecObj);
	if (newRecObj == nil) goto exit;
	newRecord = NEW_NOTHROW TRecordHandler((MCObjectHandle)newRecObj,
	                                        (MCObjectHandle)newFmtObj);
	if (newRecord == nil) goto exit;
	
	/* Transfer values from mapped format fields in the old record     */
	/*  to the new one, converting values between types when possible. */
	/*  If failure occurs here, continue conversion but value is lost. */
	numOldFields  = oldRecord->GetNumOfFields();
	for (oldFld = 1; oldFld <= numOldFields; oldFld++) {
		fldConvInfo = fmtConvInfo[oldFld-1];
		if (fldConvInfo.newFieldNum != 0) {
			/* Create and load RecordItems for old and new record fields */
			oldItem = NEW_NOTHROW TRecordItem(fldConvInfo.oldFieldType, nsGeneral, 0);
			if (oldItem == nil) goto delItems;
			success = oldRecord->GetFieldObject(oldFld, *oldItem);
			if (!success) goto delItems;
			/*oldItem->SetNumStyle(nsGeneral, 0);*/
			newItem = NEW_NOTHROW TRecordItem(fldConvInfo.newFieldType, nsGeneral, 0);
			if (newItem == nil) goto delItems;
			success = newRecord->GetFieldObject(fldConvInfo.newFieldNum, *newItem);
			if (!success) goto delItems;
			
			/* Get field value from old record's item, do any type */
			/*  conversion, and set value of the new field's item; */
			/*  if successful, set value of field in new record    */
			success = TransferFieldValue(oldItem, newItem);
			if (!success) goto delItems;
			
			//
			// SPECIAL CASE for Parametric Format Nodes with Popup or Radio button parameters.
			// For VW 10.1, opening existing drawing may contain plug-in object record with popup parameter value of wrong language.
			// Convert field value from localized choice string to universal choice string.
			// JAK 1/16/03		Bug# 31091
			//
			if (newRecord->IsParametric()) {
				if (((oldItem->GetFieldStyle() == kFieldPopUp) || (oldItem->GetFieldStyle() == kFieldRadio)) &&
					((newItem->GetFieldStyle() == kFieldPopUp) || (newItem->GetFieldStyle() == kFieldRadio))) {
						PStr63 objName;
						// This routine, RecordToNewFormat, is called in two different cases: first for default record node in data header,
						// and then several times for each instance of a plug-in object's record. 
						// In the first case: oldFmtObj has fRefNumber (sysname) of plug-in object and newFmtObj has bogus temporary fRefNumber.
						// In the second case: oldFmtObj has fRefNumber of 0 and newFmtObj has fRefNumber of plug-in object. 
						// So now we need to get the name of the plug-in object, and can't reliably grab old or new format to get it. 
						// Therefore use the record's formatUsed field which should always be correct to get matching format, to get name.
						// newRecord->GetNameOfFormat(objName);	// <--does not work because newRecord TRecordHandler sometimes has handle to bogus format node with fRefNumber of 0. 
						// GS_GetObjectName( gCBP, (MCObjectHandle) oldFmtObj, objName);	// <-- this does not work for second case.
						//
						if (inrange((*recObj)->formatUsed, 1, (*gRefNumberTable)->numberOfArraySlots)) {
							FormatHandle parmFormat = (FormatHandle)GS_GetDefinition(gCBP, (MCObjectHandle)recObj);
							GS_GetObjectName( gCBP, (MCObjectHandle) parmFormat, objName);
							short objIndex = gParametrics->NameToIndex(objName);	// returns -1 if plug-in name not found
							if (objIndex != -1) {
								short paramIndex = (oldFld - 1);
								char* textPtr;  /* Allocation/deallocation by TRecordItem */
								size_t textSize;
								newItem->GetFieldValue(textPtr, textSize);
								TXString	choiceStr( textPtr, (size_t)textSize );
								TXString	univChoiceStr;
								gParametrics->PostTranslatePluginChoiceString(objIndex, paramIndex, choiceStr, univChoiceStr);
								// JAK 3/5/03 BUG: overwrites bounds of textPtr's storage if univ choice string is longer than local choice.
								//StringCopy(univChoiceStr, textPtr, univChoiceStr.GetLength()+1);		
								//textSize = univChoiceStr.GetLength();
								//newItem->SetFieldValue(textPtr, textSize);
								newItem->SetFieldValue( (const char*)univChoiceStr, univChoiceStr.GetLength() );
	
								#if BUG
								// ***** TEST CODE *****
								char* fooPtr;
								size_t fooSize;
								newItem->GetFieldValue(fooPtr, fooSize);
								if (fooPtr[1] == textPtr[1]) {
								}
								#endif
							}
						}
					}
			}
			
			// Set Field Value in the new record.
			success = newRecord->SetFieldObject(fldConvInfo.newFieldNum, *newItem);
			
delItems:
			/* Dispose of the RecordItems */
			if (oldItem != nil) delete oldItem;
			oldItem = nil;
			if (newItem != nil) delete newItem;
			newItem = nil;
		}
	}
	
	/* Dispose of the record handlers, as we're  */
	/*  about to muck with the records ourselves */
	delete oldRecord;
	oldRecord = nil;
	delete newRecord;
	newRecord = nil;
	
	/* Replace data portion of old record with that of new one */
	/*  (avoids needing to know the record's parent aux list)  */
	/*  and store the SysName of its new format definition     */
	GS_GetHandleSize(gCBP, (GSHandle)newRecObj, newSize);
	GS_HUnlock(gCBP, (GSHandle)recObj);
	success = GS_SetHandleSize(gCBP, (GSHandle)recObj, newSize);
	GS_HLock(gCBP, (GSHandle)recObj);
	if (success) {
		BlockMoveData(&(*newRecObj)->firstByte, &(*recObj)->firstByte,
		 (newSize - offsetof(RecordType, firstByte)));
		(*recObj)->formatUsed = (*newFmtObj)->GetRefNumber();
		converted = true;
	}
	
exit:
	/* Dispose of variables and return result */
	if (oldItem != nil) delete oldItem;
	if (newItem != nil) delete newItem;
	if (oldRecord != nil) delete oldRecord;
	if (newRecord != nil) delete newRecord;
	if (newRecObj != nil) DisposeHandle((Handle)newRecObj);
	if (!converted)
	  DSTOP((kMike, "### RecordToNewFormat - Failed for record using format with SysName %d!", (*newFmtObj)->GetRefNumber()));
	return(converted);
} /* RecordToNewFormat */


/*============================================================================*/


#endif // _MINICAD_


#if defined(BUG) && !defined(_MINICAD_EXTERNAL_)
///////////////////////////////////////////////////////////////////////////////
static MCObjectHandle GetDebugLocusFormat()
{
	MCObjectHandle	theFormatNode	= GS_GetNamedObject(gCBP, PStr63("Debug Locus"));
	
	if (! theFormatNode) {
		TFormatHandler	newFormatHandler(PStr63("Debug Locus"));
		TRecordItem		message(kFieldText);					// kFieldText    = constant in MObject.h	
		TRecordItem		coord(kFieldLongInt);			    	// kFieldLongInt = constant in MObject.h
		
		message.SetFieldValue("", 0);
		coord.SetFieldValue(0L);

		(void) newFormatHandler.AddField(PStr20("Message"), message);
		
		(void) newFormatHandler.AddField(PStr20("x units"), message);		
		(void) newFormatHandler.AddField(PStr20("y units"), message);
		(void) newFormatHandler.AddField(PStr20("z units"), message);
		
		(void) newFormatHandler.AddField(PStr20("x coords"), coord);		
		(void) newFormatHandler.AddField(PStr20("y coords"), coord);
		(void) newFormatHandler.AddField(PStr20("z coords"), coord);
		
		theFormatNode	= GS_GetNamedObject(gCBP, PStr20("Debug Locus"));
	}
	
	ASSERTHANDLE(kChao, theFormatNode, sizeof(FormatType));

	return theFormatNode;
}

static void DoDebugLocus(const WorldPt3& inPt, const char *inFormat, va_list& inList)
{
	MCObjectHandle theLocus	= GS_CreateLocus3D(gCBP, inPt);

	if (theLocus) {
		TXString			message;

		TRecordItem		text(kFieldText);	
		TRecordItem		coord(kFieldReal);
		PStr255 		valstring;
		
		MCObjectHandle	theFormatNode = 	GetDebugLocusFormat();
		
		if (theFormatNode) {
			TFormatHandler	theFormatHandler(theFormatNode);
			
			MCObjectHandle	theRecord = theFormatHandler.AttachRecordToObject(theLocus); 
			TRecordHandler	theRecordHandler(theRecord);
			
			if (theRecord) {
				message.VFormat(inFormat, inList);
				text.SetFieldValue(message, message.GetLength());
				theRecordHandler.SetFieldObject(1, text);
				
				(void) UserCoord2UnitString(eIncludeUserOriginX, inPt.x, valstring);
				text.SetFieldValue((const char *) &valstring[1], (unsigned long) valstring[0]);
				theRecordHandler.SetFieldObject(2, text);
				
				(void) UserCoord2UnitString(eIncludeUserOriginY, inPt.y, valstring);
				text.SetFieldValue((const char *) &valstring[1], (unsigned long) valstring[0]);
				theRecordHandler.SetFieldObject(3, text);
				
				(void) UserCoord2UnitString(eNoUserOrigin, inPt.z, valstring);
				text.SetFieldValue((const char *) &valstring[1], (unsigned long) valstring[0]);
				theRecordHandler.SetFieldObject(4, text);
	
				coord.SetFieldValue(inPt.x);
				theRecordHandler.SetFieldObject(5, coord);
				coord.SetFieldValue(inPt.y);
				theRecordHandler.SetFieldObject(6, coord);
				coord.SetFieldValue(inPt.z);
				theRecordHandler.SetFieldObject(7, coord);
			}
		}
	}
}

void __DebugLocus(const WorldPt& inPt, const char *inFormat, ...)
{
	va_list list;
	va_start(list, inFormat);
	DoDebugLocus(WorldPt3(inPt.x, inPt.y, 0), inFormat, list);
	va_end(list);
}

void __DebugLocus(Boolean inExpression, const WorldPt& inPt, const char *inFormat, ...)
{
	if (inExpression == true) {
		va_list list;
		va_start(list, inFormat);
		DoDebugLocus(WorldPt3(inPt.x, inPt.y, 0), inFormat, list);
	}
}

void __DebugLocus(const WorldPt3& inPt, const char *inFormat, ...)
{
	va_list list;
	va_start(list, inFormat);
	DoDebugLocus(inPt, inFormat, list);
}

void __DebugLocus(Boolean inExpression, const WorldPt3& inPt, const char *inFormat, ...)
{
	if (inExpression == true) {
		va_list list;
		va_start(list, inFormat);
		DoDebugLocus(inPt, inFormat, list);
	}
}

#endif	// !BUG
