/** \file dcar.c
 * TRAIN
 */

/*  XTrkCad - Model Railroad CAD
 *  Copyright (C) 2005 Dave Bullis
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "cselect.h"
#include "ctrain.h"
#include "custom.h"
#include "fileio.h"
#include "layout.h"
#include "param.h"
#include "paths.h"
#include "track.h"
#include "include/paramfile.h"
#include "common-ui.h"

static int log_carList;
static int log_carInvList;
static int log_carDlgState;
static int log_carDlgList;
static int log_carDlgDims;

//static paramFloatRange_t r0_99999 = { 0, 99999, 80 };
static paramFloatRange_t r0d001_99999 = { 0.001, 99999, 80 };
static paramFloatRange_t r9999_9999 = {-99999, 99999, 80};
static paramIntegerRange_t i1_999999999 = { 1, 999999999, 80, PDO_NORANGECHECK_HIGH };
static paramIntegerRange_t i1_9999 = { 1, 9999, 50 };
static char * isLocoLabels[] = { "", 0 };
static char * cplrModeLabels[] = { N_("Truck"), N_("Body"), 0 };
static BOOL_T carProtoListChanged;
static void CarInvListAdd( carItem_p item );
static void CarInvListUpdate( carItem_p item );

#define T_MANUF			(0)
#define T_PROTO			(1)
#define T_DESC			(2)
#define T_PART			(3)
#define T_ROADNAME		(4)
#define T_REPMARK		(5)
#define T_NUMBER		(6)

typedef struct {
	char * name;
	long value;
} nameLongMap_t;


#define CAR_DESC_COUPLER_MODE_BODY	(1L<<0)
#define CAR_DESC_IS_LOCO			(1L<<1)
#define CAR_DESC_IS_LOCO_MASTER		(1L<<2)
#define CAR_ITEM_HASNOTES			(1L<<8)
#define CAR_ITEM_ONLAYOUT			(1L<<9)

#define CAR_DESC_BITS				(0x000000FF)
#define CAR_ITEM_BITS				(0x0000FF00)


typedef struct carProto_t * carProto_p;

typedef struct {
	DIST_T			carLength;
	DIST_T			carWidth;
	DIST_T			truckCenter;
	DIST_T          truckCenterOffset;
	DIST_T			coupledLength;
} carDim_t;
typedef struct {
	char			* number;
	FLOAT_T			purchPrice;
	FLOAT_T			currPrice;
	long			condition;
	long			purchDate;
	long			serviceDate;
	char			* notes;
} carData_t;

struct carItem_t {
	long			index;
	SCALEINX_T		scaleInx;
	char			* contentsLabel;
	char			* title;
	carProto_p		proto;
	DIST_T			barScale;
	wDrawColor		color;
	long			options;
	long			type;
	carDim_t		dim;
	carData_t		data;
	wIndex_t		segCnt;
	trkSeg_p		segPtr;
	track_p			car;
	coOrd			pos;
	ANGLE_T			angle;
};


/*
 *  Utilities
 */



typedef struct {
	char * ptr;
	int len;
} tabString_t, *tabString_p;


static void TabStringExtract(
        char * string,
        int count,
        tabString_t * tabs )
{
	int inx;
	char * next = string;

	for ( inx=0; inx<count; inx++ ) {
		tabs[inx].ptr = string;
		if ( next ) {
			next = strchr( string, '\t' );
		}
		if ( next ) {
			tabs[inx].len = (int)(next-string);
			string = next+1;
		} else {
			tabs[inx].len = (int)strlen( string );
			string += tabs[inx].len;
		}
	}
	if ( tabs[T_MANUF].len == 0 ) {
		tabs[T_MANUF].len = 7;
		tabs[T_MANUF].ptr = N_("Unknown");
	}
}


static char * TabStringDup(
        tabString_t * tab )
{
	char * ret;
	ret = MyMalloc( tab->len+1 );
	memcpy( ret, tab->ptr, tab->len );
	ret[tab->len] = '\0';
	return ret;
}


static char * TabStringCpy(
        char * dst,
        tabString_t * tab )
{
	memcpy( dst, tab->ptr, tab->len );
	dst[tab->len] = '\0';
	return dst+tab->len;
}


static int TabStringCmp(
        char * src,
        tabString_t * tab )
{
	size_t srclen = strlen(src);
	size_t len = srclen;
	int rc;
	if ( len > tab->len ) {
		len = tab->len;
	}
	rc = strncasecmp( src, tab->ptr, len );
	if ( rc != 0 || srclen == tab->len ) {
		return rc;
	} else if ( srclen > tab->len ) {
		return 1;
	} else {
		return -1;
	}
}


static long TabGetLong(
        tabString_t * tab )
{
	char old_c;
	long val;
	if ( tab->len <= 0 ) {
		return 0;
	}
	old_c = tab->ptr[tab->len];
	tab->ptr[tab->len] = '\0';
	val = atol( tab->ptr );
	tab->ptr[tab->len] = old_c;
	return val;
}


static FLOAT_T TabGetFloat(
        tabString_t * tab )
{
	char old_c;
	FLOAT_T val;
	if ( tab->len <= 0 ) {
		return 0.0;
	}
	old_c = tab->ptr[tab->len];
	tab->ptr[tab->len] = '\0';
	val = atof( tab->ptr );
	tab->ptr[tab->len] = old_c;
	return val;
}


static void RotatePts(
        int cnt,
        coOrd * pts,
        coOrd orig,
        ANGLE_T angle )
{
	int inx;
	for ( inx=0; inx<cnt; inx++ ) {
		Rotate( &pts[inx], orig, angle );
	}
}


static void RescalePts(
        int cnt,
        coOrd * pts,
        FLOAT_T scale_x,
        FLOAT_T scale_y )
{
	int inx;
	for ( inx=0; inx<cnt; inx++ ) {
		pts[inx].x *= scale_x;
		pts[inx].y *= scale_y;
	}
}


static int lookupListIndex;
static void * LookupListElem(
        dynArr_t * da,
        void * key,
        int (*cmpFunc)( void *, void * ),
        int elem_size )
{
	int hi, lo, mid, rc;
	lo = 0;
	hi = da->cnt-1;
	while (lo <= hi ) {
		mid = (lo+hi)/2;
		rc = cmpFunc( key, DYNARR_N(void*,*da,mid) );
		if ( rc == 0 ) {
			lookupListIndex = mid;
			return DYNARR_N(void*,*da,mid);
		}
		if ( rc > 0 ) {
			lo = mid+1;
		} else {
			hi = mid-1;
		}
	}
	if ( elem_size == 0 ) {
		lookupListIndex = -1;
		return NULL;
	}
	DYNARR_APPEND( void*, *da, 10 );
	for ( mid=da->cnt-1; mid>lo; mid-- ) {
		DYNARR_N(void*,*da,mid) = DYNARR_N(void*,*da,mid-1);
	}
	DYNARR_N(void*,*da,lo) = MyMalloc(elem_size);
	memset( DYNARR_N(void*,*da,lo), 0, elem_size );
	lookupListIndex = lo;
	return DYNARR_N(void*,*da,lo);
}

static void RemoveListElem(
        dynArr_t * da,
        void * elem )
{
	int inx;
	for ( inx=0; inx<da->cnt; inx++ )
		if ( DYNARR_N(void*,*da,inx) == elem ) {
			break;
		}
	CHECK( inx<da->cnt );
	for ( inx++; inx<da->cnt; inx++ ) {
		DYNARR_N(void*,*da,inx-1) = DYNARR_N(void*,*da,inx);
	}
	da->cnt--;
}

/*
 *  Draw Car Parts
 */


#define BW (8)
#define TW (45)
#define SI (30)
#define SO (37)
static coOrd truckOutline[] = {
	{ -TW, -SO },
	{  TW, -SO },
	{  TW, -SI },
	{  BW, -SI },
	{  BW,  SI },
	{  TW,  SI },
	{  TW,  SO },
	{ -TW,  SO },
	{ -TW,  SI },
	{ -BW,  SI },
	{ -BW, -SI },
	{ -TW, -SI }
};
#define WO ((56.6-2)/2)
#define WI ((56.6-12)/2)
#define Wd (36/2)
#define AW (8/2)
static coOrd wheelOutline[] = {
	{ -Wd, -WO },

	{ -AW, -WO },
	{ -AW, -SI },
	{  AW, -SI },
	{  AW, -WO },

	{  Wd, -WO },
	{  Wd, -WI },
	{  AW, -WI },
	{  AW,  WI },
	{  Wd,  WI },
	{  Wd,  WO },

	{  AW,  WO },
	{  AW,  SI },
	{ -AW,  SI },
	{ -AW,  WO },

	{ -Wd,  WO },
	{ -Wd,  WI },
	{ -AW,  WI },
	{ -AW, -WI },

	{ -Wd, -WI }
};

static void MovePts(
        int cnt,
        coOrd * pts,
        coOrd orig )
{
	int inx;
	for ( inx=0; inx<cnt; inx++ ) {
		pts[inx].x += orig.x;
		pts[inx].y += orig.y;
	}
}


static void CarProtoDrawTruck(
        drawCmd_t * d,
        DIST_T width,
        FLOAT_T ratio,
        coOrd pos,
        ANGLE_T angle )
{
	coOrd p[24], pp;
	wDrawColor color = wDrawColorBlack;

	memcpy( p, truckOutline, sizeof truckOutline );
	RescalePts( COUNT( truckOutline ), p, 1.0, width/56.5 );
	RescalePts( COUNT( truckOutline ), p, ratio, ratio );
	RotatePts( COUNT( truckOutline ), p, zero, angle );
	MovePts( COUNT( truckOutline ), p, pos );
	DrawPoly( d, COUNT( truckOutline ), p, NULL, color, 0, DRAW_FILL);
	pp.x = -70/2;
	pp.y = 0;
	memcpy( p, wheelOutline, sizeof wheelOutline );
	RescalePts( COUNT( wheelOutline ), p, 1.0, width/56.5 );
	MovePts( COUNT( wheelOutline ), p, pp );
	RescalePts( COUNT( wheelOutline ), p, ratio, ratio );
	RotatePts( COUNT( wheelOutline ), p, zero, angle );
	MovePts( COUNT( wheelOutline ), p, pos );
	DrawPoly( d, COUNT( wheelOutline ), p, NULL, color, 0, DRAW_FILL);
	pp.x = 70/2;
	memcpy( p, wheelOutline, sizeof wheelOutline );
	RescalePts( COUNT( wheelOutline ), p, 1.0, width/56.5 );
	MovePts( COUNT( wheelOutline ), p, pp );
	RescalePts( COUNT( wheelOutline ), p, ratio, ratio );
	RotatePts( COUNT( wheelOutline ), p, zero, angle );
	MovePts( COUNT( wheelOutline ), p, pos );
	DrawPoly( d, COUNT( wheelOutline ), p, NULL, color, 0, DRAW_FILL );
}


static coOrd couplerOutline[] = {
	{ 0, 2.5 },
	{ 0, -2.5 },
	{ 0, -2.5 },
	{ 3, -7 },
	{ 14, -5 },
	{ 14, 2 },
	{ 12, 2 },
	{ 12, -2 },
	{ 9, -2 },
	{ 9, 3 },
	{ 13, 6 },
	{ 13, 7 },
	{ 6, 7 },
	{ 0, 2.5 }
};
static void CarProtoDrawCoupler(
        drawCmd_t * d,
        DIST_T length,
        FLOAT_T ratio,
        coOrd pos,
        ANGLE_T angle )
{
	coOrd p[24], pp;
	wDrawColor color = wDrawColorBlack;

	length /= ratio;
	if ( length < 12.0 ) {
		return;
	}
	memcpy( p, couplerOutline, sizeof couplerOutline );
	p[0].x = p[1].x = -(length-12.0);
	pp.x = length-12.0;
	pp.y = 0;
	/* TODO - if length > 6 then draw Sills */
	MovePts( COUNT( couplerOutline ), p, pp );
	RescalePts( COUNT( couplerOutline ), p, ratio, ratio );
	RotatePts( COUNT( couplerOutline ), p, zero, angle-90.0 );
	MovePts( COUNT( couplerOutline ), p, pos );
	DrawPoly( d, COUNT( couplerOutline ), p, NULL, color, 0, DRAW_FILL );
}



/*
 *  Car Proto
 */


struct carProto_t;
typedef struct carProto_t carProto_t;

struct carProto_t {
	char            * contentsLabel;
	wIndex_t        paramFileIndex;
	char            * desc;
	long            options;
	long            type;
	carDim_t        dim;
	int             segCnt;
	trkSeg_p        segPtr;
	coOrd           size;
	coOrd           orig;
};

static dynArr_t carProto_da;
#define carProto(N) DYNARR_N( carProto_t*, carProto_da, N )

#define N_TYPELISTMAP           (7)
static nameLongMap_t typeListMap[N_TYPELISTMAP] = {
	{ N_("Diesel Loco"),  10101 },
	{ N_("Steam Loco"),  10201 },
	{ N_("Elect Loco"),  10301 },
	{ N_("Freight Car"),  30100 },
	{ N_("Psngr Car"),  50100 },
	{ N_("M-O-W"),  70100 },
	{ N_("Other"),  90100 }
};

static trkSeg_p carProtoSegPtr;
static int carProtoSegCnt;


static pts_t dummyOutlineSegPts[5];
static trkSeg_t dummyOutlineSegs;
static void CarProtoDlgCreateDummyOutline(
        int * segCntP,
        trkSeg_p * segPtrP,
        BOOL_T isLoco,
        DIST_T length,
        DIST_T width,
        wDrawColor color )
{
	trkSeg_p segPtr;
	pts_t * pts;
	DIST_T length2;

	*segCntP = 1;
	segPtr = *segPtrP = &dummyOutlineSegs;

	segPtr->type = SEG_FILPOLY;
	segPtr->color = color;
	segPtr->lineWidth = 0;
	segPtr->u.p.cnt = isLoco?5:4;
	segPtr->u.p.pts = pts = dummyOutlineSegPts;
	segPtr->u.p.orig.x = 0;
	segPtr->u.p.orig.y = 0;
	segPtr->u.p.angle = 0;
	length2 = length;
	if ( isLoco ) {
		pts->pt.x = length;
		pts->pt.y = width/2.0;
		pts->pt_type = 0;
		pts++;
		length2 -= width/2.0;
	}
	pts->pt.x = length2;
	pts->pt.y = 0.0;
	pts->pt_type = 0;
	pts++;
	pts->pt.x = 0.0;
	pts->pt.y = 0.0;
	pts->pt_type = 0;
	pts++;
	pts->pt.x = 0.0;
	pts->pt.y = width;
	pts->pt_type = 0;
	pts++;
	pts->pt.x = length2;
	pts->pt.y = width;
	pts->pt_type = 0;
}


static int CarProtoFindTypeCode(
        long code )
{
	int inx;
	for ( inx=0; inx<N_TYPELISTMAP; inx++ ) {
		if ( typeListMap[inx].value > code ) {
			if ( inx == 0 ) {
				return N_TYPELISTMAP-1;
			} else {
				return inx-1;
			}
		}
	}
	return N_TYPELISTMAP-1;
}


static int CmpCarProto(
        void * key,
        void * elem )
{
	char * key_val=key;
	carProto_p elem_val=elem;
	return strcasecmp( key_val, elem_val->desc );
}


static carProto_p CarProtoFind(
        char * desc )
{
	return LookupListElem( &carProto_da, desc, CmpCarProto, 0 );
}


static carProto_p CarProtoLookup(
        char * desc,
        BOOL_T createMissing,
        BOOL_T isLoco,
        DIST_T length,
        DIST_T width )
{
	carProto_p proto;
	trkSeg_p segPtr;
	proto = LookupListElem( &carProto_da, desc, CmpCarProto,
	                        createMissing?sizeof *proto:0 );
	if ( proto == NULL ) {
		return NULL;
	}
	if ( proto->desc == NULL ) {
		proto->desc = MyStrdup(desc);
		proto->contentsLabel = "Car Prototype";
		proto->paramFileIndex = PARAM_LAYOUT;
		proto->options = (isLoco?CAR_DESC_IS_LOCO:0);
		proto->dim.carLength = length;
		proto->dim.carWidth = width;
		proto->dim.truckCenter = length - 2.0*59.0;
		proto->dim.coupledLength = length + 2.0*16.0;
		CarProtoDlgCreateDummyOutline( &proto->segCnt, &segPtr, isLoco, length, width,
		                               drawColorBlue );
		proto->segPtr = (trkSeg_p)memdup( segPtr,
		                                  (sizeof *(trkSeg_p)0) * proto->segCnt );
		CloneFilledDraw( proto->segCnt, proto->segPtr, FALSE );
		GetSegBounds( zero, 0.0, proto->segCnt, proto->segPtr, &proto->orig,
		              &proto->size );
		carProtoListChanged = TRUE;
		// return proto;
	}
	return proto;
}

enum paramFileState
GetCarProtoCompatibility(int paramFileIndex, SCALEINX_T scaleIndex) {
	int i;
	enum paramFileState ret = PARAMFILE_NOTUSABLE;
//	DIST_T ratio = GetScaleRatio(scaleIndex);

	if (!IsParamValid(paramFileIndex))
	{
		return(PARAMFILE_UNLOADED);
	}

	for (i = 0; i < carProto_da.cnt; i++)
	{
		carProto_t *carProto = carProto(i);
		if (carProto->paramFileIndex == paramFileIndex) {
			ret = PARAMFILE_FIT;
			break;
		}
	}
	return(ret);
}

static carProto_p CarProtoNew(
        carProto_p proto,
        int paramFileIndex,
        char * desc,
        long options,
        long type,
        carDim_t * dim,
        wIndex_t segCnt,
        trkSeg_p segPtr )
{
	if ( proto == NULL ) {
		proto = LookupListElem( &carProto_da, desc, CmpCarProto, sizeof *proto );
		if ( proto->desc != NULL ) {
			if ( proto->paramFileIndex == PARAM_CUSTOM &&
			     paramFileIndex != PARAM_CUSTOM ) {
				return proto;
			}
		}
	}
	if ( proto->desc != NULL ) {
		MyFree( proto->desc );
	}
	proto->desc = MyStrdup(desc);
	proto->contentsLabel = "Car Prototype";
	proto->paramFileIndex = paramFileIndex;
	proto->options = options;
	proto->type = type;
	proto->dim = *dim;
	proto->segCnt = segCnt;
	//if (proto->segPtr)				Can't do this because segPtr could be static
	//	free(proto->segPtr);
	proto->segPtr = (trkSeg_p)memdup( segPtr,
	                                  (sizeof *(trkSeg_p)0) * proto->segCnt );
	CloneFilledDraw( proto->segCnt, proto->segPtr, FALSE );
	GetSegBounds( zero, 0.0, proto->segCnt, proto->segPtr, &proto->orig,
	              &proto->size );
	carProtoListChanged = TRUE;
	return proto;
}


static void CarProtoDelete(
        carProto_p protoP )
{
	if ( protoP == NULL ) {
		return;
	}
	RemoveListElem( &carProto_da, protoP );
	if ( protoP->desc ) {
		MyFree( protoP->desc );
	}
	MyFree( protoP );
}


/**
* Delete all car prototype definitions that came from a specific parameter file.
* Due to the way the definitions are loaded from file it is safe to
* assume that they form a contiguous block in the array.
*
* \param [IN] fileIndex parameter file
*/

void
DeleteCarProto(int fileIndex)
{
	int inx = 0;
	int startInx = -1;
	int cnt = 0;

	// go to the start of the block
	while (inx < carProto_da.cnt && carProto(inx)->paramFileIndex != fileIndex) {
		startInx = inx++;
	}

	// delete them
	for (; inx < carProto_da.cnt
	     && carProto(inx)->paramFileIndex == fileIndex; inx++) {
		carProto_t * cp = carProto(inx);
		if (cp->paramFileIndex == fileIndex) {
			CarProtoDelete(cp);
			cnt++;
		}
	}

	// copy down the rest of the list to fill the gap
	startInx++;
	while (inx < carProto_da.cnt) {
		carProto(startInx++) = carProto(inx++);
	}

	// and reduce the actual number
	carProto_da.cnt -= cnt;
}

static BOOL_T CarProtoRead(
        char * line )
{
	char * desc;
	long options;
	long type;
	carDim_t dim;
	long longCenterOffset;

	if ( !GetArgs( line+9, "qllff0lff",
	               &desc, &options, &type, &dim.carLength, &dim.carWidth, &longCenterOffset,
	               &dim.truckCenter, &dim.coupledLength ) ) {
		return FALSE;
	}
	dim.truckCenterOffset = longCenterOffset/1000.0;
	if ( !ReadSegs() ) {
		return FALSE;
	}
	CarProtoNew( NULL, curParamFileIndex, desc, options, type, &dim,
	             tempSegs_da.cnt, &tempSegs(0) );
	FreeFilledDraw(tempSegs_da.cnt,&tempSegs(0));
	MyFree(desc);
	return TRUE;
}


static BOOL_T CarProtoWrite(
        FILE * f,
        carProto_t * proto )
{
	BOOL_T rc = TRUE;

	SetCLocale();

	long longCenterOffset = (long)(proto->dim.truckCenterOffset*1000);

	rc &= fprintf( f, "CARPROTO \"%s\" %ld %ld %0.3f %0.3f 0 %ld %0.3f %0.3f\n",
	               PutTitle(proto->desc), proto->options, proto->type, proto->dim.carLength,
	               proto->dim.carWidth, longCenterOffset, proto->dim.truckCenter,
	               proto->dim.coupledLength )>0;
	rc &= WriteSegs( f, proto->segCnt, proto->segPtr );

	SetUserLocale();

	return rc;
}



static BOOL_T CarProtoCustomSave(
        FILE * f )
{
	int inx;
	carProto_t * proto;
	BOOL_T rc = TRUE;

	for ( inx=0; inx<carProto_da.cnt; inx++ ) {
		proto = carProto(inx);
		if ( proto->paramFileIndex == PARAM_CUSTOM ) {
			rc &= CarProtoWrite( f, proto );
		}
	}
	return rc;
}


/*
 *  Car Desc
 */

struct carPart_t;
typedef struct carPart_t carPart_t;
typedef carPart_t * carPart_p;
struct carPartParent_t;
typedef struct carPartParent_t carPartParent_t;
typedef carPartParent_t * carPartParent_p;

typedef struct {
	char * name;
	int len;
} cmp_key_t;

typedef struct {
	tabString_t manuf;
	tabString_t proto;
	SCALEINX_T scale;
} cmp_partparent_t;
struct carPartParent_t {
	char * manuf;
	char * proto;
	SCALEINX_T scale;
	dynArr_t parts_da;
};
struct carPart_t {
	carPartParent_p parent;
	wIndex_t paramFileIndex;
	char * title;
	long options;
	long type;
	carDim_t dim;
	wDrawColor color;
	char * partnoP;
	int partnoL;
};
static dynArr_t carPartParent_da;
#define carPartParent(N)	DYNARR_N(carPartParent_p, carPartParent_da, N)
#define carPart(P,N)		DYNARR_N(carPart_p, (P)->parts_da, N)
struct roadnameMap_t;
typedef struct roadnameMap_t roadnameMap_t;
typedef roadnameMap_t * roadnameMap_p;
struct roadnameMap_t {
	char * roadname;
	char * repmark;
};
static dynArr_t roadnameMap_da;
#define roadnameMap(N) DYNARR_N(roadnameMap_p, roadnameMap_da, N)
static BOOL_T roadnameMapChanged;


static int Cmp_part(
        void * key,
        void * elem )
{
	carPart_p cmp_key=key;
	carPart_p part_elem=elem;
	int rc;
	int len;

	len = min( cmp_key->partnoL, part_elem->partnoL );
	rc = strncasecmp( cmp_key->partnoP, part_elem->partnoP, len+1 );
	if ( rc != 0 ) {
		return rc;
	}
	if ( cmp_key->paramFileIndex == part_elem->paramFileIndex ) {
		return 0;
	}
	if ( cmp_key->paramFileIndex == PARAM_DEMO ) {
		return -1;
	}
	if ( part_elem->paramFileIndex == PARAM_DEMO ) {
		return 1;
	}
	if ( cmp_key->paramFileIndex == PARAM_CUSTOM ) {
		return -1;
	}
	if ( part_elem->paramFileIndex == PARAM_CUSTOM ) {
		return 1;
	}
	if ( cmp_key->paramFileIndex == PARAM_LAYOUT ) {
		return 1;
	}
	if ( part_elem->paramFileIndex == PARAM_LAYOUT ) {
		return -1;
	}
	if ( cmp_key->paramFileIndex > part_elem->paramFileIndex ) {
		return -1;
	} else {
		return 1;
	}
}


static int Cmp_partparent(
        void * key,
        void * elem )
{
	cmp_partparent_t * cmp_key=key;
	carPartParent_p part_elem=elem;
	int rc;

	rc = - TabStringCmp( part_elem->manuf, &cmp_key->manuf );
	if ( rc != 0 ) {
		return rc;
	}
	rc = cmp_key->scale - part_elem->scale;
	if ( rc != 0 ) {
		return rc;
	}
	rc = - TabStringCmp( part_elem->proto, &cmp_key->proto );
	return rc;
}


static int Cmp_roadnameMap(
        void * key,
        void * elem )
{
	cmp_key_t * cmp_key=key;
	roadnameMap_p roadname_elem=elem;
	int rc;

	rc = strncasecmp( cmp_key->name, roadname_elem->roadname, cmp_key->len );
	if ( rc == 0 && roadname_elem->roadname[cmp_key->len] ) {
		return -1;
	}
	return rc;
}


static roadnameMap_p LoadRoadnameList(
        tabString_p roadnameTab,
        tabString_p repmarkTab )
{
	cmp_key_t cmp_key;
	roadnameMap_p roadnameMapP;

	lookupListIndex = -1;
	if ( roadnameTab->len<=0 ) {
		return NULL;
	}
	if ( TabStringCmp( "undecorated", roadnameTab ) == 0 ) {
		return NULL;
	}

	cmp_key.name = roadnameTab->ptr;
	cmp_key.len = roadnameTab->len;
	roadnameMapP = LookupListElem( &roadnameMap_da, &cmp_key, Cmp_roadnameMap,
	                               sizeof (roadnameMap_t) );
	if ( roadnameMapP->roadname == NULL ) {
		roadnameMapP->roadname = TabStringDup(roadnameTab);
		roadnameMapP->repmark = TabStringDup(repmarkTab);
		roadnameMapChanged = TRUE;
	} else if ( repmarkTab->len > 0 &&
	            ( roadnameMapP->repmark == NULL || roadnameMapP->repmark[0] == '\0' ) ) {
		roadnameMapP->repmark = TabStringDup(repmarkTab);
		roadnameMapChanged = TRUE;
	}
	return roadnameMapP;
}


static carPart_p CarPartFind(
        char * manufP,
        int manufL,
        char * partnoP,
        int partnoL,
        SCALEINX_T scale )
{
	wIndex_t inx1, inx2;
	carPart_p partP;
	carPartParent_p parentP;
	for ( inx1=0; inx1<carPartParent_da.cnt; inx1++ ) {
		parentP = carPartParent(inx1);
		if ( manufL == (int)strlen(parentP->manuf) &&
		     strncasecmp( manufP, parentP->manuf, manufL ) == 0 &&
		     scale == parentP->scale ) {
			for ( inx2=0; inx2<parentP->parts_da.cnt; inx2++ ) {
				partP = carPart( parentP, inx2 );
				if ( partnoL == partP->partnoL &&
				     strncasecmp( partnoP, partP->partnoP, partnoL ) == 0 ) {
					return partP;
				}
			}
		}
	}
	return NULL;
}




static void CarPartParentDelete(
        carPartParent_p parentP )
{
	RemoveListElem( &carPartParent_da, parentP );
	MyFree( parentP->manuf );
	MyFree( parentP->proto );
	MyFree( parentP );
}


static void CarPartUnlink(
        carPart_p partP )
{
	carPartParent_p parentP = partP->parent;
	RemoveListElem( &parentP->parts_da, partP );
	if ( parentP->parts_da.cnt <= 0 ) {
		CarPartParentDelete( parentP );
	}
}


static carPartParent_p CarPartParentNew(
        char * manufP,
        int manufL,
        char *protoP,
        int protoL,
        SCALEINX_T scale )
{
	carPartParent_p parentP;
	cmp_partparent_t cmp_key;
	cmp_key.manuf.ptr = manufP;
	cmp_key.manuf.len = manufL;
	cmp_key.proto.ptr = protoP;
	cmp_key.proto.len = protoL;
	cmp_key.scale = scale;
	parentP = (carPartParent_p)LookupListElem( &carPartParent_da, &cmp_key,
	                Cmp_partparent, sizeof * parentP);
	if ( parentP->manuf == NULL ) {
		parentP->manuf = (char*)MyMalloc( manufL+1 );
		memcpy( parentP->manuf, manufP, manufL );
		parentP->manuf[manufL] = '\0';
		parentP->proto = (char*)MyMalloc( protoL+1 );
		memcpy( parentP->proto, protoP, protoL );
		parentP->proto[protoL] = '\0';
		parentP->scale = scale;
	}
	return parentP;
}


