
#if 0
/**********************************************************************
 *<
	FILE: boolctrl.cpp

	DESCRIPTION: A boolean controller

	CREATED BY: Rolf Berteig

	HISTORY: 11/18/96

 *>	Copyright (c) 1996, All Rights Reserved.
 **********************************************************************/
#include "ctrl.h"
#include "units.h"
#include "exprlib.h"

#define BOOL_CONTROL_CLASS_ID	Class_ID(0x984b8d27,0x938f3e43)
#define BOOL_CONTROL_CNAME		GetString(IDS_RB_BOOLCONTROL)

#define KEY_SELECTED	(1<<0)
#define KEY_FLAGGED		(1<<1)

#define MAKEBOOL(fl) ( (fl)<=0.0f ? FALSE : TRUE )

class BKey {
	public:
		TimeValue time;
		DWORD flags;

		BKey() {flags=0;}
		BKey(TimeValue t) {time=t; flags=0;}
	};

class BoolControl : public StdControl {
	public:				
		Tab<BKey> keys;
		Interval range, valid;
		BOOL onOff, startOnOff, rangeLinked;

		BoolControl();
				
		// Animatable methods		
		void DeleteThis() {delete this;}		
		int IsKeyable() {return 1;}
		BOOL IsAnimated() {return keys.Count?TRUE:FALSE;}
		Class_ID ClassID() {return BOOL_CONTROL_CLASS_ID;} 
		SClass_ID SuperClassID() {return CTRL_FLOAT_CLASS_ID;}
		void GetClassName(TSTR& s) {s = BOOL_CONTROL_CNAME;}

		// Reference methods
		RefResult NotifyRefChanged(Interval, RefTargetHandle, PartID&, RefMessage) {return REF_SUCCEED;}
		IOResult Save(ISave *isave);
		IOResult Load(ILoad *iload);
		RefTargetHandle Clone(RemapDir &remap = NoRemap());

		// Control methods				
		void Copy(Control *from);
		BOOL IsLeaf() {return TRUE;}
		void CommitValue(TimeValue t) {}
		void RestoreValue(TimeValue t) {}
		
		void HoldTrack();
		Interval GetTimeRange(DWORD flags);
		void EditTimeRange(Interval range,DWORD flags);
		void MapKeys(TimeMap *map,DWORD flags );
		
		int NumKeys() {return keys.Count();}
		TimeValue GetKeyTime(int index) {return keys[index].time;}
		int GetKeyIndex(TimeValue t);		
		void DeleteKeyAtTime(TimeValue t);
		BOOL IsKeyAtTime(TimeValue t,DWORD flags);		
		void DeleteTime(Interval iv, DWORD flags);
		void ReverseTime(Interval iv, DWORD flags);
		void ScaleTime(Interval iv, float s);
		void InsertTime(TimeValue ins, TimeValue amount);
		BOOL SupportTimeOperations() {return TRUE;}
		void DeleteKeys(DWORD flags);
		void DeleteKeyByIndex(int index);
		void SelectKeys(TrackHitTab& sel, DWORD flags);
		void SelectKeyByIndex(int i,BOOL sel);
		void FlagKey(TrackHitRecord hit);
		int GetFlagKeyIndex();
		int NumSelKeys();
		void CloneSelectedKeys(BOOL offset=FALSE);
		void AddNewKey(TimeValue t,DWORD flags);		
		BOOL IsKeySelected(int index);
		BOOL CanCopyTrack(Interval iv, DWORD flags) {return TRUE;}
		BOOL CanPasteTrack(TrackClipObject *cobj,Interval iv, DWORD flags) {return cobj->ClassID()==ClassID();}
		TrackClipObject *CopyTrack(Interval iv, DWORD flags);
		void PasteTrack(TrackClipObject *cobj,Interval iv, DWORD flags);
		int GetSelKeyCoords(TimeValue &t, float &val,DWORD flags);
		void SetSelKeyCoords(TimeValue t, float val,DWORD flags);
		int SetSelKeyCoordsExpr(ParamDimension *dim,TCHAR *timeExpr, TCHAR *valExpr, DWORD flags);