static carPart_p CarPartNew(
        carPart_p partP,
        int paramFileIndex,
        SCALEINX_T scaleInx,
        char * title,
        long options,
        long type,
        carDim_t *dim,
        wDrawColor color)
{
	carPartParent_p parentP;
	carPart_t cmp_key;
	tabString_t tabs[7];

	TabStringExtract(title, 7, tabs);
	if (TabStringCmp("Undecorated", &tabs[T_MANUF]) == 0 ||
	    TabStringCmp("Custom", &tabs[T_MANUF]) == 0 ||
	    tabs[T_PART].len == 0) {
		return NULL;
	}
	if (tabs[T_PROTO].len == 0) {
		return NULL;
	}
	if (partP == NULL) {
		partP = CarPartFind(tabs[T_MANUF].ptr, tabs[T_MANUF].len, tabs[T_PART].ptr,
		                    tabs[T_PART].len, scaleInx);
		if (partP != NULL &&
		    partP->paramFileIndex == PARAM_CUSTOM &&
		    paramFileIndex != PARAM_CUSTOM) {
			return partP;
		}
		LOG(log_carList, 2, ("new car part: %s (%d) at %d\n", title, paramFileIndex,
		                     lookupListIndex))
	}
	if (partP != NULL) {
		CarPartUnlink(partP);
		if (partP->title != NULL) {
			MyFree(partP->title);
		}
		LOG(log_carList, 2, ("upd car part: %s (%d)\n", title, paramFileIndex))
	}
	LoadRoadnameList(&tabs[T_ROADNAME], &tabs[T_REPMARK]);
	parentP = CarPartParentNew(tabs[T_MANUF].ptr, tabs[T_MANUF].len,
	                           tabs[T_PROTO].ptr, tabs[T_PROTO].len, scaleInx);
	cmp_key.title = title;
	cmp_key.parent = parentP;
	cmp_key.paramFileIndex = paramFileIndex;
	cmp_key.options = options;
	cmp_key.type = type;
	cmp_key.dim = *dim;
	cmp_key.color = color;
	cmp_key.partnoP = tabs[T_PART].ptr;
	cmp_key.partnoL = tabs[T_PART].len;
	partP = (carPart_p)LookupListElem(&parentP->parts_da, &cmp_key, Cmp_part,
	                                  sizeof * partP);
	if (partP->title != NULL) {
		MyFree(partP->title);
	}
	*partP = cmp_key;
	sprintf(message, "\t\t%s", tabs[2].ptr);
	partP->title = MyStrdup(message);
	partP->partnoP = partP->title + 2 + tabs[2].len + 1;;
	partP->partnoL = tabs[T_PART].len;
	return partP;
}


static void CarPartDelete(
        carPart_p partP )
{
	if ( partP == NULL ) {
		return;
	}
	CarPartUnlink( partP );
	if ( partP->title ) {
		MyFree( partP->title );
	}
	MyFree( partP );
}

/**
* Delete all car part definitions that came from a specific parameter file.
* CarParts are stored in DYNARR for the specific car model. These DYNARRs
* are linked from CarPartParents, again DYNARRs. Thes parents are created
* from part definition and only contain manufacturer and type information.
*
* \param [IN] fileIndex parameter file
*/

void
DeleteCarPart(int fileIndex)
{
	int inxParent = 0;
	int inx;

	while (inxParent < carPartParent_da.cnt) {
		inx = 0;
		while (inx < carPartParent(inxParent)->parts_da.cnt) {
			carPart_p part = carPart(carPartParent(inxParent), inx++);
			if (part->paramFileIndex == fileIndex) {
				CarPartDelete(part);
			}
		}
		inxParent++;
	}
}

static BOOL_T CarPartRead(
        char * line )
{
	char scale[10];
	long options;
	long type;
	char * title;
	carDim_t dim;
	long rgb;
	long longCenterOffset;

	if ( !GetArgs( line+8, "sqllff0lffl",
	               scale, &title, &options, &type, &dim.carLength, &dim.carWidth,
	               &longCenterOffset, &dim.truckCenter, &dim.coupledLength, &rgb ) ) {
		return FALSE;
	}
	dim.truckCenterOffset = longCenterOffset/1000.0;
	CarPartNew( NULL, curParamFileIndex, LookupScale(scale), title, options, type,
	            &dim, wDrawFindColor(rgb) );
	MyFree( title );
	return TRUE;
}


static BOOL_T CarPartWrite(
        FILE * f,
        carPart_p partP )
{
	BOOL_T rc = TRUE;
	carPartParent_p parentP=partP->parent;
	tabString_t tabs[7];
	long longCenterOffset = (long)(partP->dim.truckCenterOffset*1000);

	SetCLocale();

	TabStringExtract( partP->title, 7, tabs );
	sprintf( message, "%s\t%s\t%.*s\t%.*s\t%.*s\t%.*s\t%.*s",
	         parentP->manuf, parentP->proto,
	         tabs[T_DESC].len, tabs[T_DESC].ptr,
	         tabs[T_PART].len, tabs[T_PART].ptr,
	         tabs[T_ROADNAME].len, tabs[T_ROADNAME].ptr,
	         tabs[T_REPMARK].len, tabs[T_REPMARK].ptr,
	         tabs[T_NUMBER].len, tabs[T_NUMBER].ptr );
	rc &= fprintf( f, "CARPART %s \"%s\"", GetScaleName(partP->parent->scale),
	               PutTitle(message) )>0;
	rc &= fprintf( f, " %ld %ld %0.3f %0.3f 0 %ld %0.3f %0.3f %ld\n",
	               partP->options, partP->type, partP->dim.carLength, partP->dim.carWidth,
	               longCenterOffset,
	               partP->dim.truckCenter, partP->dim.coupledLength, wDrawGetRGB(partP->color) )>0;

	SetUserLocale();

	return rc;
}



static BOOL_T CarDescCustomSave(
        FILE * f )
{
	int parentX;
	carPartParent_p parentP;
	int partX;
	carPart_p partP;
	BOOL_T rc = TRUE;

	for ( parentX=0; parentX<carPartParent_da.cnt; parentX++ ) {
		parentP = carPartParent(parentX);
		for ( partX=0; partX<parentP->parts_da.cnt; partX++ ) {
			partP = carPart(parentP,partX);
			if ( partP->paramFileIndex == PARAM_CUSTOM ) {
				rc &= CarPartWrite(f, partP );
			}
		}
	}
	return rc;
}



/*
 *  Car Item
 */

static dynArr_t carItemInfo_da;
#define carItemInfo(N) DYNARR_N( carItem_t*, carItemInfo_da, N )

#define N_CONDLISTMAP			(6)
static nameLongMap_t condListMap[N_CONDLISTMAP] = {
	{ N_("N/A"), 0 },
	{ N_("Mint"), 100 },
	{ N_("Excellent"), 80 },
	{ N_("Good"), 60 },
	{ N_("Fair"), 40 },
	{ N_("Poor"), 20 }
};


static wIndex_t MapCondition(
        long conditionValue )
{
	if ( conditionValue < 10 ) {
		return 0;
	} else if ( conditionValue < 30 ) {
		return 5;
	} else if ( conditionValue < 50 ) {
		return 4;
	} else if ( conditionValue < 70 ) {
		return 3;
	} else if ( conditionValue < 90 ) {
		return 2;
	} else {
		return 1;
	}
}


static carItem_p CarItemNew(
        carItem_p item,
        int paramFileIndex,
        long itemIndex,
        SCALEINX_T scale,
        char * title,
        long options,
        long type,
        carDim_t *dim,
        wDrawColor color,
        FLOAT_T purchPrice,
        FLOAT_T currPrice,
        long condition,
        long purchDate,
        long serviceDate )
{
	carPart_p partP;
	tabString_t tabs[7];

	TabStringExtract( title, 7, tabs );
	if ( paramFileIndex != PARAM_CUSTOM ) {
		partP = CarPartFind( tabs[T_MANUF].ptr, tabs[T_MANUF].len, tabs[T_PART].ptr,
		                     tabs[T_PART].len, scale );
		if ( partP == NULL ) {
			CarPartNew( NULL, PARAM_LAYOUT, scale, title, options, type, dim, color );
		}
	}

	if ( item == NULL ) {
		DYNARR_APPEND( carItem_t*, carItemInfo_da, 10 );
		item = (carItem_t*)MyMalloc( sizeof * item );
		carItemInfo(carItemInfo_da.cnt-1) = item;
	} else {
		if ( item->title ) { MyFree( item->title ); }
		if ( item->data.number ) { MyFree( item->data.number ); }
	}
	item->index = itemIndex;
	item->scaleInx = scale;
	item->title = MyStrdup(title);
	item->contentsLabel = "Car Item";
	item->barScale = curBarScale>0?curBarScale:(60.0*12.0/curScaleRatio);
	item->options = options;
	item->type = type;
	item->dim = *dim;
	item->color = color;
	if ( tabs[T_REPMARK].len>0 || tabs[T_NUMBER].len>0 ) {
		sprintf( message, "%.*s%s%.*s", tabs[T_REPMARK].len, tabs[T_REPMARK].ptr,
		         (tabs[T_REPMARK].len>0
		          &&tabs[T_NUMBER].len>0)?" ":"", tabs[T_NUMBER].len, tabs[T_NUMBER].ptr );
	} else {
		sprintf( message, "#%ld", item->index );
	}
	item->data.number = MyStrdup( message );
	item->data.purchPrice = purchPrice;
	item->data.currPrice = currPrice;
	item->data.condition = condition;
	item->data.purchDate = purchDate;
	item->data.serviceDate = serviceDate;
	item->data.notes = NULL;
	item->segCnt = 0;
	item->segPtr = NULL;
	LoadRoadnameList( &tabs[T_ROADNAME], &tabs[T_REPMARK] );
	return item;
}

/**
 * Check the whether the parameter file has CARPARTS that are a fit or compatible
 * with the current state.
 *
 * \param paramFileIndex IN the parameter file
 * \param scaleIndex IN the scale to check against
 * \return the compatibility state of the the
 */
enum paramFileState
GetCarPartCompatibility(int paramFileIndex, SCALEINX_T scaleIndex) {
	int i;
	enum paramFileState ret = PARAMFILE_NOTUSABLE;
//	DIST_T ratio = GetScaleRatio(scaleIndex);
//	DIST_T gauge = GetScaleTrackGauge(scaleIndex);

	if (!IsParamValid(paramFileIndex))
	{
		return(PARAMFILE_UNLOADED);
	}

	for (i = 0; i < carPartParent_da.cnt && ret != PARAMFILE_FIT; i++)
	{
		carPartParent_t *carPartParent = carPartParent( i );
		SCALE_FIT_T fit = CompatibleScale(FIT_CAR,carPartParent->scale,scaleIndex);
		if(fit == FIT_EXACT) {
			for(int j = 0; j < carPartParent->parts_da.cnt; j++ ) {
				carPart_t *carPart = carPart( carPartParent, j  );
				if (carPart->paramFileIndex == paramFileIndex) {
					ret = PARAMFILE_FIT;
					break;
				}
			}
		}
		if (fit == FIT_COMPATIBLE) {
			ret = PARAMFILE_COMPATIBLE;
		}
	}
	return(ret);
}

EXPORT BOOL_T CarItemRead(
        char * line )
{
	long itemIndex;
	char scale[10];
	char * title;
	long options;
	long type;
	carDim_t dim;
	long rgb;
	FLOAT_T purchPrice = 0;
	FLOAT_T currPrice = 0;
	long condition = 0;
	long purchDate = 0;
	long serviceDate = 0;
	carItem_p item;
	char * cp;
	wIndex_t layer;
	coOrd pos;
	ANGLE_T angle;
	wIndex_t index;
	long longCenterOffset;
	char * sNote = NULL;

	if ( !GetArgs( line+4, "lsqll" "ff0lffl" "fflll000000c",
	               &itemIndex, scale, &title, &options, &type,
	               &dim.carLength, &dim.carWidth, &longCenterOffset, &dim.truckCenter,
	               &dim.coupledLength, &rgb,
	               &purchPrice, &currPrice, &condition, &purchDate, &serviceDate, &cp ) ) {
		return FALSE;
	}
	dim.truckCenterOffset = longCenterOffset/1000.0;
	if ( paramVersion < VERSION_INLINENOTE ) {
		if ( (options&CAR_ITEM_HASNOTES) ) {
			sNote = ReadMultilineText();
		}
	} else {
		if ( !GetArgs( cp, "qc", &sNote, &cp ) ) {
			return FALSE;
		}
	}
	item = CarItemNew( NULL, curParamFileIndex, itemIndex, LookupScale(scale),
	                   title,
	                   options&(CAR_DESC_BITS|CAR_ITEM_BITS), type, &dim, wDrawFindColor(rgb),
	                   purchPrice, currPrice, condition, purchDate, serviceDate );
	if ( (options&CAR_ITEM_HASNOTES) ) {
		item->data.notes = sNote;
	}
	MyFree(title);
	if ( (options&CAR_ITEM_ONLAYOUT) ) {
		if ( !GetArgs( cp, "dLpf",
		               &index, &layer, &pos, &angle ) ) {
			return FALSE;
		}
		if ( !ReadSegs() ) {
			return FALSE;
		}
		item->car = NewCar( index, item, pos, angle );
		SetTrkLayer( item->car, layer );
		SetEndPts( item->car, 2 );
		ComputeBoundingBox( item->car );
	}
	return TRUE;
}


static BOOL_T CarItemWrite(
        FILE * f,
        carItem_t * item,
        BOOL_T layout )
{
	long options = (item->options&CAR_DESC_BITS);
	coOrd pos;
	ANGLE_T angle;
	BOOL_T rc = TRUE;
	long longCenterOffset = (long)(item->dim.truckCenterOffset*1000);

	SetCLocale();

	if ( item->data.notes && item->data.notes[0] ) {
		options |= CAR_ITEM_HASNOTES;
	}
	if ( layout && item->car && !IsTrackDeleted(item->car) ) {
		options |= CAR_ITEM_ONLAYOUT;
	}
	rc &= fprintf( f,
	               "CAR %ld %s \"%s\" %ld %ld %0.3f %0.3f 0 %ld %0.3f %0.3f %ld %0.3f %0.3f %ld %ld %ld 0 0 0 0 0 0",
	               item->index, GetScaleName(item->scaleInx), PutTitle(item->title),
	               options, item->type,
	               item->dim.carLength, item->dim.carWidth, longCenterOffset,
	               item->dim.truckCenter, item->dim.coupledLength, wDrawGetRGB(item->color),
	               item->data.purchPrice, item->data.currPrice, item->data.condition,
	               item->data.purchDate, item->data.serviceDate )>0;
	if ( (options&CAR_ITEM_HASNOTES) ) {
		char * sEscapedNote = ConvertToEscapedText( item->data.notes );
		rc &= fprintf( f, " \"%s\"", sEscapedNote )>0;
		MyFree( sEscapedNote );
	} else {
		rc &= fprintf( f, " \"\"" ) > 0;
	}
	if ( ( options&CAR_ITEM_ONLAYOUT) ) {
		CarGetPos( item->car, &pos, &angle );
		rc &= fprintf( f, " %d %u %0.3f %0.3f %0.3f\n",
		               GetTrkIndex(item->car), GetTrkLayer(item->car), pos.x, pos.y, angle )>0;
		rc &= WriteEndPt( f, item->car, 0 );
		rc &= WriteEndPt( f, item->car, 1 );
		rc &= fprintf( f, "\t%s\n", END_SEGS )>0;
	} else {
		rc &= fprintf( f, "\n" )>0;
	}

	SetUserLocale();

	return rc;
}



EXPORT carItem_p CarItemFind(
        long itemInx )
{
	if ( itemInx >= 0 && itemInx < carItemInfo_da.cnt ) {
		return carItemInfo(itemInx);
	} else {
		return NULL;
	}
}


EXPORT long CarItemFindIndex(
        carItem_p item )
{
	long inx;
	for ( inx=0; inx<carItemInfo_da.cnt; inx++ )
		if ( carItemInfo(inx) == item ) {
			return inx;
		}
	CHECK( FALSE );
	return -1;
}


EXPORT void CarItemGetSegs(
        carItem_p item )
{
	coOrd orig;
	carProto_p protoP;
	tabString_t tabs[7];
	trkSeg_t * segPtr;
	DIST_T ratio = GetScaleRatio(item->scaleInx);

	TabStringExtract( item->title, 7, tabs );
	TabStringCpy( message, &tabs[T_PROTO] );
	protoP = CarProtoLookup( message, FALSE, FALSE, 0.0, 0.0 );
	if ( protoP != NULL ) {
		item->segCnt = protoP->segCnt;
		segPtr = protoP->segPtr;
		orig = protoP->orig;
	} else {
		CarProtoDlgCreateDummyOutline( &item->segCnt, &segPtr,
		                               (item->options&CAR_DESC_IS_LOCO)!=0, item->dim.carLength, item->dim.carWidth,
		                               item->color );
		orig = zero;
	}
	item->segPtr = (trkSeg_p)MyMalloc( item->segCnt * sizeof *(segPtr) );
	memcpy( item->segPtr, segPtr, item->segCnt * sizeof *(segPtr) );
	CloneFilledDraw( item->segCnt, item->segPtr, FALSE );
	if ( protoP ) {
		orig.x = -orig.x;
		orig.y = -orig.y;
		MoveSegs( item->segCnt, item->segPtr, orig );
		RescaleSegs( item->segCnt, item->segPtr, item->dim.carLength/protoP->size.x,
		             item->dim.carWidth/protoP->size.y, 1/ratio );
		RecolorSegs( item->segCnt, item->segPtr, item->color );
	}
}


EXPORT BOOL_T WriteCars(
        FILE * f )
{
	int inx;
	BOOL_T rc = TRUE;
	for ( inx=0; inx<carItemInfo_da.cnt; inx++ ) {
		rc &= CarItemWrite( f, carItemInfo(inx), TRUE );
	}
	return rc;
}


EXPORT BOOL_T CarCustomSave(
        FILE * f )
{
	BOOL_T rc = TRUE;
	rc &= CarProtoCustomSave( f );
	rc &= CarDescCustomSave( f );
	return rc;
}


/*
 * Car Item Select
 */

EXPORT carItem_p currCarItemPtr;
EXPORT long carHotbarModeInx = 1;
static long carHotbarModes[] = { 0x0002, 0x0012, 0x0312, 0x4312, 0x0021, 0x0321, 0x4321 };
static long carHotbarContents[] = { 0x0005, 0x0002, 0x0012, 0x0012, 0x0001, 0x0021, 0x0021 };
static long newCarInx;
static paramData_t newCarPLs[] = {
	{ PD_DROPLIST, &newCarInx, "index", PDO_DLGWIDE, I2VP(400), N_("Item") }
};
static paramGroup_t newCarPG = { "train-newcar", 0, newCarPLs, COUNT( newCarPLs ) };
EXPORT wControl_p newCarControls[2];
static char newCarLabel1[STR_SIZE];
static char * newCarLabels[2] = { newCarLabel1, NULL };

static dynArr_t carItemHotbar_da;
#define carItemHotbar(N)			DYNARR_N( carItem_p, carItemHotbar_da, N )


static int Cmp_carHotbar(
        const void * ptr1,
        const void * ptr2 )
{
	carItem_p item1 = *(carItem_p*)ptr1;
	carItem_p item2 = *(carItem_p*)ptr2;
	tabString_t tabs1[7], tabs2[7];
	int rc;
	long mode;

	TabStringExtract( item1->title, 7, tabs1 );
	TabStringExtract( item2->title, 7, tabs2 );
	for ( mode=carHotbarModes[carHotbarModeInx],rc=0; mode!=0&&rc==0; mode>>=4 ) {
		switch ( mode&0x000F ) {
		case 4:
			rc = (int)(item1->index-item2->index);
			break;
		case 1:
			rc = strncasecmp( tabs1[T_MANUF].ptr, tabs2[T_MANUF].ptr,
			                  max(tabs1[T_MANUF].len,tabs2[T_MANUF].len) );
			break;
		case 3:
			rc = strncasecmp( tabs1[T_PART].ptr, tabs2[T_PART].ptr, max(tabs1[T_PART].len,
			                  tabs2[T_PART].len) );
			break;
		case 2:
			if ( item1->type < item2->type ) {
				rc = -1;
			} else if ( item1->type > item2->type ) {
				rc = 1;
			} else {
				rc = strncasecmp( tabs1[T_PROTO].ptr, tabs2[T_PROTO].ptr,
				                  max(tabs1[T_PROTO].len,tabs2[T_PROTO].len) );
			}
			break;
		}
	}
	return rc;
}


static void CarItemHotbarUpdate(
        paramGroup_p pg,
        int inx,
        void * data )
{
	wIndex_t carItemInx;
	carItem_p item;
	if ( inx == 0 ) {
		carItemInx = (wIndex_t)*(long*)data;
		if ( carItemInx < 0 ) {
			return;
		}
		carItemInx = (wIndex_t)VP2L(wListGetItemContext( (wList_p)
		                            pg->paramPtr[inx].control, carItemInx ));
		CHECK( carItemInx < carItemHotbar_da.cnt );
		item = carItemHotbar(carItemInx);
		if ( item != NULL ) {
			currCarItemPtr = item;
		}
	}
}


static char * FormatCarTitle(
        carItem_p item,
        long mode )
{
	tabString_t tabs[7];
	char * cp;
	TabStringExtract( item->title, 7, tabs );
	cp = message;
	for ( ; mode!=0; mode>>=4 ) {
		switch ( mode&0x000F ) {
		case 1:
			cp = TabStringCpy( cp, &tabs[T_MANUF] );
			break;
		case 2:
			cp = TabStringCpy( cp, &tabs[T_PROTO] );
			break;
		case 3:
			cp = TabStringCpy( cp, &tabs[T_PART] );
			break;
		case 4:
			sprintf( cp, "%ld ", item->index );
			cp += strlen(cp);
			break;
		case 5:
			strcpy( cp, typeListMap[CarProtoFindTypeCode(item->type)].name );
			cp += strlen(cp);
			break;
		}
		*cp++ = '/';
	}
	*--cp = '\0';
	return message;
}


EXPORT char * CarItemDescribe(
        carItem_p item,
        long mode,
        long * index )
{
	tabString_t tabs[7];
	char * cp;
	static char desc[STR_LONG_SIZE];

	TabStringExtract( item->title, 7, tabs );
	cp = desc;
	if ( mode != -1 ) {
		sprintf( cp, "%ld ", item->index );
		cp = desc+strlen(cp);
	}
	if ( (mode&0xF)!=1 && ((mode>>4)&0xF)!=1 && ((mode>>8)&0xF)!=1
	     && ((mode>>12)&0xF)!=1 ) {
		cp = TabStringCpy( cp, &tabs[T_MANUF] );
		*cp++ = ' ';
	}
	if ( (mode&0xF)!=3 && ((mode>>4)&0xF)!=3 && ((mode>>8)&0xF)!=3
	     && ((mode>>12)&0xF)!=3 ) {
		cp = TabStringCpy( cp, &tabs[T_PART] );
		*cp++ = ' ';
	}
	if ( (mode&0xF)!=2 && ((mode>>4)&0xF)!=2 && ((mode>>8)&0xF)!=2
	     && ((mode>>12)&0xF)!=2 ) {
		cp = TabStringCpy( cp, &tabs[T_PROTO] );
		*cp++ = ' ';
	}
	if ( tabs[T_DESC].len > 0 ) {
		cp = TabStringCpy( cp, &tabs[T_DESC] );
		*cp++ = ' ';
	}
	if ( mode != -1 ) {
		if ( tabs[T_REPMARK].len > 0 ) {
			cp = TabStringCpy( cp, &tabs[T_REPMARK] );
			*cp++ = ' ';
		} else if ( tabs[T_ROADNAME].len > 0 ) {
			cp = TabStringCpy( cp, &tabs[T_ROADNAME] );
			*cp++ = ' ';
		}
		if ( tabs[T_NUMBER].len > 0 ) {
			cp = TabStringCpy( cp, &tabs[T_NUMBER] );
			*cp++ = ' ';
		}
	}
	*--cp = '\0';
	if ( index != NULL ) {
		*index = item->index;
	}
	return desc;
}


EXPORT void CarItemLoadList( void * unused )
{
	wIndex_t inx;
	carItem_p item;
	char * cp;
	wWinPix_t w, h;

	DYNARR_SET( carItem_t*, carItemHotbar_da, carItemInfo_da.cnt );
	memcpy( &carItemHotbar(0), &carItemInfo(0),
	        carItemInfo_da.cnt * sizeof carItemHotbar(0) );
	wListClear( (wList_p)newCarPLs[0].control );
	for ( inx=0; inx<carItemHotbar_da.cnt; inx++ ) {
		item = carItemHotbar(inx);
		if ( item->car && !IsTrackDeleted(item->car) ) {
			continue;
		}
		cp = CarItemDescribe( item, 0, NULL );
		wListAddValue( (wList_p)newCarPLs[0].control, cp, NULL, I2VP(inx) );
	}
	/*wListSetValue( (wList_p)newCarPLs[0].control, "Select a car" );*/
	wListSetIndex( (wList_p)newCarPLs[0].control, 0 );
	strcpy( newCarLabel1, _("Select") );
	ParamLoadControl( &newCarPG, 0 );
	InfoSubstituteControls( newCarControls, newCarLabels );
	wWinGetSize( mainW, &w, &h );
	w -= wControlGetPosX( newCarControls[0] ) + 4;
	if ( w > 20 ) {
		wListSetSize( (wList_p)newCarControls[0], w,
		              wControlGetHeight( newCarControls[0] ) );
	}
}


static char * CarItemHotbarProc(
        hotBarProc_e op,
        void * data,
        drawCmd_p d,
        coOrd * origP )
{
	wIndex_t carItemInx = (wIndex_t)VP2L(data);
	carItem_p item;
	wIndex_t inx;
	long mode;
	char * cp;
	wWinPix_t w, h;

	CHECK( carItemInx < carItemHotbar_da.cnt );
	item = carItemHotbar(carItemInx);
	if ( item == NULL ) {
		return NULL;
	}
	switch ( op ) {
	case HB_SELECT:
		currCarItemPtr = item;
		mode = carHotbarModes[carHotbarModeInx];
		if ( (mode&0xF000) == 0 ) {
			wListClear( (wList_p)newCarPLs[0].control );
			for ( inx=carItemInx;
			      inx<carItemHotbar_da.cnt && ( inx==carItemInx
			                                    || Cmp_carHotbar(&carItemHotbar(carItemInx),&carItemHotbar(inx))==0 );
			      inx++ ) {
				item = carItemHotbar(inx);
				if ( item->car && !IsTrackDeleted(item->car) ) {
					continue;
				}
				cp = CarItemDescribe( item, mode, NULL );
				wListAddValue( (wList_p)newCarPLs[0].control, cp, NULL, I2VP(inx) );
			}
			/*wListSetValue( (wList_p)newCarPLs[0].control, "Select a car" );*/
			wListSetIndex( (wList_p)newCarPLs[0].control, 0 );
			cp = CarItemHotbarProc( HB_BARTITLE, I2VP(carItemInx), NULL, NULL );
			strncpy( newCarLabel1, cp, sizeof(newCarLabel1) -1);
			newCarLabel1[sizeof(newCarLabel1) -1] = 0;
			ParamLoadControls( &newCarPG );
			ParamGroupRecord( &newCarPG );

			InfoSubstituteControls( newCarControls, newCarLabels );
			wWinGetSize( mainW, &w, &h );
			w -= wControlGetPosX( newCarControls[0] ) + 4;
			if ( w > 20 ) {
				wListSetSize( (wList_p)newCarControls[0], w,
				              wControlGetHeight( newCarControls[0] ) );
			}
		} else {
			InfoSubstituteControls( NULL, NULL );
			cp = CarItemDescribe( item, 0, NULL );
			InfoMessage( cp );
		}
		break;
	case HB_LISTTITLE:
	case HB_BARTITLE:
		return FormatCarTitle( item, carHotbarModes[carHotbarModeInx] );
	case HB_FULLTITLE:
		return item->title;
	case HB_DRAW:
		if ( item->segCnt == 0 ) {
			CarItemGetSegs( item );
		}
		DrawSegs( d, *origP, 0.0, item->segPtr, item->segCnt, trackGauge,
		          wDrawColorBlack );
		return NULL;
	}
	return NULL;
}


EXPORT int CarAvailableCount( void )
{
	wIndex_t inx;
	int cnt = 0;
	carItem_t * item;
	for ( inx=0; inx < carItemHotbar_da.cnt; inx ++ ) {
		item = carItemHotbar(inx);
		if (FIT_NONE == CompatibleScale( FIT_CAR, item->scaleInx,
		                                 GetLayoutCurScale())) {
			continue;
		}
		cnt++;
	}
	return cnt;
}


EXPORT void AddHotBarCarDesc( void )
{
	wIndex_t inx;
	carItem_t * item0, * item1;
	coOrd orig;
	coOrd size;

	LOG( log_carDlgDims, 1, ( "AddHotBarCarDesc/load carItemHB: carItemHB.cnt:%d\n",
	                          carItemInfo_da.cnt ) );
	DYNARR_SET( carItem_t*, carItemHotbar_da, carItemInfo_da.cnt );
	memcpy( &carItemHotbar(0), &carItemInfo(0),
	        carItemInfo_da.cnt * sizeof carItemHotbar(0) );
	qsort( &carItemHotbar(0), carItemHotbar_da.cnt, sizeof carItemHotbar(0),
	       Cmp_carHotbar );
	for ( inx=0,item0=NULL; inx < carItemHotbar_da.cnt; inx ++ ) {
		item1 = carItemHotbar(inx);
		if ( item1->car && !IsTrackDeleted(item1->car) ) {
			continue;
		}
		if ( FIT_NONE == CompatibleScale(FIT_CAR,item1->scaleInx,GetLayoutCurScale())) {
			continue;
		}
		if ( (carHotbarModes[carHotbarModeInx]&0xF000)!=0 || ( item0 == NULL
		                || Cmp_carHotbar( &item0, &item1 ) != 0 ) ) {
#ifdef DESCFIX
			orig.x = - item->orig.x;
			orig.y = - item->orig.y;
#endif
			orig = zero;
			size.x = item1->dim.carLength;
			size.y = item1->dim.carWidth;
			LOG( log_carDlgDims, 1, ( "AddHotBarElement( %d: %s\n", inx, item1->title ) );
			AddHotBarElement( FormatCarTitle( item1, carHotbarContents[carHotbarModeInx] ),
			                  size, orig, FALSE, FALSE, (60.0*12.0/curScaleRatio), I2VP(inx),
			                  CarItemHotbarProc );
		}
		item0 = item1;
	}
}


EXPORT void CarItemFindCouplerMountPoint(
        carItem_p item,
        traverseTrack_t trvTrk0,
        coOrd pos[2] )
{
	// We assume the coupler pivot is 'couplerLength' before the end of the car
	DIST_T couplerLength = (item->dim.coupledLength - item->dim.carLength) / 2.0;
	if ( IsClose(item->dim.truckCenter) ) {
		// Single truck/bogie
		DIST_T d = item->dim.carLength/2.0 - couplerLength;
		Translate( &pos[0], trvTrk0.pos, trvTrk0.angle,
		           d + item->dim.truckCenterOffset );
		FlipTraverseTrack( &trvTrk0 );
		Translate( &pos[1], trvTrk0.pos, trvTrk0.angle,
		           d - item->dim.truckCenterOffset );
		return;
	}
	// Find the pos of the 2 trucks
	// Note this is a slight simplification, we should use the car center, not the on-track position
	traverseTrack_t trvTrk1 = trvTrk0;
	TraverseTrack2( &trvTrk0,
	                item->dim.truckCenter/2.0 + item->dim.truckCenterOffset );
	FlipTraverseTrack( & trvTrk1 );
	TraverseTrack2( &trvTrk1,
	                item->dim.truckCenter/2.0 - item->dim.truckCenterOffset );

	// Get the angle to translate from the truck
	ANGLE_T angle[2];
	if ( trvTrk0.trk == NULL || (item->options&CAR_DESC_COUPLER_MODE_BODY)!=0 ) {
		// Body mount couplers
		// Angle is same as the car
		angle[0] = FindAngle( trvTrk1.pos, trvTrk0.pos );
		angle[1] = NormalizeAngle( angle[0]+180.0 );
	} else {
		// Truck mounted couplers
		// Angle is same as the trucks
		angle[0] = trvTrk0.angle;
		angle[1] = trvTrk1.angle;
	}

	// Get the distance to translate
	DIST_T d[2];
	d[0] = item->dim.carLength/2.0 - couplerLength - ( item->dim.truckCenter/2.0 +
	                item->dim.truckCenterOffset );
	d[1] = item->dim.carLength/2.0 - couplerLength - ( item->dim.truckCenter/2.0 -
	                item->dim.truckCenterOffset );

	// And translate
	Translate( &pos[0], trvTrk0.pos, angle[0], d[0] );
	Translate( &pos[1], trvTrk1.pos, angle[1], d[1] );

#ifdef LATER
	if ( trvTrk.trk == NULL || (item->options&CAR_DESC_COUPLER_MODE_BODY)!=0 ) {
		couplerOffset = item->dim.coupledLength/2.0;
		Translate( &pos, trvTrk.pos, trvTrk.angle, couplerOffset );
	} else {
		if (dir) {
			TraverseTrack2( &trvTrk,
			                item->dim.truckCenter/2.0-item->dim.truckCenterOffset );
		} else {
			TraverseTrack2( &trvTrk,
			                item->dim.truckCenter/2.0+item->dim.truckCenterOffset );
		}
		/*Translate( &pos1, trvTrk.pos, trvTrk.angle, item->dim.truckCenter/2.0 );*/
		couplerOffset = (item->dim.coupledLength-item->dim.truckCenter)/2.0;
		if (dir) {
			couplerOffset = couplerOffset + item->dim.truckCenterOffset;
		} else {
			couplerOffset = couplerOffset - item->dim.truckCenterOffset;
		}
		Translate( &pos, trvTrk.pos, trvTrk.angle, couplerOffset );
	}
#endif
}

EXPORT void CarItemPos(
        carItem_p item,
        coOrd * pos	)
{
	pos->x = item->pos.x;
	pos->y = item->pos.y;
}



EXPORT void CarItemSize(
        carItem_p item,
        coOrd * size )
{
	size->x = item->dim.carLength;
	size->y = item->dim.carWidth;
}

EXPORT void CarItemSetNumber(carItem_p item, char * number)
{
	if (item->data.number && number[0]) {
		MyFree(item->data.number);
	}
	if (number[0]) {
		item->data.number = MyStrdup(number);
	}
}


EXPORT char * CarItemNumber(
        carItem_p item )
{
	return item->data.number;
}




static DIST_T CarItemTruckCenter(
        carItem_p item )
{
	return item->dim.truckCenter;
}

static DIST_T CarItemTruckOffset(
        carItem_p item )
{
	return item->dim.truckCenterOffset;
}


EXPORT DIST_T CarItemCoupledLength(
        carItem_p item )
{
	return item->dim.coupledLength;
}


EXPORT BOOL_T CarItemIsLoco(
        carItem_p item )
{
	return (item->options&CAR_DESC_IS_LOCO) == (CAR_DESC_IS_LOCO);
}


EXPORT BOOL_T CarItemIsLocoMaster(
        carItem_p item )
{
	return (item->options&(CAR_DESC_IS_LOCO|CAR_DESC_IS_LOCO_MASTER)) ==
	       (CAR_DESC_IS_LOCO|CAR_DESC_IS_LOCO_MASTER);
}


EXPORT void CarItemSetLocoMaster(
        carItem_p item,
        BOOL_T locoIsMaster )
{
	if ( locoIsMaster ) {
		item->options |= CAR_DESC_IS_LOCO_MASTER;
	} else {
		item->options &= ~CAR_DESC_IS_LOCO_MASTER;
	}
}


EXPORT void CarItemSetTrack(
        carItem_p item,
        track_p trk )
{
	item->car = trk;
	if ( trk != NULL ) {
		SetTrkScale( trk, item->scaleInx );
	}
}

static DIST_T CarItemCouplerLength(
        carItem_p item,
        int dir )
{
	return item->dim.coupledLength-item->dim.carLength;
}

EXPORT BOOL_T StoreCarItem (carItem_p item, void **data,long *len)
{

	*data = item;
	*len = sizeof (carItem_t);
	return TRUE;

}

EXPORT BOOL_T ReplayCarItem(carItem_p item, void *data,long len)
{


	item->pos = ((carItem_t *)data)->pos;
	item->angle = ((carItem_t *)data)->angle;
	return TRUE;

}

EXPORT void CarItemPlace(
        carItem_p item,
        traverseTrack_p trvTrk,
        DIST_T * dists )
{
	DIST_T dist;
	DIST_T offset;
	traverseTrack_t trks[2];

	dist = CarItemTruckCenter(item)/2.0;
	offset = CarItemTruckOffset(
	                 item);				//Offset is the amount the truck centers are displaced
	trks[0] = trks[1] = *trvTrk;
	TraverseTrack2( &trks[0], dist+offset );
	TraverseTrack2( &trks[1], -dist+offset );
	item->angle = FindAngle( trks[1].pos, trks[0].pos );
	item->pos.x = (trks[0].pos.x+trks[1].pos.x)/2.0;
	item->pos.y = (trks[0].pos.y+trks[1].pos.y)/2.0;
	Translate(&item->pos,item->pos,item->angle,
	          -offset);  // Put truck center back along line by offset
	dists[0] = dists[1] = CarItemCoupledLength(item)/2.0;
}


static int drawCarTrucks = 0;
EXPORT void CarItemDraw(
        drawCmd_p d,
        carItem_p item,
        wDrawColor color,
        int direction,
        BOOL_T locoIsMaster,
        vector_t *coupler,
#ifdef CAR_CLEARANCE
        BOOL_T pencils,
#endif
        track_p traverse)
{
	coOrd size, pos, pos2;
	DIST_T length;
	wFont_p fp;
	wDrawWidth width;
	trkSeg_t simpleSegs[1];
	pts_t simplePts[4];
	int dir;
//	DIST_T rad;
	static int couplerLineWidth = 3;

	CarItemSize( item, &size );
	if ( !DrawTwoRails( d, 1 ) ) {
		simplePts[0].pt.x = simplePts[3].pt.x = -size.x/2.0;
		simplePts[1].pt.x = simplePts[2].pt.x = size.x/2.0;
		simplePts[0].pt.y = simplePts[1].pt.y = -size.y/2.0;
		simplePts[2].pt.y = simplePts[3].pt.y = size.y/2.0;
		simplePts[0].pt_type = 0;
		simplePts[1].pt_type = 0;
		simplePts[2].pt_type = 0;
		simplePts[3].pt_type = 0;
		simpleSegs[0].type = SEG_FILPOLY;
		simpleSegs[0].color = item->color;
		simpleSegs[0].lineWidth = 0;
		simpleSegs[0].u.p.cnt = 4;
		simpleSegs[0].u.p.pts = simplePts;
		simpleSegs[0].u.p.orig = zero;
		simpleSegs[0].u.p.angle = 0.0;
		DrawSegs( d, item->pos, item->angle-90.0, simpleSegs, 1, 0.0, color );
	} else {
		if ( item->segCnt == 0 ) {
			CarItemGetSegs( item );
		}
		Translate( &pos, item->pos, item->angle, -size.x/2.0 );
		Translate( &pos, pos, item->angle-90, -size.y/2.0 );
		DrawSegs( d, pos, item->angle-90.0, item->segPtr, item->segCnt, 0.0, color );
	}

#ifdef CAR_CLEARANCE
	if (pencils) {
// No code for pencils
	}
#endif

	if ( drawCarTrucks ) {

		length = item->dim.truckCenter/2.0;
		double offset = CarItemTruckOffset(item);
		Translate( &pos, item->pos, item->angle, length+(direction?offset:-offset) );
		DrawArc( d, pos, trackGauge/2.0, 0.0, 360.0, FALSE, 0, color );
		Translate( &pos, item->pos, item->angle+180,
		           length+(direction?-offset:offset) );
		DrawArc( d, pos, trackGauge/2.0, 0.0, 360.0, FALSE, 0, color );
	}

	if ( (labelEnable&LABELENABLE_CARS) ) {
		fp = wStandardFont( F_HELV, FALSE, FALSE );
		DrawBoxedString( BOX_BACKGROUND, d, item->pos, item->data.number, fp,
		                 (wFontSize_t)descriptionFontSize, color, 0.0 );
	}

	/* draw loco head light */
	if ( (item->options&CAR_DESC_IS_LOCO)!=0 ) {
		Translate( &pos, item->pos, item->angle+(direction?180.0:0.0),
		           size.x/2.0-trackGauge/2.0 );
		if ( locoIsMaster ) {
			DrawFillCircle( d, pos, trackGauge/2.0,
			                (color==wDrawColorBlack?drawColorGold:color) );
		} else {
			width = (wDrawWidth)floor( trackGauge/8.0 * d->dpi / d->scale );
			DrawArc( d, pos, trackGauge/2.0, 0.0, 360.0, FALSE, width,
			         (color==wDrawColorBlack?drawColorGold:color) );
		}
	}

	/* draw coupler */
	if ( ! DrawTwoRails(d,0.5) ) {
		return;
	}
//	rad = trackGauge/8.0;
	for ( dir=0; dir<2; dir++ ) {
		Translate( &pos, coupler[dir].pos, coupler[dir].angle,
		           CarItemCouplerLength(item,dir) );
		DrawLine( d, coupler[dir].pos, pos, couplerLineWidth, color );
		if ( DrawTwoRails(d,1) ) {
			/*DrawFillCircle( d, p0, rad, dir==0?color:selectedColor );*/
			Translate( &pos2, pos, coupler[dir].angle+90.0, trackGauge/3 );
			DrawLine( d, pos2, pos, couplerLineWidth, color );
		}
	}
}


EXPORT void CarItemUpdate(
        carItem_p item )
{
	DoChangeNotification( CHANGE_SCALE );
}

/*
 * Car Item/Part Dlg
 */

static int carDlgChanged;

static SCALEINX_T carDlgScaleInx;
static carItem_p carDlgUpdateItemPtr;
static carPart_p carDlgUpdatePartPtr;
static carProto_p carDlgUpdateProtoPtr;
static carPart_p carDlgNewPartPtr;
static carProto_p carDlgNewProtoPtr;

static BOOL_T carDlgFlipToggle;

static wIndex_t carDlgManufInx;
static char carDlgManufStr[STR_SIZE];
static wIndex_t carDlgKindInx;
static wIndex_t carDlgProtoInx;
static char carDlgProtoStr[STR_SIZE] =
        "Prototype";	// Make sure we have something in ProtoStr
static wIndex_t carDlgPartnoInx;
static char carDlgPartnoStr[STR_SIZE] = "0";		// and in PartnoStr
static char carDlgDescStr[STR_SIZE];

static long carDlgDispMode;
static wIndex_t carDlgRoadnameInx;
static char carDlgRoadnameStr[STR_SIZE];
static char carDlgRepmarkStr[STR_SIZE];
static char carDlgNumberStr[STR_SIZE];
static wDrawColor carDlgBodyColor;
static long carDlgIsLoco;
static wIndex_t carDlgTypeInx;

static carDim_t carDlgDim;
static DIST_T carDlgCouplerLength;
static long carDlgCouplerMount;

static long carDlgItemIndex = 1;
static FLOAT_T carDlgPurchPrice;
static char carDlgPurchPriceStr[STR_SIZE];
static FLOAT_T carDlgCurrPrice;
static char carDlgCurrPriceStr[STR_SIZE];
static wIndex_t carDlgConditionInx;
static long carDlgCondition;
static long carDlgPurchDate;
static char carDlgPurchDateStr[STR_SIZE];
static long carDlgServiceDate;
static char carDlgServiceDateStr[STR_SIZE];
static long carDlgQuantity = 1;
static long carDlgMultiNum;

static char *dispmodeLabels[] = { N_("Information"), N_("Customize"), NULL };
static drawCmd_t carDlgD = {
	NULL,
	&screenDrawFuncs,
	0,
	1.0,
	0.0,
	{ 0, 0 }, { 0, 0 },
	Pix2CoOrd, CoOrd2Pix
};
static void CarDlgRedraw( wDraw_p d, void * context, wWinPix_t x, wWinPix_t y );

static paramDrawData_t carDlgDrawData = { 500, 120, CarDlgRedraw, NULL, &carDlgD };
static paramTextData_t notesData = { 500, 156 };
static char *multinumLabels[] = { N_("Sequential"), N_("Repeated"), NULL };
static void CarDlgNewProto( void );
static void CarDlgUpdate( paramGroup_p, int, void * );
static void CarDlgNewDesc( void );
static void CarDlgNewProto( void );

static paramData_t carDlgPLs[] = {
#define A                       (0)
#define I_CD_MANUF_LIST         (A+0)
	{ PD_DROPLIST, &carDlgManufInx, "manuf", PDO_NOPREF, I2VP(350), N_("Manufacturer"), BL_EDITABLE },
#define I_CD_PROTOTYPE_STR      (A+1)
	{ PD_STRING, &carDlgProtoStr, "prototype", PDO_NOPREF|PDO_NOTBLANK, I2VP(350), N_("Prototype"), 0, 0, sizeof(carDlgProtoStr)},
#define I_CD_PROTOKIND_LIST     (A+2)
	{ PD_DROPLIST, &carDlgKindInx, "protokind-list", PDO_NOPREF, I2VP(125), N_("Prototype"), 0 },
#define I_CD_PROTOTYPE_LIST     (A+3)
	{ PD_DROPLIST, &carDlgProtoInx, "prototype-list", PDO_NOPREF|PDO_DLGHORZ, I2VP(225-3), NULL, 0 },
#define I_CD_TYPE_LIST          (A+4)
	{ PD_DROPLIST, &carDlgTypeInx, "type", PDO_NOPREF, I2VP(350), N_("Type"), 0 },
#define I_CD_PARTNO_LIST        (A+5)
	{ PD_DROPLIST, &carDlgPartnoInx, "partno-list", PDO_NOPREF, I2VP(350), N_("Part"), BL_EDITABLE },
#define I_CD_PARTNO_STR         (A+6)
	{ PD_STRING, &carDlgPartnoStr, "partno", PDO_NOPREF|PDO_NOTBLANK, I2VP(350), N_("Part Number"), 0, 0, sizeof(carDlgPartnoStr)},
#define I_CD_ISLOCO             (A+7)
	{ PD_TOGGLE, &carDlgIsLoco, "isLoco", PDO_NOPREF|PDO_DLGWIDE, isLocoLabels, N_("Loco?"), BC_HORZ|BC_NOBORDER },
#define I_CD_DESC_STR           (A+8)
	{ PD_STRING, &carDlgDescStr, "desc", PDO_NOPREF|PDO_STRINGLIMITLENGTH, I2VP(350), N_("Description"), 0, 0, sizeof(carDlgDescStr)},
#define I_CD_IMPORT             (A+9)
	{ PD_BUTTON, NULL, "import", 0, 0, N_("Import") },
#define I_CD_RESET              (A+10)
	{ PD_BUTTON, NULL, "reset", PDO_DLGHORZ, 0, N_("Reset") },
#define I_CD_FLIP               (A+11)
	{ PD_BUTTON, NULL, "flip", PDO_DLGHORZ|PDO_DLGWIDE|PDO_DLGBOXEND, 0, N_("Flip") },

#define I_CD_DISPMODE           (A+12)
	{ PD_RADIO, &carDlgDispMode, "dispmode", PDO_NOPREF|PDO_DLGWIDE, dispmodeLabels, N_("Mode"), BC_HORZ|BC_NOBORDER },

#define B                       (A+13)
#define I_CD_ROADNAME_LIST      (B+0)
	{ PD_DROPLIST, &carDlgRoadnameInx, "road", PDO_NOPREF|PDO_DLGWIDE, I2VP(350), N_("Road"), BL_EDITABLE },
#define I_CD_REPMARK            (B+1)
	{ PD_STRING, carDlgRepmarkStr, "repmark", PDO_NOPREF|PDO_STRINGLIMITLENGTH, I2VP(60), N_("Reporting Mark"), 0, 0, sizeof(carDlgRepmarkStr)},
#define I_CD_NUMBER             (B+2)
	{ PD_STRING, carDlgNumberStr, "number", PDO_NOPREF|PDO_DLGWIDE|PDO_DLGHORZ|PDO_STRINGLIMITLENGTH, I2VP(80), N_("Number"), 0, 0, sizeof(carDlgNumberStr)},
#define I_CD_BODYCOLOR          (B+3)
	{ PD_COLORLIST, &carDlgBodyColor, "bodyColor", PDO_DLGWIDE|PDO_DLGHORZ, NULL, N_("Color") },
#define I_CD_CARLENGTH          (B+4)
	{ PD_FLOAT, &carDlgDim.carLength, "carLength", PDO_DIM|PDO_NOPREF|PDO_DLGWIDE, &r0d001_99999, N_("Car Length") },
#define I_CD_CARWIDTH           (B+5)
	{ PD_FLOAT, &carDlgDim.carWidth, "carWidth", PDO_DIM|PDO_NOPREF|PDO_DLGWIDE|PDO_DLGHORZ, &r0d001_99999, N_("Width") },
#define I_CD_TRKCENTER          (B+6)
	{ PD_FLOAT, &carDlgDim.truckCenter, "trkCenter", PDO_DIM|PDO_NOPREF, &r0d001_99999, N_("Truck Centers") },
#define I_CD_TRKOFFSET			(B+7)
	{ PD_FLOAT, &carDlgDim.truckCenterOffset, "trkCenterOffset", PDO_DIM|PDO_NOPREF|PDO_DLGHORZ|PDO_DLGWIDE, &r9999_9999, N_("Center Offset") },
#define I_CD_CPLRMNT            (B+8)
	{ PD_RADIO, &carDlgCouplerMount, "cplrMount", PDO_NOPREF, cplrModeLabels, N_("Coupler Mount"), BC_HORZ|BC_NOBORDER },
#define I_CD_CPLDLEN            (B+9)
	{ PD_FLOAT, &carDlgDim.coupledLength, "cpldLen", PDO_DIM|PDO_NOPREF, &r0d001_99999, N_("Coupled Length") },
#define I_CD_CPLRLEN            (B+10)
	{ PD_FLOAT, &carDlgCouplerLength, "cplrLen", PDO_DIM|PDO_NOPREF|PDO_DLGHORZ, &r0d001_99999, N_("Coupler Length") },
#define I_CD_CANVAS             (B+11)
	{ PD_DRAW, NULL, "canvas", PDO_NOPSHUPD|PDO_DLGWIDE|PDO_DLGNOLABELALIGN|PDO_DLGRESETMARGIN|PDO_DLGBOXEND|PDO_DLGRESIZE, &carDlgDrawData, NULL, 0 },

#define C                       (B+12)
#define I_CD_ITEMINDEX          (C+0)
	{ PD_LONG, &carDlgItemIndex, "index", PDO_NOPREF|PDO_DLGWIDE, &i1_999999999, N_("Index"), 0 },
#define I_CD_PURPRC             (C+1)
	{ PD_STRING, &carDlgPurchPriceStr, "purchPrice", PDO_NOPREF|PDO_DLGWIDE|PDO_STRINGLIMITLENGTH, I2VP(50), N_("Purchase Price"), 0, &carDlgPurchPrice, sizeof(carDlgPurchPriceStr) },
#define I_CD_CURPRC             (C+2)
	{ PD_STRING, &carDlgCurrPriceStr, "currPrice", PDO_NOPREF|PDO_DLGWIDE|PDO_DLGHORZ|PDO_STRINGLIMITLENGTH, I2VP(50), N_("Current Price"), 0, &carDlgCurrPrice, sizeof(carDlgCurrPriceStr) },
#define I_CD_COND               (C+3)
	{ PD_DROPLIST, &carDlgConditionInx, "condition", PDO_NOPREF|PDO_DLGWIDE|PDO_DLGHORZ, I2VP(90), N_("Condition") },
#define I_CD_PURDAT             (C+4)
	{ PD_STRING, &carDlgPurchDateStr, "purchDate",  PDO_NOPREF|PDO_DLGWIDE|PDO_STRINGLIMITLENGTH, I2VP(80), N_("Purchase Date"), 0, &carDlgPurchDate, sizeof(carDlgPurchDateStr) },
#define I_CD_SRVDAT             (C+5)
	{ PD_STRING, &carDlgServiceDateStr, "serviceDate",  PDO_NOPREF|PDO_DLGWIDE|PDO_DLGHORZ|PDO_STRINGLIMITLENGTH, I2VP(80), N_("Service Date"), 0, &carDlgServiceDate, sizeof(carDlgServiceDateStr) },
#define I_CD_QTY                (C+6)
	{ PD_LONG, &carDlgQuantity, "quantity", PDO_NOPREF|PDO_DLGWIDE, &i1_9999, N_("Quantity") },
#define I_CD_MLTNUM             (C+7)
	{ PD_RADIO, &carDlgMultiNum, "multinum", PDO_NOPREF|PDO_DLGWIDE|PDO_DLGHORZ, multinumLabels, N_("Numbers"), BC_HORZ|BC_NOBORDER },
#define I_CD_NOTES              (C+8)
	{ PD_TEXT, NULL, "notes", PDO_NOPREF|PDO_DLGWIDE|PDO_DLGNOLABELALIGN|PDO_DLGRESETMARGIN|PDO_DLGRESIZE, &notesData, N_("Notes") },

#define D                       (C+9)
#define I_CD_MSG                (D+0)
	{ PD_MESSAGE, NULL, NULL, PDO_DLGNOLABELALIGN|PDO_DLGRESETMARGIN|PDO_DLGBOXEND, I2VP(450) },
#define I_CD_NEW                (D+1)
	{ PD_MENU, NULL, "new-menu", PDO_DLGCMDBUTTON, NULL, N_("New"), 0, I2VP(0) },
	{ PD_MENUITEM, CarDlgNewDesc, "new-part-mi", 0, NULL, N_("Car Part"), 0, I2VP(0) },
	{ PD_MENUITEM, CarDlgNewProto, "new-proto-mi", 0, NULL, N_("Car Prototype"), 0, I2VP(0) },
#define I_CD_NEWPROTO           (D+4)
	{ PD_BUTTON, CarDlgNewProto, "new", PDO_DLGCMDBUTTON, NULL, N_("New"), 0, I2VP(0) }
};

static paramGroup_t carDlgPG = { "carpart", 0, carDlgPLs, COUNT( carDlgPLs ) };


static dynArr_t carDlgSegs_da;
#define carDlgSegs(N) DYNARR_N( trkSeg_t, carDlgSegs_da, N )


typedef enum {
	T_ItemSel, T_ItemEnter, T_ProtoSel, T_ProtoEnter, T_PartnoSel, T_PartnoEnter
} carDlgTransistion_e;
static char *carDlgTransistion_s[] = {
	"ItemSel", "ItemEnter", "ProtoSel", "ProtoEnter", "PartnoSel", "PartnoEnter"
};
typedef enum {
	S_Error,
	S_ItemSel, S_ItemEnter, S_PartnoSel, S_PartnoEnter, S_ProtoSel
} carDlgState_e;
static char *carDlgState_s[] = {
	"Error",
	"ItemSel", "ItemEnter", "PartnoSel", "PartnoEnter", "ProtoSel"
};
typedef enum {
	A_Return,
	A_SError,
	A_Else,
	A_SItemSel,
	A_SItemEnter,
	A_SPartnoSel,
	A_SPartnoEnter,
	A_SProtoSel,
	A_IsCustom,
	A_IsNewPart,
	A_IsNewProto,
	A_LoadDataFromPartList,
	A_LoadDimsFromStack,
	A_LoadManufListForScale,
	A_LoadManufListAll,
	A_LoadProtoListForManuf,
	A_LoadProtoListAll,
	A_LoadPartnoList,
	A_LoadLists,
	A_LoadDimsFromProtoList,
	A_ConvertDimsToProto,
	A_Redraw,
	A_ClrManuf,
	A_ClrPartnoStr,
	A_ClrNumberStr,
	A_LoadProtoStrFromList,
	A_ShowPartnoList,
	A_HidePartnoList,
	A_PushDims,
	A_PopDims,
	A_PopTitleAndTypeinx,
	A_PopCouplerLength,
	A_ShowControls,
	A_LoadInfoFromUpdateItem,
	A_LoadDataFromUpdatePart,
	A_InitProto,
	A_RecallCouplerLength,
	A_Last
} carDlgAction_e;
static char *carDlgAction_s[] = {
	"Return",
	"SError",
	"Else",
	"SItemSel",
	"SItemEnter",
	"SPartnoSel",
	"SPartnoEnter",
	"SProtoSel",
	"IsCustom",
	"IsNewPart",
	"IsNewProto",
	"LoadDataFromPartList",
	"LoadDimsFromStack",
	"LoadManufListForScale",
	"LoadManufListAll",
	"LoadProtoListForManuf",
	"LoadProtoListAll",
	"LoadPartnoList",
	"LoadLists",
	"LoadDimsFromProtoList",
	"ConvertDimsToProto",
	"Redraw",
	"ClrManuf",
	"ClrPartnoStr",
	"ClrNumberStr",
	"LoadProtoStrFromList",
	"ShowPartnoList",
	"HidePartnoList",
	"PushDims",
	"PopDims",
	"PopTitleAndTypeinx",
	"PopCouplerLength",
	"ShowControls",
	"LoadInfoFromUpdateItem",
	"LoadDataFromUpdatePart",
	"InitProto",
	"RecallCouplerLength",
	"Last"
};
static carDlgAction_e stateMachine[7][7][10] = {
	/* A_SError */{   {A_SError}, {A_SError}, {A_SError}, {A_SError}, {A_SError}, {A_SError}, {A_SError} },

	/*A_SItemSel*/{
		/*T_ItemSel*/    { A_LoadProtoListForManuf, A_LoadPartnoList, A_LoadDataFromPartList, A_Redraw },
		/*T_ItemEnter*/  { A_SItemEnter, A_LoadProtoListAll, A_ClrPartnoStr, A_ClrNumberStr, A_LoadDimsFromProtoList, A_Redraw, A_HidePartnoList },
		/*T_ProtoSel*/   { A_LoadPartnoList, A_LoadDataFromPartList, A_Redraw },
		/*T_ProtoEnter*/ { A_SError },
		/*T_PartnoSel*/  { A_LoadDataFromPartList, A_Redraw },
		/*T_PartnoEnter*/{ A_SItemEnter, A_LoadProtoListAll, A_HidePartnoList }
	},

	/*A_SItemEnter*/{
		/*T_ItemSel*/    { A_SItemSel, A_LoadProtoListForManuf, A_LoadPartnoList, A_LoadDataFromPartList, A_Redraw, A_ShowPartnoList },
		/*T_ItemEnter*/  { A_Return },
		/*T_ProtoSel*/   { A_LoadDimsFromProtoList, A_Redraw },
		/*T_ProtoEnter*/ { A_SError },
		/*T_PartnoSel*/  { A_SError },
		/*T_PartnoEnter*/{ A_Return }
	},

	/*A_SPartnoSel*/{
		/*T_ItemSel*/   { A_SPartnoSel },
		/*T_ItemEnter*/ { A_SPartnoSel },
		/*T_ProtoSel*/   { A_SPartnoSel, A_LoadDimsFromProtoList, A_Redraw },
		/*T_ProtoEnter*/ { A_SError },
		/*T_PartnoSel*/  { A_SError }
	},

	/*A_SPartnoEnter*/{
		/*T_ItemSel*/   { A_SPartnoSel },
		/*T_ItemEnter*/ { A_SPartnoEnter },
		/*T_ProtoSel*/   { A_SPartnoEnter, A_LoadDimsFromProtoList, A_Redraw },
		/*T_ProtoEnter*/ { A_SError },
		/*T_PartnoSel*/  { A_SError },
		/*T_PartnoEnter*/{ A_SPartnoEnter }
	},

	/*A_SProtoSel*/{
		/*T_ItemSel*/   { A_SError },
		/*T_ItemEnter*/ { A_SError },
		/*T_ProtoSel*/   { A_SError },
		/*T_ProtoEnter*/ { A_SProtoSel },
		/*T_PartnoSel*/  { A_SError },
		/*T_PartnoEnter*/{ A_SError }
	}
};