		int HitTestTrack(			
			TrackHitTab& hits,
			Rect& rcHit,
			Rect& rcTrack,			
			float zoom,
			int scroll,
			DWORD flags);
		int PaintTrack(
			ParamDimensionBase *dim,
			HDC hdc,
			Rect& rcTrack,
			Rect& rcPaint,
			float zoom,
			int scroll,
			DWORD flags);
		

		// StdControl methods
		void GetValueLocalTime(TimeValue t, void *val, Interval &valid, GetSetMethod method=CTRL_ABSOLUTE) {GetValue(t,val,valid,method);}
		void GetValue(TimeValue t, void *val, Interval &valid, GetSetMethod method=CTRL_ABSOLUTE);
		void SetValueLocalTime(TimeValue t, void *val, int commit, GetSetMethod method);
		void Extrapolate(Interval range,TimeValue t,void *val,Interval &valid,int type);		
		void *CreateTempValue() {return new float;}
		void DeleteTempValue(void *val) {delete (float*)val;}
		void ApplyValue(void *val, void *delta) {*((float*)val) += *((float*)delta);}
		void MultiplyValue(void *val, float m) {*((float*)val) *= m;}

		void SortKeys();
		
	};

class BoolClassDesc:public ClassDesc {
	public:
	int 			IsPublic() {return 1;}
	void *			Create(BOOL loading) {return new BoolControl();}
	const TCHAR *	ClassName() {return BOOL_CONTROL_CNAME;}
	SClass_ID		SuperClassID() {return CTRL_FLOAT_CLASS_ID; }
	Class_ID		ClassID() {return BOOL_CONTROL_CLASS_ID;}
	const TCHAR* 	Category() {return _T("");}
	};
static BoolClassDesc boolCD;
ClassDesc* GetBoolControlDesc() {return &boolCD;}



class BoolControlRestore : public RestoreObj {
	public:
		Tab<BKey> undo;
		Tab<BKey> redo;
		Interval urange, rrange;
		BoolControl *cont;

		BoolControlRestore(BoolControl *c) {
			cont   = c;
			undo   = c->keys;			
			urange = c->range;
			}
		
		void Restore(int isUndo) {
			if (isUndo) {
				redo   = cont->keys;
				rrange = cont->range;
				}
			cont->keys  = undo;
			cont->range = urange;
			cont->NotifyDependents(FOREVER,0,REFMSG_CHANGE);
			}

		void Redo() {
			cont->keys  = redo;
			cont->range = rrange;
			cont->NotifyDependents(FOREVER,0,REFMSG_CHANGE);
			}

		int Size() {return 1;}
		void EndHold() {cont->ClearAFlag(A_HELD);}
	};


class BoolClipObject : public TrackClipObject {
	public:
		Tab<BKey> tab;

		Class_ID ClassID() {return BOOL_CONTROL_CLASS_ID;}
		SClass_ID SuperClassID() { return CTRL_FLOAT_CLASS_ID; }
		void DeleteThis() {delete this;}

		BoolClipObject(Interval iv) : TrackClipObject(iv) {}
	};



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

BoolControl::BoolControl() 
	{
	
	//SPEW(("jerryeds","WHY THE FUCK IS BOOL BEING CALLED??? IT SHOULD NEVER BE CALLLED!"));

	//MessageBox(NULL, "WHY THE FUCK IS BOOL BEING CALLED??? IT SHOULD NEVER BE CALLLED!", "WTF?", MB_OK);

	startOnOff  = TRUE; 
	rangeLinked = TRUE;
	}

RefTargetHandle BoolControl::Clone(RemapDir &remap)
	{
	
	//SPEW(("jerryeds","WHY THE FUCK IS BOOL BEING CALLED??? IT SHOULD NEVER BE CALLLED!"));
	//MessageBox(NULL, "WHY THE FUCK IS BOOL BEING CALLED??? IT SHOULD NEVER BE CALLLED!", "WTF?", MB_OK);

	BoolControl *bc = new BoolControl;
	bc->range      = range;
	bc->valid      = valid;
	bc->keys       = keys;
	bc->onOff      = onOff;
	bc->startOnOff = startOnOff;
	CloneControl(bc,remap);
	return bc;
	}

#define KEYTIME_CHUNKID		0x0100
#define KEYFLAGS_CHUNKID	0x0110
#define NUMKEYS_CHUNKID		0x0130

IOResult BoolControl::Save(ISave *isave)
	{
	//MessageBox(NULL, "WHY THE FUCK IS BOOL BEING CALLED??? IT SHOULD NEVER BE CALLLED!", "WTF?", MB_OK);
	//SPEW(("jerryeds","WHY THE FUCK IS BOOL BEING CALLED??? IT SHOULD NEVER BE CALLLED!"));

	ULONG nb;
	int ct = keys.Count();
	
	// Save the number of keys
	isave->BeginChunk(NUMKEYS_CHUNKID);
	isave->Write(&ct,sizeof(ct),&nb);
	isave->EndChunk();

	// Write each key
	for (int i=0; i<ct; i++) {
		isave->BeginChunk(KEYTIME_CHUNKID);
		isave->Write(&keys[i].time,sizeof(keys[i].time),&nb);
		isave->EndChunk();

		isave->BeginChunk(KEYFLAGS_CHUNKID);
		isave->Write(&keys[i].flags,sizeof(keys[i].flags),&nb);
		isave->EndChunk();		
		}
	
	return IO_OK;
	}

IOResult BoolControl::Load(ILoad *iload)
	{
	//MessageBox(NULL, "WHY THE FUCK IS BOOL BEING CALLED??? IT SHOULD NEVER BE CALLLED!", "WTF?", MB_OK);
	//SPEW(("jerryeds","WHY THE FUCK IS BOOL BEING CALLED??? IT SHOULD NEVER BE CALLLED!"));

	ULONG nb;
	int ct;	
	IOResult res;
	
	if (IO_OK!=(res=iload->OpenChunk())) return res;
	assert(iload->CurChunkID()==NUMKEYS_CHUNKID);
	iload->Read(&ct,sizeof(ct),&nb);
	iload->CloseChunk();
	
	keys.SetCount(ct);

	for (int i=0; i<ct; i++) {
		iload->OpenChunk();
		assert(iload->CurChunkID()==KEYTIME_CHUNKID);
		iload->Read(&keys[i].time,sizeof(keys[i].time),&nb);
		iload->CloseChunk();
		
		iload->OpenChunk();
		assert(iload->CurChunkID()==KEYFLAGS_CHUNKID);
		iload->Read(&keys[i].flags,sizeof(keys[i].flags),&nb);
		iload->CloseChunk();		
		}
	
	



	return IO_OK;
	}

void BoolControl::Copy(Control *from)
	{	
	//MessageBox(NULL, "WHY THE FUCK IS BOOL BEING CALLED??? IT SHOULD NEVER BE CALLLED!", "WTF?", MB_OK);
	//SPEW(("jerryeds","WHY THE FUCK IS BOOL BEING CALLED??? IT SHOULD NEVER BE CALLLED!"));

	if (from) {
		if (from->SuperClassID()!=CTRL_FLOAT_CLASS_ID) return;
		TimeValue t;
		float s;
		Interval iv;
		TimeValue tpf = GetTicksPerFrame();
		Interval v = from->GetTimeRange(TIMERANGE_ALL);

		BOOL state = 1;
		TimeValue astart = GetAnimStart();
		if (v.Start()>astart) {
			from->GetValue(astart, &s, iv);
			if (s<=0.0f) {
				state = 0;
				AddNewKey(astart, 0);		
				}
			}

		for (t = v.Start(); t<=v.End(); t+=tpf) {
			from->GetValue(t, &s, iv);
			if (state) {
				if (s<=0.0f) {
					state = 0;
					AddNewKey(t, 0);		
					}
				}
			else {
				if (s>0.0f) {
					state = 1;
					AddNewKey(t, 0);		
					}
				}
			}
		}
	}

void BoolControl::HoldTrack()
	{
	if (theHold.Holding()&&!TestAFlag(A_HELD)) {		
		theHold.Put(new BoolControlRestore(this));
		SetAFlag(A_HELD);
		}
	}

void BoolControl::EditTimeRange(Interval range,DWORD flags)
	{
	HoldTrack();
	if (flags&EDITRANGE_LINKTOKEYS && keys.Count()) {
		this->range.Set(keys[0].time,keys[keys.Count()-1].time);
		rangeLinked = TRUE;
	} else {		
		rangeLinked = FALSE;
		this->range = range;		
		}
	valid.SetEmpty();
	NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
	}