static carDlgAction_e itemNewActions[] = {
	A_RecallCouplerLength,
	A_LoadLists,
	A_IsCustom, 2+3,
	A_LoadDimsFromProtoList, A_ClrPartnoStr, A_ClrNumberStr,
	A_Else, 1,
	A_LoadDataFromPartList,
	A_ShowControls, A_Return
};
static carDlgAction_e itemUpdActions[] = { A_LoadInfoFromUpdateItem, /*A_LoadManufListForScale,
				A_IsCustom, 5,
						A_LoadProtoListAll, A_HidePartnoList, A_SItemEnter,
				A_Else, 5,
						A_LoadProtoListForManuf, A_LoadPartnoList, A_LoadDataFromPartList, A_ShowPartnoList, A_SItemSel,*/
                                           A_ShowControls, A_Return
                                         };

static carDlgAction_e partNewActions[] = { A_RecallCouplerLength, A_LoadManufListAll, A_LoadProtoListAll, A_ClrPartnoStr, A_ClrNumberStr, A_SPartnoSel, A_LoadDimsFromProtoList, A_ShowControls, A_Redraw, A_Return };
static carDlgAction_e partUpdActions[] = { A_LoadDataFromUpdatePart, A_SPartnoSel, A_ShowControls, A_Return };

static carDlgAction_e protoNewActions[] = { A_InitProto, A_SProtoSel, A_ShowControls, A_Return };
static carDlgAction_e protoUpdActions[] = { A_InitProto, A_SProtoSel, A_ShowControls, A_Return };

static carDlgAction_e item2partActions[] = {
	A_PushDims, A_LoadManufListAll, A_LoadProtoListAll,
	A_IsCustom, 0+1,
	A_ClrManuf,
	A_SPartnoSel,
	A_ShowControls, A_Return
};
static carDlgAction_e part2itemActions[] = {
	A_IsNewPart, 2+0,
	A_Else, 1,
	A_PopTitleAndTypeinx,
	A_LoadLists,
	A_IsCustom, 2+1,
	A_LoadDimsFromProtoList,
	A_Else, 1,
	A_LoadDataFromPartList,
#ifdef LATER
	A_IsNewPart, 2+0,
	A_Else, 1,
	A_LoadDimsFromStack,
#endif
	A_ShowControls,
	A_Return
};

static carDlgAction_e item2protoActions[] = { A_PushDims, A_ConvertDimsToProto, A_SProtoSel, A_ShowControls, A_Return };
static carDlgAction_e proto2itemActions[] = {
	A_IsCustom, 2+2+3,
	A_IsNewProto, 2+3,
	A_LoadProtoListAll,
	A_PopCouplerLength,
	A_LoadDimsFromProtoList,
	A_Else, 2,
	A_LoadDimsFromStack,
	A_LoadProtoStrFromList,
	A_ShowControls,
	A_Return
};

static carDlgAction_e part2protoActions[] = { A_PushDims, A_ConvertDimsToProto, A_SProtoSel, A_ShowControls, A_Return };
static carDlgAction_e proto2partActions[] = {
	A_IsNewProto, 2+3,
	A_LoadProtoListAll,
	A_PopCouplerLength,
	A_LoadDimsFromProtoList,
	A_Else, 2,
	A_LoadDimsFromStack,
	A_LoadProtoStrFromList,
	A_ShowControls,
	A_Return
};


#define CARDLG_STK_SIZE (2)
int carDlgStkPtr = 0;
struct {
	carDim_t dim;
	DIST_T couplerLength;
	carDlgState_e state;
	int changed;
	carPart_p partP;
	wIndex_t typeInx;
} carDlgStk[CARDLG_STK_SIZE];

static carDlgState_e currState = S_Error;
#define S_ITEM (currState==S_ItemSel||currState==S_ItemEnter)
#define S_PART (currState==S_PartnoSel)
#define S_PROTO (currState==S_ProtoSel)



static void CarDlgLoadDimsFromPart( carPart_p partP )
{
	tabString_t tabs[7];

	if ( partP == NULL ) { return; }
	carDlgDim = partP->dim;
	carDlgCouplerLength = (carDlgDim.coupledLength-carDlgDim.carLength)/2.0;
	sprintf( message, "%s-%s", carDlgPLs[I_CD_CPLRLEN].nameStr,
	         GetScaleName(carDlgScaleInx) );
	wPrefSetFloat( carDlgPG.nameStr, message, carDlgCouplerLength );
	carDlgIsLoco = (partP->options&CAR_DESC_IS_LOCO)?1:0;
	carDlgBodyColor = partP->color;
	ParamLoadControl( &carDlgPG, I_CD_CARLENGTH );
	ParamLoadControl( &carDlgPG, I_CD_CARWIDTH );
	ParamLoadControl( &carDlgPG, I_CD_TRKCENTER );
	ParamLoadControl( &carDlgPG, I_CD_CPLDLEN );
	wColorSelectButtonSetColor( (wButton_p)carDlgPLs[I_CD_BODYCOLOR].control,
	                            *(wDrawColor*)carDlgPLs[I_CD_BODYCOLOR].valueP );
	TabStringExtract( partP->title, 7, tabs );
}


static void CarDlgLoadDimsFromProto( carProto_p protoP )
{
	DIST_T ratio = GetScaleRatio(carDlgScaleInx);
	carDlgDim.carLength = protoP->dim.carLength/ratio;
	carDlgDim.carWidth = protoP->dim.carWidth/ratio;
	carDlgDim.truckCenter = protoP->dim.truckCenter/ratio;
	carDlgDim.truckCenterOffset = protoP->dim.truckCenterOffset/ratio;
	carDlgDim.coupledLength = carDlgDim.carLength + carDlgCouplerLength*2;
	/*carDlgCouplerLength = (carDlgDim.coupledLength-carDlgDim.carLength)/2.0;*/
	carDlgIsLoco = (protoP->options&CAR_DESC_IS_LOCO)?1:0;
	ParamLoadControl( &carDlgPG, I_CD_CARLENGTH );
	ParamLoadControl( &carDlgPG, I_CD_CARWIDTH );
	ParamLoadControl( &carDlgPG, I_CD_TRKCENTER );
	ParamLoadControl( &carDlgPG, I_CD_CPLDLEN );
}


static void CarDlgRedraw(
        wDraw_p d, void * context, wWinPix_t x, wWinPix_t y )
{
	wWinPix_t w, h;
	DIST_T ww, hh;
	DIST_T scale_w, scale_h;
	coOrd orig, pos, size;
	carProto_p protoP;
	FLOAT_T ratio;
	int segCnt;
	trkSeg_p segPtr;

	if ( S_PROTO ) {
		ratio = 1;
	} else {
		ratio = 1/GetScaleRatio(carDlgScaleInx);
	}
	wDrawClear( carDlgD.d );
	if ( carDlgDim.carLength <= 0 || carDlgDim.carWidth <= 0 ) {
		return;
	}
	FreeFilledDraw( carDlgSegs_da.cnt, &carDlgSegs(0) );
	if ( !S_PROTO ) {
		if ( carDlgProtoInx < 0 ||
		     (protoP = CarProtoLookup( carDlgProtoStr, FALSE, FALSE, 0.0, 0.0 )) == NULL ||
		     protoP->segCnt == 0 ) {
			CarProtoDlgCreateDummyOutline( &segCnt, &segPtr, (BOOL_T)carDlgIsLoco,
			                               carDlgDim.carLength, carDlgDim.carWidth, carDlgBodyColor );
		} else {
			segCnt = protoP->segCnt;
			segPtr = protoP->segPtr;
		}
	} else {
		if ( carProtoSegCnt <= 0 ) {
			CarProtoDlgCreateDummyOutline( &segCnt, &segPtr, (BOOL_T)carDlgIsLoco,
			                               carDlgDim.carLength, carDlgDim.carWidth, drawColorBlue );
		} else {
			segCnt = carProtoSegCnt;
			segPtr = carProtoSegPtr;
		}
	}
	DYNARR_SET( trkSeg_t, carDlgSegs_da, segCnt );
	memcpy( &carDlgSegs(0), segPtr, segCnt * sizeof carDlgSegs(0) );
	CloneFilledDraw( carDlgSegs_da.cnt, &carDlgSegs(0), TRUE );
	GetSegBounds( zero, 0.0, carDlgSegs_da.cnt, &carDlgSegs(0), &orig, &size );
	scale_w = carDlgDim.carLength/size.x;
	scale_h = carDlgDim.carWidth/size.y;
	RescaleSegs( carDlgSegs_da.cnt, &carDlgSegs(0), scale_w, scale_h, ratio );
	if ( !S_PROTO ) {
		RecolorSegs( carDlgSegs_da.cnt, &carDlgSegs(0), carDlgBodyColor );
	} else {
		if ( carDlgFlipToggle ) {
			pos.x = carDlgDim.carLength/2.0;
			pos.y = carDlgDim.carWidth/2.0;
			RotateSegs( carDlgSegs_da.cnt, &carDlgSegs(0), pos, 180.0 );
		}
	}

	wDrawGetSize( carDlgD.d, &w, &h );
	ww = w/carDlgD.dpi-1.0;
	hh = h/carDlgD.dpi-0.5;
	scale_w = carDlgDim.carLength/ww;
	scale_h = carDlgDim.carWidth/hh;
	if ( scale_w > scale_h ) {
		carDlgD.scale = scale_w;
	} else {
		carDlgD.scale = scale_h;
	}
	orig.x = 0.50*carDlgD.scale;
	orig.y = 0.25*carDlgD.scale;
	DrawSegsDA( &carDlgD, NULL, orig, 0.0, &carDlgSegs_da, 0.0, wDrawColorBlack,
	            0 );
	pos.y = orig.y+carDlgDim.carWidth/2.0;

	if ( carDlgDim.truckCenter > 0.0 ) {
		pos.x = orig.x+(carDlgDim.carLength-carDlgDim.truckCenter)/2.0
		        -carDlgDim.truckCenterOffset;
		CarProtoDrawTruck( &carDlgD, trackGauge*curScaleRatio, ratio, pos, 0.0 );
		pos.x = orig.x+(carDlgDim.carLength+carDlgDim.truckCenter)/2.0
		        -carDlgDim.truckCenterOffset;
		CarProtoDrawTruck( &carDlgD, trackGauge*curScaleRatio, ratio, pos, 0.0 );
	}
	if ( carDlgDim.coupledLength > carDlgDim.carLength ) {
		pos.x = orig.x;
		CarProtoDrawCoupler( &carDlgD,
		                     (carDlgDim.coupledLength-carDlgDim.carLength)/2.0, ratio, pos, 270.0 );
		pos.x = orig.x+carDlgDim.carLength;
		CarProtoDrawCoupler( &carDlgD,
		                     (carDlgDim.coupledLength-carDlgDim.carLength)/2.0, ratio, pos, 90.0 );
	}
}



static void CarDlgLoadRoadnameList( void )
/* Loads RoadnameList.
 * Set carDlgRoadnameInx to entry matching carDlgRoadnameStr (if found)
 * Otherwise not set
 */
{
	wIndex_t inx;
	roadnameMap_p roadnameMapP;

	if ( !roadnameMapChanged ) { return; }
	wListClear( (wList_p)carDlgPLs[I_CD_ROADNAME_LIST].control );
	wListAddValue( (wList_p)carDlgPLs[I_CD_ROADNAME_LIST].control, _("Undecorated"),
	               NULL, NULL );
	for ( inx=0; inx<roadnameMap_da.cnt; inx++ ) {
		roadnameMapP = DYNARR_N(roadnameMap_p, roadnameMap_da, inx);
		wListAddValue( (wList_p)carDlgPLs[I_CD_ROADNAME_LIST].control,
		               roadnameMapP->roadname, NULL, roadnameMapP );
		if ( strcasecmp( carDlgRoadnameStr, roadnameMapP->roadname )==0 ) {
			carDlgRoadnameInx = inx+1;
		}
	}
	roadnameMapChanged = FALSE;
}


static BOOL_T CheckAvail(
        carPartParent_p parentP )
{
	wIndex_t inx;
	carPart_p partP;
	for ( inx=0; inx<parentP->parts_da.cnt; inx++ ) {
		partP = carPart(parentP,inx);
		if ( IsParamValid(partP->paramFileIndex) ) {
			return TRUE;
		}
	}
	return FALSE;
}


static BOOL_T CarDlgLoadManufList(
        BOOL_T bLoadAll,
        BOOL_T bInclCustomUnknown,
        SCALEINX_T scale )
{
	carPartParent_p manufP, manufP1;
	wIndex_t inx, listInx=-1;
	BOOL_T found = TRUE;
	char * firstName = NULL;

	LOG( log_carDlgList, 3,
	     ( "CarDlgLoadManufList( %s, %s, %d )\n    carDlgManufStr=\"%s\"\n",
	       bLoadAll?"TRUE":"FALSE", bInclCustomUnknown?"TRUE":"FALSE", scale,
	       carDlgManufStr ) )
	carDlgManufInx = -1;
	manufP1 = NULL;
	wListClear( (wList_p)carDlgPLs[I_CD_MANUF_LIST].control );
	for ( inx=0; inx<carPartParent_da.cnt; inx++ ) {
		manufP = carPartParent(inx);
		if ( manufP1!=NULL && strcasecmp( manufP1->manuf, manufP->manuf ) == 0 ) {
			continue;
		}
		if ( bLoadAll==FALSE && manufP->scale != scale ) {
			continue;
		}
		if ( !CheckAvail(manufP) ) {
			continue;
		}
		listInx = wListAddValue( (wList_p)carDlgPLs[I_CD_MANUF_LIST].control,
		                         manufP->manuf, NULL, manufP );
		if ( carDlgManufInx < 0 && ( carDlgManufStr[0] == '\0'
		                             || strcasecmp( carDlgManufStr, manufP->manuf ) == 0 ) ) {
			LOG( log_carDlgList, 4, ( "    found manufStr (inx=%d, listInx=%d)\n", inx,
			                          listInx ) )
			carDlgManufInx = listInx;
			if ( carDlgManufStr[0] == '\0' ) { strcpy( carDlgManufStr, manufP->manuf ); }
		}
		if ( firstName == NULL ) {
			firstName = manufP->manuf;
		}
		manufP1 = manufP;
	}
	if ( bInclCustomUnknown ) {
		listInx = wListAddValue( (wList_p)carDlgPLs[I_CD_MANUF_LIST].control,
		                         _("Custom"), NULL, NULL );
		if ( carDlgManufInx < 0 && ( carDlgManufStr[0] == '\0'
		                             || strcasecmp( carDlgManufStr, "Custom" ) == 0 ) ) {
			LOG( log_carDlgList, 4, ( "    found Cus manufStr (inx=%d, listInx=%d)\n", inx,
			                          listInx ) )
			carDlgManufInx = listInx;
			if ( carDlgManufStr[0] == '\0' ) { strcpy( carDlgManufStr, _("Custom") ); }
		}
		if ( firstName == NULL ) {
			firstName = "Custom";
		}
		wListAddValue( (wList_p)carDlgPLs[I_CD_MANUF_LIST].control, _("Unknown"), NULL,
		               NULL );
		if ( carDlgManufInx < 0 && ( carDlgManufStr[0] == '\0'
		                             || strcasecmp( carDlgManufStr, "Unknown" ) == 0 ) ) {
			LOG( log_carDlgList, 4, ( "    found Unk manufStr (inx=%d, listInx=%d)\n", inx,
			                          listInx ) )
			carDlgManufInx = listInx;
			if ( carDlgManufStr[0] == '\0' ) { strcpy( carDlgManufStr, _("Unknown") ); }
		}
	}
	if ( carDlgManufInx < 0 ) {
		found = FALSE;
		if ( firstName != NULL ) {
			LOG( log_carDlgList, 4, ( "    didn't find manufStr, using [0] = %s\n",
			                          firstName ) )
			carDlgManufInx = 0;
			strcpy( carDlgManufStr, firstName );
		}
	}
	return found;
}


static BOOL_T CarDlgLoadProtoList(
        char * manuf,
        SCALEINX_T scale,
        BOOL_T loadTypeList )
{
	carPartParent_p parentP;
	wIndex_t inx, listInx, inx1;
	BOOL_T found;
	carProto_p protoP;
	carPart_p partP;
	char * firstName;
	int typeCount[N_TYPELISTMAP];
	int listTypeInx, currTypeInx;

	listTypeInx = -1;
	carDlgProtoInx = -1;
	firstName = NULL;

	wListClear( (wList_p)carDlgPLs[I_CD_PROTOTYPE_LIST].control );
	memset( typeCount, 0, N_TYPELISTMAP * sizeof typeCount[0] );
	LOG( log_carDlgList, 3,
	     ( "CarDlgLoadProtoList( %s, %d, %s )\n    carDlgProtoStr=\"%s\", carDlgTypeInx=%d\n",
	       manuf?manuf:"NULL", scale, loadTypeList?"TRUE":"FALSE", carDlgProtoStr,
	       carDlgTypeInx ) )
	if ( manuf==NULL ) {
		if ( carProto_da.cnt <= 0 ) { return FALSE; }
		if ( listTypeInx < 0 && carDlgProtoStr[0]
		     && (protoP=CarProtoFind(carDlgProtoStr)) ) {
			listTypeInx = CarProtoFindTypeCode(protoP->type);
		}
		if ( listTypeInx < 0 ) {
			listTypeInx = CarProtoFindTypeCode(carProto(0)->type);
		}
		for ( inx=0; inx<carProto_da.cnt; inx++ ) {
			protoP = carProto(inx);
			currTypeInx = CarProtoFindTypeCode(protoP->type);
			typeCount[currTypeInx]++;
			if ( carDlgTypeInx >= 0 &&
			     listTypeInx != carDlgTypeInx &&
			     currTypeInx == carDlgTypeInx ) {
				LOG( log_carDlgList, 4, ( "    found typeinx, reset list (old=%d)\n",
				                          listTypeInx ) )
				wListClear( (wList_p)carDlgPLs[I_CD_PROTOTYPE_LIST].control );
				listTypeInx = carDlgTypeInx;
				carDlgProtoInx = -1;
				firstName = NULL;
			}
			if ( currTypeInx != listTypeInx ) { continue; }
			listInx = wListAddValue( (wList_p)carDlgPLs[I_CD_PROTOTYPE_LIST].control,
			                         protoP->desc, NULL, protoP );
			if ( carDlgProtoInx < 0 && carDlgProtoStr[0]
			     && strcasecmp( carDlgProtoStr, protoP->desc ) == 0 ) {
				LOG( log_carDlgList, 4, ( "    found protoStr (inx=%d, listInx=%d)\n", inx,
				                          listInx ) )
				carDlgProtoInx = listInx;
				if ( carDlgProtoStr[0] == '\0' ) { strcpy( carDlgProtoStr, protoP->desc ); }
			}
			if ( firstName == NULL ) {
				firstName = protoP->desc;
			}
		}
	} else {
		for ( inx=0; inx<carPartParent_da.cnt; inx++ ) {
			parentP = carPartParent(inx);
			if ( strcasecmp( manuf, parentP->manuf ) != 0 ||
			     scale != parentP->scale ) {
				continue;
			}
			if ( !CheckAvail(parentP) ) {
				continue;
			}
			found = FALSE;
			for ( inx1=0; inx1<parentP->parts_da.cnt; inx1++ ) {
				partP = carPart( parentP, inx1 );
				currTypeInx = CarProtoFindTypeCode(partP->type);
				typeCount[currTypeInx]++;
				if ( listTypeInx < 0 ) {
					listTypeInx = currTypeInx;
				}
				if ( carDlgTypeInx >= 0 &&
				     listTypeInx != carDlgTypeInx &&
				     currTypeInx == carDlgTypeInx ) {
					LOG( log_carDlgList, 4, ( "    found typeinx, reset list (old=%d)\n",
					                          listTypeInx ) )
					wListClear( (wList_p)carDlgPLs[I_CD_PROTOTYPE_LIST].control );
					listTypeInx = carDlgTypeInx;
					carDlgProtoInx = -1;
					firstName = NULL;
				}
				if ( listTypeInx == currTypeInx ) {
					found = TRUE;
				}
			}
			if ( !found ) {
				continue;
			}
			listInx = wListAddValue( (wList_p)carDlgPLs[I_CD_PROTOTYPE_LIST].control,
			                         parentP->proto, NULL, parentP );
			if ( carDlgProtoInx < 0 && ( carDlgProtoStr[0] == '\0'
			                             || strcasecmp( carDlgProtoStr, parentP->proto ) == 0 ) ) {
				LOG( log_carDlgList, 4, ( "    found protoStr (inx=%d, listInx=%d)\n", inx,
				                          listInx ) )
				carDlgProtoInx = listInx;
				if ( carDlgProtoStr[0] == '\0' ) {
					strcpy( carDlgProtoStr, parentP->proto );
				}
			}
			if ( firstName == NULL ) {
				firstName = parentP->proto;
			}
		}
	}

	found = TRUE;
	if ( carDlgProtoInx < 0 ) {
		found = FALSE;
		if ( firstName != NULL ) {
			LOG( log_carDlgList, 4, ( "    didn't find protoStr, using [0] = %s\n",
			                          firstName ) )
			carDlgProtoInx = 0;
			strcpy( carDlgProtoStr, firstName );
		}
	}
	wListSetIndex( (wList_p)carDlgPLs[I_CD_PROTOTYPE_LIST].control,
	               carDlgProtoInx );

	if ( loadTypeList ) {
		LOG( log_carDlgList, 4, ( "    loading typelist\n" ) )
		wListClear( (wList_p)carDlgPLs[I_CD_PROTOKIND_LIST].control );
		for ( currTypeInx=0; currTypeInx<N_TYPELISTMAP; currTypeInx++ ) {
			if ( typeCount[currTypeInx] > 0 ) {
				listInx = wListAddValue( (wList_p)carDlgPLs[I_CD_PROTOKIND_LIST].control,
				                         _(typeListMap[currTypeInx].name), NULL, I2VP(currTypeInx) );
				if ( currTypeInx == listTypeInx ) {
					LOG( log_carDlgList, 4, ( "        current = %d\n", listInx ) )
					carDlgKindInx = listInx;
				}
			}
		}
	}

	return found;
}


static void ConstructPartDesc(
        tabString_t * tabs )
{
	char * cp;
	cp = message;
	*cp = '\0';
	if ( tabs[T_PART].len ) {
		cp = TabStringCpy( cp, &tabs[T_PART] );
		*cp++ = ' ';
	}
	if ( tabs[T_DESC].len ) {
		cp = TabStringCpy( cp, &tabs[T_DESC] );
		*cp++ = ' ';
	}
	if ( tabs[T_REPMARK].len ) {
		cp = TabStringCpy( cp, &tabs[T_REPMARK] );
		*cp++ = ' ';
	} else if ( tabs[T_ROADNAME].len ) {
		cp = TabStringCpy( cp, &tabs[T_ROADNAME] );
		*cp++ = ' ';
	} else {
		strcpy( cp, _("Undecorated ") );
		cp += strlen( cp );
	}
	if ( tabs[T_NUMBER].len ) {
		cp = TabStringCpy( cp, &tabs[T_NUMBER] );
		*cp++ = ' ';
	}
	*cp = '\0';
}


static BOOL_T CarDlgLoadPartList( carPartParent_p parentP )
/* Loads PartList from parentP
 * Set carDlgPartnoInx to entry matching carDlgPartnoStr (if set and found)
 * Otherwise set carDlgPartnoInx and carDlgPartnoStr to 1st entry on list
 * Set carDlgDescStr to found entry
 */
{
	wIndex_t listInx;
	wIndex_t inx;
	carPart_p partP;
	carPart_t lastPart;
	tabString_t tabs[7];
	BOOL_T found;
	carPart_p selPartP;

	carDlgPartnoInx = -1;
	wListClear( (wList_p)carDlgPLs[I_CD_PARTNO_LIST].control );
	if ( parentP==NULL ) {
		carDlgPartnoStr[0] = '\0';
		carDlgDescStr[0] = '\0';
		return FALSE;
	}
	found = FALSE;
	selPartP = NULL;
	lastPart.title = NULL;
	for ( inx=0; inx<parentP->parts_da.cnt; inx++ ) {
		partP = carPart(parentP,inx);
		TabStringExtract( partP->title, 7, tabs );
		ConstructPartDesc( tabs );
		lastPart.paramFileIndex = partP->paramFileIndex;
		if ( message[0] && IsParamValid(partP->paramFileIndex) &&
		     ( lastPart.title == NULL || Cmp_part( &lastPart, partP ) != 0 ) ) {
			listInx = wListAddValue( (wList_p)carDlgPLs[I_CD_PARTNO_LIST].control, message,
			                         NULL, partP );
			if ( carDlgPartnoInx<0 &&
			     (carDlgPartnoStr[0]?TabStringCmp( carDlgPartnoStr,
			                                       &tabs[T_PART] ) == 0:TRUE) ) {
				carDlgPartnoInx = listInx;
				found = TRUE;
				selPartP = partP;
			}
			if ( selPartP == NULL ) {
				selPartP = partP;
			}
			lastPart = *partP;
		}
	}
	if ( selPartP == NULL ) {
		carDlgPartnoStr[0] = '\0';
		carDlgDescStr[0] = '\0';
	} else {
		if ( carDlgPartnoInx<0 ) {
			carDlgPartnoInx = 0;
		}
		TabStringExtract( selPartP->title, 7, tabs );
		TabStringCpy( carDlgPartnoStr, &tabs[T_PART] );
		TabStringCpy( carDlgDescStr, &tabs[T_DESC] );
	}
	return found;
}



static void CarDlgLoadPart(
        carPart_p partP )
{
	tabString_t tabs[7];
	roadnameMap_p roadnameMapP;
	CarDlgLoadDimsFromPart( partP );
	carDlgBodyColor = partP->color;
	carDlgTypeInx = CarProtoFindTypeCode( partP->type );
	carDlgIsLoco = ((partP->type)&1)!=0;
	TabStringExtract( partP->title, 7, tabs );
	TabStringCpy( carDlgPartnoStr, &tabs[T_PART] );
	TabStringCpy( carDlgDescStr, &tabs[T_DESC] );
	roadnameMapP = LoadRoadnameList( &tabs[T_ROADNAME], &tabs[T_REPMARK] );
	carDlgRoadnameInx = lookupListIndex+1;
	if ( roadnameMapP ) {
		TabStringCpy( carDlgRoadnameStr, &tabs[T_ROADNAME] );
		CarDlgLoadRoadnameList();
		TabStringCpy( carDlgRepmarkStr, &tabs[T_REPMARK] );
	} else {
		carDlgRoadnameInx = 0;
		strcpy( carDlgRoadnameStr, _("Undecorated") );
		carDlgRepmarkStr[0] = '\0';
	}
	TabStringCpy( carDlgNumberStr, &tabs[T_NUMBER] );
	carDlgBodyColor = partP->color;
}


static BOOL_T CarDlgLoadLists(
        BOOL_T isItem,
        tabString_t * tabs,
        SCALEINX_T scale )
{
	BOOL_T loadCustomUnknown = isItem;
	DIST_T ratio;
	carPartParent_p parentP;
	static carProto_t protoTmp;
	static char protoTmpDesc[STR_SIZE];

	if ( tabs ) { TabStringCpy( carDlgManufStr, &tabs[T_MANUF] ); }
	if ( strcasecmp( carDlgManufStr, "unknown" ) == 0 ||
	     strcasecmp( carDlgManufStr, "custom" ) == 0 ) {
		loadCustomUnknown = TRUE;
		/*isItem = FALSE;*/
	}
	if ( (!CarDlgLoadManufList( !isItem, loadCustomUnknown, scale )) && tabs ) {
		TabStringCpy( carDlgManufStr, &tabs[T_MANUF] );
		carDlgManufInx = wListAddValue( (wList_p)carDlgPLs[I_CD_MANUF_LIST].control,
		                                carDlgManufStr, NULL, NULL );
		isItem = FALSE;
	}
	if ( isItem ) {
		parentP = (carPartParent_p)wListGetItemContext( (wList_p)
		                carDlgPLs[I_CD_MANUF_LIST].control, carDlgManufInx );
		if ( parentP ) {
			if ( tabs ) { TabStringCpy( carDlgProtoStr, &tabs[T_PROTO] ); }
			if ( CarDlgLoadProtoList( carDlgManufStr, scale, TRUE ) || !tabs ) {
				parentP = (carPartParent_p)wListGetItemContext( (wList_p)
				                carDlgPLs[I_CD_PROTOTYPE_LIST].control, carDlgProtoInx );
				if ( parentP ) {
					if ( tabs ) { TabStringCpy( carDlgPartnoStr, &tabs[T_PART] ); }
					if ( CarDlgLoadPartList( parentP ) || ( (!tabs) && carDlgPartnoInx>=0 ) ) {
						return TRUE;
					}
				}
			}
		}
	}
	if ( tabs ) { TabStringCpy( carDlgProtoStr, &tabs[T_PROTO] ); }
	if ( !CarDlgLoadProtoList( NULL, 0, TRUE ) && tabs ) {
		/* create dummy proto */
		ratio = GetScaleRatio( scale );
		protoTmp.contentsLabel = "temporary";
		protoTmp.paramFileIndex = PARAM_LAYOUT;
		strcpy( protoTmpDesc, carDlgProtoStr );
		protoTmp.desc = protoTmpDesc;
		protoTmp.options = (carDlgIsLoco?CAR_DESC_IS_LOCO:0);
		protoTmp.type = typeListMap[carDlgTypeInx].value;
		protoTmp.dim.carWidth = carDlgDim.carWidth*ratio;
		protoTmp.dim.carLength = carDlgDim.carLength*ratio;
		protoTmp.dim.coupledLength = carDlgDim.coupledLength*ratio;
		protoTmp.dim.truckCenter = carDlgDim.truckCenter*ratio;
		protoTmp.dim.truckCenterOffset = carDlgDim.truckCenterOffset*ratio;
		CarProtoDlgCreateDummyOutline( &carProtoSegCnt, &carProtoSegPtr,
		                               (BOOL_T)carDlgIsLoco, protoTmp.dim.carLength, protoTmp.dim.carWidth,
		                               drawColorBlue );
		protoTmp.segCnt = carProtoSegCnt;
		protoTmp.segPtr = carProtoSegPtr;
		GetSegBounds( zero, 0.0, carProtoSegCnt, carProtoSegPtr, &protoTmp.orig,
		              &protoTmp.size );
		TabStringCpy( carDlgProtoStr, &tabs[T_PROTO] );
		carDlgProtoInx = wListAddValue( (wList_p)carDlgPLs[I_CD_PROTOTYPE_LIST].control,
		                                carDlgProtoStr, NULL, &protoTmp );/*??*/
	}
	carDlgPartnoInx = -1;
	if ( tabs ) {
		TabStringCpy( carDlgPartnoStr, &tabs[T_PART] );
		TabStringCpy( carDlgDescStr, &tabs[T_DESC] );
	}
	return FALSE;
}


static void CarDlgShowControls( void )
{


	/*ParamControlActive( &carDlgPG, I_CD_MANUF_LIST,		S_ITEM||(S_PART&&carDlgUpdatePartPtr) );*/

	ParamControlShow( &carDlgPG, I_CD_NEW,					S_ITEM );
	ParamControlShow( &carDlgPG, I_CD_NEWPROTO,				S_PART );

	ParamControlShow( &carDlgPG, I_CD_ITEMINDEX,			S_ITEM && carDlgDispMode==0 );
	ParamControlShow( &carDlgPG, I_CD_PURPRC,				S_ITEM && carDlgDispMode==0 );
	ParamControlShow( &carDlgPG, I_CD_CURPRC,				S_ITEM && carDlgDispMode==0 );
	ParamControlShow( &carDlgPG, I_CD_COND,					S_ITEM && carDlgDispMode==0 );
	ParamControlShow( &carDlgPG, I_CD_PURDAT,				S_ITEM && carDlgDispMode==0 );
	ParamControlShow( &carDlgPG, I_CD_SRVDAT,				S_ITEM && carDlgDispMode==0 );
	ParamControlShow( &carDlgPG, I_CD_NOTES,				S_ITEM && carDlgDispMode==0 );
	ParamControlShow( &carDlgPG, I_CD_MLTNUM,				S_ITEM
	                  && carDlgUpdateItemPtr==NULL && carDlgDispMode==0 );
	ParamControlShow( &carDlgPG, I_CD_QTY,					S_ITEM && carDlgUpdateItemPtr==NULL
	                  && carDlgDispMode==0 );

	ParamControlShow( &carDlgPG, I_CD_ROADNAME_LIST,		S_PART || ( S_ITEM
	                  && carDlgDispMode==1 ) );
	ParamControlShow( &carDlgPG, I_CD_REPMARK,				S_PART || ( S_ITEM
	                  && carDlgDispMode==1 ) );
	ParamControlShow( &carDlgPG, I_CD_NUMBER,				S_PART || ( S_ITEM
	                  && carDlgDispMode==1 ) );
	ParamControlShow( &carDlgPG, I_CD_BODYCOLOR,			S_PART || ( S_ITEM
	                  && carDlgDispMode==1 ) );
	ParamControlShow( &carDlgPG, I_CD_CARLENGTH,			!( S_ITEM
	                  && carDlgDispMode==0 ) );
	ParamControlShow( &carDlgPG, I_CD_CARWIDTH,				!( S_ITEM
	                  && carDlgDispMode==0 ) );
	ParamControlShow( &carDlgPG, I_CD_TRKCENTER,			!( S_ITEM
	                  && carDlgDispMode==0 ) );
	ParamControlShow( &carDlgPG, I_CD_TRKOFFSET,            !( S_ITEM
	                  && carDlgDispMode==0 ) );
	ParamControlShow( &carDlgPG, I_CD_CANVAS,				!( S_ITEM && carDlgDispMode==0 ) );
	ParamControlShow( &carDlgPG, I_CD_CPLRLEN,				S_PART || ( S_ITEM
	                  && carDlgDispMode==1 ) );
	ParamControlShow( &carDlgPG, I_CD_CPLDLEN,				S_PART || ( S_ITEM
	                  && carDlgDispMode==1 ) );
	ParamControlShow( &carDlgPG, I_CD_CPLRMNT,				S_PART || ( S_ITEM
	                  && carDlgDispMode==1 ) );

	ParamControlShow( &carDlgPG, I_CD_DISPMODE,				S_ITEM );

	ParamControlShow( &carDlgPG, I_CD_TYPE_LIST,			S_PROTO );
	ParamControlShow( &carDlgPG, I_CD_FLIP,					S_PROTO );
	ParamControlShow( &carDlgPG, I_CD_DESC_STR,				S_PART
	                  || (currState==S_ItemEnter) );
	ParamControlShow( &carDlgPG, I_CD_IMPORT,				S_PROTO );
	ParamControlShow( &carDlgPG, I_CD_RESET,				S_PROTO );
	ParamControlShow( &carDlgPG, I_CD_PARTNO_STR,			S_PART
	                  || (currState==S_ItemEnter) );
	ParamControlShow( &carDlgPG, I_CD_PARTNO_LIST,			(currState==S_ItemSel) );
	ParamControlShow( &carDlgPG, I_CD_ISLOCO,				S_PROTO );
	ParamControlShow( &carDlgPG, I_CD_PROTOKIND_LIST,		!S_PROTO );
	ParamControlShow( &carDlgPG, I_CD_PROTOTYPE_LIST,		!S_PROTO );
	ParamControlShow( &carDlgPG, I_CD_PROTOTYPE_STR,		S_PROTO );
	ParamControlShow( &carDlgPG, I_CD_MANUF_LIST,			!S_PROTO );

	/*ParamControlActive( &carDlgPG, I_CD_PROTOTYPE_STR,	S_PROTO && carDlgUpdateProtoPtr==NULL );*/
	ParamControlActive( &carDlgPG, I_CD_ITEMINDEX,			S_ITEM
	                    && carDlgUpdateItemPtr==NULL );
	ParamControlActive( &carDlgPG, I_CD_MLTNUM,				S_ITEM && carDlgQuantity>1 );
	ParamControlActive( &carDlgPG, I_CD_IMPORT,				selectedTrackCount > 0 );

	ParamLoadMessage( &carDlgPG, I_CD_MSG, "" );

	if ( S_ITEM ) {
		if ( carDlgUpdateItemPtr == NULL ) {
			sprintf( message, _("New %s Scale Car"), GetScaleName( carDlgScaleInx ) );
			wButtonSetLabel( carDlgPG.okB, _("Add") );
		} else {
			sprintf( message, _("Update %s Scale Car"), GetScaleName( carDlgScaleInx ) );
			wButtonSetLabel( carDlgPG.okB, _("Update") );
		}
		wWinSetTitle( carDlgPG.win, message );
	} else if ( S_PART ) {
		if ( carDlgUpdatePartPtr == NULL ) {
			sprintf( message, _("New %s Scale Car Part"), GetScaleName( carDlgScaleInx ) );
			wButtonSetLabel( carDlgPG.okB, _("Add") );
		} else {
			sprintf( message, _("Update %s Scale Car Part"),
			         GetScaleName( carDlgScaleInx ) );
			wButtonSetLabel( carDlgPG.okB, _("Update") );
		}
		wWinSetTitle( carDlgPG.win, message );
	} else if ( S_PROTO ) {
		if ( carDlgUpdateProtoPtr == NULL ) {
			wWinSetTitle( carDlgPG.win, _("New Prototype") );
			wButtonSetLabel( carDlgPG.okB, _("Add") );
		} else {
			wWinSetTitle( carDlgPG.win, _("Update Prototype") );
			wButtonSetLabel( carDlgPG.okB, _("Update") );
		}
	}

	ParamLoadControls( &carDlgPG );

	ParamDialogOkActive( &carDlgPG, S_ITEM );
	CarDlgUpdate( &carDlgPG, -1, NULL );
}



static void CarDlgDoActions(
        carDlgAction_e * actions )
{
	carPart_p partP;
	carPartParent_p parentP;
	carProto_p protoP;
	wIndex_t inx;
	int offset;
	DIST_T ratio;
	tabString_t tabs[7];
	char * cp;
	BOOL_T reload[COUNT( carDlgPLs )];
#define RELOAD_DIMS \
		reload[I_CD_CARLENGTH] = reload[I_CD_CARWIDTH] = reload[I_CD_CPLDLEN] = \
		reload[I_CD_TRKCENTER] = reload[I_CD_TRKOFFSET] = reload[I_CD_CPLRLEN] = TRUE
#define RELOAD_PARTDATA \
		RELOAD_DIMS; \
		reload[I_CD_PARTNO_STR] = reload[I_CD_DESC_STR] = \
		reload[I_CD_ROADNAME_LIST] = reload[I_CD_REPMARK] = \
		reload[I_CD_NUMBER] = reload[I_CD_BODYCOLOR] = TRUE
#define RELOAD_LISTS \
		reload[I_CD_MANUF_LIST] = \
		reload[I_CD_PROTOKIND_LIST] = \
		reload[I_CD_PROTOTYPE_LIST] = \
		reload[I_CD_PARTNO_LIST] = TRUE

	memset( reload, 0, sizeof reload );
	while ( 1 ) {
		LOG( log_carDlgState, 2, ( "Action = %s\n", carDlgAction_s[*actions] ) )
		switch ( *actions++ ) {
		case A_Return:
			for ( inx=0; inx<COUNT( carDlgPLs ); inx++ )
				if ( reload[inx] ) {
					ParamLoadControl( &carDlgPG, inx );
				}
			return;
		case A_SError:
			currState = S_Error;
			break;
		case A_Else:
			offset = (int)*actions++;
			actions += offset;
			break;
		case A_SItemSel:
			currState = S_ItemSel;
			break;
		case A_SItemEnter:
			currState = S_ItemEnter;
			break;
		case A_SPartnoSel:
			currState = S_PartnoSel;
			break;
		case A_SPartnoEnter:
			currState = S_PartnoEnter;
			break;
		case A_SProtoSel:
			currState = S_ProtoSel;
			break;
		case A_IsCustom:
			offset = (int)*actions++;
			if ( currState != S_ItemEnter ) {
				actions += offset;
			}
			break;
		case A_IsNewPart:
			offset = (int)*actions++;
			if (carDlgNewPartPtr==NULL) {
				actions += offset;
			} else {
				TabStringExtract( carDlgNewPartPtr->title, 7, tabs );
				TabStringCpy( carDlgPartnoStr, &tabs[T_PART] );
				TabStringCpy( carDlgDescStr, &tabs[T_DESC] );
				reload[I_CD_PARTNO_STR] = reload[I_CD_DESC_STR] = TRUE;
			}
			break;
		case A_IsNewProto:
			offset = (int)*actions++;
			if (carDlgNewProtoPtr==NULL) {
				actions += offset;
			} else {
				strcpy( carDlgProtoStr, carDlgNewProtoPtr->desc );
			}
			break;
		case A_LoadDataFromPartList:
			partP = (carPart_p)wListGetItemContext( (wList_p)
			                                        carDlgPLs[I_CD_PARTNO_LIST].control, carDlgPartnoInx );
			if ( partP != NULL ) {
				CarDlgLoadPart(partP);
				RELOAD_PARTDATA;
				RELOAD_PARTDATA;
			}
			break;
		case A_LoadDimsFromStack:
			carDlgDim = carDlgStk[carDlgStkPtr].dim;
			carDlgCouplerLength = carDlgStk[carDlgStkPtr].couplerLength;
			carDlgTypeInx = carDlgStk[carDlgStkPtr].typeInx;
			carDlgIsLoco = (typeListMap[carDlgTypeInx].value&1) != 0;
			RELOAD_DIMS;
			break;
		case A_LoadManufListForScale:
			CarDlgLoadManufList( FALSE, TRUE, carDlgScaleInx );
			reload[I_CD_MANUF_LIST] = TRUE;
			break;
		case A_LoadManufListAll:
			CarDlgLoadManufList( TRUE, FALSE, carDlgScaleInx );
			reload[I_CD_MANUF_LIST] = TRUE;
			break;
		case A_LoadProtoListForManuf:
			parentP = (carPartParent_p)wListGetItemContext( (wList_p)
			                carDlgPLs[I_CD_MANUF_LIST].control, carDlgManufInx );
			CarDlgLoadProtoList( parentP->manuf, parentP->scale, TRUE );
			reload[I_CD_PROTOKIND_LIST] = TRUE;
			reload[I_CD_PROTOTYPE_LIST] = TRUE;
			break;
		case A_LoadProtoListAll:
			CarDlgLoadProtoList( NULL, 0, TRUE );
			reload[I_CD_PROTOKIND_LIST] = TRUE;
			reload[I_CD_PROTOTYPE_LIST] = TRUE;
			break;
		case A_LoadPartnoList:
			parentP = (carPartParent_p)wListGetItemContext( (wList_p)
			                carDlgPLs[I_CD_PROTOTYPE_LIST].control, carDlgProtoInx );
			CarDlgLoadPartList( parentP );
			reload[I_CD_PARTNO_LIST] = TRUE;
			break;
		case A_LoadLists:
			if ( CarDlgLoadLists( TRUE, NULL, carDlgScaleInx ) ) {
				currState = S_ItemSel;
			} else {
				currState = S_ItemEnter;
			}
			break;
		case A_LoadDimsFromProtoList:
			protoP = (carProto_p)wListGetItemContext( (wList_p)
			                carDlgPLs[I_CD_PROTOTYPE_LIST].control, carDlgProtoInx );
			if ( protoP ) {
				CarDlgLoadDimsFromProto( protoP );
				carDlgTypeInx = CarProtoFindTypeCode( protoP->type );
				carDlgIsLoco = (protoP->options&CAR_DESC_IS_LOCO)!=0;
			} else {
				ratio = GetScaleRatio( carDlgScaleInx );
				carDlgDim.carLength = 50*12/ratio;
				carDlgDim.carWidth = 10*12/ratio;
				carDlgDim.coupledLength = carDlgDim.carLength+carDlgCouplerLength*2;
				carDlgDim.truckCenter = carDlgDim.carLength-59.0*2.0/ratio;

				carDlgTypeInx = 0;
				carDlgIsLoco = (typeListMap[0].value&1);
			}
			RELOAD_DIMS;
			reload[I_CD_TYPE_LIST] = reload[I_CD_ISLOCO] = TRUE;
			break;
		case A_ConvertDimsToProto:
			ratio = GetScaleRatio( carDlgScaleInx );
			carDlgDim.carLength *= ratio;
			carDlgDim.carWidth *= ratio;
			carDlgCouplerLength = 16.0;
			carDlgDim.coupledLength = carDlgDim.carLength + 2 * carDlgCouplerLength;
			carDlgDim.truckCenter *= ratio;
			carDlgDim.truckCenterOffset *= ratio;
			RELOAD_DIMS;
			break;
		case A_Redraw:
			CarDlgRedraw( carDlgD.d, NULL, 0, 0 );
			break;
		case A_ClrManuf:
			carDlgManufStr[0] = '\0';
			wListSetValue( (wList_p)carDlgPLs[I_CD_MANUF_LIST].control, "" );
			carDlgManufInx = -1;
			break;
		case A_ClrPartnoStr:
			carDlgPartnoStr[0] = '\0';
			carDlgDescStr[0] = '\0';
			reload[I_CD_PARTNO_STR] = reload[I_CD_DESC_STR] = TRUE;
			break;
		case A_ClrNumberStr:
			carDlgNumberStr[0] = '\0';
			reload[I_CD_NUMBER] = TRUE;
			break;
		case A_LoadProtoStrFromList:
			wListGetValues( (wList_p)carDlgPLs[I_CD_PROTOTYPE_LIST].control, carDlgProtoStr,
			                sizeof carDlgProtoStr, NULL, NULL );
#ifdef LATER
			protoP = (carProto_p)wListGetItemContext( (wList_p)
			                carDlgPLs[I_CD_PROTOTYPE_LIST].control, carDlgProtoInx );
			if ( protoP ) {
				carDlgTypeInx = CarProtoFindTypeCode( protoP->type );
				carDlgIsLoco = (protoP->options&CAR_DESC_IS_LOCO)!=0;
			}
#endif
			break;
		case A_ShowPartnoList:
			reload[I_CD_PARTNO_LIST] = TRUE;
			ParamControlShow( &carDlgPG, I_CD_PARTNO_LIST, TRUE );
			ParamControlShow( &carDlgPG, I_CD_DESC_STR, FALSE );
			ParamControlShow( &carDlgPG, I_CD_PARTNO_STR, FALSE );
			break;
		case A_HidePartnoList:
			reload[I_CD_PARTNO_STR] = reload[I_CD_DESC_STR] = TRUE;
			ParamControlShow( &carDlgPG, I_CD_PARTNO_LIST, FALSE );
			ParamControlShow( &carDlgPG, I_CD_DESC_STR, TRUE );
			ParamControlShow( &carDlgPG, I_CD_PARTNO_STR, TRUE );
			break;
		case A_PushDims:
			CHECK( carDlgStkPtr < CARDLG_STK_SIZE );
			carDlgStk[carDlgStkPtr].dim = carDlgDim;
			carDlgStk[carDlgStkPtr].couplerLength = carDlgCouplerLength;
			carDlgStk[carDlgStkPtr].state = currState;
			carDlgStk[carDlgStkPtr].changed = carDlgChanged;
			carDlgStk[carDlgStkPtr].typeInx = carDlgTypeInx;
			if ( currState == S_ItemSel && carDlgPartnoInx >= 0 ) {
				carDlgStk[carDlgStkPtr].partP = (carPart_p)wListGetItemContext( (
				                                        wList_p)carDlgPLs[I_CD_PARTNO_LIST].control, carDlgPartnoInx );
			} else {
				carDlgStk[carDlgStkPtr].partP = NULL;
			}
			carDlgStkPtr++;
			break;
		case A_PopDims:
			break;
		case A_PopTitleAndTypeinx:
			if ( carDlgStk[carDlgStkPtr].partP ) {
				TabStringExtract( carDlgStk[carDlgStkPtr].partP->title, 7, tabs );
				strcpy( carDlgManufStr, carDlgStk[carDlgStkPtr].partP->parent->manuf );
				strcpy( carDlgProtoStr, carDlgStk[carDlgStkPtr].partP->parent->proto );
				TabStringCpy( carDlgPartnoStr, &tabs[T_PART] );
				TabStringCpy( carDlgDescStr, &tabs[T_DESC] );
			}
			carDlgTypeInx = carDlgStk[carDlgStkPtr].typeInx;
			break;
		case A_PopCouplerLength:
			carDlgCouplerLength = carDlgStk[carDlgStkPtr].couplerLength;
			break;
		case A_ShowControls:
			CarDlgShowControls();
			break;
		case A_LoadInfoFromUpdateItem:
			carDlgScaleInx = carDlgUpdateItemPtr->scaleInx;
			carDlgItemIndex = carDlgUpdateItemPtr->index;
			TabStringExtract( carDlgUpdateItemPtr->title, 7, tabs );
			TabStringCpy( carDlgManufStr, &tabs[T_MANUF] );
			TabStringCpy( carDlgProtoStr, &tabs[T_PROTO] );
			TabStringCpy( carDlgRoadnameStr, &tabs[T_ROADNAME] );
			TabStringCpy( carDlgRepmarkStr, &tabs[T_REPMARK] );
			TabStringCpy( carDlgNumberStr, &tabs[T_NUMBER] );
			carDlgDim = carDlgUpdateItemPtr->dim;
			carDlgBodyColor = carDlgUpdateItemPtr->color;
			carDlgTypeInx = CarProtoFindTypeCode( carDlgUpdateItemPtr->type );
			carDlgIsLoco = (carDlgUpdateItemPtr->type&1)!=0;
			carDlgCouplerLength = (carDlgDim.coupledLength-carDlgDim.carLength)/2.0;
			sprintf( message, "%s-%s", carDlgPLs[I_CD_CPLRLEN].nameStr,
			         GetScaleName(carDlgScaleInx) );
			wPrefSetFloat( carDlgPG.nameStr, message, carDlgCouplerLength );
			carDlgCouplerMount = (carDlgUpdateItemPtr->options&CAR_DESC_COUPLER_MODE_BODY)!=
			                     0;
			carDlgIsLoco = (carDlgUpdateItemPtr->options&CAR_DESC_IS_LOCO)!=0;
			carDlgPurchPrice = carDlgUpdateItemPtr->data.purchPrice;
			sprintf( carDlgPurchPriceStr, "%0.2f", carDlgPurchPrice );
			carDlgCurrPrice = carDlgUpdateItemPtr->data.currPrice;
			sprintf( carDlgCurrPriceStr, "%0.2f", carDlgCurrPrice );
			carDlgCondition = carDlgUpdateItemPtr->data.condition;
			carDlgConditionInx = MapCondition( carDlgUpdateItemPtr->data.condition );
			carDlgPurchDate = carDlgUpdateItemPtr->data.purchDate;
			if ( carDlgPurchDate ) {
				sprintf( carDlgPurchDateStr, "%ld", carDlgPurchDate );
			} else {
				carDlgPurchDateStr[0] = '\0';
			}
			carDlgServiceDate = carDlgUpdateItemPtr->data.serviceDate;
			if ( carDlgServiceDate ) {
				sprintf( carDlgServiceDateStr, "%ld", carDlgServiceDate );
			} else {
				carDlgServiceDateStr[0] = '\0';
			}
			wTextClear( (wText_p)carDlgPLs[I_CD_NOTES].control );
			if ( carDlgUpdateItemPtr->data.notes ) {
				strncpy( message, carDlgUpdateItemPtr->data.notes, sizeof message );
				message[sizeof message - 1] = '\0';
				for ( cp=message; *cp; cp++ ) {
					if ( *cp == '\n' ) { *cp = ' '; }
				}
				wTextAppend( (wText_p)carDlgPLs[I_CD_NOTES].control, message );
			}
			LoadRoadnameList( &tabs[T_ROADNAME], &tabs[T_REPMARK] );
			CarDlgLoadRoadnameList();
			carDlgRoadnameInx = lookupListIndex+1;
			memset( reload, 1, sizeof reload );

			if ( CarDlgLoadLists( TRUE, tabs, carDlgScaleInx ) ) {
				currState = S_ItemSel;
			} else {
				currState = S_ItemEnter;
			}
			break;
		case A_LoadDataFromUpdatePart:
			carDlgScaleInx = carDlgUpdatePartPtr->parent->scale;
			TabStringExtract( carDlgUpdatePartPtr->title, 7, tabs );
			tabs[T_MANUF].ptr = carDlgUpdatePartPtr->parent->manuf;
			tabs[T_MANUF].len = (int)strlen(carDlgUpdatePartPtr->parent->manuf);
			tabs[T_PROTO].ptr = carDlgUpdatePartPtr->parent->proto;
			tabs[T_PROTO].len = (int)strlen(carDlgUpdatePartPtr->parent->proto);
			CarDlgLoadLists( FALSE, tabs, carDlgScaleInx );
			CarDlgLoadPart( carDlgUpdatePartPtr );
			RELOAD_LISTS;
			RELOAD_DIMS;
			RELOAD_PARTDATA;
			break;
		case A_InitProto:
			if ( carDlgUpdateProtoPtr==NULL ) {
				carDlgProtoStr[0] = 0;
				carDlgDim.carLength = 50*12;
				carDlgDim.carWidth = 10*12;
				carDlgDim.coupledLength = carDlgDim.carLength+16.0*2.0;
				carDlgCouplerLength = (carDlgDim.coupledLength-carDlgDim.carLength)/2.0;
				carDlgDim.truckCenter = carDlgDim.carLength-59.0*2.0;
				carDlgDim.truckCenterOffset = 0;
				carDlgIsLoco = (typeListMap[carDlgTypeInx].value&1);
			} else {
				strcpy( carDlgProtoStr, carDlgUpdateProtoPtr->desc );
				carDlgDim = carDlgUpdateProtoPtr->dim;
				carDlgCouplerLength = (carDlgDim.coupledLength-carDlgDim.carLength)/2.0;
				carDlgIsLoco = (carDlgUpdateProtoPtr->options&CAR_DESC_IS_LOCO)!=0;
				carDlgTypeInx = CarProtoFindTypeCode( carDlgUpdateProtoPtr->type );
				carProtoSegCnt = carDlgUpdateProtoPtr->segCnt;
				carProtoSegPtr = carDlgUpdateProtoPtr->segPtr;
				currState = S_ProtoSel;
			}
			RELOAD_DIMS;
			break;
		case A_RecallCouplerLength:
			sprintf( message, "%s-%s", carDlgPLs[I_CD_CPLRLEN].nameStr,
			         GetScaleName(carDlgScaleInx) );
			carDlgCouplerLength = 16.0/GetScaleRatio(carDlgScaleInx);
			wPrefGetFloat( carDlgPG.nameStr, message, &carDlgCouplerLength,
			               carDlgCouplerLength );
			break;
		default:
			CHECKMSG( FALSE, ( "carDlgDoActions: bad action %d", (int)(actions[-1]) ) );
			break;
		}
	}
}


static void CarDlgDoStateActions(
        carDlgAction_e * actions )
{
	CarDlgDoActions( actions );
	LOG( log_carDlgState, 1, ( " ==> S_%s\n", carDlgState_s[currState] ) )
}

static void CarDlgStateMachine(
        carDlgTransistion_e transistion )
{
	LOG( log_carDlgState, 1, ( "S_%s[T_%s]\n", carDlgState_s[currState],
	                           carDlgTransistion_s[transistion] ) )
	CarDlgDoStateActions( stateMachine[currState][transistion] );
}


static BOOL_T CheckCarDlgItemIndex( long * index )
{
	BOOL_T found = TRUE;
	BOOL_T updated = FALSE;

	int inx;
	carItem_p item;
	while ( found ) {
		found = FALSE;
		for ( inx=0; inx<carItemInfo_da.cnt; inx++ ) {
			item = carItemInfo(inx);
			if ( item->index == *index ) {
				(*index)++;
				found = TRUE;
				updated = TRUE;
				break;
			}
		}
	}
	return !updated;
}


void CarDlgError(
        wBool_t ok,
        paramData_p p,
        char * msg )
{
	p->bInvalid = !ok;
	ParamHilite( p->group->win, p->control, !ok );
	wWinPix_t h = wControlGetHeight(p->control);
	wControlSetBalloon( p->control, 0, -h*3/4, ok?NULL:msg );
}