Interval BoolControl::GetTimeRange(DWORD flags)
	{
	if (rangeLinked && keys.Count()) 
		 return Interval(keys[0].time,keys[keys.Count()-1].time);
	else return range;
	}


int BoolControl::GetSelKeyCoords(TimeValue &t, float &val,DWORD flags) 
	{
	BOOL tfound=FALSE, tuncommon = FALSE;
	int n = keys.Count(), res;
	TimeValue atime;	
	
	for (int i = 0; i < n; i++ ) {		
		if (keys[i].flags & KEY_SELECTED) {
			if (tfound) {
				if (keys[i].time!=atime) {
					return KEYS_MULTISELECTED;
					}
			} else {
				tfound = TRUE;
				atime = keys[i].time;
				}
			}		
		if (tuncommon) return KEYS_MULTISELECTED;
		}
	t   = atime;	
	res = 0;
	if (tfound && !tuncommon) {
		res	|= KEYS_COMMONTIME;
		}	
	if (!tfound) {
		res = KEYS_NONESELECTED;
		}
	return res;
	}

int BoolControl::SetSelKeyCoordsExpr(
		ParamDimension *dim,
		TCHAR *timeExpr, TCHAR *valExpr, DWORD flags)
	{
	Expr texpr;
	float tfin, tfout=0.0f;

	if (timeExpr) {
		texpr.defVar(SCALAR_VAR,KEYCOORDS_TIMEVAR);
		if (texpr.load(timeExpr)!=EXPR_NORMAL) return KEYCOORDS_EXPR_ERROR;		
		}

	int n = keys.Count();
	if (!n) return KEYCOORDS_EXPR_OK;
	HoldTrack();

	for (int i=0; i<n; i++) {
		if (!(flags&KEYCOORDS_VALUEONLY)) {			
			if (keys[i].flags & KEY_SELECTED) {
				tfin = float(keys[i].time)/float(GetTicksPerFrame());
				texpr.eval(&tfout, 1, &tfin);
				keys[i].time = int(tfout*GetTicksPerFrame());
				}
			}
		}
	valid.SetEmpty();
	SortKeys();
	NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
	return KEYCOORDS_EXPR_OK;
	}

void BoolControl::SetSelKeyCoords(TimeValue t, float val,DWORD flags)
	{
	int n = keys.Count();
	if (!n) return;
	HoldTrack();
	for (int i = 0; i < n; i++ ) {		
		if (!(flags&KEYCOORDS_VALUEONLY)) {			
			if (keys[i].flags & KEY_SELECTED) {
				keys[i].time = t;
				}		
			}
		}	
	valid.SetEmpty();
	SortKeys();
	NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
	}

void BoolControl::MapKeys(TimeMap *map,DWORD flags)
	{
	int n = keys.Count();
	BOOL changed = FALSE;
	if (!n) return;
	HoldTrack();

	if (flags&TRACK_MAPRANGE) {		
		TimeValue t0 = map->map(range.Start());
		TimeValue t1 = map->map(range.End());
		range.Set(t0,t1);
		changed = TRUE;
		}	

	if (flags&TRACK_DOALL) {
		for (int i=0; i<n; i++) {			
			keys[i].time = map->map(keys[i].time);
			changed = TRUE;
			}
	} else 
	if (flags&TRACK_DOSEL) {
		BOOL slide = flags&TRACK_SLIDEUNSEL;
		TimeValue delta = 0, prev;
		int start, end, inc;
		if (flags&TRACK_RIGHTTOLEFT) {
			start = n-1;
			end = -1;
			inc = -1;
		} else {
			start = 0;
			end = n;
			inc = 1;
			} 
		for (int i=start; i!=end; i+=inc) {			
			if (keys[i].flags & KEY_SELECTED) {
				prev = keys[i].time;
				keys[i].time = map->map(keys[i].time);
				delta = keys[i].time - prev;
				changed = TRUE;
			} else if (slide) {
				keys[i].time += delta;
				}
			}
		}
	
	if (changed) {
		valid.SetEmpty();
		SortKeys();
		NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
		}
	}


int BoolControl::GetKeyIndex(TimeValue t)
	{
	for (int i=0; i<keys.Count(); i++) {
		if (keys[i].time==t) return i;
		if (keys[i].time<t) return -1;
		}
	return -1;
	}