static void CarDlgUpdate(
        paramGroup_p pg,
        int inx,
        void * valueP )
{
	BOOL_T redraw = FALSE;
	roadnameMap_p roadnameMapP;
	char * cp, *cq;
	long valL, d, m;
	FLOAT_T ratio;
	BOOL_T ok = TRUE;
	DIST_T len;
	BOOL_T checkTruckCenter = FALSE;
	cmp_key_t cmp_key;
	coOrd orig, size, size2;
	carPartParent_p parentP;
#ifdef TRUCK_OFFSET
	static DIST_T carDlgTruckOffsetL;
	static DIST_T carDlgTruckOffsetR;
#endif
	static long carDlgClock;
	static long carDlgCarLengthClock;
	static long carDlgTruckCenterClock;
	static long carDlgCoupledLengthClock;
	static long carDlgCouplerLengthClock;

	ratio = (S_PROTO?1.0:GetScaleRatio(carDlgScaleInx));

	LOG( log_carDlgState, 3, ( "CarDlgUpdate( %d )\n", inx ) )

	switch ( inx ) {

	case -1:
#ifdef TRUCK_OFFSET
		if ( carDlgDim.truckCenter > 0
		     && carDlgDim.carLength > carDlgDim.truckCenter ) {
			carDlgTruckOffsetL = (carDlgDim.carLength - carDlgDim.truckCenter)/2 -
			                     carDlgDim.truckCenterOffset;
			carDlgTruckOffsetR = (carDlgDim.carLength - carDlgDim.truckCenter)/2 +
			                     carDlgDim.truckCenterOffset;
		} else {
			carDlgTruckOffsetL = 0;
			carDlgTruckOffsetR = 0;
		}
#endif

		carDlgCarLengthClock = carDlgCoupledLengthClock = carDlgTruckCenterClock =
		                               carDlgCouplerLengthClock = carDlgClock = 0;
#ifdef TRUCK_OFFSET
		LOG( log_carDlgDims, 1,
		     ( "truckCenter(%d): trkCenter:%0.3f, .carLength:%0.3f = trackOffset: %0.3f %0.3f\n",
		       carDlgDim.truckCenter,
		       carDlgDim.carLength,
		       carDlgTruckOffsetL,
		       carDlgTruckOffsetR ) );
#endif
		redraw = TRUE;
		break;

	case I_CD_MANUF_LIST:
		carDlgChanged++;
		wListGetValues( (wList_p)pg->paramPtr[inx].control, carDlgManufStr,
		                sizeof carDlgManufStr, NULL, NULL );
		if ( carDlgManufInx < 0 ||
		     wListGetItemContext( (wList_p)pg->paramPtr[inx].control,
		                          carDlgManufInx ) == NULL ) {
			CarDlgStateMachine( T_ItemEnter );
		}
#ifdef LATER
		else if ( strcasecmp( carDlgManufStr, "unknown" ) == 0 ||
		          strcasecmp( carDlgManufStr, "custom" ) == 0 ) {
			CarDlgStateMachine( T_ItemEnter );
		}
#endif
		else {
			CarDlgStateMachine( T_ItemSel );
		}
		/*ParamControlShow( &carDlgPG, I_CD_MANUF_LIST, TRUE );*/
		break;

	case I_CD_PROTOKIND_LIST:
		carDlgChanged++;
		carDlgTypeInx = (int)VP2L(wListGetItemContext( (wList_p)
		                          pg->paramPtr[inx].control, carDlgKindInx ));
		if ( S_PART || (currState==S_ItemEnter) ) {
			CarDlgLoadProtoList( NULL, 0, FALSE );
		} else {
			parentP = NULL;
			if ( carDlgProtoInx >= 0 ) {
				parentP = (carPartParent_p)wListGetItemContext( (wList_p)
				                pg->paramPtr[I_CD_PROTOTYPE_LIST].control, carDlgProtoInx );
			}
			CarDlgLoadProtoList( carDlgManufStr, (parentP?parentP->scale:0), FALSE );
		}
		CarDlgStateMachine( T_ProtoSel );
		break;

	case I_CD_PROTOTYPE_LIST:
		carDlgChanged++;
		wListGetValues( (wList_p)pg->paramPtr[inx].control, carDlgProtoStr,
		                sizeof carDlgProtoStr, NULL, NULL );
		CarDlgStateMachine( T_ProtoSel );
		break;

	case I_CD_PARTNO_LIST:
		carDlgChanged++;
		wListGetValues( (wList_p)pg->paramPtr[inx].control, carDlgPartnoStr,
		                sizeof carDlgPartnoStr, NULL, NULL );
		if ( carDlgPartnoInx >= 0 ) {
			CarDlgStateMachine( T_PartnoSel );
		} else {
			CarDlgStateMachine( T_PartnoEnter );
			wControlSetFocus( pg->paramPtr[I_CD_PARTNO_STR].control );
		}
		break;

	case I_CD_DISPMODE:

		if ( !ParamCheckInputs( &carDlgPG, carDlgPLs[I_CD_DISPMODE].control ) ) {
			carDlgDispMode = 1-carDlgDispMode;
			ParamLoadControl( &carDlgPG, I_CD_DISPMODE );
			break;
		}
		for ( inx=B; inx<C; inx++ ) {
			ParamControlShow( &carDlgPG, inx, carDlgDispMode==1 );
		}
		for ( inx=C; inx<D; inx++ ) {
			ParamControlShow( &carDlgPG, inx, carDlgDispMode==0 );
		}
		if ( carDlgDispMode == 0 && carDlgUpdateItemPtr != NULL ) {
			ParamControlShow( &carDlgPG, I_CD_QTY, FALSE );
			ParamControlShow( &carDlgPG, I_CD_MLTNUM, FALSE );
		}
		redraw = carDlgDispMode==1;
		break;

	case I_CD_ROADNAME_LIST:
		carDlgChanged++;
		roadnameMapP = NULL;
		if ( *(long*)valueP == 0 ) {
			roadnameMapP = NULL;
			carDlgRoadnameStr[0] = '\0';
		} else if ( *(long*)valueP > 0 ) {
			roadnameMapP = (roadnameMap_p)wListGetItemContext( (wList_p)
			                pg->paramPtr[I_CD_ROADNAME_LIST].control, (wIndex_t)*(long*)valueP );
			strcpy( carDlgRoadnameStr, roadnameMapP->roadname );
		} else {
			wListGetValues( (wList_p)pg->paramPtr[I_CD_ROADNAME_LIST].control,
			                carDlgRoadnameStr, sizeof carDlgRoadnameStr, NULL, NULL );
			cmp_key.name = carDlgRoadnameStr;
			cmp_key.len = (int)strlen(carDlgRoadnameStr);
			roadnameMapP = LookupListElem( &roadnameMap_da, &cmp_key, Cmp_roadnameMap, 0 );
		}
		if ( roadnameMapP ) {
			strcpy( carDlgRepmarkStr, roadnameMapP->repmark );
		} else {
			carDlgRepmarkStr[0] = '\0';
		}
		ParamLoadControl( pg, I_CD_REPMARK );
		break;

	case I_CD_CARLENGTH:
		carDlgChanged++;
		if ( carDlgDim.carLength == 0.0 ) {
			carDlgCarLengthClock = 0;
		} else if ( carDlgDim.carLength < 100/ratio ) {
			return;
		} else if ( carDlgCouplerLength != 0 && ( carDlgDim.coupledLength == 0
		                || carDlgCouplerLengthClock >= carDlgCoupledLengthClock ) ) {
			len = carDlgDim.carLength+carDlgCouplerLength*2.0;
			if ( len > 0 ) {
				carDlgDim.coupledLength = len;
				ParamLoadControl( &carDlgPG, I_CD_CPLDLEN );
			}
			carDlgCarLengthClock = ++carDlgClock;
		} else if ( carDlgDim.coupledLength != 0 && ( carDlgCouplerLength == 0
		                || carDlgCoupledLengthClock > carDlgCouplerLengthClock ) ) {
			len = (carDlgDim.coupledLength-carDlgDim.carLength)/2.0;
			if ( len > 0 ) {
				carDlgCouplerLength = len;
				ParamLoadControl( &carDlgPG, I_CD_CPLRLEN );
				if ( !S_PROTO ) {
					sprintf( message, "%s-%s", carDlgPLs[I_CD_CPLRLEN].nameStr,
					         GetScaleName(carDlgScaleInx) );
					wPrefSetFloat( carDlgPG.nameStr, message, carDlgCouplerLength );
				}
			}
			carDlgCarLengthClock = ++carDlgClock;
		}
		checkTruckCenter = TRUE;
		redraw = TRUE;
		break;

	case I_CD_CPLDLEN:
		carDlgChanged++;
		if ( carDlgDim.coupledLength == 0 ) {
			carDlgCoupledLengthClock = 0;
		} else if ( carDlgDim.coupledLength < 100/ratio ) {
			return;
		} else if ( carDlgDim.carLength != 0 && ( carDlgCouplerLength == 0
		                || carDlgCarLengthClock > carDlgCouplerLengthClock ) ) {
			len = (carDlgDim.coupledLength-carDlgDim.carLength)/2.0;
			if ( len > 0 ) {
				carDlgCouplerLength = len;
				ParamLoadControl( &carDlgPG, I_CD_CPLRLEN );
				if ( !S_PROTO ) {
					sprintf( message, "%s-%s", carDlgPLs[I_CD_CPLRLEN].nameStr,
					         GetScaleName(carDlgScaleInx) );
					wPrefSetFloat( carDlgPG.nameStr, message, carDlgCouplerLength );
				}
			}
			carDlgCoupledLengthClock = ++carDlgClock;
		} else if ( carDlgCouplerLength != 0 && ( carDlgDim.carLength == 0
		                || carDlgCouplerLengthClock >= carDlgCarLengthClock ) ) {
			len = carDlgDim.coupledLength-carDlgCouplerLength*2.0;
			if ( len > 0 ) {
				carDlgDim.carLength = len;
				ParamLoadControl( &carDlgPG, I_CD_CARLENGTH );
				checkTruckCenter = TRUE;
			}
			carDlgCoupledLengthClock = ++carDlgClock;
		}
		redraw = TRUE;
		break;

	case I_CD_CPLRLEN:
		carDlgChanged++;
		if ( carDlgCouplerLength == 0 ) {
			carDlgCouplerLengthClock = 0;
			redraw = TRUE;
			break;
		} else if ( carDlgCouplerLength < 1/ratio ) {
			return;
		} else if ( carDlgDim.carLength != 0 && ( carDlgDim.coupledLength == 0
		                || carDlgCarLengthClock >= carDlgCoupledLengthClock ) ) {
			len = carDlgDim.carLength+carDlgCouplerLength*2.0;
			if ( len > 0 ) {
				carDlgDim.coupledLength = len;
				ParamLoadControl( &carDlgPG, I_CD_CPLDLEN );
			}
			carDlgCouplerLengthClock = ++carDlgClock;
		} else if ( carDlgDim.coupledLength != 0 && ( carDlgDim.carLength == 0
		                || carDlgCoupledLengthClock > carDlgCarLengthClock ) ) {
			len = carDlgDim.coupledLength-carDlgCouplerLength*2.0;
			if ( len > 0 ) {
				carDlgDim.carLength = len;
				ParamLoadControl( &carDlgPG, I_CD_CARLENGTH );
				checkTruckCenter = TRUE;
			}
			carDlgCouplerLengthClock = ++carDlgClock;
		}
		if ( !S_PROTO ) {
			sprintf( message, "%s-%s", carDlgPLs[I_CD_CPLRLEN].nameStr,
			         GetScaleName(carDlgScaleInx) );
			wPrefSetFloat( carDlgPG.nameStr, message, carDlgCouplerLength );
		}
		redraw = TRUE;
		break;

	case I_CD_CARWIDTH:
		carDlgChanged++;
		if ( carDlgDim.carLength < 30/ratio ) { return; }
		redraw = TRUE;
		break;

	case I_CD_BODYCOLOR:
		carDlgChanged++;
		RecolorSegs( carDlgSegs_da.cnt, &carDlgSegs(0), carDlgBodyColor );
		redraw = TRUE;
		break;

	case I_CD_ISLOCO:
		carDlgChanged++;
		redraw = TRUE;
		break;

	case I_CD_TRKOFFSET:
		carDlgChanged++;
#ifdef TRUCK_OFFSET
		if ( carDlgDim.truckCenterOffset == 0 ) {
			carDlgTruckOffsetL = carDlgDim.truckCenter/2;
			carDlgTruckOffsetR = carDlgTruckOffsetL;
		} else if (carDlgDim.carLength - carDlgDim.truckCenter > 2*fabs(
		                   carDlgDim.truckCenterOffset)) {
			carDlgTruckOffsetL = (carDlgDim.carLength-carDlgDim.truckCenter)/2 -
			                     carDlgDim.truckCenterOffset;
			carDlgTruckOffsetR = (carDlgDim.carLength-carDlgDim.truckCenter)/2 +
			                     carDlgDim.truckCenterOffset;
		} else {
			carDlgTruckOffsetL = 0;
			carDlgTruckOffsetR = 0;
		}
#endif
		if ( 2*carDlgDim.truckCenterOffset > carDlgDim.carLength -
		     carDlgDim.truckCenter) {
			ok = FALSE;
			CarDlgError( ok, &carDlgPLs[I_CD_TRKOFFSET],
			             _("Truck Center Offset plus Truck Centers must be less than Car Length") );
		}
		redraw = TRUE;
#ifdef TRUCK_OFFSET
		LOG( log_carDlgDims, 1,
		     ("I_CD_TRKOFFSET: len:%0.3f, center:%0.3f, cenoff:%0.3f = offset:%0.3f %0.3f\n",
		      carDlgDim.carLength,
		      carDlgDim.truckCenter,
		      carDlgDim.truckCenterOffset,
		      carDlgTruckOffsetL,
		      carDlgTruckOffsetR ) );
#endif
		break;

	case I_CD_TRKCENTER:
		carDlgChanged++;
#ifdef TRUCK_OFFSET
		if ( carDlgDim.truckCenter == 0 ||
		     carDlgDim.truckCenterOffset == 0.0 ) {
			carDlgTruckOffsetL = 0;
			carDlgTruckOffsetR = 0;
		} else if ( carDlgDim.truckCenter <
		            100/ratio /*&& carDlgDim.carLength == 0.0*/ ) {
			carDlgTruckOffsetL = 0;
			carDlgTruckOffsetR = 0;
			return;
		} else if ( carDlgDim.carLength - carDlgDim.truckCenter > 2*fabs(
		                    carDlgDim.truckCenterOffset) ) {
			carDlgTruckOffsetL = (carDlgDim.carLength-carDlgDim.truckCenter)/2 -
			                     carDlgDim.truckCenterOffset;
			carDlgTruckOffsetR = (carDlgDim.carLength-carDlgDim.truckCenter)/2 +
			                     carDlgDim.truckCenterOffset;
		} else {
			carDlgTruckOffsetL = 0;
			carDlgTruckOffsetR = 0;
		}
		LOG( log_carDlgDims, 1,
		     ("I_CD_TRKCENTER: len:%0.3f, center:%0.3f, cenoff:%0.3f = offset:%0.3f %0.3f\n",
		      carDlgDim.carLength,
		      carDlgDim.truckCenter,
		      carDlgDim.truckCenterOffset,
		      carDlgTruckOffsetL,
		      carDlgTruckOffsetR ) );
#endif
		redraw = TRUE;
		break;

	case I_CD_QTY:
		wControlActive( carDlgPLs[I_CD_MLTNUM].control, carDlgQuantity>1 );
		break;

	case I_CD_PURPRC:
	case I_CD_CURPRC:
		carDlgChanged++;
		*(FLOAT_T*)(pg->paramPtr[inx].context) = strtod( (char*)
		                pg->paramPtr[inx].valueP, &cp );
		if ( cp==NULL || *cp!='\0' ) {
			*(FLOAT_T*)(pg->paramPtr[inx].context) = -1;
			ok = FALSE;
			sprintf( message, "%s not valid", pg->paramPtr[inx].winLabel );
		}
		CarDlgError( ok, &pg->paramPtr[inx], message );
		break;

	case I_CD_COND:
		carDlgChanged++;
		carDlgCondition =
		        (carDlgConditionInx==0)?0:
		        (carDlgConditionInx==1)?100:
		        (carDlgConditionInx==2)?80:
		        (carDlgConditionInx==3)?60:
		        (carDlgConditionInx==4)?40:20;
		break;

	case I_CD_PURDAT:
	case I_CD_SRVDAT:
		carDlgChanged++;
		for ( cp = (char*)pg->paramPtr[inx].valueP; *cp
		      && isspace(*(unsigned char*)cp); cp++ );
		if ( *cp ) {
			valL = strtol( cp, &cq, 10 );
			if ( cq==NULL || *cq !='\0' ) {
				cp = N_("Enter a 8 digit numeric date (yyyymmdd)");
				ok = FALSE;
			} else {
				if ( strlen(cp) != 8  || valL == 0) {
					cp = N_("Enter a 8 digit numeric date (yyyymmdd)");
					ok = FALSE;
				} else if ( valL < 19000101 || valL > 21991231 ) {
					cp = N_("Enter a date between 19000101 and 21991231");
					ok = FALSE;
				} else {
					d = valL % 100;
					m = (valL / 100) % 100;
					if ( m < 1 || m > 12 ) {
						cp = N_("Invalid month");
						ok = FALSE;
					} else if ( d < 1 || d > 31 ) {
						cp = N_("Invalid day");
						ok = FALSE;
					} else {
						cp = NULL;
					}
				}
			}
			if ( !ok ) {
				valL = -1;
			}
		} else {
			cp = NULL;
			valL = 0;
		}
		CarDlgError( ok, &pg->paramPtr[inx], cp );
		if (inx == I_CD_PURDAT) {
			carDlgPurchDate = valL;
		} else {
			carDlgServiceDate = valL;
		}
		break;

	case I_CD_TYPE_LIST:
		carDlgChanged++;
		carDlgIsLoco = (typeListMap[carDlgTypeInx].value&1);
		ParamLoadControl( &carDlgPG, I_CD_ISLOCO );
		redraw = TRUE;
		break;

	case I_CD_IMPORT:
		carDlgChanged++;
		WriteSelectedTracksToTempSegs();
		carProtoSegCnt = tempSegs_da.cnt;
		carProtoSegPtr = &tempSegs(0);
		CloneFilledDraw( carProtoSegCnt, carProtoSegPtr, TRUE );
		GetSegBounds( zero, 0.0, carProtoSegCnt, carProtoSegPtr, &orig, &size );
		if ( size.x <= 0.0 ||
		     size.y <= 0.0 ||
		     size.x < size.y ) {
			NoticeMessage( MSG_CARPROTO_BADSEGS, _("Ok"), NULL );
			return;
		}
		orig.x = -orig.x;
		orig.y = -orig.y;
		MoveSegs( carProtoSegCnt, carProtoSegPtr, orig );
		size2.x = floor(size.x*curScaleRatio+0.5);
		size2.y = floor(size.y*curScaleRatio+0.5);
		RescaleSegs( carProtoSegCnt, carProtoSegPtr, size2.x/size.x, size2.y/size.y,
		             curScaleRatio );
		carDlgDim.carLength = size2.x;
		carDlgDim.carWidth = size2.y;
		carDlgDim.coupledLength = carDlgDim.carLength + 32;
#ifdef TRUCK_OFFSET
		if ( carDlgDim.carLength > 120 ) {
			carDlgDim.truckCenter = carDlgDim.carLength - 120;
			carDlgTruckOffsetL = (carDlgDim.carLength - carDlgDim.truckCenter)/2;
			carDlgTruckOffsetR = (carDlgDim.carLength - carDlgDim.truckCenter)/2;
		} else {
			carDlgDim.truckCenter = 0;
			carDlgTruckOffsetL = 0;
			carDlgTruckOffsetR = 0;
		}
#endif
		carDlgFlipToggle = FALSE;
		ParamLoadControl( &carDlgPG, I_CD_CARLENGTH );
		ParamLoadControl( &carDlgPG, I_CD_CARWIDTH );
		ParamLoadControl( &carDlgPG, I_CD_CPLRLEN );
		ParamLoadControl( &carDlgPG, I_CD_TRKCENTER );
		ParamLoadControl( &carDlgPG, I_CD_TRKOFFSET );
		redraw = TRUE;
		break;

	case I_CD_RESET:
		carDlgChanged++;
		carProtoSegCnt = 0;
		redraw = TRUE;
		break;

	case I_CD_FLIP:
		carDlgChanged++;
		carDlgFlipToggle = ! carDlgFlipToggle;
		redraw = TRUE;
		break;

	}

	if ( checkTruckCenter && carDlgDim.carLength > 0 ) {
#ifdef TRUCK_OFFSET
		DIST_T offL = (carDlgDim.carLength - carDlgDim.truckCenter)/2 -
		              carDlgDim.truckCenterOffset;
		DIST_T offR = (carDlgDim.carLength - carDlgDim.truckCenter)/2 +
		              carDlgDim.truckCenterOffset;
		LOG( log_carDlgDims, 1, ( "OFF %0.3f %0.3f %0.3f %0.3f\n",
		                          carDlgTruckOffsetL, carDlgTruckOffsetR, offL, offR ) );

		if ( carDlgTruckOffsetL > 0 || carDlgTruckOffsetR > 0 ) {
			carDlgDim.truckCenter = carDlgDim.carLength - ( carDlgTruckOffsetL +
			                        carDlgTruckOffsetR );
			carDlgDim.truckCenterOffset = (carDlgTruckOffsetR - carDlgTruckOffsetL)/2;
		} else {
			carDlgDim.truckCenter = carDlgDim.carLength * 0.75;
			carDlgDim.truckCenterOffset = 0;
		}
		LOG( log_carDlgDims, 1,
		     ( "checkTruckCenter length:%0.3f, offset %0.3f, %0.3f = truckCenter:%0.3f, truckCenterOffset:%0.3f\n",
		       carDlgDim.carLength, carDlgTruckOffsetL, carDlgTruckOffsetR,
		       carDlgDim.truckCenter, carDlgDim.truckCenterOffset ) );
#endif
		ParamLoadControl( &carDlgPG, I_CD_TRKCENTER );
		ParamLoadControl( &carDlgPG, I_CD_TRKOFFSET );
	}

	if ( S_PART && carDlgManufStr[0] == '\0' ) {
		ParamLoadMessage( &carDlgPG, I_CD_MSG, _("Select or Enter a Manufacturer") );
	} else if ( S_ITEM && carDlgUpdateItemPtr==NULL &&
	            ( valL = carDlgItemIndex, !CheckCarDlgItemIndex(&carDlgItemIndex) ) ) {
		sprintf( message,
		         _("Item Index %ld duplicated an existing item: updated to new value"), valL );
		ParamLoadControl( &carDlgPG, I_CD_ITEMINDEX );
		ParamLoadMessage( &carDlgPG, I_CD_MSG, message );
		ok = TRUE;
	} else {
		ParamLoadMessage( pg, I_CD_MSG, "" );
		ok = TRUE;
	}

	if ( redraw ) {
		CarDlgRedraw( carDlgD.d, NULL, 0, 0 );
	}

	ParamDialogOkActive( pg, ok );
}



static void CarDlgNewDesc( void )
{
	carDlgNewPartPtr = NULL;
	carDlgNewProtoPtr = NULL;
	carDlgUpdatePartPtr = NULL;
	carDlgNumberStr[0] = '\0';
	ParamLoadControl( &carDlgPG, I_CD_NUMBER );
	CarDlgDoStateActions( item2partActions );
	carDlgChanged = 0;
}


static void CarDlgNewProto( void )
{
	carProto_p protoP = CarProtoFind( carDlgProtoStr );
	if ( protoP != NULL ) {
		carProtoSegCnt = protoP->segCnt;;
		carProtoSegPtr = protoP->segPtr;;
	} else {
		carProtoSegCnt = 0;
		carProtoSegPtr = NULL;
	}
	carDlgUpdateProtoPtr = NULL;
	carDlgNewProtoPtr = NULL;
	if ( S_ITEM ) {
		CarDlgDoStateActions( item2protoActions );
	} else {
		CarDlgDoStateActions( part2protoActions );
	}
	carDlgChanged = 0;
}


static void CarDlgClose( wWin_p win )
{
	carDlgState_e oldState;

	if ( carDlgChanged ) {
		if ( !inPlayback ) {
			if ( NoticeMessage( MSG_CARDESC_CHANGED, _("Yes"), _("No") ) <= 0 ) {
				return;
			}
		} else {
			PlaybackMessage( "Car Desc Changed\n" );
		}
	}
	if ( carDlgStkPtr > 0 ) {
		carDlgStkPtr--;
		oldState = currState;
		currState = carDlgStk[carDlgStkPtr].state;
		carDlgChanged = carDlgStk[carDlgStkPtr].changed;
		if ( oldState == S_ProtoSel )
			if ( S_PART ) {
				CarDlgDoStateActions( proto2partActions );
			} else {
				CarDlgDoStateActions( proto2itemActions );
			} else {
			CarDlgDoStateActions( part2itemActions );
		}
	} else {
		wTextClear( (wText_p)carDlgPLs[I_CD_NOTES].control );
		wHide( carDlgPG.win );
	}
}


static void CarDlgOk( void * unused )
{
	long options = 0;
	int len;
	FILE * f;
	long number;
	char * cp;
	long count;
	tabString_t tabs[7];
	char title[STR_LONG_SIZE*2];
	carItem_p itemP=NULL;
	carPart_p partP=NULL;
	carProto_p protoP;
	BOOL_T reloadRoadnameList = FALSE;

	LOG( log_carDlgState, 3, ( "CarDlgOk()\n" ) )

	ParamUpdate(&carDlgPG);

	/*ParamUpdate( &carDlgPG );*/
	if ( carDlgDim.carLength <= 0.0 ||
	     carDlgDim.carWidth <= 0.0 ||
	     carDlgDim.truckCenter <= 0.0 ||
	     carDlgDim.truckCenterOffset < 0.0 ||
	     carDlgDim.coupledLength <= 0.0 ) {
		NoticeMessage( MSG_CARDESC_VALUE_ZERO, _("Ok"), NULL );
		return;
	}
	if ( carDlgDim.carLength <= carDlgDim.carWidth ||
	     carDlgDim.truckCenter >= carDlgDim.carLength ) {
		NoticeMessage( MSG_CARDESC_BAD_DIM_VALUE, _("Ok"), NULL );
		return;
	}
	if ( carDlgDim.coupledLength <= carDlgDim.carLength ) {
		NoticeMessage( MSG_CARDESC_BAD_COUPLER_LENGTH_VALUE, _("Ok"), NULL );
		return;
	}

	if ( S_ITEM && carDlgUpdateItemPtr==NULL
	     && !CheckCarDlgItemIndex(&carDlgItemIndex) ) {
		NoticeMessage( MSG_CARITEM_BAD_INDEX, _("Ok"), NULL );
		ParamLoadControl( &carDlgPG, I_CD_ITEMINDEX );
		return;
	}

	if ( S_ITEM && (carDlgPurchDate<0 || carDlgServiceDate<0 || carDlgPurchPrice <0
	                || carDlgCurrPrice<0)) { return; }

	if ( S_PROTO && carDlgProtoStr[0] == '\0' ) { return; }

	if ( S_PART && (carDlgManufStr[0] == '\0' || carDlgPartnoStr[0] == '\0')) { return; }

	if ( S_ITEM && carDlgItemIndex <= 0 ) { return; }

	if ( (!S_PROTO) && carDlgCouplerMount != 0 ) {
		options |= CAR_DESC_COUPLER_MODE_BODY;
	}
	if ( carDlgIsLoco == 1 ) {
		options |= CAR_DESC_IS_LOCO;
	}

	if ( S_ITEM ) {
		len = wTextGetSize( (wText_p)carDlgPLs[I_CD_NOTES].control );
		sprintf( title, "%s\t%s\t%s\t%s\t%s\t%s\t%s", carDlgManufStr, carDlgProtoStr,
		         carDlgDescStr, carDlgPartnoStr, carDlgRoadnameStr, carDlgRepmarkStr,
		         carDlgNumberStr );
		partP = NULL;
		if ( ( carDlgManufInx < 0 || carDlgPartnoInx < 0 ) && carDlgPartnoStr[0] ) {
			partP = CarPartFind( carDlgManufStr, (int)strlen(carDlgManufStr),
			                     carDlgPartnoStr, (int)strlen(carDlgPartnoStr), carDlgScaleInx );
			if ( partP != NULL &&
			     NoticeMessage( MSG_CARPART_DUPNAME, _("Yes"), _("No") ) <= 0 ) {
				return;
			}
			partP = CarPartNew( NULL, PARAM_CUSTOM, carDlgScaleInx, title, options,
			                    typeListMap[carDlgTypeInx].value, &carDlgDim, carDlgBodyColor );
			if ( partP != NULL ) {
				if ( ( f = OpenCustom("a") ) ) {
					SetCLocale();
					CarPartWrite( f, partP );
					fclose(f);
					SetUserLocale();
				}
			}
		}
		if ( carDlgUpdateItemPtr!=NULL ) {
			carDlgQuantity = 1;
		}
		for ( count=0; count<carDlgQuantity; count++ ) {
			itemP = CarItemNew( carDlgUpdateItemPtr,
			                    PARAM_CUSTOM, carDlgItemIndex,
			                    carDlgScaleInx, title, options, typeListMap[carDlgTypeInx].value,
			                    &carDlgDim, carDlgBodyColor,
			                    carDlgPurchPrice, carDlgCurrPrice, carDlgCondition,
			                    carDlgPurchDate, carDlgServiceDate );
			if ( carDlgUpdateItemPtr==NULL ) {
				wPrefSetInteger( "misc", "last-car-item-index", carDlgItemIndex );
				carDlgItemIndex++;
				CheckCarDlgItemIndex(&carDlgItemIndex);
				ParamLoadControl( &carDlgPG, I_CD_ITEMINDEX );
				if ( carDlgQuantity>1 && carDlgMultiNum==0 ) {
					number = strtol( carDlgNumberStr, &cp, 10 );
					if ( cp && *cp == 0 && number > 0 ) {
						sprintf( carDlgNumberStr, "%ld", number+1 );
						sprintf( title, "%s\t%s\t%s\t%s\t%s\t%s\t%s", carDlgManufStr, carDlgProtoStr,
						         carDlgDescStr, carDlgPartnoStr, carDlgRoadnameStr, carDlgRepmarkStr,
						         carDlgNumberStr );
					}
				}
			}
			if ( len > 0 ) {
				if ( itemP->data.notes ) {
					itemP->data.notes = MyRealloc( itemP->data.notes, (len+2) * sizeof(wchar_t) );
				} else {
					itemP->data.notes = MyMalloc( (len+2) * sizeof(wchar_t) );
				}
				// itemP->data.notes = (char*)MyMalloc( (len+2) * sizeof(wchar_t) );
				wTextGetText( (wText_p)carDlgPLs[I_CD_NOTES].control, itemP->data.notes, len );
				if ( itemP->data.notes[len-1] != '\n' ) {
					itemP->data.notes[len] = '\n';
					itemP->data.notes[len+1] = '\0';
				} else {
					itemP->data.notes[len] = '\0';
				}
			} else if ( itemP->data.notes ) {
				MyFree( itemP->data.notes );
				itemP->data.notes = NULL;
			}
		}
		if ( carDlgUpdateItemPtr==NULL ) {
			CarInvListAdd( itemP );
		} else {
			CarInvListUpdate( itemP );
		}
		SetFileChanged();
		reloadRoadnameList = TRUE;
		if ( carDlgUpdateItemPtr==NULL ) {
			if ( carDlgQuantity > 1 ) {
				sprintf( message, _("Added %ld new Cars"), carDlgQuantity );
			} else {
				strcpy( message, _("Added new Car") );
			}
		} else {
			strcpy( message, _("Updated Car") );
		}
		sprintf( message+strlen(message), "%s: %s %s %s %s %s %s",
		         (partP?_(" and Part"):""),
		         carDlgManufStr, carDlgPartnoStr, carDlgProtoStr, carDlgDescStr,
		         (carDlgRepmarkStr[ 0 ]?carDlgRepmarkStr:carDlgRoadnameStr), carDlgNumberStr );
		carDlgQuantity = 1;
		ParamLoadControl( &carDlgPG, I_CD_QTY );

	} else if ( S_PART ) {
		if ( strcasecmp( carDlgRoadnameStr, "undecorated" ) == 0 ) {
			carDlgRoadnameStr[0] = '\0';
			carDlgRepmarkStr[0] = '\0';
		}
		if ( carDlgUpdatePartPtr==NULL ) {
			partP = CarPartFind( carDlgManufStr, (int)strlen(carDlgManufStr),
			                     carDlgPartnoStr, (int)strlen(carDlgPartnoStr), carDlgScaleInx );
			if ( partP != NULL &&
			     NoticeMessage( MSG_CARPART_DUPNAME, _("Yes"), _("No") ) <= 0 ) {
				return;
			}
		}
		sprintf( message, "%s\t%s\t%s\t%s\t%s\t%s\t%s", carDlgManufStr, carDlgProtoStr,
		         carDlgDescStr, carDlgPartnoStr, carDlgRoadnameStr, carDlgRepmarkStr,
		         carDlgNumberStr );
		carDlgNewPartPtr = CarPartNew( carDlgUpdatePartPtr, PARAM_CUSTOM,
		                               carDlgScaleInx, message, options, typeListMap[carDlgTypeInx].value,
		                               &carDlgDim, carDlgBodyColor );
		if ( carDlgNewPartPtr != NULL && ( f = OpenCustom("a") ) ) {
			SetCLocale();
			CarPartWrite( f, carDlgNewPartPtr );
			fclose(f);
			SetUserLocale();
		}
		reloadRoadnameList = TRUE;
		sprintf( message, _("%s Part: %s %s %s %s %s %s"),
		         carDlgUpdatePartPtr==NULL?_("Added new"):_("Updated"), carDlgManufStr,
		         carDlgPartnoStr, carDlgProtoStr, carDlgDescStr,
		         carDlgRepmarkStr[ 0 ]?carDlgRepmarkStr:carDlgRoadnameStr, carDlgNumberStr );

	} else if ( S_PROTO ) {
		if ( carDlgUpdateProtoPtr==NULL ) {
			protoP = CarProtoFind( carDlgProtoStr );
			if ( protoP != NULL &&
			     NoticeMessage( MSG_CARPROTO_DUPNAME, _("Yes"), _("No") ) <= 0 ) {
				return;
			}
		}
		carDlgNewProtoPtr = CarProtoNew( carDlgUpdateProtoPtr, PARAM_CUSTOM,
		                                 carDlgProtoStr, options, typeListMap[carDlgTypeInx].value, &carDlgDim,
		                                 carDlgSegs_da.cnt, &carDlgSegs(0) );
		if ( (f = OpenCustom("a") ) ) {
			SetCLocale();
			CarProtoWrite( f, carDlgNewProtoPtr );
			fclose(f);
			SetUserLocale();
		}
		sprintf( message, _("%s Prototype: %s%s."),
		         carDlgUpdateProtoPtr==NULL?_("Added new"):_("Updated"), carDlgProtoStr,
		         carDlgUpdateProtoPtr==NULL?_(". Enter new values or press Close"):"" );
	}

	if ( reloadRoadnameList ) {
		tabs[0].ptr = carDlgRoadnameStr;
		tabs[0].len = (int)strlen(carDlgRoadnameStr);
		tabs[1].ptr = carDlgRepmarkStr;
		tabs[1].len = (int)strlen(carDlgRepmarkStr);
		LoadRoadnameList( &tabs[0], &tabs[1] );
		CarDlgLoadRoadnameList();
		ParamLoadControl( &carDlgPG, I_CD_ROADNAME_LIST );
	}

	ParamLoadMessage( &carDlgPG, I_CD_MSG, message );

	DoChangeNotification( CHANGE_PARAMS );

	carDlgChanged = 0;
	if ( S_ITEM ) {
		if ( carDlgUpdateItemPtr==NULL ) {
			if ( partP ) {
				TabStringExtract( title, 7, tabs );
				if ( CarDlgLoadLists( TRUE, tabs, GetLayoutCurScale()) ) {
					currState = S_ItemSel;
				} else {
					currState = S_ItemEnter;
				}
				ParamLoadControl( &carDlgPG, I_CD_MANUF_LIST );
				ParamLoadControl( &carDlgPG, I_CD_PROTOKIND_LIST );
				ParamLoadControl( &carDlgPG, I_CD_PROTOTYPE_LIST );
				ParamLoadControl( &carDlgPG, I_CD_PARTNO_LIST );
				ParamLoadControl( &carDlgPG, I_CD_PARTNO_STR );
				ParamLoadControl( &carDlgPG, I_CD_DESC_STR );
				ParamControlShow( &carDlgPG, I_CD_PARTNO_LIST, carDlgPartnoInx>=0 );
				ParamControlShow( &carDlgPG, I_CD_PARTNO_STR, carDlgPartnoInx<0 );
				ParamControlShow( &carDlgPG, I_CD_DESC_STR, carDlgPartnoInx<0 );
			} else if ( carDlgManufInx == -1 ) {
				carDlgManufStr[0] = '\0';
			}
			return;
		}
	} else if ( S_PART ) {
		if ( carDlgUpdatePartPtr==NULL ) {
			number = strtol( carDlgPartnoStr, &cp, 10 );
			if ( cp && *cp == 0 && number > 0 ) {
				sprintf( carDlgPartnoStr, "%ld", number+1 );
			} else {
				carDlgPartnoStr[0] = '\0';
			}
			carDlgNumberStr[0] = '\0';
			ParamLoadControl( &carDlgPG, I_CD_PARTNO_STR );
			ParamLoadControl( &carDlgPG, I_CD_NUMBER );
			return;
		}
	} else if ( S_PROTO ) {
		if ( carDlgUpdateProtoPtr==NULL ) {
			carDlgProtoStr[0] = '\0';
			ParamLoadControl( &carDlgPG, I_CD_PROTOTYPE_STR );
			return;
		}
	}
	CarDlgClose( carDlgPG.win );
}