void BoolControl::DeleteKeyAtTime(TimeValue t)
	{
	int index = GetKeyIndex(t);
	if (index>=0) {
		HoldTrack();
		keys.Delete(index,1);
		valid.SetEmpty();
		NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
		}
	}

BOOL BoolControl::IsKeyAtTime(TimeValue t,DWORD flags)
	{
	for (int i=0; i<keys.Count(); i++) {
		if (keys[i].time>t) return FALSE;
		if (keys[i].time==t) return TRUE;		
		}
	return FALSE;
	}

void BoolControl::DeleteTime(Interval iv, DWORD flags)
	{
	Interval test = TestInterval(iv,flags);
	int n = keys.Count();	
	int d = iv.Duration()-1;
	if (d<0) d = 0;
	HoldTrack();

	for (int i = n-1; i >= 0; i--) {
		if (test.InInterval(keys[i].time)) {
			keys.Delete(i,1);
		} else 
		if (!(flags&TIME_NOSLIDE)) {			
			if (keys[i].time > test.End()) {
				keys[i].time -= d;
				}
			}
		}		    
	
	valid.SetEmpty();
	SortKeys();
	NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
	}

void BoolControl::ReverseTime(Interval iv, DWORD flags)
	{
	Interval test = TestInterval(iv,flags);
	int n = keys.Count();
	HoldTrack();

	for (int i = 0; i < n; i++) {		
		if (test.InInterval(keys[i].time)) {
			TimeValue delta = keys[i].time - iv.Start();
			keys[i].time = iv.End()-delta;			
			}
		}
	valid.SetEmpty();
	SortKeys();
	NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
	}

void BoolControl::ScaleTime(Interval iv, float s)
	{
	int n = keys.Count();
	TimeValue delta = int(s*float(iv.End()-iv.Start())) + iv.Start()-iv.End();
	HoldTrack();

	for (int i = 0; i < n; i++) {		
		if (iv.InInterval(keys[i].time)) {
			keys[i].time = 
				int(s*float(keys[i].time - iv.Start())) + iv.Start();
		} else 
		if (keys[i].time > iv.End()) {
			keys[i].time += delta;
			}
		}
	valid.SetEmpty();
	SortKeys();
	NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
	}

void BoolControl::InsertTime(TimeValue ins, TimeValue amount)
	{
	int n = keys.Count();		
	HoldTrack();

	for (int i = 0; i < n; i++) {		
		if (keys[i].time >= ins) {
			keys[i].time += amount;
			}		
		}
	valid.SetEmpty();
	SortKeys();
	NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
	}

void BoolControl::DeleteKeys(DWORD flags)
	{
	int n = keys.Count();		
	HoldTrack();
	
	for (int i = n-1; i >= 0; i--) {
		if (flags&TRACK_DOALL || keys[i].flags&KEY_SELECTED) {
			keys.Delete(i,1);
			}
		}	
	valid.SetEmpty();
	NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
	}

void BoolControl::DeleteKeyByIndex(int index)
	{
	HoldTrack();
	keys.Delete(index,1);
	NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
	}

void BoolControl::SelectKeyByIndex(int i,BOOL sel)
	{
	HoldTrack();
	if (sel) keys[i].flags |=  KEY_SELECTED;
	else     keys[i].flags &= ~KEY_SELECTED;
	NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
	}

void BoolControl::SelectKeys(TrackHitTab& sel, DWORD flags)
	{
	HoldTrack();
	
	if (flags&SELKEYS_CLEARKEYS) {
		int n = keys.Count();
		for (int i = 0; i < n; i++ ) {
			keys[i].flags &= ~KEY_SELECTED;
			}
		}
	
	if (flags&SELKEYS_DESELECT) {
		for (int i = 0; i < sel.Count(); i++ ) {			
			keys[sel[i].hit].flags &= ~KEY_SELECTED;
			}		
		} 	
	if (flags&SELKEYS_SELECT) {			
		for (int i = 0; i < sel.Count(); i++ ) {
			keys[sel[i].hit].flags |= KEY_SELECTED;
			}
		}	
	NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
	}