static void CarDlgLayout(
        paramData_t * pd,
        int inx,
        wWinPix_t currX,
        wWinPix_t *xx,
        wWinPix_t *yy )
{
	static wWinPix_t col2pos = 0;
	wWinPix_t y0, y1;

	switch (inx) {
	case I_CD_PROTOTYPE_STR:
	case I_CD_PARTNO_STR:
	case I_CD_ISLOCO:
	case I_CD_IMPORT:
	case I_CD_TYPE_LIST:
		*yy = wControlGetPosY(carDlgPLs[inx-1].control);
		break;
	case I_CD_NEWPROTO:
		*yy = wControlGetPosY(carDlgPLs[I_CD_NEW].control);
		break;
	case I_CD_CPLRLEN:
	case I_CD_CARWIDTH:
		if ( col2pos == 0 ) {
			col2pos = wLabelWidth( _("Coupler Length") )+20;
		}
		*xx = wControlBeside(carDlgPLs[inx-1].control) + col2pos;
		break;
	case I_CD_DESC_STR:
		*yy = wControlBelow(carDlgPLs[I_CD_PARTNO_STR].control) + 3;
		break;
	case I_CD_CPLRMNT:
		*yy = wControlBelow(carDlgPLs[I_CD_TRKOFFSET].control) + 3;
		break;
	case I_CD_CPLDLEN:
		*yy = wControlBelow(carDlgPLs[I_CD_CPLRMNT].control) + 3;
		break;
	case I_CD_CANVAS:
		*yy = wControlBelow(carDlgPLs[I_CD_CPLDLEN].control)+5;
		break;
	case C:
		*yy = wControlGetPosY(carDlgPLs[B].control);
		break;
	case I_CD_MSG:
		y0 = wControlBelow(carDlgPLs[C-1].control);
		y1 = wControlBelow(carDlgPLs[D-1].control);
		*yy = ((y0>y1)?y0:y1) + 10;
		break;
	}
}


static void DoCarPartDlg( carDlgAction_e *actions )
{
	paramData_t * pd;
	int inx;

	if ( carDlgPG.win == NULL ) {
		ParamCreateDialog( &carDlgPG, MakeWindowTitle(_("New Car Part")), _("Add"),
		                   CarDlgOk, ParamCancel_Custom( CarDlgClose ),
		                   TRUE, CarDlgLayout,
		                   F_BLOCK|F_RESIZE|F_RECALLSIZE|PD_F_ALT_CANCELLABEL, CarDlgUpdate );

		if ( carDlgDim.carWidth==0 ) {
			carDlgDim.carWidth = 12.0*10.0/curScaleRatio;
		}

		for ( pd=carDlgPG.paramPtr; pd<&carDlgPG.paramPtr[carDlgPG.paramCnt]; pd++ ) {
			if ( pd->type == PD_FLOAT && pd->valueP ) {
				sprintf( message, "%s-%s", pd->nameStr, curScaleName );
				wPrefGetFloat( carDlgPG.nameStr, message, (FLOAT_T*)pd->valueP,
				               *(FLOAT_T*)pd->valueP );
			}
		}
		roadnameMapChanged = TRUE;

		for ( inx=0; inx<N_CONDLISTMAP; inx++ ) {
			wListAddValue( (wList_p)carDlgPLs[I_CD_COND].control, _(condListMap[inx].name),
			               NULL, I2VP(condListMap[inx].value) );
		}

		for ( inx=0; inx<N_TYPELISTMAP; inx++ ) {
			wListAddValue( (wList_p)carDlgPLs[I_CD_TYPE_LIST].control,
			               _(typeListMap[inx].name), NULL, I2VP(typeListMap[inx].value) );
		}

		for ( inx=0; inx<N_TYPELISTMAP; inx++ ) {
			wListAddValue( (wList_p)carDlgPLs[I_CD_PROTOKIND_LIST].control,
			               _(typeListMap[inx].name), NULL, I2VP(typeListMap[inx].value) );
		}

		wTextSetReadonly( (wText_p)carDlgPLs[I_CD_NOTES].control, FALSE );
	}

	wPrefGetInteger( "misc", "last-car-item-index", &carDlgItemIndex, 1 );
	CheckCarDlgItemIndex(&carDlgItemIndex);
	CarDlgLoadRoadnameList();
	carProtoSegCnt = 0;
	carProtoSegPtr = NULL;
	carDlgScaleInx = GetLayoutCurScale();
	carDlgFlipToggle = FALSE;
	carDlgChanged = 0;
	for ( paramData_p p=carDlgPLs; p < carDlgPLs + COUNT( carDlgPLs ); p++ ) {
		p->bInvalid = FALSE;
	}

	CarDlgDoStateActions( actions );

	wShow( carDlgPG.win );
}


EXPORT void CarDlgAddProto( void )
{
	/*carDlgPrototypeStr[0] = 0; */
	carDlgTypeInx = 0;
	carDlgUpdateProtoPtr = NULL;
	DoCarPartDlg( protoNewActions );
}

EXPORT void CarDlgAddDesc( void )
{
	if ( carProto_da.cnt <= 0 ) {
		NoticeMessage( MSG_NO_CARPROTO, _("Ok"), NULL );
		return;
	}
	carDlgIsLoco = FALSE;
	carDlgUpdatePartPtr = NULL;
	carDlgNumberStr[0] = '\0';
	ParamLoadControl( &carDlgPG, I_CD_NUMBER );
	DoCarPartDlg( partNewActions );
}

/*
 * Car Inventory List
 */

static wIndex_t carInvInx;

static wIndex_t carInvSort[] = { 0, 1, 2, 3 };
#define N_SORT			(COUNT( carInvSort ))

static void CarInvDlgAdd( void );
static void CarInvDlgEdit( void );
static void CarInvDlgDeleteShelve( void );
static void CarInvDlgImportCsv( void );
static void CarInvDlgExportCsv( void );
static void CarInvDlgSaveText( void );
static void CarInvListLoad( void );

static wWinPix_t carInvColumnWidths[] = {
	-40, 30, 100, -50, 50, 130, 120, 100,
	        -50, -50, 60, 55, 55, 40, 200
        };
static const char * carInvColumnTitles[] = {
	N_("Index"), N_("Scale"), N_("Manufacturer"), N_("Part No"), N_("Type"),
	N_("Description"), N_("Roadname"), N_("Rep Marks"), N_("Purc Price"),
	N_("Curr Price"), N_("Condition"), N_("Purc Date"), N_("Srvc Date"),
	N_("Locat'n"), N_("Notes")
};
static char * sortOrders[] = {
	N_("Index"), N_("Scale"), N_("Manufacturer"), N_("Part No"), N_("Type"),
	N_("Description"), N_("Roadname"), N_("RepMarks"), N_("Purch Price"),
	N_("Curr Price"), N_("Condition"), N_("Purch Date"), N_("Service Date")
};
#define S_INDEX			(0)
#define S_SCALE			(1)
#define S_MANUF			(2)
#define S_PARTNO		(3)
#define S_TYPE			(4)
#define S_DESC			(5)
#define S_ROADNAME		(6)
#define S_REPMARKS		(7)
#define S_PURCHPRICE	(8)
#define S_CURRPRICE		(9)
#define S_CONDITION		(10)
#define S_PURCHDATE		(11)
#define S_SRVDATE		(12)
static paramListData_t carInvListData = { 30, 600, COUNT( carInvColumnTitles ), carInvColumnWidths, carInvColumnTitles };
static paramData_t carInvPLs[] = {
#define I_CI_SORT		(0)
	{ PD_DROPLIST, &carInvSort[0], "sort1", PDO_LISTINDEX|0, I2VP(110), N_("Sort By") },
	{ PD_DROPLIST, &carInvSort[1], "sort2", PDO_LISTINDEX|PDO_DLGHORZ, I2VP(110), "" },
	{ PD_DROPLIST, &carInvSort[2], "sort3", PDO_LISTINDEX|PDO_DLGHORZ, I2VP(110), "" },
	{ PD_DROPLIST, &carInvSort[3], "sort4", PDO_LISTINDEX|PDO_DLGHORZ, I2VP(110), "" },
#define S				(4)
#define I_CI_LIST		(S+0)
	{ PD_LIST, &carInvInx, "list", PDO_LISTINDEX|PDO_DLGRESIZE|PDO_DLGNOLABELALIGN|PDO_DLGRESETMARGIN, &carInvListData, NULL, BO_READONLY|BL_MANY },
#define I_CI_EDIT		(S+1)
	{ PD_BUTTON, CarInvDlgEdit, "edit", PDO_DLGCMDBUTTON, NULL, N_("Edit") },
#define I_CI_ADD		(S+2)
	{ PD_BUTTON, CarInvDlgAdd, "add", 0, NULL, N_("Add"), 0, 0 },
#define I_CI_DELETE		(S+3)
	{ PD_BUTTON, CarInvDlgDeleteShelve, "delete", PDO_DLGWIDE, NULL, N_("Delete") },
#define I_CI_IMPORT_CSV	(S+4)
	{ PD_BUTTON, CarInvDlgImportCsv, "import", PDO_DLGWIDE, NULL, N_("Import") },
#define I_CI_EXPORT_CSV	(S+5)
	{ PD_BUTTON, CarInvDlgExportCsv, "export", 0, NULL, N_("Export") },
#define I_CI_PRINT		(S+6)
	{ PD_BUTTON, CarInvDlgSaveText, "savetext", 0, NULL, N_("List") }
};
static paramGroup_t carInvPG = { "carinv", 0, carInvPLs, COUNT( carInvPLs ) };

static carItem_p CarInvDlgFindCurrentItem( void )
{
	wIndex_t selcnt = wListGetSelectedCount( (wList_p)
	                  carInvPLs[I_CI_LIST].control );
	wIndex_t inx, cnt;

	if ( selcnt != 1 ) { return NULL; }
	cnt = wListGetCount( (wList_p)carInvPLs[I_CI_LIST].control );
	for ( inx=0; inx<cnt; inx++ )
		if ( wListGetItemSelected( (wList_p)carInvPLs[I_CI_LIST].control, inx ) ) {
			break;
		}
	if ( inx>=cnt ) { return NULL; }
	return (carItem_p)wListGetItemContext( (wList_p)carInvPLs[I_CI_LIST].control,
	                                       inx );
}


static void CarInvDlgFind( void * unused )
{
	carItem_p item = CarInvDlgFindCurrentItem();
	coOrd pos;
	ANGLE_T angle;
	if ( item == NULL || item->car == NULL || IsTrackDeleted(item->car) ) { return; }
	CarGetPos( item->car, &pos, &angle );
	CarSetVisible( item->car );
	panCenter = pos;
	LOG( log_pan, 2, ( "PanCenter:%d %0.3f %0.3f\n", __LINE__, panCenter.x,
	                   panCenter.y ) );
	PanHere( I2VP(0) );		// CarInvDlgFind
}


static void CarInvDlgAdd( void )
{
	if ( carProto_da.cnt <= 0 ) {
		NoticeMessage( MSG_NO_CARPROTO, _("Ok"), NULL );
		return;
	}
	carDlgUpdateItemPtr = NULL;
	DoCarPartDlg( itemNewActions );
}


static void CarInvDlgEdit( void )
{
	carDlgUpdateItemPtr = CarInvDlgFindCurrentItem();
	if ( carDlgUpdateItemPtr == NULL ) {
		return;
	}
	DoCarPartDlg( itemUpdActions );
}

/**
 * Move a CarItem from the layout to the Shelf
 *
 * \item car item to be moved
 */
EXPORT void CarItemShelve(
        carItem_p item )
{
	if ( item->car == NULL || IsTrackDeleted( item->car ) ) { return; }
	DeleteTrack( item->car, FALSE );
	if (CarItemIsLoco(item)) {
		LocoListChangeEntry(item->car, NULL);
	}
	CarItemUpdate(item);
	HotBarCancel();
	InfoSubstituteControls(NULL, NULL);
}


static void CarInvDlgDeleteShelve( void )
{
	carItem_p item;
	wIndex_t inx, inx1, cnt, selcnt;
	wBool_t bShowMsg = FALSE;
	wBool_t bNeedReload = FALSE;

	selcnt = wListGetSelectedCount( (wList_p)carInvPLs[I_CI_LIST].control );
	if ( selcnt == 0 ) {
		return;
	}
	cnt = wListGetCount( (wList_p)carInvPLs[I_CI_LIST].control );
	for ( inx=0; inx<cnt; inx++ ) {
		if ( !wListGetItemSelected( (wList_p)carInvPLs[I_CI_LIST].control, inx ) ) {
			continue;
		}
		item = (carItem_p)wListGetItemContext( (wList_p)carInvPLs[I_CI_LIST].control,
		                                       inx );
		if ( item == NULL ) {
			continue;
		}
		if ( item->car && !IsTrackDeleted(item->car) ) {
			// Shelve car from Layout
			CarItemShelve( item );
			bNeedReload = TRUE;
		} else {
			// Delete car from Inventory
			if ( ! bShowMsg ) {
				if ( NoticeMessage( MSG_CARINV_DELETE_CONFIRM,
				                    _("Yes"), _("No"), selcnt ) <= 0 ) {
					return;
				}
				bShowMsg = TRUE;
			}
			LOG( log_carDlgDims, 1, ( "CarInvDlgDeleteShelve( %d, %s\n", inx,
			                          item->title ) );
			wListDelete( (wList_p)carInvPLs[I_CI_LIST].control, inx );
			if ( item->title ) { MyFree( item->title ); }
			if ( item->data.number ) { MyFree( item->data.number ); }
			MyFree( item );
			for ( inx1=inx; inx1<carItemInfo_da.cnt-1; inx1++ ) {
				carItemInfo(inx1) = carItemInfo(inx1+1);
			}
			carItemInfo_da.cnt -= 1;
			inx--;
			cnt--;
			bNeedReload = TRUE; //DB
		}
	}
	if ( bNeedReload ) {
		CarInvListLoad();
		ChangeHotBar( CHANGE_SCALE );
		MainRedraw(); // Shelve Car from layout
	}
	SetFileChanged();
	carInvInx = -1;
	ParamLoadControl( &carInvPG, I_CI_LIST );
	ParamControlActive( &carInvPG, I_CI_EDIT, FALSE );
	ParamControlActive( &carInvPG, I_CI_DELETE, FALSE );
	wButtonSetLabel( (wButton_p)(carInvPLs[I_CI_DELETE].control), "" );
	ParamControlActive( &carInvPG, I_CI_EXPORT_CSV, carItemInfo_da.cnt > 0 );
	ParamDialogOkActive( &carInvPG, FALSE );
}


static int CarInvSaveText(
        int files,
        char ** fileName,
        void * data )
{
	FILE * f;
	carItem_p item;
	int inx;
	int widths[9], width;
	tabString_t tabs[7];
	char * cp0, * cp1;
	int len;

	CHECK( fileName != NULL );
	CHECK( files == 1 );

	SetCurrentPath( CARSPATHKEY, fileName[0] );
	f = fopen( fileName[0], "w" );
	if ( f == NULL ) {
		NoticeMessage( MSG_OPEN_FAIL, _("Continue"), NULL, _("Car Inventory"),
		               fileName[0], strerror(errno) );
		return FALSE;
	}

	memset( widths, 0, sizeof widths );
	for ( inx=0; inx<carItemInfo_da.cnt; inx++ ) {
		item = carItemInfo(inx);
		TabStringExtract( item->title, 7, tabs );
		sprintf( message, "%ld", item->index );
		width = (int)strlen( message );
		if ( width > widths[0] ) { widths[0] = width; }
		width = (int)strlen(GetScaleName(item->scaleInx)) + 1 + tabs[T_MANUF].len + 1 +
		        tabs[T_PART].len;
		if ( width > widths[1] ) { widths[1] = width; }
		if ( tabs[T_PROTO].len > widths[2] ) { widths[2] = tabs[T_PROTO].len; }
		width = tabs[T_REPMARK].len + tabs[T_NUMBER].len;
		if ( tabs[T_REPMARK].len > 0 && tabs[T_NUMBER].len > 0 ) {
			width += 1;
		}
		if ( width > widths[3] ) { widths[3] = width; }
		if ( item->data.purchDate > 0 ) { widths[4] = 8; }
		if ( item->data.purchPrice > 0 ) {
			sprintf( message, "%0.2f", item->data.purchPrice );
			width = (int)strlen(message);
			if ( width > widths[5] ) { widths[5] = width; }
		}
		if ( item->data.condition != 0 ) {
			widths[6] = 5;
		}
		if ( item->data.currPrice > 0 ) {
			sprintf( message, "%0.2f", item->data.currPrice );
			width = (int)strlen(message);
			if ( width > widths[7] ) { widths[7] = width; }
		}
		if ( item->data.serviceDate > 0 ) { widths[8] = 8; }
	}
	fprintf( f, "%-*.*s %-*.*s %-*.*s %-*.*s", widths[0], widths[0], "#", widths[1],
	         widths[1], "Part", widths[2], widths[2], "Description", widths[3], widths[3],
	         "Rep Mark" );
	if ( widths[4] ) { fprintf( f, " %-*.*s", widths[4], widths[4], "PurDate" ); }
	if ( widths[5] ) { fprintf( f, " %-*.*s", widths[5], widths[5], "PurPrice" ); }
	if ( widths[6] ) { fprintf( f, " %-*.*s", widths[6], widths[6], "Cond" ); }
	if ( widths[7] ) { fprintf( f, " %-*.*s", widths[7], widths[7], "CurPrice" ); }
	if ( widths[8] ) { fprintf( f, " %-*.*s", widths[8], widths[8], "SrvDate" ); }
	fprintf( f, "\n" );

	for ( inx=0; inx<carItemInfo_da.cnt; inx++ ) {
		item = carItemInfo(inx);
		TabStringExtract( item->title, 7, tabs );
		sprintf( message, "%ld", item->index );
		fprintf( f, "%.*s", widths[0], message );
		width = tabs[T_MANUF].len + 1 + tabs[T_PART].len;
		sprintf( message, "%s %.*s %.*s", GetScaleName(item->scaleInx),
		         tabs[T_MANUF].len, tabs[T_MANUF].ptr, tabs[T_PART].len, tabs[T_PART].ptr );
		fprintf( f, " %-*s", widths[1], message );
		fprintf( f, " %-*.*s", widths[2], tabs[T_PROTO].len, tabs[T_PROTO].ptr );
		width = tabs[T_REPMARK].len + tabs[T_NUMBER].len;
		sprintf( message, "%.*s%s%.*s", tabs[T_REPMARK].len, tabs[T_REPMARK].ptr,
		         (tabs[T_REPMARK].len > 0
		          && tabs[T_NUMBER].len > 0)?" ":"", tabs[T_NUMBER].len, tabs[T_NUMBER].ptr );
		fprintf( f, " %-*s", widths[3], message );
		if ( widths[4] > 0 ) {
			if ( item->data.purchDate > 0 ) {
				sprintf( message, "%ld", item->data.purchDate );
				fprintf( f, " %*.*s", widths[4], widths[4], message );
			} else {
				fprintf( f, " %*s", widths[4], " " );
			}
		}
		if ( widths[5] > 0 ) {
			if ( item->data.purchPrice > 0 ) {
				sprintf( message, "%0.2f", item->data.purchPrice );
				fprintf( f, " %*.*s", widths[5], widths[5], message );
			} else {
				fprintf( f, " %*s", widths[5], " " );
			}
		}
		if ( widths[6] > 0 ) {
			if ( item->data.condition != 0 ) {
				fprintf( f, " %-*.*s", widths[6], widths[6],
				         condListMap[MapCondition(item->data.condition)].name );
			} else {
				fprintf( f, " %*s", widths[6], " " );
			}
		}
		if ( widths[7] > 0 ) {
			if ( item->data.purchPrice > 0 ) {
				sprintf( message, "%0.2f", item->data.purchPrice );
				fprintf( f, " %*.*s", widths[7], widths[7], message );
			} else {
				fprintf( f, " %*s", widths[7], " " );
			}
		}
		if ( widths[8] > 0 ) {
			if ( item->data.serviceDate > 0 ) {
				sprintf( message, "%ld", item->data.serviceDate );
				fprintf( f, " %*.*s", widths[8], widths[8], message );
			} else {
				fprintf( f, " %*s", widths[8], " " );
			}
		}
		fprintf( f, "\n" );
		if ( item->data.notes ) {
			cp0 = item->data.notes;
			while ( 1 ) {
				cp1 = strchr( cp0, '\n' );
				if ( cp1 ) {
					len = (int)(cp1-cp0);
				} else {
					len = (int)strlen( cp0 );
					if ( len == 0 ) {
						break;
					}
				}
				fprintf( f, "%*.*s %*.*s\n", widths[0], widths[0], " ", len, len, cp0 );
				if ( cp1 == NULL ) {
					break;
				}
				cp0 = cp1+1;
			}
		}
	}
	fclose( f );
	return TRUE;
}


static struct wFilSel_t * carInvSaveText_fs;
static void CarInvDlgSaveText( void )
{
	if ( carInvSaveText_fs == NULL )
		carInvSaveText_fs = wFilSelCreate( mainW, FS_SAVE, 0, _("List Cars"),
		                                   "Text (*.txt)|*.txt", CarInvSaveText, NULL );
	wFilSelect( carInvSaveText_fs, GetCurrentPath(CARSPATHKEY));
}


static char *carCsvColumnTitles[] = {
	"Index", "Scale", "Manufacturer", "Type", "Partno", "Prototype",
	"Description", "Roadname", "Repmark", "Number", "Options", "CarLength",
	"CarWidth", "CoupledLength", "TruckOffset", "TruckCenter", "Color", "PurchPrice",
	"CurrPrice", "Condition", "PurchDate", "ServiceDate", "Notes"
};
#define M_INDEX			(0)
#define M_SCALE			(1)
#define M_MANUF			(2)
#define M_TYPE			(3)
#define M_PARTNO		(4)
#define M_PROTO			(5)
#define M_DESC			(6)
#define M_ROADNAME		(7)
#define M_REPMARK		(8)
#define M_NUMBER		(9)
#define M_OPTIONS		(10)
#define M_CARLENGTH		(11)
#define M_CARWIDTH		(12)
#define M_CPLDLENGTH	(13)
#define M_TRKOFFSET     (14)
#define M_TRKCENTER		(15)
#define M_COLOR			(16)
#define M_PURCHPRICE	(17)
#define M_CURRPRICE		(18)
#define M_CONDITION		(19)
#define M_PURCHDATE		(20)
#define M_SRVDATE		(21)
#define M_NOTES			(22)



static int ParseCsvLine(
        char * line,
        int max_elem,
        tabString_t * tabs,
        int * map )
{
	int elem = 0;
	char * cp, * cq, * ptr;
	int rc, len;

	cp = line;
	for ( cq=cp+strlen(cp)-1; cq>cp&&isspace((unsigned char)*cq); cq-- );
	cq[1] = '\0';
	for ( elem=0; elem<max_elem; elem++ ) {
		tabs[elem].ptr = "";
		tabs[elem].len = 0;
	}
	elem = 0;
	while ( *cp && elem < max_elem ) {
		while ( *cp == ' ' ) { cp++; }
		if ( *cp == ',' ) {
			ptr = "";
			len = 0;
		} else if ( *cp == '"' ) {
			cp++;
			ptr = cq = cp;
			while (1) {
				while ( *cp!='"' ) {
					if ( *cp == '\0' ) {
						rc = NoticeMessage( MSG_CARIMP_EOL, _("Continue"), _("Stop"), ptr );
						return (rc<1)?-1:elem;
					}
					*cq++ = *cp++;
				}
				cp++;
				if ( *cp!='"' ) { break; }
				*cq++ = *cp++;
			}
			if ( *cp && *cp != ',' ) {
				rc = NoticeMessage( MSG_CARIMP_MISSING_COMMA, _("Continue"), _("Stop"), ptr );
				return (rc<1)?-1:elem;
			}
			len = (int)(cq-ptr);
		} else {
			ptr = cp;
			while ( *cp && *cp != ',' ) { cp++; }
			len = (int)(cp-ptr);
		}
		if ( map[elem] >= 0 ) {
			tabs[map[elem]].ptr = ptr;
			tabs[map[elem]].len = len;
		}
		if ( *cp ) { cp++; }
		elem++;
	}
	return elem;
}


static int CarInvImportCsv(
        int files,
        char **fileName,
        void * data )
{
	FILE * f;
	carItem_p item;
	tabString_t tabs[40], partTabs[7];
	int map[40];
	int i, j, cnt, numCol, len, rc;
	char * cp, * cq;
	long type = 0;
	char title[STR_LONG_SIZE];
	long index, options, color, condition, purchDate, srvcDate;
	carDim_t dim;
	FLOAT_T purchPrice, currPrice;
	int duplicateIndexError = 0;
	SCALEINX_T scale;
	carPart_p partP;
	int requiredCols;

	CHECK( fileName != NULL );
	CHECK( files == 1 );

	SetCurrentPath( CARSPATHKEY, fileName[0] );
	f = fopen( fileName[0], "r" );
	if ( f == NULL ) {
		NoticeMessage( MSG_OPEN_FAIL, _("Continue"), NULL, _("Import Cars"),
		               fileName[0], strerror(errno) );
		return FALSE;
	}

	SetCLocale();

	if ( fgets( message, sizeof message, f ) == NULL ) {
		NoticeMessage( MSG_CARIMP_NO_DATA, _("Continue"), NULL );
		fclose( f );
		SetUserLocale();
		return FALSE;
	}
	for ( j=0; j<40; j++ ) { map[j] = j; }
	numCol = ParseCsvLine( message, 40, tabs, map );
	if ( numCol <= 0 ) {
		fclose( f );
		SetUserLocale();
		return FALSE;
	}
	for ( j=0; j<40; j++ ) { map[j] = -1; }
	requiredCols = 0;
	for ( i=0; i<numCol; i++ ) {
		for ( j=0; j<COUNT( carCsvColumnTitles ); j++ ) {
			if ( TabStringCmp( carCsvColumnTitles[j], &tabs[i] ) == 0 ) {
				if ( map[i] >= 0 ) {
					NoticeMessage( MSG_CARIMP_DUP_COLUMNS, _("Continue"), NULL,
					               carCsvColumnTitles[j] );
					fclose( f );
					SetUserLocale();
					return FALSE;
				}
				map[i] = j;
				/*j = COUNT( carCsvColumnTitles );*/
				if ( j == M_SCALE || j == M_PROTO || j == M_MANUF || j == M_PARTNO ) {
					requiredCols++;
				}
			}
		}
		if ( map[i] == -1 ) {
			tabs[i].ptr[tabs[i].len] = '\0';
			NoticeMessage( MSG_CARIMP_IGNORED_COLUMN, _("Continue"), NULL, tabs[i].ptr );
			tabs[i].ptr[tabs[i].len] = ',';
		}
	}
	if ( requiredCols != 4 ) {
		NoticeMessage( MSG_CARIMP_MISSING_COLUMNS, _("Continue"), NULL );
		fclose( f );
		SetUserLocale();
		return FALSE;
	}
	while ( fgets( message, sizeof message, f ) != NULL ) {
		cnt = ParseCsvLine( message, 40, tabs, map );
		if ( cnt > numCol ) { cnt = numCol; }
		tabs[M_SCALE].ptr[tabs[M_SCALE].len] = '\0';
		scale = LookupScale( tabs[M_SCALE].ptr );
		tabs[M_SCALE].ptr[tabs[M_SCALE].len] = ',';
		index = TabGetLong( &tabs[M_INDEX] );
		if ( index == 0 ) {
			CheckCarDlgItemIndex( &carDlgItemIndex );
			index = carDlgItemIndex;
		} else {
			carDlgItemIndex = index;
			if ( !CheckCarDlgItemIndex(&index) ) {
				if ( !duplicateIndexError ) {
					NoticeMessage( MSG_CARIMP_DUP_INDEX, _("Ok"), NULL );
					duplicateIndexError++;
				}
				carDlgItemIndex = index;
			}
		}
#ifdef OBSOLETE
		if ( TabStringCmp( "Unknown", &tabs[M_MANUF] ) != 0 &&
		     TabStringCmp( "Custom", &tabs[M_MANUF] ) != 0 ) {
			if ( tabs[M_PARTNO].len == 0 ) {
				rc = NoticeMessage( MSG_CARIMP_MISSING_PARTNO, _("Continue"), _("Stop"),
				                    tabs[M_MANUF].ptr );
				if ( rc <= 0 ) {
					fclose( f );
					SetUserLocale();
					return FALSE;
				}
				continue;
			}
		}
#endif
		dim.carLength = TabGetFloat( &tabs[M_CARLENGTH] );
		dim.carWidth = TabGetFloat( &tabs[M_CARWIDTH] );
		dim.coupledLength = TabGetFloat( &tabs[M_CPLDLENGTH] );
		dim.truckCenter = TabGetFloat( &tabs[M_TRKCENTER] );
		dim.truckCenterOffset = TabGetFloat( &tabs[M_TRKOFFSET] );
		partP = NULL;
		if ( tabs[M_MANUF].len > 0 && tabs[M_PARTNO].len > 0 ) {
			partP = CarPartFind( tabs[M_MANUF].ptr, tabs[M_MANUF].len, tabs[M_PARTNO].ptr,
			                     tabs[M_PARTNO].len, scale );
		}
		if ( partP ) {
			TabStringExtract( partP->title, 7, partTabs );
			if ( tabs[M_PROTO].len == 0 && partTabs[T_PROTO].len > 0 ) { tabs[M_PROTO].ptr = partTabs[T_PROTO].ptr; tabs[M_PROTO].len = partTabs[T_PROTO].len; }
			if ( tabs[M_DESC].len == 0 && partTabs[T_DESC].len > 0 ) { tabs[M_DESC].ptr = partTabs[T_DESC].ptr; tabs[M_DESC].len = partTabs[T_DESC].len; }
			if ( tabs[M_ROADNAME].len == 0 && partTabs[T_ROADNAME].len > 0 ) { tabs[M_ROADNAME].ptr = partTabs[T_ROADNAME].ptr; tabs[M_ROADNAME].len = partTabs[T_ROADNAME].len; }
			if ( tabs[M_REPMARK].len == 0 && partTabs[T_REPMARK].len > 0 ) { tabs[M_REPMARK].ptr = partTabs[T_REPMARK].ptr; tabs[M_REPMARK].len = partTabs[T_REPMARK].len; }
			if ( tabs[M_NUMBER].len == 0 && partTabs[T_NUMBER].len > 0 ) { tabs[M_NUMBER].ptr = partTabs[T_NUMBER].ptr; tabs[M_NUMBER].len = partTabs[T_NUMBER].len; }
			if ( dim.carLength <= 0 ) { dim.carLength = partP->dim.carLength; }
			if ( dim.carWidth <= 0 ) { dim.carWidth = partP->dim.carWidth; }
			if ( dim.coupledLength <= 0 ) { dim.coupledLength = partP->dim.coupledLength; }
			if ( dim.truckCenter <= 0 ) { dim.truckCenter = partP->dim.truckCenter; }
			if ( dim.truckCenterOffset < 0 ) { dim.truckCenterOffset = partP->dim.truckCenterOffset; }
		}
		if (dim.truckCenterOffset <0) { dim.truckCenterOffset  = 0; }
		cp = TabStringCpy( title, &tabs[M_MANUF] );
		*cp++ = '\t';
		cp = TabStringCpy( cp, &tabs[M_PROTO] );
		*cp++ = '\t';
		cp = TabStringCpy( cp, &tabs[M_DESC] );
		*cp++ = '\t';
		cp = TabStringCpy( cp, &tabs[M_PARTNO] );
		*cp++ = '\t';
		cp = TabStringCpy( cp, &tabs[M_ROADNAME] );
		*cp++ = '\t';
		cp = TabStringCpy( cp, &tabs[M_REPMARK] );
		*cp++ = '\t';
		cp = TabStringCpy( cp, &tabs[M_NUMBER] );
		*cp = '\0';
		options = TabGetLong( &tabs[M_OPTIONS] );
		type = TabGetLong( &tabs[M_TYPE] );
		color = TabGetLong( &tabs[M_COLOR] );
		purchPrice = TabGetFloat( &tabs[M_PURCHPRICE] );
		currPrice = TabGetFloat( &tabs[M_CURRPRICE] );
		condition = TabGetLong( &tabs[M_CONDITION] );
		purchDate = TabGetLong( &tabs[M_PURCHDATE] );
		srvcDate = TabGetLong( &tabs[M_SRVDATE] );
		if ( dim.carLength <= 0 || dim.carWidth <= 0 || dim.coupledLength <= 0
		     || dim.truckCenter <= 0 ) {
			rc = NoticeMessage( MSG_CARIMP_MISSING_DIMS, _("Yes"), _("No"), message );
			if ( rc <= 0 ) {
				fclose( f );
				SetUserLocale();
				return FALSE;
			}
			continue;
		}
		item = CarItemNew( NULL, PARAM_CUSTOM, index, scale, title, options, type,
		                   &dim, wDrawFindColor(color),
		                   purchPrice, currPrice, condition, purchDate, srvcDate );
		if ( tabs[M_NOTES].len > 0 ) {
			item->data.notes = cp = MyMalloc( (tabs[M_NOTES].len+2) );
			for ( cq=tabs[M_NOTES].ptr,len=tabs[M_NOTES].len; *cq&&len; ) {
				if ( strncmp( cq, "<NL>", 4 ) == 0 ) {
					*cp++ = '\n';
					cq += 4;
					len -= 4;
				} else {
					*cp++ = *cq++;
					len -= 1;
				}
			}
		}
		SetFileChanged();
	}
	fclose( f );
	SetUserLocale();
	CarInvListLoad();
	return TRUE;
}



static struct wFilSel_t * carInvImportCsv_fs;
static void CarInvDlgImportCsv( void )
{
	if ( carInvImportCsv_fs == NULL )
		carInvImportCsv_fs = wFilSelCreate( mainW, FS_LOAD, 0, _("Import Cars"),
		                                    _("Comma-Separated-Values (*.csv)|*.csv"), CarInvImportCsv, NULL );
	wFilSelect( carInvImportCsv_fs, GetCurrentPath(CARSPATHKEY));
}


static void CsvFormatString(
        FILE * f,
        char * str,
        int len,
        char * sep )
{
	while ( str && len>0 && str[len-1]=='\n' ) { len--; }
	if ( *str && len ) {
		fputc( '"', f );
		for ( ; *str && len; str++,len-- ) {
			if ( !iscntrl((unsigned char) *str ) ) {
				if ( *str == '"' ) {
					fputc( '"', f );
				}
				fputc( *str, f );
			} else if ( *str == '\n' && str[1] && len > 1 ) {
				fprintf( f, "<NL>" );
			}
		}
		fputc( '"', f );
	}
	fprintf( f, "%s", sep );
}


static void CsvFormatLong(
        FILE * f,
        long val,
        char * sep )
{
	if ( val != 0 ) {
		fprintf( f, "%ld", val );
	}
	fprintf( f, "%s", sep );
}


static void CsvFormatFloat(
        FILE * f,
        FLOAT_T val,
        int digits,
        char * sep )
{
	if ( val != 0.0 ) {
		fprintf( f, "%0.*f", digits, val );
	}
	fprintf( f, "%s", sep );
}


static int CarInvExportCsv(
        int files,
        char ** fileName,
        void * data )
{
	FILE * f;
	carItem_p item;
	long inx;
	tabString_t tabs[7];
	char * sp;

	CHECK( fileName != NULL );
	CHECK( files == 1 );
	SetCurrentPath( CARSPATHKEY, fileName[0] );

	f = fopen( fileName[0], "w" );
	if ( f == NULL ) {
		NoticeMessage( MSG_OPEN_FAIL, _("Continue"), NULL, _("Export Cars"),
		               fileName[0], strerror(errno) );
		return FALSE;
	}

	SetCLocale();

	for ( inx=0; inx<COUNT( carCsvColumnTitles ); inx++ ) {
		CsvFormatString( f, carCsvColumnTitles[inx],
		                 (int)strlen(carCsvColumnTitles[inx]),
		                 inx<(COUNT( carCsvColumnTitles ))-1?",":"\n" );
	}
	for ( inx=0; inx<carItemInfo_da.cnt; inx++ ) {
		item = carItemInfo( inx );
		TabStringExtract( item->title, 7, tabs );
		CsvFormatLong( f, item->index, "," );
		sp = GetScaleName(item->scaleInx);
		CsvFormatString( f, sp, (int)strlen(sp), "," );
		CsvFormatString( f, tabs[T_MANUF].ptr, tabs[T_MANUF].len, "," );
		CsvFormatLong( f, item->type, "," );
		CsvFormatString( f, tabs[T_PART].ptr, tabs[T_PART].len, "," );
		CsvFormatString( f, tabs[T_PROTO].ptr, tabs[T_PROTO].len, "," );
		CsvFormatString( f, tabs[T_DESC].ptr, tabs[T_DESC].len, "," );
		CsvFormatString( f, tabs[T_ROADNAME].ptr, tabs[T_ROADNAME].len, "," );
		CsvFormatString( f, tabs[T_REPMARK].ptr, tabs[T_REPMARK].len, "," );
		CsvFormatString( f, tabs[T_NUMBER].ptr, tabs[T_NUMBER].len, "," );
		CsvFormatLong( f, item->options, "," );
		CsvFormatFloat( f, item->dim.carLength, 3, "," );
		CsvFormatFloat( f, item->dim.carWidth, 3, "," );
		CsvFormatFloat( f, item->dim.coupledLength, 3, "," );
		CsvFormatFloat( f, item->dim.truckCenterOffset, 3, ",");
		CsvFormatFloat( f, item->dim.truckCenter, 3, "," );
		CsvFormatLong( f, wDrawGetRGB(item->color), "," );
		CsvFormatFloat( f, item->data.purchPrice, 2, "," );
		CsvFormatFloat( f, item->data.currPrice, 2, "," );
		CsvFormatLong( f, item->data.condition, "," );
		CsvFormatLong( f, item->data.purchDate, "," );
		CsvFormatLong( f, item->data.serviceDate, "," );
		if ( item->data.notes ) {
			CsvFormatString( f, item->data.notes, (int)strlen(item->data.notes), "\n" );
		} else {
			CsvFormatString( f, "", (int)strlen(""), "\n" );
		}
	}
	fclose( f );
	SetUserLocale();
	return TRUE;
}


static struct wFilSel_t * carInvExportCsv_fs;
static void CarInvDlgExportCsv( void )
{
	if ( carItemInfo_da.cnt <= 0 ) {
		return;
	}
	if ( carInvExportCsv_fs == NULL )
		carInvExportCsv_fs = wFilSelCreate( mainW, FS_SAVE, 0, _("Export Cars"),
		                                    _("Comma-Separated-Values (*.csv)|*.csv"), CarInvExportCsv, NULL );
	wFilSelect( carInvExportCsv_fs, GetCurrentPath(CARSPATHKEY));
}


static void CarInvLoadItem(
        carItem_p item )
{
	/* "Index", "Scale", "Manufacturer", "Type", "Part No", "Description", "Roadname", "RepMarks",
	   "Purch Price", "Curr Price", "Condition", "Purch Date", "Service Date", "Location", "Notes" */
	char *condition;
	char *location;
	char *manuf;
	char *road;
	char notes[100];
	tabString_t tabs[7];

	TabStringExtract( item->title, 7, tabs );
	if ( item->data.notes ) {
		strncpy( notes, item->data.notes, sizeof notes - 1 );
		notes[sizeof notes - 1] = '\0';
	} else {
		notes[0] = '\0';
	}
	condition =
	        (item->data.condition < 10) ? N_("N/A"):
	        (item->data.condition < 30) ? N_("Poor"):
	        (item->data.condition < 50) ? N_("Fair"):
	        (item->data.condition < 70) ? N_("Good"):
	        (item->data.condition < 90) ? N_("Excellent"):
	        N_("Mint");

	char carLocation[30];
	if ( item->car && !IsTrackDeleted(item->car) ) {
		coOrd hi, lo;
		GetBoundingBox( item->car, &hi, &lo );
		snprintf( carLocation, sizeof carLocation, "%0.0fx%0.0f",
		          PutDim((lo.x+hi.x)/2.0),
		          PutDim((lo.y+hi.y)/2.0) );
		location = carLocation;
	} else {
		location = N_("Shelf");
	}

	manuf = TabStringDup(&tabs[T_MANUF]);
	road = TabStringDup(&tabs[T_ROADNAME]);
	sprintf( message,
	         "%ld\t%s\t%s\t%.*s\t%s\t%.*s%s%.*s\t%s\t%.*s%s%.*s\t%0.2f\t%0.2f\t%s\t%ld\t%ld\t%s\t%s",
	         item->index, GetScaleName(item->scaleInx),
	         _(manuf),
	         tabs[T_PART].len, tabs[T_PART].ptr,
	         _(typeListMap[CarProtoFindTypeCode(item->type)].name),
	         tabs[T_PROTO].len, tabs[T_PROTO].ptr,
	         (tabs[T_PROTO].len>0 && tabs[T_DESC].len)?"/":"",
	         tabs[T_DESC].len, tabs[T_DESC].ptr,
	         _(road),
	         tabs[T_REPMARK].len, tabs[T_REPMARK].ptr,
	         (tabs[T_REPMARK].len>0&&tabs[T_NUMBER].len>0)?" ":"",
	         tabs[T_NUMBER].len, tabs[T_NUMBER].ptr,
	         item->data.purchPrice, item->data.currPrice, _(condition), item->data.purchDate,
	         item->data.serviceDate, _(location), notes );
	if (manuf) { MyFree(manuf); }
	if (road) { MyFree(road); }
	wListAddValue( (wList_p)carInvPLs[I_CI_LIST].control, message, NULL, item );
}


static int Cmp_carInvItem(
        const void * ptr1,
        const void * ptr2 )
{
	carItem_p item1 = *(carItem_p*)ptr1;
	carItem_p item2 = *(carItem_p*)ptr2;
	tabString_t tabs1[7], tabs2[7];
	int inx;
	int rc;

	TabStringExtract( item1->title, 7, tabs1 );
	TabStringExtract( item2->title, 7, tabs2 );
	for ( inx=0,rc=0; inx<N_SORT&&rc==0; inx++ ) {
		switch ( carInvSort[inx] ) {
		case S_INDEX:
			rc = (int)(item1->index-item2->index);
			break;
		case S_SCALE:
			rc = (int)(item1->scaleInx-item2->scaleInx);
		case S_MANUF:
			rc = strncasecmp( tabs1[T_MANUF].ptr, tabs2[T_MANUF].ptr,
			                  max(tabs1[T_MANUF].len,tabs2[T_MANUF].len) );
			break;
		case S_TYPE:
			rc = (int)(item1->type-item2->type);
			break;
		case S_PARTNO:
			rc = strncasecmp( tabs1[T_PART].ptr, tabs2[T_PART].ptr, max(tabs1[T_PART].len,
			                  tabs2[T_PART].len) );
			break;
		case S_DESC:
			rc = strncasecmp( tabs1[T_PROTO].ptr, tabs2[T_PROTO].ptr,
			                  max(tabs1[T_PROTO].len,tabs2[T_PROTO].len) );
			if ( rc != 0 ) {
				break;
			}
			rc = strncasecmp( tabs1[T_DESC].ptr, tabs2[T_DESC].ptr, max(tabs1[T_DESC].len,
			                  tabs2[T_DESC].len) );
			break;
		case S_ROADNAME:
			rc = strncasecmp( tabs1[T_ROADNAME].ptr, tabs2[T_ROADNAME].ptr,
			                  max(tabs1[T_ROADNAME].len,tabs2[T_ROADNAME].len) );
			break;
		case S_REPMARKS:
			rc = strncasecmp( tabs1[T_REPMARK].ptr, tabs2[T_REPMARK].ptr,
			                  max(tabs1[T_REPMARK].len,tabs2[T_REPMARK].len) );
			break;
		case S_PURCHPRICE:
			rc = (int)(item1->data.purchPrice-item2->data.purchPrice);
			break;
		case S_CURRPRICE:
			rc = (int)(item1->data.currPrice-item2->data.currPrice);
			break;
		case S_CONDITION:
			rc = (int)(item1->data.condition-item2->data.condition);
			break;
		case S_PURCHDATE:
			rc = (int)(item1->data.purchDate-item2->data.purchDate);
			break;
		case S_SRVDATE:
			rc = (int)(item1->data.serviceDate-item2->data.serviceDate);
			break;
		default:
			break;
		}
	}
	return rc;
}

static void CarInvListLoad( void )
{
	int inx;
	carItem_p item;

	qsort( &carItemInfo(0), carItemInfo_da.cnt, sizeof carItemInfo(0),
	       Cmp_carInvItem );
	ParamControlShow( &carInvPG, I_CI_LIST, FALSE );
	wListClear( (wList_p)carInvPLs[I_CI_LIST].control );
	for ( inx=0; inx<carItemInfo_da.cnt; inx++ ) {
		item = carItemInfo(inx);
		CarInvLoadItem( item );
	}
	ParamControlShow( &carInvPG, I_CI_LIST, TRUE );
	ParamControlActive( &carInvPG, I_CI_EDIT, FALSE );
	ParamControlActive( &carInvPG, I_CI_DELETE, FALSE );
	wButtonSetLabel( (wButton_p)(carInvPLs[I_CI_DELETE].control), "" );
	ParamControlActive( &carInvPG, I_CI_EXPORT_CSV, carItemInfo_da.cnt > 0 );
	ParamDialogOkActive( &carInvPG, FALSE );
}


static void CarInvDlgUpdate(
        paramGroup_p pg,
        int inx,
        void * valueP )
{
	carItem_p item = NULL;
	wIndex_t cnt, selinx, selcnt;

	if ( inx >= I_CI_SORT && inx < I_CI_SORT+N_SORT ) {
		item = CarInvDlgFindCurrentItem();
		CarInvListLoad();
		if ( item ) {
			carInvInx = (wIndex_t)CarItemFindIndex( item );
			if ( carInvInx >= 0 ) {
				ParamLoadControl( &carInvPG, I_CI_LIST );
			}
		}
	} else if ( inx == I_CI_LIST ) {
		cnt = wListGetCount( (wList_p)carInvPLs[I_CI_LIST].control );
		wIndex_t nOnShelf = 0;
		wIndex_t nOnLayout = 0;
		for ( selinx=selcnt=0; selinx<cnt; selinx++ ) {
			if ( wListGetItemSelected( (wList_p)carInvPLs[I_CI_LIST].control, selinx ) ) {
				selcnt++;
				item = (carItem_p)wListGetItemContext( (wList_p)carInvPLs[I_CI_LIST].control,
				                                       selinx );
				if ( ! item ) { continue; }
				if ( item->car && !IsTrackDeleted( item->car ) ) {
					nOnLayout++;
				} else {
					nOnShelf++;
				}
			}
		}
		// Enable Find if 1 selected car is on Layout
		ParamDialogOkActive( pg, nOnLayout == 1 && nOnShelf == 0 );
		// Enable Edit if 1 selected car is on Shelf
		ParamControlActive( &carInvPG, I_CI_EDIT, nOnLayout == 0 && nOnShelf == 1 );
		wBool_t bEnableDelete = nOnLayout+nOnShelf > 0 &&
		                        ( nOnLayout == 0 || nOnShelf == 0 );
		wButtonSetLabel( (wButton_p)(carInvPLs[I_CI_DELETE].control),
		                 bEnableDelete == FALSE ? "" :
		                 nOnLayout > 0 ? _("Shelve") :
		                 _("Delete") );
		ParamControlActive( &carInvPG, I_CI_DELETE, bEnableDelete );
	}
}


static void CarInvListAdd(
        carItem_p item )
{
	CarInvListLoad();
	carInvInx = (wIndex_t)CarItemFindIndex( item );
	if ( carInvInx >= 0 ) {
		ParamLoadControl( &carInvPG, I_CI_LIST );
	}
}


static void CarInvListUpdate(
        carItem_p item )
{
	CarInvListLoad();
	carInvInx = (wIndex_t)CarItemFindIndex( item );
	if ( carInvInx >= 0 ) {
		ParamLoadControl( &carInvPG, I_CI_LIST );
	}
}


EXPORT void DoCarDlg( void * unused )
{
	int inx, inx2;
	if ( carInvPG.win == NULL ) {
		ParamCreateDialog( &carInvPG, MakeWindowTitle(_("Car Inventory")), _("Find"),
		                   CarInvDlgFind, ParamCancel_Current, TRUE, NULL,
		                   F_BLOCK|F_RESIZE|F_RECALLSIZE|PD_F_ALT_CANCELLABEL, CarInvDlgUpdate );
		for ( inx=I_CI_SORT; inx<I_CI_SORT+N_SORT; inx++ ) {
			for ( inx2=0; inx2<COUNT( sortOrders ); inx2++ ) {
				wListAddValue( (wList_p)carInvPLs[inx].control, _(sortOrders[inx2]), NULL,
				               NULL );
				ParamLoadControl( &carInvPG, inx );
			}
		}
		ParamDialogOkActive( &carInvPG, FALSE );
	}
	CarInvListLoad();
	wShow( carInvPG.win );
}


static void CarDlgChange( long changes )
{
	if ( (changes&CHANGE_SCALE) ) {
		carDlgCouplerLength = 0.0;
	}
}


EXPORT void ClearCars( void )
{
	int inx;
	for ( inx=0; inx<carItemInfo_da.cnt; inx++ ) {
		MyFree( carItemInfo(inx) );
	}
	DYNARR_FREE( carItem_t*, carItemInfo_da );
}


static struct {
	dynArr_t carProto_da;
	dynArr_t carPartParent_da;
	dynArr_t carItemInfo_da;
} savedCarState;

EXPORT void SaveCarState( void )
{
	savedCarState.carProto_da = carProto_da;
	savedCarState.carPartParent_da = carPartParent_da;
	savedCarState.carItemInfo_da = carItemInfo_da;
	DYNARR_INIT( carItem_t*, carItemInfo_da );
}


EXPORT void RestoreCarState( void )
{
#ifdef LATER
	carProto_da = savedCarState.carProto_da;
	carPartParent_da = savedCarState.carPartParent_da;
#endif
	carItemInfo_da = savedCarState.carItemInfo_da;
}



EXPORT void InitCarDlg( void )
{
	log_carList = LogFindIndex( "carList" );
	log_carInvList = LogFindIndex( "carInvList" );
	log_carDlgState = LogFindIndex( "carDlgState" );
	log_carDlgList = LogFindIndex( "carDlgList" );
	log_carDlgDims = LogFindIndex( "carDlgDims" );
	carDlgBodyColor = wDrawFindColor( wRGB(255,128,0) );
	ParamRegister( &carDlgPG );
	ParamRegister( &carInvPG );
	RegisterChangeNotification( CarDlgChange );
	AddParam( "CARPROTO ", CarProtoRead );
	AddParam( "CARPART ", CarPartRead);
	ParamRegister( &newCarPG );
	ParamCreateControls( &newCarPG, CarItemHotbarUpdate );
	newCarControls[0] = newCarPLs[0].control;
}

/*****************************************************************************
 *
 * Custom Management Support
 *
 */

static int CarPartCustMgmProc(
        int cmd,
        void * data )
{
	tabString_t tabs[7];
	int rd_inx;

	carPart_p partP = (carPart_p)data;
	switch ( cmd ) {
	case CUSTMGM_DO_COPYTO:
		return CarPartWrite( customMgmF, partP );
	case CUSTMGM_CAN_EDIT:
		return TRUE;
	case CUSTMGM_DO_EDIT:
		if ( partP == NULL ) {
			return FALSE;
		}
		carDlgUpdatePartPtr = partP;
		DoCarPartDlg( partUpdActions );
		return TRUE;
	case CUSTMGM_CAN_DELETE:
		return TRUE;
	case CUSTMGM_DO_DELETE:
		CarPartDelete( partP );
		return TRUE;
	case CUSTMGM_GET_TITLE:
		TabStringExtract( partP->title, 7, tabs );
		rd_inx = T_REPMARK;
		if ( tabs[T_REPMARK].len == 0 ) {
			rd_inx = T_ROADNAME;
		}
		sprintf( message, "\t%s\t%s\t%.*s\t%s%s%.*s%s%.*s%s%.*s",
		         partP->parent->manuf,
		         GetScaleName(partP->parent->scale),
		         tabs[T_PART].len, tabs[T_PART].ptr,
		         partP->parent->proto,
		         tabs[T_DESC].len?", ":"", tabs[T_DESC].len, tabs[T_DESC].ptr,
		         tabs[rd_inx].len?", ":"", tabs[rd_inx].len, tabs[rd_inx].ptr,
		         tabs[T_NUMBER].len?" ":"", tabs[T_NUMBER].len, tabs[T_NUMBER].ptr );
		return TRUE;
	}
	return FALSE;
}


static int CarProtoCustMgmProc(
        int cmd,
        void * data )
{
	carProto_p protoP = (carProto_p)data;
	switch ( cmd ) {
	case CUSTMGM_DO_COPYTO:
		return CarProtoWrite( customMgmF, protoP );
	case CUSTMGM_CAN_EDIT:
		return TRUE;
	case CUSTMGM_DO_EDIT:
		if ( protoP == NULL ) {
			return FALSE;
		}
		carDlgUpdateProtoPtr = protoP;
		DoCarPartDlg( protoUpdActions );
		return TRUE;
	case CUSTMGM_CAN_DELETE:
		return TRUE;
	case CUSTMGM_DO_DELETE:
		CarProtoDelete( protoP );
		return TRUE;
	case CUSTMGM_GET_TITLE:
		sprintf( message, "\t%s\t\t%s\t%s", _("Prototype"),
		         _(typeListMap[CarProtoFindTypeCode(protoP->type)].name), protoP->desc );
		return TRUE;
	}
	return FALSE;
}


#include "bitmaps/carpart.image1"
#include "bitmaps/carproto.image1"

EXPORT void CarCustMgmLoad( void )
{
	long parentX, partX, protoX;
	carPartParent_p parentP;
	carPart_p partP;
	carProto_p carProtoP;
	static wIcon_p carpartI = NULL;
	static wIcon_p carprotoI = NULL;

	if ( carpartI == NULL ) {
		carpartI = wIconCreatePixMap( carpart_image1 );
	}
	if ( carprotoI == NULL ) {
		carprotoI = wIconCreatePixMap( carproto_image1 );
	}

	for ( parentX=0; parentX<carPartParent_da.cnt; parentX++ ) {
		parentP = carPartParent(parentX);
		for ( partX=0; partX<parentP->parts_da.cnt; partX++ ) {
			partP = carPart(parentP,partX);
			if ( partP->paramFileIndex != PARAM_CUSTOM ) {
				continue;
			}
			CustMgmLoad( carpartI, CarPartCustMgmProc, partP );
		}
	}

	for ( protoX=0; protoX<carProto_da.cnt; protoX++ ) {
		carProtoP = carProto(protoX);
		if ( carProtoP->paramFileIndex != PARAM_CUSTOM ) {
			continue;
		}
		if (carProtoP->paramFileIndex == PARAM_CUSTOM) {
			CustMgmLoad( carprotoI, CarProtoCustMgmProc, carProtoP );
		}
	}
}