void BoolControl::FlagKey(TrackHitRecord hit)
	{
	int n = keys.Count();
	for (int i = 0; i < n; i++) {
		keys[i].flags &= ~KEY_FLAGGED;
		}
	assert(hit.hit>=0&&hit.hit<(DWORD)n);
	keys[hit.hit].flags |= KEY_FLAGGED;
	}

int BoolControl::GetFlagKeyIndex()
	{
	int n = keys.Count();
	for (int i = 0; i < n; i++) {
		if (keys[i].flags & KEY_FLAGGED) {
			return i;
			}
		}
	return -1;
	}

int BoolControl::NumSelKeys()
	{
	int n = keys.Count();
	int c = 0;
	for ( int i = 0; i < n; i++ ) {
		if (keys[i].flags & KEY_SELECTED) {
			c++;
			}
		}
	return c;
	}

void BoolControl::CloneSelectedKeys(BOOL offset)
	{
	int n = keys.Count();			
	HoldTrack();
	BOOL changed = FALSE;

	for (int i = 0; i < n; i++) {
		if (keys[i].flags & KEY_SELECTED) {
			BKey key(keys[i].time);
			key.flags |= KEY_SELECTED;
			keys.Append(1,&key,5);
			keys[i].flags &= ~KEY_SELECTED;
			changed = TRUE;
			}
		}
	if (changed) {
		keys.Shrink();
		valid.SetEmpty();
		SortKeys();
		}
	}

void BoolControl::AddNewKey(TimeValue t,DWORD flags)
	{
	HoldTrack();
	BKey key(t);
	if (flags&ADDKEY_SELECT) {
		key.flags |= KEY_SELECTED;
		}
	if (flags&ADDKEY_FLAGGED) {
		int n = keys.Count();
		for (int i = 0; i < n; i++) {
			keys[i].flags &= ~KEY_FLAGGED;
			}		
		key.flags |= KEY_FLAGGED;
		}
	keys.Append(1,&key);
	valid.SetEmpty();
	SortKeys();
	NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);	
	}

BOOL BoolControl::IsKeySelected(int index)
	{
	return keys[index].flags & KEY_SELECTED;
	}

TrackClipObject *BoolControl::CopyTrack(Interval iv, DWORD flags)
	{	
	BoolClipObject *cobj = new BoolClipObject(iv);	
	Interval test = TestInterval(iv,flags);	
	for (int i = 0; i < keys.Count(); i++) {
		if (test.InInterval(keys[i].time)) {
			BKey nk(keys[i].time);
			cobj->tab.Append(1,&nk,10);
			}
		}
	cobj->tab.Shrink();
	return cobj;
	}

void BoolControl::PasteTrack(TrackClipObject *cobj,Interval iv, DWORD flags)
	{
	BoolClipObject *cob = (BoolClipObject*)cobj;	
	HoldTrack();		
	DeleteTime(iv,flags);	
	InsertTime(iv.Start(),cob->clip.Duration()-1);	
	for (int i = 0; i < cob->tab.Count(); i++) {
		BKey key(cob->tab[i].time);
		key.time -= cob->clip.Start() - iv.Start();
		keys.Append(1,&key);		
		}	
	valid.SetEmpty();
	SortKeys();
	NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
	}

int BoolControl::HitTestTrack(			
		TrackHitTab& hits,
		Rect& rcHit,
		Rect& rcTrack,			
		float zoom,
		int scroll,
		DWORD flags)
	{
	int left  = ScreenToTime(rcTrack.left,zoom,scroll) - 4;
	int right = ScreenToTime(rcTrack.right,zoom,scroll) + 4;
	int n = keys.Count();
	int y = (rcTrack.top+rcTrack.bottom)/2;	
		
	for ( int i = 0; i < n; i++ ) {
		if (flags&HITTRACK_SELONLY && 
			!(keys[i].flags & KEY_SELECTED)) continue;
		if (flags&HITTRACK_UNSELONLY && 
			(keys[i].flags & KEY_SELECTED)) continue;

		if (keys[i].time > right) {
			break;
			}
		if (keys[i].time > left) {
			int x = TimeToScreen(keys[i].time,zoom,scroll);
			if (rcHit.Contains(IPoint2(x,y))) {
				TrackHitRecord rec(i,0);
				hits.Append(1,&rec);
				if (flags&HITTRACK_ABORTONHIT) return TRACK_DONE;
				}
			}		
		}
	return TRACK_DONE;
	}

int BoolControl::PaintTrack(			
		ParamDimensionBase *dim,
		HDC hdc,
		Rect& rcTrack,
		Rect& rcPaint,
		float zoom,
		int scroll,
		DWORD flags)
	{
	int left  = ScreenToTime(rcPaint.left-8,zoom,scroll);
	int right = ScreenToTime(rcPaint.right+8,zoom,scroll);
	int n = keys.Count();
	int y = (rcTrack.top+rcTrack.bottom)/2, lx, x = rcPaint.left-2;
	HBRUSH selBrush   = CreateSolidBrush(RGB(255,255,255));
	HBRUSH barBrush   = CreateSolidBrush(RGB(113,160,231)); //RGB(80,100,180)
	HBRUSH unselBrush = (HBRUSH)GetStockObject(GRAY_BRUSH);	
	BOOL state = startOnOff;
	SelectObject(hdc,GetStockObject(BLACK_PEN));
	SelectObject(hdc,barBrush);
	
	for (int i = 0; i < n; i++) {		
		lx = x;
		x  = TimeToScreen(keys[i].time,zoom,scroll);
		if (keys[i].time > left) {			
			if (state) {							
				Rectangle(hdc,lx,y-4,x,y+4);
				}
			}		
		state = !state;
		if (keys[i].time > right) {
			break;
			}
		}
	if (state) {		
		Rectangle(hdc,x,y-4,rcPaint.right,y+4);
		}
	
	for (i = 0; i < n; i++) {
		if (keys[i].time > right) {
			break;
			}
		if (keys[i].time > left) {			
			x  = TimeToScreen(keys[i].time,zoom,scroll);
			if ((flags&PAINTTRACK_SHOWSEL) && (keys[i].flags&KEY_SELECTED)) {
				SelectObject(hdc,selBrush);
			} else {
				SelectObject(hdc,unselBrush);
				}
			Ellipse(hdc,x-4,y-5,x+4,y+5);
			}		
		}

	DeleteObject(selBrush);
	DeleteObject(barBrush);
	return TRACK_DONE;
	}


void BoolControl::GetValue(
		TimeValue t, void *val, Interval &valid, GetSetMethod method)
	{
	Interval wvalid = FOREVER;	
	float *v = (float*)val;
			
	t = ApplyEase(t,wvalid);	
	
 	if (!this->valid.InInterval(t)) {
		onOff = startOnOff;
		for (int i=0; i<keys.Count(); i++) {
			if (keys[i].time>t) break;
			onOff = !onOff;
			}
		if (keys.Count()) {
			if (t < keys[0].time) {
				this->valid.Set(TIME_NegInfinity,keys[0].time-1);
			} else 
			if (t > keys[keys.Count()-1].time) {
				this->valid.Set(keys[keys.Count()-1].time,TIME_PosInfinity);
			} else this->valid.SetInstant(t);
		} else {
			this->valid = FOREVER;
			}
		}
	valid &= this->valid;
	*v = onOff ? 1.0f : -1.0f;
	}

void BoolControl::SetValueLocalTime(
		TimeValue t, void *val, int commit, GetSetMethod method)
	{
	float *v = (float*)val;
	float tv;

	GetValue(t,&tv,FOREVER);
	if (MAKEBOOL(tv) == MAKEBOOL(*v)) {
		return;
		}

	if (Animating()) {		
		int index = GetKeyIndex(t);
		if (index>=0) {
			keys.Delete(index,1);
			keys.Shrink();
		} else {
			BKey k(t);
			keys.Append(1,&k);
			SortKeys();
			}
	} else {
		if (keys.Count()) {			
			startOnOff = !startOnOff;
		} else {
			onOff = startOnOff = MAKEBOOL(*v);			
			}
		}
	
	valid.SetEmpty();
	NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
	}

void BoolControl::Extrapolate(
		Interval range,TimeValue t,void *val,Interval &valid,int type)
	{	
	GetValue(t,val,valid);	
	}

static int __cdecl CompareBKeys(const BKey *k1, const BKey *k2)
	{
	if (k1->time < k2->time) return -1;
	if (k1->time > k2->time) return 1;
	return 0;
	}

void BoolControl::SortKeys()
	{
	keys.Sort((CompareFnc)CompareBKeys);
	}

#endif